diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index d83c3d2d..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,14 +0,0 @@ -### If this is a support request: - -**Please attempt to solve the problem on your own before opening an issue.** -Between old issues, StackOverflow, and Google, you should be able to find -solutions to most of the common problems. - -Include at least: -1. Steps to reproduce the issue (e.g. the command you ran) -2. The unexpected behavior that occurred (e.g. error messages or screenshots) -3. The environment (e.g. operating system and version of manim) - - -### If this is a feature request: -Include the motivation for making this change. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..bad3125e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,20 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +### Describe the bug + + +**Code**: + + +**Wrong display or Error traceback**: + + +### Additional context + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..5581a02a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Ask A Question + url: https://github.com/3b1b/manim/discussions/categories/q-a + about: Please ask questions you encountered here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/error-when-using.md b/.github/ISSUE_TEMPLATE/error-when-using.md new file mode 100644 index 00000000..0686dfd3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/error-when-using.md @@ -0,0 +1,23 @@ +--- +name: Error when using +about: The error you encountered while using manim +title: '' +labels: '' +assignees: '' + +--- + +### Describe the error + + +### Code and Error +**Code**: + + +**Error**: + + +### Environment +**OS System**: +**manim version**: master +**python version**: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index cd810c78..3ca8f474 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,9 +1,18 @@ -Thanks for contributing to manim! + -**Please ensure that your pull request works with the latest version of manim.** -You should also include: +## Motivation + -1. The motivation for making this change (or link the relevant issues) -2. How you tested the new behavior (e.g. a minimal working example, before/after -screenshots, gifs, commands, etc.) This is rather informal at the moment, but -the goal is to show us how you know the pull request works as intended. +## Proposed changes + +- +- +- + +## Test + +**Code**: + +**Result**: \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..b5ca3727 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,40 @@ +name: docs + +on: + push: + paths: + - 'docs/**' + pull_request: + paths: + - 'docs/**' + +jobs: + docs: + runs-on: ubuntu-latest + name: build up document and deploy + + steps: + - name: Checkout + uses: actions/checkout@master + + - name: Install sphinx and manim env + run: | + pip3 install --upgrade pip + sudo apt install python3-setuptools + pip3 install -r docs/requirements.txt + pip3 install -r requirements.txt + + - name: Build document with Sphinx + run: | + cd docs + export PATH="$PATH:/home/runner/.local/bin" + export SPHINXBUILD="python3 -m sphinx" + make html + + - name: Deploy to GitHub pages + if: ${{ github.event_name == 'push' }} + uses: JamesIves/github-pages-deploy-action@3.7.1 + with: + ACCESS_TOKEN: ${{ secrets.DOC_DEPLOY_TOKEN }} + BRANCH: gh-pages + FOLDER: docs/build/html \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..22f2974e --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,30 @@ +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* \ No newline at end of file diff --git a/.gitignore b/.gitignore index b59123d5..d9ccaad8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,28 +1,154 @@ -*.pyc -*.bak -.DS_Store -homeless.py -playground.py -cairo_test.py -mayavi_test.py -random_scenes/ -files/ -assets/ -ben_playground.py -ben_cairo_test.py -.floo -.flooignore -.vscode -.vs -*.xml -*.iml -media -manim.sublime-project -manim.sublime-workspace -.eggs/ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python build/ +develop-eggs/ dist/ manimlib.egg-info/ primes.py /media_dir.txt +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# profiling data +.prof + +# End of https://www.toptal.com/developers/gitignore/api/python +# Custom exclusions: +.DS_Store + +# For manim +/videos +/custom_config.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 76118118..00000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: python -dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069) -python: "3.7" -cache: pip - -addons: - apt: - packages: - - python3-sphinx -install: - - pip install --upgrade pip - - pip install -r requirements.txt - - pip install flake8 -before_script: - # stop the build if there are Python syntax errors or undefined names - - flake8 manimlib/ --count --select=E9,F63,F72,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - - flake8 manimlib/ --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics -script: - - python setup.py test - - python setup.py bdist_wheel -after_success: - - test $TRAVIS_BRANCH = "master" && test $TRAVIS_PULL_REQUEST = "false" && travis/build_docs.sh -deploy: - provider: pypi - user: eulertour - on: - tags: true - password: - secure: j5M2hiJo9kDWJhl0/iSuIQmfd2G2O1Qoc455AkUPMCheAcALnX9xJgFsYBmqfgOXTCtUCQf52XGdOIG4o4s5TY340NZ9eLKI9cWae+sTeSrDCkdwChUilm3D0jQf1FWPUf9ywScwGi20m0sRtzxEJyTuX+JMFd7PIa8bFoDXWPtEjoFOOJrfBusMsANzrI+j+vIMdJ48lc1J8UsQdZapwusTrYU9s12JLhKBPLavmaDKf0HDAJdEhFQ9SaINdkiW/QY8qbfJ/MVu5jHai168zXjD/IaswxoKqCO1G+fWlOq3KwVhG7gI7rwhnnuF+wcA7yLAaMdo0CjO2V7z15S6cG721V2Il2IIh1jq0F8irSH1ZOLOkv/fFk9hkSUQyEU0i8k4m1wE9L47a6GP/66+b+gI91PGfxBOqq4gE/1BdZJqceh0qc13KpcehtYrQwR05bSw0Ye5OoTkqAnCeON0B0Ur4ejfHd3TzkjgB06fw76cZtjAK8f/YjB3KyNCvysOixgzE4tRxlY92yX/tAKZ3iX3yD0MjsinSfwo52N5sIEaCS/FmPRMhJOQBa6ftkfbcUNQBTG9G3b134XXF/LbC4vBloCaTm5VSXagta+oY3SFKQxPAZXx7X+wcFGjqxDjZXG1e66QnA2JJH4aBDsRfSXmUtD8MblwFYdcCJWz+Ck= diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 5168bb18..00000000 --- a/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM python:3.7 -RUN apt-get update \ - && apt-get install -qqy --no-install-recommends \ - apt-utils \ - ffmpeg \ - sox \ - libcairo2-dev \ - texlive \ - texlive-fonts-extra \ - texlive-latex-extra \ - texlive-latex-recommended \ - texlive-science \ - tipa \ - && rm -rf /var/lib/apt/lists/* -COPY . /manim -RUN cd /manim \ - && python setup.py sdist \ - && python -m pip install dist/manimlib* -ENTRYPOINT ["/bin/bash"] diff --git a/LICENSE b/LICENSE.md similarity index 80% rename from LICENSE rename to LICENSE.md index be67c8db..8860bd19 100644 --- a/LICENSE +++ b/LICENSE.md @@ -1,10 +1,6 @@ -All files of this project under the directory "from_3b1b" are copyright 3Blue1Brown LLC and used by permission for this project only. - -Any other file of this project is available under the MIT license as follow: - MIT License -Copyright (c) 2018 3Blue1Brown LLC +Copyright (c) 2020 3Blue1Brown LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 81d81395..98e4b50c 100644 --- a/README.md +++ b/README.md @@ -1,155 +1,114 @@ -![logo](logo/cropped.png) +

+ + + +

-[![Build Status](https://travis-ci.org/3b1b/manim.svg?branch=master)](https://travis-ci.org/3b1b/manim) -[![Documentation](https://img.shields.io/badge/docs-EulerTour-blue.svg)](https://www.eulertour.com/learn/manim/) +[![pypi version](https://img.shields.io/pypi/v/manimgl?logo=pypi)](https://pypi.org/project/manimgl/) [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://choosealicense.com/licenses/mit/) -[![Manim Subreddit](https://img.shields.io/reddit/subreddit-subscribers/manim.svg?color=ff4301&label=reddit)](https://www.reddit.com/r/manim/) -[![Manim Discord](https://img.shields.io/discord/581738731934056449.svg?label=discord)](https://discord.gg/mMRrZQW) +[![Manim Subreddit](https://img.shields.io/reddit/subreddit-subscribers/manim.svg?color=ff4301&label=reddit&logo=reddit)](https://www.reddit.com/r/manim/) +[![Manim Discord](https://img.shields.io/discord/581738731934056449.svg?label=discord&logo=discord)](https://discord.gg/mMRrZQW) +[![docs](https://github.com/3b1b/manim/workflows/docs/badge.svg)](https://3b1b.github.io/manim/) -Manim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos at [3Blue1Brown](https://www.3blue1brown.com/). +Manim is an engine for precise programatic animations, designed for creating explanatory math videos. + +Note, there are two versions of manim. This repository began as a personal project by the author of [3Blue1Brown](https://www.3blue1brown.com/) for the purpose of animating those videos, with video-specific code available [here](https://github.com/3b1b/videos). In 2020 a group of developers forked it into what is now the [community edition](https://github.com/ManimCommunity/manim/), with a goal of being more stable, better tested, quicker to respond to community contributions, and all around friendlier to get started with. You can engage with that community by joining the discord. + +Since the fork, this version has evolved to work on top of OpenGL, and allows real-time rendering to an interactive window before scenes are finalized and written to a file. ## Installation -Manim runs on Python 3.7. You can install it from PyPI via pip: +Manim runs on Python 3.6 or higher (Python 3.8 is recommended). + +System requirements are [FFmpeg](https://ffmpeg.org/), [OpenGL](https://www.opengl.org/) and [LaTeX](https://www.latex-project.org) (optional, if you want to use LaTeX). +For Linux, [Pango](https://pango.gnome.org) along with it's developerment headers are required. See instruction [here](https://github.com/ManimCommunity/ManimPango#building). + +### Directly ```sh -pip3 install manimlib -``` +# Install manimgl +pip install manimgl -System requirements are [cairo](https://www.cairographics.org), [ffmpeg](https://www.ffmpeg.org), [sox](http://sox.sourceforge.net), [latex](https://www.latex-project.org) (optional, if you want to use LaTeX). - -You can now use it via the `manim` command. For example: - -```sh -manim my_project.py MyScene +# Try it out +manimgl ``` For more options, take a look at the [Using manim](#using-manim) sections further below. -### Directly - If you want to hack on manimlib itself, clone this repository and in that directory execute: ```sh -# Install python requirements -python3 -m pip install -r requirements.txt +# Install manimgl +pip install -e . # Try it out -python3 ./manim.py example_scenes.py SquareToCircle -pl +manimgl example_scenes.py OpeningManimExample +# or +manim-render example_scenes.py OpeningManimExample ``` ### Directly (Windows) + 1. [Install FFmpeg](https://www.wikihow.com/Install-FFmpeg-on-Windows). -2. [Install Cairo](https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycairo). For most users, ``pycairo‑1.18.0‑cp37‑cp37m‑win32.whl`` will do fine. - ```sh - pip3 install C:\path\to\wheel\pycairo‑1.18.0‑cp37‑cp37m‑win32.whl - ``` -3. Install a LaTeX distribution. [MiKTeX](https://miktex.org/download) is recommended. - -4. [Install SoX](https://sourceforge.net/projects/sox/files/sox/). - -5. Install the remaining Python packages. Make sure that ``pycairo==1.17.1`` is changed to ``pycairo==1.18.0`` in requirements.txt. +2. Install a LaTeX distribution. [MiKTeX](https://miktex.org/download) is recommended. +3. Install the remaining Python packages. ```sh git clone https://github.com/3b1b/manim.git cd manim - pip3 install -r requirements.txt - python3 manim.py example_scenes.py SquareToCircle -pl + pip install -e . + manimgl example_scenes.py OpeningManimExample ``` +### Mac OSX + +1. Install FFmpeg, LaTeX in terminal using homebrew. + ```sh + brew install ffmpeg mactex + ``` + +2. Install latest version of manim using these command. + ```sh + git clone https://github.com/3b1b/manim.git + cd manim + pip install -e . + manimgl example_scenes.py OpeningManimExample + ``` ## Anaconda Install -* Install sox and latex as above. -* Create a conda environment using `conda env create -f environment.yml` -* **WINDOWS ONLY** Install `pyreadline` via `pip install pyreadline`. +1. Install LaTeX as above. +2. Create a conda environment using `conda create -n manim python=3.8`. +3. Activate the environment using `conda activate manim`. +4. Install manimgl using `pip install -e .`. -### Using `virtualenv` and `virtualenvwrapper` -After installing `virtualenv` and `virtualenvwrapper` -```sh -git clone https://github.com/3b1b/manim.git -mkvirtualenv -a manim -r requirements.txt manim -python3 -m manim example_scenes.py SquareToCircle -pl -``` - -### Using Docker -Since it's a bit tricky to get all the dependencies set up just right, there is a Dockerfile and Compose file provided in this repo as well as [a premade image on Docker Hub](https://hub.docker.com/r/eulertour/manim/tags/). The Dockerfile contains instructions on how to build a manim image, while the Compose file contains instructions on how to run the image. - -The prebuilt container image has manim repository included. -`INPUT_PATH` is where the container looks for scene files. You must set the `INPUT_PATH` -environment variable to the absolute path containing your scene file and the -`OUTPUT_PATH` environment variable to the directory where you want media to be written. - -1. [Install Docker](https://docs.docker.com) -2. [Install Docker Compose](https://docs.docker.com/compose/install/) -3. Render an animation: -```sh -INPUT_PATH=/path/to/dir/containing/source/code \ -OUTPUT_PATH=/path/to/output/ \ -docker-compose run manim example_scenes.py SquareToCircle -l -``` -The command needs to be run as root if your username is not in the docker group. - -You can replace `example.scenes.py` with any relative path from your `INPUT_PATH`. - -![docker diagram](./manim_docker_diagram.png) - -After running the output will say files ready at `/tmp/output/`, which refers to path inside the container. Your `OUTPUT_PATH` is bind mounted to this `/tmp/output` so any changes made by the container to `/tmp/output` will be mirrored on your `OUTPUT_PATH`. `/media/` will be created in `OUTPUT_PATH`. - -`-p` won't work as manim would look for video player in the container system, which it does not have. - -The first time you execute the above command, Docker will pull the image from Docker Hub and cache it. Any subsequent runs until the image is evicted will use the cached image. -Note that the image doesn't have any development tools installed and can't preview animations. Its purpose is building and testing only. - ## Using manim Try running the following: ```sh -python3 -m manim example_scenes.py SquareToCircle -pl +manimgl example_scenes.py OpeningManimExample ``` -The `-p` flag in the command above is for previewing, meaning the video file will automatically open when it is done rendering. The `-l` flag is for a faster rendering at a lower quality. +This should pop up a window playing a simple scene. -Some other useful flags include: +Some useful flags include: +* `-w` to write the scene to a file +* `-o` to write the scene to a file and open the result * `-s` to skip to the end and just show the final frame. + * `-so` will save the final frame to an image and show it * `-n ` to skip ahead to the `n`'th animation of a scene. -* `-f` to show the file in finder (for OSX). +* `-f` to make the playback window fullscreen -Set `MEDIA_DIR` environment variable to specify where the image and animation files will be written. +Take a look at custom_config.yml for further configuration. To add your customization, you can either edit this file, or add another file by the same name "custom_config.yml" to whatever directory you are running manim from. For example [this is the one](https://github.com/3b1b/videos/blob/master/custom_config.yml) for 3blue1brown videos. There you can specify where videos should be output to, where manim should look for image files and sounds you want to read in, and other defaults regarding style and video quality. -Look through the `old_projects` folder to see the code for previous 3b1b videos. Note, however, that developments are often made to the library without considering backwards compatibility with those old projects. To run an old project with a guarantee that it will work, you will have to go back to the commit which completed that project. - -While developing a scene, the `-sp` flags are helpful to just see what things look like at the end without having to generate the full animation. It can also be helpful to use the `-n` flag to skip over some number of animations. +Look through the [example scenes](https://3b1b.github.io/manim/getting_started/example_scenes.html) to get a sense of how it is used, and feel free to look through the code behind [3blue1brown videos](https://github.com/3b1b/videos) for a much larger set of example. Note, however, that developments are often made to the library without considering backwards compatibility with those old videos. To run an old project with a guarantee that it will work, you will have to go back to the commit which completed that project. ### Documentation -Documentation is in progress at [eulertour.com/learn/manim](https://www.eulertour.com/learn/manim/). +Documentation is in progress at [3b1b.github.io/manim](https://3b1b.github.io/manim/). And there is also a Chinese version maintained by **@manim-kindergarten**: [manim.ml](https://manim.ml/) (in Chinese). -### Walkthrough -Todd Zimmerman put together a [tutorial](https://talkingphysics.wordpress.com/2019/01/08/getting-started-animating-with-manim-and-python-3-7/) on getting started with manim, which has been updated to run on Python 3.7. - -### Live Streaming -To live stream your animations, simply run manim with the `--livestream` option. - -```sh -> python -m manim --livestream -Writing to media/videos/scene/scene/1080p30/LiveStreamTemp.mp4 - -Manim is now running in streaming mode. Stream animations by passing -them to manim.play(), e.g. ->>> c = Circle() ->>> manim.play(ShowCreation(c)) - ->>> -``` - -It is also possible to stream directly to Twitch. To do that simply pass -`--livestream` and `--to-twitch to manim` and specify the stream key with -`--with-key`. Then when you follow the above example the stream will directly -start on your Twitch channel (with no audio support). +[manim-kindergarten](https://github.com/manim-kindergarten/) wrote and collected some useful extra classes and some codes of videos in [manim_sandbox repo](https://github.com/manim-kindergarten/manim_sandbox). ## Contributing -Is always welcome. In particular, there is a dire need for tests and documentation. +Is always welcome. As mentioned above, the [community edition](https://github.com/ManimCommunity/manim) has the most active ecosystem for contributions, with testing and continuous integration, but pull requests are welcome here too. Please explain the motivation for a given change and examples of its effect. ## License -All files in the directory `from_3b1b`, which by and large generate the visuals for 3b1b videos, are copyright 3Blue1Brown. - -The general purpose animation code found in the remainder of the repository, on the other hand, is under the MIT license. +This project falls under the MIT license. diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 4a25a0dc..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,16 +0,0 @@ -version: '3.1' - -services: - manim: - # comment this line if you build the image to prevent overwriting the tag - image: eulertour/manim:latest - # uncomment this line to build rather than pull the image - # build: . - entrypoint: - - manim - - --media_dir=/tmp/output - volumes: - - ${INPUT_PATH:?INPUT_PATH environment variable isn't set}:/tmp/input - - ${OUTPUT_PATH:?OUTPUT_PATH environment variable isn't set}:/tmp/output - working_dir: /tmp/input - network_mode: "none" diff --git a/docs/Makefile b/docs/Makefile index 69fe55ec..d0c3cbf1 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,9 +1,10 @@ # Minimal makefile for Sphinx documentation # -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build @@ -16,4 +17,4 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/example.py b/docs/example.py new file mode 100644 index 00000000..279f0703 --- /dev/null +++ b/docs/example.py @@ -0,0 +1,37 @@ +from manimlib.imports import * + +class SquareToCircle(Scene): + def construct(self): + circle = Circle() + circle.set_fill(BLUE, opacity=0.5) + circle.set_stroke(BLUE_E, width=4) + square = Square() + + self.play(ShowCreation(square)) + self.wait() + self.play(ReplacementTransform(square, circle)) + self.wait() + # Try typing the following lines + # self.play(circle.animate.stretch(4, dim=0)) + # self.play(Rotate(circle, TAU / 4)) + # self.play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25)) + # circle.insert_n_curves(10) + # self.play(circle.animate.apply_complex_function(lambda z: z**2)) + +class SquareToCircleEmbed(Scene): + def construct(self): + circle = Circle() + circle.set_fill(BLUE, opacity=0.5) + circle.set_stroke(BLUE_E, width=4) + + self.add(circle) + self.wait() + self.play(circle.animate.stretch(4, dim=0)) + self.wait(1.5) + self.play(Rotate(circle, TAU / 4)) + self.wait(1.5) + self.play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25)) + self.wait(1.5) + circle.insert_n_curves(10) + self.play(circle.animate.apply_complex_function(lambda z: z**2)) + self.wait(2) diff --git a/docs/make.bat b/docs/make.bat index 543c6b13..9534b018 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,35 +1,35 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..d8704109 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +Sphinx==3.0.3 +sphinx-copybutton +furo==2020.10.5b9 +Jinja2 \ No newline at end of file diff --git a/docs/source/_static/colors.css b/docs/source/_static/colors.css new file mode 100644 index 00000000..c5b69708 --- /dev/null +++ b/docs/source/_static/colors.css @@ -0,0 +1,293 @@ +p.color-text { + font-size: inherit; + font-family: var(--font-stack--monospace); + margin-top: 25px; + color: WHITE; +} + +p.color-text-small { + font-size: small; + font-family: var(--font-stack--monospace); + margin-top: 28px; + color: WHITE; +} + +.colors { + float: left; + padding: 10px; + border: 10px; + margin: 0; + width: 80px; + height: 80px; + text-align: center; +} + +.BLUE_A { + background: #C7E9F1; + color:#C7E9F1; +} + +.BLUE_B { + background: #9CDCEB; + color:#9CDCEB; +} + +.BLUE_C { + background: #58C4DD; + color:#58C4DD; +} + +.BLUE_D { + background: #29ABCA; + color:#29ABCA; +} + +.BLUE_E { + background: #1C758A; + color:#1C758A; +} + +.TEAL_A { + background: #ACEAD7; + color:#ACEAD7 ; +} + +.TEAL_B { + background: #76DDC0; + color: #76DDC0; +} + +.TEAL_C { + background: #5CD0B3; + color: #5CD0B3; +} + +.TEAL_D { + background: #55C1A7; + color: #55C1A7; +} + +.TEAL_E { + background: #49A88F; + color: #49A88F; +} + +.GREEN_A { + background: #C9E2AE; + color: #C9E2AE; +} + +.GREEN_B { + background: #A6CF8C; + color: #A6CF8C; +} + +.GREEN_C { + background: #83C167; + color: #83C167; +} + +.GREEN_D { + background: #77B05D; + color: #77B05D; +} + +.GREEN_E { + background: #699C52; + color: #699C52; +} + +.YELLOW_A { + background: #FFF1B6; + color: #FFF1B6; +} + +.YELLOW_B { + background: #FFEA94; + color:#FFEA94 ; +} + +.YELLOW_C { + background: #FFFF00; + color: #FFFF00; +} + +.YELLOW_D { + background: #F4D345; + color: #F4D345; +} + +.YELLOW_E { + background: #E8C11C; + color: #E8C11C; +} + +.GOLD_A { + background: #F7C797; + color:#F7C797; +} + +.GOLD_B { + background: #F9B775; + color:#F9B775; +} + +.GOLD_C { + background: #F0AC5F; + color:#F0AC5F; +} + +.GOLD_D { + background: #E1A158; + color:#E1A158; +} + +.GOLD_E { + background: #C78D46; + color:#C78D46; +} + +.RED_A { + background: #F7A1A3; + color:#F7A1A3; +} + +.RED_B { + background: #FF8080; + color:#FF8080; +} + +.RED_C { + background: #FC6255; + color:#FC6255; +} + +.RED_D { + background: #E65A4C; + color:#E65A4C; +} + +.RED_E { + background: #CF5044; + color:#CF5044; +} + +.MAROON_A { + background: #ECABC1; + color: #ECABC1; +} + +.MAROON_B { + background: #EC92AB; + color: #EC92AB; +} + +.MAROON_C { + background: #C55F73; + color: #C55F73; +} + +.MAROON_D { + background: #A24D61; + color: #A24D61; +} + +.MAROON_E { + background: #94424F; + color: #94424F; +} + +.PURPLE_A { + background: #CAA3E8; + color: #CAA3E8; +} + +.PURPLE_B { + background: #B189C6; + color: #B189C6; +} + +.PURPLE_C { + background: #9A72AC; + color: #9A72AC; +} + +.PURPLE_D { + background: #715582; + color: #715582; +} + +.PURPLE_E { + background: #644172; + color: #644172; +} + +.GREY_A { + background: #DDDDDD; + color: #DDDDDD; +} + +.GREY_B { + background: #BBBBBB; + color: #BBBBBB; +} + +.GREY_C { + background: #888888; + color: #888888; +} + +.GREY_D { + background: #444444; + color: #444444; +} + +.GREY_E { + background: #222222; + color: #222222; +} + +.WHITE { + background: #FFFFFF; + color: #FFFFFF; +} + +.BLACK { + background: #000000; + color: #000000; +} + +.GREY_BROWN { + background: #736357; + color: #736357; +} + +.DARK_BROWN { + background: #8B4513; + color: #8B4513; +} + +.LIGHT_BROWN { + background: #CD853F; + color: #CD853F; +} + +.PINK { + background: #D147BD; + color: #D147BD; +} + +.LIGHT_PINK { + background: #DC75CD; + color: #DC75CD; +} + +.GREEN_SCREEN { + background: #00FF00; + color: #00FF00; +} + +.ORANGE { + background: #FF862F; + color: #FF862F; +} \ No newline at end of file diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css new file mode 100644 index 00000000..4f2ea8ac --- /dev/null +++ b/docs/source/_static/custom.css @@ -0,0 +1,62 @@ +p { + font-size: initial; +} + +span.caption-text { + font-size: larger; +} + +span.pre { + font-size: initial; +} + +.highlight-python.notranslate { + margin-top: 0em; +} + +.manim-video { + width: 99.9%; + padding: 8px 0; + outline: 0; +} + +.manim-example { + background-color: #333333; + margin-bottom: 10px; + box-shadow: 2px 2px 4px #ddd; +} + +.manim-example .manim-video { + padding: 0; +} + +.manim-example img { + margin-bottom: 0; +} + +h5.example-header { + font-size: 18px; + font-weight: bold; + padding: 8px 16px; + color: white; + margin: 0; + font-family: inherit; + text-transform: none; + margin-top: -0.4em; + margin-bottom: -0.2em; +} + +.manim-example .highlight { + background-color: #fafafa; + border: 2px solid #333333; + padding: 8px 8px 10px 8px; + font-size: large; + margin: 0; +} + +.manim-example .highlight pre { + background-color: inherit; + border-left: none; + margin: 0; + padding: 0 6px 0 6px; +} \ No newline at end of file diff --git a/docs/source/_static/example_scenes/AnimatingMethods.mp4 b/docs/source/_static/example_scenes/AnimatingMethods.mp4 new file mode 100644 index 00000000..ce56fc11 Binary files /dev/null and b/docs/source/_static/example_scenes/AnimatingMethods.mp4 differ diff --git a/docs/source/_static/example_scenes/CoordinateSystemExample.mp4 b/docs/source/_static/example_scenes/CoordinateSystemExample.mp4 new file mode 100644 index 00000000..f8f9f582 Binary files /dev/null and b/docs/source/_static/example_scenes/CoordinateSystemExample.mp4 differ diff --git a/docs/source/_static/example_scenes/GraphExample.mp4 b/docs/source/_static/example_scenes/GraphExample.mp4 new file mode 100644 index 00000000..869b788f Binary files /dev/null and b/docs/source/_static/example_scenes/GraphExample.mp4 differ diff --git a/docs/source/_static/example_scenes/InteractiveDevlopment.mp4 b/docs/source/_static/example_scenes/InteractiveDevlopment.mp4 new file mode 100644 index 00000000..e6e87be8 Binary files /dev/null and b/docs/source/_static/example_scenes/InteractiveDevlopment.mp4 differ diff --git a/docs/source/_static/example_scenes/OpeningManimExample.mp4 b/docs/source/_static/example_scenes/OpeningManimExample.mp4 new file mode 100644 index 00000000..6b8a238e Binary files /dev/null and b/docs/source/_static/example_scenes/OpeningManimExample.mp4 differ diff --git a/docs/source/_static/example_scenes/SquareToCircle.mp4 b/docs/source/_static/example_scenes/SquareToCircle.mp4 new file mode 100644 index 00000000..009d6131 Binary files /dev/null and b/docs/source/_static/example_scenes/SquareToCircle.mp4 differ diff --git a/docs/source/_static/example_scenes/SurfaceExample.mp4 b/docs/source/_static/example_scenes/SurfaceExample.mp4 new file mode 100644 index 00000000..7e4f35db Binary files /dev/null and b/docs/source/_static/example_scenes/SurfaceExample.mp4 differ diff --git a/docs/source/_static/example_scenes/TexTransformExample.mp4 b/docs/source/_static/example_scenes/TexTransformExample.mp4 new file mode 100644 index 00000000..d5167ccc Binary files /dev/null and b/docs/source/_static/example_scenes/TexTransformExample.mp4 differ diff --git a/docs/source/_static/example_scenes/TextExample.mp4 b/docs/source/_static/example_scenes/TextExample.mp4 new file mode 100644 index 00000000..3dd48c8e Binary files /dev/null and b/docs/source/_static/example_scenes/TextExample.mp4 differ diff --git a/docs/source/_static/example_scenes/UpdatersExample.mp4 b/docs/source/_static/example_scenes/UpdatersExample.mp4 new file mode 100644 index 00000000..bc0e41da Binary files /dev/null and b/docs/source/_static/example_scenes/UpdatersExample.mp4 differ diff --git a/docs/source/_static/icon.png b/docs/source/_static/icon.png new file mode 100644 index 00000000..0c1a33f4 Binary files /dev/null and b/docs/source/_static/icon.png differ diff --git a/docs/source/_static/manim_shaders_process_en.png b/docs/source/_static/manim_shaders_process_en.png new file mode 100644 index 00000000..e6bb3b0a Binary files /dev/null and b/docs/source/_static/manim_shaders_process_en.png differ diff --git a/docs/source/_static/manim_shaders_structure.png b/docs/source/_static/manim_shaders_structure.png new file mode 100644 index 00000000..66a545c2 Binary files /dev/null and b/docs/source/_static/manim_shaders_structure.png differ diff --git a/docs/source/_static/quickstart/SquareToCircle.mp4 b/docs/source/_static/quickstart/SquareToCircle.mp4 new file mode 100644 index 00000000..009d6131 Binary files /dev/null and b/docs/source/_static/quickstart/SquareToCircle.mp4 differ diff --git a/docs/source/_static/quickstart/SquareToCircle.png b/docs/source/_static/quickstart/SquareToCircle.png new file mode 100644 index 00000000..bbccc3d7 Binary files /dev/null and b/docs/source/_static/quickstart/SquareToCircle.png differ diff --git a/docs/source/_static/quickstart/SquareToCircleEmbed.mp4 b/docs/source/_static/quickstart/SquareToCircleEmbed.mp4 new file mode 100644 index 00000000..dd52bc46 Binary files /dev/null and b/docs/source/_static/quickstart/SquareToCircleEmbed.mp4 differ diff --git a/docs/source/about.rst b/docs/source/about.rst deleted file mode 100644 index 2a86dfd9..00000000 --- a/docs/source/about.rst +++ /dev/null @@ -1,11 +0,0 @@ -About -===== - -Animating technical concepts is traditionally pretty tedious, since it can be -difficult to make the animations precise enough to convey them accurately. -``Manim`` uses Python to generate animations programmatically, which makes it -possible to specify exactly how each one should run. - -This project is still very much a work in progress, but I hope that the -information here will make it easier for newcomers to get started using -``Manim``. diff --git a/docs/source/animation.rst b/docs/source/animation.rst deleted file mode 100644 index e38db633..00000000 --- a/docs/source/animation.rst +++ /dev/null @@ -1,210 +0,0 @@ -Animation -========= - - - -The simplest of which is ``Scene.add``. The object appears on the first frame -without any animation:: - - class NoAnimation(Scene): - def construct(self): - square = Square() - self.add(square)) - -Animation are used in conjunction with ``scene.Play`` - -Fade ----- - -.. raw:: html - - - -.. code-block:: python - - class AnimationFadeIn(Scene): - def construct(self): - square = Square() - - anno = TextMobject("Fade In") - anno.shift(2 * DOWN) - self.add(anno) - self.play(FadeIn(square)) - -.. raw:: html - - - -.. code-block:: python - - class AnimationFadeOut(Scene): - def construct(self): - square = Square() - - anno = TextMobject("Fade Out") - anno.shift(2 * DOWN) - self.add(anno) - self.add(square) - self.play(FadeOut(square)) - - - -.. raw:: html - - - -.. code-block:: python - - class AnimationFadeInFrom(Scene): - def construct(self): - square = Square() - for label, edge in zip( - ["LEFT", "RIGHT", "UP", "DOWN"], [LEFT, RIGHT, UP, DOWN] - ): - anno = TextMobject(f"Fade In from {label}") - anno.shift(2 * DOWN) - self.add(anno) - - self.play(FadeInFrom(square, edge)) - self.remove(anno, square) - - - -.. raw:: html - - - -.. code-block:: python - - class AnimationFadeOutAndShift(Scene): - def construct(self): - square = Square() - for label, edge in zip( - ["LEFT", "RIGHT", "UP", "DOWN"], [LEFT, RIGHT, UP, DOWN] - ): - anno = TextMobject(f"Fade Out and shift {label}") - anno.shift(2 * DOWN) - self.add(anno) - - self.play(FadeOutAndShift(square, edge)) - self.remove(anno, square) - - - -.. raw:: html - - - -.. code-block:: python - - class AnimationFadeInFromLarge(Scene): - def construct(self): - square = Square() - - for factor in [0.1, 0.5, 0.8, 1, 2, 5]: - anno = TextMobject(f"Fade In from large scale\_factor={factor}") - anno.shift(2 * DOWN) - self.add(anno) - - self.play(FadeInFromLarge(square, scale_factor=factor)) - self.remove(anno, square) - -.. raw:: html - - - -.. code-block:: python - - class AnimationFadeInFromPoint(Scene): - def construct(self): - square = Square() - for i in range(-6, 7, 2): - anno = TextMobject(f"Fade In from point {i}") - anno.shift(2 * DOWN) - self.add(anno) - self.play(FadeInFromPoint(square, point=i)) - self.remove(anno, square) - - - -Grow ----- - -.. raw:: html - - - -.. code-block:: python - - class AnimationGrowFromEdge(Scene): - def construct(self): - - for label, edge in zip( - ["LEFT", "RIGHT", "UP", "DOWN"], [LEFT, RIGHT, UP, DOWN] - ): - anno = TextMobject(f"Grow from {label} edge") - anno.shift(2 * DOWN) - self.add(anno) - square = Square() - self.play(GrowFromEdge(square, edge)) - self.remove(anno, square) - - - -.. raw:: html - - - -.. code-block:: python - - class AnimationGrowFromCenter(Scene): - def construct(self): - square = Square() - - anno = TextMobject("Grow from center") - anno.shift(2 * DOWN) - self.add(anno) - - self.play(GrowFromCenter(square)) - - - - -Diagonal Directions -------------------- - -You can combine cardinal directions to form diagonal animations - -.. raw:: html - - - -.. code-block:: python - - class AnimationFadeInFromDiagonal(Scene): - def construct(self): - square = Square() - for diag in [UP + LEFT, UP + RIGHT, DOWN + LEFT, DOWN + RIGHT]: - self.play(FadeInFrom(square, diag)) - -.. note:: - You can also use the abbreviated forms like ``UL, UR, DL, DR``. - See :ref:`ref-directions`. diff --git a/docs/source/assets/AnimationFadeIn.mp4 b/docs/source/assets/AnimationFadeIn.mp4 deleted file mode 100644 index 783a6fa8..00000000 Binary files a/docs/source/assets/AnimationFadeIn.mp4 and /dev/null differ diff --git a/docs/source/assets/AnimationFadeInFrom.mp4 b/docs/source/assets/AnimationFadeInFrom.mp4 deleted file mode 100644 index 90ead072..00000000 Binary files a/docs/source/assets/AnimationFadeInFrom.mp4 and /dev/null differ diff --git a/docs/source/assets/AnimationFadeInFromDiagonal.mp4 b/docs/source/assets/AnimationFadeInFromDiagonal.mp4 deleted file mode 100644 index 25b0145c..00000000 Binary files a/docs/source/assets/AnimationFadeInFromDiagonal.mp4 and /dev/null differ diff --git a/docs/source/assets/AnimationFadeInFromLarge.mp4 b/docs/source/assets/AnimationFadeInFromLarge.mp4 deleted file mode 100644 index 589789ea..00000000 Binary files a/docs/source/assets/AnimationFadeInFromLarge.mp4 and /dev/null differ diff --git a/docs/source/assets/AnimationFadeInFromPoint.mp4 b/docs/source/assets/AnimationFadeInFromPoint.mp4 deleted file mode 100644 index cdfdc84c..00000000 Binary files a/docs/source/assets/AnimationFadeInFromPoint.mp4 and /dev/null differ diff --git a/docs/source/assets/AnimationFadeInFromSmall.mp4 b/docs/source/assets/AnimationFadeInFromSmall.mp4 deleted file mode 100644 index 9e8f8097..00000000 Binary files a/docs/source/assets/AnimationFadeInFromSmall.mp4 and /dev/null differ diff --git a/docs/source/assets/AnimationFadeOut.mp4 b/docs/source/assets/AnimationFadeOut.mp4 deleted file mode 100644 index 5938ab7e..00000000 Binary files a/docs/source/assets/AnimationFadeOut.mp4 and /dev/null differ diff --git a/docs/source/assets/AnimationFadeOutAndShift.mp4 b/docs/source/assets/AnimationFadeOutAndShift.mp4 deleted file mode 100644 index 834145cb..00000000 Binary files a/docs/source/assets/AnimationFadeOutAndShift.mp4 and /dev/null differ diff --git a/docs/source/assets/AnimationGrowFromCenter.mp4 b/docs/source/assets/AnimationGrowFromCenter.mp4 deleted file mode 100644 index 634863bb..00000000 Binary files a/docs/source/assets/AnimationGrowFromCenter.mp4 and /dev/null differ diff --git a/docs/source/assets/AnimationGrowFromEdge.mp4 b/docs/source/assets/AnimationGrowFromEdge.mp4 deleted file mode 100644 index 7da5f313..00000000 Binary files a/docs/source/assets/AnimationGrowFromEdge.mp4 and /dev/null differ diff --git a/docs/source/assets/SquareToCircle.mp4 b/docs/source/assets/SquareToCircle.mp4 deleted file mode 100644 index 5b1e9b90..00000000 Binary files a/docs/source/assets/SquareToCircle.mp4 and /dev/null differ diff --git a/docs/source/assets/coordinate/CoorAlias.mp4 b/docs/source/assets/coordinate/CoorAlias.mp4 deleted file mode 100644 index 3df8e0fd..00000000 Binary files a/docs/source/assets/coordinate/CoorAlias.mp4 and /dev/null differ diff --git a/docs/source/assets/coordinate/CoorArithmetic.mp4 b/docs/source/assets/coordinate/CoorArithmetic.mp4 deleted file mode 100644 index cea6c087..00000000 Binary files a/docs/source/assets/coordinate/CoorArithmetic.mp4 and /dev/null differ diff --git a/docs/source/assets/coordinate/CoorPolygon.png b/docs/source/assets/coordinate/CoorPolygon.png deleted file mode 100644 index 5c9793fa..00000000 Binary files a/docs/source/assets/coordinate/CoorPolygon.png and /dev/null differ diff --git a/docs/source/assets/coordinate/DotMap.mp4 b/docs/source/assets/coordinate/DotMap.mp4 deleted file mode 100644 index 6e7d0c3d..00000000 Binary files a/docs/source/assets/coordinate/DotMap.mp4 and /dev/null differ diff --git a/docs/source/conf.py b/docs/source/conf.py index 81567716..10ef5843 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,53 +1,40 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# http://www.sphinx-doc.org/en/master/config - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) +import os +import sys +sys.path.insert(0, os.path.abspath(".")) +sys.path.insert(0, os.path.abspath('../../')) -# -- Project information ----------------------------------------------------- +project = 'manim' +copyright = '- This document has been placed in the public domain.' +author = 'TonyCrane' -project = 'Manim' -copyright = '2019, EulerTour' -author = 'EulerTour' +release = '' - -# -- General configuration --------------------------------------------------- -master_doc = 'index' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = [ + 'sphinx.ext.todo', + 'sphinx.ext.githubpages', + 'sphinx.ext.mathjax', + 'sphinx.ext.intersphinx', + 'sphinx.ext.autodoc', + 'sphinx.ext.coverage', + 'sphinx.ext.napoleon', + 'sphinx_copybutton', + 'manim_example_ext' ] -# Add any paths that contain templates here, relative to this directory. +autoclass_content = 'both' +mathjax_path = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" + templates_path = ['_templates'] +source_suffix = '.rst' +master_doc = 'index' +pygments_style = 'default' -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['assets'] +html_static_path = ["_static"] +html_css_files = ["custom.css", "colors.css"] +html_theme = 'furo' # pip install furo==2020.10.5b9 +html_favicon = '_static/icon.png' +html_logo = '../../logo/transparent_graph.png' +html_theme_options = { + "sidebar_hide_name": True, +} diff --git a/docs/source/constants.rst b/docs/source/constants.rst deleted file mode 100644 index 7bc34d14..00000000 --- a/docs/source/constants.rst +++ /dev/null @@ -1,94 +0,0 @@ -Manim Constants -=============== - -The ``constants.py`` under ``manimlib/`` contains variables that are used -during setup and running manim. Some variables are not documented here as they are -only used internally by manim. - -Directories ------------ - - MEDIA_DIR - The directory where ``VIDEO_DIR`` and ``TEX_DIR`` will be created, - if they aren't specified via flags. - VIDEO_DIR - Used to store the scenes rendered by Manim. When a scene is - finished rendering, it will be stored under - ``VIDEO_DIR/module_name/scene_name/quality/scene_name.mp4``. - Created under ``MEDIA_DIR`` by default. - TEX_DIR - Files written by Latex are stored here. It also acts as a cache - so that the files aren't rewritten each time Latex is needed. - -Those directories are created if they don't exist. - -Tex ---- - TEX_USE_CTEX - A boolean value. Change it to True if you need to use Chinese typesetting. - TEX_TEXT_TO_REPLACE - Placeholder text used by manim when generating tex files - TEMPLATE_TEX_FILE - By default ``manimlib/tex_template.tex`` is used. If ``TEX_USE_CTEX`` - is set to True then ``manimlib/ctex_template.tex`` is used. - -Numerical Constants -------------------- - - PI - alias to ``numpy.pi`` - TAU - PI * 2 - - DEGREES - TAU / 360 - -Camera Configuration --------------------- - -Render setting presets - - PRODUCTION_QUALITY_CAMERA_CONFIG - 2560x1440 @ 60fps # This is the default when rendering a scene - HIGH_QUALITY_CAMERA_CONFIG - 1920x1080 @ 60fps. # Used when the ``-h`` or ``--high_quality`` flag - is passed. - MEDIUM_QUALITY_CAMERA_CONFIG - 1280x720 @ 30fps. # Used when the ``-m`` or ``--medium_quality`` - flag is passed. - LOW_QUALITY_CAMERA_CONFIG - 854x480 @ 15fps. # Used when the ``-l`` or ``--low_quality`` flag is - passed. - -.. _ref-directions: - -Coordinates ------------ - -Used for 2d/3d animations and placements:: - - ORIGIN - UP - DOWN - RIGHT - LEFT - IN # 3d camera only, away from camera - OUT # 3d camera only, close to camera - - UL = UP + LEFT # diagonal abbreviations. You can use either one - UR = UP + RIGHT - DL = DOWN + LEFT - DR = DOWN + RIGHT - - TOP - BOTTOM - LEFT_SIDE - RIGHT_SIDE`` - -Colors ------- - - COLOR_MAP - A predefined color maps - PALETTE - A list of color hex strings, derived from COLOR_MAP diff --git a/docs/source/coordinate.rst b/docs/source/coordinate.rst deleted file mode 100644 index 27edb256..00000000 --- a/docs/source/coordinate.rst +++ /dev/null @@ -1,178 +0,0 @@ -Coordinate -========== - -By default, the scene in manim is made up by 8 x 14 grid. The grid is addressed using a numpy -array in the form of [x, y, z]. For 2D animations only the x and y axes are used. - -.. code-block:: python - - class DotMap(Scene): - def construct(self): - dots = dict() - annos = dict() - var_index = 0 - for x in range(-7, 8): - for y in range(-4, 5): - annos[f"{x}{y}"] = TexMobject(f"({x}, {y})") - dots[f"{var_index}"] = Dot(np.array([x, y, 0])) - var_index = var_index + 1 - for anno, dot in zip(annos.values(), dots.values()): - self.add(anno) - self.add(dot) - self.wait(0.2) - self.remove(anno) - -.. raw:: html - - - -.. note:: - You can place objects outside this boundary, but it won't show up in the render. - -Using Coordinates ------------------ - -Coordinates are used for creating geometries (`VMobject` in manim) and animations. - -Here coordinates are used to create this Polygon - -.. code-block:: python - - class CoorPolygon(Scene): - def construct(self): - for x in range(-7, 8): - for y in range(-4, 5): - self.add(Dot(np.array([x, y, 0]), color=DARK_GREY)) - polygon = Polygon( - np.array([3, 2, 0]), - np.array([1, -1, 0]), - np.array([-5, -4, 0]), - np.array([-4, 4, 0])) - self.add(polygon) - - -.. Image:: assets/coordinate/CoorPolygon.png - :width: 700px - -Coordinate Aliasing -------------------- - -From some animations typing a ``np.array`` everytime you need a coordinate can be tedious. -Manim provides aliases to the most common coordinates:: - - UP == np.array([0, 1, 0]) - DOWN == np.array([0, -1, 0]) - LEFT == np.array([-1, 0, 0]) - RIGHT == np.array([1, 0, 0]) - UL == np.array([-1, 1, 0]) - DL == np.array([-1, -1, 0]) - UR == np.array([1, 1, 0]) - DR == np.array([1, -1, 0]) - -Here coordinates are used for animations - -.. code-block:: python - - class CoorAlias(Scene): - def construct(self): - for x in range(-7, 8): - for y in range(-4, 5): - self.add(Dot(np.array([x, y, 0]), color=DARK_GREY)) - - aliases = { - "UP": UP, - "np.array([0,1,0])": np.array([0, 1, 0]), - "DOWN": DOWN, - "np.array([0,-1,0])": np.array([0, -1, 0]), - "LEFT": LEFT, - "np.array([-1,0,0])": np.array([-1, 0, 0]), - "RIGHT": RIGHT, - "np.array([1,0,0])": np.array([1, 0, 0]), - "UL": UL, - "np.array([-1,1,0])": np.array([-1, 1, 0]), - "DL": DL, - "np.array([-1,-1,0])": np.array([-1, -1, 0]), - "UR": UR, - "np.array([1,1,0])": np.array([1, 1, 0]), - "DR": DR, - "np.array([1,-1,0])": np.array([1, -1, 0])} - circle = Circle(color=RED, radius=0.5) - self.add(circle) - self.wait(0.5) - - for text, aliase in aliases.items(): - anno = TexMobject(f"\\texttt{{{text}}}") - self.play(Write(anno, run_time=0.2)) - self.play(ApplyMethod(circle.shift, aliase)) - self.wait(0.2) - self.play(FadeOut(anno, run_time=0.2)) - -.. raw:: html - - - -Coordinate Arithmetic ---------------------- - -Numpy array allows arithmetic operations:: - - >>> numpy.array([2,2,0]) + 4 - array([6, 6, 4]) - - >>> np.array([1, -3, 0]) + np.array([-4, 2, 0]) - array([-3, -1, 0]) - - >>> np.array([2, 2, 0]) - np.array([3,6, 0]) - array([-1, -4, 0]) - - >>> numpy.array([2,2,0]) - 3 - array([-1, -1, -3]) - - >>> np.array([1, -3, 0]) * 3 - array([ 3, -9, 0]) - - >>> numpy.array([2,2,0]) / 2 - array([1., 1., 0.]) - - >>> numpy.array([2,2,0]) / numpy.array([1, 4, 0]) - __main__:1: RuntimeWarning: invalid value encountered in true_divide - array([2. , 0.5, nan]) - -.. code-block:: python - - class CoorArithmetic(Scene): - def construct(self): - for x in range(-7, 8): - for y in range(-4, 5): - self.add(Dot(np.array([x, y, 0]), color=DARK_GREY)) - - circle = Circle(color=RED, radius=0.5) - self.add(circle) - self.wait(0.5) - - aliases = { - "LEFT * 3": LEFT * 3, - "UP + RIGHT / 2": UP + RIGHT / 2, - "DOWN + LEFT * 2": DOWN + LEFT * 2, - "RIGHT * 3.75 * DOWN": RIGHT * 3.75 * DOWN, - # certain arithmetic won't work as you expected - # In [4]: RIGHT * 3.75 * DOWN - # Out[4]: array([ 0., -0., 0.]) - "RIGHT * 3.75 + DOWN": RIGHT * 3.75 + DOWN} - - for text, aliase in aliases.items(): - anno = TexMobject(f"\\texttt{{{text}}}") - self.play(Write(anno, run_time=0.2)) - self.play(ApplyMethod(circle.shift, aliase)) - self.wait(0.2) - self.play(FadeOut(anno, run_time=0.2)) - -.. raw:: html - - diff --git a/docs/source/development/about.rst b/docs/source/development/about.rst new file mode 100644 index 00000000..02bf7c35 --- /dev/null +++ b/docs/source/development/about.rst @@ -0,0 +1,31 @@ +About +===== + +About Manim +----------- + +Manim is an animation engine for explanatory math videos. +You can use it to make math videos (or other fields) like 3Blue1Brown. + +There are mainly two versions here: + +- `3b1b/manim `_ : Maintained by Grant Sanderson of 3Blue1Brown. + +Using OpenGL and its GLSL language to use GPU for rendering. It has higher efficiency, +faster rendering speed, and supports real-time rendering and interaction. + +- `ManimCommunity/manim `_ : Maintained by Manim Community Dev Team. + +Using multiple backend rendering. There is better documentation and +a more open contribution community. + +About this documentation +------------------------ + +This documentation is based on the version in `3b1b/manim `_. +Created by `TonyCrane `_ ("鹤翔万里" in Chinese) and in production. + +Among them, the ``manim_example_ext`` extension for Sphinx refers to +`the documentation of ManimCommunity `_. + +If you want to contribute to manim or this document, please see: :doc:`contributing` \ No newline at end of file diff --git a/docs/source/development/changelog.rst b/docs/source/development/changelog.rst new file mode 100644 index 00000000..1460d9b6 --- /dev/null +++ b/docs/source/development/changelog.rst @@ -0,0 +1,4 @@ +Changelog +========= + +No changes now. \ No newline at end of file diff --git a/docs/source/development/contributing.rst b/docs/source/development/contributing.rst new file mode 100644 index 00000000..ce19472c --- /dev/null +++ b/docs/source/development/contributing.rst @@ -0,0 +1,59 @@ +Contributing +============ + +Accept any contribution you make :) + +- **Contribute to the manim source code**: + +Please fork to your own repository and make changes, submit a pull request, and fill in +the motivation for the change following the instructions in the template. We will check +your pull request in detail (this usually takes a while, please be patient) + +- **Contribute to the documentation**: + +Also submit a pull request and write down the main changes. + +- **If you find a bug in the code**: + +Please open an issue and fill in the found problem and your environment according +to the template. (But please note that if you think this problem is just a problem +of yourself, rather than a problem of source code, it is recommended that you ask a +question in the `Q&A category `_ +of the discussion page) + +- **You are welcome to share the content you made with manim**: + +Post it in the `show and tell category `_ +of the discussion page. + +- **You are also welcome to share some of your suggestions and ideas**: + +Post them in the `ideas category `_ +of the discussion page. + +How to build this documentation +------------------------------- + +- Clone the 3b1b/manim repository + +.. code-block:: sh + + git clone https://github.com/3b1b/manim.git + # or your own repo + # git clone https://github.com//manim.git + cd manim + +- Install python package dependencies + +.. code-block:: sh + + pip install -r docs/requirements.txt + +- Go to the ``docs/`` folder and build + +.. code-block:: sh + + cd docs/ + make html + +- The output document is located in ``docs/build/html/`` \ No newline at end of file diff --git a/docs/source/documentation/animation/index.rst b/docs/source/documentation/animation/index.rst new file mode 100644 index 00000000..209acb94 --- /dev/null +++ b/docs/source/documentation/animation/index.rst @@ -0,0 +1,2 @@ +Animation (TODO) +================ \ No newline at end of file diff --git a/docs/source/documentation/camera/index.rst b/docs/source/documentation/camera/index.rst new file mode 100644 index 00000000..a6be1fab --- /dev/null +++ b/docs/source/documentation/camera/index.rst @@ -0,0 +1,2 @@ +Camera (TODO) +============= \ No newline at end of file diff --git a/docs/source/documentation/constants.rst b/docs/source/documentation/constants.rst new file mode 100644 index 00000000..cbf96bae --- /dev/null +++ b/docs/source/documentation/constants.rst @@ -0,0 +1,192 @@ +constants +========= + +The ``constants.py`` in the ``manimlib`` folder defines the constants +needed when running manim. Some constants are not explained here because +they are only used inside manim. + +Frame and pixel shape +--------------------- + +.. code-block:: python + + ASPECT_RATIO = 16.0 / 9.0 + FRAME_HEIGHT = 8.0 + FRAME_WIDTH = FRAME_HEIGHT * ASPECT_RATIO + FRAME_Y_RADIUS = FRAME_HEIGHT / 2 + FRAME_X_RADIUS = FRAME_WIDTH / 2 + + DEFAULT_PIXEL_HEIGHT = 1080 + DEFAULT_PIXEL_WIDTH = 1920 + DEFAULT_FRAME_RATE = 30 + +Buffs +----- + +.. code-block:: python + + SMALL_BUFF = 0.1 + MED_SMALL_BUFF = 0.25 + MED_LARGE_BUFF = 0.5 + LARGE_BUFF = 1 + + DEFAULT_MOBJECT_TO_EDGE_BUFFER = MED_LARGE_BUFF # Distance between object and edge + DEFAULT_MOBJECT_TO_MOBJECT_BUFFER = MED_SMALL_BUFF # Distance between objects + +Run times +--------- + +.. code-block:: python + + DEFAULT_POINTWISE_FUNCTION_RUN_TIME = 3.0 + DEFAULT_WAIT_TIME = 1.0 + +Coordinates +----------- + +manim uses three-dimensional coordinates and uses the type of ``ndarray`` + +.. code-block:: python + + ORIGIN = np.array((0., 0., 0.)) + UP = np.array((0., 1., 0.)) + DOWN = np.array((0., -1., 0.)) + RIGHT = np.array((1., 0., 0.)) + LEFT = np.array((-1., 0., 0.)) + IN = np.array((0., 0., -1.)) + OUT = np.array((0., 0., 1.)) + X_AXIS = np.array((1., 0., 0.)) + Y_AXIS = np.array((0., 1., 0.)) + Z_AXIS = np.array((0., 0., 1.)) + + # Useful abbreviations for diagonals + UL = UP + LEFT + UR = UP + RIGHT + DL = DOWN + LEFT + DR = DOWN + RIGHT + + TOP = FRAME_Y_RADIUS * UP + BOTTOM = FRAME_Y_RADIUS * DOWN + LEFT_SIDE = FRAME_X_RADIUS * LEFT + RIGHT_SIDE = FRAME_X_RADIUS * RIGHT + +Mathematical constant +--------------------- + +.. code-block:: python + + PI = np.pi + TAU = 2 * PI + DEGREES = TAU / 360 + +Text +---- + +.. code-block:: python + + START_X = 30 + START_Y = 20 + NORMAL = "NORMAL" + ITALIC = "ITALIC" + OBLIQUE = "OBLIQUE" + BOLD = "BOLD" + +Stroke width +------------ + +.. code-block:: python + + DEFAULT_STROKE_WIDTH = 4 + +Colours +------- + +Here are the preview of default colours. (Modified from +`elteoremadebeethoven `_) + +.. raw:: html + +
+

BLUE

+

BLUE_E

+

BLUE_D

+

BLUE_C

+

BLUE_B

+

BLUE_A

+
+
+

TEAL

+

TEAL_E

+

TEAL_D

+

TEAL_C

+

TEAL_B

+

TEAL_A

+
+
+

GREEN

+

GREEN_E

+

GREEN_D

+

GREEN_C

+

GREEN_B

+

GREEN_A

+
+
+

YELLOW

+

YELLOW_E

+

YELLOW_D

+

YELLOW_C

+

YELLOW_B

+

YELLOW_A

+
+
+

GOLD

+

GOLD_E

+

GOLD_D

+

GOLD_C

+

GOLD_B

+

GOLD_A

+
+
+

RED

+

RED_E

+

RED_D

+

RED_C

+

RED_B

+

RED_A

+
+
+

MAROON

+

MAROON_E

+

MAROON_D

+

MAROON_C

+

MAROON_B

+

MAROON_A

+
+
+

PURPLE

+

PURPLE_E

+

PURPLE_D

+

PURPLE_C

+

PURPLE_B

+

PURPLE_A

+
+
+

GREY

+

GREY_E

+

GREY_D

+

GREY_C

+

GREY_B

+

GREY_A

+
+
+

Others

+

WHITE

+

BLACK

+

GREY_BROWN

+

DARK_BROWN

+

LIGHT_BROWN

+

PINK

+

LIGHT_PINK

+

GREEN_SCREEN

+

ORANGE

+
diff --git a/docs/source/documentation/custom_config.rst b/docs/source/documentation/custom_config.rst new file mode 100644 index 00000000..496c87a9 --- /dev/null +++ b/docs/source/documentation/custom_config.rst @@ -0,0 +1,139 @@ +custom_config +============== + +``directories`` +--------------- + +- ``mirror_module_path`` + (``True`` or ``False``) Whether to create a folder named the name of the + running file under the ``output`` path, and save the output (``images/`` + or ``videos/``) in it. + +- ``output`` + Output file path, the videos will be saved in the ``videos/`` folder under it, + and the pictures will be saved in the ``images/`` folder under it. + + For example, if you set ``output`` to ``"/.../manim/output"`` and + ``mirror_module_path`` to ``False``, then you exported ``Scene1`` in the code + file and saved the last frame, then the final directory structure will be like: + + .. code-block:: text + :emphasize-lines: 9, 11 + + manim/ + ├── manimlib/ + │ ├── animation/ + │ ├── ... + │ ├── default_config.yml + │ └── window.py + ├── output/ + │ ├── images + │ │ └── Scene1.png + │ └── videos + │ └── Scene1.mp4 + ├── code.py + └── custom_config.yml + + But if you set ``mirror_module_path`` to ``True``, the directory structure will be: + + .. code-block:: text + :emphasize-lines: 8 + + manim/ + ├── manimlib/ + │ ├── animation/ + │ ├── ... + │ ├── default_config.yml + │ └── window.py + ├── output/ + │ └── code/ + │ ├── images + │ │ └── Scene1.png + │ └── videos + │ └── Scene1.mp4 + ├── code.py + └── custom_config.yml + +- ``raster_images`` + The directory for storing raster images to be used in the code (including + ``.jpg``, ``.png`` and ``.gif``), which will be read by ``ImageMobject``. + +- ``vector_images`` + The directory for storing vector images to be used in the code (including + ``.svg`` and ``.xdv``), which will be read by ``SVGMobject``. + +- ``sounds`` + The directory for storing sound files to be used in ``Scene.add_sound()`` ( + including ``.wav`` and ``.mp3``). + +- ``temporary_storage`` + The directory for storing temporarily generated cache files, including + ``Tex`` cache, ``Text`` cache and storage of object points. + +``tex`` +------- + +- ``executable`` + The executable program used to compile LaTeX (``latex`` or ``xelatex -no-pdf`` + is recommended) + +- ``template_file`` + LaTeX template used, in ``manimlib/tex_templates`` + +- ``intermediate_filetype`` + The type of intermediate vector file generated after compilation (``dvi`` if + ``latex`` is used, ``xdv`` if ``xelatex`` is used) + +- ``text_to_replace`` + The text to be replaced in the template (needn't to change) + +``universal_import_line`` +------------------------- + +Import line that need to execute when entering interactive mode directly. + +``style`` +--------- + +- ``font`` + Default font of Text + +- ``background_color`` + Default background color + +``window_position`` +------------------- + +The relative position of the playback window on the display (two characters, +the first character means upper(U) / middle(O) / lower(D), the second character +means left(L) / middle(O) / right(R)). + +``break_into_partial_movies`` +----------------------------- + +If this is set to ``True``, then many small files will be written corresponding +to each ``Scene.play`` and ``Scene.wait`` call, and these files will then be combined +to form the full scene. + +Sometimes video-editing is made easier when working with the broken up scene, which +effectively has cuts at all the places you might want. + +``camera_qualities`` +-------------------- + +Export quality + +- ``low`` + Low quality (default is 480p15) + +- ``medium`` + Medium quality (default is 720p30) + +- ``high`` + High quality (default is 1080p30) + +- ``ultra_high`` + Ultra high quality (default is 4K60) + +- ``default_quality`` + Default quality (one of the above four) \ No newline at end of file diff --git a/docs/source/documentation/mobject/index.rst b/docs/source/documentation/mobject/index.rst new file mode 100644 index 00000000..f6c97d24 --- /dev/null +++ b/docs/source/documentation/mobject/index.rst @@ -0,0 +1,2 @@ +Mobject (TODO) +============== \ No newline at end of file diff --git a/docs/source/documentation/scene/index.rst b/docs/source/documentation/scene/index.rst new file mode 100644 index 00000000..7d0a35e8 --- /dev/null +++ b/docs/source/documentation/scene/index.rst @@ -0,0 +1,2 @@ +Scene (TODO) +============ \ No newline at end of file diff --git a/docs/source/documentation/shaders/index.rst b/docs/source/documentation/shaders/index.rst new file mode 100644 index 00000000..fec1cbfd --- /dev/null +++ b/docs/source/documentation/shaders/index.rst @@ -0,0 +1,2 @@ +Shaders (TODO) +============== \ No newline at end of file diff --git a/docs/source/documentation/utils/index.rst b/docs/source/documentation/utils/index.rst new file mode 100644 index 00000000..c6ecc3f2 --- /dev/null +++ b/docs/source/documentation/utils/index.rst @@ -0,0 +1,2 @@ +Utils (TODO) +============ \ No newline at end of file diff --git a/docs/source/getting_started/animating_mobjects.rst b/docs/source/getting_started/animating_mobjects.rst deleted file mode 100644 index efc0a109..00000000 --- a/docs/source/getting_started/animating_mobjects.rst +++ /dev/null @@ -1,4 +0,0 @@ -Animating Mobjects -================== - -Learn about animations. diff --git a/docs/source/getting_started/config.rst b/docs/source/getting_started/config.rst new file mode 100644 index 00000000..cdb9f734 --- /dev/null +++ b/docs/source/getting_started/config.rst @@ -0,0 +1,104 @@ +CONFIG dictionary +================= + +What's CONFIG +------------- + +``CONFIG`` dictionary is a feature of manim, which facilitates the inheritance +and modification of parameters between parent and child classes. + +| ``CONFIG`` dictionary 's processing is in ``manimlib/utils/config_ops.py`` +| It can convert the key-value pairs in the ``CONFIG`` dictionary into class attributes and values + +Generally, the first line of the ``.__init__()`` method in some basic class (``Mobject``, ``Animation``, +etc.) will call this function ``digest_config(self, kwargs)`` to convert both +the ``CONFIG`` dictionary and ``kwargs`` into attributes. Then it can be accessed +directly through ``self.``, which simplifies the handling of inheritance between classes. + +**An example**: + +There are many class inheritance relationships in ``manimlib/mobject/geometry.py`` + +.. code-block:: python + + # Line 279 + class Circle(Arc): + CONFIG = { + "color": RED, + "close_new_points": True, + "anchors_span_full_range": False + } + +.. code-block:: python + + # Line 304 + class Dot(Circle): + CONFIG = { + "radius": DEFAULT_DOT_RADIUS, + "stroke_width": 0, + "fill_opacity": 1.0, + "color": WHITE + } + +The ``Circle`` class uses the key-value pair ``"color": RED`` in the ``CONFIG`` +dictionary to add the attribute ``self.color``. + +At the same time, the ``Dot`` class also contains the key ``color`` in the +``CONFIG`` dictionary, but the value is different. At this time, the priority will +modify the attribute ``self.color`` to ``WHITE``. + +CONFIG nesting +-------------- + +The ``CONFIG`` dictionary supports nesting, that is, the value of the key is also +a dictionary, for example: + +.. code-block:: python + + class Camera(object): + CONFIG = { + # configs + } + +.. code-block:: python + + class Scene(object): + CONFIG = { + "window_config": {}, + "camera_class": Camera, + "camera_config": {}, + "file_writer_config": {}, + # other configs + } + + def __init__(self, **kwargs): + digest_config(self, kwargs) + # some lines + self.camera = self.camera_class(**self.camera_config) + +The ``CONFIG`` dictionary of the ``Camera`` class contains many key-value pairs, +and this class needs to be instantiated in the ``Scene`` class. For more convenient +control, there is a special key-value pair in the Scene class ``"camera_config": {}``, +Its value is a dictionary, passed in as ``kwargs`` when initializing the ``Camera`` class +to modify the value of the properties of the ``Camera`` class. + +So the nesting of the ``CONFIG`` dictionary **essentially** passes in the value as ``kwargs``. + +Common usage +------------ + +When writing a class by yourself, you can add attributes or modify the attributes +of the parent class through ``CONFIG``. + +The most commonly used is to modify the properties of the camera when writing a ``Scene``: + +.. code-block:: python + + class YourScene(Scene): + CONFIG = { + "camera_config": { + "background_color": WHITE, + }, + } + +For example, the above dictionary will change the background color to white, etc. \ No newline at end of file diff --git a/docs/source/getting_started/configuration.rst b/docs/source/getting_started/configuration.rst new file mode 100644 index 00000000..17319bb1 --- /dev/null +++ b/docs/source/getting_started/configuration.rst @@ -0,0 +1,89 @@ +CLI flags and configuration +=========================== + +Command Line Interface +---------------------- + +To run manim, you need to enter the directory at the same level as ``manimlib/`` +and enter the command in the following format into terminal: + +.. code-block:: sh + + manimgl .py + # or + manim-render .py + +- ``.py`` : The python file you wrote. Needs to be at the same level as ``manimlib/``, otherwise you need to use an absolute path or a relative path. +- ```` : The scene you want to render here. If it is not written or written incorrectly, it will list all for you to choose. And if there is only one ``Scene`` in the file, this class will be rendered directly. +- ```` : CLI flags. + +Some useful flags +^^^^^^^^^^^^^^^^^ + +- ``-w`` to write the scene to a file. +- ``-o`` to write the scene to a file and open the result. +- ``-s`` to skip to the end and just show the final frame. + + - ``-so`` will save the final frame to an image and show it. + +- ``-n `` to skip ahead to the ``n``\ ’th animation of a scene. +- ``-f`` to make the playback window fullscreen. + +All supported flags +^^^^^^^^^^^^^^^^^^^ + +========================================================== ====== ================================================================================================================================================================================================= +flag abbr function +========================================================== ====== ================================================================================================================================================================================================= +``--help`` ``-h`` Show the help message and exit +``--write_file`` ``-w`` Render the scene as a movie file +``--skip_animations`` ``-s`` Skip to the last frame +``--low_quality`` ``-l`` Render at a low quality (for faster rendering) +``--medium_quality`` ``-m`` Render at a medium quality +``--hd`` Render at a 1080p quality +``--uhd`` Render at a 4k quality +``--full_screen`` ``-f`` Show window in full screen +``--save_pngs`` ``-g`` Save each frame as a png +``--save_as_gif`` ``-i`` Save the video as gif +``--transparent`` ``-t`` Render to a movie file with an alpha channel +``--quiet`` ``-q`` +``--write_all`` ``-a`` Write all the scenes from a file +``--open`` ``-o`` Automatically open the saved file once its done +``--finder`` Show the output file in finder +``--config`` Guide for automatic configuration +``--file_name FILE_NAME`` Name for the movie or image file +``--start_at_animation_number START_AT_ANIMATION_NUMBER`` ``-n`` Start rendering not from the first animation, but from another, specified by its index. If you passin two comma separated values, e.g. "3,6", it will end the rendering at the second value. +``--resolution RESOLUTION`` ``-r`` Resolution, passed as "WxH", e.g. "1920x1080" +``--frame_rate FRAME_RATE`` Frame rate, as an integer +``--color COLOR`` ``-c`` Background color +``--leave_progress_bars`` Leave progress bars displayed in terminal +``--video_dir VIDEO_DIR`` directory to write video +========================================================== ====== ================================================================================================================================================================================================= + +custom_config +-------------- + +In order to perform more configuration (about directories, etc.) and permanently +change the default value (you don't have to add flags to the command every time), +you can modify ``custom_config.yml``. The meaning of each option is in +page :doc:`../documentation/custom_config`. + +You can also use different ``custom_config.yml`` for different directories, such as +following the directory structure: + +.. code-block:: text + + manim/ + ├── manimlib/ + │ ├── animation/ + │ ├── ... + │ ├── default_config.yml + │ └── window.py + ├── project/ + │ ├── code.py + │ └── custom_config.yml + └── custom_config.yml + +When you enter the ``project/`` folder and run ``manimgl code.py ``, +it will overwrite ``manim/custom_config.yml`` with ``custom_config.yml`` +in the ``project`` folder. \ No newline at end of file diff --git a/docs/source/getting_started/example_scenes.rst b/docs/source/getting_started/example_scenes.rst new file mode 100644 index 00000000..4a9aa4f1 --- /dev/null +++ b/docs/source/getting_started/example_scenes.rst @@ -0,0 +1,719 @@ +Example Scenes +============== + +After understanding the previous knowledge, we can understand more scenes. +Many example scenes are given in ``example_scenes.py``, let's start with +the simplest and one by one. + +InteractiveDevlopment +--------------------- + +.. manim-example:: InteractiveDevlopment + :media: ../_static/example_scenes/InteractiveDevlopment.mp4 + + from manimlib import * + + class InteractiveDevlopment(Scene): + def construct(self): + circle = Circle() + circle.set_fill(BLUE, opacity=0.5) + circle.set_stroke(BLUE_E, width=4) + square = Square() + + self.play(ShowCreation(square)) + self.wait() + + # This opens an iPython termnial where you can keep writing + # lines as if they were part of this construct method. + # In particular, 'square', 'circle' and 'self' will all be + # part of the local namespace in that terminal. + self.embed() + + # Try copying and pasting some of the lines below into + # the interactive shell + self.play(ReplacementTransform(square, circle)) + self.wait() + self.play(circle.animate.stretch(4, 0)) + self.play(Rotate(circle, 90 * DEGREES)) + self.play(circle.animate.shift(2 * RIGHT).scale(0.25)) + + text = Text(""" + In general, using the interactive shell + is very helpful when developing new scenes + """) + self.play(Write(text)) + + # In the interactive shell, you can just type + # play, add, remove, clear, wait, save_state and restore, + # instead of self.play, self.add, self.remove, etc. + + # To interact with the window, type touch(). You can then + # scroll in the window, or zoom by holding down 'z' while scrolling, + # and change camera perspective by holding down 'd' while moving + # the mouse. Press 'r' to reset to the standard camera position. + # Press 'q' to stop interacting with the window and go back to + # typing new commands into the shell. + + # In principle you can customize a scene to be responsive to + # mouse and keyboard interactions + always(circle.move_to, self.mouse_point) + +This scene is similar to what we wrote in :doc:`quickstart`. +And how to interact has been written in the comments. +No more explanation here. + +AnimatingMethods +---------------- + +.. manim-example:: AnimatingMethods + :media: ../_static/example_scenes/AnimatingMethods.mp4 + + class AnimatingMethods(Scene): + def construct(self): + grid = Tex(r"\pi").get_grid(10, 10, height=4) + self.add(grid) + + # You can animate the application of mobject methods with the + # ".animate" syntax: + self.play(grid.animate.shift(LEFT)) + + # Alternatively, you can use the older syntax by passing the + # method and then the arguments to the scene's "play" function: + self.play(grid.shift, LEFT) + + # Both of those will interpolate between the mobject's initial + # state and whatever happens when you apply that method. + # For this example, calling grid.shift(LEFT) would shift the + # grid one unit to the left, but both of the previous calls to + # "self.play" animate that motion. + + # The same applies for any method, including those setting colors. + self.play(grid.animate.set_color(YELLOW)) + self.wait() + self.play(grid.animate.set_submobject_colors_by_gradient(BLUE, GREEN)) + self.wait() + self.play(grid.animate.set_height(TAU - MED_SMALL_BUFF)) + self.wait() + + # The method Mobject.apply_complex_function lets you apply arbitrary + # complex functions, treating the points defining the mobject as + # complex numbers. + self.play(grid.animate.apply_complex_function(np.exp), run_time=5) + self.wait() + + # Even more generally, you could apply Mobject.apply_function, + # which takes in functions form R^3 to R^3 + self.play( + grid.animate.apply_function( + lambda p: [ + p[0] + 0.5 * math.sin(p[1]), + p[1] + 0.5 * math.sin(p[0]), + p[2] + ] + ), + run_time=5, + ) + self.wait() + +The new usage in this scene is ``.get_grid()`` and ``self.play(mob.animate.method(args))``. + +- ``.get_grid()`` method will return a new mobject containing multiple copies of this one arranged in a grid. +- ``self.play(mob.animate.method(args))`` animates the method, and the details are in the comments above. + +TextExample +----------- + +.. manim-example:: TextExample + :media: ../_static/example_scenes/TextExample.mp4 + + class TextExample(Scene): + def construct(self): + text = Text("Here is a text", font="Consolas", font_size=90) + difference = Text( + """ + The most important difference between Text and TexText is that\n + you can change the font more easily, but can't use the LaTeX grammar + """, + font="Arial", font_size=24, + t2c={"Text": BLUE, "TexText": BLUE, "LaTeX": ORANGE} + ) + VGroup(text, difference).arrange(DOWN, buff=1) + self.play(Write(text)) + self.play(FadeIn(difference, UP)) + self.wait(3) + + fonts = Text( + "And you can also set the font according to different words", + font="Arial", + t2f={"font": "Consolas", "words": "Consolas"}, + t2c={"font": BLUE, "words": GREEN} + ) + slant = Text( + "And the same as slant and weight", + font="Consolas", + t2s={"slant": ITALIC}, + t2w={"weight": BOLD}, + t2c={"slant": ORANGE, "weight": RED} + ) + VGroup(fonts, slant).arrange(DOWN, buff=0.8) + self.play(FadeOut(text), FadeOut(difference, shift=DOWN)) + self.play(Write(fonts)) + self.wait() + self.play(Write(slant)) + self.wait() + +The new classes in this scene are ``Text``, ``VGroup``, ``Write``, ``FadeIn`` and ``FadeOut``. + +- ``Text`` can create text, define fonts, etc. The usage ais clearly reflected in the above examples. +- ``VGroup`` can put multiple ``VMobject`` together as a whole. In the example, the ``.arrange()`` method is called to arrange the sub-mobjects in sequence downward (``DOWN``), and the spacing is ``buff``. +- ``Write`` is an animation that shows similar writing effects. +- ``FadeIn`` fades the object in, the second parameter indicates the direction of the fade in. +- ``FadeOut`` fades out the object, the second parameter indicates the direction of the fade out. + +TexTransformExample +------------------- + +.. manim-example:: TexTransformExample + :media: ../_static/example_scenes/TexTransformExample.mp4 + + class TexTransformExample(Scene): + def construct(self): + to_isolate = ["B", "C", "=", "(", ")"] + lines = VGroup( + # Surrounding substrings with double braces + # will ensure that those parts are separated + # out in the Tex. For example, here the + # Tex will have 5 submobjects, corresponding + # to the strings [A^2, +, B^2, =, C^2] + Tex("{{A^2}} + {{B^2}} = {{C^2}}"), + Tex("{{A^2}} = {{C^2}} - {{B^2}}"), + # Alternatively, you can pass in the keyword argument + # "isolate" with a list of strings that should be out as + # their own submobject. So both lines below are equivalent + # to what you'd get by wrapping every instance of "B", "C" + # "=", "(" and ")" with double braces + Tex("{{A^2}} = (C + B)(C - B)", isolate=to_isolate), + Tex("A = \\sqrt{(C + B)(C - B)}", isolate=to_isolate) + ) + lines.arrange(DOWN, buff=LARGE_BUFF) + for line in lines: + line.set_color_by_tex_to_color_map({ + "A": BLUE, + "B": TEAL, + "C": GREEN, + }) + + play_kw = {"run_time": 2} + self.add(lines[0]) + # The animation TransformMatchingTex will line up parts + # of the source and target which have matching tex strings. + # Here, giving it a little path_arc makes each part sort of + # rotate into their final positions, which feels appropriate + # for the idea of rearranging an equation + self.play( + TransformMatchingTex( + lines[0].copy(), lines[1], + path_arc=90 * DEGREES, + ), + **play_kw + ) + self.wait() + + # Now, we could try this again on the next line... + self.play( + TransformMatchingTex(lines[1].copy(), lines[2]), + **play_kw + ) + self.wait() + # ...and this looks nice enough, but since there's no tex + # in lines[2] which matches "C^2" or "B^2", those terms fade + # out to nothing while the C and B terms fade in from nothing. + # If, however, we want the C^2 to go to C, and B^2 to go to B, + # we can specify that with a key map. + self.play(FadeOut(lines[2])) + self.play( + TransformMatchingTex( + lines[1].copy(), lines[2], + key_map={ + "C^2": "C", + "B^2": "B", + } + ), + **play_kw + ) + self.wait() + + # And to finish off, a simple TransformMatchingShapes would work + # just fine. But perhaps we want that exponent on A^2 to transform into + # the square root symbol. At the moment, lines[2] treats the expression + # A^2 as a unit, so we might create a new version of the same line which + # separates out just the A. This way, when TransformMatchingTex lines up + # all matching parts, the only mismatch will be between the "^2" from + # new_line2 and the "\sqrt" from the final line. By passing in, + # transform_mismatches=True, it will transform this "^2" part into + # the "\sqrt" part. + new_line2 = Tex("{{A}}^2 = (C + B)(C - B)", isolate=to_isolate) + new_line2.replace(lines[2]) + new_line2.match_style(lines[2]) + + self.play( + TransformMatchingTex( + new_line2, lines[3], + transform_mismatches=True, + ), + **play_kw + ) + self.wait(3) + self.play(FadeOut(lines, RIGHT)) + + # Alternatively, if you don't want to think about breaking up + # the tex strings deliberately, you can TransformMatchingShapes, + # which will try to line up all pieces of a source mobject with + # those of a target, regardless of the submobject hierarchy in + # each one, according to whether those pieces have the same + # shape (as best it can). + source = Text("the morse code", height=1) + target = Text("here come dots", height=1) + + self.play(Write(source)) + self.wait() + kw = {"run_time": 3, "path_arc": PI / 2} + self.play(TransformMatchingShapes(source, target, **kw)) + self.wait() + self.play(TransformMatchingShapes(target, source, **kw)) + self.wait() + +The new classes in this scene are ``Tex``, ``TexText``, ``TransformMatchingTex`` +and ``TransformMatchingShapes``. + +- ``Tex`` uses LaTeX to create mathematical formulas. +- ``TexText`` uses LaTeX to create text. +- ``TransformMatchingTeX`` automatically transforms sub-objects according to the similarities and differences of tex in ``Tex``. +- ``TransformMatchingShapes`` automatically transform sub-objects directly based on the similarities and differences of the object point sets. + +UpdatersExample +--------------- + +.. manim-example:: UpdatersExample + :media: ../_static/example_scenes/UpdatersExample.mp4 + + class UpdatersExample(Scene): + def construct(self): + square = Square() + square.set_fill(BLUE_E, 1) + + # On all all frames, the constructor Brace(square, UP) will + # be called, and the mobject brace will set its data to match + # that of the newly constructed object + brace = always_redraw(Brace, square, UP) + + text, number = label = VGroup( + Text("Width = "), + DecimalNumber( + 0, + show_ellipsis=True, + num_decimal_places=2, + include_sign=True, + ) + ) + label.arrange(RIGHT) + + # This ensures that the method deicmal.next_to(square) + # is called on every frame + always(label.next_to, brace, UP) + # You could also write the following equivalent line + # label.add_updater(lambda m: m.next_to(brace, UP)) + + # If the argument itself might change, you can use f_always, + # for which the arguments following the initial Mobject method + # should be functions returning arguments to that method. + # The following line ensures thst decimal.set_value(square.get_y()) + # is called every frame + f_always(number.set_value, square.get_width) + # You could also write the following equivalent line + # number.add_updater(lambda m: m.set_value(square.get_width())) + + self.add(square, brace, label) + + # Notice that the brace and label track with the square + self.play( + square.animate.scale(2), + rate_func=there_and_back, + run_time=2, + ) + self.wait() + self.play( + square.set_width(5, stretch=True), + run_time=3, + ) + self.wait() + self.play( + square.animate.set_width(2), + run_time=3 + ) + self.wait() + + # In general, you can alway call Mobject.add_updater, and pass in + # a function that you want to be called on every frame. The function + # should take in either one argument, the mobject, or two arguments, + # the mobject and the amount of time since the last frame. + now = self.time + w0 = square.get_width() + square.add_updater( + lambda m: m.set_width(w0 * math.cos(self.time - now)) + ) + self.wait(4 * PI) + +The new classes and usage in this scene are ``always_redraw()``, ``DecimalNumber``, ``.to_edge()``, +``.center()``, ``always()``, ``f_always()``, ``.set_y()`` and ``.add_updater()``. + +- ``always_redraw()`` function create a new mobject every frame. +- ``DecimalNumber`` is a variable number, speed it up by breaking it into ``Text`` characters. +- ``.to_edge()`` means to place the object on the edge of the screen. +- ``.center()`` means to place the object in the center of the screen. +- ``always(f, x)`` means that a certain function (``f(x)``) is executed every frame. +- ``f_always(f, g)`` is similar to ``always``, executed ``f(g())`` every frame. +- ``.set_y()`` means to set the ordinate of the object on the screen. +- ``.add_updater()`` sets an update function for the object. For example: ``mob1.add_updater(lambda mob: mob.next_to(mob2))`` means ``mob1.next_to(mob2)`` is executed every frame. + +CoordinateSystemExample +----------------------- + +.. manim-example:: CoordinateSystemExample + :media: ../_static/example_scenes/CoordinateSystemExample.mp4 + + class CoordinateSystemExample(Scene): + def construct(self): + axes = Axes( + # x-axis ranges from -1 to 10, with a default step size of 1 + x_range=(-1, 10), + # y-axis ranges from -2 to 10 with a step size of 0.5 + y_range=(-2, 2, 0.5), + # The axes will be stretched so as to match the specified + # height and width + height=6, + width=10, + # Axes is made of two NumberLine mobjects. You can specify + # their configuration with axis_config + axis_config={ + "stroke_color": GREY_A, + "stroke_width": 2, + }, + # Alternatively, you can specify configuration for just one + # of them, like this. + y_axis_config={ + "include_tip": False, + } + ) + # Keyword arguments of add_coordinate_labels can be used to + # configure the DecimalNumber mobjects which it creates and + # adds to the axes + axes.add_coordinate_labels( + font_size=20, + num_decimal_places=1, + ) + self.add(axes) + + # Axes descends from the CoordinateSystem class, meaning + # you can call call axes.coords_to_point, abbreviated to + # axes.c2p, to associate a set of coordinates with a point, + # like so: + dot = Dot(color=RED) + dot.move_to(axes.c2p(0, 0)) + self.play(FadeIn(dot, scale=0.5)) + self.play(dot.animate.move_to(axes.c2p(3, 2))) + self.wait() + self.play(dot.animate.move_to(axes.c2p(5, 0.5))) + self.wait() + + # Similarly, you can call axes.point_to_coords, or axes.p2c + # print(axes.p2c(dot.get_center())) + + # We can draw lines from the axes to better mark the coordinates + # of a given point. + # Here, the always_redraw command means that on each new frame + # the lines will be redrawn + h_line = always_redraw(lambda: axes.get_h_line(dot.get_left())) + v_line = always_redraw(lambda: axes.get_v_line(dot.get_bottom())) + + self.play( + ShowCreation(h_line), + ShowCreation(v_line), + ) + self.play(dot.animate.move_to(axes.c2p(3, -2))) + self.wait() + self.play(dot.animate.move_to(axes.c2p(1, 1))) + self.wait() + + # If we tie the dot to a particular set of coordinates, notice + # that as we move the axes around it respects the coordinate + # system defined by them. + f_always(dot.move_to, lambda: axes.c2p(1, 1)) + self.play( + axes.animate.scale(0.75), + axes.animate.to_corner(UL), + run_time=2, + ) + self.wait() + self.play(FadeOut(VGroup(axes, dot, h_line, v_line))) + + # Other coordinate systems you can play around with include + # ThreeDAxes, NumberPlane, and ComplexPlane. + + +GraphExample +------------ + +.. manim-example:: GraphExample + :media: ../_static/example_scenes/GraphExample.mp4 + + class GraphExample(Scene): + def construct(self): + axes = Axes((-3, 10), (-1, 8)) + axes.add_coordinate_labels() + + self.play(Write(axes, lag_ratio=0.01, run_time=1)) + + # Axes.get_graph will return the graph of a function + sin_graph = axes.get_graph( + lambda x: 2 * math.sin(x), + color=BLUE, + ) + # By default, it draws it so as to somewhat smoothly interpolate + # between sampled points (x, f(x)). If the graph is meant to have + # a corner, though, you can set use_smoothing to False + relu_graph = axes.get_graph( + lambda x: max(x, 0), + use_smoothing=False, + color=YELLOW, + ) + # For discontinuous functions, you can specify the point of + # discontinuity so that it does not try to draw over the gap. + step_graph = axes.get_graph( + lambda x: 2.0 if x > 3 else 1.0, + discontinuities=[3], + color=GREEN, + ) + + # Axes.get_graph_label takes in either a string or a mobject. + # If it's a string, it treats it as a LaTeX expression. By default + # it places the label next to the graph near the right side, and + # has it match the color of the graph + sin_label = axes.get_graph_label(sin_graph, "\\sin(x)") + relu_label = axes.get_graph_label(relu_graph, Text("ReLU")) + step_label = axes.get_graph_label(step_graph, Text("Step"), x=4) + + self.play( + ShowCreation(sin_graph), + FadeIn(sin_label, RIGHT), + ) + self.wait(2) + self.play( + ReplacementTransform(sin_graph, relu_graph), + FadeTransform(sin_label, relu_label), + ) + self.wait() + self.play( + ReplacementTransform(relu_graph, step_graph), + FadeTransform(relu_label, step_label), + ) + self.wait() + + parabola = axes.get_graph(lambda x: 0.25 * x**2) + parabola.set_stroke(BLUE) + self.play( + FadeOut(step_graph), + FadeOut(step_label), + ShowCreation(parabola) + ) + self.wait() + + # You can use axes.input_to_graph_point, abbreviated + # to axes.i2gp, to find a particular point on a graph + dot = Dot(color=RED) + dot.move_to(axes.i2gp(2, parabola)) + self.play(FadeIn(dot, scale=0.5)) + + # A value tracker lets us animate a parameter, usually + # with the intent of having other mobjects update based + # on the parameter + x_tracker = ValueTracker(2) + f_always( + dot.move_to, + lambda: axes.i2gp(x_tracker.get_value(), parabola) + ) + + self.play(x_tracker.animate.set_value(4), run_time=3) + self.play(x_tracker.animate.set_value(-2), run_time=3) + self.wait() + +SurfaceExample +-------------- + +.. manim-example:: SurfaceExample + :media: ../_static/example_scenes/SurfaceExample.mp4 + + class SurfaceExample(Scene): + CONFIG = { + "camera_class": ThreeDCamera, + } + + def construct(self): + surface_text = Text("For 3d scenes, try using surfaces") + surface_text.fix_in_frame() + surface_text.to_edge(UP) + self.add(surface_text) + self.wait(0.1) + + torus1 = Torus(r1=1, r2=1) + torus2 = Torus(r1=3, r2=1) + sphere = Sphere(radius=3, resolution=torus1.resolution) + # You can texture a surface with up to two images, which will + # be interpreted as the side towards the light, and away from + # the light. These can be either urls, or paths to a local file + # in whatever you've set as the image directory in + # the custom_config.yml file + + # day_texture = "EarthTextureMap" + # night_texture = "NightEarthTextureMap" + day_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Whole_world_-_land_and_oceans.jpg/1280px-Whole_world_-_land_and_oceans.jpg" + night_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/The_earth_at_night.jpg/1280px-The_earth_at_night.jpg" + + surfaces = [ + TexturedSurface(surface, day_texture, night_texture) + for surface in [sphere, torus1, torus2] + ] + + for mob in surfaces: + mob.shift(IN) + mob.mesh = SurfaceMesh(mob) + mob.mesh.set_stroke(BLUE, 1, opacity=0.5) + + # Set perspective + frame = self.camera.frame + frame.set_euler_angles( + theta=-30 * DEGREES, + phi=70 * DEGREES, + ) + + surface = surfaces[0] + + self.play( + FadeIn(surface), + ShowCreation(surface.mesh, lag_ratio=0.01, run_time=3), + ) + for mob in surfaces: + mob.add(mob.mesh) + surface.save_state() + self.play(Rotate(surface, PI / 2), run_time=2) + for mob in surfaces[1:]: + mob.rotate(PI / 2) + + self.play( + Transform(surface, surfaces[1]), + run_time=3 + ) + + self.play( + Transform(surface, surfaces[2]), + # Move camera frame during the transition + frame.animate.increment_phi(-10 * DEGREES), + frame.animate.increment_theta(-20 * DEGREES), + run_time=3 + ) + # Add ambient rotation + frame.add_updater(lambda m, dt: m.increment_theta(-0.1 * dt)) + + # Play around with where the light is + light_text = Text("You can move around the light source") + light_text.move_to(surface_text) + light_text.fix_in_frame() + + self.play(FadeTransform(surface_text, light_text)) + light = self.camera.light_source + self.add(light) + light.save_state() + self.play(light.animate.move_to(3 * IN), run_time=5) + self.play(light.animate.shift(10 * OUT), run_time=5) + + drag_text = Text("Try moving the mouse while pressing d or s") + drag_text.move_to(light_text) + drag_text.fix_in_frame() + + self.play(FadeTransform(light_text, drag_text)) + self.wait() + +This scene shows an example of using a three-dimensional surface, and +the related usage has been briefly described in the notes. + +- ``.fix_in_frame()`` makes the object not change with the view angle of the screen, and is always displayed at a fixed position on the screen. + +OpeningManimExample +------------------- + +.. manim-example:: OpeningManimExample + :media: ../_static/example_scenes/OpeningManimExample.mp4 + + + class OpeningManimExample(Scene): + def construct(self): + intro_words = Text(""" + The original motivation for manim was to + better illustrate mathematical functions + as transformations. + """) + intro_words.to_edge(UP) + + self.play(Write(intro_words)) + self.wait(2) + + # Linear transform + grid = NumberPlane((-10, 10), (-5, 5)) + matrix = [[1, 1], [0, 1]] + linear_transform_words = VGroup( + Text("This is what the matrix"), + IntegerMatrix(matrix, include_background_rectangle=True), + Text("looks like") + ) + linear_transform_words.arrange(RIGHT) + linear_transform_words.to_edge(UP) + linear_transform_words.set_stroke(BLACK, 10, background=True) + + self.play( + ShowCreation(grid), + FadeTransform(intro_words, linear_transform_words) + ) + self.wait() + self.play(grid.animate.apply_matrix(matrix), run_time=3) + self.wait() + + # Complex map + c_grid = ComplexPlane() + moving_c_grid = c_grid.copy() + moving_c_grid.prepare_for_nonlinear_transform() + c_grid.set_stroke(BLUE_E, 1) + c_grid.add_coordinate_labels(font_size=24) + complex_map_words = TexText(""" + Or thinking of the plane as $\\mathds{C}$,\\\\ + this is the map $z \\rightarrow z^2$ + """) + complex_map_words.to_corner(UR) + complex_map_words.set_stroke(BLACK, 5, background=True) + + self.play( + FadeOut(grid), + Write(c_grid, run_time=3), + FadeIn(moving_c_grid), + FadeTransform(linear_transform_words, complex_map_words), + ) + self.wait() + self.play( + moving_c_grid.animate.apply_complex_function(lambda z: z**2), + run_time=6, + ) + self.wait(2) + +This scene is a comprehensive application of a two-dimensional scene. + +After seeing these scenes, you have already understood part of the +usage of manim. For more examples, see `the video code of 3b1b `_. diff --git a/docs/source/getting_started/index.rst b/docs/source/getting_started/index.rst deleted file mode 100644 index 11f8e9d0..00000000 --- a/docs/source/getting_started/index.rst +++ /dev/null @@ -1,18 +0,0 @@ -Getting Started -=============== - -Todd Zimmerman put together `a very nice tutorial`_ on getting started with -``manim``, which has been updated to run on python 3.7. Note that you'll want -to change `from big_ol_pile_of_manim_imports import *` to `from -manimlib.imports import *` to work with the current codebase. - -.. _a very nice tutorial: https://talkingphysics.wordpress.com/2019/01/08/getting-started-animating-with-manim-and-python-3-7/ - -.. toctree:: - :caption: Contents - :maxdepth: 2 - - learning_by_example - mathematical_objects - animating_mobjects - making_a_scene diff --git a/docs/source/getting_started/installation.rst b/docs/source/getting_started/installation.rst new file mode 100644 index 00000000..6600cc1e --- /dev/null +++ b/docs/source/getting_started/installation.rst @@ -0,0 +1,69 @@ +Installation +============ + +Manim runs on Python 3.6 or higher (Python 3.8 is recommended). + +System requirements are: + +- `FFmpeg `__ +- `OpenGL `__ (included in python package ``PyOpenGL``) +- `LaTeX `__ (optional, if you want to use LaTeX) +- `Pango `__ (only for Linux) + + +Directly +-------- + +.. code-block:: sh + + # Install manimgl + pip install manimgl + + # Try it out + manimgl + +If you want to hack on manimlib itself, clone this repository and in +that directory execute: + +.. code-block:: sh + + # Install python requirements + pip install -e . + + # Try it out + manimgl example_scenes.py OpeningManimExample + # or + manim-render example_scenes.py OpeningManimExample + +If you run the above command and no error message appears, +then you have successfully installed all the environments required by manim. + +Directly (Windows) +------------------ + +1. `Install + FFmpeg `__, and make sure that its path is in the PATH environment variable. +2. Install a LaTeX distribution. + `TeXLive-full `__ is recommended. +3. Install the remaining Python packages. + +.. code-block:: sh + + git clone https://github.com/3b1b/manim.git + cd manim + pip install -e . + manimgl example_scenes.py OpeningManimExample + +For Anaconda +------------ + +- Install FFmpeg and LaTeX as above. +- Create a conda environment using + +.. code-block:: sh + + git clone https://github.com/3b1b/manim.git + cd manim + conda create -n manim python=3.8 + conda activate manim + pip install -e . diff --git a/docs/source/getting_started/learning_by_example.rst b/docs/source/getting_started/learning_by_example.rst deleted file mode 100644 index dee86d7c..00000000 --- a/docs/source/getting_started/learning_by_example.rst +++ /dev/null @@ -1,131 +0,0 @@ -Learning by Example -=================== - -SquareToCircle --------------- - -``example_scenes.py`` contains simple examples that we can use to learn about manim. - -Go ahead and try out the ``SquareToCircle`` scene by running it with ``$ manim example_scenes.py SquareToCircle -p`` -in manim directory. - -.. code-block:: python - :linenos: - - from manimlib.imports import * - - class SquareToCircle(Scene): - def construct(self): - circle = Circle() - square = Square() - square.flip(RIGHT) - square.rotate(-3 * TAU / 8) - circle.set_fill(PINK, opacity=0.5) - - self.play(ShowCreation(square)) - self.play(Transform(square, circle)) - self.play(FadeOut(square)) - -.. raw:: html - - - - -.. note:: - - The flag ``-p`` plays the rendered video with default video player. - - Other frequently used flags are: - - * ``-l`` for rendering video in lower resolution (which renders faster) - * ``-s`` to show the last frame of the video. - - Run ``manim -h`` all the available flags (``python -m manim -h`` if you installed it to a venv) - - -Let's step through each line of ``SquareToCircle`` - -.. code-block:: python - :lineno-start: 3 - - class SquareToCircle(Scene): - -You create videos in manim by writing :class:`~scene.scene.Scene` classes. - -Each :class:`~scene.scene.Scene` in manim is self-contained. That means everything -you created under this scene does not exist outside the class. - -.. code-block:: python - :lineno-start: 4 - - def construct(self): - -:meth:`~scene.scene.Scene.construct` specifies what is displayed on the screen -when the :class:`~scene.scene.Scene` is rendered to video. - -.. code-block:: python - :lineno-start: 5 - - circle = Circle() - square = Square() - -``Circle()`` and ``Square()`` create :class:`~mobject.geometry.Circle` and :class:`~mobject.geometry.Square`. - -Both of these are instances of :class:`~mobject.mobject.Mobject` subclasses, the base class for objects in manim. Note -that instantiating a :class:`~mobject.mobject.Mobject` does not add it to the -:class:`~scene.scene.Scene`, so you wouldn't see anything if you were to render -the :class:`~scene.scene.Scene` at this point. - -.. code-block:: python - :lineno-start: 7 - - square.flip(RIGHT) - square.rotate(-3 * TAU / 8) - circle.set_fill(PINK, opacity=0.5) - -``flip()`` ``rotate()`` ``set_fill()`` apply various modifications to the mobjects before animating -them. The call to :meth:`~mobject.mobject.Mobject.flip` flips the -:class:`~mobject.geometry.Square` across the RIGHT vector. This is equivalent -to a refection across the x-axis. - -The call to :meth:`~mobject.mobject.Mobject.rotate` rotates the -:class:`~mobject.geometry.Square` 3/8ths of a full rotation counterclockwise. - -The call to :meth:`~mobject.mobject.Mobject.set_fill` sets -the fill color for the :class:`~mobject.geometry.Circle` to pink, and its opacity to 0.5. - -.. code-block:: python - :lineno-start: 11 - - self.play(ShowCreation(square)) - self.play(Transform(square, circle)) - self.play(FadeOut(square)) - -To generated animation, :class:`~animation.animation.Animation` classes are used. - -Each :class:`~animation.animation.Animation` takes one or more :class:`~mobject.mobject.Mobject` instances as arguments, which it animates -when passed to :meth:`~scene.scene.Scene.play`. This is how video is typically -created in manim. - -:class:`~mobject.mobject.Mobject` instances are automatically -added to the :class:`~scene.scene.Scene` when they are animated. You can add a -:class:`~mobject.mobject.Mobject` to the :class:`~scene.scene.Scene` manually -by passing it as an argument to :meth:`~scene.scene.Scene.add`. - - -:class:`~animation.creation.ShowCreation` draws a :class:`~mobject.mobject.Mobject` to the screen. - -:class:`~animation.transform.Transform` morphs one :class:`~mobject.mobject.Mobject` into another. - -:class:`~animation.creation.FadeOut` fades a :class:`~mobject.mobject.Mobject` out of the :class:`~scene.scene.Scene`. - -.. note:: - - Only the first argument to :class:`~animation.transform.Transform` is modified, - the second is not added to the :class:`~scene.scene.Scene`. :class:`~animation.tranform.Transform` - only changes the appearance but not the underlying properties. - - After the call to ``transform()`` ``square`` is still a :class:`~mobject.geometry.Square` instance - but with the shape of :class:`~mobject.geometry.Circle`. diff --git a/docs/source/getting_started/making_a_scene.rst b/docs/source/getting_started/making_a_scene.rst deleted file mode 100644 index 619cb571..00000000 --- a/docs/source/getting_started/making_a_scene.rst +++ /dev/null @@ -1,15 +0,0 @@ -Making a Scene -============== - -A scene is what renders when manim is executed. Each scene contains mobjects, which can then be animated as -previously explained. In code, a scene is a class that extends ``Scene`` and implements the ``construct`` -function, like so. Manim will execute this function to render the scene. - -.. code-block:: python - :linenos: - - from manimlib.imports import * - - class ExampleScene(Scene): - def construct(self): - # Add and animate mobjects here \ No newline at end of file diff --git a/docs/source/getting_started/mathematical_objects.rst b/docs/source/getting_started/mathematical_objects.rst deleted file mode 100644 index ebe595b6..00000000 --- a/docs/source/getting_started/mathematical_objects.rst +++ /dev/null @@ -1,13 +0,0 @@ -Mathematical Objects -==================== - -Everything that appears on screen in a manim video is a -:class:`~mobject.mobject.Mobject`, or Mathematical Object. A -:class:`~mobject.mobject.Mobject`'s appearance is determined by 3 -factors: - -* ``m.points``, an Nx3 ``numpy.array`` specifying how to draw ``m`` -* ``m``'s style attributes, such as ``m.color``, ``m.stroke_width``, and - ``m.fill_opacity`` -* ``m.submobjects``, a list of :class:`~mobject.mobject.Mobject` instances that - are considered part of ``m`` diff --git a/docs/source/getting_started/quickstart.rst b/docs/source/getting_started/quickstart.rst new file mode 100644 index 00000000..aa68950f --- /dev/null +++ b/docs/source/getting_started/quickstart.rst @@ -0,0 +1,256 @@ +Quick Start +=========== + +After installing the manim environment according to the instructions on the +:doc:`installation` page, you can try to make a scene yourself from scratch. + +First, create a new ``.py`` file (such as ``start.py``) according to the following +directory structure: + +.. code-block:: text + :emphasize-lines: 8 + + manim/ + ├── manimlib/ + │ ├── animation/ + │ ├── ... + │ ├── default_config.yml + │ └── window.py + ├── custom_config.yml + └── start.py + +And paste the following code (I will explain the function of each line in detail later): + +.. code-block:: python + :linenos: + + from manimlib import * + + class SquareToCircle(Scene): + def construct(self): + circle = Circle() + circle.set_fill(BLUE, opacity=0.5) + circle.set_stroke(BLUE_E, width=4) + + self.add(circle) + +And run this command: + +.. code-block:: sh + + manimgl start.py SquareToCircle + +A window will pop up on the screen. And then you can : + +- scroll the middle mouse button to move the screen up and down +- hold down the :kbd:`z` on the keyboard while scrolling the middle mouse button to zoom the screen +- hold down the :kbd:`s` key on the keyboard and move the mouse to pan the screen +- hold down the :kbd:`d` key on the keyboard and move the mouse to change the three-dimensional perspective. + +Finally, you can close the window and exit the program by pressing :kbd:`q`. + +Run this command again: + +.. code-block:: sh + + manimgl start.py SquareToCircle -os + +At this time, no window will pop up. When the program is finished, this rendered +image will be automatically opened (saved in the subdirectory ``images/`` of the same +level directory of ``start.py`` by default): + +.. image:: ../_static/quickstart/SquareToCircle.png + :align: center + +Make an image +------------- + +Next, let's take a detailed look at what each row does. + +**Line 1**: + +.. code-block:: python + + from manimlib import * + +This will import all the classes that may be used when using manim. + +**Line 3**: + +.. code-block:: python + + class SquareToCircle(Scene): + +Create a :class:`Scene` subclass ``SquareToCircle``, which will be +the scene you write and render. + +**Line 4**: + +.. code-block:: python + + def construct(self): + +Write the ``construct()`` method, the content of which will determine +how to create the mobjects in the screen and what operations need to be performed. + +**Line 5**: + +.. code-block:: python + + circle = Circle() + +Create a circle (an instance of the :class:`Circle` class), called ``circle`` + +**Line 6~7**: + +.. code-block:: python + + circle.set_fill(BLUE, opacity=0.5) + circle.set_stroke(BLUE_E, width=4) + +Set the circle style by calling the circle's method. + +- The ``.set_fill()`` method sets the fill color of this circle to blue (``BLUE``, defined in :doc:`../documentation/constants`), and the fill transparency to 0.5. +- The ``.set_stroke()`` method sets the stroke color of this circle to dark blue (``BLUE_E``, defined in :doc:`../documentation/constants`), and the stroke width to 4. + +**Line 9**: + +.. code-block:: python + + self.add(circle) + +Add this circle to the screen through the ``.add()`` method of :class:`Scene`. + +Add animations +-------------- + +Let's change some codes and add some animations to make videos instead of just pictures. + +.. code-block:: python + :linenos: + + from manimlib import * + + class SquareToCircle(Scene): + def construct(self): + circle = Circle() + circle.set_fill(BLUE, opacity=0.5) + circle.set_stroke(BLUE_E, width=4) + square = Square() + + self.play(ShowCreation(square)) + self.wait() + self.play(ReplacementTransform(square, circle)) + self.wait() + +Run this command this time: + +.. code-block:: sh + + manimgl start.py SquareToCircle + +The pop-up window will play animations of drawing a square and transforming +it into a circle. If you want to save this video, run: + +.. code-block:: sh + + manimgl start.py SquareToCircle -o + +This time there will be no pop-up window, but the video file (saved in the subdirectory +``videos/`` of the same level directory of ``start.py`` by default) will be automatically +opened after the operation is over: + +.. raw:: html + + + +Let's take a look at the code this time. The first 7 lines are the same as the previous +ones, and the 8th line is similar to the 5th line, which creates an instance of the +:class:`Square` class and named it ``square``. + +**Line 10**: + +.. code-block:: python + + self.play(ShowCreation(square)) + +An animation is played through :class:`Scene`'s ``.play()`` method. :class:`ShowCreation` +is an animation that shows the process of creating a given mobject. +``self.play(ShowCreation(square))`` is to play the animation of creating ``square``. + +**Line 11**: + +.. code-block:: python + + self.wait() + +Use :class:`Scene`'s ``.wait()`` method to pause (default 1s), you can pass in +parameters to indicate the pause time (for example, ``self.wait(3)`` means pause for 3s). + +**Line 12**: + +.. code-block:: python + + self.play(ReplacementTransform(square, circle)) + +Play the animation that transforms ``square`` into ``circle``. +``ReplacementTransform(A, B)`` means to transform A into B's pattern and replace A with B. + +**Line 13**: Same as line 11, pause for 1s. + + +Enable interaction +------------------ + +Interaction is a new feature of the new version. You can add the following line +at the end of the code to enable interaction: + +.. code-block:: python + + self.embed() + +Then run ``manimgl start.py SquareToCircle``. + +After the previous animation is executed, the ipython terminal will be opened on +the command line. After that, you can continue to write code in it, and the statement +you entered will be executed immediately after pressing :kbd:`Enter`. + +For example: input the following lines (without comment lines) into it respectively +(``self.play`` can be abbreviated as ``play`` in this mode): + +.. code-block:: python + + # Stretched 4 times in the vertical direction + play(circle.animate.stretch(4, dim=0})) + # Rotate the ellipse 90° + play(Rotate(circle, TAU / 4)) + # Move 2 units to the right and shrink to 1/4 of the original + play(circle.animate.shift(2 * RIGHT), circle.animate.scale(0.25)) + # Insert 10 curves into circle for non-linear transformation (no animation will play) + circle.insert_n_curves(10) + # Apply a complex transformation of f(z)=z^2 to all points on the circle + play(circle.animate.apply_complex_function(lambda z: z**2)) + # Close the window and exit the program + exit() + +You will get an animation similar to the following: + +.. raw:: html + + + +If you want to enter the interactive mode directly, you don't have to write an +empty scene containing only ``self.embed()``, you can directly run the following command +(this will enter the ipython terminal while the window pops up): + +.. code-block:: sh + + manimgl + +You succeeded! +-------------- + +After reading the above content, you already know how to use manim. +Below you can see some examples, in the :doc:`example_scenes` page. +But before that, you'd better have a look at the :doc:`configuration` of manim. + diff --git a/docs/source/getting_started/structure.rst b/docs/source/getting_started/structure.rst new file mode 100644 index 00000000..f1a05623 --- /dev/null +++ b/docs/source/getting_started/structure.rst @@ -0,0 +1,127 @@ +Manim's structure +================= + + +Manim's directory structure +--------------------------- + +The manim directory looks very complicated, with a lot of files, +but the structure is clear. + +Below is the directory structure of manim: + +.. code-block:: text + + manimlib/ # manim library + ├── __init__.py + ├── __main__.py + ├── default_config.yml # Default configuration file + ├── config.py # Process CLI flags + ├── constants.py # Defined some constants + ├── extract_scene.py # Extract and run the scene + ├── shader_wrapper.py # Shaders' Wrapper for convenient control + ├── window.py # Playback window + ├── tex_templates/ # Templates preset for LaTeX + │ ├── tex_templates.tex # Tex template (will be compiled with latex, default) + │ └── ctex_templates.tex # Tex template that support Chinese (will be compiled with xelatex) + ├── camera/ + │ └── camera.py # Including Camera and CameraFrame + ├── scene/ + │ ├── scene_file_writer.py # Used to write scene to video file + │ ├── scene.py # The basic Scene class + │ ├── three_d_scene.py # Three-dimensional scene + │ ├── sample_space_scene.py # Probability related sample space scene + │ └── vector_space_scene.py # Vector field scene + ├── animation/ + │ ├── animation.py # The basic class of animation + │ ├── composition.py # Animation group + │ ├── creation.py # Animation related to Create + │ ├── fading.py # Fade related animation + │ ├── growing.py # Animation related to Grow + │ ├── indication.py # Some animations for emphasis + │ ├── movement.py # Animation related to movement + │ ├── numbers.py # Realize changes to DecimalNumber + │ ├── rotation.py # Animation related to rotation + │ ├── specialized.py # Some uncommon animations for special projects + │ ├── transform_matching_parts.py # Transform which can automatically match parts + │ ├── transform.py # Some Transforms + │ └── update.py # Realize update from function + ├── mobject/ + │ ├── mobject.py # The basic class of all math object + │ ├── types/ # 4 types of mobject + │ │ ├── dot_cloud.py # Dot cloud (an subclass of PMobject) + │ │ ├── image_mobject.py # Insert pictures + │ │ ├── point_cloud_mobject.py # PMobject (mobject composed of points) + │ │ ├── surface.py # ParametricSurface + │ │ └── vectorized_mobject.py # VMobject (vectorized mobject) + │ ├── svg/ # mobject related to svg + │ │ ├── svg_mobject.py # SVGMobject + │ │ ├── brace.py # Brace + │ │ ├── drawings.py # Some special mobject of svg image + │ │ ├── tex_mobject.py # Tex and TexText implemented by LaTeX + │ │ └── text_mobject.py # Text implemented by manimpango + │ ├── changing.py # Dynamically changing mobject + │ ├── coordinate_systems.py # coordinate system + │ ├── frame.py # mobject related to frame + │ ├── functions.py # ParametricFunction + │ ├── geometry.py # geometry mobjects + │ ├── matrix.py # matrix + │ ├── mobject_update_utils.py # some defined updater + │ ├── number_line.py # Number line + │ ├── numbers.py # Numbers that can be changed + │ ├── probability.py # mobject related to probability + │ ├── shape_matchers.py # mobject adapted to the size of other objects + │ ├── three_dimensions.py # Three-dimensional objects + │ ├── value_tracker.py # ValueTracker which storage number + │ └── vector_field.py # VectorField + ├── once_useful_constructs/ # 3b1b's Common scenes written for some videos + │ └── ... + ├── shaders/ # GLSL scripts for rendering + │ ├── simple_vert.glsl # a simple glsl script for position + │ ├── insert/ # glsl scripts to be inserted in other glsl scripts + │ │ ├── NOTE.md # explain how to insert glsl scripts + │ │ └── ... # useful scripts + │ ├── image/ # glsl for images + │ │ └── ... # containing shaders for vertex and fragment + │ ├── quadratic_bezier_fill/ # glsl for the fill of quadratic bezier curve + │ │ └── ... # containing shaders for vertex, fragment and geometry + │ ├── quadratic_bezier_stroke/ # glsl for the stroke of quadratic bezier curve + │ │ └── ... # containing shaders for vertex, fragment and geometry + │ ├── surface/ # glsl for surfaces + │ │ └── ... # containing shaders for vertex and fragment + │ ├── textured_surface/ # glsl for textured_surface + │ │ └── ... # containing shaders for vertex and fragment + │ └── true_dot/ # glsl for a dot + │ └── ... # containing shaders for vertex, fragment and geometry + └── utils/ # Some useful utility functions + ├── bezier.py # For bezier curve + ├── color.py # For color + ├── config_ops.py # Process CONFIG + ├── customization.py # Read from custom_config.yml + ├── debug.py # Utilities for debugging in program + ├── family_ops.py # Process family members + ├── file_ops.py # Process files and directories + ├── images.py # Read image + ├── init_config.py # Configuration guide + ├── iterables.py # Functions related to list/dictionary processing + ├── paths.py # Curve path + ├── rate_functions.py # Some defined rate_functions + ├── simple_functions.py # Some commonly used functions + ├── sounds.py # Process sounds + ├── space_ops.py # Space coordinate calculation + ├── strings.py # Process strings + └── tex_file_writing.py # Use LaTeX to write strings as svg + +Inheritance structure of manim's classes +---------------------------------------- + +`Here `_ +is a pdf showed inheritance structure of manim's classes, large, +but basically all classes have included: + +.. image:: ../_static/manim_shaders_structure.png + +Manim execution process +----------------------- + +.. image:: ../_static/manim_shaders_process_en.png diff --git a/docs/source/getting_started/whatsnew.rst b/docs/source/getting_started/whatsnew.rst new file mode 100644 index 00000000..200cd6ad --- /dev/null +++ b/docs/source/getting_started/whatsnew.rst @@ -0,0 +1,144 @@ +What's new +========== + +Usage changes of new version manim +---------------------------------- + +There are many changes in the new version of manim, and here are only the changes that +may have an impact at the code writing level. + +Some of the changes here may not have any major impact on the use, and some changes +that affect the use are not mentioned below. + +This document is for reference only, see the source code for details. + +- ``Animation`` + + - Added ``Fade`` as the parent class of ``FadeIn`` and ``FadeOut`` + - ``FadeIn`` and ``FadeOut`` can be passed in ``shift`` and ``scale`` parameters + - Deleted ``FadeInFrom, FadeInFromDown, FadeOutAndShift, FadeOutAndShiftDown, FadeInFromLarge``, these can be used ``FadeIn, FadeOut`` to achieve the same effect more easily + - Added ``FadeTransform`` to cross fade between two objects, and subclass ``FadeTransformPieces`` + - Added ``CountInFrom(decimal_mob, source_number=0)`` to count ``decimal_mob`` from ``source_number`` to the current value + - ``Rotating`` can directly pass in ``angle`` and ``axis`` without writing keywords ``angle=, axis=`` + - ``Rotate`` has become a subclass of ``Rotating``, and the distortion effect in ``Transform`` will not appear + - Removed ``MoveCar`` animation + - Added ``TransformMatchingShapes(mobject, target_mobject)`` and ``TransformMatchingTex(mobject, target_mobject)`` + +- ``Camera`` + + - Removed all camera classes except ``Camera`` (``MappingCamera``, ``MovingCamera``, ``MultiCamera``) and all functions in ``ThreeDCamera`` + - Implemented ``CameraFrame`` (as a ``Mobject``) + + - Can be called by ``self.camera.frame`` in ``Scene`` + - All methods of ``Mobject`` can be used, such as ``.shift()``, ``.scale()``, etc. + - Call ``.to_default_state()`` to place in the default position + - Set the Euler angles of the camera by ``.set_euler_angles(theta, phi, gamma)`` + - Set three single Euler angles by ``.set_theta(theta)``, ``.set_phi(phi)``, ``.set_gamma(gamma)`` + - Use ``.increment_theta(dtheta)``, ``.increment_phi(dphi)``, ``.increment_gamma(gamma)`` to increase the three Euler angles by a certain value. Can be used to realize automatic rotation ``self.camera.frame.add_updater(lambda mob, dt: mob.increment_theta(0.1 * dt))`` + + - ``Camera`` adds a light source, which is a ``Point``, which can be called by ``self.camera.light_source`` in ``Scene`` to move and so on. The default position is ``(- 10, 10, 10)`` + +- Delete ``Container`` +- ``Mobject`` + + - ``svg`` related + + - Added ``Checkmark`` and ``Exmark`` + - Some unnecessary classes have been removed from ``drawings.py`` + - Removed ``Code`` and ``Paragraph`` (by mistake) + - ``TexMobject`` is renamed to ``Tex``, ``TextMobject`` is renamed to ``TexText`` + - ``font_size`` has been added to ``Tex``, ``TexText`` and ``Text`` + - ``Tex`` and ``TexText`` added ``isolate``, which is a list, which will be automatically split + + - Mobject ``types`` + + - Added a new class ``Surface``, which is the parent class of ``ParametricSurface`` and ``TexturedSurface``. + - Added the group ``SGroup`` for ``Surface`` + - Added ``TexturedSurface(uv_surface, image_file, dark_image_file=None)``, where ``uv_surface`` is a ``Surface``, ``image_file`` is the image to be posted, and ``dark_image_file`` is the image to be posted in the dark (default and ``image_file`` is the same) + - Deleted ``Mobject1D``, ``Mobject2D``, ``PointCloudDot`` + - Added ``DotCloud`` (a ``PMobject``), which has been greatly optimized + - Removed ``AbstractImageMobject``, ``ImageMobjectFromCamera`` + - Removed ``sheen`` from ``VMobject`` + + - ``Mobject`` + + - Added ``gloss`` and ``shadow``, which are the numbers between ``[0, 1]`` respectively. There are four methods of ``.get_gloss()``, ``.set_gloss(gloss)``, ``.get_shadow()``, ``.set_shadow(shadow)`` + - Added ``.get_grid(n_rows, n_cols)`` to copy into grid + - Added ``.set_color_by_code(glsl_code)`` to use GLSL code to change the color + - Added ``.set_color_by_xyz_func(glsl_snippet, min_value=-5.0, max_value=5.0, colormap="viridis")`` to pass in GLSL expression in the form of ``x,y,z``, the return value should be a floating point number + + - Coordinate system (including ``Axes``, ``ThreeDAxes``, ``NumberPlane``, ``ComplexPlane``) + + - No longer use ``x_min``, ``x_max``, ``y_min``, ``y_max``, but use ``x_range``, ``y_range`` as a ``np.array()``, containing three numbers ``np.array([ Minimum, maximum, step size])`` + - Added the abbreviation ``.i2gp(x, graph)`` of ``.input_to_graph_point(x, graph)`` + - Added some functions of the original ``GraphScene`` + + - Added ``.get_v_line(point)``, ``.get_h_line(point)`` to return the line from ``point`` to the two coordinate axes, and specify the line type through the keyword argument of ``line_func`` (default ``DashedLine``) + - Added ``.get_graph_label(graph, label, x, direction, buff, color)`` to return the label added to the image + - Added ``.get_v_line_to_graph(x, graph)``, ``.get_h_line_to_graph(x, graph)`` to return the line from the point with the abscissa of ``x`` on the ``graph`` to the two- axis line + - Added ``.angle_of_tangent(x, graph, dx=EPSILON)``, returns the inclination angle of ``graph`` at ``x`` + - Added ``.slope_of_tangent(x, graph, dx=EPSILON)``, returns the slope of tangent line of ``graph`` at ``x`` + - Added ``.get_tangent_line(x, graph, length=5)`` to return the tangent line of ``graph`` at ``x`` + - Added ``.get_riemann_rectangles(graph, x_range, dx, input_sample_type, ...)`` to return Riemann rectangles (a ``VGroup``) + + - The attribute ``number_line_config`` of ``Axes`` is renamed to ``axis_config`` + - ``Axes`` original ``.get_coordinate_labels(x_values, y_values)`` method was renamed to ``.add_coordinate_labels(x_values, y_values)`` (but it is not added to the screen) + - ``.add_coordinate_labels(numbers)`` of ``ComplexPlane`` will directly add the coordinates to the screen + + - ``NumberLine`` + + - No longer use ``x_min``, ``x_max``, ``tick_frequency``, but use ``x_range``, which is an array containing three numbers ``[min, max, step]`` + - The original ``label_direction`` attribute changed to the ``line_to_number_direction`` attribute + - Replace ``tip_width`` and ``tip_height`` with ``tip_config`` (dictionary) attributes + - The original ``exclude_zero_from_default`` attribute is modified to the ``numbers_to_exclude`` attribute (default is None) + - The original ``.add_tick_marks()`` method was changed to the ``.add_ticks()`` method + - Delete the ``.get_number_mobjects(*numbers)`` method, only use the ``.add_numbers(x_values=None, excluding=None)`` method + + - Three-dimensional objects + + - Added ``SurfaceMesh(uv_surface)``, pass in a ``Surface`` to generate its uv mesh + - ``ParametricSurface`` no longer uses ``u_min, u_max, v_min, v_max``, but instead uses ``u_range, v_range``, which is a tuple (``(min, max)``), and ``resolution`` can be set larger, don’t worry Speed ​​issue + - Added ``Torus``, controlled by ``r1, r2`` keyword parameters + - Added ``Cylinder``, controlled by ``height, radius`` keyword parameters + - Added ``Line3D`` (extremely thin cylinder), controlled by the ``width`` keyword parameter + - Added ``Disk3D``, controlled by ``radius`` keyword parameter + - Add ``Square3D``, controlled by ``side_length`` keyword parameter + - Improved ``Cube`` and ``Prism``, the usage remains unchanged + + - Other objects + + - ``ParametricFunction`` is renamed to ``ParametricCurve``. Instead of using ``t_min, t_max, step_size``, use ``t_range``, which is an array of three numbers (``[t_min, t_max, step_size]``). ``dt`` was renamed to ``epsilon``. Other usage remains unchanged + - All ``TipableVMobject`` can pass in ``tip_length`` to control the style of ``tip`` + - ``Line`` adds ``.set_points_by_ends(start, end, buff=0, path_arc=0)`` method + - ``Line`` added ``.get_projection(point)`` to return the projection position of ``point`` on a straight line + - ``Arrow`` adds three attributes of ``thickness, tip_width_ratio, tip_angle`` + - ``CubicBezier`` is changed to ``a0, h0, h1, a1``, that is, only a third-order Bezier curve is supported + - ``Square`` can be initialized directly by passing in ``side_length`` instead of using the keyword ``side_length=`` + - ``always_redraw(func, *args, **kwargs)`` supports incoming parameters ``*args, **kwargs`` + - The ``digit_to_digit_buff`` property of ``DecimalNumber`` has been renamed to ``digit_buff_per_font_unit``, and the ``.scale()`` method has been improved + - ``ValueTracker`` adds ``value_type`` attribute, the default is ``np.float64`` + +- ``Scene`` + + - Removed all functions of ``GraphScene`` (moved to ``once_useful_constructs``), ``MovingCameraScene``, ``ReconfigurableScene``, ``SceneFromVideo``, ``ZoomedScene``, and ``ThreeDScene``. Because these can basically be achieved by adjusting ``CameraFrame`` (``self.camera.frame``) + - Currently ``SampleSpaceScene`` and ``VectorScene`` have not been changed for the new version, so it is not recommended to use (only ``Scene`` is recommended) + - Fix the export of gif, just use the ``-i`` option directly + - Added the ``.interact()`` method, during which the mouse and keyboard can be used to continue the interaction, which will be executed by default after the scene ends + - Added ``.embed()`` method, open iPython terminal to enter interactive mode + - Added ``.save_state()`` method to save the current state of the scene + - Added ``.restore()`` method to restore the entire scene to the saved state + +- ``utils`` + + - A series of functions related to second-order Bezier have been added to ``utils/bezier.py`` + - Added a function to read color map from ``matplotlib`` in ``utils/color.py`` + - Added a series of related functions for processing folders/custom styles/object families + - ``resize_array``, ``resize_preserving_order``, ``resize_with_interpolation`` three functions have been added to ``utils/iterables.py`` + - The definition of ``smooth`` is updated in ``utils/rate_functions.py`` + - ``clip(a, min_a, max_a)`` function has been added to ``utils/simple_functions.py`` + - Some functions have been improved in ``utils/space_ops.py``, some functions for space calculation, and functions for processing triangulation have been added + +- ``constants`` + + - Fixed the aspect ratio of the screen to 16:9 + - Deleted the old gray series (``LIGHT_GREY``, ``GREY``, ``DARK_GREY``, ``DARKER_GREY``), added a new series of gray ``GREY_A`` ~ ``GREY_E`` \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 865bd82c..7f4aeed8 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,30 +1,36 @@ -.. Manim documentation master file, created by - sphinx-quickstart on Mon May 27 14:19:19 2019. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +Manim's documentation +===================== -Welcome to Manim's documentation! -================================= +.. image:: ../../logo/white_with_name.png -These docs are generated from the master branch of the -`Manim repo `_. You can contribute by submitting -a pull request there. +Manim is an animation engine for explanatory math videos. It's used to create precise animations programmatically, as seen in the videos +at `3Blue1Brown `_. + +And here is a Chinese version of this documentation: https://manim.ml/shaders .. toctree:: - :maxdepth: 2 - :caption: Contents + :maxdepth: 2 + :caption: Getting Started - about - installation/index - getting_started/index - coordinate - animation - constants + getting_started/installation + getting_started/quickstart + getting_started/configuration + getting_started/example_scenes + getting_started/config + getting_started/structure + getting_started/whatsnew +.. toctree:: + :maxdepth: 2 + :caption: Documentation -Indices and tables -================== + documentation/constants + documentation/custom_config -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +.. toctree:: + :maxdepth: 2 + :caption: Development + + development/changelog + development/contributing + development/about diff --git a/docs/source/installation/index.rst b/docs/source/installation/index.rst deleted file mode 100644 index 664d154d..00000000 --- a/docs/source/installation/index.rst +++ /dev/null @@ -1,12 +0,0 @@ -Installation -============ - -Instructions on installing Manim - -.. toctree:: - :maxdepth: 2 - :caption: Contents - - linux - mac - windows diff --git a/docs/source/installation/linux.rst b/docs/source/installation/linux.rst deleted file mode 100644 index ff11bc3c..00000000 --- a/docs/source/installation/linux.rst +++ /dev/null @@ -1,64 +0,0 @@ -Linux -===== - -Ubuntu ------- - -Install system libraries:: - - # apt install sox ffmpeg libcairo2 libcairo2-dev - -Install Latex distribution:: - - # apt install texlive-full - -Install manim via pypi:: - - # pip3 install manimlib - -OR Install manim via the git repository with venv:: - - $ git clone https://github.com/3b1b/manim - $ cd manim - $ python3 -m venv ./ - $ source bin/activate - $ pip3 install -r requirement.txt - -To use manim in virtual environment you need to activate the environment with -the ``activate`` binary by doing ``source bin/activate``, to exit use the ``deactivate`` command. - -.. note:: The git repository is updated first before the one on pypi. The git repository also - includes project files used to produce 3b1b videos. Some of the old projects might not - work as due to api changes. - - -.. note:: The required latex packages are dictated by - ``manimlib/tex_template.tex`` which ``texlive-full`` will satisfy. The download size - can be quite large. If you wish to install only the packages required to use - manim, substitude ``texlive-full`` with:: - - texlive texlive-latex-extra texlive-fonts-extra - texlive-latex-recommended texlive-science texlive-fonts-extra tipa - -Arch Linux ----------- -Install system libraries:: - - # pacman -S cairo ffmpeg opencv sox - -Install Latex distribution:: - - # pacman -S texlive-most - -OR install python-manimlib_:sup:`AUR` package:: - - $ git clone https://aur.archlinux.org/python-manimlib.git - $ cd python-manimlib - $ makepkg -si - -You can use AUR helpers such as yay_:sup:`AUR`:: - - $ yay -S python-manimlib - -.. _python-manimlib: https://aur.archlinux.org/packages/python-manimlib/ -.. _yay: https://aur.archlinux.org/packages/yay/ diff --git a/docs/source/installation/mac.rst b/docs/source/installation/mac.rst deleted file mode 100644 index 43c7bfdb..00000000 --- a/docs/source/installation/mac.rst +++ /dev/null @@ -1,12 +0,0 @@ -Mac -=== - -The simplest way to install the system dependencies on Mac OS X is with Homebrew. -Mac come preinstalled with python2, but to use manim, python3 is required - -1. Install python3 https://docs.python.org/3/using/mac.html -2. Install Cairo: ``brew install cairo`` -3. Install Sox: ``brew install sox`` -4. Install ffmpeg: ``brew install ffmpeg`` -5. Install latex (MacTeX): ``brew cask install mactex`` -6. Install manimlib ``pip install manimlib`` (or ``pip install --user manimlib`` to just yourself) diff --git a/docs/source/installation/windows.rst b/docs/source/installation/windows.rst deleted file mode 100644 index e58d125c..00000000 --- a/docs/source/installation/windows.rst +++ /dev/null @@ -1,60 +0,0 @@ -Windows -======= - -Install System Libraries ------------------------- - -Make sure you have *Python 3* for Windows installed first: - -https://www.python.org/downloads/windows/ - -Install ffmpeg: - -https://ffmpeg.org/download.html#build-windows - -Install sox: - -http://sox.sourceforge.net/Main/HomePage - -Install a latex distribution. On Windows MikTex is commonly used: - -https://miktex.org/howto/install-miktex - -Path configuration ------------------- - -To invoke commandline without supplying path to the binary -the PATH environment needs to be configured. Below are template examples, please change -the path according to your username and specific python version. Assuming all the -softwares are installed with no alteration to the installation paths:: - - C:\Users\$username\AppData\local\Programs\Python\Python$version\ - C:\Users\$username\AppData\local\Programs\Python\Python$version\Scripts\ - C:\MikTex\miktex\bin\x64\ - C:\ffmpeg\bin\ - -The path entries should be separated by semicolon. - -Installing python packages and manim ------------------------------------- - -Make sure you can start pip using ``pip`` in your commandline. Then do -``pip install pyreadline`` for the ``readline`` package. - -Grab the pycairo wheel binary ``pycairo‑1.18.0‑cp37‑cp37m‑win32.whl`` from https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycairo -and install it via ``python -m pip install C:\absolute\path\to\the\whl\file`` - -clone the manim repository if you have git ``git clone https://github.com/3b1b/manim`` or download the zip file from -the repository page with ``Clone or download`` button and unzip it. - -Open the commandline within the manim directory with ``Shift + Right click`` on an empty space in the folder and select ``open command window here`` - -Install manim python dependencies with ``pip install -r requirements.txt`` - -Test the installation ---------------------- - -Type in ``python -m manim -h`` and if nothing went wrong during the installation process you should see the help text. - -Use ``python -m manim example_scenes.py SquareToCircle -pl`` to render the example scene and the file should play after rendering. The movie file should be -in ``media/videos/example_scenes/480p15`` diff --git a/docs/source/manim_example_ext.py b/docs/source/manim_example_ext.py new file mode 100644 index 00000000..e5defb9d --- /dev/null +++ b/docs/source/manim_example_ext.py @@ -0,0 +1,108 @@ +from docutils import nodes +from docutils.parsers.rst import directives, Directive + +import jinja2 +import os + + +class skip_manim_node(nodes.Admonition, nodes.Element): + pass + + +def visit(self, node, name=""): + self.visit_admonition(node, name) + + +def depart(self, node): + self.depart_admonition(node) + + +class ManimExampleDirective(Directive): + has_content = True + required_arguments = 1 + optional_arguments = 0 + option_spec = { + "hide_code": bool, + "media": str, + } + final_argument_whitespace = True + + def run(self): + hide_code = "hide_code" in self.options + scene_name = self.arguments[0] + media_file_name = self.options["media"] + + source_block = [ + ".. code-block:: python", + "", + *[" " + line for line in self.content], + ] + source_block = "\n".join(source_block) + + state_machine = self.state_machine + document = state_machine.document + + if any(media_file_name.endswith(ext) for ext in [".png", ".jpg", ".gif"]): + is_video = False + else: + is_video = True + + rendered_template = jinja2.Template(TEMPLATE).render( + scene_name=scene_name, + scene_name_lowercase=scene_name.lower(), + hide_code=hide_code, + is_video=is_video, + media_file_name=media_file_name, + source_block=source_block, + ) + state_machine.insert_input( + rendered_template.split("\n"), source=document.attributes["source"] + ) + + return [] + + +def setup(app): + app.add_node(skip_manim_node, html=(visit, depart)) + + setup.app = app + setup.config = app.config + setup.confdir = app.confdir + + app.add_directive("manim-example", ManimExampleDirective) + + metadata = {"parallel_read_safe": False, "parallel_write_safe": True} + return metadata + + +TEMPLATE = r""" +{% if not hide_code %} + +.. raw:: html + +
+ +{% endif %} + +{% if is_video %} +.. raw:: html + + +{% else %} +.. image:: {{ media_file_name }} + :align: center + :name: {{ scene_name_lowercase }} +{% endif %} + +{% if not hide_code %} +.. raw:: html + +
{{ scene_name }}
+ +{{ source_block }} +{% endif %} + +.. raw:: html + +
+""" \ No newline at end of file diff --git a/environment.yml b/environment.yml deleted file mode 100644 index 60b257c6..00000000 --- a/environment.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: manim -channels: - - defaults - - conda-forge -dependencies: - - python=3.7 - - cairo - - ffmpeg - - colour==0.1.5 - - numpy==1.15.0 - - pillow==5.2.0 - - scipy==1.1.0 - - tqdm==4.24.0 - - opencv==3.4.2 - - pycairo==1.18.0 - - pydub==0.23.0 - - ffmpeg - - pip: - - pyreadline diff --git a/example_scenes.py b/example_scenes.py index bc15c557..10484451 100644 --- a/example_scenes.py +++ b/example_scenes.py @@ -1,136 +1,674 @@ -#!/usr/bin/env python - -from manimlib.imports import * +from manimlib import * +import numpy as np # To watch one of these scenes, run the following: -# python -m manim example_scenes.py SquareToCircle -pl -# -# Use the flat -l for a faster rendering at a lower -# quality. +# python -m manim example_scenes.py SquareToCircle # Use -s to skip to the end and just save the final frame -# Use the -p to have the animation (or image, if -s was -# used) pop up once done. +# Use -w to write the animation to a file +# Use -o to write it to a file and open it once done # Use -n to skip ahead to the n'th animation of a scene. -# Use -r to specify a resolution (for example, -r 1080 -# for a 1920x1080 video) class OpeningManimExample(Scene): def construct(self): - title = TextMobject("This is some \\LaTeX") - basel = TexMobject( - "\\sum_{n=1}^\\infty " - "\\frac{1}{n^2} = \\frac{\\pi^2}{6}" + intro_words = Text(""" + The original motivation for manim was to + better illustrate mathematical functions + as transformations. + """) + intro_words.to_edge(UP) + + self.play(Write(intro_words)) + self.wait(2) + + # Linear transform + grid = NumberPlane((-10, 10), (-5, 5)) + matrix = [[1, 1], [0, 1]] + linear_transform_words = VGroup( + Text("This is what the matrix"), + IntegerMatrix(matrix, include_background_rectangle=True), + Text("looks like") ) - VGroup(title, basel).arrange(DOWN) + linear_transform_words.arrange(RIGHT) + linear_transform_words.to_edge(UP) + linear_transform_words.set_stroke(BLACK, 10, background=True) + self.play( - Write(title), - FadeInFrom(basel, UP), + ShowCreation(grid), + FadeTransform(intro_words, linear_transform_words) ) self.wait() - - transform_title = TextMobject("That was a transform") - transform_title.to_corner(UP + LEFT) - self.play( - Transform(title, transform_title), - LaggedStart(*map(FadeOutAndShiftDown, basel)), - ) + self.play(grid.animate.apply_matrix(matrix), run_time=3) self.wait() - grid = NumberPlane() - grid_title = TextMobject("This is a grid") - grid_title.scale(1.5) - grid_title.move_to(transform_title) + # Complex map + c_grid = ComplexPlane() + moving_c_grid = c_grid.copy() + moving_c_grid.prepare_for_nonlinear_transform() + c_grid.set_stroke(BLUE_E, 1) + c_grid.add_coordinate_labels(font_size=24) + complex_map_words = TexText(""" + Or thinking of the plane as $\\mathds{C}$,\\\\ + this is the map $z \\rightarrow z^2$ + """) + complex_map_words.to_corner(UR) + complex_map_words.set_stroke(BLACK, 5, background=True) - self.add(grid, grid_title) # Make sure title is on top of grid self.play( - FadeOut(title), - FadeInFromDown(grid_title), - ShowCreation(grid, run_time=3, lag_ratio=0.1), - ) - self.wait() - - grid_transform_title = TextMobject( - "That was a non-linear function \\\\" - "applied to the grid" - ) - grid_transform_title.move_to(grid_title, UL) - grid.prepare_for_nonlinear_transform() - self.play( - grid.apply_function, - lambda p: p + np.array([ - np.sin(p[1]), - np.sin(p[0]), - 0, - ]), - run_time=3, + FadeOut(grid), + Write(c_grid, run_time=3), + FadeIn(moving_c_grid), + FadeTransform(linear_transform_words, complex_map_words), ) self.wait() self.play( - Transform(grid_title, grid_transform_title) + moving_c_grid.animate.apply_complex_function(lambda z: z**2), + run_time=6, ) - self.wait() + self.wait(2) -class SquareToCircle(Scene): +class AnimatingMethods(Scene): def construct(self): - circle = Circle() - square = Square() - square.flip(RIGHT) - square.rotate(-3 * TAU / 8) - circle.set_fill(PINK, opacity=0.5) + grid = Tex(r"\pi").get_grid(10, 10, height=4) + self.add(grid) - self.play(ShowCreation(square)) - self.play(Transform(square, circle)) - self.play(FadeOut(square)) + # You can animate the application of mobject methods with the + # ".animate" syntax: + self.play(grid.animate.shift(LEFT)) + # Alternatively, you can use the older syntax by passing the + # method and then the arguments to the scene's "play" function: + self.play(grid.shift, LEFT) -class WarpSquare(Scene): - def construct(self): - square = Square() - self.play(ApplyPointwiseFunction( - lambda point: complex_to_R3(np.exp(R3_to_complex(point))), - square - )) + # Both of those will interpolate between the mobject's initial + # state and whatever happens when you apply that method. + # For this example, calling grid.shift(LEFT) would shift the + # grid one unit to the left, but both of the previous calls to + # "self.play" animate that motion. + + # The same applies for any method, including those setting colors. + self.play(grid.animate.set_color(YELLOW)) + self.wait() + self.play(grid.animate.set_submobject_colors_by_gradient(BLUE, GREEN)) + self.wait() + self.play(grid.animate.set_height(TAU - MED_SMALL_BUFF)) + self.wait() + + # The method Mobject.apply_complex_function lets you apply arbitrary + # complex functions, treating the points defining the mobject as + # complex numbers. + self.play(grid.animate.apply_complex_function(np.exp), run_time=5) + self.wait() + + # Even more generally, you could apply Mobject.apply_function, + # which takes in functions form R^3 to R^3 + self.play( + grid.animate.apply_function( + lambda p: [ + p[0] + 0.5 * math.sin(p[1]), + p[1] + 0.5 * math.sin(p[0]), + p[2] + ] + ), + run_time=5, + ) self.wait() -class WriteStuff(Scene): +class TextExample(Scene): def construct(self): - example_text = TextMobject( - "This is a some text", - tex_to_color_map={"text": YELLOW} + # To run this scene properly, you should have "Consolas" font in your computer + # for full usage, you can see https://github.com/3b1b/manim/pull/680 + text = Text("Here is a text", font="Consolas", font_size=90) + difference = Text( + """ + The most important difference between Text and TexText is that\n + you can change the font more easily, but can't use the LaTeX grammar + """, + font="Arial", font_size=24, + # t2c is a dict that you can choose color for different text + t2c={"Text": BLUE, "TexText": BLUE, "LaTeX": ORANGE} ) - example_tex = TexMobject( - "\\sum_{k=1}^\\infty {1 \\over k^2} = {\\pi^2 \\over 6}", - ) - group = VGroup(example_text, example_tex) - group.arrange(DOWN) - group.set_width(FRAME_WIDTH - 2 * LARGE_BUFF) + VGroup(text, difference).arrange(DOWN, buff=1) + self.play(Write(text)) + self.play(FadeIn(difference, UP)) + self.wait(3) - self.play(Write(example_text)) - self.play(Write(example_tex)) + fonts = Text( + "And you can also set the font according to different words", + font="Arial", + t2f={"font": "Consolas", "words": "Consolas"}, + t2c={"font": BLUE, "words": GREEN} + ) + fonts.set_width(FRAME_WIDTH - 1) + slant = Text( + "And the same as slant and weight", + font="Consolas", + t2s={"slant": ITALIC}, + t2w={"weight": BOLD}, + t2c={"slant": ORANGE, "weight": RED} + ) + VGroup(fonts, slant).arrange(DOWN, buff=0.8) + self.play(FadeOut(text), FadeOut(difference, shift=DOWN)) + self.play(Write(fonts)) + self.wait() + self.play(Write(slant)) + self.wait() + + +class TexTransformExample(Scene): + def construct(self): + to_isolate = ["B", "C", "=", "(", ")"] + lines = VGroup( + # Passing in muliple arguments to Tex will result + # in the same expression as if those arguments had + # been joined together, except that the submobject + # heirarchy of the resulting mobject ensure that the + # Tex mobject has a subject corresponding to + # each of these strings. For example, the Tex mobject + # below will have 5 subjects, corresponding to the + # expressions [A^2, +, B^2, =, C^2] + Tex("A^2", "+", "B^2", "=", "C^2"), + # Likewise here + Tex("A^2", "=", "C^2", "-", "B^2"), + # Alternatively, you can pass in the keyword argument + # "isolate" with a list of strings that should be out as + # their own submobject. So the line below is equivalent + # to the commented out line below it. + Tex("A^2 = (C + B)(C - B)", isolate=["A^2", *to_isolate]), + # Tex("A^2", "=", "(", "C", "+", "B", ")", "(", "C", "-", "B", ")"), + Tex("A = \\sqrt{(C + B)(C - B)}", isolate=["A", *to_isolate]) + ) + lines.arrange(DOWN, buff=LARGE_BUFF) + for line in lines: + line.set_color_by_tex_to_color_map({ + "A": BLUE, + "B": TEAL, + "C": GREEN, + }) + + play_kw = {"run_time": 2} + self.add(lines[0]) + # The animation TransformMatchingTex will line up parts + # of the source and target which have matching tex strings. + # Here, giving it a little path_arc makes each part sort of + # rotate into their final positions, which feels appropriate + # for the idea of rearranging an equation + self.play( + TransformMatchingTex( + lines[0].copy(), lines[1], + path_arc=90 * DEGREES, + ), + **play_kw + ) + self.wait() + + # Now, we could try this again on the next line... + self.play( + TransformMatchingTex(lines[1].copy(), lines[2]), + **play_kw + ) + self.wait() + # ...and this looks nice enough, but since there's no tex + # in lines[2] which matches "C^2" or "B^2", those terms fade + # out to nothing while the C and B terms fade in from nothing. + # If, however, we want the C^2 to go to C, and B^2 to go to B, + # we can specify that with a key map. + self.play(FadeOut(lines[2])) + self.play( + TransformMatchingTex( + lines[1].copy(), lines[2], + key_map={ + "C^2": "C", + "B^2": "B", + } + ), + **play_kw + ) + self.wait() + + # And to finish off, a simple TransformMatchingShapes would work + # just fine. But perhaps we want that exponent on A^2 to transform into + # the square root symbol. At the moment, lines[2] treats the expression + # A^2 as a unit, so we might create a new version of the same line which + # separates out just the A. This way, when TransformMatchingTex lines up + # all matching parts, the only mismatch will be between the "^2" from + # new_line2 and the "\sqrt" from the final line. By passing in, + # transform_mismatches=True, it will transform this "^2" part into + # the "\sqrt" part. + new_line2 = Tex("A^2 = (C + B)(C - B)", isolate=["A", *to_isolate]) + new_line2.replace(lines[2]) + new_line2.match_style(lines[2]) + + self.play( + TransformMatchingTex( + new_line2, lines[3], + transform_mismatches=True, + ), + **play_kw + ) + self.wait(3) + self.play(FadeOut(lines, RIGHT)) + + # Alternatively, if you don't want to think about breaking up + # the tex strings deliberately, you can TransformMatchingShapes, + # which will try to line up all pieces of a source mobject with + # those of a target, regardless of the submobject hierarchy in + # each one, according to whether those pieces have the same + # shape (as best it can). + source = Text("the morse code", height=1) + target = Text("here come dots", height=1) + + self.play(Write(source)) + self.wait() + kw = {"run_time": 3, "path_arc": PI / 2} + self.play(TransformMatchingShapes(source, target, **kw)) + self.wait() + self.play(TransformMatchingShapes(target, source, **kw)) self.wait() class UpdatersExample(Scene): def construct(self): - decimal = DecimalNumber( - 0, - show_ellipsis=True, - num_decimal_places=3, - include_sign=True, - ) - square = Square().to_edge(UP) + square = Square() + square.set_fill(BLUE_E, 1) - decimal.add_updater(lambda d: d.next_to(square, RIGHT)) - decimal.add_updater(lambda d: d.set_value(square.get_center()[1])) - self.add(square, decimal) + # On all all frames, the constructor Brace(square, UP) will + # be called, and the mobject brace will set its data to match + # that of the newly constructed object + brace = always_redraw(Brace, square, UP) + + text, number = label = VGroup( + Text("Width = "), + DecimalNumber( + 0, + show_ellipsis=True, + num_decimal_places=2, + include_sign=True, + ) + ) + label.arrange(RIGHT) + + # This ensures that the method deicmal.next_to(square) + # is called on every frame + always(label.next_to, brace, UP) + # You could also write the following equivalent line + # label.add_updater(lambda m: m.next_to(brace, UP)) + + # If the argument itself might change, you can use f_always, + # for which the arguments following the initial Mobject method + # should be functions returning arguments to that method. + # The following line ensures thst decimal.set_value(square.get_y()) + # is called every frame + f_always(number.set_value, square.get_width) + # You could also write the following equivalent line + # number.add_updater(lambda m: m.set_value(square.get_width())) + + self.add(square, brace, label) + + # Notice that the brace and label track with the square self.play( - square.to_edge, DOWN, + square.animate.scale(2), rate_func=there_and_back, - run_time=5, + run_time=2, + ) + self.wait() + self.play( + square.animate.set_width(5, stretch=True), + run_time=3, + ) + self.wait() + self.play( + square.animate.set_width(2), + run_time=3 ) self.wait() -# See old_projects folder for many, many more + # In general, you can alway call Mobject.add_updater, and pass in + # a function that you want to be called on every frame. The function + # should take in either one argument, the mobject, or two arguments, + # the mobject and the amount of time since the last frame. + now = self.time + w0 = square.get_width() + square.add_updater( + lambda m: m.set_width(w0 * math.cos(self.time - now)) + ) + self.wait(4 * PI) + + +class CoordinateSystemExample(Scene): + def construct(self): + axes = Axes( + # x-axis ranges from -1 to 10, with a default step size of 1 + x_range=(-1, 10), + # y-axis ranges from -2 to 10 with a step size of 0.5 + y_range=(-2, 2, 0.5), + # The axes will be stretched so as to match the specified + # height and width + height=6, + width=10, + # Axes is made of two NumberLine mobjects. You can specify + # their configuration with axis_config + axis_config={ + "stroke_color": GREY_A, + "stroke_width": 2, + }, + # Alternatively, you can specify configuration for just one + # of them, like this. + y_axis_config={ + "include_tip": False, + } + ) + # Keyword arguments of add_coordinate_labels can be used to + # configure the DecimalNumber mobjects which it creates and + # adds to the axes + axes.add_coordinate_labels( + font_size=20, + num_decimal_places=1, + ) + self.add(axes) + + # Axes descends from the CoordinateSystem class, meaning + # you can call call axes.coords_to_point, abbreviated to + # axes.c2p, to associate a set of coordinates with a point, + # like so: + dot = Dot(color=RED) + dot.move_to(axes.c2p(0, 0)) + self.play(FadeIn(dot, scale=0.5)) + self.play(dot.animate.move_to(axes.c2p(3, 2))) + self.wait() + self.play(dot.animate.move_to(axes.c2p(5, 0.5))) + self.wait() + + # Similarly, you can call axes.point_to_coords, or axes.p2c + # print(axes.p2c(dot.get_center())) + + # We can draw lines from the axes to better mark the coordinates + # of a given point. + # Here, the always_redraw command means that on each new frame + # the lines will be redrawn + h_line = always_redraw(lambda: axes.get_h_line(dot.get_left())) + v_line = always_redraw(lambda: axes.get_v_line(dot.get_bottom())) + + self.play( + ShowCreation(h_line), + ShowCreation(v_line), + ) + self.play(dot.animate.move_to(axes.c2p(3, -2))) + self.wait() + self.play(dot.animate.move_to(axes.c2p(1, 1))) + self.wait() + + # If we tie the dot to a particular set of coordinates, notice + # that as we move the axes around it respects the coordinate + # system defined by them. + f_always(dot.move_to, lambda: axes.c2p(1, 1)) + self.play( + axes.animate.scale(0.75).to_corner(UL), + run_time=2, + ) + self.wait() + self.play(FadeOut(VGroup(axes, dot, h_line, v_line))) + + # Other coordinate systems you can play around with include + # ThreeDAxes, NumberPlane, and ComplexPlane. + + +class GraphExample(Scene): + def construct(self): + axes = Axes((-3, 10), (-1, 8)) + axes.add_coordinate_labels() + + self.play(Write(axes, lag_ratio=0.01, run_time=1)) + + # Axes.get_graph will return the graph of a function + sin_graph = axes.get_graph( + lambda x: 2 * math.sin(x), + color=BLUE, + ) + # By default, it draws it so as to somewhat smoothly interpolate + # between sampled points (x, f(x)). If the graph is meant to have + # a corner, though, you can set use_smoothing to False + relu_graph = axes.get_graph( + lambda x: max(x, 0), + use_smoothing=False, + color=YELLOW, + ) + # For discontinuous functions, you can specify the point of + # discontinuity so that it does not try to draw over the gap. + step_graph = axes.get_graph( + lambda x: 2.0 if x > 3 else 1.0, + discontinuities=[3], + color=GREEN, + ) + + # Axes.get_graph_label takes in either a string or a mobject. + # If it's a string, it treats it as a LaTeX expression. By default + # it places the label next to the graph near the right side, and + # has it match the color of the graph + sin_label = axes.get_graph_label(sin_graph, "\\sin(x)") + relu_label = axes.get_graph_label(relu_graph, Text("ReLU")) + step_label = axes.get_graph_label(step_graph, Text("Step"), x=4) + + self.play( + ShowCreation(sin_graph), + FadeIn(sin_label, RIGHT), + ) + self.wait(2) + self.play( + ReplacementTransform(sin_graph, relu_graph), + FadeTransform(sin_label, relu_label), + ) + self.wait() + self.play( + ReplacementTransform(relu_graph, step_graph), + FadeTransform(relu_label, step_label), + ) + self.wait() + + parabola = axes.get_graph(lambda x: 0.25 * x**2) + parabola.set_stroke(BLUE) + self.play( + FadeOut(step_graph), + FadeOut(step_label), + ShowCreation(parabola) + ) + self.wait() + + # You can use axes.input_to_graph_point, abbreviated + # to axes.i2gp, to find a particular point on a graph + dot = Dot(color=RED) + dot.move_to(axes.i2gp(2, parabola)) + self.play(FadeIn(dot, scale=0.5)) + + # A value tracker lets us animate a parameter, usually + # with the intent of having other mobjects update based + # on the parameter + x_tracker = ValueTracker(2) + f_always( + dot.move_to, + lambda: axes.i2gp(x_tracker.get_value(), parabola) + ) + + self.play(x_tracker.animate.set_value(4), run_time=3) + self.play(x_tracker.animate.set_value(-2), run_time=3) + self.wait() + + +class SurfaceExample(Scene): + CONFIG = { + "camera_class": ThreeDCamera, + } + + def construct(self): + surface_text = Text("For 3d scenes, try using surfaces") + surface_text.fix_in_frame() + surface_text.to_edge(UP) + self.add(surface_text) + self.wait(0.1) + + torus1 = Torus(r1=1, r2=1) + torus2 = Torus(r1=3, r2=1) + sphere = Sphere(radius=3, resolution=torus1.resolution) + # You can texture a surface with up to two images, which will + # be interpreted as the side towards the light, and away from + # the light. These can be either urls, or paths to a local file + # in whatever you've set as the image directory in + # the custom_config.yml file + + # day_texture = "EarthTextureMap" + # night_texture = "NightEarthTextureMap" + day_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Whole_world_-_land_and_oceans.jpg/1280px-Whole_world_-_land_and_oceans.jpg" + night_texture = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/The_earth_at_night.jpg/1280px-The_earth_at_night.jpg" + + surfaces = [ + TexturedSurface(surface, day_texture, night_texture) + for surface in [sphere, torus1, torus2] + ] + + for mob in surfaces: + mob.shift(IN) + mob.mesh = SurfaceMesh(mob) + mob.mesh.set_stroke(BLUE, 1, opacity=0.5) + + # Set perspective + frame = self.camera.frame + frame.set_euler_angles( + theta=-30 * DEGREES, + phi=70 * DEGREES, + ) + + surface = surfaces[0] + + self.play( + FadeIn(surface), + ShowCreation(surface.mesh, lag_ratio=0.01, run_time=3), + ) + for mob in surfaces: + mob.add(mob.mesh) + surface.save_state() + self.play(Rotate(surface, PI / 2), run_time=2) + for mob in surfaces[1:]: + mob.rotate(PI / 2) + + self.play( + Transform(surface, surfaces[1]), + run_time=3 + ) + + self.play( + Transform(surface, surfaces[2]), + # Move camera frame during the transition + frame.animate.increment_phi(-10 * DEGREES), + frame.animate.increment_theta(-20 * DEGREES), + run_time=3 + ) + # Add ambient rotation + frame.add_updater(lambda m, dt: m.increment_theta(-0.1 * dt)) + + # Play around with where the light is + light_text = Text("You can move around the light source") + light_text.move_to(surface_text) + light_text.fix_in_frame() + + self.play(FadeTransform(surface_text, light_text)) + light = self.camera.light_source + self.add(light) + light.save_state() + self.play(light.animate.move_to(3 * IN), run_time=5) + self.play(light.animate.shift(10 * OUT), run_time=5) + + drag_text = Text("Try moving the mouse while pressing d or s") + drag_text.move_to(light_text) + drag_text.fix_in_frame() + + self.play(FadeTransform(light_text, drag_text)) + self.wait() + + +class InteractiveDevelopment(Scene): + def construct(self): + circle = Circle() + circle.set_fill(BLUE, opacity=0.5) + circle.set_stroke(BLUE_E, width=4) + square = Square() + + self.play(ShowCreation(square)) + self.wait() + + # This opens an iPython termnial where you can keep writing + # lines as if they were part of this construct method. + # In particular, 'square', 'circle' and 'self' will all be + # part of the local namespace in that terminal. + self.embed() + + # Try copying and pasting some of the lines below into + # the interactive shell + self.play(ReplacementTransform(square, circle)) + self.wait() + self.play(circle.animate.stretch(4, 0)) + self.play(Rotate(circle, 90 * DEGREES)) + self.play(circle.animate.shift(2 * RIGHT).scale(0.25)) + + text = Text(""" + In general, using the interactive shell + is very helpful when developing new scenes + """) + self.play(Write(text)) + + # In the interactive shell, you can just type + # play, add, remove, clear, wait, save_state and restore, + # instead of self.play, self.add, self.remove, etc. + + # To interact with the window, type touch(). You can then + # scroll in the window, or zoom by holding down 'z' while scrolling, + # and change camera perspective by holding down 'd' while moving + # the mouse. Press 'r' to reset to the standard camera position. + # Press 'q' to stop interacting with the window and go back to + # typing new commands into the shell. + + # In principle you can customize a scene to be responsive to + # mouse and keyboard interactions + always(circle.move_to, self.mouse_point) + + +class ControlsExample(Scene): + def setup(self): + self.textbox = Textbox() + self.checkbox = Checkbox() + self.color_picker = ColorSliders() + self.panel = ControlPanel( + Text("Text", size=0.5), self.textbox, Line(), + Text("Show/Hide Text", size=0.5), self.checkbox, Line(), + Text("Color of Text", size=0.5), self.color_picker + ) + self.add(self.panel) + + def construct(self): + text = Text("", size=2) + + def text_updater(old_text): + assert(isinstance(old_text, Text)) + new_text = Text(self.textbox.get_value(), size=old_text.size) + # new_text.align_data_and_family(old_text) + new_text.move_to(old_text) + if self.checkbox.get_value(): + new_text.set_fill( + color=self.color_picker.get_picked_color(), + opacity=self.color_picker.get_picked_opacity() + ) + else: + new_text.set_opacity(0) + old_text.become(new_text) + + text.add_updater(text_updater) + + self.add(MotionMobject(text)) + + self.textbox.set_value("Manim") + # self.wait(60) + # self.embed() + + +# See https://github.com/3b1b/videos for many, many more diff --git a/from_3b1b/active/bayes/beta1.py b/from_3b1b/active/bayes/beta1.py deleted file mode 100644 index 2837cd2f..00000000 --- a/from_3b1b/active/bayes/beta1.py +++ /dev/null @@ -1,3819 +0,0 @@ -from manimlib.imports import * -from from_3b1b.active.bayes.beta_helpers import * - -import scipy.stats - -OUTPUT_DIRECTORY = "bayes/beta1" - - -# Scenes -class BarChartTest(Scene): - def construct(self): - bar_chart = BarChart() - bar_chart.to_edge(DOWN) - self.add(bar_chart) - - -class Thumbnail1(Scene): - def construct(self): - p1 = "$96\\%$" - p2 = "$93\\%$" - n1 = "50" - n2 = "200" - t2c = { - p1: BLUE, - p2: YELLOW, - n1: BLUE_C, - n2: YELLOW, - } - kw = {"tex_to_color_map": t2c} - text = VGroup( - TextMobject(f"{p1} with {n1} reviews", **kw), - TextMobject("vs.", **kw), - TextMobject(f"{p2} with {n2} reviews", **kw), - ) - fix_percent(text[0].get_part_by_tex(p1)[-1]) - fix_percent(text[2].get_part_by_tex(p2)[-1]) - text.scale(2) - text.arrange(DOWN, buff=LARGE_BUFF) - text.set_width(FRAME_WIDTH - 1) - self.add(text) - - -class AltThumbnail1(Scene): - def construct(self): - N = 20 - n_trials = 10000 - p = 0.7 - outcomes = (np.random.random((N, n_trials)) < p).sum(0) - counts = [] - for k in range(N + 1): - counts.append((outcomes == k).sum()) - - hist = Histogram( - counts, - y_max=0.3, - y_tick_freq=0.05, - y_axis_numbers_to_show=[10, 20, 30], - x_label_freq=10, - ) - hist.set_width(FRAME_WIDTH - 1) - hist.bars.set_submobject_colors_by_gradient(YELLOW, YELLOW, GREEN, BLUE) - hist.bars.set_stroke(WHITE, 2) - - title = TextMobject("Binomial distribution") - title.set_width(12) - title.to_corner(UR, buff=0.8) - title.add_background_rectangle() - - self.add(hist) - self.add(title) - - -class Thumbnail2(Scene): - def construct(self): - axes = self.get_axes() - graph = get_beta_graph(axes, 2, 2) - # sub_graph = axes.get_graph( - # lambda x: (1 - x) * graph.underlying_function(x) - # ) - # sub_graph.add_line_to(axes.c2p(1, 0)) - # sub_graph.add_line_to(axes.c2p(0, 0)) - # sub_graph.set_stroke(YELLOW, 4) - # sub_graph.set_fill(YELLOW_D, 1) - - new_graph = get_beta_graph(axes, 9, 2) - new_graph.set_stroke(GREEN, 4) - new_graph.set_fill(GREEN, 0.5) - - self.add(axes) - self.add(graph) - self.add(new_graph) - - arrow = Arrow( - axes.input_to_graph_point(0.5, graph), - axes.input_to_graph_point(0.8, new_graph), - path_arc=-90 * DEGREES, - buff=0.3 - ) - self.add(arrow) - - formula = TexMobject( - "P(H|D) = {P(H)P(D|H) \\over P(D)}", - tex_to_color_map={ - "H": YELLOW, - "D": GREEN, - } - ) - formula.next_to(axes.c2p(0, 3), RIGHT, LARGE_BUFF) - formula.set_height(1.5) - formula.to_edge(LEFT) - formula.to_edge(UP, LARGE_BUFF) - formula.add_to_back(BackgroundRectangle(formula[:4], buff=0.25)) - - self.add(formula) - - def get_axes(self, y_max=3, y_height=4.5, y_unit=0.5): - axes = get_beta_dist_axes(y_max=y_max, y_unit=y_unit) - axes.y_axis.set_height(y_height, about_point=axes.c2p(0, 0)) - axes.to_edge(DOWN) - axes.scale(0.9) - return axes - - -class Thumbnail3(Thumbnail2): - def construct(self): - axes = self.get_axes(y_max=4, y_height=6) - axes.set_height(7) - graph = get_beta_graph(axes, 9, 2) - - self.add(axes) - self.add(graph) - - label = TexMobject( - "\\text{Beta}(10, 3)", - tex_to_color_map={ - "10": GREEN, - "3": RED, - } - ) - label = get_beta_label(9, 2) - label.set_height(1.25) - label.next_to(axes.c2p(0, 3), RIGHT, LARGE_BUFF) - - self.add(label) - - -class HighlightReviewParts(Scene): - CONFIG = { - "reverse_order": False, - } - - def construct(self): - # Setup up rectangles - rects = VGroup(*[Rectangle() for x in range(3)]) - rects.set_stroke(width=0) - rects.set_fill(GREY, 0.5) - - rects.set_height(1.35, stretch=True) - rects.set_width(9.75, stretch=True) - - rects[0].move_to([-0.2, 0.5, 0]) - rects[1].next_to(rects[0], DOWN, buff=0) - rects[2].next_to(rects[1], DOWN, buff=0) - - rects[2].set_height(1, stretch=True, about_edge=UP) - - inv_rects = VGroup() - for rect in rects: - fsr = FullScreenFadeRectangle() - fsr.append_points(rect.points[::-1]) - inv_rects.add(fsr) - - inv_rects.set_fill(BLACK, 0.85) - - # Set up labels - ratings = [100, 96, 93] - n_reviews = [10, 50, 200] - colors = [PINK, BLUE, YELLOW] - - review_labels = VGroup() - for rect, rating, nr, color in zip(rects, ratings, n_reviews, colors): - label = TexMobject( - f"{nr}", "\\text{ reviews }", - f"{rating}", "\\%", - ) - label[2:].set_color(color) - label.set_height(1) - label.next_to(rect, UP, aligned_edge=RIGHT) - label.set_stroke(BLACK, 4, background=True) - fix_percent(label[3][0]) - review_labels.add(label) - - # Animations - curr_fsr = inv_rects[0] - curr_label = None - - tuples = list(zip(inv_rects, review_labels)) - if self.reverse_order: - tuples = reversed(tuples) - curr_fsr = inv_rects[-1] - - for fsr, label in tuples: - if curr_fsr is fsr: - self.play(VFadeIn(fsr)) - else: - self.play( - Transform(curr_fsr, fsr), - MoveToTarget(curr_label), - ) - - first, second = label[2:], label[:2] - if self.reverse_order: - first, second = second, first - - self.add(first) - self.wait(2) - self.add(second) - self.wait(2) - - label.generate_target() - label.target.scale(0.3) - if curr_label is None: - label.target.to_corner(UR) - label.target.shift(MED_LARGE_BUFF * LEFT) - else: - label.target.next_to(curr_label, DOWN) - - curr_label = label - self.play(MoveToTarget(curr_label)) - self.wait() - - br = BackgroundRectangle(review_labels, buff=0.25) - br.set_fill(BLACK, 0.85) - br.set_width(FRAME_WIDTH) - br.set_height(FRAME_HEIGHT, stretch=True) - br.center() - self.add(br, review_labels) - self.play( - FadeOut(curr_fsr), - FadeIn(br), - ) - self.wait() - - -class ShowThreeCases(Scene): - def construct(self): - titles = self.get_titles() - reviews = self.get_reviews(titles) - for review in reviews: - review.match_x(reviews[2]) - - # Introduce everything - self.play(LaggedStartMap( - FadeInFrom, titles, - lambda m: (m, DOWN), - lag_ratio=0.2 - )) - self.play(LaggedStart(*[ - LaggedStartMap( - FadeInFromLarge, review, - lag_ratio=0.1 - ) - for review in reviews - ], lag_ratio=0.1)) - self.add(reviews) - self.wait() - - self.play(ShowCreationThenFadeAround(reviews[2])) - self.wait() - - # Suspicious of 100% - randy = Randolph() - randy.flip() - randy.set_height(2) - randy.next_to( - reviews[0], RIGHT, LARGE_BUFF, - aligned_edge=UP, - ) - randy.look_at(reviews[0]) - self.play(FadeIn(randy)) - self.play(randy.change, "sassy") - self.play(Blink(randy)) - self.wait() - self.play(FadeOut(randy)) - - # Low number means it could be a fluke. - review = reviews[0] - - review.generate_target() - review.target.scale(2) - review.target.arrange(RIGHT) - review.target.move_to(review) - - self.play(MoveToTarget(review)) - - alt_negs = [1, 2, 1, 0] - alt_reviews = VGroup() - for k in alt_negs: - alt_reviews.add(self.get_plusses_and_minuses(titles[0], 1, 10, k)) - for ar in alt_reviews: - for m1, m2 in zip(ar, review): - m1.replace(m2) - - alt_percents = VGroup(*[ - TexMobject(str(10 * (10 - k)) + "\\%") - for k in alt_negs - ]) - hundo = titles[0][0] - for ap in alt_percents: - fix_percent(ap.family_members_with_points()[-1]) - ap.match_style(hundo) - ap.match_height(hundo) - ap.move_to(hundo, RIGHT) - - last_review = review - last_percent = hundo - for ar, ap in zip(alt_reviews, alt_percents): - self.play( - FadeInFrom(ar, 0.5 * DOWN, lag_ratio=0.2), - FadeOut(last_review), - FadeInFrom(ap, 0.5 * DOWN), - FadeOutAndShift(last_percent, 0.5 * UP), - run_time=1.5 - ) - last_review = ar - last_percent = ap - self.remove(last_review, last_percent) - self.add(titles, *reviews) - - # How do you think about the tradeoff? - p1 = titles[1][0] - p2 = titles[2][0] - nums = VGroup(p1, p2) - lower_reviews = reviews[1:] - lower_reviews.generate_target() - lower_reviews.target.arrange(LEFT, buff=1.5) - lower_reviews.target.center() - nums.generate_target() - for nt, review in zip(nums.target, lower_reviews.target): - nt.next_to(review, UP, MED_LARGE_BUFF) - - nums.target[0].match_y(nums.target[1]) - - self.clear() - self.play( - MoveToTarget(lower_reviews), - MoveToTarget(nums), - FadeOut(titles[1][1:]), - FadeOut(titles[2][1:]), - FadeOut(titles[0]), - FadeOut(reviews[0]), - run_time=2, - ) - - greater_than = TexMobject(">") - greater_than.scale(2) - greater_than.move_to(midpoint( - reviews[2].get_right(), - reviews[1].get_left(), - )) - less_than = greater_than.copy().flip() - less_than.match_height(nums[0][0]) - less_than.match_y(nums, DOWN) - - nums.generate_target() - nums.target[1].next_to(less_than, LEFT, MED_LARGE_BUFF) - nums.target[0].next_to(less_than, RIGHT, MED_LARGE_BUFF) - - squares = VGroup(*[ - SurroundingRectangle( - submob, buff=0.01, - stroke_color=LIGHT_GREY, - stroke_width=1, - ) - for submob in reviews[2] - ]) - squares.shuffle() - self.play( - LaggedStartMap( - ShowCreationThenFadeOut, squares, - lag_ratio=0.5 / len(squares), - run_time=3, - ), - Write(greater_than), - ) - self.wait() - self.play( - MoveToTarget(nums), - TransformFromCopy( - greater_than, less_than, - ) - ) - self.wait() - - def get_titles(self): - titles = VGroup( - TextMobject( - "$100\\%$ \\\\", - "10 reviews" - ), - TextMobject( - "$96\\%$ \\\\", - "50 reviews" - ), - TextMobject( - "$93\\%$ \\\\", - "200 reviews" - ), - ) - colors = [PINK, BLUE, YELLOW] - for title, color in zip(titles, colors): - fix_percent(title[0][-1]) - title[0].set_color(color) - - titles.scale(1.25) - titles.arrange(DOWN, buff=1.5) - titles.to_corner(UL) - return titles - - def get_reviews(self, titles): - return VGroup( - self.get_plusses_and_minuses( - titles[0], 5, 2, 0, - ), - self.get_plusses_and_minuses( - titles[1], 5, 10, 2, - ), - self.get_plusses_and_minuses( - titles[2], 8, 25, 14, - ), - ) - - def get_plusses_and_minuses(self, title, n_rows, n_cols, n_minus): - check = TexMobject(CMARK_TEX, color=GREEN) - cross = TexMobject(XMARK_TEX, color=RED) - checks = VGroup(*[ - check.copy() for x in range(n_rows * n_cols) - ]) - checks.arrange_in_grid(n_rows=n_rows, n_cols=n_cols) - checks.scale(0.5) - # if checks.get_height() > title.get_height(): - # checks.match_height(title) - checks.next_to(title, RIGHT, LARGE_BUFF) - - for check in random.sample(list(checks), n_minus): - mob = cross.copy() - mob.replace(check, dim_to_match=0) - check.become(mob) - - return checks - - -class PreviewThreeVideos(Scene): - def construct(self): - # Write equations - equations = VGroup( - TexMobject("{10", "\\over", "10}", "=", "100\\%"), - TexMobject("{48", "\\over", "50}", "=", "96\\%"), - TexMobject("{186", "\\over", "200}", "=", "93\\%"), - ) - equations.arrange(RIGHT, buff=3) - equations.to_edge(UP) - - colors = [PINK, BLUE, YELLOW] - for eq, color in zip(equations, colors): - eq[-1].set_color(color) - fix_percent(eq[-1][-1]) - - vs_labels = VGroup(*[TextMobject("vs.") for x in range(2)]) - for eq1, eq2, vs in zip(equations, equations[1:], vs_labels): - vs.move_to(midpoint(eq1.get_right(), eq2.get_left())) - - self.add(equations) - self.add(vs_labels) - - # Show topics - title = TextMobject("To be explained:") - title.set_height(0.7) - title.next_to(equations, DOWN, LARGE_BUFF) - title.to_edge(LEFT) - title.add(Underline(title)) - - topics = VGroup( - TextMobject("Binomial distributions"), - TextMobject("Bayesian updating"), - TextMobject("Probability density functions"), - TextMobject("Beta distribution"), - ) - topics.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - topics.next_to(title, DOWN, MED_LARGE_BUFF) - topics.to_edge(LEFT, buff=LARGE_BUFF) - - bullets = VGroup() - for topic in topics: - bullet = Dot() - bullet.next_to(topic, LEFT) - bullets.add(bullet) - - self.play( - Write(title), - Write(bullets), - run_time=1, - ) - self.play(LaggedStart(*[ - FadeIn(topic, lag_ratio=0.1) - for topic in topics - ], run_time=3, lag_ratio=0.3)) - self.wait() - - # Show videos - images = [ - ImageMobject(os.path.join( - consts.VIDEO_DIR, - OUTPUT_DIRECTORY, - "images", - name - )) - for name in ["Thumbnail1", "Thumbnail2", "Thumbnail3"] - ] - thumbnails = Group() - for image in images: - image.set_width(FRAME_WIDTH / 3 - 1) - rect = SurroundingRectangle(image, buff=0) - rect.set_stroke(WHITE, 3) - rect.set_fill(BLACK, 1) - thumbnails.add(Group(rect, image)) - - thumbnails.arrange(RIGHT, buff=LARGE_BUFF) - - for topic, i in zip(topics, [0, 1, 1, 2]): - thumbnail = thumbnails[i] - topic.generate_target() - topic.target.scale(0.6) - topic.target.next_to(thumbnail, DOWN, aligned_edge=LEFT) - topics[2].target.next_to( - topics[1].target, DOWN, - aligned_edge=LEFT, - ) - - self.play( - FadeOutAndShift(title, LEFT), - FadeOutAndShift(bullets, LEFT), - LaggedStartMap(MoveToTarget, topics), - LaggedStartMap(FadeIn, thumbnails), - ) - self.wait() - - tn_groups = Group( - Group(thumbnails[0], topics[0]), - Group(thumbnails[1], topics[1], topics[2]), - Group(thumbnails[2], topics[3]), - ) - - setup_words = TextMobject("Set up the model") - analysis_words = TextMobject("Analysis") - for words in [setup_words, analysis_words]: - words.scale(topics[0][0].get_height() / words[0][0].get_height()) - words.set_color(YELLOW) - setup_words.move_to(topics[0], UL) - analysis_words.next_to(topics[3], DOWN, aligned_edge=LEFT) - - def set_opacity(mob, alpha): - for sm in mob.family_members_with_points(): - sm.set_opacity(alpha) - return mob - - self.play(ApplyFunction(lambda m: set_opacity(m, 0.2), tn_groups[1:])) - self.play( - FadeIn(setup_words, lag_ratio=0.1), - topics[0].next_to, setup_words, DOWN, {"aligned_edge": LEFT}, - ) - tn_groups[0].add(setup_words) - self.wait(2) - for i in 0, 1: - self.play( - ApplyFunction(lambda m: set_opacity(m, 0.2), tn_groups[i]), - ApplyFunction(lambda m: set_opacity(m, 1), tn_groups[i + 1]), - ) - self.wait(2) - self.play(FadeInFrom(analysis_words, 0.25 * UP)) - tn_groups[2].add(analysis_words) - self.wait(2) - - self.play( - FadeOut(setup_words), - FadeOut(topics[0]), - FadeOut(tn_groups[1]), - FadeOut(tn_groups[2]), - FadeOutAndShift(vs_labels, UP), - FadeOutAndShift(equations, UP), - ApplyFunction(lambda m: set_opacity(m, 1), thumbnails[0]), - ) - thumbnails[0].generate_target() - # thumbnails[0].target.set_width(FRAME_WIDTH) - # thumbnails[0].target.center() - thumbnails[0].target.to_edge(UP) - self.play(MoveToTarget(thumbnails[0], run_time=4)) - self.wait() - - -class LetsLookAtOneAnswer(TeacherStudentsScene): - def construct(self): - self.remove(self.background) - self.teacher_says( - "Let me show you\\\\one answer.", - added_anims=[ - self.get_student_changes("pondering", "thinking", "pondering") - ] - ) - self.look_at(self.screen) - self.change_all_student_modes("thinking", look_at_arg=self.screen) - self.wait(4) - - -class LaplacesRuleOfSuccession(Scene): - def construct(self): - # Setup - title = TextMobject("How to read a rating") - title.set_height(0.75) - title.to_edge(UP) - underline = Underline(title) - underline.scale(1.2) - self.add(title, underline) - - data = get_checks_and_crosses(11 * [True] + [False], width=10) - data.shift(DOWN) - underlines = get_underlines(data) - - real_data = data[:10] - fake_data = data[10:] - - def get_review_label(num, denom): - result = VGroup( - Integer(num, color=GREEN), - TextMobject("out of"), - Integer(denom), - ) - result.arrange(RIGHT) - result.set_height(0.6) - return result - - review_label = get_review_label(10, 10) - review_label.next_to(data[:10], UP, MED_LARGE_BUFF) - - # Show initial review - self.add(review_label) - self.add(underlines[:10]) - - self.play( - ShowIncreasingSubsets(real_data, int_func=np.ceil), - CountInFrom(review_label[0], 0), - rate_func=lambda t: smooth(t, 3), - ) - self.wait() - - # Fake data - fd_rect = SurroundingRectangle(VGroup(fake_data, underlines[10:])) - fd_rect.set_stroke(WHITE, 2) - fd_rect.set_fill(GREY_E, 1) - - fd_label = TextMobject("Pretend you see\\\\two more") - fd_label.next_to(fd_rect, DOWN) - fd_label.shift_onto_screen() - - self.play( - FadeInFrom(fd_label, UP), - DrawBorderThenFill(fd_rect), - ShowCreation(underlines[10:]) - ) - self.wait() - for mark in data[10:]: - self.play(Write(mark)) - self.wait() - - # Update rating - review_center = VectorizedPoint(review_label.get_center()) - pretend_label = TextMobject("Pretend that it's") - pretend_label.match_width(review_label) - pretend_label.next_to(review_label, UP, MED_LARGE_BUFF) - pretend_label.match_x(data) - pretend_label.set_color(BLUE_D) - - old_review_label = VGroup(Integer(0), TextMobject("out of"), Integer(0)) - old_review_label.become(review_label) - - self.add(old_review_label, review_label) - self.play( - review_center.set_x, data.get_center()[0], - MaintainPositionRelativeTo(review_label, review_center), - UpdateFromAlphaFunc( - review_label[0], - lambda m, a: m.set_value(int(interpolate(10, 11, a))) - ), - UpdateFromAlphaFunc( - review_label[2], - lambda m, a: m.set_value(int(interpolate(10, 12, a))) - ), - FadeInFrom(pretend_label, LEFT), - old_review_label.scale, 0.5, - old_review_label.set_opacity, 0.5, - old_review_label.to_edge, LEFT, - ) - self.wait() - - # Show fraction - eq = TexMobject( - "{11", "\\over", "12}", - "\\approx", "91.7\\%" - ) - fix_percent(eq[-1][-1]) - eq.set_color_by_tex("11", GREEN) - - eq.next_to(pretend_label, RIGHT) - eq.to_edge(RIGHT, buff=MED_LARGE_BUFF) - - self.play(Write(eq)) - self.wait() - self.play(ShowCreationThenFadeAround(eq)) - self.wait() - - # Remove clutter - old_review_label.generate_target() - old_review_label.target.next_to(title, DOWN, LARGE_BUFF) - old_review_label.target.to_edge(LEFT) - old_review_label.target.set_opacity(1) - arrow = Vector(0.5 * RIGHT) - arrow.next_to(old_review_label.target, RIGHT) - - self.play( - MoveToTarget(old_review_label), - FadeIn(arrow), - eq.next_to, arrow, RIGHT, - FadeOutAndShift( - VGroup( - fake_data, - underlines, - pretend_label, - review_label, - fd_rect, fd_label, - ), - DOWN, - lag_ratio=0.01, - ), - real_data.match_width, old_review_label.target, - real_data.next_to, old_review_label.target, DOWN, - ) - self.wait() - - # Show 48 of 50 case - # Largely copied from above...not great - data = get_checks_and_crosses( - 48 * [True] + 2 * [False] + [True, False], - width=FRAME_WIDTH - 1, - ) - data.shift(DOWN) - underlines = get_underlines(data) - - review_label = get_review_label(48, 50) - review_label.next_to(data, UP, MED_LARGE_BUFF) - - true_data = data[:-2] - fake_data = data[-2:] - - fd_rect.replace(fake_data, stretch=True) - fd_rect.stretch(1.2, 0) - fd_rect.stretch(2.2, 1) - fd_rect.shift(0.025 * DOWN) - fd_label.next_to(fd_rect, DOWN, LARGE_BUFF) - fd_label.shift_onto_screen() - fd_arrow = Arrow(fd_label.get_top(), fd_rect.get_corner(DL)) - - self.play( - FadeIn(underlines[:-2]), - ShowIncreasingSubsets(true_data, int_func=np.ceil), - CountInFrom(review_label[0], 0), - UpdateFromAlphaFunc( - review_label, - lambda m, a: m.set_opacity(a), - ), - ) - self.wait() - self.play( - FadeIn(fd_label), - GrowArrow(fd_arrow), - FadeIn(fd_rect), - Write(fake_data), - Write(underlines[-2:]), - ) - self.wait() - - # Pretend it's 49 / 52 - old_review_label = VGroup(Integer(0), TextMobject("out of"), Integer(0)) - old_review_label.become(review_label) - review_center = VectorizedPoint(review_label.get_center()) - - self.play( - review_center.set_x, data.get_center()[0] + 3, - MaintainPositionRelativeTo(review_label, review_center), - UpdateFromAlphaFunc( - review_label[0], - lambda m, a: m.set_value(int(interpolate(48, 49, a))) - ), - UpdateFromAlphaFunc( - review_label[2], - lambda m, a: m.set_value(int(interpolate(50, 52, a))) - ), - old_review_label.scale, 0.5, - old_review_label.to_edge, LEFT, - ) - self.wait() - - arrow2 = Vector(0.5 * RIGHT) - arrow2.next_to(old_review_label, RIGHT) - - eq2 = TexMobject( - "{49", "\\over", "52}", - "\\approx", "94.2\\%" - ) - fix_percent(eq2[-1][-1]) - eq2[0].set_color(GREEN) - eq2.next_to(arrow2, RIGHT) - eq2.save_state() - eq2[1].set_opacity(0) - eq2[3:].set_opacity(0) - eq2[0].replace(review_label[0]) - eq2[2].replace(review_label[2]) - - self.play( - Restore(eq2, run_time=1.5), - FadeIn(arrow2), - ) - self.wait() - - faders = VGroup( - fd_rect, fd_arrow, fd_label, - fake_data, underlines, - review_label, - ) - self.play( - FadeOut(faders), - true_data.match_width, old_review_label, - true_data.next_to, old_review_label, DOWN, - ) - - # 200 review case - final_review_label = get_review_label(186, 200) - final_review_label.match_height(old_review_label) - final_review_label.move_to(old_review_label, LEFT) - final_review_label.shift( - arrow2.get_center() - - arrow.get_center() - ) - - data = get_checks_and_crosses([True] * 186 + [False] * 14 + [True, False]) - data[:200].arrange_in_grid(10, 20, buff=0) - data[-2:].next_to(data[:200], DOWN, buff=0) - data.set_width(FRAME_WIDTH / 2 - 1) - data.to_edge(RIGHT, buff=MED_SMALL_BUFF) - data.to_edge(DOWN) - for mark in data: - mark.scale(0.5) - - true_data = data[:-2] - fake_data = data[-2:] - - self.play( - UpdateFromAlphaFunc( - final_review_label, - lambda m, a: m.set_opacity(a), - ), - CountInFrom(final_review_label[0], 0), - ShowIncreasingSubsets(true_data), - ) - self.wait() - - arrow3 = Vector(0.5 * RIGHT) - arrow3.next_to(final_review_label, RIGHT) - - eq3 = TexMobject( - "{187", "\\over", "202}", - "\\approx", "92.6\\%" - ) - fix_percent(eq3[-1][-1]) - eq3[0].set_color(GREEN) - eq3.next_to(arrow3, RIGHT) - - self.play( - GrowArrow(arrow3), - FadeIn(eq3), - Write(fake_data) - ) - self.wait() - self.play( - true_data.match_width, final_review_label, - true_data.next_to, final_review_label, DOWN, - FadeOut(fake_data) - ) - self.wait() - - # Make a selection - rect = SurroundingRectangle(VGroup(eq2, old_review_label)) - rect.set_stroke(YELLOW, 2) - - self.play( - ShowCreation(rect), - eq2[-1].set_color, YELLOW, - ) - self.wait() - - # Retitle - name = TextMobject("Laplace's rule of succession") - name.match_height(title) - name.move_to(title) - name.set_color(TEAL) - - self.play( - FadeInFromDown(name), - FadeOutAndShift(title, UP), - underline.match_width, name, - ) - self.wait() - - -class AskWhy(TeacherStudentsScene): - def construct(self): - self.student_says( - "Wait...why?", - look_at_arg=self.screen, - ) - self.play( - self.students[0].change, "confused", self.screen, - self.students[1].change, "confused", self.screen, - self.teacher.change, "tease", self.students[2].eyes, - ) - self.wait(3) - - self.students[2].bubble.content.unlock_triangulation() - self.student_says( - "Is that really\\\\the answer?", - target_mode="raise_right_hand", - added_anims=[self.teacher.change, "thinking"], - ) - self.wait(2) - self.teacher_says("Let's dive in!", target_mode="hooray") - self.change_all_student_modes("hooray") - self.wait(3) - - -class BinomialName(Scene): - def construct(self): - text = TextMobject("Probabilities of probabilities\\\\", "Part 1") - text.set_width(FRAME_WIDTH - 1) - text[0].set_color(BLUE) - self.add(text[0]) - self.play(Write(text[1], run_time=2)) - self.wait(2) - - -class WhatsTheModel(Scene): - CONFIG = { - "random_seed": 5, - } - - def construct(self): - self.add_questions() - self.introduce_buyer_and_seller() - - for x in range(3): - self.play(*self.experience_animations(self.seller, self.buyer)) - self.wait() - - self.add_probability_label() - self.bring_up_goal() - - def add_questions(self): - questions = VGroup( - TextMobject("What's the model?"), - TextMobject("What are you optimizing?"), - ) - for question, vect in zip(questions, [LEFT, RIGHT]): - question.move_to(vect * FRAME_WIDTH / 4) - questions.arrange(DOWN, buff=LARGE_BUFF) - questions.scale(1.5) - - # Intro questions - self.play(FadeInFrom(questions[0])) - self.play(FadeInFrom(questions[1], UP)) - self.wait() - questions[1].save_state() - - self.questions = questions - - def introduce_buyer_and_seller(self): - if hasattr(self, "questions"): - questions = self.questions - added_anims = [ - questions[0].to_edge, UP, - questions[1].set_opacity, 0.5, - questions[1].scale, 0.25, - questions[1].to_corner, UR, - ] - else: - added_anims = [] - - seller = Randolph(mode="coin_flip_1") - seller.to_edge(LEFT) - seller.label = TextMobject("Seller") - - buyer = Mortimer() - buyer.to_edge(RIGHT) - buyer.label = TextMobject("Buyer") - - VGroup(buyer, seller).shift(DOWN) - - labels = VGroup() - for pi in seller, buyer: - pi.set_height(2) - pi.label.scale(1.5) - pi.label.next_to(pi, DOWN, MED_LARGE_BUFF) - labels.add(pi.label) - buyer.make_eye_contact(seller) - - self.play( - LaggedStartMap( - FadeInFromDown, VGroup(seller, buyer, *labels), - lag_ratio=0.2 - ), - *added_anims - ) - self.wait() - - self.buyer = buyer - self.seller = seller - - def add_probability_label(self): - seller = self.seller - buyer = self.buyer - - label = get_prob_positive_experience_label() - label.add(TexMobject("=").next_to(label, RIGHT)) - rhs = DecimalNumber(0.75) - rhs.next_to(label, RIGHT) - rhs.align_to(label[0], DOWN) - label.add(rhs) - label.scale(1.5) - label.next_to(seller, UP, MED_LARGE_BUFF, aligned_edge=LEFT) - - rhs.set_color(YELLOW) - brace = Brace(rhs, UP) - success_rate = brace.get_text("Success rate")[0] - s_sym = brace.get_tex("s").scale(1.5, about_edge=DOWN) - success_rate.match_color(rhs) - s_sym.match_color(rhs) - - self.add(label) - - self.play( - GrowFromCenter(brace), - FadeInFrom(success_rate, 0.5 * DOWN) - ) - self.wait() - self.play( - TransformFromCopy(success_rate[0], s_sym), - FadeOutAndShift(success_rate, 0.1 * RIGHT, lag_ratio=0.1), - ) - for x in range(2): - self.play(*self.experience_animations(seller, buyer, arc=30 * DEGREES)) - self.wait() - - grey_box = SurroundingRectangle(rhs, buff=SMALL_BUFF) - grey_box.set_stroke(GREY_E, 0.5) - grey_box.set_fill(GREY_D, 1) - lil_q_marks = TexMobject("???") - lil_q_marks.scale(0.5) - lil_q_marks.next_to(buyer, UP) - - self.play( - FadeOutAndShift(rhs, 0.5 * DOWN), - FadeInFrom(grey_box, 0.5 * UP), - FadeInFrom(lil_q_marks, DOWN), - buyer.change, "confused", grey_box, - ) - rhs.set_opacity(0) - for x in range(2): - self.play(*self.experience_animations(seller, buyer, arc=30 * DEGREES)) - self.play(buyer.change, "confused", lil_q_marks) - self.play(Blink(buyer)) - - self.prob_group = VGroup( - label, grey_box, brace, s_sym, - ) - self.buyer_q_marks = lil_q_marks - - def bring_up_goal(self): - prob_group = self.prob_group - questions = self.questions - questions.generate_target() - questions.target[1].replace(questions[0], dim_to_match=1) - questions.target[1].match_style(questions[0]) - questions.target[0].replace(questions[1], dim_to_match=1) - questions.target[0].match_style(questions[1]) - - prob_group.generate_target() - prob_group.target.scale(0.5) - prob_group.target.next_to(self.seller, RIGHT) - - self.play( - FadeOut(self.buyer_q_marks), - self.buyer.change, "pondering", questions[0], - self.seller.change, "pondering", questions[0], - MoveToTarget(prob_group), - MoveToTarget(questions), - ) - self.play(self.seller.change, "coin_flip_1") - for x in range(3): - self.play(*self.experience_animations(self.seller, self.buyer)) - self.wait() - - # - def experience_animations(self, seller, buyer, arc=-30 * DEGREES, p=0.75): - positive = (random.random() < p) - words = TextMobject( - "Positive\\\\experience" - if positive else - "Negative\\\\experience" - ) - words.set_color(GREEN if positive else RED) - if positive: - new_mode = random.choice([ - "hooray", - "coin_flip_1", - ]) - else: - new_mode = random.choice([ - "tired", - "angry", - "sad", - ]) - - words.move_to(seller.get_corner(UR)) - result = [ - ApplyMethod( - words.move_to, buyer.get_corner(UL), - path_arc=arc, - run_time=2 - ), - VFadeInThenOut(words, run_time=2), - ApplyMethod( - buyer.change, new_mode, seller.eyes, - run_time=2, - rate_func=squish_rate_func(smooth, 0.5, 1), - ), - ApplyMethod( - seller.change, "coin_flip_2", buyer.eyes, - rate_func=there_and_back, - ), - ] - return result - - -class IsSellerOne100(Scene): - def construct(self): - self.add_review() - self.show_probability() - self.show_simulated_reviews() - - def add_review(self): - reviews = VGroup(*[TexMobject(CMARK_TEX) for x in range(10)]) - reviews.arrange(RIGHT) - reviews.scale(2) - reviews.set_color(GREEN) - reviews.next_to(ORIGIN, UP) - - blanks = VGroup(*[ - Line(LEFT, RIGHT).match_width(rev).next_to(rev, DOWN, SMALL_BUFF) - for rev in reviews - ]) - blanks.shift(0.25 * reviews[0].get_width() * LEFT) - - label = TextMobject( - " out of ", - ) - tens = VGroup(*[Integer(10) for x in range(2)]) - tens[0].next_to(label, LEFT) - tens[1].next_to(label, RIGHT) - tens.set_color(GREEN) - label.add(tens) - label.scale(2) - label.next_to(reviews, DOWN, LARGE_BUFF) - - self.add(label) - self.add(blanks) - tens[0].to_count = reviews - self.play( - ShowIncreasingSubsets(reviews, int_func=np.ceil), - UpdateFromAlphaFunc( - tens[0], - lambda m, a: m.set_color( - interpolate_color(RED, GREEN, a) - ).set_value(len(m.to_count)) - ), - run_time=2, - rate_func=bezier([0, 0, 1, 1]), - ) - self.wait() - - self.review_group = VGroup(reviews, blanks, label) - - def show_probability(self): - review_group = self.review_group - - prob_label = get_prob_positive_experience_label() - prob_label.add(TexMobject("=").next_to(prob_label, RIGHT)) - rhs = DecimalNumber(1) - rhs.next_to(prob_label, RIGHT) - rhs.set_color(YELLOW) - prob_label.add(rhs) - prob_label.scale(2) - prob_label.to_corner(UL) - - q_mark = TexMobject("?") - q_mark.set_color(YELLOW) - q_mark.match_height(rhs) - q_mark.reference = rhs - q_mark.add_updater(lambda m: m.next_to(m.reference, RIGHT)) - - rhs_rect = SurroundingRectangle(rhs, buff=0.2) - rhs_rect.set_color(RED) - - not_necessarily = TextMobject("Not necessarily!") - not_necessarily.set_color(RED) - not_necessarily.scale(1.5) - not_necessarily.next_to(prob_label, DOWN, 1.5) - arrow = Arrow( - not_necessarily.get_top(), - rhs_rect.get_corner(DL), - buff=MED_SMALL_BUFF, - ) - arrow.set_color(RED) - - rhs.set_value(0) - self.play( - ChangeDecimalToValue(rhs, 1), - UpdateFromAlphaFunc( - prob_label, - lambda m, a: m.set_opacity(a), - ), - FadeIn(q_mark), - ) - self.wait() - self.play( - ShowCreation(rhs_rect), - Write(not_necessarily), - ShowCreation(arrow), - review_group.to_edge, DOWN, - run_time=1, - ) - self.wait() - self.play( - ChangeDecimalToValue(rhs, 0.95), - FadeOut(rhs_rect), - FadeOut(arrow), - FadeOut(not_necessarily), - ) - self.wait() - - self.prob_label_group = VGroup( - prob_label, rhs, q_mark, - ) - - def show_simulated_reviews(self): - prob_label_group = self.prob_label_group - review_group = self.review_group - - # Set up decimals - random.seed(2) - decimals = VGroup() - for x in range(10): - dec = DecimalNumber() - decimals.add(dec) - - def randomize_decimals(decimals): - for dec in decimals: - value = random.random() - dec.set_value(value) - if value > 0.95: - dec.set_color(RED) - else: - dec.set_color(WHITE) - - randomize_decimals(decimals) - - decimals.set_height(0.3) - decimals.arrange(RIGHT, buff=MED_LARGE_BUFF) - decimals.next_to(ORIGIN, DOWN) - decimals[0].set_value(0.42) - decimals[0].set_color(WHITE) - decimals[1].set_value(0.97) - decimals[1].set_color(RED) - - random_label = TextMobject("Random number\\\\in [0, 1]") - random_label.scale(0.7) - random_label.next_to(decimals[0], DOWN) - random_label.set_color(GREY_B) - - arrows = VGroup() - for dec in decimals: - arrow = Vector(0.4 * UP) - arrow.next_to(dec, UP) - arrows.add(arrow) - - # Set up marks - def get_marks(decs, arrows): - marks = VGroup() - for dec, arrow in zip(decs, arrows): - if dec.get_value() < 0.95: - mark = TexMobject(CMARK_TEX) - mark.set_color(GREEN) - else: - mark = TexMobject(XMARK_TEX) - mark.set_color(RED) - mark.set_height(0.5) - mark.next_to(arrow, UP) - marks.add(mark) - return marks - - marks = get_marks(decimals, arrows) - - lt_p95 = TexMobject("< 0.95") - gte_p95 = TexMobject("\\ge 0.95") - for label in lt_p95, gte_p95: - label.match_height(decimals[0]) - - lt_p95.next_to(decimals[0], RIGHT, MED_SMALL_BUFF) - gte_p95.next_to(decimals[1], RIGHT, MED_SMALL_BUFF) - lt_p95.set_color(GREEN) - gte_p95.set_color(RED) - - # Introduce simulation - review_group.save_state() - self.play( - review_group.scale, 0.25, - review_group.to_corner, UR, - Write(random_label), - CountInFrom(decimals[0], 0), - ) - self.wait() - self.play(FadeInFrom(lt_p95, LEFT)) - self.play( - GrowArrow(arrows[0]), - FadeInFrom(marks[0], DOWN) - ) - self.wait() - self.play( - FadeOutAndShift(lt_p95, 0.5 * RIGHT), - FadeInFrom(gte_p95, 0.5 * LEFT), - ) - self.play( - random_label.match_x, decimals[1], - CountInFrom(decimals[1], 0), - UpdateFromAlphaFunc( - decimals[1], - lambda m, a: m.set_opacity(a), - ), - ) - self.play( - GrowArrow(arrows[1]), - FadeInFrom(marks[1], DOWN), - ) - self.wait() - self.play( - LaggedStartMap( - CountInFrom, decimals[2:], - ), - UpdateFromAlphaFunc( - decimals[2:], - lambda m, a: m.set_opacity(a), - ), - FadeOut(gte_p95), - run_time=1, - ) - self.add(decimals) - self.play( - LaggedStartMap(GrowArrow, arrows[2:]), - LaggedStartMap(FadeInFromDown, marks[2:]), - run_time=1 - ) - self.add(arrows, marks) - self.wait() - - # Add new rows - decimals.arrows = arrows - decimals.add_updater(lambda d: d.next_to(d.arrows, DOWN)) - added_anims = [FadeOut(random_label)] - rows = VGroup(marks) - for x in range(3): - self.play( - arrows.shift, DOWN, - UpdateFromFunc(decimals, randomize_decimals), - *added_anims, - ) - added_anims = [] - new_marks = get_marks(decimals, arrows) - self.play(LaggedStartMap(FadeInFromDown, new_marks)) - self.wait() - rows.add(new_marks) - - # Create a stockpile of new rows - added_rows = VGroup() - decimals.clear_updaters() - decimals.save_state() - for x in range(100): - randomize_decimals(decimals) - added_rows.add(get_marks(decimals, arrows)) - decimals.restore() - - # Compress rows - rows.generate_target() - for group in rows.target, added_rows: - group.scale(0.3) - for row in group: - row.arrange(RIGHT, buff=SMALL_BUFF) - group.arrange(DOWN, buff=0.2) - rows.target.next_to(prob_label_group, DOWN, MED_LARGE_BUFF) - rows.target.set_x(-3.5) - - nr = 15 - added_rows[:nr].move_to(rows.target, UP) - added_rows[nr:2 * nr].move_to(rows.target, UP) - added_rows[nr:2 * nr].shift(3.5 * RIGHT) - added_rows[2 * nr:3 * nr].move_to(rows.target, UP) - added_rows[2 * nr:3 * nr].shift(7 * RIGHT) - added_rows = added_rows[4:3 * nr] - - self.play( - MoveToTarget(rows), - FadeOut(decimals), - FadeOut(arrows), - ) - self.play(ShowIncreasingSubsets(added_rows), run_time=3) - - # Show scores - all_rows = VGroup(*rows, *added_rows) - scores = VGroup() - ten_rects = VGroup() - for row in all_rows: - score = Integer(sum([ - mark.get_color() == Color(GREEN) - for mark in row - ])) - score.match_height(row) - score.next_to(row, RIGHT) - if score.get_value() == 10: - score.set_color(TEAL) - ten_rects.add(SurroundingRectangle(score)) - scores.add(score) - - ten_rects.set_stroke(YELLOW, 2) - - self.play(FadeIn(scores)) - self.wait() - self.play(LaggedStartMap(ShowCreation, ten_rects)) - self.play(LaggedStartMap(FadeOut, ten_rects)) - self.wait(2) - - # Show alternate possibilities - prob = DecimalNumber(0.95) - prob.set_color(YELLOW) - template = prob_label_group[0][-1] - prob.match_height(template) - prob.move_to(template, LEFT) - rect = BackgroundRectangle(template, buff=SMALL_BUFF) - rect.set_fill(BLACK, 1) - self.add(rect) - self.add(prob) - self.play( - LaggedStartMap(FadeOutAndShift, all_rows, lag_ratio=0.01), - LaggedStartMap(FadeOutAndShift, scores, lag_ratio=0.01), - Restore(review_group), - ) - for value in [0.9, 0.99, 0.8, 0.95]: - self.play(ChangeDecimalToValue(prob, value)) - self.wait() - - # No longer used - def show_random_numbers(self): - prob_label_group = self.prob_label_group - - random.seed(2) - rows = VGroup(*[ - VGroup(*[ - Integer( - random.randint(0, 99) - ).move_to(0.85 * x * RIGHT) - for x in range(10) - ]) - for y in range(10 * 2) - ]) - rows.arrange_in_grid(n_cols=2, buff=MED_LARGE_BUFF) - rows[:10].shift(LEFT) - rows.set_height(5.5) - rows.center().to_edge(DOWN) - - lt_95 = VGroup(*[ - mob - for row in rows - for mob in row - if mob.get_value() < 95 - ]) - - square = Square() - square.set_stroke(width=0) - square.set_fill(YELLOW, 0.5) - square.set_width(1.5 * rows[0][0].get_height()) - # highlights = VGroup(*[ - # square.copy().move_to(mob) - # for row in rows - # for mob in row - # if mob.get_value() < 95 - # ]) - - row_rects = VGroup(*[ - SurroundingRectangle(row) - for row in rows - if all([m.get_value() < 95 for m in row]) - ]) - row_rects.set_stroke(GREEN, 2) - - self.play( - LaggedStartMap( - ShowIncreasingSubsets, rows, - run_time=3, - lag_ratio=0.25, - ), - FadeOutAndShift(self.review_group, DOWN), - prob_label_group.set_height, 0.75, - prob_label_group.to_corner, UL, - ) - self.wait() - # self.add(highlights, rows) - self.play( - # FadeIn(highlights) - lt_95.set_fill, BLUE, - lt_95.set_stroke, BLUE, 2, {"background": True}, - ) - self.wait() - self.play(LaggedStartMap(ShowCreation, row_rects)) - self.wait() - - -class LookAtAllPossibleSuccessRates(Scene): - def construct(self): - axes = get_beta_dist_axes(y_max=6, y_unit=1) - dist = scipy.stats.beta(10, 2) - graph = axes.get_graph(dist.pdf) - graph.set_stroke(BLUE, 3) - flat_graph = graph.copy() - flat_graph.points[:, 1] = axes.c2p(0, 0)[1] - flat_graph.set_stroke(YELLOW, 3) - - x_labels = axes.x_axis.numbers - x_labels.set_opacity(0) - - sellers = VGroup(*[ - self.get_example_seller(label.get_value()) - for label in x_labels - ]) - sellers.arrange(RIGHT, buff=LARGE_BUFF) - sellers.set_width(FRAME_WIDTH - 1) - sellers.to_edge(UP, buff=LARGE_BUFF) - - sellers.generate_target() - for seller, label in zip(sellers.target, x_labels): - seller.next_to(label, DOWN) - seller[0].set_opacity(0) - seller[1].set_opacity(0) - seller[2].replace(label, dim_to_match=1) - - x_label = TextMobject("All possible success rates") - x_label.next_to(axes.c2p(0.5, 0), UP) - x_label.shift(2 * LEFT) - - y_axis_label = TextMobject( - "A kind of probability\\\\", - "of probabilities" - ) - y_axis_label.scale(0.75) - y_axis_label.next_to(axes.y_axis, RIGHT) - y_axis_label.to_edge(UP) - y_axis_label[1].set_color(YELLOW) - - graph_label = TextMobject( - "Some notion of likelihood\\\\", - "for each one" - ) - graph_label[1].align_to(graph_label[0], LEFT) - graph_label.next_to(graph.get_boundary_point(UP), UP) - graph_label.shift(0.5 * DOWN) - graph_label.to_edge(RIGHT) - - x_axis_line = Line(axes.c2p(0, 0), axes.c2p(1, 0)) - x_axis_line.set_stroke(YELLOW, 3) - - shuffled_sellers = VGroup(*sellers) - shuffled_sellers.shuffle() - self.play(GrowFromCenter(shuffled_sellers[0])) - self.play(LaggedStartMap( - FadeInFromPoint, shuffled_sellers[1:], - lambda m: (m, sellers.get_center()) - )) - self.wait() - self.play( - MoveToTarget(sellers), - FadeIn(axes), - run_time=2, - ) - - self.play( - x_label.shift, 4 * RIGHT, - UpdateFromAlphaFunc( - x_label, - lambda m, a: m.set_opacity(a), - rate_func=there_and_back, - ), - ShowCreation(x_axis_line), - run_time=3, - ) - self.play(FadeOut(x_axis_line)) - self.wait() - self.play( - FadeInFromDown(graph_label), - ReplacementTransform(flat_graph, graph), - ) - self.wait() - self.play(FadeInFromDown(y_axis_label)) - - # Show probabilities - x_tracker = ValueTracker(0.5) - - prob_label = get_prob_positive_experience_label(True, True, True) - prob_label.next_to(axes.c2p(0, 2), RIGHT, MED_LARGE_BUFF) - prob_label.decimal.tracker = x_tracker - prob_label.decimal.add_updater( - lambda m: m.set_value(m.tracker.get_value()) - ) - - v_line = Line(DOWN, UP) - v_line.set_stroke(YELLOW, 2) - v_line.tracker = x_tracker - v_line.graph = graph - v_line.axes = axes - v_line.add_updater( - lambda m: m.put_start_and_end_on( - m.axes.x_axis.n2p(m.tracker.get_value()), - m.axes.input_to_graph_point(m.tracker.get_value(), m.graph), - ) - ) - - self.add(v_line) - for x in [0.95, 0.8, 0.9]: - self.play( - x_tracker.set_value, x, - run_time=4, - ) - self.wait() - - def get_example_seller(self, success_rate): - randy = Randolph(mode="coin_flip_1", height=1) - label = TexMobject("s = ") - decimal = DecimalNumber(success_rate) - decimal.match_height(label) - decimal.next_to(label[-1], RIGHT) - label.set_color(YELLOW) - decimal.set_color(YELLOW) - VGroup(label, decimal).next_to(randy, DOWN) - result = VGroup(randy, label, decimal) - result.randy = randy - result.label = label - result.decimal = decimal - return result - - -class AskAboutUnknownProbabilities(Scene): - def construct(self): - # Setup - unknown_title, prob_title = titles = self.get_titles() - - v_line = Line(UP, DOWN) - v_line.set_height(FRAME_HEIGHT) - v_line.set_stroke([WHITE, LIGHT_GREY], 3) - h_line = Line(LEFT, RIGHT) - h_line.set_width(FRAME_WIDTH) - h_line.next_to(titles, DOWN) - - processes = VGroup( - get_random_coin(shuffle_time=1), - get_random_die(shuffle_time=1.5), - get_random_card(shuffle_time=2), - ) - processes.arrange(DOWN, buff=0.7) - processes.next_to(unknown_title, DOWN, LARGE_BUFF) - processes_rect = BackgroundRectangle(processes) - processes_rect.set_fill(BLACK, 1) - - prob_labels = VGroup( - TexMobject("P(", "00", ")", "=", "1 / 2"), - TexMobject("P(", "00", ")", "=", "1 / 6}"), - TexMobject("P(", "00", ")", "=", "1 / 52}"), - ) - prob_labels.scale(1.5) - prob_labels.arrange(DOWN, aligned_edge=LEFT) - prob_labels.match_x(prob_title) - for pl, pr in zip(prob_labels, processes): - pl.match_y(pr) - content = pr[1].copy() - content.replace(pl[1], dim_to_match=0) - pl.replace_submobject(1, content) - - # Putting numbers to the unknown - number_rects = VGroup(*[ - SurroundingRectangle(pl[-1]) - for pl in prob_labels - ]) - number_rects.set_stroke(YELLOW, 2) - - for pl in prob_labels: - pl.save_state() - pl[:3].match_x(prob_title) - pl[3:].match_x(prob_title) - pl.set_opacity(0) - - self.add(processes) - self.play( - LaggedStartMap(FadeInFromDown, titles), - LaggedStart( - ShowCreation(v_line), - ShowCreation(h_line), - lag_ratio=0.1, - ), - LaggedStartMap(Restore, prob_labels), - run_time=1 - ) - self.wait(10) - # self.play( - # LaggedStartMap( - # ShowCreationThenFadeOut, - # number_rects, - # run_time=3, - # ) - # ) - # self.wait(2) - - # Highlight coin flip - fade_rects = VGroup(*[ - VGroup( - BackgroundRectangle(pl, buff=MED_SMALL_BUFF), - BackgroundRectangle(pr, buff=MED_SMALL_BUFF), - ) - for pl, pr in zip(prob_labels, processes) - ]) - fade_rects.set_fill(BLACK, 0.8) - - prob_half = prob_labels[0] - half = prob_half[-1] - half_underline = Line(LEFT, RIGHT) - half_underline.set_width(half.get_width() + MED_SMALL_BUFF) - half_underline.next_to(half, DOWN, buff=SMALL_BUFF) - half_underline.set_stroke(YELLOW, 3) - - self.play( - FadeIn(fade_rects[1]), - FadeIn(fade_rects[2]), - ) - self.wait(2) - self.play( - FadeIn(fade_rects[0]), - FadeOut(fade_rects[1]), - ) - self.wait(3) - self.play( - FadeOut(fade_rects[0]), - FadeOut(fade_rects[2]), - ) - self.wait(4) - - # Transition to question - processes.suspend_updating() - self.play( - LaggedStart( - FadeOutAndShift(unknown_title, UP), - FadeOutAndShift(prob_title, UP), - lag_ratio=0.2, - ), - FadeOutAndShift(h_line, UP, lag_ratio=0.1), - FadeOutAndShift(processes, LEFT, lag_ratio=0.1), - FadeOut(prob_labels[1]), - FadeOut(prob_labels[2]), - v_line.rotate, 90 * DEGREES, - v_line.shift, 0.6 * FRAME_HEIGHT * UP, - prob_half.center, - prob_half.to_edge, UP, - run_time=2, - ) - self.clear() - self.add(prob_half) - - arrow = Vector(UP) - arrow.next_to(half, DOWN) - question = TextMobject("What exactly does\\\\this mean?") - question.next_to(arrow, DOWN) - - self.play( - GrowArrow(arrow), - FadeInFrom(question, UP), - ) - self.wait(2) - self.play( - FadeOutAndShift(question, RIGHT), - Rotate(arrow, 90 * DEGREES), - VFadeOut(arrow), - ) - - # Show long run averages - self.show_many_coins(20, 50) - self.show_many_coins(40, 100) - - # Make probability itself unknown - q_marks = TexMobject("???") - q_marks.set_color(YELLOW) - q_marks.replace(half, dim_to_match=0) - - randy = Randolph(mode="confused") - randy.center() - randy.look_at(prob_half) - - self.play( - FadeOutAndShift(half, UP), - FadeInFrom(q_marks, DOWN), - ) - self.play(FadeIn(randy)) - self.play(Blink(randy)) - self.wait() - - # self.embed() - - def get_titles(self): - unknown_label = TextMobject("Random process") - prob_label = TextMobject("Long-run frequency") - titles = VGroup(unknown_label, prob_label) - titles.scale(1.25) - - unknown_label.move_to(FRAME_WIDTH * LEFT / 4) - prob_label.move_to(FRAME_WIDTH * RIGHT / 4) - titles.to_edge(UP, buff=MED_SMALL_BUFF) - titles.set_color(BLUE) - return titles - - def show_many_coins(self, n_rows, n_cols): - coin_choices = VGroup( - get_coin("H"), - get_coin("T"), - ) - coin_choices.set_stroke(width=0) - coins = VGroup(*[ - random.choice(coin_choices).copy() - for x in range(n_rows * n_cols) - ]) - - def organize_coins(coin_group): - coin_group.scale(1 / coin_group[0].get_height()) - coin_group.arrange_in_grid(n_rows=n_rows) - coin_group.set_width(FRAME_WIDTH - 1) - coin_group.to_edge(DOWN, MED_LARGE_BUFF) - - organize_coins(coins) - - sorted_coins = VGroup() - for coin in coins: - coin.generate_target() - sorted_coins.add(coin.target) - sorted_coins.submobjects.sort(key=lambda m: m.symbol) - organize_coins(sorted_coins) - - self.play(LaggedStartMap( - FadeInFrom, coins, - lambda m: (m, 0.2 * DOWN), - run_time=3, - rate_func=linear - )) - self.wait() - self.play(LaggedStartMap( - MoveToTarget, coins, - path_arc=30 * DEGREES, - run_time=2, - lag_ratio=1 / len(coins), - )) - self.wait() - self.play(FadeOut(coins)) - - -class AskProbabilityOfCoins(Scene): - def construct(self): - condition = VGroup( - TextMobject("If you've seen"), - Integer(80, color=BLUE_C), - get_coin("H").set_height(0.5), - TextMobject("and"), - Integer(20, color=RED_C), - get_coin("T").set_height(0.5), - ) - condition.arrange(RIGHT) - condition.to_edge(UP) - self.add(condition) - - question = TexMobject( - "\\text{What is }", - "P(", "00", ")", "?" - ) - coin = get_coin("H") - coin.replace(question.get_part_by_tex("00")) - question.replace_submobject( - question.index_of_part_by_tex("00"), - coin - ) - question.next_to(condition, DOWN) - self.add(question) - - values = ["H"] * 80 + ["T"] * 20 - random.shuffle(values) - - coins = VGroup(*[ - get_coin(symbol) - for symbol in values - ]) - coins.arrange_in_grid(10, 10, buff=MED_SMALL_BUFF) - coins.set_width(5) - coins.next_to(question, DOWN, MED_LARGE_BUFF) - - self.play( - ShowIncreasingSubsets(coins), - run_time=8, - rate_func=bezier([0, 0, 1, 1]) - ) - self.wait() - - self.embed() - - -class RunCarFactory(Scene): - def construct(self): - # Factory - factory = SVGMobject(file_name="factory") - factory.set_fill(GREY_D) - factory.set_stroke(width=0) - factory.flip() - factory.set_height(6) - factory.to_edge(LEFT) - - self.add(factory) - - # Dumb hack - l1 = Line( - factory[0].points[-200], - factory[0].points[-216], - ) - l2 = Line( - factory[0].points[-300], - factory[0].points[-318], - ) - for line in l1, l2: - square = Square() - square.set_fill(BLACK, 1) - square.set_stroke(width=0) - square.replace(line) - factory.add(square) - - rect = Rectangle() - rect.match_style(factory) - rect.set_height(1.1) - rect.set_width(6.75, stretch=True) - rect.move_to(factory, DL) - - # Get cars - car = Car(color=interpolate_color(BLUE_E, GREY_C, 0.5)) - car.set_height(0.9) - for tire in car.get_tires(): - tire.set_fill(GREY_C) - tire.set_stroke(BLACK) - car.randy.set_opacity(0) - car.move_to(rect.get_corner(DR)) - - cars = VGroup() - n_cars = 20 - for x in range(n_cars): - cars.add(car.copy()) - - for car in cars[4], cars[6]: - scratch = VMobject() - scratch.start_new_path(UP) - scratch.add_line_to(0.25 * DL) - scratch.add_line_to(0.25 * UR) - scratch.add_line_to(DOWN) - scratch.set_stroke([RED_A, RED_C], [0.1, 2, 2, 0.1]) - scratch.set_height(0.25) - scratch.move_to(car) - scratch.shift(0.1 * DOWN) - car.add(scratch) - - self.add(cars, rect) - self.play(LaggedStartMap( - MoveCar, cars, - lambda m: (m, m.get_corner(DR) + 10 * RIGHT), - lag_ratio=0.3, - rate_func=linear, - run_time=1.5 * n_cars, - )) - self.remove(cars) - - -class CarFactoryNumbers(Scene): - def construct(self): - # Test words - denom_words = TextMobject( - "in a test of 100 cars", - tex_to_color_map={"100": BLUE}, - ) - denom_words.to_corner(UR) - - numer_words = TextMobject( - "2 defects found", - tex_to_color_map={"2": RED} - ) - numer_words.move_to(denom_words, LEFT) - - self.play(Write(denom_words, run_time=1)) - self.wait() - self.play( - denom_words.next_to, numer_words, DOWN, {"aligned_edge": LEFT}, - FadeIn(numer_words), - ) - self.wait() - - # Question words - question = VGroup( - TextMobject("How do you plan"), - TextMobject("for"), - Integer(int(1e6), color=BLUE), - TextMobject("cars?") - ) - question[1:].arrange(RIGHT, aligned_edge=DOWN) - question[2].shift( - (question[2][1].get_bottom()[1] - question[2][0].get_bottom()[1]) * UP - ) - question[1:].next_to(question[0], DOWN, aligned_edge=LEFT) - question.next_to(denom_words, DOWN, LARGE_BUFF, aligned_edge=LEFT) - - self.play( - UpdateFromAlphaFunc( - question, - lambda m, a: m.set_opacity(a), - ), - CountInFrom(question[2], 0, run_time=1.5) - ) - self.wait() - - -class ComplainAboutSimplisticModel(TeacherStudentsScene): - def construct(self): - axes = self.get_experience_graph() - - self.add(axes) - self.play( - self.teacher.change, "raise_right_hand", axes, - self.get_student_changes( - "pondering", "erm", "sassy", - look_at_arg=axes, - ), - ShowCreation( - axes.graph, - run_time=3, - rate_func=linear, - ), - ) - self.wait(2) - - student = self.students[2] - bubble = SpeechBubble( - direction=LEFT, - height=3, - width=5, - ) - bubble.pin_to(student) - bubble.write("What about something\\\\like this?") - - self.play( - axes.next_to, student, UL, - VFadeOut(axes.graph), - FadeIn(bubble), - Write(bubble.content, run_time=1), - student.change, "raise_left_hand", - self.students[0].change, "thinking", axes, - self.students[1].change, "thinking", axes, - self.teacher.change, "happy", - ) - - new_graph = VMobject() - new_graph.set_points_as_corners([ - axes.c2p(0, 0.75), - axes.c2p(2, 0.9), - axes.c2p(4, 0.5), - axes.c2p(6, 0.75), - axes.c2p(8, 0.55), - axes.c2p(10, 0.95), - ]) - new_graph.make_smooth() - new_graph.set_stroke([YELLOW, RED, GREEN], 2) - - self.play( - ShowCreation(new_graph), - *[ - ApplyMethod(pi.look_at, new_graph) - for pi in self.pi_creatures - ] - ) - self.wait(3) - - def get_experience_graph(self): - axes = Axes( - x_min=-1, - x_max=10, - y_min=0, - y_max=1.25, - y_axis_config={ - "unit_size": 5, - "tick_frequency": 0.25, - "include_tip": False, - } - ) - axes.set_stroke(LIGHT_GREY, 1) - axes.set_height(3) - y_label = TextMobject("Experience quality") - y_label.scale(0.5) - y_label.next_to(axes.y_axis.get_top(), RIGHT, SMALL_BUFF) - axes.add(y_label) - - lines = VGroup() - for x in range(10): - lines.add( - Line(axes.c2p(x, 0), axes.c2p(x + 0.9, 0)) - ) - lines.set_stroke(RED, 3) - for line in lines: - if random.random() < 0.5: - line.set_y(axes.c2p(0, 1)[1]) - line.set_stroke(GREEN) - - axes.add(lines) - axes.graph = lines - - rect = BackgroundRectangle(axes, buff=0.25) - rect.set_stroke(WHITE, 1) - rect.set_fill(BLACK, 1) - - axes.add_to_back(rect) - axes.to_corner(UR) - - return axes - - -class ComingUpWrapper(Scene): - def construct(self): - background = FullScreenFadeRectangle() - background.set_fill(GREY_E, 1) - - title = TextMobject("What's coming...") - title.scale(1.5) - title.to_edge(UP) - - rect = ScreenRectangle() - rect.set_height(6) - rect.set_stroke(WHITE) - rect.set_fill(BLACK, 1) - rect.next_to(title, DOWN) - - self.add(background, rect) - self.play(FadeInFromDown(title)) - self.wait() - - -class PreviewBeta(Scene): - def construct(self): - axes = get_beta_dist_axes(label_y=True) - axes.y_axis.remove(axes.y_axis.numbers) - marks = get_plusses_and_minuses(p=0.75) - marks.next_to(axes.y_axis.get_top(), DR, buff=0.75) - - beta_label = get_beta_label(0, 0) - beta_label.next_to(marks, UR, buff=LARGE_BUFF) - beta_label.to_edge(UP) - bl_left = beta_label.get_left() - - beta_container = VGroup() - graph_container = VGroup() - n_graphs = 2 - for x in range(n_graphs): - graph_container.add(VMobject()) - - def get_counts(marks): - is_plusses = [m.is_plus for m in marks] - p = sum(is_plusses) - n = len(is_plusses) - p - return p, n - - def update_beta(container): - counts = get_counts(marks) - new_label = get_beta_label(*counts) - new_label.move_to(bl_left, LEFT) - container.set_submobjects([new_label]) - return container - - def update_graph(container): - counts = get_counts(marks) - new_graph = get_beta_graph(axes, *counts) - new_graphs = [*container[1:], new_graph] - for g, a in zip(new_graphs, np.linspace(0.2, 1, n_graphs)): - g.set_opacity(a) - - container.set_submobjects(new_graphs) - return container - - self.add(axes) - self.play( - ShowIncreasingSubsets(marks), - UpdateFromFunc( - beta_container, - update_beta, - ), - UpdateFromFunc( - graph_container, - update_graph, - ), - run_time=15, - rate_func=bezier([0, 0, 1, 1]), - ) - self.wait() - - -class AskInverseQuestion(WhatsTheModel): - def construct(self): - self.force_skipping() - self.introduce_buyer_and_seller() - self.bs_group = VGroup( - self.buyer, - self.seller, - self.buyer.label, - self.seller.label, - ) - self.bs_group.to_edge(DOWN) - self.revert_to_original_skipping_status() - - self.add_probability_label() - self.show_many_review_animations() - self.ask_question() - - def add_probability_label(self): - label = get_prob_positive_experience_label(True, True, False) - label.decimal.set_value(0.95) - label.next_to(self.seller, UP, aligned_edge=LEFT, buff=MED_LARGE_BUFF) - - self.add(label) - self.probability_label = label - - def show_many_review_animations(self): - for x in range(7): - self.play(*self.experience_animations( - self.seller, - self.buyer, - arc=30 * DEGREES, - p=0.95, - )) - - def ask_question(self): - pis = [self.buyer, self.seller] - labels = VGroup( - self.get_prob_review_label(10, 0), - self.get_prob_review_label(48, 2), - self.get_prob_review_label(186, 14), - ) - labels.arrange(DOWN) - labels.to_edge(UP) - - labels[0].save_state() - labels[0].set_opacity(0) - words = labels[0][-3:-1] - words.set_opacity(1) - words.scale(1.5) - words.center().to_edge(UP) - - self.play( - FadeInFromDown(words), - ) - self.wait() - self.play( - Restore(labels[0]), - *[ - ApplyMethod(pi.change, 'pondering', labels) - for pi in pis - ] - ) - self.play(Blink(pis[0])) - self.play(Blink(pis[1])) - self.play(LaggedStartMap(FadeInFromDown, labels[1:])) - self.wait(2) - - # Succinct - short_label = TexMobject( - "P(\\text{data} | s)", - tex_to_color_map={ - "\\text{data}": LIGHT_GREY, - "s": YELLOW - } - ) - short_label.scale(2) - short_label.next_to(labels, DOWN, LARGE_BUFF), - rect = SurroundingRectangle(short_label, buff=MED_SMALL_BUFF) - bs_group = self.bs_group - bs_group.add(self.probability_label) - - self.play( - FadeInFrom(short_label, UP), - bs_group.scale, 0.5, {"about_edge": DOWN}, - ) - self.play(ShowCreation(rect)) - self.wait() - - def get_prob_review_label(self, n_positive, n_negative): - label = TexMobject( - "P(", - f"{n_positive}\\,{CMARK_TEX}", ",\\,", - f"{n_negative}\\,{XMARK_TEX}", - "\\,\\text{ Given that }", - "s = 0.95", - ")", - ) - label.set_color_by_tex_to_color_map({ - CMARK_TEX: GREEN, - XMARK_TEX: RED, - "0.95": YELLOW, - }) - return label - - -class SimulationsOf10Reviews(Scene): - CONFIG = { - "s": 0.95, - "histogram_height": 5, - "histogram_width": 10, - } - - def construct(self): - # Add s label - s_label = TexMobject("s = 0.95") - s_label.set_height(0.3) - s_label.to_corner(UL, buff=MED_SMALL_BUFF) - s_label.set_color(YELLOW) - self.add(s_label) - self.camera.frame.shift(LEFT) - s_label.shift(LEFT) - - # Add random row - np.random.seed(0) - row = get_random_num_row(self.s) - count = self.get_count(row) - count.add_updater( - lambda m: m.set_value( - sum([s.positive for s in row.syms]) - ) - ) - - def update_nums(nums): - for num in nums: - num.set_value(np.random.random()) - - row.nums.save_state() - row.nums.set_color(WHITE) - self.play( - UpdateFromFunc(row.nums, update_nums), - run_time=2, - ) - row.nums.restore() - self.wait() - - self.add(count) - self.play( - ShowIncreasingSubsets(row.syms), - run_time=2, - rate_func=linear, - ) - count.clear_updaters() - self.wait() - - # Histogram - data = np.zeros(11) - histogram = self.get_histogram(data) - - stacks = VGroup() - for bar in histogram.bars: - stacks.add(VGroup(bar.copy())) - - def put_into_histogram(row_count_group): - row, count = row_count_group - count.clear_updaters() - index = int(count.get_value()) - stack = stacks[index] - - row.set_width(stack.get_width() - SMALL_BUFF) - row.next_to(stack, UP, SMALL_BUFF) - count.replace(histogram.axes.x_labels[index]) - stack.add(row) - return row_count_group - - # Random samples in histogram - self.play( - FadeIn(histogram), - ApplyFunction( - put_into_histogram, - VGroup(row, count), - ) - ) - self.wait() - for x in range(2): - row = get_random_num_row(self.s) - count = self.get_count(row) - group = VGroup(row, count) - self.play(FadeIn(group, lag_ratio=0.2)) - self.wait(0.5) - self.play( - ApplyFunction( - put_into_histogram, - VGroup(row, count), - ) - ) - - # More! - for x in range(40): - row = get_random_num_row(self.s) - count = self.get_count(row) - lower_group = VGroup(row, count).copy() - put_into_histogram(lower_group) - self.add(row, count, lower_group) - self.wait(0.1) - self.remove(row, count) - - data = np.array([len(stack) - 1 for stack in stacks]) - self.add(row, count) - self.play( - FadeOut(stacks), - FadeOut(count), - histogram.bars.become, histogram.get_bars(data), - histogram.axes.y_labels.set_opacity, 1, - histogram.axes.h_lines.set_opacity, 1, - histogram.axes.y_axis.set_opacity, 1, - ) - self.remove(stacks) - - arrow = Vector(0.5 * DOWN) - arrow.set_stroke(width=5) - arrow.set_color(YELLOW) - arrow.next_to(histogram.bars[10], UP, SMALL_BUFF) - - def update(dummy): - new_row = get_random_num_row(self.s) - row.become(new_row) - count = sum([m.positive for m in new_row.nums]) - data[count] += 1 - histogram.bars.become(histogram.get_bars(data)) - arrow.next_to(histogram.bars[count], UP, SMALL_BUFF) - - self.add(arrow) - self.play( - UpdateFromFunc(Group(row, arrow, histogram.bars), update), - run_time=10, - ) - - # - def get_histogram(self, data): - histogram = Histogram( - data, - bar_colors=[RED, RED, BLUE, GREEN], - height=self.histogram_height, - width=self.histogram_width, - ) - histogram.to_edge(DOWN) - - histogram.axes.y_labels.set_opacity(0) - histogram.axes.h_lines.set_opacity(0) - return histogram - - def get_count(self, row): - count = Integer() - count.set_height(0.75) - count.next_to(row, DOWN, buff=0.65) - count.set_value(sum([s.positive for s in row.syms])) - return count - - -class SimulationsOf10ReviewsSquished(SimulationsOf10Reviews): - CONFIG = { - "histogram_height": 2, - "histogram_width": 11, - } - - def get_histogram(self, data): - hist = super().get_histogram(data) - hist.to_edge(UP, buff=1.5) - return hist - - -class SimulationsOf50Reviews(Scene): - CONFIG = { - "s": 0.95, - "histogram_config": { - "x_label_freq": 5, - "y_axis_numbers_to_show": range(10, 70, 10), - "y_max": 0.6, - "y_tick_freq": 0.1, - "height": 5, - "bar_colors": [BLUE], - }, - "random_seed": 1, - } - - def construct(self): - self.add_s_label() - - data = np.zeros(51) - histogram = self.get_histogram(data) - - row = self.get_row() - count = self.get_count(row) - original_count = count.get_value() - count.set_value(0) - - self.add(histogram) - self.play( - ShowIncreasingSubsets(row), - ChangeDecimalToValue(count, original_count) - ) - - # Run many samples - arrow = Vector(0.5 * DOWN) - arrow.set_stroke(width=5) - arrow.set_color(YELLOW) - arrow.next_to(histogram.bars[10], UP, SMALL_BUFF) - - total_data_label = VGroup( - TextMobject("Total samples: "), - Integer(1), - ) - total_data_label.arrange(RIGHT) - total_data_label.next_to(row, DOWN) - total_data_label.add_updater( - lambda m: m[1].set_value(data.sum()) - ) - - def update(dummy, n_added_data_points=0): - new_row = self.get_row() - row.become(new_row) - num_positive = sum([m.positive for m in new_row]) - count.set_value(num_positive) - data[num_positive] += 1 - if n_added_data_points: - values = np.random.random((n_added_data_points, 50)) - counts = (values < self.s).sum(1) - for i in range(len(data)): - data[i] += (counts == i).sum() - histogram.bars.become(histogram.get_bars(data)) - histogram.bars.set_fill(GREY_C) - histogram.bars[48].set_fill(GREEN) - arrow.next_to(histogram.bars[num_positive], UP, SMALL_BUFF) - - self.add(arrow, total_data_label) - group = VGroup(histogram.bars, row, count, arrow) - self.play( - UpdateFromFunc(group, update), - run_time=4 - ) - self.play( - UpdateFromFunc( - group, - lambda m: update(m, 1000) - ), - run_time=4 - ) - random.seed(0) - np.random.seed(0) - update(group) - self.wait() - - # Show 48 bar - axes = histogram.axes - y = choose(50, 48) * (self.s)**48 * (1 - self.s)**2 - line = DashedLine( - axes.c2p(0, y), - axes.c2p(51, y), - ) - label = TexMobject("{:.1f}\\%".format(100 * y)) - fix_percent(label.family_members_with_points()[-1]) - label.next_to(line, RIGHT) - - self.play( - ShowCreation(line), - FadeInFromPoint(label, line.get_start()) - ) - - def add_s_label(self): - s_label = TexMobject("s = 0.95") - s_label.set_height(0.3) - s_label.to_corner(UL, buff=MED_SMALL_BUFF) - s_label.shift(0.8 * DOWN) - s_label.set_color(YELLOW) - self.add(s_label) - - def get_histogram(self, data): - histogram = Histogram( - data, **self.histogram_config - ) - histogram.to_edge(DOWN) - return histogram - - def get_row(self, n=50): - row = get_random_checks_and_crosses(n, self.s) - row.move_to(3.5 * UP) - return row - - def get_count(self, row): - count = Integer(sum([m.positive for m in row])) - count.set_height(0.3) - count.next_to(row, RIGHT) - return count - - -class ShowBinomialFormula(SimulationsOf50Reviews): - CONFIG = { - "histogram_config": { - "x_label_freq": 5, - "y_axis_numbers_to_show": range(10, 40, 10), - "y_max": 0.3, - "y_tick_freq": 0.1, - "height": 2.5, - "bar_colors": [BLUE], - }, - "random_seed": 0, - } - - def construct(self): - # Add histogram - dist = scipy.stats.binom(50, self.s) - data = np.array([ - dist.pmf(x) - for x in range(0, 51) - ]) - histogram = self.get_histogram(data) - histogram.bars.set_fill(GREY_C) - histogram.bars[48].set_fill(GREEN) - self.add(histogram) - - row = self.get_row() - self.add(row) - - # Formula - prob_label = get_prob_review_label(48, 2) - eq = TexMobject("=") - formula = get_binomial_formula(50, 48, self.s) - - equation = VGroup( - prob_label, - eq, - formula, - ) - equation.arrange(RIGHT) - equation.next_to(histogram, UP, LARGE_BUFF) - equation.to_edge(RIGHT) - - prob_label.save_state() - arrow = Vector(DOWN) - arrow.next_to(histogram.bars[48], UP, SMALL_BUFF) - prob_label.next_to(arrow, UP) - - self.play( - FadeIn(prob_label), - GrowArrow(arrow), - ) - for mob in prob_label[1::2]: - line = Underline(mob) - line.match_color(mob) - self.play(ShowCreationThenDestruction(line)) - self.wait(0.5) - self.play( - Restore(prob_label), - FadeIn(equation[1:], lag_ratio=0.1), - ) - self.wait() - - self.explain_n_choose_k(row, formula) - - # Circle formula parts - rect1 = SurroundingRectangle(formula[4:8]) - rect2 = SurroundingRectangle(formula[8:]) - rect1.set_stroke(GREEN, 2) - rect2.set_stroke(RED, 2) - - for rect in rect1, rect2: - self.play(ShowCreation(rect)) - self.wait() - self.play(FadeOut(rect)) - - # Show numerical answer - eq2 = TexMobject("=") - value = DecimalNumber(dist.pmf(48), num_decimal_places=5) - rhs = VGroup(eq2, value) - rhs.arrange(RIGHT) - rhs.match_y(eq) - rhs.to_edge(RIGHT, buff=MED_SMALL_BUFF) - self.play( - FadeInFrom(value, LEFT), - FadeIn(eq2), - equation.next_to, eq2, LEFT, - ) - self.wait() - - # Show alternate values of k - n = 50 - for k in it.chain(range(47, 42, -1), range(43, 51), [49, 48]): - new_prob_label = get_prob_review_label(k, n - k) - new_prob_label.replace(prob_label) - prob_label.become(new_prob_label) - new_formula = get_binomial_formula(n, k, self.s) - new_formula.replace(formula) - formula.set_submobjects(new_formula) - - value.set_value(dist.pmf(k)) - histogram.bars.set_fill(LIGHT_GREY) - histogram.bars[k].set_fill(GREEN) - arrow.next_to(histogram.bars[k], UP, SMALL_BUFF) - - new_row = get_checks_and_crosses((n - k) * [False] + k * [True]) - new_row.replace(row) - row.become(new_row) - self.wait(0.5) - - # Name it as the Binomial distribution - long_equation = VGroup(prob_label, eq, formula, eq2, value) - bin_name = TextMobject("Binomial", " Distribution") - bin_name.scale(1.5) - bin_name.next_to(histogram, UP, MED_LARGE_BUFF) - - underline = Underline(bin_name[0]) - underline.set_stroke(PINK, 2) - nck_rect = SurroundingRectangle(formula[:4]) - nck_rect.set_stroke(PINK, 2) - - self.play( - long_equation.next_to, self.slots, DOWN, MED_LARGE_BUFF, - long_equation.to_edge, RIGHT, - FadeInFrom(bin_name, DOWN), - ) - self.wait() - self.play(ShowCreationThenDestruction(underline)) - self.wait() - bools = [True] * 50 - bools[random.randint(0, 49)] = False - bools[random.randint(0, 49)] = False - row.become(get_checks_and_crosses(bools).replace(row)) - self.play(ShowIncreasingSubsets(row, run_time=4)) - self.wait() - - # Show likelihood and posterior labels - likelihood_label = TexMobject( - "P(", - "\\text{data}", "\\,|\\,", - "\\text{success rate}", - ")", - ) - posterior_label = TexMobject( - "P(", - "\\text{success rate}", - "\\,|\\,", - "\\text{data}", - ")", - ) - for label in (likelihood_label, posterior_label): - label.set_color_by_tex_to_color_map({ - "data": GREEN, - "success": YELLOW, - }) - - likelihood_label.next_to( - prob_label, DOWN, LARGE_BUFF, aligned_edge=LEFT - ) - - right_arrow = Vector(RIGHT) - right_arrow.next_to(likelihood_label, RIGHT) - ra_label = TextMobject("But we want") - ra_label.match_width(right_arrow) - ra_label.next_to(right_arrow, UP, SMALL_BUFF) - posterior_label.next_to(right_arrow, RIGHT) - - self.play( - FadeInFrom(likelihood_label, UP), - bin_name.set_height, 0.4, - bin_name.set_y, histogram.axes.c2p(0, .25)[1] - ) - self.wait() - self.play( - GrowArrow(right_arrow), - FadeInFrom(ra_label, 0.5 * LEFT), - ) - anims = [] - for i, j in enumerate([0, 3, 2, 1, 4]): - anims.append( - TransformFromCopy( - likelihood_label[i], - posterior_label[j], - path_arc=-45 * DEGREES, - run_time=2, - ) - ) - self.play(*anims) - self.add(posterior_label) - self.wait() - - # Prepare for new plot - histogram.add(bin_name) - always(arrow.next_to, histogram.bars[48], UP, SMALL_BUFF) - self.play( - FadeOut(likelihood_label), - FadeOut(posterior_label), - FadeOut(right_arrow), - FadeOut(ra_label), - FadeOutAndShift(row, UP), - FadeOutAndShift(self.slots, UP), - histogram.scale, 0.7, - histogram.to_edge, UP, - arrow.scale, 0.5, - arrow.set_stroke, None, 4, - long_equation.center, - run_time=1.5, - ) - self.add(arrow) - - # x_labels = histogram.axes.x_labels - # underline = Underline(x_labels) - # underline.set_stroke(GREEN, 3) - # self.play( - # LaggedStartMap( - # ApplyFunction, x_labels, - # lambda mob: ( - # lambda m: m.scale(1.5).set_color(GREEN), - # mob, - # ), - # rate_func=there_and_back, - # ), - # ShowCreationThenDestruction(underline), - # ) - # num_checks = TexMobject("\\# " + CMARK_TEX) - # num_checks.set_color(GREEN) - # num_checks.next_to( - # x_labels, RIGHT, - # MED_LARGE_BUFF, - # aligned_edge=DOWN, - # ) - # self.play(Write(num_checks)) - # self.wait() - - low_axes = get_beta_dist_axes(y_max=0.3, y_unit=0.1, label_y=False) - low_axes.y_axis.set_height( - 2, - about_point=low_axes.c2p(0, 0), - stretch=True, - ) - low_axes.to_edge(DOWN) - low_axes.x_axis.numbers.set_color(YELLOW) - y_label_copies = histogram.axes.y_labels.copy() - y_label_copies.set_height(0.6 * low_axes.get_height()) - y_label_copies.next_to(low_axes, LEFT, 0, aligned_edge=UP) - y_label_copies.shift(SMALL_BUFF * UP) - low_axes.y_axis.add(y_label_copies) - low_axes.y_axis.set_opacity(0) - - # Show alternate values of s - s_tracker = ValueTracker(self.s) - - s_tip = ArrowTip(start_angle=-90 * DEGREES) - s_tip.set_color(YELLOW) - s_tip.axis = low_axes.x_axis - s_tip.st = s_tracker - s_tip.add_updater( - lambda m: m.next_to(m.axis.n2p(m.st.get_value()), UP, buff=0) - ) - - pl_decimal = DecimalNumber(self.s) - pl_decimal.set_color(YELLOW) - pl_decimal.replace(prob_label[-2][2:]) - prob_label[-2][2:].set_opacity(0) - - s_label = VGroup(prob_label[-2][:2], pl_decimal).copy() - sl_rect = SurroundingRectangle(s_label) - sl_rect.set_stroke(YELLOW, 2) - - self.add(pl_decimal) - self.play( - ShowCreation(sl_rect), - Write(low_axes), - ) - self.play( - s_label.next_to, s_tip, UP, 0.2, ORIGIN, s_label[1], - ReplacementTransform(sl_rect, s_tip) - ) - always(s_label.next_to, s_tip, UP, 0.2, ORIGIN, s_label[1]) - - decimals = VGroup(pl_decimal, s_label[1], formula[5], formula[9]) - decimals.s_tracker = s_tracker - - histogram.s_tracker = s_tracker - histogram.n = n - histogram.rhs_value = value - - def update_decimals(decs): - for dec in decs: - dec.set_value(decs.s_tracker.get_value()) - - def update_histogram(hist): - new_dist = scipy.stats.binom(hist.n, hist.s_tracker.get_value()) - new_data = np.array([ - new_dist.pmf(x) - for x in range(0, 51) - ]) - new_bars = hist.get_bars(new_data) - new_bars.match_style(hist.bars) - hist.bars.become(new_bars) - hist.rhs_value.set_value(new_dist.pmf(48)) - - bar_copy = histogram.bars[48].copy() - value.initial_config["num_decimal_places"] = 3 - value.set_value(value.get_value()) - bar_copy.next_to(value, RIGHT, aligned_edge=DOWN) - bar_copy.add_updater( - lambda m: m.set_height( - max( - histogram.bars[48].get_height() * 0.75, - 1e-6, - ), - stretch=True, - about_edge=DOWN, - ) - ) - self.add(bar_copy) - - self.add(histogram) - self.add(decimals) - for s in [0.95, 0.5, 0.99, 0.9]: - self.play( - s_tracker.set_value, s, - UpdateFromFunc(decimals, update_decimals), - UpdateFromFunc(histogram, update_histogram), - UpdateFromFunc(value, lambda m: m), - UpdateFromFunc(s_label, lambda m: m.update), - run_time=5, - ) - self.wait() - - # Plot - def func(x): - return scipy.stats.binom(50, x).pmf(48) + 1e-5 - graph = low_axes.get_graph(func, step_size=0.05) - graph.set_stroke(BLUE, 3) - - v_line = Line(DOWN, UP) - v_line.axes = low_axes - v_line.st = s_tracker - v_line.graph = graph - v_line.add_updater( - lambda m: m.put_start_and_end_on( - m.axes.c2p(m.st.get_value(), 0), - m.axes.input_to_graph_point( - m.st.get_value(), - m.graph, - ), - ) - ) - v_line.set_stroke(GREEN, 2) - dot = Dot() - dot.line = v_line - dot.set_height(0.05) - dot.add_updater(lambda m: m.move_to(m.line.get_end())) - - self.play( - ApplyMethod( - histogram.bars[48].stretch, 2, 1, {"about_edge": DOWN}, - rate_func=there_and_back, - run_time=2, - ), - ) - self.wait() - self.play(low_axes.y_axis.set_opacity, 1) - self.play( - FadeIn(graph), - FadeOut(s_label), - FadeOut(s_tip), - ) - self.play( - TransformFromCopy(histogram.bars[48], v_line), - FadeIn(dot), - ) - - self.add(histogram) - decimals.remove(decimals[1]) - for s in [0.9, 0.96, 1, 0.8, 0.96]: - self.play( - s_tracker.set_value, s, - UpdateFromFunc(decimals, update_decimals), - UpdateFromFunc(histogram, update_histogram), - UpdateFromFunc(value, lambda m: m), - run_time=5, - ) - self.wait() - - # Write formula - clean_form = TexMobject( - "P(", "\\text{data}", "\\,|\\,", "{s}", ")", "=", - "c", "\\cdot", - "{s}", "^{\\#" + CMARK_TEX + "}", - "(1 - ", "{s}", ")", "^{\\#" + XMARK_TEX + "}", - tex_to_color_map={ - "{s}": YELLOW, - "\\#" + CMARK_TEX: GREEN, - "\\#" + XMARK_TEX: RED, - } - ) - clean_form.next_to(formula, DOWN, MED_LARGE_BUFF) - clean_form.save_state() - clean_form[:6].align_to(equation[1], RIGHT) - clean_form[6].match_x(formula[2]) - clean_form[7].set_opacity(0) - clean_form[7].next_to(clean_form[6], RIGHT, SMALL_BUFF) - clean_form[8:11].match_x(formula[4:8]) - clean_form[11:].match_x(formula[8:]) - clean_form.saved_state.move_to(clean_form, LEFT) - - fade_rects = VGroup( - BackgroundRectangle(equation[:2]), - BackgroundRectangle(formula), - BackgroundRectangle(VGroup(eq2, bar_copy)), - ) - fade_rects.set_fill(BLACK, 0.8) - fade_rects[1].set_fill(opacity=0) - - pre_c = formula[:4].copy() - pre_s = formula[4:8].copy() - pre_1ms = formula[8:].copy() - - self.play( - FadeIn(fade_rects), - FadeIn(clean_form[:6]) - ) - self.play(ShowCreationThenFadeAround(clean_form[3])) - self.wait() - for cf, pre in (clean_form[6], pre_c), (clean_form[8:11], pre_s), (clean_form[11:], pre_1ms): - self.play( - GrowFromPoint(cf, pre.get_center()), - pre.move_to, cf, - pre.scale, 0, - ) - self.remove(pre) - self.wait() - - self.wait() - self.play(Restore(clean_form)) - - # Show with 480 and 20 - top_fade_rect = BackgroundRectangle(histogram) - top_fade_rect.shift(SMALL_BUFF * DOWN) - top_fade_rect.scale(1.5, about_edge=DOWN) - top_fade_rect.set_fill(BLACK, 0) - - new_formula = get_binomial_formula(500, 480, 0.96) - new_formula.move_to(formula) - - def func500(x): - return scipy.stats.binom(500, x).pmf(480) + 1e-5 - - graph500 = low_axes.get_graph(func500, step_size=0.05) - graph500.set_stroke(TEAL, 3) - - self.play( - top_fade_rect.set_opacity, 1, - fade_rects.set_opacity, 1, - FadeIn(new_formula) - ) - - self.clear() - self.add(new_formula, clean_form, low_axes, graph, v_line, dot) - self.add(low_axes.y_axis) - - self.play(TransformFromCopy(graph, graph500)) - self.wait() - - y_axis = low_axes.y_axis - y_axis.save_state() - sf = 3 - y_axis.stretch(sf, 1, about_point=low_axes.c2p(0, 0)) - for label in y_label_copies: - label.stretch(1 / sf, 1) - - v_line.suspend_updating() - v_line.graph = graph500 - self.play( - Restore(y_axis, rate_func=reverse_smooth), - graph.stretch, sf, 1, {"about_edge": DOWN}, - graph500.stretch, sf, 1, {"about_edge": DOWN}, - ) - v_line.resume_updating() - self.add(v_line, dot) - - sub_decimals = VGroup(new_formula[5], new_formula[9]) - sub_decimals.s_tracker = s_tracker - - for s in [0.94, 0.98, 0.96]: - self.play( - s_tracker.set_value, s, - UpdateFromFunc(sub_decimals, update_decimals), - run_time=5, - ) - self.wait() - - def explain_n_choose_k(self, row, formula): - row.add_updater(lambda m: m) - - brace = Brace(formula[:4], UP, buff=SMALL_BUFF) - words = brace.get_text("``50 choose 48''") - - slots = self.slots = VGroup() - for sym in row: - line = Underline(sym) - line.scale(0.9) - slots.add(line) - for slot in slots: - slot.match_y(slots[0]) - - formula[1].counted = slots - k_rect = SurroundingRectangle(formula[2]) - k_rect.set_stroke(GREEN, 2) - - checks = VGroup() - for sym in row: - if sym.positive: - checks.add(sym) - - self.play( - GrowFromCenter(brace), - FadeInFromDown(words), - ) - self.wait() - self.play(FadeOut(words)) - formula.save_state() - self.play( - ShowIncreasingSubsets(slots), - UpdateFromFunc( - formula[1], - lambda m: m.set_value(len(m.counted)) - ), - run_time=2, - ) - formula.restore() - self.add(formula) - self.wait() - self.play( - LaggedStartMap( - ApplyMethod, checks, - lambda m: (m.shift, 0.3 * DOWN), - rate_func=there_and_back, - lag_ratio=0.05, - ), - ShowCreationThenFadeOut(k_rect), - run_time=2, - ) - self.remove(checks) - self.add(row) - self.wait() - - # Example orderings - row_target = VGroup() - for sym in row: - sym.generate_target() - row_target.add(sym.target) - - row_target.sort(submob_func=lambda m: -int(m.positive)) - row_target.arrange( - RIGHT, buff=get_norm(row[0].get_right() - row[1].get_left()) - ) - row_target.move_to(row) - self.play( - LaggedStartMap( - MoveToTarget, row, - path_arc=30 * DEGREES, - lag_ratio=0, - ), - ) - self.wait() - row.sort() - self.play(Swap(*row[-3:-1])) - self.add(row) - self.wait() - - # All orderings - nck_count = Integer(2) - nck_count.next_to(brace, UP) - nck_top = nck_count.get_top() - always(nck_count.move_to, nck_top, UP) - - combs = list(it.combinations(range(50), 48)) - bool_lists = [ - [i in comb for i in range(50)] - for comb in combs - ] - row.counter = nck_count - row.bool_lists = bool_lists - - def update_row(r): - i = r.counter.get_value() - 1 - new_row = get_checks_and_crosses(r.bool_lists[i]) - new_row.replace(r, dim_to_match=0) - r.set_submobjects(new_row) - - row.add_updater(update_row) - self.add(row) - self.play( - ChangeDecimalToValue(nck_count, choose(50, 48)), - run_time=10, - ) - row.clear_updaters() - self.wait() - self.play( - FadeOut(nck_count), - FadeOut(brace), - ) - - -class StateIndependence(Scene): - def construct(self): - row = get_random_checks_and_crosses() - row.to_edge(UP) - # self.add(row) - - arrows = VGroup() - for m1, m2 in zip(row, row[1:]): - arrow = Arrow( - m1.get_bottom() + 0.025 * DOWN, - m2.get_bottom(), - path_arc=145 * DEGREES, - max_stroke_width_to_length_ratio=10, - max_tip_length_to_length_ratio=0.5, - ) - arrow.tip.rotate(-10 * DEGREES) - arrow.shift(SMALL_BUFF * DOWN) - arrow.set_color(YELLOW) - arrows.add(arrow) - - words = TextMobject("No influence") - words.set_height(0.25) - words.next_to(arrows[0], DOWN) - - self.play( - ShowCreation(arrows[0]), - FadeIn(words) - ) - for i in range(10): - self.play( - words.next_to, arrows[i + 1], DOWN, - FadeOut(arrows[i]), - ShowCreation(arrows[i + 1]) - ) - last_arrow = arrows[i + 1] - - self.play( - FadeOut(words), - FadeOut(last_arrow), - ) - - -class IllustrateBinomialSetupWithCoins(Scene): - def construct(self): - coins = [ - get_coin("H"), - get_coin("T"), - ] - - coin_row = VGroup() - for x in range(12): - coin_row.add(random.choice(coins).copy()) - - coin_row.arrange(RIGHT) - coin_row.to_edge(UP) - - first_coin = get_random_coin(shuffle_time=2, total_time=2) - first_coin.move_to(coin_row[0]) - - brace = Brace(coin_row, UP) - brace_label = brace.get_text("$N$ times") - - prob_label = TexMobject( - "P(\\# 00 = k)", - tex_to_color_map={ - "00": WHITE, - "k": GREEN, - } - ) - heads = get_coin("H") - template = prob_label.get_part_by_tex("00") - heads.replace(template) - prob_label.replace_submobject( - prob_label.index_of_part(template), - heads, - ) - prob_label.set_height(1) - prob_label.next_to(coin_row, DOWN, LARGE_BUFF) - - self.camera.frame.set_height(1.5 * FRAME_HEIGHT) - - self.add(first_coin) - for x in range(4): - self.wait() - first_coin.suspend_updating() - self.wait() - first_coin.resume_updating() - - self.remove(first_coin) - self.play( - ShowIncreasingSubsets(coin_row, int_func=np.ceil), - GrowFromPoint(brace, brace.get_left()), - FadeInFrom(brace_label, 3 * LEFT) - ) - self.wait() - self.play(FadeIn(prob_label, lag_ratio=0.1)) - self.wait() - - -class WriteLikelihoodFunction(Scene): - def construct(self): - formula = TexMobject( - "f({s}) = (\\text{const.})", - "{s}^{\\#" + CMARK_TEX + "}", - "(1 - {s})^{\\#" + XMARK_TEX, "}", - tex_to_color_map={ - "{s}": YELLOW, - "\\#" + CMARK_TEX: GREEN, - "\\#" + XMARK_TEX: RED, - } - ) - formula.scale(2) - - rect1 = SurroundingRectangle(formula[3:6]) - rect2 = SurroundingRectangle(formula[6:]) - - self.play(FadeInFromDown(formula)) - self.wait() - self.play(ShowCreationThenFadeOut(rect1)) - self.wait() - self.play(ShowCreationThenFadeOut(rect2)) - self.wait() - - self.add(formula) - self.embed() - - -class Guess96Percent(Scene): - def construct(self): - randy = Randolph() - randy.set_height(1) - - bubble = SpeechBubble(height=2, width=3) - bubble.pin_to(randy) - words = TextMobject("96$\\%$, right?") - fix_percent(words[0][2]) - bubble.add_content(words) - - arrow = Vector(2 * RIGHT + DOWN) - arrow.next_to(randy, RIGHT) - arrow.shift(2 * UP) - - self.play( - FadeIn(randy), - ShowCreation(bubble), - Write(words), - ) - self.play(randy.change, "shruggie", randy.get_right() + RIGHT) - self.play(ShowCreation(arrow)) - for x in range(2): - self.play(Blink(randy)) - self.wait() - - self.embed() - - -class LikelihoodGraphFor10of10(ShowBinomialFormula): - CONFIG = { - "histogram_config": { - "x_label_freq": 2, - "y_axis_numbers_to_show": range(25, 125, 25), - "y_max": 1, - "y_tick_freq": 0.25, - "height": 2, - "bar_colors": [BLUE], - }, - } - - def construct(self): - # Add histogram - dist = scipy.stats.binom(10, self.s) - data = np.array([ - dist.pmf(x) - for x in range(0, 11) - ]) - histogram = self.get_histogram(data) - histogram.bars.set_fill(GREY_C) - histogram.bars[10].set_fill(GREEN) - histogram.to_edge(UP) - - x_label = TexMobject("\\#" + CMARK_TEX) - x_label.set_color(GREEN) - x_label.next_to(histogram.axes.x_axis.get_end(), RIGHT) - histogram.add(x_label) - self.add(histogram) - - arrow = Vector(DOWN) - arrow.next_to(histogram.bars[10], UP, SMALL_BUFF) - self.add(arrow) - - # Add formula - prob_label = get_prob_review_label(10, 0) - eq = TexMobject("=") - formula = get_binomial_formula(10, 10, self.s) - eq2 = TexMobject("=") - value = DecimalNumber(dist.pmf(10), num_decimal_places=2) - - equation = VGroup(prob_label, eq, formula, eq2, value) - equation.arrange(RIGHT) - equation.next_to(histogram, DOWN, MED_LARGE_BUFF) - - # Add lower axes - low_axes = get_beta_dist_axes(y_max=1, y_unit=0.25, label_y=False) - low_axes.y_axis.set_height( - 2, - about_point=low_axes.c2p(0, 0), - stretch=True, - ) - low_axes.to_edge(DOWN) - low_axes.x_axis.numbers.set_color(YELLOW) - y_label_copies = histogram.axes.y_labels.copy() - y_label_copies.set_height(0.7 * low_axes.get_height()) - y_label_copies.next_to(low_axes, LEFT, 0, aligned_edge=UP) - y_label_copies.shift(SMALL_BUFF * UP) - low_axes.y_axis.add(y_label_copies) - - # Add lower plot - s_tracker = ValueTracker(self.s) - - def func(x): - return x**10 - graph = low_axes.get_graph(func, step_size=0.05) - graph.set_stroke(BLUE, 3) - - v_line = Line(DOWN, UP) - v_line.axes = low_axes - v_line.st = s_tracker - v_line.graph = graph - v_line.add_updater( - lambda m: m.put_start_and_end_on( - m.axes.c2p(m.st.get_value(), 0), - m.axes.input_to_graph_point( - m.st.get_value(), - m.graph, - ), - ) - ) - v_line.set_stroke(GREEN, 2) - dot = Dot() - dot.line = v_line - dot.set_height(0.05) - dot.add_updater(lambda m: m.move_to(m.line.get_end())) - - # Show simpler formula - brace = Brace(formula, DOWN, buff=SMALL_BUFF) - simpler_formula = TexMobject("s", "^{10}") - simpler_formula.set_color_by_tex("s", YELLOW) - simpler_formula.set_color_by_tex("10", GREEN) - simpler_formula.next_to(brace, DOWN) - - rects = VGroup( - BackgroundRectangle(formula[:4]), - BackgroundRectangle(formula[8:]), - ) - rects.set_opacity(0.75) - - self.wait() - self.play(FadeIn(equation)) - - self.wait() - self.play( - FadeIn(rects), - GrowFromCenter(brace), - FadeInFrom(simpler_formula, UP) - ) - self.wait() - - # Show various values of s - pl_decimal = DecimalNumber(self.s) - pl_decimal.set_color(YELLOW) - pl_decimal.replace(prob_label[-2][2:]) - prob_label[-2][2:].set_opacity(0) - - decimals = VGroup(pl_decimal, formula[5], formula[9]) - decimals.s_tracker = s_tracker - - histogram.s_tracker = s_tracker - histogram.n = 10 - histogram.rhs_value = value - - def update_decimals(decs): - for dec in decs: - dec.set_value(decs.s_tracker.get_value()) - - def update_histogram(hist): - new_dist = scipy.stats.binom(hist.n, hist.s_tracker.get_value()) - new_data = np.array([ - new_dist.pmf(x) - for x in range(0, 11) - ]) - new_bars = hist.get_bars(new_data) - new_bars.match_style(hist.bars) - hist.bars.become(new_bars) - hist.rhs_value.set_value(new_dist.pmf(10)) - - self.add(histogram) - self.add(decimals, rects) - self.play( - FadeIn(low_axes), - ) - self.play( - ShowCreation(v_line), - FadeIn(dot), - ) - self.add(graph, v_line, dot) - self.play(ShowCreation(graph)) - self.wait() - - always(arrow.next_to, histogram.bars[10], UP, SMALL_BUFF) - for s in [0.8, 1]: - self.play( - s_tracker.set_value, s, - UpdateFromFunc(decimals, update_decimals), - UpdateFromFunc(histogram, update_histogram), - UpdateFromFunc(value, lambda m: m), - run_time=5, - ) - self.wait() - - -class StateNeedForBayesRule(TeacherStudentsScene): - def construct(self): - axes = get_beta_dist_axes(y_max=1, y_unit=0.25, label_y=False) - axes.y_axis.set_height( - 2, - about_point=axes.c2p(0, 0), - stretch=True, - ) - axes.set_width(5) - graph = axes.get_graph(lambda x: x**10) - graph.set_stroke(BLUE, 3) - alt_graph = graph.copy() - alt_graph.add_line_to(axes.c2p(1, 0)) - alt_graph.add_line_to(axes.c2p(0, 0)) - alt_graph.set_stroke(width=0) - alt_graph.set_fill(BLUE_E, 1) - - plot = VGroup(axes, alt_graph, graph) - - student0, student1, student2 = self.students - plot.next_to(student2.get_corner(UL), UP, MED_LARGE_BUFF) - plot.shift(LEFT) - - v_lines = VGroup( - DashedLine(axes.c2p(0.8, 0), axes.c2p(0.8, 1)), - DashedLine(axes.c2p(1, 0), axes.c2p(1, 1)), - ) - v_lines.set_stroke(YELLOW, 2) - - self.play( - LaggedStart( - ApplyMethod(student0.change, "pondering", plot), - ApplyMethod(student1.change, "pondering", plot), - ApplyMethod(student2.change, "raise_left_hand", plot), - ), - FadeInFrom(plot, DOWN), - run_time=1.5 - ) - self.play(*map(ShowCreation, v_lines)) - self.play( - self.teacher.change, "tease", - *[ - ApplyMethod( - v_line.move_to, - axes.c2p(0.9, 0), - DOWN, - ) - for v_line in v_lines - ] - ) - self.change_student_modes( - "thinking", "thinking", "pondering", - look_at_arg=v_lines, - ) - self.wait(2) - - self.teacher_says( - "But first...", - added_anims=[ - FadeOutAndShift(plot, LEFT), - FadeOutAndShift(v_lines, LEFT), - self.get_student_changes( - "erm", "erm", "erm", - look_at_arg=self.teacher.eyes, - ) - ] - ) - self.wait(5) - - -class Part1EndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "1stViewMaths", - "Adam Dřínek", - "Aidan Shenkman", - "Alan Stein", - "Alex Mijalis", - "Alexis Olson", - "Ali Yahya", - "Andrew Busey", - "Andrew Cary", - "Andrew R. Whalley", - "Aravind C V", - "Arjun Chakroborty", - "Arthur Zey", - "Ashwin Siddarth", - "Austin Goodman", - "Avi Finkel", - "Awoo", - "Axel Ericsson", - "Ayan Doss", - "AZsorcerer", - "Barry Fam", - "Bernd Sing", - "Boris Veselinovich", - "Bradley Pirtle", - "Brandon Huang", - "Brian Staroselsky", - "Britt Selvitelle", - "Britton Finley", - "Burt Humburg", - "Calvin Lin", - "Charles Southerland", - "Charlie N", - "Chenna Kautilya", - "Chris Connett", - "Christian Kaiser", - "cinterloper", - "Clark Gaebel", - "Colwyn Fritze-Moor", - "Cooper Jones", - "Corey Ogburn", - "D. Sivakumar", - "Dan Herbatschek", - "Daniel Herrera C", - "Dave B", - "Dave Kester", - "dave nicponski", - "David B. Hill", - "David Clark", - "David Gow", - "Delton Ding", - "Dominik Wagner", - "Douglas Cantrell", - "emptymachine", - "Eric Younge", - "Eryq Ouithaqueue", - "Farzaneh Sarafraz", - "Federico Lebron", - "Frank R. Brown, Jr.", - "Giovanni Filippi", - "Hal Hildebrand", - "Hitoshi Yamauchi", - "Ivan Sorokin", - "Jacob Baxter", - "Jacob Harmon", - "Jacob Hartmann", - "Jacob Magnuson", - "Jake Vartuli - Schonberg", - "Jalex Stark", - "Jameel Syed", - "Jason Hise", - "Jayne Gabriele", - "Jean-Manuel Izaret", - "Jeff Linse", - "Jeff Straathof", - "Jimmy Yang", - "John C. Vesey", - "John Haley", - "John Le", - "John V Wertheim", - "Jonathan Heckerman", - "Jonathan Wilson", - "Joseph John Cox", - "Joseph Kelly", - "Josh Kinnear", - "Joshua Claeys", - "Juan Benet", - "Kai-Siang Ang", - "Kanan Gill", - "Karl Niu", - "Kartik Cating-Subramanian", - "Kaustuv DeBiswas", - "Killian McGuinness", - "Kros Dai", - "L0j1k", - "LAI Oscar", - "Lambda GPU Workstations", - "Lee Redden", - "Linh Tran", - "Luc Ritchie", - "Ludwig Schubert", - "Lukas Biewald", - "Magister Mugit", - "Magnus Dahlström", - "Manoj Rewatkar - RITEK SOLUTIONS", - "Mark B Bahu", - "Mark Heising", - "Mark Mann", - "Martin Price", - "Mathias Jansson", - "Matt Godbolt", - "Matt Langford", - "Matt Roveto", - "Matt Russell", - "Matteo Delabre", - "Matthew Bouchard", - "Matthew Cocke", - "Mia Parent", - "Michael Hardel", - "Michael W White", - "Mirik Gogri", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nicholas Cahill", - "Nikita Lesnikov", - "Oleg Leonov", - "Oliver Steele", - "Omar Zrien", - "Owen Campbell-Moore", - "Patrick Lucas", - "Pavel Dubov", - "Peter Ehrnstrom", - "Peter Mcinerney", - "Pierre Lancien", - "Quantopian", - "Randy C. Will", - "rehmi post", - "Rex Godby", - "Ripta Pasay", - "Rish Kundalia", - "Roman Sergeychik", - "Roobie", - "Ryan Atallah", - "Samuel Judge", - "SansWord Huang", - "Scott Gray", - "Scott Walter, Ph.D.", - "soekul", - "Solara570", - "Steve Huynh", - "Steve Sperandeo", - "Steven Braun", - "Steven Siddals", - "Stevie Metke", - "supershabam", - "Suteerth Vishnu", - "Suthen Thomas", - "Tal Einav", - "Taras Bobrovytsky", - "Tauba Auerbach", - "Ted Suzman", - "Thomas J Sargent", - "Thomas Tarler", - "Tianyu Ge", - "Tihan Seale", - "Tyler VanValkenburg", - "Vassili Philippov", - "Veritasium", - "Vignesh Ganapathi Subramanian", - "Vinicius Reis", - "Xuanji Li", - "Yana Chernobilsky", - "Yavor Ivanov", - "YinYangBalance.Asia", - "Yu Jun", - "Yurii Monastyrshyn", - ], - } diff --git a/from_3b1b/active/bayes/beta2.py b/from_3b1b/active/bayes/beta2.py deleted file mode 100644 index d7cee960..00000000 --- a/from_3b1b/active/bayes/beta2.py +++ /dev/null @@ -1,2338 +0,0 @@ -from manimlib.imports import * -from from_3b1b.active.bayes.beta_helpers import * -from from_3b1b.active.bayes.beta1 import * -from from_3b1b.old.hyperdarts import Dartboard - -import scipy.stats - -OUTPUT_DIRECTORY = "bayes/beta2" - - -class WeightedCoin(Scene): - def construct(self): - # Coin grid - bools = 50 * [True] + 50 * [False] - random.shuffle(bools) - grid = get_coin_grid(bools) - - sorted_grid = VGroup(*grid) - sorted_grid.submobjects.sort(key=lambda m: m.symbol) - - # Prob label - p_label = get_prob_coin_label() - p_label.set_height(0.7) - p_label.to_edge(UP) - - rhs = p_label[-1] - rhs_box = SurroundingRectangle(rhs, color=RED) - rhs_label = TextMobject("Not necessarily") - rhs_label.next_to(rhs_box, DOWN, LARGE_BUFF) - rhs_label.to_edge(RIGHT) - rhs_label.match_color(rhs_box) - - rhs_arrow = Arrow( - rhs_label.get_top(), - rhs_box.get_right(), - buff=SMALL_BUFF, - path_arc=60 * DEGREES, - color=rhs_box.get_color() - ) - - # Introduce coin - self.play(FadeIn( - grid, - run_time=2, - rate_func=linear, - lag_ratio=3 / len(grid), - )) - self.wait() - self.play( - grid.set_height, 5, - grid.to_edge, DOWN, - FadeInFromDown(p_label) - ) - - for coin in grid: - coin.generate_target() - sorted_coins = list(grid) - sorted_coins.sort(key=lambda m: m.symbol) - for c1, c2 in zip(sorted_coins, grid): - c1.target.move_to(c2) - - self.play( - FadeIn(rhs_label, lag_ratio=0.1), - ShowCreation(rhs_arrow), - ShowCreation(rhs_box), - LaggedStartMap( - MoveToTarget, grid, - path_arc=30 * DEGREES, - lag_ratio=0.01, - ), - ) - - # Alternate weightings - old_grid = VGroup(*sorted_coins) - rhs_junk_on_screen = True - for value in [0.2, 0.9, 0.0, 0.31]: - n = int(100 * value) - new_grid = get_coin_grid([True] * n + [False] * (100 - n)) - new_grid.replace(grid) - - anims = [] - if rhs_junk_on_screen: - anims += [ - FadeOut(rhs_box), - FadeOut(rhs_label), - FadeOut(rhs_arrow), - ] - rhs_junk_on_screen = False - - self.wait() - self.play( - FadeOutAndShift( - old_grid, - 0.1 * DOWN, - lag_ratio=0.01, - run_time=1.5 - ), - FadeIn(new_grid, lag_ratio=0.01, run_time=1.5), - ChangeDecimalToValue(rhs, value), - *anims, - ) - old_grid = new_grid - - long_rhs = DecimalNumber( - 0.31415926, - num_decimal_places=8, - show_ellipsis=True, - ) - long_rhs.match_height(rhs) - long_rhs.move_to(rhs, DL) - - self.play(ShowIncreasingSubsets(long_rhs, rate_func=linear)) - self.wait() - - # You just don't know - box = get_q_box(rhs) - - self.remove(rhs) - self.play( - FadeOut(old_grid, lag_ratio=0.1), - FadeOutAndShift(long_rhs, 0.1 * RIGHT, lag_ratio=0.1), - Write(box), - ) - p_label.add(box) - self.wait() - - # 7/10 heads - bools = [True] * 7 + [False] * 3 - random.shuffle(bools) - coins = VGroup(*[ - get_coin("H" if heads else "T") - for heads in bools - ]) - coins.arrange(RIGHT) - coins.set_height(0.7) - coins.next_to(p_label, DOWN, buff=LARGE_BUFF) - - heads_arrows = VGroup(*[ - Vector( - 0.5 * UP, - max_stroke_width_to_length_ratio=15, - max_tip_length_to_length_ratio=0.4, - ).next_to(coin, DOWN) - for coin in coins - if coin.symbol == "H" - ]) - numbers = VGroup(*[ - Integer(i + 1).next_to(arrow, DOWN, SMALL_BUFF) - for i, arrow in enumerate(heads_arrows) - ]) - - for coin in coins: - coin.save_state() - coin.stretch(0, 0) - coin.set_opacity(0) - - self.play(LaggedStartMap(Restore, coins), run_time=1) - self.play( - ShowIncreasingSubsets(heads_arrows), - ShowIncreasingSubsets(numbers), - rate_func=linear, - ) - self.wait() - - # Plot - axes = scaled_pdf_axes() - axes.to_edge(DOWN, buff=MED_SMALL_BUFF) - axes.y_axis.numbers.set_opacity(0) - axes.y_axis_label.set_opacity(0) - - x_axis_label = p_label[:4].copy() - x_axis_label.set_height(0.4) - x_axis_label.next_to(axes.c2p(1, 0), UR, buff=SMALL_BUFF) - axes.x_axis.add(x_axis_label) - - n_heads = 7 - n_tails = 3 - graph = get_beta_graph(axes, n_heads, n_tails) - dist = scipy.stats.beta(n_heads + 1, n_tails + 1) - true_graph = axes.get_graph(dist.pdf) - - v_line = Line( - axes.c2p(0.7, 0), - axes.input_to_graph_point(0.7, true_graph), - ) - v_line.set_stroke(YELLOW, 4) - - region = get_region_under_curve(axes, true_graph, 0.6, 0.8) - region.set_fill(GREY, 0.85) - region.set_stroke(YELLOW, 1) - - eq_label = VGroup( - p_label[:4].copy(), - TexMobject("= 0.7"), - ) - for mob in eq_label: - mob.set_height(0.4) - eq_label.arrange(RIGHT, buff=SMALL_BUFF) - pp_label = VGroup( - TexMobject("P("), - eq_label, - TexMobject(")"), - ) - for mob in pp_label[::2]: - mob.set_height(0.7) - mob.set_color(YELLOW) - pp_label.arrange(RIGHT, buff=SMALL_BUFF) - pp_label.move_to(axes.c2p(0.3, 3)) - - self.play( - FadeOut(heads_arrows), - FadeOut(numbers), - Write(axes), - DrawBorderThenFill(graph), - ) - self.play( - FadeIn(pp_label[::2]), - ShowCreation(v_line), - ) - self.wait() - self.play(TransformFromCopy(p_label[:4], eq_label[0])) - self.play( - GrowFromPoint(eq_label[1], v_line.get_center()) - ) - self.wait() - - # Look confused - randy = Randolph() - randy.set_height(1.5) - randy.next_to(axes.c2p(0, 0), UR, MED_LARGE_BUFF) - - self.play(FadeIn(randy)) - self.play(randy.change, "confused", pp_label.get_top()) - self.play(Blink(randy)) - self.wait() - self.play(FadeOut(randy)) - - # Remind what the title is - title = TextMobject( - "Probabilities", "of", "Probabilities" - ) - title.arrange(DOWN, aligned_edge=LEFT) - title.next_to(axes.c2p(0, 0), UR, buff=MED_LARGE_BUFF) - title.align_to(pp_label, LEFT) - - self.play(ShowIncreasingSubsets(title, rate_func=linear)) - self.wait() - self.play(FadeOut(title)) - - # Continuous values - v_line.tracker = ValueTracker(0.7) - v_line.axes = axes - v_line.graph = true_graph - v_line.add_updater( - lambda m: m.put_start_and_end_on( - m.axes.c2p(m.tracker.get_value(), 0), - m.axes.input_to_graph_point(m.tracker.get_value(), m.graph), - ) - ) - - for value in [0.4, 0.9, 0.7]: - self.play( - v_line.tracker.set_value, value, - run_time=3, - ) - - # Label h - brace = Brace(rhs_box, DOWN, buff=SMALL_BUFF) - h_label = TexMobject("h", buff=SMALL_BUFF) - h_label.set_color(YELLOW) - h_label.next_to(brace, DOWN) - - self.play( - LaggedStartMap(FadeOutAndShift, coins, lambda m: (m, DOWN)), - GrowFromCenter(brace), - Write(h_label), - ) - self.wait() - - # End - self.embed() - - -class Eq70(Scene): - def construct(self): - label = TexMobject("=", "70", "\\%", "?") - fix_percent(label.get_part_by_tex("\\%")[0]) - self.play(FadeIn(label)) - self.wait() - - -class ShowInfiniteContinuum(Scene): - def construct(self): - # Axes - axes = scaled_pdf_axes() - axes.to_edge(DOWN, buff=MED_SMALL_BUFF) - axes.y_axis.numbers.set_opacity(0) - axes.y_axis_label.set_opacity(0) - self.add(axes) - - # Label - p_label = get_prob_coin_label() - p_label.set_height(0.7) - p_label.to_edge(UP) - box = get_q_box(p_label[-1]) - p_label.add(box) - - brace = Brace(box, DOWN, buff=SMALL_BUFF) - h_label = TexMobject("h") - h_label.next_to(brace, DOWN) - h_label.set_color(YELLOW) - eq = TexMobject("=") - eq.next_to(h_label, RIGHT) - value = DecimalNumber(0, num_decimal_places=4) - value.match_height(h_label) - value.next_to(eq, RIGHT) - value.set_color(YELLOW) - - self.add(p_label) - self.add(brace) - self.add(h_label) - - # Moving h - h_part = h_label.copy() - x_line = Line(axes.c2p(0, 0), axes.c2p(1, 0)) - x_line.set_stroke(YELLOW, 3) - - self.play( - h_part.next_to, x_line.get_start(), UR, SMALL_BUFF, - Write(eq), - FadeInFromPoint(value, h_part.get_center()), - ) - - # Scan continuum - h_part.tracked = x_line - value.tracked = x_line - value.x_axis = axes.x_axis - self.play( - ShowCreation(x_line), - UpdateFromFunc( - h_part, - lambda m: m.next_to(m.tracked.get_end(), UR, SMALL_BUFF) - ), - UpdateFromFunc( - value, - lambda m: m.set_value( - m.x_axis.p2n(m.tracked.get_end()) - ) - ), - run_time=3, - ) - self.wait() - self.play( - FadeOut(eq), - FadeOut(value), - ) - - # Arrows - arrows = VGroup() - arrow_template = Vector(DOWN) - arrow_template.lock_triangulation() - - def get_arrow(s, denom, arrow_template=arrow_template, axes=axes): - arrow = arrow_template.copy() - arrow.set_height(4 / denom) - arrow.move_to(axes.c2p(s, 0), DOWN) - arrow.set_color(interpolate_color( - GREY_A, GREY_C, random.random() - )) - return arrow - - for k in range(2, 50): - for n in range(1, k): - if np.gcd(n, k) != 1: - continue - s = n / k - arrows.add(get_arrow(s, k)) - for k in range(50, 1000): - arrows.add(get_arrow(1 / k, k)) - arrows.add(get_arrow(1 - 1 / k, k)) - - kw = { - "lag_ratio": 0.05, - "run_time": 5, - "rate_func": lambda t: t**5, - } - arrows.save_state() - for arrow in arrows: - arrow.stretch(0, 0) - arrow.set_stroke(width=0) - arrow.set_opacity(0) - self.play(Restore(arrows, **kw)) - self.play(LaggedStartMap( - ApplyMethod, arrows, - lambda m: (m.scale, 0, {"about_edge": DOWN}), - lag_ratio=10 / len(arrows), - rate_func=smooth, - run_time=3, - )) - self.remove(arrows) - self.wait() - - -class TitleCard(Scene): - def construct(self): - text = TextMobject("A beginner's guide to\\\\probability density") - text.scale(2) - text.to_edge(UP, buff=1.5) - - subtext = TextMobject("Probabilities of probabilities, ", "part 2") - subtext.set_width(FRAME_WIDTH - 3) - subtext[0].set_color(BLUE) - subtext.next_to(text, DOWN, LARGE_BUFF) - - self.add(text) - self.play(FadeIn(subtext, lag_ratio=0.1, run_time=2)) - self.wait(2) - - -class NamePdfs(Scene): - def construct(self): - label = TextMobject("Probability density\\\\function") - self.play(Write(label)) - self.wait() - - -class LabelH(Scene): - def construct(self): - p_label = get_prob_coin_label() - p_label.scale(1.5) - brace = Brace(p_label, DOWN) - h = TexMobject("h") - h.scale(2) - h.next_to(brace, DOWN) - - self.add(p_label) - self.play(ShowCreationThenFadeAround(p_label)) - self.play( - GrowFromCenter(brace), - FadeInFrom(h, UP), - ) - self.wait() - - -class DrawUnderline(Scene): - def construct(self): - line = Line(2 * LEFT, 2 * RIGHT) - line.set_stroke(PINK, 5) - self.play(ShowCreation(line)) - self.wait() - line.reverse_points() - self.play(Uncreate(line)) - - -class TryAssigningProbabilitiesToSpecificValues(Scene): - def construct(self): - # To get "P(s = .7000001) = ???" type labels - def get_p_label(value): - result = TexMobject( - # "P(", "{s}", "=", value, "\\%", ")", - "P(", "{h}", "=", value, ")", - ) - # fix_percent(result.get_part_by_tex("\\%")[0]) - result.set_color_by_tex("{h}", YELLOW) - return result - - labels = VGroup( - get_p_label("0.70000000"), - get_p_label("0.70000001"), - get_p_label("0.70314159"), - get_p_label("0.70271828"), - get_p_label("0.70466920"), - get_p_label("0.70161803"), - ) - labels.arrange(DOWN, buff=0.35, aligned_edge=LEFT) - labels.set_height(4.5) - labels.to_edge(DOWN, buff=LARGE_BUFF) - - q_marks = VGroup() - gt_zero = VGroup() - eq_zero = VGroup() - for label in labels: - qm = TexMobject("=", "\\,???") - qm.next_to(label, RIGHT) - qm[1].set_color(TEAL) - q_marks.add(qm) - - gt = TexMobject("> 0") - gt.next_to(label, RIGHT) - gt_zero.add(gt) - - eqz = TexMobject("= 0") - eqz.next_to(label, RIGHT) - eq_zero.add(eqz) - - v_dots = TexMobject("\\vdots") - v_dots.next_to(q_marks[-1][0], DOWN, MED_LARGE_BUFF) - - # Animations - self.play(FadeInFromDown(labels[0])) - self.play(FadeInFrom(q_marks[0], LEFT)) - self.wait() - self.play(*[ - TransformFromCopy(m1, m2) - for m1, m2 in [ - (q_marks[0], q_marks[1]), - (labels[0][:3], labels[1][:3]), - (labels[0][-1], labels[1][-1]), - ] - ]) - self.play(ShowIncreasingSubsets( - labels[1][3], - run_time=3, - int_func=np.ceil, - rate_func=linear, - )) - self.add(labels[1]) - self.wait() - self.play( - LaggedStartMap( - FadeInFrom, labels[2:], - lambda m: (m, UP), - ), - LaggedStartMap( - FadeInFrom, q_marks[2:], - lambda m: (m, UP), - ), - Write(v_dots, rate_func=squish_rate_func(smooth, 0.5, 1)) - ) - self.add(labels, q_marks) - self.wait() - - q_marks.unlock_triangulation() - self.play( - ReplacementTransform(q_marks, gt_zero, lag_ratio=0.05), - run_time=2, - ) - self.wait() - - # Show sum - group = VGroup(labels, gt_zero, v_dots) - sum_label = TexMobject( - "\\sum_{0 \\le {h} \\le 1}", "P(", "{h}", ")", "=", - tex_to_color_map={"{h}": YELLOW}, - ) - # sum_label.set_color_by_tex("{s}", YELLOW) - sum_label[0].set_color(WHITE) - sum_label.scale(1.75) - sum_label.next_to(ORIGIN, RIGHT, buff=1) - sum_label.shift(LEFT) - - morty = Mortimer() - morty.set_height(2) - morty.to_corner(DR) - - self.play(group.to_corner, DL) - self.play( - Write(sum_label), - VFadeIn(morty), - morty.change, "confused", sum_label, - ) - - infty = TexMobject("\\infty") - zero = TexMobject("0") - for mob in [infty, zero]: - mob.scale(2) - mob.next_to(sum_label[-1], RIGHT) - zero.set_color(RED) - zero.shift(SMALL_BUFF * RIGHT) - - self.play( - Write(infty), - morty.change, "horrified", infty, - ) - self.play(Blink(morty)) - self.wait() - - # If equal to zero - eq_zero.move_to(gt_zero) - eq_zero.set_color(RED) - gt_zero.unlock_triangulation() - self.play( - ReplacementTransform( - gt_zero, eq_zero, - lag_ratio=0.05, - run_time=2, - path_arc=30 * DEGREES, - ), - morty.change, "pondering", eq_zero, - ) - self.wait() - self.play( - FadeInFrom(zero, DOWN), - FadeOutAndShift(infty, UP), - morty.change, "sad", zero - ) - self.play(Blink(morty)) - self.wait() - - -class WanderingArrow(Scene): - def construct(self): - arrow = Vector(0.8 * DOWN) - arrow.move_to(4 * LEFT, DOWN) - for u in [1, -1, 1, -1, 1]: - self.play( - arrow.shift, u * 8 * RIGHT, - run_time=3 - ) - - -class ProbabilityToContinuousValuesSupplement(Scene): - def construct(self): - nl = UnitInterval() - nl.set_width(10) - nl.add_numbers( - *np.arange(0, 1.1, 0.1), - buff=0.3, - ) - nl.to_edge(LEFT) - self.add(nl) - - def f(x): - return -100 * (x - 0.6) * (x - 0.8) - - values = np.linspace(0.65, 0.75, 100) - lines = VGroup() - for x, color in zip(values, it.cycle([BLUE_E, BLUE_C])): - line = Line(ORIGIN, UP) - line.set_height(f(x)) - line.set_stroke(color, 1) - line.move_to(nl.n2p(x), DOWN) - lines.add(line) - - self.play(ShowCreation(lines, lag_ratio=0.9, run_time=5)) - - lines_row = lines.copy() - lines_row.generate_target() - for lt in lines_row.target: - lt.rotate(90 * DEGREES) - lines_row.target.arrange(RIGHT, buff=0) - lines_row.target.set_stroke(width=4) - lines_row.target.next_to(nl, UP, LARGE_BUFF) - lines_row.target.align_to(nl.n2p(0), LEFT) - - self.play( - MoveToTarget( - lines_row, - lag_ratio=0.1, - rate_func=rush_into, - run_time=4, - ) - ) - self.wait() - self.play( - lines.set_height, 0.01, {"about_edge": DOWN, "stretch": True}, - ApplyMethod( - lines_row.set_width, 0.01, {"about_edge": LEFT}, - rate_func=rush_into, - ), - run_time=6, - ) - self.wait() - - -class CarFactoryNumbers(Scene): - def construct(self): - # Test words - denom_words = TextMobject( - "in a test of 100 cars", - tex_to_color_map={"100": BLUE}, - ) - denom_words.to_corner(UR) - - numer_words = TextMobject( - "2 defects found", - tex_to_color_map={"2": RED} - ) - numer_words.move_to(denom_words, LEFT) - - self.play(Write(denom_words, run_time=1)) - self.wait() - self.play( - denom_words.next_to, numer_words, DOWN, {"aligned_edge": LEFT}, - FadeIn(numer_words), - ) - self.wait() - - # Question words - question = VGroup( - TextMobject("What can you say"), - TexMobject( - "\\text{about } P(\\text{defect})?", - tex_to_color_map={"\\text{defect}": RED} - ) - ) - - question.arrange(DOWN, aligned_edge=LEFT) - question.next_to(denom_words, DOWN, buff=1.5, aligned_edge=LEFT) - - self.play(FadeIn(question)) - self.wait() - - -class TeacherHoldingValue(TeacherStudentsScene): - def construct(self): - self.play(self.teacher.change, "raise_right_hand", self.screen) - self.change_all_student_modes( - "pondering", - look_at_arg=self.screen, - ) - self.wait(8) - - -class ShowLimitToPdf(Scene): - def construct(self): - # Init - axes = self.get_axes() - alpha = 4 - beta = 2 - dist = scipy.stats.beta(alpha, beta) - bars = self.get_bars(axes, dist, 0.05) - - axis_prob_label = TextMobject("Probability") - axis_prob_label.next_to(axes.y_axis, UP) - axis_prob_label.to_edge(LEFT) - - self.add(axes) - self.add(axis_prob_label) - - # From individual to ranges - kw = {"tex_to_color_map": {"h": YELLOW}} - eq_label = TexMobject("P(h = 0.8)", **kw) - ineq_label = TexMobject("P(0.8 < h < 0.85)", **kw) - - arrows = VGroup(Vector(DOWN), Vector(DOWN)) - for arrow, x in zip(arrows, [0.8, 0.85]): - arrow.move_to(axes.c2p(x, 0), DOWN) - brace = Brace( - Line(arrows[0].get_start(), arrows[1].get_start()), - UP, buff=SMALL_BUFF - ) - eq_label.next_to(arrows[0], UP) - ineq_label.next_to(brace, UP) - - self.play( - FadeInFrom(eq_label, 0.2 * DOWN), - GrowArrow(arrows[0]), - ) - self.wait() - vect = eq_label.get_center() - ineq_label.get_center() - self.play( - FadeOutAndShift(eq_label, -vect), - FadeInFrom(ineq_label, vect), - TransformFromCopy(*arrows), - GrowFromPoint(brace, brace.get_left()), - ) - self.wait() - - # Bars - arrow = arrows[0] - arrow.generate_target() - arrow.target.next_to(bars[16], UP, SMALL_BUFF) - highlighted_bar_color = RED_E - bars[16].set_color(highlighted_bar_color) - - for bar in bars: - bar.save_state() - bar.stretch(0, 1, about_edge=DOWN) - - kw = { - "run_time": 2, - "rate_func": squish_rate_func(smooth, 0.3, 0.9), - } - self.play( - MoveToTarget(arrow, **kw), - ApplyMethod(ineq_label.next_to, arrows[0].target, UP, **kw), - FadeOut(arrows[1]), - FadeOut(brace), - LaggedStartMap(Restore, bars, run_time=2, lag_ratio=0.025), - ) - self.wait() - - # Focus on area, not height - lines = VGroup() - new_bars = VGroup() - for bar in bars: - line = Line( - bar.get_corner(DL), - bar.get_corner(DR), - ) - line.set_stroke(YELLOW, 0) - line.generate_target() - line.target.set_stroke(YELLOW, 3) - line.target.move_to(bar.get_top()) - lines.add(line) - - new_bar = bar.copy() - new_bar.match_style(line) - new_bar.set_fill(YELLOW, 0.5) - new_bar.generate_target() - new_bar.stretch(0, 1, about_edge=UP) - new_bars.add(new_bar) - - prob_label = TextMobject( - "Height", - "$\\rightarrow$", - "Probability", - ) - prob_label.space_out_submobjects(1.1) - prob_label.next_to(bars[10], UL, LARGE_BUFF) - height_word = prob_label[0] - height_cross = Cross(height_word) - area_word = TextMobject("Area") - area_word.move_to(height_word, UR) - area_word.set_color(YELLOW) - - self.play( - LaggedStartMap( - MoveToTarget, lines, - lag_ratio=0.01, - ), - FadeInFromDown(prob_label), - ) - self.add(height_word) - self.play( - ShowCreation(height_cross), - FadeOutAndShift(axis_prob_label, LEFT) - ) - self.wait() - self.play( - FadeOutAndShift(height_word, UP), - FadeOutAndShift(height_cross, UP), - FadeInFromDown(area_word), - ) - self.play( - FadeOut(lines), - LaggedStartMap( - MoveToTarget, new_bars, - lag_ratio=0.01, - ) - ) - self.play( - FadeOut(new_bars), - area_word.set_color, BLUE, - ) - - prob_label = VGroup(area_word, *prob_label[1:]) - self.add(prob_label) - - # Ask about where values come from - randy = Randolph(height=1) - randy.next_to(prob_label, UP, aligned_edge=LEFT) - - bubble = SpeechBubble( - height=2, - width=4, - ) - bubble.move_to(randy.get_corner(UR), DL) - bubble.write("Where do these\\\\probabilities come from?") - - self.play( - FadeIn(randy), - ShowCreation(bubble), - ) - self.play( - randy.change, "confused", - FadeIn(bubble.content, lag_ratio=0.1) - ) - self.play(Blink(randy)) - - bars.generate_target() - bars.save_state() - bars.target.arrange(RIGHT, buff=SMALL_BUFF, aligned_edge=DOWN) - bars.target.next_to(bars.get_bottom(), UP) - - self.play(MoveToTarget(bars)) - self.play(LaggedStartMap(Indicate, bars, scale_factor=1.05), run_time=1) - self.play(Restore(bars)) - self.play(Blink(randy)) - self.play( - FadeOut(randy), - FadeOut(bubble), - FadeOut(bubble.content), - ) - - # Refine - last_ineq_label = ineq_label - last_bars = bars - all_ineq_labels = VGroup(ineq_label) - for step_size in [0.025, 0.01, 0.005, 0.001]: - new_bars = self.get_bars(axes, dist, step_size) - new_ineq_label = TexMobject( - "P(0.8 < h < {:.3})".format(0.8 + step_size), - tex_to_color_map={"h": YELLOW}, - ) - - if step_size <= 0.005: - new_bars.set_stroke(width=0) - - arrow.generate_target() - bar = new_bars[int(0.8 * len(new_bars))] - bar.set_color(highlighted_bar_color) - arrow.target.next_to(bar, UP, SMALL_BUFF) - new_ineq_label.next_to(arrow.target, UP) - - vect = new_ineq_label.get_center() - last_ineq_label.get_center() - - self.wait() - self.play( - ReplacementTransform( - last_bars, new_bars, - lag_ratio=step_size, - ), - MoveToTarget(arrow), - FadeOutAndShift(last_ineq_label, vect), - FadeInFrom(new_ineq_label, -vect), - run_time=2, - ) - last_ineq_label = new_ineq_label - last_bars = new_bars - all_ineq_labels.add(new_ineq_label) - - # Show continuous graph - graph = get_beta_graph(axes, alpha - 1, beta - 1) - graph_curve = axes.get_graph(dist.pdf) - graph_curve.set_stroke([YELLOW, GREEN]) - - limit_words = TextMobject("In the limit...") - limit_words.next_to( - axes.input_to_graph_point(0.75, graph_curve), - UP, MED_LARGE_BUFF, - ) - - self.play( - FadeIn(graph), - FadeOut(last_ineq_label), - FadeOut(arrow), - FadeOut(last_bars), - ) - self.play( - ShowCreation(graph_curve), - Write(limit_words, run_time=1) - ) - self.play(FadeOut(graph_curve)) - self.wait() - - # Show individual probabilities goes to zero - all_ineq_labels.arrange(DOWN, aligned_edge=LEFT) - all_ineq_labels.move_to(prob_label, LEFT) - all_ineq_labels.to_edge(UP) - - prob_label.generate_target() - prob_label.target.next_to( - all_ineq_labels, DOWN, - buff=MED_LARGE_BUFF, - aligned_edge=LEFT - ) - - rhss = VGroup() - step_sizes = [0.05, 0.025, 0.01, 0.005, 0.001] - for label, step in zip(all_ineq_labels, step_sizes): - eq = TexMobject("=") - decimal = DecimalNumber( - dist.cdf(0.8 + step) - dist.cdf(0.8), - num_decimal_places=3, - ) - eq.next_to(label, RIGHT) - decimal.next_to(eq, RIGHT) - decimal.set_stroke(BLACK, 3, background=True) - rhss.add(VGroup(eq, decimal)) - - for rhs in rhss: - rhs.align_to(rhss[1], LEFT) - - VGroup(all_ineq_labels, rhss).set_height(3, about_edge=UL) - - arrow = Arrow(rhss.get_top(), rhss.get_bottom(), buff=0) - arrow.next_to(rhss, RIGHT) - arrow.set_color(YELLOW) - to_zero_words = TextMobject("Individual probabilites\\\\", "go to zero") - to_zero_words[1].align_to(to_zero_words[0], LEFT) - to_zero_words.next_to(arrow, RIGHT, aligned_edge=UP) - - self.play( - LaggedStartMap( - FadeInFrom, all_ineq_labels, - lambda m: (m, UP), - ), - LaggedStartMap( - FadeInFrom, rhss, - lambda m: (m, UP), - ), - MoveToTarget(prob_label) - ) - self.play( - GrowArrow(arrow), - FadeIn(to_zero_words), - ) - self.play( - LaggedStartMap( - Indicate, rhss, - scale_factor=1.05, - ) - ) - self.wait(2) - - # What if it was heights - bars.restore() - height_word.move_to(area_word, RIGHT) - height_word.set_color(PINK) - step = 0.05 - new_y_numbers = VGroup(*[ - DecimalNumber(x) for x in np.arange(step, 5 * step, step) - ]) - for n1, n2 in zip(axes.y_axis.numbers, new_y_numbers): - n2.match_height(n1) - n2.add_background_rectangle( - opacity=1, - buff=SMALL_BUFF, - ) - n2.move_to(n1, RIGHT) - - self.play( - FadeOut(limit_words), - FadeOut(graph), - FadeIn(bars), - FadeOutAndShift(area_word, UP), - FadeInFrom(height_word, DOWN), - FadeInFrom(new_y_numbers, 0.5 * RIGHT), - ) - - # Height refine - rect = SurroundingRectangle(rhss[0][1]) - rect.set_stroke(RED, 3) - self.play(FadeIn(rect)) - - last_bars = bars - for step_size, rhs in zip(step_sizes[1:], rhss[1:]): - new_bars = self.get_bars(axes, dist, step_size) - bar = new_bars[int(0.8 * len(new_bars))] - bar.set_color(highlighted_bar_color) - new_bars.stretch( - step_size / 0.05, 1, - about_edge=DOWN, - ) - if step_size <= 0.05: - new_bars.set_stroke(width=0) - self.remove(last_bars) - self.play( - TransformFromCopy(last_bars, new_bars, lag_ratio=step_size), - rect.move_to, rhs[1], - ) - last_bars = new_bars - self.play( - FadeOut(last_bars), - FadeOutAndShiftDown(rect), - ) - self.wait() - - # Back to area - self.play( - FadeIn(graph), - FadeInFrom(area_word, 0.5 * DOWN), - FadeOutAndShift(height_word, 0.5 * UP), - FadeOut(new_y_numbers, lag_ratio=0.2), - ) - self.play( - arrow.scale, 0, {"about_edge": DOWN}, - FadeOutAndShift(to_zero_words, DOWN), - LaggedStartMap(FadeOutAndShiftDown, all_ineq_labels), - LaggedStartMap(FadeOutAndShiftDown, rhss), - ) - self.wait() - - # Ask about y_axis units - arrow = Arrow( - axes.y_axis.get_top() + 3 * RIGHT, - axes.y_axis.get_top(), - path_arc=90 * DEGREES, - ) - question = TextMobject("What are the\\\\units here?") - question.next_to(arrow.get_start(), DOWN) - - self.play( - FadeIn(question, lag_ratio=0.1), - ShowCreation(arrow), - ) - self.wait() - - # Bring back bars - bars = self.get_bars(axes, dist, 0.05) - self.play( - FadeOut(graph), - FadeIn(bars), - ) - bars.generate_target() - bars.save_state() - bars.target.set_opacity(0.2) - bar_index = int(0.8 * len(bars)) - bars.target[bar_index].set_opacity(0.8) - bar = bars[bar_index] - - prob_word = TextMobject("Probability") - prob_word.rotate(90 * DEGREES) - prob_word.set_height(0.8 * bar.get_height()) - prob_word.move_to(bar) - - self.play( - MoveToTarget(bars), - Write(prob_word, run_time=1), - ) - self.wait() - - # Show dimensions of bar - top_brace = Brace(bar, UP) - side_brace = Brace(bar, LEFT) - top_label = top_brace.get_tex("\\Delta x") - side_label = side_brace.get_tex( - "{\\text{Prob.} \\over \\Delta x}" - ) - - self.play( - GrowFromCenter(top_brace), - FadeIn(top_label), - ) - self.play(GrowFromCenter(side_brace)) - self.wait() - self.play(Write(side_label)) - self.wait() - - y_label = TextMobject("Probability density") - y_label.next_to(axes.y_axis, UP, aligned_edge=LEFT) - - self.play( - Uncreate(arrow), - FadeOutAndShiftDown(question), - Write(y_label), - ) - self.wait(2) - self.play( - Restore(bars), - FadeOut(top_brace), - FadeOut(side_brace), - FadeOut(top_label), - FadeOut(side_label), - FadeOut(prob_word), - ) - - # Point out total area is 1 - total_label = TextMobject("Total area = 1") - total_label.set_height(0.5) - total_label.next_to(bars, UP, LARGE_BUFF) - - self.play(FadeInFrom(total_label, DOWN)) - bars.save_state() - self.play( - bars.arrange, RIGHT, {"aligned_edge": DOWN, "buff": SMALL_BUFF}, - bars.move_to, bars.get_bottom() + 0.5 * UP, DOWN, - ) - self.play(LaggedStartMap(Indicate, bars, scale_factor=1.05)) - self.play(Restore(bars)) - - # Refine again - for step_size in step_sizes[1:]: - new_bars = self.get_bars(axes, dist, step_size) - if step_size <= 0.05: - new_bars.set_stroke(width=0) - self.play( - ReplacementTransform( - bars, new_bars, lag_ratio=step_size - ), - run_time=3, - ) - self.wait() - bars = new_bars - self.add(graph, total_label) - self.play( - FadeIn(graph), - FadeOut(bars), - total_label.move_to, axes.c2p(0.7, 0.8) - ) - self.wait() - - # Name pdf - func_name = TextMobject("Probability ", "Density ", "Function") - initials = TextMobject("P", "D", "F") - for mob in func_name, initials: - mob.set_color(YELLOW) - mob.next_to(axes.input_to_graph_point(0.75, graph_curve), UP) - - self.play( - ShowCreation(graph_curve), - Write(func_name, run_time=1), - ) - self.wait() - func_name_copy = func_name.copy() - self.play( - func_name.next_to, initials, UP, - *[ - ReplacementTransform(np[0], ip[0]) - for np, ip in zip(func_name_copy, initials) - ], - *[ - FadeOut(np[1:]) - for np in func_name_copy - ] - ) - self.add(initials) - self.wait() - self.play( - FadeOut(func_name), - FadeOut(total_label), - FadeOut(graph_curve), - initials.next_to, axes.input_to_graph_point(0.95, graph_curve), UR, - ) - - # Look at bounded area - min_x = 0.6 - max_x = 0.8 - region = get_region_under_curve(axes, graph_curve, min_x, max_x) - area_label = DecimalNumber( - dist.cdf(max_x) - dist.cdf(min_x), - num_decimal_places=3, - ) - area_label.move_to(region) - - v_lines = VGroup() - for x in [min_x, max_x]: - v_lines.add( - DashedLine( - axes.c2p(x, 0), - axes.c2p(x, 2.5), - ) - ) - v_lines.set_stroke(YELLOW, 2) - - p_label = VGroup( - TexMobject("P("), - DecimalNumber(min_x), - TexMobject("\\le"), - TexMobject("h", color=YELLOW), - TexMobject("\\le"), - DecimalNumber(max_x), - TexMobject(")") - ) - p_label.arrange(RIGHT, buff=0.25) - VGroup(p_label[0], p_label[-1]).space_out_submobjects(0.92) - p_label.next_to(v_lines, UP) - - rhs = VGroup( - TexMobject("="), - area_label.copy() - ) - rhs.arrange(RIGHT) - rhs.next_to(p_label, RIGHT) - - self.play( - FadeInFrom(p_label, 2 * DOWN), - *map(ShowCreation, v_lines), - ) - self.wait() - region.func = get_region_under_curve - self.play( - UpdateFromAlphaFunc( - region, - lambda m, a: m.become( - m.func( - m.axes, m.graph, - m.min_x, - interpolate(m.min_x, m.max_x, a) - ) - ) - ), - CountInFrom(area_label), - UpdateFromAlphaFunc( - area_label, - lambda m, a: m.set_opacity(a), - ), - ) - self.wait() - self.play( - TransformFromCopy(area_label, rhs[1]), - Write(rhs[0]), - ) - self.wait() - - # Change range - new_x = np.mean([min_x, max_x]) - area_label.original_width = area_label.get_width() - region.new_x = new_x - # Squish to area 1 - self.play( - ChangeDecimalToValue(p_label[1], new_x), - ChangeDecimalToValue(p_label[5], new_x), - ChangeDecimalToValue(area_label, 0), - UpdateFromAlphaFunc( - area_label, - lambda m, a: m.set_width( - interpolate(m.original_width, 1e-6, a) - ) - ), - ChangeDecimalToValue(rhs[1], 0), - v_lines[0].move_to, axes.c2p(new_x, 0), DOWN, - v_lines[1].move_to, axes.c2p(new_x, 0), DOWN, - UpdateFromAlphaFunc( - region, - lambda m, a: m.become(m.func( - m.axes, m.graph, - interpolate(m.min_x, m.new_x, a), - interpolate(m.max_x, m.new_x, a), - )) - ), - run_time=2, - ) - self.wait() - - # Stretch to area 1 - self.play( - ChangeDecimalToValue(p_label[1], 0), - ChangeDecimalToValue(p_label[5], 1), - ChangeDecimalToValue(area_label, 1), - UpdateFromAlphaFunc( - area_label, - lambda m, a: m.set_width( - interpolate(1e-6, m.original_width, clip(5 * a, 0, 1)) - ) - ), - ChangeDecimalToValue(rhs[1], 1), - v_lines[0].move_to, axes.c2p(0, 0), DOWN, - v_lines[1].move_to, axes.c2p(1, 0), DOWN, - UpdateFromAlphaFunc( - region, - lambda m, a: m.become(m.func( - m.axes, m.graph, - interpolate(m.new_x, 0, a), - interpolate(m.new_x, 1, a), - )) - ), - run_time=5, - ) - self.wait() - - def get_axes(self): - axes = Axes( - x_min=0, - x_max=1, - x_axis_config={ - "tick_frequency": 0.05, - "unit_size": 12, - "include_tip": False, - }, - y_min=0, - y_max=4, - y_axis_config={ - "tick_frequency": 1, - "unit_size": 1.25, - "include_tip": False, - } - ) - axes.center() - - h_label = TexMobject("h") - h_label.set_color(YELLOW) - h_label.next_to(axes.x_axis.n2p(1), UR, buff=0.2) - axes.x_axis.add(h_label) - axes.x_axis.label = h_label - - axes.x_axis.add_numbers( - *np.arange(0.2, 1.2, 0.2), - number_config={"num_decimal_places": 1} - ) - axes.y_axis.add_numbers(*range(1, 5)) - return axes - - def get_bars(self, axes, dist, step_size): - bars = VGroup() - for x in np.arange(0, 1, step_size): - bar = Rectangle() - bar.set_stroke(BLUE, 2) - bar.set_fill(BLUE, 0.5) - h_line = Line( - axes.c2p(x, 0), - axes.c2p(x + step_size, 0), - ) - v_line = Line( - axes.c2p(0, 0), - axes.c2p(0, dist.pdf(x)), - ) - bar.match_width(h_line, stretch=True) - bar.match_height(v_line, stretch=True) - bar.move_to(h_line, DOWN) - bars.add(bar) - return bars - - -class FiniteVsContinuum(Scene): - def construct(self): - # Title - f_title = TextMobject("Discrete context") - f_title.set_height(0.5) - f_title.to_edge(UP) - f_underline = Underline(f_title) - f_underline.scale(1.3) - f_title.add(f_underline) - self.add(f_title) - - # Equations - dice = get_die_faces()[::2] - cards = [PlayingCard(letter + "H") for letter in "A35"] - - eqs = VGroup( - self.get_union_equation(dice), - self.get_union_equation(cards), - ) - for eq in eqs: - eq.set_width(FRAME_WIDTH - 1) - eqs.arrange(DOWN, buff=LARGE_BUFF) - eqs.next_to(f_underline, DOWN, LARGE_BUFF) - - anims = [] - for eq in eqs: - movers = eq.mob_copies1.copy() - for m1, m2 in zip(movers, eq.mob_copies2): - m1.generate_target() - m1.target.replace(m2) - eq.mob_copies2.set_opacity(0) - eq.add(movers) - - self.play(FadeIn(eq[0])) - - anims.append(FadeIn(eq[1:])) - anims.append(LaggedStartMap( - MoveToTarget, movers, - path_arc=30 * DEGREES, - lag_ratio=0.1, - )) - self.wait() - for anim in anims: - self.play(anim) - - # Continuum label - c_title = TextMobject("Continuous context") - c_title.match_height(f_title) - c_underline = Underline(c_title) - c_underline.scale(1.25) - - self.play( - Write(c_title, run_time=1), - ShowCreation(c_underline), - eqs[0].shift, 0.5 * UP, - eqs[1].shift, UP, - ) - - # Range sum - c_eq = TexMobject( - "P\\big(", "x \\in [0.65, 0.75]", "\\big)", - "=", - "\\sum_{x \\in [0.65, 0.75]}", - "P(", "x", ")", - ) - c_eq.set_color_by_tex("P", YELLOW) - c_eq.set_color_by_tex(")", YELLOW) - c_eq.next_to(c_underline, DOWN, LARGE_BUFF) - c_eq.to_edge(LEFT) - - equals = c_eq.get_part_by_tex("=") - equals.shift(SMALL_BUFF * RIGHT) - e_cross = Line(DL, UR) - e_cross.replace(equals, dim_to_match=0) - e_cross.set_stroke(RED, 5) - - self.play(FadeIn(c_eq)) - self.wait(2) - self.play(ShowCreation(e_cross)) - self.wait() - - def get_union_equation(self, mobs): - mob_copies1 = VGroup() - mob_copies2 = VGroup() - p_color = YELLOW - - # Create mob_set - brackets = TexMobject("\\big\\{\\big\\}")[0] - mob_set = VGroup(brackets[0]) - commas = VGroup() - for mob in mobs: - mc = mob.copy() - mc.match_height(mob_set[0]) - mob_copies1.add(mc) - comma = TexMobject(",") - commas.add(comma) - mob_set.add(mc) - mob_set.add(comma) - - mob_set.remove(commas[-1]) - commas.remove(commas[-1]) - mob_set.add(brackets[1]) - mob_set.arrange(RIGHT, buff=0.15) - commas.set_y(mob_set[1].get_bottom()[1]) - - mob_set.scale(0.8) - - # Create individual probabilities - probs = VGroup() - for mob in mobs: - prob = TexMobject("P(", "x = ", "00", ")") - index = prob.index_of_part_by_tex("00") - mc = mob.copy() - mc.replace(prob[index]) - mc.scale(0.8, about_edge=LEFT) - mc.match_y(prob[-1]) - mob_copies2.add(mc) - prob.replace_submobject(index, mc) - prob[0].set_color(p_color) - prob[1].match_y(mc) - prob[-1].set_color(p_color) - probs.add(prob) - - # Result - lhs = VGroup( - TexMobject("P\\big(", color=p_color), - TexMobject("x \\in"), - mob_set, - TexMobject("\\big)", color=p_color), - ) - lhs.arrange(RIGHT, buff=SMALL_BUFF) - group = VGroup(lhs, TexMobject("=")) - for prob in probs: - group.add(prob) - group.add(TexMobject("+")) - group.remove(group[-1]) - - group.arrange(RIGHT, buff=0.2) - group.mob_copies1 = mob_copies1 - group.mob_copies2 = mob_copies2 - - return group - - -class ComplainAboutRuleChange(TeacherStudentsScene): - def construct(self): - self.student_says( - "Wait, the rules\\\\changed?", - target_mode="sassy", - added_anims=[self.teacher.change, "tease"] - ) - self.change_student_modes("erm", "confused") - self.wait(4) - self.teacher_says("You may enjoy\\\\``Measure theory''") - self.change_all_student_modes( - "pondering", - look_at_arg=self.teacher.bubble - ) - self.wait(8) - - -class HalfFiniteHalfContinuous(Scene): - def construct(self): - # Basic symbols - box = Rectangle(width=3, height=1.2) - box.set_stroke(WHITE, 2) - box.set_fill(GREY_E, 1) - box.move_to(2.5 * LEFT, RIGHT) - - arrows = VGroup() - arrow_labels = VGroup() - for vect in [UP, DOWN]: - arrow = Arrow( - box.get_corner(vect + RIGHT), - box.get_corner(vect + RIGHT) + 3 * RIGHT + 1.5 * vect, - buff=MED_SMALL_BUFF, - ) - label = TexMobject("50\\%") - fix_percent(label[0][-1]) - label.set_color(YELLOW) - label.next_to( - arrow.get_center(), - vect + LEFT, - buff=SMALL_BUFF, - ) - - arrow_labels.add(label) - arrows.add(arrow) - - zero = Integer(0) - zero.set_height(0.5) - zero.next_to(arrows[0].get_end(), RIGHT) - - # Half Gaussian - axes = Axes( - x_min=0, - x_max=6.5, - y_min=0, - y_max=0.25, - y_axis_config={ - "tick_frequency": 1 / 16, - "unit_size": 10, - "include_tip": False, - } - ) - axes.next_to(arrows[1].get_end(), RIGHT) - - dist = scipy.stats.norm(0, 2) - graph = axes.get_graph(dist.pdf) - graph_fill = graph.copy() - close_off_graph(axes, graph_fill) - graph.set_stroke(BLUE, 3) - graph_fill.set_fill(BLUE_E, 1) - graph_fill.set_stroke(BLUE_E, 0) - - half_gauss = Group( - graph, graph_fill, axes, - ) - - # Random Decimal - number = DecimalNumber(num_decimal_places=4) - number.set_height(0.6) - number.move_to(box) - - number.time = 0 - number.last_change = 0 - number.change_freq = 0.2 - - def update_number(number, dt, dist=dist): - number.time += dt - - if (number.time - number.last_change) < number.change_freq: - return - - number.last_change = number.time - rand_val = random.random() - if rand_val < 0.5: - number.set_value(0) - else: - number.set_value(dist.ppf(rand_val)) - - number.add_updater(update_number) - - v_line = SurroundingRectangle(zero) - v_line.save_state() - v_line.set_stroke(YELLOW, 3) - - def update_v_line(v_line, number=number, axes=axes, graph=graph): - x = number.get_value() - if x < 0.5: - v_line.restore() - else: - v_line.set_width(1e-6) - p1 = axes.c2p(x, 0) - p2 = axes.input_to_graph_point(x, graph) - v_line.set_height(get_norm(p2 - p1), stretch=True) - v_line.move_to(p1, DOWN) - - v_line.add_updater(update_v_line) - - # Add everything - self.add(box) - self.add(number) - self.wait(4) - self.play( - GrowArrow(arrows[0]), - FadeIn(arrow_labels[0]), - GrowFromPoint(zero, box.get_corner(UR)) - ) - self.wait(2) - self.play( - GrowArrow(arrows[1]), - FadeIn(arrow_labels[1]), - FadeIn(half_gauss), - ) - self.add(v_line) - - self.wait(30) - - -class SumToIntegral(Scene): - def construct(self): - # Titles - titles = VGroup( - TextMobject("Discrete context"), - TextMobject("Continuous context"), - ) - titles.set_height(0.5) - for title, vect in zip(titles, [LEFT, RIGHT]): - title.move_to(vect * FRAME_WIDTH / 4) - title.to_edge(UP, buff=MED_SMALL_BUFF) - - v_line = Line(UP, DOWN).set_height(FRAME_HEIGHT) - h_line = Line(LEFT, RIGHT).set_width(FRAME_WIDTH) - h_line.next_to(titles, DOWN) - h_line.set_x(0) - v_line.center() - - self.play( - ShowCreation(VGroup(h_line, v_line)), - LaggedStartMap( - FadeInFrom, titles, - lambda m: (m, -0.2 * m.get_center()[0] * RIGHT), - run_time=1, - lag_ratio=0.1, - ), - ) - self.wait() - - # Sum and int - kw = {"tex_to_color_map": {"S": BLUE}} - s_sym = TexMobject("\\sum", "_{x \\in S} P(x)", **kw) - i_sym = TexMobject("\\int_{S} p(x)", "\\text{d}x", **kw) - syms = VGroup(s_sym, i_sym) - syms.scale(2) - for sym, title in zip(syms, titles): - sym.shift(-sym[-1].get_center()) - sym.match_x(title) - - arrow = Arrow( - s_sym[0].get_corner(UP), - i_sym[0].get_corner(UP), - path_arc=-90 * DEGREES, - ) - arrow.set_color(YELLOW) - - self.play(Write(s_sym, run_time=1)) - anims = [ShowCreation(arrow)] - for i, j in [(0, 0), (2, 1), (3, 2)]: - source = s_sym[i].deepcopy() - target = i_sym[j] - target.save_state() - source.generate_target() - target.replace(source, stretch=True) - source.target.replace(target, stretch=True) - target.set_opacity(0) - source.target.set_opacity(0) - anims += [ - Restore(target, path_arc=-60 * DEGREES), - MoveToTarget(source, path_arc=-60 * DEGREES), - ] - self.play(LaggedStart(*anims)) - self.play(FadeInFromDown(i_sym[3])) - self.add(i_sym) - self.wait() - self.play( - FadeOutAndShift(arrow, UP), - syms.next_to, h_line, DOWN, {"buff": MED_LARGE_BUFF}, - syms.match_x, syms, - ) - - # Add curve area in editing - # Add bar chart - axes = Axes( - x_min=0, - x_max=10, - y_min=0, - y_max=7, - y_axis_config={ - "unit_size": 0.75, - } - ) - axes.set_width(0.5 * FRAME_WIDTH - 1) - axes.next_to(s_sym, DOWN) - axes.y_axis.add_numbers(2, 4, 6) - - bars = VGroup() - for x, y in [(1, 1), (4, 3), (7, 2)]: - bar = Rectangle() - bar.set_stroke(WHITE, 1) - bar.set_fill(BLUE_D, 1) - line = Line(axes.c2p(x, 0), axes.c2p(x + 2, y)) - bar.replace(line, stretch=True) - bars.add(bar) - - addition_formula = TexMobject(*"1+3+2") - addition_formula.space_out_submobjects(2.1) - addition_formula.next_to(bars, UP) - - for bar in bars: - bar.save_state() - bar.stretch(0, 1, about_edge=DOWN) - - self.play( - Write(axes), - LaggedStartMap(Restore, bars), - LaggedStartMap(FadeInFromDown, addition_formula), - ) - self.wait() - - # Confusion - morty = Mortimer() - morty.to_corner(DR) - morty.look_at(i_sym) - self.play( - *map(FadeOut, [axes, bars, addition_formula]), - FadeIn(morty) - ) - self.play(morty.change, "maybe") - self.play(Blink(morty)) - self.play(morty.change, "confused", i_sym.get_right()) - self.play(Blink(morty)) - self.wait() - - # Focus on integral - self.play( - Uncreate(VGroup(v_line, h_line)), - FadeOutAndShift(titles, UP), - FadeOutAndShift(morty, RIGHT), - FadeOutAndShift(s_sym, LEFT), - i_sym.center, - i_sym.to_edge, LEFT - ) - - arrows = VGroup() - for vect in [UP, DOWN]: - corner = i_sym[-1].get_corner(RIGHT + vect) - arrows.add(Arrow( - corner, - corner + 2 * RIGHT + 2 * vect, - path_arc=-np.sign(vect[1]) * 60 * DEGREES, - )) - - self.play(*map(ShowCreation, arrows)) - - # Types of integration - dist = scipy.stats.beta(7 + 1, 3 + 1) - axes_pair = VGroup() - graph_pair = VGroup() - for arrow in arrows: - axes = get_beta_dist_axes(y_max=5, y_unit=1) - axes.set_width(4) - axes.next_to(arrow.get_end(), RIGHT) - graph = axes.get_graph(dist.pdf) - graph.set_stroke(BLUE, 2) - graph.set_fill(BLUE_E, 0) - graph.make_smooth() - axes_pair.add(axes) - graph_pair.add(graph) - - r_axes, l_axes = axes_pair - r_graph, l_graph = graph_pair - r_name = TextMobject("Riemann\\\\Integration") - r_name.next_to(r_axes, RIGHT) - l_name = TextMobject("Lebesgue\\\\Integration$^*$") - l_name.next_to(l_axes, RIGHT) - footnote = TextMobject("*a bit more complicated than\\\\these bars make it look") - footnote.match_width(l_name) - footnote.next_to(l_name, DOWN) - - self.play(LaggedStart( - FadeIn(r_axes), - FadeIn(r_graph), - FadeIn(r_name), - FadeIn(l_axes), - FadeIn(l_graph), - FadeIn(l_name), - run_time=1, - )) - - # Approximation bars - def get_riemann_rects(dx, axes=r_axes, func=dist.pdf): - bars = VGroup() - for x in np.arange(0, 1, dx): - bar = Rectangle() - line = Line( - axes.c2p(x, 0), - axes.c2p(x + dx, func(x)), - ) - bar.replace(line, stretch=True) - bar.set_stroke(BLUE_E, width=10 * dx, opacity=1) - bar.set_fill(BLUE, 0.5) - bars.add(bar) - return bars - - def get_lebesgue_bars(dy, axes=l_axes, func=dist.pdf, mx=0.7, y_max=dist.pdf(0.7)): - bars = VGroup() - for y in np.arange(dy, y_max + dy, dy): - x0 = binary_search(func, y, 0, mx) or mx - x1 = binary_search(func, y, mx, 1) or mx - line = Line(axes.c2p(x0, y - dy), axes.c2p(x1, y)) - bar = Rectangle() - bar.set_stroke(RED_E, 0) - bar.set_fill(RED_E, 0.5) - bar.replace(line, stretch=True) - bars.add(bar) - return bars - - r_bar_groups = [] - l_bar_groups = [] - Ns = [10, 20, 40, 80, 160] - Ms = [2, 4, 8, 16, 32] - for N, M in zip(Ns, Ms): - r_bar_groups.append(get_riemann_rects(dx=1 / N)) - l_bar_groups.append(get_lebesgue_bars(dy=1 / M)) - self.play( - FadeIn(r_bar_groups[0], lag_ratio=0.1), - FadeIn(l_bar_groups[0], lag_ratio=0.1), - FadeIn(footnote), - ) - self.wait() - for rbg0, rbg1, lbg0, lbg1 in zip(r_bar_groups, r_bar_groups[1:], l_bar_groups, l_bar_groups[1:]): - self.play( - ReplacementTransform( - rbg0, rbg1, - lag_ratio=1 / len(rbg0), - run_time=2, - ), - ReplacementTransform( - lbg0, lbg1, - lag_ratio=1 / len(lbg0), - run_time=2, - ), - ) - self.wait() - self.play( - FadeOut(r_bar_groups[-1]), - FadeOut(l_bar_groups[-1]), - r_graph.set_fill, BLUE_E, 1, - l_graph.set_fill, RED_E, 1, - ) - - -class MeasureTheoryLeadsTo(Scene): - def construct(self): - words = TextMobject("Measure Theory") - words.set_color(RED) - arrow = Vector(DOWN) - arrow.next_to(words, DOWN, buff=SMALL_BUFF) - arrow.set_stroke(width=7) - arrow.rotate(45 * DEGREES, about_point=arrow.get_start()) - self.play( - FadeInFrom(words, DOWN), - GrowArrow(arrow), - UpdateFromAlphaFunc(arrow, lambda m, a: m.set_opacity(a)), - ) - self.wait() - - -class WhenIWasFirstLearning(TeacherStudentsScene): - def construct(self): - self.teacher.change_mode("raise_right_hand") - self.play( - self.get_student_changes("pondering", "thinking", "tease"), - self.teacher.change, "thinking", - ) - - younger = BabyPiCreature(color=GREY_BROWN) - younger.set_height(2) - younger.move_to(self.students, DL) - - self.look_at(self.screen) - self.wait() - self.play( - ReplacementTransform(self.teacher, younger), - LaggedStartMap( - FadeOutAndShift, self.students, - lambda m: (m, DOWN), - ) - ) - - # Bubble - bubble = ThoughtBubble() - bubble[-1].set_fill(GREEN_SCREEN, 1) - bubble.move_to(younger.get_corner(UR), DL) - - self.play( - Write(bubble), - younger.change, "maybe", bubble.get_bubble_center(), - ) - self.play(Blink(younger)) - for mode in ["confused", "angry", "pondering", "maybe"]: - self.play(younger.change, mode) - for x in range(2): - self.wait() - if random.random() < 0.5: - self.play(Blink(younger)) - - -class PossibleYetProbabilityZero(Scene): - def construct(self): - poss = TextMobject("Possible") - prob = TextMobject("Probability = 0") - total = TextMobject("P(dart hits somewhere) = 1") - # total[1].next_to(total[0][0], RIGHT) - words = VGroup(poss, prob, total) - words.scale(1.5) - words.arrange(DOWN, aligned_edge=LEFT, buff=MED_LARGE_BUFF) - - self.play(Write(poss, run_time=0.5)) - self.wait() - self.play(FadeInFrom(prob, UP)) - self.wait() - self.play(FadeInFrom(total, UP)) - self.wait() - - -class TiePossibleToDensity(Scene): - def construct(self): - poss = TextMobject("Possibility") - prob = TextMobject("Probability", " $>$ 0") - dens = TextMobject("Probability \\emph{density}", " $>$ 0") - dens[0].set_color(BLUE) - implies = TexMobject("\\Rightarrow") - implies2 = implies.copy() - - poss.next_to(implies, LEFT) - prob.next_to(implies, RIGHT) - dens.next_to(implies, RIGHT) - cross = Cross(implies) - - self.camera.frame.scale(0.7, about_point=dens.get_center()) - - self.add(poss) - self.play( - FadeInFrom(prob, LEFT), - Write(implies, run_time=1) - ) - self.wait() - self.play(ShowCreation(cross)) - self.wait() - - self.play( - VGroup(implies, cross, prob).shift, UP, - FadeIn(implies2), - FadeIn(dens), - ) - self.wait() - - self.embed() - - -class DrawBigRect(Scene): - def construct(self): - rect = Rectangle(width=7, height=2.5) - rect.set_stroke(RED, 5) - rect.to_edge(RIGHT) - - words = TextMobject("Not how to\\\\think about it") - words.set_color(RED) - words.align_to(rect, LEFT) - words.to_edge(UP) - - arrow = Arrow( - words.get_bottom(), - rect.get_top(), - buff=0.25, - color=RED, - ) - - self.play(ShowCreation(rect)) - self.play( - FadeInFromDown(words), - GrowArrow(arrow), - ) - self.wait() - - -class Thumbnail(Scene): - def construct(self): - dartboard = Dartboard() - axes = NumberPlane( - x_min=-1.25, - x_max=1.25, - y_min=-1.25, - y_max=1.25, - axis_config={ - "unit_size": 0.5 * dartboard.get_width(), - "tick_frequency": 0.25, - }, - x_line_frequency=1.0, - y_line_frequency=1.0, - ) - group = VGroup(dartboard, axes) - group.to_edge(LEFT, buff=0) - - # Arrow - arrow = Vector(DR, max_stroke_width_to_length_ratio=np.inf) - arrow.move_to(axes.c2p(PI / 10, np.exp(1) / 10), DR) - arrow.scale(1.5, about_edge=DR) - arrow.set_stroke(WHITE, 10) - - black_arrow = arrow.copy() - black_arrow.set_color(BLACK) - black_arrow.set_stroke(width=20) - - arrow.points[0] += 0.025 * DR - - # Coords - coords = TexMobject("(x, y) = (0.31415\\dots, 0.27182\\dots)") - coords.set_width(5.5) - coords.set_stroke(BLACK, 10, background=True) - coords.next_to(axes.get_bottom(), UP, buff=0) - - # Words - words = VGroup( - TextMobject("Probability = 0"), - TextMobject("$\\dots$but still possible"), - ) - for word in words: - word.set_width(6) - words.arrange(DOWN, buff=MED_LARGE_BUFF) - words.next_to(axes, RIGHT) - words.to_edge(UP, buff=LARGE_BUFF) - - # Pi - morty = Mortimer() - morty.to_corner(DR) - morty.change("confused", words) - - self.add(group) - self.add(black_arrow) - self.add(arrow) - self.add(coords) - self.add(words) - self.add(morty) - - self.embed() - - -class Part2EndScreen(PatreonEndScreen): - CONFIG = { - "scroll_time": 30, - "specific_patrons": [ - "1stViewMaths", - "Adam Dřínek", - "Aidan Shenkman", - "Alan Stein", - "Albin Egasse", - "Alex Mijalis", - "Alexander Mai", - "Alexis Olson", - "Ali Yahya", - "Andrew Busey", - "Andrew Cary", - "Andrew R. Whalley", - "Anthony Losego", - "Aravind C V", - "Arjun Chakroborty", - "Arthur Zey", - "Ashwin Siddarth", - "Augustine Lim", - "Austin Goodman", - "Avi Finkel", - "Awoo", - "Axel Ericsson", - "Ayan Doss", - "AZsorcerer", - "Barry Fam", - "Ben Delo", - "Bernd Sing", - "Bill Gatliff", - "Bob Sanderson", - "Boris Veselinovich", - "Bradley Pirtle", - "Brandon Huang", - "Brian Staroselsky", - "Britt Selvitelle", - "Britton Finley", - "Burt Humburg", - "Calvin Lin", - "Charles Southerland", - "Charlie N", - "Chenna Kautilya", - "Chris Connett", - "Chris Druta", - "Christian Kaiser", - "cinterloper", - "Clark Gaebel", - "Colwyn Fritze-Moor", - "Cooper Jones", - "Corey Ogburn", - "D. Sivakumar", - "Dan Herbatschek", - "Daniel Brown", - "Daniel Herrera C", - "Darrell Thomas", - "Dave B", - "Dave Kester", - "dave nicponski", - "David B. Hill", - "David Clark", - "David Gow", - "Delton Ding", - "Dominik Wagner", - "Eddie Landesberg", - "Eduardo Rodriguez", - "emptymachine", - "Eric Younge", - "Eryq Ouithaqueue", - "Federico Lebron", - "Fernando Via Canel", - "Frank R. Brown, Jr.", - "Gavin", - "Giovanni Filippi", - "Goodwine", - "Hal Hildebrand", - "Hitoshi Yamauchi", - "Ivan Sorokin", - "Jacob Baxter", - "Jacob Harmon", - "Jacob Hartmann", - "Jacob Magnuson", - "Jalex Stark", - "Jameel Syed", - "James Beall", - "Jason Hise", - "Jayne Gabriele", - "Jean-Manuel Izaret", - "Jeff Dodds", - "Jeff Linse", - "Jeff Straathof", - "Jimmy Yang", - "John C. Vesey", - "John Camp", - "John Haley", - "John Le", - "John Luttig", - "John Rizzo", - "John V Wertheim", - "Jonathan Heckerman", - "Jonathan Wilson", - "Joseph John Cox", - "Joseph Kelly", - "Josh Kinnear", - "Joshua Claeys", - "Joshua Ouellette", - "Juan Benet", - "Kai-Siang Ang", - "Kanan Gill", - "Karl Niu", - "Kartik Cating-Subramanian", - "Kaustuv DeBiswas", - "Killian McGuinness", - "Klaas Moerman", - "Kros Dai", - "L0j1k", - "Lael S Costa", - "LAI Oscar", - "Lambda GPU Workstations", - "Laura Gast", - "Lee Redden", - "Linh Tran", - "Luc Ritchie", - "Ludwig Schubert", - "Lukas Biewald", - "Lukas Zenick", - "Magister Mugit", - "Magnus Dahlström", - "Magnus Hiie", - "Manoj Rewatkar - RITEK SOLUTIONS", - "Mark B Bahu", - "Mark Heising", - "Mark Mann", - "Martin Price", - "Mathias Jansson", - "Matt Godbolt", - "Matt Langford", - "Matt Roveto", - "Matt Russell", - "Matteo Delabre", - "Matthew Bouchard", - "Matthew Cocke", - "Maxim Nitsche", - "Michael Bos", - "Michael Day", - "Michael Hardel", - "Michael W White", - "Mihran Vardanyan", - "Mirik Gogri", - "Molly Mackinlay", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nate Heckmann", - "Nicholas Cahill", - "Nikita Lesnikov", - "Oleg Leonov", - "Omar Zrien", - "Owen Campbell-Moore", - "Patrick Lucas", - "Pavel Dubov", - "Pesho Ivanov", - "Petar Veličković", - "Peter Ehrnstrom", - "Peter Francis", - "Peter Mcinerney", - "Pierre Lancien", - "Pradeep Gollakota", - "Rafael Bove Barrios", - "Randy C. Will", - "rehmi post", - "Rex Godby", - "Ripta Pasay", - "Rish Kundalia", - "Roman Sergeychik", - "Roobie", - "Ryan Atallah", - "Ryan Prayogo", - "Samuel Judge", - "SansWord Huang", - "Scott Gray", - "Scott Walter, Ph.D.", - "soekul", - "Solara570", - "Steve Huynh", - "Steve Muench", - "Steve Sperandeo", - "Steven Siddals", - "Stevie Metke", - "Sunil Nagaraj", - "supershabam", - "Susanne Fenja Mehr-Koks", - "Suteerth Vishnu", - "Suthen Thomas", - "Tal Einav", - "Taras Bobrovytsky", - "Tauba Auerbach", - "Ted Suzman", - "THIS IS THE point OF NO RE tUUurRrhghgGHhhnnn", - "Thomas J Sargent", - "Thomas Tarler", - "Tianyu Ge", - "Tihan Seale", - "Tyler Herrmann", - "Tyler McAtee", - "Tyler VanValkenburg", - "Tyler Veness", - "Vassili Philippov", - "Vasu Dubey", - "Veritasium", - "Vignesh Ganapathi Subramanian", - "Vinicius Reis", - "Vladimir Solomatin", - "Wooyong Ee", - "Xuanji Li", - "Yana Chernobilsky", - "YinYangBalance.Asia", - "Yorick Lesecque", - "Yu Jun", - "Yurii Monastyrshyn", - ], - } diff --git a/from_3b1b/active/bayes/beta3.py b/from_3b1b/active/bayes/beta3.py deleted file mode 100644 index 86ccbe13..00000000 --- a/from_3b1b/active/bayes/beta3.py +++ /dev/null @@ -1,2150 +0,0 @@ -from manimlib.imports import * -from from_3b1b.active.bayes.beta_helpers import * -from from_3b1b.active.bayes.beta1 import * -from from_3b1b.active.bayes.beta2 import ShowLimitToPdf - -import scipy.stats - -OUTPUT_DIRECTORY = "bayes/beta3" - - -class RemindOfWeightedCoin(Scene): - def construct(self): - # Largely copied from beta2 - - # Prob label - p_label = get_prob_coin_label() - p_label.set_height(0.7) - p_label.to_edge(UP) - - rhs = p_label[-1] - q_box = get_q_box(rhs) - p_label.add(q_box) - - self.add(p_label) - - # Coin grid - def get_random_coin_grid(p): - bools = np.random.random(100) < p - grid = get_coin_grid(bools) - return grid - - grid = get_random_coin_grid(0.5) - grid.next_to(p_label, DOWN, MED_LARGE_BUFF) - - self.play(LaggedStartMap( - FadeIn, grid, - lag_ratio=2 / len(grid), - run_time=3, - )) - self.wait() - - # Label as h - brace = Brace(q_box, DOWN, buff=SMALL_BUFF) - h_label = TexMobject("h") - h_label.next_to(brace, DOWN) - eq = TexMobject("=") - eq.next_to(h_label, RIGHT) - h_decimal = DecimalNumber(0.5) - h_decimal.next_to(eq, RIGHT) - - self.play( - GrowFromCenter(brace), - FadeInFrom(h_label, UP), - grid.scale, 0.8, {"about_edge": DOWN}, - ) - self.wait() - - # Alternate weightings - tail_grid = get_random_coin_grid(0) - head_grid = get_random_coin_grid(1) - grid70 = get_random_coin_grid(0.7) - alt_grids = [tail_grid, head_grid, grid70] - for ag in alt_grids: - ag.replace(grid) - - for coins in [grid, *alt_grids]: - for coin in coins: - coin.generate_target() - coin.target.rotate(90 * DEGREES, axis=UP) - coin.target.set_opacity(0) - - def get_grid_swap_anims(g1, g2): - return [ - LaggedStartMap(MoveToTarget, g1, lag_ratio=0.02, run_time=1.5, remover=True), - LaggedStartMap(MoveToTarget, g2, lag_ratio=0.02, run_time=1.5, rate_func=reverse_smooth), - ] - - self.play( - FadeIn(eq), - UpdateFromAlphaFunc(h_decimal, lambda m, a: m.set_opacity(a)), - ChangeDecimalToValue(h_decimal, 0, run_time=2), - *get_grid_swap_anims(grid, tail_grid) - ) - self.wait() - self.play( - ChangeDecimalToValue(h_decimal, 1, run_time=1.5), - *get_grid_swap_anims(tail_grid, head_grid) - ) - self.wait() - self.play( - ChangeDecimalToValue(h_decimal, 0.7, run_time=1.5), - *get_grid_swap_anims(head_grid, grid70) - ) - self.wait() - - # Graph - axes = scaled_pdf_axes() - axes.to_edge(DOWN, buff=MED_SMALL_BUFF) - axes.y_axis.numbers.set_opacity(0) - axes.y_axis_label.set_opacity(0) - - h_lines = VGroup() - for y in range(15): - h_line = Line(axes.c2p(0, y), axes.c2p(1, y)) - h_lines.add(h_line) - h_lines.set_stroke(WHITE, 0.5, opacity=0.5) - axes.add(h_lines) - - x_axis_label = p_label[:4].copy() - x_axis_label.set_height(0.4) - x_axis_label.next_to(axes.c2p(1, 0), UR, buff=SMALL_BUFF) - axes.x_axis.add(x_axis_label) - - n_heads_tracker = ValueTracker(3) - n_tails_tracker = ValueTracker(3) - - def get_graph(axes=axes, nht=n_heads_tracker, ntt=n_tails_tracker): - dist = scipy.stats.beta(nht.get_value() + 1, ntt.get_value() + 1) - graph = axes.get_graph(dist.pdf, step_size=0.05) - graph.set_stroke(BLUE, 3) - graph.set_fill(BLUE_E, 1) - return graph - - graph = always_redraw(get_graph) - - area_label = TextMobject("Area = 1") - area_label.set_height(0.5) - area_label.move_to(axes.c2p(0.5, 1)) - - # pdf label - pdf_label = TextMobject("probability ", "density ", "function") - pdf_label.next_to(axes.input_to_graph_point(0.5, graph), UP) - pdf_target_template = TextMobject("p", "d", "f") - pdf_target_template.next_to(axes.input_to_graph_point(0.7, graph), UR) - pdf_label.generate_target() - for part, letter2 in zip(pdf_label.target, pdf_target_template): - for letter1 in part: - letter1.move_to(letter2) - part[1:].set_opacity(0) - - # Add plot - self.add(axes, *self.mobjects) - self.play( - FadeOut(eq), - FadeOut(h_decimal), - LaggedStartMap(MoveToTarget, grid70, run_time=1, remover=True), - FadeIn(axes), - ) - self.play( - DrawBorderThenFill(graph), - FadeIn(area_label, rate_func=squish_rate_func(smooth, 0.5, 1), run_time=2), - Write(pdf_label, run_time=1), - ) - self.wait() - - # Region - lh_tracker = ValueTracker(0.7) - rh_tracker = ValueTracker(0.7) - - def get_region(axes=axes, graph=graph, lh_tracker=lh_tracker, rh_tracker=rh_tracker): - lh = lh_tracker.get_value() - rh = rh_tracker.get_value() - region = get_region_under_curve(axes, graph, lh, rh) - region.set_fill(GREY, 0.85) - region.set_stroke(YELLOW, 1) - return region - - region = always_redraw(get_region) - - region_area_label = DecimalNumber(num_decimal_places=3) - region_area_label.next_to(axes.c2p(0.7, 0), UP, MED_LARGE_BUFF) - - def update_ra_label(label, nht=n_heads_tracker, ntt=n_tails_tracker, lht=lh_tracker, rht=rh_tracker): - dist = scipy.stats.beta(nht.get_value() + 1, ntt.get_value() + 1) - area = dist.cdf(rht.get_value()) - dist.cdf(lht.get_value()) - label.set_value(area) - - region_area_label.add_updater(update_ra_label) - - range_label = VGroup( - TexMobject("0.6 \\le"), - p_label[:4].copy(), - TexMobject("\\le 0.8"), - ) - for mob in range_label: - mob.set_height(0.4) - range_label.arrange(RIGHT, buff=SMALL_BUFF) - pp_label = VGroup( - TexMobject("P("), - range_label, - TexMobject(")"), - ) - for mob in pp_label[::2]: - mob.set_height(0.7) - mob.set_color(YELLOW) - pp_label.arrange(RIGHT, buff=SMALL_BUFF) - pp_label.move_to(axes.c2p(0.3, 3)) - - self.play( - FadeIn(pp_label[::2]), - MoveToTarget(pdf_label), - FadeOut(area_label), - ) - self.wait() - self.play(TransformFromCopy(p_label[:4], range_label[1])) - self.wait() - self.play(TransformFromCopy(axes.x_axis.numbers[2], range_label[0])) - self.play(TransformFromCopy(axes.x_axis.numbers[3], range_label[2])) - self.wait() - - self.add(region) - self.play( - lh_tracker.set_value, 0.6, - rh_tracker.set_value, 0.8, - UpdateFromAlphaFunc( - region_area_label, - lambda m, a: m.set_opacity(a), - rate_func=squish_rate_func(smooth, 0.25, 1) - ), - run_time=3, - ) - self.wait() - - # 7/10 heads - bools = [True] * 7 + [False] * 3 - random.shuffle(bools) - coins = VGroup(*[ - get_coin("H" if heads else "T") - for heads in bools - ]) - coins.arrange(RIGHT) - coins.set_height(0.7) - coins.next_to(h_label, DOWN, buff=MED_LARGE_BUFF) - - heads = [c for c in coins if c.symbol == "H"] - numbers = VGroup(*[ - Integer(i + 1).set_height(0.2).next_to(coin, DOWN, SMALL_BUFF) - for i, coin in enumerate(heads) - ]) - - for coin in coins: - coin.save_state() - coin.rotate(90 * DEGREES, UP) - coin.set_opacity(0) - - pp_label.generate_target() - pp_label.target.set_height(0.5) - pp_label.target.next_to(axes.c2p(0, 2), RIGHT, MED_LARGE_BUFF) - - self.play( - LaggedStartMap(Restore, coins), - MoveToTarget(pp_label), - run_time=1, - ) - self.play(ShowIncreasingSubsets(numbers)) - self.wait() - - # Move plot - self.play( - n_heads_tracker.set_value, 7, - n_tails_tracker.set_value, 3, - FadeOut(pdf_label, rate_func=squish_rate_func(smooth, 0, 0.5)), - run_time=2 - ) - self.wait() - - # How does the answer change with more data - new_bools = [True] * 63 + [False] * 27 - random.shuffle(new_bools) - bools = [c.symbol == "H" for c in coins] + new_bools - grid = get_coin_grid(bools) - grid.set_height(3.5) - grid.next_to(axes.c2p(0, 3), RIGHT, MED_LARGE_BUFF) - - self.play( - FadeOut(numbers), - ReplacementTransform(coins, grid[:10]), - ) - self.play( - FadeIn(grid[10:], lag_ratio=0.1, rate_func=linear), - pp_label.next_to, grid, DOWN, - ) - self.wait() - self.add(graph, region, region_area_label, p_label, q_box, brace, h_label) - self.play( - n_heads_tracker.set_value, 70, - n_tails_tracker.set_value, 30, - ) - self.wait() - origin = axes.c2p(0, 0) - self.play( - axes.y_axis.stretch, 0.5, 1, {"about_point": origin}, - h_lines.stretch, 0.5, 1, {"about_point": origin}, - ) - self.wait() - - # Shift the shape around - pairs = [ - (70 * 3, 30 * 3), - (35, 15), - (35 + 20, 15 + 20), - (7, 3), - (70, 30), - ] - for nh, nt in pairs: - self.play( - n_heads_tracker.set_value, nh, - n_tails_tracker.set_value, nt, - run_time=2, - ) - self.wait() - - # End - self.embed() - - -class LastTimeWrapper(Scene): - def construct(self): - fs_rect = FullScreenFadeRectangle(fill_opacity=1, fill_color=GREY_E) - self.add(fs_rect) - - title = TextMobject("Last Time") - title.scale(1.5) - title.to_edge(UP) - - rect = ScreenRectangle() - rect.set_height(6) - rect.set_fill(BLACK, 1) - rect.next_to(title, DOWN) - - self.play( - DrawBorderThenFill(rect), - FadeInFromDown(title), - ) - self.wait() - - -class ComplainAboutSimplisticModel(ExternallyAnimatedScene): - pass - - -class BayesianFrequentistDivide(Scene): - def construct(self): - # Setup Bayesian vs. Frequentist divide - b_label = TextMobject("Bayesian") - f_label = TextMobject("Frequentist") - labels = VGroup(b_label, f_label) - for label, vect in zip(labels, [LEFT, RIGHT]): - label.set_height(0.7) - label.move_to(vect * FRAME_WIDTH / 4) - label.to_edge(UP, buff=0.35) - - h_line = Line(LEFT, RIGHT) - h_line.set_width(FRAME_WIDTH) - h_line.next_to(labels, DOWN) - v_line = Line(UP, DOWN) - v_line.set_height(FRAME_HEIGHT) - v_line.center() - - for label in labels: - label.save_state() - label.set_y(0) - self.play( - FadeInFrom(label, -normalize(label.get_center())), - ) - self.wait() - self.play( - ShowCreation(VGroup(v_line, h_line)), - *map(Restore, labels), - ) - self.wait() - - # Overlay ShowBayesianUpdating in editing - # Frequentist list (ignore?) - kw = { - "tex_to_color_map": { - "$p$-value": YELLOW, - "$H_0$": PINK, - "$\\alpha$": BLUE, - }, - "alignment": "", - } - freq_list = VGroup( - TextMobject("1. State a null hypothesis $H_0$", **kw), - TextMobject("2. Choose a test statistic,\\\\", "$\\qquad$ compute its value", **kw), - TextMobject("3. Calculate a $p$-value", **kw), - TextMobject("4. Choose a significance value $\\alpha$", **kw), - TextMobject("5. Reject $H_0$ if $p$-value\\\\", "$\\qquad$ is less than $\\alpha$", **kw), - ) - - freq_list.set_width(0.5 * FRAME_WIDTH - 1) - freq_list.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - freq_list.move_to(FRAME_WIDTH * RIGHT / 4) - freq_list.to_edge(DOWN, buff=LARGE_BUFF) - - # Frequentist icon - axes = get_beta_dist_axes(y_max=5, y_unit=1) - axes.set_width(0.5 * FRAME_WIDTH - 1) - axes.move_to(FRAME_WIDTH * RIGHT / 4 + DOWN) - - dist = scipy.stats.norm(0.5, 0.1) - graph = axes.get_graph(dist.pdf) - graphs = VGroup() - for x_min, x_max in [(0, 0.3), (0.3, 0.7), (0.7, 1.0)]: - graph = axes.get_graph(dist.pdf, x_min=x_min, x_max=x_max) - graph.add_line_to(axes.c2p(x_max, 0)) - graph.add_line_to(axes.c2p(x_min, 0)) - graph.add_line_to(graph.get_start()) - graphs.add(graph) - - graphs.set_stroke(width=0) - graphs.set_fill(RED, 1) - graphs[1].set_fill(GREY_D, 1) - - H_words = VGroup(*[TextMobject("Reject\\\\$H_0$") for x in range(2)]) - for H_word, graph, vect in zip(H_words, graphs[::2], [RIGHT, LEFT]): - H_word.next_to(graph, UP, MED_LARGE_BUFF) - arrow = Arrow( - H_word.get_bottom(), - graph.get_center() + 0.75 * vect, - buff=SMALL_BUFF - ) - H_word.add(arrow) - - H_words.set_color(RED) - self.add(H_words) - - self.add(axes) - self.add(graphs) - - self.embed() - - # Transition to 2x2 - # Go back to prior - # Label uniform prior - # Talk about real coin prior - # Update ad infinitum - - -class ArgumentBetweenBayesianAndFrequentist(Scene): - def construct(self): - pass - - -# From version 1 -class ShowBayesianUpdating(Scene): - CONFIG = { - "true_p": 0.72, - "random_seed": 4, - "initial_axis_scale_factor": 3.5 - } - - def construct(self): - # Axes - axes = scaled_pdf_axes(self.initial_axis_scale_factor) - self.add(axes) - - # Graph - n_heads = 0 - n_tails = 0 - graph = get_beta_graph(axes, n_heads, n_tails) - self.add(graph) - - # Get coins - true_p = self.true_p - bool_values = np.random.random(100) < true_p - bool_values[1] = True - coins = self.get_coins(bool_values) - coins.next_to(axes.y_axis, RIGHT, MED_LARGE_BUFF) - coins.to_edge(UP, LARGE_BUFF) - - # Probability label - p_label, prob, prob_box = self.get_probability_label() - self.add(p_label) - self.add(prob_box) - - # Slow animations - def head_likelihood(x): - return x - - def tail_likelihood(x): - return 1 - x - - n_previews = 10 - n_slow_previews = 5 - for x in range(n_previews): - coin = coins[x] - is_heads = bool_values[x] - - new_data_label = TextMobject("New data") - new_data_label.set_height(0.3) - arrow = Vector(0.5 * UP) - arrow.next_to(coin, DOWN, SMALL_BUFF) - new_data_label.next_to(arrow, DOWN, SMALL_BUFF) - new_data_label.shift(MED_SMALL_BUFF * RIGHT) - - if is_heads: - line = axes.get_graph(lambda x: x) - label = TexMobject("\\text{Scale by } x") - likelihood = head_likelihood - n_heads += 1 - else: - line = axes.get_graph(lambda x: 1 - x) - label = TexMobject("\\text{Scale by } (1 - x)") - likelihood = tail_likelihood - n_tails += 1 - label.next_to(graph, UP) - label.set_stroke(BLACK, 3, background=True) - line.set_stroke(YELLOW, 3) - - graph_copy = graph.copy() - graph_copy.unlock_triangulation() - scaled_graph = graph.copy() - scaled_graph.apply_function( - lambda p: axes.c2p( - axes.x_axis.p2n(p), - axes.y_axis.p2n(p) * likelihood(axes.x_axis.p2n(p)) - ) - ) - scaled_graph.set_color(GREEN) - - renorm_label = TextMobject("Renormalize") - renorm_label.move_to(label) - - new_graph = get_beta_graph(axes, n_heads, n_tails) - - renormalized_graph = scaled_graph.copy() - renormalized_graph.match_style(graph) - renormalized_graph.match_height(new_graph, stretch=True, about_edge=DOWN) - - if x < n_slow_previews: - self.play( - FadeInFromDown(coin), - FadeIn(new_data_label), - GrowArrow(arrow), - ) - self.play( - FadeOut(new_data_label), - FadeOut(arrow), - ShowCreation(line), - FadeIn(label), - ) - self.add(graph_copy, line, label) - self.play(Transform(graph_copy, scaled_graph)) - self.play( - FadeOut(line), - FadeOut(label), - FadeIn(renorm_label), - ) - self.play( - Transform(graph_copy, renormalized_graph), - FadeOut(graph), - ) - self.play(FadeOut(renorm_label)) - else: - self.add(coin) - graph_copy.become(scaled_graph) - self.add(graph_copy) - self.play( - Transform(graph_copy, renormalized_graph), - FadeOut(graph), - ) - graph = new_graph - self.remove(graph_copy) - self.add(new_graph) - - # Rescale y axis - axes.save_state() - sf = self.initial_axis_scale_factor - axes.y_axis.stretch(1 / sf, 1, about_point=axes.c2p(0, 0)) - for number in axes.y_axis.numbers: - number.stretch(sf, 1) - axes.y_axis.numbers[:4].set_opacity(0) - - self.play( - Restore(axes, rate_func=lambda t: smooth(1 - t)), - graph.stretch, 1 / sf, 1, {"about_edge": DOWN}, - run_time=2, - ) - - # Fast animations - for x in range(n_previews, len(coins)): - coin = coins[x] - is_heads = bool_values[x] - - if is_heads: - n_heads += 1 - else: - n_tails += 1 - new_graph = get_beta_graph(axes, n_heads, n_tails) - - self.add(coins[:x + 1]) - self.add(new_graph) - self.remove(graph) - self.wait(0.25) - # self.play( - # FadeIn(new_graph), - # run_time=0.25, - # ) - # self.play( - # FadeOut(graph), - # run_time=0.25, - # ) - graph = new_graph - - # Show confidence interval - dist = scipy.stats.beta(n_heads + 1, n_tails + 1) - v_lines = VGroup() - labels = VGroup() - x_bounds = dist.interval(0.95) - for x in x_bounds: - line = DashedLine( - axes.c2p(x, 0), - axes.c2p(x, 12), - ) - line.set_color(YELLOW) - v_lines.add(line) - label = DecimalNumber(x) - label.set_height(0.25) - label.next_to(line, UP) - label.match_color(line) - labels.add(label) - - true_graph = axes.get_graph(dist.pdf) - region = get_region_under_curve(axes, true_graph, *x_bounds) - region.set_fill(GREY_BROWN, 0.85) - region.set_stroke(YELLOW, 1) - - label95 = TexMobject("95\\%") - fix_percent(label95.family_members_with_points()[-1]) - label95.move_to(region, DOWN) - label95.shift(0.5 * UP) - - self.play(*map(ShowCreation, v_lines)) - self.play( - FadeIn(region), - Write(label95) - ) - self.wait() - for label in labels: - self.play(FadeInFromDown(label)) - self.wait() - - # Show true value - self.wait() - self.play(FadeOut(prob_box)) - self.play(ShowCreationThenFadeAround(prob)) - self.wait() - - # Much more data - many_bools = np.hstack([ - bool_values, - (np.random.random(1000) < true_p) - ]) - N_tracker = ValueTracker(100) - graph.N_tracker = N_tracker - graph.bools = many_bools - graph.axes = axes - graph.v_lines = v_lines - graph.labels = labels - graph.region = region - graph.label95 = label95 - - label95.width_ratio = label95.get_width() / region.get_width() - - def update_graph(graph): - N = int(graph.N_tracker.get_value()) - nh = sum(graph.bools[:N]) - nt = len(graph.bools[:N]) - nh - new_graph = get_beta_graph(graph.axes, nh, nt, step_size=0.05) - graph.become(new_graph) - - dist = scipy.stats.beta(nh + 1, nt + 1) - x_bounds = dist.interval(0.95) - for x, line, label in zip(x_bounds, graph.v_lines, graph.labels): - line.set_x(graph.axes.c2p(x, 0)[0]) - label.set_x(graph.axes.c2p(x, 0)[0]) - label.set_value(x) - - graph.labels[0].shift(MED_SMALL_BUFF * LEFT) - graph.labels[1].shift(MED_SMALL_BUFF * RIGHT) - - new_simple_graph = graph.axes.get_graph(dist.pdf) - new_region = get_region_under_curve(graph.axes, new_simple_graph, *x_bounds) - new_region.match_style(graph.region) - graph.region.become(new_region) - - graph.label95.set_width(graph.label95.width_ratio * graph.region.get_width()) - graph.label95.match_x(graph.region) - - self.add(graph, region, label95, p_label) - self.play( - N_tracker.set_value, 1000, - UpdateFromFunc(graph, update_graph), - Animation(v_lines), - Animation(labels), - Animation(graph.region), - Animation(graph.label95), - run_time=5, - ) - self.wait() - - # - - def get_coins(self, bool_values): - coins = VGroup(*[ - get_coin("H" if heads else "T") - for heads in bool_values - ]) - coins.arrange_in_grid(n_rows=10, buff=MED_LARGE_BUFF) - coins.set_height(5) - return coins - - def get_probability_label(self): - head = get_coin("H") - p_label = TexMobject( - "P(00) = ", - tex_to_color_map={"00": WHITE} - ) - template = p_label.get_part_by_tex("00") - head.replace(template) - p_label.replace_submobject( - p_label.index_of_part(template), - head, - ) - prob = DecimalNumber(self.true_p) - prob.next_to(p_label, RIGHT) - p_label.add(prob) - p_label.set_height(0.75) - p_label.to_corner(UR) - - prob_box = SurroundingRectangle(prob, buff=SMALL_BUFF) - prob_box.set_fill(GREY_D, 1) - prob_box.set_stroke(WHITE, 2) - - q_marks = TexMobject("???") - q_marks.move_to(prob_box) - prob_box.add(q_marks) - - return p_label, prob, prob_box - - -class HighlightReviewPartsReversed(HighlightReviewParts): - CONFIG = { - "reverse_order": True, - } - - -class Grey(Scene): - def construct(self): - self.add(FullScreenFadeRectangle(fill_color=GREY_D, fill_opacity=1)) - - -class ShowBayesRule(Scene): - def construct(self): - hyp = "\\text{Hypothesis}" - data = "\\text{Data}" - bayes = TexMobject( - f"P({hyp} \\,|\\, {data})", "=", "{", - f"P({data} \\,|\\, {hyp})", f"P({hyp})", - "\\over", f"P({data})", - tex_to_color_map={ - hyp: YELLOW, - data: GREEN, - } - ) - - title = TextMobject("Bayes' rule") - title.scale(2) - title.to_edge(UP) - - self.add(title) - self.add(*bayes[:5]) - self.wait() - self.play( - *[ - TransformFromCopy(bayes[i], bayes[j], path_arc=30 * DEGREES) - for i, j in [ - (0, 7), - (1, 10), - (2, 9), - (3, 8), - (4, 11), - ] - ], - FadeIn(bayes[5]), - run_time=1.5 - ) - self.wait() - self.play( - *[ - TransformFromCopy(bayes[i], bayes[j], path_arc=30 * DEGREES) - for i, j in [ - (0, 12), - (1, 13), - (4, 14), - (0, 16), - (3, 17), - (4, 18), - ] - ], - FadeIn(bayes[15]), - run_time=1.5 - ) - self.add(bayes) - self.wait() - - hyp_word = bayes.get_part_by_tex(hyp) - example_hyp = TextMobject( - "For example,\\\\", - "$0.9 < s < 0.99$", - ) - example_hyp[1].set_color(YELLOW) - example_hyp.next_to(hyp_word, DOWN, buff=1.5) - - data_word = bayes.get_part_by_tex(data) - example_data = TexMobject( - "48\\,", CMARK_TEX, - "\\,2\\,", XMARK_TEX, - ) - example_data.set_color_by_tex(CMARK_TEX, GREEN) - example_data.set_color_by_tex(XMARK_TEX, RED) - example_data.scale(1.5) - example_data.next_to(example_hyp, RIGHT, buff=1.5) - - hyp_arrow = Arrow( - hyp_word.get_bottom(), - example_hyp.get_top(), - ) - data_arrow = Arrow( - data_word.get_bottom(), - example_data.get_top(), - ) - - self.play( - GrowArrow(hyp_arrow), - FadeInFromPoint(example_hyp, hyp_word.get_center()), - ) - self.wait() - self.play( - GrowArrow(data_arrow), - FadeInFromPoint(example_data, data_word.get_center()), - ) - self.wait() - - -class VisualizeBayesRule(Scene): - def construct(self): - self.show_continuum() - self.show_arrows() - self.show_discrete_probabilities() - self.show_bayes_formula() - self.parallel_universes() - self.update_from_data() - - def show_continuum(self): - axes = get_beta_dist_axes(y_max=1, y_unit=0.1) - axes.y_axis.add_numbers( - *np.arange(0.2, 1.2, 0.2), - number_config={ - "num_decimal_places": 1, - } - ) - - p_label = TexMobject( - "P(s \\,|\\, \\text{data})", - tex_to_color_map={ - "s": YELLOW, - "\\text{data}": GREEN, - } - ) - p_label.scale(1.5) - p_label.to_edge(UP, LARGE_BUFF) - - s_part = p_label.get_part_by_tex("s").copy() - x_line = Line(axes.c2p(0, 0), axes.c2p(1, 0)) - x_line.set_stroke(YELLOW, 3) - - arrow = Vector(DOWN) - arrow.next_to(s_part, DOWN, SMALL_BUFF) - value = DecimalNumber(0, num_decimal_places=4) - value.set_color(YELLOW) - value.next_to(arrow, DOWN) - - self.add(axes) - self.add(p_label) - self.play( - s_part.next_to, x_line.get_start(), UR, SMALL_BUFF, - GrowArrow(arrow), - FadeInFromPoint(value, s_part.get_center()), - ) - - s_part.tracked = x_line - value.tracked = x_line - value.x_axis = axes.x_axis - self.play( - ShowCreation(x_line), - UpdateFromFunc( - s_part, - lambda m: m.next_to(m.tracked.get_end(), UR, SMALL_BUFF) - ), - UpdateFromFunc( - value, - lambda m: m.set_value( - m.x_axis.p2n(m.tracked.get_end()) - ) - ), - run_time=3, - ) - self.wait() - self.play( - FadeOut(arrow), - FadeOut(value), - ) - - self.p_label = p_label - self.s_part = s_part - self.value = value - self.x_line = x_line - self.axes = axes - - def show_arrows(self): - axes = self.axes - - arrows = VGroup() - arrow_template = Vector(DOWN) - arrow_template.lock_triangulation() - - def get_arrow(s, denom): - arrow = arrow_template.copy() - arrow.set_height(4 / denom) - arrow.move_to(axes.c2p(s, 0), DOWN) - arrow.set_color(interpolate_color( - GREY_A, GREY_C, random.random() - )) - return arrow - - for k in range(2, 50): - for n in range(1, k): - if np.gcd(n, k) != 1: - continue - s = n / k - arrows.add(get_arrow(s, k)) - for k in range(50, 1000): - arrows.add(get_arrow(1 / k, k)) - arrows.add(get_arrow(1 - 1 / k, k)) - - kw = { - "lag_ratio": 0.5, - "run_time": 5, - "rate_func": lambda t: t**4, - } - arrows.save_state() - for arrow in arrows: - arrow.stretch(0, 0) - arrow.set_stroke(width=0) - arrow.set_opacity(0) - self.play(Restore(arrows, **kw)) - self.play(LaggedStartMap( - ApplyMethod, arrows, - lambda m: (m.scale, 0, {"about_edge": DOWN}), - **kw - )) - self.remove(arrows) - self.wait() - - def show_discrete_probabilities(self): - axes = self.axes - - x_lines = VGroup() - dx = 0.01 - for x in np.arange(0, 1, dx): - line = Line( - axes.c2p(x, 0), - axes.c2p(x + dx, 0), - ) - line.set_stroke(BLUE, 3) - line.generate_target() - line.target.rotate( - 90 * DEGREES, - about_point=line.get_start() - ) - x_lines.add(line) - - self.add(x_lines) - self.play( - FadeOut(self.x_line), - LaggedStartMap( - MoveToTarget, x_lines, - ) - ) - - label = Integer(0) - label.set_height(0.5) - label.next_to(self.p_label[1], DOWN, LARGE_BUFF) - unit = TexMobject("\\%") - unit.match_height(label) - fix_percent(unit.family_members_with_points()[0]) - always(unit.next_to, label, RIGHT, SMALL_BUFF) - - arrow = Arrow() - arrow.max_stroke_width_to_length_ratio = 1 - arrow.axes = axes - arrow.label = label - arrow.add_updater(lambda m: m.put_start_and_end_on( - m.label.get_bottom() + MED_SMALL_BUFF * DOWN, - m.axes.c2p(0.01 * m.label.get_value(), 0.03), - )) - - self.add(label, unit, arrow) - self.play( - ChangeDecimalToValue(label, 99), - run_time=5, - ) - self.wait() - self.play(*map(FadeOut, [label, unit, arrow])) - - # Show prior label - p_label = self.p_label - given_data = p_label[2:4] - prior_label = TexMobject("P(s)", tex_to_color_map={"s": YELLOW}) - prior_label.match_height(p_label) - prior_label.move_to(p_label, DOWN, LARGE_BUFF) - - p_label.save_state() - self.play( - given_data.scale, 0.5, - given_data.set_opacity, 0.5, - given_data.to_corner, UR, - Transform(p_label[:2], prior_label[:2]), - Transform(p_label[-1], prior_label[-1]), - ) - self.wait() - - # Zoom in on the y-values - new_ticks = VGroup() - new_labels = VGroup() - dy = 0.01 - for y in np.arange(dy, 5 * dy, dy): - height = get_norm(axes.c2p(0, dy) - axes.c2p(0, 0)) - tick = axes.y_axis.get_tick(y, SMALL_BUFF) - label = DecimalNumber(y) - label.match_height(axes.y_axis.numbers[0]) - always(label.next_to, tick, LEFT, SMALL_BUFF) - - new_ticks.add(tick) - new_labels.add(label) - - for num in axes.y_axis.numbers: - height = num.get_height() - always(num.set_height, height, stretch=True) - - bars = VGroup() - dx = 0.01 - origin = axes.c2p(0, 0) - for x in np.arange(0, 1, dx): - rect = Rectangle( - width=get_norm(axes.c2p(dx, 0) - origin), - height=get_norm(axes.c2p(0, dy) - origin), - ) - rect.x = x - rect.set_stroke(BLUE, 1) - rect.set_fill(BLUE, 0.5) - rect.move_to(axes.c2p(x, 0), DL) - bars.add(rect) - - stretch_group = VGroup( - axes.y_axis, - bars, - new_ticks, - x_lines, - ) - x_lines.set_height( - bars.get_height(), - about_edge=DOWN, - stretch=True, - ) - - self.play( - stretch_group.stretch, 25, 1, {"about_point": axes.c2p(0, 0)}, - VFadeIn(bars), - VFadeIn(new_ticks), - VFadeIn(new_labels), - VFadeOut(x_lines), - run_time=4, - ) - - highlighted_bars = bars.copy() - highlighted_bars.set_color(YELLOW) - self.play( - LaggedStartMap( - FadeIn, highlighted_bars, - lag_ratio=0.5, - rate_func=there_and_back, - ), - ShowCreationThenFadeAround(new_labels[0]), - run_time=3, - ) - self.remove(highlighted_bars) - - # Nmae as prior - prior_name = TextMobject("Prior", " distribution") - prior_name.set_height(0.6) - prior_name.next_to(prior_label, DOWN, LARGE_BUFF) - - self.play(FadeInFromDown(prior_name)) - self.wait() - - # Show alternate distribution - bars.save_state() - for a, b in [(5, 2), (1, 6)]: - dist = scipy.stats.beta(a, b) - for bar, saved in zip(bars, bars.saved_state): - bar.target = saved.copy() - height = get_norm(axes.c2p(0.1 * dist.pdf(bar.x)) - axes.c2p(0, 0)) - bar.target.set_height(height, about_edge=DOWN, stretch=True) - - self.play(LaggedStartMap(MoveToTarget, bars, lag_ratio=0.00)) - self.wait() - self.play(Restore(bars)) - self.wait() - - uniform_name = TextMobject("Uniform") - uniform_name.match_height(prior_name) - uniform_name.move_to(prior_name, DL) - uniform_name.shift(RIGHT) - uniform_name.set_y(bars.get_top()[1] + MED_SMALL_BUFF, DOWN) - self.play( - prior_name[0].next_to, uniform_name, RIGHT, MED_SMALL_BUFF, DOWN, - FadeOutAndShift(prior_name[1], RIGHT), - FadeInFrom(uniform_name, LEFT) - ) - self.wait() - - self.bars = bars - self.uniform_label = VGroup(uniform_name, prior_name[0]) - - def show_bayes_formula(self): - uniform_label = self.uniform_label - p_label = self.p_label - bars = self.bars - - prior_label = VGroup( - p_label[0].deepcopy(), - p_label[1].deepcopy(), - p_label[4].deepcopy(), - ) - eq = TexMobject("=") - likelihood_label = TexMobject( - "P(", "\\text{data}", "|", "s", ")", - ) - likelihood_label.set_color_by_tex("data", GREEN) - likelihood_label.set_color_by_tex("s", YELLOW) - over = Line(LEFT, RIGHT) - p_data_label = TextMobject("P(", "\\text{data}", ")") - p_data_label.set_color_by_tex("data", GREEN) - - for mob in [eq, likelihood_label, over, p_data_label]: - mob.scale(1.5) - mob.set_opacity(0.1) - - eq.move_to(prior_label, LEFT) - over.set_width( - prior_label.get_width() + - likelihood_label.get_width() + - MED_SMALL_BUFF - ) - over.next_to(eq, RIGHT, MED_SMALL_BUFF) - p_data_label.next_to(over, DOWN, MED_SMALL_BUFF) - likelihood_label.next_to(over, UP, MED_SMALL_BUFF, RIGHT) - - self.play( - p_label.restore, - p_label.next_to, eq, LEFT, MED_SMALL_BUFF, - prior_label.next_to, over, UP, MED_SMALL_BUFF, LEFT, - FadeIn(eq), - FadeIn(likelihood_label), - FadeIn(over), - FadeIn(p_data_label), - FadeOut(uniform_label), - ) - - # Show new distribution - post_bars = bars.copy() - total_prob = 0 - for bar, p in zip(post_bars, np.arange(0, 1, 0.01)): - prob = scipy.stats.binom(50, p).pmf(48) - bar.stretch(prob, 1, about_edge=DOWN) - total_prob += 0.01 * prob - post_bars.stretch(1 / total_prob, 1, about_edge=DOWN) - post_bars.stretch(0.25, 1, about_edge=DOWN) # Lie to fit on screen... - post_bars.set_color(MAROON_D) - post_bars.set_fill(opacity=0.8) - - brace = Brace(p_label, DOWN) - post_word = brace.get_text("Posterior") - post_word.scale(1.25, about_edge=UP) - post_word.set_color(MAROON_D) - - self.play( - ReplacementTransform( - bars.copy().set_opacity(0), - post_bars, - ), - GrowFromCenter(brace), - FadeInFrom(post_word, 0.25 * UP) - ) - self.wait() - self.play( - eq.set_opacity, 1, - likelihood_label.set_opacity, 1, - ) - self.wait() - - data = get_check_count_label(48, 2) - data.scale(1.5) - data.next_to(likelihood_label, DOWN, buff=2, aligned_edge=LEFT) - data_arrow = Arrow( - likelihood_label[1].get_bottom(), - data.get_top() - ) - data_arrow.set_color(GREEN) - - self.play( - GrowArrow(data_arrow), - GrowFromPoint(data, data_arrow.get_start()), - ) - self.wait() - self.play(FadeOut(data_arrow)) - self.play( - over.set_opacity, 1, - p_data_label.set_opacity, 1, - ) - self.wait() - - self.play( - FadeOut(brace), - FadeOut(post_word), - FadeOut(post_bars), - FadeOut(data), - p_label.set_opacity, 0.1, - eq.set_opacity, 0.1, - likelihood_label.set_opacity, 0.1, - over.set_opacity, 0.1, - p_data_label.set_opacity, 0.1, - ) - - self.bayes = VGroup( - p_label, eq, - prior_label, likelihood_label, - over, p_data_label - ) - self.data = data - - def parallel_universes(self): - bars = self.bars - - cols = VGroup() - squares = VGroup() - sample_colors = color_gradient( - [GREEN_C, GREEN_D, GREEN_E], - 100 - ) - for bar in bars: - n_rows = 12 - col = VGroup() - for x in range(n_rows): - square = Rectangle( - width=bar.get_width(), - height=bar.get_height() / n_rows, - ) - square.set_stroke(width=0) - square.set_fill(opacity=1) - square.set_color(random.choice(sample_colors)) - col.add(square) - squares.add(square) - col.arrange(DOWN, buff=0) - col.move_to(bar) - cols.add(col) - squares.shuffle() - - self.play( - LaggedStartMap( - VFadeInThenOut, squares, - lag_ratio=0.005, - run_time=3 - ) - ) - self.remove(squares) - squares.set_opacity(1) - self.wait() - - example_col = cols[95] - - self.play( - bars.set_opacity, 0.25, - FadeIn(example_col, lag_ratio=0.1), - ) - self.wait() - - dist = scipy.stats.binom(50, 0.95) - for x in range(12): - square = random.choice(example_col).copy() - square.set_fill(opacity=0) - square.set_stroke(YELLOW, 2) - self.add(square) - nc = dist.ppf(random.random()) - data = get_check_count_label(nc, 50 - nc) - data.next_to(example_col, UP) - - self.add(square, data) - self.wait(0.5) - self.remove(square, data) - self.wait() - - self.data.set_opacity(1) - self.play( - FadeIn(self.data), - FadeOut(example_col), - self.bayes[3].set_opacity, 1, - ) - self.wait() - - def update_from_data(self): - bars = self.bars - data = self.data - bayes = self.bayes - - new_bars = bars.copy() - new_bars.set_stroke(opacity=1) - new_bars.set_fill(opacity=0.8) - for bar, p in zip(new_bars, np.arange(0, 1, 0.01)): - dist = scipy.stats.binom(50, p) - scalar = dist.pmf(48) - bar.stretch(scalar, 1, about_edge=DOWN) - - self.play( - ReplacementTransform( - bars.copy().set_opacity(0), - new_bars - ), - bars.set_fill, {"opacity": 0.1}, - bars.set_stroke, {"opacity": 0.1}, - run_time=2, - ) - - # Show example bar - bar95 = VGroup( - bars[95].copy(), - new_bars[95].copy() - ) - bar95.save_state() - bar95.generate_target() - bar95.target.scale(2) - bar95.target.next_to(bar95, UP, LARGE_BUFF) - bar95.target.set_stroke(BLUE, 3) - - ex_label = TexMobject("s", "=", "0.95") - ex_label.set_color(YELLOW) - ex_label.next_to(bar95.target, DOWN, submobject_to_align=ex_label[-1]) - - highlight = SurroundingRectangle(bar95, buff=0) - highlight.set_stroke(YELLOW, 2) - - self.play(FadeIn(highlight)) - self.play( - MoveToTarget(bar95), - FadeInFromDown(ex_label), - data.shift, LEFT, - ) - self.wait() - - side_brace = Brace(bar95[1], RIGHT, buff=SMALL_BUFF) - side_label = side_brace.get_text("0.26", buff=SMALL_BUFF) - self.play( - GrowFromCenter(side_brace), - FadeIn(side_label) - ) - self.wait() - self.play( - FadeOut(side_brace), - FadeOut(side_label), - FadeOut(ex_label), - ) - self.play( - bar95.restore, - bar95.set_opacity, 0, - ) - - for bar in bars[94:80:-1]: - highlight.move_to(bar) - self.wait(0.5) - self.play(FadeOut(highlight)) - self.wait() - - # Emphasize formula terms - tops = VGroup() - for bar, new_bar in zip(bars, new_bars): - top = Line(bar.get_corner(UL), bar.get_corner(UR)) - top.set_stroke(YELLOW, 2) - top.generate_target() - top.target.move_to(new_bar, UP) - tops.add(top) - - rect = SurroundingRectangle(bayes[2]) - rect.set_stroke(YELLOW, 1) - rect.target = SurroundingRectangle(bayes[3]) - rect.target.match_style(rect) - self.play( - ShowCreation(rect), - ShowCreation(tops), - ) - self.wait() - self.play( - LaggedStartMap( - MoveToTarget, tops, - run_time=2, - lag_ratio=0.02, - ), - MoveToTarget(rect), - ) - self.play(FadeOut(tops)) - self.wait() - - # Show alternate priors - axes = self.axes - bar_groups = VGroup() - for bar, new_bar in zip(bars, new_bars): - bar_groups.add(VGroup(bar, new_bar)) - - bar_groups.save_state() - for a, b in [(5, 2), (7, 1)]: - dist = scipy.stats.beta(a, b) - for bar, saved in zip(bar_groups, bar_groups.saved_state): - bar.target = saved.copy() - height = get_norm(axes.c2p(0.1 * dist.pdf(bar[0].x)) - axes.c2p(0, 0)) - height = max(height, 1e-6) - bar.target.set_height(height, about_edge=DOWN, stretch=True) - - self.play(LaggedStartMap(MoveToTarget, bar_groups, lag_ratio=0)) - self.wait() - self.play(Restore(bar_groups)) - self.wait() - - # Rescale - ex_p_label = TexMobject( - "P(s = 0.95 | 00000000) = ", - tex_to_color_map={ - "s = 0.95": YELLOW, - "00000000": WHITE, - } - ) - ex_p_label.scale(1.5) - ex_p_label.next_to(bars, UP, LARGE_BUFF) - ex_p_label.align_to(bayes, LEFT) - template = ex_p_label.get_part_by_tex("00000000") - template.set_opacity(0) - - highlight = SurroundingRectangle(new_bars[95], buff=0) - highlight.set_stroke(YELLOW, 1) - - self.remove(data) - self.play( - FadeIn(ex_p_label), - VFadeOut(data[0]), - data[1:].move_to, template, - FadeIn(highlight) - ) - self.wait() - - numer = new_bars[95].copy() - numer.set_stroke(YELLOW, 1) - denom = new_bars[80:].copy() - h_line = Line(LEFT, RIGHT) - h_line.set_width(3) - h_line.set_stroke(width=2) - h_line.next_to(ex_p_label, RIGHT) - - self.play( - numer.next_to, h_line, UP, - denom.next_to, h_line, DOWN, - ShowCreation(h_line), - ) - self.wait() - self.play( - denom.space_out_submobjects, - rate_func=there_and_back - ) - self.play( - bayes[4].set_opacity, 1, - bayes[5].set_opacity, 1, - FadeOut(rect), - ) - self.wait() - - # Rescale - self.play( - FadeOut(highlight), - FadeOut(ex_p_label), - FadeOut(data), - FadeOut(h_line), - FadeOut(numer), - FadeOut(denom), - bayes.set_opacity, 1, - ) - - new_bars.unlock_shader_data() - self.remove(new_bars, *new_bars) - self.play( - new_bars.set_height, 5, {"about_edge": DOWN, "stretch": True}, - new_bars.set_color, MAROON_D, - ) - self.wait() - - -class UniverseOf95Percent(WhatsTheModel): - CONFIG = {"s": 0.95} - - def construct(self): - self.introduce_buyer_and_seller() - for m, v in [(self.seller, RIGHT), (self.buyer, LEFT)]: - m.shift(v) - m.label.shift(v) - - pis = VGroup(self.seller, self.buyer) - label = get_prob_positive_experience_label(True, True) - label[-1].set_value(self.s) - label.set_height(1) - label.next_to(pis, UP, LARGE_BUFF) - self.add(label) - - for x in range(4): - self.play(*self.experience_animations( - self.seller, self.buyer, arc=30 * DEGREES, p=self.s - )) - - self.embed() - - -class UniverseOf50Percent(UniverseOf95Percent): - CONFIG = {"s": 0.5} - - -class OpenAndCloseAsideOnPdfs(Scene): - def construct(self): - labels = VGroup( - TextMobject("$\\langle$", "Aside on", " pdfs", "$\\rangle$"), - TextMobject("$\\langle$/", "Aside on", " pdfs", "$\\rangle$"), - ) - labels.set_width(FRAME_WIDTH / 2) - for label in labels: - label.set_color_by_tex("pdfs", YELLOW) - - self.play(FadeInFromDown(labels[0])) - self.wait() - self.play(Transform(*labels)) - self.wait() - - -class BayesRuleWithPdf(ShowLimitToPdf): - def construct(self): - # Axes - axes = self.get_axes() - sf = 1.5 - axes.y_axis.stretch(sf, 1, about_point=axes.c2p(0, 0)) - for number in axes.y_axis.numbers: - number.stretch(1 / sf, 1) - self.add(axes) - - # Formula - bayes = self.get_formula() - - post = bayes[:5] - eq = bayes[5] - prior = bayes[6:9] - likelihood = bayes[9:14] - over = bayes[14] - p_data = bayes[15:] - - self.play(FadeInFromDown(bayes)) - self.wait() - - # Prior - prior_graph = get_beta_graph(axes, 0, 0) - prior_graph_top = Line( - prior_graph.get_corner(UL), - prior_graph.get_corner(UR), - ) - prior_graph_top.set_stroke(YELLOW, 3) - - bayes.save_state() - bayes.set_opacity(0.2) - prior.set_opacity(1) - - self.play( - Restore(bayes, rate_func=reverse_smooth), - FadeIn(prior_graph), - ShowCreation(prior_graph_top), - ) - self.play(FadeOut(prior_graph_top)) - self.wait() - - # Scale Down - nh = 1 - nt = 2 - - scaled_graph = axes.get_graph( - lambda x: scipy.stats.binom(3, x).pmf(1) + 1e-6 - ) - scaled_graph.set_stroke(GREEN) - scaled_region = get_region_under_curve(axes, scaled_graph, 0, 1) - - def to_uniform(p, axes=axes): - return axes.c2p( - axes.x_axis.p2n(p), - int(axes.y_axis.p2n(p) != 0), - ) - - scaled_region.set_fill(opacity=0.75) - scaled_region.save_state() - scaled_region.apply_function(to_uniform) - - self.play( - Restore(scaled_region), - UpdateFromAlphaFunc( - scaled_region, - lambda m, a: m.set_opacity(a * 0.75), - ), - likelihood.set_opacity, 1, - ) - self.wait() - - # Rescale - new_graph = get_beta_graph(axes, nh, nt) - self.play( - ApplyMethod( - scaled_region.set_height, new_graph.get_height(), - {"about_edge": DOWN, "stretch": True}, - run_time=2, - ), - over.set_opacity, 1, - p_data.set_opacity, 1, - ) - self.wait() - self.play( - post.set_opacity, 1, - eq.set_opacity, 1, - ) - self.wait() - - # Use lower case - new_bayes = self.get_formula(lowercase=True) - new_bayes.replace(bayes, dim_to_match=0) - rects = VGroup( - SurroundingRectangle(new_bayes[0][0]), - SurroundingRectangle(new_bayes[6][0]), - ) - rects.set_stroke(YELLOW, 3) - - self.remove(bayes) - bayes = self.get_formula() - bayes.unlock_triangulation() - self.add(bayes) - self.play(Transform(bayes, new_bayes)) - self.play(ShowCreationThenFadeOut(rects)) - - def get_formula(self, lowercase=False): - p_sym = "p" if lowercase else "P" - bayes = TexMobject( - p_sym + "({s} \\,|\\, \\text{data})", "=", - "{" + p_sym + "({s})", - "P(\\text{data} \\,|\\, {s})", - "\\over", - "P(\\text{data})", - tex_to_color_map={ - "{s}": YELLOW, - "\\text{data}": GREEN, - } - ) - bayes.set_height(1.5) - bayes.to_edge(UP) - return bayes - - -class TalkThroughCoinExample(ShowBayesianUpdating): - def construct(self): - # Setup - axes = self.get_axes() - x_label = TexMobject("x") - x_label.next_to(axes.x_axis.get_end(), UR, MED_SMALL_BUFF) - axes.add(x_label) - - p_label, prob, prob_box = self.get_probability_label() - prob_box_x = x_label.copy().move_to(prob_box) - - self.add(axes) - self.add(p_label) - self.add(prob_box) - - self.wait() - q_marks = prob_box[1] - prob_box.remove(q_marks) - self.play( - FadeOut(q_marks), - TransformFromCopy(x_label, prob_box_x) - ) - prob_box.add(prob_box_x) - - # Setup coins - bool_values = (np.random.random(100) < self.true_p) - bool_values[:5] = [True, False, True, True, False] - coins = self.get_coins(bool_values) - coins.next_to(axes.y_axis, RIGHT, MED_LARGE_BUFF) - coins.to_edge(UP) - - # Random coin - rows = VGroup() - for x in range(5): - row = self.get_coins(np.random.random(10) < self.true_p) - row.arrange(RIGHT, buff=MED_LARGE_BUFF) - row.set_width(6) - row.move_to(UP) - rows.add(row) - - last_row = VMobject() - for row in rows: - self.play( - FadeOutAndShift(last_row, DOWN), - FadeIn(row, lag_ratio=0.1) - ) - last_row = row - self.play(FadeOutAndShift(last_row, DOWN)) - - # Uniform pdf - region = get_beta_graph(axes, 0, 0) - graph = Line( - region.get_corner(UL), - region.get_corner(UR), - ) - func_label = TexMobject("f(x) =", "1") - func_label.next_to(graph, UP) - - self.play( - FadeIn(func_label, lag_ratio=0.1), - ShowCreation(graph), - ) - self.add(region, graph) - self.play(FadeIn(region)) - self.wait() - - # First flip - coin = coins[0] - arrow = Vector(0.5 * UP) - arrow.next_to(coin, DOWN, SMALL_BUFF) - data_label = TextMobject("New data") - data_label.set_height(0.25) - data_label.next_to(arrow, DOWN) - data_label.shift(0.5 * RIGHT) - - self.play( - FadeInFrom(coin, DOWN), - GrowArrow(arrow), - Write(data_label, run_time=1) - ) - self.wait() - - # Show Bayes rule - bayes = TexMobject( - "p({x} | \\text{data})", "=", - "p({x})", - "{P(\\text{data} | {x})", - "\\over", - "P(\\text{data})", - tex_to_color_map={ - "{x}": WHITE, - "\\text{data}": GREEN, - } - ) - bayes.next_to(func_label, UP, LARGE_BUFF, LEFT) - - likelihood = bayes[9:14] - p_data = bayes[15:] - likelihood_rect = SurroundingRectangle(likelihood, buff=0.05) - likelihood_rect.save_state() - p_data_rect = SurroundingRectangle(p_data, buff=0.05) - - likelihood_x_label = TexMobject("x") - likelihood_x_label.next_to(likelihood_rect, UP) - - self.play(FadeInFromDown(bayes)) - self.wait() - self.play(ShowCreation(likelihood_rect)) - self.wait() - - self.play(TransformFromCopy(likelihood[-2], likelihood_x_label)) - self.wait() - - # Scale by x - times_x = TexMobject("\\cdot \\, x") - times_x.next_to(func_label, RIGHT, buff=0.2) - - new_graph = axes.get_graph(lambda x: x) - sub_region = get_region_under_curve(axes, new_graph, 0, 1) - - self.play( - Write(times_x), - Transform(graph, new_graph), - ) - self.play( - region.set_opacity, 0.5, - FadeIn(sub_region), - ) - self.wait() - - # Show example scalings - low_x = 0.1 - high_x = 0.9 - lines = VGroup() - for x in [low_x, high_x]: - lines.add(Line(axes.c2p(x, 0), axes.c2p(x, 1))) - - lines.set_stroke(YELLOW, 3) - - for x, line in zip([low_x, high_x], lines): - self.play(FadeIn(line)) - self.play(line.scale, x, {"about_edge": DOWN}) - self.wait() - self.play(FadeOut(lines)) - - # Renormalize - self.play( - FadeOut(likelihood_x_label), - ReplacementTransform(likelihood_rect, p_data_rect), - ) - self.wait() - - one = func_label[1] - two = TexMobject("2") - two.move_to(one, LEFT) - - self.play( - FadeOut(region), - sub_region.stretch, 2, 1, {"about_edge": DOWN}, - sub_region.set_color, BLUE, - graph.stretch, 2, 1, {"about_edge": DOWN}, - FadeInFromDown(two), - FadeOutAndShift(one, UP), - ) - region = sub_region - func_label = VGroup(func_label[0], two, times_x) - self.add(func_label) - - self.play(func_label.shift, 0.5 * UP) - self.wait() - - const = TexMobject("C") - const.scale(0.9) - const.move_to(two, DR) - const.shift(0.07 * RIGHT) - self.play( - FadeOutAndShift(two, UP), - FadeInFrom(const, DOWN) - ) - self.remove(func_label) - func_label = VGroup(func_label[0], const, times_x) - self.add(func_label) - self.play(FadeOut(p_data_rect)) - self.wait() - - # Show tails - coin = coins[1] - self.play( - arrow.next_to, coin, DOWN, SMALL_BUFF, - MaintainPositionRelativeTo(data_label, arrow), - FadeInFromDown(coin), - ) - self.wait() - - to_prior_arrow = Arrow( - func_label[0][3], - bayes[6], - max_tip_length_to_length_ratio=0.15, - stroke_width=3, - ) - to_prior_arrow.set_color(RED) - - self.play(Indicate(func_label, scale_factor=1.2, color=RED)) - self.play(ShowCreation(to_prior_arrow)) - self.wait() - self.play(FadeOut(to_prior_arrow)) - - # Scale by (1 - x) - eq_1mx = TexMobject("(1 - x)") - dot = TexMobject("\\cdot") - rhs_part = VGroup(dot, eq_1mx) - rhs_part.arrange(RIGHT, buff=0.2) - rhs_part.move_to(func_label, RIGHT) - - l_1mx = eq_1mx.copy() - likelihood_rect.restore() - l_1mx.next_to(likelihood_rect, UP, SMALL_BUFF) - - self.play( - ShowCreation(likelihood_rect), - FadeInFrom(l_1mx, 0.5 * DOWN), - ) - self.wait() - self.play(ShowCreationThenFadeOut(Underline(p_label))) - self.play(Indicate(coins[1])) - self.wait() - self.play( - TransformFromCopy(l_1mx, eq_1mx), - FadeInFrom(dot, RIGHT), - func_label.next_to, dot, LEFT, 0.2, - ) - - scaled_graph = axes.get_graph(lambda x: 2 * x * (1 - x)) - scaled_region = get_region_under_curve(axes, scaled_graph, 0, 1) - - self.play(Transform(graph, scaled_graph)) - self.play(FadeIn(scaled_region)) - self.wait() - - # Renormalize - self.remove(likelihood_rect) - self.play( - TransformFromCopy(likelihood_rect, p_data_rect), - FadeOut(l_1mx) - ) - new_graph = get_beta_graph(axes, 1, 1) - group = VGroup(graph, scaled_region) - self.play( - group.set_height, - new_graph.get_height(), {"about_edge": DOWN, "stretch": True}, - group.set_color, BLUE, - FadeOut(region), - ) - region = scaled_region - self.play(FadeOut(p_data_rect)) - self.wait() - self.play(ShowCreationThenFadeAround(const)) - - # Repeat - exp1 = Integer(1) - exp1.set_height(0.2) - exp1.move_to(func_label[2].get_corner(UR), DL) - exp1.shift(0.02 * DOWN + 0.07 * RIGHT) - - exp2 = exp1.copy() - exp2.move_to(eq_1mx.get_corner(UR), DL) - exp2.shift(0.1 * RIGHT) - exp2.align_to(exp1, DOWN) - - shift_vect = UP + 0.5 * LEFT - VGroup(exp1, exp2).shift(shift_vect) - - self.play( - FadeInFrom(exp1, DOWN), - FadeInFrom(exp2, DOWN), - VGroup(func_label, dot, eq_1mx).shift, shift_vect, - bayes.scale, 0.5, - bayes.next_to, p_label, DOWN, LARGE_BUFF, {"aligned_edge": RIGHT}, - ) - nh = 1 - nt = 1 - for coin, is_heads in zip(coins[2:10], bool_values[2:10]): - self.play( - arrow.next_to, coin, DOWN, SMALL_BUFF, - MaintainPositionRelativeTo(data_label, arrow), - FadeInFrom(coin, DOWN), - ) - if is_heads: - nh += 1 - old_exp = exp1 - else: - nt += 1 - old_exp = exp2 - - new_exp = old_exp.copy() - new_exp.increment_value(1) - - dist = scipy.stats.beta(nh + 1, nt + 1) - new_graph = axes.get_graph(dist.pdf) - new_region = get_region_under_curve(axes, new_graph, 0, 1) - new_region.match_style(region) - - self.play( - FadeOut(graph), - FadeOut(region), - FadeIn(new_graph), - FadeIn(new_region), - FadeOutAndShift(old_exp, MED_SMALL_BUFF * UP), - FadeInFrom(new_exp, MED_SMALL_BUFF * DOWN), - ) - graph = new_graph - region = new_region - self.remove(new_exp) - self.add(old_exp) - old_exp.increment_value() - self.wait() - - if coin is coins[4]: - area_label = TextMobject("Area = 1") - area_label.move_to(axes.c2p(0.6, 0.8)) - self.play(GrowFromPoint( - area_label, const.get_center() - )) - - -class PDefectEqualsQmark(Scene): - def construct(self): - label = TexMobject( - "P(\\text{Defect}) = ???", - tex_to_color_map={ - "\\text{Defect}": RED, - } - ) - self.play(FadeInFrom(label, DOWN)) - self.wait() - - -class UpdateOnceWithBinomial(TalkThroughCoinExample): - def construct(self): - # Fair bit of copy-pasting from above. If there's - # time, refactor this properly - # Setup - axes = self.get_axes() - x_label = TexMobject("x") - x_label.next_to(axes.x_axis.get_end(), UR, MED_SMALL_BUFF) - axes.add(x_label) - - p_label, prob, prob_box = self.get_probability_label() - prob_box_x = x_label.copy().move_to(prob_box) - - q_marks = prob_box[1] - prob_box.remove(q_marks) - prob_box.add(prob_box_x) - - self.add(axes) - self.add(p_label) - self.add(prob_box) - - # Coins - bool_values = (np.random.random(100) < self.true_p) - bool_values[:5] = [True, False, True, True, False] - coins = self.get_coins(bool_values) - coins.next_to(axes.y_axis, RIGHT, MED_LARGE_BUFF) - coins.to_edge(UP) - self.add(coins[:10]) - - # Uniform pdf - region = get_beta_graph(axes, 0, 0) - graph = axes.get_graph( - lambda x: 1, - min_samples=30, - ) - self.add(region, graph) - - # Show Bayes rule - bayes = TexMobject( - "p({x} | \\text{data})", "=", - "p({x})", - "{P(\\text{data} | {x})", - "\\over", - "P(\\text{data})", - tex_to_color_map={ - "{x}": WHITE, - "\\text{data}": GREEN, - } - ) - bayes.move_to(axes.c2p(0, 2.5)) - bayes.align_to(coins, LEFT) - - likelihood = bayes[9:14] - # likelihood_rect = SurroundingRectangle(likelihood, buff=0.05) - - self.add(bayes) - - # All data at once - brace = Brace(coins[:10], DOWN) - all_data_label = brace.get_text("One update from all data") - - self.wait() - self.play( - GrowFromCenter(brace), - FadeInFrom(all_data_label, 0.2 * UP), - ) - self.wait() - - # Binomial formula - nh = sum(bool_values[:10]) - nt = sum(~bool_values[:10]) - - likelihood_brace = Brace(likelihood, UP) - t2c = { - str(nh): BLUE, - str(nt): RED, - } - binom_formula = TexMobject( - "{10 \\choose ", str(nh), "}", - "x^{", str(nh), "}", - "(1-x)^{" + str(nt) + "}", - tex_to_color_map=t2c, - ) - binom_formula[0][-1].set_color(BLUE) - binom_formula[1].set_color(WHITE) - binom_formula.set_width(likelihood_brace.get_width() + 0.5) - binom_formula.next_to(likelihood_brace, UP) - - self.play( - TransformFromCopy(brace, likelihood_brace), - FadeOut(all_data_label), - FadeIn(binom_formula) - ) - self.wait() - - # New plot - rhs = TexMobject( - "C \\cdot", - "x^{", str(nh), "}", - "(1-x)^{", str(nt), "}", - tex_to_color_map=t2c - ) - rhs.next_to(bayes[:5], DOWN, LARGE_BUFF, aligned_edge=LEFT) - eq = TexMobject("=") - eq.rotate(90 * DEGREES) - eq.next_to(bayes[:5], DOWN, buff=0.35) - - dist = scipy.stats.beta(nh + 1, nt + 1) - new_graph = axes.get_graph(dist.pdf) - new_graph.shift(1e-6 * UP) - new_graph.set_stroke(WHITE, 1, opacity=0.5) - new_region = get_region_under_curve(axes, new_graph, 0, 1) - new_region.match_style(region) - new_region.set_opacity(0.75) - - self.add(new_region, new_graph, bayes) - region.unlock_triangulation() - self.play( - FadeOut(graph), - FadeOut(region), - FadeIn(new_graph), - FadeIn(new_region), - run_time=1, - ) - self.play( - Write(eq), - FadeInFrom(rhs, UP) - ) - self.wait() diff --git a/from_3b1b/active/bayes/beta_helpers.py b/from_3b1b/active/bayes/beta_helpers.py deleted file mode 100644 index 26f9ecee..00000000 --- a/from_3b1b/active/bayes/beta_helpers.py +++ /dev/null @@ -1,635 +0,0 @@ -from manimlib.imports import * -import scipy.stats - - -CMARK_TEX = "\\text{\\ding{51}}" -XMARK_TEX = "\\text{\\ding{55}}" - -COIN_COLOR_MAP = { - "H": BLUE_E, - "T": RED_E, -} - - -class Histogram(Group): - CONFIG = { - "height": 5, - "width": 10, - "y_max": 1, - "y_axis_numbers_to_show": range(20, 120, 20), - "y_axis_label_height": 0.25, - "y_tick_freq": 0.2, - "x_label_freq": 1, - "include_h_lines": True, - "h_line_style": { - "stroke_width": 1, - "stroke_color": LIGHT_GREY, - # "draw_stroke_behind_fill": True, - }, - "bar_style": { - "stroke_width": 1, - "stroke_color": WHITE, - "fill_opacity": 1, - }, - "bar_colors": [BLUE, GREEN] - } - - def __init__(self, data, **kwargs): - super().__init__(**kwargs) - self.data = data - - self.add_axes() - if self.include_h_lines: - self.add_h_lines() - self.add_bars(data) - self.add_x_axis_labels() - self.add_y_axis_labels() - - def add_axes(self): - n_bars = len(self.data) - axes_config = { - "x_min": 0, - "x_max": n_bars, - "x_axis_config": { - "unit_size": self.width / n_bars, - "include_tip": False, - }, - "y_min": 0, - "y_max": self.y_max, - "y_axis_config": { - "unit_size": self.height / self.y_max, - "include_tip": False, - "tick_frequency": self.y_tick_freq, - }, - } - axes = Axes(**axes_config) - axes.center() - self.axes = axes - self.add(axes) - - def add_h_lines(self): - axes = self.axes - axes.h_lines = VGroup() - for tick in axes.y_axis.tick_marks: - line = Line(**self.h_line_style) - line.match_width(axes.x_axis) - line.move_to(tick.get_center(), LEFT) - axes.h_lines.add(line) - axes.add(axes.h_lines) - - def add_bars(self, data): - self.bars = self.get_bars(data) - self.add(self.bars) - - def add_x_axis_labels(self): - axes = self.axes - axes.x_labels = VGroup() - for x, bar in list(enumerate(self.bars))[::self.x_label_freq]: - label = Integer(x) - label.set_height(0.25) - label.next_to(bar, DOWN) - axes.x_labels.add(label) - axes.add(axes.x_labels) - - def add_y_axis_labels(self): - axes = self.axes - labels = VGroup() - for value in self.y_axis_numbers_to_show: - label = Integer(value, unit="\\%") - fix_percent(label[-1][0]) - label.set_height(self.y_axis_label_height) - label.next_to(axes.y_axis.n2p(0.01 * value), LEFT) - labels.add(label) - axes.y_labels = labels - axes.y_axis.add(labels) - - # Bar manipulations - def get_bars(self, data): - portions = np.array(data).astype(float) - total = portions.sum() - if total == 0: - portions[:] = 0 - else: - portions /= total - bars = VGroup() - for x, prop in enumerate(portions): - bar = Rectangle() - width = get_norm(self.axes.c2p(1, 0) - self.axes.c2p(0, 0)) - height = get_norm(self.axes.c2p(0, 1) - self.axes.c2p(0, 0)) - bar.set_width(width) - bar.set_height(height * prop, stretch=True) - bar.move_to(self.axes.c2p(x, 0), DL) - bars.add(bar) - - bars.set_submobject_colors_by_gradient(*self.bar_colors) - bars.set_style(**self.bar_style) - return bars - - -# Images of randomness - -def fix_percent(sym): - # Really need to make this unneeded... - new_sym = sym.copy() - path_lengths = [len(path) for path in sym.get_subpaths()] - n = sum(path_lengths[:2]) - p1 = sym.points[:n] - p2 = sym.points[n:] - sym.points = p1 - new_sym.points = p2 - sym.add(new_sym) - sym.lock_triangulation() - - -def get_random_process(choices, shuffle_time=2, total_time=3, change_rate=0.05, - h_buff=0.1, v_buff=0.1): - content = choices[0] - - container = Square() - container.set_opacity(0) - container.set_width(content.get_width() + 2 * h_buff, stretch=True) - container.set_height(content.get_height() + 2 * v_buff, stretch=True) - container.move_to(content) - container.add(content) - container.time = 0 - container.last_change_time = 0 - - def update(container, dt): - container.time += dt - - t = container.time - change = all([ - (t % total_time) < shuffle_time, - container.time - container.last_change_time > change_rate - ]) - if change: - mob = container.submobjects[0] - new_mob = random.choice(choices) - new_mob.match_height(mob) - new_mob.move_to(container, DL) - new_mob.shift(2 * np.random.random() * h_buff * RIGHT) - new_mob.shift(2 * np.random.random() * v_buff * UP) - container.set_submobjects([new_mob]) - container.last_change_time = container.time - - container.add_updater(update) - return container - - -def get_die_faces(): - dot = Dot() - dot.set_width(0.15) - dot.set_color(BLUE_B) - - square = Square() - square.round_corners(0.25) - square.set_stroke(WHITE, 2) - square.set_fill(DARKER_GREY, 1) - square.set_width(0.6) - - edge_groups = [ - (ORIGIN,), - (UL, DR), - (UL, ORIGIN, DR), - (UL, UR, DL, DR), - (UL, UR, ORIGIN, DL, DR), - (UL, UR, LEFT, RIGHT, DL, DR), - ] - - arrangements = VGroup(*[ - VGroup(*[ - dot.copy().move_to(square.get_bounding_box_point(ec)) - for ec in edge_group - ]) - for edge_group in edge_groups - ]) - square.set_width(1) - - faces = VGroup(*[ - VGroup(square.copy(), arrangement) - for arrangement in arrangements - ]) - faces.arrange(RIGHT) - - return faces - - -def get_random_die(**kwargs): - return get_random_process(get_die_faces(), **kwargs) - - -def get_random_card(height=1, **kwargs): - cards = DeckOfCards() - cards.set_height(height) - return get_random_process(cards, **kwargs) - - -# Coins -def get_coin(symbol, color=None): - if color is None: - color = COIN_COLOR_MAP.get(symbol, GREY_E) - coin = VGroup() - circ = Circle() - circ.set_fill(color, 1) - circ.set_stroke(WHITE, 1) - circ.set_height(1) - label = TextMobject(symbol) - label.set_height(0.5 * circ.get_height()) - label.move_to(circ) - coin.add(circ, label) - coin.symbol = symbol - coin.lock_triangulation() - return coin - - -def get_random_coin(**kwargs): - return get_random_process([get_coin("H"), get_coin("T")], **kwargs) - - -def get_prob_coin_label(symbol="H", color=None, p=0.5, num_decimal_places=2): - label = TexMobject("P", "(", "00", ")", "=",) - coin = get_coin(symbol, color) - template = label.get_part_by_tex("00") - coin.replace(template) - label.replace_submobject(label.index_of_part(template), coin) - rhs = DecimalNumber(p, num_decimal_places=num_decimal_places) - rhs.next_to(label, RIGHT, buff=MED_SMALL_BUFF) - label.add(rhs) - return label - - -def get_q_box(mob): - box = SurroundingRectangle(mob) - box.set_stroke(WHITE, 1) - box.set_fill(GREY_E, 1) - q_marks = TexMobject("???") - max_width = 0.8 * box.get_width() - max_height = 0.8 * box.get_height() - - if q_marks.get_width() > max_width: - q_marks.set_width(max_width) - - if q_marks.get_height() > max_height: - q_marks.set_height(max_height) - - q_marks.move_to(box) - box.add(q_marks) - return box - - -def get_coin_grid(bools, height=6): - coins = VGroup(*[ - get_coin("H" if heads else "T") - for heads in bools - ]) - coins.arrange_in_grid() - coins.set_height(height) - return coins - - -def get_prob_positive_experience_label(include_equals=False, - include_decimal=False, - include_q_mark=False): - label = TexMobject( - "P", "(", "00000", ")", - ) - - pe = TextMobject("Positive\\\\experience") - pe.set_color(GREEN) - pe.replace(label[2], dim_to_match=0) - label.replace_submobject(2, pe) - VGroup(label[1], label[3]).match_height( - pe, stretch=True, about_edge=DOWN, - ) - if include_equals: - eq = TexMobject("=").next_to(label, RIGHT) - label.add(eq) - if include_decimal: - decimal = DecimalNumber(0.95) - decimal.next_to(label, RIGHT) - decimal.set_color(YELLOW) - label.decimal = decimal - label.add(decimal) - if include_q_mark: - q_mark = TexMobject("?") - q_mark.relative_mob = label[-1] - q_mark.add_updater( - lambda m: m.next_to(m.relative_mob, RIGHT, SMALL_BUFF) - ) - label.add(q_mark) - - return label - - -def get_beta_dist_axes(y_max=20, y_unit=2, label_y=False, **kwargs): - config = { - "x_min": 0, - "x_max": 1, - "x_axis_config": { - "unit_size": 0.1, - "tick_frequency": 0.1, - "include_tip": False, - }, - "y_min": 0, - "y_max": y_max, - "y_axis_config": { - "unit_size": 1, - "tick_frequency": y_unit, - "include_tip": False, - }, - } - result = Axes(**config) - origin = result.c2p(0, 0) - kw = { - "about_point": origin, - "stretch": True, - } - result.x_axis.set_width(11, **kw) - result.y_axis.set_height(6, **kw) - - x_vals = np.arange(0, 1, 0.2) + 0.2 - result.x_axis.add_numbers( - *x_vals, - number_config={"num_decimal_places": 1} - ) - - if label_y: - result.y_axis.add_numbers( - *np.arange(y_unit, y_max, y_unit) - ) - label = TextMobject("Probability density") - label.scale(0.5) - label.next_to(result.y_axis.get_top(), UR, SMALL_BUFF) - label.next_to(result.y_axis, UP, SMALL_BUFF) - label.align_to(result.y_axis.numbers, LEFT) - result.add(label) - result.y_axis_label = label - - result.to_corner(DR, LARGE_BUFF) - - return result - - -def scaled_pdf_axes(scale_factor=3.5): - axes = get_beta_dist_axes( - label_y=True, - y_unit=1, - ) - axes.y_axis.numbers.set_submobjects([ - *axes.y_axis.numbers[:5], - *axes.y_axis.numbers[4::5] - ]) - sf = scale_factor - axes.y_axis.stretch(sf, 1, about_point=axes.c2p(0, 0)) - for number in axes.y_axis.numbers: - number.stretch(1 / sf, 1) - axes.y_axis_label.to_edge(LEFT) - axes.y_axis_label.add_background_rectangle(opacity=1) - axes.set_stroke(background=True) - return axes - - -def close_off_graph(axes, graph): - x_max = axes.x_axis.p2n(graph.get_end()) - graph.add_line_to(axes.c2p(x_max, 0)) - graph.add_line_to(axes.c2p(0, 0)) - graph.lock_triangulation() - return graph - - -def get_beta_graph(axes, n_plus, n_minus, **kwargs): - dist = scipy.stats.beta(n_plus + 1, n_minus + 1) - graph = axes.get_graph(dist.pdf, **kwargs) - close_off_graph(axes, graph) - graph.set_stroke(BLUE, 2) - graph.set_fill(BLUE_E, 1) - graph.lock_triangulation() - return graph - - -def get_beta_label(n_plus, n_minus, point=ORIGIN): - template = TextMobject("Beta(", "00", ",", "00", ")") - template.scale(1.5) - a_label = Integer(n_plus + 1) - a_label.set_color(GREEN) - b_label = Integer(n_minus + 1) - b_label.set_color(RED) - - for i, label in (1, a_label), (3, b_label): - label.match_height(template[i]) - label.move_to(template[i], DOWN) - template.replace_submobject(i, label) - template.save_state() - template.arrange(RIGHT, buff=0.15) - for t1, t2 in zip(template, template.saved_state): - t1.align_to(t2, DOWN) - - return template - - -def get_plusses_and_minuses(n_rows=15, n_cols=20, p=0.95): - result = VGroup() - for x in range(n_rows * n_cols): - if random.random() < p: - mob = TexMobject(CMARK_TEX) - mob.set_color(GREEN) - mob.is_plus = True - else: - mob = TexMobject(XMARK_TEX) - mob.set_color(RED) - mob.is_plus = False - mob.set_width(1) - result.add(mob) - - result.arrange_in_grid(n_rows, n_cols) - result.set_width(5.5) - return result - - -def get_checks_and_crosses(bools, width=12): - result = VGroup() - for positive in bools: - if positive: - mob = TexMobject(CMARK_TEX) - mob.set_color(GREEN) - else: - mob = TexMobject(XMARK_TEX) - mob.set_color(RED) - mob.positive = positive - mob.set_width(0.5) - result.add(mob) - result.arrange(RIGHT, buff=MED_SMALL_BUFF) - result.set_width(width) - return result - - -def get_underlines(marks): - underlines = VGroup() - for mark in marks: - underlines.add(Underline(mark)) - for line in underlines: - line.align_to(underlines[-1], DOWN) - return underlines - - -def get_random_checks_and_crosses(n=50, s=0.95, width=12): - return get_checks_and_crosses( - bools=(np.random.random(n) < s), - width=width - ) - - -def get_random_num_row(s, n=10): - values = np.random.random(n) - nums = VGroup() - syms = VGroup() - for x, value in enumerate(values): - num = DecimalNumber(value) - num.set_height(0.25) - num.move_to(x * RIGHT) - num.positive = (num.get_value() < s) - if num.positive: - num.set_color(GREEN) - sym = TexMobject(CMARK_TEX) - else: - num.set_color(RED) - sym = TexMobject(XMARK_TEX) - sym.match_color(num) - sym.match_height(num) - sym.positive = num.positive - sym.next_to(num, UP) - - nums.add(num) - syms.add(sym) - - row = VGroup(nums, syms) - row.nums = nums - row.syms = syms - row.n_positive = sum([m.positive for m in nums]) - - row.set_width(10) - row.center().to_edge(UP) - return row - - -def get_prob_review_label(n_positive, n_negative, s=0.95): - label = TexMobject( - "P(", - f"{n_positive}\\,{CMARK_TEX}", ",\\,", - f"{n_negative}\\,{XMARK_TEX}", - "\\,|\\,", - "s = {:.2f}".format(s), - ")", - ) - label.set_color_by_tex_to_color_map({ - CMARK_TEX: GREEN, - XMARK_TEX: RED, - "0.95": YELLOW, - }) - return label - - -def get_binomial_formula(n, k, p): - n_mob = Integer(n, color=WHITE) - k_mob = Integer(k, color=GREEN) - nmk_mob = Integer(n - k, color=RED) - p_mob = DecimalNumber(p, color=YELLOW) - - n_str = "N" * len(n_mob) - k_str = "K" * len(k_mob) - p_str = "P" * len(k_mob) - nmk_str = "M" * len(nmk_mob) - - formula = TexMobject( - "\\left(", - "{" + n_str, - "\\over", - k_str + "}", - "\\right)", - "(", p_str, ")", - "^{" + k_str + "}", - "(1 - ", p_str, ")", - "^{" + nmk_str + "}", - ) - parens = VGroup(formula[0], formula[4]) - parens.space_out_submobjects(0.7) - formula.remove(formula.get_part_by_tex("\\over")) - pairs = ( - (n_mob, n_str), - (k_mob, k_str), - (nmk_mob, nmk_str), - (p_mob, p_str), - ) - for mob, tex in pairs: - parts = formula.get_parts_by_tex(tex) - for part in parts: - mob_copy = mob.copy() - i = formula.index_of_part_by_tex(tex) - mob_copy.match_height(part) - mob_copy.move_to(part, DOWN) - formula.replace_submobject(i, mob_copy) - - terms = VGroup( - formula[:4], - formula[4:7], - formula[7], - formula[8:11], - formula[11], - ) - ys = [term.get_y() for term in terms] - terms.arrange(RIGHT, buff=SMALL_BUFF) - terms[0].shift(SMALL_BUFF * LEFT) - for term, y in zip(terms, ys): - term.set_y(y) - - return formula - - -def get_check_count_label(nc, nx, include_rect=True): - result = VGroup( - Integer(nc), - TexMobject(CMARK_TEX, color=GREEN), - Integer(nx), - TexMobject(XMARK_TEX, color=RED), - ) - result.arrange(RIGHT, buff=SMALL_BUFF) - result[2:].shift(SMALL_BUFF * RIGHT) - - if include_rect: - rect = SurroundingRectangle(result) - rect.set_stroke(WHITE, 1) - rect.set_fill(GREY_E, 1) - result.add_to_back(rect) - - return result - - -def reverse_smooth(t): - return smooth(1 - t) - - -def get_region_under_curve(axes, graph, min_x, max_x): - props = [ - binary_search( - function=lambda a: axes.x_axis.p2n(graph.pfp(a)), - target=x, - lower_bound=axes.x_min, - upper_bound=axes.x_max, - ) - for x in [min_x, max_x] - ] - region = graph.copy() - region.pointwise_become_partial(graph, *props) - region.add_line_to(axes.c2p(max_x, 0)) - region.add_line_to(axes.c2p(min_x, 0)) - region.add_line_to(region.get_start()) - - region.set_stroke(GREEN, 2) - region.set_fill(GREEN, 0.5) - - region.axes = axes - region.graph = graph - region.min_x = min_x - region.max_x = max_x - - return region diff --git a/from_3b1b/active/bayes/footnote.py b/from_3b1b/active/bayes/footnote.py deleted file mode 100644 index 82449f2a..00000000 --- a/from_3b1b/active/bayes/footnote.py +++ /dev/null @@ -1,1152 +0,0 @@ -from manimlib.imports import * -from from_3b1b.active.bayes.part1 import BayesDiagram -from from_3b1b.active.bayes.part1 import LibrarianIcon -from from_3b1b.active.bayes.part1 import Person -from from_3b1b.active.bayes.part1 import RandomnessVsProportions - -OUTPUT_DIRECTORY = "bayes/footnote" -TEX_TO_COLOR_MAP = { - "A": YELLOW, - "B": BLUE, -} -MID_COLOR = interpolate_color(BLUE_D, YELLOW, 0.5) -SICKLY_GREEN = "#9BBD37" - - -def get_bayes_formula(): - return TexMobject( - "P(A|B) = {P(A)P(B|A) \\over P(B)}", - tex_to_color_map={ - "A": YELLOW, - "B": BLUE, - }, - substrings_to_isolate=list("P(|)") - ) - - -# Scenes - -class ThisIsAFootnote(TeacherStudentsScene): - def construct(self): - image = ImageMobject("bayes_thumbnail") - image.set_height(2.5) - rect = SurroundingRectangle(image, buff=0) - rect.set_stroke(WHITE, 3) - title = TextMobject("Bayes' theorem") - title.match_width(image) - title.next_to(image, UP) - - image_group = Group(rect, image, title) - image_group.to_corner(UL) - - asterisk = TextMobject("*") - asterisk.set_height(0.5) - asterisk.set_stroke(BLACK, 3, background=True) - asterisk.move_to(image.get_corner(UR), LEFT) - - formula = get_bayes_formula() - formula.move_to(self.hold_up_spot, DOWN) - - pab = formula[:6] - eq = formula[6] - pa = formula[7:11] - pba = formula[11:17] - over = formula[17] - pb = formula[18:23] - - # Show main video - self.play( - FadeInFromDown(image_group), - self.get_student_changes( - "pondering", "hooray", "tease", - look_at_arg=image - ) - ) - self.play( - Write(asterisk), - self.teacher.change, "speaking", - ) - self.play( - self.get_student_changes( - "thinking", "erm", "thinking" - ) - ) - self.wait(3) - self.play( - self.teacher.change, "raise_right_hand", - FadeInFromDown(formula), - self.get_student_changes(*3 * ["pondering"]) - ) - self.wait() - - # Rearrange - parts = VGroup( - pb, pab, eq, pa, pba, - ) - parts.generate_target() - parts.target.arrange(RIGHT, buff=SMALL_BUFF) - parts.target.move_to(self.hold_up_spot) - - self.play( - MoveToTarget(parts, path_arc=-30 * DEGREES), - FadeOut(over), - self.teacher.change, "pondering", - ) - self.wait() - - # Move to top - p_both = TexMobject( - "P(A \\text{ and } B)", - tex_to_color_map={"A": YELLOW, "B": BLUE}, - ) - eq2 = TexMobject("=") - full_equation = VGroup( - pb, pab, eq, p_both, eq2, pa, pba - ) - full_equation.generate_target() - full_equation.target.arrange(RIGHT, buff=SMALL_BUFF) - full_equation.target.set_width(FRAME_WIDTH - 1) - full_equation.target.center() - full_equation.target.to_edge(UP) - - p_both.set_opacity(0) - p_both.scale(0.2) - p_both.move_to(eq) - eq2.move_to(eq) - - self.play( - MoveToTarget(full_equation), - FadeOutAndShift(image_group, 2 * LEFT), - FadeOutAndShift(asterisk, 2 * LEFT), - self.teacher.look_at, 4 * UP, - self.get_student_changes( - "thinking", "erm", "confused", - look_at_arg=4 * UP - ) - ) - self.wait(2) - - -class ShowTwoPerspectives(Scene): - CONFIG = { - "pa": 1 / 3, - "pb": 1 / 4, - "p_both": 1 / 6, - "diagram_height": 4, - } - - def construct(self): - # Ask about intersection - formula = self.get_formula() - - venn_diagram = self.get_venn_diagram() - venn_diagram.next_to(formula, DOWN, LARGE_BUFF) - - arrow = Arrow( - formula[3].get_bottom(), - venn_diagram.get_center(), - ) - - self.add(formula) - self.play( - formula[:3].set_opacity, 0.2, - formula[-3:].set_opacity, 0.2, - ) - for i in (0, 1): - self.play( - FadeIn(venn_diagram[0][i]), - Write(venn_diagram[1][i]), - run_time=1, - ) - self.play(ShowCreation(arrow)) - self.wait() - - # Think with respect to A - diagram1 = self.get_diagram1() - diagram1.evidence_split.set_opacity(0) - diagram1.hypothesis_split.set_opacity(1) - diagram1.to_edge(RIGHT, LARGE_BUFF) - diagram1.refresh_braces() - - d1_line = DashedLine( - diagram1.h_rect.get_corner(UR), - diagram1.h_rect.get_corner(DR), - ) - d1_line.set_stroke(BLACK, 2) - - space_words = TextMobject( - "Space of all\\\\possibilities" - ) - space_words.match_width(diagram1.square) - space_words.scale(0.9) - space_words.move_to(diagram1.square) - space_words.set_fill(BLACK) - space_outline = SurroundingRectangle(diagram1.square, buff=0) - space_outline.set_stroke(WHITE, 10) - - self.play( - FadeOut(venn_diagram[0][1]), - FadeOut(venn_diagram[1][1]), - FadeOut(arrow), - formula[4:6].set_opacity, 1, - ) - diagram1.pa_label.update() - self.play( - FadeIn(diagram1.nh_rect), - ReplacementTransform( - venn_diagram[0][0], - diagram1.h_rect, - ), - ReplacementTransform( - venn_diagram[1][0], - diagram1.pa_label.get_part_by_tex("A"), - ), - FadeIn(diagram1.h_brace), - FadeIn(diagram1.pa_label[0]), - FadeIn(diagram1.pa_label[2]), - ShowCreation(d1_line), - ) - self.add(diagram1.pa_label) - self.wait() - self.play( - FadeIn(space_words), - ShowCreation(space_outline), - ) - self.play( - FadeOut(space_words), - FadeOut(space_outline), - ) - self.wait() - - # Show B part - B_rects = VGroup(diagram1.he_rect, diagram1.nhe_rect) - B_rects.set_opacity(1) - B_rects.set_sheen(0.2, UL) - diagram1.nhe_rect.set_fill(BLUE_D) - diagram1.he_rect.set_fill(MID_COLOR) - diagram1.save_state() - B_rects.stretch(0.001, 1, about_edge=DOWN) - - diagram1.he_brace.save_state() - diagram1.he_brace.stretch(0.001, 1, about_edge=DOWN) - - self.add(diagram1.he_brace, diagram1.pba_label) - self.add(diagram1, d1_line) - self.play( - Restore(diagram1), - Restore(diagram1.he_brace), - VFadeIn(diagram1.he_brace), - VFadeIn(diagram1.pba_label), - formula.pba.set_opacity, 1, - ) - self.wait() - - # Show symmetric perspective - diagram1_copy = diagram1.deepcopy() - diagram2 = self.get_diagram2() - d2_line = DashedLine( - diagram2.b_rect.get_corner(UL), - diagram2.b_rect.get_corner(UR), - ) - d2_line.set_stroke(BLACK, 2) - - for rect in [diagram2.ba_rect, diagram2.nba_rect]: - rect.save_state() - rect.stretch(0.001, 0, about_edge=LEFT) - - self.play( - diagram1_copy.move_to, diagram2, - formula.pb.set_opacity, 1, - ) - self.play( - diagram1_copy.set_likelihood, self.pb, - diagram1_copy.set_antilikelihood, self.pb, - VFadeOut(diagram1_copy), - FadeIn(diagram2), - TransformFromCopy(formula.pb, diagram2.pb_label), - FadeIn(diagram2.pb_brace), - ShowCreation(d2_line), - ) - self.wait() - self.play( - formula.pab.set_opacity, 1, - formula.eq1.set_opacity, 1, - ) - self.play( - TransformFromCopy(formula.pab, diagram2.pab_label), - FadeIn(diagram2.pab_brace), - Restore(diagram2.ba_rect), - Restore(diagram2.nba_rect), - ) - self.wait() - - def get_formula(self): - kw = { - "tex_to_color_map": { - "A": YELLOW, - "B": BLUE, - } - } - parts = VGroup(*[ - TexMobject(tex, **kw) - for tex in [ - "P(B)", "P(A|B)", "=", - "P(A \\text{ and } B)", - "=", "P(A)", "P(B|A)", - ] - ]) - attrs = [ - "pb", "pab", "eq1", "p_both", "eq2", "pa", "pba" - ] - for attr, part in zip(attrs, parts): - setattr(parts, attr, part) - - parts.arrange(RIGHT, buff=SMALL_BUFF), - parts.set_width(FRAME_WIDTH - 1) - parts.center().to_edge(UP) - return parts - - def get_venn_diagram(self): - c1 = Circle( - radius=2.5, - stroke_width=2, - stroke_color=YELLOW, - fill_opacity=0.5, - fill_color=YELLOW, - ) - c1.flip(RIGHT) - c1.rotate(3 * TAU / 8) - c2 = c1.copy() - c2.set_color(BLUE) - c1.shift(LEFT) - c2.shift(RIGHT) - circles = VGroup(c1, c2) - - titles = VGroup( - TexMobject("A"), - TexMobject("B"), - ) - for title, circle, vect in zip(titles, circles, [UL, UR]): - title.match_color(circle) - title.scale(2) - title.next_to( - circle.get_boundary_point(vect), - vect, - buff=SMALL_BUFF - ) - - return VGroup(circles, titles) - - def get_diagram1(self): - likelihood = (self.p_both / self.pa) - antilikelihood = (self.pb - self.p_both) / (1 - self.pa) - diagram = BayesDiagram(self.pa, likelihood, antilikelihood) - diagram.set_height(self.diagram_height) - - diagram.add_brace_attrs() - kw = {"tex_to_color_map": TEX_TO_COLOR_MAP} - diagram.pa_label = TexMobject("P(A)", **kw) - diagram.pba_label = TexMobject("P(B|A)", **kw) - diagram.pa_label.add_updater( - lambda m: m.next_to(diagram.h_brace, DOWN, SMALL_BUFF), - ) - diagram.pba_label.add_updater( - lambda m: m.next_to(diagram.he_brace, LEFT, SMALL_BUFF), - ) - - return diagram - - def get_diagram2(self): - pa = self.pa - pb = self.pb - p_both = self.p_both - square = Square() - square.set_stroke(WHITE, 1) - square.set_fill(LIGHT_GREY, 1) - square.set_height(self.diagram_height) - - b_rect = square.copy() - b_rect.stretch(pb, 1, about_edge=DOWN) - b_rect.set_fill(BLUE) - b_rect.set_sheen(0.2, UL) - - nb_rect = square.copy() - nb_rect.stretch(1 - pb, 1, about_edge=UP) - - ba_rect = b_rect.copy() - ba_rect.stretch((p_both / pb), 0, about_edge=LEFT) - ba_rect.set_fill(MID_COLOR) - - nba_rect = nb_rect.copy() - nba_rect.stretch((pa - p_both) / (1 - pb), 0, about_edge=LEFT) - nba_rect.set_fill(YELLOW) - - result = VGroup( - square.set_opacity(0), - b_rect, nb_rect, - ba_rect, nba_rect, - ) - result.b_rect = b_rect - result.nb_rect = nb_rect - result.ba_rect = ba_rect - result.nba_rect = nba_rect - - pb_brace = Brace(b_rect, LEFT, buff=SMALL_BUFF) - pab_brace = Brace(ba_rect, DOWN, buff=SMALL_BUFF) - kw = {"tex_to_color_map": TEX_TO_COLOR_MAP} - pb_label = TexMobject("P(B)", **kw) - pab_label = TexMobject("P(A|B)", **kw) - pb_label.next_to(pb_brace, LEFT, SMALL_BUFF) - pab_label.next_to(pab_brace, DOWN, SMALL_BUFF) - - result.pb_brace = pb_brace - result.pab_brace = pab_brace - result.pb_label = pb_label - result.pab_label = pab_label - - VGroup( - result, - pb_brace, pab_brace, - pb_label, pab_label, - ).to_edge(LEFT) - - return result - - -class Rearrange(ShowTwoPerspectives): - def construct(self): - formula = self.get_formula() - pb, pab, eq1, p_both, eq2, pa, pba = formula - over = TexMobject("{\\qquad\\qquad \\over \\quad}") - over.match_width(formula[:2]) - eq3 = eq1.copy() - - new_line = VGroup( - formula.pb, - formula.pab, - eq3, - formula.pa, - formula.pba, - ) - new_line.generate_target() - new_line.target.arrange(RIGHT, buff=MED_SMALL_BUFF) - new_line.target[0].shift(SMALL_BUFF * RIGHT) - new_line.target[-1].shift(SMALL_BUFF * LEFT) - new_line.target.center() - eq3.set_opacity(0) - - eq1.generate_target() - eq1.target.rotate(PI / 3) - eq1.target.move_to(midpoint( - p_both.get_corner(DL), - new_line.target[0].get_corner(UR) - )) - eq2.generate_target() - eq2.target.rotate(-PI / 3) - eq2.target.move_to(midpoint( - p_both.get_corner(DR), - new_line.target[4].get_corner(UL) - )) - - self.add(formula) - self.play( - MoveToTarget(new_line), - MoveToTarget(eq1), - MoveToTarget(eq2), - ) - self.wait() - - over.move_to(VGroup(pa, pba)) - self.play( - ApplyMethod( - pb.next_to, over, DOWN, - path_arc=30 * DEGREES, - ), - VGroup(pa, pba).next_to, over, UP, - ShowCreation(over), - FadeOut(VGroup(eq1, eq2)) - ) - self.wait(2) - over.generate_target() - over.target.next_to(eq3, LEFT) - numer = VGroup(pb, pab) - numer.generate_target() - numer.target.arrange(RIGHT, buff=SMALL_BUFF) - numer.target.next_to(over.target, UP) - self.play(LaggedStart( - MoveToTarget(over, path_arc=-30 * DEGREES), - MoveToTarget(numer, path_arc=-30 * DEGREES), - ApplyMethod(pa.next_to, over.target, DOWN), - ApplyMethod(pba.next_to, eq3, RIGHT), - lag_ratio=0.3, - )) - self.wait(2) - - # Numbers - pb_brace = Brace(pb, UP, buff=SMALL_BUFF) - pab_brace = Brace(pab, UP, buff=SMALL_BUFF) - pa_brace = Brace(pa, DOWN, buff=SMALL_BUFF) - - pb_value = pb_brace.get_tex("(1/21)") - pab_value = pab_brace.get_tex("(4/10)") - pa_value = pa_brace.get_tex("(24/210)") - - braces = VGroup(pb_brace, pab_brace, pa_brace) - values = VGroup(pb_value, pab_value, pa_value) - - self.play( - LaggedStartMap(GrowFromCenter, braces, lag_ratio=0.3), - LaggedStartMap(GrowFromCenter, values, lag_ratio=0.3), - FadeOut(p_both), - ) - self.wait() - - # Replace symbols - mag = SVGMobject(file_name="magnifying_glass") - mag.set_stroke(width=0) - mag.set_fill(GREY, 1) - mag.set_sheen(1, UL) - - Bs = VGroup(*[ - mob.get_part_by_tex("B") - for mob in [pb, pab, pba] - ]) - As = VGroup(*[ - mob.get_part_by_tex("A") - for mob in [pab, pa, pba] - ]) - books = VGroup(*[ - LibrarianIcon().replace(B, dim_to_match=0) - for B in Bs - ]) - books.set_color(YELLOW) - - mags = VGroup(*[ - mag.copy().replace(A) - for A in As - ]) - - self.play(LaggedStart(*[ - ReplacementTransform(A, mag, path_arc=PI) - for A, mag in zip(As, mags) - ])) - self.play(LaggedStart(*[ - ReplacementTransform(B, book, path_arc=PI) - for B, book in zip(Bs, books) - ])) - self.wait() - - -class ClassLooking(TeacherStudentsScene): - def construct(self): - self.play( - self.teacher.change, "pondering", - self.get_student_changes( - "pondering", "confused", "sassy", - look_at_arg=self.screen, - ), - ) - self.wait(5) - self.play( - self.teacher.change, "raise_right_hand", - ) - self.play( - self.get_student_changes( - "thinking", "pondering", "pondering", - look_at_arg=self.hold_up_spot + 2 * UP, - ) - ) - self.wait(3) - - -class LandscapeOfTools(TeacherStudentsScene): - def construct(self): - group = self.get_formulas() - bayes = group[0].copy() - - self.play( - self.teacher.change, "raise_right_hand", - self.get_student_changes( - *3 * ["confused"], - look_at_arg=group, - ), - FadeInFromDown(bayes), - ) - self.remove(bayes) - self.play( - ShowSubmobjectsOneByOne(group, remover=True), - run_time=5 - ) - self.add(bayes) - self.wait(2) - - bubble = self.students[0].get_bubble() - self.add(bubble, bayes) - self.play( - bayes.move_to, bubble.get_bubble_center(), - DrawBorderThenFill(bubble), - self.teacher.change, "happy", - self.get_student_changes( - "pondering", "erm", "erm", - look_at_arg=bubble, - ) - ) - self.change_all_student_modes( - "thinking", look_at_arg=bayes, - ) - self.wait() - self.play( - FadeOut(bayes), - bubble.set_fill, BLACK, 0.2, - bubble.set_stroke, WHITE, 1, - self.get_student_changes( - "pleading", "guilty", "guilty", - ), - self.teacher.change, "hesitant" - ) - self.wait(2) - - def get_formulas(self): - group = VGroup( - get_bayes_formula(), - TexMobject( - "P(X = k) = {\\lambda^k \\over k!}", "e^{-\\lambda}", - tex_to_color_map={ - "k": YELLOW, - "\\lambda": GREEN, - } - ), - TexMobject( - "{1 \\over \\sigma\\sqrt{2\\pi}}", - "e^{\\frac{1}{2}\\left({(x - \\mu) \\over \\sigma}\\right)^2}", - tex_to_color_map={ - "\\sigma": GREEN, - "\\mu": BLUE, - } - ), - TexMobject( - "P(X = k) =", "\\left({n \\over k}\\right)", "p^k(1-p)^{n-k}", - tex_to_color_map={ - "\\over": BLACK, - "p": WHITE, - "k": YELLOW, - "n": BLUE, - "k": GREEN - } - ), - TexMobject( - "E[X + Y] = E[x] + E[y]" - ), - TexMobject( - "\\text{Var}(X + Y) = \\text{Var}(x) + \\text{Var}(y) + 2\\text{Cov}(X, Y)" - ), - TexMobject( - "H = \\sum_{i} -p_i \\log", "(p_i)", - tex_to_color_map={ - "p_i": YELLOW, - } - ), - TexMobject( - "{n \\choose k}", - "{B(k + \\alpha, n -k + \\beta) \\over B(\\alpha, \\beta)}", - tex_to_color_map={ - "\\alpha": BLUE, - "\\beta": YELLOW, - } - ), - TexMobject( - "P(d) = \\log_{10}\\left(1 + {1 \\over d}\\right)", - tex_to_color_map={"d": BLUE}, - ), - TexMobject( - "\\text{Cov}(X, Y) = \\sum_{i, j} p({x}_i, {y}_j)({x}_i - \\mu_{x})({y}_j - \\mu_{y})", - tex_to_color_map={ - "{x}": BLUE, - "{y}": RED, - } - ), - ) - - group.move_to(self.hold_up_spot, DOWN) - group.shift_onto_screen() - return group - - -class TemptingFormula(ShowTwoPerspectives, RandomnessVsProportions): - def construct(self): - # Show venn diagram - kw = { - "tex_to_color_map": TEX_TO_COLOR_MAP, - "substrings_to_isolate": list("P()"), - } - formula = VGroup( - TexMobject("P(A \\text{ and } B)", **kw), - TexMobject("="), - TexMobject("P(A)P(B)", "\\,", "\\,", **kw), - ) - formula.arrange(RIGHT) - formula.scale(1.5) - formula.to_edge(UP) - - q_marks = TexMobject("???")[0] - q_marks.scale(1.25) - q_marks.next_to(formula[1], UP, SMALL_BUFF) - - formula.save_state() - for part in formula: - part.set_x(0) - formula[1:].set_opacity(0) - and_part = formula[0][2:5].copy() - - venn = self.get_venn_diagram() - venn.next_to(formula, DOWN, LARGE_BUFF) - - self.add(formula) - - for i in 0, 1: - self.play( - DrawBorderThenFill(venn[0][i]), - FadeIn(venn[1][i]), - ) - self.play( - and_part.scale, 0.5, - and_part.move_to, venn, - ) - self.remove(and_part) - venn.add(and_part) - self.add(venn) - self.wait() - self.play(Restore(formula)) - self.play(LaggedStartMap(FadeInFromDown, q_marks)) - - # 1 in 4 heart disease related deaths - people = VGroup(*[Person() for x in range(4)]) - people.arrange(RIGHT) - people.set_height(2) - people[0].set_color(RED) - heart = SuitSymbol("hearts") - heart.set_fill(BLACK) - heart.set_height(0.25) - heart.move_to(people[0]) - heart.shift(0.2 * UR) - people[0].add(heart) - - grid = self.get_grid(4, 4, height=4) - grid.to_corner(DL, buff=LARGE_BUFF) - both_square = grid[0][0].copy() - - people.generate_target() - people.target.set_height(both_square.get_height() - SMALL_BUFF) - left_people = people.target.copy() - self.label_grid(grid, left_people, people.target) - pairs = self.get_grid_entries(grid, left_people, people.target) - for pair in pairs: - pair.generate_target() - pair.restore() - - pair = pairs[0].target.copy() - prob = TexMobject( - "P(", "OO", ")", "= \\frac{1}{4} \\cdot \\frac{1}{4} = \\frac{1}{16}", - ) - pair.move_to(prob[1]) - prob.submobjects[1] = pair - prob.scale(1.5) - prob.next_to(grid, RIGHT, LARGE_BUFF) - - self.play( - FadeOut(venn), - LaggedStartMap(FadeInFromDown, people), - ) - self.play(WiggleOutThenIn(heart)) - self.wait() - self.play( - MoveToTarget(people), - TransformFromCopy(people, left_people), - Write(grid), - FadeIn(prob[:3]), - run_time=1, - ) - self.add(both_square, pairs) - self.play( - LaggedStartMap(MoveToTarget, pairs, path_arc=30 * DEGREES), - both_square.set_stroke, YELLOW, 5, - both_square.set_fill, YELLOW, 0.25, - ) - self.play(FadeIn(prob[3:])) - self.wait() - - grid_group = VGroup(grid, people, left_people, both_square, pairs) - - # Coin flips - ht_grid = self.get_grid(2, 2, height=3) - ht_grid.move_to(grid) - ht_labels = VGroup(TextMobject("H"), TextMobject("T")) - ht_labels.set_submobject_colors_by_gradient(BLUE, RED) - ht_labels.scale(2) - left_ht_labels = ht_labels.copy() - self.label_grid(ht_grid, left_ht_labels, ht_labels) - ht_pairs = self.get_grid_entries(ht_grid, left_ht_labels, ht_labels) - - ht_both_square = ht_grid[1][1].copy() - ht_both_square.set_stroke(YELLOW, 5) - ht_both_square.set_fill(YELLOW, 0.25) - - ht_prob = TexMobject( - "P(\\text{TT}) = \\frac{1}{2} \\cdot \\frac{1}{2} = \\frac{1}{4}", - tex_to_color_map={"\\text{TT}": RED} - ) - ht_prob.scale(1.5) - ht_prob.next_to(ht_grid, RIGHT, LARGE_BUFF) - - ht_grid_group = VGroup( - ht_grid, ht_labels, left_ht_labels, - ht_both_square, ht_pairs, - ) - - self.play( - FadeOut(grid_group), - FadeOut(prob), - FadeIn(ht_grid_group), - FadeIn(ht_prob), - ) - self.wait() - - # Dice throws - dice_grid = self.get_grid(6, 6, height=4) - dice_grid.set_stroke(WHITE, 1) - dice_grid.move_to(grid) - dice_labels = self.get_die_faces() - dice_labels.set_height(0.5) - left_dice_labels = dice_labels.copy() - self.label_grid(dice_grid, left_dice_labels, dice_labels) - dice_pairs = self.get_grid_entries(dice_grid, left_dice_labels, dice_labels) - for pair in dice_pairs: - pair.space_out_submobjects(0.9) - pair.scale(0.75) - - dice_both_square = dice_grid[0][0].copy() - dice_both_square.set_stroke(YELLOW, 5) - dice_both_square.set_fill(YELLOW, 0.25) - - dice_prob = TexMobject( - "P(", "OO", ") = \\frac{1}{6} \\cdot \\frac{1}{6} = \\frac{1}{36}", - ) - pair = dice_pairs[0].copy() - pair.scale(1.5) - pair.move_to(dice_prob[1]) - dice_prob.submobjects[1] = pair - dice_prob.scale(1.5) - dice_prob.next_to(dice_grid, RIGHT, LARGE_BUFF) - - dice_grid_group = VGroup( - dice_grid, dice_labels, left_dice_labels, - dice_both_square, dice_pairs, - ) - - self.play( - FadeOut(ht_grid_group), - FadeOut(ht_prob), - FadeIn(dice_grid_group), - FadeIn(dice_prob), - ) - self.wait() - - # Show correlation - self.play( - FadeOut(dice_grid_group), - FadeOut(dice_prob), - FadeIn(prob), - FadeIn(grid_group), - ) - self.wait() - - cross = Cross(prob[3]) - - for pair in pairs: - pair.add_updater(lambda m: m.move_to(m.square)) - for person, square in zip(people, grid[0]): - person.square = square - person.add_updater(lambda m: m.next_to(m.square, UP)) - - row_rect = SurroundingRectangle( - VGroup(grid[0], left_people[0]), - buff=SMALL_BUFF - ) - row_rect.set_stroke(RED, 3) - - self.play(ShowCreation(cross)) - self.play( - FadeOut(prob), - FadeOut(cross), - ) - self.play( - ShowCreation(row_rect) - ) - self.wait() - self.play( - grid[0][0].stretch, 2, 0, {"about_edge": LEFT}, - grid[0][1:].stretch, 2 / 3, 0, {"about_edge": RIGHT}, - both_square.stretch, 2, 0, {"about_edge": LEFT}, - *[ - ApplyMethod(grid[i][0].stretch, 2 / 3, 0, {"about_edge": LEFT}) - for i in range(1, 4) - ], - *[ - ApplyMethod(grid[i][1:].stretch, 10 / 9, 0, {"about_edge": RIGHT}) - for i in range(1, 4) - ], - ) - self.wait() - grid_group.add(row_rect) - - # Show correct formula - cross = Cross(formula) - cross.set_stroke(RED, 6) - - real_rhs = TexMobject("P(A)P(B|A)", **kw) - real_rhs.scale(1.5) - real_formula = VGroup(*formula[:2].copy(), real_rhs) - real_formula.shift(1.5 * DOWN) - real_rhs.next_to(real_formula[:2], RIGHT) - - real_rect = SurroundingRectangle(real_formula, buff=SMALL_BUFF) - real_rect.set_stroke(GREEN) - check = TexMobject("\\checkmark") - check.set_color(GREEN) - check.match_height(real_formula) - check.next_to(real_rect, LEFT) - - small_cross = Cross(check) - small_cross.match_style(cross) - small_cross.next_to(formula, LEFT) - - self.play( - ShowCreationThenFadeAround(formula), - FadeOut(q_marks), - ) - self.play(ShowCreation(cross)) - self.wait() - self.play( - TransformFromCopy(formula, real_formula), - grid_group.scale, 0.7, - grid_group.to_corner, DL, - ) - self.play( - FadeIn(real_rect), - FadeInFrom(check, RIGHT), - ) - self.wait() - - # Show other grid - ht_grid_group.scale(0.7) - ht_grid_group.next_to(grid_group, RIGHT, buff=1.5) - dice_grid_group.scale(0.7) - dice_grid_group.next_to(ht_grid_group, RIGHT, buff=1.5) - - Bs = VGroup(formula[2][4:], real_formula[2][4:]) - B_rect = SurroundingRectangle( - Bs, - stroke_color=BLUE, - # buff=SMALL_BUFF, - buff=0, - ) - B_rect.scale(1.1, about_edge=LEFT) - B_rect.set_fill(BLUE, 0.5) - B_rect.set_stroke(width=0) - - big_rect = SurroundingRectangle( - VGroup(ht_grid_group, dice_grid_group), - buff=MED_LARGE_BUFF, - color=BLUE, - ) - # B_rect.points[0] += 0.2 * RIGHT - # B_rect.points[-1] += 0.2 * RIGHT - # B_rect.points[3] += 0.2 * LEFT - # B_rect.points[4] += 0.2 * LEFT - # B_rect.make_jagged() - - self.play(FadeIn(ht_grid_group)) - self.play(FadeIn(dice_grid_group)) - self.wait() - self.add(B_rect, Bs.copy()) - self.play( - FadeIn(B_rect), - FadeIn(big_rect), - Transform(cross, small_cross), - FadeOut(real_rect), - ) - self.wait() - - def get_grid(self, n, m, height=4): - grid = VGroup(*[ - VGroup( - *[Square() for x in range(m)] - ).arrange(RIGHT, buff=0) - for y in range(n) - ]).arrange(DOWN, buff=0) - grid.set_height(height) - grid.set_stroke(WHITE, 2) - return grid - - def label_grid(self, grid, row_labels, col_labels): - for label, row in zip(row_labels, grid): - label.next_to(row, LEFT) - - for label, square in zip(col_labels, grid[0]): - label.next_to(square, UP) - - def get_grid_entries(self, grid, row_labels, col_labels): - pairs = VGroup() - for i, p1 in enumerate(row_labels): - for j, p2 in enumerate(col_labels): - pair = VGroup(p1, p2).copy() - pair.save_state() - pair.scale(0.6) - pair.arrange(RIGHT, buff=0.05) - pair.square = grid[i][j] - pair.move_to(grid[i][j]) - pairs.add(pair) - return pairs - - -class DiseaseBayes(Scene): - def construct(self): - formula = TexMobject( - "P(D | +) = {P(D) P(+ | D) \\over P(+)}", - tex_to_color_map={ - "D": YELLOW, - "+": BLUE, - }, - substrings_to_isolate=list("P(|)=") - ) - formula.scale(2.5) - - Ds = formula.get_parts_by_tex("D") - for D in Ds: - index = formula.index_of_part(D) - pi = Randolph() - pi.change("sick") - pi.set_color(SICKLY_GREEN) - pi.replace(D) - formula.submobjects[index] = pi - pi.get_tex_string = lambda: "" - - lhs = formula[:6] - lhs.save_state() - lhs.center() - - sicky = lhs[2] - - sick_words = TextMobject( - "You are sick", - tex_to_color_map={ - "sick": SICKLY_GREEN, - }, - ) - sick_words.scale(1.5) - sick_words.next_to(sicky, UP, 2 * LARGE_BUFF) - positive_words = TextMobject("Positive test result") - positive_words.scale(1.5) - positive_words.set_color(BLUE) - positive_words.next_to(lhs[4], DOWN, 2 * LARGE_BUFF) - - sick_arrow = Arrow(sicky.get_top(), sick_words.get_bottom()) - positive_arrow = Arrow(lhs[4].get_bottom(), positive_words.get_top()) - - arrow_groups = VGroup( - sick_words, sick_arrow, - positive_words, positive_arrow, - ) - - sicky.save_state() - sicky.change("happy") - sicky.set_color(BLUE) - - self.play(FadeInFromDown(lhs)) - self.play( - Restore(sicky), - GrowArrow(sick_arrow), - FadeInFromDown(sick_words), - ) - self.play( - GrowArrow(positive_arrow), - FadeInFrom(positive_words, UP), - ) - self.wait(2) - self.play( - Restore(lhs), - MaintainPositionRelativeTo(arrow_groups, lhs), - FadeIn(formula[6]), - ) - - # Prior - def get_formula_slice(*indices): - return VGroup(*[formula[i] for i in indices]) - - self.play( - TransformFromCopy( - get_formula_slice(0, 1, 2, 5), - get_formula_slice(8, 9, 10, 11), - ), - ) - - # Likelihood - lhs_copy = formula[:6].copy() - likelihood = formula[12:18] - run_time = 1 - self.play( - lhs_copy.next_to, likelihood, UP, - run_time=run_time, - ) - self.play( - Swap(lhs_copy[2], lhs_copy[4]), - run_time=run_time, - ) - self.play( - lhs_copy.move_to, likelihood, - run_time=run_time, - ) - - # Evidence - self.play( - ShowCreation(formula.get_part_by_tex("\\over")), - TransformFromCopy( - get_formula_slice(12, 13, 14, 17), - get_formula_slice(19, 20, 21, 22), - ), - ) - self.wait() - - -class EndScreen(Scene): - CONFIG = { - "camera_config": { - "background_color": DARKER_GREY - } - } - - def construct(self): - width = (475 / 1280) * FRAME_WIDTH - height = width * (323 / 575) - video_rect = Rectangle( - width=width, - height=height, - fill_color=BLACK, - fill_opacity=1, - ) - video_rect.shift(UP) - - date = TextMobject( - "Solution will be\\\\" - "posted", "1/20/19", - ) - date[1].set_color(YELLOW) - date.set_width(video_rect.get_width() - 2 * MED_SMALL_BUFF) - date.move_to(video_rect) - - handle = TextMobject("@3blue1brown") - handle.next_to(video_rect, DOWN, MED_LARGE_BUFF) - - self.add(video_rect, handle) - self.add(AnimatedBoundary(video_rect)) - self.wait(20) diff --git a/from_3b1b/active/bayes/part1.py b/from_3b1b/active/bayes/part1.py deleted file mode 100644 index 57579dc3..00000000 --- a/from_3b1b/active/bayes/part1.py +++ /dev/null @@ -1,4830 +0,0 @@ -from manimlib.imports import * - -import scipy.integrate - -OUTPUT_DIRECTORY = "bayes/part1" - -HYPOTHESIS_COLOR = YELLOW -NOT_HYPOTHESIS_COLOR = GREY -EVIDENCE_COLOR1 = BLUE_C -EVIDENCE_COLOR2 = BLUE_E -NOT_EVIDENCE_COLOR1 = GREY -NOT_EVIDENCE_COLOR2 = DARK_GREY - -# - - -def get_bayes_formula(expand_denominator=False): - t2c = { - "{H}": HYPOTHESIS_COLOR, - "{\\neg H}": NOT_HYPOTHESIS_COLOR, - "{E}": EVIDENCE_COLOR1, - } - substrings_to_isolate = ["P", "\\over", "=", "\\cdot", "+"] - - tex = "P({H} | {E}) = {P({H}) P({E} | {H}) \\over " - if expand_denominator: - tex += "P({H}) P({E} | {H}) + P({\\neg H}) \\cdot P({E} | {\\neg H})}" - else: - tex += "P({E})}" - - formula = TexMobject( - tex, - tex_to_color_map=t2c, - substrings_to_isolate=substrings_to_isolate, - ) - - formula.posterior = formula[:6] - formula.prior = formula[8:12] - formula.likelihood = formula[13:19] - - if expand_denominator: - pass - formula.denom_prior = formula[20:24] - formula.denom_likelihood = formula[25:31] - formula.denom_anti_prior = formula[32:36] - formula.denom_anti_likelihood = formula[37:42] - else: - formula.p_evidence = formula[20:] - - return formula - - -class BayesDiagram(VGroup): - CONFIG = { - "height": 2, - "square_style": { - "fill_color": DARK_GREY, - "fill_opacity": 1, - "stroke_color": WHITE, - "stroke_width": 2, - }, - "rect_style": { - "stroke_color": WHITE, - "stroke_width": 1, - "fill_opacity": 1, - }, - "hypothesis_color": HYPOTHESIS_COLOR, - "not_hypothesis_color": NOT_HYPOTHESIS_COLOR, - "evidence_color1": EVIDENCE_COLOR1, - "evidence_color2": EVIDENCE_COLOR2, - "not_evidence_color1": NOT_EVIDENCE_COLOR1, - "not_evidence_color2": NOT_EVIDENCE_COLOR2, - "prior_rect_direction": DOWN, - } - - def __init__(self, prior, likelihood, antilikelihood, **kwargs): - super().__init__(**kwargs) - square = Square(side_length=self.height) - square.set_style(**self.square_style) - - # Create all rectangles - h_rect, nh_rect, he_rect, nhe_rect, hne_rect, nhne_rect = [ - square.copy().set_style(**self.rect_style) - for x in range(6) - ] - - # Add as attributes - self.square = square - self.h_rect = h_rect # Hypothesis - self.nh_rect = nh_rect # Not hypothesis - self.he_rect = he_rect # Hypothesis and evidence - self.hne_rect = hne_rect # Hypothesis and not evidence - self.nhe_rect = nhe_rect # Not hypothesis and evidence - self.nhne_rect = nhne_rect # Not hypothesis and not evidence - - # Stretch the rectangles - for rect in h_rect, he_rect, hne_rect: - rect.stretch(prior, 0, about_edge=LEFT) - for rect in nh_rect, nhe_rect, nhne_rect: - rect.stretch(1 - prior, 0, about_edge=RIGHT) - - he_rect.stretch(likelihood, 1, about_edge=DOWN) - hne_rect.stretch(1 - likelihood, 1, about_edge=UP) - nhe_rect.stretch(antilikelihood, 1, about_edge=DOWN) - nhne_rect.stretch(1 - antilikelihood, 1, about_edge=UP) - - # Color the rectangles - h_rect.set_fill(self.hypothesis_color) - nh_rect.set_fill(self.not_hypothesis_color) - he_rect.set_fill(self.evidence_color1) - hne_rect.set_fill(self.not_evidence_color1) - nhe_rect.set_fill(self.evidence_color2) - nhne_rect.set_fill(self.not_evidence_color2) - - # Add them - self.hypothesis_split = VGroup(h_rect, nh_rect) - self.evidence_split = VGroup(he_rect, hne_rect, nhe_rect, nhne_rect) - - # Don't add hypothesis split by default - self.add(self.square, self.hypothesis_split, self.evidence_split) - self.square.set_opacity(0) - self.hypothesis_split.set_opacity(0) - - def add_brace_attrs(self, buff=SMALL_BUFF): - braces = self.braces = self.create_braces(buff) - self.braces_buff = buff - attrs = [ - "h_brace", - "nh_brace", - "he_brace", - "hne_brace", - "nhe_brace", - "nhne_brace", - ] - for brace, attr in zip(braces, attrs): - setattr(self, attr, brace) - return self - - def create_braces(self, buff=SMALL_BUFF): - kw = { - "buff": buff, - "min_num_quads": 1, - } - return VGroup( - Brace(self.h_rect, self.prior_rect_direction, **kw), - Brace(self.nh_rect, self.prior_rect_direction, **kw), - Brace(self.he_rect, LEFT, **kw), - Brace(self.hne_rect, LEFT, **kw), - Brace(self.nhe_rect, RIGHT, **kw), - Brace(self.nhne_rect, RIGHT, **kw), - ) - - def refresh_braces(self): - if hasattr(self, "braces"): - self.braces.become( - self.create_braces(self.braces_buff) - ) - return self - - def set_prior(self, new_prior): - p = new_prior - q = 1 - p - full_width = self.square.get_width() - - left_rects = [self.h_rect, self.he_rect, self.hne_rect] - right_rects = [self.nh_rect, self.nhe_rect, self.nhne_rect] - - for group, vect, value in [(left_rects, LEFT, p), (right_rects, RIGHT, q)]: - for rect in group: - rect.set_width( - value * full_width, - stretch=True, - about_edge=vect, - ) - - self.refresh_braces() - return self - - def general_set_likelihood(self, new_likelihood, low_rect, high_rect): - height = self.square.get_height() - - low_rect.set_height( - new_likelihood * height, - stretch=True, - about_edge=DOWN, - ) - high_rect.set_height( - (1 - new_likelihood) * height, - stretch=True, - about_edge=UP, - ) - self.refresh_braces() - return self - - def set_likelihood(self, new_likelihood): - self.general_set_likelihood( - new_likelihood, - self.he_rect, - self.hne_rect, - ) - return self - - def set_antilikelihood(self, new_antilikelihood): - self.general_set_likelihood( - new_antilikelihood, - self.nhe_rect, - self.nhne_rect, - ) - return self - - def copy(self): - return self.deepcopy() - - -class ProbabilityBar(VGroup): - CONFIG = { - "color1": BLUE_D, - "color2": GREY_BROWN, - "height": 0.5, - "width": 6, - "rect_style": { - "stroke_width": 1, - "stroke_color": WHITE, - "fill_opacity": 1, - }, - "include_braces": False, - "brace_direction": UP, - "include_percentages": True, - "percentage_background_stroke_width": 2, - } - - def __init__(self, p=0.5, **kwargs): - super().__init__(**kwargs) - self.add_backbone() - self.add_p_tracker(p) - self.add_bars() - if self.include_braces: - self.braces = always_redraw(lambda: self.get_braces()) - self.add(self.braces) - if self.include_percentages: - self.percentages = always_redraw(lambda: self.get_percentages()) - self.add(self.percentages) - - def add_backbone(self): - backbone = Line() - backbone.set_opacity(0) - backbone.set_width(self.width) - self.backbone = backbone - self.add(backbone) - - def add_p_tracker(self, p): - self.p_tracker = ValueTracker(p) - - def add_bars(self): - bars = VGroup(Rectangle(), Rectangle()) - bars.set_height(self.height) - colors = [self.color1, self.color2] - for bar, color in zip(bars, colors): - bar.set_style(**self.rect_style) - bar.set_fill(color=color) - - bars.add_updater(self.update_bars) - self.bars = bars - self.add(bars) - - def update_bars(self, bars): - vects = [LEFT, RIGHT] - p = self.p_tracker.get_value() - values = [p, 1 - p] - total_width = self.backbone.get_width() - for bar, vect, value in zip(bars, vects, values): - bar.set_width(value * total_width, stretch=True) - bar.move_to(self.backbone, vect) - return bars - - def get_braces(self): - return VGroup(*[ - Brace( - bar, - self.brace_direction, - min_num_quads=1, - buff=SMALL_BUFF, - ) - for bar in self.bars - ]) - - def get_percentages(self): - p = self.p_tracker.get_value() - labels = VGroup(*[ - Integer(value, unit="\\%") - for value in [ - np.floor(p * 100), - 100 - np.floor(p * 100), - ] - ]) - for label, bar in zip(labels, self.bars): - label.set_height(0.75 * bar.get_height()) - min_width = 0.75 * bar.get_width() - if label.get_width() > min_width: - label.set_width(min_width) - label.move_to(bar) - label.set_stroke( - BLACK, - self.percentage_background_stroke_width, - background=True - ) - return labels - - def add_icons(self, *icons, buff=SMALL_BUFF): - if hasattr(self, "braces"): - refs = self.braces - else: - refs = self.bars - - for icon, ref in zip(icons, refs): - icon.ref = ref - icon.add_updater(lambda i: i.next_to( - i.ref, - self.brace_direction, - buff=buff - )) - self.icons = VGroup(*icons) - self.add(self.icons) - - -class Steve(SVGMobject): - CONFIG = { - "file_name": "steve", - "fill_color": GREY, - "sheen_factor": 0.5, - "sheen_direction": UL, - "stroke_width": 0, - "height": 3, - "include_name": True, - "name": "Steve" - } - - def __init__(self, **kwargs): - super().__init__(**kwargs) - if self.include_name: - self.add_name() - - def add_name(self): - self.name = TextMobject(self.name) - self.name.match_width(self) - self.name.next_to(self, DOWN, SMALL_BUFF) - self.add(self.name) - - -class Linda(Steve): - CONFIG = { - "file_name": "linda", - "name": "Linda" - } - - -class LibrarianIcon(SVGMobject): - CONFIG = { - "file_name": "book", - "stroke_width": 0, - "fill_color": LIGHT_GREY, - "sheen_factor": 0.5, - "sheen_direction": UL, - "height": 0.75, - } - - -class FarmerIcon(SVGMobject): - CONFIG = { - "file_name": "farming", - "stroke_width": 0, - "fill_color": GREEN_E, - "sheen_factor": 0.5, - "sheen_direction": UL, - "height": 1.5, - } - - -class PitchforkIcon(SVGMobject): - CONFIG = { - "file_name": "pitch_fork_and_roll", - "stroke_width": 0, - "fill_color": LIGHT_GREY, - "sheen_factor": 0.5, - "sheen_direction": UL, - "height": 1.5, - } - - -class Person(SVGMobject): - CONFIG = { - "file_name": "person", - "height": 1.5, - "stroke_width": 0, - "fill_opacity": 1, - "fill_color": LIGHT_GREY, - } - - -class Librarian(Person): - CONFIG = { - "IconClass": LibrarianIcon, - "icon_style": { - "background_stroke_width": 5, - "background_stroke_color": BLACK, - }, - } - - def __init__(self, **kwargs): - super().__init__(**kwargs) - icon = self.IconClass() - icon.set_style(**self.icon_style) - icon.match_width(self) - icon.move_to(self.get_corner(DR), DOWN) - self.add(icon) - - -class Farmer(Librarian): - CONFIG = { - "IconClass": FarmerIcon, - "icon_style": { - "background_stroke_width": 2, - }, - "fill_color": GREEN, - } - - -# Scenes - - -class Test(Scene): - def construct(self): - icon = FarmerIcon() - icon.scale(2) - self.add(icon) - # self.add(get_submobject_index_labels(icon)) - - -# class FullFormulaIndices(Scene): -# def construct(self): -# formula = get_bayes_formula(expand_denominator=True) -# formula.set_width(FRAME_WIDTH - 1) -# self.add(formula) -# self.add(get_submobject_index_labels(formula)) - - -class IntroduceFormula(Scene): - def construct(self): - formula = get_bayes_formula() - formula.save_state() - formula.set_width(FRAME_WIDTH - 1) - - def get_formula_slice(*indices): - return VGroup(*[formula[i] for i in indices]) - - H_label = formula.get_part_by_tex("{H}") - E_label = formula.get_part_by_tex("{E}") - - hyp_label = TextMobject("Hypothesis") - hyp_label.set_color(HYPOTHESIS_COLOR) - hyp_label.next_to(H_label, UP, LARGE_BUFF) - - evid_label = TextMobject("Evidence") - evid_label.set_color(EVIDENCE_COLOR1) - evid_label.next_to(E_label, DOWN, LARGE_BUFF) - - hyp_arrow = Arrow(hyp_label.get_bottom(), H_label.get_top(), buff=SMALL_BUFF) - evid_arrow = Arrow(evid_label.get_top(), E_label.get_bottom(), buff=SMALL_BUFF) - - self.add(formula[:6]) - # self.add(get_submobject_index_labels(formula)) - # return - self.play( - FadeInFrom(hyp_label, DOWN), - GrowArrow(hyp_arrow), - FadeInFrom(evid_label, UP), - GrowArrow(evid_arrow), - ) - self.wait() - - # Prior - self.play( - ShowCreation(formula.get_part_by_tex("=")), - TransformFromCopy( - get_formula_slice(0, 1, 2, 5), - get_formula_slice(8, 9, 10, 11), - ), - ) - - # Likelihood - lhs_copy = formula[:6].copy() - likelihood = formula[12:18] - run_time = 1 - self.play( - lhs_copy.next_to, likelihood, UP, - run_time=run_time, - ) - self.play( - Swap(lhs_copy[2], lhs_copy[4]), - run_time=run_time, - ) - self.play( - lhs_copy.move_to, likelihood, - run_time=run_time, - ) - - # Evidence - self.play( - ShowCreation(formula.get_part_by_tex("\\over")), - TransformFromCopy( - get_formula_slice(0, 1, 4, 5), - get_formula_slice(19, 20, 21, 22), - ), - ) - self.wait() - - self.clear() - self.play( - formula.restore, - formula.scale, 1.5, - formula.to_edge, UP, - FadeOut(VGroup( - hyp_arrow, hyp_label, - evid_arrow, evid_label, - )) - ) - - -class StateGoal(PiCreatureScene, Scene): - CONFIG = { - "default_pi_creature_kwargs": { - "color": BLUE_B, - "height": 2, - }, - - } - - def construct(self): - # Zoom to later - you = self.pi_creature - line = NumberLine( - x_min=-2, - x_max=12, - include_tip=True - ) - line.to_edge(DOWN, buff=1.5) - line.to_edge(LEFT, buff=-0.5) - - you.next_to(line.n2p(0), UP) - - you_label = TextMobject("you") - you_label.next_to(you, RIGHT, MED_LARGE_BUFF) - you_arrow = Arrow(you_label.get_left(), you.get_right() + 0.5 * LEFT, buff=0.1) - - now_label = TextMobject("Now") - later_label = TextMobject("Later") - now_label.next_to(line.n2p(0), DOWN) - later_label.next_to(line.n2p(10), DOWN) - - self.add(line, now_label) - self.add(you) - self.play( - FadeInFrom(you_label, LEFT), - GrowArrow(you_arrow), - you.change, "pondering", - ) - self.wait() - you_label.add(you_arrow) - self.play( - you.change, "horrified", - you.look, DOWN, - you.next_to, line.n2p(10), UP, - MaintainPositionRelativeTo(you_label, you), - FadeInFromPoint(later_label, now_label.get_center()), - ) - self.wait() - - # Add bubble - bubble = you.get_bubble( - height=4, - width=6, - ) - bubble.set_fill(opacity=0) - formula = get_bayes_formula() - bubble.position_mobject_inside(formula) - - self.play( - you.change, "confused", bubble, - ShowCreation(bubble), - ) - self.play(FadeIn(formula)) - self.play(you.change, "hooray", formula) - self.wait(2) - - # Show examples - icons = VGroup( - SVGMobject(file_name="science"), - SVGMobject(file_name="robot"), - ) - for icon in icons: - icon.set_stroke(width=0) - icon.set_fill(GREY) - icon.set_sheen(1, UL) - icon.set_height(1.5) - icons[0].set_stroke(GREY, 3, background=True) - gold = self.get_gold() - icons.add(gold) - - icons.arrange(DOWN, buff=MED_LARGE_BUFF) - icons.to_corner(UL) - - for icon in icons[:2]: - self.play( - Write(icon, run_time=2), - you.change, "thinking", icon, - ) - self.play( - Blink(you), - FadeOut(VGroup( - line, now_label, later_label, - you_label, you_arrow - )), - ) - self.play( - FadeInFrom(gold, LEFT), - you.change, "erm", gold, - ) - self.play(Blink(you)) - - # Brief Thompson description - words = VGroup( - TextMobject("1988").scale(1.5), - TextMobject("Tommy Thompson\\\\and friends"), - ) - words.arrange(DOWN, buff=0.75) - - ship = ImageMobject("ss_central_america") - ship.set_width(4) - ship.move_to(gold, DL) - ship_title = TextMobject("SS Central America") - ship_title.next_to(ship, UP) - - words.next_to(ship, RIGHT) - - self.play( - FadeInFrom(words[0], LEFT), - you.change, "tease", words, - FadeOut(icons[:2]), - ) - self.play(FadeInFrom(words[1], UP)) - self.wait() - - self.add(ship, gold) - self.play( - FadeIn(ship), - gold.scale, 0.2, - gold.move_to, ship, - ) - self.play(FadeInFromDown(ship_title)) - self.play(you.change, "thinking", ship) - - amount = TexMobject("> \\$700{,}000{,}000") - amount.scale(1.5) - amount.next_to(ship, DOWN, MED_LARGE_BUFF) - amount.to_edge(LEFT, buff=2) - amount.set_color(YELLOW) - - gold_copy = gold.copy() - self.play( - gold_copy.scale, 3, - gold_copy.next_to, amount, LEFT, - FadeIn(amount), - ) - self.play(Blink(you)) - self.wait() - self.play(LaggedStartMap( - FadeOutAndShift, - Group(*words, ship_title, ship, gold, gold_copy, amount), - )) - - # Levels of understanding - # Turn bubble into level points - level_points = VGroup(*[bubble.copy() for x in range(3)]) - for n, point in enumerate(level_points): - point.set_width(0.5) - point.set_height(0.5, stretch=True) - point.add(*[ - point[-1].copy().scale(1.2**k) - for k in range(1, n + 1) - ]) - point[:3].scale(1.2**n, about_point=point[3].get_center()) - point.set_stroke(width=2) - point.set_fill(opacity=0) - level_points.arrange(DOWN, buff=LARGE_BUFF) - - title = TextMobject("Levels of understanding") - title.scale(1.5) - title.to_corner(UL) - underline = Line() - underline.match_width(title) - underline.move_to(title, DOWN) - title.add(underline) - - level_points.next_to(title, DOWN, buff=1.5) - level_points.to_edge(LEFT) - level_points.set_submobject_colors_by_gradient(GREEN, YELLOW, RED) - - self.remove(bubble) - self.play( - formula.to_corner, UR, - FadeOut(you), - *[ - ReplacementTransform(bubble.copy(), point) - for point in level_points - ], - ) - self.play(Write(title, run_time=1)) - self.wait() - - # Write level 1 - level_labels = VGroup( - TextMobject("What is it saying?"), - TextMobject("Why is it true?"), - TextMobject("When is it useful?"), - ) - for lp, ll in zip(level_points, level_labels): - ll.scale(1.25) - ll.match_color(lp) - ll.next_to(lp, RIGHT) - - formula_parts = VGroup( - formula.prior, - formula.likelihood, - formula.p_evidence, - formula.posterior, - ).copy() - formula_parts.generate_target() - formula_parts.target.scale(1.5) - formula_parts.target.arrange(DOWN, buff=LARGE_BUFF, aligned_edge=LEFT) - formula_parts.target.next_to(formula, DOWN, buff=LARGE_BUFF) - formula_parts.target.shift(3 * LEFT) - - equal_signs = VGroup(*[ - TextMobject("=").next_to(fp, RIGHT) - for fp in formula_parts.target - ]) - - kw = { - "tex_to_color_map": { - "hypothesis": HYPOTHESIS_COLOR, - "evidence": EVIDENCE_COLOR1, - }, - "alignment": "", - } - meanings = VGroup( - TextMobject("Probability a hypothesis is true\\\\(before any evidence)", **kw), - TextMobject("Probability of seeing the evidence \\quad \\\\if the hypothesis is true", **kw), - TextMobject("Probability of seeing the evidence", **kw), - TextMobject("Probability a hypothesis is true\\\\given some evidence", **kw), - ) - for meaning, equals in zip(meanings, equal_signs): - meaning.scale(0.5) - meaning.next_to(equals, RIGHT) - - self.play( - FadeIn(level_labels[0], lag_ratio=0.1), - MoveToTarget(formula_parts), - LaggedStartMap(FadeInFrom, equal_signs, lambda m: (m, RIGHT)), - LaggedStartMap(FadeIn, meanings), - ) - self.wait() - - # Write level 2 - diagram = BayesDiagram(0.35, 0.5, 0.2, height=2.5) - diagram.next_to(formula, DOWN, aligned_edge=LEFT) - - braces = VGroup(*[ - Brace(diagram.he_rect, vect, buff=SMALL_BUFF) - for vect in [DOWN, LEFT] - ]) - - formula_parts.generate_target() - formula_parts.target[:2].scale(0.5) - formula_parts.target[0].next_to(braces[0], DOWN, SMALL_BUFF) - formula_parts.target[1].next_to(braces[1], LEFT, SMALL_BUFF) - - pe_picture = VGroup( - diagram.he_rect.copy(), - TexMobject("+"), - diagram.nhe_rect.copy() - ) - pe_picture.arrange(RIGHT, buff=SMALL_BUFF) - pe_picture.next_to(equal_signs[2], RIGHT) - - phe_picture = VGroup( - diagram.he_rect.copy(), - Line().match_width(pe_picture), - pe_picture.copy(), - ) - phe_picture.arrange(DOWN, buff=MED_SMALL_BUFF) - phe_picture.next_to(equal_signs[3], RIGHT) - - pe_picture.scale(0.5, about_edge=LEFT) - phe_picture.scale(0.3, about_edge=LEFT) - - self.play( - FadeOut(meanings), - FadeOut(equal_signs[:2]), - MoveToTarget(formula_parts), - FadeIn(diagram), - LaggedStartMap(GrowFromCenter, braces), - FadeIn(level_labels[1], lag_ratio=0.1), - level_labels[0].set_opacity, 0.5, - ) - self.play( - TransformFromCopy(diagram.he_rect, pe_picture[0]), - TransformFromCopy(diagram.nhe_rect, pe_picture[2]), - FadeIn(pe_picture[1]), - ) - self.play( - TransformFromCopy(pe_picture, phe_picture[2]), - TransformFromCopy(pe_picture[0], phe_picture[0]), - ShowCreation(phe_picture[1]) - ) - self.wait() - - # Write level 3 - steve = Steve(height=3) - steve.to_edge(RIGHT, buff=2) - - arrow = Arrow(level_points.get_bottom(), level_points.get_top(), buff=0) - arrow.shift(0.25 * LEFT) - - self.play( - LaggedStartMap( - FadeOutAndShift, - VGroup( - VGroup(diagram, braces, formula_parts[:2]), - VGroup(formula_parts[2], equal_signs[2], pe_picture), - VGroup(formula_parts[3], equal_signs[3], phe_picture), - ), - lambda m: (m, 3 * RIGHT), - ), - FadeIn(level_labels[2], lag_ratio=0.1), - level_labels[1].set_opacity, 0.5, - ) - self.wait() - self.play( - GrowArrow(arrow), - level_points.shift, 0.5 * RIGHT, - level_labels.shift, 0.5 * RIGHT, - level_labels.set_opacity, 1, - ) - self.wait() - self.play(Write(steve, run_time=3)) - self.wait() - - # Transition to next scene - self.play( - steve.to_corner, UR, - Uncreate(arrow), - LaggedStartMap( - FadeOutAndShift, - VGroup( - title, - formula, - *level_points, - *level_labels, - ), - lambda m: (m, DOWN), - ), - ) - self.wait() - - def get_gold(self): - gold = SVGMobject(file_name="gold_bars")[0] - gold.set_stroke(width=0) - gold.set_fill(GOLD) - gold.set_sheen(0.5, UP) - gold.flip(UP) - gold_copy = gold.copy() - gold_copy.shift(2 * OUT) - - rects = VGroup() - for curve in CurvesAsSubmobjects(gold): - p1 = curve.points[0] - p2 = curve.points[-1] - rect = Polygon(p1, p2, p2 + 2 * OUT, p1 + 2 * OUT) - rect.match_style(gold) - # rect.set_fill(GOLD) - # rect.set_sheen(1, UL) - rects.add(rect) - rects.sort(lambda p: p[1]) - gold.add(*rects) - gold.add(gold_copy) - - # gold = rects - - gold.rotate(2 * DEGREES, UP) - gold.rotate(2 * DEGREES, RIGHT) - gold.set_shade_in_3d(True) - gold.set_height(1.5) - gold.set_stroke(BLACK, 0.5) - return gold - - -class DescriptionOfSteve(Scene): - def construct(self): - self.write_description() - self.compare_probabilities() - - def write_description(self): - steve = Steve(height=3) - steve.to_corner(UR) - - description = self.get_description() - description.to_edge(LEFT) - description.align_to(steve, UP) - - mt_parts = VGroup( - description.get_part_by_tex("meek"), - description.get_part_by_tex("soul"), - ) - mt_parts.set_color(WHITE) - - self.add(steve) - self.play( - FadeIn(description), - run_time=3, - lag_ratio=0.01, - rate_func=linear, - ) - self.wait(3) - - lines = VGroup(*[ - Line(mob.get_corner(DL), mob.get_corner(DR), color=YELLOW) - for mob in mt_parts - ]) - self.play( - ShowCreation(lines), - mt_parts.set_color, YELLOW, - ) - self.play(FadeOut(lines)) - self.wait() - - def compare_probabilities(self): - bar = ProbabilityBar( - 0.5, width=10, - include_braces=True, - percentage_background_stroke_width=2, - ) - icons = VGroup( - LibrarianIcon(), - FarmerIcon(), - ) - for icon, text, half in zip(icons, ["Librarian", "Farmer"], bar.bars): - icon.set_height(0.7) - label = TextMobject(text) - label.next_to(icon, DOWN, buff=SMALL_BUFF) - label.set_color( - interpolate_color(half.get_color(), WHITE, 0.5) - ) - icon.add(label) - - bar.add_icons(*icons) - bar.move_to(1.75 * DOWN) - - bar.icons.set_opacity(0) - - q_marks = TexMobject(*"???") - q_marks.scale(1.5) - q_marks.space_out_submobjects(1.5) - q_marks.next_to(bar, DOWN) - - self.play(FadeIn(bar)) - self.wait() - self.play( - bar.p_tracker.set_value, 0.9, - bar.icons[0].set_opacity, 1, - ) - self.wait() - self.play( - bar.p_tracker.set_value, 0.1, - bar.icons[1].set_opacity, 1, - ) - self.play( - LaggedStartMap( - FadeInFrom, q_marks, - lambda m: (m, UP), - run_time=2, - ), - ApplyMethod( - bar.p_tracker.set_value, 0.7, - run_time=8, - ) - ) - for value in 0.3, 0.7: - self.play( - bar.p_tracker.set_value, 0.3, - run_time=7, - ) - - def get_description(self): - return TextMobject( - """ - Steve is very shy and withdrawn,\\\\ - invariably helpful but with very\\\\ - little interest in people or in the\\\\ - world of reality. A meek and tidy\\\\ - soul, he has a need for order and\\\\ - structure, and a passion for detail.\\\\ - """, - tex_to_color_map={ - "shy and withdrawn": BLUE, - "meek and tidy": YELLOW, - "soul": YELLOW, - }, - alignment="", - ) - - -class IntroduceKahnemanAndTversky(DescriptionOfSteve, MovingCameraScene): - def construct(self): - # Introduce K and T - images = Group( - ImageMobject("kahneman"), - ImageMobject("tversky"), - ) - danny, amos = images - images.set_height(3.5) - images.arrange(DOWN, buff=0.5) - images.to_edge(LEFT, buff=MED_LARGE_BUFF) - - names = VGroup( - TextMobject("Daniel\\\\Kahneman", alignment=""), - TextMobject("Amos\\\\Tversky", alignment=""), - ) - for name, image in zip(names, images): - name.scale(1.25) - name.next_to(image, RIGHT) - image.name = name - - prize = ImageMobject("nobel_prize", height=1) - prize.move_to(danny, UR) - prize.shift(MED_SMALL_BUFF * UR) - - books = Group( - ImageMobject("thinking_fast_and_slow"), - ImageMobject("undoing_project"), - ) - books.set_height(5) - books.arrange(RIGHT, buff=0.5) - books.to_edge(RIGHT, buff=MED_LARGE_BUFF) - - self.play( - FadeInFrom(danny, DOWN), - FadeInFrom(danny.name, LEFT), - ) - self.play( - FadeInFrom(amos, UP), - FadeInFrom(amos.name, LEFT), - ) - self.wait() - self.play(FadeInFromLarge(prize)) - self.wait() - for book in books: - self.play(FadeInFrom(book, LEFT)) - self.wait() - - # Show them thinking - for image in images: - image.generate_target() - amos.target.to_corner(DL) - danny.target.to_corner(DR) - targets = Group(amos.target, danny.target) - - bubble = ThoughtBubble( - width=7, height=4, - ) - bubble.next_to(targets, UP) - new_stem = bubble[:-1].copy() - new_stem.rotate(PI, UP, about_point=targets.get_top()) - new_stem.shift(SMALL_BUFF * DR) - bubble.add_to_back(*new_stem) - bubble[-1].scale(1.2) - bubble[-1].to_edge(UP, buff=0) - bubble[:-1].shift(DOWN) - bubble.set_fill(DARK_GREY, 1) - - randy = Randolph(color=BLUE_B) - randy.set_height(1) - randy.next_to(bubble[-1].get_center(), DL) - randy.shift(LEFT) - - lil_bubble = ThoughtBubble(height=1.5, width=2) - lil_bubble.next_to(randy, UR, buff=0) - lil_bubble[:-1].rotate( - PI, axis=UR, about_point=lil_bubble[:-1].get_corner(UL), - ) - lil_bubble.move_to(randy.get_top(), DL) - for i, part in enumerate(lil_bubble[-2::-1]): - part.rotate(90 * DEGREES) - part.shift(0.05 * i * UR) - lil_bubble[-1].scale(0.8) - - librarian = TextMobject("Librarian") - librarian.set_color(BLUE) - librarian.scale(0.5) - librarian.move_to(lil_bubble[-1]) - - bar = ProbabilityBar(percentage_background_stroke_width=1) - bar.add_icons( - LibrarianIcon(height=1), - FarmerIcon(height=1), - ) - bar.scale(0.5) - bar.next_to(randy, RIGHT, buff=0.75) - bar.update() - - self.play( - LaggedStartMap(MoveToTarget, images), - LaggedStartMap(FadeOutAndShiftDown, books), - LaggedStartMap(FadeOut, Group(prize, *names)), - ) - self.play( - DrawBorderThenFill(bubble), - FadeInFrom( - randy, UR, - rate_func=squish_rate_func(smooth, 0.5, 1), - run_time=2, - ) - ) - self.play( - DrawBorderThenFill(lil_bubble), - randy.change, "pondering", - ) - self.play(Blink(randy)) - self.play(Write(librarian)) - self.add(bar, lil_bubble, librarian) - self.play(FadeIn(bar)) - self.play( - bar.p_tracker.set_value, 1 / 6, - randy.change, "thinking", bar, - ) - self.play(Blink(randy)) - self.wait() - - # Zoom in - description = self.get_description() - description.scale(0.4) - description.next_to(randy, UL) - description.shift(1.25 * RIGHT + 0.75 * UP) - description.set_color(WHITE) - - frame = self.camera_frame - - steve = Steve() - steve.match_height(description) - steve.align_to(bar, RIGHT) - steve.align_to(description, UP) - - # cross = Cross(librarian) - - book_border = bar.icons[0].copy() - farm_border = bar.icons[1].copy() - for border in [book_border, farm_border]: - border.set_fill(opacity=0) - border.set_stroke(YELLOW, 1) - - seems_bookish = TextMobject("Seems\\\\bookish") - seems_bookish.match_width(librarian) - seems_bookish.scale(0.8) - seems_bookish.move_to(librarian) - - self.play( - frame.scale, 0.5, - frame.move_to, bubble[-1], DOWN, - frame.shift, 0.75 * LEFT, - FadeOut(bubble), - FadeOut(images), - FadeOut(lil_bubble), - FadeOut(librarian), - FadeIn(description, lag_ratio=0.05), - randy.change, "pondering", description, - run_time=6, - ) - self.play(randy.change, "happy", description) - self.play( - description.get_part_by_tex("shy").set_color, BLUE, - lag_ratio=0.1, - ) - self.play( - description.get_part_by_tex("meek").set_color, YELLOW, - description.get_part_by_tex("soul").set_color, YELLOW, - lag_ratio=0.1, - ) - self.play( - bar.p_tracker.set_value, 0.9, - FadeIn(lil_bubble), - Write(librarian), - ) - self.play(ShowCreationThenFadeOut(book_border)) - self.play(Blink(randy)) - self.play( - FadeInFromDown(steve), - randy.look_at, steve, - ) - self.play( - randy.change, "tease", steve, - FadeOut(librarian), - FadeIn(seems_bookish), - ) - lil_bubble.add(seems_bookish) - self.wait() - self.play(Blink(randy)) - - self.play( - LaggedStartMap( - FadeOutAndShift, lil_bubble, - lambda m: (m, LEFT), - run_time=1, - ), - bar.p_tracker.set_value, 1 / 6, - randy.change, "confused", bar, - ) - self.play( - ShowCreationThenFadeOut(farm_border) - ) - self.wait() - - # Transition to next scene - fh = frame.get_height() - fw = frame.get_width() - center = frame.get_center() - right = (fw / FRAME_WIDTH) * RIGHT - up = (fh / FRAME_HEIGHT) * UP - left = -right - down = -up - - book, farm = bar.icons.deepcopy() - bar.clear_updaters() - bar.icons.set_opacity(0) - - for mob in book, farm: - mob.clear_updaters() - mob.generate_target(use_deepcopy=True) - mob.target.set_height(get_norm(up)) - mob.target.move_to(center + down + 2 * left) - farm.target.shift(4 * right) - - steve.generate_target() - steve.target.match_width(book.target) - steve.target.move_to(book.target, DOWN) - steve.target.shift(3 * up) - steve_copy = steve.target.copy() - steve_copy.match_x(farm.target), - - self.play( - TransformFromCopy(steve, steve_copy), - LaggedStartMap(MoveToTarget, VGroup(steve, book, farm)), - LaggedStartMap( - FadeOutAndShift, - description, - lambda m: (m, LEFT) - ), - FadeOutAndShift(randy, LEFT), - FadeOutAndShift(bar, LEFT), - ) - - -class CorrectViewOfFarmersAndLibrarians(Scene): - def construct(self): - # Match last scene - steves = VGroup(Steve(), Steve()) - book = LibrarianIcon() - farm = FarmerIcon() - icons = VGroup(book, farm) - - for mob in icons: - mob.set_height(1) - mob.move_to(DOWN + 2 * LEFT) - farm.shift(4 * RIGHT) - - steves.match_width(book) - steves.move_to(book, DOWN) - steves.shift(3 * UP) - steve1, steve2 = steves - steve2.match_x(farm) - - self.add(steves, book, farm) - - # Add arrows - arrows = VGroup(*[ - Arrow(s.get_bottom(), m.get_top()) - for s, m in zip(steves, icons) - ]) - words = VGroup( - TextMobject("Stereotype"), - TextMobject("Unexpected"), - ) - for arrow, word, vect in zip(arrows, words, [LEFT, RIGHT]): - word.scale(1.5) - word.next_to(arrow, vect) - self.play( - GrowArrow(arrow), - FadeInFrom(word, UP), - ) - self.wait() - - # Show people proportions - librarian = Librarian() - farmer = Farmer() - - librarian.move_to(LEFT).to_edge(UP) - farmer.move_to(RIGHT).to_edge(UP) - farmer_ul = farmer.get_corner(UL) - - farmers = VGroup(farmer, *[farmer.copy() for x in range(19)]) - farmers.arrange_in_grid(n_rows=4) - farmers.move_to(farmer_ul, UL) - - farmer_outlines = farmers.copy() - farmer_outlines.set_fill(opacity=0) - farmer_outlines.set_stroke(YELLOW, 1) - - farmer_count = Integer(1) - farmer_count.scale(2) - farmer_count.set_color(GREEN) - farmer_count.next_to(farmers, LEFT, buff=LARGE_BUFF) - farmer_count.add_updater(lambda m: m.set_value(len(farmer_outlines))) - - for person, icon in zip([librarian, farmer], icons): - person.save_state() - person[:-1].set_opacity(0) - person.scale( - icon.get_height() / person[-1].get_height() - ) - person.move_to(icon, DR) - - self.remove(*icons) - self.play( - LaggedStartMap(FadeOut, VGroup(steves, arrows, words)), - Restore(librarian), - Restore(farmer), - run_time=1, - ) - self.play( - LaggedStartMap(FadeIn, farmers[1:]) - ) - self.wait() - self.add(farmer_count) - self.play( - ShowIncreasingSubsets(farmer_outlines), - int_func=np.ceil, - rate_func=linear, - run_time=2, - ) - self.play(FadeOut(farmer_outlines)) - self.wait() - - # Show higher number of farmers - farmers.save_state() - farmers.generate_target() - farmers.target.scale(0.5, about_edge=UL) - new_farmers = VGroup(*it.chain(*[ - farmers.target.copy().next_to( - farmers.target, vect, buff=SMALL_BUFF, - ) - for vect in [RIGHT, DOWN] - ])) - new_farmers[-10:].align_to(new_farmers, RIGHT) - new_farmers[-10:].align_to(new_farmers[-20], UP) - - farmer_count.clear_updaters() - self.play( - MoveToTarget(farmers), - ShowIncreasingSubsets(new_farmers), - ChangeDecimalToValue(farmer_count, 60), - ) - self.wait() - self.play( - FadeOut(new_farmers), - Restore(farmers), - ChangeDecimalToValue(farmer_count, 20), - ) - self.wait() - - # Organize into a representative sample - farmers.generate_target() - librarian.generate_target() - group = VGroup(librarian.target, *farmers.target) - self.arrange_bottom_row(group) - - l_brace = self.get_count_brace(VGroup(librarian.target)) - f_brace = self.get_count_brace(farmers.target) - - self.play( - MoveToTarget(librarian), - MoveToTarget(farmers), - ReplacementTransform(farmer_count, f_brace[-1]), - GrowFromCenter(f_brace[:-1]), - ) - self.play(GrowFromCenter(l_brace)) - self.wait() - - def get_count_brace(self, people): - brace = Brace(people, UP, buff=SMALL_BUFF) - count = Integer(len(people), edge_to_fix=DOWN) - count.next_to(brace, UP, SMALL_BUFF) - brace.add(count) - brace.count = count - return brace - - def arrange_bottom_row(self, group): - group.arrange(RIGHT, buff=0.5) - group.set_width(FRAME_WIDTH - 3) - group.to_edge(DOWN) - for person in group: - person[-1].set_stroke(BLACK, 1, background=True) - - -class ComplainAboutNotKnowingTheStats(TeacherStudentsScene): - def construct(self): - self.student_says( - "Are people expected\\\\to know that?", - student_index=2 - ) - self.change_student_modes( - "sassy", "sassy", - ) - self.play(self.teacher.change, "hesitant") - self.look_at(self.screen) - self.wait(3) - self.teacher_says( - "No, but did you\\\\think to estimate it?", - bubble_kwargs={"width": 4.5, "height": 3.5}, - ) - self.change_all_student_modes("guilty") - self.wait(2) - self.change_all_student_modes("pondering") - self.wait(3) - - -class SpoilerAlert(Scene): - def construct(self): - sa_words = TextMobject("Spoiler Alert") - sa_words.scale(2) - sa_words.to_edge(UP) - sa_words.set_color(RED) - - alert = Triangle(start_angle=90 * DEGREES) - alert.set_stroke(RED, 8) - alert.set_height(sa_words.get_height()) - alert.round_corners(0.1) - bang = TextMobject("!") - bang.set_color(RED) - bang.scale(1.5) - bang.move_to(alert) - alert.add(bang) - - alert.next_to(sa_words, LEFT) - sa_words.add(alert.copy()) - alert.next_to(sa_words, RIGHT) - sa_words.add(alert) - - formula = get_bayes_formula() - formula_words = TextMobject("This is secretly ") - formula_words.scale(1.5) - formula_group = VGroup(formula_words, formula) - formula_group.arrange(DOWN, buff=MED_LARGE_BUFF) - formula_group.next_to(sa_words, DOWN, LARGE_BUFF) - - self.add(sa_words) - self.wait() - self.play(FadeInFrom(formula_group, UP)) - self.wait() - - -class ReasonByRepresentativeSample(CorrectViewOfFarmersAndLibrarians): - CONFIG = { - "ignore_icons": False, - # "ignore_icons": True, - } - - def construct(self): - # Match previous scene - librarians = VGroup(Librarian()) - farmers = VGroup(*[Farmer() for x in range(20)]) - everyone = VGroup(*librarians, *farmers) - self.arrange_bottom_row(everyone) - self.add(everyone) - - if self.ignore_icons: - for person in everyone: - person.submobjects.pop() - - l_brace = self.get_count_brace(librarians) - f_brace = self.get_count_brace(farmers) - braces = VGroup(l_brace, f_brace) - for brace in braces: - brace.count.set_stroke(BLACK, 3, background=True) - brace.count.brace = brace - brace.remove(brace.count) - brace.count_tracker = ValueTracker(brace.count.get_value()) - brace.count.add_updater( - lambda c: c.set_value( - c.brace.count_tracker.get_value(), - ).next_to(c.brace, UP, SMALL_BUFF) - ) - self.add(brace, brace.count) - - # Multiply by 10 - new_people = VGroup() - for group in [librarians, farmers]: - new_rows = VGroup(*[group.copy() for x in range(9)]) - new_rows.arrange(UP, buff=SMALL_BUFF) - new_rows.next_to(group, UP, SMALL_BUFF) - new_people.add(new_rows) - new_librarians, new_farmers = new_people - - self.play( - *[ - FadeIn(new_rows, lag_ratio=0.1) - for new_rows in new_people - ], - *[ - ApplyMethod(brace.next_to, new_rows, UP, {"buff": SMALL_BUFF}) - for brace, new_rows in zip(braces, new_people) - ], - *[ - ApplyMethod( - brace.count_tracker.set_value, - 10 * brace.count_tracker.get_value(), - ) - for brace in braces - ], - ) - - farmers = VGroup(farmers, *new_farmers) - librarians = VGroup(librarians, *new_librarians) - everyone = VGroup(farmers, librarians) - - # Add background rectangles - big_rect = SurroundingRectangle( - everyone, - buff=0.05, - stroke_width=1, - stroke_color=WHITE, - fill_opacity=1, - fill_color=DARKER_GREY, - ) - left_rect = big_rect.copy() - prior = 1 / 21 - left_rect.stretch(prior, 0, about_edge=LEFT) - right_rect = big_rect.copy() - right_rect.stretch(1 - prior, 0, about_edge=RIGHT) - - dl_rect = left_rect.copy() - ul_rect = left_rect.copy() - dl_rect.stretch(0.4, 1, about_edge=DOWN) - ul_rect.stretch(0.6, 1, about_edge=UP) - - dr_rect = right_rect.copy() - ur_rect = right_rect.copy() - dr_rect.stretch(0.1, 1, about_edge=DOWN) - ur_rect.stretch(0.9, 1, about_edge=UP) - - colors = [ - interpolate_color(color, BLACK, 0.5) - for color in [EVIDENCE_COLOR1, EVIDENCE_COLOR2] - ] - for rect, color in zip([dl_rect, dr_rect], colors): - rect.set_fill(color) - - all_rects = VGroup( - left_rect, right_rect, - dl_rect, ul_rect, dr_rect, ur_rect, - ) - all_rects.set_opacity(0) - - self.add(all_rects, everyone) - self.play( - left_rect.set_opacity, 1, - right_rect.set_opacity, 1, - ) - self.wait() - - # 40% of librarians and 10% of farmers - for rect, vect in zip([dl_rect, dr_rect], [LEFT, RIGHT]): - rect.set_opacity(1) - rect.save_state() - rect.brace = Brace(rect, vect, buff=SMALL_BUFF) - rect.brace.save_state() - - rect.number = Integer(0, unit="\\%") - rect.number.scale(0.75) - rect.number.next_to(rect.brace, vect, SMALL_BUFF) - rect.number.brace = rect.brace - rect.number.vect = vect - - rect.number.add_updater( - lambda d: d.set_value(100 * d.brace.get_height() / big_rect.get_height()) - ) - rect.number.add_updater(lambda d: d.next_to(d.brace, d.vect, SMALL_BUFF)) - - for mob in [rect, rect.brace]: - mob.stretch(0, 1, about_edge=DOWN) - - for rect, to_fade in [(dl_rect, librarians[4:]), (dr_rect, farmers[1:])]: - self.add(rect.brace, rect.number) - self.play( - Restore(rect), - Restore(rect.brace), - to_fade.set_opacity, 0.1, - ) - self.wait() - - # Emphasize restricted set - highlighted_librarians = librarians[:4].copy() - highlighted_farmers = farmers[0].copy() - for highlights in [highlighted_librarians, highlighted_farmers]: - highlights.set_color(YELLOW) - - self.add(braces, *[b.count for b in braces]) - self.play( - l_brace.next_to, librarians[:4], UP, SMALL_BUFF, - l_brace.count_tracker.set_value, 4, - ShowIncreasingSubsets(highlighted_librarians) - ) - self.play(FadeOut(highlighted_librarians)) - self.play( - f_brace.next_to, farmers[:1], UP, SMALL_BUFF, - f_brace.count_tracker.set_value, 20, - ShowIncreasingSubsets(highlighted_farmers), - run_time=2, - ) - self.play(FadeOut(highlighted_farmers)) - self.wait() - - # Write answer - equation = TexMobject( - "P\\left(", - "\\text{Librarian }", - "\\text{given }", - "\\text{description}", - "\\right)", - "=", - "{4", "\\over", " 4", "+", "20}", - "\\approx", "16.7\\%", - ) - equation.set_color_by_tex_to_color_map({ - "Librarian": HYPOTHESIS_COLOR, - "description": EVIDENCE_COLOR1, - }) - - equation.set_width(FRAME_WIDTH - 2) - equation.to_edge(UP) - equation_rect = BackgroundRectangle(equation, buff=MED_SMALL_BUFF) - equation_rect.set_fill(opacity=1) - equation_rect.set_stroke(WHITE, width=1, opacity=1) - - self.play( - FadeIn(equation_rect), - FadeInFromDown(equation[:6]) - ) - self.wait() - self.play( - TransformFromCopy( - l_brace.count, - equation.get_parts_by_tex("4")[0], - ), - Write(equation.get_part_by_tex("\\over")), - ) - self.play( - Write(equation.get_part_by_tex("+")), - TransformFromCopy( - f_brace.count, - equation.get_part_by_tex("20"), - ), - TransformFromCopy( - l_brace.count, - equation.get_parts_by_tex("4")[1], - ), - ) - - self.wait() - self.play(FadeIn(equation[-2:])) - self.wait() - - # Compare raw likelihoods - axes = Axes( - x_min=0, - x_max=10, - y_min=0, - y_max=100, - y_axis_config={ - "unit_size": 0.07, - "tick_frequency": 10, - }, - axis_config={ - "include_tip": False, - }, - ) - axes.x_axis.tick_marks.set_opacity(0) - axes.y_axis.add_numbers( - *range(20, 120, 20), - number_config={"unit": "\\%"} - ) - axes.center().to_edge(DOWN) - - title = TextMobject("Likelihood of fitting the description") - title.scale(1.25) - title.to_edge(UP) - title.shift(RIGHT) - axes.add(title) - - lines = VGroup( - Line(axes.c2p(3, 0), axes.c2p(3, 40)), - Line(axes.c2p(7, 0), axes.c2p(7, 10)), - ) - rects = VGroup(*[Rectangle() for x in range(2)]) - rects.set_fill(EVIDENCE_COLOR1, 1) - rects[1].set_fill(GREEN) - rects.set_stroke(WHITE, 1) - icons = VGroup(LibrarianIcon(), FarmerIcon()) - - for rect, line, icon in zip(rects, lines, icons): - rect.replace(line, dim_to_match=1) - rect.set_width(1, stretch=True) - icon.set_width(1) - icon.next_to(rect, UP) - y = axes.y_axis.p2n(rect.get_top()) - y_point = axes.y_axis.n2p(y) - rect.line = DashedLine(y_point, rect.get_corner(UL)) - - pre_rects = VGroup() - for r in dl_rect, dr_rect: - r_copy = r.deepcopy() - pre_rects.add(r_copy) - - people_copy = VGroup(librarians[:4], farmers[:1]).copy() - everything = self.get_mobjects() - - self.play( - *[FadeOut(mob) for mob in everything], - FadeIn(axes), - FadeIn(icons), - *[ - TransformFromCopy(pr, r) - for pr, r in zip(pre_rects, rects) - ], - FadeOut(people_copy), - ) - self.play(*[ - ShowCreation(rect.line) - for rect in rects - ]) - self.wait() - self.play( - FadeOut(axes), - FadeOut(rects[0].line), - FadeOut(rects[1].line), - FadeOut(icons), - *[ - ReplacementTransform(r, pr) - for pr, r in zip(pre_rects, rects) - ], - # FadeOut(fsfr), - *[FadeIn(mob) for mob in everything], - ) - self.remove(*pre_rects) - self.wait() - - # Emphasize prior belief - prior_equation = TexMobject( - "P\\left(", - "\\text{Librarian}", - "\\right)", - "=", - "{1", "\\over", "21}", - "\\approx", "4.8\\%", - ) - prior_equation.set_color_by_tex_to_color_map({ - "Librarian": HYPOTHESIS_COLOR, - }) - - prior_equation.match_height(equation) - - prior_rect = BackgroundRectangle(prior_equation, buff=MED_SMALL_BUFF) - prior_rect.match_style(equation_rect) - - group = VGroup(prior_equation, prior_rect) - group.align_to(equation_rect, UP) - group.shift( - ( - equation.get_part_by_tex("\\over").get_x() - - prior_equation.get_part_by_tex("\\over").get_x() - ) * RIGHT - ) - - prior_label = TextMobject("Prior belief") - prior_label.scale(1.5) - prior_label.next_to(prior_rect, LEFT, buff=1.5) - prior_label.to_edge(UP, buff=0.25) - prior_label.set_stroke(BLACK, 5, background=True) - prior_arrow = Arrow( - prior_label.get_right(), - prior_equation.get_left(), - buff=SMALL_BUFF, - ) - - self.play( - VGroup(equation_rect, equation).shift, prior_rect.get_height() * DOWN, - FadeIn(prior_rect), - FadeIn(prior_equation), - FadeInFrom(prior_label, RIGHT), - GrowArrow(prior_arrow), - ) - self.wait() - - -class NewEvidenceUpdatesPriorBeliefs(DescriptionOfSteve): - def construct(self): - # Determining belief in a vacuum - description = self.get_description() - rect = SurroundingRectangle(description) - rect.set_stroke(WHITE, 2) - rect.set_fill(BLACK, 0.9) - evid = VGroup(rect, description) - evid.set_height(2) - - librarian = Librarian() - librarian.set_height(2) - - arrow = Arrow(LEFT, RIGHT) - arrow.set_stroke(WHITE, 5) - - group = VGroup(evid, arrow, librarian) - group.arrange(RIGHT, buff=LARGE_BUFF) - - cross = Cross(VGroup(group)) - cross.set_stroke(RED, 12) - cross.scale(1.2) - - self.add(evid) - self.play( - GrowArrow(arrow), - FadeInFrom(librarian, LEFT) - ) - self.play(ShowCreation(cross)) - self.wait() - - # - icons = VGroup(LibrarianIcon(), FarmerIcon()) - for icon in icons: - icon.set_height(0.5) - - kw = { - "include_braces": True, - "width": 11, - "height": 0.75, - } - top_bar = ProbabilityBar(p=1 / 21, **kw) - low_bar = ProbabilityBar(p=1 / 21, brace_direction=DOWN, **kw) - - bars = VGroup(top_bar, low_bar) - for bar in bars: - bar.percentages[1].add_updater(lambda m: m.set_opacity(0)) - bar.add_icons(*icons.copy()) - bar.suspend_updating() - - new_arrow = Arrow(1.5 * UP, 1.5 * DOWN) - new_arrow.set_stroke(WHITE, 5) - - top_bar.next_to(new_arrow, UP) - low_bar.next_to(new_arrow, DOWN) - - self.add(arrow, evid, cross) - self.play( - FadeOut(cross), - Transform(arrow, new_arrow), - evid.scale, 0.6, - evid.move_to, new_arrow, - ReplacementTransform(librarian, low_bar.icons[0]), - FadeIn(bars) - ) - self.play(low_bar.p_tracker.set_value, 1 / 6) - self.wait() - - -class HeartOfBayesTheorem(Scene): - def construct(self): - title = TextMobject("Heart of Bayes' theorem") - title.scale(1.5) - title.add(Underline(title)) - title.to_edge(UP) - - # Bayes diagrams - # prior_tracker = ValueTracker(0.1) - # likelihood_tracker = ValueTracker(0.4) - # antilikelihood_tracker = ValueTracker(0.1) - diagram = self.get_diagram( - prior=0.1, likelihood=0.4, antilikelihood=0.1, - # prior_tracker.get_value(), - # likelihood_tracker.get_value(), - # antilikelihood_tracker.get_value(), - ) - diagram.nh_rect.set_fill(GREEN_E) - diagram.hypothesis_split.set_opacity(1) - diagram.evidence_split.set_opacity(0) - restricted_diagram = self.get_restricted_diagram(diagram) - diagram_copy = diagram.copy() - diagram_copy.move_to(restricted_diagram) - - diagrams = VGroup(diagram, restricted_diagram) - - label1 = TextMobject("All possibilities") - label2 = TextMobject( - "All possibilities\\\\", "fitting the evidence", - tex_to_color_map={"evidence": EVIDENCE_COLOR1}, - ) - labels = VGroup(label1, label2) - labels.scale(diagram.get_width() / label1.get_width()) - - for l, d in zip(labels, diagrams): - l.next_to(d, UP) - - label2.save_state() - label2[0].move_to(label2, DOWN) - label2[1:].shift(0.25 * UP) - label2[1:].set_opacity(0) - - # Final fraction written geometrically - fraction = always_redraw( - lambda: self.get_geometric_fraction(restricted_diagram) - ) - frac_box = always_redraw(lambda: DashedVMobject( - SurroundingRectangle( - fraction, - buff=MED_SMALL_BUFF, - stroke_width=2, - stroke_color=WHITE, - ), - num_dashes=100, - )) - prob = TexMobject( - "P\\left(", - "{\\text{Librarian }", - "\\text{given}", "\\over", "\\text{the evidence}}", - "\\right)" - ) - prob.set_color_by_tex("Librarian", HYPOTHESIS_COLOR) - prob.set_color_by_tex("evidence", EVIDENCE_COLOR1) - prob.get_part_by_tex("\\over").set_opacity(0) - prob.match_width(frac_box) - prob.next_to(frac_box, UP) - - updaters = VGroup( - diagram, restricted_diagram, fraction, frac_box - ) - updaters.suspend_updating() - - self.play( - FadeIn(diagram), - FadeInFromDown(label1), - ) - self.play( - TransformFromCopy(diagram, diagram_copy), - TransformFromCopy(label1[0], label2[0]), - ) - self.play( - Restore(label2), - ReplacementTransform(diagram_copy, restricted_diagram) - ) - self.wait() - - self.play( - TransformFromCopy( - restricted_diagram.he_rect, - fraction[0], - ), - TransformFromCopy( - restricted_diagram.he_rect, - fraction[2][0], - ), - TransformFromCopy( - restricted_diagram.nhe_rect, - fraction[2][2], - ), - ShowCreation(fraction[1]), - Write(fraction[2][1]), - ) - self.add(fraction) - self.play( - ShowCreation(frac_box), - FadeIn(prob) - ) - self.wait() - - self.play(Write(title, run_time=1)) - self.wait() - - # Mess with some numbers - updaters.resume_updating() - self.play(*[ - ApplyMethod(d.set_prior, 0.4, run_time=2) - for d in diagrams - ]) - self.play(*[ - ApplyMethod(d.set_likelihood, 0.3, run_time=2) - for d in diagrams - ]) - self.play(*[ - ApplyMethod(d.set_antilikelihood, 0.6, run_time=2) - for d in diagrams - ]) - # self.play(prior_tracker.set_value, 0.4, run_time=2) - # self.play(antilikelihood_tracker.set_value, 0.3, run_time=2) - # self.play(likelihood_tracker.set_value, 0.6, run_time=2) - self.wait() - updaters.suspend_updating() - - # Ask about a formula - words = TextMobject("Write this more\\\\mathematically") - words.scale(1.25) - words.set_color(RED) - words.to_corner(UR) - arrow = Arrow(words.get_bottom(), frac_box.get_top(), buff=SMALL_BUFF) - arrow.match_color(words) - arrow.set_stroke(width=5) - - self.play( - FadeInFrom(words, DOWN), - GrowArrow(arrow), - FadeOut(prob), - title.to_edge, LEFT - ) - self.wait() - - def get_diagram(self, prior, likelihood, antilikelihood): - diagram = BayesDiagram( - prior, likelihood, antilikelihood, - not_evidence_color1=GREY, - not_evidence_color2=GREEN_E, - ) - diagram.set_height(3) - diagram.move_to(5 * LEFT + DOWN) - - diagram.add_brace_attrs() - braces = VGroup(diagram.h_brace, diagram.nh_brace) - diagram.add(*braces) - icons = VGroup(LibrarianIcon(), FarmerIcon()) - icons[0].set_color(YELLOW_D) - for icon, brace in zip(icons, braces): - icon.set_height(0.5) - icon.brace = brace - icon.add_updater(lambda m: m.next_to(m.brace, DOWN, SMALL_BUFF)) - diagram.add(icon) - return diagram - - def get_restricted_diagram(self, diagram): - restricted_diagram = diagram.deepcopy() - restricted_diagram.set_x(0) - restricted_diagram.hypothesis_split.set_opacity(0) - restricted_diagram.evidence_split.set_opacity(1) - restricted_diagram.hne_rect.set_opacity(0.1) - restricted_diagram.nhne_rect.set_opacity(0.1) - return restricted_diagram - - def get_geometric_fraction(self, diagram): - fraction = VGroup( - diagram.he_rect.copy(), - Line(LEFT, RIGHT), - VGroup( - diagram.he_rect.copy(), - TexMobject("+"), - diagram.nhe_rect.copy(), - ).arrange(RIGHT, buff=SMALL_BUFF) - ) - fraction.arrange(DOWN) - fraction[1].match_width(fraction) - fraction.to_edge(RIGHT) - fraction.align_to(diagram.square, UP) - return fraction - - -class WhenDoesBayesApply(DescriptionOfSteve): - def construct(self): - title = TextMobject("When to use Bayes' rule") - title.add(Underline(title, buff=-SMALL_BUFF)) - title.scale(1.5) - title.to_edge(UP) - self.add(title) - - # Words - all_words = VGroup( - TextMobject("You have a\\\\", "hypothesis"), - TextMobject("You've observed\\\\some ", "evidence"), - TexMobject( - "\\text{You want}\\\\", - "P", "(", "H", "|", "E", ")\\\\", - "P", "\\left(", - "\\substack{""\\text{Hypothesis} \\\\", - "\\textbf{given} \\\\", - "\\, \\text{the evidence} \\,}", - "\\right)", - ), - ) - for words in all_words: - words.set_color_by_tex_to_color_map({ - "hypothesis": HYPOTHESIS_COLOR, - "H": HYPOTHESIS_COLOR, - "evidence": EVIDENCE_COLOR1, - "E": EVIDENCE_COLOR1, - }) - - goal = all_words[2] - prob = goal[1:7] - big_prob = goal[7:] - for mob in [prob, big_prob]: - mob.match_x(goal[0]) - prob.shift(0.2 * DOWN) - big_prob.shift(0.4 * DOWN) - VGroup(big_prob[1], big_prob[-1]).stretch(1.2, 1) - - all_words.arrange(RIGHT, buff=2, aligned_edge=UP) - all_words.next_to(title, DOWN, buff=MED_LARGE_BUFF) - - big_prob.save_state() - big_prob.move_to(prob, UP) - - # Icons - hypothesis_icon = self.get_hypothesis_icon() - evidence_icon = self.get_evidence_icon() - - hypothesis_icon.next_to(all_words[0], DOWN, LARGE_BUFF) - evidence_icon.next_to(all_words[1], DOWN, LARGE_BUFF) - - # Show icons - self.play(FadeInFromDown(all_words[0])) - self.play( - LaggedStart( - FadeInFrom(hypothesis_icon[0], DOWN), - Write(hypothesis_icon[1]), - FadeInFrom(hypothesis_icon[2], UP), - run_time=1, - ) - ) - self.wait() - - self.play(FadeInFromDown(all_words[1])) - self.play( - FadeIn(evidence_icon), - lag_ratio=0.1, - run_time=2, - ) - self.wait() - - # More compact probability - self.play(FadeInFromDown(VGroup(goal[0], big_prob))) - self.wait() - self.play( - Restore(big_prob), - *[ - TransformFromCopy(big_prob[i], prob[i]) - for i in [0, 1, -1] - ] - ) - self.play(LaggedStart(*[ - TransformFromCopy(big_prob[i][j], prob[i]) - for i, j in [(2, 0), (3, 1), (4, 3)] - ])) - self.wait() - - # Highlight "given" - rects = VGroup(*[ - SurroundingRectangle( - goal.get_part_by_tex(tex), - buff=0.05, - stroke_width=2, - stroke_color=RED, - ) - for tex in ("|", "given") - ]) - - self.play(ShowCreation(rects)) - self.play(Transform(rects[0].copy(), rects[1], remover=True)) - self.play(FadeOut(rects)) - self.wait() - - self.remove(prob) - everything = Group(*self.get_mobjects()) - self.play( - LaggedStartMap(FadeOut, everything, run_time=2), - prob.copy().to_corner, UR, - ) - - def get_hypothesis_icon(self): - group = VGroup( - Steve().set_height(1.5), - TexMobject("\\updownarrow"), - LibrarianIcon().set_color(YELLOW_D) - ) - group.arrange(DOWN) - return group - - def get_evidence_icon(self): - result = self.get_description() - result.scale(0.5) - result.set_color_by_tex("meek", EVIDENCE_COLOR1) - result.set_color_by_tex("soul", EVIDENCE_COLOR1) - rect = SurroundingRectangle(result) - rect.set_stroke(WHITE, 2) - result.add(rect) - return result - - -class CreateFormulaFromDiagram(Scene): - CONFIG = { - "tex_to_color_map": { - "P": WHITE, - "H": HYPOTHESIS_COLOR, - "E": EVIDENCE_COLOR1, - "\\neg": RED, - }, - "bayes_diagram_config": { - "prior": 1 / 21, - "likelihood": 0.4, - "antilikelihood": 0.1, - "not_evidence_color1": GREY, - "not_evidence_color2": DARK_GREY, - "prior_rect_direction": UP, - }, - } - - def construct(self): - t2c = self.tex_to_color_map - - # Add posterior - posterior = TexMobject("P(H|E)", tex_to_color_map=t2c) - posterior.to_corner(UR) - posterior_words = TextMobject("Goal: ") - posterior_words.next_to(posterior, LEFT, aligned_edge=UP) - self.add(posterior) - self.add(posterior_words) - - # Show prior - diagram = self.get_diagram() - - prior_label = TexMobject("P(H)", tex_to_color_map=t2c) - prior_label.add_updater( - lambda m: m.next_to(diagram.h_brace, UP, SMALL_BUFF) - ) - - prior_example = TexMobject("= 1 / 21") - prior_example.add_updater( - lambda m: m.next_to(prior_label, RIGHT).shift(0.03 * UP) - - ) - # example_words = TextMobject("In our example") - # example_words.next_to(prior_example[0][1:], UP, buff=SMALL_BUFF, aligned_edge=LEFT) - # prior_example.add(example_words) - - prior_arrow = Vector(0.7 * RIGHT) - prior_arrow.next_to(prior_label, LEFT, SMALL_BUFF) - prior_word = TextMobject("``Prior''") - prior_word.next_to(prior_arrow, LEFT, SMALL_BUFF) - prior_word.align_to(prior_label[0], DOWN) - prior_word.set_color(HYPOTHESIS_COLOR) - - self.add(diagram) - self.play(ShowIncreasingSubsets(diagram.people, run_time=2)) - self.wait() - self.play( - diagram.hypothesis_split.set_opacity, 1, - FadeIn(diagram.h_brace), - FadeInFromDown(prior_label), - ) - self.wait() - self.play(FadeIn(prior_example)) - self.play( - LaggedStartMap( - Indicate, diagram.people[::10], - color=BLUE, - ) - ) - self.wait() - self.play( - FadeInFrom(prior_word, RIGHT), - GrowArrow(prior_arrow) - ) - self.wait() - prior_group = VGroup( - prior_word, prior_arrow, - prior_label, prior_example, - diagram.h_brace, - ) - - # First likelihood split - like_example = TexMobject("= 0.4") - like_example.add_updater( - lambda m: m.next_to(diagram.he_brace, LEFT) - ) - like_example.update() - - like_label = TexMobject("P(E|H)", tex_to_color_map=t2c) - like_label.add_updater( - lambda m: m.next_to(like_example, LEFT) - ) - like_label.update() - - like_word = TextMobject("``Likelihood''") - like_word.next_to(like_label, UP, buff=LARGE_BUFF, aligned_edge=LEFT) - like_arrow = Arrow( - like_word.get_bottom(), - like_label.get_top(), - buff=0.2, - ) - - limit_arrow = Vector(0.5 * UP) - limit_arrow.next_to(like_label.get_part_by_tex("|"), UP, SMALL_BUFF) - limit_arrow.set_stroke(WHITE, 4) - limit_word = TextMobject("Limit\\\\your\\\\view") - limit_word.next_to(limit_arrow, UP) - - hne_people = diagram.people[:6] - he_people = diagram.people[6:10] - nhe_people = diagram.people[19::10] - nhne_people = diagram.people[10:] - nhne_people.remove(*nhe_people) - - for group in [hne_people, nhne_people]: - group.generate_target() - group.target.set_opacity(0.25) - group.target.set_stroke(BLACK, 0, background=True) - - self.play( - diagram.he_rect.set_opacity, 1, - diagram.hne_rect.set_opacity, 1, - MoveToTarget(hne_people), - GrowFromCenter(diagram.he_brace), - FadeInFrom(like_label, RIGHT), - FadeInFrom(like_example, RIGHT), - ) - self.wait() - self.play( - ShowCreationThenFadeAround( - like_label.get_part_by_tex("E"), - surrounding_rectangle_config={ - "color": EVIDENCE_COLOR1 - } - ), - ) - self.play(WiggleOutThenIn(like_label.get_part_by_tex("|"))) - self.play( - ShowCreationThenFadeAround( - like_label.get_part_by_tex("H") - ), - ) - self.wait() - self.play( - diagram.people[10:].set_opacity, 0.2, - diagram.nh_rect.set_opacity, 0.2, - FadeInFrom(limit_word, DOWN), - GrowArrow(limit_arrow), - rate_func=there_and_back_with_pause, - run_time=6, - ) - self.wait() - self.play( - Write(like_word, run_time=1), - GrowArrow(like_arrow), - ) - self.wait() - - # Show anti-likelihood - anti_label = TexMobject("P(E| \\neg H)", tex_to_color_map=t2c) - anti_label.add_updater( - lambda m: m.next_to(diagram.nhe_brace, RIGHT) - ) - - anti_example = TexMobject("= 0.1") - anti_example.add_updater( - lambda m: m.next_to(anti_label, RIGHT).align_to(anti_label[0], DOWN) - ) - - neg_sym = anti_label.get_part_by_tex("\\neg").copy() - neg_sym.generate_target() - neg_sym.target.scale(2.5) - not_word = TextMobject("means ", "``not''") - not_word[1].set_color(RED) - neg_group = VGroup(neg_sym.target, not_word) - neg_group.arrange(RIGHT) - neg_group.next_to(anti_label, UP, LARGE_BUFF) - neg_group.to_edge(RIGHT, buff=MED_SMALL_BUFF) - - diagram.nhe_rect.set_opacity(1) - diagram.nhe_rect.save_state() - diagram.nhe_rect.become(diagram.nh_rect) - self.play( - Restore(diagram.nhe_rect), - GrowFromCenter(diagram.nhe_brace), - MoveToTarget(nhne_people), - FadeInFrom(anti_label, LEFT), - FadeInFrom(anti_example, LEFT), - ) - diagram.nhne_rect.set_opacity(1) - self.wait() - self.play( - ShowCreationThenFadeAround( - anti_label[2], - surrounding_rectangle_config={"color": EVIDENCE_COLOR1}, - ) - ) - self.play( - ShowCreationThenFadeAround( - anti_label[4:6], - surrounding_rectangle_config={"color": RED}, - ) - ) - self.wait() - self.play( - MoveToTarget(neg_sym), - FadeIn(not_word) - ) - self.wait() - self.play( - FadeOut(not_word), - Transform(neg_sym, anti_label.get_part_by_tex("\\neg")) - ) - self.remove(neg_sym) - - # Recall final answer, geometrically - he_group = VGroup(diagram.he_rect, he_people) - nhe_group = VGroup(diagram.nhe_rect, nhe_people) - - denom = VGroup( - he_group.copy(), - TexMobject("+"), - nhe_group.copy(), - ) - denom.arrange(RIGHT) - answer = VGroup( - he_group.copy(), - Underline(denom, stroke_width=2), - denom, - ) - answer.arrange(DOWN) - answer.scale(0.5) - answer.to_edge(UP, MED_SMALL_BUFF) - answer.shift(LEFT) - - equals = TexMobject("=") - posterior.generate_target() - - post_group = VGroup(posterior.target, equals, answer) - post_group.arrange(RIGHT) - post_group.to_corner(UL) - - post_word = TextMobject("``Posterior''") - post_word.set_color(YELLOW) - post_word.to_corner(UL, buff=MED_SMALL_BUFF) - - post_arrow = Arrow( - post_word.get_bottom(), - posterior.target[1].get_top(), - buff=0.2, - ) - post_arrow.set_stroke(WHITE, 5) - - dividing_line = DashedLine(ORIGIN, FRAME_WIDTH * RIGHT) - dividing_line.set_stroke(WHITE, 1) - dividing_line.next_to(answer, DOWN) - dividing_line.set_x(0) - - diagram.add( - diagram.h_brace, - diagram.he_brace, - diagram.nhe_brace, - ) - diagram.generate_target(use_deepcopy=True) - diagram.target.scale(0.5, about_edge=DL) - diagram.target.refresh_braces() - - like_group = VGroup(like_word, like_arrow) - like_vect = like_group.get_center() - like_label.get_center() - self.play( - ShowCreation(dividing_line), - MoveToTarget(diagram), - MaintainPositionRelativeTo(like_group, like_label), - MaintainPositionRelativeTo( - VGroup(prior_word, prior_arrow), - prior_label, - ), - MoveToTarget(posterior), - ReplacementTransform(posterior_words, equals), - ) - like_group.move_to(like_label.get_center() + like_vect) - self.wait() - self.play(TransformFromCopy(he_group, answer[0])) - self.play(ShowCreation(answer[1])) - self.play(LaggedStart( - TransformFromCopy(he_group, answer[2][0]), - GrowFromCenter(answer[2][1]), - TransformFromCopy(nhe_group, answer[2][2]), - )) - self.wait() - - # Write final answer, as a formula - formula = TexMobject( - "=", "{P(H) P(E | H)", "\\over", - "P(H) P(E | H) + P(\\neg H) P(E | \\neg H)}", - tex_to_color_map=t2c - ) - formula.scale(0.9) - formula.next_to(answer, RIGHT) - - ph = formula[2:6] - peh = formula[6:12] - over = formula.get_part_by_tex("\\over") - ph2 = formula[13:17] - peh2 = formula[17:23] - pnh = formula[23:28] - penh = formula[28:] - - parts = VGroup(ph, peh, ph2, peh2, pnh, penh) - parts.save_state() - - np1 = TexMobject("(\\# I )")[0] - person = Person() - person.replace(np1[2], dim_to_match=1) - person.scale(1.5) - np1.submobjects[2] = person - - np1.match_height(ph) - np1.next_to(ph, LEFT, SMALL_BUFF) - VGroup(np1, ph, peh).match_x(over) - - np2 = np1.copy() - np2.next_to(ph2, LEFT, SMALL_BUFF) - VGroup(np2, ph2, peh2).match_width( - VGroup(ph2, peh2), about_edge=RIGHT - ) - - np3 = np1.copy() - np3.next_to(pnh, LEFT, SMALL_BUFF) - VGroup(np3, pnh, penh).match_width( - VGroup(pnh, penh), about_edge=RIGHT - ) - - nps = VGroup(np1, np2, np3) - crosses = VGroup(*[Cross(np)[0] for np in nps]) - - top_brace = Brace(np1, UP, buff=SMALL_BUFF) - top_count = Integer(210) - top_count.add_updater(lambda m: m.next_to(top_brace, UP, SMALL_BUFF)) - - low_brace = Brace(np3, DOWN, buff=SMALL_BUFF) - low_count = Integer(210) - low_count.add_updater(lambda m: m.next_to(low_brace, DOWN, SMALL_BUFF)) - - h_rect = Rectangle( # Highlighting rectangle - stroke_color=YELLOW, - stroke_width=3, - fill_color=YELLOW, - fill_opacity=0.25, - ) - h_rect.replace(diagram.square, stretch=True) - - s_rect = SurroundingRectangle(answer[0]) - - diagram.refresh_braces() - nh_group = VGroup( - diagram.nh_brace, - TexMobject("P(\\neg H)", tex_to_color_map=t2c), - TexMobject("= 20 / 21"), - ) - nh_group[1].next_to(nh_group[0], UP, SMALL_BUFF) - nh_group[2].next_to(nh_group[1], RIGHT, SMALL_BUFF) - - self.play( - Write(formula.get_part_by_tex("=")), - Write(formula.get_part_by_tex("\\over")), - run_time=1 - ) - self.play(ShowCreation(s_rect)) - self.wait() - self.play( - FadeIn(np1), - FadeIn(top_brace), - FadeIn(top_count), - ) - self.wait() - self.play(h_rect.replace, diagram.h_rect, {"stretch": True}) - self.play( - TransformFromCopy(prior_label, ph), - top_brace.become, Brace(VGroup(np1, ph), UP, buff=SMALL_BUFF), - ChangeDecimalToValue(top_count, 10), - ) - self.wait() - self.play(h_rect.replace, diagram.he_rect, {"stretch": True}) - self.play( - TransformFromCopy(like_label, peh), - top_brace.become, Brace(VGroup(np1, peh), UP, buff=SMALL_BUFF), - ChangeDecimalToValue(top_count, 4) - ) - self.wait() - - self.play( - s_rect.move_to, answer[2][0], - TransformFromCopy(np1, np2), - TransformFromCopy(ph, ph2), - TransformFromCopy(peh, peh2), - ) - self.wait() - - self.play( - FadeOut(h_rect), - s_rect.become, SurroundingRectangle(answer[2][2]), - FadeOut(prior_group), - FadeIn(nh_group), - ) - self.wait() - h_rect.replace(diagram.square, stretch=True) - self.play( - FadeIn(np3), - FadeIn(low_brace), - FadeIn(low_count), - ) - self.play(h_rect.replace, diagram.nh_rect, {"stretch": True}) - self.play( - TransformFromCopy(nh_group[1], pnh), - low_brace.become, Brace(VGroup(np3, pnh), DOWN, buff=SMALL_BUFF), - ChangeDecimalToValue(low_count, 200), - ) - self.play(h_rect.replace, diagram.nhe_rect, {"stretch": True}) - self.play( - TransformFromCopy(anti_label, penh), - low_brace.become, Brace(VGroup(np3, penh), DOWN, buff=SMALL_BUFF), - ChangeDecimalToValue(low_count, 20), - ) - self.wait() - - # Clean up - self.play( - FadeOut(nh_group), - FadeOut(s_rect), - FadeOut(h_rect), - FadeIn(prior_group), - ) - self.wait() - - self.play( - ShowCreation(crosses), - FadeOut(low_brace), - FadeOut(top_brace), - FadeOut(low_count), - FadeOut(top_count), - ) - self.wait() - self.play( - Restore(parts), - FadeOut(crosses), - FadeOut(nps), - answer.set_opacity, 0.2 - ) - self.wait() - - # Write Bayes' theorem - formula_rect = SurroundingRectangle(formula[1:]) - formula_rect.set_stroke(TEAL, 2) - - bayes_words = TextMobject("Bayes' theorem") - bayes_words.scale(1.5) - bayes_words.next_to(formula_rect, UP, SMALL_BUFF) - bayes_words.match_color(formula_rect) - - self.play(ShowCreation(formula_rect)) - self.play(FadeInFromDown(bayes_words)) - self.wait() - - # Simplify denominator - simpler_form = get_bayes_formula()[7:] - simpler_form.move_to(answer) - pe = simpler_form[-4:].copy() - pe.save_state() - - big_denom_rect = SurroundingRectangle(VGroup(ph2, penh)) - lil_denom_rect = SurroundingRectangle(pe) - for rect in big_denom_rect, lil_denom_rect: - rect.set_stroke(BLUE, 0) - rect.set_fill(BLUE, 0.25) - pe.move_to(big_denom_rect) - pe.set_opacity(0) - - self.play( - FadeOut(answer), - FadeIn(simpler_form[:-4]) - ) - self.play(TransformFromCopy(formula_rect, big_denom_rect)) - self.wait() - self.play( - Restore(pe), - ReplacementTransform(big_denom_rect, lil_denom_rect), - Transform( - formula_rect, - SurroundingRectangle(simpler_form, color=TEAL), - ), - bayes_words.match_x, simpler_form, - ) - self.remove(pe) - self.add(simpler_form) - self.wait() - - # Show all evidence cases - he_group_copy = he_group.copy() - nhe_group_copy = nhe_group.copy() - copies = VGroup(he_group_copy, nhe_group_copy) - - self.play( - copies.arrange, RIGHT, {"buff": LARGE_BUFF}, - copies.move_to, DOWN, - copies.to_edge, RIGHT, LARGE_BUFF, - ) - self.wait() - self.play( - he_group_copy.next_to, VGroup(ph2, peh2), DOWN, - ) - self.play( - nhe_group_copy.next_to, VGroup(pnh, penh), DOWN, - ) - self.wait() - self.play( - FadeOut(copies), - FadeOut(lil_denom_rect), - ) - self.wait() - - # Name posterior - self.play( - GrowArrow(post_arrow), - FadeInFrom(post_word, RIGHT), - FadeOut(formula_rect), - FadeOut(bayes_words), - ) - self.wait() - - # Show confusion - randy = Randolph() - randy.flip() - randy.next_to(dividing_line, DOWN) - randy.to_edge(RIGHT) - - prior_rect = SurroundingRectangle(prior_word) - post_rect = SurroundingRectangle(post_word) - VGroup(prior_rect, post_rect).set_stroke(WHITE, 2) - - self.play(FadeIn(randy)) - self.play(randy.change, "confused", formula) - self.play(Blink(randy)) - self.play(randy.change, "horrified", formula) - self.play(randy.look_at, diagram) - self.play(Blink(randy)) - self.play(randy.look_at, formula) - self.play(randy.change, "tired") - self.wait(2) - self.play( - randy.change, "pondering", prior_label, - ShowCreation(prior_rect) - ) - self.play(Blink(randy)) - self.play( - ReplacementTransform(prior_rect, post_rect), - randy.look_at, post_rect, - ) - self.play(randy.change, "thinking") - self.play(FadeOut(post_rect)) - self.play(Blink(randy)) - self.wait() - self.play(randy.look_at, formula) - self.play(Blink(randy)) - self.wait() - - # Transition to next scene - return # Skip - to_move = VGroup(posterior, formula) - self.remove(to_move, *to_move, *to_move[1]) - to_move.generate_target() - to_move.target[1].scale(1 / 0.9) - to_move.target.arrange(RIGHT) - to_move.target.to_corner(UL) - - everything = Group(*self.get_mobjects()) - - self.play( - LaggedStartMap(FadeOutAndShiftDown, everything), - MoveToTarget(to_move, rate_func=squish_rate_func(smooth, 0.5, 1)), - run_time=2, - ) - - def get_diagram(self, include_people=True): - diagram = BayesDiagram(**self.bayes_diagram_config) - diagram.set_height(5.5) - diagram.set_width(5.5, stretch=True) - diagram.move_to(0.5 * DOWN) - diagram.add_brace_attrs() - - diagram.evidence_split.set_opacity(0) - diagram.hypothesis_split.set_opacity(0) - diagram.nh_rect.set_fill(DARK_GREY) - - if include_people: - people = VGroup(*[Person() for x in range(210)]) - people.set_color(interpolate_color(LIGHT_GREY, WHITE, 0.5)) - people.arrange_in_grid(n_cols=21) - people.set_width(diagram.get_width() - SMALL_BUFF) - people.set_height(diagram.get_height() - SMALL_BUFF, stretch=True) - people.move_to(diagram) - people[10:].set_color(GREEN_D) - people.set_stroke(BLACK, 2, background=True) - - diagram.add(people) - diagram.people = people - - return diagram - - -class DiscussFormulaAndAreaModel(CreateFormulaFromDiagram): - CONFIG = { - "bayes_diagram_config": { - "prior_rect_direction": DOWN, - }, - } - - def construct(self): - # Show smaller denominator - t2c = self.tex_to_color_map - formula = TexMobject( - "P(H | E)", "=", - "{P(H) P(E | H)", "\\over", - "P(H) P(E | H) + P(\\neg H) P(E | \\neg H)}", - tex_to_color_map=t2c - ) - equals = formula.get_part_by_tex("=") - equals_index = formula.index_of_part(equals) - lhs = formula[:equals_index] - rhs = formula[equals_index + 1:] - lhs.next_to(equals, LEFT) - formula.to_corner(UL) - - alt_rhs = TexMobject( - "{P(H)P(E|H)", "\\over", "P(E)}", - "=", - tex_to_color_map=t2c, - ) - alt_rhs.next_to(equals, RIGHT, SMALL_BUFF) - - s_rect = SurroundingRectangle(rhs[12:], color=BLUE) - - self.add(formula) - self.wait() - self.play(ShowCreation(s_rect)) - self.add(alt_rhs, s_rect) - self.play( - VGroup(rhs, s_rect).next_to, alt_rhs, RIGHT, SMALL_BUFF, - GrowFromCenter(alt_rhs), - ) - self.play( - s_rect.become, - SurroundingRectangle(alt_rhs[12:-1], color=BLUE) - ) - self.wait() - - # Bring in diagram - diagram = self.get_diagram(include_people=False) - diagram.evidence_split.set_opacity(1) - diagram.set_height(3) - diagram.set_prior(0.1) - - diagram.refresh_braces() - diagram.add( - diagram.h_brace, - diagram.he_brace, - diagram.nhe_brace, - ) - - h_label, he_label, nhe_label = labels = [ - TexMobject(tex, tex_to_color_map=t2c) - for tex in ["P(H)", "P(E|H)", "P(E|\\neg H)"] - ] - h_label.add_updater(lambda m: m.next_to(diagram.h_brace, DOWN)) - he_label.add_updater(lambda m: m.next_to(diagram.he_brace, LEFT)) - nhe_label.add_updater(lambda m: m.next_to(diagram.nhe_brace, RIGHT)) - diagram.add(*labels) - - diagram.to_corner(DL) - - he_rect_copy = diagram.he_rect.copy() - nhe_rect_copy = diagram.nhe_rect.copy() - - self.play( - FadeIn(diagram), - # FadeOut(s_rect), - ReplacementTransform( - VGroup(s_rect, s_rect.copy()), - VGroup(he_rect_copy, nhe_rect_copy), - ), - ) - self.wait() - self.play(LaggedStart( - ApplyMethod(he_rect_copy.next_to, rhs[12:22], DOWN), - ApplyMethod(nhe_rect_copy.next_to, rhs[23:], DOWN), - lag_ratio=0.7, - run_time=1.5 - )) - self.wait() - self.play(FadeOut(VGroup(he_rect_copy, nhe_rect_copy))) - - # Tell what to memorize - big_rect = SurroundingRectangle(formula) - big_rect.set_stroke(WHITE, 2) - big_rect.set_fill(BLACK, 0) - - words1 = TextMobject("Don't memorize\\\\this") - words2 = TextMobject("Remember\\\\this") - for words in words1, words2: - words.scale(1.5) - words.to_edge(RIGHT) - - arrow1 = Arrow(words1.get_corner(UL), big_rect.get_bottom()) - arrow2 = Arrow(words2.get_left(), diagram.square.get_right()) - - self.play( - FadeIn(words1), - ShowCreation(arrow1), - ShowCreation(big_rect) - ) - self.wait() - self.play( - ReplacementTransform(arrow1, arrow2), - FadeOut(words1), - FadeIn(words2), - big_rect.set_stroke, WHITE, 0, - big_rect.set_fill, BLACK, 0.7, - ) - self.wait() - - # Talk about diagram slices - to_fade = VGroup( - diagram.evidence_split, - diagram.he_brace, - diagram.nhe_brace, - he_label, nhe_label, - ) - to_fade.save_state() - - h_part = VGroup( - diagram.hypothesis_split, - diagram.h_brace, - h_label, - ) - people = self.get_diagram().people - people.set_width(diagram.square.get_width() - 0.05) - people.move_to(diagram.square) - - sides = VGroup( - DashedLine( - diagram.square.get_corner(UL), - diagram.square.get_corner(UR), - ), - DashedLine( - diagram.square.get_corner(UL), - diagram.square.get_corner(DL), - ), - ) - sides.set_stroke(YELLOW, 4) - ones = VGroup( - TexMobject("1").next_to(diagram.square, UP), - TexMobject("1").next_to(diagram.square, LEFT), - ) - - self.play( - to_fade.set_opacity, 0, - h_part.set_opacity, 0, - diagram.square.set_opacity, 1, - ShowIncreasingSubsets(people), - ) - self.play(FadeOut(people)) - self.play( - LaggedStartMap(ShowCreation, sides, lag_ratio=0.8), - LaggedStartMap(FadeIn, ones, lag_ratio=0.8), - ) - self.wait() - - self.play( - h_part.set_opacity, 1, - ) - diagram.square.set_opacity(0) - self.wait() - self.play( - FadeOut(sides), - FadeOut(ones), - ) - self.wait() - VGroup( - to_fade[1:], - to_fade[0][::2], - ).stretch(0, 1, about_edge=DOWN) - self.play( - Restore(to_fade), - diagram.hypothesis_split.set_opacity, 0, - diagram.hne_rect.set_opacity, 0.2, - diagram.nhne_rect.set_opacity, 0.2, - ) - self.wait() - - # Add posterior bar - post_bar = always_redraw( - lambda: self.get_posterior_bar( - diagram.he_rect, - diagram.nhe_rect, - ) - ) - post_label = TexMobject("P(H|E)", tex_to_color_map=t2c) - post_label.add_updater(lambda m: m.next_to(post_bar.brace, DOWN)) - - self.play( - FadeOut(words2), - FadeOut(arrow2), - TransformFromCopy( - diagram.he_rect, post_bar.rects[0], - ), - TransformFromCopy( - diagram.nhe_rect, post_bar.rects[1], - ), - FadeIn(post_bar.brace), - FadeIn(post_label), - ) - self.add(post_bar) - self.wait() - - self.play( - diagram.set_likelihood, 0.8, - rate_func=there_and_back, - run_time=4, - ) - self.wait() - self.play(diagram.set_antilikelihood, 0.4) - self.wait() - self.play( - diagram.set_likelihood, 0.8, - diagram.set_antilikelihood, 0.05, - ) - self.wait() - self.play( - diagram.set_likelihood, 0.4, - diagram.set_antilikelihood, 0.1, - ) - self.wait() - - self.play( - big_rect.set_width, rhs.get_width() + 0.7, - {"about_edge": RIGHT, "stretch": True}, - ) - self.wait() - - # Terms from formula to sides in diagram - hs_rect = SurroundingRectangle(alt_rhs[:5], buff=0.05) - hes_rect = SurroundingRectangle(alt_rhs[5:11], buff=0.05) - hs_rect.set_stroke(YELLOW, 3) - hes_rect.set_stroke(BLUE, 3) - - self.play(ShowCreation(hs_rect)) - self.play(ShowCreation(hes_rect)) - self.wait() - self.play(hs_rect.move_to, h_label) - self.play(hes_rect.move_to, he_label) - self.wait() - self.play(FadeOut(VGroup(hs_rect, hes_rect))) - - def get_posterior_bar(self, he_rect, nhe_rect): - he_height = he_rect.get_height() - he_width = he_rect.get_width() - nhe_height = nhe_rect.get_height() - nhe_width = nhe_rect.get_width() - - total_width = he_width + nhe_width - he_area = he_width * he_height - nhe_area = nhe_width * nhe_height - - posterior = he_area / (he_area + nhe_area) - - new_he_width = posterior * total_width - new_he_height = he_area / new_he_width - new_nhe_width = (1 - posterior) * total_width - new_nhe_height = nhe_area / new_nhe_width - - new_he_rect = Rectangle( - width=new_he_width, - height=new_he_height, - ).match_style(he_rect) - new_nhe_rect = Rectangle( - width=new_nhe_width, - height=new_nhe_height, - ).match_style(nhe_rect) - - rects = VGroup(new_he_rect, new_nhe_rect) - rects.arrange(RIGHT, buff=0) - - brace = Brace( - new_he_rect, DOWN, - buff=SMALL_BUFF, - min_num_quads=2, - ) - result = VGroup(rects, brace) - result.rects = rects - result.brace = brace - - # Put positioning elsewhere? - result.to_edge(RIGHT) - return result - - -class RandomShapes(Scene): - def construct(self): - diagram = BayesDiagram(0.1, 0.4, 0.1) - diagram.set_height(3) - - e_part = VGroup(diagram.he_rect, diagram.nhe_rect).copy() - e_part.set_fill(BLUE) - - circle = Circle() - circle.set_fill(RED, 0.5) - circle.set_stroke(RED, 2) - circle.move_to(diagram) - - tri = Polygon(UP, ORIGIN, RIGHT) - tri.match_height(diagram) - tri.set_fill(PURPLE, 0.5) - tri.set_stroke(PURPLE, 2) - tri.move_to(diagram) - - h_rect = diagram.h_rect - h_rect.set_fill(YELLOW, 1) - - pi = TexMobject("\\pi") - pi.set_height(2) - pi.set_stroke(GREEN, 2) - pi.set_fill(GREEN, 0.5) - - events = VGroup( - e_part, - h_rect, - circle, - pi, - tri, - ) - - last = VMobject() - for event in events: - self.play( - FadeIn(event), - FadeOut(last) - ) - self.wait() - last = event - - -class BigArrow(Scene): - def construct(self): - arrow = Arrow( - 3 * DOWN + 4 * LEFT, - 3 * RIGHT + DOWN, - path_arc=50 * DEGREES, - ) - - self.play(ShowCreation(arrow)) - self.wait() - - -class UsesOfBayesTheorem(Scene): - def construct(self): - formula = get_bayes_formula() - formula.to_corner(UL) - rhs = formula[7:] - - bubble = ThoughtBubble(direction=RIGHT) - bubble.set_fill(opacity=0) - arrow = Vector(RIGHT) - arrow.next_to(formula, RIGHT, MED_LARGE_BUFF) - bubble.set_height(2) - bubble.next_to(arrow, RIGHT, MED_LARGE_BUFF) - bubble.align_to(formula, UP) - bar = ProbabilityBar( - 0.1, - percentage_background_stroke_width=1, - include_braces=False, - ) - bar.set_width(0.8 * bubble[-1].get_width()) - bar.move_to(bubble.get_bubble_center()) - - scientist = SVGMobject(file_name="scientist") - programmer = SVGMobject(file_name="programmer") - randy = Randolph().flip() - - people = VGroup(scientist, programmer, randy) - people.set_stroke(width=0) - for person in people: - person.set_height(2.5) - if person is not randy: - person.set_fill(GREY) - person.set_sheen(0.5, UL) - - people.arrange(RIGHT, buff=2.5) - # programmer.shift(0.5 * RIGHT) - people.to_edge(DOWN, buff=LARGE_BUFF) - - self.add(formula) - self.wait() - self.play(GrowArrow(arrow)) - self.play(ShowCreation(bubble), FadeIn(bar)) - self.play(bar.p_tracker.set_value, 0.8) - self.wait() - - # Add people - for person in [scientist, programmer]: - self.play(FadeInFrom(person, DOWN)) - rhs_copy = rhs.copy() - rhs_copy.add_to_back( - SurroundingRectangle( - rhs_copy, - fill_color=BLACK, - fill_opacity=0.8, - stroke_color=WHITE, - stroke_width=2, - ) - ) - rhs_copy.generate_target() - rhs_copy[0].set_stroke(width=0) - rhs_copy.target.scale(0.5) - rhs_copy.target.move_to(person.get_corner(DR)) - self.add(rhs_copy, formula) - self.play(MoveToTarget(rhs_copy)) - self.wait() - person.add(rhs_copy) - - self.play(FadeInFromDown(randy)) - self.play(randy.change, "pondering") - self.play(Blink(randy)) - bubble_group = VGroup(bubble, bar) - self.play( - bubble_group.scale, 1.4, - bubble_group.move_to, randy.get_corner(UL), DR, - randy.look_at, bubble, - FadeOut(arrow), - ) - self.wait() - self.play(Blink(randy)) - self.play(bar.p_tracker.set_value, 0.3, run_time=3) - self.play(randy.change, "thinking") - self.play(Blink(randy)) - self.wait() - - self.play(LaggedStartMap( - FadeOutAndShift, - VGroup(*people, bubble_group), - lambda m: (m, DOWN), - lag_ratio=0.15, - )) - - -class AskAboutWhenProbabilityIsIntuitive(TeacherStudentsScene): - def construct(self): - words = TextMobject("What makes probability\\\\more intuitive?") - words.move_to(self.hold_up_spot, DOWN) - words.shift_onto_screen() - - self.play( - self.teacher.change, "speaking", - self.get_student_changes( - "pondering", "sassy", "happy", - look_at_arg=self.screen, - ) - ) - self.wait(2) - - self.play( - self.teacher.change, "raise_right_hand", - FadeInFrom(words, DOWN), - self.get_student_changes("erm", "pondering", "confused") - ) - self.wait(2) - self.play( - words.scale, 2, - words.center, - words.to_edge, UP, - self.teacher.change, "pondering", 3 * UP, - self.get_student_changes( - "pondering", "thinking", "thinking", - look_at_arg=3 * UP, - ) - ) - self.wait(6) - - -class IntroduceLinda(DescriptionOfSteve): - def construct(self): - # Kahneman and Tversky - images = self.get_images() - - self.play( - LaggedStartMap( - FadeInFrom, images, - lambda m: (m, LEFT), - lag_ratio=0.3, - run_time=3, - ) - ) - self.wait() - - # Add steve - steve = Steve() - steve.set_height(3) - steve.move_to(2 * RIGHT) - steve.to_edge(DOWN, buff=LARGE_BUFF) - steve_words = self.get_description() - steve_words.scale(0.8) - steve_words.next_to(steve, UP, LARGE_BUFF) - - self.play(LaggedStart( - FadeInFrom(steve, LEFT), - FadeInFrom(steve_words, LEFT), - )) - self.wait() - - # Replace with Linda - linda = Linda() - linda_words = self.get_linda_description() - linda_words.scale(0.8) - linda.replace(steve) - linda_words.move_to(steve_words) - - self.play( - LaggedStart( - FadeOutAndShift(steve_words, 2 * RIGHT), - FadeOutAndShift(steve, 2 * RIGHT), - FadeInFrom(linda, 2 * LEFT), - lag_ratio=0.15, - ) - ) - self.wait() - - self.play( - FadeIn(linda_words), - lag_ratio=0.1, - run_time=6, - rate_func=linear, - ) - self.wait() - - # Ask question - options = VGroup( - TextMobject("1) Linda is a bank teller."), - TextMobject( - "2) Linda is a bank teller and is active\\\\", - "\\phantom{2)} in the feminist movement.", - alignment="", - ), - ) - options.arrange(DOWN, buff=LARGE_BUFF, aligned_edge=LEFT) - options.to_edge(DOWN, buff=LARGE_BUFF) - - self.play( - linda.match_height, linda_words, - linda.next_to, linda_words, LEFT, LARGE_BUFF, - LaggedStartMap( - FadeOutAndShift, images, - lambda m: (m, 2 * LEFT), - ) - ) - for option in options: - self.play(FadeIn(option, lag_ratio=0.1, run_time=2)) - self.wait() - - # Show result - rect = SurroundingRectangle(options[1], color=RED) - options.generate_target() - rect.generate_target() - VGroup(options.target, rect.target).to_edge(LEFT) - - result = VGroup( - Integer(85, unit="\\%"), - TextMobject("chose this!") - ) - result.arrange(RIGHT) - result.scale(1.25) - result.set_color(RED) - result.move_to(rect) - result.to_edge(RIGHT) - result.shift(2 * UP) - arrow = Arrow(result.get_bottom(), rect.target.get_corner(UR)) - - self.play( - MoveToTarget(options), - MoveToTarget(rect), - VFadeIn(rect), - FadeInFrom(result, LEFT), - GrowArrow(arrow) - ) - self.wait() - - # Show subsets - fb_words = TextMobject("Active feminist\\\\bank tellers") - fb_words.scale(0.5) - fb_words.set_stroke(BLACK, 0, background=True) - fb_set = Circle() - fb_set.flip(RIGHT) - fb_set.rotate(3 * TAU / 8) - fb_set.set_stroke(WHITE, 1) - fb_set.set_fill(YELLOW, 0.5) - fb_set.replace(fb_words, stretch=True) - fb_set.scale(1.2) - fb_set.stretch(1.4, 1) - fb_group = VGroup(fb_set, fb_words) - fb_group.move_to(linda_words, RIGHT) - - b_set = fb_set.copy() - b_set.set_fill(BLUE) - b_set.scale(3, about_edge=RIGHT) - b_set.stretch(1.5, 1) - b_set.to_corner(UR) - b_words = TextMobject("Bank\\\\tellers") - b_words.next_to(b_set.get_left(), RIGHT, LARGE_BUFF) - - self.play( - FadeOut(linda_words), - TransformFromCopy(rect, fb_set), - ReplacementTransform( - VectorizedPoint(rect.get_center()), - fb_words, - ), - ) - self.add(fb_set, fb_words) - self.wait() - self.add(b_set, fb_set, fb_words) - self.play( - DrawBorderThenFill(b_set), - TransformFromCopy( - options[0][0][10:], b_words[0] - ), - ) - sets_group = VGroup(b_set, b_words, fb_set, fb_words) - self.add(sets_group) - self.wait() - - # Reduce 85 - number = result[0] - - self.play( - LaggedStart( - FadeOut(arrow), - FadeOut(result[1]), - FadeOut(rect), - FadeOut(options[0]), - FadeOut(options[1]), - ), - number.scale, 1.5, - number.move_to, DOWN, - ) - self.play( - ChangeDecimalToValue(number, 0), - UpdateFromAlphaFunc( - number, - lambda m, a: m.set_color(interpolate_color( - RED, GREEN, a, - )) - ), - run_time=2, - ) - self.wait() - - self.play( - FadeOut(number), - FadeOut(sets_group), - FadeIn(linda_words), - ) - - # New options - words = TextMobject("100 people fit this description. How many are:") - words.set_color(BLUE_B) - kw = {"tex_to_color_map": {"\\underline{\\qquad}": WHITE}} - new_options = VGroup( - TextMobject("1) Bank tellers? \\underline{\\qquad} of 100", **kw), - TextMobject( - "2) Bank tellers and active in the", - " feminist movement? \\underline{\\qquad} of 100", - **kw - ), - ) - new_options.scale(0.9) - new_options.arrange(DOWN, aligned_edge=LEFT, buff=MED_LARGE_BUFF) - new_options.to_edge(DOWN, buff=LARGE_BUFF) - - words.next_to(new_options, UP, LARGE_BUFF) - - self.play( - FadeIn(words, lag_ratio=0.1, rate_func=linear) - ) - self.wait() - for option in new_options: - self.play(FadeIn(option)) - self.wait() - - example_numbers = VGroup(Integer(8), Integer(5)) - for exn, option in zip(example_numbers, new_options): - line = option.get_part_by_tex("underline") - exn.scale(1.1) - exn.next_to(line, UP, buff=0.05) - exn.set_color(YELLOW) - self.play(Write(exn)) - self.wait() - - def get_images(self): - images = Group( - ImageMobject("kahneman"), - ImageMobject("tversky"), - ) - images.set_height(3.5) - images.arrange(DOWN, buff=0.5) - images.to_edge(LEFT, buff=MED_LARGE_BUFF) - - names = VGroup( - TextMobject("Kahneman", alignment=""), - TextMobject("Tversky", alignment=""), - ) - for name, image in zip(names, images): - name.next_to(image, DOWN) - image.name = name - image.add(name) - images.arrange(DOWN, buff=1, aligned_edge=LEFT) - images.set_height(FRAME_HEIGHT - 1) - images.to_edge(LEFT) - return images - - def get_linda_description(self): - result = TextMobject( - "Linda is 31 years old, single, outspoken, and\\\\", - "very bright. She majored in philosophy. As a \\\\", - "student, she was deeply concerned with issues\\\\", - "of discrimination and social justice, and also\\\\", - "participated in anti-nuclear demonstrations.\\\\", - alignment="", - tex_to_color_map={ - "deeply concerned with issues": YELLOW, - "of discrimination": YELLOW, - } - ) - return result - - -class LindaDescription(IntroduceLinda): - def construct(self): - words = self.get_linda_description() - words.set_color(WHITE) - - highlighted_part = VGroup( - *words.get_part_by_tex("deeply"), - *words.get_part_by_tex("discrimination"), - ) - - self.add(words) - self.play( - FadeIn(words), - run_time=3, - lag_ratio=0.01, - rate_func=linear, - ) - self.wait(1) - self.play( - highlighted_part.set_color, YELLOW, - lag_ratio=0.1, - run_time=2 - ) - self.wait() - - -class AlternatePhrasings(PiCreatureScene): - CONFIG = { - "camera_config": { - "background_color": DARKER_GREY, - } - } - - def construct(self): - randy = self.pi_creature - - phrases = VGroup( - TextMobject("40 out of 100"), - TexMobject("40\\%"), - TexMobject("0.4"), - TextMobject("What's more likely$\\dots$"), - ) - for phrase in phrases: - phrase.scale(1.5) - phrase.next_to(randy, RIGHT, buff=LARGE_BUFF) - phrase.align_to(randy, UP) - - def push_down(phrase): - phrase.scale(0.8, about_edge=LEFT) - phrase.shift(1 * DOWN) - phrase.set_opacity(0.5) - return phrase - - bubble = randy.get_bubble() - content_width = 4.5 - - people = VGroup(*[Person() for x in range(100)]) - people.arrange_in_grid(n_cols=20) - people.set_width(content_width) - people.move_to(bubble.get_bubble_center()) - people.shift(SMALL_BUFF * UP) - people[:40].set_color(YELLOW) - - bar = ProbabilityBar(0.9999) - bar.set_width(content_width) - bar.move_to(people) - - steve = Steve() - steve.set_height(1) - steve_words = TextMobject("seems bookish...") - steve_words.next_to(steve, RIGHT, MED_LARGE_BUFF) - steve.add(steve_words) - - linda = Linda() - linda.set_height(1) - linda_words = TextMobject("seems activist...") - linda_words.next_to(linda, RIGHT, MED_LARGE_BUFF) - linda.add(linda_words) - - stereotypes = VGroup(steve, linda) - stereotypes.arrange(DOWN, buff=MED_SMALL_BUFF, aligned_edge=LEFT) - stereotypes.move_to(people) - - self.play( - FadeInFrom(phrases[0], UP), - randy.change, "pondering", - ) - self.play( - DrawBorderThenFill(bubble, lag_ratio=0.1), - FadeIn(people, lag_ratio=0.1), - randy.change, "thinking", people, - ) - self.wait() - self.play( - FadeInFrom(phrases[1], UP), - randy.change, "confused", phrases[1], - FadeOut(people), - ApplyFunction(push_down, phrases[0]), - FadeIn(bar), - ) - self.play(bar.p_tracker.set_value, 0.4) - bar.clear_updaters() - self.play( - FadeInFrom(phrases[2], UP), - ApplyFunction(push_down, phrases[:2]), - FadeOut(bar.percentages), - randy.change, "guilty", - ) - self.wait() - bar.remove(bar.percentages) - self.play( - FadeInFrom(phrases[3], UP), - ApplyFunction(push_down, phrases[:3]), - FadeOut(bar), - FadeIn(stereotypes), - randy.change, "shruggie", stereotypes, - ) - self.wait(6) - - -class WhenDiscreteChunksArentSoClean(Scene): - def construct(self): - squares = VGroup(*[Square() for x in range(100)]) - squares.arrange_in_grid(n_cols=10, buff=0) - squares.set_stroke(WHITE, 1) - squares.set_fill(DARKER_GREY, 1) - squares.set_height(6) - squares.to_edge(DOWN) - - target_p = 0.3612 - - rain, sun = icon_templates = VGroup( - SVGMobject("rain_cloud"), - SVGMobject("sunny"), - ) - for icon in icon_templates: - icon.set_width(0.6 * squares[0].get_width()) - icon.set_stroke(width=0) - icon.set_sheen(0.5, UL) - rain.set_color(BLUE_E) - sun.set_color(YELLOW) - - partial_rects = VGroup() - icons = VGroup() - q_marks = VGroup() - for i, square in enumerate(squares): - icon = rain.copy() if i < 40 else sun.copy() - icon.move_to(square) - icons.add(icon) - - partial_rect = square.copy() - partial_rect.set_stroke(width=0) - partial_rect.scale(0.95) - partial_rect.stretch( - 0.4, - 0, - about_edge=RIGHT - ) - partial_rects.add(partial_rect) - - q_mark = TexMobject("?") - q_mark.replace(partial_rect, dim_to_match=0) - q_mark.scale(0.8) - q_marks.add(q_mark) - - p_label = VGroup( - TexMobject("P", "(", "\\text{Rain}", ")", "="), - DecimalNumber(40, unit="\\%", num_decimal_places=2) - ) - percentage = p_label[1] - p_label.arrange(RIGHT) - p_label.to_edge(UP) - p_label[0].set_color_by_tex("Rain", BLUE) - percentage.align_to(p_label[0][0], DOWN) - - alt_percentage = Integer(0, unit="\\%") - alt_percentage.move_to(percentage, LEFT) - - self.add(squares) - self.add(p_label[0]) - self.play( - ChangeDecimalToValue(alt_percentage, 40), - ShowIncreasingSubsets(icons[:40]) - ) - self.play(FadeIn(icons[40:])) - self.wait() - self.remove(alt_percentage) - self.add(percentage) - self.play( - ChangeDecimalToValue(percentage, 100 * target_p), - FadeIn(partial_rects[30:40]), - FadeIn(q_marks[30:40], lag_ratio=0.3) - ) - self.wait(2) - - l_rect = Rectangle(fill_color=BLUE_D) - r_rect = Rectangle(fill_color=LIGHT_GREY) - rects = VGroup(l_rect, r_rect) - for rect, p in (l_rect, target_p), (r_rect, 1 - target_p): - rect.set_height(squares.get_height()) - rect.set_width(p * squares.get_width(), stretch=True) - rect.set_stroke(WHITE, 2) - rect.set_fill(opacity=1) - rects.arrange(RIGHT, buff=0) - rects.move_to(squares) - - brace = Brace(l_rect, UP, buff=SMALL_BUFF) - - sun = icons[40].copy() - rain = icons[0].copy() - for mob, rect in [(rain, l_rect), (sun, r_rect)]: - mob.generate_target() - mob.target.set_stroke(BLACK, 3, background=True) - mob.target.set_height(1) - mob.target.move_to(rect) - self.play( - FadeIn(rects), - MoveToTarget(rain), - MoveToTarget(sun), - GrowFromCenter(brace), - p_label.shift, - brace.get_top() + MED_SMALL_BUFF * UP - - percentage.get_bottom(), - ) - self.wait() - - # With updaters - full_width = rects.get_width() - rain.add_updater(lambda m: m.move_to(l_rect)) - sun.add_updater(lambda m: m.move_to(r_rect)) - r_rect.add_updater(lambda m: m.set_width( - full_width - l_rect.get_width(), - about_edge=RIGHT, - stretch=True, - )) - brace.add_updater(lambda m: m.match_width(l_rect, stretch=True)) - brace.add_updater(lambda m: m.next_to(l_rect, UP, SMALL_BUFF)) - percentage.add_updater(lambda m: m.set_value( - 100 * l_rect.get_width() / full_width, - )) - percentage.add_updater(lambda m: m.next_to(brace, UP, MED_SMALL_BUFF)) - - self.play( - MaintainPositionRelativeTo(p_label[0], percentage), - l_rect.stretch, 2, 0, {"about_edge": LEFT}, - run_time=8, - rate_func=there_and_back, - ) - - -class RandomnessVsProportions(Scene): - def construct(self): - prob_word = TextMobject("Probability") - unc_word = TextMobject("Uncertainty") - prop_word = TextMobject("Proportions") - words = VGroup(prop_word, prob_word, unc_word) - words.arrange(RIGHT, buff=LARGE_BUFF) - words.set_width(FRAME_WIDTH - 1) - words.to_edge(UP) - arrows = VGroup() - for w1, w2 in zip(words, words[1:]): - arrow = TexMobject("\\rightarrow") - arrow.move_to(midpoint(w1.get_right(), w2.get_left())) - arrows.add(arrow) - - random_dice = self.get_random_dice() - random_dice.next_to(unc_word, DOWN, LARGE_BUFF) - - diagram = self.get_dice_diagram() - diagram.next_to(prop_word, DOWN, LARGE_BUFF) - diagram.shift_onto_screen() - - grid = diagram[0] - border = grid[0][0].copy() - border.set_stroke(BLACK, 3) - border.set_fill(WHITE, opacity=0.2) - border.scale(1.02) - - def update_border(border): - r1, r2 = random_dice - i = len(r1[1]) - 1 - j = len(r2[1]) - 1 - border.move_to(diagram[0][i][j]) - border.add_updater(update_border) - - example = VGroup( - TextMobject("P(X = 5)", tex_to_color_map={"5": YELLOW}), - Line(LEFT, RIGHT) - ) - example.arrange(RIGHT) - example.next_to(grid, RIGHT, LARGE_BUFF) - example.align_to(random_dice, RIGHT) - example.shift(0.5 * DOWN) - grid_copy = grid.copy() - five_part = VGroup(*[ - square - for i, row in enumerate(grid_copy) - for j, square in enumerate(row) - if i + j == 3 - ]) - - self.play(FadeInFromDown(prob_word)) - self.play( - FadeInFrom(unc_word, LEFT), - Write(arrows[1]), - ) - self.add(random_dice) - self.wait(9) - self.play( - FadeInFrom(prop_word, RIGHT), - Write(arrows[0]) - ) - self.play(FadeIn(diagram)) - self.add(border) - self.wait(2) - - self.play(FadeIn(example)) - self.add(grid_copy, diagram[1]) - self.play( - grid_copy.set_width, 0.8 * example[1].get_width(), - grid_copy.next_to, example[1], DOWN, - ) - self.play(five_part.copy().next_to, example[1], UP) - self.wait(6) - - def get_die_faces(self): - dot = Dot() - dot.set_width(0.15) - dot.set_color(BLUE_B) - - square = Square() - square.round_corners(0.25) - square.set_stroke(WHITE, 2) - square.set_fill(DARKER_GREY, 1) - square.set_width(0.6) - - edge_groups = [ - (ORIGIN,), - (UL, DR), - (UL, ORIGIN, DR), - (UL, UR, DL, DR), - (UL, UR, ORIGIN, DL, DR), - (UL, UR, LEFT, RIGHT, DL, DR), - ] - - arrangements = VGroup(*[ - VGroup(*[ - dot.copy().move_to(square.get_bounding_box_point(ec)) - for ec in edge_group - ]) - for edge_group in edge_groups - ]) - square.set_width(1) - - faces = VGroup(*[ - VGroup(square.copy(), arrangement) - for arrangement in arrangements - ]) - faces.arrange(RIGHT) - - return faces - - def get_random_dice(self): - faces = list(self.get_die_faces()) - - def get_random_pair(): - result = VGroup(*random.sample(faces, 2)).copy() - result.arrange(RIGHT) - for mob in result: - mob.shift(random.random() * RIGHT * MED_SMALL_BUFF) - mob.shift(random.random() * UP * MED_SMALL_BUFF) - return result - - result = VGroup(*get_random_pair()) - result.time = 0 - result.iter_count = 0 - - def update_result(group, dt): - group.time += dt - group.iter_count += 1 - if int(group.time) % 3 == 2: - group.set_stroke(YELLOW) - return group - elif result.iter_count % 3 != 0: - return group - else: - pair = get_random_pair() - pair.move_to(group) - group.submobjects = [*pair] - - result.add_updater(update_result) - result.update() - return result - - def get_dice_diagram(self): - grid = VGroup(*[ - VGroup(*[ - Square() for x in range(6) - ]).arrange(RIGHT, buff=0) - for y in range(6) - ]).arrange(DOWN, buff=0) - grid.set_stroke(WHITE, 1) - grid.set_height(5) - - colors = color_gradient([RED, YELLOW, GREEN, BLUE], 11) - - numbers = VGroup() - for i, row in enumerate(grid): - for j, square in enumerate(row): - num = Integer(i + j + 2) - num.set_height(square.get_height() - MED_LARGE_BUFF) - num.move_to(square) - # num.set_stroke(BLACK, 2, background=True) - num.set_fill(DARK_GREY) - square.set_fill(colors[i + j], 0.9) - numbers.add(num) - - faces = VGroup() - face_templates = self.get_die_faces() - face_templates.scale(0.5) - for face, row in zip(face_templates, grid): - face.next_to(row, LEFT, MED_SMALL_BUFF) - faces.add(face) - for face, square in zip(faces.copy(), grid[0]): - face.next_to(square, UP, MED_SMALL_BUFF) - faces.add(face) - - result = VGroup(grid, numbers, faces) - return result - - -class JustRandomDice(RandomnessVsProportions): - def construct(self): - random_dice = self.get_random_dice() - random_dice.center() - - self.add(random_dice) - self.wait(60) - - -class BayesTheoremOnProportions(Scene): - def construct(self): - # Place on top of visuals from "HeartOfBayes" - formula = get_bayes_formula() - formula.scale(1.5) - - title = TextMobject("Bayes' theorem") - title.scale(2) - title.next_to(formula, UP, LARGE_BUFF) - group = VGroup(formula, title) - - equals = TexMobject("=") - equals.next_to(formula, RIGHT) - h_line = Line(LEFT, RIGHT) - h_line.set_width(4) - h_line.next_to(equals, RIGHT) - h_line.set_stroke(WHITE, 3) - - self.add(group) - self.wait() - self.play( - group.to_edge, LEFT, - MaintainPositionRelativeTo(equals, group), - VFadeIn(equals), - MaintainPositionRelativeTo(h_line, group), - VFadeIn(h_line), - ) - - # People - people = VGroup(*[Person() for x in range(7)]) - people.arrange(RIGHT) - people.match_width(h_line) - people.next_to(h_line, DOWN) - people.set_color(BLUE_E) - people[:3].set_color(GREEN) - num_people = people[:3].copy() - - self.play(FadeIn(people, lag_ratio=0.1)) - self.play(num_people.next_to, h_line, UP) - self.wait(0.5) - - # Diagrams - diagram = BayesDiagram(0.25, 0.5, 0.2) - diagram.set_width(0.7 * h_line.get_width()) - diagram.next_to(h_line, DOWN) - diagram.hne_rect.set_fill(opacity=0.1) - diagram.nhne_rect.set_fill(opacity=0.1) - num_diagram = diagram.deepcopy() - num_diagram.next_to(h_line, UP) - num_diagram.nhe_rect.set_fill(opacity=0.1) - low_diagram_rects = VGroup(diagram.he_rect, diagram.nhe_rect) - top_diagram_rects = VGroup(num_diagram.he_rect) - - self.play( - FadeOut(people), - FadeOut(num_people), - FadeIn(diagram), - FadeIn(num_diagram), - ) - self.wait() - - # Circle each part - E_part = VGroup(formula[4], *formula[19:]).copy() - H_part = VGroup(formula[2], *formula[8:18]).copy() - - E_arrow = Vector(UP, color=BLUE) - E_arrow.next_to(E_part[0], DOWN) - E_words = TextMobject( - "...among cases where\\\\$E$ is True", - tex_to_color_map={"$E$": BLUE}, - ) - E_words.next_to(E_arrow, DOWN) - H_arrow = Vector(DOWN, color=YELLOW) - H_arrow.next_to(H_part[0], UP) - H_words = TextMobject( - "How often is\\\\$H$ True...", - tex_to_color_map={"$H$": YELLOW}, - ) - H_words.next_to(H_arrow, UP) - - denom_rect = SurroundingRectangle(E_part[1:], color=BLUE) - numer_rect = SurroundingRectangle(H_part[1:], color=YELLOW) - - self.play( - formula.set_opacity, 0.5, - ApplyMethod( - E_part.set_stroke, YELLOW, 3, {"background": True}, - rate_func=there_and_back, - ), - FadeIn(denom_rect), - ShowCreation(E_arrow), - FadeInFrom(E_words, UP), - low_diagram_rects.set_stroke, TEAL, 3, - ) - self.wait() - self.play( - FadeOut(E_part), - FadeIn(H_part), - FadeOut(denom_rect), - FadeIn(numer_rect), - ShowCreation(H_arrow), - FadeInFrom(H_words, DOWN), - FadeOutAndShift(title, UP), - low_diagram_rects.set_stroke, WHITE, 1, - top_diagram_rects.set_stroke, YELLOW, 3, - ) - self.wait() - - -class GlimpseOfNextVideo(GraphScene): - CONFIG = { - "x_axis_label": "", - "y_axis_label": "", - "x_min": 0, - "x_max": 15, - "x_axis_width": 12, - "y_min": 0, - "y_max": 1.0, - "y_axis_height": 6, - "y_tick_frequency": 0.125, - "add_x_coords": True, - "formula_position": ORIGIN, - "dx": 0.2, - } - - def setup(self): - super().setup() - self.setup_axes() - self.y_axis.add_numbers( - 0.25, 0.5, 0.75, 1, - number_config={ - "num_decimal_places": 2, - }, - direction=LEFT, - ) - if self.add_x_coords: - self.x_axis.add_numbers(*range(1, 15),) - - def construct(self): - f1 = self.prior - - def f2(x): - return f1(x) * self.likelihood(x) - - pe = scipy.integrate.quad(f2, 0, 20)[0] - - graph1 = self.get_graph(f1) - graph2 = self.get_graph(f2) - - rects1 = self.get_riemann_rectangles(graph1, dx=self.dx) - rects2 = self.get_riemann_rectangles(graph2, dx=self.dx) - - rects1.set_color(YELLOW_D) - rects2.set_color(BLUE) - for rects in rects1, rects2: - rects.set_stroke(WHITE, 1) - - rects1.save_state() - rects1.stretch(0, 1, about_edge=DOWN) - - formula = self.get_formula() - - self.play( - FadeInFromDown(formula[:4]), - Restore(rects1, lag_ratio=0.05, run_time=2) - ) - self.wait() - self.add(rects1.copy().set_opacity(0.4)) - self.play( - FadeInFromDown(formula[4:10]), - Transform(rects1, rects2), - ) - self.wait() - self.play( - rects1.stretch, 1 / pe, 1, {"about_edge": DOWN}, - Write(formula[10:], run_time=1) - ) - self.wait() - - def get_formula(self): - formula = TexMobject( - "p(H) p(E|H) \\over p(E)", - tex_to_color_map={ - "H": HYPOTHESIS_COLOR, - "E": EVIDENCE_COLOR1, - }, - substrings_to_isolate=list("p(|)") - ) - formula.move_to(self.formula_position) - return formula - - def prior(self, x): - return (x**3 / 6) * np.exp(-x) - - def likelihood(self, x): - return np.exp(-0.5 * x) - - -class ComingUp(Scene): - CONFIG = { - "camera_config": {"background_color": DARK_GREY} - } - - def construct(self): - rect = ScreenRectangle() - rect.set_height(6) - rect.set_fill(BLACK, 1) - rect.set_stroke(WHITE, 2) - - words = TextMobject("Later...") - words.scale(2) - words.to_edge(UP) - rect.next_to(words, DOWN) - - self.add(rect) - self.play(FadeIn(words)) - self.wait() - - -class QuestionSteveConclusion(HeartOfBayesTheorem, DescriptionOfSteve): - def construct(self): - # Setup - steve = Steve() - steve.shift(UP) - self.add(steve) - - kt = Group( - ImageMobject("kahneman"), - ImageMobject("tversky"), - ) - kt.arrange(DOWN) - kt.set_height(6) - randy = Randolph() - kt.next_to(randy, RIGHT, LARGE_BUFF) - randy.align_to(kt, DOWN) - - farmers = VGroup(*[Farmer() for x in range(20)]) - farmers.arrange_in_grid(n_cols=5) - people = VGroup(Librarian(), farmers) - people.arrange(RIGHT, aligned_edge=UP) - people.set_height(3) - people.next_to(randy.get_corner(UL), UP) - cross = Cross(people) - cross.set_stroke(RED, 8) - - # Question K&T - self.play( - steve.scale, 0.5, - steve.to_corner, DL, - FadeIn(randy), - FadeInFromDown(kt, lag_ratio=0.3), - ) - self.play(randy.change, "sassy") - self.wait() - self.play( - FadeInFrom(people, RIGHT, lag_ratio=0.01), - randy.change, "raise_left_hand", people, - ) - self.wait() - self.play( - ShowCreation(cross), - randy.change, "angry" - ) - self.wait() - - # Who is Steve? - people.add(cross) - self.play( - people.scale, 0.3, - people.to_corner, UL, - steve.scale, 1.5, - steve.next_to, randy.get_corner(UL), LEFT, - randy.change, "pondering", steve, - ) - self.play(randy.look_at, steve) - self.play(Blink(randy)) - - kt.generate_target() - steve.generate_target() - steve.target.set_height(0.9 * kt[0].get_height()) - group = Group(kt.target[0], steve.target, kt.target[1]) - group.arrange(RIGHT) - group.to_edge(RIGHT) - - self.play( - MoveToTarget(kt), - MoveToTarget(steve), - randy.shift, 2 * LEFT, - randy.change, 'erm', kt.target, - FadeOutAndShift(people, 2 * LEFT), - ) - self.remove(people, cross) - self.play(Blink(randy)) - self.wait() - - jessy = Randolph(color=BLUE_B) - jessy.next_to(randy, LEFT, MED_LARGE_BUFF) - steve.target.match_height(randy) - steve.target.next_to(randy, RIGHT, MED_LARGE_BUFF) - morty = Mortimer() - morty.next_to(steve.target, RIGHT, MED_LARGE_BUFF) - morty.look_at(steve.target), - jessy.look_at(steve.target), - VGroup(jessy, morty, steve.target).to_edge(DOWN) - pis = VGroup(randy, jessy, morty) - - self.play( - LaggedStartMap(FadeOutAndShift, kt, lambda m: (m, 3 * UR)), - MoveToTarget(steve), - randy.to_edge, DOWN, - randy.change, "happy", steve.target, - FadeIn(jessy), - FadeIn(morty), - ) - self.play(LaggedStart(*[ - ApplyMethod(pi.change, "hooray", steve) - for pi in pis - ])) - self.play(Blink(morty)) - self.play(Blink(jessy)) - - # The assumption changes the prior - diagram = self.get_diagram(0.05, 0.4, 0.1) - diagram.nhne_rect.set_fill(DARK_GREY) - diagram.set_height(3.5) - diagram.center().to_edge(UP, buff=MED_SMALL_BUFF) - - self.play( - FadeIn(diagram), - *[ - ApplyMethod(pi.change, "pondering", diagram) - for pi in pis - ], - ) - self.play(diagram.set_prior, 0.5) - self.play(Blink(jessy)) - self.wait() - self.play(Blink(morty)) - self.play( - morty.change, "raise_right_hand", diagram, - ApplyMethod(diagram.set_prior, 0.9, run_time=2), - ) - self.play(Blink(randy)) - self.wait() - - # Likelihood of description - description = self.get_description() - description.scale(0.5) - description.to_corner(UL) - - self.play( - FadeIn(description), - *[ApplyMethod(pi.change, "sassy", description) for pi in pis] - ) - self.play( - diagram.set_likelihood, 0.2, - run_time=2, - ) - self.play( - diagram.set_antilikelihood, 0.5, - run_time=2, - ) - self.play(Blink(jessy)) - self.play(Blink(randy)) - self.wait() - - # Focus on diagram - diagram.generate_target() - diagram.target.set_height(6) - diagram.target.move_to(3 * LEFT) - - formula = get_bayes_formula() - formula.scale(0.75) - formula.to_corner(UR) - - self.play( - FadeInFromDown(formula), - LaggedStart(*[ - ApplyMethod(pi.change, "thinking", formula) - for pi in pis - ]) - ) - self.play(Blink(randy)) - self.play( - LaggedStartMap( - FadeOutAndShiftDown, - VGroup(description, *pis, steve), - ), - MoveToTarget(diagram, run_time=3), - ApplyMethod( - formula.scale, 1.5, {"about_edge": UR}, - run_time=2.5, - ), - ) - self.wait() - - kw = {"run_time": 2} - self.play(diagram.set_prior, 0.1, **kw) - self.play(diagram.set_prior, 0.6, **kw) - self.play(diagram.set_likelihood, 0.5, **kw), - self.play(diagram.set_antilikelihood, 0.1, **kw), - self.wait() - - -class WhoAreYou(Scene): - def construct(self): - words = TextMobject("Who are you?") - self.add(words) - - -class FadeInHeart(Scene): - def construct(self): - heart = SuitSymbol("hearts") - - self.play(FadeInFromDown(heart)) - self.play(FadeOut(heart)) - - -class ReprogrammingThought(Scene): - CONFIG = { - "camera_config": { - "background_color": DARKER_GREY, - } - } - - def construct(self): - brain = SVGMobject("brain") - brain.set_fill(GREY, 1) - brain.set_sheen(1, UL) - brain.set_stroke(width=0) - - arrow = DoubleArrow(ORIGIN, 3 * RIGHT) - - formula = get_bayes_formula() - - group = VGroup(brain, arrow, formula) - group.arrange(RIGHT) - group.center() - - q_marks = TexMobject("???") - q_marks.scale(1.5) - q_marks.next_to(arrow, UP, SMALL_BUFF) - - kt = Group( - ImageMobject("kahneman"), - ImageMobject("tversky"), - ) - kt.arrange(RIGHT) - kt.set_height(2) - kt.to_corner(UR) - - brain_outline = brain.copy() - brain_outline.set_fill(opacity=0) - brain_outline.set_stroke(TEAL, 4) - - self.play(FadeInFrom(brain, RIGHT)) - self.play( - GrowFromCenter(arrow), - LaggedStartMap(FadeInFromDown, q_marks[0]), - run_time=1 - ) - self.play(FadeInFrom(formula, LEFT)) - self.wait() - - kw = {"run_time": 1, "lag_ratio": 0.3} - self.play(LaggedStartMap(FadeInFromDown, kt, **kw)) - self.play(LaggedStartMap(FadeOut, kt, **kw)) - self.wait() - - self.add(brain) - self.play(ShowCreationThenFadeOut( - brain_outline, - lag_ratio=0.01, - run_time=2 - )) - - # Bubble - bubble = ThoughtBubble() - bubble.next_to(brain, UR, SMALL_BUFF) - bubble.shift(2 * DOWN) - - diagram = BayesDiagram(0.25, 0.8, 0.5) - diagram.set_height(2.5) - diagram.move_to(bubble.get_bubble_center()) - - group = VGroup(brain, arrow, q_marks, formula) - - self.play( - DrawBorderThenFill(VGroup(*reversed(bubble))), - group.shift, 2 * DOWN, - ) - self.play(FadeIn(diagram)) - self.wait() - self.play( - q_marks.scale, 1.5, - q_marks.space_out_submobjects, 1.5, - q_marks.set_opacity, 0, - ) - self.remove(q_marks) - self.wait() - - # Move parts - prior_outline = formula[7:12].copy() - prior_outline.set_stroke(YELLOW, 5, background=True) - like_outline = formula[12:18].copy() - like_outline.set_stroke(BLUE, 5, background=True) - - self.play( - FadeIn(prior_outline), - ApplyMethod(diagram.set_prior, 0.5, run_time=2) - ) - self.play(FadeOut(prior_outline)) - self.play( - FadeIn(like_outline), - ApplyMethod(diagram.set_likelihood, 0.2, run_time=2), - ) - self.play(FadeOut(like_outline)) - self.wait() - - -class MassOfEarthEstimates(GlimpseOfNextVideo): - CONFIG = { - "add_x_coords": False, - "formula_position": 2 * UP + 0.5 * RIGHT, - "dx": 0.05, - } - - def setup(self): - super().setup() - earth = SVGMobject( - file_name="earth", - height=1.5, - fill_color=BLACK, - ) - earth.set_stroke(width=0) - # earth.set_stroke(BLACK, 1, background=True) - circle = Circle( - stroke_width=3, - stroke_color=GREEN, - fill_opacity=1, - fill_color=BLUE_C, - ) - circle.replace(earth) - earth.add_to_back(circle) - earth.set_height(0.75) - - words = TextMobject("Mass of ") - words.next_to(earth, LEFT) - group = VGroup(words, earth) - - group.to_edge(DOWN).shift(2 * RIGHT) - self.add(group) - - def get_formula(self): - formula = TexMobject( - "p(M) p(\\text{data}|M) \\over p(\\text{data})", - tex_to_color_map={ - "M": HYPOTHESIS_COLOR, - "\\text{data}": EVIDENCE_COLOR1, - }, - substrings_to_isolate=list("p(|)") - ) - formula.move_to(self.formula_position) - return formula - - def prior(self, x, mu=6, sigma=1): - factor = (1 / sigma / np.sqrt(TAU)) - return factor * np.exp(-0.5 * ((x - mu) / sigma)**2) - - def likelihood(self, x): - return self.prior(x, 5, 1) - - -class ShowProgrammer(Scene): - CONFIG = { - "camera_config": { - "background_color": DARKER_GREY, - } - } - - def construct(self): - programmer = SVGMobject(file_name="programmer") - programmer.set_stroke(width=0) - programmer.set_fill(GREY, 1) - programmer.set_sheen(1, UL) - programmer.set_height(3) - - programmer.to_corner(DL) - self.play(FadeInFrom(programmer, DOWN)) - self.wait() - - -class BayesEndScene(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "Juan Benet", - "Vassili Philippov", - "Burt Humburg", - "D. Sivakumar", - "John Le", - "Matt Russell", - "Scott Gray", - "soekul", - "Steven Braun", - "Tihan Seale", - "Ali Yahya", - "Arthur Zey", - "dave nicponski", - "Joseph Kelly", - "Kaustuv DeBiswas", - "Lambda AI Hardware", - "Lukas Biewald", - "Mark Heising", - "Nicholas Cahill", - "Peter Mcinerney", - "Quantopian", - "Scott Walter, Ph.D.", - "Tauba Auerbach", - "Yana Chernobilsky", - "Yu Jun", - "Lukas -krtek.net- Novy", - "Britt Selvitelle", - "Britton Finley", - "David Gow", - "J", - "Jonathan Wilson", - "Joseph John Cox", - "Magnus Dahlström", - "Matteo Delabre", - "Randy C. Will", - "Ray Hua Wu", - "Ryan Atallah", - "Luc Ritchie", - "1stViewMaths", - "Adam Dřínek", - "Aidan Shenkman", - "Alan Stein", - "Alex Mijalis", - "Alexis Olson", - "Andreas Benjamin Brössel", - "Andrew Busey", - "Andrew Cary", - "Andrew R. Whalley", - "Anthony Turvey", - "Antoine Bruguier", - "Antonio Juarez", - "Arjun Chakroborty", - "Austin Goodman", - "Avi Finkel", - "Awoo", - "Azeem Ansar", - "AZsorcerer", - "Barry Fam", - "Bernd Sing", - "Boris Veselinovich", - "Bradley Pirtle", - "Brian Staroselsky", - "Calvin Lin", - "Chaitanya Upmanu", - "Charles Southerland", - "Charlie N", - "Chenna Kautilya", - "Chris Connett", - "Christian Kaiser", - "Clark Gaebel", - "Cooper Jones", - "Corey Ogburn", - "Danger Dai", - "Daniel Herrera C", - "Dave B", - "Dave Kester", - "David B. Hill", - "David Clark", - "David Pratt", - "DeathByShrimp", - "Delton Ding", - "Dominik Wagner", - "eaglle", - "emptymachine", - "Eric Younge", - "Eryq Ouithaqueue", - "Federico Lebron", - "Fernando Via Canel", - "Frank R. Brown, Jr.", - "Giovanni Filippi", - "Hal Hildebrand", - "Hitoshi Yamauchi", - "Ivan Sorokin", - "j eduardo perez", - "Jacob Baxter", - "Jacob Harmon", - "Jacob Hartmann", - "Jacob Magnuson", - "Jameel Syed", - "James Liao", - "Jason Hise", - "Jayne Gabriele", - "Jeff Linse", - "Jeff Straathof", - "John C. Vesey", - "John Griffith", - "John Haley", - "John V Wertheim", - "Jonathan Heckerman", - "Josh Kinnear", - "Joshua Claeys", - "Kai-Siang Ang", - "Kanan Gill", - "Kartik Cating-Subramanian", - "L0j1k", - "Lee Redden", - "Linh Tran", - "Ludwig Schubert", - "Magister Mugit", - "Mark B Bahu", - "Mark Mann", - "Martin Price", - "Mathias Jansson", - "Matt Godbolt", - "Matt Langford", - "Matt Roveto", - "Matthew Bouchard", - "Matthew Cocke", - "Michael Hardel", - "Michael W White", - "Mirik Gogri", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nikita Lesnikov", - "Omar Zrien", - "Owen Campbell-Moore", - "Patrick Lucas", - "Pedro Igor S. Budib", - "Peter Ehrnstrom", - "rehmi post", - "Rex Godby", - "Richard Barthel", - "Ripta Pasay", - "Rish Kundalia", - "Roman Sergeychik", - "Roobie", - "SansWord Huang", - "Sebastian Garcia", - "Solara570", - "Steven Siddals", - "Stevie Metke", - "Suthen Thomas", - "Tal Einav", - "Ted Suzman", - "The Responsible One", - "Thomas Roets", - "Thomas Tarler", - "Tianyu Ge", - "Tom Fleming", - "Tyler VanValkenburg", - "Valeriy Skobelev", - "Veritasium", - "Vinicius Reis", - "Xuanji Li", - "Yavor Ivanov", - "YinYangBalance.Asia", - ], - } - - -class Thumbnail(Scene): - def construct(self): - diagram = BayesDiagram(0.25, 0.4, 0.1) - diagram.set_height(3) - diagram.add_brace_attrs() - braces = VGroup( - diagram.h_brace, - diagram.he_brace, - diagram.nhe_brace, - ) - diagram.add(*braces) - - kw = { - "tex_to_color_map": { - "H": YELLOW, - "E": BLUE, - "\\neg": RED, - } - } - labels = VGroup( - TexMobject("P(H)", **kw), - TexMobject("P(E|H)", **kw), - TexMobject("P(E|\\neg H)", **kw), - ) - labels.scale(1) - - for label, brace, vect in zip(labels, braces, [DOWN, LEFT, RIGHT]): - label.next_to(brace, vect) - - diagram.add(*labels) - - # diagram.set_height(6) - diagram.to_edge(DOWN, buff=MED_SMALL_BUFF) - diagram.shift(2 * LEFT) - self.add(diagram) - - diagram.set_height(FRAME_HEIGHT - 1) - diagram.center().to_edge(DOWN) - for rect in diagram.evidence_split: - rect.set_sheen(0.2, UL) - return - - # Formula - formula = get_bayes_formula() - formula.scale(1.5) - formula.to_corner(UL) - - frac = VGroup( - diagram.he_rect.copy(), - Line(ORIGIN, 4 * RIGHT).set_stroke(WHITE, 3), - VGroup( - diagram.he_rect.copy(), - TexMobject("+"), - diagram.nhe_rect.copy(), - ).arrange(RIGHT) - ) - frac.arrange(DOWN) - equals = TexMobject("=") - equals.next_to(formula, RIGHT) - frac.next_to(equals, RIGHT) - - self.add(formula) - self.add(equals, frac) - - VGroup(formula, equals, frac).to_edge(UP, buff=SMALL_BUFF) diff --git a/from_3b1b/active/covid.py b/from_3b1b/active/covid.py deleted file mode 100644 index d0a8c0a0..00000000 --- a/from_3b1b/active/covid.py +++ /dev/null @@ -1,2109 +0,0 @@ -from manimlib.imports import * -import scipy.stats - - -CASE_DATA = [ - 9, - 15, - 30, - 40, - 56, - 66, - 84, - 102, - 131, - 159, - 173, - 186, - 190, - 221, - 248, - 278, - 330, - 354, - 382, - 461, - 481, - 526, - 587, - 608, - 697, - 781, - 896, - 999, - 1124, - 1212, - 1385, - 1715, - 2055, - 2429, - 2764, - 3323, - 4288, - 5364, - 6780, - 8555, - 10288, - 12742, - 14901, - 17865, - 21395, - # 25404, - # 29256, - # 33627, - # 38170, - # 45421, - # 53873, -] -SICKLY_GREEN = "#9BBD37" - - -class IntroducePlot(Scene): - def construct(self): - axes = self.get_axes() - self.add(axes) - - # Dots - dots = VGroup() - for day, nc in zip(it.count(1), CASE_DATA): - dot = Dot() - dot.set_height(0.075) - dot.x = day - dot.y = nc - dot.axes = axes - dot.add_updater(lambda d: d.move_to(d.axes.c2p(d.x, d.y))) - dots.add(dot) - dots.set_color(YELLOW) - - # Rescale y axis - origin = axes.c2p(0, 0) - axes.y_axis.tick_marks.save_state() - for tick in axes.y_axis.tick_marks: - tick.match_width(axes.y_axis.tick_marks[0]) - axes.y_axis.add( - axes.h_lines, - axes.small_h_lines, - axes.tiny_h_lines, - axes.tiny_ticks, - ) - axes.y_axis.stretch(25, 1, about_point=origin) - dots.update() - - self.add(axes.small_y_labels) - self.add(axes.tiny_y_labels) - - # Add title - title = self.get_title(axes) - self.add(title) - - # Introduce the data - day = 10 - self.add(*dots[:day + 1]) - - dot = Dot() - dot.match_style(dots[day]) - dot.replace(dots[day]) - count = Integer(CASE_DATA[day]) - count.add_updater(lambda m: m.next_to(dot, UP)) - count.add_updater(lambda m: m.set_stroke(BLACK, 5, background=True)) - - v_line = Line(DOWN, UP) - v_line.set_stroke(YELLOW, 1) - v_line.add_updater( - lambda m: m.put_start_and_end_on( - axes.c2p( - axes.x_axis.p2n(dot.get_center()), - 0, - ), - dot.get_bottom(), - ) - ) - - self.add(dot) - self.add(count) - self.add(v_line) - - for new_day in range(day + 1, len(dots)): - new_dot = dots[new_day] - new_dot.update() - line = Line(dot.get_center(), new_dot.get_center()) - line.set_stroke(PINK, 3) - - self.add(line, dot) - self.play( - dot.move_to, new_dot.get_center(), - dot.set_color, RED, - ChangeDecimalToValue(count, CASE_DATA[new_day]), - ShowCreation(line), - ) - line.rotate(PI) - self.play( - dot.set_color, YELLOW, - Uncreate(line), - run_time=0.5 - ) - self.add(dots[new_day]) - - day = new_day - - if day == 27: - self.add( - axes.y_axis, axes.tiny_y_labels, axes.tiny_h_lines, axes.tiny_ticks, - title - ) - self.play( - axes.y_axis.stretch, 0.2, 1, {"about_point": origin}, - VFadeOut(axes.tiny_y_labels), - VFadeOut(axes.tiny_h_lines), - VFadeOut(axes.tiny_ticks), - MaintainPositionRelativeTo(dot, dots[new_day]), - run_time=2, - ) - self.add(axes, title, *dots[:new_day]) - if day == 36: - self.add(axes.y_axis, axes.small_y_labels, axes.small_h_lines, title) - self.play( - axes.y_axis.stretch, 0.2, 1, {"about_point": origin}, - VFadeOut(axes.small_y_labels), - VFadeOut(axes.small_h_lines), - MaintainPositionRelativeTo(dot, dots[new_day]), - run_time=2, - ) - self.add(axes, title, *dots[:new_day]) - - count.add_background_rectangle() - count.background_rectangle.stretch(1.1, 0) - self.add(count) - - # Show multiplications - last_label = VectorizedPoint(dots[25].get_center()) - last_line = VMobject() - for d1, d2 in zip(dots[25:], dots[26:]): - line = Line( - d1.get_top(), - d2.get_corner(UL), - path_arc=-90 * DEGREES, - ) - line.set_stroke(PINK, 2) - - label = VGroup( - TexMobject("\\times"), - DecimalNumber( - axes.y_axis.p2n(d2.get_center()) / - axes.y_axis.p2n(d1.get_center()), - ) - ) - label.arrange(RIGHT, buff=SMALL_BUFF) - label.set_height(0.25) - label.next_to(line.point_from_proportion(0.5), UL, SMALL_BUFF) - label.match_color(line) - label.add_background_rectangle() - label.save_state() - label.move_to(last_label) - label.set_opacity(0) - - self.play( - ShowCreation(line), - Restore(label), - last_label.move_to, label.saved_state, - VFadeOut(last_label), - FadeOut(last_line), - ) - last_line = line - last_label = label - self.wait() - self.play( - FadeOut(last_label), - FadeOut(last_line), - ) - - # - def get_title(self, axes): - title = TextMobject( - "Recorded COVID-19 cases\\\\outside mainland China", - tex_to_color_map={"COVID-19": RED} - ) - title.next_to(axes.c2p(0, 1e3), RIGHT, LARGE_BUFF) - title.to_edge(UP) - title.add_background_rectangle() - return title - - def get_axes(self, width=12, height=6): - n_cases = len(CASE_DATA) - axes = Axes( - x_min=0, - x_max=n_cases, - x_axis_config={ - "tick_frequency": 1, - "include_tip": False, - }, - y_min=0, - y_max=25000, - y_axis_config={ - "unit_size": 1 / 2500, - "tick_frequency": 1000, - "include_tip": False, - } - ) - axes.x_axis.set_width( - width, - stretch=True, - about_point=axes.c2p(0, 0), - ) - axes.y_axis.set_height( - height, - stretch=True, - about_point=axes.c2p(0, 0), - ) - axes.center() - axes.to_edge(DOWN, buff=LARGE_BUFF) - - # Add dates - text_pos_pairs = [ - ("Mar 6", 0), - ("Feb 23", -12), - ("Feb 12", -23), - ("Feb 1", -34), - ("Jan 22", -44), - ] - labels = VGroup() - extra_ticks = VGroup() - for text, pos in text_pos_pairs: - label = TextMobject(text) - label.set_height(0.2) - label.rotate(45 * DEGREES) - axis_point = axes.c2p(n_cases + pos, 0) - label.move_to(axis_point, UR) - label.shift(MED_SMALL_BUFF * DOWN) - label.shift(SMALL_BUFF * RIGHT) - labels.add(label) - - tick = Line(UP, DOWN) - tick.set_stroke(GREEN, 3) - tick.set_height(0.25) - tick.move_to(axis_point) - extra_ticks.add(tick) - - axes.x_labels = labels - axes.extra_x_ticks = extra_ticks - axes.add(labels, extra_ticks) - - # Adjust y ticks - axes.y_axis.tick_marks.stretch(0.5, 0) - axes.y_axis.tick_marks[0::5].stretch(2, 0) - - # Add y_axis_labels - def get_y_labels(axes, y_values): - labels = VGroup() - for y in y_values: - label = TextMobject(f"{y}k") - label.set_height(0.25) - tick = axes.y_axis.tick_marks[y] - always(label.next_to, tick, LEFT, SMALL_BUFF) - labels.add(label) - return labels - - main_labels = get_y_labels(axes, range(5, 30, 5)) - axes.y_labels = main_labels - axes.add(main_labels) - axes.small_y_labels = get_y_labels(axes, range(1, 6)) - - tiny_labels = VGroup() - tiny_ticks = VGroup() - for y in range(200, 1000, 200): - tick = axes.y_axis.tick_marks[0].copy() - point = axes.c2p(0, y) - tick.move_to(point) - label = Integer(y) - label.set_height(0.25) - always(label.next_to, tick, LEFT, SMALL_BUFF) - tiny_labels.add(label) - tiny_ticks.add(tick) - - axes.tiny_y_labels = tiny_labels - axes.tiny_ticks = tiny_ticks - - # Horizontal lines - axes.h_lines = VGroup() - axes.small_h_lines = VGroup() - axes.tiny_h_lines = VGroup() - group_range_pairs = [ - (axes.h_lines, 5e3 * np.arange(1, 6)), - (axes.small_h_lines, 1e3 * np.arange(1, 5)), - (axes.tiny_h_lines, 200 * np.arange(1, 5)), - ] - for group, _range in group_range_pairs: - for y in _range: - group.add( - Line( - axes.c2p(0, y), - axes.c2p(n_cases, y), - ) - ) - group.set_stroke(WHITE, 1, opacity=0.5) - - return axes - - -class Thumbnail(IntroducePlot): - def construct(self): - axes = self.get_axes() - self.add(axes) - - dots = VGroup() - data = CASE_DATA - data.append(25398) - for day, nc in zip(it.count(1), CASE_DATA): - dot = Dot() - dot.set_height(0.2) - dot.x = day - dot.y = nc - dot.axes = axes - dot.add_updater(lambda d: d.move_to(d.axes.c2p(d.x, d.y))) - dots.add(dot) - dots.set_color(YELLOW) - dots.set_submobject_colors_by_gradient(BLUE, GREEN, RED) - - self.add(dots) - - title = TextMobject("COVID-19") - title.set_height(1) - title.set_color(RED) - title.to_edge(UP, buff=LARGE_BUFF) - - subtitle = TextMobject("and exponential growth") - subtitle.match_width(title) - subtitle.next_to(title, DOWN) - - # self.add(title) - # self.add(subtitle) - - title = TextMobject("How is ", "COVID-19\\\\", "currently growing?") - title[1].set_color(RED) - title.set_height(2.5) - title.to_edge(UP, buff=LARGE_BUFF) - self.add(title) - - # self.remove(words) - # words = TextMobject("Exponential growth") - # words.move_to(ORIGIN, DL) - # words.apply_function( - # lambda p: [ - # p[0], p[1] + np.exp(0.2 * p[0]), p[2] - # ] - # ) - # self.add(words) - - self.embed() - - -class IntroQuestion(Scene): - def construct(self): - questions = VGroup( - TextMobject("What is exponential growth?"), - TextMobject("Where does it come from?"), - TextMobject("What does it imply?"), - TextMobject("When does it stop?"), - ) - questions.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - - for question in questions: - self.play(FadeInFrom(question, RIGHT)) - self.wait() - self.play(LaggedStartMap( - FadeOutAndShift, questions, - lambda m: (m, DOWN), - )) - - -class ViralSpreadModel(Scene): - CONFIG = { - "num_neighbors": 5, - "infection_probability": 0.3, - "random_seed": 1, - } - - def construct(self): - # Init population - randys = self.get_randys() - self.add(*randys) - - # Show the sicko - self.show_patient0(randys) - - # Repeatedly spread - for x in range(20): - self.spread_infection(randys) - - def get_randys(self): - randys = VGroup(*[ - Randolph() - for x in range(150) - ]) - for randy in randys: - randy.set_height(0.5) - randys.arrange_in_grid(10, 15, buff=0.5) - randys.set_height(FRAME_HEIGHT - 1) - - for i in range(0, 10, 2): - randys[i * 15:(i + 1) * 15].shift(0.25 * RIGHT) - for randy in randys: - randy.shift(0.2 * random.random() * RIGHT) - randy.shift(0.2 * random.random() * UP) - randy.infected = False - randys.center() - return randys - - def show_patient0(self, randys): - patient0 = random.choice(randys) - patient0.infected = True - - circle = Circle() - circle.set_stroke(SICKLY_GREEN) - circle.replace(patient0) - circle.scale(1.5) - self.play( - patient0.change, "sick", - patient0.set_color, SICKLY_GREEN, - ShowCreationThenFadeOut(circle), - ) - - def spread_infection(self, randys): - E = self.num_neighbors - inf_p = self.infection_probability - - cough_anims = [] - new_infection_anims = [] - - for randy in randys: - if randy.infected: - cough_anims.append(Flash( - randy, - color=SICKLY_GREEN, - num_lines=16, - line_stroke_width=1, - flash_radius=0.5, - line_length=0.1, - )) - random.shuffle(cough_anims) - self.play(LaggedStart( - *cough_anims, - run_time=1, - lag_ratio=1 / len(cough_anims), - )) - - newly_infected = [] - for randy in randys: - if randy.infected: - distances = [ - get_norm(r2.get_center() - randy.get_center()) - for r2 in randys - ] - for i in np.argsort(distances)[1:E + 1]: - r2 = randys[i] - if random.random() < inf_p and not r2.infected and r2 not in newly_infected: - newly_infected.append(r2) - r2.generate_target() - r2.target.change("sick") - r2.target.set_color(SICKLY_GREEN) - new_infection_anims.append(MoveToTarget(r2)) - random.shuffle(new_infection_anims) - self.play(LaggedStart(*new_infection_anims, run_time=1)) - - for randy in newly_infected: - randy.infected = True - - -class GrowthEquation(Scene): - def construct(self): - # Add labels - N_label = TextMobject("$N_d$", " = Number of cases on a given day", ) - E_label = TextMobject("$E$", " = Average number of people someone infected is exposed to each day") - p_label = TextMobject("$p$", " = Probability of each exposure becoming an infection") - - N_label[0].set_color(YELLOW) - E_label[0].set_color(BLUE) - p_label[0].set_color(TEAL) - - labels = VGroup( - N_label, - E_label, - p_label - ) - labels.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - labels.set_width(FRAME_WIDTH - 1) - labels.to_edge(UP) - - for label in labels: - self.play(FadeInFromDown(label)) - self.wait() - - delta_N = TexMobject("\\Delta", "N_d") - delta_N.set_color(YELLOW) - eq = TexMobject("=") - eq.center() - delta_N.next_to(eq, LEFT) - - delta_N_brace = Brace(delta_N, DOWN) - delta_N_text = delta_N_brace.get_text("Change over a day") - - nep = TexMobject("E", "\\cdot", "p", "\\cdot", "N_d") - nep[4].match_color(N_label[0]) - nep[0].match_color(E_label[0]) - nep[2].match_color(p_label[0]) - nep.next_to(eq, RIGHT) - - self.play(FadeIn(delta_N), FadeIn(eq)) - self.play( - GrowFromCenter(delta_N_brace), - FadeInFrom(delta_N_text, 0.5 * UP), - ) - self.wait() - self.play(LaggedStart( - TransformFromCopy(N_label[0], nep[4]), - TransformFromCopy(E_label[0], nep[0]), - TransformFromCopy(p_label[0], nep[2]), - FadeIn(nep[1]), - FadeIn(nep[3]), - lag_ratio=0.2, - run_time=2, - )) - self.wait() - self.play(ShowCreationThenFadeAround( - nep[-1], - surrounding_rectangle_config={"color": RED}, - )) - - # Recursive equation - lhs = TexMobject("N_{d + 1}", "=") - lhs[0].set_color(YELLOW) - lhs.move_to(eq, RIGHT) - lhs.shift(DOWN) - - rhs = VGroup( - nep[-1].copy(), - TexMobject("+"), - nep.copy(), - ) - rhs.arrange(RIGHT) - rhs.next_to(lhs, RIGHT) - - self.play( - FadeOut(delta_N_brace), - FadeOut(delta_N_text), - FadeInFrom(lhs, UP), - ) - self.play(FadeIn(rhs[:2])) - self.play(TransformFromCopy(nep, rhs[2])) - self.wait() - - alt_rhs = TexMobject( - "(", "1", "+", "E", "\\cdot", "p", ")", "N_d", - tex_to_color_map={ - "E": BLUE, - "p": TEAL, - "N_d": YELLOW, - } - ) - new_lhs = lhs.copy() - new_lhs.shift(DOWN) - alt_rhs.next_to(new_lhs, RIGHT) - self.play(TransformFromCopy(lhs, new_lhs)) - - rhs.unlock_triangulation() - self.play( - TransformFromCopy(rhs[0], alt_rhs[7].copy()), - TransformFromCopy(rhs[2][4], alt_rhs[7]), - ) - self.play( - TransformFromCopy(rhs[1][0], alt_rhs[2]), - TransformFromCopy(rhs[2][0], alt_rhs[3]), - TransformFromCopy(rhs[2][1], alt_rhs[4]), - TransformFromCopy(rhs[2][2], alt_rhs[5]), - TransformFromCopy(rhs[2][3], alt_rhs[6]), - FadeIn(alt_rhs[0]), - FadeIn(alt_rhs[1]), - ) - self.wait() - - # Comment on factor - brace = Brace(alt_rhs[:7], DOWN) - text = TextMobject("For example, ", "1.15") - text.next_to(brace, DOWN) - self.play( - GrowFromCenter(brace), - FadeInFrom(text, 0.5 * UP) - ) - self.wait() - - # Show exponential - eq_group = VGroup( - delta_N, eq, nep, - lhs, rhs, - new_lhs, alt_rhs, - brace, - text, - ) - self.clear() - self.add(labels, eq_group) - - self.play(ShowCreationThenFadeAround( - VGroup(delta_N, eq, nep), - surrounding_rectangle_config={"color": RED}, - )) - self.play(ShowCreationThenFadeAround( - VGroup(new_lhs, alt_rhs, brace, text), - surrounding_rectangle_config={"color": RED}, - )) - self.wait() - self.play(eq_group.to_edge, LEFT, LARGE_BUFF) - - exp_eq = TexMobject( - "N_d = (1 + E \\cdot p)^{d} \\cdot N_0", - tex_to_color_map={ - "N_d": YELLOW, - "E": BLUE, - "p": TEAL, - "{d}": YELLOW, - "N_0": YELLOW, - } - ) - exp_eq.next_to(alt_rhs, RIGHT, buff=3) - arrow = Arrow(alt_rhs.get_right(), exp_eq.get_left()) - - self.play( - GrowArrow(arrow), - FadeInFrom(exp_eq, 2 * LEFT) - ) - self.wait() - - # Discuss factor in front of N - ep = nep[:3] - ep_rect = SurroundingRectangle(ep) - ep_rect.set_stroke(RED, 2) - - ep_label = TextMobject("This factor will decrease") - ep_label.next_to(ep_rect, UP, aligned_edge=LEFT) - ep_label.set_color(RED) - - self.play( - ShowCreation(ep_rect), - FadeIn(ep_label, lag_ratio=0.1), - ) - self.wait() - self.play( - FadeOut(ep_rect), - FadeOut(ep_label), - ) - - # Add carrying capacity factor to p - p_factors = TexMobject( - "\\left(1 - {N_d \\over \\text{pop. size}} \\right)", - tex_to_color_map={"N_d": YELLOW}, - ) - p_factors.next_to(nep, RIGHT, buff=3) - p_factors_rect = SurroundingRectangle(p_factors) - p_factors_rect.set_stroke(TEAL, 2) - p_arrow = Arrow( - p_factors_rect.get_corner(UL), - nep[2].get_top(), - path_arc=75 * DEGREES, - color=TEAL, - ) - - self.play( - ShowCreation(p_factors_rect), - ShowCreation(p_arrow) - ) - self.wait() - self.play(Write(p_factors)) - self.wait() - self.play( - FadeOut(p_factors), - FadeOut(p_arrow), - FadeOut(p_factors_rect), - ) - - # Ask about ep shrinking - ep_question = TextMobject("What makes this shrink?") - ep_question.set_color(RED) - ep_question.next_to(ep_rect, UP, aligned_edge=LEFT) - - E_line = Underline(E_label) - E_line.set_color(BLUE) - p_line = Underline(p_label) - p_line.set_color(TEAL) - - self.play( - ShowCreation(ep_rect), - FadeInFrom(ep_question, LEFT) - ) - self.wait() - for line in E_line, p_line: - self.play(ShowCreation(line)) - self.wait() - self.play(FadeOut(line)) - self.wait() - - # Show alternate projections - ep_value = DecimalNumber(0.15) - ep_value.next_to(ep_rect, UP) - - self.play( - FadeOut(ep_question), - FadeIn(ep_value), - FadeOut(text[0]), - text[1].next_to, brace, DOWN, - ) - - eq1 = TexMobject("(", "1.15", ")", "^{61}", "\\cdot", "21{,}000", "=") - eq2 = TexMobject("(", "1.05", ")", "^{61}", "\\cdot", "21{,}000", "=") - eq1_rhs = Integer((1.15**61) * (21000)) - eq2_rhs = Integer((1.05**61) * (21000)) - - for eq, rhs in (eq1, eq1_rhs), (eq2, eq2_rhs): - eq[1].set_color(RED) - eq.move_to(nep) - eq.to_edge(RIGHT, buff=3) - rhs.next_to(eq, RIGHT) - rhs.align_to(eq[-2], UP) - - self.play(FadeIn(eq1)) - for tex in ["21{,}000", "61"]: - self.play(ShowCreationThenFadeOut( - Underline( - eq1.get_part_by_tex(tex), - stroke_color=YELLOW, - stroke_width=2, - buff=SMALL_BUFF, - ), - run_time=2, - )) - value = eq1_rhs.get_value() - eq1_rhs.set_value(0) - self.play(ChangeDecimalToValue(eq1_rhs, value)) - self.wait() - eq1.add(eq1_rhs) - self.play( - eq1.shift, DOWN, - FadeIn(eq2), - ) - - new_text = TextMobject("1.05") - new_text.move_to(text[1]) - self.play( - ChangeDecimalToValue(ep_value, 0.05), - FadeOut(text[1]), - FadeIn(new_text), - ) - - self.wait() - - eq2_rhs.align_to(eq1_rhs, RIGHT) - value = eq2_rhs.get_value() - eq2_rhs.set_value(0) - self.play(ChangeDecimalToValue(eq2_rhs, value)) - self.wait() - - # Pi creature quote - morty = Mortimer() - morty.set_height(1) - morty.next_to(eq2_rhs, UP) - bubble = SpeechBubble( - direction=RIGHT, - height=2.5, - width=5, - ) - bubble.next_to(morty, UL, buff=0) - bubble.write("The only thing to fear\\\\is the lack of fear itself.") - - self.add(morty) - self.add(bubble) - self.add(bubble.content) - - self.play( - labels.set_opacity, 0.5, - VFadeIn(morty), - morty.change, "speaking", - FadeIn(bubble), - Write(bubble.content), - ) - self.play(Blink(morty)) - self.wait() - - -class RescaleToLogarithmic(IntroducePlot): - def construct(self): - # Setup axes - axes = self.get_axes(width=10) - title = self.get_title(axes) - - dots = VGroup() - for day, nc in zip(it.count(1), CASE_DATA): - dot = Dot() - dot.set_height(0.075) - dot.move_to(axes.c2p(day, nc)) - dots.add(dot) - dots.set_color(YELLOW) - - self.add(axes, axes.h_lines, dots, title) - - # Create logarithmic y axis - log_y_axis = NumberLine( - x_min=0, - x_max=9, - ) - log_y_axis.rotate(90 * DEGREES) - log_y_axis.move_to(axes.c2p(0, 0), DOWN) - - labels_text = [ - "10", "100", - "1k", "10k", "100k", - "1M", "10M", "100M", - "1B", - ] - log_y_labels = VGroup() - for text, tick in zip(labels_text, log_y_axis.tick_marks[1:]): - label = TextMobject(text) - label.set_height(0.25) - always(label.next_to, tick, LEFT, SMALL_BUFF) - log_y_labels.add(label) - - # Animate the rescaling to a logarithmic plot - logarithm_title = TextMobject("(Logarithmic scale)") - logarithm_title.set_color(TEAL) - logarithm_title.next_to(title, DOWN) - logarithm_title.add_background_rectangle() - - def scale_logarithmically(p): - result = np.array(p) - y = axes.y_axis.p2n(p) - result[1] = log_y_axis.n2p(np.log10(y))[1] - return result - - log_h_lines = VGroup() - for exponent in range(0, 9): - for mult in range(2, 12, 2): - y = mult * 10**exponent - line = Line( - axes.c2p(0, y), - axes.c2p(axes.x_max, y), - ) - log_h_lines.add(line) - log_h_lines.set_stroke(WHITE, 0.5, opacity=0.5) - log_h_lines[4::5].set_stroke(WHITE, 1, opacity=1) - - movers = [dots, axes.y_axis.tick_marks, axes.h_lines, log_h_lines] - for group in movers: - group.generate_target() - for mob in group.target: - mob.move_to(scale_logarithmically(mob.get_center())) - - log_y_labels.suspend_updating() - log_y_labels.save_state() - for exponent, label in zip(it.count(1), log_y_labels): - label.set_y(axes.y_axis.n2p(10**exponent)[1]) - label.set_opacity(0) - - self.add(log_y_axis) - log_y_axis.save_state() - log_y_axis.tick_marks.set_opacity(0) - log_h_lines.set_opacity(0) - self.wait() - self.add(log_h_lines, title, logarithm_title) - self.play( - MoveToTarget(dots), - MoveToTarget(axes.y_axis.tick_marks), - MoveToTarget(axes.h_lines), - MoveToTarget(log_h_lines), - VFadeOut(axes.y_labels), - VFadeOut(axes.y_axis.tick_marks), - VFadeOut(axes.h_lines), - Restore(log_y_labels), - FadeIn(logarithm_title), - run_time=2, - ) - self.play(Restore(log_y_axis)) - self.wait() - - # Walk up y axis - brace = Brace( - log_y_axis.tick_marks[1:3], - RIGHT, - buff=SMALL_BUFF, - ) - brace_label = brace.get_tex( - "\\times 10", - buff=SMALL_BUFF - ) - VGroup(brace, brace_label).set_color(TEAL) - brace_label.set_stroke(BLACK, 8, background=True) - - self.play( - GrowFromCenter(brace), - FadeIn(brace_label) - ) - brace.add(brace_label) - for i in range(2, 5): - self.play( - brace.next_to, - log_y_axis.tick_marks[i:i + 2], - {"buff": SMALL_BUFF} - ) - self.wait(0.5) - self.play(FadeOut(brace)) - self.wait() - - # Show order of magnitude jumps - remove_anims = [] - for i, j in [(7, 27), (27, 40)]: - line = Line(dots[i].get_center(), dots[j].get_center()) - rect = Rectangle() - rect.set_fill(TEAL, 0.5) - rect.set_stroke(width=0) - rect.replace(line, stretch=True) - label = TextMobject(f"{j - i} days") - label.next_to(rect, UP, SMALL_BUFF) - label.set_color(TEAL) - - rect.save_state() - rect.stretch(0, 0, about_edge=LEFT) - self.play( - Restore(rect), - FadeInFrom(label, LEFT) - ) - self.wait() - - remove_anims += [ - ApplyMethod( - rect.stretch, 0, 0, {"about_edge": RIGHT}, - remover=True, - ), - FadeOutAndShift(label, RIGHT), - ] - self.wait() - - # Linear regression - def c2p(x, y): - xp = axes.x_axis.n2p(x) - yp = log_y_axis.n2p(np.log10(y)) - return np.array([xp[0], yp[1], 0]) - - reg = scipy.stats.linregress( - range(7, len(CASE_DATA)), - np.log10(CASE_DATA[7:]) - ) - x_max = axes.x_max - axes.y_axis = log_y_axis - reg_line = Line( - c2p(0, 10**reg.intercept), - c2p(x_max, 10**(reg.intercept + reg.slope * x_max)), - ) - reg_line.set_stroke(TEAL, 3) - - self.add(reg_line, dots) - dots.set_stroke(BLACK, 3, background=True) - self.play( - LaggedStart(*remove_anims), - ShowCreation(reg_line) - ) - - # Describe linear regression - reg_label = TextMobject("Linear regression") - reg_label.move_to(c2p(25, 10), DOWN) - reg_arrows = VGroup() - for prop in [0.4, 0.6, 0.5]: - reg_arrows.add( - Arrow( - reg_label.get_top(), - reg_line.point_from_proportion(prop), - buff=SMALL_BUFF, - ) - ) - - reg_arrow = reg_arrows[0].copy() - self.play( - Write(reg_label, run_time=1), - Transform(reg_arrow, reg_arrows[1], run_time=2), - VFadeIn(reg_arrow), - ) - self.play(Transform(reg_arrow, reg_arrows[2])) - self.wait() - - # Label slope - slope_label = TextMobject("$\\times 10$ every $16$ days (on average)") - slope_label.set_color(BLUE) - slope_label.set_stroke(BLACK, 8, background=True) - slope_label.rotate(reg_line.get_angle()) - slope_label.move_to(reg_line.get_center()) - slope_label.shift(MED_LARGE_BUFF * UP) - - self.play(FadeIn(slope_label, lag_ratio=0.1)) - self.wait() - - # R^2 label - R2_label = VGroup( - TexMobject("R^2 = "), - DecimalNumber(0, num_decimal_places=3) - ) - R2_label.arrange(RIGHT, aligned_edge=DOWN) - R2_label.next_to(reg_label[0][-1], RIGHT, LARGE_BUFF, aligned_edge=DOWN) - - self.play( - ChangeDecimalToValue(R2_label[1], reg.rvalue**2, run_time=2), - UpdateFromAlphaFunc( - R2_label, - lambda m, a: m.set_opacity(a), - ) - ) - self.wait() - - rect = SurroundingRectangle(R2_label, buff=0.15) - rect.set_stroke(YELLOW, 3) - rect.set_fill(BLACK, 0) - self.add(rect, R2_label) - self.play(ShowCreation(rect)) - self.play( - rect.set_stroke, WHITE, 2, - rect.set_fill, GREY_E, 1, - ) - self.wait() - self.play( - FadeOut(rect), - FadeOut(R2_label), - FadeOut(reg_label), - FadeOut(reg_arrow), - ) - - # Zoom out - extended_x_axis = NumberLine( - x_min=axes.x_axis.x_max, - x_max=axes.x_axis.x_max + 90, - unit_size=get_norm( - axes.x_axis.n2p(1) - - axes.x_axis.n2p(0) - ), - numbers_with_elongated_ticks=[], - ) - extended_x_axis.move_to(axes.x_axis.get_right(), LEFT) - self.play( - self.camera.frame.scale, 2, {"about_edge": DL}, - self.camera.frame.shift, 2.5 * DOWN + RIGHT, - log_h_lines.stretch, 3, 0, {"about_edge": LEFT}, - ShowCreation(extended_x_axis, rate_func=squish_rate_func(smooth, 0.5, 1)), - run_time=3, - ) - self.play( - reg_line.scale, 3, {"about_point": reg_line.get_start()} - ) - self.wait() - - # Show future projections - target_ys = [1e6, 1e7, 1e8, 1e9] - last_point = dots[-1].get_center() - last_label = None - last_rect = None - - date_labels_text = [ - "Apr 5", - "Apr 22", - "May 9", - "May 26", - ] - - for target_y, date_label_text in zip(target_ys, date_labels_text): - log_y = np.log10(target_y) - x = (log_y - reg.intercept) / reg.slope - line = Line(last_point, c2p(x, target_y)) - rect = Rectangle().replace(line, stretch=True) - rect.set_stroke(width=0) - rect.set_fill(TEAL, 0.5) - label = TextMobject(f"{int(x) - axes.x_max} days") - label.scale(1.5) - label.next_to(rect, UP, SMALL_BUFF) - - date_label = TextMobject(date_label_text) - date_label.set_height(0.25) - date_label.rotate(45 * DEGREES) - axis_point = axes.c2p(int(x), 0) - date_label.move_to(axis_point, UR) - date_label.shift(MED_SMALL_BUFF * DOWN) - date_label.shift(SMALL_BUFF * RIGHT) - - v_line = DashedLine( - axes.c2p(x, 0), - c2p(x, target_y), - ) - v_line.set_stroke(WHITE, 2) - - if target_y is target_ys[-1]: - self.play(self.camera.frame.scale, 1.1, {"about_edge": LEFT}) - - if last_label: - last_label.unlock_triangulation() - self.play( - ReplacementTransform(last_label, label), - ReplacementTransform(last_rect, rect), - ) - else: - rect.save_state() - rect.stretch(0, 0, about_edge=LEFT) - self.play(Restore(rect), FadeInFrom(label, LEFT)) - self.wait() - - self.play( - ShowCreation(v_line), - FadeIn(date_label), - ) - - last_label = label - last_rect = rect - - self.wait() - self.play( - FadeOutAndShift(last_label, RIGHT), - ApplyMethod( - last_rect.stretch, 0, 0, {"about_edge": RIGHT}, - remover=True - ), - ) - - # Show alternate petering out possibilities - def get_dots_along_curve(curve): - x_min = int(axes.x_axis.p2n(curve.get_start())) - x_max = int(axes.x_axis.p2n(curve.get_end())) - result = VGroup() - for x in range(x_min, x_max): - prop = binary_search( - lambda p: axes.x_axis.p2n( - curve.point_from_proportion(p), - ), - x, 0, 1, - ) - prop = prop or 0 - point = curve.point_from_proportion(prop) - dot = Dot(point) - dot.shift(0.02 * (random.random() - 0.5)) - dot.set_height(0.075) - dot.set_color(RED) - result.add(dot) - dots.remove(dots[0]) - return result - - def get_point_from_y(y): - log_y = np.log10(y) - x = (log_y - reg.intercept) / reg.slope - return c2p(x, 10**log_y) - - p100k = get_point_from_y(1e5) - p100M = get_point_from_y(1e8) - curve1 = VMobject() - curve1.append_points([ - dots[-1].get_center(), - p100k, - p100k + 5 * RIGHT, - ]) - curve2 = VMobject() - curve2.append_points([ - dots[-1].get_center(), - p100M, - p100M + 5 * RIGHT + 0.25 * UP, - ]) - - proj_dots1 = get_dots_along_curve(curve1) - proj_dots2 = get_dots_along_curve(curve2) - - for proj_dots in [proj_dots1, proj_dots2]: - self.play(FadeIn(proj_dots, lag_ratio=0.1)) - self.wait() - self.play(FadeOut(proj_dots, lag_ratio=0.1)) - - -class LinRegNote(Scene): - def construct(self): - text = TextMobject("(Starting from when\\\\there were 100 cases)") - text.set_stroke(BLACK, 8, background=True) - self.add(text) - - -class CompareCountries(Scene): - def construct(self): - # Introduce - sk_flag = ImageMobject(os.path.join("flags", "kr")) - au_flag = ImageMobject(os.path.join("flags", "au")) - flags = Group(sk_flag, au_flag) - flags.set_height(3) - flags.arrange(RIGHT, buff=LARGE_BUFF) - flags.next_to(ORIGIN, UP) - - labels = VGroup() - case_numbers = [6593, 64] - for flag, cn in zip(flags, case_numbers): - label = VGroup(Integer(cn), TextMobject("cases")) - label.arrange(RIGHT, buff=MED_SMALL_BUFF) - label[1].align_to(label[0][-1], DOWN) - label.scale(1.5) - label.next_to(flag, DOWN, MED_LARGE_BUFF) - label[0].set_value(0) - labels.add(label) - - self.play(LaggedStartMap(FadeInFromDown, flags, lag_ratio=0.25)) - self.play( - ChangeDecimalToValue(labels[0][0], case_numbers[0]), - ChangeDecimalToValue(labels[1][0], case_numbers[1]), - UpdateFromAlphaFunc( - labels, - lambda m, a: m.set_opacity(a), - ) - ) - self.wait() - - # Compare - arrow = Arrow( - labels[1][0].get_bottom(), - labels[0][0].get_bottom(), - path_arc=-90 * DEGREES, - ) - arrow_label = TextMobject("100x better") - arrow_label.set_color(YELLOW) - arrow_label.next_to(arrow, DOWN) - - alt_arrow_label = TextMobject("1 month behind") - alt_arrow_label.set_color(RED) - alt_arrow_label.next_to(arrow, DOWN) - - self.play(ShowCreation(arrow)) - self.play(FadeInFrom(arrow_label, 0.5 * UP)) - self.wait(2) - self.play( - FadeInFrom(alt_arrow_label, 0.5 * UP), - FadeOutAndShift(arrow_label, 0.5 * DOWN), - ) - self.wait(2) - - -class SARSvs1918(Scene): - def construct(self): - titles = VGroup( - TextMobject("2002 SARS outbreak"), - TextMobject("1918 Spanish flu"), - ) - images = Group( - ImageMobject("sars_icon"), - ImageMobject("spanish_flu"), - ) - for title, vect, color, image in zip(titles, [LEFT, RIGHT], [YELLOW, RED], images): - image.set_height(4) - image.move_to(vect * FRAME_WIDTH / 4) - image.to_edge(UP) - title.scale(1.25) - title.next_to(image, DOWN, MED_LARGE_BUFF) - title.set_color(color) - title.underline = Underline(title) - title.underline.set_stroke(WHITE, 1) - title.add_to_back(title.underline) - - titles[1].underline.match_y(titles[0].underline) - - n_cases_labels = VGroup( - TextMobject("8,096 cases"), - TextMobject("$\\sim$513{,}000{,}000 cases"), - ) - - for n_cases_label, title in zip(n_cases_labels, titles): - n_cases_label.scale(1.25) - n_cases_label.next_to(title, DOWN, MED_LARGE_BUFF) - - for image, title, label in zip(images, titles, n_cases_labels): - self.play( - FadeInFrom(image, DOWN), - Write(title), - run_time=1, - ) - self.play(FadeInFrom(label, UP)) - self.wait() - - -class ViralSpreadModelWithShuffling(ViralSpreadModel): - def construct(self): - # Init population - randys = self.get_randys() - self.add(*randys) - - # Show the sicko - self.show_patient0(randys) - - # Repeatedly spread - for x in range(15): - self.spread_infection(randys) - self.shuffle_randys(randys) - - def shuffle_randys(self, randys): - indices = list(range(len(randys))) - np.random.shuffle(indices) - - anims = [] - for i, randy in zip(indices, randys): - randy.generate_target() - randy.target.move_to(randys[i]) - anims.append(MoveToTarget( - randy, path_arc=30 * DEGREES, - )) - - self.play(LaggedStart( - *anims, - lag_ratio=1 / len(randys), - run_time=3 - )) - - -class SneezingOnNeighbors(Scene): - def construct(self): - randys = VGroup(*[PiCreature() for x in range(3)]) - randys.set_height(1) - randys.arrange(RIGHT) - - self.add(randys) - self.play( - randys[1].change, "sick", - randys[1].set_color, SICKLY_GREEN, - ) - self.play( - Flash( - randys[1], - color=SICKLY_GREEN, - flash_radius=0.8, - ), - randys[0].change, "sassy", randys[1], - randys[2].change, "angry", randys[1], - ) - self.play( - randys[0].change, "sick", - randys[0].set_color, SICKLY_GREEN, - randys[2].change, "sick", - randys[2].set_color, SICKLY_GREEN, - ) - self.play( - Flash( - randys[1], - color=SICKLY_GREEN, - flash_radius=0.8, - ) - ) - self.play( - randys[0].change, "sad", randys[1], - randys[2].change, "tired", randys[1], - ) - self.play( - Flash( - randys[1], - color=SICKLY_GREEN, - flash_radius=0.8, - ) - ) - self.play( - randys[0].change, "angry", randys[1], - randys[2].change, "angry", randys[1], - ) - self.wait() - - -class ViralSpreadModelWithClusters(ViralSpreadModel): - def construct(self): - randys = self.get_randys() - self.add(*randys) - self.show_patient0(randys) - - for x in range(6): - self.spread_infection(randys) - - def get_randys(self): - cluster = VGroup(*[Randolph() for x in range(16)]) - cluster.arrange_in_grid(4, 4) - cluster.set_height(1) - cluster.space_out_submobjects(1.3) - - clusters = VGroup(*[cluster.copy() for x in range(12)]) - clusters.arrange_in_grid(3, 4, buff=LARGE_BUFF) - clusters.set_height(FRAME_HEIGHT - 1) - - for cluster in clusters: - for randy in cluster: - randy.infected = False - - self.add(clusters) - - self.clusters = clusters - return VGroup(*it.chain(*clusters)) - - -class ViralSpreadModelWithClustersAndTravel(ViralSpreadModelWithClusters): - CONFIG = { - "random_seed": 2, - } - - def construct(self): - randys = self.get_randys() - self.add(*randys) - self.show_patient0(randys) - - for x in range(20): - self.spread_infection(randys) - self.travel_between_clusters() - self.update_frame(ignore_skipping=True) - - def travel_between_clusters(self): - reps = VGroup(*[ - random.choice(cluster) - for cluster in self.clusters - ]) - targets = list(reps) - random.shuffle(targets) - - anims = [] - for rep, target in zip(reps, targets): - rep.generate_target() - rep.target.move_to(target) - anims.append(MoveToTarget( - rep, - path_arc=30 * DEGREES, - )) - self.play(LaggedStart(*anims, run_time=3)) - - -class ShowLogisticCurve(Scene): - def construct(self): - # Init axes - axes = self.get_axes() - self.add(axes) - - # Add ODE - ode = TexMobject( - "{dN \\over dt} =", - "c", - "\\left(1 - {N \\over \\text{pop.}}\\right)", - "N", - tex_to_color_map={"N": YELLOW} - ) - ode.set_height(0.75) - ode.center() - ode.to_edge(RIGHT) - ode.shift(1.5 * UP) - self.add(ode) - - # Show curve - curve = axes.get_graph( - lambda x: 8 * smooth(x / 10) + 0.2, - ) - curve.set_stroke(YELLOW, 3) - - curve_title = TextMobject("Logistic curve") - curve_title.set_height(0.75) - curve_title.next_to(curve.get_end(), UL) - - self.play(ShowCreation(curve, run_time=3)) - self.play(FadeIn(curve_title, lag_ratio=0.1)) - self.wait() - - # Early part - line = Line( - curve.point_from_proportion(0), - curve.point_from_proportion(0.25), - ) - rect = Rectangle() - rect.set_stroke(width=0) - rect.set_fill(TEAL, 0.5) - rect.replace(line, stretch=True) - - exp_curve = axes.get_graph( - lambda x: 0.15 * np.exp(0.68 * x) - ) - exp_curve.set_stroke(RED, 3) - - rect.save_state() - rect.stretch(0, 0, about_edge=LEFT) - self.play(Restore(rect)) - self.play(ShowCreation(exp_curve, run_time=4)) - - # Show capacity - line = DashedLine( - axes.c2p(0, 8.2), - axes.c2p(axes.x_max, 8.2), - ) - line.set_stroke(BLUE, 2) - - self.play(ShowCreation(line)) - self.wait() - self.play(FadeOut(rect), FadeOut(exp_curve)) - - # Show inflection point - infl_point = axes.input_to_graph_point(5, curve) - infl_dot = Dot(infl_point) - infl_dot.set_stroke(WHITE, 3) - - curve_up_part = curve.copy() - curve_up_part.pointwise_become_partial(curve, 0, 0.4) - curve_up_part.set_stroke(GREEN) - curve_down_part = curve.copy() - curve_down_part.pointwise_become_partial(curve, 0.4, 1) - curve_down_part.set_stroke(RED) - for part in curve_up_part, curve_down_part: - part.save_state() - part.stretch(0, 1) - part.set_y(axes.c2p(0, 0)[1]) - - pre_dot = curve.copy() - pre_dot.pointwise_become_partial(curve, 0.375, 0.425) - pre_dot.unlock_triangulation() - - infl_name = TextMobject("Inflection point") - infl_name.next_to(infl_dot, LEFT) - - self.play(ReplacementTransform(pre_dot, infl_dot, path_arc=90 * DEGREES)) - self.add(curve_up_part, infl_dot) - self.play(Restore(curve_up_part)) - self.add(curve_down_part, infl_dot) - self.play(Restore(curve_down_part)) - self.wait() - self.play(Write(infl_name, run_time=1)) - self.wait() - - # Show tangent line - x_tracker = ValueTracker(0) - tan_line = Line(LEFT, RIGHT) - tan_line.set_width(5) - tan_line.set_stroke(YELLOW, 2) - - def update_tan_line(line): - x1 = x_tracker.get_value() - x2 = x1 + 0.001 - p1 = axes.input_to_graph_point(x1, curve) - p2 = axes.input_to_graph_point(x2, curve) - angle = angle_of_vector(p2 - p1) - line.rotate(angle - line.get_angle()) - line.move_to(p1) - - tan_line.add_updater(update_tan_line) - - dot = Dot() - dot.scale(0.75) - dot.set_fill(BLUE, 0.75) - dot.add_updater( - lambda m: m.move_to(axes.input_to_graph_point( - x_tracker.get_value(), curve - )) - ) - - self.play( - ShowCreation(tan_line), - FadeInFromLarge(dot), - ) - self.play( - x_tracker.set_value, 5, - run_time=6, - ) - self.wait() - self.play( - x_tracker.set_value, 9.9, - run_time=6, - ) - self.wait() - - # Define growth factor - gf_label = TexMobject( - "\\text{Growth factor} =", - "{\\Delta N_d \\over \\Delta N_{d - 1}}", - tex_to_color_map={ - "\\Delta": WHITE, - "N_d": YELLOW, - "N_{d - 1}": BLUE, - } - ) - gf_label.next_to(infl_dot, RIGHT, LARGE_BUFF) - - numer_label = TextMobject("New cases one day") - denom_label = TextMobject("New cases the\\\\previous day") - - for label, tex, vect in (numer_label, "N_d", UL), (denom_label, "N_{d - 1}", DL): - part = gf_label.get_part_by_tex(tex) - label.match_color(part) - label.next_to(part, vect, LARGE_BUFF) - label.shift(2 * RIGHT) - arrow = Arrow( - label.get_corner(vect[1] * DOWN), - part.get_corner(vect[1] * UP) + 0.25 * LEFT, - buff=0.1, - ) - arrow.match_color(part) - label.add_to_back(arrow) - - self.play( - FadeInFrom(gf_label[0], RIGHT), - FadeInFrom(gf_label[1:], LEFT), - FadeOut(ode) - ) - self.wait() - for label in numer_label, denom_label: - self.play(FadeIn(label, lag_ratio=0.1)) - self.wait() - - # Show example growth factors - self.play(x_tracker.set_value, 1) - - eq = TexMobject("=") - eq.next_to(gf_label, RIGHT) - gf = DecimalNumber(1.15) - gf.set_height(0.4) - gf.next_to(eq, RIGHT) - - def get_growth_factor(): - x1 = x_tracker.get_value() - x0 = x1 - 0.2 - x2 = x1 + 0.2 - p0, p1, p2 = [ - axes.input_to_graph_point(x, curve) - for x in [x0, x1, x2] - ] - return (p2[1] - p1[1]) / (p1[1] - p0[1]) - - gf.add_updater(lambda m: m.set_value(get_growth_factor())) - - self.add(eq, gf) - self.play( - x_tracker.set_value, 5, - run_time=6, - rate_func=linear, - ) - self.wait() - self.play( - x_tracker.set_value, 9, - run_time=6, - rate_func=linear, - ) - - def get_axes(self): - axes = Axes( - x_min=0, - x_max=13, - y_min=0, - y_max=10, - y_axis_config={ - "unit_size": 0.7, - "include_tip": False, - } - ) - axes.center() - axes.to_edge(DOWN) - - x_label = TextMobject("Time") - x_label.next_to(axes.x_axis, UP, aligned_edge=RIGHT) - y_label = TextMobject("N cases") - y_label.next_to(axes.y_axis, RIGHT, aligned_edge=UP) - axes.add(x_label, y_label) - return axes - - -class SubtltyOfGrowthFactorShift(Scene): - def construct(self): - # Set up totals - total_title = TextMobject("Totals") - total_title.add(Underline(total_title)) - total_title.to_edge(UP) - total_title.scale(1.25) - total_title.shift(LEFT) - total_title.set_color(YELLOW) - total_title.shift(LEFT) - - data = CASE_DATA[-4:] - data.append(int(data[-1] + 1.15 * (data[-1] - data[-2]))) - totals = VGroup(*[Integer(value) for value in data]) - totals.scale(1.25) - totals.arrange(DOWN, buff=0.6, aligned_edge=LEFT) - totals.next_to(total_title, DOWN, buff=0.6) - totals[-1].set_color(BLUE) - - # Set up dates - dates = VGroup( - TextMobject("March 3, 2020"), - TextMobject("March 4, 2020"), - TextMobject("March 5, 2020"), - TextMobject("March 6, 2020"), - ) - for date, total in zip(dates, totals): - date.scale(0.75) - date.set_color(LIGHT_GREY) - date.next_to(total, LEFT, buff=0.75, aligned_edge=DOWN) - - # Set up changes - change_arrows = VGroup() - change_labels = VGroup() - for t1, t2 in zip(totals, totals[1:]): - arrow = Arrow( - t1.get_right(), - t2.get_right(), - path_arc=-150 * DEGREES, - buff=0.1, - max_tip_length_to_length_ratio=0.15, - ) - arrow.shift(MED_SMALL_BUFF * RIGHT) - arrow.set_stroke(width=3) - change_arrows.add(arrow) - - diff = t2.get_value() - t1.get_value() - label = Integer(diff, include_sign=True) - label.set_color(GREEN) - label.next_to(arrow, RIGHT) - change_labels.add(label) - - change_labels[-1].set_color(BLUE) - - change_title = TextMobject("Changes") - change_title.add(Underline(change_title).shift(0.128 * UP)) - change_title.scale(1.25) - change_title.set_color(GREEN) - change_title.move_to(change_labels) - change_title.align_to(total_title, UP) - - # Set up growth factors - gf_labels = VGroup() - gf_arrows = VGroup() - for c1, c2 in zip(change_labels, change_labels[1:]): - arrow = Arrow( - c1.get_right(), - c2.get_right(), - path_arc=-150 * DEGREES, - buff=0.1, - max_tip_length_to_length_ratio=0.15, - ) - arrow.set_stroke(width=1) - gf_arrows.add(arrow) - - line = Line(LEFT, RIGHT) - line.match_width(c2) - line.set_stroke(WHITE, 2) - numer = c2.deepcopy() - denom = c1.deepcopy() - frac = VGroup(numer, line, denom) - frac.arrange(DOWN, buff=SMALL_BUFF) - frac.scale(0.7) - frac.next_to(arrow, RIGHT) - eq = TexMobject("=") - eq.next_to(frac, RIGHT) - gf = DecimalNumber(c2.get_value() / c1.get_value()) - gf.next_to(eq, RIGHT) - gf_labels.add(VGroup(frac, eq, gf)) - - gf_title = TextMobject("Growth factors") - gf_title.add(Underline(gf_title)) - gf_title.scale(1.25) - gf_title.move_to(gf_labels[0][-1]) - gf_title.align_to(total_title, DOWN) - - # Add things - self.add(dates, total_title) - self.play( - LaggedStartMap( - FadeInFrom, totals[:-1], - lambda m: (m, UP), - ) - ) - self.wait() - self.play( - ShowCreation(change_arrows[:-1]), - LaggedStartMap( - FadeInFrom, change_labels[:-1], - lambda m: (m, LEFT), - ), - FadeIn(change_title), - ) - self.wait() - self.play( - ShowCreation(gf_arrows[:-1]), - LaggedStartMap(FadeIn, gf_labels[:-1]), - FadeIn(gf_title), - ) - self.wait() - - # Show hypothetical new value - self.play(LaggedStart( - FadeIn(gf_labels[-1]), - FadeIn(gf_arrows[-1]), - FadeIn(change_labels[-1]), - FadeIn(change_arrows[-1]), - FadeIn(totals[-1]), - )) - self.wait() - - # Change it - alt_change = data[-2] - data[-3] - alt_total = data[-2] + alt_change - alt_gf = 1 - - self.play( - ChangeDecimalToValue(gf_labels[-1][-1], alt_gf), - ChangeDecimalToValue(gf_labels[-1][0][0], alt_change), - ChangeDecimalToValue(change_labels[-1], alt_change), - ChangeDecimalToValue(totals[-1], alt_total), - ) - self.wait() - - -class ContrastRandomShufflingWithClustersAndTravel(Scene): - def construct(self): - background = FullScreenFadeRectangle() - background.set_fill(GREY_E) - self.add(background) - - squares = VGroup(*[Square() for x in range(2)]) - squares.set_width(FRAME_WIDTH / 2 - 1) - squares.arrange(RIGHT, buff=0.75) - squares.to_edge(DOWN) - squares.set_fill(BLACK, 1) - squares.stretch(0.8, 1) - self.add(squares) - - titles = VGroup( - TextMobject("Random shuffling"), - TextMobject("Clusters with travel"), - ) - for title, square in zip(titles, squares): - title.scale(1.4) - title.next_to(square, UP) - titles[1].align_to(titles[0], UP) - - self.play(LaggedStartMap( - FadeInFrom, titles, - lambda m: (m, 0.25 * DOWN), - )) - self.wait() - - -class ShowVaryingExpFactor(Scene): - def construct(self): - factor = DecimalNumber(0.15) - rect = BackgroundRectangle(factor, buff=SMALL_BUFF) - rect.set_fill(BLACK, 1) - arrow = Arrow( - factor.get_right(), - factor.get_right() + 4 * RIGHT + 0.5 * DOWN, - ) - - self.add(rect, factor, arrow) - for value in [0.05, 0.25, 0.15]: - self.play( - ChangeDecimalToValue(factor, value), - run_time=3, - ) - self.wait() - - -class ShowVaryingBaseFactor(ShowLogisticCurve): - def construct(self): - factor = DecimalNumber(1.15) - rect = BackgroundRectangle(factor, buff=SMALL_BUFF) - rect.set_fill(BLACK, 1) - - self.add(rect, factor) - for value in [1.05, 1.25, 1.15]: - self.play( - ChangeDecimalToValue(factor, value), - run_time=3, - ) - self.wait() - - -class ShowVaryingExpCurve(ShowLogisticCurve): - def construct(self): - axes = self.get_axes() - self.add(axes) - - curve = axes.get_graph(lambda x: np.exp(0.15 * x)) - curve.set_stroke([BLUE, YELLOW, RED]) - curve.make_jagged() - self.add(curve) - - self.camera.frame.scale(2, about_edge=DOWN) - self.camera.frame.shift(DOWN) - rect = FullScreenFadeRectangle() - rect.set_stroke(WHITE, 3) - rect.set_fill(opacity=0) - self.add(rect) - - for value in [0.05, 0.25, 0.15]: - new_curve = axes.get_graph(lambda x: np.exp(value * x)) - new_curve.set_stroke([BLUE, YELLOW, RED]) - new_curve.make_jagged() - self.play( - Transform(curve, new_curve), - run_time=3, - ) - - -class EndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "1stViewMaths", - "Adam Dřínek", - "Aidan Shenkman", - "Alan Stein", - "Alex Mijalis", - "Alexis Olson", - "Ali Yahya", - "Andrew Busey", - "Andrew Cary", - "Andrew R. Whalley", - "Aravind C V", - "Arjun Chakroborty", - "Arthur Zey", - "Austin Goodman", - "Avi Finkel", - "Awoo", - "AZsorcerer", - "Barry Fam", - "Bernd Sing", - "Boris Veselinovich", - "Bradley Pirtle", - "Brian Staroselsky", - "Britt Selvitelle", - "Britton Finley", - "Burt Humburg", - "Calvin Lin", - "Charles Southerland", - "Charlie N", - "Chenna Kautilya", - "Chris Connett", - "Christian Kaiser", - "cinterloper", - "Clark Gaebel", - "Colwyn Fritze-Moor", - "Cooper Jones", - "Corey Ogburn", - "D. Sivakumar", - "Daniel Herrera C", - "Dave B", - "Dave Kester", - "dave nicponski", - "David B. Hill", - "David Clark", - "David Gow", - "Delton Ding", - "Dominik Wagner", - "Douglas Cantrell", - "emptymachine", - "Eric Younge", - "Eryq Ouithaqueue", - "Federico Lebron", - "Frank R. Brown, Jr.", - "Giovanni Filippi", - "Hal Hildebrand", - "Hitoshi Yamauchi", - "Ivan Sorokin", - "Jacob Baxter", - "Jacob Harmon", - "Jacob Hartmann", - "Jacob Magnuson", - "Jake Vartuli - Schonberg", - "Jameel Syed", - "Jason Hise", - "Jayne Gabriele", - "Jean-Manuel Izaret", - "Jeff Linse", - "Jeff Straathof", - "John C. Vesey", - "John Haley", - "John Le", - "John V Wertheim", - "Jonathan Heckerman", - "Jonathan Wilson", - "Joseph John Cox", - "Joseph Kelly", - "Josh Kinnear", - "Joshua Claeys", - "Juan Benet", - "Kai-Siang Ang", - "Kanan Gill", - "Karl Niu", - "Kartik Cating-Subramanian", - "Kaustuv DeBiswas", - "Killian McGuinness", - "Kros Dai", - "L0j1k", - "Lambda GPU Workstations", - "Lee Redden", - "Linh Tran", - "Luc Ritchie", - "Ludwig Schubert", - "Lukas Biewald", - "Magister Mugit", - "Magnus Dahlström", - "Manoj Rewatkar - RITEK SOLUTIONS", - "Mark Heising", - "Mark Mann", - "Martin Price", - "Mathias Jansson", - "Matt Godbolt", - "Matt Langford", - "Matt Roveto", - "Matt Russell", - "Matteo Delabre", - "Matthew Bouchard", - "Matthew Cocke", - "Mia Parent", - "Michael Hardel", - "Michael W White", - "Mirik Gogri", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nicholas Cahill", - "Nikita Lesnikov", - "Oleg Leonov", - "Oliver Steele", - "Omar Zrien", - "Owen Campbell-Moore", - "Patrick Lucas", - "Peter Ehrnstrom", - "Peter Mcinerney", - "Pierre Lancien", - "Quantopian", - "Randy C. Will", - "rehmi post", - "Rex Godby", - "Ripta Pasay", - "Rish Kundalia", - "Roman Sergeychik", - "Roobie", - "Ryan Atallah", - "Samuel Judge", - "SansWord Huang", - "Scott Gray", - "Scott Walter, Ph.D.", - "Sebastian Garcia", - "soekul", - "Solara570", - "Steve Huynh", - "Steve Sperandeo", - "Steven Braun", - "Steven Siddals", - "Stevie Metke", - "supershabam", - "Suteerth Vishnu", - "Suthen Thomas", - "Tal Einav", - "Tauba Auerbach", - "Ted Suzman", - "Thomas J Sargent", - "Thomas Tarler", - "Tianyu Ge", - "Tihan Seale", - "Tyler VanValkenburg", - "Vassili Philippov", - "Veritasium", - "Vinicius Reis", - "Xuanji Li", - "Yana Chernobilsky", - "Yavor Ivanov", - "YinYangBalance.Asia", - "Yu Jun", - "Yurii Monastyrshyn", - ] - } diff --git a/from_3b1b/active/ctracing.py b/from_3b1b/active/ctracing.py deleted file mode 100644 index 416975f3..00000000 --- a/from_3b1b/active/ctracing.py +++ /dev/null @@ -1,754 +0,0 @@ -from manimlib.imports import * -from from_3b1b.active.sir import * - - -class LastFewMonths(Scene): - def construct(self): - words = TextMobject("Last ", "few\\\\", "months:") - words.set_height(4) - underlines = VGroup() - for word in words: - underline = Line(LEFT, RIGHT) - underline.match_width(word) - underline.next_to(word, DOWN, SMALL_BUFF) - underlines.add(underline) - underlines[0].stretch(1.4, 0, about_edge=LEFT) - underlines.set_color(BLUE) - - # self.play(ShowCreation(underlines)) - self.play(ShowIncreasingSubsets(words, run_time=0.75, rate_func=linear)) - self.wait() - - -class UnemploymentTitle(Scene): - def construct(self): - words = TextMobject("Unemployment claims\\\\per week in the US")[0] - words.set_width(FRAME_WIDTH - 1) - words.to_edge(UP) - arrow = Arrow( - words.get_bottom(), - words.get_bottom() + 3 * RIGHT + 3 * DOWN, - stroke_width=10, - tip_length=0.5, - ) - arrow.set_color(BLUE_E) - words.set_color(BLACK) - self.play( - ShowIncreasingSubsets(words), - ShowCreation(arrow), - ) - self.wait() - - -class ExplainTracing(Scene): - def construct(self): - # Words - words = VGroup( - TextMobject("Testing, ", "Testing, ", "Testing!"), - TextMobject("Contact Tracing"), - ) - words[0].set_color(GREEN) - words[1].set_color(BLUE_B) - words.set_width(FRAME_WIDTH - 2) - words.arrange(DOWN, buff=1) - - self.play(ShowIncreasingSubsets(words[0], rate_func=linear)) - self.wait() - self.play(Write(words[1], run_time=1)) - self.wait() - - self.play( - words[1].to_edge, UP, - FadeOutAndShift(words[0], 6 * UP) - ) - - ct_word = words[1][0] - - # Groups - clusters = VGroup() - for x in range(4): - cluster = VGroup() - for y in range(4): - cluster.add(Randolph()) - cluster.arrange_in_grid(buff=LARGE_BUFF) - clusters.add(cluster) - clusters.scale(0.5) - clusters.arrange_in_grid(buff=2) - clusters.set_height(4) - - self.play(FadeIn(clusters)) - - pis = VGroup() - boxes = VGroup() - for cluster in clusters: - for pi in cluster: - pis.add(pi) - box = SurroundingRectangle(pi, buff=0.05) - boxes.add(box) - pi.box = box - - boxes.set_stroke(WHITE, 1) - - sicky = clusters[0][2] - covid_words = TextMobject("COVID-19\\\\Positive!") - covid_words.set_color(RED) - arrow = Vector(RIGHT, color=RED) - arrow.next_to(sicky, LEFT) - covid_words.next_to(arrow, LEFT, SMALL_BUFF) - - self.play( - sicky.change, "sick", - sicky.set_color, "#9BBD37", - FadeInFrom(covid_words, RIGHT), - GrowArrow(arrow), - ) - self.play(ShowCreation(sicky.box)) - self.wait(2) - anims = [] - for pi in clusters[0]: - if pi is not sicky: - anims.append(ApplyMethod(pi.change, "tired")) - anims.append(ShowCreation(pi.box)) - self.play(*anims) - self.wait() - - self.play(VFadeIn( - boxes[4:], - run_time=2, - rate_func=there_and_back_with_pause, - )) - self.wait() - - self.play(FadeOut( - VGroup( - covid_words, - arrow, - *boxes[:4], - *pis, - ), - lag_ratio=0.1, - run_time=3, - )) - self.play(ct_word.move_to, 2 * UP) - - # Underlines - implies = TexMobject("\\Downarrow") - implies.scale(2) - implies.next_to(ct_word, DOWN, MED_LARGE_BUFF) - loc_tracking = TextMobject("Location Tracking") - loc_tracking.set_color(GREY_BROWN) - loc_tracking.match_height(ct_word) - loc_tracking.next_to(implies, DOWN, MED_LARGE_BUFF) - - q_marks = TexMobject("???") - q_marks.scale(2) - q_marks.next_to(implies, RIGHT) - - cross = Cross(implies) - cross.set_stroke(RED, 7) - - self.play( - Write(implies), - FadeInFrom(loc_tracking, UP) - ) - self.play(FadeIn(q_marks, lag_ratio=0.1)) - self.wait() - - parts = VGroup(ct_word[:7], ct_word[7:]) - lines = VGroup() - for part in parts: - line = Line(part.get_left(), part.get_right()) - line.align_to(part[0], DOWN) - line.shift(0.1 * DOWN) - lines.add(line) - - ct_word.set_stroke(BLACK, 2, background=True) - self.add(lines[1], ct_word) - self.play(ShowCreation(lines[1])) - self.wait() - self.play(ShowCreation(lines[0])) - self.wait() - - self.play( - ShowCreation(cross), - FadeOutAndShift(q_marks, RIGHT), - FadeOut(lines), - ) - self.wait() - - dp_3t = TextMobject("DP-3T") - dp_3t.match_height(ct_word) - dp_3t.move_to(loc_tracking) - dp_3t_long = TextMobject("Decentralized Privacy-Preserving Proximity Tracing") - dp_3t_long.next_to(dp_3t, DOWN, LARGE_BUFF) - - arrow = Vector(UP) - arrow.set_stroke(width=8) - arrow.move_to(implies) - - self.play( - FadeInFromDown(dp_3t), - FadeOut(loc_tracking), - FadeOut(implies), - FadeOut(cross), - ShowCreation(arrow) - ) - self.play(Write(dp_3t_long)) - self.wait() - - -class ContactTracingMisnomer(Scene): - def construct(self): - # Word play - words = TextMobject("Contact ", "Tracing") - words.scale(2) - rects = VGroup(*[ - SurroundingRectangle(word, buff=0.2) - for word in words - ]) - expl1 = TextMobject("Doesn't ``trace'' you...") - expl2 = TextMobject("...or your contacts") - expls = VGroup(expl1, expl2) - colors = [RED, BLUE] - - self.add(words) - for vect, rect, expl, color in zip([UP, DOWN], reversed(rects), expls, colors): - arrow = Vector(-vect) - arrow.next_to(rect, vect, SMALL_BUFF) - expl.next_to(arrow, vect, SMALL_BUFF) - rect.set_color(color) - arrow.set_color(color) - expl.set_color(color) - - self.play( - FadeInFrom(expl, -vect), - GrowArrow(arrow), - ShowCreation(rect), - ) - self.wait() - - self.play(Write( - VGroup(*self.mobjects), - rate_func=lambda t: smooth(1 - t), - run_time=3, - )) - - -class ContactTracingWords(Scene): - def construct(self): - words = TextMobject("Contact\\\\", "Tracing") - words.set_height(4) - for word in words: - self.add(word) - self.wait() - self.wait() - return - self.play(ShowIncreasingSubsets(words)) - self.wait() - self.play( - words.set_height, 1, - words.to_corner, UL, - ) - self.wait() - - -class WanderingDotsWithLines(Scene): - def construct(self): - sim = SIRSimulation( - city_population=20, - person_type=DotPerson, - person_config={ - "color_map": { - "S": GREY, - "I": GREY, - "R": GREY, - }, - "infection_ring_style": { - "stroke_color": YELLOW, - }, - "max_speed": 0.5, - }, - infection_time=100, - ) - - for person in sim.people: - person.set_status("S") - person.infection_start_time += random.random() - - lines = VGroup() - - max_dist = 1.25 - - def update_lines(lines): - lines.remove(*lines.submobjects) - for p1 in sim.people: - for p2 in sim.people: - if p1 is p2: - continue - dist = get_norm(p1.get_center() - p2.get_center()) - if dist < max_dist: - line = Line(p1.get_center(), p2.get_center()) - alpha = (max_dist - dist) / max_dist - line.set_stroke( - interpolate_color(WHITE, RED, alpha), - width=4 * alpha - ) - lines.add(line) - - lines.add_updater(update_lines) - - self.add(lines) - self.add(sim) - self.wait(10) - for person in sim.people: - person.set_status("I") - person.infection_start_time += random.random() - self.wait(50) - - -class WhatAboutPeopleWithoutPhones(TeacherStudentsScene): - def construct(self): - self.student_says( - "What about people\\\\without phones?", - target_mode="sassy", - added_anims=[self.teacher.change, "guilty"] - ) - self.change_student_modes("angry", "angry", "sassy") - self.wait() - self.play(self.teacher.change, "tease") - self.wait() - - words = VectorizedPoint() - words.scale(1.5) - words.to_corner(UL) - - self.play( - FadeInFromDown(words), - RemovePiCreatureBubble(self.students[2]), - *[ - ApplyMethod(pi.change, "pondering", words) - for pi in self.pi_creatures - ] - ) - self.wait(5) - - -class PiGesture1(Scene): - def construct(self): - randy = Randolph(mode="raise_right_hand", height=2) - bubble = randy.get_bubble( - bubble_class=SpeechBubble, - height=2, width=3, - ) - bubble.write("This one's\\\\great") - bubble.content.scale(0.8) - bubble.content.set_color(BLACK) - bubble.set_color(BLACK) - bubble.set_fill(opacity=0) - randy.set_stroke(BLACK, 5, background=True) - self.add(randy, bubble, bubble.content) - - -class PiGesture2(Scene): - def construct(self): - randy = Randolph(mode="raise_left_hand", height=2) - randy.look(UL) - # randy.flip() - randy.set_color(GREY_BROWN) - bubble = randy.get_bubble( - bubble_class=SpeechBubble, - height=2, width=3, - direction=LEFT, - ) - bubble.write("So is\\\\this one") - bubble.content.scale(0.8) - bubble.content.set_color(BLACK) - bubble.set_color(BLACK) - bubble.set_fill(opacity=0) - randy.set_stroke(BLACK, 5, background=True) - self.add(randy, bubble, bubble.content) - - -class PiGesture3(Scene): - def construct(self): - randy = Randolph(mode="hooray", height=2) - randy.flip() - bubble = randy.get_bubble( - bubble_class=SpeechBubble, - height=2, width=3, - direction=LEFT, - ) - bubble.write("And this\\\\one") - bubble.content.scale(0.8) - bubble.content.set_color(BLACK) - bubble.set_color(BLACK) - bubble.set_fill(opacity=0) - randy.set_stroke(BLACK, 5, background=True) - self.add(randy, bubble, bubble.content) - - -class AppleGoogleCoop(Scene): - def construct(self): - logos = Group( - self.get_apple_logo(), - self.get_google_logo(), - ) - for logo in logos: - logo.set_height(2) - apple, google = logos - - logos.arrange(RIGHT, buff=3) - - arrows = VGroup() - for vect, u in zip([UP, DOWN], [0, 1]): - m1, m2 = logos[u], logos[1 - u] - arrows.add(Arrow( - m1.get_edge_center(vect), - m2.get_edge_center(vect), - path_arc=-90 * DEGREES, - buff=MED_LARGE_BUFF, - stroke_width=10, - )) - - self.play(LaggedStart( - Write(apple), - FadeIn(google), - lag_ratio=0.7, - )) - self.wait() - self.play(ShowCreation(arrows, run_time=2)) - self.wait() - - def get_apple_logo(self): - result = SVGMobject("apple_logo") - result.set_color("#b3b3b3") - return result - - def get_google_logo(self): - result = ImageMobject("google_logo_black") - return result - - -class LocationTracking(Scene): - def construct(self): - question = TextMobject( - "Would you like this company to track\\\\", - "and occasionally sell your location?" - ) - question.to_edge(UP, buff=LARGE_BUFF) - - slider = Rectangle(width=1.25, height=0.5) - slider.round_corners(radius=0.25) - slider.set_fill(GREEN, 1) - slider.next_to(question, DOWN, buff=MED_LARGE_BUFF) - - dot = Dot(radius=0.25) - dot.set_fill(GREY_C, 1) - dot.set_stroke(WHITE, 3) - dot.move_to(slider, RIGHT) - - morty = Mortimer() - morty.next_to(slider, RIGHT) - morty.to_edge(DOWN) - - bubble = morty.get_bubble( - height=2, - width=3, - direction=LEFT, - ) - - answer = TextMobject("Um...", "no.") - answer.set_height(0.4) - answer.set_color(YELLOW) - bubble.add_content(answer) - - self.add(morty) - - self.play( - FadeInFromDown(question), - Write(slider), - FadeIn(dot), - ) - self.play(morty.change, "confused", slider) - self.play(Blink(morty)) - self.play( - FadeIn(bubble), - Write(answer[0]), - ) - self.wait() - self.play( - dot.move_to, slider, LEFT, - slider.set_fill, {"opacity": 0}, - FadeIn(answer[1]), - morty.change, "sassy" - ) - self.play(Blink(morty)) - self.wait(2) - self.play(Blink(morty)) - self.wait(2) - - -class MoreLinks(Scene): - def construct(self): - words = TextMobject("See more links\\\\in the description.") - words.scale(2) - words.to_edge(UP, buff=2) - arrows = VGroup(*[ - Vector(1.5 * DOWN, stroke_width=10) - for x in range(4) - ]) - arrows.arrange(RIGHT, buff=0.75) - arrows.next_to(words, DOWN, buff=0.5) - for arrow, color in zip(arrows, [BLUE_D, BLUE_C, BLUE_E, GREY_BROWN]): - arrow.set_color(color) - self.play(Write(words)) - self.play(LaggedStartMap(ShowCreation, arrows)) - self.wait() - - -class LDMEndScreen(PatreonEndScreen): - CONFIG = { - "scroll_time": 20, - "specific_patrons": [ - "1stViewMaths", - "Aaron", - "Adam Dřínek", - "Adam Margulies", - "Aidan Shenkman", - "Alan Stein", - "Albin Egasse", - "Alex Mijalis", - "Alexander Mai", - "Alexis Olson", - "Ali Yahya", - "Andreas Snekloth Kongsgaard", - "Andrew Busey", - "Andrew Cary", - "Andrew R. Whalley", - "Aravind C V", - "Arjun Chakroborty", - "Arthur Zey", - "Ashwin Siddarth", - "Augustine Lim", - "Austin Goodman", - "Avi Finkel", - "Awoo", - "Axel Ericsson", - "Ayan Doss", - "AZsorcerer", - "Barry Fam", - "Bartosz Burclaf", - "Ben Delo", - "Benjamin Bailey", - "Bernd Sing", - "Bill Gatliff", - "Boris Veselinovich", - "Bradley Pirtle", - "Brandon Huang", - "Brendan Shah", - "Brian Cloutier", - "Brian Staroselsky", - "Britt Selvitelle", - "Britton Finley", - "Burt Humburg", - "Calvin Lin", - "Carl-Johan R. Nordangård", - "Charles Southerland", - "Charlie N", - "Chris Connett", - "Chris Druta", - "Christian Kaiser", - "cinterloper", - "Clark Gaebel", - "Colwyn Fritze-Moor", - "Corey Ogburn", - "D. Sivakumar", - "Dan Herbatschek", - "Daniel Brown", - "Daniel Herrera C", - "Darrell Thomas", - "Dave B", - "Dave Cole", - "Dave Kester", - "dave nicponski", - "David B. Hill", - "David Clark", - "David Gow", - "Delton Ding", - "Dominik Wagner", - "Eduardo Rodriguez", - "Emilio Mendoza", - "emptymachine", - "Eric Younge", - "Eryq Ouithaqueue", - "Federico Lebron", - "Fernando Via Canel", - "Frank R. Brown, Jr.", - "gary", - "Giovanni Filippi", - "Goodwine", - "Hal Hildebrand", - "Heptonion", - "Hitoshi Yamauchi", - "Isaac Gubernick", - "Ivan Sorokin", - "Jacob Baxter", - "Jacob Harmon", - "Jacob Hartmann", - "Jacob Magnuson", - "Jalex Stark", - "Jameel Syed", - "James Beall", - "Jason Hise", - "Jayne Gabriele", - "Jean-Manuel Izaret", - "Jeff Dodds", - "Jeff Linse", - "Jeff Straathof", - "Jeffrey Wolberg", - "Jimmy Yang", - "Joe Pregracke", - "Johan Auster", - "John C. Vesey", - "John Camp", - "John Haley", - "John Le", - "John Luttig", - "John Rizzo", - "John V Wertheim", - "jonas.app", - "Jonathan Heckerman", - "Jonathan Wilson", - "Joseph John Cox", - "Joseph Kelly", - "Josh Kinnear", - "Joshua Claeys", - "Joshua Ouellette", - "Juan Benet", - "Julien Dubois", - "Kai-Siang Ang", - "Kanan Gill", - "Karl Niu", - "Kartik Cating-Subramanian", - "Kaustuv DeBiswas", - "Killian McGuinness", - "kkm", - "Klaas Moerman", - "Kristoffer Börebäck", - "Kros Dai", - "L0j1k", - "Lael S Costa", - "LAI Oscar", - "Lambda GPU Workstations", - "Laura Gast", - "Lee Redden", - "Linh Tran", - "Luc Ritchie", - "Ludwig Schubert", - "Lukas Biewald", - "Lukas Zenick", - "Magister Mugit", - "Magnus Dahlström", - "Magnus Hiie", - "Manoj Rewatkar - RITEK SOLUTIONS", - "Mark B Bahu", - "Mark Heising", - "Mark Hopkins", - "Mark Mann", - "Martin Price", - "Mathias Jansson", - "Matt Godbolt", - "Matt Langford", - "Matt Roveto", - "Matt Russell", - "Matteo Delabre", - "Matthew Bouchard", - "Matthew Cocke", - "Maxim Nitsche", - "Michael Bos", - "Michael Hardel", - "Michael W White", - "Mirik Gogri", - "Molly Mackinlay", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nero Li", - "Nicholas Cahill", - "Nikita Lesnikov", - "Nitu Kitchloo", - "Oleg Leonov", - "Oliver Steele", - "Omar Zrien", - "Omer Tuchfeld", - "Patrick Gibson", - "Patrick Lucas", - "Pavel Dubov", - "Pesho Ivanov", - "Petar Veličković", - "Peter Ehrnstrom", - "Peter Francis", - "Peter Mcinerney", - "Pierre Lancien", - "Pradeep Gollakota", - "Rafael Bove Barrios", - "Raghavendra Kotikalapudi", - "Randy C. Will", - "rehmi post", - "Rex Godby", - "Ripta Pasay", - "Rish Kundalia", - "Roman Sergeychik", - "Roobie", - "Ryan Atallah", - "Samuel Judge", - "SansWord Huang", - "Scott Gray", - "Scott Walter, Ph.D.", - "soekul", - "Solara570", - "Spyridon Michalakis", - "Stephen Shanahan", - "Steve Huynh", - "Steve Muench", - "Steve Sperandeo", - "Steven Siddals", - "Stevie Metke", - "Sundar Subbarayan", - "supershabam", - "Suteerth Vishnu", - "Suthen Thomas", - "Tal Einav", - "Taras Bobrovytsky", - "Tauba Auerbach", - "Ted Suzman", - "Terry Hayes", - "THIS IS THE point OF NO RE tUUurRrhghgGHhhnnn", - "Thomas J Sargent", - "Thomas Tarler", - "Tianyu Ge", - "Tihan Seale", - "Tim Erbes", - "Tim Kazik", - "Tomasz Legutko", - "Tyler Herrmann", - "Tyler Parcell", - "Tyler VanValkenburg", - "Tyler Veness", - "Ubiquity Ventures", - "Vassili Philippov", - "Vasu Dubey", - "Veritasium", - "Vignesh Ganapathi Subramanian", - "Vinicius Reis", - "Vladimir Solomatin", - "Wooyong Ee", - "Xuanji Li", - "Yana Chernobilsky", - "Yavor Ivanov", - "Yetinother", - "YinYangBalance.Asia", - "Yu Jun", - "Yurii Monastyrshyn", - "Zachariah Rosenberg", - ], - } diff --git a/from_3b1b/active/diffyq/all_part1_scenes.py b/from_3b1b/active/diffyq/all_part1_scenes.py deleted file mode 100644 index b82e379a..00000000 --- a/from_3b1b/active/diffyq/all_part1_scenes.py +++ /dev/null @@ -1,107 +0,0 @@ -from active_projects.diffyq.part1.pendulum import * -from active_projects.diffyq.part1.staging import * -from active_projects.diffyq.part1.pi_scenes import * -from active_projects.diffyq.part1.phase_space import * -from active_projects.diffyq.part1.wordy_scenes import * - -OUTPUT_DIRECTORY = "diffyq/part1" -SCENES_IN_ORDER = [ - WhenChangeIsEasier, - VectorFieldTest, - IntroducePendulum, - MultiplePendulumsOverlayed, - PeriodFormula, - FormulasAreLies, - MediumAnglePendulum, - MediumHighAnglePendulum, - HighAnglePendulum, - LowAnglePendulum, - SomeOfYouWatching, - SmallAngleApproximationTex, - VeryLowAnglePendulum, - FormulasAreLies, - TourOfDifferentialEquations, - WherePendulumLeads, - LongDoublePendulum, - # FollowThisThread, - StrogatzQuote, - ShowHorizontalDashedLine, - RabbitFoxPopulations, - RabbitFoxEquation, - # Something... - ShowSimpleTrajectory, - SimpleProjectileEquation, - SimpleProjectileEquationVGraphFreedom, - ShowGravityAcceleration, - UniversalGravityLawSymbols, - ExampleTypicalODE, - AnalyzePendulumForce, - ShowSineValues, - BuildUpEquation, - AirResistanceBrace, - ShowDerivativeVideo, - SubtleAirCurrents, - SimpleDampenedPendulum, - DefineODE, - SecondOrderEquationExample, - ODEvsPDEinFrames, - ProveTeacherWrong, - SetAsideSeekingSolution, - # - WriteInRadians, - XEqLThetaToCorner, - ComingUp, - InputLabel, - SoWhatIsThetaThen, - ReallyHardToSolve, - ReasonForSolution, - PhysicistPhaseSpace, - GleickQuote, - SpectrumOfStartingStates, - WritePhaseFlow, - AskAboutStability, - LoveExample, - PassageOfTime, - LovePhaseSpace, - ComparePhysicsToLove, - FramesComparingPhysicsToLove, - SetupToTakingManyTinySteps, - ShowClutterPrevention, - # VisualizeHeightSlopeCurvature, - VisualizeStates, - ReferencePiCollisionStateSpaces, - IntroduceVectorField, - XComponentArrows, - BreakingSecondOrderIntoTwoFirstOrder, - ShowPendulumPhaseFlow, - ShowHighVelocityCase, - TweakMuInFormula, - TweakMuInVectorField, - FromODEToVectorField, - LorenzVectorField, - ThreeBodiesInSpace, - AltThreeBodiesInSpace, - TwoBodiesInSpace, - TwoBodiesWithZPart, - ThreeBodyTitle, - ThreeBodySymbols, - # - HighAmplitudePendulum, - WritePhaseSpace, - # - AskAboutActuallySolving, - WriteODESolvingCode, - TakeManyTinySteps, - ManyStepsFromDifferentStartingPoints, - InaccurateComputation, - HungerForExactness, - ShowRect, - ShowSquare, - JumpToThisPoint, - ThreeBodyEquation, - ItGetsWorse, - ChaosTitle, - RevisitQuote, - EndScreen, - Thumbnail, -] diff --git a/from_3b1b/active/diffyq/all_part2_scenes.py b/from_3b1b/active/diffyq/all_part2_scenes.py deleted file mode 100644 index 47830a1a..00000000 --- a/from_3b1b/active/diffyq/all_part2_scenes.py +++ /dev/null @@ -1,41 +0,0 @@ -from active_projects.diffyq.part2.staging import * -from active_projects.diffyq.part2.fourier_series import * -from active_projects.diffyq.part2.heat_equation import * -from active_projects.diffyq.part2.pi_scenes import * -from active_projects.diffyq.part2.wordy_scenes import * - -OUTPUT_DIRECTORY = "diffyq/part2" -SCENES_IN_ORDER = [ - PartTwoOfTour, - HeatEquationIntroTitle, - BrownianMotion, - BlackScholes, - ContrastChapters1And2, - FourierSeriesIntro, - FourierSeriesIntroBackground20, - ExplainCircleAnimations, - # FourierSeriesIntroBackground4, - # FourierSeriesIntroBackground8, - # FourierSeriesIntroBackground12, - TwoDBodyWithManyTemperatures, - TwoDBodyWithManyTemperaturesGraph, - TwoDBodyWithManyTemperaturesContour, - BringTwoRodsTogether, - ShowEvolvingTempGraphWithArrows, - # TodaysTargetWrapper, - WriteHeatEquation, - ReactionsToInitialHeatEquation, - TalkThrough1DHeatGraph, - ShowCubeFormation, - CompareInputsOfGeneralCaseTo1D, - ContrastXChangesToTChanges, - ShowPartialDerivativeSymbols, - WriteHeatEquation, - ShowCurvatureToRateOfChangeIntuition, - ContrastPDEToODE, - TransitionToTempVsTime, - Show1DAnd3DEquations, - # - AskAboutWhereEquationComesFrom, - DiscreteSetup, -] diff --git a/from_3b1b/active/diffyq/all_part3_scenes.py b/from_3b1b/active/diffyq/all_part3_scenes.py deleted file mode 100644 index e86aa927..00000000 --- a/from_3b1b/active/diffyq/all_part3_scenes.py +++ /dev/null @@ -1,70 +0,0 @@ -from active_projects.diffyq.part3.staging import * -from active_projects.diffyq.part3.temperature_graphs import * -from active_projects.diffyq.part3.pi_creature_scenes import * -from active_projects.diffyq.part3.wordy_scenes import * -from active_projects.diffyq.part3.discrete_case import * - - -OUTPUT_DIRECTORY = "diffyq/part3" -SCENES_IN_ORDER = [ - LastChapterWrapper, - ThreeConstraints, - OceanOfPossibilities, - ThreeMainObservations, - SimpleCosExpGraph, - AddMultipleSolutions, - FourierSeriesIllustraiton, - BreakDownAFunction, - SineCurveIsUnrealistic, - AnalyzeSineCurve, - EquationAboveSineAnalysis, - ExponentialDecay, - InvestmentGrowth, - GrowingPileOfMoney, - CarbonDecayCurve, - CarbonDecayingInMammoth, - SineWaveScaledByExp, - ShowSinExpDerivatives, - IfOnly, - BoundaryConditionInterlude, - BoundaryConditionReference, - GiantCross, - SimulateRealSineCurve, - DerivativesOfLinearFunction, - StraightLine3DGraph, - SimulateLinearGraph, - EmphasizeBoundaryPoints, - ShowNewRuleAtDiscreteBoundary, - DiscreteEvolutionPoint25, - DiscreteEvolutionPoint1, - FlatEdgesForDiscreteEvolution, - FlatEdgesForDiscreteEvolutionTinySteps, - FlatEdgesContinuousEvolution, - FlatAtBoundaryWords, - SlopeToHeatFlow, - CloserLookAtStraightLine, - WriteOutBoundaryCondition, - SoWeGotNowhere, - ManipulateSinExpSurface, - HeatEquationFrame, - ShowFreq1CosExpDecay, - ShowFreq2CosExpDecay, - ShowFreq4CosExpDecay, - CompareFreqDecays1to2, - CompareFreqDecays1to4, - CompareFreqDecays2to4, - ShowHarmonics, - ShowHarmonicSurfaces, - - # SimpleCosExpGraph, - # AddMultipleSolutions, - # IveHeardOfThis, - # FourierSeriesOfLineIllustration, - # InFouriersShoes, -] - -PART_4_SCENES = [ - FourierSeriesIllustraiton, - FourierNameIntro, - CircleAnimationOfF, -] diff --git a/from_3b1b/active/diffyq/all_part4_scenes.py b/from_3b1b/active/diffyq/all_part4_scenes.py deleted file mode 100644 index 21ff9510..00000000 --- a/from_3b1b/active/diffyq/all_part4_scenes.py +++ /dev/null @@ -1,65 +0,0 @@ -from active_projects.diffyq.part4.staging import * -from active_projects.diffyq.part4.fourier_series_scenes import * -from active_projects.diffyq.part4.pi_creature_scenes import * -from active_projects.diffyq.part4.three_d_graphs import * -from active_projects.diffyq.part4.temperature_scenes import * -from active_projects.diffyq.part4.complex_functions import * -from active_projects.diffyq.part4.long_fourier_scenes import * - -from active_projects.diffyq.part3.staging import * - -OUTPUT_DIRECTORY = "diffyq/part4" -SCENES_IN_ORDER = [ - ComplexFourierSeriesExample, - FourierOfFourier, - FourierOfFourierZoomedIn, - FourierOfFourier100xZoom, - FourierSeriesFormula, - RelationToOtherVideos, - WhyWouldYouCare, - ShowLinearity, - CombineSeveralSolutions, - FourierGainsImmortality, - SolveForWavesNothingElse, - CycleThroughManyLinearCombinations, - StepFunctionExample, - WhichWavesAreAvailable, - AlternateBoundaryConditions, - AskQuestionOfGraph, - CommentOnFouriersImmortality, - HangOnThere, - ShowInfiniteSum, - TechnicalNuances, - BreakDownStepFunction, - StepFunctionSolutionFormla, - # How to compute - FourierSeriesOfLineIllustration, - GeneralizeToComplexFunctions, - ClarifyInputAndOutput, - GraphForFlattenedPi, - PiFourierSeries, - RealValuedFunctionFourierSeries, - YouSaidThisWasEasier, - AskAboutComplexNotVector, - SimpleComplexExponentExample, - LooseWithLanguage, - DemonstrateAddingArrows, - TRangingFrom0To1, - LabelRotatingVectors, - IntegralTrick, - SwapIntegralAndSum, - FootnoteOnSwappingIntegralAndSum, - FormulaOutOfContext, - ShowRangeOfCnFormulas, - DescribeSVG, - # TODO - IncreaseOrderOfApproximation, - ShowStepFunctionIn2dView, - StepFunctionIntegral, - GeneralChallenge, - - # Oldies - # FourierSeriesIllustraiton, - # FourierNameIntro, - # CircleAnimationOfF, -] diff --git a/from_3b1b/active/diffyq/all_part5_scenes.py b/from_3b1b/active/diffyq/all_part5_scenes.py deleted file mode 100644 index 2fd7e5da..00000000 --- a/from_3b1b/active/diffyq/all_part5_scenes.py +++ /dev/null @@ -1,5 +0,0 @@ -from active_projects.diffyq.part5.staging import * - -OUTPUT_DIRECTORY = "diffyq/part5" -SCENES_IN_ORDER = [ -] \ No newline at end of file diff --git a/from_3b1b/active/diffyq/fourier_montage_scenes.py b/from_3b1b/active/diffyq/fourier_montage_scenes.py deleted file mode 100644 index 27d028c8..00000000 --- a/from_3b1b/active/diffyq/fourier_montage_scenes.py +++ /dev/null @@ -1,23 +0,0 @@ -from active_projects.diffyq.part4.long_fourier_scenes import * - -OUTPUT_DIRECTORY = "diffyq/part4" -SCENES_IN_ORDER = [ - ZoomedInFourierSeriesExample, - FourierSeriesExampleWithRectForZoom, - ZoomedInFourierSeriesExample100x, - FourierOfFourier100xZoom, - FourierOfFourierZoomedIn, - FourierOfFourier, - SigmaZoomedInFourierSeriesExample, - SigmaFourierSeriesExampleWithRectForZoom, - NailAndGearZoomedInFourierSeriesExample, - NailAndGearFourierSeriesExampleWithRectForZoom, - TrebleClefZoomedInFourierSeriesExample, - TrebleClefFourierSeriesExampleWithRectForZoom, - FourierOfSeattle, - FourierOfSeattleZoomedIn, - FourierOfBritain, - FourierOfBritainZoomedIn, - FourierOfHilbert, - FourierOfHilbertZoomedIn, -] diff --git a/from_3b1b/active/diffyq/part1/pendulum.py b/from_3b1b/active/diffyq/part1/pendulum.py deleted file mode 100644 index 97cf34c7..00000000 --- a/from_3b1b/active/diffyq/part1/pendulum.py +++ /dev/null @@ -1,1893 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part1.shared_constructs import * - - -class Pendulum(VGroup): - CONFIG = { - "length": 3, - "gravity": 9.8, - "weight_diameter": 0.5, - "initial_theta": 0.3, - "omega": 0, - "damping": 0.1, - "top_point": 2 * UP, - "rod_style": { - "stroke_width": 3, - "stroke_color": LIGHT_GREY, - "sheen_direction": UP, - "sheen_factor": 1, - }, - "weight_style": { - "stroke_width": 0, - "fill_opacity": 1, - "fill_color": GREY_BROWN, - "sheen_direction": UL, - "sheen_factor": 0.5, - "background_stroke_color": BLACK, - "background_stroke_width": 3, - "background_stroke_opacity": 0.5, - }, - "dashed_line_config": { - "num_dashes": 25, - "stroke_color": WHITE, - "stroke_width": 2, - }, - "angle_arc_config": { - "radius": 1, - "stroke_color": WHITE, - "stroke_width": 2, - }, - "velocity_vector_config": { - "color": RED, - }, - "theta_label_height": 0.25, - "set_theta_label_height_cap": False, - "n_steps_per_frame": 100, - "include_theta_label": True, - "include_velocity_vector": False, - "velocity_vector_multiple": 0.5, - "max_velocity_vector_length_to_length_ratio": 0.5, - } - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.create_fixed_point() - self.create_rod() - self.create_weight() - self.rotating_group = VGroup(self.rod, self.weight) - self.create_dashed_line() - self.create_angle_arc() - if self.include_theta_label: - self.add_theta_label() - if self.include_velocity_vector: - self.add_velocity_vector() - - self.set_theta(self.initial_theta) - self.update() - - def create_fixed_point(self): - self.fixed_point_tracker = VectorizedPoint(self.top_point) - self.add(self.fixed_point_tracker) - return self - - def create_rod(self): - rod = self.rod = Line(UP, DOWN) - rod.set_height(self.length) - rod.set_style(**self.rod_style) - rod.move_to(self.get_fixed_point(), UP) - self.add(rod) - - def create_weight(self): - weight = self.weight = Circle() - weight.set_width(self.weight_diameter) - weight.set_style(**self.weight_style) - weight.move_to(self.rod.get_end()) - self.add(weight) - - def create_dashed_line(self): - line = self.dashed_line = DashedLine( - self.get_fixed_point(), - self.get_fixed_point() + self.length * DOWN, - **self.dashed_line_config - ) - line.add_updater( - lambda l: l.move_to(self.get_fixed_point(), UP) - ) - self.add_to_back(line) - - def create_angle_arc(self): - self.angle_arc = always_redraw(lambda: Arc( - arc_center=self.get_fixed_point(), - start_angle=-90 * DEGREES, - angle=self.get_arc_angle_theta(), - **self.angle_arc_config, - )) - self.add(self.angle_arc) - - def get_arc_angle_theta(self): - # Might be changed in certain scenes - return self.get_theta() - - def add_velocity_vector(self): - def make_vector(): - omega = self.get_omega() - theta = self.get_theta() - mvlr = self.max_velocity_vector_length_to_length_ratio - max_len = mvlr * self.rod.get_length() - vvm = self.velocity_vector_multiple - multiple = np.clip( - vvm * omega, -max_len, max_len - ) - vector = Vector( - multiple * RIGHT, - **self.velocity_vector_config, - ) - vector.rotate(theta, about_point=ORIGIN) - vector.shift(self.rod.get_end()) - return vector - - self.velocity_vector = always_redraw(make_vector) - self.add(self.velocity_vector) - return self - - def add_theta_label(self): - self.theta_label = always_redraw(self.get_label) - self.add(self.theta_label) - - def get_label(self): - label = TexMobject("\\theta") - label.set_height(self.theta_label_height) - if self.set_theta_label_height_cap: - max_height = self.angle_arc.get_width() - if label.get_height() > max_height: - label.set_height(max_height) - top = self.get_fixed_point() - arc_center = self.angle_arc.point_from_proportion(0.5) - vect = arc_center - top - norm = get_norm(vect) - vect = normalize(vect) * (norm + self.theta_label_height) - label.move_to(top + vect) - return label - - # - def get_theta(self): - theta = self.rod.get_angle() - self.dashed_line.get_angle() - theta = (theta + PI) % TAU - PI - return theta - - def set_theta(self, theta): - self.rotating_group.rotate( - theta - self.get_theta() - ) - self.rotating_group.shift( - self.get_fixed_point() - self.rod.get_start(), - ) - return self - - def get_omega(self): - return self.omega - - def set_omega(self, omega): - self.omega = omega - return self - - def get_fixed_point(self): - return self.fixed_point_tracker.get_location() - - # - def start_swinging(self): - self.add_updater(Pendulum.update_by_gravity) - - def end_swinging(self): - self.remove_updater(Pendulum.update_by_gravity) - - def update_by_gravity(self, dt): - theta = self.get_theta() - omega = self.get_omega() - nspf = self.n_steps_per_frame - for x in range(nspf): - d_theta = omega * dt / nspf - d_omega = op.add( - -self.damping * omega, - -(self.gravity / self.length) * np.sin(theta), - ) * dt / nspf - theta += d_theta - omega += d_omega - self.set_theta(theta) - self.set_omega(omega) - return self - - -class GravityVector(Vector): - CONFIG = { - "color": YELLOW, - "length_multiple": 1 / 9.8, - # TODO, continually update the length based - # on the pendulum's gravity? - } - - def __init__(self, pendulum, **kwargs): - super().__init__(DOWN, **kwargs) - self.pendulum = pendulum - self.scale(self.length_multiple * pendulum.gravity) - self.attach_to_pendulum(pendulum) - - def attach_to_pendulum(self, pendulum): - self.add_updater(lambda m: m.shift( - pendulum.weight.get_center() - self.get_start(), - )) - - def add_component_lines(self): - self.component_lines = always_redraw(self.create_component_lines) - self.add(self.component_lines) - - def create_component_lines(self): - theta = self.pendulum.get_theta() - x_new = rotate(RIGHT, theta) - base = self.get_start() - tip = self.get_end() - vect = tip - base - corner = base + x_new * np.dot(vect, x_new) - kw = {"dash_length": 0.025} - return VGroup( - DashedLine(base, corner, **kw), - DashedLine(corner, tip, **kw), - ) - - -class ThetaValueDisplay(VGroup): - CONFIG = { - - } - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - -class ThetaVsTAxes(Axes): - CONFIG = { - "x_min": 0, - "x_max": 8, - "y_min": -PI / 2, - "y_max": PI / 2, - "y_axis_config": { - "tick_frequency": PI / 8, - "unit_size": 1.5, - }, - "axis_config": { - "color": "#EEEEEE", - "stroke_width": 2, - "include_tip": False, - }, - "graph_style": { - "stroke_color": GREEN, - "stroke_width": 3, - "fill_opacity": 0, - }, - } - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.add_labels() - - def add_axes(self): - self.axes = Axes(**self.axes_config) - self.add(self.axes) - - def add_labels(self): - x_axis = self.get_x_axis() - y_axis = self.get_y_axis() - - t_label = self.t_label = TexMobject("t") - t_label.next_to(x_axis.get_right(), UP, MED_SMALL_BUFF) - x_axis.label = t_label - x_axis.add(t_label) - theta_label = self.theta_label = TexMobject("\\theta(t)") - theta_label.next_to(y_axis.get_top(), UP, SMALL_BUFF) - y_axis.label = theta_label - y_axis.add(theta_label) - - self.y_axis_label = theta_label - self.x_axis_label = t_label - - x_axis.add_numbers() - y_axis.add(self.get_y_axis_coordinates(y_axis)) - - def get_y_axis_coordinates(self, y_axis): - texs = [ - # "\\pi \\over 4", - # "\\pi \\over 2", - # "3 \\pi \\over 4", - # "\\pi", - "\\pi / 4", - "\\pi / 2", - "3 \\pi / 4", - "\\pi", - ] - values = np.arange(1, 5) * PI / 4 - labels = VGroup() - for pos_tex, pos_value in zip(texs, values): - neg_tex = "-" + pos_tex - neg_value = -1 * pos_value - for tex, value in (pos_tex, pos_value), (neg_tex, neg_value): - if value > self.y_max or value < self.y_min: - continue - symbol = TexMobject(tex) - symbol.scale(0.5) - point = y_axis.number_to_point(value) - symbol.next_to(point, LEFT, MED_SMALL_BUFF) - labels.add(symbol) - return labels - - def get_live_drawn_graph(self, pendulum, - t_max=None, - t_step=1.0 / 60, - **style): - style = merge_dicts_recursively(self.graph_style, style) - if t_max is None: - t_max = self.x_max - - graph = VMobject() - graph.set_style(**style) - - graph.all_coords = [(0, pendulum.get_theta())] - graph.time = 0 - graph.time_of_last_addition = 0 - - def update_graph(graph, dt): - graph.time += dt - if graph.time > t_max: - graph.remove_updater(update_graph) - return - new_coords = (graph.time, pendulum.get_theta()) - if graph.time - graph.time_of_last_addition >= t_step: - graph.all_coords.append(new_coords) - graph.time_of_last_addition = graph.time - points = [ - self.coords_to_point(*coords) - for coords in [*graph.all_coords, new_coords] - ] - graph.set_points_smoothly(points) - - graph.add_updater(update_graph) - return graph - - -# Scenes -class IntroducePendulum(PiCreatureScene, MovingCameraScene): - CONFIG = { - "pendulum_config": { - "length": 3, - "top_point": 4 * RIGHT, - "weight_diameter": 0.35, - "gravity": 20, - }, - "theta_vs_t_axes_config": { - "y_max": PI / 4, - "y_min": -PI / 4, - "y_axis_config": { - "tick_frequency": PI / 16, - "unit_size": 2, - "tip_length": 0.3, - }, - "x_max": 12, - "axis_config": { - "stroke_width": 2, - } - }, - } - - def setup(self): - MovingCameraScene.setup(self) - PiCreatureScene.setup(self) - - def construct(self): - self.add_pendulum() - # self.label_pi_creatures() - self.label_pendulum() - self.add_graph() - self.label_function() - self.show_graph_period() - self.show_length_and_gravity() - # self.tweak_length_and_gravity() - - def create_pi_creatures(self): - randy = Randolph(color=BLUE_C) - morty = Mortimer(color=MAROON_E) - creatures = VGroup(randy, morty) - creatures.scale(0.5) - creatures.arrange(RIGHT, buff=2.5) - creatures.to_corner(DR) - return creatures - - def add_pendulum(self): - pendulum = self.pendulum = Pendulum(**self.pendulum_config) - pendulum.start_swinging() - frame = self.camera_frame - frame.save_state() - frame.scale(0.5) - frame.move_to(pendulum.dashed_line) - - self.add(pendulum, frame) - - def label_pi_creatures(self): - randy, morty = self.pi_creatures - randy_label = TextMobject("Physics\\\\", "student") - morty_label = TextMobject("Physics\\\\", "teacher") - labels = VGroup(randy_label, morty_label) - labels.scale(0.5) - randy_label.next_to(randy, UP, LARGE_BUFF) - morty_label.next_to(morty, UP, LARGE_BUFF) - - for label, pi in zip(labels, self.pi_creatures): - label.arrow = Arrow( - label.get_bottom(), pi.eyes.get_top() - ) - label.arrow.set_color(WHITE) - label.arrow.set_stroke(width=5) - - morty.labels = VGroup( - morty_label, - morty_label.arrow, - ) - - self.play( - FadeInFromDown(randy_label), - GrowArrow(randy_label.arrow), - randy.change, "hooray", - ) - self.play( - Animation(self.pendulum.fixed_point_tracker), - TransformFromCopy(randy_label[0], morty_label[0]), - FadeIn(morty_label[1]), - GrowArrow(morty_label.arrow), - morty.change, "raise_right_hand", - ) - self.wait(2) - - def label_pendulum(self): - pendulum = self.pendulum - randy, morty = self.pi_creatures - label = pendulum.theta_label - rect = SurroundingRectangle(label, buff=0.5 * SMALL_BUFF) - rect.add_updater(lambda r: r.move_to(label)) - - for pi in randy, morty: - pi.add_updater( - lambda m: m.look_at(pendulum.weight) - ) - - self.play(randy.change, "pondering") - self.play(morty.change, "pondering") - self.wait(3) - randy.clear_updaters() - morty.clear_updaters() - self.play( - ShowCreationThenFadeOut(rect), - ) - self.wait() - - def add_graph(self): - axes = self.axes = ThetaVsTAxes(**self.theta_vs_t_axes_config) - axes.y_axis.label.next_to(axes.y_axis, UP, buff=0) - axes.to_corner(UL) - - self.play( - Restore( - self.camera_frame, - rate_func=squish_rate_func(smooth, 0, 0.9), - ), - DrawBorderThenFill( - axes, - rate_func=squish_rate_func(smooth, 0.5, 1), - lag_ratio=0.9, - ), - Transform( - self.pendulum.theta_label.copy().clear_updaters(), - axes.y_axis.label.copy(), - remover=True, - rate_func=squish_rate_func(smooth, 0, 0.8), - ), - run_time=3, - ) - - self.wait(1.5) - self.graph = axes.get_live_drawn_graph(self.pendulum) - self.add(self.graph) - - def label_function(self): - hm_word = TextMobject("Simple harmonic motion") - hm_word.scale(1.25) - hm_word.to_edge(UP) - - formula = TexMobject( - "=\\theta_0 \\cos(\\sqrt{g / L} t)" - ) - formula.next_to( - self.axes.y_axis_label, RIGHT, SMALL_BUFF - ) - formula.set_stroke(width=0, background=True) - - self.play(FadeInFrom(hm_word, DOWN)) - self.wait() - self.play( - Write(formula), - hm_word.to_corner, UR - ) - self.wait(4) - - def show_graph_period(self): - pendulum = self.pendulum - axes = self.axes - - period = self.period = TAU * np.sqrt( - pendulum.length / pendulum.gravity - ) - amplitude = pendulum.initial_theta - - line = Line( - axes.coords_to_point(0, amplitude), - axes.coords_to_point(period, amplitude), - ) - line.shift(SMALL_BUFF * RIGHT) - brace = Brace(line, UP, buff=SMALL_BUFF) - brace.add_to_back(brace.copy().set_style(BLACK, 10)) - formula = get_period_formula() - formula.next_to(brace, UP, SMALL_BUFF) - - self.period_formula = formula - self.period_brace = brace - - self.play( - GrowFromCenter(brace), - FadeInFromDown(formula), - ) - self.wait(2) - - def show_length_and_gravity(self): - formula = self.period_formula - L = formula.get_part_by_tex("L") - g = formula.get_part_by_tex("g") - - rod = self.pendulum.rod - new_rod = rod.copy() - new_rod.set_stroke(BLUE, 7) - new_rod.add_updater(lambda r: r.put_start_and_end_on( - *rod.get_start_and_end() - )) - - g_vect = GravityVector( - self.pendulum, - length_multiple=0.5 / 9.8, - ) - down_vectors = self.get_down_vectors() - down_vectors.set_color(YELLOW) - down_vectors.set_opacity(0.5) - - self.play( - ShowCreationThenDestructionAround(L), - ShowCreation(new_rod), - ) - self.play(FadeOut(new_rod)) - - self.play( - ShowCreationThenDestructionAround(g), - GrowArrow(g_vect), - ) - self.play(self.get_down_vectors_animation(down_vectors)) - self.wait(6) - - self.gravity_vector = g_vect - - def tweak_length_and_gravity(self): - pendulum = self.pendulum - axes = self.axes - graph = self.graph - brace = self.period_brace - formula = self.period_formula - g_vect = self.gravity_vector - randy, morty = self.pi_creatures - - graph.clear_updaters() - period2 = self.period * np.sqrt(2) - period3 = self.period / np.sqrt(2) - amplitude = pendulum.initial_theta - graph2, graph3 = [ - axes.get_graph( - lambda t: amplitude * np.cos(TAU * t / p), - color=RED, - ) - for p in (period2, period3) - ] - formula.add_updater(lambda m: m.next_to( - brace, UP, SMALL_BUFF - )) - - new_pendulum_config = dict(self.pendulum_config) - new_pendulum_config["length"] *= 2 - new_pendulum_config["top_point"] += 3.5 * UP - # new_pendulum_config["initial_theta"] = pendulum.get_theta() - new_pendulum = Pendulum(**new_pendulum_config) - - down_vectors = self.get_down_vectors() - - self.play(randy.change, "happy") - self.play( - ReplacementTransform(pendulum, new_pendulum), - morty.change, "horrified", - morty.shift, 3 * RIGHT, - morty.labels.shift, 3 * RIGHT, - ) - self.remove(morty, morty.labels) - g_vect.attach_to_pendulum(new_pendulum) - new_pendulum.start_swinging() - self.play( - ReplacementTransform(graph, graph2), - brace.stretch, np.sqrt(2), 0, {"about_edge": LEFT}, - ) - self.add(g_vect) - self.wait(3) - - new_pendulum.gravity *= 4 - g_vect.scale(2) - self.play( - FadeOut(graph2), - self.get_down_vectors_animation(down_vectors) - ) - self.play( - FadeIn(graph3), - brace.stretch, 0.5, 0, {"about_edge": LEFT}, - ) - self.wait(6) - - # - def get_down_vectors(self): - down_vectors = VGroup(*[ - Vector(0.5 * DOWN) - for x in range(10 * 150) - ]) - down_vectors.arrange_in_grid(10, 150, buff=MED_SMALL_BUFF) - down_vectors.set_color_by_gradient(BLUE, RED) - # for vect in down_vectors: - # vect.shift(0.1 * np.random.random(3)) - down_vectors.to_edge(RIGHT) - return down_vectors - - def get_down_vectors_animation(self, down_vectors): - return LaggedStart( - *[ - GrowArrow(v, rate_func=there_and_back) - for v in down_vectors - ], - lag_ratio=0.0005, - run_time=2, - remover=True - ) - - -class MultiplePendulumsOverlayed(Scene): - CONFIG = { - "initial_thetas": [ - 150 * DEGREES, - 90 * DEGREES, - 60 * DEGREES, - 30 * DEGREES, - 10 * DEGREES, - ], - "weight_colors": [ - PINK, RED, GREEN, BLUE, GREY, - ], - "pendulum_config": { - "top_point": ORIGIN, - "length": 3, - }, - } - - def construct(self): - pendulums = VGroup(*[ - Pendulum( - initial_theta=theta, - weight_style={ - "fill_color": wc, - "fill_opacity": 0.5, - }, - **self.pendulum_config, - ) - for theta, wc in zip( - self.initial_thetas, - self.weight_colors, - ) - ]) - for pendulum in pendulums: - pendulum.start_swinging() - pendulum.remove(pendulum.theta_label) - - randy = Randolph(color=BLUE_C) - randy.to_corner(DL) - randy.add_updater(lambda r: r.look_at(pendulums[0].weight)) - - axes = ThetaVsTAxes( - x_max=20, - y_axis_config={ - "unit_size": 0.5, - "tip_length": 0.3, - }, - ) - axes.to_corner(UL) - graphs = VGroup(*[ - axes.get_live_drawn_graph( - pendulum, - stroke_color=pendulum.weight.get_color(), - stroke_width=1, - ) - for pendulum in pendulums - ]) - - self.add(pendulums) - self.add(axes, *graphs) - self.play(randy.change, "sassy") - self.wait(2) - self.play(Blink(randy)) - self.wait(5) - self.play(randy.change, "angry") - self.play(Blink(randy)) - self.wait(10) - - -class LowAnglePendulum(Scene): - CONFIG = { - "pendulum_config": { - "initial_theta": 20 * DEGREES, - "length": 2.0, - "damping": 0, - "top_point": ORIGIN, - }, - "axes_config": { - "y_axis_config": {"unit_size": 0.75}, - "x_axis_config": { - "unit_size": 0.5, - "numbers_to_show": range(2, 25, 2), - "number_scale_val": 0.5, - }, - "x_max": 25, - "axis_config": { - "tip_length": 0.3, - "stroke_width": 2, - } - }, - "axes_corner": UL, - } - - def construct(self): - pendulum = Pendulum(**self.pendulum_config) - axes = ThetaVsTAxes(**self.axes_config) - axes.center() - axes.to_corner(self.axes_corner, buff=LARGE_BUFF) - graph = axes.get_live_drawn_graph(pendulum) - - L = pendulum.length - g = pendulum.gravity - theta0 = pendulum.initial_theta - prediction = axes.get_graph( - lambda t: theta0 * np.cos(t * np.sqrt(g / L)) - ) - dashed_prediction = DashedVMobject(prediction, num_dashes=300) - dashed_prediction.set_stroke(WHITE, 1) - prediction_formula = TexMobject( - "\\theta_0", "\\cos(\\sqrt{g / L} \\cdot t)" - ) - prediction_formula.scale(0.75) - prediction_formula.next_to( - dashed_prediction, UP, SMALL_BUFF, - ) - - theta0 = prediction_formula.get_part_by_tex("\\theta_0") - theta0_brace = Brace(theta0, UP, buff=SMALL_BUFF) - theta0_brace.stretch(0.5, 1, about_edge=DOWN) - theta0_label = Integer( - pendulum.initial_theta * 180 / PI, - unit="^\\circ" - ) - theta0_label.scale(0.75) - theta0_label.next_to(theta0_brace, UP, SMALL_BUFF) - - group = VGroup(theta0_brace, theta0_label, prediction_formula) - group.shift_onto_screen(buff=MED_SMALL_BUFF) - - self.add(axes, dashed_prediction, pendulum) - self.play( - ShowCreation(dashed_prediction, run_time=2), - FadeInFromDown(prediction_formula), - FadeInFromDown(theta0_brace), - FadeInFromDown(theta0_label), - ) - self.play( - ShowCreationThenFadeAround(theta0_label), - ShowCreationThenFadeAround(pendulum.theta_label), - ) - self.wait() - - pendulum.start_swinging() - self.add(graph) - self.wait(30) - - -class ApproxWordsLowAnglePendulum(Scene): - def construct(self): - period = TexMobject( - "\\text{Period}", "\\approx", - "2\\pi \\sqrt{\\,{L} / {g}}", - **Lg_formula_config - ) - checkmark = TexMobject("\\checkmark") - checkmark.set_color(GREEN) - checkmark.scale(2) - checkmark.next_to(period, RIGHT, MED_LARGE_BUFF) - - self.add(period, checkmark) - - -class MediumAnglePendulum(LowAnglePendulum): - CONFIG = { - "pendulum_config": { - "initial_theta": 50 * DEGREES, - "n_steps_per_frame": 1000, - }, - "axes_config": { - "y_axis_config": {"unit_size": 0.75}, - "y_max": PI / 2, - "y_min": -PI / 2, - "axis_config": { - "tip_length": 0.3, - "stroke_width": 2, - } - }, - "pendulum_shift_vect": 1 * RIGHT, - } - - -class MediumHighAnglePendulum(MediumAnglePendulum): - CONFIG = { - "pendulum_config": { - "initial_theta": 90 * DEGREES, - "n_steps_per_frame": 1000, - }, - } - - -class HighAnglePendulum(LowAnglePendulum): - CONFIG = { - "pendulum_config": { - "initial_theta": 175 * DEGREES, - "n_steps_per_frame": 1000, - "top_point": 1.5 * DOWN, - "length": 2, - }, - "axes_config": { - "y_axis_config": {"unit_size": 0.5}, - "y_max": PI, - "y_min": -PI, - "axis_config": { - "tip_length": 0.3, - "stroke_width": 2, - } - }, - "pendulum_shift_vect": 1 * RIGHT, - } - - -class VeryLowAnglePendulum(LowAnglePendulum): - CONFIG = { - "pendulum_config": { - "initial_theta": 10 * DEGREES, - "n_steps_per_frame": 1000, - "top_point": ORIGIN, - "length": 3, - }, - "axes_config": { - "y_axis_config": {"unit_size": 2}, - "y_max": PI / 4, - "y_min": -PI / 4, - "axis_config": { - "tip_length": 0.3, - "stroke_width": 2, - } - }, - "pendulum_shift_vect": 1 * RIGHT, - } - - -class WherePendulumLeads(PiCreatureScene): - def construct(self): - pendulum = Pendulum( - top_point=UP, - length=3, - gravity=20, - ) - pendulum.start_swinging() - - l_title = TextMobject("Linearization") - l_title.scale(1.5) - l_title.to_corner(UL) - c_title = TextMobject("Chaos") - c_title.scale(1.5) - c_title.move_to(l_title) - c_title.move_to( - c_title.get_center() * np.array([-1, 1, 1]) - ) - - get_theta = pendulum.get_theta - spring = always_redraw( - lambda: ParametricFunction( - lambda t: np.array([ - np.cos(TAU * t) + (1.4 + get_theta()) * t, - np.sin(TAU * t) - 0.5, - 0, - ]), - t_min=-0.5, - t_max=7, - color=GREY, - sheen_factor=1, - sheen_direction=UL, - ).scale(0.2).to_edge(LEFT, buff=0) - ) - spring_rect = SurroundingRectangle( - spring, buff=MED_LARGE_BUFF, - stroke_width=0, - fill_color=BLACK, - fill_opacity=0, - ) - - weight = Dot(radius=0.25) - weight.add_updater(lambda m: m.move_to( - spring.points[-1] - )) - weight.set_color(BLUE) - weight.set_sheen(1, UL) - spring_system = VGroup(spring, weight) - - linear_formula = TexMobject( - "\\frac{d \\vec{\\textbf{x}}}{dt}=" - "A\\vec{\\textbf{x}}" - ) - linear_formula.next_to(spring, UP, LARGE_BUFF) - linear_formula.match_x(l_title) - - randy = self.pi_creature - randy.set_height(2) - randy.center() - randy.to_edge(DOWN) - randy.shift(3 * LEFT) - q_marks = TexMobject("???") - q_marks.next_to(randy, UP) - - self.add(pendulum, randy) - self.play( - randy.change, "pondering", pendulum, - FadeInFromDown(q_marks, lag_ratio=0.3) - ) - self.play(randy.look_at, pendulum) - self.wait(5) - self.play( - Animation(VectorizedPoint(pendulum.get_top())), - FadeOutAndShift(q_marks, UP, lag_ratio=0.3), - ) - self.add(spring_system) - self.play( - FadeOut(spring_rect), - FadeInFrom(linear_formula, UP), - FadeInFromDown(l_title), - ) - self.play(FadeInFromDown(c_title)) - self.wait(8) - - -class LongDoublePendulum(ExternallyAnimatedScene): - pass - - -class AnalyzePendulumForce(MovingCameraScene): - CONFIG = { - "pendulum_config": { - "length": 5, - "top_point": 3.5 * UP, - "initial_theta": 60 * DEGREES, - "set_theta_label_height_cap": True, - }, - "g_vect_config": { - "length_multiple": 0.25, - }, - "tan_line_color": BLUE, - "perp_line_color": PINK, - } - - def construct(self): - self.add_pendulum() - self.show_arc_length() - self.add_g_vect() - self.show_constraint() - self.break_g_vect_into_components() - self.show_angle_geometry() - self.show_gsin_formula() - self.show_sign() - self.show_acceleration_formula() - # self.ask_about_what_to_do() - - # self.emphasize_theta() - # self.show_angular_velocity() - # self.show_angular_acceleration() - # self.circle_g_sin_formula() - - def add_pendulum(self): - pendulum = Pendulum(**self.pendulum_config) - theta_tracker = ValueTracker(pendulum.get_theta()) - pendulum.add_updater(lambda p: p.set_theta( - theta_tracker.get_value() - )) - - self.add(pendulum) - self.pendulum = pendulum - self.theta_tracker = theta_tracker - - def show_arc_length(self): - pendulum = self.pendulum - angle = pendulum.get_theta() - height = pendulum.length - top = pendulum.get_fixed_point() - - line = Line(UP, DOWN) - line.set_height(height) - line.move_to(top, UP) - arc = always_redraw(lambda: Arc( - start_angle=-90 * DEGREES, - angle=pendulum.get_theta(), - arc_center=pendulum.get_fixed_point(), - radius=pendulum.length, - stroke_color=GREEN, - )) - - brace = Brace(Line(ORIGIN, 5 * UP), RIGHT) - brace.point = VectorizedPoint(brace.get_right()) - brace.add(brace.point) - brace.set_height(angle) - brace.move_to(ORIGIN, DL) - brace.apply_complex_function(np.exp) - brace.scale(height) - brace.rotate(-90 * DEGREES) - brace.move_to(arc) - brace.shift(MED_SMALL_BUFF * normalize( - arc.point_from_proportion(0.5) - top - )) - x_sym = TexMobject("x") - x_sym.set_color(GREEN) - x_sym.next_to(brace.point, DR, buff=SMALL_BUFF) - - rhs = TexMobject("=", "L", "\\theta") - rhs.set_color_by_tex("\\theta", BLUE) - rhs.next_to(x_sym, RIGHT) - rhs.shift(0.7 * SMALL_BUFF * UP) - line_L = TexMobject("L") - line_L.next_to( - pendulum.rod.get_center(), UR, SMALL_BUFF, - ) - - self.play( - ShowCreation(arc), - Rotate(line, angle, about_point=top), - UpdateFromAlphaFunc( - line, lambda m, a: m.set_stroke( - width=2 * there_and_back(a) - ) - ), - GrowFromPoint( - brace, line.get_bottom(), - path_arc=angle - ), - ) - self.play(FadeInFrom(x_sym, UP)) - self.wait() - - # Show equation - line.set_stroke(BLUE, 5) - self.play( - ShowCreationThenFadeOut(line), - FadeInFromDown(line_L) - ) - self.play( - TransformFromCopy( - line_L, rhs.get_part_by_tex("L") - ), - Write(rhs.get_part_by_tex("=")) - ) - self.play( - TransformFromCopy( - pendulum.theta_label, - rhs.get_parts_by_tex("\\theta"), - ) - ) - self.add(rhs) - - x_eq = VGroup(x_sym, rhs) - - self.play( - FadeOut(brace), - x_eq.rotate, angle / 2, - x_eq.next_to, arc.point_from_proportion(0.5), - UL, {"buff": -MED_SMALL_BUFF} - ) - - self.x_eq = x_eq - self.arc = arc - self.line_L = line_L - - def add_g_vect(self): - pendulum = self.pendulum - - g_vect = self.g_vect = GravityVector( - pendulum, **self.g_vect_config, - ) - g_word = self.g_word = TextMobject("Gravity") - g_word.rotate(-90 * DEGREES) - g_word.scale(0.75) - g_word.add_updater(lambda m: m.next_to( - g_vect, RIGHT, buff=-SMALL_BUFF, - )) - - self.play( - GrowArrow(g_vect), - FadeInFrom(g_word, UP, lag_ratio=0.1), - ) - self.wait() - - def show_constraint(self): - pendulum = self.pendulum - - arcs = VGroup() - for u in [-1, 2, -1]: - d_theta = 40 * DEGREES * u - arc = Arc( - start_angle=pendulum.get_theta() - 90 * DEGREES, - angle=d_theta, - radius=pendulum.length, - arc_center=pendulum.get_fixed_point(), - stroke_width=2, - stroke_color=YELLOW, - stroke_opacity=0.5, - ) - self.play( - self.theta_tracker.increment_value, d_theta, - ShowCreation(arc) - ) - arcs.add(arc) - self.play(FadeOut(arcs)) - - def break_g_vect_into_components(self): - g_vect = self.g_vect - g_vect.component_lines = always_redraw( - g_vect.create_component_lines - ) - tan_line, perp_line = g_vect.component_lines - g_vect.tangent = always_redraw(lambda: Arrow( - tan_line.get_start(), - tan_line.get_end(), - buff=0, - color=self.tan_line_color, - )) - g_vect.perp = always_redraw(lambda: Arrow( - perp_line.get_start(), - perp_line.get_end(), - buff=0, - color=self.perp_line_color, - )) - - self.play(ShowCreation(g_vect.component_lines)) - self.play(GrowArrow(g_vect.tangent)) - self.wait() - self.play(GrowArrow(g_vect.perp)) - self.wait() - - def show_angle_geometry(self): - g_vect = self.g_vect - - arc = Arc( - start_angle=90 * DEGREES, - angle=self.pendulum.get_theta(), - radius=0.5, - arc_center=g_vect.get_end(), - ) - q_mark = TexMobject("?") - q_mark.next_to(arc.get_center(), UL, SMALL_BUFF) - theta_label = TexMobject("\\theta") - theta_label.move_to(q_mark) - - self.add(g_vect) - self.play( - ShowCreation(arc), - Write(q_mark) - ) - self.play(ShowCreationThenFadeAround(q_mark)) - self.wait() - self.play(ShowCreationThenFadeAround( - self.pendulum.theta_label - )) - self.play( - TransformFromCopy( - self.pendulum.theta_label, - theta_label, - ), - FadeOut(q_mark) - ) - self.wait() - self.play(WiggleOutThenIn(g_vect.tangent)) - self.play(WiggleOutThenIn( - Line( - *g_vect.get_start_and_end(), - buff=0, - ).add_tip().match_style(g_vect), - remover=True - )) - self.wait() - self.play( - FadeOut(arc), - FadeOut(theta_label), - ) - - def show_gsin_formula(self): - g_vect = self.g_vect - g_word = self.g_word - g_word.clear_updaters() - - g_term = self.g_term = TexMobject("-g") - g_term.add_updater(lambda m: m.next_to( - g_vect, - RIGHT if self.pendulum.get_theta() >= 0 else LEFT, - SMALL_BUFF - )) - - def create_vect_label(vect, tex, direction): - label = TexMobject(tex) - label.set_stroke(width=0, background=True) - label.add_background_rectangle() - label.scale(0.7) - max_width = 0.9 * vect.get_length() - if label.get_width() > max_width: - label.set_width(max_width) - angle = vect.get_angle() - angle = (angle + PI / 2) % PI - PI / 2 - label.next_to(ORIGIN, direction, SMALL_BUFF) - label.rotate(angle, about_point=ORIGIN) - label.shift(vect.get_center()) - return label - - g_sin_label = always_redraw(lambda: create_vect_label( - g_vect.tangent, "-g\\sin(\\theta)", UP, - )) - g_cos_label = always_redraw(lambda: create_vect_label( - g_vect.perp, "-g\\cos(\\theta)", DOWN, - )) - - self.play( - ReplacementTransform(g_word[0][0], g_term[0][1]), - FadeOut(g_word[0][1:]), - Write(g_term[0][0]), - ) - self.add(g_term) - self.wait() - for label in g_sin_label, g_cos_label: - self.play( - GrowFromPoint(label[0], g_term.get_center()), - TransformFromCopy(g_term, label[1][:2]), - GrowFromPoint(label[1][2:], g_term.get_center()), - remover=True - ) - self.add(label) - self.wait() - - self.g_sin_label = g_sin_label - self.g_cos_label = g_cos_label - - def show_sign(self): - get_theta = self.pendulum.get_theta - theta_decimal = DecimalNumber(include_sign=True) - theta_decimal.add_updater(lambda d: d.set_value( - get_theta() - )) - theta_decimal.add_updater(lambda m: m.next_to( - self.pendulum.theta_label, DOWN - )) - theta_decimal.add_updater(lambda m: m.set_color( - GREEN if get_theta() > 0 else RED - )) - - self.play( - FadeInFrom(theta_decimal, UP), - FadeOut(self.x_eq), - FadeOut(self.line_L), - ) - self.set_theta(-60 * DEGREES, run_time=4) - self.set_theta(60 * DEGREES, run_time=4) - self.play( - FadeOut(theta_decimal), - FadeIn(self.x_eq), - ) - - def show_acceleration_formula(self): - x_eq = self.x_eq - g_sin_theta = self.g_sin_label - - equation = TexMobject( - "a", "=", - "\\ddot", "x", - "=", - "-", "g", "\\sin\\big(", "\\theta", "\\big)", - ) - equation.to_edge(LEFT) - - second_deriv = equation[2:4] - x_part = equation.get_part_by_tex("x") - x_part.set_color(GREEN) - a_eq = equation[:2] - eq2 = equation.get_parts_by_tex("=")[1] - rhs = equation[5:] - - second_deriv_L_form = TexMobject( - "L", "\\ddot", "\\theta" - ) - second_deriv_L_form.move_to(second_deriv, DOWN) - eq3 = TexMobject("=") - eq3.rotate(90 * DEGREES) - eq3.next_to(second_deriv_L_form, UP) - - g_L_frac = TexMobject( - "-", "{g", "\\over", "L}" - ) - g_L_frac.move_to(rhs[:2], LEFT) - g_L_frac.shift(SMALL_BUFF * UP / 2) - - mu_term = TexMobject( - "-\\mu", "\\dot", "\\theta", - ) - mu_term.next_to(g_L_frac, LEFT) - mu_term.shift(SMALL_BUFF * UP / 2) - - mu_brace = Brace(mu_term, UP) - mu_word = mu_brace.get_text("Air resistance") - - for mob in equation, second_deriv_L_form, mu_term: - mob.set_color_by_tex("\\theta", BLUE) - - self.play( - TransformFromCopy(x_eq[0], x_part), - Write(equation[:3]), - ) - self.wait() - self.play( - Write(eq2), - TransformFromCopy(g_sin_theta, rhs) - ) - self.wait() - # - self.show_acceleration_at_different_angles() - # - self.play( - FadeInFromDown(second_deriv_L_form), - Write(eq3), - second_deriv.next_to, eq3, UP, - a_eq.shift, SMALL_BUFF * LEFT, - eq2.shift, SMALL_BUFF * RIGHT, - rhs.shift, SMALL_BUFF * RIGHT, - ) - self.wait() - self.wait() - self.play( - FadeOut(a_eq), - FadeOut(second_deriv), - FadeOut(eq3), - ReplacementTransform( - second_deriv_L_form.get_part_by_tex("L"), - g_L_frac.get_part_by_tex("L"), - ), - ReplacementTransform( - equation.get_part_by_tex("-"), - g_L_frac.get_part_by_tex("-"), - ), - ReplacementTransform( - equation.get_part_by_tex("g"), - g_L_frac.get_part_by_tex("g"), - ), - Write(g_L_frac.get_part_by_tex("\\over")), - rhs[2:].next_to, g_L_frac, RIGHT, {"buff": SMALL_BUFF}, - ) - self.wait() - self.play( - GrowFromCenter(mu_term), - VGroup(eq2, second_deriv_L_form[1:]).next_to, - mu_term, LEFT, - ) - self.play( - GrowFromCenter(mu_brace), - FadeInFromDown(mu_word), - ) - - def show_acceleration_at_different_angles(self): - to_fade = VGroup( - self.g_cos_label, - self.g_vect.perp, - ) - new_comp_line_sytle = { - "stroke_width": 0.5, - "stroke_opacity": 0.25, - } - - self.play( - FadeOut(self.x_eq), - to_fade.set_opacity, 0.25, - self.g_vect.component_lines.set_style, - new_comp_line_sytle - ) - self.g_vect.component_lines.add_updater( - lambda m: m.set_style(**new_comp_line_sytle) - ) - for mob in to_fade: - mob.add_updater(lambda m: m.set_opacity(0.25)) - - self.set_theta(0) - self.wait(2) - self.set_theta(89.9 * DEGREES, run_time=3) - self.wait(2) - self.set_theta( - 60 * DEGREES, - FadeIn(self.x_eq), - run_time=2, - ) - self.wait() - - def ask_about_what_to_do(self): - g_vect = self.g_vect - g_sin_label = self.g_sin_label - angle = g_vect.tangent.get_angle() - angle = (angle - PI) % TAU - - randy = You() - randy.to_corner(DL) - bubble = randy.get_bubble( - height=2, - width=3.5, - ) - g_sin_copy = g_sin_label.copy() - g_sin_copy.remove(g_sin_copy[0]) - g_sin_copy.generate_target() - g_sin_copy.target.scale(1 / 0.75) - g_sin_copy.target.rotate(-angle) - a_eq = TexMobject("a=") - thought_term = VGroup(a_eq, g_sin_copy.target) - thought_term.arrange(RIGHT, buff=SMALL_BUFF) - thought_term.move_to(bubble.get_bubble_center()) - - rect = SurroundingRectangle(g_sin_copy.target) - rect.rotate(angle) - rect.move_to(g_sin_label) - - randy.save_state() - randy.fade(1) - self.play(randy.restore, randy.change, "pondering") - self.play(ShowCreationThenFadeOut(rect)) - self.play( - ShowCreation(bubble), - Write(a_eq), - MoveToTarget(g_sin_copy), - randy.look_at, bubble, - ) - thought_term.remove(g_sin_copy.target) - thought_term.add(g_sin_copy) - self.play(Blink(randy)) - self.wait() - self.play( - ShowCreationThenDestruction( - thought_term.copy().set_style( - stroke_color=YELLOW, - stroke_width=2, - fill_opacity=0, - ), - run_time=2, - lag_ratio=0.2, - ), - randy.change, "confused", thought_term, - ) - self.play(Blink(randy)) - self.play( - FadeOut(randy), - FadeOut(bubble), - thought_term.next_to, self.pendulum, DOWN, LARGE_BUFF - ) - - self.accleration_equation = thought_term - - def emphasize_theta(self): - pendulum = self.pendulum - - self.play(FocusOn(pendulum.theta_label)) - self.play(Indicate(pendulum.theta_label)) - - pendulum_copy = pendulum.deepcopy() - pendulum_copy.clear_updaters() - pendulum_copy.fade(1) - pendulum_copy.start_swinging() - - def new_updater(p): - p.set_theta(pendulum_copy.get_theta()) - pendulum.add_updater(new_updater) - - self.add(pendulum_copy) - self.wait(5) - pendulum_copy.end_swinging() - self.remove(pendulum_copy) - pendulum.remove_updater(new_updater) - self.update_mobjects(0) - - def show_angular_velocity(self): - pass - - def show_angular_acceleration(self): - pass - - def circle_g_sin_formula(self): - self.play( - ShowCreationThenFadeAround( - self.accleration_equation - ) - ) - - # - def set_theta(self, value, *added_anims, **kwargs): - kwargs["run_time"] = kwargs.get("run_time", 2) - self.play( - self.theta_tracker.set_value, value, - *added_anims, - **kwargs, - ) - - -class BuildUpEquation(Scene): - CONFIG = { - "tex_config": { - "tex_to_color_map": { - "{a}": YELLOW, - "{v}": RED, - "{x}": GREEN, - "\\theta": BLUE, - "{L}": WHITE, - } - } - } - - def construct(self): - # self.add_center_line() - self.show_derivatives() - self.show_theta_double_dot_equation() - self.talk_about_sine_component() - self.add_air_resistance() - - def add_center_line(self): - line = Line(UP, DOWN) - line.set_height(FRAME_HEIGHT) - line.set_stroke(WHITE, 1) - self.add(line) - - def show_derivatives(self): - a_eq = TexMobject( - "{a}", "=", "{d{v} \\over dt}", - **self.tex_config, - ) - v_eq = TexMobject( - "{v}", "=", "{d{x} \\over dt}", - **self.tex_config, - ) - x_eq = TexMobject( - "{x} = {L} \\theta", - **self.tex_config, - ) - eqs = VGroup(a_eq, v_eq, x_eq) - eqs.arrange(DOWN, buff=LARGE_BUFF) - eqs.to_corner(UL) - - v_rhs = TexMobject( - "={L}{d\\theta \\over dt}", - "=", "{L}\\dot{\\theta}", - **self.tex_config, - ) - - v_rhs.next_to(v_eq, RIGHT, SMALL_BUFF) - v_rhs.shift( - UP * (v_eq[1].get_bottom()[1] - v_rhs[0].get_bottom()[1]) - ) - a_rhs = TexMobject( - "={L}{d", "\\dot{\\theta}", "\\over dt}", - "=", "{L}\\ddot{\\theta}", - **self.tex_config, - ) - a_rhs.next_to(a_eq, RIGHT, SMALL_BUFF) - a_rhs.shift( - UP * (a_eq[1].get_bottom()[1] - a_rhs[0].get_bottom()[1]) - ) - - # a_eq - self.play(Write(a_eq)) - self.wait() - - # v_eq - self.play( - TransformFromCopy( - a_eq.get_part_by_tex("{v}"), - v_eq.get_part_by_tex("{v}"), - ) - ) - self.play(TransformFromCopy(v_eq[:1], v_eq[1:])) - self.wait() - - # x_eq - self.play( - TransformFromCopy( - v_eq.get_part_by_tex("{x}"), - x_eq.get_part_by_tex("{x}"), - ) - ) - self.play(Write(x_eq[1:])) - self.wait() - for tex in "L", "\\theta": - self.play(ShowCreationThenFadeAround( - x_eq.get_part_by_tex(tex) - )) - self.wait() - - # v_rhs - self.play(*[ - TransformFromCopy( - x_eq.get_part_by_tex(tex), - v_rhs.get_part_by_tex(tex), - ) - for tex in ("=", "{L}", "\\theta") - ]) - self.play( - TransformFromCopy(v_eq[-3], v_rhs[2]), - TransformFromCopy(v_eq[-1], v_rhs[4]), - ) - self.wait() - self.play( - Write(v_rhs[-5]), - TransformFromCopy(*v_rhs.get_parts_by_tex("{L}")), - TransformFromCopy(v_rhs[3:4], v_rhs[-3:]) - ) - self.wait() - self.play(ShowCreationThenFadeAround(v_rhs[2:4])) - self.play(ShowCreationThenFadeAround(v_rhs[4])) - self.wait() - - # a_rhs - self.play(*[ - TransformFromCopy( - v_rhs.get_parts_by_tex(tex)[-1], - a_rhs.get_part_by_tex(tex), - ) - for tex in ("=", "{L}", "\\theta", "\\dot") - ]) - self.play( - TransformFromCopy(a_eq[-3], a_rhs[2]), - TransformFromCopy(a_eq[-1], a_rhs[6]), - ) - self.wait() - self.play( - Write(a_rhs[-5]), - TransformFromCopy(*a_rhs.get_parts_by_tex("{L}")), - TransformFromCopy(a_rhs[3:4], a_rhs[-3:]), - ) - self.wait() - - self.equations = VGroup( - a_eq, v_eq, x_eq, - v_rhs, a_rhs, - ) - - def show_theta_double_dot_equation(self): - equations = self.equations - a_deriv = equations[0] - a_rhs = equations[-1][-5:].copy() - - shift_vect = 1.5 * DOWN - - equals = TexMobject("=") - equals.rotate(90 * DEGREES) - equals.next_to(a_deriv[0], UP, MED_LARGE_BUFF) - g_sin_eq = TexMobject( - "-", "g", "\\sin", "(", "\\theta", ")", - **self.tex_config, - ) - g_sin_eq.next_to( - equals, UP, - buff=MED_LARGE_BUFF, - aligned_edge=LEFT, - ) - g_sin_eq.to_edge(LEFT) - g_sin_eq.shift(shift_vect) - - shift_vect += ( - g_sin_eq[1].get_center() - - a_deriv[0].get_center() - )[0] * RIGHT - - equals.shift(shift_vect) - a_rhs.shift(shift_vect) - - self.play( - equations.shift, shift_vect, - Write(equals), - GrowFromPoint( - g_sin_eq, 2 * RIGHT + 3 * DOWN - ) - ) - self.wait() - self.play( - a_rhs.next_to, g_sin_eq, RIGHT, - a_rhs.shift, SMALL_BUFF * UP, - ) - self.wait() - - # Fade equations - self.play( - FadeOut(equals), - equations.shift, DOWN, - equations.fade, 0.5, - ) - - # Rotate sides - equals, L, ddot, theta, junk = a_rhs - L_dd_theta = VGroup(L, ddot, theta) - minus, g, sin, lp, theta2, rp = g_sin_eq - m2, g2, over, L2 = frac = TexMobject("-", "{g", "\\over", "L}") - frac.next_to(equals, RIGHT) - - self.play( - L_dd_theta.next_to, equals, LEFT, - L_dd_theta.shift, SMALL_BUFF * UP, - g_sin_eq.next_to, equals, RIGHT, - path_arc=PI / 2, - ) - self.play( - ReplacementTransform(g, g2), - ReplacementTransform(minus, m2), - ReplacementTransform(L, L2), - Write(over), - g_sin_eq[2:].next_to, over, RIGHT, SMALL_BUFF, - ) - self.wait() - - # Surround - rect = SurroundingRectangle(VGroup(g_sin_eq, frac, ddot)) - rect.stretch(1.1, 0) - dashed_rect = DashedVMobject( - rect, num_dashes=50, positive_space_ratio=1, - ) - dashed_rect.shuffle() - dashed_rect.save_state() - dashed_rect.space_out_submobjects(1.1) - for piece in dashed_rect: - piece.rotate(90 * DEGREES) - dashed_rect.fade(1) - self.play(Restore(dashed_rect, lag_ratio=0.05)) - dashed_rect.generate_target() - dashed_rect.target.space_out_submobjects(0.9) - dashed_rect.target.fade(1) - for piece in dashed_rect.target: - piece.rotate(90 * DEGREES) - self.play(MoveToTarget( - dashed_rect, - lag_ratio=0.05, - remover=True - )) - self.wait() - - self.main_equation = VGroup( - ddot, theta, equals, - m2, L2, over, g2, - sin, lp, theta2, rp, - ) - - def talk_about_sine_component(self): - main_equation = self.main_equation - gL_part = main_equation[4:7] - sin_part = main_equation[7:] - sin = sin_part[0] - - morty = Mortimer(height=1.5) - morty.next_to(sin, DR, buff=LARGE_BUFF) - morty.add_updater(lambda m: m.look_at(sin)) - - self.play(ShowCreationThenFadeAround(gL_part)) - self.wait() - self.play(ShowCreationThenFadeAround(sin_part)) - self.wait() - - self.play(FadeIn(morty)) - sin.save_state() - self.play( - morty.change, "angry", - sin.next_to, morty, LEFT, {"aligned_edge": UP}, - ) - self.play(Blink(morty)) - morty.clear_updaters() - self.play( - morty.change, "concerned_musician", - morty.look, DR, - ) - self.play(Restore(sin)) - self.play(FadeOut(morty)) - self.wait() - - # Emphasize theta as input - theta = sin_part[2] - arrow = Vector(0.5 * UP, color=WHITE) - arrow.next_to(theta, DOWN, SMALL_BUFF) - word = TextMobject("Input") - word.next_to(arrow, DOWN) - - self.play( - FadeInFrom(word, UP), - GrowArrow(arrow) - ) - self.play( - ShowCreationThenDestruction( - theta.copy().set_style( - fill_opacity=0, - stroke_width=2, - stroke_color=YELLOW, - ), - lag_ratio=0.1, - ) - ) - self.play(FadeOut(arrow), FadeOut(word)) - - def add_air_resistance(self): - main_equation = self.main_equation - tdd_eq = main_equation[:3] - rhs = main_equation[3:] - - new_term = TexMobject( - "-", "\\mu", "\\dot{", "\\theta}", - ) - new_term.set_color_by_tex("\\theta", BLUE) - new_term.move_to(main_equation) - new_term.shift(0.5 * SMALL_BUFF * UP) - new_term[0].align_to(rhs[0], UP) - - brace = Brace(new_term, DOWN) - words = brace.get_text("Air resistance") - - self.play( - FadeInFromDown(new_term), - tdd_eq.next_to, new_term, LEFT, - tdd_eq.align_to, tdd_eq, UP, - rhs.next_to, new_term, RIGHT, - rhs.align_to, rhs, UP, - ) - self.play( - GrowFromCenter(brace), - Write(words) - ) - self.wait() - - -class SimpleDampenedPendulum(Scene): - def construct(self): - pendulum = Pendulum( - top_point=ORIGIN, - initial_theta=150 * DEGREES, - mu=0.5, - ) - self.add(pendulum) - pendulum.start_swinging() - self.wait(20) - - -class NewSceneName(Scene): - def construct(self): - pass diff --git a/from_3b1b/active/diffyq/part1/phase_space.py b/from_3b1b/active/diffyq/part1/phase_space.py deleted file mode 100644 index ff848604..00000000 --- a/from_3b1b/active/diffyq/part1/phase_space.py +++ /dev/null @@ -1,2112 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part1.shared_constructs import * -from active_projects.diffyq.part1.pendulum import Pendulum - - -# TODO: Arguably separate the part showing many -# configurations with the part showing just one. -class VisualizeStates(Scene): - CONFIG = { - "coordinate_plane_config": { - "y_line_frequency": PI / 2, - # "x_line_frequency": PI / 2, - "x_line_frequency": 1, - "y_axis_config": { - # "unit_size": 1.75, - "unit_size": 1, - }, - "y_max": 4, - "faded_line_ratio": 4, - "background_line_style": { - "stroke_width": 1, - }, - }, - "little_pendulum_config": { - "length": 1, - "gravity": 4.9, - "weight_diameter": 0.3, - "include_theta_label": False, - "include_velocity_vector": True, - "angle_arc_config": { - "radius": 0.2, - }, - "velocity_vector_config": { - "max_tip_length_to_length_ratio": 0.35, - "max_stroke_width_to_length_ratio": 6, - }, - "velocity_vector_multiple": 0.25, - "max_velocity_vector_length_to_length_ratio": 0.8, - }, - "big_pendulum_config": { - "length": 1.6, - "gravity": 4.9, - "damping": 0.2, - "weight_diameter": 0.3, - "include_velocity_vector": True, - "angle_arc_config": { - "radius": 0.5, - }, - "initial_theta": 80 * DEGREES, - "omega": -1, - "set_theta_label_height_cap": True, - }, - "n_thetas": 11, - "n_omegas": 7, - # "n_thetas": 5, - # "n_omegas": 3, - "initial_grid_wait_time": 15, - } - - def construct(self): - self.initialize_plane() - - simple = False - if simple: - self.add(self.plane) - else: - self.initialize_grid_of_states() - self.show_all_states_evolving() - self.show_grid_of_states_creation() - self.collapse_grid_into_points() - self.show_state_with_pair_of_numbers() - self.show_acceleration_dependence() - self.show_evolution_from_a_start_state() - - def initialize_grid_of_states(self): - pendulums = VGroup() - rects = VGroup() - state_grid = VGroup() - thetas = self.get_initial_thetas() - omegas = self.get_initial_omegas() - for omega in omegas: - row = VGroup() - for theta in thetas: - rect = Rectangle( - height=3, - width=3, - stroke_color=WHITE, - stroke_width=2, - fill_color=DARKER_GREY, - fill_opacity=1, - ) - pendulum = Pendulum( - initial_theta=theta, - omega=omega, - top_point=rect.get_center(), - **self.little_pendulum_config, - ) - pendulum.add_velocity_vector() - pendulums.add(pendulum) - rects.add(rect) - state = VGroup(rect, pendulum) - state.rect = rect - state.pendulum = pendulum - row.add(state) - row.arrange(RIGHT, buff=0) - state_grid.add(row) - state_grid.arrange(UP, buff=0) - - state_grid.set_height(FRAME_HEIGHT) - state_grid.center() - state_grid.save_state(use_deepcopy=True) - - self.state_grid = state_grid - self.pendulums = pendulums - - def initialize_plane(self): - plane = self.plane = NumberPlane( - **self.coordinate_plane_config - ) - plane.axis_labels = VGroup( - plane.get_x_axis_label( - "\\theta", RIGHT, UL, buff=SMALL_BUFF - ), - plane.get_y_axis_label( - "\\dot \\theta", UP, DR, buff=SMALL_BUFF - ).set_color(YELLOW), - ) - for label in plane.axis_labels: - label.add_background_rectangle() - plane.add(plane.axis_labels) - - plane.y_axis.add_numbers(direction=DL) - - x_axis = plane.x_axis - label_texs = ["\\pi \\over 2", "\\pi", "3\\pi \\over 2", "\\tau"] - values = [PI / 2, PI, 3 * PI / 2, TAU] - x_axis.coordinate_labels = VGroup() - x_axis.add(x_axis.coordinate_labels) - for value, label_tex in zip(values, label_texs): - for u in [-1, 1]: - tex = label_tex - if u < 0: - tex = "-" + tex - label = TexMobject(tex) - label.scale(0.5) - if label.get_height() > 0.4: - label.set_height(0.4) - point = x_axis.number_to_point(u * value) - label.next_to(point, DR, SMALL_BUFF) - x_axis.coordinate_labels.add(label) - return plane - - def show_all_states_evolving(self): - state_grid = self.state_grid - pendulums = self.pendulums - - for pendulum in pendulums: - pendulum.start_swinging() - - self.add(state_grid) - self.wait(self.initial_grid_wait_time) - - def show_grid_of_states_creation(self): - self.remove(self.state_grid) - self.initialize_grid_of_states() # Again - state_grid = self.state_grid - - title = TextMobject("All states") - title.to_edge(UP, buff=MED_SMALL_BUFF) - self.all_states_title = title - - state_grid.set_height( - FRAME_HEIGHT - title.get_height() - 2 * MED_SMALL_BUFF - ) - state_grid.to_edge(DOWN, buff=0) - - def place_at_top(state): - state.set_height(3) - state.center() - state.next_to(title, DOWN) - - middle_row = state_grid[len(state_grid) // 2] - middle_row_copy = middle_row.deepcopy() - right_column_copy = VGroup(*[ - row[-1] for row in state_grid - ]).deepcopy() - - for state in it.chain(middle_row_copy, right_column_copy): - place_at_top(state) - - self.add(title) - self.play( - ShowIncreasingSubsets(middle_row), - ShowIncreasingSubsets(middle_row_copy), - run_time=2, - rate_func=linear, - ) - self.wait() - self.play( - ShowIncreasingSubsets(state_grid), - ShowIncreasingSubsets(right_column_copy), - run_time=2, - rate_func=linear, - ) - self.remove(middle_row_copy) - self.remove(middle_row) - self.add(state_grid) - self.remove(right_column_copy) - self.play( - ReplacementTransform( - right_column_copy[-1], - state_grid[-1][-1], - remover=True - ) - ) - self.wait() - - def collapse_grid_into_points(self): - state_grid = self.state_grid - plane = self.plane - - dots = VGroup() - for row in state_grid: - for state in row: - dot = Dot( - self.get_state_point(state.pendulum), - radius=0.05, - color=YELLOW, - background_stroke_width=3, - background_stroke_color=BLACK, - ) - dot.state = state - dots.add(dot) - - self.add(plane) - self.remove(state_grid) - flat_state_group = VGroup(*it.chain(*state_grid)) - flat_dot_group = VGroup(*it.chain(*dots)) - self.clear() # The nuclear option - self.play( - ShowCreation(plane), - FadeOut(self.all_states_title), - LaggedStart(*[ - TransformFromCopy(m1, m2) - for m1, m2 in zip(flat_state_group, flat_dot_group) - ], lag_ratio=0.1, run_time=4) - ) - self.clear() # Again, not sure why I need this - self.add(plane, dots) - self.wait() - - self.state_dots = dots - - def show_state_with_pair_of_numbers(self): - axis_labels = self.plane.axis_labels - - state = self.get_flexible_state_picture() - dot = self.get_state_controlling_dot(state) - h_line = always_redraw( - lambda: self.get_tracking_h_line(dot.get_center()) - ) - v_line = always_redraw( - lambda: self.get_tracking_v_line(dot.get_center()) - ) - - self.add(dot) - anims = [GrowFromPoint(state, dot.get_center())] - if hasattr(self, "state_dots"): - anims.append(FadeOut(self.state_dots)) - self.play(*anims) - - for line, label in zip([h_line, v_line], axis_labels): - # self.add(line, dot) - self.play( - ShowCreation(line), - ShowCreationThenFadeAround(label), - run_time=1 - ) - for vect in LEFT, 3 * UP: - self.play( - ApplyMethod( - dot.shift, vect, - rate_func=there_and_back, - run_time=2, - ) - ) - self.wait() - for vect in 2 * LEFT, 3 * UP, 2 * RIGHT, 2 * DOWN: - self.play(dot.shift, vect, run_time=1.5) - self.wait() - - self.state = state - self.state_dot = dot - self.h_line = h_line - self.v_line = v_line - - def show_acceleration_dependence(self): - ode = get_ode() - thetas = ode.get_parts_by_tex("\\theta") - thetas[0].set_color(RED) - thetas[1].set_color(YELLOW) - ode.move_to( - FRAME_WIDTH * RIGHT / 4 + - FRAME_HEIGHT * UP / 4, - ) - ode.add_background_rectangle_to_submobjects() - - self.play(Write(ode)) - self.wait() - self.play(FadeOut(ode)) - - def show_evolution_from_a_start_state(self): - state = self.state - dot = self.state_dot - - self.play( - Rotating( - dot, - about_point=dot.get_center() + UR, - rate_func=smooth, - ) - ) - self.wait() - - # Show initial trajectory - state.pendulum.clear_updaters(recursive=False) - self.tie_dot_position_to_state(dot, state) - state.pendulum.start_swinging() - trajectory = self.get_evolving_trajectory(dot) - self.add(trajectory) - for x in range(20): - self.wait() - - # Talk through start - trajectory.suspend_updating() - state.pendulum.end_swinging() - dot.clear_updaters() - self.tie_state_to_dot_position(state, dot) - - alphas = np.linspace(0, 0.1, 1000) - index = np.argmin([ - trajectory.point_from_proportion(a)[1] - for a in alphas - ]) - alpha = alphas[index] - sub_traj = trajectory.copy() - sub_traj.suspend_updating() - sub_traj.pointwise_become_partial(trajectory, 0, alpha) - sub_traj.match_style(trajectory) - sub_traj.set_stroke(width=3) - - self.wait() - self.play(dot.move_to, sub_traj.get_start()) - self.wait() - self.play( - ShowCreation(sub_traj), - UpdateFromFunc( - dot, lambda d: d.move_to(sub_traj.get_end()) - ), - run_time=6, - ) - self.wait() - - # Comment on physical velocity vs. space position - v_vect = state.pendulum.velocity_vector - v_line_copy = self.v_line.copy() - v_line_copy.clear_updaters() - v_line_copy.set_stroke(PINK, 5) - td_label = self.plane.axis_labels[1] - y_axis_copy = self.plane.y_axis.copy() - y_axis_copy.submobjects = [] - y_axis_copy.match_style(v_line_copy) - - self.play(ShowCreationThenFadeAround(v_vect)) - self.play( - ShowCreationThenFadeAround(td_label), - ShowCreationThenFadeOut(y_axis_copy) - ) - self.play(ShowCreationThenFadeOut(v_line_copy)) - self.wait() - - # Abstract vs. physical - abstract = TextMobject("Abstract") - abstract.add_background_rectangle() - abstract.scale(2) - abstract.to_corner(UR) - physical = TextMobject("Physical") - physical.next_to(state.get_top(), DOWN) - - self.play( - ApplyMethod( - self.plane.set_stroke, YELLOW, 0.5, - rate_func=there_and_back, - lag_ratio=0.2, - ), - FadeInFromDown(abstract), - Animation(state), - ) - self.wait() - self.play(FadeInFromDown(physical)) - self.wait() - - # Continue on spiral - sub_traj.resume_updating() - state.pendulum.clear_updaters(recursive=False) - state.pendulum.start_swinging() - dot.clear_updaters() - self.tie_dot_position_to_state(dot, state) - self.wait(20) - - # - def get_initial_thetas(self): - angle = 3 * PI / 4 - return np.linspace(-angle, angle, self.n_thetas) - - def get_initial_omegas(self): - return np.linspace(-1.5, 1.5, self.n_omegas) - - def get_state(self, pendulum): - return (pendulum.get_theta(), pendulum.get_omega()) - - def get_state_point(self, pendulum): - return self.plane.coords_to_point( - *self.get_state(pendulum) - ) - - def get_flexible_state_picture(self): - buff = MED_SMALL_BUFF - height = FRAME_HEIGHT / 2 - buff - rect = Square( - side_length=height, - stroke_color=WHITE, - stroke_width=2, - fill_color="#111111", - fill_opacity=1, - ) - rect.to_corner(UL, buff=buff / 2) - pendulum = Pendulum( - top_point=rect.get_center(), - **self.big_pendulum_config - ) - pendulum.fixed_point_tracker.add_updater( - lambda m: m.move_to(rect.get_center()) - ) - - state = VGroup(rect, pendulum) - state.rect = rect - state.pendulum = pendulum - return state - - def get_state_dot(self, state): - dot = Dot(color=PINK) - dot.move_to(self.get_state_point(state.pendulum)) - return dot - - def get_state_controlling_dot(self, state): - dot = self.get_state_dot(state) - self.tie_state_to_dot_position(state, dot) - return dot - - def tie_state_to_dot_position(self, state, dot): - def update_pendulum(pend): - theta, omega = self.plane.point_to_coords( - dot.get_center() - ) - pend.set_theta(theta) - pend.set_omega(omega) - return pend - state.pendulum.add_updater(update_pendulum) - state.pendulum.get_arc_angle_theta = \ - lambda: self.plane.x_axis.point_to_number(dot.get_center()) - return self - - def tie_dot_position_to_state(self, dot, state): - dot.add_updater(lambda d: d.move_to( - self.get_state_point(state.pendulum) - )) - return dot - - def get_tracking_line(self, point, axis, color=WHITE): - number = axis.point_to_number(point) - axis_point = axis.number_to_point(number) - return DashedLine( - axis_point, point, - dash_length=0.025, - color=color, - ) - - def get_tracking_h_line(self, point): - return self.get_tracking_line( - point, self.plane.y_axis, WHITE, - ) - - def get_tracking_v_line(self, point): - return self.get_tracking_line( - point, self.plane.x_axis, YELLOW, - ) - - def get_evolving_trajectory(self, mobject): - trajectory = VMobject() - trajectory.start_new_path(mobject.get_center()) - trajectory.set_stroke(RED, 1) - - def update_trajectory(traj): - point = mobject.get_center() - if get_norm(trajectory.points[-1] == point) > 0.05: - traj.add_smooth_curve_to(point) - trajectory.add_updater(update_trajectory) - return trajectory - - -class IntroduceVectorField(VisualizeStates): - CONFIG = { - "vector_field_config": { - "max_magnitude": 3, - # "delta_x": 2, - # "delta_y": 2, - }, - "big_pendulum_config": { - "initial_theta": -80 * DEGREES, - "omega": 1, - } - } - - def construct(self): - self.initialize_plane() - self.add_flexible_state() - self.initialize_vector_field() - self.add_equation() - self.preview_vector_field() - self.write_vector_derivative() - self.interpret_first_coordinate() - self.interpret_second_coordinate() - self.show_full_vector_field() - self.show_trajectory() - - def initialize_plane(self): - super().initialize_plane() - self.add(self.plane) - - def initialize_vector_field(self): - self.vector_field = VectorField( - self.vector_field_func, - **self.vector_field_config, - ) - self.vector_field.sort(get_norm) - - def add_flexible_state(self): - self.state = self.get_flexible_state_picture() - self.add(self.state) - - def add_equation(self): - ode = get_ode() - ode.set_width(self.state.get_width() - MED_LARGE_BUFF) - ode.next_to(self.state.get_top(), DOWN, SMALL_BUFF) - thetas = ode.get_parts_by_tex("\\theta") - thetas[0].set_color(RED) - thetas[1].set_color(YELLOW) - ode_word = TextMobject("Differential equation") - ode_word.match_width(ode) - ode_word.next_to(ode, DOWN) - - self.play( - FadeInFrom(ode, 0.5 * DOWN), - FadeInFrom(ode_word, 0.5 * UP), - ) - - self.ode = ode - self.ode_word = ode_word - - def preview_vector_field(self): - vector_field = self.vector_field - - growth = LaggedStartMap( - GrowArrow, vector_field, - run_time=3, - lag_ratio=0.01, - ) - self.add( - growth.mobject, - vector_field, - self.state, self.ode, self.ode_word - ) - - self.play(growth) - self.wait() - self.play(FadeOut(vector_field)) - self.remove(growth.mobject) - - def write_vector_derivative(self): - state = self.state - plane = self.plane - - dot = self.get_state_dot(state) - - # Vector - vect = Arrow( - plane.coords_to_point(0, 0), - dot.get_center(), - buff=0, - color=dot.get_color() - ) - vect_sym, d_vect_sym = [ - self.get_vector_symbol( - "{" + a + "\\theta}(t)", - "{" + b + "\\theta}(t)", - ) - for a, b in [("", "\\dot"), ("\\dot", "\\ddot")] - ] - # vect_sym.get_entries()[1][0][1].set_color(YELLOW) - # d_vect_sym.get_entries()[0][0][1].set_color(YELLOW) - # d_vect_sym.get_entries()[1][0][1].set_color(RED) - vect_sym.next_to(vect.get_end(), UP, MED_LARGE_BUFF) - time_inputs = VGroup(*[ - e[-1][-2] for e in vect_sym.get_entries() - ]) - - # Derivative - ddt = TexMobject("d \\over dt") - ddt.set_height(0.9 * vect_sym.get_height()) - ddt.next_to(vect_sym, LEFT) - ddt.set_stroke(BLACK, 5, background=True) - equals = TexMobject("=") - equals.add_background_rectangle() - equals.next_to(vect_sym, RIGHT, SMALL_BUFF) - d_vect_sym.next_to(equals, RIGHT, SMALL_BUFF) - - # Little vector - angle_tracker = ValueTracker(0) - mag_tracker = ValueTracker(0.75) - d_vect = always_redraw( - lambda: Vector( - rotate_vector( - mag_tracker.get_value() * RIGHT, - angle_tracker.get_value(), - ), - color=WHITE - ).shift(dot.get_center()), - ) - d_vect_magnitude_factor_tracker = ValueTracker(2) - real_d_vect = always_redraw( - lambda: self.vector_field.get_vector( - dot.get_center() - ).scale( - d_vect_magnitude_factor_tracker.get_value(), - about_point=dot.get_center() - ) - ) - - # Show vector - self.play(TransformFromCopy(state[1], vect)) - self.play(FadeInFromDown(vect_sym)) - self.wait() - self.play(ReplacementTransform(vect, dot)) - self.wait() - self.play(LaggedStartMap( - ShowCreationThenFadeAround, time_inputs, - lag_ratio=0.1, - )) - self.wait() - - # Write Derivative - self.play(Write(ddt)) - self.play( - plane.y_axis.numbers.fade, 1, - FadeInFrom(equals, LEFT), - TransformFromCopy(vect_sym, d_vect_sym) - ) - self.wait() - - # Show as little vector - equation_group = VGroup( - ddt, vect_sym, equals, d_vect_sym - ) - self.play( - # equation_group.shift, 4 * DOWN, - equation_group.to_edge, RIGHT, LARGE_BUFF, - GrowArrow(d_vect), - ) - self.wait() - self.play(angle_tracker.set_value, 120 * DEGREES) - self.play(mag_tracker.set_value, 1.5) - self.wait() - - # Highlight new vector - self.play( - ShowCreationThenFadeAround(d_vect_sym), - FadeOut(d_vect) - ) - self.wait() - self.play( - TransformFromCopy(d_vect_sym, real_d_vect), - dot.set_color, WHITE, - ) - self.wait() - - # Take a walk - trajectory = VMobject() - trajectory.start_new_path(dot.get_center()) - dt = 0.01 - for x in range(130): - p = trajectory.points[-1] - dp_dt = self.vector_field_func(p) - trajectory.add_smooth_curve_to(p + dp_dt * dt) - self.tie_state_to_dot_position(state, dot) - self.play( - MoveAlongPath(dot, trajectory), - run_time=5, - rate_func=bezier([0, 0, 1, 1]), - ) - - self.state_dot = dot - self.d_vect = real_d_vect - self.equation_group = equation_group - self.d_vect_magnitude_factor_tracker = d_vect_magnitude_factor_tracker - - def interpret_first_coordinate(self): - equation = self.equation_group - ddt, vect_sym, equals, d_vect_sym = equation - dot = self.state_dot - - first_components_copy = VGroup( - vect_sym.get_entries()[0], - d_vect_sym.get_entries()[0], - ).copy() - rect = SurroundingRectangle(first_components_copy) - rect.set_stroke(YELLOW, 2) - - equation.save_state() - - self.play( - ShowCreation(rect), - equation.fade, 0.5, - Animation(first_components_copy), - ) - self.wait() - dot.save_state() - self.play(dot.shift, 2 * UP) - self.wait() - self.play(dot.shift, 6 * DOWN) - self.wait() - self.play(dot.restore) - self.wait() - - self.play( - equation.restore, - FadeOut(rect), - ) - self.remove(first_components_copy) - - def interpret_second_coordinate(self): - equation = self.equation_group - ddt, vect_sym, equals, d_vect_sym = equation - - second_components = VGroup( - vect_sym.get_entries()[1], - d_vect_sym.get_entries()[1], - ) - rect = SurroundingRectangle(second_components) - rect.set_stroke(YELLOW, 2) - - expanded_derivative = self.get_vector_symbol( - "{\\dot\\theta}(t)", - "-\\mu {\\dot\\theta}(t)" + - "-(g / L) \\sin\\big({\\theta}(t)\\big)", - ) - expanded_derivative.move_to(d_vect_sym) - expanded_derivative.to_edge(RIGHT, MED_SMALL_BUFF) - equals2 = TexMobject("=") - equals2.next_to(expanded_derivative, LEFT, SMALL_BUFF) - - equation.save_state() - self.play( - ShowCreation(rect), - ) - self.wait() - self.play( - FadeInFrom(expanded_derivative, LEFT), - FadeIn(equals2), - equation.next_to, equals2, LEFT, SMALL_BUFF, - MaintainPositionRelativeTo(rect, equation), - VFadeOut(rect), - ) - self.wait() - - self.full_equation = VGroup( - *equation, equals2, expanded_derivative, - ) - - def show_full_vector_field(self): - vector_field = self.vector_field - state = self.state - ode = self.ode - ode_word = self.ode_word - equation = self.full_equation - d_vect = self.d_vect - dot = self.state_dot - - equation.generate_target() - equation.target.scale(0.7) - equation.target.to_edge(DOWN, LARGE_BUFF) - equation.target.to_edge(LEFT, MED_SMALL_BUFF) - equation_rect = BackgroundRectangle(equation.target) - - growth = LaggedStartMap( - GrowArrow, vector_field, - run_time=3, - lag_ratio=0.01, - ) - self.add( - growth.mobject, - state, ode, ode_word, - equation_rect, equation, dot, - d_vect, - ) - self.play( - growth, - FadeIn(equation_rect), - MoveToTarget(equation), - self.d_vect_magnitude_factor_tracker.set_value, 1, - ) - - def show_trajectory(self): - state = self.state - dot = self.state_dot - - state.pendulum.clear_updaters(recursive=False) - self.tie_dot_position_to_state(dot, state) - state.pendulum.start_swinging() - - trajectory = self.get_evolving_trajectory(dot) - trajectory.set_stroke(WHITE, 3) - - self.add(trajectory, dot) - self.wait(25) - - # - def get_vector_symbol(self, tex1, tex2): - t2c = { - "{\\theta}": BLUE, - "{\\dot\\theta}": YELLOW, - "{\\omega}": YELLOW, - "{\\ddot\\theta}": RED, - } - return get_vector_symbol( - tex1, tex2, - element_to_mobject_config={ - "tex_to_color_map": t2c, - } - ).scale(0.9) - - def vector_field_func(self, point): - x, y = self.plane.point_to_coords(point) - mu, g, L = [ - self.big_pendulum_config.get(key) - for key in ["damping", "gravity", "length"] - ] - return pendulum_vector_field_func( - x * RIGHT + y * UP, - mu=mu, g=g, L=L - ) - - def ask_about_change(self): - state = self.state - - dot = self.get_state_dot(state) - d_vect = Vector(0.75 * RIGHT, color=WHITE) - d_vect.shift(dot.get_center()) - q_mark = always_redraw( - lambda: TexMobject("?").move_to( - d_vect.get_end() + 0.4 * rotate_vector( - d_vect.get_vector(), 90 * DEGREES, - ), - ) - ) - - self.play(TransformFromCopy(state[1], dot)) - self.tie_state_to_dot_position(state, dot) - self.play( - GrowArrow(d_vect), - FadeInFromDown(q_mark) - ) - for x in range(4): - angle = 90 * DEGREES - self.play( - Rotate( - d_vect, angle, - about_point=d_vect.get_start(), - ) - ) - self.play( - dot.shift, - 0.3 * d_vect.get_vector(), - rate_func=there_and_back, - ) - - -class ShowPendulumPhaseFlow(IntroduceVectorField): - CONFIG = { - "coordinate_plane_config": { - "x_axis_config": { - "unit_size": 0.8, - }, - "x_max": 9, - "x_min": -9, - }, - "flow_time": 20, - } - - def construct(self): - self.initialize_plane() - self.initialize_vector_field() - plane = self.plane - field = self.vector_field - self.add(plane, field) - - stream_lines = StreamLines( - field.func, - delta_x=0.3, - delta_y=0.3, - ) - animated_stream_lines = AnimatedStreamLines( - stream_lines, - line_anim_class=ShowPassingFlashWithThinningStrokeWidth, - ) - - self.add(animated_stream_lines) - self.wait(self.flow_time) - - -class ShowHighVelocityCase(ShowPendulumPhaseFlow, MovingCameraScene): - CONFIG = { - "coordinate_plane_config": { - "x_max": 15, - "x_min": -15, - }, - "vector_field_config": { - "x_max": 15, - }, - "big_pendulum_config": { - "max_velocity_vector_length_to_length_ratio": 1, - }, - "run_time": 25, - "initial_theta": 0, - "initial_theta_dot": 4, - "frame_shift_vect": TAU * RIGHT, - } - - def setup(self): - MovingCameraScene.setup(self) - - def construct(self): - self.initialize_plane_and_field() - self.add_flexible_state() - self.show_high_vector() - self.show_trajectory() - - def initialize_plane_and_field(self): - self.initialize_plane() - self.add(self.plane) - self.initialize_vector_field() - self.add(self.vector_field) - - def add_flexible_state(self): - super().add_flexible_state() - state = self.state - plane = self.plane - - state.to_edge(DOWN, buff=SMALL_BUFF), - start_point = plane.coords_to_point( - self.initial_theta, - self.initial_theta_dot, - ) - dot = self.get_state_controlling_dot(state) - dot.move_to(start_point) - state.update() - - self.dot = dot - self.start_point = start_point - - def show_high_vector(self): - field = self.vector_field - top_vectors = VGroup(*filter( - lambda a: np.all(a.get_center() > [-10, 1.5, -10]), - field - )).copy() - top_vectors.set_stroke(PINK, 3) - top_vectors.sort(lambda p: p[0]) - - self.play( - ShowCreationThenFadeOut( - top_vectors, - run_time=2, - lag_ratio=0.01, - ) - ) - - def show_trajectory(self): - state = self.state - frame = self.camera_frame - dot = self.dot - start_point = self.start_point - - traj = self.get_trajectory(start_point, self.run_time) - - self.add(traj, dot) - anims = [ - ShowCreation( - traj, - rate_func=linear, - ), - UpdateFromFunc( - dot, lambda d: d.move_to(traj.points[-1]) - ), - ] - if get_norm(self.frame_shift_vect) > 0: - anims += [ - ApplyMethod( - frame.shift, self.frame_shift_vect, - rate_func=squish_rate_func( - smooth, 0, 0.3, - ) - ), - MaintainPositionRelativeTo(state.rect, frame), - ] - self.play(*anims, run_time=total_time) - - def get_trajectory(self, start_point, time, dt=0.1, added_steps=100): - field = self.vector_field - traj = VMobject() - traj.start_new_path(start_point) - for x in range(int(time / dt)): - last_point = traj.points[-1] - for y in range(added_steps): - dp_dt = field.func(last_point) - last_point += dp_dt * dt / added_steps - traj.add_smooth_curve_to(last_point) - traj.make_smooth() - traj.set_stroke(WHITE, 2) - return traj - - -class TweakMuInFormula(Scene): - def construct(self): - self.add(FullScreenFadeRectangle( - opacity=0.75, - )) - - ode = get_ode() - ode.to_edge(DOWN, buff=LARGE_BUFF) - mu = ode.get_part_by_tex("\\mu") - lil_rect = SurroundingRectangle(mu, buff=0.5 * SMALL_BUFF) - lil_rect.stretch(1.2, 1, about_edge=DOWN) - lil_rect.set_stroke(PINK, 2) - - interval = UnitInterval() - interval.add_numbers( - *np.arange(0, 1.2, 0.2) - ) - interval.next_to(ode, UP, LARGE_BUFF) - big_rect_seed = SurroundingRectangle(interval, buff=MED_SMALL_BUFF) - big_rect_seed.stretch(1.5, 1, about_edge=DOWN) - big_rect_seed.stretch(1.2, 0) - big_rect = VGroup(*[ - DashedLine(v1, v2) - for v1, v2 in adjacent_pairs(big_rect_seed.get_vertices()) - ]) - big_rect.set_stroke(PINK, 2) - - arrow = Arrow( - lil_rect.get_top(), - big_rect_seed.point_from_proportion(0.65), - buff=SMALL_BUFF, - ) - arrow.match_color(lil_rect) - - mu_tracker = ValueTracker(0.1) - get_mu = mu_tracker.get_value - - triangle = Triangle( - start_angle=-90 * DEGREES, - stroke_width=0, - fill_opacity=1, - fill_color=WHITE, - ) - triangle.set_height(0.2) - triangle.add_updater(lambda t: t.next_to( - interval.number_to_point(get_mu()), - UP, buff=0, - )) - - equation = VGroup( - TexMobject("\\mu = "), - DecimalNumber(), - ) - equation.add_updater( - lambda e: e.arrange(RIGHT).next_to( - triangle, UP, SMALL_BUFF, - ).shift(0.4 * RIGHT) - ) - equation[-1].add_updater( - lambda d: d.set_value(get_mu()).shift(0.05 * UL) - ) - - self.add(ode) - self.play(ShowCreation(lil_rect)) - self.play( - GrowFromPoint(interval, mu.get_center()), - GrowFromPoint(triangle, mu.get_center()), - GrowFromPoint(equation, mu.get_center()), - TransformFromCopy(lil_rect, big_rect), - ShowCreation(arrow) - ) - self.wait() - self.play(mu_tracker.set_value, 0.9, run_time=5) - self.wait() - - -class TweakMuInVectorField(ShowPendulumPhaseFlow): - def construct(self): - self.initialize_plane() - plane = self.plane - self.add(plane) - - mu_tracker = ValueTracker(0.1) - get_mu = mu_tracker.get_value - - def vector_field_func(p): - x, y = plane.point_to_coords(p) - mu = get_mu() - g = self.big_pendulum_config.get("gravity") - L = self.big_pendulum_config.get("length") - return pendulum_vector_field_func( - x * RIGHT + y * UP, - mu=mu, g=g, L=L - ) - - def get_vector_field(): - return VectorField( - vector_field_func, - **self.vector_field_config, - ) - - field = always_redraw(get_vector_field) - self.add(field) - - self.play( - mu_tracker.set_value, 0.9, - run_time=5, - ) - field.suspend_updating() - - stream_lines = StreamLines( - field.func, - delta_x=0.3, - delta_y=0.3, - ) - animated_stream_lines = AnimatedStreamLines( - stream_lines, - line_anim_class=ShowPassingFlashWithThinningStrokeWidth, - ) - self.add(animated_stream_lines) - self.wait(self.flow_time) - - -class HighAmplitudePendulum(ShowHighVelocityCase): - CONFIG = { - "big_pendulum_config": { - "damping": 0.02, - }, - "initial_theta": 175 * DEGREES, - "initial_theta_dot": 0, - "frame_shift_vect": 0 * RIGHT, - } - - def construct(self): - self.initialize_plane_and_field() - self.add_flexible_state() - self.show_trajectory() - - -class SpectrumOfStartingStates(ShowHighVelocityCase): - CONFIG = { - "run_time": 15, - } - - def construct(self): - self.initialize_plane_and_field() - self.vector_field.set_opacity(0.5) - self.show_many_trajectories() - - def show_many_trajectories(self): - plane = self.plane - - delta_x = 0.5 - delta_y = 0.5 - n = 20 - - start_points = [ - plane.coords_to_point(x, y) - for x in np.linspace(PI - delta_x, PI + delta_x, n) - for y in np.linspace(-delta_y, delta_y, n) - ] - start_points.sort( - key=lambda p: np.dot(p, UL) - ) - time = self.run_time - - # Count points - dots = VGroup(*[ - Dot(sp, radius=0.025) - for sp in start_points - ]) - dots.set_color_by_gradient(PINK, BLUE, YELLOW) - words = TextMobject( - "Spectrum of\\\\", "initial conditions" - ) - words.set_stroke(BLACK, 5, background=True) - words.next_to(dots, UP) - - self.play( - # ShowIncreasingSubsets(dots, run_time=2), - LaggedStartMap( - FadeInFromLarge, dots, - lambda m: (m, 10), - run_time=2 - ), - FadeInFromDown(words), - ) - self.wait() - - trajs = VGroup() - for sp in start_points: - trajs.add( - self.get_trajectory( - sp, time, - added_steps=10, - ) - ) - for traj, dot in zip(trajs, dots): - traj.set_stroke(dot.get_color(), 1) - - def update_dots(ds): - for d, t in zip(ds, trajs): - d.move_to(t.points[-1]) - return ds - dots.add_updater(update_dots) - - self.add(dots, trajs, words) - self.play( - ShowCreation( - trajs, - lag_ratio=0, - ), - rate_func=linear, - run_time=time, - ) - self.wait() - - -class AskAboutStability(ShowHighVelocityCase): - CONFIG = { - "initial_theta": 60 * DEGREES, - "initial_theta_dot": 1, - } - - def construct(self): - self.initialize_plane_and_field() - self.add_flexible_state() - self.show_fixed_points() - self.label_fixed_points() - self.ask_about_stability() - self.show_nudges() - - def show_fixed_points(self): - state1 = self.state - plane = self.plane - dot1 = self.dot - - state2 = self.get_flexible_state_picture() - state2.to_corner(DR, buff=SMALL_BUFF) - dot2 = self.get_state_controlling_dot(state2) - dot2.set_color(BLUE) - - fp1 = plane.coords_to_point(0, 0) - fp2 = plane.coords_to_point(PI, 0) - - self.play( - dot1.move_to, fp1, - run_time=3, - ) - self.wait() - self.play(FadeIn(state2)) - self.play( - dot2.move_to, fp2, - path_arc=-30 * DEGREES, - run_time=2, - ) - self.wait() - - self.state1 = state1 - self.state2 = state2 - self.dot1 = dot1 - self.dot2 = dot2 - - def label_fixed_points(self): - dots = VGroup(self.dot1, self.dot2) - - label = TextMobject("Fixed points") - label.scale(1.5) - label.set_stroke(BLACK, 5, background=True) - label.next_to(dots, UP, buff=2) - label.shift(SMALL_BUFF * DOWN) - - arrows = VGroup(*[ - Arrow( - label.get_bottom(), dot.get_center(), - color=dot.get_color(), - ) - for dot in dots - ]) - - self.play( - self.vector_field.set_opacity, 0.5, - FadeInFromDown(label) - ) - self.play(ShowCreation(arrows)) - self.wait(2) - - self.to_fade = VGroup(label, arrows) - - def ask_about_stability(self): - question = TextMobject("Stable?") - question.scale(2) - question.shift(FRAME_WIDTH * RIGHT / 4) - question.to_edge(UP) - question.set_stroke(BLACK, 5, background=True) - - self.play(Write(question)) - self.play(FadeOut(self.to_fade)) - - def show_nudges(self): - dots = VGroup(self.dot1, self.dot2) - time = 20 - - self.play(*[ - ApplyMethod( - dot.shift, 0.1 * UL, - rate_func=rush_from, - ) - for dot in dots - ]) - - trajs = VGroup() - for dot in dots: - traj = self.get_trajectory( - dot.get_center(), - time, - ) - traj.set_stroke(dot.get_color(), 2) - trajs.add(traj) - - def update_dots(ds): - for t, d in zip(trajs, ds): - d.move_to(t.points[-1]) - dots.add_updater(update_dots) - self.add(trajs, dots) - self.play( - ShowCreation(trajs, lag_ratio=0), - rate_func=linear, - run_time=time - ) - self.wait() - - -class LovePhaseSpace(ShowHighVelocityCase): - CONFIG = { - "vector_field_config": { - "max_magnitude": 4, - # "delta_x": 2, - # "delta_y": 2, - }, - "a": 0.5, - "b": 0.3, - "mu": 0.2, - } - - def construct(self): - self.setup_plane() - self.add_equations() - self.show_vector_field() - self.show_example_trajectories() - self.add_resistance_term() - self.show_new_trajectories() - - def setup_plane(self): - plane = self.plane = NumberPlane() - plane.add_coordinates() - self.add(plane) - - h1, h2 = hearts = VGroup(*[ - get_heart_var(i) - for i in (1, 2) - ]) - hearts.scale(0.5) - hearts.set_stroke(BLACK, 5, background=True) - - h1.next_to(plane.x_axis.get_right(), UL, SMALL_BUFF) - h2.next_to(plane.y_axis.get_top(), DR, SMALL_BUFF) - for h in hearts: - h.shift_onto_screen(buff=MED_SMALL_BUFF) - plane.add(hearts) - - self.axis_hearts = hearts - - def add_equations(self): - equations = VGroup( - get_love_equation1(), - get_love_equation2(), - ) - equations.scale(0.5) - equations.arrange( - DOWN, - aligned_edge=LEFT, - buff=MED_LARGE_BUFF - ) - equations.to_corner(UL) - equations.add_background_rectangle_to_submobjects() - # for eq in equations: - # eq.add_background_rectangle_to_submobjects() - - self.add(equations) - self.equations = equations - - def show_vector_field(self): - field = VectorField( - lambda p: np.array([ - self.a * p[1], -self.b * p[0], 0 - ]), - **self.vector_field_config - ) - field.sort(get_norm) - x_range = np.arange(-7, 7.5, 0.5) - y_range = np.arange(-4, 4.5, 0.5) - x_axis_arrows = VGroup(*[ - field.get_vector([x, 0, 0]) - for x in x_range - ]) - y_axis_arrows = VGroup(*[ - field.get_vector([0, y, 0]) - for y in y_range - ]) - axis_arrows = VGroup(*x_axis_arrows, *y_axis_arrows) - - axis_arrows.save_state() - for arrow in axis_arrows: - real_len = get_norm(field.func(arrow.get_start())) - arrow.scale( - 0.5 * real_len / arrow.get_length(), - about_point=arrow.get_start() - ) - - self.play( - LaggedStartMap(GrowArrow, x_axis_arrows), - ) - self.play( - LaggedStartMap(GrowArrow, y_axis_arrows), - ) - self.wait() - self.add(field, self.equations, self.axis_hearts) - self.play( - axis_arrows.restore, - # axis_arrows.fade, 1, - ShowCreation(field), - run_time=3 - ) - self.remove(axis_arrows) - self.wait() - - self.field = self.vector_field = field - - def show_example_trajectories(self): - n_points = 20 - total_time = 30 - - start_points = self.start_points = [ - 2.5 * np.random.random() * rotate_vector( - RIGHT, - TAU * np.random.random() - ) - for x in range(n_points) - ] - dots = VGroup(*[Dot(sp) for sp in start_points]) - dots.set_color_by_gradient(BLUE, WHITE) - - words = TextMobject("Possible initial\\\\", "conditions") - words.scale(1.5) - words.add_background_rectangle_to_submobjects() - words.set_stroke(BLACK, 5, background=True) - words.shift(FRAME_WIDTH * RIGHT / 4) - words.to_edge(UP) - self.possibility_words = words - - self.play( - LaggedStartMap( - FadeInFromLarge, dots, - lambda m: (m, 5) - ), - FadeInFromDown(words) - ) - - trajs = VGroup(*[ - self.get_trajectory( - sp, total_time, - added_steps=10, - ) - for sp in start_points - ]) - trajs.set_color_by_gradient(BLUE, WHITE) - - dots.trajs = trajs - - def update_dots(ds): - for d, t in zip(ds, ds.trajs): - d.move_to(t.points[-1]) - dots.add_updater(update_dots) - - self.add(trajs, dots) - self.play( - ShowCreation( - trajs, - lag_ratio=0, - run_time=10, - rate_func=linear, - ) - ) - - self.trajs = trajs - self.dots = dots - - def add_resistance_term(self): - added_term = VGroup( - TexMobject("-\\mu"), - get_heart_var(2).scale(0.5), - ) - added_term.arrange(RIGHT, buff=SMALL_BUFF) - equation2 = self.equations[1] - equation2.generate_target() - br, deriv, eq, neg_b, h1 = equation2.target - added_term.next_to(eq, RIGHT, SMALL_BUFF) - added_term.align_to(h1, DOWN) - VGroup(neg_b, h1).next_to( - added_term, RIGHT, SMALL_BUFF, - aligned_edge=DOWN, - ) - br.stretch(1.2, 0, about_edge=LEFT) - - brace = Brace(added_term, DOWN, buff=SMALL_BUFF) - words = brace.get_text( - "``Resistance'' term" - ) - words.set_stroke(BLACK, 5, background=True) - words.add_background_rectangle() - - self.add(equation2, added_term) - self.play( - MoveToTarget(equation2), - FadeInFromDown(added_term), - GrowFromCenter(brace), - Write(words), - ) - self.play(ShowCreationThenFadeAround(added_term)) - - equation2.add(added_term, brace, words) - - def show_new_trajectories(self): - dots = self.dots - trajs = self.trajs - field = self.field - - new_field = VectorField( - lambda p: np.array([ - self.a * p[1], - -self.mu * p[1] - self.b * p[0], - 0 - ]), - **self.vector_field_config - ) - new_field.sort(get_norm) - - field.generate_target() - for vect in field.target: - vect.become(new_field.get_vector(vect.get_start())) - - self.play(*map( - FadeOut, - [trajs, dots, self.possibility_words] - )) - self.play(MoveToTarget(field)) - self.vector_field = new_field - - total_time = 30 - new_trajs = VGroup(*[ - self.get_trajectory( - sp, total_time, - added_steps=10, - ) - for sp in self.start_points - ]) - new_trajs.set_color_by_gradient(BLUE, WHITE) - dots.trajs = new_trajs - - self.add(new_trajs, dots) - self.play( - ShowCreation( - new_trajs, - lag_ratio=0, - run_time=10, - rate_func=linear, - ), - ) - self.wait() - - -class TakeManyTinySteps(IntroduceVectorField): - CONFIG = { - "initial_theta": 60 * DEGREES, - "initial_theta_dot": 0, - "initial_theta_tex": "\\pi / 3", - "initial_theta_dot_tex": "0", - } - - def construct(self): - self.initialize_plane_and_field() - self.take_many_time_steps() - - def initialize_plane_and_field(self): - self.initialize_plane() - self.initialize_vector_field() - field = self.vector_field - field.set_opacity(0.35) - self.add(self.plane, field) - - def take_many_time_steps(self): - self.setup_trackers() - delta_t_tracker = self.delta_t_tracker - get_delta_t = delta_t_tracker.get_value - time_tracker = self.time_tracker - get_t = time_tracker.get_value - - traj = always_redraw( - lambda: self.get_time_step_trajectory( - get_delta_t(), - get_t(), - self.initial_theta, - self.initial_theta_dot, - ) - ) - vectors = always_redraw( - lambda: self.get_path_vectors( - get_delta_t(), - get_t(), - self.initial_theta, - self.initial_theta_dot, - ) - ) - - # Labels - labels, init_labels = self.get_labels(get_t, get_delta_t) - t_label, dt_label = labels - - theta_t_label = TexMobject("\\theta(t)...\\text{ish}") - theta_t_label.scale(0.75) - theta_t_label.add_updater(lambda m: m.next_to( - vectors[-1].get_end(), - vectors[-1].get_vector(), - SMALL_BUFF, - )) - - self.add(traj, vectors, init_labels, labels) - time_tracker.set_value(0) - target_time = 10 - self.play( - VFadeIn(theta_t_label), - ApplyMethod( - time_tracker.set_value, target_time, - run_time=5, - rate_func=linear, - ) - ) - self.wait() - t_label[-1].clear_updaters() - self.remove(theta_t_label) - target_delta_t = 0.01 - self.play( - delta_t_tracker.set_value, target_delta_t, - run_time=7, - ) - self.wait() - traj.clear_updaters() - vectors.clear_updaters() - - # Show steps - count_tracker = ValueTracker(0) - count = Integer() - count.scale(1.5) - count.to_edge(LEFT) - count.shift(UP + MED_SMALL_BUFF * UR) - count.add_updater(lambda c: c.set_value( - count_tracker.get_value() - )) - count_label = TextMobject("steps") - count_label.scale(1.5) - count_label.add_updater( - lambda m: m.next_to( - count[-1], RIGHT, - submobject_to_align=m[0][0], - aligned_edge=DOWN - ) - ) - - scaled_vectors = vectors.copy() - scaled_vectors.clear_updaters() - for vector in scaled_vectors: - vector.scale( - 1 / vector.get_length(), - about_point=vector.get_start() - ) - vector.set_color(YELLOW) - - def update_scaled_vectors(group): - group.set_opacity(0) - group[min( - int(count.get_value()), - len(group) - 1, - )].set_opacity(1) - - scaled_vectors.add_updater(update_scaled_vectors) - - self.add(count, count_label, scaled_vectors) - self.play( - ApplyMethod( - count_tracker.set_value, - int(target_time / target_delta_t), - rate_func=linear, - ), - run_time=5, - ) - self.play(FadeOut(scaled_vectors)) - self.wait() - - def setup_trackers(self): - self.delta_t_tracker = ValueTracker(0.5) - self.time_tracker = ValueTracker(10) - - def get_labels(self, get_t, get_delta_t): - t_label, dt_label = labels = VGroup(*[ - VGroup( - TexMobject("{} = ".format(s)), - DecimalNumber(0) - ).arrange(RIGHT, aligned_edge=DOWN) - for s in ("t", "{\\Delta t}") - ]) - - dt_label[-1].add_updater( - lambda d: d.set_value(get_delta_t()) - ) - t_label[-1].add_updater( - lambda d: d.set_value( - int(np.ceil(get_t() / get_delta_t())) * get_delta_t() - ) - ) - - init_labels = VGroup( - TexMobject( - "\\theta_0", "=", self.initial_theta_tex, - tex_to_color_map={"\\theta": BLUE}, - ), - TexMobject( - "{\\dot\\theta}_0 =", self.initial_theta_dot_tex, - tex_to_color_map={"{\\dot\\theta}": YELLOW}, - ), - ) - for group in labels, init_labels: - for label in group: - label.scale(1.25) - label.add_background_rectangle() - group.arrange(DOWN) - group.shift(FRAME_WIDTH * RIGHT / 4) - labels.to_edge(UP) - init_labels.shift(2 * DOWN) - - return labels, init_labels - - # - def get_time_step_points(self, delta_t, total_time, theta_0, theta_dot_0): - plane = self.plane - field = self.vector_field - curr_point = plane.coords_to_point( - theta_0, - theta_dot_0, - ) - points = [curr_point] - t = 0 - while t < total_time: - new_point = curr_point + field.func(curr_point) * delta_t - points.append(new_point) - curr_point = new_point - t += delta_t - return points - - def get_time_step_trajectory(self, delta_t, total_time, theta_0, theta_dot_0): - traj = VMobject() - traj.set_points_as_corners( - self.get_time_step_points( - delta_t, total_time, - theta_0, theta_dot_0, - ) - ) - traj.set_stroke(WHITE, 2) - return traj - - def get_path_vectors(self, delta_t, total_time, theta_0, theta_dot_0): - corners = self.get_time_step_points( - delta_t, total_time, - theta_0, theta_dot_0, - ) - result = VGroup() - for a1, a2 in zip(corners, corners[1:]): - vector = Arrow( - a1, a2, buff=0, - ) - vector.match_style( - self.vector_field.get_vector(a1) - ) - result.add(vector) - return result - - -class SetupToTakingManyTinySteps(TakeManyTinySteps): - CONFIG = { - } - - def construct(self): - self.initialize_plane_and_field() - self.show_step() - - def show_step(self): - self.setup_trackers() - get_delta_t = self.delta_t_tracker.get_value - get_t = self.time_tracker.get_value - - labels, init_labels = self.get_labels(get_t, get_delta_t) - t_label, dt_label = labels - - dt_part = dt_label[1][0][:-1].copy() - - init_labels_rect = SurroundingRectangle(init_labels) - init_labels_rect.set_color(PINK) - - field = self.vector_field - point = self.plane.coords_to_point( - self.initial_theta, - self.initial_theta_dot, - ) - dot = Dot(point, color=init_labels_rect.get_color()) - - vector_value = field.func(point) - vector = field.get_vector(point) - vector.scale( - get_norm(vector_value) / vector.get_length(), - about_point=vector.get_start() - ) - scaled_vector = vector.copy() - scaled_vector.scale( - get_delta_t(), - about_point=scaled_vector.get_start() - ) - - v_label = TexMobject("\\vec{\\textbf{v}}") - v_label.set_stroke(BLACK, 5, background=True) - v_label.next_to(vector, LEFT, SMALL_BUFF) - - real_field = field.copy() - for v in real_field: - p = v.get_start() - v.scale( - get_norm(field.func(p)) / v.get_length(), - about_point=p - ) - - self.add(init_labels) - self.play(ShowCreation(init_labels_rect)) - self.play(ReplacementTransform( - init_labels_rect, - dot, - )) - self.wait() - self.add(vector, dot) - self.play( - ShowCreation(vector), - FadeInFrom(v_label, RIGHT), - ) - self.play(FadeInFromDown(dt_label)) - self.wait() - - # - v_label.generate_target() - dt_part.generate_target() - dt_part.target.next_to(scaled_vector, LEFT, SMALL_BUFF) - v_label.target.next_to(dt_part.target, LEFT, SMALL_BUFF) - rect = BackgroundRectangle( - VGroup(v_label.target, dt_part.target) - ) - - self.add(rect, v_label, dt_part) - self.play( - ReplacementTransform(vector, scaled_vector), - FadeIn(rect), - MoveToTarget(v_label), - MoveToTarget(dt_part), - ) - self.add(scaled_vector, dot) - self.wait() - - self.play( - LaggedStart(*[ - Transform( - sm1, sm2, - rate_func=there_and_back_with_pause, - ) - for sm1, sm2 in zip(field, real_field) - ], lag_ratio=0.001, run_time=3) - ) - self.wait() - - -class ShowClutterPrevention(SetupToTakingManyTinySteps): - def construct(self): - self.initialize_plane_and_field() - - # Copied from above scene - field = self.vector_field - real_field = field.copy() - for v in real_field: - p = v.get_start() - v.scale( - get_norm(field.func(p)) / v.get_length(), - about_point=p - ) - - self.play( - LaggedStart(*[ - Transform( - sm1, sm2, - rate_func=there_and_back_with_pause, - ) - for sm1, sm2 in zip(field, real_field) - ], lag_ratio=0.001, run_time=3) - ) - self.wait() - - -class ManyStepsFromDifferentStartingPoints(TakeManyTinySteps): - CONFIG = { - "initial_thetas": np.linspace(0.1, PI - 0.1, 10), - "initial_theta_dot": 0, - } - - def construct(self): - self.initialize_plane_and_field() - self.take_many_time_steps() - - def take_many_time_steps(self): - delta_t_tracker = ValueTracker(0.2) - get_delta_t = delta_t_tracker.get_value - - time_tracker = ValueTracker(10) - get_t = time_tracker.get_value - # traj = always_redraw( - # lambda: VGroup(*[ - # self.get_time_step_trajectory( - # get_delta_t(), - # get_t(), - # theta, - # self.initial_theta_dot, - # ) - # for theta in self.initial_thetas - # ]) - # ) - vectors = always_redraw( - lambda: VGroup(*[ - self.get_path_vectors( - get_delta_t(), - get_t(), - theta, - self.initial_theta_dot, - ) - for theta in self.initial_thetas - ]) - ) - - self.add(vectors) - time_tracker.set_value(0) - self.play( - time_tracker.set_value, 5, - run_time=5, - rate_func=linear, - ) - - -class Thumbnail(IntroduceVectorField): - CONFIG = { - "vector_field_config": { - # "delta_x": 0.5, - # "delta_y": 0.5, - # "max_magnitude": 5, - # "length_func": lambda norm: 0.5 * sigmoid(norm), - "delta_x": 1, - "delta_y": 1, - "max_magnitude": 5, - "length_func": lambda norm: 0.9 * sigmoid(norm), - }, - "big_pendulum_config": { - "damping": 0.4, - }, - } - - def construct(self): - self.initialize_plane() - self.plane.axes.set_stroke(width=0.5) - self.initialize_vector_field() - - field = self.vector_field - field.set_stroke(width=5) - for vector in field: - vector.set_stroke(width=8) - vector.tip.set_stroke(width=0) - vector.tip.scale(1.5, about_point=vector.get_last_point()) - vector.set_opacity(1) - - title = TextMobject("Differential\\\\", "equations") - title.space_out_submobjects(0.8) - # title.scale(3) - title.set_width(FRAME_WIDTH - 3) - # title.to_edge(UP) - # title[1].to_edge(DOWN) - - subtitle = TextMobject("Studying the unsolvable") - subtitle.set_width(FRAME_WIDTH - 1) - subtitle.set_color(WHITE) - subtitle.to_edge(DOWN, buff=1) - - # title.center() - title.to_edge(UP, buff=1) - title.add(subtitle) - # title.set_stroke(BLACK, 15, background=True) - # title.add_background_rectangle_to_submobjects(opacity=0.5) - title.set_stroke(BLACK, 15, background=True) - subtitle.set_stroke(RED, 2, background=True) - # for part in title: - # part[0].set_fill(opacity=0.25) - # part[0].set_stroke(width=0) - black_parts = VGroup() - for mob in title.family_members_with_points(): - for sp in mob.get_subpaths(): - new_mob = VMobject() - new_mob.set_points(sp) - new_mob.set_fill(BLACK, 0.25) - new_mob.set_stroke(width=0) - black_parts.add(new_mob) - - # for vect in field: - # for mob in title.family_members_with_points(): - # for p in [vect.get_start(), vect.get_end()]: - # x, y = p[:2] - # x0, y0 = mob.get_corner(DL)[:2] - # x1, y1 = mob.get_corner(UR)[:2] - # if x0 < x < x1 and y0 < y < y1: - # vect.set_opacity(0.25) - # vect.tip.set_stroke(width=0) - - self.add(self.plane) - self.add(field) - # self.add(black_parts) - # self.add(title) - - self.add_line(self.plane) - - def add_line(self, axes): - func = self.vector_field_func - - line = VMobject() - line.start_new_path(axes.c2p(-TAU, 3.5)) - - dt = 0.1 - t = 0 - total_time = 40 - - while t < total_time: - t += dt - last_point = line.get_last_point() - new_point = last_point + dt * func(last_point) - if new_point[0] > FRAME_WIDTH / 2: - new_point = last_point + FRAME_WIDTH * LEFT - line.start_new_path(new_point) - else: - line.add_smooth_curve_to(new_point) - - line.set_stroke(WHITE, 6) - line.make_smooth() - self.add(line) diff --git a/from_3b1b/active/diffyq/part1/pi_scenes.py b/from_3b1b/active/diffyq/part1/pi_scenes.py deleted file mode 100644 index 8b1fde44..00000000 --- a/from_3b1b/active/diffyq/part1/pi_scenes.py +++ /dev/null @@ -1,515 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part1.shared_constructs import * - - -class SomeOfYouWatching(TeacherStudentsScene): - CONFIG = { - "camera_config": { - "background_color": DARKER_GREY, - } - } - - def construct(self): - screen = self.screen - screen.scale(1.25, about_edge=UL) - screen.set_fill(BLACK, 1) - self.add(screen) - - self.teacher.change("raise_right_hand") - for student in self.students: - student.change("pondering", screen) - - self.student_says( - "Well...yeah", - target_mode="tease" - ) - self.wait(3) - - -class FormulasAreLies(PiCreatureScene): - def construct(self): - you = self.pi_creature - t2c = { - "{L}": BLUE, - "{g}": YELLOW, - "\\theta_0": WHITE, - "\\sqrt{\\,": WHITE, - } - kwargs = {"tex_to_color_map": t2c} - period_eq = TexMobject( - "\\text{Period} = 2\\pi \\sqrt{\\,{L} / {g}}", - **kwargs - ) - theta_eq = TexMobject( - "\\theta(t) = \\theta_0 \\cos\\left(" - "\\sqrt{\\,{L} / {g}} \\cdot t" - "\\right)", - **kwargs - ) - equations = VGroup(theta_eq, period_eq) - equations.arrange(DOWN, buff=LARGE_BUFF) - - for eq in period_eq, theta_eq: - i = eq.index_of_part_by_tex("\\sqrt") - eq.sqrt_part = eq[i:i + 4] - - theta0 = theta_eq.get_part_by_tex("\\theta_0") - theta0_words = TextMobject("Starting angle") - theta0_words.next_to(theta0, UL) - theta0_words.shift(UP + 0.5 * RIGHT) - arrow = Arrow( - theta0_words.get_bottom(), - theta0, - color=WHITE, - tip_length=0.25, - ) - - bubble = SpeechBubble() - bubble.pin_to(you) - bubble.write("Lies!") - bubble.content.scale(2) - bubble.resize_to_content() - - self.add(period_eq) - you.change("pondering", period_eq) - self.wait() - theta_eq.remove(*theta_eq.sqrt_part) - self.play( - TransformFromCopy( - period_eq.sqrt_part, - theta_eq.sqrt_part, - ), - FadeIn(theta_eq) - ) - theta_eq.add(*theta_eq.sqrt_part) - self.play( - FadeInFrom(theta0_words, LEFT), - GrowArrow(arrow), - ) - self.wait() - self.play(you.change, "confused") - self.wait() - self.play( - you.change, "angry", - ShowCreation(bubble), - FadeInFromPoint(bubble.content, you.mouth), - equations.to_edge, LEFT, - FadeOut(arrow), - FadeOut(theta0_words), - ) - self.wait() - - def create_pi_creature(self): - return You().flip().to_corner(DR) - - -# class TourOfDifferentialEquations(Scene): -# def construct(self): -# pass - - -class SoWhatIsThetaThen(TeacherStudentsScene): - def construct(self): - ode = get_ode() - ode.to_corner(UL) - self.add(ode) - - self.student_says( - "Okay, but then\\\\" - "what \\emph{is} $\\theta(t)$?" - ) - self.wait() - self.play(self.teacher.change, "happy") - self.wait(2) - self.teacher_says( - "First, you must appreciate\\\\" - "a deep truth...", - added_anims=[self.get_student_changes( - *3 * ["confused"] - )] - ) - self.wait(4) - - -class ProveTeacherWrong(TeacherStudentsScene): - def construct(self): - tex_config = { - "tex_to_color_map": { - "{\\theta}": BLUE, - "{\\dot\\theta}": YELLOW, - "{\\ddot\\theta}": RED, - } - } - func = TexMobject( - "{\\theta}(t)", "=", - "\\theta_0", "\\cos(\\sqrt{g / L} \\cdot t)", - **tex_config, - ) - d_func = TexMobject( - "{\\dot\\theta}(t)", "=", - "-\\left(\\sqrt{g / L}\\right)", - "\\theta_0", "\\sin(\\sqrt{g / L} \\cdot t)", - **tex_config, - ) - dd_func = TexMobject( - "{\\ddot\\theta}(t)", "=", - "-\\left(g / L\\right)", - "\\theta_0", "\\cos(\\sqrt{g / L} \\cdot t)", - **tex_config, - ) - # ode = TexMobject( - # "\\ddot {\\theta}({t})", "=", - # "-\\mu \\dot {\\theta}({t})", - # "-{g \\over L} \\sin\\big({\\theta}({t})\\big)", - # **tex_config, - # ) - ode = get_ode() - arrows = [TexMobject("\\Downarrow") for x in range(2)] - - VGroup(func, d_func, dd_func, ode, *arrows).scale(0.7) - - teacher = self.teacher - you = self.students[2] - - self.student_thinks(ode) - you.add_updater(lambda m: m.look_at(func)) - self.teacher_holds_up(func) - self.wait() - - group = VGroup(arrows[0], d_func, arrows[1], dd_func) - group.arrange(DOWN) - group.move_to(func, DOWN) - - arrow = Arrow( - group.get_corner(UL), - ode.get_top(), - path_arc=PI / 2, - ) - q_marks = VGroup(*[ - TexMobject("?").scale(1.5).next_to( - arrow.point_from_proportion(a), - UP - ) - for a in np.linspace(0.2, 0.8, 5) - ]) - cycle_animation(VFadeInThenOut( - q_marks, - lag_ratio=0.2, - run_time=4, - rate_func=squish_rate_func(smooth, 0, 0.5) - )) - - self.play( - func.next_to, group, UP, - LaggedStartMap( - FadeInFrom, group, - lambda m: (m, UP) - ), - teacher.change, "guilty", - you.change, "sassy", - ) - - rect = SurroundingRectangle( - VGroup(group, func) - ) - dashed_rect = DashedVMobject(rect, num_dashes=75) - animated_rect = AnimatedBoundary(dashed_rect, cycle_rate=1) - - self.wait() - self.add(animated_rect, q_marks) - self.play( - ShowCreation(arrow), - # FadeInFromDown(q_mark), - self.get_student_changes("confused", "confused") - ) - self.wait(4) - self.change_student_modes( - *3 * ["pondering"], - self.teacher.change, "maybe" - ) - self.wait(8) - - -class PhysicistPhaseSpace(PiCreatureScene): - def construct(self): - physy = self.pi_creature - name = TextMobject("Physicist") - name.scale(1.5) - name.to_corner(DL, buff=MED_SMALL_BUFF) - physy.next_to(name, UP, SMALL_BUFF) - VGroup(name, physy).shift_onto_screen() - - axes = Axes( - x_min=-1, - x_max=10, - y_min=-1, - y_max=7, - ) - axes.set_height(6) - axes.next_to(physy, RIGHT) - axes.to_edge(UP) - axes.set_stroke(width=1) - x_label = TextMobject("Position") - x_label.next_to(axes.x_axis.get_right(), UP) - y_label = TextMobject("Momentum") - y_label.next_to(axes.y_axis.get_top(), RIGHT) - - title = TextMobject("Phase space") - title.scale(1.5) - title.set_color(YELLOW) - title.move_to(axes) - - self.add(name, physy) - - self.play( - physy.change, "angry", - Write(axes), - FadeInFromDown(title) - ) - self.wait(2) - self.play( - GrowFromPoint(x_label, physy.get_corner(UR)), - physy.change, "raise_right_hand", - axes.x_axis.get_right() - ) - self.play( - GrowFromPoint(y_label, physy.get_corner(UR)), - physy.look_at, axes.y_axis.get_top(), - ) - self.wait(3) - - def create_pi_creature(self): - return PiCreature(color=GREY).to_corner(DL) - - -class AskAboutActuallySolving(TeacherStudentsScene): - def construct(self): - ode = get_ode() - ode.to_corner(UL) - self.add(ode) - morty = self.teacher - - self.student_says( - "Yeah yeah, but how do\\\\" - "you actually \\emph{solve} it?", - student_index=1, - target_mode="sassy", - added_anims=[morty.change, "thinking"], - ) - self.change_student_modes( - "confused", "sassy", "confused", - look_at_arg=ode, - ) - self.wait() - self.teacher_says( - "What do you mean\\\\ by ``solve''?", - target_mode="speaking", - added_anims=[self.get_student_changes( - *3 * ["erm"] - )] - ) - self.play(self.students[1].change, "angry") - self.wait(3) - - -class HungerForExactness(TeacherStudentsScene): - def construct(self): - students = self.students - you = students[2] - teacher = self.teacher - - ode = get_ode() - ode.to_corner(UL) - left_part = ode[:5] - friction_part = ode[5:11] - self.add(ode) - - proposed_solution = TexMobject( - "\\theta_0\\cos((\\sqrt{g/L})t)e^{-\\mu t}" - ) - proposed_solution.next_to( - you.get_corner(UL), UP, buff=0.7 - ) - proposed_solution_rect = SurroundingRectangle( - proposed_solution, buff=MED_SMALL_BUFF, - ) - proposed_solution_rect.set_color(BLUE) - proposed_solution_rect.round_corners() - - solution_p1 = TexMobject( - """ - \\theta(t) = 2\\text{am}\\left( - \\frac{\\sqrt{2g + Lc_1} (t + c_2)}{2\\sqrt{L}}, - \\frac{4g}{2g + Lc_1} - \\right) - """, - ) - solution_p1.to_corner(UL) - solution_p2 = TexMobject( - "c_1, c_2 = \\text{Constants depending on initial conditions}" - ) - solution_p2.set_color(LIGHT_GREY) - solution_p2.scale(0.75) - solution_p3 = TexMobject( - """ - \\text{am}(u, k) = - \\int_0^u \\text{dn}(v, k)\\,dv - """ - ) - solution_p3.name = TextMobject( - "(Jacobi amplitude function)" - ) - solution_p4 = TexMobject( - """ - \\text{dn}(u, k) = - \\sqrt{1 - k^2 \\sin^2(\\phi)} - """ - ) - solution_p4.name = TextMobject( - "(Jacobi elliptic function)" - ) - solution_p5 = TextMobject("Where $\\phi$ satisfies") - solution_p6 = TexMobject( - """ - u = \\int_0^\\phi \\frac{dt}{\\sqrt{1 - k^2 \\sin^2(t)}} - """ - ) - - solution = VGroup( - solution_p1, - solution_p2, - solution_p3, - solution_p4, - solution_p5, - solution_p6, - ) - solution.arrange(DOWN) - solution.scale(0.7) - solution.to_corner(UL, buff=MED_SMALL_BUFF) - solution.set_stroke(width=0, background=True) - - solution.remove(solution_p2) - solution_p1.add(solution_p2) - solution.remove(solution_p5) - solution_p6.add(solution_p5) - - for part in [solution_p3, solution_p4]: - part.name.scale(0.7 * 0.7) - part.name.set_color(LIGHT_GREY) - part.name.next_to(part, RIGHT) - part.add(part.name) - - self.student_says( - "Right, but like,\\\\" - "what \\emph{is} $\\theta(t)$?", - target_mode="sassy", - added_anims=[teacher.change, "guilty"], - ) - self.wait() - self.play( - FadeInFromDown(proposed_solution), - RemovePiCreatureBubble( - you, - target_mode="raise_left_hand", - look_at_arg=proposed_solution, - ), - teacher.change, "pondering", - students[0].change, "pondering", - students[1].change, "hesitant", - ) - self.play(ShowCreation(proposed_solution_rect)) - self.play( - proposed_solution.shift, 3 * RIGHT, - proposed_solution_rect.shift, 3 * RIGHT, - you.change, "raise_right_hand", teacher.eyes, - ) - self.wait(3) - - self.play( - FadeOut(proposed_solution), - FadeOut(proposed_solution_rect), - ode.move_to, self.hold_up_spot, DOWN, - ode.shift, LEFT, - teacher.change, "raise_right_hand", - self.get_student_changes(*3 * ["pondering"]) - ) - self.wait() - ode.save_state() - self.play( - left_part.move_to, friction_part, RIGHT, - left_part.match_y, left_part, - friction_part.to_corner, DR, - friction_part.fade, 0.5, - ) - self.wait() - - modes = ["erm", "sad", "sad", "horrified"] - for part, mode in zip(solution, modes): - self.play( - FadeInFrom(part, UP), - self.get_student_changes( - *3 * [mode], - look_at_arg=part, - ) - ) - self.wait() - self.wait(3) - self.change_student_modes("tired", "sad", "concerned_musician") - self.wait(4) - self.look_at(solution) - self.wait(5) - self.play( - FadeOutAndShift(solution, 2 * LEFT), - Restore(ode), - self.get_student_changes( - "sick", "angry", "tired", - ) - ) - self.wait(3) - - mystery = TexMobject( - "\\theta(t) = ???", - tex_to_color_map={"\\theta": BLUE}, - ) - mystery.scale(2) - mystery.to_edge(UP) - mystery.set_stroke(width=0, background=True) - mystery_boundary = AnimatedBoundary( - mystery, stroke_width=1 - ) - - self.play( - FadeInFromDown(mystery), - self.teacher.change, "pondering" - ) - self.add(mystery_boundary, mystery) - self.change_all_student_modes("sad") - self.look_at(mystery) - self.wait(5) - - # Define - self.student_says( - "Let $\\text{P}(\\mu, g, L; t)$ be a\\\\" - "function satisfying this ODE.", - student_index=0, - target_mode="speaking", - added_anims=[ - FadeOut(mystery), - FadeOut(mystery_boundary), - ode.to_corner, UR - ] - ) - self.change_student_modes( - "hooray", "sassy", "sassy", - look_at_arg=students[0].eyes.get_corner(UR), - ) - self.wait(2) - - -class ItGetsWorse(TeacherStudentsScene): - def construct(self): - self.teacher_says("It gets\\\\worse") - self.change_student_modes( - "hesitant", "pleading", "erm" - ) - self.wait(5) diff --git a/from_3b1b/active/diffyq/part1/shared_constructs.py b/from_3b1b/active/diffyq/part1/shared_constructs.py deleted file mode 100644 index 1dcfddf6..00000000 --- a/from_3b1b/active/diffyq/part1/shared_constructs.py +++ /dev/null @@ -1,118 +0,0 @@ -from manimlib.imports import * - - -Lg_formula_config = { - "tex_to_color_map": { - "\\theta_0": WHITE, - "{L}": BLUE, - "{g}": YELLOW, - }, -} - - -class You(PiCreature): - CONFIG = { - "color": BLUE_C, - } - - -def get_ode(): - tex_config = { - "tex_to_color_map": { - "{\\theta}": BLUE, - "{\\dot\\theta}": RED, - "{\\ddot\\theta}": YELLOW, - "{t}": WHITE, - "{\\mu}": WHITE, - } - } - ode = TexMobject( - "{\\ddot\\theta}({t})", "=", - "-{\\mu} {\\dot\\theta}({t})", - "-{g \\over L} \\sin\\big({\\theta}({t})\\big)", - **tex_config, - ) - return ode - - -def get_period_formula(): - return TexMobject( - "2\\pi", "\\sqrt{\\,", "L", "/", "g", "}", - tex_to_color_map={ - "L": BLUE, - "g": YELLOW, - } - ) - - -def pendulum_vector_field_func(point, mu=0.1, g=9.8, L=3): - theta, omega = point[:2] - return np.array([ - omega, - -np.sqrt(g / L) * np.sin(theta) - mu * omega, - 0, - ]) - - -def get_vector_symbol(*texs, **kwargs): - config = { - "include_background_rectangle": True, - "bracket_h_buff": SMALL_BUFF, - "bracket_v_buff": SMALL_BUFF, - "element_alignment_corner": ORIGIN, - } - config.update(kwargs) - array = [[tex] for tex in texs] - return Matrix(array, **config) - - -def get_heart_var(index): - heart = SuitSymbol("hearts") - if index == 1: - heart.set_color(BLUE_C) - elif index == 2: - heart.set_color(GREEN) - heart.set_height(0.7) - index = Integer(index) - index.move_to(heart.get_corner(DR)) - heart.add(index) - return heart - - -def get_heart_var_deriv(index): - heart = get_heart_var(index) - filler_tex = "T" - deriv = TexMobject("{d", filler_tex, "\\over", "dt}") - deriv.scale(2) - filler = deriv.get_part_by_tex(filler_tex) - heart.match_height(filler) - heart.move_to(filler) - heart.scale(1.5, about_edge=UL) - deriv.remove(filler) - deriv.add(heart) - deriv.heart = heart - return deriv - - -def get_love_equation1(): - equation = VGroup( - get_heart_var_deriv(1), - TexMobject("=").scale(2), - TexMobject("a").scale(2), - get_heart_var(2) - ) - equation.arrange(RIGHT) - equation[-1].shift(SMALL_BUFF * DL) - return equation - - -def get_love_equation2(): - equation = VGroup( - get_heart_var_deriv(2), - TexMobject("=").scale(2), - TexMobject("-b").scale(2), - get_heart_var(1), - ) - equation.arrange(RIGHT) - equation[-1].shift(SMALL_BUFF * DL) - return equation diff --git a/from_3b1b/active/diffyq/part1/staging.py b/from_3b1b/active/diffyq/part1/staging.py deleted file mode 100644 index 6c824b2c..00000000 --- a/from_3b1b/active/diffyq/part1/staging.py +++ /dev/null @@ -1,3028 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part1.shared_constructs import * -from active_projects.diffyq.part1.pendulum import Pendulum -from active_projects.diffyq.part1.pendulum import ThetaVsTAxes -from active_projects.diffyq.part1.phase_space import IntroduceVectorField -from from_3b1b.old.div_curl import PhaseSpaceOfPopulationModel -from from_3b1b.old.div_curl import ShowTwoPopulations - - -# Scenes - - -class VectorFieldTest(Scene): - def construct(self): - plane = NumberPlane( - # axis_config={"unit_size": 2} - ) - mu_tracker = ValueTracker(1) - field = VectorField( - lambda p: pendulum_vector_field_func( - plane.point_to_coords(p), - mu=mu_tracker.get_value() - ), - delta_x=0.5, - delta_y=0.5, - max_magnitude=6, - opacity=0.5, - # length_func=lambda norm: norm, - ) - field.set_opacity(1) - - self.add(plane, field) - return - - stream_lines = StreamLines( - field.func, - delta_x=0.5, - delta_y=0.5, - ) - animated_stream_lines = AnimatedStreamLines( - stream_lines, - line_anim_class=ShowPassingFlashWithThinningStrokeWidth, - ) - - self.add(plane, field, animated_stream_lines) - self.wait(10) - - -class ShowRect(Scene): - CONFIG = { - "height": 1, - "width": 3, - } - - def construct(self): - rect = Rectangle( - height=self.height, - width=self.width, - ) - rect.set_stroke(YELLOW) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - - -class ShowSquare(ShowRect): - CONFIG = { - "height": 3, - "width": 3, - } - - -class WhenChangeIsEasier(Scene): - def construct(self): - pass - - -class AirResistanceBrace(Scene): - def construct(self): - brace = Brace(Line(ORIGIN, RIGHT), DOWN) - word = TextMobject("Air resistance") - word.next_to(brace, DOWN) - self.play(GrowFromCenter(brace), FadeInFrom(word, UP)) - self.wait() - - -class PeriodFormula(Scene): - def construct(self): - formula = get_period_formula() - formula.scale(2) - q_mark = TexMobject("?") - q_mark.scale(3) - q_mark.next_to(formula, RIGHT) - self.add(formula, q_mark) - - -class TourOfDifferentialEquations(MovingCameraScene): - CONFIG = { - "screen_rect_style": { - "stroke_width": 2, - "stroke_color": WHITE, - "fill_opacity": 1, - "fill_color": BLACK, - }, - "camera_config": {"background_color": DARKER_GREY}, - "zoomed_thumbnail_index": 0, - } - - def construct(self): - self.add_title() - self.show_thumbnails() - self.zoom_in_to_one_thumbnail() - # self.show_words() - - def add_title(self): - title = TextMobject( - "A Tourist's Guide \\\\to Differential\\\\Equations" - ) - title.scale(1.5) - title.to_corner(UR) - self.add(title) - - def show_thumbnails(self): - thumbnails = self.thumbnails = Group( - Group(ScreenRectangle(**self.screen_rect_style)), - Group(ScreenRectangle(**self.screen_rect_style)), - Group(ScreenRectangle(**self.screen_rect_style)), - Group(ScreenRectangle(**self.screen_rect_style)), - Group(ScreenRectangle(**self.screen_rect_style)), - ) - n = len(thumbnails) - thumbnails.set_height(1.5) - - line = self.line = CubicBezier( - [-5, 3, 0], - [3, 3, 0], - [-3, -3, 0], - [5, -3, 0], - ) - line.shift(MED_SMALL_BUFF * LEFT) - for thumbnail, a in zip(thumbnails, np.linspace(0, 1, n)): - thumbnail.move_to(line.point_from_proportion(a)) - dots = TexMobject("\\dots") - dots.next_to(thumbnails[-1], RIGHT) - - self.add_phase_space_preview(thumbnails[0]) - self.add_heat_preview(thumbnails[1]) - self.add_fourier_series(thumbnails[2]) - self.add_matrix_exponent(thumbnails[3]) - self.add_laplace_symbol(thumbnails[4]) - - self.play( - ShowCreation( - line, - rate_func=lambda t: np.clip(t * (n + 1) / n, 0, 1) - ), - LaggedStart(*[ - GrowFromCenter( - thumbnail, - rate_func=squish_rate_func( - smooth, - 0, 0.7, - ) - ) - for thumbnail in thumbnails - ], lag_ratio=1), - run_time=5 - ) - self.play(Write(dots)) - self.wait() - - self.thumbnails = thumbnails - - def zoom_in_to_one_thumbnail(self): - self.play( - self.camera_frame.replace, - self.thumbnails[self.zoomed_thumbnail_index], - run_time=3, - ) - self.wait() - - def show_words(self): - words = VGroup( - TextMobject("Generalize"), - TextMobject("Put in context"), - TextMobject("Modify"), - ) - # words.arrange(DOWN, aligned_edge=LEFT, buff=LARGE_BUFF) - words.scale(1.5) - words.to_corner(UR) - words.add_to_back(VectorizedPoint(words.get_center())) - words.add(VectorizedPoint(words.get_center())) - - diffEq = TextMobject("Differential\\\\equations") - diffEq.scale(1.5) - diffEq.to_corner(DL, buff=LARGE_BUFF) - - for word1, word2 in zip(words, words[1:]): - self.play( - FadeInFromDown(word2), - FadeOutAndShift(word1, UP), - ) - self.wait() - self.play( - ReplacementTransform( - VGroup(self.thumbnails).copy().fade(1), - diffEq, - lag_ratio=0.01, - ) - ) - self.wait() - - # - def add_phase_space_preview(self, thumbnail): - image = ImageMobject("LovePhaseSpace") - image.replace(thumbnail) - thumbnail.add(image) - - def add_heat_preview(self, thumbnail): - image = ImageMobject("HeatSurfaceExample") - image.replace(thumbnail) - thumbnail.add(image) - - def add_matrix_exponent(self, thumbnail): - matrix = IntegerMatrix( - [[3, 1], [4, 1]], - v_buff=MED_LARGE_BUFF, - h_buff=MED_LARGE_BUFF, - bracket_h_buff=SMALL_BUFF, - bracket_v_buff=SMALL_BUFF, - ) - e = TexMobject("e") - t = TexMobject("t") - t.scale(1.5) - t.next_to(matrix, RIGHT, SMALL_BUFF) - e.scale(2) - e.move_to(matrix.get_corner(DL), UR) - group = VGroup(e, matrix, t) - group.set_height(0.7 * thumbnail.get_height()) - randy = Randolph(mode="confused", height=0.75) - randy.next_to(group, LEFT, aligned_edge=DOWN) - randy.look_at(matrix) - group.add(randy) - group.move_to(thumbnail) - thumbnail.add(group) - - def add_fourier_series(self, thumbnail): - colors = [BLUE, GREEN, YELLOW, RED, RED_E, PINK] - - waves = VGroup(*[ - self.get_square_wave_approx(N, color) - for N, color in enumerate(colors) - ]) - waves.set_stroke(width=1.5) - waves.replace(thumbnail, stretch=True) - waves.scale(0.8) - waves.move_to(thumbnail) - thumbnail.add(waves) - - def get_square_wave_approx(self, N, color): - return FunctionGraph( - lambda x: sum([ - (1 / n) * np.sin(n * PI * x) - for n in range(1, 2 * N + 3, 2) - ]), - x_min=0, - x_max=2, - color=color - ) - - def add_laplace_symbol(self, thumbnail): - mob = TexMobject( - "\\mathcal{L}\\left\\{f(t)\\right\\}" - ) - mob.set_width(0.8 * thumbnail.get_width()) - mob.move_to(thumbnail) - thumbnail.add(mob) - - -class HeatEquationPreview(ExternallyAnimatedScene): - pass - - -class ShowHorizontalDashedLine(Scene): - def construct(self): - line = DashedLine(LEFT_SIDE, RIGHT_SIDE) - self.play(ShowCreation(line)) - self.wait() - - -class RabbitFoxPopulations(ShowTwoPopulations): - pass - - -class RabbitFoxEquation(PhaseSpaceOfPopulationModel): - def construct(self): - equations = self.get_equations() - self.add(equations) - - -class ShowGravityAcceleration(Scene): - CONFIG = { - "flash": True, - "add_ball_copies": True, - } - - def construct(self): - self.add_gravity_field() - self.add_title() - self.pulse_gravity_down() - self.show_g_value() - self.show_trajectory() - self.combine_v_vects() - self.show_g_symbol() - - def add_gravity_field(self): - gravity_field = self.gravity_field = VectorField( - lambda p: DOWN, - # delta_x=2, - # delta_y=2, - ) - gravity_field.set_opacity(0.5) - gravity_field.sort_submobjects( - lambda p: -p[1], - ) - self.add(gravity_field) - - def add_title(self): - title = self.title = TextMobject("Gravitational acceleration") - title.scale(1.5) - title.to_edge(UP) - title.add_background_rectangle( - buff=0.05, - opacity=1, - ) - self.play(FadeInFromDown(title)) - - def pulse_gravity_down(self): - field = self.gravity_field - self.play(LaggedStart(*[ - ApplyFunction( - lambda v: v.set_opacity(1).scale(1.2), - vector, - rate_func=there_and_back, - ) - for vector in field - ]), run_time=2, lag_ratio=0.001) - self.add(self.title) - - def show_g_value(self): - title = self.title - g_eq = self.g_eq = TexMobject( - "-9.8", "{\\text{m/s}", "\\over", "\\text{s}}", - **Lg_formula_config - ) - g_eq.add_background_rectangle_to_submobjects() - g_eq.scale(2) - g_eq.center() - num, ms, per, s = g_eq - - self.add(num) - self.wait(0.75) - self.play( - FadeInFrom(ms, 0.25 * DOWN, run_time=0.5) - ) - self.wait(0.25) - self.play(LaggedStart( - GrowFromPoint(per, per.get_left()), - FadeInFrom(s, 0.5 * UP), - lag_ratio=0.7, - run_time=0.75 - )) - self.wait() - self.play( - g_eq.scale, 0.5, - g_eq.next_to, title, DOWN, - ) - - def show_trajectory(self): - total_time = 6 - - ball = self.get_ball() - - p0 = 3 * DOWN + 5 * LEFT - v0 = 2.8 * UP + 1.5 * RIGHT - g = 0.9 * DOWN - graph = ParametricFunction( - lambda t: p0 + v0 * t + 0.5 * g * t**2, - t_min=0, - t_max=total_time, - ) - # graph.center().to_edge(DOWN) - dashed_graph = DashedVMobject(graph, num_dashes=60) - dashed_graph.set_stroke(WHITE, 1) - - ball.move_to(graph.get_start()) - randy.add_updater( - lambda m, dt: m.rotate(dt).move_to(ball) - ) - times = np.arange(0, total_time + 1) - - velocity_graph = ParametricFunction( - lambda t: v0 + g * t, - t_min=0, t_max=total_time, - ) - v_point = VectorizedPoint() - v_point.move_to(velocity_graph.get_start()) - - def get_v_vect(): - result = Vector( - v_point.get_location(), - color=RED, - tip_length=0.2, - ) - result.scale(0.5, about_point=result.get_start()) - result.shift(ball.get_center()) - result.set_stroke(width=2, family=False) - return result - v_vect = always_redraw(get_v_vect) - self.add(v_vect) - - flash_rect = FullScreenRectangle( - stroke_width=0, - fill_color=WHITE, - fill_opacity=0.2, - ) - flash = FadeOut( - flash_rect, - rate_func=squish_rate_func(smooth, 0, 0.1) - ) - - time_label = TextMobject("Time = ") - time_label.shift(MED_SMALL_BUFF * LEFT) - time_tracker = ValueTracker(0) - time = DecimalNumber(0) - time.next_to(time_label, RIGHT) - time.add_updater(lambda d, dt: d.set_value( - time_tracker.get_value() - )) - time_group = VGroup(time_label, time) - time_group.center().to_edge(DOWN) - self.add(time_group) - - ball_copies = VGroup() - v_vect_copies = VGroup() - self.add(dashed_graph, ball) - for t1, t2 in zip(times, times[1:]): - v_vect_copy = v_vect.copy() - v_vect_copies.add(v_vect_copy) - ball_copy = ball.copy() - ball_copy.clear_updaters() - ball_copies.add(ball_copy) - - if self.add_ball_copies: - self.add(v_vect_copy) - self.add(ball_copy, ball) - - dashed_graph.save_state() - kw = { - "rate_func": lambda alpha: interpolate( - t1 / total_time, - t2 / total_time, - alpha - ) - } - anims = [ - ShowCreation(dashed_graph, **kw), - MoveAlongPath(ball, graph, **kw), - MoveAlongPath(v_point, velocity_graph, **kw), - ApplyMethod( - time_tracker.increment_value, 1, - rate_func=linear - ), - ] - if self.flash: - anims.append(flash) - self.play(*anims, run_time=1) - dashed_graph.restore() - randy.clear_updaters() - self.play(FadeOut(time_group)) - self.wait() - - self.v_vects = v_vect_copies - - def combine_v_vects(self): - v_vects = self.v_vects.copy() - v_vects.generate_target() - new_center = 2 * DOWN + 2 * LEFT - for vect in v_vects.target: - vect.scale(1.5) - vect.set_stroke(width=2) - vect.shift(new_center - vect.get_start()) - - self.play(MoveToTarget(v_vects)) - - delta_vects = VGroup(*[ - Arrow( - v1.get_end(), - v2.get_end(), - buff=0.01, - color=YELLOW, - ).set_opacity(0.5) - for v1, v2 in zip(v_vects, v_vects[1:]) - ]) - brace = Brace(Line(ORIGIN, UP), RIGHT) - braces = VGroup(*[ - brace.copy().match_height(arrow).next_to( - arrow, RIGHT, buff=0.2 * SMALL_BUFF - ) - for arrow in delta_vects - ]) - amounts = VGroup(*[ - TextMobject("9.8 m/s").scale(0.5).next_to( - brace, RIGHT, SMALL_BUFF - ) - for brace in braces - ]) - - self.play( - FadeOut(self.gravity_field), - FadeIn(delta_vects, lag_ratio=0.1), - ) - self.play( - LaggedStartMap(GrowFromCenter, braces), - LaggedStartMap(FadeInFrom, amounts, lambda m: (m, LEFT)), - ) - self.wait() - - def show_g_symbol(self): - g = TexMobject("g") - brace = Brace(self.g_eq[0][2:], UP, buff=SMALL_BUFF) - g.scale(1.5) - g.next_to(brace, UP) - g.set_color(YELLOW) - self.play( - FadeOut(self.title), - GrowFromCenter(brace), - FadeInFrom(g, UP), - ) - self.wait() - - # - def get_ball(self): - ball = Circle( - stroke_width=1, - stroke_color=WHITE, - fill_color=GREY, - fill_opacity=1, - sheen_factor=1, - sheen_direction=UL, - radius=0.25, - ) - randy = Randolph(mode="pondering") - randy.eyes.set_stroke(BLACK, 0.5) - randy.match_height(ball) - randy.scale(0.75) - randy.move_to(ball) - ball.add(randy) - return ball - - -class ShowSimpleTrajectory(ShowGravityAcceleration): - CONFIG = { - "flash": False, - } - - def construct(self): - self.show_trajectory() - - -class SimpleProjectileEquation(ShowGravityAcceleration): - CONFIG = { - "y0": 0, - "g": 9.8, - "axes_config": { - "x_min": 0, - "x_max": 6, - "x_axis_config": { - "unit_size": 1.5, - "tip_width": 0.15, - }, - "y_min": -30, - "y_max": 35, - "y_axis_config": { - "unit_size": 0.1, - "numbers_with_elongated_ticks": range( - -30, 35, 10 - ), - "tick_size": 0.05, - "numbers_to_show": range(-30, 31, 10), - "tip_width": 0.15, - }, - "center_point": 2 * LEFT, - } - } - - def construct(self): - self.add_axes() - self.setup_trajectory() - - self.show_trajectory() - self.show_equation() - self.solve_for_velocity() - self.solve_for_position() - - def add_axes(self): - axes = self.axes = Axes(**self.axes_config) - axes.set_stroke(width=2) - axes.add_coordinates() - - t_label = TexMobject("t") - t_label.next_to(axes.x_axis.get_right(), UL) - axes.add(t_label) - - self.add(axes) - - def setup_trajectory(self): - axes = self.axes - total_time = self.total_time = 5 - - ball = self.get_ball() - offset_vector = 3 * LEFT - - g = self.g - y0 = self.y0 - v0 = 0.5 * g * total_time - - t_tracker = ValueTracker(0) - get_t = t_tracker.get_value - - # Position - def y_func(t): - return -0.5 * g * t**2 + v0 * t + y0 - - graph_template = axes.get_graph(y_func, x_max=total_time) - graph_template.set_stroke(width=2) - traj_template = graph_template.copy() - traj_template.stretch(0, 0) - traj_template.move_to( - axes.coords_to_point(0, 0), DOWN - ) - traj_template.shift(offset_vector) - traj_template.set_stroke(width=0.5) - - graph = VMobject() - graph.set_stroke(BLUE, 2) - traj = VMobject() - traj.set_stroke(WHITE, 0.5) - graph.add_updater(lambda g: g.pointwise_become_partial( - graph_template, 0, get_t() / total_time - )) - traj.add_updater(lambda t: t.pointwise_become_partial( - traj_template, 0, get_t() / total_time - )) - - def get_ball_point(): - return axes.coords_to_point( - 0, y_func(get_t()) - ) + offset_vector - - f_always(ball.move_to, get_ball_point) - - h_line = always_redraw(lambda: DashedLine( - get_ball_point(), - axes.input_to_graph_point(get_t(), graph_template), - stroke_width=1, - )) - - y_label = TexMobject("y", "(t)") - y_label.set_color_by_tex("y", BLUE) - y_label.add_updater( - lambda m: m.next_to( - graph.get_last_point(), - UR, SMALL_BUFF, - ) - ) - - # Velocity - def v_func(t): - return -g * t + v0 - - def get_v_vect(): - return Vector( - axes.y_axis.unit_size * v_func(get_t()) * UP, - color=RED, - ) - v_vect = always_redraw( - lambda: get_v_vect().shift(get_ball_point()) - ) - v_brace = always_redraw(lambda: Brace(v_vect, LEFT)) - dy_dt_label = TexMobject( - "{d", "y", "\\over dt}", "(t)", - ) - dy_dt_label.scale(0.8) - dy_dt_label.set_color_by_tex("y", BLUE) - y_dot_label = TexMobject("\\dot y", "(t)") - y_dot_label.set_color_by_tex("\\dot y", RED) - for label in dy_dt_label, y_dot_label: - label.add_updater(lambda m: m.next_to( - v_brace, LEFT, SMALL_BUFF, - )) - - graphed_v_vect = always_redraw( - lambda: get_v_vect().shift( - axes.coords_to_point(get_t(), 0) - ) - ) - v_graph_template = axes.get_graph( - v_func, x_max=total_time, - ) - v_graph = VMobject() - v_graph.set_stroke(RED, 2) - v_graph.add_updater(lambda m: m.pointwise_become_partial( - v_graph_template, - 0, get_t() / total_time, - )) - - # Acceleration - def get_a_vect(): - return Vector( - axes.y_axis.unit_size * g * DOWN - ) - - a_vect = get_a_vect() - a_vect.add_updater(lambda a: a.move_to( - get_ball_point(), UP, - )) - a_brace = Brace(a_vect, RIGHT) - always(a_brace.next_to, a_vect, RIGHT, SMALL_BUFF) - d2y_dt2_label = TexMobject( - "d^2", "{y}", "\\over dt}", "(t)" - ) - d2y_dt2_label.scale(0.8) - d2y_dt2_label.set_color_by_tex( - "y", BLUE, - ) - y_ddot_label = TexMobject("\\ddot y", "(t)") - y_ddot_label.set_color_by_tex("\\ddot y", YELLOW) - for label in d2y_dt2_label, y_ddot_label: - label.add_updater(lambda m: m.next_to( - a_brace, RIGHT, SMALL_BUFF - )) - a_graph = axes.get_graph( - lambda t: -g, x_max=total_time, - ) - a_graph.set_stroke(YELLOW, 2) - - graphed_a_vect = get_a_vect() - graphed_a_vect.add_updater(lambda a: a.move_to( - axes.coords_to_point(get_t(), 0), UP, - )) - - self.set_variables_as_attrs( - t_tracker, - graph, - y_label, - traj, - h_line, - v_vect, - v_brace, - dy_dt_label, - y_dot_label, - ball, - graphed_v_vect, - v_graph, - a_vect, - a_brace, - d2y_dt2_label, - y_ddot_label, - a_graph, - graphed_a_vect, - ) - - def show_trajectory(self): - self.add( - self.h_line, - self.traj, - self.ball, - self.graph, - self.y_label, - ) - self.play_trajectory() - self.wait() - - self.add( - self.v_vect, - self.v_brace, - self.dy_dt_label, - self.ball, - self.graphed_v_vect, - self.v_graph, - ) - self.play_trajectory() - self.wait() - - self.add( - self.a_vect, - self.ball, - self.a_brace, - self.d2y_dt2_label, - self.a_graph, - self.graphed_a_vect, - ) - self.play_trajectory() - self.wait() - - self.play( - ReplacementTransform( - self.dy_dt_label, - self.y_dot_label, - ), - ShowCreationThenFadeAround( - self.y_dot_label, - ), - ) - self.play( - ReplacementTransform( - self.d2y_dt2_label, - self.y_ddot_label, - ), - ShowCreationThenFadeAround( - self.y_ddot_label, - ), - ) - - def show_equation(self): - y_ddot = self.y_ddot_label - new_y_ddot = y_ddot.deepcopy() - new_y_ddot.clear_updaters() - - equation = VGroup( - new_y_ddot, - *TexMobject( - "=", "-g", - tex_to_color_map={"-g": YELLOW}, - ), - ) - new_y_ddot.next_to(equation[1], LEFT, SMALL_BUFF) - equation.move_to(self.axes) - equation.to_edge(UP) - - self.play( - TransformFromCopy(y_ddot, new_y_ddot), - Write(equation[1:]), - FadeOut(self.graph), - FadeOut(self.y_label), - FadeOut(self.h_line), - FadeOut(self.v_graph), - FadeOut(self.graphed_v_vect), - FadeOut(self.graphed_a_vect), - ) - - self.equation = equation - - def solve_for_velocity(self): - axes = self.axes - equation = self.equation - v_graph = self.v_graph.deepcopy() - v_graph.clear_updaters() - v_start_point = v_graph.get_start() - origin = axes.coords_to_point(0, 0) - offset = v_start_point - origin - v_graph.shift(-offset) - - tex_question, answer1, answer2 = derivs = [ - TexMobject( - "{d", "(", *term, ")", "\\over", "dt}", "(t)", - "=", "-g", - tex_to_color_map={ - "-g": YELLOW, - "v_0": RED, - "?": RED, - } - ) - for term in [ - ("?", "?", "?", "?"), - ("-g", "t"), - ("-g", "t", "+", "v_0",), - ] - ] - for x in range(2): - answer1.submobjects.insert( - 4, VectorizedPoint(answer1[4].get_left()) - ) - for deriv in derivs: - deriv.next_to(equation, DOWN, MED_LARGE_BUFF) - - question = TextMobject( - "What function has slope $-g$?", - tex_to_color_map={"$-g$": YELLOW}, - ) - question.next_to(tex_question, DOWN) - question.set_stroke(BLACK, 5, background=True) - question.add_background_rectangle() - - v0_dot = Dot(v_start_point, color=PINK) - v0_label = TexMobject("v_0") - v0_label.set_color(RED) - v0_label.next_to(v0_dot, UR, buff=0) - - y_dot_equation = TexMobject( - "{\\dot y}", "(t)", "=", - "-g", "t", "+", "v_0", - tex_to_color_map={ - "{\\dot y}": RED, - "-g": YELLOW, - "v_0": RED, - } - ) - y_dot_equation.to_corner(UR) - - self.play( - FadeInFrom(tex_question, DOWN), - FadeInFrom(question, UP) - ) - self.wait() - self.add(v_graph, question) - self.play( - ReplacementTransform(tex_question, answer1), - ShowCreation(v_graph), - ) - self.wait() - self.play( - ReplacementTransform(answer1, answer2), - v_graph.shift, offset, - ) - self.play( - FadeInFromLarge(v0_dot), - FadeInFromDown(v0_label), - ) - self.wait() - self.play( - TransformFromCopy( - answer2[2:6], y_dot_equation[3:], - ), - Write(y_dot_equation[:3]), - equation.shift, LEFT, - ) - self.play( - FadeOut(question), - FadeOut(answer2), - ) - - self.remove(v_graph) - self.add(self.v_graph) - self.y_dot_equation = y_dot_equation - - def solve_for_position(self): - # Largely copied from above...not great - equation = self.equation - y_dot_equation = self.y_dot_equation - graph = self.graph - - all_terms = [ - ("?", "?", "?", "?"), - ("-", "(1/2)", "g", "t^2", "+", "v_0", "t"), - ("-", "(1/2)", "g", "t^2", "+", "v_0", "t", "+", "y_0"), - ] - tex_question, answer1, answer2 = derivs = [ - TexMobject( - "{d", "(", *term, ")", "\\over", "dt}", "(t)", - "=", - "-g", "t", "+", "v_0", - tex_to_color_map={ - "g": YELLOW, - "v_0": RED, - "?": BLUE, - "y_0": BLUE, - } - ) - for term in all_terms - ] - answer1.scale(0.8) - answer2.scale(0.8) - for deriv, terms in zip(derivs, all_terms): - for x in range(len(all_terms[-1]) - len(terms)): - n = 2 + len(terms) - deriv.submobjects.insert( - n, VectorizedPoint(deriv[n].get_left()) - ) - deriv.next_to( - VGroup(equation, y_dot_equation), - DOWN, MED_LARGE_BUFF + SMALL_BUFF - ) - deriv.shift_onto_screen() - deriv.add_background_rectangle_to_submobjects() - - y_equation = TexMobject( - "y", "(t)", "=", - "-", "(1/2)", "g", "t^2", - "+", "v_0", "t", - "+", "y_0", - tex_to_color_map={ - "y": BLUE, - "g": YELLOW, - "v_0": RED, - } - ) - y_equation.next_to( - VGroup(equation, y_dot_equation), - DOWN, MED_LARGE_BUFF, - ) - - self.play( - FadeInFrom(tex_question, DOWN), - ) - self.wait() - self.add(graph, tex_question) - self.play( - ReplacementTransform(tex_question, answer1), - ShowCreation(graph), - ) - self.add(graph, answer1) - self.wait() - self.play(ReplacementTransform(answer1, answer2)) - self.add(graph, answer2) - g_updaters = graph.updaters - graph.clear_updaters() - self.play( - graph.shift, 2 * DOWN, - rate_func=there_and_back, - run_time=2, - ) - graph.add_updater(g_updaters[0]) - self.wait() - br = BackgroundRectangle(y_equation) - self.play( - FadeIn(br), - ReplacementTransform( - answer2[2:11], - y_equation[3:] - ), - FadeIn(y_equation[:3]), - FadeOut(answer2[:2]), - FadeOut(answer2[11:]), - ) - self.play(ShowCreationThenFadeAround(y_equation)) - self.play_trajectory() - - # - def play_trajectory(self, *added_anims, **kwargs): - self.t_tracker.set_value(0) - self.play( - ApplyMethod( - self.t_tracker.set_value, 5, - rate_func=linear, - run_time=self.total_time, - ), - *added_anims, - ) - self.wait() - - -class SimpleProjectileEquationVGraphFreedom(SimpleProjectileEquation): - def construct(self): - self.add_axes() - self.setup_trajectory() - self.clear() - v_graph = self.v_graph - self.t_tracker.set_value(5) - v_graph.update() - v_graph.clear_updaters() - self.add(v_graph) - self.play(v_graph.shift, 5 * DOWN, run_time=2) - self.play(v_graph.shift, 5 * UP, run_time=2) - - -class UniversalGravityLawSymbols(Scene): - def construct(self): - x1_tex = "\\vec{\\textbf{x}}_1" - x2_tex = "\\vec{\\textbf{x}}_2" - a1_tex = "\\vec{\\textbf{a}}_1" - new_brown = interpolate_color(LIGHT_GREY, LIGHT_BROWN, 0.5) - law = TexMobject( - "F_1", "=", "m_1", a1_tex, "=", - "G", "m_1", "m_2", - "\\left({", x2_tex, "-", x1_tex, "\\over", - "||", x2_tex, "-", x1_tex, "||", "}\\right)", - "\\left({", "1", "\\over", - "||", x2_tex, "-", x1_tex, "||^2", "}\\right)", - tex_to_color_map={ - x1_tex: BLUE_C, - "m_1": BLUE_C, - x2_tex: new_brown, - "m_2": new_brown, - a1_tex: YELLOW, - } - ) - law.to_edge(UP) - - force = law[:4] - constants = law[4:8] - unit_vect = law[8:19] - inverse_square = law[19:] - parts = VGroup( - force, unit_vect, inverse_square - ) - - words = VGroup( - TextMobject("Force on\\\\mass 1"), - TextMobject("Unit vector\\\\towards mass 2"), - TextMobject("Inverse square\\\\law"), - ) - - self.add(law) - - braces = VGroup() - rects = VGroup() - for part, word in zip(parts, words): - brace = Brace(part, DOWN) - word.scale(0.8) - word.next_to(brace, DOWN) - rect = SurroundingRectangle(part) - rect.set_stroke(YELLOW, 1) - braces.add(brace) - rects.add(rect) - - self.play( - ShowCreationThenFadeOut(rects[0]), - GrowFromCenter(braces[0]), - FadeInFrom(words[0], UP) - ) - self.wait() - self.play( - ShowCreationThenFadeOut(rects[1]), - GrowFromCenter(braces[1]), - FadeInFrom(words[1], UP) - ) - self.wait() - self.play( - ShowCreationThenFadeOut(rects[2]), - TransformFromCopy(*braces[1:3]), - FadeInFrom(words[2], UP), - ) - self.wait() - - # Position derivative - v1_tex = "\\vec{\\textbf{v}}_1" - kw = { - "tex_to_color_map": { - x1_tex: BLUE_C, - v1_tex: RED, - } - } - x_deriv = TexMobject( - "{d", x1_tex, "\\over", "dt}", "=", v1_tex, **kw - ) - x_deriv.to_corner(UL) - v_deriv = TexMobject( - "{d", v1_tex, "\\over", "dt}", "=", **kw - ) - - # Make way - law.generate_target() - lt = law.target - lt.to_edge(RIGHT) - lt[6].fade(1) - lt[:6].align_to(lt[6], RIGHT) - lt[:3].fade(1) - v_deriv.next_to(lt[3], LEFT) - - self.play( - FadeInFromDown(x_deriv), - MoveToTarget(law), - braces[1:].align_to, lt, RIGHT, - MaintainPositionRelativeTo(words[1:], braces[1:]), - FadeOut(words[0]), - FadeOut(braces[0]), - ) - self.play(ShowCreationThenFadeAround(x_deriv)) - - self.play( - TransformFromCopy( - x_deriv.get_part_by_tex(v1_tex), - v_deriv.get_part_by_tex(v1_tex), - ), - Write(VGroup(*filter( - lambda m: m is not v_deriv.get_part_by_tex(v1_tex), - v_deriv, - ))) - ) - - x_parts = law.get_parts_by_tex(x1_tex) - self.play( - TransformFromCopy( - x_deriv.get_parts_by_tex(x1_tex), - x_parts.copy(), - remover=True, - path_arc=30 * DEGREES, - ) - ) - self.play( - LaggedStartMap( - ShowCreationThenFadeAround, - x_parts - ) - ) - self.wait() - - -class ExampleTypicalODE(TeacherStudentsScene): - def construct(self): - examples = VGroup( - TexMobject( - "{\\dot x}(t) = k{x}(t)", - tex_to_color_map={ - "{\\dot x}": BLUE, - "{x}": BLUE, - }, - ), - get_ode(), - TexMobject( - "{\\partial T", "\\over", "\\partial t} = ", - "{\\partial^2 T", "\\over", "\\partial x^2}", "+", - "{\\partial^2 T", "\\over", "\\partial y^2}", "+", - "{\\partial^2 T", "\\over", "\\partial z^2}", - tex_to_color_map={ - "T": RED, - } - ), - ) - examples[1].get_parts_by_tex("theta").set_color(GREEN) - examples.arrange(DOWN, buff=MED_LARGE_BUFF) - examples.to_edge(UP) - - self.play( - FadeInFrom(examples[0], UP), - self.teacher.change, "raise_right_hand", - ) - self.play( - FadeInFrom(examples[1], UP), - self.get_student_changes( - *3 * ["pondering"], - look_at_arg=examples, - ), - ) - self.play( - FadeInFrom(examples[2], UP) - ) - self.wait(5) - - -class ShowDerivativeVideo(Scene): - def construct(self): - title = TextMobject("Essence of", "Calculus") - title.scale(1.5) - title.to_edge(UP) - - title2 = TextMobject("Essence of", "Linear Algebra") - title2.scale(1.5) - title2.move_to(title, DOWN) - - rect = ScreenRectangle(height=6) - rect = rect.copy() - rect.set_style( - fill_opacity=1, - fill_color=BLACK, - stroke_width=0, - ) - rect.next_to(title, DOWN) - animated_rect = AnimatedBoundary(rect) - - self.add(title, rect) - self.add(animated_rect) - self.wait(5) - self.play(ReplacementTransform(title, title2)) - self.wait(10) - - -class SubtleAirCurrents(Scene): - def construct(self): - pass - - -class DefineODE(Scene): - CONFIG = { - "pendulum_config": { - "length": 2, - "top_point": 5 * RIGHT + 2 * UP, - "initial_theta": 150 * DEGREES, - "mu": 0.3, - }, - "axes_config": { - "y_axis_config": {"unit_size": 0.75}, - "y_max": PI, - "y_min": -PI, - "x_max": 10, - "x_axis_config": { - "numbers_to_show": range(2, 10, 2), - "unit_size": 1, - } - }, - } - - def construct(self): - self.add_graph() - self.write_differential_equation() - self.dont_know_the_value() - self.show_value_slope_curvature() - self.write_ode() - self.show_second_order() - self.show_higher_order_examples() - self.show_changing_curvature_group() - - def add_graph(self): - pendulum = Pendulum(**self.pendulum_config) - axes = ThetaVsTAxes(**self.axes_config) - - axes.center() - axes.to_corner(DL) - graph = axes.get_live_drawn_graph(pendulum) - - pendulum.start_swinging() - self.add(axes, pendulum, graph) - - self.pendulum = pendulum - self.axes = axes - self.graph = graph - - def write_differential_equation(self): - de_word = TextMobject("Differential", "Equation") - de_word.to_edge(UP, buff=MED_SMALL_BUFF) - - equation = get_ode() - equation.next_to(de_word, DOWN) - thetas = equation.get_parts_by_tex("\\theta") - - lines = VGroup(*[ - Line(v, 1.2 * v) - for v in compass_directions(25) - ]) - lines.replace(equation, stretch=True) - lines.scale(1.5) - lines.set_stroke(YELLOW) - lines.shuffle() - - self.add(equation) - self.wait(5) - self.play( - ShowPassingFlashWithThinningStrokeWidth( - lines, - lag_ratio=0.002, - run_time=1.5, - time_width=0.9, - n_segments=5, - ) - ) - self.play(FadeInFromDown(de_word)) - self.wait(2) - self.play( - LaggedStartMap( - ApplyMethod, thetas, - lambda m: (m.shift, 0.25 * DOWN), - rate_func=there_and_back, - ) - ) - self.wait() - - self.de_word = de_word - self.equation = equation - - def dont_know_the_value(self): - graph = self.graph - pendulum = self.pendulum - - q_marks = VGroup(*[ - TexMobject("?").move_to(graph.point_from_proportion(a)) - for a in np.linspace(0, 1, 20) - ]) - q_marks.set_stroke(width=0, background=True) - self.play( - VFadeOut(graph), - FadeOut(pendulum), - LaggedStart(*[ - UpdateFromAlphaFunc( - q_mark, - lambda m, a: m.set_height(0.5 * (1 + a)).set_fill( - opacity=there_and_back(a) - ), - ) - for q_mark in q_marks - ], lag_ratio=0.01, run_time=2) - ) - self.remove(q_marks) - - def show_value_slope_curvature(self): - axes = self.axes - p = self.pendulum - graph = axes.get_graph( - lambda t: p.initial_theta * np.cos( - np.sqrt(p.gravity / p.length) * t - ) * np.exp(-p.mu * t / 2) - ) - - tex_config = { - "tex_to_color_map": { - "{\\theta}": BLUE, - "{\\dot\\theta}": RED, - "{\\ddot\\theta}": YELLOW, - }, - "height": 0.5, - } - theta, d_theta, dd_theta = [ - TexMobject( - "{" + s + "\\theta}(t)", - **tex_config - ) - for s in ("", "\\dot", "\\ddot") - ] - - t_tracker = ValueTracker(2.5) - get_t = t_tracker.get_value - - def get_point(t): - return graph.point_from_proportion(t / axes.x_max) - - def get_dot(): - return Dot(get_point(get_t())).scale(0.5) - - def get_v_line(): - point = get_point(get_t()) - x_point = axes.x_axis.number_to_point( - axes.x_axis.point_to_number(point) - ) - return DashedLine( - x_point, point, - dash_length=0.025, - stroke_color=BLUE, - stroke_width=2, - ) - - def get_tangent_line(curve, alpha): - line = Line( - ORIGIN, 1.5 * RIGHT, - color=RED, - stroke_width=1.5, - ) - da = 0.0001 - p0 = curve.point_from_proportion(alpha) - p1 = curve.point_from_proportion(alpha - da) - p2 = curve.point_from_proportion(alpha + da) - angle = angle_of_vector(p2 - p1) - line.rotate(angle) - line.move_to(p0) - return line - - def get_slope_line(): - return get_tangent_line( - graph, get_t() / axes.x_max - ) - - def get_curve(): - curve = VMobject() - t = get_t() - curve.set_points_smoothly([ - get_point(t + a) - for a in np.linspace(-0.5, 0.5, 11) - ]) - curve.set_stroke(YELLOW, 1) - return curve - - v_line = always_redraw(get_v_line) - dot = always_redraw(get_dot) - slope_line = always_redraw(get_slope_line) - curve = always_redraw(get_curve) - - theta.next_to(v_line, RIGHT, SMALL_BUFF) - d_theta.next_to(slope_line.get_end(), UP, SMALL_BUFF) - dd_theta.next_to(curve.get_end(), RIGHT, SMALL_BUFF) - thetas = VGroup(theta, d_theta, dd_theta) - - words = VGroup( - TextMobject("= Height").set_color(BLUE), - TextMobject("= Slope").set_color(RED), - TextMobject("= ``Curvature''").set_color(YELLOW), - ) - words.scale(0.75) - for word, sym in zip(words, thetas): - word.next_to(sym, RIGHT, buff=2 * SMALL_BUFF) - sym.word = word - - self.play( - ShowCreation(v_line), - FadeInFromPoint(dot, v_line.get_start()), - FadeInFrom(theta, DOWN), - FadeInFrom(theta.word, DOWN), - ) - self.add(slope_line, dot) - self.play( - ShowCreation(slope_line), - FadeInFrom(d_theta, LEFT), - FadeInFrom(d_theta.word, LEFT), - ) - - a_tracker = ValueTracker(0) - curve_copy = curve.copy() - changing_slope = always_redraw( - lambda: get_tangent_line( - curve_copy, - a_tracker.get_value(), - ).set_stroke( - opacity=there_and_back(a_tracker.get_value()) - ) - ) - self.add(curve, dot) - self.play( - ShowCreation(curve), - FadeInFrom(dd_theta, LEFT), - FadeInFrom(dd_theta.word, LEFT), - ) - self.add(changing_slope) - self.play( - a_tracker.set_value, 1, - run_time=3, - ) - self.remove(changing_slope, a_tracker) - - self.t_tracker = t_tracker - self.curvature_group = VGroup( - v_line, slope_line, curve, dot - ) - self.curvature_group_labels = VGroup(thetas, words) - self.fake_graph = graph - - def write_ode(self): - equation = self.equation - axes = self.axes - de_word = self.de_word - - ts = equation.get_parts_by_tex("{t}") - t_rects = VGroup(*map(SurroundingRectangle, ts)) # Rawr - x_axis = axes.x_axis - x_axis_line = Line( - x_axis.get_start(), x_axis.get_end(), - stroke_color=YELLOW, - stroke_width=5, - ) - - ordinary = TextMobject("Ordinary") - de_word.generate_target() - group = VGroup(ordinary, de_word.target) - group.arrange(RIGHT) - group.to_edge(UP) - ordinary_underline = Line(LEFT, RIGHT) - ordinary_underline.replace(ordinary, dim_to_match=0) - ordinary_underline.next_to(ordinary, DOWN, SMALL_BUFF) - ordinary_underline.set_color(YELLOW) - - self.play( - ShowCreationThenFadeOut( - t_rects, - lag_ratio=0.8 - ), - ShowCreationThenFadeOut(x_axis_line) - ) - self.play( - MoveToTarget(de_word), - FadeInFrom(ordinary, RIGHT), - GrowFromCenter(ordinary_underline) - ) - self.play(FadeOut(ordinary_underline)) - self.wait() - - self.remove(ordinary, de_word) - ode_word = self.ode_word = VGroup(*ordinary, *de_word) - ode_initials = VGroup(*[word[0] for word in ode_word]) - ode_initials.generate_target() - ode_initials.target.scale(1.2) - ode_initials.target.set_color(PINK) - ode_initials.target.arrange( - RIGHT, buff=0.5 * SMALL_BUFF, aligned_edge=DOWN - ) - ode_initials.target.to_edge(UP, buff=MED_SMALL_BUFF) - - ode_remaining_letters = VGroup(*it.chain(*[ - word[1:] for word in ode_word - ])) - ode_remaining_letters.generate_target() - for mob in ode_remaining_letters.target: - mob.shift(0.2 * UP) - mob.fade(1) - - self.play( - MoveToTarget(ode_initials), - MoveToTarget(ode_remaining_letters, lag_ratio=0.05), - ) - self.wait() - - self.ode_initials = ode_initials - - def show_second_order(self): - so = TextMobject("Second order") - so.scale(1.4) - ode = self.ode_initials - ode.generate_target() - group = VGroup(so, ode.target) - group.arrange(RIGHT, aligned_edge=DOWN) - group.to_edge(UP, buff=MED_SMALL_BUFF) - - second_deriv = self.equation[:5] - - self.play( - Write(so), - MoveToTarget(ode), - ) - self.wait() - self.play(FocusOn(second_deriv)) - self.play( - Indicate(second_deriv, color=YELLOW), - ) - self.wait() - - self.second_order_word = so - - def show_higher_order_examples(self): - main_example = self.get_main_example() - tex_config = {"tex_to_color_map": {"{x}": BLUE}} - example3 = VGroup( - TextMobject("Third order ODE"), - TexMobject( - "\\dddot {x}(t) + \\dot {x}(t)^2 = 0", - **tex_config, - ) - ) - example4 = VGroup( - TextMobject("Fourth order ODE"), - TexMobject( - "\\ddddot {x}(t) +", - "a\\dddot {x}(t) \\dot {x}(t) + ", - "b \\ddot {x}(t) {x}(t)", - "= 1", - **tex_config, - ) - ) - for example in [example3, example4]: - example[0].scale(1.2) - example.arrange(DOWN, buff=MED_LARGE_BUFF) - example.to_edge(UP, buff=MED_SMALL_BUFF) - - self.play( - FadeOut(main_example), - FadeIn(example3), - ) - self.wait(2) - self.play( - FadeOut(example3), - FadeIn(example4), - ) - self.wait(2) - self.play( - FadeOut(example4), - FadeIn(main_example), - ) - self.wait(2) - - def get_main_example(self): - return VGroup( - self.second_order_word, - self.ode_initials, - self.equation - ) - - def show_changing_curvature_group(self): - t_tracker = self.t_tracker - curvature_group = self.curvature_group - labels = self.curvature_group_labels - graph = VMobject() - graph.pointwise_become_partial( - self.fake_graph, - t_tracker.get_value() / self.axes.x_max, - 1, - ) - dashed_graph = DashedVMobject(graph, num_dashes=100) - dashed_graph.set_stroke(GREEN, 1) - - self.play(FadeOut(labels)) - self.add(dashed_graph, curvature_group) - self.play( - t_tracker.set_value, 10, - ShowCreation(dashed_graph), - run_time=15, - rate_func=linear, - ) - self.wait() - - -# Largely a copy of DefineODE, which is not great. -# But what can you do? -class SecondOrderEquationExample(DefineODE): - def construct(self): - self.add_graph() - self.write_differential_equation() - self.show_value_slope_curvature() - self.show_higher_order_examples() - self.show_changing_curvature_group() - - def add_graph(self): - axes = self.axes = Axes( - x_min=0, - x_max=10.5, - y_min=-2.5, - y_max=2.5, - ) - axes.center() - axes.to_edge(DOWN) - x_t = TexMobject("x", "(t)") - x_t.set_color_by_tex("x", BLUE) - t = TexMobject("t") - t.next_to(axes.x_axis.get_right(), UP) - x_t.next_to(axes.y_axis.get_top(), UP) - - axes.add(t, x_t) - axes.add_coordinates() - - self.add(axes) - - def write_differential_equation(self): - de_word = TextMobject("Differential", "Equation") - de_word.scale(1.25) - de_word.to_edge(UP, buff=MED_SMALL_BUFF) - so_word = TextMobject("Second Order") - so_word.scale(1.25) - de_word.generate_target() - group = VGroup(so_word, de_word.target) - group.arrange(RIGHT) - group.to_edge(UP, buff=MED_SMALL_BUFF) - so_word.align_to(de_word.target[0], DOWN) - so_line = Line(LEFT, RIGHT, color=YELLOW) - so_line.match_width(so_word) - so_line.next_to(so_word, DOWN, buff=SMALL_BUFF) - - equation = TexMobject( - "{\\ddot x}(t)", "=", - "-\\mu", "{\\dot x}(t)", - "-", "\\omega", "{x}(t)", - tex_to_color_map={ - "{x}": BLUE, - "{\\dot x}": RED, - "{\\ddot x}": YELLOW, - } - ) - equation.next_to(de_word, DOWN) - - dd_x_part = equation[:2] - dd_x_rect = SurroundingRectangle(dd_x_part) - - self.add(de_word, equation) - self.play( - MoveToTarget(de_word), - FadeInFrom(so_word, RIGHT), - GrowFromCenter(so_line), - ) - self.play(ReplacementTransform(so_line, dd_x_rect)) - self.play(FadeOut(dd_x_rect)) - - self.equation = equation - self.title = VGroup(*so_word, *de_word) - - def show_value_slope_curvature(self): - axes = self.axes - graph = axes.get_graph( - lambda t: -2.5 * np.cos(2 * t) * np.exp(-0.2 * t) - ) - - tex_config = { - "tex_to_color_map": { - "{x}": BLUE, - "{\\dot x}": RED, - "{\\ddot x}": YELLOW, - }, - "height": 0.5, - } - x, d_x, dd_x = [ - TexMobject( - "{" + s + "x}(t)", - **tex_config - ) - for s in ("", "\\dot ", "\\ddot ") - ] - - t_tracker = ValueTracker(1.25) - get_t = t_tracker.get_value - - def get_point(t): - return graph.point_from_proportion(t / axes.x_max) - - def get_dot(): - return Dot(get_point(get_t())).scale(0.5) - - def get_v_line(): - point = get_point(get_t()) - x_point = axes.x_axis.number_to_point( - axes.x_axis.point_to_number(point) - ) - return DashedLine( - x_point, point, - dash_length=0.025, - stroke_color=BLUE, - stroke_width=2, - ) - - def get_tangent_line(curve, alpha): - line = Line( - ORIGIN, 1.5 * RIGHT, - color=RED, - stroke_width=1.5, - ) - da = 0.0001 - p0 = curve.point_from_proportion(alpha) - p1 = curve.point_from_proportion(alpha - da) - p2 = curve.point_from_proportion(alpha + da) - angle = angle_of_vector(p2 - p1) - line.rotate(angle) - line.move_to(p0) - return line - - def get_slope_line(): - return get_tangent_line( - graph, get_t() / axes.x_max - ) - - def get_curve(): - curve = VMobject() - t = get_t() - curve.set_points_smoothly([ - get_point(t + a) - for a in np.linspace(-0.5, 0.5, 11) - ]) - curve.set_stroke(YELLOW, 1) - return curve - - v_line = always_redraw(get_v_line) - dot = always_redraw(get_dot) - slope_line = always_redraw(get_slope_line) - curve = always_redraw(get_curve) - - x.next_to(v_line, RIGHT, SMALL_BUFF) - d_x.next_to(slope_line.get_end(), UP, SMALL_BUFF) - dd_x.next_to(curve.get_end(), RIGHT, SMALL_BUFF) - xs = VGroup(x, d_x, dd_x) - - words = VGroup( - TextMobject("= Height").set_color(BLUE), - TextMobject("= Slope").set_color(RED), - TextMobject("= ``Curvature''").set_color(YELLOW), - ) - words.scale(0.75) - for word, sym in zip(words, xs): - word.next_to(sym, RIGHT, buff=2 * SMALL_BUFF) - sym.word = word - - self.play( - ShowCreation(v_line), - FadeInFromPoint(dot, v_line.get_start()), - FadeInFrom(x, DOWN), - FadeInFrom(x.word, DOWN), - ) - self.add(slope_line, dot) - self.play( - ShowCreation(slope_line), - FadeInFrom(d_x, LEFT), - FadeInFrom(d_x.word, LEFT), - ) - - a_tracker = ValueTracker(0) - curve_copy = curve.copy() - changing_slope = always_redraw( - lambda: get_tangent_line( - curve_copy, - a_tracker.get_value(), - ).set_stroke( - opacity=there_and_back(a_tracker.get_value()) - ) - ) - self.add(curve, dot) - self.play( - ShowCreation(curve), - FadeInFrom(dd_x, LEFT), - FadeInFrom(dd_x.word, LEFT), - ) - self.add(changing_slope) - self.play( - a_tracker.set_value, 1, - run_time=3, - ) - self.remove(changing_slope, a_tracker) - - self.t_tracker = t_tracker - self.curvature_group = VGroup( - v_line, slope_line, curve, dot - ) - self.curvature_group_labels = VGroup(xs, words) - self.fake_graph = graph - - def get_main_example(self): - return VGroup( - self.equation, - self.title, - ) - -# class VisualizeHeightSlopeCurvature(DefineODE): -# CONFIG = { -# "pendulum_config": { -# "length": 2, -# "top_point": 5 * RIGHT + 2 * UP, -# "initial_theta": 175 * DEGREES, -# "mu": 0.3, -# }, -# } - -# def construct(self): -# self.add_graph() -# self.show_value_slope_curvature() -# self.show_changing_curvature_group() - - -class ODEvsPDEinFrames(Scene): - CONFIG = { - "camera_config": {"background_color": DARKER_GREY} - } - - def construct(self): - frames = VGroup(*[ - ScreenRectangle( - height=3.5, - fill_opacity=1, - fill_color=BLACK, - stroke_width=0, - ) - for x in range(2) - ]) - frames.arrange(RIGHT, buff=LARGE_BUFF) - frames.shift(0.5 * DOWN) - - animated_frames = VGroup(*[ - AnimatedBoundary( - frame, - cycle_rate=0.2, - max_stroke_width=1, - ) - for frame in frames - ]) - - titles = VGroup( - # TextMobject("ODEs"), - # TextMobject("PDEs"), - TextMobject("Ordinary", "Differential", "Equations"), - TextMobject("Partial", "Differential", "Equations"), - ) - for title, frame in zip(titles, frames): - title.arrange( - DOWN, - buff=MED_SMALL_BUFF, - aligned_edge=LEFT - ) - title.next_to(frame, UP, MED_LARGE_BUFF) - title.initials = VGroup(*[ - part[0] for part in title - ]) - titles[0][1].shift(0.05 * UP) - - # ODE content - ode = get_ode() - ode.set_width(frames[0].get_width() - MED_LARGE_BUFF) - ode.next_to(frames[0].get_top(), DOWN) - ts = ode.get_parts_by_tex("{t}") - one_input = TextMobject("One input") - one_input.next_to(frames[0].get_bottom(), UP) - o_arrows = VGroup(*[ - Arrow( - one_input.get_top(), - t.get_bottom(), - buff=0.2, - color=WHITE, - max_tip_length_to_length_ratio=0.075, - path_arc=pa - ) - for t, pa in zip(ts, [-0.7, 0, 0.7]) - ]) - o_arrows.set_stroke(width=3) - frames[0].add(ode, one_input, o_arrows) - - # PDE content - pde = TexMobject( - """ - \\frac{\\partial T}{\\partial t} - {(x, y, t)} = - \\frac{\\partial^2 T}{\\partial x^2} - {(x, y, t)} + - \\frac{\\partial^2 T}{\\partial y^2} - {(x, y, t)} - """, - tex_to_color_map={"{(x, y, t)}": WHITE} - ) - pde.set_width(frames[1].get_width() - MED_LARGE_BUFF) - pde.next_to(frames[1].get_top(), DOWN) - inputs = pde.get_parts_by_tex("{(x, y, t)}") - multi_input = TextMobject("Multiple inputs") - multi_input.next_to(frames[1].get_bottom(), UP) - p_arrows = VGroup(*[ - Arrow( - multi_input.get_top(), - mob.get_bottom(), - buff=0.2, - color=WHITE, - max_tip_length_to_length_ratio=0.075, - path_arc=pa - ) - for mob, pa in zip(inputs, [-0.7, 0, 0.7]) - ]) - p_arrows.set_stroke(width=3) - frames[1].add(pde, multi_input, p_arrows) - - self.add( - frames[0], - animated_frames[0], - titles[0] - ) - self.play( - Write(one_input), - ShowCreation(o_arrows, lag_ratio=0.5) - ) - self.wait(2) - self.play(titles[0].initials.set_color, BLUE) - self.wait(7) - - # Transition - self.play( - TransformFromCopy(*titles), - TransformFromCopy(*frames), - ) - self.play(VFadeIn(animated_frames[1])) - self.wait() - self.play(titles[1].initials.set_color, YELLOW) - self.wait(30) - - -class ReferencePiCollisionStateSpaces(Scene): - CONFIG = { - "camera_config": {"background_color": DARKER_GREY} - } - - def construct(self): - title = TextMobject("The block collision puzzle") - title.scale(1.5) - title.to_edge(UP) - self.add(title) - - frames = VGroup(*[ - ScreenRectangle( - height=3.5, - fill_opacity=1, - fill_color=BLACK, - stroke_width=0, - ) - for x in range(2) - ]) - frames.arrange(RIGHT, buff=LARGE_BUFF) - boundary = AnimatedBoundary(frames) - self.add(frames, boundary) - self.wait(15) - - -class XComponentArrows(Scene): - def construct(self): - field = VectorField( - lambda p: np.array([p[1], 0, 0]) - ) - field.set_opacity(0.75) - for u in (1, -1): - field.sort(lambda p: u * p[0]) - self.play(LaggedStartMap( - GrowArrow, field, - lag_ratio=0.1, - run_time=1 - )) - self.play(FadeOut(field)) - - -class BreakingSecondOrderIntoTwoFirstOrder(IntroduceVectorField): - def construct(self): - ode = TexMobject( - "{\\ddot\\theta}", "(t)", "=", - "-\\mu", "{\\dot\\theta}", "(t)" - "-(g / L)\\sin\\big(", "{\\theta}", "(t)\\big)", - tex_to_color_map={ - "{\\ddot\\theta}": RED, - "{\\dot\\theta}": YELLOW, - "{\\theta}": BLUE, - # "{t}": WHITE, - } - ) - so_word = TextMobject("Second order ODE") - sys_word = TextMobject("System of two first order ODEs") - - system1 = self.get_system("{\\theta}", "{\\dot\\theta}") - system2 = self.get_system("{\\theta}", "{\\omega}") - - so_word.to_edge(UP) - ode.next_to(so_word, DOWN) - sys_word.move_to(ORIGIN) - system1.next_to(sys_word, DOWN) - system2.move_to(system1) - - theta_dots = VGroup(*filter( - lambda m: ( - isinstance(m, SingleStringTexMobject) and - "{\\dot\\theta}" == m.get_tex_string() - ), - system1.get_family(), - )) - - self.add(ode) - self.play(FadeInFrom(so_word, 0.5 * DOWN)) - self.wait() - - self.play( - TransformFromCopy( - ode[3:], system1[3].get_entries()[1], - ), - TransformFromCopy(ode[2], system1[2]), - TransformFromCopy( - ode[:2], VGroup( - system1[0], - system1[1].get_entries()[1], - ) - ), - ) - self.play( - FadeIn(system1[1].get_brackets()), - FadeIn(system1[1].get_entries()[0]), - FadeIn(system1[3].get_brackets()), - FadeIn(system1[3].get_entries()[0]), - ) - self.play( - FadeInFromDown(sys_word) - ) - self.wait() - self.play(LaggedStartMap( - ShowCreationThenFadeAround, - theta_dots, - surrounding_rectangle_config={ - "color": PINK, - } - )) - - self.play(ReplacementTransform(system1, system2)) - self.wait() - - def get_system(self, tex1, tex2): - system = VGroup( - TexMobject("d \\over dt"), - self.get_vector_symbol( - tex1 + "(t)", - tex2 + "(t)", - ), - TexMobject("="), - self.get_vector_symbol( - tex2 + "(t)", - "".join([ - "-\\mu", tex2, "(t)", - "-(g / L) \\sin\\big(", - tex1, "(t)", "\\big)", - ]) - ) - ) - system.arrange(RIGHT) - return system - - -class FromODEToVectorField(Scene): - def construct(self): - matrix_config = { - "bracket_v_buff": 2 * SMALL_BUFF, - "element_to_mobject_config": { - "tex_to_color_map": { - "x": GREEN, - "y": RED, - "z": BLUE, - }, - } - } - vect = get_vector_symbol( - "x(t)", "y(t)", "z(t)", - **matrix_config, - ) - d_vect = get_vector_symbol( - "\\sigma\\big(y(t) - x(t)\\big)", - "x(t)\\big(\\rho - z(t)\\big) - y(t)", - "x(t)y(t) - \\beta z(t)", - **matrix_config - ) - equation = VGroup( - TexMobject("d \\over dt").scale(1.5), - vect, - TexMobject("="), - d_vect - ) - equation.scale(0.8) - equation.arrange(RIGHT) - equation.to_edge(UP) - - arrow = Vector(DOWN, color=YELLOW) - arrow.next_to(equation, DOWN) - - self.add(equation) - self.play(ShowCreation(arrow)) - self.wait() - - -class LorenzVectorField(ExternallyAnimatedScene): - pass - - -class ThreeBodiesInSpace(SpecialThreeDScene): - CONFIG = { - "masses": [1, 6, 3], - "colors": [RED_E, GREEN_E, BLUE_E], - "G": 0.5, - "play_time": 60, - } - - def construct(self): - self.add_axes() - self.add_bodies() - self.add_trajectories() - self.let_play() - - def add_axes(self): - axes = self.axes = self.get_axes() - axes.set_stroke(width=0.5) - self.add(axes) - - # Orient - self.set_camera_orientation( - phi=70 * DEGREES, - theta=-110 * DEGREES, - ) - self.begin_ambient_camera_rotation() - - def add_bodies(self): - masses = self.masses - colors = self.colors - - bodies = self.bodies = VGroup() - velocity_vectors = VGroup() - - centers = self.get_initial_positions() - - for mass, color, center in zip(masses, colors, centers): - body = self.get_sphere( - checkerboard_colors=[ - color, color - ], - color=color, - stroke_width=0.1, - ) - body.set_opacity(0.75) - body.mass = mass - body.radius = 0.08 * np.sqrt(mass) - body.set_width(2 * body.radius) - - body.point = center - body.move_to(center) - - body.velocity = self.get_initial_velocity( - center, centers, mass - ) - - vect = self.get_velocity_vector_mob(body) - - bodies.add(body) - velocity_vectors.add(vect) - - total_mass = np.sum([body.mass for body in bodies]) - center_of_mass = reduce(op.add, [ - body.mass * body.get_center() / total_mass - for body in bodies - ]) - average_momentum = reduce(op.add, [ - body.mass * body.velocity / total_mass - for body in bodies - ]) - for body in bodies: - body.shift(-center_of_mass) - body.velocity -= average_momentum - - def get_initial_positions(self): - return [ - np.dot( - 4 * (np.random.random(3) - 0.5), - [RIGHT, UP, OUT] - ) - for x in range(len(self.masses)) - ] - - def get_initial_velocity(self, center, centers, mass): - to_others = [ - center - center2 - for center2 in centers - ] - velocity = 0.2 * mass * normalize(np.cross(*filter( - lambda diff: get_norm(diff) > 0, - to_others - ))) - return velocity - - def add_trajectories(self): - def update_trajectory(traj, dt): - new_point = traj.body.point - if get_norm(new_point - traj.points[-1]) > 0.01: - traj.add_smooth_curve_to(new_point) - - for body in self.bodies: - traj = VMobject() - traj.body = body - traj.start_new_path(body.point) - traj.set_stroke(body.color, 1, opacity=0.75) - traj.add_updater(update_trajectory) - self.add(traj, body) - - def let_play(self): - bodies = self.bodies - bodies.add_updater(self.update_bodies) - # Break it up to see partial files as - # it's rendered - self.add(bodies) - for x in range(int(self.play_time)): - self.wait() - - # - def get_velocity_vector_mob(self, body): - def draw_vector(): - center = body.get_center() - vect = Arrow( - center, - center + body.velocity, - buff=0, - color=RED, - ) - vect.set_shade_in_3d(True) - return vect - # length = vect.get_length() - # if length > 2: - # vect.scale( - # 2 / length, - # about_point=vect.get_start(), - # ) - return always_redraw(draw_vector) - - def update_bodies(self, bodies, dt): - G = self.G - - num_mid_steps = 1000 - for x in range(num_mid_steps): - for body in bodies: - acceleration = np.zeros(3) - for body2 in bodies: - if body2 is body: - continue - diff = body2.point - body.point - m2 = body2.mass - R = get_norm(diff) - acceleration += G * m2 * diff / (R**3) - body.point += body.velocity * dt / num_mid_steps - body.velocity += acceleration * dt / num_mid_steps - for body in bodies: - body.move_to(body.point) - return bodies - - -class AltThreeBodiesInSpace(ThreeBodiesInSpace): - CONFIG = { - "random_seed": 6, - "masses": [1, 2, 6], - } - - -class TwoBodiesInSpace(ThreeBodiesInSpace): - CONFIG = { - "colors": [GREY, BLUE], - "masses": [6, 36], - "play_time": 60, - } - - def construct(self): - self.add_axes() - self.add_bodies() - self.add_trajectories() - self.add_velocity_vectors() - self.add_force_vectors() - self.let_play() - - def add_bodies(self): - super().add_bodies() - for body in self.bodies: - body.point = 3 * normalize(body.get_center()) - # body.point += 2 * IN - # body.velocity += (4 / 60) * OUT - body.move_to(body.point) - - def get_initial_positions(self): - return [ - np.dot( - 6 * (np.random.random(3) - 0.5), - [RIGHT, UP, ORIGIN] - ) - for x in range(len(self.masses)) - ] - - def get_initial_velocity(self, center, centers, mass): - return 0.75 * normalize(np.cross(center, OUT)) - - def add_velocity_vectors(self): - vectors = VGroup(*[ - self.get_velocity_vector(body) - for body in self.bodies - ]) - self.velocity_vectors = vectors - self.add(vectors) - - def get_velocity_vector(self, body): - def create_vector(b): - v = Vector( - b.velocity, - color=RED, - max_stroke_width_to_length_ratio=3, - ) - v.set_stroke(width=3) - v.shift( - b.point + b.radius * normalize(b.velocity) - - v.get_start(), - ) - v.set_shade_in_3d(True) - return v - return always_redraw(lambda: create_vector(body)) - - def add_force_vectors(self): - vectors = VGroup(*[ - self.get_force_vector(b1, b2) - for (b1, b2) in (self.bodies, self.bodies[::-1]) - ]) - self.force_vectors = vectors - self.add(vectors) - - def get_force_vector(self, body1, body2): - def create_vector(b1, b2): - r = b2.point - b1.point - F = r / (get_norm(r)**3) - v = Vector( - 4 * F, - color=YELLOW, - max_stroke_width_to_length_ratio=3, - ) - v.set_stroke(width=3) - v.shift( - b1.point + b1.radius * normalize(F) - - v.get_start(), - ) - v.set_shade_in_3d(True) - return v - return always_redraw(lambda: create_vector(body1, body2)) - - -class TwoBodiesWithZPart(TwoBodiesInSpace): - def add_bodies(self): - super().add_bodies() - for body in self.bodies: - body.point += 3 * IN - body.velocity += (6 / 60) * OUT - - -class LoveExample(PiCreatureScene): - def construct(self): - self.show_hearts() - self.add_love_trackers() - self.break_down_your_rule() - self.break_down_their_rule() - - def create_pi_creatures(self): - you = You() - you.shift(FRAME_WIDTH * LEFT / 4) - you.to_edge(DOWN) - - tau = TauCreature(color=GREEN) - tau.flip() - tau.shift(FRAME_WIDTH * RIGHT / 4) - tau.to_edge(DOWN) - - self.you = you - self.tau = tau - return (you, tau) - - def show_hearts(self): - you, tau = self.you, self.tau - hearts = VGroup() - n_hearts = 20 - for x in range(n_hearts): - heart = SuitSymbol("hearts") - heart.scale(0.5 + 2 * np.random.random()) - heart.shift(np.random.random() * 4 * RIGHT) - heart.shift(np.random.random() * 4 * UP) - hearts.add(heart) - hearts.move_to(2 * DOWN) - hearts.add_updater(lambda m, dt: m.shift(2 * dt * UP)) - - self.add(hearts) - self.play( - LaggedStartMap( - UpdateFromAlphaFunc, hearts, - lambda heart: ( - heart, - lambda h, a: h.set_opacity( - there_and_back(a) - ).shift(0.02 * UP) - ), - lag_ratio=0.01, - run_time=3, - suspend_mobject_updating=False, - ), - ApplyMethod( - you.change, 'hooray', tau.eyes, - run_time=2, - rate_func=squish_rate_func(smooth, 0.5, 1) - ), - ApplyMethod( - tau.change, 'hooray', you.eyes, - run_time=2, - rate_func=squish_rate_func(smooth, 0.5, 1) - ), - ) - self.remove(hearts) - self.wait() - - def add_love_trackers(self): - self.init_ps_point() - self.add_love_decimals() - self.add_love_number_lines() - self.tie_creature_state_to_ps_point() - - self.play(Rotating( - self.ps_point, - radians=-7 * TAU / 8, - about_point=ORIGIN, - run_time=10, - rate_func=linear, - )) - self.wait() - - def break_down_your_rule(self): - label1 = self.love_1_label - label2 = self.love_2_label - ps_point = self.ps_point - - up_arrow = Vector(UP, color=GREEN) - down_arrow = Vector(DOWN, color=RED) - for arrow in (up_arrow, down_arrow): - arrow.next_to(label1, RIGHT) - - self.play(GrowArrow(up_arrow)) - self.play( - self.tau.love_eyes.scale, 1.25, - self.tau.love_eyes.set_color, BLUE_C, - rate_func=there_and_back, - ) - self.play( - ps_point.shift, 6 * RIGHT, - run_time=2, - ) - self.wait() - ps_point.shift(13 * DOWN) - self.play( - FadeOut(up_arrow), - GrowArrow(down_arrow), - ) - self.play( - ps_point.shift, 11 * LEFT, - run_time=3, - ) - self.wait() - - # Derivative - equation = get_love_equation1() - equation.shift(0.5 * UP) - deriv, equals, a, h2 = equation - - self.play( - Write(deriv[:-1]), - Write(equals), - Write(a), - TransformFromCopy(label1[0], deriv.heart), - TransformFromCopy(label2[0], h2), - ) - self.wait() - self.play( - equation.scale, 0.5, - equation.to_corner, UL, - FadeOut(down_arrow) - ) - - def break_down_their_rule(self): - label1 = self.love_1_label - label2 = self.love_2_label - ps_point = self.ps_point - - up_arrow = Vector(UP, color=GREEN) - down_arrow = Vector(DOWN, color=RED) - for arrow in (up_arrow, down_arrow): - arrow.next_to(label2, RIGHT) - - # Derivative - equation = get_love_equation2() - equation.shift(0.5 * UP) - deriv, equals, mb, h1 = equation - - self.play( - Write(deriv[:-1]), - Write(equals), - Write(mb), - TransformFromCopy(label1[0], h1), - TransformFromCopy(label2[0], deriv.heart), - ) - - self.play(GrowArrow(up_arrow)) - self.play( - ps_point.shift, 13 * UP, - run_time=3, - ) - self.wait() - self.play( - ps_point.shift, 11 * RIGHT, - ) - self.play( - FadeOut(up_arrow), - GrowArrow(down_arrow), - ) - self.play( - ps_point.shift, 13 * DOWN, - run_time=3, - ) - self.wait() - - # - def init_ps_point(self): - self.ps_point = VectorizedPoint(np.array([5.0, 5.0, 0])) - - def get_love1(self): - return self.ps_point.get_location()[0] - - def get_love2(self): - return self.ps_point.get_location()[1] - - def set_loves(self, love1=None, love2=None): - if love1 is not None: - self.ps_point.set_x(love1) - if love2 is not None: - self.ps_point.set_x(love2) - - def add_love_decimals(self): - self.love_1_label = self.add_love_decimal( - 1, self.get_love1, self.you.get_color(), -3, - ) - self.love_2_label = self.add_love_decimal( - 2, self.get_love2, self.tau.get_color(), 3, - ) - - def add_love_decimal(self, index, value_func, color, x_coord): - d = DecimalNumber(include_sign=True) - d.add_updater(lambda d: d.set_value(value_func())) - - label = get_heart_var(index) - label.move_to(x_coord * RIGHT) - label.to_edge(UP) - eq = TexMobject("=") - eq.next_to(label, RIGHT, SMALL_BUFF) - eq.shift(SMALL_BUFF * UP) - d.next_to(eq, RIGHT, SMALL_BUFF) - - self.add(label, eq, d) - return VGroup(label, eq, d) - - def add_love_number_lines(self): - nl1 = NumberLine( - x_min=-8, - x_max=8, - unit_size=0.25, - tick_frequency=2, - number_scale_val=0.25, - ) - nl1.set_stroke(width=1) - nl1.next_to(self.love_1_label, DOWN) - nl1.add_numbers(*range(-6, 8, 2)) - - nl2 = nl1.copy() - nl2.next_to(self.love_2_label, DOWN) - - dot1 = Dot(color=self.you.get_color()) - dot1.add_updater(lambda d: d.move_to( - nl1.number_to_point(self.get_love1()) - )) - dot2 = Dot(color=self.tau.get_color()) - dot2.add_updater(lambda d: d.move_to( - nl2.number_to_point(self.get_love2()) - )) - - self.add(nl1, nl2, dot1, dot2) - - def get_love_eyes(self, eyes): - hearts = VGroup() - for eye in eyes: - heart = SuitSymbol("hearts") - heart.match_width(eye) - heart.move_to(eye) - heart.scale(1.25) - heart.set_stroke(BLACK, 1) - hearts.add(heart) - hearts.add_updater( - lambda m: m.move_to(eyes) - ) - return hearts - - def tie_creature_state_to_ps_point(self): - # Quite a mess, but I'm coding in a rush here... - you = self.you - you_copy = you.copy() - tau = self.tau - tau_copy = tau.copy() - - you.love_eyes = self.get_love_eyes(you.eyes) - tau.love_eyes = self.get_love_eyes(tau.eyes) - - self.add(you.love_eyes) - self.add(tau.love_eyes) - - you_height = you.get_height() - tau_height = tau.get_height() - - you_bottom = you.get_bottom() - tau_bottom = tau.get_bottom() - - def update_you(y): - love = self.get_love1() - - cutoff_values = [ - -5, -3, -1, 1, 3, 5 - ] - modes = [ - "angry", "sassy", "hesitant", - "plain", - "happy", "hooray", "surprised", - ] - - if love < cutoff_values[0]: - y.change(modes[0]) - elif love >= cutoff_values[-1]: - y.change(modes[-1]) - else: - i = 0 - while cutoff_values[i] < love: - i += 1 - m1 = modes[i - 1] - m2 = modes[i] - y.change(m1) - you_copy.change(m2) - for mob in y, you_copy: - mob.set_height(you_height) - mob.move_to(you_bottom, DOWN) - - alpha = inverse_interpolate( - cutoff_values[i - 1], - cutoff_values[i], - love, - ) - s_alpha = squish_rate_func(smooth, 0.25, 1)(alpha) - if s_alpha > 0: - y.align_data(you_copy) - f1 = y.family_members_with_points() - f2 = you_copy.family_members_with_points() - for sm1, sm2 in zip(f1, f2): - sm1.interpolate(sm1, sm2, s_alpha) - y.look_at(tau.eyes) - if love < -4: - y.look_at(LEFT_SIDE) - # y.move_to( - # you_bottom + 0.025 * love * RIGHT, DOWN, - # ) - - l_alpha = np.clip( - inverse_interpolate(5, 5.5, love), - 0, 1 - ) - y.eyes.set_opacity(1 - l_alpha) - y.love_eyes.set_opacity(l_alpha) - - return y - - def update_tau(t): - love = self.get_love2() - - cutoff_values = [ - -5, -1.7, 1.7, 5 - ] - modes = [ - "angry", "confused", "plain", - "hooray", "hooray" - ] - - if love < cutoff_values[0]: - t.change(modes[0]) - elif love >= cutoff_values[-1]: - t.change(modes[-1]) - else: - i = 0 - while cutoff_values[i] < love: - i += 1 - m1 = modes[i - 1] - m2 = modes[i] - t.change(m1) - tau_copy.change(m2) - for mob in t, tau_copy: - mob.set_height(tau_height) - mob.move_to(tau_bottom, DOWN) - - alpha = inverse_interpolate( - cutoff_values[i - 1], - cutoff_values[i], - love, - ) - s_alpha = squish_rate_func(smooth, 0.25, 1)(alpha) - if s_alpha > 0: - t.align_data(tau_copy) - f1 = t.family_members_with_points() - f2 = tau_copy.family_members_with_points() - for sm1, sm2 in zip(f1, f2): - sm1.interpolate(sm1, sm2, s_alpha) - # t.move_to( - # tau_bottom + 0.025 * love * LEFT, DOWN, - # ) - t.look_at(you.eyes) - if love < -4: - t.look_at(RIGHT_SIDE) - - l_alpha = np.clip( - inverse_interpolate(5, 5.5, love), - 0, 1 - ) - t.eyes.set_opacity(1 - l_alpha) - t.love_eyes.set_opacity(l_alpha) - - you.add_updater(update_you) - tau.add_updater(update_tau) - - self.pi_creatures = VGroup() - - -class ComparePhysicsToLove(Scene): - def construct(self): - ode = get_ode() - ode.to_edge(UP) - thetas = ode.get_parts_by_tex("theta") - - love = VGroup( - get_love_equation1(), - get_love_equation2(), - ) - love.scale(0.5) - love.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - love.move_to(DOWN) - hearts = VGroup(*filter( - lambda sm: isinstance(sm, SuitSymbol), - love.get_family() - )) - - arrow = DoubleArrow(love.get_top(), ode.get_bottom()) - - self.play(FadeInFrom(ode, DOWN)) - self.play(FadeInFrom(love, UP)) - self.wait() - self.play(LaggedStartMap( - ShowCreationThenFadeAround, - thetas, - )) - self.play(LaggedStartMap( - ShowCreationThenFadeAround, - hearts, - )) - self.wait() - self.play(ShowCreation(arrow)) - self.wait() - - -class FramesComparingPhysicsToLove(Scene): - CONFIG = { - "camera_config": {"background_color": DARKER_GREY} - } - - def construct(self): - ode = get_ode() - ode.to_edge(UP) - - love = VGroup( - get_love_equation1(), - get_love_equation2(), - ) - love.scale(0.5) - love.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - - frames = VGroup(*[ - ScreenRectangle( - height=3.5, - fill_color=BLACK, - fill_opacity=1, - stroke_width=0, - ) - for x in range(2) - ]) - frames.arrange(RIGHT, buff=LARGE_BUFF) - frames.shift(DOWN) - - animated_frames = AnimatedBoundary(frames) - - ode.next_to(frames[0], UP) - love.next_to(frames[1], UP) - - self.add(frames, animated_frames) - self.add(ode, love) - - self.wait(15) - - -class PassageOfTime(Scene): - def construct(self): - clock = Clock() - clock[0].set_color(BLUE) - clock.set_stroke(width=1) - clock.scale(0.8) - clock.to_corner(UL) - passage = ClockPassesTime( - clock, - hours_passed=48, - ) - self.play(passage, run_time=10) - - -class WriteODESolvingCode(ExternallyAnimatedScene): - pass - - -class InaccurateComputation(Scene): - def construct(self): - h_line = DashedLine(LEFT_SIDE, RIGHT_SIDE) - h_line.to_edge(UP, buff=1.5) - words = VGroup( - TextMobject("Real number"), - TextMobject("IEEE 754\\\\representation"), - TextMobject("Error"), - ) - for i, word in zip([-1, 0, 1], words): - word.next_to(h_line, UP) - word.shift(i * FRAME_WIDTH * RIGHT / 3) - - lines = VGroup(*[ - DashedLine(TOP, BOTTOM) - for x in range(4) - ]) - lines.arrange(RIGHT) - lines.stretch_to_fit_width(FRAME_WIDTH) - - self.add(h_line, lines[1:-1], words) - - numbers = VGroup( - TexMobject("\\pi").scale(2), - TexMobject("e^{\\sqrt{163}\\pi}").scale(1.5), - ) - numbers.set_color(YELLOW) - numbers.set_stroke(width=0, background=True) - - bit_strings = VGroup( - TexMobject( - "01000000", - "01001001", - "00001111", - "11011011", - ), - TexMobject( - "01011100", - "01101001", - "00101110", - "00011001", - ) - ) - for mob in bit_strings: - mob.arrange(DOWN, buff=SMALL_BUFF) - for word in mob: - for submob, bit in zip(word, word.get_tex_string()): - if bit == "0": - submob.set_color(LIGHT_GREY) - errors = VGroup( - TexMobject( - "\\approx 8.7422 \\times 10^{-8}" - ), - TexMobject( - "\\approx 5{,}289{,}803{,}032.00", - ), - ) - errors.set_color(RED) - - content = VGroup(numbers, bit_strings, errors) - - for group, word in zip(content, words): - group[1].shift(3 * DOWN) - group.move_to(DOWN) - group.match_x(word) - - self.play(*map(Write, numbers)) - self.wait() - self.play( - TransformFromCopy(numbers, bit_strings), - lag_ratio=0.01, - run_time=2, - ) - self.wait() - self.play(FadeInFrom(errors, 3 * LEFT)) - self.wait() - - -class NewSceneName(Scene): - def construct(self): - pass diff --git a/from_3b1b/active/diffyq/part1/wordy_scenes.py b/from_3b1b/active/diffyq/part1/wordy_scenes.py deleted file mode 100644 index ac2e9163..00000000 --- a/from_3b1b/active/diffyq/part1/wordy_scenes.py +++ /dev/null @@ -1,855 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part1.shared_constructs import * - - -class SmallAngleApproximationTex(Scene): - def construct(self): - approx = TexMobject( - "\\sin", "(", "\\theta", ") \\approx \\theta", - tex_to_color_map={"\\theta": RED}, - arg_separator="", - ) - - implies = TexMobject("\\Downarrow") - period = TexMobject( - "\\text{Period}", "\\approx", - "2\\pi \\sqrt{\\,{L} / {g}}", - **Lg_formula_config, - ) - group = VGroup(approx, implies, period) - group.arrange(DOWN) - - approx_brace = Brace(approx, UP, buff=SMALL_BUFF) - approx_words = TextMobject( - "For small $\\theta$", - tex_to_color_map={"$\\theta$": RED}, - ) - approx_words.scale(0.75) - approx_words.next_to(approx_brace, UP, SMALL_BUFF) - - self.add(approx, approx_brace, approx_words) - self.play( - Write(implies), - FadeInFrom(period, LEFT) - ) - self.wait() - - -class StrogatzQuote(Scene): - def construct(self): - quote = self.get_quote() - movers = VGroup(*quote[:-1].family_members_with_points()) - for mover in movers: - mover.save_state() - disc = Circle(radius=0.05) - disc.set_stroke(width=0) - disc.set_fill(BLACK, 0) - disc.move_to(mover) - mover.become(disc) - self.play( - FadeInFrom(quote.author_part, LEFT), - LaggedStartMap( - # FadeInFromLarge, - # quote[:-1].family_members_with_points(), - Restore, movers, - lag_ratio=0.005, - run_time=2, - ) - # FadeInFromDown(quote[:-1]), - # lag_ratio=0.01, - ) - self.wait() - self.play( - Write(quote.law_part.copy().set_color(YELLOW)), - run_time=1, - ) - self.wait() - self.play( - Write(quote.language_part.copy().set_color(BLUE)), - run_time=1.5, - ) - self.wait(2) - - def get_quote(self): - law_words = "laws of physics" - language_words = "language of differential equations" - author = "-Steven Strogatz" - quote = TextMobject( - """ - \\Large - ``Since Newton, mankind has come to realize - that the laws of physics are always expressed - in the language of differential equations.''\\\\ - """ + author, - alignment="", - arg_separator=" ", - substrings_to_isolate=[law_words, language_words, author] - ) - quote.law_part = quote.get_part_by_tex(law_words) - quote.language_part = quote.get_part_by_tex(language_words) - quote.author_part = quote.get_part_by_tex(author) - quote.set_width(12) - quote.to_edge(UP) - quote[-2].shift(SMALL_BUFF * LEFT) - quote.author_part.shift(RIGHT + 0.5 * DOWN) - quote.author_part.scale(1.2, about_edge=UL) - - return quote - - -class WriteInRadians(Scene): - def construct(self): - words = TextMobject("In radians") - words.set_color(YELLOW) - square = SurroundingRectangle(TexMobject("\\theta")) - square.next_to(words, UP) - self.play(ShowCreation(square)) - self.play(Write(words), FadeOut(square)) - self.wait() - - -class XEqLThetaToCorner(Scene): - def construct(self): - equation = TexMobject( - "x = L\\theta", - tex_to_color_map={ - "x": GREEN, - "\\theta": BLUE, - } - ) - equation.move_to(DOWN + 3 * RIGHT) - self.add(equation) - self.play(equation.to_corner, DL, {"buff": LARGE_BUFF}) - self.wait() - - -class ComingUp(Scene): - CONFIG = { - "camera_config": {"background_color": DARKER_GREY} - } - - def construct(self): - frame = ScreenRectangle( - stroke_width=0, - fill_color=BLACK, - fill_opacity=1, - height=6 - ) - title = TextMobject("Coming up") - title.scale(1.5) - title.to_edge(UP) - frame.next_to(title, DOWN) - animated_frame = AnimatedBoundary(frame) - self.add(frame, title, animated_frame) - self.wait(10) - - -class InputLabel(Scene): - def construct(self): - label = TextMobject("Input") - label.scale(1.25) - arrow = Vector(UP) - arrow.next_to(label, UP) - self.play( - FadeInFrom(label, UP), - GrowArrow(arrow) - ) - self.wait() - - -class ReallyHardToSolve(Scene): - def construct(self): - words = TextMobject( - "They're", "really\\\\", - "freaking", "hard\\\\", - "to", "solve\\\\", - ) - words.set_height(6) - - self.wait() - for word in words: - wait_time = 0.05 * len(word) - self.add(word) - self.wait(wait_time) - self.wait() - - -class ReasonForSolution(Scene): - def construct(self): - # Words - eq_word = TextMobject("Differential\\\\Equation") - s_word = TextMobject("Solution") - u_word = TextMobject("Understanding") - c_word = TextMobject("Computation") - cu_group = VGroup(u_word, c_word) - cu_group.arrange(DOWN, buff=2) - group = VGroup(eq_word, s_word, cu_group) - group.arrange(RIGHT, buff=2) - # words = VGroup(eq_word, s_word, u_word, c_word) - - # Arrows - arrows = VGroup( - Arrow(eq_word.get_right(), s_word.get_left()), - Arrow(s_word.get_right(), u_word.get_left()), - Arrow(s_word.get_right(), c_word.get_left()), - ) - arrows.set_color(LIGHT_GREY) - new_arrows = VGroup( - Arrow( - eq_word.get_corner(UR), - u_word.get_left(), - path_arc=-60 * DEGREES, - ), - Arrow( - eq_word.get_corner(DR), - c_word.get_left(), - path_arc=60 * DEGREES, - ), - ) - new_arrows.set_color(BLUE) - - # Define first examples - t2c = { - "{x}": BLUE, - "{\\dot x}": RED, - } - equation = TexMobject( - "{\\dot x}(t) = k {x}(t)", - tex_to_color_map=t2c, - ) - equation.next_to(eq_word, DOWN) - solution = TexMobject( - "{x}(t) = x_0 e^{kt}", - tex_to_color_map=t2c, - ) - solution.next_to(s_word, DOWN, MED_LARGE_BUFF) - equation.align_to(solution, DOWN) - - axes = Axes( - x_min=-1, - x_max=5.5, - y_min=-1, - y_max=4.5, - y_axis_config={"unit_size": 0.5} - ) - axes.set_stroke(width=2) - graph_line = axes.get_graph( - lambda x: np.exp(0.4 * x) - ) - graph_line.set_stroke(width=2) - graph = VGroup(axes, graph_line) - graph.scale(0.5) - graph.next_to(u_word, UP) - - computation = TexMobject( - # "\\displaystyle " - "e^x = \\sum_{n=0}^\\infty " - "\\frac{x^n}{n!}" - ) - computation.next_to(c_word, DOWN) - - first_examples = VGroup( - equation, solution, graph, computation - ) - - # Second example - ode = get_ode() - ode.scale(0.75) - second_examples = VGroup( - ode, - TexMobject("???").set_color(LIGHT_GREY), - ScreenRectangle( - height=2, - stroke_width=1, - ), - ) - for fe, se in zip(first_examples, second_examples): - se.move_to(fe, DOWN) - - ode.shift(2 * SMALL_BUFF * DOWN) - ode.add_to_back(BackgroundRectangle(ode[-4:])) - - self.add(eq_word) - self.add(equation) - self.play( - FadeInFrom(s_word, LEFT), - GrowArrow(arrows[0]), - TransformFromCopy(equation, solution) - ) - self.wait() - self.play( - FadeInFrom(c_word, UL), - GrowArrow(arrows[2]), - FadeInFrom(computation, UP) - ) - self.wait() - self.play( - FadeInFrom(u_word, DL), - GrowArrow(arrows[1]), - FadeInFromDown(graph) - ) - self.wait(2) - - self.play( - FadeOut(first_examples), - FadeIn(second_examples[:2]) - ) - self.wait() - self.play( - arrows.fade, 0.75, - s_word.fade, 0.75, - second_examples[1].fade, 0.75, - ShowCreation(new_arrows[0]), - FadeIn(second_examples[2]) - ) - self.play( - ShowCreation(new_arrows[1]), - Animation(second_examples), - ) - self.wait() - - -class WritePhaseSpace(Scene): - def construct(self): - word = TextMobject("Phase space") - word.scale(2) - word.shift(FRAME_WIDTH * LEFT / 4) - word.to_edge(UP) - word.add_background_rectangle() - - lines = VGroup(*[ - Line(v, 1.3 * v) - for v in compass_directions(50) - ]) - lines.replace(word, stretch=True) - lines.scale(1.5) - lines.set_stroke(YELLOW) - lines.shuffle() - - self.add(word) - self.play( - ShowPassingFlashWithThinningStrokeWidth( - lines, - lag_ratio=0.002, - run_time=1.5, - time_width=0.9, - n_segments=5, - ) - ) - self.wait() - - -class GleickQuote(Scene): - def construct(self): - quote = TextMobject( - "``[Phase space is] one of the most\\\\", - "powerful inventions", "of modern science.''\\\\", - ) - quote.power_part = quote.get_part_by_tex("power") - book = ImageMobject("ChaosBookCover") - book.set_height(5) - book.next_to(ORIGIN, LEFT) - book.to_edge(DOWN) - gleick = ImageMobject("JamesGleick") - gleick.set_height(5) - gleick.next_to(ORIGIN, RIGHT) - gleick.to_edge(DOWN) - quote.to_edge(UP) - - self.play( - FadeInFrom(book, RIGHT), - FadeInFrom(gleick, LEFT), - ) - self.wait() - self.play(Write(quote)) - self.play(Write( - quote.power_part.copy().set_color(BLUE), - run_time=1 - )) - self.wait() - - -class WritePhaseFlow(Scene): - def construct(self): - words = TextMobject("Phase flow") - words.scale(2) - words.shift(FRAME_WIDTH * LEFT / 4) - words.to_edge(UP) - words.add_background_rectangle() - self.play(Write(words)) - self.wait() - - -class ShowSineValues(Scene): - def construct(self): - angle_tracker = ValueTracker(60 * DEGREES) - get_angle = angle_tracker.get_value - formula = always_redraw( - lambda: self.get_sine_formula(get_angle()) - ) - self.add(formula) - - self.play( - angle_tracker.set_value, 0, - run_time=3, - ) - self.wait() - self.play( - angle_tracker.set_value, 90 * DEGREES, - run_time=3, - ) - self.wait() - - def get_sine_formula(self, angle): - sin, lp, rp = TexMobject( - "\\sin", "(", ") = " - ) - input_part = Integer( - angle / DEGREES, - unit="^\\circ", - ) - input_part.set_color(YELLOW) - output_part = DecimalNumber( - np.sin(input_part.get_value() * DEGREES), - num_decimal_places=3, - ) - result = VGroup( - sin, lp, input_part, rp, output_part - ) - result.arrange(RIGHT, buff=SMALL_BUFF) - sin.scale(1.1, about_edge=DOWN) - lp.align_to(rp, UP) - return result - - -class SetAsideSeekingSolution(Scene): - def construct(self): - ode = get_ode() - ode.to_edge(UP) - q1 = TextMobject("Find an exact solution") - q1.set_color(YELLOW) - q2 = TexMobject( - "\\text{What is }", "\\theta", "(t)", - "\\text{'s personality?}", - tex_to_color_map={"\\theta": BLUE}, - arg_separator="", - ) - theta = q2.get_part_by_tex("\\theta") - - for q in q1, q2: - q.scale(1.5) - q.next_to(ode, DOWN, MED_LARGE_BUFF) - eyes = Eyes(theta, height=0.1) - - self.add(ode) - self.add(q1) - self.wait() - self.play( - q1.scale, 0.3, - q1.to_corner, UR, MED_SMALL_BUFF, - ) - self.play(FadeInFrom(q2, DOWN)) - self.play( - eyes.blink, - rate_func=lambda t: smooth(1 - t), - ) - self.play(eyes.look_at, q2.get_left()) - self.play(eyes.look_at, q2.get_right()) - self.play( - eyes.blink, - rate_func=squish_rate_func(there_and_back) - ) - self.wait() - self.play( - eyes.change_mode, "confused", - eyes.look_at, ode.get_left(), - ) - self.play( - eyes.blink, - rate_func=squish_rate_func(there_and_back) - ) - - -class ThreeBodyTitle(Scene): - def construct(self): - title = TextMobject("Three body problem") - title.scale(1.5) - title.to_edge(UP) - self.add(title) - - -class ThreeBodySymbols(Scene): - def construct(self): - self.init_coord_groups() - self.introduce_coord_groups() - self.count_coords() - - def init_coord_groups(self): - kwargs = { - "bracket_v_buff": 2 * SMALL_BUFF - } - positions = VGroup(*[ - get_vector_symbol(*[ - "{}_{}".format(s, i) - for s in "xyz" - ], **kwargs) - for i in range(1, 4) - ]) - velocities = VGroup(*[ - get_vector_symbol(*[ - "p^{}_{}".format(s, i) - for s in "xyz" - ], **kwargs) - for i in range(1, 4) - ]) - groups = VGroup(positions, velocities) - colors = [GREEN, RED, BLUE] - for group in groups: - for matrix in group: - matrix.coords = matrix.get_entries() - for coord, color in zip(matrix.coords, colors): - coord.set_color(color) - group.arrange(RIGHT) - groups.arrange(DOWN, buff=LARGE_BUFF) - groups.to_edge(LEFT) - - self.coord_groups = groups - - def introduce_coord_groups(self): - groups = self.coord_groups - x_group, p_group = groups - x_word = TextMobject("Positions") - p_word = TextMobject("Momenta") - words = VGroup(x_word, p_word) - for word, group in zip(words, groups): - word.next_to(group, UP) - - rect_groups = VGroup() - for group in groups: - rect_group = VGroup(*[ - SurroundingRectangle( - VGroup(*[ - tm.coords[i] - for tm in group - ]), - color=WHITE, - stroke_width=2, - ) - for i in range(3) - ]) - rect_groups.add(rect_group) - - self.play( - *[ - LaggedStartMap( - FadeInFrom, group, - lambda m: (m, UP), - run_time=1, - ) - for group in groups - ], - *map(FadeInFromDown, words), - ) - for rect_group in rect_groups: - self.play( - ShowCreationThenFadeOut( - rect_group, - lag_ratio=0.5, - ) - ) - self.wait() - - def count_coords(self): - coord_copies = VGroup() - for group in self.coord_groups: - for tex_mob in group: - for coord in tex_mob.coords: - coord_copy = coord.copy() - coord_copy.set_stroke( - WHITE, 2, background=True - ) - coord_copies.add(coord_copy) - - count = Integer() - count_word = TextMobject("18", "degrees \\\\ of freedom")[1] - count_group = VGroup(count, count_word) - count_group.arrange( - RIGHT, - aligned_edge=DOWN, - ) - count_group.scale(1.5) - count_group.next_to( - self.coord_groups, RIGHT, - aligned_edge=DOWN, - ) - count.add_updater( - lambda m: m.set_value(len(coord_copies)) - ) - count.add_updater( - lambda m: m.next_to(count_word[0][0], LEFT, aligned_edge=DOWN) - ) - - self.add(count_group) - self.play( - # ChangeDecimalToValue(count, len(coord_copies)), - ShowIncreasingSubsets(coord_copies), - run_time=1.5, - rate_func=linear, - ) - self.play(FadeOut(coord_copies)) - - -class ThreeBodyEquation(Scene): - def construct(self): - x1 = "\\vec{\\textbf{x}}_1" - x2 = "\\vec{\\textbf{x}}_2" - x3 = "\\vec{\\textbf{x}}_3" - kw = { - "tex_to_color_map": { - x1: RED, - x2: GREEN, - x3: BLUE, - } - } - equations = VGroup(*[ - TexMobject( - "{d^2", t1, "\\over dt^2}", "=", - "G", "\\left(" - "{" + m2, "(", t2, "-", t1, ")" - "\\over" - "||", t2, "-", t1, "||^3}", - "+", - "{" + m3, "(", t3, "-", t1, ")" - "\\over" - "||", t3, "-", t1, "||^3}", - "\\right)", - **kw - ) - for t1, t2, t3, m1, m2, m3 in [ - (x1, x2, x3, "m_1", "m_2", "m_3"), - (x2, x3, x1, "m_2", "m_3", "m_1"), - (x3, x1, x2, "m_3", "m_1", "m_2"), - ] - ]) - equations.arrange(DOWN, buff=LARGE_BUFF) - - self.play(LaggedStartMap( - FadeInFrom, equations, - lambda m: (m, UP), - lag_ratio=0.2, - )) - self.wait() - - -class JumpToThisPoint(Scene): - def construct(self): - dot = Dot(color=YELLOW) - dot.scale(0.5) - - arrow = Vector(DR, color=WHITE) - arrow.next_to(dot, UL, SMALL_BUFF) - words = TextMobject( - "Jump directly to\\\\", - "this point?", - ) - words.add_background_rectangle_to_submobjects() - words.next_to(arrow.get_start(), UP, SMALL_BUFF) - - self.play( - FadeInFromLarge(dot, 20), - rate_func=rush_into, - ) - self.play( - GrowArrow(arrow), - FadeInFromDown(words), - ) - - -class ChaosTitle(Scene): - def construct(self): - title = TextMobject("Chaos theory") - title.scale(1.5) - title.to_edge(UP) - line = Line(LEFT, RIGHT) - line.set_width(FRAME_WIDTH - 1) - line.next_to(title, DOWN) - - self.play( - Write(title), - ShowCreation(line), - ) - self.wait() - - -class RevisitQuote(StrogatzQuote, PiCreatureScene): - def construct(self): - quote = self.get_quote() - quote.law_part.set_color(YELLOW) - quote.language_part.set_color(BLUE) - quote.set_stroke(BLACK, 6, background=True) - quote.scale(0.8, about_edge=UL) - - new_langauge_part = TextMobject( - "\\Large Language of differential equations" - ) - new_langauge_part.to_edge(UP) - new_langauge_part.match_style(quote.language_part) - - randy = self.pi_creature - - self.play( - FadeInFrom(quote[:-1], DOWN), - FadeInFrom(quote[-1:], LEFT), - randy.change, "raise_right_hand", - ) - self.play(Blink(randy)) - self.play(randy.change, "angry") - self.play( - Blink(randy), - VFadeOut(randy, run_time=3) - ) - mover = VGroup(quote.language_part) - self.add(quote, mover) - self.play( - ReplacementTransform( - mover, new_langauge_part, - ), - *[ - FadeOut(part) - for part in quote - if part is not quote.language_part - ], - run_time=2, - ) - self.wait() - - -class EndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "Juan Benet", - "Vassili Philippov", - "Burt Humburg", - "Carlos Vergara Del Rio", - "Matt Russell", - "Scott Gray", - "soekul", - "Tihan Seale", - "Ali Yahya", - "dave nicponski", - "Evan Phillips", - "Graham", - "Joseph Kelly", - "Kaustuv DeBiswas", - "LambdaLabs", - "Lukas Biewald", - "Mike Coleman", - "Peter Mcinerney", - "Quantopian", - "Roy Larson", - "Scott Walter, Ph.D.", - "Yana Chernobilsky", - "Yu Jun", - "Jordan Scales", - "Lukas -krtek.net- Novy", - "John Shaughnessy", - "Britt Selvitelle", - "David Gow", - "J", - "Jonathan Wilson", - "Joseph John Cox", - "Magnus Dahlström", - "Randy C. Will", - "Ryan Atallah", - "Luc Ritchie", - "1stViewMaths", - "Adrian Robinson", - "Alexis Olson", - "Andreas Benjamin Brössel", - "Andrew Busey", - "Ankalagon", - "Antonio Juarez", - "Arjun Chakroborty", - "Art Ianuzzi", - "Awoo", - "Bernd Sing", - "Boris Veselinovich", - "Brian Staroselsky", - "Chad Hurst", - "Charles Southerland", - "Chris Connett", - "Christian Kaiser", - "Clark Gaebel", - "Cooper Jones", - "Danger Dai", - "Dave B", - "Dave Kester", - "David Clark", - "DeathByShrimp", - "Delton Ding", - "eaglle", - "emptymachine", - "Eric Younge", - "Eryq Ouithaqueue", - "Federico Lebron", - "Giovanni Filippi", - "Hal Hildebrand", - "Herman Dieset", - "Hitoshi Yamauchi", - "Isaac Jeffrey Lee", - "j eduardo perez", - "Jacob Magnuson", - "Jameel Syed", - "Jason Hise", - "Jeff Linse", - "Jeff Straathof", - "John Griffith", - "John Haley", - "John V Wertheim", - "Jonathan Eppele", - "Kai-Siang Ang", - "Kanan Gill", - "L0j1k", - "Lee Redden", - "Linh Tran", - "Ludwig Schubert", - "Magister Mugit", - "Mark B Bahu", - "Mark Heising", - "Martin Price", - "Mathias Jansson", - "Matt Langford", - "Matt Roveto", - "Matthew Cocke", - "Michael Faust", - "Michael Hardel", - "Mirik Gogri", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nero Li", - "Nikita Lesnikov", - "Omar Zrien", - "Owen Campbell-Moore", - "Peter Ehrnstrom", - "RedAgent14", - "rehmi post", - "Richard Burgmann", - "Richard Comish", - "Ripta Pasay", - "Rish Kundalia", - "Robert Teed", - "Roobie", - "Ryan Williams", - "Samuel D. Judge", - "Solara570", - "Stevie Metke", - "Tal Einav", - "Ted Suzman", - "Valeriy Skobelev", - "Xavier Bernard", - "Yavor Ivanov", - "Yaw Etse", - "YinYangBalance.Asia", - "Zach Cardwell", - ] - } diff --git a/from_3b1b/active/diffyq/part2/fourier_series.py b/from_3b1b/active/diffyq/part2/fourier_series.py deleted file mode 100644 index ea58f9c0..00000000 --- a/from_3b1b/active/diffyq/part2/fourier_series.py +++ /dev/null @@ -1,933 +0,0 @@ -from manimlib.imports import * -# import scipy - - -class FourierCirclesScene(Scene): - CONFIG = { - "n_vectors": 10, - "big_radius": 2, - "colors": [ - BLUE_D, - BLUE_C, - BLUE_E, - GREY_BROWN, - ], - "circle_style": { - "stroke_width": 2, - }, - "vector_config": { - "buff": 0, - "max_tip_length_to_length_ratio": 0.35, - "tip_length": 0.15, - "max_stroke_width_to_length_ratio": 10, - "stroke_width": 2, - }, - "circle_config": { - "stroke_width": 1, - }, - "base_frequency": 1, - "slow_factor": 0.25, - "center_point": ORIGIN, - "parametric_function_step_size": 0.001, - "drawn_path_color": YELLOW, - "drawn_path_stroke_width": 2, - } - - def setup(self): - self.slow_factor_tracker = ValueTracker( - self.slow_factor - ) - self.vector_clock = ValueTracker(0) - self.vector_clock.add_updater( - lambda m, dt: m.increment_value( - self.get_slow_factor() * dt - ) - ) - self.add(self.vector_clock) - - def get_slow_factor(self): - return self.slow_factor_tracker.get_value() - - def get_vector_time(self): - return self.vector_clock.get_value() - - # - def get_freqs(self): - n = self.n_vectors - all_freqs = list(range(n // 2, -n // 2, -1)) - all_freqs.sort(key=abs) - return all_freqs - - def get_coefficients(self): - return [complex(0) for x in range(self.n_vectors)] - - def get_color_iterator(self): - return it.cycle(self.colors) - - def get_rotating_vectors(self, freqs=None, coefficients=None): - vectors = VGroup() - self.center_tracker = VectorizedPoint(self.center_point) - - if freqs is None: - freqs = self.get_freqs() - if coefficients is None: - coefficients = self.get_coefficients() - - last_vector = None - for freq, coefficient in zip(freqs, coefficients): - if last_vector: - center_func = last_vector.get_end - else: - center_func = self.center_tracker.get_location - vector = self.get_rotating_vector( - coefficient=coefficient, - freq=freq, - center_func=center_func, - ) - vectors.add(vector) - last_vector = vector - return vectors - - def get_rotating_vector(self, coefficient, freq, center_func): - vector = Vector(RIGHT, **self.vector_config) - vector.scale(abs(coefficient)) - if abs(coefficient) == 0: - phase = 0 - else: - phase = np.log(coefficient).imag - vector.rotate(phase, about_point=ORIGIN) - vector.freq = freq - vector.coefficient = coefficient - vector.center_func = center_func - vector.add_updater(self.update_vector) - return vector - - def update_vector(self, vector, dt): - time = self.get_vector_time() - coef = vector.coefficient - freq = vector.freq - phase = np.log(coef).imag - - vector.set_length(abs(coef)) - vector.set_angle(phase + time * freq * TAU) - vector.shift(vector.center_func() - vector.get_start()) - return vector - - def get_circles(self, vectors): - return VGroup(*[ - self.get_circle( - vector, - color=color - ) - for vector, color in zip( - vectors, - self.get_color_iterator() - ) - ]) - - def get_circle(self, vector, color=BLUE): - circle = Circle(color=color, **self.circle_config) - circle.center_func = vector.get_start - circle.radius_func = vector.get_length - circle.add_updater(self.update_circle) - return circle - - def update_circle(self, circle): - circle.set_width(2 * circle.radius_func()) - circle.move_to(circle.center_func()) - return circle - - def get_vector_sum_path(self, vectors, color=YELLOW): - coefs = [v.coefficient for v in vectors] - freqs = [v.freq for v in vectors] - center = vectors[0].get_start() - - path = ParametricFunction( - lambda t: center + reduce(op.add, [ - complex_to_R3( - coef * np.exp(TAU * 1j * freq * t) - ) - for coef, freq in zip(coefs, freqs) - ]), - t_min=0, - t_max=1, - color=color, - step_size=self.parametric_function_step_size, - ) - return path - - # TODO, this should be a general animated mobect - def get_drawn_path_alpha(self): - return self.get_vector_time() - - def get_drawn_path(self, vectors, stroke_width=None, **kwargs): - if stroke_width is None: - stroke_width = self.drawn_path_stroke_width - path = self.get_vector_sum_path(vectors, **kwargs) - broken_path = CurvesAsSubmobjects(path) - broken_path.curr_time = 0 - - def update_path(path, dt): - # alpha = path.curr_time * self.get_slow_factor() - alpha = self.get_drawn_path_alpha() - n_curves = len(path) - for a, sp in zip(np.linspace(0, 1, n_curves), path): - b = alpha - a - if b < 0: - width = 0 - else: - width = stroke_width * (1 - (b % 1)) - sp.set_stroke(width=width) - path.curr_time += dt - return path - - broken_path.set_color(self.drawn_path_color) - broken_path.add_updater(update_path) - return broken_path - - def get_y_component_wave(self, - vectors, - left_x=1, - color=PINK, - n_copies=2, - right_shift_rate=5): - path = self.get_vector_sum_path(vectors) - wave = ParametricFunction( - lambda t: op.add( - right_shift_rate * t * LEFT, - path.function(t)[1] * UP - ), - t_min=path.t_min, - t_max=path.t_max, - color=color, - ) - wave_copies = VGroup(*[ - wave.copy() - for x in range(n_copies) - ]) - wave_copies.arrange(RIGHT, buff=0) - top_point = wave_copies.get_top() - wave.creation = ShowCreation( - wave, - run_time=(1 / self.get_slow_factor()), - rate_func=linear, - ) - cycle_animation(wave.creation) - wave.add_updater(lambda m: m.shift( - (m.get_left()[0] - left_x) * LEFT - )) - - def update_wave_copies(wcs): - index = int( - wave.creation.total_time * self.get_slow_factor() - ) - wcs[:index].match_style(wave) - wcs[index:].set_stroke(width=0) - wcs.next_to(wave, RIGHT, buff=0) - wcs.align_to(top_point, UP) - wave_copies.add_updater(update_wave_copies) - - return VGroup(wave, wave_copies) - - def get_wave_y_line(self, vectors, wave): - return DashedLine( - vectors[-1].get_end(), - wave[0].get_end(), - stroke_width=1, - dash_length=DEFAULT_DASH_LENGTH * 0.5, - ) - - # Computing Fourier series - # i.e. where all the math happens - def get_coefficients_of_path(self, path, n_samples=10000, freqs=None): - if freqs is None: - freqs = self.get_freqs() - dt = 1 / n_samples - ts = np.arange(0, 1, dt) - samples = np.array([ - path.point_from_proportion(t) - for t in ts - ]) - samples -= self.center_point - complex_samples = samples[:, 0] + 1j * samples[:, 1] - - result = [] - for freq in freqs: - riemann_sum = np.array([ - np.exp(-TAU * 1j * freq * t) * cs - for t, cs in zip(ts, complex_samples) - ]).sum() * dt - result.append(riemann_sum) - - return result - - -class FourierSeriesIntroBackground4(FourierCirclesScene): - CONFIG = { - "n_vectors": 4, - "center_point": 4 * LEFT, - "run_time": 30, - "big_radius": 1.5, - } - - def construct(self): - circles = self.get_circles() - path = self.get_drawn_path(circles) - wave = self.get_y_component_wave(circles) - h_line = always_redraw( - lambda: self.get_wave_y_line(circles, wave) - ) - - # Why? - circles.update(-1 / self.camera.frame_rate) - # - self.add(circles, path, wave, h_line) - self.wait(self.run_time) - - def get_ks(self): - return np.arange(1, 2 * self.n_vectors + 1, 2) - - def get_freqs(self): - return self.base_frequency * self.get_ks() - - def get_coefficients(self): - return self.big_radius / self.get_ks() - - -class FourierSeriesIntroBackground8(FourierSeriesIntroBackground4): - CONFIG = { - "n_vectors": 8, - } - - -class FourierSeriesIntroBackground12(FourierSeriesIntroBackground4): - CONFIG = { - "n_vectors": 12, - } - - -class FourierSeriesIntroBackground20(FourierSeriesIntroBackground4): - CONFIG = { - "n_vectors": 20, - } - - -class FourierOfPiSymbol(FourierCirclesScene): - CONFIG = { - "n_vectors": 51, - "center_point": ORIGIN, - "slow_factor": 0.1, - "n_cycles": 1, - "tex": "\\pi", - "start_drawn": False, - "max_circle_stroke_width": 1, - } - - def construct(self): - self.add_vectors_circles_path() - for n in range(self.n_cycles): - self.run_one_cycle() - - def add_vectors_circles_path(self): - path = self.get_path() - coefs = self.get_coefficients_of_path(path) - for coef in coefs: - print(coef) - vectors = self.get_rotating_vectors(coefficients=coefs) - circles = self.get_circles(vectors) - self.set_decreasing_stroke_widths(circles) - # approx_path = self.get_vector_sum_path(circles) - drawn_path = self.get_drawn_path(vectors) - if self.start_drawn: - self.vector_clock.increment_value(1) - - self.add(path) - self.add(vectors) - self.add(circles) - self.add(drawn_path) - - self.vectors = vectors - self.circles = circles - self.path = path - self.drawn_path = drawn_path - - def run_one_cycle(self): - time = 1 / self.slow_factor - self.wait(time) - - def set_decreasing_stroke_widths(self, circles): - mcsw = self.max_circle_stroke_width - for k, circle in zip(it.count(1), circles): - circle.set_stroke(width=max( - # mcsw / np.sqrt(k), - mcsw / k, - mcsw, - )) - return circles - - def get_path(self): - tex_mob = TexMobject(self.tex) - tex_mob.set_height(6) - path = tex_mob.family_members_with_points()[0] - path.set_fill(opacity=0) - path.set_stroke(WHITE, 1) - return path - - -class FourierOfTexPaths(FourierOfPiSymbol, MovingCameraScene): - CONFIG = { - "n_vectors": 100, - "name_color": WHITE, - "animated_name": "Abc", - "time_per_symbol": 5, - "slow_factor": 1 / 5, - "parametric_function_step_size": 0.01, - } - - def construct(self): - name = TextMobject(self.animated_name) - max_width = FRAME_WIDTH - 2 - max_height = FRAME_HEIGHT - 2 - name.set_width(max_width) - if name.get_height() > max_height: - name.set_height(max_height) - - frame = self.camera.frame - frame.save_state() - - vectors = VGroup(VectorizedPoint()) - circles = VGroup(VectorizedPoint()) - for path in name.family_members_with_points(): - for subpath in path.get_subpaths(): - sp_mob = VMobject() - sp_mob.set_points(subpath) - coefs = self.get_coefficients_of_path(sp_mob) - new_vectors = self.get_rotating_vectors( - coefficients=coefs - ) - new_circles = self.get_circles(new_vectors) - self.set_decreasing_stroke_widths(new_circles) - - drawn_path = self.get_drawn_path(new_vectors) - drawn_path.clear_updaters() - drawn_path.set_stroke(self.name_color, 3) - - static_vectors = VMobject().become(new_vectors) - static_circles = VMobject().become(new_circles) - # static_circles = new_circles.deepcopy() - # static_vectors.clear_updaters() - # static_circles.clear_updaters() - - self.play( - Transform(vectors, static_vectors, remover=True), - Transform(circles, static_circles, remover=True), - frame.set_height, 1.5 * name.get_height(), - frame.move_to, path, - ) - - self.add(new_vectors, new_circles) - self.vector_clock.set_value(0) - self.play( - ShowCreation(drawn_path), - rate_func=linear, - run_time=self.time_per_symbol - ) - self.remove(new_vectors, new_circles) - self.add(static_vectors, static_circles) - - vectors = static_vectors - circles = static_circles - self.play( - FadeOut(vectors), - Restore(frame), - run_time=2 - ) - self.wait(3) - - -class FourierOfPiSymbol5(FourierOfPiSymbol): - CONFIG = { - "n_vectors": 5, - "run_time": 10, - } - - -class FourierOfTrebleClef(FourierOfPiSymbol): - CONFIG = { - "n_vectors": 101, - "run_time": 10, - "start_drawn": True, - "file_name": "TrebleClef", - "height": 7.5, - } - - def get_shape(self): - shape = SVGMobject(self.file_name) - return shape - - def get_path(self): - shape = self.get_shape() - path = shape.family_members_with_points()[0] - path.set_height(self.height) - path.set_fill(opacity=0) - path.set_stroke(WHITE, 0) - return path - - -class FourierOfIP(FourierOfTrebleClef): - CONFIG = { - "file_name": "IP_logo2", - "height": 6, - "n_vectors": 100, - } - - # def construct(self): - # path = self.get_path() - # self.add(path) - - def get_shape(self): - shape = SVGMobject(self.file_name) - return shape - - def get_path(self): - shape = self.get_shape() - path = shape.family_members_with_points()[0] - path.add_line_to(path.get_start()) - # path.make_smooth() - - path.set_height(self.height) - path.set_fill(opacity=0) - path.set_stroke(WHITE, 0) - return path - - -class FourierOfEighthNote(FourierOfTrebleClef): - CONFIG = { - "file_name": "EighthNote" - } - - -class FourierOfN(FourierOfTrebleClef): - CONFIG = { - "height": 6, - "n_vectors": 1000, - } - - def get_shape(self): - return TexMobject("N") - - -class FourierNailAndGear(FourierOfTrebleClef): - CONFIG = { - "height": 6, - "n_vectors": 200, - "run_time": 100, - "slow_factor": 0.01, - "parametric_function_step_size": 0.0001, - "arrow_config": { - "tip_length": 0.1, - "stroke_width": 2, - } - } - - def get_shape(self): - shape = SVGMobject("Nail_And_Gear")[1] - return shape - - -class FourierBatman(FourierOfTrebleClef): - CONFIG = { - "height": 4, - "n_vectors": 100, - "run_time": 10, - "arrow_config": { - "tip_length": 0.1, - "stroke_width": 2, - } - } - - def get_shape(self): - shape = SVGMobject("BatmanLogo")[1] - return shape - - -class FourierHeart(FourierOfTrebleClef): - CONFIG = { - "height": 4, - "n_vectors": 100, - "run_time": 10, - "arrow_config": { - "tip_length": 0.1, - "stroke_width": 2, - } - } - - def get_shape(self): - shape = SuitSymbol("hearts") - return shape - - def get_drawn_path(self, *args, **kwargs): - kwargs["stroke_width"] = 5 - path = super().get_drawn_path(*args, **kwargs) - path.set_color(PINK) - return path - - -class FourierNDQ(FourierOfTrebleClef): - CONFIG = { - "height": 4, - "n_vectors": 1000, - "run_time": 10, - "arrow_config": { - "tip_length": 0.1, - "stroke_width": 2, - } - } - - def get_shape(self): - path = VMobject() - shape = TexMobject("NDQ") - for sp in shape.family_members_with_points(): - path.append_points(sp.points) - return path - - -class FourierGoogleG(FourierOfTrebleClef): - CONFIG = { - "n_vectors": 10, - "height": 5, - "g_colors": [ - "#4285F4", - "#DB4437", - "#F4B400", - "#0F9D58", - ] - } - - def get_shape(self): - g = SVGMobject("google_logo")[5] - g.center() - self.add(g) - return g - - def get_drawn_path(self, *args, **kwargs): - kwargs["stroke_width"] = 7 - path = super().get_drawn_path(*args, **kwargs) - - blue, red, yellow, green = self.g_colors - - path[:250].set_color(blue) - path[250:333].set_color(green) - path[333:370].set_color(yellow) - path[370:755].set_color(red) - path[755:780].set_color(yellow) - path[780:860].set_color(green) - path[860:].set_color(blue) - - return path - - -class ExplainCircleAnimations(FourierCirclesScene): - CONFIG = { - "n_vectors": 100, - "center_point": 2 * DOWN, - "n_top_circles": 9, - "path_height": 3, - } - - def construct(self): - self.add_path() - self.add_circles() - self.wait(8) - self.organize_circles_in_a_row() - self.show_frequencies() - self.show_examples_for_frequencies() - self.show_as_vectors() - self.show_vector_sum() - self.tweak_starting_vectors() - - def add_path(self): - self.path = self.get_path() - self.add(self.path) - - def add_circles(self): - coefs = self.get_coefficients_of_path(self.path) - self.circles = self.get_circles(coefficients=coefs) - - self.add(self.circles) - self.drawn_path = self.get_drawn_path(self.circles) - self.add(self.drawn_path) - - def organize_circles_in_a_row(self): - circles = self.circles - top_circles = circles[:self.n_top_circles].copy() - - center_trackers = VGroup() - for circle in top_circles: - tracker = VectorizedPoint(circle.center_func()) - circle.center_func = tracker.get_location - center_trackers.add(tracker) - tracker.freq = circle.freq - tracker.circle = circle - - center_trackers.submobjects.sort( - key=lambda m: m.freq - ) - center_trackers.generate_target() - right_buff = 1.45 - center_trackers.target.arrange(RIGHT, buff=right_buff) - center_trackers.target.to_edge(UP, buff=1.25) - - self.add(top_circles) - self.play( - MoveToTarget(center_trackers), - run_time=2 - ) - self.wait(4) - - self.top_circles = top_circles - self.center_trackers = center_trackers - - def show_frequencies(self): - center_trackers = self.center_trackers - - freq_numbers = VGroup() - for ct in center_trackers: - number = Integer(ct.freq) - number.next_to(ct, DOWN, buff=1) - freq_numbers.add(number) - ct.circle.number = number - - ld, rd = [ - TexMobject("\\dots") - for x in range(2) - ] - ld.next_to(freq_numbers, LEFT, MED_LARGE_BUFF) - rd.next_to(freq_numbers, RIGHT, MED_LARGE_BUFF) - freq_numbers.add_to_back(ld) - freq_numbers.add(rd) - - freq_word = TextMobject("Frequencies") - freq_word.scale(1.5) - freq_word.set_color(YELLOW) - freq_word.next_to(freq_numbers, DOWN, MED_LARGE_BUFF) - - self.play( - LaggedStartMap( - FadeInFromDown, freq_numbers - ) - ) - self.play( - Write(freq_word), - LaggedStartMap( - ShowCreationThenFadeAround, freq_numbers, - ) - ) - self.wait(2) - - self.freq_numbers = freq_numbers - self.freq_word = freq_word - - def show_examples_for_frequencies(self): - top_circles = self.top_circles - c1, c2, c3 = [ - list(filter( - lambda c: c.freq == k, - top_circles - ))[0] - for k in (1, 2, 3) - ] - - neg_circles = VGroup(*filter( - lambda c: c.freq < 0, - top_circles - )) - - for c in [c1, c2, c3, *neg_circles]: - c.rect = SurroundingRectangle(c) - - self.play( - ShowCreation(c2.rect), - WiggleOutThenIn(c2.number), - ) - self.wait(2) - self.play( - ReplacementTransform(c2.rect, c1.rect), - ) - self.play(FadeOut(c1.rect)) - self.wait() - self.play( - ShowCreation(c3.rect), - WiggleOutThenIn(c3.number), - ) - self.play( - FadeOut(c3.rect), - ) - self.wait(2) - self.play( - LaggedStart(*[ - ShowCreationThenFadeOut(c.rect) - for c in neg_circles - ]) - ) - self.wait(3) - self.play(FadeOut(self.freq_word)) - - def show_as_vectors(self): - top_circles = self.top_circles - top_vectors = self.get_rotating_vectors(top_circles) - top_vectors.set_color(WHITE) - - original_circles = top_circles.copy() - self.play( - FadeIn(top_vectors), - top_circles.set_opacity, 0, - ) - self.wait(3) - self.play( - top_circles.match_style, original_circles - ) - self.remove(top_vectors) - - self.top_vectors = top_vectors - - def show_vector_sum(self): - trackers = self.center_trackers.copy() - trackers.sort( - submob_func=lambda t: abs(t.circle.freq - 0.1) - ) - plane = self.plane = NumberPlane( - x_min=-3, - x_max=3, - y_min=-2, - y_max=2, - axis_config={ - "stroke_color": LIGHT_GREY, - } - ) - plane.set_stroke(width=1) - plane.fade(0.5) - plane.move_to(self.center_point) - - self.play( - FadeOut(self.drawn_path), - FadeOut(self.circles), - self.slow_factor_tracker.set_value, 0.05, - ) - self.add(plane, self.path) - self.play(FadeIn(plane)) - - new_circles = VGroup() - last_tracker = None - for tracker in trackers: - if last_tracker: - tracker.new_location_func = last_tracker.circle.get_start - else: - tracker.new_location_func = lambda: self.center_point - - original_circle = tracker.circle - tracker.circle = original_circle.copy() - tracker.circle.center_func = tracker.get_location - new_circles.add(tracker.circle) - - self.add(tracker, tracker.circle) - start_point = tracker.get_location() - self.play( - UpdateFromAlphaFunc( - tracker, lambda t, a: t.move_to( - interpolate( - start_point, - tracker.new_location_func(), - a, - ) - ), - run_time=2 - ) - ) - tracker.add_updater(lambda t: t.move_to( - t.new_location_func() - )) - self.wait(2) - last_tracker = tracker - - self.wait(3) - - self.clear() - self.slow_factor_tracker.set_value(0.1) - self.add( - self.top_circles, - self.freq_numbers, - self.path, - ) - self.add_circles() - for tc in self.top_circles: - for c in self.circles: - if c.freq == tc.freq: - tc.rotate( - angle_of_vector(c.get_start() - c.get_center()) - - angle_of_vector(tc.get_start() - tc.get_center()) - ) - self.wait(10) - - def tweak_starting_vectors(self): - top_circles = self.top_circles - circles = self.circles - path = self.path - drawn_path = self.drawn_path - - new_path = self.get_new_path() - new_coefs = self.get_coefficients_of_path(new_path) - new_circles = self.get_circles(coefficients=new_coefs) - - new_top_circles = VGroup() - new_top_vectors = VGroup() - for top_circle in top_circles: - for circle in new_circles: - if circle.freq == top_circle.freq: - new_top_circle = circle.copy() - new_top_circle.center_func = top_circle.get_center - new_top_vector = self.get_rotating_vector( - new_top_circle - ) - new_top_circles.add(new_top_circle) - new_top_vectors.add(new_top_vector) - - self.play( - self.slow_factor_tracker.set_value, 0, - FadeOut(drawn_path) - ) - self.wait() - self.play( - ReplacementTransform(top_circles, new_top_circles), - ReplacementTransform(circles, new_circles), - FadeOut(path), - run_time=3, - ) - new_drawn_path = self.get_drawn_path( - new_circles, stroke_width=4, - ) - self.add(new_drawn_path) - self.slow_factor_tracker.set_value(0.1) - self.wait(20) - - # - def configure_path(self, path): - path.set_stroke(WHITE, 1) - path.set_fill(BLACK, opacity=1) - path.set_height(self.path_height) - path.move_to(self.center_point) - return path - - def get_path(self): - tex = TexMobject("f") - path = tex.family_members_with_points()[0] - self.configure_path(path) - return path - # return Square().set_height(3) - - def get_new_path(self): - shape = SVGMobject("TrebleClef") - path = shape.family_members_with_points()[0] - self.configure_path(path) - path.scale(1.5, about_edge=DOWN) - return path diff --git a/from_3b1b/active/diffyq/part2/heat_equation.py b/from_3b1b/active/diffyq/part2/heat_equation.py deleted file mode 100644 index 88eb1853..00000000 --- a/from_3b1b/active/diffyq/part2/heat_equation.py +++ /dev/null @@ -1,2981 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part2.shared_constructs import * - - -class TwoDBodyWithManyTemperatures(ThreeDScene): - CONFIG = { - "cells_per_side": 20, - "body_height": 6, - } - - def construct(self): - self.introduce_body() - self.show_temperature_at_all_points() - - def introduce_body(self): - height = self.body_height - buff = 0.025 - rows = VGroup(*[ - VGroup(*[ - Dot( - # stroke_width=0.5, - stroke_width=0, - fill_opacity=1, - ) - for x in range(self.cells_per_side) - ]).arrange(RIGHT, buff=buff) - for y in range(self.cells_per_side) - ]).arrange(DOWN, buff=buff) - for row in rows[1::2]: - row.submobjects.reverse() - - body = self.body = VGroup(*it.chain(*rows)) - body.set_height(height) - body.center() - body.to_edge(LEFT) - - axes = self.axes = Axes( - x_min=-5, x_max=5, - y_min=-5, y_max=5, - ) - axes.match_height(body) - axes.move_to(body) - - for cell in body: - self.color_cell(cell) - # body.set_stroke(WHITE, 0.5) # Do this? - - plate = Square( - stroke_width=0, - fill_color=DARK_GREY, - sheen_direction=UL, - sheen_factor=1, - fill_opacity=1, - ) - plate.replace(body) - - plate_words = TextMobject("Piece of \\\\ metal") - plate_words.scale(2) - plate_words.set_stroke(BLACK, 2, background=True) - plate_words.set_color(BLACK) - plate_words.move_to(plate) - - self.play( - DrawBorderThenFill(plate), - Write( - plate_words, - run_time=2, - rate_func=squish_rate_func(smooth, 0.5, 1) - ) - ) - self.wait() - - self.remove(plate_words) - - def show_temperature_at_all_points(self): - body = self.body - start_corner = body[0].get_center() - - dot = Dot(radius=0.01, color=WHITE) - dot.move_to(start_corner) - - get_point = dot.get_center - - lhs = TexMobject("T = ") - lhs.next_to(body, RIGHT, LARGE_BUFF) - - decimal = DecimalNumber( - num_decimal_places=1, - unit="^\\circ" - ) - decimal.next_to(lhs, RIGHT, MED_SMALL_BUFF, DOWN) - decimal.add_updater( - lambda d: d.set_value( - 40 + 50 * self.point_to_temp(get_point()) - ) - ) - - arrow = Arrow(color=YELLOW) - arrow.set_stroke(BLACK, 8, background=True) - arrow.tip.set_stroke(BLACK, 2, background=True) - # arrow.add_to_back(arrow.copy().set_stroke(BLACK, 5)) - arrow.add_updater(lambda a: a.put_start_and_end_on( - lhs.get_left() + MED_SMALL_BUFF * LEFT, - get_point(), - )) - - dot.add_updater(lambda p: p.move_to( - body[-1] if (1 < len(body)) else start_corner - )) - self.add(body, dot, lhs, decimal, arrow) - self.play( - ShowIncreasingSubsets( - body, - run_time=10, - rate_func=linear, - ) - ) - self.wait() - self.remove(dot) - self.play( - FadeOut(arrow), - FadeOut(lhs), - FadeOut(decimal), - ) - - # - def point_to_temp(self, point, time=0): - x, y = self.axes.point_to_coords(point) - return two_d_temp_func( - 0.3 * x, 0.3 * y, t=time - ) - - def color_cell(self, cell, vect=RIGHT): - p0 = cell.get_corner(-vect) - p1 = cell.get_corner(vect) - colors = [] - for point in p0, p1: - temp = self.point_to_temp(point) - color = temperature_to_color(temp) - colors.append(color) - cell.set_color(color=colors) - cell.set_sheen_direction(vect) - return cell - - -class TwoDBodyWithManyTemperaturesGraph(ExternallyAnimatedScene): - pass - - -class TwoDBodyWithManyTemperaturesContour(ExternallyAnimatedScene): - pass - - -class BringTwoRodsTogether(Scene): - CONFIG = { - "step_size": 0.05, - "axes_config": { - "x_min": -1, - "x_max": 11, - "y_min": -10, - "y_max": 100, - "y_axis_config": { - "unit_size": 0.06, - "tick_frequency": 10, - }, - }, - "y_labels": range(20, 100, 20), - "graph_x_min": 0, - "graph_x_max": 10, - "midpoint": 5, - "max_temp": 90, - "min_temp": 10, - "wait_time": 30, - "default_n_rod_pieces": 20, - "alpha": 1.0, - } - - def construct(self): - self.setup_axes() - self.setup_graph() - self.setup_clock() - - self.show_rods() - self.show_equilibration() - - def setup_axes(self): - axes = Axes(**self.axes_config) - axes.center().to_edge(UP) - - y_label = axes.get_y_axis_label("\\text{Temperature}") - y_label.to_edge(UP) - axes.y_axis.label = y_label - axes.y_axis.add(y_label) - axes.y_axis.add_numbers(*self.y_labels) - - self.axes = axes - self.y_label = y_label - - def setup_graph(self): - graph = self.axes.get_graph( - self.initial_function, - x_min=self.graph_x_min, - x_max=self.graph_x_max, - step_size=self.step_size, - discontinuities=[self.midpoint], - ) - graph.color_using_background_image("VerticalTempGradient") - - self.graph = graph - - def setup_clock(self): - clock = Clock() - clock.set_height(1) - clock.to_corner(UR) - clock.shift(MED_LARGE_BUFF * LEFT) - - time_lhs = TextMobject("Time: ") - time_label = DecimalNumber( - 0, num_decimal_places=2, - ) - time_rhs = TextMobject("s") - time_group = VGroup( - time_lhs, - time_label, - # time_rhs - ) - time_group.arrange(RIGHT, aligned_edge=DOWN) - time_rhs.shift(SMALL_BUFF * LEFT) - time_group.next_to(clock, DOWN) - - self.time_group = time_group - self.time_label = time_label - self.clock = clock - - def show_rods(self): - rod1, rod2 = rods = VGroup( - self.get_rod(0, 5), - self.get_rod(5, 10), - ) - rod1.set_color(rod1[0].get_color()) - rod2.set_color(rod2[-1].get_color()) - - rods.save_state() - rods.space_out_submobjects(1.5) - rods.center() - - labels = VGroup( - TexMobject("90^\\circ"), - TexMobject("10^\\circ"), - ) - for rod, label in zip(rods, labels): - label.next_to(rod, DOWN) - rod.label = label - - self.play( - FadeInFrom(rod1, UP), - Write(rod1.label), - ) - self.play( - FadeInFrom(rod2, DOWN), - Write(rod2.label) - ) - self.wait() - - self.rods = rods - self.rod_labels = labels - - def show_equilibration(self): - rods = self.rods - axes = self.axes - graph = self.graph - labels = self.rod_labels - self.play( - Write(axes), - rods.restore, - rods.space_out_submobjects, 1.1, - FadeIn(self.time_group), - FadeIn(self.clock), - *[ - MaintainPositionRelativeTo( - rod.label, rod - ) - for rod in rods - ], - ) - - br1 = Rectangle(height=0.2, width=1) - br1.set_stroke(width=0) - br1.set_fill(BLACK, opacity=1) - br2 = br1.copy() - br1.add_updater(lambda b: b.move_to(axes.c2p(0, 90))) - br1.add_updater( - lambda b: b.align_to(rods[0].get_right(), LEFT) - ) - br2.add_updater(lambda b: b.move_to(axes.c2p(0, 10))) - br2.add_updater( - lambda b: b.align_to(rods[1].get_left(), RIGHT) - ) - - self.add(graph, br1, br2) - self.play( - ShowCreation(graph), - labels[0].align_to, axes.c2p(0, 87), UP, - labels[1].align_to, axes.c2p(0, 13), DOWN, - ) - self.play() - self.play( - rods.restore, - rate_func=rush_into, - ) - self.remove(br1, br2) - - graph.add_updater(self.update_graph) - self.time_label.add_updater( - lambda d, dt: d.increment_value(dt) - ) - rods.add_updater(self.update_rods) - - self.play( - self.get_clock_anim(self.wait_time), - FadeOut(labels) - ) - - # - def get_clock_anim(self, time, **kwargs): - config = { - "run_time": time, - "hours_passed": time, - } - config.update(kwargs) - return ClockPassesTime(self.clock, **config) - - def initial_function(self, x): - epsilon = 1e-10 - if x < self.midpoint - epsilon: - return self.max_temp - elif x > self.midpoint + epsilon: - return self.min_temp - else: - return (self.min_temp + self.max_temp) / 2 - - def update_graph(self, graph, dt, alpha=None, n_mini_steps=500): - if alpha is None: - alpha = self.alpha - points = np.append( - graph.get_start_anchors(), - [graph.get_last_point()], - axis=0, - ) - for k in range(n_mini_steps): - y_change = np.zeros(points.shape[0]) - dx = points[1][0] - points[0][0] - for i in range(len(points)): - p = points[i] - lp = points[max(i - 1, 0)] - rp = points[min(i + 1, len(points) - 1)] - d2y = (rp[1] - 2 * p[1] + lp[1]) - - if (0 < i < len(points) - 1): - second_deriv = d2y / (dx**2) - else: - second_deriv = 2 * d2y / (dx**2) - # second_deriv = 0 - - y_change[i] = alpha * second_deriv * dt / n_mini_steps - - # y_change[0] = y_change[1] - # y_change[-1] = y_change[-2] - # y_change[0] = 0 - # y_change[-1] = 0 - # y_change -= np.mean(y_change) - points[:, 1] += y_change - graph.set_points_smoothly(points) - return graph - - def get_second_derivative(self, x, dx=0.001): - graph = self.graph - x_min = self.graph_x_min - x_max = self.graph_x_max - - ly, y, ry = [ - graph.point_from_proportion( - inverse_interpolate(x_min, x_max, alt_x) - )[1] - for alt_x in (x - dx, x, x + dx) - ] - - # At the boundary, don't return the second deriv, - # but instead something matching the Neumann - # boundary condition. - if x == x_max: - return (ly - y) / dx - elif x == x_min: - return (ry - y) / dx - else: - d2y = ry - 2 * y + ly - return d2y / (dx**2) - - def get_rod(self, x_min, x_max, n_pieces=None): - if n_pieces is None: - n_pieces = self.default_n_rod_pieces - axes = self.axes - line = Line(axes.c2p(x_min, 0), axes.c2p(x_max, 0)) - rod = VGroup(*[ - Square() - for n in range(n_pieces) - ]) - rod.arrange(RIGHT, buff=0) - rod.match_width(line) - rod.set_height(0.2, stretch=True) - rod.move_to(axes.c2p(x_min, 0), LEFT) - rod.set_fill(opacity=1) - rod.set_stroke(width=1) - rod.set_sheen_direction(RIGHT) - self.color_rod_by_graph(rod) - return rod - - def update_rods(self, rods): - for rod in rods: - self.color_rod_by_graph(rod) - - def color_rod_by_graph(self, rod): - for piece in rod: - piece.set_color(color=[ - self.rod_point_to_color(piece.get_left()), - self.rod_point_to_color(piece.get_right()), - ]) - - def rod_point_to_graph_y(self, point): - axes = self.axes - x = axes.x_axis.p2n(point) - - graph = self.graph - alpha = inverse_interpolate( - self.graph_x_min, - self.graph_x_max, - x, - ) - return axes.y_axis.p2n( - graph.point_from_proportion(alpha) - ) - - def y_to_color(self, y): - y_max = self.max_temp - y_min = self.min_temp - alpha = inverse_interpolate(y_min, y_max, y) - return temperature_to_color(interpolate(-0.8, 0.8, alpha)) - - def rod_point_to_color(self, point): - return self.y_to_color( - self.rod_point_to_graph_y(point) - ) - - -class ShowEvolvingTempGraphWithArrows(BringTwoRodsTogether): - CONFIG = { - "alpha": 0.1, - "arrow_xs": np.linspace(0, 10, 22)[1:-1], - "arrow_scale_factor": 0.5, - "max_magnitude": 1.5, - "wait_time": 30, - "freq_amplitude_pairs": [ - (1, 0.5), - (2, 1), - (3, 0.5), - (4, 0.3), - (5, 0.3), - (7, 0.2), - (21, 0.1), - (41, 0.05), - ], - } - - def construct(self): - self.add_axes() - self.add_graph() - self.add_clock() - self.add_rod() - self.add_arrows() - self.initialize_updaters() - self.let_play() - - def add_axes(self): - self.setup_axes() - self.add(self.axes) - - def add_graph(self): - self.setup_graph() - self.add(self.graph) - - def add_clock(self): - self.setup_clock() - self.add(self.clock) - self.add(self.time_label) - self.time_label.next_to(self.clock, DOWN) - - def add_rod(self): - rod = self.rod = self.get_rod( - self.graph_x_min, - self.graph_x_max, - ) - self.add(rod) - - def add_arrows(self): - graph = self.graph - x_min = self.graph_x_min - x_max = self.graph_x_max - - xs = self.arrow_xs - arrows = VGroup(*[Vector(DOWN) for x in xs]) - asf = self.arrow_scale_factor - - def update_arrows(arrows): - for x, arrow in zip(xs, arrows): - d2y_dx2 = self.get_second_derivative(x) - mag = asf * np.sign(d2y_dx2) * abs(d2y_dx2) - mag = np.clip( - mag, - -self.max_magnitude, - self.max_magnitude, - ) - arrow.put_start_and_end_on( - ORIGIN, mag * UP - ) - point = graph.point_from_proportion( - inverse_interpolate(x_min, x_max, x) - ) - arrow.shift(point - arrow.get_start()) - arrow.set_color( - self.rod_point_to_color(point) - ) - - arrows.add_updater(update_arrows) - - self.add(arrows) - self.arrows = arrows - - def initialize_updaters(self): - if hasattr(self, "graph"): - self.graph.add_updater(self.update_graph) - if hasattr(self, "rod"): - self.rod.add_updater(self.color_rod_by_graph) - if hasattr(self, "time_label"): - self.time_label.add_updater( - lambda d, dt: d.increment_value(dt) - ) - - def let_play(self): - self.run_clock(self.wait_time) - - def run_clock(self, time): - self.play( - ClockPassesTime( - self.clock, - run_time=time, - hours_passed=time, - ), - ) - - # - def temp_func(self, x, t): - new_x = TAU * x / 10 - return 50 + 20 * np.sum([ - amp * np.sin(freq * new_x) * - np.exp(-(self.alpha * freq**2) * t) - for freq, amp in self.freq_amplitude_pairs - ]) - - def initial_function(self, x, time=0): - return self.temp_func(x, 0) - - -class TalkThrough1DHeatGraph(ShowEvolvingTempGraphWithArrows, SpecialThreeDScene): - CONFIG = { - "freq_amplitude_pairs": [ - (1, 0.5), - (2, 1), - (3, 0.5), - (4, 0.3), - (5, 0.3), - (7, 0.2), - ], - "surface_resolution": 20, - "graph_slice_step": 10 / 20, - } - - def construct(self): - self.add_axes() - self.add_graph() - self.add_rod() - - self.emphasize_graph() - self.emphasize_rod() - self.show_x_axis() - self.show_changes_over_time() - self.show_surface() - - def add_graph(self): - self.graph = self.get_graph() - self.add(self.graph) - - def emphasize_graph(self): - graph = self.graph - q_marks = VGroup(*[ - TexMobject("?").move_to( - graph.point_from_proportion(a), - UP, - ).set_stroke(BLACK, 3, background=True) - for a in np.linspace(0, 1, 20) - ]) - - self.play(LaggedStart(*[ - Succession( - FadeInFromLarge(q_mark), - FadeOutAndShift(q_mark, DOWN), - ) - for q_mark in q_marks - ])) - self.wait() - - def emphasize_rod(self): - alt_rod = self.get_rod(0, 10, 50) - word = TextMobject("Rod") - word.scale(2) - word.next_to(alt_rod, UP, MED_SMALL_BUFF) - - self.play( - LaggedStart( - *[ - Rotating(piece, rate_func=smooth) - for piece in alt_rod - ], - run_time=2, - lag_ratio=0.01, - ), - Write(word) - ) - self.remove(*alt_rod) - self.wait() - - self.rod_word = word - - def show_x_axis(self): - rod = self.rod - axes = self.axes - graph = self.graph - x_axis = axes.x_axis - x_numbers = x_axis.get_number_mobjects(*range(1, 11)) - x_axis_label = TexMobject("x") - x_axis_label.next_to(x_axis.get_right(), UP) - - self.play( - rod.set_opacity, 0.5, - FadeInFrom(x_axis_label, UL), - LaggedStartMap( - FadeInFrom, x_numbers, - lambda m: (m, UP), - ) - ) - self.wait() - - # Show x-values - triangle = ArrowTip( - start_angle=-90 * DEGREES, - color=LIGHT_GREY, - ) - x_tracker = ValueTracker(PI) - get_x = x_tracker.get_value - - def get_x_point(): - return x_axis.n2p(get_x()) - - def get_graph_point(): - return graph.point_from_proportion( - inverse_interpolate( - self.graph_x_min, - self.graph_x_max, - get_x(), - ) - ) - - triangle.add_updater( - lambda t: t.next_to(get_x_point(), UP) - ) - x_label = VGroup( - TexMobject("x"), - TexMobject("="), - DecimalNumber( - 0, - num_decimal_places=3, - include_background_rectangle=True, - ).scale(0.9) - ) - x_label.set_stroke(BLACK, 5, background=True) - x_label.add_updater(lambda m: m[-1].set_value(get_x())) - x_label.add_updater(lambda m: m.arrange(RIGHT, buff=SMALL_BUFF)) - x_label.add_updater(lambda m: m[-1].align_to(m[0], DOWN)) - x_label.add_updater(lambda m: m.next_to(triangle, UP, SMALL_BUFF)) - x_label.add_updater(lambda m: m.shift(SMALL_BUFF * RIGHT)) - rod_piece = always_redraw( - lambda: self.get_rod( - get_x() - 0.05, get_x() + 0.05, - n_pieces=1, - ) - ) - - self.play( - FadeInFrom(triangle, UP), - FadeIn(x_label), - FadeIn(rod_piece), - FadeOut(self.rod_word), - ) - for value in [np.exp(2), (np.sqrt(5) + 1) / 2]: - self.play(x_tracker.set_value, value, run_time=2) - self.wait() - - # Show graph - v_line = always_redraw( - lambda: DashedLine( - get_x_point(), - get_graph_point(), - color=self.rod_point_to_color(get_x_point()), - ) - ) - graph_dot = Dot() - graph_dot.add_updater( - lambda m: m.set_color( - self.rod_point_to_color(m.get_center()) - ) - ) - graph_dot.add_updater( - lambda m: m.move_to(get_graph_point()) - ) - t_label = TexMobject("T(", "x", ")") - t_label.set_stroke(BLACK, 3, background=True) - t_label.add_updater( - lambda m: m.next_to(graph_dot, UR, buff=0) - ) - - self.add(v_line, rod_piece, x_label, triangle) - self.play( - TransformFromCopy(x_label[0], t_label[1]), - FadeIn(t_label[0::2]), - ShowCreation(v_line), - GrowFromPoint(graph_dot, get_x_point()), - ) - self.add(t_label) - self.wait() - self.play( - x_tracker.set_value, TAU, - run_time=5, - ) - - self.x_tracker = x_tracker - self.graph_label_group = VGroup( - v_line, rod_piece, triangle, x_label, - graph_dot, t_label, - ) - self.set_variables_as_attrs(*self.graph_label_group) - self.set_variables_as_attrs(x_numbers, x_axis_label) - - def show_changes_over_time(self): - graph = self.graph - t_label = self.t_label - new_t_label = TexMobject("T(", "x", ",", "t", ")") - new_t_label.set_color_by_tex("t", YELLOW) - new_t_label.match_updaters(t_label) - - self.setup_clock() - clock = self.clock - time_label = self.time_label - time_group = self.time_group - - time = 5 - self.play( - FadeIn(clock), - FadeIn(time_group), - ) - self.play( - self.get_graph_time_change_animation( - graph, time - ), - ClockPassesTime(clock), - ChangeDecimalToValue( - time_label, time, - rate_func=linear, - ), - ReplacementTransform( - t_label, - new_t_label, - rate_func=squish_rate_func(smooth, 0.5, 0.7), - ), - run_time=time - ) - self.play( - ShowCreationThenFadeAround( - new_t_label.get_part_by_tex("t") - ), - ) - self.wait() - self.play( - FadeOut(clock), - ChangeDecimalToValue(time_label, 0), - VFadeOut(time_group), - self.get_graph_time_change_animation( - graph, - new_time=0, - ), - run_time=1, - rate_func=smooth, - ) - - self.graph_label_group.remove(t_label) - self.graph_label_group.add(new_t_label) - - def show_surface(self): - axes = self.axes - graph = self.graph - t_min = 0 - t_max = 10 - - axes_copy = axes.deepcopy() - self.original_axes = self.axes - - # Set rod final state - final_graph = self.get_graph(t_max) - curr_graph = self.graph - self.graph = final_graph - final_rod = self.rod.copy() - final_rod.set_opacity(1) - self.color_rod_by_graph(final_rod) - self.graph = curr_graph - - # Time axis - t_axis = NumberLine( - x_min=t_min, - x_max=t_max, - ) - origin = axes.c2p(0, 0) - t_axis.shift(origin - t_axis.n2p(0)) - t_axis.add_numbers( - *range(1, t_max + 1), - direction=UP, - ) - time_label = TextMobject("Time") - time_label.scale(1.5) - time_label.next_to(t_axis, UP) - t_axis.time_label = time_label - t_axis.add(time_label) - # t_axis.rotate(90 * DEGREES, LEFT, about_point=origin) - t_axis.rotate(90 * DEGREES, UP, about_point=origin) - - # New parts of graph - step = self.graph_slice_step - graph_slices = VGroup(*[ - self.get_graph(time=t).shift( - t * IN - ) - for t in np.arange(0, t_max + step, step) - ]) - graph_slices.set_stroke(width=1) - graph_slices.set_shade_in_3d(True) - - # Input plane - x_axis = self.axes.x_axis - y = axes.c2p(0, 0)[1] - surface_config = { - "u_min": self.graph_x_min, - "u_max": self.graph_x_max, - "v_min": t_min, - "v_max": t_max, - "resolution": self.surface_resolution, - } - input_plane = ParametricSurface( - lambda x, t: np.array([ - x_axis.n2p(x)[0], - y, - t_axis.n2p(t)[2], - ]), - **surface_config, - ) - input_plane.set_style( - fill_opacity=0.5, - fill_color=BLUE_B, - stroke_width=0.5, - stroke_color=WHITE, - ) - - # Surface - y_axis = axes.y_axis - surface = ParametricSurface( - lambda x, t: np.array([ - x_axis.n2p(x)[0], - y_axis.n2p(self.temp_func(x, t))[1], - t_axis.n2p(t)[2], - ]), - **surface_config, - ) - surface.set_style( - fill_opacity=0.1, - fill_color=LIGHT_GREY, - stroke_width=0.5, - stroke_color=WHITE, - stroke_opacity=0.5, - ) - - # Rotate everything on screen and move camera - # in such a way that it looks the same - curr_group = Group(*self.get_mobjects()) - curr_group.clear_updaters() - self.set_camera_orientation( - phi=90 * DEGREES, - ) - mobs = [ - curr_group, - graph_slices, - t_axis, - input_plane, - surface, - ] - for mob in mobs: - self.orient_mobject_for_3d(mob) - - # Clean current mobjects - self.x_label.set_stroke(BLACK, 2, background=True) - self.x_label[-1][0].fade(1) - - self.move_camera( - phi=80 * DEGREES, - theta=-85 * DEGREES, - added_anims=[ - Write(input_plane), - Write(t_axis), - FadeOut(self.graph_label_group), - self.rod.set_opacity, 1, - ] - ) - self.begin_ambient_camera_rotation() - self.add(*graph_slices, *self.get_mobjects()) - self.play( - FadeIn(surface), - LaggedStart(*[ - TransformFromCopy(graph, graph_slice) - for graph_slice in graph_slices - ], lag_ratio=0.02) - ) - self.wait(4) - - # Show slices - self.axes = axes_copy # So get_graph works... - slicing_plane = Rectangle( - stroke_width=0, - fill_color=WHITE, - fill_opacity=0.2, - ) - slicing_plane.set_shade_in_3d(True) - slicing_plane.replace( - Line(axes_copy.c2p(0, 0), axes_copy.c2p(10, 100)), - stretch=True - ) - self.orient_mobject_for_3d(slicing_plane) - - def get_time_slice(t): - new_slice = self.get_graph(t) - new_slice.set_shade_in_3d(True) - self.orient_mobject_for_3d(new_slice) - new_slice.shift(t * UP) - return new_slice - - graph.set_shade_in_3d(True) - t_tracker = ValueTracker(0) - graph.add_updater(lambda g: g.become( - get_time_slice(t_tracker.get_value()) - )) - - self.orient_mobject_for_3d(final_rod) - final_rod.shift(10 * UP) - kw = {"run_time": 10, "rate_func": linear} - self.rod.save_state() - self.play( - ApplyMethod(t_tracker.set_value, 10, **kw), - Transform(self.rod, final_rod, **kw), - ApplyMethod(slicing_plane.shift, 10 * UP, **kw), - ) - self.wait() - - self.set_variables_as_attrs( - t_axis, - input_plane, - surface, - graph_slices, - slicing_plane, - t_tracker, - ) - - # - def get_graph(self, time=0): - graph = self.axes.get_graph( - lambda x: self.temp_func(x, time), - x_min=self.graph_x_min, - x_max=self.graph_x_max, - step_size=self.step_size, - ) - graph.time = time - graph.color_using_background_image("VerticalTempGradient") - return graph - - def get_graph_time_change_animation(self, graph, new_time, **kwargs): - old_time = graph.time - graph.time = new_time - config = { - "run_time": abs(new_time - old_time), - "rate_func": linear, - } - config.update(kwargs) - - return UpdateFromAlphaFunc( - graph, - lambda g, a: g.become( - self.get_graph(interpolate( - old_time, new_time, a - )) - ), - **config - ) - - def orient_mobject_for_3d(self, mob): - mob.rotate( - 90 * DEGREES, - axis=RIGHT, - about_point=ORIGIN - ) - return mob - - -class ContrastXChangesToTChanges(TalkThrough1DHeatGraph): - CONFIG = { - # "surface_resolution": 5, - # "graph_slice_step": 1, - } - - def construct(self): - self.catchup_with_last_scene() - self.emphasize_dimensions_of_input_space() - self.reset_time_to_zero() - - self.show_changes_with_x() - self.show_changes_with_t() - - def catchup_with_last_scene(self): - self.force_skipping() - - self.add_axes() - self.add_graph() - self.add_rod() - - self.rod_word = Point() - self.show_x_axis() - self.show_surface() - - self.revert_to_original_skipping_status() - - def emphasize_dimensions_of_input_space(self): - plane = self.input_plane - plane_copy = plane.copy() - plane_copy.set_color(BLUE_E) - plane_copy.shift(SMALL_BUFF * 0.5 * OUT) - - plane_copy1 = plane_copy.copy() - plane_copy1.stretch(0.01, 1, about_edge=DOWN) - plane_copy0 = plane_copy1.copy() - plane_copy0.stretch(0, 0, about_edge=LEFT) - - words = TextMobject("2d input\\\\space") - words.scale(2) - words.move_to(plane.get_center() + SMALL_BUFF * OUT) - - self.play( - Write(words), - self.camera.phi_tracker.set_value, 60 * DEGREES, - self.camera.theta_tracker.set_value, -90 * DEGREES, - run_time=1 - ) - self.play( - ReplacementTransform(plane_copy0, plane_copy1) - ) - self.play( - ReplacementTransform(plane_copy1, plane_copy) - ) - self.wait(2) - self.play(FadeOut(plane_copy)) - - self.input_plane_words = words - - def reset_time_to_zero(self): - self.play( - self.t_tracker.set_value, 0, - VFadeOut(self.slicing_plane), - Restore(self.rod), - ) - - def show_changes_with_x(self): - alpha_tracker = ValueTracker(0) - line = always_redraw( - lambda: self.get_tangent_line( - self.graph, alpha_tracker.get_value(), - ) - ) - - self.stop_ambient_camera_rotation() - self.play( - ShowCreation(line), - FadeOut(self.input_plane_words), - self.camera.phi_tracker.set_value, 80 * DEGREES, - self.camera.theta_tracker.set_value, -90 * DEGREES, - ) - self.play( - alpha_tracker.set_value, 0.425, - run_time=5, - rate_func=bezier([0, 0, 1, 1]), - ) - - # Show dx and dT - p0 = line.point_from_proportion(0.3) - p2 = line.point_from_proportion(0.7) - p1 = np.array([p2[0], *p0[1:]]) - dx_line = DashedLine(p0, p1) - dT_line = DashedLine(p1, p2) - dx = TexMobject("dx") - dT = TexMobject("dT") - VGroup(dx, dT).scale(0.7) - VGroup(dx, dT).rotate(90 * DEGREES, RIGHT) - dx.next_to(dx_line, IN, SMALL_BUFF) - dT.next_to(dT_line, RIGHT, SMALL_BUFF) - - self.play( - ShowCreation(dx_line), - FadeInFrom(dx, LEFT) - ) - self.wait() - self.play( - ShowCreation(dT_line), - FadeInFrom(dT, IN) - ) - self.wait() - self.play(*map(FadeOut, [ - line, dx_line, dT_line, dx, dT, - ])) - - def show_changes_with_t(self): - slices = self.graph_slices - slice_alpha = 0.075 - graph = VMobject() - graph.set_points_smoothly([ - gs.point_from_proportion(slice_alpha) - for gs in slices - ]) - graph.color_using_background_image("VerticalTempGradient") - graph.set_shade_in_3d(True) - - alpha_tracker = ValueTracker(0) - line = always_redraw( - lambda: self.get_tangent_line( - graph, alpha_tracker.get_value(), - ) - ) - - plane = Square() - plane.set_stroke(width=0) - plane.set_fill(WHITE, 0.1) - plane.set_shade_in_3d(True) - plane.rotate(90 * DEGREES, RIGHT) - plane.rotate(90 * DEGREES, OUT) - plane.set_height(10) - plane.set_depth(8, stretch=True) - plane.move_to(self.t_axis.n2p(0), IN + DOWN) - plane.shift(RIGHT) - - self.play( - self.camera.theta_tracker.set_value, -20 * DEGREES, - self.camera.frame_center.shift, 4 * LEFT, - ) - - self.play( - ShowCreation( - graph.copy(), - remover=True - ), - FadeInFrom(plane, 6 * DOWN, run_time=2), - VFadeIn(line), - ApplyMethod( - alpha_tracker.set_value, 1, - run_time=8, - ), - ) - self.add(graph) - - self.begin_ambient_camera_rotation(-0.02) - self.camera.frame_center.add_updater( - lambda m, dt: m.shift(0.05 * dt * RIGHT) - ) - - self.play( - FadeOut(line), - FadeOut(plane), - ) - self.wait(30) # Let rotate - - self.t_graph = graph - - # - def get_tangent_line(self, graph, alpha, d_alpha=0.001, length=2): - if alpha < 1 - d_alpha: - a1 = alpha - a2 = alpha + d_alpha - else: - a1 = alpha - d_alpha - a2 = alpha - - p1 = graph.point_from_proportion(a1) - p2 = graph.point_from_proportion(a2) - line = Line(p1, p2, color=WHITE) - line.scale( - length / line.get_length() - ) - line.move_to(p1) - return line - - -class TransitionToTempVsTime(ContrastXChangesToTChanges): - CONFIG = { - # "surface_resolution": 5, - # "graph_slice_step": 1, - } - - def construct(self): - self.catchup_with_last_scene() - - axes = self.original_axes - t_axis = self.t_axis - y_axis = axes.y_axis - x_axis = axes.x_axis - - for mob in self.get_mobjects(): - mob.clear_updaters() - self.stop_ambient_camera_rotation() - self.move_camera( - phi=90 * DEGREES, - theta=0 * DEGREES, - added_anims=[ - Rotate( - y_axis, 90 * DEGREES, - axis=OUT, - about_point=y_axis.n2p(0), - ), - FadeOut(VGroup( - self.graph_slices, - self.surface, - self.slicing_plane, - self.rod, - self.graph, - self.x_numbers, - self.x_axis_label, - self.t_graph, - )), - self.camera.frame_center.move_to, 5 * LEFT, - ] - ) - self.play( - VGroup(x_axis, self.input_plane).stretch, - 0, 0, {"about_point": y_axis.n2p(0)}, - ) - self.play( - t_axis.time_label.scale, 1 / 1.5, - t_axis.time_label.next_to, t_axis, IN, MED_LARGE_BUFF, - t_axis.numbers.shift, 0.7 * IN, - ) - self.wait() - - def catchup_with_last_scene(self): - self.force_skipping() - - self.add_axes() - self.add_graph() - self.add_rod() - - self.rod_word = Point() - self.show_x_axis() - self.show_surface() - - self.emphasize_dimensions_of_input_space() - self.reset_time_to_zero() - - self.show_changes_with_x() - self.show_changes_with_t() - - self.revert_to_original_skipping_status() - - -class ShowDelTermsAsTinyNudges(TransitionToTempVsTime): - CONFIG = { - # "surface_resolution": 5, - # "graph_slice_step": 1, - "tangent_line_length": 4, - } - - def construct(self): - self.catchup_with_last_scene() - self.stop_camera() - self.show_del_t() - self.show_del_x() - - def stop_camera(self): - self.stop_ambient_camera_rotation() - for mob in self.get_mobjects(): - mob.clear_updaters() - - def show_del_x(self): - x_tracker = ValueTracker(3) - dx_tracker = ValueTracker(0.5) - - line_group = self.get_line_group( - self.graph, - x_tracker, - dx_tracker, - corner_index=0, - ) - dx_line, dT_line, tan_line = line_group - - del_x = TexMobject("\\partial x") - del_x.set_color(GREEN) - del_x.line = dx_line - del_x.direction = OUT - del_T = TexMobject("\\partial T") - del_T.line = dT_line - del_T.direction = RIGHT - syms = VGroup(del_T, del_x) - for sym in syms: - sym.add_updater(lambda m: m.set_width( - dx_line.get_length() - )) - sym.rect = SurroundingRectangle(sym) - group = VGroup(sym, sym.rect) - group.rotate(90 * DEGREES, RIGHT) - - for sym in syms: - sym.add_updater(lambda m: m.next_to( - m.line, m.direction, SMALL_BUFF, - )) - sym.rect.move_to(sym) - - self.move_camera( - phi=80 * DEGREES, - theta=-90 * DEGREES, - added_anims=[ - self.camera.frame_center.move_to, ORIGIN, - ], - ) - for sym in reversed(syms): - self.play( - FadeInFrom(sym, -sym.direction), - ShowCreation( - sym.line.copy(), - remover=True - ), - ) - self.add(sym.line) - self.play(ShowCreation(tan_line)) - for sym in syms: - self.play( - ShowCreationThenDestruction(sym.rect) - ) - self.wait() - self.wait() - self.add(line_group) - self.play( - dx_tracker.set_value, 0.01, - run_time=5, - ) - self.play( - FadeOut(syms), - FadeOut(line_group), - ) - - def show_del_t(self): - # Largely copy pasted from above. - # Reconsolidate if any of this will actually - # be used later. - t_tracker = ValueTracker(1) - dt_tracker = ValueTracker(1) - - line_group = self.get_line_group( - self.t_graph, t_tracker, dt_tracker, - corner_index=1, - ) - dt_line, dT_line, tan_line = line_group - - del_t = TexMobject("\\partial t") - del_t.set_color(YELLOW) - del_t.line = dt_line - del_t.direction = OUT - del_T = TexMobject("\\partial T") - del_T.line = dT_line - del_T.direction = UP - syms = VGroup(del_T, del_t) - for sym in syms: - sym.rect = SurroundingRectangle(sym) - group = VGroup(sym, sym.rect) - group.rotate(90 * DEGREES, RIGHT) - group.rotate(90 * DEGREES, OUT) - sym.add_updater(lambda m: m.set_height( - 0.8 * dT_line.get_length() - )) - - del_t.add_updater(lambda m: m.set_height( - min(0.5, m.line.get_length()) - )) - del_T.add_updater(lambda m: m.set_depth( - min(0.5, m.line.get_length()) - )) - for sym in syms: - sym.add_updater(lambda m: m.next_to( - m.line, m.direction, SMALL_BUFF, - )) - sym.rect.move_to(sym) - - self.move_camera( - phi=80 * DEGREES, - theta=-10 * DEGREES, - added_anims=[ - self.camera.frame_center.move_to, 5 * LEFT, - ], - ) - for sym in reversed(syms): - self.play( - FadeInFrom(sym, -sym.direction), - ShowCreation( - sym.line.copy(), - remover=True - ), - ) - self.add(sym.line) - self.play(ShowCreation(tan_line)) - for sym in syms: - self.play( - ShowCreationThenDestruction(sym.rect) - ) - self.wait() - self.wait() - self.add(line_group) - self.play( - dt_tracker.set_value, 0.01, - run_time=5, - ) - self.play( - FadeOut(syms), - FadeOut(line_group), - ) - - # - def get_line_group(self, graph, input_tracker, nudge_tracker, corner_index): - get_x = input_tracker.get_value - get_dx = nudge_tracker.get_value - - def get_graph_point(x): - return graph.point_from_proportion( - inverse_interpolate( - self.graph_x_min, - self.graph_x_max, - x, - ) - ) - - def get_corner(p1, p2): - result = np.array(p1) - result[corner_index] = p2[corner_index] - return result - - line_group = VGroup( - Line(color=WHITE), - Line(color=RED), - Line(color=WHITE, stroke_width=2), - ) - - def update_line_group(group): - dxl, dTl, tl = group - p0 = get_graph_point(get_x()) - p2 = get_graph_point(get_x() + get_dx()) - p1 = get_corner(p0, p2) - - dxl.set_points_as_corners([p0, p1]) - dTl.set_points_as_corners([p1, p2]) - tl.set_points_as_corners([p0, p2]) - tl.scale( - self.tangent_line_length / tl.get_length() - ) - line_group.add_updater(update_line_group) - return line_group - - -class ShowCurvatureToRateOfChangeIntuition(ShowEvolvingTempGraphWithArrows): - CONFIG = { - "freq_amplitude_pairs": [ - (1, 0.7), - (2, 1), - (3, 0.5), - (4, 0.3), - (5, 0.3), - (7, 0.2), - ], - "arrow_xs": [0.7, 3.8, 4.6, 5.4, 6.2, 9.3], - "arrow_scale_factor": 0.2, - "max_magnitude": 1.0, - "wait_time": 20, - } - - def let_play(self): - arrows = self.arrows - curves = VGroup(*[ - self.get_mini_curve( - inverse_interpolate( - self.graph_x_min, - self.graph_x_max, - x, - ) - ) - for x in self.arrow_xs - ]) - curves.set_stroke(WHITE, 5) - - curve_words = VGroup() - for curve, arrow in zip(curves, arrows): - word = TextMobject("curve") - word.scale(0.7) - word.next_to(curve, arrow.get_vector()[1] * DOWN, SMALL_BUFF) - curve_words.add(word) - - self.remove(arrows) - - self.play( - ShowCreation(curves), - LaggedStartMap(FadeIn, curve_words), - self.y_label.set_fill, {"opacity": 0}, - ) - self.wait() - self.add(*arrows, curves) - self.play(LaggedStartMap(GrowArrow, arrows)) - self.wait() - - self.play(FadeOut(VGroup(curves, curve_words))) - self.add(arrows) - super().let_play() - - def get_mini_curve(self, alpha, d_alpha=0.02): - result = VMobject() - result.pointwise_become_partial( - self.graph, - alpha - d_alpha, - alpha + d_alpha, - ) - return result - - -class DiscreteSetup(ShowEvolvingTempGraphWithArrows): - CONFIG = { - "step_size": 1, - "rod_piece_size_ratio": 1 / 3, - "dashed_line_stroke_opacity": 1.0, - "dot_radius": DEFAULT_DOT_RADIUS, - "freq_amplitude_pairs": [ - (1, 0.5), - (2, 1), - (3, 0.5), - (4, 0.3), - (5, 0.3), - (7, 0.2), - (21, 0.1), - # (41, 0.05), - ], - } - - def construct(self): - self.add_axes() - self.add_graph() - self.discretize() - self.let_time_pass() - self.show_nieghbor_rule() - self.focus_on_three_points() - self.show_difference_formula() - self.gut_check_new_interpretation() - self.write_second_difference() - self.emphasize_final_expression() - - def add_axes(self): - super().add_axes() - self.axes.shift(MED_SMALL_BUFF * LEFT) - - def add_graph(self): - points = self.get_points(time=0) - graph = VMobject() - graph.set_points_smoothly(points) - graph.color_using_background_image("VerticalTempGradient") - - self.add(graph) - - self.graph = graph - self.points = points - - def discretize(self): - axes = self.axes - x_axis = axes.x_axis - graph = self.graph - - piecewise_graph = CurvesAsSubmobjects(graph) - dots = self.get_dots() - v_lines = VGroup(*map(self.get_v_line, dots)) - - rod_pieces = VGroup() - for x in self.get_sample_inputs(): - piece = Line(LEFT, RIGHT) - piece.set_width( - self.step_size * self.rod_piece_size_ratio - ) - piece.move_to(axes.c2p(x, 0)) - piece.set_color( - self.rod_point_to_color(piece.get_center()) - ) - rod_pieces.add(piece) - - word = TextMobject("Discrete version") - word.scale(1.5) - word.next_to(x_axis, UP) - word.set_stroke(BLACK, 3, background=True) - - self.remove(graph) - self.play( - ReplacementTransform( - piecewise_graph, dots, - ), - Write(word, run_time=1) - ) - self.add(v_lines, word) - self.play( - x_axis.fade, 0.8, - TransformFromCopy( - x_axis.tick_marks[1:], - rod_pieces, - ), - LaggedStartMap(ShowCreation, v_lines) - ) - self.play(FadeOut(word)) - self.wait() - - self.rod_pieces = rod_pieces - self.dots = dots - self.v_lines = v_lines - - def let_time_pass(self): - dots = self.dots - - t_tracker = ValueTracker(0) - t_tracker.add_updater(lambda m, dt: m.increment_value(dt)) - self.add(t_tracker) - - self.add_clock() - self.time_label.next_to(self.clock, DOWN) - self.time_label.add_updater( - lambda m: m.set_value(t_tracker.get_value()) - ) - dots.add_updater(lambda d: d.become( - self.get_dots(t_tracker.get_value()) - )) - run_time = 5 - self.play( - ClockPassesTime( - self.clock, - run_time=run_time, - hours_passed=run_time, - ), - ) - t_tracker.clear_updaters() - t_tracker.set_value(run_time) - self.wait() - self.play( - t_tracker.set_value, 0, - FadeOut(self.clock), - FadeOut(self.time_label), - ) - self.remove(t_tracker) - dots.clear_updaters() - - def show_nieghbor_rule(self): - dots = self.dots - rod_pieces = self.rod_pieces - index = self.index = 2 - - p1, p2, p3 = rod_pieces[index:index + 3] - d1, d2, d3 = dots[index:index + 3] - point_label = TextMobject("Point") - neighbors_label = TextMobject("Neighbors") - words = VGroup(point_label, neighbors_label) - for word in words: - word.scale(0.7) - word.add_background_rectangle() - - point_label.next_to(p2, DOWN) - neighbors_label.next_to(p2, UP, buff=1) - bottom = neighbors_label.get_bottom() - kw = { - "buff": 0.1, - "stroke_width": 2, - "tip_length": 0.15 - } - arrows = VGroup( - Arrow(bottom, p1.get_center(), **kw), - Arrow(bottom, p3.get_center(), **kw), - ) - arrows.set_color(WHITE) - - dot = Dot() - dot.set_fill(GREY, opacity=0.2) - dot.replace(p2) - dot.scale(3) - - self.play( - dot.scale, 0, - dot.set_opacity, 0, - FadeInFrom(point_label, DOWN) - ) - self.play( - FadeInFrom(neighbors_label, DOWN), - *map(GrowArrow, arrows) - ) - self.wait() - - # Let d2 change - self.play( - d1.set_y, 3, - d3.set_y, 3, - ) - - def get_v(): - return 0.25 * np.sum([ - d1.get_y(), - -2 * d2.get_y(), - + d3.get_y(), - ]) - v_vect_fader = ValueTracker(0) - v_vect = always_redraw( - lambda: Vector( - get_v() * UP, - color=temperature_to_color( - get_v(), -2, 2, - ), - ).shift(d2.get_center()).set_opacity( - v_vect_fader.get_value(), - ) - ) - d2.add_updater( - lambda d, dt: d.shift( - get_v() * dt * UP, - ) - ) - - self.add(v_vect) - self.play(v_vect_fader.set_value, 1) - self.wait(3) - self.play( - d1.set_y, 0, - d3.set_y, 0, - ) - self.wait(4) - self.play(FadeOut(VGroup( - point_label, - neighbors_label, - arrows - ))) - - self.v_vect = v_vect - self.example_pieces = VGroup(p1, p2, p3) - self.example_dots = VGroup(d1, d2, d3) - - def focus_on_three_points(self): - dots = self.example_dots - d1, d2, d3 = dots - pieces = self.example_pieces - y_axis = self.axes.y_axis - - x_labels, T_labels = [ - VGroup(*[ - TexMobject("{}_{}".format(s, i)) - for i in [1, 2, 3] - ]).scale(0.8) - for s in ("x", "T") - ] - for xl, piece in zip(x_labels, pieces): - xl.next_to(piece, DOWN) - xl.add_background_rectangle() - for Tl, dot in zip(T_labels, dots): - Tl.dot = dot - Tl.add_updater(lambda m: m.next_to( - m.dot, RIGHT, SMALL_BUFF - )) - Tl.add_background_rectangle() - T1, T2, T3 = T_labels - - d2.movement_updater = d2.get_updaters()[0] - dots.clear_updaters() - self.remove(self.v_vect) - - self.play( - ShowCreationThenFadeAround(pieces), - FadeOut(self.dots[:self.index]), - FadeOut(self.v_lines[:self.index]), - FadeOut(self.rod_pieces[:self.index]), - FadeOut(self.dots[self.index + 3:]), - FadeOut(self.v_lines[self.index + 3:]), - FadeOut(self.rod_pieces[self.index + 3:]), - ) - self.play(LaggedStartMap( - FadeInFrom, x_labels, - lambda m: (m, LEFT), - lag_ratio=0.3, - run_time=2, - )) - self.play( - d3.set_y, 1, - d2.set_y, 0.25, - d1.set_y, 0, - ) - self.wait() - self.play(LaggedStart(*[ - TransformFromCopy(xl, Tl) - for xl, Tl in zip(x_labels, T_labels) - ], lag_ratio=0.3, run_time=2)) - self.wait() - - # Show lines - h_lines = VGroup(*map(self.get_h_line, dots)) - hl1, hl2, hl3 = h_lines - - average_pointer = ArrowTip( - start_angle=0, - length=0.2, - ) - average_pointer.set_color(YELLOW) - average_pointer.stretch(0.25, 1) - average_pointer.add_updater( - lambda m: m.move_to( - 0.5 * (hl1.get_start() + hl3.get_start()), - RIGHT - ) - ) - average_arrows = always_redraw(lambda: VGroup(*[ - Arrow( - hl.get_start(), - average_pointer.get_right(), - color=WHITE, - buff=0.0, - ) - for hl in [hl1, hl3] - ])) - average_label = TexMobject( - "{T_1", "+", "T_3", "\\over", "2}" - ) - average_label.scale(0.5) - average_label.add_updater(lambda m: m.next_to( - average_pointer, LEFT, SMALL_BUFF - )) - - average_rect = SurroundingRectangle(average_label) - average_rect.add_updater( - lambda m: m.move_to(average_label) - ) - average_words = TextMobject("Neighbor\\\\average") - average_words.match_width(average_rect) - average_words.match_color(average_rect) - average_words.add_updater( - lambda m: m.next_to(average_rect, UP, SMALL_BUFF) - ) - - mini_T1 = average_label.get_part_by_tex("T_1") - mini_T3 = average_label.get_part_by_tex("T_3") - for mini, line in (mini_T1, hl1), (mini_T3, hl3): - mini.save_state() - mini.next_to(line, LEFT, SMALL_BUFF) - - self.add(hl1, hl3, T_labels) - y_axis.remove(y_axis.numbers) - self.play( - GrowFromPoint(hl1, hl1.get_end()), - GrowFromPoint(hl3, hl3.get_end()), - TransformFromCopy( - T1, mini_T1, - ), - TransformFromCopy( - T3, mini_T3, - ), - FadeOut(y_axis.numbers), - y_axis.set_stroke, {"width": 1}, - ) - self.play( - FadeIn(average_pointer), - Restore(mini_T1), - Restore(mini_T3), - FadeIn(average_label[1]), - FadeIn(average_label[3:]), - *map(GrowArrow, average_arrows) - ) - self.add(average_arrows, average_label) - self.play( - ShowCreation(average_rect), - FadeIn(average_words), - ) - self.play( - GrowFromPoint(hl2, hl2.get_end()) - ) - self.wait() - - # Show formula - formula = TexMobject( - "\\left(", - "{T_1", "+", "T_3", "\\over", "2}", - "-", "T_2", - "\\right)" - ) - formula.to_corner(UR, buff=MED_LARGE_BUFF) - formula.shift(1.7 * LEFT) - brace = Brace(formula, DOWN) - diff_value = DecimalNumber(include_sign=True) - diff_value.add_updater(lambda m: m.set_value( - y_axis.p2n(average_pointer.get_right()) - - y_axis.p2n(d2.get_center()) - )) - diff_value.next_to(brace, DOWN) - - self.play( - ReplacementTransform( - average_label.deepcopy(), - formula[1:1 + len(average_label)] - ), - TransformFromCopy(T2, formula[-2]), - FadeIn(formula[-3]), - FadeIn(formula[-1]), - FadeIn(formula[0]), - GrowFromCenter(brace), - FadeIn(diff_value) - ) - self.wait() - - # Changes - self.play(FadeIn(self.v_vect)) - d2.add_updater(d2.movement_updater) - self.wait(5) - - self.play( - d3.set_y, 3, - d1.set_y, 2.5, - d2.set_y, -2, - ) - self.wait(5) - self.play( - d3.set_y, 1, - d1.set_y, -1, - ) - self.wait(8) - - # Show derivative - lhs = TexMobject( - "{dT_2", "\\over", "dt}", "=", "\\alpha" - ) - dt = lhs.get_part_by_tex("dt") - alpha = lhs.get_part_by_tex("\\alpha") - lhs.next_to(formula, LEFT, SMALL_BUFF) - - self.play(Write(lhs)) - self.play(ShowCreationThenFadeAround(dt)) - self.wait() - self.play(ShowCreationThenFadeAround(alpha)) - self.wait() - self.play( - FadeOut(brace), - FadeOut(diff_value), - ) - - self.lhs = lhs - self.rhs = formula - - def show_difference_formula(self): - lhs = self.lhs - rhs = self.rhs - d1, d2, d3 = self.example_dots - - new_rhs = TexMobject( - "=", - "{\\alpha", "\\over", "2}", - "\\left(", - "(", "T_3", "-", "T_2", ")", - "-", - "(", "T_2", "-", "T_1", ")", - "\\right)" - ) - big_parens = VGroup( - new_rhs.get_part_by_tex("\\left("), - new_rhs.get_part_by_tex("\\right)"), - ) - for paren in big_parens: - paren.scale(2) - new_rhs.next_to(rhs, DOWN) - new_rhs.align_to(lhs.get_part_by_tex("="), LEFT) - - def p2p_anim(mob1, mob2, tex, index=0): - return TransformFromCopy( - mob1.get_parts_by_tex(tex)[index], - mob2.get_parts_by_tex(tex)[index], - ) - - self.play( - p2p_anim(lhs, new_rhs, "="), - p2p_anim(rhs, new_rhs, "\\left("), - p2p_anim(rhs, new_rhs, "\\right)"), - p2p_anim(lhs, new_rhs, "\\alpha"), - p2p_anim(rhs, new_rhs, "\\over"), - p2p_anim(rhs, new_rhs, "2"), - ) - self.play( - p2p_anim(rhs, new_rhs, "T_3"), - p2p_anim(rhs, new_rhs, "-"), - p2p_anim(rhs, new_rhs, "T_2"), - FadeIn(new_rhs.get_parts_by_tex("(")[1]), - FadeIn(new_rhs.get_parts_by_tex(")")[0]), - ) - self.play( - p2p_anim(rhs, new_rhs, "T_2", -1), - p2p_anim(rhs, new_rhs, "-", -1), - p2p_anim(rhs, new_rhs, "T_1"), - FadeIn(new_rhs.get_parts_by_tex("-")[1]), - FadeIn(new_rhs.get_parts_by_tex("(")[2]), - FadeIn(new_rhs.get_parts_by_tex(")")[1]), - ) - self.wait() - - self.rhs2 = new_rhs - - # Show deltas - T1_index = new_rhs.index_of_part_by_tex("T_1") - T3_index = new_rhs.index_of_part_by_tex("T_3") - diff1 = new_rhs[T1_index - 2:T1_index + 1] - diff2 = new_rhs[T3_index:T3_index + 3] - brace1 = Brace(diff1, DOWN, buff=SMALL_BUFF) - brace2 = Brace(diff2, DOWN, buff=SMALL_BUFF) - delta_T1 = TexMobject("\\Delta T_1") - delta_T1.next_to(brace1, DOWN, SMALL_BUFF) - delta_T2 = TexMobject("\\Delta T_2") - delta_T2.next_to(brace2, DOWN, SMALL_BUFF) - minus = TexMobject("-") - minus.move_to(Line( - delta_T1.get_right(), - delta_T2.get_left(), - )) - braces = VGroup(brace1, brace2) - deltas = VGroup(delta_T1, delta_T2) - - kw = { - "direction": LEFT, - "buff": SMALL_BUFF, - "min_num_quads": 2, - } - lil_brace1 = always_redraw(lambda: Brace( - Line(d1.get_left(), d2.get_left()), **kw - )) - lil_brace2 = always_redraw(lambda: Brace( - Line(d2.get_left(), d3.get_left()), **kw - )) - lil_braces = VGroup(lil_brace1, lil_brace2) - lil_delta_T1 = delta_T1.copy() - lil_delta_T2 = delta_T2.copy() - lil_deltas = VGroup(lil_delta_T1, lil_delta_T2) - for brace, delta in zip(lil_braces, lil_deltas): - delta.brace = brace - delta.add_updater(lambda d: d.next_to( - d.brace, LEFT, SMALL_BUFF, - )) - - delta_T1.set_color(BLUE) - lil_delta_T1.set_color(BLUE) - delta_T2.set_color(RED) - lil_delta_T2.set_color(RED) - - double_difference_brace = Brace(deltas, DOWN) - double_difference_words = TextMobject( - "Difference of differences" - ) - double_difference_words.next_to( - double_difference_brace, DOWN - ) - - self.play( - GrowFromCenter(brace1), - GrowFromCenter(lil_brace1), - FadeIn(delta_T1), - FadeIn(lil_delta_T1), - ) - self.wait() - self.play( - GrowFromCenter(brace2), - GrowFromCenter(lil_brace2), - FadeIn(delta_T2), - FadeIn(lil_delta_T2), - ) - self.wait() - self.play( - Write(minus), - GrowFromCenter(double_difference_brace), - Write(double_difference_words), - ) - self.wait() - - self.braces = braces - self.deltas = deltas - self.delta_minus = minus - self.lil_braces = lil_braces - self.lil_deltas = lil_deltas - self.double_difference_brace = double_difference_brace - self.double_difference_words = double_difference_words - - def gut_check_new_interpretation(self): - lil_deltas = self.lil_deltas - d1, d2, d3 = self.example_dots - - self.play(ShowCreationThenFadeAround(lil_deltas[0])) - self.play(ShowCreationThenFadeAround(lil_deltas[1])) - self.wait() - self.play( - d2.shift, MED_SMALL_BUFF * UP, - rate_func=there_and_back, - ) - self.wait() - self.play( - d3.set_y, 3, - d1.set_y, -0.5, - ) - self.wait(5) - self.play( - d3.set_y, 1.5, - d1.set_y, -2, - ) - self.wait(5) - - def write_second_difference(self): - dd_word = self.double_difference_words - - delta_delta = TexMobject("\\Delta \\Delta T_1") - delta_delta.set_color(MAROON_B) - - delta_delta.move_to(dd_word, UP) - - second_difference_word = TextMobject( - "``Second difference''" - ) - second_difference_word.next_to(delta_delta, DOWN) - - self.play( - FadeOutAndShift(dd_word, UP), - FadeInFrom(delta_delta, UP), - ) - self.wait() - self.play( - Write(second_difference_word), - ) - self.wait() - - # Random play - d1, d2, d3 = self.example_dots - self.play( - d3.set_y, 3, - d1.set_y, -0.5, - ) - self.wait(5) - self.play( - d3.set_y, 1.5, - d1.set_y, -2, - ) - self.wait(5) - - self.delta_delta = delta_delta - self.second_difference_word = second_difference_word - - def emphasize_final_expression(self): - lhs = self.lhs - rhs = self.rhs - rhs2 = self.rhs2 - old_dd = self.delta_delta - dd = old_dd.copy() - old_ao2 = rhs2[1:4] - ao2 = old_ao2.copy() - - new_lhs = lhs[:-1] - full_rhs = VGroup( - lhs[-1], - lhs[-2].copy(), - rhs, - rhs2, - self.braces, - self.deltas, - self.delta_minus, - self.double_difference_brace, - old_dd, - self.second_difference_word, - ) - new_rhs = VGroup(ao2, dd) - new_rhs.arrange(RIGHT, buff=SMALL_BUFF) - new_rhs.next_to(new_lhs, RIGHT) - - self.play( - full_rhs.to_edge, DOWN, {"buff": LARGE_BUFF}, - ) - self.play( - TransformFromCopy(old_ao2, ao2), - TransformFromCopy(old_dd, dd), - ) - self.play( - ShowCreationThenFadeAround( - VGroup(new_lhs, new_rhs) - ) - ) - self.wait() - - # - def get_sample_inputs(self): - return np.arange( - self.graph_x_min, - self.graph_x_max + self.step_size, - self.step_size, - ) - - def get_points(self, time=0): - return [ - self.axes.c2p(x, self.temp_func(x, t=time)) - for x in self.get_sample_inputs() - ] - - def get_dots(self, time=0): - points = self.get_points(time) - dots = VGroup(*[ - Dot( - point, - radius=self.dot_radius - ) - for point in points - ]) - dots.color_using_background_image("VerticalTempGradient") - return dots - - def get_dot_dashed_line(self, dot, index, color=False): - direction = np.zeros(3) - direction[index] = -1 - - def get_line(): - p1 = dot.get_edge_center(direction) - p0 = np.array(p1) - p0[index] = self.axes.c2p(0, 0)[index] - result = DashedLine( - p0, p1, - stroke_width=2, - color=WHITE, - stroke_opacity=self.dashed_line_stroke_opacity, - ) - if color: - result.color_using_background_image( - "VerticalTempGradient" - ) - return result - return always_redraw(get_line) - - def get_h_line(self, dot): - return self.get_dot_dashed_line(dot, 0, True) - - def get_v_line(self, dot): - return self.get_dot_dashed_line(dot, 1) - - -class ShowFinitelyManyX(DiscreteSetup): - def construct(self): - self.setup_axes() - axes = self.axes - axes.fade(1) - points = [ - axes.c2p(x, 0) - for x in self.get_sample_inputs()[1:] - ] - x_labels = VGroup(*[ - TexMobject("x_{}".format(i)).next_to( - p, DOWN - ) - for i, p in enumerate(points) - ]) - - self.play(LaggedStartMap( - FadeInFromLarge, x_labels - )) - self.play(LaggedStartMap(FadeOut, x_labels)) - self.wait() - - -class DiscreteGraphStillImage1(DiscreteSetup): - CONFIG = { - "step_size": 1, - } - - def construct(self): - self.add_axes() - self.add_graph() - self.discretize() - - -class DiscreteGraphStillImageFourth(DiscreteGraphStillImage1): - CONFIG = { - "step_size": 0.25, - } - - -class DiscreteGraphStillImageTenth(DiscreteGraphStillImage1): - CONFIG = { - "step_size": 0.1, - "dashed_line_stroke_opacity": 0.25, - "dot_radius": 0.04, - } - - -class DiscreteGraphStillImageHundredth(DiscreteGraphStillImage1): - CONFIG = { - "step_size": 0.01, - "dashed_line_stroke_opacity": 0.1, - "dot_radius": 0.01, - } - - -class TransitionToContinuousCase(DiscreteSetup): - CONFIG = { - "step_size": 0.1, - "tangent_line_length": 3, - "wait_time": 30, - } - - def construct(self): - self.add_axes() - self.add_graph() - self.show_temperature_difference() - self.show_second_derivative() - self.show_curvature_examples() - self.show_time_changes() - - def add_graph(self): - self.setup_graph() - self.play( - ShowCreation( - self.graph, - run_time=3, - ) - ) - self.wait() - - def show_temperature_difference(self): - x_tracker = ValueTracker(2) - dx_tracker = ValueTracker(1) - - line_group = self.get_line_group( - x_tracker, - dx_tracker, - ) - dx_line, dT_line, tan_line, dx_sym, dT_sym = line_group - tan_line.set_stroke(width=0) - - brace = Brace(dx_line, UP) - fixed_distance = TextMobject("Fixed\\\\distance") - fixed_distance.scale(0.7) - fixed_distance.next_to(brace, UP) - delta_T = TexMobject("\\Delta T") - delta_T.move_to(dT_sym, LEFT) - - self.play( - ShowCreation(VGroup(dx_line, dT_line)), - FadeInFrom(delta_T, LEFT) - ) - self.play( - GrowFromCenter(brace), - FadeInFromDown(fixed_distance), - ) - self.wait() - self.play( - FadeOut(delta_T, UP), - FadeIn(dT_sym, DOWN), - FadeOut(brace, UP), - FadeOut(fixed_distance, UP), - FadeIn(dx_sym, DOWN), - ) - self.add(line_group) - self.play( - dx_tracker.set_value, 0.01, - run_time=5 - ) - self.wait() - self.play( - dx_tracker.set_value, 0.3, - ) - - # Show rate of change - to_zero = TexMobject("\\rightarrow 0") - to_zero.match_height(dT_sym) - to_zero.next_to(dT_sym, buff=SMALL_BUFF) - - ratio = TexMobject( - "{\\partial T", "\\over", "\\partial x}" - ) - ratio[0].match_style(dT_sym) - ratio.to_edge(UP) - - self.play(ShowCreationThenFadeAround( - dT_sym, - surrounding_rectangle_config={ - "buff": 0.05, - "stroke_width": 1, - } - )) - self.play(GrowFromPoint(to_zero, dT_sym.get_right())) - self.wait() - self.play( - TransformFromCopy( - dT_sym, - ratio.get_part_by_tex("\\partial T") - ), - TransformFromCopy( - dx_sym, - ratio.get_part_by_tex("\\partial x") - ), - Write(ratio.get_part_by_tex("\\over")) - ) - self.play( - ShowCreation( - tan_line.copy().set_stroke(width=2), - remover=True - ), - FadeOut(to_zero), - ) - tan_line.set_stroke(width=2) - self.wait() - - # Look at neighbors - x0 = x_tracker.get_value() - dx = dx_tracker.get_value() - v_line, lv_line, rv_line = v_lines = VGroup(*[ - self.get_v_line(x) - for x in [x0, x0 - dx, x0 + dx] - ]) - v_lines[1:].set_color(BLUE) - - self.play(ShowCreation(v_line)) - self.play( - TransformFromCopy(v_line, lv_line), - TransformFromCopy(v_line, rv_line), - ) - self.wait() - self.play( - FadeOut(v_lines[1:]), - ApplyMethod( - dx_tracker.set_value, 0.01, - run_time=2 - ), - ) - - self.line_group = line_group - self.deriv = ratio - self.x_tracker = x_tracker - self.dx_tracker = dx_tracker - self.v_line = v_line - - def show_second_derivative(self): - x_tracker = self.x_tracker - deriv = self.deriv - v_line = self.v_line - - deriv_of_deriv = TexMobject( - "{\\partial", - "\\left(", - "{\\partial T", "\\over", "\\partial x}", - "\\right)", - "\\over", - "\\partial x}" - ) - deriv_of_deriv.set_color_by_tex("\\partial T", RED) - - deriv_of_deriv.to_edge(UP) - dT_index = deriv_of_deriv.index_of_part_by_tex("\\partial T") - inner_deriv = deriv_of_deriv[dT_index:dT_index + 3] - - self.play( - ReplacementTransform(deriv, inner_deriv), - Write(VGroup(*filter( - lambda m: m not in inner_deriv, - deriv_of_deriv, - ))) - ) - v_line.add_updater(lambda m: m.become( - self.get_v_line(x_tracker.get_value()) - )) - for change in [-0.1, 0.1]: - self.play( - x_tracker.increment_value, change, - run_time=3 - ) - - # Write second deriv - second_deriv = TexMobject( - "{\\partial^2 T", "\\over", "\\partial x^2}" - ) - second_deriv[0].set_color(RED) - eq = TexMobject("=") - eq.next_to(deriv_of_deriv, RIGHT) - second_deriv.next_to(eq, RIGHT) - second_deriv.align_to(deriv_of_deriv, DOWN) - eq.match_y(second_deriv.get_part_by_tex("\\over")) - - self.play(Write(eq)) - self.play( - TransformFromCopy( - deriv_of_deriv.get_parts_by_tex("\\partial")[:2], - second_deriv.get_parts_by_tex("\\partial^2 T"), - ), - ) - self.play( - Write(second_deriv.get_part_by_tex("\\over")), - TransformFromCopy( - deriv_of_deriv.get_parts_by_tex("\\partial x"), - second_deriv.get_parts_by_tex("\\partial x"), - ), - ) - self.wait() - - def show_curvature_examples(self): - x_tracker = self.x_tracker - v_line = self.v_line - line_group = self.line_group - - x_tracker.set_value(3.6) - self.wait() - self.play( - x_tracker.set_value, 3.8, - run_time=4, - ) - self.wait() - x_tracker.set_value(6.2) - self.wait() - self.play( - x_tracker.set_value, 6.4, - run_time=4, - ) - self.wait() - - # - dx = 0.2 - neighbor_lines = always_redraw(lambda: VGroup(*[ - self.get_v_line( - x_tracker.get_value() + u * dx, - line_class=Line, - ) - for u in [-1, 1] - ])) - neighbor_lines.set_color(BLUE) - - self.play(FadeOut(line_group)) - self.play(*[ - TransformFromCopy(v_line, nl) - for nl in neighbor_lines - ]) - self.add(neighbor_lines) - self.play( - x_tracker.set_value, 5, - run_time=5, - rate_func=lambda t: smooth(t, 3) - ) - v_line.clear_updaters() - self.play( - FadeOut(v_line), - FadeOut(neighbor_lines), - ) - self.wait() - - def show_time_changes(self): - self.setup_clock() - graph = self.graph - - time_label = self.time_label - clock = self.clock - time_label.next_to(clock, DOWN) - - graph.add_updater(self.update_graph) - time_label.add_updater( - lambda d, dt: d.increment_value(dt) - ) - - self.add(time_label) - self.add_arrows() - self.play( - ClockPassesTime( - clock, - run_time=self.wait_time, - hours_passed=self.wait_time, - ), - ) - - # - def get_v_line(self, x, line_class=DashedLine, stroke_width=2): - axes = self.axes - graph = self.graph - line = line_class( - axes.c2p(x, 0), - graph.point_from_proportion( - inverse_interpolate( - self.graph_x_min, - self.graph_x_max, - x, - ) - ), - stroke_width=stroke_width, - ) - return line - - def get_line_group(self, - x_tracker, - dx_tracker, - dx_tex="\\partial x", - dT_tex="\\partial T", - max_sym_width=0.5, - ): - graph = self.graph - get_x = x_tracker.get_value - get_dx = dx_tracker.get_value - - dx_line = Line(color=WHITE) - dT_line = Line(color=RED) - tan_line = Line(color=WHITE) - lines = VGroup(dx_line, dT_line, tan_line) - lines.set_stroke(width=2) - dx_sym = TexMobject(dx_tex) - dT_sym = TexMobject(dT_tex) - dT_sym.match_color(dT_line) - syms = VGroup(dx_sym, dT_sym) - - group = VGroup(*lines, *syms) - - def update_group(group): - dxl, dTl, tanl, dxs, dTs = group - x = get_x() - dx = get_dx() - p0, p2 = [ - graph.point_from_proportion( - inverse_interpolate( - self.graph_x_min, - self.graph_x_max, - x - ) - ) - for x in [x, x + dx] - ] - p1 = np.array([p2[0], *p0[1:]]) - dxl.put_start_and_end_on(p0, p1) - dTl.put_start_and_end_on(p1, p2) - tanl.put_start_and_end_on(p0, p2) - tanl.scale( - self.tangent_line_length / - tanl.get_length() - ) - dxs.match_width(dxl) - dTs.set_height(0.7 * dTl.get_height()) - for sym in dxs, dTs: - if sym.get_width() > max_sym_width: - sym.set_width(max_sym_width) - dxs.next_to( - dxl, -dTl.get_vector(), SMALL_BUFF, - ) - dTs.next_to( - dTl, dxl.get_vector(), SMALL_BUFF, - ) - - group.add_updater(update_group) - return group - - -class ShowManyVLines(TransitionToContinuousCase): - CONFIG = { - "wait_time": 20, - "max_denom": 10, - "x_step": 0.025, - } - - def construct(self): - self.add_axes() - self.add_graph() - self.add_v_lines() - self.show_time_changes() - - def add_arrows(self): - pass - - def add_v_lines(self): - axes = self.axes - - v_lines = always_redraw(lambda: VGroup(*[ - self.get_v_line( - x, - line_class=Line, - stroke_width=0.5, - ) - for x in np.arange(0, 10, self.x_step) - ])) - group = VGroup(*v_lines) - - x_pointer = ArrowTip(start_angle=PI / 2) - x_pointer.set_color(WHITE) - x_pointer.next_to(axes.c2p(0, 0), DOWN, buff=0) - x_eq = VGroup( - TexMobject("x="), - DecimalNumber(0) - ) - x_eq.add_updater( - lambda m: m.arrange(RIGHT, buff=SMALL_BUFF) - ) - x_eq.add_updater( - lambda m: m[1].set_value(axes.x_axis.p2n(x_pointer.get_top())) - ) - x_eq.add_updater(lambda m: m.next_to( - x_pointer, DOWN, SMALL_BUFF, - submobject_to_align=x_eq[0] - )) - - self.add(x_pointer, x_eq) - self.play( - Write( - group, - remover=True, - lag_ratio=self.x_step / 2, - run_time=6, - ), - ApplyMethod( - x_pointer.next_to, - axes.c2p(10, 0), - DOWN, {"buff": 0}, - rate_func=linear, - run_time=5, - ), - ) - self.add(v_lines) - x_eq.clear_updaters() - self.play( - FadeOut(x_eq), - FadeOut(x_pointer), - ) - - -class ShowNewtonsLawGraph(Scene): - CONFIG = { - "k": 0.2, - "initial_water_temp": 80, - "room_temp": 20, - "delta_T_color": YELLOW, - } - - def construct(self): - self.setup_axes() - self.show_temperatures() - self.show_graph() - self.show_equation() - self.talk_through_examples() - - def setup_axes(self): - axes = Axes( - x_min=0, - x_max=10, - y_min=0, - y_max=100, - y_axis_config={ - "unit_size": 0.06, - "tick_frequency": 10, - }, - center_point=5 * LEFT + 2.5 * DOWN - ) - x_axis = axes.x_axis - y_axis = axes.y_axis - y_axis.add_numbers(*range(20, 100, 20)) - x_axis.add_numbers(*range(1, 11)) - - x_axis.label = TextMobject("Time") - x_axis.label.next_to(x_axis, DOWN, MED_SMALL_BUFF) - - y_axis.label = TexMobject("\\text{Temperature}") - y_axis.label.next_to(y_axis, RIGHT, buff=SMALL_BUFF) - y_axis.label.align_to(axes, UP) - for axis in [x_axis, y_axis]: - axis.add(axis.label) - - self.add(axes) - self.axes = axes - - def show_temperatures(self): - axes = self.axes - - water_dot = Dot() - water_dot.color_using_background_image("VerticalTempGradient") - water_dot.move_to(axes.c2p(0, self.initial_water_temp)) - room_line = DashedLine( - axes.c2p(0, self.room_temp), - axes.c2p(10, self.room_temp), - ) - room_line.set_color(BLUE) - room_line.color_using_background_image("VerticalTempGradient") - - water_arrow = Vector(LEFT, color=WHITE) - water_arrow.next_to(water_dot, RIGHT, SMALL_BUFF) - water_words = TextMobject( - "Initial water\\\\temperature" - ) - water_words.scale(0.7) - water_words.next_to(water_arrow, RIGHT) - - room_words = TextMobject("Room temperature") - room_words.scale(0.7) - room_words.next_to(room_line, DOWN, SMALL_BUFF) - - self.play( - FadeInFrom(water_dot, RIGHT), - GrowArrow(water_arrow), - Write(water_words), - run_time=1, - ) - self.play(ShowCreation(room_line)) - self.play(FadeInFromDown(room_words)) - self.wait() - - self.set_variables_as_attrs( - water_dot, - water_arrow, - water_words, - room_line, - room_words, - ) - - def show_graph(self): - axes = self.axes - water_dot = self.water_dot - - k = self.k - rt = self.room_temp - t0 = self.initial_water_temp - graph = axes.get_graph( - lambda t: rt + (t0 - rt) * np.exp(-k * t) - ) - graph.color_using_background_image("VerticalTempGradient") - - def get_x(): - return axes.x_axis.p2n(water_dot.get_center()) - - brace_line = always_redraw(lambda: Line( - axes.c2p(get_x(), rt), - water_dot.get_center(), - stroke_width=0, - )) - brace = always_redraw( - lambda: Brace( - brace_line, RIGHT, buff=SMALL_BUFF - ) - ) - - delta_T = TexMobject("\\Delta T") - delta_T.set_color(self.delta_T_color) - delta_T.add_updater(lambda m: m.next_to( - brace, RIGHT, SMALL_BUFF - )) - - self.add(brace_line) - self.play( - GrowFromCenter(brace), - Write(delta_T), - ) - self.play( - ShowCreation(graph), - UpdateFromFunc( - water_dot, - lambda m: m.move_to(graph.get_end()) - ), - run_time=10, - rate_func=linear, - ) - self.wait() - - self.graph = graph - self.brace = brace - self.delta_T = delta_T - - def show_equation(self): - delta_T = self.delta_T - - equation = TexMobject( - "{d ({\\Delta T}) \\over dt} = -k \\cdot {\\Delta T}", - tex_to_color_map={ - "{\\Delta T}": self.delta_T_color, - "-k": WHITE, - "=": WHITE, - } - ) - equation.to_corner(UR) - equation.shift(LEFT) - - delta_T_parts = equation.get_parts_by_tex("\\Delta T") - eq_i = equation.index_of_part_by_tex("=") - deriv = equation[:eq_i] - prop_to = equation.get_part_by_tex("-k") - parts = VGroup(deriv, prop_to, delta_T_parts[1]) - - words = TextMobject( - "Rate of change", - "is proportional to", - "itself", - ) - words.scale(0.7) - words.next_to(equation, DOWN) - colors = [BLUE, WHITE, YELLOW] - for part, word, color in zip(parts, words, colors): - part.word = word - word.set_color(color) - word.save_state() - words[0].next_to(parts[0], DOWN) - - self.play( - TransformFromCopy( - VGroup(delta_T), - delta_T_parts, - ), - Write(VGroup(*filter( - lambda p: p not in delta_T_parts, - equation - ))) - ) - - rects = VGroup() - for part in parts: - rect = SurroundingRectangle( - part, - color=part.word.get_color(), - buff=SMALL_BUFF, - stroke_width=2, - ) - anims = [ - ShowCreation(rect), - FadeIn(part.word), - ] - if part is parts[1]: - anims.append(Restore(words[0])) - self.play(*anims) - rects.add(rect) - - self.play(FadeOut(rects, lag_ratio=0.2)) - - self.equation = equation - self.equation_words = words - - def talk_through_examples(self): - dot = self.water_dot - graph = self.graph - - self.play( - MoveAlongPath( - dot, graph, - rate_func=lambda t: smooth(1 - t), - run_time=2, - ) - ) - - # - def get_slope_line(self, graph, x): - pass diff --git a/from_3b1b/active/diffyq/part2/pi_scenes.py b/from_3b1b/active/diffyq/part2/pi_scenes.py deleted file mode 100644 index fa05ff4c..00000000 --- a/from_3b1b/active/diffyq/part2/pi_scenes.py +++ /dev/null @@ -1,142 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part2.wordy_scenes import WriteHeatEquationTemplate - - -class ReactionsToInitialHeatEquation(PiCreatureScene): - def construct(self): - randy = self.pi_creature - randy.set_color(BLUE_C) - randy.center() - - point = VectorizedPoint().next_to(randy, UL, LARGE_BUFF) - randy.add_updater(lambda r: r.look_at(point)) - - self.play(randy.change, "horrified") - self.wait() - self.play(randy.change, "pondering") - self.wait() - self.play( - randy.change, "confused", - point.next_to, randy, UR, LARGE_BUFF, - ) - self.wait(2) - self.play( - point.shift, 2 * DOWN, - randy.change, "horrified" - ) - self.wait(4) - - -class ContrastPDEToODE(TeacherStudentsScene): - CONFIG = { - "random_seed": 2, - } - - def construct(self): - student = self.students[2] - pde, ode = words = VGroup(*[ - TextMobject( - text + "\\\\", - "Differential\\\\", - "Equation" - ) - for text in ("Partial", "Ordinary") - ]) - pde[0].set_color(YELLOW) - ode[0].set_color(BLUE) - for word in words: - word.arrange(DOWN, aligned_edge=LEFT) - - words.arrange(RIGHT, buff=LARGE_BUFF) - words.next_to(student.get_corner(UR), UP, MED_LARGE_BUFF) - words.shift(UR) - lt = TexMobject("<") - lt.scale(1.5) - lt.move_to(Line(pde.get_right(), ode.get_left())) - - for pi in self.pi_creatures: - pi.add_updater(lambda p: p.look_at(pde)) - - self.play( - FadeInFromDown(VGroup(words, lt)), - student.change, "raise_right_hand", - ) - self.play( - self.get_student_changes("pondering", "pondering", "hooray"), - self.teacher.change, "happy" - ) - self.wait(3) - self.play( - Swap(ode, pde), - self.teacher.change, "raise_right_hand", - self.get_student_changes( - "erm", "sassy", "confused" - ) - ) - self.look_at(words) - self.change_student_modes( - "thinking", "thinking", "tease", - ) - self.wait(3) - - -class AskAboutWhereEquationComesFrom(TeacherStudentsScene, WriteHeatEquationTemplate): - def construct(self): - equation = self.get_d1_equation() - equation.move_to(self.hold_up_spot, DOWN) - - self.play( - FadeInFromDown(equation), - self.teacher.change, "raise_right_hand" - ) - self.student_says( - "Um...why?", - target_mode="sassy", - student_index=2, - bubble_kwargs={"direction": RIGHT}, - ) - self.change_student_modes( - "confused", "confused", "sassy", - ) - self.wait() - self.play( - self.teacher.change, "pondering", - ) - self.wait(2) - - -class AskWhyRewriteIt(TeacherStudentsScene): - def construct(self): - self.student_says( - "Why?", student_index=1, - bubble_kwargs={"height": 2, "width": 2}, - ) - self.students[1].bubble = None - self.teacher_says( - "One step closer\\\\to derivatives" - ) - self.change_student_modes( - "thinking", "thinking", "thinking", - look_at_arg=4 * LEFT + 2 * UP - ) - self.wait(2) - - -class ReferenceKhanVideo(TeacherStudentsScene): - def construct(self): - khan_logo = ImageMobject("KhanLogo") - khan_logo.set_height(1) - khan_logo.next_to(self.teacher, UP, buff=2) - khan_logo.shift(2 * LEFT) - - self.play( - self.teacher.change, "raise_right_hand", - ) - self.change_student_modes( - "thinking", "pondering", "thinking", - look_at_arg=self.screen - ) - self.wait() - self.play(FadeInFromDown(khan_logo)) - self.look_at(self.screen) - self.wait(15) diff --git a/from_3b1b/active/diffyq/part2/shared_constructs.py b/from_3b1b/active/diffyq/part2/shared_constructs.py deleted file mode 100644 index 9418145e..00000000 --- a/from_3b1b/active/diffyq/part2/shared_constructs.py +++ /dev/null @@ -1,35 +0,0 @@ -from manimlib.imports import * - -TIME_COLOR = YELLOW -X_COLOR = GREEN - - -def get_heat_equation(): - pass - - -def temperature_to_color(temp, min_temp=-1, max_temp=1): - colors = [BLUE, TEAL, GREEN, YELLOW, "#ff0000"] - - alpha = inverse_interpolate(min_temp, max_temp, temp) - index, sub_alpha = integer_interpolate( - 0, len(colors) - 1, alpha - ) - return interpolate_color( - colors[index], colors[index + 1], sub_alpha - ) - - -def two_d_temp_func(x, y, t): - return np.sum([ - c * np.sin(f * var) * np.exp(-(f**2) * t) - for c, f, var in [ - (0.2, 1, x), - (0.3, 3, x), - (0.02, 5, x), - (0.01, 7, x), - (0.5, 2, y), - (0.1, 10, y), - (0.01, 20, y), - ] - ]) diff --git a/from_3b1b/active/diffyq/part2/staging.py b/from_3b1b/active/diffyq/part2/staging.py deleted file mode 100644 index 234ecd32..00000000 --- a/from_3b1b/active/diffyq/part2/staging.py +++ /dev/null @@ -1,794 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part1.staging import TourOfDifferentialEquations - - -class PartTwoOfTour(TourOfDifferentialEquations): - CONFIG = { - "zoomed_thumbnail_index": 1, - } - - def construct(self): - self.add_title() - self.show_thumbnails() - self.zoom_in_to_one_thumbnail() - - def zoom_in_to_one_thumbnail(self): - frame = self.camera_frame - thumbnails = self.thumbnails - - ode = TextMobject("Ordinary\\\\", "Differential Equation") - pde = TextMobject("Partial\\\\", "Differential Equation") - for word, thumbnail, vect in zip([ode, pde], thumbnails, [DOWN, UP]): - word.match_width(thumbnail) - word.next_to(thumbnail, vect) - ode[0].set_color(BLUE) - pde[0].set_color(YELLOW) - - self.add(ode) - - frame.save_state() - self.play( - frame.replace, - thumbnails[0], - run_time=1, - ) - self.play( - Restore(frame, run_time=3), - ) - self.play( - TransformFromCopy(ode, pde), - ) - self.play( - ApplyMethod( - frame.replace, thumbnails[1], - path_arc=(-30 * DEGREES), - run_time=3 - ), - ) - self.wait() - - -class BrownianMotion(Scene): - CONFIG = { - "wait_time": 60, - "L": 3, # Box in [-L, L] x [-L, L] - "n_particles": 100, - "m1": 1, - "m2": 100, - "r1": 0.05, - "r2": 0.5, - "max_v": 5, - "random_seed": 2, - } - - def construct(self): - self.add_title() - self.add_particles() - self.wait(self.wait_time) - - def add_title(self): - square = Square(side_length=2 * self.L) - title = TextMobject("Brownian motion") - title.scale(1.5) - title.next_to(square, UP) - - self.add(square) - self.add(title) - - def add_particles(self): - m1 = self.m1 - m2 = self.m2 - r1 = self.r1 - r2 = self.r2 - L = self.L - max_v = self.max_v - n_particles = self.n_particles - - lil_particles = VGroup(*[ - self.get_particle(m1, r1, L, max_v) - for k in range(n_particles) - ]) - big_particle = self.get_particle(m2, r2, L=r2, max_v=0) - big_particle.set_fill(YELLOW, 1) - - for p in lil_particles: - if self.are_colliding(p, big_particle): - lil_particles.remove(p) - all_particles = VGroup(big_particle, *lil_particles) - all_particles.add_updater(self.update_particles) - - path = self.get_traced_path(big_particle) - - self.add(all_particles) - self.add(path) - - self.particles = all_particles - self.big_particle = big_particle - self.path = path - - def get_particle(self, m, r, L, max_v): - dot = Dot(radius=r) - dot.set_fill(WHITE, 0.7) - dot.mass = m - dot.radius = r - dot.center = op.add( - np.random.uniform(-L + r, L - r) * RIGHT, - np.random.uniform(-L + r, L - r) * UP - ) - dot.move_to(dot.center) - dot.velocity = rotate_vector( - np.random.uniform(0, max_v) * RIGHT, - np.random.uniform(0, TAU), - ) - return dot - - def are_colliding(self, p1, p2): - d = get_norm(p1.get_center() - p2.get_center()) - return (d < p1.radius + p2.radius) - - def get_traced_path(self, particle): - path = VMobject() - path.set_stroke(BLUE, 3) - path.start_new_path(particle.get_center()) - - buff = 0.02 - - def update_path(path): - new_point = particle.get_center() - if get_norm(new_point - path.get_last_point()) > buff: - path.add_line_to(new_point) - - path.add_updater(update_path) - return path - - def update_particles(self, particles, dt): - for p1 in particles: - p1.center += p1.velocity * dt - - # Check particle collisions - buff = 0.01 - for p2 in particles: - if p1 is p2: - continue - v = p2.center - p1.center - dist = get_norm(v) - r_sum = p1.radius + p2.radius - diff = dist - r_sum - if diff < 0: - unit_v = v / dist - p1.center += (diff - buff) * unit_v / 2 - p2.center += -(diff - buff) * unit_v / 2 - u1 = p1.velocity - u2 = p2.velocity - m1 = p1.mass - m2 = p2.mass - v1 = ( - (m2 * (u2 - u1) + m1 * u1 + m2 * u2) / - (m1 + m2) - ) - v2 = ( - (m1 * (u1 - u2) + m1 * u1 + m2 * u2) / - (m1 + m2) - ) - p1.velocity = v1 - p2.velocity = v2 - - # Check edge collisions - r1 = p1.radius - c1 = p1.center - for i in [0, 1]: - if abs(c1[i]) + r1 > self.L: - c1[i] = np.sign(c1[i]) * (self.L - r1) - p1.velocity[i] *= -1 * op.mul( - np.sign(p1.velocity[i]), - np.sign(c1[i]) - ) - - for p in particles: - p.move_to(p.center) - return particles - - -class AltBrownianMotion(BrownianMotion): - CONFIG = { - "wait_time": 20, - "n_particles": 100, - "m2": 10, - } - - -class BlackScholes(AltBrownianMotion): - def construct(self): - # For some reason I'm amused by the thought - # Of this graph perfectly matching the Brownian - # Motion y-coordiante - self.add_title() - self.add_particles() - self.particles.set_opacity(0) - self.remove(self.path) - self.add_graph() - self.wait(self.wait_time) - - def add_title(self): - title = TextMobject("Black-Scholes equations") - title.scale(1.5) - title.next_to(2 * UP, UP) - - equation = TexMobject( - "{\\partial V \\over \\partial t}", "+", - "\\frac{1}{2} \\sigma^2 S^2", - "{\\partial^2 V \\over \\partial S^2}", "+", - "rS", "{\\partial V \\over \\partial S}", - "-rV", "=", "0", - ) - equation.scale(0.8) - equation.next_to(title, DOWN) - - self.add(title) - self.add(equation) - self.title = title - self.equation = equation - - def add_graph(self): - axes = Axes( - x_min=-1, - x_max=20, - y_min=0, - y_max=10, - axis_config={ - "unit_size": 0.5, - }, - ) - axes.set_height(4) - axes.move_to(DOWN) - - def get_graph_point(): - return axes.c2p( - self.get_time(), - 5 + 2 * self.big_particle.get_center()[1] - ) - - graph = VMobject() - graph.match_style(self.path) - graph.start_new_path(get_graph_point()) - graph.add_updater( - lambda g: g.add_line_to(get_graph_point()) - ) - - self.add(axes) - self.add(graph) - - -class ContrastChapters1And2(Scene): - def construct(self): - c1_frame, c2_frame = frames = VGroup(*[ - ScreenRectangle(height=3.5) - for x in range(2) - ]) - frames.arrange(RIGHT, buff=LARGE_BUFF) - - c1_title, c2_title = titles = VGroup( - TextMobject("Chapter 1"), - TextMobject("Chapter 2"), - ) - titles.scale(1.5) - - ode, pde = des = VGroup( - TextMobject( - "Ordinary", - "Differential Equations\\\\", - "ODEs", - ), - TextMobject( - "Partial", - "Differential Equations\\\\", - "PDEs", - ), - ) - ode[0].set_color(BLUE) - pde[0].set_color(YELLOW) - for de in des: - de[-1][0].match_color(de[0]) - de[-1].scale(1.5, about_point=de.get_top()) - - for title, frame, de in zip(titles, frames, des): - title.next_to(frame, UP) - de.match_width(frame) - de.next_to(frame, DOWN) - - lt = TexMobject("<") - lt.move_to(Line(ode.get_right(), pde.get_left())) - lt.scale(2, about_edge=UP) - - c1_words = TextMobject( - "They're", "really\\\\", "{}", - "freaking", "hard\\\\", - "to", "solve\\\\", - ) - c1_words.set_height(0.5 * c1_frame.get_height()) - c1_words.move_to(c1_frame) - - c2_words = TextMobject( - "They're", "really", "\\emph{really}\\\\", - "freaking", "hard\\\\", - "to", "solve\\\\", - ) - c2_words.set_color_by_tex("\\emph", YELLOW) - c2_words.move_to(c2_frame) - edit_shift = MED_LARGE_BUFF * RIGHT - c2_edits = VGroup( - TextMobject("sometimes").next_to( - c2_words[1:3], UP, - aligned_edge=LEFT, - ), - Line( - c2_words[1].get_left(), - c2_words[2].get_right(), - stroke_width=8, - ), - TextMobject("not too").next_to( - c2_words[3], LEFT, - ), - Line( - c2_words[3].get_left(), - c2_words[3].get_right(), - stroke_width=8, - ), - ) - c2_edits.set_color(RED) - c2_edits[2:].shift(edit_shift) - - self.add(titles) - self.add(frames) - self.add(des) - - self.wait() - self.play(LaggedStartMap( - FadeInFromDown, c1_words, - lag_ratio=0.1, - )) - self.wait() - # self.play(FadeIn(ode)) - self.play( - # TransformFromCopy(ode, pde), - TransformFromCopy(c1_words, c2_words), - Write(lt) - ) - self.wait() - self.play( - Write(c2_edits[:2], run_time=1), - ) - self.play( - c2_words[3:5].shift, edit_shift, - Write(c2_edits[2:]), - run_time=1, - ) - self.wait() - - -class ShowCubeFormation(ThreeDScene): - CONFIG = { - "camera_config": { - "shading_factor": 1.0, - }, - "color": False, - } - - def construct(self): - light_source = self.camera.light_source - light_source.move_to(np.array([-6, -3, 6])) - - cube = Cube( - side_length=4, - fill_color=GREY, - stroke_color=WHITE, - stroke_width=0.5, - ) - cube.set_fill(opacity=1) - if self.color: - # cube[0].set_color(BLUE) - # cube[1].set_color(RED) - # for face in cube[2:]: - # face.set_color([BLUE, RED]) - cube.color_using_background_image("VerticalTempGradient") - - # light_source.next_to(cube, np.array([1, -1, 1]), buff=2) - - cube_3d = cube.copy() - cube_2d = cube_3d.copy().stretch(0, 2) - cube_1d = cube_2d.copy().stretch(0, 1) - cube_0d = cube_1d.copy().stretch(0, 0) - - cube.become(cube_0d) - - self.set_camera_orientation( - phi=70 * DEGREES, - theta=-145 * DEGREES, - ) - self.begin_ambient_camera_rotation(rate=0.05) - - for target in [cube_1d, cube_2d, cube_3d]: - self.play( - Transform(cube, target, run_time=1.5) - ) - self.wait(8) - - -class ShowCubeFormationWithColor(ShowCubeFormation): - CONFIG = { - "color": True, - } - - -class ShowRect(Scene): - CONFIG = { - "height": 1, - "width": 3, - } - - def construct(self): - rect = Rectangle( - height=self.height, - width=self.width, - ) - rect.set_color(YELLOW) - self.play(ShowCreationThenFadeOut(rect)) - - -class ShowSquare(ShowRect): - CONFIG = { - "height": 1, - "width": 1, - } - - -class ShowHLine(Scene): - def construct(self): - line = Line(LEFT, RIGHT) - line.set_color(BLUE) - self.play(ShowCreationThenFadeOut(line)) - - -class ShowCross(Scene): - def construct(self): - cross = Cross(Square()) - cross.set_width(3) - cross.set_height(1, stretch=True) - self.play(ShowCreation(cross)) - - -class TwoBodyEquations(Scene): - def construct(self): - kw = { - "tex_to_color_map": { - "x_1": LIGHT_GREY, - "y_1": LIGHT_GREY, - "x_2": BLUE, - "y_2": BLUE, - "=": WHITE, - } - } - equations = VGroup( - TexMobject( - "{d^2 x_1 \\over dt^2}", - "=", - "{x_2 - x_1 \\over m_1 \\left(", - "(x_2 - x_1)^2 + (y_2 - y_1)^2", - "\\right)^{3/2}", - **kw - ), - TexMobject( - "{d^2 y_1 \\over dt^2}", - "=", - "{y_2 - y_1 \\over m_1 \\left(", - "(x_2 - x_1)^2 + (y_2 - y_1)^2", - "\\right)^{3/2}", - **kw - ), - TexMobject( - "{d^2 x_2 \\over dt^2}", - "=", - "{x_1 - x_2 \\over m_2 \\left(", - "(x_2 - x_1)^2 + (y_2 - y_1)^2", - "\\right)^{3/2}", - **kw - ), - TexMobject( - "{d^2 y_2 \\over dt^2}", - "=", - "{y_1 - y_2 \\over m_2 \\left(", - "(x_2 - x_1)^2 + (y_2 - y_1)^2", - "\\right)^{3/2}", - **kw - ), - ) - - equations.arrange(DOWN, buff=LARGE_BUFF) - equations.set_height(6) - equations.to_edge(LEFT) - - variables = VGroup() - lhss = VGroup() - rhss = VGroup() - for equation in equations: - variable = equation[1] - lhs = equation[:4] - rhs = equation[4:] - variables.add(variable) - lhss.add(lhs) - rhss.add(rhs) - lhss_copy = lhss.copy() - - for variable, lhs in zip(variables, lhss): - variable.save_state() - variable.match_height(lhs) - variable.scale(0.7) - variable.move_to(lhs, LEFT) - - self.play(LaggedStart(*[ - FadeInFrom(v, RIGHT) - for v in variables - ])) - self.wait() - self.play( - LaggedStartMap(Restore, variables), - FadeIn( - lhss_copy, - remover=True, - lag_ratio=0.1, - run_time=2, - ) - ) - self.add(lhss) - self.wait() - self.play(LaggedStartMap( - FadeIn, rhss - )) - self.wait() - self.play( - LaggedStart(*[ - ShowCreationThenFadeAround(lhs[:3]) - for lhs in lhss - ]) - ) - self.wait() - self.play( - LaggedStartMap( - ShowCreationThenFadeAround, - rhss, - ) - ) - self.wait() - - -class LaplacianIntuition(SpecialThreeDScene): - CONFIG = { - "three_d_axes_config": { - "x_min": -5, - "x_max": 5, - "y_min": -5, - "y_max": 5, - }, - "surface_resolution": 32, - } - - def construct(self): - axes = self.get_axes() - axes.scale(0.5, about_point=ORIGIN) - self.set_camera_to_default_position() - self.begin_ambient_camera_rotation() - - def func(x, y): - return np.array([ - x, y, - 2.7 + 0.5 * (np.sin(x) + np.cos(y)) - - 0.025 * (x**2 + y**2) - ]) - - surface_config = { - "u_min": -5, - "u_max": 5, - "v_min": -5, - "v_max": 5, - "resolution": self.surface_resolution, - } - # plane = ParametricSurface( - # lambda u, v: np.array([u, v, 0]), - # **surface_config - # ) - # plane.set_stroke(WHITE, width=0.1) - # plane.set_fill(WHITE, opacity=0.1) - plane = Square( - side_length=10, - stroke_width=0, - fill_color=WHITE, - fill_opacity=0.1, - ) - plane.center() - plane.set_shade_in_3d(True) - - surface = ParametricSurface( - func, **surface_config - ) - surface.set_stroke(BLUE, width=0.1) - surface.set_fill(BLUE, opacity=0.25) - - self.add(axes, plane, surface) - - point = VectorizedPoint(np.array([2, -2, 0])) - dot = Dot() - dot.set_color(GREEN) - dot.add_updater(lambda d: d.move_to(point)) - line = always_redraw(lambda: DashedLine( - point.get_location(), - func(*point.get_location()[:2]), - background_image_file="VerticalTempGradient", - )) - - circle = Circle(radius=0.25) - circle.set_color(YELLOW) - circle.insert_n_curves(20) - circle.add_updater(lambda m: m.move_to(point)) - circle.set_shade_in_3d(True) - surface_circle = always_redraw( - lambda: circle.copy().apply_function( - lambda p: func(*p[:2]) - ).shift( - 0.02 * IN - ).color_using_background_image("VerticalTempGradient") - ) - - self.play(FadeInFromLarge(dot)) - self.play(ShowCreation(line)) - self.play(TransformFromCopy(dot, circle)) - self.play( - Transform( - circle.copy(), - surface_circle.copy().clear_updaters(), - remover=True, - ) - ) - self.add(surface_circle) - - self.wait() - for vect in [4 * LEFT, DOWN, 4 * RIGHT, UP]: - self.play( - point.shift, vect, - run_time=3, - ) - - -class StrogatzMention(PiCreatureScene): - def construct(self): - self.show_book() - self.show_motives() - self.show_pages() - - def show_book(self): - morty = self.pi_creature - book = ImageMobject("InfinitePowers") - book.set_height(5) - book.to_edge(LEFT) - - steve = ImageMobject("Strogatz_by_bricks") - steve.set_height(5) - steve.to_edge(LEFT) - - name = TextMobject("Steven Strogatz") - name.match_width(steve) - name.next_to(steve, DOWN) - - self.think( - "Hmm...many good\\\\lessons here...", - run_time=1 - ) - self.wait() - self.play(FadeInFromDown(steve)) - self.wait() - self.play( - FadeInFrom(book, DOWN), - steve.shift, 4 * RIGHT, - RemovePiCreatureBubble( - morty, target_mode="thinking" - ) - ) - self.wait(3) - self.play( - FadeOut(steve), - FadeOut(morty), - ) - - self.book = book - - def show_motives(self): - motives = VGroup( - TextMobject("1) Scratch and itch"), - TextMobject("2) Make people love math"), - ) - motives.scale(1.5) - motives.arrange( - DOWN, LARGE_BUFF, - aligned_edge=LEFT, - ) - motives.move_to( - Line( - self.book.get_right(), - FRAME_WIDTH * RIGHT / 2 - ) - ) - motives.to_edge(UP) - - for motive in motives: - self.play(FadeInFromDown(motive)) - self.wait(2) - self.play(FadeOut(motives)) - - def show_pages(self): - book = self.book - pages = Group(*[ - ImageMobject("IP_Sample_Page{}".format(i)) - for i in range(1, 4) - ]) - for page in pages: - page.match_height(book) - page.next_to(book, RIGHT) - - last_page = VectorizedPoint() - for page in pages: - self.play( - FadeOut(last_page), - FadeIn(page) - ) - self.wait() - last_page = page - - self.play(FadeOut(last_page)) - - def create_pi_creature(self): - return Mortimer().to_corner(DR) - - -class Thumbnail(Scene): - def construct(self): - image = ImageMobject("HeatSurfaceExampleFlipped") - image.set_height(6.5) - image.to_edge(DOWN, buff=-SMALL_BUFF) - self.add(image) - - equation = TexMobject( - "{\\partial {T} \\over \\partial {t}}", "=", - "\\alpha", "\\nabla^2 {T}", - tex_to_color_map={ - "{t}": YELLOW, - "{T}": RED, - } - ) - equation.scale(2) - equation.to_edge(UP) - - self.add(equation) - - Group(equation, image).shift(1.5 * RIGHT) - - question = TextMobject("What is\\\\this?") - question.scale(2.5) - question.to_edge(LEFT) - arrow = Arrow( - question.get_top(), - equation.get_left(), - buff=0.5, - path_arc=-90 * DEGREES, - ) - arrow.set_stroke(width=5) - - self.add(question, arrow) - - -class ShowNewton(Scene): - def construct(self): - pass - - -class ShowCupOfWater(Scene): - def construct(self): - pass diff --git a/from_3b1b/active/diffyq/part2/wordy_scenes.py b/from_3b1b/active/diffyq/part2/wordy_scenes.py deleted file mode 100644 index 861474ae..00000000 --- a/from_3b1b/active/diffyq/part2/wordy_scenes.py +++ /dev/null @@ -1,785 +0,0 @@ -from manimlib.imports import * - - -class WriteHeatEquationTemplate(Scene): - CONFIG = { - "tex_mobject_config": { - "tex_to_color_map": { - "{T}": WHITE, - "{t}": YELLOW, - "{x}": GREEN, - "{y}": RED, - "{z}": BLUE, - "\\partial": WHITE, - "2": WHITE, - }, - }, - } - - def get_d1_equation(self): - return TexMobject( - "{\\partial {T} \\over \\partial {t}}({x}, {t})", "=", - "\\alpha \\cdot", - "{\\partial^2 {T} \\over \\partial {x}^2} ({x}, {t})", - **self.tex_mobject_config - ) - - def get_d1_equation_without_inputs(self): - return TexMobject( - "{\\partial {T} \\over \\partial {t}}", "=", - "\\alpha \\cdot", - "{\\partial^2 {T} \\over \\partial {x}^2}", - **self.tex_mobject_config - ) - - def get_d3_equation(self): - return TexMobject( - "{\\partial {T} \\over \\partial {t}}", "=", - "\\alpha \\left(", - "{\\partial^2 {T} \\over \\partial {x}^2} + ", - "{\\partial^2 {T} \\over \\partial {y}^2} + ", - "{\\partial^2 {T} \\over \\partial {z}^2}", - "\\right)", - **self.tex_mobject_config - ) - - def get_general_equation(self): - return TexMobject( - "{\\partial {T} \\over \\partial {t}}", "=", - "\\alpha", "\\nabla^2 {T}", - **self.tex_mobject_config, - ) - - def get_d3_equation_with_inputs(self): - return TexMobject( - "{\\partial {T} \\over \\partial {t}}", - "({x}, {y}, {z}, {t})", "=", - "\\alpha \\left(", - "{\\partial^2 {T} \\over \\partial {x}^2}", - "({x}, {y}, {z}, {t}) + ", - "{\\partial^2 {T} \\over \\partial {y}^2}", - "({x}, {y}, {z}, {t}) + ", - "{\\partial^2 {T} \\over \\partial {z}^2}", - "({x}, {y}, {z}, {t})", - "\\right)", - **self.tex_mobject_config - ) - - def get_d1_words(self): - return TextMobject("Heat equation\\\\", "(1 dimension)") - - def get_d3_words(self): - return TextMobject("Heat equation\\\\", "(3 dimensions)") - - def get_d1_group(self): - group = VGroup( - self.get_d1_words(), - self.get_d1_equation(), - ) - group.arrange(DOWN, buff=MED_LARGE_BUFF) - return group - - def get_d3_group(self): - group = VGroup( - self.get_d3_words(), - self.get_d3_equation(), - ) - group.arrange(DOWN, buff=MED_LARGE_BUFF) - return group - - -class HeatEquationIntroTitle(WriteHeatEquationTemplate): - def construct(self): - scale_factor = 1.25 - title = TextMobject("The Heat Equation") - title.scale(scale_factor) - title.to_edge(UP) - - equation = self.get_general_equation() - equation.scale(scale_factor) - equation.next_to(title, DOWN, MED_LARGE_BUFF) - equation.set_color_by_tex("{T}", RED) - - self.play( - FadeInFrom(title, DOWN), - FadeInFrom(equation, UP), - ) - self.wait() - - -class BringTogether(Scene): - def construct(self): - arrows = VGroup(Vector(2 * RIGHT), Vector(2 * LEFT)) - arrows.arrange(RIGHT, buff=2) - words = TextMobject("Bring together")[0] - words.next_to(arrows, DOWN) - words.save_state() - words.space_out_submobjects(1.2) - - self.play( - VFadeIn(words), - Restore(words), - arrows.arrange, RIGHT, {"buff": SMALL_BUFF}, - VFadeIn(arrows), - ) - self.play(FadeOut(words), FadeOut(arrows)) - - -class FourierSeriesIntro(WriteHeatEquationTemplate): - def construct(self): - title_scale_value = 1.5 - - title = TextMobject( - "Fourier ", "Series", - ) - title.scale(title_scale_value) - title.to_edge(UP) - title.generate_target() - - details_coming = TextMobject("Details coming...") - details_coming.next_to(title.get_corner(DR), DOWN) - details_coming.set_color(LIGHT_GREY) - - # physics = TextMobject("Physics") - heat = TextMobject("Heat") - heat.scale(title_scale_value) - physics = self.get_general_equation() - physics.set_color_by_tex("{T}", RED) - arrow1 = Arrow(LEFT, RIGHT) - arrow2 = Arrow(LEFT, RIGHT) - group = VGroup( - heat, arrow1, physics, arrow2, title.target - ) - group.arrange(RIGHT) - # physics.align_to(title.target, UP) - group.to_edge(UP) - - rot_square = Square() - rot_square.fade(1) - rot_square.add_updater(lambda m, dt: m.rotate(dt)) - - def update_heat_colors(heat): - colors = [YELLOW, RED] - vertices = rot_square.get_vertices() - letters = heat.family_members_with_points() - for letter, vertex in zip(letters, vertices): - alpha = (normalize(vertex)[0] + 1) / 2 - i, sa = integer_interpolate(0, len(colors) - 1, alpha) - letter.set_color(interpolate_color( - colors[i], colors[i + 1], alpha, - )) - heat.add_updater(update_heat_colors) - - image = ImageMobject("Joseph Fourier") - image.set_height(5) - image.next_to(title, DOWN, LARGE_BUFF) - image.to_edge(LEFT) - name = TextMobject("Joseph", "Fourier") - name.next_to(image, DOWN) - - bubble = ThoughtBubble( - height=2, - width=2.5, - direction=RIGHT, - ) - bubble.set_fill(opacity=0) - bubble.set_stroke(WHITE) - bubble.set_stroke(BLACK, 5, background=True) - bubble.shift(heat.get_center() - bubble.get_bubble_center()) - bubble[:-1].shift(LEFT + 0.2 * DOWN) - bubble[:-1].rotate(-20 * DEGREES) - for mob in bubble[:-1]: - mob.rotate(20 * DEGREES) - - # self.play(FadeInFromDown(title)) - self.add(title) - self.play( - FadeInFromDown(image), - TransformFromCopy( - title.get_part_by_tex("Fourier"), - name.get_part_by_tex("Fourier"), - path_arc=90 * DEGREES, - ), - FadeIn(name.get_part_by_tex("Joseph")), - ) - self.play(Write(details_coming, run_time=1)) - self.play(LaggedStartMap(FadeOut, details_coming[0], run_time=1)) - self.wait() - self.add(rot_square) - self.play( - FadeInFrom(physics, RIGHT), - GrowArrow(arrow2), - FadeInFrom(heat, RIGHT), - GrowArrow(arrow1), - MoveToTarget(title), - ) - self.play(ShowCreation(bubble)) - self.wait(10) - - -class CompareODEToPDE(Scene): - def construct(self): - pass - - -class TodaysTargetWrapper(Scene): - def construct(self): - pass - - -class TwoGraphTypeTitles(Scene): - def construct(self): - left_title = TextMobject( - "Represent time\\\\with actual time" - ) - left_title.shift(FRAME_WIDTH * LEFT / 4) - right_title = TextMobject( - "Represent time\\\\with an axis" - ) - right_title.shift(FRAME_WIDTH * RIGHT / 4) - - titles = VGroup(left_title, right_title) - for title in titles: - title.scale(1.25) - title.to_edge(UP) - - self.play(FadeInFromDown(right_title)) - self.wait() - self.play(FadeInFromDown(left_title)) - self.wait() - - -class ShowPartialDerivativeSymbols(Scene): - def construct(self): - t2c = { - "{x}": GREEN, - "{t}": YELLOW, - } - d_derivs, del_derivs = VGroup(*[ - VGroup(*[ - TexMobject( - "{" + sym, "T", "\\over", sym, var + "}", - "(", "{x}", ",", "{t}", ")", - ).set_color_by_tex_to_color_map(t2c) - for var in ("{x}", "{t}") - ]) - for sym in ("d", "\\partial") - ]) - dTdx, dTdt = d_derivs - delTdelx, delTdelx = del_derivs - dels = VGroup(*it.chain(*[ - del_deriv.get_parts_by_tex("\\partial") - for del_deriv in del_derivs - ])) - - dTdx.to_edge(UP) - self.play(FadeInFrom(dTdx, DOWN)) - self.wait() - self.play(ShowCreationThenFadeAround(dTdx[3:5])) - self.play(ShowCreationThenFadeAround(dTdx[:2])) - self.wait() - - dTdt.move_to(dTdx) - self.play( - dTdx.next_to, dTdt, RIGHT, {"buff": 1.5}, - dTdx.set_opacity, 0.5, - FadeInFromDown(dTdt) - ) - self.wait() - - for m1, m2 in zip(d_derivs, del_derivs): - m2.move_to(m1) - - pd_words = TextMobject("Partial derivatives") - pd_words.next_to(del_derivs, DOWN, MED_LARGE_BUFF) - - self.play( - Write(pd_words), - dTdx.set_opacity, 1, - run_time=1, - ) - self.wait() - self.play( - ReplacementTransform(d_derivs, del_derivs) - ) - self.play( - LaggedStartMap( - ShowCreationThenFadeAround, - dels, - surrounding_rectangle_config={ - "color": BLUE, - "buff": 0.5 * SMALL_BUFF, - "stroke_width": 2, - } - ) - ) - self.wait() - - num_words = VGroup(*[ - TextMobject( - "Change in $T$\\\\caused by {}", - "$\\partial$", "${}$".format(var), - arg_separator="", - ).set_color_by_tex_to_color_map(t2c) - for var in ("{x}", "{t}") - ]) - num_words.scale(0.8) - for word, deriv in zip(num_words, del_derivs): - num = deriv[:2] - word.move_to(num, UP) - word.to_edge(UP, buff=MED_SMALL_BUFF) - deriv.rect = SurroundingRectangle( - num, - buff=SMALL_BUFF, - stroke_width=2, - color=word[-1].get_color(), - ) - deriv.rect.mob = num - deriv.rect.add_updater(lambda r: r.move_to(r.mob)) - - self.play( - Write(num_words[1]), - VGroup(del_derivs, pd_words).shift, DOWN, - ShowCreation(del_derivs[1].rect), - ) - self.play( - Write(num_words[0]), - ShowCreation(del_derivs[0].rect), - ) - self.wait() - - -class WriteHeatEquation(WriteHeatEquationTemplate): - def construct(self): - title = TextMobject("The Heat Equation") - title.to_edge(UP) - - equation = self.get_d1_equation() - equation.next_to(title, DOWN) - - eq_i = equation.index_of_part_by_tex("=") - dt_part = equation[:eq_i] - dx_part = equation[eq_i + 3:] - dt_rect = SurroundingRectangle(dt_part) - dt_rect.set_stroke(YELLOW, 2) - dx_rect = SurroundingRectangle(dx_part) - dx_rect.set_stroke(GREEN, 2) - - two_outlines = equation.get_parts_by_tex("2").copy() - two_outlines.set_stroke(YELLOW, 2) - two_outlines.set_fill(opacity=0) - - to_be_explained = TextMobject( - "To be explained shortly..." - ) - to_be_explained.scale(0.7) - to_be_explained.next_to(equation, RIGHT, MED_LARGE_BUFF) - to_be_explained.fade(1) - - pde = TextMobject("Partial Differential Equation") - pde.move_to(title) - - del_outlines = equation.get_parts_by_tex("\\partial").copy() - del_outlines.set_stroke(YELLOW, 2) - del_outlines.set_fill(opacity=0) - - self.play( - FadeInFrom(title, 0.5 * DOWN), - FadeInFrom(equation, 0.5 * UP), - ) - self.wait() - self.play(ShowCreation(dt_rect)) - self.wait() - self.play(TransformFromCopy(dt_rect, dx_rect)) - self.play(ShowCreationThenDestruction(two_outlines)) - self.wait() - self.play(Write(to_be_explained, run_time=1)) - self.wait(2) - self.play( - ShowCreationThenDestruction( - del_outlines, - lag_ratio=0.1, - ) - ) - self.play( - FadeOutAndShift(title, UP), - FadeInFrom(pde, DOWN), - FadeOut(dt_rect), - FadeOut(dx_rect), - ) - self.wait() - - -class Show3DEquation(WriteHeatEquationTemplate): - def construct(self): - equation = self.get_d3_equation_with_inputs() - equation.set_width(FRAME_WIDTH - 1) - inputs = VGroup(*it.chain(*[ - equation.get_parts_by_tex(s) - for s in ["{x}", "{y}", "{z}", "{t}"] - ])) - inputs.sort() - equation.to_edge(UP) - - self.add(equation) - self.play(LaggedStartMap( - ShowCreationThenFadeAround, inputs, - surrounding_rectangle_config={ - "buff": 0.05, - "stroke_width": 2, - } - )) - self.wait() - - -class Show1DAnd3DEquations(WriteHeatEquationTemplate): - def construct(self): - d1_group = self.get_d1_group() - d3_group = self.get_d3_group() - d1_words, d1_equation = d1_group - d3_words, d3_equation = d3_group - - groups = VGroup(d1_group, d3_group) - for group in groups: - group.arrange(DOWN, buff=MED_LARGE_BUFF) - groups.arrange(RIGHT, buff=1.5) - groups.to_edge(UP) - - d3_rhs = d3_equation[9:-2] - d3_brace = Brace(d3_rhs, DOWN) - nabla_words = TextMobject("Sometimes written as") - nabla_words.match_width(d3_brace) - nabla_words.next_to(d3_brace, DOWN) - nabla_exp = TexMobject( - "\\nabla^2 {T}", - **self.tex_mobject_config, - ) - nabla_exp.next_to(nabla_words, DOWN) - # nabla_group = VGroup(nabla_words, nabla_exp) - - d1_group.save_state() - d1_group.center().to_edge(UP) - - self.play( - Write(d1_words), - FadeInFrom(d1_equation, UP), - run_time=1, - ) - self.wait(2) - self.play( - Restore(d1_group), - FadeInFrom(d3_group, LEFT) - ) - self.wait() - self.play( - GrowFromCenter(d3_brace), - Write(nabla_words), - TransformFromCopy(d3_rhs, nabla_exp), - run_time=1, - ) - self.wait() - - -class D1EquationNoInputs(WriteHeatEquationTemplate): - def construct(self): - equation = self.get_d1_equation_without_inputs() - equation.to_edge(UP) - # i1 = equation.index_of_part_by_tex("\\partial") - # i2 = equation.index_of_part_by_tex("\\cdot") - # equation[i1:i1 + 2].set_color(RED) - # equation[i2 + 1:i2 + 6].set_color(RED) - equation.set_color_by_tex("{T}", RED) - self.add(equation) - - -class AltHeatRHS(Scene): - def construct(self): - formula = TexMobject( - "{\\alpha \\over 2}", "\\Big(", - "T({x} - 1, {t}) + T({x} + 1, {t})" - "\\Big)", - tex_to_color_map={ - "{x}": GREEN, - "{t}": YELLOW, - } - ) - self.add(formula) - - -class CompareInputsOfGeneralCaseTo1D(WriteHeatEquation): - def construct(self): - three_d_expr, one_d_expr = [ - TexMobject( - "{T}(" + inputs + ", {t})", - **self.tex_mobject_config, - ) - for inputs in ["{x}, {y}, {z}", "{x}"] - ] - for expr in three_d_expr, one_d_expr: - expr.scale(2) - expr.to_edge(UP) - - x, y, z = [ - three_d_expr.get_part_by_tex(letter) - for letter in ["x", "y", "z"] - ] - - self.play(FadeInFromDown(three_d_expr)) - self.play(LaggedStartMap( - ShowCreationThenFadeAround, - VGroup(x, y, z) - )) - self.wait() - low = 3 - high = -3 - self.play( - ReplacementTransform(three_d_expr[:low], one_d_expr[:low]), - ReplacementTransform(three_d_expr[high:], one_d_expr[high:]), - three_d_expr[low:high].scale, 0, - ) - self.wait() - - -class ShowLaplacian(WriteHeatEquation): - def construct(self): - equation = self.get_d3_equation() - equation.to_edge(UP, buff=MED_SMALL_BUFF) - - parts = VGroup() - plusses = VGroup() - for char in "xyz": - index = equation.index_of_part_by_tex( - "{" + char + "}" - ) - part = equation[index - 6:index + 3] - rect = SurroundingRectangle(part) - rect.match_color(equation[index]) - parts.add(part) - part.rect = rect - if char in "yz": - plus = equation[index - 8] - part.plus = plus - plusses.add(plus) - - lp = equation.get_part_by_tex("(") - rp = equation.get_part_by_tex(")") - - for part in parts: - part.rp = rp.copy() - part.rp.next_to(part, RIGHT, SMALL_BUFF) - part.rp.align_to(lp, UP) - rp.become(parts[0].rp) - - # Show new second derivatives - self.add(*equation) - self.remove(*plusses, *parts[1], *parts[2]) - for part in parts[1:]: - self.play( - rp.become, part.rp, - FadeInFrom(part, LEFT), - Write(part.plus), - ShowCreation(part.rect), - ) - self.play( - FadeOut(part.rect), - ) - self.wait() - - # Show laplacian - brace = Brace(parts, DOWN) - laplacian = TexMobject("\\nabla^2", "T") - laplacian.next_to(brace, DOWN) - laplacian_name = TextMobject( - "``Laplacian''" - ) - laplacian_name.next_to(laplacian, DOWN) - - T_parts = VGroup(*[part[3] for part in parts]) - non_T_parts = VGroup(*[ - VGroup(*part[:3], *part[4:]) - for part in parts - ]) - - self.play(GrowFromCenter(brace)) - self.play(Write(laplacian_name)) - self.play( - TransformFromCopy(non_T_parts, laplacian[0]) - ) - self.play( - TransformFromCopy(T_parts, laplacian[1]) - ) - self.wait(3) - - -class AskAboutActuallySolving(WriteHeatEquationTemplate): - def construct(self): - equation = self.get_d1_equation() - equation.center() - - q1 = TextMobject("Solve for T?") - q1.next_to(equation, UP, LARGE_BUFF) - q2 = TextMobject("What does it \\emph{mean} to solve this?") - q2.next_to(equation, UP, LARGE_BUFF) - formula = TexMobject( - "T({x}, {t}) = \\sin\\big(a{x}\\big) e^{-\\alpha \\cdot a^2 {t}}", - tex_to_color_map={ - "{x}": GREEN, - "{t}": YELLOW, - } - ) - formula.next_to(equation, DOWN, LARGE_BUFF) - q3 = TextMobject("Is this it?") - arrow = Vector(LEFT, color=WHITE) - arrow.next_to(formula, RIGHT) - q3.next_to(arrow, RIGHT) - - self.add(equation) - self.play(FadeInFromDown(q1)) - self.wait() - self.play( - FadeInFromDown(q2), - q1.shift, 1.5 * UP, - ) - self.play(FadeInFrom(formula, UP)) - self.play( - GrowArrow(arrow), - FadeInFrom(q3, LEFT) - ) - self.wait() - - -class PDEPatreonEndscreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "Juan Benet", - "Vassili Philippov", - "Burt Humburg", - "Matt Russell", - "Scott Gray", - "soekul", - "Tihan Seale", - "Richard Barthel", - "Ali Yahya", - "dave nicponski", - "Evan Phillips", - "Graham", - "Joseph Kelly", - "Kaustuv DeBiswas", - "LambdaLabs", - "Lukas Biewald", - "Mike Coleman", - "Peter Mcinerney", - "Quantopian", - "Roy Larson", - "Scott Walter, Ph.D.", - "Yana Chernobilsky", - "Yu Jun", - "Jordan Scales", - "D. Sivakumar", - "Lukas -krtek.net- Novy", - "John Shaughnessy", - "Britt Selvitelle", - "David Gow", - "J", - "Jonathan Wilson", - "Joseph John Cox", - "Magnus Dahlström", - "Randy C. Will", - "Ryan Atallah", - "Luc Ritchie", - "1stViewMaths", - "Adrian Robinson", - "Alexis Olson", - "Andreas Benjamin Brössel", - "Andrew Busey", - "Ankalagon", - "Antoine Bruguier", - "Antonio Juarez", - "Arjun Chakroborty", - "Art Ianuzzi", - "Awoo", - "Bernd Sing", - "Boris Veselinovich", - "Brian Staroselsky", - "Chad Hurst", - "Charles Southerland", - "Chris Connett", - "Christian Kaiser", - "Clark Gaebel", - "Cooper Jones", - "Danger Dai", - "Dave B", - "Dave Kester", - "David B. Hill", - "David Clark", - "DeathByShrimp", - "Delton Ding", - "eaglle", - "emptymachine", - "Eric Younge", - "Eryq Ouithaqueue", - "Federico Lebron", - "Giovanni Filippi", - "Hal Hildebrand", - "Hitoshi Yamauchi", - "Isaac Jeffrey Lee", - "j eduardo perez", - "Jacob Magnuson", - "Jameel Syed", - "Jason Hise", - "Jeff Linse", - "Jeff Straathof", - "John Griffith", - "John Haley", - "John V Wertheim", - "Jonathan Eppele", - "Kai-Siang Ang", - "Kanan Gill", - "L0j1k", - "Lee Beck", - "Lee Redden", - "Linh Tran", - "Ludwig Schubert", - "Magister Mugit", - "Mark B Bahu", - "Mark Heising", - "Martin Price", - "Mathias Jansson", - "Matt Langford", - "Matt Roveto", - "Matthew Bouchard", - "Matthew Cocke", - "Michael Faust", - "Michael Hardel", - "Mirik Gogri", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nero Li", - "Nikita Lesnikov", - "Omar Zrien", - "Owen Campbell-Moore", - "Peter Ehrnstrom", - "RedAgent14", - "rehmi post", - "Richard Burgmann", - "Richard Comish", - "Ripta Pasay", - "Rish Kundalia", - "Robert Teed", - "Roobie", - "Ryan Williams", - "Sachit Nagpal", - "Solara570", - "Stevie Metke", - "Tal Einav", - "Ted Suzman", - "Thomas Tarler", - "Tom Fleming", - "Valeriy Skobelev", - "Xavier Bernard", - "Yavor Ivanov", - "Yaw Etse", - "YinYangBalance.Asia", - "Zach Cardwell", - ], - } diff --git a/from_3b1b/active/diffyq/part3/discrete_case.py b/from_3b1b/active/diffyq/part3/discrete_case.py deleted file mode 100644 index e79e13ae..00000000 --- a/from_3b1b/active/diffyq/part3/discrete_case.py +++ /dev/null @@ -1,303 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part2.heat_equation import * - - -class ShowNewRuleAtDiscreteBoundary(DiscreteSetup): - CONFIG = { - "axes_config": { - "x_min": 0, - "stroke_width": 1, - "x_axis_config": { - "include_tip": False, - }, - }, - "freq_amplitude_pairs": [ - (1, 0.5), - (2, 1), - (3, 0.5), - (4, 0.3), - ], - "v_line_class": DashedLine, - "v_line_config": { - - }, - "step_size": 1, - "wait_time": 15, - "alpha": 0.25, - } - - def construct(self): - self.add_axes() - self.set_points() - self.show_boundary_point_influenced_by_neighbor() - self.add_clock() - self.let_evolve() - - def set_points(self): - axes = self.axes - for mob in axes.family_members_with_points(): - if isinstance(mob, Line): - mob.set_stroke(width=1) - - step_size = self.step_size - xs = np.arange( - axes.x_min, - axes.x_max + step_size, - step_size - ) - - dots = self.dots = self.get_dots(axes, xs) - self.v_lines = self.get_v_lines(dots) - self.rod_pieces = self.get_rod_pieces(dots) - - # rod_pieces - - self.add(self.dots) - self.add(self.v_lines) - self.add(self.rod_pieces) - - def show_boundary_point_influenced_by_neighbor(self): - dots = self.dots - ld = dots[0] - ld_in = dots[1] - rd = dots[-1] - rd_in = dots[-2] - v_len = 0.75 - l_arrow = Vector(v_len * LEFT) - l_arrow.move_to(ld.get_left(), RIGHT) - r_arrow = Vector(v_len * RIGHT) - r_arrow.move_to(rd.get_right(), LEFT) - arrows = VGroup(l_arrow, r_arrow) - q_marks = VGroup(*[ - TexMobject("?").scale(1.5).next_to( - arrow, arrow.get_vector() - ) - for arrow in arrows - ]) - - arrows.set_color(YELLOW) - q_marks.set_color(YELLOW) - - blocking_rects = VGroup(*[ - BackgroundRectangle(VGroup( - *dots[i:-i], - *self.rod_pieces[i:-i] - )) - for i in [1, 2] - ]) - for rect in blocking_rects: - rect.stretch(1.1, dim=1, about_edge=UP) - - self.play(FadeIn(blocking_rects[0])) - self.play( - LaggedStartMap(ShowCreation, arrows), - LaggedStart(*[ - FadeInFrom(q_mark, -arrow.get_vector()) - for q_mark, arrow in zip(q_marks, arrows) - ]), - run_time=1.5 - ) - self.wait() - - # Point to inward neighbor - new_arrows = VGroup(*[ - Arrow( - d1.get_center(), - VGroup(d1, d2).get_center(), - buff=0, - ).match_style(l_arrow) - for d1, d2 in [(ld, ld_in), (rd, rd_in)] - ]) - new_arrows.match_style(arrows) - - l_brace = Brace(VGroup(ld, ld_in), DOWN) - r_brace = Brace(VGroup(rd, rd_in), DOWN) - braces = VGroup(l_brace, r_brace) - for brace in braces: - brace.align_to( - self.axes.x_axis.get_center(), UP - ) - brace.shift(SMALL_BUFF * DOWN) - brace.add(brace.get_tex("\\Delta x")) - - self.play( - ReplacementTransform(arrows, new_arrows), - FadeOut(q_marks), - ReplacementTransform(*blocking_rects) - ) - self.wait() - self.play(FadeInFrom(braces, UP)) - self.wait() - self.play( - FadeOut(new_arrows), - FadeOut(blocking_rects[1]), - FadeOut(braces), - ) - - def add_clock(self): - super().add_clock() - self.time_label.add_updater( - lambda d, dt: d.increment_value(dt) - ) - VGroup( - self.clock, - self.time_label - ).shift(2 * LEFT) - - def let_evolve(self): - dots = self.dots - dots.add_updater(self.update_dots) - - wait_time = self.wait_time - self.play( - ClockPassesTime( - self.clock, - run_time=wait_time, - hours_passed=wait_time, - ), - ) - - # - - def get_dots(self, axes, xs): - dots = VGroup(*[ - Dot(axes.c2p(x, self.temp_func(x, 0))) - for x in xs - ]) - - max_width = 0.8 * self.step_size - for dot in dots: - dot.add_updater(self.update_dot_color) - if dot.get_width() > max_width: - dot.set_width(max_width) - - return dots - - def get_v_lines(self, dots): - return always_redraw(lambda: VGroup(*[ - self.get_v_line(dot) - for dot in dots - ])) - - def get_v_line(self, dot): - x_axis = self.axes.x_axis - bottom = dot.get_bottom() - x = x_axis.p2n(bottom) - proj_point = x_axis.n2p(x) - return self.v_line_class( - proj_point, bottom, - **self.v_line_config, - ) - - def get_rod_pieces(self, dots): - axis = self.axes.x_axis - factor = 1 - np.exp(-(0.8 / self.step_size)**2) - width = factor * self.step_size - - pieces = VGroup() - for dot in dots: - piece = Line(ORIGIN, width * RIGHT) - piece.set_stroke(width=5) - piece.move_to(dot) - piece.set_y(axis.get_center()[1]) - piece.dot = dot - piece.add_updater( - lambda p: p.match_color(p.dot) - ) - pieces.add(piece) - return pieces - - def update_dot_color(self, dot): - y = self.axes.y_axis.p2n(dot.get_center()) - dot.set_color(self.y_to_color(y)) - - def update_dots(self, dots, dt): - for ds in zip(dots, dots[1:], dots[2:]): - points = [d.get_center() for d in ds] - x0, x1, x2 = [p[0] for p in points] - dx = x1 - x0 - y0, y1, y2 = [p[1] for p in points] - - self.update_dot( - dot=ds[1], - dt=dt, - mean_diff=0.5 * (y2 - 2 * y1 + y0) / dx - ) - if ds[0] is dots[0]: - self.update_dot( - dot=ds[0], - dt=dt, - mean_diff=(y1 - y0) / dx - ) - elif ds[-1] is dots[-1]: - self.update_dot( - dot=ds[-1], - dt=dt, - mean_diff=(y1 - y2) / dx - ) - - def update_dot(self, dot, dt, mean_diff): - dot.shift(mean_diff * self.alpha * dt * UP) - - -class DiscreteEvolutionPoint25(ShowNewRuleAtDiscreteBoundary): - CONFIG = { - "step_size": 0.25, - "alpha": 0.5, - "wait_time": 30, - } - - def construct(self): - self.add_axes() - self.set_points() - self.add_clock() - self.let_evolve() - - -class DiscreteEvolutionPoint1(DiscreteEvolutionPoint25): - CONFIG = { - "step_size": 0.1, - "v_line_config": { - "stroke_width": 1, - }, - "wait_time": 30, - } - - -class FlatEdgesForDiscreteEvolution(DiscreteEvolutionPoint1): - CONFIG = { - "wait_time": 20, - "step_size": 0.1, - } - - def let_evolve(self): - lines = VGroup(*[ - Line(LEFT, RIGHT) - for x in range(2) - ]) - lines.set_width(1.5) - lines.set_stroke(WHITE, 5, opacity=0.5) - lines.add_updater(self.update_lines) - - turn_animation_into_updater( - ShowCreation(lines, run_time=2) - ) - self.add(lines) - - super().let_evolve() - - def update_lines(self, lines): - dots = self.dots - for line, dot in zip(lines, [dots[0], dots[-1]]): - line.move_to(dot) - - -class FlatEdgesForDiscreteEvolutionTinySteps(FlatEdgesForDiscreteEvolution): - CONFIG = { - "step_size": 0.025, - "wait_time": 10, - "v_line_class": Line, - "v_line_config": { - "stroke_opacity": 0.5, - } - } diff --git a/from_3b1b/active/diffyq/part3/pi_creature_scenes.py b/from_3b1b/active/diffyq/part3/pi_creature_scenes.py deleted file mode 100644 index 7183fba9..00000000 --- a/from_3b1b/active/diffyq/part3/pi_creature_scenes.py +++ /dev/null @@ -1,175 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part2.wordy_scenes import * - - -class IveHeardOfThis(TeacherStudentsScene): - def construct(self): - point = VectorizedPoint() - point.move_to(3 * RIGHT + 2 * UP) - self.student_says( - "I've heard\\\\", "of this!", - student_index=1, - target_mode="hooray", - bubble_kwargs={ - "height": 3, - "width": 3, - "direction": RIGHT, - }, - run_time=1, - ) - self.change_student_modes( - "thinking", "hooray", "thinking", - look_at_arg=point, - added_anims=[self.teacher.change, "happy"] - ) - self.wait(3) - self.student_says( - "But who\\\\", "cares?", - student_index=1, - target_mode="maybe", - bubble_kwargs={ - "direction": RIGHT, - "width": 3, - "height": 3, - }, - run_time=1, - ) - self.change_student_modes( - "pondering", "maybe", "pondering", - look_at_arg=point, - added_anims=[self.teacher.change, "guilty"] - ) - self.wait(5) - - -class InFouriersShoes(PiCreatureScene, WriteHeatEquationTemplate): - def construct(self): - randy = self.pi_creature - fourier = ImageMobject("Joseph Fourier") - fourier.set_height(4) - fourier.next_to(randy, RIGHT, LARGE_BUFF) - fourier.align_to(randy, DOWN) - - equation = self.get_d1_equation() - equation.next_to(fourier, UP, MED_LARGE_BUFF) - - decades = list(range(1740, 2040, 20)) - time_line = NumberLine( - x_min=decades[0], - x_max=decades[-1], - tick_frequency=1, - tick_size=0.05, - longer_tick_multiple=4, - unit_size=0.2, - numbers_with_elongated_ticks=decades, - numbers_to_show=decades, - decimal_number_config={ - "group_with_commas": False, - }, - stroke_width=2, - ) - time_line.add_numbers() - time_line.move_to(ORIGIN, RIGHT) - time_line.to_edge(UP) - triangle = ArrowTip(start_angle=-90 * DEGREES) - triangle.set_height(0.25) - triangle.move_to(time_line.n2p(2019), DOWN) - triangle.set_color(WHITE) - - self.play(FadeInFrom(fourier, 2 * LEFT)) - self.play(randy.change, "pondering") - self.wait() - self.play( - DrawBorderThenFill(triangle, run_time=1), - FadeInFromDown(equation), - FadeIn(time_line), - ) - self.play( - Animation(triangle), - ApplyMethod( - time_line.shift, - time_line.n2p(2019) - time_line.n2p(1822), - run_time=5 - ), - ) - self.wait() - - -class SineCurveIsUnrealistic(TeacherStudentsScene): - def construct(self): - self.student_says( - "But that would\\\\never happen!", - student_index=1, - bubble_kwargs={ - "direction": RIGHT, - "height": 3, - "width": 4, - }, - target_mode="angry" - ) - self.change_student_modes( - "guilty", "angry", "hesitant", - added_anims=[ - self.teacher.change, "tease" - ] - ) - self.wait(3) - self.play( - RemovePiCreatureBubble(self.students[1]), - self.teacher.change, "raise_right_hand" - ) - self.change_all_student_modes( - "pondering", - look_at_arg=3 * UP, - ) - self.wait(5) - - -class IfOnly(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "If only!", - target_mode="angry" - ) - self.change_all_student_modes( - "confused", - look_at_arg=self.screen - ) - self.wait(3) - - -class SoWeGotNowhere(TeacherStudentsScene): - def construct(self): - self.student_says( - "So we've gotten\\\\nowhere!", - target_mode="angry", - added_anims=[ - self.teacher.change, "guilty" - ] - ) - self.change_all_student_modes("angry") - self.wait() - text = TexMobject( - "&\\text{Actually,}\\\\", - "&\\sin\\left({x}\\right)" - "e^{-\\alpha {t}}\\\\", - "&\\text{isn't far off.}", - tex_to_color_map={ - "{x}": GREEN, - "{t}": YELLOW, - } - ) - text.scale(0.8) - self.teacher_says( - text, - content_introduction_class=FadeIn, - bubble_kwargs={ - "width": 4, - "height": 3.5, - } - ) - self.change_all_student_modes( - "pondering", - look_at_arg=self.screen - ) - self.wait(3) diff --git a/from_3b1b/active/diffyq/part3/staging.py b/from_3b1b/active/diffyq/part3/staging.py deleted file mode 100644 index fc917a49..00000000 --- a/from_3b1b/active/diffyq/part3/staging.py +++ /dev/null @@ -1,1243 +0,0 @@ -from manimlib.imports import * - -from active_projects.diffyq.part2.fourier_series import FourierOfTrebleClef - - -class FourierNameIntro(Scene): - def construct(self): - self.show_two_titles() - self.transition_to_image() - self.show_paper() - - def show_two_titles(self): - lt = TextMobject("Fourier", "Series") - rt = TextMobject("Fourier", "Transform") - lt_variants = VGroup( - TextMobject("Complex", "Fourier Series"), - TextMobject("Discrete", "Fourier Series"), - ) - rt_variants = VGroup( - TextMobject("Discrete", "Fourier Transform"), - TextMobject("Fast", "Fourier Transform"), - TextMobject("Quantum", "Fourier Transform"), - ) - - titles = VGroup(lt, rt) - titles.scale(1.5) - for title, vect in (lt, LEFT), (rt, RIGHT): - title.move_to(vect * FRAME_WIDTH / 4) - title.to_edge(UP) - - for title, variants in (lt, lt_variants), (rt, rt_variants): - title.save_state() - title.target = title.copy() - title.target.scale(1 / 1.5, about_edge=RIGHT) - for variant in variants: - variant.move_to(title.target, UR) - variant[0].set_color(YELLOW) - - v_line = Line(UP, DOWN) - v_line.set_height(FRAME_HEIGHT) - v_line.set_stroke(WHITE, 2) - - self.play( - FadeInFrom(lt, RIGHT), - ShowCreation(v_line) - ) - self.play( - FadeInFrom(rt, LEFT), - ) - # Edit in images of circle animations - # and clips from FT video - - # for title, variants in (rt, rt_variants), (lt, lt_variants): - for title, variants in [(rt, rt_variants)]: - # Maybe do it for left variant, maybe not... - self.play( - MoveToTarget(title), - FadeInFrom(variants[0][0], LEFT) - ) - for v1, v2 in zip(variants, variants[1:]): - self.play( - FadeOutAndShift(v1[0], UP), - FadeInFrom(v2[0], DOWN), - run_time=0.5, - ) - self.wait(0.5) - self.play( - Restore(title), - FadeOut(variants[-1][0]) - ) - self.wait() - - self.titles = titles - self.v_line = v_line - - def transition_to_image(self): - titles = self.titles - v_line = self.v_line - - image = ImageMobject("Joseph Fourier") - image.set_height(5) - image.to_edge(LEFT) - - frame = Rectangle() - frame.replace(image, stretch=True) - - name = TextMobject("Joseph", "Fourier") - fourier_part = name.get_part_by_tex("Fourier") - fourier_part.set_color(YELLOW) - F_sym = fourier_part[0] - name.match_width(image) - name.next_to(image, DOWN) - - self.play( - ReplacementTransform(v_line, frame), - FadeIn(image), - FadeIn(name[0]), - *[ - ReplacementTransform( - title[0].deepcopy(), - name[1] - ) - for title in titles - ], - titles.scale, 0.65, - titles.arrange, DOWN, - titles.next_to, image, UP, - ) - self.wait() - - big_F = F_sym.copy() - big_F.set_fill(opacity=0) - big_F.set_stroke(WHITE, 2) - big_F.set_height(3) - big_F.move_to(midpoint( - image.get_right(), - RIGHT_SIDE, - )) - big_F.shift(DOWN) - equivalence = VGroup( - fourier_part.copy().scale(1.25), - TexMobject("\\Leftrightarrow").scale(1.5), - TextMobject("Break down into\\\\pure frequencies"), - ) - equivalence.arrange(RIGHT) - equivalence.move_to(big_F) - equivalence.to_edge(UP) - - self.play( - FadeIn(big_F), - TransformFromCopy(fourier_part, equivalence[0]), - Write(equivalence[1:]), - ) - self.wait(3) - self.play(FadeOut(VGroup(big_F, equivalence))) - - self.image = image - self.name = name - - def show_paper(self): - image = self.image - paper = ImageMobject("Fourier paper") - paper.match_height(image) - paper.next_to(image, RIGHT, MED_LARGE_BUFF) - - date = TexMobject("1822") - date.next_to(paper, DOWN) - date_rect = SurroundingRectangle(date) - date_rect.scale(0.3) - date_rect.set_color(RED) - date_rect.shift(1.37 * UP + 0.08 * LEFT) - date_arrow = Arrow( - date_rect.get_bottom(), - date.get_top(), - buff=SMALL_BUFF, - color=date_rect.get_color(), - ) - - heat_rect = SurroundingRectangle( - TextMobject("CHALEUR") - ) - heat_rect.set_color(RED) - heat_rect.scale(0.6) - heat_rect.move_to( - paper.get_top() + - 1.22 * DOWN + 0.37 * RIGHT - ) - heat_word = TextMobject("Heat") - heat_word.scale(1.5) - heat_word.next_to(paper, UP) - heat_word.shift(paper.get_width() * RIGHT) - heat_arrow = Arrow( - heat_rect.get_top(), - heat_word.get_left(), - buff=0.1, - path_arc=-60 * DEGREES, - color=heat_rect.get_color(), - ) - - self.play(FadeInFrom(paper, LEFT)) - self.play( - ShowCreation(date_rect), - ) - self.play( - GrowFromPoint(date, date_arrow.get_start()), - ShowCreation(date_arrow), - ) - self.wait(3) - - # Insert animation of circles/sine waves - # approximating a square wave - - self.play( - ShowCreation(heat_rect), - ) - self.play( - GrowFromPoint(heat_word, heat_arrow.get_start()), - ShowCreation(heat_arrow), - ) - self.wait(3) - - -class ManyCousinsOfFourierThings(Scene): - def construct(self): - series_variants = VGroup( - TextMobject("Complex", "Fourier Series"), - TextMobject("Discrete", "Fourier Series"), - ) - transform_variants = VGroup( - TextMobject("Discrete", "Fourier Transform"), - TextMobject("Fast", "Fourier Transform"), - TextMobject("Quantum", "Fourier Transform"), - ) - groups = VGroup(series_variants, transform_variants) - for group, vect in zip(groups, [LEFT, RIGHT]): - group.scale(0.7) - group.arrange(DOWN, aligned_edge=LEFT) - group.move_to( - vect * FRAME_WIDTH / 4 - ) - group.set_color(YELLOW) - - self.play(*[ - LaggedStartMap(FadeIn, group) - for group in groups - ]) - self.play(*[ - LaggedStartMap(FadeOut, group) - for group in groups - ]) - - -class FourierSeriesIllustraiton(Scene): - CONFIG = { - "n_range": range(1, 31, 2), - "axes_config": { - "axis_config": { - "include_tip": False, - }, - "x_axis_config": { - "tick_frequency": 1 / 4, - "unit_size": 4, - }, - "x_min": 0, - "x_max": 1, - "y_min": -1, - "y_max": 1, - }, - "colors": [BLUE, GREEN, RED, YELLOW, PINK], - } - - def construct(self): - aaa_group = self.get_axes_arrow_axes() - aaa_group.shift(2 * UP) - aaa_group.shift_onto_screen() - axes1, arrow, axes2 = aaa_group - - axes2.add(self.get_target_func_graph(axes2)) - - sine_graphs = self.get_sine_graphs(axes1) - partial_sums = self.get_partial_sums(axes1, sine_graphs) - - sum_tex = self.get_sum_tex() - sum_tex.next_to(axes1, DOWN, LARGE_BUFF) - sum_tex.shift(RIGHT) - eq = TexMobject("=") - target_func_tex = self.get_target_func_tex() - target_func_tex.next_to(axes2, DOWN) - target_func_tex.match_y(sum_tex) - eq.move_to(midpoint( - target_func_tex.get_left(), - sum_tex.get_right() - )) - - range_words = TextMobject( - "For $0 \\le x \\le 1$" - ) - range_words.next_to( - VGroup(sum_tex, target_func_tex), - DOWN, - ) - - rects = it.chain( - [ - SurroundingRectangle(piece) - for piece in self.get_sum_tex_pieces(sum_tex) - ], - it.cycle([None]) - ) - - self.add(axes1, arrow, axes2) - self.add(sum_tex, eq, target_func_tex) - self.add(range_words) - - curr_partial_sum = axes1.get_graph(lambda x: 0) - curr_partial_sum.set_stroke(width=1) - for sine_graph, partial_sum, rect in zip(sine_graphs, partial_sums, rects): - anims1 = [ - ShowCreation(sine_graph) - ] - partial_sum.set_stroke(BLACK, 4, background=True) - anims2 = [ - curr_partial_sum.set_stroke, - {"width": 1, "opacity": 0.5}, - curr_partial_sum.set_stroke, - {"width": 0, "background": True}, - ReplacementTransform( - sine_graph, partial_sum, - remover=True - ), - ] - if rect: - rect.match_style(sine_graph) - anims1.append(ShowCreation(rect)) - anims2.append(FadeOut(rect)) - self.play(*anims1) - self.play(*anims2) - curr_partial_sum = partial_sum - - def get_axes_arrow_axes(self): - axes1 = Axes(**self.axes_config) - axes1.x_axis.add_numbers( - 0.5, 1, - number_config={"num_decimal_places": 1} - ) - axes1.y_axis.add_numbers( - -1, 1, - number_config={"num_decimal_places": 1}, - direction=LEFT, - ) - axes2 = axes1.deepcopy() - - arrow = Arrow(LEFT, RIGHT, color=WHITE) - group = VGroup(axes1, arrow, axes2) - group.arrange(RIGHT, buff=MED_LARGE_BUFF) - return group - - def get_sine_graphs(self, axes): - sine_graphs = VGroup(*[ - axes.get_graph(self.generate_nth_func(n)) - for n in self.n_range - ]) - sine_graphs.set_stroke(width=3) - for graph, color in zip(sine_graphs, it.cycle(self.colors)): - graph.set_color(color) - return sine_graphs - - def get_partial_sums(self, axes, sine_graphs): - partial_sums = VGroup(*[ - axes.get_graph(self.generate_kth_partial_sum_func(k + 1)) - for k in range(len(self.n_range)) - ]) - partial_sums.match_style(sine_graphs) - return partial_sums - - def get_sum_tex(self): - return TexMobject( - "\\frac{4}{\\pi} \\left(", - "\\frac{\\cos(\\pi x)}{1}", - "-\\frac{\\cos(3\\pi x)}{3}", - "+\\frac{\\cos(5\\pi x)}{5}", - "- \\cdots \\right)" - ).scale(0.75) - - def get_sum_tex_pieces(self, sum_tex): - return sum_tex[1:4] - - def get_target_func_tex(self): - step_tex = TexMobject( - """ - 1 \\quad \\text{if $x < 0.5$} \\\\ - 0 \\quad \\text{if $x = 0.5$} \\\\ - -1 \\quad \\text{if $x > 0.5$} \\\\ - """ - ) - lb = Brace(step_tex, LEFT, buff=SMALL_BUFF) - step_tex.add(lb) - return step_tex - - def get_target_func_graph(self, axes): - step_func = axes.get_graph( - lambda x: (1 if x < 0.5 else -1), - discontinuities=[0.5], - color=YELLOW, - stroke_width=3, - ) - dot = Dot(axes.c2p(0.5, 0), color=step_func.get_color()) - dot.scale(0.5) - step_func.add(dot) - return step_func - - # def generate_nth_func(self, n): - # return lambda x: (4 / n / PI) * np.sin(TAU * n * x) - - def generate_nth_func(self, n): - return lambda x: np.prod([ - (4 / PI), - (1 / n) * (-1)**((n - 1) / 2), - np.cos(PI * n * x) - ]) - - def generate_kth_partial_sum_func(self, k): - return lambda x: np.sum([ - self.generate_nth_func(n)(x) - for n in self.n_range[:k] - ]) - - -class FourierSeriesOfLineIllustration(FourierSeriesIllustraiton): - CONFIG = { - "n_range": range(1, 31, 2), - "axes_config": { - "y_axis_config": { - "unit_size": 2, - "tick_frequency": 0.25, - "numbers_with_elongated_ticks": [-1, 1], - } - } - } - - def get_sum_tex(self): - return TexMobject( - "\\frac{8}{\\pi^2} \\left(", - "\\frac{\\cos(\\pi x)}{1^2}", - "+\\frac{\\cos(3\\pi x)}{3^2}", - "+\\frac{\\cos(5\\pi x)}{5^2}", - "+ \\cdots \\right)" - ).scale(0.75) - - # def get_sum_tex_pieces(self, sum_tex): - # return sum_tex[1:4] - - def get_target_func_tex(self): - result = TexMobject("1 - 2x") - result.scale(1.5) - point = VectorizedPoint() - point.next_to(result, RIGHT, 1.5 * LARGE_BUFF) - # result.add(point) - return result - - def get_target_func_graph(self, axes): - return axes.get_graph( - lambda x: 1 - 2 * x, - color=YELLOW, - stroke_width=3, - ) - - # def generate_nth_func(self, n): - # return lambda x: (4 / n / PI) * np.sin(TAU * n * x) - - def generate_nth_func(self, n): - return lambda x: np.prod([ - (8 / PI**2), - (1 / n**2), - np.cos(PI * n * x) - ]) - - -class CircleAnimationOfF(FourierOfTrebleClef): - CONFIG = { - "height": 3, - "n_circles": 200, - "run_time": 10, - "arrow_config": { - "tip_length": 0.1, - "stroke_width": 2, - } - } - - def get_shape(self): - path = VMobject() - shape = TextMobject("F") - for sp in shape.family_members_with_points(): - path.append_points(sp.points) - return path - - -class ExponentialDecay(PiCreatureScene): - def construct(self): - k = 0.2 - mk_tex = "-0.2" - mk_tex_color = GREEN - t2c = {mk_tex: mk_tex_color} - - # Pi creature - randy = self.pi_creature - randy.flip() - randy.set_height(2.5) - randy.move_to(3 * RIGHT) - randy.to_edge(DOWN) - bubble = ThoughtBubble( - direction=LEFT, - height=3.5, - width=3, - ) - bubble.pin_to(randy) - bubble.set_fill(DARKER_GREY) - exp = TexMobject( - "Ce^{", mk_tex, "t}", - tex_to_color_map=t2c, - ) - exp.move_to(bubble.get_bubble_center()) - - # Setup axes - axes = Axes( - x_min=0, - x_max=13, - y_min=-4, - y_max=4, - ) - axes.set_stroke(width=2) - axes.set_color(LIGHT_GREY) - axes.scale(0.9) - axes.to_edge(LEFT, buff=LARGE_BUFF) - axes.x_axis.add_numbers() - axes.y_axis.add_numbers() - axes.y_axis.add_numbers(0) - axes.x_axis.add( - TextMobject("Time").next_to( - axes.x_axis.get_end(), DR, - ) - ) - axes.y_axis.add( - TexMobject("f").next_to( - axes.y_axis.get_corner(UR), RIGHT, - ).set_color(YELLOW) - ) - axes.x_axis.set_opacity(0) - - # Value trackers - y_tracker = ValueTracker(3) - x_tracker = ValueTracker(0) - dydt_tracker = ValueTracker() - dxdt_tracker = ValueTracker(0) - self.add( - y_tracker, x_tracker, - dydt_tracker, dxdt_tracker, - ) - - get_y = y_tracker.get_value - get_x = x_tracker.get_value - get_dydt = dydt_tracker.get_value - get_dxdt = dxdt_tracker.get_value - - dydt_tracker.add_updater(lambda m: m.set_value( - - k * get_y() - )) - y_tracker.add_updater(lambda m, dt: m.increment_value( - dt * get_dydt() - )) - x_tracker.add_updater(lambda m, dt: m.increment_value( - dt * get_dxdt() - )) - - # Tip/decimal - tip = ArrowTip(color=YELLOW) - tip.set_width(0.25) - tip.add_updater(lambda m: m.move_to( - axes.c2p(get_x(), get_y()), LEFT - )) - decimal = DecimalNumber() - decimal.add_updater(lambda d: d.set_value(get_y())) - decimal.add_updater(lambda d: d.next_to( - tip, RIGHT, - SMALL_BUFF, - )) - - # Rate of change arrow - arrow = Vector( - DOWN, color=RED, - max_stroke_width_to_length_ratio=50, - max_tip_length_to_length_ratio=0.2, - ) - arrow.set_stroke(width=4) - arrow.add_updater(lambda m: m.scale( - 2.5 * abs(get_dydt()) / m.get_length() - )) - arrow.add_updater(lambda m: m.move_to( - tip.get_left(), UP - )) - - # Graph - graph = TracedPath(tip.get_left) - - # Equation - ode = TexMobject( - "{d{f} \\over dt}(t)", - "=", mk_tex, "\\cdot {f}(t)", - tex_to_color_map={ - "{f}": YELLOW, - "=": WHITE, - mk_tex: mk_tex_color - } - ) - ode.to_edge(UP) - dfdt = ode[:3] - ft = ode[-2:] - - self.add(axes) - self.add(tip) - self.add(decimal) - self.add(arrow) - self.add(randy) - self.add(ode) - - # Show rate of change dependent on itself - rect = SurroundingRectangle(dfdt) - self.play(ShowCreation(rect)) - self.wait() - self.play( - Transform( - rect, - SurroundingRectangle(ft) - ) - ) - self.wait(3) - - # Show graph over time - self.play( - DrawBorderThenFill(bubble), - Write(exp), - FadeOut(rect), - randy.change, "thinking", - ) - axes.x_axis.set_opacity(1) - self.play( - y_tracker.set_value, 3, - ShowCreation(axes.x_axis), - ) - dxdt_tracker.set_value(1) - self.add(graph) - randy.add_updater(lambda r: r.look_at(tip)) - self.wait(4) - - # Show derivative of exponential - eq = TexMobject("=") - eq.next_to(ode.get_part_by_tex("="), DOWN, LARGE_BUFF) - exp.generate_target() - exp.target.next_to(eq, LEFT) - d_dt = TexMobject("{d \\over dt}") - d_dt.next_to(exp.target, LEFT) - const = TexMobject(mk_tex) - const.set_color(mk_tex_color) - dot = TexMobject("\\cdot") - const.next_to(eq, RIGHT) - dot.next_to(const, RIGHT, 2 * SMALL_BUFF) - exp_copy = exp.copy() - exp_copy.next_to(dot, RIGHT, 2 * SMALL_BUFF) - VGroup(const, dot, eq).align_to(exp_copy, DOWN) - - self.play( - MoveToTarget(exp), - FadeOut(bubble), - FadeIn(d_dt), - FadeIn(eq), - ) - self.wait(2) - self.play( - ApplyMethod( - exp[1].copy().replace, - const[0], - ) - ) - self.wait() - rect = SurroundingRectangle(exp) - rect.set_stroke(BLUE, 2) - self.play(FadeIn(rect)) - self.play( - Write(dot), - TransformFromCopy(exp, exp_copy), - rect.move_to, exp_copy - ) - self.play(FadeOut(rect)) - self.wait(5) - - -class InvestmentGrowth(Scene): - CONFIG = { - "output_tex": "{M}", - "output_color": GREEN, - "initial_value": 1, - "initial_value_tex": "{M_0}", - "k": 0.05, - "k_tex": "0.05", - "total_time": 43, - "time_rate": 3, - } - - def construct(self): - # Axes - axes = Axes( - x_min=0, - x_max=self.total_time, - y_min=0, - y_max=6, - x_axis_config={ - "unit_size": 0.3, - "tick_size": 0.05, - "numbers_with_elongated_ticks": range( - 0, self.total_time, 5 - ) - } - ) - axes.to_corner(DL, buff=LARGE_BUFF) - - time_label = TextMobject("Time") - time_label.next_to( - axes.x_axis.get_right(), - UP, MED_LARGE_BUFF - ) - time_label.shift_onto_screen() - axes.x_axis.add(time_label) - money_label = TexMobject(self.output_tex) - money_label.set_color(self.output_color) - money_label.next_to( - axes.y_axis.get_top(), - UP, - ) - axes.y_axis.add(money_label) - - # Graph - graph = axes.get_graph( - lambda x: self.initial_value * np.exp(self.k * x) - ) - graph.set_color(self.output_color) - full_graph = graph.copy() - time_tracker = self.get_time_tracker() - graph.add_updater(lambda m: m.pointwise_become_partial( - full_graph, 0, - np.clip( - time_tracker.get_value() / self.total_time, - 0, 1, - ) - )) - - # Equation - tex_kwargs = { - "tex_to_color_map": { - self.output_tex: self.output_color, - self.initial_value_tex: BLUE, - } - } - ode = TexMobject( - "{d", - "\\over dt}", - self.output_tex, - "(t)", - "=", self.k_tex, - "\\cdot", self.output_tex, "(t)", - **tex_kwargs - ) - ode.to_edge(UP) - exp = TexMobject( - self.output_tex, - "(t) =", self.initial_value_tex, - "e^{", self.k_tex, "t}", - **tex_kwargs - ) - exp.next_to(ode, DOWN, LARGE_BUFF) - - M0_part = exp.get_part_by_tex(self.initial_value_tex) - exp_part = exp[-3:] - M0_label = M0_part.copy() - M0_label.next_to( - axes.c2p(0, self.initial_value), - LEFT - ) - M0_part.set_opacity(0) - exp_part.save_state() - exp_part.align_to(M0_part, LEFT) - - self.add(axes) - self.add(graph) - self.add(time_tracker) - - self.play(FadeInFromDown(ode)) - self.wait(6) - self.play(FadeInFrom(exp, UP)) - self.wait(2) - self.play( - Restore(exp_part), - M0_part.set_opacity, 1, - ) - self.play(TransformFromCopy( - M0_part, M0_label - )) - self.wait(5) - - def get_time_tracker(self): - time_tracker = ValueTracker(0) - time_tracker.add_updater( - lambda m, dt: m.increment_value( - self.time_rate * dt - ) - ) - return time_tracker - - -class GrowingPileOfMoney(InvestmentGrowth): - CONFIG = { - "total_time": 60 - } - - def construct(self): - initial_count = 5 - k = self.k - total_time = self.total_time - - time_tracker = self.get_time_tracker() - - final_count = initial_count * np.exp(k * total_time) - dollar_signs = VGroup(*[ - TexMobject("\\$") - for x in range(int(final_count)) - ]) - dollar_signs.set_color(GREEN) - for ds in dollar_signs: - ds.shift( - 3 * np.random.random(3) - ) - dollar_signs.center() - dollar_signs.sort(get_norm) - dollar_signs.set_stroke(BLACK, 3, background=True) - - def update_dollar_signs(group): - t = time_tracker.get_value() - count = initial_count * np.exp(k * t) - alpha = count / final_count - n, sa = integer_interpolate(0, len(dollar_signs), alpha) - group.set_opacity(1) - group[n:].set_opacity(0) - group[n].set_opacity(sa) - - dollar_signs.add_updater(update_dollar_signs) - - self.add(time_tracker) - self.add(dollar_signs) - self.wait(20) - - -class CarbonDecayCurve(InvestmentGrowth): - CONFIG = { - "output_tex": "{^{14}C}", - "output_color": GOLD, - "initial_value": 4, - "initial_value_tex": "{^{14}C_0}", - "k": -0.1, - "k_tex": "-k", - "time_rate": 6, - } - - -class CarbonDecayingInMammoth(Scene): - def construct(self): - mammoth = SVGMobject("Mammoth") - mammoth.set_color( - interpolate_color(GREY_BROWN, WHITE, 0.25) - ) - mammoth.set_height(4) - body = mammoth[9] - - atoms = VGroup(*[ - self.get_atom(body) - for n in range(600) - ]) - - p_decay = 0.2 - - def update_atoms(group, dt): - for atom in group: - if np.random.random() < dt * p_decay: - group.remove(atom) - return group - atoms.add_updater(update_atoms) - - self.add(mammoth) - self.add(atoms) - self.wait(20) - - def get_atom(self, body): - atom = Dot(color=GOLD) - atom.set_height(0.05) - - dl = body.get_corner(DL) - ur = body.get_corner(UR) - - wn = 0 - while wn == 0: - point = np.array([ - interpolate(dl[0], ur[0], np.random.random()), - interpolate(dl[1], ur[1], np.random.random()), - 0 - ]) - wn = get_winding_number([ - body.point_from_proportion(a) - point - for a in np.linspace(0, 1, 300) - ]) - wn = int(np.round(wn)) - - atom.move_to(point) - return atom - - -class BoundaryConditionInterlude(Scene): - def construct(self): - background = FullScreenFadeRectangle( - fill_color=DARK_GREY - ) - storyline = self.get_main_storyline() - storyline.generate_target() - v_shift = 2 * DOWN - storyline.target.shift(v_shift) - im_to_im = storyline[1].get_center() - storyline[0].get_center() - - bc_image = self.get_labeled_image( - "Boundary\\\\conditions", - "boundary_condition_thumbnail" - ) - bc_image.next_to(storyline[1], UP) - new_arrow0 = Arrow( - storyline.arrows[0].get_start() + v_shift, - bc_image.get_left() + SMALL_BUFF * LEFT, - path_arc=-90 * DEGREES, - buff=0, - ) - new_mid_arrow = Arrow( - bc_image.get_bottom(), - storyline[1].get_top() + v_shift, - buff=SMALL_BUFF, - path_arc=60 * DEGREES, - ) - - # BC detour - self.add(background) - self.add(storyline[0]) - for im1, im2, arrow in zip(storyline, storyline[1:], storyline.arrows): - self.add(im2, im1) - self.play( - FadeInFrom(im2, -im_to_im), - ShowCreation(arrow), - ) - self.wait() - self.play( - GrowFromCenter(bc_image), - MoveToTarget(storyline), - Transform( - storyline.arrows[0], - new_arrow0, - ), - MaintainPositionRelativeTo( - storyline.arrows[1], - storyline, - ), - ) - self.play(ShowCreation(new_mid_arrow)) - self.wait() - - # From BC to next step - rect1 = bc_image[2].copy() - rect2 = storyline[1][2].copy() - rect3 = storyline[2][2].copy() - for rect in rect1, rect2, rect3: - rect.set_stroke(YELLOW, 3) - - self.play(FadeIn(rect1)) - kw = {"path_arc": 60 * DEGREES} - self.play( - LaggedStart( - Transform(rect1, rect2, **kw), - # TransformFromCopy(rect1, rect3, **kw), - lag_ratio=0.4, - ) - ) - self.play( - FadeOut(rect1), - # FadeOut(rect3), - ) - - # Reorganize images - im1, im3, im4 = storyline - im2 = bc_image - l_group = Group(im1, im2) - r_group = Group(im3, im4) - for group in l_group, r_group: - group.generate_target() - group.target.arrange(DOWN, buff=LARGE_BUFF) - group.target.center() - - l_group.target.to_edge(LEFT) - r_group.target.move_to( - FRAME_WIDTH * RIGHT / 4 - ) - brace = Brace(r_group.target, LEFT) - nv_text = brace.get_text("Next\\\\video") - nv_text.scale(1.5, about_edge=RIGHT) - nv_text.set_color(YELLOW) - brace.set_color(YELLOW) - - arrows = VGroup( - storyline.arrows, - new_mid_arrow, - ) - - self.play( - LaggedStart( - MoveToTarget(l_group), - MoveToTarget(r_group), - lag_ratio=0.3, - ), - FadeOut(arrows), - ) - self.play( - GrowFromCenter(brace), - FadeInFrom(nv_text, RIGHT) - ) - self.wait() - - def get_main_storyline(self): - images = Group( - self.get_sine_curve_image(), - self.get_linearity_image(), - self.get_fourier_series_image(), - ) - for image in images: - image.set_height(3) - images.arrange(RIGHT, buff=1) - images.set_width(FRAME_WIDTH - 1) - - arrows = VGroup() - for im1, im2 in zip(images, images[1:]): - arrow = Arrow( - im1.get_top(), - im2.get_top(), - color=WHITE, - buff=MED_SMALL_BUFF, - path_arc=-120 * DEGREES, - rectangular_stem_width=0.025, - ) - arrow.scale(0.7, about_edge=DOWN) - arrows.add(arrow) - images.arrows = arrows - - return images - - def get_sine_curve_image(self): - return self.get_labeled_image( - "Sine curves", - "sine_curve_temp_graph", - ) - - def get_linearity_image(self): - return self.get_labeled_image( - "Linearity", - "linearity_thumbnail", - ) - - def get_fourier_series_image(self): - return self.get_labeled_image( - "Fourier series", - "fourier_series_thumbnail", - ) - - def get_labeled_image(self, text, image_file): - rect = ScreenRectangle(height=2) - border = rect.copy() - rect.set_fill(BLACK, 1) - rect.set_stroke(WHITE, 0) - border.set_stroke(WHITE, 2) - - text_mob = TextMobject(text) - text_mob.set_stroke(BLACK, 5, background=True) - text_mob.next_to(rect.get_top(), DOWN, SMALL_BUFF) - - image = ImageMobject(image_file) - image.replace(rect, dim_to_match=1) - image.scale(0.8, about_edge=DOWN) - - return Group(rect, image, border, text_mob) - - -class GiantCross(Scene): - def construct(self): - rect = FullScreenFadeRectangle() - cross = Cross(rect) - cross.set_stroke(RED, 25) - - words = TextMobject("This wouldn't\\\\happen!") - words.scale(2) - words.set_color(RED) - words.to_edge(UP) - - self.play( - FadeInFromDown(words), - ShowCreation(cross), - ) - self.wait() - - -class EndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "1stViewMaths", - "Adrian Robinson", - "Alexis Olson", - "Andreas Benjamin Brössel", - "Andrew Busey", - "Ankalagon", - "Antoine Bruguier", - "Antonio Juarez", - "Arjun Chakroborty", - "Art Ianuzzi", - "Awoo", - "Ayan Doss", - "AZsorcerer", - "Barry Fam", - "Bernd Sing", - "Boris Veselinovich", - "Brian Staroselsky", - "Charles Southerland", - "Chris Connett", - "Christian Kaiser", - "Clark Gaebel", - "Cooper Jones", - "Danger Dai", - "Daniel Pang", - "Dave B", - "Dave Kester", - "David B. Hill", - "David Clark", - "Delton Ding", - "eaglle", - "Empirasign", - "emptymachine", - "Eric Younge", - "Eryq Ouithaqueue", - "Federico Lebron", - "Fernando Via Canel", - "Giovanni Filippi", - "Hal Hildebrand", - "Hitoshi Yamauchi", - "Isaac Jeffrey Lee", - "j eduardo perez", - "Jacob Hartmann", - "Jacob Magnuson", - "Jameel Syed", - "Jason Hise", - "Jeff Linse", - "Jeff Straathof", - "John C. Vesey", - "John Griffith", - "John Haley", - "John V Wertheim", - "Jonathan Eppele", - "Kai-Siang Ang", - "Kanan Gill", - "Kartik\\\\Cating-Subramanian", - "L0j1k", - "Lee Redden", - "Linh Tran", - "Ludwig Schubert", - "Magister Mugit", - "Mark B Bahu", - "Martin Price", - "Mathias Jansson", - "Matt Langford", - "Matt Roveto", - "Matthew Bouchard", - "Matthew Cocke", - "Michael Faust", - "Michael Hardel", - "Mirik Gogri", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nero Li", - "Nikita Lesnikov", - "Omar Zrien", - "Owen Campbell-Moore", - "Patrick", - "Peter Ehrnstrom", - "RedAgent14", - "rehmi post", - "Richard Comish", - "Ripta Pasay", - "Rish Kundalia", - "Robert Teed", - "Roobie", - "Ryan Williams", - "Sebastian Garcia", - "Solara570", - "Stephan Arlinghaus", - "Steven Siddals", - "Stevie Metke", - "Tal Einav", - "Ted Suzman", - "Thomas Tarler", - "Tianyu Ge", - "Tom Fleming", - "Valeriy Skobelev", - "Xuanji Li", - "Yavor Ivanov", - "YinYangBalance.Asia", - "Zach Cardwell", - "Luc Ritchie", - "Britt Selvitelle", - "David Gow", - "J", - "Jonathan Wilson", - "Joseph John Cox", - "Magnus Dahlström", - "Randy C. Will", - "Ryan Atallah", - "Lukas -krtek.net- Novy", - "Jordan Scales", - "Ali Yahya", - "Arthur Zey", - "Atul S", - "dave nicponski", - "Evan Phillips", - "Joseph Kelly", - "Kaustuv DeBiswas", - "Lambda AI Hardware", - "Lukas Biewald", - "Mark Heising", - "Mike Coleman", - "Nicholas Cahill", - "Peter Mcinerney", - "Quantopian", - "Roy Larson", - "Scott Walter, Ph.D.", - "Yana Chernobilsky", - "Yu Jun", - "D. Sivakumar", - "Richard Barthel", - "Burt Humburg", - "Matt Russell", - "Scott Gray", - "soekul", - "Tihan Seale", - "Juan Benet", - "Vassili Philippov", - "Kurt Dicus", - ], - } diff --git a/from_3b1b/active/diffyq/part3/temperature_graphs.py b/from_3b1b/active/diffyq/part3/temperature_graphs.py deleted file mode 100644 index e1c2f1c1..00000000 --- a/from_3b1b/active/diffyq/part3/temperature_graphs.py +++ /dev/null @@ -1,2828 +0,0 @@ -from scipy import integrate - -from manimlib.imports import * -from active_projects.diffyq.part2.heat_equation import * - - -class TemperatureGraphScene(SpecialThreeDScene): - CONFIG = { - "axes_config": { - "x_min": 0, - "x_max": TAU, - "y_min": 0, - "y_max": 10, - "z_min": -3, - "z_max": 3, - "x_axis_config": { - "tick_frequency": TAU / 8, - "include_tip": False, - }, - "num_axis_pieces": 1, - }, - "default_graph_style": { - "stroke_width": 2, - "stroke_color": WHITE, - "background_image_file": "VerticalTempGradient", - }, - "default_surface_config": { - "fill_opacity": 0.1, - "checkerboard_colors": [LIGHT_GREY], - "stroke_width": 0.5, - "stroke_color": WHITE, - "stroke_opacity": 0.5, - }, - "temp_text": "Temperature", - } - - def get_three_d_axes(self, include_labels=True, include_numbers=False, **kwargs): - config = dict(self.axes_config) - config.update(kwargs) - axes = ThreeDAxes(**config) - axes.set_stroke(width=2) - - if include_numbers: - self.add_axes_numbers(axes) - - if include_labels: - self.add_axes_labels(axes) - - # Adjust axis orientation - axes.x_axis.rotate( - 90 * DEGREES, RIGHT, - about_point=axes.c2p(0, 0, 0), - ) - axes.y_axis.rotate( - 90 * DEGREES, UP, - about_point=axes.c2p(0, 0, 0), - ) - - # Add xy-plane - input_plane = self.get_surface( - axes, lambda x, t: 0 - ) - input_plane.set_style( - fill_opacity=0.5, - fill_color=BLUE_B, - stroke_width=0.5, - stroke_color=WHITE, - ) - - axes.input_plane = input_plane - - return axes - - def add_axes_numbers(self, axes): - x_axis = axes.x_axis - y_axis = axes.y_axis - tex_vals = [ - ("\\pi \\over 2", TAU / 4), - ("\\pi", TAU / 2), - ("3\\pi \\over 2", 3 * TAU / 4), - ("2\\pi", TAU) - ] - x_labels = VGroup() - for tex, val in tex_vals: - label = TexMobject(tex) - label.scale(0.5) - label.next_to(x_axis.n2p(val), DOWN) - x_labels.add(label) - x_axis.add(x_labels) - x_axis.numbers = x_labels - - y_axis.add_numbers() - for number in y_axis.numbers: - number.rotate(90 * DEGREES) - return axes - - def add_axes_labels(self, axes): - x_label = TexMobject("x") - x_label.next_to(axes.x_axis.get_end(), RIGHT) - axes.x_axis.label = x_label - - t_label = TextMobject("Time") - t_label.rotate(90 * DEGREES, OUT) - t_label.next_to(axes.y_axis.get_end(), UP) - axes.y_axis.label = t_label - - temp_label = TextMobject(self.temp_text) - temp_label.rotate(90 * DEGREES, RIGHT) - temp_label.next_to(axes.z_axis.get_zenith(), RIGHT) - axes.z_axis.label = temp_label - for axis in axes: - axis.add(axis.label) - return axes - - def get_time_slice_graph(self, axes, func, t, **kwargs): - config = dict() - config.update(self.default_graph_style) - config.update({ - "t_min": axes.x_min, - "t_max": axes.x_max, - }) - config.update(kwargs) - return ParametricFunction( - lambda x: axes.c2p( - x, t, func(x, t) - ), - **config, - ) - - def get_initial_state_graph(self, axes, func, **kwargs): - return self.get_time_slice_graph( - axes, - lambda x, t: func(x), - t=0, - **kwargs - ) - - def get_surface(self, axes, func, **kwargs): - config = { - "u_min": axes.y_min, - "u_max": axes.y_max, - "v_min": axes.x_min, - "v_max": axes.x_max, - "resolution": ( - (axes.y_max - axes.y_min) // axes.y_axis.tick_frequency, - (axes.x_max - axes.x_min) // axes.x_axis.tick_frequency, - ), - } - config.update(self.default_surface_config) - config.update(kwargs) - return ParametricSurface( - lambda t, x: axes.c2p( - x, t, func(x, t) - ), - **config - ) - - def orient_three_d_mobject(self, mobject, - phi=85 * DEGREES, - theta=-80 * DEGREES): - mobject.rotate(-90 * DEGREES - theta, OUT) - mobject.rotate(phi, LEFT) - return mobject - - def get_rod_length(self): - return self.axes_config["x_max"] - - def get_const_time_plane(self, axes): - t_tracker = ValueTracker(0) - plane = Polygon( - *[ - axes.c2p(x, 0, z) - for x, z in [ - (axes.x_min, axes.z_min), - (axes.x_max, axes.z_min), - (axes.x_max, axes.z_max), - (axes.x_min, axes.z_max), - ] - ], - stroke_width=0, - fill_color=WHITE, - fill_opacity=0.2 - ) - plane.add_updater(lambda m: m.shift( - axes.c2p( - axes.x_min, - t_tracker.get_value(), - axes.z_min, - ) - plane.points[0] - )) - plane.t_tracker = t_tracker - return plane - - -class SimpleCosExpGraph(TemperatureGraphScene): - def construct(self): - axes = self.get_three_d_axes() - cos_graph = self.get_cos_graph(axes) - cos_exp_surface = self.get_cos_exp_surface(axes) - - self.set_camera_orientation( - phi=80 * DEGREES, - theta=-80 * DEGREES, - ) - self.camera.frame_center.shift(3 * RIGHT) - self.begin_ambient_camera_rotation(rate=0.01) - - self.add(axes) - self.play(ShowCreation(cos_graph)) - self.play(UpdateFromAlphaFunc( - cos_exp_surface, - lambda m, a: m.become( - self.get_cos_exp_surface(axes, v_max=a * 10) - ), - run_time=3 - )) - - self.add(cos_graph.copy()) - - t_tracker = ValueTracker(0) - get_t = t_tracker.get_value - cos_graph.add_updater( - lambda m: m.become(self.get_time_slice_graph( - axes, - lambda x: self.cos_exp(x, get_t()), - t=get_t() - )) - ) - - plane = Rectangle( - stroke_width=0, - fill_color=WHITE, - fill_opacity=0.1, - ) - plane.rotate(90 * DEGREES, RIGHT) - plane.match_width(axes.x_axis) - plane.match_depth(axes.z_axis, stretch=True) - plane.move_to(axes.c2p(0, 0, 0), LEFT) - - self.add(plane, cos_graph) - self.play( - ApplyMethod( - t_tracker.set_value, 10, - run_time=10, - rate_func=linear, - ), - ApplyMethod( - plane.shift, 10 * UP, - run_time=10, - rate_func=linear, - ), - VFadeIn(plane), - ) - self.wait(10) - - # - def cos_exp(self, x, t, A=2, omega=1.5, k=0.1): - return A * np.cos(omega * x) * np.exp(-k * (omega**2) * t) - - def get_cos_graph(self, axes, **config): - return self.get_initial_state_graph( - axes, - lambda x: self.cos_exp(x, 0), - **config - ) - - def get_cos_exp_surface(self, axes, **config): - return self.get_surface( - axes, - lambda x, t: self.cos_exp(x, t), - **config - ) - - -class AddMultipleSolutions(SimpleCosExpGraph): - CONFIG = { - "axes_config": { - "x_axis_config": { - "unit_size": 0.7, - }, - } - } - - def construct(self): - axes1, axes2, axes3 = all_axes = VGroup(*[ - self.get_three_d_axes( - include_labels=False, - ) - for x in range(3) - ]) - all_axes.scale(0.5) - self.orient_three_d_mobject(all_axes) - - As = [1.5, 1.5] - omegas = [1.5, 2.5] - ks = [0.1, 0.1] - quads = [ - (axes1, [As[0]], [omegas[0]], [ks[0]]), - (axes2, [As[1]], [omegas[1]], [ks[1]]), - (axes3, As, omegas, ks), - ] - - for axes, As, omegas, ks in quads: - graph = self.get_initial_state_graph( - axes, - lambda x: np.sum([ - self.cos_exp(x, 0, A, omega, k) - for A, omega, k in zip(As, omegas, ks) - ]) - ) - surface = self.get_surface( - axes, - lambda x, t: np.sum([ - self.cos_exp(x, t, A, omega, k) - for A, omega, k in zip(As, omegas, ks) - ]) - ) - surface.sort(lambda p: -p[2]) - - axes.add(surface, graph) - axes.graph = graph - axes.surface = surface - - self.set_camera_orientation(distance=100) - plus = TexMobject("+").scale(2) - equals = TexMobject("=").scale(2) - group = VGroup( - axes1, plus, axes2, equals, axes3, - ) - group.arrange(RIGHT, buff=SMALL_BUFF) - - for axes in all_axes: - checkmark = TexMobject("\\checkmark") - checkmark.set_color(GREEN) - checkmark.scale(2) - checkmark.next_to(axes, UP) - checkmark.shift(0.7 * DOWN) - axes.checkmark = checkmark - - self.add(axes1, axes2) - self.play( - LaggedStart( - Write(axes1.surface), - Write(axes2.surface), - ), - LaggedStart( - FadeInFrom(axes1.checkmark, DOWN), - FadeInFrom(axes2.checkmark, DOWN), - ), - lag_ratio=0.2, - run_time=1, - ) - self.wait() - self.play(Write(plus)) - self.play( - Transform( - axes1.copy().set_fill(opacity=0), - axes3 - ), - Transform( - axes2.copy().set_fill(opacity=0), - axes3 - ), - FadeInFrom(equals, LEFT) - ) - self.play( - FadeInFrom(axes3.checkmark, DOWN), - ) - self.wait() - - -class BreakDownAFunction(SimpleCosExpGraph): - CONFIG = { - "axes_config": { - "z_axis_config": { - "unit_size": 0.75, - "include_tip": False, - }, - "z_min": -2, - "y_max": 20, - }, - "low_axes_config": { - "z_min": -3, - "z_axis_config": {"unit_size": 1} - }, - "n_low_axes": 4, - "k": 0.2, - } - - def construct(self): - self.set_camera_orientation(distance=100) - self.set_axes() - self.setup_graphs() - self.show_break_down() - self.show_solutions_for_waves() - - def set_axes(self): - top_axes = self.get_three_d_axes() - top_axes.z_axis.label.next_to( - top_axes.z_axis.get_end(), OUT, SMALL_BUFF - ) - top_axes.y_axis.set_opacity(0) - self.orient_three_d_mobject(top_axes) - top_axes.y_axis.label.rotate(-10 * DEGREES, UP) - top_axes.scale(0.75) - top_axes.center() - top_axes.to_edge(UP) - - low_axes = self.get_three_d_axes(**self.low_axes_config) - low_axes.y_axis.set_opacity(0) - for axis in low_axes: - axis.label.fade(1) - # low_axes.add(low_axes.input_plane) - # low_axes.input_plane.set_opacity(0) - - self.orient_three_d_mobject(low_axes) - low_axes_group = VGroup(*[ - low_axes.deepcopy() - for x in range(self.n_low_axes) - ]) - low_axes_group.arrange( - RIGHT, buff=low_axes.get_width() / 3 - ) - low_axes_group.set_width(FRAME_WIDTH - 2.5) - low_axes_group.next_to(top_axes, DOWN, LARGE_BUFF) - low_axes_group.to_edge(LEFT) - - self.top_axes = top_axes - self.low_axes_group = low_axes_group - - def setup_graphs(self): - top_axes = self.top_axes - low_axes_group = self.low_axes_group - - top_graph = self.get_initial_state_graph( - top_axes, - self.initial_func, - discontinuities=self.get_initial_func_discontinuities(), - color=YELLOW, - ) - top_graph.set_stroke(width=4) - - fourier_terms = self.get_fourier_cosine_terms( - self.initial_func - ) - - low_graphs = VGroup(*[ - self.get_initial_state_graph( - axes, - lambda x: A * np.cos(n * x / 2) - ) - for n, axes, A in zip( - it.count(), - low_axes_group, - fourier_terms - ) - ]) - k = self.k - low_surfaces = VGroup(*[ - self.get_surface( - axes, - lambda x, t: np.prod([ - A, - np.cos(n * x / 2), - np.exp(-k * (n / 2)**2 * t) - ]) - ) - for n, axes, A in zip( - it.count(), - low_axes_group, - fourier_terms - ) - ]) - top_surface = self.get_surface( - top_axes, - lambda x, t: np.sum([ - np.prod([ - A, - np.cos(n * x / 2), - np.exp(-k * (n / 2)**2 * t) - ]) - for n, A in zip( - it.count(), - fourier_terms - ) - ]) - ) - - self.top_graph = top_graph - self.low_graphs = low_graphs - self.low_surfaces = low_surfaces - self.top_surface = top_surface - - def show_break_down(self): - top_axes = self.top_axes - low_axes_group = self.low_axes_group - top_graph = self.top_graph - low_graphs = self.low_graphs - - plusses = VGroup(*[ - TexMobject("+").next_to( - axes.x_axis.get_end(), - RIGHT, MED_SMALL_BUFF - ) - for axes in low_axes_group - ]) - dots = TexMobject("\\cdots") - dots.next_to(plusses, RIGHT, MED_SMALL_BUFF) - - arrow = Arrow( - dots.get_right(), - top_graph.get_end() + 1.4 * DOWN + 1.7 * RIGHT, - path_arc=90 * DEGREES, - ) - - top_words = TextMobject("Arbitrary\\\\function") - top_words.next_to(top_axes, LEFT, MED_LARGE_BUFF) - top_words.set_color(YELLOW) - top_arrow = Arrow( - top_words.get_right(), - top_graph.point_from_proportion(0.3) - ) - - low_words = TextMobject("Sine curves") - low_words.set_color(BLUE) - low_words.next_to(low_axes_group, DOWN, MED_LARGE_BUFF) - - self.add(top_axes) - self.play(ShowCreation(top_graph)) - self.play( - FadeInFrom(top_words, RIGHT), - ShowCreation(top_arrow) - ) - self.wait() - self.play( - LaggedStartMap(FadeIn, low_axes_group), - FadeInFrom(low_words, UP), - LaggedStartMap(FadeInFromDown, [*plusses, dots]), - *[ - TransformFromCopy(top_graph, low_graph) - for low_graph in low_graphs - ], - ) - self.play(ShowCreation(arrow)) - self.wait() - - def show_solutions_for_waves(self): - low_axes_group = self.low_axes_group - top_axes = self.top_axes - low_graphs = self.low_graphs - low_surfaces = self.low_surfaces - top_surface = self.top_surface - top_graph = self.top_graph - - for surface in [top_surface, *low_surfaces]: - surface.sort(lambda p: -p[2]) - - anims1 = [] - anims2 = [ - ApplyMethod( - top_axes.y_axis.set_opacity, 1, - ), - ] - for axes, surface, graph in zip(low_axes_group, low_surfaces, low_graphs): - axes.y_axis.set_opacity(1) - axes.y_axis.label.fade(1) - anims1 += [ - ShowCreation(axes.y_axis), - Write(surface, run_time=2), - ] - anims2.append(AnimationGroup( - TransformFromCopy(graph, top_graph.copy()), - Transform( - surface.copy().set_fill(opacity=0), - top_surface, - ) - )) - - self.play(*anims1) - self.wait() - self.play(LaggedStart(*anims2, run_time=2)) - self.wait() - - checkmark = TexMobject("\\checkmark") - checkmark.set_color(GREEN) - low_checkmarks = VGroup(*[ - checkmark.copy().next_to( - surface.get_top(), UP, SMALL_BUFF - ) - for surface in low_surfaces - ]) - top_checkmark = checkmark.copy() - top_checkmark.scale(1.5) - top_checkmark.move_to(top_axes.get_corner(UR)) - - self.play(LaggedStartMap(FadeInFromDown, low_checkmarks)) - self.wait() - self.play(*[ - TransformFromCopy(low_checkmark, top_checkmark.copy()) - for low_checkmark in low_checkmarks - ]) - self.wait() - - # - def initial_func(self, x): - # return 3 * np.exp(-(x - PI)**2) - - x1 = TAU / 4 - 1 - x2 = TAU / 4 + 1 - x3 = 3 * TAU / 4 - 1.6 - x4 = 3 * TAU / 4 + 0.3 - - T0 = -2 - T1 = 2 - T2 = 1 - - if x < x1: - return T0 - elif x < x2: - alpha = inverse_interpolate(x1, x2, x) - return bezier([T0, T0, T1, T1])(alpha) - elif x < x3: - return T1 - elif x < x4: - alpha = inverse_interpolate(x3, x4, x) - return bezier([T1, T1, T2, T2])(alpha) - else: - return T2 - - def get_initial_func_discontinuities(self): - # return [TAU / 4, 3 * TAU / 4] - return [] - - def get_fourier_cosine_terms(self, func, n_terms=40): - result = [ - integrate.quad( - lambda x: (1 / PI) * func(x) * np.cos(n * x / 2), - 0, TAU - )[0] - for n in range(n_terms) - ] - result[0] = result[0] / 2 - return result - - -class OceanOfPossibilities(TemperatureGraphScene): - CONFIG = { - "axes_config": { - "z_min": 0, - "z_max": 4, - }, - "k": 0.2, - "default_surface_config": { - # "resolution": (32, 20), - # "resolution": (8, 5), - } - } - - def construct(self): - self.setup_camera() - self.setup_axes() - self.setup_surface() - self.show_solution() - self.reference_boundary_conditions() - self.reference_initial_condition() - self.ambiently_change_solution() - - def setup_camera(self): - self.set_camera_orientation( - phi=80 * DEGREES, - theta=-80 * DEGREES, - ) - self.begin_ambient_camera_rotation(rate=0.01) - - def setup_axes(self): - axes = self.get_three_d_axes(include_numbers=True) - axes.add(axes.input_plane) - axes.scale(0.8) - axes.center() - axes.shift(OUT + RIGHT) - - self.add(axes) - self.axes = axes - - def setup_surface(self): - axes = self.axes - k = self.k - - # Parameters for surface function - initial_As = [2] + [ - 0.8 * random.choice([-1, 1]) / n - for n in range(1, 20) - ] - A_trackers = Group(*[ - ValueTracker(A) - for A in initial_As - ]) - - def get_As(): - return [At.get_value() for At in A_trackers] - - omegas = [n / 2 for n in range(0, 10)] - - def func(x, t): - return np.sum([ - np.prod([ - A * np.cos(omega * x), - np.exp(-k * omega**2 * t) - ]) - for A, omega in zip(get_As(), omegas) - ]) - - # Surface and graph - surface = always_redraw( - lambda: self.get_surface(axes, func) - ) - t_tracker = ValueTracker(0) - graph = always_redraw( - lambda: self.get_time_slice_graph( - axes, func, t_tracker.get_value(), - ) - ) - - surface.suspend_updating() - graph.suspend_updating() - - self.surface_func = func - self.surface = surface - self.graph = graph - self.t_tracker = t_tracker - self.A_trackers = A_trackers - self.omegas = omegas - - def show_solution(self): - axes = self.axes - surface = self.surface - graph = self.graph - t_tracker = self.t_tracker - get_t = t_tracker.get_value - - opacity_tracker = ValueTracker(0) - plane = always_redraw(lambda: Polygon( - *[ - axes.c2p(x, get_t(), T) - for x, T in [ - (0, 0), (TAU, 0), (TAU, 4), (0, 4) - ] - ], - stroke_width=0, - fill_color=WHITE, - fill_opacity=opacity_tracker.get_value(), - )) - - self.add(surface, plane, graph) - graph.resume_updating() - self.play( - opacity_tracker.set_value, 0.2, - ApplyMethod( - t_tracker.set_value, 1, - rate_func=linear - ), - run_time=1 - ) - self.play( - ApplyMethod( - t_tracker.set_value, 10, - rate_func=linear, - run_time=9 - ) - ) - self.wait() - - self.plane = plane - - def reference_boundary_conditions(self): - axes = self.axes - t_numbers = axes.y_axis.numbers - - lines = VGroup(*[ - Line( - axes.c2p(x, 0, 0), - axes.c2p(x, axes.y_max, 0), - stroke_width=3, - stroke_color=MAROON_B, - ) - for x in [0, axes.x_max] - ]) - surface_boundary_lines = always_redraw(lambda: VGroup(*[ - ParametricFunction( - lambda t: axes.c2p( - x, t, - self.surface_func(x, t) - ), - t_max=axes.y_max - ).match_style(self.graph) - for x in [0, axes.x_max] - ])) - # surface_boundary_lines.suspend_updating() - words = VGroup() - for line in lines: - word = TextMobject("Boundary") - word.set_stroke(BLACK, 3, background=True) - word.scale(1.5) - word.match_color(line) - word.rotate(90 * DEGREES, RIGHT) - word.rotate(90 * DEGREES, OUT) - word.next_to(line, OUT, SMALL_BUFF) - words.add(word) - - self.stop_ambient_camera_rotation() - self.move_camera( - theta=-45 * DEGREES, - added_anims=[ - LaggedStartMap(ShowCreation, lines), - LaggedStartMap( - FadeInFrom, words, - lambda m: (m, IN) - ), - FadeOut(t_numbers), - ] - ) - self.play( - LaggedStart(*[ - TransformFromCopy(l1, l2) - for l1, l2 in zip(lines, surface_boundary_lines) - ]) - ) - self.add(surface_boundary_lines) - self.wait() - self.move_camera( - theta=-70 * DEGREES, - ) - - self.surface_boundary_lines = surface_boundary_lines - - def reference_initial_condition(self): - plane = self.plane - t_tracker = self.t_tracker - - self.play( - t_tracker.set_value, 0, - run_time=2 - ) - plane.clear_updaters() - self.play(FadeOut(plane)) - - def ambiently_change_solution(self): - A_trackers = self.A_trackers - - def generate_A_updater(A, rate): - def update(m, dt): - m.total_time += dt - m.set_value( - 2 * A * np.sin(rate * m.total_time + PI / 6) - ) - return update - - rates = [0, 0.2] + [ - 0.5 + 0.5 * np.random.random() - for x in range(len(A_trackers) - 2) - ] - - for tracker, rate in zip(A_trackers, rates): - tracker.total_time = 0 - tracker.add_updater(generate_A_updater( - tracker.get_value(), - rate - )) - - self.add(*A_trackers) - self.surface_boundary_lines.resume_updating() - self.surface.resume_updating() - self.graph.resume_updating() - self.begin_ambient_camera_rotation(rate=0.01) - self.wait(30) - - -class AnalyzeSineCurve(TemperatureGraphScene): - CONFIG = { - "origin_point": 3 * LEFT, - "axes_config": { - "z_min": -1.5, - "z_max": 1.5, - "z_axis_config": { - "unit_size": 2, - "tick_frequency": 0.5, - } - }, - "tex_to_color_map": { - "{x}": GREEN, - "T": YELLOW, - "=": WHITE, - "0": WHITE, - "\\Delta t": WHITE, - "\\sin": WHITE, - "{t}": PINK, - } - } - - def construct(self): - self.setup_axes() - self.ask_about_sine_curve() - self.show_sine_wave_on_axes() - self.reference_curvature() - self.show_derivatives() - self.show_curvature_matching_height() - self.show_time_step_scalings() - self.smooth_evolution() - - def setup_axes(self): - axes = self.get_three_d_axes() - axes.rotate(90 * DEGREES, LEFT) - axes.shift(self.origin_point - axes.c2p(0, 0, 0)) - y_axis = axes.y_axis - y_axis.fade(1) - z_axis = axes.z_axis - z_axis.label.next_to(z_axis.get_end(), UP, SMALL_BUFF) - - self.add_axes_numbers(axes) - y_axis.remove(y_axis.numbers) - axes.z_axis.add_numbers( - *range(-1, 2), - direction=LEFT, - ) - - self.axes = axes - - def ask_about_sine_curve(self): - curve = FunctionGraph( - lambda t: np.sin(t), - x_min=0, - x_max=TAU, - ) - curve.move_to(DR) - curve.set_width(5) - curve.set_color(YELLOW) - question = TextMobject("What's so special?") - question.scale(1.5) - question.to_edge(UP) - question.shift(2 * LEFT) - arrow = Arrow( - question.get_bottom(), - curve.point_from_proportion(0.25) - ) - - self.play( - ShowCreation(curve), - Write(question, run_time=1), - GrowArrow(arrow), - ) - self.wait() - - self.quick_sine_curve = curve - self.question_group = VGroup(question, arrow) - - def show_sine_wave_on_axes(self): - axes = self.axes - graph = self.get_initial_state_graph( - axes, lambda x: np.sin(x) - ) - graph.set_stroke(width=4) - graph_label = TexMobject( - "T({x}, 0) = \\sin\\left({x}\\right)", - tex_to_color_map=self.tex_to_color_map, - ) - graph_label.next_to( - graph.point_from_proportion(0.25), UR, - buff=SMALL_BUFF, - ) - - v_line, x_tracker = self.get_v_line_with_x_tracker(graph) - - xs = VGroup( - *graph_label.get_parts_by_tex("x"), - axes.x_axis.label, - ) - - self.play( - Write(axes), - self.quick_sine_curve.become, graph, - FadeOutAndShift(self.question_group, UP), - ) - self.play( - FadeInFromDown(graph_label), - FadeIn(graph), - ) - self.remove(self.quick_sine_curve) - self.add(v_line) - self.play( - ApplyMethod( - x_tracker.set_value, TAU, - rate_func=lambda t: smooth(t, 3), - run_time=5, - ), - LaggedStartMap( - ShowCreationThenFadeAround, xs, - run_time=3, - lag_ratio=0.2, - ) - ) - self.remove(v_line, x_tracker) - self.wait() - - self.graph = graph - self.graph_label = graph_label - self.v_line = v_line - self.x_tracker = x_tracker - - def reference_curvature(self): - curve_segment, curve_x_tracker = \ - self.get_curve_segment_with_x_tracker(self.graph) - - self.add(curve_segment) - self.play( - curve_x_tracker.set_value, TAU, - run_time=5, - rate_func=lambda t: smooth(t, 3), - ) - self.play(FadeOut(curve_segment)) - - self.curve_segment = curve_segment - self.curve_x_tracker = curve_x_tracker - - def show_derivatives(self): - deriv1 = TexMobject( - "{\\partial T \\over \\partial {x}}({x}, 0)", - "= \\cos\\left({x}\\right)", - tex_to_color_map=self.tex_to_color_map, - ) - deriv2 = TexMobject( - "{\\partial^2 T \\over \\partial {x}^2}({x}, 0)", - "= -\\sin\\left({x}\\right)", - tex_to_color_map=self.tex_to_color_map, - ) - - deriv1.to_corner(UR) - deriv2.next_to( - deriv1, DOWN, - buff=0.75, - aligned_edge=LEFT, - ) - VGroup(deriv1, deriv2).shift(1.4 * RIGHT) - - self.play( - Animation(Group(*self.get_mobjects())), - FadeInFrom(deriv1, LEFT), - self.camera.frame_center.shift, 2 * RIGHT, - ) - self.wait() - self.play( - FadeInFrom(deriv2, UP) - ) - self.wait() - - self.deriv1 = deriv1 - self.deriv2 = deriv2 - - def show_curvature_matching_height(self): - axes = self.axes - graph = self.graph - curve_segment = self.curve_segment - curve_x_tracker = self.curve_x_tracker - - d2_graph = self.get_initial_state_graph( - axes, lambda x: -np.sin(x), - ) - dashed_d2_graph = DashedVMobject(d2_graph, num_dashes=50) - dashed_d2_graph.color_using_background_image(None) - dashed_d2_graph.set_stroke(RED, 2) - - vector, x_tracker = self.get_v_line_with_x_tracker( - d2_graph, - line_creator=lambda p1, p2: Arrow( - p1, p2, color=RED, buff=0 - ) - ) - - lil_vectors = self.get_many_lil_vectors(graph) - lil_vector = always_redraw( - lambda: self.get_lil_vector( - graph, x_tracker.get_value() - ) - ) - - d2_rect = SurroundingRectangle( - self.deriv2[-5:], - color=RED, - ) - self.play(ShowCreation(d2_rect)) - self.add(vector) - self.add(lil_vector) - self.add(curve_segment) - curve_x_tracker.set_value(0) - self.play( - ShowCreation(dashed_d2_graph), - x_tracker.set_value, TAU, - curve_x_tracker.set_value, TAU, - ShowIncreasingSubsets(lil_vectors[1:]), - run_time=8, - rate_func=linear, - ) - self.remove(vector) - self.remove(lil_vector) - self.add(lil_vectors) - self.play( - FadeOut(curve_segment), - FadeOut(d2_rect), - ) - - self.lil_vectors = lil_vectors - self.dashed_d2_graph = dashed_d2_graph - - def show_time_step_scalings(self): - axes = self.axes - graph_label = self.graph_label - dashed_d2_graph = self.dashed_d2_graph - lil_vectors = self.lil_vectors - graph = self.graph - - factor = 0.9 - - new_label = TexMobject( - "T({x}, \\Delta t) = c \\cdot \\sin\\left({x}\\right)", - tex_to_color_map=self.tex_to_color_map, - ) - final_label = TexMobject( - "T({x}, {t}) = (\\text{something}) \\cdot \\sin\\left({x}\\right)", - tex_to_color_map=self.tex_to_color_map, - ) - for label in (new_label, final_label): - label.shift( - graph_label.get_part_by_tex("=").get_center() - - label.get_part_by_tex("=").get_center() - ) - final_label.shift(1.5 * LEFT) - - h_lines = VGroup( - DashedLine(axes.c2p(0, 0, 1), axes.c2p(TAU, 0, 1)), - DashedLine(axes.c2p(0, 0, -1), axes.c2p(TAU, 0, -1)), - ) - - lil_vectors.add_updater(lambda m: m.become( - self.get_many_lil_vectors(graph) - )) - - i = 4 - self.play( - ReplacementTransform( - graph_label[:i], new_label[:i], - ), - ReplacementTransform( - graph_label[i + 1:i + 3], - new_label[i + 1:i + 3], - ), - FadeOutAndShift(graph_label[i], UP), - FadeInFrom(new_label[i], DOWN), - ) - self.play( - ReplacementTransform( - graph_label[i + 3:], - new_label[i + 4:] - ), - FadeInFromDown(new_label[i + 3]) - ) - self.play( - FadeOut(dashed_d2_graph), - FadeIn(h_lines), - ) - self.play( - graph.stretch, factor, 1, - h_lines.stretch, factor, 1, - ) - self.wait() - - # Repeat - last_coef = None - last_exp = None - delta_T = new_label.get_part_by_tex("\\Delta t") - c = new_label.get_part_by_tex("c")[0] - prefix = new_label[:4] - prefix.generate_target() - for x in range(5): - coef = Integer(x + 2) - exp = coef.copy().scale(0.7) - coef.next_to( - delta_T, LEFT, SMALL_BUFF, - aligned_edge=DOWN, - ) - exp.move_to(c.get_corner(UR), DL) - anims1 = [FadeInFrom(coef, 0.25 * DOWN)] - anims2 = [FadeInFrom(exp, 0.25 * DOWN)] - if last_coef: - anims1.append( - FadeOutAndShift(last_coef, 0.25 * UP) - ) - anims2.append( - FadeOutAndShift(last_exp, 0.25 * UP) - ) - last_coef = coef - last_exp = exp - prefix.target.next_to(coef, LEFT, SMALL_BUFF) - prefix.target.match_y(prefix) - anims1.append(MoveToTarget(prefix)) - - self.play(*anims1) - self.play( - graph.stretch, factor, 1, - h_lines.stretch, factor, 1, - *anims2, - ) - self.play( - ReplacementTransform( - new_label[:4], - final_label[:4], - ), - ReplacementTransform( - VGroup(last_coef, delta_T), - final_label.get_part_by_tex("{t}"), - ), - ReplacementTransform( - last_exp, - final_label.get_part_by_tex("something"), - ), - FadeOut(new_label.get_part_by_tex("\\cdot"), UP), - ReplacementTransform( - new_label[-4:], - final_label[-4:], - ), - ReplacementTransform( - new_label.get_part_by_tex("="), - final_label.get_part_by_tex("="), - ), - ReplacementTransform( - new_label.get_part_by_tex(")"), - final_label.get_part_by_tex(")"), - ), - ) - final_label.add_background_rectangle(opacity=1) - self.add(final_label) - self.wait() - - group = VGroup(graph, h_lines) - group.add_updater(lambda m, dt: m.stretch( - (1 - 0.1 * dt), 1 - )) - self.add(group) - self.wait(10) - - def smooth_evolution(self): - pass - - # - def get_rod(self, temp_func): - pass - - def get_v_line_with_x_tracker(self, graph, line_creator=DashedLine): - axes = self.axes - x_min = axes.x_axis.p2n(graph.get_start()) - x_max = axes.x_axis.p2n(graph.get_end()) - x_tracker = ValueTracker(x_min) - get_x = x_tracker.get_value - v_line = always_redraw(lambda: line_creator( - axes.c2p(get_x(), 0, 0), - graph.point_from_proportion( - inverse_interpolate( - x_min, x_max, get_x() - ) - ), - )) - return v_line, x_tracker - - def get_curve_segment_with_x_tracker(self, graph, delta_x=0.5): - axes = self.axes - x_min = axes.x_axis.p2n(graph.get_start()) - x_max = axes.x_axis.p2n(graph.get_end()) - x_tracker = ValueTracker(x_min) - get_x = x_tracker.get_value - - def x2a(x): - return inverse_interpolate(x_min, x_max, x) - - curve = VMobject( - stroke_color=WHITE, - stroke_width=5 - ) - curve.add_updater(lambda m: m.pointwise_become_partial( - graph, - max(x2a(get_x() - delta_x), 0), - min(x2a(get_x() + delta_x), 1), - )) - return curve, x_tracker - - def get_lil_vector(self, graph, x): - x_axis = self.axes.x_axis - point = graph.point_from_proportion(x / TAU) - x_axis_point = x_axis.n2p(x_axis.p2n(point)) - return Arrow( - point, - interpolate( - point, x_axis_point, 0.5, - ), - buff=0, - color=RED - ) - - def get_many_lil_vectors(self, graph, n=13): - return VGroup(*[ - self.get_lil_vector(graph, x) - for x in np.linspace(0, TAU, n) - ]) - - -class SineWaveScaledByExp(TemperatureGraphScene): - CONFIG = { - "axes_config": { - "z_min": -1.5, - "z_max": 1.5, - "z_axis_config": { - "unit_size": 2, - "tick_frequency": 0.5, - "label_direction": LEFT, - }, - "y_axis_config": { - "label_direction": RIGHT, - }, - }, - "k": 0.3, - } - - def construct(self): - self.setup_axes() - self.setup_camera() - self.show_sine_wave() - self.show_decay_surface() - self.linger_at_end() - - def setup_axes(self): - axes = self.get_three_d_axes() - - # Add number labels - self.add_axes_numbers(axes) - for axis in [axes.x_axis, axes.y_axis]: - axis.numbers.rotate( - 90 * DEGREES, - axis=axis.get_vector(), - about_point=axis.point_from_proportion(0.5) - ) - axis.numbers.set_shade_in_3d(True) - axes.z_axis.add_numbers(*range(-1, 2)) - for number in axes.z_axis.numbers: - number.rotate(90 * DEGREES, RIGHT) - - axes.z_axis.label.next_to( - axes.z_axis.get_end(), OUT, - ) - - # Input plane - axes.input_plane.set_opacity(0.25) - self.add(axes.input_plane) - - # Shift into place - # axes.shift(5 * LEFT) - self.axes = axes - self.add(axes) - - def setup_camera(self): - self.set_camera_orientation( - phi=80 * DEGREES, - theta=-80 * DEGREES, - distance=50, - ) - self.camera.frame.move_to(2 * RIGHT) - - def show_sine_wave(self): - time_tracker = ValueTracker(0) - graph = always_redraw( - lambda: self.get_time_slice_graph( - self.axes, - self.sin_exp, - t=time_tracker.get_value(), - ) - ) - graph.suspend_updating() - - graph_label = TexMobject("\\sin(x)") - graph_label.set_color(BLUE) - graph_label.rotate(90 * DEGREES, RIGHT) - graph_label.next_to( - graph.point_from_proportion(0.25), - OUT, - SMALL_BUFF, - ) - - self.play( - ShowCreation(graph), - FadeInFrom(graph_label, IN) - ) - self.wait() - graph.resume_updating() - - self.time_tracker = time_tracker - self.graph = graph - - def show_decay_surface(self): - time_tracker = self.time_tracker - axes = self.axes - - plane = Rectangle() - plane.rotate(90 * DEGREES, RIGHT) - plane.set_stroke(width=0) - plane.set_fill(WHITE, 0.2) - plane.match_depth(axes.z_axis) - plane.match_width(axes.x_axis, stretch=True) - plane.add_updater( - lambda p: p.move_to(axes.c2p( - 0, - time_tracker.get_value(), - 0, - ), LEFT) - ) - - time_slices = VGroup(*[ - self.get_time_slice_graph( - self.axes, - self.sin_exp, - t=t, - ) - for t in range(0, 10) - ]) - surface_t_tracker = ValueTracker(0) - surface = always_redraw( - lambda: self.get_surface( - self.axes, - self.sin_exp, - v_max=surface_t_tracker.get_value(), - ).set_stroke(opacity=0) - ) - - exp_graph = ParametricFunction( - lambda t: axes.c2p( - PI / 2, - t, - self.sin_exp(PI / 2, t) - ), - t_min=axes.y_min, - t_max=axes.y_max, - ) - exp_graph.set_stroke(RED, 3) - exp_graph.set_shade_in_3d(True) - - exp_label = TexMobject("e^{-\\alpha t}") - exp_label.scale(1.5) - exp_label.set_color(RED) - exp_label.rotate(90 * DEGREES, RIGHT) - exp_label.rotate(90 * DEGREES, OUT) - exp_label.next_to( - exp_graph.point_from_proportion(0.3), - OUT + UP, - ) - - self.move_camera( - theta=-25 * DEGREES, - ) - self.add(surface) - self.add(plane) - self.play( - surface_t_tracker.set_value, axes.y_max, - time_tracker.set_value, axes.y_max, - ShowIncreasingSubsets( - time_slices, - int_func=np.ceil, - ), - run_time=5, - rate_func=linear, - ) - surface.clear_updaters() - - self.play( - ShowCreation(exp_graph), - FadeOut(plane), - FadeInFrom(exp_label, IN), - time_slices.set_stroke, {"width": 1}, - ) - - def linger_at_end(self): - self.wait() - self.begin_ambient_camera_rotation(rate=-0.02) - self.wait(20) - - # - def sin_exp(self, x, t): - return np.sin(x) * np.exp(-self.k * t) - - -class BoundaryConditionReference(ShowEvolvingTempGraphWithArrows): - def construct(self): - self.setup_axes() - self.setup_graph() - - rod = self.get_rod(0, 10) - self.color_rod_by_graph(rod) - - boundary_points = [ - rod.get_right(), - rod.get_left(), - ] - boundary_dots = VGroup(*[ - Dot(point, radius=0.2) - for point in boundary_points - ]) - boundary_arrows = VGroup(*[ - Vector(2 * DOWN).next_to(dot, UP) - for dot in boundary_dots - ]) - boundary_arrows.set_stroke(YELLOW, 10) - - words = TextMobject( - "Different rules\\\\", - "at the boundary", - ) - words.scale(1.5) - words.to_edge(UP) - - # self.add(self.axes) - # self.add(self.graph) - self.add(rod) - self.play(FadeInFromDown(words)) - self.play( - LaggedStartMap(GrowArrow, boundary_arrows), - LaggedStartMap(GrowFromCenter, boundary_dots), - lag_ratio=0.3, - run_time=1, - ) - self.wait() - - -class SimulateRealSineCurve(ShowEvolvingTempGraphWithArrows): - CONFIG = { - "axes_config": { - "x_min": 0, - "x_max": TAU, - "x_axis_config": { - "unit_size": 1.5, - "include_tip": False, - "tick_frequency": PI / 4, - }, - "y_min": -1.5, - "y_max": 1.5, - "y_axis_config": { - "tick_frequency": 0.5, - "unit_size": 2, - }, - }, - "graph_x_min": 0, - "graph_x_max": TAU, - "arrow_xs": np.linspace(0, TAU, 13), - "rod_opacity": 0.5, - "wait_time": 30, - "alpha": 0.5, - } - - def construct(self): - self.add_axes() - self.add_graph() - self.add_clock() - self.add_rod() - self.add_arrows() - self.initialize_updaters() - self.let_play() - - def add_labels_to_axes(self): - x_axis = self.axes.x_axis - x_axis.add(*[ - TexMobject(tex).scale(0.5).next_to( - x_axis.n2p(n), - DOWN, - buff=MED_SMALL_BUFF - ) - for tex, n in [ - ("\\tau \\over 4", TAU / 4), - ("\\tau \\over 2", TAU / 2), - ("3 \\tau \\over 4", 3 * TAU / 4), - ("\\tau", TAU), - ] - ]) - - def add_axes(self): - super().add_axes() - self.add_labels_to_axes() - - def add_rod(self): - super().add_rod() - self.rod.set_opacity(self.rod_opacity) - self.rod.set_stroke(width=0) - - def initial_function(self, x): - return np.sin(x) - - def y_to_color(self, y): - return temperature_to_color(0.8 * y) - - -class StraightLine3DGraph(TemperatureGraphScene): - CONFIG = { - "axes_config": { - "z_min": 0, - "z_max": 10, - "z_axis_config": { - 'unit_size': 0.5, - } - }, - "c": 1.2, - } - - def construct(self): - axes = self.get_three_d_axes() - axes.add(axes.input_plane) - axes.move_to(2 * IN + UP, IN) - surface = self.get_surface( - axes, self.func, - ) - initial_graph = self.get_initial_state_graph( - axes, lambda x: self.func(x, 0) - ) - - plane = self.get_const_time_plane(axes) - initial_graph.add_updater( - lambda m: m.move_to(plane, IN) - ) - - self.set_camera_orientation( - phi=80 * DEGREES, - theta=-100 * DEGREES, - ) - self.begin_ambient_camera_rotation() - - self.add(axes) - self.add(initial_graph) - self.play( - TransformFromCopy(initial_graph, surface) - ) - self.add(surface, initial_graph) - self.wait() - - self.play( - FadeIn(plane), - ApplyMethod( - plane.t_tracker.set_value, 10, - rate_func=linear, - run_time=10, - ) - ) - self.play(FadeOut(plane)) - self.wait(15) - - def func(self, x, t): - return self.c * x - - -class SimulateLinearGraph(SimulateRealSineCurve): - CONFIG = { - "axes_config": { - "y_min": 0, - "y_max": 3, - "y_axis_config": { - "tick_frequency": 0.5, - "unit_size": 2, - }, - }, - "arrow_scale_factor": 2, - "alpha": 1, - "wait_time": 40, - "step_size": 0.02, - } - - # def let_play(self): - # pass - - def add_labels_to_axes(self): - pass - - def y_to_color(self, y): - return temperature_to_color(0.8 * (y - 1.5)) - - def initial_function(self, x): - axes = self.axes - y_max = axes.y_max - x_max = axes.x_max - slope = y_max / x_max - return slope * x - - -class EmphasizeBoundaryPoints(SimulateLinearGraph): - CONFIG = { - "wait_time": 30, - } - - def let_play(self): - rod = self.rod - self.graph.update(0.01) - self.arrows.update() - - to_update = VGroup( - self.graph, - self.arrows, - self.time_label, - ) - for mob in to_update: - mob.suspend_updating() - - dots = VGroup( - Dot(rod.get_left()), - Dot(rod.get_right()), - ) - for dot in dots: - dot.set_height(0.2) - dot.set_color(YELLOW) - - words = TextMobject( - "Different rules\\\\" - "at the boundary" - ) - words.next_to(rod, UP) - - arrows = VGroup(*[ - Arrow( - words.get_corner(corner), - dot.get_top(), - path_arc=u * 60 * DEGREES, - ) - for corner, dot, u in zip( - [LEFT, RIGHT], dots, [1, -1] - ) - ]) - - self.add(words) - self.play( - LaggedStartMap( - FadeInFromLarge, dots, - scale_factor=5, - ), - LaggedStartMap( - ShowCreation, arrows, - ), - lag_ratio=0.4 - ) - self.wait() - - for mob in to_update: - mob.resume_updating() - - super().let_play() - - -class FlatEdgesContinuousEvolution(ShowEvolvingTempGraphWithArrows): - CONFIG = { - "wait_time": 30, - "freq_amplitude_pairs": [ - (1, 0.5), - (2, 1), - (3, 0.5), - (4, 0.3), - ], - } - - def construct(self): - self.add_axes() - self.add_graph() - self.add_clock() - self.add_rod() - self.initialize_updaters() - self.add_boundary_lines() - self.let_play() - - def add_boundary_lines(self): - - lines = VGroup(*[ - Line(LEFT, RIGHT) - for x in range(2) - ]) - lines.set_width(1.5) - lines.set_stroke(WHITE, 5, opacity=0.5) - lines.add_updater(self.update_lines) - - turn_animation_into_updater( - ShowCreation(lines, run_time=2) - ) - self.add(lines) - - def update_lines(self, lines): - graph = self.graph - lines[0].move_to(graph.get_start()) - lines[1].move_to(graph.get_end()) - - def initial_function(self, x): - return ShowEvolvingTempGraphWithArrows.initial_function(self, x) - - -class MoreAccurateTempFlowWithArrows(ShowEvolvingTempGraphWithArrows): - CONFIG = { - "arrow_scale_factor": 1, - "max_magnitude": 1, - "freq_amplitude_pairs": [ - (1, 0.5), - (2, 1), - (3, 0.5), - (4, 0.3), - ], - } - - def setup_axes(self): - super().setup_axes() - y_axis = self.axes.y_axis - y_axis.remove(y_axis.label) - - -class SlopeToHeatFlow(FlatEdgesContinuousEvolution): - CONFIG = { - "wait_time": 20, - "xs": [1.75, 5.25, 7.8], - "axes_config": { - "x_min": 0, - "x_max": 10, - "x_axis_config": { - "include_tip": False, - } - }, - "n_arrows": 10, - "random_seed": 1, - } - - def construct(self): - self.add_axes() - self.add_graph() - self.add_rod() - # - self.show_slope_labels() - self.show_heat_flow() - - def show_slope_labels(self): - axes = self.axes - # axes.x_axis.add_numbers() - graph = self.graph - xs = self.xs - - lines = VGroup(*[ - TangentLine( - graph, - inverse_interpolate( - axes.x_min, - axes.x_max, - x - ), - length=2, - stroke_opacity=0.75, - ) - for x in xs - ]) - - slope_words = VGroup() - for line in lines: - slope = line.get_slope() - word = TextMobject( - "Slope =", - "${:.2}$".format(slope) - ) - if slope > 0: - word[1].set_color(GREEN) - else: - word[1].set_color(RED) - - word.set_width(line.get_length()) - word.next_to(ORIGIN, UP, SMALL_BUFF) - word.rotate( - line.get_angle(), - about_point=ORIGIN, - ) - word.shift(line.get_center()) - slope_words.add(word) - - self.play( - LaggedStartMap(ShowCreation, lines), - LaggedStartMap(Write, slope_words), - lag_ratio=0.5, - run_time=1, - ) - self.wait() - - self.lines = lines - self.slope_words = slope_words - - def show_heat_flow(self): - axes = self.axes - xs = self.xs - slope_words = self.slope_words - tan_lines = self.lines - - slopes = map(Line.get_slope, self.lines) - flow_rates = [-s for s in slopes] - - points = list(map(axes.x_axis.n2p, xs)) - v_lines = VGroup(*[ - Line( - line.get_center(), point, - stroke_width=1, - ) - for point, line in zip(points, tan_lines) - ]) - - flow_words = VGroup(*[ - TextMobject("Heat flow").next_to( - point, DOWN - ).scale(0.8) - for point in points - ]) - flow_mobjects = VGroup(*[ - self.get_flow(x, flow_rate) - for x, flow_rate in zip(xs, flow_rates) - ]) - - self.add(flow_mobjects) - self.wait() - self.play( - LaggedStart(*[ - TransformFromCopy( - sw[0], fw[0] - ) - for sw, fw in zip(slope_words, flow_words) - ]), - LaggedStartMap(ShowCreation, v_lines), - lag_ratio=0.4, - run_time=2, - ) - self.wait(self.wait_time) - - def get_flow(self, x, flow_rate): - return VGroup(*[ - self.get_single_flow_arrow( - x, flow_rate, - t_offset=to - ) - for to in np.linspace(0, 5, self.n_arrows) - ]) - - def get_single_flow_arrow(self, x, flow_rate, t_offset): - axes = self.axes - point = axes.x_axis.n2p(x) - - h_shift = 0.5 - v_shift = 0.4 * np.random.random() + 0.1 - - arrow = Vector(0.5 * RIGHT * np.sign(flow_rate)) - arrow.move_to(point) - arrow.shift(v_shift * UP) - lp = arrow.get_center() + h_shift * LEFT - rp = arrow.get_center() + h_shift * RIGHT - lc = self.rod_point_to_color(lp) - rc = self.rod_point_to_color(rp) - - run_time = 3 / flow_rate - animation = UpdateFromAlphaFunc( - arrow, - lambda m, a: m.move_to( - interpolate(lp, rp, a) - ).set_color( - interpolate_color(lc, rc, a) - ).set_opacity(there_and_back(a)), - run_time=run_time, - ) - result = cycle_animation(animation) - animation.total_time += t_offset - return result - - -class CloserLookAtStraightLine(SimulateLinearGraph): - def construct(self): - self.add_axes() - self.add_graph() - self.add_clock() - self.add_rod() - # self.initialize_updaters() - # - self.show_t_eq_0_state() - self.show_t_gt_0_state() - - def show_t_eq_0_state(self): - t_eq = TexMobject("t", "=", "0") - t_eq.next_to(self.time_label, DOWN) - - circles = VGroup(*[ - Circle( - radius=0.25, - stroke_color=YELLOW, - ) - for x in range(2) - ]) - circles.add_updater(self.attach_to_endpoints) - - self.play(Write(t_eq)) - self.play(ShowCreation(circles)) - self.wait() - self.play(FadeOut(circles)) - - self.circles = circles - self.t_eq = t_eq - - def show_t_gt_0_state(self): - # circles = self.circles - t_eq = self.t_eq - - t_ineq = TexMobject("t", ">", "0") - t_ineq.move_to(t_eq) - - slope_lines = VGroup(*[ - Line(LEFT, RIGHT) - for x in range(2) - ]) - slope_lines.set_opacity(0.5) - slope_lines.add_updater(self.attach_to_endpoints) - - self.remove(t_eq) - self.add(t_ineq) - - self.initialize_updaters() - self.run_clock(0.1) - for mob in self.mobjects: - mob.suspend_updating() - self.wait() - - self.add(slope_lines) - self.add(self.clock, self.time_label, t_ineq) - self.play(ShowCreation(slope_lines)) - for mob in self.mobjects: - mob.resume_updating() - - self.run_clock(self.wait_time) - - # - def attach_to_endpoints(self, mobs): - points = [ - self.graph.get_start(), - self.graph.get_end(), - ] - for mob, point in zip(mobs, points): - mob.move_to(point) - return mobs - - -class ManipulateSinExpSurface(TemperatureGraphScene): - CONFIG = { - "axes_config": { - "z_max": 1.25, - "z_min": -1.25, - "z_axis_config": { - "unit_size": 2.5, - "tick_frequency": 0.5, - }, - "x_axis_config": { - # "unit_size": 1.5, - "unit_size": 1.0, - "tick_frequency": 1, - }, - "x_max": 10, - "y_max": 15, - }, - "alpha": 0.2, - "tex_mobject_config": { - "tex_to_color_map": { - "{x}": GREEN, - "{t}": YELLOW, - "\\omega": MAROON_B, - "^2": WHITE, - }, - }, - "graph_config": {}, - "initial_phi": -90 * DEGREES, - "initial_omega": 1, - } - - def construct(self): - self.setup_axes() - self.add_sine_wave() - self.shift_sine_to_cosine() - self.show_derivatives_of_cos() - self.show_cos_exp_surface() - self.change_frequency() - self.talk_through_omega() - self.show_cos_omega_derivatives() - self.show_rebalanced_exp() - - def setup_axes(self): - axes = self.get_three_d_axes(include_numbers=True) - axes.x_axis.remove(axes.x_axis.numbers) - L = TexMobject("L") - L.rotate(90 * DEGREES, RIGHT) - L.next_to(axes.x_axis.get_end(), IN) - axes.x_axis.label = L - axes.x_axis.add(L) - - axes.shift(5 * LEFT + 0.5 * IN) - axes.z_axis.label[0].remove( - *axes.z_axis.label[0][1:] - ) - axes.z_axis.label.next_to( - axes.z_axis.get_end(), OUT - ) - axes.z_axis.add_numbers( - *np.arange(-1, 1.5, 0.5), - direction=LEFT, - number_config={ - "num_decimal_places": 1, - } - ) - for number in axes.z_axis.numbers: - number.rotate(90 * DEGREES, RIGHT) - - self.set_camera_orientation( - phi=90 * DEGREES, - theta=-90 * DEGREES, - distance=100, - ) - - self.add(axes) - self.remove(axes.y_axis) - self.axes = axes - - def add_sine_wave(self): - self.initialize_parameter_trackers() - graph = self.get_graph() - sin_label = TexMobject( - "\\sin\\left({x}\\right)", - **self.tex_mobject_config, - ) - sin_label.shift(2 * LEFT + 2.75 * UP) - - self.add_fixed_in_frame_mobjects(sin_label) - graph.suspend_updating() - self.play( - ShowCreation(graph), - Write(sin_label), - ) - graph.resume_updating() - self.wait() - - self.sin_label = sin_label - self.graph = graph - - def shift_sine_to_cosine(self): - graph = self.graph - sin_label = self.sin_label - - sin_cross = Cross(sin_label) - sin_cross.add_updater( - lambda m: m.move_to(sin_label) - ) - cos_label = TexMobject( - "\\cos\\left({x}\\right)", - **self.tex_mobject_config, - ) - cos_label.move_to(sin_label, LEFT) - cos_label.shift(LEFT) - # cos_label.shift( - # axes.c2p(0, 0) - axes.c2p(PI / 2, 0), - # ) - - left_tangent = Line(ORIGIN, RIGHT) - left_tangent.set_stroke(WHITE, 5) - - self.add_fixed_in_frame_mobjects(cos_label) - self.play( - ApplyMethod( - self.phi_tracker.set_value, 0, - ), - FadeOutAndShift(sin_label, LEFT), - FadeInFrom(cos_label, RIGHT), - run_time=2, - ) - left_tangent.move_to(graph.get_start(), LEFT) - self.play(ShowCreation(left_tangent)) - self.play(FadeOut(left_tangent)) - - self.cos_label = cos_label - - def show_derivatives_of_cos(self): - cos_label = self.cos_label - cos_exp_label = TexMobject( - "\\cos\\left({x}\\right)", - "e^{-\\alpha {t}}", - **self.tex_mobject_config - ) - cos_exp_label.move_to(cos_label, LEFT) - - ddx_group, ddx_exp_group = [ - self.get_ddx_computation_group( - label, - *[ - TexMobject( - s + "\\left({x}\\right)" + exp, - **self.tex_mobject_config, - ) - for s in ["-\\sin", "-\\cos"] - ] - ) - for label, exp in [ - (cos_label, ""), - (cos_exp_label, "e^{-\\alpha {t}}"), - ] - ] - - self.add_fixed_in_frame_mobjects(ddx_group) - self.play(FadeIn(ddx_group)) - self.wait() - - # Cos exp - transforms = [ - ReplacementTransform( - cos_label, cos_exp_label, - ), - ReplacementTransform( - ddx_group, ddx_exp_group, - ), - ] - for trans in transforms: - trans.begin() - self.add_fixed_in_frame_mobjects(trans.mobject) - self.play(*transforms) - self.add_fixed_in_frame_mobjects( - cos_exp_label, ddx_exp_group - ) - self.remove_fixed_in_frame_mobjects( - cos_label, - *[ - trans.mobject - for trans in transforms - ] - ) - - self.cos_exp_label = cos_exp_label - self.ddx_exp_group = ddx_exp_group - - def show_cos_exp_surface(self): - axes = self.axes - - surface = self.get_cos_exp_surface() - self.add(surface) - surface.max_t_tracker.set_value(0) - self.move_camera( - phi=85 * DEGREES, - theta=-80 * DEGREES, - added_anims=[ - ApplyMethod( - surface.max_t_tracker.set_value, - axes.y_max, - run_time=4, - ), - Write(axes.y_axis), - ] - ) - self.wait() - - self.surface = surface - - def change_frequency(self): - cos_exp_label = self.cos_exp_label - ddx_exp_group = self.ddx_exp_group - omega_tracker = self.omega_tracker - - cos_omega = TexMobject( - "\\cos\\left(", - "\\omega", "\\cdot", "{x}", - "\\right)", - **self.tex_mobject_config - ) - cos_omega.move_to(cos_exp_label, LEFT) - - omega = cos_omega.get_part_by_tex("\\omega") - brace = Brace(omega, UP, buff=SMALL_BUFF) - omega_decimal = always_redraw( - lambda: DecimalNumber( - omega_tracker.get_value(), - color=omega.get_color(), - ).next_to(brace, UP, SMALL_BUFF) - ) - - self.add_fixed_in_frame_mobjects( - cos_omega, - brace, - omega_decimal, - ) - self.play( - self.camera.phi_tracker.set_value, 90 * DEGREES, - self.camera.theta_tracker.set_value, -90 * DEGREES, - FadeOut(self.surface), - FadeOut(self.axes.y_axis), - FadeOut(cos_exp_label), - FadeOut(ddx_exp_group), - FadeIn(cos_omega), - GrowFromCenter(brace), - FadeInFromDown(omega_decimal) - ) - for n in [2, 6, 1, 4]: - freq = n * PI / self.axes.x_max - self.play( - omega_tracker.set_value, freq, - run_time=2 - ) - self.wait() - self.wait() - - self.cos_omega = cos_omega - self.omega_brace = brace - - def talk_through_omega(self): - axes = self.axes - - x_tracker = ValueTracker(0) - get_x = x_tracker.get_value - v_line = always_redraw(lambda: DashedLine( - axes.c2p(get_x(), 0, 0), - axes.c2p(get_x(), 0, self.func(get_x(), 0)), - )) - - x = self.cos_omega.get_part_by_tex("{x}") - brace = Brace(x, DOWN) - x_decimal = always_redraw( - lambda: DecimalNumber( - get_x(), - color=x.get_color() - ).next_to(brace, DOWN, SMALL_BUFF) - ) - - self.add(v_line) - self.add_fixed_in_frame_mobjects(brace, x_decimal) - self.play( - x_tracker.set_value, 5, - run_time=5, - rate_func=linear, - ) - self.play( - FadeOut(v_line), - FadeOut(brace), - FadeOut(x_decimal), - ) - self.remove_fixed_in_frame_mobjects( - brace, x_decimal - ) - - def show_cos_omega_derivatives(self): - cos_omega = self.cos_omega - ddx_omega_group = self.get_ddx_computation_group( - cos_omega, - *[ - TexMobject( - s + "\\left(\\omega \\cdot {x}\\right)", - **self.tex_mobject_config, - ) - for s in ["-\\omega \\sin", "-\\omega^2 \\cos"] - ] - ) - - omega_squared = ddx_omega_group[-1][1:3] - rect = SurroundingRectangle(omega_squared) - - self.add_fixed_in_frame_mobjects(ddx_omega_group) - self.play(FadeIn(ddx_omega_group)) - self.wait() - self.add_fixed_in_frame_mobjects(rect) - self.play(ShowCreationThenFadeOut(rect)) - self.wait() - - self.ddx_omega_group = ddx_omega_group - - def show_rebalanced_exp(self): - cos_omega = self.cos_omega - ddx_omega_group = self.ddx_omega_group - - cos_exp = TexMobject( - "\\cos\\left(", - "\\omega", "\\cdot", "{x}", - "\\right)", - "e^{-\\alpha \\omega^2 {t}}", - **self.tex_mobject_config - ) - cos_exp.move_to(cos_omega, DL) - - self.add_fixed_in_frame_mobjects(cos_exp) - self.play( - FadeOut(cos_omega), - FadeOut(ddx_omega_group), - FadeIn(cos_exp), - ) - self.remove_fixed_in_frame_mobjects( - cos_omega, - ddx_omega_group, - ) - - self.play( - FadeIn(self.surface), - FadeIn(self.axes.y_axis), - VGroup( - cos_exp, - self.omega_brace, - ).shift, 4 * RIGHT, - self.camera.phi_tracker.set_value, 80 * DEGREES, - self.camera.theta_tracker.set_value, -80 * DEGREES, - ) - self.wait() - self.play( - self.omega_tracker.set_value, TAU / 10, - run_time=6, - ) - - # - def initialize_parameter_trackers(self): - self.phi_tracker = ValueTracker( - self.initial_phi - ) - self.omega_tracker = ValueTracker( - self.initial_omega - ) - self.t_tracker = ValueTracker(0) - - def get_graph(self): - return always_redraw( - lambda: self.get_time_slice_graph( - self.axes, self.func, - t=self.t_tracker.get_value(), - **self.graph_config - ) - ) - - def get_cos_exp_surface(self): - max_t_tracker = ValueTracker(self.axes.y_max) - surface = always_redraw( - lambda: self.get_surface( - self.axes, - self.func, - v_max=max_t_tracker.get_value(), - ) - ) - surface.max_t_tracker = max_t_tracker - return surface - - def func(self, x, t): - phi = self.phi_tracker.get_value() - omega = self.omega_tracker.get_value() - alpha = self.alpha - return op.mul( - np.cos(omega * (x + phi)), - np.exp(-alpha * (omega**2) * t) - ) - - def get_ddx_computation_group(self, f, df, ddf): - arrows = VGroup(*[ - Vector(0.5 * RIGHT) for x in range(2) - ]) - group = VGroup( - arrows[0], df, arrows[1], ddf - ) - group.arrange(RIGHT) - group.next_to(f, RIGHT) - - for arrow in arrows: - label = TexMobject( - "\\partial \\over \\partial {x}", - **self.tex_mobject_config, - ) - label.scale(0.5) - label.next_to(arrow, UP, SMALL_BUFF) - arrow.add(label) - - group.arrows = arrows - group.funcs = VGroup(df, ddf) - - return group - - -class ShowFreq1CosExpDecay(ManipulateSinExpSurface): - CONFIG = { - "freq": 1, - "alpha": 0.2, - } - - def construct(self): - self.setup_axes() - self.add_back_y_axis() - self.initialize_parameter_trackers() - self.phi_tracker.set_value(0) - self.omega_tracker.set_value( - self.freq * TAU / 10, - ) - # - self.show_decay() - - def add_back_y_axis(self): - axes = self.axes - self.add(axes.y_axis) - self.remove(axes.y_axis.numbers) - self.remove(axes.y_axis.label) - - def show_decay(self): - axes = self.axes - t_tracker = self.t_tracker - t_max = self.axes.y_max - - graph = always_redraw( - lambda: self.get_time_slice_graph( - axes, - self.func, - t=t_tracker.get_value(), - stroke_width=5, - ) - ) - - surface = self.get_surface( - self.axes, self.func, - ) - plane = self.get_const_time_plane(axes) - - self.add(surface, plane, graph) - - self.set_camera_orientation( - phi=80 * DEGREES, - theta=-85 * DEGREES, - ) - self.begin_ambient_camera_rotation(rate=0.01) - self.play( - t_tracker.set_value, t_max, - plane.t_tracker.set_value, t_max, - run_time=t_max, - rate_func=linear, - ) - - -class ShowFreq2CosExpDecay(ShowFreq1CosExpDecay): - CONFIG = { - "freq": 2, - } - - -class ShowFreq4CosExpDecay(ShowFreq1CosExpDecay): - CONFIG = { - "freq": 4, - } - - -class ShowHarmonics(SimulateRealSineCurve): - CONFIG = { - "rod_opacity": 0.75, - "initial_omega": 1.27, - "default_n_rod_pieces": 32, - } - - def construct(self): - self.add_axes() - self.add_graph() - self.add_rod() - self.rod.add_updater(self.color_rod_by_graph) - # - self.show_formula() - - def add_graph(self): - omega_tracker = ValueTracker(self.initial_omega) - get_omega = omega_tracker.get_value - graph = always_redraw( - lambda: self.axes.get_graph( - lambda x: np.cos(get_omega() * x), - x_min=self.graph_x_min, - x_max=self.graph_x_max, - step_size=self.step_size, - discontinuities=[5], - ).color_using_background_image("VerticalTempGradient") - ) - - self.add(graph) - self.graph = graph - self.omega_tracker = omega_tracker - - def show_formula(self): - rod = self.rod - graph = self.graph - axes = self.axes - omega_tracker = self.omega_tracker - L = TAU - - formula = TexMobject( - "=", "\\cos\\left(", - "2(\\pi / L)", "\\cdot", "{x}", - "\\right)", - tex_to_color_map={ - "{x}": GREEN, - } - ) - formula.next_to( - self.axes.y_axis.label, RIGHT, SMALL_BUFF - ) - omega_part = formula.get_part_by_tex("\\pi") - omega_brace = Brace(omega_part, DOWN) - omega = TexMobject("\\omega") - omega.set_color(MAROON_B) - omega.next_to(omega_brace, DOWN, SMALL_BUFF) - formula.remove(omega_part) - - pi_over_L = TexMobject("(\\pi / L)") - pi_over_L.move_to(omega_part) - pi_over_L.match_color(omega) - - self.add(formula) - self.add(omega_brace) - self.add(omega) - - self.remove(graph) - self.play(GrowFromEdge(rod, LEFT)) - self.play( - ShowCreationThenFadeAround(axes.x_axis.label) - ) - self.wait() - self.play(FadeIn(graph)) - self.play(FadeInFromDown(pi_over_L)) - self.play( - omega_tracker.set_value, PI / L, - run_time=2 - ) - self.wait() - - # Show x - x_tracker = ValueTracker(0) - tip = ArrowTip( - start_angle=-90 * DEGREES, - color=WHITE, - ) - tip.add_updater(lambda m: m.move_to( - axes.x_axis.n2p(x_tracker.get_value()), - DOWN, - )) - x_sym = TexMobject("x") - x_sym.set_color(GREEN) - x_sym.add_background_rectangle(buff=SMALL_BUFF) - x_sym.add_updater(lambda m: m.next_to(tip, UP, SMALL_BUFF)) - - self.play( - Write(tip), - Write(x_sym), - ) - self.play( - x_tracker.set_value, L, - run_time=3, - ) - self.wait() - self.play(TransformFromCopy( - x_sym, formula.get_part_by_tex("{x}") - )) - self.play( - FadeOut(tip), - FadeOut(x_sym), - ) - - # Harmonics - pi_over_L.generate_target() - n_sym = Integer(2) - n_sym.match_color(pi_over_L) - group = VGroup(n_sym, pi_over_L.target) - group.arrange(RIGHT, buff=SMALL_BUFF) - group.move_to(pi_over_L) - - self.play( - MoveToTarget(pi_over_L), - FadeInFromDown(n_sym), - ApplyMethod( - omega_tracker.set_value, 2 * PI / L, - run_time=2, - ) - ) - self.wait() - for n in [*range(3, 9), 0]: - new_n_sym = Integer(n) - new_n_sym.move_to(n_sym, DR) - new_n_sym.match_style(n_sym) - self.play( - FadeOutAndShift(n_sym, UP), - FadeInFrom(new_n_sym, DOWN), - omega_tracker.set_value, n * PI / L, - ) - self.wait() - n_sym = new_n_sym - - # - def add_labels_to_axes(self): - x_axis = self.axes.x_axis - L = TexMobject("L") - L.next_to(x_axis.get_end(), DOWN) - x_axis.add(L) - x_axis.label = L - - -class ShowHarmonicSurfaces(ManipulateSinExpSurface): - CONFIG = { - "alpha": 0.2, - "initial_phi": 0, - "initial_omega": PI / 10, - "n_iterations": 8, - "default_surface_config": { - "resolution": (40, 30), - "surface_piece_config": { - "stroke_width": 0.5, - } - } - } - - def construct(self): - self.setup_axes() - self.initialize_parameter_trackers() - self.add_surface() - self.add_graph() - self.show_all_harmonic_surfaces() - - def setup_axes(self): - super().setup_axes() - self.add(self.axes.y_axis) - self.set_camera_orientation( - phi=82 * DEGREES, - theta=-80 * DEGREES, - ) - - def add_surface(self): - self.surface = self.get_cos_exp_surface() - self.add(self.surface) - - def add_graph(self): - self.graph = self.get_graph() - self.add(self.graph) - - def show_all_harmonic_surfaces(self): - omega_tracker = self.omega_tracker - formula = self.get_formula(str(1)) - L = self.axes.x_max - - self.begin_ambient_camera_rotation(rate=0.01) - self.add_fixed_in_frame_mobjects(formula) - self.wait(2) - for n in range(2, self.n_iterations): - if n > 5: - n_str = "n" - else: - n_str = str(n) - new_formula = self.get_formula(n_str) - self.play( - Transform(formula, new_formula), - ApplyMethod( - omega_tracker.set_value, - n * PI / L - ), - ) - self.wait(3) - - # - def get_formula(self, n_str): - n_str = "{" + n_str + "}" - result = TexMobject( - "\\cos\\left(", - n_str, "(", "\\pi / L", ")", "{x}" - "\\right)" - "e^{-\\alpha (", n_str, "\\pi / L", ")^2", - "{t}}", - tex_to_color_map={ - "{x}": GREEN, - "{t}": YELLOW, - "\\pi / L": MAROON_B, - n_str: MAROON_B, - } - ) - result.to_edge(UP) - return result - - -class Thumbnail(ShowHarmonicSurfaces): - CONFIG = { - "default_surface_config": { - "resolution": (40, 30), - # "resolution": (10, 10), - }, - "graph_config": { - "stroke_width": 8, - }, - } - - def construct(self): - self.setup_axes() - self.initialize_parameter_trackers() - self.add_surface() - self.add_graph() - # - self.omega_tracker.set_value(3 * PI / 10) - self.set_camera_orientation( - theta=-70 * DEGREES, - ) - - axes = self.axes - for axis in [axes.y_axis, axes.z_axis]: - axis.numbers.set_opacity(0) - axis.remove(*axis.numbers) - axes.x_axis.label.set_opacity(0) - axes.z_axis.label.set_opacity(0) - - for n in range(2, 16, 2): - new_graph = self.get_time_slice_graph( - axes, self.func, t=n, - **self.graph_config - ) - new_graph.set_shade_in_3d(True) - new_graph.set_stroke( - width=8 / np.sqrt(n), - # opacity=1 / n**(1 / 4), - ) - self.add(new_graph) - - words = TextMobject( - "Sine waves + Linearity + Fourier = Solution" - ) - words.set_width(FRAME_WIDTH - 1) - words.to_edge(DOWN) - words.shift(2 * DOWN) - self.add_fixed_in_frame_mobjects(words) - - self.camera.frame_center.shift(DOWN) - self.update_mobjects(0) - self.surface.set_stroke(width=0.1) - self.surface.set_fill(opacity=0.2) diff --git a/from_3b1b/active/diffyq/part3/wordy_scenes.py b/from_3b1b/active/diffyq/part3/wordy_scenes.py deleted file mode 100644 index 285cdc98..00000000 --- a/from_3b1b/active/diffyq/part3/wordy_scenes.py +++ /dev/null @@ -1,919 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part2.wordy_scenes import * - - -class ThreeMainObservations(Scene): - def construct(self): - fourier = ImageMobject("Joseph Fourier") - name = TextMobject("Joseph Fourier") - name.match_width(fourier) - name.next_to(fourier, DOWN, SMALL_BUFF) - fourier.add(name) - fourier.set_height(5) - fourier.to_corner(DR) - fourier.shift(LEFT) - bubble = ThoughtBubble( - direction=RIGHT, - height=3, - width=4, - ) - bubble.move_tip_to(fourier.get_corner(UL) + 0.5 * DR) - - observations = VGroup( - TextMobject( - "1)", - "Sine = Nice", - ), - TextMobject( - "2)", - "Linearity" - ), - TextMobject( - "3)", - "Fourier series" - ), - ) - # heart = SuitSymbol("hearts") - # heart.replace(observations[0][2]) - # observations[0][2].become(heart) - # observations[0][1].add(happiness) - # observations[2][2].align_to( - # observations[2][1], LEFT, - # ) - - observations.arrange( - DOWN, - aligned_edge=LEFT, - buff=2 * LARGE_BUFF, - ) - observations.set_height(FRAME_HEIGHT - 2) - observations.to_corner(UL, buff=LARGE_BUFF) - - self.add(fourier) - self.play(ShowCreation(bubble)) - self.wait() - self.play(LaggedStart(*[ - TransformFromCopy(bubble, observation[0]) - for observation in observations - ], lag_ratio=0.2)) - self.play( - FadeOut(fourier), - FadeOut(bubble), - ) - self.wait() - for obs in observations: - self.play(FadeInFrom(obs[1], LEFT)) - self.wait() - - -class LastChapterWrapper(Scene): - def construct(self): - full_rect = FullScreenFadeRectangle( - fill_color=DARK_GREY, - fill_opacity=1, - ) - rect = ScreenRectangle(height=6) - rect.set_stroke(WHITE, 2) - rect.set_fill(BLACK, 1) - title = TextMobject("Last chapter") - title.scale(2) - title.to_edge(UP) - rect.next_to(title, DOWN) - - self.add(full_rect) - self.play( - FadeIn(rect), - Write(title, run_time=2), - ) - self.wait() - - -class ThreeConstraints(WriteHeatEquationTemplate): - def construct(self): - self.cross_out_solving() - self.show_three_conditions() - - def cross_out_solving(self): - equation = self.get_d1_equation() - words = TextMobject("Solve this equation") - words.to_edge(UP) - equation.next_to(words, DOWN) - cross = Cross(words) - - self.add(words, equation) - self.wait() - self.play(ShowCreation(cross)) - self.wait() - - self.equation = equation - self.to_remove = VGroup(words, cross) - - def show_three_conditions(self): - equation = self.equation - to_remove = self.to_remove - - title = TexMobject( - "\\text{Constraints }" - "T({x}, {t})" - "\\text{ must satisfy:}", - **self.tex_mobject_config - ) - title.to_edge(UP) - - items = VGroup( - TextMobject("1)", "The PDE"), - TextMobject("2)", "Boundary condition"), - TextMobject("3)", "Initial condition"), - ) - items.scale(0.7) - items.arrange(RIGHT, buff=LARGE_BUFF) - items.set_width(FRAME_WIDTH - 2) - items.next_to(title, DOWN, LARGE_BUFF) - items[1].set_color(MAROON_B) - items[2].set_color(RED) - - bc_paren = TextMobject("(Explained soon)") - bc_paren.scale(0.7) - bc_paren.next_to(items[1], DOWN) - - self.play( - FadeInFromDown(title), - FadeOutAndShift(to_remove, UP), - equation.scale, 0.6, - equation.next_to, items[0], DOWN, - equation.shift_onto_screen, - LaggedStartMap(FadeIn, [ - items[0], - items[1][0], - items[2][0], - ]) - ) - self.wait() - self.play(Write(items[1][1])) - bc_paren.match_y(equation) - self.play(FadeInFrom(bc_paren, UP)) - self.wait(2) - self.play(Write(items[2][1])) - self.wait(2) - - self.title = title - self.items = items - self.pde = equation - self.bc_paren = bc_paren - - -class RectAroundEquation(WriteHeatEquationTemplate): - def construct(self): - eq = self.get_d1_equation() - self.play(ShowCreationThenFadeAround(eq)) - - -class BorderRect(Scene): - def construct(self): - rect = FullScreenFadeRectangle() - rect.set_stroke(WHITE, 3) - rect.set_fill(opacity=0) - self.add(rect) - - -class SeekIdealized(Scene): - def construct(self): - phrases = VGroup(*[ - TextMobject( - "Seek", text, "problems", - tex_to_color_map={ - "realistic": GREEN, - "{idealized}": YELLOW, - "over-idealized": YELLOW, - "general": BLUE, - } - ) - for text in [ - "realistic", - "{idealized}", - "over-idealized", - "general", - ] - ]) - phrases.scale(2) - words = VGroup() - for phrase in phrases: - phrase.center() - word = phrase[1] - words.add(word) - phrase.remove(word) - arrow = Vector(DOWN) - arrow.set_stroke(WHITE, 6) - arrow.next_to(words[3], UP) - low_arrow = arrow.copy() - low_arrow.next_to(words[3], DOWN) - - solutions = TextMobject("solutions") - solutions.scale(2) - solutions.move_to(phrases[3][1], UL) - models = TextMobject("models") - models.scale(2) - models.next_to( - words[0], RIGHT, buff=0.35, - aligned_edge=DOWN - ) - - phrases.center() - phrase = phrases[0] - - self.add(phrase) - self.add(words[0]) - self.wait() - words[0].save_state() - self.play( - words[0].to_edge, DOWN, - words[0].set_opacity, 0.5, - Transform(phrase, phrases[1]), - FadeInFrom(words[1], UP) - ) - self.wait() - # self.play( - # words[1].move_to, words[2], RIGHT, - # FadeIn(words[2]), - # Transform(phrase, phrases[2]) - # ) - # self.wait() - self.play( - words[1].next_to, arrow, UP, - ShowCreation(arrow), - MaintainPositionRelativeTo( - phrase, words[1] - ), - FadeInFrom(solutions, LEFT), - FadeIn(words[3]), - ) - self.wait() - - words[0].generate_target() - words[0].target.next_to(low_arrow, DOWN) - words[0].target.set_opacity(1) - models.shift( - words[0].target.get_center() - - words[0].saved_state.get_center() - ) - self.play( - MoveToTarget(words[0]), - ShowCreation(low_arrow), - FadeInFrom(models, LEFT) - ) - self.wait() - - -class SecondDerivativeOfSine(Scene): - def construct(self): - equation = TexMobject( - "{d^2 \\over d{x}^2}", - "\\cos\\left({x}\\right) =", - "-\\cos\\left({x}\\right)", - tex_to_color_map={ - "{x}": GREEN, - } - ) - - self.add(equation) - - -class EquationAboveSineAnalysis(WriteHeatEquationTemplate): - def construct(self): - equation = self.get_d1_equation() - equation.to_edge(UP) - equation.shift(2 * LEFT) - eq_index = equation.index_of_part_by_tex("=") - lhs = equation[:eq_index] - eq = equation[eq_index] - rhs = equation[eq_index + 1:] - t_terms = equation.get_parts_by_tex("{t}")[1:] - t_terms.save_state() - zeros = VGroup(*[ - TexMobject("0").replace(t, dim_to_match=1) - for t in t_terms - ]) - zeros.align_to(t_terms, DOWN) - new_rhs = TexMobject( - "=", "-\\alpha \\cdot {T}", "({x}, 0)", - **self.tex_mobject_config - ) - # new_rhs.move_to(equation.get_right()) - # new_rhs.next_to(equation, DOWN, MED_LARGE_BUFF) - # new_rhs.align_to(eq, LEFT) - new_rhs.next_to(equation, RIGHT) - new_rhs.shift(SMALL_BUFF * DOWN) - - self.add(equation) - self.play(ShowCreationThenFadeAround(rhs)) - self.wait() - self.play( - FadeOutAndShift(t_terms, UP), - FadeInFrom(zeros, DOWN), - ) - t_terms.fade(1) - self.wait() - self.play( - # VGroup(equation, zeros).next_to, - # new_rhs, LEFT, - FadeIn(new_rhs), - ) - self.wait() - self.play( - VGroup( - lhs[6:], - eq, - rhs, - new_rhs[0], - new_rhs[-3:], - zeros, - ).fade, 0.5, - ) - self.play(ShowCreationThenFadeAround(lhs[:6])) - self.play(ShowCreationThenFadeAround(new_rhs[1:-3])) - self.wait() - - -class ExpVideoWrapper(Scene): - def construct(self): - self.add(FullScreenFadeRectangle( - fill_color=DARKER_GREY, - fill_opacity=1, - )) - - screen = ImageMobject("eoc_chapter5_thumbnail") - screen.set_height(6) - rect = SurroundingRectangle(screen, buff=0) - rect.set_stroke(WHITE, 2) - screen.add(rect) - title = TextMobject("Need a refresher?") - title.scale(1.5) - title.to_edge(UP) - screen.next_to(title, DOWN) - - screen.center() - self.play( - # FadeInFrom(title, LEFT), - FadeInFrom(screen, DOWN), - ) - self.wait() - - -class ShowSinExpDerivatives(WriteHeatEquationTemplate): - CONFIG = { - "tex_mobject_config": { - "tex_to_color_map": { - "{0}": WHITE, - "\\partial": WHITE, - "=": WHITE, - } - } - } - - def construct(self): - pde = self.get_d1_equation_without_inputs() - pde.to_edge(UP) - pde.generate_target() - - new_rhs = TexMobject( - "=- \\alpha \\cdot T", - **self.tex_mobject_config, - ) - new_rhs.next_to(pde, RIGHT) - new_rhs.align_to(pde.get_part_by_tex("alpha"), DOWN) - - equation1 = TexMobject( - "T({x}, {0}) = \\sin\\left({x}\\right)", - **self.tex_mobject_config - ) - equation2 = TexMobject( - "T({x}, {t}) = \\sin\\left({x}\\right)", - "e^{-\\alpha{t}}", - **self.tex_mobject_config - ) - for eq in equation1, equation2: - eq.next_to(pde, DOWN, MED_LARGE_BUFF) - - eq2_part1 = equation2[:len(equation1)] - eq2_part2 = equation2[len(equation1):] - - # Rectangles - exp_rect = SurroundingRectangle(eq2_part2) - exp_rect.set_stroke(RED, 3) - sin_rect = SurroundingRectangle( - eq2_part1[-3:] - ) - sin_rect.set_color(BLUE) - - VGroup(pde.target, new_rhs).center().to_edge(UP) - - # Show proposed solution - self.add(pde) - self.add(equation1) - self.wait() - self.play( - MoveToTarget(pde), - FadeInFrom(new_rhs, LEFT) - ) - self.wait() - self.play( - ReplacementTransform(equation1, eq2_part1), - FadeIn(eq2_part2), - ) - self.play(ShowCreation(exp_rect)) - self.wait() - self.play(FadeOut(exp_rect)) - - # Take partial derivatives wrt x - q_mark = TexMobject("?") - q_mark.next_to(pde.get_part_by_tex("="), UP) - q_mark.set_color(RED) - - arrow1 = Vector(3 * DOWN + 1 * RIGHT, color=WHITE) - arrow1.scale(1.2 / arrow1.get_length()) - arrow1.next_to( - eq2_part2.get_corner(DL), - DOWN, MED_LARGE_BUFF - ) - ddx_label1 = TexMobject( - "\\partial \\over \\partial {x}", - **self.tex_mobject_config, - ) - ddx_label1.scale(0.7) - ddx_label1.next_to( - arrow1.point_from_proportion(0.8), - UR, SMALL_BUFF - ) - - pde_ddx = VGroup( - *pde.get_parts_by_tex("\\partial")[2:], - pde.get_parts_by_tex("\\over")[1], - pde.get_part_by_tex("{x}"), - ) - pde_ddx_rect = SurroundingRectangle(pde_ddx) - pde_ddx_rect.set_color(GREEN) - - eq2_part2_rect = SurroundingRectangle(eq2_part2) - - dx = TexMobject( - "\\cos\\left({x}\\right)", "e^{-\\alpha {t}}", - **self.tex_mobject_config - ) - ddx = TexMobject( - "-\\sin\\left({x}\\right)", "e^{-\\alpha {t}}", - **self.tex_mobject_config - ) - dx.next_to(arrow1, DOWN) - dx.align_to(eq2_part2, RIGHT) - x_shift = arrow1.get_end()[0] - arrow1.get_start()[0] - x_shift *= 2 - dx.shift(x_shift * RIGHT) - arrow2 = arrow1.copy() - arrow2.next_to(dx, DOWN) - arrow2.shift(MED_SMALL_BUFF * RIGHT) - dx_arrows = VGroup(arrow1, arrow2) - - ddx_label2 = ddx_label1.copy() - ddx_label2.shift( - arrow2.get_center() - arrow1.get_center() - ) - ddx.next_to(arrow2, DOWN) - ddx.align_to(eq2_part2, RIGHT) - ddx.shift(2 * x_shift * RIGHT) - - rhs = equation2[-6:] - - self.play( - FadeInFromDown(q_mark) - ) - self.play( - ShowCreation(pde_ddx_rect) - ) - self.wait() - self.play( - LaggedStart( - GrowArrow(arrow1), - GrowArrow(arrow2), - ), - TransformFromCopy( - pde_ddx[0], ddx_label1 - ), - TransformFromCopy( - pde_ddx[0], ddx_label2 - ), - ) - self.wait() - self.play( - TransformFromCopy(rhs, dx) - ) - self.wait() - self.play( - FadeIn(eq2_part2_rect) - ) - self.play( - Transform( - eq2_part2_rect, - SurroundingRectangle(dx[-3:]) - ) - ) - self.play( - FadeOut(eq2_part2_rect) - ) - self.wait() - self.play( - TransformFromCopy(dx, ddx) - ) - self.play( - FadeIn( - SurroundingRectangle(ddx).match_style( - pde_ddx_rect - ) - ) - ) - self.wait() - - # Take partial derivative wrt t - pde_ddt = pde[:pde.index_of_part_by_tex("=") - 1] - pde_ddt_rect = SurroundingRectangle(pde_ddt) - - dt_arrow = Arrow( - arrow1.get_start(), - arrow2.get_end() + RIGHT, - buff=0 - ) - dt_arrow.flip(UP) - dt_arrow.next_to(dx_arrows, LEFT, MED_LARGE_BUFF) - - dt_label = TexMobject( - "\\partial \\over \\partial {t}", - **self.tex_mobject_config, - ) - dt_label.scale(1) - dt_label.next_to( - dt_arrow.get_center(), UL, - SMALL_BUFF, - ) - - rhs_copy = rhs.copy() - rhs_copy.next_to(dt_arrow.get_end(), DOWN) - rhs_copy.shift(MED_LARGE_BUFF * LEFT) - rhs_copy.match_y(ddx) - - minus_alpha_in_exp = rhs_copy[-3][1:].copy() - minus_alpha_in_exp.set_color(RED) - minus_alpha = TexMobject("-\\alpha") - minus_alpha.next_to(rhs_copy, LEFT) - minus_alpha.align_to(rhs_copy[0][0], DOWN) - dot = TexMobject("\\cdot") - dot.move_to(midpoint( - minus_alpha.get_right(), - rhs_copy.get_left(), - )) - - self.play( - TransformFromCopy( - pde_ddx_rect, - pde_ddt_rect, - ) - ) - self.play( - GrowArrow(dt_arrow), - TransformFromCopy( - pde_ddt, - dt_label, - ) - ) - self.wait() - self.play(TransformFromCopy(rhs, rhs_copy)) - self.play(FadeIn(minus_alpha_in_exp)) - self.play( - ApplyMethod( - minus_alpha_in_exp.replace, minus_alpha, - path_arc=TAU / 4 - ), - FadeIn(dot), - ) - self.play( - FadeIn(minus_alpha), - FadeOut(minus_alpha_in_exp), - ) - self.wait() - rhs_copy.add(minus_alpha, dot) - self.play( - FadeIn(SurroundingRectangle(rhs_copy)) - ) - self.wait() - - # - checkmark = TexMobject("\\checkmark") - checkmark.set_color(GREEN) - checkmark.move_to(q_mark, DOWN) - self.play( - FadeInFromDown(checkmark), - FadeOutAndShift(q_mark, UP) - ) - self.wait() - - -class DerivativesOfLinearFunction(WriteHeatEquationTemplate): - CONFIG = { - "tex_mobject_config": { - "tex_to_color_map": { - "{c}": WHITE, - } - } - } - - def construct(self): - func = TexMobject( - "T({x}, {t}) = {c} \\cdot {x}", - **self.tex_mobject_config - ) - dx_T = TexMobject("{c}", **self.tex_mobject_config) - ddx_T = TexMobject("0") - dt_T = TexMobject("0") - - for mob in func, dx_T, ddx_T, dt_T: - mob.scale(1.5) - - func.generate_target() - - arrows = VGroup(*[ - Vector(1.5 * RIGHT, color=WHITE) - for x in range(3) - ]) - dx_arrows = arrows[:2] - dt_arrow = arrows[2] - dt_arrow.rotate(-TAU / 4) - dx_group = VGroup( - func.target, - dx_arrows[0], - dx_T, - dx_arrows[1], - ddx_T, - ) - dx_group.arrange(RIGHT) - for arrow, char, vect in zip(arrows, "xxt", [UP, UP, RIGHT]): - label = TexMobject( - "\\partial \\over \\partial {%s}" % char, - **self.tex_mobject_config - ) - label.scale(0.7) - label.next_to(arrow.get_center(), vect) - arrow.add(label) - - dt_arrow.shift( - func.target[-3:].get_bottom() + MED_SMALL_BUFF * DOWN - - dt_arrow.get_start(), - ) - dt_T.next_to(dt_arrow.get_end(), DOWN) - - self.play(FadeInFromDown(func)) - self.wait() - self.play( - MoveToTarget(func), - LaggedStartMap(Write, dx_arrows), - run_time=1, - ) - self.play( - TransformFromCopy(func[-3:], dx_T), - path_arc=-TAU / 4, - ) - self.play( - TransformFromCopy(dx_T, ddx_T), - path_arc=-TAU / 4, - ) - self.wait() - - # dt - self.play(Write(dt_arrow)) - self.play( - TransformFromCopy(func[-3:], dt_T) - ) - self.wait() - - -class FlatAtBoundaryWords(Scene): - def construct(self): - words = self.get_bc_words() - self.play(Write(words)) - self.wait() - - def get_bc_words(self): - return TextMobject( - "Flat at boundary\\\\" - "for all", "${t}$", "$> 0$", - ) - - -class WriteOutBoundaryCondition(FlatAtBoundaryWords, ThreeConstraints, MovingCameraScene): - def construct(self): - self.force_skipping() - ThreeConstraints.construct(self) - self.revert_to_original_skipping_status() - - self.add_ic() - self.write_bc_words() - self.write_bc_equation() - - def add_ic(self): - image = ImageMobject("temp_initial_condition_example") - image.set_width(3) - border = SurroundingRectangle(image, buff=SMALL_BUFF) - border.shift(SMALL_BUFF * UP) - border.set_stroke(WHITE, 2) - group = Group(image, border) - group.next_to(self.items[2], DOWN) - self.add(group) - - def write_bc_words(self): - bc_paren = self.bc_paren - bc_words = self.get_bc_words() - bc_words.match_width(self.items[1][1]) - bc_words.move_to(bc_paren, UP) - bc_words.set_color_by_tex("{t}", YELLOW) - - self.play(ShowCreationThenFadeAround( - VGroup(self.items[0], self.pde) - )) - self.play( - FadeOutAndShift(bc_paren, UP), - FadeInFrom(bc_words, DOWN), - ) - self.wait() - - self.bc_words = bc_words - - def write_bc_equation(self): - bc_words = self.bc_words - - equation = TexMobject( - "{\\partial {T} \\over \\partial {x}}(0, {t}) = ", - "{\\partial {T} \\over \\partial {x}}(L, {t}) = ", - "0", - **self.tex_mobject_config, - ) - equation.next_to(bc_words, DOWN, MED_LARGE_BUFF) - - self.play( - self.camera_frame.shift, 0.8 * DOWN, - ) - self.play(FadeInFrom(equation, UP)) - self.wait() - - -class HeatEquationFrame(WriteHeatEquationTemplate): - def construct(self): - equation = self.get_d1_equation() - equation.to_edge(UP, buff=MED_SMALL_BUFF) - - ddx = equation[-11:] - dt = equation[:11] - - full_rect = FullScreenFadeRectangle( - fill_color=DARK_GREY, - fill_opacity=1, - ) - smaller_rect = ScreenRectangle( - height=6, - fill_color=BLACK, - fill_opacity=1, - stroke_color=WHITE, - stroke_width=2, - ) - smaller_rect.next_to(equation, DOWN) - - self.add(full_rect) - self.add(smaller_rect) - self.add(equation) - self.wait() - self.play(ShowCreationThenFadeAround( - ddx, - surrounding_rectangle_config={ - "stroke_color": GREEN, - } - )) - self.wait() - self.play(ShowCreationThenFadeAround(dt)) - self.wait() - - -class CompareFreqDecays1to2(Scene): - CONFIG = { - "freqs": [1, 2] - } - - def construct(self): - background = FullScreenFadeRectangle( - fill_color=DARKER_GREY, - fill_opacity=1, - ) - - screens = VGroup(*[ - ScreenRectangle( - height=4, - fill_color=BLACK, - fill_opacity=1, - stroke_width=1, - stroke_color=WHITE, - ) - for x in range(2) - ]) - screens.arrange(RIGHT) - screens.set_width(FRAME_WIDTH - 1) - - formulas = VGroup(*[ - self.get_formula(freq) - for freq in self.freqs - ]) - for formula, screen in zip(formulas, screens): - formula.next_to(screen, UP) - - self.add(background) - self.add(screens) - self.add(formulas) - self.wait() - - def get_formula(self, freq): - f_str = str(freq) - return TexMobject( - "\\cos\\left(%s \\cdot {x}\\right)" % f_str, - "e^{-\\alpha \\cdot %s^2 \\cdot {t}}" % f_str, - tex_to_color_map={ - "{x}": GREEN, - "{t}": YELLOW, - f_str: MAROON_B, - } - ) - - -class CompareFreqDecays1to4(CompareFreqDecays1to2): - CONFIG = { - "freqs": [1, 4], - } - - -class CompareFreqDecays2to4(CompareFreqDecays1to2): - CONFIG = { - "freqs": [2, 4], - } - - -class WorryAboutGenerality(TeacherStudentsScene, WriteHeatEquationTemplate): - def construct(self): - eq = self.get_d1_equation() - diffyq = self.get_diffyq_set() - is_in = TexMobject("\\in") - is_in.scale(2) - - group = VGroup(eq, is_in, diffyq) - group.arrange(RIGHT, buff=MED_LARGE_BUFF) - group.to_edge(UP) - - arrow = Vector(DOWN) - arrow.set_stroke(WHITE, 5) - arrow.next_to(eq, DOWN) - themes = TextMobject("Frequent themes") - themes.scale(1.5) - themes.next_to(arrow, DOWN) - - self.play( - self.get_student_changes( - "sad", "tired", "pleading" - ), - self.teacher.change, "raise_right_hand", - FadeInFromDown(eq) - ) - self.play(Write(group[1:])) - self.wait(2) - self.play( - ShowCreation(arrow), - self.get_student_changes(*3 * ["pondering"]), - ) - self.play( - FadeInFrom(themes, UP), - self.get_student_changes(*3 * ["thinking"]), - self.teacher.change, "happy" - ) - self.wait(4) - - - # def get_d1_equation(self): - # result = super().get_d1_equation() - # lp, rp = parens = TexMobject("(", ")") - # parens.match_height(result) - # lp.next_to(result, LEFT, SMALL_BUFF) - # rp.next_to(result, RIGHT, SMALL_BUFF) - # result.add_to_back(lp) - # result.add(rp) - # return result - - def get_diffyq_set(self): - words = TextMobject( - "Differential\\\\equations" - ) - words.scale(1.5) - words.set_color(BLUE) - lb = Brace(words, LEFT) - rb = Brace(words, RIGHT) - return VGroup(lb, words, rb) diff --git a/from_3b1b/active/diffyq/part4/complex_functions.py b/from_3b1b/active/diffyq/part4/complex_functions.py deleted file mode 100644 index 222e458d..00000000 --- a/from_3b1b/active/diffyq/part4/complex_functions.py +++ /dev/null @@ -1,712 +0,0 @@ -from manimlib.imports import * - - -class GeneralizeToComplexFunctions(Scene): - CONFIG = { - "axes_config": { - "x_min": 0, - "x_max": 10, - "x_axis_config": { - "stroke_width": 2, - }, - "y_min": -2.5, - "y_max": 2.5, - "y_axis_config": { - "tick_frequency": 0.25, - "unit_size": 1.5, - "include_tip": False, - "stroke_width": 2, - }, - }, - "complex_plane_config": { - "axis_config": { - "unit_size": 2 - } - }, - } - - def construct(self): - self.show_cosine_wave() - self.transition_to_complex_plane() - self.add_rotating_vectors_making_cos() - - def show_cosine_wave(self): - axes = Axes(**self.axes_config) - axes.shift(2 * LEFT - axes.c2p(0, 0)) - y_axis = axes.y_axis - y_labels = y_axis.get_number_mobjects( - *range(-2, 3), - number_config={"num_decimal_places": 1}, - ) - - t_tracker = ValueTracker(0) - t_tracker.add_updater(lambda t, dt: t.increment_value(dt)) - get_t = t_tracker.get_value - - def func(x): - return 2 * np.cos(x) - - cos_x_max = 20 - cos_wave = axes.get_graph(func, x_max=cos_x_max) - cos_wave.set_color(YELLOW) - shown_cos_wave = cos_wave.copy() - shown_cos_wave.add_updater( - lambda m: m.pointwise_become_partial( - cos_wave, 0, - np.clip(get_t() / cos_x_max, 0, 1), - ), - ) - - dot = Dot() - dot.set_color(PINK) - dot.add_updater(lambda d: d.move_to( - y_axis.n2p(func(get_t())), - )) - - h_line = always_redraw(lambda: Line( - dot.get_right(), - shown_cos_wave.get_end(), - stroke_width=1, - )) - - real_words = TextMobject( - "Real number\\\\output" - ) - real_words.to_edge(LEFT) - real_words.shift(2 * UP) - real_arrow = Arrow() - real_arrow.add_updater( - lambda m: m.put_start_and_end_on( - real_words.get_corner(DR), - dot.get_center(), - ).scale(0.9), - ) - - self.add(t_tracker) - self.add(axes) - self.add(y_labels) - self.add(shown_cos_wave) - self.add(dot) - self.add(h_line) - - self.wait(2) - self.play( - FadeInFrom(real_words, RIGHT), - FadeIn(real_arrow), - ) - self.wait(5) - - y_axis.generate_target() - y_axis.target.rotate(-90 * DEGREES) - y_axis.target.center() - y_axis.target.scale(2 / 1.5) - y_labels.generate_target() - for label in y_labels.target: - label.next_to( - y_axis.target.n2p(label.get_value()), - DOWN, MED_SMALL_BUFF, - ) - self.play( - FadeOut(shown_cos_wave), - FadeOut(axes.x_axis), - FadeOut(h_line), - ) - self.play( - MoveToTarget(y_axis), - MoveToTarget(y_labels), - real_words.shift, 2 * RIGHT + UP, - ) - self.wait() - - self.y_axis = y_axis - self.y_labels = y_labels - self.real_words = real_words - self.real_arrow = real_arrow - self.dot = dot - self.t_tracker = t_tracker - - def transition_to_complex_plane(self): - y_axis = self.y_axis - y_labels = self.y_labels - - plane = self.get_complex_plane() - plane_words = plane.label - - self.add(plane, *self.get_mobjects()) - self.play( - FadeOut(y_labels), - FadeOut(y_axis), - ShowCreation(plane), - ) - self.play(Write(plane_words)) - self.wait() - - self.plane = plane - self.plane_words = plane_words - - def add_rotating_vectors_making_cos(self): - plane = self.plane - real_words = self.real_words - real_arrow = self.real_arrow - t_tracker = self.t_tracker - get_t = t_tracker.get_value - - v1 = Vector(2 * RIGHT) - v2 = Vector(2 * RIGHT) - v1.set_color(BLUE) - v2.set_color(interpolate_color(GREY_BROWN, WHITE, 0.5)) - v1.add_updater( - lambda v: v.set_angle(get_t()) - ) - v2.add_updater( - lambda v: v.set_angle(-get_t()) - ) - v1.add_updater( - lambda v: v.shift(plane.n2p(0) - v.get_start()) - ) - # Change? - v2.add_updater( - lambda v: v.shift(plane.n2p(0) - v.get_start()) - ) - - ghost_v1 = v1.copy() - ghost_v1.set_opacity(0.5) - ghost_v1.add_updater( - lambda v: v.shift( - v2.get_end() - v.get_start() - ) - ) - - ghost_v2 = v2.copy() - ghost_v2.set_opacity(0.5) - ghost_v2.add_updater( - lambda v: v.shift( - v1.get_end() - v.get_start() - ) - ) - - circle = Circle(color=GREY_BROWN) - circle.set_stroke(width=1) - circle.set_width(2 * v1.get_length()) - circle.move_to(plane.n2p(0)) - - formula = TexMobject( - # "\\cos(x) =" - # "{1 \\over 2}e^{ix} +" - # "{1 \\over 2}e^{-ix}", - "2\\cos(x) =", - "e^{ix}", "+", "e^{-ix}", - tex_to_color_map={ - "e^{ix}": v1.get_color(), - "e^{-ix}": v2.get_color(), - } - ) - formula.next_to(ORIGIN, UP, buff=0.75) - # formula.add_background_rectangle() - formula.set_stroke(BLACK, 3, background=True) - formula.to_edge(LEFT, buff=MED_SMALL_BUFF) - formula_brace = Brace(formula[1:], UP) - formula_words = formula_brace.get_text( - "Sum of\\\\rotations" - ) - formula_words.set_stroke(BLACK, 3, background=True) - - randy = Randolph() - randy.to_corner(DL) - randy.look_at(formula) - - self.play( - FadeOut(real_words), - FadeOut(real_arrow), - ) - self.play( - FadeIn(v1), - FadeIn(v2), - FadeIn(circle), - FadeIn(ghost_v1), - FadeIn(ghost_v2), - ) - self.wait(3) - self.play(FadeInFromDown(formula)) - self.play( - GrowFromCenter(formula_brace), - FadeIn(formula_words), - ) - self.wait(2) - self.play(FadeIn(randy)) - self.play(randy.change, "pleading") - self.play(Blink(randy)) - self.wait() - self.play(randy.change, "confused") - self.play(Blink(randy)) - self.wait() - self.play(FadeOut(randy)) - self.wait(20) - - # - def get_complex_plane(self): - plane = ComplexPlane(**self.complex_plane_config) - plane.add_coordinates() - - plane.label = TextMobject("Complex plane") - plane.label.scale(1.5) - plane.label.to_corner(UR, buff=MED_SMALL_BUFF) - return plane - - -class ClarifyInputAndOutput(GeneralizeToComplexFunctions): - CONFIG = { - "input_space_rect_config": { - "stroke_color": WHITE, - "stroke_width": 1, - "fill_color": DARKER_GREY, - "fill_opacity": 1, - "width": 6, - "height": 2, - }, - } - - def construct(self): - self.setup_plane() - self.setup_input_space() - self.setup_input_trackers() - - self.describe_input() - self.describe_output() - - def setup_plane(self): - plane = self.get_complex_plane() - plane.sublabel = TextMobject("(Output space)") - plane.sublabel.add_background_rectangle() - plane.sublabel.next_to(plane.label, DOWN) - self.add(plane, plane.label) - self.plane = plane - - def setup_input_space(self): - rect = Rectangle(**self.input_space_rect_config) - rect.to_corner(UL, buff=SMALL_BUFF) - - input_line = self.get_input_line(rect) - input_words = TextMobject("Input space") - input_words.next_to( - rect.get_bottom(), UP, - SMALL_BUFF, - ) - - self.add(rect) - self.add(input_line) - - self.input_rect = rect - self.input_line = input_line - self.input_words = input_words - - def setup_input_trackers(self): - plane = self.plane - input_line = self.input_line - input_tracker = ValueTracker(0) - get_input = input_tracker.get_value - - input_dot = Dot() - input_dot.set_color(PINK) - f_always( - input_dot.move_to, - lambda: input_line.n2p(get_input()) - ) - - input_decimal = DecimalNumber() - input_decimal.scale(0.7) - always(input_decimal.next_to, input_dot, UP) - f_always(input_decimal.set_value, get_input) - - path = self.get_path() - - def get_output_point(): - return path.point_from_proportion( - get_input() - ) - - output_dot = Dot() - output_dot.match_style(input_dot) - f_always(output_dot.move_to, get_output_point) - - output_vector = Vector() - output_vector.set_color(WHITE) - output_vector.add_updater( - lambda v: v.put_start_and_end_on( - plane.n2p(0), - get_output_point() - ) - ) - - output_decimal = DecimalNumber() - output_decimal.scale(0.7) - always(output_decimal.next_to, output_dot, UR, SMALL_BUFF) - f_always( - output_decimal.set_value, - lambda: plane.p2n(get_output_point()), - ) - - self.input_tracker = input_tracker - self.input_dot = input_dot - self.input_decimal = input_decimal - self.path = path - self.output_dot = output_dot - self.output_vector = output_vector - self.output_decimal = output_decimal - - def describe_input(self): - input_tracker = self.input_tracker - - self.play(FadeInFrom(self.input_words, UP)) - self.play( - FadeInFromLarge(self.input_dot), - FadeIn(self.input_decimal), - ) - for value in 1, 0: - self.play( - input_tracker.set_value, value, - run_time=2 - ) - self.wait() - - def describe_output(self): - path = self.path - output_dot = self.output_dot - output_decimal = self.output_decimal - input_dot = self.input_dot - input_tracker = self.input_tracker - plane = self.plane - real_line = plane.x_axis.copy() - real_line.set_stroke(RED, 4) - real_words = TextMobject("Real number line") - real_words.next_to(ORIGIN, UP) - real_words.to_edge(RIGHT) - - traced_path = TracedPath(output_dot.get_center) - traced_path.match_style(path) - - self.play( - ShowCreation(real_line), - FadeInFrom(real_words, DOWN) - ) - self.play( - FadeOut(real_line), - FadeOut(real_words), - ) - self.play( - FadeInFrom(plane.sublabel, UP) - ) - self.play( - FadeIn(output_decimal), - TransformFromCopy(input_dot, output_dot), - ) - - kw = { - "run_time": 10, - "rate_func": lambda t: smooth(t, 1), - } - self.play( - ApplyMethod(input_tracker.set_value, 1, **kw), - ShowCreation(path.copy(), remover=True, **kw), - ) - self.add(path) - self.add(output_dot) - self.wait() - - # Flatten to 1d - real_function_word = TextMobject( - "Real-valued function" - ) - real_function_word.next_to(ORIGIN, DOWN, MED_LARGE_BUFF) - path.generate_target() - path.target.stretch(0, 1) - path.target.move_to(plane.n2p(0)) - - self.play( - FadeIn(real_function_word), - MoveToTarget(path), - ) - input_tracker.set_value(0) - self.play( - input_tracker.set_value, 1, - **kw - ) - - # - def get_input_line(self, input_rect): - input_line = UnitInterval() - input_line.move_to(input_rect) - input_line.shift(0.25 * UP) - input_line.set_width( - input_rect.get_width() - 1 - ) - input_line.add_numbers(0, 0.5, 1) - return input_line - - def get_path(self): - # mob = SVGMobject("BatmanLogo") - mob = TexMobject("\\pi") - path = mob.family_members_with_points()[0] - path.set_height(3.5) - path.move_to(2 * DOWN, DOWN) - path.set_stroke(YELLOW, 2) - path.set_fill(opacity=0) - return path - - -class GraphForFlattenedPi(ClarifyInputAndOutput): - CONFIG = { - "camera_config": {"background_color": DARKER_GREY}, - } - - def construct(self): - self.setup_plane() - plane = self.plane - self.remove(plane, plane.label) - - path = self.get_path() - - axes = Axes( - x_min=0, - x_max=1, - x_axis_config={ - "unit_size": 7, - "include_tip": False, - "tick_frequency": 0.1, - }, - y_min=-1.5, - y_max=1.5, - y_axis_config={ - "include_tip": False, - "unit_size": 2.5, - "tick_frequency": 0.5, - }, - ) - axes.set_width(FRAME_WIDTH - 1) - axes.set_height(FRAME_HEIGHT - 1, stretch=True) - axes.center() - - axes.x_axis.add_numbers( - 0.5, 1.0, - number_config={"num_decimal_places": 1}, - ) - axes.y_axis.add_numbers( - -1.0, 1.0, - number_config={"num_decimal_places": 1}, - ) - - def func(t): - return plane.x_axis.p2n( - path.point_from_proportion(t) - ) - - graph = axes.get_graph(func) - graph.set_color(PINK) - - v_line = always_redraw(lambda: Line( - axes.x_axis.n2p(axes.x_axis.p2n(graph.get_end())), - graph.get_end(), - stroke_width=1, - )) - - self.add(axes) - self.add(v_line) - - kw = { - "run_time": 10, - "rate_func": lambda t: smooth(t, 1), - } - self.play(ShowCreation(graph, **kw)) - self.wait() - - -class SimpleComplexExponentExample(ClarifyInputAndOutput): - CONFIG = { - "input_space_rect_config": { - "width": 14, - "height": 1.5, - }, - "input_line_config": { - "unit_size": 0.5, - "x_min": 0, - "x_max": 25, - "stroke_width": 2, - }, - "input_numbers": range(0, 30, 5), - "input_tex_args": ["t", "="], - } - - def construct(self): - self.setup_plane() - self.setup_input_space() - self.setup_input_trackers() - self.setup_output_trackers() - - # Testing - time = self.input_line.x_max - self.play( - self.input_tracker.set_value, time, - run_time=time, - rate_func=linear, - ) - - def setup_plane(self): - plane = ComplexPlane() - plane.scale(2) - plane.add_coordinates() - plane.shift(DOWN) - self.plane = plane - self.add(plane) - - def setup_input_trackers(self): - input_line = self.input_line - input_tracker = ValueTracker(0) - get_input = input_tracker.get_value - - input_tip = ArrowTip(start_angle=-TAU / 4) - input_tip.scale(0.5) - input_tip.set_color(PINK) - f_always( - input_tip.move_to, - lambda: input_line.n2p(get_input()), - lambda: DOWN, - ) - - input_label = VGroup( - TexMobject(*self.input_tex_args), - DecimalNumber(), - ) - input_label[0].set_color_by_tex("t", PINK) - input_label.scale(0.7) - input_label.add_updater( - lambda m: m.arrange(RIGHT, buff=SMALL_BUFF) - ) - input_label.add_updater( - lambda m: m[1].set_value(get_input()) - ) - input_label.add_updater( - lambda m: m.next_to(input_tip, UP, SMALL_BUFF) - ) - - self.input_tracker = input_tracker - self.input_tip = input_tip - self.input_label = input_label - - self.add(input_tip, input_label) - - def setup_output_trackers(self): - plane = self.plane - get_input = self.input_tracker.get_value - - def get_output(): - return np.exp(complex(0, get_input())) - - def get_output_point(): - return plane.n2p(get_output()) - - output_label, static_output_label = [ - TexMobject( - "e^{i t}" + s, - tex_to_color_map={"t": PINK}, - background_stroke_width=3, - ) - for s in ["", "\\approx"] - ] - output_label.scale(1.2) - output_label.add_updater( - lambda m: m.shift( - -m.get_bottom() + - get_output_point() + - rotate_vector( - 0.35 * RIGHT, - get_input(), - ) - ) - ) - - output_vector = Vector() - output_vector.set_opacity(0.75) - output_vector.add_updater( - lambda m: m.put_start_and_end_on( - plane.n2p(0), get_output_point(), - ) - ) - - t_max = 40 - full_output_path = ParametricFunction( - lambda t: plane.n2p(np.exp(complex(0, t))), - t_min=0, - t_max=t_max - ) - output_path = VMobject() - output_path.set_stroke(YELLOW, 2) - output_path.add_updater( - lambda m: m.pointwise_become_partial( - full_output_path, - 0, get_input() / t_max, - ) - ) - - static_output_label.next_to(plane.c2p(1, 1), UR) - output_decimal = DecimalNumber( - include_sign=True, - ) - output_decimal.scale(0.8) - output_decimal.set_stroke(BLACK, 3, background=True) - output_decimal.add_updater( - lambda m: m.set_value(get_output()) - ) - output_decimal.add_updater( - lambda m: m.next_to( - static_output_label, - RIGHT, 2 * SMALL_BUFF, - aligned_edge=DOWN, - ) - ) - - self.add(output_path) - self.add(output_vector) - self.add(output_label) - self.add(static_output_label) - self.add(BackgroundRectangle(output_decimal)) - self.add(output_decimal) - - # - def get_input_line(self, input_rect): - input_line = NumberLine(**self.input_line_config) - input_line.move_to(input_rect) - input_line.set_width( - input_rect.get_width() - 1.5, - stretch=True, - ) - input_line.add_numbers(*self.input_numbers) - return input_line - - -class TRangingFrom0To1(SimpleComplexExponentExample): - CONFIG = { - "input_space_rect_config": { - "width": 6, - "height": 2, - }, - } - - def construct(self): - self.setup_input_space() - self.setup_input_trackers() - - self.play( - self.input_tracker.set_value, 1, - run_time=10, - rate_func=linear - ) - - def get_input_line(self, rect): - result = ClarifyInputAndOutput.get_input_line(self, rect) - result.stretch(0.9, 0) - result.set_stroke(width=2) - for sm in result.get_family(): - if isinstance(sm, DecimalNumber): - sm.stretch(1 / 0.9, 0) - sm.set_stroke(width=0) - return result diff --git a/from_3b1b/active/diffyq/part4/fourier_series_scenes.py b/from_3b1b/active/diffyq/part4/fourier_series_scenes.py deleted file mode 100644 index e346cf16..00000000 --- a/from_3b1b/active/diffyq/part4/fourier_series_scenes.py +++ /dev/null @@ -1,1893 +0,0 @@ -from manimlib.imports import * - -from active_projects.diffyq.part2.fourier_series import FourierOfTrebleClef -from active_projects.diffyq.part4.complex_functions import TRangingFrom0To1 -from active_projects.diffyq.part4.complex_functions import SimpleComplexExponentExample - - -class ComplexFourierSeriesExample(FourierOfTrebleClef): - CONFIG = { - "file_name": "EighthNote", - "run_time": 10, - "n_vectors": 200, - "n_cycles": 2, - "max_circle_stroke_width": 0.75, - "drawing_height": 5, - "center_point": DOWN, - "top_row_center": 3 * UP, - "top_row_label_y": 2, - "top_row_x_spacing": 1.75, - "top_row_copy_scale_factor": 0.9, - "start_drawn": False, - "plane_config": { - "axis_config": {"unit_size": 2}, - "y_min": -1.25, - "y_max": 1.25, - "x_min": -2.5, - "x_max": 2.5, - "background_line_style": { - "stroke_width": 1, - "stroke_color": LIGHT_GREY, - }, - }, - "top_rect_height": 2.5, - } - - def construct(self): - self.add_vectors_circles_path() - self.add_top_row(self.vectors, self.circles) - self.write_title() - self.highlight_vectors_one_by_one() - self.change_shape() - - def write_title(self): - title = TextMobject("Complex\\\\Fourier series") - title.scale(1.5) - title.to_edge(LEFT) - title.match_y(self.path) - - self.wait(11) - self.play(FadeInFromDown(title)) - self.wait(2) - self.title = title - - def highlight_vectors_one_by_one(self): - # Don't know why these vectors can't get copied. - # That seems like a problem that will come up again. - labels = self.top_row[-1] - next_anims = [] - for vector, circle, label in zip(self.vectors, self.circles, labels): - # v_color = vector.get_color() - c_color = circle.get_color() - c_stroke_width = circle.get_stroke_width() - - rect = SurroundingRectangle(label, color=PINK) - self.play( - # vector.set_color, PINK, - circle.set_stroke, RED, 3, - FadeIn(rect), - *next_anims - ) - self.wait() - next_anims = [ - # vector.set_color, v_color, - circle.set_stroke, c_color, c_stroke_width, - FadeOut(rect), - ] - self.play(*next_anims) - - def change_shape(self): - # path_mob = TexMobject("\\pi") - path_mob = SVGMobject("Nail_And_Gear") - new_path = path_mob.family_members_with_points()[0] - new_path.set_height(4) - new_path.move_to(self.path, DOWN) - new_path.shift(0.5 * UP) - - self.transition_to_alt_path(new_path) - for n in range(self.n_cycles): - self.run_one_cycle() - - def transition_to_alt_path(self, new_path, morph_path=False): - new_coefs = self.get_coefficients_of_path(new_path) - new_vectors = self.get_rotating_vectors( - coefficients=new_coefs - ) - new_drawn_path = self.get_drawn_path(new_vectors) - - self.vector_clock.suspend_updating() - - vectors = self.vectors - anims = [] - - for vect, new_vect in zip(vectors, new_vectors): - new_vect.update() - new_vect.clear_updaters() - - line = Line(stroke_width=0) - line.put_start_and_end_on(*vect.get_start_and_end()) - anims.append(ApplyMethod( - line.put_start_and_end_on, - *new_vect.get_start_and_end() - )) - vect.freq = new_vect.freq - vect.coefficient = new_vect.coefficient - - vect.line = line - vect.add_updater( - lambda v: v.put_start_and_end_on( - *v.line.get_start_and_end() - ) - ) - if morph_path: - anims.append( - ReplacementTransform( - self.drawn_path, - new_drawn_path - ) - ) - else: - anims.append( - FadeOut(self.drawn_path) - ) - - self.play(*anims, run_time=3) - for vect in self.vectors: - vect.remove_updater(vect.updaters[-1]) - - if not morph_path: - self.add(new_drawn_path) - self.vector_clock.set_value(0) - - self.vector_clock.resume_updating() - self.drawn_path = new_drawn_path - - # - def get_path(self): - path = super().get_path() - path.set_height(self.drawing_height) - path.to_edge(DOWN) - return path - - def add_top_row(self, vectors, circles, max_freq=3): - self.top_row = self.get_top_row( - vectors, circles, max_freq - ) - self.add(self.top_row) - - def get_top_row(self, vectors, circles, max_freq=3): - vector_copies = VGroup() - circle_copies = VGroup() - for vector, circle in zip(vectors, circles): - if vector.freq > max_freq: - break - vcopy = vector.copy() - vcopy.clear_updaters() - ccopy = circle.copy() - ccopy.clear_updaters() - ccopy.original = circle - vcopy.original = vector - - vcopy.center_point = op.add( - self.top_row_center, - vector.freq * self.top_row_x_spacing * RIGHT, - ) - ccopy.center_point = vcopy.center_point - vcopy.add_updater(self.update_top_row_vector_copy) - ccopy.add_updater(self.update_top_row_circle_copy) - vector_copies.add(vcopy) - circle_copies.add(ccopy) - - dots = VGroup(*[ - TexMobject("\\dots").next_to( - circle_copies, direction, - MED_LARGE_BUFF, - ) - for direction in [LEFT, RIGHT] - ]) - labels = self.get_top_row_labels(vector_copies) - return VGroup( - vector_copies, - circle_copies, - dots, - labels, - ) - - def update_top_row_vector_copy(self, vcopy): - vcopy.become(vcopy.original) - vcopy.scale(self.top_row_copy_scale_factor) - vcopy.shift(vcopy.center_point - vcopy.get_start()) - return vcopy - - def update_top_row_circle_copy(self, ccopy): - ccopy.become(ccopy.original) - ccopy.scale(self.top_row_copy_scale_factor) - ccopy.move_to(ccopy.center_point) - return ccopy - - def get_top_row_labels(self, vector_copies): - labels = VGroup() - for vector_copy in vector_copies: - freq = vector_copy.freq - label = Integer(freq) - label.move_to(np.array([ - freq * self.top_row_x_spacing, - self.top_row_label_y, - 0 - ])) - labels.add(label) - return labels - - def setup_plane(self): - plane = ComplexPlane(**self.plane_config) - plane.shift(self.center_point) - plane.add_coordinates() - - top_rect = Rectangle( - width=FRAME_WIDTH, - fill_color=BLACK, - fill_opacity=1, - stroke_width=0, - height=self.top_rect_height, - ) - top_rect.to_edge(UP, buff=0) - - self.plane = plane - self.add(plane) - self.add(top_rect) - - def get_path_end(self, vectors, stroke_width=None, **kwargs): - if stroke_width is None: - stroke_width = self.drawn_path_st - full_path = self.get_vector_sum_path(vectors, **kwargs) - path = VMobject() - path.set_stroke( - self.drawn_path_color, - stroke_width - ) - - def update_path(p): - alpha = self.get_vector_time() % 1 - p.pointwise_become_partial( - full_path, - np.clip(alpha - 0.01, 0, 1), - np.clip(alpha, 0, 1), - ) - p.points[-1] = vectors[-1].get_end() - - path.add_updater(update_path) - return path - - def get_drawn_path_alpha(self): - return super().get_drawn_path_alpha() - 0.002 - - def get_drawn_path(self, vectors, stroke_width=2, **kwargs): - odp = super().get_drawn_path(vectors, stroke_width, **kwargs) - return VGroup( - odp, - self.get_path_end(vectors, stroke_width, **kwargs), - ) - - def get_vertically_falling_tracing(self, vector, color, stroke_width=3, rate=0.25): - path = VMobject() - path.set_stroke(color, stroke_width) - path.start_new_path(vector.get_end()) - path.vector = vector - - def update_path(p, dt): - p.shift(rate * dt * DOWN) - p.add_smooth_curve_to(p.vector.get_end()) - path.add_updater(update_path) - return path - - -class PiFourierSeries(ComplexFourierSeriesExample): - CONFIG = { - "tex": "\\pi", - "n_vectors": 101, - "path_height": 3.5, - "max_circle_stroke_width": 1, - "top_row_copy_scale_factor": 0.6, - } - - def construct(self): - self.setup_plane() - self.add_vectors_circles_path() - self.add_top_row(self.vectors, self.circles) - - for n in range(self.n_cycles): - self.run_one_cycle() - - def get_path(self): - pi = TexMobject(self.tex) - path = pi.family_members_with_points()[0] - path.set_height(self.path_height) - path.move_to(3 * DOWN, DOWN) - path.set_stroke(YELLOW, 0) - path.set_fill(opacity=0) - return path - - -class RealValuedFunctionFourierSeries(PiFourierSeries): - CONFIG = { - "n_vectors": 101, - "start_drawn": True, - } - - def construct(self): - self.setup_plane() - self.add_vectors_circles_path() - self.add_top_row(self.vectors, self.circles) - - self.flatten_path() - self.focus_on_vector_pair() - - def flatten_path(self): - new_path = self.path.copy() - new_path.stretch(0, 1) - new_path.set_y(self.plane.n2p(0)[1]) - self.vector_clock.set_value(10) - self.transition_to_alt_path(new_path, morph_path=True) - self.run_one_cycle() - - def focus_on_vector_pair(self): - vectors = self.vectors - circles = self.circles - top_row = self.top_row - top_vectors, top_circles, dots, labels = top_row - - rects1, rects2, rects3 = [ - VGroup(*[ - SurroundingRectangle(VGroup( - top_circles[i], - labels[i], - )) - for i in pair - ]).set_stroke(LIGHT_GREY, 2) - for pair in [(1, 2), (3, 4), (5, 6)] - ] - - def get_opacity_animation(i1, i2, alpha_func): - v_group = vectors[i1:i2] - c_group = circles[i1:i2] - return AnimationGroup( - UpdateFromAlphaFunc( - VectorizedPoint(), - lambda m, a: v_group.set_opacity( - alpha_func(a) - ) - ), - UpdateFromAlphaFunc( - VectorizedPoint(), - lambda m, a: c_group.set_stroke( - opacity=alpha_func(a) - ) - ), - ) - - self.remove(self.path, self.drawn_path) - self.play( - get_opacity_animation( - 3, len(vectors), lambda a: smooth(1 - a), - ), - ShowCreation(rects1, lag_ratio=0.3), - ) - traced_path2 = self.get_vertically_falling_tracing(vectors[2], GREEN) - self.add(traced_path2) - for n in range(3): - self.run_one_cycle() - - self.play( - get_opacity_animation(3, 5, smooth), - get_opacity_animation( - 0, 3, - lambda a: 1 - 0.75 * smooth(a) - ), - ReplacementTransform(rects1, rects2), - ) - traced_path2.set_stroke(width=1) - traced_path4 = self.get_vertically_falling_tracing(vectors[4], YELLOW) - self.add(traced_path4) - self.run_one_cycle() - self.play( - get_opacity_animation(5, 7, smooth), - get_opacity_animation( - 3, 5, - lambda a: 1 - 0.75 * smooth(a) - ), - ReplacementTransform(rects2, rects3), - ) - traced_path2.set_stroke(width=1) - traced_path4.set_stroke(width=1) - traced_path6 = self.get_vertically_falling_tracing(vectors[6], TEAL) - self.add(traced_path6) - for n in range(2): - self.run_one_cycle() - - -class DemonstrateAddingArrows(PiFourierSeries): - CONFIG = { - "tex": "\\leftarrow", - "n_arrows": 21, - "parametric_function_step_size": 0.1, - } - - def construct(self): - self.setup_plane() - self.add_vectors_circles_path() - self.add_top_row(self.vectors, self.circles) - - circles = self.circles - original_vectors = self.vectors - vectors = VGroup(*[ - Vector( - **self.vector_config - ).put_start_and_end_on(*v.get_start_and_end()) - for v in original_vectors - ]) - original_top_vectors = self.top_row[0] - top_vectors = VGroup(*[ - Vector( - **self.vector_config - ).put_start_and_end_on(*v.get_start_and_end()) - for v in original_top_vectors - ]) - - self.plane.axes.set_stroke(LIGHT_GREY, 1) - - self.vector_clock.suspend_updating() - self.remove(circles, original_vectors) - self.remove(self.path, self.drawn_path) - anims1 = [ - TransformFromCopy(tv, v) - for tv, v in zip(top_vectors, vectors) - ] - anims2 = [ - ShowCreation(v) - for v in vectors[len(top_vectors):25] - ] - self.play( - LaggedStart(*anims1), - run_time=3, - lag_ratio=0.2, - ) - self.play( - LaggedStart(*anims2), - lag_ratio=0.1, - run_time=5, - ) - - -class LabelRotatingVectors(PiFourierSeries): - CONFIG = { - "n_vectors": 6, - "center_point": 1.5 * DOWN, - "top_rect_height": 3, - "plane_config": { - "axis_config": { - "unit_size": 1.75, - "stroke_color": LIGHT_GREY, - }, - }, - "top_row_x_spacing": 1.9, - "top_row_center": 3 * UP + 0.2 * LEFT, - } - - def construct(self): - self.setup_plane() - self.setup_top_row() - - self.ask_about_labels() - self.initialize_at_one() - self.show_complex_exponents() - # self.show_complex_exponents_temp() - - self.tweak_initial_states() - self.constant_examples() - - def setup_top_row(self): - vectors = self.get_rotating_vectors( - coefficients=0.5 * np.ones(self.n_vectors) - ) - circles = self.get_circles(vectors) - - top_row = self.get_top_row(vectors, circles) - top_row.shift(0.5 * DOWN + 0.25 * RIGHT) - v_copies, c_copies, dots, labels = top_row - labels.to_edge(UP, MED_SMALL_BUFF) - freq_label = TextMobject("Frequencies:") - freq_label.to_edge(LEFT, MED_SMALL_BUFF) - freq_label.match_y(labels) - VGroup(freq_label, labels).set_color(YELLOW) - - def get_constant_func(const): - return lambda: const - - for vector, v_copy in zip(vectors, v_copies): - vector.center_func = get_constant_func( - v_copy.get_start() - ) - vectors.update(0) - circles.update(0) - - self.add(vectors) - self.add(circles) - self.add(dots) - self.add(freq_label) - self.add(labels) - - self.vectors = vectors - self.circles = circles - self.labels = labels - self.freq_label = freq_label - - def ask_about_labels(self): - circles = self.circles - - formulas = TextMobject("Formulas:") - formulas.next_to(circles, DOWN) - formulas.to_edge(LEFT, MED_SMALL_BUFF) - - q_marks = VGroup(*[ - TexMobject("??").scale(1.0).next_to(circle, DOWN) - for circle in circles - ]) - - self.play(FadeInFrom(formulas, DOWN)) - self.play(LaggedStartMap( - FadeInFrom, q_marks, - lambda m: (m, UP), - lag_ratio=0.2, - run_time=3, - )) - self.wait(3) - - self.q_marks = q_marks - self.formulas_word = formulas - - def initialize_at_one(self): - vectors = self.vectors - circles = self.circles - vector_clock = self.vector_clock - plane = self.plane - q_marks = self.q_marks - - # Why so nuclear? - vc_updater = vector_clock.updaters.pop() - self.play( - vector_clock.set_value, 0, - run_time=2, - ) - - zero_vect = Vector() - zero_vect.replace(vectors[0]) - zero_circle = self.get_circle(zero_vect) - zero_circle.match_style(circles[0]) - self.add(zero_circle) - - one_label = TexMobject("1") - one_label.move_to(q_marks[0]) - - self.play( - zero_vect.put_start_and_end_on, - plane.n2p(0), plane.n2p(1), - ) - vector_clock.add_updater(vc_updater) - self.wait() - self.play( - FadeOutAndShift(q_marks[0], UP), - FadeInFrom(one_label, DOWN), - ) - self.wait(4) - - self.one_label = one_label - self.zero_vect = zero_vect - self.zero_circle = zero_circle - - def show_complex_exponents(self): - vectors = self.vectors - circles = self.circles - q_marks = self.q_marks - labels = self.labels - one_label = self.one_label - v_lines = self.get_v_lines(circles) - - # Vector 1 - v1_rect = SurroundingRectangle( - VGroup(circles[1], q_marks[1], labels[1]), - stroke_color=GREY, - stroke_width=2, - ) - f1_exp = self.get_exp_tex() - f1_exp.move_to(q_marks[1], DOWN) - - self.play( - FadeOut(self.zero_vect), - FadeOut(self.zero_circle), - FadeIn(v1_rect) - ) - - vg1 = self.get_vector_in_plane_group( - vectors[1], circles[1], - ) - vg1_copy = vg1.copy() - vg1_copy.clear_updaters() - vg1_copy.replace(circles[1]) - - cps_1 = self.get_cps_label(1) - - circle_copy = vg1[1].copy().clear_updaters() - circle_copy.set_stroke(YELLOW, 3) - arclen_decimal = DecimalNumber( - num_decimal_places=3, - show_ellipsis=True, - ) - arclen_tracker = ValueTracker(0) - arclen_decimal.add_updater(lambda m: m.next_to( - circle_copy.get_end(), UR, SMALL_BUFF, - )) - arclen_decimal.add_updater(lambda m: m.set_value( - arclen_tracker.get_value() - )) - - self.play( - ReplacementTransform(vg1_copy, vg1), - ) - self.play(FadeInFrom(cps_1, DOWN)) - self.wait(2) - self.play( - FadeOutAndShift(q_marks[1], UP), - FadeInFrom(f1_exp, DOWN), - ) - self.wait(2) - self.play(ShowCreationThenFadeAround( - f1_exp.get_part_by_tex("2\\pi") - )) - self.add(arclen_decimal), - self.play( - ShowCreation(circle_copy), - arclen_tracker.set_value, TAU, - run_time=3, - ) - self.wait() - self.play( - FadeOut(circle_copy), - FadeOut(arclen_decimal), - ) - self.wait(8) - self.play( - v1_rect.move_to, circles[2], - v1_rect.match_y, v1_rect, - FadeOut(vg1), - FadeOut(cps_1), - ) - - # Vector -1 - vgm1 = self.get_vector_in_plane_group( - vectors[2], circles[2], - ) - vgm1_copy = vgm1.copy() - vgm1_copy.clear_updaters() - vgm1_copy.replace(circles[2]) - cps_m1 = self.get_cps_label(-1) - fm1_exp = self.get_exp_tex(-1) - fm1_exp.move_to(q_marks[2], DOWN) - - self.play( - ReplacementTransform(vgm1_copy, vgm1), - FadeInFromDown(cps_m1) - ) - self.wait(2) - self.play( - FadeOutAndShift(q_marks[2], UP), - FadeInFromDown(fm1_exp), - v1_rect.stretch, 1.4, 0, - ) - self.wait(5) - self.play( - v1_rect.move_to, circles[3], - v1_rect.match_y, v1_rect, - FadeOut(vgm1), - FadeOut(cps_m1), - ) - - # Vector 2 - # (Lots of copy-pasting here) - vg2 = self.get_vector_in_plane_group( - vectors[3], circles[3], - ) - vg2_copy = vg2.copy() - vg2_copy.clear_updaters() - vg2_copy.replace(circles[3]) - cps_2 = self.get_cps_label(2) - f2_exp = self.get_exp_tex(2) - f2_exp.move_to(q_marks[3], DOWN) - circle_copy.append_vectorized_mobject(circle_copy) - - self.play( - ReplacementTransform(vg2_copy, vg2), - FadeInFromDown(cps_2) - ) - self.wait() - self.play( - FadeOutAndShift(q_marks[3], UP), - FadeInFromDown(f2_exp), - ) - self.wait(3) - - self.play(ShowCreationThenFadeAround( - f2_exp.get_parts_by_tex("2"), - )) - self.add(arclen_decimal) - arclen_tracker.set_value(0) - self.play( - ShowCreation(circle_copy), - arclen_tracker.set_value, 2 * TAU, - run_time=5 - ) - self.wait(3) - self.play( - FadeOut(circle_copy), - FadeOut(arclen_decimal), - ) - self.play( - FadeOut(vg2), - FadeOut(cps_2), - FadeOut(v1_rect), - ) - - # Show all formulas - fm2_exp = self.get_exp_tex(-2) - fm2_exp.move_to(q_marks[4], DOWN) - f3_exp = self.get_exp_tex(3) - f3_exp.move_to(q_marks[5], DOWN) - f1_exp_new = self.get_exp_tex(1) - f1_exp_new.move_to(q_marks[1], DOWN) - f0_exp = self.get_exp_tex(0) - f0_exp.move_to(q_marks[0], DOWN) - f_exp_general = self.get_exp_tex("n") - f_exp_general.next_to(self.formulas_word, DOWN) - - self.play( - FadeOut(q_marks[4:]), - FadeOut(f1_exp), - FadeIn(f1_exp_new), - FadeInFromDown(fm2_exp), - FadeInFromDown(f3_exp), - FadeIn(v_lines, lag_ratio=0.2) - ) - self.play( - FadeInFrom(f_exp_general, UP) - ) - self.play(ShowCreationThenFadeAround(f_exp_general)) - self.wait(3) - self.play( - FadeOut(one_label, UP), - TransformFromCopy(f_exp_general, f0_exp), - ) - self.wait(5) - - self.f_exp_labels = VGroup( - f0_exp, f1_exp_new, fm1_exp, - f2_exp, fm2_exp, f3_exp, - ) - self.f_exp_general = f_exp_general - - def show_complex_exponents_temp(self): - self.f_exp_labels = VGroup(*[ - self.get_exp_tex(n).move_to(qm, DOWN) - for n, qm in zip( - [0, 1, -1, 2, -2, 3], - self.q_marks, - ) - ]) - self.f_exp_general = self.get_exp_tex("n") - self.f_exp_general.next_to(self.formulas_word, DOWN) - - self.remove(*self.q_marks, self.one_label) - self.remove(self.zero_vect, self.zero_circle) - self.add(self.f_exp_labels, self.f_exp_general) - - def tweak_initial_states(self): - vector_clock = self.vector_clock - f_exp_labels = self.f_exp_labels - f_exp_general = self.f_exp_general - vectors = self.vectors - - cn_terms = VGroup() - for i, f_exp in enumerate(f_exp_labels): - n = (i + 1) // 2 - if i % 2 == 0 and i > 0: - n *= -1 - cn_terms.add(self.get_cn_label(n, f_exp)) - cn_general = self.get_cn_label("n", f_exp_general) - - new_coefs = [ - 0.5, - np.exp(complex(0, TAU / 8)), - 0.7 * np.exp(-complex(0, TAU / 8)), - 0.6 * np.exp(complex(0, TAU / 3)), - 1.1 * np.exp(-complex(0, TAU / 12)), - 0.3 * np.exp(complex(0, TAU / 12)), - ] - - def update_vectors(alpha): - for vect, new_coef in zip(vectors, new_coefs): - vect.coefficient = 0.5 * interpolate( - 1, new_coef, alpha - ) - - vector_clock.incrementer = vector_clock.updaters.pop() - self.play( - vector_clock.set_value, - int(vector_clock.get_value()) - ) - self.play( - LaggedStartMap( - MoveToTarget, - VGroup(f_exp_general, *f_exp_labels), - ), - LaggedStartMap( - FadeInFromDown, - VGroup(cn_general, *cn_terms), - ), - UpdateFromAlphaFunc( - VectorizedPoint(), - lambda m, a: update_vectors(a) - ), - run_time=2 - ) - self.wait() - self.play( - LaggedStart(*[ - ShowCreationThenFadeAround( - cn_term, - surrounding_rectangle_config={ - "buff": 0.05, - "stroke_width": 2, - }, - ) - for cn_term in cn_terms - ]) - ) - - self.cn_terms = cn_terms - self.cn_general = cn_general - - def constant_examples(self): - cn_terms = self.cn_terms - vectors = self.vectors - circles = self.circles - - # c0 term - c0_brace = Brace(cn_terms[0], DOWN, buff=SMALL_BUFF) - c0_label = TexMobject("0.5") - c0_label.next_to(c0_brace, DOWN, SMALL_BUFF) - c0_label.add_background_rectangle() - vip_group0 = self.get_vector_in_plane_group( - vectors[0], circles[0] - ) - vip_group0_copy = vip_group0.copy() - vip_group0_copy.clear_updaters() - vip_group0_copy.replace(circles[0]) - - self.play( - Transform(vip_group0_copy, vip_group0) - ) - self.wait() - self.play(vip_group0_copy.scale, 2) - self.play( - vip_group0_copy.scale, 0.5, - GrowFromCenter(c0_brace), - GrowFromCenter(c0_label), - ) - self.wait(2) - self.play( - FadeOut(c0_brace), - FadeOut(c0_label), - FadeOut(vip_group0_copy), - ) - - # c1 term - c1_brace = Brace(cn_terms[1], DOWN, buff=SMALL_BUFF) - c1_label = TexMobject("e^{(\\pi / 4)i}") - c1_label.next_to(c1_brace, DOWN, SMALL_BUFF) - c1_decimal = DecimalNumber( - np.exp(np.complex(0, PI / 4)), - num_decimal_places=3, - ) - approx = TexMobject("\\approx") - approx.next_to(c1_label, RIGHT, MED_SMALL_BUFF) - c1_decimal.next_to(approx, RIGHT, MED_SMALL_BUFF) - scalar = DecimalNumber(0.3) - scalar.next_to( - c1_label, LEFT, SMALL_BUFF, - aligned_edge=DOWN, - ) - - vip_group1 = self.get_vector_in_plane_group( - vectors[1], circles[1] - ) - vip_group1_copy = vip_group1.copy() - vip_group1_copy[0].stroke_width = 3 - vip_group1_copy.clear_updaters() - vip_group1_copy.save_state() - vip_group1_copy.replace(circles[1]) - - self.play( - Restore(vip_group1_copy) - ) - self.play(Rotate(vip_group1_copy, -PI / 4)) - self.play(Rotate(vip_group1_copy, PI / 4)) - self.play( - GrowFromCenter(c1_brace), - FadeIn(c1_label), - ) - self.play( - Write(approx), - Write(c1_decimal), - run_time=1, - ) - self.wait(2) - - def update_v1(alpha): - vectors[1].coefficient = 0.5 * interpolate( - np.exp(complex(0, PI / 4)), - 0.3 * np.exp(complex(0, PI / 4)), - alpha - ) - - self.play( - FadeIn(scalar), - c1_decimal.set_value, - scalar.get_value() * c1_decimal.get_value(), - vip_group1_copy.scale, scalar.get_value(), - UpdateFromAlphaFunc( - VMobject(), - lambda m, a: update_v1(a) - ) - ) - self.wait() - self.play( - FadeOut(c1_brace), - FadeOut(c1_label), - FadeOut(approx), - FadeOut(c1_decimal), - FadeOut(scalar), - FadeOut(vip_group1_copy), - ) - - fade_anims = [] - for cn_term, vect in zip(cn_terms[2:], vectors[2:]): - rect = SurroundingRectangle(cn_term, buff=0.025) - rect.set_stroke(width=2) - decimal = DecimalNumber(vect.coefficient) - decimal.next_to(rect, DOWN) - decimal.add_background_rectangle() - if cn_term is cn_terms[4]: - decimal.shift(0.7 * RIGHT) - - self.play( - ShowCreation(rect), - FadeIn(decimal), - *fade_anims - ) - self.wait() - fade_anims = [FadeOut(rect), FadeOut(decimal)] - self.play(*fade_anims) - - # - def get_vector_in_plane_group(self, top_vector, top_circle): - plane = self.plane - origin = plane.n2p(0) - - vector = Vector() - vector.add_updater( - lambda v: v.put_start_and_end_on( - origin, - plane.n2p(2 * top_vector.coefficient) - ).set_angle(top_vector.get_angle()) - ) - circle = Circle() - circle.match_style(top_circle) - circle.set_width(2 * vector.get_length()) - circle.move_to(origin) - - return VGroup(vector, circle) - - def get_exp_tex(self, freq=None): - if freq is None: - freq_str = "{}" - else: - freq_str = "{" + str(freq) + "}" + "\\cdot" - - result = TexMobject( - "e^{", freq_str, "2\\pi i {t}}", - tex_to_color_map={ - "2\\pi": WHITE, - "{t}": PINK, - freq_str: YELLOW, - } - ) - result.scale(0.9) - return result - - def get_cn_label(self, n, exp_label): - exp_label.generate_target() - exp_label.target.scale(0.9) - - n_str = "{" + str(n) + "}" - term = TexMobject("c_", n_str) - term.set_color(GREEN) - term[1].set_color(YELLOW) - term[1].set_width(0.12) - term[1].move_to(term[0].get_corner(DR), LEFT) - if isinstance(n, str): - term[1].scale(1.4, about_edge=LEFT) - term[1].shift(0.03 * RIGHT) - elif n < 0: - term[1].scale(1.4, about_edge=LEFT) - term[1].set_stroke(width=0.5) - else: - term[1].shift(0.05 * RIGHT) - term.scale(0.9) - term.shift( - exp_label.target[0].get_corner(LEFT) - - term[0].get_corner(RIGHT) + - 0.2 * LEFT - ) - VGroup(exp_label.target, term).move_to( - exp_label, DOWN - ) - - if isinstance(n, str): - VGroup(term, exp_label.target).scale( - 1.3, about_edge=UP - ) - - return term - - def get_cps_label(self, n): - n_str = str(n) - if n == 1: - frac_tex = "\\frac{\\text{cycle}}{\\text{second}}" - else: - frac_tex = "\\frac{\\text{cycles}}{\\text{second}}" - - result = TexMobject( - n_str, frac_tex, - tex_to_color_map={ - n_str: YELLOW - }, - ) - result[1].scale(0.7, about_edge=LEFT) - result[0].scale(1.2, about_edge=RIGHT) - result.next_to(self.plane.n2p(2), UR) - return result - - def get_v_lines(self, circles): - lines = VGroup() - o_circles = VGroup(*circles) - o_circles.sort(lambda p: p[0]) - for c1, c2 in zip(o_circles, o_circles[1:]): - line = DashedLine(3 * UP, ORIGIN) - line.set_stroke(WHITE, 1) - line.move_to(midpoint( - c1.get_center(), c2.get_center(), - )) - lines.add(line) - return lines - - -class IntegralTrick(LabelRotatingVectors, TRangingFrom0To1): - CONFIG = { - "file_name": "EighthNote", - "n_vectors": 101, - "path_height": 3.5, - "plane_config": { - "x_min": -1.75, - "x_max": 1.75, - "axis_config": { - "unit_size": 1.75, - "stroke_color": LIGHT_GREY, - }, - }, - "center_point": 1.5 * DOWN + 3 * RIGHT, - "input_space_rect_config": { - "width": 6, - "height": 1.5, - }, - "start_drawn": True, - "parametric_function_step_size": 0.01, - "top_row_center": 2 * UP + RIGHT, - "top_row_x_spacing": 2.25, - } - - def construct(self): - self.setup_plane() - self.add_vectors_circles_path() - self.setup_input_space() - self.setup_input_trackers() - self.setup_top_row() - self.setup_sum() - - self.introduce_sum() - self.issolate_c0() - self.show_center_of_mass() - self.write_integral() - - def setup_input_space(self): - super().setup_input_space() - self.input_line.next_to( - self.input_rect.get_bottom(), - UP, - ) - group = VGroup( - self.input_rect, - self.input_line, - ) - group.move_to(self.plane.n2p(0)) - group.to_edge(LEFT) - - def setup_top_row(self): - top_row = self.get_top_row( - self.vectors, self.circles, - max_freq=2, - ) - self.top_vectors, self.top_circles, dots, labels = top_row - - self.add(*top_row) - self.remove(labels) - - def setup_sum(self): - top_vectors = self.top_vectors - - terms = VGroup() - for vect in top_vectors: - freq = vect.freq - exp = self.get_exp_tex(freq) - cn = self.get_cn_label(freq, exp) - exp.become(exp.target) - term = VGroup(cn, exp) - term.move_to(vect.get_start()) - term.shift(UP) - terms.add(term) - - for vect in [LEFT, RIGHT]: - dots = TexMobject("\\cdots") - dots.next_to(terms, vect, MED_LARGE_BUFF) - terms.add(dots) - - plusses = VGroup() - o_terms = VGroup(*terms) - o_terms.sort(lambda p: p[0]) - for t1, t2 in zip(o_terms, o_terms[1:]): - plus = TexMobject("+") - plus.scale(0.7) - plus.move_to(midpoint( - t1.get_right(), - t2.get_left(), - )) - plusses.add(plus) - terms[:-2].shift(0.05 * UP) - - ft_eq = TexMobject("f(t)", "= ") - ft_eq.next_to(terms, LEFT) - - self.add(terms) - self.add(plusses) - self.add(ft_eq) - - self.terms = terms - self.plusses = plusses - self.ft_eq = ft_eq - - def introduce_sum(self): - self.remove( - self.vector_clock, - self.vectors, - self.circles, - self.drawn_path, - ) - - ft = self.ft_eq[0] - terms = self.terms - path = self.path - input_tracker = self.input_tracker - - rect = SurroundingRectangle(ft) - coefs = VGroup(*[term[0] for term in terms[:-2]]) - terms_rect = SurroundingRectangle(terms) - terms_rect.set_stroke(YELLOW, 1.5) - - dot = Dot() - dot.add_updater(lambda d: d.move_to(path.get_end())) - - self.play(ShowCreation(rect)) - self.wait() - self.play( - ReplacementTransform(rect, dot) - ) - path.set_stroke(YELLOW, 2) - self.play( - ShowCreation(path), - input_tracker.set_value, 1, - run_time=3, - rate_func=lambda t: smooth(t, 1), - ) - self.wait() - - input_tracker.add_updater( - lambda m: m.set_value( - self.vector_clock.get_value() % 1 - ) - ) - self.add( - self.vector_clock, - self.vectors, - self.circles, - ) - self.play( - FadeOut(path), - FadeOut(dot), - FadeIn(self.drawn_path), - ) - self.play(FadeIn(terms_rect)) - self.wait() - self.play(FadeOut(terms_rect)) - - fade_outs = [] - for coef in coefs: - rect = SurroundingRectangle(coef) - self.play(FadeIn(rect), *fade_outs) - fade_outs = [FadeOut(rect)] - self.play(*fade_outs) - self.wait(2) - - self.vector_clock.clear_updaters() - - def issolate_c0(self): - vectors = self.vectors - circles = self.circles - terms = self.terms - top_circles = self.top_circles - path = self.path - - path.set_stroke(YELLOW, 1) - - c0_rect = SurroundingRectangle( - VGroup(top_circles[0], terms[0]) - ) - c0_rect.set_stroke(WHITE, 1) - - opacity_tracker = ValueTracker(1) - for vect in vectors[1:]: - vect.add_updater( - lambda v: v.set_opacity( - opacity_tracker.get_value() - ) - ) - for circle in circles[0:]: - circle.add_updater( - lambda c: c.set_stroke( - opacity=opacity_tracker.get_value() - ) - ) - - self.play(ShowCreation(c0_rect)) - self.play( - opacity_tracker.set_value, 0.2, - FadeOut(self.drawn_path), - FadeIn(path) - ) - - v0 = vectors[0] - v0_point = VectorizedPoint(v0.get_end()) - origin = self.plane.n2p(0) - v0.add_updater(lambda v: v.put_start_and_end_on( - origin, v0_point.get_location(), - )) - - self.play( - MaintainPositionRelativeTo(path, v0_point), - ApplyMethod( - v0_point.shift, 1.5 * LEFT, - run_time=4, - rate_func=there_and_back, - path_arc=60 * DEGREES, - ) - ) - v0.updaters.pop() - - self.opacity_tracker = opacity_tracker - - def show_center_of_mass(self): - dot_sets = VGroup(*[ - self.get_sample_dots(dt=dt, radius=radius) - for dt, radius in [ - (0.05, 0.04), - (0.01, 0.03), - (0.0025, 0.02), - ] - ]) - input_dots, output_dots = dot_sets[0] - v0_dot = input_dots[0].deepcopy() - v0_dot.move_to(center_of_mass([ - od.get_center() - for od in output_dots - ])) - v0_dot.set_color(RED) - - self.play(LaggedStartMap( - FadeInFromLarge, input_dots, - lambda m: (m, 5), - run_time=2, - lag_ratio=0.5, - )) - self.wait() - self.play( - TransformFromCopy( - input_dots, - output_dots, - run_time=3 - ) - ) - self.wait() - self.play(*[ - Transform( - od.copy(), v0_dot.copy(), - remover=True - ) - for od in output_dots - ]) - self.add(v0_dot) - self.wait() - - for ds1, ds2 in zip(dot_sets, dot_sets[1:]): - ind1, outd1 = ds1 - ind2, outd2 = ds2 - new_v0_dot = v0_dot.copy() - new_v0_dot.move_to(center_of_mass([ - od.get_center() - for od in outd2 - ])) - self.play( - FadeOut(ind1), - LaggedStartMap( - FadeInFrom, ind2, - lambda m: (m, UP), - lag_ratio=4 / len(ind2), - run_time=2, - ) - ) - self.play( - TransformFromCopy(ind2, outd2), - FadeOut(outd1), - run_time=2, - ) - self.play( - FadeOut(v0_dot), - *[ - Transform( - od.copy(), v0_dot.copy(), - remover=True - ) - for od in outd2 - ] - ) - v0_dot = new_v0_dot - self.add(v0_dot) - self.wait() - - self.input_dots, self.output_dots = dot_sets[-1] - self.v0_dot = v0_dot - - def write_integral(self): - t_tracker = self.vector_clock - path = self.path - - expression = TexMobject( - "c_{0}", "=" - "\\int_0^1 f({t}) d{t}", - tex_to_color_map={ - "{t}": PINK, - "{0}": YELLOW, - }, - ) - expression.next_to(self.input_rect, UP) - brace = Brace(expression[2:], UP, buff=SMALL_BUFF) - average = brace.get_text("Average", buff=SMALL_BUFF) - - self.play( - FadeInFromDown(expression), - GrowFromCenter(brace), - FadeIn(average), - ) - t_tracker.clear_updaters() - t_tracker.set_value(0) - self.add(path) - self.play( - t_tracker.set_value, 0.999, - ShowCreation(path), - run_time=8, - rate_func=lambda t: smooth(t, 1), - ) - self.wait() - - # - def get_path(self): - mob = SVGMobject(self.file_name) - path = mob.family_members_with_points()[0] - path.set_height(self.path_height) - path.move_to(self.center_point) - path.shift(0.5 * UR) - path.set_stroke(YELLOW, 0) - path.set_fill(opacity=0) - return path - - def get_sample_dots(self, dt, radius): - input_line = self.input_line - path = self.path - - t_values = np.arange(0, 1 + dt, dt) - dot = Dot(color=PINK, radius=radius) - dot.set_stroke( - RED, 1, - opacity=0.8, - background=True, - ) - input_dots = VGroup() - output_dots = VGroup() - for t in t_values: - in_dot = dot.copy() - out_dot = dot.copy() - in_dot.move_to(input_line.n2p(t)) - out_dot.move_to(path.point_from_proportion(t)) - input_dots.add(in_dot) - output_dots.add(out_dot) - return VGroup(input_dots, output_dots) - - -class IncreaseOrderOfApproximation(ComplexFourierSeriesExample): - CONFIG = { - "file_name": "FourierOneLine", - "drawing_height": 6, - "n_vectors": 250, - "parametric_function_step_size": 0.001, - "run_time": 10, - # "n_vectors": 25, - # "parametric_function_step_size": 0.01, - # "run_time": 5, - "slow_factor": 0.05, - } - - def construct(self): - path = self.get_path() - path.to_edge(DOWN) - path.set_stroke(YELLOW, 2) - freqs = self.get_freqs() - coefs = self.get_coefficients_of_path( - path, freqs=freqs, - ) - vectors = self.get_rotating_vectors(freqs, coefs) - circles = self.get_circles(vectors) - - n_tracker = ValueTracker(2) - n_label = VGroup( - TextMobject("Approximation using"), - Integer(100).set_color(YELLOW), - TextMobject("vectors") - ) - n_label.arrange(RIGHT) - n_label.to_corner(UL) - n_label.add_updater( - lambda n: n[1].set_value( - n_tracker.get_value() - ).align_to(n[2], DOWN) - ) - - changing_path = VMobject() - vector_copies = VGroup() - circle_copies = VGroup() - - def update_changing_path(cp): - n = n_label[1].get_value() - cp.become(self.get_vector_sum_path(vectors[:n])) - cp.set_stroke(YELLOW, 2) - # While we're at it... - vector_copies.submobjects = list(vectors[:n]) - circle_copies.submobjects = list(circles[:n]) - - changing_path.add_updater(update_changing_path) - - self.add(n_label, n_tracker, changing_path) - self.add(vector_copies, circle_copies) - self.play( - n_tracker.set_value, self.n_vectors, - rate_func=smooth, - run_time=self.run_time, - ) - self.wait(5) - - -class ShowStepFunctionIn2dView(SimpleComplexExponentExample, ComplexFourierSeriesExample): - CONFIG = { - "input_space_rect_config": { - "width": 5, - "height": 2, - }, - "input_line_config": { - "unit_size": 3, - "x_min": 0, - "x_max": 1, - "tick_frequency": 0.1, - "stroke_width": 2, - "decimal_number_config": { - "num_decimal_places": 1, - } - }, - "input_numbers": [0, 0.5, 1], - "input_tex_args": [], - # "n_vectors": 300, - "n_vectors": 2, - } - - def construct(self): - self.setup_plane() - self.setup_input_space() - self.setup_input_trackers() - self.clear() - - self.transition_from_step_function() - self.show_output() - self.show_fourier_series() - - def setup_input_space(self): - super().setup_input_space() - rect = self.input_rect - line = self.input_line - # rect.stretch(1.2, 1, about_edge=UP) - line.shift(MED_SMALL_BUFF * UP) - sf = 1.2 - line.stretch(sf, 0) - for n in line.numbers: - n.stretch(1 / sf, 0) - - label = TextMobject("Input space") - label.next_to(rect.get_bottom(), UP, SMALL_BUFF) - self.add(label) - self.input_space_label = label - - def transition_from_step_function(self): - x_axis = self.input_line - input_tip = self.input_tip - input_label = self.input_label - input_rect = self.input_rect - input_space_label = self.input_space_label - plane = self.plane - plane.set_opacity(0) - - x_axis.save_state() - # x_axis.center() - x_axis.move_to(ORIGIN, LEFT) - sf = 1.5 - x_axis.stretch(sf, 0) - for number in x_axis.numbers: - number.stretch(1 / sf, 0) - x_axis.numbers[0].set_opacity(0) - - y_axis = NumberLine( - unit_size=2, - x_min=-1.5, - x_max=1.5, - tick_frequency=0.5, - stroke_color=LIGHT_GREY, - stroke_width=2, - ) - # y_axis.match_style(x_axis) - y_axis.rotate(90 * DEGREES) - y_axis.shift(x_axis.n2p(0) - y_axis.n2p(0)) - y_axis.add_numbers( - -1, 0, 1, - direction=LEFT, - ) - axes = Axes() - axes.x_axis = x_axis - axes.y_axis = y_axis - axes.axes = VGroup(x_axis, y_axis) - - graph = VGroup( - Line( - axes.c2p(0, 1), - axes.c2p(0.5, 1), - color=RED, - ), - Line( - axes.c2p(0.5, -1), - axes.c2p(1, -1), - color=BLUE, - ), - ) - - dot1 = Dot(color=RED) - dot2 = Dot(color=BLUE) - dot1.add_updater(lambda d: d.move_to(y_axis.n2p(1))) - dot2.add_updater(lambda d: d.move_to(y_axis.n2p(-1))) - squish_graph = VGroup(dot1, dot2) - - self.add(x_axis) - self.add(y_axis) - self.add(input_tip) - self.add(input_label) - - self.play( - self.input_tracker.set_value, 1, - ShowCreation(graph), - run_time=3, - rate_func=lambda t: smooth(t, 1) - ) - self.wait() - self.add( - plane, input_rect, input_space_label, - x_axis, input_tip, input_label, - ) - self.play( - FadeIn(input_rect), - FadeIn(input_space_label), - Restore(x_axis), - ) - self.play(ReplacementTransform(graph, squish_graph)) - - # Rotate y-axis, fade in plane - y_axis.generate_target(use_deepcopy=True) - y_axis.target.rotate(-TAU / 4) - y_axis.target.shift( - plane.n2p(0) - y_axis.target.n2p(0) - ) - y_axis.target.numbers.set_opacity(0) - - plane.set_opacity(1) - self.play( - MoveToTarget(y_axis), - ShowCreation(plane), - ) - self.play(FadeOut(y_axis)) - self.wait() - self.play(self.input_tracker.set_value, 0) - - self.output_dots = squish_graph - - def show_output(self): - input_tracker = self.input_tracker - - def get_output_point(): - return self.get_output_point(input_tracker.get_value()) - - tip = ArrowTip(start_angle=-TAU / 4) - tip.set_fill(YELLOW) - tip.match_height(self.input_tip) - tip.add_updater(lambda m: m.move_to( - get_output_point(), DOWN, - )) - output_label = TextMobject("Output") - output_label.add_background_rectangle() - output_label.add_updater(lambda m: m.next_to( - tip, UP, SMALL_BUFF, - )) - - self.play( - FadeIn(tip), - FadeIn(output_label), - ) - - self.play( - input_tracker.set_value, 1, - run_time=8, - rate_func=linear - ) - self.wait() - self.play(input_tracker.set_value, 0) - - self.output_tip = tip - - def show_fourier_series(self): - plane = self.plane - input_tracker = self.input_tracker - output_tip = self.output_tip - - self.play( - plane.axes.set_stroke, WHITE, 1, - plane.background_lines.set_stroke, LIGHT_GREY, 0.5, - plane.faded_lines.set_stroke, LIGHT_GREY, 0.25, 0.5, - ) - - self.vector_clock.set_value(0) - self.add(self.vector_clock) - input_tracker.add_updater(lambda m: m.set_value( - self.vector_clock.get_value() % 1 - )) - self.add_vectors_circles_path() - self.remove(self.drawn_path) - self.add(self.vectors) - output_tip.clear_updaters() - output_tip.add_updater(lambda m: m.move_to( - self.vectors[-1].get_end(), DOWN - )) - - self.run_one_cycle() - path = self.get_vertically_falling_tracing( - self.vectors[1], GREEN, rate=0.5, - ) - self.add(path) - for x in range(3): - self.run_one_cycle() - - # - def get_freqs(self): - n = self.n_vectors - all_freqs = [ - *range(1, n + 1 // 2, 2), - *range(-1, -n + 1 // 2, -2), - ] - all_freqs.sort(key=abs) - return all_freqs - - def get_path(self): - path = VMobject() - p0, p1 = [ - self.get_output_point(x) - for x in [0, 1] - ] - for p in p0, p1: - path.start_new_path(p) - path.add_line_to(p) - return path - - def get_output_point(self, x): - return self.plane.n2p(self.step(x)) - - def step(self, x): - if x < 0.5: - return 1 - elif x == 0.5: - return 0 - else: - return -1 - - -class AddVectorsOneByOne(IntegralTrick): - CONFIG = { - "file_name": "TrebleClef", - # "start_drawn": True, - "n_vectors": 101, - "path_height": 5, - } - - def construct(self): - self.setup_plane() - self.add_vectors_circles_path() - self.setup_input_space() - self.setup_input_trackers() - self.setup_top_row() - self.setup_sum() - - self.show_sum() - - def show_sum(self): - vectors = self.vectors - vector_clock = self.vector_clock - terms = self.terms - - vector_clock.suspend_updating() - coef_tracker = ValueTracker(0) - - def update_vector(vector): - vector.coefficient = interpolate( - 1, vector.original_coefficient, - coef_tracker.get_value() - ) - - for vector in vectors: - vector.original_coefficient = vector.coefficient - vector.add_updater(update_vector) - - rects = VGroup(*[ - SurroundingRectangle(t[0]) - for t in terms[:5] - ]) - - self.remove(self.drawn_path) - self.play(LaggedStartMap( - VFadeInThenOut, rects - )) - self.play( - coef_tracker.set_value, 1, - run_time=3 - ) - self.wait() - vector_clock.resume_updating() - self.input_tracker.add_updater( - lambda m: m.set_value(vector_clock.get_value() % 1) - ) - self.add(self.drawn_path, self.input_tracker) - self.wait(10) - - def get_path(self): - mob = SVGMobject(self.file_name) - path = mob.family_members_with_points()[0] - path.set_height(self.path_height) - path.move_to(self.plane.n2p(0)) - path.set_stroke(YELLOW, 0) - path.set_fill(opacity=0) - return path - - -class DE4Thumbnail(ComplexFourierSeriesExample): - CONFIG = { - "file_name": "FourierOneLine", - "start_drawn": True, - "n_vectors": 300, - "parametric_function_step_size": 0.0025, - "drawn_path_stroke_width": 7, - "drawing_height": 6, - } - - def construct(self): - name = TextMobject("Fourier series") - name.set_width(FRAME_WIDTH - 2) - name.to_edge(UP) - name.set_color(YELLOW) - subname = TextMobject("a.k.a ``everything is rotations''") - subname.match_width(name) - subname.next_to(name, DOWN) - VGroup(name, subname).to_edge(DOWN) - - self.add(name) - self.add(subname) - - path = self.get_path() - path.to_edge(DOWN) - path.set_stroke(YELLOW, 2) - freqs = self.get_freqs() - coefs = self.get_coefficients_of_path(path, freqs=freqs) - vectors = self.get_rotating_vectors(freqs, coefs) - # circles = self.get_circles(vectors) - - ns = [10, 50, 250] - approxs = VGroup(*[ - self.get_vector_sum_path(vectors[:n]) - for n in ns - ]) - approxs.arrange(RIGHT, buff=2.5) - approxs.set_height(3.75) - approxs.to_edge(UP, buff=1.25) - for a, c, w in zip(approxs, [BLUE, GREEN, YELLOW], [4, 3, 2]): - a.set_stroke(c, w) - a.set_stroke(WHITE, w + w / 2, background=True) - - labels = VGroup() - for n, approx in zip(ns, approxs): - label = TexMobject("n = ", str(n)) - label[1].match_color(approx) - label.scale(2) - label.next_to(approx, UP) - label.to_edge(UP, buff=MED_SMALL_BUFF) - labels.add(label) - - self.add(approxs) - self.add(labels) - - return - - self.add_vectors_circles_path() - n = 6 - self.circles[n:].set_opacity(0) - self.circles[:n].set_stroke(width=3) - path = self.drawn_path - # path.set_stroke(BLACK, 8, background=True) - # path = self.path - # path.set_stroke(YELLOW, 5) - # path.set_stroke(BLACK, 8, background=True) - self.add(path, self.circles, self.vectors) - - self.update_mobjects(0) diff --git a/from_3b1b/active/diffyq/part4/long_fourier_scenes.py b/from_3b1b/active/diffyq/part4/long_fourier_scenes.py deleted file mode 100644 index 9fae0ec0..00000000 --- a/from_3b1b/active/diffyq/part4/long_fourier_scenes.py +++ /dev/null @@ -1,242 +0,0 @@ -from manimlib.imports import * - -from active_projects.diffyq.part4.fourier_series_scenes import ComplexFourierSeriesExample -from manimlib.once_useful_constructs.fractals import HilbertCurve - - -class FourierSeriesExampleWithRectForZoom(ComplexFourierSeriesExample): - CONFIG = { - "n_vectors": 100, - "slow_factor": 0.01, - "rect_scale_factor": 0.1, - "start_drawn": True, - "drawing_height": 7, - "rect_stroke_width": 1, - } - - def construct(self): - self.add_vectors_circles_path() - self.circles.set_stroke(opacity=0.5) - rect = self.rect = self.get_rect() - rect.set_height(self.rect_scale_factor * FRAME_HEIGHT) - rect.add_updater(lambda m: m.move_to( - self.get_rect_center() - )) - self.add(rect) - self.run_one_cycle() - - def get_rect_center(self): - return center_of_mass([ - v.get_end() - for v in self.vectors - ]) - - def get_rect(self): - return ScreenRectangle( - color=BLUE, - stroke_width=self.rect_stroke_width, - ) - - -class ZoomedInFourierSeriesExample(FourierSeriesExampleWithRectForZoom, MovingCameraScene): - CONFIG = { - "vector_config": { - "max_tip_length_to_length_ratio": 0.15, - "tip_length": 0.05, - }, - "parametric_function_step_size": 0.001, - } - - def setup(self): - ComplexFourierSeriesExample.setup(self) - MovingCameraScene.setup(self) - - def get_rect(self): - return self.camera_frame - - def add_vectors_circles_path(self): - super().add_vectors_circles_path() - for v in self.vectors: - if v.get_stroke_width() < 1: - v.set_stroke(width=1) - - -class ZoomedInFourierSeriesExample100x(ZoomedInFourierSeriesExample): - CONFIG = { - "vector_config": { - "max_tip_length_to_length_ratio": 0.15 * 0.4, - "tip_length": 0.05 * 0.2, - "max_stroke_width_to_length_ratio": 80, - "stroke_width": 3, - }, - "max_circle_stroke_width": 0.5, - "rect_scale_factor": 0.01, - # "parametric_function_step_size": 0.01, - } - - def get_rect_center(self): - return self.vectors[-1].get_end() - - # def get_drawn_path(self, vectors, stroke_width=2, **kwargs): - # return self.get_path_end(vectors, stroke_width, **kwargs) - - -class TrebleClefFourierSeriesExampleWithRectForZoom(FourierSeriesExampleWithRectForZoom): - CONFIG = { - "file_name": "TrebleClef", - "drawn_path_stroke_width": 10, - } - - -class TrebleClefZoomedInFourierSeriesExample(ZoomedInFourierSeriesExample): - CONFIG = { - "file_name": "TrebleClef", - } - - -class NailAndGearFourierSeriesExampleWithRectForZoom(FourierSeriesExampleWithRectForZoom): - CONFIG = { - "file_name": "Nail_And_Gear", - "n_vectors": 200, - "drawn_path_color": "#39FF14", - } - - -class NailAndGearZoomedInFourierSeriesExample(ZoomedInFourierSeriesExample): - CONFIG = { - "file_name": "Nail_And_Gear", - "n_vectors": 200, - "drawn_path_color": "#39FF14", - } - - -class SigmaFourierSeriesExampleWithRectForZoom(FourierSeriesExampleWithRectForZoom): - CONFIG = { - "n_vectors": 200, - "drawn_path_color": PINK, - "rect_stroke_width": 0, - } - - def get_shape(self): - return TexMobject("\\Sigma") - - -class SigmaZoomedInFourierSeriesExample(SigmaFourierSeriesExampleWithRectForZoom, ZoomedInFourierSeriesExample): - pass - - -class FourierOfFourier(FourierSeriesExampleWithRectForZoom): - CONFIG = { - "file_name": "FourierOneLine", - "n_vectors": 300, - "rect_stroke_width": 1, - } - - -class FourierOfFourierZoomedIn(ZoomedInFourierSeriesExample): - CONFIG = { - "file_name": "FourierOneLine", - "max_circle_stroke_width": 0.3, - "n_vectors": 300, - } - - -class FourierOfFourier100xZoom(ZoomedInFourierSeriesExample100x): - CONFIG = { - "file_name": "FourierOneLine", - "max_circle_stroke_width": 0.3, - "n_vectors": 300, - "slow_factor": 0.001, - } - - def run_one_cycle(self): - self.vector_clock.set_value(0.3) - self.wait(40) - - -class FourierOfHilbert(FourierSeriesExampleWithRectForZoom): - CONFIG = { - "n_vectors": 300, - "rect_stroke_width": 1, - "drawn_path_stroke_width": 4, - "drawn_path_color": BLUE, - } - - def get_path(self): - path = HilbertCurve(order=5) - path.set_height(self.drawing_height) - path.to_edge(DOWN) - combined_path = VMobject() - for sm in path.family_members_with_points(): - combined_path.append_vectorized_mobject(sm) - start = combined_path.get_start() - end = combined_path.get_end() - points = [ - interpolate(end, start, alpha) - for alpha in np.linspace(0, 1, 10) - ] - for point in points: - combined_path.add_line_to(point) - - combined_path.set_stroke(width=0) - return combined_path - - -class FourierOfHilbertZoomedIn(FourierOfHilbert, ZoomedInFourierSeriesExample): - pass - - -class FourierOfBritain(FourierSeriesExampleWithRectForZoom): - CONFIG = { - "file_name": "Britain", - "n_vectors": 500, - "drawn_path_color": RED, - } - - -class FourierOfBritainZoomedIn(FourierOfBritain, ZoomedInFourierSeriesExample): - pass - - -class FourierOfSeattle(FourierSeriesExampleWithRectForZoom): - CONFIG = { - "file_name": "SeattleSkyline", - "drawing_height": 7, - "n_vectors": 400, - "drawn_path_color": TEAL, - "drawn_path_stroke_width": 5, - } - - -class FourierOfSeattleZoomedIn(ZoomedInFourierSeriesExample): - CONFIG = { - "file_name": "SeattleSkyline", - "drawing_height": 7, - "n_vectors": 400, - "drawn_path_color": TEAL, - "drawn_path_stroke_width": 5, - "max_circle_stroke_width": 0.3, - } - - -class VideoWrapper(Scene): - def construct(self): - fade_rect = FullScreenFadeRectangle() - fade_rect.set_fill(DARK_GREY, 1) - screen_rect = ScreenRectangle() - screen_rect.set_height(4) - screen_rect.set_fill(BLACK, 1) - screen_rect.set_stroke(width=0) - - boundary = AnimatedBoundary(screen_rect) - - title = TextMobject("Learn the math") - title.scale(1.5) - title.next_to(screen_rect, UP) - - self.add(fade_rect) - self.add(screen_rect) - self.add(boundary) - - self.play(FadeInFromDown(title)) - self.wait(19) diff --git a/from_3b1b/active/diffyq/part4/pi_creature_scenes.py b/from_3b1b/active/diffyq/part4/pi_creature_scenes.py deleted file mode 100644 index 45959e9d..00000000 --- a/from_3b1b/active/diffyq/part4/pi_creature_scenes.py +++ /dev/null @@ -1,235 +0,0 @@ -from manimlib.imports import * - - -class WhyWouldYouCare(TeacherStudentsScene): - def construct(self): - self.student_says( - "Who cares!", - target_mode="sassy", - student_index=2, - added_anims=[self.teacher.change, "guilty"], - ) - self.wait() - self.play( - RemovePiCreatureBubble(self.students[2]), - self.teacher.change, "raise_right_hand", - self.get_student_changes( - "pondering", "erm", "thinking", - look_at_arg=self.screen, - ) - ) - self.look_at(self.screen) - self.wait(5) - - -class SolveForWavesNothingElse(TeacherStudentsScene): - def construct(self): - self.student_says( - "Sure, we can\\\\solve it for\\\\sums of waves...", - target_mode="sassy", - student_index=2, - added_anims=[self.teacher.change, "guilty"] - ) - self.change_student_modes("pondering", "pondering", "sassy") - self.look_at(self.screen) - self.wait(4) - self.student_says( - "But nothing else!", - target_mode="angry", - ) - self.change_student_modes( - "concerned_musician", - "concerned_musician", - "angry", - ) - self.wait(5) - - -class HangOnThere(TeacherStudentsScene): - def construct(self): - student = self.students[2] - - axes1 = Axes( - x_min=0, - x_max=1, - y_min=-1.5, - y_max=1.5, - x_axis_config={ - "tick_frequency": 0.25, - "include_tip": False, - "unit_size": 3, - }, - y_axis_config={ - "tick_frequency": 0.5, - "include_tip": False, - }, - ) - axes1.set_stroke(width=2) - axes2 = axes1.deepcopy() - neq = TexMobject("\\neq") - neq.scale(2) - - group = VGroup(axes1, neq, axes2) - group.arrange(RIGHT) - group.set_height(4) - group.next_to( - student.get_corner(UL), UP, - buff=LARGE_BUFF, - ) - - step_graph = axes1.get_graph( - lambda x: (1 if x < 0.5 else -1), - discontinuities=[0.5], - ) - step_graph.set_color(YELLOW) - wave_graphs = VGroup(*[ - axes2.get_graph( - lambda x: (4 / PI) * np.sum([ - (u / n) * np.cos(n * PI * x) - for u, n in zip( - it.cycle([1, -1]), - range(1, max_n, 2), - ) - ]), - ) - for max_n in range(3, 103, 2) - ]) - wave_graphs.set_stroke(width=3) - wave_graphs.set_color_by_gradient(WHITE, PINK) - last_wave_graph = wave_graphs[-1] - last_wave_graph.set_stroke(PINK, 2) - wave_graphs.remove(last_wave_graph) - # wave_graphs[-1].set_stroke(width=3) - # wave_graphs[-1].set_stroke(BLACK, 5, background=True) - group.add(step_graph) - - self.student_says( - "Hang on\\\\hang on\\\\hang on...", - target_mode="surprised", - content_introduction_class=FadeIn, - student_index=2, - added_anims=[ - self.teacher.change, "guilty" - ], - run_time=1, - ) - self.wait() - self.play( - RemovePiCreatureBubble( - student, - target_mode="raise_left_hand", - look_at_arg=group, - ), - FadeInFromDown(group), - ) - - last_wg = VectorizedPoint() - n_first_fades = 4 - for wg in wave_graphs[:n_first_fades]: - self.play( - last_wg.set_stroke, {"width": 0.1}, - FadeIn(wg), - ) - last_wg = wg - self.play( - LaggedStart( - *[ - UpdateFromAlphaFunc( - wg, - lambda m, a: m.set_stroke( - width=(3 * there_and_back(a) + 0.1 * a) - ), - ) - for wg in wave_graphs[n_first_fades:] - ], - run_time=5, - lag_ratio=0.2, - ), - ApplyMethod( - last_wg.set_stroke, {"width": 0.1}, - run_time=0.25, - ), - FadeIn( - last_wave_graph, - rate_func=squish_rate_func(smooth, 0.9, 1), - run_time=5, - ), - self.teacher.change, "thinking", - ) - self.change_student_modes( - "confused", "confused", "angry" - ) - self.wait(3) - - -class YouSaidThisWasEasier(TeacherStudentsScene): - def construct(self): - self.change_all_student_modes( - "confused", look_at_arg=self.screen, - ) - self.student_says( - "I'm sorry, you said\\\\this was easier?", - target_mode="sassy" - ) - self.play(self.teacher.change, "guilty") - self.wait(3) - self.teacher_says( - "Bear with\\\\me", - bubble_kwargs={"height": 3, "width": 3}, - ) - self.look_at(self.screen) - self.wait(3) - - -class LooseWithLanguage(TeacherStudentsScene): - def construct(self): - terms = VGroup( - TextMobject("``Complex number''"), - TextMobject("``Vector''"), - ) - colors = [YELLOW, BLUE] - for term, color in zip(terms, colors): - term.set_color(color) - - terms.scale(1.5) - terms.arrange(DOWN, buff=LARGE_BUFF) - terms.to_edge(UP) - terms.match_x(self.students) - - self.teacher_says( - "Loose with\\\\language", - bubble_kwargs={"width": 3, "height": 3}, - run_time=2, - ) - self.play( - FadeInFrom(terms[1], DOWN), - self.get_student_changes( - "thinking", "pondering", "erm", - look_at_arg=terms, - ) - ) - self.play(FadeInFromDown(terms[0])) - self.wait() - self.play(Swap(*terms)) - self.wait(3) - - -class FormulaOutOfContext(TeacherStudentsScene): - def construct(self): - formula = TexMobject( - "c_{n} = \\int_0^1 e^{-2\\pi i {n} {t}}f({t}){dt}", - tex_to_color_map={ - "{n}": YELLOW, - "{t}": PINK, - } - ) - formula.scale(1.5) - formula.next_to(self.students, UP, LARGE_BUFF) - - self.add(formula) - self.change_all_student_modes( - "horrified", - look_at_arg=formula, - ) - self.play(self.teacher.change, "tease") - self.wait(3) diff --git a/from_3b1b/active/diffyq/part4/staging.py b/from_3b1b/active/diffyq/part4/staging.py deleted file mode 100644 index ea1a05b6..00000000 --- a/from_3b1b/active/diffyq/part4/staging.py +++ /dev/null @@ -1,2471 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part3.staging import FourierSeriesIllustraiton -from active_projects.diffyq.part2.wordy_scenes import WriteHeatEquationTemplate - - -class FourierName(Scene): - def construct(self): - name = TextMobject("Joseph Fourier") - name.scale(1.5) - self.add(name) - - -class FourierSeriesFormula(Scene): - def construct(self): - formula = TexMobject( - "c_{n} = \\int_0^1 e^{-2\\pi i {n} {t}}f({t}){dt}", - tex_to_color_map={ - "{n}": YELLOW, - "{t}": PINK, - } - ) - - self.play(Write(formula)) - self.wait() - - -class Zoom100Label(Scene): - def construct(self): - text = TextMobject("100x Zoom") - text.scale(2) - self.play(GrowFromCenter(text)) - self.wait() - - -class RelationToOtherVideos(Scene): - CONFIG = { - "camera_config": { - "background_color": DARK_GREY, - }, - } - - def construct(self): - # Show three videos - videos = self.get_video_thumbnails() - brace = Brace(videos, UP) - text = TextMobject("Heat equation") - text.scale(2) - text.next_to(brace, UP) - - self.play( - LaggedStartMap( - FadeInFrom, videos, - lambda m: (m, LEFT), - lag_ratio=0.4, - run_time=2, - ), - GrowFromCenter(brace), - FadeInFromDown(text), - ) - self.wait() - group = Group(text, brace, videos) - - # Show Fourier thinking - fourier = ImageMobject("Joseph Fourier") - fourier.set_height(4) - fourier.to_edge(RIGHT) - group.generate_target() - group.target.to_edge(DOWN) - fourier.align_to(group.target[0], DOWN) - bubble = ThoughtBubble( - direction=RIGHT, - width=3, - height=2, - fill_opacity=0.5, - stroke_color=WHITE, - ) - bubble[-1].shift(0.25 * DOWN + 0.5 * LEFT) - bubble[:-1].rotate(20 * DEGREES) - for mob in bubble[:-1]: - mob.rotate(-20 * DEGREES) - bubble.move_to( - fourier.get_center(), RIGHT - ) - bubble.shift(LEFT) - bubble.to_edge(UP, buff=SMALL_BUFF) - - self.play( - MoveToTarget(group), - FadeInFrom(fourier, LEFT) - ) - self.play(Write(bubble, run_time=1)) - self.wait() - - # Discount first two - first_two = videos[:2] - first_two.generate_target() - first_two.target.scale(0.5) - first_two.target.to_corner(DL) - new_brace = Brace(first_two.target, UP) - - self.play( - # fourier.scale, 0.8, - fourier.match_x, new_brace, - fourier.to_edge, UP, - MoveToTarget(first_two), - Transform(brace, new_brace), - text.scale, 0.7, - text.next_to, new_brace, UP, - FadeOutAndShift(bubble, LEFT), - ) - self.play( - videos[2].scale, 1.7, - videos[2].to_corner, UR, - ) - self.wait() - - # - def get_video_thumbnails(self): - thumbnails = Group( - ImageMobject("diffyq_part2_thumbnail"), - ImageMobject("diffyq_part3_thumbnail"), - ImageMobject("diffyq_part4_thumbnail"), - ) - for thumbnail in thumbnails: - thumbnail.set_height(4) - thumbnail.add(SurroundingRectangle( - thumbnail, - color=WHITE, - stroke_width=2, - buff=0 - )) - thumbnails.arrange(RIGHT, buff=LARGE_BUFF) - thumbnails.set_width(FRAME_WIDTH - 1) - return thumbnails - - -class FourierGainsImmortality(Scene): - CONFIG = { - "mathematicians": [ - "Pythagoras", - "Euclid", - "Archimedes", - "Fermat", - "Newton", - "Leibniz", - "Johann_Bernoulli2", - "Euler", - "Joseph Fourier", - "Gauss", - "Riemann", - "Cantor", - "Noether", - "Ramanujan", - "Godel", - "Turing", - ] - } - - def construct(self): - fourier = ImageMobject("Joseph Fourier") - fourier.set_height(5) - fourier.to_edge(LEFT) - name = TextMobject("Joseph Fourier") - name.next_to(fourier, DOWN) - - immortals = self.get_immortals() - immortals.remove(immortals.fourier) - immortals.to_edge(RIGHT) - - self.add(fourier, name) - self.play(LaggedStartMap( - FadeIn, immortals, - lag_ratio=0.1, - run_time=2, - )) - self.play( - TransformFromCopy(fourier, immortals.fourier) - ) - self.wait() - - def get_immortals(self): - images = Group(*[ - ImageMobject(name) - for name in self.mathematicians - ]) - for image in images: - image.set_height(1) - images.arrange_in_grid(n_rows=4) - - last_row = images[-4:] - low_center = last_row.get_center() - last_row.arrange(RIGHT, buff=0.4, center=False) - last_row.move_to(low_center) - - frame = SurroundingRectangle(images) - frame.set_color(WHITE) - title = TextMobject("Immortals of Math") - title.match_width(frame) - title.next_to(frame, UP) - - result = Group(title, frame, *images) - result.set_height(FRAME_HEIGHT - 1) - result.to_edge(RIGHT) - for image, name in zip(images, self.mathematicians): - setattr( - result, - name.split(" ")[-1].lower(), - image, - ) - return result - - -class WhichWavesAreAvailable(Scene): - CONFIG = { - "axes_config": { - "x_min": 0, - "x_max": TAU, - "y_min": -1.5, - "y_max": 1.5, - "x_axis_config": { - "tick_frequency": PI / 2, - "unit_size": 1, - "include_tip": False, - }, - "y_axis_config": { - "unit_size": 1, - "tick_frequency": 0.5, - "include_tip": False, - }, - }, - "n_axes": 5, - "trig_func": np.cos, - "trig_func_tex": "\\cos", - "bc_words": "Restricted by\\\\boundary conditions", - } - - def construct(self): - self.setup_little_axes() - self.show_cosine_waves() - - def setup_little_axes(self): - axes_group = VGroup(*[ - Axes(**self.axes_config) - for n in range(self.n_axes) - ]) - axes_group.set_stroke(width=2) - axes_group.arrange(DOWN, buff=1) - axes_group.set_height(FRAME_HEIGHT - 1.25) - axes_group.to_edge(RIGHT) - axes_group.to_edge(UP, buff=MED_SMALL_BUFF) - dots = TextMobject("\\vdots") - dots.next_to(axes_group, DOWN) - dots.shift_onto_screen() - - self.add(axes_group) - self.add(dots) - - self.axes_group = axes_group - - def show_cosine_waves(self): - axes_group = self.axes_group - colors = [BLUE, GREEN, RED, YELLOW, PINK] - - graphs = VGroup() - h_lines = VGroup() - func_labels = VGroup() - for k, axes, color in zip(it.count(1), axes_group, colors): - L = axes.x_max - graph = axes.get_graph( - lambda x: self.trig_func(x * k * PI / L), - color=color - ) - graph.set_stroke(width=2) - graphs.add(graph) - - func_label = TexMobject( - self.trig_func_tex + "\\left(", - str(k), - "(\\pi / L)x\\right)", - tex_to_color_map={ - str(k): color, - } - ) - func_label.scale(0.7) - func_label.next_to(axes, LEFT) - func_labels.add(func_label) - - hl1 = DashedLine(LEFT, RIGHT) - hl1.set_width(0.5) - hl1.set_stroke(WHITE, 2) - hl1.move_to(graph.get_start()) - hl2 = hl1.copy() - hl2.move_to(graph.get_end()) - h_lines.add(hl1, hl2) - - words = TextMobject(self.bc_words) - words.next_to(axes_group, LEFT) - words.to_edge(UP) - - self.play( - FadeIn(words), - LaggedStartMap( - ShowCreation, graphs, - ) - ) - self.play(Write(h_lines)) - self.play(FadeOut(h_lines)) - self.wait() - self.play( - words.next_to, func_labels, LEFT, - words.to_edge, UP, - LaggedStartMap( - FadeInFrom, func_labels, - lambda m: (m, RIGHT) - ) - ) - self.wait() - - -class AlternateBoundaryConditions(WhichWavesAreAvailable): - CONFIG = { - "trig_func": np.sin, - "trig_func_tex": "\\sin", - "bc_words": "Alternate\\\\boundary condition" - } - - -class AskQuestionOfGraph(PiCreatureScene): - def construct(self): - randy = self.pi_creature - - self.pi_creature_says( - randy, - "What sine waves\\\\are you made of?", - target_mode="sassy", - ) - self.wait(2) - self.play( - randy.change, "pondering", - randy.bubble.get_right() - ) - self.wait(2) - - -class CommentOnFouriersImmortality(FourierGainsImmortality): - def construct(self): - immortals = self.get_immortals() - immortals.to_edge(LEFT) - fourier = immortals.fourier - name = TextMobject("Joseph", "Fourier") - name.scale(1.5) - name.move_to(FRAME_WIDTH * RIGHT / 4) - name.to_edge(DOWN) - - fourier_things = VGroup( - TextMobject("Fourier transform"), - TextMobject("Fourier series"), - TextMobject("Complex Fourier series"), - TextMobject("Discrete Fourier transform"), - TextMobject("Fractional Fourier transform"), - TextMobject("Fast Fourier transform"), - TextMobject("Quantum Fourier transform"), - TextMobject("Fourier transform spectroscopy"), - TexMobject("\\vdots"), - ) - fourier_things.arrange( - DOWN, - aligned_edge=LEFT, - buff=MED_LARGE_BUFF - ) - fourier_things.to_corner(UL) - fourier_things[-1].shift(RIGHT + 0.25 * UP) - - self.add(immortals) - immortals.remove(immortals.fourier) - self.play( - fourier.set_height, 6, - fourier.next_to, name, UP, - FadeInFromDown(name), - ) - self.play(FadeOut(immortals)) - self.wait(2) - self.play( - LaggedStartMap( - FadeInFrom, fourier_things, - lambda m: (m, UP), - run_time=5, - lag_ratio=0.3, - ) - ) - self.wait() - - -class ShowInfiniteSum(FourierSeriesIllustraiton): - CONFIG = { - "n_range": range(1, 101, 2), - "colors": [BLUE, GREEN, RED, YELLOW, PINK], - "axes_config": { - "y_max": 1.5, - "y_min": -1.5, - "y_axis_config": { - "tick_frequency": 0.5, - "unit_size": 1.0, - "stroke_width": 2, - }, - "x_axis_config": { - "tick_frequency": 0.1, - "stroke_width": 2, - }, - }, - } - - def construct(self): - self.add_equation() - self.add_graphs() - self.show_to_infinity() - self.walk_through_constants() - - self.ask_about_infinite_sum() - self.show_inf_sum_of_numbers() - self.show_many_inputs_in_parallel() - self.follow_single_inputs() - - def add_equation(self): - inf_sum = self.get_infinite_sum() - step_func_def = self.get_step_func_definition() - step_func_def.next_to(inf_sum, RIGHT) - equation = VGroup(inf_sum, step_func_def) - equation.set_width(FRAME_WIDTH - 1) - equation.center().to_edge(UP) - - self.add(equation) - - self.equation = equation - self.inf_sum = inf_sum - self.inf_sum = inf_sum - self.step_func_def = step_func_def - - def add_graphs(self): - aaa_group = self.get_axes_arrow_axes() - aaa_group.next_to(self.equation, DOWN, buff=1.5) - axes1, arrow, axes2 = aaa_group - - tfg = self.get_target_func_graph(axes2) - tfg.set_color(WHITE) - axes2.add(tfg) - - sine_graphs = self.get_sine_graphs(axes1) - partial_sums = self.get_partial_sums(axes1, sine_graphs) - partial_sums.set_stroke(width=0.5) - partial_sums[-1].set_stroke(width=2) - colors = self.colors - partial_sums[len(colors):].set_color_by_gradient(*colors) - - self.add(aaa_group) - self.add(partial_sums) - - self.partial_sums = partial_sums - self.sine_graphs = sine_graphs - self.aaa_group = aaa_group - - def show_to_infinity(self): - dots = self.inf_sum.dots - arrow = Vector(1.5 * RIGHT) - arrow.next_to( - dots, DOWN, MED_LARGE_BUFF, - aligned_edge=RIGHT - ) - words = TextMobject("Sum to $\\infty$") - words.next_to(arrow, DOWN) - - self.play( - FadeInFrom(words, LEFT), - GrowArrow(arrow) - ) - self.wait() - - self.inf_words = VGroup(arrow, words) - - def walk_through_constants(self): - inf_sum = self.inf_sum - terms = self.inf_sum.terms - sine_graphs = self.sine_graphs - partial_sums = self.partial_sums - - rects = VGroup(*[ - SurroundingRectangle( - term, - color=term[-1].get_color(), - stroke_width=2 - ) - for term in terms - ]) - dots_rect = SurroundingRectangle(inf_sum.dots) - dots_rect.set_stroke(width=2) - - curr_rect = rects[0].copy() - curr_partial_sum = partial_sums[0] - curr_partial_sum.set_stroke(width=3) - - self.play( - FadeOut(partial_sums[1:]), - FadeIn(curr_rect), - FadeIn(curr_partial_sum), - ) - - for sg, ps, rect in zip(sine_graphs[1:], partial_sums[1:], rects[1:]): - self.play( - FadeOut(curr_rect), - FadeIn(rect), - FadeIn(sg), - ) - curr_rect.become(rect) - self.remove(rect) - self.add(curr_rect) - ps.set_stroke(width=3) - self.play( - curr_partial_sum.set_stroke, {"width": 0.5}, - ReplacementTransform(sg, ps), - ) - curr_partial_sum = ps - self.play( - Transform(curr_rect, dots_rect), - curr_partial_sum.set_stroke, {"width": 0.5}, - LaggedStart( - *[ - UpdateFromAlphaFunc( - ps, - lambda m, a: m.set_stroke( - width=(3 * there_and_back(a) + 0.5 * a) - ), - ) - for ps in partial_sums[4:] - ], - run_time=3, - lag_ratio=0.2, - ), - ) - self.play(partial_sums[-1].set_stroke, {"width": 2}) - self.play( - FadeOut(curr_rect), - ShowCreationThenFadeAround(inf_sum.four_over_pi), - ) - self.wait() - - def ask_about_infinite_sum(self): - inf_words = self.inf_words - randy = Randolph(mode="confused") - randy.set_height(1.5) - - outline = inf_words[1].copy() - outline.set_stroke(YELLOW, 1) - outline.set_fill(opacity=0) - - question = TextMobject( - "What$\\dots$\\\\ does this mean?" - ) - question.scale(0.7) - question.next_to(inf_words, LEFT) - randy.next_to(question, DOWN, aligned_edge=RIGHT) - - self.play(FadeIn(question)) - self.play(FadeIn(randy)) - self.play( - Blink(randy), - ShowCreationThenFadeOut(outline) - ) - self.wait() - self.play(FadeOut(randy), FadeOut(question)) - - def show_inf_sum_of_numbers(self): - inf_sum = self.inf_sum - graph_group = VGroup( - self.aaa_group, self.partial_sums - ) - - number_line = NumberLine( - x_min=0, - x_max=1, - tick_frequency=0.1, - numbers_with_elongated_ticks=[0, 0.5, 1], - unit_size=8, - # line_to_number_buff=0.4, - ) - number_line.set_stroke(LIGHT_GREY, 2) - number_line.move_to(2 * DOWN) - number_line.add_numbers( - *np.arange(0, 1.5, 0.5), - number_config={ - "num_decimal_places": 1, - }, - ) - - num_inf_sum = TexMobject( - "{1 \\over 1}", - "-{1 \\over 3}", - "+{1 \\over 5}", - "-{1 \\over 7}", - "+{1 \\over 9}", - "-{1 \\over 11}", - "+\\cdots", - "= {\\pi \\over 4}" - ) - num_inf_sum.move_to(UP) - - self.play( - FadeOutAndShift(graph_group, DOWN), - FadeInFrom(number_line, UP), - FadeOut(self.inf_words), - *[ - TransformFromCopy(t1[-1:], t2) - for t1, t2 in zip( - inf_sum.terms, - num_inf_sum, - ) - ], - TransformFromCopy( - inf_sum.dots, - num_inf_sum[-2], - ), - TransformFromCopy( - inf_sum.dots, - num_inf_sum[-4:-2] - ), - self.equation.set_opacity, 0.5, - ) - self.play(Write(num_inf_sum[-1])) - - # Show sums - terms = [ - u / n - for u, n in zip( - it.cycle([1, -1]), - range(1, 1001, 2) - ) - ] - partial_sums = np.cumsum(terms) - tip = ArrowTip(start_angle=-TAU / 4) - value_tracker = ValueTracker(1) - get_value = value_tracker.get_value - tip.add_updater( - lambda t: t.move_to( - number_line.n2p(get_value()), - DOWN, - ) - ) - n_braces = 7 - braces = VGroup(*[ - Brace(num_inf_sum[:n], DOWN) - for n in range(1, n_braces + 1) - ]) - brace = braces[0].copy() - decimal = DecimalNumber( - 0, num_decimal_places=6, - ) - decimal.add_updater(lambda d: d.set_value(get_value())) - decimal.add_updater(lambda d: d.next_to(tip, UP, SMALL_BUFF)) - - term_count = VGroup( - Integer(1), TextMobject("terms") - ) - term_count_tracker = ValueTracker(1) - term_count[0].set_color(YELLOW) - term_count.add_updater( - lambda m: m[0].set_value(term_count_tracker.get_value()) - ) - term_count.add_updater(lambda m: m.arrange( - RIGHT, aligned_edge=DOWN - )) - term_count.add_updater(lambda m: m.next_to(brace, DOWN)) - - pi_fourths_tip = ArrowTip(start_angle=90 * DEGREES) - pi_fourths_tip.set_color(WHITE) - pi_fourths_tip.move_to( - number_line.n2p(PI / 4), UP, - ) - pi_fourths_label = TexMobject( - "{\\pi \\over 4} =" - ) - pi_fourths_value = DecimalNumber( - PI / 4, num_decimal_places=4 - ) - pi_fourths_value.scale(0.8) - pi_fourths_value.next_to(pi_fourths_label, RIGHT, buff=0.2) - pi_fourths_label.add(pi_fourths_value) - # pi_fourths_label.scale(0.7) - pi_fourths_label.next_to( - pi_fourths_tip, DOWN, MED_SMALL_BUFF, - aligned_edge=LEFT, - ) - - self.play( - LaggedStartMap( - FadeIn, VGroup( - brace, tip, decimal, - term_count, - ) - ), - FadeIn(pi_fourths_tip), - FadeIn(pi_fourths_label), - ) - for ps, new_brace in zip(partial_sums[1:], braces[1:]): - term_count_tracker.increment_value(1) - self.play( - Transform(brace, new_brace), - value_tracker.set_value, ps, - ) - self.leave_mark(number_line, ps) - self.wait() - - count = 0 - num_moving_values = n_braces + 8 - for ps in partial_sums[n_braces:num_moving_values]: - value_tracker.set_value(ps) - self.leave_mark(number_line, ps) - count += 1 - term_count_tracker.increment_value(1) - self.wait(0.5) - decimal.remove_updater(decimal.updaters[-1]) - decimal.match_x(pi_fourths_tip) - - new_marks = VGroup(*[ - self.leave_mark(number_line, ps) - for ps in partial_sums[num_moving_values:] - ]) - number_line.remove(*new_marks) - term_count_tracker.increment_value(1) - self.play( - UpdateFromAlphaFunc( - value_tracker, - lambda m, a: m.set_value( - partial_sums[integer_interpolate( - num_moving_values, - len(partial_sums) - 1, - a, - )[0]] - ), - ), - ShowIncreasingSubsets(new_marks), - term_count_tracker.set_value, len(partial_sums), - run_time=10, - rate_func=smooth, - ) - self.play(LaggedStartMap( - FadeOut, VGroup( - num_inf_sum, - brace, - decimal, - term_count, - tip, - number_line, - new_marks, - pi_fourths_tip, - pi_fourths_label, - ), - lag_ratio=0.3, - )) - self.play( - FadeInFromDown(graph_group), - self.equation.set_opacity, 1, - ) - - def show_many_inputs_in_parallel(self): - aaa_group = self.aaa_group - axes1, arrow, axes2 = aaa_group - partial_sums = self.partial_sums - - inputs = np.linspace(0, 1, 21) - n_iterations = 100 - - values = np.array([ - [ - (4 / PI) * (u / n) * np.cos(n * PI * x) - for x in inputs - ] - for u, n in zip( - it.cycle([1, -1]), - range(1, 2 * n_iterations + 1, 2), - ) - ]) - p_sums = np.apply_along_axis(np.cumsum, 0, values) - - dots = VGroup(*[Dot() for x in inputs]) - dots.scale(0.5) - dots.set_color_by_gradient(BLUE, YELLOW) - n_tracker = ValueTracker(0) - - def update_dots(dots): - n = int(n_tracker.get_value()) - outputs = p_sums[n] - for dot, x, y in zip(dots, inputs, outputs): - dot.move_to(axes1.c2p(x, y)) - dots.add_updater(update_dots) - - lines = VGroup(*[ - self.get_dot_line(dot, axes1.x_axis) - for dot in dots - ]) - - self.remove(*self.inf_words) - self.play( - FadeOut(partial_sums), - FadeIn(dots), - FadeIn(lines), - ) - self.play( - n_tracker.set_value, len(p_sums) - 1, - run_time=5, - ) - dots.clear_updaters() - - index = 4 - self.input_dot = dots[index] - self.input_line = lines[index] - self.partial_sum_values = p_sums[:, index] - self.cosine_values = values[:, index] - - dots.remove(self.input_dot) - lines.remove(self.input_line) - self.add(self.input_line) - self.add(self.input_dot) - - self.play( - FadeOut(dots), - FadeOut(lines), - ) - - def follow_single_inputs(self): - aaa_group = self.aaa_group - inf_sum = self.inf_sum - axes1, arrow, axes2 = aaa_group - x_axis = axes1.x_axis - dot = self.input_dot - partial_sums = self.partial_sums - partial_sums.set_stroke(width=2) - - input_tracker = ValueTracker( - x_axis.p2n(dot.get_center()) - ) - get_input = input_tracker.get_value - - input_label = TexMobject("x =", "0.2") - input_decimal = DecimalNumber(get_input()) - input_decimal.replace(input_label[1]) - input_decimal.set_color(BLUE) - input_label.remove(input_label[1]) - input_label.add(input_decimal) - input_label.next_to(dot, UR, SMALL_BUFF) - - def get_brace_value_label(brace, u, n): - result = DecimalNumber() - result.scale(0.7) - result.next_to(brace, DOWN, SMALL_BUFF) - result.add_updater(lambda d: d.set_value( - (u / n) * np.cos(PI * n * get_input()) - )) - return result - - braces = VGroup(*[ - Brace(term, DOWN) - for term in inf_sum.terms - ]) - brace_value_labels = VGroup(*[ - get_brace_value_label(brace, u, n) - for brace, u, n in zip( - braces, - it.cycle([1, -1]), - it.count(1, 2), - ) - ]) - bv_rects = VGroup(*[ - SurroundingRectangle( - brace_value_labels[:n], - color=BLUE, - stroke_width=1, - ) - for n in range(1, len(braces) + 1) - ]) - - bv_rect = bv_rects[0].copy() - partial_sum = partial_sums[0].copy() - - dot.add_updater( - lambda d: d.move_to(partial_sum.point_from_proportion( - inverse_interpolate( - x_axis.x_min, - x_axis.x_max, - get_input(), - ) - )) - ) - - n_waves_label = TextMobject( - "Sum of", "10", "waves" - ) - n_waves_label.next_to(axes1.c2p(0.5, 1), UR) - n_tracker = ValueTracker(1) - n_dec = Integer(1) - n_dec.set_color(YELLOW) - n_dec.move_to(n_waves_label[1]) - n_waves_label[1].set_opacity(0) - n_waves_label.add(n_dec) - n_dec.add_updater(lambda n: n.set_value( - n_tracker.get_value() - )) - n_dec.add_updater(lambda m: m.move_to( - n_waves_label[1] - )) - - self.play( - FadeInFrom(input_label, LEFT), - dot.scale, 1.5, - ) - self.wait() - self.play( - LaggedStartMap(GrowFromCenter, braces), - LaggedStartMap(GrowFromCenter, brace_value_labels), - *[ - TransformFromCopy( - input_label[0][0], - term[n][1], - ) - for i, term in enumerate(inf_sum.terms) - for n in [2 if i == 0 else 4] - ] - ) - self.wait() - - self.add(partial_sum, dot) - dot.set_stroke(BLACK, 1, background=True) - self.play( - FadeOut(input_label), - FadeIn(n_waves_label), - FadeIn(bv_rect), - FadeIn(partial_sum), - ) - self.wait() - - ps_iter = iter(partial_sums[1:]) - - def get_ps_anim(): - return AnimationGroup( - Transform(partial_sum, next(ps_iter)), - ApplyMethod(n_tracker.increment_value, 1), - ) - - for i in range(1, 4): - self.play( - Transform(bv_rect, bv_rects[i]), - get_ps_anim(), - ) - self.wait() - - self.play( - FadeOut(bv_rect), - get_ps_anim() - ) - for x in range(3): - self.play(get_ps_anim()) - self.play( - input_tracker.set_value, 0.7, - ) - for x in range(6): - self.play(get_ps_anim()) - self.play(input_tracker.set_value, 0.5) - for x in range(40): - self.play( - get_ps_anim(), - run_time=0.5, - ) - - # - def get_dot_line(self, dot, axis, line_class=Line): - def get_line(): - p = dot.get_center() - lp = axis.n2p(axis.p2n(p)) - return line_class(lp, p, stroke_width=1) - return always_redraw(get_line) - - def leave_mark(self, number_line, value): - tick = number_line.get_tick(value) - tick.set_color(BLUE) - number_line.add(tick) - return tick - - def get_infinite_sum(self): - colors = self.colors - inf_sum = TexMobject( - "{4 \\over \\pi}", "\\left(", - "{\\cos\\left({1}\\pi x\\right) \\over {1}}", - "-", - "{\\cos\\left({3}\\pi x\\right) \\over {3}}", - "+", - "{\\cos\\left({5}\\pi x\\right) \\over {5}}", - "-", - "{\\cos\\left({7}\\pi x\\right) \\over {7}}", - "+", - "\\cdots", - "\\right)", - tex_to_color_map={ - "{1}": colors[0], - "{-1 \\over 3}": colors[1], - "{3}": colors[1], - "{1 \\over 5}": colors[2], - "{5}": colors[2], - "{-1 \\over 7}": colors[3], - "{7}": colors[3], - "{1 \\over 9}": colors[4], - "{9}": colors[4], - } - ) - inf_sum.get_parts_by_tex("-")[0].set_color(colors[1]) - inf_sum.get_parts_by_tex("-")[1].set_color(colors[3]) - inf_sum.get_parts_by_tex("+")[0].set_color(colors[2]) - - inf_sum.terms = VGroup( - inf_sum[2:6], - inf_sum[6:12], - inf_sum[12:18], - inf_sum[18:24], - ) - inf_sum.dots = inf_sum.get_part_by_tex("\\cdots") - inf_sum.four_over_pi = inf_sum.get_part_by_tex( - "{4 \\over \\pi}" - ) - - return inf_sum - - def get_step_func_definition(self): - values = VGroup(*map(Integer, [1, 0, -1])) - conditions = VGroup(*map(TextMobject, [ - "if $x < 0.5$", - "if $x = 0.5$", - "if $x > 0.5$", - ])) - values.arrange(DOWN, buff=MED_LARGE_BUFF) - for condition, value in zip(conditions, values): - condition.move_to(value) - condition.align_to(conditions[0], LEFT) - conditions.next_to(values, RIGHT, LARGE_BUFF) - brace = Brace(values, LEFT) - - eq = TexMobject("=") - eq.scale(1.5) - eq.next_to(brace, LEFT) - - return VGroup(eq, brace, values, conditions) - - -class StepFunctionSolutionFormla(WriteHeatEquationTemplate): - CONFIG = { - "tex_mobject_config": { - "tex_to_color_map": { - "{1}": WHITE, # BLUE, - "{3}": WHITE, # GREEN, - "{5}": WHITE, # RED, - "{7}": WHITE, # YELLOW, - }, - }, - } - - def construct(self): - formula = TexMobject( - "T({x}, {t}) = ", - "{4 \\over \\pi}", - "\\left(", - "{\\cos\\left({1}\\pi {x}\\right) \\over {1}}", - "e^{-\\alpha {1}^2 {t} }", - "-" - "{\\cos\\left({3}\\pi {x}\\right) \\over {3}}", - "e^{-\\alpha {3}^2 {t} }", - "+", - "{\\cos\\left({5}\\pi {x}\\right) \\over {5}}", - "e^{-\\alpha {5}^2 {t} }", - "- \\cdots", - "\\right)", - **self.tex_mobject_config, - ) - - formula.set_width(FRAME_WIDTH - 1) - formula.to_edge(UP) - - self.play(FadeInFromDown(formula)) - self.wait() - - -class TechnicalNuances(Scene): - def construct(self): - title = TextMobject("Technical nuances not discussed") - title.scale(1.5) - title.set_color(YELLOW) - line = DashedLine(title.get_left(), title.get_right()) - line.next_to(title, DOWN, SMALL_BUFF) - line.set_stroke(LIGHT_GREY, 3) - - questions = VGroup(*map(TextMobject, [ - "Does the value at $0.5$ matter?", - "What does it mean to solve a PDE with\\\\" - "a discontinuous initial condition?", - "Is the limit of a sequence of solutions\\\\" - "also a solution?", - "Do all functions have a Fourier series?", - ])) - - self.play( - Write(title), - ShowCreation(line), - ) - title.add(line) - self.play( - title.to_edge, UP, - ) - - last_question = VMobject() - for question in questions: - question.next_to(line, DOWN, MED_LARGE_BUFF) - self.play( - FadeInFromDown(question), - FadeOutAndShift(last_question, UP) - ) - self.wait(2) - last_question = question - - -class ArrowAndCircle(Scene): - def construct(self): - circle = Circle(color=RED) - circle.set_stroke(width=4) - circle.set_height(0.25) - - arrow = Vector(DL) - arrow.set_color(RED) - arrow.next_to(circle, UR) - self.play(ShowCreation(arrow)) - self.play(ShowCreation(circle)) - self.play(FadeOut(circle)) - self.wait() - - -class SineWaveResidue(Scene): - def construct(self): - f = 2 - wave = FunctionGraph( - lambda x: np.cos(-0.1 * f * TAU * x), - x_min=0, - x_max=20, - color=YELLOW, - ) - wave.next_to(LEFT_SIDE, LEFT, buff=0) - time = 10 - self.play( - wave.shift, time * RIGHT, - run_time=f * time, - rate_func=linear, - ) - - -class AskAboutComplexNotVector(Scene): - def construct(self): - c_ex = DecimalNumber( - complex(2, 1), - num_decimal_places=0 - ) - i_part = c_ex[-1] - v_ex = IntegerMatrix( - [[2], [1]], - bracket_h_buff=SMALL_BUFF, - bracket_v_buff=SMALL_BUFF, - ) - group = VGroup(v_ex, c_ex) - group.scale(1.5) - # group.arrange(RIGHT, buff=4) - group.arrange(DOWN, buff=0.9) - group.to_corner(UL, buff=0.2) - - q1, q2 = questions = VGroup( - TextMobject("Why not this?").set_color(BLUE), - TextMobject("Instead of this?").set_color(YELLOW), - ) - questions.scale(1.5) - for q, ex in zip(questions, group): - ex.add_background_rectangle() - q.add_background_rectangle() - q.next_to(ex, RIGHT) - - plane = NumberPlane(axis_config={"unit_size": 2}) - vect = Vector([4, 2, 0]) - - self.play( - ShowCreation(plane, lag_ratio=0.1), - FadeIn(vect), - ) - for q, ex in zip(questions, group): - self.play( - FadeInFrom(q, LEFT), - FadeIn(ex) - ) - self.wait() - self.play(ShowCreationThenFadeAround(i_part)) - self.wait() - - -class SwapIntegralAndSum(Scene): - def construct(self): - self.perform_swap() - self.show_average_of_individual_terms() - self.ask_about_c2() - self.multiply_f_by_exp_neg_2() - self.show_modified_averages() - self.add_general_expression() - - def perform_swap(self): - light_pink = self.light_pink = interpolate_color( - PINK, WHITE, 0.25 - ) - tex_config = self.tex_config = { - "tex_to_color_map": { - "=": WHITE, - "\\int_0^1": WHITE, - "+": WHITE, - "\\cdots": WHITE, - "{t}": PINK, - "{dt}": light_pink, - "{-2}": YELLOW, - "{\\cdot 1}": YELLOW, - "{0}": YELLOW, - "{1}": YELLOW, - "{2}": YELLOW, - "{n}": YELLOW, - }, - } - int_ft = TexMobject( - "\\int_0^1 f({t}) {dt}", - **tex_config - ) - int_sum = TexMobject( - """ - = - \\int_0^1 \\left( - \\cdots + - c_{\\cdot 1}e^{{\\cdot 1} \\cdot 2\\pi i {t}} + - c_{0}e^{{0} \\cdot 2\\pi i {t}} + - c_{1}e^{{1} \\cdot 2\\pi i {t}} + - c_{2}e^{{2} \\cdot 2\\pi i {t}} + - \\cdots - \\right){dt} - """, - **tex_config - ) - sum_int = TexMobject( - """ - = \\cdots + - \\int_0^1 - c_{\\cdot 1}e^{{\\cdot 1} \\cdot 2\\pi i {t}} - {dt} + - \\int_0^1 - c_{0}e^{{0} \\cdot 2\\pi i {t}} - {dt} + - \\int_0^1 - c_{1}e^{{1} \\cdot 2\\pi i {t}} - {dt} + - \\int_0^1 - c_{2}e^{{2} \\cdot 2\\pi i {t}} - {dt} + - \\cdots - """, - **tex_config - ) - - self.fix_minuses(int_sum) - self.fix_minuses(sum_int) - - group = VGroup(int_ft, int_sum, sum_int) - group.arrange( - DOWN, buff=MED_LARGE_BUFF, - aligned_edge=LEFT - ) - group.set_width(FRAME_WIDTH - 1) - group.to_corner(UL) - int_ft.align_to(int_sum[1], LEFT) - int_ft.shift(0.2 * RIGHT) - - int_sum.exp_terms = self.get_exp_terms(int_sum) - sum_int.exp_terms = self.get_exp_terms(sum_int) - sum_int.int_terms = self.get_integral_terms(sum_int) - - breakdown_label = TextMobject( - "Break this down" - ) - arrow = Vector(LEFT) - arrow.next_to(int_ft, RIGHT) - breakdown_label.next_to(arrow, RIGHT) - - aos_label = TextMobject( - "Average of a sum", - tex_to_color_map={ - "Average": light_pink, - "sum": YELLOW, - } - ) - soa_label = TextMobject( - "Sum over averages", - tex_to_color_map={ - "averages": light_pink, - "Sum": YELLOW, - } - ) - aos_label.next_to(ORIGIN, RIGHT, buff=2) - aos_label.to_edge(UP) - soa_label.move_to(2.5 * DOWN) - aos_arrow = Arrow( - aos_label.get_corner(DL), - int_sum.get_corner(TOP) + 2 * RIGHT, - buff=0.3, - ) - soa_arrow = Arrow( - soa_label.get_top(), - sum_int.get_bottom(), - buff=0.3, - ) - - self.add(int_ft) - self.play( - FadeInFrom(breakdown_label, LEFT), - GrowArrow(arrow), - ) - self.wait() - self.play( - FadeOut(breakdown_label), - FadeOut(arrow), - FadeIn(int_sum.get_parts_by_tex("=")), - FadeIn(int_sum.get_parts_by_tex("+")), - FadeIn(int_sum.get_parts_by_tex("\\cdots")), - FadeIn(int_sum.get_parts_by_tex("(")), - FadeIn(int_sum.get_parts_by_tex(")")), - *[ - TransformFromCopy( - int_ft.get_part_by_tex(tex), - int_sum.get_part_by_tex(tex), - ) - for tex in ["\\int_0^1", "{dt}"] - ] - ) - self.play(LaggedStart(*[ - GrowFromPoint( - term, - int_ft.get_part_by_tex("{t}").get_center(), - ) - for term in int_sum.exp_terms - ])) - self.wait() - self.play( - FadeIn(aos_label), - GrowArrow(aos_arrow) - ) - self.play( - *[ - TransformFromCopy( - int_sum.get_parts_by_tex(tex), - sum_int.get_parts_by_tex(tex), - run_time=2, - ) - for tex in ["\\cdots", "+"] - ], - TransformFromCopy( - int_sum.exp_terms, - sum_int.exp_terms, - run_time=2, - ), - FadeIn(soa_label), - GrowArrow(soa_arrow), - ) - self.play( - *[ - TransformFromCopy( - int_sum.get_part_by_tex("\\int_0^1"), - part, - run_time=2, - ) - for part in sum_int.get_parts_by_tex( - "\\int_0^1" - ) - ], - FadeIn(sum_int.get_parts_by_tex("{dt}")) - ) - self.wait() - self.play( - FadeOut(soa_arrow), - soa_label.to_edge, DOWN, - ) - - self.int_sum = int_sum - self.sum_int = sum_int - self.int_ft = int_ft - self.aos_label = aos_label - self.aos_arrow = aos_arrow - self.soa_label = soa_label - self.soa_arrow = soa_arrow - - def show_average_of_individual_terms(self): - sum_int = self.sum_int - int_ft = self.int_ft - - braces = VGroup(*[ - Brace(term, DOWN, buff=SMALL_BUFF) - for term in sum_int.int_terms - ]) - self.play(LaggedStartMap( - GrowFromCenter, braces - )) - - self.show_individual_average(braces[0], -1) - self.show_individual_average(braces[2], 1) - self.show_individual_average(braces[3], 2) - self.show_individual_average(braces[1], 0) - - eq = TexMobject("=") - eq.next_to(int_ft, RIGHT) - c0 = self.c0.copy() - c0.generate_target() - c0.target.next_to(eq, RIGHT) - c0.target.shift(0.05 * DOWN) - self.play( - FadeIn(eq), - MoveToTarget(c0, run_time=2) - ) - self.wait() - - self.braces = braces - self.eq_c0 = VGroup(eq, c0) - - def ask_about_c2(self): - int_sum = self.int_sum - sum_int = self.sum_int - - c2 = int_sum.exp_terms[-1][:2] - c2_rect = SurroundingRectangle(c2, buff=0.05) - c2_rect.set_stroke(WHITE, 2) - c2_rect.stretch(1.5, 1, about_edge=DOWN) - - q_marks = TexMobject("???") - q_marks.next_to(c2_rect, UP) - - last_diagram = self.all_average_diagrams[-2] - last_int = sum_int.int_terms[-1] - big_rect = SurroundingRectangle( - VGroup(last_int, last_diagram) - ) - big_rect.set_stroke(WHITE, 2) - - self.play( - ShowCreation(c2_rect), - FadeOut(self.aos_arrow), - FadeInFromDown(q_marks), - ) - self.play(ShowCreation(big_rect)) - self.wait(2) - self.to_fade = VGroup(c2_rect, q_marks, big_rect) - - def multiply_f_by_exp_neg_2(self): - int_ft = self.int_ft - int_sum = self.int_sum - sum_int = self.sum_int - eq_c0 = self.eq_c0 - diagrams = self.all_average_diagrams - diagrams.sort(lambda p: p[0]) - - dt_part = int_ft.get_part_by_tex("{dt}") - pre_dt_part = int_ft[:-1] - new_exp = TexMobject( - "e^{{-2} \\cdot 2\\pi i {t}}", - **self.tex_config, - ) - new_exp.next_to(pre_dt_part, RIGHT, SMALL_BUFF) - new_exp.shift(0.05 * UP) - something = TextMobject("(something)") - something.replace(new_exp, dim_to_match=0) - something.align_to(new_exp, DOWN) - - self.play( - FadeOut(eq_c0), - Write(something, run_time=1), - dt_part.next_to, new_exp, RIGHT, SMALL_BUFF, - dt_part.align_to, dt_part, DOWN, - ) - self.wait() - self.play(*[ - Rotating( - diagram[2], - radians=n * TAU, - about_point=diagram[0].n2p(0), - run_time=3, - rate_func=lambda t: smooth(t, 1) - ) - for n, diagram in zip(it.count(-3), diagrams) - ]) - self.wait() - self.play( - FadeOutAndShift(something, UP), - FadeInFrom(new_exp, DOWN), - ) - self.play(FadeOut(self.to_fade)) - - # Go through each term - moving_exp = new_exp.copy() - rect = SurroundingRectangle(moving_exp) - rect.set_stroke(LIGHT_GREY, width=1) - times = TexMobject("\\times") - times.next_to(rect, LEFT, SMALL_BUFF) - moving_exp.add(VGroup(rect, times)) - moving_exp[-1].set_opacity(0) - moving_exp.save_state() - moving_exp.generate_target() - - quads = zip( - it.count(-3), - int_sum.exp_terms, - sum_int.exp_terms, - diagrams, - ) - for n, c_exp1, c_exp2, diagram in quads: - exp1 = c_exp1[2:] - exp2 = c_exp2[2:] - moving_exp.target[-1][0].set_stroke(opacity=1) - moving_exp.target[-1][1].set_fill(opacity=1) - moving_exp.target.next_to(exp1, UP, SMALL_BUFF) - if n < 0: - n_str = "{\\cdot" + str(abs(n)) + "}" - else: - n_str = "{" + str(n) + "}" - replacement1 = TexMobject( - "e^{", n_str, "\\cdot 2\\pi i", "{t}", - tex_to_color_map={ - "{t}": PINK, - n_str: YELLOW, - } - ) - self.fix_minuses(replacement1) - replacement1[1][0].shift(0.025 * LEFT) - replacement1.match_height(exp1) - replacement1.move_to(exp1, DL) - replacement2 = replacement1.copy() - replacement2.move_to(exp2, DL) - self.play(MoveToTarget(moving_exp)) - self.play( - TransformFromCopy( - moving_exp[1], - replacement1[1], - ), - FadeOutAndShift(exp1[1], DOWN), - Transform(exp1[0], replacement1[0]), - Transform(exp1[2:], replacement1[2:]), - ) - self.play( - TransformFromCopy(replacement1, replacement2), - FadeOutAndShift(exp2, DOWN), - FadeOut(diagram), - ) - self.play(Restore(moving_exp)) - - def show_modified_averages(self): - braces = self.braces - - for brace, n in zip(braces, it.count(-3)): - self.show_individual_average(brace, n, "c_2") - - int_ft = self.int_ft - c2 = self.c0.copy() - c2.generate_target() - eq = TexMobject("=") - eq.next_to(int_ft, RIGHT) - c2.target.next_to(eq, RIGHT) - c2.target.shift(0.05 * DOWN) - self.play( - FadeIn(eq), - MoveToTarget(c2) - ) - self.wait() - - def add_general_expression(self): - expression = TexMobject( - """ - c_{n} = - \\int_0^1 f({t}) - e^{-{n} \\cdot 2\\pi i {t}}{dt} - """, - **self.tex_config, - ) - rect = SurroundingRectangle(expression, buff=MED_SMALL_BUFF) - rect.set_fill(DARK_GREY, 1) - rect.set_stroke(WHITE, 3) - group = VGroup(rect, expression) - group.to_edge(UP, buff=SMALL_BUFF) - group.to_edge(RIGHT, buff=LARGE_BUFF) - - self.play( - FadeOut(self.aos_label), - FadeIn(rect), - FadeIn(expression) - ) - self.wait() - - # - def show_individual_average(self, brace, n, cn_tex=None): - if not hasattr(self, "all_average_diagrams"): - self.all_average_diagrams = VGroup() - - seed = brace.get_center()[0] + 1 - int_seed = np.array([seed]).astype("uint32") - np.random.seed(int_seed) - if n == 0: - if cn_tex is None: - cn_tex = "c_" + str(n) - result = TexMobject(cn_tex) - result[0][1].set_color(YELLOW) - self.c0 = result - else: - result = TexMobject("0") - result.set_color(self.light_pink) - result.next_to(brace, DOWN, SMALL_BUFF) - - coord_max = 1.25 - plane = ComplexPlane( - x_min=-coord_max, - x_max=coord_max, - y_min=-coord_max, - y_max=coord_max, - axis_config={ - "stroke_color": LIGHT_GREY, - "stroke_width": 1, - "unit_size": 0.75, - }, - background_line_style={ - "stroke_color": LIGHT_GREY, - "stroke_width": 1, - }, - ) - plane.next_to(result, DOWN, SMALL_BUFF) - - vector = Arrow( - plane.n2p(0), - plane.n2p( - (0.2 + 0.8 * np.random.random()) * np.exp(complex( - 0, TAU * np.random.random() - )) - ), - buff=0, - color=WHITE, - ) - circle = Circle() - circle.set_stroke(BLUE, 2) - circle.rotate(vector.get_angle()) - circle.set_width(2 * vector.get_length()) - circle.move_to(plane.n2p(0)) - - dots = VGroup(*[ - Dot( - circle.point_from_proportion( - (n * a) % 1 - ), - radius=0.04, - color=PINK, - fill_opacity=0.75, - ) - for a in np.arange(0, 1, 1 / 25) - ]) - dot_center = center_of_mass([ - d.get_center() for d in dots - ]) - - self.play( - FadeIn(plane), - FadeIn(circle), - FadeIn(vector), - ) - self.play( - Rotating( - vector, - radians=n * TAU, - about_point=plane.n2p(0), - ), - ShowIncreasingSubsets( - dots, - int_func=np.ceil, - ), - rate_func=lambda t: smooth(t, 1), - run_time=3, - ) - dot_copies = dots.copy() - self.play(*[ - ApplyMethod(dot.move_to, dot_center) - for dot in dot_copies - ]) - self.play( - TransformFromCopy(dot_copies[0], result) - ) - self.wait() - - self.all_average_diagrams.add(VGroup( - plane, circle, vector, - dots, dot_copies, result, - )) - - # - def fix_minuses(self, tex_mob): - for mob in tex_mob.get_parts_by_tex("{\\cdot"): - minus = TexMobject("-") - minus.match_style(mob[0]) - minus.set_width( - 3 * mob[0].get_width(), - stretch=True, - ) - minus.move_to(mob, LEFT) - mob.submobjects[0] = minus - - def get_terms_by_tex_bounds(self, tex_mob, tex1, tex2): - c_parts = tex_mob.get_parts_by_tex(tex1) - t_parts = tex_mob.get_parts_by_tex(tex2) - result = VGroup() - for p1, p2 in zip(c_parts, t_parts): - i1 = tex_mob.index_of_part(p1) - i2 = tex_mob.index_of_part(p2) - result.add(tex_mob[i1:i2 + 1]) - return result - - def get_exp_terms(self, tex_mob): - return self.get_terms_by_tex_bounds( - tex_mob, "c_", "{t}" - ) - - def get_integral_terms(self, tex_mob): - return self.get_terms_by_tex_bounds( - tex_mob, "\\int", "{dt}" - ) - - -class FootnoteOnSwappingIntegralAndSum(Scene): - def construct(self): - words = TextMobject( - """ - Typically in math, you have to be more\\\\ - careful swapping the order of sums like\\\\ - this when infinities are involved. Again,\\\\ - such questions are what real analysis \\\\ - is built for. - """, - alignment="" - ) - - self.add(words) - self.wait() - - -class ShowRangeOfCnFormulas(Scene): - def construct(self): - formulas = VGroup(*[ - self.get_formula(n) - for n in [-50, None, -1, 0, 1, None, 50] - ]) - formulas.scale(0.7) - formulas.arrange( - DOWN, - buff=MED_LARGE_BUFF, - aligned_edge=LEFT - ) - dots = formulas[1::4] - dots.shift(MED_LARGE_BUFF * RIGHT) - - formulas.set_height(FRAME_HEIGHT - 0.5) - formulas.to_edge(LEFT) - - self.play(LaggedStartMap( - FadeInFrom, formulas, - lambda m: (m, UP), - lag_ratio=0.2, - run_time=4, - )) - self.wait() - - def get_formula(self, n): - if n is None: - return TexMobject("\\vdots") - light_pink = interpolate_color(PINK, WHITE, 0.25) - n_str = "{" + str(n) + "}" - neg_n_str = "{" + str(-n) + "}" - expression = TexMobject( - "c_" + n_str, "=", - "\\int_0^1 f({t})", - "e^{", neg_n_str, - "\\cdot 2\\pi i {t}}{dt}", - tex_to_color_map={ - "{t}": light_pink, - "{dt}": light_pink, - n_str: YELLOW, - neg_n_str: YELLOW, - } - ) - return expression - - -class DescribeSVG(Scene): - def construct(self): - plane = ComplexPlane( - axis_config={"unit_size": 2} - ) - plane.add_coordinates() - self.add(plane) - - svg_mob = SVGMobject("TrebleClef") - path = svg_mob.family_members_with_points()[0] - path.set_fill(opacity=0) - path.set_stroke(YELLOW, 2) - path.set_height(6) - path.shift(0.5 * RIGHT) - path_parts = CurvesAsSubmobjects(path) - path_parts.set_stroke(GREEN, 3) - self.add(path) - - dot = Dot() - dot.set_color(PINK) - ft_label = TexMobject("f(t) = ") - ft_label.to_corner(UL) - z_decimal = DecimalNumber(num_decimal_places=3) - z_decimal.next_to(ft_label, RIGHT) - z_decimal.add_updater(lambda m: m.set_value( - plane.p2n(dot.get_center()) - )) - z_decimal.set_color(interpolate_color(PINK, WHITE, 0.25)) - rect = BackgroundRectangle(VGroup(ft_label, z_decimal)) - - brace = Brace(rect, DOWN) - question = TextMobject("Where is this defined?") - question.add_background_rectangle() - question.next_to(brace, DOWN) - - answer = TextMobject("Read in some .svg") - answer.add_background_rectangle() - answer.next_to(question, DOWN, LARGE_BUFF) - - self.add(rect, ft_label, z_decimal) - self.add(question, brace) - self.play(UpdateFromAlphaFunc( - dot, - lambda d, a: d.move_to(path.point_from_proportion(a)), - run_time=5, - rate_func=lambda t: 0.3 * there_and_back(t) - )) - self.wait() - self.play(FadeInFrom(answer, UP)) - self.play( - FadeOut(path), - FadeOut(dot), - FadeIn(path_parts), - ) - - path_parts.generate_target() - path_parts.save_state() - path_parts.target.space_out_submobjects(1.3) - for part in path_parts.target: - part.shift(0.2 * part.get_center()[0] * RIGHT) - path_parts.target.move_to(path_parts) - self.play( - MoveToTarget(path_parts) - ) - - indicators = self.get_bezier_point_indicators(path_parts) - self.play(ShowCreation( - indicators, - lag_ratio=0.5, - run_time=3, - )) - self.wait() - self.play( - FadeOut(indicators), - path_parts.restore, - ) - - def get_bezier_point_indicators(self, path): - dots = VGroup() - lines = VGroup() - for part in path.family_members_with_points(): - for tup in part.get_cubic_bezier_tuples(): - a1, h1, h2, a2 = tup - dots.add( - Dot(a1), - Dot(a2), - Dot(h2, color=YELLOW), - Dot(h1, color=YELLOW), - ) - lines.add( - Line(a1, h1), - Line(a2, h2), - ) - for dot in dots: - dot.set_width(0.05) - lines.set_stroke(WHITE, 1) - return VGroup(dots, lines) - - -class NumericalIntegration(Scene): - CONFIG = { - "tex_config": { - "tex_to_color_map": { - "{t}": LIGHT_PINK, - "{dt}": LIGHT_PINK, - "{n}": YELLOW, - "{0.01}": WHITE, - } - } - } - - def construct(self): - self.add_title() - self.add_integral() - - def add_title(self): - title = TextMobject("Integrate numerically") - title.scale(1.5) - title.to_edge(UP) - line = Line() - line.set_width(title.get_width() + 1) - line.next_to(title, DOWN) - self.add(title, line) - self.title = title - - def add_integral(self): - integral = TexMobject( - "c_{n}", "=", "\\int_0^1 f({t})" - "e^{-{n} \\cdot 2\\pi i {t}}{dt}", - **self.tex_config, - ) - integral.to_edge(LEFT) - - sum_tex = TexMobject( - " \\approx \\text{sum([} \\dots", - "f({0.01}) e^{-{n} \\cdot 2\\pi i ({0.01})}({0.01})" - "\\dots \\text{])}", - **self.tex_config, - ) - sum_tex.next_to( - integral.get_part_by_tex("="), DOWN, - buff=LARGE_BUFF, - aligned_edge=LEFT, - ) - - group = VGroup(integral, sum_tex) - group.next_to(self.title, DOWN, LARGE_BUFF) - - t_tracker = ValueTracker(0) - decimal_templates = sum_tex.get_parts_by_tex("{0.01}") - decimal_templates.set_color(LIGHT_PINK) - decimals = VGroup(*[DecimalNumber() for x in range(2)]) - for d, dt in zip(decimals, decimal_templates): - d.replace(dt) - d.set_color(LIGHT_PINK) - d.add_updater(lambda d: d.set_value( - t_tracker.get_value() - )) - dt.set_opacity(0) - - delta_t_brace = Brace(decimal_templates[-1], UP, buff=SMALL_BUFF) - delta_t = delta_t_brace.get_tex( - "\\Delta t", buff=SMALL_BUFF - ) - delta_t.set_color(PINK) - - input_line = UnitInterval( - unit_size=10, - ) - input_line.next_to(group, DOWN, LARGE_BUFF) - input_line.add_numbers(0, 0.5, 1) - dots = VGroup(*[ - Dot( - input_line.n2p(t), - color=PINK, - radius=0.03, - ).stretch(2, 1) - for t in np.arange(0, 1, 0.01) - ]) - - self.add(integral) - self.add(sum_tex) - self.add(decimals) - self.add(delta_t, delta_t_brace) - self.add(input_line) - - time = 15 - self.play( - t_tracker.set_value, 0.99, - ShowIncreasingSubsets(dots), - run_time=time, - rate_func=lambda t: smooth(t, 1), - ) - self.wait() - - def get_term(self, t, dt): - pass - - -class StepFunctionIntegral(Scene): - def construct(self): - light_pink = interpolate_color(PINK, WHITE, 0.25) - tex_config = { - "tex_to_color_map": { - "{t}": light_pink, - "{dt}": light_pink, - "{n}": YELLOW, - } - } - cn_expression = TexMobject( - """ - c_{n} = - \\int_0^1 \\text{step}({t}) - e^{-{n}\\cdot 2\\pi i {t}} {dt} - """, - **tex_config - ) - expansion = TexMobject( - """ - = - \\int_0^{0.5} 1 \\cdot e^{-{n}\\cdot 2\\pi i {t}} {dt} + - \\int_{0.5}^1 -1 \\cdot e^{-{n}\\cdot 2\\pi i {t}} {dt} - """, - **tex_config - ) - group = VGroup(cn_expression, expansion) - group.arrange(RIGHT) - group.set_width(FRAME_WIDTH - 1) - group.to_corner(UL) - - words1 = TexMobject( - "\\text{Challenge 1: Show that }" - "c_{n} = {2 \\over {n} \\pi i}", - "\\text{ for odd }", "{n}", - "\\text{ and 0 otherwise}", - **tex_config, - ) - words2 = TexMobject( - "\\text{Challenge 2: }", - "&\\text{Using }", - "\\sin(x) = (e^{ix} - e^{-ix}) / 2i,", - "\\text{ show that}\\\\" - "&\\text{step}({t}) = " - "\\sum_{n = -\\infty}^{\\infty}", - "c_{n} e^{{n} \\cdot 2\\pi i {t}}", - "=", - "\\sum_{n = 1, 3, 5, \\dots}", - "{4 \\over {n} \\pi}", - "\\sin\\left({n} \\cdot 2\\pi {t}\\right)", - **tex_config, - ) - words3 = TextMobject( - "Challenge 3: How can you turn this into an " - "expansion with \\\\ \\phantom{Challenge 3:} cosines? " - "(Hint: Draw the sine waves over $[0.25, 0.75]$)", - # "Challenge 3: By focusing on the range $[0.25, 0.75]$, relate\\\\" - # "\\quad\\;\\, this to an expansion with cosines." - alignment="", - ) - - all_words = VGroup(words1, words2, words3) - all_words.arrange(DOWN, buff=LARGE_BUFF, aligned_edge=LEFT) - all_words.scale(0.8) - all_words.to_edge(DOWN) - - self.add(cn_expression) - self.wait() - self.play(FadeInFrom(expansion, LEFT)) - for words in all_words: - self.play(FadeIn(words)) - self.wait() - - -class GeneralChallenge(Scene): - def construct(self): - title = TextMobject("More ambitious challenge") - title.scale(1.5) - title.to_edge(UP, buff=MED_SMALL_BUFF) - title.set_color(BLUE) - line = Line() - line.match_width(title) - line.next_to(title, DOWN, SMALL_BUFF) - self.add(title, line) - - light_pink = interpolate_color(PINK, WHITE, 0.25) - tex_config = { - "tex_to_color_map": { - "{t}": light_pink, - "{dt}": light_pink, - # "{0}": YELLOW, - "{n}": YELLOW, - } - } - words1 = TextMobject( - "In some texts, for a function $f$ on the input range $[0, 1]$,\\\\", - "its Fourier series is presented like this:", - alignment="", - ) - formula1 = TexMobject( - "f(t) = {a_{0} \\over 2} + " - "\\sum_{n = 1}^{\\infty} \\big(" - "a_{n} \\cos\\left({n} \\cdot 2\\pi {t}\\right) + " - "b_{n} \\sin\\left({n} \\cdot 2\\pi {t}\\right) \\big)", - **tex_config - ) - formula1[0][6].set_color(YELLOW) - words2 = TextMobject("where") - formula2 = TexMobject( - "a_{n} = 2\\int_0^1 f({t})\\cos\\left({n} \\cdot 2\\pi {t}\\right) {dt}\\\\" - # "\\text{ and }" - "b_{n} = 2\\int_0^1 f({t})\\sin\\left({n} \\cdot 2\\pi {t}\\right) {dt}", - **tex_config - ) - words3 = TextMobject("How is this the same as what we just did?") - - prompt = VGroup(words1, formula1, words2, formula2, words3) - prompt.arrange(DOWN, buff=MED_LARGE_BUFF) - words2.align_to(words1, LEFT) - words3.align_to(words1, LEFT) - prompt.set_height(6) - prompt.next_to(line, DOWN) - - self.add(prompt) - self.wait() - - -class HintToGeneralChallenge(Scene): - def construct(self): - self.add(FullScreenFadeRectangle( - fill_color=DARKER_GREY, - fill_opacity=1, - )) - words1 = TextMobject("Hint: Try writing") - formula1 = TexMobject( - "c_{n} =", - "{", "a_{n} \\over 2} +", - "{", "b_{n} \\over 2} i", - tex_to_color_map={ - "{n}": YELLOW, - } - ) - words2 = TextMobject("and") - formula2 = TexMobject( - "e^{i{x}} =", - "\\cos\\left({x}\\right) +", - "i\\sin\\left({x}\\right)", - tex_to_color_map={ - "{x}": GREEN - } - ) - - all_words = VGroup( - words1, formula1, - words2, formula2 - ) - all_words.arrange(DOWN, buff=LARGE_BUFF) - - self.add(all_words) - - -class OtherResources(Scene): - def construct(self): - thumbnails = Group( - ImageMobject("MathologerFourier"), - ImageMobject("CodingTrainFourier"), - ) - names = VGroup( - TextMobject("Mathologer"), - TextMobject("The Coding Train"), - ) - - for thumbnail, name in zip(thumbnails, names): - thumbnail.set_height(3) - - thumbnails.arrange(RIGHT, buff=LARGE_BUFF) - thumbnails.set_width(FRAME_WIDTH - 1) - - for thumbnail, name in zip(thumbnails, names): - name.scale(1.5) - name.next_to(thumbnail, UP) - thumbnail.add(name) - self.play( - FadeInFromDown(name), - GrowFromCenter(thumbnail) - ) - self.wait() - - url = TextMobject("www.jezzamon.com") - url.scale(1.5) - url.move_to(FRAME_WIDTH * RIGHT / 5) - url.to_edge(UP) - self.play( - thumbnails.arrange, DOWN, {"buff": LARGE_BUFF}, - thumbnails.set_height, FRAME_HEIGHT - 2, - thumbnails.to_edge, LEFT - ) - self.play(FadeInFromDown(url)) - self.wait() - - -class ExponentialsMoreBroadly(Scene): - def construct(self): - self.write_fourier_series() - self.pull_out_complex_exponent() - self.show_matrix_exponent() - - def write_fourier_series(self): - formula = TexMobject( - "f({t}) = " - "\\sum_{n = -\\infty}^\\infty", - "c_{n}", "e^{{n} \\cdot 2\\pi i {t}}" - "", - tex_to_color_map={ - "{t}": LIGHT_PINK, - "{n}": YELLOW - } - ) - formula[2][4].set_color(YELLOW) - formula.scale(1.5) - formula.to_edge(UP) - formula.add_background_rectangle_to_submobjects() - - plane = ComplexPlane( - axis_config={"unit_size": 2} - ) - - self.play(FadeInFromDown(formula)) - self.wait() - self.add(plane, formula) - self.play( - formula.scale, 0.7, - formula.to_corner, UL, - ShowCreation(plane) - ) - - self.formula = formula - self.plane = plane - - def pull_out_complex_exponent(self): - formula = self.formula - - c_exp = TexMobject("e^{it}") - c_exp[0][2].set_color(LIGHT_PINK) - c_exp.set_stroke(BLACK, 3, background=True) - c_exp.move_to(formula.get_part_by_tex("e^"), DL) - c_exp.set_opacity(0) - - dot = Dot() - circle = Circle(radius=2) - circle.set_color(YELLOW) - dot.add_updater(lambda d: d.move_to(circle.get_end())) - - self.play( - c_exp.set_opacity, 1, - c_exp.scale, 1.5, - c_exp.next_to, dot, UR, SMALL_BUFF, - GrowFromCenter(dot), - ) - c_exp.add_updater( - lambda m: m.next_to(dot, UR, SMALL_BUFF) - ) - self.play( - ShowCreation(circle), - run_time=4, - ) - self.wait() - - self.c_exp = c_exp - self.circle = circle - self.dot = dot - - def show_matrix_exponent(self): - c_exp = self.c_exp - formula = self.formula - circle = self.circle - dot = self.dot - - m_exp = TexMobject( - "\\text{exp}\\left(" - "\\left[ \\begin{array}{cc}0 & -1 \\\\ 1 & 0 \\end{array} \\right]", - "{t} \\right)", - "=", - "\\left[ \\begin{array}{cc}" - "\\cos(t) & -\\sin(t) \\\\ \\sin(t) & \\cos(t)" - "\\end{array} \\right]", - ) - VGroup( - m_exp[1][0], - m_exp[3][5], - m_exp[3][12], - m_exp[3][18], - m_exp[3][24], - ).set_color(LIGHT_PINK) - m_exp.to_corner(UL) - m_exp.add_background_rectangle_to_submobjects() - - vector_field = VectorField( - lambda p: rotate_vector(p, TAU / 4), - max_magnitude=7, - delta_x=0.5, - delta_y=0.5, - length_func=lambda norm: 0.35 * sigmoid(norm), - ) - vector_field.sort(get_norm) - - self.play( - FadeInFromDown(m_exp), - FadeOutAndShift(formula, UP), - FadeOut(c_exp) - ) - self.add(vector_field, circle, dot, m_exp) - self.play( - ShowCreation( - vector_field, - lag_ratio=0.001, - ) - ) - self.play(Rotating(circle, run_time=TAU)) - self.wait() - - -class Part4EndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "1stViewMaths", - "Adrian Robinson", - "Alexis Olson", - "Andreas Benjamin Brössel", - "Andrew Busey", - "Ankalagon", - "Antoine Bruguier", - "Antonio Juarez", - "Arjun Chakroborty", - "Art Ianuzzi", - "Awoo", - "Ayan Doss", - "AZsorcerer", - "Barry Fam", - "Bernd Sing", - "Boris Veselinovich", - "Brian Staroselsky", - "Caleb Johnstone", - "Charles Southerland", - "Chris Connett", - "Christian Kaiser", - "Clark Gaebel", - "Cooper Jones", - "Danger Dai", - "Daniel Pang", - "Dave B", - "Dave Kester", - "David B. Hill", - "David Clark", - "DeathByShrimp", - "Delton Ding", - "eaglle", - "Empirasign", - "emptymachine", - "Eric Younge", - "Eryq Ouithaqueue", - "Federico Lebron", - "Fernando Via Canel", - "Giovanni Filippi", - "Hal Hildebrand", - "Hitoshi Yamauchi", - "Isaac Jeffrey Lee", - "j eduardo perez", - "Jacob Harmon", - "Jacob Hartmann", - "Jacob Magnuson", - "Jameel Syed", - "Jason Hise", - "Jeff Linse", - "Jeff Straathof", - "John C. Vesey", - "John Griffith", - "John Haley", - "John V Wertheim", - "Jonathan Eppele", - "Josh Kinnear", - "Kai-Siang Ang", - "Kanan Gill", - "Kartik Cating-Subramanian", - "L0j1k", - "Lee Redden", - "Linh Tran", - "Ludwig Schubert", - "Magister Mugit", - "Mark B Bahu", - "Martin Price", - "Mathias Jansson", - "Matt Langford", - "Matt Roveto", - "Matthew Bouchard", - "Matthew Cocke", - "Michael Faust", - "Michael Hardel", - "Michael R Rader", - "Mirik Gogri", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nero Li", - "Nikita Lesnikov", - "Omar Zrien", - "Oscar Wu", - "Owen Campbell-Moore", - "Patrick Lucas", - "Peter Ehrnstrom", - "RedAgent14", - "rehmi post", - "Richard Comish", - "Ripta Pasay", - "Rish Kundalia", - "Roobie", - "Ryan Williams", - "Sebastian Garcia", - "Solara570", - "Steven Siddals", - "Stevie Metke", - "Tal Einav", - "Ted Suzman", - "Tianyu Ge", - "Tom Fleming", - "Tyler VanValkenburg", - "Valeriy Skobelev", - "Xuanji Li", - "Yavor Ivanov", - "YinYangBalance.Asia", - "Zach Cardwell", - "Luc Ritchie", - "Britt Selvitelle", - "David Gow", - "J", - "Jonathan Wilson", - "Joseph John Cox", - "Magnus Dahlström", - "Randy C. Will", - "Ryan Atallah", - "Lukas -krtek.net- Novy", - "Jordan Scales", - "Ali Yahya", - "Arthur Zey", - "Atul S", - "dave nicponski", - "Evan Phillips", - "Joseph Kelly", - "Kaustuv DeBiswas", - "Lambda AI Hardware", - "Lukas Biewald", - "Mark Heising", - "Mike Coleman", - "Nicholas Cahill", - "Peter Mcinerney", - "Quantopian", - "Roy Larson", - "Scott Walter, Ph.D.", - "Yana Chernobilsky", - "Yu Jun", - "D. Sivakumar", - "Richard Barthel", - "Burt Humburg", - "Matt Russell", - "Scott Gray", - "soekul", - "Tihan Seale", - "Juan Benet", - "Vassili Philippov", - "Kurt Dicus", - ], - } - diff --git a/from_3b1b/active/diffyq/part4/temperature_scenes.py b/from_3b1b/active/diffyq/part4/temperature_scenes.py deleted file mode 100644 index 8a2b6d8e..00000000 --- a/from_3b1b/active/diffyq/part4/temperature_scenes.py +++ /dev/null @@ -1,371 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part2.heat_equation import BringTwoRodsTogether -from active_projects.diffyq.part3.staging import FourierSeriesIllustraiton - - -class StepFunctionExample(BringTwoRodsTogether, FourierSeriesIllustraiton): - CONFIG = { - "axes_config": { - "y_min": -1.5, - "y_max": 1.5, - "y_axis_config": { - "unit_size": 2.5, - "tick_frequency": 0.5, - }, - "x_min": 0, - "x_max": 1, - "x_axis_config": { - "unit_size": 8, - "tick_frequency": 0.1, - "include_tip": False, - }, - }, - "y_labels": [-1, 1], - "graph_x_min": 0, - "graph_x_max": 1, - "midpoint": 0.5, - "min_temp": -1, - "max_temp": 1, - "alpha": 0.25, - "step_size": 0.01, - "n_range": range(1, 41, 2), - } - - def construct(self): - self.setup_axes() - self.setup_graph() - self.setup_clock() - - self.bring_rods_together() - self.let_evolve_for_a_bit() - self.add_labels() - self.compare_to_sine_wave() - self.sum_of_sine_waves() - - def bring_rods_together(self): - rods = VGroup( - self.get_rod(0, 0.5), - self.get_rod(0.5, 1), - ) - rods.add_updater(self.update_rods) - - arrows = VGroup( - Vector(RIGHT).next_to(rods[0], UP), - Vector(LEFT).next_to(rods[1], UP), - ) - - words = VGroup( - TextMobject("Hot").next_to(rods[0], DOWN), - TextMobject("Cold").next_to(rods[1], DOWN), - ) - - for pair in rods, words: - pair.save_state() - pair.space_out_submobjects(1.2) - - black_rects = VGroup(*[ - Square( - side_length=1, - fill_color=BLACK, - fill_opacity=1, - stroke_width=0, - ).move_to(self.axes.c2p(0, u)) - for u in [1, -1] - ]) - black_rects[0].add_updater( - lambda m: m.align_to(rods[0].get_right(), LEFT) - ) - black_rects[1].add_updater( - lambda m: m.align_to(rods[1].get_left(), RIGHT) - ) - - self.add( - self.axes, - self.graph, - self.clock, - ) - self.add(rods, words) - self.add(black_rects) - - kw = { - "run_time": 2, - "rate_func": rush_into, - } - self.play( - Restore(rods, **kw), - Restore(words, **kw), - *map(ShowCreation, arrows) - ) - self.remove(black_rects) - - self.to_fade = VGroup(words, arrows) - self.rods = rods - - def let_evolve_for_a_bit(self): - rods = self.rods - # axes = self.axes - time_label = self.time_label - graph = self.graph - graph.save_state() - - graph.add_updater(self.update_graph) - time_label.next_to(self.clock, DOWN) - time_label.add_updater( - lambda d, dt: d.increment_value(dt) - ) - rods.add_updater(self.update_rods) - - self.add(time_label) - self.play( - FadeOut(self.to_fade), - self.get_clock_anim(1) - ) - self.play(self.get_clock_anim(3)) - - time_label.clear_updaters() - graph.clear_updaters() - self.play( - self.get_clock_anim( - -4, - run_time=1, - rate_func=smooth, - ), - graph.restore, - time_label.set_value, 0, - ) - rods.clear_updaters() - self.wait() - - def add_labels(self): - axes = self.axes - y_axis = axes.y_axis - x_axis = axes.x_axis - y_numbers = y_axis.get_number_mobjects( - *np.arange(-1, 1.5, 0.5), - number_config={ - "unit": "^\\circ", - "num_decimal_places": 1, - } - ) - x_numbers = x_axis.get_number_mobjects( - *np.arange(0.2, 1.2, 0.2), - number_config={ - "num_decimal_places": 1, - }, - ) - - self.play(FadeIn(y_numbers)) - self.play(ShowCreationThenFadeAround(y_numbers[-1])) - self.play(ShowCreationThenFadeAround(y_numbers[0])) - self.play( - LaggedStartMap( - FadeInFrom, x_numbers, - lambda m: (m, UP) - ), - self.rods.set_opacity, 0.8, - ) - self.wait() - - def compare_to_sine_wave(self): - phi_tracker = ValueTracker(0) - get_phi = phi_tracker.get_value - k_tracker = ValueTracker(TAU) - get_k = k_tracker.get_value - A_tracker = ValueTracker(1) - get_A = A_tracker.get_value - - sine_wave = always_redraw(lambda: self.axes.get_graph( - lambda x: get_A() * np.sin( - get_k() * x - get_phi() - ), - x_min=self.graph_x_min, - x_max=self.graph_x_max, - ).color_using_background_image("VerticalTempGradient")) - - self.play(ShowCreation(sine_wave, run_time=3)) - self.wait() - self.play(A_tracker.set_value, 1.25) - self.play(A_tracker.set_value, 0.75) - self.play(phi_tracker.set_value, -PI / 2) - self.play(k_tracker.set_value, 3 * TAU) - self.play(k_tracker.set_value, 2 * TAU) - self.play( - k_tracker.set_value, PI, - A_tracker.set_value, 4 / PI, - run_time=3 - ) - self.wait() - - self.sine_wave = sine_wave - - def sum_of_sine_waves(self): - curr_sine_wave = self.sine_wave - axes = self.axes - - sine_graphs = self.get_sine_graphs(axes) - partial_sums = self.get_partial_sums(axes, sine_graphs) - - curr_partial_sum = partial_sums[0] - curr_partial_sum.set_color(WHITE) - self.play( - FadeOut(curr_sine_wave), - FadeIn(curr_partial_sum), - FadeOut(self.rods), - ) - # Copy-pasting from superclass...in theory, - # this should be better abstracted, but eh. - pairs = list(zip(sine_graphs, partial_sums))[1:] - for sine_graph, partial_sum in pairs: - anims1 = [ - ShowCreation(sine_graph) - ] - partial_sum.set_stroke(BLACK, 4, background=True) - anims2 = [ - curr_partial_sum.set_stroke, - {"width": 1, "opacity": 0.25}, - curr_partial_sum.set_stroke, - {"width": 0, "background": True}, - ReplacementTransform( - sine_graph, partial_sum, - remover=True - ), - ] - self.play(*anims1) - self.play(*anims2) - curr_partial_sum = partial_sum - - # - def setup_axes(self): - super().setup_axes() - self.axes.shift( - self.axes.c2p(0, 0)[1] * DOWN - ) - - -class BreakDownStepFunction(StepFunctionExample): - CONFIG = { - "axes_config": { - "x_axis_config": { - "stroke_width": 2, - }, - "y_axis_config": { - "tick_frequency": 0.25, - "stroke_width": 2, - }, - "y_min": -1.25, - "y_max": 1.25, - }, - "alpha": 0.1, - "wait_time": 30, - } - - def construct(self): - self.setup_axes() - self.setup_graph() - self.setup_clock() - self.add_rod() - - self.wait() - self.init_updaters() - self.play( - self.get_clock_anim(self.wait_time) - ) - - def setup_axes(self): - super().setup_axes() - axes = self.axes - axes.to_edge(LEFT) - - mini_axes = VGroup(*[ - axes.deepcopy() - for x in range(4) - ]) - for n, ma in zip(it.count(1, 2), mini_axes): - if n == 1: - t1 = TexMobject("1") - t2 = TexMobject("-1") - else: - t1 = TexMobject("1 / " + str(n)) - t2 = TexMobject("-1 / " + str(n)) - VGroup(t1, t2).scale(1.5) - t1.next_to(ma.y_axis.n2p(1), LEFT, MED_SMALL_BUFF) - t2.next_to(ma.y_axis.n2p(-1), LEFT, MED_SMALL_BUFF) - ma.y_axis.numbers.set_opacity(0) - ma.y_axis.add(t1, t2) - - for mob in mini_axes.get_family(): - if isinstance(mob, Line): - mob.set_stroke(width=1, family=False) - mini_axes.arrange(DOWN, buff=2) - mini_axes.set_height(FRAME_HEIGHT - 1.5) - mini_axes.to_corner(UR) - self.scale_factor = fdiv( - mini_axes[0].get_width(), - axes.get_width(), - ) - - # mini_axes.arrange(RIGHT, buff=2) - # mini_axes.set_width(FRAME_WIDTH - 1.5) - # mini_axes.to_edge(LEFT) - - dots = TexMobject("\\vdots") - dots.next_to(mini_axes, DOWN) - dots.shift_onto_screen() - - self.add(axes) - self.add(mini_axes) - self.add(dots) - - self.mini_axes = mini_axes - - def setup_graph(self): - super().setup_graph() - graph = self.graph - self.add(graph) - - mini_axes = self.mini_axes - mini_graphs = VGroup() - for axes, u, n in zip(mini_axes, it.cycle([1, -1]), it.count(1, 2)): - mini_graph = axes.get_graph( - lambda x: (4 / PI) * (u / 1) * np.cos(PI * n * x), - ) - mini_graph.set_stroke(WHITE, width=2) - mini_graphs.add(mini_graph) - # mini_graphs.set_color_by_gradient( - # BLUE, GREEN, RED, YELLOW, - # ) - - self.mini_graphs = mini_graphs - self.add(mini_graphs) - - def setup_clock(self): - super().setup_clock() - clock = self.clock - time_label = self.time_label - - clock.move_to(3 * RIGHT) - clock.to_corner(UP) - time_label.next_to(clock, DOWN) - - self.add(clock) - self.add(time_label) - - def add_rod(self): - self.rod = self.get_rod(0, 1) - self.add(self.rod) - - def init_updaters(self): - self.graph.add_updater(self.update_graph) - for mg in self.mini_graphs: - mg.add_updater( - lambda m, dt: self.update_graph( - m, dt, - alpha=self.scale_factor * self.alpha - ) - ) - self.time_label.add_updater( - lambda d, dt: d.increment_value(dt) - ) - self.rod.add_updater( - lambda r: self.update_rods([r]) - ) diff --git a/from_3b1b/active/diffyq/part4/three_d_graphs.py b/from_3b1b/active/diffyq/part4/three_d_graphs.py deleted file mode 100644 index 420b5f74..00000000 --- a/from_3b1b/active/diffyq/part4/three_d_graphs.py +++ /dev/null @@ -1,474 +0,0 @@ -from manimlib.imports import * -from active_projects.diffyq.part3.temperature_graphs import TemperatureGraphScene -from active_projects.diffyq.part2.wordy_scenes import WriteHeatEquationTemplate - - -class ShowLinearity(WriteHeatEquationTemplate, TemperatureGraphScene): - CONFIG = { - "temp_text": "Temp", - "alpha": 0.1, - "axes_config": { - "z_max": 2, - "z_min": -2, - "z_axis_config": { - "tick_frequency": 0.5, - "unit_size": 1.5, - }, - }, - "default_surface_config": { - "resolution": (16, 16) - # "resolution": (4, 4) - }, - "freqs": [2, 5], - } - - def setup(self): - TemperatureGraphScene.setup(self) - WriteHeatEquationTemplate.setup(self) - - def construct(self): - self.init_camera() - self.add_three_graphs() - self.show_words() - self.add_function_labels() - self.change_scalars() - - def init_camera(self): - self.camera.set_distance(1000) - - def add_three_graphs(self): - axes_group = self.get_axes_group() - axes0, axes1, axes2 = axes_group - freqs = self.freqs - scalar_trackers = Group( - ValueTracker(1), - ValueTracker(1), - ) - graphs = VGroup( - self.get_graph(axes0, [freqs[0]], [scalar_trackers[0]]), - self.get_graph(axes1, [freqs[1]], [scalar_trackers[1]]), - self.get_graph(axes2, freqs, scalar_trackers), - ) - - plus = TexMobject("+").scale(2) - equals = TexMobject("=").scale(2) - plus.move_to(midpoint( - axes0.get_right(), - axes1.get_left(), - )) - equals.move_to(midpoint( - axes1.get_right(), - axes2.get_left(), - )) - - self.add(axes_group) - self.add(graphs) - self.add(plus) - self.add(equals) - - self.axes_group = axes_group - self.graphs = graphs - self.scalar_trackers = scalar_trackers - self.plus = plus - self.equals = equals - - def show_words(self): - equation = self.get_d1_equation() - name = TextMobject("Heat equation") - name.next_to(equation, DOWN) - name.set_color_by_gradient(RED, YELLOW) - group = VGroup(equation, name) - group.to_edge(UP) - - shift_val = 0.5 * RIGHT - - arrow = Vector(1.5 * RIGHT) - arrow.move_to(group) - arrow.shift(shift_val) - linear_word = TextMobject("``Linear''") - linear_word.scale(2) - linear_word.next_to(arrow, RIGHT) - - self.add(group) - self.wait() - self.play( - ShowCreation(arrow), - group.next_to, arrow, LEFT - ) - self.play(FadeInFrom(linear_word, LEFT)) - self.wait() - - def add_function_labels(self): - axes_group = self.axes_group - graphs = self.graphs - - solution_labels = VGroup() - for axes in axes_group: - label = TextMobject("Solution", "$\\checkmark$") - label.set_color_by_tex("checkmark", GREEN) - label.next_to(axes, DOWN) - solution_labels.add(label) - - kw = { - "tex_to_color_map": { - "T_1": BLUE, - "T_2": GREEN, - } - } - T1 = TexMobject("a", "T_1", **kw) - T2 = TexMobject("b", "T_2", **kw) - T_sum = TexMobject("T_1", "+", "T_2", **kw) - T_sum_with_scalars = TexMobject( - "a", "T_1", "+", "b", "T_2", **kw - ) - - T1.next_to(graphs[0], UP) - T2.next_to(graphs[1], UP) - T_sum.next_to(graphs[2], UP) - T_sum.shift(SMALL_BUFF * DOWN) - T_sum_with_scalars.move_to(T_sum) - - a_brace = Brace(T1[0], UP, buff=SMALL_BUFF) - b_brace = Brace(T2[0], UP, buff=SMALL_BUFF) - s1_decimal = DecimalNumber() - s1_decimal.match_color(T1[1]) - s1_decimal.next_to(a_brace, UP, SMALL_BUFF) - s1_decimal.add_updater(lambda m: m.set_value( - self.scalar_trackers[0].get_value() - )) - s2_decimal = DecimalNumber() - s2_decimal.match_color(T2[1]) - s2_decimal.next_to(b_brace, UP, SMALL_BUFF) - s2_decimal.add_updater(lambda m: m.set_value( - self.scalar_trackers[1].get_value() - )) - - self.play( - FadeInFrom(T1[1], DOWN), - FadeInFrom(solution_labels[0], UP), - ) - self.play( - FadeInFrom(T2[1], DOWN), - FadeInFrom(solution_labels[1], UP), - ) - self.wait() - self.play( - TransformFromCopy(T1[1], T_sum[0]), - TransformFromCopy(T2[1], T_sum[2]), - TransformFromCopy(self.plus, T_sum[1]), - *[ - Transform( - graph.copy().set_fill(opacity=0), - graphs[2].copy().set_fill(opacity=0), - remover=True - ) - for graph in graphs[:2] - ] - ) - self.wait() - self.play(FadeInFrom(solution_labels[2], UP)) - self.wait() - - # Show constants - self.play( - FadeIn(T1[0]), - FadeIn(T2[0]), - FadeIn(a_brace), - FadeIn(b_brace), - FadeIn(s1_decimal), - FadeIn(s2_decimal), - FadeOut(T_sum), - FadeIn(T_sum_with_scalars), - ) - - def change_scalars(self): - s1, s2 = self.scalar_trackers - - kw = { - "run_time": 2, - } - for graph in self.graphs: - graph.resume_updating() - self.play(s2.set_value, -0.5, **kw) - self.play(s1.set_value, -0.2, **kw) - self.play(s2.set_value, 1.5, **kw) - self.play(s1.set_value, 1.2, **kw) - self.play(s2.set_value, 0.3, **kw) - self.wait() - - # - def get_axes_group(self): - axes_group = VGroup(*[ - self.get_axes() - for x in range(3) - ]) - axes_group.arrange(RIGHT, buff=2) - axes_group.set_width(FRAME_WIDTH - 1) - axes_group.to_edge(DOWN, buff=1) - return axes_group - - def get_axes(self, **kwargs): - axes = self.get_three_d_axes(**kwargs) - # axes.input_plane.set_fill(opacity=0) - # axes.input_plane.set_stroke(width=0.5) - # axes.add(axes.input_plane) - self.orient_three_d_mobject(axes) - axes.rotate(-5 * DEGREES, UP) - axes.set_width(4) - axes.x_axis.label.next_to( - axes.x_axis.get_end(), DOWN, - buff=2 * SMALL_BUFF - ) - return axes - - def get_graph(self, axes, freqs, scalar_trackers): - L = axes.x_max - a = self.alpha - - def func(x, t): - scalars = [st.get_value() for st in scalar_trackers] - return np.sum([ - s * np.cos(k * x) * np.exp(-a * (k**2) * t) - for freq, s in zip(freqs, scalars) - for k in [freq * PI / L] - ]) - - def get_surface_graph_group(): - return VGroup( - self.get_surface(axes, func), - self.get_time_slice_graph(axes, func, t=0), - ) - - result = always_redraw(get_surface_graph_group) - result.func = func - result.suspend_updating() - return result - - -class CombineSeveralSolutions(ShowLinearity): - CONFIG = { - "default_surface_config": { - "resolution": (16, 16), - # "resolution": (4, 4), - }, - "n_top_graphs": 5, - "axes_config": { - "y_max": 15, - }, - "target_scalars": [ - 0.81, -0.53, 0.41, 0.62, -0.95 - ], - "final_run_time": 14, - } - - def construct(self): - self.init_camera() - self.add_all_axes() - self.setup_all_graphs() - self.show_infinite_family() - self.show_sum() - self.show_time_passing() - - def add_all_axes(self): - top_axes_group = VGroup(*[ - self.get_axes( - z_min=-1.25, - z_max=1.25, - z_axis_config={ - "unit_size": 2, - "tick_frequency": 0.5, - }, - ) - for x in range(self.n_top_graphs) - ]) - top_axes_group.arrange(RIGHT, buff=2) - top_axes_group.set_width(FRAME_WIDTH - 1.5) - top_axes_group.to_corner(UL) - dots = TexMobject("\\dots") - dots.next_to(top_axes_group, RIGHT) - - low_axes = self.get_axes() - low_axes.center() - low_axes.scale(1.2) - low_axes.to_edge(DOWN, buff=SMALL_BUFF) - - self.add(top_axes_group) - self.add(dots) - self.add(low_axes) - - self.top_axes_group = top_axes_group - self.low_axes = low_axes - - def setup_all_graphs(self): - scalar_trackers = Group(*[ - ValueTracker(1) - for x in range(self.n_top_graphs) - ]) - freqs = np.arange(self.n_top_graphs) - freqs += 1 - self.top_graphs = VGroup(*[ - self.get_graph(axes, [n], [st]) - for axes, n, st in zip( - self.top_axes_group, - freqs, - scalar_trackers, - ) - ]) - self.low_graph = self.get_graph( - self.low_axes, freqs, scalar_trackers - ) - - self.scalar_trackers = scalar_trackers - - def show_infinite_family(self): - top_axes_group = self.top_axes_group - top_graphs = self.top_graphs - scalar_trackers = self.scalar_trackers - - decimals = self.get_decimals( - top_axes_group, scalar_trackers - ) - - self.play(LaggedStart(*[ - AnimationGroup( - Write(graph[0]), - FadeIn(graph[1]), - ) - for graph in top_graphs - ])) - self.wait() - self.play(FadeIn(decimals)) - for graph in top_graphs: - graph.resume_updating() - - self.play(LaggedStart(*[ - ApplyMethod(st.set_value, value) - for st, value in zip( - scalar_trackers, - self.target_scalars, - ) - ]), run_time=3) - self.wait() - - def show_sum(self): - top_graphs = self.top_graphs - low_graph = self.low_graph - low_graph.resume_updating() - low_graph.update() - - self.play( - LaggedStart(*[ - Transform( - top_graph.copy().set_fill(opacity=0), - low_graph.copy().set_fill(opacity=0), - remover=True, - ) - for top_graph in top_graphs - ]), - FadeIn( - low_graph, - rate_func=squish_rate_func(smooth, 0.7, 1) - ), - run_time=3, - ) - self.wait() - - def show_time_passing(self): - all_graphs = [*self.top_graphs, self.low_graph] - all_axes = [*self.top_axes_group, self.low_axes] - - time_tracker = ValueTracker(0) - get_t = time_tracker.get_value - - anims = [ - ApplyMethod( - time_tracker.set_value, 1, - run_time=1, - rate_func=linear - ) - ] - - for axes, graph_group in zip(all_axes, all_graphs): - graph_group.clear_updaters() - surface, gslice = graph_group - plane = self.get_const_time_plane(axes) - plane.t_tracker.add_updater( - lambda m: m.set_value(get_t()) - ) - gslice.axes = axes - gslice.func = graph_group.func - gslice.add_updater(lambda m: m.become( - self.get_time_slice_graph( - m.axes, m.func, t=get_t() - ) - )) - self.add(gslice) - self.add(plane.t_tracker) - anims.append(FadeIn(plane)) - - self.play(*anims) - run_time = self.final_run_time - self.play( - time_tracker.increment_value, run_time, - run_time=run_time, - rate_func=linear, - ) - - # - def get_decimals(self, axes_group, scalar_trackers): - result = VGroup() - for axes, st in zip(axes_group, scalar_trackers): - decimal = DecimalNumber() - decimal.move_to(axes.get_bottom(), UP) - decimal.shift(SMALL_BUFF * RIGHT) - decimal.set_color(YELLOW) - decimal.scalar_tracker = st - times = TexMobject("\\times") - times.next_to(decimal, LEFT, SMALL_BUFF) - decimal.add_updater(lambda d: d.set_value( - d.scalar_tracker.get_value() - )) - group = VGroup(times, decimal) - group.scale(0.7) - result.add(group) - return result - - -class CycleThroughManyLinearCombinations(CombineSeveralSolutions): - CONFIG = { - "default_surface_config": { - "resolution": (16, 16), - # "resolution": (4, 4), - }, - "n_cycles": 10, - } - - def construct(self): - self.init_camera() - self.add_all_axes() - self.setup_all_graphs() - # - self.cycle_through_superpositions() - - def cycle_through_superpositions(self): - top_graphs = self.top_graphs - low_graph = self.low_graph - scalar_trackers = self.scalar_trackers - self.add(self.get_decimals( - self.top_axes_group, scalar_trackers - )) - - for graph in [low_graph, *top_graphs]: - graph.resume_updating() - self.add(graph) - - nst = len(scalar_trackers) - for x in range(self.n_cycles): - self.play(LaggedStart(*[ - ApplyMethod(st.set_value, value) - for st, value in zip( - scalar_trackers, - 3 * np.random.random(nst) - 1.5 - ) - ]), run_time=3) - self.wait() diff --git a/from_3b1b/active/diffyq/part5/staging.py b/from_3b1b/active/diffyq/part5/staging.py deleted file mode 100644 index baf34ba1..00000000 --- a/from_3b1b/active/diffyq/part5/staging.py +++ /dev/null @@ -1,1586 +0,0 @@ -from manimlib.imports import * - -T_COLOR = LIGHT_GREY -VELOCITY_COLOR = GREEN -POSITION_COLOR = BLUE -CONST_COLOR = YELLOW - -tex_config = { - "tex_to_color_map": { - "{t}": T_COLOR, - "{0}": T_COLOR, - "e^": WHITE, - "=": WHITE, - } -} - - -class IntroductionOfExp(Scene): - def construct(self): - questions = VGroup( - TextMobject("What", "is", "$e^{t}$", "?"), - TextMobject("What", "properties", "does", "$e^{t}$", "have?"), - TextMobject( - "What", "property", "defines", "$e^{t}$", "?", - tex_to_color_map={"defines": BLUE} - ), - ) - questions.scale(2) - questions[0].next_to(questions[1], UP, LARGE_BUFF) - questions[2][-1].next_to(questions[2][-2], RIGHT, SMALL_BUFF) - questions.to_edge(UP) - for question in questions: - part = question.get_part_by_tex("e^{t}") - part[1].set_color(T_COLOR) - - top_cross = Cross(questions[0]) - - deriv_ic = self.get_deriv_and_ic() - deriv_ic.save_state() - deriv_ic.scale(2 / 1.5) - deriv_ic.to_edge(DOWN, buff=LARGE_BUFF) - derivative, ic = deriv_ic - derivative.save_state() - derivative.set_x(0) - - self.play(FadeInFromDown(questions[0])) - self.wait() - self.play( - ShowCreation(top_cross), - LaggedStart( - *[ - TransformFromCopy( - questions[0].get_part_by_tex(tex), - questions[1].get_part_by_tex(tex), - ) - for tex in ("What", "e^{t}") - ], - *[ - FadeIn(questions[1][i]) - for i in [1, 2, 4] - ] - ) - ) - self.wait() - self.play( - TransformFromCopy( - questions[1].get_part_by_tex("$e^{t}$"), - derivative[3:7], - ), - FadeIn(derivative[:2]), - FadeIn(derivative.get_part_by_tex("=")), - ) - self.play( - ReplacementTransform( - questions[1][0], - questions[2][0], - ), - FadeOut(questions[1][1]), - FadeIn(questions[2][1]), - FadeOut(questions[1][2]), - FadeIn(questions[2][2]), - ReplacementTransform( - questions[1][3], - questions[2][3], - ), - FadeOut(questions[1][4]), - FadeIn(questions[2][4]), - ) - self.wait() - self.play( - TransformFromCopy( - derivative[3:7:3], - derivative[-5:], - path_arc=-60 * DEGREES, - ), - ) - self.wait() - self.play( - Restore(derivative), - FadeInFrom(ic, LEFT) - ) - self.wait() - self.play( - FadeOut(questions[0]), - FadeOut(top_cross), - FadeOut(questions[2]), - Restore(deriv_ic), - ) - self.wait() - - def get_deriv_and_ic(self, const=None): - if const: - const_str = "{" + str(const) + "}" - mult_str = "\\cdot" - else: - const_str = "{}" - mult_str = "{}" - args = [ - "{d \\over d{t}}", - "e^{", - const_str, - "{t}}", - "=", - const_str, - mult_str, - "e^{", - const_str, - "{t}}" - ] - derivative = TexMobject( - *args, - **tex_config - ) - if const: - derivative.set_color_by_tex(const_str, CONST_COLOR) - - ic = TexMobject("e^{0} = 1", **tex_config) - group = VGroup(derivative, ic) - group.arrange(RIGHT, buff=LARGE_BUFF) - ic.align_to(derivative.get_part_by_tex("e^"), DOWN) - group.scale(1.5) - group.to_edge(UP) - return group - - -class IntroducePhysicalModel(IntroductionOfExp): - CONFIG = { - "number_line_config": { - "x_min": 0, - "x_max": 100, - "unit_size": 1.5, - "numbers_with_elongated_ticks": [0], - "numbers_to_show": list(range(0, 15)), - "label_direction": UP, - }, - "number_line_y": -2, - "const": 1, - "const_str": "", - "num_decimal_places": 2, - "output_label_config": { - "show_ellipsis": True, - }, - } - - def construct(self): - self.setup_number_line() - self.setup_movers() - self.show_number_line() - self.show_formulas() - self.let_time_pass() - - def setup_number_line(self): - nl = self.number_line = NumberLine( - **self.number_line_config, - ) - nl.to_edge(LEFT) - nl.set_y(self.number_line_y) - nl.add_numbers() - - def setup_movers(self): - self.setup_value_trackers() - self.setup_vectors() - self.setup_vector_labels() - self.setup_tip() - self.setup_tip_label() - self.setup_output_label() - - def setup_value_trackers(self): - number_line = self.number_line - - input_tracker = ValueTracker(0) - get_input = input_tracker.get_value - - def complex_number_to_point(z): - zero = number_line.n2p(0) - unit = number_line.n2p(1) - zero - perp = rotate_vector(unit, TAU / 4) - z = complex(z) - return zero + unit * z.real + perp * z.imag - - number_line.cn2p = complex_number_to_point - - def get_output(): - return np.exp(self.const * get_input()) - - def get_output_point(): - return number_line.n2p(get_output()) - - output_tracker = ValueTracker(1) - output_tracker.add_updater( - lambda m: m.set_value(get_output()) - ) - - self.get_input = get_input - self.get_output = get_output - self.get_output_point = get_output_point - - self.add( - input_tracker, - output_tracker, - ) - self.input_tracker = input_tracker - self.output_tracker = output_tracker - - def setup_tip(self): - tip = ArrowTip(start_angle=-PI / 2) - tip.set_height(0.6) - tip.set_width(0.1, stretch=True) - tip.add_updater(lambda m: m.move_to( - self.get_output_point(), DOWN - )) - # tip.set_color(WHITE) - tip.set_fill(GREY, 1) - - self.tip = tip - - def setup_tip_label(self): - tip = self.tip - const_str = self.const_str - ndp = self.num_decimal_places - - args = ["e^{"] - if const_str: - args += [const_str, "\\cdot"] - args += ["0." + ndp * "0"] - - tip_label = TexMobject(*args, arg_separator="") - if const_str: - tip_label[1].set_color(CONST_COLOR) - - template_decimal = tip_label[-1] - # parens = tip_label[-3::2] - template_decimal.set_opacity(0) - tip_label.add_updater(lambda m: m.next_to( - tip, UP, - buff=2 * SMALL_BUFF, - index_of_submobject_to_align=0 - )) - decimal = DecimalNumber(num_decimal_places=ndp) - decimal.set_color(T_COLOR) - decimal.match_height(template_decimal) - decimal.add_updater(lambda m: m.set_value(self.get_input())) - decimal.add_updater( - lambda m: m.move_to(template_decimal, LEFT) - ) - tip_label.add(decimal) - - self.tip_label = tip_label - - def setup_output_label(self): - label = VGroup( - TexMobject("="), - DecimalNumber(1, **self.output_label_config) - ) - label.set_color(POSITION_COLOR) - label.add_updater( - lambda m: m[1].set_value( - self.get_output() - ) - ) - label.add_updater( - lambda m: m.arrange(RIGHT, buff=SMALL_BUFF) - ) - label.add_updater( - lambda m: m.next_to( - self.tip_label, RIGHT, - buff=SMALL_BUFF, - aligned_edge=DOWN, - ) - ) - - self.output_label = label - - def setup_vectors(self): - number_line = self.number_line - - position_vect = Vector(color=POSITION_COLOR) - velocity_vect = Vector(color=VELOCITY_COLOR) - - position_vect.add_updater( - lambda m: m.put_start_and_end_on( - number_line.cn2p(0), - number_line.cn2p(self.get_output()), - ) - ) - - def update_velocity_vect(vect): - vect.put_start_and_end_on( - number_line.cn2p(0), - number_line.cn2p(self.const * self.get_output()) - ) - vect.shift( - number_line.cn2p(self.get_output()) - number_line.n2p(0) - ) - return vect - - velocity_vect.add_updater(update_velocity_vect) - - self.position_vect = position_vect - self.velocity_vect = velocity_vect - - def setup_vector_labels(self): - position_vect = self.position_vect - velocity_vect = self.velocity_vect - - max_width = 1.5 - p_label = TextMobject("Position") - p_label.set_color(POSITION_COLOR) - p_label.vect = position_vect - v_label = TextMobject("Velocity") - v_label.set_color(VELOCITY_COLOR) - v_label.vect = velocity_vect - - # for label in [p_label, v_label]: - # label.add_background_rectangle() - - def update_label(label): - label.set_width(min( - max_width, - 0.8 * label.vect.get_length(), - )) - direction = normalize(label.vect.get_vector()) - perp_direction = rotate_vector(direction, -TAU / 4) - label.next_to( - label.vect.get_center(), - np.round(perp_direction), - buff=MED_SMALL_BUFF, - ) - p_label.add_updater(update_label) - v_label.add_updater(update_label) - - self.position_label = p_label - self.velocity_label = v_label - - def show_number_line(self): - number_line = self.number_line - self.play( - LaggedStartMap( - FadeIn, - number_line.numbers.copy(), - remover=True, - ), - Write(number_line), - run_time=2 - ) - self.wait() - - def show_formulas(self): - deriv, ic = self.get_deriv_and_ic() - eq_index = deriv.index_of_part_by_tex("=") - lhs = deriv[:eq_index - 1] - rhs = deriv[eq_index + 1:] - - rhs_rect = SurroundingRectangle(rhs) - lhs_rect = SurroundingRectangle(lhs) - rects = VGroup(rhs_rect, lhs_rect) - rhs_rect.set_stroke(POSITION_COLOR, 2) - lhs_rect.set_stroke(VELOCITY_COLOR, 2) - - rhs_word = TextMobject("Position") - lhs_word = TextMobject("Velocity") - words = VGroup(rhs_word, lhs_word) - for word, rect in zip(words, rects): - word.match_color(rect) - word.next_to(rect, DOWN) - - self.add(deriv, ic) - - self.play( - ShowCreation(rhs_rect), - FadeInFrom(rhs_word, UP), - ShowCreation(self.position_vect) - ) - self.add( - self.tip, - self.number_line, - self.position_vect, - ) - self.number_line.numbers.set_stroke(BLACK, 3, background=True) - self.play( - Transform( - rhs.copy(), - self.tip_label.copy().clear_updaters(), - remover=True, - ), - GrowFromPoint(self.tip, rhs.get_bottom()), - TransformFromCopy(rhs_word, self.position_label), - ) - self.add(self.tip_label) - self.wait() - self.play( - ShowCreation(lhs_rect), - FadeInFrom(lhs_word, UP), - ) - self.wait() - self.play( - TransformFromCopy( - self.position_vect, - self.velocity_vect, - path_arc=PI, - ) - ) - self.play( - TransformFromCopy( - lhs_word, - self.velocity_label, - ) - ) - self.wait() - - def let_time_pass(self): - # Sort of a quick and dirty implementation, - # not the best for reusability - - rate = 0.25 - t_tracker = self.input_tracker - t_tracker.add_updater( - lambda m, dt: m.increment_value(rate * dt) - ) - - nl = self.number_line - zero_point = nl.n2p(0) - - nl_copy = nl.copy() - nl_copy.submobjects = [] - nl_copy.stretch(100, 0, about_point=zero_point) - nl.add(nl_copy) - - # For first zoom - xs1 = range(25, 100, 25) - nl.add(*[ - nl.get_tick(x, size=1.5) - for x in xs1 - ]) - nl.add_numbers( - *xs1, - number_config={"height": 5}, - buff=2, - ) - - # For second zoom - xs2 = range(200, 1000, 200) - nl.add(*[ - nl.get_tick(x, size=15) - for x in xs2 - ]) - nl.add_numbers( - *xs2, - number_config={"height": 50}, - buff=20, - ) - - # For third zoom - xs3 = range(2000, 10000, 2000) - nl.add(*[ - nl.get_tick(x, size=150) - for x in xs3 - ]) - nl.add_numbers( - *xs3, - number_config={ - "height": 300, - }, - buff=200, - ) - for n in nl.numbers: - n[1].scale(0.5) - - self.add(self.tip) - - self.add(self.output_label) - self.play(VFadeIn(self.output_label)) - self.wait(5) - self.play( - nl.scale, 0.1, {"about_point": zero_point}, - run_time=1, - ) - self.wait(6) - self.play( - nl.scale, 0.1, {"about_point": zero_point}, - run_time=1, - ) - self.wait(8) - self.play( - nl.scale, 0.1, {"about_point": zero_point}, - run_time=1, - ) - self.wait(12) - - -class ConstantEquals2(IntroducePhysicalModel): - CONFIG = { - "const": 2, - "const_str": "2", - "const_text": "Double", - } - - def construct(self): - self.setup_number_line() - self.setup_movers() - - self.add_initial_objects() - self.point_out_chain_rule() - self.show_double_vector() - self.let_time_pass() - - def add_initial_objects(self): - deriv, ic = self.get_deriv_and_ic( - const=self.const - ) - self.deriv = deriv - self.ic = ic - self.add(ic) - - self.add(self.tip) - self.add(self.number_line) - self.add(self.position_vect) - self.add(self.position_label) - self.add(self.tip_label) - - def point_out_chain_rule(self): - deriv = self.deriv - - eq = deriv.get_part_by_tex("=") - dot = deriv.get_part_by_tex("\\cdot") - eq_index = deriv.index_of_part(eq) - dot_index = deriv.index_of_part(dot) - v_part = deriv[:eq_index - 1] - c_part = deriv[eq_index + 1: dot_index] - p_part = deriv[dot_index + 1:] - parts = VGroup(v_part, c_part, p_part) - - rects = VGroup(*map(SurroundingRectangle, parts)) - words = VGroup(*map(TextMobject, [ - "Velocity", self.const_text, "Position" - ])) - colors = [VELOCITY_COLOR, CONST_COLOR, POSITION_COLOR] - for rect, word, color in zip(rects, words, colors): - rect.set_stroke(color, 2) - word.set_color(color) - word.next_to(rect, DOWN) - - v_rect, c_rect, p_rect = rects - v_word, c_word, p_word = words - self.readjust_const_label(c_word, c_rect) - - self.add(v_part, eq) - self.add(v_rect, v_word) - - p_part.save_state() - p_part.replace(v_part[-4:]) - c_part.save_state() - c_part.replace(p_part.saved_state[2]) - - self.play(ShowCreationThenFadeAround( - v_part[-2], - surrounding_rectangle_config={ - "color": CONST_COLOR - }, - )) - self.wait() - self.play( - Restore(p_part, path_arc=-TAU / 6), - FadeIn(p_rect), - FadeIn(p_word), - ) - self.play( - Restore(c_part, path_arc=TAU / 6), - FadeIn(dot) - ) - self.play( - FadeIn(c_rect), - FadeIn(c_word), - ) - self.wait() - - self.v_word = v_word - - def show_double_vector(self): - v_vect = self.velocity_vect - p_vect = self.position_vect - p_copy = p_vect.copy() - p_copy.clear_updaters() - p_copy.generate_target() - p_copy.target.shift(2 * UR) - - times_2 = TexMobject("\\times", "2") - times_2.set_color_by_tex("2", CONST_COLOR) - times_2.next_to(p_copy.target.get_end(), UP, MED_SMALL_BUFF) - - self.play( - MoveToTarget(p_copy), - FadeIn(times_2), - ) - self.play( - p_copy.scale, 2, {"about_edge": LEFT}, - p_copy.match_color, v_vect, - ) - self.play(FadeOut(times_2)) - self.play( - ReplacementTransform(p_copy, v_vect), - TransformFromCopy( - self.v_word, - self.velocity_label, - ) - ) - self.wait() - - def let_time_pass(self): - # Largely copying from the above scene. - # Note to self: If these are reused anymore, - # factor this out properly - rate = 0.25 - t_tracker = self.input_tracker - t_tracker.add_updater( - lambda m, dt: m.increment_value(rate * dt) - ) - - nl = self.number_line - zero_point = nl.n2p(0) - - nl_copy = nl.copy() - nl_copy.submobjects = [] - nl_copy.stretch(1000, 0, about_point=zero_point) - nl.add(nl_copy) - - # For first zoom - xs1 = range(25, 100, 25) - nl.add(*[ - nl.get_tick(x, size=1.5) - for x in xs1 - ]) - nl.add_numbers( - *xs1, - number_config={"height": 5}, - buff=2, - ) - - # For second zoom - xs2 = range(200, 1000, 200) - nl.add(*[ - nl.get_tick(x, size=15) - for x in xs2 - ]) - nl.add_numbers( - *xs2, - number_config={"height": 50}, - buff=20, - ) - - # For third zoom - xs3 = range(2000, 10000, 2000) - nl.add(*[ - nl.get_tick(x, size=150) - for x in xs3 - ]) - nl.add_numbers( - *xs3, - number_config={ - "height": 300, - }, - buff=200, - ) - for n in nl.numbers: - n[1].scale(0.5) - - # For fourth zoom - xs3 = range(20000, 100000, 20000) - nl.add(*[ - nl.get_tick(x, size=1500) - for x in xs3 - ]) - nl.add_numbers( - *xs3, - number_config={ - "height": 3000, - }, - buff=2000, - ) - for n in nl.numbers: - n[2].scale(0.5) - - self.add(self.tip) - self.add(self.output_label) - self.play(VFadeIn(self.output_label)) - for x in range(4): - self.wait_until( - lambda: self.position_vect.get_end()[0] > 0 - ) - self.play( - nl.scale, 0.1, {"about_point": zero_point}, - run_time=1, - ) - self.wait(5) - - # - def readjust_const_label(self, c_word, c_rect): - c_rect.stretch(1.2, 1, about_edge=DOWN) - c_word.next_to(c_rect, UP) - - -class NegativeConstant(ConstantEquals2): - CONFIG = { - "const": -0.5, - "const_str": "-0.5", - "const_text": "Flip and squish", - "number_line_config": { - "unit_size": 2.5, - } - } - - def construct(self): - self.setup_number_line() - self.setup_movers() - - self.add_initial_objects() - self.point_out_chain_rule() - self.show_vector_manipulation() - self.let_time_pass() - - def show_vector_manipulation(self): - p_vect = self.position_vect - v_vect = self.velocity_vect - - p_copy = p_vect.copy() - p_copy.clear_updaters() - p_copy.generate_target() - p_copy.target.shift(5 * RIGHT + 2 * UP) - - times_neg1 = TexMobject("\\times", "(\\cdot 1)") - temp_neg = times_neg1[1][-3] - neg = TexMobject("-") - neg.set_width(0.2, stretch=True) - neg.move_to(temp_neg) - temp_neg.become(neg) - - times_point5 = TexMobject("\\times", "0.5") - terms = VGroup(times_neg1, times_point5) - for term in terms: - term[1].set_color(CONST_COLOR) - terms.arrange(LEFT, buff=SMALL_BUFF) - - terms.next_to(p_copy.target.get_start(), UP) - - v_vect.add_updater( - lambda m: m.shift(SMALL_BUFF * UP) - ) - self.velocity_label.add_updater( - lambda m: m.next_to( - self.velocity_vect, - UP, buff=SMALL_BUFF, - ) - ) - - v_vect_back = v_vect.copy() - v_vect_back.set_stroke(BLACK, 3) - - self.play( - MoveToTarget(p_copy), - FadeIn(times_neg1), - ) - self.play( - Rotate( - p_copy, - angle=PI, - about_point=p_copy.get_start(), - ) - ) - self.wait() - self.play( - p_copy.scale, 0.5, - {"about_point": p_copy.get_start()}, - p_copy.match_color, v_vect, - FadeIn(times_point5) - ) - self.wait() - self.play( - ReplacementTransform(p_copy, v_vect), - FadeOut(terms), - TransformFromCopy( - self.v_word, - self.velocity_label, - ), - ) - self.add(v_vect_back, v_vect) - self.add(self.velocity_label) - - def let_time_pass(self): - nl = self.number_line - t_tracker = self.input_tracker - zero_point = nl.n2p(0) - - scale_factor = 4.5 - nl.generate_target(use_deepcopy=True) - nl.target.scale(scale_factor, about_point=zero_point) - for tick in [*nl.target.tick_marks, *nl.target.big_tick_marks]: - tick.scale(1 / scale_factor) - nl.target.tick_marks[1].scale(2) - - for number in nl.target.numbers: - number.scale(1.5 / scale_factor, about_edge=DOWN) - number.align_to(zero_point + 0.3 * UP, DOWN) - - new_ticks = VGroup(*[ - nl.get_tick(x) - for x in np.arange(0.1, 1.5, 0.1) - ]) - - self.play( - MoveToTarget(nl), - new_ticks.stretch, scale_factor, 0, - {"about_point": zero_point}, - VFadeIn(new_ticks), - ) - self.wait() - - rate = 0.25 - t_tracker.add_updater( - lambda m, dt: m.increment_value(rate * dt) - ) - self.play(VFadeIn(self.output_label)) - self.wait(20) - - # - def readjust_const_label(self, c_word, c_rect): - super().readjust_const_label(c_word, c_rect) - c_word.shift(SMALL_BUFF * DOWN) - c_word.shift(MED_SMALL_BUFF * RIGHT) - - -class ImaginaryConstant(ConstantEquals2): - CONFIG = { - "const": complex(0, 1), - "const_str": "i", - "const_text": "Rotate $90^\\circ$", - "number_line_config": { - "x_min": -5, - "unit_size": 2.5, - }, - "output_label_config": { - "show_ellipsis": False, - }, - "plane_unit_size": 2.5, - } - - def construct(self): - self.setup_number_line() - self.setup_movers() - self.add_initial_objects() - self.show_vector_manipulation() - self.transition_to_complex_plane() - self.show_vector_field() - self.show_circle() - - def setup_number_line(self): - super().setup_number_line() - nl = self.number_line - nl.shift(nl.n2p(-5) - nl.n2p(0)) - nl.shift(0.5 * DOWN) - shift_distance = (4 * nl.tick_size + nl.numbers.get_height() + SMALL_BUFF) - nl.numbers.shift(shift_distance * DOWN) - - def add_initial_objects(self): - deriv, ic = self.get_deriv_and_ic("i") - VGroup(deriv, ic).shift(0.5 * DOWN) - ic.shift(RIGHT) - self.deriv = deriv - self.ic = ic - - self.add(self.number_line) - self.add(self.position_vect) - self.add(self.position_label) - - # self.tip_label.clear_updaters() - self.tip_label.shift_vect = VectorizedPoint(0.2 * UP) - self.tip_label.add_updater( - lambda m: m.next_to( - self.position_vect.get_end(), UP, - buff=0, - index_of_submobject_to_align=0, - ).shift(m.shift_vect.get_location()) - ) - - eq = deriv.get_part_by_tex("=") - dot = deriv.get_part_by_tex("\\cdot") - eq_index = deriv.index_of_part(eq) - dot_index = deriv.index_of_part(dot) - v_term = deriv[:eq_index - 1] - c_term = deriv[eq_index + 1: dot_index] - p_term = deriv[dot_index + 1:] - - terms = VGroup(v_term, c_term, p_term) - rects = VGroup(*map(SurroundingRectangle, terms)) - colors = [VELOCITY_COLOR, CONST_COLOR, POSITION_COLOR] - labels = VGroup(*map( - TextMobject, - ["Velocity", self.const_text, "Position"] - )) - for rect, color, label in zip(rects, colors, labels): - rect.set_stroke(color, 2) - label.set_color(color) - label.next_to(rect, DOWN) - - v_label, c_label, p_label = labels - v_rect, c_rect, p_rect = rects - self.term_rects = rects - self.term_labels = labels - - randy = Randolph() - randy.next_to(deriv, DOWN, LARGE_BUFF) - point = VectorizedPoint(p_term.get_center()) - randy.add_updater(lambda r: r.look_at(point)) - - self.play( - FadeInFromDown(p_term), - VFadeIn(randy), - ) - self.play(randy.change, "confused") - self.play( - ShowCreation(p_rect), - FadeInFrom(p_label, UP) - ) - self.add(self.number_line, self.position_vect) - self.play( - Transform( - p_label.copy(), - self.tip_label.copy().clear_updaters(), - remover=True, - ), - point.move_to, self.position_vect.get_end(), - FadeIn(ic), - ) - self.add(self.tip_label) - self.play(Blink(randy)) - self.play(FadeOut(randy)) - - # Velocity - self.play( - LaggedStartMap(FadeIn, [ - v_term, eq, v_rect, v_label, - ]), - run_time=1 - ) - c_term.save_state() - c_term.replace(p_term[2]) - self.play( - Restore(c_term, path_arc=TAU / 4), - FadeIn(dot), - ) - - c_rect.stretch(1.2, 1, about_edge=DOWN) - c_label.next_to(c_rect, UP) - c_label.shift(0.5 * RIGHT) - self.c_rect = c_rect - self.c_label = c_label - self.v_label = v_label - - def show_vector_manipulation(self): - p_vect = self.position_vect - v_vect = self.velocity_vect - - p_copy = p_vect.copy() - p_copy.clear_updaters() - p_copy.generate_target() - p_copy.target.center() - p_copy.target.shift(1.5 * DOWN) - - rot_p = p_copy.target.copy() - rot_p.rotate(TAU / 4, about_point=rot_p.get_start()) - rot_p.match_style(v_vect) - - arc = Arc( - angle=TAU / 4, - radius=0.5, - arc_center=p_copy.target.get_start(), - ) - arc.add_tip(tip_length=0.1) - angle_label = TexMobject("90^\\circ") - angle_label.scale(0.5) - angle_label.next_to(arc.point_from_proportion(0.5), UR, SMALL_BUFF) - - times_i = TexMobject("\\times", "i") - times_i.set_color_by_tex("i", CONST_COLOR) - times_i.next_to(p_copy.target, UP, buff=0) - - rot_v_label = TextMobject("Velocity") - rot_v_label.set_color(VELOCITY_COLOR) - rot_v_label.add_background_rectangle() - rot_v_label.scale(0.8) - rot_v_label.curr_angle = 0 - - def update_rot_v_label(label): - label.rotate(-label.curr_angle) - label.next_to(ORIGIN, DOWN, 2 * SMALL_BUFF) - # angle = PI + self.velocity_vect.get_angle() - angle = 0 - label.rotate(angle, about_point=ORIGIN) - label.curr_angle = angle - # label.shift(self.velocity_vect.get_center()) - label.next_to( - self.velocity_vect.get_end(), - RIGHT, - buff=SMALL_BUFF - ) - - rot_v_label.add_updater(update_rot_v_label) - - self.play( - MoveToTarget(p_copy), - FadeIn(times_i), - ) - self.play( - FadeIn(self.c_rect), - FadeInFromDown(self.c_label), - ) - self.play( - ShowCreation(arc), - FadeIn(angle_label), - ApplyMethod( - times_i.next_to, rot_p, RIGHT, {"buff": 0}, - path_arc=TAU / 4, - ), - TransformFromCopy( - p_copy, rot_p, - path_arc=-TAU / 4, - ), - ) - self.wait() - - temp_rot_v_label = rot_v_label.copy() - temp_rot_v_label.clear_updaters() - temp_rot_v_label.replace(self.v_label, dim_to_match=0) - self.play( - TransformFromCopy(rot_p, v_vect), - TransformFromCopy(temp_rot_v_label, rot_v_label), - self.tip_label.shift_vect.move_to, 0.1 * UP + 0.2 * RIGHT, - ) - self.play(FadeOut(VGroup( - p_copy, arc, angle_label, - times_i, rot_p, - ))) - - self.velocity_label = rot_v_label - - def transition_to_complex_plane(self): - nl = self.number_line - background_rects = VGroup(*[ - BackgroundRectangle(rect) - for rect in self.term_rects - ]) - self.ic.add_background_rectangle() - for label in self.term_labels: - label.add_background_rectangle() - - corner_group = VGroup( - background_rects, - self.deriv, - self.term_rects, - self.term_labels, - self.ic, - ) - corner_group.generate_target() - corner_group.target.to_corner(UL) - corner_group.target[-1].next_to( - corner_group.target, DOWN, - buff=0.75, - aligned_edge=LEFT - ) - - plane = ComplexPlane() - plane.unit_size = self.plane_unit_size - plane.scale(plane.unit_size) - plane.add_coordinates() - plane.shift(DOWN + 0.25 * RIGHT) - - nl.generate_target(use_deepcopy=True) - nl.target.numbers.set_opacity(0) - nl.target.scale( - plane.unit_size / nl.unit_size - ) - nl.target.shift(plane.n2p(0) - nl.target.n2p(0)) - - plane_title = TextMobject("Complex plane") - plane_title.set_stroke(BLACK, 3, background=True) - plane_title.scale(2) - plane_title.to_corner(UR) - - self.add( - plane, - corner_group, - self.position_vect, - self.velocity_vect, - self.position_label, - self.velocity_label, - self.tip_label, - ) - plane.save_state() - plane.set_opacity(0) - self.play( - VFadeIn(background_rects), - MoveToTarget(corner_group), - MoveToTarget(nl), - ) - self.play( - Restore(plane, lag_ratio=0.01), - FadeInFromDown(plane_title) - ) - self.play(FadeOut(nl)) - self.play(FadeOut(plane_title)) - self.wait() - - self.plane = plane - self.corner_group = corner_group - - def show_vector_field(self): - plane = self.plane - temp_faders = VGroup( - self.position_vect, - self.position_label, - self.velocity_vect, - self.velocity_label, - self.tip_label, - ) - foreground = VGroup( - self.corner_group, - ) - - # Initial examples - n = 12 - zs = [ - np.exp(complex(0, k * TAU / n)) - for k in range(n) - ] - points = map(plane.n2p, zs) - vects = VGroup(*[ - Arrow( - plane.n2p(0), point, - buff=0, - color=POSITION_COLOR, - ) - for point in points - ]) - vects.set_opacity(0.75) - attached_vects = VGroup(*[ - vect.copy().shift(vect.get_vector()) - for vect in vects - ]) - attached_vects.set_color(VELOCITY_COLOR) - v_vects = VGroup(*[ - av.copy().rotate( - 90 * DEGREES, - about_point=av.get_start() - ) - for av in attached_vects - ]) - - self.play( - LaggedStartMap(GrowArrow, vects), - FadeOut(temp_faders), - ) - self.wait() - self.play( - TransformFromCopy( - vects, attached_vects, - path_arc=20 * DEGREES, - ) - ) - self.play( - ReplacementTransform( - attached_vects, - v_vects, - path_arc=90 * DEGREES, - ) - ) - self.wait() - - # Vector fields - zero_point = plane.n2p(0) - - def func(p): - vect = p - zero_point - return rotate_vector(vect, 90 * DEGREES) - - vf_config = { - # "x_min": plane.n2p(-3)[0], - # "x_max": plane.n2p(3)[0], - # "y_min": plane.c2p(0, -2)[1], - # "y_max": plane.c2p(0, 2)[1], - "max_magnitude": 8, - "opacity": 0.5, - } - vf0 = VectorField( - lambda p: np.array(p) - zero_point, - length_func=lambda x: x, - # opacity=0.5, - **vf_config, - ) - vf1 = VectorField( - func, - length_func=lambda x: x, - # opacity=0.5, - **vf_config, - ) - vf2 = VectorField( - func, - **vf_config, - ) - for vf in [vf0, vf1, vf2]: - vf.submobjects.sort( - key=lambda m: get_norm(m.get_start()) - ) - - self.play( - FadeIn(vf0, lag_ratio=0.01, run_time=3), - FadeOut(vects), - FadeOut(v_vects), - FadeOut(foreground), - ) - self.play( - Transform(vf0, vf1, path_arc=90 * DEGREES) - ) - self.play( - Transform( - vf0, vf2, - lag_ratio=0.001, - run_time=2, - ), - ) - self.play( - FadeIn(foreground) - ) - self.play(FadeIn(temp_faders)) - - self.vector_field = vf0 - self.foreground = foreground - - def show_circle(self): - t_tracker = self.input_tracker - ic = self.ic - output_label = self.output_label - plane = self.plane - tip_label = self.tip_label - - output_label.update(0) - # output_rect = BackgroundRectangle(output_label) - # output_rect.add_updater( - # lambda m: m.move_to(output_label) - # ) - # output_rect.set_opacity(0) - output_label.set_stroke(BLACK, 5, background=True) - - ic_rect = SurroundingRectangle(ic) - ic_rect.set_color(BLUE) - - circle = Circle( - radius=get_norm(plane.n2p(1) - plane.n2p(0)), - stroke_color=TEAL, - ) - circle.move_to(plane.n2p(0)) - - epii, eti = results = [ - TexMobject( - "e^{{i}" + s1 + "} = " + s2, - tex_to_color_map={ - "{i}": CONST_COLOR, - "\\pi": T_COLOR, - "\\tau": T_COLOR, - "-1": POSITION_COLOR, - "1": POSITION_COLOR, - } - ) - for s1, s2 in [("\\pi", "-1"), ("\\tau", "1")] - ] - for result in results: - rect = SurroundingRectangle(result) - rect.set_stroke(WHITE, 1) - rect.set_fill(BLACK, 0.75) - result.add_to_back(rect) - # result.set_stroke(BLACK, 5, background=True) - result.scale(2) - result.to_corner(UR) - result.save_state() - result.fade(1) - eti.saved_state.next_to(epii, DOWN, buff=MED_LARGE_BUFF) - - epii.move_to(plane.n2p(-1), LEFT) - eti.move_to(plane.n2p(1), LEFT) - - self.play(ShowCreation(ic_rect)) - self.play(FadeOut(ic_rect)) - self.play( - Transform( - ic[-1].copy(), - output_label.deepcopy().clear_updaters(), - remover=True - ), - FadeOut(self.position_label), - FadeOut(self.velocity_label), - ) - self.add(output_label) - self.wait() - - circle_copy = circle.copy() - circle_copy.save_state() - self.play( - ShowCreation(circle), - t_tracker.set_value, TAU, - run_time=TAU, - rate_func=linear, - ) - self.wait() - self.play( - t_tracker.set_value, 0, - circle.set_stroke, {"width": 1}, - ) - self.wait() - self.add(circle_copy, self.tip_label, self.output_label) - self.play( - ApplyMethod( - t_tracker.set_value, PI, - rate_func=linear, - ), - tip_label.shift_vect.move_to, 0.25 * UR, - ShowCreation( - circle_copy, - rate_func=lambda t: 0.5 * t, - ), - run_time=PI, - ) - self.wait() - self.play(Restore(epii, run_time=2)) - self.wait() - circle_copy.restore() - self.play( - ApplyMethod( - t_tracker.set_value, TAU, - rate_func=linear, - ), - tip_label.shift_vect.move_to, 0.1 * UR, - ShowCreation( - circle_copy, - rate_func=lambda t: 0.5 + 0.5 * t, - ), - run_time=PI, - ) - self.wait() - self.play(Restore(eti, run_time=2)) - self.wait() - - rate = 1 - t_tracker.add_updater( - lambda m, dt: m.increment_value(rate * dt) - ) - # self.play(VFadeOut(output_label)) - self.wait(16) - - -class PiMinuteMark(Scene): - def construct(self): - text = TexMobject( - "\\pi \\text{ minutes} \\approx \\text{3:08}" - ) - rect = SurroundingRectangle(text) - rect.set_fill(BLACK, 1) - rect.set_stroke(WHITE, 2) - - self.play( - FadeInFromDown(rect), - FadeInFromDown(text), - ) - self.wait() - - -class ReferenceWhatItMeans(PiCreatureScene): - def construct(self): - randy = self.pi_creature - - tex_config = { - "tex_to_color_map": { - "{i}": CONST_COLOR, - "\\pi": T_COLOR, - "-1": POSITION_COLOR, - }, - } - epii = TexMobject( - "e^{{i} \\pi} = -1", - **tex_config - ) - epii.scale(2) - - many_es = TexMobject("e \\cdot e \\cdots e", "= -1", **tex_config) - many_es.scale(2) - brace = Brace(many_es[0], DOWN) - pi_i = TexMobject("{i} \\pi \\text{ times}", **tex_config) - pi_i.next_to(brace, DOWN, SMALL_BUFF) - repeated_mult = VGroup(many_es, brace, pi_i) - - # arrow = Vector(2 * RIGHT) - arrow = TexMobject("\\Rightarrow") - arrow.scale(2) - - group = VGroup(epii, arrow, repeated_mult) - group.arrange( - RIGHT, buff=LARGE_BUFF, - ) - for mob in group[1:]: - mob.shift( - (epii[0].get_bottom() - mob[0].get_bottom())[1] * UP - ) - group.to_edge(UP) - - does_not_mean = TextMobject("Does \\emph{not}\\\\mean") - does_not_mean.set_color(RED) - does_not_mean.move_to(arrow) - # cross = Cross(repeated_mult) - - nonsense = TextMobject( - "\\dots because that's " - "literal nonsense!" - ) - nonsense.next_to(repeated_mult, DOWN) - nonsense.to_edge(RIGHT, buff=MED_SMALL_BUFF) - nonsense.set_color(RED) - - down_arrow = TexMobject("\\Downarrow") - down_arrow.scale(2) - down_arrow.stretch(1.5, 1) - down_arrow.set_color(GREEN) - down_arrow.next_to(epii, DOWN, LARGE_BUFF) - actually_means = TextMobject("It actually\\\\means") - actually_means.next_to(down_arrow, RIGHT) - actually_means.set_color(GREEN) - - series = TexMobject( - "{({i} \\pi )^0 \\over 0!} + ", - "{({i} \\pi )^1 \\over 1!} + ", - "{({i} \\pi )^2 \\over 2!} + ", - "{({i} \\pi )^3 \\over 3!} + ", - "{({i} \\pi )^4 \\over 4!} + ", - "\\cdots = -1", - **tex_config - ) - series.set_width(FRAME_WIDTH - 1) - series.next_to(down_arrow, DOWN, LARGE_BUFF) - series.set_x(0) - - self.add(epii) - self.play( - FadeInFrom(arrow, LEFT), - FadeInFrom(repeated_mult, 2 * LEFT), - randy.change, "maybe", - ) - self.wait() - self.play(randy.change, "confused") - self.play( - FadeOutAndShift(arrow, DOWN), - FadeInFrom(does_not_mean, UP), - ) - self.play(Write(nonsense)) - self.play(randy.change, "angry") - # self.play(ShowCreation(cross)) - self.wait() - self.play( - FadeOutAndShift(randy, DOWN), - FadeInFrom(down_arrow, UP), - FadeInFrom(actually_means, LEFT), - FadeIn(series, lag_ratio=0.1, run_time=2) - ) - self.wait() - - -class VideoWrapper(Scene): - def construct(self): - fade_rect = FullScreenFadeRectangle() - fade_rect.set_fill(DARK_GREY, 1) - screen_rects = VGroup( - ScreenRectangle(), - ScreenRectangle(), - ) - screen_rects.set_height(3) - screen_rects.set_fill(BLACK, 1) - screen_rects.set_stroke(width=0) - screen_rects.arrange(RIGHT, buff=LARGE_BUFF) - screen_rects.shift(1.25 * UP) - - boundary = VGroup(*map(AnimatedBoundary, screen_rects)) - - titles = VGroup( - TextMobject("Want more?"), - TextMobject("Learn calculus"), - ) - titles.scale(1.5) - for rect, title, in zip(screen_rects, titles): - title.next_to(rect, UP) - - self.add(fade_rect) - self.add(screen_rects) - - self.add(boundary[0]) - self.play(FadeInFromDown(titles[0])) - self.add(boundary[1]) - self.play(FadeInFromDown(titles[1])) - self.wait(18) - - -class Thumbnail(Scene): - def construct(self): - epii = TexMobject( - "e^{{i} \\pi} = -1", - tex_to_color_map={ - "{i}": CONST_COLOR, - "\\pi": T_COLOR, - "-1": POSITION_COLOR, - } - ) - epii.set_width(8) - epii.to_edge(UP) - - words = VGroup( - TextMobject("in"), - TextMobject( - "in", - "3.14", " minutes" - ), - ) - # words.set_width(FRAME_WIDTH - 4) - words.set_stroke(BLACK, 6, background=True) - words.set_width(FRAME_WIDTH - 2) - words.arrange(DOWN, buff=LARGE_BUFF) - words.next_to(epii, DOWN, LARGE_BUFF) - words[1].set_color_by_tex("3.14", YELLOW) - words.shift(-words[0].get_center()) - words[0].set_opacity(0) - - unit_size = 1.5 - plane = ComplexPlane() - plane.scale(unit_size) - plane.add_coordinates() - - circle = Circle( - radius=unit_size, - color=YELLOW, - ) - # half_circle = VMobject() - # half_circle.pointwise_become_partial(circle, 0, 0.5) - # half_circle.set_stroke(RED, 6) - p_vect = Arrow( - plane.n2p(0), - plane.n2p(1), - buff=0, - color=POSITION_COLOR, - ) - v_vect = Arrow( - plane.n2p(1), - plane.n2p(complex(1, 1)), - buff=0, - color=VELOCITY_COLOR, - ) - vects = VGroup(p_vect, v_vect) - vects.set_stroke(width=10) - - self.add(plane) - self.add(circle) - self.add(vects) - self.add(epii) - self.add(words) diff --git a/from_3b1b/active/diffyq/solve_pendulum_ode_sample_code.py b/from_3b1b/active/diffyq/solve_pendulum_ode_sample_code.py deleted file mode 100644 index e7076c3c..00000000 --- a/from_3b1b/active/diffyq/solve_pendulum_ode_sample_code.py +++ /dev/null @@ -1,63 +0,0 @@ -import numpy as np - -# Physical constants -g = 9.8 -L = 2 -mu = 0.1 - -THETA_0 = np.pi / 3 # 60 degrees -THETA_DOT_0 = 0 # No initial angular velocity - -# Definition of ODE -def get_theta_double_dot(theta, theta_dot): - return -mu * theta_dot - (g / L) * np.sin(theta) - - -# Solution to the differential equation -def theta(t): - # Initialize changing values - theta = THETA_0 - theta_dot = THETA_DOT_0 - delta_t = 0.01 # Some time step - for time in np.arange(0, t, delta_t): - # Take many little time steps of size delta_t - # until the total time is the function's input - theta_double_dot = get_theta_double_dot( - theta, theta_dot - ) - theta += theta_dot * delta_t - theta_dot += theta_double_dot * delta_t - return theta - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/active/ldm.py b/from_3b1b/active/ldm.py deleted file mode 100644 index d6880eb4..00000000 --- a/from_3b1b/active/ldm.py +++ /dev/null @@ -1,1445 +0,0 @@ -from manimlib.imports import * -from from_3b1b.active.bayes.beta_helpers import * -import math - - -class StreamIntro(Scene): - def construct(self): - # Add logo - logo = Logo() - spikes = VGroup(*[ - spike - for layer in logo.spike_layers - for spike in layer - ]) - self.add(*logo.family_members_with_points()) - - # Add label - label = TextMobject("The lesson will\\\\begin shortly") - label.scale(2) - label.next_to(logo, DOWN) - self.add(label) - - self.camera.frame.move_to(DOWN) - - for spike in spikes: - point = spike.get_start() - spike.angle = angle_of_vector(point) - - anims = [] - for spike in spikes: - anims.append(Rotate( - spike, spike.angle * 28 * 2, - about_point=ORIGIN, - rate_func=linear, - )) - self.play(*anims, run_time=60 * 5) - self.wait(20) - - -class OldStreamIntro(Scene): - def construct(self): - morty = Mortimer() - morty.flip() - morty.set_height(2) - morty.to_corner(DL) - self.play(PiCreatureSays( - morty, "The lesson will\\\\begin soon.", - bubble_kwargs={ - "height": 2, - "width": 3, - }, - target_mode="hooray", - )) - bound = AnimatedBoundary(morty.bubble.content, max_stroke_width=1) - self.add(bound, morty.bubble, morty.bubble.content) - self.remove(morty.bubble.content) - morty.bubble.set_fill(opacity=0) - - self.camera.frame.scale(0.6, about_edge=DL) - - self.play(Blink(morty)) - self.wait(5) - self.play(Blink(morty)) - self.wait(3) - return - - text = TextMobject("The lesson will\\\\begin soon.") - text.set_height(1.5) - text.to_corner(DL, buff=LARGE_BUFF) - self.add(text) - - -class QuadraticFormula(TeacherStudentsScene): - def construct(self): - formula = TexMobject( - "\\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}", - ) - formula.next_to(self.students, UP, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - self.add(formula) - - self.change_student_modes( - "angry", "tired", "sad", - look_at_arg=formula, - ) - self.teacher_says( - "It doesn't have\\\\to be this way.", - bubble_kwargs={ - "width": 4, - "height": 3, - } - ) - self.wait(5) - self.change_student_modes( - "pondering", "thinking", "erm", - look_at_arg=formula - ) - self.wait(12) - - -class SimplerQuadratic(Scene): - def construct(self): - tex = TexMobject("m \\pm \\sqrt{m^2 - p}") - tex.set_stroke(BLACK, 12, background=True) - tex.scale(1.5) - self.add(tex) - - -class CosGraphs(Scene): - def construct(self): - axes = Axes( - x_min=-0.75 * TAU, - x_max=0.75 * TAU, - y_min=-1.5, - y_max=1.5, - x_axis_config={ - "tick_frequency": PI / 4, - "include_tip": False, - }, - y_axis_config={ - "tick_frequency": 0.5, - "include_tip": False, - "unit_size": 1.5, - } - ) - - graph1 = axes.get_graph(np.cos) - graph2 = axes.get_graph(lambda x: np.cos(x)**2) - - graph1.set_stroke(YELLOW, 5) - graph2.set_stroke(BLUE, 5) - - label1 = TexMobject("\\cos(x)") - label2 = TexMobject("\\cos^2(x)") - - label1.match_color(graph1) - label1.set_height(0.75) - label1.next_to(axes.input_to_graph_point(-PI, graph1), DOWN) - - label2.match_color(graph2) - label2.set_height(0.75) - label2.next_to(axes.input_to_graph_point(PI, graph2), UP) - - for mob in [graph1, graph2, label1, label2]: - mc = mob.copy() - mc.set_stroke(BLACK, 10, background=True) - self.add(mc) - - self.add(axes) - self.add(graph1) - self.add(graph2) - self.add(label1) - self.add(label2) - - self.embed() - - -class SineWave(Scene): - def construct(self): - w_axes = self.get_wave_axes() - square, circle, c_axes = self.get_edge_group() - - self.add(w_axes) - self.add(square, circle, c_axes) - - theta_tracker = ValueTracker(0) - c_dot = Dot(color=YELLOW) - c_line = Line(DOWN, UP, color=GREEN) - w_dot = Dot(color=YELLOW) - w_line = Line(DOWN, UP, color=GREEN) - - def update_c_dot(dot, axes=c_axes, tracker=theta_tracker): - theta = tracker.get_value() - dot.move_to(axes.c2p( - np.cos(theta), - np.sin(theta), - )) - - def update_c_line(line, axes=c_axes, tracker=theta_tracker): - theta = tracker.get_value() - x = np.cos(theta) - y = np.sin(theta) - if y == 0: - y = 1e-6 - line.put_start_and_end_on( - axes.c2p(x, 0), - axes.c2p(x, y), - ) - - def update_w_dot(dot, axes=w_axes, tracker=theta_tracker): - theta = tracker.get_value() - dot.move_to(axes.c2p(theta, np.sin(theta))) - - def update_w_line(line, axes=w_axes, tracker=theta_tracker): - theta = tracker.get_value() - x = theta - y = np.sin(theta) - if y == 0: - y = 1e-6 - line.put_start_and_end_on( - axes.c2p(x, 0), - axes.c2p(x, y), - ) - - def get_partial_circle(circle=circle, tracker=theta_tracker): - result = circle.copy() - theta = tracker.get_value() - result.pointwise_become_partial( - circle, 0, clip(theta / TAU, 0, 1), - ) - result.set_stroke(RED, width=3) - return result - - def get_partial_wave(axes=w_axes, tracker=theta_tracker): - theta = tracker.get_value() - graph = axes.get_graph(np.sin, x_min=0, x_max=theta, step_size=0.025) - graph.set_stroke(BLUE, 3) - return graph - - def get_h_line(axes=w_axes, tracker=theta_tracker): - theta = tracker.get_value() - return Line( - axes.c2p(0, 0), - axes.c2p(theta, 0), - stroke_color=RED - ) - - c_dot.add_updater(update_c_dot) - c_line.add_updater(update_c_line) - w_dot.add_updater(update_w_dot) - w_line.add_updater(update_w_line) - partial_circle = always_redraw(get_partial_circle) - partial_wave = always_redraw(get_partial_wave) - h_line = always_redraw(get_h_line) - - self.add(partial_circle) - self.add(partial_wave) - self.add(h_line) - self.add(c_line, c_dot) - self.add(w_line, w_dot) - - sin_label = TexMobject( - "\\sin\\left(\\theta\\right)", - tex_to_color_map={"\\theta": RED} - ) - sin_label.next_to(w_axes.get_top(), UR) - self.add(sin_label) - - self.play( - theta_tracker.set_value, 1.25 * TAU, - run_time=15, - rate_func=linear, - ) - - def get_wave_axes(self): - wave_axes = Axes( - x_min=0, - x_max=1.25 * TAU, - y_min=-1.0, - y_max=1.0, - x_axis_config={ - "tick_frequency": TAU / 8, - "unit_size": 1.0, - }, - y_axis_config={ - "tick_frequency": 0.5, - "include_tip": False, - "unit_size": 1.5, - } - ) - wave_axes.y_axis.add_numbers( - -1, 1, number_config={"num_decimal_places": 1} - ) - wave_axes.to_edge(RIGHT, buff=MED_SMALL_BUFF) - - pairs = [ - (PI / 2, "\\frac{\\pi}{2}"), - (PI, "\\pi"), - (3 * PI / 2, "\\frac{3\\pi}{2}"), - (2 * PI, "2\\pi"), - ] - syms = VGroup() - for val, tex in pairs: - sym = TexMobject(tex) - sym.scale(0.5) - sym.next_to(wave_axes.c2p(val, 0), DOWN, MED_SMALL_BUFF) - syms.add(sym) - wave_axes.add(syms) - - theta = TexMobject("\\theta") - theta.set_color(RED) - theta.next_to(wave_axes.x_axis.get_end(), UP) - wave_axes.add(theta) - - return wave_axes - - def get_edge_group(self): - axes_max = 1.25 - radius = 1.5 - axes = Axes( - x_min=-axes_max, - x_max=axes_max, - y_min=-axes_max, - y_max=axes_max, - axis_config={ - "tick_frequency": 0.5, - "include_tip": False, - "numbers_with_elongated_ticks": [-1, 1], - "tick_size": 0.05, - "unit_size": radius, - }, - ) - axes.to_edge(LEFT, buff=MED_LARGE_BUFF) - - background = SurroundingRectangle(axes, buff=MED_SMALL_BUFF) - background.set_stroke(WHITE, 1) - background.set_fill(GREY_E, 1) - - circle = Circle(radius=radius) - circle.move_to(axes) - circle.set_stroke(WHITE, 1) - - nums = VGroup() - for u in 1, -1: - num = Integer(u) - num.set_height(0.2) - num.set_stroke(BLACK, 3, background=True) - num.next_to(axes.c2p(u, 0), DOWN + u * RIGHT, SMALL_BUFF) - nums.add(num) - - axes.add(nums) - - return background, circle, axes - - -class CosWave(SineWave): - CONFIG = { - "include_square": False, - } - - def construct(self): - w_axes = self.get_wave_axes() - square, circle, c_axes = self.get_edge_group() - - self.add(w_axes) - self.add(square, circle, c_axes) - - theta_tracker = ValueTracker(0) - c_dot = Dot(color=YELLOW) - c_line = Line(DOWN, UP, color=GREEN) - w_dot = Dot(color=YELLOW) - w_line = Line(DOWN, UP, color=GREEN) - - def update_c_dot(dot, axes=c_axes, tracker=theta_tracker): - theta = tracker.get_value() - dot.move_to(axes.c2p( - np.cos(theta), - np.sin(theta), - )) - - def update_c_line(line, axes=c_axes, tracker=theta_tracker): - theta = tracker.get_value() - x = np.cos(theta) - y = np.sin(theta) - line.set_points_as_corners([ - axes.c2p(0, y), - axes.c2p(x, y), - ]) - - def update_w_dot(dot, axes=w_axes, tracker=theta_tracker): - theta = tracker.get_value() - dot.move_to(axes.c2p(theta, np.cos(theta))) - - def update_w_line(line, axes=w_axes, tracker=theta_tracker): - theta = tracker.get_value() - x = theta - y = np.cos(theta) - if y == 0: - y = 1e-6 - line.set_points_as_corners([ - axes.c2p(x, 0), - axes.c2p(x, y), - ]) - - def get_partial_circle(circle=circle, tracker=theta_tracker): - result = circle.copy() - theta = tracker.get_value() - result.pointwise_become_partial( - circle, 0, clip(theta / TAU, 0, 1), - ) - result.set_stroke(RED, width=3) - return result - - def get_partial_wave(axes=w_axes, tracker=theta_tracker): - theta = tracker.get_value() - graph = axes.get_graph(np.cos, x_min=0, x_max=theta, step_size=0.025) - graph.set_stroke(PINK, 3) - return graph - - def get_h_line(axes=w_axes, tracker=theta_tracker): - theta = tracker.get_value() - return Line( - axes.c2p(0, 0), - axes.c2p(theta, 0), - stroke_color=RED - ) - - def get_square(line=c_line): - square = Square() - square.set_stroke(WHITE, 1) - square.set_fill(MAROON_B, opacity=0.5) - square.match_width(line) - square.move_to(line, DOWN) - return square - - def get_square_graph(axes=w_axes, tracker=theta_tracker): - theta = tracker.get_value() - graph = axes.get_graph( - lambda x: np.cos(x)**2, x_min=0, x_max=theta, step_size=0.025 - ) - graph.set_stroke(MAROON_B, 3) - return graph - - c_dot.add_updater(update_c_dot) - c_line.add_updater(update_c_line) - w_dot.add_updater(update_w_dot) - w_line.add_updater(update_w_line) - h_line = always_redraw(get_h_line) - partial_circle = always_redraw(get_partial_circle) - partial_wave = always_redraw(get_partial_wave) - - self.add(partial_circle) - self.add(partial_wave) - self.add(h_line) - self.add(c_line, c_dot) - self.add(w_line, w_dot) - - if self.include_square: - self.add(always_redraw(get_square)) - self.add(always_redraw(get_square_graph)) - - cos_label = TexMobject( - "\\cos\\left(\\theta\\right)", - tex_to_color_map={"\\theta": RED} - ) - cos_label.next_to(w_axes.get_top(), UR) - self.add(cos_label) - - self.play( - theta_tracker.set_value, 1.25 * TAU, - run_time=15, - rate_func=linear, - ) - - -class CosSquare(CosWave): - CONFIG = { - "include_square": True - } - - -class ComplexNumberPreview(Scene): - def construct(self): - plane = ComplexPlane(axis_config={"stroke_width": 4}) - plane.add_coordinates() - - z = complex(2, 1) - dot = Dot() - dot.move_to(plane.n2p(z)) - label = TexMobject("2+i") - label.set_color(YELLOW) - dot.set_color(YELLOW) - label.next_to(dot, UR, SMALL_BUFF) - label.set_stroke(BLACK, 5, background=True) - - line = Line(plane.n2p(0), plane.n2p(z)) - arc = Arc(start_angle=0, angle=np.log(z).imag, radius=0.5) - - self.add(plane) - self.add(line, arc) - self.add(dot) - self.add(label) - - self.embed() - - -class ComplexMultiplication(Scene): - def construct(self): - # Add plane - plane = ComplexPlane() - plane.add_coordinates() - - z = complex(2, 1) - z_dot = Dot(color=PINK) - z_dot.move_to(plane.n2p(z)) - z_label = TexMobject("z") - z_label.next_to(z_dot, UR, buff=0.5 * SMALL_BUFF) - z_label.match_color(z_dot) - - self.add(plane) - self.add(z_dot) - self.add(z_label) - - # Show 1 - one_vect = Vector(RIGHT) - one_vect.set_color(YELLOW) - one_vect.target = Vector(plane.n2p(z)) - one_vect.target.match_style(one_vect) - - z_rhs = TexMobject("=", "z \\cdot 1") - z_rhs[1].match_color(one_vect) - z_rhs.next_to(z_label, RIGHT, 1.5 * SMALL_BUFF, aligned_edge=DOWN) - z_rhs.set_stroke(BLACK, 3, background=True) - - one_label, i_label = [l for l in plane.coordinate_labels if l.get_value() == 1] - - self.play(GrowArrow(one_vect)) - self.wait() - self.add(one_vect, z_dot) - self.play( - MoveToTarget(one_vect), - TransformFromCopy(one_label, z_rhs), - ) - self.wait() - - # Show i - i_vect = Vector(UP, color=GREEN) - zi_point = plane.n2p(z * complex(0, 1)) - i_vect.target = Vector(zi_point) - i_vect.target.match_style(i_vect) - i_vect_label = TexMobject("z \\cdot i") - i_vect_label.match_color(i_vect) - i_vect_label.set_stroke(BLACK, 3, background=True) - i_vect_label.next_to(zi_point, UL, SMALL_BUFF) - - self.play(GrowArrow(i_vect)) - self.wait() - self.play( - MoveToTarget(i_vect), - TransformFromCopy(i_label, i_vect_label), - run_time=1, - ) - self.wait() - - self.play( - TransformFromCopy(one_vect, i_vect.target, path_arc=-90 * DEGREES), - ) - self.wait() - - # Transform plane - plane.generate_target() - for mob in plane.target.family_members_with_points(): - if isinstance(mob, Line): - mob.set_stroke(GREY, opacity=0.5) - new_plane = ComplexPlane(faded_line_ratio=0) - - self.remove(plane) - self.add(plane, new_plane, *self.mobjects) - - new_plane.generate_target() - new_plane.target.apply_complex_function(lambda w, z=z: w * z) - - self.play( - MoveToTarget(plane), - MoveToTarget(new_plane), - run_time=6, - rate_func=there_and_back_with_pause - ) - self.wait() - - # Show Example Point - w = complex(2, -1) - w_dot = Dot(plane.n2p(w), color=WHITE) - one_vects = VGroup(*[Vector(RIGHT) for x in range(2)]) - one_vects.arrange(RIGHT, buff=0) - one_vects.move_to(plane.n2p(0), LEFT) - one_vects.set_color(YELLOW) - new_i_vect = Vector(DOWN) - new_i_vect.move_to(plane.n2p(2), UP) - new_i_vect.set_color(GREEN) - vects = VGroup(*one_vects, new_i_vect) - vects.set_opacity(0.8) - - w_group = VGroup(*vects, w_dot) - w_group.target = VGroup( - one_vect.copy().set_opacity(0.8), - one_vect.copy().shift(plane.n2p(z)).set_opacity(0.8), - i_vect.copy().rotate(PI, about_point=ORIGIN).shift(2 * plane.n2p(z)).set_opacity(0.8), - Dot(plane.n2p(w * z), color=WHITE) - ) - - self.play(FadeInFromLarge(w_dot)) - self.wait() - self.play(ShowCreation(vects)) - self.wait() - - self.play( - MoveToTarget(plane), - MoveToTarget(new_plane), - MoveToTarget(w_group), - run_time=2, - path_arc=np.log(z).imag, - ) - self.wait() - - -class RotatePiCreature(Scene): - def construct(self): - randy = Randolph(mode="thinking") - randy.set_height(6) - - plane = ComplexPlane(x_min=-12, x_max=12) - plane.add_coordinates() - - self.camera.frame.move_to(3 * RIGHT) - - self.add(randy) - self.wait() - self.play(Rotate(randy, 30 * DEGREES, run_time=3)) - self.wait() - self.play(Rotate(randy, -30 * DEGREES)) - - self.add(plane, randy) - self.play( - ShowCreation(plane), - randy.set_opacity, 0.75, - ) - self.wait() - - dots = VGroup() - for mob in randy.family_members_with_points(): - for point in mob.get_anchors(): - dot = Dot(point) - dot.set_height(0.05) - dots.add(dot) - - self.play(ShowIncreasingSubsets(dots)) - self.wait() - - label = VGroup( - TexMobject("(x + iy)"), - Vector(DOWN), - TexMobject("(\\cos(30^\\circ) + i\\sin(30^\\circ))", "(x + iy)"), - ) - label[2][0].set_color(YELLOW) - label.arrange(DOWN) - label.to_corner(DR) - label.shift(3 * RIGHT) - - for mob in label: - mob.add_background_rectangle() - - self.play(FadeIn(label)) - self.wait() - - randy.add(dots) - self.play(Rotate(randy, 30 * DEGREES), run_time=3) - self.wait() - - -class ExpMeaning(Scene): - CONFIG = { - "include_circle": True - } - - def construct(self): - # Plane - plane = ComplexPlane(y_min=-6, y_max=6) - plane.shift(1.5 * DOWN) - plane.add_coordinates() - if self.include_circle: - circle = Circle(radius=1) - circle.set_stroke(RED, 1) - circle.move_to(plane.n2p(0)) - plane.add(circle) - - # Equation - equation = TexMobject( - "\\text{exp}(i\\theta) = ", - "1 + ", - "i\\theta + ", - "{(i\\theta)^2 \\over 2} + ", - "{(i\\theta)^3 \\over 6} + ", - "{(i\\theta)^4 \\over 24} + ", - "\\cdots", - tex_to_color_map={ - "\\theta": YELLOW, - "i": GREEN, - }, - ) - equation.add_background_rectangle(buff=MED_SMALL_BUFF, opacity=1) - equation.to_edge(UL, buff=0) - - # Label - theta_tracker = ValueTracker(0) - theta_label = VGroup( - TexMobject("\\theta = "), - DecimalNumber(0, num_decimal_places=4) - ) - theta_decimal = theta_label[1] - theta_decimal.add_updater( - lambda m, tt=theta_tracker: m.set_value(tt.get_value()) - ) - theta_label.arrange(RIGHT, buff=SMALL_BUFF) - theta_label.set_color(YELLOW) - theta_label.add_to_back(BackgroundRectangle( - theta_label, - buff=MED_SMALL_BUFF, - fill_opacity=1, - )) - theta_label.next_to(equation, DOWN, aligned_edge=LEFT, buff=0) - - # Vectors - def get_vectors(n_vectors=20, plane=plane, tracker=theta_tracker): - last_tip = plane.n2p(0) - z = complex(0, tracker.get_value()) - vects = VGroup() - colors = color_gradient([GREEN, YELLOW, RED], 6) - for i, color in zip(range(n_vectors), it.cycle(colors)): - vect = Vector(complex_to_R3(z**i / math.factorial(i))) - vect.set_color(color) - vect.shift(last_tip) - last_tip = vect.get_end() - vects.add(vect) - return vects - - vectors = always_redraw(get_vectors) - dot = Dot() - dot.set_height(0.03) - dot.add_updater(lambda m, vs=vectors: m.move_to(vs[-1].get_end())) - - self.add(plane) - self.add(vectors) - self.add(dot) - self.add(equation) - self.add(theta_label) - - self.play( - theta_tracker.set_value, 1, - run_time=3, - rate_func=smooth, - ) - self.wait() - for target in PI, TAU: - self.play( - theta_tracker.set_value, target, - run_time=10, - ) - self.wait() - - self.embed() - - -class ExpMeaningWithoutCircle(ExpMeaning): - CONFIG = { - "include_circle": False, - } - - -class PositionAndVelocityExample(Scene): - def construct(self): - plane = NumberPlane() - - self.add(plane) - - self.embed() - - -class EulersFormula(Scene): - def construct(self): - kw = {"tex_to_color_map": {"\\theta": YELLOW}} - formula = TexMobject( - "&e^{i\\theta} = \\\\ &\\cos\\left(\\theta\\right) + i\\cdot\\sin\\left(\\theta\\right)", - )[0] - formula[:4].scale(2, about_edge=UL) - formula[:4].shift(SMALL_BUFF * RIGHT + MED_LARGE_BUFF * UP) - VGroup(formula[2], formula[8], formula[17]).set_color(YELLOW) - formula.scale(1.5) - formula.set_stroke(BLACK, 5, background=True) - self.add(formula) - - -class EtoILimit(Scene): - def construct(self): - tex = TexMobject( - "\\lim_{n \\to \\infty} \\left(1 + \\frac{it}{n}\\right)^n", - )[0] - VGroup(tex[3], tex[12], tex[14]).set_color(YELLOW) - tex[9].set_color(BLUE) - tex.scale(1.5) - tex.set_stroke(BLACK, 5, background=True) - # self.add(tex) - - text = TextMobject("Interest rate\\\\of ", "$\\sqrt{-1}$") - text[1].set_color(BLUE) - text.scale(1.5) - text.set_stroke(BLACK, 5, background=True) - self.add(text) - - -class ImaginaryInterestRates(Scene): - def construct(self): - plane = ComplexPlane(x_min=-20, x_max=20, y_min=-20, y_max=20) - plane.add_coordinates() - circle = Circle(radius=1) - circle.set_stroke(YELLOW, 1) - self.add(plane, circle) - - frame = self.camera.frame - frame.save_state() - frame.generate_target() - frame.target.set_width(25) - frame.target.move_to(8 * RIGHT + 2 * DOWN) - - dt_tracker = ValueTracker(1) - - def get_vectors(tracker=dt_tracker, plane=plane, T=8): - dt = tracker.get_value() - last_z = 1 - vects = VGroup() - for t in np.arange(0, T, dt): - next_z = last_z + complex(0, 1) * last_z * dt - vects.add(Arrow( - plane.n2p(last_z), - plane.n2p(next_z), - buff=0, - )) - last_z = next_z - vects.set_submobject_colors_by_gradient(YELLOW, GREEN, BLUE) - return vects - - vects = get_vectors() - - line = Line() - line.add_updater(lambda m, v=vects: m.put_start_and_end_on( - ORIGIN, v[-1].get_start() if len(v) > 0 else RIGHT, - )) - - self.add(line) - self.play( - ShowIncreasingSubsets( - vects, - rate_func=linear, - int_func=np.ceil, - ), - MoveToTarget( - frame, - rate_func=squish_rate_func(smooth, 0.5, 1), - ), - run_time=8, - ) - self.wait() - self.play(FadeOut(line)) - - self.remove(vects) - vects = always_redraw(get_vectors) - self.add(vects) - self.play( - Restore(frame), - dt_tracker.set_value, 0.2, - run_time=5, - ) - self.wait() - self.play(dt_tracker.set_value, 0.01, run_time=3) - vects.clear_updaters() - self.wait() - - theta_tracker = ValueTracker(0) - - def get_arc(tracker=theta_tracker): - theta = tracker.get_value() - arc = Arc( - radius=1, - stroke_width=3, - stroke_color=RED, - start_angle=0, - angle=theta - ) - return arc - - arc = always_redraw(get_arc) - dot = Dot() - dot.add_updater(lambda m, arc=arc: m.move_to(arc.get_end())) - - label = VGroup( - DecimalNumber(0, num_decimal_places=3), - TextMobject("Years") - ) - label.arrange(RIGHT, aligned_edge=DOWN) - label.move_to(3 * LEFT + 1.5 * UP) - - label[0].set_color(RED) - label[0].add_updater(lambda m, tt=theta_tracker: m.set_value(tt.get_value())) - - self.add(BackgroundRectangle(label), label, arc, dot) - for n in range(1, 5): - target = n * PI / 2 - self.play( - theta_tracker.set_value, target, - run_time=3 - ) - self.wait(2) - - -class Logs(Scene): - def construct(self): - log = TexMobject( - "&\\text{log}(ab) = \\\\ &\\text{log}(a) + \\text{log}(b)", - tex_to_color_map={"a": BLUE, "b": YELLOW}, - alignment="", - ) - - log.scale(1.5) - log.set_stroke(BLACK, 5, background=True) - - self.add(log) - - -class LnX(Scene): - def construct(self): - sym = TexMobject("\\ln(x)") - sym.scale(3) - sym.shift(UP) - sym.set_stroke(BLACK, 5, background=True) - - word = TextMobject("Natural?") - word.scale(1.5) - word.set_color(YELLOW) - word.set_stroke(BLACK, 5, background=True) - word.next_to(sym, DOWN, buff=0.5) - arrow = Arrow(word.get_top(), sym[0][1].get_bottom()) - - self.add(sym, word, arrow) - - -class HarmonicSum(Scene): - def construct(self): - axes = Axes( - x_min=0, - x_max=13, - y_min=0, - y_max=1.25, - y_axis_config={ - "unit_size": 4, - "tick_frequency": 0.25, - } - ) - axes.to_corner(DL, buff=1) - axes.x_axis.add_numbers() - axes.y_axis.add_numbers( - *np.arange(0.25, 1.25, 0.25), - number_config={"num_decimal_places": 2}, - ) - self.add(axes) - - graph = axes.get_graph(lambda x: 1 / x, x_min=0.1, x_max=15) - graph_fill = graph.copy() - graph_fill.add_line_to(axes.c2p(15, 0)) - graph_fill.add_line_to(axes.c2p(1, 0)) - graph_fill.add_line_to(axes.c2p(1, 1)) - graph.set_stroke(WHITE, 3) - graph_fill.set_fill(BLUE_E, 0.5) - graph_fill.set_stroke(width=0) - self.add(graph, graph_fill) - - bars = VGroup() - bar_labels = VGroup() - for x in range(1, 15): - line = Line(axes.c2p(x, 0), axes.c2p(x + 1, 1 / x)) - bar = Rectangle() - bar.set_fill(GREEN_E, 1) - bar.replace(line, stretch=True) - bars.add(bar) - - label = TexMobject(f"1 \\over {x}") - label.set_height(0.7) - label.next_to(bar, UP, SMALL_BUFF) - bar_labels.add(label) - - bars.set_submobject_colors_by_gradient(GREEN_C, GREEN_E) - bars.set_stroke(WHITE, 1) - bars.set_fill(opacity=0.25) - - self.add(bars) - self.add(bar_labels) - - - self.embed() - - -class PowerTower(Scene): - def construct(self): - mob = TexMobject("4 = x^{x^{{x^{x^{x^{\cdot^{\cdot^{\cdot}}}}}}}}") - mob[0][-1].shift(0.1 * DL) - mob[0][-2].shift(0.05 * DL) - - mob.set_height(4) - mob.set_stroke(BLACK, 5, background=True) - - self.add(mob) - - -class ItoTheI(Scene): - def construct(self): - tex = TexMobject("i^i") - # tex = TexMobject("\\sqrt{-1}^{\\sqrt{-1}}") - tex.set_height(3) - tex.set_stroke(BLACK, 8, background=True) - self.add(tex) - - -class ComplexExponentialPlay(Scene): - def setup(self): - self.transform_alpha = 0 - - def construct(self): - # Plane - plane = ComplexPlane( - x_min=-2 * FRAME_WIDTH, - x_max=2 * FRAME_WIDTH, - y_min=-2 * FRAME_HEIGHT, - y_max=2 * FRAME_HEIGHT, - ) - plane.add_coordinates() - self.add(plane) - - # R Dot - r_dot = Dot(color=YELLOW) - - def update_r_dot(dot, point_tracker=self.mouse_drag_point): - point = point_tracker.get_location() - if abs(point[0]) < 0.1: - point[0] = 0 - if abs(point[1]) < 0.1: - point[1] = 0 - dot.move_to(point) - - r_dot.add_updater(update_r_dot) - self.mouse_drag_point.move_to(plane.n2p(1)) - - # Transformed sample dots - def func(z, dot=r_dot, plane=plane): - r = plane.p2n(dot.get_center()) - result = np.exp(r * z) - if abs(result) > 20: - result *= 20 / abs(result) - return result - - sample_dots = VGroup() - dot_template = Dot(radius=0.05) - dot_template.set_opacity(0.8) - spacing = 0.05 - for x in np.arange(-7, 7, spacing): - dot = dot_template.copy() - dot.set_color(TEAL) - dot.z = x - dot.move_to(plane.n2p(dot.z)) - sample_dots.add(dot) - for y in np.arange(-6, 6, spacing): - dot = dot_template.copy() - dot.set_color(MAROON) - dot.z = complex(0, y) - dot.move_to(plane.n2p(dot.z)) - sample_dots.add(dot) - - special_values = [1, complex(0, 1), -1, complex(0, -1)] - special_dots = VGroup(*[ - list(filter(lambda d: abs(d.z - x) < 0.01, sample_dots))[0] - for x in special_values - ]) - for dot in special_dots: - dot.set_fill(opacity=1) - dot.scale(1.2) - dot.set_stroke(WHITE, 2) - - sample_dots.save_state() - - def update_sample(sample, f=func, plane=plane, scene=self): - sample.restore() - sample.apply_function_to_submobject_positions( - lambda p: interpolate( - p, - plane.n2p(f(plane.p2n(p))), - scene.transform_alpha, - ) - ) - return sample - - sample_dots.add_updater(update_sample) - - # Sample lines - x_line = Line(plane.n2p(plane.x_min), plane.n2p(plane.x_max)) - y_line = Line(plane.n2p(plane.y_min), plane.n2p(plane.y_max)) - y_line.rotate(90 * DEGREES) - x_line.set_color(GREEN) - y_line.set_color(PINK) - axis_lines = VGroup(x_line, y_line) - for line in axis_lines: - line.insert_n_curves(50) - axis_lines.save_state() - - def update_axis_liens(lines=axis_lines, f=func, plane=plane, scene=self): - lines.restore() - lines.apply_function( - lambda p: interpolate( - p, - plane.n2p(f(plane.p2n(p))), - scene.transform_alpha, - ) - ) - lines.make_smooth() - - axis_lines.add_updater(update_axis_liens) - - # Labels - labels = VGroup( - TexMobject("f(1)"), - TexMobject("f(i)"), - TexMobject("f(-1)"), - TexMobject("f(-i)"), - ) - for label, dot in zip(labels, special_dots): - label.set_height(0.3) - label.match_color(dot) - label.set_stroke(BLACK, 3, background=True) - label.add_background_rectangle(opacity=0.5) - - def update_labels(labels, dots=special_dots, scene=self): - for label, dot in zip(labels, dots): - label.next_to(dot, UR, 0.5 * SMALL_BUFF) - label.set_opacity(self.transform_alpha) - - labels.add_updater(update_labels) - - # Titles - title = TexMobject( - "f(x) =", "\\text{exp}(r\\cdot x)", - tex_to_color_map={"r": YELLOW} - ) - title.to_corner(UL) - title.set_stroke(BLACK, 5, background=True) - brace = Brace(title[1:], UP, buff=SMALL_BUFF) - e_pow = TexMobject("e^{rx}", tex_to_color_map={"r": YELLOW}) - e_pow.add_background_rectangle() - e_pow.next_to(brace, UP, buff=SMALL_BUFF) - title.add(brace, e_pow) - - r_eq = VGroup( - TexMobject("r=", tex_to_color_map={"r": YELLOW}), - DecimalNumber(1) - ) - r_eq.arrange(RIGHT, aligned_edge=DOWN) - r_eq.next_to(title, DOWN, aligned_edge=LEFT) - r_eq[0].set_stroke(BLACK, 5, background=True) - r_eq[1].set_color(YELLOW) - r_eq[1].add_updater(lambda m: m.set_value(plane.p2n(r_dot.get_center()))) - - self.add(title) - self.add(r_eq) - - # self.add(axis_lines) - self.add(sample_dots) - self.add(r_dot) - self.add(labels) - - # Animations - def update_transform_alpha(mob, alpha, scene=self): - scene.transform_alpha = alpha - - frame = self.camera.frame - frame.set_height(10) - r_dot.clear_updaters() - r_dot.move_to(plane.n2p(1)) - - self.play( - UpdateFromAlphaFunc( - VectorizedPoint(), - update_transform_alpha, - ) - ) - self.play(r_dot.move_to, plane.n2p(2)) - self.wait() - self.play(r_dot.move_to, plane.n2p(PI)) - self.wait() - self.play(r_dot.move_to, plane.n2p(np.log(2))) - self.wait() - self.play(r_dot.move_to, plane.n2p(complex(0, np.log(2))), path_arc=90 * DEGREES, run_time=2) - self.wait() - self.play(r_dot.move_to, plane.n2p(complex(0, PI / 2))) - self.wait() - self.play(r_dot.move_to, plane.n2p(np.log(2)), run_time=2) - self.wait() - self.play(frame.set_height, 14) - self.play(r_dot.move_to, plane.n2p(complex(np.log(2), PI)), run_time=3) - self.wait() - self.play(r_dot.move_to, plane.n2p(complex(np.log(2), TAU)), run_time=3) - self.wait() - - self.embed() - - def on_mouse_scroll(self, point, offset): - frame = self.camera.frame - if self.zoom_on_scroll: - factor = 1 + np.arctan(10 * offset[1]) - frame.scale(factor, about_point=ORIGIN) - else: - self.transform_alpha = clip(self.transform_alpha + 5 * offset[1], 0, 1) - - -class LDMEndScreen(PatreonEndScreen): - CONFIG = { - "scroll_time": 20, - "specific_patrons": [ - "1stViewMaths", - "Adam Dřínek", - "Adam Margulies", - "Aidan Shenkman", - "Alan Stein", - "Alex Mijalis", - "Alexander Mai", - "Alexis Olson", - "Ali Yahya", - "Andreas Snekloth Kongsgaard", - "Andrew Busey", - "Andrew Cary", - "Andrew R. Whalley", - "Anthony Losego", - "Aravind C V", - "Arjun Chakroborty", - "Arthur Zey", - "Ashwin Siddarth", - "Augustine Lim", - "Austin Goodman", - "Avi Finkel", - "Awoo", - "Axel Ericsson", - "Ayan Doss", - "AZsorcerer", - "Barry Fam", - "Bartosz Burclaf", - "Ben Delo", - "Bernd Sing", - "Bill Gatliff", - "Bob Sanderson", - "Boris Veselinovich", - "Bradley Pirtle", - "Brandon Huang", - "Brendan Shah", - "Brian Cloutier", - "Brian Staroselsky", - "Britt Selvitelle", - "Britton Finley", - "Burt Humburg", - "Calvin Lin", - "Charles Southerland", - "Charlie N", - "Chenna Kautilya", - "Chris Connett", - "Chris Druta", - "Christian Kaiser", - "cinterloper", - "Clark Gaebel", - "Colwyn Fritze-Moor", - "Cooper Jones", - "Corey Ogburn", - "D. Sivakumar", - "Dan Herbatschek", - "Daniel Herrera C", - "Darrell Thomas", - "Dave B", - "Dave Cole", - "Dave Kester", - "dave nicponski", - "David B. Hill", - "David Clark", - "David Gow", - "Delton Ding", - "Eduardo Rodriguez", - "Emilio Mendoza Palafox", - "emptymachine", - "Eric Younge", - "Eryq Ouithaqueue", - "Federico Lebron", - "Fernando Via Canel", - "Frank R. Brown, Jr.", - "Giovanni Filippi", - "Goodwine", - "Hal Hildebrand", - "Heptonion", - "Hitoshi Yamauchi", - "Ivan Sorokin", - "Jacob Baxter", - "Jacob Harmon", - "Jacob Hartmann", - "Jacob Magnuson", - "Jalex Stark", - "Jameel Syed", - "James Beall", - "Jason Hise", - "Jayne Gabriele", - "Jean-Manuel Izaret", - "Jeff Dodds", - "Jeff Linse", - "Jeff Straathof", - "Jeffrey Wolberg", - "Jimmy Yang", - "Joe Pregracke", - "Johan Auster", - "John C. Vesey", - "John Camp", - "John Haley", - "John Le", - "John Luttig", - "John Rizzo", - "John V Wertheim", - "Jonathan Heckerman", - "Jonathan Wilson", - "Joseph John Cox", - "Joseph Kelly", - "Josh Kinnear", - "Joshua Claeys", - "Joshua Ouellette", - "Juan Benet", - "Julien Dubois", - "Kai-Siang Ang", - "Kanan Gill", - "Karl Niu", - "Kartik Cating-Subramanian", - "Kaustuv DeBiswas", - "Killian McGuinness", - "kkm", - "Klaas Moerman", - "Kristoffer Börebäck", - "Kros Dai", - "L0j1k", - "Lael S Costa", - "LAI Oscar", - "Lambda GPU Workstations", - "Laura Gast", - "Lee Redden", - "Linh Tran", - "Luc Ritchie", - "Ludwig Schubert", - "Lukas Biewald", - "Lukas Zenick", - "Magister Mugit", - "Magnus Dahlström", - "Magnus Hiie", - "Manoj Rewatkar", - "Mark B Bahu", - "Mark Heising", - "Mark Hopkins", - "Mark Mann", - "Martin Price", - "Mathias Jansson", - "Matt Godbolt", - "Matt Langford", - "Matt Roveto", - "Matt Russell", - "Matteo Delabre", - "Matthew Bouchard", - "Matthew Cocke", - "Maxim Nitsche", - "Michael Bos", - "Michael Day", - "Michael Hardel", - "Michael W White", - "Mihran Vardanyan", - "Mirik Gogri", - "Molly Mackinlay", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nate Heckmann", - "Nero Li", - "Nicholas Cahill", - "Nikita Lesnikov", - "Oleg Leonov", - "Oliver Steele", - "Omar Zrien", - "Omer Tuchfeld", - "Patrick Lucas", - "Pavel Dubov", - "Pesho Ivanov", - "Petar Veličković", - "Peter Ehrnstrom", - "Peter Francis", - "Peter Mcinerney", - "Pierre Lancien", - "Pradeep Gollakota", - "Rafael Bove Barrios", - "Raghavendra Kotikalapudi", - "Randy C. Will", - "rehmi post", - "Rex Godby", - "Ripta Pasay", - "Rish Kundalia", - "Roman Sergeychik", - "Roobie", - "Ryan Atallah", - "Samuel Judge", - "SansWord Huang", - "Scott Gray", - "Scott Walter, Ph.D.", - "soekul", - "Solara570", - "Stephen Shanahan", - "Steve Huynh", - "Steve Muench", - "Steve Sperandeo", - "Steven Siddals", - "Stevie Metke", - "Sundar Subbarayan", - "Sunil Nagaraj", - "supershabam", - "Suteerth Vishnu", - "Suthen Thomas", - "Tal Einav", - "Taras Bobrovytsky", - "Tauba Auerbach", - "Ted Suzman", - "Thomas J Sargent", - "Thomas Tarler", - "Tianyu Ge", - "Tihan Seale", - "Tim Erbes", - "Tim Kazik", - "Tomasz Legutko", - "Tyler Herrmann", - "Tyler Parcell", - "Tyler VanValkenburg", - "Tyler Veness", - "Vassili Philippov", - "Vasu Dubey", - "Veritasium", - "Vignesh Ganapathi Subramanian", - "Vinicius Reis", - "Vladimir Solomatin", - "Wooyong Ee", - "Xuanji Li", - "Yana Chernobilsky", - "Yavor Ivanov", - "YinYangBalance.Asia", - "Yu Jun", - "Yurii Monastyrshyn", - ], - } diff --git a/from_3b1b/active/sir.py b/from_3b1b/active/sir.py deleted file mode 100644 index 66515786..00000000 --- a/from_3b1b/active/sir.py +++ /dev/null @@ -1,3366 +0,0 @@ -from manimlib.imports import * -from from_3b1b.active.bayes.beta_helpers import fix_percent -from from_3b1b.active.bayes.beta_helpers import XMARK_TEX -from from_3b1b.active.bayes.beta_helpers import CMARK_TEX - - -SICKLY_GREEN = "#9BBD37" -COLOR_MAP = { - "S": BLUE, - "I": RED, - "R": GREY_D, -} - - -def update_time(mob, dt): - mob.time += dt - - -class Person(VGroup): - CONFIG = { - "status": "S", # S, I or R - "height": 0.2, - "color_map": COLOR_MAP, - "infection_ring_style": { - "stroke_color": RED, - "stroke_opacity": 0.8, - "stroke_width": 0, - }, - "infection_radius": 0.5, - "infection_animation_period": 2, - "symptomatic": False, - "p_symptomatic_on_infection": 1, - "max_speed": 1, - "dl_bound": [-FRAME_WIDTH / 2, -FRAME_HEIGHT / 2], - "ur_bound": [FRAME_WIDTH / 2, FRAME_HEIGHT / 2], - "gravity_well": None, - "gravity_strength": 1, - "wall_buffer": 1, - "wander_step_size": 1, - "wander_step_duration": 1, - "social_distance_factor": 0, - "social_distance_color_threshold": 2, - "n_repulsion_points": 10, - "social_distance_color": YELLOW, - "max_social_distance_stroke_width": 5, - "asymptomatic_color": YELLOW, - } - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - self.time = 0 - self.last_step_change = -1 - self.change_anims = [] - self.velocity = np.zeros(3) - self.infection_start_time = np.inf - self.infection_end_time = np.inf - self.repulsion_points = [] - self.num_infected = 0 - - self.center_point = VectorizedPoint() - self.add(self.center_point) - self.add_body() - self.add_infection_ring() - self.set_status(self.status, run_time=0) - - # Updaters - self.add_updater(update_time) - self.add_updater(lambda m, dt: m.update_position(dt)) - self.add_updater(lambda m, dt: m.update_infection_ring(dt)) - self.add_updater(lambda m: m.progress_through_change_anims()) - - def add_body(self): - body = self.get_body() - body.set_height(self.height) - body.move_to(self.get_center()) - self.add(body) - self.body = body - - def get_body(self, status): - person = SVGMobject(file_name="person") - person.set_stroke(width=0) - return person - - def set_status(self, status, run_time=1): - start_color = self.color_map[self.status] - end_color = self.color_map[status] - - if status == "I": - self.infection_start_time = self.time - self.infection_ring.set_stroke(width=0, opacity=0) - if random.random() < self.p_symptomatic_on_infection: - self.symptomatic = True - else: - self.infection_ring.set_color(self.asymptomatic_color) - end_color = self.asymptomatic_color - if self.status == "I": - self.infection_end_time = self.time - self.symptomatic = False - - anims = [ - UpdateFromAlphaFunc( - self.body, - lambda m, a: m.set_color(interpolate_color( - start_color, end_color, a - )), - run_time=run_time, - ) - ] - for anim in anims: - self.push_anim(anim) - - self.status = status - - def push_anim(self, anim): - anim.suspend_mobject_updating = False - anim.begin() - anim.start_time = self.time - self.change_anims.append(anim) - return self - - def pop_anim(self, anim): - anim.update(1) - anim.finish() - self.change_anims.remove(anim) - - def add_infection_ring(self): - self.infection_ring = Circle( - radius=self.height / 2, - ) - self.infection_ring.set_style(**self.infection_ring_style) - self.add(self.infection_ring) - self.infection_ring.time = 0 - return self - - def update_position(self, dt): - center = self.get_center() - total_force = np.zeros(3) - - # Gravity - if self.wander_step_size != 0: - if (self.time - self.last_step_change) > self.wander_step_duration: - vect = rotate_vector(RIGHT, TAU * random.random()) - self.gravity_well = center + self.wander_step_size * vect - self.last_step_change = self.time - - if self.gravity_well is not None: - to_well = (self.gravity_well - center) - dist = get_norm(to_well) - if dist != 0: - total_force += self.gravity_strength * to_well / (dist**3) - - # Potentially avoid neighbors - if self.social_distance_factor > 0: - repulsion_force = np.zeros(3) - min_dist = np.inf - for point in self.repulsion_points: - to_point = point - center - dist = get_norm(to_point) - if 0 < dist < min_dist: - min_dist = dist - if dist > 0: - repulsion_force -= self.social_distance_factor * to_point / (dist**3) - sdct = self.social_distance_color_threshold - self.body.set_stroke( - self.social_distance_color, - width=clip( - (sdct / min_dist) - sdct, - # 2 * (sdct / min_dist), - 0, - self.max_social_distance_stroke_width - ), - background=True, - ) - total_force += repulsion_force - - # Avoid walls - wall_force = np.zeros(3) - for i in range(2): - to_lower = center[i] - self.dl_bound[i] - to_upper = self.ur_bound[i] - center[i] - - # Bounce - if to_lower < 0: - self.velocity[i] = abs(self.velocity[i]) - self.set_coord(self.dl_bound[i], i) - if to_upper < 0: - self.velocity[i] = -abs(self.velocity[i]) - self.set_coord(self.ur_bound[i], i) - - # Repelling force - wall_force[i] += max((-1 / self.wall_buffer + 1 / to_lower), 0) - wall_force[i] -= max((-1 / self.wall_buffer + 1 / to_upper), 0) - total_force += wall_force - - # Apply force - self.velocity += total_force * dt - - # Limit speed - speed = get_norm(self.velocity) - if speed > self.max_speed: - self.velocity *= self.max_speed / speed - - # Update position - self.shift(self.velocity * dt) - - def update_infection_ring(self, dt): - ring = self.infection_ring - if not (self.infection_start_time <= self.time <= self.infection_end_time + 1): - return self - - ring_time = self.time - self.infection_start_time - period = self.infection_animation_period - - alpha = (ring_time % period) / period - ring.set_height(interpolate( - self.height, - self.infection_radius, - smooth(alpha), - )) - ring.set_stroke( - width=interpolate( - 0, 5, - there_and_back(alpha), - ), - opacity=min([ - min([ring_time, 1]), - min([self.infection_end_time + 1 - self.time, 1]), - ]), - ) - - return self - - def progress_through_change_anims(self): - for anim in self.change_anims: - if anim.run_time == 0: - alpha = 1 - else: - alpha = (self.time - anim.start_time) / anim.run_time - anim.interpolate(alpha) - if alpha >= 1: - self.pop_anim(anim) - - def get_center(self): - return self.center_point.points[0] - - -class DotPerson(Person): - def get_body(self): - return Dot() - - -class PiPerson(Person): - CONFIG = { - "mode_map": { - "S": "guilty", - "I": "sick", - "R": "tease", - } - } - - def get_body(self): - return Randolph() - - def set_status(self, status, run_time=1): - super().set_status(status) - - target = self.body.copy() - target.change(self.mode_map[status]) - target.set_color(self.color_map[status]) - - transform = Transform(self.body, target) - transform.begin() - - def update(body, alpha): - transform.update(alpha) - body.move_to(self.center_point) - - anims = [ - UpdateFromAlphaFunc(self.body, update, run_time=run_time), - ] - for anim in anims: - self.push_anim(anim) - - return self - - -class SIRSimulation(VGroup): - CONFIG = { - "n_cities": 1, - "city_population": 100, - "box_size": 7, - "person_type": PiPerson, - "person_config": { - "height": 0.2, - "infection_radius": 0.6, - "gravity_strength": 1, - "wander_step_size": 1, - }, - "p_infection_per_day": 0.2, - "infection_time": 5, - "travel_rate": 0, - "limit_social_distancing_to_infectious": False, - } - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.time = 0 - self.add_updater(update_time) - - self.add_boxes() - self.add_people() - - self.add_updater(lambda m, dt: m.update_statusses(dt)) - - def add_boxes(self): - boxes = VGroup() - for x in range(self.n_cities): - box = Square() - box.set_height(self.box_size) - box.set_stroke(WHITE, 3) - boxes.add(box) - boxes.arrange_in_grid(buff=LARGE_BUFF) - self.add(boxes) - self.boxes = boxes - - def add_people(self): - people = VGroup() - for box in self.boxes: - dl_bound = box.get_corner(DL) - ur_bound = box.get_corner(UR) - box.people = VGroup() - for x in range(self.city_population): - person = self.person_type( - dl_bound=dl_bound, - ur_bound=ur_bound, - **self.person_config - ) - person.move_to([ - interpolate(lower, upper, random.random()) - for lower, upper in zip(dl_bound, ur_bound) - ]) - person.box = box - box.people.add(person) - people.add(person) - - # Choose a patient zero - random.choice(people).set_status("I") - self.add(people) - self.people = people - - def update_statusses(self, dt): - for box in self.boxes: - s_group, i_group = [ - list(filter( - lambda m: m.status == status, - box.people - )) - for status in ["S", "I"] - ] - - for s_person in s_group: - for i_person in i_group: - dist = get_norm(i_person.get_center() - s_person.get_center()) - if dist < s_person.infection_radius and random.random() < self.p_infection_per_day * dt: - s_person.set_status("I") - i_person.num_infected += 1 - for i_person in i_group: - if (i_person.time - i_person.infection_start_time) > self.infection_time: - i_person.set_status("R") - - # Travel - if self.travel_rate > 0: - path_func = path_along_arc(45 * DEGREES) - for person in self.people: - if random.random() < self.travel_rate * dt: - new_box = random.choice(self.boxes) - person.box.people.remove(person) - new_box.people.add(person) - person.box = new_box - person.dl_bound = new_box.get_corner(DL) - person.ur_bound = new_box.get_corner(UR) - - person.old_center = person.get_center() - person.new_center = new_box.get_center() - anim = UpdateFromAlphaFunc( - person, - lambda m, a: m.move_to(path_func( - m.old_center, m.new_center, a, - )), - run_time=1, - ) - person.push_anim(anim) - - # Social distancing - centers = np.array([person.get_center() for person in self.people]) - if self.limit_social_distancing_to_infectious: - repelled_centers = np.array([ - person.get_center() - for person in self.people - if person.symptomatic - ]) - else: - repelled_centers = centers - - if len(repelled_centers) > 0: - for center, person in zip(centers, self.people): - if person.social_distance_factor > 0: - diffs = np.linalg.norm(repelled_centers - center, axis=1) - person.repulsion_points = repelled_centers[np.argsort(diffs)[1:person.n_repulsion_points + 1]] - - def get_status_counts(self): - return np.array([ - len(list(filter( - lambda m: m.status == status, - self.people - ))) - for status in "SIR" - ]) - - def get_status_proportions(self): - counts = self.get_status_counts() - return counts / sum(counts) - - -class SIRGraph(VGroup): - CONFIG = { - "color_map": COLOR_MAP, - "height": 7, - "width": 5, - "update_frequency": 0.5, - "include_braces": False, - } - - def __init__(self, simulation, **kwargs): - super().__init__(**kwargs) - self.simulation = simulation - self.data = [simulation.get_status_proportions()] * 2 - self.add_axes() - self.add_graph() - self.add_x_labels() - - self.time = 0 - self.last_update_time = 0 - self.add_updater(update_time) - self.add_updater(lambda m: m.update_graph()) - self.add_updater(lambda m: m.update_x_labels()) - - def add_axes(self): - axes = Axes( - y_min=0, - y_max=1, - y_axis_config={ - "tick_frequency": 0.1, - }, - x_min=0, - x_max=1, - axis_config={ - "include_tip": False, - }, - ) - origin = axes.c2p(0, 0) - axes.x_axis.set_width(self.width, about_point=origin, stretch=True) - axes.y_axis.set_height(self.height, about_point=origin, stretch=True) - - self.add(axes) - self.axes = axes - - def add_graph(self): - self.graph = self.get_graph(self.data) - self.add(self.graph) - - def add_x_labels(self): - self.x_labels = VGroup() - self.x_ticks = VGroup() - self.add(self.x_ticks, self.x_labels) - - def get_graph(self, data): - axes = self.axes - i_points = [] - s_points = [] - for x, props in zip(np.linspace(0, 1, len(data)), data): - i_point = axes.c2p(x, props[1]) - s_point = axes.c2p(x, sum(props[:2])) - i_points.append(i_point) - s_points.append(s_point) - - r_points = [ - axes.c2p(0, 1), - axes.c2p(1, 1), - *s_points[::-1], - axes.c2p(0, 1), - ] - s_points.extend([ - *i_points[::-1], - s_points[0], - ]) - i_points.extend([ - axes.c2p(1, 0), - axes.c2p(0, 0), - i_points[0], - ]) - - points_lists = [s_points, i_points, r_points] - regions = VGroup(VMobject(), VMobject(), VMobject()) - - for region, status, points in zip(regions, "SIR", points_lists): - region.set_points_as_corners(points) - region.set_stroke(width=0) - region.set_fill(self.color_map[status], 1) - regions[0].set_fill(opacity=0.5) - - return regions - - def update_graph(self): - if (self.time - self.last_update_time) > self.update_frequency: - self.data.append(self.simulation.get_status_proportions()) - self.graph.become(self.get_graph(self.data)) - self.last_update_time = self.time - - def update_x_labels(self): - tick_height = 0.03 * self.graph.get_height() - tick_template = Line(DOWN, UP) - tick_template.set_height(tick_height) - - def get_tick(x): - tick = tick_template.copy() - tick.move_to(self.axes.c2p(x / self.time, 0)) - return tick - - def get_label(x, tick): - label = Integer(x) - label.set_height(tick_height) - label.next_to(tick, DOWN, buff=0.5 * tick_height) - return label - - self.x_labels.set_submobjects([]) - self.x_ticks.set_submobjects([]) - - if self.time < 15: - tick_range = range(1, int(self.time) + 1) - elif self.time < 50: - tick_range = range(5, int(self.time) + 1, 5) - elif self.time < 100: - tick_range = range(10, int(self.time) + 1, 10) - else: - tick_range = range(20, int(self.time) + 1, 20) - - for x in tick_range: - tick = get_tick(x) - label = get_label(x, tick) - self.x_ticks.add(tick) - self.x_labels.add(label) - - # TODO, if I care, refactor - if 10 < self.time < 15: - alpha = (self.time - 10) / 5 - for tick, label in zip(self.x_ticks, self.x_labels): - if label.get_value() % 5 != 0: - label.set_opacity(1 - alpha) - tick.set_opacity(1 - alpha) - if 45 < self.time < 50: - alpha = (self.time - 45) / 5 - for tick, label in zip(self.x_ticks, self.x_labels): - if label.get_value() % 10 == 5: - label.set_opacity(1 - alpha) - tick.set_opacity(1 - alpha) - - def add_v_line(self, line_time=None, color=YELLOW, stroke_width=3): - if line_time is None: - line_time = self.time - - axes = self.axes - v_line = Line( - axes.c2p(1, 0), axes.c2p(1, 1), - stroke_color=color, - stroke_width=stroke_width, - ) - v_line.add_updater( - lambda m: m.move_to( - axes.c2p(line_time / max(self.time, 1e-6), 0), - DOWN, - ) - ) - - self.add(v_line) - - -class GraphBraces(VGroup): - CONFIG = { - "update_frequency": 0.5, - } - - def __init__(self, graph, simulation, **kwargs): - super().__init__(**kwargs) - axes = self.axes = graph.axes - self.simulation = simulation - - ys = np.linspace(0, 1, 4) - self.lines = VGroup(*[ - Line(axes.c2p(1, y1), axes.c2p(1, y2)) - for y1, y2 in zip(ys, ys[1:]) - ]) - self.braces = VGroup(*[Brace(line, RIGHT) for line in self.lines]) - self.labels = VGroup( - TextMobject("Susceptible", color=COLOR_MAP["S"]), - TextMobject("Infectious", color=COLOR_MAP["I"]), - TextMobject("Removed", color=COLOR_MAP["R"]), - ) - - self.max_label_height = graph.get_height() * 0.05 - - self.add(self.braces, self.labels) - - self.time = 0 - self.last_update_time = -1 - self.add_updater(update_time) - self.add_updater(lambda m: m.update_braces()) - self.update(0) - - def update_braces(self): - if (self.time - self.last_update_time) <= self.update_frequency: - return - - self.last_update_time = self.time - lines = self.lines - braces = self.braces - labels = self.labels - axes = self.axes - - props = self.simulation.get_status_proportions() - ys = np.cumsum([0, props[1], props[0], props[2]]) - - epsilon = 1e-6 - for i, y1, y2 in zip([1, 0, 2], ys, ys[1:]): - lines[i].set_points_as_corners([ - axes.c2p(1, y1), - axes.c2p(1, y2), - ]) - height = lines[i].get_height() - - braces[i].set_height( - max(height, epsilon), - stretch=True - ) - braces[i].next_to(lines[i], RIGHT) - label_height = clip(height, epsilon, self.max_label_height) - labels[i].scale(label_height / labels[i][0][0].get_height()) - labels[i].next_to(braces[i], RIGHT) - return self - - -class ValueSlider(NumberLine): - CONFIG = { - "x_min": 0, - "x_max": 1, - "tick_frequency": 0.1, - "numbers_with_elongated_ticks": [], - "numbers_to_show": np.linspace(0, 1, 6), - "decimal_number_config": { - "num_decimal_places": 1, - }, - "stroke_width": 5, - "width": 8, - "marker_color": BLUE, - } - - def __init__(self, name, value, **kwargs): - super().__init__(**kwargs) - self.set_width(self.width, stretch=True) - self.add_numbers() - - self.marker = ArrowTip(start_angle=-90 * DEGREES) - self.marker.move_to(self.n2p(value), DOWN) - self.marker.set_color(self.marker_color) - self.add(self.marker) - - # self.label = DecimalNumber(value) - # self.label.next_to(self.marker, UP) - # self.add(self.label) - - self.name = TextMobject(name) - self.name.scale(1.25) - self.name.next_to(self, DOWN) - self.name.match_color(self.marker) - self.add(self.name) - - def get_change_anim(self, new_value, **kwargs): - start_value = self.p2n(self.marker.get_bottom()) - # m2l = self.label.get_center() - self.marker.get_center() - - def update(mob, alpha): - interim_value = interpolate(start_value, new_value, alpha) - mob.marker.move_to(mob.n2p(interim_value), DOWN) - # mob.label.move_to(mob.marker.get_center() + m2l) - # mob.label.set_value(interim_value) - - return UpdateFromAlphaFunc(self, update, **kwargs) - - -# Scenes - -class Test(Scene): - def construct(self): - path_func = path_along_arc(45 * DEGREES) - person = PiPerson(height=1, gravity_strength=0.2) - person.gravity_strength = 0 - - person.old_center = person.get_center() - person.new_center = 4 * RIGHT - - self.add(person) - self.wait() - - self.play(UpdateFromAlphaFunc( - person, - lambda m, a: m.move_to(path_func( - m.old_center, - m.new_center, - a, - )), - run_time=3, - rate_func=there_and_back, - )) - - self.wait(3) - self.wait(3) - - -class RunSimpleSimulation(Scene): - CONFIG = { - "simulation_config": { - "person_type": PiPerson, - "n_cities": 1, - "city_population": 100, - "person_config": { - "infection_radius": 0.75, - "social_distance_factor": 0, - "gravity_strength": 0.2, - "max_speed": 0.5, - }, - "travel_rate": 0, - "infection_time": 5, - }, - "graph_config": { - "update_frequency": 1 / 15, - }, - "graph_height_to_frame_height": 0.5, - "graph_width_to_frame_height": 0.75, - "include_graph_braces": True, - } - - def setup(self): - self.add_simulation() - self.position_camera() - self.add_graph() - self.add_sliders() - self.add_R_label() - self.add_total_cases_label() - - def construct(self): - self.run_until_zero_infections() - - def wait_until_infection_threshold(self, threshold): - self.wait_until(lambda: self.simulation.get_status_counts()[1] > threshold) - - def run_until_zero_infections(self): - while True: - self.wait(5) - if self.simulation.get_status_counts()[1] == 0: - self.wait(5) - break - - def add_R_label(self): - label = VGroup( - TexMobject("R = "), - DecimalNumber(), - ) - label.arrange(RIGHT) - boxes = self.simulation.boxes - label.set_width(0.25 * boxes.get_width()) - label.next_to(boxes.get_corner(DL), DR) - self.add(label) - - all_R0_values = [] - - def update_label(label): - if (self.time - label.last_update_time) < label.update_period: - return - label.last_update_time = self.time - - values = [] - for person in self.simulation.people: - if person.status == "I": - prop = (person.time - person.infection_start_time) / self.simulation.infection_time - if prop > 0.1: - values.append(person.num_infected / prop) - if len(values) > 0: - all_R0_values.append(np.mean(values)) - average = np.mean(all_R0_values[-20:]) - label[1].set_value(average) - - label.last_update_time = 0 - label.update_period = 1 - label.add_updater(update_label) - - def add_total_cases_label(self): - label = VGroup( - TextMobject("\\# Active cases = "), - Integer(1), - ) - label.arrange(RIGHT) - label[1].align_to(label[0][0][1], DOWN) - label.set_color(RED) - boxes = self.simulation.boxes - label.set_width(0.5 * boxes.get_width()) - label.next_to(boxes, UP, buff=0.03 * boxes.get_width()) - - label.add_updater( - lambda m: m[1].set_value(self.simulation.get_status_counts()[1]) - ) - self.total_cases_label = label - self.add(label) - - def add_simulation(self): - self.simulation = SIRSimulation(**self.simulation_config) - self.add(self.simulation) - - def position_camera(self): - frame = self.camera.frame - boxes = self.simulation.boxes - min_height = boxes.get_height() + 1 - min_width = 3 * boxes.get_width() - if frame.get_height() < min_height: - frame.set_height(min_height) - if frame.get_width() < min_width: - frame.set_width(min_width) - - frame.next_to(boxes.get_right(), LEFT, buff=-0.1 * boxes.get_width()) - - def add_graph(self): - frame = self.camera.frame - frame_height = frame.get_height() - graph = SIRGraph( - self.simulation, - height=self.graph_height_to_frame_height * frame_height, - width=self.graph_width_to_frame_height * frame_height, - **self.graph_config, - ) - graph.move_to(frame, UL) - graph.shift(0.05 * DR * frame_height) - self.add(graph) - self.graph = graph - - if self.include_graph_braces: - self.graph_braces = GraphBraces( - graph, - self.simulation, - update_frequency=graph.update_frequency - ) - self.add(self.graph_braces) - - def add_sliders(self): - pass - - -class RunSimpleSimulationWithDots(RunSimpleSimulation): - CONFIG = { - "simulation_config": { - "person_type": DotPerson, - } - } - - -class LargerCity(RunSimpleSimulation): - CONFIG = { - "simulation_config": { - "person_type": DotPerson, - "city_population": 1000, - "person_config": { - "infection_radius": 0.25, - "social_distance_factor": 0, - "gravity_strength": 0.2, - "max_speed": 0.25, - "height": 0.2 / 3, - "wall_buffer": 1 / 3, - "social_distance_color_threshold": 2 / 3, - }, - } - } - - -class LargerCity2(LargerCity): - CONFIG = { - "random_seed": 1, - } - - -class LargeCityHighInfectionRadius(LargerCity): - CONFIG = { - "simulation_config": { - "person_config": { - "infection_radius": 0.5, - }, - }, - "graph_config": { - "update_frequency": 1 / 60, - }, - } - - -class LargeCityLowerInfectionRate(LargerCity): - CONFIG = { - "p_infection_per_day": 0.1, - } - - -class SimpleSocialDistancing(RunSimpleSimulation): - CONFIG = { - "simulation_config": { - "person_type": PiPerson, - "n_cities": 1, - "city_population": 100, - "person_config": { - "infection_radius": 0.75, - "social_distance_factor": 2, - "gravity_strength": 0.1, - }, - "travel_rate": 0, - "infection_time": 5, - }, - } - - -class DelayedSocialDistancing(RunSimpleSimulation): - CONFIG = { - "delay_time": 8, - "target_sd_factor": 2, - "sd_probability": 1, - "random_seed": 1, - } - - def construct(self): - self.wait(self.delay_time) - self.change_social_distance_factor( - self.target_sd_factor, - self.sd_probability, - ) - self.graph.add_v_line() - self.play(self.sd_slider.get_change_anim(self.target_sd_factor)) - - self.run_until_zero_infections() - - def change_social_distance_factor(self, new_factor, prob): - for person in self.simulation.people: - if random.random() < prob: - person.social_distance_factor = new_factor - - def add_sliders(self): - slider = ValueSlider( - self.get_sd_slider_name(), - value=0, - x_min=0, - x_max=2, - tick_frequency=0.5, - numbers_with_elongated_ticks=[], - numbers_to_show=range(3), - decimal_number_config={ - "num_decimal_places": 0, - } - ) - fix_percent(slider.name[0][23 + int(self.sd_probability == 1)]) # So dumb - slider.match_width(self.graph) - slider.next_to(self.graph, DOWN, buff=0.2 * self.graph.get_height()) - self.add(slider) - self.sd_slider = slider - - def get_sd_slider_name(self): - return f"Social Distance Factor\\\\({int(100 * self.sd_probability)}$\\%$ of population)" - - -class DelayedSocialDistancingDot(DelayedSocialDistancing): - CONFIG = { - "simulation_config": { - "person_type": DotPerson, - } - } - - -class DelayedSocialDistancingLargeCity(DelayedSocialDistancing, LargerCity): - CONFIG = { - "trigger_infection_count": 50, - "simulation_config": { - 'city_population': 900, - } - } - - def construct(self): - self.wait_until_infection_threshold(self.trigger_infection_count) - self.change_social_distance_factor( - self.target_sd_factor, - self.sd_probability, - ) - self.graph.add_v_line() - self.play(self.sd_slider.get_change_anim(self.target_sd_factor)) - - self.run_until_zero_infections() - - -class DelayedSocialDistancingLargeCity90p(DelayedSocialDistancingLargeCity): - CONFIG = { - "sd_probability": 0.9, - } - - -class DelayedSocialDistancingLargeCity90pAlt(DelayedSocialDistancingLargeCity): - CONFIG = { - "sd_probability": 0.9, - "random_seed": 5, - } - - -class DelayedSocialDistancingLargeCity70p(DelayedSocialDistancingLargeCity): - CONFIG = { - "sd_probability": 0.7, - } - - -class DelayedSocialDistancingLargeCity50p(DelayedSocialDistancingLargeCity): - CONFIG = { - "sd_probability": 0.5, - } - - -class DelayedSocialDistancingWithDots(DelayedSocialDistancing): - CONFIG = { - "person_type": DotPerson, - } - - -class DelayedSocialDistancingProbHalf(DelayedSocialDistancing): - CONFIG = { - "sd_probability": 0.5, - } - - -class ReduceInfectionDuration(LargerCity): - CONFIG = { - "trigger_infection_count": 50, - } - - def construct(self): - self.wait_until_infection_threshold(self.trigger_infection_count) - self.play(self.slider.get_change_anim(1)) - self.simulation.infection_time = 1 - self.graph.add_v_line() - self.run_until_zero_infections() - - def add_sliders(self): - slider = ValueSlider( - "Infection duration", - value=5, - x_min=0, - x_max=5, - tick_frequency=1, - numbers_with_elongated_ticks=[], - numbers_to_show=range(6), - decimal_number_config={ - "num_decimal_places": 0, - }, - marker_color=RED, - ) - slider.match_width(self.graph) - slider.next_to(self.graph, DOWN, buff=0.2 * self.graph.get_height()) - self.add(slider) - self.slider = slider - - -class SimpleTravel(RunSimpleSimulation): - CONFIG = { - "simulation_config": { - "person_type": DotPerson, - "n_cities": 12, - "city_population": 100, - "person_config": { - "infection_radius": 0.75, - "social_distance_factor": 0, - "gravity_strength": 0.5, - }, - "travel_rate": 0.02, - "infection_time": 5, - }, - } - - def add_sliders(self): - slider = ValueSlider( - "Travel rate", - self.simulation.travel_rate, - x_min=0, - x_max=0.02, - tick_frequency=0.005, - numbers_with_elongated_ticks=[], - numbers_to_show=np.arange(0, 0.03, 0.01), - decimal_number_config={ - "num_decimal_places": 2, - } - ) - slider.match_width(self.graph) - slider.next_to(self.graph, DOWN, buff=0.2 * self.graph.get_height()) - self.add(slider) - self.tr_slider = slider - - -class SimpleTravel2(SimpleTravel): - CONFIG = { - "random_seed": 1, - } - - -class SimpleTravelLongInfectionPeriod(SimpleTravel): - CONFIG = { - "simulation_config": { - "infection_time": 10, - } - } - - -class SimpleTravelDelayedSocialDistancing(DelayedSocialDistancing, SimpleTravel): - CONFIG = { - "target_sd_factor": 2, - "sd_probability": 0.7, - "delay_time": 15, - "trigger_infection_count": 50, - } - - def construct(self): - self.wait_until_infection_threshold(self.trigger_infection_count) - self.change_social_distance_factor( - self.target_sd_factor, - self.sd_probability, - ) - self.graph.add_v_line() - self.play(self.sd_slider.get_change_anim(self.target_sd_factor)) - - self.run_until_zero_infections() - - def add_sliders(self): - SimpleTravel.add_sliders(self) - DelayedSocialDistancing.add_sliders(self) - - buff = 0.1 * self.graph.get_height() - - self.tr_slider.scale(0.8, about_edge=UP) - self.tr_slider.next_to(self.graph, DOWN, buff=buff) - - self.sd_slider.scale(0.8) - self.sd_slider.marker.set_color(YELLOW) - self.sd_slider.name.set_color(YELLOW) - self.sd_slider.next_to(self.tr_slider, DOWN, buff=buff) - - -class SimpleTravelDelayedSocialDistancing70p(SimpleTravelDelayedSocialDistancing): - pass - - -class SimpleTravelDelayedSocialDistancing99p(SimpleTravelDelayedSocialDistancing): - CONFIG = { - "sd_probability": 0.99, - } - - -class SimpleTravelDelayedSocialDistancing20p(SimpleTravelDelayedSocialDistancing): - CONFIG = { - "sd_probability": 0.20, - } - - -class SimpleTravelDelayedSocialDistancing50p(SimpleTravelDelayedSocialDistancing): - CONFIG = { - "sd_probability": 0.50, - "random_seed": 1, - } - - -class SimpleTravelDelayedSocialDistancing50pThreshold100(SimpleTravelDelayedSocialDistancing): - CONFIG = { - "sd_probability": 0.50, - "trigger_infection_count": 100, - "random_seed": 5, - } - - -class SimpleTravelDelayedSocialDistancing70pThreshold100(SimpleTravelDelayedSocialDistancing): - CONFIG = { - "sd_probability": 0.70, - "trigger_infection_count": 100, - } - - -class SimpleTravelSocialDistancePlusZeroTravel(SimpleTravelDelayedSocialDistancing): - CONFIG = { - "sd_probability": 1, - "target_travel_rate": 0, - } - - def construct(self): - self.wait_until_infection_threshold(self.trigger_infection_count) - self.change_social_distance_factor( - self.target_sd_factor, - self.sd_probability, - ) - self.simulation.travel_rate = self.target_travel_rate - self.graph.add_v_line() - self.play( - self.tr_slider.get_change_anim(self.simulation.travel_rate), - self.sd_slider.get_change_anim(self.target_sd_factor), - ) - - self.run_until_zero_infections() - - -class SecondWave(SimpleTravelSocialDistancePlusZeroTravel): - def run_until_zero_infections(self): - self.wait_until(lambda: self.simulation.get_status_counts()[1] < 10) - self.change_social_distance_factor(0, 1) - self.simulation.travel_rate = 0.02 - self.graph.add_v_line() - self.play( - self.tr_slider.get_change_anim(0.02), - self.sd_slider.get_change_anim(0), - ) - super().run_until_zero_infections() - - -class SimpleTravelSocialDistancePlusZeroTravel99p(SimpleTravelSocialDistancePlusZeroTravel): - CONFIG = { - "sd_probability": 0.99, - } - - -class SimpleTravelDelayedTravelReduction(SimpleTravelDelayedSocialDistancing): - CONFIG = { - "trigger_infection_count": 50, - "target_travel_rate": 0.002, - } - - def construct(self): - self.wait_until_infection_threshold(self.trigger_infection_count) - self.simulation.travel_rate = self.target_travel_rate - self.graph.add_v_line() - self.play(self.tr_slider.get_change_anim(self.simulation.travel_rate)) - self.run_until_zero_infections() - - -class SimpleTravelDelayedTravelReductionThreshold100(SimpleTravelDelayedTravelReduction): - CONFIG = { - "random_seed": 2, - "trigger_infection_count": 100, - } - - -class SimpleTravelDelayedTravelReductionThreshold100TargetHalfPercent(SimpleTravelDelayedTravelReduction): - CONFIG = { - "random_seed": 2, - "trigger_infection_count": 100, - "target_travel_rate": 0.005, - } - - -class SimpleTravelDelayedTravelReductionThreshold100TargetHalfPercent2(SimpleTravelDelayedTravelReductionThreshold100TargetHalfPercent): - CONFIG = { - "random_seed": 1, - "sd_probability": 0.5, - } - - def setup(self): - super().setup() - for x in range(2): - random.choice(self.simulation.people).set_status("I") - - -class SimpleTravelLargeCity(SimpleTravel, LargerCity): - CONFIG = { - "simulation_config": { - "n_cities": 12, - "travel_rate": 0.02, - } - } - - -class SimpleTravelLongerDelayedSocialDistancing(SimpleTravelDelayedSocialDistancing): - CONFIG = { - "trigger_infection_count": 100, - } - - -class SimpleTravelLongerDelayedTravelReduction(SimpleTravelDelayedTravelReduction): - CONFIG = { - "trigger_infection_count": 100, - } - - -class SocialDistanceAfterFiveDays(DelayedSocialDistancing): - CONFIG = { - "sd_probability": 0.7, - "delay_time": 5, - "simulation_config": { - "travel_rate": 0 - }, - } - - -class QuarantineInfectious(RunSimpleSimulation): - CONFIG = { - "trigger_infection_count": 10, - "target_sd_factor": 3, - "infection_time_before_quarantine": 1, - } - - def construct(self): - self.wait_until_infection_threshold(self.trigger_infection_count) - self.add_quarantine_box() - self.set_quarantine_updaters() - self.run_until_zero_infections() - - def add_quarantine_box(self): - boxes = self.simulation.boxes - q_box = boxes[0].copy() - q_box.set_color(RED_E) - q_box.set_width(boxes.get_width() / 3) - q_box.next_to( - boxes, LEFT, - aligned_edge=DOWN, - buff=0.25 * q_box.get_width() - ) - - label = TextMobject("Quarantine zone") - label.set_color(RED) - label.match_width(q_box) - label.next_to(q_box, DOWN, buff=0.1 * q_box.get_width()) - - self.add(q_box) - self.add(label) - self.q_box = q_box - - def set_quarantine_updaters(self): - def quarantine_if_ready(simulation): - for person in simulation.people: - send_to_q_box = all([ - not person.is_quarantined, - person.symptomatic, - (person.time - person.infection_start_time) > self.infection_time_before_quarantine, - ]) - if send_to_q_box: - person.box = self.q_box - person.dl_bound = self.q_box.get_corner(DL) - person.ur_bound = self.q_box.get_corner(UR) - person.old_center = person.get_center() - person.new_center = self.q_box.get_center() - point = VectorizedPoint(person.get_center()) - person.push_anim(ApplyMethod(point.move_to, self.q_box.get_center(), run_time=0.5)) - person.push_anim(MaintainPositionRelativeTo(person, point)) - person.move_to(self.q_box.get_center()) - person.is_quarantined = True - - for person in self.simulation.people: - person.is_quarantined = False - # person.add_updater(quarantine_if_ready) - self.simulation.add_updater(quarantine_if_ready) - - -class QuarantineInfectiousLarger(QuarantineInfectious, LargerCity): - CONFIG = { - "trigger_infection_count": 50, - } - - -class QuarantineInfectiousLargerWithTail(QuarantineInfectiousLarger): - def construct(self): - super().construct() - self.simulation.clear_updaters() - self.wait(25) - - -class QuarantineInfectiousTravel(QuarantineInfectious, SimpleTravel): - CONFIG = { - "trigger_infection_count": 50, - } - - def add_sliders(self): - pass - - -class QuarantineInfectious80p(QuarantineInfectious): - CONFIG = { - "simulation_config": { - "person_config": { - "p_symptomatic_on_infection": 0.8, - } - } - } - - -class QuarantineInfectiousLarger80p(QuarantineInfectiousLarger): - CONFIG = { - "simulation_config": { - "person_config": { - "p_symptomatic_on_infection": 0.8, - } - } - } - - -class QuarantineInfectiousTravel80p(QuarantineInfectiousTravel): - CONFIG = { - "simulation_config": { - "person_config": { - "p_symptomatic_on_infection": 0.8, - } - } - } - - -class QuarantineInfectious50p(QuarantineInfectious): - CONFIG = { - "simulation_config": { - "person_config": { - "p_symptomatic_on_infection": 0.5, - } - } - } - - -class QuarantineInfectiousLarger50p(QuarantineInfectiousLarger): - CONFIG = { - "simulation_config": { - "person_config": { - "p_symptomatic_on_infection": 0.5, - } - } - } - - -class QuarantineInfectiousTravel50p(QuarantineInfectiousTravel): - CONFIG = { - "simulation_config": { - "person_config": { - "p_symptomatic_on_infection": 0.5, - } - } - } - - -class CentralMarket(DelayedSocialDistancing): - CONFIG = { - "sd_probability": 0.7, - "delay_time": 5, - "simulation_config": { - "person_type": DotPerson, - "travel_rate": 0 - }, - "shopping_frequency": 0.05, - "shopping_time": 1, - } - - def setup(self): - super().setup() - for person in self.simulation.people: - person.last_shopping_trip = -3 - person.is_shopping = False - - square = Square() - square.set_height(0.2) - square.set_color(WHITE) - square.move_to(self.simulation.boxes[0].get_center()) - self.add(square) - - self.simulation.add_updater( - lambda m, dt: self.add_travel_anims(m, dt) - ) - - def construct(self): - self.run_until_zero_infections() - - def add_travel_anims(self, simulation, dt): - shopping_time = self.shopping_time - for person in simulation.people: - time_since_trip = person.time - person.last_shopping_trip - if time_since_trip > shopping_time: - if random.random() < dt * self.shopping_frequency: - person.last_shopping_trip = person.time - - point = VectorizedPoint(person.get_center()) - anim1 = ApplyMethod( - point.move_to, person.box.get_center(), - path_arc=45 * DEGREES, - run_time=shopping_time, - rate_func=there_and_back_with_pause, - ) - anim2 = MaintainPositionRelativeTo(person, point, run_time=shopping_time) - - person.push_anim(anim1) - person.push_anim(anim2) - - def add_sliders(self): - pass - - -class CentralMarketLargePopulation(CentralMarket, LargerCity): - pass - - -class CentralMarketLowerInfection(CentralMarketLargePopulation): - CONFIG = { - "simulation_config": { - "p_infection_per_day": 0.1, - } - } - - -class CentralMarketVeryFrequentLargePopulationDelayedSocialDistancing(CentralMarketLowerInfection): - CONFIG = { - "sd_probability": 0.7, - "trigger_infection_count": 25, - "simulation_config": { - "person_type": DotPerson, - }, - "target_sd_factor": 2, - } - - def construct(self): - self.wait_until_infection_threshold(self.trigger_infection_count) - self.graph.add_v_line() - for person in self.simulation.people: - person.social_distance_factor = self.target_sd_factor - self.run_until_zero_infections() - - -class CentralMarketLessFrequent(CentralMarketVeryFrequentLargePopulationDelayedSocialDistancing): - CONFIG = { - "target_shopping_frequency": 0.01, - "trigger_infection_count": 100, - "random_seed": 1, - "simulation_config": { - 'city_population': 900, - }, - } - - def construct(self): - self.wait_until(lambda: self.simulation.get_status_counts()[1] > self.trigger_infection_count) - for person in self.simulation.people: - person.social_distance_factor = 2 - # Decrease shopping rate - self.graph.add_v_line() - self.change_slider() - self.run_until_zero_infections() - - def change_slider(self): - self.play(self.shopping_slider.get_change_anim(self.target_shopping_frequency)) - self.shopping_frequency = self.target_shopping_frequency - - def add_sliders(self): - slider = ValueSlider( - "Shopping frequency", - value=self.shopping_frequency, - x_min=0, - x_max=0.05, - tick_frequency=0.01, - numbers_with_elongated_ticks=[], - numbers_to_show=np.arange(0, 0.06, 0.01), - decimal_number_config={ - "num_decimal_places": 2, - } - ) - slider.match_width(self.graph) - slider.next_to(self.graph, DOWN, buff=0.2 * self.graph.get_height()) - self.add(slider) - self.shopping_slider = slider - - -class CentralMarketDownToZeroFrequency(CentralMarketLessFrequent): - CONFIG = { - "target_shopping_frequency": 0, - } - - -class CentralMarketQuarantine(QuarantineInfectiousLarger, CentralMarketLowerInfection): - CONFIG = { - "random_seed": 1, - } - - def construct(self): - self.wait_until_infection_threshold(self.trigger_infection_count) - self.graph.add_v_line() - self.add_quarantine_box() - self.set_quarantine_updaters() - self.run_until_zero_infections() - - -class CentralMarketQuarantine80p(CentralMarketQuarantine): - CONFIG = { - "simulation_config": { - "person_config": { - "p_symptomatic_on_infection": 0.8, - } - } - } - - -class CentralMarketTransitionToLowerInfection(CentralMarketLessFrequent): - CONFIG = { - "target_p_infection_per_day": 0.05, # From 0.1 - "trigger_infection_count": 100, - "random_seed": 1, - "simulation_config": { - 'city_population': 900, - }, - } - - def change_slider(self): - self.play(self.infection_slider.get_change_anim(self.target_p_infection_per_day)) - self.simulation.p_infection_per_day = self.target_p_infection_per_day - - def add_sliders(self): - slider = ValueSlider( - "Infection rate", - value=self.simulation.p_infection_per_day, - x_min=0, - x_max=0.2, - tick_frequency=0.05, - numbers_with_elongated_ticks=[], - numbers_to_show=np.arange(0, 0.25, 0.05), - decimal_number_config={ - "num_decimal_places": 2, - }, - marker_color=RED, - ) - slider.match_width(self.graph) - slider.next_to(self.graph, DOWN, buff=0.2 * self.graph.get_height()) - self.add(slider) - self.infection_slider = slider - - -class CentralMarketTransitionToLowerInfectionAndLowerFrequency(CentralMarketTransitionToLowerInfection): - CONFIG = { - "random_seed": 2, - } - - def change_slider(self): - super().change_slider() - self.shopping_frequency = self.target_shopping_frequency - - -# Filler animations - -class TableOfContents(Scene): - def construct(self): - chapters = VGroup( - TextMobject("Basic setup"), - TextMobject("Identify and isolate"), - TextMobject("Social distancing"), - TextMobject("Travel restrictions"), - TextMobject("$R$"), - TextMobject("Central hubs"), - ) - chapters.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - chapters.set_height(FRAME_HEIGHT - 1) - chapters.to_edge(LEFT, buff=LARGE_BUFF) - - for chapter in chapters: - dot = Dot() - dot.next_to(chapter, LEFT, SMALL_BUFF) - chapter.add(dot) - chapter.save_state() - - self.add(chapters) - - for chapter in chapters: - non_chapter = [c for c in chapters if c is not chapter] - chapter.target = chapter.saved_state.copy() - chapter.target.scale(1.5, about_edge=LEFT) - chapter.target.set_fill(YELLOW, 1) - for nc in non_chapter: - nc.target = nc.saved_state.copy() - nc.target.scale(0.8, about_edge=LEFT) - nc.target.set_fill(WHITE, 0.5) - self.play(*map(MoveToTarget, chapters)) - self.wait() - self.play(*map(Restore, chapters)) - self.wait() - - -class DescribeModel(Scene): - def construct(self): - # Setup words - words = TextMobject( - "Susceptible", - "Infectious", - "Recovered", - ) - words.scale(0.8 / words[0][0].get_height()) - - colors = [ - COLOR_MAP["S"], - COLOR_MAP["I"], - interpolate_color(COLOR_MAP["R"], WHITE, 0.5), - ] - - initials = VGroup() - for i, word, color in zip(it.count(), words, colors): - initials.add(word[0][0]) - word.set_color(color) - word.move_to(2 * i * DOWN) - word.to_edge(LEFT) - words.to_corner(UL) - - # Rearrange initials - initials.save_state() - initials.arrange(RIGHT, buff=SMALL_BUFF) - initials.set_color(WHITE) - - title = VGroup(initials, TextMobject("Model")) - title[1].match_height(title[0]) - title.arrange(RIGHT, buff=MED_LARGE_BUFF) - title.center() - title.to_edge(UP) - - self.play(FadeInFromDown(title)) - self.wait() - self.play( - Restore( - initials, - path_arc=-90 * DEGREES, - ), - FadeOut(title[1]) - ) - self.wait() - - # Show each category - pi = PiPerson( - height=3, - max_speed=0, - infection_radius=5, - ) - pi.color_map["R"] = words[2].get_color() - pi.center() - pi.body.change("pondering", initials[0]) - - word_anims = [] - for word in words: - word_anims.append(LaggedStartMap( - FadeInFrom, word[1:], - lambda m: (m, 0.2 * LEFT), - )) - - self.play( - Succession( - FadeInFromDown(pi), - ApplyMethod(pi.body.change, "guilty"), - ), - word_anims[0], - run_time=2, - ) - words[0].pi = pi.copy() - self.play( - words[0].pi.set_height, 1, - words[0].pi.next_to, words[0], RIGHT, - ) - self.play(Blink(pi.body)) - - pi.set_status("I") - point = VectorizedPoint(pi.get_center()) - self.play( - point.shift, 3 * RIGHT, - MaintainPositionRelativeTo(pi, point), - word_anims[1], - run_time=2, - ) - words[1].pi = pi.copy() - self.play( - words[1].pi.set_height, 1, - words[1].pi.next_to, words[1], RIGHT, - ) - self.wait(3) - - pi.set_status("R") - self.play( - word_anims[2], - Animation(pi, suspend_mobject_updating=False) - ) - words[2].pi = pi.copy() - self.play( - words[2].pi.set_height, 1, - words[2].pi.next_to, words[2], RIGHT, - ) - self.wait() - - # Show rules - i_pi = PiPerson( - height=1.5, - max_speed=0, - infection_radius=6, - status="S", - ) - i_pi.set_status("I") - s_pis = VGroup() - for vect in [RIGHT, UP, LEFT, DOWN]: - s_pi = PiPerson( - height=1.5, - max_speed=0, - infection_radius=6, - status="S", - ) - s_pi.next_to(i_pi, vect, MED_LARGE_BUFF) - s_pis.add(s_pi) - - VGroup(i_pi, s_pis).to_edge(RIGHT) - - circle = Circle(radius=3) - circle.move_to(i_pi) - dashed_circle = DashedVMobject(circle, num_dashes=30) - dashed_circle.set_color(RED) - - self.play( - FadeOut(pi), - FadeIn(s_pis), - FadeIn(i_pi), - ) - anims = [] - for s_pi in s_pis: - anims.append(ApplyMethod(s_pi.body.look_at, i_pi.body.eyes)) - self.play(*anims) - self.add(VGroup(i_pi, *s_pis)) - self.wait() - self.play(ShowCreation(dashed_circle)) - self.wait() - shuffled = list(s_pis) - random.shuffle(shuffled) - for s_pi in shuffled: - s_pi.set_status("I") - self.wait(3 * random.random()) - self.wait(2) - self.play(FadeOut(s_pis), FadeOut(dashed_circle)) - - # Let time pass - clock = Clock() - clock.next_to(i_pi.body, UP, buff=LARGE_BUFF) - - self.play( - VFadeIn(clock), - ClockPassesTime( - clock, - run_time=5, - hours_passed=5, - ), - ) - i_pi.set_status("R") - self.wait(1) - self.play(Blink(i_pi.body)) - self.play(FadeOut(clock)) - - # Removed - removed = TextMobject("Removed") - removed.match_color(words[2]) - removed.match_height(words[2]) - removed.move_to(words[2], DL) - - self.play( - FadeOutAndShift(words[2], UP), - FadeInFrom(removed, DOWN), - ) - self.play( - i_pi.body.change, 'pleading', removed, - ) - self.play(Blink(i_pi.body)) - self.wait() - - -class SubtlePangolin(Scene): - def construct(self): - pangolin = SVGMobject(file_name="pangolin") - pangolin.set_height(0.5) - pangolin.set_fill(GREY_BROWN, opacity=0) - pangolin.set_stroke(GREY_BROWN, width=1) - self.play(ShowCreationThenFadeOut(pangolin)) - self.play(FadeOut(pangolin)) - - self.embed() - - -class DoubleRadiusInGroup(Scene): - def construct(self): - radius = 1 - - pis = VGroup(*[ - PiPerson( - height=0.5, - max_speed=0, - wander_step_size=0, - infection_radius=4 * radius, - ) - for x in range(49) - ]) - pis.arrange_in_grid() - pis.set_height(FRAME_HEIGHT - 1) - sicky = pis[24] - sicky.set_status("I") - - circle = Circle(radius=radius) - circle.move_to(sicky) - dashed_circle = DashedVMobject(circle, num_dashes=30) - dashed_circle2 = dashed_circle.copy() - dashed_circle2.scale(2) - - self.add(pis) - self.play(ShowCreation(dashed_circle, lag_ratio=0)) - self.play(ShowCreation(dashed_circle2, lag_ratio=0)) - anims = [] - for pi in pis: - if pi.status == "S": - anims.append(ApplyMethod( - pi.body.change, "pleading", sicky.body.eyes - )) - random.shuffle(anims) - self.play(LaggedStart(*anims)) - self.wait(10) - - -class CutPInfectionInHalf(Scene): - def construct(self): - # Add people - sicky = PiPerson( - height=1, - infection_radius=4, - max_speed=0, - wander_step_size=0, - ) - normy = sicky.deepcopy() - normy.next_to(sicky, RIGHT) - normy.body.look_at(sicky.body.eyes) - - circ = Circle(radius=4) - d_circ = DashedVMobject(circ, num_dashes=30) - d_circ.set_color(RED) - d_circ.move_to(sicky) - - sicky.set_status("I") - self.add(sicky, normy) - self.add(d_circ) - self.play(d_circ.scale, 0.5) - self.wait() - - # Prob label - eq = VGroup( - TexMobject("P(\\text{Infection}) = "), - DecimalNumber(0.2), - ) - eq.arrange(RIGHT, buff=0.2) - eq.to_edge(UP) - - arrow = Vector(0.5 * RIGHT) - arrow.next_to(eq, RIGHT) - new_rhs = eq[1].copy() - new_rhs.next_to(arrow, RIGHT) - new_rhs.set_color(YELLOW) - - self.play(FadeIn(eq)) - self.play( - TransformFromCopy(eq[1], new_rhs), - GrowArrow(arrow) - ) - self.play(ChangeDecimalToValue(new_rhs, 0.1)) - self.wait(2) - - # Each day - clock = Clock() - clock.set_height(1) - clock.next_to(normy, UR, buff=0.7) - - def get_clock_run(clock): - return ClockPassesTime( - clock, - hours_passed=1, - run_time=1, - ) - - self.play( - VFadeIn(clock), - get_clock_run(clock), - ) - - # Random choice - choices = VGroup() - for x in range(9): - choices.add(TexMobject(CMARK_TEX, color=BLUE)) - for x in range(1): - choices.add(TexMobject(XMARK_TEX, color=RED)) - choices.arrange(DOWN) - choices.set_height(3) - choices.next_to(clock, DOWN) - - rect = SurroundingRectangle(choices[0]) - self.add(choices, rect) - - def show_random_choice(scene, rect, choices): - for x in range(10): - rect.move_to(random.choice(choices[:-1])) - scene.wait(0.1) - - show_random_choice(self, rect, choices) - - for x in range(6): - self.play(get_clock_run(clock)) - show_random_choice(self, rect, choices) - rect.move_to(choices[-1]) - normy.set_status("I") - self.add(normy) - self.play( - FadeOut(clock), - FadeOut(choices), - FadeOut(rect), - ) - self.wait(4) - - -class KeyTakeaways(Scene): - def construct(self): - takeaways = VGroup(*[ - TextMobject( - text, - alignment="", - ) - for text in [ - """ - Changes in how many people slip through the tests\\\\ - cause disproportionately large changes to the total\\\\ - number of people infected. - """, - """ - Social distancing slows the spread, but\\\\ - even small imperfections prolong it. - """, - """ - Reducing transit between communities, late in the game,\\\\ - has a very limited effect. - """, - """ - Shared central locations dramatically speed up\\\\ - the spread. - """, - ] - ]) - takeaway = TextMobject( - "The growth rate is very sensitive to:\\\\", - "- \\# Daily interactions\\\\", - "- Probability of infection\\\\", - "- Duration of illness\\\\", - alignment="", - ) - takeaway[1].set_color(GREEN_D) - takeaway[2].set_color(GREEN_C) - takeaway[3].set_color(GREEN_B) - takeaway.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - takeaways.add_to_back(takeaway) - - takeaways.scale(1.25) - - titles = VGroup() - for i in range(len(takeaways)): - title = TextMobject("Key takeaway \\#") - num = Integer(i + 1) - num.next_to(title, RIGHT, buff=SMALL_BUFF) - title.add(num) - titles.add(title) - - titles.arrange(DOWN, buff=LARGE_BUFF, aligned_edge=LEFT) - titles.to_edge(LEFT) - titles.set_color(GREY_D) - - h_line = Line(LEFT, RIGHT) - h_line.set_width(FRAME_WIDTH - 1) - h_line.to_edge(UP, buff=1.5) - - for takeaway in takeaways: - takeaway.next_to(h_line, DOWN, buff=0.75) - max_width = FRAME_WIDTH - 2 - if takeaway.get_width() > max_width: - takeaway.set_width(max_width) - - takeaways[3].shift(0.25 * UP) - - self.add(titles) - self.wait() - - for title, takeaway in zip(titles, takeaways): - other_titles = VGroup(*titles) - other_titles.remove(title) - self.play(title.set_color, WHITE, run_time=0.5) - title.save_state() - temp_h_line = h_line.copy() - self.play( - title.scale, 1.5, - title.to_edge, UP, - title.set_x, 0, - title.set_color, YELLOW, - FadeOut(other_titles), - ShowCreation(temp_h_line), - ) - - self.play(FadeIn(takeaway, lag_ratio=0.1, run_time=2, rate_func=linear)) - self.wait(2) - temp_h_line.rotate(PI) - self.play( - Restore(title), - FadeIn(other_titles), - Uncreate(temp_h_line), - FadeOutAndShift(takeaway, DOWN, lag_ratio=0.25 / len(takeaway.family_members_with_points())) - ) - self.wait() - - self.embed() - - -class AsymptomaticCases(Scene): - def construct(self): - pis = VGroup(*[ - PiPerson( - height=1, - infection_radius=2, - wander_step_size=0, - max_speed=0, - ) - for x in range(5) - ]) - pis.arrange(RIGHT, buff=2) - pis.to_edge(DOWN, buff=2) - - sneaky = pis[1] - sneaky.p_symptomatic_on_infection = 0 - - self.add(pis) - - for pi in pis: - if pi is sneaky: - pi.color_map["I"] = YELLOW - pi.mode_map["I"] = "coin_flip_1" - else: - pi.color_map["I"] = RED - pi.mode_map["I"] = "sick" - pi.unlock_triangulation() - pi.set_status("I") - self.wait(0.1) - self.wait(2) - - label = TextMobject("Never isolated") - label.set_height(0.8) - label.to_edge(UP) - label.set_color(YELLOW) - - arrow = Arrow( - label.get_bottom(), - sneaky.body.get_top(), - buff=0.5, - max_tip_length_to_length_ratio=0.5, - stroke_width=6, - max_stroke_width_to_length_ratio=10, - ) - - self.play( - FadeInFromDown(label), - GrowArrow(arrow), - ) - self.wait(13) - - -class WeDontHaveThat(TeacherStudentsScene): - def construct(self): - self.student_says( - "But we don't\\\\have that!", - target_mode="angry", - added_anims=[self.teacher.change, "guilty"] - ) - self.change_all_student_modes( - "angry", - look_at_arg=self.teacher.eyes - ) - self.wait(5) - - -class IntroduceSocialDistancing(Scene): - def construct(self): - pis = VGroup(*[ - PiPerson( - height=2, - wander_step_size=0, - gravity_well=None, - social_distance_color_threshold=5, - max_social_distance_stroke_width=10, - dl_bound=[-FRAME_WIDTH / 2 + 1, -2], - ur_bound=[FRAME_WIDTH / 2 - 1, 2], - ) - for x in range(3) - ]) - pis.arrange(RIGHT, buff=0.25) - pis.move_to(DOWN) - pi1, pi2, pi3 = pis - - slider = ValueSlider( - "Social distance factor", - 0, - x_min=0, - x_max=5, - tick_frequency=1, - numbers_to_show=range(6), - marker_color=YELLOW, - ) - slider.center() - slider.to_edge(UP) - self.add(slider) - - def update_pi(pi): - pi.social_distance_factor = 4 * slider.p2n(slider.marker.get_center()) - - for pi in pis: - pi.add_updater(update_pi) - pi.repulsion_points = [ - pi2.get_center() - for pi2 in pis - if pi2 is not pi - ] - - self.add(pis) - self.play( - FadeIn(slider), - *[ - ApplyMethod(pi1.body.look_at, pi2.body.eyes) - for pi1, pi2 in zip(pis, [*pis[1:], pis[0]]) - ] - ) - self.add(*pis) - self.wait() - self.play(slider.get_change_anim(3)) - self.wait(4) - - for i, vect in (0, RIGHT), (2, LEFT): - pis.suspend_updating() - pis[1].generate_target() - pis[1].target.next_to(pis[i], vect, SMALL_BUFF) - pis[1].target.body.look_at(pis[i].body.eyes) - self.play( - MoveToTarget(pis[1]), - path_arc=PI, - ) - pis.resume_updating() - self.wait(5) - self.wait(5) - - self.embed() - - -class FastForwardBy2(Scene): - CONFIG = { - "n": 2, - } - - def construct(self): - n = self.n - triangles = VGroup(*[ - ArrowTip(start_angle=0) - for x in range(n) - ]) - triangles.arrange(RIGHT, buff=0.01) - - label = VGroup(TexMobject("\\times"), Integer(n)) - label.set_height(0.4) - label.arrange(RIGHT, buff=SMALL_BUFF) - label.next_to(triangles, RIGHT, buff=SMALL_BUFF) - - for mob in triangles, label: - mob.set_color(GREY_A) - mob.set_stroke(BLACK, 4, background=True) - - self.play( - LaggedStartMap( - FadeInFrom, triangles, - lambda m: (m, 0.4 * LEFT), - ), - FadeInFrom(label, 0.2 * LEFT), - run_time=1, - ) - self.play( - FadeOut(label), - FadeOut(triangles), - ) - - -class FastForwardBy4(FastForwardBy2): - CONFIG = { - "n": 4, - } - - -class DontLetThisHappen(Scene): - def construct(self): - text = TextMobject("Don't let\\\\this happen!") - text.scale(1.5) - text.set_stroke(BLACK, 5, background=True) - arrow = Arrow( - text.get_top(), - text.get_top() + 2 * UR + 0.5 * RIGHT, - path_arc=-120 * DEGREES, - ) - - self.add(text) - self.play( - Write(text, run_time=1), - ShowCreation(arrow), - ) - self.wait() - - -class ThatsNotHowIBehave(TeacherStudentsScene): - def construct(self): - self.student_says( - "That's...not\\\\how I behave.", - target_mode="sassy", - look_at_arg=self.screen, - ) - self.play( - self.teacher.change, "guilty", - self.get_student_changes("erm", "erm", "sassy") - ) - self.look_at(self.screen) - self.wait(20) - - -class BetweenNothingAndQuarantineWrapper(Scene): - def construct(self): - self.add(FullScreenFadeRectangle( - fill_color=GREY_E, - fill_opacity=1, - )) - rects = VGroup(*[ - ScreenRectangle() - for x in range(3) - ]) - rects.arrange(RIGHT) - rects.set_width(FRAME_WIDTH - 1) - self.add(rects) - - rects.set_fill(BLACK, 1) - rects.set_stroke(WHITE, 2) - - titles = VGroup( - TextMobject("Do nothing"), - TextMobject("Quarantine\\\\", "80$\\%$ of cases"), - TextMobject("Quarantine\\\\", "all cases"), - ) - fix_percent(titles[1][1][2]) - for title, rect in zip(titles, rects): - title.next_to(rect, UP) - - q_marks = TexMobject("???") - q_marks.scale(2) - q_marks.move_to(rects[1]) - - self.add(rects) - self.play(LaggedStartMap( - FadeInFromDown, - VGroup(titles[0], titles[2], titles[1]), - lag_ratio=0.3, - )) - self.play(Write(q_marks)) - self.wait() - - -class DarkerInterpretation(Scene): - def construct(self): - qz = TextMobject("Quarantine zone") - qz.set_color(RED) - qz.set_width(2) - line = Line(qz.get_left(), qz.get_right()) - - new_words = TextMobject("Deceased") - new_words.replace(qz, dim_to_match=1) - new_words.set_color(RED) - - self.add(qz) - self.wait() - self.play(ShowCreation(line)) - self.play( - FadeOut(qz), - FadeOut(line), - FadeIn(new_words) - ) - self.wait() - - -class SARS2002(TeacherStudentsScene): - def construct(self): - image = ImageMobject("sars_icon") - image.set_height(3.5) - image.move_to(self.hold_up_spot, DR) - image.shift(RIGHT) - - name = TextMobject("2002 SARS Outbreak") - name.next_to(image, LEFT, MED_LARGE_BUFF, aligned_edge=UP) - - n_cases = VGroup( - Integer(0, edge_to_fix=UR), - TextMobject("total cases") - ) - n_cases.arrange(RIGHT) - n_cases.scale(1.25) - n_cases.next_to(name, DOWN, buff=2) - - arrow = Arrow(name.get_bottom(), n_cases.get_top()) - - n_cases.shift(MED_SMALL_BUFF * RIGHT) - - self.play( - self.teacher.change, "raise_right_hand", - FadeInFrom(image, DOWN, run_time=2), - self.get_student_changes( - "pondering", "thinking", "pondering", - look_at_arg=image, - ) - ) - self.play( - FadeInFrom(name, RIGHT), - ) - self.play( - GrowArrow(arrow), - UpdateFromAlphaFunc( - n_cases, - lambda m, a: m.set_opacity(a), - ), - ChangeDecimalToValue(n_cases[0], 8098), - self.get_student_changes(look_at_arg=n_cases), - ) - self.wait() - self.change_all_student_modes( - "thinking", look_at_arg=n_cases, - ) - self.play(self.teacher.change, "tease") - self.wait(6) - - self.embed() - - -class QuarteringLines(Scene): - def construct(self): - lines = VGroup( - Line(UP, DOWN), - Line(LEFT, RIGHT), - ) - lines.set_width(FRAME_WIDTH) - lines.set_height(FRAME_HEIGHT, stretch=True) - lines.set_stroke(WHITE, 3) - self.play(ShowCreation(lines)) - self.wait() - - -class Eradicated(Scene): - def construct(self): - word = TextMobject("Eradicated") - word.set_color(GREEN) - self.add(word) - - -class LeftArrow(Scene): - def construct(self): - arrow = Vector(2 * LEFT) - self.play(GrowArrow(arrow)) - self.wait() - self.play(FadeOut(arrow)) - - -class IndicationArrow(Scene): - def construct(self): - vect = Vector( - 0.5 * DR, - max_tip_length_to_length_ratio=0.4, - max_stroke_width_to_length_ratio=10, - stroke_width=5, - ) - vect.set_color(YELLOW) - self.play(GrowArrow(vect)) - self.play(FadeOut(vect)) - - -class REq(Scene): - def construct(self): - mob = TexMobject("R_0 = ")[0] - mob[1].set_color(BLACK) - mob[2].shift(mob[1].get_width() * LEFT * 0.7) - self.add(mob) - - -class IntroduceR0(Scene): - def construct(self): - # Infect - pis = VGroup(*[ - PiPerson( - height=0.5, - infection_radius=1.5, - wander_step_size=0, - max_speed=0, - ) - for x in range(5) - ]) - - pis[:4].arrange(RIGHT, buff=2) - pis[:4].to_edge(DOWN, buff=2) - sicky = pis[4] - sicky.move_to(2 * UP) - sicky.set_status("I") - - pis[1].set_status("R") - for anim in pis[1].change_anims: - pis[1].pop_anim(anim) - - count = VGroup( - TextMobject("Infection count: "), - Integer(0) - ) - count.arrange(RIGHT, aligned_edge=DOWN) - count.to_corner(UL) - - self.add(pis) - self.add(count) - self.wait(2) - - for pi in pis[:4]: - point = VectorizedPoint(sicky.get_center()) - self.play( - point.move_to, pi.get_right() + 0.25 * RIGHT, - MaintainPositionRelativeTo(sicky, point), - run_time=0.5, - ) - if pi.status == "S": - count[1].increment_value() - count[1].set_color(WHITE) - pi.set_status("I") - self.play( - Flash( - sicky.get_center(), - color=RED, - line_length=0.3, - flash_radius=0.7, - ), - count[1].set_color, RED, - ) - self.wait() - - # Attach count to sicky - self.play( - point.move_to, 2 * UP, - MaintainPositionRelativeTo(sicky, point), - ) - count_copy = count[1].copy() - self.play( - count_copy.next_to, sicky.body, UR, - count_copy.set_color, WHITE, - ) - self.wait() - - # Zeros - zeros = VGroup() - for pi in pis[:4]: - if pi.status == "I": - zero = Integer(0) - zero.next_to(pi.body, UR) - zeros.add(zero) - - # R label - R_label = TextMobject("Average :=", " $R$") - R_label.set_color_by_tex("R", YELLOW) - R_label.next_to(count, DOWN, buff=1.5) - - arrow = Arrow(R_label[0].get_top(), count.get_bottom()) - - self.play( - LaggedStartMap(FadeInFromDown, zeros), - GrowArrow(arrow), - FadeIn(R_label), - ) - - brace = Brace(R_label[1], DOWN) - name = TextMobject("``Effective reproductive number''") - name.set_color(YELLOW) - name.next_to(brace, DOWN) - name.to_edge(LEFT) - self.play( - GrowFromCenter(brace), - FadeInFrom(name, 0.5 * UP), - ) - self.wait(5) - - # R0 - brr = TextMobject("``Basic reproductive number''") - brr.set_color(TEAL) - brr.move_to(name, LEFT) - R0 = TexMobject("R_0") - R0.move_to(R_label[1], UL) - R0.set_color(TEAL) - - for pi in pis[:4]: - pi.set_status("S") - - self.play( - FadeOutAndShift(R_label[1], UP), - FadeOutAndShift(name, UP), - FadeInFrom(R0, DOWN), - FadeInFrom(brr, DOWN), - FadeOut(zeros), - FadeOut(count_copy), - brace.match_width, R0, {"stretch": True}, - brace.match_x, R0, - ) - self.wait() - - # Copied from above - count[1].set_value(0) - - for pi in pis[:4]: - point = VectorizedPoint(sicky.get_center()) - self.play( - point.move_to, pi.body.get_right() + 0.25 * RIGHT, - MaintainPositionRelativeTo(sicky, point), - run_time=0.5, - ) - count[1].increment_value() - count[1].set_color(WHITE) - pi.set_status("I") - self.play( - Flash( - sicky.get_center(), - color=RED, - line_length=0.3, - flash_radius=0.7, - ), - count[1].set_color, RED, - ) - self.play( - point.move_to, 2 * UP, - MaintainPositionRelativeTo(sicky, point), - ) - self.wait(4) - - -class HowR0IsCalculatedHere(Scene): - def construct(self): - words = VGroup( - TextMobject("Count ", "\\# transfers"), - TextMobject("for every infectious case") - ) - words.arrange(DOWN) - words[1].set_color(RED) - words.to_edge(UP) - - estimate = TextMobject("Estimate") - estimate.move_to(words[0][0], RIGHT) - - lp, rp = parens = TexMobject("(", ")") - parens.match_height(words) - lp.next_to(words, LEFT, SMALL_BUFF) - rp.next_to(words, RIGHT, SMALL_BUFF) - average = TextMobject("Average") - average.scale(1.5) - average.next_to(parens, LEFT, SMALL_BUFF) - - words.save_state() - words[1].move_to(words[0]) - words[0].set_opacity(0) - - self.add(FullScreenFadeRectangle(fill_color=GREY_E, fill_opacity=1).scale(2)) - self.wait() - self.play(Write(words[1], run_time=1)) - self.wait() - self.add(words) - self.play(Restore(words)) - self.wait() - self.play( - FadeOutAndShift(words[0][0], UP), - FadeInFrom(estimate, DOWN), - ) - self.wait() - - self.play( - Write(parens), - FadeInFrom(average, 0.5 * RIGHT), - self.camera.frame.shift, LEFT, - ) - self.wait() - - self.embed() - - -class R0Rect(Scene): - def construct(self): - rect = SurroundingRectangle(TextMobject("R0 = 2.20")) - rect.set_stroke(YELLOW, 4) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - - -class DoubleInfectionRadius(Scene): - CONFIG = { - "random_seed": 1, - } - - def construct(self): - c1 = Circle(radius=0.25, color=RED) - c2 = Circle(radius=0.5, color=RED) - arrow = Vector(RIGHT) - c1.next_to(arrow, LEFT) - c2.next_to(arrow, RIGHT) - - title = TextMobject("Double the\\\\infection radius") - title.next_to(VGroup(c1, c2), UP) - - self.add(c1, title) - self.wait() - self.play( - GrowArrow(arrow), - TransformFromCopy(c1, c2), - ) - self.wait() - - c2.label = TextMobject("4x area") - c2.label.scale(0.5) - c2.label.next_to(c2, DOWN) - - for circ, count in (c1, 4), (c2, 16): - dots = VGroup() - for x in range(count): - dot = Dot(color=BLUE) - dot.set_stroke(BLACK, 2, background=True) - dot.set_height(0.05) - vect = rotate_vector(RIGHT, TAU * random.random()) - vect *= 0.9 * random.random() * circ.get_height() / 2 - dot.move_to(circ.get_center() + vect) - dots.add(dot) - circ.dot = dots - anims = [ShowIncreasingSubsets(dots)] - if hasattr(circ, "label"): - anims.append(FadeInFrom(circ.label, 0.5 * UP)) - self.play(*anims) - self.wait() - - -class PInfectionSlider(Scene): - def construct(self): - slider = ValueSlider( - "Probability of infection", - 0.2, - x_min=0, - x_max=0.2, - numbers_to_show=np.arange(0.05, 0.25, 0.05), - decimal_number_config={ - "num_decimal_places": 2, - }, - tick_frequency=0.05, - ) - self.add(slider) - self.wait() - self.play(slider.get_change_anim(0.1)) - self.wait() - self.play(slider.get_change_anim(0.05)) - self.wait() - - -class R0Categories(Scene): - def construct(self): - # Titles - titles = VGroup( - TexMobject("R > 1", color=RED), - TexMobject("R = 1", color=YELLOW), - TexMobject("R < 1", color=GREEN), - ) - titles.scale(1.25) - titles.arrange(RIGHT, buff=2.7) - titles.to_edge(UP) - - v_lines = VGroup() - for t1, t2 in zip(titles, titles[1:]): - v_line = Line(UP, DOWN) - v_line.set_height(FRAME_HEIGHT) - v_line.set_x(midpoint(t1.get_right(), t2.get_left())[0]) - v_lines.add(v_line) - - self.add(titles) - self.add(v_lines) - - # Names - names = VGroup( - TextMobject("Epidemic", color=RED), - TextMobject("Endemic", color=YELLOW), - TextMobject("...Hypodemic?", color=GREEN), - ) - for name, title in zip(names, titles): - name.next_to(title, DOWN, MED_LARGE_BUFF) - - # Doubling dots - dot = Dot(color=RED) - dot.next_to(names[0], DOWN, LARGE_BUFF) - rows = VGroup(VGroup(dot)) - lines = VGroup() - for x in range(4): - new_row = VGroup() - new_lines = VGroup() - for dot in rows[-1]: - dot.children = [dot.copy(), dot.copy()] - new_row.add(*dot.children) - new_row.arrange(RIGHT) - max_width = 4 - if new_row.get_width() > max_width: - new_row.set_width(max_width) - new_row.next_to(rows[-1], DOWN, LARGE_BUFF) - for dot in rows[-1]: - for child in dot.children: - new_lines.add(Line( - dot.get_center(), - child.get_center(), - buff=0.2, - )) - rows.add(new_row) - lines.add(new_lines) - - for row, line_row in zip(rows[:-1], lines): - self.add(row) - anims = [] - for dot in row: - for child in dot.children: - anims.append(TransformFromCopy(dot, child)) - for line in line_row: - anims.append(ShowCreation(line)) - self.play(*anims) - self.play(FadeInFrom(names[0], UP)) - self.wait() - - exp_tree = VGroup(rows, lines) - - # Singleton dots - mid_rows = VGroup() - mid_lines = VGroup() - - for row in rows: - dot = Dot(color=RED) - dot.match_y(row) - mid_rows.add(dot) - - for d1, d2 in zip(mid_rows[:-1], mid_rows[1:]): - d1.child = d2 - mid_lines.add(Line( - d1.get_center(), - d2.get_center(), - buff=0.2, - )) - - for dot, line in zip(mid_rows[:-1], mid_lines): - self.add(dot) - self.play( - TransformFromCopy(dot, dot.child), - ShowCreation(line) - ) - self.play(FadeInFrom(names[1], UP)) - self.wait() - - # Inverted tree - exp_tree_copy = exp_tree.copy() - exp_tree_copy.rotate(PI) - exp_tree_copy.match_x(titles[2]) - - self.play(TransformFromCopy(exp_tree, exp_tree_copy)) - self.play(FadeInFrom(names[2], UP)) - self.wait() - - -class RealR0Estimates(Scene): - def construct(self): - labels = VGroup( - TextMobject("COVID-19\\\\", "$R_0 \\approx 2$"), - TextMobject("1918 Spanish flu\\\\", "$R_0 \\approx 2$"), - TextMobject("Usual seasonal flu\\\\", "$R_0 \\approx 1.3$"), - ) - images = Group( - ImageMobject("COVID-19_Map"), - ImageMobject("spanish_flu"), - ImageMobject("Influenza"), - ) - for image in images: - image.set_height(3) - - images.arrange(RIGHT, buff=0.5) - images.to_edge(UP, buff=2) - - for label, image in zip(labels, images): - label.next_to(image, DOWN) - - for label, image in zip(labels, images): - self.play( - FadeInFrom(image, DOWN), - FadeInFrom(label, UP), - ) - self.wait() - - self.embed() - - -class WhyChooseJustOne(TeacherStudentsScene): - def construct(self): - self.student_says( - "Why choose\\\\just one?", - target_mode="sassy", - added_anims=[self.teacher.change, "thinking"], - run_time=1, - ) - self.play( - self.get_student_changes( - "confused", "confused", "sassy", - ), - ) - self.wait(2) - self.teacher_says( - "For science!", target_mode="hooray", - look_at_arg=self.students[2].eyes, - ) - self.change_student_modes("hesitant", "hesitant", "hesitant") - self.wait(3) - - self.embed() - - -class NickyCaseMention(Scene): - def construct(self): - words = TextMobject( - "Artwork by Nicky Case\\\\", - "...who is awesome." - ) - words.scale(1) - words.to_edge(LEFT) - arrow = Arrow( - words.get_top(), - words.get_top() + 2 * UR + RIGHT, - path_arc=-90 * DEGREES, - ) - self.play( - Write(words[0]), - ShowCreation(arrow), - run_time=1, - ) - self.wait(2) - self.play(Write(words[1]), run_time=1) - self.wait() - - -class PonderingRandy(Scene): - def construct(self): - randy = Randolph() - self.play(FadeIn(randy)) - self.play(randy.change, "pondering") - for x in range(3): - self.play(Blink(randy)) - self.wait(2) - - -class WideSpreadTesting(Scene): - def construct(self): - # Add dots - dots = VGroup(*[ - DotPerson( - height=0.2, - infection_radius=0.6, - max_speed=0, - wander_step_size=0, - p_symptomatic_on_infection=0.8, - ) - for x in range(600) - ]) - dots.arrange_in_grid(20, 30) - dots.set_height(FRAME_HEIGHT - 1) - - self.add(dots) - sick_dots = VGroup() - for x in range(36): - sicky = random.choice(dots) - sicky.set_status("I") - sick_dots.add(sicky) - self.wait(0.1) - self.wait(2) - - healthy_dots = VGroup() - for dot in dots: - if dot.status != "I": - healthy_dots.add(dot) - - # Show Flash - rings = self.get_rings(ORIGIN, FRAME_WIDTH + FRAME_HEIGHT, 0.1) - rings.shift(7 * LEFT) - for i, ring in enumerate(rings): - ring.shift(0.05 * i**1.2 * RIGHT) - - self.play(LaggedStartMap( - FadeIn, rings, - lag_ratio=3 / len(rings), - run_time=2.5, - rate_func=there_and_back, - )) - - # Quarantine - box = Square() - box.set_height(2) - box.to_corner(DL) - box.shift(LEFT) - - anims = [] - points = VGroup() - points_target = VGroup() - for dot in sick_dots: - point = VectorizedPoint(dot.get_center()) - point.generate_target() - points.add(point) - points_target.add(point.target) - - dot.push_anim(MaintainPositionRelativeTo(dot, point, run_time=3)) - anims.append(MoveToTarget(point)) - - points_target.arrange_in_grid() - points_target.set_width(box.get_width() - 1) - points_target.move_to(box) - - self.play( - ShowCreation(box), - LaggedStartMap(MoveToTarget, points, lag_ratio=0.05), - self.camera.frame.shift, LEFT, - run_time=3, - ) - self.wait(9) - - def get_rings(self, center, max_radius, delta_r): - radii = np.arange(0, max_radius, delta_r) - rings = VGroup(*[ - Circle( - radius=r, - stroke_opacity=0.75 * (1 - fdiv(r, max_radius)), - stroke_color=TEAL, - stroke_width=100 * delta_r, - ) - for r in radii - ]) - rings.move_to(center) - return rings - - -class VirusSpreading(Scene): - def construct(self): - virus = SVGMobject(file_name="virus") - virus.lock_triangulation() - virus.set_fill(RED_E, 1) - virus.set_stroke([RED, WHITE], width=0) - height = 3 - virus.set_height(height) - - self.play(DrawBorderThenFill(virus)) - - viruses = VGroup(virus) - - for x in range(8): - height *= 0.8 - anims = [] - new_viruses = VGroup() - for virus in viruses: - children = [virus.copy(), virus.copy()] - for child in children: - child.set_height(height) - child.set_color(interpolate_color( - RED_E, - GREY_D, - 0.7 * random.random(), - )) - child.shift([ - (random.random() - 0.5) * 3, - (random.random() - 0.5) * 3, - 0, - ]) - anims.append(TransformFromCopy(virus, child)) - new_viruses.add(child) - new_viruses.center() - self.remove(viruses) - self.play(*anims, run_time=0.5) - viruses.set_submobjects(list(new_viruses)) - self.wait() - - # Eliminate - for virus in viruses: - virus.generate_target() - virus.target.scale(3) - virus.target.set_color(WHITE) - virus.target.set_opacity(0) - - self.play(LaggedStartMap( - MoveToTarget, viruses, - run_time=8, - lag_ratio=3 / len(viruses) - )) - - -class GraciousPi(Scene): - def construct(self): - morty = Mortimer() - morty.flip() - self.play(FadeIn(morty)) - self.play(morty.change, "hesitant") - self.play(Blink(morty)) - self.wait() - self.play(morty.change, "gracious", morty.get_bottom()) - self.play(Blink(morty)) - self.wait(2) - self.play(Blink(morty)) - self.wait(2) - - -class SIREndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "1stViewMaths", - "Adam Dřínek", - "Aidan Shenkman", - "Alan Stein", - "Alex Mijalis", - "Alexis Olson", - "Ali Yahya", - "Andrew Busey", - "Andrew Cary", - "Andrew R. Whalley", - "Aravind C V", - "Arjun Chakroborty", - "Arthur Zey", - "Ashwin Siddarth", - "Austin Goodman", - "Avi Finkel", - "Awoo", - "Axel Ericsson", - "Ayan Doss", - "AZsorcerer", - "Barry Fam", - "Ben Delo", - "Bernd Sing", - "Boris Veselinovich", - "Bradley Pirtle", - "Brandon Huang", - "Brian Staroselsky", - "Britt Selvitelle", - "Britton Finley", - "Burt Humburg", - "Calvin Lin", - "Charles Southerland", - "Charlie N", - "Chenna Kautilya", - "Chris Connett", - "Christian Kaiser", - "cinterloper", - "Clark Gaebel", - "Colwyn Fritze-Moor", - "Cooper Jones", - "Corey Ogburn", - "D. Sivakumar", - "Dan Herbatschek", - "Daniel Herrera C", - "Dave B", - "Dave Kester", - "dave nicponski", - "David B. Hill", - "David Clark", - "David Gow", - "Delton Ding", - "Dominik Wagner", - "Eddie Landesberg", - "emptymachine", - "Eric Younge", - "Eryq Ouithaqueue", - "Farzaneh Sarafraz", - "Federico Lebron", - "Frank R. Brown, Jr.", - "Giovanni Filippi", - "Goodwine Carlos", - "Hal Hildebrand", - "Hitoshi Yamauchi", - "Ivan Sorokin", - "Jacob Baxter", - "Jacob Harmon", - "Jacob Hartmann", - "Jacob Magnuson", - "Jake Vartuli - Schonberg", - "Jalex Stark", - "Jameel Syed", - "Jason Hise", - "Jayne Gabriele", - "Jean-Manuel Izaret", - "Jeff Linse", - "Jeff Straathof", - "Jimmy Yang", - "John C. Vesey", - "John Camp", - "John Haley", - "John Le", - "John Rizzo", - "John V Wertheim", - "Jonathan Heckerman", - "Jonathan Wilson", - "Joseph John Cox", - "Joseph Kelly", - "Josh Kinnear", - "Joshua Claeys", - "Juan Benet", - "Kai-Siang Ang", - "Kanan Gill", - "Karl Niu", - "Kartik Cating-Subramanian", - "Kaustuv DeBiswas", - "Killian McGuinness", - "Kros Dai", - "L0j1k", - "Lael S Costa", - "LAI Oscar", - "Lambda GPU Workstations", - "Lee Redden", - "Linh Tran", - "lol I totally forgot I had a patreon", - "Luc Ritchie", - "Ludwig Schubert", - "Lukas Biewald", - "Magister Mugit", - "Magnus Dahlström", - "Manoj Rewatkar - RITEK SOLUTIONS", - "Mark B Bahu", - "Mark Heising", - "Mark Mann", - "Martin Price", - "Mathias Jansson", - "Matt Godbolt", - "Matt Langford", - "Matt Roveto", - "Matt Russell", - "Matteo Delabre", - "Matthew Bouchard", - "Matthew Cocke", - "Maxim Nitsche", - "Mia Parent", - "Michael Hardel", - "Michael W White", - "Mirik Gogri", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nate Heckmann", - "Nicholas Cahill", - "Nikita Lesnikov", - "Oleg Leonov", - "Oliver Steele", - "Omar Zrien", - "Owen Campbell-Moore", - "Patrick Lucas", - "Pavel Dubov", - "Petar Veličković", - "Peter Ehrnstrom", - "Peter Mcinerney", - "Pierre Lancien", - "Pradeep Gollakota", - "Quantopian", - "Rafael Bove Barrios", - "Randy C. Will", - "rehmi post", - "Rex Godby", - "Ripta Pasay", - "Rish Kundalia", - "Roman Sergeychik", - "Roobie", - "Ryan Atallah", - "Samuel Judge", - "SansWord Huang", - "Scott Gray", - "Scott Walter, Ph.D.", - "soekul", - "Solara570", - "Steve Huynh", - "Steve Sperandeo", - "Steven Siddals", - "Stevie Metke", - "Still working on an upcoming skeptical humanist SciFi novels- Elux Luc", - "supershabam", - "Suteerth Vishnu", - "Suthen Thomas", - "Tal Einav", - "Taras Bobrovytsky", - "Tauba Auerbach", - "Ted Suzman", - "Thomas J Sargent", - "Thomas Tarler", - "Tianyu Ge", - "Tihan Seale", - "Tyler VanValkenburg", - "Vassili Philippov", - "Veritasium", - "Vignesh Ganapathi Subramanian", - "Vinicius Reis", - "Xuanji Li", - "Yana Chernobilsky", - "Yavor Ivanov", - "YinYangBalance.Asia", - "Yu Jun", - "Yurii Monastyrshyn", - ], - } - - -# For assembling script -# SCENES_IN_ORDER = [ -WILL_BE_SCENES_IN_ORDER = [ - # Quarantining - QuarantineInfectious, - QuarantineInfectiousLarger, - QuarantineInfectiousLarger80p, - QuarantineInfectiousTravel, - QuarantineInfectiousTravel80p, - QuarantineInfectiousTravel50p, - # Social distancing - SimpleSocialDistancing, - DelayedSocialDistancing, # Maybe remove? - DelayedSocialDistancingLargeCity, # Probably don't use - SimpleTravelSocialDistancePlusZeroTravel, - SimpleTravelDelayedSocialDistancing99p, - SimpleTravelDelayedTravelReductionThreshold100TargetHalfPercent, - SimpleTravelDelayedSocialDistancing50pThreshold100, - SimpleTravelDelayedTravelReductionThreshold100, # Maybe don't use - # Describing R0 - LargerCity2, - LargeCityHighInfectionRadius, - LargeCityLowerInfectionRate, - SimpleTravelDelayedSocialDistancing99p, # Need to re-render - # Market - CentralMarketLargePopulation, - CentralMarketLowerInfection, - CentralMarketVeryFrequentLargePopulationDelayedSocialDistancing, - CentralMarketLessFrequent, - CentralMarketTransitionToLowerInfection, - CentralMarketTransitionToLowerInfectionAndLowerFrequency, - CentralMarketQuarantine, - CentralMarketQuarantine80p, -] - - -to_run_later = [ - CentralMarketTransitionToLowerInfectionAndLowerFrequency, - SimpleTravelDelayedTravelReduction, - CentralMarketLargePopulation, - CentralMarketLowerInfection, - CentralMarketVeryFrequentLargePopulationDelayedSocialDistancing, - CentralMarketLessFrequent, - CentralMarketTransitionToLowerInfection, - CentralMarketTransitionToLowerInfectionAndLowerFrequency, - CentralMarketQuarantine, - CentralMarketQuarantine80p, - SecondWave, -] diff --git a/from_3b1b/old/256.py b/from_3b1b/old/256.py deleted file mode 100644 index 9e22af87..00000000 --- a/from_3b1b/old/256.py +++ /dev/null @@ -1,912 +0,0 @@ -from manimlib.imports import * - -from from_3b1b.old.crypto import sha256_tex_mob, bit_string_to_mobject, BitcoinLogo - -def get_google_logo(): - result = SVGMobject( - file_name = "google_logo", - height = 0.75 - ) - blue, red, yellow, green = [ - "#4885ed", "#db3236", "#f4c20d", "#3cba54" - ] - colors = [red, yellow, blue, green, red, blue] - result.set_color_by_gradient(*colors) - return result - -class LastVideo(Scene): - def construct(self): - title = TextMobject("Crypto", "currencies", arg_separator = "") - title[0].set_color(YELLOW) - title.scale(1.5) - title.to_edge(UP) - screen_rect = ScreenRectangle(height = 6) - screen_rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(screen_rect)) - self.wait() - -class BreakUp2To256(PiCreatureScene): - def construct(self): - self.initialize_bits() - self.add_number() - self.break_up_as_powers_of_two() - self.break_up_as_four_billions() - self.reorganize_four_billions() - - def initialize_bits(self): - bits = bit_string_to_mobject("") - bits.to_corner(UP+LEFT) - one = TexMobject("1")[0] - one.replace(bits[0], dim_to_match = 1) - self.add(bits) - self.add_foreground_mobject(VGroup(*bits[-15:])) - self.number = 0 - self.bits = bits - self.one = one - self.zero = bits[0].copy() - - def add_number(self): - brace = Brace(self.bits, RIGHT) - - number, possibilities = expression = TextMobject( - "$2^{256}$", "possibilities" - ) - number.set_color(YELLOW) - expression.next_to(brace, RIGHT) - - words = TextMobject("Seems big...I guess...") - words.next_to(self.pi_creature.get_corner(UP+LEFT), UP) - - self.play( - self.pi_creature.change, "raise_right_hand", - GrowFromCenter(brace), - Write(expression, run_time = 1) - ) - self.wait() - self.play( - self.pi_creature.change, "maybe", - Write(words) - ) - self.wait(2) - self.play( - self.pi_creature.change, "happy", - FadeOut(words) - ) - self.wait() - - self.expression = expression - self.bits_brace = brace - - def break_up_as_powers_of_two(self): - bits = self.bits - bits.generate_target() - subgroups = [ - VGroup(*bits.target[32*i:32*(i+1)]) - for i in range(8) - ] - subexpressions = VGroup() - for i, subgroup in enumerate(subgroups): - subgroup.shift(i*MED_LARGE_BUFF*DOWN) - subexpression = TextMobject( - "$2^{32}$", "possibilities" - ) - subexpression[0].set_color(GREEN) - subexpression.next_to(subgroup, RIGHT) - subexpressions.add(subexpression) - - self.play( - FadeOut(self.bits_brace), - ReplacementTransform( - VGroup(self.expression), - subexpressions - ), - MoveToTarget(bits) - ) - self.play(self.pi_creature.change, "pondering") - self.wait() - - - self.subexpressions = subexpressions - - def break_up_as_four_billions(self): - new_subexpressions = VGroup() - for subexpression in self.subexpressions: - new_subexpression = TextMobject( - "4 Billion", "possibilities" - ) - new_subexpression[0].set_color(YELLOW) - new_subexpression.move_to(subexpression, LEFT) - new_subexpressions.add(new_subexpression) - - self.play( - Transform( - self.subexpressions, new_subexpressions, - run_time = 2, - lag_ratio = 0.5, - ), - FadeOut(self.pi_creature) - ) - self.wait(3) - - def reorganize_four_billions(self): - target = VGroup(*[ - TextMobject( - "$\\big($", "4 Billion", "$\\big)$", - arg_separator = "" - ) - for x in range(8) - ]) - target.arrange(RIGHT, buff = SMALL_BUFF) - target.to_edge(UP) - target.set_width(FRAME_WIDTH - LARGE_BUFF) - parens = VGroup(*it.chain(*[ - [t[0], t[2]] for t in target - ])) - target_four_billions = VGroup(*[t[1] for t in target]) - target_four_billions.set_color(YELLOW) - four_billions, to_fade = [ - VGroup(*[se[i] for se in self.subexpressions]) - for i in range(2) - ] - - self.play( - self.bits.to_corner, DOWN+LEFT, - Transform(four_billions, target_four_billions), - LaggedStartMap(FadeIn, parens), - FadeOut(to_fade) - ) - self.wait() - - ###### - - def wait(self, time = 1): - self.play(Animation(self.bits, run_time = time)) - - def update_frame(self, *args, **kwargs): - self.number += 1 - new_bit_string = bin(self.number)[2:] - for i, bit in enumerate(reversed(new_bit_string)): - index = -i-1 - bit_mob = self.bits[index] - if bit == "0": - new_mob = self.zero.copy() - else: - new_mob = self.one.copy() - new_mob.replace(bit_mob, dim_to_match = 1) - Transform(bit_mob, new_mob).update(1) - Scene.update_frame(self, *args, **kwargs) - -class ShowTwoTo32(Scene): - def construct(self): - mob = TexMobject("2^{32} = 4{,}294{,}967{,}296") - mob.scale(1.5) - self.add(mob) - self.wait() - -class MainBreakdown(Scene): - CONFIG = { - "n_group_rows" : 8, - "n_group_cols" : 8, - } - def construct(self): - self.add_four_billions() - self.gpu_packed_computer() - self.kilo_google() - self.half_all_people_on_earth() - self.four_billion_earths() - self.four_billion_galxies() - self.show_time_scale() - self.show_probability() - - def add_four_billions(self): - top_line = VGroup() - four_billions = VGroup() - for x in range(8): - mob = TextMobject( - "$\\big($", "4 Billion", "$\\big)$", - arg_separator = "" - ) - top_line.add(mob) - four_billions.add(mob[1]) - top_line.arrange(RIGHT, buff = SMALL_BUFF) - top_line.set_width(FRAME_WIDTH - LARGE_BUFF) - top_line.to_edge(UP) - four_billions.set_color(YELLOW) - self.add(top_line) - - self.top_line = top_line - self.four_billions = four_billions - - def gpu_packed_computer(self): - self.show_gpu() - self.cram_computer_with_gpus() - - def show_gpu(self): - gpu = SVGMobject( - file_name = "gpu", - height = 1, - fill_color = LIGHT_GREY, - ) - name = TextMobject("Graphics", "Processing", "Unit") - for word in name: - word[0].set_color(BLUE) - name.to_edge(LEFT) - gpu.next_to(name, UP) - - hash_names = VGroup(*[ - TextMobject("hash") - for x in range(10) - ]) - hash_names.arrange(DOWN, buff = MED_SMALL_BUFF) - hash_names.next_to(name, RIGHT, buff = 2) - - paths = VGroup() - for hash_name in hash_names: - hash_name.add_background_rectangle(opacity = 0.5) - path = VMobject() - start_point = name.get_right() + SMALL_BUFF*RIGHT - end_point = start_point + (4+hash_name.get_width())*RIGHT - path.set_points([ - start_point, - start_point+RIGHT, - hash_name.get_left()+LEFT, - hash_name.get_left(), - hash_name.get_left(), - hash_name.get_right(), - hash_name.get_right(), - hash_name.get_right() + RIGHT, - end_point + LEFT, - end_point, - ]) - paths.add(path) - paths.set_stroke(width = 3) - paths.set_color_by_gradient(BLUE, GREEN) - def get_passing_flash(): - return ShowPassingFlash( - paths, - lag_ratio = 0, - time_width = 0.7, - run_time = 2, - ) - rate_words = TextMobject( - "$<$ 1 Billion", "Hashes/sec" - ) - rate_words.next_to(name, DOWN) - - self.play(FadeIn(name)) - self.play(DrawBorderThenFill(gpu)) - self.play( - get_passing_flash(), - FadeIn(hash_names) - ) - for x in range(2): - self.play( - get_passing_flash(), - Animation(hash_names) - ) - self.play( - Write(rate_words, run_time = 2), - get_passing_flash(), - Animation(hash_names) - ) - self.play(get_passing_flash(), Animation(hash_names)) - self.play(*list(map(FadeOut, [name, hash_names]))) - - self.gpu = gpu - self.rate_words = rate_words - - def cram_computer_with_gpus(self): - gpu = self.gpu - gpus = VGroup(gpu, *[gpu.copy() for x in range(5)]) - - rate_words = self.rate_words - four_billion = self.four_billions[0] - - laptop = Laptop() - laptop.next_to(rate_words, RIGHT) - laptop.to_edge(RIGHT) - new_rate_words = TextMobject("4 Billion", "Hashes/sec") - new_rate_words.move_to(rate_words) - new_rate_words[0].set_color(BLUE) - - hps, h_line, target_laptop = self.get_fraction( - 0, TextMobject("H/s"), Laptop() - ) - hps.scale_in_place(0.7) - - self.play(FadeIn(laptop)) - self.play( - gpus.arrange, RIGHT, SMALL_BUFF, - gpus.next_to, rate_words, UP, - gpus.to_edge, LEFT - ) - self.play( - Transform( - four_billion.copy(), new_rate_words[0], - remover = True, - ), - Transform(rate_words, new_rate_words) - ) - self.wait() - self.play( - LaggedStartMap( - ApplyFunction, gpus, - lambda g : ( - lambda m : m.scale(0.01).move_to(laptop), - g - ), - remover = True - ) - ) - self.wait() - self.play( - Transform( - rate_words[0], four_billion.copy().set_color(BLUE), - remover = True, - ), - four_billion.set_color, BLUE, - Transform(rate_words[1], hps), - ) - self.play( - Transform(laptop, target_laptop), - ShowCreation(h_line), - ) - self.wait() - - def kilo_google(self): - self.create_four_billion_copies(1, Laptop()) - google = self.get_google_logo() - google.next_to( - self.group_of_four_billion_things, UP, - buff = LARGE_BUFF, - aligned_edge = LEFT - ) - google.shift(RIGHT) - millions = TextMobject("$\\sim$ Millions of servers") - millions.next_to(google, RIGHT) - plus_plus = TexMobject("++") - plus_plus.next_to(google, RIGHT, SMALL_BUFF) - plus_plus.set_stroke(width = 2) - kilo = TextMobject("Kilo") - kilo.scale(1.5) - kilo.next_to(google[-1], LEFT, SMALL_BUFF, DOWN) - kilogoogle = VGroup(kilo, google, plus_plus) - - four_billion = self.four_billions[1] - laptop, h_line, target_kilogoogle = self.get_fraction( - 1, Laptop(), self.get_kilogoogle() - ) - - self.revert_to_original_skipping_status() - self.play(DrawBorderThenFill(google)) - self.wait(2) - self.play(Write(millions)) - self.wait(2) - self.play(LaggedStartMap( - Indicate, self.group_of_four_billion_things, - run_time = 4, - rate_func = there_and_back, - lag_ratio = 0.25, - )) - self.play(FadeOut(millions), FadeIn(plus_plus)) - self.play(Write(kilo)) - self.wait() - self.play( - four_billion.restore, - FadeOut(self.group_of_four_billion_things) - ) - self.play( - Transform(kilogoogle, target_kilogoogle), - FadeIn(laptop), - FadeIn(h_line), - ) - self.wait() - - def half_all_people_on_earth(self): - earth = self.get_earth() - people = TextMobject("7.3 Billion people") - people.next_to(earth, RIGHT) - group = VGroup(earth, people) - group.next_to(self.four_billions, DOWN, MED_LARGE_BUFF) - group.shift(RIGHT) - - kg, h_line, target_earth = self.get_fraction( - 2, self.get_kilogoogle(), self.get_earth(), - ) - - self.play( - GrowFromCenter(earth), - Write(people) - ) - self.wait() - self.create_four_billion_copies(2, self.get_kilogoogle()) - self.wait() - self.play( - self.four_billions[2].restore, - Transform(earth, target_earth), - FadeIn(h_line), - FadeIn(kg), - FadeOut(self.group_of_four_billion_things), - FadeOut(people) - ) - self.wait() - - def four_billion_earths(self): - self.create_four_billion_copies( - 3, self.get_earth() - ) - milky_way = ImageMobject("milky_way") - milky_way.set_height(3) - milky_way.to_edge(LEFT, buff = 0) - milky_way.shift(DOWN) - - n_stars_estimate = TextMobject("100 to 400 \\\\ billion stars") - n_stars_estimate.next_to(milky_way, RIGHT) - n_stars_estimate.shift(UP) - - earth, h_line, denom = self.get_fraction( - 3, self.get_earth(), self.get_galaxy() - ) - - self.revert_to_original_skipping_status() - self.play(FadeIn(milky_way)) - self.play(Write(n_stars_estimate)) - self.wait() - self.play(LaggedStartMap( - Indicate, self.group_of_four_billion_things, - rate_func = there_and_back, - lag_ratio = 0.2, - run_time = 3, - )) - self.wait() - self.play( - ReplacementTransform( - self.group_of_four_billion_things, - VGroup(earth) - ), - ShowCreation(h_line), - FadeIn(denom), - self.four_billions[3].restore, - FadeOut(milky_way), - FadeOut(n_stars_estimate), - ) - self.wait() - - def four_billion_galxies(self): - self.create_four_billion_copies(4, self.get_galaxy()) - num, h_line, denom = fraction = self.get_fraction( - 4, self.get_galaxy(), TextMobject("GGSC").set_color(BLUE) - ) - - name = TextMobject( - "Giga", "Galactic \\\\", " Super", " Computer", - arg_separator = "" - ) - for word in name: - word[0].set_color(BLUE) - name.next_to(self.group_of_four_billion_things, UP) - - self.play(Write(name)) - self.wait() - self.play( - self.four_billions[4].restore, - ReplacementTransform( - self.group_of_four_billion_things, VGroup(num), - run_time = 2, - lag_ratio = 0.5 - ), - ShowCreation(h_line), - ReplacementTransform( - name, denom - ), - ) - self.wait() - - def show_time_scale(self): - fb1, fb2 = self.four_billions[5:7] - seconds_to_years = TextMobject("seconds $\\approx$ 126.8 years") - seconds_to_years.shift(LEFT) - years_to_eons = TextMobject( - "$\\times$ 126.8 years", "$\\approx$ 507 Billion years", - ) - years_to_eons.next_to( - seconds_to_years, DOWN, - aligned_edge = LEFT, - ) - universe_lifetimes = TextMobject("$\\approx 37 \\times$ Age of universe") - universe_lifetimes.next_to( - years_to_eons[1], DOWN, - aligned_edge = LEFT - ) - - for fb, words in (fb1, seconds_to_years), (fb2, years_to_eons): - self.play( - fb.scale, 1.3, - fb.next_to, words, LEFT, - fb.set_color, BLUE, - Write(words) - ) - self.wait() - self.play(Write(universe_lifetimes)) - self.wait() - - def show_probability(self): - four_billion = self.four_billions[7] - words = TextMobject( - "1 in ", "4 Billion\\\\", - "chance of success" - ) - words.next_to(four_billion, DOWN, buff = MED_LARGE_BUFF) - words.to_edge(RIGHT) - words[1].set_color(BLUE) - - self.play( - Write(VGroup(*words[::2])), - Transform(four_billion, words[1]) - ) - self.wait() - - - ############ - - def create_four_billion_copies(self, index, mobject): - four_billion = self.four_billions[index] - four_billion.set_color(BLUE) - four_billion.save_state() - - group = VGroup(*[ - VGroup(*[ - mobject.copy().set_height(0.25) - for x in range(self.n_group_rows) - ]).arrange(DOWN, buff = SMALL_BUFF) - for y in range(self.n_group_cols-1) - ]) - dots = TexMobject("\\dots") - group.add(dots) - group.add(*[group[0].copy() for x in range(2)]) - group.arrange(RIGHT, buff = SMALL_BUFF) - group.set_height(FRAME_Y_RADIUS) - max_width = 1.25*FRAME_X_RADIUS - if group.get_width() > max_width: - group.set_width(max_width) - group.to_corner(DOWN+RIGHT) - group = VGroup(*it.chain(*group)) - - brace = Brace(group, LEFT) - - self.play( - four_billion.scale, 2, - four_billion.next_to, brace, LEFT, - GrowFromCenter(brace), - LaggedStartMap( - FadeIn, group, - run_time = 3, - lag_ratio = 0.2 - ) - ) - self.wait() - - group.add_to_back(brace) - self.group_of_four_billion_things = group - - def get_fraction(self, index, numerator, denominator): - four_billion = self.four_billions[index] - if hasattr(four_billion, "saved_state"): - four_billion = four_billion.saved_state - - space = LARGE_BUFF - h_line = Line(LEFT, RIGHT) - h_line.set_width(four_billion.get_width()) - h_line.next_to(four_billion, DOWN, space) - for mob in numerator, denominator: - mob.set_height(0.75*space) - max_width = h_line.get_width() - if mob.get_width() > max_width: - mob.set_width(max_width) - numerator.next_to(h_line, UP, SMALL_BUFF) - denominator.next_to(h_line, DOWN, SMALL_BUFF) - fraction = VGroup(numerator, h_line, denominator) - - return fraction - - def get_google_logo(self): - return get_google_logo() - - def get_kilogoogle(self): - G = self.get_google_logo()[-1] - kilo = TextMobject("K") - kilo.scale(1.5) - kilo.next_to(G[-1], LEFT, SMALL_BUFF, DOWN) - plus_plus = TexMobject("++") - plus_plus.set_stroke(width = 1) - plus_plus.next_to(G, RIGHT, SMALL_BUFF) - return VGroup(kilo, G, plus_plus) - - def get_earth(self): - earth = SVGMobject( - file_name = "earth", - height = 1.5, - fill_color = BLACK, - ) - circle = Circle( - stroke_width = 3, - stroke_color = GREEN, - fill_opacity = 1, - fill_color = BLUE_C, - ) - circle.replace(earth) - earth.add_to_back(circle) - return earth - - def get_galaxy(self): - return SVGMobject( - file_name = "galaxy", - fill_opacity = 0, - stroke_width = 3, - stroke_color = WHITE, - height = 1, - ) - -class WriteTWoTo160(Scene): - def construct(self): - mob = TextMobject("$2^{160}$ ", "Hashes/sec") - mob[0].set_color(BLUE) - mob.scale(2) - self.play(Write(mob)) - self.wait() - -class StateOfBitcoin(TeacherStudentsScene): - def construct(self): - title = TextMobject("Total", "B", "mining") - title.to_edge(UP) - bitcoin_logo = BitcoinLogo() - bitcoin_logo.set_height(0.5) - bitcoin_logo.move_to(title[1]) - title.remove(title[1]) - - rate = TextMobject( - "5 Billion Billion", - "$\\frac{\\text{Hashes}}{\\text{Second}}$" - ) - rate.next_to(title, DOWN, MED_LARGE_BUFF) - - google = get_google_logo() - kilo = TextMobject("Kilo") - kilo.scale(1.5) - kilo.next_to(google[-1], LEFT, SMALL_BUFF, DOWN) - third = TexMobject("1 \\over 3") - third.next_to(kilo, LEFT) - kilogoogle = VGroup(*it.chain(third, kilo, google)) - kilogoogle.sort() - kilogoogle.next_to(rate, DOWN, MED_LARGE_BUFF) - - rate.save_state() - rate.shift(DOWN) - rate.set_fill(opacity = 0) - - all_text = VGroup(title, bitcoin_logo, rate, kilogoogle) - - gpu = SVGMobject( - file_name = "gpu", - height = 1, - fill_color = LIGHT_GREY, - ) - gpu.shift(0.5*FRAME_X_RADIUS*RIGHT) - gpu_name = TextMobject("GPU") - gpu_name.set_color(BLUE) - gpu_name.next_to(gpu, UP) - gpu_group = VGroup(gpu, gpu_name) - gpu_group.to_edge(UP) - cross = Cross(gpu_group) - gpu_group.add(cross) - - asic = TextMobject( - "Application", "Specific\\\\", "Integrated", "Circuit" - ) - for word in asic: - word[0].set_color(YELLOW) - asic.move_to(gpu) - asic.to_edge(UP) - asic.shift(LEFT) - circuit = SVGMobject( - file_name = "circuit", - height = asic.get_height(), - fill_color = WHITE, - ) - random.shuffle(circuit.submobjects) - circuit.set_color_by_gradient(WHITE, GREY) - circuit.next_to(asic, RIGHT) - asic_rate = TextMobject("Trillion hashes/sec") - asic_rate.next_to(asic, DOWN, MED_LARGE_BUFF) - asic_rate.set_color(GREEN) - - self.play( - Write(title), - DrawBorderThenFill(bitcoin_logo) - ) - self.play( - self.teacher.change, "raise_right_hand", - rate.restore, - ) - self.change_student_modes(*["pondering"]*3) - self.play(LaggedStartMap(FadeIn, kilogoogle)) - self.change_student_modes(*["surprised"]*3) - self.wait() - self.change_student_modes( - *["plain"]*3, - added_anims = [ - all_text.to_edge, LEFT, - self.teacher.change_mode, "happy" - ], - look_at_arg = gpu - ) - self.play( - Write(gpu_name), - DrawBorderThenFill(gpu) - ) - self.play(ShowCreation(cross)) - self.wait() - self.play( - Write(asic), - gpu_group.to_edge, DOWN, - self.teacher.change, "raise_right_hand", - ) - - self.change_student_modes( - *["pondering"]*3, - added_anims = [Write(asic_rate)] - ) - self.play(LaggedStartMap( - FadeIn, circuit, - run_time = 3, - lag_ratio = 0.2, - )) - self.wait() - -class QAndA(PiCreatureScene): - def construct(self): - self.pi_creature.center().to_edge(DOWN) - self.show_powers_of_two() - - num_subscriber_words = TexMobject( - "> 2^{18} = 262{,}144", "\\text{ subscribers}" - ) - num_subscriber_words.to_edge(UP) - num_subscriber_words.shift(RIGHT) - num_subscriber_words.set_color_by_tex("subscribers", RED) - - q_and_a = TextMobject("Q\\&A") - q_and_a.next_to(self.pi_creature.get_corner(UP+LEFT), UP) - q_and_a.save_state() - q_and_a.shift(DOWN) - q_and_a.set_fill(opacity = 0) - - reddit = TextMobject("reddit.com/r/3blue1brown") - reddit.next_to(num_subscriber_words, DOWN, LARGE_BUFF) - twitter = TextMobject("@3blue1brown") - twitter.set_color(BLUE_C) - twitter.next_to(reddit, DOWN) - - self.play(Write(num_subscriber_words)) - self.play(self.pi_creature.change, "gracious", num_subscriber_words) - self.wait() - self.play( - q_and_a.restore, - self.pi_creature.change, "raise_right_hand", - ) - self.wait() - self.play(Write(reddit)) - self.wait() - self.play( - FadeIn(twitter), - self.pi_creature.change_mode, "shruggie" - ) - self.wait(2) - - def show_powers_of_two(self): - rows = 16 - cols = 64 - dots = VGroup(*[ - VGroup(*[ - Dot() for x in range(rows) - ]).arrange(DOWN, buff = SMALL_BUFF) - for y in range(cols) - ]).arrange(RIGHT, buff = SMALL_BUFF) - dots.set_width(FRAME_WIDTH - 2*LARGE_BUFF) - dots.next_to(self.pi_creature, UP) - dots = VGroup(*it.chain(*dots)) - top = dots.get_top() - dots.sort( - lambda p : get_norm(p-top) - ) - - powers_of_two = VGroup(*[ - Integer(2**i) - for i in range(int(np.log2(rows*cols))+1) - ]) - - curr_power = powers_of_two[0] - curr_power.to_edge(UP) - self.play( - Write(curr_power), - FadeIn(dots[0]) - ) - for i, power_of_two in enumerate(powers_of_two): - if i == 0: - continue - power_of_two.to_edge(UP) - self.play( - FadeIn(VGroup(*dots[2**(i-1) : 2**i])), - Transform( - curr_power, power_of_two, - rate_func = squish_rate_func(smooth, 0, 0.5) - ) - ) - self.wait() - self.play( - FadeOut(dots), - FadeOut(powers_of_two) - ) - -class Thumbnail(Scene): - def construct(self): - num = TexMobject("2^{256}") - num.set_height(2) - num.set_color(BLUE_C) - num.set_stroke(BLUE_B, 3) - num.shift(MED_SMALL_BUFF*UP) - num.add_background_rectangle(opacity = 1) - num.background_rectangle.scale_in_place(1.5) - self.add(num) - - background_num_str = "115792089237316195423570985008687907853269984665640564039457584007913129639936" - n_chars = len(background_num_str) - new_str = "" - for i, char in enumerate(background_num_str): - new_str += char - # if i%3 == 1: - # new_str += "{,}" - if i%(n_chars/4) == 0: - new_str += " \\\\ " - background_num = TexMobject(new_str) - background_num.set_width(FRAME_WIDTH - LARGE_BUFF) - background_num.set_fill(opacity = 0.2) - - secure = TextMobject("Secure?") - secure.scale(4) - secure.shift(FRAME_Y_RADIUS*DOWN/2) - secure.set_color(RED) - secure.set_stroke(RED_A, 3) - - lock = SVGMobject( - file_name = "shield_locked", - fill_color = WHITE, - ) - lock.set_height(6) - - self.add(background_num, num) - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/WindingNumber.py b/from_3b1b/old/WindingNumber.py deleted file mode 100644 index afcdb0e8..00000000 --- a/from_3b1b/old/WindingNumber.py +++ /dev/null @@ -1,2885 +0,0 @@ -from manimlib.imports import * - -import warnings -warnings.warn(""" - Warning: This file makes use of - ContinualAnimation, which has since - been deprecated -""") - -import time - -import mpmath -mpmath.mp.dps = 7 - - -# Warning, this file uses ContinualChangingDecimal, -# which has since been been deprecated. Use a mobject -# updater instead - - -# Useful constants to play around with -UL = UP + LEFT -UR = UP + RIGHT -DL = DOWN + LEFT -DR = DOWN + RIGHT -standard_rect = np.array([UL, UR, DR, DL]) - -# Used in EquationSolver2d, and a few other places -border_stroke_width = 10 - -# Used for clockwise circling in some scenes -cw_circle = Circle(color = WHITE).stretch(-1, 0) - -# Used when walker animations are on black backgrounds, in EquationSolver2d and PiWalker -WALKER_LIGHT_COLOR = DARK_GREY - -ODOMETER_RADIUS = 1.5 -ODOMETER_STROKE_WIDTH = 20 - -# TODO/WARNING: There's a lot of refactoring and cleanup to be done in this code, -# (and it will be done, but first I'll figure out what I'm doing with all this...) -# -SR - -# This turns counterclockwise revs into their color. Beware, we use CCW angles -# in all display code, but generally think in this video's script in terms of -# CW angles -def rev_to_rgba(alpha): - alpha = (0.5 - alpha) % 1 # For convenience, to go CW from red on left instead of CCW from right - # 0 is red, 1/6 is yellow, 1/3 is green, 2/3 is blue - hue_list = [0, 0.5/6.0, 1/6.0, 1.1/6.0, 2/6.0, 3/6.0, 4/6.0, 5/6.0] - num_hues = len(hue_list) - start_index = int(np.floor(num_hues * alpha)) % num_hues - end_index = (start_index + 1) % num_hues - beta = (alpha % (1.0/num_hues)) * num_hues - - start_hue = hue_list[start_index] - end_hue = hue_list[end_index] - if end_hue < start_hue: - end_hue = end_hue + 1 - hue = interpolate(start_hue, end_hue, beta) - - return color_to_rgba(Color(hue = hue, saturation = 1, luminance = 0.5)) - - # alpha = alpha % 1 - # colors = colorslist - # num_colors = len(colors) - # beta = (alpha % (1.0/num_colors)) * num_colors - # start_index = int(np.floor(num_colors * alpha)) % num_colors - # end_index = (start_index + 1) % num_colors - - # return interpolate(colors[start_index], colors[end_index], beta) - -def rev_to_color(alpha): - return rgba_to_color(rev_to_rgba(alpha)) - -def point_to_rev(xxx_todo_changeme6, allow_origin = False): - # Warning: np.arctan2 would happily discontinuously returns the value 0 for (0, 0), due to - # design choices in the underlying atan2 library call, but for our purposes, this is - # illegitimate, and all winding number calculations must be set up to avoid this - (x, y) = xxx_todo_changeme6 - if not(allow_origin) and (x, y) == (0, 0): - print("Error! Angle of (0, 0) computed!") - return - return fdiv(np.arctan2(y, x), TAU) - -def point_to_size(xxx_todo_changeme7): - (x, y) = xxx_todo_changeme7 - return np.sqrt(x**2 + y**2) - -# rescaled_size goes from 0 to 1 as size goes from 0 to infinity -# The exact method is arbitrarily chosen to make pleasing color map -# brightness levels -def point_to_rescaled_size(p): - base_size = point_to_size(p) - - return np.sqrt(fdiv(base_size, base_size + 1)) - -def point_to_rgba(point): - rev = point_to_rev(point, allow_origin = True) - rgba = rev_to_rgba(rev) - rescaled_size = point_to_rescaled_size(point) - return rgba * [rescaled_size, rescaled_size, rescaled_size, 1] # Preserve alpha - -positive_color = rev_to_color(0) -negative_color = rev_to_color(0.5) -neutral_color = rev_to_color(0.25) - -class EquationSolver1d(GraphScene, ZoomedScene): - CONFIG = { - "camera_config" : - { - "use_z_coordinate_for_display_order": True, - }, - "func" : lambda x : x, - "targetX" : 0, - "targetY" : 0, - "initial_lower_x" : 0, - "initial_upper_x" : 10, - "num_iterations" : 10, - "iteration_at_which_to_start_zoom" : None, - "graph_label" : None, - "show_target_line" : True, - "base_line_y" : 0, # The y coordinate at which to draw our x guesses - "show_y_as_deviation" : False, # Displays y-values as deviations from target, - } - - def drawGraph(self): - self.setup_axes() - self.graph = self.get_graph(self.func) - self.add(self.graph) - - if self.graph_label != None: - curve_label = self.get_graph_label(self.graph, self.graph_label, - x_val = 4, direction = LEFT) - curve_label.shift(LEFT) - self.add(curve_label) - - if self.show_target_line: - target_line_object = DashedLine( - self.coords_to_point(self.x_min, self.targetY), - self.coords_to_point(self.x_max, self.targetY), - dash_length = 0.1) - self.add(target_line_object) - - target_label_num = 0 if self.show_y_as_deviation else self.targetY - target_line_label = TexMobject("y = " + str(target_label_num)) - target_line_label.next_to(target_line_object.get_left(), UP + RIGHT) - self.add(target_line_label) - - self.wait() # Give us time to appreciate the graph - - if self.show_target_line: - self.play(FadeOut(target_line_label)) # Reduce clutter - - print("For reference, graphOrigin: ", self.coords_to_point(0, 0)) - print("targetYPoint: ", self.coords_to_point(0, self.targetY)) - - # This is a mess right now (first major animation coded), - # but it works; can be refactored later or never - def solveEquation(self): - # Under special conditions, used in GuaranteedZeroScene, we let the - # "lower" guesses actually be too high, or vice versa, and color - # everything accordingly - - def color_by_comparison(val, ref): - if val > ref: - return positive_color - elif val < ref: - return negative_color - else: - return neutral_color - - lower_color = color_by_comparison(self.func(self.initial_lower_x), self.targetY) - upper_color = color_by_comparison(self.func(self.initial_upper_x), self.targetY) - - if self.show_y_as_deviation: - y_bias = -self.targetY - else: - y_bias = 0 - - startBrace = TexMobject("|", stroke_width = 10) #TexMobject("[") # Not using [ and ] because they end up crossing over - startBrace.set_color(lower_color) - endBrace = startBrace.copy().stretch(-1, 0) - endBrace.set_color(upper_color) - genericBraces = Group(startBrace, endBrace) - #genericBraces.scale(1.5) - - leftBrace = startBrace.copy() - rightBrace = endBrace.copy() - xBraces = Group(leftBrace, rightBrace) - - downBrace = startBrace.copy() - upBrace = endBrace.copy() - yBraces = Group(downBrace, upBrace) - yBraces.rotate(TAU/4) - - lowerX = self.initial_lower_x - lowerY = self.func(lowerX) - upperX = self.initial_upper_x - upperY = self.func(upperX) - - leftBrace.move_to(self.coords_to_point(lowerX, self.base_line_y)) #, aligned_edge = RIGHT) - leftBraceLabel = DecimalNumber(lowerX) - leftBraceLabel.next_to(leftBrace, DOWN + LEFT, buff = SMALL_BUFF) - leftBraceLabelAnimation = ContinualChangingDecimal(leftBraceLabel, - lambda alpha : self.point_to_coords(leftBrace.get_center())[0], - tracked_mobject = leftBrace) - - rightBrace.move_to(self.coords_to_point(upperX, self.base_line_y)) #, aligned_edge = LEFT) - rightBraceLabel = DecimalNumber(upperX) - rightBraceLabel.next_to(rightBrace, DOWN + RIGHT, buff = SMALL_BUFF) - rightBraceLabelAnimation = ContinualChangingDecimal(rightBraceLabel, - lambda alpha : self.point_to_coords(rightBrace.get_center())[0], - tracked_mobject = rightBrace) - - downBrace.move_to(self.coords_to_point(0, lowerY)) #, aligned_edge = UP) - downBraceLabel = DecimalNumber(lowerY) - downBraceLabel.next_to(downBrace, LEFT + DOWN, buff = SMALL_BUFF) - downBraceLabelAnimation = ContinualChangingDecimal(downBraceLabel, - lambda alpha : self.point_to_coords(downBrace.get_center())[1] + y_bias, - tracked_mobject = downBrace) - - upBrace.move_to(self.coords_to_point(0, upperY)) #, aligned_edge = DOWN) - upBraceLabel = DecimalNumber(upperY) - upBraceLabel.next_to(upBrace, LEFT + UP, buff = SMALL_BUFF) - upBraceLabelAnimation = ContinualChangingDecimal(upBraceLabel, - lambda alpha : self.point_to_coords(upBrace.get_center())[1] + y_bias, - tracked_mobject = upBrace) - - lowerDotPoint = self.input_to_graph_point(lowerX, self.graph) - lowerDotXPoint = self.coords_to_point(lowerX, self.base_line_y) - lowerDotYPoint = self.coords_to_point(0, self.func(lowerX)) - lowerDot = Dot(lowerDotPoint + OUT, color = lower_color) - upperDotPoint = self.input_to_graph_point(upperX, self.graph) - upperDot = Dot(upperDotPoint + OUT, color = upper_color) - upperDotXPoint = self.coords_to_point(upperX, self.base_line_y) - upperDotYPoint = self.coords_to_point(0, self.func(upperX)) - - lowerXLine = Line(lowerDotXPoint, lowerDotPoint, color = lower_color) - upperXLine = Line(upperDotXPoint, upperDotPoint, color = upper_color) - lowerYLine = Line(lowerDotPoint, lowerDotYPoint, color = lower_color) - upperYLine = Line(upperDotPoint, upperDotYPoint, color = upper_color) - - x_guess_line = Line(lowerDotXPoint, upperDotXPoint, color = WHITE, stroke_width = 10) - - - lowerGroup = Group( - lowerDot, - leftBrace, downBrace, - lowerXLine, lowerYLine, - x_guess_line - ) - - upperGroup = Group( - upperDot, - rightBrace, upBrace, - upperXLine, upperYLine, - x_guess_line - ) - - initialLowerXDot = Dot(lowerDotXPoint + OUT, color = lower_color) - initialUpperXDot = Dot(upperDotXPoint + OUT, color = upper_color) - initialLowerYDot = Dot(lowerDotYPoint + OUT, color = lower_color) - initialUpperYDot = Dot(upperDotYPoint + OUT, color = upper_color) - - # All the initial adds and ShowCreations are here now: - self.play(FadeIn(initialLowerXDot), FadeIn(leftBrace), FadeIn(leftBraceLabel)) - self.add_foreground_mobjects(initialLowerXDot, leftBrace) - self.add(leftBraceLabelAnimation) - self.play(ShowCreation(lowerXLine)) - self.add_foreground_mobject(lowerDot) - self.play(ShowCreation(lowerYLine)) - self.play(FadeIn(initialLowerYDot), FadeIn(downBrace), FadeIn(downBraceLabel)) - self.add_foreground_mobjects(initialLowerYDot, downBrace) - self.add(downBraceLabelAnimation) - - self.wait() - - self.play(FadeIn(initialUpperXDot), FadeIn(rightBrace), FadeIn(rightBraceLabel)) - self.add_foreground_mobjects(initialUpperXDot, rightBrace) - self.add(rightBraceLabelAnimation) - self.play(ShowCreation(upperXLine)) - self.add_foreground_mobject(upperDot) - self.play(ShowCreation(upperYLine)) - self.play(FadeIn(initialUpperYDot), FadeIn(upBrace), FadeIn(upBraceLabel)) - self.add_foreground_mobjects(initialUpperYDot, upBrace) - self.add(upBraceLabelAnimation) - - self.wait() - - self.play(FadeIn(x_guess_line)) - - self.wait() - - for i in range(self.num_iterations): - if i == self.iteration_at_which_to_start_zoom: - self.activate_zooming() - self.little_rectangle.move_to( - self.coords_to_point(self.targetX, self.targetY)) - inverseZoomFactor = 1/float(self.zoom_factor) - self.play( - lowerDot.scale_in_place, inverseZoomFactor, - upperDot.scale_in_place, inverseZoomFactor) - - - def makeUpdater(xAtStart, fixed_guess_x): - def updater(group, alpha): - dot, xBrace, yBrace, xLine, yLine, guess_line = group - newX = interpolate(xAtStart, midX, alpha) - newY = self.func(newX) - graphPoint = self.input_to_graph_point(newX, - self.graph) - dot.move_to(graphPoint) - xAxisPoint = self.coords_to_point(newX, self.base_line_y) - xBrace.move_to(xAxisPoint) - yAxisPoint = self.coords_to_point(0, newY) - yBrace.move_to(yAxisPoint) - xLine.put_start_and_end_on(xAxisPoint, graphPoint) - yLine.put_start_and_end_on(yAxisPoint, graphPoint) - fixed_guess_point = self.coords_to_point(fixed_guess_x, self.base_line_y) - guess_line.put_start_and_end_on(xAxisPoint, fixed_guess_point) - return group - return updater - - midX = (lowerX + upperX)/float(2) - midY = self.func(midX) - - # If we run with an interval whose endpoints start off with same sign, - # then nothing after this branching can be trusted to do anything reasonable - # in terms of picking branches or assigning colors - in_negative_branch = midY < self.targetY - sign_color = negative_color if in_negative_branch else positive_color - - midCoords = self.coords_to_point(midX, midY) - midColor = neutral_color - # Hm... even the z buffer isn't helping keep this above x_guess_line - - midXBrace = startBrace.copy() # Had start and endBrace been asymmetric, we'd do something different here - midXBrace.set_color(midColor) - midXBrace.move_to(self.coords_to_point(midX, self.base_line_y) + OUT) - - # We only actually add this much later - midXPoint = Dot(self.coords_to_point(midX, self.base_line_y) + OUT, color = sign_color) - - x_guess_label_caption = TextMobject("New guess: x = ", fill_color = midColor) - x_guess_label_num = DecimalNumber(midX, fill_color = midColor) - x_guess_label_num.move_to(0.9 * FRAME_Y_RADIUS * DOWN) - x_guess_label_caption.next_to(x_guess_label_num, LEFT) - x_guess_label = Group(x_guess_label_caption, x_guess_label_num) - y_guess_label_caption = TextMobject(", y = ", fill_color = midColor) - y_guess_label_num = DecimalNumber(midY, fill_color = sign_color) - y_guess_label_caption.next_to(x_guess_label_num, RIGHT) - y_guess_label_num.next_to(y_guess_label_caption, RIGHT) - y_guess_label = Group(y_guess_label_caption, y_guess_label_num) - guess_labels = Group(x_guess_label, y_guess_label) - - self.play( - ReplacementTransform(leftBrace.copy(), midXBrace), - ReplacementTransform(rightBrace.copy(), midXBrace), - FadeIn(x_guess_label)) - - self.add_foreground_mobject(midXBrace) - - midXLine = DashedLine( - self.coords_to_point(midX, self.base_line_y), - midCoords, - color = midColor - ) - self.play(ShowCreation(midXLine)) - midDot = Dot(midCoords, color = sign_color) - if(self.iteration_at_which_to_start_zoom != None and - i >= self.iteration_at_which_to_start_zoom): - midDot.scale_in_place(inverseZoomFactor) - self.add(midDot) - midYLine = DashedLine(midCoords, self.coords_to_point(0, midY), color = sign_color) - self.play( - ShowCreation(midYLine), - FadeIn(y_guess_label), - ApplyMethod(midXBrace.set_color, sign_color), - ApplyMethod(midXLine.set_color, sign_color), - run_time = 0.25 - ) - midYPoint = Dot(self.coords_to_point(0, midY), color = sign_color) - self.add(midXPoint, midYPoint) - - if in_negative_branch: - self.play( - UpdateFromAlphaFunc(lowerGroup, - makeUpdater(lowerX, - fixed_guess_x = upperX - ) - ), - FadeOut(guess_labels), - ) - lowerX = midX - lowerY = midY - - else: - self.play( - UpdateFromAlphaFunc(upperGroup, - makeUpdater(upperX, - fixed_guess_x = lowerX - ) - ), - FadeOut(guess_labels), - ) - upperX = midX - upperY = midY - #mid_group = Group(midXLine, midDot, midYLine) Removing groups doesn't flatten as expected? - self.remove(midXLine, midDot, midYLine, midXBrace) - - self.wait() - - def construct(self): - self.drawGraph() - self.solveEquation() - -# Returns the value with the same fractional component as x, closest to m -def resit_near(x, m): - frac_diff = (x - m) % 1 - if frac_diff > 0.5: - frac_diff -= 1 - return m + frac_diff - -# TODO?: Perhaps use modulus of (uniform) continuity instead of num_checkpoints, calculating -# latter as needed from former? -# -# "cheap" argument only used for diagnostic testing right now -def make_alpha_winder(func, start, end, num_checkpoints, cheap = False): - check_points = [None for i in range(num_checkpoints)] - check_points[0] = func(start) - step_size = fdiv(end - start, num_checkpoints) - for i in range(num_checkpoints - 1): - check_points[i + 1] = \ - resit_near( - func(start + (i + 1) * step_size), - check_points[i]) - def return_func(alpha): - if cheap: - return alpha # A test to see if this func is responsible for slowdown - - index = np.clip(0, num_checkpoints - 1, int(alpha * num_checkpoints)) - x = interpolate(start, end, alpha) - if cheap: - return check_points[index] # A more principled test that at least returns a reasonable answer - else: - return resit_near(func(x), check_points[index]) - return return_func - -# The various inconsistent choices of what datatype to use where are a bit of a mess, -# but I'm more keen to rush this video out now than to sort this out. - -def complex_to_pair(c): - return np.array((c.real, c.imag)) - -def plane_func_from_complex_func(f): - return lambda x_y4 : complex_to_pair(f(complex(x_y4[0],x_y4[1]))) - -def point3d_func_from_plane_func(f): - def g(xxx_todo_changeme): - (x, y, z) = xxx_todo_changeme - f_val = f((x, y)) - return np.array((f_val[0], f_val[1], 0)) - return g - -def point3d_func_from_complex_func(f): - return point3d_func_from_plane_func(plane_func_from_complex_func(f)) - -def plane_zeta(xxx_todo_changeme8): - (x, y) = xxx_todo_changeme8 - CLAMP_SIZE = 1000 - z = complex(x, y) - try: - answer = mpmath.zeta(z) - except ValueError: - return (CLAMP_SIZE, 0) - if abs(answer) > CLAMP_SIZE: - answer = answer/abs(answer) * CLAMP_SIZE - return (float(answer.real), float(answer.imag)) - -def rescaled_plane_zeta(xxx_todo_changeme9): - (x, y) = xxx_todo_changeme9 - return plane_zeta((x/FRAME_X_RADIUS, 8*y)) - -# Returns a function from 2-ples to 2-ples -# This function is specified by a list of (x, y, z) tuples, -# and has winding number z (or total of all specified z) around each (x, y) -# -# Can also pass in (x, y) tuples, interpreted as (x, y, 1) -def plane_func_by_wind_spec(*specs): - def embiggen(p): - if len(p) == 3: - return p - elif len(p) == 2: - return (p[0], p[1], 1) - else: - print("Error in plane_func_by_wind_spec embiggen!") - specs = list(map(embiggen, specs)) - - pos_specs = [x_y_z for x_y_z in specs if x_y_z[2] > 0] - neg_specs = [x_y_z1 for x_y_z1 in specs if x_y_z1[2] < 0] - - neg_specs_made_pos = [(x_y_z2[0], x_y_z2[1], -x_y_z2[2]) for x_y_z2 in neg_specs] - - def poly(c, root_specs): - return np.prod([(c - complex(x, y))**z for (x, y, z) in root_specs]) - - def complex_func(c): - return poly(c, pos_specs) * np.conjugate(poly(c, neg_specs_made_pos)) - - return plane_func_from_complex_func(complex_func) - -def scale_func(func, scale_factor): - return lambda x : func(x) * scale_factor - -# Used in Initial2dFunc scenes, VectorField scene, and ExamplePlaneFunc -example_plane_func_spec = [(-3, -1.3, 2), (0.1, 0.2, 1), (2.8, -2, -1)] -example_plane_func = plane_func_by_wind_spec(*example_plane_func_spec) - -empty_animation = EmptyAnimation() - -class WalkerAnimation(Animation): - CONFIG = { - "walk_func" : None, # Must be initialized to use - "remover" : True, - "rate_func" : None, - "coords_to_point" : None - } - - def __init__(self, walk_func, val_func, coords_to_point, - show_arrows = True, scale_arrows = False, - **kwargs): - self.walk_func = walk_func - self.val_func = val_func - self.coords_to_point = coords_to_point - self.compound_walker = VGroup() - self.show_arrows = show_arrows - self.scale_arrows = scale_arrows - - if "walker_stroke_color" in kwargs: - walker_stroke_color = kwargs["walker_stroke_color"] - else: - walker_stroke_color = BLACK - - base_walker = Dot().scale(5 * 0.35).set_stroke(walker_stroke_color, 2) # PiCreature().scale(0.8 * 0.35) - self.compound_walker.walker = base_walker - if show_arrows: - self.compound_walker.arrow = Arrow(ORIGIN, 0.5 * RIGHT, buff = 0) - self.compound_walker.arrow.match_style(self.compound_walker.walker) - self.compound_walker.digest_mobject_attrs() - Animation.__init__(self, self.compound_walker, **kwargs) - - # Perhaps abstract this out into an "Animation updating from original object" class - def interpolate_submobject(self, submobject, starting_submobject, alpha): - submobject.points = np.array(starting_submobject.points) - - def interpolate_mobject(self, alpha): - Animation.interpolate_mobject(self, alpha) - cur_x, cur_y = cur_coords = self.walk_func(alpha) - cur_point = self.coords_to_point(cur_x, cur_y) - self.mobject.shift(cur_point - self.mobject.walker.get_center()) - val = self.val_func(cur_coords) - rev = point_to_rev(val) - self.mobject.walker.set_fill(rev_to_color(rev)) - if self.show_arrows: - self.mobject.arrow.set_fill(rev_to_color(rev)) - self.mobject.arrow.rotate( - rev * TAU, - about_point = self.mobject.arrow.get_start() - ) - - if self.scale_arrows: - size = point_to_rescaled_size(val) - self.mobject.arrow.scale( - size * 0.3, # Hack constant; we barely use this feature right now - about_point = self.mobject.arrow.get_start() - ) - -def walker_animation_with_display( - walk_func, - val_func, - coords_to_point, - number_update_func = None, - show_arrows = True, - scale_arrows = False, - num_decimal_places = 1, - include_background_rectangle = True, - **kwargs - ): - - walker_anim = WalkerAnimation( - walk_func = walk_func, - val_func = val_func, - coords_to_point = coords_to_point, - show_arrows = show_arrows, - scale_arrows = scale_arrows, - **kwargs) - walker = walker_anim.compound_walker.walker - - if number_update_func != None: - display = DecimalNumber(0, - num_decimal_places = num_decimal_places, - fill_color = WHITE if include_background_rectangle else BLACK, - include_background_rectangle = include_background_rectangle) - if include_background_rectangle: - display.background_rectangle.fill_opacity = 0.5 - display.background_rectangle.fill_color = GREY - display.background_rectangle.scale(1.2) - displaycement = 0.5 * DOWN # How about that pun, eh? - # display.move_to(walker.get_center() + displaycement) - display.next_to(walker, DOWN+RIGHT, SMALL_BUFF) - display_anim = ChangingDecimal(display, - number_update_func, - tracked_mobject = walker_anim.compound_walker.walker, - **kwargs) - anim_group = AnimationGroup(walker_anim, display_anim, rate_func=linear) - return anim_group - else: - return walker_anim - -def LinearWalker( - start_coords, - end_coords, - coords_to_point, - val_func, - number_update_func = None, - show_arrows = True, - scale_arrows = False, - include_background_rectangle = True, - **kwargs - ): - walk_func = lambda alpha : interpolate(start_coords, end_coords, alpha) - return walker_animation_with_display( - walk_func = walk_func, - coords_to_point = coords_to_point, - val_func = val_func, - number_update_func = number_update_func, - show_arrows = show_arrows, - scale_arrows = scale_arrows, - include_background_rectangle = include_background_rectangle, - **kwargs) - -class ColorMappedByFuncScene(Scene): - CONFIG = { - "func" : lambda p : p, - "num_plane" : NumberPlane(), - "show_num_plane" : True, - - "show_output" : False, - - "hide_background" : False #Background used for color mapped objects, not as background - } - - def short_path_to_long_path(self, filename_with_ext): - return self.get_image_file_path(filename_with_ext) - - def setup(self): - # The composition of input_to_pos and pos_to_color - # is to be equal to func (which turns inputs into colors) - # However, depending on whether we are showing input or output (via a MappingCamera), - # we color the background using either func or the identity map - if self.show_output: - self.input_to_pos_func = self.func - self.pos_to_color_func = lambda p : p - else: - self.input_to_pos_func = lambda p : p - self.pos_to_color_func = self.func - - self.pixel_pos_to_color_func = lambda x_y3 : self.pos_to_color_func( - self.num_plane.point_to_coords_cheap(np.array([x_y3[0], x_y3[1], 0])) - ) - - jitter_val = 0.1 - line_coords = np.linspace(-10, 10) + jitter_val - func_hash_points = it.product(line_coords, line_coords) - - def mini_hasher(p): - rgba = point_to_rgba(self.pixel_pos_to_color_func(p)) - if rgba[3] != 1.0: - print("Warning! point_to_rgba assigns fractional alpha", rgba[3]) - return tuple(rgba) - - to_hash = tuple(mini_hasher(p) for p in func_hash_points) - func_hash = hash(to_hash) - # We hash just based on output image - # Thus, multiple scenes with same output image can re-use it - # without recomputation - full_hash = hash((func_hash, self.camera.get_pixel_width())) - self.background_image_file = self.short_path_to_long_path( - "color_mapped_bg_hash_" + str(full_hash) + ".png" - ) - self.in_background_pass = not os.path.exists(self.background_image_file) - - print("Background file: " + self.background_image_file) - if self.in_background_pass: - print("The background file does not exist yet; this will be a background creation + video pass") - else: - print("The background file already exists; this will only be a video pass") - - def construct(self): - if self.in_background_pass: - self.camera.set_background_from_func( - lambda x_y: point_to_rgba( - self.pixel_pos_to_color_func( - (x_y[0], x_y[1]) - ) - ) - ) - self.save_image(self.background_image_file, mode="RGBA") - - if self.hide_background: - # Clearing background - self.camera.background_image = None - else: - # Even if we just computed the background, we switch to the file now - self.camera.background_image = self.background_image_file - self.camera.init_background() - - if self.show_num_plane: - self.num_plane.fade() - self.add(self.num_plane) - -class PureColorMap(ColorMappedByFuncScene): - CONFIG = { - "show_num_plane" : False - } - - def construct(self): - ColorMappedByFuncScene.construct(self) - self.wait() - -# This sets self.background_image_file, but does not display it as the background -class ColorMappedObjectsScene(ColorMappedByFuncScene): - CONFIG = { - "show_num_plane" : False, - "hide_background" : True, - } - -class PiWalker(ColorMappedByFuncScene): - CONFIG = { - "walk_coords" : [], - "step_run_time" : 1, - "scale_arrows" : False, - "display_wind" : True, - "wind_reset_indices" : [], - "display_size" : False, - "display_odometer" : False, - "color_foreground_not_background" : False, - "show_num_plane" : False, - "draw_lines" : True, - "num_checkpoints" : 10, - "num_decimal_places" : 1, - "include_background_rectangle" : False, - } - - def construct(self): - ColorMappedByFuncScene.construct(self) - - if self.color_foreground_not_background or self.display_odometer: - # Clear background - self.camera.background_image = None - self.camera.init_background() - - num_plane = self.num_plane - - walk_coords = self.walk_coords - points = [num_plane.coords_to_point(x, y) for x, y in walk_coords] - polygon = Polygon(*points, color = WHITE) - if self.color_foreground_not_background: - polygon.stroke_width = border_stroke_width - polygon.color_using_background_image(self.background_image_file) - total_run_time = len(points) * self.step_run_time - polygon_anim = ShowCreation(polygon, run_time = total_run_time, rate_func=linear) - walker_anim = empty_animation - - start_wind = 0 - for i in range(len(walk_coords)): - start_coords = walk_coords[i] - end_coords = walk_coords[(i + 1) % len(walk_coords)] - - # We need to do this roundabout default argument thing to get the closure we want, - # so the next iteration changing start_coords, end_coords doesn't change this closure - val_alpha_func = lambda a, start_coords = start_coords, end_coords = end_coords : self.func(interpolate(start_coords, end_coords, a)) - - if self.display_wind: - clockwise_val_func = lambda p : -point_to_rev(self.func(p)) - alpha_winder = make_alpha_winder(clockwise_val_func, start_coords, end_coords, self.num_checkpoints) - number_update_func = lambda alpha, alpha_winder = alpha_winder, start_wind = start_wind: alpha_winder(alpha) - alpha_winder(0) + start_wind - start_wind = 0 if i + 1 in self.wind_reset_indices else number_update_func(1) - elif self.display_size: - # We need to do this roundabout default argument thing to get the closure we want, - # so the next iteration changing val_alpha_func doesn't change this closure - number_update_func = lambda a, val_alpha_func = val_alpha_func : point_to_rescaled_size(val_alpha_func(a)) # We only use this for diagnostics - else: - number_update_func = None - - new_anim = LinearWalker( - start_coords = start_coords, - end_coords = end_coords, - coords_to_point = num_plane.coords_to_point, - val_func = self.func, - remover = (i < len(walk_coords) - 1), - show_arrows = not self.show_output, - scale_arrows = self.scale_arrows, - number_update_func = number_update_func, - run_time = self.step_run_time, - walker_stroke_color = WALKER_LIGHT_COLOR if self.color_foreground_not_background else BLACK, - num_decimal_places = self.num_decimal_places, - include_background_rectangle = self.include_background_rectangle, - ) - - if self.display_odometer: - # Discard above animation and show an odometer instead - - # We need to do this roundabout default argument thing to get the closure we want, - # so the next iteration changing val_alpha_func doesn't change this closure - rev_func = lambda a, val_alpha_func = val_alpha_func : point_to_rev(val_alpha_func(a)) - base_arrow = Arrow(ORIGIN, RIGHT, buff = 0) - new_anim = FuncRotater(base_arrow, - rev_func = rev_func, - run_time = self.step_run_time, - rate_func=linear, - remover = i < len(walk_coords) - 1, - ) - - walker_anim = Succession(walker_anim, new_anim) - - # TODO: Allow smooth paths instead of breaking them up into lines, and - # use point_from_proportion to get points along the way - - if self.display_odometer: - color_wheel = Circle(radius = ODOMETER_RADIUS) - color_wheel.stroke_width = ODOMETER_STROKE_WIDTH - color_wheel.color_using_background_image(self.short_path_to_long_path("pure_color_map.png")) # Manually inserted here; this is unclean - self.add(color_wheel) - self.play(walker_anim) - else: - if self.draw_lines: - self.play(polygon_anim, walker_anim) - else: - # (Note: Turns out, play is unhappy playing empty_animation, as had been - # previous approach to this toggle; should fix that) - self.play(walker_anim) - - self.wait() - -class PiWalkerRect(PiWalker): - CONFIG = { - "start_x" : -1, - "start_y" : 1, - "walk_width" : 2, - "walk_height" : 2, - "func" : plane_func_from_complex_func(lambda c: c**2), - "double_up" : False, - - # New default for the scenes using this: - "display_wind" : True - } - - def setup(self): - TL = np.array((self.start_x, self.start_y)) - TR = TL + (self.walk_width, 0) - BR = TR + (0, -self.walk_height) - BL = BR + (-self.walk_width, 0) - self.walk_coords = [TL, TR, BR, BL] - if self.double_up: - self.walk_coords = self.walk_coords + self.walk_coords - PiWalker.setup(self) - -class PiWalkerCircle(PiWalker): - CONFIG = { - "radius" : 1, - "num_steps" : 100, - "step_run_time" : 0.01 - } - - def setup(self): - r = self.radius - N = self.num_steps - self.walk_coords = [r * np.array((np.cos(i * TAU/N), np.sin(i * TAU/N))) for i in range(N)] - PiWalker.setup(self) - -def split_interval(xxx_todo_changeme10): - (a, b) = xxx_todo_changeme10 - mid = (a + b)/2.0 - return ((a, mid), (mid, b)) - -# I am surely reinventing some wheel here, but what's done is done... -class RectangleData(): - def __init__(self, x_interval, y_interval): - self.rect = (x_interval, y_interval) - - def get_top_left(self): - return np.array((self.rect[0][0], self.rect[1][1])) - - def get_top_right(self): - return np.array((self.rect[0][1], self.rect[1][1])) - - def get_bottom_right(self): - return np.array((self.rect[0][1], self.rect[1][0])) - - def get_bottom_left(self): - return np.array((self.rect[0][0], self.rect[1][0])) - - def get_top(self): - return (self.get_top_left(), self.get_top_right()) - - def get_right(self): - return (self.get_top_right(), self.get_bottom_right()) - - def get_bottom(self): - return (self.get_bottom_right(), self.get_bottom_left()) - - def get_left(self): - return (self.get_bottom_left(), self.get_top_left()) - - def get_center(self): - return interpolate(self.get_top_left(), self.get_bottom_right(), 0.5) - - def get_width(self): - return self.rect[0][1] - self.rect[0][0] - - def get_height(self): - return self.rect[1][1] - self.rect[1][0] - - def splits_on_dim(self, dim): - x_interval = self.rect[0] - y_interval = self.rect[1] - - # TODO: Can refactor the following; will do later - if dim == 0: - return_data = [RectangleData(new_interval, y_interval) for new_interval in split_interval(x_interval)] - elif dim == 1: - return_data = [RectangleData(x_interval, new_interval) for new_interval in split_interval(y_interval)[::-1]] - else: - print("RectangleData.splits_on_dim passed illegitimate dimension!") - - return tuple(return_data) - - def split_line_on_dim(self, dim): - x_interval = self.rect[0] - y_interval = self.rect[1] - - if dim == 0: - sides = (self.get_top(), self.get_bottom()) - elif dim == 1: - sides = (self.get_left(), self.get_right()) - else: - print("RectangleData.split_line_on_dim passed illegitimate dimension!") - - return tuple([mid(x, y) for (x, y) in sides]) - - -class EquationSolver2dNode(object): - def __init__(self, first_anim, children = []): - self.first_anim = first_anim - self.children = children - - def depth(self): - if len(self.children) == 0: - return 0 - - return 1 + max([n.depth() for n in self.children]) - - def nodes_at_depth(self, n): - if n == 0: - return [self] - - # Not the efficient way to flatten lists, because Python + is linear in list size, - # but we have at most two children, so no big deal here - return sum([c.nodes_at_depth(n - 1) for c in self.children], []) - - # This is definitely NOT the efficient way to do BFS, but I'm just trying to write something - # quick without thinking that gets the job done on small instances for now - def hacky_bfs(self): - depth = self.depth() - - # Not the efficient way to flatten lists, because Python + is linear in list size, - # but this IS hacky_bfs... - return sum([self.nodes_at_depth(i) for i in range(depth + 1)], []) - - def display_in_series(self): - return Succession(self.first_anim, *[n.display_in_series() for n in self.children]) - - def display_in_parallel(self): - return Succession(self.first_anim, AnimationGroup(*[n.display_in_parallel() for n in self.children])) - - def display_in_bfs(self): - bfs_nodes = self.hacky_bfs() - return Succession(*[n.first_anim for n in bfs_nodes]) - - def play_in_bfs(self, scene, border_anim): - bfs_nodes = self.hacky_bfs() - print("Number of nodes: ", len(bfs_nodes)) - - if len(bfs_nodes) < 1: - print("Less than 1 node! Aborting!") - return - - scene.play(bfs_nodes[0].first_anim, border_anim) - for node in bfs_nodes[1:]: - scene.play(node.first_anim) - -class EquationSolver2d(ColorMappedObjectsScene): - CONFIG = { - "camera_config" : {"use_z_coordinate_for_display_order": True}, - "initial_lower_x" : -5, - "initial_upper_x" : 5, - "initial_lower_y" : -3, - "initial_upper_y" : 3, - "num_iterations" : 0, - "num_checkpoints" : 10, - - # Should really merge this into one enum-style variable - "display_in_parallel" : False, - "display_in_bfs" : False, - - "use_fancy_lines" : True, - "line_color" : WHITE, # Only used for non-fancy lines - - - # TODO: Consider adding a "find_all_roots" flag, which could be turned off - # to only explore one of the two candidate subrectangles when both are viable - - # Walker settings - "show_arrows" : True, - "scale_arrows" : False, - - # Special case settings - # These are used to hack UhOhScene, where we display different colors than - # are actually, secretly, guiding the evolution of the EquationSolver2d - # - # replacement_background_image_file has to be manually configured - "show_winding_numbers" : True, - - # Used for UhOhScene; - "manual_wind_override" : None, - - "show_cursor" : True, - - "linger_parameter" : 0.5, - - "use_separate_plays" : False, - - "use_cheap_winding_numbers" : False, # To use this, make num_checkpoints large - } - - def construct(self): - if self.num_iterations == 0: - print("You forgot to set num_iterations (maybe you meant to subclass something other than EquationSolver2d directly?)") - return - - ColorMappedObjectsScene.construct(self) - num_plane = self.num_plane - - clockwise_val_func = lambda p : -point_to_rev(self.func(p)) - - base_line = Line(UP, RIGHT, stroke_width = border_stroke_width, color = self.line_color) - - if self.use_fancy_lines: - base_line.color_using_background_image(self.background_image_file) - - def match_style_with_bg(obj1, obj2): - obj1.match_style(obj2) - bg = obj2.get_background_image_file() - if bg != None: - obj1.color_using_background_image(bg) - - run_time_base = 1 - run_time_with_lingering = run_time_base + self.linger_parameter - base_rate = lambda t : t - linger_rate = squish_rate_func(lambda t : t, 0, - fdiv(run_time_base, run_time_with_lingering)) - - cursor_base = TextMobject("?") - cursor_base.scale(2) - - # Helper functions for manual_wind_override - def head(m): - if m == None: - return None - return m[0] - - def child(m, i): - if m == None or m == 0: - return None - return m[i + 1] - - def Animate2dSolver(cur_depth, rect, dim_to_split, - sides_to_draw = [0, 1, 2, 3], - manual_wind_override = None): - print("Solver at depth: " + str(cur_depth)) - - if cur_depth >= self.num_iterations: - return EquationSolver2dNode(empty_animation) - - def draw_line_return_wind(start, end, start_wind, should_linger = False, draw_line = True): - alpha_winder = make_alpha_winder(clockwise_val_func, start, end, self.num_checkpoints, cheap = self.use_cheap_winding_numbers) - a0 = alpha_winder(0) - rebased_winder = lambda alpha: alpha_winder(alpha) - a0 + start_wind - colored_line = Line(num_plane.coords_to_point(*start) + IN, num_plane.coords_to_point(*end) + IN) - match_style_with_bg(colored_line, base_line) - - walker_anim = LinearWalker( - start_coords = start, - end_coords = end, - coords_to_point = num_plane.coords_to_point, - val_func = self.func, # Note: This is the image func, and not logic_func - number_update_func = rebased_winder if self.show_winding_numbers else None, - remover = True, - walker_stroke_color = WALKER_LIGHT_COLOR, - - show_arrows = self.show_arrows, - scale_arrows = self.scale_arrows, - ) - - if should_linger: # Do we need an "and not self.display_in_parallel" here? - run_time = run_time_with_lingering - rate_func = linger_rate - else: - run_time = run_time_base - rate_func = base_rate - - opt_line_anim = ShowCreation(colored_line) if draw_line else empty_animation - - line_draw_anim = AnimationGroup( - opt_line_anim, - walker_anim, - run_time = run_time, - rate_func = rate_func) - return (line_draw_anim, rebased_winder(1)) - - wind_so_far = 0 - anim = empty_animation - sides = [ - rect.get_top(), - rect.get_right(), - rect.get_bottom(), - rect.get_left() - ] - for (i, (start, end)) in enumerate(sides): - (next_anim, wind_so_far) = draw_line_return_wind(start, end, wind_so_far, - should_linger = i == len(sides) - 1, - draw_line = i in sides_to_draw) - anim = Succession(anim, next_anim) - - if self.show_cursor: - cursor = cursor_base.copy() - center_x, center_y = rect.get_center() - width = rect.get_width() - height = rect.get_height() - - cursor.move_to(num_plane.coords_to_point(center_x, center_y) + 10 * IN) - cursor.scale(min(width, height)) - - # Do a quick FadeIn, wait, and quick FadeOut on the cursor, matching rectangle-drawing time - cursor_anim = Succession( - FadeIn(cursor, run_time = 0.1), - Animation(cursor, run_time = 3.8), - FadeOut(cursor, run_time = 0.1) - ) - - anim = AnimationGroup(anim, cursor_anim) - - override_wind = head(manual_wind_override) - if override_wind != None: - total_wind = override_wind - else: - total_wind = round(wind_so_far) - - if total_wind == 0: - coords = [ - rect.get_top_left(), - rect.get_top_right(), - rect.get_bottom_right(), - rect.get_bottom_left() - ] - points = np.array([num_plane.coords_to_point(x, y) for (x, y) in coords]) + 3 * IN - # TODO: Maybe use diagonal lines or something to fill in rectangles indicating - # their "Nothing here" status? - # Or draw a large X or something - fill_rect = polygonObject = Polygon(*points, fill_opacity = 0.8, color = DARK_GREY) - return EquationSolver2dNode(Succession(anim, FadeIn(fill_rect))) - else: - (sub_rect1, sub_rect2) = rect.splits_on_dim(dim_to_split) - if dim_to_split == 0: - sub_rect_and_sides = [(sub_rect1, 1), (sub_rect2, 3)] - else: - sub_rect_and_sides = [(sub_rect1, 2), (sub_rect2, 0)] - children = [ - Animate2dSolver( - cur_depth = cur_depth + 1, - rect = sub_rect, - dim_to_split = 1 - dim_to_split, - sides_to_draw = [side_to_draw], - manual_wind_override = child(manual_wind_override, index) - ) - for (index, (sub_rect, side_to_draw)) in enumerate(sub_rect_and_sides) - ] - mid_line_coords = rect.split_line_on_dim(dim_to_split) - mid_line_points = [num_plane.coords_to_point(x, y) + 2 * IN for (x, y) in mid_line_coords] - mid_line = DashedLine(*mid_line_points) - - return EquationSolver2dNode(Succession(anim, ShowCreation(mid_line)), children) - - lower_x = self.initial_lower_x - upper_x = self.initial_upper_x - lower_y = self.initial_lower_y - upper_y = self.initial_upper_y - - x_interval = (lower_x, upper_x) - y_interval = (lower_y, upper_y) - - rect = RectangleData(x_interval, y_interval) - - print("Starting to compute anim") - - node = Animate2dSolver( - cur_depth = 0, - rect = rect, - dim_to_split = 0, - sides_to_draw = [], - manual_wind_override = self.manual_wind_override - ) - - print("Done computing anim") - - if self.display_in_parallel: - anim = node.display_in_parallel() - elif self.display_in_bfs: - anim = node.display_in_bfs() - else: - anim = node.display_in_series() - - # Keep timing details here in sync with details above - rect_points = [ - rect.get_top_left(), - rect.get_top_right(), - rect.get_bottom_right(), - rect.get_bottom_left(), - ] - border = Polygon(*[num_plane.coords_to_point(*x) + IN for x in rect_points]) - match_style_with_bg(border, base_line) - - rect_time_without_linger = 4 * run_time_base - rect_time_with_linger = 3 * run_time_base + run_time_with_lingering - def rect_rate(alpha): - time_in = alpha * rect_time_with_linger - if time_in < 3 * run_time_base: - return fdiv(time_in, 4 * run_time_base) - else: - time_in_last_leg = time_in - 3 * run_time_base - alpha_in_last_leg = fdiv(time_in_last_leg, run_time_with_lingering) - return interpolate(0.75, 1, linger_rate(alpha_in_last_leg)) - - border_anim = ShowCreation( - border, - run_time = rect_time_with_linger, - rate_func = rect_rate - ) - - print("About to do the big Play; for reference, the current time is ", time.strftime("%H:%M:%S")) - - if self.use_separate_plays: - node.play_in_bfs(self, border_anim) - else: - self.play(anim, border_anim) - - print("All done; for reference, the current time is ", time.strftime("%H:%M:%S")) - - self.wait() - -# TODO: Perhaps have option for bullets (pulses) to fade out and in at ends of line, instead of -# jarringly popping out and in? -# -# TODO: Perhaps have bullets change color corresponding to a function of their coordinates? -# This could involve some merging of functoinality with PiWalker -class LinePulser(ContinualAnimation): - def __init__(self, line, bullet_template, num_bullets, pulse_time, output_func = None, **kwargs): - self.line = line - self.num_bullets = num_bullets - self.pulse_time = pulse_time - self.bullets = [bullet_template.copy() for i in range(num_bullets)] - self.output_func = output_func - ContinualAnimation.__init__(self, VGroup(*self.bullets), **kwargs) - - def update_mobject(self, dt): - alpha = self.external_time % self.pulse_time - start = self.line.get_start() - end = self.line.get_end() - for i in range(self.num_bullets): - position = interpolate(start, end, - fdiv((i + alpha),(self.num_bullets))) - self.bullets[i].move_to(position) - if self.output_func: - position_2d = (position[0], position[1]) - rev = point_to_rev(self.output_func(position_2d)) - color = rev_to_color(rev) - self.bullets[i].set_color(color) - -class ArrowCircleTest(Scene): - def construct(self): - circle_radius = 3 - circle = Circle(radius = circle_radius, color = WHITE) - self.add(circle) - - base_arrow = Arrow(circle_radius * 0.7 * RIGHT, circle_radius * 1.3 * RIGHT) - - def rev_rotate(x, revs): - x.rotate(revs * TAU, about_point = ORIGIN) - x.set_color(rev_to_color(revs)) - return x - - num_arrows = 8 * 3 - - # 0.5 - fdiv below so as to get a clockwise rotation from left - arrows = [rev_rotate(base_arrow.copy(), 0.5 - (fdiv(i, num_arrows))) for i in range(num_arrows)] - arrows_vgroup = VGroup(*arrows) - - self.play(ShowCreation(arrows_vgroup), run_time = 2.5, rate_func=linear) - - self.wait() - -class FuncRotater(Animation): - CONFIG = { - "rev_func" : lambda x : x, # Func from alpha to CCW revolutions, - } - - # Perhaps abstract this out into an "Animation updating from original object" class - def interpolate_submobject(self, submobject, starting_submobject, alpha): - submobject.points = np.array(starting_submobject.points) - - def interpolate_mobject(self, alpha): - Animation.interpolate_mobject(self, alpha) - angle_revs = self.rev_func(alpha) - self.mobject.rotate( - angle_revs * TAU, - about_point = ORIGIN - ) - self.mobject.set_color(rev_to_color(angle_revs)) - -class TestRotater(Scene): - def construct(self): - test_line = Line(ORIGIN, RIGHT) - self.play(FuncRotater( - test_line, - rev_func = lambda x : x % 0.25, - run_time = 10)) - -# TODO: Be careful about clockwise vs. counterclockwise convention throughout! -# Make sure this is correct everywhere in resulting video. -class OdometerScene(ColorMappedObjectsScene): - CONFIG = { - # "func" : lambda p : 100 * p # Full coloring, essentially - "rotate_func" : lambda x : 2 * np.sin(2 * x * TAU), # This is given in terms of CW revs - "run_time" : 40, - "dashed_line_angle" : None, - "biased_display_start" : None, - "pure_odometer_background" : False - } - - def construct(self): - ColorMappedObjectsScene.construct(self) - - radius = ODOMETER_RADIUS - circle = Circle(center = ORIGIN, radius = radius) - circle.stroke_width = ODOMETER_STROKE_WIDTH - circle.color_using_background_image(self.background_image_file) - self.add(circle) - - if self.pure_odometer_background: - # Just display this background circle, for compositing in Premiere with PiWalker odometers - self.wait() - return - - if self.dashed_line_angle: - dashed_line = DashedLine(ORIGIN, radius * RIGHT) - # Clockwise rotation - dashed_line.rotate(-self.dashed_line_angle * TAU, about_point = ORIGIN) - self.add(dashed_line) - - num_display = DecimalNumber(0, include_background_rectangle = False) - num_display.move_to(2 * DOWN) - - caption = TextMobject("turns clockwise") - caption.next_to(num_display, DOWN) - self.add(caption) - - display_val_bias = 0 - if self.biased_display_start != None: - display_val_bias = self.biased_display_start - self.rotate_func(0) - display_func = lambda alpha : self.rotate_func(alpha) + display_val_bias - - base_arrow = Arrow(ORIGIN, RIGHT, buff = 0) - - self.play( - FuncRotater(base_arrow, rev_func = lambda x : -self.rotate_func(x)), - ChangingDecimal(num_display, display_func), - run_time = self.run_time, - rate_func=linear) - -############# -# Above are mostly general tools; here, we list, in order, finished or near-finished scenes - -class FirstSqrtScene(EquationSolver1d): - CONFIG = { - "x_min" : 0, - "x_max" : 2.5, - "y_min" : 0, - "y_max" : 2.5**2, - "graph_origin" : 2.5*DOWN + 5.5*LEFT, - "x_axis_width" : 12, - "zoom_factor" : 3, - "zoomed_canvas_center" : 2.25 * UP + 1.75 * LEFT, - "func" : lambda x : x**2, - "targetX" : np.sqrt(2), - "targetY" : 2, - "initial_lower_x" : 1, - "initial_upper_x" : 2, - "num_iterations" : 5, - "iteration_at_which_to_start_zoom" : 3, - "graph_label" : "y = x^2", - "show_target_line" : True, - "x_tick_frequency" : 0.25 - } - -class TestFirstSqrtScene(FirstSqrtScene): - CONFIG = { - "num_iterations" : 1, - } - -FirstSqrtSceneConfig = FirstSqrtScene.CONFIG -shiftVal = FirstSqrtSceneConfig["targetY"] - -class SecondSqrtScene(FirstSqrtScene): - CONFIG = { - "graph_label" : FirstSqrtSceneConfig["graph_label"] + " - " + str(shiftVal), - "show_y_as_deviation" : True, - } - -class TestSecondSqrtScene(SecondSqrtScene): - CONFIG = { - "num_iterations" : 1 - } - -class GuaranteedZeroScene(SecondSqrtScene): - CONFIG = { - # Manual config values, not automatically synced to anything above - "initial_lower_x" : 1.75, - "initial_upper_x" : 2 - } - -class TestGuaranteedZeroScene(GuaranteedZeroScene): - CONFIG = { - "num_iterations" : 1 - } - -# TODO: Pi creatures intrigued - -class RewriteEquation(Scene): - def construct(self): - # Can maybe use get_center() to perfectly center Groups before and after transform - - f_old = TexMobject("f(x)") - f_new = f_old.copy() - equals_old = TexMobject("=") - equals_old_2 = equals_old.copy() - equals_new = equals_old.copy() - g_old = TexMobject("g(x)") - g_new = g_old.copy() - minus_new = TexMobject("-") - zero_new = TexMobject("0") - f_old.next_to(equals_old, LEFT) - g_old.next_to(equals_old, RIGHT) - minus_new.next_to(g_new, LEFT) - f_new.next_to(minus_new, LEFT) - equals_new.next_to(g_new, RIGHT) - zero_new.next_to(equals_new, RIGHT) - - # where_old = TextMobject("Where does ") - # where_old.next_to(f_old, LEFT) - # where_new = where_old.copy() - # where_new.next_to(f_new, LEFT) - - # qmark_old = TextMobject("?") - # qmark_old.next_to(g_old, RIGHT) - # qmark_new = qmark_old.copy() - # qmark_new.next_to(zero_new, RIGHT) - - self.add(f_old, equals_old, equals_old_2, g_old) #, where_old, qmark_old) - self.wait() - self.play( - ReplacementTransform(f_old, f_new), - ReplacementTransform(equals_old, equals_new), - ReplacementTransform(g_old, g_new), - ReplacementTransform(equals_old_2, minus_new), - ShowCreation(zero_new), - # ReplacementTransform(where_old, where_new), - # ReplacementTransform(qmark_old, qmark_new), - ) - self.wait() - -class SignsExplanation(Scene): - def construct(self): - num_line = NumberLine() - largest_num = 10 - num_line.add_numbers(*list(range(-largest_num, largest_num + 1))) - self.add(num_line) - self.wait() - - pos_num = 3 - neg_num = -pos_num - - pos_arrow = Arrow( - num_line.number_to_point(0), - num_line.number_to_point(pos_num), - buff = 0, - color = positive_color) - neg_arrow = Arrow( - num_line.number_to_point(0), - num_line.number_to_point(neg_num), - buff = 0, - color = negative_color) - - plus_sign = TexMobject("+", fill_color = positive_color) - minus_sign = TexMobject("-", fill_color = negative_color) - - plus_sign.next_to(pos_arrow, UP) - minus_sign.next_to(neg_arrow, UP) - - #num_line.add_numbers(pos_num) - self.play(ShowCreation(pos_arrow), FadeIn(plus_sign)) - - #num_line.add_numbers(neg_num) - self.play(ShowCreation(neg_arrow), FadeIn(minus_sign)) - -class VectorField(Scene): - CONFIG = { - "func" : example_plane_func, - "granularity" : 10, - "arrow_scale_factor" : 0.1, - "normalized_arrow_scale_factor" : 5 - } - - def construct(self): - num_plane = NumberPlane() - self.add(num_plane) - - x_min, y_min = num_plane.point_to_coords(FRAME_X_RADIUS * LEFT + FRAME_Y_RADIUS * UP) - x_max, y_max = num_plane.point_to_coords(FRAME_X_RADIUS * RIGHT + FRAME_Y_RADIUS * DOWN) - - x_points = np.linspace(x_min, x_max, self.granularity) - y_points = np.linspace(y_min, y_max, self.granularity) - points = it.product(x_points, y_points) - - sized_arrows = Group() - unsized_arrows = Group() - for (x, y) in points: - output = self.func((x, y)) - output_size = np.sqrt(sum(output**2)) - normalized_output = output * fdiv(self.normalized_arrow_scale_factor, output_size) # Assume output has nonzero size here - arrow = Vector(output * self.arrow_scale_factor) - normalized_arrow = Vector(normalized_output * self.arrow_scale_factor) - arrow.move_to(num_plane.coords_to_point(x, y)) - normalized_arrow.move_to(arrow) - sized_arrows.add(arrow) - unsized_arrows.add(normalized_arrow) - - self.add(sized_arrows) - self.wait() - - self.play(ReplacementTransform(sized_arrows, unsized_arrows)) - self.wait() - -class HasItsLimitations(Scene): - CONFIG = { - "camera_config" : {"use_z_coordinate_for_display_order": True}, - } - - def construct(self): - num_line = NumberLine() - num_line.add_numbers() - self.add(num_line) - - self.wait() - - # We arrange to go from 2 to 4, a la the squaring in FirstSqrtScene - base_point = num_line.number_to_point(2) + OUT - - dot_color = ORANGE - - DOT_Z = OUT - # Note: This z-buffer value is needed for our static scenes, but is - # not sufficient for everything, in that we still need to use - # the foreground_mobjects trick during animations. - # At some point, we should figure out how to have animations - # play well with z coordinates. - - input_dot = Dot(base_point + DOT_Z, color = dot_color) - input_label = TextMobject("Input", fill_color = dot_color) - input_label.next_to(input_dot, UP + LEFT) - input_label.add_background_rectangle() - self.add_foreground_mobject(input_dot) - self.add(input_label) - - curved_arrow = Arc(0, color = MAROON_E) - curved_arrow.set_bound_angles(np.pi, 0) - curved_arrow.init_points() - curved_arrow.add_tip() - curved_arrow.move_arc_center_to(base_point + RIGHT) - # Could do something smoother, with arrowhead moving along partial arc? - self.play(ShowCreation(curved_arrow)) - - output_dot = Dot(base_point + 2 * RIGHT + DOT_Z, color = dot_color) - output_label = TextMobject("Output", fill_color = dot_color) - output_label.next_to(output_dot, UP + RIGHT) - output_label.add_background_rectangle() - - self.add_foreground_mobject(output_dot) - self.add(output_label) - self.wait() - - num_plane = NumberPlane() - num_plane.add_coordinates() - - new_base_point = base_point + 2 * UP - new_input_dot = input_dot.copy().move_to(new_base_point) - new_input_label = input_label.copy().next_to(new_input_dot, UP + LEFT) - - new_curved_arrow = Arc(0).match_style(curved_arrow) - new_curved_arrow.set_bound_angles(np.pi * 3/4, 0) - new_curved_arrow.init_points() - new_curved_arrow.add_tip() - - input_diff = input_dot.get_center() - curved_arrow.points[0] - output_diff = output_dot.get_center() - curved_arrow.points[-1] - - new_curved_arrow.shift((new_input_dot.get_center() - new_curved_arrow.points[0]) - input_diff) - - new_output_dot = output_dot.copy().move_to(new_curved_arrow.points[-1] + output_diff) - new_output_label = output_label.copy().next_to(new_output_dot, UP + RIGHT) - - dot_objects = Group(input_dot, input_label, output_dot, output_label, curved_arrow) - new_dot_objects = Group(new_input_dot, new_input_label, new_output_dot, new_output_label, new_curved_arrow) - - self.play( - FadeOut(num_line), FadeIn(num_plane), - ReplacementTransform(dot_objects, new_dot_objects), - ) - - self.wait() - - self.add_foreground_mobject(new_dot_objects) - - complex_plane = ComplexPlane() - complex_plane.add_coordinates() - - # This looks a little wonky and we may wish to do a crossfade in Premiere instead - self.play(FadeOut(num_plane), FadeIn(complex_plane)) - - self.wait() - - -class ComplexPlaneIs2d(Scene): - def construct(self): - com_plane = ComplexPlane() - self.add(com_plane) - # TODO: Add labels to axes, specific complex points - self.wait() - -class NumberLineScene(Scene): - def construct(self): - num_line = NumberLine() - self.add(num_line) - # TODO: Add labels, arrows, specific points - self.wait() - - border_color = PURPLE_E - inner_color = RED - stroke_width = 10 - - left_point = num_line.number_to_point(-1) - right_point = num_line.number_to_point(1) - # TODO: Make this line a thin rectangle - interval_1d = Line(left_point, right_point, - stroke_color = inner_color, stroke_width = stroke_width) - rect_1d = Rectangle(stroke_width = 0, fill_opacity = 1, fill_color = inner_color) - rect_1d.replace(interval_1d) - rect_1d.stretch_to_fit_height(SMALL_BUFF) - left_dot = Dot(left_point, stroke_width = stroke_width, color = border_color) - right_dot = Dot(right_point, stroke_width = stroke_width, color = border_color) - endpoints_1d = VGroup(left_dot, right_dot) - full_1d = VGroup(rect_1d, endpoints_1d) - self.play(ShowCreation(full_1d)) - self.wait() - - # TODO: Can polish the morphing above; have dots become left and right sides, and - # only then fill in the top and bottom - - num_plane = NumberPlane() - - random_points = [UP + LEFT, UP + RIGHT, DOWN + RIGHT, DOWN + LEFT] - - border_2d = Polygon( - *random_points, - stroke_color = border_color, - stroke_width = stroke_width) - - filling_2d = Polygon( - *random_points, - fill_color = inner_color, - fill_opacity = 0.8, - stroke_width = stroke_width) - full_2d = VGroup(filling_2d, border_2d) - - self.play( - FadeOut(num_line), - FadeIn(num_plane), - ReplacementTransform(full_1d, full_2d)) - - self.wait() - -class Initial2dFuncSceneBase(Scene): - CONFIG = { - "func" : point3d_func_from_complex_func(lambda c : c**2 - c**3/5 + 1) - # We don't use example_plane_func because, unfortunately, the sort of examples - # which are good for demonstrating our color mapping haven't turned out to be - # good for visualizing in this manner; the gridlines run over themselves multiple - # times in too confusing a fashion - } - - def show_planes(self): - print("Error! Unimplemented (pure virtual) show_planes") - - def shared_construct(self): - points = [LEFT + DOWN, RIGHT + DOWN, LEFT + UP, RIGHT + UP] - for i in range(len(points) - 1): - line = Line(points[i], points[i + 1], color = RED) - self.obj_draw(line) - - def wiggle_around(point): - radius = 0.2 - small_circle = cw_circle.copy() - small_circle.scale(radius) - small_circle.move_to(point + radius * RIGHT) - small_circle.set_color(RED) - self.obj_draw(small_circle) - - wiggle_around(points[-1]) - - def obj_draw(self, input_object): - self.play(ShowCreation(input_object)) - - def construct(self): - self.show_planes() - self.shared_construct() - -# Alternative to the below, using MappingCameras, but no morphing animation -class Initial2dFuncSceneWithoutMorphing(Initial2dFuncSceneBase): - - def setup(self): - left_camera = Camera(**self.camera_config) - right_camera = MappingCamera( - mapping_func = self.func, - **self.camera_config) - split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config) - self.camera = split_screen_camera - - def show_planes(self): - self.num_plane = NumberPlane() - self.num_plane.prepare_for_nonlinear_transform() - #num_plane.fade() - self.add(self.num_plane) - -# Alternative to the above, manually implementing split screen with a morphing animation -class Initial2dFuncSceneMorphing(Initial2dFuncSceneBase): - CONFIG = { - "num_needed_anchor_curves" : 10, - } - - def setup(self): - split_line = DashedLine(FRAME_Y_RADIUS * UP, FRAME_Y_RADIUS * DOWN) - self.num_plane = NumberPlane(x_radius = FRAME_X_RADIUS/2) - self.num_plane.to_edge(LEFT, buff = 0) - self.num_plane.prepare_for_nonlinear_transform() - self.add(self.num_plane, split_line) - - def squash_onto_left(self, object): - object.shift(FRAME_X_RADIUS/2 * LEFT) - - def squash_onto_right(self, object): - object.shift(FRAME_X_RADIUS/2 * RIGHT) - - def obj_draw(self, input_object): - output_object = input_object.copy() - if input_object.get_num_curves() < self.num_needed_anchor_curves: - input_object.insert_n_curves(self.num_needed_anchor_curves) - output_object.apply_function(self.func) - self.squash_onto_left(input_object) - self.squash_onto_right(output_object) - self.play( - ShowCreation(input_object), - ShowCreation(output_object) - ) - - def show_planes(self): - right_plane = self.num_plane.copy() - right_plane.center() - right_plane.prepare_for_nonlinear_transform() - right_plane.apply_function(self.func) - right_plane.shift(FRAME_X_RADIUS/2 * RIGHT) - self.right_plane = right_plane - crappy_cropper = FullScreenFadeRectangle(fill_opacity = 1) - crappy_cropper.stretch_to_fit_width(FRAME_X_RADIUS) - crappy_cropper.to_edge(LEFT, buff = 0) - self.play( - ReplacementTransform(self.num_plane.copy(), right_plane), - FadeIn(crappy_cropper), - Animation(self.num_plane), - run_time = 3 - ) - -class DemonstrateColorMapping(ColorMappedObjectsScene): - CONFIG = { - "show_num_plane" : False, - "show_full_color_map" : True - } - - def construct(self): - ColorMappedObjectsScene.construct(self) - - # Doing this in Premiere now instead - # output_plane_label = TextMobject("Output Plane", color = WHITE) - # output_plane_label.move_to(3 * UP) - # self.add_foreground_mobject(output_plane_label) - - if self.show_full_color_map: - bright_background = Rectangle(width = 2 * FRAME_X_RADIUS + 1, height = 2 * FRAME_Y_RADIUS + 1, fill_opacity = 1) - bright_background.color_using_background_image(self.background_image_file) - dim_background = bright_background.copy() - dim_background.fill_opacity = 0.3 - - background = bright_background.copy() - self.add(background) - self.wait() - self.play(ReplacementTransform(background, dim_background)) - - self.wait() - - ray = Line(ORIGIN, 10 * LEFT) - - - circle = cw_circle.copy() - circle.color_using_background_image(self.background_image_file) - - self.play(ShowCreation(circle)) - - self.wait() - - scale_up_factor = 5 - scale_down_factor = 20 - self.play(ApplyMethod(circle.scale, fdiv(1, scale_down_factor))) - - self.play(ApplyMethod(circle.scale, scale_up_factor * scale_down_factor)) - - self.play(ApplyMethod(circle.scale, fdiv(1, scale_up_factor))) - - self.wait() - self.remove(circle) - - ray = Line(ORIGIN, 10 * LEFT) - ray.color_using_background_image(self.background_image_file) - - self.play(ShowCreation(ray)) - - self.wait() - - self.play(Rotating(ray, about_point = ORIGIN, radians = -TAU/2)) - - self.wait() - - self.play(Rotating(ray, about_point = ORIGIN, radians = -TAU/2)) - - self.wait() - - if self.show_full_color_map: - self.play(ReplacementTransform(background, bright_background)) - self.wait() - -# Everything in this is manually kept in sync with WindingNumber_G/TransitionFromPathsToBoundaries -class LoopSplitScene(ColorMappedObjectsScene): - CONFIG = { - "func" : plane_func_by_wind_spec( - (-2, 0, 2), (2, 0, 1) - ), - "use_fancy_lines" : True, - } - - def PulsedLine(self, - start, end, - bullet_template, - num_bullets = 4, - pulse_time = 1, - **kwargs): - line = Line(start, end, color = WHITE, stroke_width = 4, **kwargs) - if self.use_fancy_lines: - line.color_using_background_image(self.background_image_file) - anim = LinePulser( - line = line, - bullet_template = bullet_template, - num_bullets = num_bullets, - pulse_time = pulse_time, - output_func = self.func, - **kwargs) - return (line, VMobject(*anim.bullets), anim) - - def construct(self): - ColorMappedObjectsScene.construct(self) - - scale_factor = 2 - shift_term = 0 - - # TODO: Change all this to use a wider than tall loop, made of two squares - - # Original loop - tl = (UP + 2 * LEFT) * scale_factor - tm = UP * scale_factor - tr = (UP + 2 * RIGHT) * scale_factor - bl = (DOWN + 2 * LEFT) * scale_factor - bm = DOWN * scale_factor - br = (DOWN + 2 * RIGHT) * scale_factor - - top_line = Line(tl, tr) # Invisible; only used for surrounding circle - bottom_line = Line(br, bl) # Invisible; only used for surrounding circle - - stroke_width = top_line.stroke_width - - default_bullet = PiCreature() - default_bullet.scale(0.15) - - def pl(a, b): - return self.PulsedLine(a, b, default_bullet) - - def indicate_circle(x, double_horizontal_stretch = False): - circle = Circle(color = WHITE, radius = 2 * np.sqrt(2)) - circle.move_to(x.get_center()) - - if x.get_slope() == 0: - circle.stretch(0.2, 1) - if double_horizontal_stretch: - circle.stretch(2, 0) - else: - circle.stretch(0.2, 0) - return circle - - tl_line_trip = pl(tl, tm) - midline_left_trip = pl(tm, bm) - bl_line_trip = pl(bm, bl) - left_line_trip = pl(bl, tl) - - left_square_trips = [tl_line_trip, midline_left_trip, bl_line_trip, left_line_trip] - left_square_lines = [x[0] for x in left_square_trips] - left_square_lines_vmobject = VMobject(*left_square_lines) - left_square_bullets = [x[1] for x in left_square_trips] - left_square_anims = [x[2] for x in left_square_trips] - - tr_line_trip = pl(tm, tr) - right_line_trip = pl(tr, br) - br_line_trip = pl(br, bm) - midline_right_trip = pl(bm, tm) - - right_square_trips = [tr_line_trip, right_line_trip, br_line_trip, midline_right_trip] - right_square_lines = [x[0] for x in right_square_trips] - right_square_lines_vmobject = VMobject(*right_square_lines) - right_square_bullets = [x[1] for x in right_square_trips] - right_square_anims = [x[2] for x in right_square_trips] - - midline_trips = [midline_left_trip, midline_right_trip] - midline_lines = [x[0] for x in midline_trips] - midline_lines_vmobject = VMobject(*midline_lines) - midline_bullets = [x[1] for x in midline_trips] - midline_anims = [x[1] for x in midline_trips] - - left_line = left_line_trip[0] - right_line = right_line_trip[0] - - for b in left_square_bullets + right_square_bullets: - b.set_fill(opacity = 0) - - faded = 0.3 - - # Workaround for FadeOut/FadeIn not playing well with ContinualAnimations due to - # Transforms making copies no longer identified with the ContinualAnimation's tracked mobject - def bullet_fade(start, end, mob): - return UpdateFromAlphaFunc(mob, lambda m, a : m.set_fill(opacity = interpolate(start, end, a))) - - def bullet_list_fade(start, end, bullet_list): - return [bullet_fade(start, end, b) for b in bullet_list] - - def line_fade(start, end, mob): - return UpdateFromAlphaFunc(mob, lambda m, a : m.set_stroke(width = interpolate(start, end, a) * stroke_width)) - - def play_combined_fade(start, end, lines_vmobject, bullets): - self.play( - line_fade(start, end, lines_vmobject), - *bullet_list_fade(start, end, bullets) - ) - - def play_fade_left(start, end): - play_combined_fade(start, end, left_square_lines_vmobject, left_square_bullets) - - def play_fade_right(start, end): - play_combined_fade(start, end, right_square_lines_vmobject, right_square_bullets) - - def play_fade_mid(start, end): - play_combined_fade(start, end, midline_lines_vmobject, midline_bullets) - - def flash_circles(circles): - self.play(LaggedStartMap(FadeIn, VGroup(circles))) - self.wait() - self.play(FadeOut(VGroup(circles))) - self.wait() - - self.add(left_square_lines_vmobject, right_square_lines_vmobject) - self.remove(*midline_lines) - self.wait() - self.play(ShowCreation(midline_lines[0])) - self.add(midline_lines_vmobject) - self.wait() - - self.add(*left_square_anims) - self.play(line_fade(1, faded, right_square_lines_vmobject), *bullet_list_fade(0, 1, left_square_bullets)) - self.wait() - flash_circles([indicate_circle(l) for l in left_square_lines]) - self.play(line_fade(faded, 1, right_square_lines_vmobject), *bullet_list_fade(1, 0, left_square_bullets)) - self.wait() - - self.add(*right_square_anims) - self.play(line_fade(1, faded, left_square_lines_vmobject), *bullet_list_fade(0, 1, right_square_bullets)) - self.wait() - flash_circles([indicate_circle(l) for l in right_square_lines]) - self.play(line_fade(faded, 1, left_square_lines_vmobject), *bullet_list_fade(1, 0, right_square_bullets)) - self.wait() - - self.play(*bullet_list_fade(0, 1, left_square_bullets + right_square_bullets)) - self.wait() - - outside_circlers = [ - indicate_circle(left_line), - indicate_circle(right_line), - indicate_circle(top_line, double_horizontal_stretch = True), - indicate_circle(bottom_line, double_horizontal_stretch = True) - ] - flash_circles(outside_circlers) - - inner_circle = indicate_circle(midline_lines[0]) - self.play(FadeIn(inner_circle)) - self.wait() - self.play(FadeOut(inner_circle), line_fade(1, 0, midline_lines_vmobject), *bullet_list_fade(1, 0, midline_bullets)) - self.wait() - - # Repeat for effect, goes well with narration - self.play(FadeIn(inner_circle), line_fade(0, 1, midline_lines_vmobject), *bullet_list_fade(0, 1, midline_bullets)) - self.wait() - self.play(FadeOut(inner_circle), line_fade(1, 0, midline_lines_vmobject), *bullet_list_fade(1, 0, midline_bullets)) - self.wait() - -# TODO: Perhaps do extra illustration of zooming out and winding around a large circle, -# to illustrate relation between degree and large-scale winding number -class FundThmAlg(EquationSolver2d): - CONFIG = { - "func" : plane_func_by_wind_spec((1, 2), (-1, 1.5), (-1, 1.5)), - "num_iterations" : 2, - } - -class SolveX5MinusXMinus1(EquationSolver2d): - CONFIG = { - "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), - "num_iterations" : 10, - "show_cursor" : True, - "display_in_bfs" : True, - } - -class PureColorMapOfX5Thing(PureColorMap): - CONFIG = { - "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), - } - -class X5ThingWithRightHalfGreyed(SolveX5MinusXMinus1): - CONFIG = { - "num_iterations" : 3, - "manual_wind_override" : (1, None, (1, (0, None, None), (0, None, None))) - } - -class SolveX5MinusXMinus1_5Iterations(EquationSolver2d): - CONFIG = { - "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), - "num_iterations" : 5, - "show_cursor" : True, - "display_in_bfs" : True, - "manual_wind_override" : (None, None, (None, (0, None, None), (0, None, None))) - } - -class X5_Monster_Red_Lines(SolveX5MinusXMinus1_5Iterations): - CONFIG = { - "use_separate_plays" : True, - "use_fancy_lines" : False, - "line_color" : RED, - } - -class X5_Monster_Green_Lines(X5_Monster_Red_Lines): - CONFIG = { - "line_color" : GREEN, - } - -class X5_Monster_Red_Lines_Long(X5_Monster_Red_Lines): - CONFIG = { - "num_iterations" : 6 - } - -class X5_Monster_Green_Lines_Long(X5_Monster_Green_Lines): - CONFIG = { - "num_iterations" : 6 - } - -class X5_Monster_Red_Lines_Little_More(X5_Monster_Red_Lines_Long): - CONFIG = { - "num_iterations" : 7 - } - -class X5_Monster_Green_Lines_Little_More(X5_Monster_Green_Lines_Long): - CONFIG = { - "num_iterations" : 7 - } - -class X5_Monster_Red_Lines_No_Numbers(X5_Monster_Red_Lines): - CONFIG = { - "num_iterations" : 3, - "show_winding_numbers" : False, - } - -class X5_Monster_Green_Lines_No_Numbers(X5_Monster_Green_Lines): - CONFIG = { - "num_iterations" : 3, - "show_winding_numbers" : False, - } - -class SolveX5MinusXMinus1_3Iterations(EquationSolver2d): - CONFIG = { - "func" : plane_func_from_complex_func(lambda c : c**5 - c - 1), - "num_iterations" : 3, - "show_cursor" : True, - "display_in_bfs" : True, - } - -class Diagnostic(SolveX5MinusXMinus1_3Iterations): - CONFIG = { - # I think the combination of these two makes things slow - "use_separate_plays" : not False, # This one isn't important to set any particular way, so let's leave it like this - "use_fancy_lines" : True, - - # This causes a small slowdown (before rendering, in particular), but not the big one, I think - "show_winding_numbers" : True, - - # This doesn't significantly matter for rendering time, I think - "camera_config" : {"use_z_coordinate_for_display_order" : True} - } - -# All above flags False (meaning not db = False): just under 30 it/s -# not db = True: 30 -# use_fancy_lines = True: 30 at first (if scene.play(bfs_nodes[0].first_anim, border_anim is off), but then drops to 3 (or drops right away if that simultaneous play is on) -# use_z_coordinate = True: 30 -# show_winding_numbers = True: 10 -# winding AND use_fancy_lines: 10 -# not db AND fancy_lines AND z_coords = true, winding = false: 3. Not 30, but 3. Slow. -# db AND use_fancy: 3. Slow. -# fancy AND z_coords: 30. Fast. [Hm, this may have been a mistake; fancy and z_coords is now slow?] -# fancy, winding, AND z_coords, but not (not db): 10 -# not db, winding, AND z_coords, but not fancy: 10 - -# class DiagnosticB(Diagnostic): -# CONFIG = { -# "num_iterations" : 3, -# #"num_checkpoints" : 100, -# #"show_winding_numbers" : False, -# #"use_cheap_winding_numbers" : True, -# } - -class SolveX5MinusXMinus1Parallel(SolveX5MinusXMinus1): - CONFIG = { - "display_in_parallel" : True - } - -class SolveX5MinusXMinus1BFS(SolveX5MinusXMinus1): - CONFIG = { - "display_in_bfs" : True - } - -class PreviewClip(EquationSolver2d): - CONFIG = { - "func" : example_plane_func, - "num_iterations" : 5, - "display_in_parallel" : True, - "use_fancy_lines" : True, - } - -class ParallelClip(EquationSolver2d): - CONFIG = { - "func" : plane_func_by_wind_spec( - (-3, -1.3, 2), (0.1, 0.2, 1), (2.8, -2, 1) - ), - "num_iterations" : 5, - "display_in_parallel" : True, - } - -class EquationSolver2dMatchBreakdown(EquationSolver2d): - CONFIG = { - "func" : plane_func_by_wind_spec( - (-2, 0.3, 2), (2, -0.2, 1) # Not an exact match, because our breakdown function has a zero along midlines... - ), - "num_iterations" : 5, - "display_in_parallel" : True, - "show_cursor" : True - } - -class EquationSolver2dMatchBreakdown_parallel(EquationSolver2dMatchBreakdown): - CONFIG = { - "display_in_parallel" : True, - "display_in_bfs" : False, - } - -class EquationSolver2dMatchBreakdown_bfs(EquationSolver2dMatchBreakdown): - CONFIG = { - "display_in_parallel" : False, - "display_in_bfs" : True, - } - -class QuickPreview(PreviewClip): - CONFIG = { - "num_iterations" : 3, - "display_in_parallel" : False, - "display_in_bfs" : True, - "show_cursor" : True - } - -class LongEquationSolver(EquationSolver2d): - CONFIG = { - "func" : example_plane_func, - "num_iterations" : 10, - "display_in_bfs" : True, - "linger_parameter" : 0.4, - "show_cursor" : True, - } - -class QuickPreviewUnfancy(LongEquationSolver): - CONFIG = { - # "use_fancy_lines" : False, - } - -# TODO: Borsuk-Ulam visuals -# Note: May want to do an ordinary square scene, then MappingCamera it into a circle -# class BorsukUlamScene(PiWalker): - -# 3-way scene of "Good enough"-illustrating odometers; to be composed in Premiere -left_func = lambda x : x**2 - x + 1 -diff_func = lambda x : np.cos(1.4 * (x - 0.1) * (np.log(x + 0.1) - 0.3) * TAU)/2.1 - -class LeftOdometer(OdometerScene): - CONFIG = { - "rotate_func" : left_func, - "biased_display_start" : 0 - } - -class RightOdometer(OdometerScene): - CONFIG = { - "rotate_func" : lambda x : left_func(x) + diff_func(x), - "biased_display_start" : 0 - } - -class DiffOdometer(OdometerScene): - CONFIG = { - "rotate_func" : diff_func, - "dashed_line_angle" : 0.5, - "biased_display_start" : 0 - } - -class CombineInterval(Scene): - def construct(self): - plus_sign = TexMobject("+", fill_color = positive_color) - minus_sign = TexMobject("-", fill_color = negative_color) - - left_point = Dot(LEFT, color = positive_color) - right_point = Dot(RIGHT, color = negative_color) - line1 = Line(LEFT, RIGHT) - interval1 = Group(line1, left_point, right_point) - - plus_sign.next_to(left_point, UP) - minus_sign.next_to(right_point, UP) - - self.add(interval1, plus_sign, minus_sign) - self.wait() - self.play( - CircleIndicate(plus_sign), - CircleIndicate(minus_sign), - ) - self.wait() - - mid_point = Dot(ORIGIN, color = GREY) - - question_mark = TexMobject("?", fill_color = GREY) - plus_sign_copy = plus_sign.copy() - minus_sign_copy = minus_sign.copy() - new_signs = Group(question_mark, plus_sign_copy, minus_sign_copy) - for sign in new_signs: sign.next_to(mid_point, UP) - - self.play(FadeIn(mid_point), FadeIn(question_mark)) - self.wait() - - self.play( - ApplyMethod(mid_point.set_color, positive_color), - ReplacementTransform(question_mark, plus_sign_copy), - ) - self.play( - CircleIndicate(plus_sign_copy), - CircleIndicate(minus_sign), - ) - - self.wait() - - self.play( - ApplyMethod(mid_point.set_color, negative_color), - ReplacementTransform(plus_sign_copy, minus_sign_copy), - ) - self.play( - CircleIndicate(minus_sign_copy), - CircleIndicate(plus_sign), - ) - - self.wait() - -class CombineInterval2(Scene): - def construct(self): - plus_sign = TexMobject("+", fill_color = positive_color) - - def make_interval(a, b): - line = Line(a, b) - start_dot = Dot(a, color = positive_color) - end_dot = Dot(b, color = positive_color) - start_sign = plus_sign.copy().next_to(start_dot, UP) - end_sign = plus_sign.copy().next_to(end_dot, UP) - return Group(start_sign, end_sign, line, start_dot, end_dot) - - def pair_indicate(a, b): - self.play( - CircleIndicate(a), - CircleIndicate(b) - ) - - left_interval = make_interval(2 * LEFT, LEFT) - right_interval = make_interval(RIGHT, 2 * RIGHT) - - self.play(FadeIn(left_interval), FadeIn(right_interval)) - - pair_indicate(left_interval[0], left_interval[1]) - - pair_indicate(right_interval[0], right_interval[1]) - - self.play( - ApplyMethod(left_interval.shift, RIGHT), - ApplyMethod(right_interval.shift, LEFT), - ) - - pair_indicate(left_interval[0], right_interval[1]) - - self.wait() - -tiny_loop_func = scale_func(plane_func_by_wind_spec((-1, -2), (1, 1), (1, 1)), 0.3) - -class TinyLoopScene(ColorMappedByFuncScene): - CONFIG = { - "func" : tiny_loop_func, - "show_num_plane" : False, - "loop_point" : ORIGIN, - "circle_scale" : 0.7 - } - - def construct(self): - ColorMappedByFuncScene.construct(self) - - circle = cw_circle.copy() - circle.scale(self.circle_scale) - circle.move_to(self.loop_point) - - self.play(ShowCreation(circle)) - self.wait() - -class TinyLoopInInputPlaneAroundNonZero(TinyLoopScene): - CONFIG = { - "loop_point" : 0.5 * RIGHT - } - -class TinyLoopInInputPlaneAroundZero(TinyLoopScene): - CONFIG = { - "loop_point" : UP + RIGHT - } - -class TinyLoopInOutputPlaneAroundNonZero(TinyLoopInInputPlaneAroundNonZero): - CONFIG = { - "camera_class" : MappingCamera, - "camera_config" : {"mapping_func" : point3d_func_from_plane_func(tiny_loop_func)}, - "show_output" : True, - "show_num_plane" : False, - } - -class TinyLoopInOutputPlaneAroundZero(TinyLoopInInputPlaneAroundZero): - CONFIG = { - "camera_class" : MappingCamera, - "camera_config" : {"mapping_func" : point3d_func_from_plane_func(tiny_loop_func)}, - "show_output" : True, - "show_num_plane" : False, - } - -class BorderOf2dRegionScene(Scene): - def construct(self): - num_plane = NumberPlane() - self.add(num_plane) - - points = standard_rect + 1.5 * UP + 2 * RIGHT - interior = Polygon(*points, fill_color = neutral_color, fill_opacity = 1, stroke_width = 0) - self.play(FadeIn(interior)) - - border = Polygon(*points, color = negative_color, stroke_width = border_stroke_width) - self.play(ShowCreation(border)) - -big_loop_no_zeros_func = lambda x_y5 : complex_to_pair(np.exp(complex(10, x_y5[1] * np.pi))) - -class BigLoopNoZeros(ColorMappedObjectsScene): - CONFIG = { - "func" : big_loop_no_zeros_func - } - - def construct(self): - ColorMappedObjectsScene.construct(self) - points = 3 * np.array([UL, UR, DR, DL]) - polygon = Polygon(*points) - polygon.color_using_background_image(self.background_image_file) - self.play(ShowCreation(polygon)) - - self.wait() - - polygon2 = polygon.copy() - polygon2.fill_opacity = 1 - self.play(FadeIn(polygon2)) - - self.wait() - -class ExamplePlaneFunc(ColorMappedByFuncScene): - CONFIG = { - "show_num_plane" : False, - "func" : example_plane_func - } - - def construct(self): - ColorMappedByFuncScene.construct(self) - - radius = 0.5 - - def circle_point(point): - circle = cw_circle.copy().scale(radius).move_to(point) - self.play(ShowCreation(circle)) - return circle - - def circle_spec(spec): - point = spec[0] * RIGHT + spec[1] * UP - return circle_point(point) - - nonzero_point = ORIGIN # Manually chosen, not auto-synced with example_plane_func - nonzero_point_circle = circle_point(nonzero_point) - self.wait() - self.play(FadeOut(nonzero_point_circle)) - self.wait() - - zero_circles = Group() - - for spec in example_plane_func_spec: - zero_circles.add(circle_spec(spec)) - - self.wait() - - # TODO: Fix the code in Fade to automatically propagate correctly - # to subobjects, even with special vectorized object handler. - # Also, remove the special handling from FadeOut, have it implemented - # solely through Fade. - # - # But for now, I'll just take care of this stuff myself here. - # self.play(*[FadeOut(zero_circle) for zero_circle in zero_circles]) - self.play(FadeOut(zero_circles)) - self.wait() - - # We can reuse our nonzero point from before for "Output doesn't go through ever color" - # Do re-use in Premiere - - # We can also re-use the first of our zero-circles for "Output does go through every color", - # but just in case it would be useful, here's another one, all on its own - - specific_spec_index = 0 - temp_circle = circle_spec(example_plane_func_spec[specific_spec_index]) - self.play(FadeOut(temp_circle)) - - self.wait() - -class PiWalkerExamplePlaneFunc(PiWalkerRect): - CONFIG = { - "show_num_plane" : False, - "func" : example_plane_func, - # These are just manually entered, not - # automatically kept in sync with example_plane_func: - "start_x" : -4, - "start_y" : 3, - "walk_width" : 8, - "walk_height" : 6, - } - -class NoticeHowOnThisLoop(PiWalkerRect): - CONFIG = { - "show_num_plane" : False, - "func" : example_plane_func, - # These are just manually entered, not - # automatically kept in sync with example_plane_func: - "start_x" : 0.5, - "start_y" : -0.5, - "walk_width" : -1, # We trace from bottom-right clockwise on this one, to start at a red point - "walk_height" : -1, - } - -class ButOnThisLoopOverHere(NoticeHowOnThisLoop): - CONFIG = { - # These are just manually entered, not - # automatically kept in sync with example_plane_func: - "start_x" : -1, - "start_y" : 0, - "walk_width" : 1, - "walk_height" : 1, - } - -class PiWalkerExamplePlaneFuncWithScaling(PiWalkerExamplePlaneFunc): - CONFIG = { - "scale_arrows" : True, - "display_size" : True, - } - -class TinyLoopOfBasicallySameColor(PureColorMap): - def construct(self): - PureColorMap.construct(self) - radius = 0.5 - circle = cw_circle.copy().scale(radius).move_to(UP + RIGHT) - self.play(ShowCreation(circle)) - self.wait() - -def uhOhFunc(xxx_todo_changeme11): - (x, y) = xxx_todo_changeme11 - x = -np.clip(x, -5, 5)/5 - y = -np.clip(y, -3, 3)/3 - - alpha = 0.5 # Most things will return green - - # These next three things should really be abstracted into some "Interpolated triangle" function - - if x >= 0 and y >= x and y <= 1: - alpha = interpolate(0.5, 1, y - x) - - if x < 0 and y >= -2 * x and y <= 1: - alpha = interpolate(0.5, 1, y + 2 * x) - - if x >= -1 and y >= 2 * (x + 1) and y <= 1: - alpha = interpolate(0.5, 0, y - 2 * (x + 1)) - - return complex_to_pair(100 * np.exp(complex(0, TAU * (0.5 - alpha)))) - -class UhOhFuncTest(PureColorMap): - CONFIG = { - "func" : uhOhFunc - } - - -class UhOhScene(EquationSolver2d): - CONFIG = { - "func" : uhOhFunc, - "manual_wind_override" : (1, None, (1, None, (1, None, None))), # Tailored to UhOhFunc above - "show_winding_numbers" : False, - "num_iterations" : 5, - } - -class UhOhSceneWithWindingNumbers(UhOhScene): - CONFIG = { - "show_winding_numbers" : True, - } - -class UhOhSceneWithWindingNumbersNoOverride(UhOhSceneWithWindingNumbers): - CONFIG = { - "manual_wind_override" : None, - "num_iterations" : 2 - } - -class UhOhSalientStill(ColorMappedObjectsScene): - CONFIG = { - "func" : uhOhFunc - } - - def construct(self): - ColorMappedObjectsScene.construct(self) - - new_up = 3 * UP - new_left = 5 * LEFT - - thin_line = Line(UP, RIGHT, color = WHITE) - - main_points = [new_left + new_up, new_up, ORIGIN, new_left] - polygon = Polygon(*main_points, stroke_width = border_stroke_width) - thin_polygon = polygon.copy().match_style(thin_line) - polygon.color_using_background_image(self.background_image_file) - - midline = Line(new_up + 0.5 * new_left, 0.5 * new_left, stroke_width = border_stroke_width) - thin_midline = midline.copy().match_style(thin_line) - midline.color_using_background_image(self.background_image_file) - - self.add(polygon, midline) - - self.wait() - - everything_filler = FullScreenFadeRectangle(fill_opacity = 1) - everything_filler.color_using_background_image(self.background_image_file) - - thin_white_copy = Group(thin_polygon, thin_midline) - - self.play(FadeIn(everything_filler), FadeIn(thin_white_copy)) - - self.wait() - - -# TODO: Brouwer's fixed point theorem visuals -# class BFTScene(Scene): - -# TODO: Pi creatures wide-eyed in amazement - -################# - -# TODOs, from easiest to hardest: - -# Minor fiddling with little things in each animation; placements, colors, timing, text - -# Initial odometer scene (simple once previous Pi walker scene is decided upon) - -# Writing new Pi walker scenes by parametrizing general template - -# (All the above are basically trivial tinkering at this point) - -# ---- - -# Pi creature emotion stuff - -# BFT visuals - -# Borsuk-Ulam visuals - -#################### - -# Random test scenes and test functions go here: - -def rect_to_circle(xxx_todo_changeme12): - (x, y, z) = xxx_todo_changeme12 - size = np.sqrt(x**2 + y**2) - max_abs_size = max(abs(x), abs(y)) - return fdiv(np.array((x, y, z)) * max_abs_size, size) - -class MapPiWalkerRect(PiWalkerRect): - CONFIG = { - "camera_class" : MappingCamera, - "camera_config" : {"mapping_func" : rect_to_circle}, - "show_output" : True - } - -class ShowBack(PiWalkerRect): - CONFIG = { - "func" : plane_func_by_wind_spec((1, 2), (-1, 1.5), (-1, 1.5)) - } - -class PiWalkerOdometerTest(PiWalkerExamplePlaneFunc): - CONFIG = { - "display_odometer" : True - } - -class PiWalkerFancyLineTest(PiWalkerExamplePlaneFunc): - CONFIG = { - "color_foreground_not_background" : True - } - -class NotFoundScene(Scene): - def construct(self): - self.add(TextMobject("SCENE NOT FOUND!")) - self.wait() - -criticalStripYScale = 100 -criticalStrip = Axes(x_min = -0.5, x_max = 1.5, x_axis_config = {"unit_size" : FRAME_X_RADIUS, - "number_at_center" : 0.5}, - y_min = -criticalStripYScale, y_max = criticalStripYScale, - y_axis_config = {"unit_size" : fdiv(FRAME_Y_RADIUS, criticalStripYScale)}) - -class ZetaViz(PureColorMap): - CONFIG = { - "func" : plane_zeta, - #"num_plane" : criticalStrip, - "show_num_plane" : True - } - -class TopLabel(Scene): - CONFIG = { - "text" : "Text" - } - def construct(self): - label = TextMobject(self.text) - label.move_to(3 * UP) - self.add(label) - self.wait() - -# This is a giant hack that doesn't handle rev wrap-around correctly; should use -# make_alpha_winder instead -class SpecifiedWinder(PiWalker): - CONFIG = { - "start_x" : 0, - "start_y" : 0, - "x_wind" : 1, # Assumed positive - "y_wind" : 1, # Assumed positive - "step_size" : 0.1 - } - - def setup(self): - rev_func = lambda p : point_to_rev(self.func(p)) - start_pos = np.array((self.start_x, self.start_y)) - cur_pos = start_pos.copy() - start_rev = rev_func(start_pos) - - mid_rev = start_rev - while (abs(mid_rev - start_rev) < self.x_wind): - cur_pos += (self.step_size, 0) - mid_rev = rev_func(cur_pos) - - print("Reached ", cur_pos, ", with rev ", mid_rev - start_rev) - mid_pos = cur_pos.copy() - - end_rev = mid_rev - while (abs(end_rev - mid_rev) < self.y_wind): - cur_pos -= (0, self.step_size) - end_rev = rev_func(cur_pos) - - end_pos = cur_pos.copy() - - print("Reached ", cur_pos, ", with rev ", end_rev - mid_rev) - - self.walk_coords = [start_pos, mid_pos, end_pos] - print("Walk coords: ", self.walk_coords) - PiWalker.setup(self) - -class OneFifthTwoFifthWinder(SpecifiedWinder): - CONFIG = { - "func" : example_plane_func, - "start_x" : -2.0, - "start_y" : 1.0, - "x_wind" : 0.2, - "y_wind" : 0.2, - "step_size" : 0.01, - "show_num_plane" : False, - "step_run_time" : 6, - "num_decimal_places" : 2, - } - -class OneFifthOneFifthWinderWithReset(OneFifthTwoFifthWinder): - CONFIG = { - "wind_reset_indices" : [1] - } - -class OneFifthTwoFifthWinderOdometer(OneFifthTwoFifthWinder): - CONFIG = { - "display_odometer" : True, - } - -class ForwardBackWalker(PiWalker): - CONFIG = { - "func" : example_plane_func, - "walk_coords" : [np.array((-2, 1)), np.array((1, 1))], - "step_run_time" : 3, - } - -class ForwardBackWalkerOdometer(ForwardBackWalker): - CONFIG = { - "display_odometer" : True, - } - -class PureOdometerBackground(OdometerScene): - CONFIG = { - "pure_odometer_background" : True - } - -class CWColorWalk(PiWalkerRect): - CONFIG = { - "func" : example_plane_func, - "start_x" : example_plane_func_spec[0][0] - 1, - "start_y" : example_plane_func_spec[0][1] + 1, - "walk_width" : 2, - "walk_height" : 2, - "draw_lines" : False, - "display_wind" : False, - "step_run_time" : 2 - } - -class CWColorWalkOdometer(CWColorWalk): - CONFIG = { - "display_odometer" : True, - } - -class CCWColorWalk(CWColorWalk): - CONFIG = { - "start_x" : example_plane_func_spec[2][0] - 1, - "start_y" : example_plane_func_spec[2][1] + 1, - } - -class CCWColorWalkOdometer(CCWColorWalk): - CONFIG = { - "display_odometer" : True, - } - -class ThreeTurnWalker(PiWalkerRect): - CONFIG = { - "func" : plane_func_from_complex_func(lambda c: c**3 * complex(1, 1)**3), - "double_up" : True, - "wind_reset_indices" : [4] - } - -class ThreeTurnWalkerOdometer(ThreeTurnWalker): - CONFIG = { - "display_odometer" : True, - } - -class FourTurnWalker(PiWalkerRect): - CONFIG = { - "func" : plane_func_by_wind_spec((0, 0, 4)) - } - -class FourTurnWalkerOdometer(FourTurnWalker): - CONFIG = { - "display_odometer" : True, - } - -class OneTurnWalker(PiWalkerRect): - CONFIG = { - "func" : plane_func_from_complex_func(lambda c : np.exp(c) + c) - } - -class OneTurnWalkerOdometer(OneTurnWalker): - CONFIG = { - "display_odometer" : True, - } - -class ZeroTurnWalker(PiWalkerRect): - CONFIG = { - "func" : plane_func_by_wind_spec((2, 2, 1), (-1, 2, -1)) - } - -class ZeroTurnWalkerOdometer(ZeroTurnWalker): - CONFIG = { - "display_odometer" : True, - } - -class NegOneTurnWalker(PiWalkerRect): - CONFIG = { - "step_run_time" : 2, - "func" : plane_func_by_wind_spec((0, 0, -1)) - } - -class NegOneTurnWalkerOdometer(NegOneTurnWalker): - CONFIG = { - "display_odometer" : True, - } - -# FIN \ No newline at end of file diff --git a/from_3b1b/old/WindingNumber_G.py b/from_3b1b/old/WindingNumber_G.py deleted file mode 100644 index b0fb6201..00000000 --- a/from_3b1b/old/WindingNumber_G.py +++ /dev/null @@ -1,3128 +0,0 @@ -# -*- coding: utf-8 -*- - -from manimlib.imports import * - -from from_3b1b.old.uncertainty import Flash -from from_3b1b.old.WindingNumber import * - - -# Warning, this file uses ContinualChangingDecimal, -# which has since been been deprecated. Use a mobject -# updater instead - - -class AltTeacherStudentsScene(TeacherStudentsScene): - def setup(self): - TeacherStudentsScene.setup(self) - self.teacher.set_color(YELLOW_E) - -############### - - -class IntroSceneWrapper(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs" : { - "color" : YELLOW_E, - "flip_at_start" : False, - "height" : 2, - }, - "default_pi_creature_start_corner" : DOWN+LEFT, - } - def construct(self): - self.introduce_two_words() - self.describe_main_topic() - self.describe_meta_topic() - - def introduce_two_words(self): - morty = self.pi_creature - rect = ScreenRectangle(height = 5) - rect.to_corner(UP+RIGHT) - self.add(rect) - - h_line = Line(LEFT, RIGHT).scale(2) - h_line.to_corner(UP+LEFT) - h_line.shift(0.5*DOWN) - - main_topic, meta_topic = topics = VGroup( - TextMobject("Main topic"), - TextMobject("Meta topic"), - ) - topics.next_to(morty, UP) - topics.shift_onto_screen() - - self.play( - morty.change, "raise_left_hand", - FadeInFromDown(main_topic) - ) - self.wait() - self.play( - morty.change, "raise_right_hand", - main_topic.next_to, meta_topic.get_top(), UP, MED_SMALL_BUFF, - FadeInFromDown(meta_topic) - ) - self.wait() - self.play( - morty.change, "happy", - main_topic.next_to, h_line, UP, - meta_topic.set_fill, {"opacity" : 0.2}, - ) - self.play(ShowCreation(h_line)) - self.wait() - - self.set_variables_as_attrs(h_line, main_topic, meta_topic) - - def describe_main_topic(self): - h_line = self.h_line - morty = self.pi_creature - main_topic = self.main_topic - meta_topic = self.meta_topic - - solver = TextMobject("2d equation solver") - solver.match_width(h_line) - solver.next_to(h_line, DOWN) - rainbow_solver1 = solver.copy() - rainbow_solver2 = solver.copy() - colors = ["RED", "ORANGE", "YELLOW", "GREEN", BLUE, "PURPLE", PINK] - rainbow_solver1.set_color_by_gradient(*colors) - rainbow_solver2.set_color_by_gradient(*reversed(colors)) - - - xy_equation = TexMobject(""" - \\left[\\begin{array}{c} - ye^x \\\\ - \\sin(|xy|) - \\end{array}\\right] = - \\left[\\begin{array}{c} - y^2 \\\\ - 3y - \\end{array}\\right] - """) - # xy_equation.set_color_by_tex_to_color_map({ - # "x" : BLUE, - # "y" : YELLOW - # }) - xy_equation.scale(0.8) - xy_equation.next_to(solver, DOWN, MED_LARGE_BUFF) - - z_equation = TexMobject("z", "^5", "+", "z", "+", "1", "=", "0") - z_equation.set_color_by_tex("z", GREEN) - z_equation.move_to(xy_equation, UP) - - zeta = TexMobject("\\zeta(s) = 0") - zeta[2].set_color(GREEN) - zeta.next_to(z_equation, DOWN, MED_LARGE_BUFF) - - self.play(Write(solver)) - self.play( - LaggedStartMap(FadeIn, xy_equation, run_time = 1), - morty.change, "pondering" - ) - self.wait(2) - self.play( - FadeOut(xy_equation), - FadeIn(z_equation) - ) - self.wait() - self.play(Write(zeta)) - self.wait() - solver.save_state() - for rainbow_solver in rainbow_solver1, rainbow_solver2: - self.play(Transform( - solver, rainbow_solver, - run_time = 2, - lag_ratio = 0.5 - )) - self.play(solver.restore) - self.wait() - - self.play(LaggedStartMap( - FadeOut, VGroup(solver, z_equation, zeta) - )) - self.play( - main_topic.move_to, meta_topic, - main_topic.set_fill, {"opacity" : 0.2}, - meta_topic.move_to, main_topic, - meta_topic.set_fill, {"opacity" : 1}, - morty.change, "hesitant", - path_arc = TAU/8, - ) - - def describe_meta_topic(self): - h_line = self.h_line - morty = self.pi_creature - - words = TextMobject("Seek constructs which \\\\ compose nicely") - words.scale(0.7) - words.next_to(h_line, DOWN) - - self.play(Write(words)) - self.play(morty.change, "happy") - self.wait(3) - -class Introduce1DFunctionCase(Scene): - CONFIG = { - "search_range_rect_height" : 0.15, - "arrow_opacity" : 1, - "show_dotted_line_to_f" : True, - "arrow_config": { - "max_tip_length_to_length_ratio" : 0.5, - }, - "show_midpoint_value" : True, - } - def construct(self): - self.show_axes_one_at_a_time() - self.show_two_graphs() - self.transition_to_sqrt_2_case() - self.show_example_binary_search() - - def show_axes_one_at_a_time(self): - axes = Axes( - x_min = -1, x_max = 3.2, - x_axis_config = { - "unit_size" : 3, - "tick_frequency" : 0.25, - "numbers_with_elongated_ticks" : list(range(-1, 4)) - }, - y_min = -2, y_max = 4.5, - ) - axes.to_corner(DOWN+LEFT) - axes.x_axis.add_numbers(*list(range(-1, 4))) - axes.y_axis.label_direction = LEFT - axes.y_axis.add_numbers(-1, *list(range(1, 5))) - - inputs = TextMobject("Inputs") - inputs.next_to(axes.x_axis, UP, aligned_edge = RIGHT) - - outputs = TextMobject("Outputs") - outputs.next_to(axes.y_axis, UP, SMALL_BUFF) - - self.play( - ShowCreation(axes.x_axis), - Write(inputs) - ) - self.wait() - self.play( - ShowCreation(axes.y_axis), - FadeOut(axes.x_axis.numbers[1], rate_func = squish_rate_func(smooth, 0, 0.2)), - Write(outputs) - ) - self.wait() - - self.axes = axes - self.inputs_label = inputs - self.outputs_label = outputs - - def show_two_graphs(self): - axes = self.axes - f_graph = axes.get_graph( - lambda x : 2*x*(x - 0.75)*(x - 1.5) + 1, - color = BLUE - ) - g_graph = axes.get_graph( - lambda x : 1.8*np.cos(TAU*x/2), - color = YELLOW - ) - - label_x_corod = 2 - f_label = TexMobject("f(x)") - f_label.match_color(f_graph) - f_label.next_to(axes.input_to_graph_point(label_x_corod, f_graph), LEFT) - - g_label = TexMobject("g(x)") - g_label.match_color(g_graph) - g_label.next_to( - axes.input_to_graph_point(label_x_corod, g_graph), UP, SMALL_BUFF - ) - - solution = 0.24 - cross_point = axes.input_to_graph_point(solution, f_graph) - l_v_line, r_v_line, v_line = [ - DashedLine( - axes.coords_to_point(x, 0), - axes.coords_to_point(x, f_graph.underlying_function(solution)), - ) - for x in (axes.x_min, axes.x_max, solution) - ] - - equation = TexMobject("f(x)", "=", "g(x)") - equation[0].match_color(f_label) - equation[2].match_color(g_label) - equation.next_to(cross_point, UP, buff = 1.5, aligned_edge = LEFT) - equation_arrow = Arrow( - equation.get_bottom(), cross_point, - buff = SMALL_BUFF, - color = WHITE - ) - equation.target = TexMobject("x^2", "=", "2") - equation.target.match_style(equation) - equation.target.to_edge(UP) - - for graph, label in (f_graph, f_label), (g_graph, g_label): - self.play( - ShowCreation(graph), - Write(label, rate_func = squish_rate_func(smooth, 0.5, 1)), - run_time = 2 - ) - self.wait() - self.play( - ReplacementTransform(r_v_line.copy().fade(1), v_line), - ReplacementTransform(l_v_line.copy().fade(1), v_line), - run_time = 2 - ) - self.play( - ReplacementTransform(f_label.copy(), equation[0]), - ReplacementTransform(g_label.copy(), equation[2]), - Write(equation[1]), - GrowArrow(equation_arrow), - ) - for x in range(4): - self.play( - FadeOut(v_line.copy()), - ShowCreation(v_line, rate_func = squish_rate_func(smooth, 0.5, 1)), - run_time = 1.5 - ) - self.wait() - self.play( - MoveToTarget(equation, replace_mobject_with_target_in_scene = True), - *list(map(FadeOut, [equation_arrow, v_line])) - ) - - self.set_variables_as_attrs( - f_graph, f_label, g_graph, g_label, - equation = equation.target - ) - - def transition_to_sqrt_2_case(self): - f_graph = self.f_graph - f_label = VGroup(self.f_label) - g_graph = self.g_graph - g_label = VGroup(self.g_label) - axes = self.axes - for label in f_label, g_label: - for x in range(2): - label.add(VectorizedPoint(label.get_center())) - for number in axes.y_axis.numbers: - number.add_background_rectangle() - - squared_graph = axes.get_graph(lambda x : x**2) - squared_graph.match_style(f_graph) - two_graph = axes.get_graph(lambda x : 2) - two_graph.match_style(g_graph) - - squared_label = TexMobject("f(x)", "=", "x^2") - squared_label.next_to( - axes.input_to_graph_point(2, squared_graph), RIGHT - ) - squared_label.match_color(squared_graph) - two_label = TexMobject("g(x)", "=", "2") - two_label.next_to( - axes.input_to_graph_point(3, two_graph), UP, - ) - two_label.match_color(two_graph) - - find_sqrt_2 = self.find_sqrt_2 = TextMobject("(Find $\\sqrt{2}$)") - find_sqrt_2.next_to(self.equation, DOWN) - - self.play( - ReplacementTransform(f_graph, squared_graph), - ReplacementTransform(f_label, squared_label), - ) - self.play( - ReplacementTransform(g_graph, two_graph), - ReplacementTransform(g_label, two_label), - Animation(axes.y_axis.numbers) - ) - self.wait() - self.play(Write(find_sqrt_2)) - self.wait() - - self.set_variables_as_attrs( - squared_graph, two_graph, - squared_label, two_label, - ) - - def show_example_binary_search(self): - self.binary_search( - self.squared_graph, self.two_graph, - x0 = 1, x1 = 2, - n_iterations = 8 - ) - - ## - - def binary_search( - self, - f_graph, g_graph, - x0, x1, - n_iterations, - n_iterations_with_sign_mention = 0, - zoom = False, - ): - - axes = self.axes - rect = self.rect = Rectangle() - rect.set_stroke(width = 0) - rect.set_fill(YELLOW, 0.5) - rect.replace(Line( - axes.coords_to_point(x0, 0), - axes.coords_to_point(x1, 0), - ), dim_to_match = 0) - rect.stretch_to_fit_height(self.search_range_rect_height) - - #Show first left and right - mention_signs = n_iterations_with_sign_mention > 0 - kwargs = {"mention_signs" : mention_signs} - leftovers0 = self.compare_graphs_at_x(f_graph, g_graph, x0, **kwargs) - self.wait() - leftovers1 = self.compare_graphs_at_x(f_graph, g_graph, x1, **kwargs) - self.wait() - self.play(GrowFromCenter(rect)) - self.wait() - - all_leftovers = VGroup(leftovers0, leftovers1) - end_points = [x0, x1] - if mention_signs: - sign_word0 = leftovers0.sign_word - sign_word1 = leftovers1.sign_word - - midpoint_line = Line(MED_SMALL_BUFF*UP, ORIGIN, color = YELLOW) - midpoint_line_update = UpdateFromFunc( - midpoint_line, lambda l : l.move_to(rect) - ) - decimal = DecimalNumber( - 0, - num_decimal_places = 3, - show_ellipsis = True, - ) - decimal.scale(0.7) - decimal_update = ChangingDecimal( - decimal, lambda a : axes.x_axis.point_to_number(rect.get_center()), - position_update_func = lambda m : m.next_to( - midpoint_line, DOWN, SMALL_BUFF, - submobject_to_align = decimal[:-1], - ), - ) - if not self.show_midpoint_value: - decimal.set_fill(opacity = 0) - midpoint_line.set_stroke(width = 0) - - #Restrict to by a half each time - kwargs = { - "mention_signs" : False, - "show_decimal" : zoom, - } - for x in range(n_iterations - 1): - x_mid = np.mean(end_points) - leftovers_mid = self.compare_graphs_at_x(f_graph, g_graph, x_mid, **kwargs) - if leftovers_mid.too_high == all_leftovers[0].too_high: - index_to_fade = 0 - else: - index_to_fade = 1 - edge = [RIGHT, LEFT][index_to_fade] - to_fade = all_leftovers[index_to_fade] - all_leftovers.submobjects[index_to_fade] = leftovers_mid - end_points[index_to_fade] = x_mid - - added_anims = [] - if mention_signs: - word = [leftovers0, leftovers1][index_to_fade].sign_word - if x < n_iterations_with_sign_mention: - added_anims = [word.next_to, leftovers_mid[0].get_end(), -edge] - elif word in self.camera.extract_mobject_family_members(self.mobjects): - added_anims = [FadeOut(word)] - - rect.generate_target() - rect.target.stretch(0.5, 0, about_edge = edge) - rect.target.stretch_to_fit_height(self.search_range_rect_height) - self.play( - MoveToTarget(rect), - midpoint_line_update, - decimal_update, - Animation(all_leftovers), - FadeOut(to_fade), - *added_anims - ) - if zoom: - factor = 2.0/rect.get_width() - everything = VGroup(*self.mobjects) - decimal_index = everything.submobjects.index(decimal) - midpoint_line_index = everything.submobjects.index(midpoint_line) - everything.generate_target() - everything.target.scale(factor, about_point = rect.get_center()) - everything.target[decimal_index].scale(1./factor, about_edge = UP) - everything.target[midpoint_line_index].scale(1./factor) - if factor > 1: - self.play( - everything.scale, factor, - {"about_point" : rect.get_center()} - ) - else: - self.wait() - - def compare_graphs_at_x( - self, f_graph, g_graph, x, - mention_signs = False, - show_decimal = False, - ): - axes = self.axes - f_point = axes.input_to_graph_point(x, f_graph) - g_point = axes.input_to_graph_point(x, g_graph) - arrow = Arrow( - g_point, f_point, buff = 0, - **self.arrow_config - ) - too_high = f_point[1] > g_point[1] - if too_high: - arrow.set_fill(GREEN, opacity = self.arrow_opacity) - else: - arrow.set_fill(RED, opacity = self.arrow_opacity) - - leftovers = VGroup(arrow) - leftovers.too_high = too_high - - if self.show_dotted_line_to_f: - v_line = DashedLine(axes.coords_to_point(x, 0), f_point) - self.play(ShowCreation(v_line)) - leftovers.add(v_line) - - added_anims = [] - if show_decimal: - decimal = DecimalNumber( - axes.x_axis.point_to_number(arrow.get_start()), - num_decimal_places = 3, - # show_ellipsis = True, - ) - height = self.rect.get_height() - decimal.set_height(height) - next_to_kwargs = { - "buff" : height, - } - if too_high: - decimal.next_to(arrow, DOWN, **next_to_kwargs) - if hasattr(self, "last_up_arrow_decimal"): - added_anims += [FadeOut(self.last_up_arrow_decimal)] - self.last_up_arrow_decimal = decimal - else: - decimal.next_to(arrow, UP, **next_to_kwargs) - if hasattr(self, "last_down_arrow_decimal"): - added_anims += [FadeOut(self.last_down_arrow_decimal)] - self.last_down_arrow_decimal = decimal - line = Line(decimal, arrow, buff = 0) - # line.match_color(arrow) - line.set_stroke(WHITE, 1) - decimal.add(line) - added_anims += [FadeIn(decimal)] - - if mention_signs: - if too_high: - sign_word = TextMobject("Positive") - sign_word.set_color(GREEN) - sign_word.scale(0.7) - sign_word.next_to(arrow.get_end(), RIGHT) - else: - sign_word = TextMobject("Negative") - sign_word.set_color(RED) - sign_word.scale(0.7) - sign_word.next_to(arrow.get_end(), LEFT) - sign_word.add_background_rectangle() - added_anims += [FadeIn(sign_word)] - leftovers.sign_word = sign_word - - self.play(GrowArrow(arrow), *added_anims) - - return leftovers - -class PiCreaturesAreIntrigued(AltTeacherStudentsScene): - def construct(self): - self.teacher_says( - "You can extend \\\\ this to 2d", - bubble_kwargs = {"width" : 4, "height" : 3} - ) - self.change_student_modes("pondering", "confused", "erm") - self.look_at(self.screen) - self.wait(3) - -class TransitionFromEquationSolverToZeroFinder(Introduce1DFunctionCase): - CONFIG = { - "show_dotted_line_to_f" : False, - "arrow_config" : {}, - "show_midpoint_value" : False, - } - def construct(self): - #Just run through these without animating. - self.force_skipping() - self.show_axes_one_at_a_time() - self.show_two_graphs() - self.transition_to_sqrt_2_case() - self.revert_to_original_skipping_status() - ## - - self.transition_to_difference_graph() - self.show_binary_search_with_signs() - - def transition_to_difference_graph(self): - axes = self.axes - equation = x_squared, equals, two = self.equation - for s in "-", "0": - tex_mob = TexMobject(s) - tex_mob.scale(0.01) - tex_mob.fade(1) - tex_mob.move_to(equation.get_right()) - equation.add(tex_mob) - find_sqrt_2 = self.find_sqrt_2 - rect = SurroundingRectangle(VGroup(equation, find_sqrt_2)) - rect.set_color(WHITE) - - f_graph = self.squared_graph - g_graph = self.two_graph - new_graph = axes.get_graph( - lambda x : f_graph.underlying_function(x) - g_graph.underlying_function(x), - color = GREEN - ) - zero_graph = axes.get_graph(lambda x : 0) - zero_graph.set_stroke(BLACK, 0) - - f_label = self.squared_label - g_label = self.two_label - new_label = TexMobject("f(x)", "-", "g(x)") - new_label[0].match_color(f_label) - new_label[2].match_color(g_label) - new_label.next_to( - axes.input_to_graph_point(2, new_graph), - LEFT - ) - - fg_labels = VGroup(f_label, g_label) - fg_labels.generate_target() - fg_labels.target.arrange(DOWN, aligned_edge = LEFT) - fg_labels.target.to_corner(UP+RIGHT) - - new_equation = TexMobject("x^2", "-", "2", "=", "0") - new_equation[0].match_style(equation[0]) - new_equation[2].match_style(equation[2]) - new_equation.move_to(equation, RIGHT) - for tex in equation, new_equation: - tex.sort_alphabetically() - - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.play( - ReplacementTransform(equation, new_equation, path_arc = TAU/4), - find_sqrt_2.next_to, new_equation, DOWN, - ) - self.play(MoveToTarget(fg_labels)) - self.play( - ReplacementTransform(f_graph, new_graph), - ReplacementTransform(g_graph, zero_graph), - ) - self.play( - ReplacementTransform(f_label[0].copy(), new_label[0]), - ReplacementTransform(g_label[0].copy(), new_label[2]), - Write(new_label[1]), - ) - self.wait() - - self.set_variables_as_attrs(new_graph, zero_graph) - - def show_binary_search_with_signs(self): - self.play(FadeOut(self.axes.x_axis.numbers[2])) - self.binary_search( - self.new_graph, self.zero_graph, - 1, 2, - n_iterations = 9, - n_iterations_with_sign_mention = 2, - zoom = True, - ) - -class RewriteEquationWithTeacher(AltTeacherStudentsScene): - def construct(self): - root_two_equations = VGroup( - TexMobject("x^2", "", "=", "2", ""), - TexMobject("x^2", "-", "2", "=", "0"), - ) - for equation in root_two_equations: - equation.sort_alphabetically() - for part in equation.get_parts_by_tex("text"): - part[2:-1].set_color(YELLOW) - part[2:-1].scale(0.9) - equation.move_to(self.hold_up_spot, DOWN) - - brace = Brace(root_two_equations[1], UP) - f_equals_0 = brace.get_tex("f(x) = 0") - - self.teacher_holds_up(root_two_equations[0]) - self.wait() - self.play(Transform( - *root_two_equations, - run_time = 1.5, - path_arc = TAU/2 - )) - self.play(self.get_student_changes(*["pondering"]*3)) - self.play( - GrowFromCenter(brace), - self.teacher.change, "happy" - ) - self.play(Write(f_equals_0)) - self.change_student_modes(*["happy"]*3) - self.wait() - - # - to_remove = VGroup(root_two_equations[0], brace, f_equals_0) - two_d_equation = TexMobject(""" - \\left[\\begin{array}{c} - ye^x \\\\ - \\sin(xy) - \\end{array}\\right] = - \\left[\\begin{array}{c} - y^2 + x^3 \\\\ - 3y - x - \\end{array}\\right] - """) - complex_equation = TexMobject("z", "^5 + ", "z", " + 1 = 0") - z_def = TextMobject( - "(", "$z$", " is complex, ", "$a + bi$", ")", - arg_separator = "" - ) - complex_group = VGroup(complex_equation, z_def) - complex_group.arrange(DOWN) - for tex in complex_group: - tex.set_color_by_tex("z", GREEN) - complex_group.move_to(self.hold_up_spot, DOWN) - - self.play( - ApplyMethod( - to_remove.next_to, FRAME_X_RADIUS*RIGHT, RIGHT, - remover = True, - rate_func = running_start, - path_arc = -TAU/4, - ), - self.teacher.change, "hesitant", - self.get_student_changes(*["erm"]*3) - ) - self.teacher_holds_up(two_d_equation) - self.change_all_student_modes("horrified") - self.wait() - self.play( - FadeOut(two_d_equation), - FadeInFromDown(complex_group), - ) - self.change_all_student_modes("confused") - self.wait(3) - -class InputOutputScene(Scene): - CONFIG = { - "plane_width" : 6, - "plane_height" : 6, - "x_shift" : FRAME_X_RADIUS/2, - "y_shift" : MED_LARGE_BUFF, - "output_scalar" : 10, - "non_renormalized_func" : plane_func_by_wind_spec( - (-2, -1, 2), - (1, 1, 1), - (2, -2, -1), - ), - } - - ### - - def func(self, coord_pair): - out_coords = np.array(self.non_renormalized_func(coord_pair)) - out_norm = get_norm(out_coords) - if out_norm > 1: - angle = angle_of_vector(out_coords) - factor = 0.5-0.1*np.cos(4*angle) - target_norm = factor*np.log(out_norm) - out_coords *= target_norm / out_norm - else: - out_coords = (0, 0) - return tuple(out_coords) - - def point_function(self, point): - in_coords = self.input_plane.point_to_coords(point) - out_coords = self.func(in_coords) - return self.output_plane.coords_to_point(*out_coords) - - def get_colorings(self): - in_cmos = ColorMappedObjectsScene( - func = lambda p : self.non_renormalized_func( - (p[0]+self.x_shift, p[1]+self.y_shift) - ) - ) - scalar = self.output_scalar - out_cmos = ColorMappedObjectsScene( - func = lambda p : ( - scalar*(p[0]-self.x_shift), scalar*(p[1]+self.y_shift) - ) - ) - - input_coloring = Rectangle( - height = self.plane_height, - width = self.plane_width, - stroke_width = 0, - fill_color = WHITE, - fill_opacity = 1, - ) - output_coloring = input_coloring.copy() - colorings = VGroup(input_coloring, output_coloring) - vects = [LEFT, RIGHT] - cmos_pair = [in_cmos, out_cmos] - for coloring, vect, cmos in zip(colorings, vects, cmos_pair): - coloring.move_to(self.x_shift*vect + self.y_shift*DOWN) - coloring.color_using_background_image(cmos.background_image_file) - return colorings - - def get_planes(self): - input_plane = self.input_plane = NumberPlane( - x_radius = self.plane_width / 2.0, - y_radius = self.plane_height / 2.0, - ) - output_plane = self.output_plane = input_plane.deepcopy() - planes = VGroup(input_plane, output_plane) - vects = [LEFT, RIGHT] - label_texts = ["Input", "Output"] - label_colors = [GREEN, RED] - for plane, vect, text, color in zip(planes, vects, label_texts, label_colors): - plane.stretch_to_fit_width(self.plane_width) - plane.add_coordinates(x_vals = list(range(-2, 3)), y_vals = list(range(-2, 3))) - plane.white_parts = VGroup(plane.axes, plane.coordinate_labels) - plane.coordinate_labels.set_background_stroke(width=0) - plane.lines_to_fade = VGroup(planes, plane.secondary_lines) - plane.move_to(vect*FRAME_X_RADIUS/2 + self.y_shift*DOWN) - label = TextMobject(text) - label.scale(1.5) - label.add_background_rectangle() - label.move_to(plane) - label.to_edge(UP, buff = MED_SMALL_BUFF) - plane.add(label) - plane.label = label - for submob in plane.get_family(): - if isinstance(submob, TexMobject) and hasattr(submob, "background_rectangle"): - submob.remove(submob.background_rectangle) - - return planes - - def get_v_line(self): - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - v_line.set_stroke(WHITE, 5) - return v_line - - def get_dots(self, input_plane, output_plane): - step = self.dot_density - x_min = -3.0 - x_max = 3.0 - y_min = -3.0 - y_max = 3.0 - dots = VGroup() - for x in np.arange(x_min, x_max + step, step): - for y in np.arange(y_max, y_min - step, -step): - out_coords = self.func((x, y)) - dot = Dot(radius = self.dot_radius) - dot.set_stroke(BLACK, 1) - dot.move_to(input_plane.coords_to_point(x, y)) - dot.original_position = dot.get_center() - dot.generate_target() - dot.target.move_to(output_plane.coords_to_point(*out_coords)) - dot.target_color = rgba_to_color(point_to_rgba( - tuple(self.output_scalar*np.array(out_coords)) - )) - dots.add(dot) - return dots - -class IntroduceInputOutputScene(InputOutputScene): - CONFIG = { - "dot_radius" : 0.05, - "dot_density" : 0.25, - } - def construct(self): - self.setup_planes() - self.map_single_point_to_point() - - def setup_planes(self): - self.input_plane, self.output_plane = self.get_planes() - self.v_line = self.get_v_line() - self.add(self.input_plane, self.output_plane, self.v_line) - - def map_single_point_to_point(self): - input_plane = self.input_plane - output_plane = self.output_plane - - #Dots - dots = self.get_dots() - - in_dot = dots[int(0.55*len(dots))].copy() - out_dot = in_dot.target - for mob in in_dot, out_dot: - mob.scale(1.5) - in_dot.set_color(YELLOW) - out_dot.set_color(PINK) - - input_label_arrow = Vector(DOWN+RIGHT) - input_label_arrow.next_to(in_dot, UP+LEFT, SMALL_BUFF) - input_label = TextMobject("Input point") - input_label.next_to(input_label_arrow.get_start(), UP, SMALL_BUFF) - for mob in input_label, input_label_arrow: - mob.match_color(in_dot) - input_label.add_background_rectangle() - - output_label_arrow = Vector(DOWN+LEFT) - output_label_arrow.next_to(out_dot, UP+RIGHT, SMALL_BUFF) - output_label = TextMobject("Output point") - output_label.next_to(output_label_arrow.get_start(), UP, SMALL_BUFF) - for mob in output_label, output_label_arrow: - mob.match_color(out_dot) - output_label.add_background_rectangle() - - path_arc = -TAU/4 - curved_arrow = Arrow( - in_dot, out_dot, - buff = SMALL_BUFF, - path_arc = path_arc, - color = WHITE, - ) - curved_arrow.pointwise_become_partial(curved_arrow, 0, 0.95) - function_label = TexMobject("f(", "\\text{2d input}", ")") - function_label.next_to(curved_arrow, UP) - function_label.add_background_rectangle() - - - self.play(LaggedStartMap(GrowFromCenter, dots)) - self.play(LaggedStartMap( - MoveToTarget, dots, - path_arc = path_arc - )) - self.wait() - self.play(FadeOut(dots)) - self.play( - GrowFromCenter(in_dot), - GrowArrow(input_label_arrow), - FadeIn(input_label) - ) - self.wait() - self.play( - ShowCreation(curved_arrow), - ReplacementTransform( - in_dot.copy(), out_dot, - path_arc = path_arc - ), - FadeIn(function_label), - ) - self.play( - GrowArrow(output_label_arrow), - FadeIn(output_label) - ) - self.wait() - self.play(*list(map(FadeOut, [ - input_label_arrow, input_label, - output_label_arrow, output_label, - curved_arrow, function_label, - ]))) - - #General movements and wiggles - out_dot_continual_update = self.get_output_dot_continual_update(in_dot, out_dot) - self.add(out_dot_continual_update) - - for vect in UP, RIGHT: - self.play( - in_dot.shift, 0.25*vect, - rate_func = lambda t : wiggle(t, 8), - run_time = 2 - ) - for vect in compass_directions(4, UP+RIGHT): - self.play(Rotating( - in_dot, about_point = in_dot.get_corner(vect), - radians = TAU, - run_time = 1 - )) - self.wait() - for coords in (-2, 2), (-2, -2), (2, -2), (1.5, 1.5): - self.play( - in_dot.move_to, input_plane.coords_to_point(*coords), - path_arc = -TAU/4, - run_time = 2 - ) - self.wait() - - ### - - def get_dots(self): - input_plane = self.input_plane - dots = VGroup() - step = self.dot_density - x_max = input_plane.x_radius - x_min = -x_max - y_max = input_plane.y_radius - y_min = -y_max - - reverse = False - for x in np.arange(x_min+step, x_max, step): - y_range = list(np.arange(x_min+step, x_max, step)) - if reverse: - y_range.reverse() - reverse = not reverse - for y in y_range: - dot = Dot(radius = self.dot_radius) - dot.move_to(input_plane.coords_to_point(x, y)) - dot.generate_target() - dot.target.move_to(self.point_function(dot.get_center())) - dots.add(dot) - return dots - - def get_output_dot_continual_update(self, input_dot, output_dot): - return Mobject.add_updater( - output_dot, - lambda od : od.move_to(self.point_function(input_dot.get_center())) - ) - -class IntroduceVectorField(IntroduceInputOutputScene): - CONFIG = { - "dot_density" : 0.5, - } - def construct(self): - self.setup_planes() - input_plane, output_plane = self.input_plane, self.output_plane - dots = self.get_dots() - - in_dot = dots[0].copy() - in_dot.move_to(input_plane.coords_to_point(1.5, 1.5)) - out_dot = in_dot.copy() - out_dot_continual_update = self.get_output_dot_continual_update(in_dot, out_dot) - for mob in in_dot, out_dot: - mob.scale(1.5) - in_dot.set_color(YELLOW) - out_dot.set_color(PINK) - - out_vector = Arrow( - LEFT, RIGHT, - color = out_dot.get_color(), - ) - out_vector.set_stroke(BLACK, 1) - continual_out_vector_update = Mobject.add_updater( - out_vector, lambda ov : ov.put_start_and_end_on( - output_plane.coords_to_point(0, 0), - out_dot.get_center(), - ) - ) - - in_vector = out_vector.copy() - def update_in_vector(in_vector): - Transform(in_vector, out_vector).update(1) - in_vector.scale(0.5) - in_vector.shift(in_dot.get_center() - in_vector.get_start()) - continual_in_vector_update = Mobject.add_updater( - in_vector, update_in_vector - ) - continual_updates = [ - out_dot_continual_update, - continual_out_vector_update, - continual_in_vector_update - ] - - self.add(in_dot, out_dot) - self.play(GrowArrow(out_vector, run_time = 2)) - self.wait() - self.add_foreground_mobjects(in_dot) - self.play(ReplacementTransform(out_vector.copy(), in_vector)) - self.wait() - self.add(*continual_updates) - path = Square().rotate(-90*DEGREES) - path.replace(Line( - input_plane.coords_to_point(-1.5, -1.5), - input_plane.coords_to_point(1.5, 1.5), - ), stretch = True) - in_vectors = VGroup() - self.add(in_vectors) - for a in np.linspace(0, 1, 25): - self.play( - in_dot.move_to, path.point_from_proportion(a), - run_time = 0.2, - rate_func=linear, - ) - in_vectors.add(in_vector.copy()) - - # Full vector field - newer_in_vectors = VGroup() - self.add(newer_in_vectors) - for dot in dots: - self.play(in_dot.move_to, dot, run_time = 1./15) - newer_in_vectors.add(in_vector.copy()) - self.remove(*continual_updates) - self.remove() - self.play(*list(map(FadeOut, [ - out_dot, out_vector, in_vectors, in_dot, in_vector - ]))) - self.wait() - target_length = 0.4 - for vector in newer_in_vectors: - vector.generate_target() - if vector.get_length() == 0: - continue - factor = target_length / vector.get_length() - vector.target.scale(factor, about_point = vector.get_start()) - - self.play(LaggedStartMap(MoveToTarget, newer_in_vectors)) - self.wait() - -class TwoDScreenInOurThreeDWorld(AltTeacherStudentsScene, ThreeDScene): - def construct(self): - self.ask_about_2d_functions() - self.show_3d() - - def ask_about_2d_functions(self): - in_plane = NumberPlane(x_radius = 2.5, y_radius = 2.5) - in_plane.add_coordinates() - in_plane.set_height(3) - out_plane = in_plane.copy() - - in_text = TextMobject("Input space") - out_text = TextMobject("Output space") - VGroup(in_text, out_text).scale(0.75) - in_text.next_to(in_plane, UP, SMALL_BUFF) - out_text.next_to(out_plane, UP, SMALL_BUFF) - in_plane.add(in_text) - out_plane.add(out_text) - - arrow = Arrow( - LEFT, RIGHT, - path_arc = -TAU/4, - color = WHITE - ) - arrow.pointwise_become_partial(arrow, 0.0, 0.97) - group = VGroup(in_plane, arrow, out_plane) - group.arrange(RIGHT) - arrow.shift(UP) - group.move_to(self.students) - group.to_edge(UP) - - dots = VGroup() - dots_target = VGroup() - for x in np.arange(-2.5, 3.0, 0.5): - for y in np.arange(-2.5, 3.0, 0.5): - dot = Dot(radius = 0.05) - dot.move_to(in_plane.coords_to_point(x, y)) - dot.generate_target() - dot.target.move_to(out_plane.coords_to_point( - x + 0.25*np.cos(5*y), y + 0.25*np.sin(3*x) - )) - dots.add(dot) - dots_target.add(dot.target) - dots.set_color_by_gradient(YELLOW, RED) - dots_target.set_color_by_gradient(YELLOW, RED) - - self.play( - self.teacher.change, "raise_right_hand", - Write(in_plane, run_time = 1) - ) - self.play( - ShowCreation(arrow), - ReplacementTransform( - in_plane.copy(), out_plane, - path_arc = -TAU/4, - ) - ) - self.play( - LaggedStartMap(GrowFromCenter, dots, run_time = 1), - self.get_student_changes(*3*["erm"]), - ) - self.play(LaggedStartMap(MoveToTarget, dots, path_arc = -TAU/4)) - self.wait(3) - - - def show_3d(self): - laptop = Laptop().scale(2) - laptop.rotate(-TAU/12, DOWN) - laptop.rotate(-5*TAU/24, LEFT) - laptop.rotate(TAU/8, LEFT) - laptop.scale(2.3*FRAME_X_RADIUS/laptop.screen_plate.get_width()) - laptop.shift(-laptop.screen_plate.get_center() + 0.1*IN) - should_shade_in_3d(laptop) - - everything = VGroup(laptop, *self.mobjects) - everything.generate_target() - # for mob in everything.target.get_family(): - # if isinstance(mob, PiCreature): - # mob.change_mode("confused") - everything.target.rotate(TAU/12, LEFT) - everything.target.rotate(TAU/16, UP) - everything.target.shift(4*UP) - - self.move_camera( - distance = 12, - run_time = 4, - added_anims = [MoveToTarget(everything, run_time = 4)], - ) - always_rotate(everything, axis=UP, rate=3 * DEGREES) - self.wait(10) - -class EveryOutputPointHasAColor(ColorMappedObjectsScene): - CONFIG = { - "func" : lambda p : p, - "dot_spacing" : 0.1, - "dot_radius" : 0.01, - } - def construct(self): - full_rect = FullScreenRectangle() - full_rect.set_fill(WHITE, 1) - full_rect.set_stroke(WHITE, 0) - full_rect.color_using_background_image(self.background_image_file) - - title = TextMobject("Output Space") - title.scale(1.5) - title.to_edge(UP, buff = MED_SMALL_BUFF) - title.set_stroke(BLACK, 1) - # self.add_foreground_mobjects(title) - - plane = NumberPlane() - plane.fade(0.5) - plane.axes.set_stroke(WHITE, 3) - # plane.add(BackgroundRectangle(title)) - self.add(plane) - - - dots = VGroup() - step = self.dot_spacing - for x in np.arange(-FRAME_X_RADIUS, FRAME_X_RADIUS+step, step): - for y in np.arange(-FRAME_Y_RADIUS, FRAME_Y_RADIUS+step, step): - dot = Dot(color = WHITE) - dot.color_using_background_image(self.background_image_file) - dot.move_to(x*RIGHT + y*UP) - dots.add(dot) - random.shuffle(dots.submobjects) - - m = 3 #exponential factor - n = 1 - dot_groups = VGroup() - while n <= len(dots): - dot_groups.add(dots[n-1:m*n-1]) - n *= m - self.play(LaggedStartMap( - LaggedStartMap, dot_groups, - lambda dg : (GrowFromCenter, dg), - run_time = 8, - lag_ratio = 0.2, - )) - -class DotsHoppingToColor(InputOutputScene): - CONFIG = { - "dot_radius" : 0.05, - "dot_density" : 0.25, - } - def construct(self): - input_coloring, output_coloring = self.get_colorings() - input_plane, output_plane = self.get_planes() - v_line = self.get_v_line() - - dots = self.get_dots(input_plane, output_plane) - - right_half_block = Rectangle( - height = FRAME_HEIGHT, - width = FRAME_X_RADIUS - SMALL_BUFF, - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 0.8, - ) - right_half_block.to_edge(RIGHT, buff = 0) - - #Introduce parts - self.add(input_plane, output_plane, v_line) - self.play( - FadeIn(output_coloring), - Animation(output_plane), - output_plane.white_parts.set_color, BLACK, - output_plane.lines_to_fade.set_stroke, {"width" : 0}, - ) - self.wait() - self.play(LaggedStartMap(GrowFromCenter, dots, run_time = 3)) - self.wait() - - #Hop over and back - self.play(LaggedStartMap( - MoveToTarget, dots, - path_arc = -TAU/4, - run_time = 3, - )) - self.wait() - self.play(LaggedStartMap( - ApplyMethod, dots, - lambda d : (d.set_fill, d.target_color), - )) - self.wait() - self.play(LaggedStartMap( - ApplyMethod, dots, - lambda d : (d.move_to, d.original_position), - path_arc = TAU/4, - run_time = 3, - )) - self.wait() - self.play( - FadeIn(input_coloring), - Animation(input_plane), - input_plane.white_parts.set_color, BLACK, - input_plane.lines_to_fade.set_stroke, {"width" : 0}, - FadeOut(dots), - ) - self.wait() - - #Cover output half - right_half_block.save_state() - right_half_block.next_to(FRAME_X_RADIUS*RIGHT, RIGHT) - self.play(right_half_block.restore) - self.wait() - - # Show yellow points - inspector = DashedLine( - ORIGIN, TAU*UP, - dash_length = TAU/24, - fill_opacity = 0, - stroke_width = 3, - stroke_color = WHITE, - ) - inspector.add(*inspector.copy().set_color(BLACK).shift((TAU/24)*UP)) - inspector.apply_complex_function(np.exp) - inspector.scale(0.15) - - inspector_image = inspector.copy() - def update_inspector_image(inspector_image): - inspector_image.move_to(self.point_function(inspector.get_center())) - - inspector_image_update_anim = UpdateFromFunc( - inspector_image, update_inspector_image - ) - pink_points_label = TextMobject("Pink points") - pink_points_label.scale(0.7) - pink_points_label.set_color(BLACK) - - self.play( - inspector.move_to, input_plane.coords_to_point(-2.75, 2.75), - inspector.set_stroke, {"width" : 2}, - ) - pink_points_label.next_to(inspector, RIGHT) - self.play( - Rotating( - inspector, about_point = inspector.get_center(), - rate_func = smooth, - run_time = 2, - ), - Write(pink_points_label) - ) - self.wait() - self.play(right_half_block.next_to, FRAME_X_RADIUS*RIGHT, RIGHT) - inspector_image_update_anim.update(0) - self.play(ReplacementTransform( - inspector.copy(), inspector_image, - path_arc = -TAU/4, - )) - self.play( - ApplyMethod( - inspector.move_to, - input_plane.coords_to_point(-2, 0), - path_arc = -TAU/8, - run_time = 3, - ), - inspector_image_update_anim - ) - self.play( - ApplyMethod( - inspector.move_to, - input_plane.coords_to_point(-2.75, 2.75), - path_arc = TAU/8, - run_time = 3, - ), - inspector_image_update_anim - ) - self.play(FadeOut(pink_points_label)) - - # Show black zero - zeros = tuple(it.starmap(input_plane.coords_to_point, [ - (-2., -1), (1, 1), (2, -2), - ])) - for x in range(2): - for zero in zeros: - path = ParametricFunction( - bezier([ - inspector.get_center(), - input_plane.coords_to_point(0, 0), - zero - ]), - t_min = 0, t_max = 1 - ) - self.play( - MoveAlongPath(inspector, path, run_time = 2), - inspector_image_update_anim, - ) - self.wait() - self.play(FadeOut(VGroup(inspector, inspector_image))) - - # Show all dots and slowly fade them out - for dot in dots: - dot.scale(1.5) - self.play( - FadeOut(input_coloring), - input_plane.white_parts.set_color, WHITE, - LaggedStartMap(GrowFromCenter, dots) - ) - self.wait() - random.shuffle(dots.submobjects) - self.play(LaggedStartMap( - FadeOut, dots, - lag_ratio = 0.05, - run_time = 10, - )) - - # Ask about whether a region contains a zero - question = TextMobject("Does this region \\\\ contain a zero?") - question.add_background_rectangle(opacity = 1) - question.next_to(input_plane.label, DOWN) - square = Square() - square.match_background_image_file(input_coloring) - square.move_to(input_plane) - - self.play(ShowCreation(square), Write(question)) - self.wait() - quads = [ - (0, 0.5, 6, 6.25), - (1, 1, 0.5, 2), - (-1, -1, 3, 4.5), - (0, 1.25, 5, 1.7), - (-2, -1, 1, 1), - ] - for x, y, width, height in quads: - self.play( - square.stretch_to_fit_width, width, - square.stretch_to_fit_height, height, - square.move_to, input_plane.coords_to_point(x, y) - ) - self.wait() - -class SoWeFoundTheZeros(AltTeacherStudentsScene): - def construct(self): - self.student_says( - "Aha! So we \\\\ found the solutions!", - target_mode = "hooray", - student_index = 2, - bubble_kwargs = {"direction" : LEFT}, - ) - self.wait() - self.teacher_says( - "Er...only \\\\ kind of", - target_mode = "hesitant" - ) - self.wait(3) - -class Rearrange2DEquation(AltTeacherStudentsScene): - def construct(self): - f_tex, g_tex, h_tex = [ - "%s(\\text{2d point})"%char - for char in ("f", "g", "h") - ] - zero_tex = "\\vec{\\textbf{0}}" - equations = VGroup( - TexMobject(g_tex, "", "=", h_tex, ""), - TexMobject(g_tex, "-", h_tex, "=", zero_tex), - ) - equations.move_to(self.hold_up_spot, DOWN) - equations.shift_onto_screen() - - brace = Brace(equations[1], UP) - zero_eq = brace.get_tex("%s = %s"%(f_tex, zero_tex)) - - for equation in equations: - equation.set_color_by_tex(g_tex, BLUE) - equation.set_color_by_tex(h_tex, YELLOW) - equation.sort_alphabetically() - - - self.teacher_holds_up(equations[0]) - self.change_all_student_modes("pondering") - self.play(Transform( - *equations, - run_time = 1.5, - path_arc = TAU/2 - )) - self.play( - Succession( - GrowFromCenter(brace), - Write(zero_eq, run_time = 1) - ), - self.get_student_changes(*["happy"]*3) - ) - self.play(*[ - ApplyMethod(pi.change, "thinking", self.screen) - for pi in self.pi_creatures - ]) - self.wait(3) - -class SearchForZerosInInputSpace(ColorMappedObjectsScene): - CONFIG = { - "func" : example_plane_func, - } - def construct(self): - ColorMappedObjectsScene.construct(self) - title = TextMobject("Input space") - title.scale(2) - title.to_edge(UP) - title.set_stroke(BLACK, 1) - title.add_background_rectangle() - - plane = NumberPlane() - plane.fade(0.5) - plane.axes.set_stroke(WHITE, 3) - - self.add(plane, title) - - looking_glass = Circle() - looking_glass.set_stroke(WHITE, 3) - looking_glass.set_fill(WHITE, 0.6) - looking_glass.color_using_background_image(self.background_image_file) - question = TextMobject("Which points go to 0?") - question.next_to(looking_glass, DOWN) - question.add_background_rectangle() - - mover = VGroup(looking_glass, question) - mover.move_to(4*LEFT + UP) - - self.play(FadeIn(mover)) - points = [4*RIGHT+UP, 2*RIGHT+2*DOWN, 2*LEFT+2*DOWN, 3*RIGHT+2.5*DOWN] - for point in points: - self.play(mover.move_to, point, run_time = 1.5) - self.wait() - -class OneDRegionBoundary(Scene): - CONFIG = { - "graph_color" : BLUE, - "region_rect_height" : 0.1, - } - def construct(self): - x0 = self.x0 = 3 - x1 = self.x1 = 6 - fx0 = self.fx0 = -2 - fx1 = self.fx1 = 2 - - axes = self.axes = Axes( - x_min = -1, x_max = 10, - y_min = -3, y_max = 3, - ) - axes.center() - axes.set_stroke(width = 2) - - input_word = TextMobject("Input") - input_word.next_to(axes.x_axis, UP, SMALL_BUFF, RIGHT) - output_word = TextMobject("Output") - output_word.next_to(axes.y_axis, UP) - axes.add(input_word, output_word) - self.add(axes) - - graph = self.get_graph_part(1, 1) - alt_graphs = [ - self.get_graph_part(*points) - for points in [ - (-1, -2), - (-1, -1, -1), - (1, 1, 1), - (-0.75, 0, 1.75), - (-3, -2, -1), - ] - ] - - #Region and boundary - line = Line(axes.coords_to_point(x0, 0), axes.coords_to_point(x1, 0)) - region = Rectangle( - stroke_width = 0, - fill_color = YELLOW, - fill_opacity = 0.5, - height = self.region_rect_height - ) - region.match_width(line, stretch = True) - region.move_to(line) - - region_words = TextMobject("Input region") - region_words.set_width(0.8*region.get_width()) - region_words.next_to(region, UP) - - x0_arrow, x1_arrow = arrows = VGroup(*[ - Arrow( - axes.coords_to_point(x, 0), - axes.coords_to_point(x, fx), - color = color, - buff = 0 - ) - for x, fx, color in [(x0, fx0, RED), (x1, fx1, GREEN)] - ]) - minus = TexMobject("-") - minus.match_color(x0_arrow) - minus.next_to(x0_arrow, UP) - plus = TexMobject("+") - plus.match_color(x1_arrow) - plus.next_to(x1_arrow, DOWN) - signs = VGroup(plus, minus) - - - self.play( - GrowFromCenter(region), - FadeIn(region_words) - ) - self.wait() - self.play(*it.chain( - list(map(GrowArrow, arrows)), - list(map(Write, signs)) - )) - self.wait() - self.play( - ShowCreation(graph), - FadeOut(region_words), - ) - self.wait() - for alt_graph in alt_graphs + alt_graphs: - self.play(Transform(graph, alt_graph, path_arc = 0.1*TAU)) - self.wait() - - - ### - - def get_graph_part(self, *interim_values): - result = VMobject() - result.set_stroke(self.graph_color, 3) - result.set_fill(opacity = 0) - values = [self.fx0] + list(interim_values) + [self.fx1] - result.set_points_smoothly([ - self.axes.coords_to_point(x, fx) - for x, fx in zip( - np.linspace(self.x0, self.x1, len(values)), - values - ) - ]) - return result - -class DirectionOfA2DFunctionAlongABoundary(InputOutputScene): - def construct(self): - colorings = self.get_colorings() - colorings.set_fill(opacity = 0.25) - input_plane, output_plane = planes = self.get_planes() - for plane in planes: - plane.lines_to_fade.set_stroke(width = 0) - v_line = self.get_v_line() - - rect = Rectangle() - rect.set_stroke(WHITE, 5) - rect.set_fill(WHITE, 0) - line = Line( - input_plane.coords_to_point(-0.75, 2.5), - input_plane.coords_to_point(2.5, -1.5), - ) - rect.replace(line, stretch = True) - rect.insert_n_curves(50) - rect.match_background_image_file(colorings[0]) - - rect_image = rect.copy() - rect_image.match_background_image_file(colorings[1]) - def update_rect_image(rect_image): - rect_image.points = np.array(rect.points) - rect_image.apply_function(self.point_function) - rect_image_update_anim = UpdateFromFunc(rect_image, update_rect_image) - - - def get_input_point(): - return rect.points[-1] - - def get_output_coords(): - in_coords = input_plane.point_to_coords(get_input_point()) - return self.func(in_coords) - - def get_angle(): - return angle_of_vector(get_output_coords()) - - def get_color(): - return rev_to_color(get_angle()/TAU) #Negative? - - - out_vect = Vector(RIGHT, color = WHITE) - out_vect_update_anim = UpdateFromFunc( - out_vect, - lambda ov : ov.put_start_and_end_on( - output_plane.coords_to_point(0, 0), - rect_image.points[-1] - ).set_color(get_color()) - ) - - dot = Dot() - dot.set_stroke(BLACK, 1) - dot_update_anim = UpdateFromFunc( - dot, lambda d : d.move_to(get_input_point()).set_fill(get_color()) - ) - - in_vect = Vector(RIGHT) - def update_in_vect(in_vect): - in_vect.put_start_and_end_on(ORIGIN, 0.5*RIGHT) - in_vect.rotate(get_angle()) - in_vect.set_color(get_color()) - in_vect.shift(get_input_point() - in_vect.get_start()) - return in_vect - in_vect_update_anim = UpdateFromFunc(in_vect, update_in_vect) - - self.add(colorings, planes, v_line) - - self.play( - GrowArrow(out_vect), - GrowArrow(in_vect), - Animation(dot), - ) - self.play( - ShowCreation(rect), - ShowCreation(rect_image), - out_vect_update_anim, - in_vect_update_anim, - dot_update_anim, - rate_func = bezier([0, 0, 1, 1]), - run_time = 10, - ) - -class AskAboutHowToGeneralizeSigns(AltTeacherStudentsScene): - def construct(self): - # 2d plane - plane = NumberPlane(x_radius = 2.5, y_radius = 2.5) - plane.scale(0.8) - plane.to_corner(UP+LEFT) - plane.add_coordinates() - - dot = Dot(color = YELLOW) - label = TextMobject("Sign?") - label.add_background_rectangle() - label.scale(0.5) - label.next_to(dot, UP, SMALL_BUFF) - dot.add(label) - dot.move_to(plane.coords_to_point(1, 1)) - dot.save_state() - dot.fade(1) - dot.center() - - question = TextMobject( - "Wait...what would \\\\ positive and negative \\\\ be in 2d?", - ) - # question.set_color_by_tex_to_color_map({ - # "+" : "green", - # "textminus" : "red" - # }) - - - self.student_says( - question, - target_mode = "sassy", - student_index = 2, - added_anims = [ - self.teacher.change, "plain", - ], - bubble_kwargs = {"direction" : LEFT}, - run_time = 1, - ) - self.play( - Write(plane, run_time = 1), - self.students[0].change, "confused", - self.students[1].change, "confused", - ) - self.play(dot.restore) - for coords in (-1, 1), (1, -1), (0, -2), (-2, 1): - self.wait(0.5) - self.play(dot.move_to, plane.coords_to_point(*coords)) - self.wait() - -class HypothesisAboutFullyColoredBoundary(ColorMappedObjectsScene): - CONFIG = { - "func" : plane_func_from_complex_func(lambda z : z**3), - } - def construct(self): - ColorMappedObjectsScene.construct(self) - square = Square(side_length = 4) - square.color_using_background_image(self.background_image_file) - hypothesis = TextMobject( - "Working Hypothesis: \\\\", - "If a 2d function hits outputs of all possible colors \\\\" + - "on the boundary of a 2d region,", - "that region \\\\ contains a zero.", - alignment = "", - ) - hypothesis[0].next_to(hypothesis[1:], UP) - hypothesis[0].set_color(YELLOW) - s = hypothesis[1].get_tex_string() - s = [c for c in s if c not in string.whitespace] - n = s.index("colors") - hypothesis[1][n:n+len("colors")].set_color_by_gradient( - # RED, GOLD_E, YELLOW, GREEN, BLUE, PINK, - BLUE, PINK, YELLOW, - ) - hypothesis.to_edge(UP) - square.next_to(hypothesis, DOWN, MED_LARGE_BUFF) - - self.add(hypothesis[0]) - self.play( - LaggedStartMap(FadeIn, hypothesis[1]), - ShowCreation(square, run_time = 8) - ) - self.play(LaggedStartMap(FadeIn, hypothesis[2])) - self.play(square.set_fill, {"opacity" : 1}, run_time = 2) - self.wait() - -class PiCreatureAsksWhatWentWrong(PiCreatureScene): - def construct(self): - randy = self.pi_creature - randy.set_color(YELLOW_E) - randy.flip() - randy.to_corner(DOWN+LEFT) - question = TextMobject("What went wrong?") - question.next_to(randy, UP) - question.shift_onto_screen() - question.save_state() - question.shift(DOWN).fade(1) - - self.play(randy.change, "erm") - self.wait(2) - self.play( - Animation(VectorizedPoint(ORIGIN)), - question.restore, - randy.change, "confused", - ) - self.wait(5) - -class ForeverNarrowingLoop(InputOutputScene): - CONFIG = { - "target_coords" : (1, 1), - "input_plane_corner" : UP+RIGHT, - "shrink_time" : 20, - "circle_start_radius" : 2.25, - "start_around_target" : False, - - # Added as a flag to not mess up one clip already used and fine-timed - # but to make it more convenient to do the other TinyLoop edits - "add_convenient_waits" : False - } - def construct(self): - input_coloring, output_coloring = colorings = VGroup(*self.get_colorings()) - input_plane, output_plane = planes = VGroup(*self.get_planes()) - for plane in planes: - plane.white_parts.set_color(BLACK) - plane.lines_to_fade.set_stroke(width = 0) - - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - v_line.set_stroke(WHITE, 5) - - self.add(colorings, v_line, planes) - self.play(*it.chain( - [ - ApplyMethod(coloring.set_fill, {"opacity" : 0.2}) - for coloring in colorings - ], - [ - ApplyMethod(plane.white_parts.set_color, WHITE) - for plane in planes - ] - ), run_time = 2) - - # circle - circle = Circle(color = WHITE, radius = self.circle_start_radius) - circle.flip(axis = RIGHT) - circle.insert_n_curves(50) - if self.start_around_target: - circle.move_to(input_plane.coords_to_point(*self.target_coords)) - else: - circle.next_to( - input_coloring.get_corner(self.input_plane_corner), - -self.input_plane_corner, - SMALL_BUFF - ) - circle.set_stroke(width = 5) - circle_image = circle.copy() - circle.match_background_image_file(input_coloring) - circle_image.match_background_image_file(output_coloring) - - def update_circle_image(circle_image): - circle_image.points = circle.points - circle_image.apply_function(self.point_function) - circle_image.make_smooth() - - circle_image_update_anim = UpdateFromFunc( - circle_image, update_circle_image - ) - - def optional_wait(): - if self.add_convenient_waits: - self.wait() - - optional_wait() - self.play( - ShowCreation(circle), - ShowCreation(circle_image), - run_time = 3, - rate_func = bezier([0, 0, 1, 1]) - ) - optional_wait() - self.play( - circle.scale, 0, - circle.move_to, input_plane.coords_to_point(*self.target_coords), - circle_image_update_anim, - run_time = self.shrink_time, - rate_func = bezier([0, 0, 1, 1]) - ) - -class AltForeverNarrowingLoop(ForeverNarrowingLoop): - CONFIG = { - "target_coords" : (-2, -1), - "input_plane_corner" : DOWN+LEFT, - "shrink_time" : 3, - } - -class TinyLoop(ForeverNarrowingLoop): - CONFIG = { - "circle_start_radius" : 0.5, - "start_around_target" : True, - "shrink_time" : 1, - "add_convenient_waits" : True, - } - -class TinyLoopAroundZero(TinyLoop): - CONFIG = { - "target_coords" : (1, 1), - } - -class TinyLoopAroundBlue(TinyLoop): - CONFIG = { - "target_coords" : (2.4, 0), - } - -class TinyLoopAroundYellow(TinyLoop): - CONFIG = { - "target_coords" : (0, -1.3), - } - -class TinyLoopAroundOrange(TinyLoop): - CONFIG = { - "target_coords" : (0, -0.5), - } - -class TinyLoopAroundRed(TinyLoop): - CONFIG = { - "target_coords" : (-1, 1), - } - -class ConfusedPiCreature(PiCreatureScene): - def construct(self): - morty = self.pi_creature - morty.set_color(YELLOW_E) - morty.flip() - morty.center() - - self.play(morty.change, "awe", DOWN+3*RIGHT) - self.wait(2) - self.play(morty.change, "confused") - self.wait(2) - self.play(morty.change, "pondering") - self.wait(2) - -class FailureOfComposition(ColorMappedObjectsScene): - CONFIG = { - "func" : lambda p : ( - np.cos(TAU*p[1]/3.5), - np.sin(TAU*p[1]/3.5) - ) - } - def construct(self): - ColorMappedObjectsScene.construct(self) - - big_square = Square(side_length = 4) - big_square.move_to(ORIGIN, RIGHT) - small_squares = VGroup(*[ - Square(side_length = 2) for x in range(2) - ]) - small_squares.match_width(big_square, stretch = True) - small_squares.arrange(DOWN, buff = 0) - small_squares.move_to(big_square) - small_squares.space_out_submobjects(1.1) - all_squares = VGroup(big_square, *small_squares) - all_squares.set_stroke(width = 6) - - for square in all_squares: - square.set_color(WHITE) - square.color_using_background_image(self.background_image_file) - - question = TextMobject("Does my border go through every color?") - question.to_edge(UP) - no_answers = VGroup() - yes_answers = VGroup() - for square in all_squares: - if square is big_square: - square.answer = TextMobject("Yes") - square.answer.set_color(GREEN) - yes_answers.add(square.answer) - else: - square.answer = TextMobject("No") - square.answer.set_color(RED) - no_answers.add(square.answer) - square.answer.move_to(square) - - no_answers_in_equation = no_answers.copy() - yes_answers_in_equation = yes_answers.copy() - plus, equals = plus_equals = TexMobject("+=") - equation = VGroup( - no_answers_in_equation[0], plus, - no_answers_in_equation[1], equals, - yes_answers_in_equation - ) - equation.arrange(RIGHT, buff = SMALL_BUFF) - equation.next_to(big_square, RIGHT, MED_LARGE_BUFF) - q_marks = TexMobject("???") - q_marks.next_to(equals, UP) - - - self.add(question) - self.play(LaggedStartMap(ShowCreation, small_squares, lag_ratio = 0.8)) - self.play(LaggedStartMap(Write, no_answers)) - self.wait() - self.play( - small_squares.arrange, DOWN, {"buff" : 0}, - small_squares.move_to, big_square, - no_answers.space_out_submobjects, 0.9, - ) - self.add(big_square) - no_answers_copy = no_answers.copy() - small_squares.save_state() - self.play( - Transform(no_answers, no_answers_in_equation), - Write(plus_equals), - small_squares.set_stroke, {"width" : 0}, - ) - self.play( - Write(yes_answers), - Write(yes_answers_in_equation), - ) - self.play(LaggedStartMap(FadeIn, q_marks, run_time = 1, lag_ratio = 0.8)) - self.wait(2) - self.play( - small_squares.restore, - FadeOut(yes_answers), - FadeIn(no_answers_copy), - ) - self.wait() - self.play( - small_squares.set_stroke, {"width" : 0}, - FadeOut(no_answers_copy), - FadeIn(yes_answers), - ) - self.wait() - - # We can find a better notion of what we want - - cross = Cross(question) - - self.play( - ShowCreation(cross, run_time = 2), - FadeOut(equation), - FadeOut(no_answers), - FadeOut(q_marks), - FadeOut(yes_answers), - ) - - x, plus, y = x_plus_y = TexMobject("x+y") - x_plus_y.move_to(big_square) - x_plus_y.save_state() - x.move_to(no_answers_copy[0]) - y.move_to(no_answers_copy[1]) - plus.fade(1) - - for square, char in zip(small_squares, [x, y]): - ghost = square.copy() - ghost.set_stroke(width = 5) - ghost.background_image_file = None - self.play( - small_squares.restore, - ShowPassingFlash(ghost), - Write(char) - ) - self.wait() - ghost = big_square.copy() - ghost.background_image_file = None - self.play( - small_squares.set_stroke, {"width" : 0}, - x_plus_y.restore, - ) - self.play(ShowPassingFlash(ghost)) - self.wait() - -class PathContainingZero(InputOutputScene, PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs" : { - "flip_at_start" : False, - "height" : 1.5, - }, - "default_pi_creature_start_corner" : DOWN+LEFT, - } - def construct(self): - self.setup_planes() - self.draw_path_hitting_zero() - self.comment_on_zero() - - def setup_planes(self): - colorings = VGroup(*self.get_colorings()) - self.input_coloring, self.output_coloring = colorings - colorings.set_fill(opacity = 0.3) - - planes = VGroup(*self.get_planes()) - self.input_plane, self.output_plane = planes - for plane in planes: - # plane.white_parts.set_color(BLACK) - plane.lines_to_fade.set_stroke(width = 0) - - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - v_line.set_stroke(WHITE, 5) - - self.add(colorings, planes) - self.add(v_line) - - def draw_path_hitting_zero(self): - morty = self.pi_creature - - path = self.path = VMobject( - stroke_width = 5, - stroke_color = WHITE, - fill_opacity = 0, - ) - path.match_background_image_file(self.input_coloring) - path.set_points_smoothly(list(it.starmap( - self.input_plane.coords_to_point, - [(1, 2.5), (2.5, 2.5), (2, 0.5), (1, 1), (0.5, 1), (0.5, 2), (1, 2.5)] - ))) - - out_path = self.out_path = path.copy() - out_path.apply_function(self.point_function) - out_path.match_background_image_file(self.output_coloring) - out_path.make_smooth() - - self.play( - Flash( - VectorizedPoint(self.output_plane.coords_to_point(0, 0)), - color = WHITE, - flash_radius = 0.3, - line_length = 0.2, - num_lines = 13, - rate_func = squish_rate_func(smooth, 0.5, 0.6), - ), - morty.change, "pondering", - *[ - ShowCreation(mob, rate_func = bezier([0, 0, 1, 1])) - for mob in (path, out_path) - ], - run_time = 5 - ) - - def comment_on_zero(self): - morty = self.pi_creature - - words = TextMobject( - "Output is zero \\\\", - "which has no direction" - ) - origin = self.output_plane.coords_to_point(0, 0) - words.to_edge(DOWN, buff = LARGE_BUFF) - background_rect = BackgroundRectangle( - words, buff = SMALL_BUFF, - opacity = 1.0 - ) - background_rect.stretch_to_fit_width(0.1) - - arrow = Arrow(words.get_top(), origin) - - circles = VGroup() - for point in self.input_plane.coords_to_point(1, 1), origin: - circle = Circle(color = BLACK, radius = 0.5, stroke_width = 0) - circle.move_to(point) - circle.generate_target() - circle.target.scale(0) - circle.target.set_stroke(width = 4) - circles.add(circle) - in_circle, out_circle = circles - - new_words = TextMobject( - "But we want $\\vec{\\textbf{x}}$ \\\\", - "where $f(\\vec{\\textbf{x}}) = 0$", - ) - new_words.move_to(words) - - self.play( - FadeIn(background_rect), - Write(words[0]), - GrowArrow(arrow), - ) - self.play( - Write(words[1]), - morty.change, "pleading", - MoveToTarget(out_circle, run_time = 2) - ) - self.wait() - self.play(FadeOut(words)) - self.play( - FadeIn(new_words), - morty.change, "happy" - ) - self.play(MoveToTarget(in_circle, run_time = 2)) - self.play(morty.change, "hooray") - self.wait(3) - -class TransitionFromPathsToBoundaries(ColorMappedObjectsScene): - CONFIG = { - "func" : plane_func_by_wind_spec( - (-2, 0, 2), (2, 0, 1) - ), - "dot_fill_opacity" : 1, - "dot_stroke_width" : 1, - "include_walkers" : True, - "include_question_mark" : True, - } - def construct(self): - ColorMappedObjectsScene.construct(self) - - #Setup paths - squares, joint_rect = self.get_squares_and_joint_rect() - left_square, right_square = squares - - path1, path2 = paths = VGroup(*[ - Line(square.get_corner(UP+LEFT), square.get_corner(UP+RIGHT)) - for square in squares - ]) - joint_path = Line(path1.get_start(), path2.get_end()) - - for mob in it.chain(paths, [joint_path]): - mob.set_stroke(WHITE, 4) - mob.color_using_background_image(self.background_image_file) - - dot = self.get_dot_and_add_continual_animations() - - #Setup path braces - for mob, tex in (path1, "x"), (path2, "y"), (joint_path, "x+y"): - mob.brace = Brace(mob, DOWN) - label = TextMobject("Winding =", "$%s$"%tex) - label.next_to(mob.brace, DOWN) - mob.brace.add(label) - - #Setup region labels - - sum_tex = "x+y" - if self.include_question_mark: - sum_tex += "\\, ?" - for square, tex in (left_square, "x"), (right_square, "y"), (joint_rect, sum_tex): - square.label = TextMobject("Winding = ", "$%s$"%tex) - square.label.move_to(square) - - #Add paths - self.position_dot(path1.get_start()) - for path in path1, path2: - self.position_dot(path.get_start()) - self.play( - MoveAlongPath(dot, path.copy()), - ShowCreation(path), - run_time = 2 - ) - self.play(GrowFromCenter(path.brace)) - self.wait() - self.position_dot(joint_path.get_start()) - self.play( - MoveAlongPath(dot, joint_path, run_time = 3), - FadeOut(VGroup(path1.brace, path2.brace)), - FadeIn(joint_path.brace), - ) - self.wait() - - #Add regions - self.play( - FadeOut(paths), - FadeOut(joint_path.brace), - dot.move_to, path1.get_start() - ) - for square in squares: - self.position_dot(square.points[0]) - kwargs = { - "run_time" : 4, - "rate_func" : bezier([0, 0, 1, 1]), - } - self.play( - MoveAlongPath(dot, square.copy(), **kwargs), - ShowCreation(square, **kwargs), - Write(square.label, run_time = 2), - ) - self.wait() - self.play( - dot.move_to, joint_rect.points[0], - FadeOut(squares), - FadeIn(joint_rect), - ) - self.position_dot(joint_rect.points[0]) - self.play( - Transform(left_square.label[0], joint_rect.label[0]), - Transform( - left_square.label[1], joint_rect.label[1][0], - path_arc = TAU/6 - ), - FadeIn(joint_rect.label[1][1]), - FadeIn(joint_rect.label[1][3:]), - FadeOut(right_square.label[0]), - Transform( - right_square.label[1], joint_rect.label[1][2], - path_arc = TAU/6 - ), - MoveAlongPath( - dot, joint_rect, - run_time = 6, - rate_func = bezier([0, 0, 1, 1]) - ) - ) - self.wait() - - ### - - def get_squares_and_joint_rect(self): - squares = VGroup(*[ - Square(side_length = 4).next_to(ORIGIN, vect, buff = 0) - for vect in (LEFT, RIGHT) - ]) - joint_rect = SurroundingRectangle(squares, buff = 0) - for mob in it.chain(squares, [joint_rect]): - mob.set_stroke(WHITE, 4) - mob.color_using_background_image(self.background_image_file) - return squares, joint_rect - - def get_dot_and_add_continual_animations(self): - #Define important functions for updates - get_output = lambda : self.func(tuple(dot.get_center()[:2])) - get_output_color = lambda : rgba_to_color(point_to_rgba(get_output())) - get_output_rev = lambda : -point_to_rev(get_output()) - self.get_output_rev = get_output_rev - - self.start_rev = 0 - self.curr_winding = 0 - def get_total_winding(dt = 0): - rev = (get_output_rev() - self.start_rev)%1 - possible_windings = [ - np.floor(self.curr_winding)+k+rev - for k in (-1, 0, 1) - ] - i = np.argmin([abs(pw - self.curr_winding) for pw in possible_windings]) - self.curr_winding = possible_windings[i] - return self.curr_winding - - - #Setup dot, arrow and label - dot = self.dot = Dot(radius = 0.1) - dot.set_stroke(WHITE, self.dot_stroke_width) - update_dot_color = Mobject.add_updater( - dot, lambda d : d.set_fill( - get_output_color(), - self.dot_fill_opacity - ) - ) - - label = DecimalNumber(0, num_decimal_places = 1) - label_upadte = ContinualChangingDecimal( - label, get_total_winding, - position_update_func = lambda l : l.next_to(dot, UP+LEFT, SMALL_BUFF) - ) - - arrow_length = 0.75 - arrow = Vector(arrow_length*RIGHT) - arrow.set_stroke(WHITE, self.dot_stroke_width) - def arrow_update_func(arrow): - arrow.set_fill(get_output_color(), 1) - arrow.rotate(-TAU*get_output_rev() - arrow.get_angle()) - arrow.scale(arrow_length/arrow.get_length()) - arrow.shift(dot.get_center() - arrow.get_start()) - return arrow - update_arrow = Mobject.add_updater(arrow, arrow_update_func) - - if self.include_walkers: - self.add(update_arrow, update_dot_color, label_upadte) - return dot - - def position_dot(self, point): - self.dot.move_to(point) - self.start_rev = self.get_output_rev() - self.curr_winding = 0 - -class TransitionFromPathsToBoundariesArrowless(TransitionFromPathsToBoundaries): - CONFIG = { - "func" : plane_func_by_wind_spec( - (-2, 0, 2), (2, 0, 1) - ), - "dot_fill_opacity" : 0, - "dot_stroke_width" : 0, - "include_walkers" : False, - "include_question_mark" : False, - } - -class BreakDownLoopWithNonzeroWinding(TransitionFromPathsToBoundaries): - def construct(self): - TransitionFromPathsToBoundaries.construct(self) - zero_point = 2*LEFT - - squares, joint_rect = self.get_squares_and_joint_rect() - left_square, right_square = squares - VGroup(squares, joint_rect).shift(MED_LARGE_BUFF*DOWN) - - dot = self.get_dot_and_add_continual_animations() - - for rect, tex in (left_square, "x"), (right_square, "y"), (joint_rect, "3"): - rect.label = TextMobject("Winding = ", "$%s$"%tex) - rect.label.move_to(rect) - sum_label = TexMobject("x", "+", "y", "=", "3") - x, plus, y, equals, three = sum_label - sum_label.next_to(joint_rect, UP) - - both_cannot_be_zero = TextMobject("These cannot both be 0") - both_cannot_be_zero.move_to(plus) - both_cannot_be_zero.to_edge(UP) - arrows = VGroup(*[ - Arrow(both_cannot_be_zero.get_bottom(), var.get_top(), buff = SMALL_BUFF) - for var in (x, y) - ]) - - self.position_dot(joint_rect.points[0]) - self.add(joint_rect) - self.play( - MoveAlongPath(dot, joint_rect, rate_func = bezier([0, 0, 1, 1])), - Write(joint_rect.label, rate_func = squish_rate_func(smooth, 0.7, 1)), - run_time = 4 - ) - self.wait() - self.play( - ReplacementTransform(joint_rect.label, left_square.label), - ReplacementTransform(joint_rect.label.copy(), right_square.label), - ReplacementTransform(joint_rect.label[1].copy(), three), - FadeIn(left_square), - FadeIn(right_square), - ) - self.play( - ReplacementTransform(left_square.label[1].copy(), x), - ReplacementTransform(right_square.label[1].copy(), y), - FadeIn(plus), - FadeIn(equals), - ) - self.play( - FadeIn(both_cannot_be_zero), - *list(map(GrowArrow, arrows)) - ) - self.wait() - -class BackToEquationSolving(AltTeacherStudentsScene): - def construct(self): - self.teacher_says( - "Back to solving \\\\ equations" - ) - self.change_all_student_modes("hooray") - self.play(*[ - ApplyMethod(pi.look_at, self.screen) - for pi in self.pi_creatures - ]) - self.wait(3) - -class MonomialTerm(PathContainingZero): - CONFIG = { - "non_renormalized_func" : plane_func_from_complex_func(lambda z : z**5), - "full_func_label" : "f(x) = x^5", - "func_label" : "x^5", - "loop_radius" : 1.1, - "label_buff" : 0.3, - "label_move_to_corner" : ORIGIN, - "should_end_with_rescaling" : True, - } - def construct(self): - self.setup_planes() - self.relabel_planes() - self.add_function_label() - self.show_winding() - if self.should_end_with_rescaling: - self.rescale_output_plane() - - def relabel_planes(self): - for plane in self.input_plane, self.output_plane: - for mob in plane: - if isinstance(mob, TexMobject): - plane.remove(mob) - - if hasattr(plane, "numbers_to_show"): - _range = plane.numbers_to_show - else: - _range = list(range(-2, 3)) - for x in _range: - if x == 0: - continue - label = TexMobject(str(x)) - label.scale(0.5) - point = plane.coords_to_point(x, 0) - label.next_to(point, DOWN, MED_SMALL_BUFF) - plane.add(label) - self.add_foreground_mobject(label) - tick = Line(SMALL_BUFF*DOWN, SMALL_BUFF*UP) - tick.move_to(point) - plane.add(tick) - for y in _range: - if y == 0: - continue - label = TexMobject("%di"%y) - label.scale(0.5) - point = plane.coords_to_point(0, y) - label.next_to(point, LEFT, MED_SMALL_BUFF) - plane.add(label) - self.add_foreground_mobject(label) - tick = Line(SMALL_BUFF*LEFT, SMALL_BUFF*RIGHT) - tick.move_to(point) - plane.add(tick) - self.add(self.input_plane, self.output_plane) - - def add_function_label(self): - label = TexMobject(self.full_func_label) - label.add_background_rectangle(opacity = 1, buff = SMALL_BUFF) - arrow = Arrow( - 2*LEFT, 2*RIGHT, path_arc = -TAU/3, - ) - arrow.pointwise_become_partial(arrow, 0, 0.95) - label.next_to(arrow, UP) - VGroup(arrow, label).to_edge(UP) - self.add(label, arrow) - - def show_winding(self): - loop = Arc(color = WHITE, angle = 1.02*TAU, num_anchors = 42) - loop.scale(self.loop_radius) - loop.match_background_image_file(self.input_coloring) - loop.move_to(self.input_plane.coords_to_point(0, 0)) - - out_loop = loop.copy() - out_loop.apply_function(self.point_function) - out_loop.match_background_image_file(self.output_coloring) - - get_in_point = lambda : loop.points[-1] - get_out_point = lambda : out_loop.points[-1] - in_origin = self.input_plane.coords_to_point(0, 0) - out_origin = self.output_plane.coords_to_point(0, 0) - - dot = Dot() - update_dot = UpdateFromFunc(dot, lambda d : d.move_to(get_in_point())) - - out_dot = Dot() - update_out_dot = UpdateFromFunc(out_dot, lambda d : d.move_to(get_out_point())) - - buff = self.label_buff - def generate_label_update(label, point_func, origin): - return UpdateFromFunc( - label, lambda m : m.move_to( - (1+buff)*point_func() - buff*origin, - self.label_move_to_corner - ) - ) - x = TexMobject("x") - fx = TexMobject(self.func_label) - update_x = generate_label_update(x, get_in_point, in_origin) - update_fx = generate_label_update(fx, get_out_point, out_origin) - - morty = self.pi_creature - - kwargs = { - "run_time" : 15, - "rate_func" : None, - } - self.play( - ShowCreation(loop, **kwargs), - ShowCreation(out_loop, **kwargs), - update_dot, - update_out_dot, - update_x, - update_fx, - ApplyMethod(morty.change, "pondering", out_dot), - ) - self.play( - FadeOut(VGroup(dot, out_dot, x, fx)) - ) - self.loop = loop - self.out_loop = out_loop - - def rescale_output_plane(self): - output_stuff = VGroup(self.output_plane, self.output_coloring) - self.play(*list(map(FadeOut, [self.loop, self.out_loop]))) - self.play( - output_stuff.scale, 3.0/50, run_time = 2 - ) - self.wait() - - ### - - def func(self, coords): - return self.non_renormalized_func(coords) - -class PolynomialTerms(MonomialTerm): - CONFIG = { - "non_renormalized_func" : plane_func_from_complex_func(lambda z : z**5 - z - 1), - "full_func_label" : "f(x) = x^5 - x - 1", - "func_label" : "x^5 + \\cdots", - "loop_radius" : 2.0, - "label_buff" : 0.15, - "label_move_to_corner" : DOWN+LEFT, - "should_end_with_rescaling" : False, - } - def construct(self): - self.pi_creature.change("pondering", VectorizedPoint(ORIGIN)) - MonomialTerm.construct(self) - self.cinch_loop() - # self.sweep_through_loop_interior() - - def relabel_planes(self): - self.output_plane.x_radius = 50 - self.output_plane.y_radius = 50 - self.output_plane.numbers_to_show = list(range(-45, 50, 15)) - MonomialTerm.relabel_planes(self) - - def sweep_through_loop_interior(self): - loop = self.loop - morty = self.pi_creature - - line, line_target = [ - Line( - loop.get_left(), loop.get_right(), - path_arc = u*TAU/2, - n_arc_anchors = 40, - background_image_file = self.input_coloring.background_image_file , - stroke_width = 4, - ) - for u in (-1, 1) - ] - out_line = line.copy() - update_out_line = UpdateFromFunc( - out_line, - lambda m : m.set_points(line.points).apply_function(self.point_function), - ) - - self.play( - Transform( - line, line_target, - run_time = 10, - rate_func = there_and_back - ), - update_out_line, - morty.change, "hooray" - ) - self.wait() - - def cinch_loop(self): - loop = self.loop - out_loop = self.out_loop - morty = self.pi_creature - - update_out_loop = UpdateFromFunc( - out_loop, - lambda m : m.set_points(loop.points).apply_function(self.point_function) - ) - - self.add( - loop.copy().set_stroke(width = 1), - out_loop.copy().set_stroke(width = 1), - ) - self.play( - ApplyMethod( - loop.scale, 0, {"about_point" : self.input_plane.coords_to_point(0.2, 1)}, - run_time = 12, - rate_func = bezier([0, 0, 1, 1]) - ), - update_out_loop, - morty.change, "hooray" - ) - self.wait() - -class SearchSpacePerimeterVsArea(EquationSolver2d): - CONFIG = { - "func" : example_plane_func, - "num_iterations" : 15, - "display_in_parallel" : False, - "use_fancy_lines" : True, - } - def construct(self): - self.force_skipping() - EquationSolver2d.construct(self) - self.revert_to_original_skipping_status() - - all_parts = VGroup(*self.get_mobjects()) - path_parts = VGroup() - non_path_parts = VGroup() - for part in all_parts: - if part.get_background_image_file() is not None: - path_parts.add(part) - else: - non_path_parts.add(part) - path_parts.save_state() - path_parts.generate_target() - for path_target in path_parts.target: - if isinstance(path_target, Line): - path_target.rotate(-path_target.get_angle()) - path_parts.target.arrange(DOWN, buff = MED_SMALL_BUFF) - alt_path_parts = path_parts.copy() - size = lambda m : m.get_height() + m.get_width() - alt_path_parts.submobjects.sort( - key=lambda m1: -size(m1) - ) - - full_rect = SurroundingRectangle( - path_parts, - stroke_width = 0, - fill_color = WHITE, - fill_opacity = 1, - background_image_file = path_parts[0].background_image_file - ) - full_rect.save_state() - full_rect.stretch(0, 1, about_edge = UP) - - self.play( - FadeOut(non_path_parts), - path_parts.set_stroke, {"width" : 1}, - ) - self.remove(all_parts) - for x in range(2): - alt_path_parts.save_state() - self.play(LaggedStartMap( - FadeIn, alt_path_parts, - rate_func = there_and_back, - lag_ratio = 0.3, - run_time = 3, - remover = True - )) - alt_path_parts.restore() - self.play( - full_rect.restore, - run_time = 2, - ) - self.wait() - self.play(FadeOut(full_rect)) - self.wait() - -class ShowPolynomialFinalState(SolveX5MinusXMinus1): - CONFIG = { - "num_iterations" : 15, - } - def construct(self): - self.force_skipping() - SolveX5MinusXMinus1.construct(self) - self.revert_to_original_skipping_status() - -class PiCreatureInAwe(Scene): - def construct(self): - randy = Randolph() - - - self.play(randy.change, "awe") - self.play(Blink(randy)) - self.play(randy.look, UP, run_time = 2) - self.play( - randy.look, RIGHT, - run_time = 4, - rate_func = there_and_back, - path_arc = -TAU/4 - ) - self.wait() - -class ShowComplexFunction(Scene): - def construct(self): - plane = ComplexPlane() - plane.add_coordinates() - four_i = plane.coordinate_labels[-1] - plane.coordinate_labels.remove(four_i) - plane.remove(four_i) - - title = TextMobject("Complex Plane") - title.to_edge(UP, buff = MED_SMALL_BUFF) - rect = BackgroundRectangle(title, fill_opacity = 1, buff = MED_SMALL_BUFF) - - x = complex(1, 0.4) - f = lambda x : x**5 - x - 1 - - x_point = plane.number_to_point(x) - fx_point = plane.number_to_point(f(x)) - - x_dot = Dot(x_point) - fx_dot = Dot(fx_point, color = YELLOW) - arrow = Arrow( - x_point, fx_point, - path_arc = TAU/3, - color = YELLOW - ) - arrow.pointwise_become_partial(arrow, 0, 0.95) - - x_label = TexMobject("x = %d+%.1fi"%(x.real, x.imag)) - x_label.next_to(x_dot, RIGHT) - x_label.add_background_rectangle() - - fx_label = TexMobject("f(x) = x^5 - x - 1") - fx_label.next_to(fx_dot, DOWN, SMALL_BUFF) - fx_label.set_color(YELLOW) - fx_label.add_background_rectangle() - fx_label.generate_target() - fx_label.target.move_to(title) - fx_label.target[1].set_color(WHITE) - - self.play( - Write(plane), - FadeIn(rect), - LaggedStartMap(FadeIn, title) - ) - self.play(*list(map(FadeIn, [x_dot, x_label]))) - self.wait() - self.play( - ReplacementTransform(x_dot.copy(), fx_dot, path_arc = arrow.path_arc), - ShowCreation(arrow, rate_func = squish_rate_func(smooth, 0.2, 1)) - ) - self.play(FadeIn(fx_label)) - self.wait(2) - self.play( - MoveToTarget(fx_label), - *list(map(FadeOut, [title, x_dot, x_label, arrow, fx_dot])) - ) - self.play(FadeOut(plane.coordinate_labels)) - self.wait() - -class WindingNumbersInInputOutputContext(PathContainingZero): - CONFIG = { - "in_loop_center_coords" : (-2, -1), - "run_time" : 10, - } - def construct(self): - self.remove(self.pi_creature) - self.setup_planes() - - in_loop = Circle() - in_loop.flip(RIGHT) - # in_loop = Square(side_length = 2) - in_loop.insert_n_curves(100) - in_loop.move_to(self.input_plane.coords_to_point( - *self.in_loop_center_coords - )) - in_loop.match_background_image_file(self.input_coloring) - - out_loop = in_loop.copy() - out_loop.match_background_image_file(self.output_coloring) - update_out_loop = Mobject.add_updater( - out_loop, - lambda m : m.set_points(in_loop.points).apply_function(self.point_function) - ) - # self.add(update_out_loop) - - in_dot = Dot(radius = 0.04) - update_in_dot = Mobject.add_updater( - in_dot, lambda d : d.move_to(in_loop.point_from_proportion(1)) - ) - self.add(update_in_dot) - - out_arrow = Arrow(LEFT, RIGHT) - update_out_arrow = Mobject.add_updater( - out_arrow, - lambda a : a.put_start_and_end_on( - self.output_plane.coords_to_point(0, 0), - out_loop.point_from_proportion(1) - ) - ) - update_out_arrow_color = Mobject.add_updater( - out_arrow, - lambda a : a.set_color(rev_to_color(a.get_angle()/TAU)) - ) - self.add(update_out_arrow, update_out_arrow_color) - - words = TextMobject( - "How many times does \\\\ the output wind around?" - ) - label = self.output_plane.label - words.move_to(label, UP) - self.output_plane.remove(label) - self.add(words) - - decimal = DecimalNumber(0) - decimal.next_to(self.output_plane.get_corner(UP+RIGHT), DOWN+LEFT) - - - self.play( - ShowCreation(in_loop), - ShowCreation(out_loop), - ChangeDecimalToValue(decimal, 2), - Animation(in_dot), - run_time = self.run_time, - rate_func = bezier([0, 0, 1, 1]) - ) - -class SolveX5SkipToEnd(SolveX5MinusXMinus1): - CONFIG = { - "num_iterations" : 4, - } - def construct(self): - self.force_skipping() - SolveX5MinusXMinus1.construct(self) - self.revert_to_original_skipping_status() - - mobjects = VGroup(*self.get_mobjects()) - lines = VGroup() - rects = VGroup() - for mob in mobjects: - if mob.background_image_file is not None: - mob.set_stroke(width = 2) - lines.add(mob) - elif isinstance(mob, Polygon): - rects.add(mob) - else: - self.remove(mob) - - self.clear() - self.add(lines, rects) - -class ZeroFoundOnBoundary(Scene): - def construct(self): - arrow = Vector(DOWN+LEFT, color = WHITE) - words = TextMobject("Found zero on boundary!") - words.next_to(arrow.get_start(), UP) - words.shift(1.5*RIGHT) - - point = VectorizedPoint() - point.next_to(arrow, DOWN+LEFT) - - self.play(Flash(point)) - self.play( - GrowArrow(arrow), - Write(words), - ) - self.wait() - -class AllOfTheVideos(Scene): - CONFIG = { - "camera_config" : { - "background_opacity" : 1, - } - } - def construct(self): - thumbnail_dir = os.path.join(MEDIA_DIR, "3b1b_videos/Winding/OldThumbnails") - n = 4 - images = Group(*[ - ImageMobject(os.path.join(thumbnail_dir, file)) - for file in os.listdir(thumbnail_dir)[:n**2] - ]) - for image in images: - rect = SurroundingRectangle(image, buff = 0) - rect.set_stroke(WHITE, 1) - image.add(rect) - images.arrange_in_grid(n, n, buff = 0) - images.set_height(FRAME_HEIGHT) - random.shuffle(images.submobjects) - - self.play(LaggedStartMap(FadeIn, images, run_time = 4)) - self.wait() - -class EndingCredits(Scene): - def construct(self): - text = TextMobject( - "Written and animated by: \\\\", - "Sridhar Ramesh \\\\", - "Grant Sanderson" - ) - text[0].shift(MED_SMALL_BUFF*UP) - text.to_edge(UP) - - pi = PiCreature(color = YELLOW_E, height = 2) - pi.to_edge(DOWN) - pi.change_mode("happy") - self.add(pi) - - self.play(LaggedStartMap(FadeIn, text), pi.look_at, text) - self.play(pi.change, "wave_1", text) - self.play(Blink(pi)) - self.play(pi.change, "happy") - self.wait() - -class MentionQAndA(Scene): - def construct(self): - title = TextMobject("Q\\&A with ", "Ben", "and", "Sridhar\\\\", "at", "Patreon") - title.set_color_by_tex_to_color_map({ - "Ben" : MAROON, - "Sridhar" : YELLOW, - }) - patreon_logo = VGroup(*PatreonLogo().family_members_with_points()) - patreon_logo.sort() - patreon_logo.replace(title.get_parts_by_tex("Patreon")) - patreon_logo.scale(1.3, about_edge = LEFT) - patreon_logo.shift(0.5*SMALL_BUFF*DOWN) - title.submobjects[-1] = patreon_logo - - title.to_edge(UP) - self.add(title) - - questions = VGroup(*list(map(TextMobject, [ - "If you think of the current videos as short stories, \\\\ what is the novel that you want to write?", - "How did you get into mathematics?", - "What motivated you to join 3b1b?", - "$\\vdots$", - ]))) - questions.arrange(DOWN, buff = 0.75) - questions.next_to(title, DOWN, LARGE_BUFF) - - self.play(LaggedStartMap(FadeIn, questions, run_time = 3)) - self.wait(2) - self.play(FadeOut(questions)) - self.wait() - -class TickingClock(Scene): - CONFIG = { - "run_time" : 90, - } - def construct(self): - clock = Clock() - clock.set_height(FRAME_HEIGHT - 1) - clock.to_edge(LEFT) - lines = [clock.hour_hand, clock.minute_hand] - def update_line(line): - rev = line.get_angle()/TAU - line.set_color(rev_to_color(rev)) - - for line in lines: - self.add(Mobject.add_updater(line, update_line)) - - run_time = self.run_time - self.play(ClockPassesTime( - clock, - run_time = run_time, - hours_passed = 0.1*run_time - )) - -class InfiniteListOfTopics(Scene): - def construct(self): - rect = Rectangle(width = 5, height = 7) - rect.to_edge(RIGHT) - title = TextMobject("Infinite list \\\\ of topics") - title.next_to(rect.get_top(), DOWN) - lines = VGroup(*[ - TextMobject(words).scale(0.5) - for words in [ - "Winding number", - "Laplace transform", - "Wallis product", - "Quantum information", - "Elliptic curve cryptography", - "Strange attractors", - "Convolutional neural networks", - "Fixed points", - ] - ] + [TexMobject("\\vdots")]) - lines.arrange(DOWN, buff = MED_SMALL_BUFF, aligned_edge = LEFT) - lines.next_to(title, DOWN, MED_LARGE_BUFF) - lines[-1].next_to(lines[-2], DOWN) - - self.add(rect, title) - self.play(LaggedStartMap(FadeIn, lines, run_time = 5)) - self.wait() - -class ManyIterations(Scene): - def construct(self): - words = VGroup(*[ - TextMobject(word, alignment = "") - for word in [ - "Winding numbers, v1", - "Winding numbers, v2 \\\\ (center on domain coloring)", - "Winding numbers, v3 \\\\ (clarify visuals of 2d functions)", - "Winding numbers, v4 \\\\ (postpone topology examples for part 2)", - "Winding numbers, v5 \\\\ (start down wrong path)", - ] - ]) - words.arrange(DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT) - words.scale(0.75) - words.to_edge(RIGHT) - - self.add(words[0]) - for last_word, word in zip(words, words[1:]): - cross = Cross(last_word) - self.play(ShowCreation(cross)) - self.play(FadeIn(word)) - self.wait() - -class MentionFree(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs" : { - "flip_at_start" : False, - }, - "default_pi_creature_start_corner" : DOWN, - } - def construct(self): - morty = self.pi_creature - morty.shift(RIGHT) - - items = VGroup( - TextMobject("Movie:", "$>\\$10.00$"), - TextMobject("College course:", "$>\\$1{,}000.00$"), - TextMobject("YouTube video:", "$=\\$0.00$"), - ) - # items.arrange(DOWN, buff = MED_LARGE_BUFF) - items.next_to(morty, UP, LARGE_BUFF) - right_x = morty.get_right()[0] - for item in items: - item[1].set_color(GREEN) - item.shift((right_x - item[0].get_right()[0])*RIGHT) - - self.play( - morty.change, "raise_right_hand", - FadeInFromDown(items[0]) - ) - self.wait() - self.play( - FadeInFromDown(items[1]), - items[0].shift, UP, - ) - self.wait() - self.play( - items[:2].shift, UP, - FadeInFromDown(items[2]), - morty.change, "surprised" - ) - self.wait(4) - self.play( - morty.change, "raise_left_hand", VectorizedPoint(3*LEFT) - ) - self.wait(4) - self.play(morty.change, "gracious", OUT) - self.wait(4) - - -class EndScreen(PatreonEndScreen, PiCreatureScene): - CONFIG = { - "run_time" : 0, - } - def construct(self): - self.remove(self.pi_creature) - PatreonEndScreen.construct(self) - randy, morty = self.pi_creatures - randy.change("plain") - morty.change("plain") - - for mode in "thinking", "confused", "pondering", "hooray": - self.play(randy.change, mode) - self.wait() - self.play(morty.change, mode) - self.wait(2) - -class Thumbnail(SearchSpacePerimeterVsArea): - CONFIG = { - "num_iterations" : 18, - "func" : plane_func_by_wind_spec( - (-3, -1.3, 2), (0.1, 0.2, 1), (2.8, -2, 1) - ), - } - def construct(self): - self.force_skipping() - EquationSolver2d.construct(self) - self.revert_to_original_skipping_status() - - mobjects = VGroup(*self.get_mobjects()) - lines = VGroup() - rects = VGroup() - get_length = lambda mob : max(mob.get_width(), mob.get_height()) - for mob in mobjects: - if mob.background_image_file is not None: - mob.set_stroke(width = 4*np.sqrt(get_length(mob))) - lines.add(mob) - elif isinstance(mob, Polygon): - rects.add(mob) - else: - self.remove(mob) - - self.clear() - self.add(lines) - diff --git a/from_3b1b/old/alt_calc.py b/from_3b1b/old/alt_calc.py deleted file mode 100644 index c4d6ccc5..00000000 --- a/from_3b1b/old/alt_calc.py +++ /dev/null @@ -1,3774 +0,0 @@ - # -*- coding: utf-8 -*- -from manimlib.imports import * - - -def apply_function_to_center(point_func, mobject): - mobject.apply_function_to_position(point_func) - - -def apply_function_to_submobjects(point_func, mobject): - mobject.apply_function_to_submobject_positions(point_func) - - -def apply_function_to_points(point_func, mobject): - mobject.apply_function(point_func) - - -def get_nested_one_plus_one_over_x(n_terms, bottom_term="x"): - tex = "1+ {1 \\over" * n_terms + bottom_term + "}" * n_terms - return TexMobject(tex, substrings_to_isolate=["1", "\\over", bottom_term]) - - -def get_phi_continued_fraction(n_terms): - return get_nested_one_plus_one_over_x(n_terms, bottom_term="1+\\cdots") - - -def get_nested_f(n_terms, arg="x"): - terms = ["f("] * n_terms + [arg] + [")"] * n_terms - return TexMobject(*terms) - - -# Scene types - -class NumberlineTransformationScene(ZoomedScene): - CONFIG = { - "input_line_zero_point": 0.5 * UP + (FRAME_X_RADIUS - 1) * LEFT, - "output_line_zero_point": 2 * DOWN + (FRAME_X_RADIUS - 1) * LEFT, - "number_line_config": { - "include_numbers": True, - "x_min": -3.5, - "x_max": 3.5, - "unit_size": 2, - }, - # These would override number_line_config - "input_line_config": { - "color": BLUE, - }, - "output_line_config": {}, - "num_inserted_number_line_curves": 20, - "default_delta_x": 0.1, - "default_sample_dot_radius": 0.07, - "default_sample_dot_colors": [RED, YELLOW], - "default_mapping_animation_config": { - "run_time": 3, - # "path_arc": 30 * DEGREES, - }, - "local_coordinate_num_decimal_places": 2, - "zoom_factor": 0.1, - "zoomed_display_height": 2.5, - "zoomed_display_corner_buff": MED_SMALL_BUFF, - "mini_line_scale_factor": 2, - "default_coordinate_value_dx": 0.05, - "zoomed_camera_background_rectangle_fill_opacity": 1.0, - } - - def setup(self): - ZoomedScene.setup(self) - self.setup_number_lines() - self.setup_titles() - self.setup_zoomed_camera_background_rectangle() - - def setup_number_lines(self): - number_lines = self.number_lines = VGroup() - added_configs = (self.input_line_config, self.output_line_config) - zero_opints = (self.input_line_zero_point, self.output_line_zero_point) - for added_config, zero_point in zip(added_configs, zero_opints): - full_config = dict(self.number_line_config) - full_config.update(added_config) - number_line = NumberLine(**full_config) - number_line.insert_n_curves( - self.num_inserted_number_line_curves - ) - number_line.shift(zero_point - number_line.number_to_point(0)) - number_lines.add(number_line) - self.input_line, self.output_line = number_lines - - self.add(number_lines) - - def setup_titles(self): - input_title, output_title = self.titles = VGroup(*[ - TextMobject(word) - for word in ("Inputs", "Outputs") - ]) - vects = [UP, DOWN] - for title, line, vect in zip(self.titles, self.number_lines, vects): - title.next_to(line, vect, aligned_edge=LEFT) - title.shift_onto_screen() - - self.add(self.titles) - - def setup_zoomed_camera_background_rectangle(self): - frame = self.zoomed_camera.frame - frame.next_to(self.camera.frame, UL) - self.zoomed_camera_background_rectangle = BackgroundRectangle( - frame, fill_opacity=self.zoomed_camera_background_rectangle_fill_opacity - ) - self.zoomed_camera_background_rectangle_anim = UpdateFromFunc( - self.zoomed_camera_background_rectangle, - lambda m: m.replace(frame, stretch=True) - ) - self.zoomed_camera_background_rectangle_group = VGroup( - self.zoomed_camera_background_rectangle, - ) - - def get_sample_input_points(self, x_min=None, x_max=None, delta_x=None): - x_min = x_min or self.input_line.x_min - x_max = x_max or self.input_line.x_max - delta_x = delta_x or self.default_delta_x - return [ - self.get_input_point(x) - for x in np.arange(x_min, x_max + delta_x, delta_x) - ] - - def get_sample_dots(self, x_min=None, x_max=None, - delta_x=None, dot_radius=None, colors=None): - dot_radius = dot_radius or self.default_sample_dot_radius - colors = colors or self.default_sample_dot_colors - - dots = VGroup(*[ - Dot(point, radius=dot_radius) - for point in self.get_sample_input_points(x_min, x_max, delta_x) - ]) - dots.set_color_by_gradient(*colors) - return dots - - def get_local_sample_dots(self, x, sample_radius=None, **kwargs): - zoom_factor = self.get_zoom_factor() - delta_x = kwargs.get("delta_x", self.default_delta_x * zoom_factor) - dot_radius = kwargs.get("dot_radius", self.default_sample_dot_radius * zoom_factor) - - if sample_radius is None: - unrounded_radius = self.zoomed_camera.frame.get_width() / 2 - sample_radius = int(unrounded_radius / delta_x) * delta_x - config = { - "x_min": x - sample_radius, - "x_max": x + sample_radius, - "delta_x": delta_x, - "dot_radius": dot_radius, - } - config.update(kwargs) - return self.get_sample_dots(**config) - - def add_sample_dot_ghosts(self, sample_dots, fade_factor=0.5): - self.sample_dot_ghosts = sample_dots.copy() - self.sample_dot_ghosts.fade(fade_factor) - self.add(self.sample_dot_ghosts, sample_dots) - - def get_local_coordinate_values(self, x, dx=None, n_neighbors=1): - dx = dx or self.default_coordinate_value_dx - return [ - x + n * dx - for n in range(-n_neighbors, n_neighbors + 1) - ] - - # Mapping animations - def get_mapping_animation(self, func, mobject, - how_to_apply_func=apply_function_to_center, - **kwargs): - anim_config = dict(self.default_mapping_animation_config) - anim_config.update(kwargs) - - point_func = self.number_func_to_point_func(func) - - mobject.generate_target(use_deepcopy=True) - how_to_apply_func(point_func, mobject.target) - return MoveToTarget(mobject, **anim_config) - - def get_line_mapping_animation(self, func, **kwargs): - input_line_copy = self.input_line.deepcopy() - self.moving_input_line = input_line_copy - input_line_copy.remove(input_line_copy.numbers) - # input_line_copy.set_stroke(width=2) - input_line_copy.insert_n_curves( - self.num_inserted_number_line_curves - ) - return AnimationGroup( - self.get_mapping_animation( - func, input_line_copy, - apply_function_to_points - ), - self.get_mapping_animation( - func, input_line_copy.tick_marks, - apply_function_to_submobjects - ), - ) - - def get_sample_dots_mapping_animation(self, func, dots, **kwargs): - return self.get_mapping_animation( - func, dots, how_to_apply_func=apply_function_to_submobjects - ) - - def get_zoomed_camera_frame_mapping_animation(self, func, x=None, **kwargs): - frame = self.zoomed_camera.frame - if x is None: - point = frame.get_center() - else: - point = self.get_input_point(x) - point_mob = VectorizedPoint(point) - return AnimationGroup( - self.get_mapping_animation(func, point_mob), - UpdateFromFunc(frame, lambda m: m.move_to(point_mob)), - ) - - def apply_function(self, func, - apply_function_to_number_line=True, - sample_dots=None, - local_sample_dots=None, - target_coordinate_values=None, - added_anims=None, - **kwargs - ): - zcbr_group = self.zoomed_camera_background_rectangle_group - zcbr_anim = self.zoomed_camera_background_rectangle_anim - frame = self.zoomed_camera.frame - - anims = [] - if apply_function_to_number_line: - anims.append(self.get_line_mapping_animation(func)) - if hasattr(self, "mini_line"): # Test for if mini_line is in self? - anims.append(self.get_mapping_animation( - func, self.mini_line, - how_to_apply_func=apply_function_to_center - )) - if sample_dots: - anims.append( - self.get_sample_dots_mapping_animation(func, sample_dots) - ) - if self.zoom_activated: - zoom_anim = self.get_zoomed_camera_frame_mapping_animation(func) - anims.append(zoom_anim) - anims.append(zcbr_anim) - - zoom_anim.update(1) - target_mini_line = Line(frame.get_left(), frame.get_right()) - target_mini_line.scale(self.mini_line_scale_factor) - target_mini_line.match_style(self.output_line) - zoom_anim.update(0) - zcbr_group.submobjects.insert(1, target_mini_line) - if target_coordinate_values: - coordinates = self.get_local_coordinates( - self.output_line, - *target_coordinate_values - ) - anims.append(FadeIn(coordinates)) - zcbr_group.add(coordinates) - self.local_target_coordinates = coordinates - if local_sample_dots: - anims.append( - self.get_sample_dots_mapping_animation(func, local_sample_dots) - ) - zcbr_group.add(local_sample_dots) - if added_anims: - anims += added_anims - anims.append(Animation(zcbr_group)) - - self.play(*anims, **kwargs) - - # Zooming - def zoom_in_on_input(self, x, - local_sample_dots=None, - local_coordinate_values=None, - pop_out=True, - first_added_anims=None, - first_anim_kwargs=None, - second_added_anims=None, - second_anim_kwargs=None, - zoom_factor=None, - ): - - first_added_anims = first_added_anims or [] - first_anim_kwargs = first_anim_kwargs or {} - second_added_anims = second_added_anims or [] - second_anim_kwargs = second_anim_kwargs or {} - input_point = self.get_input_point(x) - - # Decide how to move camera frame into place - frame = self.zoomed_camera.frame - frame.generate_target() - frame.target.move_to(input_point) - if zoom_factor: - frame.target.set_height( - self.zoomed_display.get_height() * zoom_factor - ) - movement = MoveToTarget(frame) - zcbr = self.zoomed_camera_background_rectangle - zcbr_group = self.zoomed_camera_background_rectangle_group - zcbr_anim = self.zoomed_camera_background_rectangle_anim - anims = [] - if self.zoom_activated: - anims.append(movement) - anims.append(zcbr_anim) - else: - movement.update(1) - zcbr_anim.update(1) - anims.append(self.get_zoom_in_animation()) - anims.append(FadeIn(zcbr)) - - # Make sure frame is in final place - for anim in anims: - anim.update(1) - - # Add miniature number_line - mini_line = self.mini_line = Line(frame.get_left(), frame.get_right()) - mini_line.scale(self.mini_line_scale_factor) - mini_line.insert_n_curves(self.num_inserted_number_line_curves) - mini_line.match_style(self.input_line) - mini_line_copy = mini_line.copy() - zcbr_group.add(mini_line_copy, mini_line) - anims += [FadeIn(mini_line), FadeIn(mini_line_copy)] - - # Add tiny coordinates - if local_coordinate_values is None: - local_coordinate_values = [x] - local_coordinates = self.get_local_coordinates( - self.input_line, - *local_coordinate_values - ) - anims.append(FadeIn(local_coordinates)) - zcbr_group.add(local_coordinates) - self.local_coordinates = local_coordinates - - # Add tiny dots - if local_sample_dots is not None: - anims.append(LaggedStartMap(GrowFromCenter, local_sample_dots)) - zcbr_group.add(local_sample_dots) - - if first_added_anims: - anims += first_added_anims - - anims.append(Animation(zcbr_group)) - if not pop_out: - self.activate_zooming(animate=False) - self.play(*anims, **first_anim_kwargs) - - if not self.zoom_activated and pop_out: - self.activate_zooming(animate=False) - added_anims = second_added_anims or [] - self.play( - self.get_zoomed_display_pop_out_animation(), - *added_anims, - **second_anim_kwargs - ) - - def get_local_coordinates(self, line, *x_values, **kwargs): - num_decimal_places = kwargs.get( - "num_decimal_places", self.local_coordinate_num_decimal_places - ) - result = VGroup() - result.tick_marks = VGroup() - result.numbers = VGroup() - result.add(result.tick_marks, result.numbers) - for x in x_values: - tick_mark = Line(UP, DOWN) - tick_mark.set_height( - 0.15 * self.zoomed_camera.frame.get_height() - ) - tick_mark.move_to(line.number_to_point(x)) - result.tick_marks.add(tick_mark) - - number = DecimalNumber(x, num_decimal_places=num_decimal_places) - number.scale(self.get_zoom_factor()) - number.scale(0.5) # To make it seem small - number.next_to(tick_mark, DOWN, buff=0.5 * number.get_height()) - result.numbers.add(number) - return result - - def get_mobjects_in_zoomed_camera(self, mobjects): - frame = self.zoomed_camera.frame - x_min = frame.get_left()[0] - x_max = frame.get_right()[0] - y_min = frame.get_bottom()[1] - y_max = frame.get_top()[1] - result = VGroup() - for mob in mobjects: - for point in mob.get_all_points(): - if (x_min < point[0] < x_max) and (y_min < point[1] < y_max): - result.add(mob) - break - return result - - # Helpers - def get_input_point(self, x): - return self.input_line.number_to_point(x) - - def get_output_point(self, fx): - return self.output_line.number_to_point(fx) - - def number_func_to_point_func(self, number_func): - input_line, output_line = self.number_lines - - def point_func(point): - input_number = input_line.point_to_number(point) - output_number = number_func(input_number) - return output_line.number_to_point(output_number) - return point_func - - -class ExampleNumberlineTransformationScene(NumberlineTransformationScene): - CONFIG = { - "number_line_config": { - "x_min": 0, - "x_max": 5, - "unit_size": 2.0 - }, - "output_line_config": { - "x_max": 20, - }, - } - - def construct(self): - func = lambda x: x**2 - x = 3 - dx = 0.05 - - sample_dots = self.get_sample_dots() - local_sample_dots = self.get_local_sample_dots(x) - - self.play(LaggedStartMap(GrowFromCenter, sample_dots)) - self.zoom_in_on_input( - x, - local_sample_dots=local_sample_dots, - local_coordinate_values=[x - dx, x, x + dx], - ) - self.wait() - self.apply_function( - func, - sample_dots=sample_dots, - local_sample_dots=local_sample_dots, - target_coordinate_values=[func(x) - dx, func(x), func(x) + dx], - ) - self.wait() - - -# Scenes - - -class WriteOpeningWords(Scene): - def construct(self): - raw_string1 = "Dear calculus student," - raw_string2 = "You're about to go through your first course. Like " + \ - "any new topic, it will take some hard work to understand," - words1, words2 = [ - TextMobject("\\Large", *rs.split(" ")) - for rs in (raw_string1, raw_string2) - ] - words1.next_to(words2, UP, aligned_edge=LEFT, buff=LARGE_BUFF) - words = VGroup(*it.chain(words1, words2)) - words.set_width(FRAME_WIDTH - 2 * LARGE_BUFF) - words.to_edge(UP) - - letter_wait = 0.05 - word_wait = 2 * letter_wait - comma_wait = 5 * letter_wait - for word in words: - self.play(LaggedStartMap( - FadeIn, word, - run_time=len(word) * letter_wait, - lag_ratio=1.5 / len(word) - )) - self.wait(word_wait) - if word.get_tex_string()[-1] == ",": - self.wait(comma_wait) - - -class StartingCalc101(PiCreatureScene): - CONFIG = { - "camera_config": {"background_opacity": 1}, - "image_frame_width": 3.5, - "image_frame_height": 2.5, - } - - def construct(self): - self.show_you() - self.show_images() - self.show_mystery_topic() - - def show_you(self): - randy = self.pi_creature - title = self.title = Title("Calculus 101") - you = TextMobject("You") - arrow = Vector(DL, color=WHITE) - arrow.next_to(randy, UR) - you.next_to(arrow.get_start(), UP) - - self.play( - Write(you), - GrowArrow(arrow), - randy.change, "erm", title - ) - self.wait() - self.play(Write(title, run_time=1)) - self.play(FadeOut(VGroup(arrow, you))) - - def show_images(self): - randy = self.pi_creature - images = self.get_all_images() - modes = [ - "pondering", # hard_work_image - "pondering", # neat_example_image - "hesitant", # not_so_neat_example_image - "hesitant", # physics_image - "horrified", # piles_of_formulas_image - "horrified", # getting_stuck_image - "thinking", # aha_image - "thinking", # graphical_intuition_image - ] - - for i, image, mode in zip(it.count(), images, modes): - anims = [] - if hasattr(image, "fade_in_anim"): - anims.append(image.fade_in_anim) - anims.append(FadeIn(image.frame)) - else: - anims.append(FadeIn(image)) - - if i >= 3: - image_to_fade_out = images[i - 3] - if hasattr(image_to_fade_out, "fade_out_anim"): - anims.append(image_to_fade_out.fade_out_anim) - else: - anims.append(FadeOut(image_to_fade_out)) - - if hasattr(image, "continual_animations"): - self.add(*image.continual_animations) - - anims.append(ApplyMethod(randy.change, mode)) - self.play(*anims) - self.wait() - if i >= 3: - if hasattr(image_to_fade_out, "continual_animations"): - self.remove(*image_to_fade_out.continual_animations) - self.remove(image_to_fade_out.frame) - self.wait(3) - - self.remaining_images = images[-3:] - - def show_mystery_topic(self): - images = self.remaining_images - randy = self.pi_creature - - mystery_box = Rectangle( - width=self.image_frame_width, - height=self.image_frame_height, - stroke_color=YELLOW, - fill_color=DARK_GREY, - fill_opacity=0.5, - ) - mystery_box.scale(1.5) - mystery_box.next_to(self.title, DOWN, MED_LARGE_BUFF) - - rects = images[-1].rects.copy() - rects.center() - rects.set_height(FRAME_HEIGHT - 1) - # image = rects.get_image() - open_cv_image = cv2.imread(get_full_raster_image_path("alt_calc_hidden_image")) - blurry_iamge = cv2.blur(open_cv_image, (50, 50)) - array = np.array(blurry_iamge)[:, :, ::-1] - im_mob = ImageMobject(array) - im_mob.replace(mystery_box, stretch=True) - mystery_box.add(im_mob) - - q_marks = TexMobject("???").scale(3) - q_marks.space_out_submobjects(1.5) - q_marks.set_stroke(BLACK, 1) - q_marks.move_to(mystery_box) - mystery_box.add(q_marks) - - for image in images: - if hasattr(image, "continual_animations"): - self.remove(*image.continual_animations) - self.play( - image.shift, DOWN, - image.fade, 1, - randy.change, "erm", - run_time=1.5 - ) - self.remove(image) - self.wait() - self.play( - FadeInFromDown(mystery_box), - randy.change, "confused" - ) - self.wait(5) - - # Helpers - - def get_all_images(self): - # Images matched to narration's introductory list - images = VGroup( - self.get_hard_work_image(), - self.get_neat_example_image(), - self.get_not_so_neat_example_image(), - self.get_physics_image(), - self.get_piles_of_formulas_image(), - self.get_getting_stuck_image(), - self.get_aha_image(), - self.get_graphical_intuition_image(), - ) - colors = color_gradient([BLUE, YELLOW], len(images)) - for i, image, color in zip(it.count(), images, colors): - self.adjust_size(image) - frame = Rectangle( - width=self.image_frame_width, - height=self.image_frame_height, - color=color, - stroke_width=2, - ) - frame.move_to(image) - image.frame = frame - image.add(frame) - image.next_to(self.title, DOWN) - alt_i = (i % 3) - 1 - vect = (self.image_frame_width + LARGE_BUFF) * RIGHT - image.shift(alt_i * vect) - return images - - def adjust_size(self, group): - group.set_width(min( - group.get_width(), - self.image_frame_width - 2 * MED_SMALL_BUFF - )) - group.set_height(min( - group.get_height(), - self.image_frame_height - 2 * MED_SMALL_BUFF - )) - return group - - def get_hard_work_image(self): - new_randy = self.pi_creature.copy() - new_randy.change_mode("telepath") - bubble = new_randy.get_bubble(height=3.5, width=4) - bubble.add_content(TexMobject("\\frac{d}{dx}(\\sin(\\sqrt{x}))")) - bubble.add(bubble.content) # Remove? - - return VGroup(new_randy, bubble) - - def get_neat_example_image(self): - filled_circle = Circle( - stroke_width=0, - fill_color=BLUE_E, - fill_opacity=1 - ) - area = TexMobject("\\pi r^2") - area.move_to(filled_circle) - unfilled_circle = Circle( - stroke_width=3, - stroke_color=YELLOW, - fill_opacity=0, - ) - unfilled_circle.next_to(filled_circle, RIGHT) - circles = VGroup(filled_circle, unfilled_circle) - circumference = TexMobject("2\\pi r") - circumference.move_to(unfilled_circle) - equation = TexMobject( - "{d (\\pi r^2) \\over dr} = 2\\pi r", - tex_to_color_map={ - "\\pi r^2": BLUE_D, - "2\\pi r": YELLOW, - } - ) - equation.next_to(circles, UP) - - return VGroup( - filled_circle, area, - unfilled_circle, circumference, - equation - ) - - def get_not_so_neat_example_image(self): - return TexMobject("\\int x \\cos(x) \\, dx") - - def get_physics_image(self): - t_max = 6.5 - r = 0.2 - spring = ParametricFunction( - lambda t: op.add( - r * (np.sin(TAU * t) * RIGHT + np.cos(TAU * t) * UP), - t * DOWN, - ), - t_min=0, t_max=t_max, - color=WHITE, - stroke_width=2, - ) - spring.color_using_background_image("grey_gradient") - - weight = Square() - weight.set_stroke(width=0) - weight.set_fill(opacity=1) - weight.color_using_background_image("grey_gradient") - weight.set_height(0.4) - - t_tracker = ValueTracker(0) - group = VGroup(spring, weight) - group.continual_animations = [ - t_tracker.add_udpater( - lambda tracker, dt: tracker.set_value( - tracker.get_value() + dt - ) - ), - spring.add_updater( - lambda s: s.stretch_to_fit_height( - 1.5 + 0.5 * np.cos(3 * t_tracker.get_value()), - about_edge=UP - ) - ), - weight.add_updater( - lambda w: w.move_to(spring.points[-1]) - ) - ] - - def update_group_style(alpha): - spring.set_stroke(width=2 * alpha) - weight.set_fill(opacity=alpha) - - group.fade_in_anim = UpdateFromAlphaFunc( - group, - lambda g, a: update_group_style(a) - ) - group.fade_out_anim = UpdateFromAlphaFunc( - group, - lambda g, a: update_group_style(1 - a) - ) - return group - - def get_piles_of_formulas_image(self): - return TexMobject("(f/g)' = \\frac{gf' - fg'}{g^2}") - - def get_getting_stuck_image(self): - creature = self.pi_creature.copy() - creature.change_mode("angry") - equation = TexMobject("\\frac{d}{dx}(x^x)") - equation.set_height(creature.get_height() / 2) - equation.next_to(creature, RIGHT, aligned_edge=UP) - creature.look_at(equation) - return VGroup(creature, equation) - - def get_aha_image(self): - creature = self.pi_creature.copy() - creature.change_mode("hooray") - from from_3b1b.old.eoc.chapter3 import NudgeSideLengthOfCube - scene = NudgeSideLengthOfCube( - end_at_animation_number=7, - skip_animations=True - ) - group = VGroup( - scene.cube, scene.faces, - scene.bars, scene.corner_cube, - ) - group.set_height(0.75 * creature.get_height()) - group.next_to(creature, RIGHT) - creature.look_at(group) - return VGroup(creature, group) - - def get_graphical_intuition_image(self): - gs = GraphScene() - gs.setup_axes() - graph = gs.get_graph( - lambda x: 0.2 * (x - 3) * (x - 5) * (x - 6) + 4, - x_min=2, x_max=8, - ) - rects = gs.get_riemann_rectangles( - graph, x_min=2, x_max=8, - stroke_width=0.5, - dx=0.25 - ) - gs.add(graph, rects, gs.axes) - group = VGroup(*gs.mobjects) - self.adjust_size(group) - group.next_to(self.title, DOWN, MED_LARGE_BUFF) - group.rects = rects - group.continual_animations = [ - turn_animation_into_updater(Write(rects)), - turn_animation_into_updater(ShowCreation(graph)), - turn_animation_into_updater(FadeIn(gs.axes)), - ] - self.adjust_size(group) - return group - - -class GraphicalIntuitions(GraphScene): - CONFIG = { - "func": lambda x: 0.1 * (x - 2) * (x - 5) * (x - 7) + 4, - "x_labeled_nums": list(range(1, 10)), - } - - def construct(self): - self.setup_axes() - axes = self.axes - graph = self.get_graph(self.func) - - ss_group = self.get_secant_slope_group( - x=8, graph=graph, dx=0.01, - secant_line_length=6, - secant_line_color=RED, - ) - rects = self.get_riemann_rectangles( - graph, x_min=2, x_max=8, dx=0.01, stroke_width=0 - ) - - deriv_text = TextMobject( - "Derivative $\\rightarrow$ slope", - tex_to_color_map={"slope": ss_group.secant_line.get_color()} - ) - deriv_text.to_edge(UP) - integral_text = TextMobject( - "Integral $\\rightarrow$ area", - tex_to_color_map={"area": rects[0].get_color()} - ) - integral_text.next_to(deriv_text, DOWN) - - self.play( - Succession(Write(axes), ShowCreation(graph, run_time=2)), - self.get_graph_words_anim(), - ) - self.animate_secant_slope_group_change( - ss_group, - target_x=2, - rate_func=smooth, - run_time=2.5, - added_anims=[ - Write(deriv_text), - VFadeIn(ss_group, run_time=2), - ] - ) - self.play(FadeIn(integral_text)) - self.play( - LaggedStartMap( - GrowFromEdge, rects, - lambda r: (r, DOWN) - ), - Animation(axes), - Animation(graph), - ) - self.wait() - - def get_graph_words_anim(self): - words = VGroup( - TextMobject("Graphs,"), - TextMobject("graphs,"), - TextMobject("non-stop graphs"), - TextMobject("all day"), - TextMobject("every day"), - TextMobject("as if to visualize is to graph"), - ) - for word in words: - word.add_background_rectangle() - words.arrange(DOWN) - words.to_edge(UP) - return LaggedStartMap( - FadeIn, words, - rate_func=there_and_back, - run_time=len(words) - 1, - lag_ratio=0.6, - remover=True - ) - - -class Wrapper(Scene): - CONFIG = { - "title": "", - "title_kwargs": {}, - "screen_height": 6, - "wait_time": 2, - } - - def construct(self): - rect = self.rect = ScreenRectangle(height=self.screen_height) - title = self.title = TextMobject(self.title, **self.title_kwargs) - title.to_edge(UP) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait(self.wait_time) - - -class DomainColoringWrapper(Wrapper): - CONFIG = { - "title": "Complex $\\rightarrow$ Complex", - } - - -class R2ToR2Wrapper(Wrapper): - CONFIG = {"title": "$\\mathds{R}^2 \\rightarrow \\mathds{R}^2$"} - - -class ExampleMultivariableFunction(LinearTransformationScene): - CONFIG = { - "show_basis_vectors": False, - "show_coordinates": True, - } - - def construct(self): - def example_function(point): - x, y, z = point - return np.array([ - x + np.sin(y), - y + np.sin(x), - 0 - ]) - self.wait() - self.apply_nonlinear_transformation(example_function, run_time=5) - self.wait() - - -class ChangingVectorFieldWrapper(Wrapper): - CONFIG = {"title": "$(x, y, t) \\rightarrow (x', y')$"} - - -class ChangingVectorField(Scene): - CONFIG = { - "wait_time": 30, - } - - def construct(self): - plane = self.plane = NumberPlane() - plane.set_stroke(width=2) - plane.add_coordinates() - self.add(plane) - - # Obviously a silly thing to do, but I'm sweeping - # through trying to make sure old scenes don't - # completely break in spots which used to have - # Continual animations - time_tracker = self.time_tracker = ValueTracker(0) - time_tracker.add_updater( - lambda t: t.set_value(self.get_time()) - ) - - vectors = self.get_vectors() - vectors.add_updater(self.update_vectors) - self.add(vectors) - self.wait(self.wait_time) - - def get_vectors(self): - vectors = VGroup() - x_max = int(np.ceil(FRAME_WIDTH)) - y_max = int(np.ceil(FRAME_HEIGHT)) - step = 0.5 - for x in np.arange(-x_max, x_max + 1, step): - for y in np.arange(-y_max, y_max + 1, step): - point = x * RIGHT + y * UP - vectors.add(Vector(RIGHT).shift(point)) - vectors.set_color_by_gradient(YELLOW, RED) - return vectors - - def update_vectors(self, vectors): - time = self.time_tracker.get_value() - for vector in vectors: - point = vector.get_start() - out_point = self.func(point, time) - norm = get_norm(out_point) - if norm == 0: - out_point = RIGHT # Fake it - vector.set_fill(opacity=0) - else: - out_point *= 0.5 - color = interpolate_color(BLUE, RED, norm / np.sqrt(8)) - vector.set_fill(color, opacity=1) - vector.set_stroke(BLACK, width=1) - new_x, new_y = out_point[:2] - vector.put_start_and_end_on( - point, point + new_x * RIGHT + new_y * UP - ) - - def func(self, point, time): - x, y, z = point - return np.array([ - np.sin(time + 0.5 * x + y), - np.cos(time + 0.2 * x * y + 0.7), - 0 - ]) - - -class MoreTopics(Scene): - def construct(self): - calculus = TextMobject("Calculus") - calculus.next_to(LEFT, LEFT) - calculus.set_color(YELLOW) - calculus.add_background_rectangle() - others = VGroup( - TextMobject("Multivariable calculus"), - TextMobject("Complex analysis"), - TextMobject("Differential geometry"), - TextMobject("$\\vdots$") - ) - for word in others: - word.add_background_rectangle() - others.arrange( - DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT, - ) - others.next_to(RIGHT, RIGHT) - lines = VGroup(*[ - Line(calculus.get_right(), word.get_left(), buff=MED_SMALL_BUFF) - for word in others - ]) - - rect = FullScreenFadeRectangle(fill_opacity=0.7) - self.add(rect) - - self.add(calculus) - self.play( - LaggedStartMap(ShowCreation, lines), - LaggedStartMap(Write, others), - ) - self.wait() - - self.calculus = calculus - self.lines = lines - self.full_screen_rect = rect - self.other_topics = others - - -class TransformationalViewWrapper(Wrapper): - CONFIG = { - "title": "Transformational view" - } - - -class SetTheStage(TeacherStudentsScene): - def construct(self): - ordinary = TextMobject("Ordinary visual") - transformational = TextMobject("Transformational visual") - for word in ordinary, transformational: - word.move_to(self.hold_up_spot, DOWN) - word.shift_onto_screen() - - self.screen.scale(1.25, about_edge=UL) - self.add(self.screen) - self.teacher_holds_up( - ordinary, - added_anims=[self.get_student_changes(*3 * ["sassy"])] - ) - self.wait() - self.play( - ordinary.shift, UP, - FadeInFromDown(transformational), - self.teacher.change, "hooray", - self.get_student_changes(*3 * ["erm"]) - ) - self.wait(3) - self.change_all_student_modes("pondering", look_at_arg=self.screen) - - -class StandardDerivativeVisual(GraphScene): - CONFIG = { - "y_max": 8, - "y_axis_height": 5, - } - - def construct(self): - self.add_title() - self.show_function_graph() - self.show_slope_of_graph() - self.encourage_not_to_think_of_slope_as_definition() - self.show_sensitivity() - - def add_title(self): - title = self.title = TextMobject("Standard derivative visual") - title.to_edge(UP) - h_line = Line(LEFT, RIGHT) - h_line.set_width(FRAME_WIDTH - 2 * LARGE_BUFF) - h_line.next_to(title, DOWN) - - self.add(title, h_line) - - def show_function_graph(self): - self.setup_axes() - - def func(x): - x -= 5 - return 0.1 * (x + 3) * (x - 3) * x + 3 - graph = self.get_graph(func) - graph_label = self.get_graph_label(graph, x_val=9.5) - - input_tracker = ValueTracker(4) - - def get_x_value(): - return input_tracker.get_value() - - def get_y_value(): - return graph.underlying_function(get_x_value()) - - def get_x_point(): - return self.coords_to_point(get_x_value(), 0) - - def get_y_point(): - return self.coords_to_point(0, get_y_value()) - - def get_graph_point(): - return self.coords_to_point(get_x_value(), get_y_value()) - - def get_v_line(): - return DashedLine(get_x_point(), get_graph_point(), stroke_width=2) - - def get_h_line(): - return DashedLine(get_graph_point(), get_y_point(), stroke_width=2) - - input_triangle = RegularPolygon(n=3, start_angle=TAU / 4) - output_triangle = RegularPolygon(n=3, start_angle=0) - for triangle in input_triangle, output_triangle: - triangle.set_fill(WHITE, 1) - triangle.set_stroke(width=0) - triangle.scale(0.1) - - input_triangle_update = input_tracker.add_updater( - lambda m: m.move_to(get_x_point(), UP) - ) - output_triangle_update = output_triangle.add_updater( - lambda m: m.move_to(get_y_point(), RIGHT) - ) - - x_label = TexMobject("x") - x_label_update = Mobject.add_updater( - x_label, lambda m: m.next_to(input_triangle, DOWN, SMALL_BUFF) - ) - - output_label = TexMobject("f(x)") - output_label_update = Mobject.add_updater( - output_label, lambda m: m.next_to( - output_triangle, LEFT, SMALL_BUFF) - ) - - v_line = get_v_line() - v_line_update = Mobject.add_updater( - v_line, lambda vl: Transform(vl, get_v_line()).update(1) - ) - - h_line = get_h_line() - h_line_update = Mobject.add_updater( - h_line, lambda hl: Transform(hl, get_h_line()).update(1) - ) - - graph_dot = Dot(color=YELLOW) - graph_dot_update = Mobject.add_updater( - graph_dot, lambda m: m.move_to(get_graph_point()) - ) - - self.play( - ShowCreation(graph), - Write(graph_label), - ) - self.play( - DrawBorderThenFill(input_triangle, run_time=1), - Write(x_label), - ShowCreation(v_line), - GrowFromCenter(graph_dot), - ) - self.add_foreground_mobject(graph_dot) - self.play( - ShowCreation(h_line), - Write(output_label), - DrawBorderThenFill(output_triangle, run_time=1) - ) - self.add( - input_triangle_update, - x_label_update, - graph_dot_update, - v_line_update, - h_line_update, - output_triangle_update, - output_label_update, - ) - self.play( - input_tracker.set_value, 8, - run_time=6, - rate_func=there_and_back - ) - - self.input_tracker = input_tracker - self.graph = graph - - def show_slope_of_graph(self): - input_tracker = self.input_tracker - deriv_input_tracker = ValueTracker(input_tracker.get_value()) - - # Slope line - def get_slope_line(): - return self.get_secant_slope_group( - x=deriv_input_tracker.get_value(), - graph=self.graph, - dx=0.01, - secant_line_length=4 - ).secant_line - - slope_line = get_slope_line() - slope_line_update = Mobject.add_updater( - slope_line, lambda sg: Transform(sg, get_slope_line()).update(1) - ) - - def position_deriv_label(deriv_label): - deriv_label.next_to(slope_line, UP) - return deriv_label - deriv_label = TexMobject( - "\\frac{df}{dx}(x) =", "\\text{Slope}", "=" - ) - deriv_label.get_part_by_tex("Slope").match_color(slope_line) - deriv_label_update = Mobject.add_updater( - deriv_label, position_deriv_label - ) - - slope_decimal = DecimalNumber(slope_line.get_slope()) - slope_decimal.match_color(slope_line) - slope_decimal.add_updater( - lambda d: d.set_value(slope_line.get_slope()) - ) - slope_decimal.add_upater( - lambda d: d.next_to( - deriv_label, RIGHT, SMALL_BUFF - ).shift(0.2 * SMALL_BUFF * DOWN) - ) - - self.play( - ShowCreation(slope_line), - Write(deriv_label), - Write(slope_decimal), - run_time=1 - ) - self.wait() - self.add( - slope_line_update, - # deriv_label_update, - ) - for x in 9, 2, 4: - self.play( - input_tracker.set_value, x, - deriv_input_tracker.set_value, x, - run_time=3 - ) - self.wait() - - self.deriv_input_tracker = deriv_input_tracker - - def encourage_not_to_think_of_slope_as_definition(self): - morty = Mortimer(height=2) - morty.to_corner(DR) - - self.play(FadeIn(morty)) - self.play(PiCreatureSays( - morty, "Don't think of \\\\ this as the definition", - bubble_kwargs={"height": 2, "width": 4} - )) - self.play(Blink(morty)) - self.wait() - self.play( - RemovePiCreatureBubble(morty), - UpdateFromAlphaFunc( - morty, lambda m, a: m.set_fill(opacity=1 - a), - remover=True - ) - ) - - def show_sensitivity(self): - input_tracker = self.input_tracker - deriv_input_tracker = self.deriv_input_tracker - - self.wiggle_input() - for x in 9, 7, 2: - self.play( - input_tracker.set_value, x, - deriv_input_tracker.set_value, x, - run_time=3 - ) - self.wiggle_input() - - ### - def wiggle_input(self, dx=0.5, run_time=3): - input_tracker = self.input_tracker - - x = input_tracker.get_value() - x_min = x - dx - x_max = x + dx - y, y_min, y_max = list(map( - self.graph.underlying_function, - [x, x_min, x_max] - )) - x_line = Line( - self.coords_to_point(x_min, 0), - self.coords_to_point(x_max, 0), - ) - y_line = Line( - self.coords_to_point(0, y_min), - self.coords_to_point(0, y_max), - ) - - x_rect, y_rect = rects = VGroup(Rectangle(), Rectangle()) - rects.set_stroke(width=0) - rects.set_fill(YELLOW, 0.5) - x_rect.match_width(x_line) - x_rect.stretch_to_fit_height(0.25) - x_rect.move_to(x_line) - y_rect.match_height(y_line) - y_rect.stretch_to_fit_width(0.25) - y_rect.move_to(y_line) - - self.play( - ApplyMethod( - input_tracker.set_value, input_tracker.get_value() + dx, - rate_func=lambda t: wiggle(t, 6) - ), - FadeIn( - rects, - rate_func=squish_rate_func(smooth, 0, 0.33), - remover=True, - ), - run_time=run_time, - ) - self.play(FadeOut(rects)) - - -class EoCWrapper(Scene): - def construct(self): - title = Title("Essence of calculus") - self.play(Write(title)) - self.wait() - - -class IntroduceTransformationView(NumberlineTransformationScene): - CONFIG = { - "func": lambda x: 0.5 * np.sin(2 * x) + x, - "number_line_config": { - "x_min": 0, - "x_max": 6, - "unit_size": 2.0 - }, - } - - def construct(self): - self.add_title() - self.show_animation_preview() - self.indicate_indicate_point_densities() - self.show_zoomed_transformation() - - def add_title(self): - title = self.title = TextMobject("$f(x)$ as a transformation") - title.to_edge(UP) - self.add(title) - - def show_animation_preview(self): - input_points = self.get_sample_input_points() - output_points = list(map( - self.number_func_to_point_func(self.func), - input_points - )) - sample_dots = self.get_sample_dots() - sample_dot_ghosts = sample_dots.copy().fade(0.5) - arrows = VGroup(*[ - Arrow(ip, op, buff=MED_SMALL_BUFF) - for ip, op in zip(input_points, output_points) - ]) - arrows = arrows[1::3] - arrows.set_stroke(BLACK, 1) - - for sd in sample_dots: - sd.save_state() - sd.scale(2) - sd.fade(1) - self.play(LaggedStartMap( - ApplyMethod, sample_dots, - lambda sd: (sd.restore,), - run_time=2 - )) - self.play(LaggedStartMap( - GrowArrow, arrows, - run_time=6, - lag_ratio=0.3, - )) - self.add(sample_dot_ghosts) - self.apply_function( - self.func, sample_dots=sample_dots, - run_time=3 - ) - self.wait() - self.play(LaggedStartMap(FadeOut, arrows, run_time=1)) - - self.sample_dots = sample_dots - self.sample_dot_ghosts = sample_dot_ghosts - - def indicate_indicate_point_densities(self): - lower_brace = Brace(Line(LEFT, RIGHT), UP) - upper_brace = lower_brace.copy() - input_tracker = ValueTracker(0.5) - dx = 0.5 - - def update_upper_brace(brace): - x = input_tracker.get_value() - line = Line( - self.get_input_point(x), - self.get_input_point(x + dx), - ) - brace.match_width(line, stretch=True) - brace.next_to(line, UP, buff=SMALL_BUFF) - return brace - - def update_lower_brace(brace): - x = input_tracker.get_value() - line = Line( - self.get_output_point(self.func(x)), - self.get_output_point(self.func(x + dx)), - ) - brace.match_width(line, stretch=True) - brace.next_to(line, UP, buff=SMALL_BUFF) - return brace - - lower_brace_anim = UpdateFromFunc(lower_brace, update_lower_brace) - upper_brace_anim = UpdateFromFunc(upper_brace, update_upper_brace) - - new_title = TextMobject( - "$\\frac{df}{dx}(x)$ measures stretch/squishing" - ) - new_title.move_to(self.title, UP) - - stretch_factor = DecimalNumber(0, color=YELLOW) - stretch_factor_anim = ChangingDecimal( - stretch_factor, lambda a: lower_brace.get_width() / upper_brace.get_width(), - position_update_func=lambda m: m.next_to(lower_brace, UP, SMALL_BUFF) - ) - - self.play( - GrowFromCenter(upper_brace), - FadeOut(self.title), - # FadeIn(new_title) - Write(new_title, run_time=2) - ) - self.title = new_title - self.play( - ReplacementTransform(upper_brace.copy(), lower_brace), - GrowFromPoint(stretch_factor, upper_brace.get_center()) - ) - self.play( - input_tracker.set_value, self.input_line.x_max - dx, - lower_brace_anim, - upper_brace_anim, - stretch_factor_anim, - run_time=8, - rate_func=bezier([0, 0, 1, 1]) - ) - self.wait() - - new_sample_dots = self.get_sample_dots() - self.play( - FadeOut(VGroup( - upper_brace, lower_brace, stretch_factor, - self.sample_dots, self.moving_input_line, - )), - FadeIn(new_sample_dots), - ) - self.sample_dots = new_sample_dots - - def show_zoomed_transformation(self): - x = 2.75 - local_sample_dots = self.get_local_sample_dots(x) - - self.zoom_in_on_input( - x, - local_sample_dots=local_sample_dots, - local_coordinate_values=self.get_local_coordinate_values(x), - ) - self.wait() - self.apply_function( - self.func, - sample_dots=self.sample_dots, - local_sample_dots=local_sample_dots, - target_coordinate_values=self.get_local_coordinate_values(self.func(x)) - ) - self.wait() - - -class ExamplePlease(TeacherStudentsScene): - def construct(self): - self.student_says("Example?", student_index=0) - self.teacher_holds_up(TexMobject("f(x) = x^2").scale(1.5)) - self.wait(2) - - -class TalkThroughXSquaredExample(IntroduceTransformationView): - CONFIG = { - "func": lambda x: x**2, - "number_line_config": { - "x_min": 0, - "x_max": 5, - "unit_size": 1.25, - }, - "output_line_config": { - "x_max": 25, - }, - "default_delta_x": 0.2 - } - - def construct(self): - self.add_title() - self.show_specific_points_mapping() - - def add_title(self): - title = self.title = TextMobject("$f(x) = x^2$") - title.to_edge(UP, buff=MED_SMALL_BUFF) - self.add(title) - - def show_specific_points_mapping(self): - # First, just show integers as examples - int_dots = self.get_sample_dots(1, 6, 1) - int_dot_ghosts = int_dots.copy().fade(0.5) - int_arrows = VGroup(*[ - Arrow( - # num.get_bottom(), - self.get_input_point(x), - self.get_output_point(self.func(x)), - buff=MED_SMALL_BUFF - ) - for x, num in zip(list(range(1, 6)), self.input_line.numbers[1:]) - ]) - point_func = self.number_func_to_point_func(self.func) - - numbers = self.input_line.numbers - numbers.next_to(self.input_line, UP, SMALL_BUFF) - self.titles[0].next_to(numbers, UP, MED_SMALL_BUFF, LEFT) - # map(TexMobject.add_background_rectangle, numbers) - # self.add_foreground_mobject(numbers) - - for dot, dot_ghost, arrow in zip(int_dots, int_dot_ghosts, int_arrows): - arrow.match_color(dot) - self.play(DrawBorderThenFill(dot, run_time=1)) - self.add(dot_ghost) - self.play( - GrowArrow(arrow), - dot.apply_function_to_position, point_func - ) - self.wait() - - # Show more sample_dots - sample_dots = self.get_sample_dots() - sample_dot_ghosts = sample_dots.copy().fade(0.5) - - self.play( - LaggedStartMap(DrawBorderThenFill, sample_dots), - LaggedStartMap(FadeOut, int_arrows), - ) - self.remove(int_dot_ghosts) - self.add(sample_dot_ghosts) - self.apply_function(self.func, sample_dots=sample_dots) - self.remove(int_dots) - self.wait() - - self.sample_dots = sample_dots - self.sample_dot_ghosts = sample_dot_ghosts - - def get_stretch_words(self, factor, color=RED, less_than_one=False): - factor_str = "$%s$" % str(factor) - result = TextMobject( - "Scale \\\\ by", factor_str, - tex_to_color_map={factor_str: color} - ) - result.scale(0.7) - la, ra = TexMobject("\\leftarrow \\rightarrow") - if less_than_one: - la, ra = ra, la - if factor < 0: - kwargs = { - "path_arc": np.pi, - } - la = Arrow(UP, DOWN, **kwargs) - ra = Arrow(DOWN, UP, **kwargs) - for arrow in la, ra: - arrow.pointwise_become_partial(arrow, 0, 0.9) - arrow.tip.scale(2) - VGroup(la, ra).match_height(result) - la.next_to(result, LEFT) - ra.next_to(result, RIGHT) - result.add(la, ra) - result.next_to( - self.zoomed_display.get_top(), DOWN, SMALL_BUFF - ) - return result - - def get_deriv_equation(self, x, rhs, color=RED): - deriv_equation = self.deriv_equation = TexMobject( - "\\frac{df}{dx}(", str(x), ")", "=", str(rhs), - tex_to_color_map={str(x): color, str(rhs): color} - ) - deriv_equation.next_to(self.title, DOWN, MED_LARGE_BUFF) - return deriv_equation - - -class ZoomInOnXSquaredNearOne(TalkThroughXSquaredExample): - def setup(self): - TalkThroughXSquaredExample.setup(self) - self.force_skipping() - self.add_title() - self.show_specific_points_mapping() - self.revert_to_original_skipping_status() - - def construct(self): - zoom_words = TextMobject("Zoomed view \\\\ near 1") - zoom_words.next_to(self.zoomed_display, DOWN) - # zoom_words.shift_onto_screen() - - x = 1 - local_sample_dots = self.get_local_sample_dots(x) - local_coords = self.get_local_coordinate_values(x, dx=0.1) - - zcbr_anim = self.zoomed_camera_background_rectangle_anim - zcbr_group = self.zoomed_camera_background_rectangle_group - frame = self.zoomed_camera.frame - sample_dot_ghost_copies = self.sample_dot_ghosts.copy() - - self.zoom_in_on_input(x, local_sample_dots, local_coords) - self.play(FadeIn(zoom_words)) - self.wait() - local_sample_dots.save_state() - frame.save_state() - self.mini_line.save_state() - self.apply_function( - self.func, - apply_function_to_number_line=False, - sample_dots=sample_dot_ghost_copies, - local_sample_dots=local_sample_dots, - target_coordinate_values=local_coords - ) - self.remove(sample_dot_ghost_copies) - self.wait() - - # Go back - self.play( - frame.restore, - self.mini_line.restore, - local_sample_dots.restore, - zcbr_anim, - Animation(zcbr_group) - ) - self.wait() - - # Zoom in even more - extra_zoom_factor = 0.3 - one_group = VGroup( - self.local_coordinates.tick_marks[1], - self.local_coordinates.numbers[1], - ) - all_other_coordinates = VGroup( - self.local_coordinates.tick_marks[::2], - self.local_coordinates.numbers[::2], - self.local_target_coordinates, - ) - self.play(frame.scale, extra_zoom_factor) - new_local_sample_dots = self.get_local_sample_dots(x, delta_x=0.005) - new_coordinate_values = self.get_local_coordinate_values(x, dx=0.02) - new_local_coordinates = self.get_local_coordinates( - self.input_line, *new_coordinate_values - ) - - self.play( - Write(new_local_coordinates), - Write(new_local_sample_dots), - one_group.scale, extra_zoom_factor, {"about_point": self.get_input_point(1)}, - FadeOut(all_other_coordinates), - *[ - ApplyMethod(dot.scale, extra_zoom_factor) - for dot in local_sample_dots - ] - ) - self.remove(one_group, local_sample_dots) - zcbr_group.remove( - self.local_coordinates, self.local_target_coordinates, - local_sample_dots - ) - - # Transform new zoomed view - stretch_by_two_words = self.get_stretch_words(2) - self.add_foreground_mobject(stretch_by_two_words) - sample_dot_ghost_copies = self.sample_dot_ghosts.copy() - self.apply_function( - self.func, - apply_function_to_number_line=False, - sample_dots=sample_dot_ghost_copies, - local_sample_dots=new_local_sample_dots, - target_coordinate_values=new_coordinate_values, - added_anims=[FadeIn(stretch_by_two_words)] - ) - self.remove(sample_dot_ghost_copies) - self.wait() - - # Write derivative - deriv_equation = self.get_deriv_equation(1, 2, color=RED) - self.play(Write(deriv_equation)) - self.wait() - - -class ZoomInOnXSquaredNearThree(ZoomInOnXSquaredNearOne): - CONFIG = { - "zoomed_display_width": 4, - } - - def construct(self): - zoom_words = TextMobject("Zoomed view \\\\ near 3") - zoom_words.next_to(self.zoomed_display, DOWN) - - x = 3 - local_sample_dots = self.get_local_sample_dots(x) - local_coordinate_values = self.get_local_coordinate_values(x, dx=0.1) - target_coordinate_values = self.get_local_coordinate_values(self.func(x), dx=0.1) - - color = self.sample_dots[len(self.sample_dots) / 2].get_color() - sample_dot_ghost_copies = self.sample_dot_ghosts.copy() - stretch_words = self.get_stretch_words(2 * x, color) - deriv_equation = self.get_deriv_equation(x, 2 * x, color) - - self.add(deriv_equation) - self.zoom_in_on_input( - x, - pop_out=False, - local_sample_dots=local_sample_dots, - local_coordinate_values=local_coordinate_values - ) - self.play(Write(zoom_words, run_time=1)) - self.wait() - self.add_foreground_mobject(stretch_words) - self.apply_function( - self.func, - apply_function_to_number_line=False, - sample_dots=sample_dot_ghost_copies, - local_sample_dots=local_sample_dots, - target_coordinate_values=target_coordinate_values, - added_anims=[Write(stretch_words)] - ) - self.wait(2) - - -class ZoomInOnXSquaredNearOneFourth(ZoomInOnXSquaredNearOne): - CONFIG = { - "zoom_factor": 0.01, - "local_coordinate_num_decimal_places": 4, - "zoomed_display_width": 4, - "default_delta_x": 0.25, - } - - def construct(self): - # Much copy-pasting from previous scenes. Not great, but - # the fastest way to get the ease-of-tweaking I'd like. - zoom_words = TextMobject("Zoomed view \\\\ near $1/4$") - zoom_words.next_to(self.zoomed_display, DOWN) - - x = 0.25 - local_sample_dots = self.get_local_sample_dots( - x, sample_radius=2.5 * self.zoomed_camera.frame.get_width(), - ) - local_coordinate_values = self.get_local_coordinate_values( - x, dx=0.01, - ) - target_coordinate_values = self.get_local_coordinate_values( - self.func(x), dx=0.01, - ) - - color = RED - sample_dot_ghost_copies = self.sample_dot_ghosts.copy() - stretch_words = self.get_stretch_words("1/2", color, less_than_one=True) - deriv_equation = self.get_deriv_equation("1/4", "1/2", color) - - one_fourth_point = self.get_input_point(x) - one_fourth_arrow = Vector(0.5 * UP, color=WHITE) - one_fourth_arrow.stem.stretch(0.75, 0) - one_fourth_arrow.tip.scale(0.75, about_edge=DOWN) - one_fourth_arrow.next_to(one_fourth_point, DOWN, SMALL_BUFF) - one_fourth_label = TexMobject("0.25") - one_fourth_label.match_height(self.input_line.numbers) - one_fourth_label.next_to(one_fourth_arrow, DOWN, SMALL_BUFF) - - self.add(deriv_equation) - self.zoom_in_on_input( - x, - local_sample_dots=local_sample_dots, - local_coordinate_values=local_coordinate_values, - pop_out=False, - first_added_anims=[ - FadeIn(one_fourth_label), - GrowArrow(one_fourth_arrow), - ] - ) - self.play(Write(zoom_words, run_time=1)) - self.wait() - self.add_foreground_mobject(stretch_words) - self.apply_function( - self.func, - apply_function_to_number_line=False, - sample_dots=sample_dot_ghost_copies, - local_sample_dots=local_sample_dots, - target_coordinate_values=target_coordinate_values, - added_anims=[Write(stretch_words)] - ) - self.wait(2) - - -class ZoomInOnXSquaredNearZero(ZoomInOnXSquaredNearOne): - CONFIG = { - "zoom_factor": 0.1, - "zoomed_display_width": 4, - "scale_by_term": "???", - } - - def construct(self): - zoom_words = TextMobject( - "Zoomed %sx \\\\ near 0" % "{:,}".format(int(1.0 / self.zoom_factor)) - ) - zoom_words.next_to(self.zoomed_display, DOWN) - - x = 0 - local_sample_dots = self.get_local_sample_dots( - x, sample_radius=2 * self.zoomed_camera.frame.get_width() - ) - local_coordinate_values = self.get_local_coordinate_values( - x, dx=self.zoom_factor - ) - # target_coordinate_values = self.get_local_coordinate_values( - # self.func(x), dx=self.zoom_factor - # ) - - color = self.sample_dots[len(self.sample_dots) / 2].get_color() - sample_dot_ghost_copies = self.sample_dot_ghosts.copy() - stretch_words = self.get_stretch_words( - self.scale_by_term, color, less_than_one=True - ) - deriv_equation = self.get_deriv_equation(x, 2 * x, color) - - self.add(deriv_equation) - self.zoom_in_on_input( - x, - pop_out=False, - local_sample_dots=local_sample_dots, - local_coordinate_values=local_coordinate_values - ) - self.play(Write(zoom_words, run_time=1)) - self.wait() - self.add_foreground_mobject(stretch_words) - self.apply_function( - self.func, - apply_function_to_number_line=False, - sample_dots=sample_dot_ghost_copies, - local_sample_dots=local_sample_dots, - # target_coordinate_values=target_coordinate_values, - added_anims=[ - Write(stretch_words), - MaintainPositionRelativeTo( - self.local_coordinates, - self.zoomed_camera.frame - ) - ] - ) - self.wait(2) - - -class ZoomInMoreAndMoreToZero(ZoomInOnXSquaredNearZero): - def construct(self): - x = 0 - color = self.sample_dots[len(self.sample_dots) / 2].get_color() - deriv_equation = self.get_deriv_equation(x, 2 * x, color) - self.add(deriv_equation) - - frame = self.zoomed_camera.frame - zoomed_display_height = self.zoomed_display.get_height() - - last_sample_dots = VGroup() - last_coords = VGroup() - last_zoom_words = None - for factor in 0.1, 0.01, 0.001, 0.0001: - frame.save_state() - frame.set_height(factor * zoomed_display_height) - self.local_coordinate_num_decimal_places = int(-np.log10(factor)) - zoom_words = TextMobject( - "Zoomed", "{:,}x \\\\".format(int(1.0 / factor)), - "near 0", - ) - zoom_words.next_to(self.zoomed_display, DOWN) - - sample_dots = self.get_local_sample_dots(x) - coords = self.get_local_coordinate_values(x, dx=factor) - frame.restore() - - added_anims = [ - ApplyMethod(last_sample_dots.fade, 1), - ApplyMethod(last_coords.fade, 1), - ] - if last_zoom_words is not None: - added_anims.append(ReplacementTransform( - last_zoom_words, zoom_words - )) - else: - added_anims.append(FadeIn(zoom_words)) - self.zoom_in_on_input( - x, - local_sample_dots=sample_dots, - local_coordinate_values=coords, - pop_out=False, - zoom_factor=factor, - first_added_anims=added_anims - ) - self.wait() - last_sample_dots = sample_dots - last_coords = self.local_coordinates - last_zoom_words = zoom_words - - -class ZoomInOnXSquared100xZero(ZoomInOnXSquaredNearZero): - CONFIG = { - "zoom_factor": 0.01 - } - - -class ZoomInOnXSquared1000xZero(ZoomInOnXSquaredNearZero): - CONFIG = { - "zoom_factor": 0.001, - "local_coordinate_num_decimal_places": 3, - } - - -class ZoomInOnXSquared10000xZero(ZoomInOnXSquaredNearZero): - CONFIG = { - "zoom_factor": 0.0001, - "local_coordinate_num_decimal_places": 4, - "scale_by_term": "0", - } - - -class XSquaredForNegativeInput(TalkThroughXSquaredExample): - CONFIG = { - "input_line_config": { - "x_min": -4, - "x_max": 4, - }, - "input_line_zero_point": 0.5 * UP + 0 * LEFT, - "output_line_config": {}, - "default_mapping_animation_config": { - "path_arc": 30 * DEGREES - }, - "zoomed_display_width": 4, - } - - def construct(self): - self.add_title() - self.show_full_transformation() - self.zoom_in_on_example() - - def show_full_transformation(self): - sample_dots = self.get_sample_dots( - x_min=-4.005, - delta_x=0.05, - dot_radius=0.05 - ) - sample_dots.set_fill(opacity=0.8) - - self.play(LaggedStartMap(DrawBorderThenFill, sample_dots)) - self.play(LaggedStartMap( - ApplyFunction, sample_dots[len(sample_dots) / 2:0:-1], - lambda mob: ( - lambda m: m.scale(2).shift(SMALL_BUFF * UP).set_color(PINK), - mob, - ), - rate_func=there_and_back, - )) - self.add_sample_dot_ghosts(sample_dots) - self.apply_function(self.func, sample_dots=sample_dots) - self.wait() - - def zoom_in_on_example(self): - x = -2 - - local_sample_dots = self.get_local_sample_dots(x) - local_coordinate_values = self.get_local_coordinate_values( - x, dx=0.1 - ) - target_coordinate_values = self.get_local_coordinate_values( - self.func(x), dx=0.1 - ) - deriv_equation = self.get_deriv_equation(x, 2 * x, color=BLUE) - sample_dot_ghost_copies = self.sample_dot_ghosts.copy() - scale_words = self.get_stretch_words(-4, color=BLUE) - - self.zoom_in_on_input( - x, - local_sample_dots=local_sample_dots, - local_coordinate_values=local_coordinate_values, - ) - self.wait() - self.play(Write(deriv_equation)) - self.add_foreground_mobject(scale_words) - self.play(Write(scale_words)) - self.apply_function( - self.func, - sample_dots=sample_dot_ghost_copies, - local_sample_dots=local_sample_dots, - target_coordinate_values=target_coordinate_values - ) - self.wait() - - -class FeelsALittleCramped(TeacherStudentsScene): - def construct(self): - self.student_says( - "Kind of cramped,\\\\ isn't it?", - target_mode="sassy" - ) - self.wait() - self.teacher_says( - "Sure, but think \\\\ locally" - ) - self.change_all_student_modes("pondering", look_at_arg=self.screen) - self.wait(3) - - -class HowDoesThisSolveProblems(TeacherStudentsScene): - def construct(self): - self.student_says( - "Is this...useful?", - target_mode="confused" - ) - self.change_student_modes("maybe", "confused", "sassy") - self.play(self.teacher.change, "happy") - self.wait(3) - - -class IntroduceContinuedFractionPuzzle(PiCreatureScene): - CONFIG = { - "remove_initial_rhs": True, - } - - def construct(self): - self.ask_question() - self.set_equal_to_x() - - def create_pi_creatures(self): - morty = Mortimer(height=2) - morty.to_corner(DR) - - friend = PiCreature(color=GREEN, height=2) - friend.to_edge(DOWN) - friend.shift(LEFT) - - group = VGroup(morty, friend) - group.shift(2 * LEFT) - - return morty, friend - - def ask_question(self): - morty, friend = self.pi_creatures - frac = get_phi_continued_fraction(9) - frac.scale(0.8) - rhs = DecimalNumber( - (1 - np.sqrt(5)) / 2.0, - num_decimal_places=5, - show_ellipsis=True, - ) - rhs.set_color(YELLOW) - equals = TexMobject("=") - equals.next_to(frac.get_part_by_tex("\\over"), RIGHT) - rhs.next_to(equals, RIGHT) - group = VGroup(frac, equals, rhs) - group.scale(1.5) - group.to_corner(UR) - - self.play( - LaggedStartMap( - Write, frac, - run_time=15, - lag_ratio=0.15, - ), - FadeInFromDown(equals), - FadeInFromDown(rhs), - PiCreatureSays( - friend, "Would this be valid? \\\\ If not, why not?", - target_mode="confused", - look_at_arg=frac, - bubble_kwargs={ - "direction": RIGHT, - "width": 4, - "height": 3, - } - ), - morty.change, "pondering", - ) - self.wait() - - anims = [ - RemovePiCreatureBubble( - friend, target_mode="pondering", - look_at_arg=frac - ), - ] - if self.remove_initial_rhs: - anims += [ - Animation(frac), - FadeOut(equals), - rhs.scale, 0.5, - rhs.to_corner, DL, - ] - self.play(*anims) - - self.neg_one_over_phi = rhs - self.equals = equals - self.frac = frac - - def set_equal_to_x(self): - frac = self.frac - morty, friend = self.get_pi_creatures() - - inner_frac = frac[4:] - inner_frac_rect = SurroundingRectangle( - inner_frac, stroke_width=2, buff=0.5 * SMALL_BUFF - ) - inner_frac_group = VGroup(inner_frac, inner_frac_rect) - - equals = TexMobject("=") - equals.next_to(frac[3], RIGHT) - x, new_x = [TexMobject("x") for i in range(2)] - xs = VGroup(x, new_x) - xs.set_color(YELLOW) - xs.scale(1.3) - x.next_to(equals, RIGHT) - new_x.next_to(frac[3], DOWN, 2 * SMALL_BUFF) - - fixed_point_words = VGroup( - TextMobject("Fixed point of"), - TexMobject( - "f(x) = 1 + \\frac{1}{x}", - tex_to_color_map={"x": YELLOW} - ) - ) - fixed_point_words.arrange(DOWN) - - self.play(Write(x), Write(equals)) - self.wait() - self.play(ShowCreation(inner_frac_rect)) - self.wait() - self.play( - inner_frac_group.scale, 0.75, - inner_frac_group.center, - inner_frac_group.to_edge, LEFT, - ReplacementTransform( - x.copy(), new_x, - path_arc=-90 * DEGREES - ) - ) - self.wait() - self.play( - frac[3].stretch, 0.1, 0, {"about_edge": RIGHT}, - MaintainPositionRelativeTo( - VGroup(frac[2], new_x), frac[3] - ), - UpdateFromFunc( - frac[:2], lambda m: m.next_to(frac[3], LEFT) - ) - ) - self.wait() - fixed_point_words.next_to(VGroup(frac[0], xs), DOWN, LARGE_BUFF) - self.play( - Write(fixed_point_words), - morty.change, "hooray", - friend.change, "happy" - ) - self.wait(3) - - -class GraphOnePlusOneOverX(GraphScene): - CONFIG = { - "x_min": -6, - "x_max": 6, - "x_axis_width": 12, - "y_min": -4, - "y_max": 5, - "y_axis_height": 8, - "y_axis_label": None, - "graph_origin": 0.5 * DOWN, - "num_graph_anchor_points": 100, - "func_graph_color": GREEN, - "identity_graph_color": BLUE, - } - - def construct(self): - self.add_title() - self.setup_axes() - self.draw_graphs() - self.show_solutions() - - def add_title(self): - title = self.title = TexMobject( - "\\text{Solve: }", "1 + \\frac{1}{x}", "=", "x", - ) - title.set_color_by_tex("x", self.identity_graph_color, substring=False) - title.set_color_by_tex("frac", self.func_graph_color) - title.to_corner(UL) - self.add(title) - - def setup_axes(self): - GraphScene.setup_axes(self) - step = 2 - self.x_axis.add_numbers(*list(range(-6, 0, step)) + list(range(step, 7, step))) - self.y_axis.label_direction = RIGHT - self.y_axis.add_numbers(*list(range(-2, 0, step)) + list(range(step, 4, step))) - - def draw_graphs(self, animate=True): - lower_func_graph, upper_func_graph = func_graph = VGroup(*[ - self.get_graph( - lambda x: 1.0 + 1.0 / x, - x_min=x_min, - x_max=x_max, - color=self.func_graph_color, - ) - for x_min, x_max in [(-10, -0.1), (0.1, 10)] - ]) - func_graph.label = self.get_graph_label( - upper_func_graph, "y = 1 + \\frac{1}{x}", - x_val=6, direction=UP, - ) - - identity_graph = self.get_graph( - lambda x: x, color=self.identity_graph_color - ) - identity_graph.label = self.get_graph_label( - identity_graph, "y = x", - x_val=3, direction=UL, buff=SMALL_BUFF - ) - - if animate: - for graph in func_graph, identity_graph: - self.play( - ShowCreation(graph), - Write(graph.label), - run_time=2 - ) - self.wait() - else: - self.add( - func_graph, func_graph.label, - identity_graph, identity_graph.label, - ) - - self.func_graph = func_graph - self.identity_graph = identity_graph - - def show_solutions(self): - phi = 0.5 * (1 + np.sqrt(5)) - phi_bro = 0.5 * (1 - np.sqrt(5)) - - lines = VGroup() - for num in phi, phi_bro: - line = DashedLine( - self.coords_to_point(num, 0), - self.coords_to_point(num, num), - color=WHITE - ) - line_copy = line.copy() - line_copy.set_color(YELLOW) - line.fade(0.5) - line_anim = ShowCreationThenDestruction( - line_copy, - lag_ratio=0.5, - run_time=2 - ) - cycle_animation(line_anim) - lines.add(line) - - phi_line, phi_bro_line = lines - - decimal_kwargs = { - "num_decimal_places": 3, - "show_ellipsis": True, - "color": YELLOW, - } - arrow_kwargs = { - "buff": SMALL_BUFF, - "color": WHITE, - "tip_length": 0.15, - "rectangular_stem_width": 0.025, - } - - phi_decimal = DecimalNumber(phi, **decimal_kwargs) - phi_decimal.next_to(phi_line, DOWN, LARGE_BUFF) - phi_arrow = Arrow( - phi_decimal[:4].get_top(), phi_line.get_bottom(), - **arrow_kwargs - ) - phi_label = TexMobject("=", "\\varphi") - phi_label.next_to(phi_decimal, RIGHT) - phi_label.set_color_by_tex("\\varphi", YELLOW) - - phi_bro_decimal = DecimalNumber(phi_bro, **decimal_kwargs) - phi_bro_decimal.next_to(phi_bro_line, UP, LARGE_BUFF) - phi_bro_decimal.shift(0.5 * LEFT) - phi_bro_arrow = Arrow( - phi_bro_decimal[:6].get_bottom(), phi_bro_line.get_top(), - **arrow_kwargs - ) - - brother_words = TextMobject( - "$\\varphi$'s little brother", - tex_to_color_map={"$\\varphi$": YELLOW}, - arg_separator="" - ) - brother_words.next_to( - phi_bro_decimal[-2], UP, buff=MED_SMALL_BUFF, - aligned_edge=RIGHT - ) - - self.add(phi_line.continual_anim) - self.play(ShowCreation(phi_line)) - self.play( - Write(phi_decimal), - GrowArrow(phi_arrow), - ) - self.play(Write(phi_label)) - self.wait(3) - self.add(phi_bro_line.continual_anim) - self.play(ShowCreation(phi_bro_line)) - self.play( - Write(phi_bro_decimal), - GrowArrow(phi_bro_arrow), - ) - self.wait(4) - self.play(Write(brother_words)) - self.wait(8) - - -class ThinkAboutWithRepeatedApplication(IntroduceContinuedFractionPuzzle): - CONFIG = { - "remove_initial_rhs": False, - } - - def construct(self): - self.force_skipping() - self.ask_question() - self.revert_to_original_skipping_status() - - self.obviously_not() - self.ask_about_fraction() - self.plug_func_into_self() - - def obviously_not(self): - morty, friend = self.get_pi_creatures() - friend.change_mode("confused") - randy = Randolph() - randy.match_height(morty) - randy.to_corner(DL) - - frac = self.frac - rhs = self.neg_one_over_phi - plusses = frac[1::4] - plus_rects = VGroup(*[ - SurroundingRectangle(plus, buff=0) for plus in plusses - ]) - plus_rects.set_color(PINK) - - self.play(FadeIn(randy)) - self.play( - PiCreatureSays( - randy, "Obviously not!", - bubble_kwargs={"width": 3, "height": 2}, - target_mode="angry", - run_time=1, - ), - morty.change, "guilty", - friend.change, "hesitant" - ) - self.wait() - self.play( - Animation(frac), - RemovePiCreatureBubble(randy, target_mode="sassy"), - morty.change, "confused", - friend.change, "confused", - ) - self.play(LaggedStartMap( - ShowCreationThenDestruction, plus_rects, - run_time=2, - lag_ratio=0.35, - )) - self.play(WiggleOutThenIn(rhs)) - self.wait(2) - self.play( - frac.scale, 0.7, - frac.to_corner, UL, - FadeOut(self.equals), - rhs.scale, 0.5, - rhs.center, - rhs.to_edge, LEFT, - FadeOut(randy), - morty.change, "pondering", - friend.change, "pondering", - ) - - def ask_about_fraction(self): - frac = self.frac - arrow = Vector(LEFT, color=RED) - arrow.next_to(frac, RIGHT) - question = TextMobject("What does this \\\\ actually mean?") - question.set_color(RED) - question.next_to(arrow, RIGHT) - - self.play( - LaggedStartMap(FadeIn, question, run_time=1), - GrowArrow(arrow), - LaggedStartMap( - ApplyMethod, frac, - lambda m: (m.set_color, RED), - rate_func=there_and_back, - lag_ratio=0.2, - run_time=2 - ) - ) - self.wait() - self.play(FadeOut(question), FadeOut(arrow)) - - def plug_func_into_self(self, value=1, value_str="1"): - morty, friend = self.pi_creatures - - def func(x): - return 1 + 1.0 / x - - lines = VGroup() - value_labels = VGroup() - for n_terms in range(5): - lhs = get_nested_f(n_terms, arg="c") - equals = TexMobject("=") - rhs = get_nested_one_plus_one_over_x(n_terms, bottom_term=value_str) - equals.next_to(rhs[0], LEFT) - lhs.next_to(equals, LEFT) - lines.add(VGroup(lhs, equals, rhs)) - - value_label = TexMobject("= %.3f\\dots" % value) - value = func(value) - value_labels.add(value_label) - - lines.arrange( - DOWN, buff=MED_LARGE_BUFF, - ) - VGroup(lines, value_labels).scale(0.8) - lines.to_corner(UR) - buff = MED_LARGE_BUFF + MED_SMALL_BUFF + value_labels.get_width() - lines.to_edge(RIGHT, buff=buff) - for line, value_label in zip(lines, value_labels): - value_label.move_to(line[1]).to_edge(RIGHT) - - top_line = lines[0] - colors = [WHITE] + color_gradient([YELLOW, RED, PINK], len(lines) - 1) - for n in range(1, len(lines)): - color = colors[n] - lines[n][0].set_color(color) - lines[n][0][1:-1].match_style(lines[n - 1][0]) - lines[n][2].set_color(color) - lines[n][2][4:].match_style(lines[n - 1][2]) - - arrow = Vector(0.5 * DOWN, color=WHITE) - arrow.next_to(value_labels[-1], DOWN) - q_marks = TexMobject("???") - q_marks.next_to(arrow, DOWN) - - self.play( - FadeInFromDown(top_line), - FadeInFromDown(value_labels[0]) - ) - for n in range(1, len(lines)): - new_line = lines[n] - last_line = lines[n - 1] - value_label = value_labels[n] - mover, target = [ - VGroup( - line[0][0], - line[0][-1], - line[1], - line[2][:4], - ) - for line in (lines[1], new_line) - ] - anims = [ReplacementTransform( - mover.copy().fade(1), target, path_arc=30 * DEGREES - )] - if n == 4: - morty.generate_target() - morty.target.change("horrified") - morty.target.shift(2.5 * DOWN) - anims.append(MoveToTarget(morty, remover=True)) - self.play(*anims) - self.wait() - self.play( - ReplacementTransform( - last_line[0].copy(), new_line[0][1:-1] - ), - ReplacementTransform( - last_line[2].copy(), new_line[2][4:] - ), - ) - self.play(FadeIn(value_label)) - self.wait() - self.play( - GrowArrow(arrow), - Write(q_marks), - friend.change, "confused" - ) - self.wait(3) - - self.top_line = VGroup(lines[0], value_labels[0]) - - -class RepeatedApplicationWithPhiBro(ThinkAboutWithRepeatedApplication): - CONFIG = { - "value": (1 - np.sqrt(5)) / 2, - "value_str": "-1/\\varphi", - } - - def construct(self): - self.force_skipping() - self.ask_question() - self.obviously_not() - self.revert_to_original_skipping_status() - - self.plug_func_into_self( - value=self.value, - value_str=self.value_str - ) - - -class RepeatedApplicationWithNegativeSeed(RepeatedApplicationWithPhiBro, MovingCameraScene): - CONFIG = { - "value": -0.65, - "value_str": "-0.65" - } - - def setup(self): - MovingCameraScene.setup(self) - RepeatedApplicationWithPhiBro.setup(self) - - def construct(self): - RepeatedApplicationWithPhiBro.construct(self) - - rect = SurroundingRectangle(self.top_line) - question = TextMobject("What about a negative seed?") - question.match_color(rect) - question.next_to(rect, UP) - self.play(ShowCreation(rect)) - self.play( - Write(question), - self.camera.frame.set_height, FRAME_HEIGHT + 1.5 - ) - self.wait() - self.play( - FadeOut(question), - FadeOut(rect), - self.camera.frame.set_height, FRAME_HEIGHT - ) - - -class ShowRepeatedApplication(Scene): - CONFIG = { - "title_color": YELLOW, - } - - def construct(self): - self.add_func_title() - self.show_repeated_iteration() - - def add_func_title(self): - title = self.title = VGroup( - TexMobject("f(", "x", ")"), - TexMobject("="), - get_nested_one_plus_one_over_x(1) - ) - title.arrange(RIGHT) - title.to_corner(UL) - title.set_color(self.title_color) - - self.add(title) - - def show_repeated_iteration(self): - line = VGroup() - decimal_kwargs = { - "num_decimal_places": 3, - "show_ellipsis": True, - } - phi = (1 + np.sqrt(5)) / 2 - - def func(x): - return 1.0 + 1.0 / x - - initial_term = DecimalNumber(2.71828, **decimal_kwargs) - line.add(initial_term) - last_term = initial_term - - def get_arrow(): - arrow = TexMobject("\\rightarrow") - arrow.stretch(1.5, 0) - arrow.next_to(line[-1], RIGHT) - tex = TexMobject("f(x)") - tex.set_color(YELLOW) - tex.match_width(arrow) - tex.next_to(arrow, UP, SMALL_BUFF) - return VGroup(arrow, tex) - - for x in range(2): - arrow = get_arrow() - line.add(arrow) - - new_term = DecimalNumber( - func(last_term.number), - **decimal_kwargs - ) - new_term.next_to(arrow[0], RIGHT) - last_term = new_term - line.add(new_term) - - line.add(get_arrow()) - line.add(TexMobject("\\dots\\dots").next_to(line[-1][0], RIGHT)) - num_phi_mob = DecimalNumber(phi, **decimal_kwargs) - line.add(num_phi_mob.next_to(line[-1], RIGHT)) - line.move_to(DOWN) - - rects = VGroup(*[ - SurroundingRectangle(mob) - for mob in (line[0], line[1:-1], line[-1]) - ]) - rects.set_stroke(BLUE, 2) - - braces = VGroup(*[ - Brace(rect, DOWN, buff=SMALL_BUFF) - for rect in rects - ]) - braces.set_color_by_gradient(GREEN, YELLOW) - brace_texts = VGroup(*[ - brace.get_text(text).scale(0.75, about_edge=UP) - for brace, text in zip(braces, [ - "Arbitrary \\\\ starting \\\\ value", - "Repeatedly apply $f(x)$", - "$\\varphi$ \\\\ ``Golden ratio''" - ]) - ]) - var_phi_mob = brace_texts[2][0] - var_phi_mob.scale(2, about_edge=UP).set_color(YELLOW) - brace_texts[2][1:].next_to(var_phi_mob, DOWN, MED_SMALL_BUFF) - - # Animations - self.add(line[0]) - self.play( - GrowFromCenter(braces[0]), - Write(brace_texts[0]) - ) - self.wait() - self.play( - GrowFromEdge(line[1], LEFT), - FadeIn(braces[1]), - FadeIn(brace_texts[1]), - ) - self.play(ReplacementTransform(line[0].copy(), line[2])) - self.wait() - - self.play(GrowFromEdge(line[3], LEFT)) - self.play(ReplacementTransform(line[2].copy(), line[4])) - self.wait() - - self.play(GrowFromEdge(line[5], LEFT)) - self.play(LaggedStartMap(GrowFromCenter, line[6])) - self.wait() - - self.play(FadeIn(line[7])) - self.play( - GrowFromCenter(braces[2]), - FadeIn(brace_texts[2]), - ) - self.wait() - - -class NumericalPlayFromOne(ExternallyAnimatedScene): - pass - - -class NumericalPlayFromTau(ExternallyAnimatedScene): - pass - - -class NumericalPlayFromNegPhi(ExternallyAnimatedScene): - pass - - -class NumericalPlayOnNumberLineFromOne(Scene): - CONFIG = { - "starting_value": 1, - "n_jumps": 10, - "func": lambda x: 1 + 1.0 / x, - "number_line_config": { - "x_min": 0, - "x_max": 2, - "unit_size": 6, - "tick_frequency": 0.25, - "numbers_with_elongated_ticks": [0, 1, 2] - } - } - - def construct(self): - self.add_number_line() - self.add_phi_label() - self.add_title() - self.bounce_around() - - def add_number_line(self): - number_line = NumberLine(**self.number_line_config) - number_line.move_to(2 * DOWN) - number_line.add_numbers() - - self.add(number_line) - self.number_line = number_line - - def add_phi_label(self): - number_line = self.number_line - phi_point = number_line.number_to_point( - (1 + np.sqrt(5)) / 2 - ) - phi_dot = Dot(phi_point, color=YELLOW) - arrow = Vector(DL) - arrow.next_to(phi_point, UR, SMALL_BUFF) - phi_label = TexMobject("\\varphi = 1.618\\dots") - phi_label.set_color(YELLOW) - phi_label.next_to(arrow.get_start(), UP, SMALL_BUFF) - - self.add(phi_dot, phi_label, arrow) - - def add_title(self): - title = TexMobject("x \\rightarrow 1 + \\frac{1}{x}") - title.to_corner(UL) - self.add(title) - - def bounce_around(self): - number_line = self.number_line - value = self.starting_value - point = number_line.number_to_point(value) - dot = Dot(point) - dot.set_fill(RED, opacity=0.8) - arrow = Vector(DR) - arrow.next_to(point, UL, buff=SMALL_BUFF) - arrow.match_color(dot) - start_here = TextMobject("Start here") - start_here.next_to(arrow.get_start(), UP, SMALL_BUFF) - start_here.match_color(dot) - - self.play( - FadeIn(start_here), - GrowArrow(arrow), - GrowFromPoint(dot, arrow.get_start()) - ) - self.play( - FadeOut(start_here), - FadeOut(arrow) - ) - self.wait() - for x in range(self.n_jumps): - new_value = self.func(value) - new_point = number_line.number_to_point(new_value) - if new_value - value > 0: - path_arc = -120 * DEGREES - else: - path_arc = -120 * DEGREES - arc = Line( - point, new_point, - path_arc=path_arc, - buff=SMALL_BUFF - ) - self.play( - ShowCreationThenDestruction(arc, run_time=1.5), - ApplyMethod( - dot.move_to, new_point, - path_arc=path_arc - ), - ) - self.wait(0.5) - - value = new_value - point = new_point - - -class NumericalPlayOnNumberLineFromTau(NumericalPlayOnNumberLineFromOne): - CONFIG = { - "starting_value": TAU, - "number_line_config": { - "x_min": 0, - "x_max": 7, - "unit_size": 2, - } - } - - -class NumericalPlayOnNumberLineFromMinusPhi(NumericalPlayOnNumberLineFromOne): - CONFIG = { - "starting_value": -0.61803, - "number_line_config": { - "x_min": -3, - "x_max": 3, - "unit_size": 2, - "tick_frequency": 0.25, - }, - "n_jumps": 25, - } - - def add_phi_label(self): - NumericalPlayOnNumberLineFromOne.add_phi_label(self) - number_line = self.number_line - new_point = number_line.number_to_point( - (1 - np.sqrt(5)) / 2 - ) - arrow = Vector(DR) - arrow.next_to(new_point, UL, SMALL_BUFF) - arrow.set_color(RED) - new_label = TexMobject("-\\frac{1}{\\varphi} = -0.618\\dots") - new_label.set_color(RED) - new_label.next_to(arrow.get_start(), UP, SMALL_BUFF) - new_label.shift(RIGHT) - self.add(new_label, arrow) - - -class RepeatedApplicationGraphically(GraphOnePlusOneOverX, PiCreatureScene): - CONFIG = { - "starting_value": 1, - "n_jumps": 5, - "n_times_to_show_identity_property": 2, - } - - def setup(self): - GraphOnePlusOneOverX.setup(self) - PiCreatureScene.setup(self) - - def construct(self): - self.setup_axes() - self.draw_graphs(animate=False) - self.draw_spider_web() - - def create_pi_creature(self): - randy = Randolph(height=2) - randy.flip() - randy.to_corner(DR) - return randy - - def get_new_randy_mode(self): - randy = self.pi_creature - if not hasattr(self, "n_mode_changes"): - self.n_mode_changes = 0 - else: - self.n_mode_changes += 1 - if self.n_mode_changes % 3 != 0: - return randy.get_mode() - return random.choice([ - "confused", - "erm", - "maybe" - ]) - - def draw_spider_web(self): - randy = self.pi_creature - func = self.func_graph[0].underlying_function - x_val = self.starting_value - curr_output = 0 - dot = Dot(color=RED, fill_opacity=0.7) - dot.move_to(self.coords_to_point(x_val, curr_output)) - - self.play(FadeInFrom(dot, 2 * UR)) - self.wait() - - for n in range(self.n_jumps): - new_output = func(x_val) - func_graph_point = self.coords_to_point(x_val, new_output) - id_graph_point = self.coords_to_point(new_output, new_output) - v_line = DashedLine(dot.get_center(), func_graph_point) - h_line = DashedLine(func_graph_point, id_graph_point) - - curr_output = new_output - x_val = new_output - - for line in v_line, h_line: - line_end = line.get_end() - self.play( - ShowCreation(line), - dot.move_to, line_end, - randy.change, self.get_new_randy_mode() - ) - self.wait() - - if n < self.n_times_to_show_identity_property: - x_point = self.coords_to_point(new_output, 0) - y_point = self.coords_to_point(0, new_output) - lines = VGroup(*[ - Line(dot.get_center(), point) - for point in (x_point, y_point) - ]) - lines.set_color(YELLOW) - self.play(ShowCreationThenDestruction( - lines, run_time=2 - )) - self.wait(0.25) - - -class RepeatedApplicationGraphicallyFromNegPhi(RepeatedApplicationGraphically): - CONFIG = { - "starting_value": -0.61, - "n_jumps": 13, - "n_times_to_show_identity_property": 0, - } - - -class LetsSwitchToTheTransformationalView(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Lose the \\\\ graphs!", - target_mode="hooray" - ) - self.change_student_modes("hooray", "erm", "surprised") - self.wait(5) - - -class AnalyzeFunctionWithTransformations(NumberlineTransformationScene): - CONFIG = { - "input_line_zero_point": 0.5 * UP, - "output_line_zero_point": 2 * DOWN, - "func": lambda x: 1 + 1.0 / x, - "num_initial_applications": 10, - "num_repeated_local_applications": 7, - "zoomed_display_width": 3.5, - "zoomed_display_height": 2, - "default_mapping_animation_config": {}, - } - - def construct(self): - self.add_function_title() - self.repeatedly_apply_function() - self.show_phi_and_phi_bro() - self.zoom_in_on_phi() - self.zoom_in_on_phi_bro() - - def setup_number_lines(self): - NumberlineTransformationScene.setup_number_lines(self) - for line in self.input_line, self.output_line: - VGroup(line, line.tick_marks).set_stroke(width=2) - - def add_function_title(self): - title = TexMobject("f(x)", "=", "1 +", "\\frac{1}{x}") - title.to_edge(UP) - self.add(title) - self.title = title - - def repeatedly_apply_function(self): - input_zero_point = self.input_line.number_to_point(0) - output_zero_point = self.output_line.number_to_point(0) - sample_dots = self.get_sample_dots( - delta_x=0.05, - dot_radius=0.05, - x_min=-10, - x_max=10, - ) - sample_dots.set_stroke(BLACK, 0.5) - sample_points = list(map(Mobject.get_center, sample_dots)) - - self.play(LaggedStartMap( - FadeInFrom, sample_dots, - lambda m: (m, UP) - )) - self.show_arrows(sample_points) - self.wait() - for x in range(self.num_initial_applications): - self.apply_function( - self.func, - apply_function_to_number_line=False, - sample_dots=sample_dots - ) - self.wait() - - shift_vect = input_zero_point - output_zero_point - shift_vect[0] = 0 - lower_output_line = self.output_line.copy() - upper_output_line = self.output_line.copy() - lower_output_line.shift(-shift_vect) - lower_output_line.fade(1) - - self.remove(self.output_line) - self.play( - ReplacementTransform(lower_output_line, self.output_line), - upper_output_line.shift, shift_vect, - upper_output_line.fade, 1, - sample_dots.shift, shift_vect, - ) - self.remove(upper_output_line) - self.wait() - self.play(FadeOut(sample_dots)) - - def show_arrows(self, sample_points): - input_zero_point = self.input_line.number_to_point(0) - point_func = self.number_func_to_point_func(self.func) - alt_point_func = self.number_func_to_point_func(lambda x: 1.0 / x) - arrows, alt_arrows = [ - VGroup(*[ - Arrow( - point, func(point), buff=SMALL_BUFF, - tip_length=0.15 - ) - for point in sample_points - if get_norm(point - input_zero_point) > 0.3 - ]) - for func in (point_func, alt_point_func) - ] - for group in arrows, alt_arrows: - group.set_stroke(WHITE, 0.5) - group.set_color_by_gradient(RED, YELLOW) - for arrow in group: - arrow.tip.set_stroke(BLACK, 0.5) - - one_plus = self.title.get_part_by_tex("1 +") - one_plus_rect = BackgroundRectangle(one_plus) - one_plus_rect.set_fill(BLACK, opacity=0.8) - - self.play(LaggedStartMap(GrowArrow, arrows)) - self.wait() - self.play(LaggedStartMap( - ApplyMethod, arrows, - lambda a: (a.scale, 0.7), - rate_func=there_and_back, - )) - self.wait() - self.play( - Transform(arrows, alt_arrows), - FadeIn(one_plus_rect, remover=True), - rate_func=there_and_back_with_pause, - run_time=4, - ) - self.wait() - - self.all_arrows = arrows - - def show_phi_and_phi_bro(self): - phi = (1 + np.sqrt(5)) / 2 - phi_bro = (1 - np.sqrt(5)) / 2 - - input_phi_point = self.input_line.number_to_point(phi) - output_phi_point = self.output_line.number_to_point(phi) - input_phi_bro_point = self.input_line.number_to_point(phi_bro) - output_phi_bro_point = self.output_line.number_to_point(phi_bro) - - tick = Line(UP, DOWN) - tick.set_stroke(YELLOW, 3) - tick.match_height(self.input_line.tick_marks) - phi_tick = tick.copy().move_to(input_phi_point, DOWN) - phi_bro_tick = tick.copy().move_to(input_phi_bro_point, DOWN) - VGroup(phi_tick, phi_bro_tick).shift(SMALL_BUFF * DOWN) - - phi_label = TexMobject("1.618\\dots") - phi_label.next_to(phi_tick, UP) - phi_bro_label = TexMobject("-0.618\\dots") - phi_bro_label.next_to(phi_bro_tick, UP) - VGroup(phi_label, phi_bro_label).set_color(YELLOW) - - arrow_kwargs = { - "buff": SMALL_BUFF, - "rectangular_stem_width": 0.035, - "tip_length": 0.2, - } - phi_arrow = Arrow(phi_tick, output_phi_point, **arrow_kwargs) - phi_bro_arrow = Arrow(phi_bro_tick, output_phi_bro_point, **arrow_kwargs) - - def fade_arrow(arrow): - # arrow.set_stroke(DARK_GREY, 0.5) - arrow.set_stroke(width=0.1) - arrow.tip.set_fill(opacity=0) - arrow.tip.set_stroke(width=0) - return arrow - - self.play( - LaggedStartMap( - ApplyFunction, self.all_arrows, - lambda a: (fade_arrow, a) - ), - FadeIn(phi_arrow), - FadeIn(phi_bro_arrow), - ) - self.play( - Write(phi_label), - GrowFromCenter(phi_tick) - ) - self.play( - Write(phi_bro_label), - GrowFromCenter(phi_bro_tick) - ) - - self.set_variables_as_attrs( - input_phi_point, output_phi_point, - input_phi_bro_point, output_phi_bro_point, - phi_label, phi_tick, - phi_bro_label, phi_bro_tick, - phi_arrow, phi_bro_arrow - ) - - def zoom_in_on_phi(self): - phi = (1 + np.sqrt(5)) / 2 - # phi_point = self.get_input_point(phi) - local_sample_dots = self.get_local_sample_dots( - phi, dot_radius=0.005, sample_radius=1 - ) - local_coordinate_values = [1.55, 1.6, 1.65, 1.7] - - # zcbr = self.zoomed_camera_background_rectangle - zcbr_group = self.zoomed_camera_background_rectangle_group - zcbr_group.add(self.phi_tick) - - title = self.title - deriv_text = TexMobject( - "|", "\\frac{df}{dx}(\\varphi)", "|", "< 1", - tex_to_color_map={"\\varphi": YELLOW} - ) - deriv_text.get_parts_by_tex("|").match_height( - deriv_text, stretch=True - ) - deriv_text.move_to(title, UP) - approx_value = TexMobject("\\approx |%.2f|" % (-1 / phi**2)) - approx_value.move_to(deriv_text) - deriv_text_lhs = deriv_text[:-1] - deriv_text_rhs = deriv_text[-1] - - self.zoom_in_on_input( - phi, - local_sample_dots=local_sample_dots, - local_coordinate_values=local_coordinate_values - ) - self.wait() - self.apply_function( - self.func, - apply_function_to_number_line=False, - local_sample_dots=local_sample_dots, - target_coordinate_values=local_coordinate_values, - ) - self.wait() - self.play( - FadeInFromDown(deriv_text_lhs), - FadeInFromDown(deriv_text_rhs), - title.to_corner, UL - ) - self.wait() - self.play( - deriv_text_lhs.next_to, approx_value, LEFT, - deriv_text_rhs.next_to, approx_value, RIGHT, - FadeIn(approx_value) - ) - self.wait() - for n in range(self.num_repeated_local_applications): - self.apply_function( - self.func, - apply_function_to_number_line=False, - local_sample_dots=local_sample_dots, - path_arc=60 * DEGREES, - run_time=2 - ) - - self.deriv_text = VGroup( - deriv_text_lhs, deriv_text_rhs, approx_value - ) - - def zoom_in_on_phi_bro(self): - zcbr = self.zoomed_camera_background_rectangle - # zcbr_group = self.zoomed_camera_background_rectangle_group - zoomed_frame = self.zoomed_camera.frame - - phi_bro = (1 - np.sqrt(5)) / 2 - # phi_bro_point = self.get_input_point(phi_bro) - local_sample_dots = self.get_local_sample_dots(phi_bro) - local_coordinate_values = [-0.65, -0.6, -0.55] - - deriv_text = TexMobject( - "\\left| \\frac{df}{dx}\\left(\\frac{-1}{\\varphi}\\right) \\right|", - "\\approx |%.2f|" % (-1 / (phi_bro**2)), - "> 1" - ) - deriv_text.move_to(self.deriv_text, UL) - deriv_text[0][10:14].set_color(YELLOW) - - self.play( - zoomed_frame.set_height, 4, - zoomed_frame.center, - self.deriv_text.fade, 1, - run_time=2 - ) - self.wait() - zcbr.set_fill(opacity=0) - self.zoom_in_on_input( - phi_bro, - local_sample_dots=local_sample_dots, - local_coordinate_values=local_coordinate_values, - zoom_factor=self.zoom_factor, - first_anim_kwargs={"run_time": 2}, - ) - self.wait() - self.play(FadeInFromDown(deriv_text)) - self.wait() - zcbr.set_fill(opacity=1) - self.apply_function( - self.func, - apply_function_to_number_line=False, - local_sample_dots=local_sample_dots, - target_coordinate_values=local_coordinate_values, - ) - self.wait() - for n in range(self.num_repeated_local_applications): - self.apply_function( - self.func, - apply_function_to_number_line=False, - local_sample_dots=local_sample_dots, - path_arc=20 * DEGREES, - run_time=2, - ) - - -class StabilityAndInstability(AnalyzeFunctionWithTransformations): - CONFIG = { - "num_initial_applications": 0, - } - - def construct(self): - self.force_skipping() - self.add_function_title() - self.repeatedly_apply_function() - self.show_phi_and_phi_bro() - self.revert_to_original_skipping_status() - - self.label_stability() - self.write_derivative_fact() - - def label_stability(self): - self.title.to_corner(UL) - - stable_label = TextMobject("Stable fixed point") - unstable_label = TextMobject("Unstable fixed point") - labels = VGroup(stable_label, unstable_label) - labels.scale(0.8) - stable_label.next_to(self.phi_label, UP, aligned_edge=ORIGIN) - unstable_label.next_to(self.phi_bro_label, UP, aligned_edge=ORIGIN) - - phi_point = self.input_phi_point - phi_bro_point = self.input_phi_bro_point - - arrow_groups = VGroup() - for point in phi_point, phi_bro_point: - arrows = VGroup(*[a for a in self.all_arrows if get_norm(a.get_start() - point) < 0.75]).copy() - arrows.set_fill(PINK, 1) - arrows.set_stroke(PINK, 3) - arrows.second_anim = LaggedStartMap( - ApplyMethod, arrows, - lambda m: (m.set_color, YELLOW), - rate_func=there_and_back_with_pause, - lag_ratio=0.7, - run_time=2, - ) - arrows.anim = AnimationGroup(*list(map(GrowArrow, arrows))) - arrow_groups.add(arrows) - phi_arrows, phi_bro_arrows = arrow_groups - - self.add_foreground_mobjects(self.phi_arrow, self.phi_bro_arrow) - self.play( - Write(stable_label), - phi_arrows.anim, - ) - self.play(phi_arrows.second_anim) - self.play( - Write(unstable_label), - phi_bro_arrows.anim, - ) - self.play(phi_bro_arrows.second_anim) - self.wait() - - self.stable_label = stable_label - self.unstable_label = unstable_label - self.phi_arrows = phi_arrows - self.phi_bro_arrows = phi_bro_arrows - - def write_derivative_fact(self): - stable_label = self.stable_label - unstable_label = self.unstable_label - labels = VGroup(stable_label, unstable_label) - phi_arrows = self.phi_arrows - phi_bro_arrows = self.phi_bro_arrows - arrow_groups = VGroup(phi_arrows, phi_bro_arrows) - - deriv_labels = VGroup() - for char, label in zip("<>", labels): - deriv_label = TexMobject( - "\\big|", "\\frac{df}{dx}(", "x", ")", "\\big|", - char, "1" - ) - deriv_label.get_parts_by_tex("\\big|").match_height( - deriv_label, stretch=True - ) - deriv_label.set_color_by_tex("x", YELLOW, substring=False) - deriv_label.next_to(label, UP) - deriv_labels.add(deriv_label) - - dot_groups = VGroup() - for arrow_group in arrow_groups: - dot_group = VGroup() - for arrow in arrow_group: - start_point, end_point = [ - line.number_to_point(line.point_to_number(p)) - for line, p in [ - (self.input_line, arrow.get_start()), - (self.output_line, arrow.get_end()), - ] - ] - dot = Dot(start_point, radius=0.05) - dot.set_color(YELLOW) - dot.generate_target() - dot.target.move_to(end_point) - dot_group.add(dot) - dot_groups.add(dot_group) - - for deriv_label, dot_group in zip(deriv_labels, dot_groups): - self.play(FadeInFromDown(deriv_label)) - self.play(LaggedStartMap(GrowFromCenter, dot_group)) - self.play(*list(map(MoveToTarget, dot_group)), run_time=2) - self.wait() - - -class StaticAlgebraicObject(Scene): - def construct(self): - frac = get_phi_continued_fraction(40) - frac.set_width(FRAME_WIDTH - 1) - # frac.shift(2 * DOWN) - frac.to_edge(DOWN) - frac.set_stroke(WHITE, width=0.5) - - title = TexMobject( - "\\infty \\ne \\lim", - tex_to_color_map={"\\ne": RED} - ) - title.scale(1.5) - title.to_edge(UP) - - polynomial = TexMobject("x^2 - x - 1 = 0") - polynomial.move_to(title) - - self.add(title) - self.play(LaggedStartMap( - GrowFromCenter, frac, - lag_ratio=0.1, - run_time=3 - )) - self.wait() - factor = 1.1 - self.play(frac.scale, factor, run_time=0.5) - self.play( - frac.scale, 1 / factor, - frac.set_color, LIGHT_GREY, - run_time=0.5, rate_func=lambda t: t**5, - ) - self.wait() - self.play( - FadeOut(title), - FadeIn(polynomial) - ) - self.wait(2) - - -class NotBetterThanGraphs(TeacherStudentsScene): - def construct(self): - self.student_says( - "Um, yeah, I'll stick \\\\ with graphs thanks", - target_mode="sassy", - ) - self.play( - self.teacher.change, "guilty", - self.get_student_changes("sad", "sassy", "hesitant") - ) - self.wait(2) - self.play( - RemovePiCreatureBubble(self.students[1]), - self.teacher.change, "raise_right_hand" - ) - self.change_all_student_modes( - "confused", look_at_arg=self.screen - ) - self.wait(3) - self.teacher_says( - "You must flex those \\\\ conceptual muscles", - added_anims=[self.get_student_changes( - *3 * ["thinking"], - look_at_arg=self.teacher.eyes - )] - ) - self.wait(3) - - -class WhatComesAfterWrapper(Wrapper): - CONFIG = {"title": "Beyond the first year"} - - def construct(self): - Wrapper.construct(self) - new_title = TextMobject("Next video") - new_title.set_color(BLUE) - new_title.move_to(self.title) - - self.play( - FadeInFromDown(new_title), - self.title.shift, UP, - self.title.fade, 1, - ) - self.wait(3) - - -class TopicsAfterSingleVariable(PiCreatureScene, MoreTopics): - CONFIG = { - "pi_creatures_start_on_screen": False, - } - - def construct(self): - MoreTopics.construct(self) - self.show_horror() - self.zero_in_on_complex_analysis() - - def create_pi_creatures(self): - creatures = VGroup(*[ - PiCreature(color=color) - for color in [BLUE_E, BLUE_C, BLUE_D] - ]) - creatures.arrange(RIGHT, buff=LARGE_BUFF) - creatures.scale(0.5) - creatures.to_corner(DR) - return creatures - - def show_horror(self): - creatures = self.get_pi_creatures() - modes = ["horrified", "tired", "horrified"] - for creature, mode in zip(creatures, modes): - creature.generate_target() - creature.target.change(mode, self.other_topics) - creatures.fade(1) - - self.play(LaggedStartMap(MoveToTarget, creatures)) - self.wait(2) - - def zero_in_on_complex_analysis(self): - creatures = self.get_pi_creatures() - complex_analysis = self.other_topics[1] - self.other_topics.remove(complex_analysis) - - self.play( - complex_analysis.scale, 1.25, - complex_analysis.center, - complex_analysis.to_edge, UP, - LaggedStartMap(FadeOut, self.other_topics), - LaggedStartMap(FadeOut, self.lines), - FadeOut(self.calculus), - *[ - ApplyMethod(creature.change, "pondering") - for creature in creatures - ] - ) - self.wait(4) - - -class ShowJacobianZoomedIn(LinearTransformationScene, ZoomedScene): - CONFIG = { - "show_basis_vectors": False, - "show_coordinates": True, - "zoom_factor": 0.05, - } - - def setup(self): - LinearTransformationScene.setup(self) - ZoomedScene.setup(self) - - def construct(self): - def example_function(point): - x, y, z = point - return np.array([ - x + np.sin(y), - y + np.sin(x), - 0 - ]) - - zoomed_camera = self.zoomed_camera - zoomed_display = self.zoomed_display - frame = zoomed_camera.frame - frame.move_to(3 * LEFT + 1 * UP) - frame.set_color(YELLOW) - zoomed_display.display_frame.set_color(YELLOW) - zd_rect = BackgroundRectangle( - zoomed_display, - fill_opacity=1, - buff=MED_SMALL_BUFF, - ) - self.add_foreground_mobject(zd_rect) - zd_rect.anim = UpdateFromFunc( - zd_rect, - lambda rect: rect.replace(zoomed_display).scale(1.1) - ) - zd_rect.next_to(FRAME_HEIGHT * UP, UP) - - tiny_grid = NumberPlane( - x_radius=2, - y_radius=2, - color=BLUE_E, - secondary_color=DARK_GREY, - ) - tiny_grid.replace(frame) - - jacobian_words = TextMobject("Jacobian") - jacobian_words.add_background_rectangle() - jacobian_words.scale(1.5) - jacobian_words.move_to(zoomed_display, UP) - zoomed_display.next_to(jacobian_words, DOWN) - - self.play(self.get_zoom_in_animation()) - self.activate_zooming() - self.play( - self.get_zoomed_display_pop_out_animation(), - zd_rect.anim - ) - self.play( - ShowCreation(tiny_grid), - Write(jacobian_words), - run_time=2 - ) - self.add_transformable_mobject(tiny_grid) - self.add_foreground_mobject(jacobian_words) - self.wait() - self.apply_nonlinear_transformation( - example_function, - added_anims=[MaintainPositionRelativeTo( - zoomed_camera.frame, tiny_grid, - )], - run_time=5 - ) - self.wait() - - -class PrinciplesOverlay(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs": { - "color": GREY_BROWN, - "flip_at_start": True, - }, - "default_pi_creature_start_corner": DR, - } - - def construct(self): - morty = self.pi_creature - q_marks = VGroup(*[TexMobject("?") for x in range(40)]) - q_marks.arrange_in_grid(4, 10) - q_marks.space_out_submobjects(1.4) - for mark in q_marks: - mark.shift( - random.random() * RIGHT, - random.random() * UP, - ) - mark.scale(1.5) - mark.set_stroke(BLACK, 1) - q_marks.next_to(morty, UP) - q_marks.shift_onto_screen() - q_marks.sort( - lambda p: get_norm(p - morty.get_top()) - ) - - self.play(morty.change, "pondering") - self.wait(2) - self.play(morty.change, "raise_right_hand") - self.wait() - self.play(morty.change, "thinking") - self.wait(4) - self.play(FadeInFromDown(q_marks[0])) - self.wait(2) - self.play(LaggedStartMap( - FadeInFromDown, q_marks[1:], - run_time=3 - )) - self.wait(3) - - -class ManyInfiniteExpressions(Scene): - def construct(self): - frac = get_phi_continued_fraction(10) - frac.set_height(2) - frac.to_corner(UL) - - n = 9 - radical_str_parts = [ - "%d + \\sqrt{" % d - for d in range(1, n + 1) - ] - radical_str_parts += ["\\cdots"] - radical_str_parts += ["}"] * n - radical = TexMobject("".join(radical_str_parts)) - radical.to_corner(UR) - radical.to_edge(DOWN) - radical.set_color_by_gradient(YELLOW, RED) - - n = 12 - power_tower = TexMobject( - *["\\sqrt{2}^{"] * n + ["\\dots"] + ["}"] * n - ) - power_tower.to_corner(UR) - power_tower.set_color_by_gradient(BLUE, GREEN) - - self.play(*[ - LaggedStartMap( - GrowFromCenter, group, - lag_ratio=0.1, - run_time=8, - ) - for group in (frac, radical, power_tower) - ]) - self.wait(2) - - -class HoldUpPromo(PrinciplesOverlay): - def construct(self): - morty = self.pi_creature - - url = TextMobject("https://brilliant.org/3b1b/") - url.to_corner(UL) - - rect = ScreenRectangle(height=5.5) - rect.next_to(url, DOWN) - rect.to_edge(LEFT) - - self.play( - Write(url), - self.pi_creature.change, "raise_right_hand" - ) - self.play(ShowCreation(rect)) - self.wait(2) - self.change_mode("thinking") - self.wait() - self.look_at(url) - self.wait(10) - self.change_mode("happy") - self.wait(10) - self.change_mode("raise_right_hand") - self.wait(10) - - self.play(FadeOut(rect), FadeOut(url)) - self.play(morty.change, "raise_right_hand") - self.wait() - self.play(morty.change, "hooray") - self.wait(3) - - -class EndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "Juan Benet", - "Keith Smith", - "Chloe Zhou", - "Desmos", - "Burt Humburg", - "CrypticSwarm", - "Andrew Sachs", - "Ho\\`ang T\\`ung L\\^am", - # "Hoàng Tùng Lâm", - "Devin Scott", - "Akash Kumar", - "Felix Tripier", - "Arthur Zey", - "David Kedmey", - "Ali Yahya", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Yu Jun", - "dave nicponski", - "Damion Kistler", - "Jordan Scales", - "Markus Persson", - "Fela", - "Fred Ehrsam", - "Britt Selvitelle", - "Jonathan Wilson", - "Ryan Atallah", - "Joseph John Cox", - "Luc Ritchie", - "Matt Roveto", - "Jamie Warner", - "Marek Cirkos", - "Magister Mugit", - "Stevie Metke", - "Cooper Jones", - "James Hughes", - "John V Wertheim", - "Chris Giddings", - "Song Gao", - "Alexander Feldman", - "Matt Langford", - "Max Mitchell", - "Richard Burgmann", - "John Griffith", - "Chris Connett", - "Steven Tomlinson", - "Jameel Syed", - "Bong Choung", - "Ignacio Freiberg", - "Zhilong Yang", - "Giovanni Filippi", - "Eric Younge", - "Prasant Jagannath", - "Cody Brocious", - "James H. Park", - "Norton Wang", - "Kevin Le", - "Tianyu Ge", - "David MacCumber", - "Oliver Steele", - "Yaw Etse", - "Dave B", - "Waleed Hamied", - "George Chiesa", - "supershabam", - "Delton Ding", - "Thomas Tarler", - "Isak Hietala", - "1st ViewMaths", - "Jacob Magnuson", - "Mark Govea", - "Clark Gaebel", - "Mathias Jansson", - "David Clark", - "Michael Gardner", - "Mads Elvheim", - "Awoo", - "Dr . David G. Stork", - "Ted Suzman", - "Linh Tran", - "Andrew Busey", - "John Haley", - "Ankalagon", - "Eric Lavault", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Sh\\`im\\'in Ku\\=ang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ] - } - - -# class Thumbnail(GraphicalIntuitions): -class Thumbnail(AnalyzeFunctionWithTransformations): - CONFIG = { - "x_axis_width": 12, - "graph_origin": 1.5 * DOWN + 4 * LEFT, - "num_initial_applications": 1, - "input_line_zero_point": 2 * UP, - } - - def construct(self): - self.add_function_title() - self.title.fade(1) - self.titles.fade(1) - self.repeatedly_apply_function() - self.all_arrows.set_stroke(width=1) - - full_rect = FullScreenFadeRectangle() - cross = Cross(full_rect) - cross.set_stroke(width=40) - - # self.add(cross) diff --git a/from_3b1b/old/basel/basel.py b/from_3b1b/old/basel/basel.py deleted file mode 100644 index 064bef39..00000000 --- a/from_3b1b/old/basel/basel.py +++ /dev/null @@ -1,4524 +0,0 @@ -#!/usr/bin/env python - - -from manimlib.imports import * - -from once_useful_constructs.light import * - -import warnings -warnings.warn(""" - Warning: This file makes use of - ContinualAnimation, which has since - been deprecated -""") - -import types -import functools - -LIGHT_COLOR = YELLOW -INDICATOR_RADIUS = 0.7 -INDICATOR_STROKE_WIDTH = 1 -INDICATOR_STROKE_COLOR = WHITE -INDICATOR_TEXT_COLOR = WHITE -INDICATOR_UPDATE_TIME = 0.2 -FAST_INDICATOR_UPDATE_TIME = 0.1 -OPACITY_FOR_UNIT_INTENSITY = 0.2 -SWITCH_ON_RUN_TIME = 1.5 -FAST_SWITCH_ON_RUN_TIME = 0.1 -NUM_CONES = 7 # in first lighthouse scene -NUM_VISIBLE_CONES = 5 # ibidem -ARC_TIP_LENGTH = 0.2 - -NUM_LEVELS = 15 -AMBIENT_FULL = 0.8 -AMBIENT_DIMMED = 0.5 -AMBIENT_SCALE = 2.0 -AMBIENT_RADIUS = 20.0 -SPOTLIGHT_FULL = 0.8 -SPOTLIGHT_DIMMED = 0.2 -SPOTLIGHT_SCALE = 1.0 -SPOTLIGHT_RADIUS = 20.0 - -LIGHT_COLOR = YELLOW -DEGREES = TAU/360 - - -BASELINE_YPOS = -2.5 -OBSERVER_POINT = np.array([0,BASELINE_YPOS,0]) -LAKE0_RADIUS = 1.5 -INDICATOR_RADIUS = 0.6 -TICK_SIZE = 0.5 -LIGHTHOUSE_HEIGHT = 0.5 -LAKE_COLOR = BLUE -LAKE_OPACITY = 0.15 -LAKE_STROKE_WIDTH = 5.0 -LAKE_STROKE_COLOR = BLUE -TEX_SCALE = 0.8 -DOT_COLOR = BLUE - -LIGHT_MAX_INT = 1 -LIGHT_SCALE = 2.5 -LIGHT_CUTOFF = 1 - -RIGHT_ANGLE_SIZE = 0.3 - -inverse_power_law = lambda maxint,scale,cutoff,exponent: \ - (lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent) -inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) - - -A = np.array([5.,-3.,0.]) -B = np.array([-5.,3.,0.]) -C = np.array([-5.,-3.,0.]) -xA = A[0] -yA = A[1] -xB = B[0] -yB = B[1] -xC = C[0] -yC = C[1] - -# find the coords of the altitude point H -# as the solution of a certain LSE -prelim_matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic -prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) -H2 = np.linalg.solve(prelim_matrix,prelim_vector) -H = np.append(H2, 0.) - - - - -class AngleUpdater(ContinualAnimation): - def __init__(self, angle_arc, spotlight, **kwargs): - self.angle_arc = angle_arc - - self.spotlight = spotlight - ContinualAnimation.__init__(self, self.angle_arc, **kwargs) - - def update_mobject(self, dt): - new_arc = self.angle_arc.copy().set_bound_angles( - start = self.spotlight.start_angle(), - stop = self.spotlight.stop_angle() - ) - new_arc.init_points() - new_arc.move_arc_center_to(self.spotlight.get_source_point()) - self.angle_arc.points = new_arc.points - self.angle_arc.add_tip(tip_length = ARC_TIP_LENGTH, - at_start = True, at_end = True) - - - - - -class LightIndicator(VMobject): - CONFIG = { - "radius": 0.5, - "intensity": 0, - "opacity_for_unit_intensity": 1, - "precision": 3, - "show_reading": True, - "measurement_point": ORIGIN, - "light_source": None - } - - def init_points(self): - self.background = Circle(color=BLACK, radius = self.radius) - self.background.set_fill(opacity=1.0) - self.foreground = Circle(color=self.color, radius = self.radius) - self.foreground.set_stroke(color=INDICATOR_STROKE_COLOR,width=INDICATOR_STROKE_WIDTH) - - self.add(self.background, self.foreground) - self.reading = DecimalNumber(self.intensity,num_decimal_places = self.precision) - self.reading.set_fill(color=INDICATOR_TEXT_COLOR) - self.reading.move_to(self.get_center()) - if self.show_reading: - self.add(self.reading) - - def set_intensity(self, new_int): - self.intensity = new_int - new_opacity = min(1, new_int * self.opacity_for_unit_intensity) - self.foreground.set_fill(opacity=new_opacity) - ChangeDecimalToValue(self.reading, new_int).update(1) - return self - - def get_measurement_point(self): - if self.measurement_point != None: - return self.measurement_point - else: - return self.get_center() - - - def measured_intensity(self): - distance = get_norm(self.get_measurement_point() - - self.light_source.get_source_point()) - intensity = self.light_source.opacity_function(distance) / self.opacity_for_unit_intensity - return intensity - - def update_mobjects(self): - if self.light_source == None: - print("Indicator cannot update, reason: no light source found") - self.set_intensity(self.measured_intensity()) - - - - -class UpdateLightIndicator(AnimationGroup): - - def __init__(self, indicator, intensity, **kwargs): - if not isinstance(indicator,LightIndicator): - raise Exception("This transform applies only to LightIndicator") - - target_foreground = indicator.copy().set_intensity(intensity).foreground - change_opacity = Transform( - indicator.foreground, target_foreground - ) - changing_decimal = ChangeDecimalToValue(indicator.reading, intensity) - AnimationGroup.__init__(self, changing_decimal, change_opacity, **kwargs) - self.mobject = indicator - - -class ContinualLightIndicatorUpdate(ContinualAnimation): - - def update_mobject(self,dt): - self.mobject.update_mobjects() - - -def copy_func(f): - """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)""" - g = types.FunctionType(f.__code__, f.__globals__, name=f.__name__, - argdefs=f.__defaults__, - closure=f.__closure__) - g = functools.update_wrapper(g, f) - return g - -class ScaleLightSources(Transform): - - def __init__(self, light_sources_mob, factor, about_point = None, **kwargs): - - if about_point == None: - about_point = light_sources_mob.get_center() - - ls_target = light_sources_mob.copy() - - for submob in ls_target: - - if type(submob) == LightSource: - - new_sp = submob.source_point.copy() # a mob - new_sp.scale(factor,about_point = about_point) - submob.move_source_to(new_sp.get_location()) - - # ambient_of = copy_func(submob.ambient_light.opacity_function) - # new_of = lambda r: ambient_of(r / factor) - # submob.ambient_light.change_opacity_function(new_of) - - # spotlight_of = copy_func(submob.ambient_light.opacity_function) - # new_of = lambda r: spotlight_of(r / factor) - # submob.spotlight.change_opacity_function(new_of) - - new_r = factor * submob.radius - submob.set_radius(new_r) - - new_r = factor * submob.ambient_light.radius - submob.ambient_light.radius = new_r - - new_r = factor * submob.spotlight.radius - submob.spotlight.radius = new_r - - submob.ambient_light.scale_about_point(factor, new_sp.get_center()) - submob.spotlight.scale_about_point(factor, new_sp.get_center()) - - - Transform.__init__(self,light_sources_mob,ls_target,**kwargs) - - - - - - - - - - - - - - - - - - -class IntroScene(PiCreatureScene): - - CONFIG = { - "rect_height" : 0.2, - "duration" : 0.5, - "eq_spacing" : 6 * MED_LARGE_BUFF - } - - def construct(self): - - randy = self.get_primary_pi_creature() - randy.scale(0.7).to_corner(DOWN+RIGHT) - - self.build_up_euler_sum() - self.build_up_sum_on_number_line() - self.show_pi_answer() - self.other_pi_formulas() - self.refocus_on_euler_sum() - - - - - - def build_up_euler_sum(self): - - self.euler_sum = TexMobject( - "1", "+", - "{1 \\over 4}", "+", - "{1 \\over 9}", "+", - "{1 \\over 16}", "+", - "{1 \\over 25}", "+", - "\\cdots", "=", - arg_separator = " \\, " - ) - - self.euler_sum.to_edge(UP) - self.euler_sum.shift(2*LEFT) - - terms = [1./n**2 for n in range(1,6)] - partial_results_values = np.cumsum(terms) - - self.play( - FadeIn(self.euler_sum[0], run_time = self.duration) - ) - - equals_sign = self.euler_sum.get_part_by_tex("=") - - self.partial_sum_decimal = DecimalNumber(partial_results_values[1], - num_decimal_places = 2) - self.partial_sum_decimal.next_to(equals_sign, RIGHT) - - - - for i in range(4): - - FadeIn(self.partial_sum_decimal, run_time = self.duration) - - if i == 0: - - self.play( - FadeIn(self.euler_sum[1], run_time = self.duration), - FadeIn(self.euler_sum[2], run_time = self.duration), - FadeIn(equals_sign, run_time = self.duration), - FadeIn(self.partial_sum_decimal, run_time = self.duration) - ) - - else: - self.play( - FadeIn(self.euler_sum[2*i+1], run_time = self.duration), - FadeIn(self.euler_sum[2*i+2], run_time = self.duration), - ChangeDecimalToValue( - self.partial_sum_decimal, - partial_results_values[i+1], - run_time = self.duration, - num_decimal_places = 6, - show_ellipsis = True, - position_update_func = lambda m: m.next_to(equals_sign, RIGHT) - ) - ) - - self.wait() - - self.q_marks = TextMobject("???").set_color(LIGHT_COLOR) - self.q_marks.move_to(self.partial_sum_decimal) - - self.play( - FadeIn(self.euler_sum[-3], run_time = self.duration), # + - FadeIn(self.euler_sum[-2], run_time = self.duration), # ... - ReplacementTransform(self.partial_sum_decimal, self.q_marks) - ) - - self.wait() - - - - def build_up_sum_on_number_line(self): - - self.number_line = NumberLine( - x_min = 0, - color = WHITE, - number_at_center = 1, - stroke_width = 1, - numbers_with_elongated_ticks = [0,1,2,3], - numbers_to_show = np.arange(0,5), - unit_size = 5, - tick_frequency = 0.2, - line_to_number_buff = MED_LARGE_BUFF - ).shift(LEFT) - - self.number_line_labels = self.number_line.get_number_mobjects() - self.play( - FadeIn(self.number_line), - FadeIn(self.number_line_labels) - ) - self.wait() - - # create slabs for series terms - - max_n1 = 10 - max_n2 = 100 - - terms = [0] + [1./(n**2) for n in range(1, max_n2 + 1)] - series_terms = np.cumsum(terms) - lines = VGroup() - self.rects = VGroup() - slab_colors = [YELLOW, BLUE] * (max_n2 / 2) - - for t1, t2, color in zip(series_terms, series_terms[1:], slab_colors): - line = Line(*list(map(self.number_line.number_to_point, [t1, t2]))) - rect = Rectangle() - rect.stroke_width = 0 - rect.fill_opacity = 1 - rect.set_color(color) - rect.stretch_to_fit_height( - self.rect_height, - ) - rect.stretch_to_fit_width(0.5 * line.get_width()) - rect.move_to(line) - - self.rects.add(rect) - lines.add(line) - - #self.rects.set_colors_by_radial_gradient(ORIGIN, 5, YELLOW, BLUE) - - self.little_euler_terms = VGroup() - for i in range(1,7): - if i == 1: - term = TexMobject("1", fill_color = slab_colors[i-1]) - else: - term = TexMobject("{1\over " + str(i**2) + "}", fill_color = slab_colors[i-1]) - term.scale(0.4) - self.little_euler_terms.add(term) - - - for i in range(5): - self.play( - GrowFromPoint(self.rects[i], self.euler_sum[2*i].get_center(), - run_time = 1) - ) - term = self.little_euler_terms.submobjects[i] - term.next_to(self.rects[i], UP) - self.play(FadeIn(term)) - - self.ellipsis = TexMobject("\cdots") - self.ellipsis.scale(0.4) - for i in range(5, max_n1): - - if i == 5: - self.ellipsis.next_to(self.rects[i+3], UP) - self.play( - FadeIn(self.ellipsis), - GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), - run_time = 0.5) - ) - else: - self.play( - GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), - run_time = 0.5) - ) - for i in range(max_n1, max_n2): - self.play( - GrowFromPoint(self.rects[i], self.euler_sum[10].get_center(), - run_time = 0.01) - ) - - self.wait() - - PI = TAU/2 - P = self.q_marks.get_center() + 0.5 * DOWN + 0.5 * LEFT - Q = self.rects[-1].get_center() + 0.2 * UP - self.arrow = CurvedArrow(P, Q, - angle = TAU/12, - color = YELLOW - ) - - self.play(FadeIn(self.arrow)) - - self.wait() - - - def show_pi_answer(self): - - self.pi_answer = TexMobject("{\\pi^2 \\over 6}").set_color(YELLOW) - self.pi_answer.move_to(self.partial_sum_decimal) - self.pi_answer.next_to(self.euler_sum[-1], RIGHT, buff = 1, - submobject_to_align = self.pi_answer[-2]) - self.play(ReplacementTransform(self.q_marks, self.pi_answer)) - - self.wait() - - - def other_pi_formulas(self): - - self.play( - FadeOut(self.rects), - FadeOut(self.number_line_labels), - FadeOut(self.number_line), - FadeOut(self.little_euler_terms), - FadeOut(self.ellipsis), - FadeOut(self.arrow) - ) - - self.leibniz_sum = TexMobject( - "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", - "=", "\quad\,\,{\\pi \\over 4}", arg_separator = " \\, ") - - self.wallis_product = TexMobject( - "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + - "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", - "=", "\quad\,\, {\\pi \\over 2}", arg_separator = " \\, ") - - self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN, - buff = 2, - submobject_to_align = self.leibniz_sum.get_part_by_tex("=") - ) - - self.wallis_product.next_to(self.leibniz_sum.get_part_by_tex("="), DOWN, - buff = 2, - submobject_to_align = self.wallis_product.get_part_by_tex("=") - ) - - - self.play( - Write(self.leibniz_sum) - ) - self.play( - Write(self.wallis_product) - ) - - - - def refocus_on_euler_sum(self): - - self.euler_sum.add(self.pi_answer) - - self.play( - FadeOut(self.leibniz_sum), - FadeOut(self.wallis_product), - ApplyMethod(self.euler_sum.shift, - ORIGIN + 2*UP - self.euler_sum.get_center()) - ) - - # focus on pi squared - pi_squared = self.euler_sum.get_part_by_tex("\\pi")[-3] - self.play( - WiggleOutThenIn(pi_squared, - scale_value = 4, - angle = 0.003 * TAU, - run_time = 2 - ) - ) - - - - # Morty thinks of a circle - - q_circle = Circle( - stroke_color = YELLOW, - fill_color = YELLOW, - fill_opacity = 0.25, - radius = 0.5, - stroke_width = 3.0 - ) - q_mark = TexMobject("?") - q_mark.next_to(q_circle) - - thought = Group(q_circle, q_mark) - q_mark.set_height(0.6 * q_circle.get_height()) - - self.look_at(pi_squared) - self.pi_creature_thinks(thought,target_mode = "confused", - bubble_kwargs = { "height" : 2.5, "width" : 5 }) - self.look_at(pi_squared) - - self.wait() - - - - - - - - - - - - - - - - - - - - -class FirstLighthouseScene(PiCreatureScene): - - def construct(self): - self.remove(self.get_primary_pi_creature()) - self.show_lighthouses_on_number_line() - - - - def show_lighthouses_on_number_line(self): - - self.number_line = NumberLine( - x_min = 0, - color = WHITE, - number_at_center = 1.6, - stroke_width = 1, - numbers_with_elongated_ticks = list(range(1,5)), - numbers_to_show = list(range(1,5)), - unit_size = 2, - tick_frequency = 0.2, - line_to_number_buff = LARGE_BUFF, - label_direction = UP, - ) - - self.number_line.label_direction = DOWN - - self.number_line_labels = self.number_line.get_number_mobjects() - self.add(self.number_line,self.number_line_labels) - self.wait() - - origin_point = self.number_line.number_to_point(0) - - self.default_pi_creature_class = Randolph - randy = self.get_primary_pi_creature() - - randy.scale(0.5) - randy.flip() - right_pupil = randy.pupils[1] - randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) - - - - light_indicator = LightIndicator(radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - color = LIGHT_COLOR) - light_indicator.reading.scale(0.8) - - bubble = ThoughtBubble(direction = RIGHT, - width = 2.5, height = 3.5) - bubble.next_to(randy,LEFT+UP) - bubble.add_content(light_indicator) - self.wait() - self.play( - randy.change, "wave_2", - ShowCreation(bubble), - FadeIn(light_indicator) - ) - - light_sources = [] - - - euler_sum_above = TexMobject("1", "+", "{1\over 4}", - "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "{1\over 36}") - - for (i,term) in zip(list(range(len(euler_sum_above))),euler_sum_above): - #horizontal alignment with tick marks - term.next_to(self.number_line.number_to_point(0.5*i+1),UP,buff = 2) - # vertical alignment with light indicator - old_y = term.get_center()[1] - new_y = light_indicator.get_center()[1] - term.shift([0,new_y - old_y,0]) - - - - for i in range(1,NUM_CONES+1): - light_source = LightSource( - opacity_function = inverse_quadratic(1,AMBIENT_SCALE,1), - num_levels = NUM_LEVELS, - radius = AMBIENT_RADIUS, - ) - point = self.number_line.number_to_point(i) - light_source.move_source_to(point) - light_sources.append(light_source) - - self.wait() - for ls in light_sources: - self.add_foreground_mobject(ls.lighthouse) - - light_indicator.set_intensity(0) - - intensities = np.cumsum(np.array([1./n**2 for n in range(1,NUM_CONES+1)])) - opacities = intensities * light_indicator.opacity_for_unit_intensity - - self.remove_foreground_mobjects(light_indicator) - - - # slowly switch on visible light cones and increment indicator - for (i,light_source) in zip(list(range(NUM_VISIBLE_CONES)),light_sources[:NUM_VISIBLE_CONES]): - indicator_start_time = 1.0 * (i+1) * SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size - indicator_stop_time = indicator_start_time + INDICATOR_UPDATE_TIME - indicator_rate_func = squish_rate_func( - smooth,indicator_start_time,indicator_stop_time) - self.play( - SwitchOn(light_source.ambient_light), - FadeIn(euler_sum_above[2*i], run_time = SWITCH_ON_RUN_TIME, - rate_func = indicator_rate_func), - FadeIn(euler_sum_above[2*i - 1], run_time = SWITCH_ON_RUN_TIME, - rate_func = indicator_rate_func), - # this last line *technically* fades in the last term, but it is off-screen - ChangeDecimalToValue(light_indicator.reading,intensities[i], - rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME), - ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i], - rate_func = indicator_rate_func, run_time = SWITCH_ON_RUN_TIME) - ) - - if i == 0: - self.wait() - # move a copy out of the thought bubble for comparison - light_indicator_copy = light_indicator.copy() - old_y = light_indicator_copy.get_center()[1] - new_y = self.number_line.get_center()[1] - self.play( - light_indicator_copy.shift,[0, new_y - old_y,0] - ) - - self.wait() - - self.wait() - - # quickly switch on off-screen light cones and increment indicator - for (i,light_source) in zip(list(range(NUM_VISIBLE_CONES,NUM_CONES)),light_sources[NUM_VISIBLE_CONES:NUM_CONES]): - indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.radius * self.number_line.unit_size - indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME - indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) - smooth,indicator_start_time,indicator_stop_time) - self.play( - SwitchOn(light_source.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), - ChangeDecimalToValue(light_indicator.reading,intensities[i-1], - rate_func = indicator_rate_func, run_time = FAST_SWITCH_ON_RUN_TIME), - ApplyMethod(light_indicator.foreground.set_fill,None,opacities[i-1]) - ) - - - # show limit value in light indicator and an equals sign - limit_reading = TexMobject("{\pi^2 \over 6}") - limit_reading.move_to(light_indicator.reading) - - equals_sign = TexMobject("=") - equals_sign.next_to(randy, UP) - old_y = equals_sign.get_center()[1] - new_y = euler_sum_above.get_center()[1] - equals_sign.shift([0,new_y - old_y,0]) - - self.play( - FadeOut(light_indicator.reading), - FadeIn(limit_reading), - FadeIn(equals_sign), - ) - - - - self.wait() - - - - - - - - - - - - - - - - - - - - - - - -class SingleLighthouseScene(PiCreatureScene): - - def construct(self): - - self.setup_elements() - self.setup_angle() # spotlight and angle msmt change when screen rotates - self.rotate_screen() - #self.morph_lighthouse_into_sun() - - - def setup_elements(self): - - self.remove(self.get_primary_pi_creature()) - - SCREEN_SIZE = 3.0 - DISTANCE_FROM_LIGHTHOUSE = 10.0 - source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0] - observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0] - - # Light source - - self.light_source = LightSource( - opacity_function = inverse_quadratic(1,SPOTLIGHT_SCALE,1), - num_levels = NUM_LEVELS, - radius = 10, - max_opacity_ambient = AMBIENT_FULL, - max_opacity_spotlight = SPOTLIGHT_FULL, - - ) - - self.light_source.move_source_to(source_point) - - - # Pi Creature - - morty = self.get_primary_pi_creature() - morty.scale(0.5) - morty.move_to(observer_point) - morty.shift(2*OUT) - self.add_foreground_mobject(morty) - - self.add(self.light_source.lighthouse) - - self.play( - SwitchOn(self.light_source.ambient_light) - ) - - # Screen - - self.screen = Rectangle( - width = 0.06, - height = 2, - mark_paths_closed = True, - fill_color = WHITE, - fill_opacity = 1.0, - stroke_width = 0.0 - ) - - self.screen.rotate(-TAU/6) - self.screen.next_to(morty,LEFT) - - self.light_source.set_screen(self.screen) - - # Animations - - self.play(FadeIn(self.screen)) - - #self.light_source.set_max_opacity_spotlight(0.001) - #self.play(SwitchOn(self.light_source.spotlight)) - - - self.wait() - - - - - # just calling .dim_ambient via ApplyMethod does not work, why? - dimmed_ambient_light = self.light_source.ambient_light.deepcopy() - dimmed_ambient_light.dimming(AMBIENT_DIMMED) - self.light_source.update_shadow() - - self.play( - FadeIn(self.light_source.shadow), - ) - self.add_foreground_mobject(self.light_source.shadow) - self.add_foreground_mobject(morty) - - self.play( - self.light_source.dim_ambient, - #Transform(self.light_source.ambient_light,dimmed_ambient_light), - #self.light_source.set_max_opacity_spotlight,1.0, - ) - self.play( - FadeIn(self.light_source.spotlight) - ) - - self.screen_tracker = ScreenTracker(self.light_source) - self.add(self.screen_tracker) - - self.wait() - - - - - def setup_angle(self): - - self.wait() - - - pointing_screen_at_source = Rotate(self.screen,TAU/6) - self.play(pointing_screen_at_source) - - # angle msmt (arc) - - arc_angle = self.light_source.spotlight.opening_angle() - # draw arc arrows to show the opening angle - self.angle_arc = Arc(radius = 5, start_angle = self.light_source.spotlight.start_angle(), - angle = self.light_source.spotlight.opening_angle(), tip_length = ARC_TIP_LENGTH) - #angle_arc.add_tip(at_start = True, at_end = True) - self.angle_arc.move_arc_center_to(self.light_source.get_source_point()) - - - # angle msmt (decimal number) - - self.angle_indicator = DecimalNumber(arc_angle / DEGREES, - num_decimal_places = 0, - unit = "^\\circ", - fill_opacity = 1.0, - fill_color = WHITE) - self.angle_indicator.next_to(self.angle_arc,RIGHT) - - angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES - self.angle_indicator.add_updater( - lambda d: d.set_value(angle_update_func()) - ) - self.add(self.angle_indicator) - - ca2 = AngleUpdater(self.angle_arc, self.light_source.spotlight) - self.add(ca2) - - self.play( - ShowCreation(self.angle_arc), - ShowCreation(self.angle_indicator) - ) - - self.wait() - - def rotate_screen(self): - - - - self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) - self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) - - self.play(Rotate(self.light_source.spotlight.screen, TAU/8)) - - self.wait() - - self.play(Rotate(self.light_source.spotlight.screen, -TAU/4)) - - self.wait() - - self.play(Rotate(self.light_source.spotlight.screen, TAU/4)) - -### The following is supposed to morph the scene into the Earth scene, -### but it doesn't work - - -class MorphIntoSunScene(PiCreatureScene): - def construct(self): - self.setup_elements() - self.morph_lighthouse_into_sun() - - def setup_elements(self): - self.remove(self.get_primary_pi_creature()) - - SCREEN_SIZE = 3.0 - DISTANCE_FROM_LIGHTHOUSE = 10.0 - source_point = [-DISTANCE_FROM_LIGHTHOUSE/2,0,0] - observer_point = [DISTANCE_FROM_LIGHTHOUSE/2,0,0] - - # Light source - - self.light_source = LightSource( - opacity_function = inverse_quadratic(1,SPOTLIGHT_SCALE,1), - num_levels = NUM_LEVELS, - radius = 10, - max_opacity_ambient = AMBIENT_FULL, - max_opacity_spotlight = SPOTLIGHT_FULL, - - ) - - self.light_source.move_source_to(source_point) - - - # Pi Creature - - morty = self.get_primary_pi_creature() - morty.scale(0.5) - morty.move_to(observer_point) - morty.shift(2*OUT) - self.add_foreground_mobject(morty) - - self.add(self.light_source.lighthouse,self.light_source.ambient_light) - - - # Screen - - self.screen = Rectangle( - width = 0.06, - height = 2, - mark_paths_closed = True, - fill_color = WHITE, - fill_opacity = 1.0, - stroke_width = 0.0 - ) - - self.screen.next_to(morty,LEFT) - - self.light_source.set_screen(self.screen) - self.add(self.screen,self.light_source.shadow) - - self.add_foreground_mobject(self.light_source.shadow) - self.add_foreground_mobject(morty) - self.light_source.dim_ambient - self.add(self.light_source.spotlight) - self.screen_tracker = ScreenTracker(self.light_source) - self.add(self.screen_tracker) - - self.wait() - - - def morph_lighthouse_into_sun(self): - - sun_position = np.array([-100,0,0]) - - - - - # Why does none of this change the opacity function??? - - self.sun = self.light_source.copy() - - self.sun.change_spotlight_opacity_function(lambda r: 0.1) - # self.sun.spotlight.opacity_function = lambda r: 0.1 - # for submob in self.sun.spotlight.submobjects: - # submob.set_fill(opacity = 0.1) - - #self.sun.move_source_to(sun_position) - #self.sun.set_radius(120) - - self.sun.spotlight.init_points() - - self.wait() - - self.play( - Transform(self.light_source,self.sun) - ) - - self.wait() - - - - - - - - - - - - - - - - - - - -class EarthScene(Scene): - - def construct(self): - - SCREEN_THICKNESS = 10 - - self.screen_height = 2.0 - self.brightness_rect_height = 1.0 - - # screen - self.screen = VMobject(stroke_color = WHITE, stroke_width = SCREEN_THICKNESS) - self.screen.set_points_as_corners([ - [0,-self.screen_height/2,0], - [0,self.screen_height/2,0] - ]) - - # Earth - - earth_center_x = 2 - earth_center = [earth_center_x,0,0] - earth_radius = 3 - earth = Circle(radius = earth_radius) - earth.add(self.screen) - earth.move_to(earth_center) - #self.remove(self.screen_tracker) - - theta0 = 70 * DEGREES - dtheta = 10 * DEGREES - theta1 = theta0 + dtheta - theta = (theta0 + theta1)/2 - - self.add_foreground_mobject(self.screen) - - # background Earth - background_earth = SVGMobject( - file_name = "earth", - width = 2 * earth_radius, - fill_color = BLUE, - ) - background_earth.move_to(earth_center) - # Morty - - morty = Mortimer().scale(0.5).next_to(self.screen, RIGHT, buff = 1.5) - self.add_foreground_mobject(morty) - - - # Light source (far-away Sun) - - sun_position = [-100,0,0] - - self.sun = LightSource( - opacity_function = lambda r : 0.5, - max_opacity_ambient = 0, - max_opacity_spotlight = 0.5, - num_levels = NUM_LEVELS, - radius = 150, - screen = self.screen - ) - - self.sun.move_source_to(sun_position) - - - # Add elements to scene - - self.add(self.sun,self.screen) - self.bring_to_back(self.sun.shadow) - screen_tracker = ScreenTracker(self.sun) - - self.add(screen_tracker) - - self.wait() - - self.play( - FadeIn(earth), - FadeIn(background_earth) - ) - self.add_foreground_mobject(earth) - self.add_foreground_mobject(self.screen) - - - # move screen onto Earth - screen_on_earth = self.screen.deepcopy() - screen_on_earth.rotate(-theta) - screen_on_earth.scale(0.3) - screen_on_earth.move_to(np.array([ - earth_center_x - earth_radius * np.cos(theta), - earth_radius * np.sin(theta), - 0])) - - polar_morty = morty.copy().scale(0.5).next_to(screen_on_earth,DOWN,buff = 0.5) - polar_morty.set_color(BLUE_C) - - self.play( - Transform(self.screen, screen_on_earth), - Transform(morty,polar_morty) - ) - - self.wait() - - - tropical_morty = polar_morty.copy() - tropical_morty.move_to(np.array([0,0,0])) - tropical_morty.set_color(RED) - - morty.target = tropical_morty - - # move screen to equator - - self.play( - Rotate(earth, theta0 + dtheta/2,run_time = 3), - MoveToTarget(morty, path_arc = 70*DEGREES, run_time = 3), - ) - - - - - - - - - - - - - - - - - - - - - - - - - -class ScreenShapingScene(ThreeDScene): - - - # TODO: Morph from Earth Scene into this scene - - def construct(self): - - #self.force_skipping() - self.setup_elements() - self.deform_screen() - self.create_brightness_rect() - self.slant_screen() - self.unslant_screen() - self.left_shift_screen_while_showing_light_indicator() - self.add_distance_arrow() - self.right_shift_screen_while_showing_light_indicator_and_distance_arrow() - self.left_shift_again() - #self.revert_to_original_skipping_status() - - self.morph_into_3d() - self.prove_inverse_square_law() - - - def setup_elements(self): - - SCREEN_THICKNESS = 10 - - self.screen_height = 1.0 - self.brightness_rect_height = 1.0 - - # screen - self.screen = Line([3,-self.screen_height/2,0],[3,self.screen_height/2,0], - path_arc = 0, num_arc_anchors = 10) - - # light source - self.light_source = LightSource( - opacity_function = inverse_quadratic(1,5,1), - num_levels = NUM_LEVELS, - radius = 10, - max_opacity = 0.2 - #screen = self.screen - ) - self.light_source.set_max_opacity_spotlight(0.2) - - self.light_source.set_screen(self.screen) - self.light_source.move_source_to([-5,0,0]) - - # abbreviations - self.ambient_light = self.light_source.ambient_light - self.spotlight = self.light_source.spotlight - self.lighthouse = self.light_source.lighthouse - - - #self.add_foreground_mobject(self.light_source.shadow) - - # Morty - self.morty = Mortimer().scale(0.3).next_to(self.screen, RIGHT, buff = 0.5) - - # Add everything to the scene - self.add(self.lighthouse) - - self.wait() - self.play(FadeIn(self.screen)) - self.wait() - - self.add_foreground_mobject(self.screen) - self.add_foreground_mobject(self.morty) - - self.play(SwitchOn(self.ambient_light)) - - self.play( - SwitchOn(self.spotlight), - self.light_source.dim_ambient - ) - - screen_tracker = ScreenTracker(self.light_source) - self.add(screen_tracker) - - - self.wait() - - - - def deform_screen(self): - - self.wait() - - self.play(ApplyMethod(self.screen.set_path_arc, 45 * DEGREES)) - self.play(ApplyMethod(self.screen.set_path_arc, -90 * DEGREES)) - self.play(ApplyMethod(self.screen.set_path_arc, 0)) - - - - - def create_brightness_rect(self): - - # in preparation for the slanting, create a rectangle that shows the brightness - - # a rect a zero width overlaying the screen - # so we can morph it into the brightness rect above - brightness_rect0 = Rectangle(width = 0, - height = self.screen_height).move_to(self.screen.get_center()) - self.add_foreground_mobject(brightness_rect0) - - self.brightness_rect = Rectangle(width = self.brightness_rect_height, - height = self.brightness_rect_height, fill_color = YELLOW, fill_opacity = 0.5) - - self.brightness_rect.next_to(self.screen, UP, buff = 1) - - self.play( - ReplacementTransform(brightness_rect0,self.brightness_rect) - ) - - self.unslanted_screen = self.screen.deepcopy() - self.unslanted_brightness_rect = self.brightness_rect.copy() - # for unslanting the screen later - - - def slant_screen(self): - - SLANTING_AMOUNT = 0.1 - - lower_screen_point, upper_screen_point = self.screen.get_start_and_end() - - lower_slanted_screen_point = interpolate( - lower_screen_point, self.spotlight.get_source_point(), SLANTING_AMOUNT - ) - upper_slanted_screen_point = interpolate( - upper_screen_point, self.spotlight.get_source_point(), -SLANTING_AMOUNT - ) - - self.slanted_brightness_rect = self.brightness_rect.copy() - self.slanted_brightness_rect.width *= 2 - self.slanted_brightness_rect.init_points() - self.slanted_brightness_rect.set_fill(opacity = 0.25) - - self.slanted_screen = Line(lower_slanted_screen_point,upper_slanted_screen_point, - path_arc = 0, num_arc_anchors = 10) - self.slanted_brightness_rect.move_to(self.brightness_rect.get_center()) - - self.play( - Transform(self.screen,self.slanted_screen), - Transform(self.brightness_rect,self.slanted_brightness_rect), - ) - - - - def unslant_screen(self): - - self.wait() - self.play( - Transform(self.screen,self.unslanted_screen), - Transform(self.brightness_rect,self.unslanted_brightness_rect), - ) - - - - - def left_shift_screen_while_showing_light_indicator(self): - - # Scene 5: constant screen size, changing opening angle - - OPACITY_FOR_UNIT_INTENSITY = 1 - - # let's use an actual light indicator instead of just rects - - self.indicator_intensity = 0.25 - indicator_height = 1.25 * self.screen_height - - self.indicator = LightIndicator(radius = indicator_height/2, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - color = LIGHT_COLOR, - precision = 2) - self.indicator.set_intensity(self.indicator_intensity) - - self.indicator.move_to(self.brightness_rect.get_center()) - - self.play( - FadeOut(self.brightness_rect), - FadeIn(self.indicator) - ) - - # Here some digits of the indicator disappear... - - self.add_foreground_mobject(self.indicator.reading) - - - self.unit_indicator_intensity = 1.0 # intensity at distance 1 - # (where we are about to move to) - - self.left_shift = (self.screen.get_center()[0] - self.spotlight.get_source_point()[0])/2 - - self.play( - self.screen.shift,[-self.left_shift,0,0], - self.morty.shift,[-self.left_shift,0,0], - self.indicator.shift,[-self.left_shift,0,0], - self.indicator.set_intensity,self.unit_indicator_intensity, - ) - - - - def add_distance_arrow(self): - - # distance arrow (length 1) - left_x = self.spotlight.get_source_point()[0] - right_x = self.screen.get_center()[0] - arrow_y = -2 - arrow1 = Arrow([left_x,arrow_y,0],[right_x,arrow_y,0]) - arrow2 = Arrow([right_x,arrow_y,0],[left_x,arrow_y,0]) - arrow1.set_fill(color = WHITE) - arrow2.set_fill(color = WHITE) - distance_decimal = Integer(1).next_to(arrow1,DOWN) - self.arrow = VGroup(arrow1, arrow2,distance_decimal) - self.add(self.arrow) - - - # distance arrow (length 2) - # will be morphed into - self.distance_to_source = right_x - left_x - new_right_x = left_x + 2 * self.distance_to_source - new_arrow1 = Arrow([left_x,arrow_y,0],[new_right_x,arrow_y,0]) - new_arrow2 = Arrow([new_right_x,arrow_y,0],[left_x,arrow_y,0]) - new_arrow1.set_fill(color = WHITE) - new_arrow2.set_fill(color = WHITE) - new_distance_decimal = Integer(2).next_to(new_arrow1,DOWN) - self.new_arrow = VGroup(new_arrow1, new_arrow2, new_distance_decimal) - # don't add it yet - - - def right_shift_screen_while_showing_light_indicator_and_distance_arrow(self): - - self.wait() - - self.play( - ReplacementTransform(self.arrow,self.new_arrow), - ApplyMethod(self.screen.shift,[self.distance_to_source,0,0]), - ApplyMethod(self.indicator.shift,[self.left_shift,0,0]), - - ApplyMethod(self.indicator.set_intensity,self.indicator_intensity), - # this should trigger ChangingDecimal, but it doesn't - # maybe bc it's an anim within an anim? - - ApplyMethod(self.morty.shift,[self.distance_to_source,0,0]), - ) - - - def left_shift_again(self): - - self.wait() - - self.play( - ReplacementTransform(self.new_arrow,self.arrow), - ApplyMethod(self.screen.shift,[-self.distance_to_source,0,0]), - #ApplyMethod(self.indicator.shift,[-self.left_shift,0,0]), - ApplyMethod(self.indicator.set_intensity,self.unit_indicator_intensity), - ApplyMethod(self.morty.shift,[-self.distance_to_source,0,0]), - ) - - def morph_into_3d(self): - - - self.play(FadeOut(self.morty)) - - axes = ThreeDAxes() - self.add(axes) - - phi0 = self.camera.get_phi() # default is 0 degs - theta0 = self.camera.get_theta() # default is -90 degs - distance0 = self.camera.get_distance() - - phi1 = 60 * DEGREES # angle from zenith (0 to 180) - theta1 = -135 * DEGREES # azimuth (0 to 360) - distance1 = distance0 - target_point = self.camera.get_spherical_coords(phi1, theta1, distance1) - - dphi = phi1 - phi0 - dtheta = theta1 - theta0 - - camera_target_point = target_point # self.camera.get_spherical_coords(45 * DEGREES, -60 * DEGREES) - projection_direction = self.camera.spherical_coords_to_point(phi1,theta1, 1) - - new_screen0 = Rectangle(height = self.screen_height, - width = 0.1, stroke_color = RED, fill_color = RED, fill_opacity = 1) - new_screen0.rotate(TAU/4,axis = DOWN) - new_screen0.move_to(self.screen.get_center()) - self.add(new_screen0) - self.remove(self.screen) - self.light_source.set_screen(new_screen0) - - self.light_source.set_camera(self.camera) - - - new_screen = Rectangle(height = self.screen_height, - width = self.screen_height, stroke_color = RED, fill_color = RED, fill_opacity = 1) - new_screen.rotate(TAU/4,axis = DOWN) - new_screen.move_to(self.screen.get_center()) - - self.add_foreground_mobject(self.ambient_light) - self.add_foreground_mobject(self.spotlight) - self.add_foreground_mobject(self.light_source.shadow) - - self.play( - ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), - - ) - self.remove(self.spotlight) - - self.play(Transform(new_screen0,new_screen)) - - self.wait() - - self.unit_screen = new_screen0 # better name - - - - def prove_inverse_square_law(self): - - def orientate(mob): - mob.move_to(self.unit_screen) - mob.rotate(TAU/4, axis = LEFT) - mob.rotate(TAU/4, axis = OUT) - mob.rotate(TAU/2, axis = LEFT) - return mob - - unit_screen_copy = self.unit_screen.copy() - fourfold_screen = self.unit_screen.copy() - fourfold_screen.scale(2,about_point = self.light_source.get_source_point()) - - self.remove(self.spotlight) - - - reading1 = TexMobject("1") - orientate(reading1) - - self.play(FadeIn(reading1)) - self.wait() - self.play(FadeOut(reading1)) - - - self.play( - Transform(self.unit_screen, fourfold_screen) - ) - - reading21 = TexMobject("{1\over 4}").scale(0.8) - orientate(reading21) - reading22 = reading21.deepcopy() - reading23 = reading21.deepcopy() - reading24 = reading21.deepcopy() - reading21.shift(0.5*OUT + 0.5*UP) - reading22.shift(0.5*OUT + 0.5*DOWN) - reading23.shift(0.5*IN + 0.5*UP) - reading24.shift(0.5*IN + 0.5*DOWN) - - - corners = fourfold_screen.get_anchors() - midpoint1 = (corners[0] + corners[1])/2 - midpoint2 = (corners[1] + corners[2])/2 - midpoint3 = (corners[2] + corners[3])/2 - midpoint4 = (corners[3] + corners[0])/2 - midline1 = Line(midpoint1, midpoint3) - midline2 = Line(midpoint2, midpoint4) - - self.play( - ShowCreation(midline1), - ShowCreation(midline2) - ) - - self.play( - FadeIn(reading21), - FadeIn(reading22), - FadeIn(reading23), - FadeIn(reading24), - ) - - self.wait() - - self.play( - FadeOut(reading21), - FadeOut(reading22), - FadeOut(reading23), - FadeOut(reading24), - FadeOut(midline1), - FadeOut(midline2) - ) - - ninefold_screen = unit_screen_copy.copy() - ninefold_screen.scale(3,about_point = self.light_source.get_source_point()) - - self.play( - Transform(self.unit_screen, ninefold_screen) - ) - - reading31 = TexMobject("{1\over 9}").scale(0.8) - orientate(reading31) - reading32 = reading31.deepcopy() - reading33 = reading31.deepcopy() - reading34 = reading31.deepcopy() - reading35 = reading31.deepcopy() - reading36 = reading31.deepcopy() - reading37 = reading31.deepcopy() - reading38 = reading31.deepcopy() - reading39 = reading31.deepcopy() - reading31.shift(IN + UP) - reading32.shift(IN) - reading33.shift(IN + DOWN) - reading34.shift(UP) - reading35.shift(ORIGIN) - reading36.shift(DOWN) - reading37.shift(OUT + UP) - reading38.shift(OUT) - reading39.shift(OUT + DOWN) - - corners = ninefold_screen.get_anchors() - midpoint11 = (2*corners[0] + corners[1])/3 - midpoint12 = (corners[0] + 2*corners[1])/3 - midpoint21 = (2*corners[1] + corners[2])/3 - midpoint22 = (corners[1] + 2*corners[2])/3 - midpoint31 = (2*corners[2] + corners[3])/3 - midpoint32 = (corners[2] + 2*corners[3])/3 - midpoint41 = (2*corners[3] + corners[0])/3 - midpoint42 = (corners[3] + 2*corners[0])/3 - midline11 = Line(midpoint11, midpoint32) - midline12 = Line(midpoint12, midpoint31) - midline21 = Line(midpoint21, midpoint42) - midline22 = Line(midpoint22, midpoint41) - - self.play( - ShowCreation(midline11), - ShowCreation(midline12), - ShowCreation(midline21), - ShowCreation(midline22), - ) - - self.play( - FadeIn(reading31), - FadeIn(reading32), - FadeIn(reading33), - FadeIn(reading34), - FadeIn(reading35), - FadeIn(reading36), - FadeIn(reading37), - FadeIn(reading38), - FadeIn(reading39), - ) - - - - -class IndicatorScalingScene(Scene): - - def construct(self): - - unit_intensity = 0.6 - - indicator1 = LightIndicator(show_reading = False, color = LIGHT_COLOR) - indicator1.set_intensity(unit_intensity) - reading1 = TexMobject("1") - reading1.move_to(indicator1) - - - indicator2 = LightIndicator(show_reading = False, color = LIGHT_COLOR) - indicator2.shift(2*RIGHT) - indicator2.set_intensity(unit_intensity/4) - reading2 = TexMobject("{1\over 4}").scale(0.8) - reading2.move_to(indicator2) - - indicator3 = LightIndicator(show_reading = False, color = LIGHT_COLOR) - indicator3.shift(4*RIGHT) - indicator3.set_intensity(unit_intensity/9) - reading3 = TexMobject("{1\over 9}").scale(0.8) - reading3.move_to(indicator3) - - - self.play(FadeIn(indicator1)) - self.play(FadeIn(reading1)) - self.wait() - self.play(FadeOut(reading1)) - self.play(Transform(indicator1, indicator2)) - self.play(FadeIn(reading2)) - self.wait() - self.play(FadeOut(reading2)) - self.play(Transform(indicator1, indicator3)) - self.play(FadeIn(reading3)) - self.wait() - - - - - - -class BackToEulerSumScene(PiCreatureScene): - - - def construct(self): - self.remove(self.get_primary_pi_creature()) - - NUM_CONES = 7 - NUM_VISIBLE_CONES = 6 - INDICATOR_RADIUS = 0.5 - OPACITY_FOR_UNIT_INTENSITY = 1.0 - - self.number_line = NumberLine( - x_min = 0, - color = WHITE, - number_at_center = 1.6, - stroke_width = 1, - numbers_with_elongated_ticks = list(range(1,5)), - numbers_to_show = list(range(1,5)), - unit_size = 2, - tick_frequency = 0.2, - line_to_number_buff = LARGE_BUFF, - label_direction = UP, - ) - - self.number_line.label_direction = DOWN - #self.number_line.shift(3*UP) - - self.number_line_labels = self.number_line.get_number_mobjects() - self.add(self.number_line,self.number_line_labels) - self.wait() - - origin_point = self.number_line.number_to_point(0) - - self.default_pi_creature_class = Randolph - randy = self.get_primary_pi_creature() - - randy.scale(0.5) - randy.flip() - right_pupil = randy.pupils[1] - randy.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) - - randy_copy = randy.copy() - randy_copy.target = randy.copy().shift(DOWN) - - - - bubble = ThoughtBubble(direction = RIGHT, - width = 4, height = 3, - file_name = "Bubbles_thought.svg") - bubble.next_to(randy,LEFT+UP) - bubble.set_fill(color = BLACK, opacity = 1) - - self.play( - randy.change, "wave_2", - ShowCreation(bubble), - ) - - - euler_sum = TexMobject("1", "+", "{1\over 4}", - "+", "{1\over 9}", "+", "{1\over 16}", "+", "{1\over 25}", "+", "\cdots", " ") - # the last entry is a dummy element which makes looping easier - # used just for putting the fractions into the light indicators - - intensities = np.array([1./(n+1)**2 for n in range(NUM_CONES)]) - opacities = intensities * OPACITY_FOR_UNIT_INTENSITY - - # repeat: - - # fade in lighthouse - # switch on / fade in ambient light - # show creation / write light indicator - # move indicator onto origin - # while morphing and dimming - # move indicator into thought bubble - # while indicators already inside shift to the back - # and while term appears in the series below - - point = self.number_line.number_to_point(1) - v = point - self.number_line.number_to_point(0) - light_source = LightSource() - light_source.move_source_to(point) - #light_source.ambient_light.move_source_to(point) - #light_source.lighthouse.move_to(point) - - self.play(FadeIn(light_source.lighthouse)) - self.play(SwitchOn(light_source.ambient_light)) - - - # create an indicator that will move along the number line - indicator = LightIndicator(color = LIGHT_COLOR, - radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - show_reading = False - ) - indicator_reading = euler_sum[0] - indicator_reading.set_height(0.5 * indicator.get_height()) - indicator_reading.move_to(indicator.get_center()) - indicator.add(indicator_reading) - indicator.tex_reading = indicator_reading - # the TeX reading is too bright at full intensity - indicator.tex_reading.set_fill(color = BLACK) - indicator.foreground.set_fill(None,opacities[0]) - - - indicator.move_to(point) - indicator.set_intensity(intensities[0]) - - self.play(FadeIn(indicator)) - self.add_foreground_mobject(indicator) - - collection_point = np.array([-6.,2.,0.]) - left_shift = 0.2*LEFT - collected_indicators = Mobject() - - - for i in range(2, NUM_VISIBLE_CONES + 1): - - previous_point = self.number_line.number_to_point(i - 1) - point = self.number_line.number_to_point(i) - - - v = point - previous_point - #print v - # Create and position the target indicator (next on number line). - indicator_target = indicator.deepcopy() - indicator_target.shift(v) - - - # Here we make a copy that will move into the thought bubble. - bubble_indicator = indicator.deepcopy() - # And its target - bubble_indicator_target = bubble_indicator.deepcopy() - bubble_indicator_target.set_intensity(intensities[i - 2]) - - # give the target the appropriate reading - euler_sum[2*i-4].move_to(bubble_indicator_target) - bubble_indicator_target.remove(bubble_indicator_target.tex_reading) - bubble_indicator_target.tex_reading = euler_sum[2*i-4].copy() - bubble_indicator_target.add(bubble_indicator_target.tex_reading) - # center it in the indicator - - if bubble_indicator_target.tex_reading.get_tex_string() != "1": - bubble_indicator_target.tex_reading.set_height(0.8*indicator.get_height()) - # the target is less bright, possibly switch to a white text color - if bubble_indicator_target.intensity < 0.7: - bubble_indicator.tex_reading.set_fill(color = WHITE) - - # position the target in the thought bubble - bubble_indicator_target.move_to(collection_point) - - - self.add_foreground_mobject(bubble_indicator) - - - self.wait() - - self.play( - Transform(bubble_indicator,bubble_indicator_target), - collected_indicators.shift,left_shift, - ) - - collected_indicators.add(bubble_indicator) - - new_light = light_source.deepcopy() - w = new_light.get_source_point() - new_light.move_source_to(w + (i-2)*v) - w2 = new_light.get_source_point() - - self.add(new_light.lighthouse) - self.play( - Transform(indicator,indicator_target), - new_light.lighthouse.shift,v, - ) - new_light.move_source_to(w + (i-1)*v) - new_light.lighthouse.move_to(w + (i-1)*v) - - self.play(SwitchOn(new_light.ambient_light), - ) - - - - - # quickly switch on off-screen light cones - for i in range(NUM_VISIBLE_CONES,NUM_CONES): - indicator_start_time = 0.5 * (i+1) * FAST_SWITCH_ON_RUN_TIME/light_source.ambient_light.radius * self.number_line.unit_size - indicator_stop_time = indicator_start_time + FAST_INDICATOR_UPDATE_TIME - indicator_rate_func = squish_rate_func(#smooth, 0.8, 0.9) - smooth,indicator_start_time,indicator_stop_time) - ls = LightSource() - point = point = self.number_line.number_to_point(i) - ls.move_source_to(point) - self.play( - SwitchOn(ls.ambient_light, run_time = FAST_SWITCH_ON_RUN_TIME), - ) - - # and morph indicator stack into limit value - - sum_indicator = LightIndicator(color = LIGHT_COLOR, - radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - show_reading = False - ) - sum_indicator.set_intensity(intensities[0] * np.pi**2/6) - sum_indicator_reading = TexMobject("{\pi^2 \over 6}") - sum_indicator_reading.set_fill(color = BLACK) - sum_indicator_reading.set_height(0.8 * sum_indicator.get_height()) - sum_indicator.add(sum_indicator_reading) - sum_indicator.move_to(collection_point) - - self.play( - FadeOut(collected_indicators), - FadeIn(sum_indicator) - ) - - - - self.wait() - - - - - -class TwoLightSourcesScene(PiCreatureScene): - - def construct(self): - - MAX_OPACITY = 0.4 - INDICATOR_RADIUS = 0.6 - OPACITY_FOR_UNIT_INTENSITY = 0.5 - - morty = self.get_primary_pi_creature() - morty.scale(0.3).flip() - right_pupil = morty.pupils[1] - morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) - - horizontal = VMobject(stroke_width = 1) - horizontal.set_points_as_corners([C,A]) - vertical = VMobject(stroke_width = 1) - vertical.set_points_as_corners([C,B]) - - self.play( - ShowCreation(horizontal), - ShowCreation(vertical) - ) - - indicator = LightIndicator(color = LIGHT_COLOR, - radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - show_reading = True, - precision = 2 - ) - - indicator.next_to(morty,LEFT) - - self.play( - Write(indicator) - ) - - - ls1 = LightSource(radius = 20, num_levels = 50) - ls2 = ls1.deepcopy() - ls1.move_source_to(A) - ls2.move_source_to(B) - - self.play( - FadeIn(ls1.lighthouse), - FadeIn(ls2.lighthouse), - SwitchOn(ls1.ambient_light), - SwitchOn(ls2.ambient_light) - ) - - distance1 = get_norm(C - ls1.get_source_point()) - intensity = ls1.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity - distance2 = get_norm(C - ls2.get_source_point()) - intensity += ls2.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity - - self.play( - UpdateLightIndicator(indicator,intensity) - ) - - self.wait() - - ls3 = ls1.deepcopy() - ls3.move_to(np.array([6,3.5,0])) - - new_indicator = indicator.copy() - new_indicator.light_source = ls3 - new_indicator.measurement_point = C - self.add(new_indicator) - self.play( - indicator.shift, 2 * UP - ) - - - - #intensity = intensity_for_light_source(ls3) - - - self.play( - SwitchOff(ls1.ambient_light), - #FadeOut(ls1.lighthouse), - SwitchOff(ls2.ambient_light), - #FadeOut(ls2.lighthouse), - UpdateLightIndicator(new_indicator,0.0) - ) - - # create a *continual* animation for the replacement source - updater = ContinualLightIndicatorUpdate(new_indicator) - self.add(updater) - - self.play( - SwitchOn(ls3.ambient_light), - FadeIn(ls3.lighthouse), - - ) - - self.wait() - - # move the light source around - # TODO: moving along a path arc - - location = np.array([-3,-2.,0.]) - self.play(ls3.move_source_to,location) - location = np.array([6.,1.,0.]) - self.play(ls3.move_source_to,location) - location = np.array([5.,2.,0.]) - self.play(ls3.move_source_to,location) - closer_location = interpolate(location, C, 0.5) - self.play(ls3.move_source_to,closer_location) - self.play(ls3.move_source_to,location) - - # maybe move in a circle around C using a loop? - - - - self.play(ls3.move_source_to,H) - - - - # draw lines to complete the geometric picture - # and label the lengths - - line_a = VMobject() - line_a.set_points_as_corners([B,C]) - line_b = VMobject() - line_b.set_points_as_corners([A,C]) - line_c = VMobject() - line_c.set_points_as_corners([A,B]) - line_h = VMobject() - line_h.set_points_as_corners([H,C]) - - label_a = TexMobject("a") - label_a.next_to(line_a, LEFT, buff = 0.5) - label_b = TexMobject("b") - label_b.next_to(line_b, DOWN, buff = 0.5) - label_h = TexMobject("h") - label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) - - self.play( - ShowCreation(line_a), - Write(label_a) - ) - - self.play( - ShowCreation(line_b), - Write(label_b) - ) - - self.play( - ShowCreation(line_c), - ) - - self.play( - ShowCreation(line_h), - Write(label_h) - ) - - - # state the IPT - theorem_location = np.array([3.,2.,0.]) - theorem = TexMobject("{1\over a^2} + {1\over b^2} = {1\over h^2}") - theorem_name = TextMobject("Inverse Pythagorean Theorem") - buffer = 1.2 - theorem_box = Rectangle(width = buffer*theorem.get_width(), - height = buffer*theorem.get_height()) - - theorem.move_to(theorem_location) - theorem_box.move_to(theorem_location) - theorem_name.next_to(theorem_box,UP) - - self.play( - Write(theorem), - ) - - self.play( - ShowCreation(theorem_box), - Write(theorem_name), - ) - - - -class IPTScene1(PiCreatureScene): - - def construct(self): - - show_detail = True - - SCREEN_SCALE = 0.1 - SCREEN_THICKNESS = 0.2 - - - # use the following for the zoomed inset - if show_detail: - self.camera.frame_shape = (0.02 * FRAME_HEIGHT, 0.02 * FRAME_WIDTH) - self.camera.frame_center = C - SCREEN_SCALE = 0.01 - SCREEN_THICKNESS = 0.02 - - - - - - morty = self.get_primary_pi_creature() - self.remove(morty) - morty.scale(0.3).flip() - right_pupil = morty.pupils[1] - morty.next_to(C, LEFT, buff = 0, submobject_to_align = right_pupil) - - if not show_detail: - self.add_foreground_mobject(morty) - - stroke_width = 6 - line_a = Line(B,C,stroke_width = stroke_width) - line_b = Line(A,C,stroke_width = stroke_width) - line_c = Line(A,B,stroke_width = stroke_width) - line_h = Line(C,H,stroke_width = stroke_width) - - length_a = line_a.get_length() - length_b = line_b.get_length() - length_c = line_c.get_length() - length_h = line_h.get_length() - - label_a = TexMobject("a") - label_a.next_to(line_a, LEFT, buff = 0.5) - label_b = TexMobject("b") - label_b.next_to(line_b, DOWN, buff = 0.5) - label_h = TexMobject("h") - label_h.next_to(line_h.get_center(), RIGHT, buff = 0.5) - - self.add_foreground_mobject(line_a) - self.add_foreground_mobject(line_b) - self.add_foreground_mobject(line_c) - self.add_foreground_mobject(line_h) - self.add_foreground_mobject(label_a) - self.add_foreground_mobject(label_b) - self.add_foreground_mobject(label_h) - - if not show_detail: - self.add_foreground_mobject(morty) - - ls1 = LightSource(radius = 10) - ls1.move_source_to(B) - - self.add(ls1.lighthouse) - - if not show_detail: - self.play( - SwitchOn(ls1.ambient_light) - ) - - self.wait() - - # adding the first screen - - screen_width_a = SCREEN_SCALE * length_a - screen_width_b = SCREEN_SCALE * length_b - screen_width_ap = screen_width_a * length_a / length_c - screen_width_bp = screen_width_b * length_b / length_c - screen_width_c = SCREEN_SCALE * length_c - - screen_thickness_a = SCREEN_THICKNESS - screen_thickness_b = SCREEN_THICKNESS - - screen1 = Rectangle(width = screen_width_b, - height = screen_thickness_b, - stroke_width = 0, - fill_opacity = 1.0) - screen1.move_to(C + screen_width_b/2 * RIGHT + screen_thickness_b/2 * DOWN) - - if not show_detail: - self.add_foreground_mobject(morty) - - self.play( - FadeIn(screen1) - ) - self.add_foreground_mobject(screen1) - - ls1.set_screen(screen1) - screen_tracker = ScreenTracker(ls1) - self.add(screen_tracker) - #self.add(ls1.shadow) - - if not show_detail: - self.play( - SwitchOn(ls1.ambient_light) - ) - - self.play( - SwitchOn(ls1.spotlight), - SwitchOff(ls1.ambient_light) - ) - - - - # now move the light source to the height point - # while shifting scaling the screen - screen1p = screen1.deepcopy() - screen1pp = screen1.deepcopy() - #self.add(screen1p) - angle = np.arccos(length_b / length_c) - - screen1p.stretch_to_fit_width(screen_width_bp) - screen1p.move_to(C + (screen_width_b - screen_width_bp/2) * RIGHT + SCREEN_THICKNESS/2 * DOWN) - screen1p.rotate(-angle, about_point = C + screen_width_b * RIGHT) - - - self.play( - ls1.move_source_to,H, - Transform(screen1,screen1p) - ) - - # add and move the second light source and screen - ls2 = ls1.deepcopy() - ls2.move_source_to(A) - screen2 = Rectangle(width = screen_width_a, - height = screen_thickness_a, - stroke_width = 0, - fill_opacity = 1.0) - screen2.rotate(-TAU/4) - screen2.move_to(C + screen_width_a/2 * UP + screen_thickness_a/2 * LEFT) - - self.play( - FadeIn(screen2) - ) - self.add_foreground_mobject(screen2) - - if not show_detail: - self.add_foreground_mobject(morty) - - # the same scene adding sequence as before - ls2.set_screen(screen2) - screen_tracker2 = ScreenTracker(ls2) - self.add(screen_tracker2) - - if not show_detail: - self.play( - SwitchOn(ls2.ambient_light) - ) - - self.wait() - - self.play( - SwitchOn(ls2.spotlight), - SwitchOff(ls2.ambient_light) - ) - - - - # now move the light source to the height point - # while shifting scaling the screen - screen2p = screen2.deepcopy() - screen2pp = screen2.deepcopy() - angle = np.arccos(length_a / length_c) - screen2p.stretch_to_fit_height(screen_width_ap) - screen2p.move_to(C + (screen_width_a - screen_width_ap/2) * UP + screen_thickness_a/2 * LEFT) - screen2p.rotate(angle, about_point = C + screen_width_a * UP) - # we can reuse the translation vector - # screen2p.shift(vector) - - self.play( - ls2.move_source_to,H, - SwitchOff(ls1.ambient_light), - Transform(screen2,screen2p) - ) - - # now transform both screens back - self.play( - Transform(screen1, screen1pp), - Transform(screen2, screen2pp), - ) - - - -class IPTScene2(Scene): - - def construct(self): - - intensity1 = 0.3 - intensity2 = 0.2 - formula_scale = 01.2 - indy_radius = 1 - - indy1 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) - indy1.set_intensity(intensity1) - reading1 = TexMobject("{1\over a^2}").scale(formula_scale).move_to(indy1) - indy1.add(reading1) - - indy2 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) - indy2.set_intensity(intensity2) - reading2 = TexMobject("{1\over b^2}").scale(formula_scale).move_to(indy2) - indy2.add(reading2) - - indy3 = LightIndicator(color = LIGHT_COLOR, show_reading = False, radius = indy_radius) - indy3.set_intensity(intensity1 + intensity2) - reading3 = TexMobject("{1\over h^2}").scale(formula_scale).move_to(indy3) - indy3.add(reading3) - - plus_sign = TexMobject("+").scale(formula_scale) - equals_sign = TexMobject("=").scale(formula_scale) - - plus_sign.next_to(indy1, RIGHT) - indy2.next_to(plus_sign, RIGHT) - equals_sign.next_to(indy2, RIGHT) - indy3.next_to(equals_sign, RIGHT) - - - formula = VGroup( - indy1, plus_sign, indy2, equals_sign, indy3 - ) - - formula.move_to(ORIGIN) - - self.play(FadeIn(indy1)) - self.play(FadeIn(plus_sign), FadeIn(indy2)) - self.play(FadeIn(equals_sign), FadeIn(indy3)) - - buffer = 1.5 - box = Rectangle(width = formula.get_width() * buffer, - height = formula.get_height() * buffer) - box.move_to(formula) - text = TextMobject("Inverse Pythagorean Theorem").scale(formula_scale) - text.next_to(box,UP) - self.play(ShowCreation(box),Write(text)) - - - - - - - - -class InscribedAngleScene(ThreeDScene): - - - - - def construct(self): - - BASELINE_YPOS = -2.5 - OBSERVER_POINT = [0,BASELINE_YPOS,0] - LAKE0_RADIUS = 1.5 - INDICATOR_RADIUS = 0.6 - TICK_SIZE = 0.5 - LIGHTHOUSE_HEIGHT = 0.3 - LAKE_COLOR = BLUE - LAKE_OPACITY = 0.15 - LAKE_STROKE_WIDTH = 5.0 - LAKE_STROKE_COLOR = BLUE - TEX_SCALE = 0.8 - DOT_COLOR = BLUE - - LIGHT_MAX_INT = 1 - LIGHT_SCALE = 5 - LIGHT_CUTOFF = 1 - - self.cumulated_zoom_factor = 1 - - - def zoom_out_scene(factor): - - - phi0 = self.camera.get_phi() # default is 0 degs - theta0 = self.camera.get_theta() # default is -90 degs - distance0 = self.camera.get_distance() - - distance1 = 2 * distance0 - camera_target_point = self.camera.get_spherical_coords(phi0, theta0, distance1) - - self.play( - ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), - self.zoomable_mobs.shift, self.obs_dot.get_center(), - self.unzoomable_mobs.scale,2,{"about_point" : ORIGIN}, - ) - - self.cumulated_zoom_factor *= factor - - - def shift_scene(v): - self.play( - self.zoomable_mobs.shift,v, - self.unzoomable_mobs.shift,v - ) - - self.force_skipping() - - self.zoomable_mobs = VMobject() - self.unzoomable_mobs = VMobject() - - - baseline = VMobject() - baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) - baseline.set_stroke(width = 0) # in case it gets accidentally added to the scene - self.zoomable_mobs.add(baseline) # prob not necessary - - self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) - self.ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) - self.unzoomable_mobs.add(self.obs_dot) #, self.ls0_dot) - - # lake - lake0 = Circle(radius = LAKE0_RADIUS, - stroke_width = 0, - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY - ) - lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) - self.zoomable_mobs.add(lake0) - - # Morty and indicator - morty = Mortimer().scale(0.3) - morty.next_to(OBSERVER_POINT,DOWN) - indicator = LightIndicator(precision = 2, - radius = INDICATOR_RADIUS, - show_reading = False, - color = LIGHT_COLOR - ) - indicator.next_to(morty,LEFT) - self.unzoomable_mobs.add(morty, indicator) - - # first lighthouse - original_op_func = inverse_quadratic(LIGHT_MAX_INT,AMBIENT_SCALE,LIGHT_CUTOFF) - ls0 = LightSource(opacity_function = original_op_func, num_levels = NUM_LEVELS) - ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) - self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light) - - self.add(lake0,morty,self.obs_dot,self.ls0_dot, ls0.lighthouse) - - self.wait() - - - # shore arcs - arc_left = Arc(-TAU/2, - radius = LAKE0_RADIUS, - start_angle = -TAU/4, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR - ) - arc_left.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) - - one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) - one_left.next_to(arc_left,LEFT) - - - arc_right = Arc(TAU/2, - radius = LAKE0_RADIUS, - start_angle = -TAU/4, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR - ) - arc_right.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) - - one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) - one_right.next_to(arc_right,RIGHT) - - self.play( - ShowCreation(arc_left), - Write(one_left), - ShowCreation(arc_right), - Write(one_right), - ) - - - self.play( - SwitchOn(ls0.ambient_light), - lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH}, - ) - - self.play(FadeIn(indicator)) - - self.play( - indicator.set_intensity,0.5 - ) - - # diameter - diameter = DoubleArrow(OBSERVER_POINT, - ls0.get_source_point(), - buff = 0, - color = WHITE, - ) - diameter_text = TexMobject("d").scale(TEX_SCALE) - diameter_text.next_to(diameter,RIGHT) - - self.play( - ShowCreation(diameter), - Write(diameter_text), - #FadeOut(self.obs_dot), - FadeOut(self.ls0_dot) - ) - - indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) - indicator_reading.move_to(indicator) - self.unzoomable_mobs.add(indicator_reading) - - self.play( - FadeIn(indicator_reading) - ) - - # replace d with its value - new_diameter_text = TexMobject("{2\over \pi}").scale(TEX_SCALE) - new_diameter_text.color = LAKE_COLOR - new_diameter_text.move_to(diameter_text) - self.play( - Transform(diameter_text,new_diameter_text) - ) - - # insert into indicator reading - new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE) - new_reading.move_to(indicator) - - self.play( - Transform(indicator_reading,new_reading) - ) - - self.play( - FadeOut(one_left), - FadeOut(one_right), - FadeOut(diameter_text), - FadeOut(arc_left), - FadeOut(arc_right) - ) - - - - - def indicator_wiggle(): - INDICATOR_WIGGLE_FACTOR = 1.3 - - self.play( - ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), - ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) - ) - - - def angle_for_index(i,step): - return -TAU/4 + TAU/2**step * (i + 0.5) - - - def position_for_index(i, step, scaled_down = False): - - theta = angle_for_index(i,step) - radial_vector = np.array([np.cos(theta),np.sin(theta),0]) - position = self.lake_center + self.lake_radius * radial_vector - - if scaled_down: - return position.scale_about_point(self.obs_dot.get_center(),0.5) - else: - return position - - - def split_light_source(i, step, show_steps = True, run_time = 1): - - ls_new_loc1 = position_for_index(i,step + 1) - ls_new_loc2 = position_for_index(i + 2**step,step + 1) - - hyp = VMobject() - hyp1 = Line(self.lake_center,ls_new_loc1) - hyp2 = Line(self.lake_center,ls_new_loc2) - hyp.add(hyp2,hyp1) - self.new_hypotenuses.append(hyp) - - if show_steps == True: - self.play( - ShowCreation(hyp, run_time = run_time) - ) - - leg1 = Line(self.obs_dot.get_center(),ls_new_loc1) - leg2 = Line(self.obs_dot.get_center(),ls_new_loc2) - self.new_legs_1.append(leg1) - self.new_legs_2.append(leg2) - - if show_steps == True: - self.play( - ShowCreation(leg1, run_time = run_time), - ShowCreation(leg2, run_time = run_time), - ) - - ls1 = self.light_sources_array[i] - - - ls2 = ls1.copy() - self.add(ls2) - self.additional_light_sources.append(ls2) - - # check if the light sources are on screen - ls_old_loc = np.array(ls1.get_source_point()) - onscreen_old = np.all(np.abs(ls_old_loc[:2]) < 10 ** 2**step) - onscreen_1 = np.all(np.abs(ls_new_loc1[:2][:2]) < 10 ** 2**step) - onscreen_2 = np.all(np.abs(ls_new_loc2[:2]) < 10 ** 2**step) - show_animation = (onscreen_old or onscreen_1 or onscreen_2) - - if show_animation: - print("animating (", i, ",", step, ")") - self.play( - ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), - ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), - ) - else: - ls1.move_source_to(ls_new_loc1) - ls2.move_source_to(ls_new_loc1) - - - - - - def construction_step(n, show_steps = True, run_time = 1, - simultaneous_splitting = False): - - # we assume that the scene contains: - # an inner lake, self.inner_lake - # an outer lake, self.outer_lake - # light sources, self.light_sources - # legs from the observer point to each light source - # self.legs - # altitudes from the observer point to the - # locations of the light sources in the previous step - # self.altitudes - # hypotenuses connecting antipodal light sources - # self.hypotenuses - - # these are mobjects! - - - # first, fade out all of the hypotenuses and altitudes - - if show_steps == True: - self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake) - self.play( - FadeOut(self.hypotenuses), - FadeOut(self.altitudes), - FadeOut(self.inner_lake) - ) - else: - self.zoomable_mobs.remove(self.inner_lake) - self.play( - FadeOut(self.inner_lake) - ) - - # create a new, outer lake - self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP - - new_outer_lake = Circle(radius = self.lake_radius, - stroke_width = LAKE_STROKE_WIDTH, - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY, - stroke_color = LAKE_STROKE_COLOR - ) - new_outer_lake.move_to(self.lake_center) - - if show_steps == True: - self.play( - FadeIn(new_outer_lake, run_time = run_time), - FadeIn(self.ls0_dot) - ) - else: - self.play( - FadeIn(new_outer_lake, run_time = run_time), - ) - - self.wait() - - self.inner_lake = self.outer_lake - self.outer_lake = new_outer_lake - self.altitudes = self.legs - #self.lake_center = self.outer_lake.get_center() - - self.additional_light_sources = [] - self.new_legs_1 = [] - self.new_legs_2 = [] - self.new_hypotenuses = [] - - for i in range(2**n): - split_light_source(i, - step = n, - show_steps = show_steps, - run_time = run_time - ) - - - - # collect the newly created mobs (in arrays) - # into the appropriate Mobject containers - - self.legs = VMobject() - for leg in self.new_legs_1: - self.legs.add(leg) - self.zoomable_mobs.add(leg) - for leg in self.new_legs_2: - self.legs.add(leg) - self.zoomable_mobs.add(leg) - - for hyp in self.hypotenuses.submobjects: - self.zoomable_mobs.remove(hyp) - - self.hypotenuses = VMobject() - for hyp in self.new_hypotenuses: - self.hypotenuses.add(hyp) - self.zoomable_mobs.add(hyp) - - for ls in self.additional_light_sources: - self.light_sources.add(ls) - self.light_sources_array.append(ls) - self.zoomable_mobs.add(ls) - - # update scene - self.add( - self.light_sources, - self.inner_lake, - self.outer_lake, - ) - self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake) - - if show_steps == True: - self.add( - self.legs, - self.hypotenuses, - self.altitudes, - ) - self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes) - - - self.wait() - - if show_steps == True: - self.play(FadeOut(self.ls0_dot)) - - #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP - self.lake_radius *= 2 - - - - - - - - - self.lake_center = ls0_loc = ls0.get_source_point() - - self.inner_lake = VMobject() - self.outer_lake = lake0 - self.legs = VMobject() - self.legs.add(Line(OBSERVER_POINT,self.lake_center)) - self.altitudes = VMobject() - self.hypotenuses = VMobject() - self.light_sources_array = [ls0] - self.light_sources = VMobject() - self.light_sources.add(ls0) - - self.lake_radius = 2 * LAKE0_RADIUS # don't ask... - - self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources) - - self.add(self.inner_lake, - self.outer_lake, - self.legs, - self.altitudes, - self.hypotenuses - ) - - self.play(FadeOut(diameter)) - - self.additional_light_sources = [] - self.new_legs_1 = [] - self.new_legs_2 = [] - self.new_hypotenuses = [] - - self.revert_to_original_skipping_status() - - construction_step(0) - indicator_wiggle() - self.play(FadeOut(self.ls0_dot)) - zoom_out_scene(2) - - return - - construction_step(1) - indicator_wiggle() - self.play(FadeOut(self.ls0_dot)) - zoom_out_scene(2) - - construction_step(2) - indicator_wiggle() - self.play(FadeOut(self.ls0_dot)) - - - - self.revert_to_original_skipping_status() - - - ANGLE_COLOR1 = BLUE_C - ANGLE_COLOR2 = GREEN_D - - - for mob in self.mobjects: - mob.fade(1.0) - - for hyp in self.hypotenuses: - hyp.set_stroke(width = 0) - for alt in self.altitudes: - alt.set_stroke(width = 0) - for leg in self.legs: - leg.set_stroke(width = 0) - self.inner_lake.set_stroke(width = 0) - self.outer_lake.set_stroke(width = 0) - - self.wait() - - inner_lake_center = self.inner_lake.get_center() - inner_lake_radius = self.lake_radius * 0.25 - inner_ls = VGroup() - for i in range(4): - theta = -TAU/4 + (i+0.5) * TAU/4 - point = inner_lake_center + inner_lake_radius * np.array([np.cos(theta), np.sin(theta),0]) - dot = Dot(point, color = LAKE_STROKE_COLOR, radius = 0.3) - inner_ls.add(dot) - - self.add(inner_ls) - - inner_ls1 = inner_ls.submobjects[0] - inner_ls2 = inner_ls.submobjects[1] - inner_ls1_center = inner_ls1.get_center() - inner_ls2_center = inner_ls2.get_center() - - outer_lake_center = self.outer_lake.get_center() - outer_lake_radius = self.lake_radius * 0.5 - outer_ls = VGroup() - for i in range(8): - theta = -TAU/4 + (i+0.5) * TAU/8 - point = outer_lake_center + outer_lake_radius * np.array([np.cos(theta), np.sin(theta),0]) - dot = Dot(point, color = LAKE_STROKE_COLOR, radius = 0.3) - outer_ls.add(dot) - - self.add(outer_ls) - - outer_ls1 = outer_ls.submobjects[0] - outer_ls2 = outer_ls.submobjects[1] - outer_ls1_center = outer_ls1.get_center() - outer_ls2_center = outer_ls2.get_center() - - self.wait() - - arc_radius = 2.0 - - line1 = Line(inner_lake_center, inner_ls1_center, color = WHITE) - line2 = Line(inner_lake_center, inner_ls2_center, color = WHITE) - - - #arc_point1 = interpolate(inner_lake_center, inner_ls1_center, 0.2) - #arc_point2 = interpolate(inner_lake_center, inner_ls2_center, 0.2) - #inner_angle_arc = ArcBetweenPoints(arc_point1, arc_point2, angle = TAU/4) - inner_angle_arc = Arc(angle = TAU/4, start_angle = -TAU/8, radius = arc_radius, - stroke_color = ANGLE_COLOR1) - inner_angle_arc.move_arc_center_to(inner_lake_center) - - inner_label = TexMobject("\\theta", fill_color = ANGLE_COLOR1).scale(3).next_to(inner_angle_arc, LEFT, buff = -0.1) - - self.play( - ShowCreation(line1), - ShowCreation(line2), - ) - self.play( - ShowCreation(inner_angle_arc), - FadeIn(inner_label) - ) - - - - - self.wait() - - - - line3 = Line(outer_lake_center, inner_ls1_center, color = WHITE) - line4 = Line(outer_lake_center, inner_ls2_center, color = WHITE) - outer_angle_arc = Arc(angle = TAU/8, start_angle = -3*TAU/16, radius = arc_radius, - stroke_color = ANGLE_COLOR2) - outer_angle_arc.move_arc_center_to(outer_lake_center) - - outer_label = TexMobject("{\\theta \over 2}", color = ANGLE_COLOR2).scale(2.5).move_to(outer_angle_arc) - outer_label.shift([-2,-1,0]) - - self.play( - ShowCreation(line3), - ShowCreation(line4), - ) - self.play( - ShowCreation(outer_angle_arc), - FadeIn(outer_label) - ) - - - - self.wait() - - - - line5 = Line(outer_lake_center, outer_ls1_center, color = WHITE) - line6 = Line(outer_lake_center, outer_ls2_center, color = WHITE) - - self.play( - ShowCreation(line5), - ShowCreation(line6) - ) - - - self.wait() - - self.play( - FadeOut(line1), - FadeOut(line2), - FadeOut(line3), - FadeOut(line4), - FadeOut(line5), - FadeOut(line6), - FadeOut(inner_angle_arc), - FadeOut(outer_angle_arc), - FadeOut(inner_label), - FadeOut(outer_label), - ) - - - self.wait() - - inner_lines = VGroup() - inner_arcs = VGroup() - - for i in range(-2,2): - - theta = -TAU/4 + (i+0.5)*TAU/4 - ls_point = inner_lake_center + inner_lake_radius * np.array([ - np.cos(theta), np.sin(theta),0]) - line = Line(inner_lake_center, ls_point, color = WHITE) - inner_lines.add(line) - - arc = Arc(angle = TAU/4, start_angle = theta, radius = arc_radius, - stroke_color = ANGLE_COLOR1) - arc.move_arc_center_to(inner_lake_center) - inner_arcs.add(arc) - - if i == 1: - arc.set_stroke(width = 0) - - for line in inner_lines.submobjects: - self.play( - ShowCreation(line), - ) - self.add_foreground_mobject(inner_lines) - for arc in inner_arcs.submobjects: - self.play( - ShowCreation(arc) - ) - - self.wait() - - outer_lines = VGroup() - outer_arcs = VGroup() - - for i in range(-2,2): - - theta = -TAU/4 + (i+0.5)*TAU/4 - - ls_point = inner_lake_center + inner_lake_radius * np.array([ - np.cos(theta), np.sin(theta),0]) - line = Line(outer_lake_center, ls_point, color = WHITE) - outer_lines.add(line) - - theta = -TAU/4 + (i+0.5)*TAU/8 - arc = Arc(angle = TAU/8, start_angle = theta, radius = arc_radius, - stroke_color = ANGLE_COLOR2) - arc.move_arc_center_to(outer_lake_center) - outer_arcs.add(arc) - - if i == 1: - arc.set_stroke(width = 0) - - - for line in outer_lines.submobjects: - self.play( - ShowCreation(line), - ) - self.add_foreground_mobject(outer_lines) - for arc in outer_arcs.submobjects: - self.play( - ShowCreation(arc) - ) - - self.wait() - - self.play( - FadeOut(inner_lines), - FadeOut(inner_arcs) - ) - - - outer_lines2 = VGroup() - - for i in range(-2,2): - - theta = -TAU/4 + (i+0.5)*TAU/8 - ls_point = outer_lake_center + outer_lake_radius * np.array([ - np.cos(theta), np.sin(theta),0]) - line = Line(outer_lake_center, ls_point, color = WHITE) - outer_lines2.add(line) - - self.play( - ShowCreation(outer_lines2), - ) - - self.wait() - - outer_lines3 = outer_lines2.copy().rotate(TAU/2, about_point = outer_lake_center) - outer_arcs3 = outer_arcs.copy().rotate(TAU/2, about_point = outer_lake_center) - - self.play( - ShowCreation(outer_lines3), - ) - self.add_foreground_mobject(outer_lines3) - for arc in outer_arcs3.submobjects: - self.play( - ShowCreation(arc) - ) - - last_arc = outer_arcs3.submobjects[0].copy() - last_arc.rotate(-TAU/8, about_point = outer_lake_center) - last_arc2 = last_arc.copy() - last_arc2.rotate(TAU/2, about_point = outer_lake_center) - - self.play( - ShowCreation(last_arc), - ShowCreation(last_arc2), - ) - - self.wait() - - self.play( - FadeOut(outer_lines2), - FadeOut(outer_lines3), - FadeOut(outer_arcs), - FadeOut(outer_arcs3), - FadeOut(last_arc), - FadeOut(last_arc2), - ) - - self.play( - FadeOut(inner_ls), - FadeOut(outer_ls), - ) - - - self.wait() - - -class PondScene(ThreeDScene): - - - - - - def construct(self): - - BASELINE_YPOS = -2.5 - OBSERVER_POINT = np.array([0,BASELINE_YPOS,0]) - LAKE0_RADIUS = 1.5 - INDICATOR_RADIUS = 0.6 - TICK_SIZE = 0.5 - LIGHTHOUSE_HEIGHT = 0.5 - LAKE_COLOR = BLUE - LAKE_OPACITY = 0.15 - LAKE_STROKE_WIDTH = 5.0 - LAKE_STROKE_COLOR = BLUE - TEX_SCALE = 0.8 - DOT_COLOR = BLUE - - LIGHT_MAX_INT = 1 - LIGHT_SCALE = 2.5 - LIGHT_CUTOFF = 1 - - RIGHT_ANGLE_SIZE = 0.3 - - self.cumulated_zoom_factor = 1 - - STEP_RUN_TIME = 0.5 - - - #self.force_skipping() - - - def right_angle(pointA, pointB, pointC, size = 1): - - v1 = pointA - pointB - v1 = size * v1/get_norm(v1) - v2 = pointC - pointB - v2 = size * v2/get_norm(v2) - - P = pointB - Q = pointB + v1 - R = Q + v2 - S = R - v1 - angle_sign = VMobject() - angle_sign.set_points_as_corners([P,Q,R,S,P]) - angle_sign.mark_paths_closed = True - angle_sign.set_fill(color = WHITE, opacity = 1) - angle_sign.set_stroke(width = 0) - return angle_sign - - - def triangle(pointA, pointB, pointC): - - mob = VMobject() - mob.set_points_as_corners([pointA, pointB, pointC, pointA]) - mob.mark_paths_closed = True - mob.set_fill(color = WHITE, opacity = 0.5) - mob.set_stroke(width = 0) - return mob - - - def zoom_out_scene(factor): - - self.remove_foreground_mobject(self.ls0_dot) - self.remove(self.ls0_dot) - - phi0 = self.camera.get_phi() # default is 0 degs - theta0 = self.camera.get_theta() # default is -90 degs - distance0 = self.camera.get_distance() - - distance1 = 2 * distance0 - camera_target_point = self.camera.get_spherical_coords(phi0, theta0, distance1) - - self.play( - ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), - self.zoomable_mobs.shift, self.obs_dot.get_center(), - self.unzoomable_mobs.scale,2,{"about_point" : ORIGIN}, - ) - - self.cumulated_zoom_factor *= factor - - # place ls0_dot by hand - #old_radius = self.ls0_dot.radius - #self.ls0_dot.radius = 2 * old_radius - - #v = self.ls0_dot.get_center() - self.obs_dot.get_center() - #self.ls0_dot.shift(v) - #self.ls0_dot.move_to(self.outer_lake.get_center()) - self.ls0_dot.scale(2, about_point = ORIGIN) - - #self.add_foreground_mobject(self.ls0_dot) - - - def shift_scene(v): - self.play( - self.zoomable_mobs.shift,v, - self.unzoomable_mobs.shift,v - ) - - - self.zoomable_mobs = VMobject() - self.unzoomable_mobs = VMobject() - - - baseline = VMobject() - baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) - baseline.set_stroke(width = 0) # in case it gets accidentally added to the scene - self.zoomable_mobs.add(baseline) # prob not necessary - - self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) - self.ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) - self.unzoomable_mobs.add(self.obs_dot)#, self.ls0_dot) - - # lake - lake0 = Circle(radius = LAKE0_RADIUS, - stroke_width = 0, - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY - ) - lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) - self.zoomable_mobs.add(lake0) - - # Morty and indicator - morty = Randolph(color = MAROON_D).scale(0.3) - morty.next_to(OBSERVER_POINT,DOWN) - indicator = LightIndicator(precision = 2, - radius = INDICATOR_RADIUS, - show_reading = False, - color = LIGHT_COLOR - ) - indicator.next_to(morty,LEFT) - self.unzoomable_mobs.add(morty, indicator) - - # first lighthouse - original_op_func = inverse_quadratic(LIGHT_MAX_INT,LIGHT_SCALE,LIGHT_CUTOFF) - ls0 = LightSource(opacity_function = original_op_func, radius = 15.0, num_levels = 15) - ls0.lighthouse.set_height(LIGHTHOUSE_HEIGHT) - ls0.lighthouse.height = LIGHTHOUSE_HEIGHT - ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) - self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light) - - self.add(lake0,morty,self.obs_dot,self.ls0_dot, ls0.lighthouse) - self.add_foreground_mobject(morty) - self.add_foreground_mobject(self.obs_dot) - self.add_foreground_mobject(self.ls0_dot) - self.wait() - - - # shore arcs - arc_left = Arc(-TAU/2, - radius = LAKE0_RADIUS, - start_angle = -TAU/4, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR - ) - arc_left.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) - - one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) - one_left.next_to(arc_left,LEFT) - - - arc_right = Arc(TAU/2, - radius = LAKE0_RADIUS, - start_angle = -TAU/4, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR - ) - arc_right.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) - - one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) - one_right.next_to(arc_right,RIGHT) - - self.play( - ShowCreation(arc_left), - Write(one_left), - ShowCreation(arc_right), - Write(one_right), - ) - - - self.play( - SwitchOn(ls0.ambient_light), - lake0.set_stroke,{"color": LAKE_STROKE_COLOR, "width" : LAKE_STROKE_WIDTH}, - ) - - self.play(FadeIn(indicator)) - self.add_foreground_mobject(indicator) - - self.play( - indicator.set_intensity,0.5 - ) - - diameter_start = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.02) - diameter_stop = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.98) - - # diameter - diameter = DoubleArrow(diameter_start, - diameter_stop, - buff = 0, - color = WHITE, - ) - diameter_text = TexMobject("d").scale(TEX_SCALE) - diameter_text.next_to(diameter,RIGHT) - - self.play( - ShowCreation(diameter), - Write(diameter_text), - #FadeOut(self.obs_dot), - FadeOut(self.ls0_dot) - ) - - indicator_reading = TexMobject("{1\over d^2}").scale(TEX_SCALE) - indicator_reading.move_to(indicator) - self.unzoomable_mobs.add(indicator_reading) - - self.play( - FadeIn(indicator_reading) - ) - self.add_foreground_mobject(indicator_reading) - - # replace d with its value - new_diameter_text = TexMobject("{2\over \pi}").scale(TEX_SCALE) - new_diameter_text.color = LAKE_COLOR - new_diameter_text.move_to(diameter_text) - self.play( - Transform(diameter_text,new_diameter_text) - ) - - # insert into indicator reading - new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE) - new_reading.move_to(indicator) - - self.play( - Transform(indicator_reading,new_reading) - ) - - self.wait() - - self.play( - FadeOut(one_left), - FadeOut(one_right), - FadeOut(diameter_text), - FadeOut(arc_left), - FadeOut(arc_right) - ) - - - - - def indicator_wiggle(): - INDICATOR_WIGGLE_FACTOR = 1.3 - - self.play( - ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), - ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) - ) - - - def angle_for_index(i,step): - return -TAU/4 + TAU/2**step * (i + 0.5) - - - def position_for_index(i, step, scaled_down = False): - - theta = angle_for_index(i,step) - radial_vector = np.array([np.cos(theta),np.sin(theta),0]) - position = self.lake_center + self.lake_radius * radial_vector - - if scaled_down: - return position.scale_about_point(self.obs_dot.get_center(),0.5) - else: - return position - - - def split_light_source(i, step, show_steps = True, animate = True, run_time = 1): - - ls_new_loc1 = position_for_index(i,step + 1) - ls_new_loc2 = position_for_index(i + 2**step,step + 1) - - hyp = VMobject() - hyp1 = Line(self.lake_center,ls_new_loc1) - hyp2 = Line(self.lake_center,ls_new_loc2) - hyp.add(hyp2,hyp1) - self.new_hypotenuses.append(hyp) - - if show_steps == True: - self.play( - ShowCreation(hyp, run_time = run_time) - ) - - leg1 = Line(self.obs_dot.get_center(),ls_new_loc1) - leg2 = Line(self.obs_dot.get_center(),ls_new_loc2) - self.new_legs_1.append(leg1) - self.new_legs_2.append(leg2) - - if show_steps == True: - self.play( - ShowCreation(leg1, run_time = run_time), - ShowCreation(leg2, run_time = run_time), - ) - - ls1 = self.light_sources_array[i] - - - ls2 = ls1.copy() - if animate == True: - self.add(ls2) - - self.additional_light_sources.append(ls2) - - # check if the light sources are on screen - ls_old_loc = np.array(ls1.get_source_point()) - onscreen_old = np.all(np.abs(ls_old_loc[:2]) < 10 * 2**3) - onscreen_1 = np.all(np.abs(ls_new_loc1[:2]) < 10 * 2**3) - onscreen_2 = np.all(np.abs(ls_new_loc2[:2]) < 10 * 2**3) - show_animation = (onscreen_old or onscreen_1 or onscreen_2) - - if show_animation or animate: - print("animating (", i, ",", step, ")") - self.play( - ApplyMethod(ls1.move_source_to,ls_new_loc1, run_time = run_time), - ApplyMethod(ls2.move_source_to,ls_new_loc2, run_time = run_time), - ) - else: - ls1.move_source_to(ls_new_loc1) - ls2.move_source_to(ls_new_loc1) - - - - - - def construction_step(n, show_steps = True, run_time = 1, - simultaneous_splitting = False): - - # we assume that the scene contains: - # an inner lake, self.inner_lake - # an outer lake, self.outer_lake - # light sources, self.light_sources - # legs from the observer point to each light source - # self.legs - # altitudes from the observer point to the - # locations of the light sources in the previous step - # self.altitudes - # hypotenuses connecting antipodal light sources - # self.hypotenuses - - # these are mobjects! - - - # first, fade out all of the hypotenuses and altitudes - - if show_steps == True: - self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake) - self.play( - FadeOut(self.hypotenuses), - FadeOut(self.altitudes), - FadeOut(self.inner_lake) - ) - else: - self.zoomable_mobs.remove(self.inner_lake) - self.play( - FadeOut(self.inner_lake) - ) - - # create a new, outer lake - self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP - - new_outer_lake = Circle(radius = self.lake_radius, - stroke_width = LAKE_STROKE_WIDTH, - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY, - stroke_color = LAKE_STROKE_COLOR - ) - new_outer_lake.move_to(self.lake_center) - - if show_steps == True: - self.play( - FadeIn(new_outer_lake, run_time = run_time), - FadeIn(self.ls0_dot) - ) - else: - self.play( - FadeIn(new_outer_lake, run_time = run_time), - ) - - self.wait() - - self.inner_lake = self.outer_lake - self.outer_lake = new_outer_lake - self.altitudes = self.legs - #self.lake_center = self.outer_lake.get_center() - - self.additional_light_sources = [] - self.new_legs_1 = [] - self.new_legs_2 = [] - self.new_hypotenuses = [] - - # WE ALWAYS USE THIS CASE BRANCH - if simultaneous_splitting == False: - - for i in range(2**n): - - split_light_source(i, - step = n, - show_steps = show_steps, - run_time = run_time - ) - - if n == 1 and i == 0: - # show again where the right angles are - A = self.light_sources[0].get_center() - B = self.additional_light_sources[0].get_center() - C = self.obs_dot.get_center() - - triangle1 = triangle( - A, C, B - ) - right_angle1 = right_angle( - A, C, B, size = 2 * RIGHT_ANGLE_SIZE - ) - - self.play( - FadeIn(triangle1), - FadeIn(right_angle1) - ) - - self.wait() - - self.play( - FadeOut(triangle1), - FadeOut(right_angle1) - ) - - self.wait() - - H = self.inner_lake.get_center() + self.lake_radius/2 * RIGHT - L = self.outer_lake.get_center() - triangle2 = triangle( - L, H, C - ) - - right_angle2 = right_angle( - L, H, C, size = 2 * RIGHT_ANGLE_SIZE - ) - - self.play( - FadeIn(triangle2), - FadeIn(right_angle2) - ) - - self.wait() - - self.play( - FadeOut(triangle2), - FadeOut(right_angle2) - ) - - self.wait() - - - - # WE DON'T USE THIS CASE BRANCH ANYMORE - else: # simultaneous splitting - - old_lake = self.outer_lake.copy() - old_ls = self.light_sources.copy() - old_ls2 = old_ls.copy() - for submob in old_ls2.submobjects: - old_ls.add(submob) - - self.remove(self.outer_lake, self.light_sources) - self.add(old_lake, old_ls) - - for i in range(2**n): - split_light_source(i, - step = n, - show_steps = show_steps, - run_time = run_time, - animate = False - ) - - self.play( - ReplacementTransform(old_ls, self.light_sources, run_time = run_time), - ReplacementTransform(old_lake, self.outer_lake, run_time = run_time), - ) - - - - - # collect the newly created mobs (in arrays) - # into the appropriate Mobject containers - - self.legs = VMobject() - for leg in self.new_legs_1: - self.legs.add(leg) - self.zoomable_mobs.add(leg) - for leg in self.new_legs_2: - self.legs.add(leg) - self.zoomable_mobs.add(leg) - - for hyp in self.hypotenuses.submobjects: - self.zoomable_mobs.remove(hyp) - - self.hypotenuses = VMobject() - for hyp in self.new_hypotenuses: - self.hypotenuses.add(hyp) - self.zoomable_mobs.add(hyp) - - for ls in self.additional_light_sources: - self.light_sources.add(ls) - self.light_sources_array.append(ls) - self.zoomable_mobs.add(ls) - - # update scene - self.add( - self.light_sources, - self.inner_lake, - self.outer_lake, - ) - self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake) - - if show_steps == True: - self.add( - self.legs, - self.hypotenuses, - self.altitudes, - ) - self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes) - - - self.wait() - - if show_steps == True: - self.play(FadeOut(self.ls0_dot)) - - #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP - self.lake_radius *= 2 - - - - - - - - - self.lake_center = ls0_loc = ls0.get_source_point() - - self.inner_lake = VMobject() - self.outer_lake = lake0 - self.legs = VMobject() - self.legs.add(Line(OBSERVER_POINT,self.lake_center)) - self.altitudes = VMobject() - self.hypotenuses = VMobject() - self.light_sources_array = [ls0] - self.light_sources = VMobject() - self.light_sources.add(ls0) - - self.lake_radius = 2 * LAKE0_RADIUS # don't ask... - - self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources) - - self.add(self.inner_lake, - self.outer_lake, - self.legs, - self.altitudes, - self.hypotenuses - ) - - self.play(FadeOut(diameter)) - - self.additional_light_sources = [] - self.new_legs_1 = [] - self.new_legs_2 = [] - self.new_hypotenuses = [] - - - construction_step(0, run_time = STEP_RUN_TIME) - - my_triangle = triangle( - self.light_sources[0].get_source_point(), - OBSERVER_POINT, - self.light_sources[1].get_source_point() - ) - - angle_sign1 = right_angle( - self.light_sources[0].get_source_point(), - OBSERVER_POINT, - self.light_sources[1].get_source_point(), - size = RIGHT_ANGLE_SIZE - ) - - self.play( - FadeIn(angle_sign1), - FadeIn(my_triangle) - ) - - angle_sign2 = right_angle( - self.light_sources[1].get_source_point(), - self.lake_center, - OBSERVER_POINT, - size = RIGHT_ANGLE_SIZE - ) - - self.play( - FadeIn(angle_sign2) - ) - - self.wait() - - self.play( - FadeOut(angle_sign1), - FadeOut(angle_sign2), - FadeOut(my_triangle) - ) - - indicator_wiggle() - self.remove(self.ls0_dot) - zoom_out_scene(2) - - - construction_step(1, run_time = STEP_RUN_TIME) - indicator_wiggle() - #self.play(FadeOut(self.ls0_dot)) - zoom_out_scene(2) - - - construction_step(2, run_time = STEP_RUN_TIME) - indicator_wiggle() - self.play(FadeOut(self.ls0_dot)) - - - - - self.play( - FadeOut(self.altitudes), - FadeOut(self.hypotenuses), - FadeOut(self.legs) - ) - - max_it = 10 - scale = 2**(max_it - 5) - TEX_SCALE *= scale - - - - # for i in range(3,max_it + 1): - # construction_step(i, show_steps = False, run_time = 4.0/2**i, - # simultaneous_splitting = True) - - - #print "starting simultaneous expansion" - - # simultaneous expansion of light sources from now on - self.play(FadeOut(self.inner_lake)) - - for n in range(3,max_it + 1): - print("working on n = ", n, "...") - new_lake = self.outer_lake.copy().scale(2,about_point = self.obs_dot.get_center()) - for (i,ls) in enumerate(self.light_sources_array[:2**n]): - #print i - lsp = ls.copy() - self.light_sources.add(lsp) - self.add(lsp) - self.light_sources_array.append(lsp) - - new_lake_center = new_lake.get_center() - new_lake_radius = 0.5 * new_lake.get_width() - - self.play(Transform(self.outer_lake,new_lake)) - shift_list = [] - - for i in range(2**n): - #print "===========" - #print i - theta = -TAU/4 + (i + 0.5) * TAU / 2**(n+1) - v = np.array([np.cos(theta), np.sin(theta),0]) - pos1 = new_lake_center + new_lake_radius * v - pos2 = new_lake_center - new_lake_radius * v - ls1 = self.light_sources.submobjects[i] - ls2 = self.light_sources.submobjects[i+2**n] - - ls_old_loc = np.array(ls1.get_source_point()) - onscreen_old = np.all(np.abs(ls_old_loc[:2]) < 10 * 2**2) - onscreen_1 = np.all(np.abs(pos1[:2]) < 10 * 2**2) - onscreen_2 = np.all(np.abs(pos2[:2]) < 10 * 2**2) - - if onscreen_old or onscreen_1: - print("anim1 for step", n, "part", i) - print("------------------ moving from", ls_old_loc[:2], "to", pos1[:2]) - shift_list.append(ApplyMethod(ls1.move_source_to, pos1, run_time = STEP_RUN_TIME)) - else: - ls1.move_source_to(pos1) - if onscreen_old or onscreen_2: - print("anim2 for step", n, "part", i) - print("------------------ moving from", ls_old_loc[:2], "to", pos2[:2]) - shift_list.append(ApplyMethod(ls2.move_source_to, pos2, run_time = STEP_RUN_TIME)) - else: - ls2.move_source_to(pos2) - - - #print shift_list - - self.play(*shift_list) - print("...done") - - - #self.revert_to_original_skipping_status() - - # Now create a straight number line and transform into it - MAX_N = 7 - - origin_point = self.obs_dot.get_center() - - self.number_line = NumberLine( - x_min = -MAX_N, - x_max = MAX_N + 1, - color = WHITE, - number_at_center = 0, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR, - numbers_with_elongated_ticks = [], - numbers_to_show = list(range(-MAX_N,MAX_N + 1)),#,2), - unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale, - tick_frequency = 1, - tick_size = LAKE_STROKE_WIDTH, - number_scale_val = 3, - line_to_number_buff = LARGE_BUFF, - label_direction = UP, - ).shift(origin_point - self.number_line.number_to_point(0)) # .shift(scale * 2.5 * DOWN) - - print("scale ", scale) - print("number line at", self.number_line.get_center()) - print("should be at", origin_point, "or", OBSERVER_POINT) - - self.number_line.tick_marks.fade(1) - self.number_line_labels = self.number_line.get_number_mobjects() - self.wait() - - origin_point = self.number_line.number_to_point(0) - nl_sources = VMobject() - pond_sources = VMobject() - - for i in range(-MAX_N,MAX_N+1): - anchor = self.number_line.number_to_point(2*i + 1) - ls = self.light_sources_array[i].copy() - ls.move_source_to(anchor) - nl_sources.add(ls) - pond_sources.add(self.light_sources_array[i].copy()) - - self.add(pond_sources) - self.remove(self.light_sources) - for ls in self.light_sources_array: - self.remove(ls) - - self.outer_lake.rotate(TAU/8) - - # open sea - open_sea = Rectangle( - width = 200 * scale, - height = 100 * scale, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR, - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY, - ).flip().next_to(self.obs_dot.get_center(),UP,buff = 0) - - self.revert_to_original_skipping_status() - - self.play( - ReplacementTransform(pond_sources,nl_sources), - #FadeOut(pond_sources), - #FadeIn(nl_sources), - ReplacementTransform(self.outer_lake,open_sea), - #FadeOut(self.inner_lake) - ) - self.play(FadeIn(self.number_line)) - - self.wait() - - v = 4 * scale * UP - self.play( - nl_sources.shift,v, - morty.shift,v, - self.number_line.shift,v, - indicator.shift,v, - indicator_reading.shift,v, - open_sea.shift,v, - self.obs_dot.shift,v, - ) - self.number_line_labels.shift(v) - - origin_point = self.number_line.number_to_point(0) - #self.remove(self.obs_dot) - self.play( - indicator.move_to, origin_point + scale * UP + 2 * UP, - indicator_reading.move_to, origin_point + scale * UP + 2 * UP, - FadeOut(open_sea), - FadeOut(morty), - FadeIn(self.number_line_labels), - FadeIn(self.number_line.tick_marks), - ) - - two_sided_sum = TexMobject("\dots", "+", "{1\over (-11)^2}",\ - "+", "{1\over (-9)^2}", " + ", "{1\over (-7)^2}", " + ", "{1\over (-5)^2}", " + ", \ - "{1\over (-3)^2}", " + ", "{1\over (-1)^2}", " + ", "{1\over 1^2}", " + ", \ - "{1\over 3^2}", " + ", "{1\over 5^2}", " + ", "{1\over 7^2}", " + ", \ - "{1\over 9^2}", " + ", "{1\over 11^2}", " + ", "\dots") - - nb_symbols = len(two_sided_sum.submobjects) - - two_sided_sum.scale(TEX_SCALE) - - for (i,submob) in zip(list(range(nb_symbols)),two_sided_sum.submobjects): - submob.next_to(self.number_line.number_to_point(i - 13),DOWN, buff = 2*scale) - if (i == 0 or i % 2 == 1 or i == nb_symbols - 1): # non-fractions - submob.shift(0.3 * scale * DOWN) - - self.play(Write(two_sided_sum)) - - self.wait() - - for ls in nl_sources.submobjects: - if ls.get_source_point()[0] < 0: - self.remove_foreground_mobject(ls.ambient_light) - self.remove(ls.ambient_light) - else: - self.add_foreground_mobject(ls.ambient_light) - - for label in self.number_line_labels.submobjects: - if label.get_center()[0] <= 0: - self.remove(label) - - - - covering_rectangle = Rectangle( - width = FRAME_X_RADIUS * scale, - height = 2 * FRAME_Y_RADIUS * scale, - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 1, - ) - covering_rectangle.next_to(ORIGIN, LEFT, buff = 0) - #for i in range(10): - # self.add_foreground_mobject(nl_sources.submobjects[i]) - - self.add_foreground_mobject(indicator) - self.add_foreground_mobject(indicator_reading) - - - half_indicator_reading = TexMobject("{\pi^2 \over 8}").scale(TEX_SCALE) - half_indicator_reading.move_to(indicator) - - central_plus_sign = two_sided_sum[13] - - self.play( - FadeIn(covering_rectangle), - Transform(indicator_reading, half_indicator_reading), - FadeOut(central_plus_sign) - ) - - - equals_sign = TexMobject("=").scale(TEX_SCALE) - equals_sign.move_to(central_plus_sign) - p = 2 * scale * LEFT + central_plus_sign.get_center()[1] * UP - - self.play( - indicator.move_to,p, - indicator_reading.move_to,p, - FadeIn(equals_sign), - ) - - self.revert_to_original_skipping_status() - - # show Randy admiring the result - randy = Randolph(color = MAROON_D).scale(scale).move_to(2*scale*DOWN+5*scale*LEFT) - self.play(FadeIn(randy)) - self.play(randy.change,"happy") - self.play(randy.change,"hooray") - - - - - - - - -class WaitScene(TeacherStudentsScene): - - def construct(self): - - - self.teacher_says(TexMobject("{1\over 1^2}+{1\over 3^2}+{1\over 5^2}+{1\over 7^2}+\dots = {\pi^2 \over 8}!")) - - student_q = TextMobject("What about") - full_sum = TexMobject("{1\over 1^2}+{1\over 2^2}+{1\over 3^2}+{1\over 4^2}+\dots?") - full_sum.next_to(student_q,RIGHT) - student_q.add(full_sum) - - - self.student_says(student_q, target_mode = "angry") - - -class FinalSumManipulationScene(PiCreatureScene): - - def construct(self): - - LAKE_COLOR = BLUE - LAKE_OPACITY = 0.15 - LAKE_STROKE_WIDTH = 5.0 - LAKE_STROKE_COLOR = BLUE - TEX_SCALE = 0.8 - - LIGHT_COLOR2 = RED - LIGHT_COLOR3 = BLUE - - unit_length = 1.5 - vertical_spacing = 2.5 * DOWN - switch_on_time = 0.2 - - sum_vertical_spacing = 1.5 - - randy = self.get_primary_pi_creature() - randy.set_color(MAROON_D) - randy.color = MAROON_D - randy.scale(0.7).flip().to_edge(DOWN + LEFT) - self.wait() - - ls_template = LightSource( - radius = 1, - num_levels = 10, - max_opacity_ambient = 0.5, - opacity_function = inverse_quadratic(1,0.75,1) - ) - - - odd_range = np.arange(1,9,2) - even_range = np.arange(2,16,2) - full_range = np.arange(1,8,1) - - self.number_line1 = NumberLine( - x_min = 0, - x_max = 11, - color = LAKE_STROKE_COLOR, - number_at_center = 0, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR, - #numbers_to_show = full_range, - number_scale_val = 0.5, - numbers_with_elongated_ticks = [], - unit_size = unit_length, - tick_frequency = 1, - line_to_number_buff = MED_SMALL_BUFF, - include_tip = True, - label_direction = UP, - ) - - self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0.3) - self.number_line1.add_numbers() - - odd_lights = VMobject() - for i in odd_range: - pos = self.number_line1.number_to_point(i) - ls = ls_template.copy() - ls.move_source_to(pos) - odd_lights.add(ls) - - self.play( - ShowCreation(self.number_line1, run_time = 5), - ) - self.wait() - - - odd_terms = VMobject() - for i in odd_range: - if i == 1: - term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", - fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) - else: - term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", - fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) - - term.next_to(self.number_line1.number_to_point(i), DOWN, buff = 1.5) - odd_terms.add(term) - - - for (ls, term) in zip(odd_lights.submobjects, odd_terms.submobjects): - self.play( - FadeIn(ls.lighthouse, run_time = switch_on_time), - SwitchOn(ls.ambient_light, run_time = switch_on_time), - Write(term, run_time = switch_on_time) - ) - - result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR, - stroke_color = LIGHT_COLOR) - result1.next_to(self.number_line1, LEFT, buff = 0.5) - result1.shift(0.87 * vertical_spacing) - self.play(Write(result1)) - - - - - self.number_line2 = self.number_line1.copy() - self.number_line2.numbers_to_show = full_range - self.number_line2.shift(2 * vertical_spacing) - self.number_line2.add_numbers() - - full_lights = VMobject() - - for i in full_range: - pos = self.number_line2.number_to_point(i) - ls = ls_template.copy() - ls.color = LIGHT_COLOR3 - ls.move_source_to(pos) - full_lights.add(ls) - - self.play( - ShowCreation(self.number_line2, run_time = 5), - ) - self.wait() - - - full_lighthouses = VMobject() - full_ambient_lights = VMobject() - for ls in full_lights: - full_lighthouses.add(ls.lighthouse) - full_ambient_lights.add(ls.ambient_light) - - self.play( - LaggedStartMap(FadeIn, full_lighthouses, lag_ratio = 0.2, run_time = 3), - ) - - self.play( - LaggedStartMap(SwitchOn, full_ambient_lights, lag_ratio = 0.2, run_time = 3) - ) - - # for ls in full_lights.submobjects: - # self.play( - # FadeIn(ls.lighthouse, run_time = 0.1),#5 * switch_on_time), - # SwitchOn(ls.ambient_light, run_time = 0.1)#5 * switch_on_time), - # ) - - - - even_terms = VMobject() - for i in even_range: - term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR) - term.next_to(self.number_line1.number_to_point(i), DOWN, buff = sum_vertical_spacing) - even_terms.add(term) - - - even_lights = VMobject() - - for i in even_range: - pos = self.number_line1.number_to_point(i) - ls = ls_template.copy() - ls.color = LIGHT_COLOR2 - ls.move_source_to(pos) - even_lights.add(ls) - - for (ls, term) in zip(even_lights.submobjects, even_terms.submobjects): - self.play( - SwitchOn(ls.ambient_light, run_time = switch_on_time), - Write(term) - ) - self.wait() - - - - # now morph the even lights into the full lights - full_lights_copy = full_lights.copy() - even_lights_copy = even_lights.copy() - - - self.play( - Transform(even_lights,full_lights, run_time = 2) - ) - - - self.wait() - - for i in range(6): - self.play( - Transform(even_lights[i], even_lights_copy[i]) - ) - self.wait() - - # draw arrows - P1 = self.number_line2.number_to_point(1) - P2 = even_terms.submobjects[0].get_center() - Q1 = interpolate(P1, P2, 0.2) - Q2 = interpolate(P1, P2, 0.8) - quarter_arrow = Arrow(Q1, Q2, - color = LIGHT_COLOR2) - quarter_label = TexMobject("\\times {1\over 4}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR2) - quarter_label.scale(0.7) - quarter_label.next_to(quarter_arrow.get_center(), RIGHT) - - self.play( - ShowCreation(quarter_arrow), - Write(quarter_label), - ) - self.wait() - - P3 = odd_terms.submobjects[0].get_center() - R1 = interpolate(P1, P3, 0.2) - R2 = interpolate(P1, P3, 0.8) - three_quarters_arrow = Arrow(R1, R2, - color = LIGHT_COLOR) - three_quarters_label = TexMobject("\\times {3\over 4}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) - three_quarters_label.scale(0.7) - three_quarters_label.next_to(three_quarters_arrow.get_center(), LEFT) - - self.play( - ShowCreation(three_quarters_arrow), - Write(three_quarters_label) - ) - self.wait() - - four_thirds_arrow = Arrow(R2, R1, color = LIGHT_COLOR) - four_thirds_label = TexMobject("\\times {4\over 3}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) - four_thirds_label.scale(0.7) - four_thirds_label.next_to(four_thirds_arrow.get_center(), LEFT) - - - self.play( - FadeOut(quarter_label), - FadeOut(quarter_arrow), - FadeOut(even_lights), - FadeOut(even_terms) - - ) - self.wait() - - self.play( - ReplacementTransform(three_quarters_arrow, four_thirds_arrow), - ReplacementTransform(three_quarters_label, four_thirds_label) - ) - self.wait() - - full_terms = VMobject() - for i in range(1,8): #full_range: - if i == 1: - term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) - elif i == 7: - term = TexMobject("+\,\,\,\dots", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) - else: - term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) - - term.move_to(self.number_line2.number_to_point(i)) - full_terms.add(term) - - #return - - self.play( - FadeOut(self.number_line1), - FadeOut(odd_lights), - FadeOut(self.number_line2), - FadeOut(full_lights), - FadeIn(full_terms) - ) - self.wait() - - v = (sum_vertical_spacing + 0.5) * UP - self.play( - odd_terms.shift, v, - result1.shift, v, - four_thirds_arrow.shift, v, - four_thirds_label.shift, v, - odd_terms.shift, v, - full_terms.shift, v - ) - - arrow_copy = four_thirds_arrow.copy() - label_copy = four_thirds_label.copy() - arrow_copy.shift(2.5 * LEFT) - label_copy.shift(2.5 * LEFT) - - self.play( - FadeIn(arrow_copy), - FadeIn(label_copy) - ) - self.wait() - - final_result = TexMobject("{\pi^2 \over 6}=", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) - final_result.next_to(arrow_copy, DOWN) - - self.play( - Write(final_result), - randy.change_mode,"hooray" - ) - self.wait() - - equation = VMobject() - equation.add(final_result) - equation.add(full_terms) - - - self.play( - FadeOut(result1), - FadeOut(odd_terms), - FadeOut(arrow_copy), - FadeOut(label_copy), - FadeOut(four_thirds_arrow), - FadeOut(four_thirds_label), - full_terms.shift,LEFT, - ) - self.wait() - - self.play(equation.shift, -equation.get_center()[1] * UP + UP + 1.5 * LEFT) - - result_box = Rectangle(width = 1.1 * equation.get_width(), - height = 2 * equation.get_height(), color = LIGHT_COLOR3) - result_box.move_to(equation) - - - self.play( - ShowCreation(result_box) - ) - self.wait() - - - - - - - - - - - - - - - - - -class LabeledArc(Arc): - CONFIG = { - "length" : 1 - } - - def __init__(self, angle, **kwargs): - - BUFFER = 1.3 - - Arc.__init__(self,angle,**kwargs) - - label = DecimalNumber(self.length, num_decimal_places = 0) - r = BUFFER * self.radius - theta = self.start_angle + self.angle/2 - label_pos = r * np.array([np.cos(theta), np.sin(theta), 0]) - - label.move_to(label_pos) - self.add(label) - - - - - - - -class ArcHighlightOverlayScene(Scene): - - def construct(self): - - BASELINE_YPOS = -2.5 - OBSERVER_POINT = [0,BASELINE_YPOS,0] - LAKE0_RADIUS = 1.5 - INDICATOR_RADIUS = 0.6 - TICK_SIZE = 0.5 - LIGHTHOUSE_HEIGHT = 0.2 - LAKE_COLOR = BLUE - LAKE_OPACITY = 0.15 - LAKE_STROKE_WIDTH = 5.0 - LAKE_STROKE_COLOR = BLUE - TEX_SCALE = 0.8 - DOT_COLOR = BLUE - - FLASH_TIME = 0.25 - - def flash_arcs(n): - - angle = TAU/2**n - arcs = [] - arcs.append(LabeledArc(angle/2, start_angle = -TAU/4, radius = LAKE0_RADIUS, length = 1)) - - for i in range(1,2**n): - arcs.append(LabeledArc(angle, start_angle = -TAU/4 + (i-0.5)*angle, radius = LAKE0_RADIUS, length = 2)) - - arcs.append(LabeledArc(angle/2, start_angle = -TAU/4 - angle/2, radius = LAKE0_RADIUS, length = 1)) - - self.play( - FadeIn(arcs[0], run_time = FLASH_TIME) - ) - - for i in range(1,2**n + 1): - self.play( - FadeOut(arcs[i-1], run_time = FLASH_TIME), - FadeIn(arcs[i], run_time = FLASH_TIME) - ) - - self.play( - FadeOut(arcs[2**n], run_time = FLASH_TIME), - ) - - - -class ThumbnailScene(Scene): - - def construct(self): - - equation = TexMobject("1+{1\over 4}+{1\over 9}+{1\over 16}+{1\over 25}+\dots") - equation.scale(1.5) - equation.move_to(1.5 * UP) - q_mark = TexMobject("=?", color = LIGHT_COLOR).scale(5) - q_mark.next_to(equation, DOWN, buff = 1.5) - #equation.move_to(2 * UP) - #q_mark = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3) - #q_mark.next_to(equation, DOWN, buff = 1) - - lake_radius = 6 - lake_center = ORIGIN - op_scale = 0.4 - - lake = Circle( - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY, - radius = lake_radius, - stroke_color = LAKE_STROKE_COLOR, - stroke_width = LAKE_STROKE_WIDTH, - ) - lake.move_to(lake_center) - - for i in range(16): - theta = -TAU/4 + (i + 0.5) * TAU/16 - pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0]) - ls = LightSource( - radius = 15.0, - num_levels = 150, - max_opacity_ambient = 1.0, - opacity_function = inverse_quadratic(1,op_scale,1) - ) - ls.move_source_to(pos) - lake.add(ls.ambient_light, ls.lighthouse) - - self.add(lake) - - self.add(equation, q_mark) - - self.wait() - - - - -class InfiniteCircleScene(PiCreatureScene): - - def construct(self): - - morty = self.get_primary_pi_creature() - morty.set_color(MAROON_D).flip() - morty.color = MAROON_D - morty.scale(0.5).move_to(ORIGIN) - - arrow = Arrow(ORIGIN, 2.4 * RIGHT) - dot = Dot(color = BLUE).next_to(arrow) - ellipsis = TexMobject("\dots") - - infsum = VGroup() - infsum.add(ellipsis.copy()) - - for i in range(3): - infsum.add(arrow.copy().next_to(infsum.submobjects[-1])) - infsum.add(dot.copy().next_to(infsum.submobjects[-1])) - - infsum.add(arrow.copy().next_to(infsum.submobjects[-1])) - infsum.add(ellipsis.copy().next_to(infsum.submobjects[-1])) - - infsum.next_to(morty,DOWN, buff = 1) - - self.wait() - self.play( - LaggedStartMap(FadeIn,infsum,lag_ratio = 0.2) - ) - self.wait() - - A = infsum.submobjects[-1].get_center() + 0.5 * RIGHT - B = A + RIGHT + 1.3 * UP + 0.025 * LEFT - right_arc = DashedLine(TAU/4*UP, ORIGIN, stroke_color = YELLOW, - stroke_width = 8).apply_complex_function(np.exp) - right_arc.rotate(-TAU/4).next_to(infsum, RIGHT).shift(0.5 * UP) - right_tip_line = Arrow(B - UP, B, color = WHITE) - right_tip_line.add_tip() - right_tip = right_tip_line.get_tip() - right_tip.set_fill(color = YELLOW) - right_arc.add(right_tip) - - - C = B + 3.2 * UP - right_line = DashedLine(B + 0.2 * DOWN,C + 0.2 * UP, stroke_color = YELLOW, - stroke_width = 8) - - ru_arc = right_arc.copy().rotate(angle = TAU/4) - ru_arc.remove(ru_arc.submobjects[-1]) - ru_arc.to_edge(UP+RIGHT, buff = 0.15) - - D = np.array([5.85, 3.85,0]) - E = np.array([-D[0],D[1],0]) - up_line = DashedLine(D, E, stroke_color = YELLOW, - stroke_width = 8) - - lu_arc = ru_arc.copy().flip().to_edge(LEFT + UP, buff = 0.15) - left_line = right_line.copy().flip(axis = RIGHT).to_edge(LEFT, buff = 0.15) - - left_arc = right_arc.copy().rotate(-TAU/4) - left_arc.next_to(infsum, LEFT).shift(0.5 * UP + 0.1 * LEFT) - - right_arc.shift(0.2 * RIGHT) - right_line.shift(0.2 * RIGHT) - - self.play(FadeIn(right_arc)) - self.play(ShowCreation(right_line)) - self.play(FadeIn(ru_arc)) - self.play(ShowCreation(up_line)) - self.play(FadeIn(lu_arc)) - self.play(ShowCreation(left_line)) - self.play(FadeIn(left_arc)) - - - - self.wait() - - - - -class RightAnglesOverlay(Scene): - - def construct(self): - - BASELINE_YPOS = -2.5 - OBSERVER_POINT = [0,BASELINE_YPOS,0] - LAKE0_RADIUS = 1.5 * 2 - INDICATOR_RADIUS = 0.6 - TICK_SIZE = 0.5 - LIGHTHOUSE_HEIGHT = 0.2 - LAKE_COLOR = BLUE - LAKE_OPACITY = 0.15 - LAKE_STROKE_WIDTH = 5.0 - LAKE_STROKE_COLOR = BLUE - TEX_SCALE = 0.8 - DOT_COLOR = BLUE - - RIGHT_ANGLE_SIZE = 0.3 - - - def right_angle(pointA, pointB, pointC, size = 1): - - v1 = pointA - pointB - v1 = size * v1/get_norm(v1) - v2 = pointC - pointB - v2 = size * v2/get_norm(v2) - - P = pointB - Q = pointB + v1 - R = Q + v2 - S = R - v1 - angle_sign = VMobject() - angle_sign.set_points_as_corners([P,Q,R,S,P]) - angle_sign.mark_paths_closed = True - angle_sign.set_fill(color = WHITE, opacity = 1) - angle_sign.set_stroke(width = 0) - return angle_sign - - - lake_center = OBSERVER_POINT + LAKE0_RADIUS * UP - points = [] - lines = VGroup() - for i in range(4): - theta = -TAU/4 + (i+0.5)*TAU/4 - v = np.array([np.cos(theta), np.sin(theta), 0]) - P = lake_center + LAKE0_RADIUS * v - points.append(P) - lines.add(Line(lake_center, P, stroke_width = 8)) - - self.play(FadeIn(lines)) - - self.wait() - - for i in range(4): - sign = right_angle(points[i-1], lake_center, points[i],RIGHT_ANGLE_SIZE) - self.play(FadeIn(sign)) - self.play(FadeOut(sign)) - - self.wait() - - self.play(FadeOut(lines)) - - flash_arcs(3) diff --git a/from_3b1b/old/basel/basel2.py b/from_3b1b/old/basel/basel2.py deleted file mode 100644 index 40147c9b..00000000 --- a/from_3b1b/old/basel/basel2.py +++ /dev/null @@ -1,4629 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - - -from manimlib.imports import * -from once_useful_constructs.light import * - -import warnings -warnings.warn(""" - Warning: This file makes use of - ContinualAnimation, which has since - been deprecated -""") - - -import types -import functools - -LIGHT_COLOR = YELLOW -INDICATOR_RADIUS = 0.7 -INDICATOR_STROKE_WIDTH = 1 -INDICATOR_STROKE_COLOR = WHITE -INDICATOR_TEXT_COLOR = WHITE -INDICATOR_UPDATE_TIME = 0.2 -FAST_INDICATOR_UPDATE_TIME = 0.1 -OPACITY_FOR_UNIT_INTENSITY = 0.2 -SWITCH_ON_RUN_TIME = 1.5 -FAST_SWITCH_ON_RUN_TIME = 0.1 -NUM_LEVELS = 30 -NUM_CONES = 7 # in first lighthouse scene -NUM_VISIBLE_CONES = 5 # ibidem -ARC_TIP_LENGTH = 0.2 -AMBIENT_FULL = 0.5 -AMBIENT_DIMMED = 0.2 -SPOTLIGHT_FULL = 0.9 -SPOTLIGHT_DIMMED = 0.2 - -LIGHT_COLOR = YELLOW -DEGREES = TAU/360 - -inverse_power_law = lambda maxint,scale,cutoff,exponent: \ - (lambda r: maxint * (cutoff/(r/scale+cutoff))**exponent) -inverse_quadratic = lambda maxint,scale,cutoff: inverse_power_law(maxint,scale,cutoff,2) - -# A = np.array([5.,-3.,0.]) -# B = np.array([-5.,3.,0.]) -# C = np.array([-5.,-3.,0.]) -# xA = A[0] -# yA = A[1] -# xB = B[0] -# yB = B[1] -# xC = C[0] -# yC = C[1] - -# find the coords of the altitude point H -# as the solution of a certain LSE -# prelim_matrix = np.array([[yA - yB, xB - xA], [xA - xB, yA - yB]]) # sic -# prelim_vector = np.array([xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)]) -# H2 = np.linalg.solve(prelim_matrix,prelim_vector) -# H = np.append(H2, 0.) - -class AngleUpdater(ContinualAnimation): - def __init__(self, angle_arc, spotlight, **kwargs): - self.angle_arc = angle_arc - - self.spotlight = spotlight - ContinualAnimation.__init__(self, self.angle_arc, **kwargs) - - def update_mobject(self, dt): - new_arc = self.angle_arc.copy().set_bound_angles( - start = self.spotlight.start_angle(), - stop = self.spotlight.stop_angle() - ) - new_arc.init_points() - new_arc.move_arc_center_to(self.spotlight.get_source_point()) - self.angle_arc.points = new_arc.points - self.angle_arc.add_tip( - tip_length = ARC_TIP_LENGTH, - at_start = True, at_end = True - ) - -class LightIndicator(Mobject): - CONFIG = { - "radius": 0.5, - "reading_height" : 0.25, - "intensity": 0, - "opacity_for_unit_intensity": 1, - "fill_color" : YELLOW, - "precision": 3, - "show_reading": True, - "measurement_point": ORIGIN, - "light_source": None - } - - def init_points(self): - self.background = Circle(color=BLACK, radius = self.radius) - self.background.set_fill(opacity = 1.0) - self.foreground = Circle(color=self.color, radius = self.radius) - self.foreground.set_stroke( - color=INDICATOR_STROKE_COLOR, - width=INDICATOR_STROKE_WIDTH - ) - self.foreground.set_fill(color = self.fill_color) - - self.add(self.background, self.foreground) - self.reading = DecimalNumber(self.intensity,num_decimal_places = self.precision) - self.reading.set_fill(color=INDICATOR_TEXT_COLOR) - self.reading.set_height(self.reading_height) - self.reading.move_to(self.get_center()) - if self.show_reading: - self.add(self.reading) - - def set_intensity(self, new_int): - self.intensity = new_int - new_opacity = min(1, new_int * self.opacity_for_unit_intensity) - self.foreground.set_fill(opacity=new_opacity) - ChangeDecimalToValue(self.reading, new_int).update(1) - if new_int > 1.1: - self.reading.set_fill(color = BLACK) - else: - self.reading.set_fill(color = WHITE) - return self - - def get_measurement_point(self): - if self.measurement_point is not None: - return self.measurement_point - else: - return self.get_center() - - def measured_intensity(self): - distance = get_norm( - self.get_measurement_point() - - self.light_source.get_source_point() - ) - intensity = self.light_source.opacity_function(distance) / self.opacity_for_unit_intensity - return intensity - - def update_mobjects(self): - if self.light_source == None: - print("Indicator cannot update, reason: no light source found") - self.set_intensity(self.measured_intensity()) - -class UpdateLightIndicator(AnimationGroup): - - def __init__(self, indicator, intensity, **kwargs): - if not isinstance(indicator,LightIndicator): - raise Exception("This transform applies only to LightIndicator") - - target_foreground = indicator.copy().set_intensity(intensity).foreground - change_opacity = Transform( - indicator.foreground, target_foreground - ) - changing_decimal = ChangeDecimalToValue(indicator.reading, intensity) - - AnimationGroup.__init__(self, changing_decimal, change_opacity, **kwargs) - self.mobject = indicator - -class ContinualLightIndicatorUpdate(ContinualAnimation): - def update_mobject(self,dt): - self.mobject.update_mobjects() - -def copy_func(f): - """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)""" - g = types.FunctionType(f.__code__, f.__globals__, name=f.__name__, - argdefs=f.__defaults__, - closure=f.__closure__) - g = functools.update_wrapper(g, f) - return g - -class ScaleLightSources(Transform): - - def __init__(self, light_sources_mob, factor, about_point = None, **kwargs): - - if about_point == None: - about_point = light_sources_mob.get_center() - - ls_target = light_sources_mob.copy() - - for submob in ls_target: - - if type(submob) == LightSource: - - new_sp = submob.source_point.copy() # a mob - new_sp.scale(factor,about_point = about_point) - submob.move_source_to(new_sp.get_location()) - - #ambient_of = copy_func(submob.ambient_light.opacity_function) - #new_of = lambda r: ambient_of(r/factor) - #submob.ambient_light.opacity_function = new_of - - #spotlight_of = copy_func(submob.ambient_light.opacity_function) - #new_of = lambda r: spotlight_of(r/factor) - #submob.spotlight.change_opacity_function(new_of) - - new_r = factor * submob.radius - submob.set_radius(new_r) - - new_r = factor * submob.ambient_light.radius - submob.ambient_light.radius = new_r - - new_r = factor * submob.spotlight.radius - submob.spotlight.radius = new_r - - submob.ambient_light.scale_about_point(factor, new_sp.get_center()) - submob.spotlight.scale_about_point(factor, new_sp.get_center()) - - - Transform.__init__(self,light_sources_mob,ls_target,**kwargs) - -class ThreeDSpotlight(VGroup): - CONFIG = { - "fill_color" : YELLOW, - } - def __init__(self, screen, ambient_light, source_point_func, **kwargs): - self.screen = screen - self.ambient_light = ambient_light - self.source_point_func = source_point_func - self.dr = ambient_light.radius/ambient_light.num_levels - VGroup.__init__(self, **kwargs) - - def update(self): - screen = self.screen - source_point = self.source_point_func() - dr = self.dr - corners = screen.get_anchors() - self.submobjects = [VGroup() for a in screen.get_anchors()] - - distance = get_norm( - screen.get_center() - source_point - ) - n_parts = np.ceil(distance/dr) - alphas = np.linspace(0, 1, n_parts+1) - for face, (c1, c2) in zip(self, adjacent_pairs(corners)): - face.submobjects = [] - for a1, a2 in zip(alphas, alphas[1:]): - face.add(Polygon( - interpolate(source_point, c1, a1), - interpolate(source_point, c1, a2), - interpolate(source_point, c2, a2), - interpolate(source_point, c2, a1), - fill_color = self.fill_color, - fill_opacity = self.ambient_light.opacity_function(a1*distance), - stroke_width = 0 - )) - -class ContinualThreeDLightConeUpdate(ContinualAnimation): - def update(self, dt): - self.mobject.update() - -### - -class ThinkAboutPondScene(PiCreatureScene): - CONFIG = { - "default_pi_creature_class" : Randolph, - } - def construct(self): - randy = self.pi_creature - randy.to_corner(DOWN+LEFT) - bubble = ThoughtBubble( - width = 11, - height = 8, - ) - circles = bubble[:3] - angle = -15*DEGREES - circles.rotate(angle, about_point = bubble.get_bubble_center()) - circles.shift(LARGE_BUFF*LEFT) - for circle in circles: - circle.rotate(-angle) - bubble.pin_to(randy) - bubble.shift(DOWN) - bubble[:3].rotate(np.pi, axis = UP+2*RIGHT, about_edge = UP+LEFT) - bubble[:3].scale(0.7, about_edge = DOWN+RIGHT) - bubble[:3].shift(1.5*DOWN) - for oval in bubble[:3]: - oval.rotate(TAU/3) - - self.play( - randy.change, "thinking", - ShowCreation(bubble) - ) - self.wait(2) - self.play(randy.change, "happy", bubble) - self.wait(4) - self.play(randy.change, "hooray", bubble) - self.wait(2) - -class IntroScene(PiCreatureScene): - CONFIG = { - "rect_height" : 0.075, - "duration" : 1.0, - "eq_spacing" : 3 * MED_LARGE_BUFF, - "n_rects_to_show" : 30, - } - - def construct(self): - randy = self.get_primary_pi_creature() - randy.scale(0.7).to_corner(DOWN+RIGHT) - - self.build_up_euler_sum() - self.show_history() - # self.other_pi_formulas() - # self.refocus_on_euler_sum() - - def build_up_euler_sum(self): - morty = self.pi_creature - euler_sum = self.euler_sum = TexMobject( - "1", "+", - "{1 \\over 4}", "+", - "{1 \\over 9}", "+", - "{1 \\over 16}", "+", - "{1 \\over 25}", "+", - "\\cdots", "=", - arg_separator = " \\, " - ) - equals_sign = euler_sum.get_part_by_tex("=") - plusses = euler_sum.get_parts_by_tex("+") - term_mobjects = euler_sum.get_parts_by_tex("1") - - self.euler_sum.to_edge(UP) - self.euler_sum.shift(2*LEFT) - - max_n = self.n_rects_to_show - terms = [1./(n**2) for n in range(1, max_n + 1)] - series_terms = list(np.cumsum(terms)) - series_terms.append(np.pi**2/6) ##Just force this up there - - partial_sum_decimal = self.partial_sum_decimal = DecimalNumber( - series_terms[1], - num_decimal_places = 2 - ) - partial_sum_decimal.next_to(equals_sign, RIGHT) - - ## Number line - - number_line = self.number_line = NumberLine( - x_min = 0, - color = WHITE, - number_at_center = 1, - stroke_width = 1, - numbers_with_elongated_ticks = [0,1,2,3], - numbers_to_show = np.arange(0,5), - unit_size = 5, - tick_frequency = 0.2, - line_to_number_buff = MED_LARGE_BUFF - ) - number_line.add_numbers() - number_line.to_edge(LEFT) - number_line.shift(MED_LARGE_BUFF*UP) - - # create slabs for series terms - - lines = VGroup() - rects = self.rects = VGroup() - rect_labels = VGroup() - slab_colors = it.cycle([YELLOW, BLUE]) - rect_anims = [] - rect_label_anims = [] - - for i, t1, t2 in zip(it.count(1), [0]+series_terms, series_terms): - color = next(slab_colors) - line = Line(*list(map(number_line.number_to_point, [t1, t2]))) - rect = Rectangle( - stroke_width = 0, - fill_opacity = 1, - fill_color = color - ) - rect.match_width(line) - rect.stretch_to_fit_height(self.rect_height) - rect.move_to(line) - - if i <= 5: - if i == 1: - rect_label = TexMobject("1") - else: - rect_label = TexMobject("\\frac{1}{%d}"%(i**2)) - rect_label.scale(0.75) - max_width = 0.7*rect.get_width() - if rect_label.get_width() > max_width: - rect_label.set_width(max_width) - rect_label.next_to(rect, UP, buff = MED_LARGE_BUFF/(i+1)) - - term_mobject = term_mobjects[i-1] - rect_anim = GrowFromPoint(rect, term_mobject.get_center()) - rect_label_anim = ReplacementTransform( - term_mobject.copy(), rect_label - ) - else: - rect_label = VectorizedPoint() - rect_anim = GrowFromPoint(rect, rect.get_left()) - rect_label_anim = FadeIn(rect_label) - - rects.add(rect) - rect_labels.add(rect_label) - rect_anims.append(rect_anim) - rect_label_anims.append(rect_label_anim) - lines.add(line) - dots = TexMobject("\\dots").scale(0.5) - last_rect = rect_anims[-1].target_mobject - dots.set_width(0.9*last_rect.get_width()) - dots.move_to(last_rect, UP+RIGHT) - rects.submobjects[-1] = dots - rect_anims[-1] = FadeIn(dots) - - self.add(number_line) - self.play(FadeIn(euler_sum[0])) - self.play( - rect_anims[0], - rect_label_anims[0] - ) - for i in range(4): - self.play( - FadeIn(term_mobjects[i+1]), - FadeIn(plusses[i]), - ) - anims = [ - rect_anims[i+1], - rect_label_anims[i+1], - ] - if i == 0: - anims += [ - FadeIn(equals_sign), - FadeIn(partial_sum_decimal) - ] - elif i <= 5: - anims += [ - ChangeDecimalToValue( - partial_sum_decimal, - series_terms[i+1], - run_time = 1, - num_decimal_places = 6, - position_update_func = lambda m: m.next_to(equals_sign, RIGHT) - ) - ] - self.play(*anims) - - for i in range(4, len(series_terms)-2): - anims = [ - rect_anims[i+1], - ChangeDecimalToValue( - partial_sum_decimal, - series_terms[i+1], - num_decimal_places = 6, - ), - ] - if i == 5: - anims += [ - FadeIn(euler_sum[-3]), # + - FadeIn(euler_sum[-2]), # ... - ] - self.play(*anims, run_time = 2./i) - - brace = self.brace = Brace(partial_sum_decimal, DOWN) - q_marks = self.q_marks = TextMobject("???") - q_marks.next_to(brace, DOWN) - q_marks.set_color(LIGHT_COLOR) - - self.play( - GrowFromCenter(brace), - Write(q_marks), - ChangeDecimalToValue( - partial_sum_decimal, - series_terms[-1], - num_decimal_places = 6, - ), - morty.change, "confused", - ) - self.wait() - - self.number_line_group = VGroup( - number_line, rects, rect_labels - ) - - def show_history(self): - # Pietro Mengoli in 1644 - morty = self.pi_creature - pietro = ImageMobject("Pietro_Mengoli") - euler = ImageMobject("Euler") - - pietro_words = TextMobject("Challenge posed by \\\\ Pietro Mengoli in 1644") - pietro_words.scale(0.75) - pietro_words.next_to(pietro, DOWN) - pietro.add(pietro_words) - - euler_words = TextMobject("Solved by Leonard \\\\ Euler in 1735") - euler_words.scale(0.75) - euler_words.next_to(euler, DOWN) - euler.add(euler_words) - - pietro.next_to(FRAME_X_RADIUS*LEFT, LEFT) - euler.next_to(FRAME_X_RADIUS*RIGHT, RIGHT) - - pi_answer = self.pi_answer = TexMobject("{\\pi^2 \\over 6}") - pi_answer.set_color(YELLOW) - pi_answer.move_to(self.partial_sum_decimal, LEFT) - equals_sign = TexMobject("=") - equals_sign.next_to(pi_answer, RIGHT) - pi_answer.shift(SMALL_BUFF*UP) - self.partial_sum_decimal.generate_target() - self.partial_sum_decimal.target.next_to(equals_sign, RIGHT) - - pi = pi_answer[0] - pi_rect = SurroundingRectangle(pi, color = RED) - pi_rect.save_state() - pi_rect.set_height(FRAME_Y_RADIUS) - pi_rect.center() - pi_rect.set_stroke(width = 0) - squared = pi_answer[1] - squared_rect = SurroundingRectangle(squared, color = BLUE) - - brace = Brace( - VGroup(self.euler_sum, self.partial_sum_decimal.target), - DOWN, buff = SMALL_BUFF - ) - basel_text = brace.get_text("Basel problem", buff = SMALL_BUFF) - - self.number_line_group.save_state() - self.play( - pietro.next_to, ORIGIN, LEFT, LARGE_BUFF, - self.number_line_group.next_to, FRAME_Y_RADIUS*DOWN, DOWN, - morty.change, "pondering", - ) - self.wait(2) - self.play(euler.next_to, ORIGIN, RIGHT, LARGE_BUFF) - self.wait(2) - self.play( - ReplacementTransform(self.q_marks, pi_answer), - FadeIn(equals_sign), - FadeOut(self.brace), - MoveToTarget(self.partial_sum_decimal) - ) - self.wait() - self.play(morty.change, "surprised") - self.play(pi_rect.restore) - self.wait() - self.play(Transform(pi_rect, squared_rect)) - self.play(FadeOut(pi_rect)) - self.play(morty.change, "hesitant") - self.wait(2) - self.play( - GrowFromCenter(brace), - euler.to_edge, DOWN, - pietro.to_edge, DOWN, - self.number_line_group.restore, - self.number_line_group.shift, LARGE_BUFF*RIGHT, - ) - self.play(Write(basel_text)) - self.play(morty.change, "happy") - self.wait(4) - - def other_pi_formulas(self): - - self.play( - FadeOut(self.rects), - FadeOut(self.number_line) - ) - - self.leibniz_sum = TexMobject( - "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", - "=", "{\\pi \\over 4}") - - self.wallis_product = TexMobject( - "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + - "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", - "=", "{\\pi \\over 2}") - - self.leibniz_sum.next_to(self.euler_sum.get_part_by_tex("="), DOWN, - buff = self.eq_spacing, - submobject_to_align = self.leibniz_sum.get_part_by_tex("=") - ) - - self.wallis_product.next_to(self.leibniz_sum.get_part_by_tex("="), DOWN, - buff = self.eq_spacing, - submobject_to_align = self.wallis_product.get_part_by_tex("=") - ) - - - self.play( - Write(self.leibniz_sum) - ) - self.play( - Write(self.wallis_product) - ) - - def refocus_on_euler_sum(self): - - self.euler_sum.add(self.pi_answer) - - self.play( - FadeOut(self.leibniz_sum), - FadeOut(self.wallis_product), - ApplyMethod(self.euler_sum.shift, - ORIGIN + 2*UP - self.euler_sum.get_center()) - ) - - # focus on pi squared - pi_squared = self.euler_sum.get_part_by_tex("\\pi")[-3] - self.play( - ScaleInPlace(pi_squared,2,rate_func = wiggle) - ) - - - - # Morty thinks of a circle - - q_circle = Circle( - stroke_color = YELLOW, - fill_color = YELLOW, - fill_opacity = 0.5, - radius = 0.4, - stroke_width = 10.0 - ) - q_mark = TexMobject("?") - q_mark.next_to(q_circle) - - thought = Group(q_circle, q_mark) - q_mark.set_height(0.8 * q_circle.get_height()) - self.pi_creature_thinks(thought,target_mode = "confused", - bubble_kwargs = { "height" : 2, "width" : 3 }) - - self.wait() - -class PiHidingWrapper(Scene): - def construct(self): - title = TextMobject("Pi hiding in prime regularities") - title.to_edge(UP) - screen = ScreenRectangle(height = 6) - screen.next_to(title, DOWN) - self.add(title) - self.play(ShowCreation(screen)) - self.wait(2) - -class MathematicalWebOfConnections(PiCreatureScene): - def construct(self): - self.complain_that_pi_is_not_about_circles() - self.show_other_pi_formulas() - self.question_fundamental() - self.draw_circle() - self.remove_all_but_basel_sum() - self.show_web_of_connections() - self.show_light() - - def complain_that_pi_is_not_about_circles(self): - jerk, randy = self.pi_creatures - - words = self.words = TextMobject( - "I am not", - "fundamentally \\\\", - "about circles" - ) - words.set_color_by_tex("fundamentally", YELLOW) - - self.play(PiCreatureSays( - jerk, words, - target_mode = "angry" - )) - self.play(randy.change, "guilty") - self.wait(2) - - def show_other_pi_formulas(self): - jerk, randy = self.pi_creatures - words = self.words - - basel_sum = TexMobject( - "1 + {1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots", - "=", "{\\pi^2 \\over 6}" - ) - leibniz_sum = TexMobject( - "1-{1\\over 3}+{1\\over 5}-{1\\over 7}+{1\\over 9}-\\cdots", - "=", "{\\pi \\over 4}") - - wallis_product = TexMobject( - "{2\\over 1} \\cdot {2\\over 3} \\cdot {4\\over 3} \\cdot {4\\over 5}" + - "\\cdot {6\\over 5} \\cdot {6\\over 7} \\cdots", - "=", "{\\pi \\over 2}") - - basel_sum.move_to(randy) - basel_sum.to_edge(UP) - basel_equals = basel_sum.get_part_by_tex("=") - - formulas = VGroup(basel_sum, leibniz_sum, wallis_product) - formulas.scale(0.75) - formulas.arrange(DOWN, buff = MED_LARGE_BUFF) - for formula in formulas: - basel_equals_x = basel_equals.get_center()[0] - formula_equals_x = formula.get_part_by_tex("=").get_center()[0] - formula.shift((basel_equals_x - formula_equals_x)*RIGHT) - - formulas.to_corner(UP+RIGHT) - formulas.shift(2*LEFT) - self.formulas = formulas - - self.play( - jerk.change, "sassy", - randy.change, "raise_right_hand", - FadeOut(jerk.bubble), - words.next_to, jerk, UP, - FadeIn(basel_sum, lag_ratio = 0.5, run_time = 3) - ) - for formula in formulas[1:]: - self.play( - FadeIn( - formula, - lag_ratio = 0.5, - run_time = 3 - ), - ) - self.wait() - - def question_fundamental(self): - jerk, randy = self.pi_creatures - words = self.words - fundamentally = words.get_part_by_tex("fundamentally") - words.remove(fundamentally) - - self.play( - fundamentally.move_to, self.pi_creatures, - fundamentally.shift, UP, - FadeOut(words), - jerk.change, "pondering", - randy.change, "pondering", - ) - self.wait() - - question = TextMobject("Does this mean \\\\ anything?") - question.scale(0.8) - question.set_stroke(WHITE, 0.5) - question.next_to(fundamentally, DOWN, LARGE_BUFF) - arrow = Arrow(question, fundamentally) - arrow.set_color(WHITE) - - self.play( - FadeIn(question), - GrowArrow(arrow) - ) - self.wait() - - fundamentally.add(question, arrow) - self.fundamentally = fundamentally - - def draw_circle(self): - semi_circle = Arc(angle = np.pi, radius = 2) - radius = Line(ORIGIN, semi_circle.points[0]) - radius.set_color(BLUE) - semi_circle.set_color(YELLOW) - - VGroup(radius, semi_circle).move_to( - FRAME_X_RADIUS*LEFT/2 + FRAME_Y_RADIUS*UP/2, - ) - - decimal = DecimalNumber(0) - def decimal_position_update_func(decimal): - decimal.move_to(semi_circle.points[-1]) - decimal.shift(0.3*radius.get_vector()) - - one = TexMobject("1") - one.next_to(radius, UP) - - self.play(ShowCreation(radius), FadeIn(one)) - self.play( - Rotate(radius, np.pi, about_point = radius.get_start()), - ShowCreation(semi_circle), - ChangeDecimalToValue( - decimal, np.pi, - position_update_func = decimal_position_update_func - ), - MaintainPositionRelativeTo(one, radius), - run_time = 3, - ) - self.wait(2) - - self.circle_group = VGroup(semi_circle, radius, one, decimal) - - def remove_all_but_basel_sum(self): - to_shift_down = VGroup( - self.circle_group, self.pi_creatures, - self.fundamentally, self.formulas[1:], - ) - to_shift_down.generate_target() - for part in to_shift_down.target: - part.move_to(FRAME_HEIGHT*DOWN) - - basel_sum = self.formulas[0] - - self.play( - MoveToTarget(to_shift_down), - basel_sum.scale, 1.5, - basel_sum.move_to, 1.5*DOWN, - ) - - self.basel_sum = basel_sum - - def show_web_of_connections(self): - self.remove(self.pi_creatures) - title = TextMobject("Interconnected web of mathematics") - title.to_edge(UP) - basel_sum = self.basel_sum - - dots = VGroup(*[ - Dot(radius = 0.1).move_to( - (j - 0.5*(i%2))*RIGHT + \ - (np.sqrt(3)/2.0)* i*DOWN + \ - 0.5*(random.random()*RIGHT + random.random()*UP), - ) - for i in range(4) - for j in range(7+(i%2)) - ]) - dots.set_height(3) - dots.next_to(title, DOWN, MED_LARGE_BUFF) - edges = VGroup() - for x in range(100): - d1, d2 = random.sample(dots, 2) - edge = Line(d1.get_center(), d2.get_center()) - edge.set_stroke(YELLOW, 0.5) - edges.add(edge) - - ## Choose special path - path_dots = VGroup( - dots[-7], - dots[-14], - dots[9], - dots[19], - dots[14], - ) - path_edges = VGroup(*[ - Line( - d1.get_center(), d2.get_center(), - color = RED - ) - for d1, d2 in zip(path_dots, path_dots[1:]) - ]) - - circle = Circle(color = YELLOW, radius = 1) - radius = Line(circle.get_center(), circle.get_right()) - radius.set_color(BLUE) - VGroup(circle, radius).next_to(path_dots[-1], RIGHT) - - self.play( - Write(title), - LaggedStartMap(ShowCreation, edges, run_time = 3), - LaggedStartMap(GrowFromCenter, dots, run_time = 3) - ) - self.play(path_dots[0].set_color, RED) - for dot, edge in zip(path_dots[1:], path_edges): - self.play( - ShowCreation(edge), - dot.set_color, RED - ) - self.play(ShowCreation(radius)) - radius.set_points_as_corners(radius.get_anchors()) - self.play( - ShowCreation(circle), - Rotate(radius, angle = 0.999*TAU, about_point = radius.get_start()), - run_time = 2 - ) - self.wait() - - graph = VGroup(dots, edges, path_edges, title) - circle.add(radius) - basel_sum.generate_target() - basel_sum.target.to_edge(UP) - - arrow = Arrow( - UP, DOWN, - rectangular_stem_width = 0.1, - tip_length = 0.45, - color = RED, - ) - arrow.next_to(basel_sum.target, DOWN, buff = MED_LARGE_BUFF) - - self.play( - MoveToTarget(basel_sum), - graph.next_to, basel_sum.target, UP, LARGE_BUFF, - circle.next_to, arrow, DOWN, MED_LARGE_BUFF, - ) - self.play(GrowArrow(arrow)) - self.wait() - - self.arrow = arrow - self.circle = circle - - def show_light(self): - light = AmbientLight( - num_levels = 500, radius = 13, - opacity_function = lambda r : 1.0/(r+1), - ) - pi = self.basel_sum[-1][0] - pi.set_stroke(BLACK, 0.5) - light.move_to(pi) - self.play( - SwitchOn(light, run_time = 3), - Animation(self.arrow), - Animation(self.circle), - Animation(self.basel_sum), - ) - self.wait() - - ### - - def create_pi_creatures(self): - jerk = PiCreature(color = GREEN_D) - randy = Randolph().flip() - jerk.move_to(0.5*FRAME_X_RADIUS*LEFT).to_edge(DOWN) - randy.move_to(0.5*FRAME_X_RADIUS*RIGHT).to_edge(DOWN) - - return VGroup(jerk, randy) - -class FirstLighthouseScene(PiCreatureScene): - CONFIG = { - "num_levels" : 100, - "opacity_function" : inverse_quadratic(1,2,1), - } - def construct(self): - self.remove(self.pi_creature) - self.show_lighthouses_on_number_line() - self.describe_brightness_of_each() - self.ask_about_rearrangements() - - def show_lighthouses_on_number_line(self): - number_line = self.number_line = NumberLine( - x_min = 0, - color = WHITE, - number_at_center = 1.6, - stroke_width = 1, - numbers_with_elongated_ticks = list(range(1,6)), - numbers_to_show = list(range(1,6)), - unit_size = 2, - tick_frequency = 0.2, - line_to_number_buff = LARGE_BUFF, - label_direction = DOWN, - ) - - number_line.add_numbers() - self.add(number_line) - - origin_point = number_line.number_to_point(0) - - morty = self.pi_creature - morty.scale(0.75) - morty.flip() - right_pupil = morty.eyes[1] - morty.next_to(origin_point, LEFT, buff = 0, submobject_to_align = right_pupil) - - - light_sources = VGroup() - for i in range(1,NUM_CONES+1): - light_source = LightSource( - opacity_function = self.opacity_function, - num_levels = self.num_levels, - radius = 12.0, - ) - point = number_line.number_to_point(i) - light_source.move_source_to(point) - light_sources.add(light_source) - - lighthouses = self.lighthouses = VGroup(*[ - ls.lighthouse - for ls in light_sources[:NUM_VISIBLE_CONES+1] - ]) - - morty.save_state() - morty.scale(3) - morty.fade(1) - morty.center() - self.play(morty.restore) - self.play( - morty.change, "pondering", - LaggedStartMap( - FadeIn, lighthouses, - run_time = 1 - ) - ) - self.play(LaggedStartMap( - SwitchOn, VGroup(*[ - ls.ambient_light - for ls in light_sources - ]), - run_time = 5, - lag_ratio = 0.1, - rate_func = rush_into, - ), Animation(lighthouses)) - self.wait() - - self.light_sources = light_sources - - def describe_brightness_of_each(self): - number_line = self.number_line - morty = self.pi_creature - light_sources = self.light_sources - lighthouses = self.lighthouses - - light_indicator = LightIndicator( - radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - color = LIGHT_COLOR - ) - light_indicator.reading.scale(0.8) - light_indicator.set_intensity(0) - intensities = np.cumsum(np.array([1./n**2 for n in range(1,NUM_CONES+1)])) - opacities = intensities * light_indicator.opacity_for_unit_intensity - - bubble = ThoughtBubble( - direction = RIGHT, - width = 2.5, height = 3.5 - ) - bubble.pin_to(morty) - bubble.add_content(light_indicator) - - euler_sum_above = TexMobject( - "1", "+", - "{1\over 4}", "+", - "{1\over 9}", "+", - "{1\over 16}", "+", - "{1\over 25}", "+", - "{1\over 36}" - ) - euler_sum_terms = euler_sum_above[::2] - plusses = euler_sum_above[1::2] - - for i, term in enumerate(euler_sum_above): - #horizontal alignment with tick marks - term.next_to(number_line.number_to_point(0.5*i+1), UP , buff = 2) - # vertical alignment with light indicator - old_y = term.get_center()[1] - new_y = light_indicator.get_center()[1] - term.shift([0,new_y - old_y,0]) - - # show limit value in light indicator and an equals sign - limit_reading = TexMobject("{\pi^2 \over 6}") - limit_reading.move_to(light_indicator.reading) - - equals_sign = TexMobject("=") - equals_sign.next_to(morty, UP) - old_y = equals_sign.get_center()[1] - new_y = euler_sum_above.get_center()[1] - equals_sign.shift([0,new_y - old_y,0]) - - #Triangle of light to morty's eye - ls0 = light_sources[0] - ls0.save_state() - eye = morty.eyes[1] - triangle = Polygon( - number_line.number_to_point(1), - eye.get_top(), eye.get_bottom(), - stroke_width = 0, - fill_color = YELLOW, - fill_opacity = 1, - ) - triangle_anim = GrowFromPoint( - triangle, triangle.get_right(), - point_color = YELLOW - ) - - # First lighthouse has apparent reading - self.play(LaggedStartMap(FadeOut, light_sources[1:])) - self.wait() - self.play( - triangle_anim, - # Animation(eye) - ) - for x in range(4): - triangle_copy = triangle.copy() - self.play( - FadeOut(triangle.copy()), - triangle_anim, - ) - self.play( - FadeOut(triangle), - ShowCreation(bubble), - FadeIn(light_indicator), - ) - self.play( - UpdateLightIndicator(light_indicator, 1), - FadeIn(euler_sum_terms[0]) - ) - self.wait(2) - - # Second lighthouse is 1/4, third is 1/9, etc. - for i in range(1, 5): - self.play( - ApplyMethod( - ls0.move_to, light_sources[i], - run_time = 3 - ), - UpdateLightIndicator(light_indicator, 1./(i+1)**2, run_time = 3), - FadeIn( - euler_sum_terms[i], - run_time = 3, - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - ) - self.wait() - self.play( - ApplyMethod(ls0.restore), - UpdateLightIndicator(light_indicator, 1) - ) - - #Switch them all on - self.play( - LaggedStartMap(FadeIn, lighthouses[1:]), - morty.change, "hooray", - ) - self.play( - LaggedStartMap( - SwitchOn, VGroup(*[ - ls.ambient_light - for ls in light_sources[1:] - ]), - run_time = 5, - rate_func = rush_into, - ), - Animation(lighthouses), - Animation(euler_sum_above), - Write(plusses), - UpdateLightIndicator(light_indicator, np.pi**2/6, run_time = 5), - morty.change, "happy", - ) - self.wait() - self.play( - FadeOut(light_indicator.reading), - FadeIn(limit_reading), - morty.change, "confused", - ) - self.play(Write(equals_sign)) - self.wait() - - def ask_about_rearrangements(self): - light_sources = self.light_sources - origin = self.number_line.number_to_point(0) - morty = self.pi_creature - - self.play( - LaggedStartMap( - Rotate, light_sources, - lambda m : (m, (2*random.random()-1)*90*DEGREES), - about_point = origin, - rate_func = lambda t : wiggle(t, 4), - run_time = 10, - lag_ratio = 0.9, - ), - morty.change, "pondering", - ) - -class RearrangeWords(Scene): - def construct(self): - words = TextMobject("Rearrange without changing \\\\ the apparent brightness") - self.play(Write(words)) - self.wait(5) - -class ThatJustSeemsUseless(TeacherStudentsScene): - def construct(self): - self.student_says( - "How would \\\\ that help?", - target_mode = "sassy", - student_index = 2, - bubble_kwargs = {"direction" : LEFT}, - ) - self.play( - self.teacher.change, "guilty", - self.get_student_changes(*3*['sassy']) - ) - self.wait() - -class AskAboutBrightness(TeacherStudentsScene): - CONFIG = { - "num_levels" : 200, - "radius" : 10, - } - def construct(self): - light_source = LightSource( - num_levels = self.num_levels, - radius = self.radius, - opacity_function = inverse_quadratic(1,2,1), - ) - light_source.lighthouse.scale(0.5, about_edge = UP) - light_source.move_source_to(5*LEFT + 2*UP) - - self.add_foreground_mobjects(self.pi_creatures) - self.student_says( - "What do you mean \\\\ by ``brightness''?", - added_anims = [ - SwitchOn(light_source.ambient_light), - Animation(light_source.lighthouse) - ] - ) - self.play(self.teacher.change, "happy") - self.wait(4) - -class IntroduceScreen(Scene): - CONFIG = { - "num_levels" : 100, - "radius" : 10, - "num_rays" : 250, - "min_ray_angle" : 0, - "max_ray_angle" : TAU, - "source_point" : 2.5*LEFT, - "observer_point" : 3.5*RIGHT, - "screen_height" : 2, - } - def construct(self): - self.setup_elements() - self.setup_angle() # spotlight and angle msmt change when screen rotates - self.rotate_screen() - # self.morph_lighthouse_into_sun() - - def setup_elements(self): - SCREEN_SIZE = 3.0 - source_point = self.source_point - observer_point = self.observer_point, - - # Light source - light_source = self.light_source = self.get_light_source() - - # Screen - - screen = self.screen = Rectangle( - width = 0.05, - height = self.screen_height, - mark_paths_closed = True, - fill_color = WHITE, - fill_opacity = 1.0, - stroke_width = 0.0 - ) - - screen.next_to(observer_point, LEFT) - - screen_label = TextMobject("Screen") - screen_label.next_to(screen, UP+LEFT) - screen_arrow = Arrow( - screen_label.get_bottom(), - screen.get_center(), - ) - - # Pi creature - morty = Mortimer() - morty.shift(screen.get_center() - morty.eyes.get_left()) - morty.look_at(source_point) - - # Camera - camera = SVGMobject(file_name = "camera") - camera.rotate(TAU/4) - camera.set_height(1.5) - camera.move_to(morty.eyes, LEFT) - - # Animations - light_source.set_max_opacity_spotlight(0.001) - screen_tracker = self.screen_tracker = ScreenTracker(light_source) - - self.add(light_source.lighthouse) - self.play(SwitchOn(light_source.ambient_light)) - self.play( - Write(screen_label), - GrowArrow(screen_arrow), - FadeIn(screen) - ) - self.wait() - self.play(*list(map(FadeOut, [screen_label, screen_arrow]))) - screen.save_state() - self.play( - FadeIn(morty), - screen.match_height, morty.eyes, - screen.next_to, morty.eyes, LEFT, SMALL_BUFF - ) - self.play(Blink(morty)) - self.play( - FadeOut(morty), - FadeIn(camera), - screen.scale, 2, {"about_edge" : UP}, - ) - self.wait() - self.play( - FadeOut(camera), - screen.restore, - ) - - light_source.set_screen(screen) - light_source.spotlight.opacity_function = lambda r : 0.2/(r+1) - screen_tracker.update(0) - - ## Ask about proportion - self.add_foreground_mobjects(light_source.shadow, screen) - self.shoot_rays() - - ## - self.play(SwitchOn(light_source.spotlight)) - - def setup_angle(self): - - self.wait() - - # angle msmt (arc) - arc_angle = self.light_source.spotlight.opening_angle() - # draw arc arrows to show the opening angle - self.angle_arc = Arc( - radius = 3, - start_angle = self.light_source.spotlight.start_angle(), - angle = self.light_source.spotlight.opening_angle(), - tip_length = ARC_TIP_LENGTH - ) - #angle_arc.add_tip(at_start = True, at_end = True) - self.angle_arc.move_arc_center_to(self.light_source.get_source_point()) - - - # angle msmt (decimal number) - - self.angle_indicator = DecimalNumber( - arc_angle / DEGREES, - num_decimal_places = 0, - unit = "^\\circ" - ) - self.angle_indicator.next_to(self.angle_arc, RIGHT) - - angle_update_func = lambda x: self.light_source.spotlight.opening_angle() / DEGREES - self.angle_indicator.add_updater( - lambda d: d.set_value(angle_update_func()) - ) - self.add(self.angle_indicator) - - arc_tracker = AngleUpdater( - self.angle_arc, - self.light_source.spotlight - ) - self.add(arc_tracker) - - self.play( - ShowCreation(self.angle_arc), - ShowCreation(self.angle_indicator) - ) - - self.wait() - - def rotate_screen(self): - self.add( - Mobject.add_updater( - self.light_source, - lambda m : m.update() - ), - ) - self.add( - Mobject.add_updater( - self.angle_indicator, - lambda m : m.set_stroke(width = 0).set_fill(opacity = 1) - ) - ) - self.remove(self.light_source.ambient_light) - def rotate_screen(angle): - self.play( - Rotate(self.light_source.spotlight.screen, angle), - Animation(self.angle_arc), - run_time = 2, - ) - for angle in TAU/8, -TAU/4, TAU/8, -TAU/6: - rotate_screen(angle) - self.wait() - self.shoot_rays() - rotate_screen(TAU/6) - - ## - - def get_light_source(self): - light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), - num_levels = self.num_levels, - radius = self.radius, - max_opacity_ambient = AMBIENT_FULL, - ) - light_source.move_source_to(self.source_point) - return light_source - - def shoot_rays(self, show_creation_kwargs = None): - if show_creation_kwargs is None: - show_creation_kwargs = {} - source_point = self.source_point - screen = self.screen - - # Rays - step_size = (self.max_ray_angle - self.min_ray_angle)/self.num_rays - rays = VGroup(*[ - Line(ORIGIN, self.radius*rotate_vector(RIGHT, angle)) - for angle in np.arange( - self.min_ray_angle, - self.max_ray_angle, - step_size - ) - ]) - rays.shift(source_point) - rays.set_stroke(YELLOW, 1) - max_angle = np.max([ - angle_of_vector(point - source_point) - for point in screen.points - ]) - min_angle = np.min([ - angle_of_vector(point - source_point) - for point in screen.points - ]) - for ray in rays: - if min_angle <= ray.get_angle() <= max_angle: - ray.target_color = GREEN - else: - ray.target_color = RED - - self.play(*[ - ShowCreation(ray, run_time = 3, **show_creation_kwargs) - for ray in rays - ]) - self.play(*[ - ApplyMethod(ray.set_color, ray.target_color) - for ray in rays - ]) - self.wait() - self.play(FadeOut(rays)) - -class EarthScene(IntroduceScreen): - CONFIG = { - "screen_height" : 0.5, - "screen_thickness" : 0, - "radius" : 100 + FRAME_X_RADIUS, - "source_point" : 100*LEFT, - "min_ray_angle" : -1.65*DEGREES, - "max_ray_angle" : 1.65*DEGREES, - "num_rays" : 100, - } - def construct(self): - # Earth - earth_radius = 3 - earth = ImageMobject("earth") - earth_circle = Circle(radius = earth_radius) - earth_circle.to_edge(RIGHT) - earth.replace(earth_circle) - - black_rect = Rectangle( - height = FRAME_HEIGHT, - width = earth_radius + LARGE_BUFF, - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 1 - ) - black_rect.move_to(earth.get_center(), LEFT) - - self.add_foreground_mobjects(black_rect, earth) - - # screen - screen = self.screen = Line( - self.screen_height*UP, ORIGIN, - stroke_color = WHITE, - stroke_width = self.screen_thickness, - ) - screen.move_to(earth.get_left()) - screen.generate_target() - screen.target.rotate( - -60*DEGREES, about_point = earth_circle.get_center() - ) - - equator_arrow = Vector( - DOWN+2*RIGHT, color = WHITE, - ) - equator_arrow.next_to(screen.get_center(), UP+LEFT, SMALL_BUFF) - pole_arrow = Vector( - UP+3*RIGHT, - color = WHITE, - path_arc = -60*DEGREES, - ) - pole_arrow.shift( - screen.target.get_center()+SMALL_BUFF*LEFT - \ - pole_arrow.get_end() - ) - for arrow in equator_arrow, pole_arrow: - arrow.pointwise_become_partial(arrow, 0, 0.95) - equator_words = TextMobject("Some", "unit of area") - pole_words = TextMobject("The same\\\\", "unit of area") - pole_words.next_to(pole_arrow.get_start(), DOWN) - equator_words.next_to(equator_arrow.get_start(), UP) - - - # Light source (far-away Sun) - - sun = sun = LightSource( - opacity_function = lambda r : 0.5, - max_opacity_ambient = 0, - max_opacity_spotlight = 0.5, - num_levels = 5, - radius = self.radius, - screen = screen - ) - sun.move_source_to(self.source_point) - sunlight = sun.spotlight - sunlight.opacity_function = lambda r : 5./(r+1) - - screen_tracker = ScreenTracker(sun) - - # Add elements to scene - - self.add(screen) - self.play(SwitchOn( - sunlight, - rate_func = squish_rate_func(smooth, 0.7, 0.8), - )) - self.add(screen_tracker) - self.play( - Write(equator_words), - GrowArrow(equator_arrow) - ) - self.add_foreground_mobjects(equator_words, equator_arrow) - self.shoot_rays(show_creation_kwargs = { - "rate_func" : lambda t : interpolate(0.98, 1, smooth(t)) - }) - self.wait() - # Point to patch - self.play( - MoveToTarget(screen), - Transform(equator_arrow, pole_arrow), - Transform( - equator_words, pole_words, - rate_func = squish_rate_func(smooth, 0.6, 1), - ), - Animation(sunlight), - run_time = 3, - ) - self.shoot_rays(show_creation_kwargs = { - "rate_func" : lambda t : interpolate(0.98, 1, smooth(t)) - }) - self.wait() - -class ShowLightInThreeDimensions(IntroduceScreen, ThreeDScene): - CONFIG = { - "num_levels" : 200, - } - def construct(self): - light_source = self.get_light_source() - screens = VGroup( - Square(), - RegularPolygon(8), - Circle().insert_n_curves(25), - ) - for screen in screens: - screen.set_height(self.screen_height) - screens.rotate(TAU/4, UP) - screens.next_to(self.observer_point, LEFT) - screens.set_stroke(WHITE, 2) - screens.set_fill(WHITE, 0.5) - screen = screens[0] - - cone = ThreeDSpotlight( - screen, light_source.ambient_light, - light_source.get_source_point - ) - cone_update_anim = ContinualThreeDLightConeUpdate(cone) - - self.add(light_source, screen, cone) - self.add(cone_update_anim) - self.move_camera( - phi = 60*DEGREES, - theta = -155*DEGREES, - run_time = 3, - ) - self.begin_ambient_camera_rotation() - kwargs = {"run_time" : 2} - self.play(screen.stretch, 0.5, 1, **kwargs) - self.play(screen.stretch, 2, 2, **kwargs) - self.play(Rotate( - screen, TAU/4, - axis = UP+OUT, - rate_func = there_and_back, - run_time = 3, - )) - self.play(Transform(screen, screens[1], **kwargs)) - self.play(screen.stretch, 0.5, 2, **kwargs) - self.play(Transform(screen, screens[2], **kwargs)) - self.wait(2) - self.play( - screen.stretch, 0.5, 1, - screen.stretch, 2, 2, - **kwargs - ) - self.play( - screen.stretch, 3, 1, - screen.stretch, 0.7, 2, - **kwargs - ) - self.wait(2) - -class LightInThreeDimensionsOverlay(Scene): - def construct(self): - words = TextMobject(""" - ``Solid angle'' \\\\ - (measured in ``steradians'') - """) - self.play(Write(words)) - self.wait() - -class InverseSquareLaw(ThreeDScene): - CONFIG = { - "screen_height" : 1.0, - "source_point" : 5*LEFT, - "unit_distance" : 4, - "num_levels" : 100, - } - def construct(self): - self.move_screen_farther_away() - self.morph_into_3d() - - def move_screen_farther_away(self): - source_point = self.source_point - unit_distance = self.unit_distance - - # screen - screen = self.screen = Line(self.screen_height*UP, ORIGIN) - screen.get_reference_point = screen.get_center - screen.shift( - source_point + unit_distance*RIGHT -\ - screen.get_reference_point() - ) - - # light source - light_source = self.light_source = LightSource( - # opacity_function = inverse_quadratic(1,5,1), - opacity_function = lambda r : 1./(r+1), - num_levels = self.num_levels, - radius = 10, - max_opacity = 0.2 - ) - light_source.set_max_opacity_spotlight(0.2) - - light_source.set_screen(screen) - light_source.move_source_to(source_point) - - # abbreviations - ambient_light = light_source.ambient_light - spotlight = light_source.spotlight - lighthouse = light_source.lighthouse - shadow = light_source.shadow - - # Morty - morty = self.morty = Mortimer().scale(0.3) - morty.next_to(screen, RIGHT, buff = MED_LARGE_BUFF) - - #Screen tracker - def update_spotlight(spotlight): - spotlight.update_sectors() - - spotlight_update = Mobject.add_updater(spotlight, update_spotlight) - shadow_update = Mobject.add_updater( - shadow, lambda m : light_source.update_shadow() - ) - - # Light indicator - light_indicator = self.light_indicator = LightIndicator( - opacity_for_unit_intensity = 0.5, - ) - def update_light_indicator(light_indicator): - distance = get_norm(screen.get_reference_point() - source_point) - light_indicator.set_intensity(1.0/(distance/unit_distance)**2) - light_indicator.next_to(morty, UP, MED_LARGE_BUFF) - light_indicator_update = Mobject.add_updater( - light_indicator, update_light_indicator - ) - light_indicator_update.update(0) - - continual_updates = self.continual_updates = [ - spotlight_update, light_indicator_update, shadow_update - ] - - # Distance indicators - - one_arrow = DoubleArrow(ORIGIN, unit_distance*RIGHT, buff = 0) - two_arrow = DoubleArrow(ORIGIN, 2*unit_distance*RIGHT, buff = 0) - arrows = VGroup(one_arrow, two_arrow) - arrows.set_color(WHITE) - one_arrow.move_to(source_point + DOWN, LEFT) - two_arrow.move_to(source_point + 1.75*DOWN, LEFT) - one = Integer(1).next_to(one_arrow, UP, SMALL_BUFF) - two = Integer(2).next_to(two_arrow, DOWN, SMALL_BUFF) - arrow_group = VGroup(one_arrow, one, two_arrow, two) - - # Animations - - self.add_foreground_mobjects(lighthouse, screen, morty) - self.add(shadow_update) - - self.play( - SwitchOn(ambient_light), - morty.change, "pondering" - ) - self.play( - SwitchOn(spotlight), - FadeIn(light_indicator) - ) - # self.remove(spotlight) - self.add(*continual_updates) - self.wait() - for distance in -0.5, 0.5: - self.shift_by_distance(distance) - self.wait() - self.add_foreground_mobjects(one_arrow, one) - self.play(GrowFromCenter(one_arrow), Write(one)) - self.wait() - self.add_foreground_mobjects(two_arrow, two) - self.shift_by_distance(1, - GrowFromPoint(two_arrow, two_arrow.get_left()), - Write(two, rate_func = squish_rate_func(smooth, 0.5, 1)) - ) - self.wait() - - q_marks = TextMobject("???") - q_marks.next_to(light_indicator, UP) - self.play( - Write(q_marks), - morty.change, "confused", q_marks - ) - self.play(Blink(morty)) - self.play(FadeOut(q_marks), morty.change, "pondering") - self.wait() - self.shift_by_distance(-1, arrow_group.shift, DOWN) - - self.set_variables_as_attrs( - ambient_light, spotlight, shadow, lighthouse, - morty, arrow_group, - *continual_updates - ) - - def morph_into_3d(self): - # axes = ThreeDAxes() - old_screen = self.screen - spotlight = self.spotlight - source_point = self.source_point - ambient_light = self.ambient_light - unit_distance = self.unit_distance - light_indicator = self.light_indicator - morty = self.morty - - new_screen = Square( - side_length = self.screen_height, - stroke_color = WHITE, - stroke_width = 1, - fill_color = WHITE, - fill_opacity = 0.5 - ) - new_screen.rotate(TAU/4, UP) - new_screen.move_to(old_screen, IN) - old_screen.fade(1) - - cone = ThreeDSpotlight( - new_screen, ambient_light, - source_point_func = lambda : source_point - ) - cone_update_anim = ContinualThreeDLightConeUpdate(cone) - - - self.remove(self.spotlight_update, self.light_indicator_update) - self.add( - ContinualAnimation(new_screen), - cone_update_anim - ) - self.remove(spotlight) - self.move_camera( - phi = 60*DEGREES, - theta = -145*DEGREES, - added_anims = [ - # ApplyMethod( - # old_screen.scale, 1.8, {"about_edge" : DOWN}, - # run_time = 2, - # ), - ApplyFunction( - lambda m : m.fade(1).shift(1.5*DOWN), - light_indicator, - remover = True - ), - FadeOut(morty) - ], - run_time = 2, - ) - self.wait() - - ## Create screen copies - def get_screen_copy_group(distance): - n = int(distance)**2 - copies = VGroup(*[new_screen.copy() for x in range(n)]) - copies.rotate(-TAU/4, axis = UP) - copies.arrange_in_grid(buff = 0) - copies.rotate(TAU/4, axis = UP) - copies.move_to(source_point, IN) - copies.shift(distance*RIGHT*unit_distance) - return copies - screen_copy_groups = list(map(get_screen_copy_group, list(range(1, 8)))) - def get_screen_copy_group_anim(n): - group = screen_copy_groups[n] - prev_group = screen_copy_groups[n-1] - group.save_state() - group.fade(1) - group.replace(prev_group, dim_to_match = 1) - return ApplyMethod(group.restore) - - # corner_directions = [UP+OUT, DOWN+OUT, DOWN+IN, UP+IN] - # edge_directions = [ - # UP, UP+OUT, OUT, DOWN+OUT, DOWN, DOWN+IN, IN, UP+IN, ORIGIN - # ] - - # four_copies = VGroup(*[new_screen.copy() for x in range(4)]) - # nine_copies = VGroup(*[new_screen.copy() for x in range(9)]) - # def update_four_copies(four_copies): - # for mob, corner_direction in zip(four_copies, corner_directions): - # mob.move_to(new_screen, corner_direction) - # four_copies_update_anim = UpdateFromFunc(four_copies, update_four_copies) - # def update_nine_copies(nine_copies): - # for mob, corner_direction in zip(nine_copies, edge_directions): - # mob.move_to(new_screen, corner_direction) - # nine_copies_update_anim = UpdateFromFunc(nine_copies, update_nine_copies) - - three_arrow = DoubleArrow( - source_point + 4*DOWN, - source_point + 4*DOWN + 3*unit_distance*RIGHT, - buff = 0, - color = WHITE - ) - three = Integer(3) - three.next_to(three_arrow, DOWN) - - new_screen.fade(1) - # self.add( - # ContinualAnimation(screen_copy), - # ContinualAnimation(four_copies), - # ) - - self.add(ContinualAnimation(screen_copy_groups[0])) - self.add(ContinualAnimation(screen_copy_groups[1])) - self.play( - new_screen.scale, 2, {"about_edge" : IN}, - new_screen.shift, unit_distance*RIGHT, - get_screen_copy_group_anim(1), - run_time = 2, - ) - self.wait() - self.move_camera( - phi = 75*DEGREES, - theta = -155*DEGREES, - distance = 7, - run_time = 10, - ) - self.begin_ambient_camera_rotation(rate = -0.01) - self.add(ContinualAnimation(screen_copy_groups[2])) - self.play( - new_screen.scale, 3./2, {"about_edge" : IN}, - new_screen.shift, unit_distance*RIGHT, - get_screen_copy_group_anim(2), - GrowFromPoint(three_arrow, three_arrow.get_left()), - Write(three, rate_func = squish_rate_func(smooth, 0.5, 1)), - run_time = 2, - ) - self.begin_ambient_camera_rotation(rate = -0.01) - self.play(LaggedStartMap( - ApplyMethod, screen_copy_groups[2], - lambda m : (m.set_color, RED), - run_time = 5, - rate_func = there_and_back, - )) - self.wait(2) - self.move_camera(distance = 18) - self.play(*[ - ApplyMethod(mob.fade, 1) - for mob in screen_copy_groups[:2] - ]) - last_group = screen_copy_groups[2] - for n in range(4, len(screen_copy_groups)+1): - group = screen_copy_groups[n-1] - self.add(ContinualAnimation(group)) - self.play( - new_screen.scale, float(n)/(n-1), {"about_edge" : IN}, - new_screen.shift, unit_distance*RIGHT, - get_screen_copy_group_anim(n-1), - last_group.fade, 1, - ) - last_group = group - self.wait() - - ### - - def shift_by_distance(self, distance, *added_anims): - anims = [ - self.screen.shift, self.unit_distance*distance*RIGHT, - ] - if self.morty in self.mobjects: - anims.append(MaintainPositionRelativeTo(self.morty, self.screen)) - anims += added_anims - self.play(*anims, run_time = 2) - -class OtherInstanceOfInverseSquareLaw(Scene): - def construct(self): - title = TextMobject("Where the inverse square law shows up") - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - self.add(title, h_line) - - items = VGroup(*[ - TextMobject("- %s"%s).scale(1) - for s in [ - "Heat", "Sound", "Radio waves", "Electric fields", - ] - ]) - items.arrange(DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT) - items.next_to(h_line, DOWN, LARGE_BUFF) - items.to_edge(LEFT) - - dot = Dot() - dot.move_to(4*RIGHT) - self.add(dot) - def get_broadcast(): - return Broadcast(dot, big_radius = 5, run_time = 5) - - self.play( - LaggedStartMap(FadeIn, items, run_time = 4, lag_ratio = 0.7), - Succession(*[ - get_broadcast() - for x in range(2) - ]) - ) - self.play(get_broadcast()) - self.wait() - -class ScreensIntroWrapper(TeacherStudentsScene): - def construct(self): - point = VectorizedPoint(FRAME_X_RADIUS*LEFT/2 + FRAME_Y_RADIUS*UP/2) - self.play(self.teacher.change, "raise_right_hand") - self.change_student_modes( - "pondering", "erm", "confused", - look_at_arg = point, - ) - self.play(self.teacher.look_at, point) - self.wait(5) - -class ManipulateLightsourceSetups(PiCreatureScene): - CONFIG = { - "num_levels" : 100, - "radius" : 10, - "pi_creature_point" : 2*LEFT + 2*DOWN, - } - def construct(self): - unit_distance = 3 - - # Morty - morty = self.pi_creature - observer_point = morty.eyes[1].get_center() - - bubble = ThoughtBubble(height = 3, width = 4, direction = RIGHT) - bubble.set_fill(BLACK, 1) - bubble.pin_to(morty) - - # Indicator - light_indicator = LightIndicator( - opacity_for_unit_intensity = 0.5, - fill_color = YELLOW, - radius = 0.4, - reading_height = 0.2, - ) - light_indicator.move_to(bubble.get_bubble_center()) - def update_light_indicator(light_indicator): - distance = get_norm(light_source.get_source_point()-observer_point) - light_indicator.set_intensity((unit_distance/distance)**2) - - #Light source - light_source = LightSource( - opacity_function = inverse_quadratic(1,2,1), - num_levels = self.num_levels, - radius = self.radius, - max_opacity_ambient = AMBIENT_FULL, - ) - light_source.move_to(observer_point + unit_distance*RIGHT) - - #Light source copies - light_source_copies = VGroup(*[light_source.copy() for x in range(2)]) - for lsc, vect in zip(light_source_copies, [RIGHT, UP]): - lsc.move_to(observer_point + np.sqrt(2)*unit_distance*vect) - - self.add(light_source) - self.add_foreground_mobjects(morty, bubble, light_indicator) - self.add(Mobject.add_updater(light_indicator, update_light_indicator)) - self.play( - ApplyMethod( - light_source.shift, 0.66*unit_distance*LEFT, - rate_func = wiggle, - run_time = 5, - ), - morty.change, "erm", - ) - self.play( - UpdateFromAlphaFunc( - light_source, - lambda ls, a : ls.move_to( - observer_point + rotate_vector( - unit_distance*RIGHT, (1+1./8)*a*TAU - ) - ), - run_time = 6, - rate_func = bezier([0, 0, 1, 1]) - ), - morty.change, "pondering", - UpdateFromFunc(morty, lambda m : m.look_at(light_source)) - ) - self.wait() - - plus = TexMobject("+") - point = light_indicator.get_center() - plus.move_to(point) - light_indicator_copy = light_indicator.copy() - self.add_foreground_mobjects(plus, light_indicator_copy) - self.play( - ReplacementTransform( - light_source, light_source_copies[0] - ), - ReplacementTransform( - light_source.copy().fade(1), - light_source_copies[1] - ), - FadeIn(plus), - UpdateFromFunc( - light_indicator_copy, - lambda li : update_light_indicator(li), - ), - UpdateFromAlphaFunc( - light_indicator, lambda m, a : m.move_to( - point + a*0.75*RIGHT, - ) - ), - UpdateFromAlphaFunc( - light_indicator_copy, lambda m, a : m.move_to( - point + a*0.75*LEFT, - ) - ), - run_time = 2 - ) - self.play(morty.change, "hooray") - self.wait(2) - - ## - - def create_pi_creature(self): - morty = Mortimer() - morty.flip() - morty.scale(0.5) - morty.move_to(self.pi_creature_point) - return morty - -class TwoLightSourcesScene(ManipulateLightsourceSetups): - CONFIG = { - "num_levels" : 200, - "radius" : 15, - "a" : 9, - "b" : 5, - "origin_point" : 5*LEFT + 2.5*DOWN - } - def construct(self): - MAX_OPACITY = 0.4 - INDICATOR_RADIUS = 0.6 - OPACITY_FOR_UNIT_INTENSITY = 0.5 - origin_point = self.origin_point - - #Morty - morty = self.pi_creature - morty.change("hooray") # From last scen - morty.generate_target() - morty.target.change("plain") - morty.target.scale(0.6) - morty.target.next_to( - origin_point, LEFT, buff = 0, - submobject_to_align = morty.target.eyes[1] - ) - - #Axes - axes = Axes( - x_min = -1, x_max = 10.5, - y_min = -1, y_max = 6.5, - ) - axes.shift(origin_point) - - #Important reference points - A = axes.coords_to_point(self.a, 0) - B = axes.coords_to_point(0, self.b) - C = axes.coords_to_point(0, 0) - xA = A[0] - yA = A[1] - xB = B[0] - yB = B[1] - xC = C[0] - yC = C[1] - # find the coords of the altitude point H - # as the solution of a certain LSE - prelim_matrix = np.array([ - [yA - yB, xB - xA], - [xA - xB, yA - yB] - ]) # sic - prelim_vector = np.array( - [xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)] - ) - H2 = np.linalg.solve(prelim_matrix, prelim_vector) - H = np.append(H2, 0.) - - #Lightsources - lsA = LightSource( - radius = self.radius, - num_levels = self.num_levels, - opacity_function = inverse_power_law(2, 1, 1, 1.5), - ) - lsB = lsA.deepcopy() - lsA.move_source_to(A) - lsB.move_source_to(B) - lsC = lsA.deepcopy() - lsC.move_source_to(H) - - #Lighthouse labels - A_label = TextMobject("A") - A_label.next_to(lsA.lighthouse, RIGHT) - B_label = TextMobject("B") - B_label.next_to(lsB.lighthouse, LEFT) - - #Identical lighthouse labels - identical_lighthouses_words = TextMobject("All identical \\\\ lighthouses") - identical_lighthouses_words.to_corner(UP+RIGHT) - identical_lighthouses_words.shift(LEFT) - identical_lighthouses_arrows = VGroup(*[ - Arrow( - identical_lighthouses_words.get_corner(DOWN+LEFT), - ls.get_source_point(), - buff = SMALL_BUFF, - color = WHITE, - ) - for ls in (lsA, lsB, lsC) - ]) - - #Lines - line_a = Line(C, A) - line_a.set_color(BLUE) - line_b = Line(C, B) - line_b.set_color(RED) - line_c = Line(A, B) - line_h = Line(H, C) - line_h.set_color(GREEN) - - label_a = TexMobject("a") - label_a.match_color(line_a) - label_a.next_to(line_a, DOWN, buff = SMALL_BUFF) - label_b = TexMobject("b") - label_b.match_color(line_b) - label_b.next_to(line_b, LEFT, buff = SMALL_BUFF) - label_h = TexMobject("h") - label_h.match_color(line_h) - label_h.next_to(line_h.get_center(), RIGHT, buff = SMALL_BUFF) - - perp_mark = VMobject().set_points_as_corners([ - RIGHT, RIGHT+DOWN, DOWN - ]) - perp_mark.scale(0.25, about_point = ORIGIN) - perp_mark.rotate(line_c.get_angle() + TAU/4, about_point = ORIGIN) - perp_mark.shift(H) - # perp_mark.set_color(BLACK) - - #Indicators - indicator = LightIndicator( - color = LIGHT_COLOR, - radius = INDICATOR_RADIUS, - opacity_for_unit_intensity = OPACITY_FOR_UNIT_INTENSITY, - show_reading = True, - precision = 2, - ) - indicator.next_to(origin_point, UP+LEFT) - def update_indicator(indicator): - intensity = 0 - for ls in lsA, lsB, lsC: - if ls in self.mobjects: - distance = get_norm(ls.get_source_point() - origin_point) - d_indensity = fdiv( - 3./(distance**2), - indicator.opacity_for_unit_intensity - ) - d_indensity *= ls.ambient_light.submobjects[1].get_fill_opacity() - intensity += d_indensity - indicator.set_intensity(intensity) - indicator_update_anim = Mobject.add_updater(indicator, update_indicator) - - new_indicator = indicator.copy() - new_indicator.light_source = lsC - new_indicator.measurement_point = C - - #Note sure what this is... - distance1 = get_norm(origin_point - lsA.get_source_point()) - intensity = lsA.ambient_light.opacity_function(distance1) / indicator.opacity_for_unit_intensity - distance2 = get_norm(origin_point - lsB.get_source_point()) - intensity += lsB.ambient_light.opacity_function(distance2) / indicator.opacity_for_unit_intensity - - # IPT Theorem - theorem = TexMobject( - "{1 \over ", "a^2}", "+", - "{1 \over", "b^2}", "=", "{1 \over","h^2}" - ) - theorem.set_color_by_tex_to_color_map({ - "a" : line_a.get_color(), - "b" : line_b.get_color(), - "h" : line_h.get_color(), - }) - theorem_name = TextMobject("Inverse Pythagorean Theorem") - theorem_name.to_corner(UP+RIGHT) - theorem.next_to(theorem_name, DOWN, buff = MED_LARGE_BUFF) - theorem_box = SurroundingRectangle(theorem, color = WHITE) - - #Transition from last_scene - self.play( - ShowCreation(axes, run_time = 2), - MoveToTarget(morty), - FadeIn(indicator), - ) - - #Move lsC around - self.add(lsC) - indicator_update_anim.update(0) - intensity = indicator.reading.number - self.play( - SwitchOn(lsC.ambient_light), - FadeIn(lsC.lighthouse), - UpdateFromAlphaFunc( - indicator, lambda i, a : i.set_intensity(a*intensity) - ) - ) - self.add(indicator_update_anim) - self.play(Animation(lsC), run_time = 0) #Why is this needed? - for point in axes.coords_to_point(5, 2), H: - self.play( - lsC.move_source_to, point, - path_arc = TAU/4, - run_time = 1.5, - ) - self.wait() - - # Draw line - self.play( - ShowCreation(line_h), - morty.change, "pondering" - ) - self.wait() - self.play( - ShowCreation(line_c), - ShowCreation(perp_mark) - ) - self.wait() - self.add_foreground_mobjects(line_c, line_h) - - #Add alternate light_sources - for ls in lsA, lsB: - ls.save_state() - ls.move_to(lsC) - ls.fade(1) - self.add(ls) - self.play( - ls.restore, - run_time = 2 - ) - self.wait() - A_label.save_state() - A_label.center().fade(1) - self.play(A_label.restore) - self.wait() - self.play(ReplacementTransform( - A_label.copy().fade(1), B_label - )) - self.wait(2) - - #Compare combined of laA + lsB with lsC - rect = SurroundingRectangle(indicator, color = RED) - self.play( - FadeOut(lsA), - FadeOut(lsB), - ) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.play(FadeOut(lsC)) - self.add(lsA, lsB) - self.play( - FadeIn(lsA), - FadeIn(lsB), - ) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.wait(2) - - # All standard lighthouses - self.add(lsC) - self.play(FadeIn(lsC)) - self.play( - Write(identical_lighthouses_words), - LaggedStartMap(GrowArrow, identical_lighthouses_arrows) - ) - self.wait() - self.play(*list(map(FadeOut, [ - identical_lighthouses_words, - identical_lighthouses_arrows, - ]))) - - #Show labels of lengths - self.play(ShowCreation(line_a), Write(label_a)) - self.wait() - self.play(ShowCreation(line_b), Write(label_b)) - self.wait() - self.play(Write(label_h)) - self.wait() - - #Write IPT - a_part = theorem[:2] - b_part = theorem[2:5] - h_part = theorem[5:] - for part in a_part, b_part, h_part: - part.save_state() - part.scale(3) - part.fade(1) - a_part.move_to(lsA) - b_part.move_to(lsB) - h_part.move_to(lsC) - - self.play(*list(map(FadeOut, [lsA, lsB, lsC, indicator]))) - for ls, part in (lsA, a_part), (lsB, b_part), (lsC, h_part): - self.add(ls) - self.play( - SwitchOn(ls.ambient_light, run_time = 2), - FadeIn(ls.lighthouse), - part.restore - ) - self.wait() - self.play( - Write(theorem_name), - ShowCreation(theorem_box) - ) - self.play(morty.change, "confused") - self.wait(2) - -class MathologerVideoWrapper(Scene): - def construct(self): - title = TextMobject(""" - Mathologer's excellent video on \\\\ - the many Pythagorean theorem cousins - """) - # title.scale(0.7) - title.to_edge(UP) - logo = ImageMobject("mathologer_logo") - logo.set_height(1) - logo.to_corner(UP+LEFT) - logo.shift(FRAME_WIDTH*RIGHT) - screen = ScreenRectangle(height = 5.5) - screen.next_to(title, DOWN) - - self.play( - logo.shift, FRAME_WIDTH*LEFT, - LaggedStartMap(FadeIn, title), - run_time = 2 - ) - self.play(ShowCreation(screen)) - self.wait(5) - -class SimpleIPTProof(Scene): - def construct(self): - A = 5*RIGHT - B = 3*UP - C = ORIGIN - #Dumb and inefficient - alphas = np.linspace(0, 1, 500) - i = np.argmin([get_norm(interpolate(A, B, a)) for a in alphas]) - H = interpolate(A, B, alphas[i]) - triangle = VGroup( - Line(C, A, color = BLUE), - Line(C, B, color = RED), - Line(A, B, color = WHITE), - Line(C, H, color = GREEN) - ) - for line, char in zip(triangle, ["a", "b", "c", "h"]): - label = TexMobject(char) - label.match_color(line) - vect = line.get_center() - triangle.get_center() - vect /= get_norm(vect) - label.next_to(line.get_center(), vect) - triangle.add(label) - if char == "h": - label.next_to(line.get_center(), UP+LEFT, SMALL_BUFF) - - triangle.to_corner(UP+LEFT) - self.add(triangle) - - argument_lines = VGroup( - TexMobject( - "\\text{Area} = ", - "{1 \\over 2}", "a", "b", "=", - "{1 \\over 2}", "c", "h" - ), - TexMobject("\\Downarrow"), - TexMobject("a^2", "b^2", "=", "c^2", "h^2"), - TexMobject("\\Downarrow"), - TexMobject( - "a^2", "b^2", "=", - "(", "a^2", "+", "b^2", ")", "h^2" - ), - TexMobject("\\Downarrow"), - TexMobject( - "{1 \\over ", "h^2}", "=", - "{1 \\over ", "b^2}", "+", - "{1 \\over ", "a^2}", - ), - ) - argument_lines.arrange(DOWN) - for line in argument_lines: - line.set_color_by_tex_to_color_map({ - "a" : BLUE, - "b" : RED, - "h" : GREEN, - "Area" : WHITE, - "Downarrow" : WHITE, - }) - all_equals = line.get_parts_by_tex("=") - if all_equals: - line.alignment_mob = all_equals[-1] - else: - line.alignment_mob = line[0] - line.shift(-line.alignment_mob.get_center()[0]*RIGHT) - argument_lines.next_to(triangle, RIGHT) - argument_lines.to_edge(UP) - - prev_line = argument_lines[0] - self.play(FadeIn(prev_line)) - for arrow, line in zip(argument_lines[1::2], argument_lines[2::2]): - line.save_state() - line.shift( - prev_line.alignment_mob.get_center() - \ - line.alignment_mob.get_center() - ) - line.fade(1) - self.play( - line.restore, - GrowFromPoint(arrow, arrow.get_top()) - ) - self.wait() - prev_line = line - -class WeCanHaveMoreFunThanThat(TeacherStudentsScene): - def construct(self): - point = VectorizedPoint(FRAME_X_RADIUS*LEFT/2 + FRAME_Y_RADIUS*UP/2) - self.teacher_says( - "We can have \\\\ more fun than that!", - target_mode = "hooray" - ) - self.change_student_modes(*3*["erm"], look_at_arg = point) - self.wait() - self.play( - RemovePiCreatureBubble( - self.teacher, - target_mode = "raise_right_hand", - look_at_arg = point, - ), - self.get_student_changes(*3*["pondering"], look_at_arg = point) - ) - self.wait(3) - -class IPTScene(TwoLightSourcesScene, ZoomedScene): - CONFIG = { - "max_opacity_ambient" : 0.2, - "num_levels" : 200, - } - def construct(self): - #Copy pasting from TwoLightSourcesScene....Very bad... - origin_point = self.origin_point - self.remove(self.pi_creature) - - #Axes - axes = Axes( - x_min = -1, x_max = 10.5, - y_min = -1, y_max = 6.5, - ) - axes.shift(origin_point) - - #Important reference points - A = axes.coords_to_point(self.a, 0) - B = axes.coords_to_point(0, self.b) - C = axes.coords_to_point(0, 0) - xA = A[0] - yA = A[1] - xB = B[0] - yB = B[1] - xC = C[0] - yC = C[1] - # find the coords of the altitude point H - # as the solution of a certain LSE - prelim_matrix = np.array([ - [yA - yB, xB - xA], - [xA - xB, yA - yB] - ]) # sic - prelim_vector = np.array( - [xB * yA - xA * yB, xC * (xA - xB) + yC * (yA - yB)] - ) - H2 = np.linalg.solve(prelim_matrix, prelim_vector) - H = np.append(H2, 0.) - - #Lightsources - lsA = LightSource( - radius = self.radius, - num_levels = self.num_levels, - opacity_function = inverse_power_law(2, 1, 1, 1.5), - max_opacity_ambient = self.max_opacity_ambient, - ) - lsA.lighthouse.scale(0.5, about_edge = UP) - lsB = lsA.deepcopy() - lsA.move_source_to(A) - lsB.move_source_to(B) - lsC = lsA.deepcopy() - lsC.move_source_to(H) - - #Lighthouse labels - A_label = TextMobject("A") - A_label.next_to(lsA.lighthouse, RIGHT) - B_label = TextMobject("B") - B_label.next_to(lsB.lighthouse, LEFT) - - #Lines - line_a = Line(C, A) - line_a.set_color(BLUE) - line_b = Line(C, B) - line_b.set_color(RED) - line_c = Line(A, B) - line_h = Line(H, C) - line_h.set_color(GREEN) - - label_a = TexMobject("a") - label_a.match_color(line_a) - label_a.next_to(line_a, DOWN, buff = SMALL_BUFF) - label_b = TexMobject("b") - label_b.match_color(line_b) - label_b.next_to(line_b, LEFT, buff = SMALL_BUFF) - label_h = TexMobject("h") - label_h.match_color(line_h) - label_h.next_to(line_h.get_center(), RIGHT, buff = SMALL_BUFF) - - perp_mark = VMobject().set_points_as_corners([ - RIGHT, RIGHT+DOWN, DOWN - ]) - perp_mark.scale(0.25, about_point = ORIGIN) - perp_mark.rotate(line_c.get_angle() + TAU/4, about_point = ORIGIN) - perp_mark.shift(H) - - # Mini triangle - m_hyp_a = Line(H, A) - m_a = line_a.copy() - m_hyp_b = Line(H, B) - m_b = line_b.copy() - mini_triangle = VGroup(m_a, m_hyp_a, m_b, m_hyp_b) - mini_triangle.set_stroke(width = 5) - - mini_triangle.generate_target() - mini_triangle.target.scale(0.1, about_point = origin_point) - for part, part_target in zip(mini_triangle, mini_triangle.target): - part.target = part_target - - # Screen label - screen_word = TextMobject("Screen") - screen_word.next_to(mini_triangle.target, UP+RIGHT, LARGE_BUFF) - screen_arrow = Arrow( - screen_word.get_bottom(), - mini_triangle.target.get_center(), - color = WHITE, - ) - - # IPT Theorem - theorem = TexMobject( - "{1 \over ", "a^2}", "+", - "{1 \over", "b^2}", "=", "{1 \over","h^2}" - ) - theorem.set_color_by_tex_to_color_map({ - "a" : line_a.get_color(), - "b" : line_b.get_color(), - "h" : line_h.get_color(), - }) - theorem_name = TextMobject("Inverse Pythagorean Theorem") - theorem_name.to_corner(UP+RIGHT) - theorem.next_to(theorem_name, DOWN, buff = MED_LARGE_BUFF) - theorem_box = SurroundingRectangle(theorem, color = WHITE) - - # Setup spotlights - spotlight_a = VGroup() - spotlight_a.screen = m_hyp_a - spotlight_b = VGroup() - spotlight_b.screen = m_hyp_b - for spotlight in spotlight_a, spotlight_b: - spotlight.get_source_point = lsC.get_source_point - dr = lsC.ambient_light.radius/lsC.ambient_light.num_levels - def update_spotlight(spotlight): - spotlight.submobjects = [] - source_point = spotlight.get_source_point() - c1, c2 = spotlight.screen.get_start(), spotlight.screen.get_end() - distance = max( - get_norm(c1 - source_point), - get_norm(c2 - source_point), - ) - n_parts = np.ceil(distance/dr) - alphas = np.linspace(0, 1, n_parts+1) - for a1, a2 in zip(alphas, alphas[1:]): - spotlight.add(Polygon( - interpolate(source_point, c1, a1), - interpolate(source_point, c1, a2), - interpolate(source_point, c2, a2), - interpolate(source_point, c2, a1), - fill_color = YELLOW, - fill_opacity = 2*lsC.ambient_light.opacity_function(a1*distance), - stroke_width = 0 - )) - - def update_spotlights(spotlights): - for spotlight in spotlights: - update_spotlight(spotlight) - - def get_spotlight_triangle(spotlight): - sp = spotlight.get_source_point() - c1 = spotlight.screen.get_start() - c2 = spotlight.screen.get_end() - return Polygon( - sp, c1, c2, - stroke_width = 0, - fill_color = YELLOW, - fill_opacity = 0.5, - ) - - spotlights = VGroup(spotlight_a, spotlight_b) - spotlights_update_anim = Mobject.add_updater( - spotlights, update_spotlights - ) - - # Add components - self.add( - axes, - lsA.ambient_light, - lsB.ambient_light, - lsC.ambient_light, - line_c, - ) - self.add_foreground_mobjects( - lsA.lighthouse, A_label, - lsB.lighthouse, B_label, - lsC.lighthouse, line_h, - theorem, theorem_name, theorem_box, - ) - - # Show miniature triangle - self.play(ShowCreation(mini_triangle, lag_ratio = 0)) - self.play( - MoveToTarget(mini_triangle), - run_time = 2, - ) - self.add_foreground_mobject(mini_triangle) - - # Show beams of light - self.play( - Write(screen_word), - GrowArrow(screen_arrow), - ) - self.wait() - spotlights_update_anim.update(0) - self.play( - LaggedStartMap(FadeIn, spotlight_a), - LaggedStartMap(FadeIn, spotlight_b), - Animation(screen_arrow), - ) - self.add(spotlights_update_anim) - self.play(*list(map(FadeOut, [screen_word, screen_arrow]))) - self.wait() - - # Reshape screen - m_hyps = [m_hyp_a, m_hyp_b] - for hyp, line in (m_hyp_a, m_a), (m_hyp_b, m_b): - hyp.save_state() - hyp.alt_version = line.copy() - hyp.alt_version.set_color(WHITE) - - for x in range(2): - self.play(*[ - Transform(m, m.alt_version) - for m in m_hyps - ]) - self.wait() - self.play(*[m.restore for m in m_hyps]) - self.wait() - - # Show spotlight a key point - def show_beaming_light(spotlight): - triangle = get_spotlight_triangle(spotlight) - for x in range(3): - anims = [] - if x > 0: - anims.append(FadeOut(triangle.copy())) - anims.append(GrowFromPoint(triangle, triangle.points[0])) - self.play(*anims) - self.play(FadeOut(triangle)) - pass - - def show_key_point(spotlight, new_point): - screen = spotlight.screen - update_spotlight_anim = UpdateFromFunc(spotlight, update_spotlight) - self.play( - Transform(screen, screen.alt_version), - update_spotlight_anim, - ) - show_beaming_light(spotlight) - self.play(screen.restore, update_spotlight_anim) - self.wait() - self.play( - lsC.move_source_to, new_point, - Transform(screen, screen.alt_version), - update_spotlight_anim, - run_time = 2 - ) - show_beaming_light(spotlight) - self.wait() - self.play( - lsC.move_source_to, H, - screen.restore, - update_spotlight_anim, - run_time = 2 - ) - self.wait() - - self.remove(spotlights_update_anim) - self.add(spotlight_b) - self.play(*list(map(FadeOut, [ - spotlight_a, lsA.ambient_light, lsB.ambient_light - ]))) - show_key_point(spotlight_b, A) - self.play( - FadeOut(spotlight_b), - FadeIn(spotlight_a), - ) - show_key_point(spotlight_a, B) - self.wait() - -class HomeworkWrapper(Scene): - def construct(self): - title = TextMobject("Homework") - title.to_edge(UP) - screen = ScreenRectangle(height = 6) - screen.center() - self.add(title) - self.play(ShowCreation(screen)) - self.wait(5) - -class HeresWhereThingsGetGood(TeacherStudentsScene): - def construct(self): - self.teacher_says("Now for the \\\\ good part!") - self.change_student_modes(*["hooray"]*3) - self.change_student_modes(*["happy"]*3) - self.wait() - -class DiameterTheorem(TeacherStudentsScene): - def construct(self): - circle = Circle(radius = 2, color = WHITE) - circle.next_to(self.students[2], UP) - self.add(circle) - - center = Dot(circle.get_center(), color = WHITE) - self.add_foreground_mobject(center) - - diameter_word = TextMobject("Diameter") - diameter_word.next_to(center, DOWN, SMALL_BUFF) - - point = VectorizedPoint(circle.get_top()) - triangle = Polygon(LEFT, RIGHT, UP) - triangle.set_stroke(BLUE) - triangle.set_fill(WHITE, 0.5) - def update_triangle(triangle): - triangle.set_points_as_corners([ - circle.get_left(), circle.get_right(), - point.get_center(), circle.get_left(), - ]) - triangle_update_anim = Mobject.add_updater( - triangle, update_triangle - ) - triangle_update_anim.update(0) - - perp_mark = VMobject() - perp_mark.set_points_as_corners([LEFT, DOWN, RIGHT]) - perp_mark.shift(DOWN) - perp_mark.scale(0.15, about_point = ORIGIN) - perp_mark.shift(point.get_center()) - perp_mark.add(point.copy()) - - self.play( - self.teacher.change, "raise_right_hand", - DrawBorderThenFill(triangle), - Write(diameter_word), - ) - self.play( - ShowCreation(perp_mark), - self.get_student_changes(*["pondering"]*3) - ) - self.add_foreground_mobjects(perp_mark) - self.add(triangle_update_anim) - for angle in 0.2*TAU, -0.4*TAU, 0.3*TAU: - point.generate_target() - point.target.rotate(angle, about_point = circle.get_center()) - - perp_mark.generate_target() - perp_mark.target.rotate(angle/2) - perp_mark.target.shift( - point.target.get_center() - \ - perp_mark.target[1].get_center() - ) - - self.play( - MoveToTarget(point), - MoveToTarget(perp_mark), - path_arc = angle, - run_time = 3, - ) - -class InscribedeAngleThreorem(TeacherStudentsScene): - def construct(self): - circle = Circle(radius = 2, color = WHITE) - circle.next_to(self.students[2], UP) - self.add(circle) - - title = TextMobject("Inscribed angle \\\\ theorem") - title.to_corner(UP+LEFT) - self.add(title) - - center = Dot(circle.get_center(), color = WHITE) - self.add_foreground_mobject(center) - - point = VectorizedPoint(circle.get_left()) - shape = Polygon(UP+LEFT, ORIGIN, DOWN+LEFT, RIGHT) - shape.set_stroke(BLUE) - def update_shape(shape): - shape.set_points_as_corners([ - point.get_center(), - circle.point_from_proportion(7./8), - circle.get_center(), - circle.point_from_proportion(1./8), - point.get_center(), - ]) - shape_update_anim = Mobject.add_updater( - shape, update_shape - ) - shape_update_anim.update(0) - - angle_mark = Arc(start_angle = -TAU/8, angle = TAU/4) - angle_mark.scale(0.3, about_point = ORIGIN) - angle_mark.shift(circle.get_center()) - theta = TexMobject("\\theta").set_color(RED) - theta.next_to(angle_mark, RIGHT, MED_SMALL_BUFF) - angle_mark.match_color(theta) - - half_angle_mark = Arc(start_angle = -TAU/16, angle = TAU/8) - half_angle_mark.scale(0.3, about_point = ORIGIN) - half_angle_mark.shift(point.get_center()) - half_angle_mark.add(point.copy()) - theta_halves = TexMobject("\\theta/2").set_color(GREEN) - theta_halves.scale(0.7) - half_angle_mark.match_color(theta_halves) - theta_halves_update = UpdateFromFunc( - theta_halves, lambda m : m.move_to(interpolate( - point.get_center(), - half_angle_mark.point_from_proportion(0.5), - 2.5, - )) - ) - theta_halves_update.update(0) - - self.play( - self.teacher.change, "raise_right_hand", - ShowCreation(shape, rate_func=linear), - ) - self.play(*list(map(FadeIn, [angle_mark, theta]))) - self.play( - ShowCreation(half_angle_mark), - Write(theta_halves), - self.get_student_changes(*["pondering"]*3) - ) - self.add_foreground_mobjects(half_angle_mark, theta_halves) - self.add(shape_update_anim) - for angle in 0.25*TAU, -0.4*TAU, 0.3*TAU, -0.35*TAU: - point.generate_target() - point.target.rotate(angle, about_point = circle.get_center()) - - half_angle_mark.generate_target() - half_angle_mark.target.rotate(angle/2) - half_angle_mark.target.shift( - point.target.get_center() - \ - half_angle_mark.target[1].get_center() - ) - - self.play( - MoveToTarget(point), - MoveToTarget(half_angle_mark), - theta_halves_update, - path_arc = angle, - run_time = 3, - ) - -class PondScene(ThreeDScene): - def construct(self): - - BASELINE_YPOS = -2.5 - OBSERVER_POINT = np.array([0,BASELINE_YPOS,0]) - LAKE0_RADIUS = 1.5 - INDICATOR_RADIUS = 0.6 - TICK_SIZE = 0.5 - LIGHTHOUSE_HEIGHT = 0.5 - LAKE_COLOR = BLUE - LAKE_OPACITY = 0.15 - LAKE_STROKE_WIDTH = 5.0 - LAKE_STROKE_COLOR = BLUE - TEX_SCALE = 0.8 - DOT_COLOR = BLUE - - LIGHT_MAX_INT = 1 - LIGHT_SCALE = 2.5 - LIGHT_CUTOFF = 1 - - RIGHT_ANGLE_SIZE = 0.3 - - self.cumulated_zoom_factor = 1 - - def right_angle(pointA, pointB, pointC, size = 1): - - v1 = pointA - pointB - v1 = size * v1/get_norm(v1) - v2 = pointC - pointB - v2 = size * v2/get_norm(v2) - - P = pointB - Q = pointB + v1 - R = Q + v2 - S = R - v1 - angle_sign = VMobject() - angle_sign.set_points_as_corners([P,Q,R,S,P]) - angle_sign.mark_paths_closed = True - angle_sign.set_fill(color = WHITE, opacity = 1) - angle_sign.set_stroke(width = 0) - return angle_sign - - def triangle(pointA, pointB, pointC): - - mob = VMobject() - mob.set_points_as_corners([pointA, pointB, pointC, pointA]) - mob.mark_paths_closed = True - mob.set_fill(color = WHITE, opacity = 0.5) - mob.set_stroke(width = 0) - return mob - - def zoom_out_scene(factor): - - self.remove_foreground_mobject(self.ls0_dot) - self.remove(self.ls0_dot) - - phi0 = self.camera.get_phi() # default is 0 degs - theta0 = self.camera.get_theta() # default is -90 degs - distance0 = self.camera.get_distance() - - distance1 = 2 * distance0 - camera_target_point = self.camera.get_spherical_coords(phi0, theta0, distance1) - - self.play( - ApplyMethod(self.camera.rotation_mobject.move_to, camera_target_point), - self.zoomable_mobs.shift, self.obs_dot.get_center(), - self.unzoomable_mobs.scale,2,{"about_point" : ORIGIN}, - ) - - self.cumulated_zoom_factor *= factor - - # place ls0_dot by hand - #old_radius = self.ls0_dot.radius - #self.ls0_dot.radius = 2 * old_radius - - #v = self.ls0_dot.get_center() - self.obs_dot.get_center() - #self.ls0_dot.shift(v) - #self.ls0_dot.move_to(self.outer_lake.get_center()) - self.ls0_dot.scale(2, about_point = ORIGIN) - - #self.add_foreground_mobject(self.ls0_dot) - - def shift_scene(v): - self.play( - self.zoomable_mobs.shift,v, - self.unzoomable_mobs.shift,v - ) - - self.zoomable_mobs = VMobject() - self.unzoomable_mobs = VMobject() - - baseline = VMobject() - baseline.set_points_as_corners([[-8,BASELINE_YPOS,0],[8,BASELINE_YPOS,0]]) - baseline.set_stroke(width = 0) # in case it gets accidentally added to the scene - self.zoomable_mobs.add(baseline) # prob not necessary - - obs_dot = self.obs_dot = Dot(OBSERVER_POINT, fill_color = DOT_COLOR) - ls0_dot = self.ls0_dot = Dot(OBSERVER_POINT + 2 * LAKE0_RADIUS * UP, fill_color = WHITE) - self.unzoomable_mobs.add(self.obs_dot)#, self.ls0_dot) - - # lake - lake0 = Circle(radius = LAKE0_RADIUS, - stroke_width = 0, - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY - ) - lake0.move_to(OBSERVER_POINT + LAKE0_RADIUS * UP) - self.zoomable_mobs.add(lake0) - - # Morty and indicator - morty = Mortimer().flip().scale(0.3) - morty.next_to(OBSERVER_POINT,DOWN) - indicator = LightIndicator(precision = 2, - radius = INDICATOR_RADIUS, - show_reading = False, - color = LIGHT_COLOR - ) - indicator.next_to(morty,LEFT) - self.unzoomable_mobs.add(morty, indicator) - - # first lighthouse - original_op_func = inverse_quadratic(LIGHT_MAX_INT,LIGHT_SCALE,LIGHT_CUTOFF) - ls0 = LightSource(opacity_function = original_op_func, radius = 15.0, num_levels = 150) - ls0.lighthouse.set_height(LIGHTHOUSE_HEIGHT) - ls0.lighthouse.height = LIGHTHOUSE_HEIGHT - ls0.move_source_to(OBSERVER_POINT + LAKE0_RADIUS * 2 * UP) - self.zoomable_mobs.add(ls0, ls0.lighthouse, ls0.ambient_light) - - # self.add(lake0, morty, obs_dot, ls0_dot, ls0.lighthouse) - - # shore arcs - arc_left = Arc(-TAU/2, - radius = LAKE0_RADIUS, - start_angle = -TAU/4, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR - ) - arc_left.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) - - one_left = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) - one_left.next_to(arc_left,LEFT) - - arc_right = Arc(TAU/2, - radius = LAKE0_RADIUS, - start_angle = -TAU/4, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR - ) - arc_right.move_arc_center_to(OBSERVER_POINT + LAKE0_RADIUS * UP) - - one_right = TexMobject("1", color = LAKE_COLOR).scale(TEX_SCALE) - one_right.next_to(arc_right,RIGHT) - - # New introduction - lake0.save_state() - morty.save_state() - lake0.set_height(6) - morty.to_corner(UP+LEFT) - morty.fade(1) - lake0.center() - - lake_word = TextMobject("Lake") - lake_word.scale(2) - lake_word.move_to(lake0) - - self.play( - DrawBorderThenFill(lake0, stroke_width = 1), - Write(lake_word) - ) - self.play( - lake0.restore, - lake_word.scale, 0.5, {"about_point" : lake0.get_bottom()}, - lake_word.fade, 1 - ) - self.remove(lake_word) - self.play(morty.restore) - self.play( - GrowFromCenter(obs_dot), - GrowFromCenter(ls0_dot), - FadeIn(ls0.lighthouse) - ) - self.add_foreground_mobjects(ls0.lighthouse, obs_dot, ls0_dot) - self.play( - SwitchOn(ls0.ambient_light), - Animation(ls0.lighthouse), - ) - self.wait() - self.play( - morty.move_to, ls0.lighthouse, - run_time = 3, - path_arc = TAU/2, - rate_func = there_and_back - ) - - self.play( - ShowCreation(arc_right), - Write(one_right), - ) - self.play( - ShowCreation(arc_left), - Write(one_left), - ) - self.play( - lake0.set_stroke, { - "color": LAKE_STROKE_COLOR, - "width" : LAKE_STROKE_WIDTH - }, - ) - self.wait() - self.add_foreground_mobjects(morty) - - - # Show indicator - self.play(FadeIn(indicator)) - - self.play(indicator.set_intensity, 0.5) - - diameter_start = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.02) - diameter_stop = interpolate(OBSERVER_POINT,ls0.get_source_point(),0.98) - - # diameter - diameter = DoubleArrow(diameter_start, - diameter_stop, - buff = 0, - color = WHITE, - ) - diameter_text = TexMobject("d").scale(TEX_SCALE) - diameter_text.next_to(diameter,RIGHT) - - self.play( - GrowFromCenter(diameter), - Write(diameter_text), - #FadeOut(self.obs_dot), - FadeOut(ls0_dot) - ) - self.wait() - - indicator_reading = TexMobject("{1 \over d^2}").scale(TEX_SCALE) - indicator_reading.move_to(indicator) - self.unzoomable_mobs.add(indicator_reading) - - self.play( - ReplacementTransform( - diameter_text[0].copy(), - indicator_reading[2], - ), - FadeIn(indicator_reading) - ) - self.wait() - - # replace d with its value - new_diameter_text = TexMobject("{2 \over \pi}").scale(TEX_SCALE) - new_diameter_text.color = LAKE_COLOR - new_diameter_text.move_to(diameter_text) - self.play(FadeOut(diameter_text)) - self.play(FadeIn(new_diameter_text)) - self.wait(2) - - # insert into indicator reading - new_reading = TexMobject("{\pi^2 \over 4}").scale(TEX_SCALE) - new_reading.move_to(indicator) - new_diameter_text_copy = new_diameter_text.copy() - new_diameter_text_copy.submobjects.reverse() - - self.play( - FadeOut(indicator_reading), - ReplacementTransform( - new_diameter_text_copy, - new_reading, - parth_arc = 30*DEGREES - ) - ) - indicator_reading = new_reading - - self.wait(2) - - self.play( - FadeOut(one_left), - FadeOut(one_right), - FadeOut(new_diameter_text), - FadeOut(arc_left), - FadeOut(arc_right) - ) - self.add_foreground_mobjects(indicator, indicator_reading) - self.unzoomable_mobs.add(indicator_reading) - - def indicator_wiggle(): - INDICATOR_WIGGLE_FACTOR = 1.3 - - self.play( - ScaleInPlace(indicator, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle), - ScaleInPlace(indicator_reading, INDICATOR_WIGGLE_FACTOR, rate_func = wiggle) - ) - - def angle_for_index(i,step): - return -TAU/4 + TAU/2**step * (i + 0.5) - - - def position_for_index(i, step, scaled_down = False): - - theta = angle_for_index(i,step) - radial_vector = np.array([np.cos(theta),np.sin(theta),0]) - position = self.lake_center + self.lake_radius * radial_vector - - if scaled_down: - return position.scale_about_point(self.obs_dot.get_center(),0.5) - else: - return position - - - def split_light_source(i, step, show_steps = True, animate = True, run_time = 1): - - ls_new_loc1 = position_for_index(i,step + 1) - ls_new_loc2 = position_for_index(i + 2**step,step + 1) - - hyp = VMobject() - hyp1 = Line(self.lake_center,ls_new_loc1) - hyp2 = Line(self.lake_center,ls_new_loc2) - hyp.add(hyp2,hyp1) - self.new_hypotenuses.append(hyp) - - if show_steps == True: - self.play( - ShowCreation(hyp, run_time = run_time) - ) - - leg1 = Line(self.obs_dot.get_center(),ls_new_loc1) - leg2 = Line(self.obs_dot.get_center(),ls_new_loc2) - self.new_legs_1.append(leg1) - self.new_legs_2.append(leg2) - - if show_steps == True: - self.play( - ShowCreation(leg1, run_time = run_time), - ShowCreation(leg2, run_time = run_time), - ) - - ls1 = self.light_sources_array[i] - - - ls2 = ls1.copy() - if animate == True: - self.add(ls2) - - self.additional_light_sources.append(ls2) - - # check if the light sources are on screen - ls_old_loc = np.array(ls1.get_source_point()) - onscreen_old = np.any(np.abs(ls_old_loc) < 10) - onscreen_1 = np.any(np.abs(ls_new_loc1) < 10) - onscreen_2 = np.any(np.abs(ls_new_loc2) < 10) - show_animation = (onscreen_old or onscreen_1 or onscreen_2) - - if show_animation or animate: - ls1.generate_target() - ls2.generate_target() - ls1.target.move_source_to(ls_new_loc1) - ls2.target.move_source_to(ls_new_loc2) - ls1.fade(1) - self.play( - MoveToTarget(ls1), MoveToTarget(ls2), - run_time = run_time - ) - else: - ls1.move_source_to(ls_new_loc1) - ls2.move_source_to(ls_new_loc1) - - - def construction_step(n, show_steps = True, run_time = 1, - simultaneous_splitting = False): - - # we assume that the scene contains: - # an inner lake, self.inner_lake - # an outer lake, self.outer_lake - # light sources, self.light_sources - # legs from the observer point to each light source - # self.legs - # altitudes from the observer point to the - # locations of the light sources in the previous step - # self.altitudes - # hypotenuses connecting antipodal light sources - # self.hypotenuses - - # these are mobjects! - - - # first, fade out all of the hypotenuses and altitudes - - if show_steps == True: - self.zoomable_mobs.remove(self.hypotenuses, self.altitudes, self.inner_lake) - self.play( - FadeOut(self.hypotenuses), - FadeOut(self.altitudes), - FadeOut(self.inner_lake) - ) - else: - self.zoomable_mobs.remove(self.inner_lake) - self.play( - FadeOut(self.inner_lake) - ) - - # create a new, outer lake - self.lake_center = self.obs_dot.get_center() + self.lake_radius * UP - - new_outer_lake = Circle(radius = self.lake_radius, - stroke_width = LAKE_STROKE_WIDTH, - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY, - stroke_color = LAKE_STROKE_COLOR - ) - new_outer_lake.move_to(self.lake_center) - - if show_steps == True: - self.play( - FadeIn(new_outer_lake, run_time = run_time), - FadeIn(self.ls0_dot) - ) - else: - self.play( - FadeIn(new_outer_lake, run_time = run_time), - ) - - self.wait() - - self.inner_lake = self.outer_lake - self.outer_lake = new_outer_lake - self.altitudes = self.legs - #self.lake_center = self.outer_lake.get_center() - - self.additional_light_sources = [] - self.new_legs_1 = [] - self.new_legs_2 = [] - self.new_hypotenuses = [] - - if simultaneous_splitting == False: - - for i in range(2**n): - - split_light_source(i, - step = n, - show_steps = show_steps, - run_time = run_time - ) - - if n == 1 and i == 0: - # show again where the right angles are - A = self.light_sources[0].get_center() - B = self.additional_light_sources[0].get_center() - C = self.obs_dot.get_center() - - triangle1 = triangle( - A, C, B - ) - right_angle1 = right_angle( - A, C, B, size = 2 * RIGHT_ANGLE_SIZE - ) - - self.play( - FadeIn(triangle1), - FadeIn(right_angle1) - ) - - self.wait() - - self.play( - FadeOut(triangle1), - FadeOut(right_angle1) - ) - - self.wait() - - H = self.inner_lake.get_center() + self.lake_radius/2 * RIGHT - L = self.outer_lake.get_center() - triangle2 = triangle( - L, H, C - ) - - right_angle2 = right_angle( - L, H, C, size = 2 * RIGHT_ANGLE_SIZE - ) - - self.play( - FadeIn(triangle2), - FadeIn(right_angle2) - ) - - self.wait() - - self.play( - FadeOut(triangle2), - FadeOut(right_angle2) - ) - - self.wait() - - else: # simultaneous splitting - - old_lake = self.outer_lake.copy() - old_ls = self.light_sources.copy() - old_ls2 = old_ls.copy() - for submob in old_ls2.submobjects: - old_ls.add(submob) - - self.remove(self.outer_lake, self.light_sources) - self.add(old_lake, old_ls) - - for i in range(2**n): - split_light_source(i, - step = n, - show_steps = show_steps, - run_time = run_time, - animate = False - ) - - self.play( - ReplacementTransform(old_ls, self.light_sources, run_time = run_time), - ReplacementTransform(old_lake, self.outer_lake, run_time = run_time), - ) - - - - - # collect the newly created mobs (in arrays) - # into the appropriate Mobject containers - - self.legs = VMobject() - for leg in self.new_legs_1: - self.legs.add(leg) - self.zoomable_mobs.add(leg) - for leg in self.new_legs_2: - self.legs.add(leg) - self.zoomable_mobs.add(leg) - - for hyp in self.hypotenuses.submobjects: - self.zoomable_mobs.remove(hyp) - - self.hypotenuses = VMobject() - for hyp in self.new_hypotenuses: - self.hypotenuses.add(hyp) - self.zoomable_mobs.add(hyp) - - for ls in self.additional_light_sources: - self.light_sources.add(ls) - self.light_sources_array.append(ls) - self.zoomable_mobs.add(ls) - - # update scene - self.add( - self.light_sources, - self.inner_lake, - self.outer_lake, - ) - self.zoomable_mobs.add(self.light_sources, self.inner_lake, self.outer_lake) - - if show_steps == True: - self.add( - self.legs, - self.hypotenuses, - self.altitudes, - ) - self.zoomable_mobs.add(self.legs, self.hypotenuses, self.altitudes) - - - self.wait() - - if show_steps == True: - self.play(FadeOut(self.ls0_dot)) - - #self.lake_center = ls0_loc = self.obs_dot.get_center() + self.lake_radius * UP - self.lake_radius *= 2 - - self.lake_center = ls0_loc = ls0.get_source_point() - - self.inner_lake = VMobject() - self.outer_lake = lake0 - self.legs = VMobject() - self.legs.add(Line(OBSERVER_POINT,self.lake_center)) - self.altitudes = VMobject() - self.hypotenuses = VMobject() - self.light_sources_array = [ls0] - self.light_sources = VMobject() - self.light_sources.add(ls0) - - self.lake_radius = 2 * LAKE0_RADIUS # don't ask... - - self.zoomable_mobs.add(self.inner_lake, self.outer_lake, self.altitudes, self.light_sources) - - self.add( - self.inner_lake, - self.outer_lake, - self.legs, - self.altitudes, - self.hypotenuses - ) - - self.play(FadeOut(diameter)) - - self.additional_light_sources = [] - self.new_legs_1 = [] - self.new_legs_2 = [] - self.new_hypotenuses = [] - - construction_step(0) - - my_triangle = triangle( - self.light_sources[0].get_source_point(), - OBSERVER_POINT, - self.light_sources[1].get_source_point() - ) - - angle_sign1 = right_angle( - self.light_sources[0].get_source_point(), - OBSERVER_POINT, - self.light_sources[1].get_source_point(), - size = RIGHT_ANGLE_SIZE - ) - - self.play( - FadeIn(angle_sign1), - FadeIn(my_triangle) - ) - - angle_sign2 = right_angle( - self.light_sources[1].get_source_point(), - self.lake_center, - OBSERVER_POINT, - size = RIGHT_ANGLE_SIZE - ) - - self.play( - FadeIn(angle_sign2) - ) - - self.wait() - - self.play( - FadeOut(angle_sign1), - FadeOut(angle_sign2), - FadeOut(my_triangle) - ) - - indicator_wiggle() - self.remove(self.ls0_dot) - zoom_out_scene(2) - - - construction_step(1) - indicator_wiggle() - #self.play(FadeOut(self.ls0_dot)) - zoom_out_scene(2) - - - construction_step(2) - indicator_wiggle() - self.play(FadeOut(self.ls0_dot)) - - - - - self.play( - FadeOut(self.altitudes), - FadeOut(self.hypotenuses), - FadeOut(self.legs) - ) - - max_it = 6 - scale = 2**(max_it - 4) - TEX_SCALE *= scale - - - - # for i in range(3,max_it + 1): - # construction_step(i, show_steps = False, run_time = 4.0/2**i, - # simultaneous_splitting = True) - - - - # simultaneous expansion of light sources from now on - self.play(FadeOut(self.inner_lake)) - - for n in range(3,max_it + 1): - - new_lake = self.outer_lake.copy().scale(2,about_point = self.obs_dot.get_center()) - for ls in self.light_sources_array: - lsp = ls.copy() - self.light_sources.add(lsp) - self.add(lsp) - self.light_sources_array.append(lsp) - - new_lake_center = new_lake.get_center() - new_lake_radius = 0.5 * new_lake.get_width() - - shift_list = (Transform(self.outer_lake,new_lake),) - - - for i in range(2**n): - theta = -TAU/4 + (i + 0.5) * TAU / 2**n - v = np.array([np.cos(theta), np.sin(theta),0]) - pos1 = new_lake_center + new_lake_radius * v - pos2 = new_lake_center - new_lake_radius * v - shift_list += (self.light_sources.submobjects[i].move_source_to,pos1) - shift_list += (self.light_sources.submobjects[i+2**n].move_source_to,pos2) - - self.play(*shift_list) - - #self.revert_to_original_skipping_status() - - # Now create a straight number line and transform into it - MAX_N = 17 - - origin_point = self.obs_dot.get_center() - - self.number_line = NumberLine( - x_min = -MAX_N, - x_max = MAX_N + 1, - color = WHITE, - number_at_center = 0, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR, - #numbers_with_elongated_ticks = range(-MAX_N,MAX_N + 1), - numbers_to_show = list(range(-MAX_N,MAX_N + 1,2)), - unit_size = LAKE0_RADIUS * TAU/4 / 2 * scale, - tick_frequency = 1, - line_to_number_buff = LARGE_BUFF, - label_direction = UP, - ).shift(scale * 2.5 * DOWN) - - self.number_line.label_direction = DOWN - - self.number_line_labels = self.number_line.get_number_mobjects() - self.wait() - - origin_point = self.number_line.number_to_point(0) - nl_sources = VMobject() - pond_sources = VMobject() - - for i in range(-MAX_N,MAX_N+1): - anchor = self.number_line.number_to_point(2*i + 1) - ls = self.light_sources_array[i].copy() - ls.move_source_to(anchor) - nl_sources.add(ls) - pond_sources.add(self.light_sources_array[i].copy()) - - self.add(pond_sources) - self.remove(self.light_sources) - - self.outer_lake.rotate(TAU/8) - - # open sea - open_sea = Rectangle( - width = 20 * scale, - height = 10 * scale, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR, - fill_color = LAKE_COLOR, - fill_opacity = LAKE_OPACITY, - ).flip().next_to(origin_point,UP,buff = 0) - - self.play( - ReplacementTransform(pond_sources,nl_sources), - ReplacementTransform(self.outer_lake,open_sea), - FadeOut(self.inner_lake) - ) - self.play(FadeIn(self.number_line)) - - self.wait() - - v = 4 * scale * UP - self.play( - nl_sources.shift,v, - morty.shift,v, - self.number_line.shift,v, - indicator.shift,v, - indicator_reading.shift,v, - open_sea.shift,v, - self.obs_dot.shift,v, - ) - self.number_line_labels.shift(v) - - origin_point = self.number_line.number_to_point(0) - #self.remove(self.obs_dot) - self.play( - indicator.move_to, origin_point + scale * UP, - indicator_reading.move_to, origin_point + scale * UP, - FadeOut(open_sea), - FadeOut(morty), - FadeIn(self.number_line_labels) - ) - - two_sided_sum = TexMobject("\dots", "+", "{1\over (-11)^2}",\ - "+", "{1\over (-9)^2}", " + ", "{1\over (-7)^2}", " + ", "{1\over (-5)^2}", " + ", \ - "{1\over (-3)^2}", " + ", "{1\over (-1)^2}", " + ", "{1\over 1^2}", " + ", \ - "{1\over 3^2}", " + ", "{1\over 5^2}", " + ", "{1\over 7^2}", " + ", \ - "{1\over 9^2}", " + ", "{1\over 11^2}", " + ", "\dots") - - nb_symbols = len(two_sided_sum.submobjects) - - two_sided_sum.scale(TEX_SCALE) - - for (i,submob) in zip(list(range(nb_symbols)),two_sided_sum.submobjects): - submob.next_to(self.number_line.number_to_point(i - 13),DOWN, buff = 2*scale) - if (i == 0 or i % 2 == 1 or i == nb_symbols - 1): # non-fractions - submob.shift(0.3 * scale * DOWN) - - self.play(Write(two_sided_sum)) - - for i in range(MAX_N - 5, MAX_N): - self.remove(nl_sources.submobjects[i].ambient_light) - - for i in range(MAX_N, MAX_N + 5): - self.add_foreground_mobject(nl_sources.submobjects[i].ambient_light) - - self.wait() - - covering_rectangle = Rectangle( - width = FRAME_X_RADIUS * scale, - height = 2 * FRAME_Y_RADIUS * scale, - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 1, - ) - covering_rectangle.next_to(ORIGIN,LEFT,buff = 0) - for i in range(10): - self.add_foreground_mobject(nl_sources.submobjects[i]) - - self.add_foreground_mobject(indicator) - self.add_foreground_mobject(indicator_reading) - - - half_indicator_reading = TexMobject("{\pi^2 \over 8}").scale(TEX_SCALE) - half_indicator_reading.move_to(indicator) - - central_plus_sign = two_sided_sum[13] - - self.play( - FadeIn(covering_rectangle), - Transform(indicator_reading, half_indicator_reading), - FadeOut(central_plus_sign) - ) - - equals_sign = TexMobject("=").scale(TEX_SCALE) - equals_sign.move_to(central_plus_sign) - p = 2 * scale * LEFT + central_plus_sign.get_center()[1] * UP - - self.play( - indicator.move_to,p, - indicator_reading.move_to,p, - FadeIn(equals_sign), - ) - - self.revert_to_original_skipping_status() - - # show Randy admiring the result - randy = Randolph(color = MAROON_E).scale(scale).move_to(2*scale*DOWN+5*scale*LEFT) - self.play(FadeIn(randy)) - self.play(randy.change,"happy") - self.play(randy.change,"hooray") - -class CircumferenceText(Scene): - CONFIG = {"n" : 16} - def construct(self): - words = TextMobject("Circumference %d"%self.n) - words.scale(1.25) - words.to_corner(UP+LEFT) - self.add(words) - -class CenterOfLargerCircleOverlayText(Scene): - def construct(self): - words = TextMobject("Center of \\\\ larger circle") - arrow = Vector(DOWN+LEFT, color = WHITE) - arrow.shift(words.get_bottom() + SMALL_BUFF*DOWN - arrow.get_start()) - group = VGroup(words, arrow) - group.set_height(FRAME_HEIGHT - 1) - group.to_edge(UP) - self.add(group) - -class DiameterWordOverlay(Scene): - def construct(self): - word = TextMobject("Diameter") - word.set_width(FRAME_X_RADIUS) - word.rotate(-45*DEGREES) - self.play(Write(word)) - self.wait() - -class YayIPTApplies(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Heyo! The Inverse \\\\ Pythagorean Theorem \\\\ applies!", - bubble_kwargs = {"width" : 5}, - target_mode = "surprised" - ) - self.change_student_modes(*3*["hooray"]) - self.wait(2) - -class WalkThroughOneMoreStep(TeacherStudentsScene): - def construct(self): - self.student_says(""" - Wait...can you walk \\\\ - through one more step? - """) - self.play(self.teacher.change, "happy") - self.wait(4) - -class ThinkBackToHowAmazingThisIs(ThreeDScene): - CONFIG = { - "x_radius" : 100, - "max_shown_n" : 20, - } - def construct(self): - self.show_sum() - self.show_giant_circle() - - def show_sum(self): - number_line = NumberLine( - x_min = -self.x_radius, - x_max = self.x_radius, - numbers_to_show = list(range(-self.max_shown_n, self.max_shown_n)), - ) - number_line.add_numbers() - number_line.shift(2*DOWN) - - positive_dots, negative_dots = [ - VGroup(*[ - Dot(number_line.number_to_point(u*x)) - for x in range(1, int(self.x_radius), 2) - ]) - for u in (1, -1) - ] - dot_pairs = it.starmap(VGroup, list(zip(positive_dots, negative_dots))) - - # Decimal - decimal = DecimalNumber(0, num_decimal_places = 6) - decimal.to_edge(UP) - terms = [2./(n**2) for n in range(1, 100, 2)] - partial_sums = np.cumsum(terms) - - # pi^2/4 label - brace = Brace(decimal, DOWN) - pi_term = TexMobject("\pi^2 \over 4") - pi_term.next_to(brace, DOWN) - - term_mobjects = VGroup() - for n in range(1, self.max_shown_n, 2): - p_term = TexMobject("\\left(\\frac{1}{%d}\\right)^2"%n) - n_term = TexMobject("\\left(\\frac{-1}{%d}\\right)^2"%n) - group = VGroup(p_term, n_term) - group.scale(0.7) - p_term.next_to(number_line.number_to_point(n), UP, LARGE_BUFF) - n_term.next_to(number_line.number_to_point(-n), UP, LARGE_BUFF) - term_mobjects.add(group) - term_mobjects.set_color_by_gradient(BLUE, YELLOW) - plusses = VGroup(*[ - VGroup(*[ - TexMobject("+").next_to( - number_line.number_to_point(u*n), UP, buff = 1.25, - ) - for u in (-1, 1) - ]) - for n in range(0, self.max_shown_n, 2) - ]) - - zoom_out = always_shift( - self.camera.rotation_mobject, - direction = OUT, rate = 0.4 - ) - def update_decimal(decimal): - z = self.camera.rotation_mobject.get_center()[2] - decimal.set_height(0.07*z) - decimal.move_to(0.7*z*UP) - scale_decimal = Mobject.add_updater(decimal, update_decimal) - - - self.add(number_line, *dot_pairs) - self.add(zoom_out, scale_decimal) - - tuples = list(zip(term_mobjects, plusses, partial_sums)) - run_time = 1 - for term_mobs, plus_pair, partial_sum in tuples: - self.play( - FadeIn(term_mobs), - Write(plus_pair, run_time = 1), - ChangeDecimalToValue(decimal, partial_sum), - run_time = run_time - ) - self.wait(run_time) - run_time *= 0.9 - self.play(ChangeDecimalToValue(decimal, np.pi**2/4, run_time = 5)) - zoom_out.begin_wind_down() - self.wait() - self.remove(zoom_out, scale_decimal) - self.play(*list(map(FadeOut, it.chain( - term_mobjects, plusses, - number_line.numbers, [decimal] - )))) - - self.number_line = number_line - - def show_giant_circle(self): - self.number_line.insert_n_curves(10000) - everything = VGroup(*self.mobjects) - circle = everything.copy() - circle.move_to(ORIGIN) - circle.apply_function( - lambda x_y_z : complex_to_R3(7*np.exp(complex(0, 0.0315*x_y_z[0]))) - ) - circle.rotate(-TAU/4, about_point = ORIGIN) - circle.center() - - self.play(Transform(everything, circle, run_time = 6)) - -class ButWait(TeacherStudentsScene): - def construct(self): - self.student_says( - "But wait!", - target_mode = "angry", - run_time = 1, - ) - self.change_student_modes( - "sassy", "angry", "sassy", - added_anims = [self.teacher.change, "guilty"], - run_time = 1 - ) - self.student_says( - """ - You promised us \\\\ - $1+{1 \\over 4} + {1 \\over 9} + {1 \\over 16} + \\cdots$ - """, - target_mode = "sassy", - ) - self.wait(3) - self.teacher_says("Yes, but that's \\\\ very close.") - self.change_student_modes(*["plain"]*3) - self.wait(2) - -class FinalSumManipulationScene(PiCreatureScene): - - def construct(self): - - LAKE_COLOR = BLUE - LAKE_OPACITY = 0.15 - LAKE_STROKE_WIDTH = 5.0 - LAKE_STROKE_COLOR = BLUE - TEX_SCALE = 0.8 - - LIGHT_COLOR2 = RED - LIGHT_COLOR3 = BLUE - - unit_length = 1.5 - vertical_spacing = 2.5 * DOWN - switch_on_time = 0.2 - - sum_vertical_spacing = 1.5 - - randy = self.get_primary_pi_creature() - randy.set_color(MAROON_D) - randy.color = MAROON_D - randy.scale(0.7).flip().to_edge(DOWN + LEFT) - self.wait() - - ls_template = LightSource( - radius = 1, - num_levels = 10, - max_opacity_ambient = 0.5, - opacity_function = inverse_quadratic(1,0.75,1) - ) - - - odd_range = np.arange(1,9,2) - even_range = np.arange(2,16,2) - full_range = np.arange(1,8,1) - - self.number_line1 = NumberLine( - x_min = 0, - x_max = 11, - color = LAKE_STROKE_COLOR, - number_at_center = 0, - stroke_width = LAKE_STROKE_WIDTH, - stroke_color = LAKE_STROKE_COLOR, - #numbers_to_show = full_range, - number_scale_val = 0.5, - numbers_with_elongated_ticks = [], - unit_size = unit_length, - tick_frequency = 1, - line_to_number_buff = MED_SMALL_BUFF, - include_tip = True, - label_direction = UP, - ) - - self.number_line1.next_to(2.5 * UP + 3 * LEFT, RIGHT, buff = 0.3) - self.number_line1.add_numbers() - - odd_lights = VMobject() - for i in odd_range: - pos = self.number_line1.number_to_point(i) - ls = ls_template.copy() - ls.move_source_to(pos) - odd_lights.add(ls) - - self.play( - ShowCreation(self.number_line1, run_time = 5), - ) - self.wait() - - - odd_terms = VMobject() - for i in odd_range: - if i == 1: - term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", - fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) - else: - term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", - fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) - - term.next_to(self.number_line1.number_to_point(i), DOWN, buff = 1.5) - odd_terms.add(term) - - - for (ls, term) in zip(odd_lights.submobjects, odd_terms.submobjects): - self.play( - FadeIn(ls.lighthouse, run_time = switch_on_time), - SwitchOn(ls.ambient_light, run_time = switch_on_time), - Write(term, run_time = switch_on_time) - ) - - result1 = TexMobject("{\pi^2\over 8} =", fill_color = LIGHT_COLOR, - stroke_color = LIGHT_COLOR) - result1.next_to(self.number_line1, LEFT, buff = 0.5) - result1.shift(0.87 * vertical_spacing) - self.play(Write(result1)) - - - - - self.number_line2 = self.number_line1.copy() - self.number_line2.numbers_to_show = full_range - self.number_line2.shift(2 * vertical_spacing) - self.number_line2.add_numbers() - - full_lights = VMobject() - - for i in full_range: - pos = self.number_line2.number_to_point(i) - ls = ls_template.copy() - ls.color = LIGHT_COLOR3 - ls.move_source_to(pos) - full_lights.add(ls) - - self.play( - ShowCreation(self.number_line2, run_time = 5), - ) - self.wait() - - - full_lighthouses = VMobject() - full_ambient_lights = VMobject() - for ls in full_lights: - full_lighthouses.add(ls.lighthouse) - full_ambient_lights.add(ls.ambient_light) - - self.play( - LaggedStartMap(FadeIn, full_lighthouses, lag_ratio = 0.2, run_time = 3), - ) - - self.play( - LaggedStartMap(SwitchOn, full_ambient_lights, lag_ratio = 0.2, run_time = 3) - ) - - # for ls in full_lights.submobjects: - # self.play( - # FadeIn(ls.lighthouse, run_time = 0.1),#5 * switch_on_time), - # SwitchOn(ls.ambient_light, run_time = 0.1)#5 * switch_on_time), - # ) - - - - even_terms = VMobject() - for i in even_range: - term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR) - term.next_to(self.number_line1.number_to_point(i), DOWN, buff = sum_vertical_spacing) - even_terms.add(term) - - - even_lights = VMobject() - - for i in even_range: - pos = self.number_line1.number_to_point(i) - ls = ls_template.copy() - ls.color = LIGHT_COLOR2 - ls.move_source_to(pos) - even_lights.add(ls) - - for (ls, term) in zip(even_lights.submobjects, even_terms.submobjects): - self.play( - SwitchOn(ls.ambient_light, run_time = switch_on_time), - Write(term) - ) - self.wait() - - - - # now morph the even lights into the full lights - full_lights_copy = full_lights.copy() - even_lights_copy = even_lights.copy() - - - self.play( - Transform(even_lights,full_lights, run_time = 2) - ) - - - self.wait() - - for i in range(6): - self.play( - Transform(even_lights[i], even_lights_copy[i]) - ) - self.wait() - - # draw arrows - P1 = self.number_line2.number_to_point(1) - P2 = even_terms.submobjects[0].get_center() - Q1 = interpolate(P1, P2, 0.2) - Q2 = interpolate(P1, P2, 0.8) - quarter_arrow = Arrow(Q1, Q2, - color = LIGHT_COLOR2) - quarter_label = TexMobject("\\times {1\over 4}", fill_color = LIGHT_COLOR2, stroke_color = LIGHT_COLOR2) - quarter_label.scale(0.7) - quarter_label.next_to(quarter_arrow.get_center(), RIGHT) - - self.play( - ShowCreation(quarter_arrow), - Write(quarter_label), - ) - self.wait() - - P3 = odd_terms.submobjects[0].get_center() - R1 = interpolate(P1, P3, 0.2) - R2 = interpolate(P1, P3, 0.8) - three_quarters_arrow = Arrow(R1, R2, - color = LIGHT_COLOR) - three_quarters_label = TexMobject("\\times {3\over 4}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) - three_quarters_label.scale(0.7) - three_quarters_label.next_to(three_quarters_arrow.get_center(), LEFT) - - self.play( - ShowCreation(three_quarters_arrow), - Write(three_quarters_label) - ) - self.wait() - - four_thirds_arrow = Arrow(R2, R1, color = LIGHT_COLOR) - four_thirds_label = TexMobject("\\times {4\over 3}", fill_color = LIGHT_COLOR, stroke_color = LIGHT_COLOR) - four_thirds_label.scale(0.7) - four_thirds_label.next_to(four_thirds_arrow.get_center(), LEFT) - - - self.play( - FadeOut(quarter_label), - FadeOut(quarter_arrow), - FadeOut(even_lights), - FadeOut(even_terms) - - ) - self.wait() - - self.play( - ReplacementTransform(three_quarters_arrow, four_thirds_arrow), - ReplacementTransform(three_quarters_label, four_thirds_label) - ) - self.wait() - - full_terms = VMobject() - for i in range(1,8): #full_range: - if i == 1: - term = TexMobject("\phantom{+\,\,\,}{1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) - elif i == 7: - term = TexMobject("+\,\,\,\dots", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) - else: - term = TexMobject("+\,\,\, {1\over " + str(i) + "^2}", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) - - term.move_to(self.number_line2.number_to_point(i)) - full_terms.add(term) - - #return - - self.play( - FadeOut(self.number_line1), - FadeOut(odd_lights), - FadeOut(self.number_line2), - FadeOut(full_lights), - FadeIn(full_terms) - ) - self.wait() - - v = (sum_vertical_spacing + 0.5) * UP - self.play( - odd_terms.shift, v, - result1.shift, v, - four_thirds_arrow.shift, v, - four_thirds_label.shift, v, - odd_terms.shift, v, - full_terms.shift, v - ) - - arrow_copy = four_thirds_arrow.copy() - label_copy = four_thirds_label.copy() - arrow_copy.shift(2.5 * LEFT) - label_copy.shift(2.5 * LEFT) - - self.play( - FadeIn(arrow_copy), - FadeIn(label_copy) - ) - self.wait() - - final_result = TexMobject("{\pi^2 \over 6}=", fill_color = LIGHT_COLOR3, stroke_color = LIGHT_COLOR3) - final_result.next_to(arrow_copy, DOWN) - - self.play( - Write(final_result), - randy.change_mode,"hooray" - ) - self.wait() - - equation = VMobject() - equation.add(final_result) - equation.add(full_terms) - - - self.play( - FadeOut(result1), - FadeOut(odd_terms), - FadeOut(arrow_copy), - FadeOut(label_copy), - FadeOut(four_thirds_arrow), - FadeOut(four_thirds_label), - full_terms.shift,LEFT, - ) - self.wait() - - self.play(equation.shift, -equation.get_center()[1] * UP + UP + 1.5 * LEFT) - - result_box = Rectangle(width = 1.1 * equation.get_width(), - height = 2 * equation.get_height(), color = LIGHT_COLOR3) - result_box.move_to(equation) - - - self.play( - ShowCreation(result_box) - ) - self.wait() - -class LabeledArc(Arc): - CONFIG = { - "length" : 1 - } - - def __init__(self, angle, **kwargs): - - BUFFER = 0.8 - - Arc.__init__(self,angle,**kwargs) - - label = DecimalNumber(self.length, num_decimal_places = 0) - r = BUFFER * self.radius - theta = self.start_angle + self.angle/2 - label_pos = r * np.array([np.cos(theta), np.sin(theta), 0]) - - label.move_to(label_pos) - self.add(label) - -class ArcHighlightOverlaySceneCircumferenceEight(Scene): - CONFIG = { - "n" : 2, - } - def construct(self): - BASELINE_YPOS = -2.5 - OBSERVER_POINT = [0,BASELINE_YPOS,0] - LAKE0_RADIUS = 2.5 - INDICATOR_RADIUS = 0.6 - TICK_SIZE = 0.5 - LIGHTHOUSE_HEIGHT = 0.2 - LAKE_COLOR = BLUE - LAKE_OPACITY = 0.15 - LAKE_STROKE_WIDTH = 5.0 - LAKE_STROKE_COLOR = BLUE - TEX_SCALE = 0.8 - DOT_COLOR = BLUE - - FLASH_TIME = 1 - - def flash_arcs(n): - - angle = TAU/2**n - arcs = [] - arcs.append(LabeledArc(angle/2, start_angle = -TAU/4, radius = LAKE0_RADIUS, length = 1)) - - for i in range(1,2**n): - arcs.append(LabeledArc(angle, start_angle = -TAU/4 + (i-0.5)*angle, radius = LAKE0_RADIUS, length = 2)) - - arcs.append(LabeledArc(angle/2, start_angle = -TAU/4 - angle/2, radius = LAKE0_RADIUS, length = 1)) - - self.play( - FadeIn(arcs[0], run_time = FLASH_TIME) - ) - - for i in range(1,2**n + 1): - self.play( - FadeOut(arcs[i-1], run_time = FLASH_TIME), - FadeIn(arcs[i], run_time = FLASH_TIME) - ) - - self.play( - FadeOut(arcs[2**n], run_time = FLASH_TIME), - ) - - flash_arcs(self.n) - -class ArcHighlightOverlaySceneCircumferenceSixteen(ArcHighlightOverlaySceneCircumferenceEight): - CONFIG = { - "n" : 3, - } - -class InfiniteCircleScene(PiCreatureScene): - - def construct(self): - - morty = self.get_primary_pi_creature() - morty.set_color(MAROON_D).flip() - morty.color = MAROON_D - morty.scale(0.5).move_to(ORIGIN) - - arrow = Arrow(ORIGIN, 2.4 * RIGHT) - dot = Dot(color = BLUE).next_to(arrow) - ellipsis = TexMobject("\dots") - - infsum = VGroup() - infsum.add(ellipsis.copy()) - - for i in range(3): - infsum.add(arrow.copy().next_to(infsum.submobjects[-1])) - infsum.add(dot.copy().next_to(infsum.submobjects[-1])) - - infsum.add(arrow.copy().next_to(infsum.submobjects[-1])) - infsum.add(ellipsis.copy().next_to(infsum.submobjects[-1])) - - infsum.next_to(morty,DOWN, buff = 1) - - self.wait() - self.play( - LaggedStartMap(FadeIn,infsum,lag_ratio = 0.2) - ) - self.wait() - - A = infsum.submobjects[-1].get_center() + 0.5 * RIGHT - B = A + RIGHT + 1.3 * UP + 0.025 * LEFT - right_arc = DashedLine(TAU/4*UP, ORIGIN, stroke_color = YELLOW, - stroke_width = 8).apply_complex_function(np.exp) - right_arc.rotate(-TAU/4).next_to(infsum, RIGHT).shift(0.5 * UP) - right_tip_line = Arrow(B - UP, B, color = WHITE) - right_tip_line.add_tip() - right_tip = right_tip_line.get_tip() - right_tip.set_fill(color = YELLOW) - right_arc.add(right_tip) - - - C = B + 3.2 * UP - right_line = DashedLine(B + 0.2 * DOWN,C + 0.2 * UP, stroke_color = YELLOW, - stroke_width = 8) - - ru_arc = right_arc.copy().rotate(angle = TAU/4) - ru_arc.remove(ru_arc.submobjects[-1]) - ru_arc.to_edge(UP+RIGHT, buff = 0.15) - - D = np.array([5.85, 3.85,0]) - E = np.array([-D[0],D[1],0]) - up_line = DashedLine(D, E, stroke_color = YELLOW, - stroke_width = 8) - - lu_arc = ru_arc.copy().flip().to_edge(LEFT + UP, buff = 0.15) - left_line = right_line.copy().flip(axis = RIGHT).to_edge(LEFT, buff = 0.15) - - left_arc = right_arc.copy().rotate(-TAU/4) - left_arc.next_to(infsum, LEFT).shift(0.5 * UP + 0.1 * LEFT) - - right_arc.shift(0.2 * RIGHT) - right_line.shift(0.2 * RIGHT) - - self.play(FadeIn(right_arc)) - self.play(ShowCreation(right_line)) - self.play(FadeIn(ru_arc)) - self.play(ShowCreation(up_line)) - self.play(FadeIn(lu_arc)) - self.play(ShowCreation(left_line)) - self.play(FadeIn(left_arc)) - - - - self.wait() - -class Credits(Scene): - def construct(self): - credits = VGroup(*[ - VGroup(*list(map(TextMobject, pair))) - for pair in [ - ("Primary writer and animator:", "Ben Hambrecht"), - ("Editing, advising, narrating:", "Grant Sanderson"), - ("Based on a paper originally by:", "Johan Wästlund"), - ] - ]) - for credit, color in zip(credits, [MAROON_D, BLUE_D, WHITE]): - credit[1].set_color(color) - credit.arrange(DOWN, buff = SMALL_BUFF) - - credits.arrange(DOWN, buff = LARGE_BUFF) - - credits.center() - patreon_logo = PatreonLogo() - patreon_logo.to_edge(UP) - - for credit in credits: - self.play(LaggedStartMap(FadeIn, credit[0])) - self.play(FadeIn(credit[1])) - self.wait() - self.play( - credits.next_to, patreon_logo.get_bottom(), DOWN, MED_LARGE_BUFF, - DrawBorderThenFill(patreon_logo) - ) - self.wait() - -class Promotion(PiCreatureScene): - CONFIG = { - "seconds_to_blink" : 5, - } - def construct(self): - url = TextMobject("https://brilliant.org/3b1b/") - url.to_corner(UP+LEFT) - - rect = Rectangle(height = 9, width = 16) - rect.set_height(5.5) - rect.next_to(url, DOWN) - rect.to_edge(LEFT) - - self.play( - Write(url), - self.pi_creature.change, "raise_right_hand" - ) - self.play(ShowCreation(rect)) - self.wait(2) - self.change_mode("thinking") - self.wait() - self.look_at(url) - self.wait(10) - self.change_mode("happy") - self.wait(10) - self.change_mode("raise_right_hand") - self.wait(10) - - self.remove(rect) - self.play( - url.next_to, self.pi_creature, UP+LEFT - ) - url_rect = SurroundingRectangle(url) - self.play(ShowCreation(url_rect)) - self.play(FadeOut(url_rect)) - self.wait(3) - -class BaselPatreonThanks(PatreonEndScreen): - CONFIG = { - "specific_patrons" : [ - "CrypticSwarm ", - "Ali Yahya", - "Juan Benet", - "Markus Persson", - "Damion Kistler", - "Burt Humburg", - "Yu Jun", - "Dave Nicponski", - "Kaustuv DeBiswas", - "Joseph John Cox", - "Luc Ritchie", - "Achille Brighton", - "Rish Kundalia", - "Yana Chernobilsky", - "Shìmín Ku$\\overline{\\text{a}}$ng", - "Mathew Bramson", - "Jerry Ling", - "Mustafa Mahdi", - "Meshal Alshammari", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Robert Teed", - "Samantha D. Suplee", - "Mark Govea", - "John Haley", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Desmos ", - "Boris Veselinovich", - "Ryan Dahl", - "Ripta Pasay", - "Eric Lavault", - "Randall Hunt", - "Andrew Busey", - "Mads Elvheim", - "Tianyu Ge", - "Awoo", - "Dr. David G. Stork", - "Linh Tran", - "Jason Hise", - "Bernd Sing", - "James H. Park", - "Ankalagon ", - "Devin Scott", - "Mathias Jansson", - "David Clark", - "Ted Suzman", - "Eric Chow", - "Michael Gardner", - "David Kedmey", - "Jonathan Eppele", - "Clark Gaebel", - "Jordan Scales", - "Ryan Atallah", - "supershabam ", - "1stViewMaths ", - "Jacob Magnuson", - "Chloe Zhou", - "Ross Garber", - "Thomas Tarler", - "Isak Hietala", - "Egor Gumenuk", - "Waleed Hamied", - "Oliver Steele", - "Yaw Etse", - "David B", - "Delton Ding", - "James Thornton", - "Felix Tripier", - "Arthur Zey", - "George Chiesa", - "Norton Wang", - "Kevin Le", - "Alexander Feldman", - "David MacCumber", - "Jacob Kohl", - "Sergei ", - "Frank Secilia", - "Patrick Mézard", - "George John", - "Akash Kumar", - "Britt Selvitelle", - "Jonathan Wilson", - "Ignacio Freiberg", - "Zhilong Yang", - "Karl Niu", - "Dan Esposito", - "Michael Kunze", - "Giovanni Filippi", - "Eric Younge", - "Prasant Jagannath", - "Andrejs Olins", - "Cody Brocious", - ], - } - def construct(self): - next_video = TextMobject("$\\uparrow$ Next video $\\uparrow$") - next_video.to_edge(RIGHT, buff = 1.5) - next_video.shift(MED_SMALL_BUFF*UP) - next_video.set_color(YELLOW) - self.add_foreground_mobject(next_video) - PatreonEndScreen.construct(self) - -class Thumbnail(Scene): - CONFIG = { - "light_source_config" : { - "num_levels" : 250, - "radius" : 10.0, - "max_opacity_ambient" : 1.0, - "opacity_function" : inverse_quadratic(1,0.25,1) - } - } - def construct(self): - equation = TexMobject( - "1", "+", "{1\over 4}", "+", - "{1\over 9}","+", "{1\over 16}","+", - "{1\over 25}", "+", "\cdots" - ) - equation.scale(1.8) - equation.move_to(2*UP) - equation.set_stroke(RED, 1) - answer = TexMobject("= \\frac{\\pi^2}{6}", color = LIGHT_COLOR) - answer.scale(3) - answer.set_stroke(RED, 1) - # answer.next_to(equation, DOWN, buff = 1) - answer.move_to(1.25*DOWN) - #equation.move_to(2 * UP) - #answer = TexMobject("={\pi^2\over 6}", color = LIGHT_COLOR).scale(3) - #answer.next_to(equation, DOWN, buff = 1) - - lake_radius = 6 - lake_center = ORIGIN - - lake = Circle( - fill_color = BLUE, - fill_opacity = 0.15, - radius = lake_radius, - stroke_color = BLUE_D, - stroke_width = 3, - ) - lake.move_to(lake_center) - - for i in range(16): - theta = -TAU/4 + (i + 0.5) * TAU/16 - pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0]) - ls = LightSource(**self.light_source_config) - ls.move_source_to(pos) - lake.add(ls.ambient_light) - lake.add(ls.lighthouse) - - self.add(lake) - self.add(equation, answer) - self.wait() - - - - - - - - - - - - diff --git a/from_3b1b/old/bell.py b/from_3b1b/old/bell.py deleted file mode 100644 index b83c98da..00000000 --- a/from_3b1b/old/bell.py +++ /dev/null @@ -1,2452 +0,0 @@ -from manimlib.imports import * - -from tqdm import tqdm as ProgressDisplay - -from .waves import * -from functools import reduce - -#force_skipping -#revert_to_original_skipping_status - -class PhotonPassesCompletelyOrNotAtAll(DirectionOfPolarizationScene): - CONFIG = { - "pol_filter_configs" : [{ - "include_arrow_label" : False, - "label_tex" : "\\text{Filter}", - }], - "EMWave_config" : { - "wave_number" : 0, - "A_vect" : [0, 1, 1], - "start_point" : FRAME_X_RADIUS*LEFT + DOWN + 1.5*OUT, - }, - "start_theta" : -0.9*np.pi, - "target_theta" : -0.6*np.pi, - "apply_filter" : True, - "lower_portion_shift" : 3*IN, - "show_M_vects" : True, - } - def setup(self): - DirectionOfPolarizationScene.setup(self) - if not self.show_M_vects: - for M_vect in self.em_wave.M_vects: - M_vect.set_fill(opacity = 0) - self.update_mobjects() - for vect in it.chain(self.em_wave.E_vects, self.em_wave.M_vects): - vect.reset_normal_vector() - self.remove(self.em_wave) - - def construct(self): - pol_filter = self.pol_filter - pol_filter.shift(0.5*OUT) - lower_filter = pol_filter.copy() - lower_filter.save_state() - pol_filter.remove(pol_filter.label) - - passing_words = TextMobject("Photon", "passes through\\\\", "entirely") - passing_words.set_color(GREEN) - filtered_words = TextMobject("Photon", "is blocked\\\\", "entirely") - filtered_words.set_color(RED) - for words in passing_words, filtered_words: - words.next_to(ORIGIN, UP+LEFT) - words.shift(2*UP) - words.add_background_rectangle() - words.rotate(np.pi/2, RIGHT) - filtered_words.shift(self.lower_portion_shift) - - passing_photon = WavePacket( - run_time = 2, - get_filtered = False, - em_wave = self.em_wave.copy() - ) - lower_em_wave = self.em_wave.copy() - lower_em_wave.mobject.shift(self.lower_portion_shift) - lower_em_wave.start_point += self.lower_portion_shift - filtered_photon = WavePacket( - run_time = 2, - get_filtered = True, - em_wave = lower_em_wave.copy() - ) - green_flash = ApplyMethod( - pol_filter.set_fill, GREEN, - rate_func = squish_rate_func(there_and_back, 0.4, 0.6), - run_time = passing_photon.run_time - ) - - self.play( - DrawBorderThenFill(pol_filter), - Write(pol_filter.label, run_time = 2), - ) - self.move_camera(theta = self.target_theta) - self.play( - FadeIn(passing_words), - passing_photon, - green_flash, - ) - self.play( - lower_filter.restore, - lower_filter.shift, self.lower_portion_shift, - FadeIn(filtered_words) - ) - red_flash = ApplyMethod( - lower_filter.set_fill, RED, - rate_func = squish_rate_func(there_and_back, 0.4, 0.6), - run_time = filtered_photon.run_time - ) - for x in range(4): - self.play( - passing_photon, - filtered_photon, - green_flash, - red_flash, - ) - self.wait() - -class PhotonPassesCompletelyOrNotAtAllForWavesVideo(PhotonPassesCompletelyOrNotAtAll): - CONFIG = { - "show_M_vects" : False, - } - -class DirectionOfPolarization(DirectionOfPolarizationScene): - def construct(self): - self.remove(self.pol_filter) - self.axes.z_axis.rotate(np.pi/2, OUT) - words = TextMobject("Polarization direction") - words.next_to(ORIGIN, UP+RIGHT, LARGE_BUFF) - words.shift(2*UP) - words.rotate(np.pi/2, RIGHT) - words.rotate(-np.pi/2, OUT) - - em_wave = self.em_wave - - self.add(em_wave) - self.wait(2) - self.move_camera( - phi = self.target_phi, - theta = self.target_theta - ) - self.play(Write(words, run_time = 1)) - for angle in 2*np.pi/3, -np.pi/3, np.pi/4: - self.change_polarization_direction(angle) - self.wait() - self.wait(2) - -class PhotonsThroughPerpendicularFilters(PhotonPassesCompletelyOrNotAtAll): - CONFIG = { - "filter_x_coordinates" : [-2, 2], - "pol_filter_configs" : [ - {"filter_angle" : 0}, - {"filter_angle" : np.pi/2}, - ], - "start_theta" : -0.9*np.pi, - "target_theta" : -0.6*np.pi, - "EMWave_config" : { - "A_vect" : [0, 0, 1], - "start_point" : FRAME_X_RADIUS*LEFT + DOWN + OUT, - }, - "apply_filter" : False, - } - def construct(self): - photons = self.get_photons() - prob_text = self.get_probability_text() - self.pol_filters = VGroup(*reversed(self.pol_filters)) - ninety_filter, zero_filter = self.pol_filters - self.remove(*self.pol_filters) - - self.play(DrawBorderThenFill(zero_filter), run_time = 1) - self.add_foreground_mobject(zero_filter) - self.move_camera( - theta = self.target_theta, - added_anims = [ApplyFunction( - self.reposition_filter_label, - zero_filter - )] - ) - self.reposition_filter_label(ninety_filter) - self.play(self.get_photons()[2]) - self.play(FadeIn(ninety_filter)) - self.add_foreground_mobject(ninety_filter) - self.shoot_photon() - self.shoot_photon() - self.play(FadeIn(prob_text)) - for x in range(6): - self.shoot_photon() - - def reposition_filter_label(self, pf): - pf.arrow_label.rotate_in_place(np.pi/2, OUT) - pf.arrow_label.next_to(pf.arrow, RIGHT) - return pf - - - def shoot_photon(self, *added_anims): - photon = self.get_photons()[1] - pol_filter = self.pol_filters[0] - absorption = self.get_filter_absorption_animation(pol_filter, photon) - self.play(photon, absorption) - - - def get_photons(self): - self.reference_line.rotate(np.pi/4) - self.update_mobjects() - return [ - WavePacket( - filter_distance = FRAME_X_RADIUS + x, - get_filtered = True, - em_wave = self.em_wave.copy(), - run_time = 1, - ) - for x in (-2, 2, 10) - ] - - def get_probability_text(self, prob = 0): - prob_text = TexMobject( - "P(", "\\substack", "{\\text{photons that make it} \\\\ ", - " \\text{here } ", "\\text{make it}", - " \\text{ here} }", ")", "=", str(int(prob*100)), "\\%", - arg_separator = "" - ) - here1, here2 = prob_text.get_parts_by_tex("here") - here1.set_color(GREEN) - here2.set_color(RED) - prob_text.add_background_rectangle() - prob_text.next_to(ORIGIN, UP+RIGHT) - prob_text.shift(2.5*UP+LEFT) - prob_text.rotate(np.pi/2, RIGHT) - arrows = [ - Arrow( - here.get_edge_center(IN), - DOWN+OUT + x*RIGHT, - color = here.get_color(), - normal_vector = DOWN+OUT, - ) - for here, x in ((here1, 0), (here2, 4)) - ] - prob_text.add(*arrows) - - return prob_text - -class MoreFiltersMoreLight(FilterScene): - CONFIG = { - "filter_x_coordinates" : list(range(-2, 3)), - "pol_filter_configs" : [ - { - "include_arrow_label" : False, - "filter_angle" : angle - } - for angle in np.linspace(0, np.pi/2, 5) - ], - "ambient_rotation_rate" : 0, - "arrow_rgb" : (0, 0, 0), - "background_rgb" : (245, 245, 245), - } - def construct(self): - self.remove(self.axes) - pfs = VGroup(*reversed(self.pol_filters)) - self.color_filters(pfs) - self.remove(pfs) - self.build_color_map(pfs) - self.add(pfs[0], pfs[2], pfs[4]) - pfs.center().scale(1.5) - - self.move_camera( - phi = 0.9*np.pi/2, - theta = -0.95*np.pi, - ) - self.play( - Animation(pfs[0]), - pfs[2].shift, 3*OUT, - Animation(pfs[4]), - ) - self.wait() - self.play( - Animation(pfs[0]), - pfs[2].shift, 3*IN, - Animation(pfs[4]), - ) - - pfs[1].shift(8*OUT) - self.play( - Animation(pfs[0]), - pfs[1].shift, 8*IN, - Animation(VGroup(pfs[2], pfs[4])), - run_time = 2 - ) - self.wait() - - pfs[3].shift(8*OUT) - self.play( - Animation(VGroup(*pfs[:3])), - pfs[3].shift, 8*IN, - Animation(VGroup(*pfs[4:])), - run_time = 2 - ) - self.wait() - - def color_filters(self, pfs): - colors = [RED, GREEN, BLUE, MAROON_B, PURPLE_C] - for pf, color in zip(pfs, colors): - pf.set_fill(color, 0.5) - pf.arrow.set_fill(WHITE, 1) - turn_off_3d_shading(pfs) - - def build_color_map(self, pfs): - phi, theta = self.camera.get_phi(), self.camera.get_theta() - self.set_camera_orientation(np.pi/2, -np.pi) - - self.original_rgbas = [(255, 255, 255)] - self.new_rgbas = [self.arrow_rgb] - for bool_array in it.product(*5*[[True, False]]): - pfs_to_use = VGroup(*[ - pf - for pf, b in zip(pfs, bool_array) - if b - ]) - self.camera.capture_mobject(pfs_to_use) - frame = self.camera.get_image() - h, w, three = frame.shape - rgb = frame[3*h/8, 7*w/12] - self.original_rgbas.append(rgb) - - angles = [pf.filter_angle for pf in pfs_to_use] - p = 0.5 - for a1, a2 in zip(angles, angles[1:]): - p *= np.cos(a2 - a1)**2 - new_rgb = (255*p*np.ones(3)).astype(int) - if not any(bool_array): - new_rgb = self.background_rgb - - self.new_rgbas.append(new_rgb) - self.camera.reset() - self.set_camera_orientation(phi, theta) - - def update_frame(self, mobjects = None, image = None): - FilterScene.update_frame(self, mobjects) - - def get_frame(self): - frame = FilterScene.get_frame(self) - bool_arrays = [ - (frame[:,:,0] == r) & (frame[:,:,1] == g) & (frame[:,:,2] == b) - for (r, g, b) in self.original_rgbas - ] - for ba, new_rgb in zip(bool_arrays, self.new_rgbas): - frame[ba] = new_rgb - covered = reduce( - lambda b1, b2 : b1 | b2, - bool_arrays - ) - frame[~covered] = [65, 65, 65] - return frame - -class MoreFiltersMoreLightBlackBackground(MoreFiltersMoreLight): - CONFIG = { - "arrow_rgb" : (255, 255, 255), - "background_rgb" : (0, 0, 0), - } - -class ConfusedPiCreature(Scene): - def construct(self): - randy = Randolph() - self.play( - randy.change, "confused", 3*(UP+RIGHT), - ) - self.play(Blink(randy)) - self.wait(2) - self.play(Blink(randy)) - self.wait(2) - -class AngryPiCreature(PiCreatureScene): - def construct(self): - self.pi_creature_says( - "No, \\emph{locality} \\\\ must be wrong!", - target_mode = "angry", - look_at_arg = 2*RIGHT, - run_time = 1 - ) - self.wait(3) - - def create_pi_creature(self): - return Randolph().shift(DOWN+3*LEFT) - -class ShowALittleMath(TeacherStudentsScene): - def construct(self): - exp1 = TexMobject( - "|", "\\psi", "\\rangle = ", - "\\alpha", "|\\uparrow\\rangle", - "+", "\\beta", "|\\rightarrow\\rangle" - ) - exp2 = TexMobject( - "|| \\langle", "\\psi", "|", "\\psi", "\\rangle ||^2", - "= ", "\\alpha", "^2", "+", "\\beta", "^2" - ) - color_map = { - "alpha" : GREEN, - "beta" : RED, - "psi" : BLUE - } - for exp in exp1, exp2: - exp.set_color_by_tex_to_color_map(color_map) - exp1.next_to(self.teacher.get_corner(UP+LEFT), UP, LARGE_BUFF) - - exp2.move_to(exp1) - - self.play( - Write(exp1, run_time = 2), - self.teacher.change, "raise_right_hand" - ) - self.play(exp1.shift, UP) - self.play(*[ - ReplacementTransform( - exp1.get_parts_by_tex(tex).copy(), - exp2.get_parts_by_tex(tex).copy(), - ) - for tex in list(color_map.keys()) - ] + [Write(exp2, run_time = 2)]) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = exp2 - ) - self.wait(2) - -class SecondVideoWrapper(Scene): - def construct(self): - title = TextMobject("Some light quantum mechanics") - title.to_edge(UP) - self.add(title) - - screen_rect = ScreenRectangle(height = 6) - screen_rect.next_to(title, DOWN) - self.play(ShowCreation(screen_rect)) - self.wait(3) - -class BasicsOfPolarization(DirectionOfPolarizationScene): - CONFIG = { - "apply_filter" : True, - } - def construct(self): - self.setup_rectangles() - self.show_continual_wave() - self.show_photons() - - def show_continual_wave(self): - em_wave = self.em_wave - - title = TextMobject("Waves in the ``electromagnetic field''") - title.to_edge(UP) - subtitle = TextMobject("Polarization = Direction of", "wiggling") - subtitle.set_color_by_tex("wiggling", BLUE) - subtitle.next_to(title, DOWN) - for words in title, subtitle: - words.add_background_rectangle() - words.rotate(np.pi/2, RIGHT) - - self.play(Write(title)) - self.wait(2) - self.play( - Write(subtitle, run_time = 2), - FadeIn(self.rectangles) - ) - self.change_polarization_direction(np.pi/2, run_time = 3) - self.wait() - self.change_polarization_direction(-np.pi/12, run_time = 2) - self.move_camera(theta = -0.95*np.pi) - self.change_polarization_direction(-np.pi/6, run_time = 2) - self.change_polarization_direction(np.pi/6, run_time = 2) - self.move_camera(theta = -0.6*np.pi) - self.change_polarization_direction(-np.pi/6, run_time = 2) - self.change_polarization_direction(np.pi/6, run_time = 2) - self.change_polarization_direction(-5*np.pi/12, run_time = 2) - self.play( - FadeOut(em_wave.mobject), - FadeOut(self.rectangles), - ) - self.remove(em_wave) - self.reference_line.put_start_and_end_on(ORIGIN, RIGHT) - - def show_photons(self): - quantum_left_words = TextMobject( - "Quantum", "$\\Rightarrow$", - ) - quantum_left_words.next_to(ORIGIN, UP+RIGHT) - quantum_left_words.shift(UP) - quantum_right_words = TextMobject( - "Completely through", "or \\\\", - "Completely blocked", - ) - quantum_right_words.scale(0.8) - quantum_right_words.next_to(quantum_left_words, buff = 0) - quantum_right_words.set_color_by_tex("through", GREEN) - quantum_right_words.set_color_by_tex("blocked", RED) - quantum_words = VGroup(quantum_left_words, quantum_right_words) - quantum_words.rotate(np.pi/2, RIGHT) - - prob_eq = TexMobject( - "&P(", "\\text{Pass}", ")", "=", "p\\\\", - "&P(", "\\text{Blocked}", ")", "=", "1-p", - ) - prob_eq.set_color_by_tex_to_color_map({ - "Pass" : GREEN, - "Blocked" : RED, - }) - prob_eq.next_to(ORIGIN, DOWN+RIGHT) - prob_eq.shift(RIGHT) - prob_eq.rotate(np.pi/2, RIGHT) - - config = dict(self.EMWave_config) - config.update({ - "wave_number" : 0, - "A_vect" : [0, 1, -1], - }) - self.em_wave = EMWave(**config) - self.update_mobjects() - passing_photon = WavePacket( - em_wave = self.em_wave.copy(), - run_time = 1.5, - ) - filtered_photon = WavePacket( - em_wave = self.em_wave.copy(), - get_filtered = True, - run_time = 1.5, - ) - - self.play(FadeIn( - quantum_words, - run_time = 2, - lag_ratio = 0.5 - )) - anim_sets = [ - [passing_photon], - [ - filtered_photon, - self.get_filter_absorption_animation( - self.pol_filter, - filtered_photon - ) - ], - ] - - for index in 0, 1: - self.play(*anim_sets[index]) - self.play( - # FadeIn(prob_eq, lag_ratio = 0.5), - passing_photon - ) - for index in 1, 0, 0, 1: - self.play(*anim_sets[index]) - -class AngleToProbabilityChart(Scene): - def construct(self): - left_title = TextMobject("Angle between \\\\ filters") - left_title.to_corner(UP+LEFT) - right_title = TextMobject( - "Probability that photons passing \\\\", - "through the first pass through the second" - ) - right_title.next_to(left_title, RIGHT, LARGE_BUFF) - - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.to_edge(UP, buff = 2) - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - v_line.next_to(left_title, RIGHT, MED_LARGE_BUFF) - v_line.to_edge(UP, buff = 0) - VGroup(h_line, v_line).set_color(BLUE) - self.add(left_title, right_title, h_line, v_line) - - angles = [0, 22.5, 45, 67.5, 90] - angle_mobs = VGroup(*[ - TexMobject(str(angle) + "^\\circ") - for angle in angles - ]) - angle_mobs.arrange(DOWN, buff = MED_LARGE_BUFF) - angle_mobs.next_to(left_title, DOWN, LARGE_BUFF) - - probs = [ - np.cos(angle*np.pi/180.0)**2 - for angle in angles - ] - prob_mobs = VGroup(*[ - TexMobject("%.1f"%(100*prob) + "\\%") - for prob in probs - ]) - prob_mobs.set_color(YELLOW) - - angle_prob_pairs = list(zip(angle_mobs, prob_mobs)) - for angle_mob, prob_mob in angle_prob_pairs: - prob_mob.next_to(angle_mob, RIGHT, buff = 3) - for prob_mob in prob_mobs[1:]: - prob_mob.align_to(prob_mobs[0], LEFT) - - - for i in [0, 4, 2, 1, 3]: - self.play(FadeIn(angle_mobs[i])) - self.play(ReplacementTransform( - angle_mobs[i].copy(), prob_mobs[i] - )) - - explanation = TextMobject("Based on $\\cos(\\theta)^2$") - explanation.next_to(prob_mobs, RIGHT, LARGE_BUFF) - self.play(Write(explanation, run_time = 2)) - self.wait() - -class ShowVariousFilterPairsWithPhotonsOverTime(PhotonsThroughPerpendicularFilters): - CONFIG = { - "filter_x_coordinates" : [-2, 2, 2, 2, 2], - "pol_filter_configs" : [ - {"filter_angle" : angle} - for angle in (0, 0, np.pi/2, np.pi/4, np.pi/8) - ], - "apply_filter" : False, - } - def setup(self): - PhotonsThroughPerpendicularFilters.setup(self) - self.new_filters = self.pol_filters[2:] - self.remove(*self.new_filters) - self.pol_filters = self.pol_filters[:2] - - def construct(self): - self.photons = self.get_photons() - - self.add_filters() - self.add_probability_text() - self.show_photons() - for pol_filter in self.new_filters: - self.change_to_new_filter(pol_filter) - self.show_photons() - - def add_filters(self): - self.remove(*self.pol_filters[1:]) - self.wait() - self.play(ReplacementTransform( - self.pol_filters[0].copy().set_fill(BLACK, 1), - self.pol_filters[1] - )) - self.move_camera( - theta = -0.65*np.pi, - added_anims = list(it.chain(*[ - [ - pf.arrow_label.rotate_in_place, np.pi/2, OUT, - pf.arrow_label.next_to, pf.arrow, RIGHT - ] - for pf in self.pol_filters[:2] - ])) - ) - for pf in self.new_filters: - pf.arrow_label.rotate_in_place(np.pi/2, OUT) - pf.arrow_label.next_to(pf.arrow, RIGHT) - - self.second_filter = self.pol_filters[1] - self.add_foreground_mobject(self.second_filter) - - def add_probability_text(self): - prob_text = self.get_probability_text(self.get_prob()) - self.play(FadeIn(prob_text)) - self.prob_text = prob_text - - def show_photons(self, n_photons = 5): - p = self.get_prob() - blocked_photon = copy.deepcopy(self.photons[0]) - blocked_photon.rate_func = squish_rate_func( - lambda x : x, 0, 0.5, - ) - first_absorption = self.get_filter_absorption_animation( - self.pol_filters[0], blocked_photon - ) - first_absorption.rate_func = squish_rate_func( - first_absorption.rate_func, 0, 0.5, - ) - - photons = [ - copy.deepcopy(self.photons[2 if q <= p else 1]) - for q in np.arange(0, 1, 1./n_photons) - ] - random.shuffle(photons) - for photon in photons: - photon.rate_func = squish_rate_func( - lambda x : x, 0.5, 1 - ) - added_anims = [] - if photon.filter_distance == FRAME_X_RADIUS + 2: - absorption = self.get_filter_absorption_animation( - self.second_filter, photon - ) - absorption.rate_func = squish_rate_func( - absorption.rate_func, 0.5, 1 - ) - added_anims.append(absorption) - self.play( - blocked_photon, - first_absorption, - photon, - *added_anims - ) - self.wait() - - def change_to_new_filter(self, pol_filter, added_anims = None): - if added_anims is None: - added_anims = [] - self.play( - Transform(self.second_filter, pol_filter), - *added_anims - ) - self.second_filter.filter_angle = pol_filter.filter_angle - new_prob_text = self.get_probability_text(self.get_prob()) - new_prob_text[1][-2].set_color(YELLOW) - self.play(Transform(self.prob_text, new_prob_text)) - - #### - - def get_prob(self, pol_filter = None): - if pol_filter is None: - pol_filter = self.second_filter - return np.cos(pol_filter.filter_angle)**2 - -class ShowVariousFilterPairs(ShowVariousFilterPairsWithPhotonsOverTime): - CONFIG = { - "filter_x_coordinates" : [], - "filter_z_coordinates" : [2.5, 0, -2.5], - "angles" : [0, np.pi/4, np.pi/2], - "n_lines" : 20, - "new_group_shift_val" : 2.5*IN, - "prev_group_shift_val" : 1.75*IN, - "ambient_rotation_rate" : 0.015, - "line_start_length" : 16, - "line_end_length" : 16, - "lines_depth" : 1.2, - "lines_shift_vect" : SMALL_BUFF*OUT, - } - def setup(self): - ShowVariousFilterPairsWithPhotonsOverTime.setup(self) - self.remove(*self.pol_filters) - self.prev_groups = VGroup() - self.remove(self.axes) - self.setup_filters() - self.stop_ambient_camera_rotation() - self.prob_texts = VGroup() - - def setup_filters(self): - self.filter_pairs = [] - zs = self.filter_z_coordinates - for non_zero_angle, z in zip(self.angles, zs): - filter_pair = VGroup() - for angle, x in (0, -3), (non_zero_angle, 3): - pf = PolarizingFilter(filter_angle = angle) - pf.scale(0.7) - pf.rotate(np.pi/2, RIGHT) - pf.rotate(np.pi/2, IN) - pf.shift(x*RIGHT + z*OUT) - pf.arrow_label.rotate(np.pi/2, OUT) - pf.arrow_label.next_to(pf.arrow, RIGHT, SMALL_BUFF) - - filter_pair.add(pf) - self.filter_pairs.append(filter_pair) - - def construct(self): - self.add_top_filters() - self.show_light(self.filter_pairs[0]) - self.turn_one_filter_pair_into_another(0, 2) - self.show_light(self.filter_pairs[2]) - self.turn_one_filter_pair_into_another(2, 1) - self.show_light(self.filter_pairs[1]) - - def add_top_filters(self): - pf1, pf2 = pair = self.filter_pairs[0] - for pf in pair: - pf.save_state() - pf.arrow_label.rotate(np.pi/2, IN) - pf.arrow_label.next_to(pf.arrow, UP, SMALL_BUFF) - pf.shift(2*IN) - - self.add(pf1) - self.play( - ReplacementTransform(pf1.copy().fade(1), pf2), - Animation(pf1) - ) - self.move_camera( - 0.9*np.pi/2, -0.6*np.pi, - added_anims = [ - pf.restore - for pf in pair - ] - ) - - def show_light(self, filter_pair): - pf1, pf2 = filter_pair - lines_to_pf1 = self.get_lines(None, pf1) - vect = lines_to_pf1[1].get_center() - lines_to_pf1[0].get_center() - lines_to_pf1.add(*lines_to_pf1.copy().shift(vect/2)) - lines_to_pf2 = self.get_lines(pf1, pf2) - lines_from_pf2 = self.get_lines(pf2) - - prob = self.get_prob(pf2) - n_black = int(prob*len(lines_from_pf2)) - VGroup(*lines_from_pf2[n_black:]).set_stroke(BLACK, 0) - - kwargs = { - "rate_func" : None, - "lag_ratio" : 0, - } - - self.play(ShowCreation(lines_to_pf1, run_time = 2./3, **kwargs)) - self.play( - ShowCreation(lines_to_pf2, **kwargs), - Animation(VGroup(pf1, lines_to_pf1)), - run_time = 1./6, - ) - self.play( - ShowCreation(lines_from_pf2, **kwargs), - Animation(VGroup(pf2, lines_to_pf2, pf1, lines_to_pf1)), - run_time = 2./3, - ) - - group = VGroup( - lines_from_pf2, pf2, lines_to_pf2, pf1, lines_to_pf2 - ) - - #Write probability - prob_text = self.get_probability_text(pf2) - self.play(Write(prob_text, run_time = 1)) - self.wait() - - self.prob_texts.add(prob_text) - - def turn_one_filter_pair_into_another(self, i1, i2): - mover = self.filter_pairs[i1].copy() - mover.set_stroke(width = 0) - mover.set_fill(opacity = 0) - target = self.filter_pairs[i2] - self.play(ReplacementTransform(mover, target)) - - def get_probability_text(self, pol_filter = None): - if pol_filter is None: - pol_filter = self.second_filter - prob = self.get_prob(pol_filter) - prob_mob = TextMobject(str(int(prob*100)) + "\\%", " pass") - prob_mob.scale(0.7) - prob_mob.rotate(np.pi/2, RIGHT) - prob_mob.next_to(pol_filter.arrow_label, RIGHT) - prob_mob.set_color( - list(Color(RED).range_to(GREEN, 11))[int(prob*10)] - ) - return prob_mob - - - ##### - - def get_lines( - self, filter1 = None, filter2 = None, - ratio = 1.0, - remove_from_bottom = False, - ): - # n = int(ratio*self.n_lines) - n = self.n_lines - start, end = [ - (f.point_from_proportion(0.75) if f is not None else None) - for f in (filter1, filter2) - ] - if start is None: - start = end + self.line_start_length*LEFT - if end is None: - end = start + self.line_end_length*RIGHT - nudge = (float(self.lines_depth)/self.n_lines)*OUT - lines = VGroup(*[ - Line(start, end).shift(z*nudge) - for z in range(n) - ]) - lines.set_stroke(YELLOW, 2) - lines.move_to(start, IN+LEFT) - lines.shift(self.lines_shift_vect) - n_to_block = int((1-ratio)*self.n_lines) - if remove_from_bottom: - to_block = lines[:n_to_block] - else: - to_block = lines[len(lines)-n_to_block:] - VGroup(*to_block).set_stroke(width = 0) - return lines - -class ShowVariousFilterPairsFrom0To45(ShowVariousFilterPairs): - CONFIG = { - "angles" : [0, np.pi/8, np.pi/4] - } - def construct(self): - ShowVariousFilterPairs.construct(self) - self.mention_probabilities() - - def mention_probabilities(self): - rects = VGroup() - for prob_text in self.prob_texts: - prob_text.rotate(np.pi/2, LEFT) - rect = SurroundingRectangle(prob_text, color = BLUE) - VGroup(prob_text, rect).rotate(np.pi/2, RIGHT) - rects.add(rect) - - cosines = VGroup(*[ - TexMobject("\\cos^2(%s^\\circ)"%str(x)) - for x in (45, 22.5) - ]) - cosines.scale(0.8) - # cosines.set_color(BLUE) - cosines.rotate(np.pi/2, RIGHT) - for cos, rect in zip(cosines, rects[1:]): - cos.next_to(rect, OUT, SMALL_BUFF) - - self.play(LaggedStartMap(ShowCreation, rects)) - self.wait() - self.play(*list(map(Write, cosines)), run_time = 2) - self.wait() - -class ForgetPreviousActions(ShowVariousFilterPairs): - CONFIG = { - "filter_x_coordinates" : [-6, -2, 2, 2, 2], - "pol_filter_configs" : [ - {"filter_angle" : angle} - for angle in (np.pi/4, 0, np.pi/4, np.pi/3, np.pi/6) - ], - "start_theta" : -0.6*np.pi, - "EMWave_config" : { - "wave_number" : 0, - "start_point" : FRAME_X_RADIUS*LEFT + DOWN, - }, - "apply_filter" : False, - } - def setup(self): - PhotonsThroughPerpendicularFilters.setup(self) - self.remove(self.axes) - - VGroup(*self.pol_filters).shift(IN) - - for pf in self.pol_filters: - pf.arrow_label.rotate_in_place(np.pi/2, OUT) - pf.arrow_label.next_to(pf.arrow, RIGHT) - - self.stop_ambient_camera_rotation() - - def construct(self): - front_filter = self.pol_filters[0] - first_filter = self.pol_filters[1] - possible_second_filters = self.pol_filters[2:] - for pf in possible_second_filters: - prob_text = self.get_probability_text(pf) - prob_text.scale(1.3, about_point = prob_text.get_left()) - pf.add(prob_text) - - second_filter = possible_second_filters[0].copy() - self.second_filter = second_filter - self.pol_filters = VGroup( - first_filter, second_filter - ) - self.remove(front_filter) - self.remove(*possible_second_filters) - self.add(second_filter) - self.apply_filter = True - self.update_mobjects() - self.photons = self.get_photons()[1:] - - group = VGroup(*self.pol_filters) - rect1 = SurroundingRectangle(group) - rect1.rotate_in_place(np.pi/2, RIGHT) - rect1.rescale_to_fit(group.get_depth()+MED_SMALL_BUFF, 2, True) - rect1.stretch_in_place(1.2, 0) - prob_words = TextMobject( - "Probabilities depend only\\\\", - "on this angle difference" - ) - prob_words.add_background_rectangle() - prob_words.rotate(np.pi/2, RIGHT) - prob_words.next_to(rect1, OUT) - - self.add(rect1) - self.play(FadeIn(prob_words)) - for index in 1, 2: - self.shoot_photon() - self.play(Transform( - second_filter, possible_second_filters[index] - )) - - rect2 = SurroundingRectangle(front_filter, color = RED) - rect2.rotate_in_place(np.pi/2, RIGHT) - rect2.rescale_to_fit(front_filter.get_depth()+MED_SMALL_BUFF, 2, True) - rect2.stretch_in_place(1.5, 0) - ignore_words = TextMobject("Photon \\\\", "``forgets'' this") - ignore_words.add_background_rectangle() - ignore_words.rotate(np.pi/2, RIGHT) - ignore_words.next_to(rect2, OUT) - - self.play( - ShowCreation(rect2), - Write(ignore_words, run_time = 1), - FadeIn(front_filter), - run_time = 1.5, - ) - self.shoot_photon() - for index in 0, 1, 2: - self.play(Transform( - second_filter, possible_second_filters[index] - )) - self.shoot_photon() - - def shoot_photon(self): - photon = random.choice(self.photons) - added_anims = [] - if photon.filter_distance == FRAME_X_RADIUS + 2: - added_anims.append( - ApplyMethod( - self.second_filter.set_color, RED, - rate_func = squish_rate_func(there_and_back, 0.5, 0.7) - ) - ) - self.play(photon, *added_anims, run_time = 1.5) - -class IntroduceLabeledFilters(ShowVariousFilterPairs): - CONFIG = { - "filter_x_coordinates" : [-5, -2, 1], - "pol_filter_configs" : [ - {"filter_angle" : angle} - for angle in [0, np.pi/8, np.pi/4] - ], - "start_phi" : 0.9*np.pi/2, - "start_theta" : -0.85*np.pi, - "target_theta" : -0.65*np.pi, - "lines_depth" : 1.7, - "lines_shift_vect" : MED_SMALL_BUFF*OUT, - "line_start_length" : 3, - "line_end_length" : 9, - "ambient_rotation_rate" : 0.005, - } - def setup(self): - PhotonsThroughPerpendicularFilters.setup(self) - self.remove(self.axes) - - def construct(self): - self.add_letters_to_labels() - self.introduce_filters() - self.reposition_camera() - self.separate_cases() - self.show_bottom_lines() - self.comment_on_half_blocked_by_C() - self.show_top_lines() - self.comment_on_those_blocked_by_B() - self.show_sum() - - def add_letters_to_labels(self): - for char, pf, color in zip("ABC", self.pol_filters, [RED, GREEN, BLUE]): - label = TextMobject(char) - label.scale(0.9) - label.add_background_rectangle() - label.set_color(color) - label.rotate(np.pi/2, RIGHT) - label.rotate(np.pi/2, IN) - label.next_to(pf.arrow_label, UP) - pf.arrow_label.add(label) - pf.arrow_label.next_to(pf.arrow, OUT, SMALL_BUFF) - self.remove(*self.pol_filters) - - def introduce_filters(self): - self.A_filter, self.B_filter, self.C_filter = self.pol_filters - for pf in self.pol_filters: - pf.save_state() - pf.shift(4*OUT) - pf.fade(1) - self.play(pf.restore) - self.wait() - - def reposition_camera(self): - self.move_camera( - theta = self.target_theta, - added_anims = list(it.chain(*[ - [ - pf.arrow_label.rotate, np.pi/2, OUT, - pf.arrow_label.next_to, pf.arrow, RIGHT - ] - for pf in self.pol_filters - ])) - ) - - def separate_cases(self): - self.lower_pol_filters = VGroup( - self.A_filter.deepcopy(), - self.C_filter.deepcopy(), - ) - self.lower_pol_filters.save_state() - self.lower_pol_filters.fade(1) - - self.play( - self.lower_pol_filters.restore, - self.lower_pol_filters.shift, 3*IN, - self.pol_filters.shift, 1.5*OUT, - ) - self.wait() - - def show_bottom_lines(self): - A, C = self.lower_pol_filters - lines_to_A = self.get_lines(None, A) - vect = lines_to_A[1].get_center() - lines_to_A[0].get_center() - lines_to_A.add(*lines_to_A.copy().shift(vect/2)) - lines_to_C = self.get_lines(A, C) - lines_from_C = self.get_lines(C, ratio = 0.5) - kwargs = { - "rate_func" : None, - "lag_ratio" : 0, - "run_time" : 1./3, - } - self.play( - ShowCreation(lines_to_A), - **kwargs - ) - self.play( - ShowCreation(lines_to_C), - Animation(VGroup(A, lines_to_A)), - **kwargs - ) - kwargs["run_time"] = 3*kwargs["run_time"] - self.play( - ShowCreation(lines_from_C), - Animation(VGroup(C, lines_to_C, A, lines_to_A)), - **kwargs - ) - - line_group = VGroup(lines_from_C, C, lines_to_C, A, lines_to_A) - self.remove(*line_group) - self.add_foreground_mobject(line_group) - - self.bottom_lines_group = line_group - - def comment_on_half_blocked_by_C(self): - arrow = Arrow( - ORIGIN, 3.5*RIGHT, - path_arc = -0.9*np.pi, - color = BLUE, - stroke_width = 5, - ) - words = TextMobject("50\\% blocked") - words.set_color(BLUE) - words.next_to(arrow, RIGHT, buff = 0) - group = VGroup(arrow, words) - group.rotate(np.pi/2, RIGHT) - group.shift(1.7*IN + 0.5*RIGHT) - - self.play( - Write(words, run_time = 2), - ShowCreation(arrow) - ) - self.wait(2) - - self.blocked_at_C_words = words - self.blocked_at_C_label_group = VGroup(arrow, words) - - def show_top_lines(self): - A, B, C = self.pol_filters - lines_to_A = self.get_lines(None, A) - vect = lines_to_A[1].get_center() - lines_to_A[0].get_center() - lines_to_A.add(*lines_to_A.copy().shift(vect/2)) - lines_to_B = self.get_lines(A, B) - lines_to_C = self.get_lines(B, C, ratio = 0.85, remove_from_bottom = True) - lines_from_C = self.get_lines(C, ratio = 0.85**2, remove_from_bottom = True) - - kwargs = { - "rate_func" : None, - "lag_ratio" : 0, - "run_time" : 1./5, - } - self.play( - ShowCreation(lines_to_A), - **kwargs - ) - self.play( - ShowCreation(lines_to_B), - Animation(VGroup(A, lines_to_A)), - **kwargs - ) - self.play( - ShowCreation(lines_to_C), - Animation(VGroup(B, lines_to_B, A, lines_to_A)), - **kwargs - ) - kwargs["run_time"] = 3*kwargs["run_time"] - self.play( - ShowCreation(lines_from_C), - Animation(VGroup(C, lines_to_C, B, lines_to_B, A, lines_to_A)), - **kwargs - ) - - line_group = VGroup( - lines_from_C, C, lines_to_C, B, lines_to_B, A, lines_to_A - ) - self.remove(*line_group) - self.add_foreground_mobject(line_group) - - self.top_lines_group = line_group - - def comment_on_those_blocked_by_B(self): - arrow0 = Arrow( - 2*LEFT, 0.5*(UP+RIGHT), - path_arc = 0.8*np.pi, - color = WHITE, - stroke_width = 5, - buff = 0 - ) - arrow1 = Arrow( - 2*LEFT, ORIGIN, - path_arc = 0.8*np.pi, - color = GREEN, - stroke_width = 5, - buff = 0 - ) - arrow2 = arrow1.copy() - arrow2.next_to(arrow1, RIGHT, buff = LARGE_BUFF) - words1 = TextMobject("15\\%", "blocked") - words1.set_color(GREEN) - words2 = words1.copy() - words1.next_to(arrow1, DOWN, buff = SMALL_BUFF) - words2.next_to(arrow2, DOWN, buff = SMALL_BUFF) - words2.shift(MED_LARGE_BUFF*RIGHT) - - words0 = TextMobject("85\\%", "pass") - words0.move_to(words1) - - group = VGroup(arrow0, arrow1, arrow2, words0, words1, words2) - group.rotate(np.pi/2, RIGHT) - group.shift(0.8*LEFT+1.5*OUT) - - self.play( - ShowCreation(arrow0), - Write(words0, run_time = 1) - ) - self.wait() - self.play( - ReplacementTransform(words0, words1), - ReplacementTransform(arrow0, arrow1), - ) - self.wait() - self.play( - ShowCreation(arrow2), - Write(words2) - ) - self.wait(2) - - self.fifteens = VGroup(words1, words2) - self.blocked_at_B_label_group = VGroup( - words1, words2, arrow1, arrow2 - ) - - def show_sum(self): - fifteen1, fifteen2 = self.fifteens - fifty = self.blocked_at_C_words - plus = TexMobject("+").rotate(np.pi/2, RIGHT) - plus.move_to(Line(fifteen1.get_right(), fifteen2.get_left())) - equals = TexMobject("=").rotate(np.pi/2, RIGHT) - equals.next_to(fifteen2, RIGHT, 2*SMALL_BUFF) - q_mark = TexMobject("?").rotate(np.pi/2, RIGHT) - q_mark.next_to(equals, OUT, SMALL_BUFF) - q_mark.set_color(RED) - randy = Randolph(mode = "confused").flip() - randy.scale(0.7) - randy.rotate(np.pi/2, RIGHT) - randy.move_to(fifty) - randy.shift(0.5*RIGHT) - randy.look_at(equals) - blinked = randy.copy() - blinked.rotate(np.pi/2, LEFT) - blinked.blink() - blinked.rotate(np.pi/2, RIGHT) - - self.play( - fifteen1.set_color, YELLOW, - Write(plus) - ) - self.play( - fifteen2.set_color, YELLOW, - Write(equals) - ) - self.play( - fifty.next_to, equals, RIGHT, 2*SMALL_BUFF, - Write(q_mark), - FadeIn(randy) - ) - self.play(Transform( - randy, blinked, - rate_func = squish_rate_func(there_and_back) - )) - self.wait(3) - -class IntroduceLabeledFiltersNoRotation(IntroduceLabeledFilters): - CONFIG = { - "ambient_rotation_rate" : 0, - "target_theta" : -0.55*np.pi, - } - -class RemoveBFromLabeledFilters(IntroduceLabeledFiltersNoRotation): - def construct(self): - self.force_skipping() - IntroduceLabeledFiltersNoRotation.construct(self) - self.revert_to_original_skipping_status() - - self.setup_start() - self.show_filter_B_removal() - self.fade_in_labels() - - def show_sum(self): - pass - - - def setup_start(self): - self.remove(self.blocked_at_C_label_group) - self.remove(self.blocked_at_B_label_group) - self.remove(self.bottom_lines_group) - self.top_lines_group.save_state() - self.top_lines_group.shift(2*IN) - - def show_filter_B_removal(self): - top_lines_group = self.top_lines_group - bottom_lines_group = self.bottom_lines_group - - - mover = top_lines_group.copy() - mover.save_state() - mover.fade(1) - - sl1, sC, sl2, sB, sl3, sA, sl4 = mover - tl1, tC, tl2, tA, tl3 = bottom_lines_group - - for line in tl2: - line.scale(0.5, about_point = line.get_end()) - - kwargs = { - "lag_ratio" : 0, - "rate_func" : None, - } - - self.play( - top_lines_group.shift, 2*OUT, - mover.restore, - mover.shift, 2.5*IN, - ) - self.wait() - self.play( - ApplyMethod(sB.shift, 4*IN, rate_func = running_start), - FadeOut(sl1), - Animation(sC), - FadeOut(sl2), - ) - self.play(ShowCreation(tl2, run_time = 0.25, **kwargs)) - self.play( - ShowCreation(tl1, run_time = 0.5, **kwargs), - Animation(sC), - Animation(tl2), - ) - self.wait() - - def fade_in_labels(self): - self.play(*list(map(FadeIn, [ - self.blocked_at_B_label_group, - self.blocked_at_C_label_group, - ]))) - self.wait() - -class NumbersSuggestHiddenVariablesAreImpossible(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "These numbers suggest\\\\", - "no hidden variables" - ) - self.change_student_modes("erm", "sassy", "confused") - self.wait(3) - -class VennDiagramProofByContradiction(Scene): - CONFIG = { - "circle_colors" : [RED, GREEN, BLUE] - } - def construct(self): - self.setup_venn_diagram_sections() - self.draw_venn_diagram() - self.show_100_photons() - self.show_one_photon_answering_questions() - self.put_all_photons_in_A() - self.separate_by_B() - self.separate_by_C() - self.show_two_relevant_subsets() - - def draw_venn_diagram(self, send_to_corner = True): - A, B, C = venn_diagram = VGroup(*[ - Circle( - radius = 3, - stroke_width = 3, - stroke_color = c, - fill_opacity = 0.2, - fill_color = c, - ).shift(vect) - for c, vect in zip( - self.circle_colors, - compass_directions(3, UP) - ) - ]) - self.A_to_B_vect = B.get_center() - A.get_center() - self.A_to_C_vect = C.get_center() - A.get_center() - - venn_diagram.center() - labels = VGroup() - alt_labels = VGroup() - props = [1./12, 0.5, 0] - angles = [0, np.pi/8, np.pi/4] - for circle, char, prop, angle in zip(venn_diagram, "ABC", props, angles): - label, alt_label = [ - TextMobject( - "%s \\\\"%start, - "through", char + "$\\! \\uparrow$" - ).set_color_by_tex(char, circle.get_color()) - for start in ("Would pass", "Pass") - ] - for mob in label, alt_label: - mob[-1][-1].rotate_in_place(-angle) - mob[-1][-1].shift(0.5*SMALL_BUFF*UP) - center = circle.get_center() - label.move_to(center) - label.generate_target() - point = circle.point_from_proportion(prop) - alt_label.scale(2) - for mob in label.target, alt_label: - mob.next_to(point, point-center, SMALL_BUFF) - - circle.label = label - circle.alt_label = alt_label - labels.add(label) - alt_labels.add(alt_label) - - last_circle = None - for circle in venn_diagram: - added_anims = [] - if last_circle: - added_anims.append(MoveToTarget(last_circle.label)) - self.play( - DrawBorderThenFill(circle, run_time = 2), - Write(circle.label, run_time = 2), - *added_anims - ) - last_circle = circle - self.play(MoveToTarget(last_circle.label)) - self.wait() - - if hasattr(self, "A_segments"): - A.add(self.A_segments) - - if send_to_corner: - group = VGroup(venn_diagram, labels) - target = VGroup(venn_diagram.copy(), alt_labels) - target.scale(0.25) - target.to_corner(UP+RIGHT) - self.play(Transform(group, target)) - self.remove(group) - for circle in venn_diagram: - circle.label = circle.alt_label - self.add(circle) - for circle in venn_diagram: - self.add(circle.label) - - self.venn_diagram = venn_diagram - - def show_100_photons(self): - photon = FunctionGraph( - lambda x : -np.cos(3*np.pi*x)*np.exp(-x*x), - x_min = -2, - x_max = 2, - color = YELLOW, - stroke_width = 2, - ) - photon.shift(LEFT + 2*UP) - eyes = Eyes(photon) - photon.eyes = eyes - - hundred, photon_word, s = words = TextMobject( - "100 ", "Photon", "s", - arg_separator = "" - ) - words.next_to(eyes, UP) - - self.play( - ShowCreation(photon), - FadeIn(photon.eyes), - Write(photon_word, run_time = 1.5) - ) - photon.add(photon.eyes) - - #Split to hundred - photons = VGroup(*[photon.deepcopy() for x in range(100)]) - self.arrange_photons_in_circle(photons) - photons.set_height(6) - photons.next_to(words, DOWN) - photons.to_edge(LEFT) - - self.play( - Write(hundred), Write(s), - ReplacementTransform( - VGroup(photon), photons, - lag_ratio = 0.5 - ) - ) - - self.photons = photons - self.photon_words = words - - def show_one_photon_answering_questions(self): - photon = self.photons[89] - photon.save_state() - photon.generate_target() - - answers = TextMobject( - "Pass through A?", "Yes\\\\", - "Pass through B?", "No\\\\", - "Pass through C?", "No\\\\", - ) - answers.set_color_by_tex_to_color_map({ - "Yes" : GREEN, - "No" : RED, - }) - bubble = ThoughtBubble() - bubble.add_content(answers) - bubble.resize_to_content() - answers.shift(SMALL_BUFF*(RIGHT+UP)) - bubble_group = VGroup(bubble, answers) - bubble_group.scale(0.25) - bubble_group.next_to(photon, UP+RIGHT, buff = 0) - - group = VGroup(photon, bubble_group) - group.save_state() - bubble_group.set_fill(opacity = 0) - bubble_group.set_stroke(width = 0) - - self.play( - group.restore, - group.scale, 4, - group.to_corner, DOWN + RIGHT, - ) - self.play(photon.eyes.blink_anim()) - self.wait() - self.play( - FadeOut(bubble_group), - photon.restore, - ) - - def put_all_photons_in_A(self): - A, B, C = circles = self.venn_diagram[:3] - A_group, B_group, C_group = [ - VGroup(circle, circle.label) - for circle in circles - ] - B_group.save_state() - C_group.save_state() - - A.generate_target() - A.target.scale(4) - A.target.shift( - (FRAME_Y_RADIUS-MED_LARGE_BUFF)*UP - \ - A.target.get_top() - ) - A.label.generate_target() - A.label.target.scale(2) - A.label.target.next_to( - A.target.point_from_proportion(0.1), - UP+RIGHT, SMALL_BUFF - ) - - self.play( - B_group.fade, 1, - C_group.fade, 1, - MoveToTarget(A), - MoveToTarget(A.label), - FadeOut(self.photon_words), - self.photons.set_height, - 0.85*A.target.get_height(), - self.photons.space_out_submobjects, 0.8, - self.photons.move_to, A.target, - ) - self.wait() - - self.A_group = A_group - self.B_group = B_group - self.C_group = C_group - - def separate_by_B(self): - A_group = self.A_group - B_group = self.B_group - photons = self.photons - B = B_group[0] - - B.target, B.label.target = B_group.saved_state - B.target.scale(4) - B.target.move_to(A_group[0]) - B.target.shift(self.A_to_B_vect) - B.label.target.scale(2) - B.label.target.next_to( - B.target.point_from_proportion(0.55), - LEFT, SMALL_BUFF - ) - - B_center = B.target.get_center() - photons.sort( - lambda p : get_norm(p-B_center) - ) - in_B = VGroup(*photons[:85]) - out_of_B = VGroup(*photons[85:]) - out_of_B.sort(lambda p : np.dot(p, 2*UP+LEFT)) - - self.play( - MoveToTarget(B), - MoveToTarget(B.label), - in_B.shift, 0.5*DOWN+0.2*LEFT, - out_of_B.scale_in_place, 1./0.8, - out_of_B.shift, 0.15*(UP+RIGHT), - ) - - words1 = TextMobject("85 also \\\\", "pass ", "B") - words1.set_color_by_tex("B", GREEN) - words1.scale(0.8) - words1.next_to(A_group, LEFT, LARGE_BUFF).shift(UP) - arrow1 = Arrow( - words1.get_right(), - in_B.get_corner(UP+LEFT) + MED_LARGE_BUFF*(DOWN+RIGHT) - ) - arrow1.set_color(GREEN) - - words2 = TextMobject("15 blocked \\\\", "by ", "B") - words2.set_color_by_tex("B", GREEN) - words2.scale(0.8) - words2.next_to(A_group, LEFT, MED_LARGE_BUFF, UP) - arrow2 = Arrow(words2.get_right(), out_of_B[-1]) - arrow2.set_color(RED) - - self.play( - Write(words1, run_time = 1), - ShowCreation(arrow1), - self.in_A_in_B.set_fill, GREEN, 0.5, - Animation(in_B), - ) - self.wait() - self.play( - ReplacementTransform(words1, words2), - ReplacementTransform(arrow1, arrow2), - self.in_A_in_B.set_fill, None, 0, - Animation(in_B), - self.in_A_out_B.set_fill, RED, 0.5, - Animation(out_of_B) - ) - self.wait() - self.play(ApplyMethod( - VGroup(self.in_A_out_B, out_of_B).shift, - MED_LARGE_BUFF*UP, - rate_func = wiggle, - run_time = 1.5, - )) - self.wait(0.5) - - self.in_B = in_B - self.out_of_B = out_of_B - self.out_of_B_words = words2 - self.out_of_B_arrow = arrow2 - - def separate_by_C(self): - A_group = self.A_group - B_group = self.B_group - C_group = self.C_group - in_B = self.in_B - A, B, C = self.venn_diagram - - C.target, C.label.target = C_group.saved_state - C.target.scale(4) - C.target.move_to(A) - C.target.shift(self.A_to_C_vect) - C_center = C.target.get_center() - C.label.target.scale(2) - C.label.target.next_to( - C.target.point_from_proportion(0), - DOWN+RIGHT, buff = SMALL_BUFF - ) - - in_B.sort( - lambda p : get_norm(p - C_center) - ) - in_C = VGroup(*in_B[:-11]) - out_of_C = VGroup(*in_B[-11:]) - in_C_out_B = VGroup(*self.out_of_B[:6]) - - words = TextMobject( - "$15\\%$", "passing", "B \\\\", - "get blocked by ", "C", - ) - words.scale(0.8) - words.set_color_by_tex_to_color_map({ - "B" : GREEN, - "C" : BLUE, - }) - words.next_to(self.out_of_B_words, DOWN, LARGE_BUFF) - words.to_edge(LEFT) - percent = words[0] - pound = TexMobject("\\#") - pound.move_to(percent, RIGHT) - less_than_15 = TexMobject("<15") - less_than_15.next_to(words, DOWN) - - - arrow = Arrow(words.get_right(), out_of_C) - arrow.set_color(GREEN) - - C_copy = C.copy() - C_copy.set_fill(BLACK, opacity = 1) - - self.play( - self.in_A_in_B.set_fill, GREEN, 0.5, - rate_func = there_and_back, - ) - self.play( - MoveToTarget(C), - MoveToTarget(C.label), - in_C.shift, 0.2*DOWN+0.15*RIGHT, - out_of_C.shift, SMALL_BUFF*(UP+LEFT), - in_C_out_B.shift, 0.3*DOWN - ) - self.play( - self.in_A_in_B_out_C.set_fill, GREEN, 0.5, - Write(words, run_time = 1), - ShowCreation(arrow), - Animation(out_of_C), - ) - self.play(ApplyMethod( - VGroup(self.in_A_in_B_out_C, out_of_C).shift, - MED_LARGE_BUFF*UP, - rate_func = wiggle - )) - self.wait() - C.save_state() - self.play(C.set_fill, BLACK, 1) - self.wait() - self.play(C.restore) - self.wait(2) - self.play(Transform(percent, pound)) - self.play(Write(less_than_15, run_time = 1)) - self.wait() - - self.in_C = in_C - self.out_of_C = out_of_C - words.add(less_than_15) - self.out_of_C_words = words - self.out_of_C_arrow = arrow - - def show_two_relevant_subsets(self): - A, B, C = self.venn_diagram - - all_out_of_C = VGroup(*it.chain( - self.out_of_B[6:], - self.out_of_C, - )) - everything = VGroup(*self.get_top_level_mobjects()) - photon_groups = [all_out_of_C, self.out_of_C, self.out_of_B] - regions = [self.in_A_out_C, self.in_A_in_B_out_C, self.in_A_out_B] - self.play(*[ - ApplyMethod( - m.scale, 0.7, - method_kwargs = { - "about_point" : FRAME_Y_RADIUS*DOWN - } - ) - for m in everything - ]) - - terms = VGroup( - TexMobject("N(", "A", "\\checkmark", ",", "C", ")", "\\le"), - TexMobject( - "N(", "A", "\\checkmark", ",", - "B", "\\checkmark", ",", "C", ")" - ), - TexMobject("+\\, N(", "A", "\\checkmark", ",", "B", ")"), - ) - terms.arrange(RIGHT) - terms.to_edge(UP) - for term, index, group in zip(terms, [-3, -2, -2], photon_groups): - term.set_color_by_tex("checkmark", "#00ff00") - cross = Cross(term[index]) - cross.set_color("#ff0000") - cross.set_stroke(width = 8) - term[index].add(cross) - - less_than = terms[0][-1] - terms[0].remove(less_than) - plus = terms[2][0][0] - terms[2][0].remove(plus) - rects = list(map(SurroundingRectangle, terms)) - terms[2][0].add_to_back(plus) - last_rects = VGroup(*rects[1:]) - - should_be_50 = TextMobject("Should be 50 \\\\", "...somehow") - should_be_50.scale(0.8) - should_be_50.next_to(rects[0], DOWN) - - lt_fifteen = VGroup(self.out_of_C_words[-1]).copy() - something_lt_15 = TextMobject("(Something", "$<15$", ")") - something_lt_15.scale(0.8) - something_lt_15.next_to(rects[1], DOWN) - lt_fifteen.target = something_lt_15 - - fifteen = VGroup(*self.out_of_B_words[0][:2]).copy() - fifteen.generate_target() - fifteen.target.scale(1.5) - fifteen.target.next_to(rects[2], DOWN) - - nums = [should_be_50, lt_fifteen, fifteen] - - cross = Cross(less_than) - cross.set_color("#ff0000") - cross.set_stroke(width = 8) - - tweaser_group = VGroup( - self.in_A_in_B_out_C.copy(), - self.in_A_out_B.copy(), - ) - tweaser_group.set_fill(TEAL, 1) - tweaser_group.set_stroke(TEAL, 5) - - #Fade out B circle - faders = VGroup( - B, B.label, - self.out_of_B_words, self.out_of_C_words, - self.out_of_B_arrow, self.out_of_C_arrow, - *regions[1:] - ) - faders.save_state() - - self.play(faders.fade, 1) - self.play(Write(terms[0]), run_time = 1) - self.wait() - self.photon_thinks_in_A_out_C() - regions[0].set_stroke(YELLOW, width = 8) - regions[0].set_fill(YELLOW, opacity = 0.25) - self.play( - VGroup(regions[0], all_out_of_C).shift, 0.5*UP, - run_time = 1.5, - rate_func = wiggle, - ) - self.wait(2) - - #Photons jump - self.photons.save_state() - self.play(Write(should_be_50[0], run_time = 1)) - self.photons_jump_to_A_not_C_region() - self.wait() - self.play( - faders.restore, - self.photons.restore, - ) - - self.play( - faders.restore, - regions[0].set_fill, None, 0, - Animation(self.photons) - ) - - #Funny business - everything_copy = everything.copy().scale(1./3) - braces = VGroup( - Brace(everything_copy, LEFT), - Brace(everything_copy, RIGHT), - ).scale(3) - funny_business = TextMobject("Funny business") - funny_business.scale(1.5) - funny_business.to_edge(UP) - funny_business.shift(RIGHT) - - self.play( - FadeIn(funny_business), - *list(map(Write, braces)), - run_time = 1 - ) - self.wait() - self.play( - FadeIn(less_than), - *list(map(FadeOut, [funny_business, braces])) - ) - - for term, group, region, num in zip(terms, photon_groups, regions, nums)[1:]: - group.set_stroke(WHITE) - self.play(Write(term, run_time = 1)) - self.wait() - self.play( - ApplyMethod( - VGroup(region, group).shift, 0.5*UP, - rate_func = wiggle, - run_time = 1.5, - ), - ) - self.play(MoveToTarget(num)) - self.wait() - self.wait() - - self.play(ShowCreation(rects[0])) - self.play( - VGroup(regions[0], all_out_of_C).shift, 0.5*UP, - run_time = 1.5, - rate_func = wiggle, - ) - self.wait() - self.play(Transform(rects[0], last_rects)) - self.in_A_out_B.save_state() - self.in_A_in_B_out_C.save_state() - self.play( - self.in_A_out_B.set_fill, YELLOW, 0.5, - self.in_A_in_B_out_C.set_fill, YELLOW, 0.5, - Animation(self.photons) - ) - self.wait() - self.play( - FadeOut(rects[0]), - self.in_A_out_B.restore, - self.in_A_in_B_out_C.restore, - Animation(self.in_A_out_C), - Animation(self.photons) - ) - self.wait() - self.play( - FadeIn(should_be_50[1]), - ShowCreation(cross) - ) - - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - contradiction = TextMobject("Contradiction!") - contradiction.next_to(morty, UP, aligned_edge = RIGHT) - contradiction.set_color(RED) - - self.play(FadeIn(morty)) - self.play( - morty.change, "confused", should_be_50, - Write(contradiction, run_time = 1) - ) - self.play(Blink(morty)) - self.wait() - - def photons_jump_to_A_not_C_region(self): - in_C = self.in_C - in_C.sort(lambda p : np.dot(p, DOWN+RIGHT)) - movers = VGroup(*self.in_C[:30]) - for mover in movers: - mover.generate_target() - mover.target.shift(1.2*UP + 0.6*LEFT) - mover.target.set_stroke(WHITE) - self.play(LaggedStartMap( - MoveToTarget, movers, - path_arc = np.pi, - lag_ratio = 0.3 - )) - - def photon_thinks_in_A_out_C(self): - photon = self.photons[-1] - photon.save_state() - photon.generate_target() - photon.target.scale(4) - photon.target.center().to_edge(LEFT).shift(DOWN) - bubble = ThoughtBubble() - content = TexMobject("A", "\\checkmark", ",", "C") - content.set_color_by_tex("checkmark", "#00ff00") - cross = Cross(content[-1]) - cross.set_color("#ff0000") - content.add(cross) - bubble.add_content(content) - bubble.resize_to_content() - bubble.add(bubble.content) - bubble.pin_to(photon.target).shift(SMALL_BUFF*RIGHT) - bubble.save_state() - bubble.scale(0.25) - bubble.move_to(photon.get_corner(UP+RIGHT), DOWN+LEFT) - bubble.fade() - - self.play( - MoveToTarget(photon), - bubble.restore, - ) - self.play(photon.eyes.blink_anim()) - self.play( - photon.restore, - FadeOut(bubble) - ) - - - ####### - - def setup_venn_diagram_sections(self): - in_A_out_B, in_A_in_B_out_C, in_A_out_C, in_A_in_B = segments = VGroup(*[ - SVGMobject( - file_name = "VennDiagram_" + s, - stroke_width = 0, - fill_opacity = 0.5, - fill_color = YELLOW, - ) - for s in ("in_A_out_B", "in_A_in_B_out_C", "in_A_out_C", "in_A_in_B") - ]) - - in_A_out_B.scale(2.59) - in_A_out_B.move_to(3.74*UP + 2.97*RIGHT, UP+RIGHT) - - in_A_in_B_out_C.scale(1.84) - in_A_in_B_out_C.move_to(2.23*UP, UP+RIGHT) - - in_A_out_C.scale(2.56) - in_A_out_C.move_to(3*LEFT + (3.69)*UP, UP+LEFT) - - in_A_in_B.scale(2.24) - in_A_in_B.move_to(2.23*UP + 3*LEFT, UP+LEFT) - - segments.set_fill(BLACK, opacity = 0) - - self.in_A_out_B = in_A_out_B - self.in_A_in_B_out_C = in_A_in_B_out_C - self.in_A_out_C = in_A_out_C - self.in_A_in_B = in_A_in_B - self.A_segments = segments - - def arrange_photons_in_circle(self, photons): - R = np.sqrt(len(photons) / np.pi) - pairs = [] - rejected = [] - for x, y in it.product(*[list(range(-int(R)-1, int(R)+2))]*2): - if x**2 + y**2 < R**2: - pairs.append((x, y)) - else: - rejected.append((x, y)) - rejected.sort( - kay=lambda x, y: (x**2 + y**2) - ) - for i in range(len(photons) - len(pairs)): - pairs.append(rejected.pop()) - for photon, (x, y) in zip(photons, pairs): - photon.set_width(0.7) - photon.move_to(x*RIGHT + y*UP) - return photons - -class PonderingPiCreature(Scene): - def construct(self): - randy = Randolph() - randy.to_edge(DOWN).shift(3*LEFT) - self.play(randy.change, "pondering", UP+RIGHT) - self.play(Blink(randy)) - self.wait(2) - self.play(Blink(randy)) - self.wait(2) - -class ReEmphasizeVennDiagram(VennDiagramProofByContradiction): - def construct(self): - self.draw_venn_diagram(send_to_corner = False) - self.rescale_diagram() - self.setup_faded_circles() - self.shift_B_circle() - self.shift_C_circle() - self.show_A_not_C_region() - self.shorten_labels() - self.show_inequality_with_circle() - # self.emphasize_containment() - self.write_50_percent() - self.write_assumption() - self.adjust_circles() - - def rescale_diagram(self): - group = VGroup(self.venn_diagram, *[ - c.label for c in self.venn_diagram - ]) - self.play( - group.scale, 0.7, - group.to_edge, DOWN, MED_SMALL_BUFF, - ) - self.clear() - self.add_foreground_mobjects(*group) - - def setup_faded_circles(self): - self.circles = self.venn_diagram[:3] - self.black_circles = VGroup(*[ - circ.copy().set_stroke(width = 0).set_fill(BLACK, 1) - for circ in self.circles - ]) - self.filled_circles = VGroup(*[ - circ.copy().set_stroke(width = 0).set_fill(circ.get_color(), 1) - for circ in self.circles - ]) - - def shift_B_circle(self): - A, B, C = self.circles - A0, B0, C0 = self.black_circles - A1, B1, C1 = self.filled_circles - - words = TextMobject("Should be 15\\% \\\\ of circle ", "A") - words.scale(0.7) - words.set_color_by_tex("A", RED) - words.next_to(A, UP, LARGE_BUFF) - words.shift(RIGHT) - arrow = Arrow( - words.get_bottom(), - A.get_top() + MED_SMALL_BUFF*RIGHT, - color = RED - ) - - self.play(FadeIn(A1)) - self.play(FadeIn(B0)) - self.play( - FadeIn(words, lag_ratio = 0.5), - ShowCreation(arrow) - ) - self.wait() - vect = 0.6*(A.get_center() - B.get_center()) - self.play( - B0.shift, vect, - B.shift, vect, - B.label.shift, vect, - run_time = 2, - rate_func = running_start, - ) - B1.shift(vect) - self.wait() - - self.in_A_out_B_words = words - self.in_A_out_B_arrow = arrow - for mob in words, arrow: - mob.save_state() - - def shift_C_circle(self): - A, B, C = self.circles - A0, B0, C0 = self.black_circles - A1, B1, C1 = self.filled_circles - - words = TextMobject("Should be 15\\% \\\\ of circle ", "B") - words.scale(0.7) - words.set_color_by_tex("B", GREEN) - words.next_to(B, LEFT) - words.shift(2.5*UP) - arrow = Arrow( - words.get_bottom(), - B.point_from_proportion(0.4), - color = GREEN - ) - - self.play( - FadeOut(A1), - FadeOut(B0), - self.in_A_out_B_words.fade, 1, - self.in_A_out_B_arrow.fade, 1, - FadeIn(B1), - FadeIn(words, lag_ratio = 0.5), - ShowCreation(arrow) - ) - self.play(FadeIn(C0)) - self.wait(2) - vect = 0.5*(B.get_center() - C.get_center()) - self.play( - C0.shift, vect, - C.shift, vect, - C.label.shift, vect, - run_time = 2, - rate_func = running_start, - ) - C1.shift(vect) - self.wait() - - for mob in words, arrow: - mob.save_state() - self.in_B_out_C_words = words - self.in_B_out_C_arrow = arrow - - def show_A_not_C_region(self): - A, B, C = self.circles - A0, B0, C0 = self.black_circles - A1, B1, C1 = self.filled_circles - A1_yellow_copy = A1.copy().set_fill(YELLOW) - - self.play( - FadeOut(B1), - FadeOut(C0), - self.in_B_out_C_words.fade, 1, - self.in_B_out_C_arrow.fade, 1, - FadeIn(A1_yellow_copy) - ) - self.play(FadeIn(C0)) - self.wait() - self.A1_yellow_copy = A1_yellow_copy - - def shorten_labels(self): - A, B, C = self.circles - A0, B0, C0 = self.black_circles - A1, B1, C1 = self.filled_circles - for circle in A, B, C: - circle.pre_label = VGroup(*circle.label[:-1]) - circle.letter = circle.label[-1] - - self.play( - A.pre_label.fade, 1, - A.letter.scale, 2, - A.letter.move_to, A.pre_label, LEFT, - B.pre_label.fade, 1, - B.letter.scale, 2, B.letter.get_right(), - C.pre_label.fade, 1, - C.letter.scale, 2, - C.letter.move_to, C.pre_label, LEFT, - C.letter.shift, DOWN+0.5*LEFT, - ) - for circle in A, B, C: - circle.remove(circle.label) - self.remove(circle.label) - circle.add(circle.letter) - self.add(circle.letter) - - def show_inequality_with_circle(self): - A, B, C = self.circles - A0, B0, C0 = self.black_circles - A1, B1, C1 = self.filled_circles - A1_yellow_copy = self.A1_yellow_copy - - inequality = VGroup( - TexMobject("N(", "A", "\\checkmark", ",", "C", ")"), - TexMobject("N(", "B", "\\checkmark", ",", "C", ")"), - TexMobject("N(", "A", "\\checkmark", ",", "B", ")"), - ) - inequality.arrange(RIGHT) - for tex in inequality: - tex.set_color_by_tex("checkmark", "#00ff00") - if len(tex) > 1: - cross = Cross(tex[-2], color = "#ff0000") - cross.set_stroke(width = 8) - tex[-2].add(cross) - inequality.space_out_submobjects(2.1) - big_le = TexMobject("\\le").scale(2) - big_plus = TexMobject("+").scale(2) - big_le.move_to(2.75*LEFT) - big_plus.move_to(2.25*RIGHT) - - groups = VGroup(*[ - VGroup( - m2.copy(), m1.copy(), - VGroup(*self.circles).copy() - ) - for m1, m2 in [(C0, A1_yellow_copy), (C0, B1), (B0, A1)] - ]) - for group, vect in zip(groups[1:], [UP, 5*RIGHT+UP]): - group.scale_in_place(0.5) - group.shift(vect) - group.save_state() - group.shift(-vect[0]*RIGHT + 5*LEFT) - inequality.shift(2.25*DOWN + 0.25*LEFT) - - self.in_B_out_C_words.restore() - self.in_B_out_C_words.move_to(2*UP) - self.in_A_out_B_words.restore() - self.in_A_out_B_words.move_to(5*RIGHT+2*UP) - - self.clear() - self.play( - groups[0].scale_in_place, 0.5, - groups[0].shift, 5*LEFT + UP, - Write(inequality[0], run_time = 1), - FadeIn(big_le), - ) - self.wait() - self.play(FadeIn(groups[1])) - self.play( - groups[1].restore, - FadeIn(inequality[1]), - FadeIn(self.in_B_out_C_words), - FadeIn(big_plus), - ) - self.play(FadeIn(groups[2])) - self.play( - groups[2].restore, - FadeIn(inequality[2]), - FadeIn(self.in_A_out_B_words), - ) - self.wait(2) - - self.groups = groups - self.inequality = inequality - - def emphasize_containment(self): - groups = self.groups - c1, c2 = [VGroup(*group[:2]).copy() for group in groups[1:]] - foreground = VGroup(groups[0][-1], *groups[1:]) - - rect = SurroundingRectangle(groups[0]) - - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.play( - ApplyMethod( - c1.shift, 4*LEFT, - path_arc = -np.pi/2, - ), - Animation(foreground) - ) - self.play( - ApplyMethod( - c2.shift, 8*LEFT, - path_arc = -np.pi/2, - ), - Animation(c1), - Animation(foreground), - run_time = 1.5 - ) - self.play( - FadeOut(c2), - FadeOut(c1), - Animation(foreground), - run_time = 2 - ) - self.wait() - - def write_50_percent(self): - words = TextMobject( - "Should be 50\\% \\\\ of circle ", "A", - "...somehow" - ) - words.scale(0.7) - words.set_color_by_tex("A", RED) - words.move_to(5*LEFT + 2*UP) - - self.play(Write(words)) - self.wait() - - def write_assumption(self): - words = TextMobject("Assume circles have the same size$^*$") - words.scale(0.8) - words.to_edge(UP) - - footnote = TextMobject(""" - *If you prefer, you can avoid the need for that - assumption by swapping the roles of A and C here - and writing a second inequality for added constraint. - """) - footnote.scale(0.5) - footnote.to_corner(DOWN+RIGHT) - footnote.add(words[-1]) - words.remove(words[-1]) - - self.footnote = footnote - - self.play(FadeIn(words)) - - def adjust_circles(self): - groups = self.groups - A_group = VGroup( - groups[0][0], - groups[2][0], - groups[0][2][0], - groups[1][2][0], - groups[2][2][0], - ) - B_group = VGroup( - groups[1][0], - groups[2][1], - groups[0][2][1], - groups[1][2][1], - groups[2][2][1], - ) - C_group = VGroup( - groups[0][1], - groups[1][1], - groups[0][2][2], - groups[1][2][2], - groups[2][2][2], - ) - - def center_of_mass(mob): - return np.apply_along_axis(np.mean, 0, mob.points) - - movers = [A_group, B_group, C_group] - A_ref, B_ref, C_ref = [g[4] for g in movers] - B_center = center_of_mass(B_ref) - B_to_A = center_of_mass(A_ref) - B_center - B_to_C = center_of_mass(C_ref) - B_center - - A_freq = 1 - C_freq = -0.7 - - self.time = 0 - dt = 1 / self.camera.frame_rate - - def move_around(total_time): - self.time - t_range = list(range(int(total_time/dt))) - for x in ProgressDisplay(t_range): - self.time += dt - new_B_to_A = rotate_vector(B_to_A, self.time*A_freq) - new_B_to_C = rotate_vector(B_to_C, self.time*C_freq) - A_group.shift(B_center + new_B_to_A - center_of_mass(A_ref)) - C_group.shift(B_center + new_B_to_C - center_of_mass(C_ref)) - self.wait(dt) - - move_around(3) - self.add(self.footnote) - move_around(1) - self.remove(self.footnote) - move_around(15) - -class NoFirstMeasurementPreferenceBasedOnDirection(ShowVariousFilterPairs): - CONFIG = { - "filter_x_coordinates" : [0, 0, 0], - "pol_filter_configs" : [ - {"filter_angle" : angle} - for angle in (0, np.pi/8, np.pi/4) - ], - "lines_depth" : 1.2, - "lines_shift_vect" : SMALL_BUFF*OUT, - "n_lines" : 30, - } - def setup(self): - DirectionOfPolarization.setup(self) - self.remove(self.axes, self.em_wave) - zs = [2.5, 0, -2.5] - chars = "ABC" - colors = [RED, GREEN, BLUE] - for z, char, color, pf in zip(zs, chars, colors, self.pol_filters): - pf.scale_in_place(0.7) - pf.move_to(z*OUT) - label = TextMobject(char) - label.add_background_rectangle() - label.set_color(color) - label.scale(0.7) - label.rotate(np.pi/2, RIGHT) - label.rotate(-np.pi/2, OUT) - label.next_to(pf.arrow_label, UP, SMALL_BUFF) - pf.arrow_label.add(label) - - self.add_foreground_mobject(pf) - - def construct(self): - self.reposition_camera() - self.show_lines() - - def reposition_camera(self): - words = TextMobject("No statistical preference") - words.to_corner(UP+LEFT) - words.rotate(np.pi/2, RIGHT) - self.move_camera( - theta = -0.6*np.pi, - added_anims = list(it.chain(*[ - [ - pf.arrow_label.rotate, np.pi/2, OUT, - pf.arrow_label.next_to, pf.arrow, OUT+RIGHT, SMALL_BUFF - ] - for pf in self.pol_filters - ] + [[FadeIn(words)]])) - ) - - def show_lines(self): - all_pre_lines = VGroup() - all_post_lines = VGroup() - for pf in self.pol_filters: - pre_lines = self.get_lines(None, pf) - post_lines = self.get_lines(pf, None) - VGroup( - *random.sample(post_lines, self.n_lines/2) - ).set_stroke(BLACK, 0) - all_pre_lines.add(*pre_lines) - all_post_lines.add(*post_lines) - - kwargs = { - "rate_func" : None, - "lag_ratio" : 0 - } - self.play(ShowCreation(all_pre_lines, **kwargs)) - self.play( - ShowCreation(all_post_lines, **kwargs), - Animation(self.pol_filters), - Animation(all_pre_lines), - ) - self.add_foreground_mobject(all_pre_lines) - self.wait(7) diff --git a/from_3b1b/old/borsuk.py b/from_3b1b/old/borsuk.py deleted file mode 100644 index 54495858..00000000 --- a/from_3b1b/old/borsuk.py +++ /dev/null @@ -1,2656 +0,0 @@ -from manimlib.imports import * -from functools import reduce - -class Jewel(VMobject): - CONFIG = { - "color" : WHITE, - "fill_opacity" : 0.75, - "stroke_width" : 0, - "height" : 0.5, - "num_equator_points" : 5, - "sun_vect" : OUT+LEFT+UP, - } - def init_points(self): - for vect in OUT, IN: - compass_vects = list(compass_directions(self.num_equator_points)) - if vect is IN: - compass_vects.reverse() - for vect_pair in adjacent_pairs(compass_vects): - self.add(Polygon(vect, *vect_pair)) - self.set_height(self.height) - self.rotate(-np.pi/2-np.pi/24, RIGHT) - self.rotate(-np.pi/12, UP) - self.submobjects.sort( - key=lambda m: -m1.get_center()[2] - ) - return self - -class Necklace(VMobject): - CONFIG = { - "width" : FRAME_WIDTH - 1, - "jewel_buff" : MED_SMALL_BUFF, - "chain_color" : GREY, - "default_colors" : [(4, BLUE), (6, WHITE), (4, GREEN)] - } - def __init__(self, *colors, **kwargs): - digest_config(self, kwargs, locals()) - if len(colors) == 0: - self.colors = self.get_default_colors() - VMobject.__init__(self, **kwargs) - - def get_default_colors(self): - result = list(it.chain(*[ - num*[color] - for num, color in self.default_colors - ])) - random.shuffle(result) - return result - - def init_points(self): - jewels = VGroup(*[ - Jewel(color = color) - for color in self.colors - ]) - jewels.arrange(buff = self.jewel_buff) - jewels.set_width(self.width) - jewels.center() - j_to_j_dist = (jewels[1].get_center()-jewels[0].get_center())[0] - - chain = Line( - jewels[0].get_center() + j_to_j_dist*LEFT/2, - jewels[-1].get_center() + j_to_j_dist*RIGHT/2, - color = self.chain_color, - ) - self.add(chain, *jewels) - self.chain = chain - self.jewels = jewels - -################ - -class FromPreviousTopologyVideo(Scene): - def construct(self): - rect = Rectangle(height = 9, width = 16) - rect.set_height(FRAME_HEIGHT-2) - title = TextMobject("From original ``Who cares about topology'' video") - title.to_edge(UP) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - -class CheckOutMathologer(PiCreatureScene): - CONFIG = { - "logo_height" : 1.5, - "screen_height" : 5, - "channel_name" : "Mathologer", - "logo_file" : "mathologer_logo", - "logo_color" : None, - } - def construct(self): - logo = self.get_logo() - name = TextMobject(self.channel_name) - name.next_to(logo, RIGHT) - - rect = Rectangle(height = 9, width = 16) - rect.set_height(self.screen_height) - rect.next_to(logo, DOWN) - rect.to_edge(LEFT) - - self.play( - self.get_logo_intro_animation(logo), - self.pi_creature.change_mode, "hooray", - ) - self.play( - ShowCreation(rect), - Write(name) - ) - self.wait(2) - self.change_mode("happy") - self.wait(2) - - def get_logo(self): - logo = ImageMobject(self.logo_file) - logo.set_height(self.logo_height) - logo.to_corner(UP+LEFT) - if self.logo_color is not None: - logo.set_color(self.logo_color) - logo.stroke_width = 1 - return logo - - def get_logo_intro_animation(self, logo): - logo.save_state() - logo.shift(DOWN) - logo.set_color(BLACK) - return ApplyMethod(logo.restore) - -class IntroduceStolenNecklaceProblem(ThreeDScene): - CONFIG = { - "jewel_colors" : [BLUE, GREEN, WHITE, RED], - "num_per_jewel" : [8, 10, 4, 6], - "num_shuffles" : 1, - "necklace_center" : UP, - "random_seed" : 2, - "forced_binary_choices" : (0, 1, 0, 1, 0), - } - def construct(self): - random.seed(self.random_seed) - self.add_thieves() - self.write_title() - self.introduce_necklace() - self.divvy_by_cutting_all() - self.divvy_with_n_cuts() - self.shuffle_jewels(self.necklace.jewels) - self.divvy_with_n_cuts() - - def add_thieves(self): - thieves = VGroup( - Randolph(), - Mortimer() - ) - thieves.arrange(RIGHT, buff = 4*LARGE_BUFF) - thieves.to_edge(DOWN) - thieves[0].make_eye_contact(thieves[1]) - - self.add(thieves) - self.thieves = thieves - - def write_title(self): - title = TextMobject("Stolen necklace problem") - title.to_edge(UP) - self.play( - Write(title), - *[ - ApplyMethod(pi.look_at, title) - for pi in self.thieves - ] - ) - self.title = title - - def introduce_necklace(self): - necklace = self.get_necklace() - jewels = necklace.jewels - jewel_types = self.get_jewels_organized_by_type(jewels) - - enumeration_labels = VGroup() - for jewel_type in jewel_types: - num_mob = TexMobject(str(len(jewel_type))) - jewel_copy = jewel_type[0].copy().scale(2) - jewel_copy.next_to(num_mob) - label = VGroup(num_mob, jewel_copy) - enumeration_labels.add(label) - enumeration_labels.arrange(RIGHT, buff = LARGE_BUFF) - enumeration_labels.to_edge(UP) - - self.play( - FadeIn( - necklace, - lag_ratio = 0.5, - run_time = 3 - ), - *it.chain(*[ - [pi.change_mode, "conniving", pi.look_at, necklace] - for pi in self.thieves - ]) - ) - self.play(*[ - ApplyMethod( - jewel.rotate_in_place, np.pi/6, UP, - rate_func = there_and_back - ) - for jewel in jewels - ]) - self.play(Blink(self.thieves[0])) - for jewel_type in jewel_types: - self.play( - jewel_type.shift, 0.2*UP, - rate_func = wiggle - ) - self.wait() - for x in range(self.num_shuffles): - self.shuffle_jewels(jewels) - self.play(FadeOut(self.title)) - for jewel_type, label in zip(jewel_types, enumeration_labels): - jewel_type.submobjects.sort( - key=lambda m: m1.get - ) - jewel_type.save_state() - jewel_type.generate_target() - jewel_type.target.arrange() - jewel_type.target.scale(2) - jewel_type.target.move_to(2*UP) - self.play( - MoveToTarget(jewel_type), - Write(label) - ) - self.play(jewel_type.restore) - self.play(Blink(self.thieves[1])) - - self.enumeration_labels = enumeration_labels - self.jewel_types = jewel_types - - def divvy_by_cutting_all(self): - enumeration_labels = self.enumeration_labels - necklace = self.necklace - jewel_types = self.jewel_types - thieves = self.thieves - - both_half_labels = VGroup() - for thief, vect in zip(self.thieves, [LEFT, RIGHT]): - half_labels = VGroup() - for label in enumeration_labels: - tex, jewel = label - num = int(tex.get_tex_string()) - half_label = VGroup( - TexMobject(str(num/2)), - jewel.copy() - ) - half_label.arrange() - half_labels.add(half_label) - half_labels.arrange(DOWN) - half_labels.set_height(thief.get_height()) - half_labels.next_to( - thief, vect, - buff = MED_LARGE_BUFF, - aligned_edge = DOWN - ) - both_half_labels.add(half_labels) - - for half_labels in both_half_labels: - self.play(ReplacementTransform( - enumeration_labels.copy(), - half_labels - )) - self.play(*[ApplyMethod(pi.change_mode, "pondering") for pi in thieves]) - self.wait() - - for type_index, jewel_type in enumerate(jewel_types): - jewel_type.save_state() - jewel_type_copy = jewel_type.copy() - n_jewels = len(jewel_type) - halves = [ - VGroup(*jewel_type_copy[:n_jewels/2]), - VGroup(*jewel_type_copy[n_jewels/2:]), - ] - for half, thief, vect in zip(halves, thieves, [RIGHT, LEFT]): - half.arrange(DOWN) - half.next_to( - thief, vect, - buff = SMALL_BUFF + type_index*half.get_width(), - aligned_edge = DOWN - ) - self.play( - Transform(jewel_type, jewel_type_copy), - *[ - ApplyMethod(thief.look_at, jewel_type_copy) - for thief in thieves - ] - ) - self.play(*it.chain(*[ - [thief.change_mode, "happy", thief.look_at, necklace] - for thief in thieves - ])) - self.wait() - self.play(*[ - jewel_type.restore - for jewel_type in jewel_types - ]) - self.play(*it.chain(*[ - [thief.change_mode, "confused", thief.look_at, necklace] - for thief in thieves - ])) - - def divvy_with_n_cuts( - self, - with_thieves = True, - highlight_groups = True, - show_matching_after_divvying = True, - ): - necklace = self.necklace - jewel_types = self.jewel_types - jewels = sorted( - necklace.jewels, - lambda m1, m2 : cmp(m1.get_center()[0], m2.get_center()[0]) - ) - slice_indices, binary_choices = self.find_slice_indices(jewels, jewel_types) - subgroups = [ - VGroup(*jewels[i1:i2]) - for i1, i2 in zip(slice_indices, slice_indices[1:]) - ] - buff = (jewels[1].get_left()[0]-jewels[0].get_right()[0])/2 - v_lines = VGroup(*[ - DashedLine(UP, DOWN).next_to(group, RIGHT, buff = buff) - for group in subgroups[:-1] - ]) - strand_groups = [VGroup(), VGroup()] - for group, choice in zip(subgroups, binary_choices): - strand = Line( - group[0].get_center(), group[-1].get_center(), - color = necklace.chain.get_color() - ) - strand.add(*group) - strand_groups[choice].add(strand) - self.add(strand) - - self.play(ShowCreation(v_lines)) - self.play( - FadeOut(necklace.chain), - *it.chain(*[ - list(map(Animation, group)) - for group in strand_groups - ]) - ) - for group in strand_groups: - group.save_state() - self.play( - strand_groups[0].shift, UP/2., - strand_groups[1].shift, DOWN/2., - ) - if with_thieves: - self.play(*it.chain(*[ - [thief.change_mode, "happy", thief.look_at, self.necklace] - for thief in self.thieves - ])) - self.play(Blink(self.thieves[1])) - else: - self.wait() - - if highlight_groups: - for group in strand_groups: - box = Rectangle( - width = group.get_width()+2*SMALL_BUFF, - height = group.get_height()+2*SMALL_BUFF, - stroke_width = 0, - fill_color = YELLOW, - fill_opacity = 0.3, - ) - box.move_to(group) - self.play(FadeIn(box)) - self.wait() - self.play(FadeOut(box)) - - self.wait() - if show_matching_after_divvying: - for jewel_type in jewel_types: - self.play( - *[ - ApplyMethod(jewel.scale_in_place, 1.5) - for jewel in jewel_type - ], - rate_func = there_and_back, - run_time = 2 - ) - self.wait() - self.play( - FadeOut(v_lines), - FadeIn(necklace.chain), - *[ - group.restore for group in strand_groups - ] - ) - self.remove(*strand_groups) - self.add(necklace) - - ######## - - def get_necklace(self, **kwargs): - colors = reduce(op.add, [ - num*[color] - for num, color in zip(self.num_per_jewel, self.jewel_colors) - ]) - self.necklace = Necklace(*colors, **kwargs) - self.necklace.shift(self.necklace_center) - return self.necklace - - def get_jewels_organized_by_type(self, jewels): - return [ - VGroup(*[m for m in jewels if m.get_color() == color]) - for color in map(Color, self.jewel_colors) - ] - - def shuffle_jewels(self, jewels, run_time = 2, path_arc = np.pi/2, **kwargs): - shuffled_indices = list(range(len(jewels))) - random.shuffle(shuffled_indices) - target_group = VGroup(*[ - jewel.copy().move_to(jewels[shuffled_indices[i]]) - for i, jewel in enumerate(jewels) - ]) - self.play(Transform( - jewels, target_group, - run_time = run_time, - path_arc = path_arc, - **kwargs - )) - - def find_slice_indices(self, jewels, jewel_types): - - def jewel_to_type_number(jewel): - for i, jewel_type in enumerate(jewel_types): - if jewel in jewel_type: - return i - raise Exception("Not in any jewel_types") - type_numbers = list(map(jewel_to_type_number, jewels)) - - n_types = len(jewel_types) - for slice_indices in it.combinations(list(range(1, len(jewels))), n_types): - slice_indices = [0] + list(slice_indices) + [len(jewels)] - if self.forced_binary_choices is not None: - all_binary_choices = [self.forced_binary_choices] - else: - all_binary_choices = it.product(*[list(range(2))]*(n_types+1)) - for binary_choices in all_binary_choices: - subsets = [ - type_numbers[i1:i2] - for i1, i2 in zip(slice_indices, slice_indices[1:]) - ] - left_sets, right_sets = [ - [ - subset - for subset, index in zip(subsets, binary_choices) - if index == target_index - ] - for target_index in range(2) - ] - flat_left_set = np.array(list(it.chain(*left_sets))) - flat_right_set = np.array(list(it.chain(*right_sets))) - - - match_array = [ - sum(flat_left_set == n) == sum(flat_right_set == n) - for n in range(n_types) - ] - if np.all(match_array): - return slice_indices, binary_choices - raise Exception("No fair division found") - -class ThingToProve(PiCreatureScene): - def construct(self): - arrow = Arrow(UP, DOWN) - top_words = TextMobject("$n$ jewel types") - top_words.next_to(arrow, UP) - bottom_words = TextMobject(""" - Fair division possible - with $n$ cuts - """) - bottom_words.next_to(arrow, DOWN) - - self.play( - Write(top_words, run_time = 2), - self.pi_creature.change_mode, "raise_right_hand" - ) - self.play(ShowCreation(arrow)) - self.play( - Write(bottom_words, run_time = 2), - self.pi_creature.change_mode, "pondering" - ) - self.wait(3) - -class FiveJewelCase(IntroduceStolenNecklaceProblem): - CONFIG = { - "jewel_colors" : [BLUE, GREEN, WHITE, RED, YELLOW], - "num_per_jewel" : [6, 4, 4, 2, 8], - "forced_binary_choices" : (0, 1, 0, 1, 0, 1), - } - def construct(self): - random.seed(self.random_seed) - self.add(self.get_necklace()) - jewels = self.necklace.jewels - self.shuffle_jewels(jewels, run_time = 0) - self.jewel_types = self.get_jewels_organized_by_type(jewels) - self.add_title() - self.add_thieves() - for thief in self.thieves: - ApplyMethod(thief.change_mode, "pondering").update(1) - thief.look_at(self.necklace) - self.divvy_with_n_cuts() - - def add_title(self): - n_cuts = len(self.jewel_colors) - title = TextMobject( - "%d jewel types, %d cuts"%(n_cuts, n_cuts) - ) - title.to_edge(UP) - self.add(title) - -class SixJewelCase(FiveJewelCase): - CONFIG = { - "jewel_colors" : [BLUE, GREEN, WHITE, RED, YELLOW, MAROON_B], - "num_per_jewel" : [6, 4, 4, 2, 2, 6], - "forced_binary_choices" : (0, 1, 0, 1, 0, 1, 0), - } - -class DiscussApplicability(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Minimize sharding, - allocate resources evenly - """) - self.change_student_modes(*["pondering"]*3) - self.wait(2) - -class ThreeJewelCase(FiveJewelCase): - CONFIG = { - "jewel_colors" : [BLUE, GREEN, WHITE], - "num_per_jewel" : [6, 4, 8], - "forced_binary_choices" : (0, 1, 0, 1), - } - -class RepeatedShuffling(IntroduceStolenNecklaceProblem): - CONFIG = { - "num_shuffles" : 5, - "random_seed" : 3, - "show_matching_after_divvying" : False, - } - def construct(self): - random.seed(self.random_seed) - self.add(self.get_necklace()) - jewels = self.necklace.jewels - self.jewel_types = self.get_jewels_organized_by_type(jewels) - self.add_thieves() - for thief in self.thieves: - ApplyMethod(thief.change_mode, "pondering").update(1) - thief.look_at(self.necklace) - - for x in range(self.num_shuffles): - self.shuffle_jewels(jewels) - self.divvy_with_n_cuts( - show_matching_after_divvying = False - ) - -class NowForTheTopology(TeacherStudentsScene): - def construct(self): - self.teacher_says("Now for the \\\\ topology") - self.change_student_modes(*["hooray"]*3) - self.wait(3) - -class ExternallyAnimatedScene(Scene): - def construct(self): - raise Exception("Don't actually run this class.") - -class SphereOntoPlaneIn3D(ExternallyAnimatedScene): - pass - -class DiscontinuousSphereOntoPlaneIn3D(ExternallyAnimatedScene): - pass - -class WriteWords(Scene): - CONFIG = { - "words" : "", - "color" : WHITE, - } - def construct(self): - words = TextMobject(self.words) - words.set_color(self.color) - words.set_width(FRAME_WIDTH-1) - words.to_edge(DOWN) - self.play(Write(words)) - self.wait(2) - -class WriteNotAllowed(WriteWords): - CONFIG = { - "words" : "Not allowed", - "color" : RED, - } - -class NonAntipodalCollisionIn3D(ExternallyAnimatedScene): - pass - -class AntipodalCollisionIn3D(ExternallyAnimatedScene): - pass - -class WriteBorsukUlam(WriteWords): - CONFIG = { - "words" : "Borsuk-Ulam Theorem", - } - -class WriteAntipodal(WriteWords): - CONFIG = { - "words" : "``Antipodal''", - "color" : MAROON_B, - } - -class ProjectOntoEquatorIn3D(ExternallyAnimatedScene): - pass - -class ProjectOntoEquatorWithPolesIn3D(ExternallyAnimatedScene): - pass - -class ProjectAntipodalNonCollisionIn3D(ExternallyAnimatedScene): - pass - -class ShearThenProjectnOntoEquatorPolesMissIn3D(ExternallyAnimatedScene): - pass - -class ShearThenProjectnOntoEquatorAntipodalCollisionIn3D(ExternallyAnimatedScene): - pass - -class ClassicExample(TeacherStudentsScene): - def construct(self): - self.teacher_says("The classic example...") - self.change_student_modes(*["happy"]*3) - self.wait(2) - -class AntipodalEarthPoints(ExternallyAnimatedScene): - pass - -class RotatingEarth(ExternallyAnimatedScene): - pass - -class TemperaturePressurePlane(GraphScene): - CONFIG = { - "x_labeled_nums" : [], - "y_labeled_nums" : [], - "x_axis_label" : "Temperature", - "y_axis_label" : "Pressure", - "graph_origin" : 2.5*DOWN + 2*LEFT, - "corner_square_width" : 4, - "example_point_coords" : (2, 5), - } - def construct(self): - self.setup_axes() - self.draw_arrow() - self.add_example_coordinates() - self.wander_continuously() - - def draw_arrow(self): - square = Square( - side_length = self.corner_square_width, - stroke_color = WHITE, - stroke_width = 0, - ) - square.to_corner(UP+LEFT, buff = 0) - - arrow = Arrow( - square.get_right(), - self.coords_to_point(*self.example_point_coords) - ) - - self.play(ShowCreation(arrow)) - - def add_example_coordinates(self): - dot = Dot(self.coords_to_point(*self.example_point_coords)) - dot.set_color(YELLOW) - tex = TexMobject("(25^\\circ\\text{C}, 101 \\text{ kPa})") - tex.next_to(dot, UP+RIGHT, buff = SMALL_BUFF) - - self.play(ShowCreation(dot)) - self.play(Write(tex)) - self.wait() - self.play(FadeOut(tex)) - - def wander_continuously(self): - path = VMobject().set_points_smoothly([ - ORIGIN, 2*UP+RIGHT, 2*DOWN+RIGHT, - 5*RIGHT, 4*RIGHT+UP, 3*RIGHT+2*DOWN, - DOWN+LEFT, 2*RIGHT - ]) - point = self.coords_to_point(*self.example_point_coords) - path.shift(point) - - path.set_color(GREEN) - - self.play(ShowCreation(path, run_time = 10, rate_func=linear)) - self.wait() - -class AlternateSphereSquishing(ExternallyAnimatedScene): - pass - -class AlternateAntipodalCollision(ExternallyAnimatedScene): - pass - -class AskWhy(TeacherStudentsScene): - def construct(self): - self.student_says("But...why?") - self.change_student_modes("pondering", None, "thinking") - self.play(self.get_teacher().change_mode, "happy") - self.wait(3) - -class PointOutVSauce(CheckOutMathologer): - CONFIG = { - "channel_name" : "", - "logo_file" : "Vsauce_logo", - "logo_height" : 1, - "logo_color" : GREY, - } - def get_logo(self): - logo = SVGMobject(file_name = self.logo_file) - logo.set_height(self.logo_height) - logo.to_corner(UP+LEFT) - logo.set_stroke(width = 0) - logo.set_fill(GREEN) - logo.sort() - return logo - - def get_logo_intro_animation(self, logo): - return DrawBorderThenFill( - logo, - run_time = 2, - ) - -class WalkEquatorPostTransform(GraphScene): - CONFIG = { - "x_labeled_nums" : [], - "y_labeled_nums" : [], - "graph_origin" : 2.5*DOWN + 2*LEFT, - "curved_arrow_color" : WHITE, - "curved_arrow_radius" : 3, - "num_great_arcs" : 10, - } - def construct(self): - self.setup_axes() - self.add_curved_arrow() - self.great_arc_images = self.get_great_arc_images() - - self.walk_equator() - self.walk_tilted_equator() - self.draw_transverse_curve() - self.walk_transverse_curve() - - def add_curved_arrow(self): - arc = Arc( - start_angle = 2*np.pi/3, angle = -np.pi/2, - radius = self.curved_arrow_radius, - color = self.curved_arrow_color - ) - arc.add_tip() - arc.move_to(self.coords_to_point(0, 7)) - - self.add(arc) - - def walk_equator(self): - equator = self.great_arc_images[0] - dots = VGroup(Dot(), Dot()) - dots.set_color(MAROON_B) - dot_movement = self.get_arc_walk_dot_movement(equator, dots) - dot_movement.update(0) - - self.play(ShowCreation(equator, run_time = 3)) - self.play(FadeIn(dots[0])) - dots[1].set_fill(opacity = 0) - self.play(dot_movement) - self.play(dots[1].set_fill, None, 1) - self.play(dot_movement) - self.play(dot_movement) - - proportion = equator.collision_point_proportion - self.play(self.get_arc_walk_dot_movement( - equator, dots, - rate_func = lambda t : 2*proportion*smooth(t) - )) - v_line = DashedLine(FRAME_Y_RADIUS*UP, FRAME_Y_RADIUS*DOWN) - v_line.shift(dots.get_center()[0]*RIGHT) - self.play(ShowCreation(v_line)) - self.wait() - self.play(FadeOut(v_line)) - - dots.save_state() - equator.save_state() - self.play( - equator.fade, - dots.fade - ) - - self.first_dots = dots - - def walk_tilted_equator(self): - equator = self.great_arc_images[0] - tilted_eq = self.great_arc_images[1] - - dots = VGroup(Dot(), Dot()) - dots.set_color(MAROON_B) - dot_movement = self.get_arc_walk_dot_movement(tilted_eq, dots) - dot_movement.update(0) - - self.play(ReplacementTransform(equator.copy(), tilted_eq)) - self.wait() - self.play(FadeIn(dots)) - self.play(dot_movement) - - proportion = tilted_eq.collision_point_proportion - self.play(self.get_arc_walk_dot_movement( - tilted_eq, dots, - rate_func = lambda t : 2*proportion*smooth(t) - )) - v_line = DashedLine(FRAME_Y_RADIUS*UP, FRAME_Y_RADIUS*DOWN) - v_line.shift(dots.get_center()[0]*RIGHT) - self.play(ShowCreation(v_line)) - self.wait() - self.play(FadeOut(v_line)) - self.play(*list(map(FadeOut, [tilted_eq, dots]))) - - def draw_transverse_curve(self): - transverse_curve = self.get_transverse_curve(self.great_arc_images) - dots = self.first_dots - equator = self.great_arc_images[0] - - self.play(dots.restore) - equator.restore() - self.great_arc_images.fade() - - target_arcs = list(self.great_arc_images[1:]) - target_dots = [] - for arc in target_arcs: - new_dots = dots.copy() - for dot, point in zip(new_dots, arc.x_collision_points): - dot.move_to(point) - target_dots.append(new_dots) - - alt_eq = equator.copy() - alt_eq.points = np.array(list(reversed(alt_eq.points))) - alt_dots = dots.copy() - alt_dots.submobjects.reverse() - target_arcs += [alt_eq, alt_eq.copy()] - target_dots += [alt_dots, alt_dots.copy()] - - equator_transform = Succession(*[ - Transform(equator, arc, rate_func=linear) - for arc in target_arcs - ]) - dots_transform = Succession(*[ - Transform(dots, target, rate_func=linear) - for target in target_dots - ]) - - self.play( - ShowCreation(transverse_curve, lag_ratio = 0), - equator_transform, - dots_transform, - run_time = 10, - rate_func=linear, - ) - self.wait(2) - - def walk_transverse_curve(self): - transverse_curve = self.get_transverse_curve(self.great_arc_images) - dots = self.first_dots - - def dot_update(dots, alpha): - for dot, curve in zip(dots, transverse_curve): - dot.move_to(curve.point_from_proportion(alpha)) - return dots - - for x in range(2): - self.play( - UpdateFromAlphaFunc(dots, dot_update), - run_time = 4 - ) - self.play( - UpdateFromAlphaFunc(dots, dot_update), - run_time = 4, - rate_func = lambda t : 0.455*smooth(t) - ) - self.play( - dots.set_color, YELLOW, - dots.scale_in_place, 1.2, - rate_func = there_and_back - ) - self.wait() - - ####### - - def get_arc_walk_dot_movement(self, arc, dots, **kwargs): - def dot_update(dots, alpha): - dots[0].move_to(arc.point_from_proportion(0.5*alpha)) - dots[1].move_to(arc.point_from_proportion(0.5+0.5*alpha)) - return dots - if "run_time" not in kwargs: - kwargs["run_time"] = 5 - return UpdateFromAlphaFunc(dots, dot_update, **kwargs) - - def sphere_to_plane(self, point): - x, y, z = point - return np.array([ - x - 2*x*z + y + 1, - y+0.5*y*np.cos(z*np.pi), - 0 - ]) - - def sphere_point(self, portion_around_equator, equator_tilt = 0): - theta = portion_around_equator*2*np.pi - point = np.cos(theta)*RIGHT + np.sin(theta)*UP - phi = equator_tilt*np.pi - return rotate_vector(point, phi, RIGHT) - - def get_great_arc_images(self): - curves = VGroup(*[ - ParametricFunction( - lambda t : self.sphere_point(t, s) - ).apply_function(self.sphere_to_plane) - for s in np.arange(0, 1, 1./self.num_great_arcs) - # for s in [0] - ]) - curves.set_color(YELLOW) - curves[0].set_color(RED) - for curve in curves: - antipodal_x_diff = lambda x : \ - curve.point_from_proportion(x+0.5)[0]-\ - curve.point_from_proportion(x)[0] - last_x = 0 - last_sign = np.sign(antipodal_x_diff(last_x)) - for x in np.linspace(0, 0.5, 100): - sign = np.sign(antipodal_x_diff(x)) - if sign != last_sign: - mean = np.mean([last_x, x]) - curve.x_collision_points = [ - curve.point_from_proportion(mean), - curve.point_from_proportion(mean+0.5), - ] - curve.collision_point_proportion = mean - break - last_x = x - last_sign = sign - return curves - - def get_transverse_curve(self, gerat_arc_images): - points = list(it.chain(*[ - [ - curve.x_collision_points[i] - for curve in gerat_arc_images - ] - for i in (0, 1) - ])) - full_curve = VMobject(close_new_points = True) - full_curve.set_points_smoothly(points + [points[0]]) - full_curve.set_color(MAROON_B) - first_half = full_curve.copy().pointwise_become_partial( - full_curve, 0, 0.5 - ) - second_half = first_half.copy().rotate_in_place(np.pi, RIGHT) - broken_curve = VGroup(first_half, second_half) - return broken_curve - -class WalkAroundEquatorPreimage(ExternallyAnimatedScene): - pass - -class WalkTiltedEquatorPreimage(ExternallyAnimatedScene): - pass - -class FormLoopTransverseToEquator(ExternallyAnimatedScene): - pass - -class AntipodalWalkAroundTransverseLoop(ExternallyAnimatedScene): - pass - -class MentionGenerality(TeacherStudentsScene, ThreeDScene): - def construct(self): - necklace = Necklace(width = FRAME_X_RADIUS) - necklace.shift(2*UP) - necklace.to_edge(RIGHT) - arrow = TexMobject("\\Leftrightarrow") - arrow.scale(2) - arrow.next_to(necklace, LEFT) - q_marks = TexMobject("???") - q_marks.next_to(arrow, UP) - arrow.add(q_marks) - - formula = TexMobject("f(\\textbf{x}) = f(-\\textbf{x})") - formula.next_to(self.get_students(), UP, buff = LARGE_BUFF) - formula.to_edge(LEFT, buff = LARGE_BUFF) - - self.play( - self.teacher.change_mode, "raise_right_hand", - self.teacher.look_at, arrow - ) - self.play( - FadeIn(necklace, run_time = 2, lag_ratio = 0.5), - Write(arrow), - *[ - ApplyMethod(pi.look_at, arrow) - for pi in self.get_pi_creatures() - ] - ) - self.change_student_modes("pondering", "erm", "confused") - self.wait() - self.play(*[ - ApplyMethod(pi.look_at, arrow) - for pi in self.get_pi_creatures() - ]) - self.play(Write(formula)) - self.wait(3) - -class SimpleSphere(ExternallyAnimatedScene): - pass - -class PointsIn3D(Scene): - CONFIG = { - # "colors" : [RED, GREEN, BLUE], - "colors" : color_gradient([GREEN, BLUE], 3), - } - def construct(self): - sphere_def = TextMobject( - "\\doublespacing Sphere in 3D: All", "$(x_1, x_2, x_3)$\\\\", - "such that", "$x_1^2 + x_2^2 + x_3^2 = 1$", - alignment = "", - ) - sphere_def.next_to(ORIGIN, DOWN) - for index, subindex_list in (1, [1, 2, 4, 5, 7, 8]), (3, [0, 2, 4, 6, 8, 10]): - colors = np.repeat(self.colors, 2) - for subindex, color in zip(subindex_list, colors): - sphere_def[index][subindex].set_color(color) - - point_ex = TextMobject( - "For example, ", - "(", "0.41", ", ", "-0.58", ", ", "0.71", ")", - arg_separator = "" - ) - for index, color in zip([2, 4, 6], self.colors): - point_ex[index].set_color(color) - point_ex.scale(0.8) - point_ex.next_to( - sphere_def[1], UP+RIGHT, - buff = 1.5*LARGE_BUFF - ) - point_ex.shift_onto_screen() - arrow = Arrow(sphere_def[1].get_top(), point_ex.get_bottom()) - - self.play(Write(sphere_def[1])) - self.play(ShowCreation(arrow)) - self.play(Write(point_ex)) - self.wait() - self.play( - Animation(sphere_def[1].copy(), remover = True), - Write(sphere_def), - ) - self.wait() - -class AntipodalPairToBeGivenCoordinates(ExternallyAnimatedScene): - pass - -class WritePointCoordinates(Scene): - CONFIG = { - "colors" : color_gradient([GREEN, BLUE], 3), - "corner" : DOWN+RIGHT, - } - def construct(self): - coords = self.get_coords() - arrow = Arrow( - -self.corner, self.corner, - stroke_width = 8, - color = MAROON_B - ) - x_component = self.corner[0]*RIGHT - y_component = self.corner[1]*UP - arrow.next_to( - coords.get_edge_center(y_component), - y_component, - aligned_edge = -x_component, - buff = MED_SMALL_BUFF - ) - - group = VGroup(coords, arrow) - group.scale(2) - group.to_corner(self.corner) - - - self.play(FadeIn(coords)) - self.play(ShowCreation(arrow)) - self.wait() - - def get_coords(self): - coords = TexMobject( - "(", "0.41", ", ", "-0.58", ", ", "0.71", ")", - arg_separator = "" - ) - for index, color in zip([1, 3, 5], self.colors): - coords[index].set_color(color) - return coords - -class WriteAntipodalCoordinates(WritePointCoordinates): - CONFIG = { - "corner" : UP+LEFT, - "sign_color" : RED, - } - - def get_coords(self): - coords = TexMobject( - "(", "-", "0.41", ", ", "+", "0.58", ", ", "-", "0.71", ")", - arg_separator = "" - ) - for index, color in zip([2, 5, 8], self.colors): - coords[index].set_color(color) - coords[index-1].set_color(self.sign_color) - return coords - -class GeneralizeBorsukUlam(Scene): - CONFIG = { - "n_dims" : 3, - "boundary_colors" : [GREEN_B, BLUE], - "output_boundary_color" : [MAROON_B, YELLOW], - "negative_color" : RED, - } - def setup(self): - self.colors = color_gradient(self.boundary_colors, self.n_dims) - - def construct(self): - sphere_set = self.get_sphere_set() - arrow = Arrow(LEFT, RIGHT) - f = TexMobject("f") - output_space = self.get_output_space() - equation = self.get_equation() - - sphere_set.to_corner(UP+LEFT) - arrow.next_to(sphere_set, RIGHT) - f.next_to(arrow, UP) - output_space.next_to(arrow, RIGHT) - equation.next_to(sphere_set, DOWN, buff = LARGE_BUFF) - equation.to_edge(RIGHT) - lhs = VGroup(*equation[:2]) - eq = equation[2] - rhs = VGroup(*equation[3:]) - - self.play(FadeIn(sphere_set)) - self.wait() - self.play( - ShowCreation(arrow), - Write(f) - ) - self.play(Write(output_space)) - self.wait() - self.play(FadeIn(lhs)) - self.play( - ReplacementTransform(lhs.copy(), rhs), - Write(eq) - ) - self.wait() - - def get_condition(self): - squares = list(map(TexMobject, [ - "x_%d^2"%d - for d in range(1, 1+self.n_dims) - ])) - for square, color in zip(squares, self.colors): - square[0].set_color(color) - square[-1].set_color(color) - plusses = [TexMobject("+") for x in range(self.n_dims-1)] - plusses += [TexMobject("=1")] - condition = VGroup(*it.chain(*list(zip(squares, plusses)))) - condition.arrange(RIGHT) - - return condition - - def get_tuple(self): - mid_parts = list(it.chain(*[ - ["x_%d"%d, ","] - for d in range(1, self.n_dims) - ])) - tup = TexMobject(*["("] + mid_parts + ["x_%d"%self.n_dims, ")"]) - for index, color in zip(it.count(1, 2), self.colors): - tup[index].set_color(color) - - return tup - - def get_negative_tuple(self): - mid_parts = list(it.chain(*[ - ["-", "x_%d"%d, ","] - for d in range(1, self.n_dims) - ])) - tup = TexMobject(*["("] + mid_parts + ["-", "x_%d"%self.n_dims, ")"]) - for index, color in zip(it.count(1, 3), self.colors): - tup[index].set_color(self.negative_color) - tup[index+1].set_color(color) - - return tup - - def get_output_space(self): - return TextMobject("%dD space"%(self.n_dims-1)) - # n_dims = self.n_dims-1 - # colors = color_gradient(self.output_boundary_color, n_dims) - # mid_parts = list(it.chain(*[ - # ["y_%d"%d, ","] - # for d in range(1, n_dims) - # ])) - # tup = TexMobject(*["("] + mid_parts + ["y_%d"%n_dims, ")"]) - # for index, color in zip(it.count(1, 2), colors): - # tup[index].set_color(color) - - # return tup - - def get_equation(self): - tup = self.get_tuple() - neg_tup = self.get_negative_tuple() - f1, f2 = [TexMobject("f") for x in range(2)] - equals = TexMobject("=") - equation = VGroup(f1, tup, equals, f2, neg_tup) - equation.arrange(RIGHT, buff = SMALL_BUFF) - - return equation - - def get_sphere_set(self): - tup = self.get_tuple() - such_that = TextMobject("such that") - such_that.next_to(tup, RIGHT) - condition = self.get_condition() - condition.next_to( - tup, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - group = VGroup(tup, such_that, condition) - l_brace = Brace(group, LEFT) - r_brace = Brace(group, RIGHT) - group.add(l_brace, r_brace) - - return group - -# class FiveDBorsukUlam(GeneralizeBorsukUlam): -# CONFIG = { -# "n_dims" : 5, -# } - -class MentionMakingNecklaceProblemContinuous(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Translate this into - a continuous problem. - """) - self.change_student_modes("confused", "pondering", "erm") - self.wait(3) - -class MakeTwoJewelCaseContinuous(IntroduceStolenNecklaceProblem): - CONFIG = { - "jewel_colors" : [BLUE, GREEN], - "num_per_jewel" : [8, 10], - "random_seed" : 2, - "forced_binary_choices" : (0, 1, 0), - "show_matching_after_divvying" : True, - "necklace_center" : ORIGIN, - "necklace_width" : FRAME_WIDTH - 3, - "random_seed" : 0, - "num_continuous_division_searches" : 4, - } - def construct(self): - random.seed(self.random_seed) - self.introduce_necklace() - self.divvy_with_n_cuts() - self.identify_necklace_with_unit_interval() - self.color_necklace() - self.find_continuous_fair_division() - self.show_continuous_fair_division() - self.set_color_continuous_groups() - self.mention_equivalence_to_discrete_case() - self.shift_divide_off_tick_marks() - - def introduce_necklace(self): - self.get_necklace( - width = self.necklace_width, - ) - self.play(FadeIn( - self.necklace, - lag_ratio = 0.5 - )) - self.shuffle_jewels(self.necklace.jewels) - jewel_types = self.get_jewels_organized_by_type( - self.necklace.jewels - ) - self.wait() - self.count_jewel_types(jewel_types) - self.wait() - - self.jewel_types = jewel_types - - def count_jewel_types(self, jewel_types): - enumeration_labels = VGroup() - for jewel_type in jewel_types: - num_mob = TexMobject(str(len(jewel_type))) - jewel_copy = jewel_type[0].copy() - # jewel_copy.set_height(num_mob.get_height()) - jewel_copy.next_to(num_mob) - label = VGroup(num_mob, jewel_copy) - enumeration_labels.add(label) - enumeration_labels.arrange(RIGHT, buff = LARGE_BUFF) - enumeration_labels.to_edge(UP) - - for jewel_type, label in zip(jewel_types, enumeration_labels): - jewel_type.sort_submobjects() - - jewel_type.save_state() - jewel_type.generate_target() - jewel_type.target.arrange() - jewel_type.target.move_to(2*UP) - self.play( - MoveToTarget(jewel_type), - Write(label) - ) - self.play(jewel_type.restore) - - def divvy_with_n_cuts(self): - IntroduceStolenNecklaceProblem.divvy_with_n_cuts( - self, - with_thieves = False, - highlight_groups = False, - show_matching_after_divvying = True, - ) - - def identify_necklace_with_unit_interval(self): - interval = UnitInterval( - tick_frequency = 1./sum(self.num_per_jewel), - tick_size = 0.2, - numbers_with_elongated_ticks = [], - ) - interval.stretch_to_fit_width(self.necklace.get_width()) - interval.move_to(self.necklace) - tick_marks = interval.tick_marks - tick_marks.set_stroke(WHITE, width = 2) - - brace = Brace(interval) - brace_text = brace.get_text("Length = 1") - - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait() - self.play( - ShowCreation(interval.tick_marks), - ) - self.wait() - - self.tick_marks = interval.tick_marks - self.length_brace = VGroup(brace, brace_text) - - def color_necklace(self): - example_index = len(self.necklace.jewels)/2 - jewels = self.necklace.jewels - chain = self.necklace.chain - self.remove(self.necklace) - self.add(chain, jewels) - - jewels.submobjects.sort( - key=lambda m: m.get_center()[0] - ) - remaining_indices = list(range(len(jewels))) - remaining_indices.remove(example_index) - - example_segment = self.color_necklace_by_indices(example_index) - remaining_segments = self.color_necklace_by_indices(*remaining_indices) - self.remove(chain) - segments = VGroup(example_segment[0], *remaining_segments) - segments.submobjects.sort( - key=lambda m: m.get_center()[0] - ) - segment_types = VGroup(*[ - VGroup(*[m for m in segments if m.get_color() == Color(color)]) - for color in self.jewel_colors - ]) - - for group in segment_types: - length_tex = TexMobject("\\frac{%d}{%d}"%( - len(group), - len(jewels) - )) - length_tex.next_to(group, UP) - length_tex.shift(UP) - self.play( - group.shift, UP, - Write(length_tex, run_time = 1), - ) - self.wait() - self.play( - group.shift, DOWN, - FadeOut(length_tex) - ) - self.play(FadeOut(self.length_brace)) - - self.segments = segments - - def color_necklace_by_indices(self, *indices): - chain = self.necklace.chain - jewels = VGroup(*[ - self.necklace.jewels[i] - for i in indices - ]) - n_jewels = len(self.necklace.jewels) - - segments = VGroup(*[ - Line( - chain.point_from_proportion(index/float(n_jewels)), - chain.point_from_proportion((index+1)/float(n_jewels)), - color = jewel.get_color() - ) - for index, jewel in zip(indices, jewels) - ]) - for jewel in jewels: - jewel.save_state() - - self.play(jewels.shift, jewels.get_height()*UP) - self.play(ReplacementTransform( - jewels, segments, - lag_ratio = 0.5, - run_time = 2 - )) - self.wait() - return segments - - def find_continuous_fair_division(self): - chain = self.necklace.chain - n_jewels = len(self.necklace.jewels) - - slice_indices, ignore = self.find_slice_indices( - self.necklace.jewels, - self.jewel_types - ) - cut_proportions = [ - sorted([random.random(), random.random()]) - for x in range(self.num_continuous_division_searches) - ] - cut_proportions.append([ - float(i)/n_jewels - for i in slice_indices[1:-1] - ]) - cut_points = [ - list(map(chain.point_from_proportion, pair)) - for pair in cut_proportions - ] - v_lines = VGroup(*[DashedLine(UP, DOWN) for x in range(2)]) - - for line, point in zip(v_lines, cut_points[0]): - line.move_to(point) - - self.play(ShowCreation(v_lines)) - self.wait() - for target_points in cut_points[1:]: - self.play(*[ - ApplyMethod(line.move_to, point) - for line, point in zip(v_lines, target_points) - ]) - self.wait() - - self.slice_indices = slice_indices - self.v_lines = v_lines - - def show_continuous_fair_division(self): - slice_indices = self.slice_indices - - groups = [ - VGroup( - VGroup(*self.segments[i1:i2]), - VGroup(*self.tick_marks[i1:i2]), - ) - for i1, i2 in zip(slice_indices, slice_indices[1:]) - ] - groups[-1].add(self.tick_marks[-1]) - vects = [[UP, DOWN][i] for i in self.forced_binary_choices] - - self.play(*[ - ApplyMethod(group.shift, 0.5*vect) - for group, vect in zip(groups, vects) - ]) - self.wait() - - self.groups = groups - - def set_color_continuous_groups(self): - top_group = VGroup(self.groups[0], self.groups[2]) - bottom_group = self.groups[1] - boxes = VGroup() - for group in top_group, bottom_group: - box = Rectangle( - width = FRAME_WIDTH-2, - height = group.get_height()+SMALL_BUFF, - stroke_width = 0, - fill_color = WHITE, - fill_opacity = 0.25, - ) - box.shift(group.get_center()[1]*UP) - boxes.add(box) - - weight_description = VGroup(*[ - VGroup( - TexMobject("\\frac{%d}{%d}"%( - len(jewel_type)/2, len(self.segments) - )), - Jewel(color = jewel_type[0].get_color()) - ).arrange() - for jewel_type in self.jewel_types - ]) - weight_description.arrange(buff = LARGE_BUFF) - weight_description.next_to(boxes, UP, aligned_edge = LEFT) - - self.play(FadeIn(boxes)) - self.play(Write(weight_description)) - self.wait() - - self.set_color_box = boxes - self.weight_description = weight_description - - def mention_equivalence_to_discrete_case(self): - morty = Mortimer() - morty.flip() - morty.to_edge(DOWN) - morty.shift(LEFT) - self.play(FadeIn(morty)) - self.play(PiCreatureSays( - morty, - """This is equivalent to - the discrete case. """, - bubble_kwargs = { - "height" : 3, - "direction" : LEFT, - } - )) - self.play(Blink(morty)) - self.wait() - self.play(*list(map(FadeOut, [ - morty, morty.bubble, morty.bubble.content - ]))) - - def shift_divide_off_tick_marks(self): - groups = self.groups - slice_indices = self.slice_indices - v_lines = self.v_lines - - left_segment = groups[1][0][0] - left_tick = groups[1][1][0] - right_segment = groups[-1][0][0] - right_tick = groups[-1][1][0] - - segment_width = left_segment.get_width() - - for mob in left_segment, right_segment: - mob.parts = VGroup( - mob.copy().pointwise_become_partial(mob, 0, 0.5), - mob.copy().pointwise_become_partial(mob, 0.5, 1), - ) - self.remove(mob) - self.add(mob.parts) - restorers = [left_segment.parts, left_tick, right_segment.parts, right_tick] - for mob in restorers: - mob.save_state() - - emerald_segments = VGroup(*[ - segment - for segment in list(groups[0][0])+list(groups[2][0]) - if segment.get_color() == Color(self.jewel_colors[1]) - if segment is not right_segment - ]) - emerald_segments.add( - left_segment.parts[0], - right_segment.parts[1], - ) - emerald_segments.sort() - - self.play(v_lines.shift, segment_width*RIGHT/2) - self.play(*[ - ApplyMethod(mob.shift, vect) - for mob, vect in [ - (left_segment.parts[0], UP), - (left_tick, UP), - (right_segment.parts[0], DOWN), - (right_tick, DOWN), - ] - ]) - self.wait() - - words = TextMobject("Cut part way through segment") - words.to_edge(RIGHT) - words.shift(2*UP) - arrow1 = Arrow(words.get_bottom(), left_segment.parts[0].get_right()) - arrow2 = Arrow(words.get_bottom(), right_segment.parts[1].get_left()) - VGroup(words, arrow1, arrow2).set_color(RED) - - self.play(Write(words), ShowCreation(arrow1)) - self.wait() - - emerald_segments.save_state() - emerald_segments.generate_target() - emerald_segments.target.arrange() - emerald_segments.target.move_to(2*DOWN) - brace = Brace(emerald_segments.target, DOWN) - label = VGroup( - TexMobject("5\\left( 1/18 \\right)"), - Jewel(color = self.jewel_colors[1]) - ).arrange() - label.next_to(brace, DOWN) - self.play(MoveToTarget(emerald_segments)) - self.play(GrowFromCenter(brace)) - self.play(Write(label)) - self.wait() - broken_pair = VGroup(*emerald_segments[2:4]) - broken_pair.save_state() - self.play(broken_pair.shift, 0.5*UP) - vect = broken_pair[1].get_left()-broken_pair[1].get_right() - self.play( - broken_pair[0].shift, -vect/2, - broken_pair[1].shift, vect/2, - ) - self.wait() - self.play(broken_pair.space_out_submobjects) - self.play(broken_pair.restore) - self.wait() - self.play( - emerald_segments.restore, - *list(map(FadeOut, [brace, label])) - ) - - self.wait() - self.play(ShowCreation(arrow2)) - self.wait() - self.play(*list(map(FadeOut, [words, arrow1, arrow2]))) - - for line in v_lines: - self.play(line.shift, segment_width*LEFT/2) - self.play(*[mob.restore for mob in restorers]) - self.remove(left_segment.parts, right_segment.parts) - self.add(left_segment, right_segment) - -class ThinkAboutTheChoices(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Think about the choices - behind a division... - """) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = FRAME_X_RADIUS*RIGHT+FRAME_Y_RADIUS*DOWN - ) - self.wait(3) - -class ChoicesInNecklaceCutting(ReconfigurableScene): - CONFIG = { - "num_continuous_division_searches" : 4, - "denoms" : [6, 3, 2], - "necklace_center" : DOWN, - "thief_box_offset" : 1.2, - } - def construct(self): - self.add_necklace() - self.choose_places_to_cut() - self.show_three_numbers_adding_to_one() - self.make_binary_choice() - - def add_necklace(self): - width, colors, num_per_color = [ - MakeTwoJewelCaseContinuous.CONFIG[key] - for key in [ - "necklace_width", "jewel_colors", "num_per_jewel" - ] - ] - color_list = list(it.chain(*[ - num*[color] - for num, color in zip(num_per_color, colors) - ])) - random.shuffle(color_list) - - interval = UnitInterval( - tick_frequency = 1./sum(num_per_color), - tick_size = 0.2, - numbers_with_elongated_ticks = [], - ) - interval.stretch_to_fit_width(width) - interval.shift(self.necklace_center) - tick_marks = interval.tick_marks - tick_marks.set_stroke(WHITE, width = 2) - - segments = VGroup() - for l_tick, r_tick, color in zip(tick_marks, tick_marks[1:], color_list): - segment = Line( - l_tick.get_center(), - r_tick.get_center(), - color = color - ) - segments.add(segment) - - self.necklace = VGroup(segments, tick_marks) - self.add(self.necklace) - - self.interval = interval - - def choose_places_to_cut(self): - v_lines = VGroup(*[DashedLine(UP, DOWN) for x in range(2)]) - final_num_pair = np.cumsum([1./d for d in self.denoms[:2]]) - - num_pairs = [ - sorted([random.random(), random.random()]) - for x in range(self.num_continuous_division_searches) - ] + [final_num_pair] - - point_pairs = [ - list(map(self.interval.number_to_point, num_pair)) - for num_pair in num_pairs - ] - - for line, point in zip(v_lines, point_pairs[0]): - line.move_to(point) - self.play(ShowCreation(v_lines)) - for point_pair in point_pairs[1:]: - self.wait() - self.play(*[ - ApplyMethod(line.move_to, point) - for line, point in zip(v_lines, point_pair) - ]) - self.wait() - - self.division_points = list(it.chain( - [self.interval.get_left()], - point_pairs[-1], - [self.interval.get_right()] - )) - - self.v_lines = v_lines - - def show_three_numbers_adding_to_one(self): - points = self.division_points - braces = [ - Brace(Line(p1+SMALL_BUFF*RIGHT/2, p2+SMALL_BUFF*LEFT/2)) - for p1, p2 in zip(points, points[1:]) - ] - for char, denom, brace in zip("abc", self.denoms, braces): - brace.label = brace.get_text("$%s$"%char) - brace.concrete_label = brace.get_text("$\\frac{1}{%d}$"%denom) - VGroup( - brace.label, - brace.concrete_label - ).set_color(YELLOW) - - words = TextMobject( - "1) Choose", "$a$, $b$, $c$", "so that", "$a+b+c = 1$" - ) - words[1].set_color(YELLOW) - words[3].set_color(YELLOW) - words.to_corner(UP+LEFT) - - self.play(*it.chain(*[ - [GrowFromCenter(brace), Write(brace.label)] - for brace in braces - ])) - self.play(Write(words)) - self.wait(2) - self.play(*[ - ReplacementTransform(brace.label, brace.concrete_label) - for brace in braces - ]) - self.wait() - self.wiggle_v_lines() - self.wait() - self.transition_to_alt_config(denoms = [3, 3, 3]) - self.wait() - self.play(*list(map(FadeOut, list(braces) + [ - brace.concrete_label for brace in braces - ]))) - - self.choice_one_words = words - - def make_binary_choice(self): - groups = self.get_groups() - boxes, labels = self.get_boxes_and_labels() - arrow_pairs, curr_arrows = self.get_choice_arrow_pairs(groups) - words = TextMobject("2) Make a binary choice for each segment") - words.next_to( - self.choice_one_words, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - - self.play(Write(words)) - self.play(*list(map(FadeIn, [boxes, labels]))) - for binary_choices in it.product(*[[0, 1]]*3): - self.play(*[ - ApplyMethod(group.move_to, group.target_points[choice]) - for group, choice in zip(groups, binary_choices) - ] + [ - Transform( - curr_arrow, arrow_pair[choice], - path_arc = np.pi - ) - for curr_arrow, arrow_pair, choice in zip( - curr_arrows, arrow_pairs, binary_choices - ) - ]) - self.wait() - - ###### - - def get_groups(self, indices = None): - segments, tick_marks = self.necklace - if indices is None: - n_segments = len(segments) - indices = [0, n_segments/6, n_segments/2, n_segments] - - groups = [ - VGroup( - VGroup(*segments[i1:i2]), - VGroup(*tick_marks[i1:i2]), - ) - for i1, i2 in zip(indices, indices[1:]) - ] - for group, index in zip(groups, indices[1:]): - group[1].add(tick_marks[index].copy()) - groups[-1][1].add(tick_marks[-1]) - - for group in groups: - group.target_points = [ - group.get_center() + self.thief_box_offset*vect - for vect in (UP, DOWN) - ] - - return groups - - def get_boxes_and_labels(self): - box = Rectangle( - height = self.necklace.get_height()+SMALL_BUFF, - width = self.necklace.get_width()+2*SMALL_BUFF, - stroke_width = 0, - fill_color = WHITE, - fill_opacity = 0.25 - ) - box.move_to(self.necklace) - - boxes = VGroup(*[ - box.copy().shift(self.thief_box_offset*vect) - for vect in (UP, DOWN) - ]) - labels = VGroup(*[ - TextMobject( - "Thief %d"%(i+1) - ).next_to(box, UP, aligned_edge = RIGHT) - for i, box in enumerate(boxes) - ]) - return boxes, labels - - def get_choice_arrow_pairs(self, groups): - arrow = TexMobject("\\uparrow") - arrow_pairs = [ - [arrow.copy(), arrow.copy().rotate(np.pi)] - for group in groups - ] - pre_arrow_points = [ - VectorizedPoint(group.get_center()) - for group in groups - ] - for point, arrow_pair in zip(pre_arrow_points, arrow_pairs): - for arrow, color in zip(arrow_pair, [GREEN, RED]): - arrow.set_color(color) - arrow.move_to(point.get_center()) - return arrow_pairs, pre_arrow_points - - def wiggle_v_lines(self): - self.play( - *it.chain(*[ - [ - line.rotate_in_place, np.pi/12, vect, - line.set_color, RED - ] - for line, vect in zip(self.v_lines, [OUT, IN]) - ]), - rate_func = wiggle - ) - -class CompareThisToSphereChoice(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Compare this to choosing - a point on the sphere. - """) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = FRAME_X_RADIUS*RIGHT+FRAME_Y_RADIUS*DOWN - ) - self.wait(3) - -class SimpleRotatingSphereWithPoint(ExternallyAnimatedScene): - pass - -class ChoicesForSpherePoint(GeneralizeBorsukUlam): - def construct(self): - self.add_sphere_set() - self.initialize_words() - self.play(Write(self.choice_one_words)) - self.wait() - self.show_example_choices() - self.show_binary_choices() - - def get_tuple(self): - tup = TexMobject("(x, y, z)") - for i, color in zip([1, 3, 5], self.colors): - tup[i].set_color(color) - return tup - - def get_condition(self): - condition = TexMobject("x^2+y^2+z^2 = 1") - for i, color in zip([0, 3, 6], self.colors): - VGroup(*condition[i:i+2]).set_color(color) - return condition - - def add_sphere_set(self): - sphere_set = self.get_sphere_set() - sphere_set.scale(0.7) - sphere_set.to_edge(RIGHT) - sphere_set.shift(UP) - - self.add(sphere_set) - self.sphere_set = sphere_set - - def initialize_words(self): - choice_one_words = TextMobject( - "1) Choose", "$x^2$, $y^2$, $z^2$", - "so that", "$x^2+y^2+z^2 = 1$" - ) - for i in 1, 3: - for j, color in zip([0, 3, 6], self.colors): - VGroup(*choice_one_words[i][j:j+2]).set_color(color) - choice_one_words.to_corner(UP+LEFT) - - choice_two_words = TextMobject( - "2) Make a binary choice for each one" - ) - choice_two_words.next_to( - choice_one_words, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - - self.choice_one_words = choice_one_words - self.choice_two_words = choice_two_words - - def show_example_choices(self): - choices = VGroup(*[ - TexMobject(*tex).set_color(color) - for color, tex in zip(self.colors, [ - ("x", "^2 = ", "1/6"), - ("y", "^2 = ", "1/3"), - ("z", "^2 = ", "1/2"), - ]) - ]) - choices.arrange( - DOWN, - buff = LARGE_BUFF, - aligned_edge = LEFT - ) - choices.set_height(FRAME_Y_RADIUS) - choices.to_edge(LEFT) - choices.shift(DOWN) - - self.play(FadeIn( - choices, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait() - - self.choices = choices - - def show_binary_choices(self): - for choice in self.choices: - var_tex = choice.expression_parts[0] - frac_tex = choice.expression_parts[2] - sqrts = VGroup(*[ - TexMobject( - var_tex + "=" + sign + \ - "\\sqrt{%s}"%frac_tex) - for sign in ["+", "-"] - ]) - for sqrt in sqrts: - sqrt.scale(0.6) - sqrts.arrange(DOWN) - sqrts.next_to(choice, RIGHT, buff = LARGE_BUFF) - sqrts.set_color(choice.get_color()) - - arrows = VGroup(*[ - Arrow( - choice.get_right(), sqrt.get_left(), - color = WHITE, - tip_length = 0.1, - buff = SMALL_BUFF - ) - for sqrt in sqrts - ]) - - self.play(ShowCreation(arrows)) - self.play(FadeIn(sqrts, lag_ratio = 0.5)) - self.play(Write(self.choice_two_words)) - self.wait() - -class NecklaceDivisionSphereAssociation(ChoicesInNecklaceCutting): - CONFIG = { - "xyz_colors" : color_gradient([GREEN_B, BLUE], 3), - "necklace_center" : DOWN, - "thief_box_offset" : 1.6, - "denoms" : [6, 3, 2], - } - def construct(self): - self.add_necklace() - self.add_sphere_point_label() - self.choose_places_to_cut() - self.add_braces() - self.add_boxes_and_labels() - self.show_binary_choice_association() - self.ask_about_antipodal_pairs() - - def add_sphere_point_label(self): - label = TextMobject( - "$(x, y, z)$", - "such that", - "$x^2 + y^2 + z^2 = 1$" - ) - for i, j_list in (0, [1, 3, 5]), (2, [0, 3, 6]): - for j, color in zip(j_list, self.xyz_colors): - label[i][j].set_color(color) - label.to_corner(UP+RIGHT) - - ghost_sphere_point = VectorizedPoint() - ghost_sphere_point.to_corner(UP+LEFT, buff = LARGE_BUFF) - ghost_sphere_point.shift(2*RIGHT) - - arrow = Arrow( - label.get_left(), ghost_sphere_point, - color = WHITE - ) - - self.add(label, arrow) - - self.sphere_point_label = label - - def add_braces(self): - points = self.division_points - braces = [ - Brace( - Line(p1+SMALL_BUFF*RIGHT/2, p2+SMALL_BUFF*LEFT/2), - UP - ) - for p1, p2 in zip(points, points[1:]) - ] - for char, brace, color, denom in zip("xyz", braces, self.xyz_colors, self.denoms): - brace.label = brace.get_text( - "$%s^2$"%char, "$= 1/%d$"%denom, - buff = SMALL_BUFF - ) - brace.label.set_color(color) - if brace.label.get_right()[0] > brace.get_right()[0]: - brace.label.next_to( - brace, UP, buff = SMALL_BUFF, - aligned_edge = RIGHT - ) - - self.play(*it.chain( - list(map(GrowFromCenter, braces)), - [Write(brace.label) for brace in braces] - )) - self.wait() - - self.braces = braces - - def add_boxes_and_labels(self): - boxes, labels = self.get_boxes_and_labels() - self.play(*list(map(FadeIn, [boxes, labels]))) - self.wait() - - def show_binary_choice_association(self): - groups = self.get_groups() - self.swapping_anims = [] - final_choices = [1, 0, 1] - quads = list(zip(self.braces, self.denoms, groups, final_choices)) - for brace, denom, group, final_choice in quads: - char = brace.label.args[0][1] - choices = [ - TexMobject( - char, "=", sign, "\\sqrt{\\frac{1}{%d}}"%denom - ) - for sign in ("+", "-") - ] - for choice, color in zip(choices, [GREEN, RED]): - # choice[0].set_color(brace.label.get_color()) - choice[2].set_color(color) - choice.scale(0.8) - choice.move_to(group) - if choice.get_width() > 0.8*group.get_width(): - choice.next_to(group.get_right(), LEFT, buff = MED_SMALL_BUFF) - original_choices = [m.copy() for m in choices] - - self.play( - ReplacementTransform( - VGroup(brace.label[0], brace, brace.label[1]), - choices[0] - ), - group.move_to, group.target_points[0] - ) - self.wait() - self.play( - Transform(*choices), - group.move_to, group.target_points[1] - ) - self.wait() - if final_choice == 0: - self.play( - Transform(choices[0], original_choices[0]), - group.move_to, group.target_points[0] - ) - self.swapping_anims += [ - Transform(choices[0], original_choices[1-final_choice]), - group.move_to, group.target_points[1-final_choice] - ] - - def ask_about_antipodal_pairs(self): - question = TextMobject("What do antipodal points signify?") - question.move_to(self.sphere_point_label, LEFT) - question.set_color(MAROON_B) - antipodal_tex = TexMobject( - "(x, y, z) \\rightarrow (-x, -y, -z)" - ) - antipodal_tex.next_to(question, DOWN, aligned_edge = LEFT) - - self.play(FadeOut(self.sphere_point_label)) - self.play(FadeIn(question)) - self.wait() - self.play(Write(antipodal_tex)) - self.wait() - self.wiggle_v_lines() - self.wait() - self.play(*self.swapping_anims) - self.wait() - -class SimpleRotatingSphereWithAntipodes(ExternallyAnimatedScene): - pass - -class TotalLengthOfEachJewelEquals(NecklaceDivisionSphereAssociation, ThreeDScene): - CONFIG = { - "random_seed" : 1, - "thief_box_offset" : 1.2, - } - def construct(self): - random.seed(self.random_seed) - self.add_necklace() - self.add_boxes_and_labels() - self.find_fair_division() - self.demonstrate_fair_division() - self.perform_antipodal_swap() - - def find_fair_division(self): - segments, tick_marks = self.necklace - segments.sort() - segment_colors = [ - segment.get_color() - for segment in segments - ] - indices = self.get_fair_division_indices(segment_colors) - groups = self.get_groups( - [0] + list(np.array(indices)+1) + [len(segments)] - ) - self.add(*groups) - binary_choice = [0, 1, 0] - - v_lines = VGroup(*[DashedLine(UP, DOWN) for x in range(2)]) - v_lines.move_to(self.necklace) - self.play(ShowCreation(v_lines)) - self.play(*[ - ApplyMethod(line.move_to, segments[index].get_right()) - for line, index in zip(v_lines, indices) - ]) - self.wait() - self.play(*[ - ApplyMethod(group.move_to, group.target_points[choice]) - for group, choice in zip(groups, binary_choice) - ]) - self.wait() - - self.groups = groups - self.v_lines = v_lines - - def get_fair_division_indices(self, colors): - colors = np.array(list(colors)) - color_types = list(map(Color, set([c.get_hex_l() for c in colors]))) - type_to_count = dict([ - (color, sum(colors == color)) - for color in color_types - ]) - for i1, i2 in it.combinations(list(range(1, len(colors)-1)), 2): - bools = [ - sum(colors[i1:i2] == color) == type_to_count[color]/2 - for color in color_types - ] - if np.all(bools): - return i1, i2 - raise Exception("No fair division found") - - def demonstrate_fair_division(self): - segments, tick_marks = self.necklace - color_types = list(map(Color, set([ - segment.get_color().get_hex_l() - for segment in segments - ]))) - top_segments = VGroup(*it.chain( - self.groups[0][0], - self.groups[2][0], - )) - bottom_segments = self.groups[1][0] - for color in color_types: - monochrome_groups = [ - VGroup(*[segment for segment in segment_group if segment.get_color() == color]) - for segment_group in (top_segments, bottom_segments) - ] - labels = VGroup() - for i, group in enumerate(monochrome_groups): - group.save_state() - group.generate_target() - group.target.arrange(buff = SMALL_BUFF) - brace = Brace(group.target, UP) - label = VGroup( - TextMobject("Thief %d"%(i+1)), - Jewel(color = group[0].get_color()) - ) - label.arrange() - label.next_to(brace, UP) - full_group = VGroup(group.target, brace, label) - vect = LEFT if i == 0 else RIGHT - full_group.next_to(ORIGIN, vect, buff = MED_LARGE_BUFF) - full_group.to_edge(UP) - labels.add(VGroup(brace, label)) - equals = TexMobject("=") - equals.next_to(monochrome_groups[0].target, RIGHT) - labels[-1].add(equals) - - for group, label in zip(monochrome_groups, labels): - self.play( - MoveToTarget(group), - FadeIn(label), - ) - self.wait() - self.play( - FadeOut(labels), - *[group.restore for group in monochrome_groups] - ) - self.wait() - - def perform_antipodal_swap(self): - binary_choices_list = [(1, 0, 1), (0, 1, 0)] - for binary_choices in binary_choices_list: - self.play(*[ - ApplyMethod( - group.move_to, - group.target_points[choice] - ) - for group, choice in zip(self.groups, binary_choices) - ]) - self.wait() - -class ExclaimBorsukUlam(TeacherStudentsScene): - def construct(self): - self.student_says( - "Borsuk-Ulam!", - target_mode = "hooray" - ) - self.play(*[ - ApplyMethod(pi.change_mode, "hooray") - for pi in self.get_pi_creatures() - ]) - self.wait(3) - -class ShowFunctionDiagram(TotalLengthOfEachJewelEquals, ReconfigurableScene): - CONFIG = { - "necklace_center" : ORIGIN, - "camera_class" : ThreeDCamera, - "thief_box_offset" : 0.3, - "make_up_fair_division_indices" : False, - } - def construct(self): - self.add_necklace() - self.add_number_pair() - self.swap_necklace_allocation() - self.add_sphere_arrow() - - def add_necklace(self): - random.seed(self.random_seed) - ChoicesInNecklaceCutting.add_necklace(self) - self.necklace.set_width(FRAME_X_RADIUS-1) - self.necklace.to_edge(UP, buff = LARGE_BUFF) - self.necklace.to_edge(LEFT, buff = SMALL_BUFF) - self.add(self.necklace) - - self.find_fair_division() - - def add_number_pair(self): - plane_classes = [ - JewelPairPlane( - skip_animations = True, - thief_number = x - ) - for x in (1, 2) - ] - t1_plane, t2_plane = planes = VGroup(*[ - VGroup(*plane_class.get_top_level_mobjects()) - for plane_class in plane_classes - ]) - planes.set_width(FRAME_X_RADIUS) - planes.to_edge(RIGHT) - self.example_coords = plane_classes[0].example_coords[0] - - arrow = Arrow( - self.necklace.get_corner(DOWN+RIGHT), - self.example_coords, - color = YELLOW - ) - - self.play(ShowCreation(arrow)) - self.play(Write(t1_plane), Animation(arrow)) - self.wait() - clean_state = VGroup(*self.mobjects).family_members_with_points() - self.clear() - self.add(*clean_state) - self.transition_to_alt_config( - make_up_fair_division_indices = True - ) - self.wait() - t1_plane.save_state() - self.play( - Transform(*planes, path_arc = np.pi), - Animation(arrow) - ) - self.wait(2) - self.play( - ApplyMethod(t1_plane.restore, path_arc = np.pi), - Animation(arrow) - ) - self.wait() - - def swap_necklace_allocation(self): - for choices in [(1, 0, 1), (0, 1, 0)]: - self.play(*[ - ApplyMethod(group.move_to, group.target_points[i]) - for group, i in zip(self.groups, choices) - ]) - self.wait() - - def add_sphere_arrow(self): - up_down_arrow = TexMobject("\\updownarrow") - up_down_arrow.scale(1.5) - up_down_arrow.set_color(YELLOW) - up_down_arrow.next_to(self.necklace, DOWN, buff = LARGE_BUFF) - - to_plane_arrow = Arrow( - up_down_arrow.get_bottom() + DOWN+RIGHT, - self.example_coords, - color = YELLOW - ) - - self.play(Write(up_down_arrow)) - self.wait() - self.play(ShowCreation(to_plane_arrow)) - self.wait() - - def get_fair_division_indices(self, *args): - if self.make_up_fair_division_indices: - return [9, 14] - else: - return TotalLengthOfEachJewelEquals.get_fair_division_indices(self, *args) - -class JewelPairPlane(GraphScene): - CONFIG = { - "camera_class" : ThreeDCamera, - "x_labeled_nums" : [], - "y_labeled_nums" : [], - "thief_number" : 1, - "colors" : [BLUE, GREEN], - } - def construct(self): - self.setup_axes() - point = self.coords_to_point(4, 5) - dot = Dot(point, color = WHITE) - coord_pair = TexMobject( - "\\big(", - "\\text{Thief %d }"%self.thief_number, "X", ",", - "\\text{Thief %d }"%self.thief_number, "X", - "\\big)" - ) - # coord_pair.scale(1.5) - to_replace = [coord_pair[i] for i in [2, 5]] - for mob, color in zip(to_replace, self.colors): - jewel = Jewel(color = color) - jewel.replace(mob) - coord_pair.remove(mob) - coord_pair.add(jewel) - coord_pair.next_to(dot, UP+RIGHT, buff = 0) - - self.example_coords = VGroup(dot, coord_pair) - self.add(self.example_coords) - -class WhatThisMappingActuallyLooksLikeWords(Scene): - def construct(self): - words = TextMobject("What this mapping actually looks like") - words.set_width(FRAME_WIDTH-1) - words.to_edge(DOWN) - - self.play(Write(words)) - self.wait() - -class WhatAboutGeneralCase(TeacherStudentsScene): - def construct(self): - self.student_says(""" - What about when - there's more than 2 jewels? - """) - self.change_student_modes("confused", None, "sassy") - self.wait() - self.play(self.get_teacher().change_mode, "thinking") - self.wait() - self.teacher_says( - """Use Borsuk-Ulam for - higher-dimensional spheres """, - target_mode = "hooray" - ) - self.change_student_modes(*["confused"]*3) - self.wait(2) - -class Simple3DSpace(ExternallyAnimatedScene): - pass - -class FourDBorsukUlam(GeneralizeBorsukUlam, PiCreatureScene): - CONFIG = { - "n_dims" : 4, - "use_morty" : False, - } - def setup(self): - GeneralizeBorsukUlam.setup(self) - PiCreatureScene.setup(self) - self.pi_creature.to_corner(DOWN+LEFT, buff = MED_SMALL_BUFF) - - def construct(self): - sphere_set = self.get_sphere_set() - arrow = Arrow(LEFT, RIGHT) - f = TexMobject("f") - output_space = self.get_output_space() - equation = self.get_equation() - - sphere_set.to_corner(UP+LEFT) - arrow.next_to(sphere_set, RIGHT) - f.next_to(arrow, UP) - output_space.next_to(arrow, RIGHT) - equation.next_to(sphere_set, DOWN, buff = LARGE_BUFF) - equation.to_edge(RIGHT) - lhs = VGroup(*equation[:2]) - eq = equation[2] - rhs = VGroup(*equation[3:]) - - brace = Brace(Line(ORIGIN, 5*RIGHT)) - brace.to_edge(RIGHT) - brace_text = brace.get_text("Triplets of numbers") - brace_text.shift_onto_screen() - - self.play(FadeIn(sphere_set)) - self.change_mode("confused") - self.wait() - self.play( - ShowCreation(arrow), - Write(f) - ) - self.play(Write(output_space)) - self.wait() - self.change_mode("maybe") - self.wait(2) - self.change_mode("pondering") - self.wait() - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait() - self.play(*list(map(FadeOut, [brace, brace_text]))) - self.wait() - self.play( - FadeIn(lhs), - self.pi_creature.change_mode, "raise_right_hand" - ) - self.play( - ReplacementTransform(lhs.copy(), rhs), - Write(eq) - ) - self.wait(2) - - def get_sphere_set(self): - sphere_set = GeneralizeBorsukUlam.get_sphere_set(self) - brace = Brace(sphere_set) - text = brace.get_text("Hypersphere in 4D") - sphere_set.add(brace, text) - return sphere_set - -class CircleToSphereToQMarks(Scene): - def construct(self): - pi_groups = VGroup() - modes = ["happy", "pondering", "pleading"] - shapes = [ - Circle(color = BLUE, radius = 0.5), - VectorizedPoint(), - TexMobject("???") - ] - for d, mode, shape in zip(it.count(2), modes, shapes): - randy = Randolph(mode = mode) - randy.scale(0.7) - bubble = randy.get_bubble( - height = 3, width = 4, - direction = LEFT - ) - bubble.pin_to(randy) - bubble.position_mobject_inside(shape) - title = TextMobject("%dD"%d) - title.next_to(randy, UP) - arrow = Arrow(LEFT, RIGHT) - arrow.next_to(randy.get_corner(UP+RIGHT)) - pi_groups.add(VGroup( - randy, bubble, shape, title, arrow - )) - - pi_groups[-1].remove(pi_groups[-1][-1]) - pi_groups.arrange(buff = -1) - for mob in pi_groups: - self.play(FadeIn(mob)) - self.wait(2) - self.play(pi_groups[-1][0].change_mode, "thinking") - self.wait(2) - -class BorsukPatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "Meshal Alshammari", - "CrypticSwarm ", - "Ankit Agarwal", - "Yu Jun", - "Shelby Doolittle", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Justin Helps", - "Markus Persson", - "Dan Buchoff", - "Derek Dai", - "Joseph John Cox", - "Luc Ritchie", - "Guido Gambardella", - "Jerry Ling", - "Mark Govea", - "Vecht", - "Jonathan Eppele", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Kirk Werklund", - "Ripta Pasay", - "Felipe Diniz", - ] - } - -class MortyLookingAtRectangle(Scene): - def construct(self): - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - url = TextMobject("www.thegreatcoursesplus.com/3blue1brown") - url.scale(0.75) - url.to_corner(UP+LEFT) - rect = Rectangle(height = 9, width = 16) - rect.set_height(5) - rect.next_to(url, DOWN) - rect.shift_onto_screen() - url.save_state() - url.next_to(morty.get_corner(UP+LEFT), UP) - url.shift_onto_screen() - - self.add(morty) - self.play( - morty.change_mode, "raise_right_hand", - morty.look_at, url, - ) - self.play(Write(url)) - self.play(Blink(morty)) - self.wait() - self.play( - url.restore, - morty.change_mode, "happy" - ) - self.play(ShowCreation(rect)) - self.wait() - self.play(Blink(morty)) - for mode in ["pondering", "hooray", "happy", "pondering", "happy"]: - self.play(morty.change_mode, mode) - self.wait(2) - self.play(Blink(morty)) - self.wait(2) - -class RotatingThreeDSphereProjection(Scene): - CONFIG = { - "camera_class" : ThreeDCamera, - } - def construct(self): - sphere = VGroup(*[ - Circle(radius = np.sin(t)).shift(np.cos(t)*OUT) - for t in np.linspace(0, np.pi, 20) - ]) - sphere.set_stroke(BLUE, width = 2) - # sphere.set_fill(BLUE, opacity = 0.1) - - self.play(Rotating( - sphere, axis = RIGHT+OUT, - run_time = 10 - )) - self.repeat_frames(4) - -class FourDSphereProjectTo4D(ExternallyAnimatedScene): - pass - - -class Test(Scene): - CONFIG = { - "camera_class" : ThreeDCamera, - } - def construct(self): - randy = Randolph() - necklace = Necklace() - necklace.insert_n_curves(20) - # necklace.apply_function( - # lambda (x, y, z) : x*RIGHT + (y + 0.1*x**2)*UP - # ) - necklace.set_width(randy.get_width() + 1) - necklace.move_to(randy) - - self.add(randy, necklace) - - - - - - - - - - - - - diff --git a/from_3b1b/old/borsuk_addition.py b/from_3b1b/old/borsuk_addition.py deleted file mode 100644 index 0c59f3ca..00000000 --- a/from_3b1b/old/borsuk_addition.py +++ /dev/null @@ -1,1236 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.lost_lecture import GeometryProofLand -from from_3b1b.old.quaternions import SpecialThreeDScene -from from_3b1b.old.uncertainty import Flash - - -class Introduction(TeacherStudentsScene): - CONFIG = { - "random_seed": 2, - } - - def construct(self): - self.play( - Animation(VectorizedPoint(self.hold_up_spot)), - self.teacher.change, "raise_right_hand", - ) - self.change_student_modes( - "angry", "sassy", "pleading" - ) - self.wait() - - movements = [] - for student in self.students: - student.center_tracker = VectorizedPoint() - student.center_tracker.move_to(student) - student.center_tracker.save_state() - student.add_updater( - lambda m: m.move_to(m.center_tracker) - ) - always_shift( - student.center_tracker, - direction=DOWN + 3 * LEFT, - rate=1.5 * random.random() - ) - movements.append(student.center_tracker) - self.add(*movements) - self.change_student_modes( - "pondering", "sad", "concerned_musician", - look_at_arg=10 * LEFT + 2 * DOWN - ) - self.teacher_says( - "Wait, wait, wait!", - target_mode="surprised" - ) - self.remove(*movements) - self.play( - self.get_student_changes(*3 * ["hesitant"]), - *[ - Restore(student.center_tracker) - for student in self.students - ] - ) - - -class StudentsWatching(TeacherStudentsScene): - def construct(self): - self.play( - self.teacher.change, "raise_right_hand", - self.get_student_changes( - *3 * ["thinking"], - look_at_arg=self.screen - ), - VFadeIn(self.pi_creatures, run_time=2) - ) - self.wait(5) - - -class UnexpectedConnection(Scene): - def construct(self): - primes = TexMobject( - "2,", "3,", "5,", "7,", "11,", "13,", "17,", "\\dots" - ) - primes.move_to(2.5 * UP) - - circle = Circle( - color=YELLOW, - stroke_width=1, - radius=1.5, - ) - circle.shift(1.5 * DOWN) - center = circle.get_center() - center_dot = Dot(center) - radius = Line(center, circle.get_right()) - radius.set_stroke(WHITE, 3) - - arrow = DoubleArrow(primes, circle) - arrow.tip[1].shift(SMALL_BUFF * UP) - arrow.save_state() - arrow.rotate(90 * DEGREES) - arrow.scale(1.5) - arrow.fade(1) - - formula = TexMobject( - "\\frac{\\pi^2}{6} = \\prod_{p \\text{ prime}}" - "\\frac{1}{1 - p^{-2}}" - ) - formula.next_to(arrow.get_center(), RIGHT) - - def get_arc(): - angle = radius.get_angle() - return Arc( - start_angle=0, - angle=angle, - radius=circle.radius, - stroke_color=YELLOW, - stroke_width=5 - ).shift(center) - - arc = always_redraw(get_arc) - - decimal = DecimalNumber(0) - decimal.add_updater( - lambda d: d.move_to(interpolate( - radius.get_start(), - radius.get_end(), - 1.5, - )), - ) - decimal.add_updater( - lambda d: d.set_value(radius.get_angle()) - ) - pi = TexMobject("\\pi") - pi.scale(2) - pi.next_to(circle, LEFT) - - self.add(circle, radius, center_dot, decimal, arc) - self.play( - Rotate(radius, PI - 1e-7, about_point=center), - LaggedStartMap(FadeInFromDown, primes), - run_time=4 - ) - self.remove(decimal) - self.add(pi) - self.wait() - self.play( - Restore(arrow), - FadeInFrom(formula, LEFT) - ) - self.wait() - - -class MapOfVideo(MovingCameraScene): - def construct(self): - images = Group( - ImageMobject("NecklaceThumbnail"), - ImageMobject("BorsukUlamThumbnail"), - ImageMobject("TopologyProofThumbnail"), - ImageMobject("ContinuousNecklaceThumbnail"), - ImageMobject("NecklaceSphereAssociationThumbnail") - ) - for image in images: - rect = SurroundingRectangle(image, buff=0) - rect.set_stroke(WHITE, 3) - image.add(rect) - - image_line = Group(*images[:2], *images[3:]) - image_line.arrange(RIGHT, buff=LARGE_BUFF) - images[2].next_to(image_line, DOWN, buff=1.5) - images.set_width(FRAME_WIDTH - 1) - images.to_edge(UP, buff=LARGE_BUFF) - - arrows = VGroup( - Arrow(images[0], images[1], buff=SMALL_BUFF), - Arrow( - images[1].get_corner(DR) + 0.5 * LEFT, - images[2].get_top() + 0.5 * LEFT, - ), - Arrow( - images[2].get_top() + 0.5 * RIGHT, - images[3].get_corner(DL) + 0.5 * RIGHT, - ), - Arrow(images[3], images[4], buff=SMALL_BUFF), - ) - - self.play(LaggedStartMap(FadeInFromDown, images, run_time=4)) - self.play(LaggedStartMap(GrowArrow, arrows)) - self.wait() - group = Group(images, arrows) - for image in images: - group.save_state() - group.generate_target() - group.target.shift(-image.get_center()) - group.target.scale( - FRAME_WIDTH / image.get_width(), - about_point=ORIGIN, - ) - - self.play(MoveToTarget(group, run_time=3)) - self.wait() - self.play(Restore(group, run_time=3)) - - def get_curved_arrow(self, *points): - line = VMobject() - line.set_points(points) - tip = Arrow(points[-2], points[-1], buff=SMALL_BUFF).tip - line.pointwise_become_partial(line, 0, 0.9) - line.add(tip) - return line - - -class MathIsDeep(PiCreatureScene): - def construct(self): - words = TextMobject( - "Math", "is", "deep" - ) - words.scale(2) - words.to_edge(UP) - math = words[0].copy() - math[1].remove(math[1][1]) - math.set_fill(opacity=0) - math.set_stroke(width=0, background=True) - numbers = [13, 1, 20, 8] - num_mobs = VGroup(*[Integer(d) for d in numbers]) - num_mobs.arrange(RIGHT, buff=MED_LARGE_BUFF) - num_mobs.next_to(math, DOWN, buff=1.5) - num_mobs.set_color(YELLOW) - top_arrows = VGroup(*[ - Arrow(c.get_bottom(), n.get_top()) - for c, n in zip(math, num_mobs) - ]) - n_sum = Integer(sum(numbers)) - n_sum.scale(1.5) - n_sum.next_to(num_mobs, DOWN, buff=1.5) - low_arrows = VGroup(*[ - Arrow(n.get_bottom(), n_sum.get_top()) - for n in num_mobs - ]) - VGroup(top_arrows, low_arrows).set_color(WHITE) - - n_sum_border = n_sum.deepcopy() - n_sum_border.set_fill(opacity=0) - n_sum_border.set_stroke(YELLOW, width=1) - n_sum_border.set_stroke(width=0, background=True) - - # pre_num_mobs = num_mobs.copy() - # for pn, letter in zip(pre_num_mobs, math): - # pn.fade(1) - # pn.set_color(RED) - # pn.move_to(letter) - # num_mobs[1].add_subpath(num_mobs[1].points) - - self.play( - LaggedStartMap( - FadeInFromLarge, words, - scale_factor=1.5, - run_time=0.6, - lag_ratio=0.6, - ), - self.pi_creature.change, "pondering" - ) - self.play( - TransformFromCopy(math, num_mobs), - *map(GrowArrow, top_arrows), - ) - self.wait() - self.play( - TransformFromCopy(num_mobs, VGroup(n_sum)), - self.pi_creature.change, "thinking", - *map(GrowArrow, low_arrows), - ) - self.play(LaggedStartMap(ShowCreationThenDestruction, n_sum_border)) - self.play(Blink(self.pi_creature)) - self.wait() - - -class MinimizeSharding(Scene): - def construct(self): - piece_groups = VGroup(*[ - VGroup(*[ - self.get_piece() - for x in range(3) - ]).arrange(RIGHT, buff=SMALL_BUFF) - for y in range(4) - ]).arrange(RIGHT, buff=SMALL_BUFF) - - self.add(piece_groups) - self.play(*[ - ApplyMethod(mob.space_out_submobjects, 0.7) - for mob in piece_groups - ]) - self.wait() - group1 = piece_groups[:2] - group2 = piece_groups[2:] - self.play( - group1.arrange, DOWN, - group1.next_to, ORIGIN, LEFT, LARGE_BUFF, - group2.arrange, DOWN, - group2.next_to, ORIGIN, RIGHT, LARGE_BUFF, - ) - self.wait() - - def get_piece(self): - jagged_spots = [ - ORIGIN, 2 * UP + RIGHT, 4 * UP + LEFT, 6 * UP, - ] - corners = list(it.chain( - jagged_spots, - [6 * UP + 10 * RIGHT], - [ - p + 10 * RIGHT - for p in reversed(jagged_spots) - ], - [ORIGIN] - )) - piece = VMobject().set_points_as_corners(corners) - piece.set_width(1) - piece.center() - piece.set_stroke(WHITE, width=0.5) - piece.set_fill(BLUE, opacity=1) - return piece - - -class Antipodes(Scene): - def construct(self): - word = TextMobject("``Antipodes''") - word.set_width(FRAME_WIDTH - 1) - word.set_color(MAROON_B) - self.play(Write(word)) - self.wait() - - -class TopologyWordBreak(Scene): - def construct(self): - word = TextMobject("Topology") - word.scale(2) - colors = [BLUE, YELLOW, RED] - classes = VGroup(*[VGroup() for x in range(3)]) - for letter in word: - genus = len(letter.submobjects) - letter.target_color = colors[genus] - letter.generate_target() - letter.target.set_color(colors[genus]) - classes[genus].add(letter.target) - signs = VGroup() - for group in classes: - new_group = VGroup() - for elem in group[:-1]: - new_group.add(elem) - sign = TexMobject("\\simeq") - new_group.add(sign) - signs.add(sign) - new_group.add(group[-1]) - group.submobjects = list(new_group.submobjects) - group.arrange(RIGHT) - - word[2].target.shift(0.1 * DOWN) - word[7].target.shift(0.1 * DOWN) - - classes.arrange(DOWN, buff=LARGE_BUFF, aligned_edge=LEFT) - classes.shift(2 * RIGHT) - - genus_labels = VGroup(*[ - TextMobject("Genus %d:" % d).scale(1.5).next_to( - classes[d], LEFT, MED_LARGE_BUFF - ) - for d in range(3) - ]) - genus_labels.shift(SMALL_BUFF * UP) - - self.play(Write(word)) - self.play(LaggedStartMap( - ApplyMethod, word, - lambda m: (m.set_color, m.target_color), - run_time=1 - )) - self.play( - LaggedStartMap(MoveToTarget, word), - LaggedStartMap(FadeIn, signs), - LaggedStartMap(FadeInFromDown, genus_labels), - ) - self.wait(3) - - -class TopologyProofLand(GeometryProofLand): - CONFIG = { - "text": "Topology proof land" - } - - -class GreenLine(Scene): - def construct(self): - self.add(Line(LEFT, RIGHT, color=GREEN)) - - -class Thief(Scene): - def construct(self): - self.play(Write(TextMobject("Thief"))) - self.wait() - - -class FunctionGInSymbols(Scene): - def construct(self): - p_tex = "\\vec{\\textbf{p}}" - neg_p_tex = "-\\vec{\\textbf{p}}" - - def color_tex(tex_mob): - pairs = [ - (p_tex, YELLOW), - (neg_p_tex, RED), - ("{g}", GREEN), - ] - for tex, color in pairs: - tex_mob.set_color_by_tex( - tex, color, substring=False - ) - - f_of_p = TexMobject("f", "(", p_tex, ")") - f_of_p.shift(2.5 * LEFT + 2.5 * UP) - f_of_neg_p = TexMobject("f", "(", neg_p_tex, ")") - g_of_p = TexMobject("g", "(", p_tex, ")") - g_of_p[0].set_color(YELLOW) - for mob in f_of_p, f_of_neg_p, g_of_p: - color_tex(mob) - dec_rhs = DecimalMatrix([[-0.9], [0.5]]) - dec_rhs.next_to(f_of_p, RIGHT) - minus = TexMobject("-") - equals = TexMobject("=") - equals.next_to(f_of_p, RIGHT) - zero_zero = IntegerMatrix([[0], [0]]) - - for matrix in dec_rhs, zero_zero: - matrix.space_out_submobjects(0.8) - matrix.brackets.scale(0.9) - matrix.next_to(equals, RIGHT) - - f_of_neg_p.next_to(equals, RIGHT) - - f = f_of_p.get_part_by_tex("f") - p = f_of_p.get_part_by_tex(p_tex) - f_brace = Brace(f, UP, buff=SMALL_BUFF) - f_brace.add(f_brace.get_text("Continuous function")) - p_brace = Brace(p, DOWN, buff=SMALL_BUFF) - p_brace.add(p_brace.get_text("Sphere point").match_color(p)) - - f_of_p.save_state() - f_of_p.space_out_submobjects(2) - f_of_p.scale(2) - f_of_p.fade(1) - - self.play(f_of_p.restore) - self.play(GrowFromCenter(f_brace)) - self.wait() - self.play(GrowFromCenter(p_brace)) - self.wait() - self.play( - FadeInFromDown(equals), - Write(dec_rhs), - FadeOut(f_brace), - FadeOut(p_brace), - ) - self.wait(2) - self.play(WiggleOutThenIn(f)) - self.wait() - self.play( - FadeOutAndShift(dec_rhs, DOWN), - FadeInFromDown(f_of_neg_p) - ) - self.wait() - - # Rearrange - f_of_neg_p.generate_target() - f_of_p.generate_target() - group = VGroup(f_of_p.target, minus, f_of_neg_p.target) - group.arrange(RIGHT, buff=SMALL_BUFF) - group.next_to(equals, LEFT) - - self.play( - MoveToTarget(f_of_p, path_arc=PI), - MoveToTarget(f_of_neg_p, path_arc=-PI), - FadeInFromLarge(minus), - FadeInFrom(zero_zero, LEFT) - ) - self.wait() - - # Define g - def_eq = TexMobject(":=") - def_eq.next_to(f_of_p, LEFT) - g_of_p.next_to(def_eq, LEFT) - rect = SurroundingRectangle(VGroup(g_of_p, f_of_neg_p)) - rect.set_stroke(width=1) - seeking_text = TexMobject( - "\\text{Looking for }", p_tex, "\\text{ where}" - ) - color_tex(seeking_text) - seeking_text.next_to(zero_zero, DOWN, MED_LARGE_BUFF) - seeking_text.to_edge(LEFT) - g_equals_zero = VGroup( - g_of_p.copy(), equals, zero_zero - ) - g_equals_zero.generate_target() - g_equals_zero.target.arrange(RIGHT, SMALL_BUFF) - g_equals_zero.target.next_to(seeking_text, DOWN) - - self.play( - FadeInFromLarge(g_of_p), - FadeInFrom(def_eq, LEFT) - ) - self.play( - FadeInFromDown(seeking_text), - MoveToTarget(g_equals_zero) - ) - self.play(ShowCreation(rect)) - self.wait() - self.play(FadeOut(rect)) - - # Show g is odd - g_of_neg_p = TexMobject("{g}", "(", neg_p_tex, ")") - eq2 = TexMobject("=") - rhs = TexMobject( - "f", "(", neg_p_tex, ")", "-", - "f", "(", p_tex, ")", "=", - "-", "{g}", "(", p_tex, ")", - ) - for mob in g_of_neg_p, rhs: - color_tex(mob) - g_of_neg_p.next_to(g_of_p, DOWN, aligned_edge=LEFT, buff=LARGE_BUFF) - eq2.next_to(g_of_neg_p, RIGHT, SMALL_BUFF) - rhs.next_to(eq2, RIGHT, SMALL_BUFF) - neg_g_of_p = rhs[-5:] - neg_g_of_p.save_state() - neg_g_of_p.next_to(eq2, RIGHT, SMALL_BUFF) - - self.play( - FadeIn(g_of_neg_p), - FadeIn(eq2), - FadeIn(neg_g_of_p), - VGroup(seeking_text, g_equals_zero).shift, 1.5 * DOWN - ) - self.wait() - self.play(ShowCreationThenFadeAround(g_of_neg_p[2])) - self.wait() - self.play(ShowCreationThenFadeAround(neg_g_of_p)) - self.wait() - self.play(neg_g_of_p.restore) - rects = VGroup(*map(SurroundingRectangle, [f_of_p, f_of_neg_p])) - self.play(LaggedStartMap( - ShowCreationThenDestruction, rects, - lag_ratio=0.8 - )) - self.play( - TransformFromCopy(f_of_p, rhs[5:9]), - TransformFromCopy(f_of_neg_p, rhs[:4]), - FadeIn(rhs[4]), - FadeIn(rhs[-6]), - ) - self.wait() - - -class FunctionGInputSpace(SpecialThreeDScene): - def setup(self): - self.init_tracked_point() - - sphere = self.get_sphere() - sphere.set_fill(BLUE_E, opacity=0.5) - self.sphere = sphere - - self.set_camera_orientation( - phi=70 * DEGREES, - theta=-120 * DEGREES, - ) - self.begin_ambient_camera_rotation(rate=0.02) - - self.init_dot() - - self.add(ThreeDAxes()) - - def construct(self): - self.show_input_dot() - self.show_start_path() - self.show_antipodal_point() - self.show_equator() - self.deform_towards_north_pole() - - def show_input_dot(self): - sphere = self.sphere - dot = self.dot - point_mob = self.tracked_point - start_point = self.get_start_point() - - arrow = Arrow( - start_point + (LEFT + OUT + UP), start_point, - color=BLUE, - buff=MED_LARGE_BUFF, - ) - arrow.rotate(90 * DEGREES, axis=arrow.get_vector()) - arrow.add_to_back(arrow.copy().set_stroke(BLACK, 5)) - - p_label = self.p_label = TexMobject("\\vec{\\textbf{p}}") - p_label.set_color(YELLOW) - p_label.next_to(arrow.get_start(), OUT, buff=0.3) - p_label.set_shade_in_3d(True) - - self.play(Write(sphere, run_time=3)) - self.add(dot) - self.add_fixed_orientation_mobjects(p_label) - self.play( - point_mob.move_to, start_point, - GrowArrow(arrow), - FadeInFrom(p_label, IN) - ) - self.wait() - self.play( - arrow.scale, 0, {"about_point": arrow.get_end()}, - p_label.next_to, dot, OUT + LEFT, SMALL_BUFF - ) - p_label.add_updater(lambda p: p.next_to(dot, OUT + LEFT, SMALL_BUFF)) - self.wait(4) - - def show_start_path(self): - path = self.get_start_path() - self.draw_path(path, uncreate=True) - self.wait() - - def show_antipodal_point(self): - path = self.get_antipodal_path() - end_dot = always_redraw( - lambda: self.get_dot( - path[-1].point_from_proportion(1) - ).set_color(RED) - ) - - neg_p = TexMobject("-\\vec{\\textbf{p}}") - neg_p.add_updater( - lambda p: p.next_to(end_dot, UP + RIGHT + IN) - ) - neg_p.set_color(RED) - neg_p.set_shade_in_3d(True) - - self.move_camera( - phi=100 * DEGREES, - theta=30 * DEGREES, - added_anims=[ShowCreation(path)], - run_time=4, - ) - self.wait() - self.add_fixed_orientation_mobjects(neg_p) - self.play( - FadeInFromLarge(end_dot), - Write(neg_p) - ) - self.wait(4) - self.move_camera( - phi=70 * DEGREES, - theta=-120 * DEGREES, - run_time=2 - ) - self.wait(7) - # Flip - self.move_camera( - phi=100 * DEGREES, - theta=30 * DEGREES, - run_time=2, - ) - self.wait(7) - self.move_camera( - phi=70 * DEGREES, - theta=-120 * DEGREES, - added_anims=[ - FadeOut(end_dot), - FadeOut(neg_p), - FadeOut(path), - ], - run_time=2, - ) - - def show_equator(self): - point_mob = self.tracked_point - equator = self.get_lat_line() - - self.play(point_mob.move_to, equator[0].point_from_proportion(0)) - self.play(ShowCreation(equator, run_time=4)) - for x in range(2): - self.play( - Rotate(point_mob, PI, about_point=ORIGIN, axis=OUT), - run_time=4 - ) - self.wait(3) - self.play( - FadeOut(self.dot), - FadeOut(self.p_label), - ) - - self.equator = equator - - def deform_towards_north_pole(self): - equator = self.equator - - self.play(UpdateFromAlphaFunc( - equator, - lambda m, a: m.become(self.get_lat_line(a * PI / 2)), - run_time=16 - )) - self.wait() - - # - def init_tracked_point(self): - self.tracked_point = VectorizedPoint([0, 0, 2]) - self.tracked_point.add_updater( - lambda p: p.move_to(2 * normalize(p.get_center())) - ) - self.add(self.tracked_point) - - def init_dot(self): - self.dot = always_redraw( - lambda: self.get_dot(self.tracked_point.get_center()) - ) - - def get_start_path(self): - path = ParametricFunction( - lambda t: np.array([ - -np.sin(TAU * t + TAU / 4), - np.cos(2 * TAU * t + TAU / 4), - 0 - ]), - color=RED - ) - path.scale(0.5) - path.shift(0.5 * OUT) - path.rotate(60 * DEGREES, RIGHT, about_point=ORIGIN) - path.shift( - self.get_start_point() - path.point_from_proportion(0) - ) - path.apply_function(lambda p: 2 * normalize(p)) - return path - - def get_antipodal_path(self): - start = self.get_start_point() - path = ParametricFunction( - lambda t: 2.03 * np.array([ - 0, - np.sin(PI * t), - np.cos(PI * t), - ]), - color=YELLOW - ) - path.apply_matrix(z_to_vector(start)) - - dashed_path = DashedVMobject(path) - dashed_path.set_shade_in_3d(True) - - return dashed_path - - def get_lat_line(self, lat=0): - equator = ParametricFunction(lambda t: 2.03 * np.array([ - np.cos(lat) * np.sin(TAU * t), - np.cos(lat) * (-np.cos(TAU * t)), - np.sin(lat) - ])) - equator.rotate(-90 * DEGREES) - dashed_equator = DashedVMobject( - equator, - num_dashes=40, - color=RED, - ) - dashed_equator.set_shade_in_3d(True) - return dashed_equator - - def draw_path(self, path, - run_time=4, - dot_follow=True, - uncreate=False, - added_anims=None - ): - added_anims = added_anims or [] - point_mob = self.tracked_point - anims = [ShowCreation(path)] - if dot_follow: - anims.append(UpdateFromFunc( - point_mob, - lambda p: p.move_to(path.point_from_proportion(1)) - )) - self.add(path, self.dot) - self.play(*anims, run_time=run_time) - - if uncreate: - self.wait() - self.play( - Uncreate(path), - run_time=run_time - ) - - def modify_path(self, path): - return path - - def get_start_point(self): - return 2 * normalize([-1, -1, 1]) - - def get_dot(self, point): - dot = Dot(color=WHITE) - dot.shift(2.05 * OUT) - dot.apply_matrix(z_to_vector(normalize(point))) - dot.set_shade_in_3d(True) - return dot - - -class FunctionGOutputSpace(FunctionGInputSpace): - def construct(self): - self.show_input_dot() - self.show_start_path() - self.show_antipodal_point() - self.show_equator() - self.deform_towards_north_pole() - - def setup(self): - axes = self.axes = Axes( - x_min=-2.5, - x_max=2.5, - y_min=-2.5, - y_max=2.5, - axis_config={'unit_size': 1.5} - ) - for axis in axes: - numbers = list(range(-2, 3)) - numbers.remove(0) - axis.add_numbers(*numbers) - - self.init_tracked_point() - self.init_dot() - - def show_input_dot(self): - axes = self.axes - dot = self.dot - point_mob = self.tracked_point - - point_mob.move_to(self.get_start_point()) - self.add(dot) - self.update_mobjects(0) - self.remove(dot) - - p_tex = "\\vec{\\textbf{p}}" - fp_label = self.fp_label = TexMobject("f(", p_tex, ")") - fp_label.set_color_by_tex(p_tex, YELLOW) - - self.play(Write(axes, run_time=3)) - self.wait(3) - dc = dot.copy() - self.play( - FadeInFrom(dc, 2 * UP, remover=True), - UpdateFromFunc(fp_label, lambda fp: fp.next_to(dc, UL, SMALL_BUFF)) - ) - self.add(dot) - fp_label.add_updater( - lambda fp: fp.next_to(dot, UL, SMALL_BUFF) - ) - self.wait(2) - - def draw_path(self, path, - run_time=4, - dot_follow=True, - uncreate=False, - added_anims=None - ): - added_anims = added_anims or [] - point_mob = self.tracked_point - shadow_path = path.deepcopy().fade(1) - flat_path = self.modify_path(path) - anims = [ - ShowCreation(flat_path), - ShowCreation(shadow_path), - ] - if dot_follow: - anims.append(UpdateFromFunc( - point_mob, - lambda p: p.move_to(shadow_path.point_from_proportion(1)) - )) - self.add(flat_path, self.dot) - self.play(*anims, run_time=run_time) - - if uncreate: - self.wait() - self.remove(shadow_path) - self.play( - Uncreate(flat_path), - run_time=run_time - ) - - def show_antipodal_point(self): - dot = self.dot - pre_path = VMobject().set_points_smoothly([ - ORIGIN, DOWN, DOWN + 2 * RIGHT, - 3 * RIGHT + 0.5 * UP, 0.5 * RIGHT, ORIGIN - ]) - pre_path.rotate(-45 * DEGREES, about_point=ORIGIN) - pre_path.shift(dot.get_center()) - path = DashedVMobject(pre_path) - - fp_label = self.fp_label - equals = TexMobject("=") - equals.next_to(fp_label, RIGHT, SMALL_BUFF) - f_neg_p = TexMobject("f(", "-\\vec{\\textbf{p}}", ")") - f_neg_p[1].set_color(RED) - f_neg_p.next_to(equals, RIGHT) - - gp_label = TexMobject("g", "(", "\\vec{\\textbf{p}}", ")") - gp_label[0].set_color(GREEN) - gp_label[2].set_color(YELLOW) - gp_label.add_updater(lambda m: m.next_to(dot, UL, SMALL_BUFF)) - self.gp_label = gp_label - # gp_label.next_to(Dot(ORIGIN), UL, SMALL_BUFF) - - self.play(ShowCreation(path, run_time=4)) - self.wait() - self.play( - Write(equals), - Write(f_neg_p), - ) - self.wait(6) - self.play( - FadeOut(VGroup(path, equals, f_neg_p)) - ) - dot.clear_updaters() - self.add(fp_label, gp_label) - gp_label.set_background_stroke(width=0) - self.play( - dot.move_to, ORIGIN, - VFadeOut(fp_label), - VFadeIn(gp_label), - ) - self.wait(4) - self.play( - dot.move_to, self.odd_func(self.get_start_point()) - ) - # Flip, 2 second for flip, 7 seconds after - path = self.get_antipodal_path() - path.apply_function(self.odd_func) - end_dot = Dot(color=RED) - end_dot.move_to(path[-1].point_from_proportion(1)) - g_neg_p = TexMobject( - "g", "(", "-\\vec{\\textbf{p}}", ")" - ) - g_neg_p[0].set_color(GREEN) - g_neg_p[2].set_color(RED) - g_neg_p.next_to(end_dot, UR, SMALL_BUFF) - reflection_line = DashedLine( - dot.get_center(), end_dot.get_center(), - stroke_width=0, - ) - vector = Vector(dot.get_center()) - - self.play(ShowCreation(path, run_time=1)) - self.wait() - self.play( - ShowCreationThenDestruction(reflection_line, run_time=2), - TransformFromCopy(dot, end_dot), - ReplacementTransform( - gp_label.deepcopy().clear_updaters(), g_neg_p - ), - ) - self.wait() - self.play(FadeIn(vector)) - self.play(Rotate(vector, angle=PI, about_point=ORIGIN)) - self.play(FadeOut(vector)) - self.play( - FadeOut(end_dot), - FadeOut(g_neg_p), - FadeOut(path), - ) - - def show_equator(self): - dot = self.dot - point_mob = self.tracked_point - equator = self.get_lat_line() - flat_eq = equator.deepcopy().apply_function(self.odd_func) - equator.fade(1) - - equator_start = equator[0].point_from_proportion(0) - - # To address - self.play( - point_mob.move_to, equator_start, - dot.move_to, self.odd_func(equator_start) - ) - dot.add_updater(lambda m: m.move_to( - self.odd_func(point_mob.get_center()) - )) - self.play( - ShowCreation(equator), - ShowCreation(flat_eq), - run_time=4, - ) - for x in range(2): - self.play( - Rotate(point_mob, PI, about_point=ORIGIN, axis=OUT), - run_time=4 - ) - self.wait(3) - self.play( - FadeOut(self.dot), - FadeOut(self.gp_label), - ) - - self.equator = equator - self.flat_eq = flat_eq - - def deform_towards_north_pole(self): - equator = self.equator - flat_eq = self.flat_eq - - self.play( - UpdateFromAlphaFunc( - equator, - lambda m, a: m.become(self.get_lat_line(a * PI / 2)).set_stroke(width=0), - run_time=16 - ), - UpdateFromFunc( - flat_eq, - lambda m: m.become( - equator.deepcopy().apply_function(self.odd_func).set_stroke( - color=RED, width=3 - ) - ) - ) - ) - self.wait() - # - - def func(self, point): - x, y, z = point - return 0.5 * self.axes.coords_to_point( - 2 * x + 0.5 * y + z, - 2 * y - 0.5 * np.sin(PI * x) + z**2 + 1 - x, - ) - - def odd_func(self, point): - return (self.func(point) - self.func(-point)) / 2 - - def get_dot(self, point): - return Dot(self.func(point)) - - def modify_path(self, path): - path.apply_function(self.func) - return path - - -class RotationOfEquatorGraphInOuputSpace(FunctionGOutputSpace): - def construct(self): - self.add(self.axes) - equator = self.get_lat_line(0) - equator.remove(*equator[len(equator) // 2:]) - - flat_eq = equator.copy().apply_function(self.odd_func) - vector = Vector(flat_eq[0].point_from_proportion(0)) - vector_copy = vector.copy().fade(0.5) - - self.add(flat_eq) - self.add(flat_eq.copy()) - self.wait() - self.play(FadeIn(vector)) - self.add(vector_copy) - self.play( - Rotate( - VGroup(flat_eq, vector), - PI, about_point=ORIGIN, run_time=5 - )) - self.play(FadeOut(vector), FadeOut(vector_copy)) - - -class WriteInputSpace(Scene): - def construct(self): - self.play(Write(TextMobject("Input space"))) - self.wait() - - -class WriteOutputSpace(Scene): - def construct(self): - self.play(Write(TextMobject("Output space"))) - self.wait() - - -class LineScene(Scene): - def construct(self): - self.add(DashedLine(5 * LEFT, 5 * RIGHT, color=WHITE)) - - -class ShowFlash(Scene): - def construct(self): - dot = Dot(ORIGIN, color=YELLOW) - dot.set_stroke(width=0) - dot.set_fill(opacity=0) - self.play(Flash(dot, flash_radius=0.8, line_length=0.6, run_time=2)) - self.wait() - - -class WaitForIt(Scene): - def construct(self): - words = TextMobject("Wait for it", "$\\dots$", arg_separator="") - words.scale(2) - self.add(words[0]) - self.play(Write(words[1], run_time=3)) - self.wait() - - -class DrawSphere(SpecialThreeDScene): - def construct(self): - sphere = self.get_sphere() - sphere.shift(IN) - question = TextMobject("What \\emph{is} a sphere?") - question.set_width(FRAME_WIDTH - 3) - question.to_edge(UP) - self.move_camera(phi=70 * DEGREES, run_time=0) - self.begin_ambient_camera_rotation() - self.add_fixed_in_frame_mobjects(question) - self.play( - Write(sphere), - FadeInFromDown(question) - ) - self.wait(4) - - -class DivisionOfUnity(Scene): - def construct(self): - factor = 2 - line = Line(factor * LEFT, factor * RIGHT) - lower_brace = Brace(line, DOWN) - lower_brace.add(lower_brace.get_text("1")) - v_lines = VGroup(*[ - DashedLine(0.2 * UP, 0.2 * DOWN).shift(factor * v) - for v in [LEFT, 0.3 * LEFT, 0.1 * RIGHT, RIGHT] - ]) - upper_braces = VGroup(*[ - Brace(VGroup(vl1, vl2), UP) - for vl1, vl2 in zip(v_lines[:-1], v_lines[1:]) - ]) - colors = color_gradient([GREEN, BLUE], 3) - for i, color, brace in zip(it.count(1), colors, upper_braces): - label = brace.get_tex("x_%d^2" % i) - label.set_color(color) - brace.add(label) - - self.add(line, lower_brace) - self.play(LaggedStartMap( - ShowCreation, v_lines[1:3], - lag_ratio=0.8, - run_time=1 - )) - self.play(LaggedStartMap( - GrowFromCenter, upper_braces - )) - self.wait() - - -class ThreeDSpace(ThreeDScene): - def construct(self): - axes = ThreeDAxes() - self.add(axes) - self.set_camera_orientation(phi=70 * DEGREES, theta=-130 * DEGREES) - self.begin_ambient_camera_rotation() - - density = 1 - radius = 3 - lines = VGroup(*[ - VGroup(*[ - Line( - radius * IN, radius * OUT, - stroke_color=WHITE, - stroke_width=1, - stroke_opacity=0.5, - ).shift(x * RIGHT + y * UP) - for x in np.arange(-radius, radius + density, density) - for y in np.arange(-radius, radius + density, density) - ]).rotate(n * 120 * DEGREES, axis=[1, 1, 1]) - for n in range(3) - ]) - - self.play(Write(lines)) - self.wait(30) - - -class NecklaceSphereConnectionTitle(Scene): - def construct(self): - text = TextMobject("Necklace Sphere Association") - text.set_width(FRAME_WIDTH - 1) - self.add(text) - - -class BorsukEndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "Ali Yahya", - "Meshal Alshammari", - "Crypticswarm", - "Ankit Agarwal", - "Yu Jun", - "Shelby Doolittle", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Justin Helps", - "Markus Persson", - "Dan Buchoff", - "Derek Dai", - "Joseph John Cox", - "Luc Ritchie", - "Guido Gambardella", - "Jerry Ling", - "Mark Govea", - "Vecht ", - "Jonathan Eppele", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Kirk Werklund", - "Ripta Pasay", - "Felipe Diniz", - ], - "n_patron_columns": 2, - } - - -class Thumbnail(SpecialThreeDScene): - def construct(self): - sphere = ParametricSurface( - func=lambda u, v: 2 * np.array([ - np.cos(v) * np.sin(u) + 0.2 * np.cos(3 * u), - np.sin(v) * np.sin(u), - np.cos(u) + 0.2 * np.sin(4 * v) - 0.3 * np.cos(3 * u) - ]), - resolution=(24, 48), - u_min=0.001, - u_max=PI - 0.001, - v_min=0, - v_max=TAU, - ) - sphere.rotate(70 * DEGREES, DOWN) - self.set_camera_orientation( - phi=80 * DEGREES, - theta=-90 * DEGREES, - ) - # randy = Randolph(mode="telepath") - # eyes = VGroup(randy.eyes, randy.pupils) - # eyes.scale(3.5) - # eyes.rotate(90 * DEGREES, RIGHT) - # eyes.next_to(sphere, OUT, buff=0) - - self.add(sphere) diff --git a/from_3b1b/old/brachistochrone/curves.py b/from_3b1b/old/brachistochrone/curves.py deleted file mode 100644 index 504f015a..00000000 --- a/from_3b1b/old/brachistochrone/curves.py +++ /dev/null @@ -1,699 +0,0 @@ -from manimlib.imports import * - -RANDY_SCALE_FACTOR = 0.3 - - - -class Cycloid(ParametricFunction): - CONFIG = { - "point_a" : 6*LEFT+3*UP, - "radius" : 2, - "end_theta" : 3*np.pi/2, - "density" : 5*DEFAULT_POINT_DENSITY_1D, - "color" : YELLOW - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - ParametricFunction.__init__(self, self.pos_func, **kwargs) - - def pos_func(self, t): - T = t*self.end_theta - return self.point_a + self.radius * np.array([ - T - np.sin(T), - np.cos(T) - 1, - 0 - ]) - -class LoopTheLoop(ParametricFunction): - CONFIG = { - "color" : YELLOW_D, - "density" : 10*DEFAULT_POINT_DENSITY_1D - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - def func(t): - t = (6*np.pi/2)*(t-0.5) - return (t/2-np.sin(t))*RIGHT + \ - (np.cos(t)+(t**2)/10)*UP - ParametricFunction.__init__(self, func, **kwargs) - - -class SlideWordDownCycloid(Animation): - CONFIG = { - "rate_func" : None, - "run_time" : 8 - } - def __init__(self, word, **kwargs): - self.path = Cycloid(end_theta = np.pi) - word_mob = TextMobject(list(word)) - end_word = word_mob.copy() - end_word.shift(-end_word.get_bottom()) - end_word.shift(self.path.get_corner(DOWN+RIGHT)) - end_word.shift(3*RIGHT) - self.end_letters = end_word.split() - for letter in word_mob.split(): - letter.center() - letter.angle = 0 - unit_interval = np.arange(0, 1, 1./len(word)) - self.start_times = 0.5*(1-(unit_interval)) - Animation.__init__(self, word_mob, **kwargs) - - def interpolate_mobject(self, alpha): - virtual_times = 2*(alpha - self.start_times) - cut_offs = [ - 0.1, - 0.3, - 0.7, - ] - for letter, time, end_letter in zip( - self.mobject.split(), virtual_times, self.end_letters - ): - time = max(time, 0) - time = min(time, 1) - if time < cut_offs[0]: - brightness = time/cut_offs[0] - letter.rgbas = brightness*np.ones(letter.rgbas.shape) - position = self.path.points[0] - angle = 0 - elif time < cut_offs[1]: - alpha = (time-cut_offs[0])/(cut_offs[1]-cut_offs[0]) - angle = -rush_into(alpha)*np.pi/2 - position = self.path.points[0] - elif time < cut_offs[2]: - alpha = (time-cut_offs[1])/(cut_offs[2]-cut_offs[1]) - index = int(alpha*self.path.get_num_points()) - position = self.path.points[index] - try: - angle = angle_of_vector( - self.path.points[index+1] - \ - self.path.points[index] - ) - except: - angle = letter.angle - else: - alpha = (time-cut_offs[2])/(1-cut_offs[2]) - start = self.path.points[-1] - end = end_letter.get_bottom() - position = interpolate(start, end, rush_from(alpha)) - angle = 0 - - letter.shift(position-letter.get_bottom()) - letter.rotate_in_place(angle-letter.angle) - letter.angle = angle - - -class BrachistochroneWordSliding(Scene): - def construct(self): - anim = SlideWordDownCycloid("Brachistochrone") - anim.path.set_color_by_gradient(WHITE, BLUE_E) - self.play(ShowCreation(anim.path)) - self.play(anim) - self.wait() - self.play( - FadeOut(anim.path), - ApplyMethod(anim.mobject.center) - ) - - - -class PathSlidingScene(Scene): - CONFIG = { - "gravity" : 3, - "delta_t" : 0.05, - "wait_and_add" : True, - "show_time" : True, - } - def slide(self, mobject, path, roll = False, ceiling = None): - points = path.points - time_slices = self.get_time_slices(points, ceiling = ceiling) - curr_t = 0 - last_index = 0 - curr_index = 1 - if self.show_time: - self.t_equals = TexMobject("t = ") - self.t_equals.shift(3.5*UP+4*RIGHT) - self.add(self.t_equals) - while curr_index < len(points): - self.slider = mobject.copy() - self.adjust_mobject_to_index( - self.slider, curr_index, points - ) - if roll: - distance = get_norm( - points[curr_index] - points[last_index] - ) - self.roll(mobject, distance) - self.add(self.slider) - if self.show_time: - self.write_time(curr_t) - self.wait(self.frame_duration) - self.remove(self.slider) - curr_t += self.delta_t - last_index = curr_index - while time_slices[curr_index] < curr_t: - curr_index += 1 - if curr_index == len(points): - break - if self.wait_and_add: - self.add(self.slider) - self.wait() - else: - return self.slider - - def get_time_slices(self, points, ceiling = None): - dt_list = np.zeros(len(points)) - ds_list = np.apply_along_axis( - get_norm, - 1, - points[1:]-points[:-1] - ) - if ceiling is None: - ceiling = points[0, 1] - delta_y_list = np.abs(ceiling - points[1:,1]) - delta_y_list += 0.001*(delta_y_list == 0) - v_list = self.gravity*np.sqrt(delta_y_list) - dt_list[1:] = ds_list / v_list - return np.cumsum(dt_list) - - def adjust_mobject_to_index(self, mobject, index, points): - point_a, point_b = points[index-1], points[index] - while np.all(point_a == point_b): - index += 1 - point_b = points[index] - theta = angle_of_vector(point_b - point_a) - mobject.rotate(theta) - mobject.shift(points[index]) - self.midslide_action(point_a, theta) - return mobject - - def midslide_action(self, point, angle): - pass - - def write_time(self, time): - if hasattr(self, "time_mob"): - self.remove(self.time_mob) - digits = list(map(TexMobject, "%.2f"%time)) - digits[0].next_to(self.t_equals, buff = 0.1) - for left, right in zip(digits, digits[1:]): - right.next_to(left, buff = 0.1, aligned_edge = DOWN) - self.time_mob = Mobject(*digits) - self.add(self.time_mob) - - def roll(self, mobject, arc_length): - radius = mobject.get_width()/2 - theta = arc_length / radius - mobject.rotate_in_place(-theta) - - def add_cycloid_end_points(self): - cycloid = Cycloid() - point_a = Dot(cycloid.points[0]) - point_b = Dot(cycloid.points[-1]) - A = TexMobject("A").next_to(point_a, LEFT) - B = TexMobject("B").next_to(point_b, RIGHT) - self.add(point_a, point_b, A, B) - digest_locals(self) - - -class TryManyPaths(PathSlidingScene): - def construct(self): - randy = Randolph() - randy.shift(-randy.get_bottom()) - self.slider = randy.copy() - randy.scale(RANDY_SCALE_FACTOR) - paths = self.get_paths() - point_a = Dot(paths[0].points[0]) - point_b = Dot(paths[0].points[-1]) - A = TexMobject("A").next_to(point_a, LEFT) - B = TexMobject("B").next_to(point_b, RIGHT) - for point, tex in [(point_a, A), (point_b, B)]: - self.play(ShowCreation(point)) - self.play(ShimmerIn(tex)) - self.wait() - curr_path = None - for path in paths: - new_slider = self.adjust_mobject_to_index( - randy.copy(), 1, path.points - ) - if curr_path is None: - curr_path = path - self.play(ShowCreation(curr_path)) - else: - self.play(Transform(curr_path, path)) - self.play(Transform(self.slider, new_slider)) - self.wait() - self.remove(self.slider) - self.slide(randy, curr_path) - self.clear() - self.add(point_a, point_b, A, B, curr_path) - text = self.get_text() - text.to_edge(UP) - self.play(ShimmerIn(text)) - for path in paths: - self.play(Transform( - curr_path, path, - path_func = path_along_arc(np.pi/2), - run_time = 3 - )) - - def get_text(self): - return TextMobject("Which path is fastest?") - - def get_paths(self): - sharp_corner = Mobject( - Line(3*UP+LEFT, LEFT), - Arc(angle = np.pi/2, start_angle = np.pi), - Line(DOWN, DOWN+3*RIGHT) - ).ingest_submobjects().set_color(GREEN) - paths = [ - Arc( - angle = np.pi/2, - radius = 3, - start_angle = 4 - ), - LoopTheLoop(), - Line(7*LEFT, 7*RIGHT, color = RED_D), - sharp_corner, - FunctionGraph( - lambda x : 0.05*(x**2)+0.1*np.sin(2*x) - ), - FunctionGraph( - lambda x : x**2, - x_min = -3, - x_max = 2, - density = 3*DEFAULT_POINT_DENSITY_1D - ) - ] - cycloid = Cycloid() - self.align_paths(paths, cycloid) - return paths + [cycloid] - - def align_paths(self, paths, target_path): - start = target_path.points[0] - end = target_path.points[-1] - for path in paths: - path.put_start_and_end_on(start, end) - - -class RollingRandolph(PathSlidingScene): - def construct(self): - randy = Randolph() - randy.scale(RANDY_SCALE_FACTOR) - randy.shift(-randy.get_bottom()) - self.add_cycloid_end_points() - self.slide(randy, self.cycloid, roll = True) - - - -class NotTheCircle(PathSlidingScene): - def construct(self): - self.add_cycloid_end_points() - start = self.point_a.get_center() - end = self.point_b.get_center() - angle = 2*np.pi/3 - path = Arc(angle, radius = 3) - path.set_color_by_gradient(RED_D, WHITE) - radius = Line(ORIGIN, path.points[0]) - randy = Randolph() - randy.scale(RANDY_SCALE_FACTOR) - randy.shift(-randy.get_bottom()) - randy_copy = randy.copy() - words = TextMobject("Circular paths are good, \\\\ but still not the best") - words.shift(UP) - - self.play( - ShowCreation(path), - ApplyMethod( - radius.rotate, - angle, - path_func = path_along_arc(angle) - ) - ) - self.play(FadeOut(radius)) - self.play( - ApplyMethod( - path.put_start_and_end_on, start, end, - path_func = path_along_arc(-angle) - ), - run_time = 3 - ) - self.adjust_mobject_to_index(randy_copy, 1, path.points) - self.play(FadeIn(randy_copy)) - self.remove(randy_copy) - self.slide(randy, path) - self.play(ShimmerIn(words)) - self.wait() - - -class TransitionAwayFromSlide(PathSlidingScene): - def construct(self): - randy = Randolph() - randy.scale(RANDY_SCALE_FACTOR) - randy.shift(-randy.get_bottom()) - self.add_cycloid_end_points() - arrow = Arrow(ORIGIN, 2*RIGHT) - arrows = Mobject(*[ - arrow.copy().shift(vect) - for vect in (3*LEFT, ORIGIN, 3*RIGHT) - ]) - arrows.shift(FRAME_WIDTH*RIGHT) - self.add(arrows) - - self.add(self.cycloid) - self.slide(randy, self.cycloid) - everything = Mobject(*self.mobjects) - self.play(ApplyMethod( - everything.shift, 4*FRAME_X_RADIUS*LEFT, - run_time = 2, - rate_func = rush_into - )) - - -class MinimalPotentialEnergy(Scene): - def construct(self): - horiz_radius = 5 - vert_radius = 3 - - vert_axis = NumberLine(numerical_radius = vert_radius) - vert_axis.rotate(np.pi/2) - vert_axis.shift(horiz_radius*LEFT) - horiz_axis = NumberLine( - numerical_radius = 5, - numbers_with_elongated_ticks = [] - ) - axes = Mobject(horiz_axis, vert_axis) - graph = FunctionGraph( - lambda x : 0.4*(x-2)*(x+2)+3, - x_min = -2, - x_max = 2, - density = 3*DEFAULT_POINT_DENSITY_1D - ) - graph.stretch_to_fit_width(2*horiz_radius) - graph.set_color(YELLOW) - min_point = Dot(graph.get_bottom()) - nature_finds = TextMobject("Nature finds this point") - nature_finds.scale(0.5) - nature_finds.set_color(GREEN) - nature_finds.shift(2*RIGHT+3*UP) - arrow = Arrow( - nature_finds.get_bottom(), min_point, - color = GREEN - ) - - side_words_start = TextMobject("Parameter describing") - top_words, last_side_words = [ - list(map(TextMobject, pair)) - for pair in [ - ("Light's travel time", "Potential energy"), - ("path", "mechanical state") - ] - ] - for word in top_words + last_side_words + [side_words_start]: - word.scale(0.7) - side_words_start.next_to(horiz_axis, DOWN) - side_words_start.to_edge(RIGHT) - for words in top_words: - words.next_to(vert_axis, UP) - words.to_edge(LEFT) - for words in last_side_words: - words.next_to(side_words_start, DOWN) - for words in top_words[1], last_side_words[1]: - words.set_color(RED) - - self.add( - axes, top_words[0], side_words_start, - last_side_words[0] - ) - self.play(ShowCreation(graph)) - self.play( - ShimmerIn(nature_finds), - ShowCreation(arrow), - ShowCreation(min_point) - ) - self.wait() - self.play( - FadeOut(top_words[0]), - FadeOut(last_side_words[0]), - GrowFromCenter(top_words[1]), - GrowFromCenter(last_side_words[1]) - ) - self.wait(3) - - - - -class WhatGovernsSpeed(PathSlidingScene): - CONFIG = { - "num_pieces" : 6, - "wait_and_add" : False, - "show_time" : False, - } - def construct(self): - randy = Randolph() - randy.scale(RANDY_SCALE_FACTOR) - randy.shift(-randy.get_bottom()) - self.add_cycloid_end_points() - points = self.cycloid.points - ceiling = points[0, 1] - n = len(points) - broken_points = [ - points[k*n/self.num_pieces:(k+1)*n/self.num_pieces] - for k in range(self.num_pieces) - ] - words = TextMobject(""" - What determines the speed\\\\ - at each point? - """) - words.to_edge(UP) - - self.add(self.cycloid) - sliders, vectors = [], [] - for points in broken_points: - path = Mobject().add_points(points) - vect = points[-1] - points[-2] - magnitude = np.sqrt(ceiling - points[-1, 1]) - vect = magnitude*vect/get_norm(vect) - slider = self.slide(randy, path, ceiling = ceiling) - vector = Vector(slider.get_center(), vect) - self.add(slider, vector) - sliders.append(slider) - vectors.append(vector) - self.wait() - self.play(ShimmerIn(words)) - self.wait(3) - slider = sliders.pop(1) - vector = vectors.pop(1) - faders = sliders+vectors+[words] - self.play(*list(map(FadeOut, faders))) - self.remove(*faders) - self.show_geometry(slider, vector) - - def show_geometry(self, slider, vector): - point_a = self.point_a.get_center() - horiz_line = Line(point_a, point_a + 6*RIGHT) - ceil_point = point_a - ceil_point[0] = slider.get_center()[0] - vert_brace = Brace( - Mobject(Point(ceil_point), Point(slider.get_center())), - RIGHT, - buff = 0.5 - ) - vect_brace = Brace(slider) - vect_brace.stretch_to_fit_width(vector.get_length()) - vect_brace.rotate(np.arctan(vector.get_slope())) - vect_brace.center().shift(vector.get_center()) - nudge = 0.2*(DOWN+LEFT) - vect_brace.shift(nudge) - y_mob = TexMobject("y") - y_mob.next_to(vert_brace) - sqrt_y = TexMobject("k\\sqrt{y}") - sqrt_y.scale(0.5) - sqrt_y.shift(vect_brace.get_center()) - sqrt_y.shift(3*nudge) - - self.play(ShowCreation(horiz_line)) - self.play( - GrowFromCenter(vert_brace), - ShimmerIn(y_mob) - ) - self.play( - GrowFromCenter(vect_brace), - ShimmerIn(sqrt_y) - ) - self.wait(3) - self.solve_energy() - - def solve_energy(self): - loss_in_potential = TextMobject("Loss in potential: ") - loss_in_potential.shift(2*UP) - potential = TexMobject("m g y".split()) - potential.next_to(loss_in_potential) - kinetic = TexMobject([ - "\\dfrac{1}{2}","m","v","^2","=" - ]) - kinetic.next_to(potential, LEFT) - nudge = 0.1*UP - kinetic.shift(nudge) - loss_in_potential.shift(nudge) - ms = Mobject(kinetic.split()[1], potential.split()[0]) - two = TexMobject("2") - two.shift(ms.split()[1].get_center()) - half = kinetic.split()[0] - sqrt = TexMobject("\\sqrt{\\phantom{2mg}}") - sqrt.shift(potential.get_center()) - nudge = 0.2*LEFT - sqrt.shift(nudge) - squared = kinetic.split()[3] - equals = kinetic.split()[-1] - new_eq = equals.copy().next_to(kinetic.split()[2]) - - self.play( - Transform( - Point(loss_in_potential.get_left()), - loss_in_potential - ), - *list(map(GrowFromCenter, potential.split())) - ) - self.wait(2) - self.play( - FadeOut(loss_in_potential), - GrowFromCenter(kinetic) - ) - self.wait(2) - self.play(ApplyMethod(ms.shift, 5*UP)) - self.wait() - self.play(Transform( - half, two, - path_func = counterclockwise_path() - )) - self.wait() - self.play( - Transform( - squared, sqrt, - path_func = clockwise_path() - ), - Transform(equals, new_eq) - ) - self.wait(2) - - - - -class ThetaTInsteadOfXY(Scene): - def construct(self): - cycloid = Cycloid() - index = cycloid.get_num_points()/3 - point = cycloid.points[index] - vect = cycloid.points[index+1]-point - vect /= get_norm(vect) - vect *= 3 - vect_mob = Vector(point, vect) - dot = Dot(point) - xy = TexMobject("\\big( x(t), y(t) \\big)") - xy.next_to(dot, UP+RIGHT, buff = 0.1) - vert_line = Line(2*DOWN, 2*UP) - vert_line.shift(point) - angle = vect_mob.get_angle() + np.pi/2 - arc = Arc(angle, radius = 1, start_angle = -np.pi/2) - arc.shift(point) - theta = TexMobject("\\theta(t)") - theta.next_to(arc, DOWN, buff = 0.1, aligned_edge = LEFT) - theta.shift(0.2*RIGHT) - - self.play(ShowCreation(cycloid)) - self.play(ShowCreation(dot)) - self.play(ShimmerIn(xy)) - self.wait() - self.play( - FadeOut(xy), - ShowCreation(vect_mob) - ) - self.play( - ShowCreation(arc), - ShowCreation(vert_line), - ShimmerIn(theta) - ) - self.wait() - - -class DefineCurveWithKnob(PathSlidingScene): - def construct(self): - self.knob = Circle(color = BLUE_D) - self.knob.add_line(UP, DOWN) - self.knob.to_corner(UP+RIGHT) - self.knob.shift(0.5*DOWN) - self.last_angle = np.pi/2 - arrow = Vector(ORIGIN, RIGHT) - arrow.next_to(self.knob, LEFT) - words = TextMobject("Turn this knob over time to define the curve") - words.next_to(arrow, LEFT) - self.path = self.get_path() - self.path.shift(1.5*DOWN) - self.path.show() - self.path.set_color(BLACK) - - randy = Randolph() - randy.scale(RANDY_SCALE_FACTOR) - randy.shift(-randy.get_bottom()) - - self.play(ShimmerIn(words)) - self.play(ShowCreation(arrow)) - self.play(ShowCreation(self.knob)) - self.wait() - self.add(self.path) - - self.slide(randy, self.path) - self.wait() - - - def get_path(self): - return Cycloid(end_theta = 2*np.pi) - - def midslide_action(self, point, angle): - d_angle = angle-self.last_angle - self.knob.rotate_in_place(d_angle) - self.last_angle = angle - self.path.set_color(BLUE_D, lambda p : p[0] < point[0]) - - - -class WonkyDefineCurveWithKnob(DefineCurveWithKnob): - def get_path(self): - return ParametricFunction( - lambda t : t*RIGHT + (-0.2*t-np.sin(2*np.pi*t/6))*UP, - start = -7, - end = 10 - ) - - -class SlowDefineCurveWithKnob(DefineCurveWithKnob): - def get_path(self): - return ParametricFunction( - lambda t : t*RIGHT + (np.exp(-(t+2)**2)-0.2*np.exp(t-2)), - start = -4, - end = 4 - ) - - -class BumpyDefineCurveWithKnob(DefineCurveWithKnob): - def get_path(self): - - result = FunctionGraph( - lambda x : 0.05*(x**2)+0.1*np.sin(2*x) - ) - result.rotate(-np.pi/20) - result.scale(0.7) - result.shift(DOWN) - return result - - - - - - - - - - - - - diff --git a/from_3b1b/old/brachistochrone/cycloid.py b/from_3b1b/old/brachistochrone/cycloid.py deleted file mode 100644 index e0b5b37d..00000000 --- a/from_3b1b/old/brachistochrone/cycloid.py +++ /dev/null @@ -1,590 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.brachistochrone.curves import * - -class RollAlongVector(Animation): - CONFIG = { - "rotation_vector" : OUT, - } - def __init__(self, mobject, vector, **kwargs): - radius = mobject.get_width()/2 - radians = get_norm(vector)/radius - last_alpha = 0 - digest_config(self, kwargs, locals()) - Animation.__init__(self, mobject, **kwargs) - - def interpolate_mobject(self, alpha): - d_alpha = alpha - self.last_alpha - self.last_alpha = alpha - self.mobject.rotate_in_place( - d_alpha*self.radians, - self.rotation_vector - ) - self.mobject.shift(d_alpha*self.vector) - - -class CycloidScene(Scene): - CONFIG = { - "point_a" : 6*LEFT+3*UP, - "radius" : 2, - "end_theta" : 2*np.pi - } - def construct(self): - self.generate_cycloid() - self.generate_circle() - self.generate_ceiling() - - def grow_parts(self): - self.play(*[ - ShowCreation(mob) - for mob in (self.circle, self.ceiling) - ]) - - def generate_cycloid(self): - self.cycloid = Cycloid( - point_a = self.point_a, - radius = self.radius, - end_theta = self.end_theta - ) - - def generate_circle(self, **kwargs): - self.circle = Circle(radius = self.radius, **kwargs) - self.circle.shift(self.point_a - self.circle.get_top()) - radial_line = Line( - self.circle.get_center(), self.point_a - ) - self.circle.add(radial_line) - - def generate_ceiling(self): - self.ceiling = Line(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) - self.ceiling.shift(self.cycloid.get_top()[1]*UP) - - def draw_cycloid(self, run_time = 3, *anims, **kwargs): - kwargs["run_time"] = run_time - self.play( - RollAlongVector( - self.circle, - self.cycloid.points[-1]-self.cycloid.points[0], - **kwargs - ), - ShowCreation(self.cycloid, **kwargs), - *anims - ) - - def roll_back(self, run_time = 3, *anims, **kwargs): - kwargs["run_time"] = run_time - self.play( - RollAlongVector( - self.circle, - self.cycloid.points[0]-self.cycloid.points[- 1], - rotation_vector = IN, - **kwargs - ), - ShowCreation( - self.cycloid, - rate_func = lambda t : smooth(1-t), - **kwargs - ), - *anims - ) - self.generate_cycloid() - - -class IntroduceCycloid(CycloidScene): - def construct(self): - CycloidScene.construct(self) - - equation = TexMobject([ - "\\dfrac{\\sin(\\theta)}{\\sqrt{y}}", - "= \\text{constant}" - ]) - sin_sqrt, const = equation.split() - new_eq = equation.copy() - new_eq.to_edge(UP, buff = 1.3) - cycloid_word = TextMobject("Cycloid") - arrow = Arrow(2*UP, cycloid_word) - arrow.reverse_points() - q_mark = TextMobject("?") - - self.play(*list(map(ShimmerIn, equation.split()))) - self.wait() - self.play( - ApplyMethod(equation.shift, 2.2*UP), - ShowCreation(arrow) - ) - q_mark.next_to(sin_sqrt) - self.play(ShimmerIn(cycloid_word)) - self.wait() - self.grow_parts() - self.draw_cycloid() - self.wait() - extra_terms = [const, arrow, cycloid_word] - self.play(*[ - Transform(mob, q_mark) - for mob in extra_terms - ]) - self.remove(*extra_terms) - self.roll_back() - q_marks, arrows = self.get_q_marks_and_arrows(sin_sqrt) - self.draw_cycloid(3, - ShowCreation(q_marks), - ShowCreation(arrows) - ) - self.wait() - - def get_q_marks_and_arrows(self, mob, n_marks = 10): - circle = Circle().replace(mob) - q_marks, arrows = result = [Mobject(), Mobject()] - for x in range(n_marks): - index = (x+0.5)*self.cycloid.get_num_points()/n_marks - q_point = self.cycloid.points[index] - vect = q_point-mob.get_center() - start_point = circle.get_boundary_point(vect) - arrow = Arrow( - start_point, q_point, - color = BLUE_E - ) - - q_marks.add(TextMobject("?").shift(q_point)) - arrows.add(arrow) - for mob in result: - mob.ingest_submobjects() - return result - - -class LeviSolution(CycloidScene): - CONFIG = { - "cycloid_fraction" : 0.25, - } - def construct(self): - CycloidScene.construct(self) - self.add(self.ceiling) - self.init_points() - methods = [ - self.draw_cycloid, - self.roll_into_position, - self.draw_p_and_c, - self.show_pendulum, - self.show_diameter, - self.show_theta, - self.show_similar_triangles, - self.show_sin_thetas, - self.show_y, - self.rearrange, - ] - for method in methods: - method() - self.wait() - - - def init_points(self): - index = int(self.cycloid_fraction*self.cycloid.get_num_points()) - p_point = self.cycloid.points[index] - p_dot = Dot(p_point) - p_label = TexMobject("P") - p_label.next_to(p_dot, DOWN+LEFT) - c_point = self.point_a + self.cycloid_fraction*self.radius*2*np.pi*RIGHT - c_dot = Dot(c_point) - c_label = TexMobject("C") - c_label.next_to(c_dot, UP) - - digest_locals(self) - - def roll_into_position(self): - self.play(RollAlongVector( - self.circle, - (1-self.cycloid_fraction)*self.radius*2*np.pi*LEFT, - rotation_vector = IN, - run_time = 2 - )) - - def draw_p_and_c(self): - radial_line = self.circle.submobjects[0] ##Hacky - self.play(Transform(radial_line, self.p_dot)) - self.remove(radial_line) - self.add(self.p_dot) - self.play(ShimmerIn(self.p_label)) - self.wait() - self.play(Transform(self.ceiling.copy(), self.c_dot)) - self.play(ShimmerIn(self.c_label)) - - def show_pendulum(self, arc_angle = np.pi, arc_color = GREEN): - words = TextMobject(": Instantaneous center of rotation") - words.next_to(self.c_label) - line = Line(self.p_point, self.c_point) - line_angle = line.get_angle()+np.pi - line_length = line.get_length() - line.add(self.p_dot.copy()) - line.get_center = lambda : self.c_point - tangent_line = Line(3*LEFT, 3*RIGHT) - tangent_line.rotate(line_angle-np.pi/2) - tangent_line.shift(self.p_point) - tangent_line.set_color(arc_color) - right_angle_symbol = Mobject( - Line(UP, UP+RIGHT), - Line(UP+RIGHT, RIGHT) - ) - right_angle_symbol.scale(0.3) - right_angle_symbol.rotate(tangent_line.get_angle()+np.pi) - right_angle_symbol.shift(self.p_point) - - self.play(ShowCreation(line)) - self.play(ShimmerIn(words)) - self.wait() - pairs = [ - (line_angle, arc_angle/2), - (line_angle+arc_angle/2, -arc_angle), - (line_angle-arc_angle/2, arc_angle/2), - ] - arcs = [] - for start, angle in pairs: - arc = Arc( - angle = angle, - radius = line_length, - start_angle = start, - color = GREEN - ) - arc.shift(self.c_point) - self.play( - ShowCreation(arc), - ApplyMethod( - line.rotate_in_place, - angle, - path_func = path_along_arc(angle) - ), - run_time = 2 - ) - arcs.append(arc) - self.wait() - self.play(Transform(arcs[1], tangent_line)) - self.add(tangent_line) - self.play(ShowCreation(right_angle_symbol)) - self.wait() - - self.tangent_line = tangent_line - self.right_angle_symbol = right_angle_symbol - self.pc_line = line - self.remove(words, *arcs) - - def show_diameter(self): - exceptions = [ - self.circle, - self.tangent_line, - self.pc_line, - self.right_angle_symbol - ] - everything = set(self.mobjects).difference(exceptions) - everything_copy = Mobject(*everything).copy() - light_everything = everything_copy.copy() - dark_everything = everything_copy.copy() - dark_everything.fade(0.8) - bottom_point = np.array(self.c_point) - bottom_point += 2*self.radius*DOWN - diameter = Line(bottom_point, self.c_point) - brace = Brace(diameter, RIGHT) - diameter_word = TextMobject("Diameter") - d_mob = TexMobject("D") - diameter_word.next_to(brace) - d_mob.next_to(diameter) - - self.remove(*everything) - self.play(Transform(everything_copy, dark_everything)) - self.wait() - self.play(ShowCreation(diameter)) - self.play(GrowFromCenter(brace)) - self.play(ShimmerIn(diameter_word)) - self.wait() - self.play(*[ - Transform(mob, d_mob) - for mob in (brace, diameter_word) - ]) - self.remove(brace, diameter_word) - self.add(d_mob) - self.play(Transform(everything_copy, light_everything)) - self.remove(everything_copy) - self.add(*everything) - - self.d_mob = d_mob - self.bottom_point = bottom_point - - def show_theta(self, radius = 1): - arc = Arc( - angle = self.tangent_line.get_angle()-np.pi/2, - radius = radius, - start_angle = np.pi/2 - ) - - theta = TexMobject("\\theta") - theta.shift(1.5*arc.get_center()) - Mobject(arc, theta).shift(self.bottom_point) - - self.play( - ShowCreation(arc), - ShimmerIn(theta) - ) - self.arc = arc - self.theta = theta - - def show_similar_triangles(self): - y_point = np.array(self.p_point) - y_point[1] = self.point_a[1] - new_arc = Arc( - angle = self.tangent_line.get_angle()-np.pi/2, - radius = 0.5, - start_angle = np.pi - ) - new_arc.shift(self.c_point) - new_theta = self.theta.copy() - new_theta.next_to(new_arc, LEFT) - new_theta.shift(0.1*DOWN) - kwargs = { - "stroke_width" : 2*DEFAULT_STROKE_WIDTH, - } - triangle1 = Polygon( - self.p_point, self.c_point, self.bottom_point, - color = MAROON, - **kwargs - ) - triangle2 = Polygon( - y_point, self.p_point, self.c_point, - color = WHITE, - **kwargs - ) - y_line = Line(self.p_point, y_point) - - self.play( - Transform(self.arc.copy(), new_arc), - Transform(self.theta.copy(), new_theta), - run_time = 3 - ) - self.wait() - self.play(FadeIn(triangle1)) - self.wait() - self.play(Transform(triangle1, triangle2)) - self.play(ApplyMethod(triangle1.set_color, MAROON)) - self.wait() - self.remove(triangle1) - self.add(y_line) - - self.y_line = y_line - - def show_sin_thetas(self): - pc = Line(self.p_point, self.c_point) - mob = Mobject(self.theta, self.d_mob).copy() - mob.ingest_submobjects() - triplets = [ - (pc, "D\\sin(\\theta)", 0.5), - (self.y_line, "D\\sin^2(\\theta)", 0.7), - ] - for line, tex, scale in triplets: - trig_mob = TexMobject(tex) - trig_mob.set_width( - scale*line.get_length() - ) - trig_mob.shift(-1.2*trig_mob.get_top()) - trig_mob.rotate(line.get_angle()) - trig_mob.shift(line.get_center()) - if line is self.y_line: - trig_mob.shift(0.1*UP) - - self.play(Transform(mob, trig_mob)) - self.add(trig_mob) - self.wait() - - self.remove(mob) - self.d_sin_squared_theta = trig_mob - - - def show_y(self): - y_equals = TexMobject(["y", "="]) - y_equals.shift(2*UP) - y_expression = TexMobject([ - "D ", "\\sin", "^2", "(\\theta)" - ]) - y_expression.next_to(y_equals) - y_expression.shift(0.05*UP+0.1*RIGHT) - temp_expr = self.d_sin_squared_theta.copy() - temp_expr.rotate(-np.pi/2) - temp_expr.replace(y_expression) - y_mob = TexMobject("y") - y_mob.next_to(self.y_line, RIGHT) - y_mob.shift(0.2*UP) - - self.play( - Transform(self.d_sin_squared_theta, temp_expr), - ShimmerIn(y_mob), - ShowCreation(y_equals) - ) - self.remove(self.d_sin_squared_theta) - self.add(y_expression) - - self.y_equals = y_equals - self.y_expression = y_expression - - def rearrange(self): - sqrt_nudge = 0.2*LEFT - y, equals = self.y_equals.split() - d, sin, squared, theta = self.y_expression.split() - y_sqrt = TexMobject("\\sqrt{\\phantom{y}}") - d_sqrt = y_sqrt.copy() - y_sqrt.shift(y.get_center()+sqrt_nudge) - d_sqrt.shift(d.get_center()+sqrt_nudge) - - self.play( - ShimmerIn(y_sqrt), - ShimmerIn(d_sqrt), - ApplyMethod(squared.shift, 4*UP), - ApplyMethod(theta.shift, 1.5* squared.get_width()*LEFT) - ) - self.wait() - y_sqrt.add(y) - d_sqrt.add(d) - sin.add(theta) - - sin_over = TexMobject("\\dfrac{\\phantom{\\sin(\\theta)}}{\\quad}") - sin_over.next_to(sin, DOWN, 0.15) - new_eq = equals.copy() - new_eq.next_to(sin_over, LEFT) - one_over = TexMobject("\\dfrac{1}{\\quad}") - one_over.next_to(new_eq, LEFT) - one_over.shift( - (sin_over.get_bottom()[1]-one_over.get_bottom()[1])*UP - ) - - self.play( - Transform(equals, new_eq), - ShimmerIn(sin_over), - ShimmerIn(one_over), - ApplyMethod( - d_sqrt.next_to, one_over, DOWN, - path_func = path_along_arc(-np.pi) - ), - ApplyMethod( - y_sqrt.next_to, sin_over, DOWN, - path_func = path_along_arc(-np.pi) - ), - run_time = 2 - ) - self.wait() - - brace = Brace(d_sqrt, DOWN) - constant = TextMobject("Constant") - constant.next_to(brace, DOWN) - - self.play( - GrowFromCenter(brace), - ShimmerIn(constant) - ) - - - - -class EquationsForCycloid(CycloidScene): - def construct(self): - CycloidScene.construct(self) - equations = TexMobject([ - "x(t) = Rt - R\\sin(t)", - "y(t) = -R + R\\cos(t)" - ]) - top, bottom = equations.split() - bottom.next_to(top, DOWN) - equations.center() - equations.to_edge(UP, buff = 1.3) - - self.play(ShimmerIn(equations)) - self.grow_parts() - self.draw_cycloid(rate_func=linear, run_time = 5) - self.wait() - -class SlidingObject(CycloidScene, PathSlidingScene): - CONFIG = { - "show_time" : False, - "wait_and_add" : False - } - - args_list = [(True,), (False,)] - - @staticmethod - def args_to_string(with_words): - return "WithWords" if with_words else "WithoutWords" - - @staticmethod - def string_to_args(string): - return string == "WithWords" - - def construct(self, with_words): - CycloidScene.construct(self) - - randy = Randolph() - randy.scale(RANDY_SCALE_FACTOR) - randy.shift(-randy.get_bottom()) - central_randy = randy.copy() - start_randy = self.adjust_mobject_to_index( - randy.copy(), 1, self.cycloid.points - ) - - if with_words: - words1 = TextMobject("Trajectory due to gravity") - arrow = TexMobject("\\leftrightarrow") - words2 = TextMobject("Trajectory due \\emph{constantly} rotating wheel") - words1.next_to(arrow, LEFT) - words2.next_to(arrow, RIGHT) - words = Mobject(words1, arrow, words2) - words.set_width(FRAME_WIDTH-1) - words.to_edge(UP, buff = 0.2) - words.to_edge(LEFT) - - self.play(ShowCreation(self.cycloid.copy())) - self.slide(randy, self.cycloid) - self.add(self.slider) - self.wait() - self.grow_parts() - self.draw_cycloid() - self.wait() - self.play(Transform(self.slider, start_randy)) - self.wait() - self.roll_back() - self.wait() - if with_words: - self.play(*list(map(ShimmerIn, [words1, arrow, words2]))) - self.wait() - self.remove(self.circle) - start_time = len(self.frames)*self.frame_duration - self.remove(self.slider) - self.slide(central_randy, self.cycloid) - end_time = len(self.frames)*self.frame_duration - self.play_over_time_range( - start_time, - end_time, - RollAlongVector( - self.circle, - self.cycloid.points[-1]-self.cycloid.points[0], - run_time = end_time-start_time, - rate_func=linear - ) - ) - self.add(self.circle, self.slider) - self.wait() - - - -class RotateWheel(CycloidScene): - def construct(self): - CycloidScene.construct(self) - self.circle.center() - - self.play(Rotating( - self.circle, - axis = OUT, - run_time = 5, - rate_func = smooth - )) - - - - - - - - - - diff --git a/from_3b1b/old/brachistochrone/drawing_images.py b/from_3b1b/old/brachistochrone/drawing_images.py deleted file mode 100644 index c546ff67..00000000 --- a/from_3b1b/old/brachistochrone/drawing_images.py +++ /dev/null @@ -1,409 +0,0 @@ -import numpy as np -import itertools as it -import operator as op -import sys -import inspect -from PIL import Image -import cv2 -import random -from scipy.spatial.distance import cdist -from scipy import ndimage - -from manimlib.imports import * - - -DEFAULT_GAUSS_BLUR_CONFIG = { - "ksize" : (5, 5), - "sigmaX" : 6, - "sigmaY" : 6, -} - -DEFAULT_CANNY_CONFIG = { - "threshold1" : 50, - "threshold2" : 100, -} - -DEFAULT_BLUR_RADIUS = 0.5 -DEFAULT_CONNECTED_COMPONENT_THRESHOLD = 25 - - -def reverse_colors(nparray): - return nparray[:,:,[2, 1, 0]] - -def show(nparray): - Image.fromarray(reverse_colors(nparray)).show() - - -def thicken(nparray): - height, width = nparray.shape - nparray = nparray.reshape((height, width, 1)) - return np.repeat(nparray, 3, 2) - -def sort_by_color(mob): - indices = np.argsort(np.apply_along_axis( - lambda p : -get_norm(p), - 1, - mob.rgbas - )) - mob.rgbas = mob.rgbas[indices] - mob.points = mob.points[indices] - - - -def get_image_array(name): - image_files = os.listdir(IMAGE_DIR) - possibilities = [s for s in image_files if s.startswith(name)] - for possibility in possibilities: - try: - path = os.path.join(IMAGE_DIR, possibility) - image = Image.open(path) - image = image.convert('RGB') - return np.array(image) - except: - pass - raise Exception("Image for %s not found"%name) - -def get_edges(image_array): - blurred = cv2.GaussianBlur( - image_array, - **DEFAULT_GAUSS_BLUR_CONFIG - ) - edges = cv2.Canny( - blurred, - **DEFAULT_CANNY_CONFIG - ) - return edges - -def nearest_neighbor_align(mobject1, mobject2): - distance_matrix = cdist(mobject1.points, mobject2.points) - closest_point_indices = np.apply_along_axis( - np.argmin, 0, distance_matrix - ) - new_mob1 = Mobject() - new_mob2 = Mobject() - for n in range(mobject1.get_num_points()): - indices = (closest_point_indices == n) - new_mob1.add_points( - [mobject1.points[n]]*sum(indices) - ) - new_mob2.add_points( - mobject2.points[indices], - rgbas = mobject2.rgbas[indices] - ) - return new_mob1, new_mob2 - -def get_connected_components(image_array, - blur_radius = DEFAULT_BLUR_RADIUS, - threshold = DEFAULT_CONNECTED_COMPONENT_THRESHOLD): - blurred_image = ndimage.gaussian_filter(image_array, blur_radius) - labels, component_count = ndimage.label(blurred_image > threshold) - return [ - image_array * (labels == count) - for count in range(1, component_count+1) - ] - -def color_region(bw_region, colored_image): - return thicken(bw_region > 0) * colored_image - - -class TracePicture(Scene): - args_list = [ - ("Newton",), - ("Mark_Levi",), - ("Steven_Strogatz",), - ("Pierre_de_Fermat",), - ("Galileo_Galilei",), - ("Jacob_Bernoulli",), - ("Johann_Bernoulli2",), - ("Old_Newton",) - ] - - @staticmethod - def args_to_string(name): - return name - - @staticmethod - def string_to_args(name): - return name - - def construct(self, name): - run_time = 20 - scale_factor = 0.8 - image_array = get_image_array(name) - edge_mobject = self.get_edge_mobject(image_array) - full_picture = MobjectFromPixelArray(image_array) - for mob in edge_mobject, full_picture: - # mob.stroke_width = 4 - mob.scale(scale_factor) - mob.show() - - self.play( - DelayByOrder(FadeIn( - full_picture, - run_time = run_time, - rate_func = squish_rate_func(smooth, 0.7, 1) - )), - ShowCreation( - edge_mobject, - run_time = run_time, - rate_func=linear - ) - ) - self.remove(edge_mobject) - self.wait() - - - def get_edge_mobject(self, image_array): - edged_image = get_edges(image_array) - individual_edges = get_connected_components(edged_image) - colored_edges = [ - color_region(edge, image_array) - for edge in individual_edges - ] - colored_edge_mobject_list = [ - MobjectFromPixelArray(colored_edge) - for colored_edge in colored_edges - ] - random.shuffle(colored_edge_mobject_list) - edge_mobject = Mobject(*colored_edge_mobject_list) - edge_mobject.ingest_submobjects() - return edge_mobject - - - -class JohannThinksHeIsBetter(Scene): - def construct(self): - names = [ - "Johann_Bernoulli2", - "Jacob_Bernoulli", - "Gottfried_Wilhelm_von_Leibniz", - "Newton" - ] - guys = [ - ImageMobject(name, invert = False) - for name in names - ] - johann = guys[0] - johann.scale(0.8) - pensive_johann = johann.copy() - pensive_johann.scale(0.25) - pensive_johann.to_corner(DOWN+LEFT) - comparitive_johann = johann.copy() - template = Square(side_length = 2) - comparitive_johann.replace(template) - comparitive_johann.shift(UP+LEFT) - greater_than = TexMobject(">") - greater_than.next_to(comparitive_johann) - for guy, name in zip(guys, names)[1:]: - guy.replace(template) - guy.next_to(greater_than) - name_mob = TextMobject(name.replace("_", " ")) - name_mob.scale(0.5) - name_mob.next_to(guy, DOWN) - guy.name_mob = name_mob - guy.sort_points(lambda p : np.dot(p, DOWN+RIGHT)) - bubble = ThoughtBubble(initial_width = 12) - bubble.stretch_to_fit_height(6) - bubble.ingest_submobjects() - bubble.pin_to(pensive_johann) - bubble.shift(DOWN) - point = Point(johann.get_corner(UP+RIGHT)) - upper_point = Point(comparitive_johann.get_corner(UP+RIGHT)) - lightbulb = ImageMobject("Lightbulb", invert = False) - lightbulb.scale(0.1) - lightbulb.sort_points(get_norm) - lightbulb.next_to(upper_point, RIGHT) - - self.add(johann) - self.wait() - self.play( - Transform(johann, pensive_johann), - Transform(point, bubble), - run_time = 2 - ) - self.remove(point) - self.add(bubble) - weakling = guys[1] - self.play( - FadeIn(comparitive_johann), - ShowCreation(greater_than), - FadeIn(weakling) - ) - self.wait(2) - for guy in guys[2:]: - self.play(DelayByOrder(Transform( - weakling, upper_point - ))) - self.play( - FadeIn(guy), - ShimmerIn(guy.name_mob) - ) - self.wait(3) - self.remove(guy.name_mob) - weakling = guy - self.play(FadeOut(weakling), FadeOut(greater_than)) - self.play(ShowCreation(lightbulb)) - self.wait() - self.play(FadeOut(comparitive_johann), FadeOut(lightbulb)) - self.play(ApplyMethod( - Mobject(johann, bubble).scale, 10, - run_time = 3 - )) - - -class NewtonVsJohann(Scene): - def construct(self): - newton, johann = [ - ImageMobject(name, invert = False).scale(0.5) - for name in ("Newton", "Johann_Bernoulli2") - ] - greater_than = TexMobject(">") - newton.next_to(greater_than, RIGHT) - johann.next_to(greater_than, LEFT) - self.add(johann, greater_than, newton) - for i in range(2): - kwargs = { - "path_func" : counterclockwise_path(), - "run_time" : 2 - } - self.play( - ApplyMethod(newton.replace, johann, **kwargs), - ApplyMethod(johann.replace, newton, **kwargs), - ) - self.wait() - - -class JohannThinksOfFermat(Scene): - def construct(self): - johann, fermat = [ - ImageMobject(name, invert = False) - for name in ("Johann_Bernoulli2", "Pierre_de_Fermat") - ] - johann.scale(0.2) - johann.to_corner(DOWN+LEFT) - bubble = ThoughtBubble(initial_width = 12) - bubble.stretch_to_fit_height(6) - bubble.pin_to(johann) - bubble.shift(DOWN) - bubble.add_content(fermat) - fermat.scale_in_place(0.4) - - - self.add(johann, bubble) - self.wait() - self.play(FadeIn(fermat)) - self.wait() - - -class MathematiciansOfEurope(Scene): - def construct(self): - europe = ImageMobject("Europe", use_cache = False) - self.add(europe) - self.freeze_background() - - mathematicians = [ - ("Newton", [-1.75, -0.75, 0]), - ("Jacob_Bernoulli",[-0.75, -1.75, 0]), - ("Ehrenfried_von_Tschirnhaus",[0.5, -0.5, 0]), - ("Gottfried_Wilhelm_von_Leibniz",[0.2, -1.75, 0]), - ("Guillaume_de_L'Hopital", [-1.75, -1.25, 0]), - ] - - for name, point in mathematicians: - man = ImageMobject(name, invert = False) - if name == "Newton": - name = "Isaac_Newton" - name_mob = TextMobject(name.replace("_", " ")) - name_mob.to_corner(UP+LEFT, buff=0.75) - self.add(name_mob) - man.set_height(4) - mobject = Point(man.get_corner(UP+LEFT)) - self.play(Transform(mobject, man)) - man.scale(0.2) - man.shift(point) - self.play(Transform(mobject, man)) - self.remove(name_mob) - -class OldNewtonIsDispleased(Scene): - def construct(self): - old_newton = ImageMobject("Old_Newton", invert = False) - old_newton.scale(0.8) - self.add(old_newton) - self.freeze_background() - - words = TextMobject("Note the displeasure") - words.to_corner(UP+RIGHT) - face_point = 1.8*UP+0.5*LEFT - arrow = Arrow(words.get_bottom(), face_point) - - - self.play(ShimmerIn(words)) - self.play(ShowCreation(arrow)) - self.wait() - - -class NewtonConsideredEveryoneBeneathHim(Scene): - def construct(self): - mathematicians = [ - ImageMobject(name, invert = False) - for name in [ - "Old_Newton", - "Johann_Bernoulli2", - "Jacob_Bernoulli", - "Ehrenfried_von_Tschirnhaus", - "Gottfried_Wilhelm_von_Leibniz", - "Guillaume_de_L'Hopital", - ] - ] - newton = mathematicians.pop(0) - newton.scale(0.8) - new_newton = newton.copy() - new_newton.set_height(3) - new_newton.to_edge(UP) - for man in mathematicians: - man.set_width(1.7) - johann = mathematicians.pop(0) - johann.next_to(new_newton, DOWN) - last_left, last_right = johann, johann - for man, count in zip(mathematicians, it.count()): - if count%2 == 0: - man.next_to(last_left, LEFT) - last_left = man - else: - man.next_to(last_right, RIGHT) - last_right = man - - self.play( - Transform(newton, new_newton), - GrowFromCenter(johann) - ) - self.wait() - self.play(FadeIn(Mobject(*mathematicians))) - self.wait() - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/brachistochrone/graveyard.py b/from_3b1b/old/brachistochrone/graveyard.py deleted file mode 100644 index aa2a2155..00000000 --- a/from_3b1b/old/brachistochrone/graveyard.py +++ /dev/null @@ -1,326 +0,0 @@ -import numpy as np -import itertools as it - -from manimlib.imports import * - -from from_3b1b.old.brachistochrone.curves import Cycloid - -class MultilayeredGlass(PhotonScene, ZoomedScene): - CONFIG = { - "num_discrete_layers" : 5, - "num_variables" : 3, - "top_color" : BLUE_E, - "bottom_color" : BLUE_A, - "zoomed_canvas_frame_shape" : (5, 5), - "square_color" : GREEN_B, - } - def construct(self): - self.cycloid = Cycloid(end_theta = np.pi) - self.cycloid.set_color(YELLOW) - self.top = self.cycloid.get_top()[1] - self.bottom = self.cycloid.get_bottom()[1]-1 - self.generate_layers() - self.generate_discrete_path() - photon_run = self.photon_run_along_path( - self.discrete_path, - run_time = 1, - rate_func = rush_into - ) - - - - self.continuous_to_smooth() - self.add(*self.layers) - self.show_layer_variables() - self.play(photon_run) - self.play(ShowCreation(self.discrete_path)) - self.isolate_bend_points() - self.clear() - self.add(*self.layers) - self.show_main_equation() - self.ask_continuous_question() - - def continuous_to_smooth(self): - self.add(*self.layers) - continuous = self.get_continuous_background() - self.add(continuous) - self.wait() - self.play(ShowCreation( - continuous, - rate_func = lambda t : smooth(1-t) - )) - self.remove(continuous) - self.wait() - - def get_continuous_background(self): - glass = FilledRectangle( - height = self.top-self.bottom, - width = FRAME_WIDTH, - ) - glass.sort_points(lambda p : -p[1]) - glass.shift((self.top-glass.get_top()[1])*UP) - glass.set_color_by_gradient(self.top_color, self.bottom_color) - return glass - - def generate_layer_info(self): - self.layer_thickness = float(self.top-self.bottom)/self.num_discrete_layers - self.layer_tops = np.arange( - self.top, self.bottom, -self.layer_thickness - ) - top_rgb, bottom_rgb = [ - np.array(Color(color).get_rgb()) - for color in (self.top_color, self.bottom_color) - ] - epsilon = 1./(self.num_discrete_layers-1) - self.layer_colors = [ - Color(rgb = interpolate(top_rgb, bottom_rgb, alpha)) - for alpha in np.arange(0, 1+epsilon, epsilon) - ] - - def generate_layers(self): - self.generate_layer_info() - def create_region(top, color): - return Region( - lambda x, y : (y < top) & (y > top-self.layer_thickness), - color = color - ) - self.layers = [ - create_region(top, color) - for top, color in zip(self.layer_tops, self.layer_colors) - ] - - - def generate_discrete_path(self): - points = self.cycloid.points - tops = list(self.layer_tops) - tops.append(tops[-1]-self.layer_thickness) - indices = [ - np.argmin(np.abs(points[:, 1]-top)) - for top in tops - ] - self.bend_points = points[indices[1:-1]] - self.path_angles = [] - self.discrete_path = Mobject1D( - color = YELLOW, - density = 3*DEFAULT_POINT_DENSITY_1D - ) - for start, end in zip(indices, indices[1:]): - start_point, end_point = points[start], points[end] - self.discrete_path.add_line( - start_point, end_point - ) - self.path_angles.append( - angle_of_vector(start_point-end_point)-np.pi/2 - ) - self.discrete_path.add_line( - points[end], FRAME_X_RADIUS*RIGHT+(self.layer_tops[-1]-1)*UP - ) - - def show_layer_variables(self): - layer_top_pairs = list(zip( - self.layer_tops[:self.num_variables], - self.layer_tops[1:] - )) - v_equations = [] - start_ys = [] - end_ys = [] - center_paths = [] - braces = [] - for (top1, top2), x in zip(layer_top_pairs, it.count(1)): - eq_mob = TexMobject( - ["v_%d"%x, "=", "\sqrt{\phantom{y_1}}"], - size = "\\Large" - ) - midpoint = UP*(top1+top2)/2 - eq_mob.shift(midpoint) - v_eq = eq_mob.split() - center_paths.append(Line( - midpoint+FRAME_X_RADIUS*LEFT, - midpoint+FRAME_X_RADIUS*RIGHT - )) - brace_endpoints = Mobject( - Point(self.top*UP+x*RIGHT), - Point(top2*UP+x*RIGHT) - ) - brace = Brace(brace_endpoints, RIGHT) - - start_y = TexMobject("y_%d"%x, size = "\\Large") - end_y = start_y.copy() - start_y.next_to(brace, RIGHT) - end_y.shift(v_eq[-1].get_center()) - end_y.shift(0.2*RIGHT) - - v_equations.append(v_eq) - start_ys.append(start_y) - end_ys.append(end_y) - braces.append(brace) - - for v_eq, path, time in zip(v_equations, center_paths, [2, 1, 0.5]): - photon_run = self.photon_run_along_path( - path, - rate_func=linear - ) - self.play( - ShimmerIn(v_eq[0]), - photon_run, - run_time = time - ) - self.wait() - for start_y, brace in zip(start_ys, braces): - self.add(start_y) - self.play(GrowFromCenter(brace)) - self.wait() - quads = list(zip(v_equations, start_ys, end_ys, braces)) - self.equations = [] - for v_eq, start_y, end_y, brace in quads: - self.remove(brace) - self.play( - ShowCreation(v_eq[1]), - ShowCreation(v_eq[2]), - Transform(start_y, end_y) - ) - - v_eq.append(start_y) - self.equations.append(Mobject(*v_eq)) - - def isolate_bend_points(self): - arc_radius = 0.1 - self.activate_zooming() - little_square = self.get_zoomed_camera_mobject() - - for index in range(3): - bend_point = self.bend_points[index] - line = Line( - bend_point+DOWN, - bend_point+UP, - color = WHITE, - density = self.zoom_factor*DEFAULT_POINT_DENSITY_1D - ) - angle_arcs = [] - for i, rotation in [(index, np.pi/2), (index+1, -np.pi/2)]: - arc = Arc(angle = self.path_angles[i]) - arc.scale(arc_radius) - arc.rotate(rotation) - arc.shift(bend_point) - angle_arcs.append(arc) - thetas = [] - for i in [index+1, index+2]: - theta = TexMobject("\\theta_%d"%i) - theta.scale(0.5/self.zoom_factor) - vert = UP if i == index+1 else DOWN - horiz = rotate_vector(vert, np.pi/2) - theta.next_to( - Point(bend_point), - horiz, - buff = 0.01 - ) - theta.shift(1.5*arc_radius*vert) - thetas.append(theta) - figure_marks = [line] + angle_arcs + thetas - - self.play(ApplyMethod( - little_square.shift, - bend_point - little_square.get_center(), - run_time = 2 - )) - self.play(*list(map(ShowCreation, figure_marks))) - self.wait() - equation_frame = little_square.copy() - equation_frame.scale(0.5) - equation_frame.shift( - little_square.get_corner(UP+RIGHT) - \ - equation_frame.get_corner(UP+RIGHT) - ) - equation_frame.scale_in_place(0.9) - self.show_snells(index+1, equation_frame) - self.remove(*figure_marks) - self.disactivate_zooming() - - def show_snells(self, index, frame): - left_text, right_text = [ - "\\dfrac{\\sin(\\theta_%d)}{\\phantom{\\sqrt{y_1}}}"%x - for x in (index, index+1) - ] - left, equals, right = TexMobject( - [left_text, "=", right_text] - ).split() - vs = [] - sqrt_ys = [] - for x, numerator in [(index, left), (index+1, right)]: - v, sqrt_y = [ - TexMobject( - text, size = "\\Large" - ).next_to(numerator, DOWN) - for text in ("v_%d"%x, "\\sqrt{y_%d}"%x) - ] - vs.append(v) - sqrt_ys.append(sqrt_y) - start, end = [ - Mobject( - left.copy(), mobs[0], equals.copy(), right.copy(), mobs[1] - ).replace(frame) - for mobs in (vs, sqrt_ys) - ] - - self.add(start) - self.wait(2) - self.play(Transform( - start, end, - path_func = counterclockwise_path() - )) - self.wait(2) - self.remove(start, end) - - def show_main_equation(self): - self.equation = TexMobject(""" - \\dfrac{\\sin(\\theta)}{\\sqrt{y}} = - \\text{constant} - """) - self.equation.shift(LEFT) - self.equation.shift( - (self.layer_tops[0]-self.equation.get_top())*UP - ) - self.add(self.equation) - self.wait() - - def ask_continuous_question(self): - continuous = self.get_continuous_background() - line = Line( - UP, DOWN, - density = self.zoom_factor*DEFAULT_POINT_DENSITY_1D - ) - theta = TexMobject("\\theta") - theta.scale(0.5/self.zoom_factor) - - self.play( - ShowCreation(continuous), - Animation(self.equation) - ) - self.remove(*self.layers) - self.play(ShowCreation(self.cycloid)) - self.activate_zooming() - little_square = self.get_zoomed_camera_mobject() - - self.add(line) - indices = np.arange( - 0, self.cycloid.get_num_points()-1, 10 - ) - for index in indices: - point = self.cycloid.points[index] - next_point = self.cycloid.points[index+1] - angle = angle_of_vector(point - next_point) - for mob in little_square, line: - mob.shift(point - mob.get_center()) - arc = Arc(angle-np.pi/2, start_angle = np.pi/2) - arc.scale(0.1) - arc.shift(point) - self.add(arc) - if angle > np.pi/2 + np.pi/6: - vect_angle = interpolate(np.pi/2, angle, 0.5) - vect = rotate_vector(RIGHT, vect_angle) - theta.center() - theta.shift(point) - theta.shift(0.15*vect) - self.add(theta) - self.wait(self.frame_duration) - self.remove(arc) \ No newline at end of file diff --git a/from_3b1b/old/brachistochrone/light.py b/from_3b1b/old/brachistochrone/light.py deleted file mode 100644 index 6076465f..00000000 --- a/from_3b1b/old/brachistochrone/light.py +++ /dev/null @@ -1,925 +0,0 @@ -import numpy as np -import itertools as it - -from manimlib.imports import * - -from from_3b1b.old.brachistochrone.curves import \ - Cycloid, PathSlidingScene, RANDY_SCALE_FACTOR, TryManyPaths - - -class Lens(Arc): - CONFIG = { - "radius" : 2, - "angle" : np.pi/2, - "color" : BLUE_B, - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - Arc.__init__(self, self.angle, **kwargs) - - def init_points(self): - Arc.init_points(self) - self.rotate(-np.pi/4) - self.shift(-self.get_left()) - self.add_points(self.copy().rotate(np.pi).points) - - - -class PhotonScene(Scene): - def wavify(self, mobject): - result = mobject.copy() - result.ingest_submobjects() - tangent_vectors = result.points[1:]-result.points[:-1] - lengths = np.apply_along_axis( - get_norm, 1, tangent_vectors - ) - thick_lengths = lengths.repeat(3).reshape((len(lengths), 3)) - unit_tangent_vectors = tangent_vectors/thick_lengths - rot_matrix = np.transpose(rotation_matrix(np.pi/2, OUT)) - normal_vectors = np.dot(unit_tangent_vectors, rot_matrix) - # total_length = np.sum(lengths) - times = np.cumsum(lengths) - nudge_sizes = 0.1*np.sin(2*np.pi*times) - thick_nudge_sizes = nudge_sizes.repeat(3).reshape((len(nudge_sizes), 3)) - nudges = thick_nudge_sizes*normal_vectors - result.points[1:] += nudges - return result - - - def photon_run_along_path(self, path, color = YELLOW, **kwargs): - if "rate_func" not in kwargs: - kwargs["rate_func"] = None - photon = self.wavify(path) - photon.set_color(color) - return ShowPassingFlash(photon, **kwargs) - - -class SimplePhoton(PhotonScene): - def construct(self): - text = TextMobject("Light") - text.to_edge(UP) - self.play(ShimmerIn(text)) - self.play(self.photon_run_along_path( - Cycloid(), rate_func=linear - )) - self.wait() - - -class MultipathPhotonScene(PhotonScene): - CONFIG = { - "num_paths" : 5 - } - def run_along_paths(self, **kwargs): - paths = self.get_paths() - colors = Color(YELLOW).range_to(WHITE, len(paths)) - for path, color in zip(paths, colors): - path.set_color(color) - photon_runs = [ - self.photon_run_along_path(path) - for path in paths - ] - for photon_run, path in zip(photon_runs, paths): - self.play( - photon_run, - ShowCreation( - path, - rate_func = lambda t : 0.9*smooth(t) - ), - **kwargs - ) - self.wait() - - def generate_paths(self): - raise Exception("Not Implemented") - - -class PhotonThroughLens(MultipathPhotonScene): - def construct(self): - self.lens = Lens() - self.add(self.lens) - self.run_along_paths() - - - def get_paths(self): - interval_values = np.arange(self.num_paths).astype('float') - interval_values /= (self.num_paths-1.) - first_contact = [ - self.lens.point_from_proportion(0.4*v+0.55) - for v in reversed(interval_values) - ] - second_contact = [ - self.lens.point_from_proportion(0.3*v + 0.1) - for v in interval_values - ] - focal_point = 2*RIGHT - return [ - Mobject( - Line(FRAME_X_RADIUS*LEFT + fc[1]*UP, fc), - Line(fc, sc), - Line(sc, focal_point), - Line(focal_point, 6*focal_point-5*sc) - ).ingest_submobjects() - for fc, sc in zip(first_contact, second_contact) - ] - -class TransitionToOptics(PhotonThroughLens): - def construct(self): - optics = TextMobject("Optics") - optics.to_edge(UP) - self.add(optics) - self.has_started = False - PhotonThroughLens.construct(self) - - def play(self, *args, **kwargs): - if not self.has_started: - self.has_started = True - everything = Mobject(*self.mobjects) - vect = FRAME_WIDTH*RIGHT - everything.shift(vect) - self.play(ApplyMethod( - everything.shift, -vect, - rate_func = rush_from - )) - Scene.play(self, *args, **kwargs) - - -class PhotonOffMirror(MultipathPhotonScene): - def construct(self): - self.mirror = Line(*FRAME_Y_RADIUS*np.array([DOWN, UP])) - self.mirror.set_color(GREY) - self.add(self.mirror) - self.run_along_paths() - - def get_paths(self): - interval_values = np.arange(self.num_paths).astype('float') - interval_values /= (self.num_paths-1) - anchor_points = [ - self.mirror.point_from_proportion(0.6*v+0.3) - for v in interval_values - ] - start_point = 5*LEFT+3*UP - end_points = [] - for point in anchor_points: - vect = start_point-point - vect[1] *= -1 - end_points.append(point+2*vect) - return [ - Mobject( - Line(start_point, anchor_point), - Line(anchor_point, end_point) - ).ingest_submobjects() - for anchor_point, end_point in zip(anchor_points, end_points) - ] - -class PhotonsInWater(MultipathPhotonScene): - def construct(self): - water = Region(lambda x, y : y < 0, color = BLUE_E) - self.add(water) - self.run_along_paths() - - def get_paths(self): - x, y = -3, 3 - start_point = x*RIGHT + y*UP - angles = np.arange(np.pi/18, np.pi/3, np.pi/18) - midpoints = y*np.arctan(angles) - end_points = midpoints + FRAME_Y_RADIUS*np.arctan(2*angles) - return [ - Mobject( - Line(start_point, [midpoint, 0, 0]), - Line([midpoint, 0, 0], [end_point, -FRAME_Y_RADIUS, 0]) - ).ingest_submobjects() - for midpoint, end_point in zip(midpoints, end_points) - ] - - -class ShowMultiplePathsScene(PhotonScene): - def construct(self): - text = TextMobject("Which path minimizes travel time?") - text.to_edge(UP) - self.generate_start_and_end_points() - point_a = Dot(self.start_point) - point_b = Dot(self.end_point) - A = TextMobject("A").next_to(point_a, UP) - B = TextMobject("B").next_to(point_b, DOWN) - paths = self.get_paths() - - for point, letter in [(point_a, A), (point_b, B)]: - self.play( - ShowCreation(point), - ShimmerIn(letter) - ) - self.play(ShimmerIn(text)) - curr_path = paths[0].copy() - curr_path_copy = curr_path.copy().ingest_submobjects() - self.play( - self.photon_run_along_path(curr_path), - ShowCreation(curr_path_copy, rate_func = rush_into) - ) - self.remove(curr_path_copy) - for path in paths[1:] + [paths[0]]: - self.play(Transform(curr_path, path, run_time = 4)) - self.wait() - self.path = curr_path.ingest_submobjects() - - def generate_start_and_end_points(self): - raise Exception("Not Implemented") - - def get_paths(self): - raise Exception("Not implemented") - - -class ShowMultiplePathsThroughLens(ShowMultiplePathsScene): - def construct(self): - self.lens = Lens() - self.add(self.lens) - ShowMultiplePathsScene.construct(self) - - def generate_start_and_end_points(self): - self.start_point = 3*LEFT + UP - self.end_point = 2*RIGHT - - def get_paths(self): - alphas = [0.25, 0.4, 0.58, 0.75] - lower_right, upper_right, upper_left, lower_left = list(map( - self.lens.point_from_proportion, alphas - )) - return [ - Mobject( - Line(self.start_point, a), - Line(a, b), - Line(b, self.end_point) - ).set_color(color) - for (a, b), color in zip( - [ - (upper_left, upper_right), - (upper_left, lower_right), - (lower_left, lower_right), - (lower_left, upper_right), - ], - Color(YELLOW).range_to(WHITE, 4) - ) - ] - - -class ShowMultiplePathsOffMirror(ShowMultiplePathsScene): - def construct(self): - mirror = Line(*FRAME_Y_RADIUS*np.array([DOWN, UP])) - mirror.set_color(GREY) - self.add(mirror) - ShowMultiplePathsScene.construct(self) - - def generate_start_and_end_points(self): - self.start_point = 4*LEFT + 2*UP - self.end_point = 4*LEFT + 2*DOWN - - def get_paths(self): - return [ - Mobject( - Line(self.start_point, midpoint), - Line(midpoint, self.end_point) - ).set_color(color) - for midpoint, color in zip( - [2*UP, 2*DOWN], - Color(YELLOW).range_to(WHITE, 2) - ) - ] - - -class ShowMultiplePathsInWater(ShowMultiplePathsScene): - def construct(self): - glass = Region(lambda x, y : y < 0, color = BLUE_E) - self.generate_start_and_end_points() - straight = Line(self.start_point, self.end_point) - slow = TextMobject("Slow") - slow.rotate(np.arctan(straight.get_slope())) - slow.shift(straight.points[int(0.7*straight.get_num_points())]) - slow.shift(0.5*DOWN) - too_long = TextMobject("Too long") - too_long.shift(UP) - air = TextMobject("Air").shift(2*UP) - water = TextMobject("Water").shift(2*DOWN) - - self.add(glass) - self.play(GrowFromCenter(air)) - self.play(GrowFromCenter(water)) - self.wait() - self.remove(air, water) - ShowMultiplePathsScene.construct(self) - self.play( - Transform(self.path, straight) - ) - self.wait() - self.play(GrowFromCenter(slow)) - self.wait() - self.remove(slow) - self.leftmost.ingest_submobjects() - self.play(Transform(self.path, self.leftmost, run_time = 3)) - self.wait() - self.play(ShimmerIn(too_long)) - self.wait() - - - def generate_start_and_end_points(self): - self.start_point = 3*LEFT + 2*UP - self.end_point = 3*RIGHT + 2*DOWN - - def get_paths(self): - self.leftmost, self.rightmost = result = [ - Mobject( - Line(self.start_point, midpoint), - Line(midpoint, self.end_point) - ).set_color(color) - for midpoint, color in zip( - [3*LEFT, 3*RIGHT], - Color(YELLOW).range_to(WHITE, 2) - ) - ] - return result - - -class StraightLinesFastestInConstantMedium(PhotonScene): - def construct(self): - kwargs = {"size" : "\\Large"} - left = TextMobject("Speed of light is constant", **kwargs) - arrow = TexMobject("\\Rightarrow", **kwargs) - right = TextMobject("Staight path is fastest", **kwargs) - left.next_to(arrow, LEFT) - right.next_to(arrow, RIGHT) - squaggle, line = self.get_paths() - - self.play(*list(map(ShimmerIn, [left, arrow, right]))) - self.play(ShowCreation(squaggle)) - self.play(self.photon_run_along_path( - squaggle, run_time = 2, rate_func=linear - )) - self.play(Transform( - squaggle, line, - path_func = path_along_arc(np.pi) - )) - self.play(self.photon_run_along_path(line, rate_func=linear)) - self.wait() - - - def get_paths(self): - squaggle = ParametricFunction( - lambda t : (0.5*t+np.cos(t))*RIGHT+np.sin(t)*UP, - start = -np.pi, - end = 2*np.pi - ) - squaggle.shift(2*UP) - start, end = squaggle.points[0], squaggle.points[-1] - line = Line(start, end) - result = [squaggle, line] - for mob in result: - mob.set_color(BLUE_D) - return result - -class PhtonBendsInWater(PhotonScene, ZoomedScene): - def construct(self): - glass = Region(lambda x, y : y < 0, color = BLUE_E) - kwargs = { - "density" : self.zoom_factor*DEFAULT_POINT_DENSITY_1D - } - top_line = Line(FRAME_Y_RADIUS*UP+2*LEFT, ORIGIN, **kwargs) - extension = Line(ORIGIN, FRAME_Y_RADIUS*DOWN+2*RIGHT, **kwargs) - bottom_line = Line(ORIGIN, FRAME_Y_RADIUS*DOWN+RIGHT, **kwargs) - path1 = Mobject(top_line, extension) - path2 = Mobject(top_line, bottom_line) - for mob in path1, path2: - mob.ingest_submobjects() - extension.set_color(RED) - theta1 = np.arctan(bottom_line.get_slope()) - theta2 = np.arctan(extension.get_slope()) - arc = Arc(theta2-theta1, start_angle = theta1, radius = 2) - question_mark = TextMobject("$\\theta$?") - question_mark.shift(arc.get_center()+0.5*DOWN+0.25*RIGHT) - wave = self.wavify(path2) - wave.set_color(YELLOW) - wave.scale(0.5) - - self.add(glass) - self.play(ShowCreation(path1)) - self.play(Transform(path1, path2)) - self.wait() - # self.activate_zooming() - self.wait() - self.play(ShowPassingFlash( - wave, run_time = 3, rate_func=linear - )) - self.wait() - self.play(ShowCreation(extension)) - self.play( - ShowCreation(arc), - ShimmerIn(question_mark) - ) - -class LightIsFasterInAirThanWater(ShowMultiplePathsInWater): - def construct(self): - glass = Region(lambda x, y : y < 0, color = BLUE_E) - equation = TexMobject("v_{\\text{air}} > v_{\\text{water}}") - equation.to_edge(UP) - path = Line(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) - path1 = path.copy().shift(2*UP) - path2 = path.copy().shift(2*DOWN) - - self.add(glass) - self.play(ShimmerIn(equation)) - self.wait() - photon_runs = [] - photon_runs.append(self.photon_run_along_path( - path1, rate_func = lambda t : min(1, 1.2*t) - )) - photon_runs.append(self.photon_run_along_path(path2)) - self.play(*photon_runs, **{"run_time" : 2}) - self.wait() - - -class GeometryOfGlassSituation(ShowMultiplePathsInWater): - def construct(self): - glass = Region(lambda x, y : y < 0, color = BLUE_E) - self.generate_start_and_end_points() - left = self.start_point[0]*RIGHT - right = self.end_point[0]*RIGHT - start_x = interpolate(left, right, 0.2) - end_x = interpolate(left, right, 1.0) - left_line = Line(self.start_point, left, color = RED_D) - right_line = Line(self.end_point, right, color = RED_D) - h_1, h_2 = list(map(TexMobject, ["h_1", "h_2"])) - h_1.next_to(left_line, LEFT) - h_2.next_to(right_line, RIGHT) - point_a = Dot(self.start_point) - point_b = Dot(self.end_point) - A = TextMobject("A").next_to(point_a, UP) - B = TextMobject("B").next_to(point_b, DOWN) - - x = start_x - left_brace = Brace(Mobject(Point(left), Point(x))) - right_brace = Brace(Mobject(Point(x), Point(right)), UP) - x_mob = TexMobject("x") - x_mob.next_to(left_brace, DOWN) - w_minus_x = TexMobject("w-x") - w_minus_x.next_to(right_brace, UP) - top_line = Line(self.start_point, x) - bottom_line = Line(x, self.end_point) - top_dist = TexMobject("\\sqrt{h_1^2+x^2}") - top_dist.scale(0.5) - a = 0.3 - n = top_line.get_num_points() - point = top_line.points[int(a*n)] - top_dist.next_to(Point(point), RIGHT, buff = 0.3) - bottom_dist = TexMobject("\\sqrt{h_2^2+(w-x)^2}") - bottom_dist.scale(0.5) - n = bottom_line.get_num_points() - point = bottom_line.points[int((1-a)*n)] - bottom_dist.next_to(Point(point), LEFT, buff = 1) - - end_top_line = Line(self.start_point, end_x) - end_bottom_line = Line(end_x, self.end_point) - end_brace = Brace(Mobject(Point(left), Point(end_x))) - end_x_mob = TexMobject("x").next_to(end_brace, DOWN) - - axes = Mobject( - NumberLine(), - NumberLine().rotate(np.pi/2).shift(7*LEFT) - ) - graph = FunctionGraph( - lambda x : 0.4*(x+1)*(x-3)+4, - x_min = -2, - x_max = 4 - ) - graph.set_color(YELLOW) - Mobject(axes, graph).scale(0.2).to_corner(UP+RIGHT, buff = 1) - axes.add(TexMobject("x", size = "\\small").next_to(axes, RIGHT)) - axes.add(TextMobject("Travel time", size = "\\small").next_to( - axes, UP - )) - new_graph = graph.copy() - midpoint = new_graph.points[new_graph.get_num_points()/2] - new_graph.filter_out(lambda p : p[0] < midpoint[0]) - new_graph.reverse_points() - pairs_for_end_transform = [ - (mob, mob.copy()) - for mob in (top_line, bottom_line, left_brace, x_mob) - ] - - self.add(glass, point_a, point_b, A, B) - line = Mobject(top_line, bottom_line).ingest_submobjects() - self.play(ShowCreation(line)) - self.wait() - self.play( - GrowFromCenter(left_brace), - GrowFromCenter(x_mob) - ) - self.play( - GrowFromCenter(right_brace), - GrowFromCenter(w_minus_x) - ) - self.play(ShowCreation(left_line), ShimmerIn(h_1)) - self.play(ShowCreation(right_line), GrowFromCenter(h_2)) - self.play(ShimmerIn(top_dist)) - self.play(GrowFromCenter(bottom_dist)) - self.wait(3) - self.clear() - self.add(glass, point_a, point_b, A, B, - top_line, bottom_line, left_brace, x_mob) - self.play(ShowCreation(axes)) - kwargs = { - "run_time" : 4, - } - self.play(*[ - Transform(*pair, **kwargs) - for pair in [ - (top_line, end_top_line), - (bottom_line, end_bottom_line), - (left_brace, end_brace), - (x_mob, end_x_mob) - ] - ]+[ShowCreation(graph, **kwargs)]) - self.wait() - self.show_derivatives(graph) - line = self.show_derivatives(new_graph) - self.add(line) - self.play(*[ - Transform(*pair, rate_func = lambda x : 0.3*smooth(x)) - for pair in pairs_for_end_transform - ]) - self.wait() - - def show_derivatives(self, graph, run_time = 2): - step = self.frame_duration/run_time - for a in smooth(np.arange(0, 1-step, step)): - index = int(a*graph.get_num_points()) - p1, p2 = graph.points[index], graph.points[index+1] - line = Line(LEFT, RIGHT) - line.rotate(angle_of_vector(p2-p1)) - line.shift(p1) - self.add(line) - self.wait(self.frame_duration) - self.remove(line) - return line - - -class Spring(Line): - CONFIG = { - "num_loops" : 5, - "loop_radius" : 0.3, - "color" : GREY - } - - def init_points(self): - ## self.start, self.end - length = get_norm(self.end-self.start) - angle = angle_of_vector(self.end-self.start) - micro_radius = self.loop_radius/length - m = 2*np.pi*(self.num_loops+0.5) - def loop(t): - return micro_radius*( - RIGHT + np.cos(m*t)*LEFT + np.sin(m*t)*UP - ) - new_epsilon = self.epsilon/(m*micro_radius)/length - - self.add_points([ - t*RIGHT + loop(t) - for t in np.arange(0, 1, new_epsilon) - ]) - self.scale(length/(1+2*micro_radius)) - self.rotate(angle) - self.shift(self.start) - - -class SpringSetup(ShowMultiplePathsInWater): - def construct(self): - self.ring_shift_val = 6*RIGHT - self.slide_kwargs = { - "rate_func" : there_and_back, - "run_time" : 5 - } - - self.setup_background() - rod = Region( - lambda x, y : (abs(x) < 5) & (abs(y) < 0.05), - color = GOLD_E - ) - ring = Arc( - angle = 11*np.pi/6, - start_angle = -11*np.pi/12, - radius = 0.2, - color = YELLOW - ) - ring.shift(-self.ring_shift_val/2) - self.generate_springs(ring) - - - self.add_rod_and_ring(rod, ring) - self.slide_ring(ring) - self.wait() - self.add_springs() - self.add_force_definitions() - self.slide_system(ring) - self.show_horizontal_component(ring) - self.show_angles(ring) - self.show_equation() - - - def setup_background(self): - glass = Region(lambda x, y : y < 0, color = BLUE_E) - self.generate_start_and_end_points() - point_a = Dot(self.start_point) - point_b = Dot(self.end_point) - A = TextMobject("A").next_to(point_a, UP) - B = TextMobject("B").next_to(point_b, DOWN) - self.add(glass, point_a, point_b, A, B) - - def generate_springs(self, ring): - self.start_springs, self.end_springs = [ - Mobject( - Spring(self.start_point, r.get_top()), - Spring(self.end_point, r.get_bottom()) - ) - for r in (ring, ring.copy().shift(self.ring_shift_val)) - ] - - def add_rod_and_ring(self, rod, ring): - rod_word = TextMobject("Rod") - rod_word.next_to(Point(), UP) - ring_word = TextMobject("Ring") - ring_word.next_to(ring, UP) - self.wait() - self.add(rod) - self.play(ShimmerIn(rod_word)) - self.wait() - self.remove(rod_word) - self.play(ShowCreation(ring)) - self.play(ShimmerIn(ring_word)) - self.wait() - self.remove(ring_word) - - def slide_ring(self, ring): - self.play(ApplyMethod( - ring.shift, self.ring_shift_val, - **self.slide_kwargs - )) - - def add_springs(self): - colors = iter([BLACK, BLUE_E]) - for spring in self.start_springs.split(): - circle = Circle(color = next(colors)) - circle.reverse_points() - circle.scale(spring.loop_radius) - circle.shift(spring.points[0]) - - self.play(Transform(circle, spring)) - self.remove(circle) - self.add(spring) - self.wait() - - def add_force_definitions(self): - top_force = TexMobject("F_1 = \\dfrac{1}{v_{\\text{air}}}") - bottom_force = TexMobject("F_2 = \\dfrac{1}{v_{\\text{water}}}") - top_spring, bottom_spring = self.start_springs.split() - top_force.next_to(top_spring) - bottom_force.next_to(bottom_spring, DOWN, buff = -0.5) - words = TextMobject(""" - The force in a real spring is - proportional to that spring's length - """) - words.to_corner(UP+RIGHT) - for force in top_force, bottom_force: - self.play(GrowFromCenter(force)) - self.wait() - self.play(ShimmerIn(words)) - self.wait(3) - self.remove(top_force, bottom_force, words) - - def slide_system(self, ring): - equilibrium_slide_kwargs = dict(self.slide_kwargs) - def jiggle_to_equilibrium(t): - return 0.7*(1+((1-t)**2)*(-np.cos(10*np.pi*t))) - equilibrium_slide_kwargs = { - "rate_func" : jiggle_to_equilibrium, - "run_time" : 3 - } - start = Mobject(ring, self.start_springs) - end = Mobject( - ring.copy().shift(self.ring_shift_val), - self.end_springs - ) - for kwargs in self.slide_kwargs, equilibrium_slide_kwargs: - self.play(Transform(start, end, **kwargs)) - self.wait() - - def show_horizontal_component(self, ring): - v_right = Vector(ring.get_top(), RIGHT) - v_left = Vector(ring.get_bottom(), LEFT) - self.play(*list(map(ShowCreation, [v_right, v_left]))) - self.wait() - self.remove(v_right, v_left) - - def show_angles(self, ring): - ring_center = ring.get_center() - lines, arcs, thetas = [], [], [] - counter = it.count(1) - for point in self.start_point, self.end_point: - line = Line(point, ring_center, color = GREY) - angle = np.pi/2-np.abs(np.arctan(line.get_slope())) - arc = Arc(angle, radius = 0.5).rotate(np.pi/2) - if point is self.end_point: - arc.rotate(np.pi) - theta = TexMobject("\\theta_%d"%next(counter)) - theta.scale(0.5) - theta.shift(2*arc.get_center()) - arc.shift(ring_center) - theta.shift(ring_center) - - lines.append(line) - arcs.append(arc) - thetas.append(theta) - vert_line = Line(2*UP, 2*DOWN) - vert_line.shift(ring_center) - top_spring, bottom_spring = self.start_springs.split() - - self.play( - Transform(ring, Point(ring_center)), - Transform(top_spring, lines[0]), - Transform(bottom_spring, lines[1]) - ) - self.play(ShowCreation(vert_line)) - anims = [] - for arc, theta in zip(arcs, thetas): - anims += [ - ShowCreation(arc), - GrowFromCenter(theta) - ] - self.play(*anims) - self.wait() - - def show_equation(self): - equation = TexMobject([ - "\\left(\\dfrac{1}{\\phantom{v_air}}\\right)", - "\\sin(\\theta_1)", - "=", - "\\left(\\dfrac{1}{\\phantom{v_water}}\\right)", - "\\sin(\\theta_2)" - ]) - equation.to_corner(UP+RIGHT) - frac1, sin1, equals, frac2, sin2 = equation.split() - v_air, v_water = [ - TexMobject("v_{\\text{%s}}"%s, size = "\\Large") - for s in ("air", "water") - ] - v_air.next_to(Point(frac1.get_center()), DOWN) - v_water.next_to(Point(frac2.get_center()), DOWN) - frac1.add(v_air) - frac2.add(v_water) - f1, f2 = [ - TexMobject("F_%d"%d, size = "\\Large") - for d in (1, 2) - ] - f1.next_to(sin1, LEFT) - f2.next_to(equals, RIGHT) - sin2_start = sin2.copy().next_to(f2, RIGHT) - bar1 = TexMobject("\\dfrac{\\qquad}{\\qquad}") - bar2 = bar1.copy() - bar1.next_to(sin1, DOWN) - bar2.next_to(sin2, DOWN) - v_air_copy = v_air.copy().next_to(bar1, DOWN) - v_water_copy = v_water.copy().next_to(bar2, DOWN) - bars = Mobject(bar1, bar2) - new_eq = equals.copy().center().shift(bars.get_center()) - snells = TextMobject("Snell's Law") - snells.set_color(YELLOW) - snells.shift(new_eq.get_center()[0]*RIGHT) - snells.shift(UP) - - anims = [] - for mob in f1, sin1, equals, f2, sin2_start: - anims.append(ShimmerIn(mob)) - self.play(*anims) - self.wait() - for f, frac in (f1, frac1), (f2, frac2): - target = frac.copy().ingest_submobjects() - also = [] - if f is f2: - also.append(Transform(sin2_start, sin2)) - sin2 = sin2_start - self.play(Transform(f, target), *also) - self.remove(f) - self.add(frac) - self.wait() - self.play( - FadeOut(frac1), - FadeOut(frac2), - Transform(v_air, v_air_copy), - Transform(v_water, v_water_copy), - ShowCreation(bars), - Transform(equals, new_eq) - ) - self.wait() - frac1 = Mobject(sin1, bar1, v_air) - frac2 = Mobject(sin2, bar2, v_water) - for frac, vect in (frac1, LEFT), (frac2, RIGHT): - self.play(ApplyMethod( - frac.next_to, equals, vect - )) - self.wait() - self.play(ShimmerIn(snells)) - self.wait() - -class WhatGovernsTheSpeedOfLight(PhotonScene, PathSlidingScene): - def construct(self): - randy = Randolph() - randy.scale(RANDY_SCALE_FACTOR) - randy.shift(-randy.get_bottom()) - self.add_cycloid_end_points() - - self.add(self.cycloid) - self.slide(randy, self.cycloid) - self.play(self.photon_run_along_path(self.cycloid)) - - self.wait() - -class WhichPathWouldLightTake(PhotonScene, TryManyPaths): - def construct(self): - words = TextMobject( - ["Which path ", "would \\emph{light} take", "?"] - ) - words.split()[1].set_color(YELLOW) - words.to_corner(UP+RIGHT) - self.add_cycloid_end_points() - - anims = [ - self.photon_run_along_path( - path, - rate_func = smooth - ) - for path in self.get_paths() - ] - self.play(anims[0], ShimmerIn(words)) - for anim in anims[1:]: - self.play(anim) - - - -class StateSnellsLaw(PhotonScene): - def construct(self): - point_a = 3*LEFT+3*UP - point_b = 1.5*RIGHT+3*DOWN - midpoint = ORIGIN - - lines, arcs, thetas = [], [], [] - counter = it.count(1) - for point in point_a, point_b: - line = Line(point, midpoint, color = RED_D) - angle = np.pi/2-np.abs(np.arctan(line.get_slope())) - arc = Arc(angle, radius = 0.5).rotate(np.pi/2) - if point is point_b: - arc.rotate(np.pi) - line.reverse_points() - theta = TexMobject("\\theta_%d"%next(counter)) - theta.scale(0.5) - theta.shift(2*arc.get_center()) - arc.shift(midpoint) - theta.shift(midpoint) - - lines.append(line) - arcs.append(arc) - thetas.append(theta) - vert_line = Line(2*UP, 2*DOWN) - vert_line.shift(midpoint) - path = Mobject(*lines).ingest_submobjects() - glass = Region(lambda x, y : y < 0, color = BLUE_E) - self.add(glass) - equation = TexMobject([ - "\\dfrac{\\sin(\\theta_1)}{v_{\\text{air}}}", - "=", - "\\dfrac{\\sin(\\theta_2)}{v_{\\text{water}}}", - ]) - equation.to_corner(UP+RIGHT) - exp1, equals, exp2 = equation.split() - snells_law = TextMobject("Snell's Law:") - snells_law.set_color(YELLOW) - snells_law.to_edge(UP) - - self.play(ShimmerIn(snells_law)) - self.wait() - self.play(ShowCreation(path)) - self.play(self.photon_run_along_path(path)) - self.wait() - self.play(ShowCreation(vert_line)) - self.play(*list(map(ShowCreation, arcs))) - self.play(*list(map(GrowFromCenter, thetas))) - self.wait() - self.play(ShimmerIn(exp1)) - self.wait() - self.play(*list(map(ShimmerIn, [equals, exp2]))) - self.wait() - - - - - - - - - - diff --git a/from_3b1b/old/brachistochrone/misc.py b/from_3b1b/old/brachistochrone/misc.py deleted file mode 100644 index a887d348..00000000 --- a/from_3b1b/old/brachistochrone/misc.py +++ /dev/null @@ -1,495 +0,0 @@ -import numpy as np -import itertools as it - -from manimlib.imports import * -from from_3b1b.old.brachistochrone.curves import Cycloid - -class PhysicalIntuition(Scene): - def construct(self): - n_terms = 4 - def func(xxx_todo_changeme): - (x, y, ignore) = xxx_todo_changeme - z = complex(x, y) - if (np.abs(x%1 - 0.5)<0.01 and y < 0.01) or np.abs(z)<0.01: - return ORIGIN - out_z = 1./(2*np.tan(np.pi*z)*(z**2)) - return out_z.real*RIGHT - out_z.imag*UP - arrows = Mobject(*[ - Arrow(ORIGIN, np.sqrt(2)*point) - for point in compass_directions(4, RIGHT+UP) - ]) - arrows.set_color(YELLOW) - arrows.ingest_submobjects() - all_arrows = Mobject(*[ - arrows.copy().scale(0.3/(x)).shift(x*RIGHT) - for x in range(1, n_terms+2) - ]) - terms = TexMobject([ - "\\dfrac{1}{%d^2} + "%(x+1) - for x in range(n_terms) - ]+["\\cdots"]) - terms.shift(2*UP) - plane = NumberPlane(color = BLUE_E) - axes = Mobject(NumberLine(), NumberLine().rotate(np.pi/2)) - axes.set_color(WHITE) - - for term in terms.split(): - self.play(ShimmerIn(term, run_time = 0.5)) - self.wait() - self.play(ShowCreation(plane), ShowCreation(axes)) - self.play(*[ - Transform(*pair) - for pair in zip(terms.split(), all_arrows.split()) - ]) - self.play(PhaseFlow( - func, plane, - run_time = 5, - virtual_time = 8 - )) - - - -class TimeLine(Scene): - def construct(self): - dated_events = [ - { - "date" : 1696, - "text": "Johann Bernoulli poses Brachistochrone problem", - "picture" : "Johann_Bernoulli2" - }, - { - "date" : 1662, - "text" : "Fermat states his principle of least time", - "picture" : "Pierre_de_Fermat" - } - ] - speical_dates = [2016] + [ - obj["date"] for obj in dated_events - ] - centuries = list(range(1600, 2100, 100)) - timeline = NumberLine( - numerical_radius = 300, - number_at_center = 1800, - unit_length_to_spatial_width = FRAME_X_RADIUS/100, - tick_frequency = 10, - numbers_with_elongated_ticks = centuries - ) - timeline.add_numbers(*centuries) - centers = [ - Point(timeline.number_to_point(year)) - for year in speical_dates - ] - timeline.add(*centers) - timeline.shift(-centers[0].get_center()) - - self.add(timeline) - self.wait() - run_times = iter([3, 1]) - for point, event in zip(centers[1:], dated_events): - self.play(ApplyMethod( - timeline.shift, -point.get_center(), - run_time = next(run_times) - )) - picture = ImageMobject(event["picture"], invert = False) - picture.set_width(2) - picture.to_corner(UP+RIGHT) - event_mob = TextMobject(event["text"]) - event_mob.shift(2*LEFT+2*UP) - date_mob = TexMobject(str(event["date"])) - date_mob.scale(0.5) - date_mob.shift(0.6*UP) - line = Line(event_mob.get_bottom(), 0.2*UP) - self.play( - ShimmerIn(event_mob), - ShowCreation(line), - ShimmerIn(date_mob) - ) - self.play(FadeIn(picture)) - self.wait(3) - self.play(*list(map(FadeOut, [event_mob, date_mob, line, picture]))) - - -class StayedUpAllNight(Scene): - def construct(self): - clock = Circle(radius = 2, color = WHITE) - clock.add(Dot(ORIGIN)) - ticks = Mobject(*[ - Line(1.8*vect, 2*vect, color = GREY) - for vect in compass_directions(12) - ]) - clock.add(ticks) - hour_hand = Line(ORIGIN, UP) - minute_hand = Line(ORIGIN, 1.5*UP) - clock.add(hour_hand, minute_hand) - clock.to_corner(UP+RIGHT) - hour_hand.get_center = lambda : clock.get_center() - minute_hand.get_center = lambda : clock.get_center() - - solution = ImageMobject( - "Newton_brachistochrone_solution2", - use_cache = False - ) - solution.stroke_width = 3 - solution.set_color(GREY) - solution.set_width(5) - solution.to_corner(UP+RIGHT) - newton = ImageMobject("Old_Newton", invert = False) - newton.scale(0.8) - phil_trans = TextMobject("Philosophical Transactions") - rect = Rectangle(height = 6, width = 4.5, color = WHITE) - rect.to_corner(UP+RIGHT) - rect.shift(DOWN) - phil_trans.set_width(0.8*rect.get_width()) - phil_trans.next_to(Point(rect.get_top()), DOWN) - new_solution = solution.copy() - new_solution.set_width(phil_trans.get_width()) - new_solution.next_to(phil_trans, DOWN, buff = 1) - not_newton = TextMobject("-Totally not by Newton") - not_newton.set_width(2.5) - not_newton.next_to(new_solution, DOWN, aligned_edge = RIGHT) - phil_trans.add(rect) - - newton_complaint = TextMobject([ - "``I do not love to be", - " \\emph{dunned} ", - "and teased by foreigners''" - ], size = "\\small") - newton_complaint.to_edge(UP, buff = 0.2) - dunned = newton_complaint.split()[1] - dunned.set_color() - dunned_def = TextMobject("(old timey term for making \\\\ demands on someone)") - dunned_def.scale(0.7) - dunned_def.next_to(phil_trans, LEFT) - dunned_def.shift(2*UP) - dunned_arrow = Arrow(dunned_def, dunned) - - johann = ImageMobject("Johann_Bernoulli2", invert = False) - johann.scale(0.4) - johann.to_edge(LEFT) - johann.shift(DOWN) - johann_quote = TextMobject("``I recognize the lion by his claw''") - johann_quote.next_to(johann, UP, aligned_edge = LEFT) - - self.play(ApplyMethod(newton.to_edge, LEFT)) - self.play(ShowCreation(clock)) - kwargs = { - "axis" : OUT, - "rate_func" : smooth - } - self.play( - Rotating(hour_hand, radians = -2*np.pi, **kwargs), - Rotating(minute_hand, radians = -12*2*np.pi, **kwargs), - run_time = 5 - ) - self.wait() - self.clear() - self.add(newton) - clock.ingest_submobjects() - self.play(Transform(clock, solution)) - self.remove(clock) - self.add(solution) - self.wait() - self.play( - FadeIn(phil_trans), - Transform(solution, new_solution) - ) - self.wait() - self.play(ShimmerIn(not_newton)) - phil_trans.add(solution, not_newton) - self.wait() - self.play(*list(map(ShimmerIn, newton_complaint.split()))) - self.wait() - self.play( - ShimmerIn(dunned_def), - ShowCreation(dunned_arrow) - ) - self.wait() - self.remove(dunned_def, dunned_arrow) - self.play(FadeOut(newton_complaint)) - self.remove(newton_complaint) - self.play( - FadeOut(newton), - GrowFromCenter(johann) - ) - self.remove(newton) - self.wait() - self.play(ShimmerIn(johann_quote)) - self.wait() - - -class ThetaTGraph(Scene): - def construct(self): - t_axis = NumberLine() - theta_axis = NumberLine().rotate(np.pi/2) - theta_mob = TexMobject("\\theta(t)") - t_mob = TexMobject("t") - theta_mob.next_to(theta_axis, RIGHT) - theta_mob.to_edge(UP) - t_mob.next_to(t_axis, UP) - t_mob.to_edge(RIGHT) - graph = ParametricFunction( - lambda t : 4*t*RIGHT + 2*smooth(t)*UP - ) - line = Line(graph.points[0], graph.points[-1], color = WHITE) - q_mark = TextMobject("?") - q_mark.next_to(Point(graph.get_center()), LEFT) - stars = Stars(color = BLACK) - stars.scale(0.1).shift(q_mark.get_center()) - - - squiggle = ParametricFunction( - lambda t : t*RIGHT + 0.2*t*(5-t)*(np.sin(t)**2)*UP, - start = 0, - end = 5 - ) - - self.play( - ShowCreation(t_axis), - ShowCreation(theta_axis), - ShimmerIn(theta_mob), - ShimmerIn(t_mob) - ) - self.play( - ShimmerIn(q_mark), - ShowCreation(graph) - ) - self.wait() - self.play( - Transform(q_mark, stars), - Transform(graph, line) - ) - self.wait() - self.play(Transform(graph, squiggle)) - self.wait() - - -class SolutionsToTheBrachistochrone(Scene): - def construct(self): - r_range = np.arange(0.5, 2, 0.25) - cycloids = Mobject(*[ - Cycloid(radius = r, end_theta=2*np.pi) - for r in r_range - ]) - lower_left = 2*DOWN+6*LEFT - lines = Mobject(*[ - Line( - lower_left, - lower_left+5*r*np.cos(np.arctan(r))*RIGHT+2*r*np.sin(np.arctan(r))*UP - ) - for r in r_range - ]) - nl = NumberLine(numbers_with_elongated_ticks = []) - x_axis = nl.copy().shift(3*UP) - y_axis = nl.copy().rotate(np.pi/2).shift(6*LEFT) - t_axis = nl.copy().shift(2*DOWN) - x_label = TexMobject("x") - x_label.next_to(x_axis, DOWN) - x_label.to_edge(RIGHT) - y_label = TexMobject("y") - y_label.next_to(y_axis, RIGHT) - y_label.shift(2*DOWN) - t_label = TexMobject("t") - t_label.next_to(t_axis, UP) - t_label.to_edge(RIGHT) - theta_label = TexMobject("\\theta") - theta_label.next_to(y_axis, RIGHT) - theta_label.to_edge(UP) - words = TextMobject("Boundary conditions?") - words.next_to(lines, RIGHT) - words.shift(2*UP) - - self.play(ShowCreation(x_axis), ShimmerIn(x_label)) - self.play(ShowCreation(y_axis), ShimmerIn(y_label)) - self.play(ShowCreation(cycloids)) - self.wait() - self.play( - Transform(cycloids, lines), - Transform(x_axis, t_axis), - Transform(x_label, t_label), - Transform(y_label, theta_label), - run_time = 2 - ) - self.wait() - self.play(ShimmerIn(words)) - self.wait() - - -class VideoLayout(Scene): - def construct(self): - left, right = 5*LEFT, 5*RIGHT - top_words = TextMobject("The next 15 minutes of your life:") - top_words.to_edge(UP) - line = Line(left, right, color = BLUE_D) - for a in np.arange(0, 4./3, 1./3): - vect = interpolate(left, right, a) - line.add_line(vect+0.2*DOWN, vect+0.2*UP) - left_brace = Brace( - Mobject( - Point(left), - Point(interpolate(left, right, 2./3)) - ), - DOWN - ) - right_brace = Brace( - Mobject( - Point(interpolate(left, right, 2./3)), - Point(right) - ), - UP - ) - left_brace.words = list(map(TextMobject, [ - "Problem statement", - "History", - "Johann Bernoulli's cleverness" - ])) - curr = left_brace - right_brace.words = list(map(TextMobject, [ - "Challenge", - "Mark Levi's cleverness", - ])) - for brace in left_brace, right_brace: - curr = brace - direction = DOWN if brace is left_brace else UP - for word in brace.words: - word.next_to(curr, direction) - curr = word - right_brace.words.reverse() - - self.play(ShimmerIn(top_words)) - self.play(ShowCreation(line)) - for brace in left_brace, right_brace: - self.play(GrowFromCenter(brace)) - self.wait() - for word in brace.words: - self.play(ShimmerIn(word)) - self.wait() - - - - -class ShortestPathProblem(Scene): - def construct(self): - point_a, point_b = 3*LEFT, 3*RIGHT - dots = [] - for point, char in [(point_a, "A"), (point_b, "B")]: - dot = Dot(point) - letter = TexMobject(char) - letter.next_to(dot, UP+LEFT) - dot.add(letter) - dots.append(dot) - - path = ParametricFunction( - lambda t : (t/2 + np.cos(t))*RIGHT + np.sin(t)*UP, - start = -2*np.pi, - end = 2*np.pi - ) - path.scale(6/(2*np.pi)) - path.shift(point_a - path.points[0]) - path.set_color(RED) - line = Line(point_a, point_b) - words = TextMobject("Shortest path from $A$ to $B$") - words.to_edge(UP) - - self.play( - ShimmerIn(words), - *list(map(GrowFromCenter, dots)) - ) - self.play(ShowCreation(path)) - self.play(Transform( - path, line, - path_func = path_along_arc(np.pi) - )) - self.wait() - - -class MathBetterThanTalking(Scene): - def construct(self): - mathy = Mathematician() - mathy.to_corner(DOWN+LEFT) - bubble = ThoughtBubble() - bubble.pin_to(mathy) - bubble.write("Math $>$ Talking about math") - - self.add(mathy) - self.play(ShowCreation(bubble)) - self.play(ShimmerIn(bubble.content)) - self.wait() - self.play(ApplyMethod( - mathy.blink, - rate_func = squish_rate_func(there_and_back, 0.4, 0.6) - )) - - -class DetailsOfProofBox(Scene): - def construct(self): - rect = Rectangle(height = 4, width = 6, color = WHITE) - words = TextMobject("Details of proof") - words.to_edge(UP) - - self.play( - ShowCreation(rect), - ShimmerIn(words) - ) - self.wait() - - - -class TalkedAboutSnellsLaw(Scene): - def construct(self): - randy = Randolph() - randy.to_corner(DOWN+LEFT) - morty = Mortimer() - morty.to_edge(DOWN+RIGHT) - randy.bubble = SpeechBubble().pin_to(randy) - morty.bubble = SpeechBubble().pin_to(morty) - - phrases = [ - "Let's talk about Snell's law", - "I love Snell's law", - "It's like running from \\\\ a beach into the ocean", - "It's like two constant \\\\ tension springs", - ] - - self.add(randy, morty) - talkers = it.cycle([randy, morty]) - for talker, phrase in zip(talkers, phrases): - talker.bubble.write(phrase) - self.play( - FadeIn(talker.bubble), - ShimmerIn(talker.bubble.content) - ) - self.play(ApplyMethod( - talker.blink, - rate_func = squish_rate_func(there_and_back) - )) - self.wait() - self.remove(talker.bubble, talker.bubble.content) - - -class YetAnotherMarkLevi(Scene): - def construct(self): - words = TextMobject("Yet another bit of Mark Levi cleverness") - words.to_edge(UP) - levi = ImageMobject("Mark_Levi", invert = False) - levi.set_width(6) - levi.show() - - self.add(levi) - self.play(ShimmerIn(words)) - self.wait(2) - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/brachistochrone/multilayered.py b/from_3b1b/old/brachistochrone/multilayered.py deleted file mode 100644 index ab86c586..00000000 --- a/from_3b1b/old/brachistochrone/multilayered.py +++ /dev/null @@ -1,480 +0,0 @@ -import numpy as np -import itertools as it - -from manimlib.imports import * -from from_3b1b.old.brachistochrone.light import PhotonScene -from from_3b1b.old.brachistochrone.curves import * - - -class MultilayeredScene(Scene): - CONFIG = { - "n_layers" : 5, - "top_color" : BLUE_E, - "bottom_color" : BLUE_A, - "total_glass_height" : 5, - "top" : 3*UP, - "RectClass" : Rectangle #FilledRectangle - } - - def get_layers(self, n_layers = None): - if n_layers is None: - n_layers = self.n_layers - width = FRAME_WIDTH - height = float(self.total_glass_height)/n_layers - rgb_pair = [ - np.array(Color(color).get_rgb()) - for color in (self.top_color, self.bottom_color) - ] - rgb_range = [ - interpolate(*rgb_pair+[x]) - for x in np.arange(0, 1, 1./n_layers) - ] - tops = [ - self.top + x*height*DOWN - for x in range(n_layers) - ] - color = Color() - result = [] - for top, rgb in zip(tops, rgb_range): - color.set_rgb(rgb) - rect = self.RectClass( - height = height, - width = width, - color = color - ) - rect.shift(top-rect.get_top()) - result.append(rect) - return result - - def add_layers(self): - self.layers = self.get_layers() - self.add(*self.layers) - self.freeze_background() - - def get_bottom(self): - return self.top + self.total_glass_height*DOWN - - def get_continuous_glass(self): - result = self.RectClass( - width = FRAME_WIDTH, - height = self.total_glass_height, - ) - result.sort_points(lambda p : -p[1]) - result.set_color_by_gradient(self.top_color, self.bottom_color) - result.shift(self.top-result.get_top()) - return result - - -class TwoToMany(MultilayeredScene): - CONFIG = { - "RectClass" : FilledRectangle - } - def construct(self): - glass = self.get_glass() - layers = self.get_layers() - - self.add(glass) - self.wait() - self.play(*[ - FadeIn( - layer, - rate_func = squish_rate_func(smooth, x, 1) - ) - for layer, x in zip(layers[1:], it.count(0, 0.2)) - ]+[ - Transform(glass, layers[0]) - ]) - self.wait() - - def get_glass(self): - return self.RectClass( - height = FRAME_Y_RADIUS, - width = FRAME_WIDTH, - color = BLUE_E - ).shift(FRAME_Y_RADIUS*DOWN/2) - - -class RaceLightInLayers(MultilayeredScene, PhotonScene): - CONFIG = { - "RectClass" : FilledRectangle - } - def construct(self): - self.add_layers() - line = Line(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) - lines = [ - line.copy().shift(layer.get_center()) - for layer in self.layers - ] - - def rate_maker(x): - return lambda t : min(x*x*t, 1) - min_rate, max_rate = 1., 2. - rates = np.arange(min_rate, max_rate, (max_rate-min_rate)/self.n_layers) - self.play(*[ - self.photon_run_along_path( - line, - rate_func = rate_maker(rate), - run_time = 2 - ) - for line, rate in zip(lines, rates) - ]) - -class ShowDiscretePath(MultilayeredScene, PhotonScene): - CONFIG = { - "RectClass" : FilledRectangle - } - def construct(self): - self.add_layers() - self.cycloid = Cycloid(end_theta = np.pi) - - self.generate_discrete_path() - self.play(ShowCreation(self.discrete_path)) - self.wait() - self.play(self.photon_run_along_path( - self.discrete_path, - rate_func = rush_into, - run_time = 3 - )) - self.wait() - - - def generate_discrete_path(self): - points = self.cycloid.points - tops = [mob.get_top()[1] for mob in self.layers] - tops.append(tops[-1]-self.layers[0].get_height()) - indices = [ - np.argmin(np.abs(points[:, 1]-top)) - for top in tops - ] - self.bend_points = points[indices[1:-1]] - self.path_angles = [] - self.discrete_path = Mobject1D( - color = WHITE, - density = 3*DEFAULT_POINT_DENSITY_1D - ) - for start, end in zip(indices, indices[1:]): - start_point, end_point = points[start], points[end] - self.discrete_path.add_line( - start_point, end_point - ) - self.path_angles.append( - angle_of_vector(start_point-end_point)-np.pi/2 - ) - self.discrete_path.add_line( - points[end], FRAME_X_RADIUS*RIGHT+(tops[-1]-0.5)*UP - ) - -class NLayers(MultilayeredScene): - CONFIG = { - "RectClass" : FilledRectangle - } - def construct(self): - self.add_layers() - brace = Brace( - Mobject( - Point(self.top), - Point(self.get_bottom()) - ), - RIGHT - ) - n_layers = TextMobject("$n$ layers") - n_layers.next_to(brace) - - self.wait() - - self.add(brace) - self.show_frame() - - self.play( - GrowFromCenter(brace), - GrowFromCenter(n_layers) - ) - self.wait() - -class ShowLayerVariables(MultilayeredScene, PhotonScene): - CONFIG = { - "RectClass" : FilledRectangle - } - def construct(self): - self.add_layers() - v_equations = [] - start_ys = [] - end_ys = [] - center_paths = [] - braces = [] - for layer, x in zip(self.layers[:3], it.count(1)): - eq_mob = TexMobject( - ["v_%d"%x, "=", "\sqrt{\phantom{y_1}}"], - size = "\\Large" - ) - eq_mob.shift(layer.get_center()+2*LEFT) - v_eq = eq_mob.split() - v_eq[0].set_color(layer.get_color()) - path = Line(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) - path.shift(layer.get_center()) - brace_endpoints = Mobject( - Point(self.top), - Point(layer.get_bottom()) - ) - brace = Brace(brace_endpoints, RIGHT) - brace.shift(x*RIGHT) - - start_y = TexMobject("y_%d"%x, size = "\\Large") - end_y = start_y.copy() - start_y.next_to(brace, RIGHT) - end_y.shift(v_eq[-1].get_center()) - nudge = 0.2*RIGHT - end_y.shift(nudge) - - v_equations.append(v_eq) - start_ys.append(start_y) - end_ys.append(end_y) - center_paths.append(path) - braces.append(brace) - - for v_eq, path, time in zip(v_equations, center_paths, [2, 1, 0.5]): - photon_run = self.photon_run_along_path( - path, - rate_func=linear - ) - self.play( - FadeToColor(v_eq[0], WHITE), - photon_run, - run_time = time - ) - self.wait() - - starts = [0, 0.3, 0.6] - self.play(*it.chain(*[ - [ - GrowFromCenter( - mob, - rate_func=squish_rate_func(smooth, start, 1) - ) - for mob, start in zip(mobs, starts) - ] - for mobs in (start_ys, braces) - ])) - self.wait() - - triplets = list(zip(v_equations, start_ys, end_ys)) - anims = [] - for v_eq, start_y, end_y in triplets: - anims += [ - ShowCreation(v_eq[1]), - ShowCreation(v_eq[2]), - Transform(start_y.copy(), end_y) - ] - self.play(*anims) - self.wait() - - -class LimitingProcess(MultilayeredScene): - CONFIG = { - "RectClass" : FilledRectangle - } - def construct(self): - num_iterations = 3 - layer_sets = [ - self.get_layers((2**x)*self.n_layers) - for x in range(num_iterations) - ] - glass_sets = [ - Mobject(*[ - Mobject( - *layer_sets[x][(2**x)*index:(2**x)*(index+1)] - ) - for index in range(self.n_layers) - ]).ingest_submobjects() - for x in range(num_iterations) - ] - glass_sets.append(self.get_continuous_glass()) - for glass_set in glass_sets: - glass_set.sort_points(lambda p : p[1]) - curr_set = glass_sets[0] - self.add(curr_set) - for layer_set in glass_sets[1:]: - self.wait() - self.play(Transform(curr_set, layer_set)) - self.wait() - - - -class ShowLightAndSlidingObject(MultilayeredScene, TryManyPaths, PhotonScene): - CONFIG = { - "show_time" : False, - "wait_and_add" : False, - "RectClass" : FilledRectangle - } - def construct(self): - glass = self.get_continuous_glass() - self.play(ApplyMethod(glass.fade, 0.8)) - self.freeze_background() - - paths = self.get_paths() - for path in paths: - if path.get_height() > self.total_glass_height: - path.stretch(0.7, 1) - path.shift(self.top - path.get_top()) - path.rgbas[:,2] = 0 - loop = paths.pop(1) ##Bad! - randy = Randolph() - randy.scale(RANDY_SCALE_FACTOR) - randy.shift(-randy.get_bottom()) - photon_run = self.photon_run_along_path( - loop, - rate_func = lambda t : smooth(1.2*t, 2), - run_time = 4.1 - ) - text = self.get_text().to_edge(UP, buff = 0.2) - - self.play(ShowCreation(loop)) - self.wait() - self.play(photon_run) - self.remove(photon_run.mobject) - randy = self.slide(randy, loop) - self.add(randy) - self.wait() - self.remove(randy) - self.play(ShimmerIn(text)) - for path in paths: - self.play(Transform( - loop, path, - path_func = path_along_arc(np.pi/2), - run_time = 2 - )) - - -class ContinuouslyObeyingSnellsLaw(MultilayeredScene): - CONFIG = { - "arc_radius" : 0.5, - "RectClass" : FilledRectangle - } - def construct(self): - glass = self.get_continuous_glass() - self.add(glass) - self.freeze_background() - - cycloid = Cycloid(end_theta = np.pi) - cycloid.set_color(YELLOW) - chopped_cycloid = cycloid.copy() - n = cycloid.get_num_points() - chopped_cycloid.filter_out(lambda p : p[1] > 1 and p[0] < 0) - chopped_cycloid.reverse_points() - - - self.play(ShowCreation(cycloid)) - ref_mob = self.snells_law_at_every_point(cycloid, chopped_cycloid) - self.show_equation(chopped_cycloid, ref_mob) - - def snells_law_at_every_point(self, cycloid, chopped_cycloid): - square = Square(side_length = 0.2, color = WHITE) - words = TextMobject(["Snell's law ", "everywhere"]) - snells, rest = words.split() - colon = TextMobject(":") - words.next_to(square) - words.shift(0.3*UP) - combo = Mobject(square, words) - combo.get_center = lambda : square.get_center() - new_snells = snells.copy().center().to_edge(UP, buff = 1.5) - colon.next_to(new_snells) - colon.shift(0.05*DOWN) - - self.play(MoveAlongPath( - combo, cycloid, - run_time = 5 - )) - self.play(MoveAlongPath( - combo, chopped_cycloid, - run_time = 4 - )) - dot = Dot(combo.get_center()) - self.play(Transform(square, dot)) - self.play( - Transform(snells, new_snells), - Transform(rest, colon) - ) - self.wait() - return colon - - def get_marks(self, point1, point2): - vert_line = Line(2*DOWN, 2*UP) - tangent_line = vert_line.copy() - theta = TexMobject("\\theta") - theta.scale(0.5) - angle = angle_of_vector(point1 - point2) - tangent_line.rotate( - angle - tangent_line.get_angle() - ) - angle_from_vert = angle - np.pi/2 - for mob in vert_line, tangent_line: - mob.shift(point1 - mob.get_center()) - arc = Arc(angle_from_vert, start_angle = np.pi/2) - arc.scale(self.arc_radius) - arc.shift(point1) - vect_angle = angle_from_vert/2 + np.pi/2 - vect = rotate_vector(RIGHT, vect_angle) - theta.center() - theta.shift(point1) - theta.shift(1.5*self.arc_radius*vect) - return arc, theta, vert_line, tangent_line - - - def show_equation(self, chopped_cycloid, ref_mob): - point2, point1 = chopped_cycloid.points[-2:] - arc, theta, vert_line, tangent_line = self.get_marks( - point1, point2 - ) - equation = TexMobject([ - "\\sin(\\theta)", - "\\over \\sqrt{y}", - ]) - sin, sqrt_y = equation.split() - equation.next_to(ref_mob) - const = TexMobject(" = \\text{constant}") - const.next_to(equation) - ceil_point = np.array(point1) - ceil_point[1] = self.top[1] - brace = Brace( - Mobject(Point(point1), Point(ceil_point)), - RIGHT - ) - y_mob = TexMobject("y").next_to(brace) - - self.play( - GrowFromCenter(sin), - ShowCreation(arc), - GrowFromCenter(theta) - ) - self.play(ShowCreation(vert_line)) - self.play(ShowCreation(tangent_line)) - self.wait() - self.play( - GrowFromCenter(sqrt_y), - GrowFromCenter(brace), - GrowFromCenter(y_mob) - ) - self.wait() - self.play(Transform( - Point(const.get_left()), const - )) - self.wait() - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/brachistochrone/wordplay.py b/from_3b1b/old/brachistochrone/wordplay.py deleted file mode 100644 index 281885f2..00000000 --- a/from_3b1b/old/brachistochrone/wordplay.py +++ /dev/null @@ -1,554 +0,0 @@ -import numpy as np -import itertools as it -import os - -from manimlib.imports import * -from from_3b1b.old.brachistochrone.drawing_images import sort_by_color - -class Intro(Scene): - def construct(self): - logo = ImageMobject("LogoGeneration", invert = False) - name_mob = TextMobject("3Blue1Brown").center() - name_mob.set_color("grey") - name_mob.shift(2*DOWN) - self.add(name_mob, logo) - - new_text = TextMobject(["with ", "Steven Strogatz"]) - new_text.next_to(name_mob, DOWN) - self.play(*[ - ShimmerIn(part) - for part in new_text.split() - ]) - self.wait() - with_word, steve = new_text.split() - steve_copy = steve.copy().center().to_edge(UP) - # logo.sort_points(lambda p : -get_norm(p)) - sort_by_color(logo) - self.play( - Transform(steve, steve_copy), - DelayByOrder(Transform(logo, Point())), - FadeOut(with_word), - FadeOut(name_mob), - run_time = 3 - ) - - -class IntroduceSteve(Scene): - def construct(self): - name = TextMobject("Steven Strogatz") - name.to_edge(UP) - contributions = TextMobject("Frequent Contributions") - contributions.scale(0.5).to_edge(RIGHT).shift(2*UP) - books_word = TextMobject("Books") - books_word.scale(0.5).to_edge(LEFT).shift(2*UP) - radio_lab, sci_fri, cornell, book2, book3, book4 = [ - ImageMobject(filename, invert = False, filter_color = WHITE) - for filename in [ - "radio_lab", - "science_friday", - "cornell", - "strogatz_book2", - "strogatz_book3", - "strogatz_book4", - ] - ] - book1 = ImageMobject("strogatz_book1", invert = False) - nyt = ImageMobject("new_york_times") - logos = [radio_lab, nyt, sci_fri] - books = [book1, book2, book3, book4] - - sample_size = Square(side_length = 2) - last = contributions - for image in logos: - image.replace(sample_size) - image.next_to(last, DOWN) - last = image - sci_fri.scale_in_place(0.9) - shift_val = 0 - sample_size.scale(0.75) - for book in books: - book.replace(sample_size) - book.next_to(books_word, DOWN) - book.shift(shift_val*(RIGHT+DOWN)) - shift_val += 0.5 - sample_size.scale(2) - cornell.replace(sample_size) - cornell.next_to(name, DOWN) - - self.add(name) - self.play(FadeIn(cornell)) - self.play(ShimmerIn(books_word)) - for book in books: - book.shift(5*LEFT) - self.play(ApplyMethod(book.shift, 5*RIGHT)) - self.play(ShimmerIn(contributions)) - for logo in logos: - self.play(FadeIn(logo)) - self.wait() - -class ShowTweets(Scene): - def construct(self): - tweets = [ - ImageMobject("tweet%d"%x, invert = False) - for x in range(1, 4) - ] - for tweet in tweets: - tweet.scale(0.4) - tweets[0].to_corner(UP+LEFT) - tweets[1].next_to(tweets[0], RIGHT, aligned_edge = UP) - tweets[2].next_to(tweets[1], DOWN) - - self.play(GrowFromCenter(tweets[0])) - for x in 1, 2: - self.play( - Transform(Point(tweets[x-1].get_center()), tweets[x]), - Animation(tweets[x-1]) - ) - self.wait() - -class LetsBeHonest(Scene): - def construct(self): - self.play(ShimmerIn(TextMobject(""" - Let's be honest about who benefits - from this collaboration... - """))) - self.wait() - - -class WhatIsTheBrachistochrone(Scene): - def construct(self): - self.play(ShimmerIn(TextMobject(""" - So \\dots what is the Brachistochrone? - """))) - self.wait() - - -class DisectBrachistochroneWord(Scene): - def construct(self): - word = TextMobject(["Bra", "chis", "to", "chrone"]) - original_word = word.copy() - dots = [] - for part in word.split(): - if dots: - part.next_to(dots[-1], buff = 0.06) - dot = TexMobject("\\cdot") - dot.next_to(part, buff = 0.06) - dots.append(dot) - dots = Mobject(*dots[:-1]) - dots.shift(0.1*DOWN) - Mobject(word, dots).center() - overbrace1 = Brace(Mobject(*word.split()[:-1]), UP) - overbrace2 = Brace(word.split()[-1], UP) - shortest = TextMobject("Shortest") - shortest.next_to(overbrace1, UP) - shortest.set_color(YELLOW) - time = TextMobject("Time") - time.next_to(overbrace2, UP) - time.set_color(YELLOW) - chrono_example = TextMobject(""" - As in ``Chronological'' \\\\ - or ``Synchronize'' - """) - chrono_example.scale(0.5) - chrono_example.to_edge(RIGHT) - chrono_example.shift(2*UP) - chrono_example.set_color(BLUE_D) - chrono_arrow = Arrow( - word.get_right(), - chrono_example.get_bottom(), - color = BLUE_D - ) - brachy_example = TextMobject("As in . . . brachydactyly?") - brachy_example.scale(0.5) - brachy_example.to_edge(LEFT) - brachy_example.shift(2*DOWN) - brachy_example.set_color(GREEN) - brachy_arrow = Arrow( - word.get_left(), - brachy_example.get_top(), - color = GREEN - ) - - pronunciation = TextMobject(["/br", "e", "kist","e","kr$\\bar{o}$n/"]) - pronunciation.split()[1].rotate_in_place(np.pi) - pronunciation.split()[3].rotate_in_place(np.pi) - pronunciation.scale(0.7) - pronunciation.shift(DOWN) - - latin = TextMobject(list("Latin")) - greek = TextMobject(list("Greek")) - for mob in latin, greek: - mob.to_edge(LEFT) - question_mark = TextMobject("?").next_to(greek, buff = 0.1) - stars = Stars().set_color(BLACK) - stars.scale(0.5).shift(question_mark.get_center()) - - self.play(Transform(original_word, word), ShowCreation(dots)) - self.play(ShimmerIn(pronunciation)) - self.wait() - self.play( - GrowFromCenter(overbrace1), - GrowFromCenter(overbrace2) - ) - self.wait() - self.play(ShimmerIn(latin)) - self.play(FadeIn(question_mark)) - self.play(Transform( - latin, greek, - path_func = counterclockwise_path() - )) - self.wait() - self.play(Transform(question_mark, stars)) - self.remove(stars) - self.wait() - self.play(ShimmerIn(shortest)) - self.play(ShimmerIn(time)) - for ex, ar in [(chrono_example, chrono_arrow), (brachy_example, brachy_arrow)]: - self.play( - ShowCreation(ar), - ShimmerIn(ex) - ) - self.wait() - -class OneSolutionTwoInsights(Scene): - def construct(self): - one_solution = TextMobject(["One ", "solution"]) - two_insights = TextMobject(["Two ", " insights"]) - two, insights = two_insights.split() - johann = ImageMobject("Johann_Bernoulli2", invert = False) - mark = ImageMobject("Mark_Levi", invert = False) - for mob in johann, mark: - mob.scale(0.4) - johann.next_to(insights, LEFT) - mark.next_to(johann, RIGHT) - name = TextMobject("Mark Levi").to_edge(UP) - - self.play(*list(map(ShimmerIn, one_solution.split()))) - self.wait() - for pair in zip(one_solution.split(), two_insights.split()): - self.play(Transform(*pair, path_func = path_along_arc(np.pi))) - self.wait() - self.clear() - self.add(two, insights) - for word, man in [(two, johann), (insights, mark)]: - self.play( - Transform(word, Point(word.get_left())), - GrowFromCenter(man) - ) - self.wait() - self.clear() - self.play(ApplyMethod(mark.center)) - self.play(ShimmerIn(name)) - self.wait() - -class CircleOfIdeas(Scene): - def construct(self): - words = list(map(TextMobject, [ - "optics", "calculus", "mechanics", "geometry", "history" - ])) - words[0].set_color(YELLOW) - words[1].set_color(BLUE_D) - words[2].set_color(GREY) - words[3].set_color(GREEN) - words[4].set_color(MAROON) - brachistochrone = TextMobject("Brachistochrone") - displayed_words = [] - for word in words: - anims = self.get_spinning_anims(displayed_words) - word.shift(3*RIGHT) - point = Point() - anims.append(Transform(point, word)) - self.play(*anims) - self.remove(point) - self.add(word) - displayed_words.append(word) - self.play(*self.get_spinning_anims(displayed_words)) - self.play(*[ - Transform( - word, word.copy().set_color(BLACK).center().scale(0.1), - path_func = path_along_arc(np.pi), - rate_func=linear, - run_time = 2 - ) - for word in displayed_words - ]+[ - GrowFromCenter(brachistochrone) - ]) - self.wait() - - def get_spinning_anims(self, words, angle = np.pi/6): - anims = [] - for word in words: - old_center = word.get_center() - new_center = rotate_vector(old_center, angle) - vect = new_center-old_center - anims.append(ApplyMethod( - word.shift, vect, - path_func = path_along_arc(angle), - rate_func=linear - )) - return anims - - -class FermatsPrincipleStatement(Scene): - def construct(self): - words = TextMobject([ - "Fermat's principle:", - """ - If a beam of light travels - from point $A$ to $B$, it does so along the - fastest path possible. - """ - ]) - words.split()[0].set_color(BLUE) - everything = MobjectFromRegion(Region()) - everything.scale(0.9) - angles = np.apply_along_axis( - angle_of_vector, 1, everything.points - ) - norms = np.apply_along_axis( - get_norm, 1, everything.points - ) - norms -= np.min(norms) - norms /= np.max(norms) - alphas = 0.25 + 0.75 * norms * (1 + np.sin(12*angles))/2 - everything.rgbas = alphas.repeat(3).reshape((len(alphas), 3)) - - Mobject(everything, words).show() - - everything.sort_points(get_norm) - self.add(words) - self.play( - DelayByOrder(FadeIn(everything, run_time = 3)), - Animation(words) - ) - self.play( - ApplyMethod(everything.set_color, WHITE), - ) - self.wait() - -class VideoProgression(Scene): - def construct(self): - spacing = 2*UP - brachy, optics, light_in_two, snells, multi = words = [ - TextMobject(text) - for text in [ - "Brachistochrone", - "Optics", - "Light in two media", - "Snell's Law", - "Multilayered glass", - ] - ] - for mob in light_in_two, snells: - mob.shift(-spacing) - arrow1 = Arrow(brachy, optics) - arrow2 = Arrow(optics, snells) - point = Point(DOWN) - - self.play(ShimmerIn(brachy)) - self.wait() - self.play( - ApplyMethod(brachy.shift, spacing), - Transform(point, optics) - ) - optics = point - arrow1 = Arrow(optics, brachy) - self.play(ShowCreation(arrow1)) - self.wait() - arrow2 = Arrow(light_in_two, optics) - self.play( - ShowCreation(arrow2), - ShimmerIn(light_in_two) - ) - self.wait() - self.play( - FadeOut(light_in_two), - GrowFromCenter(snells), - DelayByOrder( - ApplyMethod(arrow2.set_color, BLUE_D) - ) - ) - self.wait() - self.play( - FadeOut(optics), - GrowFromCenter(multi), - DelayByOrder( - ApplyMethod(arrow1.set_color, BLUE_D) - ) - ) - self.wait() - - - - - -class BalanceCompetingFactors(Scene): - args_list = [ - ("Short", "Steep"), - ("Minimal time \\\\ in water", "Short path") - ] - - @staticmethod - def args_to_string(*words): - return "".join([word.split(" ")[0] for word in words]) - - def construct(self, *words): - factor1, factor2 = [ - TextMobject("Factor %d"%x).set_color(c) - for x, c in [ - (1, RED_D), - (2, BLUE_D) - ] - ] - real_factor1, real_factor2 = list(map(TextMobject, words)) - for word in factor1, factor2, real_factor1, real_factor2: - word.shift(0.2*UP-word.get_bottom()) - for f1 in factor1, real_factor1: - f1.set_color(RED_D) - f1.shift(2*LEFT) - for f2 in factor2, real_factor2: - f2.set_color(BLUE_D) - f2.shift(2*RIGHT) - line = Line( - factor1.get_left(), - factor2.get_right() - ) - line.center() - self.balancers = Mobject(factor1, factor2, line) - self.hidden_balancers = Mobject(real_factor1, real_factor2) - - triangle = Polygon(RIGHT, np.sqrt(3)*UP, LEFT) - triangle.next_to(line, DOWN, buff = 0) - - self.add(triangle, self.balancers) - self.rotate(1) - self.rotate(-2) - self.wait() - self.play(Transform( - factor1, real_factor1, - path_func = path_along_arc(np.pi/4) - )) - self.rotate(2) - self.wait() - self.play(Transform( - factor2, real_factor2, - path_func = path_along_arc(np.pi/4) - )) - self.rotate(-2) - self.wait() - self.rotate(1) - - def rotate(self, factor): - angle = np.pi/11 - self.play(Rotate( - self.balancers, - factor*angle, - run_time = abs(factor) - )) - self.hidden_balancers.rotate(factor*angle) - - - - -class Challenge(Scene): - def construct(self): - self.add(TextMobject(""" - Can you find a new solution to the - Brachistochrone problem by finding - an intuitive reason that time-minimizing - curves look like straight lines in - $t$-$\\theta$ space? - """)) - self.wait() - - - -class Section1(Scene): - def construct(self): - self.add(TextMobject("Section 1: Johann Bernoulli's insight")) - self.wait() - -class Section2(Scene): - def construct(self): - self.add(TextMobject( - "Section 2: Mark Levi's insight, and a challenge", - size = "\\large" - )) - self.wait() - - - -class NarratorInterjection(Scene): - def construct(self): - words1 = TexMobject("<\\text{Narrator interjection}>") - words2 = TexMobject("<\\!/\\text{Narrator interjection}>") - self.add(words1) - self.wait() - self.clear() - self.add(words2) - self.wait() - - -class ThisCouldBeTheEnd(Scene): - def construct(self): - words = TextMobject([ - "This could be the end\\dots", - "but\\dots" - ]) - for part in words.split(): - self.play(ShimmerIn(part)) - self.wait() - - -class MyOwnChallenge(Scene): - def construct(self): - self.add(TextMobject("My own challenge:")) - self.wait() - - -class WarmupChallenge(Scene): - def construct(self): - self.add(TextMobject("\\large Warm-up challenge: Confirm this for yourself")) - self.wait() - -class FindAnotherSolution(Scene): - def construct(self): - self.add(TextMobject("Find another brachistochrone solution\\dots")) - self.wait() - - -class ProofOfSnellsLaw(Scene): - def construct(self): - self.add(TextMobject("Proof of Snell's law:")) - self.wait() - - -class CondensedVersion(Scene): - def construct(self): - snells = TextMobject("Snell's") - snells.shift(-snells.get_left()) - snells.to_edge(UP) - for vect in [RIGHT, RIGHT, LEFT, DOWN, DOWN, DOWN]: - snells.add(snells.copy().next_to(snells, vect)) - snells.ingest_submobjects() - snells.show() - condensed = TextMobject("condensed") - - self.add(snells) - self.wait() - self.play(DelayByOrder( - Transform(snells, condensed, run_time = 2) - )) - self.wait() - - - - - - - - - - - diff --git a/from_3b1b/old/cba.py b/from_3b1b/old/cba.py deleted file mode 100644 index 532e49b0..00000000 --- a/from_3b1b/old/cba.py +++ /dev/null @@ -1,405 +0,0 @@ -from manimlib.imports import * - - -class EnumerableSaveScene(Scene): - def setup(self): - self.save_count = 0 - - def save_enumerated_image(self): - file_path = self.file_writer.get_image_file_path() - file_path = file_path.replace( - ".png", "{:02}.png".format(self.save_count) - ) - self.update_frame(ignore_skipping=True) - image = self.get_image() - image.save(file_path) - self.save_count += 1 - - -class LayersOfAbstraction(EnumerableSaveScene): - def construct(self): - self.save_count = 0 - # self.add_title() - self.show_layers() - self.show_pairwise_relations() - self.circle_certain_pairs() - - def add_title(self): - title = TextMobject("Layers of abstraction") - title.scale(1.5) - title.to_edge(UP, buff=MED_SMALL_BUFF) - line = Line(LEFT, RIGHT) - line.set_width(FRAME_WIDTH) - line.next_to(title, DOWN, SMALL_BUFF) - - self.add(title, line) - - def show_layers(self): - layers = self.layers = self.get_layers() - for layer in layers: - self.add(layer[0]) - - self.save_enumerated_image() - for layer in layers: - self.add(layer) - self.save_enumerated_image() - - def show_pairwise_relations(self): - p1, p2 = [l.get_left() for l in self.layers[2:4]] - down_arrow = Arrow(p2, p1, path_arc=PI) - down_words = TextMobject("``For example''") - down_words.scale(0.8) - down_words.next_to(down_arrow, LEFT) - up_arrow = Arrow(p1, p2, path_arc=-PI) - up_words = TextMobject("``In general''") - up_words.scale(0.8) - up_words.next_to(up_arrow, LEFT) - - VGroup(up_words, down_words).set_color(YELLOW) - - self.add(down_arrow, down_words) - self.save_enumerated_image() - self.remove(down_arrow, down_words) - self.add(up_arrow, up_words) - self.save_enumerated_image() - self.remove(up_arrow, up_words) - - def circle_certain_pairs(self): - layers = self.layers - - for l1, l2 in zip(layers, layers[1:]): - group = VGroup(l1, l2) - group.save_state() - layers.save_state() - layers.fade(0.75) - rect = SurroundingRectangle(group) - rect.set_stroke(YELLOW, 5) - group.restore() - self.add(rect) - self.save_enumerated_image() - self.remove(rect) - layers.restore() - - # - - def get_layers(self): - layers = VGroup(*[ - VGroup(Rectangle(height=1, width=5)) - for x in range(6) - ]) - layers.arrange_submobjects(UP, buff=0) - layers.set_stroke(GREY, 2) - layers.set_sheen(1, UL) - - # Layer 0: Quantities - triangle = Triangle().set_height(0.25) - tri_dots = VGroup(*[Dot(v) for v in triangle.get_vertices()]) - dots_rect = VGroup(*[Dot() for x in range(12)]) - dots_rect.arrange_in_grid(3, 4, buff=SMALL_BUFF) - for i, color in enumerate([RED, GREEN, BLUE]): - dots_rect[i::4].set_color(color) - pi_chart = VGroup(*[ - Sector(start_angle=a, angle=TAU / 3) - for a in np.arange(0, TAU, TAU / 3) - ]) - pi_chart.set_fill(opacity=0) - pi_chart.set_stroke(WHITE, 2) - pi_chart[0].set_fill(BLUE, 1) - pi_chart.rotate(PI / 3) - pi_chart.match_height(dots_rect) - quantities = VGroup(tri_dots, dots_rect, pi_chart) - quantities.arrange(RIGHT, buff=LARGE_BUFF) - - # Layer 1: Numbers - numbers = VGroup( - TexMobject("3"), - TexMobject("3 \\times 4"), - TexMobject("1 / 3"), - ) - for number, quantity in zip(numbers, quantities): - number.move_to(quantity) - - # Layer 2: Algebra - algebra = VGroup( - TexMobject("x^2 - 1 = (x + 1)(x - 1)") - ) - algebra.set_width(layers.get_width() - MED_LARGE_BUFF) - - # Layer 3: Functions - functions = VGroup( - TexMobject("f(x) = 0"), - TexMobject("\\frac{df}{dx}"), - ) - functions.set_height(layers[0].get_height() - 2 * SMALL_BUFF) - functions.arrange(RIGHT, buff=LARGE_BUFF) - # functions.match_width(algebra) - - # Layer 4: Vector space - t2c_map = { - "\\textbf{v}": YELLOW, - "\\textbf{w}": PINK, - } - vector_spaces = VGroup( - TexMobject( - "\\textbf{v} + \\textbf{w} =" - "\\textbf{w} + \\textbf{v}", - tex_to_color_map=t2c_map, - ), - TexMobject( - "s(\\textbf{v} + \\textbf{w}) =" - "s\\textbf{v} + s\\textbf{w}", - tex_to_color_map=t2c_map, - ), - ) - vector_spaces.arrange(DOWN, buff=MED_SMALL_BUFF) - vector_spaces.set_height(layers[0].get_height() - MED_LARGE_BUFF) - v, w = vectors = VGroup( - Vector([2, 1, 0], color=YELLOW), - Vector([1, 2, 0], color=PINK), - ) - vectors.add(DashedLine(v.get_end(), v.get_end() + w.get_vector())) - vectors.add(DashedLine(w.get_end(), w.get_end() + v.get_vector())) - vectors.match_height(vector_spaces) - vectors.next_to(vector_spaces, RIGHT) - vectors.set_stroke(width=2) - # vector_spaces.add(vectors) - - inner_product = TexMobject( - "\\langle f, g \\rangle =" - "\\int f(x)g(x)dx" - ) - inner_product.match_height(vector_spaces) - inner_product.next_to(vector_spaces, RIGHT) - vector_spaces.add(inner_product) - - # Layer 5: Categories - dots = VGroup(Dot(UP), Dot(UR), Dot(RIGHT)) - arrows = VGroup( - Arrow(dots[0], dots[1], buff=SMALL_BUFF), - Arrow(dots[1], dots[2], buff=SMALL_BUFF), - Arrow(dots[0], dots[2], buff=SMALL_BUFF), - ) - arrows.set_stroke(width=2) - arrow_labels = VGroup( - TexMobject("m_1").next_to(arrows[0], UP, SMALL_BUFF), - TexMobject("m_2").next_to(arrows[1], RIGHT, SMALL_BUFF), - TexMobject("m_2 \\circ m_1").rotate(-45 * DEGREES).move_to( - arrows[2] - ).shift(MED_SMALL_BUFF * DL) - ) - categories = VGroup(dots, arrows, arrow_labels) - categories.set_height(layers[0].get_height() - MED_SMALL_BUFF) - - # Put it all together - all_content = [ - quantities, numbers, algebra, - functions, vector_spaces, categories, - ] - - for layer, content in zip(layers, all_content): - content.move_to(layer) - layer.add(content) - layer.content = content - - layer_titles = VGroup(*map(TextMobject, [ - "Quantities", - "Numbers", - "Algebra", - "Functions", - "Vector spaces", - "Categories", - ])) - for layer, title in zip(layers, layer_titles): - title.next_to(layer, RIGHT) - layer.add(title) - layer.title = title - layers.titles = layer_titles - - layers.center() - layers.to_edge(DOWN) - layers.shift(0.5 * RIGHT) - return layers - - -class DifferenceOfSquares(Scene): - def construct(self): - squares = VGroup(*[ - VGroup(*[ - Square() - for x in range(8) - ]).arrange(RIGHT, buff=0) - for y in range(8) - ]).arrange(DOWN, buff=0) - squares.set_height(4) - squares.set_stroke(BLUE, 3) - squares.set_fill(BLUE, 0.5) - - last_row_parts = VGroup() - for row in squares[-3:]: - row[-3:].set_color(RED) - row[:-3].set_color(BLUE_B) - last_row_parts.add(row[:-3]) - squares.to_edge(LEFT) - - arrow = Vector(RIGHT, color=WHITE) - arrow.shift(1.5 * LEFT) - squares.next_to(arrow, LEFT) - - new_squares = squares[:-3].copy() - new_squares.next_to(arrow, RIGHT) - new_squares.align_to(squares, UP) - - x1 = TexMobject("x").set_color(BLUE) - x2 = x1.copy() - x1.next_to(squares, UP) - x2.next_to(squares, LEFT) - y1 = TexMobject("y").set_color(RED) - y2 = y1.copy() - y1.next_to(squares[-2], RIGHT) - y2.next_to(squares[-1][-2], DOWN) - - xpy = TexMobject("x", "+", "y") - xmy = TexMobject("x", "-", "y") - for mob in xpy, xmy: - mob[0].set_color(BLUE) - mob[2].set_color(RED) - xpy.next_to(new_squares, UP) - # xmy.rotate(90 * DEGREES) - xmy.next_to(new_squares, RIGHT) - xmy.to_edge(RIGHT) - - self.add(squares, x1, x2, y1, y2) - self.play( - ReplacementTransform( - squares[:-3].copy().set_fill(opacity=0), - new_squares - ), - ShowCreation(arrow), - lag_ratio=0, - ) - last_row_parts = last_row_parts.copy() - last_row_parts.save_state() - last_row_parts.set_fill(opacity=0) - self.play( - last_row_parts.restore, - last_row_parts.rotate, -90 * DEGREES, - last_row_parts.next_to, new_squares, RIGHT, {"buff": 0}, - lag_ratio=0, - ) - self.play(Write(xmy), Write(xpy)) - self.wait() - - -class Lightbulbs(EnumerableSaveScene): - def construct(self): - dots = VGroup(*[Dot() for x in range(4)]) - dots.set_height(0.5) - dots.arrange(RIGHT, buff=2) - dots.set_fill(opacity=0) - dots.set_stroke(width=2, color=WHITE) - dot_radius = dots[0].get_width() / 2 - - connections = VGroup() - for d1, d2 in it.product(dots, dots): - line = Line( - d1.get_center(), - d2.get_center(), - path_arc=30 * DEGREES, - buff=dot_radius, - color=YELLOW, - ) - connections.add(line) - - lower_dots = dots[:3].copy() - lower_dots.next_to(dots, DOWN, buff=2) - lower_lines = VGroup(*[ - Line(d.get_center(), ld.get_center(), buff=dot_radius) - for d, ld in it.product(dots, lower_dots[1:]) - ]) - lower_lines.match_style(connections) - - top_dot = dots[0].copy() - top_dot.next_to(dots, UP, buff=2) - - top_lines = VGroup(*[ - Line(d.get_center(), top_dot.get_center(), buff=dot_radius) - for d in dots - ]) - top_lines.match_style(connections) - - self.add(dots) - self.add(top_dot) - self.save_enumerated_image() - dots.set_fill(YELLOW, 1) - self.save_enumerated_image() - self.add(connections) - self.save_enumerated_image() - self.add(lower_dots) - self.add(lower_lines) - lower_dots[1:].set_fill(YELLOW, 1) - self.save_enumerated_image() - - self.add(top_lines) - connections.set_stroke(width=1) - lower_lines.set_stroke(width=1) - top_dot.set_fill(YELLOW, 1) - self.save_enumerated_image() - - self.remove(connections) - self.remove(top_lines) - self.remove(lower_lines) - dots.set_fill(opacity=0) - lower_dots.set_fill(opacity=0) - - -class LayersOfLightbulbs(Scene): - CONFIG = { - "random_seed": 1, - } - - def construct(self): - layers = VGroup() - for x in range(6): - n_dots = 5 + (x % 2) - dots = VGroup(*[Dot() for x in range(n_dots)]) - dots.scale(2) - dots.arrange(RIGHT, buff=MED_LARGE_BUFF) - dots.set_stroke(WHITE, 2) - for dot in dots: - dot.set_fill(YELLOW, np.random.random()) - layers.add(dots) - - layers.arrange(UP, buff=LARGE_BUFF) - - lines = VGroup() - for l1, l2 in zip(layers, layers[1:]): - for d1, d2 in it.product(l1, l2): - color = interpolate_color( - YELLOW, GREEN, np.random.random() - ) - line = Line( - d1.get_center(), - d2.get_center(), - buff=(d1.get_width() / 2), - color=color, - stroke_width=2 * np.random.random(), - ) - lines.add(line) - - self.add(layers, lines) - - -class Test(Scene): - def construct(self): - # self.change_all_student_modes("hooray") - # self.teacher.change("raise_right_hand") - # self.look_at(3 * UP) - randy = Randolph() - randy.change("pondering") - randy.set_height(6) - randy.look(RIGHT) - self.add(randy) - # eq = TexMobject("143", "=", "11 \\cdot 13") - # eq[0].set_color(YELLOW) - # eq.scale(0.7) - # self.add(eq) diff --git a/from_3b1b/old/clacks/all_s2_scenes.py b/from_3b1b/old/clacks/all_s2_scenes.py deleted file mode 100644 index efca6a1f..00000000 --- a/from_3b1b/old/clacks/all_s2_scenes.py +++ /dev/null @@ -1,62 +0,0 @@ -from from_3b1b.old.clacks import question -from from_3b1b.old.clacks.solution2 import block_collision_scenes -from from_3b1b.old.clacks.solution2 import mirror_scenes -from from_3b1b.old.clacks.solution2 import pi_creature_scenes -from from_3b1b.old.clacks.solution2 import position_phase_space -from from_3b1b.old.clacks.solution2 import simple_scenes -from from_3b1b.old.clacks.solution2 import wordy_scenes - -OUTPUT_DIRECTORY = "clacks/solution2" -SCENES_IN_ORDER = [ - question.NameIntro, - block_collision_scenes.IntroducePreviousTwoVideos, - block_collision_scenes.PreviousTwoVideos, - simple_scenes.ComingUpWrapper, - wordy_scenes.ConnectionToOptics, - pi_creature_scenes.OnAnsweringTwice, - simple_scenes.LastVideoWrapper, - simple_scenes.Rectangle, - simple_scenes.ShowRectangleCreation, - simple_scenes.LeftEdge, - simple_scenes.RightEdge, - position_phase_space.IntroducePositionPhaseSpace, - position_phase_space.UnscaledPositionPhaseSpaceMass100, - simple_scenes.FourtyFiveDegreeLine, - position_phase_space.EqualMassCase, - pi_creature_scenes.AskAboutEqualMassMomentumTransfer, - position_phase_space.FailedAngleRelation, - position_phase_space.UnscaledPositionPhaseSpaceMass10, - pi_creature_scenes.ComplainAboutRelevanceOfAnalogy, - simple_scenes.LastVideoWrapper, - simple_scenes.NoteOnEnergyLostToSound, - position_phase_space.RescaleCoordinates, - wordy_scenes.ConnectionToOpticsTransparent, - position_phase_space.RescaleCoordinatesMass16, - position_phase_space.RescaleCoordinatesMass64, - position_phase_space.RescaleCoordinatesMass100, - position_phase_space.IntroduceVelocityVector, - position_phase_space.IntroduceVelocityVectorWithoutZoom, - position_phase_space.ShowMomentumConservation, - wordy_scenes.RearrangeMomentumEquation, - simple_scenes.DotProductVideoWrapper, - simple_scenes.ShowDotProductMeaning, - position_phase_space.JustTheProcessNew, - mirror_scenes.ShowTrajectoryWithChangingTheta, - pi_creature_scenes.ReplaceOneTrickySceneWithAnother, - mirror_scenes.MirrorAndWiresOverlay, - pi_creature_scenes.NowForTheGoodPart, - mirror_scenes.ReflectWorldThroughMirrorNew, - mirror_scenes.ReflectWorldThroughMirrorThetaPoint2, - mirror_scenes.ReflectWorldThroughMirrorThetaPoint1, - simple_scenes.AskAboutAddingThetaToItself, - simple_scenes.AskAboutAddingThetaToItselfThetaPoint1, - simple_scenes.AskAboutAddingThetaToItselfThetaPoint2, - simple_scenes.FinalFormula, - simple_scenes.ArctanSqrtPoint1Angle, - simple_scenes.ReviewWrapper, - simple_scenes.SurprisedRandy, - simple_scenes.TwoSolutionsWrapper, - simple_scenes.FinalQuote, - simple_scenes.EndScreen, - simple_scenes.ClacksSolution2Thumbnail, -] diff --git a/from_3b1b/old/clacks/name_bump.py b/from_3b1b/old/clacks/name_bump.py deleted file mode 100644 index 5e817882..00000000 --- a/from_3b1b/old/clacks/name_bump.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python -from manimlib.imports import * -from from_3b1b.old.clacks.question import BlocksAndWallExample - - -class NameBump(BlocksAndWallExample): - CONFIG = { - "name": "Grant Sanderson", - "sliding_blocks_config": { - "block1_config": { - "mass": 1e6, - "velocity": -0.5, - "distance": 7, - }, - "block2_config": {}, - }, - "wait_time": 25, - } - - def setup(self): - names = self.name.split(" ") - n = len(names) - if n == 1: - names = 2 * [names[0]] - elif n > 2: - names = [ - " ".join(names[:n // 2]), - " ".join(names[n // 2:]), - ] - # Swap, to show first name on the left - names = [names[1], names[0]] - - name_mobs = VGroup(*map(TextMobject, names)) - name_mobs.set_stroke(BLACK, 3, background=True) - name_mobs.set_fill(LIGHT_GREY, 1) - name_mobs.set_sheen(3, UL) - name_mobs.scale(2) - configs = [ - self.sliding_blocks_config["block1_config"], - self.sliding_blocks_config["block2_config"], - ] - for name_mob, config in zip(name_mobs, configs): - config["width"] = name_mob.get_width() - self.name_mobs = name_mobs - - super().setup() - - def add_blocks(self): - super().add_blocks() - blocks = self.blocks - name_mobs = self.name_mobs - - blocks.fade(1) - - def update_name_mobs(name_mobs): - for name_mob, block in zip(name_mobs, self.blocks): - name_mob.move_to(block) - target_y = block.get_bottom()[1] + SMALL_BUFF - curr_y = name_mob[0].get_bottom()[1] - name_mob.shift((target_y - curr_y) * UP) - - name_mobs.add_updater(update_name_mobs) - self.add(name_mobs) - - clack_y = self.name_mobs[1].get_center()[1] - for location, time in self.clack_data: - location[1] = clack_y - - for block, name_mob in zip(blocks, name_mobs): - block.label.next_to(name_mob, UP) - block.label.set_fill(YELLOW, opacity=1) - - -# for name in names: -# file_name = name.replace(".", "") -# file_name += " Name Bump" -# scene = NameBump( -# name=name, -# write_to_movie=True, -# output_file_name=file_name, -# camera_config=PRODUCTION_QUALITY_CAMERA_CONFIG, -# ) diff --git a/from_3b1b/old/clacks/question.py b/from_3b1b/old/clacks/question.py deleted file mode 100644 index 5b927d57..00000000 --- a/from_3b1b/old/clacks/question.py +++ /dev/null @@ -1,1635 +0,0 @@ -from manimlib.imports import * - - -OUTPUT_DIRECTORY = "clacks/question" - - -class Block(Square): - CONFIG = { - "mass": 1, - "velocity": 0, - "width": None, - "label_text": None, - "label_scale_value": 0.8, - "fill_opacity": 1, - "stroke_width": 3, - "stroke_color": WHITE, - "fill_color": None, - "sheen_direction": UL, - "sheen_factor": 0.5, - "sheen_direction": UL, - } - - def __init__(self, **kwargs): - digest_config(self, kwargs) - if self.width is None: - self.width = self.mass_to_width(self.mass) - if self.fill_color is None: - self.fill_color = self.mass_to_color(self.mass) - if self.label_text is None: - self.label_text = self.mass_to_label_text(self.mass) - if "width" in kwargs: - kwargs.pop("width") - Square.__init__(self, side_length=self.width, **kwargs) - self.label = self.get_label() - self.add(self.label) - - def get_label(self): - label = TextMobject(self.label_text) - label.scale(self.label_scale_value) - label.next_to(self, UP, SMALL_BUFF) - return label - - def get_points_defining_boundary(self): - return self.points - - def mass_to_color(self, mass): - colors = [ - LIGHT_GREY, - BLUE_D, - BLUE_D, - BLUE_E, - BLUE_E, - DARK_GREY, - DARK_GREY, - BLACK, - ] - index = min(int(np.log10(mass)), len(colors) - 1) - return colors[index] - - def mass_to_width(self, mass): - return 1 + 0.25 * np.log10(mass) - - def mass_to_label_text(self, mass): - return "{:,}\\,kg".format(int(mass)) - - -class SlidingBlocks(VGroup): - CONFIG = { - "block1_config": { - "distance": 7, - "mass": 1e6, - "velocity": -2, - }, - "block2_config": { - "distance": 3, - "mass": 1, - "velocity": 0, - }, - "collect_clack_data": True, - } - - def __init__(self, scene, **kwargs): - VGroup.__init__(self, **kwargs) - self.scene = scene - self.floor = scene.floor - self.wall = scene.wall - - self.block1 = self.get_block(**self.block1_config) - self.block2 = self.get_block(**self.block2_config) - self.mass_ratio = self.block2.mass / self.block1.mass - self.phase_space_point_tracker = self.get_phase_space_point_tracker() - self.add( - self.block1, self.block2, - self.phase_space_point_tracker, - ) - self.add_updater(self.__class__.update_positions) - - if self.collect_clack_data: - self.clack_data = self.get_clack_data() - - def get_block(self, distance, **kwargs): - block = Block(**kwargs) - block.move_to( - self.floor.get_top()[1] * UP + - (self.wall.get_right()[0] + distance) * RIGHT, - DL, - ) - return block - - def get_phase_space_point_tracker(self): - block1, block2 = self.block1, self.block2 - w2 = block2.get_width() - s1 = block1.get_left()[0] - self.wall.get_right()[0] - w2 - s2 = block2.get_right()[0] - self.wall.get_right()[0] - w2 - result = VectorizedPoint([ - s1 * np.sqrt(block1.mass), - s2 * np.sqrt(block2.mass), - 0 - ]) - - result.velocity = np.array([ - np.sqrt(block1.mass) * block1.velocity, - np.sqrt(block2.mass) * block2.velocity, - 0 - ]) - return result - - def update_positions(self, dt): - self.phase_space_point_tracker.shift( - self.phase_space_point_tracker.velocity * dt - ) - self.update_blocks_from_phase_space_point_tracker() - - def update_blocks_from_phase_space_point_tracker(self): - block1, block2 = self.block1, self.block2 - ps_point = self.phase_space_point_tracker.get_location() - - theta = np.arctan(np.sqrt(self.mass_ratio)) - ps_point_angle = angle_of_vector(ps_point) - n_clacks = int(ps_point_angle / theta) - reflected_point = rotate_vector( - ps_point, - -2 * np.ceil(n_clacks / 2) * theta - ) - reflected_point = np.abs(reflected_point) - - shadow_wall_x = self.wall.get_right()[0] + block2.get_width() - floor_y = self.floor.get_top()[1] - s1 = reflected_point[0] / np.sqrt(block1.mass) - s2 = reflected_point[1] / np.sqrt(block2.mass) - block1.move_to( - (shadow_wall_x + s1) * RIGHT + - floor_y * UP, - DL, - ) - block2.move_to( - (shadow_wall_x + s2) * RIGHT + - floor_y * UP, - DR, - ) - - self.scene.update_num_clacks(n_clacks) - - def get_clack_data(self): - ps_point = self.phase_space_point_tracker.get_location() - ps_velocity = self.phase_space_point_tracker.velocity - if ps_velocity[1] != 0: - raise Exception( - "Haven't implemented anything to gather clack " - "data from a start state with block2 moving" - ) - y = ps_point[1] - theta = np.arctan(np.sqrt(self.mass_ratio)) - - clack_data = [] - for k in range(1, int(PI / theta) + 1): - clack_ps_point = np.array([ - y / np.tan(k * theta), - y, - 0 - ]) - time = get_norm(ps_point - clack_ps_point) / get_norm(ps_velocity) - reflected_point = rotate_vector( - clack_ps_point, - -2 * np.ceil((k - 1) / 2) * theta - ) - block2 = self.block2 - s2 = reflected_point[1] / np.sqrt(block2.mass) - location = np.array([ - self.wall.get_right()[0] + s2, - block2.get_center()[1], - 0 - ]) - if k % 2 == 1: - location += block2.get_width() * RIGHT - clack_data.append((location, time)) - return clack_data - - -# TODO, this is untested after turning it from a -# ContinualAnimation into a VGroup -class ClackFlashes(VGroup): - CONFIG = { - "flash_config": { - "run_time": 0.5, - "line_length": 0.1, - "flash_radius": 0.2, - }, - "start_up_time": 0, - "min_time_between_flashes": 1 / 30, - } - - def __init__(self, clack_data, **kwargs): - VGroup.__init__(self, **kwargs) - self.flashes = [] - last_time = 0 - for location, time in clack_data: - if (time - last_time) < self.min_time_between_flashes: - continue - last_time = time - flash = Flash(location, **self.flash_config) - flash.begin() - for sm in flash.mobject.family_members_with_points(): - if isinstance(sm, VMobject): - sm.set_stroke(YELLOW, 3) - sm.set_stroke(WHITE, 6, 0.5, background=True) - flash.start_time = time - flash.end_time = time + flash.run_time - self.flashes.append(flash) - - self.time = 0 - self.add_updater(lambda m: m.update(dt)) - - def update(self, dt): - time = self.time - self.time += dt - for flash in self.flashes: - if flash.start_time < time < flash.end_time: - if flash.mobject not in self.submobjects: - self.add(flash.mobject) - flash.update( - (time - flash.start_time) / flash.run_time - ) - else: - if flash.mobject in self.submobjects: - self.remove(flash.mobject) - - -class Wall(Line): - CONFIG = { - "tick_spacing": 0.5, - "tick_length": 0.25, - "tick_style": { - "stroke_width": 1, - "stroke_color": WHITE, - }, - } - - def __init__(self, height, **kwargs): - Line.__init__(self, ORIGIN, height * UP, **kwargs) - self.height = height - self.ticks = self.get_ticks() - self.add(self.ticks) - - def get_ticks(self): - n_lines = int(self.height / self.tick_spacing) - lines = VGroup(*[ - Line(ORIGIN, self.tick_length * UR).shift(n * self.tick_spacing * UP) - for n in range(n_lines) - ]) - lines.set_style(**self.tick_style) - lines.move_to(self, DR) - return lines - - -class BlocksAndWallScene(Scene): - CONFIG = { - "include_sound": True, - "collision_sound": "clack.wav", - "count_clacks": True, - "counter_group_shift_vect": LEFT, - "sliding_blocks_config": {}, - "floor_y": -2, - "wall_x": -6, - "n_wall_ticks": 15, - "counter_label": "\\# Collisions: ", - "show_flash_animations": True, - "min_time_between_sounds": 0.004, - } - - def setup(self): - self.track_time() - self.add_floor_and_wall() - self.add_blocks() - if self.show_flash_animations: - self.add_flash_animations() - - if self.count_clacks: - self.add_counter() - - def add_floor_and_wall(self): - self.floor = self.get_floor() - self.wall = self.get_wall() - self.add(self.floor, self.wall) - - def add_blocks(self): - self.blocks = SlidingBlocks(self, **self.sliding_blocks_config) - if hasattr(self.blocks, "clack_data"): - self.clack_data = self.blocks.clack_data - self.add(self.blocks) - - def add_flash_animations(self): - self.clack_flashes = ClackFlashes(self.clack_data) - self.add(self.clack_flashes) - - def track_time(self): - time_tracker = ValueTracker() - time_tracker.add_updater(lambda m, dt: m.increment_value(dt)) - self.add(time_tracker) - self.get_time = time_tracker.get_value - - def add_counter(self): - self.n_clacks = 0 - counter_label = TextMobject(self.counter_label) - counter_mob = Integer(self.n_clacks) - counter_mob.next_to( - counter_label[-1], RIGHT, - ) - counter_mob.align_to(counter_label[-1][-1], DOWN) - counter_group = VGroup( - counter_label, - counter_mob, - ) - counter_group.to_corner(UR) - counter_group.shift(self.counter_group_shift_vect) - self.add(counter_group) - - self.counter_mob = counter_mob - - def get_wall(self): - height = (FRAME_HEIGHT / 2) - self.floor_y - wall = Wall(height=height) - wall.shift(self.wall_x * RIGHT) - wall.to_edge(UP, buff=0) - return wall - - def get_floor(self): - floor = Line(self.wall_x * RIGHT, FRAME_WIDTH * RIGHT / 2) - floor.shift(self.floor_y * UP) - return floor - - def update_num_clacks(self, n_clacks): - if hasattr(self, "n_clacks"): - if n_clacks == self.n_clacks: - return - self.counter_mob.set_value(n_clacks) - - def add_clack_sounds(self, clack_data): - clack_file = self.collision_sound - total_time = self.get_time() - times = [ - time - for location, time in clack_data - if time < total_time - ] - last_time = 0 - for time in times: - d_time = time - last_time - if d_time < self.min_time_between_sounds: - continue - last_time = time - self.add_sound( - clack_file, - time_offset=(time - total_time), - gain=-20, - ) - return self - - def tear_down(self): - super().tear_down() - if self.include_sound: - self.add_clack_sounds(self.clack_data) - -# Animated scenes - - -class NameIntro(Scene): - def construct(self): - name = TextMobject("3Blue", "1Brown", arg_separator="") - blue, brown = name - name.scale(2.5) - for part in name: - part.save_state() - brown.to_edge(RIGHT, buff=0) - flash_time = 0.75 - - self.add(blue, brown) - self.play( - VFadeIn(blue), - VFadeIn(brown), - Restore(brown, rate_func=linear), - ) - self.play( - Flash(blue.get_right(), run_time=flash_time), - ApplyMethod( - blue.to_edge, LEFT, {"buff": 0}, - rate_func=linear, - ), - ) - self.play( - Flash(blue.get_left(), run_time=flash_time), - Restore(blue, rate_func=linear), - ) - self.play( - Flash(blue.get_right(), run_time=flash_time), - ApplyMethod( - brown.to_edge, RIGHT, {"buff": 0}, - rate_func=linear, - ) - ) - self.play( - Flash(brown.get_right(), run_time=flash_time), - Restore(brown, rate_func=linear) - ) - - -class MathAndPhysicsConspiring(Scene): - def construct(self): - v_line = Line(DOWN, UP).scale(FRAME_HEIGHT) - v_line.save_state() - v_line.fade(1) - v_line.scale(0) - math_title = TextMobject("Math") - math_title.set_color(BLUE) - physics_title = TextMobject("Physics") - physics_title.set_color(YELLOW) - for title, vect in (math_title, LEFT), (physics_title, RIGHT): - title.scale(2) - title.shift(vect * FRAME_WIDTH / 4) - title.to_edge(UP) - - math_stuffs = VGroup( - TexMobject("\\pi = {:.16}\\dots".format(PI)), - self.get_tangent_image(), - ) - math_stuffs.arrange(DOWN, buff=MED_LARGE_BUFF) - math_stuffs.next_to(math_title, DOWN, LARGE_BUFF) - to_fade = VGroup(math_title, *math_stuffs, physics_title) - - self.play( - LaggedStartMap( - FadeInFromDown, to_fade, - lag_ratio=0.7, - run_time=3, - ), - Restore(v_line, run_time=2, path_arc=PI / 2), - ) - self.wait() - - def get_tangent_image(self): - axes = Axes( - x_min=-1.5, - x_max=1.5, - y_min=-1.5, - y_max=1.5, - ) - circle = Circle() - circle.set_color(WHITE) - theta = 30 * DEGREES - arc = Arc(angle=theta, radius=0.4) - theta_label = TexMobject("\\theta") - theta_label.scale(0.5) - theta_label.next_to(arc.get_center(), RIGHT, buff=SMALL_BUFF) - theta_label.shift(0.025 * UL) - line = Line(ORIGIN, rotate_vector(RIGHT, theta)) - line.set_color(WHITE) - one = TexMobject("1").scale(0.5) - one.next_to(line.point_from_proportion(0.7), UL, 0.5 * SMALL_BUFF) - tan_line = Line( - line.get_end(), - (1.0 / np.cos(theta)) * RIGHT - ) - tan_line.set_color(RED) - tan_text = TexMobject("\\tan(\\theta)") - tan_text.rotate(tan_line.get_angle()) - tan_text.scale(0.5) - tan_text.move_to(tan_line) - tan_text.match_color(tan_line) - tan_text.shift(0.2 * normalize(line.get_vector())) - - result = VGroup( - axes, circle, - line, one, - arc, theta_label, - tan_line, tan_text, - ) - result.set_height(4) - return result - - -class LightBouncing(MovingCameraScene): - CONFIG = { - "theta": np.arctan(0.15), - "show_fanning": False, - "mirror_shift_vect": 5 * LEFT, - "mirror_length": 10, - "beam_start_x": 12, - "beam_height": 1, - } - - def construct(self): - theta = self.theta - h_line = Line(ORIGIN, self.mirror_length * RIGHT) - d_line = h_line.copy().rotate(theta, about_point=ORIGIN) - mirrors = VGroup(h_line, d_line) - self.add(mirrors) - - beam_height = self.beam_height - start_point = self.beam_start_x * RIGHT + beam_height * UP - points = [start_point] + [ - np.array([ - (beam_height / np.tan(k * theta)), - beam_height, - 0, - ]) - for k in range(1, int(PI / theta)) - ] + [rotate(start_point, PI, UP)] - reflected_points = [] - for k, point in enumerate(points): - reflected_point = rotate_vector(point, -2 * (k // 2) * theta) - reflected_point[1] = abs(reflected_point[1]) - reflected_points.append(reflected_point) - beam = VMobject() - beam.set_points_as_corners(reflected_points) - beam.set_stroke(YELLOW, 2) - - anims = [self.get_beam_anim(beam)] - - if self.show_fanning: - for k in range(2, int(PI / theta) + 1): - line = h_line.copy() - line.set_stroke(WHITE, 1) - line.rotate(k * theta, about_point=ORIGIN) - self.add(line) - straight_beam = VMobject() - straight_beam.set_points_as_corners(points) - straight_beam.set_stroke(YELLOW, 2) - anims.append(self.get_beam_anim(straight_beam)) - - self.camera_frame.shift(-self.mirror_shift_vect) - self.play(*anims) - self.wait() - - def get_beam_anim(self, beam): - dot = Dot() - dot.scale(0.5) - dot.match_color(beam) - return AnimationGroup( - ShowPassingFlash( - beam, - run_time=5, - rate_func=lambda t: smooth(t, 5), - time_width=0.05, - ), - UpdateFromFunc( - dot, - lambda m: m.move_to(beam.points[-1]) - ), - ) - - -class BlocksAndWallExample(BlocksAndWallScene): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e0, - "velocity": -2, - } - }, - "wait_time": 15, - } - - def construct(self): - self.wait(self.wait_time) - - -class BlocksAndWallExampleMass1e1(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e1, - "velocity": -1.5, - } - }, - "wait_time": 20, - } - - -class TwoBlocksLabel(Scene): - def construct(self): - label = TextMobject("Two sliding \\\\ blocks") - label.to_edge(UP) - arrows = VGroup(*[ - Arrow(label.get_bottom(), point) - for point in [RIGHT, LEFT] - ]) - arrows.set_color(RED) - self.play( - Write(label), - LaggedStartMap(GrowArrow, arrows, lag_ratio=0.7), - run_time=1 - ) - self.wait() - - -class WallLabel(Scene): - def construct(self): - wall = Line(TOP, 2 * DOWN) - wall.set_stroke(YELLOW, 10) - word = TextMobject("Wall") - word.rotate(-90 * DEGREES) - word.next_to(wall, RIGHT, MED_SMALL_BUFF) - self.play( - Write(word), - ShowPassingFlash(wall) - ) - self.wait() - - -class CowToSphere(ExternallyAnimatedScene): - pass - - -class NoFrictionLabel(Scene): - def construct(self): - words = TextMobject("Frictionless") - words.shift(2 * RIGHT) - words.add_updater( - lambda m, dt: m.shift(dt * LEFT) - ) - - self.play(VFadeIn(words)) - self.wait(2) - self.play(VFadeOut(words)) - - -class Mass1e1WithElasticLabel(BlocksAndWallExampleMass1e1): - def add_flash_animations(self): - super().add_flash_animations() - flashes = self.clack_flashes - label = TextMobject( - "Purely elastic collisions\\\\" - "(no energy lost)" - ) - label.set_color(YELLOW) - label.move_to(2 * LEFT + 2 * UP) - self.add(label) - self.add(*[ - self.get_arrow(label, flashes, flash) - for flash in flashes.flashes - ]) - - def get_arrow(self, label, clack_flashes, flash): - arrow = Arrow( - label.get_bottom(), - flash.mobject.get_center() + 0.0 * UP, - ) - arrow.set_fill(YELLOW) - arrow.set_stroke(BLACK, 1, background=True) - arrow.original_length = arrow.get_length() - - def set_opacity(arrow): - time = self.get_time() - from_start = time - flash.start_time - if from_start < 0: - opacity = 0 - else: - opacity = smooth(1 - 2 * from_start) - arrow.set_fill(opacity=opacity) - arrow.set_stroke(opacity=opacity, background=True) - # if opacity > 0: - # arrow.scale( - # opacity * arrow.original_length / arrow.get_length(), - # about_point=arrow.get_end() - # ) - - arrow.add_updater(set_opacity) - return arrow - - -class AskAboutSoundlessness(TeacherStudentsScene): - def construct(self): - self.student_says( - "No sound,\\\\right?" - ) - self.play(self.teacher.change, "guilty") - self.wait(2) - self.teacher_says( - "Focus on \\\\ collisions", - target_mode="speaking", - added_anims=[ - self.get_student_changes("pondering", "confused", "thinking") - ] - ) - self.look_at(self.screen) - self.wait(3) - - -class ShowCreationRect(Scene): - def construct(self): - rect = SurroundingRectangle(TextMobject("\\# Collisions: 3")) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.wait() - - -class BlocksAndWallExampleSameMass(BlocksAndWallExample): - pass - - -class ShowLeftArrow(Scene): - def construct(self): - arrow = Vector(2 * LEFT, color=RED) - self.play(GrowArrow(arrow)) - self.wait() - self.play(FadeOut(arrow)) - - -class AskWhatWillHappen(PiCreatureScene): - def construct(self): - morty = self.pi_creature - morty.set_color(GREY_BROWN) - - self.pi_creature_says( - "What will\\\\" - "happen?", - target_mode="maybe", - look_at_arg=4 * DR, - ) - self.wait(3) - - -class BlocksAndWallExampleMass1e2(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e2, - "velocity": -0.6, - } - }, - "wait_time": 35, - } - - -class BlocksAndWallExampleMassSameSpeedAtEnd(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1 / np.tan(PI / 5)**2, - "velocity": -1, - "label_text": "$\\frac{1}{\\tan(\\pi / 5)^2}$ kg" - } - }, - "wait_time": 25, - } - - -class BlocksAndWallExampleMass1e4(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e4, - "velocity": -1.5, - }, - }, - "wait_time": 25, - } - - -class BlocksAndWallExampleMass1e4SlowMo(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e4, - "velocity": -0.1, - "distance": 4.1 - }, - }, - "wait_time": 50, - "collision_sound": "slow_clack.wav", - } - - -class SlowMotionLabel(Scene): - def construct(self): - words = TextMobject("Slow motion replay") - words.scale(2).to_edge(UP) - self.play(Write(words)) - self.wait() - - -class BlocksAndWallExampleMass1e6(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e6, - "velocity": -1, - }, - }, - "wait_time": 20, - } - - -class BlocksAndWallExampleMass1e6SlowMo(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e6, - "velocity": -0.1, - "distance": 4.1 - }, - }, - "wait_time": 60, - "collision_sound": "slow_clack.wav", - } - - -class BlocksAndWallExampleMass1e8(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e8, - "velocity": -1, - }, - }, - "wait_time": 25, - } - - -class BlocksAndWallExampleMass1e10(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e10, - "velocity": -1, - }, - }, - "wait_time": 25, - } - - -class DigitsOfPi(Scene): - CONFIG = {"n_digits": 9} - - def construct(self): - nd = self.n_digits - pow10 = int(10**nd) - rounded_pi = int(pow10 * PI) / pow10 - equation = TexMobject( - ("\\pi = {:." + str(nd) + "f}...").format(rounded_pi) - ) - equation.set_color(YELLOW) - pi_creature = Randolph(color=YELLOW) - pi_creature.match_width(equation[0]) - pi_creature.scale(1.4) - pi_creature.move_to(equation[0], DOWN) - self.add(pi_creature, equation[1]) - self.play(ShowIncreasingSubsets( - equation[2:], - rate_func=linear, - run_time=1, - )) - self.play(Blink(pi_creature)) - self.wait() - - -class GalperinPaperScroll(ExternallyAnimatedScene): - pass - - -class PiComputingAlgorithmsAxes(Scene): - def construct(self): - self.setup_axes() - self.add_methods() - - def setup_axes(self): - axes = Axes( - x_min=0, - y_min=0, - x_max=9, - y_max=5, - axis_config={ - "tick_frequency": 100, - "numbers_with_elongated_ticks": [], - } - ) - - y_label = TextMobject("Efficiency") - y_label.rotate(90 * DEGREES) - y_label.next_to(axes.y_axis, LEFT, SMALL_BUFF) - x_label = TextMobject("Elegance") - x_label.next_to(axes.x_axis, DOWN, SMALL_BUFF) - axes.add(y_label, x_label) - axes.center().to_edge(DOWN) - self.axes = axes - - title = TextMobject("Algorithms for computing $\\pi$") - title.scale(1.5) - title.to_edge(UP, buff=MED_SMALL_BUFF) - - self.add(title, axes) - - def add_methods(self): - method_location_list = [ - (self.get_machin_like_formula(), (1, 4.5)), - (self.get_viete(), (3, 3.5)), - (self.get_measuring_tape(), (0.5, 1)), - (self.get_monte_carlo(), (2, 0.2)), - (self.get_basel(), (6, 1)), - (self.get_blocks_image(), (8, 0.1)), - ] - - algorithms = VGroup() - for method, location in method_location_list: - cross = TexMobject("\\times") - cross.set_color(RED) - cross.move_to(self.axes.coords_to_point(*location)) - method.next_to(cross, UP, SMALL_BUFF) - method.align_to(cross, LEFT) - method.shift_onto_screen() - algorithms.add(VGroup(method, cross)) - - self.play(LaggedStartMap( - FadeInFromDown, algorithms, - run_time=4, - lag_ratio=0.4, - )) - self.wait() - self.play(ShowCreationThenFadeAround(algorithms[-1][0])) - - def get_machin_like_formula(self): - formula = TexMobject( - "\\frac{\\pi}{4} = " - "12\\arctan\\left(\\frac{1}{49}\\right) + " - "32\\arctan\\left(\\frac{1}{57}\\right) - " - "5\\arctan\\left(\\frac{1}{239}\\right) + " - "12\\arctan\\left(\\frac{1}{110{,}443}\\right)" - ) - formula.scale(0.5) - return formula - - def get_viete(self): - formula = TexMobject( - "\\frac{2}{\\pi} = " - "\\frac{\\sqrt{2}}{2} \\cdot" - "\\frac{\\sqrt{2 + \\sqrt{2}}}{2} \\cdot" - "\\frac{\\sqrt{2 + \\sqrt{2 + \\sqrt{2}}}}{2} \\cdots" - ) - formula.scale(0.5) - return formula - - def get_measuring_tape(self): - return TextMobject("Measuring tape").scale(0.75) - - def get_monte_carlo(self): - return TextMobject("Monte Carlo").scale(0.75) - - def get_basel(self): - formula = TexMobject( - "\\frac{\\pi^2}{6} = " - "\\sum_{n=1}^\\infty \\frac{1}{n^2}" - ) - formula.scale(0.5) - return formula - - def get_blocks_image(self): - scene = BlocksAndWallScene( - write_to_movie=False, - skip_animations=True, - count_clacks=False, - floor_y=1, - wall_x=0, - n_wall_ticks=6, - sliding_blocks_config={ - "block1_config": { - "mass": 1e2, - "velocity": -0.01, - "distance": 3.5 - }, - "block2_config": { - "distance": 1, - "velocity": 0, - }, - } - ) - group = VGroup( - scene.wall, scene.floor, - scene.blocks.block1, - scene.blocks.block2, - ) - group.set_width(3) - return group - - -class StepsOfTheAlgorithm(TeacherStudentsScene): - def construct(self): - steps = self.get_steps() - steps.arrange( - DOWN, - buff=MED_LARGE_BUFF, - aligned_edge=LEFT, - ) - steps.to_corner(UL) - steps.scale(0.8) - - for step in steps: - self.play( - FadeInFromDown(step[0]), - self.teacher.change, "raise_right_hand" - ) - self.play( - Write(step[1], run_time=2), - self.get_student_changes( - *["pondering"] * 3, - look_at_arg=step, - ) - ) - self.wait() - self.change_student_modes( - "sassy", "erm", "confused", - look_at_arg=steps, - added_anims=[self.teacher.change, "happy"] - ) - self.wait(3) - - def get_steps(self): - return VGroup( - TextMobject("Step 1:", "Implement a physics engine"), - TextMobject( - "Step 2:", - "Choose the number of digits, $d$,\\\\" - "of $\\pi$ that you want to compute" - ), - TextMobject( - "Step 3:", - "Set one mass to $100^{d - 1}$,\\\\" - "the other to $1$" - ), - TextMobject("Step 4:", "Count collisions"), - ) - - -class StepsOfTheAlgorithmJustTitles(StepsOfTheAlgorithm): - def construct(self): - self.remove(*self.pi_creatures) - titles = self.get_steps() - for title in titles: - title.scale(1.5) - title.to_edge(UP) - - last_title = VectorizedPoint() - for title in titles: - self.play( - FadeInFromDown(title), - FadeOutAndShift(last_title, UP), - ) - self.wait() - last_title = title - - -class BlocksAndWallExampleToShowWithSteps(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e22, - "velocity": -1, - "label_text": "$100^{(12 - 1)}$\\,kg", - "width": 2, - }, - "collect_clack_data": False, - }, - "wait_time": 25, - "counter_group_shift_vect": 5 * LEFT, - "count_clacks": True, - "include_sound": False, - "show_flash_animations": False, - } - - -class CompareToGalacticMass(Scene): - def construct(self): - self.add_digits_of_pi() - self.show_mass() - self.show_galactic_black_holes() - self.show_total_count() - - def add_digits_of_pi(self): - # 20 digits of pi - digits = TexMobject("3.1415926535897932384...") - digits.set_width(FRAME_WIDTH - 3) - digits.to_edge(UP) - - highlighted_digits = VGroup(*[ - d.copy().set_background_stroke(color=BLUE, width=5) - for d in [digits[0], *digits[2:-3]] - ]) - counter = Integer(0) - counter.scale(1.5) - counter.set_color(BLUE) - brace = VMobject() - self.add(counter, brace) - - for k in range(len(highlighted_digits)): - if k == 0: - self.add(digits[0]) - else: - self.remove(highlighted_digits[k - 1]) - self.add(digits[k + 1]) - self.add(highlighted_digits[k]) - counter.increment_value() - brace.become(Brace(highlighted_digits[:k + 1], DOWN)) - counter.next_to(brace, DOWN) - self.wait(0.1) - self.add(digits) - self.remove(*highlighted_digits) - - digits_word = TextMobject("digits") - digits_word.scale(1.5) - digits_word.match_color(counter) - counter.generate_target() - group = VGroup(counter.target, digits_word) - group.arrange( - RIGHT, - index_of_submobject_to_align=0, - aligned_edge=DOWN, - buff=0.7, - ) - group.next_to(brace, DOWN) - self.play( - MoveToTarget(counter), - FadeInFrom(digits_word, LEFT), - ) - self.wait() - - self.pi_digits_group = VGroup( - digits, brace, counter, digits_word - ) - - def show_mass(self): - bw_scene = BlocksAndWallExample( - write_to_movie=False, - skip_animations=True, - count_clacks=False, - show_flash_animations=False, - floor_y=0, - wall_x=-2, - n_wall_ticks=8, - sliding_blocks_config={ - "block1_config": { - "mass": 1e6, - "velocity": -0.01, - "distance": 4.5, - "label_text": "$100^{(20 - 1)}$ kg", - "fill_color": BLACK, - }, - "block2_config": { - "distance": 1, - "velocity": 0, - }, - } - ) - block1 = bw_scene.blocks.block1 - block2 = bw_scene.blocks.block2 - group = VGroup( - bw_scene.wall, bw_scene.floor, - block1, block2 - ) - group.center() - group.to_edge(DOWN) - - arrow = Vector(2 * LEFT, color=RED) - arrow.shift(block1.get_center()) - group.add(arrow) - - brace = Brace(block1.label[:-2], UP, buff=SMALL_BUFF) - number_words = TextMobject( - "100", *["billion"] * 4, - ) - number_words.next_to(brace, UP, buff=SMALL_BUFF) - VGroup(brace, number_words).set_color(YELLOW) - - self.play(Write(group)) - self.wait() - last_word = number_words[0].copy() - last_word.next_to(brace, UP, SMALL_BUFF) - self.play( - GrowFromCenter(brace), - FadeInFromDown(last_word), - ) - for k in range(1, len(number_words) + 1): - self.remove(last_word) - last_word = number_words[:k].copy() - last_word.next_to(brace, UP, SMALL_BUFF) - self.add(last_word) - self.wait(0.4) - self.wait() - self.remove(last_word) - self.add(number_words) - group.add(brace, number_words) - self.play(group.to_corner, DL) - - self.block1 = block1 - self.block2 = block2 - self.block_setup_group = group - - def show_galactic_black_holes(self): - black_hole = SVGMobject(file_name="black_hole") - black_hole.set_color(BLACK) - black_hole.set_sheen(0.2, UL) - black_hole.set_height(1) - black_holes = VGroup(*[ - black_hole.copy() for k in range(10) - ]) - black_holes.arrange_in_grid(5, 2) - black_holes.to_corner(DR) - random.shuffle(black_holes.submobjects) - for bh in black_holes: - bh.save_state() - bh.scale(3) - bh.set_fill(DARK_GREY, 0) - - equals = TexMobject("=") - equals.scale(2) - equals.next_to(self.block1, RIGHT) - - words = TextMobject("10x Sgr A$^*$ \\\\ supermassive \\\\ black hole") - words.next_to(equals, RIGHT) - self.add(words) - - self.play( - Write(equals), - Write(words), - LaggedStartMap( - Restore, black_holes, - run_time=3 - ) - ) - self.wait() - - self.black_hole_words = VGroup(equals, words) - self.black_holes = black_holes - - def show_total_count(self): - digits = self.pi_digits_group[0] - to_fade = self.pi_digits_group[1:] - tex_string = "{:,}".format(31415926535897932384) - number = TexMobject(tex_string) - number.scale(1.5) - number.to_edge(UP) - - commas = VGroup(*[ - mob - for c, mob in zip(tex_string, number) - if c is "," - ]) - dots = VGroup(*[ - mob - for c, mob in zip(digits.get_tex_string(), digits) - if c is "." - ]) - - self.play(FadeOut(to_fade)) - self.play( - ReplacementTransform( - VGroup(*filter(lambda m: m not in dots, digits)), - VGroup(*filter(lambda m: m not in commas, number)), - ), - ReplacementTransform( - dots, commas, - lag_ratio=0.5, - run_time=2 - ) - ) - - group0 = number[:2].copy() - group1 = number[3:3 + 9 + 2].copy() - group2 = number[-(9 + 2):].copy() - for group in group0, group1, group2: - group.set_background_stroke(color=BLUE, width=5) - self.add(group) - self.wait(0.5) - self.remove(group) - - -class BlocksAndWallExampleGalacticMass(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e10, - "velocity": -1, - "label_text": "$100^{(20 - 1)}$\\,kg", - "width": 2, - }, - }, - "wait_time": 25, - "counter_group_shift_vect": 5 * LEFT, - "count_clacks": False, - } - - def setup(self): - super().setup() - words = TextMobject( - "Burst of $10^{38}$ clacks per second" - ) - words.scale(1.5) - words.to_edge(UP) - self.add(words) - - -class RealPhysicsVsThis(Scene): - def construct(self): - physics = TextMobject("Real physics") - this = TextMobject("This process") - this.set_color() - physics.to_edge(LEFT) - this.next_to(physics) - self.add(physics, this) - self.play( - this.shift, FRAME_WIDTH * RIGHT, - rate_func=rush_into, - run_time=3, - ) - self.wait() - - -class CompareAlgorithmToPhysics(PiCreatureScene): - def construct(self): - morty = self.pi_creature - right_pic = ImageMobject( - self.get_image_file_path().replace( - str(self), "PiComputingAlgorithmsAxes" - ) - ) - right_rect = SurroundingRectangle(right_pic, buff=0, color=WHITE) - right_pic.add(right_rect) - right_pic.set_height(3) - right_pic.next_to(morty, UR) - right_pic.shift_onto_screen() - - left_rect = right_rect.copy() - left_rect.next_to(morty, UL) - left_rect.shift_onto_screen() - - self.play( - FadeInFromDown(right_pic), - morty.change, "raise_right_hand", - ) - self.wait() - self.play( - FadeInFromDown(left_rect), - morty.change, "raise_left_hand", - ) - self.wait() - - digits = TexMobject("3.141592653589793238462643383279502884197...") - digits.set_width(FRAME_WIDTH - 1) - digits.to_edge(UP) - self.play( - FadeOutAndShift(right_pic, 5 * RIGHT), - # FadeOutAndShift(left_rect, 5 * LEFT), - FadeOut(left_rect), - PiCreatureBubbleIntroduction( - morty, "This doesn't seem \\\\ like me...", - bubble_class=ThoughtBubble, - bubble_kwargs={"direction": LEFT}, - target_mode="pondering", - look_at_arg=left_rect, - ), - LaggedStartMap( - FadeInFrom, digits, - lambda m: (m, LEFT), - run_time=5, - lag_ratio=0.2, - ) - ) - self.blink() - self.wait() - self.play(morty.change, "confused", left_rect) - self.wait(5) - - def create_pi_creature(self): - return Mortimer().flip().to_edge(DOWN) - - -class AskAboutWhy(TeacherStudentsScene): - def construct(self): - circle = Circle(radius=2, color=YELLOW) - circle.next_to(self.teacher, UL) - ke_conservation = TexMobject( - "\\frac{1}{2}m_1 v_1^2 + " - "\\frac{1}{2}m_2 v_2^2 = \\text{const.}" - ) - ke_conservation.move_to(circle) - - self.student_says("But why?") - self.change_student_modes( - "erm", "raise_left_hand", "sassy", - added_anims=[self.teacher.change, "happy"] - ) - self.wait() - self.play( - ShowCreation(circle), - RemovePiCreatureBubble(self.students[1]), - self.teacher.change, "raise_right_hand", - ) - self.change_all_student_modes( - "pondering", look_at_arg=circle - ) - self.wait(2) - self.play( - Write(ke_conservation), - circle.stretch, 1.5, 0, - ) - self.change_all_student_modes("confused") - self.look_at(circle) - self.wait(3) - - -class LightBouncingNoFanning(LightBouncing): - CONFIG = { - "mirror_shift_vect": 2 * DOWN, - "mirror_length": 6, - "beam_start_x": 8, - "beam_height": 0.5, - } - - -class LightBouncingFanning(LightBouncingNoFanning): - CONFIG = { - "show_fanning": True, - } - - -class NextVideo(Scene): - def construct(self): - videos = VGroup(*[VideoIcon() for x in range(2)]) - videos.set_height(2) - for video in videos: - video.set_color(BLUE) - video.set_sheen(0.5, UL) - videos.arrange(RIGHT, buff=2) - - titles = VGroup( - TextMobject("Here and now"), - TextMobject("Solution"), - ) - for title, video in zip(titles, videos): - # title.scale(1.5) - title.next_to(video, UP) - video.add(title) - - dots = TextMobject(".....") - dots.scale(2) - dots.move_to(videos) - - mid_words = TextMobject( - "Patient\\\\", "problem\\\\", "solving" - ) - mid_words.next_to(dots, DOWN) - randy = Randolph(height=1) - randy.next_to(dots, UP, SMALL_BUFF) - thought_bubble = ThoughtBubble(height=2, width=2, direction=LEFT) - thought_bubble.set_stroke(width=2) - thought_bubble.move_to(randy.get_corner(UR), DL) - speech_bubble = SpeechBubble(height=2, width=2) - speech_bubble.pin_to(randy) - speech_bubble.write("What do \\\\ you think?") - friends = VGroup( - PiCreature(color=BLUE_E), - PiCreature(color=BLUE_C), - Mortimer() - ) - friends.set_height(1) - friends.arrange(RIGHT, buff=MED_SMALL_BUFF) - friends[:2].next_to(randy, LEFT) - friends[2].next_to(randy, RIGHT) - - self.add(videos[0]) - self.wait() - self.play( - TransformFromCopy(*videos), - ) - self.play(Write(dots)) - self.wait() - self.play( - LaggedStartMap( - FadeInFrom, mid_words, - lambda m: (m, UP), - lag_ratio=0.8, - ), - randy.change, "pondering", - VFadeIn(randy), - videos.space_out_submobjects, 1.3, - ) - self.play(ShowCreation(thought_bubble)) - self.play(Blink(randy)) - self.play( - Uncreate(thought_bubble), - ShowCreation(speech_bubble), - Write(speech_bubble.content), - randy.change, "maybe", friends[0].eyes, - LaggedStartMap(FadeInFromDown, friends), - videos.space_out_submobjects, 1.6, - ) - self.play( - LaggedStartMap( - ApplyMethod, friends, - lambda m: (m.change, "pondering"), - run_time=1, - lag_ratio=0.7, - ) - ) - self.play(Blink(friends[2])) - self.play(friends[0].change, "confused") - self.wait() - - -class EndScreen(Scene): - def construct(self): - width = (475 / 1280) * FRAME_WIDTH - height = width * (323 / 575) - video_rect = Rectangle( - width=width, - height=height, - ) - video_rect.shift(UP) - video_rects = VGroup(*[ - video_rect.copy().set_color(color) - for color in [BLUE_E, BLUE_C, BLUE_D, GREY_BROWN] - ]) - for rect in video_rects[1::2]: - rect.reverse_points() - video_rect.set_fill(DARK_GREY, 0.5) - video_rect.set_stroke(GREY_BROWN, 0.5) - date = TextMobject( - "Solution will be\\\\" - "posted", "1/20/19", - ) - date[1].set_color(YELLOW) - date.set_width(video_rect.get_width() - 2 * MED_SMALL_BUFF) - date.move_to(video_rect) - - handle = TextMobject("@3blue1brown") - handle.next_to(video_rect, DOWN, MED_LARGE_BUFF) - - self.add(video_rect, date, handle) - for n in range(10): - self.play( - FadeOut(video_rects[(n - 1) % 4]), - ShowCreation(video_rects[n % 4]), - run_time=2, - ) - - -class Thumbnail(BlocksAndWallExample, MovingCameraScene): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e4, - "velocity": -1.5, - }, - "collect_clack_data": False, - }, - "wait_time": 0, - "count_clacks": False, - "show_flash_animations": False, - "floor_y": -3.0, - "include_sound": False, - } - - def setup(self): - MovingCameraScene.setup(self) - BlocksAndWallExample.setup(self) - - def construct(self): - self.camera_frame.shift(0.9 * UP) - # self.mobjects.insert( - # 0, - # FullScreenFadeRectangle( - # color=DARK_GREY, - # opacity=0.5, - # sheen_direction=UL, - # sheen=0.5, - # ), - # ) - self.thicken_lines() - self.grow_labels() - self.add_vector() - self.add_text() - - def thicken_lines(self): - self.floor.set_stroke(WHITE, 10) - self.wall.set_stroke(WHITE, 10) - self.wall[1:].set_stroke(WHITE, 4) - - def grow_labels(self): - blocks = self.blocks - for block in blocks.block1, blocks.block2: - block.remove(block.label) - block.label.scale(2.5, about_point=block.get_top()) - self.add(block.label) - - def add_vector(self): - blocks = self.blocks - arrow = self.arrow = Vector( - 2.5 * LEFT, - color=RED, - rectangular_stem_width=1.5, - tip_length=0.5 - ) - arrow.move_to(blocks.block1.get_center(), RIGHT) - arrow.add_to_back( - arrow.copy().set_stroke(GREY, 5) - ) - self.add(arrow) - - def add_text(self): - question = self.question = TextMobject( - "How many\\\\collisions?" - ) - question.scale(2.5) - question.to_edge(UP) - question.set_color(YELLOW) - question.set_stroke(RED, 2, background=True) - self.add(question) diff --git a/from_3b1b/old/clacks/solution1.py b/from_3b1b/old/clacks/solution1.py deleted file mode 100644 index 8751802c..00000000 --- a/from_3b1b/old/clacks/solution1.py +++ /dev/null @@ -1,3204 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.clacks.question import * -from from_3b1b.old.div_curl import ShowTwoPopulations - - -OUTPUT_DIRECTORY = "clacks/solution1" - - -class FromPuzzleToSolution(MovingCameraScene): - def construct(self): - big_rect = FullScreenFadeRectangle() - big_rect.set_fill(DARK_GREY, 0.5) - self.add(big_rect) - - rects = VGroup(ScreenRectangle(), ScreenRectangle()) - rects.set_height(3) - rects.arrange(RIGHT, buff=2) - - titles = VGroup( - TextMobject("Puzzle"), - TextMobject("Solution"), - ) - - images = Group( - ImageMobject("BlocksAndWallExampleMass16"), - ImageMobject("AnalyzeCircleGeometry"), - ) - for title, rect, image in zip(titles, rects, images): - title.scale(1.5) - title.next_to(rect, UP) - image.replace(rect) - self.add(image, rect, title) - - frame = self.camera_frame - frame.save_state() - - self.play( - frame.replace, images[0], - run_time=3 - ) - self.wait() - self.play(Restore(frame, run_time=3)) - self.play( - frame.replace, images[1], - run_time=3, - ) - self.wait() - - -class BlocksAndWallExampleMass16(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 16, - "velocity": -1.5, - }, - }, - "wait_time": 25, - } - - -class Mass16WithElasticLabel(Mass1e1WithElasticLabel): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 16, - } - }, - } - - -class BlocksAndWallExampleMass64(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 64, - "velocity": -1.5, - }, - }, - "wait_time": 25, - } - - -class BlocksAndWallExampleMass1e4(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e4, - "velocity": -1.5, - }, - }, - "wait_time": 25, - } - - -class BlocksAndWallExampleMassMillion(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e6, - "velocity": -0.9, - "label_text": "$100^{3}$ kg" - }, - }, - "wait_time": 30, - "million_fade_time": 4, - "min_time_between_sounds": 0.002, - } - - def setup(self): - super().setup() - self.add_million_label() - - def add_million_label(self): - first_label = self.blocks.block1.label - brace = Brace(first_label[:-2], UP, buff=SMALL_BUFF) - new_label = TexMobject("1{,}000{,}000") - new_label.next_to(brace, UP, buff=SMALL_BUFF) - new_label.add(brace) - new_label.set_color(YELLOW) - - def update_label(label): - d_time = self.get_time() - self.million_fade_time - opacity = smooth(d_time) - label.set_fill(opacity=d_time) - - new_label.add_updater(update_label) - first_label.add(new_label) - - -class BlocksAndWallExampleMassTrillion(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e12, - "velocity": -1, - }, - }, - "wait_time": 30, - "min_time_between_sounds": 0.001, - } - - -class First6DigitsOfPi(DigitsOfPi): - CONFIG = {"n_digits": 6} - - -class FavoritesInDescription(Scene): - def construct(self): - words = TextMobject("(See the description for \\\\ some favorites)") - words.scale(1.5) - self.add(words) - - -class V1EqualsV2Line(Scene): - def construct(self): - line = Line(LEFT, 7 * RIGHT) - eq = TexMobject("v_1", "=", "v_2") - eq.set_color_by_tex("v_", RED) - eq.next_to(RIGHT, UR, SMALL_BUFF) - self.play( - Write(eq, run_time=1), - ShowCreation(line), - ) - self.wait() - - -class PhaseSpaceTitle(Scene): - def construct(self): - title = TextMobject("Phase space") - title.scale(1.5) - title.to_edge(UP) - rect = ScreenRectangle(height=6) - rect.next_to(title, DOWN) - self.add(rect) - self.play(Write(title, run_time=1)) - self.wait() - - -class AskAboutFindingNewVelocities(Scene): - CONFIG = { - "floor_y": -3, - "wall_x": -6.5, - "wall_height": 7, - "block1_config": { - "mass": 10, - "fill_color": BLUE_E, - "velocity": -1, - }, - "block2_config": {"mass": 1}, - "block1_start_x": 7, - "block2_start_x": 3, - "v_arrow_scale_value": 1.0, - "is_halted": False, - } - - def setup(self): - self.add_clack_sound_file() - - def construct(self): - self.add_clack_sound_file() - self.add_floor() - self.add_wall() - self.add_blocks() - self.add_velocity_labels() - - self.ask_about_transfer() - self.show_ms_and_vs() - self.show_value_on_equations() - - def add_clack_sound_file(self): - self.clack_file = os.path.join(SOUND_DIR, "clack.wav") - - def add_floor(self): - floor = self.floor = Line( - self.wall_x * RIGHT, - (FRAME_WIDTH / 2) * RIGHT, - ) - floor.shift(self.floor_y * UP) - self.add(floor) - - def add_wall(self): - wall = self.wall = Wall(height=self.wall_height) - wall.move_to( - self.wall_x * RIGHT + self.floor_y * UP, - DR, - ) - self.add(wall) - - def add_blocks(self): - block1 = self.block1 = Block(**self.block1_config) - block2 = self.block2 = Block(**self.block2_config) - blocks = self.blocks = VGroup(block1, block2) - block1.move_to(self.block1_start_x * RIGHT + self.floor_y * UP, DOWN) - block2.move_to(self.block2_start_x * RIGHT + self.floor_y * UP, DOWN) - - self.add_velocity_phase_space_point() - # Add arrows - for block in blocks: - arrow = Vector(self.block_to_v_vector(block)) - arrow.set_color(RED) - arrow.set_stroke(BLACK, 1, background=True) - arrow.move_to(block.get_center(), RIGHT) - block.arrow = arrow - block.add(arrow) - - block.v_label = DecimalNumber( - block.velocity, - num_decimal_places=2, - background_stroke_width=2, - ) - block.v_label.set_color(RED) - block.add(block.v_label) - - # Add updater - blocks.add_updater(self.update_blocks) - self.add( - blocks, - block2.arrow, block1.arrow, - block2.v_label, block1.v_label, - ) - - def add_velocity_phase_space_point(self): - self.vps_point = VectorizedPoint([ - np.sqrt(self.block1.mass) * self.block1.velocity, - np.sqrt(self.block2.mass) * self.block2.velocity, - 0 - ]) - - def add_velocity_labels(self): - v_labels = self.get_next_velocity_labels() - - self.add(v_labels) - - def ask_about_transfer(self): - energy_expression, momentum_expression = \ - self.get_energy_and_momentum_expressions() - energy_words = TextMobject("Conservation of energy:") - energy_words.move_to(UP) - energy_words.to_edge(LEFT, buff=1.5) - momentum_words = TextMobject("Conservation of momentum:") - momentum_words.next_to( - energy_words, DOWN, - buff=0.7, - ) - - energy_expression.next_to(energy_words, RIGHT, MED_LARGE_BUFF) - momentum_expression.next_to(energy_expression, DOWN) - momentum_expression.next_to(momentum_words, RIGHT) - - velocity_labels = self.all_velocity_labels - randy = Randolph(height=2) - randy.next_to(velocity_labels, DR) - randy.save_state() - randy.fade(1) - - # Up to collisions - self.go_through_next_collision(include_velocity_label_animation=True) - self.play( - randy.restore, - randy.change, "pondering", velocity_labels[0], - ) - self.halt() - self.play(randy.look_at, velocity_labels[-1]) - self.play(Blink(randy)) - self.play(randy.change, "confused") - self.play(Blink(randy)) - self.wait() - self.play( - FadeInFrom(energy_words, RIGHT), - FadeInFromDown(energy_expression), - FadeOut(randy), - ) - self.wait() - self.play( - FadeInFrom(momentum_words, RIGHT), - FadeInFromDown(momentum_expression) - ) - self.wait() - - self.energy_expression = energy_expression - self.energy_words = energy_words - self.momentum_expression = momentum_expression - self.momentum_words = momentum_words - - def show_ms_and_vs(self): - block1 = self.block1 - block2 = self.block2 - energy_expression = self.energy_expression - momentum_expression = self.momentum_expression - - for block in self.blocks: - block.shift_onto_screen() - - m1_labels = VGroup( - block1.label, - energy_expression.get_part_by_tex("m_1"), - momentum_expression.get_part_by_tex("m_1"), - ) - m2_labels = VGroup( - block2.label, - energy_expression.get_part_by_tex("m_2"), - momentum_expression.get_part_by_tex("m_2"), - ) - v1_labels = VGroup( - block1.v_label, - energy_expression.get_part_by_tex("v_1"), - momentum_expression.get_part_by_tex("v_1"), - ) - v2_labels = VGroup( - block2.v_label, - energy_expression.get_part_by_tex("v_2"), - momentum_expression.get_part_by_tex("v_2"), - ) - label_groups = VGroup( - m1_labels, m2_labels, - v1_labels, v2_labels, - ) - for group in label_groups: - group.rects = VGroup(*map( - SurroundingRectangle, - group - )) - - for group in label_groups: - self.play(LaggedStartMap( - ShowCreation, group.rects, - lag_ratio=0.8, - run_time=1, - )) - self.play(FadeOut(group.rects)) - - def show_value_on_equations(self): - energy_expression = self.energy_expression - momentum_expression = self.momentum_expression - energy_text = VGroup(energy_expression, self.energy_words) - momentum_text = VGroup(momentum_expression, self.momentum_words) - block1 = self.block1 - block2 = self.block2 - block1.save_state() - block2.save_state() - - v_terms, momentum_v_terms = [ - VGroup(*[ - expr.get_part_by_tex("v_{}".format(d)) - for d in [1, 2] - ]) - for expr in [energy_expression, momentum_expression] - ] - v_braces = VGroup(*[ - Brace(term, UP, buff=SMALL_BUFF) - for term in v_terms - ]) - v_decimals = VGroup(*[DecimalNumber(0) for x in range(2)]) - - def update_v_decimals(v_decimals): - values = self.get_velocities() - for decimal, value, brace in zip(v_decimals, values, v_braces): - decimal.set_value(value) - decimal.next_to(brace, UP, SMALL_BUFF) - - update_v_decimals(v_decimals) - - energy_const_brace, momentum_const_brace = [ - Brace( - expr.get_part_by_tex("const"), UP, - buff=SMALL_BUFF, - ) - for expr in [energy_expression, momentum_expression] - ] - - sqrt_m_vect = np.array([ - np.sqrt(self.block1.mass), - np.sqrt(self.block2.mass), - 0 - ]) - - def get_energy(): - return 0.5 * get_norm(self.vps_point.get_location())**2 - - def get_momentum(): - return np.dot(self.vps_point.get_location(), sqrt_m_vect) - - energy_decimal = DecimalNumber(get_energy()) - energy_decimal.next_to(energy_const_brace, UP, SMALL_BUFF) - momentum_decimal = DecimalNumber(get_momentum()) - momentum_decimal.next_to(momentum_const_brace, UP, SMALL_BUFF) - - VGroup( - energy_const_brace, energy_decimal, - momentum_const_brace, momentum_decimal, - ).set_color(YELLOW) - - self.play( - ShowCreationThenFadeAround(energy_expression), - momentum_text.set_fill, {"opacity": 0.25}, - FadeOut(self.all_velocity_labels), - ) - self.play(*[ - *map(GrowFromCenter, v_braces), - *map(VFadeIn, v_decimals), - GrowFromCenter(energy_const_brace), - FadeIn(energy_decimal), - ]) - energy_decimal.add_updater( - lambda m: m.set_value(get_energy()) - ) - v_decimals.add_updater(update_v_decimals) - self.add(v_decimals) - self.unhalt() - self.vps_point.save_state() - for x in range(8): - self.go_through_next_collision() - energy_decimal.clear_updaters() - momentum_decimal.set_value(get_momentum()) - self.halt() - self.play(*[ - momentum_text.set_fill, {"opacity": 1}, - FadeOut(energy_text), - FadeOut(energy_const_brace), - FadeOut(energy_decimal), - GrowFromCenter(momentum_const_brace), - FadeIn(momentum_decimal), - *[ - ApplyMethod(b.next_to, vt, UP, SMALL_BUFF) - for b, vt in zip(v_braces, momentum_v_terms) - ], - ]) - self.unhalt() - self.vps_point.restore() - momentum_decimal.add_updater( - lambda m: m.set_value(get_momentum()) - ) - momentum_decimal.add_updater( - lambda m: m.next_to(momentum_const_brace, UP, SMALL_BUFF) - ) - for x in range(9): - self.go_through_next_collision() - self.wait(10) - - # Helpers - - def get_energy_and_momentum_expressions(self): - tex_to_color_map = { - "v_1": RED_B, - "v_2": RED_B, - "m_1": BLUE_C, - "m_2": BLUE_C, - } - energy_expression = TexMobject( - "\\frac{1}{2} m_1 (v_1)^2 + ", - "\\frac{1}{2} m_2 (v_2)^2 = ", - "\\text{const.}", - tex_to_color_map=tex_to_color_map, - ) - momentum_expression = TexMobject( - "m_1 v_1 + m_2 v_2 =", "\\text{const.}", - tex_to_color_map=tex_to_color_map - ) - return VGroup( - energy_expression, - momentum_expression, - ) - - def go_through_next_collision(self, include_velocity_label_animation=False): - block2 = self.block2 - if block2.velocity >= 0: - self.wait_until(self.blocks_are_hitting) - self.add_sound(self.clack_file) - self.transfer_momentum() - edge = RIGHT - else: - self.wait_until(self.block2_is_hitting_wall) - self.add_sound(self.clack_file) - self.reflect_block2() - edge = LEFT - anims = [Flash(block2.get_edge_center(edge))] - if include_velocity_label_animation: - anims.append(self.get_next_velocity_labels_animation()) - self.play(*anims, run_time=0.5) - - def get_next_velocity_labels_animation(self): - return FadeInFrom( - self.get_next_velocity_labels(), - LEFT, - run_time=0.5 - ) - - def get_next_velocity_labels(self, v1=None, v2=None): - new_labels = self.get_velocity_labels(v1, v2) - if hasattr(self, "all_velocity_labels"): - arrow = Vector(RIGHT) - arrow.next_to(self.all_velocity_labels) - new_labels.next_to(arrow, RIGHT) - new_labels.add(arrow) - else: - self.all_velocity_labels = VGroup() - self.all_velocity_labels.add(new_labels) - return new_labels - - def get_velocity_labels(self, v1=None, v2=None): - default_vs = self.get_velocities() - v1 = v1 or default_vs[0] - v2 = v2 or default_vs[1] - labels = VGroup( - TexMobject("v_1 = {:.2f}".format(v1)), - TexMobject("v_2 = {:.2f}".format(v2)), - ) - labels.arrange( - DOWN, - buff=MED_SMALL_BUFF, - aligned_edge=LEFT, - ) - labels.scale(0.9) - for label in labels: - label[:2].set_color(RED) - labels.next_to(self.wall, RIGHT) - labels.to_edge(UP, buff=MED_SMALL_BUFF) - return labels - - def update_blocks(self, blocks, dt): - for block, velocity in zip(blocks, self.get_velocities()): - block.velocity = velocity - if not self.is_halted: - block.shift(block.velocity * dt * RIGHT) - center = block.get_center() - block.arrow.put_start_and_end_on( - center, - center + self.block_to_v_vector(block), - ) - max_height = 0.25 - block.v_label.set_value(block.velocity) - if block.v_label.get_height() > max_height: - block.v_label.set_height(max_height) - block.v_label.next_to( - block.arrow.get_start(), UP, - buff=SMALL_BUFF, - ) - return blocks - - def block_to_v_vector(self, block): - return block.velocity * self.v_arrow_scale_value * RIGHT - - def blocks_are_hitting(self): - x1 = self.block1.get_left()[0] - x2 = self.block2.get_right()[0] - buff = 0.01 - return (x1 < x2 + buff) - - def block2_is_hitting_wall(self): - x2 = self.block2.get_left()[0] - buff = 0.01 - return (x2 < self.wall_x + buff) - - def get_velocities(self): - m1 = self.block1.mass - m2 = self.block2.mass - vps_coords = self.vps_point.get_location() - return [ - vps_coords[0] / np.sqrt(m1), - vps_coords[1] / np.sqrt(m2), - ] - - def transfer_momentum(self): - m1 = self.block1.mass - m2 = self.block2.mass - theta = np.arctan(np.sqrt(m2 / m1)) - self.reflect_block2() - self.vps_point.rotate(2 * theta, about_point=ORIGIN) - - def reflect_block2(self): - self.vps_point.points[:, 1] *= -1 - - def halt(self): - self.is_halted = True - - def unhalt(self): - self.is_halted = False - - -class IntroduceVelocityPhaseSpace(AskAboutFindingNewVelocities): - CONFIG = { - "wall_height": 1.5, - "floor_y": -3.5, - "block1_start_x": 5, - "block2_start_x": 0, - "axes_config": { - "x_axis_config": { - "x_min": -5.5, - "x_max": 6, - }, - "y_axis_config": { - "x_min": -3.5, - "x_max": 4, - }, - "axis_config": { - "unit_size": 0.7, - }, - }, - "momentum_line_scale_factor": 4, - } - - def construct(self): - self.add_wall_floor_and_blocks() - self.show_two_equations() - self.draw_axes() - self.draw_ellipse() - self.rescale_axes() - self.show_starting_point() - self.show_initial_collide() - self.ask_about_where_to_land() - self.show_conservation_of_momentum_equation() - self.show_momentum_line() - self.reiterate_meaning_of_line_and_circle() - self.reshow_first_jump() - self.show_bounce_off_wall() - self.show_reflection_about_x() - self.show_remaining_collisions() - - def add_wall_floor_and_blocks(self): - self.add_floor() - self.add_wall() - self.add_blocks() - self.halt() - - def show_two_equations(self): - equations = self.get_energy_and_momentum_expressions() - equations.arrange(DOWN, buff=LARGE_BUFF) - equations.shift(UP) - v1_terms, v2_terms = v_terms = VGroup(*[ - VGroup(*[ - expr.get_parts_by_tex(tex) - for expr in equations - ]) - for tex in ("v_1", "v_2") - ]) - for eq in equations: - eq.highlighted_copy = eq.copy() - eq.highlighted_copy.set_fill(opacity=0) - eq.highlighted_copy.set_stroke(YELLOW, 3) - - self.add(equations) - self.play( - ShowCreation(equations[0].highlighted_copy), - run_time=0.75, - ) - self.play( - FadeOut(equations[0].highlighted_copy), - ShowCreation(equations[1].highlighted_copy), - run_time=0.75, - ) - self.play( - FadeOut(equations[1].highlighted_copy), - run_time=0.75, - ) - self.play(LaggedStartMap( - Indicate, v_terms, - lag_ratio=0.75, - rate_func=there_and_back, - )) - self.wait() - - self.equations = equations - - def draw_axes(self): - equations = self.equations - energy_expression, momentum_expression = equations - - axes = self.axes = Axes(**self.axes_config) - axes.to_edge(UP, buff=SMALL_BUFF) - axes.set_stroke(width=2) - - # Axes labels - x_axis_labels = VGroup( - TexMobject("x = ", "v_1"), - TexMobject("x = ", "\\sqrt{m_1}", "\\cdot", "v_1"), - ) - y_axis_labels = VGroup( - TexMobject("y = ", "v_2"), - TexMobject("y = ", "\\sqrt{m_2}", "\\cdot", "v_2"), - ) - axis_labels = self.axis_labels = VGroup(x_axis_labels, y_axis_labels) - for label_group in axis_labels: - for label in label_group: - label.set_color_by_tex("v_", RED) - label.set_color_by_tex("m_", BLUE) - for label in x_axis_labels: - label.next_to(axes.x_axis.get_right(), UP) - for label in y_axis_labels: - label.next_to(axes.y_axis.get_top(), DR) - - # Introduce axes and labels - self.play( - equations.scale, 0.8, - equations.to_corner, UL, {"buff": MED_SMALL_BUFF}, - Write(axes), - ) - self.wait() - self.play( - momentum_expression.set_fill, {"opacity": 0.2}, - Indicate(energy_expression, scale_factor=1.05), - ) - self.wait() - for n in range(2): - tex = "v_{}".format(n + 1) - self.play( - TransformFromCopy( - energy_expression.get_part_by_tex(tex), - axis_labels[n][0].get_part_by_tex(tex), - ), - FadeInFromDown(axis_labels[n][0][0]), - ) - - # Show vps_dot - vps_dot = self.vps_dot = Dot(color=RED) - vps_dot.set_stroke(BLACK, 2, background=True) - vps_dot.add_updater( - lambda m: m.move_to(axes.coords_to_point( - *self.get_velocities() - )) - ) - - vps_point = self.vps_point - vps_point.save_state() - kwargs = { - "path_arc": PI / 3, - "run_time": 2, - } - target_locations = [ - 6 * RIGHT + 2 * UP, - 6 * RIGHT + 2 * DOWN, - 6 * LEFT + 1 * UP, - ] - self.add(vps_dot) - for target_location in target_locations: - self.play( - vps_point.move_to, target_location, - **kwargs, - ) - self.play(Restore(vps_point, **kwargs)) - self.wait() - - def draw_ellipse(self): - vps_dot = self.vps_dot - vps_point = self.vps_point - axes = self.axes - energy_expression = self.equations[0] - - ellipse = self.ellipse = Circle(color=YELLOW) - ellipse.set_stroke(BLACK, 5, background=True) - ellipse.rotate(PI) - mass_ratio = self.block1.mass / self.block2.mass - ellipse.replace( - Polygon(*[ - axes.coords_to_point(x, y * np.sqrt(mass_ratio)) - for x, y in [(1, 0), (0, 1), (-1, 0), (0, -1)] - ]), - stretch=True - ) - - self.play(Indicate(energy_expression, scale_factor=1.05)) - self.add(ellipse, vps_dot) - self.play( - ShowCreation(ellipse), - Rotating(vps_point, about_point=ORIGIN), - run_time=6, - rate_func=lambda t: smooth(t, 3), - ) - self.wait() - - def rescale_axes(self): - ellipse = self.ellipse - axis_labels = self.axis_labels - equations = self.equations - vps_point = self.vps_point - vps_dot = self.vps_dot - vps_dot.clear_updaters() - vps_dot.add_updater( - lambda m: m.move_to(ellipse.get_left()) - ) - - mass_ratio = self.block1.mass / self.block2.mass - brief_circle = ellipse.copy() - brief_circle.stretch(np.sqrt(mass_ratio), 0) - brief_circle.set_stroke(WHITE, 2) - - xy_equation = self.xy_equation = TexMobject( - "\\frac{1}{2}", - "\\left(", "x^2", "+", "y^2", "\\right)", - "=", "\\text{const.}" - ) - xy_equation.scale(0.8) - xy_equation.next_to(equations[0], DOWN) - - self.play(ShowCreationThenFadeOut(brief_circle)) - for i, labels, block in zip(it.count(), axis_labels, self.blocks): - self.play(ShowCreationThenFadeAround(labels[0])) - self.play( - ReplacementTransform(labels[0][0], labels[1][0]), - ReplacementTransform(labels[0][-1], labels[1][-1]), - FadeInFromDown(labels[1][1:-1]), - ellipse.stretch, np.sqrt(block.mass), i, - ) - self.wait() - - vps_dot.clear_updaters() - vps_dot.add_updater( - lambda m: m.move_to(self.axes.coords_to_point( - *self.vps_point.get_location()[:2] - )) - ) - - self.play( - FadeInFrom(xy_equation, UP), - FadeOut(equations[1]) - ) - self.wait() - curr_x = vps_point.get_location()[0] - for x in [0.5 * curr_x, 2 * curr_x, curr_x]: - axes_center = self.axes.coords_to_point(0, 0) - self.play( - vps_point.move_to, x * RIGHT, - UpdateFromFunc( - ellipse, - lambda m: m.set_width( - 2 * get_norm( - vps_dot.get_center() - axes_center, - ), - ).move_to(axes_center) - ), - run_time=2, - ) - self.wait() - - def show_starting_point(self): - vps_dot = self.vps_dot - block1, block2 = self.blocks - - self.unhalt() - self.wait(3) - self.halt() - self.play(ShowCreationThenFadeAround(vps_dot)) - self.wait() - - def show_initial_collide(self): - self.unhalt() - self.go_through_next_collision() - self.wait() - self.halt() - self.wait() - - def ask_about_where_to_land(self): - self.play( - Rotating( - self.vps_point, - about_point=ORIGIN, - run_time=6, - rate_func=lambda t: smooth(t, 3), - ), - ) - self.wait(2) - - def show_conservation_of_momentum_equation(self): - equations = self.equations - energy_expression, momentum_expression = equations - momentum_expression.set_fill(opacity=1) - momentum_expression.shift(MED_SMALL_BUFF * UP) - momentum_expression.shift(MED_SMALL_BUFF * LEFT) - xy_equation = self.xy_equation - - momentum_xy_equation = self.momentum_xy_equation = TexMobject( - "\\sqrt{m_1}", "x", "+", - "\\sqrt{m_2}", "y", "=", - "\\text{const.}", - ) - momentum_xy_equation.set_color_by_tex("m_", BLUE) - momentum_xy_equation.scale(0.8) - momentum_xy_equation.next_to( - momentum_expression, DOWN, - buff=MED_LARGE_BUFF, - aligned_edge=RIGHT, - ) - - self.play( - FadeOut(xy_equation), - energy_expression.set_fill, {"opacity": 0.2}, - FadeInFromDown(momentum_expression) - ) - self.play(ShowCreationThenFadeAround(momentum_expression)) - self.wait() - self.play(FadeInFrom(momentum_xy_equation, UP)) - self.wait() - - def show_momentum_line(self): - vps_dot = self.vps_dot - m1 = self.block1.mass - m2 = self.block2.mass - line = Line(np.sqrt(m2) * LEFT, np.sqrt(m1) * DOWN) - line.scale(self.momentum_line_scale_factor) - line.set_stroke(GREEN, 3) - line.move_to(vps_dot) - - slope_label = TexMobject( - "\\text{Slope =}", "-\\sqrt{\\frac{m_1}{m_2}}" - ) - slope_label.scale(0.8) - slope_label.next_to(vps_dot, LEFT, LARGE_BUFF) - slope_arrow = Arrow( - slope_label.get_right(), - line.point_from_proportion(0.45), - buff=SMALL_BUFF, - ) - slope_group = VGroup(line, slope_label, slope_arrow) - foreground_mobs = VGroup( - self.equations[1], self.momentum_xy_equation, - self.blocks, self.vps_dot - ) - for mob in foreground_mobs: - if isinstance(mob, TexMobject): - mob.set_stroke(BLACK, 3, background=True) - - self.add(line, *foreground_mobs) - self.play(ShowCreation(line)) - self.play( - FadeInFrom(slope_label, RIGHT), - GrowArrow(slope_arrow), - ) - self.wait() - self.add(slope_group, *foreground_mobs) - self.play(slope_group.shift, 4 * RIGHT, run_time=3) - self.play(slope_group.shift, 5 * LEFT, run_time=3) - self.play( - slope_group.shift, RIGHT, - run_time=1, - rate_func=lambda t: t**4, - ) - self.wait() - - self.momentum_line = line - self.slope_group = slope_group - - def reiterate_meaning_of_line_and_circle(self): - line_vect = self.momentum_line.get_vector() - vps_point = self.vps_point - - for x in [0.25, -0.5, 0.25]: - self.play( - vps_point.shift, x * line_vect, - run_time=2 - ) - self.wait() - self.play(Rotating( - vps_point, - about_point=ORIGIN, - rate_func=lambda t: smooth(t, 3), - )) - self.wait() - - def reshow_first_jump(self): - vps_point = self.vps_point - curr_point = vps_point.get_location() - start_point = get_norm(curr_point) * LEFT - - for n in range(8): - vps_point.move_to( - [start_point, curr_point][n % 2] - ) - self.wait(0.5) - self.wait() - - def show_bounce_off_wall(self): - self.unhalt() - self.go_through_next_collision() - self.halt() - - def show_reflection_about_x(self): - vps_point = self.vps_point - - curr_location = vps_point.get_location() - old_location = np.array(curr_location) - old_location[1] *= -1 - - # self.play( - # ApplyMethod( - # self.block2.move_to, self.wall.get_corner(DR), DL, - # path_arc=30 * DEGREES, - # ) - # ) - for n in range(4): - self.play( - vps_point.move_to, - [old_location, curr_location][n % 2] - ) - self.wait() - - group = VGroup( - self.ellipse, - self.lines[-1], - self.vps_dot.copy().clear_updaters() - ) - for x in range(2): - self.play( - Rotate( - group, PI, RIGHT, - about_point=self.axes.coords_to_point(0, 0) - ), - ) - self.remove(group[-1]) - - def show_remaining_collisions(self): - line = self.momentum_line - # slope_group = self.slope_group - vps_dot = self.vps_dot - axes = self.axes - slope = np.sqrt(self.block2.mass / self.block1.mass) - - end_region = Polygon( - axes.coords_to_point(0, 0), - axes.coords_to_point(10, 0), - axes.coords_to_point(10, slope * 10), - stroke_width=0, - fill_color=GREEN, - fill_opacity=0.3 - ) - - self.unhalt() - for x in range(7): - self.go_through_next_collision() - if x == 0: - self.halt() - self.play(line.move_to, vps_dot) - self.wait() - self.unhalt() - self.play(FadeIn(end_region)) - self.go_through_next_collision() - self.wait(5) - - # Helpers - def add_update_line(self, func): - if not hasattr(self, "lines"): - self.lines = VGroup() - if hasattr(self, "vps_dot"): - old_vps_point = self.vps_dot.get_center() - func() - self.vps_dot.update() - new_vps_point = self.vps_dot.get_center() - line = Line(old_vps_point, new_vps_point) - line.set_stroke(WHITE, 2) - self.add(line) - self.lines.add(line) - else: - func() - - def transfer_momentum(self): - self.add_update_line(super().transfer_momentum) - - def reflect_block2(self): - self.add_update_line(super().reflect_block2) - - -class IntroduceVelocityPhaseSpaceWith16(IntroduceVelocityPhaseSpace): - CONFIG = { - "block1_config": { - "mass": 16, - "velocity": -0.5, - }, - "momentum_line_scale_factor": 0, - } - - -class SimpleRect(Scene): - def construct(self): - self.add(Rectangle(width=6, height=2, color=WHITE)) - - -class SurprisedRandy(Scene): - def construct(self): - randy = Randolph() - self.play(FadeIn(randy)) - self.play(randy.change, "surprised", 3 * UR) - self.play(Blink(randy)) - self.wait() - self.play(randy.change, "pondering", 3 * UR) - self.play(Blink(randy)) - self.wait(2) - self.play(FadeOut(randy)) - - -class HuntForPi(TeacherStudentsScene): - def construct(self): - self.student_says( - "Hunt for $\\pi$!", - bubble_kwargs={"direction": LEFT}, - target_mode="hooray" - ) - self.change_all_student_modes( - "hooray", - added_anims=[self.teacher.change, "happy"] - ) - self.wait() - - -class StretchBySqrt10(Scene): - def construct(self): - arrow = DoubleArrow(2 * LEFT, 2 * RIGHT) - arrow.tip[1].shift(0.05 * LEFT) - value = TexMobject("\\sqrt{10}") - value.next_to(arrow, UP) - arrow.save_state() - arrow.stretch(0, 0) - self.play( - Restore(arrow), - Write(value, run_time=1), - ) - self.wait() - - -class XCoordNegative(Scene): - def construct(self): - rect = Rectangle(height=4, width=4) - rect.set_stroke(width=0) - rect.set_fill(RED, 0.5) - rect.save_state() - rect.stretch(0, 0, about_edge=RIGHT) - self.play(Restore(rect)) - self.wait() - - -class YCoordZero(Scene): - def construct(self): - rect = Rectangle(height=4, width=8) - rect.set_stroke(width=0) - rect.set_fill(WHITE, 0.5) - rect.save_state() - self.play( - rect.stretch, 0.01, 1, - rect.set_fill, {"opacity": 1} - ) - self.wait() - - -class CircleDiagramFromSlidingBlocks(Scene): - CONFIG = { - "BlocksAndWallSceneClass": BlocksAndWallExampleMass1e1, - "circle_config": { - "radius": 2, - "stroke_color": YELLOW, - "stroke_width": 3, - }, - "lines_style": { - "stroke_color": WHITE, - "stroke_width": 2, - }, - "axes_config": { - "style": { - "stroke_color": LIGHT_GREY, - "stroke_width": 1, - }, - "width": 5, - "height": 4.5, - }, - "end_zone_style": { - "stroke_width": 0, - "fill_color": GREEN, - "fill_opacity": 0.3, - }, - "show_dot": True, - "show_vector": False, - } - - def construct(self): - sliding_blocks_scene = self.BlocksAndWallSceneClass( - show_flash_animations=False, - write_to_movie=False, - wait_time=0, - file_writer_config={ - "output_directory": ".", - } - ) - blocks = sliding_blocks_scene.blocks - times = [pair[1] for pair in blocks.clack_data] - self.mass_ratio = 1 / blocks.mass_ratio - self.show_circle_lines( - times=times, - slope=(-1 / np.sqrt(blocks.mass_ratio)) - ) - - def show_circle_lines(self, times, slope): - circle = self.get_circle() - axes = self.get_axes() - lines = self.get_lines(circle.radius, slope) - end_zone = self.get_end_zone() - - dot = Dot(color=RED, radius=0.06) - dot.move_to(lines[0].get_start()) - - vector = Vector(lines[0].get_start()) - vector.set_color(RED) - vector.add_updater(lambda v: v.put_start_and_end_on( - ORIGIN, dot.get_center() - )) - vector.set_stroke(BLACK, 2, background=True) - - dot.set_opacity(int(self.show_dot)) - vector.set_opacity(int(self.show_vector)) - - self.add(end_zone, axes, circle, dot, vector) - - last_time = 0 - for time, line in zip(times, lines): - if time > 300: - time = last_time + 1 - self.wait(time - last_time) - last_time = time - dot.move_to(line.get_end()) - self.add(line, dot, vector) - self.wait() - - def get_circle(self): - circle = Circle(**self.circle_config) - circle.rotate(PI) # Nice to have start point on left - return circle - - def get_axes(self): - config = self.axes_config - axes = VGroup( - Line(LEFT, RIGHT).set_width(config["width"]), - Line(DOWN, UP).set_height(config["height"]) - ) - axes.set_style(**config["style"]) - return axes - - def get_lines(self, radius, slope): - theta = np.arctan(-1 / slope) - n_clacks = int(PI / theta) - points = [] - for n in range(n_clacks + 1): - theta_mult = (n + 1) // 2 - angle = 2 * theta * theta_mult - if n % 2 == 0: - angle *= -1 - new_point = radius * np.array([ - -np.cos(angle), -np.sin(angle), 0 - ]) - points.append(new_point) - - lines = VGroup(*[ - Line(p1, p2) - for p1, p2 in zip(points, points[1:]) - ]) - lines.set_style(**self.lines_style) - return lines - - def get_end_zone(self): - slope = 1 / np.sqrt(self.mass_ratio) - x = self.axes_config["width"] / 2 - zone = Polygon( - ORIGIN, x * RIGHT, x * RIGHT + slope * x * UP, - ) - zone.set_style(**self.end_zone_style) - return zone - - -class CircleDiagramFromSlidingBlocksSameMass(CircleDiagramFromSlidingBlocks): - CONFIG = { - "BlocksAndWallSceneClass": BlocksAndWallExampleSameMass - } - - -class CircleDiagramFromSlidingBlocksSameMass1e1(CircleDiagramFromSlidingBlocks): - CONFIG = { - "BlocksAndWallSceneClass": BlocksAndWallExampleMass1e1 - } - - -class CircleDiagramFromSlidingBlocks1e2(CircleDiagramFromSlidingBlocks): - CONFIG = { - "BlocksAndWallSceneClass": BlocksAndWallExampleMass1e2 - } - - -class CircleDiagramFromSlidingBlocks1e4(CircleDiagramFromSlidingBlocks): - CONFIG = { - "BlocksAndWallSceneClass": BlocksAndWallExampleMass1e4 - } - - -class AnnouncePhaseDiagram(CircleDiagramFromSlidingBlocks): - def construct(self): - pd_words = TextMobject("Phase diagram") - pd_words.scale(1.5) - pd_words.move_to(self.hold_up_spot, DOWN) - pd_words_border = pd_words.copy() - pd_words_border.set_stroke(YELLOW, 2) - pd_words_border.set_fill(opacity=0) - - simple_words = TextMobject("Simple but powerful") - simple_words.next_to(pd_words, UP, LARGE_BUFF) - simple_words.shift(LEFT) - simple_words.set_color(BLUE) - simple_arrow = Arrow( - simple_words.get_bottom(), - pd_words.get_top(), - color=simple_words.get_color(), - ) - - self.play( - self.teacher.change, "raise_right_hand", - FadeInFromDown(pd_words) - ) - self.change_student_modes( - "pondering", "thinking", "pondering", - added_anims=[ShowCreationThenFadeOut(pd_words_border)] - ) - self.wait() - self.play( - FadeInFrom(simple_words, RIGHT), - GrowArrow(simple_arrow), - self.teacher.change, "hooray", - ) - self.change_student_modes( - "thinking", "happy", "thinking", - ) - self.wait(3) - - -class AnalyzeCircleGeometry(CircleDiagramFromSlidingBlocks, MovingCameraScene): - CONFIG = { - "mass_ratio": 16, - "circle_config": { - "radius": 3, - }, - "axes_config": { - "width": FRAME_WIDTH, - "height": FRAME_HEIGHT, - }, - "lines_style": { - "stroke_width": 2, - }, - } - - def construct(self): - self.add_mass_ratio_label() - self.add_circle_with_lines() - self.show_equal_arc_lengths() - self.use_arc_lengths_to_count() - self.focus_on_three_points() - self.show_likewise_for_all_jumps() - self.drop_arc_for_each_hop() - self.try_adding_one_more_arc() - self.zoom_out() - - def add_mass_ratio_label(self, mass_ratio=None): - mass_ratio = mass_ratio or self.mass_ratio - mass_ratio_label = TextMobject( - "Mass ratio =", "{:,} : 1".format(mass_ratio) - ) - mass_ratio_label.to_corner(UL, buff=MED_SMALL_BUFF) - self.add(mass_ratio_label) - self.mass_ratio_label = mass_ratio_label - - def add_circle_with_lines(self): - circle = self.get_circle() - axes = self.get_axes() - axes_labels = self.get_axes_labels(axes) - slope = -np.sqrt(self.mass_ratio) - lines = self.get_lines( - radius=circle.radius, - slope=slope, - ) - end_zone = self.get_end_zone() - - end_zone_words = TextMobject("End zone") - end_zone_words.set_height(0.25) - end_zone_words.next_to(ORIGIN, UP, SMALL_BUFF) - end_zone_words.to_edge(RIGHT, buff=MED_SMALL_BUFF) - end_zone_words.set_color(GREEN) - - self.add( - axes, axes_labels, - circle, end_zone, end_zone_words, - ) - self.play(ShowCreation(lines, run_time=3, rate_func=linear)) - self.wait() - - self.set_variables_as_attrs( - circle, axes, lines, - end_zone, end_zone_words, - ) - - def show_equal_arc_lengths(self): - circle = self.circle - radius = circle.radius - theta = self.theta = np.arctan(1 / np.sqrt(self.mass_ratio)) - n_arcs = int(PI / (2 * theta)) - - lower_arcs = VGroup(*[ - Arc( - start_angle=(PI + n * 2 * theta), - angle=(2 * theta), - radius=radius - ) - for n in range(n_arcs + 1) - ]) - lower_arcs[0::2].set_color(RED) - lower_arcs[1::2].set_color(BLUE) - - upper_arcs = lower_arcs.copy() - upper_arcs.rotate(PI, axis=RIGHT, about_point=ORIGIN) - upper_arcs[0::2].set_color(BLUE) - upper_arcs[1::2].set_color(RED) - - all_arcs = VGroup(*it.chain(*zip(lower_arcs, upper_arcs))) - if int(PI / theta) % 2 == 1: - all_arcs.remove(all_arcs[-1]) - - arc_copies = lower_arcs.copy() - for arc_copy in arc_copies: - arc_copy.generate_target() - for arc in arc_copies: - arc.target.rotate(-(arc.start_angle - PI + theta)) - - equal_signs = VGroup(*[ - TexMobject("=") for x in range(len(lower_arcs)) - ]) - equal_signs.scale(0.8) - for sign in equal_signs: - sign.generate_target() - - movers = VGroup(*it.chain(*zip( - arc_copies, equal_signs - ))) - movers.remove(movers[-1]) - mover_targets = VGroup(*[mover.target for mover in movers]) - mover_targets.arrange(RIGHT, buff=SMALL_BUFF) - mover_targets.next_to(ORIGIN, DOWN) - mover_targets.to_edge(LEFT) - - equal_signs.scale(0) - equal_signs.fade(1) - equal_signs.move_to(mover_targets) - - all_arcs.save_state() - for arc in all_arcs: - arc.rotate(90 * DEGREES) - arc.fade(1) - arc.set_stroke(width=20) - self.play(Restore( - all_arcs, lag_ratio=0.5, - run_time=2, - )) - self.wait() - self.play(LaggedStartMap(MoveToTarget, movers)) - self.wait() - - self.arcs_equation = movers - self.lower_arcs = lower_arcs - self.upper_arcs = upper_arcs - self.all_arcs = all_arcs - - def use_arc_lengths_to_count(self): - all_arcs = self.all_arcs - lines = self.lines - - arc_counts = VGroup() - for n, arc in enumerate(all_arcs): - count_mob = Integer(n + 1) - count_mob.scale(0.75) - buff = SMALL_BUFF - if len(all_arcs) > 100: - count_mob.scale(0.1) - count_mob.set_stroke(WHITE, 0.25) - buff = 0.4 * SMALL_BUFF - point = arc.point_from_proportion(0.5) - count_mob.next_to(point, normalize(point), buff) - arc_counts.add(count_mob) - - self.play( - FadeOut(lines), - FadeOut(all_arcs), - FadeOut(self.arcs_equation), - ) - self.play( - ShowIncreasingSubsets(all_arcs), - ShowIncreasingSubsets(lines), - ShowIncreasingSubsets(arc_counts), - run_time=5, - rate_func=bezier([0, 0, 1, 1]) - ) - self.wait() - - for group in all_arcs, arc_counts: - targets = VGroup() - for elem in group: - elem.generate_target() - targets.add(elem.target) - targets.space_out_submobjects(1.2) - - kwargs = { - "rate_func": there_and_back, - "run_time": 3, - } - self.play( - LaggedStartMap(MoveToTarget, all_arcs, **kwargs), - LaggedStartMap(MoveToTarget, arc_counts, **kwargs), - ) - - self.arc_counts = arc_counts - - def focus_on_three_points(self): - lines = self.lines - arcs = self.all_arcs - arc_counts = self.arc_counts - theta = self.theta - - arc = arcs[4] - - lines.save_state() - line_pair = lines[3:5] - lines_to_fade = VGroup(*lines[:3], *lines[5:]) - - three_points = [ - line_pair[0].get_start(), - line_pair[1].get_start(), - line_pair[1].get_end(), - ] - three_dots = VGroup(*map(Dot, three_points)) - three_dots.set_color(RED) - - theta_arc = Arc( - radius=1, - start_angle=-90 * DEGREES, - angle=theta - ) - theta_arc.shift(three_points[1]) - theta_label = TexMobject("\\theta") - theta_label.next_to(theta_arc, DOWN, SMALL_BUFF) - - center_lines = VGroup( - Line(three_points[0], ORIGIN), - Line(ORIGIN, three_points[2]), - ) - center_lines.match_style(line_pair) - - two_theta_arc = Arc( - radius=1, - start_angle=(center_lines[0].get_angle() + PI), - angle=2 * theta - ) - two_theta_label = TexMobject("2\\theta") - arc_center = two_theta_arc.point_from_proportion(0.5) - two_theta_label.next_to( - arc_center, normalize(arc_center), SMALL_BUFF - ) - two_theta_label.shift(SMALL_BUFF * RIGHT) - - to_fade = VGroup(arc_counts, arcs, lines_to_fade) - - self.play( - LaggedStartMap( - FadeOut, VGroup(*to_fade.family_members_with_points()) - ) - ) - lines_to_fade.fade(1) - self.play(FadeInFromLarge(three_dots[0])) - self.play(TransformFromCopy(*three_dots[:2])) - self.play(TransformFromCopy(*three_dots[1:3])) - self.wait() - self.play( - ShowCreation(theta_arc), - FadeInFrom(theta_label, UP) - ) - self.wait() - self.play( - line_pair.set_stroke, WHITE, 1, - TransformFromCopy(line_pair, center_lines), - TransformFromCopy(theta_arc, two_theta_arc), - TransformFromCopy(theta_label, two_theta_label), - ) - self.wait() - self.play( - TransformFromCopy(two_theta_arc, arc), - two_theta_label.move_to, 2.7 * arc_center, - ) - self.wait() - - self.three_dots = three_dots - self.theta_group = VGroup(theta_arc, theta_label) - self.center_lines_group = VGroup( - center_lines, two_theta_arc, - ) - self.two_theta_label = two_theta_label - - def show_likewise_for_all_jumps(self): - lines = self.lines - arcs = self.all_arcs - - every_other_line = lines[::2] - - self.play( - Restore( - lines, - lag_ratio=0.5, - run_time=2 - ), - FadeOut(self.center_lines_group), - FadeOut(self.three_dots), - ) - self.play(LaggedStartMap( - ApplyFunction, every_other_line, - lambda line: ( - lambda l: l.scale(10 / l.get_length()).set_stroke(BLUE, 3), - line - ) - )) - self.play(Restore(lines)) - self.wait() - - # Shift theta label - last_point = lines[3].get_end() - last_arc = arcs[4] - two_theta_label = self.two_theta_label - theta_group_copy = self.theta_group.copy() - for line, arc in zip(lines[5:10:2], arcs[6:11:2]): - new_point = line.get_end() - arc_point = arc.point_from_proportion(0.5) - self.play( - theta_group_copy.shift, new_point - last_point, - two_theta_label.move_to, 1.1 * arc_point, - FadeIn(arc), - FadeOut(last_arc), - ) - self.wait() - last_point = new_point - last_arc = arc - self.play( - FadeOut(theta_group_copy), - FadeOut(two_theta_label), - FadeOut(last_arc), - ) - self.wait() - - def drop_arc_for_each_hop(self): - lines = self.lines - arcs = self.all_arcs - - two_theta_labels = VGroup() - wedges = VGroup() - for arc in arcs: - label = TexMobject("2\\theta") - label.scale(0.8) - label.move_to(1.1 * arc.point_from_proportion(0.5)) - two_theta_labels.add(label) - - wedge = arc.copy() - wedge.add_line_to(ORIGIN) - wedge.add_line_to(wedge.points[0]) - wedge.set_stroke(width=0) - wedge.set_fill(arc.get_color(), 0.2) - wedges.add(wedge) - - self.remove(lines) - for line, arc, label, wedge in zip(lines, arcs, two_theta_labels, wedges): - self.play( - ShowCreation(line), - FadeInFrom(arc, normalize(arc.get_center())), - FadeInFrom(label, normalize(arc.get_center())), - FadeIn(wedge), - ) - - self.wedges = wedges - self.two_theta_labels = two_theta_labels - - def try_adding_one_more_arc(self): - wedges = self.wedges - theta = self.theta - - last_wedge = wedges[-1] - new_wedge = last_wedge.copy() - new_wedge.set_color(PURPLE) - new_wedge.set_stroke(WHITE, 1) - - self.play(FadeIn(new_wedge)) - for angle in [-2 * theta, 4 * DEGREES, -2 * DEGREES]: - self.play(Rotate(new_wedge, angle, about_point=ORIGIN)) - self.wait() - self.play( - new_wedge.shift, 10 * RIGHT, - rate_func=running_start, - path_arc=-30 * DEGREES, - ) - self.remove(new_wedge) - - def zoom_out(self): - frame = self.camera_frame - self.play( - frame.scale, 2, {"about_point": (TOP + RIGHT_SIDE)}, - run_time=3 - ) - self.wait() - - # Helpers - def get_axes_labels(self, axes): - axes_labels = VGroup( - TexMobject("x = ", "\\sqrt{m_1}", "\\cdot", "v_1"), - TexMobject("y = ", "\\sqrt{m_2}", "\\cdot", "v_2"), - ) - for label in axes_labels: - label.set_height(0.4) - axes_labels[0].next_to(ORIGIN, DOWN, SMALL_BUFF) - axes_labels[0].to_edge(RIGHT, MED_SMALL_BUFF) - axes_labels[1].next_to(ORIGIN, RIGHT, SMALL_BUFF) - axes_labels[1].to_edge(UP, SMALL_BUFF) - return axes_labels - - -class InscribedAngleTheorem(Scene): - def construct(self): - self.add_title() - self.show_circle() - self.let_point_vary() - - def add_title(self): - title = TextMobject("Inscribed angle theorem") - title.scale(1.5) - title.to_edge(UP) - self.add(title) - self.title = title - - def show_circle(self): - # Boy is this over engineered... - circle = self.circle = Circle( - color=BLUE, - radius=2, - ) - center_dot = Dot(circle.get_center(), color=WHITE) - self.add(circle, center_dot) - - angle_trackers = self.angle_trackers = VGroup( - ValueTracker(TAU / 4), - ValueTracker(PI), - ValueTracker(-TAU / 4), - ) - - def get_point(angle): - return circle.point_from_proportion( - (angle % TAU) / TAU - ) - - def get_dot(angle): - dot = Dot(get_point(angle)) - dot.set_color(RED) - dot.set_stroke(BLACK, 3, background=True) - return dot - - def get_dots(): - return VGroup(*[ - get_dot(at.get_value()) - for at in angle_trackers - ]) - - def update_labels(labels): - center = circle.get_center() - for dot, label in zip(dots, labels): - label.move_to( - center + 1.2 * (dot.get_center() - center) - ) - - def get_lines(): - lines = VGroup(*[ - Line(d1.get_center(), d2.get_center()) - for d1, d2 in zip(dots, dots[1:]) - ]) - lines.set_stroke(WHITE, 3) - return lines - - def get_center_lines(): - points = [ - dots[0].get_center(), - circle.get_center(), - dots[2].get_center(), - ] - lines = VGroup(*[ - Line(p1, p2) - for p1, p2 in zip(points, points[1:]) - ]) - lines.set_stroke(LIGHT_GREY, 3) - return lines - - def get_angle_label(lines, tex, reduce_angle=True): - a1 = (lines[0].get_angle() + PI) % TAU - a2 = lines[1].get_angle() - diff = (a2 - a1) - if reduce_angle: - diff = ((diff + PI) % TAU) - PI - point = lines[0].get_end() - arc = Arc( - start_angle=a1, - angle=diff, - radius=0.5, - ) - arc.shift(point) - arc_center = arc.point_from_proportion(0.5) - label = TexMobject(tex) - vect = (arc_center - point) - vect = (0.3 + get_norm(vect)) * normalize(vect) - label.move_to(point + vect) - return VGroup(arc, label) - - def get_theta_label(): - return get_angle_label(lines, "\\theta") - - def get_2theta_label(): - return get_angle_label(center_lines, "2\\theta", False) - - dots = get_dots() - lines = get_lines() - center_lines = get_center_lines() - labels = VGroup(*[ - TexMobject("P_{}".format(n + 1)) - for n in range(3) - ]) - update_labels(labels) - theta_label = get_theta_label() - two_theta_label = get_2theta_label() - - self.play( - FadeInFromDown(labels[0]), - FadeInFromLarge(dots[0]), - ) - self.play( - TransformFromCopy(*labels[:2]), - TransformFromCopy(*dots[:2]), - ShowCreation(lines[0]), - ) - self.play( - ShowCreation(lines[1]), - TransformFromCopy(*labels[1:3]), - TransformFromCopy(*dots[1:3]), - Write(theta_label), - ) - self.wait() - - # Add updaters - labels.add_updater(update_labels) - dots.add_updater(lambda m: m.become(get_dots())) - lines.add_updater(lambda m: m.become(get_lines())) - center_lines.add_updater(lambda m: m.become(get_center_lines())) - theta_label.add_updater(lambda m: m.become(get_theta_label())) - two_theta_label.add_updater(lambda m: m.become(get_2theta_label())) - - self.add(labels, lines, dots, theta_label) - # Further animations - self.play( - angle_trackers[0].set_value, TAU / 8, - ) - self.play( - angle_trackers[2].set_value, -TAU / 8, - ) - self.wait() - center_lines.update() - two_theta_label.update() - self.play( - TransformFromCopy(lines.copy().clear_updaters(), center_lines), - TransformFromCopy(theta_label.copy().clear_updaters(), two_theta_label), - ) - self.wait() - - self.add(center_lines, two_theta_label) - - def let_point_vary(self): - p1_tracker, p2_tracker, p3_tracker = self.angle_trackers - - kwargs = {"run_time": 2} - for angle in [TAU / 4, 3 * TAU / 4]: - self.play( - p2_tracker.set_value, angle, - **kwargs - ) - self.wait() - self.play( - p1_tracker.set_value, PI, - **kwargs - ) - self.wait() - self.play( - p3_tracker.set_value, TAU / 3, - **kwargs - ) - self.wait() - self.play( - p2_tracker.set_value, 7 * TAU / 8, - **kwargs - ) - self.wait() - - -class SimpleSlopeLabel(Scene): - def construct(self): - label = TexMobject( - "\\text{Slope}", "=", - "-\\frac{\\sqrt{m_1}}{\\sqrt{m_2}}" - ) - vector = Vector(DOWN + 2 * LEFT, color=WHITE) - vector.move_to(label[0].get_bottom(), UR) - vector.shift(SMALL_BUFF * DOWN) - self.play( - Write(label), - GrowArrow(vector), - ) - self.wait() - - -class AddTwoThetaManyTimes(Scene): - def construct(self): - expression = TexMobject( - "2\\theta", "+", - "2\\theta", "+", - "2\\theta", "+", - "\\cdots", "+", - "2\\theta", - "<", "2\\pi", - ) - expression.to_corner(UL) - - brace = Brace(expression[:-2], DOWN) - question = brace.get_text("Max number of times?") - question.set_color(YELLOW) - - central_question_group = self.get_central_question() - simplified, lil_brace, new_question = central_question_group - central_question_group.next_to(question, DOWN, LARGE_BUFF) - new_question.align_to(question, LEFT) - - for n in range(5): - self.add(expression[:2 * n + 1]) - self.wait(0.25) - self.play( - FadeInFrom(expression[-2:], LEFT), - GrowFromCenter(brace), - FadeInFrom(question, UP) - ) - self.wait(3) - self.play( - TransformFromCopy(expression[:-2], simplified[:3]), - TransformFromCopy(expression[-2:], simplified[3:]), - TransformFromCopy(brace, lil_brace), - ) - self.play(Write(new_question)) - self.wait() - - self.central_question_group = central_question_group - self.show_example() - - def get_central_question(self, brace_vect=DOWN): - expression = TexMobject( - "N", "\\cdot", "\\theta", "<", "\\pi" - ) - N = expression[0] - N.set_color(BLUE) - brace = Brace(N, brace_vect, buff=SMALL_BUFF) - question = brace.get_text( - "Maximal integer?", - ) - question.set_color(YELLOW) - result = VGroup(expression, brace, question) - result.to_corner(UL) - return result - - def show_example(self): - equation = self.get_changable_equation(0.01, n_decimal_places=2) - expression, brace, question = self.central_question_group - N_mob, dot_theta_eq, rhs, comp_pi = equation - - equation.next_to(expression, DOWN, 2, aligned_edge=LEFT) - - self.play( - TransformFromCopy(expression[0], N_mob), - TransformFromCopy(expression[1:4], dot_theta_eq), - TransformFromCopy(expression[4], rhs), - TransformFromCopy(expression[4], comp_pi), - ) - self.wait() - self.play( - ChangeDecimalToValue(N_mob, 314, run_time=5) - ) - self.wait() - self.play(ChangeDecimalToValue(N_mob, 315)) - self.wait() - self.play(ChangeDecimalToValue(N_mob, 314)) - self.wait() - self.play(ShowCreationThenFadeAround(N_mob)) - - # - def get_changable_equation(self, value, tex_string=None, n_decimal_places=10): - int_mob = Integer(1) - int_mob.set_color(BLUE) - formatter = "({:0." + str(n_decimal_places) + "f})" - tex_string = tex_string or formatter.format(value) - tex_mob = TexMobject("\\cdot", tex_string, "=") - rhs = DecimalNumber(value, num_decimal_places=n_decimal_places) - - def align_number(mob): - y0 = mob[0].get_center()[1] - y1 = tex_mob[1][1:-1].get_center()[1] - mob.shift((y1 - y0) * UP) - - int_mob.add_updater( - lambda m: m.next_to(tex_mob, LEFT, SMALL_BUFF) - ) - int_mob.add_updater(align_number) - rhs.add_updater( - lambda m: m.set_value(value * int_mob.get_value()) - ) - rhs.add_updater( - lambda m: m.next_to(tex_mob, RIGHT, SMALL_BUFF) - ) - rhs.add_updater(align_number) - - def get_comp_pi(): - if rhs.get_value() < np.pi: - result = TexMobject("< \\pi") - result.set_color(GREEN) - elif rhs.get_value() > np.pi: - result = TexMobject("> \\pi") - result.set_color(RED) - else: - result = TexMobject("= \\pi") - result.next_to(rhs, RIGHT, 2 * SMALL_BUFF) - result[1].scale(1.5, about_edge=LEFT) - return result - - comp_pi = always_redraw(get_comp_pi) - - return VGroup(int_mob, tex_mob, rhs, comp_pi) - - -class AskAboutTheta(TeacherStudentsScene): - def construct(self): - self.student_says( - "But what is $\\theta$?", - target_mode="raise_left_hand", - ) - self.change_student_modes( - "confused", "sassy", "raise_left_hand", - added_anims=[self.teacher.change, "happy"] - ) - self.wait(3) - - -class ComputeThetaFor1e4(AnalyzeCircleGeometry): - CONFIG = { - "mass_ratio": 100, - } - - def construct(self): - self.add_mass_ratio_label() - self.add_circle_with_three_lines() - self.write_slope() - self.show_tangent() - - def add_circle_with_three_lines(self): - circle = self.get_circle() - axes = self.get_axes() - slope = -np.sqrt(self.mass_ratio) - lines = self.get_lines( - radius=circle.radius, - slope=slope, - ) - end_zone = self.get_end_zone() - axes_labels = self.get_axes_labels(axes) - axes.add(axes_labels) - - lines_to_fade = VGroup(*lines[:11], *lines[13:]) - two_lines = lines[11:13] - - theta = self.theta = np.arctan(-1 / slope) - arc = Arc( - start_angle=(-90 * DEGREES), - angle=theta, - radius=2, - arc_center=two_lines[0].get_end(), - ) - theta_label = TexMobject("\\theta") - theta_label.scale(0.8) - theta_label.next_to(arc, DOWN, SMALL_BUFF) - - self.add(end_zone, axes, circle) - self.play(ShowCreation(lines, rate_func=linear)) - self.play( - lines_to_fade.set_stroke, WHITE, 1, 0.3, - ShowCreation(arc), - FadeInFrom(theta_label, UP) - ) - - self.two_lines = two_lines - self.lines = lines - self.circle = circle - self.axes = axes - self.theta_label_group = VGroup(theta_label, arc) - - def write_slope(self): - line = self.two_lines[1] - slope_label = TexMobject( - "\\text{Slope}", "=", - "\\frac{\\text{rise}}{\\text{run}}", "=", - "\\frac{-\\sqrt{m_1}}{\\sqrt{m_2}}", "=", "-10" - ) - for mob in slope_label: - mob.add_to_back(mob.copy().set_stroke(BLACK, 6)) - slope_label.next_to(line.get_center(), UR, buff=1) - slope_arrow = Arrow( - slope_label[0].get_bottom(), - line.point_from_proportion(0.45), - color=RED, - buff=SMALL_BUFF, - ) - new_line = line.copy().set_color(RED) - - self.play( - FadeInFromDown(slope_label[:3]), - ShowCreation(new_line), - GrowArrow(slope_arrow), - ) - self.remove(new_line) - line.match_style(new_line) - self.play( - FadeInFrom(slope_label[3:5], LEFT) - ) - self.wait() - self.play( - Write(slope_label[5]), - TransformFromCopy( - self.mass_ratio_label[1][:3], - slope_label[6] - ) - ) - self.wait() - - self.slope_label = slope_label - self.slope_arrow = slope_arrow - - def show_tangent(self): - l1, l2 = self.two_lines - theta = self.theta - theta_label_group = self.theta_label_group - - tan_equation = TexMobject( - "\\tan", "(", "\\theta", ")", "=", - "{\\text{run}", "\\over", "-\\text{rise}}", "=", - "\\frac{1}{10}", - ) - tan_equation.scale(0.9) - tan_equation.to_edge(LEFT, buff=MED_SMALL_BUFF) - tan_equation.shift(2 * UP) - run_word = tan_equation.get_part_by_tex("run") - rise_word = tan_equation.get_part_by_tex("rise") - - p1, p2 = l1.get_start(), l1.get_end() - p3 = p1 + get_norm(p2 - p1) * np.tan(theta) * RIGHT - triangle = Polygon(p1, p2, p3) - triangle.set_stroke(width=0) - triangle.set_fill(GREEN, 0.5) - - opposite = Line(p1, p3) - adjacent = Line(p1, p2) - opposite.set_stroke(BLUE, 3) - adjacent.set_stroke(PINK, 3) - - arctan_equation = TexMobject( - "\\theta", "=", "\\arctan", "(", "1 / 10", ")" - ) - arctan_equation.next_to(tan_equation, DOWN, MED_LARGE_BUFF) - - self.play( - FadeInFromDown(tan_equation[:8]), - ) - self.play( - TransformFromCopy(theta_label_group[1], opposite), - run_word.set_color, opposite.get_color() - ) - self.play(WiggleOutThenIn(run_word)) - self.play( - TransformFromCopy(opposite, adjacent), - rise_word.set_color, adjacent.get_color() - ) - self.play(WiggleOutThenIn(rise_word)) - self.wait() - self.play(TransformFromCopy( - self.slope_label[-1], - tan_equation[-2:], - )) - self.wait(2) - - indices = [2, 4, 0, 1, -1, 3] - movers = VGroup(*[tan_equation[i] for i in indices]).copy() - for mover, target in zip(movers, arctan_equation): - mover.target = target - # Swap last two - sm = movers.submobjects - sm[-1], sm[-2] = sm[-2], sm[-1] - self.play(LaggedStartMap( - Transform, movers[:-1], - lambda m: (m, m.target), - lag_ratio=1, - run_time=1, - path_arc=PI / 6, - )) - self.play(MoveToTarget(movers[-1])) - self.remove(movers) - self.add(arctan_equation) - self.play(ShowCreationThenFadeAround(arctan_equation)) - self.wait() - - -class ThetaChart(Scene): - def construct(self): - self.create_columns() - self.populate_columns() - self.show_values() - self.highlight_example(2) - self.highlight_example(3) - - def create_columns(self): - titles = VGroup(*[ - TextMobject("Mass ratio"), - TextMobject("$\\theta$ formula"), - TextMobject("$\\theta$ value"), - ]) - titles.scale(1.5) - titles.arrange(RIGHT, buff=1.5) - titles[1].shift(MED_SMALL_BUFF * LEFT) - titles[2].shift(MED_SMALL_BUFF * RIGHT) - titles.to_corner(UL) - - lines = VGroup() - for t1, t2 in zip(titles, titles[1:]): - line = Line(TOP, BOTTOM) - x = np.mean([t1.get_center()[0], t2.get_center()[0]]) - line.shift(x * RIGHT) - lines.add(line) - - h_line = Line(LEFT_SIDE, RIGHT_SIDE) - h_line.next_to(titles, DOWN) - h_line.to_edge(LEFT, buff=0) - lines.add(h_line) - lines.set_stroke(WHITE, 1) - - self.play( - LaggedStartMap(FadeInFromDown, titles), - LaggedStartMap(ShowCreation, lines, lag_ratio=0.8), - ) - - self.h_line = h_line - self.titles = titles - - def populate_columns(self): - top_h_line = self.h_line - x_vals = [t.get_center()[0] for t in self.titles] - - entries = [ - ( - "$m_1$ : $m_2$", - "$\\arctan(\\sqrt{m_2} / \\sqrt{m_1})$", - "" - ) - ] + [ - ( - "{:,} : 1".format(10**(2 * exp)), - "$\\arctan(1 / {:,})$".format(10**exp), - self.get_theta_decimal(exp), - ) - for exp in [1, 2, 3, 4, 5] - ] - - h_lines = VGroup(top_h_line) - entry_mobs = VGroup() - for entry in entries: - mobs = VGroup(*map(TextMobject, entry)) - for mob, x in zip(mobs, x_vals): - mob.shift(x * RIGHT) - delta_y = (mobs.get_height() / 2) + MED_SMALL_BUFF - y = h_lines[-1].get_center()[1] - delta_y - mobs.shift(y * UP) - mobs[0].set_color(BLUE) - mobs[2].set_color(YELLOW) - entry_mobs.add(mobs) - - h_line = DashedLine(LEFT_SIDE, RIGHT_SIDE) - h_line.shift((y - delta_y) * UP) - h_lines.add(h_line) - - self.play( - LaggedStartMap( - FadeInFromDown, - VGroup(*[em[:2] for em in entry_mobs]), - ), - LaggedStartMap(ShowCreation, h_lines[1:]), - lag_ratio=0.1, - run_time=5, - ) - - self.entry_mobs = entry_mobs - self.h_lines = h_lines - - def show_values(self): - values = VGroup(*[em[2] for em in self.entry_mobs]) - for value in values: - self.play(LaggedStartMap( - FadeIn, value, - lag_ratio=0.1, - run_time=0.5 - )) - self.wait(0.5) - - def highlight_example(self, exp): - entry_mobs = self.entry_mobs - example = entry_mobs[exp] - other_entries = VGroup(*entry_mobs[:exp], *entry_mobs[exp + 1:]) - - value = example[-1] - rhs = TexMobject("\\approx {:}".format(10**(-exp))) - rhs.next_to(value, RIGHT) - rhs.to_edge(RIGHT, buff=MED_SMALL_BUFF) - value.generate_target() - value.target.set_fill(opacity=1) - value.target.scale(0.9) - value.target.next_to(rhs, LEFT, SMALL_BUFF) - - self.play( - other_entries.set_fill, {"opacity": 0.25}, - example.set_fill, {"opacity": 1}, - ShowCreationThenFadeAround(example) - ) - self.wait() - self.play( - MoveToTarget(value), - Write(rhs), - ) - self.wait() - value.add(rhs) - - def get_theta_decimal(self, exp): - theta = np.arctan(10**(-exp)) - rounded_theta = np.floor(1e10 * theta) / 1e10 - return "{:0.10f}\\dots".format(rounded_theta) - - -class CentralQuestionFor1e2(AddTwoThetaManyTimes): - CONFIG = { - "exp": 2, - } - - def construct(self): - exp = self.exp - question = self.get_central_question(UP) - pi_value = TexMobject(" = {:0.10f}\\dots".format(PI)) - pi_value.next_to(question[0][-1], RIGHT, SMALL_BUFF) - pi_value.shift(0.3 * SMALL_BUFF * UP) - question.add(pi_value) - - max_count = int(PI * 10**exp) - - question.center().to_edge(UP) - self.add(question) - - b10_equation = self.get_changable_equation( - 10**(-exp), n_decimal_places=exp - ) - b10_equation.next_to(question, DOWN, buff=1.5) - arctan_equation = self.get_changable_equation( - np.arctan(10**(-exp)), n_decimal_places=10, - ) - arctan_equation.next_to(b10_equation, DOWN, MED_LARGE_BUFF) - eq_centers = [ - eq[1][2].get_center() - for eq in [b10_equation, arctan_equation] - ] - arctan_equation.shift((eq_centers[1][0] - eq_centers[1][0]) * RIGHT) - - # b10_brace = Brace(b10_equation[1][1][1:-1], UP) - arctan_brace = Brace(arctan_equation[1][1][1:-1], DOWN) - # b10_tex = b10_brace.get_tex("1 / 10") - arctan_tex = arctan_brace.get_tex( - "\\theta = \\arctan(1 / {:,})".format(10**exp) - ) - - int_mobs = b10_equation[0], arctan_equation[0] - - self.add(*b10_equation, *arctan_equation) - # self.add(b10_brace, b10_tex) - self.add(arctan_brace, arctan_tex) - - self.wait() - self.play(*[ - ChangeDecimalToValue(int_mob, max_count, run_time=8) - for int_mob in int_mobs - ]) - self.wait() - self.play(*[ - ChangeDecimalToValue(int_mob, max_count + 1, run_time=1) - for int_mob in int_mobs - ]) - self.wait() - self.play(*[ - ChangeDecimalToValue(int_mob, max_count, run_time=1) - for int_mob in int_mobs - ]) - self.play(ShowCreationThenFadeAround(int_mobs[1])) - self.wait() - - -class AnalyzeCircleGeometry1e2(AnalyzeCircleGeometry): - CONFIG = { - "mass_ratio": 100, - } - - -class CentralQuestionFor1e3(CentralQuestionFor1e2): - CONFIG = {"exp": 3} - - -class AskAboutArctanOfSmallValues(TeacherStudentsScene): - def construct(self): - self.add_title() - - equation1 = TexMobject( - "\\arctan", "(", "x", ")", "\\approx", "x" - ) - equation1.set_color_by_tex("arctan", YELLOW) - equation2 = TexMobject( - "x", "\\approx", "\\tan", "(", "x", ")", - ) - equation2.set_color_by_tex("tan", BLUE) - for mob in equation1, equation2: - mob.move_to(self.hold_up_spot, DOWN) - - self.play( - FadeInFromDown(equation1), - self.teacher.change, "raise_right_hand", - self.get_student_changes( - "erm", "sassy", "confused" - ) - ) - self.look_at(3 * UL) - self.play(equation1.shift, UP) - self.play( - TransformFromCopy( - VGroup(*[equation1[i] for i in (2, 4, 5)]), - VGroup(*[equation2[i] for i in (0, 1, 4)]), - ) - ) - self.play( - TransformFromCopy( - VGroup(*[equation1[i] for i in (0, 1, 3)]), - VGroup(*[equation2[i] for i in (2, 3, 5)]), - ), - self.get_student_changes( - "confused", "erm", "sassy", - ), - ) - self.look_at(3 * UL) - self.wait(3) - # self.student_says("Why?", target_mode="maybe") - # self.wait(3) - - def add_title(self): - title = TextMobject("For small $x$") - subtitle = TextMobject("(e.g. $x = 0.001$)") - subtitle.scale(0.75) - subtitle.next_to(title, DOWN) - title.add(subtitle) - # title.scale(1.5) - # title.to_edge(UP, buff=MED_SMALL_BUFF) - title.move_to(self.hold_up_spot) - title.to_edge(UP) - self.add(title) - - -class ActanAndTanGraphs(GraphScene): - CONFIG = { - "x_min": -PI / 8, - "x_max": 5 * PI / 8, - "y_min": -PI / 8, - "y_max": 4 * PI / 8, - "x_tick_frequency": PI / 8, - "x_leftmost_tick": -PI / 8, - "y_tick_frequency": PI / 8, - "y_leftmost_tick": -PI / 8, - "x_axis_width": 10, - "y_axis_height": 7, - "graph_origin": 2.5 * DOWN + 5 * LEFT, - "num_graph_anchor_points": 500, - } - - def construct(self): - self.setup_axes() - axes = self.axes - labels = VGroup( - TexMobject("\\pi / 8"), - TexMobject("\\pi / 4"), - TexMobject("3\\pi / 8"), - TexMobject("\\pi / 2"), - ) - for n, label in zip(it.count(1), labels): - label.scale(0.75) - label.next_to(self.coords_to_point(n * PI / 8, 0), DOWN) - self.add(label) - - id_graph = self.get_graph(lambda x: x, x_max=1.5) - arctan_graph = self.get_graph(np.arctan, x_max=1.5) - tan_graph = self.get_graph(np.tan, x_max=1.5) - graphs = VGroup(id_graph, arctan_graph, tan_graph) - - id_label = TexMobject("f(x) = x") - arctan_label = TexMobject("\\arctan(x)") - tan_label = TexMobject("\\tan(x)") - labels = VGroup(id_label, arctan_label, tan_label) - for label, graph in zip(labels, graphs): - label.match_color(graph) - label.next_to(graph.points[-1], RIGHT) - if label.get_bottom()[1] > FRAME_HEIGHT / 2: - label.next_to(graph.point_from_proportion(0.75), LEFT) - - arctan_x_tracker = ValueTracker(3 * PI / 8) - arctan_v_line = always_redraw( - lambda: self.get_vertical_line_to_graph( - arctan_x_tracker.get_value(), - arctan_graph, - line_class=DashedLine, - color=WHITE, - ) - ) - tan_x_tracker = ValueTracker(2 * PI / 8) - tan_v_line = always_redraw( - lambda: self.get_vertical_line_to_graph( - tan_x_tracker.get_value(), - tan_graph, - line_class=DashedLine, - color=WHITE, - ) - ) - - self.add(axes) - self.play( - ShowCreation(id_graph), - Write(id_label) - ) - self.play( - ShowCreation(arctan_graph), - Write(arctan_label) - ) - self.add(arctan_v_line) - self.play(arctan_x_tracker.set_value, 0, run_time=2) - self.wait() - self.play( - TransformFromCopy(arctan_graph, tan_graph), - TransformFromCopy(arctan_label, tan_label), - ) - self.add(tan_v_line) - self.play(tan_x_tracker.set_value, 0, run_time=2) - self.wait() - - -class UnitCircleIntuition(Scene): - def construct(self): - self.draw_unit_circle() - self.show_angle() - self.show_fraction() - self.show_fraction_approximation() - - def draw_unit_circle(self): - unit_size = 2.5 - axes = Axes( - axis_config={"unit_size": unit_size}, - x_min=-2.5, x_max=2.5, - y_min=-1.5, y_max=1.5, - ) - axes.set_stroke(width=1) - self.add(axes) - - radius_line = Line(ORIGIN, axes.coords_to_point(1, 0)) - radius_line.set_color(BLUE) - r_label = TexMobject("1") - r_label.add_updater( - lambda m: m.next_to(radius_line.get_center(), DOWN, SMALL_BUFF) - ) - circle = Circle(radius=unit_size, color=WHITE) - - self.add(radius_line, r_label) - self.play( - Rotating(radius_line, about_point=ORIGIN), - ShowCreation(circle), - run_time=2, - rate_func=smooth, - ) - - self.radius_line = radius_line - self.r_label = r_label - self.circle = circle - self.axes = axes - - def show_angle(self): - circle = self.circle - - tan_eq = TexMobject( - "\\tan", "(", "\\theta", ")", "=", - tex_to_color_map={"\\theta": RED}, - ) - tan_eq.next_to(ORIGIN, RIGHT, LARGE_BUFF) - tan_eq.to_edge(UP, buff=LARGE_BUFF) - - theta_tracker = ValueTracker(0) - get_theta = theta_tracker.get_value - - def get_r_line(): - return Line( - circle.get_center(), - circle.point_at_angle(get_theta()) - ) - r_line = always_redraw(get_r_line) - - def get_arc(radius=None, **kwargs): - if radius is None: - alpha = inverse_interpolate(0, 20 * DEGREES, get_theta()) - radius = interpolate(2, 1, alpha) - return Arc( - radius=radius, - start_angle=0, - angle=get_theta(), - arc_center=circle.get_center(), - **kwargs - ) - arc = always_redraw(get_arc) - self.circle_arc = always_redraw( - lambda: get_arc(radius=circle.radius, color=RED) - ) - - def get_theta_label(): - label = TexMobject("\\theta") - label.set_height(min(arc.get_height(), 0.3)) - label.set_color(RED) - center = circle.get_center() - vect = arc.point_from_proportion(0.5) - center - vect = (get_norm(vect) + 2 * SMALL_BUFF) * normalize(vect) - label.move_to(center + vect) - return label - theta_label = always_redraw(get_theta_label) - - def get_height_line(): - p2 = circle.point_at_angle(get_theta()) - p1 = np.array(p2) - p1[1] = circle.get_center()[1] - return Line( - p1, p2, - stroke_color=YELLOW, - stroke_width=3, - ) - self.height_line = always_redraw(get_height_line) - - def get_width_line(): - p2 = circle.get_center() - p1 = circle.point_at_angle(get_theta()) - p1[1] = p2[1] - return Line( - p1, p2, - stroke_color=PINK, - stroke_width=3, - ) - self.width_line = always_redraw(get_width_line) - - def get_h_label(): - label = TexMobject("h") - height_line = self.height_line - label.match_color(height_line) - label.set_height(min(height_line.get_height(), 0.3)) - label.set_stroke(BLACK, 3, background=True) - label.next_to(height_line, RIGHT, SMALL_BUFF) - return label - self.h_label = always_redraw(get_h_label) - - def get_w_label(): - label = TexMobject("w") - width_line = self.width_line - label.match_color(width_line) - label.next_to(width_line, DOWN, SMALL_BUFF) - return label - self.w_label = always_redraw(get_w_label) - - self.add(r_line, theta_label, arc, self.radius_line) - self.play( - FadeInFromDown(tan_eq), - theta_tracker.set_value, 20 * DEGREES, - ) - self.wait() - - self.tan_eq = tan_eq - self.theta_tracker = theta_tracker - - def show_fraction(self): - height_line = self.height_line - width_line = self.width_line - h_label = self.h_label - w_label = self.w_label - tan_eq = self.tan_eq - - rhs = TexMobject( - "{\\text{height}", "\\over", "\\text{width}}" - ) - rhs.next_to(tan_eq, RIGHT) - rhs.get_part_by_tex("height").match_color(height_line) - rhs.get_part_by_tex("width").match_color(width_line) - - for mob in [height_line, width_line, h_label, w_label]: - mob.update() - - self.play( - ShowCreation(height_line.copy().clear_updaters(), remover=True), - FadeInFrom(h_label.copy().clear_updaters(), RIGHT, remover=True), - Write(rhs[:2]) - ) - self.add(height_line, h_label) - self.play( - ShowCreation(width_line.copy().clear_updaters(), remover=True), - FadeInFrom(w_label.copy().clear_updaters(), UP, remover=True), - self.r_label.fade, 1, - Write(rhs[2]) - ) - self.add(width_line, w_label) - self.wait() - - self.rhs = rhs - - def show_fraction_approximation(self): - theta_tracker = self.theta_tracker - approx_rhs = TexMobject( - "\\approx", "{\\theta", "\\over", "1}", - ) - height, over1, width = self.rhs - approx, theta, over2, one = approx_rhs - approx_rhs.set_color_by_tex("\\theta", RED) - approx_rhs.next_to(self.rhs, RIGHT, MED_SMALL_BUFF) - - self.play(theta_tracker.set_value, 5 * DEGREES) - self.play(Write(VGroup(approx, over2))) - self.wait() - self.play(Indicate(width)) - self.play(TransformFromCopy(width, one)) - self.wait() - self.play(Indicate(height)) - self.play(TransformFromCopy(height, theta)) - self.wait() - - -class TangentTaylorSeries(TeacherStudentsScene): - def construct(self): - series = TexMobject( - "\\tan", "(", "\\theta", ")", "=", "\\theta", "+", - "\\frac{1}{3}", "\\theta", "^3", "+", - "\\frac{2}{15}", "\\theta", "^5", "+", "\\cdots", - tex_to_color_map={"\\theta": YELLOW}, - ) - series.move_to(2 * UP) - series.move_to(self.hold_up_spot, DOWN) - series_error = series[7:] - series_error_rect = SurroundingRectangle(series_error) - - example = TexMobject( - "\\tan", "\\left(", "\\frac{1}{100}", "\\right)", - "=", "\\frac{1}{100}", "+", - "\\frac{1}{3}", "\\left(", - "\\frac{1}{1{,}000{,}000}", - "\\right)", "+", - "\\frac{2}{15}", "\\left(", - "\\frac{1}{10{,}000{,}000{,}000}", - "\\right)", "+", "\\cdots", - ) - example.set_color_by_tex("\\frac{1}{1", BLUE) - example.set_width(FRAME_WIDTH - 1) - example.next_to(self.students, UP, buff=2) - example.shift_onto_screen() - error = example[7:] - error_rect = SurroundingRectangle(error) - error_rect.set_color(RED) - error_decimal = DecimalNumber( - np.tan(0.01) - 0.01, - num_decimal_places=15, - ) - error_decimal.next_to(error_rect, DOWN) - approx = TexMobject("\\approx") - approx.next_to(error_decimal, LEFT) - error_decimal.add(approx) - error_decimal.match_color(error_rect) - - self.play( - FadeInFromDown(series), - self.teacher.change, "raise_right_hand", - ) - self.play( - ShowCreation(series_error_rect), - self.get_student_changes(*3 * ["pondering"]) - ) - self.play(FadeOut(series_error_rect)) - self.play( - series.center, series.to_edge, UP, - ) - self.look_at(series) - self.play( - TransformFromCopy(series[:8], example[:8]), - TransformFromCopy(series[8], example[9]), - TransformFromCopy(series[10:12], example[11:13]), - TransformFromCopy(series[12], example[14]), - TransformFromCopy(series[14:], example[16:]), - *map(GrowFromCenter, [example[i] for i in (8, 10, 13, 15)]) - ) - self.change_student_modes("happy", "confused", "sad") - self.play(ShowCreation(error_rect)) - self.play(ShowIncreasingSubsets(error_decimal)) - self.change_all_student_modes("hooray") - self.wait(3) - - -class AnalyzeCircleGeometry1e4(AnalyzeCircleGeometry): - CONFIG = { - "mass_ratio": 10000, - } - - -class SumUpWrapper(Scene): - def construct(self): - title = TextMobject("To sum up:") - title.scale(1.5) - title.to_edge(UP) - screen_rect = ScreenRectangle(height=6) - screen_rect.set_fill(BLACK, 1) - screen_rect.next_to(title, DOWN) - self.add(FullScreenFadeRectangle( - fill_color=DARK_GREY, - fill_opacity=0.5 - )) - self.play( - FadeInFromDown(title), - FadeIn(screen_rect), - ) - self.wait() - - -class ConservationLawSummary(Scene): - def construct(self): - energy_eq = TexMobject( - "\\frac{1}{2}", "m_1", "(", "v_1", ")", "^2", "+", - "\\frac{1}{2}", "m_2", "(", "v_2", ")", "^2", "=", - "\\text{const.}", - ) - energy_word = TextMobject("Energy") - energy_word.scale(2) - circle = Circle(color=YELLOW, radius=2) - energy_group = VGroup(energy_word, energy_eq, circle) - momentum_eq = TexMobject( - "m_1", "v_1", "+", "m_2", "v_2", "=", - "\\text{const.}", - ) - momentum_word = TextMobject("Momentum") - momentum_word.scale(2) - line = Line(ORIGIN, RIGHT + np.sqrt(10) * DOWN) - line.set_color(GREEN) - momentum_group = VGroup(momentum_word, momentum_eq, line) - - equations = VGroup(energy_eq, momentum_eq) - words = VGroup(energy_word, momentum_word) - - for equation in equations: - equation.set_color_by_tex("m_", BLUE) - equation.set_color_by_tex("v_", RED) - - words.arrange( - DOWN, buff=3, - ) - words.to_edge(LEFT, buff=1.5) - - for group in energy_group, momentum_group: - arrow = Arrow( - LEFT, 2 * RIGHT, - rectangular_stem_width=0.1, - tip_length=0.5, - color=WHITE - ) - arrow.next_to(group[0], RIGHT) - group[1].next_to(group[0], DOWN) - group[2].next_to(arrow, RIGHT) - group[2].set_stroke(width=6) - group.add(arrow) - # line.scale(4, about_edge=DR) - red_energy_word = energy_word.copy() - red_energy_word.set_fill(opacity=0) - red_energy_word.set_stroke(RED, 2) - - self.add(energy_group, momentum_group) - self.wait() - self.play( - LaggedStartMap( - ShowCreationThenDestruction, - red_energy_word - ), - ) - for color in [RED, BLUE, PINK, YELLOW]: - self.play(ShowCreation( - circle.copy().set_color(color), - )) - - -class FinalCommentsOnPhaseSpace(Scene): - def construct(self): - self.add_title() - self.show_related_fields() - self.state_to_point() - self.puzzle_as_remnant() - - def add_title(self): - title = self.title = TextMobject("Phase space") - title.scale(2) - title.to_edge(UP) - title.set_color(YELLOW) - - self.play(Write(title)) - - def show_related_fields(self): - title = self.title - - images = Group( - ImageMobject("ClacksThumbnail"), - ImageMobject("PictoralODE"), - # ImageMobject("DoublePendulumStart"), - ImageMobject("MobiusStrip"), - ) - colors = [BLUE_D, GREY_BROWN, BLUE_C] - for image, color in zip(images, colors): - image.set_height(2.5) - image.add(SurroundingRectangle( - image, - color=color, - stroke_width=5, - buff=0, - )) - images.arrange(RIGHT) - images.move_to(DOWN) - - arrows = VGroup(*[ - Arrow( - title.get_bottom(), image.get_top(), - color=WHITE, - ) - for image in images - ]) - - for image, arrow in zip(images, arrows): - self.play( - GrowArrow(arrow), - GrowFromPoint(image, title.get_bottom()), - ) - self.wait() - self.wait() - - self.to_fade = Group(images, arrows) - - def state_to_point(self): - state = TextMobject("State") - arrow = Arrow( - 2 * LEFT, 2 * RIGHT, - color=WHITE, - rectangular_stem_width=0.1, - tip_length=0.5 - ) - point = TextMobject("Point") - dynamics = TextMobject("Dynamics") - geometry = TextMobject("Geometry") - words = VGroup(state, point, dynamics, geometry) - for word in words: - word.scale(2) - - group = VGroup(state, arrow, point) - group.arrange(RIGHT, buff=MED_LARGE_BUFF) - group.move_to(2.5 * DOWN) - - dynamics.move_to(state, RIGHT) - geometry.move_to(point, LEFT) - - self.play( - FadeOutAndShift(self.to_fade, UP), - FadeInFrom(state, UP) - ) - self.play( - GrowArrow(arrow), - FadeInFrom(point, LEFT) - ) - self.wait(2) - for w1, w2 in [(state, dynamics), (point, geometry)]: - self.play( - FadeOutAndShift(w1, UP), - FadeInFrom(w2, DOWN), - ) - self.wait() - self.wait() - - def puzzle_as_remnant(self): - pass - - -class AltShowTwoPopulations(ShowTwoPopulations): - CONFIG = { - "count_word_scale_val": 2, - } - - -class SimpleTeacherHolding(TeacherStudentsScene): - def construct(self): - self.play(self.teacher.change, "raise_right_hand") - self.change_all_student_modes("pondering") - self.wait(3) - - -class EndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "Juan Benet", - "Vassili Philippov", - "Burt Humburg", - "Matt Russell", - "soekul", - "Richard Barthel", - "Nathan Jessurun", - "Ali Yahya", - "dave nicponski", - "Yu Jun", - "Kaustuv DeBiswas", - "Yana Chernobilsky", - "Lukas Biewald", - "Arthur Zey", - "Roy Larson", - "Joseph Kelly", - "Peter Mcinerney", - "Scott Walter, Ph.D.", - "Magnus Lysfjord", - "Evan Phillips", - "Graham", - "Mauricio Collares", - "Quantopian", - "Jordan Scales", - "Lukas -krtek.net- Novy", - "John Shaughnessy", - "Joseph John Cox", - "Ryan Atallah", - "Britt Selvitelle", - "Jonathan Wilson", - "Randy C. Will", - "Magnus Dahlström", - "David Gow", - "J", - "Luc Ritchie", - "Rish Kundalia", - "Bob Sanderson", - "Mathew Bramson", - "Mustafa Mahdi", - "Robert Teed", - "Cooper Jones", - "Jeff Linse", - "John Haley", - "Boris Veselinovich", - "Andrew Busey", - "Awoo", - "Linh Tran", - "Ripta Pasay", - "David Clark", - "Mathias Jansson", - "Clark Gaebel", - "Bernd Sing", - "Jason Hise", - "Ankalagon", - "Dave B", - "Ted Suzman", - "Chris Connett", - "Eric Younge", - "1stViewMaths", - "Jacob Magnuson", - "Jonathan Eppele", - "Delton Ding", - "James Hughes", - "Stevie Metke", - "Yaw Etse", - "John Griffith", - "Magister Mugit", - "Ludwig Schubert", - "Giovanni Filippi", - "Matt Langford", - "Matt Roveto", - "Jameel Syed", - "Richard Burgmann", - "Solara570", - "Alexis Olson", - "Jeff Straathof", - "John V Wertheim", - "Sindre Reino Trosterud", - "Song Gao", - "Peter Ehrnstrom", - "Valeriy Skobelev", - "Art Ianuzzi", - "Michael Faust", - "Omar Zrien", - "Adrian Robinson", - "Federico Lebron", - "Kai-Siang Ang", - "Michael Hardel", - "Nero Li", - "Ryan Williams", - "Charles Southerland", - "Devarsh Desai", - "Hal Hildebrand", - "Jan Pijpers", - "L0j1k", - "Mark B Bahu", - "Márton Vaitkus", - "Richard Comish", - "Zach Cardwell", - "Brian Staroselsky", - "Matthew Cocke", - "Christian Kaiser", - "Danger Dai", - "Dave Kester", - "eaglle", - "Florian Chudigiewitsch", - "Roobie", - "Xavier Bernard", - "YinYangBalance.Asia", - "Eryq Ouithaqueue", - "Kanan Gill", - "j eduardo perez", - "Antonio Juarez", - "Owen Campbell-Moore", - ], - } - - -class SolutionThumbnail(Thumbnail): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "label_text": "$100^{d}$ kg", - }, - "collect_clack_data": False, - }, - } - - def add_text(self): - word = TextMobject("Solution") - question = TextMobject("How many collisions?") - word.set_width(7) - question.match_width(word) - question.next_to(word, UP) - group = VGroup(word, question) - group.to_edge(UP, buff=MED_LARGE_BUFF) - word.set_color(RED) - question.set_color(YELLOW) - group.set_stroke(RED, 2, background=True) - self.add(group) diff --git a/from_3b1b/old/clacks/solution2/block_collision_scenes.py b/from_3b1b/old/clacks/solution2/block_collision_scenes.py deleted file mode 100644 index 824b7943..00000000 --- a/from_3b1b/old/clacks/solution2/block_collision_scenes.py +++ /dev/null @@ -1,76 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.clacks.question import BlocksAndWallExample - - -class PreviousTwoVideos(BlocksAndWallExample): - CONFIG = { - "sliding_blocks_config": { - "block1_config": { - "mass": 1e2, - "velocity": -2, - "width": 4, - "distance": 8, - }, - "block2_config": { - "width": 4, - "distance": 3, - }, - }, - "floor_y": -3, - "wait_time": 15, - } - - def setup(self): - super().setup() - blocks = self.blocks - videos = Group( - ImageMobject("ClacksSolution1Thumbnail"), - ImageMobject("ClacksQuestionThumbnail"), - ) - for n, video, block in zip([2, 1], videos, blocks): - block.fade(1) - video.add(SurroundingRectangle( - video, buff=0, - color=BLUE, - stroke_width=3, - )) - video.replace(block) - - title = TextMobject("Part {}".format(n)) - title.scale(1.5) - title.next_to(video, UP, MED_SMALL_BUFF) - video.add(title) - - def update_videos(videos): - for video, block in zip(videos, blocks): - video.move_to(block, DOWN) - video.shift(0.04 * UP) - - videos.add_updater(update_videos) - self.add(videos) - if self.show_flash_animations: - self.add(self.clack_flashes.mobject) - self.videos = videos - - -class IntroducePreviousTwoVideos(PreviousTwoVideos): - CONFIG = { - "show_flash_animations": False, - "include_sound": False, - } - - def construct(self): - blocks = self.blocks - videos = self.videos - - self.remove(blocks) - videos.clear_updaters() - self.remove(videos) - - self.play(FadeInFromLarge(videos[1])) - self.play(TransformFromCopy( - videos[0].copy().fade(1).shift(2 * RIGHT), - videos[0], - rate_func=lambda t: rush_into(t, 3), - )) - # self.wait() diff --git a/from_3b1b/old/clacks/solution2/mirror_scenes.py b/from_3b1b/old/clacks/solution2/mirror_scenes.py deleted file mode 100644 index 516116c8..00000000 --- a/from_3b1b/old/clacks/solution2/mirror_scenes.py +++ /dev/null @@ -1,1021 +0,0 @@ -from manimlib.imports import * - - -class MirrorScene(Scene): - CONFIG = { - "center": DOWN + 3 * LEFT, - "line_length": FRAME_WIDTH, - "start_theta": np.arctan(0.25), - "start_y_offset": 0.5, - "start_x_offset": 8, - "arc_config": { - "radius": 1, - "stroke_color": WHITE, - "stroke_width": 2, - }, - "trajectory_point_spacing": 0.1, - "trajectory_style": { - "stroke_color": YELLOW, - "stroke_width": 2, - }, - "ghost_lines_style": { - "stroke_color": WHITE, - "stroke_width": 1, - "stroke_opacity": 0.5, - }, - # "reflect_sound": "ping", - "reflect_sound": "pen_click", - } - - def setup(self): - self.theta_tracker = ValueTracker(self.start_theta) - self.start_y_offset_tracker = ValueTracker(self.start_y_offset) - self.start_x_offset_tracker = ValueTracker(self.start_x_offset) - self.center_tracker = VectorizedPoint(self.center) - self.beam_point = VectorizedPoint(np.array([ - self.get_start_x_offset(), - self.get_start_y_offset(), - 0 - ])) - self.ghost_beam_point = self.beam_point.copy() - self.is_sound_allowed = False - - self.mirrors = self.get_mirrors() - self.arc = self.get_arc() - self.theta_symbol = self.get_theta_symbol() - self.trajectory = self.get_trajectory() - self.ghost_trajectory = self.get_ghost_trajectory() - self.theta_display = self.get_theta_display() - self.count_display_word = self.get_count_display_word() - self.count_display_number = self.get_count_display_number() - self.last_count = self.get_count() - - # Add some of them - self.add( - self.mirrors, - self.arc, - self.theta_symbol, - self.theta_display, - self.count_display_word, - self.count_display_number, - ) - - def get_center(self): - return self.center_tracker.get_location() - - def get_theta(self): - return self.theta_tracker.get_value() - - def get_start_y_offset(self): - return self.start_y_offset_tracker.get_value() - - def get_start_x_offset(self): - return self.start_x_offset_tracker.get_value() - - def get_mirror(self): - mirror = VGroup( - Line(ORIGIN, 2 * RIGHT), - Line(ORIGIN, 2 * RIGHT), - Line(ORIGIN, (self.line_length - 4) * RIGHT), - ) - mirror.arrange(RIGHT, buff=0) - mirror.set_stroke(width=5) - mirror[0::2].set_stroke((WHITE, GREY)) - mirror[1::2].set_stroke((GREY, WHITE)) - return mirror - - def get_mirrors(self): - mirrors = VGroup(self.get_mirror(), self.get_mirror()) - - def update_mirrors(mirrors): - m1, m2 = mirrors - center = self.get_center() - theta = self.get_theta() - m1.move_to(center, DL) - m2.become(m1) - m2.rotate(theta, about_point=center) - - mirrors.add_updater(update_mirrors) - return mirrors - - def get_arc(self, radius=0.5): - return always_redraw(lambda: Arc( - start_angle=0, - angle=self.get_theta(), - arc_center=self.get_center(), - **self.arc_config, - )) - - def get_theta_symbol(self, arc=None, buff=0.15): - if arc is None: - arc = self.arc - symbol = TexMobject("\\theta") - - def update_symbol(symbol): - midpoint = arc.point_from_proportion(0.5) - center = arc.arc_center - vect = (midpoint - center) - max_height = 0.8 * arc.get_height() - if symbol.get_height() > max_height: - symbol.set_height(max_height) - symbol.move_to( - center + vect + buff * normalize(vect) - ) - symbol.add_updater(update_symbol) - return symbol - - def get_ghost_collision_points(self): - x = self.get_start_x_offset() - y = self.get_start_y_offset() - theta = self.get_theta() - - points = [np.array([x, y, 0])] - points += [ - np.array([x, y, 0]) - for k in range(1, int(PI / theta) + 1) - for x in [y / np.tan(k * theta)] - if abs(x) < FRAME_WIDTH - ] - points.append(points[-1] + x * LEFT) - points = np.array(points) - points += self.get_center() - return points - - def get_collision_points(self, ghost_points=None): - if ghost_points is None: - ghost_points = self.get_ghost_collision_points() - theta = self.get_theta() - center = self.get_center() - points = [] - for ghost_point in ghost_points: - vect = ghost_point - center - angle = angle_of_vector(vect) - k = int(angle / theta) - if k % 2 == 0: - vect = rotate_vector(vect, -k * theta) - else: - vect = rotate_vector(vect, -(k + 1) * theta) - vect[1] = abs(vect[1]) - points.append(center + vect) - return points - - def get_trajectory(self, collision_points=None): - if collision_points is None: - collision_points = self.get_collision_points() - points = [] - spacing = self.trajectory_point_spacing - for p0, p1 in zip(collision_points, collision_points[1:]): - n_intervals = max(1, int(get_norm(p1 - p0) / spacing)) - for alpha in np.linspace(0, 1, n_intervals + 1): - points.append(interpolate(p0, p1, alpha)) - trajectory = VMobject() - trajectory.set_points_as_corners(points) - trajectory.set_style(**self.trajectory_style) - return trajectory - - def get_ghost_trajectory(self): - return self.get_trajectory(self.get_ghost_collision_points()) - - def get_collision_point_counts(self, collision_points=None): - if collision_points is None: - collision_points = self.get_collision_points()[1:-1] - result = VGroup() - for n, point in enumerate(collision_points): - count = Integer(n + 1) - count.set_height(0.25) - vect = UP if n % 2 == 0 else DOWN - count.next_to(point, vect, SMALL_BUFF) - result.add(count) - return result - - def get_collision_count_anim(self, collision_point_counts=None): - if collision_point_counts is None: - collision_point_counts = self.get_collision_point_counts() - group = VGroup() - - def update(group): - count = self.get_count() - if count == 0: - group.submobjects = [] - elif count < len(collision_point_counts) + 1: - group.submobjects = [ - collision_point_counts[count - 1] - ] - - return UpdateFromFunc(group, update, remover=True) - - def get_ghost_lines(self): - line = self.mirrors[0] - center = self.get_center() - theta = self.get_theta() - lines = VGroup() - for k in range(1, int(PI / theta) + 2): - new_line = line.copy() - new_line.rotate(k * theta, about_point=center) - lines.add(new_line) - lines.set_style(**self.ghost_lines_style) - return lines - - # Displays - def get_theta_display(self): - lhs = TexMobject("\\theta = ") - radians = DecimalNumber() - radians.add_updater( - lambda m: m.set_value(self.get_theta()) - ) - radians_word = TextMobject("radians") - radians_word.next_to( - radians, RIGHT, aligned_edge=DOWN - ) - equals = TexMobject("=") - degrees = Integer(0, unit="^\\circ") - degrees.add_updater( - lambda m: m.set_value( - int(np.round(self.get_theta() / DEGREES)) - ) - ) - group = VGroup(lhs, radians, radians_word, equals, degrees) - group.arrange(RIGHT, aligned_edge=DOWN) - equals.align_to(lhs[-1], DOWN) - group.to_corner(UL) - return group - - def get_count_display_word(self): - result = TextMobject("\\# Bounces: ") - result.to_corner(UL) - result.shift(DOWN) - result.set_color(YELLOW) - return result - - def get_count_display_number(self, count_display_word=None, ghost_beam_point=None): - if count_display_word is None: - count_display_word = self.count_display_word - result = Integer() - result.next_to( - count_display_word[-1], RIGHT, - aligned_edge=DOWN, - ) - result.set_color(YELLOW) - result.add_updater( - lambda m: m.set_value(self.get_count()) - ) - return result - - def get_count(self, ghost_beam_point=None): - if ghost_beam_point is None: - ghost_beam_point = self.ghost_beam_point.get_location() - angle = angle_of_vector( - ghost_beam_point - self.get_center() - ) - return int(angle / self.get_theta()) - - # Sounds - def allow_sound(self): - self.is_sound_allowed = True - - def disallow_sound(self): - self.is_sound_allowed = False - - def update_mobjects(self, dt): - super().update_mobjects(dt) - if self.get_count() != self.last_count: - self.last_count = self.get_count() - if self.is_sound_allowed: - self.add_sound( - self.reflect_sound, - gain=-20, - ) - - # Bouncing animations - def show_bouncing(self, run_time=5): - trajectory = self.trajectory - ghost_trajectory = self.get_ghost_trajectory() - - beam_anims = self.get_shooting_beam_anims( - trajectory, ghost_trajectory - ) - count_anim = self.get_collision_count_anim() - - self.allow_sound() - self.play(count_anim, *beam_anims, run_time=run_time) - self.disallow_sound() - - def get_special_flash(self, mobject, stroke_width, time_width, rate_func=linear, **kwargs): - kwargs["rate_func"] = rate_func - mob_copy = mobject.copy() - mob_copy.set_stroke(width=stroke_width) - mob_copy.time_width = time_width - return UpdateFromAlphaFunc( - mob_copy, - lambda m, a: m.pointwise_become_partial( - mobject, - max(a - (1 - a) * m.time_width, 0), - a, - ), - **kwargs - ) - - def get_shooting_beam_anims(self, - trajectory, - ghost_trajectory=None, - update_beam_point=True, - num_flashes=20, - min_time_width=0.01, - max_time_width=0.5, - min_stroke_width=0.01, - max_stroke_width=6, - fade_trajectory=True, - faded_trajectory_width=0.25, - faded_trajectory_time_exp=0.2, - ): - # Most flashes - result = [ - self.get_special_flash(trajectory, stroke_width, time_width) - for stroke_width, time_width in zip( - np.linspace(max_stroke_width, min_stroke_width, num_flashes), - np.linspace(min_time_width, max_time_width, num_flashes), - ) - ] - - # Make sure beam point is updated - if update_beam_point: - smallest_flash = result[0] - result.append( - UpdateFromFunc( - self.beam_point, - lambda m: m.move_to(smallest_flash.mobject.points[-1]) - ) - ) - - # Make sure ghost beam point is updated - if ghost_trajectory: - ghost_flash = self.get_special_flash( - ghost_trajectory, 0, min_time_width, - ) - ghost_beam_point_update = UpdateFromFunc( - self.ghost_beam_point, - lambda m: m.move_to(ghost_flash.mobject.points[-1]) - ) - result += [ - ghost_flash, - ghost_beam_point_update, - ] - - # Fade trajectory - if fade_trajectory: - ftte = faded_trajectory_time_exp - result.append( - ApplyMethod( - trajectory.set_stroke, - {"width": faded_trajectory_width}, - rate_func=lambda t: there_and_back(t)**ftte - ), - ) - return result - - -class ShowTrajectoryWithChangingTheta(MirrorScene): - def construct(self): - trajectory = self.trajectory - self.add(trajectory) - angles = [30 * DEGREES, 10 * DEGREES] - ys = [1, 1] - self.show_bouncing() - for angle, y in zip(angles, ys): - rect = SurroundingRectangle(self.theta_display) - self.play( - self.theta_tracker.set_value, angle, - self.start_y_offset_tracker.set_value, y, - FadeIn(rect, rate_func=there_and_back, remover=True), - UpdateFromFunc( - trajectory, - lambda m: m.become(self.get_trajectory()) - ), - run_time=2 - ) - self.show_bouncing() - self.wait(2) - - -class ReflectWorldThroughMirrorNew(MirrorScene): - CONFIG = { - "start_y_offset": 1.25, - "center": DOWN, - "randy_height": 1, - "partial_trajectory_values": [ - 0, 0.22, 0.28, 0.315, 1, - ], - } - - def construct(self): - self.add_randy() - self.shift_displays() - self.add_ghost_beam_point() - self.up_through_first_bounce() - self.create_reflected_worlds() - self.create_reflected_trajectories() - self.first_reflection() - self.next_reflection(2) - self.next_reflection(3) - self.unfold_all_reflected_worlds() - self.show_completed_beam() - self.blink_all_randys() - self.add_randy_updates() - self.show_all_trajectories() - self.focus_on_two_important_trajectories() - - def add_randy(self): - randy = self.randy = Randolph() - randy.flip() - randy.set_height(self.randy_height) - randy.change("pondering") - randy.align_to(self.mirrors, DOWN) - randy.shift(0.01 * UP) - randy.to_edge(RIGHT, buff=1) - randy.tracked_mobject = self.trajectory - randy.add_updater( - lambda m: m.look_at( - m.tracked_mobject.points[-1] - ) - ) - self.add(randy) - - def shift_displays(self): - VGroup( - self.theta_display, - self.count_display_word, - self.count_display_number, - ).to_edge(DOWN) - - def add_ghost_beam_point(self): - self.ghost_beam_point.add_updater( - lambda m: m.move_to( - self.ghost_trajectory.points[-1] - ) - ) - self.add(self.ghost_beam_point) - - def up_through_first_bounce(self): - self.play(*self.get_both_partial_trajectory_anims( - *self.partial_trajectory_values[:2] - )) - self.wait() - - def create_reflected_worlds(self): - mirrors = self.mirrors - triangle = Polygon(*[ - mirrors.get_corner(corner) - for corner in (DR, DL, UR) - ]) - triangle.set_stroke(width=0) - triangle.set_fill(BLUE_E, opacity=0) - world = self.world = VGroup( - triangle, - mirrors, - self.arc, - self.theta_symbol, - self.randy, - ) - reflected_worlds = self.get_reflected_worlds(world) - self.reflected_worlds = reflected_worlds - # Alternating triangle opacities - for rw in reflected_worlds[::2]: - rw[0].set_fill(opacity=0.25) - - def create_reflected_trajectories(self): - self.reflected_trajectories = always_redraw( - lambda: self.get_reflected_worlds(self.trajectory) - ) - - def first_reflection(self): - reflected_trajectory = self.reflected_trajectories[0] - reflected_world = self.reflected_worlds[0] - world = self.world - trajectory = self.trajectory - ghost_trajectory = self.ghost_trajectory - - self.play( - TransformFromCopy(world, reflected_world), - TransformFromCopy(trajectory, reflected_trajectory), - run_time=2 - ) - beam_anims = self.get_shooting_beam_anims( - ghost_trajectory, - fade_trajectory=False, - ) - self.play( - *[ - ApplyMethod(m.set_stroke, GREY, 1) - for m in (trajectory, reflected_trajectory) - ] + beam_anims, - run_time=2 - ) - for x in range(2): - self.play(*beam_anims, run_time=2) - - ghost_trajectory.set_stroke(YELLOW, 4) - self.bring_to_front(ghost_trajectory) - self.play(FadeIn(ghost_trajectory)) - self.wait() - - def next_reflection(self, index=2): - i = index - self.play( - *self.get_both_partial_trajectory_anims( - *self.partial_trajectory_values[i - 1:i + 1] - ), - UpdateFromFunc( - VMobject(), # Null - lambda m: self.reflected_trajectories.update(), - remover=True, - ), - ) - - anims = [ - TransformFromCopy(*reflections[i - 2:i]) - for reflections in [ - self.reflected_worlds, - self.reflected_trajectories - ] - ] - self.play(*anims, run_time=2) - self.add(self.ghost_trajectory) - self.wait() - - def unfold_all_reflected_worlds(self): - worlds = self.reflected_worlds - trajectories = self.reflected_trajectories - - pairs = [ - (VGroup(w1, t1), VGroup(w2, t2)) - for w1, w2, t1, t2 in zip( - worlds[2:], worlds[3:], - trajectories[2:], trajectories[3:], - ) - ] - - new_worlds = VGroup() # Brought to you by Dvorak - for m1, m2 in pairs: - m2.pre_world = m1.copy() - new_worlds.add(m2) - for mob in new_worlds: - mob.save_state() - mob.become(mob.pre_world) - mob.fade(1) - - self.play(LaggedStartMap( - Restore, new_worlds, - lag_ratio=0.4, - run_time=3 - )) - - def show_completed_beam(self): - self.add(self.reflected_trajectories) - self.add(self.ghost_trajectory) - self.play(*self.get_both_partial_trajectory_anims( - *self.partial_trajectory_values[-2:], - run_time=7 - )) - - def blink_all_randys(self): - randys = self.randys = VGroup(self.randy) - randys.add(*[rw[-1] for rw in self.reflected_worlds]) - self.play(LaggedStartMap(Blink, randys)) - - def add_randy_updates(self): - # Makes it run slower, but it's fun! - reflected_randys = VGroup(*[ - rw[-1] for rw in self.reflected_worlds - ]) - reflected_randys.add_updater( - lambda m: m.become( - self.get_reflected_worlds(self.randy) - ) - ) - self.add(reflected_randys) - - def show_all_trajectories(self): - ghost_trajectory = self.ghost_trajectory - reflected_trajectories = self.reflected_trajectories - trajectory = self.trajectory - reflected_trajectories.suspend_updating() - trajectories = VGroup(trajectory, *reflected_trajectories) - - all_mirrors = VGroup(*[ - world[1] - for world in it.chain([self.world], self.reflected_worlds) - ]) - - self.play( - FadeOut(ghost_trajectory), - trajectories.set_stroke, YELLOW, 0.5, - all_mirrors.set_stroke, {"width": 1}, - ) - - # All trajectory light beams - flash_groups = [ - self.get_shooting_beam_anims( - mob, fade_trajectory=False, - ) - for mob in trajectories - ] - all_flashes = list(it.chain(*flash_groups)) - - # Have all the pi creature eyes follows - self.randy.tracked_mobject = all_flashes[0].mobject - - # Highlight the illustory straight beam - red_ghost = self.ghost_trajectory.copy() - red_ghost.set_color(RED) - red_ghost_beam = self.get_shooting_beam_anims( - red_ghost, fade_trajectory=False, - ) - - num_repeats = 3 - for x in range(num_repeats): - anims = list(all_flashes) - if x == num_repeats - 1: - anims += list(red_ghost_beam) - self.randy.tracked_mobject = red_ghost_beam[0].mobject - for flash in all_flashes: - if hasattr(flash.mobject, "time_width"): - flash.mobject.set_stroke( - width=0.25 * flash.mobject.get_stroke_width() - ) - flash.mobject.time_width *= 0.25 - self.play(*anims, run_time=3) - - def focus_on_two_important_trajectories(self): - self.ghost_trajectory.set_stroke(YELLOW, 1) - self.play( - FadeOut(self.reflected_trajectories), - FadeIn(self.ghost_trajectory), - self.trajectory.set_stroke, YELLOW, 1, - ) - self.add_flashing_windows() - t_beam_anims = self.get_shooting_beam_anims(self.trajectory) - gt_beam_anims = self.get_shooting_beam_anims(self.ghost_trajectory) - self.ghost_beam_point.clear_updaters() - self.ghost_beam_point.add_updater( - lambda m: m.move_to( - gt_beam_anims[0].mobject.points[-1] - ) - ) - self.randy.tracked_mobject = t_beam_anims[0].mobject - self.allow_sound() - self.play( - *t_beam_anims, *gt_beam_anims, - run_time=6 - ) - self.add_room_color_updates() - self.play( - *t_beam_anims, *gt_beam_anims, - run_time=6 - ) - self.blink_all_randys() - self.play( - *t_beam_anims, *gt_beam_anims, - run_time=6 - ) - - # Helpers - def get_reflected_worlds(self, world, n_reflections=None): - theta = self.get_theta() - center = self.get_center() - if n_reflections is None: - n_reflections = int(PI / theta) - - result = VGroup() - last_world = world - for n in range(n_reflections): - vect = rotate_vector(RIGHT, (n + 1) * theta) - reflected_world = last_world.copy() - reflected_world.clear_updaters() - reflected_world.rotate( - PI, axis=vect, about_point=center, - ) - last_world = reflected_world - result.add(last_world) - return result - - def get_partial_trajectory_anims(self, trajectory, a, b, **kwargs): - if not hasattr(trajectory, "full_self"): - trajectory.full_self = trajectory.copy() - return UpdateFromAlphaFunc( - trajectory, - lambda m, alpha: m.pointwise_become_partial( - m.full_self, 0, - interpolate(a, b, alpha) - ), - **kwargs - ) - - def get_both_partial_trajectory_anims(self, a, b, run_time=2, **kwargs): - kwargs["run_time"] = run_time - return [ - self.get_partial_trajectory_anims( - mob, a, b, **kwargs - ) - for mob in (self.trajectory, self.ghost_trajectory) - ] - - def add_flashing_windows(self): - theta = self.get_theta() - center = self.get_center() - windows = self.windows = VGroup(*[ - Line( - center, - center + rotate_vector(10 * RIGHT, k * theta), - color=BLUE, - stroke_width=0, - ) - for k in range(0, self.get_count() + 1) - ]) - windows[0].set_stroke(opacity=0) - - # Warning, windows update manager may launch - def update_windows(windows): - windows.set_stroke(width=0) - windows[self.get_count()].set_stroke(width=5) - windows.add_updater(update_windows) - self.add(windows) - - def add_room_color_updates(self): - def update_reflected_worlds(worlds): - for n, world in enumerate(worlds): - worlds[n][0].set_fill( - opacity=(0.25 if (n % 2 == 0) else 0) - ) - index = self.get_count() - 1 - if index < 0: - return - worlds[index][0].set_fill(opacity=0.5) - self.reflected_worlds.add_updater(update_reflected_worlds) - self.add(self.reflected_worlds) - - -class ReflectWorldThroughMirrorThetaPoint2(ReflectWorldThroughMirrorNew): - CONFIG = { - "start_theta": 0.2, - "randy_height": 0.8, - } - - -class ReflectWorldThroughMirrorThetaPoint1(ReflectWorldThroughMirrorNew): - CONFIG = { - "start_theta": 0.1, - "randy_height": 0.5, - "start_y_offset": 0.5, - "arc_config": { - "radius": 0.5, - }, - } - - -class MirrorAndWiresOverlay(MirrorScene): - CONFIG = { - "wire_pixel_points": [ - (355, 574), - (846, 438), - (839, 629), - (845, 288), - (1273, 314), - ], - "max_x_pixel": 1440, - "max_y_pixel": 1440, - } - - def setup(self): - self.last_count = 0 - - def get_count(self): - return 0 - - def get_shooting_beam_anims(self, mobject, **new_kwargs): - kwargs = { - "update_beam_point": False, - "fade_trajectory": False, - "max_stroke_width": 10, - } - kwargs.update(new_kwargs) - return super().get_shooting_beam_anims(mobject, **kwargs) - - def construct(self): - self.add_wires() - self.add_diagram() - - self.introduce_wires() - self.show_illusion() - self.show_angles() - - # self.show_reflecting_beam() - - def add_wires(self): - ul_corner = TOP + LEFT_SIDE - points = self.wire_points = [ - ul_corner + np.array([ - (x / self.max_x_pixel) * FRAME_HEIGHT, - (-y / self.max_y_pixel) * FRAME_HEIGHT, - 0 - ]) - for x, y in self.wire_pixel_points - ] - wires = self.wires = VGroup( - Line(points[0], points[1]), - Line(points[1], points[2]), - Line(points[1], points[3]), - Line(points[1], points[4]), - ) - wires.set_stroke(RED, 4) - self.dl_wire, self.dr_wire, self.ul_wire, self.ur_wire = wires - - self.trajectory = VMobject() - self.trajectory.set_points_as_corners(points[:3]) - self.ghost_trajectory = VMobject() - self.ghost_trajectory.set_points_as_corners([*points[:2], points[4]]) - VGroup(self.trajectory, self.ghost_trajectory).match_style( - self.wires - ) - - def add_diagram(self): - diagram = self.diagram = VGroup() - rect = diagram.rect = Rectangle( - height=4, width=5, - stroke_color=WHITE, - stroke_width=1, - fill_color=BLACK, - fill_opacity=0.9, - ) - rect.to_corner(UR) - diagram.add(rect) - - center = rect.get_center() - - mirror = diagram.mirror = VGroup( - Line(rect.get_left(), center + 1.5 * LEFT), - Line(center + 1.5 * LEFT, rect.get_right()), - ) - mirror.scale(0.8) - mirror[0].set_color((WHITE, GREY)) - mirror[1].set_color((GREY, WHITE)) - diagram.add(mirror) - - def set_as_reflection(m1, m2): - m1.become(m2) - m1.rotate(PI, axis=UP, about_point=center) - - def set_as_mirror_image(m1, m2): - m1.become(m2) - m1.rotate(PI, axis=RIGHT, about_point=center) - - wires = VGroup(*[ - Line(center + np.array([-1, -1.5, 0]), center) - for x in range(4) - ]) - dl_wire, dr_wire, ul_wire, ur_wire = wires - dr_wire.add_updater( - lambda m: set_as_reflection(m, dl_wire) - ) - ul_wire.add_updater( - lambda m: set_as_mirror_image(m, dl_wire) - ) - ur_wire.add_updater( - lambda m: set_as_mirror_image(m, dr_wire) - ) - - diagram.wires = wires - diagram.wires.set_stroke(RED, 2) - diagram.add(diagram.wires) - - def introduce_wires(self): - dl_wire = self.dl_wire - dr_wire = self.dr_wire - - def get_rect(wire): - rect = Rectangle( - width=wire.get_length(), - height=0.25, - color=YELLOW, - ) - rect.rotate(wire.get_angle()) - rect.move_to(wire) - return rect - - for wire in dl_wire, dr_wire: - self.play(ShowCreationThenFadeOut(get_rect(wire))) - self.play(*self.get_shooting_beam_anims(wire)) - self.wait() - - diagram = self.diagram.copy() - diagram.clear_updaters() - self.play( - FadeIn(diagram.rect), - ShowCreation(diagram.mirror), - LaggedStartMap(ShowCreation, diagram.wires), - run_time=1 - ) - self.remove(diagram) - self.add(self.diagram) - - self.wait() - - def show_illusion(self): - g_trajectory = self.ghost_trajectory - d_trajectory = self.d_trajectory = Line( - self.diagram.wires[0].get_start(), - self.diagram.wires[3].get_start(), - ) - d_trajectory.match_style(g_trajectory) - - g_trajectory.points[0] += 0.2 * RIGHT + 0.1 * DOWN - g_trajectory.make_jagged() - for x in range(3): - self.play( - *self.get_shooting_beam_anims(g_trajectory), - *self.get_shooting_beam_anims(d_trajectory), - ) - self.wait() - - def show_angles(self): - dl_wire = self.diagram.wires[0] - dr_wire = self.diagram.wires[1] - center = self.diagram.get_center() - arc_config = { - "radius": 0.5, - "arc_center": center, - } - - def get_dl_arc(): - return Arc( - start_angle=PI, - angle=dl_wire.get_angle(), - **arc_config, - ) - dl_arc = always_redraw(get_dl_arc) - - def get_dr_arc(): - return Arc( - start_angle=0, - angle=dr_wire.get_angle() - PI, - **arc_config, - ) - dr_arc = always_redraw(get_dr_arc) - - incidence = TextMobject("Incidence") - reflection = TextMobject("Reflection") - words = VGroup(incidence, reflection) - words.scale(0.75) - incidence.add_updater( - lambda m: m.next_to(dl_arc, LEFT, SMALL_BUFF) - ) - reflection.add_updater( - lambda m: m.next_to(dr_arc, RIGHT, SMALL_BUFF) - ) - for word in words: - word.set_background_stroke(width=0) - word.add_updater(lambda m: m.shift(SMALL_BUFF * DOWN)) - - self.add(incidence) - self.play( - ShowCreation(dl_arc), - UpdateFromAlphaFunc( - VMobject(), - lambda m, a: incidence.set_fill(opacity=a), - remover=True - ), - ) - self.wait() - self.add(reflection) - self.play( - ShowCreation(dr_arc), - UpdateFromAlphaFunc( - VMobject(), - lambda m, a: reflection.set_fill(opacity=a), - remover=True - ), - ) - self.wait() - - # Change dr wire angle - # dr_wire.suspend_updating() - dr_wire.clear_updaters() - for angle in [20 * DEGREES, -20 * DEGREES]: - self.play( - Rotate( - dr_wire, angle, - about_point=dr_wire.get_end(), - run_time=2, - ), - ) - self.play( - *self.get_shooting_beam_anims(self.ghost_trajectory), - *self.get_shooting_beam_anims(self.d_trajectory), - ) - self.wait() - - def show_reflecting_beam(self): - self.play( - *self.get_shooting_beam_anims(self.trajectory), - *self.get_shooting_beam_anims(self.ghost_trajectory), - ) - self.wait() diff --git a/from_3b1b/old/clacks/solution2/pi_creature_scenes.py b/from_3b1b/old/clacks/solution2/pi_creature_scenes.py deleted file mode 100644 index 52f756ab..00000000 --- a/from_3b1b/old/clacks/solution2/pi_creature_scenes.py +++ /dev/null @@ -1,118 +0,0 @@ -from manimlib.imports import * - - -class OnAnsweringTwice(TeacherStudentsScene): - def construct(self): - question = TextMobject("Why $\\pi$?") - question.move_to(self.screen) - question.to_edge(UP) - other_questions = VGroup( - TextMobject("Frequency of collisions?"), - TextMobject("Efficient simulation?"), - TextMobject("Time until last collision?"), - ) - for mob in other_questions: - mob.move_to(self.hold_up_spot, DOWN) - - self.add(question) - - self.student_says( - "But we already \\\\ solved it", - bubble_kwargs={"direction": LEFT}, - target_mode="raise_left_hand", - added_anims=[self.teacher.change, "thinking"] - ) - self.change_student_modes("sassy", "angry") - self.wait() - self.play( - RemovePiCreatureBubble(self.students[2]), - self.get_student_changes("erm", "erm"), - ApplyMethod( - question.move_to, self.hold_up_spot, DOWN, - path_arc=-90 * DEGREES, - ), - self.teacher.change, "raise_right_hand", - ) - shown_questions = VGroup(question) - for oq in other_questions: - self.play( - shown_questions.shift, 0.85 * UP, - FadeInFromDown(oq), - self.get_student_changes( - *["pondering"] * 3, - look_at_arg=oq - ) - ) - shown_questions.add(oq) - self.wait(3) - - -class AskAboutEqualMassMomentumTransfer(TeacherStudentsScene): - def construct(self): - self.student_says("Why?") - self.change_student_modes("confused", "confused") - self.wait() - self.play( - RemovePiCreatureBubble(self.students[2]), - self.teacher.change, "raise_right_hand" - ) - self.change_all_student_modes("pondering") - self.look_at(self.hold_up_spot + 2 * UP) - self.wait(5) - - -class ComplainAboutRelevanceOfAnalogy(TeacherStudentsScene): - def construct(self): - self.student_says( - "Why would \\\\ you care", - target_mode="maybe" - ) - self.change_student_modes( - "angry", "sassy", "maybe", - added_anims=[self.teacher.change, "guilty"] - ) - self.wait(2) - self.play( - self.teacher.change, "raise_right_hand", - self.get_student_changes( - "pondering", "erm", "pondering", - look_at_arg=self.hold_up_spot, - ), - RemovePiCreatureBubble(self.students[2]) - ) - self.play( - self.students[2].change, "thinking", - self.hold_up_spot + UP, - ) - self.wait(3) - - -class ReplaceOneTrickySceneWithAnother(TeacherStudentsScene): - def construct(self): - self.student_says( - "This replaces one tricky\\\\problem with another", - student_index=1, - target_mode="sassy", - added_anims=[self.teacher.change, "happy"], - ) - self.change_student_modes("erm", "sassy", "angry") - self.wait(4) - self.play( - RemovePiCreatureBubble(self.students[1]), - self.teacher.change, "raise_right_hand", - self.get_student_changes(*3 * ["pondering"]) - ) - self.look_at(self.hold_up_spot + 2 * UP) - self.wait(5) - - -class NowForTheGoodPart(TeacherStudentsScene): - def construct(self): - self.teacher_says( - r"Now for the \\ good part!", - target_mode="hooray", - added_anims=[self.get_student_changes( - "hooray", "surprised", "happy" - )], - ) - self.wait(2) diff --git a/from_3b1b/old/clacks/solution2/position_phase_space.py b/from_3b1b/old/clacks/solution2/position_phase_space.py deleted file mode 100644 index 46f82921..00000000 --- a/from_3b1b/old/clacks/solution2/position_phase_space.py +++ /dev/null @@ -1,2374 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.clacks.question import Block -from from_3b1b.old.clacks.question import Wall -from from_3b1b.old.clacks.question import ClackFlashes - - -class PositionPhaseSpaceScene(Scene): - CONFIG = { - "rescale_coordinates": True, - "wall_x": -6, - "wall_config": { - "height": 1.6, - "tick_spacing": 0.35, - "tick_length": 0.2, - }, - "wall_height": 1.5, - "floor_y": -3.5, - "block1_config": { - "mass": 10, - "distance": 9, - "velocity": -1, - "width": 1.6, - }, - "block2_config": { - "mass": 1, - "distance": 4, - }, - "axes_config": { - "x_min": -0.5, - "x_max": 31, - "y_min": -0.5, - "y_max": 10.5, - "x_axis_config": { - "unit_size": 0.4, - "tick_frequency": 2, - }, - "y_axis_config": { - "unit_size": 0.4, - "tick_frequency": 2, - }, - }, - "axes_center": 5 * LEFT + 0.65 * DOWN, - "ps_dot_config": { - "fill_color": RED, - "background_stroke_width": 1, - "background_stroke_color": BLACK, - "radius": 0.05, - }, - "ps_d2_label_vect": RIGHT, - "ps_x_line_config": { - "color": GREEN, - "stroke_width": 2, - }, - "ps_y_line_config": { - "color": RED, - "stroke_width": 2, - }, - "clack_sound": "clack", - "mirror_line_class": Line, - "mirror_line_style": { - "stroke_color": WHITE, - "stroke_width": 1, - }, - "d1_eq_d2_line_color": MAROON_B, - "d1_eq_d2_tex": "d1 = d2", - "trajectory_style": { - "stroke_color": YELLOW, - "stroke_width": 2, - }, - "ps_velocity_vector_length": 0.75, - "ps_velocity_vector_config": { - "color": PINK, - "rectangular_stem_width": 0.025, - "tip_length": 0.15, - }, - "block_velocity_vector_length_multiple": 2, - "block_velocity_vector_config": { - "color": PINK, - }, - } - - def setup(self): - self.total_sliding_time = 0 - self.all_items = [ - self.get_floor(), - self.get_wall(), - self.get_blocks(), - self.get_axes(), - self.get_phase_space_point(), - self.get_phase_space_x_line(), - self.get_phase_space_y_line(), - self.get_phase_space_dot(), - self.get_phase_space_d1_label(), - self.get_phase_space_d2_label(), - self.get_d1_brace(), - self.get_d2_brace(), - self.get_d1_label(), - self.get_d2_label(), - self.get_d1_eq_d2_line(), - self.get_d1_eq_d2_label(), - self.get_d2_eq_w2_line(), - self.get_d2_eq_w2_label(), - ] - - def get_floor_wall_corner(self): - return self.wall_x * RIGHT + self.floor_y * UP - - def get_mass_ratio(self): - return op.truediv( - self.block1.mass, - self.block2.mass, - ) - - def d1_to_x(self, d1): - if self.rescale_coordinates: - d1 *= np.sqrt(self.block1.mass) - return d1 - - def d2_to_y(self, d2): - if self.rescale_coordinates: - d2 *= np.sqrt(self.block2.mass) - return d2 - - def ds_to_point(self, d1, d2): - return self.axes.coords_to_point( - self.d1_to_x(d1), self.d2_to_y(d2), - ) - - def point_to_ds(self, point): - x, y = self.axes.point_to_coords(point) - if self.rescale_coordinates: - x /= np.sqrt(self.block1.mass) - y /= np.sqrt(self.block2.mass) - return (x, y) - - def get_d1(self): - return self.get_ds()[0] - - def get_d2(self): - return self.get_ds()[1] - - def get_ds(self): - return self.point_to_ds(self.ps_point.get_location()) - - # Relevant for sliding - def tie_blocks_to_ps_point(self): - def update_blocks(blocks): - d1, d2 = self.point_to_ds(self.ps_point.get_location()) - b1, b2 = blocks - corner = self.get_floor_wall_corner() - b1.move_to(corner + d1 * RIGHT, DL) - b2.move_to(corner + d2 * RIGHT, DR) - self.blocks.add_updater(update_blocks) - - def time_to_ds(self, time): - # Deals in its own phase space, different - # from the one displayed - m1 = self.block1.mass - m2 = self.block2.mass - v1 = self.block1.velocity - start_d1 = self.block1_config["distance"] - start_d2 = self.block2_config["distance"] - w2 = self.block2.width - start_d2 += w2 - ps_speed = np.sqrt(m1) * abs(v1) - theta = np.arctan(np.sqrt(m2 / m1)) - - def ds_to_ps_point(d1, d2): - return np.array([ - d1 * np.sqrt(m1), - d2 * np.sqrt(m2), - 0 - ]) - - def ps_point_to_ds(point): - return ( - point[0] / np.sqrt(m1), - point[1] / np.sqrt(m2), - ) - - ps_point = ds_to_ps_point(start_d1, start_d2) - wedge_corner = ds_to_ps_point(w2, w2) - ps_point -= wedge_corner - # Pass into the mirror worlds - ps_point += time * ps_speed * LEFT - # Reflect back to the real world - angle = angle_of_vector(ps_point) - n = int(angle / theta) - if n % 2 == 0: - ps_point = rotate_vector(ps_point, -n * theta) - else: - ps_point = rotate_vector( - ps_point, - -(n + 1) * theta, - ) - ps_point[1] = abs(ps_point[1]) - ps_point += wedge_corner - return ps_point_to_ds(ps_point) - - def get_clack_data(self): - # Copying from time_to_ds. Not great, but - # maybe I'll factor things out properly later. - m1 = self.block1.mass - m2 = self.block2.mass - v1 = self.block1.velocity - w2 = self.block2.get_width() - h2 = self.block2.get_height() - start_d1, start_d2 = self.get_ds() - ps_speed = np.sqrt(m1) * abs(v1) - theta = np.arctan(np.sqrt(m2 / m1)) - - def ds_to_ps_point(d1, d2): - return np.array([ - d1 * np.sqrt(m1), - d2 * np.sqrt(m2), - 0 - ]) - - def ps_point_to_ds(point): - return ( - point[0] / np.sqrt(m1), - point[1] / np.sqrt(m2), - ) - - ps_point = ds_to_ps_point(start_d1, start_d2) - wedge_corner = ds_to_ps_point(w2, w2) - ps_point -= wedge_corner - y = ps_point[1] - - clack_data = [] - for k in range(1, int(PI / theta) + 1): - clack_ps_point = np.array([ - y / np.tan(k * theta), y, 0 - ]) - time = get_norm(ps_point - clack_ps_point) / ps_speed - reflected_point = rotate_vector( - clack_ps_point, - -2 * np.ceil((k - 1) / 2) * theta - ) - d1, d2 = ps_point_to_ds(reflected_point + wedge_corner) - loc1 = self.get_floor_wall_corner() + h2 * UP / 2 + d2 * RIGHT - if k % 2 == 0: - loc1 += w2 * LEFT - loc2 = self.ds_to_point(d1, d2) - clack_data.append((time, loc1, loc2)) - return clack_data - - def tie_ps_point_to_time_tracker(self): - if not hasattr(self, "sliding_time_tracker"): - self.sliding_time_tracker = self.get_time_tracker() - - def update_ps_point(p): - time = self.sliding_time_tracker.get_value() - ds = self.time_to_ds(time) - p.move_to(self.ds_to_point(*ds)) - - self.ps_point.add_updater(update_ps_point) - self.add(self.sliding_time_tracker, self.ps_point) - - def add_clack_flashes(self): - if hasattr(self, "flash_anims"): - self.add(*self.flash_anims) - else: - clack_data = self.get_clack_data() - self.clack_times = [ - time for (time, loc1, loc2) in clack_data - ] - self.block_flashes = ClackFlashes([ - (loc1, time) - for (time, loc1, loc2) in clack_data - ]) - self.ps_flashes = ClackFlashes([ - (loc2, time) - for (time, loc1, loc2) in clack_data - ]) - self.flash_anims = [self.block_flashes, self.ps_flashes] - for anim in self.flash_anims: - anim.get_time = self.sliding_time_tracker.get_value - self.add(*self.flash_anims) - - def get_continually_building_trajectory(self): - trajectory = VMobject() - self.trajectory = trajectory - trajectory.set_style(**self.trajectory_style) - - def get_point(): - return np.array(self.ps_point.get_location()) - - points = [get_point(), get_point()] - trajectory.set_points_as_corners(points) - epsilon = 0.001 - - def update_trajectory(trajectory): - new_point = get_point() - p1, p2 = trajectory.get_anchors()[-2:] - angle = angle_between_vectors( - p2 - p1, - new_point - p2, - ) - if angle > epsilon: - points.append(new_point) - else: - points[-1] = new_point - trajectory.set_points_as_corners(points) - - trajectory.add_updater(update_trajectory) - return trajectory - - def begin_sliding(self, show_trajectory=True): - self.tie_ps_point_to_time_tracker() - self.add_clack_flashes() - if show_trajectory: - if hasattr(self, "trajectory"): - self.trajectory.resume_updating() - else: - self.add(self.get_continually_building_trajectory()) - - def end_sliding(self): - self.update_mobjects(dt=0) - self.ps_point.clear_updaters() - if hasattr(self, "sliding_time_tracker"): - self.remove(self.sliding_time_tracker) - if hasattr(self, "flash_anims"): - self.remove(*self.flash_anims) - if hasattr(self, "trajectory"): - self.trajectory.suspend_updating() - old_total_sliding_time = self.total_sliding_time - new_total_sliding_time = self.sliding_time_tracker.get_value() - self.total_sliding_time = new_total_sliding_time - for time in self.clack_times: - if old_total_sliding_time < time < new_total_sliding_time: - offset = time - new_total_sliding_time - self.add_sound( - "clack", - time_offset=offset, - ) - - def slide(self, time, stop_condition=None): - self.begin_sliding() - self.wait(time, stop_condition) - self.end_sliding() - - def slide_until(self, stop_condition, max_time=60): - self.slide(max_time, stop_condition=stop_condition) - - def get_ps_point_change_anim(self, d1, d2, **added_kwargs): - b1 = self.block1 - ps_speed = np.sqrt(b1.mass) * abs(b1.velocity) - curr_d1, curr_d2 = self.get_ds() - distance = get_norm([curr_d1 - d1, curr_d2 - d2]) - - # Default - kwargs = { - "run_time": (distance / ps_speed), - "rate_func": linear, - } - kwargs.update(added_kwargs) - return ApplyMethod( - self.ps_point.move_to, - self.ds_to_point(d1, d2), - **kwargs - ) - - # Mobject getters - def get_floor(self): - floor = self.floor = Line( - self.wall_x * RIGHT, - FRAME_WIDTH * RIGHT / 2, - stroke_color=WHITE, - stroke_width=3, - ) - floor.move_to(self.get_floor_wall_corner(), LEFT) - return floor - - def get_wall(self): - wall = self.wall = Wall(**self.wall_config) - wall.move_to(self.get_floor_wall_corner(), DR) - return wall - - def get_blocks(self): - blocks = self.blocks = VGroup() - for n in [1, 2]: - config = getattr(self, "block{}_config".format(n)) - block = Block(**config) - block.move_to(self.get_floor_wall_corner(), DL) - block.shift(config["distance"] * RIGHT) - block.label.move_to(block) - block.label.set_fill(BLACK) - block.label.set_stroke(WHITE, 1, background=True) - self.blocks.add(block) - self.block1, self.block2 = blocks - return blocks - - def get_axes(self): - axes = self.axes = Axes(**self.axes_config) - axes.set_stroke(LIGHT_GREY, 2) - axes.shift( - self.axes_center - axes.coords_to_point(0, 0) - ) - axes.labels = self.get_axes_labels(axes) - axes.add(axes.labels) - axes.added_lines = self.get_added_axes_lines(axes) - axes.add(axes.added_lines) - return axes - - def get_added_axes_lines(self, axes): - c2p = axes.coords_to_point - x_mult = y_mult = 1 - if self.rescale_coordinates: - x_mult = np.sqrt(self.block1.mass) - y_mult = np.sqrt(self.block2.mass) - y_lines = VGroup(*[ - Line( - c2p(0, 0), c2p(0, axes.y_max * y_mult + 1), - ).move_to(c2p(x, 0), DOWN) - for x in np.arange(0, axes.x_max) * x_mult - ]) - x_lines = VGroup(*[ - Line( - c2p(0, 0), c2p(axes.x_max * x_mult, 0), - ).move_to(c2p(0, y), LEFT) - for y in np.arange(0, axes.y_max) * y_mult - ]) - line_groups = VGroup(x_lines, y_lines) - for lines in line_groups: - lines.set_stroke(BLUE, 1, 0.5) - lines[1::2].set_stroke(width=0.5, opacity=0.25) - return line_groups - - def get_axes_labels(self, axes, with_sqrts=None): - if with_sqrts is None: - with_sqrts = self.rescale_coordinates - x_label = TexMobject("x", "=", "d_1") - y_label = TexMobject("y", "=", "d_2") - labels = VGroup(x_label, y_label) - if with_sqrts: - additions = map(TexMobject, [ - "\\sqrt{m_1}", "\\sqrt{m_2}" - ]) - for label, addition in zip(labels, additions): - addition.move_to(label[2], DL) - label[2].next_to( - addition, RIGHT, SMALL_BUFF, - aligned_edge=DOWN - ) - addition[2:].set_color(BLUE) - label.submobjects.insert(2, addition) - x_label.next_to(axes.x_axis.get_right(), DL, MED_SMALL_BUFF) - y_label.next_to(axes.y_axis.get_top(), DR, MED_SMALL_BUFF) - for label in labels: - label.shift_onto_screen() - return labels - - def get_phase_space_point(self): - ps_point = self.ps_point = VectorizedPoint() - ps_point.move_to(self.ds_to_point( - self.block1.distance, - self.block2.distance + self.block2.width - )) - self.tie_blocks_to_ps_point() - return ps_point - - def get_phase_space_x_line(self): - def get_x_line(): - origin = self.axes.coords_to_point(0, 0) - point = self.ps_point.get_location() - y_axis_point = np.array(origin) - y_axis_point[1] = point[1] - return DashedLine( - y_axis_point, point, - **self.ps_x_line_config, - ) - self.x_line = always_redraw(get_x_line) - return self.x_line - - def get_phase_space_y_line(self): - def get_y_line(): - origin = self.axes.coords_to_point(0, 0) - point = self.ps_point.get_location() - x_axis_point = np.array(origin) - x_axis_point[0] = point[0] - return DashedLine( - x_axis_point, point, - **self.ps_y_line_config, - ) - self.y_line = always_redraw(get_y_line) - return self.y_line - - def get_phase_space_dot(self): - self.ps_dot = ps_dot = Dot(**self.ps_dot_config) - ps_dot.add_updater(lambda m: m.move_to(self.ps_point)) - return ps_dot - - def get_d_label(self, n, get_d): - label = VGroup( - TexMobject("d_{}".format(n), "="), - DecimalNumber(), - ) - color = GREEN if n == 1 else RED - label[0].set_color_by_tex("d_", color) - label.scale(0.7) - label.set_stroke(BLACK, 3, background=True) - - def update_value(label): - lhs, rhs = label - rhs.set_value(get_d()) - rhs.next_to( - lhs, RIGHT, SMALL_BUFF, - aligned_edge=DOWN, - ) - label.add_updater(update_value) - return label - - def get_phase_space_d_label(self, n, get_d, line, vect): - label = self.get_d_label(n, get_d) - label.add_updater( - lambda m: m.next_to(line, vect, SMALL_BUFF) - ) - return label - - def get_phase_space_d1_label(self): - self.ps_d1_label = self.get_phase_space_d_label( - 1, self.get_d1, self.x_line, UP, - ) - return self.ps_d1_label - - def get_phase_space_d2_label(self): - self.ps_d2_label = self.get_phase_space_d_label( - 2, self.get_d2, self.y_line, - self.ps_d2_label_vect, - ) - return self.ps_d2_label - - def get_d_brace(self, get_right_point): - line = Line(LEFT, RIGHT).set_width(6) - - def get_brace(): - right_point = get_right_point() - left_point = np.array(right_point) - left_point[0] = self.wall_x - line.put_start_and_end_on(left_point, right_point) - return Brace(line, UP, buff=SMALL_BUFF) - - brace = always_redraw(get_brace) - return brace - - def get_d1_brace(self): - self.d1_brace = self.get_d_brace( - lambda: self.block1.get_corner(UL) - ) - return self.d1_brace - - def get_d2_brace(self): - self.d2_brace = self.get_d_brace( - lambda: self.block2.get_corner(UR) - ) - # self.flip_brace_nip() - return self.d2_brace - - def flip_brace_nip(self, brace): - nip_index = (len(brace) // 2) - 1 - nip = brace[nip_index:nip_index + 2] - rect = brace[nip_index - 1] - center = rect.get_center() - center[0] = nip.get_center()[0] - nip.rotate(PI, about_point=center) - - def get_brace_d_label(self, n, get_d, brace, vect, buff): - label = self.get_d_label(n, get_d) - label.add_updater( - lambda m: m.next_to(brace, vect, buff) - ) - return label - - def get_d1_label(self): - self.d1_label = self.get_brace_d_label( - 1, self.get_d1, self.d1_brace, UP, SMALL_BUFF, - ) - return self.d1_label - - def get_d2_label(self): - self.d2_label = self.get_brace_d_label( - 2, self.get_d2, self.d2_brace, UP, 0 - ) - return self.d2_label - - def get_d1_eq_d2_line(self): - start = self.ds_to_point(0, 0) - end = self.ds_to_point(15, 15) - line = self.d1_eq_d2_line = self.mirror_line_class(start, end) - line.set_style(**self.mirror_line_style) - line.set_color(self.d1_eq_d2_line_color) - return self.d1_eq_d2_line - - def get_d1_eq_d2_label(self): - label = TexMobject(self.d1_eq_d2_tex) - label.scale(0.75) - line = self.d1_eq_d2_line - point = interpolate( - line.get_start(), line.get_end(), - 0.7, - ) - label.next_to(point, DR, SMALL_BUFF) - label.match_color(line) - label.set_stroke(BLACK, 5, background=True) - self.d1_eq_d2_label = label - return label - - def get_d2_eq_w2_line(self): - w2 = self.block2.width - start = self.ds_to_point(0, w2) - end = np.array(start) - end[0] = FRAME_WIDTH / 2 - self.d2_eq_w2_line = self.mirror_line_class(start, end) - self.d2_eq_w2_line.set_style(**self.mirror_line_style) - return self.d2_eq_w2_line - - def get_d2_eq_w2_label(self): - label = TexMobject("d2 = \\text{block width}") - label.scale(0.75) - label.next_to(self.d2_eq_w2_line, UP, SMALL_BUFF) - label.to_edge(RIGHT, buff=MED_SMALL_BUFF) - self.d2_eq_w2_label = label - return label - - def get_time_tracker(self, time=0): - time_tracker = self.time_tracker = ValueTracker(time) - time_tracker.add_updater( - lambda m, dt: m.increment_value(dt) - ) - return time_tracker - - # Things associated with velocity - def get_ps_velocity_vector(self, trajectory): - vector = Vector( - self.ps_velocity_vector_length * LEFT, - **self.ps_velocity_vector_config, - ) - - def update_vector(v): - anchors = trajectory.get_anchors() - index = len(anchors) - 2 - vect = np.array(ORIGIN) - while get_norm(vect) == 0 and index > 0: - p0, p1 = anchors[index:index + 2] - vect = p1 - p0 - index -= 1 - angle = angle_of_vector(vect) - point = self.ps_point.get_location() - v.set_angle(angle) - v.shift(point - v.get_start()) - vector.add_updater(update_vector) - self.ps_velocity_vector = vector - return vector - - def get_block_velocity_vectors(self, ps_vect): - blocks = self.blocks - vectors = VGroup(*[ - Vector(LEFT, **self.block_velocity_vector_config) - for x in range(2) - ]) - # TODO: Put in config - vectors[0].set_color(GREEN) - vectors[1].set_color(RED) - - def update_vectors(vs): - v_2d = ps_vect.get_vector()[:2] - v_2d *= self.block_velocity_vector_length_multiple - for v, coord, block in zip(vs, v_2d, blocks): - v.put_start_and_end_on(ORIGIN, coord * RIGHT) - start = block.get_edge_center(v.get_vector()) - v.shift(start) - vectors.add_updater(update_vectors) - - self.block_velocity_vectors = vectors - return vectors - - -class IntroducePositionPhaseSpace(PositionPhaseSpaceScene): - CONFIG = { - "rescale_coordinates": False, - "d1_eq_d2_tex": "x = y", - "block1_config": { - "velocity": 1.5, - }, - "slide_wait_time": 12, - } - - def setup(self): - super().setup() - self.add( - self.floor, - self.wall, - self.blocks, - self.axes, - ) - - def construct(self): - self.show_coordinates() - self.show_xy_line() - self.let_process_play_out() - self.show_w2_line() - - def show_coordinates(self): - ps_point = self.ps_point - axes = self.axes - - self.play(Write(axes.added_lines)) - self.play(FadeInFromLarge(self.ps_dot, scale_factor=10)) - self.play( - ShowCreation(self.x_line), - GrowFromPoint( - self.d1_brace, - self.d1_brace.get_left(), - ), - Indicate(axes.labels[0]), - ) - self.play( - FadeInFromDown(self.ps_d1_label), - FadeInFromDown(self.d1_label), - ) - self.play(ps_point.shift, 0.5 * LEFT) - self.play(ps_point.shift, 0.5 * RIGHT) - self.wait() - self.play( - ShowCreation(self.y_line), - GrowFromPoint( - self.d2_brace, - self.d2_brace.get_left(), - ), - Indicate(axes.labels[1]), - ) - self.play( - FadeInFromDown(self.ps_d2_label), - FadeInFromDown(self.d2_label), - ) - self.play(ps_point.shift, 0.5 * UP) - self.play(ps_point.shift, 0.5 * DOWN) - self.wait() - self.play(Rotating( - ps_point, - about_point=ps_point.get_location() + 0.5 * RIGHT, - run_time=3, - rate_func=smooth, - )) - self.wait() - - def show_xy_line(self): - ps_point = self.ps_point - ps_point.save_state() - d1, d2 = self.point_to_ds(ps_point.get_location()) - - xy_line = self.d1_eq_d2_line - xy_label = self.d1_eq_d2_label - - self.play( - ShowCreation(xy_line), - Write(xy_label), - ) - self.play( - ps_point.move_to, self.ds_to_point(d2, d2), - run_time=3 - ) - self.wait() - for d in [3, 7]: - self.play( - ps_point.move_to, self.ds_to_point(d, d), - run_time=2 - ) - self.wait() - self.play(ps_point.restore) - self.wait() - - def let_process_play_out(self): - self.begin_sliding() - sliding_trajectory = self.get_continually_building_trajectory() - self.add(sliding_trajectory, self.ps_dot) - self.wait(self.slide_wait_time) - - def show_w2_line(self): - line = self.d2_eq_w2_line - label = self.d2_eq_w2_label - - self.play(ShowCreation(line)) - self.play(FadeInFromDown(label)) - self.wait(self.slide_wait_time) - self.end_sliding() - self.wait(self.slide_wait_time) - - -class SpecialShowPassingFlash(ShowPassingFlash): - CONFIG = { - "max_time_width": 0.1, - } - - def get_bounds(self, alpha): - tw = self.time_width - max_tw = self.max_time_width - upper = interpolate(0, 1 + max_tw, alpha) - lower = upper - tw - upper = min(upper, 1) - lower = max(lower, 0) - return (lower, upper) - - -class EqualMassCase(PositionPhaseSpaceScene): - CONFIG = { - "block1_config": { - "mass": 1, - "width": 1, - "velocity": 1.5, - }, - "rescale_coordinates": False, - "d1_eq_d2_tex": "x = y", - } - - def setup(self): - super().setup() - self.add( - self.floor, - self.wall, - self.blocks, - self.axes, - self.d1_eq_d2_line, - self.d1_eq_d2_label, - self.d2_eq_w2_line, - self.d2_eq_w2_label, - self.ps_dot, - self.x_line, - self.y_line, - self.ps_d1_label, - self.ps_d2_label, - ) - - def construct(self): - self.show_same_mass() - self.show_first_point() - self.up_to_first_collision() - self.up_to_second_collision() - self.up_to_third_collision() - - self.fade_distance_indicators() - self.show_beam_bouncing() - - def show_same_mass(self): - blocks = self.blocks - self.play(LaggedStartMap( - Indicate, blocks, - lag_ratio=0.8, - run_time=1, - )) - - def show_first_point(self): - ps_dot = self.ps_dot - ps_point = self.ps_point - d1, d2 = self.get_ds() - - self.play(FocusOn(ps_dot)) - self.play(ShowCreationThenFadeOut( - Circle(color=RED).replace(ps_dot).scale(2), - run_time=1 - )) - self.wait() - self.play( - ps_point.move_to, self.ds_to_point(d1 - 1, d2), - rate_func=wiggle, - run_time=3, - ) - # self.play(ps_point.move_to, self.ds_to_point(d1, d2)) - self.wait() - - def up_to_first_collision(self): - ps_point = self.ps_point - d1, d2 = self.get_ds() - block1 = self.block1 - block2 = self.block2 - xy_line = self.d1_eq_d2_line - xy_line_label = self.d1_eq_d2_label - - block_arrow = Vector(LEFT, color=RED) - block_arrow.block = block1 - block_arrow.add_updater( - lambda m: m.shift( - m.block.get_center() - m.get_start() - ) - ) - ps_arrow = Vector(LEFT, color=RED) - ps_arrow.next_to(ps_point, DL, buff=SMALL_BUFF) - - block_labels = VGroup(block1.label, block2.label) - block_label_copies = block_labels.copy() - - def update_bl_copies(bl_copies): - for bc, b in zip(bl_copies, block_labels): - bc.move_to(b) - block_label_copies.add_updater(update_bl_copies) - - trajectory = self.get_continually_building_trajectory() - - self.add(block_arrow, ps_arrow, block_label_copies) - self.play( - GrowArrow(block_arrow), - GrowArrow(ps_arrow), - ) - self.add(trajectory) - self.play(self.get_ps_point_change_anim(d2, d2)) - block_arrow.block = block2 - ps_arrow.rotate(90 * DEGREES) - ps_arrow.next_to(ps_point, DR, SMALL_BUFF) - self.add_sound(self.clack_sound) - self.play( - Flash(ps_point), - Flash(block1.get_left()), - self.get_ps_point_change_anim(d2, d2 - 1) - ) - self.play( - ShowPassingFlash( - xy_line.copy().set_stroke(YELLOW, 3) - ), - Indicate(xy_line_label), - ) - - trajectory.suspend_updating() - self.wait() - - self.ps_arrow = ps_arrow - self.block_arrow = block_arrow - - def up_to_second_collision(self): - trajectory = self.trajectory - ps_point = self.ps_point - ps_arrow = self.ps_arrow - block_arrow = self.block_arrow - - d1, d2 = self.get_ds() - w2 = self.block2.get_width() - - trajectory.resume_updating() - self.play(self.get_ps_point_change_anim(d1, w2)) - block_arrow.rotate(PI) - ps_arrow.rotate(PI) - ps_arrow.next_to(ps_point, UR, SMALL_BUFF) - self.add_sound(self.clack_sound) - self.play( - Flash(self.block2.get_left()), - Flash(ps_point), - self.get_ps_point_change_anim(d1, w2 + 1) - ) - - trajectory.suspend_updating() - self.wait() - - def up_to_third_collision(self): - trajectory = self.trajectory - ps_point = self.ps_point - ps_arrow = self.ps_arrow - block_arrow = self.block_arrow - d1, d2 = self.get_ds() - - trajectory.resume_updating() - self.play(self.get_ps_point_change_anim(d1, d1)) - block_arrow.block = self.block1 - ps_arrow.rotate(-90 * DEGREES) - ps_arrow.next_to(ps_point, DR, SMALL_BUFF) - self.add_sound(self.clack_sound) - self.play( - Flash(self.block2.get_left()), - Flash(ps_point.get_location()), - self.get_ps_point_change_anim(d1 + 10, d1) - ) - trajectory.suspend_updating() - - def fade_distance_indicators(self): - trajectory = self.trajectory - self.play( - trajectory.set_stroke, {"width": 1}, - *map(FadeOut, [ - self.ps_arrow, - self.block_arrow, - self.x_line, - self.y_line, - self.ps_d1_label, - self.ps_d2_label, - ]) - ) - trajectory.clear_updaters() - - def show_beam_bouncing(self): - d1, d2 = self.get_ds() - d1 = int(d1) - d2 = int(d2) - # w2 = self.block2.get_width() - ps_point = self.ps_point - - points = [] - while d1 > d2: - points.append(self.ds_to_point(d1, d2)) - d1 -= 1 - while d2 >= 1: - points.append(self.ds_to_point(d1, d2)) - d2 -= 1 - points += list(reversed(points))[1:] - trajectory = VMobject() - trajectory.set_points_as_corners(points) - flashes = [ - SpecialShowPassingFlash( - trajectory.copy().set_stroke(YELLOW, width=6 - n), - time_width=(0.01 * n), - max_time_width=0.05, - remover=True - ) - for n in np.arange(0, 6, 0.25) - ] - flash_mob = flashes[0].mobject # Lol - - def update_ps_point_from_flas_mob(ps_point): - if len(flash_mob.points) > 0: - ps_point.move_to(flash_mob.points[-1]) - else: - ps_point.move_to(trajectory.points[0]) - - # Mirror words - xy_line = self.d1_eq_d2_line - w2_line = self.d2_eq_w2_line - lines = VGroup(xy_line, w2_line) - for line in lines: - word = TextMobject("Mirror") - word.next_to(ORIGIN, UP, SMALL_BUFF) - word.rotate(line.get_angle(), about_point=ORIGIN) - word.shift(line.get_center()) - line.word = word - - for line in lines: - line.set_stroke(LIGHT_GREY) - line.set_sheen(1, LEFT) - self.play( - Write(line.word), - line.set_sheen, 1, RIGHT, - line.set_stroke, {"width": 2}, - run_time=1, - ) - - # TODO, clacks? - for x in range(3): - self.play( - UpdateFromFunc( - ps_point, - update_ps_point_from_flas_mob, - ), - *flashes, - run_time=3, - rate_func=linear, - ) - self.wait() - - -class FailedAngleRelation(PositionPhaseSpaceScene): - CONFIG = { - "block1_config": { - "distance": 10, - "velocity": -1.5, - }, - "block2_config": { - "distance": 5, - }, - "rescale_coordinates": False, - "trajectory_style": { - "stroke_width": 2, - } - } - - def setup(self): - super().setup() - self.add( - self.floor, - self.wall, - self.blocks, - self.axes, - self.ps_dot, - self.x_line, - self.y_line, - self.d1_eq_d2_line, - self.d1_eq_d2_label, - self.d2_eq_w2_line, - self.d2_eq_w2_label, - ) - - def construct(self): - self.show_first_collision() - self.show_angles() - - def show_first_collision(self): - self.slide_until(lambda: self.get_ds()[1] < 2) - - def show_angles(self): - trajectory = self.trajectory - arcs = self.get_arcs(trajectory) - equation = self.get_word_equation() - equation.next_to( - trajectory.points[0], UR, MED_SMALL_BUFF, - index_of_submobject_to_align=0, - ) - - for arc in arcs: - line = Line(ORIGIN, RIGHT) - line.set_stroke(WHITE, 2) - line.rotate(arc.start_angle) - line.shift(arc.arc_center - line.get_start()) - arc.line = line - - arc1, arc2 = arcs - arc1.arrow = Arrow( - equation[0].get_left(), arc1.get_right(), - buff=SMALL_BUFF, - color=WHITE, - path_arc=0, - ) - arc2.arrow = Arrow( - equation[2].get_corner(DL), - arc2.get_left(), - path_arc=-120 * DEGREES, - buff=SMALL_BUFF, - ) - arc2.arrow.pointwise_become_partial(arc.arrow, 0, 0.95) - - arc1.word = equation[0] - arc2.word = equation[1:] - - for arc in arcs: - self.play( - FadeInFrom(arc.word, LEFT), - GrowArrow(arc.arrow, path_arc=arc.arrow.path_arc), - ) - self.play( - ShowCreation(arc), - arc.line.rotate, arc.angle, - {"about_point": arc.line.get_start()}, - UpdateFromAlphaFunc( - arc.line, - lambda m, a: m.set_stroke( - opacity=(there_and_back(a)**0.5) - ) - ), - ) - - # - def get_arcs(self, trajectory): - p0, p1, p2 = trajectory.get_anchors()[1:4] - arc_config = { - "stroke_color": WHITE, - "stroke_width": 2, - "radius": 0.5, - "arc_center": p1, - } - arc1 = Arc( - start_angle=0, - angle=45 * DEGREES, - **arc_config - ) - a2_start = angle_of_vector(DL) - a2_angle = angle_between_vectors((p2 - p1), DL) - arc2 = Arc( - start_angle=a2_start, - angle=a2_angle, - **arc_config - ) - return VGroup(arc1, arc2) - - def get_word_equation(self): - result = VGroup( - TextMobject("Angle of incidence"), - TexMobject("\\ne").rotate(90 * DEGREES), - TextMobject("Angle of reflection") - ) - result.arrange(DOWN) - result.set_stroke(BLACK, 5, background=True) - return result - - -class UnscaledPositionPhaseSpaceMass10(FailedAngleRelation): - CONFIG = { - "block1_config": { - "mass": 10 - }, - "wait_time": 25, - } - - def construct(self): - self.slide(self.wait_time) - - -class UnscaledPositionPhaseSpaceMass100(UnscaledPositionPhaseSpaceMass10): - CONFIG = { - "block1_config": { - "mass": 100 - } - } - - -class RescaleCoordinates(PositionPhaseSpaceScene, MovingCameraScene): - CONFIG = { - "rescale_coordinates": False, - "ps_d2_label_vect": LEFT, - "axes_center": 6 * LEFT + 0.65 * DOWN, - "block1_config": {"distance": 7}, - "wait_time": 30, - } - - def setup(self): - PositionPhaseSpaceScene.setup(self) - MovingCameraScene.setup(self) - self.add( - self.floor, - self.wall, - self.blocks, - self.axes, - self.d1_eq_d2_line, - self.d1_eq_d2_label, - self.d2_eq_w2_line, - self.ps_dot, - self.x_line, - self.y_line, - self.ps_d1_label, - self.ps_d2_label, - self.d1_brace, - self.d2_brace, - self.d1_label, - self.d2_label, - ) - - def construct(self): - self.show_rescaling() - self.comment_on_ugliness() - self.put_into_frame() - - def show_rescaling(self): - axes = self.axes - blocks = self.blocks - to_stretch = VGroup( - axes.added_lines, - self.d1_eq_d2_line, - self.ps_point, - ) - m1 = self.block1.mass - new_axes_labels = self.get_axes_labels(axes, with_sqrts=True) - - # Show label - def show_label(index, block, vect): - self.play( - ShowCreationThenFadeAround(axes.labels[index]) - ) - self.play( - Transform( - axes.labels[index], - VGroup( - *new_axes_labels[index][:2], - new_axes_labels[index][3] - ), - ), - GrowFromCenter(new_axes_labels[index][2]) - ) - group = VGroup( - new_axes_labels[index][2][-2:].copy(), - TexMobject("="), - block.label.copy(), - ) - group.generate_target() - group.target.arrange(RIGHT, buff=SMALL_BUFF) - group.target.next_to(block, vect) - group[1].scale(0) - group[1].move_to(group.target[1]) - group.target[2].set_fill(WHITE) - group.target[2].set_stroke(width=0, background=True) - self.play(MoveToTarget( - group, - rate_func=there_and_back_with_pause, - run_time=3 - )) - self.remove(group) - self.wait() - - show_label(0, self.block1, RIGHT) - - # The stretch - blocks.suspend_updating() - self.play( - ApplyMethod( - to_stretch.stretch, np.sqrt(m1), 0, - {"about_point": axes.coords_to_point(0, 0)}, - ), - self.d1_eq_d2_label.shift, 6 * RIGHT, - run_time=2, - ) - self.rescale_coordinates = True - blocks.resume_updating() - self.wait() - - # Show wiggle - d1, d2 = self.get_ds() - for new_d1 in [d1 - 2, d1]: - self.play(self.get_ps_point_change_anim( - new_d1, d2, - rate_func=smooth, - run_time=2, - )) - self.wait() - - # Change y-coord - show_label(1, self.block2, LEFT) - - axes.remove(axes.labels) - self.remove(axes.labels) - axes.labels = new_axes_labels - axes.add(axes.labels) - self.add(axes) - - def comment_on_ugliness(self): - axes = self.axes - - randy = Randolph(height=1.7) - randy.flip() - randy.next_to(self.d2_eq_w2_line, UP, buff=0) - randy.to_edge(RIGHT) - randy.change("sassy") - randy.save_state() - randy.fade(1) - randy.change("plain") - - self.play(Restore(randy)) - self.play( - PiCreatureSays( - randy, "Hideous!", - bubble_kwargs={"height": 1.5, "width": 2}, - target_mode="angry", - look_at_arg=axes.labels[0] - ) - ) - self.play(randy.look_at, axes.labels[1]) - self.play(Blink(randy)) - self.play( - RemovePiCreatureBubble( - randy, target_mode="confused" - ) - ) - self.play(Blink(randy)) - self.play(randy.look_at, axes.labels[0]) - self.wait() - self.play(FadeOut(randy)) - - def put_into_frame(self): - rect = ScreenRectangle(height=FRAME_HEIGHT + 10) - inner_rect = ScreenRectangle(height=FRAME_HEIGHT) - rect.add_subpath(inner_rect.points[::-1]) - rect.set_fill("#333333", opacity=1) - frame = self.camera_frame - - self.begin_sliding() - self.add(rect) - self.play( - frame.scale, 1.5, - {"about_point": frame.get_bottom() + UP}, - run_time=2, - ) - self.wait(self.wait_time) - self.end_sliding() - - # - def get_ds(self): - if self.rescale_coordinates: - return super().get_ds() - return ( - self.block1_config["distance"], - self.block2_config["distance"], - ) - - -class RescaleCoordinatesMass16(RescaleCoordinates): - CONFIG = { - "block1_config": { - "mass": 16, - "distance": 10, - }, - "rescale_coordinates": True, - "wait_time": 20, - } - - def construct(self): - self.put_into_frame() - - -class RescaleCoordinatesMass64(RescaleCoordinatesMass16): - CONFIG = { - "block1_config": { - "mass": 64, - "distance": 6, - }, - "block2_config": {"distance": 3}, - } - - -class RescaleCoordinatesMass100(RescaleCoordinatesMass16): - CONFIG = { - "block1_config": { - "mass": 100, - "distance": 6, - "velocity": 0.5, - }, - "block2_config": {"distance": 2}, - "wait_time": 25, - } - - -class IntroduceVelocityVector(PositionPhaseSpaceScene, MovingCameraScene): - CONFIG = { - "zoom": True, - "ps_x_line_config": { - "color": WHITE, - "stroke_width": 1, - "stroke_opacity": 0.5, - }, - "ps_y_line_config": { - "color": WHITE, - "stroke_width": 1, - "stroke_opacity": 0.5, - }, - "axes_center": 6 * LEFT + 0.65 * DOWN, - "slide_time": 20, - "new_vect_config": { - "tip_length": 0.1, - "rectangular_stem_width": 0.02, - } - } - - def setup(self): - MovingCameraScene.setup(self) - PositionPhaseSpaceScene.setup(self) - self.add( - self.floor, - self.wall, - self.blocks, - self.axes, - self.d1_eq_d2_line, - self.d1_eq_d2_label, - self.d2_eq_w2_line, - self.x_line, - self.y_line, - self.ps_dot, - ) - - def construct(self): - self.show_velocity_vector() - self.contrast_with_physical_velocities() - self.zoom_in_on_vector() - self.break_down_components() - self.zoom_out() - self.relate_x_dot_y_dot_to_v1_v2() - self.calculate_magnitude() - self.let_process_play_out() - - def show_velocity_vector(self): - self.slide(2) - ps_vect = self.get_ps_velocity_vector(self.trajectory) - self.play(GrowArrow(ps_vect)) - self.play(ShowCreationThenFadeAround(ps_vect)) - self.wait() - - def contrast_with_physical_velocities(self): - ps_vect = self.ps_velocity_vector - block_vectors = self.get_block_velocity_vectors(ps_vect) - - self.play(LaggedStartMap(GrowArrow, block_vectors)) - self.play(Rotating( - ps_vect, - angle=TAU, - about_point=ps_vect.get_start(), - run_time=5, - rate_func=smooth, - )) - self.wait() - self.slide_until(lambda: self.get_d2() < 2.5) - - def zoom_in_on_vector(self): - if not self.zoom: - self.wait(3) - return - ps_vect = self.ps_velocity_vector - new_vect = Arrow( - ps_vect.get_start(), - ps_vect.get_end(), - buff=0, - **self.new_vect_config - ) - new_vect.match_style(ps_vect) - - camera_frame = self.camera_frame - camera_frame.save_state() - point = self.ps_point.get_location() - point += MED_SMALL_BUFF * DOWN - self.play( - camera_frame.scale, 0.25, {"about_point": point}, - Transform(ps_vect, new_vect), - run_time=2, - ) - self.wait() - - def break_down_components(self): - # Create vectors - ps_vect = self.ps_velocity_vector - start = ps_vect.get_start() - end = ps_vect.get_end() - ul_corner = np.array(start) - dr_corner = np.array(start) - ul_corner[0] = end[0] - dr_corner[1] = end[1] - - x_vect = Arrow( - start, ul_corner, - buff=0, - **self.new_vect_config - ) - y_vect = Arrow( - start, dr_corner, - buff=0, - **self.new_vect_config - ) - x_vect.set_fill(GREEN, opacity=0.75) - y_vect.set_fill(RED, opacity=0.75) - vects = VGroup(x_vect, y_vect) - - # Projection lines - x_line, y_line = [ - DashedLine( - ps_vect.get_end(), - vect.get_end(), - dash_length=0.01, - color=vect.get_color(), - ) - for vect in (x_vect, y_vect) - ] - self.projection_lines = VGroup(x_line, y_line) - - # Vector labels - dx_label = TexMobject("\\frac{dx}{dt}") - dy_label = TexMobject("\\frac{dy}{dt}") - labels = VGroup(dx_label, dy_label) - for label, arrow, direction in zip(labels, vects, [UP, RIGHT]): - label.scale(0.25) - buff = 0.25 * SMALL_BUFF - label.next_to(arrow, direction, buff=buff) - label.set_stroke(BLACK, 3, background=True) - - if not self.zoom: - self.grow_labels(labels) - - self.play( - TransformFromCopy(ps_vect, x_vect), - ShowCreation(x_line), - ) - self.play(FadeInFrom(dx_label, 0.25 * DOWN)) - self.wait() - self.play( - TransformFromCopy(ps_vect, y_vect), - ShowCreation(y_line), - ) - self.play(FadeInFrom(dy_label, 0.25 * LEFT)) - self.wait() - - # Ask about dx_dt - randy = Randolph() - randy.match_height(dx_label) - randy.next_to(dx_label, LEFT, SMALL_BUFF) - randy.change("confused", dx_label) - randy.save_state() - randy.fade(1) - randy.change("plain") - - self.play(Restore(randy)) - self.play(WiggleOutThenIn(dx_label)) - self.play(Blink(randy)) - self.play(FadeOut(randy)) - - self.derivative_labels = labels - self.component_vectors = vects - - def zoom_out(self): - if not self.zoom: - self.wait(2) - return - labels = self.derivative_labels - self.play( - Restore(self.camera_frame), - ApplyFunction(self.grow_labels, labels), - run_time=2 - ) - - def relate_x_dot_y_dot_to_v1_v2(self): - derivative_labels = self.derivative_labels.copy() - dx_label, dy_label = derivative_labels - x_label, y_label = self.axes.labels - m_part = x_label[2] - block1 = self.block1 - block_vectors = self.block_velocity_vectors - - x_eq = x_label[1] - dx_eq = TexMobject("=") - dx_eq.next_to( - x_eq, DOWN, - buff=LARGE_BUFF, - aligned_edge=RIGHT, - ) - for label in derivative_labels: - label.generate_target() - label.target.scale(1.5) - dx_label.target.next_to(dx_eq, LEFT) - dx_rhs = TexMobject("\\sqrt{m_1}", "v_1") - dx_rhs[0][2:].set_color(BLUE) - dx_rhs[1].set_color(GREEN) - dx_rhs.next_to(dx_eq, RIGHT) - alt_v1 = dx_rhs[1].copy() - - self.play(ShowCreationThenFadeAround(x_label)) - self.play(MoveToTarget(dx_label)) - self.play(TransformFromCopy(x_eq, dx_eq)) - self.wait() - self.play( - VGroup(block1, m_part).shift, SMALL_BUFF * UP, - rate_func=wiggle, - ) - self.wait() - self.d1_brace.update() - self.d1_label.update() - self.play( - ShowCreationThenFadeAround(x_label[3]), - FadeIn(self.d1_brace), - FadeIn(self.d1_label), - ) - self.wait() - self.play( - TransformFromCopy(x_label[3], dx_rhs[1]), - TransformFromCopy(x_label[2], VGroup(dx_rhs[0])), - ) - block_vectors.suspend_updating() - self.play(alt_v1.next_to, block_vectors[0], UP, SMALL_BUFF) - self.play( - Rotate( - block_vectors[0], 10 * DEGREES, - about_point=block_vectors[0].get_start(), - rate_func=wiggle, - run_time=1, - ) - ) - self.play(FadeOut(alt_v1)) - block_vectors.resume_updating() - self.wait() - - # dy_label - y_eq = y_label[1] - dy_eq = TexMobject("=") - dy_eq.next_to(y_eq, DOWN, LARGE_BUFF) - dy_label.target.next_to(dy_eq, LEFT) - dy_rhs = TexMobject("\\sqrt{m_2}", "v_2") - dy_rhs[0][2:].set_color(BLUE) - dy_rhs[1].set_color(RED) - dy_rhs.next_to(dy_eq, RIGHT) - VGroup(dy_label.target, dy_eq, dy_rhs).align_to(y_label, LEFT) - alt_v2 = dy_rhs[1].copy() - self.play(MoveToTarget(dy_label)) - self.play( - Write(dy_eq), - Write(dy_rhs), - ) - self.play(alt_v2.next_to, block_vectors[1], UP, SMALL_BUFF) - self.wait() - self.play(FadeOut(alt_v2)) - self.wait() - - self.derivative_equations = VGroup( - VGroup(dx_label, dx_eq, dx_rhs), - VGroup(dy_label, dy_eq, dy_rhs), - ) - - def calculate_magnitude(self): - corner_rect = Rectangle( - stroke_color=WHITE, - stroke_width=1, - fill_color=BLACK, - fill_opacity=1, - height=2.5, - width=8.5, - ) - corner_rect.to_corner(UR, buff=0) - - ps_vect = self.ps_velocity_vector - big_ps_vect = Arrow( - ps_vect.get_start(), ps_vect.get_end(), - buff=0, - ) - big_ps_vect.match_style(ps_vect) - big_ps_vect.scale(1.5) - magnitude_bars = TexMobject("||", "||") - magnitude_bars.match_height( - big_ps_vect, stretch=True - ) - rhs_scale_val = 0.8 - rhs = TexMobject( - "=\\sqrt{" - "\\left( dx/dt \\right)^2 + " - "\\left( dy/dt \\right)^2" - "}" - ) - rhs.scale(rhs_scale_val) - group = VGroup( - magnitude_bars[0], big_ps_vect, - magnitude_bars[1], rhs - ) - group.arrange(RIGHT) - group.next_to(corner_rect.get_corner(UL), DR) - - new_rhs = TexMobject( - "=", "\\sqrt", "{m_1(v_1)^2 + m_2(v_2)^2}", - tex_to_color_map={ - "m_1": BLUE, - "m_2": BLUE, - "v_1": GREEN, - "v_2": RED, - } - ) - new_rhs.scale(rhs_scale_val) - new_rhs.next_to(rhs, DOWN, aligned_edge=LEFT) - - final_rhs = TexMobject( - "=", "\\sqrt{2(\\text{Kinetic energy})}" - ) - final_rhs.scale(rhs_scale_val) - final_rhs.next_to(new_rhs, DOWN, aligned_edge=LEFT) - - self.play( - FadeIn(corner_rect), - TransformFromCopy(ps_vect, big_ps_vect) - ) - self.play(Write(magnitude_bars), Write(rhs[0])) - self.wait() - self.play(Write(rhs[1:])) - self.wait() - self.play(FadeInFrom(new_rhs, UP)) - for equation in self.derivative_equations: - self.play(ShowCreationThenFadeAround(equation)) - self.wait() - self.play(FadeInFrom(final_rhs, UP)) - self.wait() - - def let_process_play_out(self): - self.play(*map(FadeOut, [ - self.projection_lines, - self.derivative_labels, - self.component_vectors, - self.d1_brace, - self.d1_label, - ])) - self.add(self.blocks, self.derivative_equations) - self.blocks.resume_updating() - self.slide(self.slide_time) - - # - def grow_labels(self, labels): - for label, vect in zip(labels, [DOWN, LEFT]): - p = label.get_edge_center(vect) - p += SMALL_BUFF * vect - label.scale(2.5, about_point=p) - return labels - - -class IntroduceVelocityVectorWithoutZoom(IntroduceVelocityVector): - CONFIG = { - "zoom": False, - } - - -class ShowMomentumConservation(IntroduceVelocityVector): - CONFIG = { - "ps_velocity_vector_length": 1.25, - "block_velocity_vector_length_multiple": 1, - "block1_config": { - "distance": 7, - }, - "axes_config": { - "y_max": 11, - }, - "axes_center": 6.5 * LEFT + 1.2 * DOWN, - "floor_y": -3.75, - "wait_time": 15, - } - - def construct(self): - self.add_velocity_vectors() - self.contrast_d1_d2_line_with_xy_line() - self.rearrange_for_slope() - self.up_to_first_collision() - self.ask_what_next() - self.show_conservation_of_momentum() - self.show_rate_of_change_vector() - self.show_sqrty_m_vector() - self.show_dot_product() - self.show_same_angles() - self.show_horizontal_bounce() - self.let_process_play_out() - - def add_velocity_vectors(self): - self.slide(1) - self.ps_vect = self.get_ps_velocity_vector(self.trajectory) - self.block_vectors = self.get_block_velocity_vectors(self.ps_vect) - self.play( - GrowArrow(self.ps_vect), - LaggedStartMap(GrowArrow, self.block_vectors, run_time=1), - ) - self.add(self.ps_vect, self.block_vectors) - - def contrast_d1_d2_line_with_xy_line(self): - line = self.d1_eq_d2_line - label = self.d1_eq_d2_label - label.to_edge(RIGHT, buff=1.1) - label.shift(0.65 * DOWN) - - xy_line = line.copy() - xy_line.set_stroke(YELLOW, 3) - xy_line.set_angle(45 * DEGREES) - xy_label = TexMobject("x = y") - xy_label.next_to(ORIGIN, DOWN, SMALL_BUFF) - xy_label.rotate(45 * DEGREES, about_point=ORIGIN) - xy_label.shift(xy_line.point_from_proportion(0.2)) - self.xy_group = VGroup(xy_line, xy_label) - - self.play( - ShowPassingFlash( - line.copy().set_stroke(YELLOW, 4) - ), - Write(label), - ) - self.play( - TransformFromCopy(line, xy_line, run_time=2) - ) - self.play(Write(xy_label)) - self.wait() - - def rearrange_for_slope(self): - eqs = VGroup(*reversed(self.axes.labels)).copy() - y_eq, x_eq = eqs - for eq in eqs: - point = VectorizedPoint(eq[1].get_center()) - eq.submobjects.insert(1, point) - eq.submobjects[3] = eq[3].submobjects[0] - eq.generate_target() - eqs_targets = VGroup(*[eq.target for eq in eqs]) - - new_eqs = VGroup( - TexMobject("{y", "\\over", "\\sqrt{m_2}}", "=", "d_2"), - TexMobject("{x", "\\over", "\\sqrt{m_1}}", "=", "d_1"), - ) - new_x_eq, new_y_eq = new_eqs - # Shuffle to align with x_eq and y_eq - for new_eq in new_eqs: - new_eq[2][2:].set_color(BLUE) - new_eq.submobjects = [new_eq[i] for i in [0, 1, 3, 2, 4]] - - eqs_targets.arrange(DOWN, buff=LARGE_BUFF) - eqs_targets.move_to(RIGHT).to_edge(UP) - for eq, new_eq in zip(eqs_targets, new_eqs): - new_eq.move_to(eq) - - self.play(LaggedStartMap(MoveToTarget, eqs, lag_ratio=0.7)) - self.play(*[ - Transform( - eq, new_eq, - path_arc=-90 * DEGREES, - ) - for eq, new_eq in zip(eqs, new_eqs) - ]) - self.wait() - - # Shuffle back - for eq in eqs: - eq[2][2:].set_color(BLUE) - eq.submobjects = [eq[i] for i in [0, 1, 3, 2, 4]] - - # Set equal - equals = TexMobject("=") - for eq in eqs: - eq.generate_target() - VGroup( - x_eq.target[4], - x_eq.target[3], - x_eq.target[:3], - ).arrange(RIGHT) - for p1, p2 in zip(x_eq, x_eq.target): - p2.align_to(p1, DOWN) - group = VGroup(y_eq.target, equals, x_eq.target) - group.arrange(RIGHT) - x_eq.target.align_to(y_eq.target, DOWN) - equals.align_to(y_eq.target[3], DOWN) - group.to_edge(UP, buff=MED_SMALL_BUFF) - group.to_edge(RIGHT, buff=3) - - self.play( - MoveToTarget(y_eq), - MoveToTarget(x_eq, path_arc=90 * DEGREES), - GrowFromCenter(equals) - ) - self.wait() - - # Simplify - final_eq = TexMobject( - "y", "=", - "{\\sqrt{m_2}", "\\over", "\\sqrt{m_1}}", - "x", - ) - for part in final_eq.get_parts_by_tex("sqrt"): - part[2:].set_color(BLUE) - m_part = final_eq[2:5] - - final_eq.next_to(group, DOWN) - final_eq.shift(0.4 * UP) - movers = VGroup( - y_eq[0], equals.submobjects[0], - y_eq[2], y_eq[1], x_eq[2], - x_eq[0] - ).copy() - for mover, part in zip(movers, final_eq): - mover.target = part - self.play( - LaggedStartMap( - MoveToTarget, movers, - path_arc=30 * DEGREES, - lag_ratio=0.9 - ), - VGroup(x_eq, equals, y_eq).scale, - 0.7, {"about_edge": UP}, - ) - self.remove(movers) - self.add(final_eq) - self.wait() - - # Highlight slope - flash_line = self.d1_eq_d2_line.copy() - flash_line.set_stroke(YELLOW, 5) - self.play(ShowPassingFlash(flash_line)) - self.play(ShowCreationThenFadeAround(m_part)) - self.wait() - - # Tuck away slope in mind - slope = m_part.copy() - slope.generate_target() - randy = Randolph(height=1.5) - randy.next_to(final_eq, LEFT, MED_SMALL_BUFF) - randy.align_to(self.d2_eq_w2_line, DOWN) - bubble = ThoughtBubble( - height=1.3, width=1.3, direction=RIGHT, - ) - bubble.pin_to(randy) - slope.target.scale(0.5) - slope.target.move_to(bubble.get_bubble_center()) - randy.change("pondering", slope.target) - randy.save_state() - randy.change("plane") - randy.fade(1) - - self.play( - Restore(randy), - Write(bubble), - MoveToTarget(slope) - ) - self.play(Blink(randy)) - - self.thinking_on_slope_group = VGroup( - randy, bubble, slope, - ) - - self.to_fade = VGroup( - eqs, equals, final_eq, - self.thinking_on_slope_group, - self.xy_group, - ) - - def up_to_first_collision(self): - self.begin_sliding() - self.play(FadeOut(self.to_fade)) - self.wait_until( - lambda: abs(self.ps_velocity_vector.get_vector()[1]) > 0.01 - ) - self.end_sliding() - self.wait(3 + 3 / 60) # Final cut reasons - - def ask_what_next(self): - ps_vect = self.ps_velocity_vector - question = TextMobject("What next?") - question.set_background_stroke(color=BLACK, width=3) - question.next_to(self.ps_point, UP) - - self.play(FadeInFrom(question, DOWN)) - ps_vect.suspend_updating() - angles = [0.75 * PI, -0.5 * PI, -0.25 * PI] - for last_angle, angle in zip(np.cumsum([0] + angles), angles): - # This is dumb and shouldn't be needed - ps_vect.rotate(last_angle, about_point=ps_vect.get_start()) - target = ps_vect.copy() - target.rotate( - angle, - about_point=ps_vect.get_start() - ) - self.play( - Transform( - ps_vect, target, - path_arc=angle - ), - ) - ps_vect.resume_updating() - - self.whats_next_question = question - - def show_conservation_of_momentum(self): - equation = self.get_momentum_equation() - - # Main equation - self.play(FadeInFromDown(equation)) - for part in equation[:2], equation[3:5]: - outline = part.copy() - outline.set_fill(opacity=0) - outline.set_stroke(YELLOW, 3) - self.play(ShowPassingFlash( - outline, - run_time=1.5 - )) - self.wait(0.5) - - # Dot product - dot_product = self.get_dot_product() - dot_product.next_to(equation, DOWN) - sqrty_m_array = dot_product[0] - - x_label, y_label = self.axes.labels - - self.play( - FadeOut(self.whats_next_question), - FadeIn(dot_product), - Transform( - x_label[2].copy(), - sqrty_m_array.get_entries()[0], - remover=True, - ), - Transform( - y_label[2].copy(), - sqrty_m_array.get_entries()[1], - remover=True, - ), - ) - - self.momentum_equation = equation - self.dot_product = dot_product - - def show_rate_of_change_vector(self): - ps_vect = self.ps_velocity_vector - original_d_array = self.dot_product[2] - - d_array = original_d_array.copy() - d_array.generate_target() - d_array.scale(0.75) - d_array.add_updater(lambda m: m.next_to( - ps_vect.get_end(), - np.sign(ps_vect.get_vector()[0]) * RIGHT, - SMALL_BUFF - )) - - self.play(TransformFromCopy(original_d_array, d_array)) - self.wait() - - self.d_array = d_array - - def show_sqrty_m_vector(self): - original_sqrty_m_array = self.dot_product[0] - sqrty_m_vector = Arrow( - self.ds_to_point(0, 0), - # self.ds_to_point(1, 1), - self.ds_to_point(2, 2), - buff=0, - color=YELLOW, - ) - - sqrty_m_array = original_sqrty_m_array.deepcopy() - sqrty_m_array.scale(0.75) - sqrty_m_array.next_to( - sqrty_m_vector.get_end(), UP, SMALL_BUFF - ) - - rise = DashedLine( - sqrty_m_vector.get_end(), - sqrty_m_vector.get_corner(DR), - color=RED, - ) - run = DashedLine( - sqrty_m_vector.get_corner(DR), - sqrty_m_vector.get_start(), - color=GREEN, - ) - sqrty_m_array.add_background_to_entries() - run_label, rise_label = sqrty_m_array.get_entries().copy() - rise_label.next_to(rise, RIGHT, SMALL_BUFF) - run_label.next_to(run, DOWN, SMALL_BUFF) - - randy_group = self.thinking_on_slope_group - randy_group.align_to(self.d2_eq_w2_line, DOWN) - randy_group.to_edge(LEFT) - randy_group.shift(2 * RIGHT) - - self.play(GrowArrow(sqrty_m_vector)) - self.play(TransformFromCopy( - original_sqrty_m_array, sqrty_m_array, - )) - self.play(FadeIn(randy_group)) - self.play( - ShowCreation(rise), - TransformFromCopy( - sqrty_m_array.get_entries()[1], - rise_label, - ), - ) - self.add(run, randy_group) - self.play( - ShowCreation(run), - TransformFromCopy( - sqrty_m_array.get_entries()[0], - run_label, - ), - ) - self.wait() - self.play(FadeOut(randy_group)) - self.play(FadeOut(VGroup( - rise, run, rise_label, run_label, - ))) - - # move to ps_point - point = self.ps_point.get_location() - sqrty_m_vector.generate_target() - sqrty_m_vector.target.shift( - point - sqrty_m_vector.get_start() - ) - sqrty_m_array.generate_target() - sqrty_m_array.target.next_to( - sqrty_m_vector.target.get_end(), - RIGHT, SMALL_BUFF, - ) - sqrty_m_array.target.shift(SMALL_BUFF * UP) - self.play( - MoveToTarget(sqrty_m_vector), - MoveToTarget(sqrty_m_array), - run_time=2 - ) - - self.sqrty_m_vector = sqrty_m_vector - self.sqrty_m_array = sqrty_m_array - - def show_dot_product(self): - # Highlight arrays - d_array = self.d_array - big_d_array = self.dot_product[2] - m_array = self.sqrty_m_array - big_m_array = self.dot_product[0] - - self.play( - ShowCreationThenFadeAround(big_d_array), - ShowCreationThenFadeAround(d_array), - ) - self.play( - ShowCreationThenFadeAround(big_m_array), - ShowCreationThenFadeAround(m_array), - ) - - # Before and after - ps_vect = self.ps_velocity_vector - theta = np.arctan(np.sqrt(self.block2.mass / self.block1.mass)) - self.theta = theta - - ps_vect.suspend_updating() - kwargs = {"about_point": ps_vect.get_start()} - for x in range(2): - for u in [-1, 1]: - ps_vect.rotate(u * 2 * theta, **kwargs) - self.update_mobjects(dt=0) - self.wait() - ps_vect.resume_updating() - - # Circle - circle = Circle( - radius=ps_vect.get_length(), - arc_center=ps_vect.get_start(), - color=RED, - stroke_width=1, - ) - self.play( - Rotating( - ps_vect, - about_point=ps_vect.get_start(), - run_time=5, - rate_func=lambda t: smooth(t, 3), - ), - FadeIn(circle), - ) - self.wait() - - self.ps_vect_circle = circle - - def show_same_angles(self): - # circle = self.ps_vect_circle - ps_vect = self.ps_velocity_vector - ps_point = self.ps_point - point = ps_point.get_center() - ghost_ps_vect = ps_vect.copy() - ghost_ps_vect.clear_updaters() - ghost_ps_vect.set_fill(opacity=0.5) - theta = self.theta - ghost_ps_vect.rotate( - -2 * theta, - about_point=ghost_ps_vect.get_start(), - ) - - arc1 = Arc( - start_angle=PI, - angle=theta, - arc_center=point, - radius=0.5, - color=WHITE, - ) - arc2 = arc1.copy() - arc2.rotate(theta, about_point=point) - arc3 = arc1.copy() - arc3.rotate(PI, about_point=point) - arc1.set_color(BLUE) - - line_pair = VGroup(*[ - Line(point, point + LEFT).rotate( - angle, about_point=point - ) - for angle in [0, theta] - ]) - line_pair.set_stroke(width=0) - - ps_vect.suspend_updating() - self.play( - ShowCreation(arc1), - FadeIn(ghost_ps_vect) - ) - self.wait() - self.play( - Rotate(ps_vect, 2 * theta, about_point=point) - ) - self.begin_sliding() - ps_vect.resume_updating() - self.play(GrowFromPoint(arc2, point)) - self.wait(0.5) - self.end_sliding() - self.play( - TransformFromCopy(arc1, arc3, path_arc=-PI), - Rotate(line_pair, -PI, about_point=point), - UpdateFromAlphaFunc( - line_pair, lambda m, a: m.set_stroke( - width=there_and_back(a)**0.5 - ) - ), - ) - # Show light beam along trajectory - self.show_trajectory_beam_bounce() - self.wait() - - # TODO: Add labels for angles? - - self.play(FadeOut(VGroup( - self.ps_vect_circle, ghost_ps_vect, arc1 - ))) - - def show_horizontal_bounce(self): - self.slide_until( - lambda: self.ps_velocity_vector.get_vector()[1] > 0 - ) - point = self.ps_point.get_location() - theta = self.theta - arc1 = Arc( - start_angle=0, - angle=2 * theta, - radius=0.5, - arc_center=point, - ) - arc2 = arc1.copy() - arc2.rotate(PI - 2 * theta, about_point=point) - arcs = VGroup(arc1, arc2) - - self.slide(0.5) - self.play(LaggedStartMap( - FadeInFromLarge, arcs, - lag_ratio=0.75, - )) - self.show_trajectory_beam_bounce() - - def let_process_play_out(self): - self.slide(self.wait_time) - self.wait(10) # Just to be sure... - - # - def show_trajectory_beam_bounce(self, n_times=2): - # Show light beam along trajectory - beam = self.trajectory.copy() - beam.clear_updaters() - beam.set_stroke(YELLOW, 3) - for x in range(n_times): - self.play(ShowPassingFlash( - beam, - run_time=2, - rate_func=bezier([0, 0, 1, 1]) - )) - - def get_momentum_equation(self): - equation = TexMobject( - "m_1", "v_1", "+", "m_2", "v_2", - "=", "\\text{const.}", - tex_to_color_map={ - "m_1": BLUE, - "m_2": BLUE, - "v_1": RED, - "v_2": RED, - } - ) - equation.to_edge(UP, buff=MED_SMALL_BUFF) - return equation - - def get_dot_product(self, - m1="\\sqrt{m_1}", m2="\\sqrt{m_2}", - d1="dx/dt", d2="dy/dt"): - sqrty_m = Matrix([[m1], [m2]]) - deriv_array = Matrix([[d1], [d2]]) - for entry in sqrty_m.get_entries(): - if "sqrt" in entry.get_tex_string(): - entry[2:].set_color(BLUE) - for matrix in sqrty_m, deriv_array: - matrix.add_to_back(BackgroundRectangle(matrix)) - matrix.get_brackets().scale(0.9) - matrix.set_height(1.25) - dot = TexMobject("\\cdot") - rhs = TexMobject("= \\text{const.}") - dot_product = VGroup( - sqrty_m, dot, deriv_array, rhs - ) - dot_product.arrange(RIGHT, buff=SMALL_BUFF) - return dot_product - - -class JustTheProcessNew(PositionPhaseSpaceScene): - CONFIG = { - "block1_config": { - "mass": 16, - "velocity": -2 - }, - "wait_time": 10, - } - - def setup(self): - super().setup() - self.add( - self.floor, - self.wall, - self.blocks, - self.axes, - self.d1_eq_d2_line, - self.d2_eq_w2_line, - ) - - def construct(self): - self.slide(self.wait_time) diff --git a/from_3b1b/old/clacks/solution2/simple_scenes.py b/from_3b1b/old/clacks/solution2/simple_scenes.py deleted file mode 100644 index 988ac98d..00000000 --- a/from_3b1b/old/clacks/solution2/simple_scenes.py +++ /dev/null @@ -1,877 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.lost_lecture import ShowWord -from from_3b1b.old.clacks.solution2.mirror_scenes import ReflectWorldThroughMirrorNew -from from_3b1b.old.clacks.question import Thumbnail - - -class WrapperScene(Scene): - CONFIG = { - "title": "Title", - "shade_of_grey": "#333333" - } - - def construct(self): - title = TextMobject(self.title) - title.scale(1.5) - title.to_edge(UP) - big_rect = self.get_big_rect() - screen_rect = self.get_screen_rect() - screen_rect.next_to(title, DOWN) - - self.add(big_rect, screen_rect) - self.play( - FadeIn(big_rect), - FadeInFrom(title, DOWN), - FadeInFrom(screen_rect, UP), - ) - self.wait() - - def get_big_rect(self): - big_rect = FullScreenFadeRectangle() - big_rect.set_fill(self.shade_of_grey, 1) - return big_rect - - def get_screen_rect(self, height=6): - screen_rect = ScreenRectangle(height=height) - screen_rect.set_fill(BLACK, 1) - return screen_rect - - -class ComingUpWrapper(WrapperScene): - CONFIG = {"title": "Coming up..."} - - -class LastVideoWrapper(WrapperScene): - CONFIG = {"title": "Last time..."} - - -class LeftEdge(Scene): - CONFIG = { - "text": "Left edge", - "vect": LEFT, - } - - def construct(self): - words = TextMobject(self.text) - arrow = Vector(self.vect) - arrow.match_width(words) - arrow.next_to(words, DOWN) - - self.play( - FadeInFromDown(words), - GrowArrow(arrow) - ) - self.wait() - - -class RightEdge(LeftEdge): - CONFIG = { - "text": "Right edge", - "vect": RIGHT, - } - - -class NoteOnEnergyLostToSound(Scene): - def construct(self): - self.add(TextMobject( - "Yeah yeah, the clack sound\\\\" - "would require energy, but\\\\" - "don't let accuracy get in the\\\\" - "way of delight!", - alignment="", - )) - - -class DotProductVideoWrapper(WrapperScene): - CONFIG = {"title": "Dot product"} - - -class Rectangle(Scene): - def construct(self): - rect = ScreenRectangle(height=FRAME_HEIGHT - 0.25) - rect.set_stroke(WHITE, 6) - self.add(rect) - - -class ShowRectangleCreation(Scene): - def construct(self): - rect = ScreenRectangle(height=2) - rect.set_stroke(YELLOW, 6) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - - -class ShowDotProductMeaning(Scene): - def construct(self): - v_vect = Vector(2 * RIGHT, color=YELLOW) - w_vect = Vector(3 * RIGHT, color=PINK) - dot = Dot(color=RED) - dot.shift(DOWN) - - v_vect.angle_tracker = ValueTracker() - w_vect.angle_tracker = ValueTracker() - - def update_vect(vect): - target = vect.angle_tracker.get_value() - vect.rotate(target - vect.get_angle()) - vect.shift(dot.get_center() - vect.get_start()) - - v_vect.add_updater(update_vect) - w_vect.add_updater(update_vect) - - v_label = TexMobject("\\vec{\\textbf{v}}") - v_label.vect = v_vect - w_label = TexMobject("\\vec{\\textbf{w}}") - w_label.vect = w_vect - for label in v_label, w_label: - label.match_color(label.vect) - label.set_stroke(BLACK, 5, background=True) - - def update_label(label): - target = np.array(label.vect.get_end()) - target += 0.25 * normalize(label.vect.get_vector()) - label.move_to(target) - - v_label.add_updater(update_label) - w_label.add_updater(update_label) - - title = TexMobject( - "\\vec{\\textbf{w}}", - "\\cdot", - "\\vec{\\textbf{v}}", - "=", - "||", "\\vec{\\textbf{w}}", "||", - "\\cdot", - "||", "\\vec{\\textbf{v}}", "||", - "\\cdot", - "\\cos(\\theta)" - ) - title.set_color_by_tex_to_color_map({ - "textbf{v}": v_vect.get_color(), - "textbf{w}": w_vect.get_color(), - }) - title.to_edge(UP) - - def get_w_line(): - center = dot.get_center() - direction = w_vect.get_vector() - return Line( - center - 3 * direction, - center + 3 * direction, - stroke_color=LIGHT_GREY, - stroke_width=1, - ) - w_line = always_redraw(get_w_line) - - def get_proj_v(): - center = dot.get_center() - v = v_vect.get_vector() - w = w_vect.get_vector() - w_unit = normalize(w) - result = Vector(np.dot(v, w_unit) * w_unit) - result.set_fill(v_vect.get_color(), 0.5) - result.shift(center - result.get_start()) - return result - proj_v = always_redraw(get_proj_v) - - def get_proj_line(): - return DashedLine( - v_vect.get_end(), - proj_v.get_end(), - stroke_width=1, - dash_length=0.025, - ) - proj_line = always_redraw(get_proj_line) - - template_line = Line(LEFT, RIGHT) - - def get_vect_brace(vect): - brace = Brace(template_line, UP, buff=SMALL_BUFF) - brace.set_width(vect.get_length(), stretch=True) - angle = vect.get_angle() % TAU - if angle < PI: - angle += PI - brace.rotate(angle, about_point=ORIGIN) - brace.shift(vect.get_center()) - return brace - w_brace = always_redraw( - lambda: get_vect_brace(w_vect) - ) - proj_v_brace = always_redraw( - lambda: get_vect_brace(proj_v) - ) - - def get_arc(): - center = dot.get_center() - a1 = w_vect.get_angle() - a2 = v_vect.get_angle() - arc = Arc( - start_angle=a1, - angle=a2 - a1, - radius=0.5, - arc_center=center, - ) - theta = TexMobject("\\theta") - p = arc.point_from_proportion(0.5) - theta.move_to( - center + 1.5 * (p - center) - ) - return VGroup(arc, theta) - arc = always_redraw(get_arc) - - self.add( - title[:3], - w_vect, v_vect, dot, - w_label, v_label, - ) - self.play( - v_vect.angle_tracker.set_value, 170 * DEGREES, - w_vect.angle_tracker.set_value, 45 * DEGREES, - run_time=2, - ) - self.wait() - w_brace.update() - self.play( - GrowFromCenter(w_brace), - Write(title[3:7]) - ) - - self.add(w_line, w_vect, w_label, dot) - self.play(ShowCreation(w_line)) - proj_v.update() - self.play( - ShowCreation(proj_line), - TransformFromCopy(v_vect, proj_v), - ) - self.add(proj_v, proj_line, dot) - proj_v_brace.update() - self.play( - GrowFromCenter(proj_v_brace), - FadeInFromDown(title[7:]) - ) - arc.update() - self.play(Write(arc)) - self.wait() - - for angle in [135, 225, 270, 90, 150]: - self.play( - v_vect.angle_tracker.set_value, angle * DEGREES, - run_time=2 - ) - self.wait() - - -class FinalComment(Scene): - def construct(self): - self.add(TextMobject( - "Thoughts on what ending should go here?\\\\" - "See the Patreon post." - )) - - -class FourtyFiveDegreeLine(Scene): - CONFIG = { - "angle": 45 * DEGREES, - "label_config": { - "num_decimal_places": 0, - "unit": "^\\circ", - "label_height": 0.3, - }, - "degrees": True - } - - def construct(self): - angle = self.angle - arc = Arc(angle, radius=1) - label = DecimalNumber(0, **self.label_config) - label.set_height(self.label_config["label_height"]) - label.next_to(arc, RIGHT) - label.shift(0.5 * SMALL_BUFF * UP) - line1 = Line(ORIGIN, 3 * RIGHT) - line2 = line1.copy() - - if self.degrees: - target_value = int(angle / DEGREES) - else: - target_value = angle - - self.add(line1, label) - self.play( - ChangeDecimalToValue(label, target_value), - ShowCreation(arc), - Rotate(line2, angle, about_point=ORIGIN) - ) - self.wait() - - -class ArctanSqrtPoint1Angle(FourtyFiveDegreeLine): - CONFIG = { - "angle": np.arctan(np.sqrt(0.1)), - } - - -class AskAboutAddingThetaToItself(Scene): - CONFIG = { - "theta": np.arctan(0.25), - "wait_time": 0.25, - "wedge_radius": 3, - "theta_symbol_scale_val": 0.5, - "number_height": 0.2, - } - - def construct(self): - theta = self.theta - groups = self.get_groups(theta) - horizon = self.get_horizon() - counter = ValueTracker(0) - dynamic_ineq = self.get_dynamic_inequality(counter) - semicircle = self.get_semicircle() - - self.add(horizon) - self.add(dynamic_ineq) - - for n in range(len(groups)): - counter.set_value(n + 1) - if n < len(groups) - 1: - groups[n][-1].set_color(YELLOW) - if n > 0: - groups[n - 1][-1].set_color(WHITE) - self.add(groups[:n + 1]) - self.add_sound("pen_click", gain=-20) - self.wait(self.wait_time) - self.wait(0.5) - - counter.set_value(counter.get_value() - 1) - self.remove(groups[-1]) - self.add_sound("pen_click", gain=-20) - self.wait() - - self.play(ShowCreation(semicircle)) - self.play(FadeOut(semicircle)) - - self.wait(3) - - def get_group(self, theta): - # Define group - wedge_radius = self.wedge_radius - wedge = VGroup( - Line(ORIGIN, wedge_radius * RIGHT), - Line(ORIGIN, wedge_radius * RIGHT).rotate( - theta, about_point=ORIGIN - ), - ) - wedge.set_stroke((WHITE, GREY), 2) - arc = Arc(theta, radius=1) - theta_symbol = TexMobject("\\theta") - tssv = self.theta_symbol_scale_val - theta_symbol.scale(tssv) - theta_symbol.next_to(arc, RIGHT, tssv / 2) - theta_symbol.shift(tssv * SMALL_BUFF * UP) - - return VGroup(wedge, arc, theta_symbol) - - def get_groups(self, theta): - group = self.get_group(theta) - angles = [k * theta for k in range(int(PI / theta) + 1)] - groups = VGroup(*[ - group.copy().rotate(angle, about_point=ORIGIN) - for angle in angles - ]) - # colors = it.cycle([BLUE_D, BLUE_B, BLUE_C, GREY_BROWN]) - colors = it.cycle([BLUE_D, GREY_BROWN]) - for n, angle, group, color in zip(it.count(1), angles, groups, colors): - wedge, arc, symbol = group - symbol.rotate(-angle) - arc.set_color(color) - number = Integer(n) - number.set_height(self.number_height) - number.move_to(center_of_mass([ - wedge[0].get_end(), - wedge[1].get_end(), - ])) - group.add(number) - groups[-1][-1].set_color(RED) - - return groups - - def get_horizon(self): - horizon = DashedLine(5 * LEFT, 5 * RIGHT) - horizon.set_stroke(WHITE, 1) - return horizon - - def get_semicircle(self): - return Arc( - start_angle=0, - angle=PI, - radius=self.wedge_radius / 2, - color=YELLOW, - stroke_width=4, - ) - - def get_inequality(self): - ineq = TexMobject( - "N", "\\cdot", "\\theta", "<", - "\\pi", "=", "3.1415926\\dots" - ) - N = ineq.get_part_by_tex("N") - self.pi_symbol = ineq.get_part_by_tex("\\pi") - N.set_color(YELLOW) - # ineq[-3:].set_color(BLUE) - - brace = Brace(N, UP, buff=SMALL_BUFF) - text = brace.get_text("Maximum", buff=SMALL_BUFF) - group = VGroup(ineq, brace, text) - group.next_to(ORIGIN, DOWN, MED_LARGE_BUFF) - return group - - def get_dynamic_inequality(self, counter): - multiple = Integer(0) - dot = TexMobject("\\cdot") - theta_tex = TexMobject("({:.2f})".format(self.theta)) - eq = TexMobject("=") - value = DecimalNumber(0) - ineq = TexMobject("<") - pi = TexMobject("\\pi", "=", "3.1415926\\dots") - # pi.set_color(BLUE) - group = VGroup( - multiple, dot, theta_tex, - eq, value, - ineq, pi - ) - group.arrange(RIGHT, buff=0.2) - group.next_to(ORIGIN, DOWN, buff=LARGE_BUFF) - theta_brace = Brace(group[2], DOWN, buff=SMALL_BUFF) - theta_symbol = theta_brace.get_tex("\\theta") - group.add(theta_brace, theta_symbol) - # group.align_to(self.pi_symbol, RIGHT) - - def get_count(): - return int(counter.get_value()) - - def get_product(): - return get_count() * self.theta - - def is_greater_than_pi(): - return get_product() > PI - - def get_color(): - return RED if is_greater_than_pi() else YELLOW - - def get_ineq(): - result = TexMobject( - ">" if is_greater_than_pi() else "<" - ) - result.set_color(get_color()) - result.move_to(ineq) - return result - dynamic_ineq = always_redraw(get_ineq) - group.remove(ineq) - group.add(dynamic_ineq) - - multiple.add_updater(lambda m: m.set_value(get_count())) - multiple.add_updater(lambda m: m.next_to(dot, LEFT, 0.2)) - multiple.add_updater(lambda m: m.set_color(get_color())) - value.add_updater(lambda m: m.set_value(get_product())) - - return group - - -class AskAboutAddingThetaToItselfThetaPoint1(AskAboutAddingThetaToItself): - CONFIG = { - "theta": 0.1, - "wait_time": 0.1, - "theta_symbol_scale_val": 0.25, - "number_height": 0.15, - } - - -class AskAboutAddingThetaToItselfThetaPoint2(AskAboutAddingThetaToItself): - CONFIG = { - "theta": 0.2, - "wait_time": 0.1, - } - - -class FinalFormula(Scene): - def construct(self): - text = TextMobject("Final answer: ") - t2c_map = { - "\\theta": BLUE, - "m_1": GREEN, - "m_2": RED, - } - - formula = TexMobject( - "\\left\\lfloor", - "{\\pi", "\\over", "\\theta}", - "\\right\\rfloor" - ) - formula.set_color_by_tex_to_color_map(t2c_map) - group = VGroup(text, formula) - group.arrange(RIGHT) - group.scale(1.5) - group.to_edge(UP) - - self.play(Write(text)) - self.play(FadeInFrom(formula)) - self.play(ShowCreationThenFadeAround(formula)) - self.wait() - - theta_eq = TexMobject( - "\\theta", "=", "\\arctan", "\\left(", - "\\sqrt", - "{{m_2", "\\over", "m_1}}", - "\\right)" - ) - theta_eq.set_color_by_tex_to_color_map(t2c_map) - theta_eq.scale(1.5) - theta_eq.next_to(group, DOWN, MED_LARGE_BUFF) - - self.play(TransformFromCopy( - formula.get_part_by_tex("\\theta"), - theta_eq.get_part_by_tex("\\theta"), - )) - self.play(Write(theta_eq[1:])) - self.wait() - - -class ReviewWrapper(WrapperScene): - CONFIG = {"title": "To review:"} - - -class SurprisedRandy(Scene): - def construct(self): - randy = Randolph() - self.play(randy.change, "surprised", 3 * UR) - self.play(Blink(randy)) - self.play(randy.change, "confused") - self.play(Blink(randy)) - self.wait() - - -class TwoSolutionsWrapper(WrapperScene): - def construct(self): - big_rect = self.get_big_rect() - screen_rects = VGroup(*[ - self.get_screen_rect(height=3) - for x in range(2) - ]) - screen_rects.arrange(RIGHT, buff=LARGE_BUFF) - title = TextMobject("Two solutions") - title.scale(1.5) - title.to_edge(UP) - screen_rects.next_to(title, DOWN, LARGE_BUFF) - - # pi creatures - pis = VGroup( - Randolph(color=BLUE_D), - Randolph(color=BLUE_E), - Randolph(color=BLUE_B), - Mortimer().scale(1.2) - ) - pis.set_height(2) - pis.arrange(RIGHT, buff=MED_LARGE_BUFF) - pis.to_edge(DOWN, buff=SMALL_BUFF) - - self.add(big_rect, title, pis) - self.play( - LaggedStartMap( - ShowCreation, screen_rects.copy().set_fill(opacity=0), - lag_ratio=0.8 - ), - LaggedStartMap( - FadeIn, screen_rects, - lag_ratio=0.8 - ), - LaggedStartMap( - ApplyMethod, pis, - lambda pi: (pi.change, "pondering", screen_rects[0]) - ), - ) - self.play(Blink(random.choice(pis))) - self.play(LaggedStartMap( - ApplyMethod, pis, - lambda pi: (pi.change, "thinking", screen_rects[1]) - )) - self.play(Blink(random.choice(pis))) - self.wait() - - -class FinalQuote(Scene): - def construct(self): - quote_text = """ - A change of perspective\\\\ - is worth 80 IQ points. - """ - quote_parts = [s for s in quote_text.split(" ") if s] - quote = TextMobject( - *quote_parts, - ) - quote.scale(1.2) - quote.shift(2 * RIGHT + UP) - - image = ImageMobject("AlanKay") - image.set_height(6) - image.to_corner(UL) - image.shift(2 * LEFT + 0.5 * UP) - name = TextMobject("Alan Kay") - name.scale(1.5) - name.next_to(image, DOWN) - name.shift_onto_screen() - - self.play( - FadeInFromDown(image), - Write(name), - ) - self.wait() - - for word in quote: - self.play(ShowWord(word)) - self.wait(0.005 * len(word)**1.5) - self.wait() - - -class EndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "1stViewMaths", - "Adam Kozak", - "Adrian Robinson", - "Alexis Olson", - "Ali Yahya", - "Andreas Benjamin Brössel", - "Andrew Busey", - "Ankalagon", - "Antonio Juarez", - "Arjun Chakroborty", - "Art Ianuzzi", - "Arthur Zey", - "Awoo", - "Bernd Sing", - "Bob Sanderson", - "Boris Veselinovich", - "Brian Staroselsky", - "Britt Selvitelle", - "Burt Humburg", - "Chad Hurst", - "Charles Southerland", - "Chris Connett", - "Christian Kaiser", - "Clark Gaebel", - "Cooper Jones", - "D. Sivakumar", - "Danger Dai", - "Dave B", - "Dave Kester", - "dave nicponski", - "David Clark", - "David Gow", - "Delton Ding", - "Devarsh Desai", - "eaglle", - "emptymachine", - "Eric Younge", - "Eryq Ouithaqueue", - "Evan Phillips", - "Federico Lebron", - "Florian Chudigiewitsch", - "Giovanni Filippi", - "Graham", - "Hal Hildebrand", - "Hitoshi Yamauchi", - "J", - "j eduardo perez", - "Jacob Magnuson", - "Jameel Syed", - "James Hughes", - "Jan Pijpers", - "Jason Hise", - "Jeff Linse", - "Jeff Straathof", - "John Griffith", - "John Haley", - "John Shaughnessy", - "John V Wertheim", - "Jonathan Eppele", - "Jonathan Wilson", - "Jordan Scales", - "Joseph John Cox", - "Joseph Kelly", - "Juan Benet", - "Kai-Siang Ang", - "Kanan Gill", - "Kaustuv DeBiswas", - "L0j1k", - "Lee Redden", - "Linh Tran", - "Luc Ritchie", - "Ludwig Schubert", - "Lukas -krtek.net- Novy", - "Lukas Biewald", - "Magister Mugit", - "Magnus Dahlström", - "Magnus Lysfjord", - "Mark B Bahu", - "Mark Heising", - "Mathew Bramson", - "Mathias Jansson", - "Matt Langford", - "Matt Roveto", - "Matt Russell", - "Matthew Cocke", - "Mauricio Collares", - "Michael Faust", - "Michael Hardel", - "Mike Coleman", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nathan Jessurun", - "Nero Li", - "Omar Zrien", - "Owen Campbell-Moore", - "Peter Ehrnstrom", - "Peter Mcinerney", - "Quantopian", - "Randy C. Will", - "Richard Barthel", - "Richard Burgmann", - "Richard Comish", - "Ripta Pasay", - "Rish Kundalia", - "Robert Teed", - "Roobie", - "Roy Larson", - "Ryan Atallah", - "Ryan Williams", - "Samuel D. Judge", - "Scott Gray", - "Scott Walter, Ph.D.", - "Sindre Reino Trosterud", - "soekul", - "Solara570", - "Song Gao", - "Stevie Metke", - "Ted Suzman", - "Tihan Seale", - "Valeriy Skobelev", - "Vassili Philippov", - "Xavier Bernard", - "Yana Chernobilsky", - "Yaw Etse", - "YinYangBalance.Asia", - "Yu Jun", - "Zach Cardwell", - ], - } - - -class ClacksSolution2Thumbnail(Scene): - def construct(self): - self.add_scene1() - self.add_scene2() - - arrow = Arrow( - self.block_word.get_top(), - self.light_dot.get_bottom(), - tip_length=0.75, - rectangular_stem_width=0.2, - color=RED, - buff=0.5, - ) - arrow.add_to_back(arrow.copy().set_stroke(BLACK, 20)) - self.add(arrow) - return - - arrow = TexMobject("\\Updownarrow") - arrow.set_height(2) - arrow.set_color(YELLOW) - arrow.set_stroke(Color("red"), 2, background=True) - self.add(arrow) - - def add_scene1(self): - scene1 = Thumbnail( - sliding_blocks_config={ - "block1_config": { - "label_text": "$100^{d}$ kg", - "distance": 8, - }, - } - ) - group = Group(*scene1.mobjects) - group.scale(0.75, about_point=ORIGIN) - group.shift(1.5 * DOWN + 3 * LEFT) - scene1.remove(scene1.question) - self.add(*scene1.mobjects) - - black_rect = FullScreenFadeRectangle(fill_opacity=1) - black_rect.shift(3.5 * UP) - self.add(black_rect) - - word = TextMobject("Blocks") - word.set_color(YELLOW) - word.scale(3) - word.to_corner(DR, buff=LARGE_BUFF) - word.shift(0.5 * LEFT) - self.block_word = word - self.add(word) - - def add_scene2(self): - scene2 = ReflectWorldThroughMirrorNew( - skip_animations=True, - file_writer_config={ - "write_to_movie": False, - }, - end_at_animation_number=18, - # center=1.5 * DOWN, - center=ORIGIN, - ) - worlds = VGroup(scene2.world, *scene2.reflected_worlds) - mirrors = VGroup(*[rw[1] for rw in worlds]) - mirrors.set_stroke(width=5) - triangles = VGroup(*[rw[0] for rw in worlds]) - trajectories = VGroup( - scene2.trajectory, - *scene2.reflected_trajectories - ) - trajectories.set_stroke(YELLOW, 1) - - beams1, beams2 = [ - scene2.get_shooting_beam_anims( - path, - max_stroke_width=20, - max_time_width=1, - num_flashes=50, - ) - for path in [ - scene2.trajectory, - scene2.ghost_trajectory, - ] - ] - beams = beams1 + beams2 - beams = beams2 - flashes = VGroup() - for beam in beams: - beam.update(0.5) - flashes.add(beam.mobject) - - dot = self.light_dot = Dot(color=YELLOW, radius=0.1) - dot.move_to(flashes[0].get_left()) - flashes.add(dot) - - self.add(triangles, mirrors) - # self.add(randys) - self.add(trajectories[0].set_stroke(width=2)) - self.add(flashes) - - word = TextMobject("Light beam") - word.scale(3.5) - word.set_color(YELLOW) - # word.set_stroke(BLACK, 25, background=True) - word.add_to_back(word.copy().set_stroke( - BLACK, 25, - )) - word.next_to(dot, UR) - word.shift(-word.get_center()[0] * RIGHT) - word.shift(SMALL_BUFF * RIGHT) - self.light_word = word - self.add(word) diff --git a/from_3b1b/old/clacks/solution2/wordy_scenes.py b/from_3b1b/old/clacks/solution2/wordy_scenes.py deleted file mode 100644 index 39f880d0..00000000 --- a/from_3b1b/old/clacks/solution2/wordy_scenes.py +++ /dev/null @@ -1,318 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.clacks.solution2.position_phase_space import ShowMomentumConservation - - -class ConnectionToOptics(Scene): - def construct(self): - e_group, m_group = k_groups = self.get_kinematics_groups() - c_group, a_group = o_groups = self.get_optics_groups() - arrows = VGroup() - for g1, g2 in zip(k_groups, o_groups): - g2.align_to(g1, UP) - g2.to_edge(RIGHT) - arrow = TexMobject("\\Rightarrow") - arrow.scale(1.5) - arrow.move_to(interpolate( - g1[0].get_right(), g2[0].get_left(), 0.5 - )) - arrows.add(arrow) - everything = VGroup(k_groups, arrows, o_groups) - everything.to_edge(UP) - - everything.generate_target() - everything.target.scale(0.9) - everything.target.to_edge(DOWN) - width = max([m.get_width() for m in everything.target]) - width += 2 * MED_SMALL_BUFF - rects = VGroup() - for k in [0, 2]: - rect = DashedVMobject(Rectangle( - height=FRAME_HEIGHT - 1.5, - width=width - ), num_dashes=100) - rect.move_to(everything.target[k]) - rect.to_edge(DOWN, buff=SMALL_BUFF) - rects.add(rect) - titles = VGroup( - TextMobject("Kinematics"), - TextMobject("Optics"), - ) - titles.scale(1.5) - for title, rect in zip(titles, rects): - title.next_to(rect, UP) - titles[0].align_to(titles[1], UP) - - self.play(FadeInFromDown(e_group)) - self.play( - Write(arrows[0]), - FadeInFrom(c_group, LEFT) - ) - self.wait() - self.play(FadeInFromDown(m_group)) - self.play( - Write(arrows[1]), - FadeInFrom(a_group, LEFT) - ) - self.wait(4) - for k in range(2): - anims = [ - ShowCreation(rects[k]), - FadeInFromDown(titles[k]), - ] - if k == 0: - anims.append(MoveToTarget(everything)) - self.play(*anims) - self.wait() - self.wait() - self.wait(4) - - def get_kinematics_groups(self): - tex_to_color_map = { - "m_1": BLUE, - "m_2": BLUE, - "v_1": RED, - "v_2": RED, - } - energy_eq = TexMobject( - "\\frac{1}{2} m_1 (v_1)^2 + " - "\\frac{1}{2} m_2 (v_2)^2 = " - "\\text{const.}", - tex_to_color_map=tex_to_color_map - ) - energy_eq.scale(0.8) - momentum_eq = TexMobject( - "m_1 v_1 + m_2 v_2 = \\text{const.}", - tex_to_color_map=tex_to_color_map - ) - energy_label = TextMobject( - "Conservation of energy" - ) - momentum_label = TextMobject( - "Conservation of momentum" - ) - energy_group = VGroup(energy_label, energy_eq) - momentum_group = VGroup(momentum_label, momentum_eq) - groups = VGroup(energy_group, momentum_group) - for group in groups: - group.arrange(DOWN, buff=MED_LARGE_BUFF) - group[0].set_color(GREEN) - groups.arrange(DOWN, buff=2) - groups.to_edge(LEFT) - return groups - - def get_optics_groups(self): - self.time_tracker = ValueTracker(0) - self.time_tracker.add_updater( - lambda m, dt: m.increment_value(dt) - ) - self.add(self.time_tracker) - return VGroup( - self.get_speed_group(), - self.get_angle_group() - ) - - def get_speed_group(self): - speed_label = TextMobject("Constant speed of light") - speed_label.set_color(YELLOW) - speed_light_template = Line(LEFT, RIGHT) - speed_light_template.fade(1) - speed_light_template.match_width(speed_label) - speed_light_template.next_to(speed_label, DOWN, MED_LARGE_BUFF) - speed_light = speed_light_template.deepcopy() - - def update_speed_light(light, period=2, time_width=0.05): - time = self.time_tracker.get_value() - alpha = (time / period) % 1 - # alpha = 1 - 2 * abs(alpha - 0.5) - alpha *= 1.5 - a = alpha - time_width / 2 - b = alpha + time_width / 2 - light.pointwise_become_partial( - speed_light_template, max(a, 0), min(b, 1) - ) - opacity = speed_label.family_members_with_points()[0].get_fill_opacity() - light.set_stroke(YELLOW, width=3, opacity=opacity) - # light.stretch(0.5, 0) - # point = speed_light_template.point_from_proportion(0.25) - # light.stretch(2, 0, about_point=point) - - speed_light.add_updater(update_speed_light) - result = VGroup( - speed_label, speed_light_template, speed_light - ) - return result - - def get_angle_group(self): - title = VGroup(*map(TextMobject, [ - "Angle of\\\\Incidence", - "=", - "Angle of\\\\Reflection", - ])).arrange(RIGHT) - title.set_color(YELLOW) - h_line = Line(LEFT, RIGHT) - h_line.match_width(title) - h_line.set_stroke(LIGHT_GREY) - h_line.set_sheen(1, UL) - points = [ - h_line.get_left() + UP, - h_line.get_center(), - h_line.get_right() + UP, - ] - dashed_lines = VGroup( - DashedLine(*points[0:2]), DashedLine(*points[1:3]) - ) - dashed_lines.set_stroke(WHITE, 2) - v_shape = VMobject() - v_shape.set_points_as_corners(points) - v_shape.fade(1) - - theta = dashed_lines[1].get_angle() - arcs = VGroup( - Arc(start_angle=0, angle=theta), - Arc(start_angle=PI, angle=-theta), - ) - arcs.set_stroke(WHITE, 2) - thetas = VGroup() - for v in LEFT, RIGHT: - theta = TexMobject("\\theta") - theta.next_to(arcs, v, aligned_edge=DOWN) - theta.shift(SMALL_BUFF * UP) - thetas.add(theta) - - beam = VMobject() - - def update_beam(beam, period=2, time_width=0.05): - time = self.time_tracker.get_value() - alpha = (time / period) % 1 - alpha *= 1.5 - a = alpha - time_width / 2 - b = alpha + time_width / 2 - beam.pointwise_become_partial( - v_shape, max(a, 0), min(b, 1) - ) - opacity = title.family_members_with_points()[0].get_fill_opacity() - beam.set_stroke(YELLOW, width=3, opacity=opacity) - - beam.add_updater(update_beam) - title.next_to(v_shape, UP, MED_LARGE_BUFF) - - return VGroup( - title, h_line, arcs, thetas, - dashed_lines, v_shape, beam - ) - - -class ConnectionToOpticsTransparent(ConnectionToOptics): - pass - - -class RearrangeMomentumEquation(ShowMomentumConservation): - def setup(self): - pass # Don't build all the things - - def construct(self): - self.add(FullScreenFadeRectangle( - fill_color=BLACK, - fill_opacity=0.95, - )) - self.show_initial_dot_product() - self.show_with_x_and_y() - - def show_initial_dot_product(self): - equation = self.get_momentum_equation() - dot_product = self.get_dot_product( - "m_1", "m_2", "v_1", "v_2" - ) - dot_product.next_to(equation, DOWN, LARGE_BUFF) - m_array, dot, v_array, rhs = dot_product - m_array.get_entries().set_color(BLUE) - v_array.get_entries().set_color(RED) - - self.add(equation) - self.play(FadeInFromDown(VGroup( - m_array.get_brackets(), dot, - v_array.get_brackets(), rhs, - ))) - self.play(TransformFromCopy( - equation.get_parts_by_tex("m_"), - m_array.get_entries(), - )) - self.play(TransformFromCopy( - equation.get_parts_by_tex("v_"), - v_array.get_entries(), - )) - self.wait() - - self.simple_dot_product = dot_product - self.momentum_equation = equation - - def show_with_x_and_y(self): - simple_dot_product = self.simple_dot_product - momentum_equation = self.momentum_equation - - new_equation = TexMobject( - "\\sqrt{m_1}", - "\\left(", "\\sqrt{m_1}", "v_1", "\\right)", - "+", "\\sqrt{m_2}", - "\\left(", "\\sqrt{m_2}", "v_2", "\\right)", - "=", "\\text{const.}", - ) - new_equation.set_color_by_tex_to_color_map({ - "m_": BLUE, - "v_": RED, - }) - new_equation.next_to(momentum_equation, DOWN, MED_LARGE_BUFF) - - x_term = new_equation[1:5] - y_term = new_equation[7:11] - x_brace = Brace(x_term, DOWN) - y_brace = Brace(y_term, DOWN) - dx_dt = x_brace.get_tex("dx / dt") - dy_dt = y_brace.get_tex("dy / dt") - - new_eq_group = VGroup( - new_equation, x_brace, y_brace, dx_dt, dy_dt - ) - new_eq_group.generate_target() - - new_dot_product = self.get_dot_product() - m_array, dot, d_array, rhs = new_dot_product - new_dot_product.next_to(momentum_equation, DOWN) - new_eq_group.target.next_to(new_dot_product, DOWN, LARGE_BUFF) - - self.play( - FadeInFrom(new_equation, UP), - simple_dot_product.to_edge, DOWN, LARGE_BUFF, - ) - self.wait() - self.play( - GrowFromCenter(x_brace), - GrowFromCenter(y_brace), - FadeInFrom(dx_dt, UP), - FadeInFrom(dy_dt, UP), - ) - self.wait() - self.play( - FadeIn(VGroup( - m_array.get_brackets(), dot, - d_array.get_brackets(), rhs - )), - MoveToTarget(new_eq_group) - ) - self.play(TransformFromCopy( - VGroup( - VGroup(new_equation[0]), - VGroup(new_equation[6]), - ), - m_array.get_entries(), - )) - self.play(TransformFromCopy( - VGroup(dx_dt, dy_dt), - d_array.get_entries(), - )) - self.wait() - - -class NewSceneName(Scene): - def construct(self): - pass diff --git a/from_3b1b/old/complex_multiplication_article.py b/from_3b1b/old/complex_multiplication_article.py deleted file mode 100644 index f0819b2c..00000000 --- a/from_3b1b/old/complex_multiplication_article.py +++ /dev/null @@ -1,250 +0,0 @@ -#!/usr/bin/env python - -import numpy as np -import itertools as it -from copy import deepcopy -import sys - -from manimlib.imports import * -from functools import reduce - -DEFAULT_PLANE_CONFIG = { - "stroke_width" : 2*DEFAULT_STROKE_WIDTH - } - - -class SuccessiveComplexMultiplications(ComplexMultiplication): - args_list = [ - (complex(1, 2), complex(1, -2)), - (complex(-2, 1), complex(-2, -1)), - ] - - @staticmethod - def args_to_string(*multipliers): - return "_".join([str(m)[1:-1] for m in multipliers]) - - @staticmethod - def string_to_args(arg_string): - args_string.replac("i", "j") - return list(map(copmlex, arg_string.split())) - - def construct(self, *multipliers): - norm = abs(reduce(op.mul, multipliers, 1)) - shrink_factor = FRAME_X_RADIUS/max(FRAME_X_RADIUS, norm) - plane_config = { - "density" : norm*DEFAULT_POINT_DENSITY_1D, - "unit_to_spatial_width" : shrink_factor, - "x_radius" : shrink_factor*FRAME_X_RADIUS, - "y_radius" : shrink_factor*FRAME_Y_RADIUS, - } - ComplexMultiplication.construct(self, multipliers[0], **plane_config) - - one_dot = self.draw_dot("1", 1, True) - one_dot_copy = deepcopy(one_dot) - - for multiplier, count in zip(multipliers, it.count()): - if multiplier == multipliers[0]: - tex = "z" - elif np.conj(multiplier) == multipliers[0]: - tex = "\\bar z" - else: - tex = "z_%d"%count - self.draw_dot(tex, multiplier) - - for multiplier in multipliers: - self.multiplier = multiplier - self.apply_multiplication() - new_one = deepcopy(one_dot_copy) - self.mobjects_to_move_without_molding.append(new_one) - - - -class ShowComplexPower(SuccessiveComplexMultiplications): - args_list = [ - (complex(0, 1), 1), - (complex(0, 1), 2), - (np.exp(complex(0, 2*np.pi/5)), 1), - (np.exp(complex(0, 2*np.pi/5)), 5), - (np.exp(complex(0, 4*np.pi/5)), 5), - (np.exp(complex(0, -2*np.pi/5)), 5), - (complex(1, np.sqrt(3)), 1), - (complex(1, np.sqrt(3)), 3), - ] - - @staticmethod - def args_to_string(multiplier, num_repeats): - start = ComplexMultiplication.args_to_string(multiplier) - return start + "ToThe%d"%num_repeats - - @staticmethod - def string_to_args(arg_string): - parts = arg_string.split() - if len(parts) < 2 or len(parts) > 3: - raise Exception("Invalid arguments") - multiplier = complex(parts[0]) - num_repeats = int(parts[1]) - return multiplier, num_repeats - - def construct(self, multiplier, num_repeats): - SuccessiveComplexMultiplications.construct( - [multiplier]*num_repeats - ) - - -class ComplexDivision(ComplexMultiplication): - args_list = [ - complex(np.sqrt(3), 1), - complex(1./3, -1./3), - complex(1, 2), - ] - - def construct(self, num): - ComplexMultiplication.construct(self, 1./num) - self.draw_dot("1", 1, False), - self.draw_dot("z", num, True) - self.apply_multiplication() - -class ConjugateDivisionExample(ComplexMultiplication): - args_list = [ - complex(1, 2), - ] - - def construct(self, num): - ComplexMultiplication.construct(self, np.conj(num), radius = 2.5*FRAME_X_RADIUS) - self.draw_dot("1", 1, True) - self.draw_dot("\\bar z", self.multiplier) - self.apply_multiplication() - self.multiplier = 1./(abs(num)**2) - self.anim_config["path_func"] = straight_path - self.apply_multiplication() - self.wait() - -class DrawSolutionsToZToTheNEqualsW(Scene): - @staticmethod - def args_to_string(n, w): - return str(n) + "_" + complex_string(w) - - @staticmethod - def string_to_args(args_string): - parts = args_string.split() - return int(parts[0]), complex(parts[1]) - - def construct(self, n, w): - w = complex(w) - plane_config = DEFAULT_PLANE_CONFIG.copy() - norm = abs(w) - theta = np.log(w).imag - radius = norm**(1./n) - zoom_value = (FRAME_Y_RADIUS-0.5)/radius - plane_config["unit_to_spatial_width"] = zoom_value - plane = ComplexPlane(**plane_config) - circle = Circle( - radius = radius*zoom_value, - stroke_width = plane.stroke_width - ) - solutions = [ - radius*np.exp(complex(0, 1)*(2*np.pi*k + theta)/n) - for k in range(n) - ] - points = list(map(plane.number_to_point, solutions)) - dots = [ - Dot(point, color = BLUE_B, radius = 0.1) - for point in points - ] - lines = [Line(*pair) for pair in adjacent_pairs(points)] - - self.add(plane, circle, *dots+lines) - self.add(*plane.get_coordinate_labels()) - - -class DrawComplexAngleAndMagnitude(Scene): - args_list = [ - ( - ("1+i\\sqrt{3}", complex(1, np.sqrt(3)) ), - ("\\frac{\\sqrt{3}}{2} - \\frac{1}{2}i", complex(np.sqrt(3)/2, -1./2)), - ), - (("1+i", complex(1, 1)),), - ] - @staticmethod - def args_to_string(*reps_and_nums): - return "--".join([ - complex_string(num) - for rep, num in reps_and_nums - ]) - - def construct(self, *reps_and_nums): - radius = max([abs(n.imag) for r, n in reps_and_nums]) + 1 - plane_config = { - "color" : "grey", - "unit_to_spatial_width" : FRAME_Y_RADIUS / radius, - } - plane_config.update(DEFAULT_PLANE_CONFIG) - self.plane = ComplexPlane(**plane_config) - coordinates = self.plane.get_coordinate_labels() - # self.plane.add_spider_web() - self.add(self.plane, *coordinates) - for rep, num in reps_and_nums: - self.draw_number(rep, num) - self.add_angle_label(num) - self.add_lines(rep, num) - - def draw_number(self, tex_representation, number): - point = self.plane.number_to_point(number) - dot = Dot(point) - label = TexMobject(tex_representation) - max_width = 0.8*self.plane.unit_to_spatial_width - if label.get_width() > max_width: - label.set_width(max_width) - dot_to_label_dir = RIGHT if point[0] > 0 else LEFT - edge = label.get_edge_center(-dot_to_label_dir) - buff = 0.1 - label.shift(point - edge + buff*dot_to_label_dir) - label.set_color(YELLOW) - - self.add_mobjects_among(list(locals().values())) - - - def add_angle_label(self, number): - arc = Arc( - np.log(number).imag, - radius = 0.2 - ) - - self.add_mobjects_among(list(locals().values())) - - def add_lines(self, tex_representation, number): - point = self.plane.number_to_point(number) - x_line, y_line, num_line = [ - Line( - start, end, - color = color, - stroke_width = self.plane.stroke_width - ) - for start, end, color in zip( - [ORIGIN, point[0]*RIGHT, ORIGIN], - [point[0]*RIGHT, point, point], - [BLUE_D, GOLD_E, WHITE] - ) - ] - # tex_representation.replace("i", "") - # if "+" in tex_representation: - # tex_parts = tex_representation.split("+") - # elif "-" in tex_representation: - # tex_parts = tex_representation.split("-") - # x_label, y_label = map(TexMobject, tex_parts) - # for label in x_label, y_label: - # label.set_height(0.5) - # x_label.next_to(x_line, point[1]*DOWN/abs(point[1])) - # y_label.next_to(y_line, point[0]*RIGHT/abs(point[0])) - norm = get_norm(point) - brace = Underbrace(ORIGIN, ORIGIN+norm*RIGHT) - if point[1] > 0: - brace.rotate(np.pi, RIGHT) - brace.rotate(np.log(number).imag) - norm_label = TexMobject("%.1f"%abs(number)) - norm_label.scale(0.5) - axis = OUT if point[1] > 0 else IN - norm_label.next_to(brace, rotate_vector(point, np.pi/2, axis)) - - self.add_mobjects_among(list(locals().values())) - diff --git a/from_3b1b/old/counting_in_binary.py b/from_3b1b/old/counting_in_binary.py deleted file mode 100644 index 2730f348..00000000 --- a/from_3b1b/old/counting_in_binary.py +++ /dev/null @@ -1,478 +0,0 @@ -#!/usr/bin/env python - - -import numpy as np -import itertools as it -from copy import deepcopy -import sys - -from manimlib.imports import * -from script_wrapper import command_line_create_scene - -MOVIE_PREFIX = "counting_in_binary/" -BASE_HAND_FILE = os.path.join(VIDEO_DIR, MOVIE_PREFIX, "Base.mp4") -FORCED_FRAME_DURATION = 0.02 -TIME_RANGE = (0, 42) -INITIAL_PADDING = 27 -NUM_GOOD_FRAMES = 1223 - -ALGORITHM_TEXT = [ - """ - \\begin{flushleft} - Turn up the rightmost finger that is down. - """, """ - Turn down all fingers to its right. - \\end{flushleft} - """ -] -FINGER_WORDS = [ - "Thumb", - "Index Finger", - "Middle Finger", - "Ring Finger", - "Pinky", -] - -COUNT_TO_FRAME_NUM = { - 0 : 0, - 1 : 27, - 2 : 76, - 3 : 110, - 4 : 163, - 5 : 189, - 6 : 226, - 7 : 264, - 8 : 318, - 9 : 356, - 10 : 384, - 11 : 423, - 12 : 457, - 13 : 513, - 14 : 528, - 15 : 590, - 16 : 620, - 17 : 671, - 18 : 691, - 19 : 740, - 20 : 781, - 21 : 810, - 22 : 855, - 23 : 881, - 24 : 940, - 25 : 970, - 26 : 1014, - 27 : 1055, - 28 : 1092, - 29 : 1143, - 30 : 1184, - 31 : 1219, -} - -COUNT_TO_TIP_POS = { - 0 : [5.0, 0.5, 0.0], - 1 : [3.1, 2.5, 0.0], - 2 : [1.5, 2.3, 0.0], - 3 : [0.7, 2.0, 0.0], - 4 : [0.0, 1.0, 0.0], -} - -def finger_tip_power_of_2(finger_no): - return TexMobject(str(2**finger_no)).shift(COUNT_TO_TIP_POS[finger_no]) - -class Hand(ImageMobject): - STARTING_BOTTOM_RIGHT = [4.61111111e+00, -3.98888889e+00, 9.80454690e-16] - def __init__(self, num, small = False, **kwargs): - Mobject2D.__init__(self, **kwargs) - path = os.path.join( - VIDEO_DIR, MOVIE_PREFIX, "images", "Hand%d.png"%num - ) - invert = False - if not self.read_in_cached_attrs(path, invert): - ImageMobject.__init__(self, path, invert = invert) - center = self.get_center() - self.center() - self.rotate(np.pi, axis = RIGHT+UP) - self.sort_points(lambda p : np.log(complex(*p[:2])).imag) - self.rotate(np.pi, axis = RIGHT+UP) - self.shift(center) - self.cache_attrs(path, invert = False) - self.shift(self.STARTING_BOTTOM_RIGHT-self.get_boundary_point(DOWN+RIGHT)) - if small: - self.shrink() - - def shrink(self): - self.scale_in_place(0.8).to_edge(DOWN, buff = 0.0) - - # def set_color_thumb(self, color = "yellow"): - # self.set_color( - # color = color, - # condition = lambda p : p[0] > 4.5 and p[1] > -1.5 - # ) - -def get_algorithm(): - return TextMobject(ALGORITHM_TEXT) - -def get_finger_colors(): - return list(Color("yellow").range_to("red", 5)) - -def five_char_binary(num): - result = bin(num)[2:] - return (5-len(result))*"0" + result - -def read_reversed_binary(string): - return sum([ - 2**count if char == '1' else 0 - for count, char in zip(it.count(), string) - ]) - -class LeftHand(Hand): - def __init__(self, num, **kwargs): - Hand.__init__( - self, - read_reversed_binary(five_char_binary(num)), - **kwargs - ) - self.rotate(np.pi, UP) - self.shift(LEFT) - -def get_hand_map(which_hand = "right"): - if which_hand == "right": - Class = Hand - elif which_hand == "left": - Class = LeftHand - else: - print("Bad arg, bro") - return - return dict([ - (num, Class(num, small=True)) - for num in range(32) - ]) - -class OverHand(SceneFromVideo): - def construct(self): - SceneFromVideo.construct(self, BASE_HAND_FILE) - self.frame_duration = FORCED_FRAME_DURATION - self.frames = self.frames[:NUM_GOOD_FRAMES] - - -class SaveEachNumber(OverHand): - def construct(self): - OverHand.construct(self) - for count in COUNT_TO_FRAME_NUM: - path = os.path.join( - VIDEO_DIR, MOVIE_PREFIX, "images", - "Hand%d.png"%count - ) - Image.fromarray(self.frames[COUNT_TO_FRAME_NUM[count]]).save(path) - - def write_to_movie(self, name = None): - print("Why bother writing to movie...") - -class ShowCounting(OverHand): - def construct(self): - OverHand.construct(self) - self.frames = INITIAL_PADDING*[self.frames[0]] + self.frames - num_frames = len(self.frames) - self.frames = [ - disp.paint_mobject( - self.get_counting_mob(32*count // num_frames), - frame - ) - for frame, count in zip(self.frames, it.count()) - ] - - def get_counting_mob(self, count): - mob = TexMobject(str(count)) - mob.scale(2) - mob.shift(LEFT) - mob.to_edge(UP, buff = 0.1) - return mob - - -class ShowFrameNum(OverHand): - def construct(self): - OverHand.construct(self) - for frame, count in zip(self.frames, it.count()): - print(count + "of" + len(self.frames)) - mob = Mobject(*[ - TexMobject(char).shift(0.3*x*RIGHT) - for char, x, in zip(str(count), it.count()) - ]) - self.frames[count] = disp.paint_mobject( - mob.to_corner(UP+LEFT), - frame - ) - -class CountTo1023(Scene): - def construct(self): - rh_map = get_hand_map("right") - lh_map = get_hand_map("left") - def get_num(count): - return Mobject(*[ - TexMobject(char).shift(0.35*x*RIGHT) - for char, x, in zip(str(count), it.count()) - ]).center().to_edge(UP) - self.frames = [ - disp.paint_mobject(Mobject( - rh_map[count%32], lh_map[count//32], get_num(count) - )) - for count in range(2**10) - ] - -class Introduction(Scene): - def construct(self): - words = TextMobject(""" - First, let's see how to count - to 31 on just one hand... - """) - hand = Hand(0) - for mob in words, hand: - mob.sort_points(lambda p : p[0]) - - self.add(words) - self.wait() - self.play(DelayByOrder(Transform(words, hand))) - self.wait() - - -class ShowReadingRule(Scene): - def construct(self): - sample_counts = [6, 17, 27, 31] - question = TextMobject(""" - How do you recognize what number a given configuration represents? - """, size = "\\Huge").scale(0.75).to_corner(UP+LEFT) - answer = TextMobject([ - "Think of each finger as representing a power of 2, ", - "then add up the numbers represented by the standing fingers." - ], size = "\\Huge").scale(0.75).to_corner(UP+LEFT).split() - self.add(question) - for count in sample_counts: - hand = Hand(count, small = True) - self.add(hand) - self.wait() - self.remove(hand) - self.add(hand) - self.wait() - self.remove(question) - self.add(answer[0]) - counts = list(map(finger_tip_power_of_2, list(range(5)))) - for count in counts: - self.play(SpinInFromNothing(count, run_time = 0.3)) - self.wait() - self.play(ShimmerIn(answer[1])) - for count in sample_counts: - self.clear() - self.add(*answer) - self.read_sample(count) - - def read_sample(self, num): - hand = Hand(num, small = True) - bool_arr = [c == '1' for c in five_char_binary(num)] - counts = [4-count for count in range(5) if bool_arr[count]] - count_mobs = list(map(finger_tip_power_of_2, counts)) - if num in [6, 27]: - count_mobs[1].shift(0.2*DOWN + 0.2*LEFT) - if num in [6, 17]: - hand.shift(0.8*LEFT) - sum_mobs = TexMobject( - " + ".join([str(2**c) for c in counts]).split(" ") + ["=%d"%num] - ).to_corner(UP+RIGHT).split() - self.add(hand, *count_mobs) - self.wait() - self.play(*[ - Transform(count_mobs[n/2], sum_mobs[n]) - if n%2 == 0 and n/2 < len(counts) - else FadeIn(sum_mobs[n]) - for n in range(len(sum_mobs)) - ]) - self.wait(2.0) - - -class ShowIncrementRule(Scene): - def construct(self): - #First count from 18 to 22 - def to_left(words): - return "\\begin{flushleft}" + words + "\\end{flushleft}" - phrases = [ - TextMobject(to_left(words), size = "\\Huge").scale(0.75).to_corner(UP+LEFT) - for words in [ - "But while you're counting, you don't need to think about powers of 2.", - "Can you see the pattern for incrementing?", - "If the thumb is down, turn it up.", - "If the thumb is up, but the forefinger is down, flip them both.", - "If the thumb and forefinger are up, but the middle finger is down, flip all three.", - "In general, flip all of the fingers up to the rightmost one which is down.", - "After practicing for a minute or two, you're mind starts doing it automatically.", - "Question: Why does this rule for incrementation work?", - ] - ] - ranges = [ - (0, 14, False), - (14, 28, False), - (12, 13, True), - (29, 31, True), - (27, 29, True), - (23, 24, True), - (14, 20, False), - (20, 26, False) - ] - oh = OverHand() - for phrase, (start, end, pause) in zip(phrases, ranges): - if pause: - self.background = oh.frames[COUNT_TO_FRAME_NUM[start]] - self.add(phrase) - self.play(ShimmerIn(self.get_arrow_set(start))) - self.wait() - self.clear() - self.reset_background() - self.frames += [ - disp.paint_mobject(phrase, frame) - for frame in oh.frames[COUNT_TO_FRAME_NUM[start]:COUNT_TO_FRAME_NUM[end]] - ] - if pause: - self.frames += [self.frames[-1]]*int(1.0/self.frame_duration) - - def get_arrow_set(self, num): - arrow = TexMobject("\\downarrow", size = "\\Huge") - arrow.set_color("green") - arrow.shift(-arrow.get_bottom()) - if num == 12: - tips = [(4, 1, 0)] - elif num == 29: - tips = [ - (6, 1.5, 0), - (3, 1.5, 0), - ] - elif num == 27: - tips = [ - (5.5, 1.5, 0), - (2.75, 3.5, 0), - (2, 1.0, 0), - ] - elif num == 23: - tips = [ - (6, 1, 0), - (3.25, 3.5, 0), - (2.25, 3.5, 0), - (1.5, 0.75, 0), - ] - return Mobject(*[ - deepcopy(arrow).shift(tip) - for tip in tips - ]) - - -class MindFindsShortcuts(Scene): - def construct(self): - words1 = TextMobject(""" - Before long, your mind starts to recognize certain - patterns without needing to perform the addition. - """, size = "\\Huge").scale(0.75).to_corner(LEFT+UP) - words2 = TextMobject(""" - Which makes it faster to recognize other patterns... - """, size = "\\Huge").scale(0.75).to_corner(LEFT+UP) - - hand = Hand(7).scale(0.5).center().shift(DOWN+2*LEFT) - sum421 = TexMobject("4+2+1").shift(DOWN+2*RIGHT) - seven = TexMobject("7").shift(DOWN+6*RIGHT) - compound = Mobject( - Arrow(hand, sum421), - sum421, - Arrow(sum421, seven) - ) - self.add( - words1, - hand, - compound, - seven - ) - self.wait() - self.play( - Transform(compound, Arrow(hand, seven).set_color("yellow")), - ShimmerIn(TextMobject("Directly recognize").shift(1.5*DOWN+2*RIGHT)) - ) - self.wait() - - self.clear() - hands = dict([ - (num, Hand(num).center().scale(0.5).shift(DOWN)) - for num in [23, 16, 7] - ]) - hands[23].shift(5*LEFT) - hands[16].shift(LEFT) - hands[7].shift(3*RIGHT) - for num in 7, 16: - hands[num].add(TexMobject(str(num)).shift(hands[num].get_top()+0.5*UP)) - plus = TexMobject("+").shift(DOWN + RIGHT) - equals = TexMobject("=").shift(DOWN + 2.5*LEFT) - equals23 = TexMobject("=23").shift(DOWN + 5.5*RIGHT) - - self.add(words2, hands[23]) - self.wait() - self.play( - Transform( - deepcopy(hands[16]).set_color("black").center().shift(hands[23].get_center()), - hands[16] - ), - Transform( - deepcopy(hands[7]).set_color("black").center().shift(hands[23].get_center()), - hands[7] - ), - Animation(hands[23]), - FadeIn(equals), - FadeIn(plus) - ) - self.wait() - self.play(ShimmerIn(equals23)) - self.wait() - - -class CountingExampleSentence(ShowCounting): - def construct(self): - words = "As an example, this is me counting the number of words in this sentence on just one hand!" - self.words = TextMobject(words.split(), size = "\\Huge").scale(0.7).to_corner(UP+LEFT, buff = 0.25).split() - ShowCounting.construct(self) - - def get_counting_mob(self, num): - return Mobject(*self.words[:num]) - -class FinishCountingExampleSentence(Scene): - def construct(self): - words = "As an example, this is me counting the number of words in this sentence on just one hand!" - words = TextMobject(words, size = "\\Huge").scale(0.7).to_corner(UP+LEFT, buff = 0.25) - hand = Hand(18) - sixteen = TexMobject("16").shift([0, 2.25, 0]) - two = TexMobject("2").shift([3, 3.65, 0]) - eightteen = TexMobject("18").shift([1.5, 2.5, 0]) - eightteen.sort_points() - comp = Mobject(sixteen, two) - self.add(hand, comp, words) - self.wait() - self.play(Transform(comp, eightteen)) - self.wait() - -class Question(Scene): - def construct(self): - self.add(TextMobject("Left to ponder: Why does this rule for incrementing work?")) - - -class TwoHandStatement(Scene): - def construct(self): - self.add(TextMobject( - "With 10 fingers, you can count up to $2^{10} - 1 = 1023$..." - )) - -class WithToes(Scene): - def construct(self): - words = TextMobject([ - "If you were dexterous enough to use your toes as well,", - "you could count to 1,048,575" - ]).split() - self.add(words[0]) - self.wait() - self.play(ShimmerIn(words[1])) - self.wait() - - -if __name__ == "__main__": - command_line_create_scene(MOVIE_PREFIX) diff --git a/from_3b1b/old/crypto.py b/from_3b1b/old/crypto.py deleted file mode 100644 index ffb0a94c..00000000 --- a/from_3b1b/old/crypto.py +++ /dev/null @@ -1,5265 +0,0 @@ -from manimlib.imports import * - -from hashlib import sha256 -import binascii - -#force_skipping -#revert_to_original_skipping_status - -BITCOIN_COLOR = "#f7931a" - -def get_cursive_name(name): - result = TextMobject("\\normalfont\\calligra %s"%name) - result.set_stroke(width = 0.5) - return result - -def sha256_bit_string(message): - hexdigest = sha256(message.encode('utf-8')).hexdigest() - return bin(int(hexdigest, 16))[2:] - -def bit_string_to_mobject(bit_string): - line = TexMobject("0"*32) - pre_result = VGroup(*[ - line.copy() for row in range(8) - ]) - pre_result.arrange(DOWN, buff = SMALL_BUFF) - result = VGroup(*it.chain(*pre_result)) - result.scale(0.7) - bit_string = (256 - len(bit_string))*"0" + bit_string - - for i, (bit, part) in enumerate(zip(bit_string, result)): - if bit == "1": - one = TexMobject("1")[0] - one.replace(part, dim_to_match = 1) - result.submobjects[i] = one - - return result - -def sha256_tex_mob(message, n_forced_start_zeros = 0): - true_bit_string = sha256_bit_string(message) - n = n_forced_start_zeros - bit_string = "0"*n + true_bit_string[n:] - return bit_string_to_mobject(bit_string) - -class EthereumLogo(SVGMobject): - CONFIG = { - "file_name" : "ethereum_logo", - "stroke_width" : 0, - "fill_opacity" : 1, - "color_chars" : "8B8B48", - "height" : 0.5, - } - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - for part, char in zip(self.submobjects, self.color_chars): - part.set_color("#" + 6*char) - -class LitecoinLogo(SVGMobject): - CONFIG = { - "file_name" : "litecoin_logo", - "stroke_width" : 0, - "fill_opacity" : 1, - "fill_color" : LIGHT_GREY, - "height" : 0.5, - } - -class TenDollarBill(VGroup): - CONFIG = { - "color" : GREEN, - "height" : 0.5, - "mark_paths_closed" : False, - } - def __init__(self, **kwargs): - VGroup.__init__(self, **kwargs) - rect = Rectangle( - height = 2.61, - width = 6.14, - color = self.color, - mark_paths_closed = False, - fill_color = BLACK, - fill_opacity = 1, - ) - rect.set_height(self.height) - oval = Circle() - oval.stretch_to_fit_height(0.7*self.height) - oval.stretch_to_fit_width(0.4*self.height) - rect.add_subpath(oval.points) - - pi = Randolph( - mode = "pondering", - color = GREEN_B - ) - pi.set_width(oval.get_width()) - pi.move_to(oval) - pi.shift(0.1*pi.get_height()*DOWN) - - self.add(pi, rect) - for vect in UP+LEFT, DOWN+RIGHT: - ten = TexMobject("\\$10") - ten.set_height(0.25*self.height) - ten.next_to(self.get_corner(vect), -vect, SMALL_BUFF) - ten.set_color(GREEN_C) - self.add(ten) - - -################## - -class AskQuestion(Scene): - CONFIG = { - "time_per_char" : 0.06, - } - def construct(self): - strings = [ - "What", "does", "it", "mean ", "to", - "have ", "a", "Bitcoin?" - ] - question = TextMobject(*strings) - question.set_color_by_tex("have", YELLOW) - self.wait() - for word, part in zip(strings, question): - n_chars = len(word.strip()) - n_spaces = len(word) - n_chars - self.play( - LaggedStartMap(FadeIn, part), - run_time = self.time_per_char * len(word), - rate_func = squish_rate_func(smooth, 0, 0.5) - ) - self.wait(self.time_per_char*n_spaces) - self.wait(2) - -class ListOfAttributes(Scene): - def construct(self): - logo = BitcoinLogo() - - digital = TextMobject("Digital") - government, bank = buildings = [ - SVGMobject( - file_name = "%s_building"%word, - height = 2, - fill_color = LIGHT_GREY, - fill_opacity = 1, - stroke_width = 0, - ) - for word in ("government", "bank") - ] - attributes = VGroup(digital, *buildings) - attributes.arrange(RIGHT, buff = LARGE_BUFF) - for building in buildings: - building.cross = Cross(building) - building.cross.set_stroke(width = 12) - - self.play(DrawBorderThenFill(logo)) - self.play( - logo.to_corner, UP+LEFT, - Write(digital, run_time = 2) - ) - for building in buildings: - self.play(FadeIn(building)) - self.play(ShowCreation(building.cross)) - self.wait() - -class UnknownAuthor(Scene): - CONFIG = { - "camera_config" : { - "background_image" : "bitcoin_paper" - } - } - def construct(self): - rect = Rectangle(height = 0.4, width = 2.5) - rect.shift(2.45*UP) - question = TextMobject("Who is this?") - question.next_to(rect, RIGHT, buff = 1.5) - arrow = Arrow(question, rect, buff = SMALL_BUFF) - VGroup(question, arrow, rect).set_color(RED_D) - - self.play(ShowCreation(rect)) - self.play( - Write(question), - ShowCreation(arrow) - ) - self.wait() - -class DisectQuestion(TeacherStudentsScene): - def construct(self): - self.hold_up_question() - self.list_topics() - self.isolate_you() - - def hold_up_question(self): - question = TextMobject( - "What does it mean to", "have", "a", "Bitcoin?" - ) - question.set_color_by_tex("have", YELLOW) - question.next_to(self.teacher, UP) - question.to_edge(RIGHT, buff = LARGE_BUFF) - question.save_state() - question.shift(DOWN) - question.set_fill(opacity = 0) - - self.play( - self.teacher.change, "raise_right_hand", - question.restore - ) - self.change_student_modes(*["pondering"]*3) - self.wait() - - self.bitcoin_word = question.get_part_by_tex("Bitcoin") - - def list_topics(self): - topics = TextMobject( - "Digital signatures, ", - "Proof of work, ", - "Cryptographic hash functions, \\dots" - ) - topics.set_width(FRAME_WIDTH - LARGE_BUFF) - topics.to_edge(UP) - topics.set_color_by_tex("Digital", BLUE) - topics.set_color_by_tex("Proof", GREEN) - topics.set_color_by_tex("hash", YELLOW) - - for word in topics: - anims = [Write(word, run_time = 1)] - self.change_student_modes( - *["confused"]*3, - added_anims = anims, - look_at_arg = word - ) - - def isolate_you(self): - self.pi_creatures = VGroup() - you = self.students[1] - rect = FullScreenFadeRectangle() - words = TextMobject("Invent your own") - arrow = Arrow(UP, DOWN) - arrow.next_to(you, UP) - words.next_to(arrow, UP) - - self.revert_to_original_skipping_status() - self.play(FadeIn(rect), Animation(you)) - self.play( - Write(words), - ShowCreation(arrow), - you.change, "erm", words - ) - self.play(Blink(you)) - self.wait() - -class CryptocurrencyEquation(Scene): - def construct(self): - parts = TextMobject( - "Ledger", - "- Trust", - "+ Cryptography", - "= Cryptocurrency" - ) - VGroup(*parts[-1][1:]).set_color(YELLOW) - parts.set_width(FRAME_WIDTH - LARGE_BUFF) - - for part in parts: - self.play(FadeIn(part)) - self.wait(2) - -class CryptocurrencyMarketCaps(ExternallyAnimatedScene): - pass - -class ListRecentCurrencies(Scene): - def construct(self): - footnote = TextMobject("$^*$Listed by market cap") - footnote.scale(0.5) - footnote.to_corner(DOWN+RIGHT) - self.add(footnote) - - logos = VGroup( - BitcoinLogo(), - EthereumLogo(), - ImageMobject("ripple_logo"), - LitecoinLogo(), - EthereumLogo().set_color_by_gradient(GREEN_B, GREEN_D), - ) - for logo in logos: - logo.set_height(0.75) - logos.arrange(DOWN, buff = MED_LARGE_BUFF) - logos.shift(LEFT) - logos.to_edge(UP) - names = list(map( - TextMobject, - [ - "Bitcoin", "Ethereum", "Ripple", - "Litecoin", "Ethereum Classic" - ], - )) - for logo, name in zip(logos, names): - name.next_to(logo, RIGHT) - anims = [] - if isinstance(logo, SVGMobject): - anims.append(DrawBorderThenFill(logo, run_time = 1)) - else: - anims.append(FadeIn(logo)) - anims.append(Write(name, run_time = 2)) - self.play(*anims) - dots = TexMobject("\\vdots") - dots.next_to(logos, DOWN) - self.play(LaggedStartMap(FadeIn, dots, run_time = 1)) - self.wait() - -class Hype(TeacherStudentsScene): - def construct(self): - self.teacher.change_mode("guilty") - phrases = list(map(TextMobject, [ - "I want some!", - "I'll get rich, right?", - "Buy them all!" - ])) - modes = ["hooray", "conniving", "surprised"] - for student, phrase, mode in zip(self.students, phrases, modes): - bubble = SpeechBubble() - bubble.set_fill(BLACK, 1) - bubble.add_content(phrase) - bubble.resize_to_content() - bubble.pin_to(student) - bubble.add(phrase) - self.play( - student.change_mode, mode, - FadeIn(bubble), - ) - self.wait(3) - -class NoCommentOnSpeculation(TeacherStudentsScene): - def construct(self): - axes = VGroup( - Line(0.25*LEFT, 4*RIGHT), - Line(0.25*DOWN, 3*UP), - ) - times = np.arange(0, 4.25, 0.25) - prices = [ - 0.1, 0.5, 1.75, 1.5, - 2.75, 2.2, 1.3, 0.8, - 1.1, 1.3, 1.2, 1.4, - 1.5, 1.7, 1.2, 1.3, - ] - graph = VMobject() - graph.set_points_as_corners([ - time*RIGHT + price*UP - for time, price in zip(times, prices) - ]) - graph.set_stroke(BLUE) - group = VGroup(axes, graph) - group.next_to(self.teacher, UP+LEFT) - - cross = Cross(group) - - mining_graphic = ImageMobject("bitcoin_mining_graphic") - mining_graphic.set_height(2) - mining_graphic.next_to(self.teacher, UP+LEFT) - mining_cross = Cross(mining_graphic) - mining_cross.set_stroke(RED, 8) - - axes.save_state() - axes.shift(DOWN) - axes.fade(1) - self.play( - self.teacher.change, "sassy", - axes.restore, - ) - self.play(ShowCreation( - graph, run_time = 2, - rate_func=linear - )) - self.wait() - self.play(ShowCreation(cross)) - group.add(cross) - self.play( - group.shift, FRAME_WIDTH*RIGHT, - self.teacher.change, "happy" - ) - self.wait() - - self.student_says( - "But...what are they?", - student_index = 0, - target_mode = "confused" - ) - self.wait(2) - self.play( - FadeIn(mining_graphic), - RemovePiCreatureBubble(self.students[0]), - self.teacher.change, "sassy", - ) - self.play(ShowCreation(mining_cross)) - self.wait() - self.play( - VGroup(mining_graphic, mining_cross).shift, - FRAME_WIDTH*RIGHT - ) - black_words = TextMobject("Random words\\\\Blah blah") - black_words.set_color(BLACK) - self.teacher_thinks(black_words) - self.zoom_in_on_thought_bubble() - -class MiningIsALotteryCopy(ExternallyAnimatedScene): - pass - -class LedgerScene(PiCreatureScene): - CONFIG = { - "ledger_width" : 6, - "ledger_height" : 7, - "denomination" : "USD", - "ledger_line_height" : 0.4, - "sign_transactions" : False, - "enumerate_lines" : False, - "line_number_color" : YELLOW, - } - def setup(self): - PiCreatureScene.setup(self) - self.remove(self.pi_creatures) - - def add_ledger_and_network(self): - self.add(self.get_ledger(), self.get_network()) - - def get_ledger(self): - title = TextMobject("Ledger") - rect = Rectangle( - width = self.ledger_width, - height = self.ledger_height - ) - title.next_to(rect.get_top(), DOWN) - h_line = Line(rect.get_left(), rect.get_right()) - h_line.scale(0.8) - h_line.set_stroke(width = 2) - h_line.next_to(title, DOWN) - content = VGroup(h_line) - - self.ledger = VGroup(rect, title, content) - self.ledger.content = content - self.ledger.to_corner(UP+LEFT) - return self.ledger - - def add_line_to_ledger(self, string_or_mob): - if isinstance(string_or_mob, str): - mob = TextMobject(string_or_mob) - elif isinstance(string_or_mob, Mobject): - mob = string_or_mob - else: - raise Exception("Invalid input") - - items = self.ledger.content - - mob.set_height(self.ledger_line_height) - if self.enumerate_lines: - num = TexMobject(str(len(items)) + ".") - num.scale(0.8) - num.set_color(self.line_number_color) - num.next_to(mob, LEFT, MED_SMALL_BUFF) - mob.add_to_back(num) - mob.next_to( - items[-1], DOWN, - buff = MED_SMALL_BUFF, - aligned_edge = LEFT - ) - if self.enumerate_lines and len(items) == 1: - mob.shift(MED_LARGE_BUFF * LEFT) - items.add(mob) - return mob - - def add_payment_line_to_ledger(self, from_name, to_name, amount): - amount_str = str(amount) - if self.denomination == "USD": - amount_str = "\\$" + amount_str - else: - amount_str += " " + self.denomination - line_tex_parts = [ - from_name.capitalize(), - "pays" if from_name.lower() != "you" else "pay", - to_name.capitalize(), - amount_str, - ] - if self.sign_transactions: - line_tex_parts.append(self.get_signature_tex()) - line = TextMobject(*line_tex_parts) - for name in from_name, to_name: - color = self.get_color_from_name(name) - line.set_color_by_tex(name.capitalize(), color) - if self.sign_transactions: - from_part = line.get_part_by_tex(from_name.capitalize()) - line[-1].set_color(from_part.get_color()) - - amount_color = { - "USD" : GREEN, - "BTC" : YELLOW, - "LD" : YELLOW, - }.get(self.denomination, WHITE) - line.set_color_by_tex(amount_str, amount_color) - - return self.add_line_to_ledger(line) - - def get_color_from_name(self, name): - if hasattr(self, name.lower()): - creature = getattr(self, name.lower()) - color = creature.get_color() - if np.mean(color.get_rgb()) < 0.5: - color = average_color(color, color, WHITE) - return color - return WHITE - - def animate_payment_addition(self, *args, **kwargs): - line = self.add_payment_line_to_ledger(*args, **kwargs) - self.play(LaggedStartMap( - FadeIn, - VGroup(*it.chain(*line)), - run_time = 1 - )) - - def get_network(self): - creatures = self.pi_creatures - lines = VGroup(*[ - Line( - VGroup(pi1, pi1.label), VGroup(pi2, pi2.label), - buff = MED_SMALL_BUFF, - stroke_width = 2, - ) - for pi1, pi2 in it.combinations(creatures, 2) - ]) - labels = VGroup(*[pi.label for pi in creatures]) - self.network = VGroup(creatures, labels, lines) - self.network.lines = lines - return self.network - - def create_pi_creatures(self): - creatures = VGroup(*[ - PiCreature(color = color, height = 1).shift(2*vect) - for color, vect in zip( - [BLUE_C, MAROON_D, GREY_BROWN, BLUE_E], - [UP+LEFT, UP+RIGHT, DOWN+LEFT, DOWN+RIGHT], - ) - ]) - creatures.to_edge(RIGHT) - names = self.get_names() - for name, creature in zip(names, creatures): - setattr(self, name, creature) - label = TextMobject(name.capitalize()) - label.scale(0.75) - label.next_to(creature, DOWN, SMALL_BUFF) - creature.label = label - if (creature.get_center() - creatures.get_center())[0] > 0: - creature.flip() - creature.look_at(creatures.get_center()) - - return creatures - - def get_names(self): - return ["alice", "bob", "charlie", "you"] - - def get_signature_tex(self): - if not hasattr(self, "nonce"): - self.nonce = 0 - binary = bin(hash(str(self.nonce)))[-8:] - self.nonce += 1 - return binary + "\\dots" - - def get_signature(self, color = BLUE_C): - result = TexMobject(self.get_signature_tex()) - result.set_color(color) - return result - - def add_ellipsis(self): - last_item = self.ledger.content[-1] - dots = TexMobject("\\vdots") - dots.next_to(last_item.get_left(), DOWN) - last_item.add(dots) - self.add(last_item) - -class LayOutPlan(LedgerScene): - def construct(self): - self.ask_question() - self.show_ledger() - self.become_skeptical() - - def ask_question(self): - btc = BitcoinLogo() - group = VGroup(btc, TexMobject("= ???")) - group.arrange(RIGHT) - - self.play( - DrawBorderThenFill(btc), - Write(group[1], run_time = 2) - ) - self.wait() - self.play( - group.scale, 0.7, - group.next_to, ORIGIN, RIGHT, - group.to_edge, UP - ) - - def show_ledger(self): - network = self.get_network() - ledger = self.get_ledger() - payments = [ - ("Alice", "Bob", 20), - ("Bob", "Charlie", 40), - ("Alice", "You", 50), - ] - - self.play(*list(map(FadeIn, [network, ledger]))) - for payment in payments: - new_line = self.add_payment_line_to_ledger(*payment) - from_name, to_name, amount = payment - from_pi = getattr(self, from_name.lower()) - to_pi = getattr(self, to_name.lower()) - cash = TexMobject("\\$"*(amount/10)) - cash.scale(0.5) - cash.move_to(from_pi) - cash.set_color(GREEN) - - self.play( - cash.move_to, to_pi, - to_pi.change_mode, "hooray" - ) - self.play( - FadeOut(cash), - Write(new_line, run_time = 1) - ) - self.wait() - - def become_skeptical(self): - creatures = self.pi_creatures - - self.play(*[ - ApplyMethod(pi.change_mode, "sassy") - for pi in creatures - ]) - for k in range(3): - self.play(*[ - ApplyMethod( - creatures[i].look_at, - creatures[k*(i+1)%4] - ) - for i in range(4) - ]) - self.wait(2) - -class UnderlyingSystemVsUserFacing(Scene): - def construct(self): - underlying = TextMobject("Underlying \\\\ system") - underlying.shift(DOWN).to_edge(LEFT) - user_facing = TextMobject("User-facing") - user_facing.next_to(underlying, UP, LARGE_BUFF, LEFT) - - protocol = TextMobject("Bitcoin protocol") - protocol.next_to(underlying, RIGHT, MED_LARGE_BUFF) - protocol.set_color(BITCOIN_COLOR) - banking = TextMobject("Banking system") - banking.next_to(protocol, RIGHT, MED_LARGE_BUFF) - banking.set_color(GREEN) - - phone = SVGMobject( - file_name = "phone", - fill_color = WHITE, - fill_opacity = 1, - height = 1, - stroke_width = 0, - ) - phone.next_to(protocol, UP, LARGE_BUFF) - card = SVGMobject( - file_name = "credit_card", - fill_color = LIGHT_GREY, - fill_opacity = 1, - stroke_width = 0, - height = 1 - ) - card.next_to(banking, UP, LARGE_BUFF) - - btc = BitcoinLogo() - btc.next_to(phone, UP, MED_LARGE_BUFF) - dollar = TexMobject("\\$") - dollar.set_height(1) - dollar.set_color(GREEN) - dollar.next_to(card, UP, MED_LARGE_BUFF) - card.save_state() - card.shift(2*RIGHT) - card.set_fill(opacity = 0) - - - h_line = Line(underlying.get_left(), banking.get_right()) - h_line.next_to(underlying, DOWN, MED_SMALL_BUFF, LEFT) - h_line2 = h_line.copy() - h_line2.next_to(user_facing, DOWN, MED_LARGE_BUFF, LEFT) - h_line3 = h_line.copy() - h_line3.next_to(user_facing, UP, MED_LARGE_BUFF, LEFT) - v_line = Line(5*UP, ORIGIN) - v_line.next_to(underlying, RIGHT, MED_SMALL_BUFF) - v_line.shift(1.7*UP) - v_line2 = v_line.copy() - v_line2.next_to(protocol, RIGHT, MED_SMALL_BUFF) - v_line2.shift(1.7*UP) - - self.add(h_line, h_line2, h_line3, v_line, v_line2) - self.add(underlying, user_facing, btc) - self.play(Write(protocol)) - self.wait(2) - self.play( - card.restore, - Write(dollar) - ) - self.play(Write(banking)) - self.wait(2) - self.play(DrawBorderThenFill(phone)) - self.wait(2) - -class FromBankToDecentralizedSystemCopy(ExternallyAnimatedScene): - pass - -class IntroduceLedgerSystem(LedgerScene): - CONFIG = { - "payments" : [ - ("Alice", "Bob", 20), - ("Bob", "Charlie", 40), - ("Charlie", "You", 30), - ("You", "Alice", 10), - ] - } - def construct(self): - self.add(self.get_network()) - self.exchange_money() - self.add_ledger() - self.tally_it_all_up() - - - def exchange_money(self): - for from_name, to_name, num in self.payments: - from_pi = getattr(self, from_name.lower()) - to_pi = getattr(self, to_name.lower()) - cash = TexMobject("\\$"*(num/10)).set_color(GREEN) - cash.set_height(0.5) - cash.move_to(from_pi) - self.play( - cash.move_to, to_pi, - to_pi.change_mode, "hooray" - ) - self.play(FadeOut(cash)) - self.wait() - - def add_ledger(self): - ledger = self.get_ledger() - - self.play( - Write(ledger), - *[ - ApplyMethod(pi.change, "pondering", ledger) - for pi in self.pi_creatures - ] - ) - for payment in self.payments: - self.animate_payment_addition(*payment) - self.wait(3) - - def tally_it_all_up(self): - accounts = dict() - names = "alice", "bob", "charlie", "you" - for name in names: - accounts[name] = 0 - for from_name, to_name, amount in self.payments: - accounts[from_name.lower()] -= amount - accounts[to_name.lower()] += amount - - results = VGroup() - debtors = VGroup() - creditors = VGroup() - for name in names: - amount = accounts[name] - creature = getattr(self, name) - creature.cash = TexMobject("\\$"*abs(amount/10)) - creature.cash.next_to(creature, UP+LEFT, SMALL_BUFF) - creature.cash.set_color(GREEN) - if amount < 0: - verb = "Owes" - debtors.add(creature) - else: - verb = "Gets" - creditors.add(creature) - if name == "you": - verb = verb[:-1] - result = TextMobject( - verb, "\\$%d"%abs(amount) - ) - result.set_color_by_tex("Owe", RED) - result.set_color_by_tex("Get", GREEN) - result.add_background_rectangle() - result.scale(0.7) - result.next_to(creature.label, DOWN) - results.add(result) - - brace = Brace(VGroup(*self.ledger.content[1:]), RIGHT) - tally_up = brace.get_text("Tally up") - tally_up.add_background_rectangle() - - self.play( - GrowFromCenter(brace), - FadeIn(tally_up) - ) - self.play( - LaggedStartMap(FadeIn, results), - *[ - ApplyMethod(pi.change, "happy") - for pi in creditors - ] + [ - ApplyMethod(pi.change, "plain") - for pi in debtors - ] - ) - self.wait() - debtor_cash, creditor_cash = [ - VGroup(*it.chain(*[pi.cash for pi in group])) - for group in (debtors, creditors) - ] - self.play(FadeIn(debtor_cash)) - self.play( - debtor_cash.arrange, RIGHT, SMALL_BUFF, - debtor_cash.move_to, self.pi_creatures, - ) - self.wait() - self.play(ReplacementTransform( - debtor_cash, creditor_cash - )) - self.wait(2) - -class InitialProtocol(Scene): - def construct(self): - self.add_title() - self.show_first_two_items() - - def add_title(self): - title = TextMobject("Protocol") - title.scale(1.5) - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(4) - h_line.next_to(title, DOWN) - self.h_line = h_line - self.title = title - self.add(title, h_line) - - def show_first_two_items(self): - items = VGroup(*list(map(self.get_new_item, [ - "Anyone can add lines to the Ledger", - "Settle up with real money each month" - ]))) - - for item in items: - self.wait() - self.play(LaggedStartMap(FadeIn, item)) - self.wait(2) - - def get_new_item(self, item_string): - item = TextMobject("$\\cdot$ %s"%item_string) - if not hasattr(self, "items"): - self.items = VGroup(item) - self.items.next_to(self.h_line, DOWN, MED_LARGE_BUFF) - else: - item.next_to(self.items, DOWN, MED_LARGE_BUFF, LEFT) - self.items.add(item) - return item - -class AddFraudulentLine(LedgerScene): - def construct(self): - self.add_ledger_and_network() - self.anyone_can_add_a_line() - self.bob_adds_lines() - self.alice_reacts() - - def anyone_can_add_a_line(self): - words = TextMobject("Anyone can add a line") - words.to_corner(UP+RIGHT) - words.set_color(YELLOW) - arrow = Arrow( - words.get_left(), - self.ledger.content.get_center() + DOWN, - ) - - self.play(Write(words, run_time = 1)) - self.play(ShowCreation(arrow)) - self.wait() - self.play( - FadeOut(words), - FadeOut(arrow), - FocusOn(self.bob), - ) - - def bob_adds_lines(self): - line = self.add_payment_line_to_ledger("Alice", "Bob", 100) - line.save_state() - line.scale(0.001) - line.move_to(self.bob) - - self.play(self.bob.change, "conniving") - self.play(line.restore) - self.wait() - - def alice_reacts(self): - bubble = SpeechBubble( - height = 1.5, width = 2, direction = LEFT, - ) - bubble.next_to(self.alice, UP+RIGHT, buff = 0) - bubble.write("Hey!") - self.play( - Animation(self.bob.pupils), - self.alice.change, "angry", - FadeIn(bubble), - Write(bubble.content, run_time = 1) - ) - self.wait(3) - self.play( - FadeOut(bubble), - FadeOut(bubble.content), - self.alice.change_mode, "pondering" - ) - -class AnnounceDigitalSignatures(TeacherStudentsScene): - def construct(self): - words = TextMobject("Digital \\\\ signatures!") - words.scale(1.5) - self.teacher_says( - words, - target_mode = "hooray", - ) - self.change_student_modes(*["hooray"]*3) - self.wait(2) - -class IntroduceSignatures(LedgerScene): - CONFIG = { - "payments" : [ - ("Alice", "Bob", 100), - ("Charlie", "You", 20), - ("Bob", "You", 30), - ], - } - def construct(self): - self.add_ledger_and_network() - self.add_transactions() - self.add_signatures() - - def add_transactions(self): - transactions = VGroup(*[ - self.add_payment_line_to_ledger(*payment) - for payment in self.payments - ]) - self.play(LaggedStartMap(FadeIn, transactions)) - self.wait() - - def add_signatures(self): - signatures = VGroup(*[ - get_cursive_name(payments[0].capitalize()) - for payments in self.payments - ]) - for signature, transaction in zip(signatures, self.ledger.content[1:]): - signature.next_to(transaction, RIGHT) - signature.set_color(transaction[0].get_color()) - self.play(Write(signature, run_time = 2)) - transaction.add(signature) - self.wait(2) - - rect = SurroundingRectangle(self.ledger.content[1]) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.wait() - self.play(Indicate(signatures[0])) - self.wait() - -class AskHowDigitalSignaturesArePossible(TeacherStudentsScene): - def construct(self): - signature = get_cursive_name("Alice") - signature.scale(1.5) - signature.set_color(BLUE_C) - signature.to_corner(UP+LEFT) - signature_copy = signature.copy() - signature_copy.shift(3*RIGHT) - - bits = TexMobject("01100001") - bits.next_to(signature, DOWN) - bits.shift_onto_screen() - bits_copy = bits.copy() - bits_copy.next_to(signature_copy, DOWN) - - - self.student_says( - "Couldn't you just \\\\ copy the signature?", - target_mode = "confused", - run_time = 1 - ) - self.change_student_modes("pondering", "confused", "erm") - self.play(Write(signature)) - self.play(LaggedStartMap(FadeIn, bits, run_time = 1)) - self.wait() - self.play(ReplacementTransform( - bits.copy(), bits_copy, - path_arc = np.pi/2 - )) - self.play(Write(signature_copy)) - self.wait(3) - -class DescribeDigitalSignatures(LedgerScene): - CONFIG = { - "public_color" : GREEN, - "private_color" : RED, - "signature_color" : BLUE_C, - } - def construct(self): - self.reorganize_pi_creatures() - self.generate_key_pairs() - self.keep_secret_key_secret() - self.show_handwritten_signatures() - self.show_digital_signatures() - self.show_signing_functions() - - def reorganize_pi_creatures(self): - self.pi_creatures.remove(self.you) - creature_groups = VGroup(*[ - VGroup(pi, pi.label).scale(1.7) - for pi in self.pi_creatures - ]) - creature_groups.arrange(RIGHT, buff = 2) - creature_groups.to_edge(DOWN) - self.add(creature_groups) - for pi in self.pi_creatures: - if pi.is_flipped(): - pi.flip() - - def generate_key_pairs(self): - title = TextMobject("Private", "key /", "Public", "key") - title.to_edge(UP) - private, public = list(map(title.get_part_by_tex, ["Private", "Public"])) - private.set_color(self.private_color) - public.set_color(self.public_color) - secret = TextMobject("Secret") - secret.move_to(private, RIGHT) - secret.set_color(self.private_color) - - names = self.get_names()[:-1] - public_key_strings = [ - bin(256+ord(name[0].capitalize()))[3:] - for name in names - ] - private_key_strings = [ - bin(hash(name))[2:10] - for name in names - ] - public_keys, private_keys = [ - VGroup(*[ - TextMobject(key_name+":"," $%s\\dots$"%key) - for key in keys - ]) - for key_name, keys in [ - ("pk", public_key_strings), - ("sk", private_key_strings) - ] - ] - key_pairs = [ - VGroup(*pair).arrange(DOWN, aligned_edge = LEFT) - for pair in zip(public_keys, private_keys) - ] - for key_pair, pi in zip(key_pairs, self.pi_creatures): - key_pair.next_to(pi, UP, MED_LARGE_BUFF) - for key in key_pair: - key.set_color_by_tex("sk", self.private_color) - key.set_color_by_tex("pk", self.public_color) - - self.play(Write(title, run_time = 2)) - self.play(ReplacementTransform( - VGroup(VGroup(public.copy())), - public_keys - )) - self.play(ReplacementTransform( - VGroup(VGroup(private.copy())), - private_keys - )) - self.wait() - self.play(private.shift, DOWN) - self.play(FadeIn(secret)) - self.play(FadeOut(private)) - self.wait() - - title.remove(private) - title.add(secret) - self.title = title - self.private_keys = private_keys - self.public_keys = public_keys - - def keep_secret_key_secret(self): - keys = self.private_keys - rects = VGroup(*list(map(SurroundingRectangle, keys))) - rects.set_color(self.private_color) - lock = SVGMobject( - file_name = "lock", - height = rects.get_height(), - fill_color = LIGHT_GREY, - fill_opacity = 1, - stroke_width = 0, - ) - locks = VGroup(*[ - lock.copy().next_to(rect, LEFT, SMALL_BUFF) - for rect in rects - ]) - - self.play(ShowCreation(rects)) - self.play(LaggedStartMap(DrawBorderThenFill, locks)) - self.wait() - - self.private_key_rects = rects - self.locks = locks - - def show_handwritten_signatures(self): - lines = VGroup(*[Line(LEFT, RIGHT) for x in range(5)]) - lines.arrange(DOWN) - last_line = lines[-1] - last_line.scale(0.7, about_point = last_line.get_left()) - - signature_line = lines[0].copy() - signature_line.set_stroke(width = 2) - signature_line.next_to(lines, DOWN, LARGE_BUFF) - ex = TexMobject("\\times") - ex.scale(0.7) - ex.next_to(signature_line, UP, SMALL_BUFF, LEFT) - lines.add(ex, signature_line) - - rect = SurroundingRectangle( - lines, - color = LIGHT_GREY, - buff = MED_SMALL_BUFF - ) - document = VGroup(rect, lines) - documents = VGroup(*[ - document.copy() - for x in range(2) - ]) - documents.arrange(RIGHT, buff = MED_LARGE_BUFF) - documents.to_corner(UP+LEFT) - - signatures = VGroup() - for document in documents: - signature = get_cursive_name("Alice") - signature.set_color(self.signature_color) - line = document[1][-1] - signature.next_to(line, UP, SMALL_BUFF) - signatures.add(signature) - - self.play( - FadeOut(self.title), - LaggedStartMap(FadeIn, documents, run_time = 1) - ) - self.play(Write(signatures)) - self.wait() - - self.signatures = signatures - self.documents = documents - - def show_digital_signatures(self): - rect = SurroundingRectangle(VGroup( - self.public_keys[0], - self.private_key_rects[0], - self.locks[0] - )) - digital_signatures = VGroup() - for i, signature in enumerate(self.signatures): - bits = bin(hash(str(i)))[-8:] - digital_signature = TexMobject(bits + "\\dots") - digital_signature.scale(0.7) - digital_signature.set_color(signature.get_color()) - digital_signature.move_to(signature, DOWN) - digital_signatures.add(digital_signature) - - arrows = VGroup(*[ - Arrow( - rect.get_corner(UP), sig.get_bottom(), - tip_length = 0.15, - color = WHITE - ) - for sig in digital_signatures - ]) - - words = VGroup(*list(map( - TextMobject, - ["Different messages", "Completely different signatures"] - ))) - words.arrange(DOWN, aligned_edge = LEFT) - words.scale(1.3) - words.next_to(self.documents, RIGHT) - - self.play(FadeIn(rect)) - self.play(*list(map(ShowCreation, arrows))) - self.play(Transform(self.signatures, digital_signatures)) - self.play(*[ - ApplyMethod(pi.change, "pondering", digital_signatures) - for pi in self.pi_creatures - ]) - for word in words: - self.play(FadeIn(word)) - self.wait() - self.play(FadeOut(words)) - - def show_signing_functions(self): - sign = TextMobject( - "Sign(", "Message", ", ", "sk", ") = ", "Signature", - arg_separator = "" - ) - sign.to_corner(UP+RIGHT) - verify = TextMobject( - "Verify(", "Message", ", ", "Signature", ", ", "pk", ") = ", "T/F", - arg_separator = "" - ) - for mob in sign, verify: - mob.set_color_by_tex("sk", self.private_color) - mob.set_color_by_tex("pk", self.public_color) - mob.set_color_by_tex( - "Signature", self.signature_color, - ) - for name in "Message", "sk", "Signature", "pk": - part = mob.get_part_by_tex(name) - if part is not None: - setattr(mob, name.lower(), part) - verify.next_to(sign, DOWN, MED_LARGE_BUFF, LEFT) - VGroup(sign, verify).to_corner(UP+RIGHT) - - private_key = self.private_key_rects[0] - public_key = self.public_keys[0] - message = self.documents[0] - signature = self.signatures[0] - - self.play(*[ - FadeIn(part) - for part in sign - if part not in [sign.message, sign.sk, sign.signature] - ]) - self.play(ReplacementTransform( - message.copy(), VGroup(sign.message) - )) - self.wait() - self.play(ReplacementTransform( - private_key.copy(), sign.sk - )) - self.wait() - self.play(ReplacementTransform( - VGroup(sign.sk, sign.message).copy(), - VGroup(sign.signature) - )) - self.wait() - self.play(Indicate(sign.sk)) - self.wait() - self.play(Indicate(sign.message)) - self.wait() - self.play(*[ - FadeIn(part) - for part in verify - if part not in [ - verify.message, verify.signature, - verify.pk, verify[-1] - ] - ]) - self.wait() - self.play( - ReplacementTransform( - sign.message.copy(), verify.message - ), - ReplacementTransform( - sign.signature.copy(), verify.signature - ) - ) - self.wait() - self.play(ReplacementTransform( - public_key.copy(), VGroup(verify.pk) - )) - self.wait() - self.play(Write(verify[-1])) - self.wait() - -class TryGuessingDigitalSignature(Scene): - def construct(self): - verify = TextMobject( - "Verify(", "Message", ", ", - "256 bit Signature", ", ", "pk", ")", - arg_separator = "" - ) - verify.scale(1.5) - verify.shift(DOWN) - signature = verify.get_part_by_tex("Signature") - verify.set_color_by_tex("Signature", BLUE) - verify.set_color_by_tex("pk", GREEN) - brace = Brace(signature, UP) - - zeros_row = TexMobject("0"*32) - zeros = VGroup(*[zeros_row.copy() for x in range(8)]) - zeros.arrange(DOWN, buff = SMALL_BUFF) - zeros.next_to(brace, UP) - - self.add(verify) - self.play( - GrowFromCenter(brace), - FadeIn( - zeros, - lag_ratio = 0.5, - run_time = 3 - ) - ) - self.wait() - for n in range(2**10): - last_row = zeros[-1] - binary = bin(n)[2:] - for i, bit_str in enumerate(reversed(binary)): - curr_bit = last_row.submobjects[-i-1] - new_bit = TexMobject(bit_str) - new_bit.replace(curr_bit, dim_to_match = 1) - last_row.submobjects[-i-1] = new_bit - self.remove(curr_bit) - self.add(last_row) - self.wait(1./30) - -class WriteTwoTo256PossibleSignatures(Scene): - def construct(self): - words = TextMobject( - "$2^{256}$", "possible\\\\", "signatures" - ) - words.scale(2) - words.set_color_by_tex("256", BLUE) - self.play(Write(words)) - self.wait() - -class SupplementVideoWrapper(Scene): - def construct(self): - title = TextMobject("How secure is 256 bit security?") - title.scale(1.5) - title.to_edge(UP) - rect = ScreenRectangle(height = 6) - rect.next_to(title, DOWN) - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - -class FeelConfidentWithVerification(PiCreatureScene): - def construct(self): - self.show_verification() - self.show_secret_key() - - def show_verification(self): - verify = TextMobject( - "Verify(", "Message", ", ", - "256 bit Signature", ", ", "pk", ")", - arg_separator = "" - ) - signature_word = verify.get_part_by_tex("Signature") - verify.set_color_by_tex("Signature", BLUE) - verify.set_color_by_tex("pk", GREEN) - brace = Brace(signature_word, UP) - signature = sha256_tex_mob("Signature") - signature.next_to(brace, UP) - signature.set_color(BLUE_C) - - rhs = TextMobject("=", "True") - rhs.set_color_by_tex("True", YELLOW) - rhs.next_to(verify, RIGHT) - - pk = verify.get_part_by_tex("pk") - sk = TextMobject("sk") - sk.set_color(RED) - arrow = TexMobject("\\Updownarrow") - arrow.next_to(pk, DOWN) - sk.next_to(arrow, DOWN) - sk_group = VGroup(arrow, sk) - lock_box = SurroundingRectangle(sk_group, buff = SMALL_BUFF) - lock_box.set_color(RED) - lock = SVGMobject( - file_name = "lock", - fill_color = LIGHT_GREY, - height = 0.5, - ) - lock.next_to(lock_box, LEFT, SMALL_BUFF) - - self.add(verify) - self.play( - GrowFromCenter(brace), - Write(signature), - self.pi_creature.change, "pondering" - ) - self.play(ReplacementTransform( - verify.copy(), rhs - )) - self.wait() - self.play(self.pi_creature.change, "happy") - self.play(Write(sk_group)) - self.play( - ShowCreation(lock_box), - DrawBorderThenFill(lock, run_time = 1) - ) - self.wait(2) - - - def show_secret_key(self): - pass - - - ##### - - def create_pi_creature(self): - return Randolph().to_corner(DOWN+LEFT) - -class IncludeTransactionNumber(LedgerScene): - CONFIG = { - "ledger_width" : 7, - } - def construct(self): - self.add_ledger_and_network() - self.add_signed_payment() - self.fail_to_sign_new_transaction() - self.copy_payment_many_times() - self.add_ids() - - def add_signed_payment(self): - line = self.add_payment_line_to_ledger( - "Alice", "Bob", 100 - ) - signature = self.get_signature() - signature.scale(0.7) - signature.next_to(line, RIGHT) - signature.save_state() - signature.scale(0.1) - signature.move_to(self.alice) - - self.play(Write(line, run_time = 1)) - self.play( - signature.restore, - self.alice.change, "raise_left_hand" - ) - self.wait() - self.play(self.alice.change, "happy") - self.wait() - - line.add(signature) - - def fail_to_sign_new_transaction(self): - payment = self.add_payment_line_to_ledger("Alice", "Bob", 3000) - q_marks = TexMobject("???") - q_marks.next_to(payment, RIGHT) - cross = Cross(payment) - payment.save_state() - payment.move_to(self.bob.get_corner(UP+LEFT)) - payment.set_fill(opacity = 0) - - self.play( - self.bob.change, "raise_right_hand", - payment.restore, - ) - self.play( - self.bob.change, "confused", - Write(q_marks) - ) - self.play(ShowCreation(cross)) - self.wait() - self.play(*list(map(FadeOut, [payment, cross, q_marks]))) - self.ledger.content.remove(payment) - - def copy_payment_many_times(self): - line = self.ledger.content[-1] - copies = VGroup(*[line.copy() for x in range(4)]) - copies.arrange(DOWN, buff = MED_SMALL_BUFF) - copies.next_to(line, DOWN, buff = MED_SMALL_BUFF) - - self.play( - LaggedStartMap(FadeIn, copies, run_time = 3), - self.bob.change, "conniving", - ) - self.play(self.alice.change, "angry") - self.wait() - - self.copies = copies - - def add_ids(self): - top_line = self.ledger.content[-1] - lines = VGroup(top_line, *self.copies) - numbers = VGroup() - old_signatures = VGroup() - new_signatures = VGroup() - colors = list(Color(BLUE_B).range_to(GREEN_B, len(lines))) - for i, line in enumerate(lines): - number = TexMobject(str(i)) - number.scale(0.7) - number.set_color(YELLOW) - number.next_to(line, LEFT) - numbers.add(number) - line.add_to_back(number) - old_signature = line[-1] - new_signature = self.get_signature() - new_signature.replace(old_signature) - new_signature.set_color(colors[i]) - old_signatures.add(old_signature) - new_signatures.add(VGroup(new_signature)) - line.remove(old_signature) - - self.play( - Write(numbers), - self.alice.change, "thinking" - ) - self.play(FadeOut(old_signatures)) - self.play(ReplacementTransform( - lines.copy(), new_signatures, - lag_ratio = 0.5, - run_time = 2, - )) - self.play(self.bob.change, "erm") - self.wait(2) - -class ProtocolWithDigitalSignatures(InitialProtocol): - def construct(self): - self.force_skipping() - InitialProtocol.construct(self) - self.revert_to_original_skipping_status() - - rect = SurroundingRectangle(self.items[-1]) - rect.set_color(RED) - - new_item = self.get_new_item( - "Only signed transactions are valid" - ) - new_item.set_color(YELLOW) - - self.play(Write(new_item)) - self.wait() - self.play(ShowCreation(rect)) - self.wait() - -class SignedLedgerScene(LedgerScene): - CONFIG = { - "sign_transactions" : True, - "enumerate_lines" : True, - "ledger_width" : 7.5, - } - -class CharlieRacksUpDebt(SignedLedgerScene): - CONFIG = { - "payments" : [ - ("Charlie", "Alice", 100), - ("Charlie", "Bob", 200), - ("Charlie", "You", 800), - ("Charlie", "Bob", 600), - ("Charlie", "Alice", 900), - ], - } - def construct(self): - self.add_ledger_and_network() - lines = VGroup(*[ - self.add_payment_line_to_ledger(*payment) - for payment in self.payments - ]) - - self.play(LaggedStartMap( - FadeIn, lines, - run_time = 3, - lag_ratio = 0.25 - )) - self.play(*[ - ApplyMethod(pi.change, "sassy", self.charlie) - for pi in self.pi_creatures - if pi is not self.charlie - ]) - self.play( - self.charlie.shift, FRAME_X_RADIUS*RIGHT, - rate_func = running_start - ) - self.play(*[ - ApplyMethod(pi.change, "angry", self.charlie) - for pi in self.get_pi_creatures() - ]) - self.wait() - -class CharlieFeelsGuilty(Scene): - def construct(self): - charlie = PiCreature(color = GREY_BROWN) - charlie.scale(2) - - self.play(FadeIn(charlie)) - self.play(charlie.change, "sad") - for x in range(2): - self.play(Blink(charlie)) - self.wait(2) - -class ThinkAboutSettlingUp(Scene): - def construct(self): - randy = Randolph() - randy.to_corner(DOWN+LEFT) - - self.play(PiCreatureBubbleIntroduction( - randy, - "You don't \\emph{actually} \\\\" + \ - "need to settle up $\\dots$", - bubble_class = ThoughtBubble, - target_mode = "thinking" - )) - self.play(Blink(randy)) - self.wait() - -class DontAllowOverdrawing(InitialProtocol): - def construct(self): - self.add_title() - lines = list(map(self.get_new_item, [ - "Anyone can add lines to the Ledger \\,", - "Only signed transactions are valid \\,", - "No overspending" - ])) - lines[2].set_color(YELLOW) - - self.add(*lines[:2]) - self.wait() - self.play(Write(lines[2])) - self.wait() - -class LedgerWithInitialBuyIn(SignedLedgerScene): - def construct(self): - self.add_ledger_and_network() - self.everyone_buys_in() - self.add_initial_lines() - self.add_charlie_payments() - self.point_out_charlie_is_broke() - self.running_balance() - - def everyone_buys_in(self): - center = self.network.get_center() - moneys = VGroup(*[ - TexMobject("\\$100") - for pi in self.pi_creatures - ]) - moneys.set_color(GREEN) - for pi, money in zip(reversed(self.pi_creatures), moneys): - vect = pi.get_center() - center - money.next_to(center, vect, SMALL_BUFF) - money.add_background_rectangle() - money.save_state() - money.scale(0.01) - corner = pi.get_corner(UP + np.sign(vect[0])*LEFT) - money.move_to(corner) - - self.play( - LaggedStartMap( - ApplyMethod, moneys, - lambda m : (m.restore,) - ), - self.charlie.change, "raise_right_hand", - self.you.change, "raise_right_hand", - ) - self.network.add(moneys) - - def add_initial_lines(self): - lines = VGroup() - for name in self.get_names(): - new_line = TextMobject( - name.capitalize(), - "get" if name == "you" else "gets", - "\\$100" - ) - new_line.set_color_by_tex( - name.capitalize(), - self.get_color_from_name(name) - ) - new_line.set_color_by_tex("100", GREEN) - self.add_line_to_ledger(new_line) - lines.add(new_line) - line = Line(LEFT, RIGHT) - line.set_width(self.ledger.get_width()) - line.scale_in_place(0.9) - line.next_to(lines[-1], DOWN, SMALL_BUFF, LEFT) - line.set_stroke(width = 1) - lines[-1].add(line) - - self.play( - LaggedStartMap(FadeIn, lines), - *[ - ApplyMethod(pi.change, "thinking", self.ledger) - for pi in self.pi_creatures - ] - ) - self.wait() - - def add_charlie_payments(self): - payments = [ - ("Charlie", "Alice", 50), - ("Charlie", "Bob", 50), - ("Charlie", "You", 20), - ] - new_lines = VGroup(*[ - self.add_payment_line_to_ledger(*payment) - for payment in payments - ]) - - for line in new_lines: - self.play(Write(line, run_time = 1)) - self.wait() - - def point_out_charlie_is_broke(self): - charlie_lines = VGroup(*[ - VGroup(*self.ledger.content[i][1:5]) - for i in (3, 5, 6, 7) - ]) - rects = VGroup(*[ - SurroundingRectangle(line) - for line in charlie_lines - ]) - rects.set_color(YELLOW) - rects.set_stroke(width = 2) - last_rect = rects[-1] - last_rect.set_stroke(RED, 4) - rects.remove(last_rect) - invalid = TextMobject("Invalid") - invalid.set_color(RED) - invalid.next_to(last_rect, DOWN) - - self.play(ShowCreation(rects)) - self.play(self.charlie.change_mode, "guilty") - self.wait() - self.play(ShowCreation(last_rect)) - self.play(*[ - ApplyMethod(pi.change, "sassy", self.charlie) - for pi in self.pi_creatures - if pi is not self.charlie - ]) - self.play(Write(invalid)) - self.wait(2) - self.play(*list(map(FadeOut, [rects, last_rect, invalid]))) - - def running_balance(self): - charlie_lines = VGroup(*[ - VGroup(*self.ledger.content[i][1:5]) - for i in (3, 5, 6, 7) - ]) - signatures = VGroup(*[ - self.ledger.content[i][5] - for i in (5, 6, 7) - ]) - rect = Rectangle(color = WHITE) - rect.set_fill(BLACK, 0.8) - rect.stretch_to_fit_height(self.ledger.get_height() - 2*MED_SMALL_BUFF) - title = TextMobject("Charlie's running \\\\ balance") - rect.stretch_to_fit_width(title.get_width() + 2*MED_SMALL_BUFF) - rect.move_to(self.ledger.get_right()) - title.next_to(rect.get_top(), DOWN) - balance = VGroup(rect, title) - - lines = VGroup(*list(map(TextMobject, [ - "\\$100", "\\$50", "\\$0", "Overdrawn" - ]))) - lines.set_color(GREEN) - lines[-1].set_color(RED) - arrows = VGroup() - for line, c_line in zip(lines, charlie_lines): - line.next_to(rect.get_left(), RIGHT, LARGE_BUFF) - line.shift( - (c_line.get_center() - line.get_center())[1]*UP - ) - arrow = Arrow(c_line, line) - arrows.add(arrow) - - self.pi_creatures.remove(self.alice, self.charlie) - self.play( - FadeOut(signatures), - FadeIn(balance) - ) - self.play( - LaggedStartMap(FadeIn, lines, run_time = 3), - LaggedStartMap(ShowCreation, arrows, run_time = 3), - ) - self.wait() - -class RemovedConnectionBetweenLedgerAndCash(TeacherStudentsScene): - def construct(self): - ledger = Rectangle( - height = 2, width = 1.5, - color = WHITE - ) - ledger_name = TextMobject("Ledger") - ledger_name.set_width(ledger.get_width() - MED_SMALL_BUFF) - ledger_name.next_to(ledger.get_top(), DOWN) - ledger.add(ledger_name) - - arrow = TexMobject("\\leftrightarrow") - cash = TexMobject("\\$\\$\\$") - cash.set_color(GREEN) - arrow.next_to(ledger, RIGHT) - cash.next_to(arrow, RIGHT) - group = VGroup(ledger, arrow, cash) - group.next_to(self.teacher, UP+LEFT) - - self.add(group) - self.play( - Animation(group), - self.teacher.change, "raise_right_hand" - ) - self.play( - arrow.shift, 2*UP, - arrow.set_fill, None, 0 - ) - self.play( - ledger.shift, LEFT, - cash.shift, RIGHT - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = ledger, - added_anims = [self.teacher.change, "happy"] - ) - self.wait(3) - -class RenameToLedgerDollars(LedgerScene): - CONFIG = { - "payments" : [ - ("Alice", "Bob", 20), - ("Charlie", "You", 80), - ("Bob", "Charlie", 60), - ("Bob", "Alice", 30), - ("Alice", "You", 100), - ], - "enumerate_lines" : True, - "line_number_color" : WHITE, - } - def construct(self): - self.add(self.get_ledger()) - self.add_bubble() - self.jump_in_to_middle() - self.add_payments_in_dollars() - self.rewrite_as_ledger_dollars() - - def add_bubble(self): - randy = self.pi_creature - bubble = SpeechBubble(direction = RIGHT) - bubble.write("Who needs \\\\ cash?") - bubble.resize_to_content() - bubble.add(bubble.content) - bubble.pin_to(randy) - bubble.shift(MED_LARGE_BUFF*RIGHT) - self.bubble = bubble - - self.add(randy, bubble) - self.add_foreground_mobject(bubble) - - def jump_in_to_middle(self): - h_line = self.ledger.content[0] - dots = TexMobject("\\vdots") - dots.next_to(h_line.get_left(), DOWN) - h_line.add(dots) - self.add(h_line) - point = VectorizedPoint(h_line.get_corner(DOWN+LEFT)) - point.shift(MED_SMALL_BUFF*LEFT) - self.ledger.content.add(*[ - point.copy() - for x in range(103) - ]) - - def add_payments_in_dollars(self): - lines = VGroup(*[ - self.add_payment_line_to_ledger(*payment) - for payment in self.payments - ]) - - self.play(LaggedStartMap( - FadeIn, lines, - run_time = 4, - lag_ratio = 0.3 - )) - self.wait() - - self.payment_lines = lines - - def rewrite_as_ledger_dollars(self): - curr_lines = self.payment_lines - amounts = VGroup(*[line[4] for line in curr_lines]) - amounts.target = VGroup() - for amount in amounts: - dollar_sign = amount[0] - amount.remove(dollar_sign) - amount.add(dollar_sign) - tex_string = amount.get_tex_string() - ld = TextMobject(tex_string[2:] + " LD") - ld.set_color(YELLOW) - ld.scale(0.8) - ld.move_to(amount, LEFT) - amounts.target.add(ld) - - ledger_dollars = TextMobject("Ledger Dollars \\\\ ``LD'' ") - ledger_dollars.set_color(YELLOW) - ledger_dollars.next_to(self.ledger, RIGHT) - - self.play( - Write(ledger_dollars), - FadeOut(self.bubble), - self.pi_creature.change, "thinking", ledger_dollars - ) - self.play(MoveToTarget(amounts)) - self.wait(2) - - - ### - - def create_pi_creatures(self): - LedgerScene.create_pi_creatures(self) - randy = Randolph(mode = "shruggie").flip() - randy.to_corner(DOWN+RIGHT) - return VGroup(randy) - -class ExchangeCashForLedgerDollars(LedgerScene): - CONFIG = { - "sign_transactions" : True, - "denomination" : "LD", - "ledger_width" : 7.5, - "ledger_height" : 6, - } - def construct(self): - self.add_ledger_and_network() - self.add_ellipsis() - self.add_title() - self.give_ten_dollar_bill() - self.add_bob_pays_alice_line() - self.everyone_thinks() - - def add_title(self): - self.ledger.shift(DOWN) - title = TextMobject( - "Exchange", "LD", "for", "\\$\\$\\$" - ) - title.set_color_by_tex("LD", YELLOW) - title.set_color_by_tex("\\$", GREEN) - title.scale(1.3) - title.to_edge(UP).shift(LEFT) - - self.play(Write(title)) - self.wait() - - def give_ten_dollar_bill(self): - bill = TenDollarBill() - bill.next_to(self.alice.get_corner(UP+RIGHT), UP) - bill.generate_target() - bill.target.next_to(self.bob.get_corner(UP+LEFT), UP) - - arrow = Arrow(bill, bill.target, color = GREEN) - - small_bill = bill.copy() - small_bill.scale(0.01, about_point = bill.get_bottom()) - - self.play( - ReplacementTransform(small_bill, bill), - self.alice.change, "raise_right_hand" - ) - self.play(ShowCreation(arrow)) - self.play(MoveToTarget(bill)) - self.play(self.bob.change, "happy", bill) - self.wait() - - def add_bob_pays_alice_line(self): - line = self.add_payment_line_to_ledger( - "Bob", "Alice", 10 - ) - line.save_state() - line.scale(0.01) - line.move_to(self.bob.get_corner(UP+LEFT)) - self.play(self.bob.change, "raise_right_hand", line) - self.play(line.restore, run_time = 2) - self.wait() - - def everyone_thinks(self): - self.play(*[ - ApplyMethod(pi.change, "thinking", self.ledger) - for pi in self.pi_creatures - ]) - self.wait(4) - -class BitcoinIsALedger(Scene): - def construct(self): - self.add_btc_to_ledger() - self.add_currency_to_tx_history() - - def add_btc_to_ledger(self): - logo = BitcoinLogo() - ledger = self.get_ledger() - arrow = TexMobject("\\Leftrightarrow") - group = VGroup(logo, arrow, ledger) - group.arrange(RIGHT) - - self.play(DrawBorderThenFill(logo)) - self.wait() - self.play( - Write(arrow), - Write(ledger) - ) - self.wait() - - self.btc_to_ledger = group - - def add_currency_to_tx_history(self): - equation = TextMobject( - "Currency", "=", "Transaction history" - ) - equation.set_color_by_tex("Currency", BITCOIN_COLOR) - equation.shift( - self.btc_to_ledger[1].get_center() - \ - equation[1].get_center() + 2*UP - ) - - for part in reversed(equation): - self.play(FadeIn(part)) - self.wait() - - def get_ledger(self): - rect = Rectangle(height = 2, width = 1.5) - title = TextMobject("Ledger") - title.set_width(0.8*rect.get_width()) - title.next_to(rect.get_top(), DOWN, SMALL_BUFF) - - lines = VGroup(*[ - Line(LEFT, RIGHT) - for x in range(8) - ]) - lines.arrange(DOWN, buff = SMALL_BUFF) - lines.stretch_to_fit_width(title.get_width()) - lines.next_to(title, DOWN) - return VGroup(rect, title, lines) - -class BigDifferenceBetweenLDAndCryptocurrencies(Scene): - def construct(self): - ld = TextMobject("LD").scale(1.5).set_color(YELLOW) - btc = BitcoinLogo() - eth = EthereumLogo() - ltc = LitecoinLogo() - logos = VGroup(ltc, eth, ld, btc) - cryptos = VGroup(btc, eth, ltc) - for logo in cryptos: - logo.set_height(1) - vects = compass_directions(4, DOWN+LEFT) - for logo, vect in zip(logos, vects): - logo.move_to(0.75*vect) - - centralized = TextMobject("Centralized") - decentralized = TextMobject("Decentralized") - words = VGroup(centralized, decentralized) - words.scale(1.5) - words.to_edge(UP) - for word, vect in zip(words, [RIGHT, LEFT]): - word.shift(FRAME_X_RADIUS*vect/2) - - self.add(logos) - self.wait() - self.play( - cryptos.next_to, decentralized, DOWN, LARGE_BUFF, - ld.next_to, centralized, DOWN, LARGE_BUFF, - ) - self.play(*list(map(Write, words))) - self.wait(2) - -class DistributedLedgerScene(LedgerScene): - def get_large_network(self): - network = self.get_network() - network.set_height(FRAME_HEIGHT - LARGE_BUFF) - network.center() - for pi in self.pi_creatures: - pi.label.scale(0.8, about_point = pi.get_bottom()) - return network - - def get_distributed_ledgers(self): - ledger = self.get_ledger() - title = ledger[1] - h_line = ledger.content - title.set_width(0.7*ledger.get_width()) - title.next_to(ledger.get_top(), DOWN, MED_LARGE_BUFF) - h_line.next_to(title, DOWN) - added_lines = VGroup(*[h_line.copy() for x in range(5)]) - added_lines.arrange(DOWN, buff = MED_LARGE_BUFF) - added_lines.next_to(h_line, DOWN, MED_LARGE_BUFF) - ledger.content.add(added_lines) - - ledgers = VGroup() - for pi in self.pi_creatures: - pi.ledger = ledger.copy() - pi.ledger.set_height(pi.get_height()) - pi.ledger[0].set_color(pi.get_color()) - vect = pi.get_center()-self.pi_creatures.get_center() - x_vect = vect[0]*RIGHT - pi.ledger.next_to(pi, x_vect, SMALL_BUFF) - ledgers.add(pi.ledger) - return ledgers - - def add_large_network_and_distributed_ledger(self): - self.add(self.get_large_network()) - self.add(self.get_distributed_ledgers()) - -class TransitionToDistributedLedger(DistributedLedgerScene): - CONFIG = { - "sign_transactions" : True, - "ledger_width" : 7.5, - "ledger_height" : 6, - "enumerate_lines" : True, - "denomination" : "LD", - "line_number_color" : WHITE, - "ledger_line_height" : 0.35, - } - def construct(self): - self.add_ledger_and_network() - self.ledger.shift(DOWN) - self.add_ellipsis() - - self.ask_where_is_ledger() - self.add_various_payements() - self.ask_who_controls_ledger() - self.distribute_ledger() - self.broadcast_transaction() - self.ask_about_ledger_consistency() - - def ask_where_is_ledger(self): - question = TextMobject("Where", "is", "this?!") - question.set_color(RED) - question.scale(1.5) - question.next_to(self.ledger, UP) - - self.play(Write(question)) - self.wait() - - self.question = question - - def add_various_payements(self): - payments = VGroup(*[ - self.add_payment_line_to_ledger(*payment) - for payment in [ - ("Alice", "Bob", 20), - ("Charlie", "You", 100), - ("You", "Alice", 50), - ("Bob", "You", 30), - ] - ]) - - for payment in payments: - self.play(LaggedStartMap(FadeIn, payment, run_time = 1)) - self.wait() - - def ask_who_controls_ledger(self): - new_question = TextMobject("Who", "controls", "this?!") - new_question.scale(1.3) - new_question.move_to(self.question) - new_question.set_color(RED) - - self.play(Transform(self.question, new_question)) - self.play(*[ - ApplyMethod(pi.change, "confused", new_question) - for pi in self.pi_creatures - ]) - self.wait(2) - - def distribute_ledger(self): - ledger = self.ledger - self.ledger_width = 6 - self.ledger_height = 7 - distribute_ledgers = self.get_distributed_ledgers() - group = VGroup(self.network, distribute_ledgers) - - self.play(FadeOut(self.question)) - self.play(ReplacementTransform( - VGroup(ledger), distribute_ledgers - )) - self.play(*[ - ApplyMethod(pi.change, "pondering", pi.ledger) - for pi in self.pi_creatures - ]) - self.play( - group.set_height, FRAME_HEIGHT - 2, - group.center - ) - self.wait(2) - - def broadcast_transaction(self): - payment = TextMobject( - "Alice", "pays", "Bob", "100 LD" - ) - payment.set_color_by_tex("Alice", self.alice.get_color()) - payment.set_color_by_tex("Bob", self.bob.get_color()) - payment.set_color_by_tex("LD", YELLOW) - payment.scale(0.75) - payment.add_background_rectangle() - payment_copies = VGroup(*[ - payment.copy().next_to(pi, UP) - for pi in self.pi_creatures - ]) - payment = payment_copies[0] - - self.play( - self.alice.change, "raise_right_hand", payment, - Write(payment, run_time = 2) - ) - self.wait() - self.play( - ReplacementTransform( - VGroup(payment), payment_copies, - run_time = 3, - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - Broadcast(self.alice.get_corner(UP+RIGHT)), - self.alice.change, "happy" - ) - self.wait() - pairs = list(zip(payment_copies, self.pi_creatures)) - Scene.play(self, *it.chain(*[ - [ - pi.look_at, pi.ledger[-1], - line.scale, 0.2, - line.next_to, pi.ledger[-1], DOWN, SMALL_BUFF, - ] - for line, pi in pairs - ])) - self.wait(3) - - for line, pi in pairs: - pi.ledger.add(line) - - def ask_about_ledger_consistency(self): - ledgers = VGroup(*[ - pi.ledger - for pi in self.pi_creatures - ]) - - self.play( - FadeOut(self.network), - ledgers.scale, 2, - ledgers.arrange, RIGHT, - ledgers.space_out_submobjects, - ) - - question = TextMobject("Are these the same?") - question.scale(1.5) - question.to_edge(UP) - arrows = VGroup(*[ - Arrow(question.get_bottom(), ledger.get_top()) - for ledger in ledgers - ]) - - self.play(*list(map(ShowCreation, arrows))) - self.play(Write(question)) - self.wait() - -class BobDoubtsBroadcastTransaction(DistributedLedgerScene): - def construct(self): - self.setup_bob_and_charlie() - self.bob_receives_transaction() - self.bob_tries_to_pay_charlie() - - def setup_bob_and_charlie(self): - bob, charlie = self.bob, self.charlie - self.pi_creatures = VGroup(bob, charlie) - for pi in self.pi_creatures: - pi.flip() - self.pi_creatures.scale(2) - self.pi_creatures.arrange(RIGHT, buff = 5) - - for name in "bob", "charlie": - label = TextMobject(name.capitalize()) - pi = getattr(self, name) - label.next_to(pi, DOWN) - pi.label = label - bob.make_eye_contact(charlie) - - self.get_distributed_ledgers() - - self.add(bob, bob.label, bob.ledger) - - def bob_receives_transaction(self): - bob, charlie = self.bob, self.charlie - corner = FRAME_Y_RADIUS*UP + FRAME_X_RADIUS*LEFT - - payment = TextMobject( - "Alice", "pays", "Bob", "10 LD" - ) - payment.set_color_by_tex("Alice", self.alice.get_color()) - payment.set_color_by_tex("Bob", self.bob.get_color()) - payment.set_color_by_tex("LD", YELLOW) - payment.next_to(corner, UP+LEFT) - - self.play( - Broadcast(corner), - ApplyMethod( - payment.next_to, bob, UP, - run_time = 3, - rate_func = squish_rate_func(smooth, 0.3, 1) - ), - ) - self.play(bob.look_at, payment) - self.play( - payment.scale, 0.3, - payment.next_to, bob.ledger[-1], DOWN, SMALL_BUFF - ) - - def bob_tries_to_pay_charlie(self): - bob, charlie = self.bob, self.charlie - chralie_group = VGroup( - charlie, charlie.label, charlie.ledger - ) - - self.play( - PiCreatureSays( - bob, "Did you hear that?", - target_mode = "sassy" - ), - FadeIn(chralie_group) - ) - self.play(charlie.change, "maybe", bob.eyes) - self.wait(2) - -class YouListeningToBroadcasts(LedgerScene): - CONFIG = { - "denomination" : "LD" - } - def construct(self): - ledger = self.get_ledger() - payments = VGroup(*[ - self.add_payment_line_to_ledger(*payment) - for payment in [ - ("Alice", "You", 20), - ("Bob", "You", 50), - ("Charlie", "You", 30), - ] - ]) - self.remove(self.ledger) - corners = [ - FRAME_X_RADIUS*RIGHT*u1 + FRAME_Y_RADIUS*UP*u2 - for u1, u2 in [(-1, 1), (1, 1), (-1, -1)] - ] - you = self.you - you.scale(2) - you.center() - - self.add(you) - for payment, corner in zip(payments, corners): - vect = corner/get_norm(corner) - payment.next_to(corner, vect) - self.play( - Broadcast(corner), - ApplyMethod( - payment.next_to, you, vect, - run_time = 3, - rate_func = squish_rate_func(smooth, 0.3, 1) - ), - you.change_mode, "pondering" - ) - self.wait() - -class AskWhatToAddToProtocol(InitialProtocol): - def construct(self): - self.add_title() - items = VGroup(*list(map(self.get_new_item, [ - "Broadcast transactions", - "Only accept signed transactions", - "No overspending", - ] + [""]*6))) - brace = Brace(VGroup(*items[3:]), LEFT) - question = TextMobject("What to \\\\ add here?") - question.set_color(RED) - question.scale(1.5) - brace.set_color(RED) - question.next_to(brace, LEFT) - - self.add(*items[:3]) - self.play(GrowFromCenter(brace)) - self.play(Write(question)) - self.wait() - -class TrustComputationalWork(DistributedLedgerScene): - def construct(self): - self.add_ledger() - self.show_work() - - def add_ledger(self): - ledgers = self.get_distributed_ledgers() - ledger = ledgers[0] - ledger.scale(3) - ledger[1].scale_in_place(2./3) - ledger.center().to_edge(UP).shift(4*LEFT) - plus = TexMobject("+") - plus.next_to(ledger, RIGHT) - - self.add(ledger, plus) - self.ledger = ledger - self.plus = plus - - def show_work(self): - zeros = TexMobject("0"*32) - zeros.next_to(self.plus, RIGHT) - brace = Brace(zeros, DOWN) - words = brace.get_text("Computational work") - self.add(brace, words) - - for n in range(2**12): - binary = bin(n)[2:] - for i, bit_str in enumerate(reversed(binary)): - curr_bit = zeros.submobjects[-i-1] - new_bit = TexMobject(bit_str) - new_bit.replace(curr_bit, dim_to_match = 1) - if bit_str == "1": - new_bit.set_color(YELLOW) - zeros.submobjects[-i-1] = new_bit - self.remove(curr_bit) - self.add(zeros) - self.wait(1./30) - -class TrustComputationalWorkSupplement(Scene): - def construct(self): - words = TextMobject( - "Main tool: ", "Cryptographic hash functions" - ) - words[1].set_color(YELLOW) - self.add(words[0]) - self.play(Write(words[1])) - self.wait() - -class FraudIsInfeasible(Scene): - def construct(self): - words = TextMobject( - "Fraud", "$\\Leftrightarrow$", - "Computationally infeasible" - ) - words.set_color_by_tex("Fraud", RED) - words.to_edge(UP) - self.play(FadeIn(words[0])) - self.play(FadeIn(words[2])) - self.play(Write(words[1])) - self.wait() - -class ThisIsWellIntoTheWeeds(TeacherStudentsScene): - def construct(self): - idea = TextMobject("Proof of work") - idea.move_to(self.teacher.get_corner(UP+LEFT)) - idea.shift(MED_LARGE_BUFF*UP) - idea.save_state() - lightbulb = Lightbulb() - lightbulb.next_to(idea, UP) - idea.shift(DOWN) - idea.set_fill(opacity = 0) - - self.teacher_says( - "We're well into \\\\ the weeds now", - target_mode = "sassy", - added_anims = [ - ApplyMethod(pi.change, mode) - for pi, mode in zip(self.students, [ - "hooray", "sad", "erm" - ]) - ], - ) - self.wait() - self.play( - idea.restore, - RemovePiCreatureBubble( - self.teacher, target_mode = "hooray", - look_at_arg = lightbulb - ), - ) - self.change_student_modes( - *["pondering"]*3, - added_anims = [LaggedStartMap(FadeIn, lightbulb)] - ) - self.play(LaggedStartMap( - ApplyMethod, lightbulb, - lambda b : (b.set_color, YELLOW_A), - rate_func = there_and_back - )) - self.wait(2) - -class IntroduceSHA256(Scene): - def construct(self): - self.introduce_evaluation() - self.inverse_function_question() - self.issue_challenge() - self.shift_everything_down() - self.guess_and_check() - - def introduce_evaluation(self): - messages = [ - "3Blue1Brown", - "3Blue1Crown", - "Mathologer", - "Infinite Series", - "Numberphile", - "Welch Labs", - "3Blue1Brown", - ] - groups = VGroup() - for message in messages: - lhs = TextMobject( - "SHA256", "(``", message, "'') =", - arg_separator = "" - ) - lhs.set_color_by_tex(message, BLUE) - digest = sha256_tex_mob(message) - digest.next_to(lhs, RIGHT) - group = VGroup(lhs, digest) - group.to_corner(UP+RIGHT) - group.shift(MED_LARGE_BUFF*DOWN) - groups.add(group) - - group = groups[0] - lhs, digest = group - sha, lp, message, lp = lhs - sha_brace = Brace(sha, UP) - message_brace = Brace(message, DOWN) - digest_brace = Brace(digest, DOWN) - sha_text = sha_brace.get_text("", "Hash function") - sha_text.set_color(YELLOW) - message_text = message_brace.get_text("Message/file") - message_text.set_color(BLUE) - digest_text = digest_brace.get_text("``Hash'' or ``Digest''") - brace_text_pairs = [ - (sha_brace, sha_text), - (message_brace, message_text), - (digest_brace, digest_text), - ] - - looks_random = TextMobject("Looks random") - looks_random.set_color(MAROON_B) - looks_random.next_to(digest_text, DOWN) - - self.add(group) - self.remove(digest) - for brace, text in brace_text_pairs: - if brace is digest_brace: - self.play(LaggedStartMap( - FadeIn, digest, - run_time = 4, - lag_ratio = 0.05 - )) - self.wait() - self.play( - GrowFromCenter(brace), - Write(text, run_time = 2) - ) - self.wait() - self.play(Write(looks_random)) - self.wait(2) - for mob in digest, message: - self.play(LaggedStartMap( - ApplyMethod, mob, - lambda m : (m.set_color, YELLOW), - rate_func = there_and_back, - run_time = 1 - )) - self.wait() - self.play(FadeOut(looks_random)) - - new_lhs, new_digest = groups[1] - char = new_lhs[2][-5] - arrow = Arrow(UP, ORIGIN, buff = 0) - arrow.next_to(char, UP) - arrow.set_color(RED) - self.play(ShowCreation(arrow)) - for new_group in groups[1:]: - new_lhs, new_digest = new_group - new_message = new_lhs[2] - self.play( - Transform(lhs, new_lhs), - message_brace.stretch_to_fit_width, new_message.get_width(), - message_brace.next_to, new_message, DOWN, - MaintainPositionRelativeTo(message_text, message_brace), - MaintainPositionRelativeTo(sha_brace, lhs[0]), - MaintainPositionRelativeTo(sha_text, sha_brace) - ) - self.play(Transform( - digest, new_digest, - run_time = 2, - lag_ratio = 0.5, - path_arc = np.pi/2 - )) - if arrow in self.get_mobjects(): - self.wait() - self.play(FadeOut(arrow)) - self.wait() - - new_sha_text = TextMobject( - "Cryptographic", "hash function" - ) - new_sha_text.next_to(sha_brace, UP) - new_sha_text.shift_onto_screen() - new_sha_text.set_color(YELLOW) - new_sha_text[0].set_color(GREEN) - self.play(Transform(sha_text, new_sha_text)) - self.wait() - - self.lhs = lhs - self.message = message - self.digest = digest - self.digest_text = digest_text - self.message_text = message_text - - def inverse_function_question(self): - arrow = Arrow(3*RIGHT, 3*LEFT, buff = 0) - arrow.set_stroke(width = 8) - arrow.set_color(RED) - everything = VGroup(*self.get_mobjects()) - arrow.next_to(everything, DOWN) - words = TextMobject("Inverse is infeasible") - words.set_color(RED) - words.next_to(arrow, DOWN) - - self.play(ShowCreation(arrow)) - self.play(Write(words)) - self.wait() - - def issue_challenge(self): - desired_output_text = TextMobject("Desired output") - desired_output_text.move_to(self.digest_text) - desired_output_text.set_color(YELLOW) - new_digest = sha256_tex_mob("Challenge") - new_digest.replace(self.digest) - q_marks = TextMobject("???") - q_marks.move_to(self.message_text) - q_marks.set_color(BLUE) - - self.play( - Transform( - self.digest, new_digest, - run_time = 2, - lag_ratio = 0.5, - path_arc = np.pi/2 - ), - Transform(self.digest_text, desired_output_text) - ) - self.play( - FadeOut(self.message), - Transform(self.message_text, q_marks) - ) - self.wait() - - def shift_everything_down(self): - everything = VGroup(*self.get_top_level_mobjects()) - self.play( - everything.scale, 0.85, - everything.to_edge, DOWN - ) - - def guess_and_check(self): - groups = VGroup() - for x in range(32): - message = "Guess \\#%d"%x - lhs = TextMobject( - "SHA256(``", message, "'') = ", - arg_separator = "" - ) - lhs.set_color_by_tex("Guess", BLUE) - digest = sha256_tex_mob(message) - digest.next_to(lhs, RIGHT) - group = VGroup(lhs, digest) - group.scale(0.85) - group.next_to(self.digest, UP, aligned_edge = RIGHT) - group.to_edge(UP) - groups.add(group) - - group = groups[0] - self.play(FadeIn(group)) - for new_group in groups[1:]: - self.play(Transform( - group[0], new_group[0], - run_time = 0.5, - )) - self.play(Transform( - group[1], new_group[1], - run_time = 1, - lag_ratio = 0.5 - )) - -class PonderScematic(Scene): - def construct(self): - randy = Randolph() - randy.to_corner(DOWN+LEFT) - self.play(randy.change, "confused", ORIGIN) - for x in range(3): - self.play(Blink(randy)) - self.wait(2) - -class ViewingSLLCertificate(ExternallyAnimatedScene): - pass - -class SHA256ToProofOfWork(TeacherStudentsScene): - def construct(self): - sha = TextMobject("SHA256") - proof = TextMobject("Proof of work") - arrow = Arrow(LEFT, RIGHT) - group = VGroup(sha, arrow, proof) - group.arrange(RIGHT) - group.next_to(self.teacher, UP, buff = LARGE_BUFF) - group.to_edge(RIGHT, buff = LARGE_BUFF) - - self.play( - Write(sha, run_time = 1), - self.teacher.change, "raise_right_hand" - ) - self.play(ShowCreation(arrow)) - self.play(Write(proof, run_time = 1)) - self.wait(3) - -class IntroduceNonceOnTrasactions(LedgerScene): - CONFIG = { - "denomination" : "LD", - "ledger_width" : 5, - "ledger_line_height" : 0.3, - } - def construct(self): - self.add(self.get_ledger()) - self.hash_with_nonce() - self.write_probability() - self.guess_and_check() - self.name_proof_of_work() - self.change_ledger() - self.guess_and_check() - - def hash_with_nonce(self): - ledger = self.ledger - self.add(*[ - self.add_payment_line_to_ledger(*payment) - for payment in [ - ("Alice", "Bob", 20), - ("Alice", "You", 30), - ("Charlie", "You", 100), - ] - ]) - - nonce = TexMobject(str(2**30 + hash("Hey there")%(2**15))) - nonce.next_to(ledger, RIGHT, LARGE_BUFF) - nonce.set_color(GREEN_C) - nonce_brace = Brace(nonce, DOWN) - special_word = nonce_brace.get_text("Special number") - arrow = Arrow(LEFT, RIGHT, buff = 0) - arrow.next_to(ledger, RIGHT) - arrow.shift(MED_LARGE_BUFF*DOWN) - sha = TextMobject("SHA256") - sha.next_to(arrow, UP) - digest = sha256_tex_mob( - """Man, you're reading this deeply into - the code behind videos? I'm touched, - really touched. Keeping loving math, my - friend. """, - n_forced_start_zeros = 30, - ) - digest.next_to(arrow, RIGHT) - zeros = VGroup(*digest[:30]) - zeros_brace = Brace(zeros, UP) - zeros_words = zeros_brace.get_text("30 zeros") - - self.play(LaggedStartMap( - FadeIn, VGroup(special_word, nonce_brace, nonce) - )) - self.wait() - self.play( - nonce.next_to, ledger.content, DOWN, MED_SMALL_BUFF, LEFT, - FadeOut(special_word), - FadeOut(nonce_brace) - ) - ledger.content.add(nonce) - decomposed_ledger = VGroup(*[m for m in ledger.family_members_with_points() if not m.is_subpath]) - self.play( - ShowCreation(arrow), - FadeIn(sha) - ) - self.play(LaggedStartMap( - ApplyMethod, decomposed_ledger, - lambda m : (m.set_color, YELLOW), - rate_func = there_and_back - )) - point = VectorizedPoint(sha.get_center()) - point.set_fill(opacity = 1) - self.play(LaggedStartMap( - Transform, decomposed_ledger.copy(), - lambda m : (m, point), - run_time = 1 - )) - bit_iter = iter(digest) - self.play(LaggedStartMap( - ReplacementTransform, - VGroup(*[point.copy() for x in range(256)]), - lambda m : (m, next(bit_iter)), - )) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(digest) - self.play( - GrowFromCenter(zeros_brace), - Write(zeros_words, run_time = 1) - ) - self.play(LaggedStartMap( - ApplyMethod, zeros, - lambda m : (m.set_color, YELLOW) - )) - self.wait(2) - - self.nonce = nonce - self.digest = digest - self.zeros_brace = zeros_brace - self.zeros_words = zeros_words - - def write_probability(self): - probability = TextMobject( - "Probability: $\\frac{1}{2^{30}}$", - "$\\approx \\frac{1}{1{,}000{,}000{,}000}$", - ) - probability.next_to(self.zeros_words, UP, MED_LARGE_BUFF) - - self.play(FadeIn(probability[0])) - self.wait() - self.play(Write(probability[1], run_time = 2)) - self.wait(2) - - def guess_and_check(self): - q_mark = TexMobject("?") - q_mark.set_color(RED) - q_mark.next_to(self.zeros_words, RIGHT, SMALL_BUFF) - - self.digest.save_state() - self.nonce.save_state() - - self.play(FadeIn(q_mark)) - for x in range(1, 13): - nonce = TexMobject(str(x)) - nonce.move_to(self.nonce) - nonce.set_color(GREEN_C) - digest = sha256_tex_mob(str(x)) - digest.replace(self.digest) - - self.play(Transform( - self.nonce, nonce, - run_time = 1 if x == 1 else 0.3 - )) - self.play(Transform( - self.digest, digest, - run_time = 1, - lag_ratio = 0.5 - )) - self.wait() - self.play(self.nonce.restore) - self.play( - self.digest.restore, - lag_ratio = 0.5, - run_time = 2 - ) - self.play(FadeOut(q_mark)) - self.wait() - - def name_proof_of_work(self): - words = TextMobject("``Proof of work''") - words.next_to(self.nonce, DOWN, LARGE_BUFF) - words.shift(MED_LARGE_BUFF*RIGHT) - words.set_color(GREEN) - arrow = Arrow( - words.get_top(), self.nonce.get_bottom(), - color = WHITE, - tip_length = 0.15 - ) - self.play(Write(words, run_time = 2)) - self.play(ShowCreation(arrow)) - self.wait() - - def change_ledger(self): - amount = self.ledger.content[2][-1] - new_amount = TextMobject("300 LD") - new_amount.set_height(amount.get_height()) - new_amount.set_color(amount.get_color()) - new_amount.move_to(amount, LEFT) - - new_digest = sha256_tex_mob("Ah shucks") - new_digest.replace(self.digest) - - dot = Dot(amount.get_center()) - dot.set_fill(opacity = 0.5) - - self.play(FocusOn(amount)) - self.play(Transform(amount, new_amount)) - self.play( - dot.move_to, new_digest, - dot.set_fill, None, 0 - ) - self.play(Transform( - self.digest, new_digest, - lag_ratio = 0.5, - )) - -class ShowSomeBroadcasting(DistributedLedgerScene): - def construct(self): - self.add_large_network_and_distributed_ledger() - lines = self.network.lines.copy() - lines.add(*[ - line.copy().rotate(np.pi) - for line in lines - ]) - - point = VectorizedPoint(self.pi_creatures.get_center()) - last_pi = None - for pi in self.pi_creatures: - outgoing_lines = [] - for line in lines: - vect = line.get_start() - pi.get_center() - dist = get_norm(vect) - if dist < 2: - outgoing_lines.append(line) - dots = VGroup() - for line in outgoing_lines: - dot = Dot(line.get_start()) - dot.set_color(YELLOW) - dot.generate_target() - dot.target.move_to(line.get_end()) - for alt_pi in self.pi_creatures: - vect = line.get_end() - alt_pi.get_center() - dist = get_norm(vect) - if dist < 2: - dot.ledger = alt_pi.ledger - dots.add(dot) - self.play( - Animation(point), - Broadcast(pi), - *[ - Succession( - FadeIn(dot), - MoveToTarget(dot, run_time = 2), - ) - for dot in dots - ] - ) - self.play(*it.chain(*[ - [dot.move_to, dot.ledger, dot.set_fill, None, 0] - for dot in dots - ])) - -class IntroduceBlockChain(Scene): - CONFIG = { - "transaction_color" : YELLOW, - "proof_of_work_color" : GREEN, - "prev_hash_color" : BLUE, - "block_width" : 3, - "block_height" : 3.5, - "n_transaction_lines" : 8, - "payment_height_to_block_height" : 0.15, - } - def setup(self): - ls = LedgerScene() - self.names = [ - name.capitalize() - for name in ls.get_names() - ] - self.name_colors = [ - ls.get_color_from_name(name) - for name in self.names - ] - - def construct(self): - self.divide_ledger_into_blocks() - self.show_proofs_of_work() - self.chain_blocks_together() - self.mess_with_early_block() - self.propagate_hash_change() - self.redo_proof_of_work() - self.write_block_chain() - - - def divide_ledger_into_blocks(self): - blocks = VGroup(*[ - self.get_block() for x in range(3) - ]) - blocks.arrange(RIGHT, buff = 1.5) - blocks.to_edge(UP) - - all_payments = VGroup() - all_proofs_of_work = VGroup() - for block in blocks: - block.remove(block.prev_hash) - all_payments.add(*block.payments) - all_proofs_of_work.add(block.proof_of_work) - - blocks_word = TextMobject("Blocks") - blocks_word.scale(1.5) - blocks_word.shift(2*DOWN) - arrows = VGroup(*[ - Arrow( - blocks_word.get_top(), block.get_bottom(), - buff = MED_LARGE_BUFF, - color = WHITE - ) - for block in blocks - ]) - - self.play(LaggedStartMap(FadeIn, blocks)) - self.play( - Write(blocks_word), - LaggedStartMap( - ShowCreation, arrows, - run_time = 1, - ) - ) - self.wait() - for group in all_payments, all_proofs_of_work: - self.play(LaggedStartMap( - Indicate, group, - rate_func = there_and_back, - scale_factor = 1.1, - )) - self.play(*list(map(FadeOut, [blocks_word, arrows]))) - - self.blocks = blocks - - def show_proofs_of_work(self): - random.seed(0) - blocks = self.blocks - - proofs_of_work = VGroup() - new_proofs_of_work = VGroup() - digests = VGroup() - arrows = VGroup() - sha_words = VGroup() - signatures = VGroup() - - for block in blocks: - proofs_of_work.add(block.proof_of_work) - num_str = str(random.randint(0, 10**12)) - number = TexMobject(num_str) - number.set_color(self.proof_of_work_color) - number.replace(block.proof_of_work, dim_to_match = 1) - new_proofs_of_work.add(number) - - digest = sha256_tex_mob(num_str, 60) - digest.scale(0.7) - digest.move_to(block).to_edge(DOWN) - VGroup(*digest[:60]).set_color(YELLOW) - arrow = Arrow(block, digest) - sha = TextMobject("SHA256") - sha.scale(0.7) - point = arrow.get_center() - sha.next_to(point, UP, SMALL_BUFF) - sha.rotate(-np.pi/2, about_point = point) - sha.shift(SMALL_BUFF*(UP+RIGHT)) - digests.add(digest) - arrows.add(arrow) - sha_words.add(sha) - - for payment in block.payments[:2]: - signatures.add(payment[-1]) - - proofs_of_work.save_state() - - self.play(Transform( - proofs_of_work, new_proofs_of_work, - lag_ratio = 0.5 - )) - self.play( - ShowCreation(arrows), - Write(sha_words), - run_time = 2 - ) - self.play(Write(digests)) - self.wait() - for group in signatures, proofs_of_work: - self.play(LaggedStartMap( - Indicate, group, - run_time = 2, - rate_func = there_and_back, - )) - self.wait() - self.play( - proofs_of_work.restore, - FadeOut(sha_words) - ) - - self.digests = digests - self.sha_arrows = arrows - - def chain_blocks_together(self): - blocks = self.blocks - digests = self.digests - sha_arrows = self.sha_arrows - block_spacing = blocks[1].get_center() - blocks[0].get_center() - prev_hashes = VGroup(*[ - block.prev_hash for block in blocks - ]) - - prev_hashes.add( - prev_hashes[-1].copy().shift(block_spacing).fade(1) - ) - - new_arrows = VGroup() - for block in blocks: - end = np.array([ - block.get_left()[0] + block_spacing[0], - block.prev_hash.get_center()[1], - 0 - ]) - arrow = Arrow(end+LEFT, end, buff = SMALL_BUFF) - arrow.points[0] = block.get_right() - arrow.points[1] = block.get_right() + RIGHT - arrow.points[2] = end + LEFT + SMALL_BUFF*UP - new_arrows.add(arrow) - - for i in range(3): - self.play( - ReplacementTransform(digests[i], prev_hashes[i+1]), - Transform(sha_arrows[i], new_arrows[i]) - ) - arrow = new_arrows[0].copy().shift(-block_spacing) - sha_arrows.add_to_back(arrow) - self.play(*list(map(FadeIn, [arrow, prev_hashes[0]]))) - self.wait(2) - - self.prev_hashes = prev_hashes - - def mess_with_early_block(self): - blocks = self.blocks - amount = blocks[0].payments[1][3] - new_amount = TextMobject("400 LD") - new_amount.set_height(amount.get_height()) - new_amount.set_color(RED) - new_amount.move_to(amount, LEFT) - - self.play(FocusOn(amount)) - self.play(Transform(amount, new_amount)) - self.wait() - self.play(Swap(*blocks[:2])) - self.wait() - - blocks.submobjects[:2] = blocks.submobjects[1::-1] - - def propagate_hash_change(self): - prev_hashes = self.prev_hashes - - for block, prev_hash in zip(self.blocks, prev_hashes[1:]): - rect = block.rect.copy() - rect.set_stroke(RED, 8) - rect.target = SurroundingRectangle(prev_hash) - rect.target.set_stroke(rect.get_color(), 0) - self.play(ShowCreation(rect)) - self.play( - MoveToTarget(rect), - prev_hash.set_color, RED - ) - - def redo_proof_of_work(self): - proofs_of_work = VGroup(*[ - block.proof_of_work for block in self.blocks - ]) - hashes = self.prev_hashes[1:] - - self.play(FadeOut(proofs_of_work)) - for proof_of_work, prev_hash in zip(proofs_of_work, hashes): - num_pow_group = VGroup(*[ - Integer(random.randint(10**9, 10**10)) - for x in range(50) - ]) - num_pow_group.set_color(proof_of_work.get_color()) - num_pow_group.set_width(proof_of_work.get_width()) - num_pow_group.move_to(proof_of_work) - for num_pow in num_pow_group: - self.add(num_pow) - self.wait(1./20) - prev_hash.set_color(random_bright_color()) - self.remove(num_pow) - self.add(num_pow) - prev_hash.set_color(BLUE) - - def write_block_chain(self): - ledger = TextMobject("Ledger") - ledger.next_to(self.blocks, DOWN, LARGE_BUFF) - cross = Cross(ledger) - block_chain = TextMobject("``Block Chain''") - block_chain.next_to(ledger, DOWN) - - self.play(FadeIn(ledger)) - self.play( - ShowCreation(cross), - Write(block_chain) - ) - self.wait(2) - - - ###### - - def get_block(self): - block = VGroup() - rect = Rectangle( - color = WHITE, - height = self.block_height, - width = self.block_width, - ) - h_line1, h_line2 = [ - Line( - rect.get_left(), rect.get_right() - ).shift(0.3*rect.get_height()*vect) - for vect in (UP, DOWN) - ] - - payments = VGroup() - if not hasattr(self, "transaction_counter"): - self.transaction_counter = 0 - for x in range(2): - hashes = [ - hash("%d %d"%(seed, self.transaction_counter)) - for seed in range(3) - ] - payment = TextMobject( - self.names[hashes[0]%3], - "pays", - self.names[hashes[1]%4], - "%d0 LD"%(hashes[2]%9 + 1), - ) - payment.set_color_by_tex("LD", YELLOW) - for name, color in zip(self.names, self.name_colors): - payment.set_color_by_tex(name, color) - signature = TextMobject("$\\langle$ Signature $\\rangle$") - signature.set_color(payment[0].get_color()) - signature.next_to(payment, DOWN, SMALL_BUFF) - payment.add(signature) - - factor = self.payment_height_to_block_height - payment.set_height(factor*rect.get_height()) - payments.add(payment) - self.transaction_counter += 1 - payments.add(TexMobject("\\dots").scale(0.5)) - payments.arrange(DOWN, buff = MED_SMALL_BUFF) - payments.next_to(h_line1, DOWN) - - proof_of_work = TextMobject("Proof of work") - proof_of_work.set_color(self.proof_of_work_color) - proof_of_work.scale(0.8) - proof_of_work.move_to( - VGroup(h_line2, VectorizedPoint(rect.get_bottom())) - ) - - prev_hash = TextMobject("Prev hash") - prev_hash.scale(0.8) - prev_hash.set_color(self.prev_hash_color) - prev_hash.move_to( - VGroup(h_line1, VectorizedPoint(rect.get_top())) - ) - - block.rect = rect - block.h_lines = VGroup(h_line1, h_line2) - block.payments = payments - block.proof_of_work = proof_of_work - block.prev_hash = prev_hash - block.digest_mobject_attrs() - return block - -class DistributedBlockChainScene(DistributedLedgerScene): - CONFIG = { - "block_height" : 0.5, - "block_width" : 0.5, - "n_blocks" : 3, - } - def get_distributed_ledgers(self): - ledgers = VGroup() - point = self.pi_creatures.get_center() - for pi in self.pi_creatures: - vect = pi.get_center() - point - vect[0] = 0 - block_chain = self.get_block_chain() - block_chain.next_to( - VGroup(pi, pi.label), vect, SMALL_BUFF - ) - pi.block_chain = pi.ledger = block_chain - ledgers.add(block_chain) - self.ledgers = self.block_chains = ledgers - return ledgers - - def get_block_chain(self): - blocks = VGroup(*[ - self.get_block() - for x in range(self.n_blocks) - ]) - blocks.arrange(RIGHT, buff = MED_SMALL_BUFF) - arrows = VGroup() - - for b1, b2 in zip(blocks, blocks[1:]): - arrow = Arrow( - LEFT, RIGHT, - preserve_tip_size_when_scaling = False, - tip_length = 0.15, - ) - arrow.set_width(b1.get_width()) - target_point = interpolate( - b2.get_left(), b2.get_corner(UP+LEFT), 0.8 - ) - arrow.next_to(target_point, LEFT, 0.5*SMALL_BUFF) - arrow.points[0] = b1.get_right() - arrow.points[1] = b2.get_left() - arrow.points[2] = b1.get_corner(UP+RIGHT) - arrow.points[2] += SMALL_BUFF*LEFT - arrows.add(arrow) - block_chain = VGroup(blocks, arrows) - block_chain.blocks = blocks - block_chain.arrows = arrows - return block_chain - - def get_block(self): - block = Rectangle( - color = WHITE, - height = self.block_height, - width = self.block_width, - ) - for vect in UP, DOWN: - line = Line(block.get_left(), block.get_right()) - line.shift(0.3*block.get_height()*vect) - block.add(line) - return block - - def create_pi_creatures(self): - creatures = DistributedLedgerScene.create_pi_creatures(self) - VGroup( - self.alice, self.alice.label, - self.charlie, self.charlie.label, - ).shift(LEFT) - return creatures - -#Out of order -class FromBankToDecentralizedSystem(DistributedBlockChainScene): - CONFIG = { - "n_blocks" : 5, - "ledger_height" : 3, - } - def construct(self): - self.remove_bank() - self.show_block_chains() - self.add_crypto_terms() - self.set_aside_everything() - - def remove_bank(self): - bank = SVGMobject( - file_name = "bank_building", - color = LIGHT_GREY, - height = 3, - ) - cross = Cross(bank) - cross.set_stroke(width = 10) - group = VGroup(bank, cross) - - self.play(LaggedStartMap(DrawBorderThenFill, bank)) - self.play(ShowCreation(cross)) - self.wait() - self.play( - group.next_to, FRAME_X_RADIUS*RIGHT, RIGHT, - rate_func = running_start, - path_arc = -np.pi/6, - ) - self.wait() - self.remove(group) - - def show_block_chains(self): - creatures = self.pi_creatures - creatures.center() - VGroup(self.charlie, self.you).to_edge(DOWN) - chains = self.get_distributed_ledgers() - for pi, chain in zip(creatures, chains): - pi.scale_in_place(1.5) - pi.shift(0.5*pi.get_center()[0]*RIGHT) - chain.next_to(pi, UP) - center_chain = self.get_block_chain() - center_chain.scale(2) - center_chain.center() - - self.play(LaggedStartMap(FadeIn, creatures, run_time = 1)) - self.play( - LaggedStartMap(FadeIn, center_chain.blocks, run_time = 1), - ShowCreation(center_chain.arrows), - ) - self.wait() - self.play( - ReplacementTransform(VGroup(center_chain), chains), - *[ - ApplyMethod(pi.change, "pondering", pi.ledger) - for pi in creatures - ] - ) - self.wait() - - def add_crypto_terms(self): - terms = TextMobject( - "Digital signatures \\\\", - "Cryptographic hash functions", - ) - terms.set_color_by_tex("signature", BLUE) - terms.set_color_by_tex("hash", YELLOW) - for term in terms: - self.play(Write(term, run_time = 1)) - self.wait() - self.digital_signature = terms[0] - - def set_aside_everything(self): - digital_signature = self.digital_signature - ledger = LedgerScene.get_ledger(self) - LedgerScene.add_payment_line_to_ledger(self, - "Alice", "Bob", "40", - ) - LedgerScene.add_payment_line_to_ledger(self, - "Charlie", "Alice", "60", - ) - ledger.next_to(ORIGIN, LEFT) - - self.remove(digital_signature) - everything = VGroup(*self.get_top_level_mobjects()) - - self.play( - Animation(digital_signature), - everything.scale, 0.1, - everything.to_corner, DOWN+LEFT, - ) - self.play( - Write(ledger), - digital_signature.next_to, ORIGIN, RIGHT - ) - self.wait(2) - -class IntroduceBlockCreator(DistributedBlockChainScene): - CONFIG = { - "n_block_creators" : 3, - "n_pow_guesses" : 60, - } - def construct(self): - self.add_network() - self.add_block_creators() - self.broadcast_transactions() - self.collect_transactions() - self.find_proof_of_work() - self.add_block_reward() - self.comment_on_block_reward() - self.write_miners() - self.broadcast_block() - - def add_network(self): - network = self.get_large_network() - network.remove(network.lines) - network.scale(0.7) - ledgers = self.get_distributed_ledgers() - self.add(network, ledgers) - VGroup(network, ledgers).to_edge(RIGHT) - - def add_block_creators(self): - block_creators = VGroup() - labels = VGroup() - everything = VGroup() - for x in range(self.n_block_creators): - block_creator = PiCreature(color = GREY) - block_creator.set_height(self.alice.get_height()) - label = TextMobject("Block creator %d"%(x+1)) - label.scale(0.7) - label.next_to(block_creator, DOWN, SMALL_BUFF) - block_creator.label = label - block_creators.add(block_creator) - labels.add(label) - everything.add(VGroup(block_creator, label)) - everything.arrange(DOWN, buff = LARGE_BUFF) - everything.to_edge(LEFT) - - self.play(LaggedStartMap(FadeIn, everything)) - self.pi_creatures.add(*block_creators) - self.wait() - - self.block_creators = block_creators - self.block_creator_labels = labels - - def broadcast_transactions(self): - payment_parts = [ - ("Alice", "Bob", 20), - ("Bob", "Charlie", 10), - ("Charlie", "You", 50), - ("You", "Alice", 30), - ] - payments = VGroup() - payment_targets = VGroup() - for from_name, to_name, amount in payment_parts: - verb = "pay" if from_name == "You" else "pays" - payment = TextMobject( - from_name, verb, to_name, "%d LD"%amount - ) - payment.set_color_by_tex("LD", YELLOW) - for name in self.get_names(): - payment.set_color_by_tex( - name.capitalize(), - self.get_color_from_name(name) - ) - payment.scale(0.7) - payment.generate_target() - payment_targets.add(payment.target) - - pi = getattr(self, from_name.lower()) - payment.scale(0.1) - payment.set_fill(opacity = 0) - payment.move_to(pi) - payments.add(payment) - payment_targets.arrange(DOWN, aligned_edge = LEFT) - payment_targets.next_to( - self.block_creator_labels, RIGHT, - MED_LARGE_BUFF - ) - payment_targets.shift(UP) - - anims = [] - alpha_range = np.linspace(0, 0.5, len(payments)) - for pi, payment, alpha in zip(self.pi_creatures, payments, alpha_range): - rf1 = squish_rate_func(smooth, alpha, alpha+0.5) - rf2 = squish_rate_func(smooth, alpha, alpha+0.5) - anims.append(Broadcast( - pi, rate_func = rf1, - big_radius = 3, - )) - anims.append(MoveToTarget(payment, rate_func = rf2)) - - self.play(*anims, run_time = 5) - self.payments = payments - - def collect_transactions(self): - creator = self.block_creators[0] - block = self.get_block() - block.stretch_to_fit_height(4) - block.stretch_to_fit_width(3.5) - block.next_to(creator.label, RIGHT, MED_LARGE_BUFF) - block.to_edge(UP) - - payments = self.payments - payments.generate_target() - payments.target.set_height(1.5) - payments.target.move_to(block) - - prev_hash = TextMobject("Prev hash") - prev_hash.set_color(BLUE) - prev_hash.set_height(0.3) - prev_hash.next_to(block.get_top(), DOWN, MED_SMALL_BUFF) - block.add(prev_hash) - - self.play( - FadeIn(block), - MoveToTarget(payments), - creator.change, "raise_right_hand" - ) - self.wait() - block.add(payments) - - self.block = block - - def find_proof_of_work(self): - block = self.block - - arrow = Arrow(UP, ORIGIN, buff = 0) - arrow.next_to(block, DOWN) - sha = TextMobject("SHA256") - sha.scale(0.7) - sha.next_to(arrow, RIGHT) - arrow.add(sha) - - self.add(arrow) - for x in range(self.n_pow_guesses): - guess = Integer(random.randint(10**11, 10**12)) - guess.set_color(GREEN) - guess.set_height(0.3) - guess.next_to(block.get_bottom(), UP, MED_SMALL_BUFF) - - if x == self.n_pow_guesses - 1: - digest = sha256_tex_mob(str(x), 60) - VGroup(*digest[:60]).set_color(YELLOW) - else: - digest = sha256_tex_mob(str(x)) - digest.set_width(block.get_width()) - digest.next_to(arrow.get_end(), DOWN) - - self.add(guess, digest) - self.wait(1./20) - self.remove(guess, digest) - proof_of_work = guess - self.add(proof_of_work, digest) - block.add(proof_of_work) - self.wait() - - self.hash_group = VGroup(arrow, digest) - - def add_block_reward(self): - payments = self.payments - new_transaction = TextMobject( - self.block_creator_labels[0].get_tex_string(), - "gets", "10 LD" - ) - new_transaction[0].set_color(LIGHT_GREY) - new_transaction.set_color_by_tex("LD", YELLOW) - new_transaction.set_height(payments[0].get_height()) - new_transaction.move_to(payments.get_top()) - payments.generate_target() - payments.target.next_to(new_transaction, DOWN, SMALL_BUFF, LEFT) - new_transaction.shift(SMALL_BUFF*UP) - - self.play( - MoveToTarget(payments), - Write(new_transaction) - ) - payments.add_to_back(new_transaction) - self.wait() - - def comment_on_block_reward(self): - reward = self.payments[0] - reward_rect = SurroundingRectangle(reward) - big_rect = SurroundingRectangle(self.ledgers) - big_rect.set_stroke(width = 0) - big_rect.set_fill(BLACK, opacity = 1) - - comments = VGroup(*list(map(TextMobject, [ - "- ``Block reward''", - "- No sender/signature", - "- Adds to total money supply", - ]))) - comments.arrange(DOWN, aligned_edge = LEFT) - comments.move_to(big_rect, UP+LEFT) - - pi_creatures = self.pi_creatures - self.pi_creatures = VGroup() - - self.play(ShowCreation(reward_rect)) - self.play(FadeIn(big_rect)) - for comment in comments: - self.play(FadeIn(comment)) - self.wait(2) - self.play(*list(map(FadeOut, [big_rect, comments, reward_rect]))) - - self.pi_creatures = pi_creatures - - def write_miners(self): - for label in self.block_creator_labels: - tex = label.get_tex_string() - new_label = TextMobject("Miner " + tex[-1]) - new_label.set_color(label.get_color()) - new_label.replace(label, dim_to_match = 1) - self.play(Transform(label, new_label)) - top_payment = self.payments[0] - new_top_payment = TextMobject("Miner 1", "gets", "10 LD") - new_top_payment[0].set_color(LIGHT_GREY) - new_top_payment[-1].set_color(YELLOW) - new_top_payment.set_height(top_payment.get_height()) - new_top_payment.move_to(top_payment, LEFT) - self.play(Transform(top_payment, new_top_payment)) - self.wait() - - def broadcast_block(self): - old_chains = self.block_chains - self.n_blocks = 4 - new_chains = self.get_distributed_ledgers() - block_target_group = VGroup() - anims = [] - arrow_creations = [] - for old_chain, new_chain in zip(old_chains, new_chains): - for attr in "blocks", "arrows": - pairs = list(zip( - getattr(old_chain, attr), - getattr(new_chain, attr), - )) - for m1, m2 in pairs: - anims.append(Transform(m1, m2)) - arrow_creations.append(ShowCreation(new_chain.arrows[-1])) - block_target = self.block.copy() - block_target.replace(new_chain.blocks[-1], stretch = True) - block_target_group.add(block_target) - anims.append(Transform( - VGroup(self.block), - block_target_group - )) - anims.append(Broadcast(self.block, n_circles = 4)) - anims.append(FadeOut(self.hash_group)) - anims.append(ApplyMethod( - self.block_creators[0].change, "happy" - )) - - self.play(*anims, run_time = 2) - self.play(*it.chain( - arrow_creations, - [ - ApplyMethod( - pi.change, "hooray", - pi.block_chain.get_right() - ) - for pi in self.pi_creatures - ] - )) - self.wait(3) - -class MiningIsALottery(IntroduceBlockCreator): - CONFIG = { - "n_miners" : 3, - "denomination" : "LD", - "n_guesses" : 90, - "n_nonce_digits" : 15, - } - def construct(self): - self.add_blocks() - self.add_arrows() - self.make_guesses() - - def create_pi_creatures(self): - IntroduceBlockCreator.create_pi_creatures(self) - miners = VGroup(*[ - PiCreature(color = GREY) - for n in range(self.n_miners) - ]) - miners.scale(0.5) - miners.arrange(DOWN, buff = LARGE_BUFF) - miners.to_edge(LEFT) - for x, miner in enumerate(miners): - label = TextMobject("Miner %d"%(x+1)) - label.scale(0.7) - label.next_to(miner, DOWN, SMALL_BUFF) - miner.label = label - self.add(label) - self.miners = miners - return miners - - def add_blocks(self): - self.add(self.miners) - - blocks = VGroup() - for miner in self.miners: - block = self.get_block() - block.stretch_to_fit_height(2) - block.stretch_to_fit_width(3) - block.next_to(miner, RIGHT) - - payments = self.get_payments(miner) - payments.set_height(1) - payments.move_to(block) - block.add(payments) - - prev_hash = TextMobject("Prev hash") - prev_hash.set_color(BLUE) - prev_hash.set_height(0.2) - prev_hash.next_to(block.get_top(), DOWN, SMALL_BUFF) - block.add(prev_hash) - - miner.block = block - miner.change("pondering", block) - blocks.add(block) - - self.blocks = blocks - self.add(blocks) - - def add_arrows(self): - self.arrows = VGroup() - for block in self.blocks: - arrow = Arrow(LEFT, RIGHT) - arrow.next_to(block) - label = TextMobject("SHA256") - label.scale(0.7) - label.next_to(arrow, UP, buff = SMALL_BUFF) - self.add(arrow, label) - block.arrow = arrow - self.arrows.add(VGroup(arrow, label)) - - def make_guesses(self): - for x in range(self.n_guesses): - e = self.n_nonce_digits - nonces = VGroup() - digests = VGroup() - for block in self.blocks: - nonce = Integer(random.randint(10**e, 10**(e+1))) - nonce.set_height(0.2) - nonce.next_to(block.get_bottom(), UP, SMALL_BUFF) - nonces.add(nonce) - digest = sha256_tex_mob(str(x) + str(block)) - digest.set_height(block.get_height()) - digest.next_to(block.arrow, RIGHT) - digests.add(digest) - self.add(nonces, digests) - self.wait(1./20) - self.remove(nonces, digests) - self.add(nonces, digests) - - winner_index = 1 - winner = self.miners[winner_index] - losers = VGroup(*[m for m in self.miners if m is not winner]) - - nonces[winner_index].set_color(GREEN) - new_digest = sha256_tex_mob("Winner", 60) - VGroup(*new_digest[:60]).set_color(YELLOW) - old_digest = digests[winner_index] - new_digest.replace(old_digest) - Transform(old_digest, new_digest).update(1) - - self.play( - winner.change, "hooray", - *[ - ApplyMethod(VGroup( - self.blocks[i], self.arrows[i], - nonces[i], digests[i] - ).fade, 0.7) - for i in range(len(self.blocks)) - if i is not winner_index - ] - ) - self.play(*[ - ApplyMethod(loser.change, "angry", winner) - for loser in losers - ]) - self.wait(2) - - ##### - - def get_payments(self, miner): - if not hasattr(self, "ledger"): - self.get_ledger() ##Unused - self.ledger.content.remove(*self.ledger.content[1:]) - - lines = VGroup() - - miner_name = miner.label.get_tex_string() - top_line = TextMobject(miner_name, "gets", "10 LD") - top_line.set_color_by_tex(miner_name, LIGHT_GREY) - top_line.set_color_by_tex("LD", YELLOW) - lines.add(top_line) - payments = [ - ("Alice", "Bob", 20), - ("Charlie", "You", 50), - ] - for payment in payments: - lines.add(self.add_payment_line_to_ledger(*payment)) - lines.add(TexMobject("\\vdots")) - for line in lines: - line.set_height(0.5) - lines.arrange( - DOWN, buff = SMALL_BUFF, aligned_edge = LEFT - ) - lines[-1].next_to(lines[-2], DOWN, buff = SMALL_BUFF) - return lines - -class TwoBlockChains(DistributedBlockChainScene): - CONFIG = { - "n_blocks" : 5, - } - def construct(self): - self.listen_for_new_blocks() - self.defer_to_longer() - self.break_tie() - - def listen_for_new_blocks(self): - randy = self.randy - chain = self.get_block_chain() - chain.scale(1.5) - chain.next_to(randy, UP+RIGHT) - chain.shift_onto_screen() - randy.change("raise_right_hand", chain) - - corners = [ - u1 * FRAME_X_RADIUS*RIGHT + u2 * FRAME_Y_RADIUS*UP - for u1, u2 in it.product(*[[-1, 1]]*2) - ] - moving_blocks = chain.blocks[1:] - - self.add(randy, chain.blocks[0]) - for corner, block, arrow in zip(corners, moving_blocks, chain.arrows): - block.save_state() - block.next_to(corner, corner) - self.play( - ApplyMethod( - block.restore, - rate_func = squish_rate_func(smooth, 0.3, 0.8), - run_time = 3, - ), - Broadcast(corner, run_time = 3), - ShowCreation( - arrow, - rate_func = squish_rate_func(smooth, 0.8, 1), - run_time = 3, - ), - ) - self.wait() - - self.block_chain = chain - - def defer_to_longer(self): - randy = self.randy - self.n_blocks -= 1 - block_chains = VGroup( - self.block_chain, - self.get_block_chain().scale(1.5) - ) - block_chains[1].next_to(randy, UP+LEFT) - block_chains[1].shift_onto_screen() - - conflicting = TextMobject("Conflicting") - conflicting.to_edge(UP) - conflicting.set_color(RED) - arrows = VGroup(*[ - Arrow( - conflicting.get_bottom(), block_chain.get_top(), - color = RED, - buff = MED_LARGE_BUFF - ) - for block_chain in block_chains - ]) - - longer_chain_rect = SurroundingRectangle(block_chains[0]) - longer_chain_rect.set_stroke(GREEN, 8) - checkmark = TexMobject("\\checkmark") - checkmark.set_color(GREEN) - checkmark.next_to(longer_chain_rect, UP) - checkmark.shift(RIGHT) - - chain = block_chains[1] - chain.save_state() - corner = FRAME_X_RADIUS*LEFT + FRAME_Y_RADIUS*UP - chain.next_to(corner, UP+LEFT) - self.play( - randy.change, "confused", chain, - Broadcast(corner), - ApplyMethod( - chain.restore, - rate_func = squish_rate_func(smooth, 0.3, 0.7), - run_time = 3 - ) - ) - self.play( - Write(conflicting), - *list(map(ShowCreation, arrows)) - ) - self.wait() - self.play(ShowCreation(longer_chain_rect)) - self.play(Write(checkmark, run_time = 1)) - self.play(randy.change, "thinking", checkmark) - self.wait() - - self.to_fade = VGroup( - conflicting, arrows, - longer_chain_rect, checkmark - ) - self.block_chains = block_chains - - def break_tie(self): - to_fade = self.to_fade - block_chains = self.block_chains - randy = self.randy - - arrow = block_chains[1].arrows[-1] - block = block_chains[1].blocks[-1] - arrow_block = VGroup(arrow, block).copy() - - block_chains.generate_target() - block_chains.target.arrange( - DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT - ) - block_chains.target.next_to(randy, UP) - block_chains.target.to_edge(LEFT) - - self.play( - MoveToTarget(block_chains), - FadeOut(to_fade), - run_time = 1 - ) - arrow_block.next_to(block_chains[1], RIGHT, buff = 0) - block_chains[1].add(arrow_block) - self.play( - randy.change, "confused", block_chains, - FadeIn(arrow_block), - ) - self.wait() - - arrow_block = arrow_block.copy() - arrow_block.next_to(FRAME_X_RADIUS*RIGHT, RIGHT) - self.play( - ApplyMethod( - arrow_block.next_to, block_chains[0], RIGHT, 0, - run_time = 3, - rate_func = squish_rate_func(smooth, 0.3, 0.8) - ), - Broadcast(arrow_block), - ) - block_chains[0].add(arrow_block) - rect = SurroundingRectangle(block_chains[0]) - rect.set_stroke(GREEN, 8) - checkmark = TexMobject("\\checkmark") - checkmark.next_to(rect, UP) - checkmark.set_color(GREEN) - - self.play( - ShowCreation(rect), - Write(checkmark), - randy.change, "happy", arrow_block - ) - self.wait(2) - - - #### - - def create_pi_creatures(self): - randy = Randolph() - randy.to_edge(DOWN) - self.randy = randy - return VGroup(randy) - -class ReplaceCentralAuthorityWithWork(Scene): - def construct(self): - trust, central = words = TextMobject("Trust", "central authority") - words.scale(1.5) - cross = Cross(central) - work = TextMobject("computational work") - work.scale(1.5) - work.move_to(central, LEFT) - work.set_color(YELLOW) - - self.play(Write(words)) - self.play(ShowCreation(cross)) - central.add(cross) - self.play( - central.shift, DOWN, - Write(work) - ) - self.wait() - -class AskAboutTrustingWork(TeacherStudentsScene): - def construct(self): - mode = "raise_left_hand" - self.student_says( - "Is trusting work \\\\ really enough?", - target_mode = mode, - ) - self.change_student_modes("confused", mode, "erm") - self.wait(3) - self.teacher_says( - "Well, let's try\\\\ fooling someone", - target_mode = "speaking" - ) - self.wait(2) - -class DoubleSpendingAttack(DistributedBlockChainScene): - CONFIG = { - "fraud_block_height" : 3, - } - def construct(self): - self.initialize_characters() - self.show_fraudulent_block() - self.send_to_bob() - self.dont_send_to_rest_of_network() - - def initialize_characters(self): - network = self.get_large_network() - network.scale(0.7) - block_chains = self.get_distributed_ledgers() - self.add(network, block_chains) - self.play(self.alice.change, "conniving") - self.wait() - - def show_fraudulent_block(self): - block = self.get_fraud_block() - block.next_to(self.alice, LEFT, LARGE_BUFF) - block.remove(block.content) - self.play( - ShowCreation(block), - Write(block.content), - self.alice.change, "raise_left_hand" - ) - block.add(block.content) - self.wait() - - self.block = block - - def send_to_bob(self): - block = self.block.copy() - block.generate_target() - block.target.replace( - self.bob.block_chain.blocks[-1], - stretch = True - ) - arrow = self.bob.block_chain.arrows[-1].copy() - VGroup(arrow, block.target).next_to( - self.bob.block_chain.blocks[-1], RIGHT, buff = 0 - ) - - self.play( - MoveToTarget(block), - self.alice.change, "happy" - ) - self.play(ShowCreation(arrow)) - self.wait() - - def dont_send_to_rest_of_network(self): - bubble = ThoughtBubble() - words = TextMobject("Alice", "never \\\\ paid", "Bob") - for name in "Alice", "Bob": - words.set_color_by_tex(name, self.get_color_from_name(name)) - bubble.add_content(words) - bubble.resize_to_content() - bubble.add(*bubble.content) - bubble.move_to(self.you.get_corner(UP+RIGHT), DOWN+LEFT) - - self.play( - self.charlie.change, "shruggie", - self.you.change, "shruggie", - ) - self.play(LaggedStartMap(FadeIn, bubble)) - self.play(self.bob.change, "confused", words) - self.wait(2) - - ### - - def get_fraud_block(self): - block = self.get_block() - block.set_height(self.fraud_block_height) - content = VGroup() - - tuples = [ - ("Prev hash", UP, BLUE), - ("Proof of work", DOWN, GREEN), - ] - for word, vect, color in tuples: - mob = TextMobject(word) - mob.set_color(color) - mob.set_height(0.07*block.get_height()) - mob.next_to( - block.get_edge_center(vect), -vect, - buff = 0.06*block.get_height() - ) - content.add(mob) - attr = word.lower().replace(" ", "_") - setattr(block, attr, mob) - - payment = TextMobject("Alice", "pays", "Bob", "100 LD") - for name in "Alice", "Bob": - payment.set_color_by_tex(name, self.get_color_from_name(name)) - payment.set_color_by_tex("LD", YELLOW) - payments = VGroup( - TexMobject("\\vdots"), - payment, - TexMobject("\\vdots") - ) - payments.arrange(DOWN) - payments.set_width(0.9*block.get_width()) - payments.move_to(block) - content.add(payments) - - block.content = content - block.add(content) - return block - -class AliceRacesOtherMiners(DoubleSpendingAttack): - CONFIG = { - "n_frames_per_pow" : 3, - "fraud_block_height" : 2, - "n_miners" : 3, - "n_proof_of_work_digits" : 11, - "proof_of_work_update_counter" : 0, - } - def construct(self): - self.initialize_characters() - self.find_proof_of_work() - self.receive_broadcast_from_other_miners() - self.race_between_alice_and_miners() - - def initialize_characters(self): - alice = self.alice - bob = self.bob - alice_l = VGroup(alice, alice.label) - bob_l = VGroup(bob, bob.label) - bob_l.move_to(ORIGIN) - alice_l.to_corner(UP+LEFT) - alice_l.shift(DOWN) - self.add(bob_l, alice_l) - - chain = self.get_block_chain() - chain.next_to(bob, UP) - self.block_chain = chain - self.add(chain) - - fraud_block = self.get_fraud_block() - fraud_block.next_to(alice, RIGHT) - fraud_block.to_edge(UP) - proof_of_work = self.get_rand_int_mob() - proof_of_work.replace(fraud_block.proof_of_work, dim_to_match = 1) - fraud_block.content.remove(fraud_block.proof_of_work) - fraud_block.content.add(proof_of_work) - fraud_block.proof_of_work = proof_of_work - self.fraud_block = fraud_block - self.add(fraud_block) - - miners = VGroup(*[ - PiCreature(color = GREY) - for x in range(self.n_miners) - ]) - miners.set_height(alice.get_height()) - miners.arrange(RIGHT, buff = LARGE_BUFF) - miners.to_edge(DOWN+LEFT) - miners.shift(0.5*UP) - miners_word = TextMobject("Miners") - miners_word.next_to(miners, DOWN) - self.miners = miners - self.add(miners_word, miners) - - miner_blocks = VGroup() - self.proofs_of_work = VGroup() - self.add_foreground_mobject(self.proofs_of_work) - for miner in miners: - block = self.get_block() - block.set_width(1.5*miner.get_width()) - block.next_to(miner, UP) - - transactions = self.get_block_filler(block) - block.add(transactions) - - proof_of_work = self.get_rand_int_mob() - prev_hash = TextMobject("Prev hash").set_color(BLUE) - for mob, vect in (proof_of_work, DOWN), (prev_hash, UP): - mob.set_height(0.1*block.get_height()) - mob.next_to( - block.get_edge_center(vect), -vect, - buff = 0.05*block.get_height() - ) - block.add(mob) - block.proof_of_work = proof_of_work - block.prev_hash = prev_hash - self.proofs_of_work.add(proof_of_work) - - miner.block = block - miner_blocks.add(block) - self.add(miner_blocks) - - def find_proof_of_work(self): - fraud_block = self.fraud_block - chain = self.block_chain - - self.proofs_of_work.add(self.fraud_block.proof_of_work) - self.wait(3) - self.proofs_of_work.remove(self.fraud_block.proof_of_work) - fraud_block.proof_of_work.set_color(GREEN) - self.play( - Indicate(fraud_block.proof_of_work), - self.alice.change, "hooray" - ) - self.wait() - - block = fraud_block.copy() - block.generate_target() - block.target.replace(chain.blocks[-1], stretch = True) - arrow = chain.arrows[-1].copy() - VGroup(arrow, block.target).next_to( - chain.blocks[-1], RIGHT, buff = 0 - ) - self.remove(fraud_block) - self.clean_fraud_block_content() - self.fraud_fork_head = block - self.fraud_fork_arrow = arrow - - self.play(MoveToTarget(block)) - self.play(ShowCreation(arrow)) - self.play(self.alice.change, "happy") - self.wait() - - def receive_broadcast_from_other_miners(self): - winner = self.miners[-1] - proof_of_work = winner.block.proof_of_work - self.proofs_of_work.remove(proof_of_work) - proof_of_work.set_color(GREEN) - self.play( - Indicate(proof_of_work), - winner.change, "hooray" - ) - block = winner.block.copy() - proof_of_work.set_color(WHITE) - self.remove(winner.block) - - ff_head = self.fraud_fork_head - ff_arrow = self.fraud_fork_arrow - arrow = ff_arrow.copy() - movers = [ff_head, ff_arrow, block, arrow] - for mover in movers: - mover.generate_target() - block.target.replace(ff_head, stretch = True) - - dist = 0.5*ff_head.get_height() + MED_SMALL_BUFF - block.target.shift(dist*DOWN) - ff_head.target.shift(dist*UP) - arrow.target[1].shift(dist*DOWN) - arrow.target.points[-2:] += dist*DOWN - ff_arrow.target[1].shift(dist*UP) - ff_arrow.target.points[-2:] += dist*UP - - self.play( - Broadcast(block), - *[ - MoveToTarget( - mover, run_time = 3, - rate_func = squish_rate_func(smooth, 0.3, 0.8) - ) - for mover in movers - ] - ) - self.play( - FadeIn(winner.block), - winner.change_mode, "plain", - self.alice.change, "sassy", - ) - self.proofs_of_work.add(winner.block.proof_of_work) - self.wait(2) - self.play( - self.alice.change, "pondering", - FadeIn(self.fraud_block) - ) - self.proofs_of_work.add(self.fraud_block.proof_of_work) - - self.valid_fork_head = block - - def race_between_alice_and_miners(self): - last_fraud_block = self.fraud_fork_head - last_valid_block = self.valid_fork_head - chain = self.block_chain - winners = [ - "Alice", "Alice", - "Miners", "Miners", "Miners", - "Alice", "Miners", "Miners", "Miners", - "Alice", "Miners", "Miners" - ] - - self.revert_to_original_skipping_status() - for winner in winners: - self.wait() - if winner == "Alice": - block = self.fraud_block - prev_block = last_fraud_block - else: - block = random.choice(self.miners).block - prev_block = last_valid_block - block_copy = block.deepcopy() - block_copy.proof_of_work.set_color(GREEN) - block_copy.generate_target() - block_copy.target.replace(chain.blocks[1], stretch = True) - arrow = chain.arrows[0].copy() - VGroup(arrow, block_copy.target).next_to( - prev_block, RIGHT, buff = 0 - ) - anims = [ - MoveToTarget(block_copy, run_time = 2), - ShowCreation( - arrow, - run_time = 2, - rate_func = squish_rate_func(smooth, 0.5, 1), - ), - FadeIn(block), - ] - if winner == "Alice": - last_fraud_block = block_copy - else: - last_valid_block = block_copy - anims.append(Broadcast(block, run_time = 2)) - self.proofs_of_work.remove(block.proof_of_work) - self.play(*anims) - self.proofs_of_work.add(block.proof_of_work) - - - ##### - - def wait(self, time = 1): - self.play( - Animation(VGroup(*self.foreground_mobjects)), - run_time = time - ) - - def update_frame(self, *args, **kwargs): - self.update_proofs_of_work() - Scene.update_frame(self, *args, **kwargs) - - def update_proofs_of_work(self): - self.proof_of_work_update_counter += 1 - if self.proof_of_work_update_counter%self.n_frames_per_pow != 0: - return - for proof_of_work in self.proofs_of_work: - new_pow = self.get_rand_int_mob() - new_pow.replace(proof_of_work, dim_to_match = 1) - Transform(proof_of_work, new_pow).update(1) - - def get_rand_int_mob(self): - e = self.n_proof_of_work_digits - return Integer(random.randint(10**e, 10**(e+1))) - - def clean_fraud_block_content(self): - content = self.fraud_block.content - payments = content[1] - content.remove(payments) - transactions = self.get_block_filler(self.fraud_block) - content.add(transactions) - - def get_block_filler(self, block): - result = TextMobject("$\\langle$Transactions$\\rangle$") - result.set_width(0.8*block.get_width()) - result.move_to(block) - return result - -class WhenToTrustANewBlock(DistributedBlockChainScene): - def construct(self): - chain = self.block_chain = self.get_block_chain() - chain.scale(2) - chain.to_edge(LEFT) - self.add(chain) - - words = list(map(TextMobject, [ - "Don't trust yet", - "Still don't trust", - "...a little more...", - "Maybe trust", - "Probably safe", - "Alright, you're good." - ])) - colors = [RED, RED, YELLOW, YELLOW, GREEN, GREEN] - self.add_new_block() - arrow = Arrow(UP, DOWN, color = RED) - arrow.next_to(chain.blocks[-1], UP) - for word, color in zip(words, colors): - word.set_color(color) - word.next_to(arrow, UP) - word = words[0] - self.play( - FadeIn(word), - ShowCreation(arrow) - ) - for new_word in words[1:]: - kwargs = { - "run_time" : 3, - "rate_func" : squish_rate_func(smooth, 0.7, 1) - } - self.add_new_block( - Transform(word, new_word, **kwargs), - ApplyMethod( - arrow.set_color, new_word.get_color(), - **kwargs - ) - ) - self.wait(2) - - def get_block(self): - block = DistributedBlockChainScene.get_block(self) - tuples = [ - ("Prev hash", UP, BLUE), - ("Proof of work", DOWN, GREEN), - ] - for word, vect, color in tuples: - mob = TextMobject(word) - mob.set_color(color) - mob.set_height(0.07*block.get_height()) - mob.next_to( - block.get_edge_center(vect), -vect, - buff = 0.06*block.get_height() - ) - block.add(mob) - attr = word.lower().replace(" ", "_") - setattr(block, attr, mob) - transactions = TextMobject("$\\langle$Transactions$\\rangle$") - transactions.set_width(0.8*block.get_width()) - transactions.move_to(block) - block.add(transactions) - return block - - def add_new_block(self, *added_anims): - blocks = self.block_chain.blocks - arrows = self.block_chain.arrows - block = blocks[-1].copy() - arrow = arrows[-1].copy() - VGroup(block, arrow).next_to(blocks[-1], RIGHT, buff = 0) - corner = FRAME_X_RADIUS*RIGHT + FRAME_Y_RADIUS*UP - block.save_state() - block.next_to(corner, UP+RIGHT) - - self.play( - Broadcast(block), - ApplyMethod( - block.restore, - run_time = 3, - rate_func = squish_rate_func(smooth, 0.3, 0.8), - ), - ShowCreation( - arrow, - run_time = 3, - rate_func = squish_rate_func(smooth, 0.7, 1), - ), - *added_anims - ) - arrows.add(arrow) - blocks.add(block) - -class MainIdeas(Scene): - def construct(self): - title = TextMobject("Main ideas") - title.scale(1.5) - h_line = Line(LEFT, RIGHT) - h_line.set_width(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - VGroup(title, h_line).to_corner(UP+LEFT) - - ideas = VGroup(*[ - TextMobject("$\\cdot$ " + words) - for words in [ - "Digital signatures", - "The ledger is the currency", - "Decentralize", - "Proof of work", - "Block chain", - ] - ]) - colors = BLUE, WHITE, RED, GREEN, YELLOW - for idea, color in zip(ideas, colors): - idea.set_color(color) - ideas.arrange( - DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - ideas.next_to(h_line, DOWN) - - self.add(title, h_line) - for idea in ideas: - self.play(LaggedStartMap(FadeIn, idea)) - self.wait() - -class VariableProofOfWork(WhenToTrustANewBlock): - CONFIG = { - "block_height" : 3, - "block_width" : 3, - "n_guesses" : 60, - "n_proof_of_work_digits" : 11, - "n_miners" : 6, - } - def construct(self): - self.add_miner_and_hash() - self.change_requirement() - self.add_more_miners() - - def add_miner_and_hash(self): - miner = PiCreature(color = GREY) - miner.scale(0.7) - miner.to_edge(LEFT) - - block = self.get_block() - block.next_to(miner, RIGHT) - old_proof_of_work = block.proof_of_work - proof_of_work = self.get_rand_int_mob() - proof_of_work.replace(old_proof_of_work, dim_to_match = 1) - block.remove(old_proof_of_work) - block.add(proof_of_work) - block.proof_of_work = proof_of_work - - arrow = Arrow(LEFT, RIGHT) - arrow.next_to(block) - sha_tex = TextMobject("SHA256") - sha_tex.scale(0.7) - sha_tex.next_to(arrow, UP, SMALL_BUFF) - sha_tex.set_color(YELLOW) - arrow.add(sha_tex) - - digest = sha256_tex_mob("Random") - digest.next_to(arrow.get_end(), RIGHT) - - miner.change("pondering", digest) - - self.add(miner, block, arrow, digest) - for x in range(self.n_guesses): - new_pow = self.get_rand_int_mob() - new_pow.replace(proof_of_work, dim_to_match = 1) - Transform(proof_of_work, new_pow).update(1) - if x == self.n_guesses-1: - n_zeros = 60 - else: - n_zeros = 0 - new_digest = sha256_tex_mob(str(x+1), n_zeros) - new_digest.replace(digest) - Transform(digest, new_digest).update(1) - self.wait(1./20) - proof_of_work.set_color(GREEN) - VGroup(*digest[:60]).set_color(YELLOW) - - self.miner = miner - self.block = block - self.arrow = arrow - self.digest = digest - - def change_requirement(self): - digest = self.digest - requirement = TextMobject( - "Must start with \\\\", - "60", "zeros" - ) - requirement.next_to(digest, UP, MED_LARGE_BUFF) - self.n_zeros_mob = requirement.get_part_by_tex("60") - self.n_zeros_mob.set_color(YELLOW) - - self.play(Write(requirement, run_time = 2)) - self.wait(2) - for n_zeros in 30, 32, 35, 37, 42: - self.change_challenge(n_zeros) - self.wait() - - def add_more_miners(self): - miner = self.miner - block = self.block - miner_block = VGroup(miner, block) - target = miner_block.copy() - target[1].scale( - 0.5, about_point = miner.get_right() - ) - copies = VGroup(*[ - target.copy() - for x in range(self.n_miners - 1) - ]) - everyone = VGroup(target, *copies) - everyone.arrange(DOWN) - everyone.set_height(FRAME_HEIGHT - LARGE_BUFF) - everyone.to_corner(UP+LEFT) - - self.play(Transform(miner_block, target)) - self.play(LaggedStartMap(FadeIn, copies)) - self.change_challenge(72) - self.wait(2) - - ### - def change_challenge(self, n_zeros): - digest = self.digest - proof_of_work = self.block.proof_of_work - n_zeros_mob = self.n_zeros_mob - - new_digest = sha256_tex_mob(str(n_zeros), n_zeros) - new_digest.move_to(digest) - VGroup(*new_digest[:n_zeros]).set_color(YELLOW) - new_n_zeros_mob = TexMobject(str(n_zeros)) - new_n_zeros_mob.move_to(n_zeros_mob) - new_n_zeros_mob.set_color(n_zeros_mob.get_color()) - new_pow = self.get_rand_int_mob() - new_pow.replace(proof_of_work, dim_to_match = 1) - new_pow.set_color(proof_of_work.get_color()) - - self.play( - Transform(n_zeros_mob, new_n_zeros_mob), - Transform( - digest, new_digest, - lag_ratio = 0.5 - ), - Transform(proof_of_work, new_pow), - ) - - def get_rand_int_mob(self): - e = self.n_proof_of_work_digits - return Integer(random.randint(10**e, 10**(e+1))) - -class CompareBlockTimes(Scene): - def construct(self): - title = TextMobject("Average block time") - title.scale(1.5) - title.to_edge(UP) - h_line = Line(LEFT, RIGHT) - h_line.set_width(FRAME_X_RADIUS) - h_line.next_to(title, DOWN, SMALL_BUFF) - - examples = VGroup( - TextMobject("BTC: ", "10 minutes"), - TextMobject("ETH: ", "15 Seconds"), - TextMobject("XRP: ", "3.5 Seconds"), - TextMobject("LTC: ", "2.5 Minutes"), - ) - examples.arrange( - DOWN, - buff = LARGE_BUFF, - aligned_edge = LEFT, - ) - examples.next_to(h_line, DOWN) - logos = VGroup( - BitcoinLogo(), - EthereumLogo(), - ImageMobject("ripple_logo"), - LitecoinLogo(), - ) - colors = [BITCOIN_COLOR, GREEN, BLUE_B, LIGHT_GREY] - for logo, example, color in zip(logos, examples, colors): - logo.set_height(0.5) - logo.next_to(example, LEFT) - example[0].set_color(color) - - self.add(title, h_line) - self.play( - FadeIn(examples[0]), - DrawBorderThenFill(logos[0]) - ) - self.wait() - self.play(*[ - LaggedStartMap(FadeIn, VGroup(*group[1:])) - for group in (examples, logos) - ]) - self.wait(2) - - # def get_ethereum_logo(self): - # logo = SVGMobject( - # file_name = "ethereum_logo", - # height = 1, - # ) - # logo.set_fill(GREEN, 1) - # logo.set_stroke(WHITE, 3) - # logo.set_color_by_gradient(GREEN_B, GREEN_D) - # logo.set_width(1) - # logo.center() - # self.add(SurroundingRectangle(logo)) - # return logo - -class BlockRewards(Scene): - def construct(self): - title = TextMobject("Block rewards") - title.scale(1.5) - logo = BitcoinLogo() - logo.set_height(0.75) - logo.next_to(title, LEFT) - title.add(logo) - title.to_edge(UP) - h_line = Line(LEFT, RIGHT) - h_line.set_width(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - self.add(title, logo, h_line) - - rewards = VGroup( - TextMobject("Jan 2009 - Nov 2012:", "50", "BTC"), - TextMobject("Nov 2012 - Jul 2016:", "25", "BTC"), - TextMobject("Jul 2016 - Feb 2020$^*$:", "12.5", "BTC"), - TextMobject("Feb 2020$^*$ - Sep 2023$^*$:", "6.25", "BTC"), - ) - rewards.arrange( - DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - rewards.next_to(h_line, DOWN) - for reward in rewards: - reward[1].set_color(YELLOW) - - footnote = TextMobject( - "$^*$ Extrapolating from the 25 BTC reward period" - ) - footnote.scale(0.5) - footnote.to_corner(DOWN+RIGHT) - - self.play(LaggedStartMap( - FadeIn, rewards, - run_time = 4, - lag_ratio = 0.5 - )) - self.play(FadeIn(footnote)) - self.wait(3) - -class ShowFirstFewBlocks(ExternallyAnimatedScene): - pass - -class ShowGeometricSum(Scene): - def construct(self): - equation = TexMobject( - "210{,}000", "(", - "50", "+", "25", "+", "12.5", "+", - "6.25", "+\\cdots", ")", "=", "21{,}000{,}000" - ) - numbers = ["50", "25", "12.5", "6.25"] - colors = color_gradient([BLUE_D, BLUE_B], 4) - for tex, color in zip(numbers, colors): - equation.set_color_by_tex(tex, color) - equation[-1].set_color(YELLOW) - - self.add(*equation[:2] + equation[-3:-1]) - for i in range(2, 9, 2): - self.play(FadeIn(VGroup(*equation[i:i+2]))) - self.wait() - self.play(Write(equation[-1])) - self.wait(2) - -class TransactionFeeExample(PiCreatureScene): - def construct(self): - alice = self.pi_creature - payment = TextMobject( - "Alice", "pays", "Bob", "0.42 BTC", - ) - payment.set_color_by_tex("Alice", BLUE_C) - payment.set_color_by_tex("Bob", MAROON) - payment.set_color_by_tex("BTC", YELLOW) - payment.move_to(2.5*UP) - - fee = TextMobject("And leaves", "0.001 BTC", "to the miner") - fee.set_color_by_tex("BTC", YELLOW) - fee.next_to(payment, DOWN) - - signature = TextMobject( - "$\\langle$Alice's digital signature$\\rangle$" - ) - signature.set_color(BLUE_C) - signature.next_to(fee, DOWN) - - group = VGroup(payment, fee, signature) - rect = SurroundingRectangle(group, color = BLUE_B) - - incentive_words = TextMobject( - "Incentivizes miner \\\\ to include" - ) - incentive_words.next_to(rect, DOWN, buff = 1.5) - incentive_words.shift(2*RIGHT) - arrow = Arrow( - incentive_words.get_top(), - rect.get_bottom(), - buff = MED_LARGE_BUFF - ) - - fee.save_state() - fee.shift(DOWN) - fee.set_fill(opacity = 0) - - self.play(Write(payment)) - self.wait() - self.play( - alice.change, "raise_right_hand", payment, - fee.restore, - ) - self.play(Write(signature)) - self.play( - ShowCreation(rect), - alice.change_mode, "happy" - ) - self.wait() - self.play( - Write(incentive_words), - ShowCreation(arrow), - alice.change, "pondering" - ) - self.wait(2) - - def create_pi_creature(self): - alice = PiCreature(color = BLUE_C) - alice.to_edge(DOWN) - alice.shift(FRAME_X_RADIUS*LEFT/2) - return alice - -class ShowBitcoinBlockSize(LedgerScene): - CONFIG = { - "denomination" : "BTC" - } - def construct(self): - block = VGroup() - ledger = self.get_ledger() - - payments = VGroup(*[ - self.add_payment_line_to_ledger(*args) - for args in [ - ("Alice", "Bob", "0.42"), - ("You", "Charlie", "3.14"), - ("Bob", "You", "2.72"), - ("Alice", "Charlie", "4.67"), - ] - ]) - dots = TexMobject("\\vdots") - dots.next_to(payments, DOWN) - payments.add(dots) - payments.to_edge(LEFT) - payments.shift(DOWN+0.5*RIGHT) - payments_rect = SurroundingRectangle( - payments, color = WHITE, buff = MED_LARGE_BUFF - ) - block.add(payments_rect, payments) - - tuples = [ - ("Prev hash", UP, BLUE_C), - ("Proof of work", DOWN, GREEN), - ] - for word, vect, color in tuples: - mob = TextMobject(word) - mob.set_color(color) - rect = SurroundingRectangle( - mob, color = WHITE, buff = MED_SMALL_BUFF - ) - VGroup(mob, rect).next_to(payments_rect, vect, 0) - rect.stretch_to_fit_width(payments_rect.get_width()) - block.add(mob, rect) - - title = VGroup( - BitcoinLogo(height = 0.75), - TextMobject("Block").scale(1.5) - ) - title.arrange(RIGHT, SMALL_BUFF) - title.next_to(block, UP) - - brace = Brace(payments_rect, RIGHT) - limit = brace.get_text( - "Limited to\\\\", - "$\\sim 2{,}400$", "transactions" - ) - limit.set_color_by_tex("2{,}400", RED) - - self.add(title, block) - self.remove(payments) - self.play( - GrowFromCenter(brace), - Write(limit) - ) - self.play(LaggedStartMap(FadeIn, payments)) - self.wait() - - ####Visa - - visa_logo = SVGMobject( - file_name = "visa_logo", - height = 0.5, - stroke_width = 0, - fill_color = BLUE_D, - fill_opacity = 1, - ) - visa_logo[-1].set_color("#faa61a") - visa_logo.sort() - avg_rate = TextMobject("Avg: $1{,}700$/second") - max_rate = TextMobject("Max: $>24{,}000$/second") - rates = VGroup(avg_rate, max_rate) - rates.scale(0.8) - rates.arrange(DOWN, aligned_edge = LEFT) - rates.next_to(visa_logo, RIGHT, buff = MED_SMALL_BUFF) - visa = VGroup(visa_logo, rates) - visa.to_corner(UP+RIGHT) - - self.play(LaggedStartMap(DrawBorderThenFill, visa_logo)) - self.play(LaggedStartMap(FadeIn, avg_rate)) - self.wait() - self.play(LaggedStartMap(FadeIn, max_rate)) - self.wait(2) - -class CurrentAverageFees(Scene): - def construct(self): - fees = TextMobject( - "Current average fees: ", - "$\\sim 0.0013$ BTC", - "$\\approx$", "\\$3.39" - ) - fees.set_color_by_tex("BTC", YELLOW) - fees.set_color_by_tex("\\$", GREEN) - fees.to_edge(UP) - - self.play(Write(fees)) - self.wait() - -class HighlightingAFewFees(ExternallyAnimatedScene): - pass - -class TopicsNotCovered(TeacherStudentsScene): - def construct(self): - title = TextMobject("Topics not covered:") - title.to_corner(UP+LEFT) - title.set_color(YELLOW) - title.save_state() - title.shift(DOWN) - title.set_fill(opacity = 0) - - topics = VGroup(*list(map(TextMobject, [ - "Merkle trees", - "Alternatives to proof of work", - "Scripting", - "$\\vdots$", - "(See links in description)", - ]))) - topics.arrange(DOWN, aligned_edge = LEFT) - topics[-2].next_to(topics[-3], DOWN) - topics.next_to(title, RIGHT) - topics.to_edge(UP) - - self.play( - title.restore, - self.teacher.change_mode, "raise_right_hand" - ) - for topic in topics: - self.change_student_modes( - "confused", "thinking","pondering", - look_at_arg = topic, - added_anims = [LaggedStartMap(FadeIn, topic)] - ) - self.wait() - -class Exchange(Animation): - CONFIG = { - "rate_func" : None, - } - def __init__(self, exchange, **kwargs): - self.swap = Swap( - exchange.left, - exchange.right, - ) - self.changed_symbols_yet = False - Animation.__init__(self, exchange, **kwargs) - - def interpolate_mobject(self, alpha): - exchange = self.mobject - if alpha < 1./3: - self.swap.update(3*alpha) - elif alpha < 2./3: - sub_alpha = alpha*3 - 1 - group = VGroup(exchange.left, exchange.right) - group.set_fill(opacity = 1-smooth(sub_alpha)) - else: - if not self.changed_symbols_yet: - new_left = random.choice( - exchange.cryptocurrencies - ).copy() - new_left.move_to(exchange.right) - new_right = random.choice( - exchange.currencies - ).copy() - new_right.move_to(exchange.left) - Transform(exchange.left, new_left).update(1) - Transform(exchange.right, new_right).update(1) - self.changed_symbols_yet = True - sub_alpha = 3*alpha - 2 - group = VGroup(exchange.left, exchange.right) - group.set_fill(opacity = smooth(sub_alpha)) - -class ShowManyExchanges(Scene): - CONFIG = { - "n_rows" : 2, - "n_cols" : 8, - "shift_radius" : 0.5, - "run_time" : 30, - } - def construct(self): - cryptocurrencies = self.get_cryptocurrencies() - currencies = self.get_currencies() - for currency in it.chain(currencies, cryptocurrencies): - currency.set_height(0.5) - currency.align_data(EthereumLogo()) - exchange = VGroup(*[ - Arrow( - p1, p2, - path_arc = np.pi, - buff = MED_LARGE_BUFF - ) - for p1, p2 in [(LEFT, RIGHT), (RIGHT, LEFT)] - ]).set_color(WHITE) - exchanges = VGroup(*[ - VGroup(*[ - exchange.copy() - for x in range(3) - ]).arrange(RIGHT, buff = 2*LARGE_BUFF) - for y in range(3) - ]).arrange(DOWN, buff = MED_LARGE_BUFF) - exchanges = VGroup(*it.chain(*exchanges)) - self.add(exchanges) - start_times = list(np.linspace(0, 2, len(exchanges))) - random.shuffle(start_times) - for exchange, start_time in zip(exchanges, start_times): - left = random.choice(cryptocurrencies).copy() - right = random.choice(currencies).copy() - left.move_to(exchange.get_left()) - right.move_to(exchange.get_right()) - - exchange.left = left - exchange.right = right - exchange.start_time = start_time - exchange.add(left, right) - exchange.currencies = currencies - exchange.cryptocurrencies = cryptocurrencies - exchange.animation = Exchange(exchange) - - times = np.arange(0, self.run_time, self.frame_duration) - from scene.scene import ProgressDisplay - for t in ProgressDisplay(times): - for exchange in exchanges: - sub_t = t - exchange.start_time - if sub_t < 0: - continue - elif sub_t > 3: - exchange.start_time = t - sub_t = 0 - exchange.animation = Exchange(exchange) - exchange.animation.update(sub_t/3.0) - self.update_frame( - self.extract_mobject_family_members(exchanges) - ) - self.add_frames(self.get_frame()) - - def get_cryptocurrencies(self): - return [ - BitcoinLogo(), - BitcoinLogo(), - BitcoinLogo(), - EthereumLogo(), - LitecoinLogo() - ] - - def get_currencies(self): - return [ - TexMobject("\\$").set_color(GREEN), - TexMobject("\\$").set_color(GREEN), - TexMobject("\\$").set_color(GREEN), - SVGMobject( - file_name = "euro_symbol", - stroke_width = 0, - fill_opacity = 1, - fill_color = BLUE, - ), - SVGMobject( - file_name = "yen_symbol", - stroke_width = 0, - fill_opacity = 1, - fill_color = RED, - ) - ] - -class ShowLDAndOtherCurrencyExchanges(ShowManyExchanges): - CONFIG = { - "run_time" : 15, - } - def get_cryptocurrencies(self): - euro = SVGMobject( - file_name = "euro_symbol", - stroke_width = 0, - fill_opacity = 1, - fill_color = BLUE, - ) - return [ - TextMobject("LD").set_color(YELLOW), - TextMobject("LD").set_color(YELLOW), - euro, euro.copy(), - SVGMobject( - file_name = "yen_symbol", - stroke_width = 0, - fill_opacity = 1, - fill_color = RED, - ), - BitcoinLogo(), - ] - - def get_currencies(self): - return [TexMobject("\\$").set_color(GREEN)] - -class CryptoPatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "Desmos", - "Burt Humburg", - "CrypticSwarm", - "Juan Benet", - "Samantha D. Suplee", - "James Park", - "Erik Sundell", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Karan Bhargava", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Markus Persson", - "Yoni Nazarathy", - "Ed Kellett", - "Joseph John Cox", - "Dan Buchoff", - "Luc Ritchie", - "Andrew Busey", - "Michael McGuffin", - "John Haley", - "Mourits de Beer", - "Ankalagon", - "Eric Lavault", - "Tomohiro Furusawa", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Ryan Dahl", - "Mark Govea", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "Nils Schneider", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ] - } - -class ProtocolLabs(PiCreatureScene): - def construct(self): - morty = self.pi_creature - logo = self.get_logo() - logo.next_to(morty, UP) - logo.shift_onto_screen() - screen_rect = ScreenRectangle() - screen_rect.set_height(5) - screen_rect.to_edge(LEFT) - - self.play( - DrawBorderThenFill(logo[0]), - LaggedStartMap(FadeIn, logo[1]), - morty.change, "raise_right_hand", - ) - self.wait() - self.play( - logo.scale, 0.5, - logo.to_corner, UP+LEFT, - ShowCreation(screen_rect) - ) - modes = ["pondering", "happy", "happy"] - for mode in modes: - for x in range(2): - self.play(Blink(morty)) - self.wait(3) - self.play(morty.change, mode, screen_rect) - - def get_logo(self): - logo = SVGMobject( - file_name = "protocol_labs_logo", - height = 1.5, - fill_color = WHITE, - ) - name = SVGMobject( - file_name = "protocol_labs_name", - height = 0.5*logo.get_height(), - fill_color = LIGHT_GREY, - ) - for mob in logo, name: - for submob in mob: - submob.is_subpath = False - name.next_to(logo, RIGHT) - return VGroup(logo, name) - -class Thumbnail(DistributedBlockChainScene): - CONFIG = { - "n_blocks" : 4, - } - def construct(self): - title = TextMobject("Crypto", "currencies", arg_separator = "") - title.scale(2.5) - title.to_edge(UP) - title[0].set_color(YELLOW) - title[0].set_stroke(RED, 2) - self.add(title) - - # logos = VGroup( - # BitcoinLogo(), EthereumLogo(), LitecoinLogo() - # ) - # for logo in logos: - # logo.set_height(1) - # logos.add(TexMobject("\\dots").scale(2)) - # logos.arrange(RIGHT) - # logos.next_to(title, RIGHT, LARGE_BUFF) - # self.add(logos) - - block_chain = self.get_block_chain() - block_chain.arrows.set_color(RED) - block_chain.blocks.set_color_by_gradient(BLUE, GREEN) - block_chain.set_width(FRAME_WIDTH-1) - block_chain.set_stroke(width = 12) - self.add(block_chain) - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/dandelin.py b/from_3b1b/old/dandelin.py deleted file mode 100644 index d89482c1..00000000 --- a/from_3b1b/old/dandelin.py +++ /dev/null @@ -1,1830 +0,0 @@ -from manimlib.imports import * - -from from_3b1b.old.lost_lecture import Orbiting -from from_3b1b.old.lost_lecture import ShowWord - - -class LogoGeneration(LogoGenerationTemplate): - CONFIG = { - "random_seed": 2, - } - - def get_logo_animations(self, logo): - layers = logo.spike_layers - for layer in layers: - random.shuffle(layer.submobjects) - for spike in layer: - spike.save_state() - spike.scale(0.5) - spike.apply_complex_function(np.log) - spike.rotate(-90 * DEGREES, about_point=ORIGIN) - spike.set_fill(opacity=0) - - return [ - FadeIn( - logo.iris_background, - rate_func=squish_rate_func(smooth, 0.25, 1), - run_time=3, - ), - AnimationGroup(*[ - LaggedStartMap( - Restore, layer, - run_time=3, - path_arc=180 * DEGREES, - rate_func=squish_rate_func(smooth, a / 3.0, (a + 0.9) / 3.0), - lag_ratio=0.8, - ) - for layer, a in zip(layers, [0, 2, 1, 0]) - ]), - Animation(logo.pupil), - ] - - -class ThinkingAboutAProof(PiCreatureScene): - def construct(self): - randy = self.pi_creature - randy.scale(0.5, about_edge=DL) - bubble = ThoughtBubble() - bubble.pin_to(randy) - bubble.shift(MED_SMALL_BUFF * RIGHT) - cloud = bubble[-1] - cloud.rotate(90 * DEGREES) - cloud.set_height(FRAME_HEIGHT - 0.5) - cloud.stretch(2.8, 0) - cloud.next_to(bubble[2], RIGHT) - cloud.to_edge(UP, buff=0.25) - bubble[1].shift(0.25 * UL) - - you_arrow = Vector(LEFT, color=WHITE) - you_arrow.next_to(randy, RIGHT) - you = TextMobject("You") - you.next_to(you_arrow, RIGHT) - lm_arrow = Vector(DOWN, color=WHITE) - lm_arrow.next_to(randy, UP) - love_math = TextMobject("Love math") - love_math.next_to(lm_arrow, UP) - love_math.shift_onto_screen() - - self.add(bubble) - self.play( - FadeInFrom(you, LEFT), - GrowArrow(you_arrow), - ) - self.play( - FadeInFromDown(love_math), - GrowArrow(lm_arrow), - randy.change, "erm" - ) - self.wait(2) - self.play( - randy.change, "pondering", cloud - ) - self.wait(10) - - -class SumOfIntegersProof(Scene): - CONFIG = { - "n": 6, - } - - def construct(self): - equation = TexMobject( - "1", "+", "2", "+", "3", "+", - "\\cdots", "+", "n", - "=", "\\frac{n(n+1)}{2}" - ) - equation.scale(1.5) - equation.to_edge(UP) - one, two, three, dots, n = numbers = VGroup(*[ - equation.get_part_by_tex(tex, substring=False).copy() - for tex in ("1", "2", "3", "\\cdots", "n",) - ]) - for number in numbers: - number.generate_target() - number.target.scale(0.75) - - rows = self.get_rows() - rows.next_to(equation, DOWN, buff=MED_LARGE_BUFF) - flipped_rows = self.get_flipped_rows(rows) - - for row, num in zip(rows, [one, two, three]): - num.target.next_to(row, LEFT) - dots.target.rotate(90 * DEGREES) - dots.target.next_to(rows[3:-1], LEFT) - dots.target.align_to(one.target, LEFT) - n.target.next_to(rows[-1], LEFT) - - for row in rows: - row.save_state() - for square in row: - square.stretch(0, 0) - square.move_to(row, LEFT) - row.fade(1) - - self.play(LaggedStartMap(FadeInFromDown, equation[:-1])) - self.wait() - self.play( - LaggedStartMap( - MoveToTarget, numbers, - path_arc=-90 * DEGREES, - lag_ratio=1, - run_time=1 - ) - ) - self.play(LaggedStartMap(Restore, rows)) - self.wait() - self.play( - ReplacementTransform( - rows.copy().set_fill(opacity=0), flipped_rows, - path_arc=-PI, - run_time=2 - ) - ) - self.wait() - self.play(Write(equation[-1])) - self.wait(5) - - def get_rows(self): - rows = VGroup() - for count in range(1, self.n + 1): - row = VGroup(*[Square() for k in range(count)]) - row.arrange(RIGHT, buff=0) - rows.add(row) - rows.arrange(DOWN, buff=0, aligned_edge=LEFT) - rows.set_height(5) - rows.set_stroke(WHITE, 3) - rows.set_fill(BLUE, 0.5) - return rows - - def get_flipped_rows(self, rows): - result = rows.copy() - result.rotate(PI) - result.set_fill(RED_D, 0.5) - result.move_to(rows, LEFT) - result.shift(rows[0][0].get_width() * RIGHT) - return result - - -class FeynmansLostLectureWrapper(Scene): - def construct(self): - title = TextMobject("Feynman's Lost Lecture") - title.scale(1.5) - title.to_edge(UP) - rect = ScreenRectangle(height=6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - -class HoldUpProof(TeacherStudentsScene): - def construct(self): - title = TextMobject("One of my all-time favorite proofs") - title.to_edge(UP) - self.add(title) - - self.play( - self.teacher.change, "raise_right_hand", self.screen, - self.get_student_changes( - "pondering", "confused", "maybe", - look_at_arg=title - ) - ) - self.look_at(title) - self.wait(5) - self.change_student_modes( - "happy", "thinking", "hooray", - look_at_arg=title - ) - self.wait(5) - - -class MultipleDefinitionsOfAnEllipse(Scene): - def construct(self): - title = Title("Multiple definitions of ``ellipse''") - self.add(title) - - definitions = VGroup( - TextMobject("1. Stretch a circle"), - TextMobject("2. Thumbtack \\\\ \\quad\\, construction"), - TextMobject("3. Slice a cone"), - ) - definitions.arrange( - DOWN, buff=LARGE_BUFF, - aligned_edge=LEFT - ) - definitions.next_to(title, DOWN, LARGE_BUFF) - definitions.to_edge(LEFT) - - for definition in definitions: - definition.saved_state = definition.copy() - definition.saved_state.set_fill(LIGHT_GREY, 0.5) - - self.play(LaggedStartMap( - FadeInFrom, definitions, - lambda m: (m, RIGHT), - run_time=4 - )) - self.wait() - for definition in definitions: - others = [d for d in definitions if d is not definition] - self.play( - definition.set_fill, WHITE, 1, - definition.scale, 1.2, {"about_edge": LEFT}, - *list(map(Restore, others)) - ) - self.wait(2) - - -class StretchACircle(Scene): - def construct(self): - plane = NumberPlane( - x_unit_size=2, - y_unit_size=2 - ) - - circle = Circle(radius=2) - circle.set_stroke(YELLOW, 5) - circle_ghost = circle.copy() - circle_ghost.set_stroke(width=1) - - plane_circle_group = VGroup(plane, circle) - plane_circle_group.save_state() - - arrows = self.get_arrows() - - prop = 1.0 / 8 - start_point = Dot(circle.point_from_proportion(prop)) - end_point = start_point.copy().stretch(2, 0, about_point=ORIGIN) - end_point.stretch(0.5, 0) - end_point.set_color(RED) - - xy = TexMobject("(x, y)") - cxy = TexMobject("(c \\cdot x, y)") - cxy[1].set_color(RED) - for tex in xy, cxy: - tex.scale(1.5) - tex.add_background_rectangle() - - xy_arrow = Vector(DOWN, color=WHITE) - cxy_arrow = Vector(DL, color=WHITE) - xy_arrow.next_to(start_point, UP, SMALL_BUFF) - xy.next_to(xy_arrow, UP, SMALL_BUFF) - cxy_arrow.next_to(end_point, UR, SMALL_BUFF) - cxy.next_to(cxy_arrow, UR, SMALL_BUFF) - - self.add(plane_circle_group) - self.wait() - self.play( - ApplyMethod( - plane_circle_group.stretch, 2, 0, - run_time=2, - ), - LaggedStartMap( - GrowArrow, arrows, - run_time=1, - lag_ratio=1 - ), - ) - self.play(FadeOut(arrows)) - self.wait() - self.play(Restore(plane_circle_group)) - self.play( - GrowArrow(xy_arrow), - Write(xy), - FadeInFrom(start_point, UP), - ) - self.wait() - self.add(circle_ghost) - self.play( - circle.stretch, 2, 0, - ReplacementTransform(start_point.copy(), end_point), - run_time=2 - ) - self.play( - GrowArrow(cxy_arrow), - Write(cxy) - ) - self.wait(2) - - def get_arrows(self): - result = VGroup() - for vect in [LEFT, RIGHT]: - for y in range(-3, 4): - arrow = Vector(vect) - arrow.move_to(2 * vect + y * UP) - result.add(arrow) - result.set_color(RED) - return result - - -class ShowArrayOfEccentricities(Scene): - def construct(self): - eccentricities = np.linspace(0, 0.99, 6) - eccentricity_labels = VGroup(*list(map( - DecimalNumber, eccentricities - ))) - ellipses = self.get_ellipse_row(eccentricities) - ellipses.set_color_by_gradient(BLUE, YELLOW) - ellipses.move_to(DOWN) - - for label, ellipse in zip(eccentricity_labels, ellipses): - label.next_to(ellipse, UP) - - name = TextMobject("Eccentricity") - name.scale(1.5) - name.to_edge(UP) - alt_name = TextMobject("(read ``squishification'')") - alt_name.set_color(YELLOW) - alt_name.next_to(name, RIGHT) - alt_name.shift_onto_screen() - name.generate_target() - name.target.next_to(alt_name, LEFT) - - arrows = VGroup(*[ - Arrow(name.get_bottom(), label.get_top()) - for label in eccentricity_labels - ]) - arrows.set_color_by_gradient(BLUE, YELLOW) - - for label, arrow in zip(eccentricity_labels, arrows): - label.save_state() - label.fade(1) - label.scale(0.1) - label.move_to(arrow.get_start()) - - morty = Mortimer(height=2) - morty.next_to(alt_name, DOWN) - - self.add(ellipses[0]) - for e1, e2 in zip(ellipses[:-1], ellipses[1:]): - self.play(ReplacementTransform( - e1.copy(), e2, - path_arc=10 * DEGREES - )) - self.wait() - - self.play( - Write(name), - LaggedStartMap(GrowArrow, arrows), - LaggedStartMap(Restore, eccentricity_labels) - ) - self.wait() - self.play( - Write(alt_name), - MoveToTarget(name), - morty.change, "hooray", alt_name, - VFadeIn(morty), - ) - self.play(Blink(morty)) - self.play(morty.change, "thinking", ellipses) - self.wait() - self.play(Blink(morty)) - - for i in 0, -1: - e_copy = ellipses[i].copy() - e_copy.set_color(RED) - self.play(ShowCreation(e_copy)) - self.play( - ShowCreationThenFadeAround( - eccentricity_labels[i], - ), - FadeOut(e_copy) - ) - self.wait() - - circle = ellipses[0] - group = VGroup(*it.chain( - eccentricity_labels, - ellipses[1:], - arrows, - name, - alt_name, - [morty] - )) - self.play( - LaggedStartMap(FadeOutAndShiftDown, group), - circle.set_height, 5, - circle.center, - ) - - def get_ellipse(self, eccentricity, width=2): - result = Circle(color=WHITE) - result.set_width(width) - a = width / 2.0 - c = eccentricity * a - b = np.sqrt(a**2 - c**2) - result.stretch(b / a, 1) - result.shift(c * LEFT) - - result.eccentricity = eccentricity - return result - - def get_ellipse_row(self, eccentricities, buff=MED_SMALL_BUFF, **kwargs): - result = VGroup(*[ - self.get_ellipse(e, **kwargs) - for e in eccentricities - ]) - result.arrange(RIGHT, buff=buff) - return result - - def get_eccentricity(self, ellipse): - """ - Assumes that it's major/minor axes line up - with x and y axes - """ - a = ellipse.get_width() - b = ellipse.get_height() - if b > a: - a, b = b, a - c = np.sqrt(a**2 - b**2) - return fdiv(c, a) - - -class ShowOrbits(ShowArrayOfEccentricities): - CONFIG = {"camera_config": {"background_opacity": 1}} - - def construct(self): - earth_eccentricity = 0.0167 - comet_eccentricity = 0.9671 - earth_orbit = self.get_ellipse(eccentricity=earth_eccentricity) - comet_orbit = self.get_ellipse(eccentricity=comet_eccentricity) - earth_orbit.set_height(5) - comet_orbit.set_width( - 0.7 * FRAME_WIDTH, - about_point=ORIGIN, - ) - - sun = ImageMobject("Sun") - earth = ImageMobject("Earth") - comet = ImageMobject("Comet") - sun.set_height(1) - earth.set_height(0.5) - comet.set_height(0.1) - - earth_parts = VGroup(sun, earth_orbit, earth) - - eccentricity_label = DecimalNumber( - earth_eccentricity, - num_decimal_places=4 - ) - eccentricity_equals = TextMobject( - "Eccentricity = " - ) - earth_orbit_words = TextMobject("Earth's orbit") - earth_orbit_words.set_color(BLUE) - full_label = VGroup( - earth_orbit_words, - eccentricity_equals, eccentricity_label - ) - full_label.arrange(RIGHT, SMALL_BUFF) - earth_orbit_words.shift(0.5 * SMALL_BUFF * UL) - full_label.to_edge(UP) - - comet_orbit_words = TextMobject("Halley's comet orbit") - comet_orbit_words.set_color(LIGHT_GREY) - comet_orbit_words.move_to(earth_orbit_words, RIGHT) - - orbiting_earth = Orbiting(earth, sun, earth_orbit) - orbiting_comet = Orbiting(comet, sun, comet_orbit) - - self.add(full_label, earth_orbit_words) - self.add(sun, earth_orbit, orbiting_earth) - self.wait(10) - orbiting_earth.rate = 1.5 - orbiting_comet.rate = 1.5 - self.play( - earth_parts.set_height, - comet_orbit.get_height() / 4.53, - earth_parts.shift, 3 * RIGHT - ) - comet_orbit.shift(3 * RIGHT) - comet_orbit.save_state() - Transform(comet_orbit, earth_orbit).update(1) - self.play( - Restore(comet_orbit, run_time=2), - ChangingDecimal( - eccentricity_label, - lambda a: self.get_eccentricity(comet_orbit) - ), - FadeOutAndShift(earth_orbit_words, UP), - FadeInFromDown(comet_orbit_words) - ) - self.add(orbiting_comet) - self.wait(10) - - -class EccentricityInThumbtackCase(ShowArrayOfEccentricities): - def construct(self): - ellipse = self.get_ellipse(0.2, width=5) - ellipse_target = self.get_ellipse(0.9, width=5) - ellipse_target.scale(fdiv( - sum(self.get_abc(ellipse)), - sum(self.get_abc(ellipse_target)), - )) - for mob in ellipse, ellipse_target: - mob.center() - mob.set_color(BLUE) - thumbtack_update = self.get_thumbtack_update(ellipse) - ellipse_point_update = self.get_ellipse_point_update(ellipse) - focal_lines_update = self.get_focal_lines_update( - ellipse, ellipse_point_update.mobject - ) - focus_to_focus_line_update = self.get_focus_to_focus_line_update(ellipse) - eccentricity_label = self.get_eccentricity_label() - eccentricity_value_update = self.get_eccentricity_value_update( - eccentricity_label, ellipse, - ) - inner_brace_update = self.get_focus_line_to_focus_line_brace_update( - focus_to_focus_line_update.mobject - ) - outer_lines = self.get_outer_dashed_lines(ellipse) - outer_lines_brace = Brace(outer_lines, DOWN) - - focus_distance = TextMobject("Focus distance") - focus_distance.set_color(GREEN) - focus_distance.next_to(inner_brace_update.mobject, DOWN, SMALL_BUFF) - focus_distance.add_to_back(focus_distance.copy().set_stroke(BLACK, 5)) - focus_distance_update = Mobject.add_updater( - focus_distance, - lambda m: m.set_width( - inner_brace_update.mobject.get_width(), - ).next_to(inner_brace_update.mobject, DOWN, SMALL_BUFF) - ) - diameter = TextMobject("Diameter") - diameter.set_color(RED) - diameter.next_to(outer_lines_brace, DOWN, SMALL_BUFF) - - fraction = TexMobject( - "{\\text{Focus distance}", "\\over", - "\\text{Diameter}}" - ) - numerator = fraction.get_part_by_tex("Focus") - numerator.set_color(GREEN) - fraction.set_color_by_tex("Diameter", RED) - fraction.move_to(2 * UP) - fraction.to_edge(RIGHT, buff=MED_LARGE_BUFF) - numerator_update = Mobject.add_updater( - numerator, - lambda m: m.set_width(focus_distance.get_width()).next_to( - fraction[1], UP, MED_SMALL_BUFF - ) - ) - - fraction_arrow = Arrow( - eccentricity_label.get_right(), - fraction.get_top() + MED_SMALL_BUFF * UP, - path_arc=-60 * DEGREES, - ) - fraction_arrow.pointwise_become_partial(fraction_arrow, 0, 0.95) - - ellipse_transformation = Transform( - ellipse, ellipse_target, - rate_func=there_and_back, - run_time=8, - ) - - self.add(ellipse) - self.add(thumbtack_update) - self.add(ellipse_point_update) - self.add(focal_lines_update) - self.add(focus_to_focus_line_update) - self.add(eccentricity_label) - self.add(eccentricity_value_update) - - self.play(ellipse_transformation) - - self.add(inner_brace_update) - self.add(outer_lines) - self.add(outer_lines_brace) - - self.add(fraction) - self.add(fraction_arrow) - self.add(focus_distance) - self.add(diameter) - - self.add(focus_distance_update) - self.add(numerator_update) - - self.play( - ellipse_transformation, - VFadeIn(inner_brace_update.mobject), - VFadeIn(outer_lines), - VFadeIn(outer_lines_brace), - VFadeIn(fraction), - VFadeIn(fraction_arrow), - VFadeIn(focus_distance), - VFadeIn(diameter), - ) - - def get_thumbtack_update(self, ellipse): - thumbtacks = VGroup(*[ - self.get_thumbtack() - for x in range(2) - ]) - - def update_thumbtacks(thumbtacks): - foci = self.get_foci(ellipse) - for thumbtack, focus in zip(thumbtacks, foci): - thumbtack.move_to(focus, DR) - return thumbtacks - - return Mobject.add_updater(thumbtacks, update_thumbtacks) - - def get_ellipse_point_update(self, ellipse): - dot = Dot(color=RED) - return cycle_animation(MoveAlongPath( - dot, ellipse, - run_time=5, - rate_func=linear - )) - - def get_focal_lines_update(self, ellipse, ellipse_point): - lines = VGroup(*[Line(LEFT, RIGHT) for x in range(2)]) - lines.set_color_by_gradient(YELLOW, PINK) - - def update_lines(lines): - foci = self.get_foci(ellipse) - Q = ellipse_point.get_center() - for line, focus in zip(lines, foci): - line.put_start_and_end_on(focus, Q) - return lines - - return Mobject.add_updater(lines, update_lines) - - def get_focus_to_focus_line_update(self, ellipse): - return Mobject.add_updater( - Line(LEFT, RIGHT, color=WHITE), - lambda m: m.put_start_and_end_on(*self.get_foci(ellipse)) - ) - - def get_focus_line_to_focus_line_brace_update(self, line): - brace = Brace(Line(LEFT, RIGHT)) - brace.add_to_back(brace.copy().set_stroke(BLACK, 5)) - return Mobject.add_updater( - brace, - lambda b: b.match_width(line, stretch=True).next_to( - line, DOWN, buff=SMALL_BUFF - ) - ) - - def get_eccentricity_label(self): - words = TextMobject("Eccentricity = ") - decimal = DecimalNumber(0, num_decimal_places=2) - group = VGroup(words, decimal) - group.arrange(RIGHT) - group.to_edge(UP) - return group - - def get_eccentricity_value_update(self, eccentricity_label, ellipse): - decimal = eccentricity_label[1] - decimal.add_updater(lambda d: d.set_value( - self.get_eccentricity(ellipse) - )) - return decimal - - def get_outer_dashed_lines(self, ellipse): - line = DashedLine(2.5 * UP, 2.5 * DOWN) - return VGroup( - line.move_to(ellipse, RIGHT), - line.copy().move_to(ellipse, LEFT), - ) - - # - def get_abc(self, ellipse): - a = ellipse.get_width() / 2 - b = ellipse.get_height() / 2 - c = np.sqrt(a**2 - b**2) - return a, b, c - - def get_foci(self, ellipse): - a, b, c = self.get_abc(ellipse) - return [ - ellipse.get_center() + c * RIGHT, - ellipse.get_center() + c * LEFT, - ] - - def get_thumbtack(self): - angle = 10 * DEGREES - result = SVGMobject(file_name="push_pin") - result.set_height(0.5) - result.set_fill(LIGHT_GREY) - result.rotate(angle) - return result - - -class EccentricityForSlicedConed(Scene): - def construct(self): - equation = TexMobject( - "\\text{Eccentricity} = ", - "{\\sin(", "\\text{angle of plane}", ")", "\\over", - "\\sin(", "\\text{angle of cone slant}", ")}" - ) - equation.set_color_by_tex("plane", YELLOW) - equation.set_color_by_tex("cone", BLUE) - equation.to_edge(LEFT) - self.play(FadeInFromDown(equation)) - - -class AskWhyAreTheyTheSame(TeacherStudentsScene): - def construct(self): - morty = self.teacher - self.student_says( - "Why on earth \\\\ are these the same?", - student_index=2, - target_mode="sassy", - bubble_kwargs={"direction": LEFT} - ) - bubble = self.students[2].bubble - self.play( - morty.change, "awe", - self.get_student_changes("confused", "confused", "sassy") - ) - self.look_at(self.screen) - self.wait(3) - self.play(morty.change, "thinking", self.screen) - self.change_student_modes("maybe", "erm", "confused") - self.look_at(self.screen) - self.wait(3) - - baby_morty = BabyPiCreature() - baby_morty.match_style(morty) - baby_morty.to_corner(DL) - - self.play( - FadeOutAndShift(bubble), - FadeOutAndShift(bubble.content), - LaggedStartMap( - FadeOutAndShift, self.students, - lambda m: (m, 3 * DOWN), - ), - ReplacementTransform( - morty, baby_morty, - path_arc=30 * DEGREES, - run_time=2, - ) - ) - self.pi_creatures = VGroup(baby_morty) - bubble = ThoughtBubble(height=6, width=7) - bubble.set_fill(DARK_GREY, 0.5) - bubble.pin_to(baby_morty) - - egg = Circle(radius=0.4) - egg.stretch(0.75, 1) - egg.move_to(RIGHT) - egg.apply_function( - lambda p: np.array([ - p[0], p[0] * p[1], p[2] - ]) - ) - egg.flip() - egg.set_width(3) - egg.set_stroke(RED, 5) - egg.move_to(bubble.get_bubble_center()) - - self.play(baby_morty.change, "confused", 2 * DOWN) - self.wait(2) - self.play( - baby_morty.change, "thinking", - LaggedStartMap(DrawBorderThenFill, bubble) - ) - self.play(ShowCreation(egg)) - self.wait(3) - - bubble_group = VGroup(bubble, egg) - self.play( - ApplyMethod( - bubble_group.shift, FRAME_WIDTH * LEFT, - rate_func=running_start, - ), - baby_morty.change, "awe" - ) - self.play(Blink(baby_morty)) - self.wait() - - -class TriangleOfEquivalences(Scene): - def construct(self): - title = Title("How do you prove this\\textinterrobang.") - self.add(title) - rects = VGroup(*[ScreenRectangle() for x in range(3)]) - rects.set_height(2) - rects[:2].arrange(RIGHT, buff=2) - rects[2].next_to(rects[:2], DOWN, buff=1.5) - rects.next_to(title, DOWN) - - arrows = VGroup(*[ - TexMobject("\\Leftrightarrow") - for x in range(3) - ]) - arrows.scale(2) - arrows[0].move_to(rects[:2]) - arrows[1].rotate(60 * DEGREES) - arrows[1].move_to(rects[1:]) - arrows[2].rotate(-60 * DEGREES) - arrows[2].move_to(rects[::2]) - arrows[1:].shift(0.5 * DOWN) - - self.play(LaggedStartMap( - DrawBorderThenFill, arrows, - lag_ratio=0.7, - run_time=3, - )) - self.wait() - self.play(FadeOutAndShift(arrows[1:])) - self.wait() - - -class SliceCone(ExternallyAnimatedScene): - pass - - -class TiltPlane(ExternallyAnimatedScene): - pass - - -class IntroduceConeEllipseFocalSum(ExternallyAnimatedScene): - pass - - -class ShowMeasurementBook(TeacherStudentsScene): - CONFIG = {"camera_config": {"background_opacity": 1}} - - def construct(self): - measurement = ImageMobject("MeasurementCover") - measurement.set_height(3.5) - measurement.move_to(self.hold_up_spot, DOWN) - - words = TextMobject("Highly recommended") - arrow = Vector(RIGHT, color=WHITE) - arrow.next_to(measurement, LEFT) - words.next_to(arrow, LEFT) - - self.play( - self.teacher.change, "raise_right_hand", - FadeInFromDown(measurement) - ) - self.change_all_student_modes("hooray") - self.wait() - self.play( - GrowArrow(arrow), - FadeInFrom(words, RIGHT), - self.get_student_changes( - "thinking", "happy", "pondering", - look_at_arg=arrow - ) - ) - self.wait(3) - - -class IntroduceSpheres(ExternallyAnimatedScene): - pass - - -class TangencyAnimation(Scene): - def construct(self): - rings = VGroup(*[ - Circle(color=YELLOW, stroke_width=3, radius=0.5) - for x in range(5) - ]) - for ring in rings: - ring.save_state() - ring.scale(0) - ring.saved_state.set_stroke(width=0) - - self.play(LaggedStartMap( - Restore, rings, - run_time=2, - lag_ratio=0.7 - )) - - -class TwoSpheresRotating(ExternallyAnimatedScene): - pass - - -class TiltSlopeWithOnlySpheres(ExternallyAnimatedScene): - pass - - -class TiltSlopeWithOnlySpheresSideView(ExternallyAnimatedScene): - pass - - -class AskAboutWhyYouWouldAddSpheres(PiCreatureScene): - def construct(self): - randy = self.pi_creature - randy.flip() - randy.set_height(2) - randy.set_color(BLUE_C) - randy.to_edge(RIGHT) - randy.shift(2 * UP) - randy.look(UP) - - graph_spot = VectorizedPoint() - - why = TextMobject("...why?") - why.next_to(randy, UP) - - bubble = ThoughtBubble(height=2, width=2) - bubble.pin_to(randy) - - self.play(FadeInFromDown(randy)) - self.play( - Animation(graph_spot), - randy.change, "maybe", - Write(why), - ) - self.wait(3) - self.play(randy.change, "pondering") - self.play( - why.to_corner, DR, - why.set_fill, LIGHT_GREY, 0.5, - ) - self.wait() - self.play( - ShowCreation(bubble), - randy.change, "thinking" - ) - self.wait() - self.look_at(graph_spot) - self.wait(2) - - -class ShowTangencyPoints(ExternallyAnimatedScene): - pass - - -class ShowFocalLinesAsTangent(ExternallyAnimatedScene): - pass - - -class UseDefiningFeatures(Scene): - def construct(self): - title = TextMobject("Problem-solving tip:") - title.scale(1.5) - title.to_edge(UP) - tip = TextMobject( - """ - - Make sure you're using all the \\\\ - \\phantom{-} defining features of the objects \\\\ - \\phantom{-} involved. - """, - alignment="" - ) - tip.next_to(title, DOWN, MED_LARGE_BUFF) - tip.shift(MED_SMALL_BUFF * RIGHT) - tip.set_color(YELLOW) - - self.add(title) - self.play(Write(tip, run_time=4)) - self.wait() - - -class RemindAboutTangencyToCone(ExternallyAnimatedScene): - pass - - -class ShowCircleToCircleLine(ExternallyAnimatedScene): - pass - - -class ShowSegmentSplit(Scene): - CONFIG = { - "include_image": True, - } - - def construct(self): - if self.include_image: - image = ImageMobject("ShowCircleToCircleLine") - image.set_height(FRAME_HEIGHT) - self.add(image) - - brace1 = Brace(Line(ORIGIN, 1.05 * UP), LEFT) - brace2 = Brace(Line(1.7 * DOWN, ORIGIN), LEFT) - braces = VGroup(brace1, brace2) - braces.rotate(-14 * DEGREES) - braces.move_to(0.85 * UP + 1.7 * LEFT) - - words = VGroup( - TextMobject("Top segment"), - TextMobject("Bottom segment") - ) - for word, brace in zip(words, braces): - word.next_to( - brace.get_center(), LEFT, - buff=0.35 - ) - words[0].set_color(PINK) - words[1].set_color(GOLD) - - for mob in it.chain(braces, words): - mob.add_to_back(mob.copy().set_stroke(BLACK, 5)) - - for brace in braces: - brace.save_state() - brace.set_stroke(width=0) - brace.scale(0) - - self.play( - LaggedStartMap( - Restore, braces, - lag_ratio=0.7 - ), - ) - for word in words: - self.play(Write(word, run_time=1)) - self.wait() - - -class ShowSegmentSplitWithoutImage(ShowSegmentSplit): - CONFIG = { - "include_image": False, - } - - -class ShowCircleToCircleLineAtMultiplePoints(ExternallyAnimatedScene): - pass - - -class ConjectureLineEquivalence(ExternallyAnimatedScene): - pass - - -class WriteConjecture(Scene): - CONFIG = { - "image_name": "ConjectureLineEquivalenceBigSphere", - "q_coords": 1.28 * UP + 0.15 * LEFT, - "circle_point_coords": 0.84 * RIGHT + 0.05 * DOWN, - "tangent_point_coords": 0.85 * UP + 1.75 * LEFT, - "plane_line_color": GOLD, - "text_scale_factor": 0.75, - "shift_plane_word_down": False, - "include_image": False, - } - - def construct(self): - if self.include_image: - image = ImageMobject(self.image_name) - image.set_height(FRAME_HEIGHT) - self.add(image) - - title = TextMobject("Conjecture:") - title.to_corner(UR) - - cone_line = Line(self.q_coords, self.circle_point_coords) - plane_line = Line(self.q_coords, self.tangent_point_coords) - plane_line.set_color(self.plane_line_color) - lines = VGroup(cone_line, plane_line) - - cone_line_words = TextMobject("Cone line") - plane_line_words = TextMobject("Plane line") - plane_line_words.set_color(self.plane_line_color) - words = VGroup(cone_line_words, plane_line_words) - - for word in words: - word.add_to_back(word.copy().set_stroke(BLACK, 5)) - word.in_equation = word.copy() - - equation = VGroup( - TexMobject("||"), - words[0].in_equation, - TexMobject("||"), - TexMobject("="), - TexMobject("||"), - words[1].in_equation, - TexMobject("||"), - ) - equation.arrange(RIGHT, buff=SMALL_BUFF) - equation.scale(0.75) - equation.next_to(title, DOWN, MED_LARGE_BUFF) - equation.shift_onto_screen() - title.next_to(equation, UP) - - for word, line in zip(words, lines): - word.scale(self.text_scale_factor) - word.next_to(ORIGIN, UP, SMALL_BUFF) - if self.shift_plane_word_down and (word is plane_line_words): - word.next_to(ORIGIN, DOWN, SMALL_BUFF) - angle = line.get_angle() - if abs(angle) > 90 * DEGREES: - angle += PI - word.rotate(angle, about_point=ORIGIN) - word.shift(line.get_center()) - - self.play(LaggedStartMap( - FadeInFromDown, - VGroup(title, equation), - lag_ratio=0.7 - )) - self.wait() - for word, line in zip(words, lines): - self.play(ShowCreation(line)) - self.play(WiggleOutThenIn(line)) - self.play(ReplacementTransform( - word.in_equation.copy(), word - )) - self.wait() - - -class WriteConjectureV2(WriteConjecture): - CONFIG = { - "image_name": "ConjectureLineEquivalenceSmallSphere", - "q_coords": 2.2 * LEFT + 1.3 * UP, - "circle_point_coords": 1.4 * LEFT + 2.25 * UP, - "tangent_point_coords": 0.95 * LEFT + 1.51 * UP, - "plane_line_color": PINK, - "text_scale_factor": 0.5, - "shift_plane_word_down": True, - "include_image": False, - } - - -class ShowQ(Scene): - def construct(self): - mob = TexMobject("Q") - mob.scale(2) - mob.add_to_back(mob.copy().set_stroke(BLACK, 5)) - self.play(FadeInFromDown(mob)) - self.wait() - - -class ShowBigSphereTangentLines(ExternallyAnimatedScene): - pass - - -class LinesTangentToSphere(ExternallyAnimatedScene): - pass - - -class QuickGeometryProof(Scene): - def construct(self): - radius = 2 - circle = Circle(color=BLUE, radius=radius) - circle.shift(0.5 * DOWN) - angle = 60 * DEGREES - O = circle.get_center() - p1 = circle.point_from_proportion(angle / TAU) - p2 = circle.point_from_proportion(1 - angle / TAU) - Q = O + (radius / np.cos(angle)) * RIGHT - - O_p1 = Line(O, p1) - O_p2 = Line(O, p2) - p1_Q = Line(p1, Q, color=MAROON_B) - p2_Q = Line(p2, Q, color=MAROON_B) - O_Q = DashedLine(O, Q) - - elbow = VGroup(Line(RIGHT, UR), Line(UR, UP)) - elbow.set_stroke(WHITE, 1) - elbow.scale(0.2, about_point=ORIGIN) - - ticks = VGroup(Line(DOWN, UP), Line(DOWN, UP)) - ticks.scale(0.1) - ticks.arrange(RIGHT, buff=SMALL_BUFF) - - equation = TexMobject( - "\\Delta OP_1Q \\cong \\Delta OP_2Q", - tex_to_color_map={ - "O": BLUE, - "P_1": MAROON_B, - "P_2": MAROON_B, - "Q": YELLOW, - } - ) - equation.to_edge(UP) - self.add(*equation) - - self.add(circle) - self.add( - TexMobject("O").next_to(O, LEFT), - TexMobject("P_1").next_to(p1, UP).set_color(MAROON_B), - TexMobject("P_2").next_to(p2, DOWN).set_color(MAROON_B), - TexMobject("Q").next_to(Q, RIGHT).set_color(YELLOW), - ) - self.add(O_p1, O_p2, p1_Q, p2_Q, O_Q) - self.add( - Dot(O, color=BLUE), - Dot(p1, color=MAROON_B), - Dot(p2, color=MAROON_B), - Dot(Q, color=YELLOW) - ) - self.add( - elbow.copy().rotate(angle + PI, about_point=ORIGIN).shift(p1), - elbow.copy().rotate(-angle + PI / 2, about_point=ORIGIN).shift(p2), - ) - self.add( - ticks.copy().rotate(angle).move_to(O_p1), - ticks.copy().rotate(-angle).move_to(O_p2), - ) - - everything = VGroup(*self.mobjects) - - self.play(LaggedStartMap( - GrowFromCenter, everything, - lag_ratio=0.25, - run_time=4 - )) - - -class ShowFocalSumEqualsCircleDistance(ExternallyAnimatedScene): - pass - - -class FinalMovingEllipsePoint(ExternallyAnimatedScene): - pass - - -class TiltPlaneWithSpheres(ExternallyAnimatedScene): - pass - - -class NameDandelin(Scene): - CONFIG = {"camera_config": {"background_opacity": 1}} - - def construct(self): - title = TextMobject( - "Proof by\\\\", - "Germinal Pierre Dandelin (1822)" - ) - title.to_edge(UP) - - portrait = ImageMobject("GerminalDandelin") - portrait.set_height(5) - portrait.next_to(title, DOWN) - - google_result = ImageMobject("GoogleDandelin") - google_result.set_height(4) - google_result.center() - google_result.to_corner(DR) - - cmon_google = TextMobject("C'mon Google...") - cmon_google.set_color(RED) - cmon_google.next_to(google_result, RIGHT) - cmon_google.next_to(google_result, UP, aligned_edge=RIGHT) - - dandelion = ImageMobject("DandelionSphere", height=1.5) - dandelion.to_edge(LEFT, buff=LARGE_BUFF) - dandelion.shift(UP) - big_dandelion = dandelion.copy().scale(2) - big_dandelion.next_to(dandelion, DOWN, buff=0) - dandelions = Group(dandelion, big_dandelion) - - self.add(title[0]) - self.play(FadeInFromDown(portrait)) - self.play(Write(title[1])) - self.wait() - self.play(FadeInFrom(google_result, LEFT)) - self.play(Write(cmon_google, run_time=1)) - self.wait() - - self.play(LaggedStartMap( - FadeInFromDown, dandelions, - lag_ratio=0.7, - run_time=1 - )) - self.wait() - - -class DandelinSpheresInCylinder(ExternallyAnimatedScene): - pass - - -class ProjectCircleOntoTiltedPlane(ExternallyAnimatedScene): - pass - - -class CylinderDandelinSpheresChangingSlope(ExternallyAnimatedScene): - pass - - -class DetailsLeftAsHomework(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs": { - "color": GREY_BROWN, - "flip_at_start": False, - }, - "default_pi_creature_start_corner": DL, - } - - def construct(self): - # morty = self.pi_creature - self.pi_creature_says( - "Details left \\\\ as homework", - target_mode="hooray" - ) - self.wait(3) - - -class AskWhyYouWouldChooseThisProof(PiCreatureScene): - def construct(self): - randy, other = self.pi_creatures - screen = ScreenRectangle(height=4).to_edge(UP) - - for pi, vect, word in (randy, UP, "You"), (other, LEFT, "Non-math \\\\ enthusiast"): - arrow = Vector(-vect, color=WHITE) - arrow.next_to(pi, vect) - label = TextMobject(word) - label.next_to(arrow, vect) - pi.arrow = arrow - pi.label = label - - for pi, mode in (randy, "hooray"), (other, "tired"): - self.play( - GrowArrow(pi.arrow), - FadeInFrom(pi.label, RIGHT), - pi.change, mode, - ) - self.play( - randy.change, "raise_right_hand", screen, - other.look_at, screen, - ) - self.wait() - self.play(other.change, "thinking", screen) - self.wait(5) - - def create_pi_creatures(self): - randy = Randolph(color=BLUE_C) - other = PiCreature(color=RED_D) - other.flip() - group = VGroup(randy, other) - group.arrange(RIGHT, buff=5) - group.to_edge(DOWN) - return group - - -class CreativeConstruction(PiCreatureScene): - def construct(self): - randy = self.pi_creature - self.remove(randy) - - dandelin = ImageMobject("GerminalDandelin") - dandelin.set_height(4) - dandelin.move_to(FRAME_WIDTH * RIGHT / 4) - - lightbulb = Lightbulb() - lightbulb.next_to(dandelin, UP) - - kant = ImageMobject("Kant") - kant.set_height(3) - bubble = ThoughtBubble(height=3, width=4) - bubble.pin_to(kant) - kant_words = TextMobject( - "How is synthetic a priori\\\\" + - "reasoning possible?" - ) - kant_words.scale(0.75) - bubble.position_mobject_inside(kant_words) - kant_group = VGroup(bubble, kant_words, kant) - kant_group.to_corner(UR) - - self.add(dandelin) - self.add(lightbulb) - self.play( - Write(lightbulb, run_time=1), - self.get_light_shine(lightbulb) - ) - self.wait() - self.play( - lightbulb.next_to, randy, RIGHT, - {"buff": LARGE_BUFF, "aligned_edge": UP}, - randy.change, "pondering", - VFadeIn(randy), - FadeOutAndShift(dandelin, DOWN), - ) - - self.play( - self.get_light_shine(lightbulb), - Blink(randy), - ) - self.wait() - self.play( - FadeInFromDown(kant), - Write(bubble), - Write(kant_words), - ) - - lightbulb.generate_target() - q_marks = VGroup() - for submob in lightbulb.target.family_members_with_points(): - if True or get_norm(submob.get_center() - lightbulb.get_center()) > 0.25: - q_mark = TexMobject("?") - q_mark.set_height(0.25) - q_mark.move_to(submob) - Transform(submob, q_mark).update(1) - q_marks.add(submob) - q_marks.space_out_submobjects(2) - - self.wait() - self.play(randy.change, 'confused', lightbulb) - self.play(MoveToTarget( - lightbulb, - run_time=3, - rate_func=there_and_back, - lag_ratio=0.5 - )) - self.play(Blink(randy)) - self.wait() - - def get_rings(self, center, max_radius, delta_r): - radii = np.arange(0, max_radius, delta_r) - rings = VGroup(*[ - Annulus( - inner_radius=r1, - outer_radius=r2, - fill_opacity=0.75 * (1 - fdiv(r1, max_radius)), - fill_color=YELLOW - ) - for r1, r2 in zip(radii, radii[1:]) - ]) - rings.move_to(center) - return rings - - def get_light_shine(self, lightbulb, max_radius=15.0, delta_r=0.025): - rings = self.get_rings( - lightbulb.get_center(), - max_radius=15.0, - delta_r=0.025, - ) - return LaggedStartMap( - FadeIn, rings, - rate_func=there_and_back, - run_time=2, - lag_ratio=0.5 - ) - - -class LockhartQuote(Scene): - CONFIG = { - "camera_config": {"background_opacity": 1} - } - - def construct(self): - mb_string = "Madame\\,\\,Bovary" - ml_string = "Mona\\,\\,Lisa." - strings = (mb_string, ml_string) - quote_text = """ - \\large - How do people come up with such ingenious arguments? - It's the same way people come up with %s or %s - I have no idea how it happens. I only know that - when it happens to me, I feel very fortunate. - """ % strings - quote_parts = [s for s in quote_text.split(" ") if s] - quote = TextMobject( - *quote_parts, - tex_to_color_map={ - mb_string: BLUE, - ml_string: YELLOW, - }, - alignment="" - ) - quote.set_width(FRAME_WIDTH - 2) - quote.to_edge(UP) - - measurement = ImageMobject("MeasurementCover") - madame_bovary = ImageMobject("MadameBovary") - mona_lisa = ImageMobject("MonaLisa") - pictures = Group(measurement, madame_bovary, mona_lisa) - for picture in pictures: - picture.set_height(4) - pictures.arrange(RIGHT, buff=LARGE_BUFF) - pictures.to_edge(DOWN) - - measurement.save_state() - measurement.set_width(FRAME_WIDTH) - measurement.center() - measurement.fade(1) - self.play(Restore(measurement)) - self.wait() - for word in quote: - anims = [ShowWord(word)] - for text, picture in zip(strings, pictures[1:]): - if word is quote.get_part_by_tex(text): - anims.append(FadeInFromDown( - picture, run_time=1 - )) - self.play(*anims) - self.wait(0.005 * len(word)**1.5) - self.wait(2) - self.play( - LaggedStartMap( - FadeOutAndShiftDown, quote, - lag_ratio=0.2, - run_time=5, - ), - LaggedStartMap( - FadeOutAndShiftDown, pictures, - run_time=3, - ), - ) - - -class ImmersedInGeometryProblems(PiCreatureScene): - def construct(self): - randy = self.pi_creature - randy.center().to_edge(DOWN) - - for vect in compass_directions(start_vect=UL): - self.play(randy.change, "pondering", 4 * vect) - self.wait(2) - - -class ShowApollonianCircles(Scene): - def construct(self): - radii = [1.0, 2.0, 3.0] - curvatures = [1.0 / r for r in radii] - k4 = sum(curvatures) - 2 * np.sqrt( - sum([ - k1 * k2 - for k1, k2 in it.combinations(curvatures, 2) - ]) - ) - radii.append(1.0 / k4) - centers = [ - ORIGIN, 3 * RIGHT, 4 * UP, - 4 * UP + 3 * RIGHT, - ] - circles = VGroup(*[ - Circle(radius=r).shift(c) - for r, c in zip(radii, centers) - ]) - - circles.set_height(FRAME_HEIGHT - 3) - circles.center().to_edge(DOWN) - # circles.set_fill(opacity=1) - circles.submobjects.reverse() - circles.set_stroke(width=5) - circles.set_color_by_gradient(BLUE, YELLOW) - - equation = TexMobject(""" - \\left( - {1 \\over r_1} + {1 \\over r_2} + - {1 \\over r_3} + {1 \\over r_4} - \\right)^2 = - 2\\left( - {1 \\over r_1^2} + {1 \\over r_2^2} + - {1 \\over r_3^2} + {1 \\over r_4^2} - \\right) - """) - # equation.scale(1.5) - equation.next_to(circles, UP) - - self.add(equation) - self.play(LaggedStartMap( - DrawBorderThenFill, circles - )) - self.wait() - - -class EllipseLengthsLinedUp(EccentricityInThumbtackCase): - def construct(self): - ellipse = self.get_ellipse(eccentricity=0.6) - ellipse.scale(2) - foci = self.get_foci(ellipse) - - point = VectorizedPoint() - point_movement = cycle_animation( - MoveAlongPath( - point, ellipse, - run_time=5, - rate_func=linear, - ) - ) - - arrow = Vector(RIGHT, color=WHITE) - arrow.to_edge(LEFT) - q_mark = TexMobject("?") - q_mark.next_to(arrow, UP) - - lines = VGroup(*[Line(UP, DOWN) for x in range(2)]) - lines.set_color_by_gradient(PINK, GOLD) - lines.set_stroke(width=5) - - h_line = Line(LEFT, RIGHT, color=WHITE) - h_line.set_width(0.25) - - def update_lines(lines): - for line, focus in zip(lines, foci): - d = get_norm(point.get_center() - focus) - line.put_start_and_end_on( - ORIGIN, d * UP - ) - lines.arrange(DOWN, buff=0) - lines.next_to(arrow, RIGHT) - h_line.move_to(lines[0].get_bottom()) - lines_animation = Mobject.add_updater( - lines, update_lines - ) - - self.add(point_movement) - self.add(arrow) - self.add(q_mark) - self.add(h_line) - self.add(lines_animation) - - self.wait(20) - - -class ReactionToGlimpseOfGenius(TeacherStudentsScene, CreativeConstruction): - def construct(self): - morty = self.teacher - - lightbulb = Lightbulb() - lightbulb.set_stroke(width=4) - lightbulb.scale(1.5) - lightbulb.move_to(self.hold_up_spot, DOWN) - - rings = self.get_rings( - lightbulb.get_center(), - max_radius=15, - delta_r=0.1, - ) - - arrow = Vector(RIGHT, color=WHITE) - arrow.next_to(lightbulb, LEFT) - - clock = Clock() - clock.next_to(arrow, LEFT) - - pi_lights = VGroup() - for pi in self.students: - light = Lightbulb() - light.scale(0.75) - light.next_to(pi, UP) - pi.light = light - pi_lights.add(light) - - q_marks = VGroup() - for submob in lightbulb: - q_mark = TexMobject("?") - q_mark.move_to(submob) - q_marks.add(q_mark) - q_marks.space_out_submobjects(2) - - self.student_says( - "I think Lockhart was \\\\" - "speaking more generally.", - target_mode="sassy", - added_anims=[morty.change, "guilty"] - ) - self.wait(2) - self.play( - morty.change, "raise_right_hand", - FadeInFromDown(lightbulb), - RemovePiCreatureBubble(self.students[1]), - self.get_student_changes(*3 * ["confused"]), - run_time=1 - ) - self.play(Transform( - lightbulb, q_marks, - run_time=3, - rate_func=there_and_back_with_pause, - lag_ratio=0.5 - )) - self.play( - ClockPassesTime(clock, hours_passed=4, run_tim=4), - VFadeIn(clock), - GrowArrow(arrow), - self.get_student_changes( - *3 * ["pondering"], - look_at_arg=clock - ) - ) - self.play( - ClockPassesTime(clock, run_time=1, hours_passed=1), - VFadeOut(clock), - FadeOut(arrow), - lightbulb.scale, 1.5, - lightbulb.move_to, 2 * UP, - self.get_student_changes( - *3 * ["awe"], - look_at_arg=2 * UP - ), - run_time=1 - ) - self.play(self.get_light_shine(lightbulb)) - self.play( - ReplacementTransform( - VGroup(lightbulb), - pi_lights - ), - morty.change, "happy", - *[ - ApplyMethod(pi.change, mode, pi.get_top()) - for pi, mode in zip(self.students, [ - "hooray", "tease", "surprised" - ]) - ] - ) - self.wait(3) - - -class DandelinEndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "Juan Benet", - "Matt Russell", - "Soekul", - "Keith Smith", - "Burt Humburg", - "CrypticSwarm", - "Brian Tiger Chow", - "Joseph Kelly", - "Roy Larson", - "Andrew Sachs", - "Devin Scott", - "Akash Kumar", - "Arthur Zey", - "Ali Yahya", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Jordan Scales", - "Markus Persson", - "Fela", - "Fred Ehrsam", - "Gary Kong", - "Randy C. Will", - "Britt Selvitelle", - "Jonathan Wilson", - "Ryan Atallah", - "Joseph John Cox", - "Luc Ritchie", - "Valeriy Skobelev", - "Adrian Robinson", - "Michael Faust", - "Solara570", - "George M. Botros", - "Peter Ehrnstrom", - "Kai-Siang Ang", - "Alexis Olson", - "Ludwig", - "Omar Zrien", - "Sindre Reino Trosterud", - "Jeff Straathof", - "Matt Langford", - "Matt Roveto", - "Marek Cirkos", - "Magister Mugit", - "Stevie Metke", - "Cooper Jones", - "James Hughes", - "John V Wertheim", - "Chris Giddings", - "Song Gao", - "Richard Burgmann", - "John Griffith", - "Chris Connett", - "Steven Tomlinson", - "Jameel Syed", - "Bong Choung", - "Ignacio Freiberg", - "Zhilong Yang", - "Giovanni Filippi", - "Eric Younge", - "Prasant Jagannath", - "James H. Park", - "Norton Wang", - "Kevin Le", - "Oliver Steele", - "Yaw Etse", - "Dave B", - "supershabam", - "Delton Ding", - "Thomas Tarler", - "1stViewMaths", - "Jacob Magnuson", - "Mark Govea", - "Clark Gaebel", - "Mathias Jansson", - "David Clark", - "Michael Gardner", - "Mads Elvheim", - "Awoo", - "Dr . David G. Stork", - "Ted Suzman", - "Linh Tran", - "Andrew Busey", - "John Haley", - "Ankalagon", - "Eric Lavault", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Robert Teed", - "Jason Hise", - "Bernd Sing", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ], - } diff --git a/from_3b1b/old/div_curl.py b/from_3b1b/old/div_curl.py deleted file mode 100644 index 90731ce2..00000000 --- a/from_3b1b/old/div_curl.py +++ /dev/null @@ -1,4616 +0,0 @@ - -from manimlib.imports import * - - -# Quick note to anyone coming to this file with the -# intent of recreating animations from the video. Some -# of these, especially those involving AnimatedStreamLines, -# can take an extremely long time to run, but much of the -# computational cost is just for giving subtle little effects -# which don't matter too much. Switching the line_anim_class -# to ShowPassingFlash will give significant speedups, as will -# increasing the values of delta_x and delta_y in sampling for -# the stream lines. Certainly while developing, things were not -# run at production quality. - -FOX_COLOR = "#DF7F20" -RABBIT_COLOR = "#C6D6EF" - - -# Warning, this file uses ContinualChangingDecimal, -# which has since been been deprecated. Use a mobject -# updater instead - - -# Helper functions -def joukowsky_map(z): - if z == 0: - return 0 - return z + fdiv(1, z) - - -def inverse_joukowsky_map(w): - u = 1 if w.real >= 0 else -1 - return (w + u * np.sqrt(w**2 - 4)) / 2 - - -def derivative(func, dt=1e-7): - return lambda z: (func(z + dt) - func(z)) / dt - - -def negative_gradient(potential_func, dt=1e-7): - def result(p): - output = potential_func(p) - dx = dt * RIGHT - dy = dt * UP - dz = dt * OUT - return -np.array([ - (potential_func(p + dx) - output) / dt, - (potential_func(p + dy) - output) / dt, - (potential_func(p + dz) - output) / dt, - ]) - return result - - -def divergence(vector_func, dt=1e-7): - def result(point): - value = vector_func(point) - return sum([ - (vector_func(point + dt * RIGHT) - value)[i] / dt - for i, vect in enumerate([RIGHT, UP, OUT]) - ]) - return result - - -def two_d_curl(vector_func, dt=1e-7): - def result(point): - value = vector_func(point) - return op.add( - (vector_func(point + dt * RIGHT) - value)[1] / dt, - -(vector_func(point + dt * UP) - value)[0] / dt, - ) - return result - - -def cylinder_flow_vector_field(point, R=1, U=1): - z = R3_to_complex(point) - # return complex_to_R3(1.0 / derivative(joukowsky_map)(z)) - return complex_to_R3(derivative(joukowsky_map)(z).conjugate()) - - -def cylinder_flow_magnitude_field(point): - return get_norm(cylinder_flow_vector_field(point)) - - -def vec_tex(s): - return "\\vec{\\textbf{%s}}" % s - - -def four_swirls_function(point): - x, y = point[:2] - result = (y**3 - 4 * y) * RIGHT + (x**3 - 16 * x) * UP - result *= 0.05 - norm = get_norm(result) - if norm == 0: - return result - # result *= 2 * sigmoid(norm) / norm - return result - - -def get_force_field_func(*point_strength_pairs, **kwargs): - radius = kwargs.get("radius", 0.5) - - def func(point): - result = np.array(ORIGIN) - for center, strength in point_strength_pairs: - to_center = center - point - norm = get_norm(to_center) - if norm == 0: - continue - elif norm < radius: - to_center /= radius**3 - elif norm >= radius: - to_center /= norm**3 - to_center *= -strength - result += to_center - return result - return func - - -def get_charged_particles(color, sign, radius=0.1): - result = Circle( - stroke_color=WHITE, - stroke_width=0.5, - fill_color=color, - fill_opacity=0.8, - radius=radius - ) - sign = TexMobject(sign) - sign.set_stroke(WHITE, 1) - sign.set_width(0.5 * result.get_width()) - sign.move_to(result) - result.add(sign) - return result - - -def get_proton(radius=0.1): - return get_charged_particles(RED, "+", radius) - - -def get_electron(radius=0.05): - return get_charged_particles(BLUE, "-", radius) - - -def preditor_prey_vector_field(point): - alpha = 30.0 - beta = 1.0 - gamma = 30.0 - delta = 1.0 - x, y = point[:2] - result = 0.05 * np.array([ - alpha * x - beta * x * y, - delta * x * y - gamma * y, - 0, - ]) - return rotate(result, 1 * DEGREES) - -# Mobjects - -# TODO, this is untested after turning it from a -# ContinualAnimation into a VGroup -class JigglingSubmobjects(VGroup): - CONFIG = { - "amplitude": 0.05, - "jiggles_per_second": 1, - } - - def __init__(self, group, **kwargs): - VGroup.__init__(self, **kwargs) - for submob in group.submobjects: - submob.jiggling_direction = rotate_vector( - RIGHT, np.random.random() * TAU, - ) - submob.jiggling_phase = np.random.random() * TAU - self.add(submob) - self.add_updater(lambda m, dt: m.update(dt)) - - def update(self, dt): - for submob in self.submobjects: - submob.jiggling_phase += dt * self.jiggles_per_second * TAU - submob.shift( - self.amplitude * - submob.jiggling_direction * - np.sin(submob.jiggling_phase) * dt - ) - -# Scenes - - -class Introduction(MovingCameraScene): - CONFIG = { - "stream_lines_config": { - "start_points_generator_config": { - "delta_x": 1.0 / 8, - "delta_y": 1.0 / 8, - "y_min": -8.5, - "y_max": 8.5, - } - }, - "vector_field_config": {}, - "virtual_time": 3, - } - - def construct(self): - # Divergence - def div_func(p): - return p / 3 - div_vector_field = VectorField( - div_func, **self.vector_field_config - ) - stream_lines = StreamLines( - div_func, **self.stream_lines_config - ) - stream_lines.shuffle() - div_title = self.get_title("Divergence") - - self.add(div_vector_field) - self.play( - LaggedStartMap(ShowPassingFlash, stream_lines), - FadeIn(div_title[0]), - *list(map(GrowFromCenter, div_title[1])) - ) - - # Curl - def curl_func(p): - return rotate_vector(p / 3, 90 * DEGREES) - - curl_vector_field = VectorField( - curl_func, **self.vector_field_config - ) - stream_lines = StreamLines( - curl_func, **self.stream_lines_config - ) - stream_lines.shuffle() - curl_title = self.get_title("Curl") - - self.play( - ReplacementTransform(div_vector_field, curl_vector_field), - ReplacementTransform( - div_title, curl_title, - path_arc=90 * DEGREES - ), - ) - self.play(ShowPassingFlash(stream_lines, run_time=3)) - self.wait() - - def get_title(self, word): - title = TextMobject(word) - title.scale(2) - title.to_edge(UP) - title.add_background_rectangle() - return title - - -class ShowWritingTrajectory(TeacherStudentsScene): - def construct(self): - self.add_screen() - self.show_meandering_path() - self.show_previous_video() - - def add_screen(self): - self.screen.scale(1.4, about_edge=UL) - self.add(self.screen) - - def show_meandering_path(self): - solid_path = VMobject().set_points_smoothly([ - 3 * UP + 2 * RIGHT, - 2 * UP + 4 * RIGHT, - 3.5 * UP + 3.5 * RIGHT, - 2 * UP + 3 * RIGHT, - 3 * UP + 7 * RIGHT, - 3 * UP + 5 * RIGHT, - UP + 6 * RIGHT, - 2 * RIGHT, - 2 * UP + 2 * RIGHT, - self.screen.get_right() - ]) - step = 1.0 / 80 - dashed_path = VGroup(*[ - VMobject().pointwise_become_partial( - solid_path, x, x + step / 2 - ) - for x in np.arange(0, 1 + step, step) - ]) - arrow = Arrow( - solid_path.points[-2], - solid_path.points[-1], - buff=0 - ) - dashed_path.add(arrow.tip) - dashed_path.set_color_by_gradient(BLUE, YELLOW) - - self.play( - ShowCreation( - dashed_path, - rate_func=bezier([0, 0, 1, 1]), - run_time=5, - ), - self.teacher.change, "raise_right_hand", - self.get_student_changes(*["sassy"] * 3) - ) - self.play( - LaggedStartMap( - ApplyMethod, dashed_path, - lambda m: (m.scale, 0), - remover=True - ), - self.teacher.change, "tease", - self.get_student_changes( - *["pondering"] * 3, - look_at_arg=self.screen - ) - ) - - def show_previous_video(self): - screen = self.screen - - arrow = Vector(LEFT, color=WHITE) - arrow.next_to(screen, RIGHT) - prev_words = TextMobject("Previous video") - prev_words.next_to(arrow, RIGHT) - - screen.generate_target() - screen.target.set_height(3.75) - screen.target.to_corner(UR) - complex_words = TextMobject("Complex derivatives") - complex_words.next_to( - screen.target, LEFT, - buff=2 * SMALL_BUFF + arrow.get_length() - ) - - self.play( - GrowArrow(arrow), - Write(prev_words) - ) - self.wait(3) - self.play( - arrow.flip, - arrow.next_to, screen.target, LEFT, SMALL_BUFF, - MoveToTarget(screen), - FadeOut(prev_words), - Write(complex_words), - self.teacher.change, "raise_right_hand", - path_arc=30 * DEGREES - ) - self.change_student_modes("erm", "sassy", "confused") - self.look_at(screen) - self.wait(2) - self.change_student_modes("pondering", "confused", "sassy") - self.wait(2) - - bubble = self.teacher.get_bubble( - bubble_class=SpeechBubble, - height=3, width=5 - ) - complex_words.generate_target() - complex_words.target.move_to(bubble.get_bubble_center()) - # self.play( - # FadeOut(screen), - # FadeOut(arrow), - # ShowCreation(bubble), - # self.teacher.change, "hooray", - # MoveToTarget(complex_words), - # ) - - s0 = self.students[0] - s0.target_center = s0.get_center() - - def update_s0(s0, dt): - s0.target_center += dt * LEFT * 0.5 - s0.move_to(s0.target_center) - - self.add(Mobject.add_updater(s0, update_s0)) - self.change_student_modes("tired", "horrified", "sad") - self.play(s0.look, LEFT) - self.wait(4) - - -class TestVectorField(Scene): - CONFIG = { - "func": cylinder_flow_vector_field, - "flow_time": 15, - } - - def construct(self): - lines = StreamLines( - four_swirls_function, - virtual_time=3, - min_magnitude=0, - max_magnitude=2, - ) - self.add(AnimatedStreamLines( - lines, - line_anim_class=ShowPassingFlash - )) - self.wait(10) - - -class CylinderModel(Scene): - CONFIG = { - "production_quality_flow": True, - "vector_field_func": cylinder_flow_vector_field, - } - - def construct(self): - self.add_plane() - self.add_title() - self.show_numbers() - self.show_contour_lines() - self.show_flow() - self.apply_joukowsky_map() - - def add_plane(self): - self.plane = ComplexPlane() - self.plane.add_coordinates() - self.plane.coordinate_labels.submobjects.pop(-1) - self.add(self.plane) - - def add_title(self): - title = TextMobject("Complex Plane") - title.to_edge(UP, buff=MED_SMALL_BUFF) - title.add_background_rectangle() - self.title = title - self.add(title) - - def show_numbers(self): - run_time = 5 - - unit_circle = self.unit_circle = Circle( - radius=self.plane.unit_size, - fill_color=BLACK, - fill_opacity=0, - stroke_color=YELLOW - ) - dot = Dot() - dot_update = UpdateFromFunc( - dot, lambda d: d.move_to(unit_circle.point_from_proportion(1)) - ) - exp_tex = TexMobject("e^{", "0.00", "i}") - zero = exp_tex.get_part_by_tex("0.00") - zero.fade(1) - exp_tex_update = UpdateFromFunc( - exp_tex, lambda et: et.next_to(dot, UR, SMALL_BUFF) - ) - exp_decimal = DecimalNumber( - 0, num_decimal_places=2, - include_background_rectangle=True, - color=YELLOW - ) - exp_decimal.replace(zero) - exp_decimal_update = ChangeDecimalToValue( - exp_decimal, TAU, - position_update_func=lambda mob: mob.move_to(zero), - run_time=run_time, - ) - - sample_numbers = [ - complex(-5, 2), - complex(2, 2), - complex(3, 1), - complex(-5, -2), - complex(-4, 1), - ] - sample_labels = VGroup() - for z in sample_numbers: - sample_dot = Dot(self.plane.number_to_point(z)) - sample_label = DecimalNumber( - z, - num_decimal_places=0, - include_background_rectangle=True, - ) - sample_label.next_to(sample_dot, UR, SMALL_BUFF) - sample_labels.add(VGroup(sample_dot, sample_label)) - - self.play( - ShowCreation(unit_circle, run_time=run_time), - VFadeIn(exp_tex), - UpdateFromAlphaFunc( - exp_decimal, - lambda ed, a: ed.set_fill(opacity=a) - ), - dot_update, - exp_tex_update, - exp_decimal_update, - LaggedStartMap( - FadeIn, sample_labels, - remover=True, - rate_func=there_and_back, - run_time=run_time, - ) - ) - self.play( - FadeOut(exp_tex), - FadeOut(exp_decimal), - FadeOut(dot), - unit_circle.set_fill, BLACK, {"opacity": 1}, - ) - self.wait() - - def show_contour_lines(self): - warped_grid = self.warped_grid = self.get_warpable_grid() - h_line = Line(3 * LEFT, 3 * RIGHT, color=WHITE) # Hack - func_label = self.get_func_label() - - self.remove(self.plane) - self.add_foreground_mobjects(self.unit_circle, self.title) - self.play( - warped_grid.apply_complex_function, inverse_joukowsky_map, - Animation(h_line, remover=True) - ) - self.play(Write(func_label)) - self.add_foreground_mobjects(func_label) - self.wait() - - def show_flow(self): - stream_lines = self.get_stream_lines() - stream_lines_copy = stream_lines.copy() - stream_lines_copy.set_stroke(YELLOW, 1) - stream_lines_animation = self.get_stream_lines_animation( - stream_lines - ) - - tiny_buff = 0.0001 - v_lines = VGroup(*[ - Line( - UP, ORIGIN, - path_arc=0, - n_arc_anchors=20, - ).shift(x * RIGHT) - for x in np.linspace(0, 1, 5) - ]) - v_lines.match_background_image_file(stream_lines) - fast_lines, slow_lines = [ - VGroup(*[ - v_lines.copy().next_to(point, vect, tiny_buff) - for point, vect in it.product(h_points, [UP, DOWN]) - ]) - for h_points in [ - [0.5 * LEFT, 0.5 * RIGHT], - [2 * LEFT, 2 * RIGHT], - ] - ] - for lines in fast_lines, slow_lines: - lines.apply_complex_function(inverse_joukowsky_map) - - self.add(stream_lines_animation) - self.wait(7) - self.play( - ShowCreationThenDestruction( - stream_lines_copy, - lag_ratio=0, - run_time=3, - ) - ) - self.wait() - self.play(ShowCreation(fast_lines)) - self.wait(2) - self.play(ReplacementTransform(fast_lines, slow_lines)) - self.wait(3) - self.play( - FadeOut(slow_lines), - VFadeOut(stream_lines_animation.mobject) - ) - self.remove(stream_lines_animation) - - def apply_joukowsky_map(self): - shift_val = 0.1 * LEFT + 0.2 * UP - scale_factor = get_norm(RIGHT - shift_val) - movers = VGroup(self.warped_grid, self.unit_circle) - self.unit_circle.insert_n_curves(50) - - stream_lines = self.get_stream_lines() - stream_lines.scale(scale_factor) - stream_lines.shift(shift_val) - stream_lines.apply_complex_function(joukowsky_map) - - self.play( - movers.scale, scale_factor, - movers.shift, shift_val, - ) - self.wait() - self.play( - movers.apply_complex_function, joukowsky_map, - ShowCreationThenFadeAround(self.func_label), - run_time=2 - ) - self.add(self.get_stream_lines_animation(stream_lines)) - self.wait(20) - - # Helpers - - def get_func_label(self): - func_label = self.func_label = TexMobject("f(z) = z + 1 / z") - func_label.add_background_rectangle() - func_label.next_to(self.title, DOWN, MED_SMALL_BUFF) - return func_label - - def get_warpable_grid(self): - top_grid = NumberPlane() - top_grid.prepare_for_nonlinear_transform() - bottom_grid = top_grid.copy() - tiny_buff = 0.0001 - top_grid.next_to(ORIGIN, UP, buff=tiny_buff) - bottom_grid.next_to(ORIGIN, DOWN, buff=tiny_buff) - result = VGroup(top_grid, bottom_grid) - result.add(*[ - Line( - ORIGIN, FRAME_WIDTH * RIGHT / 2, - color=WHITE, - path_arc=0, - n_arc_anchors=100, - ).next_to(ORIGIN, vect, buff=2) - for vect in (LEFT, RIGHT) - ]) - # This line is a bit of a hack - h_line = Line(LEFT, RIGHT, color=WHITE) - h_line.set_points([LEFT, LEFT, RIGHT, RIGHT]) - h_line.scale(2) - result.add(h_line) - return result - - def get_stream_lines(self): - func = self.vector_field_func - if self.production_quality_flow: - delta_x = 0.5 - delta_y = 0.1 - else: - delta_x = 1 - # delta_y = 1 - delta_y = 0.1 - return StreamLines( - func, - start_points_generator_config={ - "x_min": -8, - "x_max": -7, - "y_min": -4, - "y_max": 4, - "delta_x": delta_x, - "delta_y": delta_y, - "n_repeats": 1, - "noise_factor": 0.1, - }, - stroke_width=2, - virtual_time=15, - ) - - def get_stream_lines_animation(self, stream_lines): - if self.production_quality_flow: - line_anim_class = ShowPassingFlashWithThinningStrokeWidth - else: - line_anim_class = ShowPassingFlash - return AnimatedStreamLines( - stream_lines, - line_anim_class=line_anim_class, - ) - - -class OkayNotToUnderstand(Scene): - def construct(self): - words = TextMobject( - "It's okay not to \\\\ understand this just yet." - ) - morty = Mortimer() - morty.change("confused") - words.next_to(morty, UP) - self.add(morty, words) - - -class ThatsKindOfInteresting(TeacherStudentsScene): - def construct(self): - self.student_says( - "Cool!", target_mode="hooray", - student_index=2, - added_anims=[self.teacher.change, "happy"] - ) - self.change_student_modes("happy", "happy") - self.wait(2) - - -class ElectricField(CylinderModel, MovingCameraScene): - def construct(self): - self.add_plane() - self.add_title() - self.setup_warped_grid() - self.show_uniform_field() - self.show_moving_charges() - self.show_field_lines() - - def setup_warped_grid(self): - warped_grid = self.warped_grid = self.get_warpable_grid() - warped_grid.save_state() - func_label = self.get_func_label() - unit_circle = self.unit_circle = Circle( - radius=self.plane.unit_size, - stroke_color=YELLOW, - fill_color=BLACK, - fill_opacity=1 - ) - - self.add_foreground_mobjects(self.title, func_label, unit_circle) - self.remove(self.plane) - self.play( - warped_grid.apply_complex_function, inverse_joukowsky_map, - ) - self.wait() - - def show_uniform_field(self): - vector_field = self.vector_field = VectorField( - lambda p: UP, - colors=[BLUE_E, WHITE, RED] - ) - protons, electrons = groups = [ - VGroup(*[method(radius=0.2) for x in range(20)]) - for method in (get_proton, get_electron) - ] - for group in groups: - group.arrange(RIGHT, buff=MED_SMALL_BUFF) - random.shuffle(group.submobjects) - protons.next_to(FRAME_HEIGHT * DOWN / 2, DOWN) - electrons.next_to(FRAME_HEIGHT * UP / 2, UP) - - self.play( - self.warped_grid.restore, - FadeOut(self.unit_circle), - FadeOut(self.title), - FadeOut(self.func_label), - LaggedStartMap(GrowArrow, vector_field) - ) - self.remove_foreground_mobjects(self.title, self.func_label) - self.wait() - for group, vect in (protons, UP), (electrons, DOWN): - self.play(LaggedStartMap( - ApplyMethod, group, - lambda m: (m.shift, (FRAME_HEIGHT + 1) * vect), - run_time=3, - rate_func=rush_into - )) - - def show_moving_charges(self): - unit_circle = self.unit_circle - - protons = VGroup(*[ - get_proton().move_to( - rotate_vector(0.275 * n * RIGHT, angle) - ) - for n in range(4) - for angle in np.arange( - 0, TAU, TAU / (6 * n) if n > 0 else TAU - ) - ]) - jiggling_protons = JigglingSubmobjects(protons) - electrons = VGroup(*[ - get_electron().move_to( - proton.get_center() + - proton.radius * rotate_vector(RIGHT, angle) - ) - for proton in protons - for angle in [np.random.random() * TAU] - ]) - jiggling_electrons = JigglingSubmobjects(electrons) - electrons.generate_target() - for electron in electrons.target: - y_part = electron.get_center()[1] - if y_part > 0: - electron.shift(2 * y_part * DOWN) - - # New vector field - def new_electric_field(point): - if get_norm(point) < 1: - return ORIGIN - vect = cylinder_flow_vector_field(point) - return rotate_vector(vect, 90 * DEGREES) - new_vector_field = VectorField( - new_electric_field, - colors=self.vector_field.colors - ) - - warped_grid = self.warped_grid - - self.play(GrowFromCenter(unit_circle)) - self.add(jiggling_protons, jiggling_electrons) - self.add_foreground_mobjects( - self.vector_field, unit_circle, protons, electrons - ) - self.play( - LaggedStartMap(VFadeIn, protons), - LaggedStartMap(VFadeIn, electrons), - ) - self.play( - self.camera.frame.scale, 0.7, - run_time=3 - ) - self.play( - MoveToTarget(electrons), # More indication? - warped_grid.apply_complex_function, inverse_joukowsky_map, - Transform( - self.vector_field, - new_vector_field - ), - run_time=3 - ) - self.wait(5) - - def show_field_lines(self): - h_lines = VGroup(*[ - Line( - 5 * LEFT, 5 * RIGHT, - path_arc=0, - n_arc_anchors=50, - stroke_color=LIGHT_GREY, - stroke_width=2, - ).shift(y * UP) - for y in np.arange(-3, 3.25, 0.25) - if y != 0 - ]) - h_lines.apply_complex_function(inverse_joukowsky_map) - for h_line in h_lines: - h_line.save_state() - - voltage = DecimalNumber( - 10, num_decimal_places=1, - unit="\\, V", - color=YELLOW, - include_background_rectangle=True, - ) - vp_prop = 0.1 - voltage_point = VectorizedPoint( - h_lines[4].point_from_proportion(vp_prop) - ) - - def get_voltage(dummy_arg): - y = voltage_point.get_center()[1] - return 10 - y - - voltage_update = voltage.add_updater( - lambda d: d.set_value(get_voltage), - ) - voltage.add_updater( - lambda d: d.next_to( - voltage_point, UP, SMALL_BUFF - ) - ) - - self.play(ShowCreation( - h_lines, - run_time=2, - lag_ratio=0 - )) - self.add(voltage_update) - self.add_foreground_mobjects(voltage) - self.play( - UpdateFromAlphaFunc( - voltage, lambda m, a: m.set_fill(opacity=a) - ), - h_lines[4].set_stroke, YELLOW, 4, - ) - for hl1, hl2 in zip(h_lines[4:], h_lines[5:]): - self.play( - voltage_point.move_to, - hl2.point_from_proportion(vp_prop), - hl1.restore, - hl2.set_stroke, YELLOW, 3, - run_time=0.5 - ) - self.wait(0.5) - - -class AskQuestions(TeacherStudentsScene): - def construct(self): - div_tex = TexMobject("\\nabla \\cdot", vec_tex("v")) - curl_tex = TexMobject("\\nabla \\times", vec_tex("v")) - div_name = TextMobject("Divergence") - curl_name = TextMobject("Curl") - div = VGroup(div_name, div_tex) - curl = VGroup(curl_name, curl_tex) - for group in div, curl: - group[1].set_color_by_tex(vec_tex("v"), YELLOW) - group.arrange(DOWN) - topics = VGroup(div, curl) - topics.arrange(DOWN, buff=LARGE_BUFF) - topics.move_to(self.hold_up_spot, DOWN) - div.save_state() - div.move_to(self.hold_up_spot, DOWN) - screen = self.screen - - self.student_says( - "What does fluid flow have \\\\ to do with electricity?", - added_anims=[self.teacher.change, "happy"] - ) - self.wait() - self.student_says( - "And you mentioned \\\\ complex numbers?", - student_index=0, - ) - self.wait(3) - self.play( - FadeInFromDown(div), - self.teacher.change, "raise_right_hand", - FadeOut(self.students[0].bubble), - FadeOut(self.students[0].bubble.content), - self.get_student_changes(*["pondering"] * 3) - ) - self.play( - FadeInFromDown(curl), - div.restore - ) - self.wait() - self.look_at(self.screen) - self.wait() - self.change_all_student_modes("hooray", look_at_arg=screen) - self.wait(3) - - topics.generate_target() - topics.target.to_edge(LEFT, buff=LARGE_BUFF) - arrow = TexMobject("\\leftrightarrow") - arrow.scale(2) - arrow.next_to(topics.target, RIGHT, buff=LARGE_BUFF) - screen.next_to(arrow, RIGHT, LARGE_BUFF) - complex_analysis = TextMobject("Complex analysis") - complex_analysis.next_to(screen, UP) - - self.play( - MoveToTarget(topics), - self.get_student_changes( - "confused", "sassy", "erm", - look_at_arg=topics.target - ), - self.teacher.change, "pondering", screen - ) - self.play( - Write(arrow), - FadeInFromDown(complex_analysis) - ) - self.look_at(screen) - self.wait(6) - - -class ScopeMeiosis(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs": { - "flip_at_start": True, - "color": GREY_BROWN, - }, - "default_pi_creature_start_corner": DR, - } - - def construct(self): - morty = self.pi_creature - section_titles = VGroup(*list(map(TextMobject, [ - "Background on div/curl", - "Conformal maps", - "Conformal map $\\Rightarrow" + - "\\text{div}\\textbf{F} = " + - "\\text{curl}\\textbf{F} = 0$", - "Complex derivatives", - ]))) - sections = VGroup(*[ - VGroup(title, self.get_lines(title, 3)) - for title in section_titles - ]) - sections.arrange( - DOWN, buff=MED_LARGE_BUFF, - aligned_edge=LEFT - ) - sections.to_edge(UP) - - top_title = section_titles[0] - lower_sections = sections[1:] - - self.add(sections) - modes = [ - "pondering", - "pondering", - "bump", - "bump", - "concerned_musician", - "concerned_musician", - ] - - for n, mode in zip(list(range(6)), modes): - if n % 2 == 1: - top_title = lines - lines = self.get_lines(top_title, 4) - else: - lines = self.get_lines(top_title, 6) - lower_sections.generate_target() - lower_sections.target.next_to( - lines, DOWN, MED_LARGE_BUFF, LEFT, - ) - self.play( - ShowCreation(lines), - MoveToTarget(lower_sections), - morty.change, mode, lines, - ) - - def get_lines(self, title, n_lines): - lines = VGroup(*[ - Line(3 * LEFT, 3 * RIGHT, color=LIGHT_GREY) - for x in range(n_lines) - ]) - lines.arrange(DOWN, buff=MED_SMALL_BUFF) - lines.next_to( - title, DOWN, - buff=MED_LARGE_BUFF, - aligned_edge=LEFT - ) - lines[-1].pointwise_become_partial( - lines[-1], 0, random.random() - ) - return lines - - -class WhyAreYouTellingUsThis(TeacherStudentsScene): - def construct(self): - self.student_says( - "Cool story bro...\\\\ how about the actual math?", - target_mode="sassy", - added_anims=[self.teacher.change, "guilty"] - ) - self.change_student_modes("angry", "sassy", "angry") - self.wait(2) - - -class TopicsAndConnections(Scene): - CONFIG = { - "random_seed": 1, - } - - def construct(self): - dots = VGroup(*[ - Dot(8 * np.array([ - random.random(), - random.random(), - 0 - ])) - for n in range(5) - ]) - topics = VGroup(*[ - TextMobject(word).next_to( - dot, RIGHT, SMALL_BUFF - ) - for dot, word in zip(dots, [ - "Divergence/curl", - "Fluid flow", - "Electricity and magnetism", - "Conformal maps", - "Complex numbers" - ]) - ]) - for topic in topics: - topic.add_to_back( - topic.copy().set_stroke(BLACK, 2) - ) - - VGroup(dots, topics).center() - for dot in dots: - dot.save_state() - dot.scale(3) - dot.set_fill(opacity=0) - - connections = VGroup(*[ - DashedLine(d1.get_center(), d2.get_center()) - for d1, d2 in it.combinations(dots, 2) - ]) - connections.set_stroke(YELLOW, 2) - - full_rect = FullScreenFadeRectangle() - - self.play( - LaggedStartMap( - ApplyMethod, dots, - lambda d: (d.restore,) - ), - LaggedStartMap(Write, topics), - ) - self.wait() - self.play( - LaggedStartMap(ShowCreation, connections), - Animation(topics), - Animation(dots), - ) - self.wait() - self.play( - FadeIn(full_rect), - Animation(topics[0]), - Animation(dots[0]), - ) - self.wait() - - -class OnToTheLesson(Scene): - def construct(self): - words = TextMobject("On to the lesson!") - words.scale(1.5) - self.add(words) - self.play(FadeInFromDown(words)) - self.wait() - - -class IntroduceVectorField(Scene): - CONFIG = { - "vector_field_config": { - # "delta_x": 2, - # "delta_y": 2, - "delta_x": 0.5, - "delta_y": 0.5, - }, - "stream_line_config": { - "start_points_generator_config": { - # "delta_x": 1, - # "delta_y": 1, - "delta_x": 0.25, - "delta_y": 0.25, - }, - "virtual_time": 3, - }, - "stream_line_animation_config": { - # "line_anim_class": ShowPassingFlash, - "line_anim_class": ShowPassingFlashWithThinningStrokeWidth, - } - } - - def construct(self): - self.add_plane() - self.add_title() - self.points_to_vectors() - self.show_fluid_flow() - self.show_gravitational_force() - self.show_magnetic_force() - self.show_fluid_flow() - - def add_plane(self): - plane = self.plane = NumberPlane() - plane.add_coordinates() - plane.remove(plane.coordinate_labels[-1]) - self.add(plane) - - def add_title(self): - title = TextMobject("Vector field") - title.scale(1.5) - title.to_edge(UP, buff=MED_SMALL_BUFF) - title.add_background_rectangle(opacity=1, buff=SMALL_BUFF) - self.add_foreground_mobjects(title) - - def points_to_vectors(self): - vector_field = self.vector_field = VectorField( - four_swirls_function, - **self.vector_field_config - ) - dots = VGroup() - for vector in vector_field: - dot = Dot(radius=0.05) - dot.move_to(vector.get_start()) - dot.target = vector - dots.add(dot) - - self.play(LaggedStartMap(GrowFromCenter, dots)) - self.wait() - self.play(LaggedStartMap(MoveToTarget, dots, remover=True)) - self.add(vector_field) - self.wait() - - def show_fluid_flow(self): - vector_field = self.vector_field - stream_lines = StreamLines( - vector_field.func, - **self.stream_line_config - ) - stream_line_animation = AnimatedStreamLines( - stream_lines, - **self.stream_line_animation_config - ) - - self.add(stream_line_animation) - self.play( - vector_field.set_fill, {"opacity": 0.5} - ) - self.wait(7) - self.play( - vector_field.set_fill, {"opacity": 1}, - VFadeOut(stream_line_animation.mobject), - ) - self.remove(stream_line_animation) - - def show_gravitational_force(self): - earth = self.earth = ImageMobject("earth") - moon = self.moon = ImageMobject("moon", height=1) - earth_center = 3 * RIGHT + 2 * UP - moon_center = 3 * LEFT + DOWN - earth.move_to(earth_center) - moon.move_to(moon_center) - - gravity_func = get_force_field_func((earth_center, -6), (moon_center, -1)) - gravity_field = VectorField( - gravity_func, - **self.vector_field_config - ) - - self.add_foreground_mobjects(earth, moon) - self.play( - GrowFromCenter(earth), - GrowFromCenter(moon), - Transform(self.vector_field, gravity_field), - run_time=2 - ) - self.vector_field.func = gravity_field.func - self.wait() - - def show_magnetic_force(self): - magnetic_func = get_force_field_func( - (3 * LEFT, -1), (3 * RIGHT, +1) - ) - magnetic_field = VectorField( - magnetic_func, - **self.vector_field_config - ) - magnet = VGroup(*[ - Rectangle( - width=3.5, - height=1, - stroke_width=0, - fill_opacity=1, - fill_color=color - ) - for color in (BLUE, RED) - ]) - magnet.arrange(RIGHT, buff=0) - for char, vect in ("S", LEFT), ("N", RIGHT): - letter = TextMobject(char) - edge = magnet.get_edge_center(vect) - letter.next_to(edge, -vect, buff=MED_LARGE_BUFF) - magnet.add(letter) - - self.add_foreground_mobjects(magnet) - self.play( - self.earth.scale, 0, - self.moon.scale, 0, - DrawBorderThenFill(magnet), - Transform(self.vector_field, magnetic_field), - run_time=2 - ) - self.vector_field.func = magnetic_field.func - self.remove_foreground_mobjects(self.earth, self.moon) - - -class QuickNoteOnDrawingThese(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Quick note on \\\\ drawing vector fields", - bubble_kwargs={"width": 5, "height": 3}, - added_anims=[self.get_student_changes( - "confused", "erm", "sassy" - )] - ) - self.look_at(self.screen) - self.wait(3) - - -class ShorteningLongVectors(IntroduceVectorField): - def construct(self): - self.add_plane() - self.add_title() - self.contrast_adjusted_and_non_adjusted() - - def contrast_adjusted_and_non_adjusted(self): - func = four_swirls_function - unadjusted = VectorField( - func, length_func=lambda n: n, colors=[WHITE], - ) - adjusted = VectorField(func) - for v1, v2 in zip(adjusted, unadjusted): - v1.save_state() - v1.target = v2 - - self.add(adjusted) - self.wait() - self.play(LaggedStartMap( - MoveToTarget, adjusted, - run_time=3 - )) - self.wait() - self.play(LaggedStartMap( - ApplyMethod, adjusted, - lambda m: (m.restore,), - run_time=3 - )) - self.wait() - - -class TimeDependentVectorField(ExternallyAnimatedScene): - pass - - -class ChangingElectricField(Scene): - CONFIG = { - "vector_field_config": {} - } - - def construct(self): - particles = self.get_particles() - vector_field = self.get_vector_field() - - def update_vector_field(vector_field): - new_field = self.get_vector_field() - Transform(vector_field, new_field).update(1) - vector_field.func = new_field.func - - def update_particles(particles, dt): - func = vector_field.func - for particle in particles: - force = func(particle.get_center()) - particle.velocity += force * dt - particle.shift(particle.velocity * dt) - - self.add( - Mobject.add_updater(vector_field, update_vector_field), - Mobject.add_updater(particles, update_particles), - ) - self.wait(20) - - def get_particles(self): - particles = self.particles = VGroup() - for n in range(9): - if n % 2 == 0: - particle = get_proton(radius=0.2) - particle.charge = +1 - else: - particle = get_electron(radius=0.2) - particle.charge = -1 - particle.velocity = np.random.normal(0, 0.1, 3) - particles.add(particle) - particle.shift(np.random.normal(0, 0.2, 3)) - - particles.arrange_in_grid(buff=LARGE_BUFF) - return particles - - def get_vector_field(self): - func = get_force_field_func(*list(zip( - list(map(Mobject.get_center, self.particles)), - [p.charge for p in self.particles] - ))) - self.vector_field = VectorField(func, **self.vector_field_config) - return self.vector_field - - -class InsertAirfoildTODO(TODOStub): - CONFIG = {"message": "Insert airfoil flow animation"} - - -class ThreeDVectorField(ExternallyAnimatedScene): - pass - - -class ThreeDVectorFieldEquation(Scene): - def construct(self): - vector = Matrix([ - "yz", - "xz", - "xy", - ]) - vector.set_height(FRAME_HEIGHT - 1) - self.add(vector) - - -class GravityFluidFlow(IntroduceVectorField): - def construct(self): - self.vector_field = VectorField( - lambda p: np.array(ORIGIN), - **self.vector_field_config - ) - self.show_gravitational_force() - self.show_fluid_flow() - - -class TotallyToScale(Scene): - def construct(self): - words = TextMobject( - "Totally drawn to scale. \\\\ Don't even worry about it." - ) - words.set_width(FRAME_WIDTH - 1) - words.add_background_rectangle() - self.add(words) - self.wait() - - -# TODO: Revisit this -class FluidFlowAsHillGradient(CylinderModel, ThreeDScene): - CONFIG = { - "production_quality_flow": False, - } - - def construct(self): - def potential(point): - x, y = point[:2] - result = 2 - 0.01 * op.mul( - ((x - 4)**2 + y**2), - ((x + 4)**2 + y**2) - ) - return max(-10, result) - - vector_field_func = negative_gradient(potential) - - stream_lines = StreamLines( - vector_field_func, - virtual_time=3, - color_lines_by_magnitude=False, - start_points_generator_config={ - "delta_x": 0.2, - "delta_y": 0.2, - } - ) - for line in stream_lines: - line.points[:, 2] = np.apply_along_axis( - potential, 1, line.points - ) - stream_lines_animation = self.get_stream_lines_animation( - stream_lines - ) - - plane = NumberPlane() - - self.add(plane) - self.add(stream_lines_animation) - self.wait(3) - self.begin_ambient_camera_rotation(rate=0.1) - self.move_camera( - phi=70 * DEGREES, - run_time=2 - ) - self.wait(5) - - -class DefineDivergence(ChangingElectricField): - CONFIG = { - "vector_field_config": { - "length_func": lambda norm: 0.3, - "min_magnitude": 0, - "max_magnitude": 1, - }, - "stream_line_config": { - "start_points_generator_config": { - "delta_x": 0.125, - "delta_y": 0.125, - }, - "virtual_time": 5, - "n_anchors_per_line": 10, - "min_magnitude": 0, - "max_magnitude": 1, - "stroke_width": 2, - }, - "stream_line_animation_config": { - "line_anim_class": ShowPassingFlash, - }, - "flow_time": 10, - "random_seed": 7, - } - - def construct(self): - self.draw_vector_field() - self.show_flow() - self.point_out_sources_and_sinks() - self.show_divergence_values() - - def draw_vector_field(self): - particles = self.get_particles() - random.shuffle(particles.submobjects) - particles.remove(particles[0]) - particles.arrange_in_grid( - n_cols=4, buff=3 - ) - for particle in particles: - particle.shift( - np.random.normal(0, 0.75) * RIGHT, - np.random.normal(0, 0.5) * UP, - ) - particle.shift_onto_screen(buff=2 * LARGE_BUFF) - particle.charge *= 0.125 - vector_field = self.get_vector_field() - - self.play( - LaggedStartMap(GrowArrow, vector_field), - LaggedStartMap(GrowFromCenter, particles), - run_time=4 - ) - self.wait() - self.play(LaggedStartMap(FadeOut, particles)) - - def show_flow(self): - stream_lines = StreamLines( - self.vector_field.func, - **self.stream_line_config - ) - stream_line_animation = AnimatedStreamLines( - stream_lines, - **self.stream_line_animation_config - ) - self.add(stream_line_animation) - self.wait(self.flow_time) - - def point_out_sources_and_sinks(self): - particles = self.particles - self.positive_points, self.negative_points = [ - [ - particle.get_center() - for particle in particles - if u * particle.charge > 0 - ] - for u in (+1, -1) - ] - pair_of_vector_circle_groups = VGroup() - for point_set in self.positive_points, self.negative_points: - vector_circle_groups = VGroup() - for point in point_set: - vector_circle_group = VGroup() - for angle in np.linspace(0, TAU, 12, endpoint=False): - step = 0.5 * rotate_vector(RIGHT, angle) - vect = self.vector_field.get_vector(point + step) - vect.set_color(WHITE) - vect.set_stroke(width=2) - vector_circle_group.add(vect) - vector_circle_groups.add(vector_circle_group) - pair_of_vector_circle_groups.add(vector_circle_groups) - - self.play( - self.vector_field.set_fill, {"opacity": 0.5}, - LaggedStartMap( - LaggedStartMap, vector_circle_groups, - lambda vcg: (GrowArrow, vcg), - ), - ) - self.wait(4) - self.play(FadeOut(vector_circle_groups)) - self.play(self.vector_field.set_fill, {"opacity": 1}) - self.positive_vector_circle_groups = pair_of_vector_circle_groups[0] - self.negative_vector_circle_groups = pair_of_vector_circle_groups[1] - self.wait() - - def show_divergence_values(self): - positive_points = self.positive_points - negative_points = self.negative_points - div_func = divergence(self.vector_field.func) - - circle = Circle(color=WHITE, radius=0.2) - circle.add(Dot(circle.get_center(), radius=0.02)) - circle.move_to(positive_points[0]) - - div_tex = TexMobject( - "\\text{div} \\, \\textbf{F}(x, y) = " - ) - div_tex.add_background_rectangle() - div_tex_update = Mobject.add_updater( - div_tex, lambda m: m.next_to(circle, UP, SMALL_BUFF) - ) - - div_value = DecimalNumber( - 0, - num_decimal_places=1, - include_background_rectangle=True, - include_sign=True, - ) - div_value_update = ContinualChangingDecimal( - div_value, - lambda a: np.round(div_func(circle.get_center()), 1), - position_update_func=lambda m: m.next_to(div_tex, RIGHT, SMALL_BUFF), - include_sign=True, - ) - - self.play( - ShowCreation(circle), - FadeIn(div_tex), - FadeIn(div_value), - ) - self.add(div_tex_update) - self.add(div_value_update) - - self.wait() - for point in positive_points[1:-1]: - self.play(circle.move_to, point) - self.wait(1.5) - for point in negative_points: - self.play(circle.move_to, point) - self.wait(2) - self.wait(4) - # self.remove(div_tex_update) - # self.remove(div_value_update) - # self.play( - # ApplyMethod(circle.scale, 0, remover=True), - # FadeOut(div_tex), - # FadeOut(div_value), - # ) - - -class DefineDivergenceJustFlow(DefineDivergence): - CONFIG = { - "flow_time": 10, - } - - def construct(self): - self.force_skipping() - self.draw_vector_field() - self.revert_to_original_skipping_status() - self.clear() - self.show_flow() - - -class DefineDivergenceSymbols(Scene): - def construct(self): - tex_mob = TexMobject( - "\\text{div}", - "\\textbf{F}", - "(x, y)", - "=", - ) - div, F, xy, eq = tex_mob - output = DecimalNumber(0, include_sign=True) - output.next_to(tex_mob, RIGHT) - time_tracker = ValueTracker() - always_shift(time_tracker, rate=1) - self.add(time_tracker) - output_animation = ContinualChangingDecimal( - output, lambda a: 3 * np.cos(int(time_tracker.get_value())), - ) - - F.set_color(BLUE) - xy.set_color(YELLOW) - - F_brace = Brace(F, UP, buff=SMALL_BUFF) - F_label = F_brace.get_text( - "Vector field function", - ) - F_label.match_color(F) - xy_brace = Brace(xy, DOWN, buff=SMALL_BUFF) - xy_label = xy_brace.get_text("Some point") - xy_label.match_color(xy) - output_brace = Brace(output, UP, buff=SMALL_BUFF) - output_label = output_brace.get_text( - "Measure of how much \\\\ " - "$(x, y)$ ``generates'' fluid" - ) - brace_label_pairs = [ - (F_brace, F_label), - (xy_brace, xy_label), - (output_brace, output_label), - ] - - self.add(tex_mob, output_animation) - fade_anims = [] - for brace, label in brace_label_pairs: - self.play( - GrowFromCenter(brace), - FadeInFromDown(label), - *fade_anims - ) - self.wait(2) - fade_anims = list(map(FadeOut, [brace, label])) - self.wait() - - -class DivergenceAtSlowFastPoint(Scene): - CONFIG = { - "vector_field_config": { - "length_func": lambda norm: 0.1 + 0.4 * norm / 4.0, - "min_magnitude": 0, - "max_magnitude": 3, - }, - "stream_lines_config": { - "start_points_generator_config": { - "delta_x": 0.125, - "delta_y": 0.125, - }, - "virtual_time": 1, - "min_magnitude": 0, - "max_magnitude": 3, - }, - } - - def construct(self): - def func(point): - return 3 * sigmoid(point[0]) * RIGHT - vector_field = self.vector_field = VectorField( - func, **self.vector_field_config - ) - - circle = Circle(color=WHITE) - slow_words = TextMobject("Slow flow in") - fast_words = TextMobject("Fast flow out") - words = VGroup(slow_words, fast_words) - for word, vect in zip(words, [LEFT, RIGHT]): - word.add_background_rectangle() - word.next_to(circle, vect) - - div_tex = TexMobject( - "\\text{div}\\,\\textbf{F}(x, y) > 0" - ) - div_tex.add_background_rectangle() - div_tex.next_to(circle, UP) - - self.add(vector_field) - self.add_foreground_mobjects(circle, div_tex) - self.begin_flow() - self.wait(2) - for word in words: - self.add_foreground_mobjects(word) - self.play(Write(word)) - self.wait(8) - - def begin_flow(self): - stream_lines = StreamLines( - self.vector_field.func, - **self.stream_lines_config - ) - stream_line_animation = AnimatedStreamLines(stream_lines) - stream_line_animation.update(3) - self.add(stream_line_animation) - - -class DivergenceAsNewFunction(Scene): - def construct(self): - self.add_plane() - self.show_vector_field_function() - self.show_divergence_function() - - def add_plane(self): - plane = self.plane = NumberPlane() - plane.add_coordinates() - self.add(plane) - - def show_vector_field_function(self): - func = self.func - unscaled_vector_field = VectorField( - func, - length_func=lambda norm: norm, - colors=[BLUE_C, YELLOW, RED], - delta_x=np.inf, - delta_y=np.inf, - ) - - in_dot = Dot(color=PINK) - in_dot.move_to(3.75 * LEFT + 1.25 * UP) - - def get_input(): - return in_dot.get_center() - - def get_out_vect(): - return unscaled_vector_field.get_vector(get_input()) - - # Tex - func_tex = TexMobject( - "\\textbf{F}(", "+0.00", ",", "+0.00", ")", "=", - ) - dummy_in_x, dummy_in_y = func_tex.get_parts_by_tex("+0.00") - func_tex.add_background_rectangle() - rhs = DecimalMatrix( - [[0], [0]], - element_to_mobject_config={ - "num_decimal_places": 2, - "include_sign": True, - }, - include_background_rectangle=True - ) - rhs.next_to(func_tex, RIGHT) - dummy_out_x, dummy_out_y = rhs.get_mob_matrix().flatten() - - VGroup(func_tex, rhs).to_corner(UL, buff=MED_SMALL_BUFF) - - VGroup( - dummy_in_x, dummy_in_y, - dummy_out_x, dummy_out_y, - ).set_fill(BLACK, opacity=0) - - # Changing decimals - in_x, in_y, out_x, out_y = [ - DecimalNumber(0, include_sign=True) - for x in range(4) - ] - VGroup(in_x, in_y).set_color(in_dot.get_color()) - VGroup(out_x, out_y).set_color(get_out_vect().get_fill_color()) - in_x_update = ContinualChangingDecimal( - in_x, lambda a: get_input()[0], - position_update_func=lambda m: m.move_to(dummy_in_x) - ) - in_y_update = ContinualChangingDecimal( - in_y, lambda a: get_input()[1], - position_update_func=lambda m: m.move_to(dummy_in_y) - ) - out_x_update = ContinualChangingDecimal( - out_x, lambda a: func(get_input())[0], - position_update_func=lambda m: m.move_to(dummy_out_x) - ) - out_y_update = ContinualChangingDecimal( - out_y, lambda a: func(get_input())[1], - position_update_func=lambda m: m.move_to(dummy_out_y) - ) - - self.add(func_tex, rhs) - # self.add(Mobject.add_updater( - # rhs, lambda m: m.next_to(func_tex, RIGHT) - # )) - - # Where those decimals actually change - self.add(in_x_update, in_y_update) - - in_dot.save_state() - in_dot.move_to(ORIGIN) - self.play(in_dot.restore) - self.wait() - self.play(*[ - ReplacementTransform( - VGroup(mob.copy().fade(1)), - VGroup(out_x, out_y), - ) - for mob in (in_x, in_y) - ]) - out_vect = get_out_vect() - VGroup(out_x, out_y).match_style(out_vect) - out_vect.save_state() - out_vect.move_to(rhs) - out_vect.set_fill(opacity=0) - self.play(out_vect.restore) - self.out_vect_update = Mobject.add_updater( - out_vect, - lambda ov: Transform(ov, get_out_vect()).update(1) - ) - - self.add(self.out_vect_update) - self.add(out_x_update, out_y_update) - - self.add(Mobject.add_updater( - VGroup(out_x, out_y), - lambda m: m.match_style(out_vect) - )) - self.wait() - - for vect in DOWN, 2 * RIGHT, UP: - self.play( - in_dot.shift, 3 * vect, - run_time=3 - ) - self.wait() - - self.in_dot = in_dot - self.out_vect = out_vect - self.func_equation = VGroup(func_tex, rhs) - self.out_x, self.out_y = out_x, out_y - self.in_x, self.in_y = out_x, out_y - self.in_x_update = in_x_update - self.in_y_update = in_y_update - self.out_x_update = out_x_update - self.out_y_update = out_y_update - - def show_divergence_function(self): - vector_field = VectorField(self.func) - vector_field.remove(*[ - v for v in vector_field - if v.get_start()[0] < 0 and v.get_start()[1] > 2 - ]) - vector_field.set_fill(opacity=0.5) - in_dot = self.in_dot - - def get_neighboring_points(step_sizes=[0.3], n_angles=12): - point = in_dot.get_center() - return list(it.chain(*[ - [ - point + step_size * step - for step in compass_directions(n_angles) - ] - for step_size in step_sizes - ])) - - def get_vector_ring(): - return VGroup(*[ - vector_field.get_vector(point) - for point in get_neighboring_points() - ]) - - def get_stream_lines(): - return StreamLines( - self.func, - start_points_generator=get_neighboring_points, - start_points_generator_config={ - "step_sizes": np.arange(0.1, 0.5, 0.1) - }, - virtual_time=1, - stroke_width=3, - ) - - def show_flow(): - stream_lines = get_stream_lines() - random.shuffle(stream_lines.submobjects) - self.play(LaggedStartMap( - ShowCreationThenDestruction, - stream_lines, - remover=True - )) - - vector_ring = get_vector_ring() - vector_ring_update = Mobject.add_updater( - vector_ring, - lambda vr: Transform(vr, get_vector_ring()).update(1) - ) - - func_tex, rhs = self.func_equation - out_x, out_y = self.out_x, self.out_y - out_x_update = self.out_x_update - out_y_update = self.out_y_update - div_tex = TexMobject("\\text{div}") - div_tex.add_background_rectangle() - div_tex.move_to(func_tex, LEFT) - div_tex.shift(2 * SMALL_BUFF * RIGHT) - - self.remove(out_x_update, out_y_update) - self.remove(self.out_vect_update) - self.add(self.in_x_update, self.in_y_update) - self.play( - func_tex.next_to, div_tex, RIGHT, SMALL_BUFF, - {"submobject_to_align": func_tex[1][0]}, - Write(div_tex), - FadeOut(self.out_vect), - FadeOut(out_x), - FadeOut(out_y), - FadeOut(rhs), - ) - # This line is a dumb hack around a Scene bug - self.add(*[ - Mobject.add_updater( - mob, lambda m: m.set_fill(None, 0) - ) - for mob in (out_x, out_y) - ]) - self.add_foreground_mobjects(div_tex) - self.play( - LaggedStartMap(GrowArrow, vector_field), - LaggedStartMap(GrowArrow, vector_ring), - ) - self.add(vector_ring_update) - self.wait() - - div_func = divergence(self.func) - div_rhs = DecimalNumber( - 0, include_sign=True, - include_background_rectangle=True - ) - div_rhs_update = ContinualChangingDecimal( - div_rhs, lambda a: div_func(in_dot.get_center()), - position_update_func=lambda d: d.next_to(func_tex, RIGHT, SMALL_BUFF) - ) - - self.play(FadeIn(div_rhs)) - self.add(div_rhs_update) - show_flow() - - for vect in 2 * RIGHT, 3 * DOWN, 2 * LEFT, 2 * LEFT: - self.play(in_dot.shift, vect, run_time=3) - show_flow() - self.wait() - - def func(self, point): - x, y = point[:2] - return np.sin(x + y) * RIGHT + np.sin(y * x / 3) * UP - - -class DivergenceZeroCondition(Scene): - def construct(self): - title = TextMobject( - "For actual (incompressible) fluid flow:" - ) - title.to_edge(UP) - equation = TexMobject( - "\\text{div} \\, \\textbf{F} = 0 \\quad \\text{everywhere}" - ) - equation.next_to(title, DOWN) - - for mob in title, equation: - mob.add_background_rectangle(buff=MED_SMALL_BUFF / 2) - self.add_foreground_mobjects(mob) - self.wait(1) - - -class PureCylinderFlow(Scene): - def construct(self): - self.add_vector_field() - self.begin_flow() - self.add_circle() - self.wait(5) - - def add_vector_field(self): - vector_field = VectorField( - cylinder_flow_vector_field, - ) - for vector in vector_field: - if get_norm(vector.get_start()) < 1: - vector_field.remove(vector) - vector_field.set_fill(opacity=0.75) - self.modify_vector_field(vector_field) - self.add_foreground_mobjects(vector_field) - - def begin_flow(self): - stream_lines = StreamLines( - cylinder_flow_vector_field, - colors=[BLUE_E, BLUE_D, BLUE_C], - start_points_generator_config={ - "delta_x": 0.125, - "delta_y": 0.125, - }, - virtual_time=5, - ) - self.add(stream_lines) - for stream_line in stream_lines: - if get_norm(stream_line.points[0]) < 1: - stream_lines.remove(stream_line) - - self.modify_flow(stream_lines) - - stream_line_animation = AnimatedStreamLines(stream_lines) - stream_line_animation.update(3) - - self.add(stream_line_animation) - - def add_circle(self): - circle = Circle( - radius=1, - stroke_color=YELLOW, - fill_color=BLACK, - fill_opacity=1, - ) - self.modify_flow(circle) - self.add_foreground_mobjects(circle) - - def modify_flow(self, mobject): - pass - - def modify_vector_field(self, vector_field): - pass - - -class PureAirfoilFlow(PureCylinderFlow): - def modify_flow(self, mobject): - vect = 0.1 * LEFT + 0.2 * UP - mobject.scale(get_norm(vect - RIGHT)) - mobject.shift(vect) - mobject.apply_complex_function(joukowsky_map) - return mobject - - def modify_vector_field(self, vector_field): - def func(z): - w = complex(-0.1, 0.2) - n = abs(w - 1) - return joukowsky_map(inverse_joukowsky_map(z) - w / n) - - def new_vector_field_func(point): - z = R3_to_complex(point) - return complex_to_R3(derivative(func)(z).conjugate()) - - vf = VectorField(new_vector_field_func, delta_y=0.33) - Transform(vector_field, vf).update(1) - vf.set_fill(opacity=0.5) - - -class IntroduceCurl(IntroduceVectorField): - CONFIG = { - "stream_line_animation_config": { - "line_anim_class": ShowPassingFlash, - }, - "stream_line_config": { - "start_points_generator_config": { - "delta_x": 0.125, - "delta_y": 0.125, - }, - "virtual_time": 1, - } - } - - def construct(self): - self.add_title() - self.show_vector_field() - self.begin_flow() - self.show_rotation() - - def add_title(self): - title = self.title = Title( - "Curl", - match_underline_width_to_text=True, - scale_factor=1.5, - ) - title.add_background_rectangle() - title.to_edge(UP, buff=MED_SMALL_BUFF) - self.add_foreground_mobjects(title) - - def show_vector_field(self): - vector_field = self.vector_field = VectorField( - four_swirls_function, - **self.vector_field_config - ) - vector_field.submobjects.sort( - key=lambda v: v.get_length() - ) - - self.play(LaggedStartMap(GrowArrow, vector_field)) - self.wait() - - def begin_flow(self): - stream_lines = StreamLines( - self.vector_field.func, - **self.stream_line_config - ) - stream_line_animation = AnimatedStreamLines( - stream_lines, - **self.stream_line_animation_config - ) - - self.add(stream_line_animation) - self.wait(3) - - def show_rotation(self): - clockwise_arrows, counterclockwise_arrows = [ - VGroup(*[ - self.get_rotation_arrows(clockwise=cw).move_to(point) - for point in points - ]) - for cw, points in [ - (True, [2 * UP, 2 * DOWN]), - (False, [4 * LEFT, 4 * RIGHT]), - ] - ] - - for group, u in (counterclockwise_arrows, +1), (clockwise_arrows, -1): - for arrows in group: - label = TexMobject( - "\\text{curl} \\, \\textbf{F}", - ">" if u > 0 else "<", - "0" - ) - label.add_background_rectangle() - label.next_to(arrows, DOWN) - self.add_foreground_mobjects(label) - always_rotate(arrows, rate=u * 30 * DEGREES) - self.play( - FadeIn(arrows), - FadeIn(label) - ) - self.wait(2) - for group in counterclockwise_arrows, clockwise_arrows: - self.play(FocusOn(group[0])) - self.play( - UpdateFromAlphaFunc( - group, - lambda mob, alpha: mob.set_color( - interpolate_color(WHITE, PINK, alpha) - ).set_stroke( - width=interpolate(5, 10, alpha) - ), - rate_func=there_and_back, - run_time=2 - ) - ) - self.wait() - self.wait(6) - - # Helpers - def get_rotation_arrows(self, clockwise=True, width=1): - result = VGroup(*[ - Arrow( - *points, - buff=2 * SMALL_BUFF, - path_arc=90 * DEGREES - ).set_stroke(width=5) - for points in adjacent_pairs(compass_directions(4, RIGHT)) - ]) - if clockwise: - result.flip() - result.set_width(width) - return result - - -class ShearCurl(IntroduceCurl): - def construct(self): - self.show_vector_field() - self.begin_flow() - self.wait(2) - self.comment_on_relevant_region() - - def show_vector_field(self): - vector_field = self.vector_field = VectorField( - self.func, **self.vector_field_config - ) - vector_field.submobjects.key=sort( - key=lambda a: a.get_length() - ) - self.play(LaggedStartMap(GrowArrow, vector_field)) - - def comment_on_relevant_region(self): - circle = Circle(color=WHITE, radius=0.75) - circle.next_to(ORIGIN, UP, LARGE_BUFF) - self.play(ShowCreation(circle)) - - slow_words, fast_words = words = [ - TextMobject("Slow flow below"), - TextMobject("Fast flow above") - ] - for word, vect in zip(words, [DOWN, UP]): - word.add_background_rectangle(buff=SMALL_BUFF) - word.next_to(circle, vect) - self.add_foreground_mobjects(word) - self.play(Write(word)) - self.wait() - - twig = Rectangle( - height=0.8 * 2 * circle.radius, - width=SMALL_BUFF, - stroke_width=0, - fill_color=GREY_BROWN, - fill_opacity=1, - ) - twig.add(Dot(twig.get_center())) - twig.move_to(circle) - always_rotate( - twig, rate=-90 * DEGREES, - ) - - self.play(FadeInFrom(twig, UP)) - self.add(twig_rotation) - self.wait(16) - - # Helpers - def func(self, point): - return 0.5 * point[1] * RIGHT - - -class FromKAWrapper(TeacherStudentsScene): - def construct(self): - screen = self.screen - self.play( - self.teacher.change, "raise_right_hand", - self.get_student_changes( - "pondering", "confused", "hooray", - ) - ) - self.look_at(screen) - self.wait(2) - self.change_student_modes("erm", "happy", "confused") - self.wait(3) - self.teacher_says( - "Our focus is \\\\ the 2d version", - bubble_kwargs={"width": 4, "height": 3}, - added_anims=[self.get_student_changes( - "happy", "hooray", "happy" - )] - ) - self.wait() - - -class ShowCurlAtVariousPoints(IntroduceCurl): - CONFIG = { - "func": four_swirls_function, - "sample_points": [ - 4 * RIGHT, - 2 * UP, - 4 * LEFT, - 2 * DOWN, - ORIGIN, - 3 * RIGHT + 2 * UP, - 3 * LEFT + 2 * UP, - ], - "vector_field_config": { - "fill_opacity": 0.75 - }, - "stream_line_config": { - "virtual_time": 5, - "start_points_generator_config": { - "delta_x": 0.25, - "delta_y": 0.25, - } - } - } - - def construct(self): - self.add_plane() - self.show_vector_field() - self.begin_flow() - self.show_curl_at_points() - - def add_plane(self): - plane = NumberPlane() - plane.add_coordinates() - self.add(plane) - self.plane = plane - - def show_curl_at_points(self): - dot = Dot() - circle = Circle(radius=0.25, color=WHITE) - circle.move_to(dot) - circle_update = Mobject.add_updater( - circle, - lambda m: m.move_to(dot) - ) - - curl_tex = TexMobject( - "\\text{curl} \\, \\textbf{F}(x, y) = " - ) - curl_tex.add_background_rectangle(buff=0.025) - curl_tex_update = Mobject.add_updater( - curl_tex, - lambda m: m.next_to(circle, UP, SMALL_BUFF) - ) - - curl_func = two_d_curl(self.func) - curl_value = DecimalNumber( - 0, include_sign=True, - include_background_rectangle=True, - ) - curl_value_update = ContinualChangingDecimal( - curl_value, - lambda a: curl_func(dot.get_center()), - position_update_func=lambda m: m.next_to( - curl_tex, RIGHT, buff=0 - ), - include_background_rectangle=True, - include_sign=True, - ) - - points = self.sample_points - self.add(dot, circle_update) - self.play( - dot.move_to, points[0], - VFadeIn(dot), - VFadeIn(circle), - ) - curl_tex_update.update(0) - curl_value_update.update(0) - self.play(Write(curl_tex), FadeIn(curl_value)) - self.add(curl_tex_update, curl_value_update) - self.wait() - for point in points[1:]: - self.play(dot.move_to, point, run_time=3) - self.wait(2) - self.wait(2) - - -class IllustrationUseVennDiagram(Scene): - def construct(self): - title = Title("Divergence \\& Curl") - title.to_edge(UP, buff=MED_SMALL_BUFF) - - useful_for = TextMobject("Useful for") - useful_for.next_to(title, DOWN) - useful_for.set_color(BLUE) - - fluid_flow = TextMobject("Fluid \\\\ flow") - fluid_flow.next_to(ORIGIN, UL) - ff_circle = Circle(color=YELLOW) - ff_circle.surround(fluid_flow, stretch=True) - fluid_flow.match_color(ff_circle) - - big_circle = Circle( - fill_color=BLUE, - fill_opacity=0.2, - stroke_color=BLUE, - ) - big_circle.stretch_to_fit_width(9) - big_circle.stretch_to_fit_height(6) - big_circle.next_to(useful_for, DOWN, SMALL_BUFF) - - illustrated_by = TextMobject("Illustrated by") - illustrated_by.next_to( - big_circle.point_from_proportion(3. / 8), UL - ) - illustrated_by.match_color(ff_circle) - illustrated_by_arrow = Arrow( - illustrated_by.get_bottom(), - ff_circle.get_left(), - path_arc=90 * DEGREES, - color=YELLOW, - ) - illustrated_by_arrow.pointwise_become_partial( - illustrated_by_arrow, 0, 0.95 - ) - - examples = VGroup( - TextMobject("Electricity"), - TextMobject("Magnetism"), - TextMobject("Phase flow"), - TextMobject("Stokes' theorem"), - ) - points = [ - 2 * RIGHT + 0.5 * UP, - 2 * RIGHT + 0.5 * DOWN, - 2 * DOWN, - 2 * LEFT + DOWN, - ] - for example, point in zip(examples, points): - example.move_to(point) - - self.play(Write(title), run_time=1) - self.play( - Write(illustrated_by), - ShowCreation(illustrated_by_arrow), - run_time=1, - ) - self.play( - ShowCreation(ff_circle), - FadeIn(fluid_flow), - ) - self.wait() - self.play( - Write(useful_for), - DrawBorderThenFill(big_circle), - Animation(fluid_flow), - Animation(ff_circle), - ) - self.play(LaggedStartMap( - FadeIn, examples, - run_time=3, - )) - self.wait() - - -class MaxwellsEquations(Scene): - CONFIG = { - "faded_opacity": 0.3, - } - - def construct(self): - self.add_equations() - self.circle_gauss_law() - self.circle_magnetic_divergence() - self.circle_curl_equations() - - def add_equations(self): - title = Title("Maxwell's equations") - title.to_edge(UP, buff=MED_SMALL_BUFF) - - tex_to_color_map = { - "\\textbf{E}": BLUE, - "\\textbf{B}": YELLOW, - "\\rho": WHITE, - } - - equations = self.equations = VGroup(*[ - TexMobject( - tex, tex_to_color_map=tex_to_color_map - ) - for tex in [ - """ - \\text{div} \\, \\textbf{E} = - {\\rho \\over \\varepsilon_0} - """, - """\\text{div} \\, \\textbf{B} = 0""", - """ - \\text{curl} \\, \\textbf{E} = - -{\\partial \\textbf{B} \\over \\partial t} - """, - """ - \\text{curl} \\, \\textbf{B} = - \\mu_0 \\left( - \\textbf{J} + \\varepsilon_0 - {\\partial \\textbf{E} \\over \\partial t} - \\right) - """, - ] - ]) - equations.arrange( - DOWN, aligned_edge=LEFT, - buff=MED_LARGE_BUFF - ) - - field_definitions = VGroup(*[ - TexMobject(text, tex_to_color_map=tex_to_color_map) - for text in [ - "\\text{Electric field: } \\textbf{E}", - "\\text{Magnetic field: } \\textbf{B}", - ] - ]) - field_definitions.arrange( - RIGHT, buff=MED_LARGE_BUFF - ) - field_definitions.next_to(title, DOWN, MED_LARGE_BUFF) - equations.next_to(field_definitions, DOWN, MED_LARGE_BUFF) - field_definitions.shift(MED_SMALL_BUFF * UP) - - self.add(title) - self.add(field_definitions) - self.play(LaggedStartMap( - FadeIn, equations, - run_time=3, - lag_range=0.4 - )) - self.wait() - - def circle_gauss_law(self): - equation = self.equations[0] - rect = SurroundingRectangle(equation) - rect.set_color(RED) - rho = equation.get_part_by_tex("\\rho") - sub_rect = SurroundingRectangle(rho) - sub_rect.match_color(rect) - rho_label = TextMobject("Charge density") - rho_label.next_to(sub_rect, RIGHT) - rho_label.match_color(sub_rect) - gauss_law = TextMobject("Gauss's law") - gauss_law.next_to(rect, RIGHT) - - self.play( - ShowCreation(rect), - Write(gauss_law, run_time=1), - self.equations[1:].set_fill, {"opacity": self.faded_opacity} - ) - self.wait(2) - self.play( - ReplacementTransform(rect, sub_rect), - FadeOut(gauss_law), - FadeIn(rho_label), - rho.match_color, sub_rect, - ) - self.wait() - self.play( - self.equations.to_edge, LEFT, - MaintainPositionRelativeTo(rho_label, equation), - MaintainPositionRelativeTo(sub_rect, equation), - VFadeOut(rho_label), - VFadeOut(sub_rect), - ) - self.wait() - - def circle_magnetic_divergence(self): - equations = self.equations - rect = SurroundingRectangle(equations[1]) - - self.play( - equations[0].set_fill, {"opacity": self.faded_opacity}, - equations[1].set_fill, {"opacity": 1.0}, - ) - self.play(ShowCreation(rect)) - self.wait(3) - self.play(FadeOut(rect)) - - def circle_curl_equations(self): - equations = self.equations - rect = SurroundingRectangle(equations[2:]) - randy = Randolph(height=2) - randy.flip() - randy.next_to(rect, RIGHT, aligned_edge=DOWN) - randy.look_at(rect) - - self.play( - equations[1].set_fill, {"opacity": self.faded_opacity}, - equations[2:].set_fill, {"opacity": 1.0}, - ) - self.play(ShowCreation(rect)) - self.play( - randy.change, "confused", - VFadeIn(randy), - ) - self.play(Blink(randy)) - self.play(randy.look_at, 2 * RIGHT) - self.wait(3) - self.play( - FadeOut(rect), - randy.change, "pondering", - randy.look_at, rect, - ) - self.wait() - self.play(Blink(randy)) - self.wait() - - -class ThatWeKnowOf(Scene): - def construct(self): - words = TextMobject("*That we know of!") - self.add(words) - - -class IllustrateGaussLaw(DefineDivergence, MovingCameraScene): - CONFIG = { - "flow_time": 10, - "stream_line_config": { - "start_points_generator_config": { - "delta_x": 1.0 / 16, - "delta_y": 1.0 / 16, - "x_min": -2, - "x_max": 2, - "y_min": -1.5, - "y_max": 1.5, - }, - "color_lines_by_magnitude": True, - "colors": [BLUE_E, BLUE_D, BLUE_C], - "stroke_width": 3, - }, - "stream_line_animation_config": { - "line_anim_class": ShowPassingFlashWithThinningStrokeWidth, - "line_anim_config": { - "n_segments": 5, - } - }, - "final_frame_width": 4, - } - - def construct(self): - particles = self.get_particles() - vector_field = self.get_vector_field() - - self.add_foreground_mobjects(vector_field) - self.add_foreground_mobjects(particles) - self.zoom_in() - self.show_flow() - - def get_particles(self): - particles = VGroup( - get_proton(radius=0.1), - get_electron(radius=0.1), - ) - particles.arrange(RIGHT, buff=2.25) - particles.shift(0.25 * UP) - for particle, sign in zip(particles, [+1, -1]): - particle.charge = sign - - self.particles = particles - return particles - - def zoom_in(self): - self.play( - self.camera_frame.set_width, self.final_frame_width, - run_time=2 - ) - - -class IllustrateGaussMagnetic(IllustrateGaussLaw): - CONFIG = { - "final_frame_width": 7, - "stream_line_config": { - "start_points_generator_config": { - "delta_x": 1.0 / 16, - "delta_y": 1.0 / 16, - "x_min": -3.5, - "x_max": 3.5, - "y_min": -2, - "y_max": 2, - }, - "color_lines_by_magnitude": True, - "colors": [BLUE_E, BLUE_D, BLUE_C], - "stroke_width": 3, - }, - "stream_line_animation_config": { - "start_up_time": 0, - }, - "flow_time": 10, - } - - def construct(self): - self.add_wires() - self.show_vector_field() - self.zoom_in() - self.show_flow() - - def add_wires(self): - top, bottom = [ - Circle( - radius=0.275, - stroke_color=WHITE, - fill_color=BLACK, - fill_opacity=1 - ) - for x in range(2) - ] - top.add(TexMobject("\\times").scale(0.5)) - bottom.add(Dot().scale(0.5)) - top.move_to(1 * UP) - bottom.move_to(1 * DOWN) - - self.add_foreground_mobjects(top, bottom) - - def show_vector_field(self): - vector_field = self.vector_field = VectorField( - self.func, **self.vector_field_config - ) - vector_field.submobjects.sort( - key=lambda a: -a1.get_length() - ) - self.play(LaggedStartMap(GrowArrow, vector_field)) - self.add_foreground_mobjects( - vector_field, *self.foreground_mobjects - ) - - def func(self, point): - x, y = point[:2] - top_part = np.array([(y - 1.0), -x, 0]) - bottom_part = np.array([-(y + 1.0), x, 0]) - norm = get_norm - return 1 * op.add( - top_part / (norm(top_part) * norm(point - UP) + 0.1), - bottom_part / (norm(bottom_part) * norm(point - DOWN) + 0.1), - # top_part / (norm(top_part)**2 + 1), - # bottom_part / (norm(bottom_part)**2 + 1), - ) - - -class IllustrateEMCurlEquations(ExternallyAnimatedScene): - pass - - -class RelevantInNonSpatialCircumstances(TeacherStudentsScene): - def construct(self): - self.teacher_says( - """ - $\\textbf{div}$ and $\\textbf{curl}$ are \\\\ - even useful in some \\\\ - non-spatial problems - """, - target_mode="hooray" - ) - self.change_student_modes( - "sassy", "confused", "hesitant" - ) - self.wait(3) - - -class ShowTwoPopulations(Scene): - CONFIG = { - "total_num_animals": 80, - "start_num_foxes": 40, - "start_num_rabbits": 20, - "animal_height": 0.5, - "final_wait_time": 30, - "count_word_scale_val": 1, - } - - def construct(self): - self.introduce_animals() - self.evolve_system() - - def introduce_animals(self): - foxes = self.foxes = VGroup(*[ - self.get_fox() - for n in range(self.total_num_animals) - ]) - rabbits = self.rabbits = VGroup(*[ - self.get_rabbit() - for n in range(self.total_num_animals) - ]) - foxes[self.start_num_foxes:].set_fill(opacity=0) - rabbits[self.start_num_rabbits:].set_fill(opacity=0) - - fox, rabbit = examples = VGroup(foxes[0], rabbits[0]) - for mob in examples: - mob.save_state() - mob.set_height(3) - examples.arrange(LEFT, buff=2) - - preditor, prey = words = VGroup( - TextMobject("Predator"), - TextMobject("Prey") - ) - for mob, word in zip(examples, words): - word.scale(1.5) - word.next_to(mob, UP) - self.play( - FadeInFromDown(mob), - Write(word, run_time=1), - ) - self.play( - LaggedStartMap( - ApplyMethod, examples, - lambda m: (m.restore,) - ), - LaggedStartMap(FadeOut, words), - *[ - LaggedStartMap( - FadeIn, - group[1:], - run_time=4, - lag_ratio=0.1, - rate_func=lambda t: np.clip(smooth(2 * t), 0, 1) - ) - for group in [foxes, rabbits] - ] - ) - - def evolve_system(self): - foxes = self.foxes - rabbits = self.rabbits - phase_point = VectorizedPoint( - self.start_num_rabbits * RIGHT + - self.start_num_foxes * UP - ) - self.add(move_along_vector_field( - phase_point, - preditor_prey_vector_field, - )) - - def get_num_rabbits(): - return phase_point.get_center()[0] - - def get_num_foxes(): - return phase_point.get_center()[1] - - def get_updater(pop_size_getter): - def update(animals): - target_num = pop_size_getter() - for n, animal in enumerate(animals): - animal.set_fill( - opacity=np.clip(target_num - n, 0, 1) - ) - target_int = int(np.ceil(target_num)) - tail = animals.submobjects[target_int:] - random.shuffle(tail) - animals.submobjects[target_int:] = tail - - return update - - self.add(Mobject.add_updater( - foxes, get_updater(get_num_foxes) - )) - self.add(Mobject.add_updater( - rabbits, get_updater(get_num_rabbits) - )) - - # Add counts for foxes and rabbits - labels = self.get_pop_labels() - num_foxes = Integer(10) - num_foxes.scale(self.count_word_scale_val) - num_foxes.next_to(labels[0], RIGHT) - num_foxes.align_to(labels[0][0][1], DOWN) - num_rabbits = Integer(10) - num_rabbits.scale(self.count_word_scale_val) - num_rabbits.next_to(labels[1], RIGHT) - num_rabbits.align_to(labels[1][0][1], DOWN) - - num_foxes.add_updater(lambda d: d.set_value(get_num_foxes())) - num_rabbits.add_updater(lambda d: d.set_value(get_num_rabbits())) - - self.add(num_foxes, num_rabbits) - - for count in num_foxes, num_rabbits: - self.add(Mobject.add_updater( - count, self.update_count_color, - )) - - self.play( - FadeIn(labels), - *[ - UpdateFromAlphaFunc(count, lambda m, a: m.set_fill(opacity=a)) - for count in (num_foxes, num_rabbits) - ] - ) - - self.wait(self.final_wait_time) - - # Helpers - - def get_animal(self, name, color): - result = SVGMobject( - file_name=name, - height=self.animal_height, - fill_color=color, - ) - # for submob in result.family_members_with_points(): - # if submob.is_subpath: - # submob.is_subpath = False - # submob.set_fill( - # interpolate_color(color, BLACK, 0.8), - # opacity=1 - # ) - x_shift, y_shift = [ - (2 * random.random() - 1) * max_val - for max_val in [ - FRAME_WIDTH / 2 - 2, - FRAME_HEIGHT / 2 - 2 - ] - ] - result.shift(x_shift * RIGHT + y_shift * UP) - return result - - def get_fox(self): - return self.get_animal("fox", FOX_COLOR) - - def get_rabbit(self): - # return self.get_animal("rabbit", WHITE) - return self.get_animal("bunny", RABBIT_COLOR) - - def get_pop_labels(self): - labels = VGroup( - TextMobject("\\# Foxes: "), - TextMobject("\\# Rabbits: "), - ) - for label in labels: - label.scale(self.count_word_scale_val) - labels.arrange(RIGHT, buff=2) - labels.to_edge(UP) - return labels - - def update_count_color(self, count): - count.set_fill(interpolate_color( - BLUE, RED, (count.number - 20) / 30.0 - )) - return count - - -class PhaseSpaceOfPopulationModel(ShowTwoPopulations, PiCreatureScene, MovingCameraScene): - CONFIG = { - "origin": 5 * LEFT + 2.5 * DOWN, - "vector_field_config": { - "max_magnitude": 50, - }, - "pi_creatures_start_on_screen": False, - "default_pi_creature_kwargs": { - "height": 1.8 - }, - "flow_time": 10, - } - - def setup(self): - MovingCameraScene.setup(self) - PiCreatureScene.setup(self) - - def construct(self): - self.add_axes() - self.add_example_point() - self.write_differential_equations() - self.add_vectors() - self.show_phase_flow() - - def add_axes(self): - axes = self.axes = Axes( - x_min=0, - x_max=55, - x_axis_config={"unit_size": 0.15}, - y_min=0, - y_max=55, - y_axis_config={"unit_size": 0.09}, - axis_config={ - "tick_frequency": 10, - }, - ) - axes.shift(self.origin) - for axis in axes.x_axis, axes.y_axis: - axis.add_numbers(*list(range(10, 60, 10))) - - axes_labels = self.axes_labels = VGroup(*[ - VGroup( - method().set_height(0.75), - TextMobject("Population"), - ).arrange(RIGHT, buff=MED_SMALL_BUFF) - for method in (self.get_rabbit, self.get_fox) - ]) - for axis, label, vect in zip(axes, axes_labels, [RIGHT, UP]): - label.next_to( - axis, vect, - submobject_to_align=label[0] - ) - - self.add(axes, axes_labels) - - def add_example_point(self): - axes = self.axes - origin = self.origin - x = self.start_num_rabbits - y = self.start_num_foxes - point = axes.coords_to_point(x, y) - x_point = axes.coords_to_point(x, 0) - y_point = axes.coords_to_point(0, y) - v_line = DashedLine(x_point, point) - h_line = DashedLine(y_point, point) - v_line.set_color(FOX_COLOR) - h_line.set_color(LIGHT_GREY) - dot = Dot(point) - - coord_pair = TexMobject( - "(10, 10)", substrings_to_isolate=["10"] - ) - pop_sizes = VGroup(Integer(10), Integer(10)) - pop_sizes[0].set_color(LIGHT_GREY) - pop_sizes[1].set_color(FOX_COLOR) - tens = coord_pair.get_parts_by_tex("10") - tens.fade(1) - - def get_pop_size_update(i): - return ContinualChangingDecimal( - pop_sizes[i], - lambda a: int(np.round( - axes.point_to_coords(dot.get_center())[i] - )), - position_update_func=lambda m: m.move_to(tens[i]) - ) - coord_pair.add_background_rectangle() - coord_pair_update = Mobject.add_updater( - coord_pair, lambda m: m.next_to(dot, UR, SMALL_BUFF) - ) - pop_sizes_updates = [get_pop_size_update(i) for i in (0, 1)] - - phase_space = TextMobject("``Phase space''") - phase_space.set_color(YELLOW) - phase_space.scale(1.5) - phase_space.to_edge(UP) - phase_space.shift(2 * RIGHT) - - self.play(ShowCreation(v_line)) - self.play(ShowCreation(h_line)) - dot.save_state() - dot.move_to(origin) - self.add(coord_pair_update) - self.add(*pop_sizes_updates) - self.play( - dot.restore, - VFadeIn(coord_pair), - UpdateFromAlphaFunc(pop_sizes, lambda m, a: m.set_fill(opacity=a)), - ) - self.wait() - self.play(Write(phase_space)) - self.wait(2) - self.play(FadeOut(VGroup(h_line, v_line, phase_space))) - self.play(Rotating( - dot, - about_point=axes.coords_to_point(30, 30), - rate_func=smooth, - )) - - self.dot = dot - self.coord_pair = coord_pair - self.coord_pair_update = coord_pair_update - self.pop_sizes = pop_sizes - self.pop_sizes_updates = pop_sizes_updates - - def write_differential_equations(self): - equations = self.get_equations() - equations.shift(2 * DOWN) - rect = SurroundingRectangle(equations, color=YELLOW) - rect.set_fill(BLACK, 0.8) - title = TextMobject("Differential equations") - title.next_to(rect, UP) - title.set_color(rect.get_stroke_color()) - self.differential_equation_group = VGroup( - rect, equations, title - ) - self.differential_equation_group.to_corner(UR) - - randy = self.pi_creature - randy.next_to(rect, DL) - - self.play( - Write(title, run_time=1), - ShowCreation(rect) - ) - self.play( - LaggedStartMap(FadeIn, equations), - randy.change, "confused", equations, - VFadeIn(randy), - ) - self.wait(3) - - def add_vectors(self): - origin = self.axes.coords_to_point(0, 0) - dot = self.dot - randy = self.pi_creature - - def rescaled_field(point): - x, y = self.axes.point_to_coords(point) - result = preditor_prey_vector_field(np.array([x, y, 0])) - return self.axes.coords_to_point(*result[:2]) - origin - - self.vector_field_config.update({ - "x_min": origin[0] + 0.5, - "x_max": self.axes.get_right()[0] + 1, - "y_min": origin[1] + 0.5, - "y_max": self.axes.get_top()[1], - }) - vector_field = VectorField( - rescaled_field, **self.vector_field_config - ) - - def get_dot_vector(): - vector = vector_field.get_vector(dot.get_center()) - vector.scale(1, about_point=vector.get_start()) - return vector - - dot_vector = get_dot_vector() - - self.play( - LaggedStartMap(GrowArrow, vector_field), - randy.change, "thinking", dot, - Animation(self.differential_equation_group) - ) - self.wait(3) - self.play( - Animation(dot), - vector_field.set_fill, {"opacity": 0.2}, - Animation(self.differential_equation_group), - GrowArrow(dot_vector), - randy.change, "pondering", - ) - self.wait() - self.play( - dot.move_to, dot_vector.get_end(), - dot.align_to, dot, RIGHT, - run_time=3, - ) - self.wait(2) - self.play( - dot.move_to, dot_vector.get_end(), - run_time=3, - ) - self.wait(2) - for x in range(6): - new_dot_vector = get_dot_vector() - fade_anims = [ - FadeOut(dot_vector), - FadeIn(new_dot_vector), - Animation(dot), - ] - if x == 4: - fade_anims += [ - vector_field.set_fill, {"opacity": 0.5}, - FadeOut(randy), - FadeOut(self.differential_equation_group), - ] - self.play(*fade_anims) - dot_vector = new_dot_vector - self.play(dot.move_to, dot_vector.get_end()) - - dot_movement = move_along_vector_field( - dot, lambda p: 0.3 * vector_field.func(p) - ) - self.add(dot_movement) - self.play(FadeOut(dot_vector)) - self.wait(10) - self.play( - vector_field.set_fill, {"opacity": 1.0}, - VFadeOut(dot), - VFadeOut(self.coord_pair), - UpdateFromAlphaFunc(self.pop_sizes, lambda m, a: m.set_fill(opacity=1 - a)), - ) - self.remove( - dot_movement, - self.coord_pair_update, - *self.pop_sizes_updates - ) - self.wait() - - self.vector_field = vector_field - - def show_phase_flow(self): - vector_field = self.vector_field - stream_lines = StreamLines( - vector_field.func, - start_points_generator_config={ - "x_min": vector_field.x_min, - "x_max": vector_field.x_max, - "y_min": vector_field.y_min, - "y_max": vector_field.y_max, - "delta_x": 0.25, - "delta_y": 0.25, - }, - min_magnitude=vector_field.min_magnitude, - max_magnitude=vector_field.max_magnitude, - virtual_time=4, - ) - stream_line_animation = AnimatedStreamLines( - stream_lines, - ) - self.add(stream_line_animation) - self.add_foreground_mobjects(vector_field) - self.wait(self.flow_time) - self.play( - self.camera_frame.scale, 1.5, {"about_point": self.origin}, - run_time=self.flow_time, - ) - self.wait(self.flow_time) - - # - def get_equations(self): - variables = ["X", "YY"] - equations = TexMobject( - """ - {dX \\over dt} = - X \\cdot (\\alpha - \\beta YY \\,) \\\\ - \\quad \\\\ - {dYY \\over dt} = - YY \\cdot (\\delta X - \\gamma) - """, - substrings_to_isolate=variables - ) - animals = [self.get_rabbit(), self.get_fox().flip()] - for char, animal in zip(variables, animals): - for part in equations.get_parts_by_tex(char): - animal_copy = animal.copy() - animal_copy.set_height(0.5) - animal_copy.move_to(part, DL) - part.become(animal_copy) - return equations - - -class PhaseFlowWords(Scene): - def construct(self): - words = TextMobject("``Phase flow''") - words.scale(2) - self.play(Write(words)) - self.wait() - - -class PhaseFlowQuestions(Scene): - def construct(self): - questions = VGroup( - TextMobject( - "Which points does the flow \\\\" + - "converge to? Diverge away from?", - ), - TextMobject("Where are there cycles?"), - ) - questions.arrange(DOWN, buff=LARGE_BUFF) - questions.to_corner(UR) - for question in questions: - self.play(FadeInFromDown(question)) - self.wait(2) - - -class ToolsBeyondDivAndCurlForODEs(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs": { - "color": GREY_BROWN, - }, - "default_pi_creature_start_corner": DOWN, - } - - def construct(self): - morty = self.pi_creature - div_curl = TextMobject("div \\\\", "curl") - div_curl.set_color_by_tex("div", BLUE) - div_curl.set_color_by_tex("curl", YELLOW) - div_curl.next_to(morty.get_corner(UL), UP, MED_LARGE_BUFF) - - jacobian = TextMobject("Analyze the \\\\ Jacobian") - jacobian.set_color(GREEN) - jacobian.next_to(morty.get_corner(UR), UP, MED_LARGE_BUFF) - - flow_intuitions = TextMobject("Flow-based intuitions") - flow_intuitions.next_to( - VGroup(div_curl, jacobian), - UP, buff=1.5 - ) - arrow1 = Arrow(div_curl.get_top(), flow_intuitions.get_bottom()) - arrow2 = Arrow(flow_intuitions.get_bottom(), jacobian.get_top()) - - self.play( - FadeInFromDown(div_curl), - morty.change, "raise_left_hand", - ) - self.wait() - self.play( - FadeInFromDown(jacobian), - morty.change, "raise_right_hand" - ) - self.wait() - self.play( - ReplacementTransform( - flow_intuitions.copy().fade(1).move_to(div_curl), - flow_intuitions, - ), - GrowArrow(arrow1), - morty.change, "pondering" - ) - self.wait(0.5) - self.play(GrowArrow(arrow2)) - self.wait() - - -class AskAboutComputation(TeacherStudentsScene): - def construct(self): - self.student_says( - "Sure, but how do you \\\\" + - "\\emph{compute} $\\textbf{div}$ and $\\textbf{curl}$?", - target_mode="sassy", - ) - self.change_student_modes( - "confused", "sassy", "angry", - added_anims=[self.teacher.change, "guilty"] - ) - self.wait() - self.teacher_says( - "Are you familiar \\\\" + - "with my work \\\\" + - "at Khan Academy?", - target_mode="speaking", - bubble_kwargs={"width": 4, "height": 3} - ) - self.change_student_modes( - * 3 * ["pondering"], - look_at_arg=self.screen - ) - self.wait(5) - - -class QuickWordsOnNotation(Scene): - def construct(self): - words = TextMobject("Quick words on notation:") - words.scale(1.5) - self.play(FadeInFromDown(words)) - self.wait() - - -class NablaNotation(PiCreatureScene, MovingCameraScene): - CONFIG = { - "default_pi_creature_kwargs": { - "color": GREY_BROWN, - }, - "default_pi_creature_start_corner": DL, - } - - def setup(self): - MovingCameraScene.setup(self) - PiCreatureScene.setup(self) - - def construct(self): - self.show_notation() - self.show_expansion() - self.zoom_out() - - def show_notation(self): - morty = self.pi_creature - - tex_to_color_map = { - "\\text{div}": BLUE, - "\\nabla \\cdot": BLUE, - "\\text{curl}": YELLOW, - "\\nabla \\times": YELLOW, - } - div_equation = TexMobject( - "\\text{div} \\, \\textbf{F} = \\nabla \\cdot \\textbf{F}", - tex_to_color_map=tex_to_color_map - ) - div_nabla = div_equation.get_part_by_tex("\\nabla") - curl_equation = TexMobject( - "\\text{curl} \\, \\textbf{F} = \\nabla \\times \\textbf{F}", - tex_to_color_map=tex_to_color_map - ) - curl_nabla = curl_equation.get_part_by_tex("\\nabla") - equations = VGroup(div_equation, curl_equation) - equations.arrange(DOWN, buff=LARGE_BUFF) - equations.next_to(morty, UP, 2) - equations.to_edge(LEFT) - - self.play( - FadeInFromDown(div_equation), - morty.change, "raise_right_hand" - ) - self.wait() - self.play(WiggleOutThenIn(div_nabla, scale_value=1.5)) - self.wait() - self.play( - FadeInFromDown(curl_equation), - morty.change, "raise_left_hand" - ) - self.wait() - self.play(WiggleOutThenIn(curl_nabla, scale_value=1.5)) - self.wait() - - self.equations = equations - - def show_expansion(self): - equations = self.equations - morty = self.pi_creature - - nabla_vector = Matrix([ - ["\\partial \\over \\partial x"], - ["\\partial \\over \\partial y"], - ], v_buff=1.5) - F_vector = Matrix([ - ["\\textbf{F}_x"], - ["\\textbf{F}_y"], - ], v_buff=1.2) - nabla_vector.match_height(F_vector) - - div_lhs, curl_lhs = lhs_groups = VGroup(*[ - VGroup( - nabla_vector.deepcopy(), - TexMobject(tex).scale(1.5), - F_vector.copy(), - TexMobject("=") - ) - for tex in ("\\cdot", "\\times") - ]) - colors = [BLUE, YELLOW] - for lhs, color in zip(lhs_groups, colors): - lhs.arrange(RIGHT, buff=MED_SMALL_BUFF) - VGroup(lhs[0].brackets, lhs[1]).set_color(color) - div_lhs.to_edge(UP) - curl_lhs.next_to(div_lhs, DOWN, buff=LARGE_BUFF) - - div_rhs = TexMobject( - "{\\partial F_x \\over \\partial x} + " + - "{\\partial F_y \\over \\partial y}" - ) - curl_rhs = TexMobject( - "{\\partial F_y \\over \\partial x} - " + - "{\\partial F_x \\over \\partial y}" - ) - rhs_groups = VGroup(div_rhs, curl_rhs) - for rhs, lhs in zip(rhs_groups, lhs_groups): - rhs.next_to(lhs, RIGHT) - - for rhs, tex, color in zip(rhs_groups, ["div", "curl"], colors): - rhs.rect = SurroundingRectangle(rhs, color=color) - rhs.label = TexMobject( - "\\text{%s}" % tex, - "\\, \\textbf{F}" - ) - rhs.label.set_color(color) - rhs.label.next_to(rhs.rect, UP) - - for i in 0, 1: - self.play( - ReplacementTransform( - equations[i][2].copy(), - lhs_groups[i][0].brackets - ), - ReplacementTransform( - equations[i][3].copy(), - lhs_groups[i][2], - ), - morty.change, "pondering", - *[ - GrowFromPoint(mob, equations[i].get_right()) - for mob in [ - lhs_groups[i][0].get_entries(), - lhs_groups[i][1], - lhs_groups[i][3] - ] - ], - run_time=2 - ) - self.wait() - self.wait() - for rhs in rhs_groups: - self.play( - Write(rhs), - morty.change, 'confused' - ) - self.play( - ShowCreation(rhs.rect), - FadeInFromDown(rhs.label), - ) - self.wait() - self.play(morty.change, "erm") - self.wait(3) - - def zoom_out(self): - screen_rect = self.camera_frame.copy() - screen_rect.set_stroke(WHITE, 3) - screen_rect.scale(1.01) - words = TextMobject("Something deeper at play...") - words.scale(1.3) - words.next_to(screen_rect, UP) - - self.add(screen_rect) - self.play( - self.camera_frame.set_height, FRAME_HEIGHT + 3, - Write(words, rate_func=squish_rate_func(smooth, 0.3, 1)), - run_time=2, - ) - self.wait() - - -class DivCurlDotCross(Scene): - def construct(self): - rects = VGroup(*[ - ScreenRectangle(height=2.5) - for n in range(4) - ]) - rects.arrange_in_grid(n_rows=2, buff=LARGE_BUFF) - rects[2:].shift(MED_LARGE_BUFF * DOWN) - titles = VGroup(*list(map(TextMobject, [ - "Divergence", "Curl", - "Dot product", "Cross product" - ]))) - for title, rect in zip(titles, rects): - title.next_to(rect, UP) - - self.add(rects, titles) - - -class ShowDotProduct(MovingCameraScene): - CONFIG = { - "prod_tex": "\\cdot" - } - - def construct(self): - plane = NumberPlane() - v1 = Vector(RIGHT, color=BLUE) - v2 = Vector(UP, color=YELLOW) - - dot_product = TexMobject( - "\\vec{\\textbf{v}}", self.prod_tex, - "\\vec{\\textbf{w}}", "=" - ) - dot_product.set_color_by_tex_to_color_map({ - "textbf{v}": BLUE, - "textbf{w}": YELLOW, - }) - dot_product.add_background_rectangle() - dot_product.next_to(2.25 * UP, RIGHT) - dot_product_value = DecimalNumber( - 1.0, - include_background_rectangle=True, - ) - dot_product_value.next_to(dot_product) - dot_product_value_update = ContinualChangingDecimal( - dot_product_value, - lambda a: self.get_product(v1, v2), - include_background_rectangle=True, - ) - - self.camera_frame.set_height(4) - self.camera_frame.move_to(DL, DL) - self.add(plane) - self.add(dot_product, dot_product_value_update) - self.add_additional_continual_animations(v1, v2) - self.add_foreground_mobjects(v1, v2) - for n in range(5): - self.play( - Rotate(v1, 45 * DEGREES, about_point=ORIGIN), - Rotate(v2, -45 * DEGREES, about_point=ORIGIN), - run_time=3, - rate_func=there_and_back - ) - self.wait(0.5) - - def get_product(self, v1, v2): - return np.dot(v1.get_vector(), v2.get_vector()) - - def add_additional_continual_animations(self, v1, v2): - pass - - -class ShowCrossProduct(ShowDotProduct): - CONFIG = { - "prod_tex": "\\times" - } - - def get_product(self, v1, v2): - return get_norm( - np.cross(v1.get_vector(), v2.get_vector()) - ) - - def add_additional_continual_animations(self, v1, v2): - square = Square( - stroke_color=YELLOW, - stroke_width=3, - fill_color=YELLOW, - fill_opacity=0.2, - ) - - self.add(Mobject.add_updater( - square, - lambda s: s.set_points_as_corners([ - ORIGIN, - v1.get_end(), - v1.get_end() + v2.get_end(), - v2.get_end(), - ]) - )) - - -class DivergenceTinyNudgesView(MovingCameraScene): - CONFIG = { - "scale_factor": 0.25, - "point": ORIGIN, - } - - def construct(self): - self.add_vector_field() - self.zoom_in() - self.take_tiny_step() - self.show_dot_product() - self.show_circle_of_values() - self.switch_to_curl_words() - self.rotate_difference_vectors() - - def add_vector_field(self): - plane = self.plane = NumberPlane() - - def func(p): - x, y = p[:2] - result = np.array([ - np.sin(x + 0.1), - np.cos(2 * y), - 0 - ]) - result /= (get_norm(result)**0.5 + 1) - return result - - vector_field = self.vector_field = VectorField( - func, - length_func=lambda n: 0.5 * sigmoid(n), - # max_magnitude=1.0, - ) - self.add(plane) - self.add(vector_field) - - def zoom_in(self): - point = self.point - vector_field = self.vector_field - sf = self.scale_factor - - vector_field.vector_config.update({ - "rectangular_stem_width": 0.02, - "tip_length": 0.1, - }) - vector_field.length_func = lambda n: n - vector = vector_field.get_vector(point) - - input_dot = Dot(point).scale(sf) - input_words = TextMobject("$(x_0, y_0)$").scale(sf) - input_words.next_to(input_dot, DL, SMALL_BUFF * sf) - output_words = TextMobject("Output").scale(sf) - output_words.add_background_rectangle() - output_words.next_to(vector.get_top(), UP, sf * SMALL_BUFF) - output_words.match_color(vector) - - self.play( - self.camera_frame.scale, sf, - self.camera_frame.move_to, point, - FadeOut(vector_field), - FadeIn(vector), - run_time=2 - ) - self.add_foreground_mobjects(input_dot) - self.play( - FadeInFrom(input_dot, SMALL_BUFF * DL), - Write(input_words), - ) - self.play( - Indicate(vector), - Write(output_words), - ) - self.wait() - - self.set_variables_as_attrs( - point, vector, input_dot, - input_words, output_words, - ) - - def take_tiny_step(self): - sf = self.scale_factor - vector_field = self.vector_field - point = self.point - vector = self.vector - output_words = self.output_words - input_dot = self.input_dot - - nudge = 0.5 * RIGHT - nudged_point = point + nudge - - new_vector = vector_field.get_vector(nudged_point) - new_vector.set_color(YELLOW) - new_dot = Dot(nudged_point).scale(sf) - step_vector = Arrow( - point, nudged_point, - buff=0, - color=TEAL, - **vector_field.vector_config - ) - step_vector.set_stroke(BLACK, 0.5) - - new_output_words = TextMobject("New output").scale(sf) - new_output_words.add_background_rectangle() - new_output_words.next_to(new_vector.get_end(), UP, sf * SMALL_BUFF) - new_output_words.match_color(new_vector) - step_words = TextMobject("Step").scale(sf) - step_words.next_to(step_vector, UP, buff=0) - step_words.set_color(step_vector.get_fill_color()) - step_words.add_background_rectangle() - small_step_words = TextMobject("(think tiny step)").scale(sf) - small_step_words.next_to( - step_words, RIGHT, - buff=sf * MED_SMALL_BUFF, - ) - small_step_words.add_background_rectangle() - small_step_words.match_style(step_words) - - shifted_vector = vector.copy().shift(nudge) - diff_vector = Arrow( - shifted_vector.get_end(), - new_vector.get_end(), - buff=0, - color=RED, - **vector_field.vector_config - ) - diff_words = TextMobject("Difference").scale(sf) - diff_words.add_background_rectangle() - diff_words.next_to(diff_vector.get_start(), UR, buff=2 * sf * SMALL_BUFF) - diff_words.match_color(diff_vector) - diff_words.rotate( - diff_vector.get_angle(), - about_point=diff_vector.get_start() - ) - - self.play( - GrowArrow(step_vector), - Write(step_words), - ReplacementTransform(input_dot.copy(), new_dot) - ) - self.add_foreground_mobjects(new_dot) - self.play(FadeIn(small_step_words)) - self.play(FadeOut(small_step_words)) - self.play( - ReplacementTransform(vector.copy(), new_vector), - ReplacementTransform(output_words.copy(), new_output_words), - ) - self.wait() - self.play(ReplacementTransform( - vector.copy(), shifted_vector, - path_arc=-TAU / 4 - )) - self.wait() - self.play( - FadeOut(output_words), - FadeOut(new_output_words), - GrowArrow(diff_vector), - Write(diff_words) - ) - self.wait() - self.play( - vector.scale, 0, {"about_point": vector.get_start()}, - shifted_vector.scale, 0, {"about_point": shifted_vector.get_start()}, - ReplacementTransform( - new_vector, - diff_vector.copy().shift(-vector.get_vector()), - remover=True - ), - diff_vector.shift, -vector.get_vector(), - MaintainPositionRelativeTo(diff_words, diff_vector), - run_time=2 - ) - self.wait() - - self.set_variables_as_attrs( - step_vector, step_words, - diff_vector, diff_words, - ) - - def show_dot_product(self): - sf = self.scale_factor - point = self.point - step_vector = self.step_vector - step_words = self.step_words - diff_vector = self.diff_vector - diff_words = self.diff_words - vects = VGroup(step_vector, diff_vector) - - moving_step_vector = step_vector.copy() - moving_diff_vector = diff_vector.copy() - - def update_moving_diff_vector(dv): - step = moving_step_vector.get_vector() - o1 = self.vector_field.get_vector(point).get_vector() - o2 = self.vector_field.get_vector(point + step).get_vector() - diff = o2 - o1 - dv.put_start_and_end_on( - moving_step_vector.get_end(), - moving_step_vector.get_end() + diff, - ) - self.moving_diff_vector_update = Mobject.add_updater( - moving_diff_vector, - update_moving_diff_vector - ) - self.add(self.moving_diff_vector_update) - - div_text = self.get_operator_text("div") - - step_words_copy = step_words.copy() - diff_words_copy = diff_words.copy() - copies = VGroup(step_words_copy, diff_words_copy) - - substrings = ["Step", "Difference"] - dot_product = TextMobject( - "(Step) $\\cdot$ (Difference)", - substrings_to_isolate=substrings, - arg_separator="", - ).scale(sf) - group = VGroup(div_text, dot_product) - group.arrange(RIGHT, buff=sf * MED_SMALL_BUFF) - group.next_to( - self.camera_frame.get_top(), DOWN, - buff=sf * MED_SMALL_BUFF - ) - - for substring, mob, vect in zip(substrings, copies, vects): - part = dot_product.get_part_by_tex(substring) - mob.generate_target() - mob.target.rotate(-vect.get_angle()) - mob.target.replace(part) - # part.set_fill(opacity=0) - part.match_color(mob) - dot_product.add_background_rectangle() - - brace = Brace( - dot_product.copy().scale(1 / sf, about_point=ORIGIN), DOWN, - buff=SMALL_BUFF - ).scale(sf, about_point=ORIGIN) - dp_kwargs = { - "include_sign": True, - } - dot_product_value = DecimalNumber(1.0, **dp_kwargs) - dot_product_value.scale(sf) - dot_product_value.next_to(brace, DOWN, sf * SMALL_BUFF) - dot_product_value_update = ContinualChangingDecimal( - dot_product_value, - lambda a: np.dot( - moving_step_vector.get_vector(), - moving_diff_vector.get_vector(), - ), - **dp_kwargs - ) - - self.play( - Write(dot_product), - LaggedStartMap(MoveToTarget, copies) - ) - self.remove(copies) - self.play(FadeIn(div_text)) - self.play(ShowPassingFlashAround( - div_text[1:3], - surrounding_rectangle_config={"buff": sf * SMALL_BUFF} - )) - self.add(BackgroundRectangle(dot_product_value)) - self.play( - GrowFromCenter(brace), - Write(dot_product_value), - ) - self.add(dot_product_value_update) - self.wait() - - self.set_variables_as_attrs( - div_text, dot_product, - moving_step_vector, - moving_diff_vector, - dot_product_value, - dot_product_value_update, - brace, - ) - - def show_circle_of_values(self): - point = self.point - moving_step_vector = self.moving_step_vector - moving_diff_vector = self.moving_diff_vector - - all_diff_vectors = VGroup() - all_step_vectors = VGroup() - # Loop around - n_samples = 12 - angle = TAU / n_samples - self.add_foreground_mobjects(self.step_words) - for n in range(n_samples): - self.play( - Rotating( - moving_step_vector, - radians=angle, - about_point=point, - run_time=15.0 / n_samples, - rate_func=linear, - ) - ) - step_vector_copy = moving_step_vector.copy() - diff_vector_copy = moving_diff_vector.copy() - diff_vector_copy.set_stroke(BLACK, 0.5) - self.add(step_vector_copy, diff_vector_copy) - all_step_vectors.add(step_vector_copy) - all_diff_vectors.add(diff_vector_copy) - self.remove( - self.step_vector, self.diff_vector, - self.moving_step_vector, self.moving_diff_vector, - self.moving_diff_vector_update, - self.dot_product_value_update - ) - self.remove_foreground_mobjects(self.step_words) - self.play( - FadeOut(self.brace), - FadeOut(self.dot_product_value), - FadeOut(self.step_words), - FadeOut(self.diff_words), - ) - self.wait() - - for s in 0.6, -0.6: - for step, diff in zip(all_step_vectors, all_diff_vectors): - diff.generate_target() - diff.target.put_start_and_end_on( - step.get_end(), - step.get_end() + s * step.get_vector() - ) - self.play( - all_step_vectors.set_fill, {"opacity": 0.5}, - LaggedStartMap( - MoveToTarget, all_diff_vectors, - run_time=3 - ), - ) - self.wait() - self.show_stream_lines(lambda p: s * (p - point)) - self.wait() - - self.set_variables_as_attrs( - all_step_vectors, all_diff_vectors, - ) - - def switch_to_curl_words(self): - sf = self.scale_factor - div_text = self.div_text - dot_product = self.dot_product - - curl_text = self.get_operator_text("curl") - cross_product = TextMobject( - "(Step) $\\times$ (Difference)", - tex_to_color_map={ - "Step": TEAL, - "Difference": RED - }, - arg_separator="", - ).scale(sf) - cross_product.add_background_rectangle(opacity=1) - - group = VGroup(curl_text, cross_product) - group.arrange(RIGHT, buff=sf * MED_SMALL_BUFF) - group.next_to(self.camera_frame.get_top(), sf * DOWN) - - self.play( - dot_product.shift, sf * DOWN, - dot_product.fade, 1, - remover=True - ) - self.play(FadeInFrom(cross_product, sf * DOWN)) - self.play( - div_text.shift, sf * DOWN, - div_text.fade, 1, - remover=True - ) - self.play(FadeInFrom(curl_text, sf * DOWN)) - self.wait() - - def rotate_difference_vectors(self): - point = self.point - all_step_vectors = self.all_step_vectors - all_diff_vectors = self.all_diff_vectors - - for s in 0.6, -0.6: - for step, diff in zip(all_step_vectors, all_diff_vectors): - diff.generate_target() - diff.target.put_start_and_end_on( - step.get_end(), - step.get_end() + s * rotate_vector( - step.get_vector(), - 90 * DEGREES - ) - ) - self.play( - LaggedStartMap( - MoveToTarget, all_diff_vectors, - run_time=2 - ), - ) - self.wait() - self.show_stream_lines( - lambda p: s * rotate_vector((p - point), 90 * DEGREES) - ) - self.wait() - - self.set_variables_as_attrs( - all_step_vectors, all_diff_vectors, - ) - - # Helpers - - def get_operator_text(self, operator): - text = TextMobject( - operator + "\\,", - "$\\textbf{F}(x_0, y_0)\\,$", - "corresponds to average of", - arg_separator="" - ).scale(self.scale_factor) - text.set_color_by_tex(operator, YELLOW) - text.add_background_rectangle() - return text - - def show_stream_lines(self, func): - point = self.point - stream_lines = StreamLines( - func, - start_points_generator_config={ - "x_min": point[0] - 2, - "x_max": point[0] + 2, - "y_min": point[1] - 1, - "y_max": point[1] + 1, - "delta_x": 0.025, - "delta_y": 0.025, - }, - virtual_time=1, - ) - random.shuffle(stream_lines.submobjects) - self.play(LaggedStartMap( - ShowPassingFlash, - stream_lines, - run_time=4, - )) - - -class ZToHalfFlowNearWall(ComplexTransformationScene, MovingCameraScene): - CONFIG = { - "num_anchors_to_add_per_line": 200, - "plane_config": {"y_radius": 8} - } - - def setup(self): - MovingCameraScene.setup(self) - ComplexTransformationScene.setup(self) - - def construct(self): - # self.camera.frame.shift(2 * UP) - self.camera.frame.scale(0.5, about_point=ORIGIN) - - plane = NumberPlane( - x_radius=15, - y_radius=25, - y_unit_size=0.5, - secondary_line_ratio=0, - ) - plane.next_to(ORIGIN, UP, buff=0.001) - horizontal_lines = VGroup(*[l for l in list(planes) + [plane.axes[0]] if np.abs(l.get_center()[0]) < 0.1]) - plane.set_stroke(MAROON_B, width=2) - horizontal_lines.set_stroke(BLUE, width=2) - - self.prepare_for_transformation(plane) - self.add_transformable_mobjects(plane) - - self.background.set_stroke(width=2) - for label in self.background.coordinate_labels: - label.set_stroke(width=0) - label.scale(0.75, about_edge=UR) - - words = TextMobject("(Idealized) Flow \\\\", "near a wall") - words.scale(0.75) - words.add_background_rectangle_to_submobjects() - words.next_to(0.75 * UP, LEFT, MED_LARGE_BUFF) - equation = TexMobject("z \\rightarrow z^{1/2}") - equation.scale(0.75) - equation.add_background_rectangle() - equation.next_to(words, UP) - - self.apply_complex_function( - lambda x: x**(1. / 2), - added_anims=[Write(equation)] - ) - self.play(Write(words, run_time=1)) - - def func(point): - z = R3_to_complex(point) - d_half = derivative(lambda z: z**2) - return complex_to_R3(d_half(z).conjugate()) - - stream_lines = StreamLines( - func, - start_points_generator_config={ - "x_min": 0.01, - "y_min": 0.01, - "delta_x": 0.125, - "delta_y": 0.125, - }, - virtual_time=3, - stroke_width=2, - max_magnitude=10, - ) - stream_line_animation = AnimatedStreamLines(stream_lines) - - self.add(stream_line_animation) - self.wait(7) - - -class IncmpressibleAndIrrotational(Scene): - def construct(self): - div_0 = TextMobject("div$\\textbf{F} = 0$") - curl_0 = TextMobject("curl$\\textbf{F}$ = 0") - incompressible = TextMobject("Incompressible") - irrotational = TextMobject("Irrotational") - - for text in [div_0, curl_0, incompressible, irrotational]: - self.stylize_word_for_background(text) - - div_0.to_edge(UP) - curl_0.next_to(div_0, DOWN, MED_LARGE_BUFF) - - for op, word in (div_0, incompressible), (curl_0, irrotational): - op.generate_target() - group = VGroup(op.target, word) - group.arrange(RIGHT, buff=MED_LARGE_BUFF) - group.move_to(op) - - self.play(FadeInFromDown(div_0)) - self.play(FadeInFromDown(curl_0)) - self.wait() - self.play( - MoveToTarget(div_0), - FadeInFromDown(incompressible), - ) - self.wait() - self.play( - MoveToTarget(curl_0), - FadeInFromDown(irrotational), - ) - self.wait() - - rect = SurroundingRectangle(VGroup(curl_0, irrotational)) - question = TextMobject("Does this actually happen?") - question.next_to(rect, DOWN) - question.match_color(rect) - self.stylize_word_for_background(question) - - self.play(ShowCreation(rect)) - self.play(Write(question)) - self.wait() - - def stylize_word_for_background(self, word): - word.add_background_rectangle() - - -class NoChargesOverlay(Scene): - def construct(self): - rect = FullScreenFadeRectangle() - rect.set_fill(BLUE_D, 0.75) - circle = Circle(radius=1.5, num_anchors=5000) - circle.rotate(135 * DEGREES) - rect.add_subpath(circle.points) - - words = TextMobject("No charges outside wire") - words.scale(1.5) - words.to_edge(UP) - - self.add(rect, words) - - -# End message -class BroughtToYouBy(PiCreatureScene): - CONFIG = { - "pi_creatures_start_on_screen": False, - } - - def construct(self): - self.brought_to_you_by() - self.just_you_and_me() - - def brought_to_you_by(self): - so_words = TextMobject("So", "...", arg_separator="") - so_words.scale(2) - - btyb = TextMobject("Brought to you", "by") - btyb.scale(1.5) - btyb_line = Line(LEFT, RIGHT) - btyb_line.next_to(btyb, RIGHT, SMALL_BUFF) - btyb_line.align_to(btyb[0], DOWN) - btyb_group = VGroup(btyb, btyb_line) - btyb_group.center() - - you_word = TextMobject("\\emph{you}") - you_word.set_color(YELLOW) - you_word.scale(1.75) - you_word.move_to(btyb_line) - you_word.align_to(btyb, DOWN) - - only_word = TextMobject("(only)") - only_word.scale(1.25) - only_brace = Brace(only_word, DOWN, buff=SMALL_BUFF) - only_group = VGroup(only_word, only_brace) - only_group.next_to( - VGroup(btyb[0][-1], btyb[1][0]), UP, SMALL_BUFF - ) - only_group.set_color(RED) - - full_group = VGroup(btyb_group, only_group, you_word) - full_group.generate_target() - full_group.target.scale(0.4) - full_group.target.to_corner(UL) - - patreon_logo = PatreonLogo() - patreon_logo.scale(0.4) - patreon_logo.next_to(full_group.target, DOWN) - - self.play( - Write(so_words[0]), - LaggedStartMap( - DrawBorderThenFill, so_words[1], - run_time=5 - ), - ) - self.play( - so_words.shift, DOWN, - so_words.fade, 1, - remover=True - ) - self.play(FadeInFromDown(btyb_group)) - self.wait() - self.play(Write(you_word)) - self.play( - GrowFromCenter(only_brace), - Write(only_word) - ) - self.wait() - self.play(MoveToTarget( - full_group, - rate_func=running_start, - )) - self.play(LaggedStartMap( - DrawBorderThenFill, patreon_logo - )) - self.wait() - - def just_you_and_me(self): - randy, morty = self.pi_creatures - for pi in self.pi_creatures: - pi.change("pondering") - math = TexMobject("\\sum_{n=1}^\\infty \\frac{1}{n^s}") - math.scale(2) - math.move_to(self.pi_creatures) - - spiral = Line(0.5 * RIGHT, 0.5 * RIGHT + 70 * UP) - spiral.insert_n_curves(1000) - from from_3b1b.old.zeta import zeta - spiral.apply_complex_function(zeta) - step = 0.1 - spiral = VGroup(*[ - VMobject().pointwise_become_partial( - spiral, a, a + step - ) - for a in np.arange(0, 1, step) - ]) - spiral.set_color_by_gradient(BLUE, YELLOW, RED) - spiral.scale(0.5) - spiral.move_to(math) - - self.play(FadeInFromDown(randy)) - self.play(FadeInFromDown(morty)) - self.play( - Write(math), - randy.change, "hooray", - morty.change, "hooray", - ) - self.look_at(math) - self.play( - ShowCreation(spiral, run_time=6, rate_func=linear), - math.scale, 0.5, - math.shift, 3 * UP, - randy.change, "thinking", - morty.change, "thinking", - ) - self.play(LaggedStartMap(FadeOut, spiral, run_time=3)) - self.wait(3) - - # Helpers - def create_pi_creatures(self): - randy = Randolph(color=BLUE_C) - randy.to_edge(DOWN).shift(4 * LEFT) - morty = Mortimer() - morty.to_edge(DOWN).shift(4 * RIGHT) - return VGroup(randy, morty) - - -class ThoughtsOnAds(Scene): - def construct(self): - title = Title( - "Internet advertising", - match_underline_width_to_text=True, - underline_buff=SMALL_BUFF, - ) - - line = NumberLine( - color=LIGHT_GREY, - x_min=0, - x_max=12, - numbers_with_elongated_ticks=[] - ) - line.move_to(DOWN) - - arrows = VGroup(Vector(2 * LEFT), Vector(2 * RIGHT)) - arrows.arrange(RIGHT, buff=2) - arrows.next_to(line, DOWN) - - misaligned = TextMobject("Misaligned") - misaligned.next_to(arrows[0], DOWN) - aligned = TextMobject("Well-aligned") - aligned.next_to(arrows[1], DOWN) - - VGroup(arrows[0], misaligned).set_color(RED) - VGroup(arrows[1], aligned).set_color(BLUE) - - left_text = TextMobject( - "Any website presented \\\\", - "as a click-maximizing \\\\ slideshow" - ) - left_text.scale(0.8) - left_text.next_to(line, UP, buff=MED_LARGE_BUFF) - left_text.to_edge(LEFT) - - viewer, brand, creator = vcb = VGroup( - *list(map(TextMobject, ["viewer", "brand", "creator"])) - ) - brand.next_to(creator, LEFT, LARGE_BUFF) - viewer.next_to(vcb[1:], UP, LARGE_BUFF) - arrow_config = { - "path_arc": 60 * DEGREES, - "tip_length": 0.15, - } - vcb_arrows = VGroup(*[ - VGroup( - Arrow(p1, p2, **arrow_config), - Arrow(p2, p1, **arrow_config), - ) - for p1, p2 in [ - (creator.get_left(), brand.get_right()), - (brand.get_top(), viewer.get_bottom()), - (viewer.get_bottom(), creator.get_top()), - ] - ]) - vcb_arrows.set_stroke(width=2) - vcb_arrows.set_color(BLUE) - vcb_group = VGroup(vcb, vcb_arrows) - vcb_group.next_to(line, UP, buff=MED_LARGE_BUFF) - vcb_group.to_edge(RIGHT) - - knob = RegularPolygon(n=3, start_angle=-90 * DEGREES) - knob.set_height(0.25) - knob.set_stroke(width=0) - knob.set_fill(YELLOW, 1) - knob.move_to(line.get_left(), DOWN) - - right_rect = Rectangle( - width=3, - height=0.25, - stroke_color=WHITE, - stroke_width=2, - fill_color=BLUE, - fill_opacity=0.5 - ) - right_rect.move_to(line, RIGHT) - right_rect_label = Group( - ImageMobject("3b1b_logo", height=1), - TextMobject("(hopefully)").scale(0.8) - ) - right_rect_label.arrange(DOWN, buff=SMALL_BUFF) - # TextMobject( - # "Where I hope \\\\ I've been" - # ) - right_rect_label.next_to( - right_rect, UP, SMALL_BUFF - ) - # right_rect_label.set_color(BLUE) - - self.add(title) - self.play(ShowCreation(line)) - self.play( - Write(misaligned), - Write(aligned), - *list(map(GrowArrow, arrows)), - run_time=1 - ) - - self.play( - FadeIn(left_text), - FadeInFrom(knob, 2 * RIGHT) - ) - self.wait() - self.play( - LaggedStartMap(FadeInFromDown, vcb), - LaggedStartMap(ShowCreation, vcb_arrows), - ApplyMethod( - knob.move_to, line.get_right(), DOWN, - run_time=2 - ) - ) - self.wait(2) - self.play(vcb_group.shift, 2 * UP) - self.play( - DrawBorderThenFill(right_rect), - FadeIn(right_rect_label), - ) - self.wait() - - -class HoldUpPreviousPromo(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs": { - "color": GREY_BROWN, - "flip_at_start": True, - }, - "default_pi_creature_start_corner": DR, - } - - def construct(self): - morty = self.pi_creature - screen_rect = ScreenRectangle(height=5) - screen_rect.to_corner(UL) - - self.play( - FadeInFromDown(screen_rect), - morty.change, "raise_right_hand", - ) - self.wait(5) - - -class GoalWrapper(Scene): - def construct(self): - goal = TextMobject( - "Goal: Teach/remind people \\\\ that they love math" - ) - goal.to_edge(UP) - self.add(goal) - - screen_rect = ScreenRectangle(height=6) - screen_rect.next_to(goal, DOWN) - self.play(ShowCreation(screen_rect)) - self.wait() - - -class PeopleValueGraph(GraphScene): - CONFIG = { - "x_axis_label": "People reached", - "y_axis_label": "Value per person", - "x_min": 0, - "x_max": 12, - "x_axis_width": 11, - "y_max": 8, - "y_axis_height": 5, - "graph_origin": 2 * DOWN + 5 * LEFT, - "riemann_rectangles_config": { - "dx": 0.01, - "stroke_width": 0, - "start_color": GREEN, - "end_color": BLUE, - } - } - - def construct(self): - self.setup_axes() - self.tweak_labels() - self.add_curve() - self.comment_on_incentives() - self.change_curve() - - def tweak_labels(self): - self.add_foreground_mobjects(self.x_axis_label_mob) - self.y_axis_label_mob.to_edge(LEFT) - - def add_curve(self): - graph = self.graph = self.get_graph( - lambda x: 7 * np.exp(-0.5 * x), - ) - - self.play( - ShowCreation(graph), - rate_func=bezier([0, 0, 1, 1]), - run_time=3 - ) - - def comment_on_incentives(self): - reach_arrow = Vector(5 * RIGHT) - reach_arrow.next_to( - self.x_axis, DOWN, - buff=SMALL_BUFF, - aligned_edge=RIGHT - ) - reach_words = TextMobject("Maximize reach?") - reach_words.next_to(reach_arrow, DOWN, buff=SMALL_BUFF) - reach_words.match_color(reach_arrow) - - area = self.area = self.get_riemann_rectangles( - self.graph, **self.riemann_rectangles_config - ) - area_words = TextMobject("Maximize this area") - area_words.set_color(BLUE) - area_words.move_to(self.coords_to_point(4, 5)) - area_arrow = Arrow( - area_words.get_bottom(), - self.coords_to_point(1.5, 2) - ) - - self.play(GrowArrow(reach_arrow)) - self.play(Write(reach_words)) - self.wait() - self.play( - LaggedStartMap(DrawBorderThenFill, area), - Animation(self.graph), - Animation(self.axes), - Write(area_words), - GrowArrow(area_arrow), - ) - self.wait() - - self.area_label_group = VGroup(area_words, area_arrow) - - def change_curve(self): - new_graph = self.get_graph( - lambda x: interpolate( - 7 * np.exp(-0.01 * x), - 7 * np.exp(-3 * x), - smooth(np.clip(x / 5, 0, 1)) - ) - ) - new_area = self.get_riemann_rectangles( - new_graph, **self.riemann_rectangles_config - ) - - self.play( - Transform(self.area, new_area), - Transform(self.graph, new_graph), - self.area_label_group[0].shift, RIGHT, - Animation(self.area_label_group), - Animation(self.axes), - run_time=4, - ) - self.wait() - - -class DivCurlEndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "Juan Benet", - "Keith Smith", - "Chloe Zhou", - "Desmos ", - "Burt Humburg", - "CrypticSwarm", - "Andrew Sachs", - "Devin Scott", - "Akash Kumar", - "Felix Tripier", - "Arthur Zey", - "David Kedmey", - "Ali Yahya", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Jordan Scales", - "Markus Persson", - "Fela ", - "Fred Ehrsam", - "Randy C. Will", - "Britt Selvitelle", - "Jonathan Wilson", - "Ryan Atallah", - "Joseph John Cox", - "Luc Ritchie", - "Omar Zrien", - "Sindre Reino Trosterud", - "Jeff Straathof", - "Matt Langford", - "Matt Roveto", - "Marek Cirkos", - "Magister Mugit", - "Stevie Metke", - "Cooper Jones", - "James Hughes", - "John V Wertheim", - "Chris Giddings", - "Song Gao", - "Alexander Feldman", - "Richard Burgmann", - "John Griffith", - "Chris Connett", - "Steven Tomlinson", - "Jameel Syed", - "Bong Choung", - "Ignacio Freiberg", - "Zhilong Yang", - "Giovanni Filippi", - "Eric Younge", - "Prasant Jagannath", - "James H. Park", - "Norton Wang", - "Kevin Le", - "Tianyu Ge", - "David MacCumber", - "Oliver Steele", - "Yaw Etse", - "Dave B", - "Waleed Hamied", - "George Chiesa", - "supershabam ", - "Delton Ding", - "Thomas Tarler", - "1stViewMaths", - "Jacob Magnuson", - "Mark Govea", - "Clark Gaebel", - "Mathias Jansson", - "David Clark", - "Michael Gardner", - "Mads Elvheim", - "Awoo ", - "Dr . David G. Stork", - "Ted Suzman", - "Linh Tran", - "Andrew Busey", - "John Haley", - "Ankalagon ", - "Eric Lavault", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Robert Teed", - "Jason Hise", - "Bernd Sing", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Sh\\`im\\'in Ku\\=ang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ], - } diff --git a/from_3b1b/old/domino_play.py b/from_3b1b/old/domino_play.py deleted file mode 100644 index ff47d1e4..00000000 --- a/from_3b1b/old/domino_play.py +++ /dev/null @@ -1,899 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - - -from manimlib.imports import * - - -class SimpleVelocityGraph(GraphScene): - CONFIG = { - # "frame_rate" : 4000, - # "domino_thickness" : 7.5438, - # "domino_spacing" : 8.701314282, - "data_files" : [ - "data07.txt", - "data13.txt", - # "data11.txt", - ], - "colors" : [WHITE, BLUE, YELLOW, GREEN, MAROON_B], - "x_axis_label" : "$t$", - "y_axis_label" : "$v$", - "x_min" : 0, - "x_max" : 1.8, - "x_tick_frequency" : 0.1, - "x_labeled_nums" : np.arange(0, 1.8, 0.2), - "y_tick_frequency" : 100, - "y_min" : 0, - "y_max" : 1000, - "y_labeled_nums" : list(range(0, 1000, 200)), - "x_axis_width" : 12, - "graph_origin" : 2.5*DOWN + 5*LEFT, - "trailing_average_length" : 20, - "include_domino_thickness" : False, - } - def construct(self): - self.setup_axes() - # self.save_all_images() - for data_file, color in zip(self.data_files, self.colors): - self.init_data(data_file) - self.draw_dots(color) - self.add_label_to_last_dot( - "%s %s %.2f"%( - data_file[4:6], - "hard" if self.friction == "low" else "felt", - self.domino_spacing, - ), - color - ) - self.draw_lines(color) - - def save_all_images(self): - indices = list(range(1, 20)) - for i1, i2 in it.combinations(indices, 2): - to_remove = VGroup() - for index in i1, i2: - index_str = ("%.2f"%float(0.01*index))[-2:] - data_file = "data%s.txt"%index_str - self.init_data(data_file) - color = WHITE if self.friction == "low" else BLUE - self.draw_dots(color) - self.add_label_to_last_dot( - "%s %s %.2f"%( - data_file[4:6], - "hard" if self.friction == "low" else "felt", - self.domino_spacing, - ), - color - ) - self.draw_lines(color) - to_remove.add(self.dots, self.lines, self.label) - self.save_image("dominos_%d_vs_%d"%(i1, i2)) - self.remove(to_remove) - - def init_data(self, data_file): - file_name = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - "dominos", - data_file - ) - - file = open(file_name, "r") - frames, notes = [], [] - for line in file: - line = line.replace(" ", ",") - line = line.replace("\n", "") - entries = [s for s in line.split(",") if s is not ""] - if len(entries) == 0: - continue - - if entries[0] == "framerate": - frame_rate = float(entries[1]) - elif entries[0] == "domino spacing": - domino_spacing = float(entries[1]) - elif entries[0] == "domino thickness": - domino_thickness = float(entries[1]) - elif entries[0] == "friction": - self.friction = entries[1] - else: - try: - frames.append(int(entries[0])) - except: - continue #How to treat? - # frames.append(frames[-1] + (frames[-1] - frames[-2])) - if len(entries) > 1: - notes.append(entries[1]) - else: - notes.append("") - frames = np.array(frames) - - self.times = (frames - frames[0])/float(frame_rate) - delta_times = self.times[1:] - self.times[:-1] - if self.include_domino_thickness: - distance = domino_spacing+domino_thickness - else: - distance = domino_spacing - self.velocities = distance/delta_times - self.notes = notes - - n = self.trailing_average_length - self.velocities = np.array([ - np.mean(self.velocities[max(0, i-n):i]) - for i in range(len(self.velocities)) - ]) - self.domino_spacing = domino_spacing - self.domino_thickness = domino_thickness - - def draw_dots(self, color = WHITE): - dots = VGroup() - for time, v, note in zip(self.times, self.velocities, self.notes): - dot = Dot(color = color) - dot.scale(0.5) - dot.move_to(self.coords_to_point(time, v)) - self.add(dot) - dots.add(dot) - if note == "twist": - dot.set_color(RED) - self.dots = dots - - def add_label_to_last_dot(self, label, color = WHITE): - dot = self.dots[-1] - label = TextMobject(label) - label.scale(0.75) - label.next_to(dot, UP, buff = MED_SMALL_BUFF) - label.set_color(color) - label.shift_onto_screen() - self.label = label - self.add(label) - - def draw_lines(self, color = WHITE, stroke_width = 2): - lines = VGroup() - for d1, d2 in zip(self.dots, self.dots[1:]): - line = Line(d1.get_center(), d2.get_center()) - lines.add(line) - lines.set_stroke(color, stroke_width) - self.add(lines, self.dots) - self.lines = lines - -ALL_VELOCITIES = { - 10 : [ - 0.350308642, - 0.3861880046, - 0.8665243271, - 0.9738947062, - 0.8087560386, - 1.067001275, - 0.9059117965, - 1.113855622, - 0.9088626493, - 1.504155436, - 1.347926731, - 1.274067732, - 1.242854491, - 1.118319973, - 1.177303094, - 1.202676006, - 0.9965029762, - 1.558775605, - 1.472405453, - 1.357765612, - 1.200089606, - 1.285810292, - 1.138860544, - 1.322373618, - 1.51230804, - 1.148233882, - 1.276983219, - 1.150601375, - 1.492090018, - 1.210502531, - 1.221097739, - 1.141189502, - 1.364405053, - 1.608189241, - 1.223775585, - 1.174824561, - 1.069045338, - 1.468530702, - 1.733048654, - 1.328670635, - 1.118319973, - 1.143528005, - 1.010945048, - 1.430876068, - 1.395104167, - 1.018324209, - 1.405646516, - 1.120565596, - 1.24562872, - 1.65590999, - 1.276983219, - 1.282854406, - 1.338229416, - 1.240092593, - 0.9982856291, - 1.811823593, - 1.328670635, - 1.167451185, - 1.27991208, - 1.221097739, - 1.022054335, - 1.160169785, - 1.805960086, - 0.9965029762, - 1.449458874, - 1.603568008, - 1.234605457, - 1.210502531, - 1.192396724, - 1.020185862, - 1.496090259, - 1.322373618, - 1.291763117, - 1.210502531, - 0.9807410662, - 1.341446314, - 1.391625104, - 1.480216622, - 1.148233882, - 1.125084005, - 1.670783433, - 1.118319973, - 1.174824561, - 1.395104167, - 1.167451185, - 1.182291667, - 1.696175279, - 1.306889149, - 1.430876068, - 1.048950501, - 1.823665577, - 1.24562872, - ], - 13 : [ - 0.2480920273, - 0.3532654568, - 0.549163523, - 0.5979017857, - 0.6643353175, - 0.8495940117, - 1.037573598, - 0.897413562, - 1.410977665, - 0.9180833562, - 1.303328143, - 0.9324004456, - 2.026785714, - 0.9721980256, - 1.339835934, - 1.002770291, - 2.3797086, - 1.235972684, - 1.508900406, - 1.239174685, - 1.374486864, - 1.181040564, - 1.144309638, - 1.195803571, - 1.265400605, - 1.223328462, - 1.678320802, - 1.198800573, - 0.7958759211, - 1.573425752, - 1.046655205, - 2.009753902, - 1.42782516, - 1.289276088, - 1.347384306, - 1.299786491, - 1.06530385, - 1.339835934, - 1.242393321, - 1.053571429, - 1.317689886, - 1.626943635, - 1.112375415, - 1.362739113, - 0.9110884354, - 1.578618576, - 1.853959025, - 1.504155436, - 1.158163265, - 1.262061817, - 1.060579664, - 1.122820255, - 1.594404762, - 1.27552381, - 1.382431874, - 1.109794498, - 1.303328143, - 1.160974341, - 1.296264034, - 1.092058056, - 1.077300515, - 1.462756662, - 1.317689886, - 1.390469269, - 1.099589491, - 1.649384236, - 1.467243646, - 1.402702137, - 1.092058056, - 1.201812635, - 1.258740602, - 1.321329913, - 1.272131459, - 1.175236925, - 1.181040564, - 1.296264034, - 1.24562872, - 1.358867695, - 1.332371667, - 1.296264034, - 1.217102872, - 1.169490045, - 1.114968365, - 1.528183478, - 1.374486864, - 1.223328462, - 1.324990107, - 1.268757105, - 1.169490045, - 1.578618576, - ], - 14 : [ - 0.4905860806, - 0.6236263736, - 0.71391258, - 0.8436004031, - 1.048950501, - 0.9585599771, - 1.138860544, - 1.210940325, - 1.27552381, - 1.282363079, - 1.166637631, - 1.242393321, - 1.163799096, - 1.166637631, - 1.42357568, - 1.382431874, - 1.278934301, - 1.390469269, - 1.181040564, - 1.107225529, - 1.08462909, - 1.160974341, - 1.374486864, - 1.382431874, - 1.355018211, - 1.25543682, - 1.192821518, - 0.9360497624, - 1.449458874, - 1.370548506, - 1.485470275, - 1.471758242, - 1.149811126, - 1.217102872, - 1.152581756, - 1.402702137, - 1.155365769, - 1.141578588, - 1.248881015, - 1.074879615, - 1.453864525, - 1.303328143, - 1.248881015, - 1.169490045, - 1.214013778, - 1.220207726, - 1.310469667, - 1.42357568, - 1.163799096, - 1.220207726, - 1.141578588, - 1.207882395, - 1.104668426, - 1.328670635, - 1.25543682, - 1.239174685, - 1.169490045, - 1.149811126, - 1.000672445, - 1.144309638, - 1.232787187, - 1.268757105, - 1.306889149, - 1.538011024, - 1.355018211, - 1.347384306, - 1.223328462, - 1.149811126, - 1.158163265, - 1.24562872, - 1.485470275, - 1.339835934, - 1.314069859, - 1.235972684, - 1.265400605, - 1.181040564, - 1.638087084, - 1.568266979, - 1.299786491, - 1.278934301, - 1.336093376, - 1.089570452, - 1.004876951, - 1.089570452, - 1.282363079, - 1.449458874, - 1.370548506, - 1.265400605, - 1.143215651, - ], - 15 : [ - 1.087094156, - 1.223328462, - 1.563141923, - 1.394523115, - 1.268757105, - 1.513675407, - 1.436400686, - 1.094557045, - 0.9761661808, - 1.072469571, - 1.178131597, - 1.366632653, - 1.258740602, - 1.25543682, - 1.285810292, - 1.235972684, - 1.347384306, - 1.239174685, - 1.195803571, - 1.186901808, - 1.141578588, - 1.152581756, - 1.136155412, - 1.102123107, - 1.242393321, - 1.347384306, - 1.278934301, - 1.366632653, - 1.351190476, - 0.9882674144, - 1.1898543, - 1.351190476, - 1.169490045, - 1.292760618, - 1.638087084, - 1.436400686, - 1.328670635, - 1.242393321, - 1.355018211, - 1.303328143, - 1.186901808, - 1.112375415, - 1.432100086, - 1.1898543, - 1.324990107, - 1.074879615, - 1.214013778, - 1.20483987, - 1.158163265, - 1.112375415, - 1.220207726, - 1.402702137, - 1.268757105, - 1.282363079, - 1.289276088, - 1.292760618, - 1.183963932, - 1.252150337, - 1.42782516, - 1.292760618, - 1.026440834, - 1.268757105, - 1.268757105, - 1.285810292, - 1.347384306, - 1.272131459, - 1.220207726, - 1.296264034, - 1.25543682, - 1.494754464, - 1.347384306, - 1.214013778, - 1.169490045, - 1.147053786, - 1.082175178, - 1.109794498, - 1.382431874, - 1.24562872, - 1.201812635, - 1.328670635, - 1.122820255, - 1.220207726, - 1.192821518, - 1.563141923, - 1.41935142, - 1.336093376, - 1.406827731, - 1.258740602, - 1.186901808, - 1.232787187, - 1.107225529, - ], - -} -# Felt: 8,9,10,11,12,13 -# Hardwood 1-7, 14,15 - -class ContrastTwoGraphs(SimpleVelocityGraph): - CONFIG = { - "velocities1_index" : 13, - "velocities2_index" : 14, - "x_min" : -1, - "x_max" : 10, - "y_min" : -0.25, - "y_max" : 2, - "x_axis_label" : "", - "y_axis_label" : "", - "x_labeled_nums" : [], - "y_labeled_nums" : [], - "y_tick_frequency" : 0.25, - "x_tick_frequency" : 12, - "moving_average_n" : 20, - } - def construct(self): - self.setup_axes() - velocities1 = ALL_VELOCITIES[self.velocities1_index] - velocities2 = ALL_VELOCITIES[self.velocities2_index] - - self.n_data_points = len(velocities1) - graph1 = self.get_velocity_graph(velocities1) - graph2 = self.get_velocity_graph(velocities2) - smoothed_graph1 = self.get_smoothed_velocity_graph(velocities1) - smoothed_graph2 = self.get_smoothed_velocity_graph(velocities2) - for graph in graph1, smoothed_graph1: - self.color_graph(graph) - for graph in graph2, smoothed_graph2: - self.color_graph(graph, BLUE, RED) - for graph in graph1, graph2, smoothed_graph1, smoothed_graph2: - graph.axes = self.axes.deepcopy() - graph.add_to_back(graph.axes) - - lower_left = self.axes.get_corner(DOWN+LEFT) - self.remove(self.axes) - - felt = TextMobject("Felt") - hardwood = TextMobject("Hardwood") - hardwood.set_color(RED) - words = VGroup(felt, hardwood) - - self.force_skipping() - - #Show jaggediness - graph1.scale(0.5).to_edge(UP) - graph2.scale(0.5).to_edge(DOWN) - felt.next_to(graph1, LEFT, buff = 0.75) - hardwood.next_to(graph2, LEFT, buff = 0.75) - - self.play( - ShowCreation(graph1, run_time = 3, rate_func=linear), - Write(felt) - ) - self.play( - ShowCreation(graph2, run_time = 4, rate_func=linear), - Write(hardwood) - ) - self.wait() - - for g, sg in (graph1, smoothed_graph1), (graph2, smoothed_graph2): - sg_copy = sg.deepcopy() - sg_copy.scale(0.5) - sg_copy.shift(g.get_center()) - mover = VGroup(*it.chain(*list(zip(g.dots, g.lines)))) - target = VGroup(*it.chain(*list(zip(sg_copy.dots, sg_copy.lines)))) - for m, t in zip(mover, target): - m.target = t - self.play(LaggedStartMap( - MoveToTarget, mover, - rate_func = lambda t : 0.3*wiggle(t), - run_time = 3, - lag_ratio = 0.2, - )) - twists = TextMobject("Twists?") - variable_distances = TextMobject("Variable distances") - for word in twists, variable_distances: - word.to_corner(UP+RIGHT) - self.play(Write(twists)) - self.wait() - self.play(ReplacementTransform(twists, variable_distances)) - self.wait(3) - self.play(FadeOut(variable_distances)) - - self.revert_to_original_skipping_status() - self.play( - graph1.scale, 2, - graph1.move_to, lower_left, DOWN+LEFT, - graph2.scale, 2, - graph2.move_to, lower_left, DOWN+LEFT, - FadeOut(words) - ) - self.wait() - return - - #Show moving averages - self.play(FadeOut(graph2)) - - dots = graph1.dots - dot1, dot2 = dots[21], dots[41] - rect = Rectangle( - width = dot2.get_center()[0] - dot1.get_center()[0], - height = FRAME_Y_RADIUS - self.x_axis.get_center()[1], - stroke_width = 0, - fill_color = YELLOW, - fill_opacity = 0.5 - ) - rect.move_to(dot2.get_center(), RIGHT) - rect.to_edge(UP, buff = 0) - pre_rect = rect.copy() - pre_rect.stretch_to_fit_width(0) - pre_rect.move_to(rect, RIGHT) - - arrow = Vector(DOWN) - arrow.next_to(dot2, UP, SMALL_BUFF) - - self.play(GrowArrow(arrow)) - self.play( - dot2.shift, MED_SMALL_BUFF*UP, - dot2.set_color, PINK, - rate_func = wiggle - ) - self.wait() - self.play( - FadeOut(arrow), - ReplacementTransform(pre_rect, rect), - ) - self.wait() - self.play( - Transform(graph1, smoothed_graph1, run_time = 2), - Animation(rect) - ) - self.play(FadeOut(rect)) - self.wait() - self.play(FadeIn(graph2)) - self.play(Transform(graph2, smoothed_graph2)) - - felt.next_to(dot2, UP, MED_LARGE_BUFF) - hardwood.next_to(felt, DOWN, LARGE_BUFF) - - self.play( - LaggedStartMap(FadeIn, felt), - LaggedStartMap(FadeIn, hardwood), - run_time = 1 - ) - self.wait() - - #Compare regions - dot_group1 = VGroup( - *graph1.dots[35:75] + graph2.dots[35:75] - ) - dot_group2 = VGroup( - *graph1.dots[75:] + graph2.dots[75:] - ) - dot_group3 = VGroup( - *graph1.dots[35:] + graph2.dots[35:] - ) - rect1 = SurroundingRectangle(dot_group1) - rect2 = SurroundingRectangle(dot_group2) - rect3 = SurroundingRectangle(dot_group3) - - self.play(ShowCreation(rect1)) - for x in range(2): - self.play(LaggedStartMap( - ApplyMethod, dot_group1, - lambda m : (m.scale_in_place, 0.5), - rate_func = wiggle, - lag_ratio = 0.05, - run_time = 3, - )) - self.wait() - self.play(ReplacementTransform(rect1, rect2)) - for x in range(2): - self.play(LaggedStartMap( - ApplyMethod, dot_group2, - lambda m : (m.scale_in_place, 0.5), - rate_func = wiggle, - lag_ratio = 0.05, - run_time = 3, - )) - self.wait() - self.play(ReplacementTransform(rect1, rect3)) - for x in range(2): - self.play(LaggedStartMap( - ApplyMethod, dot_group3, - lambda m : (m.scale_in_place, 0.5), - rate_func = wiggle, - lag_ratio = 0.05, - run_time = 3, - )) - self.wait() - - - ### - - def color_graph(self, graph, color1 = BLUE, color2 = WHITE, n_starts = 20): - graph.set_color(color2) - VGroup(*graph.dots[:11] + graph.lines[:10]).set_color(color1) - - def get_smoothed_velocity_graph(self, velocities): - n = self.moving_average_n - smoothed_vs = np.array([ - np.mean(velocities[max(0, i-n):i]) - for i in range(len(velocities)) - ]) - return self.get_velocity_graph(smoothed_vs) - - def get_velocity_graph(self, velocities): - n = len(velocities) - dots = VGroup(self.get_dot(1, velocities[0])) - lines = VGroup() - for x in range(1, n): - dots.add(self.get_dot(x+1, velocities[x])) - lines.add(Line( - dots[-2].get_center(), - dots[-1].get_center(), - )) - graph = VGroup(dots, lines) - graph.dots = dots - graph.lines = lines - - return graph - - def get_dot(self, x, y): - dot = Dot(radius = 0.05) - dot.move_to(self.coords_to_point( - x * float(self.x_max) / self.n_data_points, y - )) - return dot - - - - -class ShowAllSteadyStateVelocities(SimpleVelocityGraph): - CONFIG = { - "x_axis_label" : "\\text{Domino spacing}", - "x_min" : 0, - "x_max" : 40, - "x_axis_width" : 9, - "x_tick_frequency" : 5, - "x_labeled_nums" : list(range(0, 50, 10)), - "y_min" : 0, - "y_max" : 400, - "y_labeled_nums" : [], - # "y_labeled_nums" : range(200, 1400, 200), - } - def construct(self): - self.setup_axes() - for index in range(1, 20): - index_str = ("%.2f"%float(0.01*index))[-2:] - data_file = "data%s.txt"%index_str - self.init_data(data_file) - color = WHITE if self.friction == "low" else BLUE - label = TextMobject( - index_str, - color = color - ) - label.scale(0.5) - label.set_color(color) - - dot = Dot(color = color) - dot.scale(0.5) - dot.move_to(self.coords_to_point( - self.domino_spacing, self.velocities[-1] - 400 - )) - label.next_to( - dot, - random.choice([LEFT, RIGHT]), - SMALL_BUFF - ) - self.add(dot) - self.add(label) - print(index_str, self.velocities[-1], self.friction) - -class Test(Scene): - def construct(self): - shift_val = 1.5 - - domino1 = Rectangle( - width = 0.5, height = 3, - stroke_width = 0, - fill_color = GREEN, - fill_opacity = 1 - ) - domino1.shift(LEFT) - domino2 = domino1.copy() - domino2.set_fill(BLUE_E) - domino2.shift(shift_val*RIGHT) - spacing = shift_val - domino2.get_width() - dominos = VGroup(domino1, domino2) - for domino in dominos: - line = DashedLine(domino.get_left(), domino.get_right()) - dot = Dot(domino.get_center()) - domino.add(line, dot) - - arc1 = Arc( - radius = domino1.get_height(), - start_angle = np.pi/2, - angle = -np.arcsin(spacing / domino1.get_height()) - ) - arc1.shift(domino1.get_corner(DOWN+RIGHT)) - arc2 = Arc( - radius = domino1.get_height()/2, - start_angle = np.pi/2, - angle = -np.arcsin(2*spacing/domino1.get_height()) - ) - arc2.shift(domino1.get_right()) - arc2.set_color(BLUE) - arcs = VGroup(arc1, arc2) - for arc, vect in zip(arcs, [DOWN+RIGHT, RIGHT]): - arc_copy = arc.copy() - point = domino1.get_bounding_box_point(vect) - arc_copy.add_line_to([point]) - arc_copy.set_stroke(width = 0) - arc_copy.set_fill( - arc.get_stroke_color(), - 0.2, - ) - self.add(arc_copy) - - domino1_ghost = domino1.copy() - domino1_ghost.fade(0.8) - self.add(domino1_ghost, dominos, arcs) - - self.play(Rotate( - domino1, - angle = arc1.angle, - about_point = domino1.get_corner(DOWN+RIGHT), - rate_func = there_and_back, - run_time = 3, - )) - self.play(Rotate( - domino1, - angle = arc2.angle, - about_point = domino1.get_right(), - rate_func = there_and_back, - run_time = 3, - )) - - print(arc1.angle, arc2.angle) - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/dominos/data01.txt b/from_3b1b/old/dominos/data01.txt deleted file mode 100644 index 551eb0c0..00000000 --- a/from_3b1b/old/dominos/data01.txt +++ /dev/null @@ -1,19 +0,0 @@ -framerate 4000 -domino thickness 7.5438 -domino spacing 9.38 -friction low -1910 -2099 -2297 -2404 -2488 -2575 twist -2665 -2751 -2829 -2891 -2961 -3043 -3118 -3184 -3259 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data02.txt b/from_3b1b/old/dominos/data02.txt deleted file mode 100644 index 4a25ace5..00000000 --- a/from_3b1b/old/dominos/data02.txt +++ /dev/null @@ -1,63 +0,0 @@ -framerate 4000 -domino thickness 7.5438 -domino spacing 9.38 -friction low -959 -1087 -1210 -1283 -1357 -1430 -1510 -1565 -1630 -1695 twist -1756 -1810 -1869 -1932 -1976 twist -2025 twist -2083 -2141 -2198 -2242 -2305 -2347 twist -2387 twist -2440 twist -2492 twist -2552 twist -2610 twist -2667 -2720 -2770 -2854 larger gap between dominos -2900 -2947 -2992 -3041 twist -3097 -3154 -3212 -3260 -3308 -3363 -3418 -3473 -3520 -3569 -3625 -3680 -3739 -3791 -3850 -3900 -3952 -4008 -4065 -4121 -4179 -4222 -4274 -4329 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data03.txt b/from_3b1b/old/dominos/data03.txt deleted file mode 100644 index 5a4ff65c..00000000 --- a/from_3b1b/old/dominos/data03.txt +++ /dev/null @@ -1,63 +0,0 @@ -framerate 4000 -domino thickness 7.5438 -domino spacing 9.38 -friction low -2518 -2675 -2801 -2905 -2980 -3054 -3150 -3220 -3290 -3350 -3404 -3479 -3545 -3598 -3648 -3710 -3770 -3830 -3892 -3942 -3990 -4040 twist -4090 twist -4142 twist -4186 twist -4241 twist -4302 twist -4355 twist -4422 -4481 -4550 -4595 -4651 -4701 twist -4766 twist -4817 -4888 -4950 -5001 -5067 -5130 -5178 -5236 -5294 -5343 -5394 -5456 twist -5518 -5573 -5635 -5698 -5760 -5804 -5863 -5925 -5981 -6021 -6080 -6120 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data04.txt b/from_3b1b/old/dominos/data04.txt deleted file mode 100644 index d7dde2d5..00000000 --- a/from_3b1b/old/dominos/data04.txt +++ /dev/null @@ -1,63 +0,0 @@ -framerate 4000 -domino thickness 7.5438 -domino spacing 9.38 -friction low -2295 -2425 -2551 -2637 -2760 -2844 -2924 -2995 -3047 -3112 -3165 -3245 -3300 -3357 -3408 -3469 -3525 -3579 -3626 twist -3672 twist -3727 twist -3791 -3848 -3904 -3965 -4018 -4076 -4134 -4179 -4231 -4310 -4358 -4411 -4465 -4514 -4571 -4618 -4673 -4734 -4793 - -4881 -4935 -4987 -5039 -5091 -5155 -5207 -5249 -5303 twist -5358 -5420 -5473 -5527 -5589 -5650 -5704 -5772 -5814 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data05.txt b/from_3b1b/old/dominos/data05.txt deleted file mode 100644 index 48239213..00000000 --- a/from_3b1b/old/dominos/data05.txt +++ /dev/null @@ -1,125 +0,0 @@ -framerate 4000 -domino thickness 7.5438 -domino spacing 9.38 -friction low -309 -430 twist -521 twist -580 twist -626 twist -675 twist -717 twist -756 twist -795 twist -839 twist -885 twist -933 twist -983 twist -1041 twist -1092 -1143 -1197 -1242 twist -1291 twist -1339 twist -1398 twist -1448 twist -1506 twist -1560 twist -1612 -1650 -1697 -1747 other (double bounce) -1798 other (double bounce) -1862 -1922 -1979 -2021 -2074 -2130 -2180 -2237 -2295 -2358 -2395 -2465 offscreen -2520 -2570 -2622 -2676 -2730 -2770 -2831 -2881 -2919 -2978 -3024 -3077 -3128 -3195 -3248 -3310 -3352 -3396 -3450 -3508 -3561 -3618 -3682 -3740 -3787 -3835 -3898 -3950 -4008 -4063 -4118 -4173 -4228 -4278 -4330 -4387 -4432 -4490 -4540 -4589 -4635 -4692 -4746 -4797 -4856 -4916 -4965 -5019 -5083 -5139 -5187 -5225 -5285 -5329 -5385 -5440 -5494 -5544 other (There's an extra purple and then a clear between the black and this green) -5599 -5648 -5693 -5749 -5709 -5856 -5911 -5965 -6013 -6065 -6122 -6181 -6235 -6283 -6332 -6388 -6440 -6497 -6541 twist -6590 -6646 twist -6703 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data06.txt b/from_3b1b/old/dominos/data06.txt deleted file mode 100644 index c16b47cd..00000000 --- a/from_3b1b/old/dominos/data06.txt +++ /dev/null @@ -1,127 +0,0 @@ -framerate 4000 -domino thickness 7.5438 -domino spacing 9.38 -friction low -852 -1072 -1240 -1359 twist -1457 -1533 twist -1603 -1661 -1735 -1785 -1836 -1893 -1950 -2011 -2058 -2105 -2154 -2208 -2266 -2318 -2371 -2424 -2471 -2525 -2579 -2629 -2678 -2721 twist -2776 twist -2846 twist -2896 -2947 -2997 twist -3047 -3096 -3154 -3201 -3276 low-hitting impact -3323 -3371 -3423 -3466 -3509 -3556 -3611 -3667 -3733 low-hitting impact -3778 -3843 unclear -3893 unclear -3948 unclear -4000 -4065 low-hitting impact -4115 -4160 -4209 -4255 -4306 -4355 -4412 -4472 -4526 -4572 unclear -4632 -4686 -4751 low-hitting impact -4805 low-hitting impact -4862 offscreen -4898 twist -4962 -5017 -5065 -5109 -5161 -5207 offscreen -5249 low-hitting impact -5305 offscreen -5360 offscreen -5435 offscreen -5471 offscreen -5528 unclear -5596 offscreen -5641 offscreen - offscreen - offscreen - offscreen - offscreen - offscreen - offscreen - offscreen - offscreen - offscreen - offscreen - offscreen - offscreen - offscreen -6346 -6395 -6454 -6519 -6578 -6644 -6695 -6741 -6798 -6851 -6905 -6971 -7024 -7074 -7122 -7174 -7223 -7272 -7331 -7382 -7434 -7478 -7519 -7565 -7607 twist -7661 twist -7724 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data07.txt b/from_3b1b/old/dominos/data07.txt deleted file mode 100644 index 4d947208..00000000 --- a/from_3b1b/old/dominos/data07.txt +++ /dev/null @@ -1,127 +0,0 @@ -framerate 1000 -domino thickness 7.5438 -domino spacing 9.38 -friction low -347 twist -383 twist -424 -445 -473 -499 -530 -551 -569 -588 -604 -620 -635 -648 -661 twist -673 twist -685 -699 -712 twist -725 twist -739 twist -753 -765 -778 -791 -805 -818 -832 -847 -864 -876 twist -886 -897 -909 twist -921 twist -934 twist -947 twist -962 twist -976 twist -992 -1006 -1021 -1034 -1047 twist -1060 twist -1074 twist -1088 twist -1106 -1118 -1128 -1142 twist -1155 twist -1169 -1182 -1196 -1209 -1222 -1236 -1248 -1263 -1272 -1283 -1296 -1309 -1324 -1342 -1351 -1363 -1375 -1388 -1402 -1416 -1429 -1442 -1456 -1469 -1483 -1497 -1511 -1525 -1537 -1549 -1562 -1574 -1587 -1598 -1610 -1623 twist -1637 twist -1650 -1664 -1678 -1693 -1705 -1719 -1733 -1746 twist -1760 -1769 -1782 -1798 -1808 -1820 -1831 -1843 -1858 -1870 -1884 twist -1895 twist -1911 -1924 -1938 -1953 -1962 -1975 -1989 -2002 -2015 -2029 -2043 -2058 -2070 -2084 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data08.txt b/from_3b1b/old/dominos/data08.txt deleted file mode 100644 index a7e18ceb..00000000 --- a/from_3b1b/old/dominos/data08.txt +++ /dev/null @@ -1,45 +0,0 @@ -framerate 3200 -domino thickness 7.5438 -domino spacing 9.38 -friction high -2713 -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -3367 -3424 -3455 -3503 -3561 -3600 -3650 -3696 -3735 -3775 -3815 -3865 -3906 -3941 twist -3968 twist -4007 twist -4041 twist -4081 twist -4123 twist -4158 -4217 -4243 -4276 -4309 -4367 -4413 -4458 -4499 -4543 -4583 -4618 -4650 -4693 -4732 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data09.txt b/from_3b1b/old/dominos/data09.txt deleted file mode 100644 index 01711e55..00000000 --- a/from_3b1b/old/dominos/data09.txt +++ /dev/null @@ -1,48 +0,0 @@ -framerate 5000 -domino thickness 7.5438 -domino spacing 9.38 -friction high -1173 -1270 -1322 twist -1389 twist -1445 twist -1521 twist -1583 twist -1657 -1715 -1776 -1837 -1895 -1956 twist -2037 -2091 -2148 -2204 twist -2264 twist -2326 twist -2383 twist -2462 -2523 -2587 twist -2651 -2733 -2789 twist -2850 -2906 -2966 -3024 -3082 -3149 -3206 -3266 -3331 -3405 -3461 -3524 -3590 -3655 -3730 -3788 -3850 -3913 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data10.txt b/from_3b1b/old/dominos/data10.txt deleted file mode 100644 index 54d8b734..00000000 --- a/from_3b1b/old/dominos/data10.txt +++ /dev/null @@ -1,97 +0,0 @@ -framerate 5000 -domino thickness 7.5438 -domino spacing 9.38 -friction high -6212 -6433 -6643 -6737 -6818 -6914 -6992 -7079 -7150 -7239 -7293 twist -7352 twist -7414 twist -7478 twist -7549 twist -7616 twist -7683 -7763 -7815 -7868 -7929 -7994 -8057 -8125 -8187 -8238 -8309 -8371 -8440 -8493 -8559 -8624 -8693 -8754 -8802 -8868 -8936 -9009 -9065 -9110 -9169 -9242 -9308 -9389 -9442 -9501 -9582 -9637 -9708 -9774 -9821 -9884 -9946 -10005 -10070 -10152 -10193 -10254 -10322 -10384 -10449 -10527 -10595 -10640 -10721 -10776 -10826 -10890 -10956 -11022 -11102 -11153 -11214 -11274 twist -11342 -11423 -11483 -11539 -11593 -11663 -11732 -11781 -11852 -11920 -11980 -12046 -12114 -12160 -12221 -12278 -12354 -12397 -12462 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data11.txt b/from_3b1b/old/dominos/data11.txt deleted file mode 100644 index 97b9fbd8..00000000 --- a/from_3b1b/old/dominos/data11.txt +++ /dev/null @@ -1,60 +0,0 @@ -framerate 5000 -domino thickness 7.5438 -domino spacing 9.38 -friction high -1881 -2072 -2285 -2477 -2648 -2766 -2851 -2950 -3032 -3118 -3185 -3265 -3316 -3372 -3441 -3498 -3566 -3642 -3712 -3764 -3813 -3886 - -4019 -4074 twist -4112 twist -4170 twist -4232 twist -4306 twist -4370 -4485 -4535 -4616 -4673 -4730 -4793 -4861 -4928 -4981 -5049 -5113 -5173 -5245 -5310 -5361 -5434 -5501 -5528 twist -5603 -5681 -5750 -5812 -5857 -5935 -6000 -6023 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data12.txt b/from_3b1b/old/dominos/data12.txt deleted file mode 100644 index a8acedd1..00000000 --- a/from_3b1b/old/dominos/data12.txt +++ /dev/null @@ -1,47 +0,0 @@ -framerate 5000 -domino thickness 7.5438 -domino spacing 9.38 -friction high -984 -1083 -1165 -1255 -1337 -1441 -1550 -1627 -1698 -1793 -1851 -1937 -2006 -2061 bounce -2117 bounce -2202 -2259 -2323 -2380 -2434 twist -2484 twist -2549 twist -2611 twist -2685 twist -2751 -2819 -2880 -2943 -3007 -3074 -3151 -3219 -3282 unclear -3332 unclear -3381 -3427 -3485 -3555 -3628 -3713 -3787 -3853 -3923 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data13.txt b/from_3b1b/old/dominos/data13.txt deleted file mode 100644 index 9e6b4106..00000000 --- a/from_3b1b/old/dominos/data13.txt +++ /dev/null @@ -1,95 +0,0 @@ -framerate 5000 -domino thickness 7.5438 -domino spacing 9.38 -friction high -2362 -2688 unclear -2904 -3050 -3184 -3303 -3403 -3478 -3564 -3620 -3710 -3770 -3851 -3898 -3977 -4035 -4118 -4149 -4215 -4266 -4331 -4389 -4455 -4528 -4596 -4657 -4722 -4766 -4836 -4935 -4980 -5061 -5100 -5156 -5219 -5279 -5341 -5415 -5472 -5537 -5614 -5674 -5723 -5792 -5854 -5939 -5992 -6034 -6088 -6157 -6219 -6293 -6366 -6415 -6479 -6536 -6601 -6670 -6737 -6797 -6871 -6946 -7001 -7062 -7119 -7192 -7238 -7293 -7350 -7422 -7490 -7552 -7613 -7675 -7745 -7812 -7871 -7937 -7996 -8061 -8117 -8184 -8251 -8320 -8375 -8430 -8498 -8554 -8619 -8688 -8737 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data14.txt b/from_3b1b/old/dominos/data14.txt deleted file mode 100644 index ea51b5d5..00000000 --- a/from_3b1b/old/dominos/data14.txt +++ /dev/null @@ -1,94 +0,0 @@ -framerate 5000 -domino thickness 7.5438 -domino spacing 8.959989562 -friction low -3344 First two discarded (fell offscreen) -3481 -3615 -3733 -3829 -3902 twist -3989 twist -4054 -4121 -4192 -4249 -4318 -4378 -4449 -4512 -4570 twist -4629 twist -4694 twist -4745 -4818 -4886 -4955 -5030 -5087 -5144 twist -5206 twist -5270 -5337 -5420 -5475 -5535 -5586 twist -5642 twist -5710 twist -5776 -5846 -5902 -5971 -6039 -6109 -6179 -6240 -6299 twist -6355 -6428 -6493 -6556 -6618 -6673 -6740 -6808 -6878 -6942 -7014 -7075 -7140 twist -7204 -7271 -7341 -7419 -7490 -7556 -7615 -7678 -7731 twist -7789 twist -7851 twist -7916 twist -7986 -8059 -8115 -8170 -8229 -8292 -8357 -8417 -8485 -8535 -8587 twist -8648 twist -8711 twist -8767 twist -8843 -8922 -8994 -9055 -9112 -9171 -9234 -9304 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data15.txt b/from_3b1b/old/dominos/data15.txt deleted file mode 100644 index 2d5ef515..00000000 --- a/from_3b1b/old/dominos/data15.txt +++ /dev/null @@ -1,96 +0,0 @@ -framerate 5000 -domino thickness 7.5438 -domino spacing 9.38 -friction low -3581 -3660 -3725 -3775 -3833 -3898 -3945 -4001 twist -4079 -4159 -4230 -4300 -4358 -4421 -4485 -4545 -4610 -4670 -4733 -4800 -4868 -4941 -5006 -5076 -5147 -5212 -5272 -5331 -5392 -5452 -5530 -5600 -5659 -5725 -5787 twist -5840 twist -5891 twist -5951 -6016 -6075 -6137 -6204 -6275 -6331 -6398 -6460 -6532 -6600 -6661 -6732 -6808 -6870 -6926 -6990 -7050 -7115 -7177 -7243 -7310 -7364 -7424 twist -7502 -7564 -7629 -7690 -7750 -7813 -7878 -7938 -8003 -8056 -8118 -8176 -8245 -8316 -8389 -8464 -8522 -8584 -8651 -8713 -8780 -8850 -8914 -8966 -9022 -9082 -9137 -9200 -9267 -9333 -9404 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data16.txt b/from_3b1b/old/dominos/data16.txt deleted file mode 100644 index 0dc2837f..00000000 --- a/from_3b1b/old/dominos/data16.txt +++ /dev/null @@ -1,98 +0,0 @@ -framerate 5000 -domino thickness 7.5438 -domino spacing 43.2562 -friction low -866 -1239 -1578 -1810 -2031 -2241 twist -2447 -2657 -2879 low-hitting impact -3151 twist -3458 twist -3810 twist -4155 -4402 -4670 low-hitting impact -5017 -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -11767 -12076 twist -12410 twist -12758 -13012 -13256 -13593 twist -13948 low-hitting impact -14276 -14592 low-hitting impact -14972 -15345 -15600 -15840 twist -16158 -16469 twist -16831 other (type) -17202 low-hitting impact -17705 unclear / other -18684 twist -19118 -19415 -19656 -19877 -20112 -20320 twist -20615 -20856 low-hitting impact -21207 twist -21530 -21906 -22269 -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -28742 -29273 -29575 -29830 twist -30072 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data17.txt b/from_3b1b/old/dominos/data17.txt deleted file mode 100644 index c6cf78c5..00000000 --- a/from_3b1b/old/dominos/data17.txt +++ /dev/null @@ -1,33 +0,0 @@ -framerate 5000 -domino thickness 7.5438 -domino spacing 43.2562 -friction low -2163 -2563 -2875 twist -3137 -3384 twist -3708 -4014 -4257 -4503 -4763 -5029 unclear -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -X offscreen -7534 unclear -7838 unclear -8166 -8404 twist -8653 twist -X offscreen -X offscreen -X offscreen -X offscreen -10188 twist -10837 twist \ No newline at end of file diff --git a/from_3b1b/old/dominos/data18.txt b/from_3b1b/old/dominos/data18.txt deleted file mode 100644 index 0d6c428b..00000000 --- a/from_3b1b/old/dominos/data18.txt +++ /dev/null @@ -1,99 +0,0 @@ -framerate 5000 -domino thickness 7.5438 -domino spacing 43.2562 -friction low -3295 -3703 -3994 -4227 -4480 twist -4704 twist -4997 twist -5276 -5539 -5996 unclear - -X -X -X -X -X -X -X -X -X -X -X -X -X -X -10818 unclear -11083 -11395 -11715 -11999 -12315 -12670 -12966 low-hitting impact -13293 -13588 -13943 -14296 -14541 -14820 -15080 -15306 -15631 -15954 -16279 -16573 low-hitting impact -17057 -17429 -17687 -17947 -18252 -18575 -18952 -19314 -19638 -19941 low-hitting impact -20308 -20663 -20911 -21137 -21380 -21605 -21842 -X -X -X -X -X -X -X -X -X -X -X -X -25440 unclear -25784 -26133 -26393 -26666 -26949 -27233 -27641 -28008 -28282 -28529 -28794 -29059 -29442 -29861 -30114 -30351 -30614 -30864 low-hitting impact -31202 twist -31530 \ No newline at end of file diff --git a/from_3b1b/old/dominos/data19.txt b/from_3b1b/old/dominos/data19.txt deleted file mode 100644 index 814943ca..00000000 --- a/from_3b1b/old/dominos/data19.txt +++ /dev/null @@ -1,74 +0,0 @@ -framerate 5000 -domino thickness 7.5438 -domino spacing 15.0876 -friction low -2741 -3018 -3171 -3315 -3403 -3536 -3654 -3742 -3842 -3927 -4024 -4133 -4220 -4330 -4424 -4528 -4617 -4717 -4816 -4908 -5013 -5123 -5232 -5315 -5426 -5522 -5626 -5725 -5808 -5916 -6021 -6125 -6219 -6332 -6416 -6512 -6635 -6724 -6829 -6927 -7021 -7131 -7230 -7320 -7420 -7525 -7633 -7713 -7794 -7903 -8020 -8111 -8215 -8319 -8416 -8510 -8612 -8708 -8820 -8917 -9022 -9101 -9188 -9291 -9405 -9530 -9609 -9695 -9790 -9906 \ No newline at end of file diff --git a/from_3b1b/old/efvgt.py b/from_3b1b/old/efvgt.py deleted file mode 100644 index 56880d53..00000000 --- a/from_3b1b/old/efvgt.py +++ /dev/null @@ -1,3400 +0,0 @@ -from manimlib.imports import * - -ADDER_COLOR = GREEN -MULTIPLIER_COLOR = YELLOW - -def normalize(vect): - norm = get_norm(vect) - if norm == 0: - return OUT - else: - return vect/norm - -def get_composite_rotation_angle_and_axis(angles, axes): - angle1, axis1 = 0, OUT - for angle2, axis2 in zip(angles, axes): - ## Figure out what (angle3, axis3) is the same - ## as first applying (angle1, axis1), then (angle2, axis2) - axis2 = normalize(axis2) - dot = np.dot(axis2, axis1) - cross = np.cross(axis2, axis1) - angle3 = 2*np.arccos( - np.cos(angle2/2)*np.cos(angle1/2) - \ - np.sin(angle2/2)*np.sin(angle1/2)*dot - ) - axis3 = ( - np.sin(angle2/2)*np.cos(angle1/2)*axis2 + \ - np.cos(angle2/2)*np.sin(angle1/2)*axis1 + \ - np.sin(angle2/2)*np.sin(angle1/2)*cross - ) - axis3 = normalize(axis3) - angle1, axis1 = angle3, axis3 - - if angle1 > np.pi: - angle1 -= 2*np.pi - return angle1, axis1 - -class ConfettiSpiril(Animation): - CONFIG = { - "x_start" : 0, - "spiril_radius" : 0.5, - "num_spirils" : 4, - "run_time" : 10, - "rate_func" : None, - } - def __init__(self, mobject, **kwargs): - digest_config(self, kwargs) - mobject.next_to(self.x_start*RIGHT + FRAME_Y_RADIUS*UP, UP) - self.total_vert_shift = \ - FRAME_HEIGHT + mobject.get_height() + 2*MED_SMALL_BUFF - - Animation.__init__(self, mobject, **kwargs) - - def interpolate_submobject(self, submobject, starting_submobject, alpha): - submobject.points = np.array(starting_submobject.points) - - def interpolate_mobject(self, alpha): - Animation.interpolate_mobject(self, alpha) - angle = alpha*self.num_spirils*2*np.pi - vert_shift = alpha*self.total_vert_shift - - start_center = self.mobject.get_center() - self.mobject.shift(self.spiril_radius*OUT) - self.mobject.rotate(angle, axis = UP, about_point = start_center) - self.mobject.shift(vert_shift*DOWN) - -def get_confetti_animations(num_confetti_squares): - colors = [RED, YELLOW, GREEN, BLUE, PURPLE, RED] - confetti_squares = [ - Square( - side_length = 0.2, - stroke_width = 0, - fill_opacity = 0.75, - fill_color = random.choice(colors), - ) - for x in range(num_confetti_squares) - ] - confetti_spirils = [ - ConfettiSpiril( - square, - x_start = 2*random.random()*FRAME_X_RADIUS - FRAME_X_RADIUS, - rate_func = squish_rate_func(lambda t : t, a, a+0.5) - ) - for a, square in zip( - np.linspace(0, 0.5, num_confetti_squares), - confetti_squares - ) - ] - return confetti_spirils - -class Anniversary(TeacherStudentsScene): - CONFIG = { - "num_confetti_squares" : 50, - "message": "2 year Anniversary!", - } - def construct(self): - self.celebrate() - self.complain() - - def celebrate(self): - title = TextMobject(self.message) - title.scale(1.5) - title.to_edge(UP) - - first_video = Rectangle( - height = 2, width = 2*(16.0/9), - stroke_color = WHITE, - fill_color = "#111111", - fill_opacity = 0.75, - ) - first_video.next_to(self.get_teacher(), UP+LEFT) - first_video.shift(RIGHT) - formula = TexMobject("e^{\\pi i} = -1") - formula.move_to(first_video) - first_video.add(formula) - first_video.fade(1) - - hats = self.get_party_hats() - confetti_spirils = get_confetti_animations( - self.num_confetti_squares - ) - self.play( - Write(title, run_time = 2), - *[ - ApplyMethod(pi.change_mode, "hooray") - for pi in self.get_pi_creatures() - ] - ) - self.play( - DrawBorderThenFill( - hats, - lag_ratio = 0.5, - rate_func=linear, - run_time = 2, - ), - *confetti_spirils + [ - Succession( - Animation(pi, run_time = 2), - ApplyMethod(pi.look, UP+LEFT), - ApplyMethod(pi.look, UP+RIGHT), - Animation(pi), - ApplyMethod(pi.look_at, first_video), - rate_func=linear - ) - for pi in self.get_students() - ] + [ - Succession( - Animation(self.get_teacher(), run_time = 2), - Blink(self.get_teacher()), - Animation(self.get_teacher(), run_time = 2), - ApplyMethod(self.get_teacher().change_mode, "raise_right_hand"), - rate_func=linear - ), - DrawBorderThenFill( - first_video, - run_time = 10, - rate_func = squish_rate_func(smooth, 0.5, 0.7) - ) - ] - ) - self.change_student_modes(*["confused"]*3) - - def complain(self): - self.student_says( - "Why were you \\\\ talking so fast?", - student_index = 0, - target_mode = "sassy", - ) - self.change_student_modes(*["sassy"]*3) - self.play(self.get_teacher().change_mode, "shruggie") - self.wait(2) - - def get_party_hats(self): - hats = VGroup(*[ - PartyHat( - pi_creature = pi, - height = 0.5*pi.get_height() - ) - for pi in self.get_pi_creatures() - ]) - max_angle = np.pi/6 - for hat in hats: - hat.rotate( - random.random()*2*max_angle - max_angle, - about_point = hat.get_bottom() - ) - return hats - -class HomomophismPreview(Scene): - def construct(self): - raise Exception("Meant as a place holder, not to be excecuted") - -class WatchingScreen(PiCreatureScene): - CONFIG = { - "screen_height" : 5.5 - } - def create_pi_creatures(self): - randy = Randolph().to_corner(DOWN+LEFT) - return VGroup(randy) - - def construct(self): - screen = Rectangle(height = 9, width = 16) - screen.set_height(self.screen_height) - screen.to_corner(UP+RIGHT) - - self.add(screen) - for mode in "erm", "pondering", "confused": - self.wait() - self.change_mode(mode) - self.play(Animation(screen)) - self.wait() - -class LetsStudyTheBasics(TeacherStudentsScene): - def construct(self): - self.teacher_says("Let's learn some \\\\ group theory!") - self.change_student_modes(*["hooray"]*3) - self.wait(2) - -class JustGiveMeAQuickExplanation(TeacherStudentsScene): - def construct(self): - self.student_says( - "I ain't got \\\\ time for this!", - target_mode = "angry", - ) - self.play(*it.chain(*[ - [ - pi.change_mode, "hesitant", - pi.look_at, self.get_students()[1].eyes - ] - for pi in self.get_students()[::2] - ])) - self.wait(2) - -class QuickExplanation(ComplexTransformationScene): - CONFIG = { - "plane_config" : { - "x_line_frequency" : 1, - "y_line_frequency" : 1, - "secondary_line_ratio" : 1, - "space_unit_to_x_unit" : 1.5, - "space_unit_to_y_unit" : 1.5, - }, - "background_fade_factor" : 0.2, - "background_label_scale_val" : 0.7, - "velocity_color" : RED, - "position_color" : YELLOW, - } - def construct(self): - # self.add_transformable_plane() - self.add_equation() - self.add_explanation() - self.add_vectors() - - def add_equation(self): - equation = TexMobject( - "\\frac{d(e^{it})}{dt}", - "=", - "i", "e^{it}" - ) - equation[0].set_color(self.velocity_color) - equation[-1].set_color(self.position_color) - equation.add_background_rectangle() - brace = Brace(equation, UP) - equation.add(brace) - brace_text = TextMobject( - "Velocity vector", "is a", - "$90^\\circ$ \\\\ rotation", - "of", "position vector" - ) - brace_text[0].set_color(self.velocity_color) - brace_text[-1].set_color(self.position_color) - brace_text.add_background_rectangle() - brace_text.scale(0.8) - brace_text.to_corner(UP+LEFT, buff = MED_SMALL_BUFF) - equation.next_to(brace_text, DOWN) - - self.add_foreground_mobjects(brace_text, equation) - self.brace_text = brace_text - - def add_explanation(self): - words = TextMobject(""" - Only a walk around the unit - circle at rate 1 satisfies both - this property and e^0 = 1. - """) - words.scale(0.8) - words.add_background_rectangle() - words.to_corner(UP+RIGHT, buff = MED_SMALL_BUFF) - arrow = Arrow(RIGHT, 1.5*LEFT, color = WHITE) - arrow.to_edge(UP) - - self.add(words, arrow) - - def add_vectors(self): - right = self.z_to_point(1) - s_vector = Arrow( - ORIGIN, right, - tip_length = 0.2, - buff = 0, - color = self.position_color, - ) - - v_vector = s_vector.copy().rotate(np.pi/2) - v_vector.set_color(self.velocity_color) - circle = Circle( - radius = self.z_to_point(1)[0], - color = self.position_color - ) - - self.wait(2) - self.play(ShowCreation(s_vector)) - self.play(ReplacementTransform( - s_vector.copy(), v_vector, path_arc = np.pi/2 - )) - self.wait() - self.play(v_vector.shift, right) - self.wait() - self.vectors = VGroup(s_vector, v_vector) - - kwargs = { - "rate_func" : None, - "run_time" : 5, - } - rotation = Rotating(self.vectors, about_point = ORIGIN, **kwargs) - self.play( - ShowCreation(circle, **kwargs), - rotation - ) - self.play(rotation) - self.play(rotation) - -class SymmetriesOfSquare(ThreeDScene): - CONFIG = { - "square_config" : { - "side_length" : 2, - "stroke_width" : 0, - "fill_color" : BLUE, - "fill_opacity" : 0.75, - }, - "dashed_line_config" : {}, - } - def construct(self): - self.add_title() - self.ask_about_square_symmetry() - self.talk_through_90_degree_rotation() - self.talk_through_vertical_flip() - self.confused_by_lack_of_labels() - self.add_labels() - self.show_full_group() - self.show_top_actions() - self.show_bottom_actions() - self.name_dihedral_group() - - def add_title(self): - title = TextMobject("Groups", "$\\leftrightarrow$", "Symmetry") - title.to_edge(UP) - - for index in 0, 2: - self.play(Write(title[index], run_time = 1)) - self.play(GrowFromCenter(title[1])) - self.wait() - - self.title = title - - def ask_about_square_symmetry(self): - brace = Brace(self.title[-1]) - q_marks = brace.get_text("???") - - self.square = Square(**self.square_config) - - self.play(DrawBorderThenFill(self.square)) - self.play(GrowFromCenter(brace), Write(q_marks)) - self.rotate_square() - self.wait() - for axis in UP, UP+RIGHT: - self.flip_square(axis) - self.wait() - self.rotate_square(-np.pi) - self.wait() - self.play(*list(map(FadeOut, [brace, q_marks]))) - - def talk_through_90_degree_rotation(self): - arcs = self.get_rotation_arcs(self.square, np.pi/2) - - self.play(*list(map(ShowCreation, arcs))) - self.wait() - self.rotate_square(np.pi/2, run_time = 2) - self.wait() - self.play(FadeOut(arcs)) - self.wait() - - def talk_through_vertical_flip(self): - self.flip_square(UP, run_time = 2) - self.wait() - - def confused_by_lack_of_labels(self): - randy = Randolph(mode = "confused") - randy.next_to(self.square, LEFT, buff = LARGE_BUFF) - randy.to_edge(DOWN) - self.play(FadeIn(randy)) - for axis in OUT, RIGHT, UP: - self.rotate_square( - angle = np.pi, axis = axis, - added_anims = [randy.look_at, self.square.points[0]] - ) - self.play(Blink(randy)) - self.wait() - - self.randy = randy - - def add_labels(self): - self.add_randy_to_square(self.square) - - self.play( - FadeIn(self.square.randy), - self.randy.change_mode, "happy", - self.randy.look_at, self.square.randy.eyes - ) - self.play(Blink(self.randy)) - self.play(FadeOut(self.randy)) - - self.wait() - - def show_full_group(self): - new_title = TextMobject("Group", "of", "symmetries") - new_title.move_to(self.title) - - all_squares = VGroup(*[ - self.square.copy().scale(0.5) - for x in range(8) - ]) - all_squares.arrange(RIGHT, buff = LARGE_BUFF) - - top_squares = VGroup(*all_squares[:4]) - bottom_squares = VGroup(*all_squares[4:]) - bottom_squares.next_to(top_squares, DOWN, buff = LARGE_BUFF) - - all_squares.set_width(FRAME_WIDTH-2*LARGE_BUFF) - all_squares.center() - all_squares.to_edge(DOWN, buff = LARGE_BUFF) - - self.play(ReplacementTransform(self.square, all_squares[0])) - self.play(ReplacementTransform(self.title, new_title)) - self.title = new_title - self.play(*[ - ApplyMethod(mob.set_color, GREY) - for mob in self.title[1:] - ]) - - for square, angle in zip(all_squares[1:4], [np.pi/2, np.pi, -np.pi/2]): - arcs = self.get_rotation_arcs(square, angle, MED_SMALL_BUFF) - self.play(*list(map(FadeIn, [square, arcs]))) - square.rotation_kwargs = {"angle" : angle} - self.rotate_square(square = square, **square.rotation_kwargs) - square.add(arcs) - - for square, axis in zip(bottom_squares, [RIGHT, RIGHT+UP, UP, UP+LEFT]): - axis_line = self.get_axis_line(square, axis) - self.play(FadeIn(square)) - self.play(ShowCreation(axis_line)) - square.rotation_kwargs = {"angle" : np.pi, "axis" : axis} - self.rotate_square(square = square, **square.rotation_kwargs) - square.add(axis_line) - self.wait() - - self.all_squares = all_squares - - def show_top_actions(self): - all_squares = self.all_squares - - self.play(Indicate(all_squares[0])) - self.wait() - - self.play(*[ - Rotate( - square, - rate_func = lambda t : -there_and_back(t), - run_time = 3, - about_point = square.get_center(), - **square.rotation_kwargs - ) - for square in all_squares[1:4] - ]) - self.wait() - - def show_bottom_actions(self): - for square in self.all_squares[4:]: - self.rotate_square( - square = square, - rate_func = there_and_back, - run_time = 2, - **square.rotation_kwargs - ) - self.wait() - - def name_dihedral_group(self): - new_title = TextMobject( - "``Dihedral group'' of order 8" - ) - new_title.to_edge(UP) - - self.play(FadeOut(self.title)) - self.play(FadeIn(new_title)) - self.wait() - - ########## - - def rotate_square( - self, - angle = np.pi/2, - axis = OUT, - square = None, - show_axis = False, - added_anims = None, - **kwargs - ): - if square is None: - assert hasattr(self, "square") - square = self.square - added_anims = added_anims or [] - rotation = Rotate( - square, - angle = angle, - axis = axis, - about_point = square.get_center(), - **kwargs - ) - if hasattr(square, "labels"): - for label in rotation.target_mobject.labels: - label.rotate_in_place(-angle, axis) - - if show_axis: - axis_line = self.get_axis_line(square, axis) - self.play( - ShowCreation(axis_line), - Animation(square) - ) - self.play(rotation, *added_anims) - if show_axis: - self.play( - FadeOut(axis_line), - Animation(square) - ) - - def flip_square(self, axis = UP, **kwargs): - self.rotate_square( - angle = np.pi, - axis = axis, - show_axis = True, - **kwargs - ) - - def get_rotation_arcs(self, square, angle, angle_buff = SMALL_BUFF): - square_radius = get_norm( - square.points[0] - square.get_center() - ) - arc = Arc( - radius = square_radius + SMALL_BUFF, - start_angle = np.pi/4 + np.sign(angle)*angle_buff, - angle = angle - np.sign(angle)*2*angle_buff, - color = YELLOW - ) - arc.add_tip() - if abs(angle) < 3*np.pi/4: - angle_multiple_range = list(range(1, 4)) - else: - angle_multiple_range = [2] - arcs = VGroup(arc, *[ - arc.copy().rotate(i*np.pi/2) - for i in angle_multiple_range - ]) - arcs.move_to(square[0]) - - return arcs - - def get_axis_line(self, square, axis): - axis_line = DashedLine(2*axis, -2*axis, **self.dashed_line_config) - axis_line.replace(square, dim_to_match = np.argmax(np.abs(axis))) - axis_line.scale_in_place(1.2) - return axis_line - - def add_labels_and_dots(self, square): - labels = VGroup() - dots = VGroup() - for tex, vertex in zip("ABCD", square.get_anchors()): - label = TexMobject(tex) - label.add_background_rectangle() - label.next_to(vertex, vertex-square.get_center(), SMALL_BUFF) - labels.add(label) - dot = Dot(vertex, color = WHITE) - dots.add(dot) - square.add(labels, dots) - square.labels = labels - square.dots = dots - - def add_randy_to_square(self, square, mode = "pondering"): - randy = Randolph(mode = mode) - randy.set_height(0.75*square.get_height()) - randy.move_to(square) - square.add(randy) - square.randy = randy - -class ManyGroupsAreInfinite(TeacherStudentsScene): - def construct(self): - self.teacher_says("Many groups are infinite") - self.change_student_modes(*["pondering"]*3) - self.wait(2) - -class CircleSymmetries(Scene): - CONFIG = { - "circle_radius" : 2, - } - def construct(self): - self.add_circle_and_title() - self.show_range_of_angles() - self.associate_rotations_with_points() - - def add_circle_and_title(self): - title = TextMobject("Group of rotations") - title.to_edge(UP) - - circle = self.get_circle() - - self.play(Write(title), ShowCreation(circle, run_time = 2)) - self.wait() - angles = [ - np.pi/2, -np.pi/3, 5*np.pi/6, - 3*np.pi/2 + 0.1 - ] - angles.append(-sum(angles)) - for angle in angles: - self.play(Rotate(circle, angle = angle)) - self.wait() - - self.circle = circle - - def show_range_of_angles(self): - self.add_radial_line() - arc_circle = self.get_arc_circle() - - theta = TexMobject("\\theta = ") - theta_value = DecimalNumber(0.00) - theta_value.next_to(theta, RIGHT) - theta_group = VGroup(theta, theta_value) - theta_group.next_to(arc_circle, UP) - def theta_value_update(theta_value, alpha): - new_theta_value = DecimalNumber(alpha*2*np.pi) - new_theta_value.set_height(theta.get_height()) - new_theta_value.next_to(theta, RIGHT) - Transform(theta_value, new_theta_value).update(1) - return new_theta_value - - - self.play(FadeIn(theta_group)) - for rate_func in smooth, lambda t : smooth(1-t): - self.play( - Rotate(self.circle, 2*np.pi-0.001), - ShowCreation(arc_circle), - UpdateFromAlphaFunc(theta_value, theta_value_update), - run_time = 7, - rate_func = rate_func - ) - self.wait() - self.play(FadeOut(theta_group)) - self.wait() - - def associate_rotations_with_points(self): - zero_dot = Dot(self.circle.point_from_proportion(0)) - zero_dot.set_color(RED) - zero_arrow = Arrow(UP+RIGHT, ORIGIN) - zero_arrow.set_color(zero_dot.get_color()) - zero_arrow.next_to(zero_dot, UP+RIGHT, buff = SMALL_BUFF) - - self.play( - ShowCreation(zero_arrow), - DrawBorderThenFill(zero_dot) - ) - self.circle.add(zero_dot) - self.wait() - - for alpha in 0.2, 0.6, 0.4, 0.8: - point = self.circle.point_from_proportion(alpha) - dot = Dot(point, color = YELLOW) - vect = np.sign(point) - arrow = Arrow(vect, ORIGIN) - arrow.next_to(dot, vect, buff = SMALL_BUFF) - arrow.set_color(dot.get_color()) - angle = alpha*2*np.pi - - self.play( - ShowCreation(arrow), - DrawBorderThenFill(dot) - ) - self.play( - Rotate(self.circle, angle, run_time = 2), - Animation(dot) - ) - self.wait() - self.play( - Rotate(self.circle, -angle, run_time = 2), - FadeOut(dot), - FadeOut(arrow), - ) - self.wait() - - #### - - def get_circle(self): - circle = Circle(color = MAROON_B, radius = self.circle_radius) - circle.ticks = VGroup() - for alpha in np.arange(0, 1, 1./8): - point = circle.point_from_proportion(alpha) - tick = Line((1 - 0.05)*point, (1 + 0.05)*point) - circle.ticks.add(tick) - circle.add(circle.ticks) - return circle - - def add_radial_line(self): - radius = Line( - self.circle.get_center(), - self.circle.point_from_proportion(0) - ) - static_radius = radius.copy().set_color(GREY) - - self.play(ShowCreation(radius)) - self.add(static_radius, radius) - self.circle.radius = radius - self.circle.static_radius = static_radius - self.circle.add(radius) - - def get_arc_circle(self): - arc_radius = self.circle_radius/5.0 - arc_circle = Circle( - radius = arc_radius, - color = WHITE - ) - return arc_circle - -class GroupOfCubeSymmetries(ThreeDScene): - CONFIG = { - "cube_opacity" : 0.5, - "cube_colors" : [BLUE], - "put_randy_on_cube" : True, - } - def construct(self): - title = TextMobject("Group of cube symmetries") - title.to_edge(UP) - self.add(title) - - cube = self.get_cube() - - face_centers = [face.get_center() for face in cube[0:7:2]] - angle_axis_pairs = list(zip(3*[np.pi/2], face_centers)) - for i in range(3): - ones = np.ones(3) - ones[i] = -1 - axis = np.dot(ones, face_centers) - angle_axis_pairs.append((2*np.pi/3, axis)) - - for angle, axis in angle_axis_pairs: - self.play(Rotate( - cube, angle = angle, axis = axis, - run_time = 2 - )) - self.wait() - - def get_cube(self): - cube = Cube(fill_opacity = self.cube_opacity) - cube.set_color_by_gradient(*self.cube_colors) - if self.put_randy_on_cube: - randy = Randolph(mode = "pondering") - # randy.pupils.shift(0.01*OUT) - # randy.add(randy.pupils.copy().shift(0.02*IN)) - # for submob in randy.get_family(): - # submob.part_of_three_d_mobject = True - randy.scale(0.5) - face = cube[1] - randy.move_to(face) - face.add(randy) - pose_matrix = self.get_pose_matrix() - cube.apply_function( - lambda p : np.dot(p, pose_matrix.T), - maintain_smoothness = False - ) - return cube - - def get_pose_matrix(self): - return np.dot( - rotation_matrix(np.pi/8, UP), - rotation_matrix(np.pi/24, RIGHT) - ) - -class HowDoSymmetriesPlayWithEachOther(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "How do symmetries \\\\ play with each other?", - target_mode = "hesitant", - ) - self.change_student_modes("pondering", "maybe", "confused") - self.wait(2) - -class AddSquareSymmetries(SymmetriesOfSquare): - def construct(self): - square = Square(**self.square_config) - square.flip(RIGHT) - square.shift(DOWN) - self.add_randy_to_square(square, mode = "shruggie") - alt_square = square.copy() - equals = TexMobject("=") - equals.move_to(square) - - equation_square = Square(**self.square_config) - equation = VGroup( - equation_square, TexMobject("+"), - equation_square.copy(), TexMobject("="), - equation_square.copy(), - ) - equation[0].add(self.get_rotation_arcs( - equation[0], np.pi/2, - )) - equation[2].add(self.get_axis_line(equation[4], UP)) - equation[4].add(self.get_axis_line(equation[4], UP+RIGHT)) - for mob in equation[::2]: - mob.scale(0.5) - equation.arrange(RIGHT) - equation.to_edge(UP) - - arcs = self.get_rotation_arcs(square, np.pi/2) - - self.add(square) - self.play(FadeIn(arcs)) - self.rotate_square( - square = square, angle = np.pi/2, - added_anims = list(map(FadeIn, equation[:2])) - ) - self.wait() - self.play(FadeOut(arcs)) - self.flip_square( - square = square, axis = UP, - added_anims = list(map(FadeIn, equation[2:4])) - ) - self.wait() - alt_square.next_to(equals, RIGHT, buff = LARGE_BUFF) - alt_square.save_state() - alt_square.move_to(square) - alt_square.set_fill(opacity = 0) - self.play( - square.next_to, equals, LEFT, LARGE_BUFF, - alt_square.restore, - Write(equals) - ) - self.flip_square( - square = alt_square, axis = UP+RIGHT, - added_anims = list(map(FadeIn, equation[4:])), - ) - self.wait(2) - - ## Reiterate composition - self.rotate_square(square = square, angle = np.pi/2) - self.flip_square(square = square, axis = UP) - self.wait() - self.flip_square(square = alt_square, axis = UP+RIGHT) - self.wait() - -class AddCircleSymmetries(CircleSymmetries): - def construct(self): - circle = self.circle = self.get_circle() - arc_circle = self.get_arc_circle() - angles = [3*np.pi/2, 2*np.pi/3, np.pi/6] - arcs = [ - arc_circle.copy().scale(scalar) - for scalar in [1, 1.2, 1.4] - ] - - equation = TexMobject( - "270^\\circ", "+", "120^\\circ", "=", "30^\\circ", - ) - equation.to_edge(UP) - - colors = [BLUE, YELLOW, GREEN] - for color, arc, term in zip(colors, arcs, equation[::2]): - arc.set_color(color) - term.set_color(color) - - self.play(FadeIn(circle)) - self.add_radial_line() - alt_radius = circle.radius.copy() - alt_radius.set_color(GREY) - alt_circle = circle.copy() - equals = TexMobject("=") - equals.move_to(circle) - - def rotate(circle, angle, arc, terms): - self.play( - Rotate(circle, angle, in_place = True), - ShowCreation( - arc, - rate_func = lambda t : (angle/(2*np.pi))*smooth(t) - ), - Write(VGroup(*terms)), - run_time = 2, - ) - - rotate(circle, angles[0], arcs[0], equation[:2]) - self.wait() - circle.add(alt_radius) - rotate(circle, angles[1], arcs[1], equation[2:4]) - self.play(FadeOut(alt_radius)) - circle.remove(alt_radius) - self.wait() - - circle.add(circle.static_radius) - circle.add(*arcs[:2]) - - alt_static_radius = circle.static_radius.copy() - alt_circle.add(alt_static_radius) - alt_circle.next_to(equals, RIGHT, buff = LARGE_BUFF) - alt_circle.save_state() - alt_circle.move_to(circle) - alt_circle.set_stroke(width = 0) - self.play( - circle.next_to, equals, LEFT, LARGE_BUFF, - alt_circle.restore, - Write(equals) - ) - arcs[2].shift(alt_circle.get_center()) - alt_circle.remove(alt_static_radius) - self.wait() - rotate(alt_circle, angles[2], arcs[2], equation[4:]) - self.wait() - self.play( - Rotate(arcs[1], angles[0], about_point = circle.get_center()) - ) - self.wait(2) - for term, arc in zip(equation[::2], arcs): - self.play(*[ - ApplyMethod(mob.scale_in_place, 1.2, rate_func = there_and_back) - for mob in (term, arc) - ]) - self.wait() - -class AddCubeSymmetries(GroupOfCubeSymmetries): - CONFIG = { - "angle_axis_pairs" : [ - (np.pi/2, RIGHT), - (np.pi/2, UP), - ], - "cube_opacity" : 0.5, - "cube_colors" : [BLUE], - } - def construct(self): - angle_axis_pairs = list(self.angle_axis_pairs) - angle_axis_pairs.append( - self.get_composition_angle_and_axis() - ) - self.pose_matrix = self.get_pose_matrix() - cube = self.get_cube() - - equation = cube1, plus, cube2, equals, cube3 = VGroup( - cube, TexMobject("+"), - cube.copy(), TexMobject("="), - cube.copy() - ) - equation.arrange(RIGHT, buff = MED_LARGE_BUFF) - equation.center() - - self.add(cube1) - self.rotate_cube(cube1, *angle_axis_pairs[0]) - cube_copy = cube1.copy() - cube_copy.set_fill(opacity = 0) - self.play( - cube_copy.move_to, cube2, - cube_copy.set_fill, None, self.cube_opacity, - Write(plus) - ) - self.rotate_cube(cube_copy, *angle_axis_pairs[1]) - self.play(Write(equals)) - self.play(DrawBorderThenFill(cube3, run_time = 1)) - self.rotate_cube(cube3, *angle_axis_pairs[2]) - self.wait(2) - - times = TexMobject("\\times") - times.scale(1.5) - times.move_to(plus) - times.set_color(RED) - self.wait() - self.play(ReplacementTransform(plus, times)) - self.play(Indicate(times)) - self.wait() - for cube, (angle, axis) in zip([cube1, cube_copy, cube3], angle_axis_pairs): - self.rotate_cube( - cube, -angle, axis, add_arrows = False, - rate_func = there_and_back, - run_time = 1.5 - ) - self.wait() - - def rotate_cube(self, cube, angle, axis, add_arrows = True, **kwargs): - axis = np.dot(axis, self.pose_matrix.T) - anims = [] - if add_arrows: - arrows = VGroup(*[ - Arc( - start_angle = np.pi/12+a, angle = 5*np.pi/6, - color = YELLOW - ).add_tip() - for a in (0, np.pi) - ]) - arrows.set_height(1.5*cube.get_height()) - z_to_axis = z_to_vector(axis) - arrows.apply_function( - lambda p : np.dot(p, z_to_axis.T), - maintain_smoothness = False - ) - arrows.move_to(cube) - arrows.shift(-axis*cube.get_height()/2/get_norm(axis)) - anims += list(map(ShowCreation, arrows)) - anims.append( - Rotate( - cube, axis = axis, angle = angle, in_place = True, - **kwargs - ) - ) - self.play(*anims, run_time = 1.5) - - def get_composition_angle_and_axis(self): - return get_composite_rotation_angle_and_axis( - *list(zip(*self.angle_axis_pairs)) - ) - -class DihedralGroupStructure(SymmetriesOfSquare): - CONFIG = { - "dashed_line_config" : { - "dash_length" : 0.1 - }, - "filed_sum_scale_factor" : 0.4, - "num_rows" : 5, - } - def construct(self): - angle_axis_pairs = [ - (np.pi/2, OUT), - (np.pi, OUT), - (-np.pi/2, OUT), - # (np.pi, RIGHT), - # (np.pi, UP+RIGHT), - (np.pi, UP), - (np.pi, UP+LEFT), - ] - pair_pairs = list(it.product(*[angle_axis_pairs]*2)) - random.shuffle(pair_pairs) - for pair_pair in pair_pairs[:4]: - sum_expression = self.demonstrate_sum(pair_pair) - self.file_away_sum(sum_expression) - for pair_pair in pair_pairs[4:]: - should_skip_animations = self.skip_animations - self.skip_animations = True - sum_expression = self.demonstrate_sum(pair_pair) - self.file_away_sum(sum_expression) - self.skip_animations = should_skip_animations - self.play(FadeIn(sum_expression)) - self.wait(3) - - - def demonstrate_sum(self, angle_axis_pairs): - angle_axis_pairs = list(angle_axis_pairs) + [ - get_composite_rotation_angle_and_axis( - *list(zip(*angle_axis_pairs)) - ) - ] - - prototype_square = Square(**self.square_config) - prototype_square.flip(RIGHT) - self.add_randy_to_square(prototype_square) - - # self.add_labels_and_dots(prototype_square) - prototype_square.scale(0.7) - expression = s1, plus, s2, equals, s3 = VGroup( - prototype_square, TexMobject("+").scale(2), - prototype_square.copy(), TexMobject("=").scale(2), - prototype_square.copy() - ) - - final_expression = VGroup() - for square, (angle, axis) in zip([s1, s2, s3], angle_axis_pairs): - if np.cos(angle) > 0.5: - square.action_illustration = VectorizedPoint() - elif np.argmax(np.abs(axis)) == 2: ##Axis is in z direction - square.action_illustration = self.get_rotation_arcs( - square, angle - ) - else: - square.action_illustration = self.get_axis_line( - square, axis - ) - square.add(square.action_illustration) - final_expression.add(square.action_illustration) - square.rotation_kwargs = { - "square" : square, - "angle" : angle, - "axis" : axis, - } - expression.arrange() - expression.set_width(FRAME_X_RADIUS+1) - expression.to_edge(RIGHT, buff = SMALL_BUFF) - for square in s1, s2, s3: - square.remove(square.action_illustration) - - self.play(FadeIn(s1)) - self.play(*list(map(ShowCreation, s1.action_illustration))) - self.rotate_square(**s1.rotation_kwargs) - - s1_copy = s1.copy() - self.play( - # FadeIn(s2), - s1_copy.move_to, s2, - Write(plus) - ) - Transform(s2, s1_copy).update(1) - self.remove(s1_copy) - self.add(s2) - self.play(*list(map(ShowCreation, s2.action_illustration))) - self.rotate_square(**s2.rotation_kwargs) - - self.play( - Write(equals), - FadeIn(s3) - ) - self.play(*list(map(ShowCreation, s3.action_illustration))) - self.rotate_square(**s3.rotation_kwargs) - self.wait() - final_expression.add(*expression) - - return final_expression - - def file_away_sum(self, sum_expression): - if not hasattr(self, "num_sum_expressions"): - self.num_sum_expressions = 0 - target = sum_expression.copy() - target.scale(self.filed_sum_scale_factor) - y_index = self.num_sum_expressions%self.num_rows - y_prop = float(y_index)/(self.num_rows-1) - y = interpolate(FRAME_Y_RADIUS-LARGE_BUFF, -FRAME_Y_RADIUS+LARGE_BUFF, y_prop) - x_index = self.num_sum_expressions//self.num_rows - x_spacing = FRAME_WIDTH/3 - x = (x_index-1)*x_spacing - - target.move_to(x*RIGHT + y*UP) - - self.play(Transform(sum_expression, target)) - self.wait() - - self.num_sum_expressions += 1 - self.last_sum_expression = sum_expression - -class ThisIsAVeryGeneralIdea(Scene): - def construct(self): - groups = TextMobject("Groups") - groups.to_edge(UP) - groups.set_color(BLUE) - - examples = VGroup(*list(map(TextMobject, [ - "Square matrices \\\\ \\small (Where $\\det(M) \\ne 0$)", - "Molecular \\\\ symmetry", - "Cryptography", - "Numbers", - ]))) - numbers = examples[-1] - examples.arrange(buff = LARGE_BUFF) - examples.set_width(FRAME_WIDTH-1) - examples.move_to(UP) - - lines = VGroup(*[ - Line(groups.get_bottom(), ex.get_top(), buff = MED_SMALL_BUFF) - for ex in examples - ]) - lines.set_color(groups.get_color()) - - self.add(groups) - - for example, line in zip(examples, lines): - self.play( - ShowCreation(line), - Write(example, run_time = 2) - ) - self.wait() - self.play( - VGroup(*examples[:-1]).fade, 0.7, - VGroup(*lines[:-1]).fade, 0.7, - ) - - self.play( - numbers.scale, 1.2, numbers.get_corner(UP+RIGHT), - ) - self.wait(2) - - sub_categories = VGroup(*list(map(TextMobject, [ - "Numbers \\\\ (Additive)", - "Numbers \\\\ (Multiplicative)", - ]))) - sub_categories.arrange(RIGHT, buff = MED_LARGE_BUFF) - sub_categories.next_to(numbers, DOWN, 1.5*LARGE_BUFF) - sub_categories.to_edge(RIGHT) - sub_categories[0].set_color(ADDER_COLOR) - sub_categories[1].set_color(MULTIPLIER_COLOR) - - sub_lines = VGroup(*[ - Line(numbers.get_bottom(), sc.get_top(), buff = MED_SMALL_BUFF) - for sc in sub_categories - ]) - sub_lines.set_color(numbers.get_color()) - - self.play(*it.chain( - list(map(ShowCreation, sub_lines)), - list(map(Write, sub_categories)) - )) - self.wait() - -class NumbersAsActionsQ(TeacherStudentsScene): - def construct(self): - self.student_says( - "Numbers are actions?", - target_mode = "confused", - ) - self.change_student_modes("pondering", "confused", "erm") - self.play(self.get_teacher().change_mode, "happy") - self.wait(3) - -class AdditiveGroupOfReals(Scene): - CONFIG = { - "number_line_center" : UP, - "shadow_line_center" : DOWN, - "zero_color" : GREEN_B, - "x_min" : -FRAME_WIDTH, - "x_max" : FRAME_WIDTH, - } - def construct(self): - self.add_number_line() - self.show_example_slides(3, -7) - self.write_group_of_slides() - self.show_example_slides(2, 6, -1, -3) - self.mark_zero() - self.show_example_slides_labeled(3, -2) - self.comment_on_zero_as_identity() - self.show_example_slides_labeled( - 5.5, added_anims = [self.get_write_name_of_group_anim()] - ) - self.show_example_additions((3, 2), (2, -5), (-4, 4)) - - def add_number_line(self): - number_line = NumberLine( - x_min = self.x_min, - x_max = self.x_max, - ) - - number_line.shift(self.number_line_center) - shadow_line = NumberLine(color = GREY, stroke_width = 2) - shadow_line.shift(self.shadow_line_center) - for line in number_line, shadow_line: - line.add_numbers() - shadow_line.numbers.fade(0.25) - shadow_line.save_state() - shadow_line.set_color(BLACK) - shadow_line.move_to(number_line) - - - self.play(*list(map(Write, number_line)), run_time = 1) - self.play(shadow_line.restore, Animation(number_line)) - self.wait() - - self.number_line = number_line - self.shadow_line = shadow_line - - def show_example_slides(self, *nums): - for num in nums: - zero_point = self.number_line.number_to_point(0) - num_point = self.number_line.number_to_point(num) - arrow = Arrow(zero_point, num_point, buff = 0) - arrow.set_color(ADDER_COLOR) - arrow.shift(MED_LARGE_BUFF*UP) - - self.play(ShowCreation(arrow)) - self.play( - self.number_line.shift, - num_point - zero_point, - run_time = 2 - ) - self.play(FadeOut(arrow)) - - def write_group_of_slides(self): - title = TextMobject("Group of line symmetries") - title.to_edge(UP) - self.play(Write(title)) - self.title = title - - def mark_zero(self): - dot = Dot( - self.number_line.number_to_point(0), - color = self.zero_color - ) - arrow = Arrow(dot, color = self.zero_color) - words = TextMobject("Follow zero") - words.next_to(arrow.get_start(), UP) - words.set_color(self.zero_color) - - self.play( - ShowCreation(arrow), - DrawBorderThenFill(dot), - Write(words), - ) - self.wait() - self.play(*list(map(FadeOut, [arrow, words]))) - - self.number_line.add(dot) - - def show_example_slides_labeled(self, *nums, **kwargs): - for num in nums: - line = DashedLine( - self.number_line.number_to_point(num)+MED_LARGE_BUFF*UP, - self.shadow_line.number_to_point(num)+MED_LARGE_BUFF*DOWN, - ) - vect = self.number_line.number_to_point(num) - \ - self.number_line.number_to_point(0) - self.play(ShowCreation(line)) - self.wait() - self.play(self.number_line.shift, vect, run_time = 2) - self.wait() - if "added_anims" in kwargs: - self.play(*kwargs["added_anims"]) - self.wait() - self.play( - self.number_line.shift, -vect, - FadeOut(line) - ) - - def comment_on_zero_as_identity(self): - line = DashedLine( - self.number_line.number_to_point(0)+MED_LARGE_BUFF*UP, - self.shadow_line.number_to_point(0)+MED_LARGE_BUFF*DOWN, - ) - words = TexMobject("0 \\leftrightarrow \\text{Do nothing}") - words.shift(line.get_top()+MED_SMALL_BUFF*UP - words[0].get_bottom()) - - self.play( - ShowCreation(line), - Write(words) - ) - self.wait(2) - self.play(*list(map(FadeOut, [line, words]))) - - def get_write_name_of_group_anim(self): - new_title = TextMobject("Additive group of real numbers") - VGroup(*new_title[-len("realnumbers"):]).set_color(BLUE) - VGroup(*new_title[:len("Additive")]).set_color(ADDER_COLOR) - new_title.to_edge(UP) - return Transform(self.title, new_title) - - def show_example_additions(self, *num_pairs): - for num_pair in num_pairs: - num_mobs = VGroup() - arrows = VGroup() - self.number_line.save_state() - for num in num_pair: - zero_point, num_point, arrow, num_mob = \ - self.get_adder_mobs(num) - if len(num_mobs) > 0: - last_num_mob = num_mobs[0] - x = num_mob.get_center()[0] - if x < last_num_mob.get_right()[0] and x > last_num_mob.get_left()[0]: - num_mob.next_to(last_num_mob, RIGHT) - num_mobs.add(num_mob) - arrows.add(arrow) - - self.play( - ShowCreation(arrow), - Write(num_mob, run_time = 1) - ) - self.play( - self.number_line.shift, - num_point - zero_point - ) - self.wait() - #Reset - self.play( - FadeOut(num_mobs), - FadeOut(self.number_line) - ) - ApplyMethod(self.number_line.restore).update(1) - self.play(FadeIn(self.number_line)) - - #Sum arrow - num = sum(num_pair) - zero_point, sum_point, arrow, sum_mob = \ - self.get_adder_mobs(sum(num_pair)) - VGroup(arrow, sum_mob).shift(MED_LARGE_BUFF*UP) - arrows.add(arrow) - self.play( - ShowCreation(arrow), - Write(sum_mob, run_time = 1) - ) - self.wait() - self.play( - self.number_line.shift, - num_point - zero_point, - run_time = 2 - ) - self.wait() - self.play( - self.number_line.restore, - *list(map(FadeOut, [arrows, sum_mob])) - ) - - def get_adder_mobs(self, num): - zero_point = self.number_line.number_to_point(0) - num_point = self.number_line.number_to_point(num) - arrow = Arrow(zero_point, num_point, buff = 0) - arrow.set_color(ADDER_COLOR) - arrow.shift(MED_SMALL_BUFF*UP) - if num == 0: - arrow = DashedLine(UP, ORIGIN) - arrow.move_to(zero_point) - elif num < 0: - arrow.set_color(RED) - arrow.shift(SMALL_BUFF*UP) - sign = "+" if num >= 0 else "" - num_mob = TexMobject(sign + str(num)) - num_mob.next_to(arrow, UP) - num_mob.set_color(arrow.get_color()) - return zero_point, num_point, arrow, num_mob - -class AdditiveGroupOfComplexNumbers(ComplexTransformationScene): - CONFIG = { - "x_min" : -2*int(FRAME_X_RADIUS), - "x_max" : 2*int(FRAME_X_RADIUS), - "y_min" : -FRAME_HEIGHT, - "y_max" : FRAME_HEIGHT, - "example_points" : [ - complex(3, 2), - complex(1, -3), - ] - } - def construct(self): - self.add_plane() - self.show_preview_example_slides() - self.show_vertical_slide() - self.show_example_point() - self.show_example_addition() - self.write_group_name() - self.show_some_random_slides() - - def add_plane(self): - self.add_transformable_plane(animate = True) - zero_dot = Dot( - self.z_to_point(0), - color = ADDER_COLOR - ) - self.play(ShowCreation(zero_dot)) - self.plane.add(zero_dot) - self.plane.zero_dot = zero_dot - self.wait() - - def show_preview_example_slides(self): - example_vect = 2*UP+RIGHT - for vect in example_vect, -example_vect: - self.play(self.plane.shift, vect, run_time = 2) - self.wait() - - def show_vertical_slide(self): - dots = VGroup(*[ - Dot(self.z_to_point(complex(0, i))) - for i in range(1, 4) - ]) - dots.set_color(YELLOW) - labels = VGroup(*self.imag_labels[-3:]) - - arrow = Arrow(ORIGIN, dots[-1].get_center(), buff = 0) - arrow.set_color(ADDER_COLOR) - - self.plane.save_state() - for dot, label in zip(dots, labels): - self.play( - Indicate(label), - ShowCreation(dot) - ) - self.add_foreground_mobjects(dots) - self.wait() - Scene.play(self, ShowCreation(arrow)) - self.add_foreground_mobjects(arrow) - self.play( - self.plane.shift, dots[-1].get_center(), - run_time = 2 - ) - self.wait() - self.play(FadeOut(arrow)) - self.foreground_mobjects.remove(arrow) - self.play( - self.plane.shift, 6*DOWN, - run_time = 3, - ) - self.wait() - self.play(self.plane.restore, run_time = 2) - self.foreground_mobjects.remove(dots) - self.play(FadeOut(dots)) - - def show_example_point(self): - z = self.example_points[0] - point = self.z_to_point(z) - dot = Dot(point, color = YELLOW) - arrow = Vector(point, buff = dot.radius) - arrow.set_color(dot.get_color()) - label = TexMobject("%d + %di"%(z.real, z.imag)) - label.next_to(point, UP) - label.set_color(dot.get_color()) - label.add_background_rectangle() - - real_arrow = Vector(self.z_to_point(z.real)) - imag_arrow = Vector(self.z_to_point(z - z.real)) - VGroup(real_arrow, imag_arrow).set_color(ADDER_COLOR) - - self.play( - Write(label), - DrawBorderThenFill(dot) - ) - self.wait() - self.play(ShowCreation(arrow)) - self.add_foreground_mobjects(label, dot, arrow) - self.wait() - self.slide(z) - self.wait() - self.play(FadeOut(self.plane)) - self.plane.restore() - self.plane.set_stroke(width = 0) - self.play(self.plane.restore) - self.play(ShowCreation(real_arrow)) - self.add_foreground_mobjects(real_arrow) - self.slide(z.real) - self.wait() - self.play(ShowCreation(imag_arrow)) - self.wait() - self.play(imag_arrow.shift, self.z_to_point(z.real)) - self.add_foreground_mobjects(imag_arrow) - self.slide(z - z.real) - self.wait() - - self.foreground_mobjects.remove(real_arrow) - self.foreground_mobjects.remove(imag_arrow) - self.play(*list(map(FadeOut, [real_arrow, imag_arrow, self.plane]))) - self.plane.restore() - self.plane.set_stroke(width = 0) - self.play(self.plane.restore) - - self.z1 = z - self.arrow1 = arrow - self.dot1 = dot - self.label1 = label - - def show_example_addition(self): - z1 = self.z1 - arrow1 = self.arrow1 - dot1 = self.dot1 - label1 = self.label1 - - z2 = self.example_points[1] - point2 = self.z_to_point(z2) - dot2 = Dot(point2, color = TEAL) - arrow2 = Vector( - point2, - buff = dot2.radius, - color = dot2.get_color() - ) - label2 = TexMobject( - "%d %di"%(z2.real, z2.imag) - ) - label2.next_to(point2, UP+RIGHT) - label2.set_color(dot2.get_color()) - label2.add_background_rectangle() - - self.play(ShowCreation(arrow2)) - self.play( - DrawBorderThenFill(dot2), - Write(label2) - ) - self.add_foreground_mobjects(arrow2, dot2, label2) - self.wait() - - self.slide(z1) - arrow2_copy = arrow2.copy() - self.play(arrow2_copy.shift, self.z_to_point(z1)) - self.add_foreground_mobjects(arrow2_copy) - self.slide(z2) - self.play(FadeOut(arrow2_copy)) - self.foreground_mobjects.remove(arrow2_copy) - self.wait() - - ##Break into components - real_arrow, imag_arrow = component_arrows = [ - Vector( - self.z_to_point(z), - color = ADDER_COLOR - ) - for z in [ - z1.real+z2.real, - complex(0, z1.imag+z2.imag), - ] - ] - imag_arrow.shift(real_arrow.get_end()) - plus = TexMobject("+").next_to( - real_arrow.get_center(), UP+RIGHT - ) - plus.add_background_rectangle() - - rp1, rp2, ip1, ip2 = label_parts = [ - VGroup(label1[1][0].copy()), - VGroup(label2[1][0].copy()), - VGroup(*label1[1][2:]).copy(), - VGroup(*label2[1][1:]).copy(), - ] - for part in label_parts: - part.generate_target() - - rp1.target.next_to(plus, LEFT) - rp2.target.next_to(plus, RIGHT) - ip1.target.next_to(imag_arrow.get_center(), RIGHT) - ip1.target.shift(SMALL_BUFF*DOWN) - ip2.target.next_to(ip1.target, RIGHT) - - real_background_rect = BackgroundRectangle( - VGroup(rp1.target, rp2.target) - ) - imag_background_rect = BackgroundRectangle( - VGroup(ip1.target, ip2.target) - ) - - self.play( - ShowCreation(real_arrow), - ShowCreation( - real_background_rect, - rate_func = squish_rate_func(smooth, 0.75, 1), - ), - Write(plus), - *list(map(MoveToTarget, [rp1, rp2])) - ) - self.wait() - self.play( - ShowCreation(imag_arrow), - ShowCreation( - imag_background_rect, - rate_func = squish_rate_func(smooth, 0.75, 1), - ), - *list(map(MoveToTarget, [ip1, ip2])) - ) - self.wait(2) - to_remove = [ - arrow1, dot1, label1, - arrow2, dot2, label2, - real_background_rect, - imag_background_rect, - plus, - ] + label_parts + component_arrows - for mob in to_remove: - if mob in self.foreground_mobjects: - self.foreground_mobjects.remove(mob) - self.play(*list(map(FadeOut, to_remove))) - self.play(self.plane.restore, run_time = 2) - self.wait() - - def write_group_name(self): - title = TextMobject( - "Additive", "group of", "complex numbers" - ) - title[0].set_color(ADDER_COLOR) - title[2].set_color(BLUE) - title.add_background_rectangle() - title.to_edge(UP, buff = MED_SMALL_BUFF) - - self.play(Write(title)) - self.add_foreground_mobjects(title) - self.wait() - - def show_some_random_slides(self): - example_slides = [ - complex(3), - complex(0, 2), - complex(-4, -1), - complex(-2, -1), - complex(4, 2), - ] - for z in example_slides: - self.slide(z) - self.wait() - - ######### - - def slide(self, z, *added_anims, **kwargs): - kwargs["run_time"] = kwargs.get("run_time", 2) - self.play( - ApplyMethod( - self.plane.shift, self.z_to_point(z), - **kwargs - ), - *added_anims - ) - -class SchizophrenicNumbers(Scene): - def construct(self): - v_line = DashedLine( - FRAME_Y_RADIUS*UP, - FRAME_Y_RADIUS*DOWN - ) - left_title = TextMobject("Additive group") - left_title.shift(FRAME_X_RADIUS*LEFT/2) - right_title = TextMobject("Multiplicative group") - right_title.shift(FRAME_X_RADIUS*RIGHT/2) - VGroup(left_title, right_title).to_edge(UP) - self.add(v_line, left_title, right_title) - - numbers = VGroup( - Randolph(mode = "happy").scale(0.2), - TexMobject("3").shift(UP+LEFT), - TexMobject("5.83").shift(UP+RIGHT), - TexMobject("\\sqrt{2}").shift(DOWN+LEFT), - TexMobject("2-i").shift(DOWN+RIGHT), - ) - for number in numbers: - number.set_color(ADDER_COLOR) - number.scale(1.5) - if isinstance(number, PiCreature): - continue - number.eyes = Eyes(number[0], height = 0.1) - number.add(number.eyes) - numbers[3].eyes.next_to(numbers[3][1], UP, buff = 0) - numbers.shift(FRAME_X_RADIUS*LEFT/2) - - self.play(FadeIn(numbers)) - self.blink_numbers(numbers) - self.wait() - self.add(numbers.copy()) - for number in numbers: - number.generate_target() - number.target.shift(FRAME_X_RADIUS*RIGHT) - number.target.eyes.save_state() - number.target.set_color(MULTIPLIER_COLOR) - number.target.eyes.restore() - self.play(*[ - MoveToTarget( - number, - rate_func = squish_rate_func( - smooth, alpha, alpha+0.5 - ), - run_time = 2, - ) - for number, alpha in zip(numbers, np.linspace(0, 0.5, len(numbers))) - ]) - self.wait() - self.blink_numbers(numbers) - self.wait() - - def blink_numbers(self, numbers): - self.play(*[ - num.eyes.blink_anim( - rate_func = squish_rate_func( - there_and_back, alpha, alpha+0.2 - ) - ) - for num, alpha in zip( - numbers[1:], 0.8*np.random.random(len(numbers)) - ) - ]) - -class MultiplicativeGroupOfReals(AdditiveGroupOfReals): - CONFIG = { - "number_line_center" : 0.5*UP, - "shadow_line_center" : 1.5*DOWN, - "x_min" : -3*FRAME_X_RADIUS, - "x_max" : 3*FRAME_X_RADIUS, - "positive_reals_color" : MAROON_B, - } - def setup(self): - self.foreground_mobjects = VGroup() - - def construct(self): - self.add_title() - self.add_number_line() - self.introduce_stretch_and_squish() - self.show_zero_fixed_in_place() - self.follow_one() - self.every_positive_number_association() - self.compose_actions(3, 2) - self.compose_actions(4, 0.5) - self.write_group_name() - self.compose_actions(1.5, 1.5) - - def add_title(self): - self.title = TextMobject("Group of stretching/squishing actions") - self.title.to_edge(UP) - self.add(self.title) - - def add_number_line(self): - AdditiveGroupOfReals.add_number_line(self) - self.zero_point = self.number_line.number_to_point(0) - self.one = [m for m in self.number_line.numbers if m.get_tex_string() is "1"][0] - self.one.add_background_rectangle() - self.one.background_rectangle.scale_in_place(1.3) - self.number_line.save_state() - - def introduce_stretch_and_squish(self): - for num in [3, 0.25]: - self.stretch(num) - self.wait() - self.play(self.number_line.restore) - self.wait() - - def show_zero_fixed_in_place(self): - arrow = Arrow(self.zero_point + UP, self.zero_point, buff = 0) - arrow.set_color(ADDER_COLOR) - words = TextMobject("Fix zero") - words.set_color(ADDER_COLOR) - words.next_to(arrow, UP) - - self.play( - ShowCreation(arrow), - Write(words) - ) - self.foreground_mobjects.add(arrow) - self.stretch(4) - self.stretch(0.1) - self.wait() - self.play(self.number_line.restore) - self.play(FadeOut(words)) - self.wait() - - self.zero_arrow = arrow - - def follow_one(self): - dot = Dot(self.number_line.number_to_point(1)) - arrow = Arrow(dot.get_center()+UP+RIGHT, dot) - words = TextMobject("Follow one") - words.next_to(arrow.get_start(), UP) - for mob in dot, arrow, words: - mob.set_color(MULTIPLIER_COLOR) - - three_line, half_line = [ - DashedLine( - self.number_line.number_to_point(num), - self.shadow_line.number_to_point(num) - ) - for num in (3, 0.5) - ] - three_mob = [m for m in self.shadow_line.numbers if m.get_tex_string() == "3"][0] - half_point = self.shadow_line.number_to_point(0.5) - half_arrow = Arrow( - half_point+UP+LEFT, half_point, buff = SMALL_BUFF, - tip_length = 0.15, - ) - half_label = TexMobject("1/2") - half_label.scale(0.7) - half_label.set_color(MULTIPLIER_COLOR) - half_label.next_to(half_arrow.get_start(), LEFT, buff = SMALL_BUFF) - - self.play( - ShowCreation(arrow), - DrawBorderThenFill(dot), - Write(words) - ) - self.number_line.add(dot) - self.number_line.numbers.add(dot) - self.number_line.save_state() - self.wait() - self.play(*list(map(FadeOut, [arrow, words]))) - - self.stretch(3) - self.play( - ShowCreation(three_line), - Animation(self.one) - ) - dot_copy = dot.copy() - self.play( - dot_copy.move_to, three_line.get_bottom() - ) - self.play(Indicate(three_mob, run_time = 2)) - self.wait() - self.play( - self.number_line.restore, - *list(map(FadeOut, [three_line, dot_copy])) - ) - self.wait() - self.stretch(0.5) - self.play( - ShowCreation(half_line), - Animation(self.one) - ) - dot_copy = dot.copy() - self.play( - dot_copy.move_to, half_line.get_bottom() - ) - self.play( - Write(half_label), - ShowCreation(half_arrow) - ) - self.wait() - self.play( - self.number_line.restore, - *list(map(FadeOut, [ - half_label, half_arrow, - half_line, dot_copy - ])) - ) - self.wait() - - self.one_dot = dot - - def every_positive_number_association(self): - positive_reals_line = Line( - self.shadow_line.number_to_point(0), - self.shadow_line.number_to_point(FRAME_X_RADIUS), - color = self.positive_reals_color - ) - positive_reals_words = TextMobject("All positive reals") - positive_reals_words.set_color(self.positive_reals_color) - positive_reals_words.next_to(positive_reals_line, UP) - positive_reals_words.add_background_rectangle() - - third_line, one_line = [ - DashedLine( - self.number_line.number_to_point(num), - self.shadow_line.number_to_point(num) - ) - for num in (0.33, 1) - ] - - self.play( - self.zero_arrow.shift, 0.5*UP, - rate_func = there_and_back - ) - self.wait() - self.play( - self.one_dot.shift, 0.25*UP, - rate_func = wiggle - ) - self.stretch(3) - self.stretch(0.33/3, run_time = 3) - self.wait() - self.play(ShowCreation(third_line), Animation(self.one)) - self.play( - ShowCreation(positive_reals_line), - Write(positive_reals_words), - ) - self.wait() - self.play( - ReplacementTransform(third_line, one_line), - self.number_line.restore, - Animation(positive_reals_words), - run_time = 2 - ) - self.number_line.add_to_back(one_line) - self.number_line.save_state() - self.stretch( - 7, run_time = 10, rate_func = there_and_back, - added_anims = [Animation(positive_reals_words)] - ) - self.wait() - - def compose_actions(self, num1, num2): - words = VGroup(*[ - TextMobject("(%s by %s)"%(word, str(num))) - for num in (num1, num2, num1*num2) - for word in ["Stretch" if num > 1 else "Squish"] - ]) - words.submobjects.insert(2, TexMobject("=")) - words.arrange(RIGHT) - top_words = VGroup(*words[:2]) - top_words.set_color(MULTIPLIER_COLOR) - bottom_words = VGroup(*words[2:]) - bottom_words.next_to(top_words, DOWN) - words.scale(0.8) - words.next_to(self.number_line, UP) - words.to_edge(RIGHT) - - for num, word in zip([num1, num2], top_words): - self.stretch( - num, - added_anims = [FadeIn(word)], - run_time = 3 - ) - self.wait() - self.play(Write(bottom_words, run_time = 2)) - self.wait(2) - self.play( - ApplyMethod(self.number_line.restore, run_time = 2), - FadeOut(words), - ) - self.wait() - - def write_group_name(self): - new_title = TextMobject( - "Multiplicative group of positive real numbers" - ) - new_title.to_edge(UP) - VGroup( - *new_title[:len("Multiplicative")] - ).set_color(MULTIPLIER_COLOR) - VGroup( - *new_title[-len("positiverealnumbers"):] - ).set_color(self.positive_reals_color) - - self.play(Transform(self.title, new_title)) - self.wait() - - ### - - def stretch(self, factor, run_time = 2, **kwargs): - kwargs["run_time"] = run_time - target = self.number_line.copy() - target.stretch_about_point(factor, 0, self.zero_point) - total_factor = (target.number_to_point(1)-self.zero_point)[0] - for number in target.numbers: - number.stretch_in_place(1./factor, dim = 0) - if total_factor < 0.7: - number.stretch_in_place(total_factor, dim = 0) - self.play( - Transform(self.number_line, target, **kwargs), - *kwargs.get("added_anims", []) - ) - - def play(self, *anims, **kwargs): - anims = list(anims) + [Animation(self.foreground_mobjects)] - Scene.play(self, *anims, **kwargs) - -class MultiplicativeGroupOfComplexNumbers(AdditiveGroupOfComplexNumbers): - CONFIG = { - "dot_radius" : Dot.CONFIG["radius"], - "y_min" : -3*FRAME_Y_RADIUS, - "y_max" : 3*FRAME_Y_RADIUS, - } - def construct(self): - self.add_plane() - self.add_title() - self.fix_zero_and_move_one() - self.show_example_actions() - self.show_action_at_i() - self.show_action_at_i_again() - self.show_i_squared_is_negative_one() - self.talk_through_specific_example() - self.show_break_down() - self.example_actions_broken_down() - - def add_plane(self): - AdditiveGroupOfComplexNumbers.add_plane(self) - one_dot = Dot( - self.z_to_point(1), - color = MULTIPLIER_COLOR, - radius = self.dot_radius, - ) - self.plane.add(one_dot) - self.plane.one_dot = one_dot - self.plane.save_state() - self.add(self.plane) - - def add_title(self): - title = TextMobject( - "Multiplicative", "group of", - "complex numbers" - ) - title.to_edge(UP) - title[0].set_color(MULTIPLIER_COLOR) - title[2].set_color(BLUE) - title.add_background_rectangle() - - self.play(Write(title, run_time = 2)) - self.wait() - self.add_foreground_mobjects(title) - - def fix_zero_and_move_one(self): - zero_arrow = Arrow( - UP+1.25*LEFT, ORIGIN, - buff = 2*self.dot_radius - ) - zero_arrow.set_color(ADDER_COLOR) - zero_words = TextMobject("Fix zero") - zero_words.set_color(ADDER_COLOR) - zero_words.add_background_rectangle() - zero_words.next_to(zero_arrow.get_start(), UP) - - one_point = self.z_to_point(1) - one_arrow = Arrow( - one_point+UP+1.25*RIGHT, one_point, - buff = 2*self.dot_radius, - color = MULTIPLIER_COLOR, - ) - one_words = TextMobject("Drag one") - one_words.set_color(MULTIPLIER_COLOR) - one_words.add_background_rectangle() - one_words.next_to(one_arrow.get_start(), UP) - - self.play( - Write(zero_words, run_time = 2), - ShowCreation(zero_arrow), - Indicate(self.plane.zero_dot, color = RED), - ) - self.play( - Write(one_words, run_time = 2), - ShowCreation(one_arrow), - Indicate(self.plane.one_dot, color = RED), - ) - self.wait(2) - self.play(*list(map(FadeOut, [ - zero_words, zero_arrow, - one_words, one_arrow, - ]))) - - def show_example_actions(self): - z_list = [ - complex(2), - complex(0.5), - complex(2, 1), - complex(-2, 2), - ] - for last_z, z in zip([1] + z_list, z_list): - self.multiply_by_z(z/last_z) - self.wait() - self.reset_plane() - self.wait() - - def show_action_at_i(self): - i_point = self.z_to_point(complex(0, 1)) - i_dot = Dot(i_point) - i_dot.set_color(RED) - i_arrow = Arrow(i_point+UP+LEFT, i_point) - i_arrow.set_color(i_dot.get_color()) - - arc = Arc( - start_angle = np.pi/24, - angle = 10*np.pi/24, - radius = self.z_to_point(1)[0], - num_anchors = 20, - ) - arc.add_tip(tip_length = 0.15) - arc.set_color(YELLOW) - - self.play( - ShowCreation(i_arrow), - DrawBorderThenFill(i_dot) - ) - self.wait() - self.play( - FadeOut(i_arrow), - ShowCreation(arc) - ) - self.add_foreground_mobjects(arc) - self.wait(2) - self.multiply_by_z(complex(0, 1), run_time = 3) - self.remove(i_dot) - self.wait() - - self.turn_arrow = arc - - def show_action_at_i_again(self): - neg_one_label = [m for m in self.real_labels if m.get_tex_string() == "-1"][0] - half_turn_arc = Arc( - start_angle = np.pi/12, - angle = 10*np.pi/12, - color = self.turn_arrow.get_color() - ) - half_turn_arc.add_tip(tip_length = 0.15) - - self.multiply_by_z(complex(0, 1), run_time = 3) - self.wait() - self.play(Transform( - self.turn_arrow, half_turn_arc, - path_arc = np.pi/2 - )) - self.wait() - self.play(Indicate(neg_one_label, run_time = 2)) - self.wait() - self.foreground_mobjects.remove(self.turn_arrow) - self.reset_plane(FadeOut(self.turn_arrow)) - - def show_i_squared_is_negative_one(self): - equation = TexMobject("i", "\\cdot", "i", "=", "-1") - terms = equation[::2] - equation.add_background_rectangle() - equation.next_to(ORIGIN, RIGHT) - equation.shift(1.5*UP) - equation.set_color(MULTIPLIER_COLOR) - - self.play(Write(equation, run_time = 2)) - self.wait() - for term in terms[:2]: - self.multiply_by_z( - complex(0, 1), - added_anims = [ - Animation(equation), - Indicate(term, color = RED, run_time = 2) - ] - ) - self.wait() - self.play(Indicate(terms[-1], color = RED, run_time = 2)) - self.wait() - self.reset_plane(FadeOut(equation)) - - def talk_through_specific_example(self): - z = complex(2, 1) - angle = np.angle(z) - point = self.z_to_point(z) - dot = Dot(point, color = WHITE) - label = TexMobject("%d + %di"%(z.real, z.imag)) - label.add_background_rectangle() - label.next_to(dot, UP+RIGHT, buff = 0) - - brace = Brace( - Line(ORIGIN, self.z_to_point(np.sqrt(5))), - UP - ) - brace_text = brace.get_text("$\\sqrt{5}$") - brace_text.add_background_rectangle() - brace_text.scale(0.7, about_point = brace.get_top()) - brace.rotate(angle) - brace_text.rotate(angle).rotate_in_place(-angle) - VGroup(brace, brace_text).set_color(MAROON_B) - arc = Arc(angle, color = WHITE, radius = 0.5) - angle_label = TexMobject("30^\\circ") - angle_label.scale(0.7) - angle_label.next_to( - arc, RIGHT, - buff = SMALL_BUFF, aligned_edge = DOWN - ) - angle_label.set_color(MULTIPLIER_COLOR) - - self.play( - Write(label), - DrawBorderThenFill(dot) - ) - self.add_foreground_mobjects(label, dot) - self.wait() - self.multiply_by_z(z, run_time = 3) - self.wait() - self.reset_plane() - self.multiply_by_z( - np.exp(complex(0, 1)*angle), - added_anims = [ - ShowCreation(arc, run_time = 2), - Write(angle_label) - ] - ) - self.add_foreground_mobjects(arc, angle_label) - self.wait() - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.add_foreground_mobjects(brace, brace_text) - self.multiply_by_z(np.sqrt(5), run_time = 3) - self.wait(2) - to_remove = [ - label, dot, - brace, brace_text, - arc, angle_label, - ] - for mob in to_remove: - self.foreground_mobjects.remove(mob) - self.reset_plane(*list(map(FadeOut, to_remove))) - self.wait() - - def show_break_down(self): - positive_reals = Line(ORIGIN, FRAME_X_RADIUS*RIGHT) - positive_reals.set_color(MAROON_B) - circle = Circle( - radius = self.z_to_point(1)[0], - color = MULTIPLIER_COLOR - ) - real_actions = [3, 0.5, 1] - rotation_actions = [ - np.exp(complex(0, angle)) - for angle in np.linspace(0, 2*np.pi, 4)[1:] - ] - - self.play(ShowCreation(positive_reals)) - self.add_foreground_mobjects(positive_reals) - for last_z, z in zip([1]+real_actions, real_actions): - self.multiply_by_z(z/last_z) - self.wait() - self.play(ShowCreation(circle)) - self.add_foreground_mobjects(circle) - for last_z, z in zip([1]+rotation_actions, rotation_actions): - self.multiply_by_z(z/last_z, run_time = 3) - self.wait() - - def example_actions_broken_down(self): - z_list = [ - complex(2, -1), - complex(-2, -3), - complex(0.5, 0.5), - ] - for z in z_list: - dot = Dot(self.z_to_point(z)) - dot.set_color(WHITE) - dot.save_state() - dot.move_to(self.plane.one_dot) - dot.set_fill(opacity = 1) - - norm = np.abs(z) - angle = np.angle(z) - rot_z = np.exp(complex(0, angle)) - - self.play(dot.restore) - self.multiply_by_z(norm) - self.wait() - self.multiply_by_z(rot_z) - self.wait() - self.reset_plane(FadeOut(dot)) - - ## - - def multiply_by_z(self, z, run_time = 2, **kwargs): - target = self.plane.copy() - target.apply_complex_function(lambda w : z*w) - for dot in target.zero_dot, target.one_dot: - dot.set_width(2*self.dot_radius) - angle = np.angle(z) - kwargs["path_arc"] = kwargs.get("path_arc", angle) - self.play( - Transform(self.plane, target, run_time = run_time, **kwargs), - *kwargs.get("added_anims", []) - ) - - def reset_plane(self, *added_anims): - self.play(FadeOut(self.plane), *added_anims) - self.plane.restore() - self.play(FadeIn(self.plane)) - -class ExponentsAsRepeatedMultiplication(TeacherStudentsScene): - def construct(self): - self.show_repeated_multiplication() - self.show_non_counting_exponents() - - def show_repeated_multiplication(self): - three_twos = TexMobject("2 \\cdot 2 \\cdot 2") - five_twos = TexMobject("2 \\cdot "*4 + "2") - exponents = [] - teacher_corner = self.get_teacher().get_corner(UP+LEFT) - for twos in three_twos, five_twos: - twos.next_to(teacher_corner, UP) - twos.generate_target() - d = sum(np.array(list(twos.get_tex_string())) == "2") - exponents.append(d) - twos.brace = Brace(twos, UP) - twos.exp = twos.brace.get_text("$2^%d$"%d) - twos.generate_target() - twos.brace_anim = MaintainPositionRelativeTo( - VGroup(twos.brace, twos.exp), twos - ) - - self.play( - GrowFromCenter(three_twos.brace), - Write(three_twos.exp), - self.get_teacher().change_mode, "raise_right_hand", - ) - for mob in three_twos: - self.play(Write(mob, run_time = 1)) - self.change_student_modes(*["pondering"]*3) - self.wait(2) - self.play( - FadeIn(five_twos.brace), - FadeIn(five_twos.exp), - three_twos.center, - three_twos.to_edge, UP, 2*LARGE_BUFF, - three_twos.brace_anim, - ) - self.play(FadeIn( - five_twos, - run_time = 3, - lag_ratio = 0.5 - )) - self.wait(2) - - cdot = TexMobject("\\cdot") - lhs = TexMobject("2^{%d + %d} = "%tuple(exponents)) - rule = VGroup( - lhs, three_twos.target, cdot, five_twos.target - ) - rule.arrange() - lhs.next_to(three_twos.target, LEFT, aligned_edge = DOWN) - rule.next_to(self.get_pi_creatures(), UP) - - self.play( - MoveToTarget(three_twos), - three_twos.brace_anim, - MoveToTarget(five_twos), - five_twos.brace_anim, - Write(cdot), - self.get_teacher().change_mode, "happy", - ) - self.wait() - self.play(Write(lhs)) - self.wait() - self.change_student_modes(*["happy"]*3) - self.wait() - - general_equation = TexMobject("2^{x+y}=", "2^x", "2^y") - general_equation.to_edge(UP, buff = MED_LARGE_BUFF) - general_equation[0].set_color(GREEN_B) - VGroup(*general_equation[1:]).set_color(MULTIPLIER_COLOR) - self.play(*[ - ReplacementTransform( - mob.copy(), term, run_time = 2 - ) - for term, mob in zip(general_equation, [ - lhs, three_twos.exp, five_twos.exp - ]) - ]) - self.wait(2) - - self.exponential_rule = general_equation - self.expanded_exponential_rule = VGroup( - lhs, three_twos, three_twos.brace, three_twos.exp, - cdot, five_twos, five_twos.brace, five_twos.exp, - ) - - def show_non_counting_exponents(self): - self.play( - self.expanded_exponential_rule.scale, 0.5, - self.expanded_exponential_rule.to_corner, UP+LEFT - ) - half_power, neg_power, imag_power = alt_powers = VGroup( - TexMobject("2^{1/2}"), - TexMobject("2^{-1}"), - TexMobject("2^{i}"), - ) - alt_powers.arrange(RIGHT, buff = LARGE_BUFF) - alt_powers.next_to(self.get_students(), UP, buff = LARGE_BUFF) - - self.play( - Write(half_power, run_time = 2), - *[ - ApplyMethod(pi.change_mode, "pondering") - for pi in self.get_pi_creatures() - ] - ) - for mob in alt_powers[1:]: - self.play(Write(mob, run_time = 1)) - self.wait() - self.wait() - self.play(*it.chain(*[ - [pi.change_mode, "confused", pi.look_at, half_power] - for pi in self.get_students() - ])) - for power in alt_powers[:2]: - self.play(Indicate(power)) - self.wait() - self.wait() - - self.teacher_says("Extend the \\\\ definition") - self.change_student_modes("pondering", "confused", "erm") - self.wait() - - half_expression = TexMobject( - "\\big(", "2^{1/2}", "\\big)", - "\\big(2^{1/2}\\big) = 2^{1}" - ) - neg_one_expression = TexMobject( - "\\big(", "2^{-1}", "\\big)", - "\\big( 2^{1} \\big) = 2^{0}" - ) - expressions = VGroup(half_expression, neg_one_expression) - expressions.arrange( - DOWN, aligned_edge = LEFT, buff = MED_LARGE_BUFF - ) - expressions.next_to(self.get_students(), UP, buff = LARGE_BUFF) - expressions.to_edge(LEFT) - - self.play( - Transform(half_power, half_expression[1]), - Write(half_expression), - RemovePiCreatureBubble(self.get_teacher()), - ) - self.wait() - self.play( - Transform(neg_power, neg_one_expression[1]), - Write(neg_one_expression) - ) - self.wait(2) - self.play( - self.exponential_rule.next_to, - self.get_teacher().get_corner(UP+LEFT), UP, MED_LARGE_BUFF, - self.get_teacher().change_mode, "raise_right_hand", - ) - self.wait(2) - self.play( - imag_power.move_to, UP, - imag_power.scale_in_place, 1.5, - imag_power.set_color, BLUE, - self.exponential_rule.to_edge, RIGHT, - self.get_teacher().change_mode, "speaking" - ) - self.play(*it.chain(*[ - [pi.change_mode, "pondering", pi.look_at, imag_power] - for pi in self.get_students() - ])) - self.wait() - - group_theory_words = TextMobject("Group theory?") - group_theory_words.next_to( - self.exponential_rule, UP, buff = LARGE_BUFF - ) - arrow = Arrow( - group_theory_words, - self.exponential_rule, - color = WHITE, - buff = SMALL_BUFF - ) - group_theory_words.shift_onto_screen() - self.play( - Write(group_theory_words), - ShowCreation(arrow) - ) - self.wait(2) - -class ExponentsAsHomomorphism(Scene): - CONFIG = { - "top_line_center" : 2.5*UP, - "top_line_config" : { - "x_min" : -16, - "x_max" : 16, - }, - "bottom_line_center" : 2.5*DOWN, - "bottom_line_config" : { - "x_min" : -FRAME_WIDTH, - "x_max" : FRAME_WIDTH, - } - } - def construct(self): - self.comment_on_equation() - self.show_adders() - self.show_multipliers() - self.confused_at_mapping() - self.talk_through_composition() - self.add_quote() - - def comment_on_equation(self): - equation = TexMobject( - "2", "^{x", "+", "y}", "=", "2^x", "2^y" - ) - lhs = VGroup(*equation[:4]) - rhs = VGroup(*equation[5:]) - lhs_brace = Brace(lhs, UP) - lhs_text = lhs_brace.get_text("Add inputs") - lhs_text.set_color(GREEN_B) - rhs_brace = Brace(rhs, DOWN) - rhs_text = rhs_brace.get_text("Multiply outputs") - rhs_text.set_color(MULTIPLIER_COLOR) - - self.add(equation) - for brace, text in (lhs_brace, lhs_text), (rhs_brace, rhs_text): - self.play( - GrowFromCenter(brace), - Write(text) - ) - self.wait() - self.wait() - - self.equation = equation - self.lhs_brace_group = VGroup(lhs_brace, lhs_text) - self.rhs_brace_group = VGroup(rhs_brace, rhs_text) - - def show_adders(self): - equation = self.equation - adders = VGroup(equation[1], equation[3]).copy() - top_line = NumberLine(**self.top_line_config) - top_line.add_numbers() - top_line.shift(self.top_line_center) - - self.play( - adders.scale, 1.5, - adders.center, - adders.space_out_submobjects, 2, - adders.to_edge, UP, - adders.set_color, GREEN_B, - FadeOut(self.lhs_brace_group), - Write(top_line) - ) - self.wait() - for x in 3, 5, -8: - self.play(top_line.shift, x*RIGHT, run_time = 2) - self.wait() - - self.top_line = top_line - self.adders = adders - - def show_multipliers(self): - equation = self.equation - multipliers = VGroup(*self.equation[-2:]).copy() - - bottom_line = NumberLine(**self.bottom_line_config) - bottom_line.add_numbers() - bottom_line.shift(self.bottom_line_center) - - self.play( - multipliers.space_out_submobjects, 4, - multipliers.next_to, self.bottom_line_center, - UP, MED_LARGE_BUFF, - multipliers.set_color, YELLOW, - FadeOut(self.rhs_brace_group), - Write(bottom_line), - ) - stretch_kwargs = { - } - for x in 3, 1./5, 5./3: - self.play( - self.get_stretch_anim(bottom_line, x), - run_time = 3 - ) - self.wait() - - self.bottom_line = bottom_line - self.multipliers = multipliers - - def confused_at_mapping(self): - arrow = Arrow( - self.top_line.get_bottom()[1]*UP, - self.bottom_line.get_top()[1]*UP, - color = WHITE - ) - randy = Randolph(mode = "confused") - randy.scale(0.75) - randy.flip() - randy.next_to(arrow, RIGHT, LARGE_BUFF) - randy.look_at(arrow.get_top()) - - self.play(self.equation.to_edge, LEFT) - self.play( - ShowCreation(arrow), - FadeIn(randy) - ) - self.play(randy.look_at, arrow.get_bottom()) - self.play(Blink(randy)) - self.wait() - for x in 1, -2, 3, 1, -3: - self.play( - self.get_stretch_anim(self.bottom_line, 2**x), - self.top_line.shift, x*RIGHT, - randy.look_at, self.top_line, - run_time = 2 - ) - if random.random() < 0.3: - self.play(Blink(randy)) - else: - self.wait() - - self.randy = randy - - def talk_through_composition(self): - randy = self.randy - terms = list(self.adders) + list(self.multipliers) - inputs = [-1, 2] - target_texs = list(map(str, inputs)) - target_texs += ["2^{%d}"%x for x in inputs] - for mob, target_tex in zip(terms, target_texs): - target = TexMobject(target_tex) - target.set_color(mob[0].get_color()) - target.move_to(mob, DOWN) - if mob in self.adders: - target.to_edge(UP) - mob.target = target - - self.play( - self.equation.next_to, ORIGIN, LEFT, MED_LARGE_BUFF, - randy.change_mode, "pondering", - randy.look_at, self.equation - ) - self.wait() - self.play(randy.look_at, self.top_line) - self.show_composition( - *inputs, - parallel_anims = list(map(MoveToTarget, self.adders)) - ) - self.play( - FocusOn(self.bottom_line_center), - randy.look_at, self.bottom_line_center, - ) - self.show_composition( - *inputs, - parallel_anims = list(map(MoveToTarget, self.multipliers)) - ) - self.wait() - - def add_quote(self): - brace = Brace(self.equation, UP) - quote = TextMobject("``Preserves the group structure''") - quote.add_background_rectangle() - quote.next_to(brace, UP) - - self.play( - GrowFromCenter(brace), - Write(quote), - self.randy.look_at, quote, - ) - self.play(self.randy.change_mode, "thinking") - self.play(Blink(self.randy)) - self.wait() - self.show_composition(-1, 2) - self.wait() - - #### - - def show_composition(self, *inputs, **kwargs): - parallel_anims = kwargs.get("parallel_anims", []) - for x in range(len(inputs) - len(parallel_anims)): - parallel_anims.append(Animation(Mobject())) - - for line in self.top_line, self.bottom_line: - line.save_state() - - for x, parallel_anim in zip(inputs, parallel_anims): - anims = [ - ApplyMethod(self.top_line.shift, x*RIGHT), - self.get_stretch_anim(self.bottom_line, 2**x), - ] - for anim in anims: - anim.set_run_time(2) - self.play(parallel_anim) - self.play(*anims) - self.wait() - self.play(*[ - line.restore - for line in (self.top_line, self.bottom_line) - ]) - - def get_stretch_anim(self, bottom_line, x): - target = bottom_line.copy() - target.stretch_about_point( - x, 0, self.bottom_line_center, - ) - for number in target.numbers: - number.stretch_in_place(1./x, dim = 0) - return Transform(bottom_line, target) - -class DihedralCubeHomomorphism(GroupOfCubeSymmetries, SymmetriesOfSquare): - def construct(self): - angle_axis_pairs = [ - (np.pi/2, OUT), - (np.pi, RIGHT), - (np.pi, OUT), - (np.pi, UP+RIGHT), - (-np.pi/2, OUT), - (np.pi, UP+LEFT), - ] - angle_axis_pairs *= 3 - - title = TextMobject( - "``", "Homo", "morph", "ism", "''", - arg_separator = "" - ) - homo_brace = Brace(title[1], UP, buff = SMALL_BUFF) - homo_def = homo_brace.get_text("same") - morph_brace = Brace(title[2], UP, buff = SMALL_BUFF) - morph_def = morph_brace.get_text("shape", buff = SMALL_BUFF) - def_group = VGroup( - homo_brace, homo_def, - morph_brace, morph_def - ) - VGroup(title, def_group).to_edge(UP) - homo_group = VGroup(title[1], homo_brace, homo_def) - morph_group = VGroup(title[2], morph_brace, morph_def) - - equation = TexMobject("f(X \\circ Y) = f(X) \\circ f(Y)") - equation.next_to(title, DOWN) - - self.add(title, equation) - - arrow = Arrow(LEFT, RIGHT) - cube = self.get_cube() - cube.next_to(arrow, RIGHT) - pose_matrix = self.get_pose_matrix() - - square = self.square = Square(**self.square_config) - self.add_randy_to_square(square) - square.next_to(arrow, LEFT) - - VGroup(square, arrow, cube).next_to( - equation, DOWN, buff = MED_LARGE_BUFF - ) - - self.add(square, cube) - self.play(ShowCreation(arrow)) - for i, (angle, raw_axis) in enumerate(angle_axis_pairs): - posed_axis = np.dot(raw_axis, pose_matrix.T) - self.play(*[ - Rotate( - mob, angle = angle, axis = axis, - in_place = True, - run_time = abs(angle/(np.pi/2)) - ) - for mob, axis in [(square, raw_axis), (cube, posed_axis)] - ]) - self.wait() - if i == 2: - for group, color in (homo_group, YELLOW), (morph_group, BLUE): - part, remainder = group[0], VGroup(*group[1:]) - remainder.set_color(color) - self.play( - part.set_color, color, - FadeIn(remainder) - ) - -class ComplexExponentiationAbstract(): - CONFIG = { - "start_base" : 2, - "new_base" : 5, - "group_type" : None, - "color" : None, - "vect" : None, - } - def construct(self): - self.base = self.start_base - example_inputs = [2, -3, 1] - self.add_vertical_line() - self.add_plane_unanimated() - self.add_title() - self.add_arrow() - self.show_example(complex(1, 1)) - self.draw_real_line() - self.show_real_actions(*example_inputs) - self.show_pure_imaginary_actions(*example_inputs) - self.set_color_vertical_line() - self.set_color_unit_circle() - self.show_pure_imaginary_actions(*example_inputs) - self.walk_input_up_vertical() - self.change_base(self.new_base, str(self.new_base)) - self.walk_input_up_vertical() - self.change_base(np.exp(1), "e") - self.take_steps_for_e() - self.write_eulers_formula() - self.show_pure_imaginary_actions(-np.pi, np.pi) - self.wait() - - def add_vertical_line(self): - line = Line(FRAME_Y_RADIUS*UP, FRAME_Y_RADIUS*DOWN) - line.set_stroke(color = self.color, width = 10) - line.shift(-FRAME_X_RADIUS*self.vect/2) - self.add(line) - self.add_foreground_mobjects(line) - - def add_plane_unanimated(self): - should_skip_animations = self.skip_animations - self.skip_animations = True - self.add_plane() - self.skip_animations = should_skip_animations - - def add_title(self): - title = TextMobject(self.group_type, "group") - title.scale(0.8) - title[0].set_color(self.color) - title.add_background_rectangle() - title.to_edge(UP, buff = MED_SMALL_BUFF) - self.add_foreground_mobjects(title) - - def add_arrow(self): - arrow = Arrow(LEFT, RIGHT, color = WHITE) - arrow.move_to(-FRAME_X_RADIUS*self.vect/2 + 2*UP) - arrow.set_stroke(width = 6), - func_mob = TexMobject("2^x") - func_mob.next_to(arrow, UP, aligned_edge = LEFT) - func_mob.add_background_rectangle() - - self.add_foreground_mobjects(arrow, func_mob) - self.wait() - self.func_mob = func_mob - - def show_example(self, z): - self.apply_action( - z, - run_time = 5, - rate_func = there_and_back - ) - - def draw_real_line(self): - line = VGroup(Line(ORIGIN, FRAME_X_RADIUS*RIGHT)) - if self.vect[0] < 0: - line.add(Line(ORIGIN, FRAME_X_RADIUS*LEFT)) - line.set_color(RED) - - self.play(*list(map(ShowCreation, line)), run_time = 3) - self.add_foreground_mobjects(line) - - self.real_line = line - - def show_real_actions(self, *example_inputs): - for x in example_inputs: - self.apply_action(x) - self.wait() - - def show_pure_imaginary_actions(self, *example_input_imag_parts): - for y in example_input_imag_parts: - self.apply_action(complex(0, y), run_time = 3) - self.wait() - - def change_base(self, new_base, new_base_tex): - new_func_mob = TexMobject(new_base_tex + "^x") - new_func_mob.add_background_rectangle() - new_func_mob.move_to(self.func_mob) - - self.play(FocusOn(self.func_mob)) - self.play(Transform(self.func_mob, new_func_mob)) - self.wait() - self.base = new_base - - def write_eulers_formula(self): - formula = TexMobject("e^", "{\\pi", "i}", "=", "-1") - VGroup(*formula[1:3]).set_color(ADDER_COLOR) - formula[-1].set_color(MULTIPLIER_COLOR) - formula.scale(1.5) - formula.next_to(ORIGIN, UP) - formula.shift(-FRAME_X_RADIUS*self.vect/2) - for part in formula: - part.add_to_back(BackgroundRectangle(part)) - - Scene.play(self, Write(formula)) - self.add_foreground_mobjects(formula) - self.wait(2) - -class ComplexExponentiationAdderHalf( - ComplexExponentiationAbstract, - AdditiveGroupOfComplexNumbers - ): - CONFIG = { - "group_type" : "Additive", - "color" : GREEN_B, - "vect" : LEFT, - } - def construct(self): - ComplexExponentiationAbstract.construct(self) - - def apply_action(self, z, run_time = 2, **kwargs): - kwargs["run_time"] = run_time - self.play( - ApplyMethod( - self.plane.shift, self.z_to_point(z), - **kwargs - ), - *kwargs.get("added_anims", []) - ) - - def set_color_vertical_line(self): - line = VGroup( - Line(ORIGIN, FRAME_Y_RADIUS*UP), - Line(ORIGIN, FRAME_Y_RADIUS*DOWN), - ) - line.set_color(YELLOW) - - self.play( - FadeOut(self.real_line), - *list(map(ShowCreation, line)) - ) - self.foreground_mobjects.remove(self.real_line) - self.play( - line.rotate, np.pi/24, - rate_func = wiggle, - ) - self.wait() - - self.foreground_mobjects = [line] + self.foreground_mobjects - self.vertical_line = line - - def set_color_unit_circle(self): - line = VGroup( - Line(ORIGIN, FRAME_Y_RADIUS*UP), - Line(ORIGIN, FRAME_Y_RADIUS*DOWN), - ) - line.set_color(YELLOW) - for submob in line: - submob.insert_n_curves(10) - submob.make_smooth() - circle = VGroup( - Circle(), - Circle().flip(RIGHT), - ) - circle.set_color(YELLOW) - circle.shift(FRAME_X_RADIUS*RIGHT) - - self.play(ReplacementTransform( - line, circle, run_time = 3 - )) - self.remove(circle) - self.wait() - - def walk_input_up_vertical(self): - arrow = Arrow(ORIGIN, UP, buff = 0, tip_length = 0.15) - arrow.set_color(GREEN) - brace = Brace(arrow, RIGHT, buff = SMALL_BUFF) - brace_text = brace.get_text("1 unit") - brace_text.add_background_rectangle() - - Scene.play(self, ShowCreation(arrow)) - self.add_foreground_mobjects(arrow) - self.play( - GrowFromCenter(brace), - Write(brace_text, run_time = 1) - ) - self.add_foreground_mobjects(brace, brace_text) - self.wait() - self.apply_action(complex(0, 1)) - self.wait(7)##Line up with MultiplierHalf - - to_remove = arrow, brace, brace_text - for mob in to_remove: - self.foreground_mobjects.remove(mob) - self.play(*list(map(FadeOut, to_remove))) - self.apply_action(complex(0, -1)) - - def take_steps_for_e(self): - slide_values = [1, 1, 1, np.pi-3] - braces = [ - Brace(Line(ORIGIN, x*UP), RIGHT, buff = SMALL_BUFF) - for x in np.cumsum(slide_values) - ] - labels = list(map(TextMobject, [ - "1 unit", - "2 units", - "3 units", - "$\\pi$ units", - ])) - for label, brace in zip(labels, braces): - label.add_background_rectangle() - label.next_to(brace, RIGHT, buff = SMALL_BUFF) - - curr_brace = None - curr_label = None - for slide_value, label, brace in zip(slide_values, labels, braces): - self.apply_action(complex(0, slide_value)) - if curr_brace is None: - curr_brace = brace - curr_label = label - self.play( - GrowFromCenter(curr_brace), - Write(curr_label) - ) - self.add_foreground_mobjects(brace, label) - else: - self.play( - Transform(curr_brace, brace), - Transform(curr_label, label), - ) - self.wait() - self.wait(4) ##Line up with multiplier half - -class ComplexExponentiationMultiplierHalf( - ComplexExponentiationAbstract, - MultiplicativeGroupOfComplexNumbers - ): - CONFIG = { - "group_type" : "Multiplicative", - "color" : MULTIPLIER_COLOR, - "vect" : RIGHT, - } - - def construct(self): - ComplexExponentiationAbstract.construct(self) - - def apply_action(self, z, run_time = 2, **kwargs): - kwargs["run_time"] = run_time - self.multiply_by_z(self.base**z, **kwargs) - - def set_color_vertical_line(self): - self.play(FadeOut(self.real_line)) - self.foreground_mobjects.remove(self.real_line) - self.wait(2) - - def set_color_unit_circle(self): - line = VGroup( - Line(ORIGIN, FRAME_Y_RADIUS*UP), - Line(ORIGIN, FRAME_Y_RADIUS*DOWN), - ) - line.set_color(YELLOW) - line.shift(FRAME_X_RADIUS*LEFT) - for submob in line: - submob.insert_n_curves(10) - submob.make_smooth() - circle = VGroup( - Circle(), - Circle().flip(RIGHT), - ) - circle.set_color(YELLOW) - - self.play(ReplacementTransform( - line, circle, run_time = 3 - )) - self.add_foreground_mobjects(circle) - self.wait() - - def walk_input_up_vertical(self): - output_z = self.base**complex(0, 1) - angle = np.angle(output_z) - - arc, brace, curved_brace, radians_label = \ - self.get_arc_braces_and_label(angle) - - self.wait(3) - self.apply_action(complex(0, 1)) - - Scene.play(self, ShowCreation(arc)) - self.add_foreground_mobjects(arc) - self.play(GrowFromCenter(brace)) - self.play(Transform(brace, curved_brace)) - self.play(Write(radians_label, run_time = 2)) - self.wait(2) - - self.foreground_mobjects.remove(arc) - self.play(*list(map(FadeOut, [arc, brace, radians_label]))) - self.apply_action(complex(0, -1)) - - def get_arc_braces_and_label(self, angle): - arc = Arc(angle) - arc.set_stroke(GREEN, width = 6) - arc_line = Line(RIGHT, RIGHT+angle*UP) - brace = Brace(arc_line, RIGHT, buff = 0) - for submob in brace.family_members_with_points(): - submob.insert_n_curves(10) - curved_brace = brace.copy() - curved_brace.shift(LEFT) - curved_brace.apply_complex_function( - np.exp, maintain_smoothness = False - ) - - half_point = arc.point_from_proportion(0.5) - radians_label = TexMobject("%.3f"%angle) - radians_label.add_background_rectangle() - radians_label.next_to( - 1.5*half_point, np.round(half_point), buff = 0 - ) - - return arc, brace, curved_brace, radians_label - - def take_steps_for_e(self): - angles = [1, 2, 3, np.pi] - - curr_brace = None - curr_label = None - curr_arc = None - for last_angle, angle in zip([0]+angles, angles): - arc, brace, curved_brace, label = self.get_arc_braces_and_label(angle) - if angle == np.pi: - label = TexMobject("%.5f\\dots"%np.pi) - label.add_background_rectangle(opacity = 1) - label.next_to(curved_brace, UP, buff = SMALL_BUFF) - - self.apply_action(complex(0, angle-last_angle)) - self.wait(2)#Line up with Adder half - if curr_brace is None: - curr_brace = curved_brace - curr_label = label - curr_arc = arc - brace.set_fill(opacity = 0) - Scene.play(self, ShowCreation(curr_arc)) - self.add_foreground_mobjects(curr_arc) - self.play( - ReplacementTransform(brace, curr_brace), - Write(curr_label) - ) - self.add_foreground_mobjects(curr_brace, curr_label) - else: - Scene.play(self, ShowCreation(arc)) - self.add_foreground_mobjects(arc) - self.foreground_mobjects.remove(curr_arc) - self.remove(curr_arc) - curr_arc = arc - self.play( - Transform(curr_brace, curved_brace), - Transform(curr_label, label), - ) - self.wait() - self.wait() - -class ExpComplexHomomorphismPreviewAbstract(ComplexExponentiationAbstract): - def construct(self): - self.base = self.start_base - - self.add_vertical_line() - self.add_plane_unanimated() - self.add_title() - self.add_arrow() - self.change_base(np.exp(1), "e") - self.write_eulers_formula() - self.show_pure_imaginary_actions(np.pi, 0, -np.pi) - self.wait() - -class ExpComplexHomomorphismPreviewAdderHalf( - ExpComplexHomomorphismPreviewAbstract, - ComplexExponentiationAdderHalf - ): - def construct(self): - ExpComplexHomomorphismPreviewAbstract.construct(self) - -class ExpComplexHomomorphismPreviewMultiplierHalf( - ExpComplexHomomorphismPreviewAbstract, - ComplexExponentiationMultiplierHalf - ): - def construct(self): - ExpComplexHomomorphismPreviewAbstract.construct(self) - -class WhyE(TeacherStudentsScene): - def construct(self): - self.student_says("Why e?") - self.play(self.get_teacher().change_mode, "pondering") - self.wait(3) - -class ReadFormula(Scene): - def construct(self): - formula = TexMobject("e^", "{\\pi i}", "=", "-1") - formula[1].set_color(GREEN_B) - formula[3].set_color(MULTIPLIER_COLOR) - formula.scale(2) - - randy = Randolph() - randy.shift(2*LEFT) - formula.next_to(randy, RIGHT, aligned_edge = UP) - randy.look_at(formula) - - self.add(randy, formula) - self.wait() - self.play(randy.change_mode, "thinking") - self.wait() - self.play(Blink(randy)) - self.wait(3) - -class EfvgtPatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "Meshal Alshammari", - "CrypticSwarm ", - "Justin Helps", - "Ankit Agarwal", - "Yu Jun", - "Shelby Doolittle", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Markus Persson", - "Dan Buchoff", - "Derek Dai", - "Joseph John Cox", - "Luc Ritchie", - "Nils Schneider", - "Mathew Bramson", - "Guido Gambardella", - "Jerry Ling", - "Mark Govea", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Kirk Werklund", - "Ripta Pasay", - "Felipe Diniz", - ] - } - -class EmeraldLogo(SVGMobject): - CONFIG = { - "file_name" : "emerald_logo", - "stroke_width" : 0, - "fill_opacity" : 1, - # "helix_color" : "#439271", - "helix_color" : GREEN_E, - } - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - self.set_height(1) - for submob in self.split()[18:]: - submob.set_color(self.helix_color) - -class ECLPromo(PiCreatureScene): - CONFIG = { - "seconds_to_blink" : 4, - } - def construct(self): - logo = EmeraldLogo() - logo.to_corner(UP+LEFT, buff = MED_SMALL_BUFF) - logo_part1 = VGroup(*logo[:15]) - logo_part2 = VGroup(*logo[15:]) - - rect = Rectangle(height = 9, width = 16) - rect.set_height(5) - rect.next_to(logo, DOWN) - rect.to_edge(LEFT) - - self.play( - self.pi_creature.change_mode, "hooray", - ShowCreation(rect) - ) - self.wait(3) - self.play(FadeIn( - logo_part1, run_time = 3, - lag_ratio = 0.5 - )) - logo_part2.save_state() - logo_part2.scale(2) - logo_part2.next_to(self.pi_creature.get_corner(UP+LEFT), UP) - logo_part2.shift(MED_SMALL_BUFF*RIGHT) - self.play( - self.pi_creature.change_mode, "raise_right_hand", - ) - self.play(DrawBorderThenFill(logo_part2)) - self.play( - logo_part2.scale_in_place, 0.5, - logo_part2.to_edge, UP - ) - self.play( - logo_part2.restore, - self.pi_creature.change_mode, "happy" - ) - self.play(self.pi_creature.look_at, rect) - self.wait(10) - self.play( - self.pi_creature.change_mode, "pondering", - self.pi_creature.look, DOWN - ) - self.wait(10) - -class ExpTransformation(ComplexTransformationScene): - CONFIG = { - "camera_class": ThreeDCamera, - } - def construct(self): - self.camera.camera_distance = 10, - self.add_transformable_plane() - self.prepare_for_transformation(self.plane) - final_plane = self.plane.copy().apply_complex_function(np.exp) - cylinder = self.plane.copy().apply_function( - lambda x_y_z : np.array([x_y_z[0], np.sin(x_y_z[1]), -np.cos(x_y_z[1])]) - ) - title = TexMobject("x \\to e^x") - title.add_background_rectangle() - title.scale(1.5) - title.next_to(ORIGIN, RIGHT) - title.to_edge(UP, buff = MED_SMALL_BUFF) - self.add_foreground_mobjects(title) - - self.play(Transform( - self.plane, cylinder, - run_time = 3, - path_arc_axis = RIGHT, - path_arc = np.pi, - )) - self.play(Rotate( - self.plane, -np.pi/3, UP, - run_time = 5 - )) - self.play(Transform(self.plane, final_plane, run_time = 3)) - self.wait(3) - -class Thumbnail(Scene): - def construct(self): - formula = TexMobject("e^", "{\\pi i}", "=", "-1") - formula[1].set_color(GREEN_B) - formula[3].set_color(YELLOW) - formula.scale(4) - formula.to_edge(UP, buff = LARGE_BUFF) - self.add(formula) - - via = TextMobject("via") - via.scale(2) - groups = TextMobject("Group theory") - groups.scale(3) - groups.to_edge(DOWN) - via.move_to(VGroup(formula, groups)) - self.add(via, groups) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eoc/chapter1.py b/from_3b1b/old/eoc/chapter1.py deleted file mode 100644 index a51dc690..00000000 --- a/from_3b1b/old/eoc/chapter1.py +++ /dev/null @@ -1,2804 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eoc.chapter2 import Car, MoveCar - -class CircleScene(PiCreatureScene): - CONFIG = { - "radius" : 1.5, - "stroke_color" : WHITE, - "fill_color" : BLUE_E, - "fill_opacity" : 0.75, - "radial_line_color" : MAROON_B, - "outer_ring_color" : GREEN_E, - "ring_colors" : [BLUE, GREEN], - "dR" : 0.1, - "dR_color" : YELLOW, - "unwrapped_tip" : ORIGIN, - "include_pi_creature" : False, - "circle_corner" : UP+LEFT, - } - def setup(self): - PiCreatureScene.setup(self) - self.circle = Circle( - radius = self.radius, - stroke_color = self.stroke_color, - fill_color = self.fill_color, - fill_opacity = self.fill_opacity, - ) - self.circle.to_corner(self.circle_corner, buff = MED_LARGE_BUFF) - self.radius_line = Line( - self.circle.get_center(), - self.circle.get_right(), - color = self.radial_line_color - ) - self.radius_brace = Brace(self.radius_line, buff = SMALL_BUFF) - self.radius_label = self.radius_brace.get_text("$R$", buff = SMALL_BUFF) - - self.radius_group = VGroup( - self.radius_line, self.radius_brace, self.radius_label - ) - self.add(self.circle, *self.radius_group) - - if not self.include_pi_creature: - self.remove(self.get_primary_pi_creature()) - - def introduce_circle(self, added_anims = []): - self.remove(self.circle) - self.play( - ShowCreation(self.radius_line), - GrowFromCenter(self.radius_brace), - Write(self.radius_label), - ) - self.circle.set_fill(opacity = 0) - - self.play( - Rotate( - self.radius_line, 2*np.pi-0.001, - about_point = self.circle.get_center(), - ), - ShowCreation(self.circle), - *added_anims, - run_time = 2 - ) - self.play( - self.circle.set_fill, self.fill_color, self.fill_opacity, - Animation(self.radius_line), - Animation(self.radius_brace), - Animation(self.radius_label), - ) - - def increase_radius(self, numerical_dr = True, run_time = 2): - radius_mobs = VGroup( - self.radius_line, self.radius_brace, self.radius_label - ) - nudge_line = Line( - self.radius_line.get_right(), - self.radius_line.get_right() + self.dR*RIGHT, - color = self.dR_color - ) - nudge_arrow = Arrow( - nudge_line.get_center() + 0.5*RIGHT+DOWN, - nudge_line.get_center(), - color = YELLOW, - buff = SMALL_BUFF, - tip_length = 0.2, - ) - if numerical_dr: - nudge_label = TexMobject("%.01f"%self.dR) - else: - nudge_label = TexMobject("dr") - nudge_label.set_color(self.dR_color) - nudge_label.scale(0.75) - nudge_label.next_to(nudge_arrow.get_start(), DOWN) - - radius_mobs.add(nudge_line, nudge_arrow, nudge_label) - - outer_ring = self.get_outer_ring() - - self.play( - FadeIn(outer_ring), - ShowCreation(nudge_line), - ShowCreation(nudge_arrow), - Write(nudge_label), - run_time = run_time/2. - ) - self.wait(run_time/2.) - self.nudge_line = nudge_line - self.nudge_arrow = nudge_arrow - self.nudge_label = nudge_label - self.outer_ring = outer_ring - return outer_ring - - def get_ring(self, radius, dR, color = GREEN): - ring = Circle(radius = radius + dR).center() - inner_ring = Circle(radius = radius) - inner_ring.rotate(np.pi, RIGHT) - ring.append_vectorized_mobject(inner_ring) - ring.set_stroke(width = 0) - ring.set_fill(color) - ring.move_to(self.circle) - ring.R = radius - ring.dR = dR - return ring - - def get_rings(self, **kwargs): - dR = kwargs.get("dR", self.dR) - colors = kwargs.get("colors", self.ring_colors) - radii = np.arange(0, self.radius, dR) - colors = color_gradient(colors, len(radii)) - - rings = VGroup(*[ - self.get_ring(radius, dR = dR, color = color) - for radius, color in zip(radii, colors) - ]) - return rings - - def get_outer_ring(self): - return self.get_ring( - radius = self.radius, dR = self.dR, - color = self.outer_ring_color - ) - - def unwrap_ring(self, ring, **kwargs): - self.unwrap_rings(ring, **kwargs) - - def unwrap_rings(self, *rings, **kwargs): - added_anims = kwargs.get("added_anims", []) - rings = VGroup(*rings) - unwrapped = VGroup(*[ - self.get_unwrapped(ring, **kwargs) - for ring in rings - ]) - self.play( - rings.rotate, np.pi/2, - rings.next_to, unwrapped.get_bottom(), UP, - run_time = 2, - path_arc = np.pi/2, - ) - self.play( - Transform(rings, unwrapped, run_time = 3), - *added_anims - ) - - def get_unwrapped(self, ring, to_edge = LEFT, **kwargs): - R = ring.R - R_plus_dr = ring.R + ring.dR - n_anchors = ring.get_num_curves() - result = VMobject() - result.set_points_as_corners([ - interpolate(np.pi*R_plus_dr*LEFT, np.pi*R_plus_dr*RIGHT, a) - for a in np.linspace(0, 1, n_anchors/2) - ]+[ - interpolate(np.pi*R*RIGHT+ring.dR*UP, np.pi*R*LEFT+ring.dR*UP, a) - for a in np.linspace(0, 1, n_anchors/2) - ]) - result.set_style_data( - stroke_color = ring.get_stroke_color(), - stroke_width = ring.get_stroke_width(), - fill_color = ring.get_fill_color(), - fill_opacity = ring.get_fill_opacity(), - ) - result.move_to(self.unwrapped_tip, aligned_edge = DOWN) - result.shift(R_plus_dr*DOWN) - if to_edge is not None: - result.to_edge(to_edge) - - return result - - def create_pi_creature(self): - self.pi_creature = Randolph(color = BLUE_C) - self.pi_creature.to_corner(DOWN+LEFT) - return self.pi_creature - - -############# - -class Chapter1OpeningQuote(OpeningQuote): - CONFIG = { - "quote" : [ - """The art of doing mathematics is finding - that """, "special case", - """that contains all the - germs of generality.""" - ], - "quote_arg_separator" : " ", - "highlighted_quote_terms" : { - "special case" : BLUE - }, - "author" : "David Hilbert", - } - -class Introduction(TeacherStudentsScene): - def construct(self): - self.show_series() - self.show_many_facts() - self.invent_calculus() - - def show_series(self): - series = VideoSeries() - series.to_edge(UP) - this_video = series[0] - this_video.set_color(YELLOW) - this_video.save_state() - this_video.set_fill(opacity = 0) - this_video.center() - this_video.set_height(FRAME_HEIGHT) - self.this_video = this_video - - - words = TextMobject( - "Welcome to \\\\", - "Essence of calculus" - ) - words.set_color_by_tex("Essence of calculus", YELLOW) - - self.teacher.change_mode("happy") - self.play( - FadeIn( - series, - lag_ratio = 0.5, - run_time = 2 - ), - Blink(self.get_teacher()) - ) - self.teacher_says(words, target_mode = "hooray") - self.change_student_modes( - *["hooray"]*3, - look_at_arg = series[1].get_left(), - added_anims = [ - ApplyMethod(this_video.restore, run_time = 3), - ] - ) - self.play(*[ - ApplyMethod( - video.shift, 0.5*video.get_height()*DOWN, - run_time = 3, - rate_func = squish_rate_func( - there_and_back, alpha, alpha+0.3 - ) - ) - for video, alpha in zip(series, np.linspace(0, 0.7, len(series))) - ]+[ - Animation(self.teacher.bubble), - Animation(self.teacher.bubble.content), - ]) - - essence_words = words.get_part_by_tex("Essence").copy() - self.play( - FadeOut(self.teacher.bubble), - FadeOut(self.teacher.bubble.content), - essence_words.next_to, series, DOWN, - *[ - ApplyMethod(pi.change_mode, "pondering") - for pi in self.get_pi_creatures() - ] - ) - self.wait(3) - - self.series = series - self.essence_words = essence_words - - def show_many_facts(self): - rules = list(it.starmap(TexMobject, [ - ("{d(", "x", "^2)", "\\over \\,", "dx}", "=", "2", "x"), - ( - "d(", "f", "g", ")", "=", - "f", "dg", "+", "g", "df", - ), - ( - "F(x)", "=", "\\int_0^x", - "\\frac{dF}{dg}(t)\\,", "dt" - ), - ( - "f(x)", "=", "\\sum_{n = 0}^\\infty", - "f^{(n)}(a)", "\\frac{(x-a)^n}{n!}" - ), - ])) - video_indices = [2, 3, 7, 10] - tex_to_color = [ - ("x", BLUE), - ("f", BLUE), - ("df", BLUE), - ("g", YELLOW), - ("dg", YELLOW), - ("f(x)", BLUE), - ( "f^{(n)}(a)", BLUE), - ] - - for rule in rules: - for tex, color in tex_to_color: - rule.set_color_by_tex(tex, color, substring = False) - rule.next_to(self.teacher.get_corner(UP+LEFT), UP) - rule.shift_onto_screen() - - student_index = 1 - student = self.get_students()[student_index] - self.change_student_modes( - "pondering", "sassy", "pondering", - look_at_arg = self.teacher.eyes, - added_anims = [ - self.teacher.change_mode, "plain" - ] - ) - self.wait(2) - self.play( - Write(rules[0]), - self.teacher.change_mode, "raise_right_hand", - ) - self.wait() - alt_rules_list = list(rules[1:]) + [VectorizedPoint(self.teacher.eyes.get_top())] - for last_rule, rule, video_index in zip(rules, alt_rules_list, video_indices): - video = self.series[video_index] - self.play( - last_rule.replace, video, - FadeIn(rule), - ) - self.play(Animation(rule)) - self.wait() - self.play( - self.teacher.change_mode, "happy", - self.teacher.look_at, student.eyes - ) - - def invent_calculus(self): - student = self.get_students()[1] - creatures = self.get_pi_creatures() - creatures.remove(student) - creature_copies = creatures.copy() - self.remove(creatures) - self.add(creature_copies) - - calculus = VGroup(*self.essence_words[-len("calculus"):]) - calculus.generate_target() - invent = TextMobject("Invent") - invent_calculus = VGroup(invent, calculus.target) - invent_calculus.arrange(RIGHT, buff = MED_SMALL_BUFF) - invent_calculus.next_to(student, UP, 1.5*LARGE_BUFF) - invent_calculus.shift(RIGHT) - arrow = Arrow(invent_calculus, student) - - fader = Rectangle( - width = FRAME_WIDTH, - height = FRAME_HEIGHT, - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 0.5, - ) - - self.play( - FadeIn(fader), - Animation(student), - Animation(calculus) - ) - self.play( - Write(invent), - MoveToTarget(calculus), - student.change_mode, "erm", - student.look_at, calculus - ) - self.play(ShowCreation(arrow)) - self.wait(2) - -class PreviewFrame(Scene): - def construct(self): - frame = Rectangle(height = 9, width = 16, color = WHITE) - frame.set_height(1.5*FRAME_Y_RADIUS) - - colors = iter(color_gradient([BLUE, YELLOW], 3)) - titles = [ - TextMobject("Chapter %d:"%d, s).to_edge(UP).set_color(next(colors)) - for d, s in [ - (3, "Derivative formulas through geometry"), - (4, "Chain rule, product rule, etc."), - (7, "Limits"), - ] - ] - title = titles[0] - - frame.next_to(title, DOWN) - - self.add(frame, title) - self.wait(3) - for next_title in titles[1:]: - self.play(Transform(title, next_title)) - self.wait(3) - -class ProductRuleDiagram(Scene): - def construct(self): - df = 0.4 - dg = 0.2 - rect_kwargs = { - "stroke_width" : 0, - "fill_color" : BLUE, - "fill_opacity" : 0.6, - } - - rect = Rectangle(width = 4, height = 3, **rect_kwargs) - rect.shift(DOWN) - df_rect = Rectangle( - height = rect.get_height(), - width = df, - **rect_kwargs - ) - dg_rect = Rectangle( - height = dg, - width = rect.get_width(), - **rect_kwargs - ) - corner_rect = Rectangle( - height = dg, - width = df, - **rect_kwargs - ) - d_rects = VGroup(df_rect, dg_rect, corner_rect) - for d_rect, direction in zip(d_rects, [RIGHT, DOWN, RIGHT+DOWN]): - d_rect.next_to(rect, direction, buff = 0) - d_rect.set_fill(YELLOW, 0.75) - - corner_pairs = [ - (DOWN+RIGHT, UP+RIGHT), - (DOWN+RIGHT, DOWN+LEFT), - (DOWN+RIGHT, DOWN+RIGHT), - ] - for d_rect, corner_pair in zip(d_rects, corner_pairs): - line = Line(*[ - rect.get_corner(corner) - for corner in corner_pair - ]) - d_rect.line = d_rect.copy().replace(line, stretch = True) - d_rect.line.set_color(d_rect.get_color()) - - f_brace = Brace(rect, UP) - g_brace = Brace(rect, LEFT) - df_brace = Brace(df_rect, UP) - dg_brace = Brace(dg_rect, LEFT) - - f_label = f_brace.get_text("$f$") - g_label = g_brace.get_text("$g$") - df_label = df_brace.get_text("$df$") - dg_label = dg_brace.get_text("$dg$") - - VGroup(f_label, df_label).set_color(GREEN) - VGroup(g_label, dg_label).set_color(RED) - - f_label.generate_target() - g_label.generate_target() - fg_group = VGroup(f_label.target, g_label.target) - fg_group.generate_target() - fg_group.target.arrange(RIGHT, buff = SMALL_BUFF) - fg_group.target.move_to(rect.get_center()) - - for mob in df_brace, df_label, dg_brace, dg_label: - mob.save_state() - mob.scale(0.01, about_point = rect.get_corner( - mob.get_center() - rect.get_center() - )) - - self.add(rect) - self.play( - GrowFromCenter(f_brace), - GrowFromCenter(g_brace), - Write(f_label), - Write(g_label), - ) - self.play(MoveToTarget(fg_group)) - self.play(*[ - mob.restore - for mob in (df_brace, df_label, dg_brace, dg_label) - ] + [ - ReplacementTransform(d_rect.line, d_rect) - for d_rect in d_rects - ]) - self.wait() - self.play( - d_rects.space_out_submobjects, 1.2, - MaintainPositionRelativeTo( - VGroup(df_brace, df_label), - df_rect - ), - MaintainPositionRelativeTo( - VGroup(dg_brace, dg_label), - dg_rect - ), - ) - self.wait() - - deriv = TexMobject( - "d(", "fg", ")", "=", - "f", "\\cdot", "dg", "+", "g", "\\cdot", "df" - ) - deriv.to_edge(UP) - alpha_iter = iter(np.linspace(0, 0.5, 5)) - self.play(*[ - ApplyMethod( - mob.copy().move_to, - deriv.get_part_by_tex(tex, substring = False), - rate_func = squish_rate_func(smooth, alpha, alpha+0.5) - ) - for mob, tex in [ - (fg_group, "fg"), - (f_label, "f"), - (dg_label, "dg"), - (g_label, "g"), - (df_label, "df"), - ] - for alpha in [next(alpha_iter)] - ]+[ - Write(VGroup(*it.chain(*[ - deriv.get_parts_by_tex(tex, substring = False) - for tex in ("d(", ")", "=", "\\cdot", "+") - ]))) - ], run_time = 3) - self.wait() - -class IntroduceCircle(CircleScene): - CONFIG = { - "include_pi_creature" : True, - "unwrapped_tip" : 2*RIGHT - } - def construct(self): - self.force_skipping() - - self.introduce_area() - self.question_area() - self.show_calculus_symbols() - - def introduce_area(self): - area = TexMobject("\\text{Area}", "=", "\\pi", "R", "^2") - area.next_to(self.pi_creature.get_corner(UP+RIGHT), UP+RIGHT) - - self.remove(self.circle, self.radius_group) - self.play( - self.pi_creature.change_mode, "pondering", - self.pi_creature.look_at, self.circle - ) - self.introduce_circle() - self.wait() - R_copy = self.radius_label.copy() - self.play( - self.pi_creature.change_mode, "raise_right_hand", - self.pi_creature.look_at, area, - Transform(R_copy, area.get_part_by_tex("R")) - ) - self.play(Write(area)) - self.remove(R_copy) - self.wait() - - self.area = area - - def question_area(self): - q_marks = TexMobject("???") - q_marks.next_to(self.pi_creature, UP) - rings = VGroup(*reversed(self.get_rings())) - unwrapped_rings = VGroup(*[ - self.get_unwrapped(ring, to_edge = None) - for ring in rings - ]) - unwrapped_rings.arrange(UP, buff = SMALL_BUFF) - unwrapped_rings.move_to(self.unwrapped_tip, UP) - ring_anim_kwargs = { - "run_time" : 3, - "lag_ratio" : 0.5 - } - - self.play( - Animation(self.area), - Write(q_marks), - self.pi_creature.change_mode, "confused", - self.pi_creature.look_at, self.area, - ) - self.wait() - self.play( - FadeIn(rings, **ring_anim_kwargs), - Animation(self.radius_group), - FadeOut(q_marks), - self.pi_creature.change_mode, "thinking" - ) - self.wait() - self.play( - rings.rotate, np.pi/2, - rings.move_to, unwrapped_rings.get_top(), - Animation(self.radius_group), - path_arc = np.pi/2, - **ring_anim_kwargs - ) - self.play( - Transform(rings, unwrapped_rings, **ring_anim_kwargs), - ) - self.wait() - - def show_calculus_symbols(self): - ftc = TexMobject( - "\\int_0^R", "\\frac{dA}{dr}", "\\,dr", - "=", "A(R)" - ) - ftc.shift(2*UP) - - self.play( - ReplacementTransform( - self.area.get_part_by_tex("R").copy(), - ftc.get_part_by_tex("int") - ), - self.pi_creature.change_mode, "plain" - ) - self.wait() - self.play( - ReplacementTransform( - self.area.get_part_by_tex("Area").copy(), - ftc.get_part_by_tex("frac") - ), - ReplacementTransform( - self.area.get_part_by_tex("R").copy(), - ftc.get_part_by_tex("\\,dr") - ) - ) - self.wait() - self.play(Write(VGroup(*ftc[-2:]))) - self.wait(2) - -class ApproximateOneRing(CircleScene, ReconfigurableScene): - CONFIG = { - "num_lines" : 24, - "ring_index_proportion" : 0.6, - "ring_shift_val" : 6*RIGHT, - "ring_colors" : [BLUE, GREEN_E], - "unwrapped_tip" : 2*RIGHT+0.5*UP, - } - def setup(self): - CircleScene.setup(self) - ReconfigurableScene.setup(self) - - def construct(self): - self.force_skipping() - - self.write_radius_three() - self.try_to_understand_area() - self.slice_into_rings() - self.isolate_one_ring() - - self.revert_to_original_skipping_status() - self.straighten_ring_out() - self.force_skipping() - - self.approximate_as_rectangle() - - def write_radius_three(self): - three = TexMobject("3") - three.move_to(self.radius_label) - - self.look_at(self.circle) - self.play(Transform( - self.radius_label, three, - path_arc = np.pi - )) - self.wait() - - def try_to_understand_area(self): - line_sets = [ - VGroup(*[ - Line( - self.circle.point_from_proportion(alpha), - self.circle.point_from_proportion(func(alpha)), - ) - for alpha in np.linspace(0, 1, self.num_lines) - ]) - for func in [ - lambda alpha : 1-alpha, - lambda alpha : (0.5-alpha)%1, - lambda alpha : (alpha + 0.4)%1, - lambda alpha : (alpha + 0.5)%1, - ] - ] - for lines in line_sets: - lines.set_stroke(BLACK, 2) - lines = line_sets[0] - - self.play( - ShowCreation( - lines, - run_time = 2, - lag_ratio = 0.5 - ), - Animation(self.radius_group), - self.pi_creature.change_mode, "maybe" - ) - self.wait(2) - for new_lines in line_sets[1:]: - self.play( - Transform(lines, new_lines), - Animation(self.radius_group) - ) - self.wait() - self.wait() - self.play(FadeOut(lines), Animation(self.radius_group)) - - def slice_into_rings(self): - rings = self.get_rings() - rings.set_stroke(BLACK, 1) - - self.play( - FadeIn( - rings, - lag_ratio = 0.5, - run_time = 3 - ), - Animation(self.radius_group), - self.pi_creature.change_mode, "pondering", - self.pi_creature.look_at, self.circle - ) - self.wait(2) - for x in range(2): - self.play( - Rotate(rings, np.pi, in_place = True, run_time = 2), - Animation(self.radius_group), - self.pi_creature.change_mode, "happy" - ) - self.wait(2) - - self.rings = rings - - def isolate_one_ring(self): - rings = self.rings - index = int(self.ring_index_proportion*len(rings)) - original_ring = rings[index] - ring = original_ring.copy() - - radius = Line(ORIGIN, ring.R*RIGHT, color = WHITE) - radius.rotate(np.pi/4) - r_label = TexMobject("r") - r_label.next_to(radius.get_center(), UP+LEFT, SMALL_BUFF) - area_q = TextMobject("Area", "?", arg_separator = "") - area_q.set_color(YELLOW) - - - self.play( - ring.shift, self.ring_shift_val, - original_ring.set_fill, None, 0.25, - Animation(self.radius_group), - ) - - VGroup(radius, r_label).shift(ring.get_center()) - area_q.next_to(ring, RIGHT) - - self.play(ShowCreation(radius)) - self.play(Write(r_label)) - self.wait() - self.play(Write(area_q)) - self.wait() - self.play(*[ - ApplyMethod( - r.set_fill, YELLOW, - rate_func = squish_rate_func(there_and_back, alpha, alpha+0.15), - run_time = 3 - ) - for r, alpha in zip(rings, np.linspace(0, 0.85, len(rings))) - ]+[ - Animation(self.radius_group) - ]) - self.wait() - self.change_mode("thinking") - self.wait() - - self.original_ring = original_ring - self.ring = ring - self.ring_radius_group = VGroup(radius, r_label) - self.area_q = area_q - - def straighten_ring_out(self): - ring = self.ring.copy() - trapezoid = TextMobject("Trapezoid?") - rectangle_ish = TextMobject("Rectangle-ish") - for text in trapezoid, rectangle_ish: - text.next_to( - self.pi_creature.get_corner(UP+RIGHT), - DOWN+RIGHT, buff = MED_LARGE_BUFF - ) - - self.unwrap_ring(ring, to_edge = RIGHT) - self.change_mode("pondering") - self.wait() - self.play(Write(trapezoid)) - self.wait() - self.play(trapezoid.shift, DOWN) - strike = Line( - trapezoid.get_left(), trapezoid.get_right(), - stroke_color = RED, - stroke_width = 8 - ) - self.play( - Write(rectangle_ish), - ShowCreation(strike), - self.pi_creature.change_mode, "happy" - ) - self.wait() - self.play(*list(map(FadeOut, [trapezoid, strike]))) - - self.unwrapped_ring = ring - - def approximate_as_rectangle(self): - top_brace, side_brace = [ - Brace( - self.unwrapped_ring, vect, buff = SMALL_BUFF, - min_num_quads = 2, - ) - for vect in (UP, LEFT) - ] - top_brace.scale_in_place(self.ring.R/(self.ring.R+self.dR)) - side_brace.set_stroke(WHITE, 0.5) - - - width_label = TexMobject("2\\pi", "r") - width_label.next_to(top_brace, UP, SMALL_BUFF) - dr_label = TexMobject("dr") - q_marks = TexMobject("???") - concrete_dr = TexMobject("=0.1") - concrete_dr.submobjects.reverse() - for mob in dr_label, q_marks, concrete_dr: - mob.next_to(side_brace, LEFT, SMALL_BUFF) - dr_label.save_state() - - alt_side_brace = side_brace.copy() - alt_side_brace.move_to(ORIGIN, UP+RIGHT) - alt_side_brace.rotate(-np.pi/2) - alt_side_brace.shift( - self.original_ring.get_boundary_point(RIGHT) - ) - alt_dr_label = dr_label.copy() - alt_dr_label.next_to(alt_side_brace, UP, SMALL_BUFF) - - approx = TexMobject("\\approx") - approx.next_to( - self.area_q.get_part_by_tex("Area"), - RIGHT, - align_using_submobjects = True, - ) - two_pi_r_dr = VGroup(width_label, dr_label).copy() - two_pi_r_dr.generate_target() - two_pi_r_dr.target.arrange( - RIGHT, buff = SMALL_BUFF, aligned_edge = DOWN - ) - two_pi_r_dr.target.next_to(approx, RIGHT, aligned_edge = DOWN) - - self.play(GrowFromCenter(top_brace)) - self.play( - Write(width_label.get_part_by_tex("pi")), - ReplacementTransform( - self.ring_radius_group[1].copy(), - width_label.get_part_by_tex("r") - ) - ) - self.wait() - self.play( - GrowFromCenter(side_brace), - Write(q_marks) - ) - self.change_mode("confused") - self.wait() - for num_rings in 20, 7: - self.show_alternate_width(num_rings) - self.play(ReplacementTransform(q_marks, dr_label)) - self.play( - ReplacementTransform(side_brace.copy(), alt_side_brace), - ReplacementTransform(dr_label.copy(), alt_dr_label), - run_time = 2 - ) - self.wait() - self.play( - dr_label.next_to, concrete_dr.copy(), LEFT, SMALL_BUFF, DOWN, - Write(concrete_dr, run_time = 2), - self.pi_creature.change_mode, "pondering" - ) - self.wait(2) - self.play( - MoveToTarget(two_pi_r_dr), - FadeIn(approx), - self.area_q.get_part_by_tex("?").fade, 1, - ) - self.wait() - self.play( - FadeOut(concrete_dr), - dr_label.restore - ) - self.show_alternate_width( - 40, - transformation_kwargs = {"run_time" : 4}, - return_to_original_configuration = False, - ) - self.wait(2) - self.look_at(self.circle) - self.play( - ApplyWave(self.rings, amplitude = 0.1), - Animation(self.radius_group), - Animation(alt_side_brace), - Animation(alt_dr_label), - run_time = 3, - lag_ratio = 0.5 - ) - self.wait(2) - - def show_alternate_width(self, num_rings, **kwargs): - self.transition_to_alt_config( - dR = self.radius/num_rings, **kwargs - ) - -class MoveForwardWithApproximation(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Move forward with \\\\", - "the", "approximation" - ) - self.change_student_modes("hesitant", "erm", "sassy") - self.wait() - words = TextMobject( - "It gets better", - "\\\\ for smaller ", - "$dr$" - ) - words.set_color_by_tex("dr", BLUE) - self.teacher_says(words, target_mode = "shruggie") - self.wait(3) - -class GraphRectangles(CircleScene, GraphScene): - CONFIG = { - "graph_origin" : 3.25*LEFT+2.5*DOWN, - "x_min" : 0, - "x_max" : 4, - "x_axis_width" : 7, - "x_labeled_nums" : list(range(5)), - "x_axis_label" : "$r$", - "y_min" : 0, - "y_max" : 20, - "y_tick_frequency" : 2.5, - "y_labeled_nums" : list(range(5, 25, 5)), - "y_axis_label" : "", - "exclude_zero_label" : False, - "num_rings_in_ring_sum_start" : 3, - "tick_height" : 0.2, - } - def setup(self): - CircleScene.setup(self) - GraphScene.setup(self) - self.setup_axes() - self.remove(self.axes) - - # self.pi_creature.change_mode("pondering") - # self.pi_creature.look_at(self.circle) - # self.add(self.pi_creature) - - three = TexMobject("3") - three.move_to(self.radius_label) - self.radius_label.save_state() - Transform(self.radius_label, three).update(1) - - def construct(self): - self.draw_ring_sum() - self.draw_r_values() - self.unwrap_rings_onto_graph() - self.draw_graph() - self.point_out_approximation() - self.let_dr_approah_zero() - self.compute_area_under_graph() - self.show_circle_unwrapping() - - def draw_ring_sum(self): - rings = self.get_rings() - rings.set_stroke(BLACK, 1) - ring_sum, draw_ring_sum_anims = self.get_ring_sum(rings) - area_label = TexMobject( - "\\text{Area}", "\\approx", - "2\\pi", "r", "\\,dr" - ) - area_label.set_color_by_tex("r", YELLOW, substring = False) - area_label.next_to(ring_sum, RIGHT, aligned_edge = UP) - area = area_label.get_part_by_tex("Area") - arrow_start = area.get_corner(DOWN+LEFT) - arrows = VGroup(*[ - Arrow( - arrow_start, - ring.target.get_boundary_point( - arrow_start - ring.target.get_center() - ), - color = ring.get_color() - ) - for ring in rings - if ring.target.get_fill_opacity() > 0 - ]) - - self.add(rings, self.radius_group) - self.remove(self.circle) - self.wait() - self.play(*draw_ring_sum_anims) - self.play(Write(area_label, run_time = 2)) - self.play(ShowCreation(arrows)) - self.wait() - - self.ring_sum = ring_sum - area_label.add(arrows) - self.area_label = area_label - self.rings = rings - - def draw_r_values(self): - values_of_r = TextMobject("Values of ", "$r$") - values_of_r.set_color_by_tex("r", YELLOW) - values_of_r.next_to( - self.x_axis, UP, - buff = 2*LARGE_BUFF, - aligned_edge = LEFT - ) - - r_ticks = VGroup(*[ - Line( - self.coords_to_point(r, -self.tick_height), - self.coords_to_point(r, self.tick_height), - color = YELLOW - ) - for r in np.arange(0, 3, 0.1) - ]) - arrows = VGroup(*[ - Arrow( - values_of_r.get_part_by_tex("r").get_bottom(), - tick.get_top(), - buff = SMALL_BUFF, - color = YELLOW, - tip_length = 0.15 - ) - for tick in (r_ticks[0], r_ticks[-1]) - ]) - first_tick = r_ticks[0].copy() - moving_arrow = arrows[0].copy() - - index = 2 - dr_brace = Brace( - VGroup(*r_ticks[index:index+2]), - DOWN, buff = SMALL_BUFF - ) - dr_label = TexMobject("dr") - dr_label.next_to( - dr_brace, DOWN, - buff = SMALL_BUFF, - aligned_edge = LEFT - ) - dr_group = VGroup(dr_brace, dr_label) - - self.play( - FadeIn(values_of_r), - FadeIn(self.x_axis), - ) - self.play( - ShowCreation(moving_arrow), - ShowCreation(first_tick), - ) - self.play(Indicate(self.rings[0])) - self.wait() - self.play( - Transform(moving_arrow, arrows[-1]), - ShowCreation(r_ticks, lag_ratio = 0.5), - run_time = 2 - ) - self.play(Indicate(self.rings[-1])) - self.wait() - self.play(FadeIn(dr_group)) - self.wait() - self.play(*list(map(FadeOut, [moving_arrow, values_of_r]))) - - self.x_axis.add(r_ticks) - self.r_ticks = r_ticks - self.dr_group = dr_group - - def unwrap_rings_onto_graph(self): - rings = self.rings - graph = self.get_graph(lambda r : 2*np.pi*r) - flat_graph = self.get_graph(lambda r : 0) - rects, flat_rects = [ - self.get_riemann_rectangles( - g, x_min = 0, x_max = 3, dx = self.dR, - start_color = self.rings[0].get_fill_color(), - end_color = self.rings[-1].get_fill_color(), - ) - for g in (graph, flat_graph) - ] - self.graph, self.flat_rects = graph, flat_rects - - transformed_rings = VGroup() - self.ghost_rings = VGroup() - for index, rect, r in zip(it.count(), rects, np.arange(0, 3, 0.1)): - proportion = float(index)/len(rects) - ring_index = int(len(rings)*proportion**0.6) - ring = rings[ring_index] - if ring in transformed_rings: - ring = ring.copy() - transformed_rings.add(ring) - if ring.get_fill_opacity() > 0: - ghost_ring = ring.copy() - ghost_ring.set_fill(opacity = 0.25) - self.add(ghost_ring, ring) - self.ghost_rings.add(ghost_ring) - - ring.rect = rect - - n_anchors = ring.get_num_curves() - target = VMobject() - target.set_points_as_corners([ - interpolate(ORIGIN, DOWN, a) - for a in np.linspace(0, 1, n_anchors/2) - ]+[ - interpolate(DOWN+RIGHT, RIGHT, a) - for a in np.linspace(0, 1, n_anchors/2) - ]) - target.replace(rect, stretch = True) - target.stretch_to_fit_height(2*np.pi*r) - target.move_to(rect, DOWN) - target.set_stroke(BLACK, 1) - target.set_fill(ring.get_fill_color(), 1) - ring.target = target - ring.original_ring = ring.copy() - - foreground_animations = list(map(Animation, [self.x_axis, self.area_label])) - example_ring = transformed_rings[2] - - self.play( - MoveToTarget( - example_ring, - path_arc = -np.pi/2, - run_time = 2 - ), - Animation(self.x_axis), - ) - self.wait(2) - self.play(*[ - MoveToTarget( - ring, - path_arc = -np.pi/2, - run_time = 4, - rate_func = squish_rate_func(smooth, alpha, alpha+0.25) - ) - for ring, alpha in zip( - transformed_rings, - np.linspace(0, 0.75, len(transformed_rings)) - ) - ] + foreground_animations) - self.wait() - - ##Demonstrate height of one rect - highlighted_ring = transformed_rings[6].copy() - original_ring = transformed_rings[6].original_ring - original_ring.move_to(highlighted_ring, RIGHT) - original_ring.set_fill(opacity = 1) - highlighted_ring.save_state() - - side_brace = Brace(highlighted_ring, RIGHT) - height_label = side_brace.get_text("2\\pi", "r") - height_label.set_color_by_tex("r", YELLOW) - - self.play( - transformed_rings.set_fill, None, 0.2, - Animation(highlighted_ring), - *foreground_animations - ) - self.play( - self.dr_group.arrange, DOWN, - self.dr_group.next_to, highlighted_ring, - DOWN, SMALL_BUFF - ) - self.wait() - self.play( - GrowFromCenter(side_brace), - Write(height_label) - ) - self.wait() - self.play(Transform(highlighted_ring, original_ring)) - self.wait() - self.play(highlighted_ring.restore) - self.wait() - self.play( - transformed_rings.set_fill, None, 1, - FadeOut(side_brace), - FadeOut(height_label), - *foreground_animations - ) - self.remove(highlighted_ring) - self.wait() - - ##Rescale - self.play(*[ - ApplyMethod( - ring.replace, ring.rect, - method_kwargs = {"stretch" : True} - ) - for ring in transformed_rings - ] + [ - Write(self.y_axis), - FadeOut(self.area_label), - ] + foreground_animations) - self.remove(transformed_rings) - self.add(rects) - self.wait() - - self.rects = rects - - def draw_graph(self): - graph_label = self.get_graph_label( - self.graph, "2\\pi r", - direction = UP+LEFT, - x_val = 2.5, - buff = SMALL_BUFF - ) - - self.play(ShowCreation(self.graph)) - self.play(Write(graph_label)) - self.wait() - self.play(*[ - Transform( - rect, flat_rect, - run_time = 2, - rate_func = squish_rate_func( - lambda t : 0.1*there_and_back(t), - alpha, alpha+0.5 - ), - lag_ratio = 0.5 - ) - for rect, flat_rect, alpha in zip( - self.rects, self.flat_rects, - np.linspace(0, 0.5, len(self.rects)) - ) - ] + list(map(Animation, [self.x_axis, self.graph])) - ) - self.wait(2) - - def point_out_approximation(self): - rect = self.rects[10] - rect.generate_target() - rect.save_state() - approximation = TextMobject("= Approximation") - approximation.scale(0.8) - group = VGroup(rect.target, approximation) - group.arrange(RIGHT) - group.to_edge(RIGHT) - - self.play( - MoveToTarget(rect), - Write(approximation), - ) - self.wait(2) - self.play( - rect.restore, - FadeOut(approximation) - ) - self.wait() - - def let_dr_approah_zero(self): - thinner_rects_list = [ - self.get_riemann_rectangles( - self.graph, - x_min = 0, - x_max = 3, - dx = 1./(10*2**n), - stroke_width = 1./(2**n), - start_color = self.rects[0].get_fill_color(), - end_color = self.rects[-1].get_fill_color(), - ) - for n in range(1, 5) - ] - - self.play(*list(map(FadeOut, [self.r_ticks, self.dr_group]))) - self.x_axis.remove(self.r_ticks, *self.r_ticks) - for new_rects in thinner_rects_list: - self.play( - Transform( - self.rects, new_rects, - lag_ratio = 0.5, - run_time = 2 - ), - Animation(self.axes), - Animation(self.graph), - ) - self.wait() - self.play(ApplyWave( - self.rects, - direction = RIGHT, - run_time = 2, - lag_ratio = 0.5, - )) - self.wait() - - def compute_area_under_graph(self): - formula, formula_with_R = formulas = [ - self.get_area_formula(R) - for R in ("3", "R") - ] - for mob in formulas: - mob.to_corner(UP+RIGHT, buff = MED_SMALL_BUFF) - - brace = Brace(self.rects, RIGHT) - height_label = brace.get_text("$2\\pi \\cdot 3$") - height_label_with_R = brace.get_text("$2\\pi \\cdot R$") - base_line = Line( - self.coords_to_point(0, 0), - self.coords_to_point(3, 0), - color = YELLOW - ) - - fresh_rings = self.get_rings(dR = 0.025) - fresh_rings.set_stroke(width = 0) - self.radius_label.restore() - VGroup( - fresh_rings, self.radius_group - ).to_corner(UP+LEFT, buff = SMALL_BUFF) - - self.play(Write(formula.top_line, run_time = 2)) - self.play(FocusOn(base_line)) - self.play(ShowCreation(base_line)) - self.wait() - self.play( - GrowFromCenter(brace), - Write(height_label) - ) - self.wait() - self.play(FocusOn(formula)) - self.play(Write(formula.mid_line)) - self.wait() - self.play(Write(formula.bottom_line)) - self.wait(2) - - self.play(*list(map(FadeOut, [ - self.ghost_rings, - self.ring_sum.tex_mobs - ]))) - self.play(*list(map(FadeIn, [fresh_rings, self.radius_group]))) - self.wait() - self.play( - Transform(formula, formula_with_R), - Transform(height_label, height_label_with_R), - ) - self.wait(2) - - self.fresh_rings = fresh_rings - - def show_circle_unwrapping(self): - rings = self.fresh_rings - rings.rotate_in_place(np.pi) - rings.submobjects.reverse() - ghost_rings = rings.copy() - ghost_rings.set_fill(opacity = 0.25) - self.add(ghost_rings, rings, self.radius_group) - - unwrapped = VGroup(*[ - self.get_unwrapped(ring, to_edge = None) - for ring in rings - ]) - unwrapped.stretch_to_fit_height(1) - unwrapped.stretch_to_fit_width(2) - unwrapped.move_to(ORIGIN, DOWN) - unwrapped.apply_function( - lambda p : np.dot(p, - np.array([[1, 0, 0], [-1, 1, 0], [0, 0, 1]]) - ), - maintain_smoothness = False - ) - unwrapped.rotate(np.pi/2) - unwrapped.replace(self.rects, stretch = True) - - self.play(self.rects.fade, 0.8) - self.play( - Transform( - rings, unwrapped, - run_time = 5, - lag_ratio = 0.5, - ), - Animation(self.radius_group) - ) - self.wait() - - ##### - - def get_ring_sum(self, rings): - arranged_group = VGroup() - tex_mobs = VGroup() - for ring in rings: - ring.generate_target() - ring.target.set_stroke(width = 0) - - for ring in rings[:self.num_rings_in_ring_sum_start]: - plus = TexMobject("+") - arranged_group.add(ring.target) - arranged_group.add(plus) - tex_mobs.add(plus) - dots = TexMobject("\\vdots") - plus = TexMobject("+") - arranged_group.add(dots, plus) - tex_mobs.add(dots, plus) - last_ring = rings[-1] - - arranged_group.add(last_ring.target) - arranged_group.arrange(DOWN, buff = SMALL_BUFF) - arranged_group.set_height(FRAME_HEIGHT-1) - arranged_group.to_corner(DOWN+LEFT, buff = MED_SMALL_BUFF) - for mob in tex_mobs: - mob.scale_in_place(0.7) - - middle_rings = rings[self.num_rings_in_ring_sum_start:-1] - alphas = np.linspace(0, 1, len(middle_rings)) - for ring, alpha in zip(middle_rings, alphas): - ring.target.set_fill(opacity = 0) - ring.target.move_to(interpolate( - dots.get_left(), last_ring.target.get_center(), alpha - )) - - draw_ring_sum_anims = [Write(tex_mobs)] - draw_ring_sum_anims += [ - MoveToTarget( - ring, - run_time = 3, - path_arc = -np.pi/3, - rate_func = squish_rate_func(smooth, alpha, alpha+0.8) - ) - for ring, alpha in zip(rings, np.linspace(0, 0.2, len(rings))) - ] - draw_ring_sum_anims.append(FadeOut(self.radius_group)) - - ring_sum = VGroup(rings, tex_mobs) - ring_sum.rings = VGroup(*[r.target for r in rings]) - ring_sum.tex_mobs = tex_mobs - - return ring_sum, draw_ring_sum_anims - - def get_area_formula(self, R): - formula = TexMobject( - "\\text{Area}", "&= \\frac{1}{2}", "b", "h", - "\\\\ &=", "\\frac{1}{2}", "(%s)"%R, "(2\\pi \\cdot %s)"%R, - "\\\\ &=", "\\pi ", "%s"%R, "^2" - - ) - formula.set_color_by_tex("b", GREEN, substring = False) - formula.set_color_by_tex("h", RED, substring = False) - formula.set_color_by_tex("%s"%R, GREEN) - formula.set_color_by_tex("(2\\pi ", RED) - formula.set_color_by_tex("(2\\pi ", RED) - formula.scale(0.8) - - formula.top_line = VGroup(*formula[:4]) - formula.mid_line = VGroup(*formula[4:8]) - formula.bottom_line = VGroup(*formula[8:]) - return formula - -class ThinkLikeAMathematician(TeacherStudentsScene): - def construct(self): - pi_R_squraed = TexMobject("\\pi", "R", "^2") - pi_R_squraed.set_color_by_tex("R", YELLOW) - pi_R_squraed.move_to(self.get_students(), UP) - pi_R_squraed.set_fill(opacity = 0) - - self.play( - pi_R_squraed.shift, 2*UP, - pi_R_squraed.set_fill, None, 1 - ) - self.change_student_modes(*["hooray"]*3) - self.wait(2) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = self.teacher.eyes, - added_anims = [PiCreatureSays( - self.teacher, "But why did \\\\ that work?" - )] - ) - self.play(FadeOut(pi_R_squraed)) - self.look_at(2*UP+4*LEFT) - self.wait(5) - -class TwoThingsToNotice(TeacherStudentsScene): - def construct(self): - words = TextMobject( - "Two things to \\\\ note about", - "$dr$", - ) - words.set_color_by_tex("dr", GREEN) - self.teacher_says(words, run_time = 1) - self.wait(3) - -class RecapCircleSolution(GraphRectangles, ReconfigurableScene): - def setup(self): - GraphRectangles.setup(self) - ReconfigurableScene.setup(self) - - def construct(self): - self.break_up_circle() - self.show_sum() - self.dr_indicates_spacing() - self.smaller_dr() - self.show_riemann_sum() - self.limiting_riemann_sum() - self.full_precision() - - def break_up_circle(self): - self.remove(self.circle) - rings = self.get_rings() - rings.set_stroke(BLACK, 1) - ring_sum, draw_ring_sum_anims = self.get_ring_sum(rings) - - hard_problem = TextMobject("Hard problem") - down_arrow = TexMobject("\\Downarrow") - sum_words = TextMobject("Sum of many \\\\ small values") - integral_condition = VGroup(hard_problem, down_arrow, sum_words) - integral_condition.arrange(DOWN) - integral_condition.scale(0.8) - integral_condition.to_corner(UP+RIGHT) - - self.add(rings, self.radius_group) - self.play(FadeIn( - integral_condition, - lag_ratio = 0.5 - )) - self.wait() - self.play(*draw_ring_sum_anims) - - self.rings = rings - self.integral_condition = integral_condition - - def show_sum(self): - visible_rings = [ring for ring in self.rings if ring.get_fill_opacity() > 0] - radii = self.dR*np.arange(len(visible_rings)) - radii[-1] = 3-self.dR - - radial_lines = VGroup() - for ring in visible_rings: - radius_line = Line(ORIGIN, ring.R*RIGHT, color = YELLOW) - radius_line.rotate(np.pi/4) - radius_line.shift(ring.get_center()) - radial_lines.add(radius_line) - - approximations = VGroup() - for ring, radius in zip(visible_rings, radii): - label = TexMobject( - "\\approx", "2\\pi", - "(%s)"%str(radius), "(%s)"%str(self.dR) - ) - label[2].set_color(YELLOW) - label[3].set_color(GREEN) - label.scale(0.75) - label.next_to(ring, RIGHT) - approximations.add(label) - approximations[-1].shift(UP+0.5*LEFT) - area_label = TexMobject("2\\pi", "r", "\\, dr") - area_label.set_color_by_tex("r", YELLOW) - area_label.set_color_by_tex("dr", GREEN) - area_label.next_to(approximations, RIGHT, buff = 2*LARGE_BUFF) - arrows = VGroup(*[ - Arrow( - area_label.get_left(), - approximation.get_right(), - color = WHITE - ) - for approximation in approximations - ]) - - self.play(Write(area_label)) - self.play( - ShowCreation(arrows, lag_ratio = 0), - FadeIn(radial_lines), - *[ - ReplacementTransform( - area_label.copy(), - VGroup(*approximation[1:]) - ) - for approximation in approximations - ] - ) - self.wait() - self.play(Write(VGroup(*[ - approximation[0] - for approximation in approximations - ]))) - self.wait() - - self.area_label = area_label - self.area_arrows = arrows - self.approximations = approximations - - def dr_indicates_spacing(self): - r_ticks = VGroup(*[ - Line( - self.coords_to_point(r, -self.tick_height), - self.coords_to_point(r, self.tick_height), - color = YELLOW - ) - for r in np.arange(0, 3, self.dR) - ]) - - index = int(0.75*len(r_ticks)) - brace_ticks = VGroup(*r_ticks[index:index+2]) - dr_brace = Brace(brace_ticks, UP, buff = SMALL_BUFF) - - dr = self.area_label.get_part_by_tex("dr") - dr_copy = dr.copy() - circle = Circle().replace(dr) - circle.scale_in_place(1.3) - - dr_num = self.approximations[0][-1] - - self.play(ShowCreation(circle)) - self.play(FadeOut(circle)) - self.play(ReplacementTransform( - dr.copy(), dr_num, - run_time = 2, - path_arc = np.pi/2, - )) - self.wait() - self.play(FadeIn(self.x_axis)) - self.play(Write(r_ticks, run_time = 1)) - self.wait() - self.play( - GrowFromCenter(dr_brace), - dr_copy.next_to, dr_brace.copy(), UP - ) - self.wait() - - self.r_ticks = r_ticks - self.dr_brace_group = VGroup(dr_brace, dr_copy) - - def smaller_dr(self): - self.transition_to_alt_config(dR = 0.05) - - def show_riemann_sum(self): - graph = self.get_graph(lambda r : 2*np.pi*r) - graph_label = self.get_graph_label( - graph, "2\\pi r", - x_val = 2.5, - direction = UP+LEFT - ) - rects = self.get_riemann_rectangles( - graph, - x_min = 0, - x_max = 3, - dx = self.dR - ) - - self.play( - Write(self.y_axis, run_time = 2), - *list(map(FadeOut, [ - self.approximations, - self.area_label, - self.area_arrows, - self.dr_brace_group, - self.r_ticks, - ])) - ) - self.play( - ReplacementTransform( - self.rings.copy(), rects, - run_time = 2, - lag_ratio = 0.5 - ), - Animation(self.x_axis), - ) - self.play(ShowCreation(graph)) - self.play(Write(graph_label)) - self.wait() - - self.graph = graph - self.graph_label = graph_label - self.rects = rects - - def limiting_riemann_sum(self): - thinner_rects_list = [ - self.get_riemann_rectangles( - self.graph, - x_min = 0, - x_max = 3, - dx = 1./(10*2**n), - stroke_width = 1./(2**n), - start_color = self.rects[0].get_fill_color(), - end_color = self.rects[-1].get_fill_color(), - ) - for n in range(1, 4) - ] - - for new_rects in thinner_rects_list: - self.play( - Transform( - self.rects, new_rects, - lag_ratio = 0.5, - run_time = 2 - ), - Animation(self.axes), - Animation(self.graph), - ) - self.wait() - - def full_precision(self): - words = TextMobject("Area under \\\\ a graph") - group = VGroup(TexMobject("\\Downarrow"), words) - group.arrange(DOWN) - group.set_color(YELLOW) - group.scale(0.8) - group.next_to(self.integral_condition, DOWN) - - arc = Arc(start_angle = 2*np.pi/3, angle = 2*np.pi/3) - arc.scale(2) - arc.add_tip() - arc.add(arc[1].copy().rotate(np.pi, RIGHT)) - arc_next_to_group = VGroup( - self.integral_condition[0][0], - words[0] - ) - arc.set_height( - arc_next_to_group.get_height()-MED_LARGE_BUFF - ) - arc.next_to(arc_next_to_group, LEFT, SMALL_BUFF) - - self.play(Write(group)) - self.wait() - self.play(ShowCreation(arc)) - self.wait() - -class ExampleIntegralProblems(PiCreatureScene, GraphScene): - CONFIG = { - "dt" : 0.2, - "t_max" : 7, - "x_max" : 8, - "y_axis_height" : 5.5, - "x_axis_label" : "$t$", - "y_axis_label" : "", - "graph_origin" : 3*DOWN + 4.5*LEFT - } - def construct(self): - self.write_integral_condition() - self.show_car() - self.show_graph() - self.let_dt_approach_zero() - self.show_confusion() - - def write_integral_condition(self): - words = TextMobject( - "Hard problem $\\Rightarrow$ Sum of many small values" - ) - words.to_edge(UP) - - self.play( - Write(words), - self.pi_creature.change_mode, "raise_right_hand" - ) - self.wait() - - self.words = words - - def show_car(self): - car = Car() - start, end = 3*LEFT+UP, 5*RIGHT+UP - car.move_to(start) - - line = Line(start, end) - tick_height = MED_SMALL_BUFF - ticks = VGroup(*[ - Line( - p+tick_height*UP/2, - p+tick_height*DOWN/2, - color = YELLOW, - stroke_width = 2 - ) - for t in np.arange(0, self.t_max, self.dt) - for p in [ - line.point_from_proportion(smooth(t/self.t_max)) - ] - ]) - - index = int(len(ticks)/2) - brace_ticks = VGroup(*ticks[index:index+2]) - brace = Brace(brace_ticks, UP) - v_dt = TexMobject("v(t)", "dt") - v_dt.next_to(brace, UP, SMALL_BUFF) - v_dt.set_color(YELLOW) - v_dt_brace_group = VGroup(brace, v_dt) - - self.play( - FadeIn(car), - self.pi_creature.change_mode, "plain" - ) - self.play( - MoveCar(car, end), - FadeIn( - ticks, - lag_ratio=1, - rate_func=linear, - ), - ShowCreation(line), - FadeIn( - v_dt_brace_group, - rate_func = squish_rate_func(smooth, 0.6, 0.8) - ), - run_time = self.t_max - ) - self.wait() - for mob in v_dt: - self.play(Indicate(mob)) - self.wait(2) - - self.v_dt_brace_group = v_dt_brace_group - self.line = line - self.ticks = ticks - self.car = car - - def show_graph(self): - self.setup_axes() - self.remove(self.axes) - s_graph = self.get_graph( - lambda t : 1.8*self.y_max*smooth(t/self.t_max) - ) - v_graph = self.get_derivative_graph(s_graph) - rects = self.get_riemann_rectangles( - v_graph, - x_min = 0, - x_max = self.t_max, - dx = self.dt - ) - rects.set_fill(opacity = 0.5) - pre_rects = rects.copy() - pre_rects.rotate(-np.pi/2) - for index, pre_rect in enumerate(pre_rects): - ti1 = len(self.ticks)*index/len(pre_rects) - ti2 = min(ti1+1, len(self.ticks)-1) - tick_pair = VGroup(self.ticks[ti1], self.ticks[ti2]) - pre_rect.stretch_to_fit_width(tick_pair.get_width()) - pre_rect.move_to(tick_pair) - - special_rect = rects[int(0.6*len(rects))] - brace = Brace(special_rect, LEFT, buff = 0) - - v_dt_brace_group_copy = self.v_dt_brace_group.copy() - start_brace, (v_t, dt) = v_dt_brace_group_copy - - self.play( - FadeIn( - pre_rects, - run_time = 2, - lag_ratio = 0.5 - ), - Animation(self.ticks) - ) - self.play( - ReplacementTransform( - pre_rects, rects, - run_time = 3, - lag_ratio = 0.5 - ), - Animation(self.ticks), - Write(self.axes, run_time = 1) - ) - self.play(ShowCreation(v_graph)) - self.change_mode("pondering") - self.wait() - self.play( - v_t.next_to, brace, LEFT, SMALL_BUFF, - dt.next_to, special_rect, DOWN, - special_rect.set_fill, None, 1, - ReplacementTransform(start_brace, brace), - ) - self.wait(3) - - self.v_graph = v_graph - self.rects = rects - self.v_dt_brace_group_copy = v_dt_brace_group_copy - - def let_dt_approach_zero(self): - thinner_rects_list = [ - self.get_riemann_rectangles( - self.v_graph, - x_min = 0, - x_max = self.t_max, - dx = self.dt/(2**n), - stroke_width = 1./(2**n) - ) - for n in range(1, 4) - ] - - self.play( - self.rects.set_fill, None, 1, - Animation(self.x_axis), - FadeOut(self.v_dt_brace_group_copy), - ) - self.change_mode("thinking") - self.wait() - for thinner_rects in thinner_rects_list: - self.play( - Transform( - self.rects, thinner_rects, - run_time = 2, - lag_ratio = 0.5 - ) - ) - self.wait() - - def show_confusion(self): - randy = Randolph(color = BLUE_C) - randy.to_corner(DOWN+LEFT) - randy.to_edge(LEFT, buff = MED_SMALL_BUFF) - - self.play(FadeIn(randy)) - self.play( - randy.change_mode, "confused", - randy.look_at, self.rects - ) - self.play( - self.pi_creature.change_mode, "confused", - self.pi_creature.look_at, randy.eyes - ) - self.play(Blink(randy)) - self.wait() - -class MathematicianPonderingAreaUnderDifferentCurves(PiCreatureScene): - def construct(self): - self.play( - self.pi_creature.change_mode, "raise_left_hand", - self.pi_creature.look, UP+LEFT - ) - self.wait(4) - self.play( - self.pi_creature.change_mode, "raise_right_hand", - self.pi_creature.look, UP+RIGHT - ) - self.wait(4) - self.play( - self.pi_creature.change_mode, "pondering", - self.pi_creature.look, UP+LEFT - ) - self.wait(2) - - def create_pi_creature(self): - self.pi_creature = Randolph(color = BLUE_C) - self.pi_creature.to_edge(DOWN) - return self.pi_creature - -class AreaUnderParabola(GraphScene): - CONFIG = { - "x_max" : 4, - "x_labeled_nums" : list(range(-1, 5)), - "y_min" : 0, - "y_max" : 15, - "y_tick_frequency" : 2.5, - "y_labeled_nums" : list(range(5, 20, 5)), - "n_rect_iterations" : 6, - "default_right_x" : 3, - "func" : lambda x : x**2, - "graph_label_tex" : "x^2", - "graph_label_x_val" : 3.8, - } - def construct(self): - self.setup_axes() - self.show_graph() - self.show_area() - self.ask_about_area() - self.show_confusion() - self.show_variable_endpoint() - self.name_integral() - - def show_graph(self): - graph = self.get_graph(self.func) - graph_label = self.get_graph_label( - graph, self.graph_label_tex, - direction = LEFT, - x_val = self.graph_label_x_val, - ) - - self.play(ShowCreation(graph)) - self.play(Write(graph_label)) - self.wait() - - self.graph = graph - self.graph_label = graph_label - - def show_area(self): - dx_list = [0.25/(2**n) for n in range(self.n_rect_iterations)] - rect_lists = [ - self.get_riemann_rectangles( - self.graph, - x_min = 0, - x_max = self.default_right_x, - dx = dx, - stroke_width = 4*dx, - ) - for dx in dx_list - ] - rects = rect_lists[0] - foreground_mobjects = [self.axes, self.graph] - - self.play( - DrawBorderThenFill( - rects, - run_time = 2, - rate_func = smooth, - lag_ratio = 0.5, - ), - *list(map(Animation, foreground_mobjects)) - ) - self.wait() - for new_rects in rect_lists[1:]: - self.play( - Transform( - rects, new_rects, - lag_ratio = 0.5, - ), - *list(map(Animation, foreground_mobjects)) - ) - self.wait() - - self.rects = rects - self.dx = dx_list[-1] - self.foreground_mobjects = foreground_mobjects - - def ask_about_area(self): - rects = self.rects - question = TextMobject("Area?") - question.move_to(rects.get_top(), DOWN) - mid_rect = rects[2*len(rects)/3] - arrow = Arrow(question.get_bottom(), mid_rect.get_center()) - - v_lines = VGroup(*[ - DashedLine( - FRAME_HEIGHT*UP, ORIGIN, - color = RED - ).move_to(self.coords_to_point(x, 0), DOWN) - for x in (0, self.default_right_x) - ]) - - self.play( - Write(question), - ShowCreation(arrow) - ) - self.wait() - self.play(ShowCreation(v_lines, run_time = 2)) - self.wait() - - self.foreground_mobjects += [question, arrow] - self.question = question - self.question_arrow = arrow - self.v_lines = v_lines - - def show_confusion(self): - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - - self.play(FadeIn(morty)) - self.play( - morty.change_mode, "confused", - morty.look_at, self.question, - ) - self.play(morty.look_at, self.rects.get_bottom()) - self.play(Blink(morty)) - self.play(morty.look_at, self.question) - self.wait() - self.play(Blink(morty)) - self.play(FadeOut(morty)) - - def show_variable_endpoint(self): - triangle = RegularPolygon( - n = 3, - start_angle = np.pi/2, - stroke_width = 0, - fill_color = WHITE, - fill_opacity = 1, - ) - triangle.set_height(0.25) - triangle.move_to(self.v_lines[1].get_bottom(), UP) - x_label = TexMobject("x") - x_label.next_to(triangle, DOWN) - self.right_point_slider = VGroup(triangle, x_label) - - A_func = TexMobject("A(x)") - A_func.move_to(self.question, DOWN) - - self.play(FadeOut(self.x_axis.numbers)) - self.x_axis.remove(*self.x_axis.numbers) - self.foreground_mobjects.remove(self.axes) - self.play(DrawBorderThenFill(self.right_point_slider)) - self.move_right_point_to(2) - self.wait() - self.move_right_point_to(self.default_right_x) - self.wait() - self.play(ReplacementTransform(self.question, A_func)) - self.wait() - - self.A_func = A_func - - def name_integral(self): - f_tex = "$%s$"%self.graph_label_tex - words = TextMobject("``Integral'' of ", f_tex) - words.set_color_by_tex(f_tex, self.graph_label.get_color()) - brace = Brace(self.A_func, UP) - words.next_to(brace, UP) - - self.play( - Write(words), - GrowFromCenter(brace) - ) - self.wait() - for x in 4, 2, self.default_right_x: - self.move_right_point_to(x, run_time = 2) - - self.integral_words_group = VGroup(brace, words) - - #### - - def move_right_point_to(self, target_x, **kwargs): - v_line = self.v_lines[1] - slider = self.right_point_slider - rects = self.rects - curr_x = self.x_axis.point_to_number(v_line.get_bottom()) - - group = VGroup(rects, v_line, slider) - def update_group(group, alpha): - rects, v_line, slider = group - new_x = interpolate(curr_x, target_x, alpha) - new_rects = self.get_riemann_rectangles( - self.graph, - x_min = 0, - x_max = new_x, - dx = self.dx*new_x/3.0, - stroke_width = rects[0].get_stroke_width(), - ) - point = self.coords_to_point(new_x, 0) - v_line.move_to(point, DOWN) - slider.move_to(point, UP) - Transform(rects, new_rects).update(1) - return VGroup(rects, v_line, slider) - - self.play( - UpdateFromAlphaFunc( - group, update_group, - **kwargs - ), - *list(map(Animation, self.foreground_mobjects)) - ) - -class WhoCaresAboutArea(TeacherStudentsScene): - def construct(self): - point = 2*RIGHT+3*UP - - self.student_says( - "Who cares!?!", target_mode = "angry", - ) - self.play(self.teacher.change_mode, "guilty") - self.wait() - self.play( - RemovePiCreatureBubble(self.students[1]), - self.teacher.change_mode, "raise_right_hand", - self.teacher.look_at, point - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = point, - added_anims = [self.teacher.look_at, point] - - ) - self.wait(3) - -class PlayWithThisIdea(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Play with", "the", "thought!", - target_mode = "hooray" - ) - self.change_student_modes(*["happy"]*3) - self.wait() - equation = TexMobject("A(x)", "\\leftrightarrow", "x^2") - equation.set_color_by_tex("x^2", BLUE) - self.teacher_says(equation, target_mode = "sassy") - self.change_student_modes(*["thinking"]*3) - self.wait(2) - -class PlayingTowardsDADX(AreaUnderParabola, ReconfigurableScene): - CONFIG = { - "n_rect_iterations" : 6, - "deriv_dx" : 0.2, - "graph_origin" : 2.5*DOWN + 6*LEFT, - } - def setup(self): - AreaUnderParabola.setup(self) - ReconfigurableScene.setup(self) - - def construct(self): - self.fast_forward_to_end_of_previous_scene() - - self.nudge_x() - self.describe_sliver() - self.shrink_dx() - self.write_dA_dx() - self.dA_remains_a_mystery() - self.write_example_inputs() - self.show_dA_dx_in_detail() - self.show_smaller_x() - - def fast_forward_to_end_of_previous_scene(self): - self.force_skipping() - AreaUnderParabola.construct(self) - self.revert_to_original_skipping_status() - - def nudge_x(self): - shadow_rects = self.rects.copy() - shadow_rects.set_fill(BLACK, opacity = 0.5) - - original_v_line = self.v_lines[1].copy() - right_v_lines = VGroup(original_v_line, self.v_lines[1]) - curr_x = self.x_axis.point_to_number(original_v_line.get_bottom()) - - self.add(original_v_line) - self.foreground_mobjects.append(original_v_line) - self.move_right_point_to(curr_x + self.deriv_dx) - self.play( - FadeIn(shadow_rects), - *list(map(Animation, self.foreground_mobjects)) - ) - - self.shadow_rects = shadow_rects - self.right_v_lines = right_v_lines - - def describe_sliver(self): - dx_brace = Brace(self.right_v_lines, DOWN, buff = 0) - dx_label = dx_brace.get_text("$dx$") - dx_group = VGroup(dx_brace, dx_label) - - dA_rect = Rectangle( - width = self.right_v_lines.get_width(), - height = self.shadow_rects[-1].get_height(), - stroke_width = 0, - fill_color = YELLOW, - fill_opacity = 0.5, - ).move_to(self.right_v_lines, DOWN) - dA_label = TexMobject("d", "A") - dA_label.next_to(dA_rect, RIGHT, MED_LARGE_BUFF, UP) - dA_label.set_color(GREEN) - dA_arrow = Arrow( - dA_label.get_bottom()+MED_SMALL_BUFF*DOWN, - dA_rect.get_center(), - buff = 0, - color = WHITE - ) - - difference_in_area = TextMobject( - "d", "ifference in ", "A", "rea", - arg_separator = "" - ) - difference_in_area.set_color_by_tex("d", GREEN) - difference_in_area.set_color_by_tex("A", GREEN) - difference_in_area.scale(0.7) - difference_in_area.next_to(dA_label, UP, MED_SMALL_BUFF, LEFT) - - side_brace = Brace(dA_rect, LEFT, buff = 0) - graph_label_copy = self.graph_label.copy() - - self.play( - FadeOut(self.right_point_slider), - FadeIn(dx_group) - ) - self.play(Indicate(dx_label)) - self.wait() - self.play(ShowCreation(dA_arrow)) - self.wait() - self.play(Write(dA_label, run_time = 2)) - self.wait() - self.play( - ReplacementTransform(dA_label[0].copy(), difference_in_area[0]), - ReplacementTransform(dA_label[1].copy(), difference_in_area[2]), - *list(map(FadeIn, [difference_in_area[1], difference_in_area[3]])) - ) - self.wait(2) - self.play(FadeIn(dA_rect), Animation(dA_arrow)) - self.play(GrowFromCenter(side_brace)) - self.play( - graph_label_copy.set_color, WHITE, - graph_label_copy.next_to, side_brace, LEFT, SMALL_BUFF - ) - self.wait() - self.play(Indicate(dx_group)) - self.wait() - self.play(FadeOut(difference_in_area)) - - self.dx_group = dx_group - self.dA_rect = dA_rect - self.dA_label = dA_label - self.graph_label_copy = graph_label_copy - - def shrink_dx(self, **kwargs): - self.transition_to_alt_config( - deriv_dx = 0.05, - transformation_kwargs = {"run_time" : 2}, - **kwargs - ) - - def write_dA_dx(self): - f_tex = self.graph_label_tex - equation = TexMobject("dA", "\\approx", f_tex, "dx") - equation.to_edge(RIGHT).shift(3*UP) - deriv_equation = TexMobject( - "{dA", "\\over \\,", "dx}", "\\approx", f_tex - ) - deriv_equation.move_to(equation, UP+LEFT) - - for tex_mob in equation, deriv_equation: - tex_mob.set_color_by_tex( - "dA", self.dA_label.get_color() - ) - - dA = VGroup(self.dA_label[0][0], self.dA_label[1][0]) - x_squared = self.graph_label_copy - dx = self.dx_group[1] - - self.play(*[ - ReplacementTransform( - mob.copy(), - equation.get_part_by_tex(tex), - run_time = 2 - ) - for mob, tex in [(x_squared, f_tex), (dx, "dx"), (dA, "dA")] - ]) - self.play(Write(equation.get_part_by_tex("approx"))) - self.wait() - for tex, mob in (f_tex, x_squared), ("dx", dx): - self.play(*list(map(Indicate, [ - equation.get_part_by_tex(tex), - mob - ]))) - self.wait(2) - self.play(*[ - ReplacementTransform( - equation.get_part_by_tex(tex), - deriv_equation.get_part_by_tex(tex), - run_time = 2, - ) - for tex in ("dA", "approx", f_tex, "dx") - ] + [ - Write(deriv_equation.get_part_by_tex("over")) - ]) - self.wait(2) - self.shrink_dx(return_to_original_configuration = False) - self.wait() - - self.deriv_equation = deriv_equation - - def dA_remains_a_mystery(self): - randy = Randolph(color = BLUE_C) - randy.to_corner(DOWN+LEFT) - randy.look_at(self.A_func) - - A_circle, dA_circle = [ - Circle(color = color).replace( - mob, stretch = True - ).scale_in_place(1.5) - for mob, color in [(self.A_func, RED), (self.deriv_equation, GREEN)] - ] - q_marks = TexMobject("???") - q_marks.next_to(A_circle, UP) - - self.play( - FadeOut(self.integral_words_group), - FadeIn(randy) - ) - self.play( - ShowCreation(A_circle), - randy.change_mode, "confused" - ) - self.play(Write(q_marks, run_time = 2)) - self.play(Blink(randy)) - self.wait() - self.play( - randy.change_mode, "surprised", - randy.look_at, dA_circle, - ReplacementTransform(A_circle, dA_circle) - ) - self.play(Blink(randy)) - self.wait() - self.play(*list(map(FadeOut, [randy, q_marks, dA_circle]))) - - def write_example_inputs(self): - d = self.default_right_x - three = TexMobject("x =", "%d"%d) - three_plus_dx = TexMobject("x = ", "%d.001"%d) - labels_lines_vects = list(zip( - [three, three_plus_dx], - self.right_v_lines, - [LEFT, RIGHT] - )) - - for label, line, vect in labels_lines_vects: - point = line.get_bottom() - label.next_to(point, DOWN+vect, MED_SMALL_BUFF) - label.shift(LARGE_BUFF*vect) - label.arrow = Arrow( - label, point, - buff = SMALL_BUFF, - color = WHITE, - tip_length = 0.15 - ) - line_copy = line.copy() - line_copy.set_color(YELLOW) - - self.play( - FadeIn(label), - FadeIn(label.arrow), - ShowCreation(line_copy) - ) - self.play(FadeOut(line_copy)) - self.wait() - - self.three = three - self.three_plus_dx = three_plus_dx - - def show_dA_dx_in_detail(self): - d = self.default_right_x - expression = TexMobject( - "{A(", "%d.001"%d, ") ", "-A(", "%d"%d, ")", - "\\over \\,", "0.001}", - "\\approx", "%d"%d, "^2" - ) - expression.scale(0.9) - expression.next_to( - self.deriv_equation, DOWN, MED_LARGE_BUFF - ) - expression.to_edge(RIGHT) - - self.play( - ReplacementTransform( - self.three_plus_dx.get_part_by_tex("%d.001"%d).copy(), - expression.get_part_by_tex("%d.001"%d) - ), - Write(VGroup( - expression.get_part_by_tex("A("), - expression.get_part_by_tex(")"), - )), - ) - self.wait() - self.play( - ReplacementTransform( - self.three.get_part_by_tex("%d"%d).copy(), - expression.get_part_by_tex("%d"%d, substring = False) - ), - Write(VGroup( - expression.get_part_by_tex("-A("), - expression.get_parts_by_tex(")")[1], - )), - ) - self.wait(2) - self.play( - Write(expression.get_part_by_tex("over")), - ReplacementTransform( - expression.get_part_by_tex("%d.001"%d).copy(), - expression.get_part_by_tex("0.001"), - ) - - ) - self.wait() - self.play( - Write(expression.get_part_by_tex("approx")), - ReplacementTransform( - self.graph_label_copy.copy(), - VGroup(*expression[-2:]), - run_time = 2 - ) - ) - self.wait() - - def show_smaller_x(self): - self.transition_to_alt_config( - default_right_x = 2, - deriv_dx = 0.04, - transformation_kwargs = {"run_time" : 2} - ) - -class AlternateAreaUnderCurve(PlayingTowardsDADX): - CONFIG = { - "func" : lambda x : (x-2)**3 - 3*(x-2) + 6, - "graph_label_tex" : "f(x)", - "deriv_dx" : 0.1, - "x_max" : 5, - "x_axis_width" : 11, - "graph_label_x_val" : 4.5, - } - def construct(self): - #Superclass parts to skip - self.force_skipping() - self.setup_axes() - self.show_graph() - self.show_area() - self.ask_about_area() - self.show_confusion() - - #Superclass parts to show - self.revert_to_original_skipping_status() - self.show_variable_endpoint() - self.name_integral() - self.nudge_x() - self.describe_sliver() - self.write_dA_dx() - - #New animations - self.approximation_improves_for_smaller_dx() - self.name_derivative() - - def approximation_improves_for_smaller_dx(self): - color = YELLOW - approx = self.deriv_equation.get_part_by_tex("approx") - dx_to_zero_words = TextMobject( - "Gets better \\\\ as", - "$dx \\to 0$" - ) - dx_to_zero_words.set_color_by_tex("dx", color) - dx_to_zero_words.next_to(approx, DOWN, 1.5*LARGE_BUFF) - arrow = Arrow(dx_to_zero_words, approx, color = color) - - self.play( - approx.set_color, color, - ShowCreation(arrow), - FadeIn(dx_to_zero_words), - ) - self.wait() - self.transition_to_alt_config( - deriv_dx = self.deriv_dx/4.0, - transformation_kwargs = {"run_time" : 2} - ) - - self.dx_to_zero_words = dx_to_zero_words - self.dx_to_zero_words_arrow = arrow - - def name_derivative(self): - deriv_words = TextMobject("``Derivative'' of $A$") - deriv_words.scale(0.9) - deriv_words.to_edge(UP+RIGHT) - moving_group = VGroup( - self.deriv_equation, - self.dx_to_zero_words, - self.dx_to_zero_words_arrow, - ) - moving_group.generate_target() - moving_group.target.next_to(deriv_words, DOWN, LARGE_BUFF) - moving_group.target.to_edge(RIGHT) - - self.play( - FadeIn(deriv_words), - MoveToTarget(moving_group) - ) - - dA_dx = VGroup(*self.deriv_equation[:3]) - box = Rectangle(color = GREEN) - box.replace(dA_dx, stretch = True) - box.scale_in_place(1.3) - brace = Brace(box, UP) - faders = VGroup( - self.dx_to_zero_words[0], - self.dx_to_zero_words_arrow - ) - dx_to_zero = self.dx_to_zero_words[1] - - self.play(*list(map(FadeIn, [box, brace]))) - self.wait() - self.play( - FadeOut(faders), - dx_to_zero.next_to, box, DOWN - ) - self.wait() - - - ######## - def show_smaller_x(self): - return - - def shrink_dx(self, **kwargs): - return - -class NextVideoWrapper(Scene): - def construct(self): - rect = Rectangle(height = 9, width = 16) - rect.set_height(1.5*FRAME_Y_RADIUS) - titles = [ - TextMobject("Chapter %d:"%d, s) - for d, s in [ - (2, "The paradox of the derivative"), - (3, "Derivative formulas through geometry"), - ] - ] - for title in titles: - title.to_edge(UP) - rect.next_to(VGroup(*titles), DOWN) - - self.add(titles[0]) - self.play(ShowCreation(rect)) - self.wait(3) - self.play(Transform(*titles)) - self.wait(3) - -class ProblemSolvingTool(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - The derivative is a - problem-solving tool - """) - self.wait(3) - -class FundamentalTheorem(Scene): - def construct(self): - words = TextMobject(""" - Fundamental theorem of calculus - """) - words.to_edge(UP) - arrow = DoubleArrow(LEFT, RIGHT).shift(2*RIGHT) - deriv = TexMobject( - "{dA", "\\over \\,", "dx}", "=", "x^2" - ) - deriv.set_color_by_tex("dA", GREEN) - deriv.next_to(arrow, RIGHT) - - self.play(ShowCreation(arrow)) - self.wait() - self.play(Write(deriv)) - self.wait() - self.play(Write(words)) - self.wait() - -class NextVideos(TeacherStudentsScene): - def construct(self): - series = VideoSeries() - series.to_edge(UP) - this_video = series[0] - this_video.set_color(YELLOW) - - self.add(series) - self.teacher_says( - "That's a high-level view" - ) - self.wait() - self.play( - RemovePiCreatureBubble( - self.teacher, - target_mode = "raise_right_hand", - look_at_arg = this_video, - ), - *it.chain(*[ - [pi.change_mode, "pondering", pi.look_at, this_video] - for pi in self.get_students() - ]) - ) - self.play(*[ - ApplyMethod(pi.look_at, series) - for pi in self.get_pi_creatures() - ]) - self.play(*[ - ApplyMethod( - video.shift, 0.5*video.get_height()*DOWN, - run_time = 3, - rate_func = squish_rate_func( - there_and_back, alpha, alpha+0.3 - ) - ) - for video, alpha in zip(series, np.linspace(0, 0.7, len(series))) - ]) - self.wait() - - student = self.get_students()[1] - self.remove(student) - everything = VGroup(*self.get_top_level_mobjects()) - self.add(student) - words = TextMobject(""" - You could have - invented this. - """) - words.next_to(student, UP, LARGE_BUFF) - - self.play(self.teacher.change_mode, "plain") - self.play( - everything.fade, 0.75, - student.change_mode, "plain" - ) - self.play( - Write(words), - student.look_at, words, - ) - self.play( - student.change_mode, "confused", - student.look_at, words - ) - self.wait(3) - self.play(student.change_mode, "thinking") - self.wait(4) - -class Chapter1PatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "CrypticSwarm", - "Juan Benet", - "Yu Jun", - "Othman Alikhan", - "Markus Persson", - "Joseph John Cox", - "Luc Ritchie", - "Einar Johansen", - "Rish Kundalia", - "Achille Brighton", - "Kirk Werklund", - "Ripta Pasay", - "Felipe Diniz", - ], - "patron_scale_val" : 0.9 - } - -class EndScreen(PiCreatureScene): - CONFIG = { - "seconds_to_blink" : 3, - } - def construct(self): - words = TextMobject("Clicky stuffs") - words.scale(1.5) - words.next_to(self.pi_creature, UP) - words.to_edge(UP) - - self.play( - FadeIn( - words, - run_time = 2, - lag_ratio = 0.5 - ), - self.pi_creature.change_mode, "hooray" - ) - self.wait() - mode_point_pairs = [ - ("raise_left_hand", 5*LEFT+3*UP), - ("raise_right_hand", 5*RIGHT+3*UP), - ("thinking", 5*LEFT+2*DOWN), - ("thinking", 5*RIGHT+2*DOWN), - ("thinking", 5*RIGHT+2*DOWN), - ("happy", 5*LEFT+3*UP), - ("raise_right_hand", 5*RIGHT+3*UP), - ] - for mode, point in mode_point_pairs: - self.play(self.pi_creature.change, mode, point) - self.wait() - self.wait(3) - - - def create_pi_creature(self): - self.pi_creature = Randolph() - self.pi_creature.shift(2*DOWN + 1.5*LEFT) - return self.pi_creature - -class Thumbnail(AlternateAreaUnderCurve): - CONFIG = { - "x_axis_label" : "", - "y_axis_label" : "", - "graph_origin" : 2.4 * DOWN + 3 * LEFT, - } - def construct(self): - self.setup_axes() - self.remove(*self.x_axis.numbers) - self.remove(*self.y_axis.numbers) - graph = self.get_graph(self.func) - rects = self.get_riemann_rectangles( - graph, - x_min = 0, - x_max = 4, - dx = 0.25, - start_color = BLUE_E, - ) - words = TextMobject(""" - Could \\emph{you} invent - calculus? - """) - words.set_width(9) - words.to_edge(UP) - - self.add(graph, rects, words) - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eoc/chapter10.py b/from_3b1b/old/eoc/chapter10.py deleted file mode 100644 index 08229472..00000000 --- a/from_3b1b/old/eoc/chapter10.py +++ /dev/null @@ -1,3675 +0,0 @@ -from manimlib.imports import * - -def derivative(func, x, n = 1, dx = 0.01): - samples = [func(x + (k - n/2)*dx) for k in range(n+1)] - while len(samples) > 1: - samples = [ - (s_plus_dx - s)/dx - for s, s_plus_dx in zip(samples, samples[1:]) - ] - return samples[0] - -def taylor_approximation(func, highest_term, center_point = 0): - derivatives = [ - derivative(func, center_point, n = n) - for n in range(highest_term + 1) - ] - coefficients = [ - d/math.factorial(n) - for n, d in enumerate(derivatives) - ] - return lambda x : sum([ - c*((x-center_point)**n) - for n, c in enumerate(coefficients) - ]) - -class Chapter10OpeningQuote(OpeningQuote): - CONFIG = { - "quote" : [ - "For me, mathematics is a collection of ", - "examples", "; a ", - "theorem", " is a statement about a collection of ", - "examples", " and the purpose of proving ", - "theorems", " is to classify and explain the ", - "examples", "." - ], - "quote_arg_separator" : "", - "highlighted_quote_terms" : { - "examples" : BLUE, - }, - "author" : "John B. Conway", - "fade_in_kwargs" : { - "run_time" : 7, - } - } - -class ExampleApproximation(GraphScene): - CONFIG = { - "function" : lambda x : np.exp(-x**2), - "function_tex" : "e^{-x^2}", - "function_color" : BLUE, - "order_sequence" : [0, 2, 4], - "center_point" : 0, - "approximation_terms" : ["1 ", "-x^2", "+\\frac{1}{2}x^4"], - "approximation_color" : GREEN, - "x_min" : -3, - "x_max" : 3, - "y_min" : -1, - "y_max" : 2, - "graph_origin" : DOWN + 2*LEFT, - } - def construct(self): - self.setup_axes() - func_graph = self.get_graph( - self.function, - self.function_color, - ) - approx_graphs = [ - self.get_graph( - taylor_approximation(self.function, n), - self.approximation_color - ) - for n in self.order_sequence - ] - - near_text = TextMobject( - "Near %s $= %d$"%( - self.x_axis_label, self.center_point - ) - ) - near_text.to_corner(UP + RIGHT) - near_text.add_background_rectangle() - equation = TexMobject( - self.function_tex, - "\\approx", - *self.approximation_terms - ) - equation.next_to(near_text, DOWN, MED_LARGE_BUFF) - equation.to_edge(RIGHT) - near_text.next_to(equation, UP, MED_LARGE_BUFF) - equation.set_color_by_tex( - self.function_tex, self.function_color, - substring = False - ) - approx_terms = VGroup(*[ - equation.get_part_by_tex(tex, substring = False) - for tex in self.approximation_terms - ]) - approx_terms.set_fill( - self.approximation_color, - opacity = 0, - ) - equation.add_background_rectangle() - - approx_graph = VectorizedPoint( - self.input_to_graph_point(self.center_point, func_graph) - ) - - self.play( - ShowCreation(func_graph, run_time = 2), - Animation(equation), - Animation(near_text), - ) - for graph, term in zip(approx_graphs, approx_terms): - self.play( - Transform(approx_graph, graph, run_time = 2), - Animation(equation), - Animation(near_text), - term.set_fill, None, 1, - ) - self.wait() - self.wait(2) - -class ExampleApproximationWithSine(ExampleApproximation): - CONFIG = { - "function" : np.sin, - "function_tex" : "\\sin(x)", - "order_sequence" : [1, 3, 5], - "center_point" : 0, - "approximation_terms" : [ - "x", - "-\\frac{1}{6}x^3", - "+\\frac{1}{120}x^5", - ], - "approximation_color" : GREEN, - "x_min" : -2*np.pi, - "x_max" : 2*np.pi, - "x_tick_frequency" : np.pi/2, - "y_min" : -2, - "y_max" : 2, - "graph_origin" : DOWN + 2*LEFT, - } - -class ExampleApproximationWithExp(ExampleApproximation): - CONFIG = { - "function" : np.exp, - "function_tex" : "e^x", - "order_sequence" : [1, 2, 3, 4], - "center_point" : 0, - "approximation_terms" : [ - "1 + x", - "+\\frac{1}{2}x^2", - "+\\frac{1}{6}x^3", - "+\\frac{1}{24}x^4", - ], - "approximation_color" : GREEN, - "x_min" : -3, - "x_max" : 4, - "y_min" : -1, - "y_max" : 10, - "graph_origin" : 2*DOWN + 3*LEFT, - } - -class Pendulum(ReconfigurableScene): - CONFIG = { - "anchor_point" : 3*UP + 4*LEFT, - "radius" : 4, - "weight_radius" : 0.2, - "angle" : np.pi/6, - "approx_tex" : [ - "\\approx 1 - ", "{\\theta", "^2", "\\over", "2}" - ], - "leave_original_cosine" : False, - "perform_substitution" : True, - } - def construct(self): - self.draw_pendulum() - self.show_oscillation() - self.show_height() - self.get_angry_at_cosine() - self.substitute_approximation() - self.show_confusion() - - def draw_pendulum(self): - pendulum = self.get_pendulum() - ceiling = self.get_ceiling() - - self.add(ceiling) - self.play(ShowCreation(pendulum.line)) - self.play(DrawBorderThenFill(pendulum.weight, run_time = 1)) - - self.pendulum = pendulum - - def show_oscillation(self): - trajectory_dots = self.get_trajectory_dots() - kwargs = self.get_swing_kwargs() - - self.play( - ShowCreation( - trajectory_dots, - rate_func=linear, - run_time = kwargs["run_time"] - ), - Rotate(self.pendulum, -2*self.angle, **kwargs), - ) - for m in 2, -2, 2: - self.play(Rotate(self.pendulum, m*self.angle, **kwargs)) - self.wait() - - def show_height(self): - v_line = self.get_v_line() - h_line = self.get_h_line() - radius_brace = self.get_radius_brace() - height_brace = self.get_height_brace() - height_tex = self.get_height_brace_tex(height_brace) - arc, theta = self.get_arc_and_theta() - - height_tex_R = height_tex.get_part_by_tex("R") - height_tex_theta = height_tex.get_part_by_tex("\\theta") - to_write = VGroup(*[ - part - for part in height_tex - if part not in [height_tex_R, height_tex_theta] - ]) - - self.play( - ShowCreation(h_line), - GrowFromCenter(height_brace) - ) - self.play( - ShowCreation(v_line), - ShowCreation(arc), - Write(theta), - ) - self.play( - GrowFromCenter(radius_brace) - ) - self.wait(2) - self.play( - Write(to_write), - ReplacementTransform( - radius_brace[-1].copy(), - height_tex_R - ), - ReplacementTransform( - theta.copy(), - height_tex_theta - ), - run_time = 2 - ) - self.wait(2) - - self.arc = arc - self.theta = theta - self.height_tex_R = height_tex_R - self.cosine = VGroup(*[ - height_tex.get_part_by_tex(tex) - for tex in ("cos", "theta", ")") - ]) - self.one_minus = VGroup(*[ - height_tex.get_part_by_tex(tex) - for tex in ("\\big(1-", "\\big)") - ]) - - def get_angry_at_cosine(self): - cosine = self.cosine - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - cosine.generate_target() - cosine.save_state() - cosine.target.next_to(morty, UP) - if self.leave_original_cosine: - cosine_copy = cosine.copy() - self.add(cosine_copy) - self.one_minus.add(cosine_copy) - - self.play(FadeIn(morty)) - self.play( - MoveToTarget(cosine), - morty.change, "angry", cosine.target, - ) - self.wait() - self.play(Blink(morty)) - self.wait() - - self.morty = morty - - def substitute_approximation(self): - morty = self.morty - cosine = self.cosine - cosine.generate_target() - cosine_approx = self.get_cosine_approx() - cosine_approx.next_to(cosine, UP+RIGHT) - cosine_approx.to_edge(RIGHT) - cosine.target.next_to( - cosine_approx, LEFT, - align_using_submobjects = True - ) - kwargs = self.get_swing_kwargs() - - self.play( - FadeIn( - cosine_approx, - run_time = 2, - lag_ratio = 0.5 - ), - MoveToTarget(cosine), - morty.change, "pondering", cosine_approx - ) - self.wait() - if not self.perform_substitution: - return - self.play( - ApplyMethod( - cosine_approx.theta_squared_over_two.copy().next_to, - self.height_tex_R, - run_time = 2, - ), - FadeOut(self.one_minus), - morty.look_at, self.height_tex_R, - ) - self.play(morty.change, "thinking", self.height_tex_R) - self.transition_to_alt_config( - angle = np.pi/12, - transformation_kwargs = {"run_time" : 2}, - ) - - def show_confusion(self): - randy = Randolph(color = BLUE_C) - randy.scale(0.8) - randy.next_to(self.cosine, DOWN+LEFT) - randy.to_edge(DOWN) - - self.play(FadeIn(randy)) - self.play( - randy.change, "confused", self.cosine - ) - self.play(randy.look_at, self.height_tex_R) - self.wait() - self.play(randy.look_at, self.cosine) - self.play(Blink(randy)) - self.wait() - - ####### - - def get_pendulum(self): - line = Line( - self.anchor_point, - self.anchor_point + self.radius*DOWN, - color = WHITE - ) - weight = Circle( - radius = self.weight_radius, - fill_color = GREY, - fill_opacity = 1, - stroke_width = 0, - ) - weight.move_to(line.get_end()) - result = VGroup(line, weight) - result.rotate( - self.angle, - about_point = self.anchor_point - ) - result.line = line - result.weight = weight - - return result - - def get_ceiling(self): - line = Line(LEFT, RIGHT, color = GREY) - line.scale(FRAME_X_RADIUS) - line.move_to(self.anchor_point[1]*UP) - return line - - def get_trajectory_dots(self, n_dots = 40, color = YELLOW): - arc_angle = np.pi/6 - proportions = self.swing_rate_func( - np.linspace(0, 1, n_dots) - ) - angles = -2*arc_angle*proportions - angle_to_point = lambda a : np.cos(a)*RIGHT + np.sin(a)*UP - dots = VGroup(*[ - # Line(*map(angle_to_point, pair)) - Dot(angle_to_point(angle), radius = 0.005) - for angle in angles - ]) - - dots.set_color(color) - dots.scale(self.radius) - dots.rotate(-np.pi/2 + arc_angle) - dots.shift(self.anchor_point) - return dots - - def get_v_line(self): - return DashedLine( - self.anchor_point, - self.anchor_point + self.radius*DOWN, - color = WHITE - ) - - def get_h_line(self, color = BLUE): - start = self.anchor_point + self.radius*DOWN - end = start + self.radius*np.sin(self.angle)*RIGHT - - return Line(start, end, color = color) - - def get_radius_brace(self): - v_line = self.get_v_line() - brace = Brace(v_line, RIGHT) - brace.rotate(self.angle, about_point = self.anchor_point) - brace.add(brace.get_text("$R$", buff = SMALL_BUFF)) - return brace - - def get_height_brace(self): - h_line = self.get_h_line() - height = (1 - np.cos(self.angle))*self.radius - line = Line( - h_line.get_end(), - h_line.get_end() + height*UP, - ) - brace = Brace(line, RIGHT) - return brace - - def get_height_brace_tex(self, brace): - tex_mob = TexMobject( - "R", "\\big(1-", "\\cos(", "\\theta", ")", "\\big)" - ) - tex_mob.set_color_by_tex("theta", YELLOW) - tex_mob.next_to(brace, RIGHT) - return tex_mob - - def get_arc_and_theta(self): - arc = Arc( - start_angle = -np.pi/2, - angle = self.angle, - color = YELLOW - ) - theta = TexMobject("\\theta") - theta.set_color(YELLOW) - theta.next_to( - arc.point_from_proportion(0.5), - DOWN, SMALL_BUFF - ) - for mob in arc, theta: - mob.shift(self.anchor_point) - return arc, theta - - def get_cosine_approx(self): - approx = TexMobject(*self.approx_tex) - approx.set_color_by_tex("theta", YELLOW) - approx.theta_squared_over_two = VGroup(*approx[1:5]) - - return approx - - def get_swing_kwargs(self): - return { - "about_point" : self.anchor_point, - "run_time" : 1.7, - "rate_func" : self.swing_rate_func, - } - - def swing_rate_func(self, t): - return (1-np.cos(np.pi*t))/2.0 - -class PendulumWithBetterApprox(Pendulum): - CONFIG = { - "approx_tex" : [ - "\\approx 1 - ", "{\\theta", "^2", "\\over", "2}", - "+", "{\\theta", "^4", "\\over", "24}" - ], - "leave_original_cosine" : True, - "perform_substitution" : False, - } - def show_confusion(self): - pass - -class ExampleApproximationWithCos(ExampleApproximationWithSine): - CONFIG = { - "function" : np.cos, - "function_tex" : "\\cos(\\theta)", - "order_sequence" : [0, 2], - "approximation_terms" : [ - "1", - "-\\frac{1}{2} \\theta ^2", - ], - "x_axis_label" : "$\\theta$", - "y_axis_label" : "", - "x_axis_width" : 13, - "graph_origin" : DOWN, - } - - def construct(self): - ExampleApproximationWithSine.construct(self) - randy = Randolph(color = BLUE_C) - randy.to_corner(DOWN+LEFT) - high_graph = self.get_graph(lambda x : 4) - v_lines, alt_v_lines = [ - VGroup(*[ - self.get_vertical_line_to_graph( - u*dx, high_graph, - line_class = DashedLine, - color = YELLOW - ) - for u in (-1, 1) - ]) - for dx in (0.01, 0.7) - ] - - self.play(*list(map(ShowCreation, v_lines)), run_time = 2) - self.play(Transform( - v_lines, alt_v_lines, - run_time = 2, - )) - self.play(FadeIn(randy)) - self.play(PiCreatureBubbleIntroduction( - randy, "How...?", - bubble_class = ThoughtBubble, - look_at_arg = self.graph_origin, - target_mode = "confused" - )) - self.wait(2) - self.play(Blink(randy)) - self.wait() - - def setup_axes(self): - GraphScene.setup_axes(self) - x_val_label_pairs = [ - (-np.pi, "-\\pi"), - (np.pi, "\\pi"), - (2*np.pi, "2\\pi"), - ] - self.x_axis_labels = VGroup() - for x_val, label in x_val_label_pairs: - tex = TexMobject(label) - tex.next_to(self.coords_to_point(x_val, 0), DOWN) - self.add(tex) - self.x_axis_labels.add(tex) - -class ConstructQuadraticApproximation(ExampleApproximationWithCos): - CONFIG = { - "x_axis_label" : "$x$", - "colors" : [BLUE, YELLOW, GREEN], - } - def construct(self): - self.setup_axes() - self.add_cosine_graph() - self.add_quadratic_graph() - self.introduce_quadratic_constants() - self.show_value_at_zero() - self.set_c0_to_one() - self.let_c1_and_c2_vary() - self.show_tangent_slope() - self.compute_cosine_derivative() - self.compute_polynomial_derivative() - self.let_c2_vary() - self.point_out_negative_concavity() - self.compute_cosine_second_derivative() - self.show_matching_curvature() - self.show_matching_tangent_lines() - self.compute_polynomial_second_derivative() - self.box_final_answer() - - def add_cosine_graph(self): - cosine_label = TexMobject("\\cos(x)") - cosine_label.to_corner(UP+LEFT) - cosine_graph = self.get_graph(np.cos) - dot = Dot(color = WHITE) - dot.move_to(cosine_label) - for mob in cosine_label, cosine_graph: - mob.set_color(self.colors[0]) - - def update_dot(dot): - dot.move_to(cosine_graph.points[-1]) - return dot - - self.play(Write(cosine_label, run_time = 1)) - self.play(dot.move_to, cosine_graph.points[0]) - self.play( - ShowCreation(cosine_graph), - UpdateFromFunc(dot, update_dot), - run_time = 4 - ) - self.play(FadeOut(dot)) - - self.cosine_label = cosine_label - self.cosine_graph = cosine_graph - - def add_quadratic_graph(self): - quadratic_graph = self.get_quadratic_graph() - - self.play(ReplacementTransform( - self.cosine_graph.copy(), - quadratic_graph, - run_time = 3 - )) - - self.quadratic_graph = quadratic_graph - - def introduce_quadratic_constants(self): - quadratic_tex = self.get_quadratic_tex("c_0", "c_1", "c_2") - const_terms = quadratic_tex.get_parts_by_tex("c") - free_to_change = TextMobject("Free to change") - free_to_change.next_to(const_terms, DOWN, LARGE_BUFF) - arrows = VGroup(*[ - Arrow( - free_to_change.get_top(), - const.get_bottom(), - tip_length = 0.75*Arrow.CONFIG["tip_length"], - color = const.get_color() - ) - for const in const_terms - ]) - alt_consts_list = [ - (0, -1, -0.25), - (1, -1, -0.25), - (1, 0, -0.25), - (), - ] - - self.play(FadeIn( - quadratic_tex, - run_time = 3, - lag_ratio = 0.5 - )) - self.play( - FadeIn(free_to_change), - *list(map(ShowCreation, arrows)) - ) - self.play(*[ - ApplyMethod( - const.scale_in_place, 0.8, - run_time = 2, - rate_func = squish_rate_func(there_and_back, a, a + 0.75) - ) - for const, a in zip(const_terms, np.linspace(0, 0.25, len(const_terms))) - ]) - for alt_consts in alt_consts_list: - self.change_quadratic_graph( - self.quadratic_graph, *alt_consts - ) - self.wait() - - self.quadratic_tex = quadratic_tex - self.free_to_change_group = VGroup(free_to_change, *arrows) - self.free_to_change_group.arrows = arrows - - def show_value_at_zero(self): - arrow, x_equals_0 = ax0_group = self.get_arrow_x_equals_0_group() - ax0_group.next_to( - self.cosine_label, RIGHT, - align_using_submobjects = True - ) - one = TexMobject("1") - one.next_to(arrow, RIGHT) - one.save_state() - one.move_to(self.cosine_label) - one.set_fill(opacity = 0) - - v_line = self.get_vertical_line_to_graph( - 0, self.cosine_graph, - line_class = DashedLine, - color = YELLOW - ) - - self.play(ShowCreation(v_line)) - self.play( - ShowCreation(arrow), - Write(x_equals_0, run_time = 2) - ) - self.play(one.restore) - self.wait() - - self.v_line = v_line - self.equals_one_group = VGroup(arrow, x_equals_0, one) - - def set_c0_to_one(self): - poly_at_zero = self.get_quadratic_tex( - "c_0", "c_1", "c_2", arg = "0" - ) - poly_at_zero.next_to(self.quadratic_tex, DOWN) - equals_c0 = TexMobject("=", "c_0", "+0") - equals_c0.set_color_by_tex("c_0", self.colors[0]) - equals_c0.next_to( - poly_at_zero.get_part_by_tex("="), DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - poly_group = VGroup( - equals_c0, - poly_at_zero, - self.quadratic_tex, - ) - poly_group_target = VGroup( - TexMobject("=", "1", "+0").set_color_by_tex("1", self.colors[0]), - self.get_quadratic_tex("1", "c_1", "c_2", arg = "0"), - self.get_quadratic_tex("1", "c_1", "c_2"), - ) - for start, target in zip(poly_group, poly_group_target): - target.move_to(start) - - self.play(FadeOut(self.free_to_change_group)) - self.play(ReplacementTransform( - self.quadratic_tex.copy(), - poly_at_zero - )) - self.wait(2) - self.play(FadeIn(equals_c0)) - self.wait(2) - self.play(Transform( - poly_group, poly_group_target, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait(2) - self.play(*list(map(FadeOut, [poly_at_zero, equals_c0]))) - - self.free_to_change_group.remove( - self.free_to_change_group.arrows[0] - ) - self.play(FadeIn(self.free_to_change_group)) - - def let_c1_and_c2_vary(self): - alt_consts_list = [ - (1, 1, -0.25), - (1, -1, -0.25), - (1, -1, 0.25), - (1, 1, -0.1), - ] - - for alt_consts in alt_consts_list: - self.change_quadratic_graph( - self.quadratic_graph, - *alt_consts - ) - self.wait() - - def show_tangent_slope(self): - graph_point_at_zero = self.input_to_graph_point( - 0, self.cosine_graph - ) - tangent_line = self.get_tangent_line(0, self.cosine_graph) - - self.play(ShowCreation(tangent_line)) - self.change_quadratic_graph( - self.quadratic_graph, 1, 0, -0.1 - ) - self.wait() - self.change_quadratic_graph( - self.quadratic_graph, 1, 1, -0.1 - ) - self.wait(2) - self.change_quadratic_graph( - self.quadratic_graph, 1, 0, -0.1 - ) - self.wait(2) - - self.tangent_line = tangent_line - - def compute_cosine_derivative(self): - derivative, rhs = self.get_cosine_derivative() - - - self.play(FadeIn( - VGroup(derivative, *rhs[:2]), - run_time = 2, - lag_ratio = 0.5 - )) - self.wait(2) - self.play(Write(VGroup(*rhs[2:])), run_time = 2) - self.wait() - self.play(Rotate( - self.tangent_line, np.pi/12, - in_place = True, - run_time = 3, - rate_func = wiggle - )) - self.wait() - - def compute_polynomial_derivative(self): - derivative = self.get_quadratic_derivative("c_1", "c_2") - derivative_at_zero = self.get_quadratic_derivative( - "c_1", "c_2", arg = "0" - ) - equals_c1 = TexMobject("=", "c_1", "+0") - equals_c1.next_to( - derivative_at_zero.get_part_by_tex("="), DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT, - ) - equals_c1.set_color_by_tex("c_1", self.colors[1]) - poly_group = VGroup( - equals_c1, - derivative, - self.quadratic_tex - ) - poly_group_target = VGroup( - TexMobject("=", "0", "+0").set_color_by_tex( - "0", self.colors[1], substring = False - ), - self.get_quadratic_derivative("0", "c_2", arg = "0"), - self.get_quadratic_tex("1", "0", "c_2") - ) - for start, target in zip(poly_group, poly_group_target): - target.move_to(start) - - self.play(FadeOut(self.free_to_change_group)) - self.play(FadeIn( - derivative, - run_time = 3, - lag_ratio = 0.5 - )) - self.wait() - self.play(Transform( - derivative, derivative_at_zero, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait(2) - self.play(Write(equals_c1)) - self.wait(2) - self.play(Transform( - poly_group, poly_group_target, - run_time = 3, - lag_ratio = 0.5 - )) - self.wait(2) - - self.play(*list(map(FadeOut, poly_group[:-1]))) - self.free_to_change_group.remove( - self.free_to_change_group.arrows[1] - ) - self.play(FadeIn(self.free_to_change_group)) - - def let_c2_vary(self): - alt_c2_values = [-1, -0.05, 1, -0.2] - for alt_c2 in alt_c2_values: - self.change_quadratic_graph( - self.quadratic_graph, - 1, 0, alt_c2 - ) - self.wait() - - def point_out_negative_concavity(self): - partial_cosine_graph = self.get_graph( - np.cos, - x_min = -1, - x_max = 1, - color = PINK - ) - - self.play(ShowCreation(partial_cosine_graph, run_time = 2)) - self.wait() - for x, run_time in (-1, 2), (1, 4): - self.play(self.get_tangent_line_change_anim( - self.tangent_line, x, self.cosine_graph, - run_time = run_time - )) - self.wait() - self.play(*list(map(FadeOut, [ - partial_cosine_graph, self.tangent_line - ]))) - - def compute_cosine_second_derivative(self): - second_deriv, rhs = self.get_cosine_second_derivative() - - self.play(FadeIn( - VGroup(second_deriv, *rhs[1][:2]), - run_time = 2, - lag_ratio = 0.5 - )) - self.wait(3) - self.play(Write(VGroup(*rhs[1][2:]), run_time = 2)) - self.wait() - - def show_matching_curvature(self): - alt_consts_list = [ - (1, 1, -0.2), - (1, 0, -0.2), - (1, 0, -0.5), - ] - for alt_consts in alt_consts_list: - self.change_quadratic_graph( - self.quadratic_graph, - *alt_consts - ) - self.wait() - - def show_matching_tangent_lines(self): - graphs = [self.quadratic_graph, self.cosine_graph] - tangent_lines = [ - self.get_tangent_line(0, graph, color = color) - for graph, color in zip(graphs, [WHITE, YELLOW]) - ] - tangent_change_anims = [ - self.get_tangent_line_change_anim( - line, np.pi/2, graph, - run_time = 6, - rate_func = there_and_back, - ) - for line, graph in zip(tangent_lines, graphs) - ] - - self.play(*list(map(ShowCreation, tangent_lines))) - self.play(*tangent_change_anims) - self.play(*list(map(FadeOut, tangent_lines))) - - def compute_polynomial_second_derivative(self): - c2s = ["c_2", "\\text{\\tiny $\\left(-\\frac{1}{2}\\right)$}"] - derivs = [ - self.get_quadratic_derivative("0", c2) - for c2 in c2s - ] - second_derivs = [ - TexMobject( - "{d^2 P \\over dx^2}", "(x)", "=", "2", c2 - ) - for c2 in c2s - ] - for deriv, second_deriv in zip(derivs, second_derivs): - second_deriv[0].scale( - 0.7, about_point = second_deriv[0].get_right() - ) - second_deriv[-1].set_color(self.colors[-1]) - second_deriv.next_to( - deriv, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - - poly_group = VGroup( - second_derivs[0], - derivs[0], - self.quadratic_tex - ) - poly_group_target = VGroup( - second_derivs[1], - derivs[1], - self.get_quadratic_tex("1", "0", c2s[1]) - ) - for tex_mob in poly_group_target: - tex_mob.get_part_by_tex(c2s[1]).shift(SMALL_BUFF*UP) - - self.play(FadeOut(self.free_to_change_group)) - self.play(FadeIn(derivs[0])) - self.wait(2) - self.play(Write(second_derivs[0])) - self.wait(2) - self.play(Transform( - poly_group, poly_group_target, - run_time = 3, - lag_ratio = 0.5 - )) - self.wait(3) - - def box_final_answer(self): - box = Rectangle(stroke_color = PINK) - box.stretch_to_fit_width( - self.quadratic_tex.get_width() + MED_LARGE_BUFF - ) - box.stretch_to_fit_height( - self.quadratic_tex.get_height() + MED_LARGE_BUFF - ) - box.move_to(self.quadratic_tex) - - self.play(ShowCreation(box, run_time = 2)) - self.wait(2) - - ###### - - def change_quadratic_graph(self, graph, *args, **kwargs): - transformation_kwargs = {} - transformation_kwargs["run_time"] = kwargs.pop("run_time", 2) - transformation_kwargs["rate_func"] = kwargs.pop("rate_func", smooth) - new_graph = self.get_quadratic_graph(*args, **kwargs) - self.play(Transform(graph, new_graph, **transformation_kwargs)) - graph.underlying_function = new_graph.underlying_function - - def get_quadratic_graph(self, c0 = 1, c1 = 0, c2 = -0.5): - return self.get_graph( - lambda x : c0 + c1*x + c2*x**2, - color = self.colors[2] - ) - - def get_quadratic_tex(self, c0, c1, c2, arg = "x"): - tex_mob = TexMobject( - "P(", arg, ")", "=", - c0, "+", c1, arg, "+", c2, arg, "^2" - ) - for tex, color in zip([c0, c1, c2], self.colors): - tex_mob.set_color_by_tex(tex, color) - tex_mob.to_corner(UP+RIGHT) - return tex_mob - - def get_quadratic_derivative(self, c1, c2, arg = "x"): - result = TexMobject( - "{dP \\over dx}", "(", arg, ")", "=", - c1, "+", "2", c2, arg - ) - result[0].scale(0.7, about_point = result[0].get_right()) - for index, color in zip([5, 8], self.colors[1:]): - result[index].set_color(color) - if hasattr(self, "quadratic_tex"): - result.next_to( - self.quadratic_tex, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - return result - - def get_arrow_x_equals_0_group(self): - arrow = Arrow(LEFT, RIGHT) - x_equals_0 = TexMobject("x = 0") - x_equals_0.scale(0.75) - x_equals_0.next_to(arrow.get_center(), UP, 2*SMALL_BUFF) - x_equals_0.shift(SMALL_BUFF*LEFT) - return VGroup(arrow, x_equals_0) - - def get_tangent_line(self, x, graph, color = YELLOW): - tangent_line = Line(LEFT, RIGHT, color = color) - tangent_line.rotate(self.angle_of_tangent(x, graph)) - tangent_line.scale(2) - tangent_line.move_to(self.input_to_graph_point(x, graph)) - return tangent_line - - def get_tangent_line_change_anim(self, tangent_line, new_x, graph, **kwargs): - start_x = self.x_axis.point_to_number( - tangent_line.get_center() - ) - def update(tangent_line, alpha): - x = interpolate(start_x, new_x, alpha) - new_line = self.get_tangent_line( - x, graph, color = tangent_line.get_color() - ) - Transform(tangent_line, new_line).update(1) - return tangent_line - return UpdateFromAlphaFunc(tangent_line, update, **kwargs) - - def get_cosine_derivative(self): - if not hasattr(self, "cosine_label"): - self.cosine_label = TexMobject("\\cos(x)") - self.cosine_label.to_corner(UP+LEFT) - derivative = TexMobject( - "{d(", "\\cos", ")", "\\over", "dx}", "(0)", - ) - derivative.set_color_by_tex("\\cos", self.colors[0]) - derivative.scale(0.7) - derivative.next_to( - self.cosine_label, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - rhs = TexMobject("=", "-\\sin(0)", "=", "0") - rhs.set_color_by_tex("\\sin", self.colors[1]) - rhs.scale(0.75) - rhs.next_to( - derivative, RIGHT, - align_using_submobjects = True - ) - - self.cosine_derivative = VGroup(derivative, rhs) - return self.cosine_derivative - - def get_cosine_second_derivative(self): - if not hasattr(self, "cosine_derivative"): - self.get_cosine_derivative() - second_deriv = TexMobject( - "{d^2(", "\\cos", ")", "\\over", "dx^2}", - "(", "0", ")", - ) - second_deriv.set_color_by_tex("cos", self.colors[0]) - second_deriv.set_color_by_tex("-\\cos", self.colors[2]) - second_deriv.scale(0.75) - second_deriv.add_background_rectangle() - second_deriv.next_to( - self.cosine_derivative, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - rhs = TexMobject("=", "-\\cos(0)", "=", "-1") - rhs.set_color_by_tex("cos", self.colors[2]) - rhs.scale(0.8) - rhs.next_to( - second_deriv, RIGHT, - align_using_submobjects = True - ) - rhs.add_background_rectangle() - - self.cosine_second_derivative = VGroup(second_deriv, rhs) - return self.cosine_second_derivative - -class ReflectOnQuadraticApproximation(TeacherStudentsScene): - def construct(self): - self.show_example_approximation() - # self.add_polynomial() - # self.show_c0() - # self.show_c1() - # self.show_c2() - - def show_example_approximation(self): - approx_at_x, approx_at_point = [ - TexMobject( - "\\cos(", s, ")", "\\approx", - "1 - \\frac{1}{2}", "(", s, ")", "^2" - ).next_to(self.get_students(), UP, 2) - for s in ("x", "0.1",) - ] - approx_rhs = TexMobject("=", "0.995") - approx_rhs.next_to(approx_at_point, RIGHT) - real_result = TexMobject( - "\\cos(", "0.1", ")", "=", - "%.7f\\dots"%np.cos(0.1) - ) - real_result.shift( - approx_rhs.get_part_by_tex("=").get_center() -\ - real_result.get_part_by_tex("=").get_center() - ) - for mob in approx_at_point, real_result: - mob.set_color_by_tex("0.1", YELLOW) - real_result.set_fill(opacity = 0) - - self.play( - Write(approx_at_x, run_time = 2), - self.teacher.change_mode, "raise_right_hand" - ) - self.wait(2) - self.play(ReplacementTransform( - approx_at_x, approx_at_point, - )) - self.wait() - self.play(Write(approx_rhs)) - self.wait(2) - self.play( - real_result.shift, 1.5*DOWN, - real_result.set_fill, None, 1, - ) - self.change_student_modes(*["hooray"]*3) - self.wait(2) - self.change_student_modes( - *["plain"]*3, - added_anims = list(map(FadeOut, [ - approx_at_point, approx_rhs, real_result - ])), - look_at_arg = approx_at_x - ) - - def add_polynomial(self): - polynomial = self.get_polynomial() - const_terms = polynomial.get_parts_by_tex("c") - - self.play( - Write(polynomial), - self.teacher.change, "pondering" - ) - self.wait(2) - self.play(*[ - ApplyMethod( - const.shift, MED_LARGE_BUFF*UP, - run_time = 2, - rate_func = squish_rate_func(there_and_back, a, a+0.7) - ) - for const, a in zip(const_terms, np.linspace(0, 0.3, len(const_terms))) - ]) - self.wait() - - self.const_terms = const_terms - self.polynomial = polynomial - - def show_c0(self): - c0 = self.polynomial.get_part_by_tex("c_0") - c0.save_state() - equation = TexMobject("P(0) = \\cos(0)") - equation.to_corner(UP+RIGHT) - new_polynomial = self.get_polynomial(c0 = "1") - - self.play(c0.shift, UP) - self.play(Write(equation)) - self.wait() - self.play(Transform(self.polynomial, new_polynomial)) - self.play(FadeOut(equation)) - - def show_c1(self): - c1 = self.polynomial.get_part_by_tex("c_1") - c1.save_state() - equation = TexMobject( - "\\frac{dP}{dx}(0) = \\frac{d(\\cos)}{dx}(0)" - ) - equation.to_corner(UP+RIGHT) - new_polynomial = self.get_polynomial(c0 = "1", c1 = "0") - - self.play(c1.shift, UP) - self.play(Write(equation)) - self.wait() - self.play(Transform(self.polynomial, new_polynomial)) - self.wait() - self.play(FadeOut(equation)) - - def show_c2(self): - c2 = self.polynomial.get_part_by_tex("c_2") - c2.save_state() - equation = TexMobject( - "\\frac{d^2 P}{dx^2}(0) = \\frac{d^2(\\cos)}{dx^2}(0)" - ) - equation.to_corner(UP+RIGHT) - alt_c2_tex = "\\text{\\tiny $\\left(-\\frac{1}{2}\\right)$}" - new_polynomial = self.get_polynomial( - c0 = "1", c1 = "0", c2 = alt_c2_tex - ) - new_polynomial.get_part_by_tex(alt_c2_tex).shift(SMALL_BUFF*UP) - - self.play(c2.shift, UP) - self.play(FadeIn(equation)) - self.wait(2) - self.play(Transform(self.polynomial, new_polynomial)) - self.wait(2) - self.play(FadeOut(equation)) - - ##### - - def get_polynomial(self, c0 = "c_0", c1 = "c_1", c2 = "c_2"): - polynomial = TexMobject( - "P(x) = ", c0, "+", c1, "x", "+", c2, "x^2" - ) - colors = ConstructQuadraticApproximation.CONFIG["colors"] - for tex, color in zip([c0, c1, c2], colors): - polynomial.set_color_by_tex(tex, color, substring = False) - - polynomial.next_to(self.teacher, UP, LARGE_BUFF) - polynomial.to_edge(RIGHT) - return polynomial - -class ReflectionOnQuadraticSupplement(ConstructQuadraticApproximation): - def construct(self): - self.setup_axes() - self.add(self.get_graph(np.cos, color = self.colors[0])) - quadratic_graph = self.get_quadratic_graph() - self.add(quadratic_graph) - - self.wait() - for c0 in 0, 2, 1: - self.change_quadratic_graph( - quadratic_graph, - c0 = c0 - ) - self.wait(2) - for c1 in 1, -1, 0: - self.change_quadratic_graph( - quadratic_graph, - c1 = c1 - ) - self.wait(2) - for c2 in -0.1, -1, -0.5: - self.change_quadratic_graph( - quadratic_graph, - c2 = c2 - ) - self.wait(2) - -class SimilarityOfChangeBehavior(ConstructQuadraticApproximation): - def construct(self): - colors = [YELLOW, WHITE] - max_x = np.pi/2 - - self.setup_axes() - cosine_graph = self.get_graph(np.cos, color = self.colors[0]) - quadratic_graph = self.get_quadratic_graph() - graphs = VGroup(cosine_graph, quadratic_graph) - dots = VGroup() - for graph, color in zip(graphs, colors): - dot = Dot(color = color) - dot.move_to(self.input_to_graph_point(0, graph)) - dot.graph = graph - dots.add(dot) - - def update_dot(dot, alpha): - x = interpolate(0, max_x, alpha) - dot.move_to(self.input_to_graph_point(x, dot.graph)) - dot_anims = [ - UpdateFromAlphaFunc(dot, update_dot, run_time = 3) - for dot in dots - ] - tangent_lines = VGroup(*[ - self.get_tangent_line(0, graph, color) - for graph, color in zip(graphs, colors) - ]) - tangent_line_movements = [ - self.get_tangent_line_change_anim( - line, max_x, graph, - run_time = 5, - ) - for line, graph in zip(tangent_lines, graphs) - ] - - self.add(cosine_graph, quadratic_graph) - self.play(FadeIn(dots)) - self.play(*dot_anims) - self.play( - FadeIn(tangent_lines), - FadeOut(dots) - ) - self.play(*tangent_line_movements + dot_anims, run_time = 6) - self.play(*list(map(FadeOut, [tangent_lines, dots]))) - self.wait() - -class MoreTerms(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "More terms!", - target_mode = "surprised", - ) - self.change_student_modes(*["hooray"]*3) - self.wait(3) - -class CubicAndQuarticApproximations(ConstructQuadraticApproximation): - CONFIG = { - "colors": [BLUE, YELLOW, GREEN, RED, MAROON_B], - } - def construct(self): - self.add_background() - self.take_third_derivative_of_cubic() - self.show_third_derivative_of_cosine() - self.set_c3_to_zero() - self.show_cubic_curves() - self.add_quartic_term() - self.show_fourth_derivative_of_cosine() - self.take_fourth_derivative_of_quartic() - self.solve_for_c4() - self.show_quartic_approximation() - - - def add_background(self): - self.setup_axes() - self.cosine_graph = self.get_graph( - np.cos, color = self.colors[0] - ) - self.quadratic_graph = self.get_quadratic_graph() - self.big_rect = Rectangle( - height = FRAME_HEIGHT, - width = FRAME_WIDTH, - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 0.5, - ) - self.add( - self.cosine_graph, self.quadratic_graph, - self.big_rect - ) - - self.cosine_label = TexMobject("\\cos", "(0)", "=1") - self.cosine_label.set_color_by_tex("cos", self.colors[0]) - self.cosine_label.scale(0.75) - self.cosine_label.to_corner(UP+LEFT) - self.add(self.cosine_label) - self.add(self.get_cosine_derivative()) - self.add(self.get_cosine_second_derivative()) - - self.polynomial = TexMobject( - "P(x)=", "1", "-\\frac{1}{2}", "x^2" - ) - self.polynomial.set_color_by_tex("1", self.colors[0]) - self.polynomial.set_color_by_tex("-\\frac{1}{2}", self.colors[2]) - self.polynomial.to_corner(UP+RIGHT) - self.polynomial.quadratic_part = VGroup( - *self.polynomial[1:] - ) - self.add(self.polynomial) - - def take_third_derivative_of_cubic(self): - polynomial = self.polynomial - plus_cubic_term = TexMobject("+\\,", "c_3", "x^3") - plus_cubic_term.next_to(polynomial, RIGHT) - plus_cubic_term.to_edge(RIGHT, buff = LARGE_BUFF) - plus_cubic_term.set_color_by_tex("c_3", self.colors[3]) - plus_cubic_copy = plus_cubic_term.copy() - - polynomial.generate_target() - polynomial.target.next_to(plus_cubic_term, LEFT) - - self.play(FocusOn(polynomial)) - self.play( - MoveToTarget(polynomial), - GrowFromCenter(plus_cubic_term) - ) - self.wait() - - brace = Brace(polynomial.quadratic_part, DOWN) - third_derivative = TexMobject( - "\\frac{d^3 P}{dx^3}(x) = ", "0" - ) - third_derivative.shift( - brace.get_bottom() + MED_SMALL_BUFF*DOWN -\ - third_derivative.get_part_by_tex("0").get_top() - ) - - self.play(Write(third_derivative[0])) - self.play(GrowFromCenter(brace)) - self.play(ReplacementTransform( - polynomial.quadratic_part.copy(), - VGroup(third_derivative[1]) - )) - self.wait(2) - self.play(plus_cubic_copy.next_to, third_derivative, RIGHT) - derivative_term = self.take_derivatives_of_monomial( - VGroup(*plus_cubic_copy[1:]) - ) - third_derivative.add(plus_cubic_copy[0], derivative_term) - - self.plus_cubic_term = plus_cubic_term - self.polynomial_third_derivative = third_derivative - self.polynomial_third_derivative_brace = brace - - def show_third_derivative_of_cosine(self): - cosine_third_derivative = self.get_cosine_third_derivative() - dot = Dot(fill_opacity = 0.5) - dot.move_to(self.polynomial_third_derivative) - - self.play( - dot.move_to, cosine_third_derivative, - dot.set_fill, None, 0 - ) - self.play(ReplacementTransform( - self.cosine_second_derivative.copy(), - cosine_third_derivative - )) - self.wait(2) - dot.set_fill(opacity = 0.5) - self.play( - dot.move_to, self.polynomial_third_derivative.get_right(), - dot.set_fill, None, 0, - ) - self.wait() - - def set_c3_to_zero(self): - c3s = VGroup( - self.polynomial_third_derivative[-1][-1], - self.plus_cubic_term.get_part_by_tex("c_3") - ) - zeros = VGroup(*[ - TexMobject("0").move_to(c3) - for c3 in c3s - ]) - zeros.set_color(self.colors[3]) - zeros.shift(SMALL_BUFF*UP) - zeros[0].shift(0.25*SMALL_BUFF*(UP+LEFT)) - - self.play(Transform( - c3s, zeros, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait(2) - - def show_cubic_curves(self): - real_graph = self.quadratic_graph - real_graph.save_state() - graph = real_graph.copy() - graph.save_state() - alt_graphs = [ - self.get_graph(func, color = real_graph.get_color()) - for func in [ - lambda x : x*(x-1)*(x+1), - lambda x : 1 - 0.5*(x**2) + 0.2*(x**3) - ] - ] - - self.play(FadeIn(graph)) - real_graph.set_stroke(width = 0) - for alt_graph in alt_graphs: - self.play(Transform(graph, alt_graph, run_time = 2)) - self.wait() - self.play(graph.restore, run_time = 2) - real_graph.restore() - self.play(FadeOut(graph)) - - def add_quartic_term(self): - polynomial = self.polynomial - plus_quartic_term = TexMobject("+\\,", "c_4", "x^4") - plus_quartic_term.next_to(polynomial, RIGHT) - plus_quartic_term.set_color_by_tex("c_4", self.colors[4]) - - self.play(*list(map(FadeOut, [ - self.plus_cubic_term, - self.polynomial_third_derivative, - self.polynomial_third_derivative_brace, - ]))) - self.play(Write(plus_quartic_term)) - self.wait() - - self.plus_quartic_term = plus_quartic_term - - def show_fourth_derivative_of_cosine(self): - cosine_fourth_derivative = self.get_cosine_fourth_derivative() - - self.play(FocusOn(self.cosine_third_derivative)) - self.play(ReplacementTransform( - self.cosine_third_derivative.copy(), - cosine_fourth_derivative - )) - self.wait(3) - - def take_fourth_derivative_of_quartic(self): - quartic_term = VGroup(*self.plus_quartic_term.copy()[1:]) - fourth_deriv_lhs = TexMobject("{d^4 P \\over dx^4}(x)", "=") - fourth_deriv_lhs.next_to( - self.polynomial, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - alt_rhs = TexMobject("=", "24 \\cdot", "c_4") - alt_rhs.next_to( - fourth_deriv_lhs.get_part_by_tex("="), DOWN, - buff = LARGE_BUFF, - aligned_edge = LEFT - ) - alt_rhs.set_color_by_tex("c_4", self.colors[4]) - - self.play(Write(fourth_deriv_lhs)) - self.play( - quartic_term.next_to, fourth_deriv_lhs, RIGHT - ) - self.wait() - fourth_deriv_rhs = self.take_derivatives_of_monomial(quartic_term) - self.wait() - self.play(Write(alt_rhs)) - self.wait() - - self.fourth_deriv_lhs = fourth_deriv_lhs - self.fourth_deriv_rhs = fourth_deriv_rhs - self.fourth_deriv_alt_rhs = alt_rhs - - def solve_for_c4(self): - c4s = VGroup( - self.fourth_deriv_alt_rhs.get_part_by_tex("c_4"), - self.fourth_deriv_rhs[-1], - self.plus_quartic_term.get_part_by_tex("c_4") - ) - fraction = TexMobject("\\text{\\small $\\frac{1}{24}$}") - fraction.set_color(self.colors[4]) - fractions = VGroup(*[ - fraction.copy().move_to(c4, LEFT) - for c4 in c4s - ]) - fractions.shift(SMALL_BUFF*UP) - x_to_4 = self.plus_quartic_term.get_part_by_tex("x^4") - x_to_4.generate_target() - x_to_4.target.shift(MED_SMALL_BUFF*RIGHT) - - self.play( - Transform( - c4s, fractions, - run_time = 3, - lag_ratio = 0.5, - ), - MoveToTarget(x_to_4, run_time = 2) - ) - self.wait(3) - - def show_quartic_approximation(self): - real_graph = self.quadratic_graph - graph = real_graph.copy() - quartic_graph = self.get_graph( - lambda x : 1 - (x**2)/2.0 + (x**4)/24.0, - color = graph.get_color(), - ) - tex_mobs = VGroup(*[ - self.polynomial, - self.fourth_deriv_rhs, - self.fourth_deriv_alt_rhs, - self.cosine_label, - self.cosine_derivative, - self.cosine_second_derivative, - self.cosine_third_derivative[1], - ]) - for tex_mob in tex_mobs: - tex_mob.add_to_back(BackgroundRectangle(tex_mob)) - - - self.play(FadeIn(graph)) - real_graph.set_stroke(width = 0) - self.play( - Transform( - graph, quartic_graph, - run_time = 3, - ), - Animation(tex_mobs) - ) - self.wait(3) - - - #### - - def take_derivatives_of_monomial(self, term, *added_anims): - """ - Must be a group of pure TexMobjects, - last part must be of the form x^n - """ - n = int(term[-1].get_tex_string()[-1]) - curr_term = term - added_anims_iter = iter(added_anims) - for k in range(n, 0, -1): - exponent = curr_term[-1][-1] - exponent_copy = exponent.copy() - front_num = TexMobject("%d \\cdot"%k) - front_num.move_to(curr_term[0][0], LEFT) - - new_monomial = TexMobject("x^%d"%(k-1)) - new_monomial.replace(curr_term[-1]) - Transform(curr_term[-1], new_monomial).update(1) - curr_term.generate_target() - curr_term.target.shift( - (front_num.get_width()+SMALL_BUFF)*RIGHT - ) - curr_term[-1][-1].set_fill(opacity = 0) - - possibly_added_anims = [] - try: - possibly_added_anims.append(next(added_anims_iter)) - except: - pass - - self.play( - ApplyMethod( - exponent_copy.replace, front_num[0], - path_arc = np.pi, - ), - Write( - front_num[1], - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - MoveToTarget(curr_term), - *possibly_added_anims, - run_time = 2 - ) - self.remove(exponent_copy) - self.add(front_num) - curr_term = VGroup(front_num, *curr_term) - self.wait() - self.play(FadeOut(curr_term[-1])) - - return VGroup(*curr_term[:-1]) - - def get_cosine_third_derivative(self): - if not hasattr(self, "cosine_second_derivative"): - self.get_cosine_second_derivative() - third_deriv = TexMobject( - "{d^3(", "\\cos", ")", "\\over", "dx^3}", - "(", "0", ")", - ) - third_deriv.set_color_by_tex("cos", self.colors[0]) - third_deriv.set_color_by_tex("-\\cos", self.colors[3]) - third_deriv.scale(0.75) - third_deriv.add_background_rectangle() - third_deriv.next_to( - self.cosine_second_derivative, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - rhs = TexMobject("=", "\\sin(0)", "=", "0") - rhs.set_color_by_tex("sin", self.colors[3]) - rhs.scale(0.8) - rhs.next_to( - third_deriv, RIGHT, - align_using_submobjects = True - ) - rhs.add_background_rectangle() - rhs.background_rectangle.scale_in_place(1.2) - - self.cosine_third_derivative = VGroup(third_deriv, rhs) - return self.cosine_third_derivative - - def get_cosine_fourth_derivative(self): - if not hasattr(self, "cosine_third_derivative"): - self.get_cosine_third_derivative() - fourth_deriv = TexMobject( - "{d^4(", "\\cos", ")", "\\over", "dx^4}", - "(", "0", ")", - ) - fourth_deriv.set_color_by_tex("cos", self.colors[0]) - fourth_deriv.scale(0.75) - fourth_deriv.add_background_rectangle() - fourth_deriv.next_to( - self.cosine_third_derivative, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - rhs = TexMobject("=", "\\cos(0)", "=", "1") - rhs.set_color_by_tex("cos", self.colors[4]) - rhs.scale(0.8) - rhs.next_to( - fourth_deriv, RIGHT, - align_using_submobjects = True - ) - rhs.add_background_rectangle() - rhs.background_rectangle.scale_in_place(1.2) - - self.cosine_fourth_derivative = VGroup(fourth_deriv, rhs) - return self.cosine_fourth_derivative - -class NoticeAFewThings(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Notice a few things", - target_mode = "hesitant" - ) - self.wait(3) - -class FactorialTerms(CubicAndQuarticApproximations): - def construct(self): - lhs_list = [ - TexMobject( - "{d%s"%s, "\\over", "dx%s}"%s, "(", "c_8", "x^8", ")=" - ) - for i in range(9) - for s in ["^%d"%i if i > 1 else ""] - ] - for lhs in lhs_list: - lhs.set_color_by_tex("c_8", YELLOW) - lhs.next_to(ORIGIN, LEFT) - lhs_list[0].set_fill(opacity = 0) - added_anims = [ - ReplacementTransform( - start_lhs, target_lhs, - rate_func = squish_rate_func(smooth, 0, 0.5) - ) - for start_lhs, target_lhs in zip(lhs_list, lhs_list[1:]) - ] - - term = TexMobject("c_8", "x^8") - term.next_to(lhs[-1], RIGHT) - term.set_color_by_tex("c_8", YELLOW) - - self.add(term) - self.wait() - result = self.take_derivatives_of_monomial(term, *added_anims) - - factorial_term = VGroup(*result[:-1]) - brace = Brace(factorial_term) - eight_factorial = brace.get_text("$8!$") - - coefficient = result[-1] - words = TextMobject( - "Set", "$c_8$", - "$ = \\frac{\\text{Desired derivative value}}{8!}" - ) - words.set_color_by_tex("c_8", YELLOW) - words.shift(2*UP) - - self.play( - GrowFromCenter(brace), - Write(eight_factorial) - ) - self.play( - ReplacementTransform( - coefficient.copy(), - words.get_part_by_tex("c_8") - ), - Write(words), - ) - self.wait(2) - -class HigherTermsDontMessUpLowerTerms(Scene): - CONFIG = { - "colors" : CubicAndQuarticApproximations.CONFIG["colors"][::2], - - } - def construct(self): - self.add_polynomial() - self.show_second_derivative() - - def add_polynomial(self): - c0_tex = "1" - c2_tex = "\\text{\\small $\\left(-\\frac{1}{2}\\right)$}" - c4_tex = "c_4" - - polynomial = TexMobject( - "P(x) = ", - c0_tex, "+", - c2_tex, "x^2", "+", - c4_tex, "x^4", - ) - polynomial.shift(2*LEFT + UP) - c0, c2, c4 = [ - polynomial.get_part_by_tex(tex) - for tex in (c0_tex, c2_tex, c4_tex) - ] - for term, color in zip([c0, c2, c4], self.colors): - term.set_color(color) - arrows = VGroup(*[ - Arrow( - c4.get_top(), c.get_top(), - path_arc = arc, - color = c.get_color() - ) - for c, arc in [(c2, 0.9*np.pi), (c0, np.pi)] - ]) - no_affect_words = TextMobject( - "Doesn't affect \\\\ previous terms" - ) - no_affect_words.next_to(arrows, RIGHT) - no_affect_words.shift(MED_SMALL_BUFF*(UP+LEFT)) - - self.add(*polynomial[:-2]) - self.wait() - self.play(Write(VGroup(*polynomial[-2:]))) - self.play( - Write(no_affect_words), - ShowCreation(arrows), - run_time = 3 - ) - self.wait(2) - - self.polynomial = polynomial - self.c0_tex = c0_tex - self.c2_tex = c2_tex - self.c4_tex = c4_tex - - def show_second_derivative(self): - second_deriv = TexMobject( - "{d^2 P \\over dx^2}(", "0", ")", "=", - "2", self.c2_tex, "+", - "3 \\cdot 4", self.c4_tex, "(", "0", ")", "^2" - ) - second_deriv.set_color_by_tex(self.c2_tex, self.colors[1]) - second_deriv.set_color_by_tex(self.c4_tex, self.colors[2]) - second_deriv.set_color_by_tex("0", YELLOW) - second_deriv.next_to( - self.polynomial, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - higher_terms = VGroup(*second_deriv[-6:]) - brace = Brace(higher_terms, DOWN) - equals_zero = brace.get_text("=0") - - second_deriv.save_state() - second_deriv.move_to(self.polynomial, LEFT) - second_deriv.set_fill(opacity = 0) - - self.play(second_deriv.restore) - self.wait() - self.play(GrowFromCenter(brace)) - self.wait() - self.play(Write(equals_zero)) - self.wait(3) - -class EachTermControlsOneDerivative(Scene): - def construct(self): - colors = CubicAndQuarticApproximations.CONFIG["colors"] - polynomial = TexMobject( - "P(x) = ", "c_0", "+", "c_1", "x", *it.chain(*[ - ["+", "c_%d"%n, "x^%d"%n] - for n in range(2, 5) - ]) - ) - consts = polynomial.get_parts_by_tex("c") - deriv_words = VGroup(*[ - TextMobject("Controls \\\\ $%s(0)$"%tex) - for tex in [ - "P", - "\\frac{dP}{dx}", - ] + [ - "\\frac{d^%d P}{dx^%d}"%(n, n) - for n in range(2, 5) - ] - ]) - deriv_words.arrange( - RIGHT, - buff = LARGE_BUFF, - aligned_edge = UP - ) - deriv_words.set_width(FRAME_WIDTH - MED_LARGE_BUFF) - deriv_words.to_edge(UP) - - for const, deriv, color in zip(consts, deriv_words, colors): - for mob in const, deriv: - mob.set_color(color) - arrow = Arrow( - const.get_top(), - deriv.get_bottom(), - # buff = SMALL_BUFF, - color = color - ) - deriv.arrow = arrow - - self.add(polynomial) - for deriv in deriv_words: - self.play( - ShowCreation(deriv.arrow), - FadeIn(deriv) - ) - self.wait() - -class ApproximateNearNewPoint(CubicAndQuarticApproximations): - CONFIG = { - "target_approx_centers" : [np.pi/2, np.pi], - } - def construct(self): - self.setup_axes() - self.add_cosine_graph() - self.shift_approximation_center() - self.show_polynomials() - - def add_cosine_graph(self): - self.cosine_graph = self.get_graph( - np.cos, self.colors[0] - ) - self.add(self.cosine_graph) - - def shift_approximation_center(self): - quartic_graph = self.get_quartic_approximation(0) - dot = Dot(color = YELLOW) - dot.move_to(self.coords_to_point(0, 1)) - - v_line = self.get_vertical_line_to_graph( - self.target_approx_centers[-1], self.cosine_graph, - line_class = DashedLine, - color = YELLOW - ) - pi = self.x_axis_labels[1] - pi.add_background_rectangle() - - self.play( - ReplacementTransform( - self.cosine_graph.copy(), - quartic_graph, - ), - DrawBorderThenFill(dot, run_time = 1) - ) - for target, rt in zip(self.target_approx_centers, [3, 4, 4]): - self.change_approximation_center( - quartic_graph, dot, target, run_time = rt - ) - self.play( - ShowCreation(v_line), - Animation(pi) - ) - self.wait() - - def change_approximation_center(self, graph, dot, target, **kwargs): - start = self.x_axis.point_to_number(dot.get_center()) - def update_quartic(graph, alpha): - new_a = interpolate(start, target, alpha) - new_graph = self.get_quartic_approximation(new_a) - Transform(graph, new_graph).update(1) - return graph - - def update_dot(dot, alpha): - new_x = interpolate(start, target, alpha) - dot.move_to(self.input_to_graph_point(new_x, self.cosine_graph)) - - self.play( - UpdateFromAlphaFunc(graph, update_quartic), - UpdateFromAlphaFunc(dot, update_dot), - **kwargs - ) - - def show_polynomials(self): - poly_around_pi = self.get_polynomial("(x-\\pi)", "\\pi") - poly_around_pi.to_corner(UP+LEFT) - - randy = Randolph() - randy.to_corner(DOWN+LEFT) - - self.play(FadeIn( - poly_around_pi, - run_time = 4, - lag_ratio = 0.5 - )) - self.wait(2) - self.play(FadeIn(randy)) - self.play(randy.change, "confused", poly_around_pi) - self.play(Blink(randy)) - self.wait(2) - self.play(randy.change_mode, "happy") - self.wait(2) - - ### - - def get_polynomial(self, arg, center_tex): - result = TexMobject( - "P_{%s}(x)"%center_tex, "=", "c_0", *it.chain(*[ - ["+", "c_%d"%d, "%s^%d"%(arg, d)] - for d in range(1, 5) - ]) - ) - for d, color in enumerate(self.colors): - result.set_color_by_tex("c_%d"%d, color) - result.scale(0.85) - result.add_background_rectangle() - return result - - def get_quartic_approximation(self, a): - coefficients = [ - derivative(np.cos, a, n) - for n in range(5) - ] - func = lambda x : sum([ - (c/math.factorial(n))*(x - a)**n - for n, c in enumerate(coefficients) - ]) - return self.get_graph(func, color = GREEN) - -class OnAPhilosophicalLevel(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "And on a \\\\ philosophical level", - run_time = 1 - ) - self.wait(3) - -class TranslationOfInformation(CubicAndQuarticApproximations): - def construct(self): - self.add_background() - self.mention_information_exchange() - self.show_derivative_pattern() - self.show_polynomial() - self.name_taylor_polynomial() - self.draw_new_function_graph() - self.write_general_function_derivative() - self.replace_coefficients_in_generality() - self.walk_through_terms() - self.show_polynomial_around_a() - - def add_background(self): - self.setup_axes() - self.cosine_graph = self.get_graph( - np.cos, color = self.colors[0] - ) - self.add(self.cosine_graph) - - def mention_information_exchange(self): - deriv_info = TextMobject( - "Derivative \\\\ information \\\\ at a point" - ) - deriv_info.next_to(ORIGIN, LEFT, LARGE_BUFF) - deriv_info.to_edge(UP) - output_info = TextMobject( - "Output \\\\ information \\\\ near that point" - ) - output_info.next_to(ORIGIN, RIGHT, LARGE_BUFF) - output_info.to_edge(UP) - arrow = Arrow(deriv_info, output_info) - - center_v_line = self.get_vertical_line_to_graph( - 0, self.cosine_graph, - line_class = DashedLine, - color = YELLOW - ) - outer_v_lines = VGroup(*[ - center_v_line.copy().shift(vect) - for vect in (LEFT, RIGHT) - ]) - outer_v_lines.set_color(GREEN) - dot = Dot(color = YELLOW) - dot.move_to(center_v_line.get_top()) - dot.save_state() - dot.move_to(deriv_info) - dot.set_fill(opacity = 0) - - quadratic_graph = self.get_quadratic_graph() - - - self.play(Write(deriv_info, run_time = 2)) - self.play(dot.restore) - self.play(ShowCreation(center_v_line)) - self.wait() - self.play(ShowCreation(arrow)) - self.play(Write(output_info, run_time = 2)) - - self.play(ReplacementTransform( - VGroup(center_v_line).copy(), - outer_v_lines - )) - self.play(ReplacementTransform( - self.cosine_graph.copy(), - quadratic_graph - ), Animation(dot)) - for x in -1, 1, 0: - start_x = self.x_axis.point_to_number(dot.get_center()) - self.play(UpdateFromAlphaFunc( - dot, - lambda d, a : d.move_to(self.input_to_graph_point( - interpolate(start_x, x, a), - self.cosine_graph - )), - run_time = 2 - )) - self.wait() - self.play(*list(map(FadeOut, [ - deriv_info, arrow, output_info, outer_v_lines - ]))) - - self.quadratic_graph = quadratic_graph - self.v_line = center_v_line - self.dot = dot - - def show_derivative_pattern(self): - derivs_at_x, derivs_at_zero = [ - VGroup(*[ - TexMobject(tex, "(", arg, ")") - for tex in [ - "\\cos", "-\\sin", - "-\\cos", "\\sin", "\\cos" - ] - ]) - for arg in ("x", "0") - ] - arrows = VGroup(*[ - Arrow( - UP, ORIGIN, - color = WHITE, - buff = 0, - tip_length = MED_SMALL_BUFF - ) - for d in derivs_at_x - ]) - group = VGroup(*it.chain(*list(zip( - derivs_at_x, - arrows - )))) - group.add(TexMobject("\\vdots")) - group.arrange(DOWN, buff = SMALL_BUFF) - group.set_height(FRAME_HEIGHT - MED_LARGE_BUFF) - group.to_edge(LEFT) - for dx, d0, color in zip(derivs_at_x, derivs_at_zero, self.colors): - for d in dx, d0: - d.set_color(color) - d0.replace(dx) - rhs_group = VGroup(*[ - TexMobject("=", "%d"%d).scale(0.7).next_to(deriv, RIGHT) - for deriv, d in zip(derivs_at_zero, [1, 0, -1, 0, 1]) - ]) - derivative_values = VGroup(*[ - rhs[1] for rhs in rhs_group - ]) - for value, color in zip(derivative_values, self.colors): - value.set_color(color) - zeros = VGroup(*[ - deriv.get_part_by_tex("0") - for deriv in derivs_at_zero - ]) - - self.play(FadeIn(derivs_at_x[0])) - self.wait() - for start_d, arrow, target_d in zip(group[::2], group[1::2], group[2::2]): - self.play( - ReplacementTransform( - start_d.copy(), target_d - ), - ShowCreation(arrow) - ) - self.wait() - self.wait() - self.play(ReplacementTransform( - derivs_at_x, derivs_at_zero - )) - self.wait() - self.play(*list(map(Write, rhs_group))) - self.wait() - for rhs in rhs_group: - self.play(Indicate(rhs[1]), color = WHITE) - self.wait() - self.play(*[ - ReplacementTransform( - zero.copy(), self.dot, - run_time = 3, - rate_func = squish_rate_func(smooth, a, a+0.4) - ) - for zero, a in zip(zeros, np.linspace(0, 0.6, len(zeros))) - ]) - self.wait() - - self.cosine_derivative_group = VGroup( - derivs_at_zero, arrows, group[-1], rhs_group - ) - self.derivative_values = derivative_values - - def show_polynomial(self): - derivative_values = self.derivative_values.copy() - polynomial = self.get_polynomial("x", 1, 0, -1, 0, 1) - polynomial.to_corner(UP+RIGHT) - - monomial = TexMobject("\\frac{1}{4!}", "x^4") - monomial = VGroup(VGroup(monomial[0]), monomial[1]) - monomial.next_to(polynomial, DOWN, LARGE_BUFF) - - self.play(*[ - Transform( - dv, pc, - run_time = 2, - path_arc = np.pi/2 - ) - for dv, pc, a in zip( - derivative_values, - polynomial.coefficients, - np.linspace(0, 0.6, len(derivative_values)) - ) - ]) - self.play( - Write(polynomial, run_time = 5), - Animation(derivative_values) - ) - self.remove(derivative_values) - self.wait(2) - to_fade = self.take_derivatives_of_monomial(monomial) - self.play(FadeOut(to_fade)) - self.wait() - - self.polynomial = polynomial - - def name_taylor_polynomial(self): - brace = Brace( - VGroup( - self.polynomial.coefficients, - self.polynomial.factorials - ), - DOWN - ) - name = brace.get_text("``Taylor polynomial''") - name.shift(MED_SMALL_BUFF*RIGHT) - quartic_graph = self.get_graph( - lambda x : 1 - (x**2)/2.0 + (x**4)/24.0, - color = GREEN, - x_min = -3.2, - x_max = 3.2, - ) - quartic_graph.set_color(self.colors[4]) - - self.play(GrowFromCenter(brace)) - self.play(Write(name)) - self.wait() - self.play( - Transform( - self.quadratic_graph, quartic_graph, - run_time = 2 - ), - Animation(self.dot) - ) - self.wait(2) - - self.taylor_name_group = VGroup(brace, name) - - def draw_new_function_graph(self): - def func(x): - return (np.sin(x**2 + x)+0.5)*np.exp(-x**2) - graph = self.get_graph( - func, color = self.colors[0] - ) - - self.play(*list(map(FadeOut, [ - self.cosine_derivative_group, - self.cosine_graph, - self.quadratic_graph, - self.v_line, - self.dot - ]))) - self.play(ShowCreation(graph)) - - self.graph = graph - - def write_general_function_derivative(self): - derivs_at_x, derivs_at_zero, derivs_at_a = deriv_lists = [ - VGroup(*[ - TexMobject("\\text{$%s$}"%args[0], *args[1:]) - for args in [ - ("f", "(", arg, ")"), - ("\\frac{df}{dx}", "(", arg, ")"), - ("\\frac{d^2 f}{dx^2}", "(", arg, ")"), - ("\\frac{d^3 f}{dx^3}", "(", arg, ")"), - ("\\frac{d^4 f}{dx^4}", "(", arg, ")"), - ] - ]) - for arg in ("x", "0", "a") - ] - derivs_at_x.arrange(DOWN, buff = MED_LARGE_BUFF) - derivs_at_x.set_height(FRAME_HEIGHT - MED_LARGE_BUFF) - derivs_at_x.to_edge(LEFT) - zeros = VGroup(*[ - deriv.get_part_by_tex("0") - for deriv in derivs_at_zero - ]) - self.dot.move_to(self.input_to_graph_point(0, self.graph)) - self.v_line.put_start_and_end_on( - self.graph_origin, self.dot.get_center() - ) - - for color, dx, d0, da in zip(self.colors, *deriv_lists): - for d in dx, d0, da: - d.set_color(color) - d.add_background_rectangle() - d0.replace(dx) - da.replace(dx) - - self.play(FadeIn(derivs_at_x[0])) - self.wait() - for start, target in zip(derivs_at_x, derivs_at_x[1:]): - self.play(ReplacementTransform( - start.copy(), target - )) - self.wait() - self.wait() - self.play(ReplacementTransform( - derivs_at_x, derivs_at_zero, - )) - self.play(ReplacementTransform( - zeros.copy(), self.dot, - run_time = 2, - lag_ratio = 0.5 - )) - self.play(ShowCreation(self.v_line)) - self.wait() - - self.derivs_at_zero = derivs_at_zero - self.derivs_at_a = derivs_at_a - - def replace_coefficients_in_generality(self): - new_polynomial = self.get_polynomial("x", *[ - tex_mob.get_tex_string() - for tex_mob in self.derivs_at_zero[:-1] - ]) - new_polynomial.to_corner(UP+RIGHT) - polynomial_fourth_term = VGroup( - *self.polynomial[-7:-1] - ) - self.polynomial.remove(*polynomial_fourth_term) - - self.play( - ReplacementTransform( - self.polynomial, new_polynomial, - run_time = 2, - lag_ratio = 0.5 - ), - FadeOut(polynomial_fourth_term), - FadeOut(self.taylor_name_group), - ) - self.polynomial = new_polynomial - self.wait(3) - - def walk_through_terms(self): - func = self.graph.underlying_function - approx_graphs = [ - self.get_graph( - taylor_approximation(func, n), - color = WHITE - ) - for n in range(7) - ] - for graph, color in zip(approx_graphs, self.colors): - graph.set_color(color) - - left_mob = self.polynomial.coefficients[0] - right_mobs = list(self.polynomial.factorials) - right_mobs.append(self.polynomial[-1]) - braces = [ - Brace( - VGroup(left_mob, *right_mobs[:n]), - DOWN - ) - for n in range(len(approx_graphs)) - ] - brace = braces[0] - brace.stretch_to_fit_width(MED_LARGE_BUFF) - approx_graph = approx_graphs[0] - - self.polynomial.add_background_rectangle() - - self.play(GrowFromCenter(brace)) - self.play(ShowCreation(approx_graph)) - self.wait() - for new_brace, new_graph in zip(braces[1:], approx_graphs[1:]): - self.play(Transform(brace, new_brace)) - self.play( - Transform(approx_graph, new_graph, run_time = 2), - Animation(self.polynomial), - Animation(self.dot), - ) - self.wait() - self.play(FadeOut(brace)) - - self.approx_graph = approx_graph - self.approx_order = len(approx_graphs) - 1 - - def show_polynomial_around_a(self): - new_polynomial = self.get_polynomial("(x-a)", *[ - tex_mob.get_tex_string() - for tex_mob in self.derivs_at_a[:-2] - ]) - new_polynomial.to_corner(UP+RIGHT) - new_polynomial.add_background_rectangle() - - polynomial_third_term = VGroup( - *self.polynomial[1][-7:-1] - ) - self.polynomial[1].remove(*polynomial_third_term) - - group = VGroup(self.approx_graph, self.dot, self.v_line) - def get_update_function(target_x): - def update(group, alpha): - graph, dot, line = group - start_x = self.x_axis.point_to_number(dot.get_center()) - x = interpolate(start_x, target_x, alpha) - graph_point = self.input_to_graph_point(x, self.graph) - dot.move_to(graph_point) - line.put_start_and_end_on( - self.coords_to_point(x, 0), - graph_point, - ) - new_approx_graph = self.get_graph( - taylor_approximation( - self.graph.underlying_function, - self.approx_order, - center_point = x - ), - color = graph.get_color() - ) - Transform(graph, new_approx_graph).update(1) - return VGroup(graph, dot, line) - return update - - self.play( - UpdateFromAlphaFunc( - group, get_update_function(1), run_time = 2 - ), - Animation(self.polynomial), - Animation(polynomial_third_term) - ) - self.wait() - self.play(Transform( - self.derivs_at_zero, - self.derivs_at_a - )) - self.play( - Transform(self.polynomial, new_polynomial), - FadeOut(polynomial_third_term) - ) - self.wait() - for x in -1, np.pi/6: - self.play( - UpdateFromAlphaFunc( - group, get_update_function(x), - ), - Animation(self.polynomial), - run_time = 4, - ) - self.wait() - - - ##### - - def get_polynomial(self, arg, *coefficients): - result = TexMobject( - "P(x) = ", str(coefficients[0]), *list(it.chain(*[ - ["+", str(c), "{%s"%arg, "^%d"%n, "\\over", "%d!}"%n] - for n, c in zip(it.count(1), coefficients[1:]) - ])) + [ - "+ \\cdots" - ] - ) - result.scale(0.8) - coefs = VGroup(result[1], *result[3:-1:6]) - for coef, color in zip(coefs, self.colors): - coef.set_color(color) - result.coefficients = coefs - result.factorials = VGroup(*result[7::6]) - - return result - -class ThisIsAStandardFormula(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "You will see this \\\\ in your texts", - run_time = 1 - ) - self.change_student_modes( - *["sad"]*3, - look_at_arg = FRAME_Y_RADIUS*UP - ) - self.wait(2) - -class ExpPolynomial(TranslationOfInformation, ExampleApproximationWithExp): - CONFIG = { - "x_tick_frequency" : 1, - "x_leftmost_tick" : -3, - "graph_origin" : 2*(DOWN+LEFT), - "y_axis_label" : "", - } - def construct(self): - self.setup_axes() - self.add_graph() - self.show_derivatives() - self.show_polynomial() - self.walk_through_terms() - - def add_graph(self): - graph = self.get_graph(np.exp) - e_to_x = self.get_graph_label(graph, "e^x") - - self.play( - ShowCreation(graph), - Write( - e_to_x, - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - run_time = 2 - ) - self.wait() - - self.graph = graph - self.e_to_x = e_to_x - - def show_derivatives(self): - self.e_to_x.generate_target() - derivs_at_x, derivs_at_zero = [ - VGroup(*[ - TexMobject("e^%s"%s).set_color(c) - for c in self.colors - ]) - for s in ("x", "0") - ] - derivs_at_x.submobjects[0] = self.e_to_x.target - arrows = VGroup(*[ - Arrow( - UP, ORIGIN, - color = WHITE, - buff = SMALL_BUFF, - tip_length = 0.2, - ) - for d in derivs_at_x - ]) - group = VGroup(*it.chain(*list(zip( - derivs_at_x, - arrows - )))) - group.add(TexMobject("\\vdots")) - group.arrange(DOWN, buff = 2*SMALL_BUFF) - group.set_height(FRAME_HEIGHT - MED_LARGE_BUFF) - group.to_edge(LEFT) - for dx, d0 in zip(derivs_at_x, derivs_at_zero): - for d in dx, d0: - d.add_background_rectangle() - d0.replace(dx) - rhs_group = VGroup(*[ - TexMobject("=", "1").scale(0.7).next_to(deriv, RIGHT) - for deriv in derivs_at_zero - ]) - derivative_values = VGroup(*[ - rhs[1] for rhs in rhs_group - ]) - for value, color in zip(derivative_values, self.colors): - value.set_color(color) - - for arrow in arrows: - d_dx = TexMobject("d/dx") - d_dx.scale(0.5) - d_dx.next_to(arrow, RIGHT, SMALL_BUFF) - d_dx.shift(SMALL_BUFF*UP) - arrow.add(d_dx) - - self.play(MoveToTarget(self.e_to_x)) - derivs_at_x.submobjects[0] = self.e_to_x - for start_d, arrow, target_d in zip(group[::2], group[1::2], group[2::2]): - self.play( - ReplacementTransform( - start_d.copy(), target_d - ), - Write(arrow, run_time = 1) - ) - self.wait() - self.wait() - self.play(ReplacementTransform( - derivs_at_x, derivs_at_zero - )) - self.wait() - self.play(*list(map(Write, rhs_group))) - - self.derivative_values = derivative_values - - def show_polynomial(self): - derivative_values = self.derivative_values.copy() - polynomial = self.get_polynomial("x", 1, 1, 1, 1, 1) - polynomial.to_corner(UP+RIGHT) - - ##These are to make the walk_through_terms method work - self.polynomial = polynomial.copy() - self.dot = Dot(fill_opacity = 0) - ### - polynomial.add_background_rectangle() - - self.play(*[ - Transform( - dv, pc, - run_time = 2, - path_arc = np.pi/2 - ) - for dv, pc in zip( - derivative_values, - polynomial.coefficients, - ) - ]) - self.play( - Write(polynomial, run_time = 4), - Animation(derivative_values) - ) - - #### - - def setup_axes(self): - GraphScene.setup_axes(self) - -class ShowSecondTerm(TeacherStudentsScene): - def construct(self): - colors = CubicAndQuarticApproximations.CONFIG["colors"] - polynomial = TexMobject( - "f(a)", "+", - "\\frac{df}{dx}(a)", "(x - a)", "+", - "\\frac{d^2 f}{dx^2}(a)", "(x - a)^2" - ) - for tex, color in zip(["f(a)", "df", "d^2 f"], colors): - polynomial.set_color_by_tex(tex, color) - polynomial.next_to(self.teacher, UP+LEFT) - polynomial.shift(MED_LARGE_BUFF*UP) - second_term = VGroup(*polynomial[-2:]) - box = Rectangle(color = colors[2]) - box.replace(second_term, stretch = True) - box.stretch_in_place(1.1, 0) - box.stretch_in_place(1.2, 1) - words = TextMobject("Geometric view") - words.next_to(box, UP) - - self.teacher_says( - "Now for \\\\ something fun!", - target_mode = "hooray" - ) - self.wait(2) - self.play( - RemovePiCreatureBubble( - self.teacher, - target_mode = "raise_right_hand" - ), - Write(polynomial) - ) - self.play( - ShowCreation(box), - FadeIn(words), - ) - self.change_student_modes(*["pondering"]*3) - self.wait(3) - -class SecondTermIntuition(AreaIsDerivative): - CONFIG = { - "func" : lambda x : x*(x-1)*(x-2) + 2, - "num_rects" : 300, - "t_max" : 2.3, - "x_max" : 4, - "x_labeled_nums" : None, - "x_axis_label" : "", - "y_labeled_nums" : None, - "y_axis_label" : "", - "y_min" : -1, - "y_max" : 5, - "y_tick_frequency" : 1, - "variable_point_label" : "x", - "area_opacity" : 1, - "default_riemann_start_color" : BLUE_E, - "dx" : 0.15, - "skip_reconfiguration" : False, - } - def setup(self): - GraphScene.setup(self) - ReconfigurableScene.setup(self) - self.foreground_mobjects = [] - - def construct(self): - self.setup_axes() - self.introduce_area() - self.write_derivative() - self.nudge_end_point() - self.point_out_triangle() - self.relabel_start_and_end() - self.compute_triangle_area() - self.walk_through_taylor_terms() - - def introduce_area(self): - graph = self.v_graph = self.get_graph( - self.func, color = WHITE, - ) - self.foreground_mobjects.append(graph) - area = self.area = self.get_area(0, self.t_max) - - func_name = TexMobject("f_{\\text{area}}(x)") - func_name.move_to(self.coords_to_point(0.6, 1)) - self.foreground_mobjects.append(func_name) - - self.add(graph, area, func_name) - self.add_T_label(self.t_max) - - if not self.skip_animations: - for target in 1.6, 2.7, self.t_max: - self.change_area_bounds( - new_t_max = target, - run_time = 3, - ) - self.__name__ = func_name - - def write_derivative(self): - deriv = TexMobject("\\frac{df_{\\text{area}}}{dx}(x)") - deriv.next_to( - self.input_to_graph_point(2.7, self.v_graph), - RIGHT - ) - deriv.shift_onto_screen() - - self.play(ApplyWave(self.v_graph, direction = UP)) - self.play(Write(deriv, run_time = 2)) - self.wait() - - self.deriv_label = deriv - - def nudge_end_point(self): - dark_area = self.area.copy() - dark_area.set_fill(BLACK, opacity = 0.5) - curr_x = self.x_axis.point_to_number(self.area.get_right()) - new_x = curr_x + self.dx - - rect = Rectangle( - stroke_width = 0, - fill_color = YELLOW, - fill_opacity = 0.75 - ) - rect.replace( - VGroup( - VectorizedPoint(self.coords_to_point(new_x, 0)), - self.right_v_line, - ), - stretch = True - ) - - dx_brace = Brace(rect, DOWN, buff = 0) - dx_label = dx_brace.get_text("$dx$", buff = SMALL_BUFF) - dx_label_group = VGroup(dx_label, dx_brace) - - height_brace = Brace(rect, LEFT, buff = SMALL_BUFF) - - self.change_area_bounds(new_t_max = new_x) - self.play( - FadeIn(dark_area), - *list(map(Animation, self.foreground_mobjects)) - ) - self.play( - FadeOut(self.T_label_group), - FadeIn(dx_label_group), - FadeIn(rect), - FadeIn(height_brace) - ) - self.wait() - if not self.skip_reconfiguration: - self.transition_to_alt_config( - dx = self.dx/10.0, - run_time = 3, - ) - self.play(FadeOut(height_brace)) - - self.dx_label_group = dx_label_group - self.rect = rect - self.dark_area = dark_area - - def point_out_triangle(self): - triangle = Polygon(LEFT, ORIGIN, UP) - triangle.set_stroke(width = 0) - triangle.set_fill(MAROON_B, opacity = 1) - triangle.replace( - Line( - self.rect.get_corner(UP+LEFT), - self.right_v_line.get_top() - ), - stretch = True - ) - circle = Circle(color = RED) - circle.set_height(triangle.get_height()) - circle.replace(triangle, dim_to_match = 1) - circle.scale_in_place(1.3) - - self.play(DrawBorderThenFill(triangle)) - self.play(ShowCreation(circle)) - self.play(FadeOut(circle)) - - self.triangle = triangle - - def relabel_start_and_end(self): - dx_label, dx_brace = self.dx_label_group - x_minus_a = TexMobject("(x-a)") - x_minus_a.scale(0.7) - x_minus_a.move_to(dx_label) - labels = VGroup() - arrows = VGroup() - for vect, tex in (LEFT, "a"), (RIGHT, "x"): - point = self.rect.get_corner(DOWN+vect) - label = TexMobject(tex) - label.next_to(point, DOWN+vect) - label.shift(LARGE_BUFF*vect) - arrow = Arrow( - label.get_corner(UP-vect), - point, - buff = SMALL_BUFF, - tip_length = 0.2, - color = WHITE - ) - labels.add(label) - arrows.add(arrow) - - - for label, arrow in zip(labels, arrows): - self.play( - Write(label), - ShowCreation(arrow) - ) - self.wait() - self.wait() - self.play(ReplacementTransform( - dx_label, x_minus_a - )) - self.wait() - - self.x_minus_a = x_minus_a - - def compute_triangle_area(self): - triangle = self.triangle.copy() - tex_scale_factor = 0.7 - base_line = Line(*[ - triangle.get_corner(DOWN+vect) - for vect in (LEFT, RIGHT) - ]) - base_line.set_color(RED) - base_label = TextMobject("Base = ", "$(x-a)$") - base_label.scale(tex_scale_factor) - base_label.next_to(base_line, DOWN+RIGHT, MED_LARGE_BUFF) - base_label.shift(SMALL_BUFF*UP) - base_term = base_label[1].copy() - base_arrow = Arrow( - base_label.get_left(), - base_line.get_center(), - buff = SMALL_BUFF, - color = base_line.get_color(), - tip_length = 0.2 - ) - - height_brace = Brace(triangle, RIGHT, buff = SMALL_BUFF) - height_labels = [ - TexMobject("\\text{Height} = ", s, "(x-a)") - for s in [ - "(\\text{Slope})", - "\\frac{d^2 f_{\\text{area}}}{dx^2}(a)" - ] - ] - for label in height_labels: - label.scale(tex_scale_factor) - label.next_to(height_brace, RIGHT) - height_term = VGroup(*height_labels[1][1:]).copy() - - self.play( - FadeIn(base_label), - ShowCreation(base_arrow), - ShowCreation(base_line) - ) - self.wait(2) - self.play( - GrowFromCenter(height_brace), - Write(height_labels[0]) - ) - self.wait(2) - self.play(ReplacementTransform(*height_labels)) - self.wait(2) - - #Show area formula - equals_half = TexMobject("=\\frac{1}{2}") - equals_half.scale(tex_scale_factor) - group = VGroup(triangle, equals_half, height_term, base_term) - group.generate_target() - group.target.arrange(RIGHT, buff = SMALL_BUFF) - group.target[-1].next_to( - group.target[-2], RIGHT, - buff = SMALL_BUFF, - align_using_submobjects = True - ) - group.target[1].shift(0.02*DOWN) - group.target.to_corner(UP+RIGHT) - exp_2 = TexMobject("2").scale(0.8*tex_scale_factor) - exp_2.next_to( - group.target[-2], UP+RIGHT, - buff = 0, - align_using_submobjects = True - ) - equals_half.scale(0.1) - equals_half.move_to(triangle) - equals_half.set_fill(opacity = 0) - - self.play( - FadeOut(self.deriv_label), - MoveToTarget(group, run_time = 2) - ) - self.wait(2) - self.play(Transform( - group[-1], exp_2 - )) - self.wait(2) - - def walk_through_taylor_terms(self): - mini_area, mini_rect, mini_triangle = [ - mob.copy() - for mob in (self.dark_area, self.rect, self.triangle) - ] - mini_area.set_fill(BLUE_E, opacity = 1) - mini_area.set_height(1) - mini_rect.set_height(1) - mini_triangle.set_height(0.5) - - geometric_taylor = VGroup( - TexMobject("f(x) \\approx "), mini_area, - TexMobject("+"), mini_rect, - TexMobject("+"), mini_triangle, - ) - geometric_taylor.arrange( - RIGHT, buff = MED_SMALL_BUFF - ) - geometric_taylor.to_corner(UP+LEFT) - - analytic_taylor = TexMobject( - "f(x) \\approx", "f(a)", "+", - "\\frac{df}{dx}(a)(x-a)", "+", - "\\frac{1}{2}\\frac{d^2 f}{dx^2}(a)(x-a)^2" - ) - analytic_taylor.set_color_by_tex("f(a)", BLUE) - analytic_taylor.set_color_by_tex("df", YELLOW) - analytic_taylor.set_color_by_tex("d^2 f", MAROON_B) - analytic_taylor.scale(0.7) - analytic_taylor.next_to( - geometric_taylor, DOWN, - aligned_edge = LEFT - ) - for part in analytic_taylor: - part.add_to_back(BackgroundRectangle(part)) - - new_func_name = TexMobject("f_{\\text{area}}(a)") - new_func_name.replace(self.__name__) - - self.play(FadeIn( - geometric_taylor, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait() - self.play( - FadeIn(VGroup(*analytic_taylor[:3])), - self.dark_area.set_fill, BLUE_E, 1, - Transform(self.__name__, new_func_name) - ) - self.wait() - self.play( - self.rect.scale_in_place, 0.5, - rate_func = there_and_back - ) - self.play(FadeIn(VGroup(*analytic_taylor[3:5]))) - self.wait(2) - self.play( - self.triangle.scale_in_place, 0.5, - rate_func = there_and_back - ) - self.play(FadeIn(VGroup(*analytic_taylor[5:]))) - self.wait(3) - -class EachTermHasMeaning(TeacherStudentsScene): - def construct(self): - self.get_pi_creatures().scale_in_place(0.8).shift(UP) - self.teacher_says( - "Each term \\\\ has meaning!", - target_mode = "hooray", - bubble_kwargs = {"height" : 3, "width" : 4} - ) - self.change_student_modes( - *["thinking"]*3, - look_at_arg = 4*UP - ) - self.wait(3) - -class AskAboutInfiniteSum(TeacherStudentsScene): - def construct(self): - self.ask_question() - self.name_taylor_series() - self.be_careful() - - - def ask_question(self): - big_rect = Rectangle( - width = FRAME_WIDTH, - height = FRAME_HEIGHT, - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 0.7, - ) - randy = self.get_students()[1] - series = TexMobject( - "\\cos(x)", "\\approx", - "1 - \\frac{x^2}{2!} + \\frac{x^4}{4!}", - " - \\frac{x^6}{6!}", - "+\\cdots" - ) - series.next_to(randy, UP, 2) - series.shift_onto_screen() - rhs = VGroup(*series[2:]) - arrow = Arrow(rhs.get_left(), rhs.get_right()) - arrow.next_to(rhs, UP) - words = TextMobject("Add infinitely many") - words.next_to(arrow, UP) - - self.teacher_says( - "We could call \\\\ it an end here" - ) - self.change_student_modes(*["erm"]*3) - self.wait(3) - self.play( - RemovePiCreatureBubble(self.teacher), - self.get_students()[0].change_mode, "plain", - self.get_students()[2].change_mode, "plain", - FadeIn(big_rect), - randy.change_mode, "pondering" - ) - crowd = VGroup(*self.get_pi_creatures()) - crowd.remove(randy) - self.crowd_copy = crowd.copy() - self.remove(crowd) - self.add(self.crowd_copy, big_rect, randy) - - self.play(Write(series)) - self.play( - ShowCreation(arrow), - Write(words) - ) - self.wait(3) - - self.arrow = arrow - self.words = words - self.series = series - self.randy = randy - - def name_taylor_series(self): - series_def = TextMobject( - "Infinite sum $\\Leftrightarrow$ series" - ) - taylor_series = TextMobject("Taylor series") - for mob in series_def, taylor_series: - mob.move_to(self.words) - brace = Brace(self.series.get_part_by_tex("4!"), DOWN) - taylor_polynomial = brace.get_text("Taylor polynomial") - - self.play(ReplacementTransform( - self.words, series_def - )) - self.wait() - self.play( - GrowFromCenter(brace), - Write(taylor_polynomial) - ) - self.wait(2) - self.play( - series_def.scale, 0.7, - series_def.to_corner, UP+RIGHT, - FadeIn(taylor_series) - ) - self.play(self.randy.change, "thinking", taylor_series) - self.wait() - - def be_careful(self): - self.play(FadeIn(self.teacher)) - self.remove(self.crowd_copy[0]) - self.teacher_says( - "Be careful", - bubble_kwargs = { - "width" : 3, - "height" : 2 - }, - added_anims = [self.randy.change, "hesitant"] - ) - self.wait(2) - self.play(self.randy.change, "confused", self.series) - self.wait(3) - -class ConvergenceExample(Scene): - def construct(self): - max_shown_power = 6 - max_computed_power = 13 - series = TexMobject(*list(it.chain(*[ - ["\\frac{1}{%d}"%(3**n), "+"] - for n in range(1, max_shown_power) - ])) + ["\\cdots"]) - series_nums = [3**(-n) for n in range(1, max_computed_power)] - partial_sums = np.cumsum(series_nums) - braces = self.get_partial_sum_braces(series, partial_sums) - - convergence_words = TextMobject("``Converges'' to $\\frac{1}{2}$") - convergence_words.next_to(series, UP, LARGE_BUFF) - convergence_words.set_color(YELLOW) - rhs = TexMobject("= \\frac{1}{2}") - rhs.next_to(series, RIGHT) - rhs.set_color(BLUE) - - brace = braces[0] - self.add(series, brace) - for i, new_brace in enumerate(braces[1:]): - self.play(Transform(brace, new_brace)) - if i == 4: - self.play(FadeIn(convergence_words)) - else: - self.wait() - self.play( - FadeOut(brace), - Write(rhs) - ) - self.wait(2) - - def get_partial_sum_braces(self, series, partial_sums): - braces = [ - Brace(VGroup(*series[:n])) - for n in range(1, len(series)-1, 2) - ] - last_brace = braces[-1] - braces += [ - braces[-1].copy().stretch_in_place( - 1 + 0.1 + 0.02*(n+1), dim = 0, - ).move_to(last_brace, LEFT) - for n in range(len(partial_sums) - len(braces)) - ] - for brace, partial_sum in zip(braces, partial_sums): - number = brace.get_text("%.7f"%partial_sum) - number.set_color(YELLOW) - brace.add(number) - return braces - -class ExpConvergenceExample(ConvergenceExample): - def construct(self): - e_to_x, series_with_x = x_group = self.get_series("x") - x_group.to_edge(UP) - e_to_1, series_with_1 = one_group = self.get_series("1") - terms = [1./math.factorial(n) for n in range(11)] - partial_sums = np.cumsum(terms) - braces = self.get_partial_sum_braces(series_with_1, partial_sums) - brace = braces[1] - - for lhs, s in (e_to_x, "x"), (e_to_1, "1"): - new_lhs = TexMobject("e^%s"%s, "=") - new_lhs.move_to(lhs, RIGHT) - lhs.target = new_lhs - - self.add(x_group) - self.wait() - self.play(ReplacementTransform(x_group.copy(), one_group)) - self.play(FadeIn(brace)) - self.wait() - for new_brace in braces[2:]: - self.play(Transform(brace, new_brace)) - self.wait() - self.wait() - self.play(MoveToTarget(e_to_1)) - self.wait(2) - - def get_series(self, arg, n_terms = 5): - series = TexMobject("1", "+", *list(it.chain(*[ - ["\\frac{%s^%d}{%d!}"%(arg, n, n), "+"] - for n in range(1, n_terms+1) - ])) + ["\\cdots"]) - colors = list(CubicAndQuarticApproximations.CONFIG["colors"]) - colors += [BLUE_C] - for term, color in zip(series[::2], colors): - term.set_color(color) - - lhs = TexMobject("e^%s"%arg, "\\rightarrow") - lhs.arrange(RIGHT, buff = SMALL_BUFF) - group = VGroup(lhs, series) - group.arrange(RIGHT, buff = SMALL_BUFF) - - return group - -class ExpGraphConvergence(ExpPolynomial, ExpConvergenceExample): - CONFIG = { - "example_input" : 2, - "graph_origin" : 3*DOWN + LEFT, - "n_iterations" : 8, - "y_max" : 20, - "num_graph_anchor_points" : 50, - "func" : np.exp, - } - def construct(self): - self.setup_axes() - self.add_series() - approx_graphs = self.get_approx_graphs() - - graph = self.get_graph(self.func, color = self.colors[0]) - v_line = self.get_vertical_line_to_graph( - self.example_input, graph, - line_class = DashedLine, - color = YELLOW - ) - dot = Dot(color = YELLOW) - dot.to_corner(UP+LEFT) - - equals = TexMobject("=") - equals.replace(self.arrow) - equals.scale_in_place(0.8) - - brace = self.braces[1] - approx_graph = approx_graphs[1] - x = self.example_input - self.add(graph, self.series) - self.wait() - self.play(dot.move_to, self.coords_to_point(x, 0)) - self.play( - dot.move_to, self.input_to_graph_point(x, graph), - ShowCreation(v_line) - ) - self.wait() - self.play( - GrowFromCenter(brace), - ShowCreation(approx_graph) - ) - self.wait() - for new_brace, new_graph in zip(self.braces[2:], approx_graphs[2:]): - self.play( - Transform(brace, new_brace), - Transform(approx_graph, new_graph), - Animation(self.series), - ) - self.wait() - self.play(FocusOn(equals)) - self.play(Transform(self.arrow, equals)) - self.wait(2) - - def add_series(self): - series_group = self.get_series("x") - e_to_x, series = series_group - series_group.scale(0.8) - series_group.to_corner(UP+LEFT) - braces = self.get_partial_sum_braces( - series, np.zeros(self.n_iterations) - ) - for brace in braces: - brace.remove(brace[-1]) - - series.add_background_rectangle() - self.add(series_group) - - self.braces = braces - self.series = series - self.arrow = e_to_x[1] - - def get_approx_graphs(self): - def get_nth_approximation(n): - return lambda x : sum([ - float(x**k)/math.factorial(k) - for k in range(n+1) - ]) - approx_graphs = [ - self.get_graph(get_nth_approximation(n)) - for n in range(self.n_iterations) - ] - - colors = it.chain(self.colors, it.repeat(WHITE)) - for approx_graph, color in zip(approx_graphs, colors): - approx_graph.set_color(color) - dot = Dot(color = WHITE) - dot.move_to(self.input_to_graph_point( - self.example_input, approx_graph - )) - approx_graph.add(dot) - - return approx_graphs - -class SecondExpGraphConvergence(ExpGraphConvergence): - CONFIG = { - "example_input" : 3, - "n_iterations" : 12, - } - -class BoundedRadiusOfConvergence(CubicAndQuarticApproximations): - CONFIG = { - "num_graph_anchor_points" : 100, - } - def construct(self): - self.setup_axes() - func = lambda x : (np.sin(x**2 + x)+0.5)*(np.log(x+1.01)+1)*np.exp(-x) - graph = self.get_graph( - func, color = self.colors[0], - x_min = -0.99, - x_max = self.x_max, - ) - v_line = self.get_vertical_line_to_graph( - 0, graph, - line_class = DashedLine, - color = YELLOW - ) - dot = Dot(color = YELLOW).move_to(v_line.get_top()) - two_graph = self.get_graph(lambda x : 2) - outer_v_lines = VGroup(*[ - DashedLine( - self.coords_to_point(x, -2), - self.coords_to_point(x, 2), - color = WHITE - ) - for x in (-1, 1) - ]) - - colors = list(self.colors) + [GREEN, MAROON_B, PINK] - approx_graphs = [ - self.get_graph( - taylor_approximation(func, n), - color = color - ) - for n, color in enumerate(colors) - ] - approx_graph = approx_graphs[1] - - self.add(graph, v_line, dot) - self.play(ReplacementTransform( - VGroup(v_line.copy()), outer_v_lines - )) - self.play( - ShowCreation(approx_graph), - Animation(dot) - ) - self.wait() - for new_graph in approx_graphs[2:]: - self.play( - Transform(approx_graph, new_graph), - Animation(dot) - ) - self.wait() - self.wait() - -class RadiusOfConvergenceForLnX(ExpGraphConvergence): - CONFIG = { - "x_min" : -1, - "x_leftmost_tick" : None, - "x_max" : 5, - "y_min" : -2, - "y_max" : 3, - "graph_origin" : DOWN+2*LEFT, - "func" : np.log, - "num_graph_anchor_points" : 100, - "initial_n_iterations" : 7, - "n_iterations" : 11, - "convergent_example" : 1.5, - "divergent_example" : 2.5, - } - def construct(self): - self.add_graph() - self.add_series() - self.show_bounds() - self.show_converging_point() - self.show_diverging_point() - self.write_divergence() - self.write_radius_of_convergence() - - def add_graph(self): - self.setup_axes() - self.graph = self.get_graph( - self.func, - x_min = 0.01 - ) - self.add(self.graph) - - def add_series(self): - series = TexMobject( - "\\ln(x) \\rightarrow", - "(x-1)", "-", - "\\frac{(x-1)^2}{2}", "+", - "\\frac{(x-1)^3}{3}", "-", - "\\frac{(x-1)^4}{4}", "+", - "\\cdots" - ) - lhs = VGroup(*series[1:]) - series.add_background_rectangle() - series.scale(0.8) - series.to_corner(UP+LEFT) - for n in range(4): - lhs[2*n].set_color(self.colors[n+1]) - self.braces = self.get_partial_sum_braces( - lhs, np.zeros(self.n_iterations) - ) - for brace in self.braces: - brace.remove(brace[-1]) - - self.play(FadeIn( - series, - run_time = 3, - lag_ratio = 0.5 - )) - self.wait() - self.series = series - self.foreground_mobjects = [series] - - def show_bounds(self): - dot = Dot(fill_opacity = 0) - dot.move_to(self.series) - v_lines = [ - DashedLine(*[ - self.coords_to_point(x, y) - for y in (-2, 2) - ]) - for x in (0, 1, 2) - ] - outer_v_lines = VGroup(*v_lines[::2]) - center_v_line = VGroup(v_lines[1]) - input_v_line = Line(*[ - self.coords_to_point(self.convergent_example, y) - for y in (-4, 3) - ]) - input_v_line.set_stroke(WHITE, width = 2) - - self.play( - dot.move_to, self.coords_to_point(1, 0), - dot.set_fill, YELLOW, 1, - ) - self.wait() - self.play( - GrowFromCenter(center_v_line), - Animation(dot) - ) - self.play(Transform(center_v_line, outer_v_lines)) - - self.foreground_mobjects.append(dot) - - def show_converging_point(self): - approx_graphs = [ - self.get_graph( - taylor_approximation(self.func, n, 1), - color = WHITE - ) - for n in range(1, self.n_iterations+1) - ] - colors = it.chain( - self.colors[1:], - [GREEN, MAROON_B], - it.repeat(PINK) - ) - for graph, color in zip(approx_graphs, colors): - graph.set_color(color) - for graph in approx_graphs: - dot = Dot(color = WHITE) - dot.move_to(self.input_to_graph_point( - self.convergent_example, graph - )) - graph.dot = dot - graph.add(dot) - - approx_graph = approx_graphs[0].deepcopy() - approx_dot = approx_graph.dot - brace = self.braces[0].copy() - - self.play(*it.chain( - list(map(FadeIn, [approx_graph, brace])), - list(map(Animation, self.foreground_mobjects)) - )) - self.wait() - new_graphs = approx_graphs[1:self.initial_n_iterations] - for new_graph, new_brace in zip(new_graphs, self.braces[1:]): - self.play( - Transform(approx_graph, new_graph), - Transform(brace, new_brace), - *list(map(Animation, self.foreground_mobjects)) - ) - self.wait() - approx_graph.remove(approx_dot) - self.play( - approx_dot.move_to, self.coords_to_point(self.divergent_example, 0), - *it.chain( - list(map(FadeOut, [approx_graph, brace])), - list(map(Animation, self.foreground_mobjects)) - ) - ) - self.wait() - - self.approx_graphs = approx_graphs - self.approx_dot = approx_dot - - def show_diverging_point(self): - for graph in self.approx_graphs: - graph.dot.move_to(self.input_to_graph_point( - self.divergent_example, graph - )) - - approx_graph = self.approx_graphs[0].deepcopy() - brace = self.braces[0].copy() - - self.play( - ReplacementTransform( - self.approx_dot, approx_graph.dot - ), - FadeIn(approx_graph[0]), - FadeIn(brace), - *list(map(Animation, self.foreground_mobjects)) - ) - - new_graphs = self.approx_graphs[1:self.initial_n_iterations] - for new_graph, new_brace in zip(self.approx_graphs[1:], self.braces[1:]): - self.play( - Transform(approx_graph, new_graph), - Transform(brace, new_brace), - *list(map(Animation, self.foreground_mobjects)) - ) - self.wait() - - self.approx_dot = approx_graph.dot - self.approx_graph = approx_graph - - def write_divergence(self): - word = TextMobject("``Diverges''") - word.next_to(self.approx_dot, RIGHT, LARGE_BUFF) - word.shift(MED_SMALL_BUFF*DOWN) - word.add_background_rectangle() - arrow = Arrow( - word.get_left(), self.approx_dot, - buff = SMALL_BUFF, - color = WHITE - ) - - self.play( - Write(word), - ShowCreation(arrow) - ) - self.wait() - new_graphs = self.approx_graphs[self.initial_n_iterations:] - for new_graph in new_graphs: - self.play( - Transform(self.approx_graph, new_graph), - *list(map(Animation, self.foreground_mobjects)) - ) - self.wait() - - def write_radius_of_convergence(self): - line = Line(*[ - self.coords_to_point(x, 0) - for x in (1, 2) - ]) - line.set_color(YELLOW) - brace = Brace(line, DOWN) - words = brace.get_text("``Radius of convergence''") - words.add_background_rectangle() - - self.play( - GrowFromCenter(brace), - ShowCreation(line) - ) - self.wait() - self.play(Write(words)) - self.wait(3) - -class MoreToBeSaid(TeacherStudentsScene): - CONFIG = { - "seconds_to_blink" : 4, - } - def construct(self): - words = TextMobject( - "Lagrange error bounds, ", - "convergence tests, ", - "$\\dots$" - ) - words[0].set_color(BLUE) - words[1].set_color(GREEN) - words.to_edge(UP) - fade_rect = FullScreenFadeRectangle() - rect = Rectangle(height = 9, width = 16) - rect.set_height(FRAME_Y_RADIUS) - rect.to_corner(UP+RIGHT) - randy = self.get_students()[1] - - self.teacher_says( - "There's still \\\\ more to learn!", - target_mode = "surprised", - bubble_kwargs = {"height" : 3, "width" : 4} - ) - for word in words: - self.play(FadeIn(word)) - self.wait() - self.teacher_says( - "About everything", - ) - self.change_student_modes(*["pondering"]*3) - self.wait() - self.remove() - self.pi_creatures = []##Hack - self.play( - RemovePiCreatureBubble(self.teacher), - FadeOut(words), - FadeIn(fade_rect), - randy.change, "happy", rect - ) - self.pi_creatures = [randy] - self.play(ShowCreation(rect)) - self.wait(4) - -class Chapter10Thanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "CrypticSwarm", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Karan Bhargava", - "Ankit Agarwal", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Markus Persson", - "Joseph John Cox", - "Dan Buchoff", - "Derek Dai", - "Luc Ritchie", - "Ahmad Bamieh", - "Mark Govea", - "Zac Wentzell", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "Nils Schneider", - "James Thornton", - "Mustafa Mahdi", - "Jonathan Eppele", - "Mathew Bramson", - "Jerry Ling", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ], - } - -class Thumbnail(ExampleApproximationWithSine): - CONFIG = { - "graph_origin" : DOWN, - "x_axis_label" : "", - "y_axis_label" : "", - "x_axis_width" : 14, - "graph_stroke_width" : 8, - } - def construct(self): - self.setup_axes() - - cos_graph = self.get_graph(np.cos) - cos_graph.set_stroke(BLUE, self.graph_stroke_width) - quad_graph = self.get_graph(taylor_approximation(np.cos, 2)) - quad_graph.set_stroke(GREEN, self.graph_stroke_width) - quartic = self.get_graph(taylor_approximation(np.cos, 4)) - quartic.set_stroke(PINK, self.graph_stroke_width) - self.add(cos_graph, quad_graph, quartic) - - title = TextMobject("Taylor Series") - title.set_width(1.5*FRAME_X_RADIUS) - title.add_background_rectangle() - title.to_edge(UP) - self.add(title) - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eoc/chapter2.py b/from_3b1b/old/eoc/chapter2.py deleted file mode 100644 index ec3e53d4..00000000 --- a/from_3b1b/old/eoc/chapter2.py +++ /dev/null @@ -1,2607 +0,0 @@ -from manimlib.imports import * - -DISTANCE_COLOR = BLUE -TIME_COLOR = YELLOW -VELOCITY_COLOR = GREEN - - -#### Warning, scenes here not updated based on most recent GraphScene changes ####### - - -class IncrementNumber(Succession): - CONFIG = { - "start_num" : 0, - "changes_per_second" : 1, - "run_time" : 11, - } - def __init__(self, num_mob, **kwargs): - digest_config(self, kwargs) - n_iterations = int(self.run_time * self.changes_per_second) - new_num_mobs = [ - TexMobject(str(num)).move_to(num_mob, LEFT) - for num in range(self.start_num, self.start_num+n_iterations) - ] - transforms = [ - Transform( - num_mob, new_num_mob, - run_time = 1.0/self.changes_per_second, - rate_func = squish_rate_func(smooth, 0, 0.5) - ) - for new_num_mob in new_num_mobs - ] - Succession.__init__( - self, *transforms, **{ - "rate_func" : None, - "run_time" : self.run_time, - } - ) - -class IncrementTest(Scene): - def construct(self): - num = TexMobject("0") - num.shift(UP) - self.play(IncrementNumber(num)) - self.wait() - - - -############################ - -class Chapter2OpeningQuote(OpeningQuote): - CONFIG = { - "quote" : [ - "So far as the theories of mathematics are about", - "reality,", - "they are not", - "certain;", - "so far as they are", - "certain,", - "they are not about", - "reality.", - ], - "highlighted_quote_terms" : { - "reality," : BLUE, - "certain;" : GREEN, - "certain," : GREEN, - "reality." : BLUE, - }, - "author" : "Albert Einstein" - } - -class Introduction(TeacherStudentsScene): - def construct(self): - goals = TextMobject( - "Goals: ", - "1) Learn derivatives", - ", 2) Avoid paradoxes.", - arg_separator = "" - ) - goals[1].set_color(MAROON_B) - goals[2].set_color(RED) - goals[2][0].set_color(WHITE) - goals.to_edge(UP) - self.add(*goals[:2]) - - self.student_says( - "What is a derivative?", - run_time = 2 - ) - self.play(self.get_teacher().change_mode, "happy") - self.wait() - self.teacher_says( - "It's actually a \\\\", - "very subtle idea", - target_mode = "well" - ) - self.change_student_modes(None, "pondering", "thinking") - self.play(Write(goals[2], run_time = 2)) - self.change_student_modes("erm") - self.student_says( - "Instantaneous rate of change", "?", - student_index = 0, - ) - self.wait() - - bubble = self.get_students()[0].bubble - phrase = bubble.content[0] - bubble.content.remove(phrase) - self.play( - FadeOut(bubble), - FadeOut(bubble.content), - FadeOut(goals), - phrase.center, - phrase.scale, 1.5, - phrase.to_edge, UP, - *it.chain(*[ - [ - pi.change_mode, mode, - pi.look_at, FRAME_Y_RADIUS*UP - ] - for pi, mode in zip(self.get_pi_creatures(), [ - "speaking", "pondering", "confused", "confused", - ]) - ]) - ) - self.wait() - change = VGroup(*phrase[-len("change"):]) - instantaneous = VGroup(*phrase[:len("instantaneous")]) - change_brace = Brace(change) - change_description = change_brace.get_text( - "Requires multiple \\\\ points in time" - ) - instantaneous_brace = Brace(instantaneous) - instantaneous_description = instantaneous_brace.get_text( - "One point \\\\ in time" - ) - clock = Clock() - clock.next_to(change_description, DOWN) - def get_clock_anim(run_time = 3): - return ClockPassesTime( - clock, - hours_passed = 0.4*run_time, - run_time = run_time, - ) - self.play(FadeIn(clock)) - self.play( - change.set_color_by_gradient, BLUE, YELLOW, - GrowFromCenter(change_brace), - Write(change_description), - get_clock_anim() - ) - self.play(get_clock_anim(1)) - stopped_clock = clock.copy() - stopped_clock.next_to(instantaneous_description, DOWN) - self.play( - instantaneous.set_color, BLUE, - GrowFromCenter(instantaneous_brace), - Transform(change_description.copy(), instantaneous_description), - clock.copy().next_to, instantaneous_description, DOWN, - get_clock_anim(3) - ) - self.play(get_clock_anim(12)) - -class FathersOfCalculus(Scene): - CONFIG = { - "names" : [ - "Barrow", - "Newton", - "Leibniz", - "Cauchy", - "Weierstrass", - ], - "picture_height" : 2.5, - } - def construct(self): - title = TextMobject("(A few) Fathers of Calculus") - title.to_edge(UP) - self.add(title) - - men = Mobject() - for name in self.names: - image = ImageMobject(name, invert = False) - image.set_height(self.picture_height) - title = TextMobject(name) - title.scale(0.8) - title.next_to(image, DOWN) - image.add(title) - men.add(image) - men.arrange(RIGHT, aligned_edge = UP) - men.shift(DOWN) - - discover_brace = Brace(Mobject(*men[:3]), UP) - discover = discover_brace.get_text("Discovered it") - VGroup(discover_brace, discover).set_color(BLUE) - rigor_brace = Brace(Mobject(*men[3:]), UP) - rigor = rigor_brace.get_text("Made it rigorous") - rigor.shift(0.1*DOWN) - VGroup(rigor_brace, rigor).set_color(YELLOW) - - - for man in men: - self.play(FadeIn(man)) - self.play( - GrowFromCenter(discover_brace), - Write(discover, run_time = 1) - ) - self.play( - GrowFromCenter(rigor_brace), - Write(rigor, run_time = 1) - ) - self.wait() - -class IntroduceCar(Scene): - CONFIG = { - "should_transition_to_graph" : True, - "show_distance" : True, - "point_A" : DOWN+4*LEFT, - "point_B" : DOWN+5*RIGHT, - } - def construct(self): - point_A, point_B = self.point_A, self.point_B - A = Dot(point_A) - B = Dot(point_B) - line = Line(point_A, point_B) - VGroup(A, B, line).set_color(WHITE) - for dot, tex in (A, "A"), (B, "B"): - label = TexMobject(tex).next_to(dot, DOWN) - dot.add(label) - - car = Car() - self.car = car #For introduce_added_mobjects use in subclasses - car.move_to(point_A) - front_line = car.get_front_line() - - time_label = TextMobject("Time (in seconds):", "0") - time_label.shift(2*UP) - - distance_brace = Brace(line, UP) - # distance_brace.set_fill(opacity = 0.5) - distance = distance_brace.get_text("100m") - - self.add(A, B, line, car, time_label) - self.play(ShowCreation(front_line)) - self.play(FadeOut(front_line)) - self.introduce_added_mobjects() - self.play( - MoveCar(car, point_B, run_time = 10), - IncrementNumber(time_label[1], run_time = 11), - *self.get_added_movement_anims() - ) - front_line = car.get_front_line() - self.play(ShowCreation(front_line)) - self.play(FadeOut(front_line)) - - if self.show_distance: - self.play( - GrowFromCenter(distance_brace), - Write(distance) - ) - self.wait() - - if self.should_transition_to_graph: - self.play( - car.move_to, point_A, - FadeOut(time_label), - FadeOut(distance_brace), - FadeOut(distance), - ) - graph_scene = GraphCarTrajectory(skip_animations = True) - origin = graph_scene.graph_origin - top = graph_scene.coords_to_point(0, 100) - new_length = get_norm(top-origin) - new_point_B = point_A + new_length*RIGHT - car_line_group = VGroup(car, A, B, line) - for mob in car_line_group: - mob.generate_target() - car_line_group.target = VGroup(*[ - m.target for m in car_line_group - ]) - B = car_line_group[2] - B.target.shift(new_point_B - point_B) - line.target.put_start_and_end_on( - point_A, new_point_B - ) - - car_line_group.target.rotate(np.pi/2, about_point = point_A) - car_line_group.target.shift(graph_scene.graph_origin - point_A) - self.play(MoveToTarget(car_line_group, path_arc = np.pi/2)) - self.wait() - - def introduce_added_mobjects(self): - pass - - def get_added_movement_anims(self): - return [] - -class GraphCarTrajectory(GraphScene): - CONFIG = { - "x_min" : 0, - "x_max" : 10, - "x_labeled_nums" : list(range(1, 11)), - "x_axis_label" : "Time (seconds)", - "y_min" : 0, - "y_max" : 110, - "y_tick_frequency" : 10, - "y_labeled_nums" : list(range(10, 110, 10)), - "y_axis_label" : "Distance traveled \\\\ (meters)", - "graph_origin" : 2.5*DOWN + 5*LEFT, - "default_graph_colors" : [DISTANCE_COLOR, VELOCITY_COLOR], - "default_derivative_color" : VELOCITY_COLOR, - "time_of_journey" : 10, - "care_movement_rate_func" : smooth, - } - def construct(self): - self.setup_axes(animate = False) - graph = self.graph_sigmoid_trajectory_function() - origin = self.coords_to_point(0, 0) - - self.introduce_graph(graph, origin) - self.comment_on_slope(graph, origin) - self.show_velocity_graph() - self.ask_critically_about_velocity() - - def graph_sigmoid_trajectory_function(self, **kwargs): - graph = self.get_graph( - lambda t : 100*smooth(t/10.), - **kwargs - ) - self.s_graph = graph - return graph - - def introduce_graph(self, graph, origin): - h_line, v_line = [ - Line(origin, origin, color = color, stroke_width = 2) - for color in (TIME_COLOR, DISTANCE_COLOR) - ] - def h_update(h_line, proportion = 1): - end = graph.point_from_proportion(proportion) - t_axis_point = end[0]*RIGHT + origin[1]*UP - h_line.put_start_and_end_on(t_axis_point, end) - def v_update(v_line, proportion = 1): - end = graph.point_from_proportion(proportion) - d_axis_point = origin[0]*RIGHT + end[1]*UP - v_line.put_start_and_end_on(d_axis_point, end) - - car = Car() - car.rotate(np.pi/2) - car.move_to(origin) - car_target = origin*RIGHT + graph.point_from_proportion(1)*UP - - - self.add(car) - self.play( - ShowCreation( - graph, - rate_func=linear, - ), - MoveCar( - car, car_target, - rate_func = self.care_movement_rate_func - ), - UpdateFromFunc(h_line, h_update), - UpdateFromFunc(v_line, v_update), - run_time = self.time_of_journey, - ) - self.wait() - self.play(*list(map(FadeOut, [h_line, v_line, car]))) - - #Show example vertical distance - h_update(h_line, 0.6) - t_dot = Dot(h_line.get_start(), color = h_line.get_color()) - t_dot.save_state() - t_dot.move_to(self.x_axis_label_mob) - t_dot.set_fill(opacity = 0) - dashed_h = DashedLine(*h_line.get_start_and_end()) - dashed_h.set_color(h_line.get_color()) - brace = Brace(dashed_h, RIGHT) - brace_text = brace.get_text("Distance traveled") - self.play(t_dot.restore) - self.wait() - self.play(ShowCreation(dashed_h)) - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait(2) - self.play(*list(map(FadeOut, [t_dot, dashed_h, brace, brace_text]))) - - #Name graph - s_of_t = TexMobject("s(t)") - s_of_t.next_to( - graph.point_from_proportion(1), - DOWN+RIGHT, - buff = SMALL_BUFF - ) - s = s_of_t[0] - d = TexMobject("d") - d.move_to(s, DOWN) - d.set_color(DISTANCE_COLOR) - - self.play(Write(s_of_t)) - self.wait() - s.save_state() - self.play(Transform(s, d)) - self.wait() - self.play(s.restore) - - def comment_on_slope(self, graph, origin): - delta_t = 1 - curr_time = 0 - ghost_line = Line( - origin, - self.coords_to_point(delta_t, self.y_max) - ) - rect = Rectangle().replace(ghost_line, stretch = True) - rect.set_stroke(width = 0) - rect.set_fill(TIME_COLOR, opacity = 0.3) - - change_lines = self.get_change_lines(curr_time, delta_t) - self.play(FadeIn(rect)) - self.wait() - self.play(Write(change_lines)) - self.wait() - for x in range(1, 10): - curr_time = x - new_change_lines = self.get_change_lines(curr_time, delta_t) - self.play( - rect.move_to, self.coords_to_point(curr_time, 0), DOWN+LEFT, - Transform(change_lines, new_change_lines) - ) - if curr_time == 5: - text = change_lines[-1].get_text( - "$\\frac{\\text{meters}}{\\text{second}}$" - ) - self.play(Write(text)) - self.wait() - self.play(FadeOut(text)) - else: - self.wait() - self.play(*list(map(FadeOut, [rect, change_lines]))) - self.rect = rect - - def get_change_lines(self, curr_time, delta_t = 1): - p1 = self.input_to_graph_point( - curr_time, self.s_graph - ) - p2 = self.input_to_graph_point( - curr_time+delta_t, self.s_graph - ) - interim_point = p2[0]*RIGHT + p1[1]*UP - delta_t_line = Line(p1, interim_point, color = TIME_COLOR) - delta_s_line = Line(interim_point, p2, color = DISTANCE_COLOR) - brace = Brace(delta_s_line, RIGHT, buff = SMALL_BUFF) - return VGroup(delta_t_line, delta_s_line, brace) - - def show_velocity_graph(self): - velocity_graph = self.get_derivative_graph(self.s_graph) - - self.play(ShowCreation(velocity_graph)) - def get_velocity_label(v_graph): - result = self.get_graph_label( - v_graph, - label = "v(t)", - direction = UP+RIGHT, - x_val = 5, - buff = SMALL_BUFF, - ) - self.remove(result) - return result - label = get_velocity_label(velocity_graph) - self.play(Write(label)) - self.wait() - self.rect.move_to(self.coords_to_point(0, 0), DOWN+LEFT) - self.play(FadeIn(self.rect)) - self.wait() - for time, show_slope in (4.5, True), (9, False): - self.play( - self.rect.move_to, self.coords_to_point(time, 0), DOWN+LEFT - ) - if show_slope: - change_lines = self.get_change_lines(time) - self.play(FadeIn(change_lines)) - self.wait() - self.play(FadeOut(change_lines)) - else: - self.wait() - self.play(FadeOut(self.rect)) - - #Change distance and velocity graphs - self.s_graph.save_state() - velocity_graph.save_state() - label.save_state() - def shallow_slope(t): - return 100*smooth(t/10., inflection = 4) - def steep_slope(t): - return 100*smooth(t/10., inflection = 25) - def double_smooth_graph_function(t): - if t < 5: - return 50*smooth(t/5.) - else: - return 50*(1+smooth((t-5)/5.)) - graph_funcs = [ - shallow_slope, - steep_slope, - double_smooth_graph_function, - ] - for graph_func in graph_funcs: - new_graph = self.get_graph( - graph_func, - color = DISTANCE_COLOR, - ) - self.remove(new_graph) - new_velocity_graph = self.get_derivative_graph( - graph = new_graph, - ) - new_velocity_label = get_velocity_label(new_velocity_graph) - - self.play(Transform(self.s_graph, new_graph)) - self.play( - Transform(velocity_graph, new_velocity_graph), - Transform(label, new_velocity_label), - ) - self.wait(2) - self.play(self.s_graph.restore) - self.play( - velocity_graph.restore, - label.restore, - ) - self.wait(2) - - def ask_critically_about_velocity(self): - morty = Mortimer().flip() - morty.to_corner(DOWN+LEFT) - self.play(PiCreatureSays(morty, - "Think critically about \\\\", - "what velocity means." - )) - self.play(Blink(morty)) - self.wait() - -class ShowSpeedometer(IntroduceCar): - CONFIG = { - "num_ticks" : 8, - "tick_length" : 0.2, - "needle_width" : 0.1, - "needle_height" : 0.8, - "should_transition_to_graph" : False, - "show_distance" : False, - "speedometer_title_text" : "Speedometer", - } - def setup(self): - start_angle = -np.pi/6 - end_angle = 7*np.pi/6 - speedometer = Arc( - start_angle = start_angle, - angle = end_angle-start_angle - ) - tick_angle_range = np.linspace(end_angle, start_angle, self.num_ticks) - for index, angle in enumerate(tick_angle_range): - vect = rotate_vector(RIGHT, angle) - tick = Line((1-self.tick_length)*vect, vect) - label = TexMobject(str(10*index)) - label.set_height(self.tick_length) - label.shift((1+self.tick_length)*vect) - speedometer.add(tick, label) - - needle = Polygon( - LEFT, UP, RIGHT, - stroke_width = 0, - fill_opacity = 1, - fill_color = YELLOW - ) - needle.stretch_to_fit_width(self.needle_width) - needle.stretch_to_fit_height(self.needle_height) - needle.rotate(end_angle-np.pi/2) - speedometer.add(needle) - speedometer.needle = needle - - speedometer.center_offset = speedometer.get_center() - - speedometer_title = TextMobject(self.speedometer_title_text) - speedometer_title.to_corner(UP+LEFT) - speedometer.next_to(speedometer_title, DOWN) - - self.speedometer = speedometer - self.speedometer_title = speedometer_title - - def introduce_added_mobjects(self): - speedometer = self.speedometer - speedometer_title = self.speedometer_title - - speedometer.save_state() - speedometer.rotate(-np.pi/2, UP) - speedometer.set_height(self.car.get_height()/4) - speedometer.move_to(self.car) - speedometer.shift((self.car.get_width()/4)*RIGHT) - - self.play(speedometer.restore, run_time = 2) - self.play(Write(speedometer_title, run_time = 1)) - - def get_added_movement_anims(self, **kwargs): - needle = self.speedometer.needle - center = self.speedometer.get_center() - self.speedometer.center_offset - default_kwargs = { - "about_point" : center, - "radians" : -np.pi/2, - "run_time" : 10, - "rate_func" : there_and_back, - } - default_kwargs.update(kwargs) - return [Rotating(needle, **default_kwargs)] - - # def construct(self): - # self.add(self.speedometer) - # self.play(*self.get_added_movement_anims()) - -class VelocityInAMomentMakesNoSense(Scene): - def construct(self): - randy = Randolph() - randy.next_to(ORIGIN, DOWN+LEFT) - words = TextMobject("Velocity in \\\\ a moment") - words.next_to(randy, UP+RIGHT) - randy.look_at(words) - q_marks = TextMobject("???") - q_marks.next_to(randy, UP) - - self.play( - randy.change_mode, "confused", - Write(words) - ) - self.play(Blink(randy)) - self.play(Write(q_marks)) - self.play(Blink(randy)) - self.wait() - -class SnapshotOfACar(Scene): - def construct(self): - car = Car() - car.scale(1.5) - car.move_to(3*LEFT+DOWN) - flash_box = Rectangle( - width = FRAME_WIDTH, - height = FRAME_HEIGHT, - stroke_width = 0, - fill_color = WHITE, - fill_opacity = 1, - ) - speed_lines = VGroup(*[ - Line(point, point+0.5*LEFT) - for point in [ - 0.5*UP+0.25*RIGHT, - ORIGIN, - 0.5*DOWN+0.25*RIGHT - ] - ]) - question = TextMobject(""" - How fast is - this car going? - """) - - self.play(MoveCar( - car, RIGHT+DOWN, - run_time = 2, - rate_func = rush_into - )) - car.get_tires().set_color(GREY) - speed_lines.next_to(car, LEFT) - self.add(speed_lines) - self.play( - flash_box.set_fill, None, 0, - rate_func = rush_from - ) - question.next_to(car, UP, buff = LARGE_BUFF) - self.play(Write(question, run_time = 2)) - self.wait(2) - -class CompareTwoTimes(Scene): - CONFIG = { - "start_distance" : 30, - "start_time" : 4, - "end_distance" : 50, - "end_time" : 5, - "fade_at_the_end" : True, - } - def construct(self): - self.introduce_states() - self.show_equation() - if self.fade_at_the_end: - self.fade_all_but_one_moment() - - def introduce_states(self): - state1 = self.get_car_state(self.start_distance, self.start_time) - state2 = self.get_car_state(self.end_distance, self.end_time) - - state1.to_corner(UP+LEFT) - state2.to_corner(DOWN+LEFT) - - dividers = VGroup( - Line(FRAME_X_RADIUS*LEFT, RIGHT), - Line(RIGHT+FRAME_Y_RADIUS*UP, RIGHT+FRAME_Y_RADIUS*DOWN), - ) - dividers.set_color(GREY) - - self.add(dividers, state1) - self.wait() - copied_state = state1.copy() - self.play(copied_state.move_to, state2) - self.play(Transform(copied_state, state2)) - self.wait(2) - self.keeper = state1 - - def show_equation(self): - velocity = TextMobject("Velocity") - change_over_change = TexMobject( - "\\frac{\\text{Change in distance}}{\\text{Change in time}}" - ) - formula = TexMobject( - "\\frac{(%s - %s) \\text{ meters}}{(%s - %s) \\text{ seconds}}"%( - str(self.end_distance), str(self.start_distance), - str(self.end_time), str(self.start_time), - ) - ) - ed_len = len(str(self.end_distance)) - sd_len = len(str(self.start_distance)) - et_len = len(str(self.end_time)) - st_len = len(str(self.start_time)) - seconds_len = len("seconds") - VGroup( - VGroup(*formula[1:1+ed_len]), - VGroup(*formula[2+ed_len:2+ed_len+sd_len]) - ).set_color(DISTANCE_COLOR) - VGroup( - VGroup(*formula[-2-seconds_len-et_len-st_len:-2-seconds_len-st_len]), - VGroup(*formula[-1-seconds_len-st_len:-1-seconds_len]), - ).set_color(TIME_COLOR) - - down_arrow1 = TexMobject("\\Downarrow") - down_arrow2 = TexMobject("\\Downarrow") - group = VGroup( - velocity, down_arrow1, - change_over_change, down_arrow2, - formula, - ) - group.arrange(DOWN) - group.to_corner(UP+RIGHT) - - self.play(FadeIn( - group, lag_ratio = 0.5, - run_time = 3 - )) - self.wait(3) - self.formula = formula - - def fade_all_but_one_moment(self): - anims = [ - ApplyMethod(mob.fade, 0.5) - for mob in self.get_mobjects() - ] - anims.append(Animation(self.keeper.copy())) - self.play(*anims) - self.wait() - - def get_car_state(self, distance, time): - line = Line(3*LEFT, 3*RIGHT) - dots = list(map(Dot, line.get_start_and_end())) - line.add(*dots) - car = Car() - car.move_to(line.get_start()) - car.shift((distance/10)*RIGHT) - front_line = car.get_front_line() - - brace = Brace(VGroup(dots[0], front_line), DOWN) - distance_label = brace.get_text( - str(distance), " meters" - ) - distance_label.set_color_by_tex(str(distance), DISTANCE_COLOR) - brace.add(distance_label) - time_label = TextMobject( - "Time:", str(time), "seconds" - ) - time_label.set_color_by_tex(str(time), TIME_COLOR) - time_label.next_to( - VGroup(line, car), UP, - aligned_edge = LEFT - ) - - return VGroup(line, car, front_line, brace, time_label) - -class VelocityAtIndividualPointsVsPairs(GraphCarTrajectory): - CONFIG = { - "start_time" : 6.5, - "end_time" : 3, - "dt" : 1.0, - } - def construct(self): - self.setup_axes(animate = False) - distance_graph = self.graph_function(lambda t : 100*smooth(t/10.)) - distance_label = self.label_graph( - distance_graph, - label = "s(t)", - proportion = 1, - direction = RIGHT, - buff = SMALL_BUFF - ) - velocity_graph = self.get_derivative_graph() - self.play(ShowCreation(velocity_graph)) - velocity_label = self.label_graph( - velocity_graph, - label = "v(t)", - proportion = self.start_time/10.0, - direction = UP, - buff = MED_SMALL_BUFF - ) - velocity_graph.add(velocity_label) - - self.show_individual_times_to_velocity(velocity_graph) - self.play(velocity_graph.fade, 0.4) - self.show_two_times_on_distance() - self.show_confused_pi_creature() - - def show_individual_times_to_velocity(self, velocity_graph): - start_time = self.start_time - end_time = self.end_time - line = self.get_vertical_line_to_graph(start_time, velocity_graph) - def line_update(line, alpha): - time = interpolate(start_time, end_time, alpha) - line.put_start_and_end_on( - self.coords_to_point(time, 0), - self.input_to_graph_point(time, graph = velocity_graph) - ) - - self.play(ShowCreation(line)) - self.wait() - self.play(UpdateFromAlphaFunc( - line, line_update, - run_time = 4, - rate_func = there_and_back - )) - self.wait() - velocity_graph.add(line) - - def show_two_times_on_distance(self): - line1 = self.get_vertical_line_to_graph(self.start_time-self.dt/2.0) - line2 = self.get_vertical_line_to_graph(self.start_time+self.dt/2.0) - p1 = line1.get_end() - p2 = line2.get_end() - interim_point = p2[0]*RIGHT+p1[1]*UP - dt_line = Line(p1, interim_point, color = TIME_COLOR) - ds_line = Line(interim_point, p2, color = DISTANCE_COLOR) - dt_brace = Brace(dt_line, DOWN, buff = SMALL_BUFF) - ds_brace = Brace(ds_line, RIGHT, buff = SMALL_BUFF) - dt_text = dt_brace.get_text("Change in time", buff = SMALL_BUFF) - ds_text = ds_brace.get_text("Change in distance", buff = SMALL_BUFF) - - self.play(ShowCreation(VGroup(line1, line2))) - for line, brace, text in (dt_line, dt_brace, dt_text), (ds_line, ds_brace, ds_text): - brace.set_color(line.get_color()) - text.set_color(line.get_color()) - text.add_background_rectangle() - self.play( - ShowCreation(line), - GrowFromCenter(brace), - Write(text) - ) - self.wait() - - def show_confused_pi_creature(self): - randy = Randolph() - randy.to_corner(DOWN+LEFT) - randy.shift(2*RIGHT) - - self.play(randy.change_mode, "confused") - self.play(Blink(randy)) - self.wait(2) - self.play(Blink(randy)) - self.play(randy.change_mode, "erm") - self.wait() - self.play(Blink(randy)) - self.wait(2) - -class FirstRealWorld(TeacherStudentsScene): - def construct(self): - self.teacher_says("First, the real world.") - self.change_student_modes( - "happy", "hooray", "happy" - ) - self.wait(3) - -class SidestepParadox(Scene): - def construct(self): - car = Car() - car.shift(DOWN) - show_speedometer = ShowSpeedometer(skip_animations = True) - speedometer = show_speedometer.speedometer - speedometer.next_to(car, UP) - - title = TextMobject( - "Instantaneous", "rate of change" - ) - title.to_edge(UP) - cross = TexMobject("\\times") - cross.replace(title[0], stretch = True) - cross.set_fill(RED, opacity = 0.8) - - new_words = TextMobject("over a small time") - new_words.next_to(title[1], DOWN) - new_words.set_color(TIME_COLOR) - - self.add(title, car) - self.play(Write(speedometer)) - self.wait() - self.play(Write(cross)) - self.wait() - self.play(Write(new_words)) - self.wait() - -class CompareTwoVerySimilarTimes(CompareTwoTimes): - CONFIG = { - "start_distance" : 20, - "start_time" : 3, - "end_distance" : 20.21, - "end_time" : 3.01, - "fade_at_the_end" : False, - } - def construct(self): - CompareTwoTimes.construct(self) - - formula = self.formula - ds_symbols, dt_symbols = [ - VGroup(*[ - mob - for mob in formula - if mob.get_color() == Color(color) - ]) - for color in (DISTANCE_COLOR, TIME_COLOR) - ] - ds_brace = Brace(ds_symbols, UP) - ds_text = ds_brace.get_text("$ds$", buff = SMALL_BUFF) - ds_text.set_color(DISTANCE_COLOR) - dt_brace = Brace(dt_symbols, DOWN) - dt_text = dt_brace.get_text("$dt$", buff = SMALL_BUFF) - dt_text.set_color(TIME_COLOR) - - self.play( - GrowFromCenter(dt_brace), - Write(dt_text) - ) - formula.add(dt_brace, dt_text) - self.wait(2) - - formula.generate_target() - VGroup( - ds_brace, ds_text, formula.target - ).move_to(formula, UP).shift(0.5*UP) - self.play( - MoveToTarget(formula), - GrowFromCenter(ds_brace), - Write(ds_text) - ) - self.wait(2) - -class DsOverDtGraphically(GraphCarTrajectory, ZoomedScene): - CONFIG = { - "dt" : 0.1, - "zoom_factor" : 4,#Before being shrunk by dt - "start_time" : 3, - "end_time" : 7, - } - def construct(self): - self.setup_axes(animate = False) - distance_graph = self.graph_function( - lambda t : 100*smooth(t/10.), - animate = False, - ) - distance_label = self.label_graph( - distance_graph, - label = "s(t)", - proportion = 0.9, - direction = UP+LEFT, - buff = SMALL_BUFF - ) - input_point_line = self.get_vertical_line_to_graph( - self.start_time, - line_kwargs = { - "dash_length" : 0.02, - "stroke_width" : 4, - "color" : WHITE, - }, - ) - def get_ds_dt_group(time): - point1 = self.input_to_graph_point(time) - point2 = self.input_to_graph_point(time+self.dt) - interim_point = point2[0]*RIGHT+point1[1]*UP - dt_line = Line(point1, interim_point, color = TIME_COLOR) - ds_line = Line(interim_point, point2, color = DISTANCE_COLOR) - result = VGroup() - for line, char, vect in (dt_line, "t", DOWN), (ds_line, "s", RIGHT): - line.scale(1./self.dt) - brace = Brace(line, vect) - text = brace.get_text("$d%s$"%char) - text.next_to(brace, vect) - text.set_color(line.get_color()) - subgroup = VGroup(line, brace, text) - subgroup.scale(self.dt) - result.add(subgroup) - return result - def align_little_rectangle_on_ds_dt_group(rect): - rect.move_to(ds_dt_group, DOWN+RIGHT) - rect.shift(self.dt*(DOWN+RIGHT)/4) - return rect - ds_dt_group = get_ds_dt_group(self.start_time) - - #Initially zoom in - self.play(ShowCreation(input_point_line)) - self.activate_zooming() - self.play(*list(map(FadeIn, [self.big_rectangle, self.little_rectangle]))) - self.play( - ApplyFunction( - align_little_rectangle_on_ds_dt_group, - self.little_rectangle - ) - ) - self.little_rectangle.generate_target() - self.little_rectangle.target.scale(self.zoom_factor*self.dt) - align_little_rectangle_on_ds_dt_group( - self.little_rectangle.target - ) - self.play( - MoveToTarget(self.little_rectangle), - run_time = 3 - ) - for subgroup in ds_dt_group: - line, brace, text= subgroup - self.play(ShowCreation(line)) - self.play( - GrowFromCenter(brace), - Write(text) - ) - self.wait() - - #Show as function - frac = TexMobject("\\frac{ds}{dt}") - VGroup(*frac[:2]).set_color(DISTANCE_COLOR) - VGroup(*frac[-2:]).set_color(TIME_COLOR) - frac.next_to(self.input_to_graph_point(5.25), DOWN+RIGHT) - rise_over_run = TexMobject( - "=\\frac{\\text{rise}}{\\text{run}}" - ) - rise_over_run.next_to(frac, RIGHT) - of_t = TexMobject("(t)") - of_t.next_to(frac, RIGHT, buff = SMALL_BUFF) - - dt_choice = TexMobject("dt = 0.01") - dt_choice.set_color(TIME_COLOR) - dt_choice.next_to(of_t, UP, aligned_edge = LEFT, buff = LARGE_BUFF) - - - full_formula = TexMobject( - "=\\frac{s(t+dt) - s(t)}{dt}" - ) - full_formula.next_to(of_t) - s_t_plus_dt = VGroup(*full_formula[1:8]) - s_t = VGroup(*full_formula[9:13]) - numerator = VGroup(*full_formula[1:13]) - lower_dt = VGroup(*full_formula[-2:]) - upper_dt = VGroup(*full_formula[5:7]) - equals = full_formula[0] - frac_line = full_formula[-3] - s_t_plus_dt.set_color(DISTANCE_COLOR) - s_t.set_color(DISTANCE_COLOR) - lower_dt.set_color(TIME_COLOR) - upper_dt.set_color(TIME_COLOR) - - velocity_graph = self.get_derivative_graph() - t_tick_marks = VGroup(*[ - Line( - UP, DOWN, - color = TIME_COLOR, - stroke_width = 3, - ).scale(0.1).move_to(self.coords_to_point(t, 0)) - for t in np.linspace(0, 10, 75) - ]) - - v_line_at_t, v_line_at_t_plus_dt = [ - self.get_vertical_line_to_graph( - time, - line_class = Line, - line_kwargs = {"color" : MAROON_B} - ) - for time in (self.end_time, self.end_time + self.dt) - ] - - - self.play(Write(frac)) - self.play(Write(rise_over_run)) - self.wait() - def input_point_line_update(line, alpha): - time = interpolate(self.start_time, self.end_time, alpha) - line.put_start_and_end_on( - self.coords_to_point(time, 0), - self.input_to_graph_point(time), - ) - def ds_dt_group_update(group, alpha): - time = interpolate(self.start_time, self.end_time, alpha) - new_group = get_ds_dt_group(time) - Transform(group, new_group).update(1) - self.play( - UpdateFromAlphaFunc(input_point_line, input_point_line_update), - UpdateFromAlphaFunc(ds_dt_group, ds_dt_group_update), - UpdateFromFunc(self.little_rectangle, align_little_rectangle_on_ds_dt_group), - run_time = 6, - ) - self.play(FadeOut(input_point_line)) - self.wait() - self.play(FadeOut(rise_over_run)) - self.play(Write(of_t)) - self.wait(2) - self.play(ShowCreation(velocity_graph)) - velocity_label = self.label_graph( - velocity_graph, - label = "v(t)", - proportion = 0.6, - direction = DOWN+LEFT, - buff = SMALL_BUFF - ) - self.wait(2) - self.play(Write(dt_choice)) - self.wait() - for anim_class in FadeIn, FadeOut: - self.play(anim_class( - t_tick_marks, lag_ratio = 0.5, - run_time = 2 - )) - self.play( - Write(equals), - Write(numerator) - ) - self.wait() - - self.play(ShowCreation(v_line_at_t)) - self.wait() - self.play(ShowCreation(v_line_at_t_plus_dt)) - self.wait() - self.play(*list(map(FadeOut, [v_line_at_t, v_line_at_t_plus_dt]))) - self.play( - Write(frac_line), - Write(lower_dt) - ) - self.wait(2) - - #Show different curves - self.disactivate_zooming() - self.remove(ds_dt_group) - - self.graph.save_state() - velocity_graph.save_state() - velocity_label.save_state() - def steep_slope(t): - return 100*smooth(t/10., inflection = 25) - def sin_wiggle(t): - return (10/(2*np.pi/10.))*(np.sin(2*np.pi*t/10.) + 2*np.pi*t/10.) - def double_smooth_graph_function(t): - if t < 5: - return 50*smooth(t/5.) - else: - return 50*(1+smooth((t-5)/5.)) - graph_funcs = [ - steep_slope, - sin_wiggle, - double_smooth_graph_function, - ] - for graph_func in graph_funcs: - new_graph = self.graph_function( - graph_func, - color = DISTANCE_COLOR, - is_main_graph = False - ) - self.remove(new_graph) - new_velocity_graph = self.get_derivative_graph( - graph = new_graph, - ) - - self.play(Transform(self.graph, new_graph)) - self.play(Transform(velocity_graph, new_velocity_graph)) - self.wait(2) - self.play(self.graph.restore) - self.play( - velocity_graph.restore, - velocity_label.restore, - ) - - #Pause and reflect - randy = Randolph() - randy.to_corner(DOWN+LEFT).shift(2*RIGHT) - randy.look_at(frac_line) - - self.play(FadeIn(randy)) - self.play(randy.change_mode, "pondering") - self.wait() - self.play(Blink(randy)) - self.play(randy.change_mode, "thinking") - self.wait() - self.play(Blink(randy)) - self.wait() - -class DefineTrueDerivative(Scene): - def construct(self): - title = TextMobject("The true derivative") - title.to_edge(UP) - - lhs = TexMobject("\\frac{ds}{dt}(t) = ") - VGroup(*lhs[:2]).set_color(DISTANCE_COLOR) - VGroup(*lhs[3:5]).set_color(TIME_COLOR) - lhs.shift(3*LEFT+UP) - - dt_rhs = self.get_fraction("dt") - numerical_rhs_list = [ - self.get_fraction("0.%s1"%("0"*x)) - for x in range(7) - ] - for rhs in [dt_rhs] + numerical_rhs_list: - rhs.next_to(lhs, RIGHT) - - brace, dt_to_zero = self.get_brace_and_text(dt_rhs) - - self.add(lhs, dt_rhs) - self.play(Write(title)) - self.wait() - dt_rhs.save_state() - for num_rhs in numerical_rhs_list: - self.play(Transform(dt_rhs, num_rhs)) - self.wait() - self.play(dt_rhs.restore) - self.play( - GrowFromCenter(brace), - Write(dt_to_zero) - ) - self.wait() - - def get_fraction(self, dt_string): - tex_mob = TexMobject( - "\\frac{s(t + %s) - s(t)}{%s}"%(dt_string, dt_string) - ) - part_lengths = [ - 0, - len("s(t+"), - 1,#1 and -1 below are purely for transformation quirks - len(dt_string)-1, - len(")-s(t)_"),#Underscore represents frac_line - 1, - len(dt_string)-1, - ] - pl_cumsum = np.cumsum(part_lengths) - result = VGroup(*[ - VGroup(*tex_mob[i1:i2]) - for i1, i2 in zip(pl_cumsum, pl_cumsum[1:]) - ]) - VGroup(*result[1:3]+result[4:6]).set_color(TIME_COLOR) - return result - - def get_brace_and_text(self, deriv_frac): - brace = Brace(VGroup(deriv_frac), DOWN) - dt_to_zero = brace.get_text("$dt \\to 0$") - VGroup(*dt_to_zero[:2]).set_color(TIME_COLOR) - return brace, dt_to_zero - -class SecantLineToTangentLine(GraphCarTrajectory, DefineTrueDerivative): - CONFIG = { - "start_time" : 6, - "end_time" : 2, - "alt_end_time" : 10, - "start_dt" : 2, - "end_dt" : 0.01, - "secant_line_length" : 10, - - } - def construct(self): - self.setup_axes(animate = False) - self.remove(self.y_axis_label_mob, self.x_axis_label_mob) - self.add_derivative_definition(self.y_axis_label_mob) - self.add_graph() - self.draw_axes() - self.show_tangent_line() - self.best_constant_approximation_around_a_point() - - def get_ds_dt_group(self, dt, animate = False): - points = [ - self.input_to_graph_point(time, self.graph) - for time in (self.curr_time, self.curr_time+dt) - ] - dots = list(map(Dot, points)) - for dot in dots: - dot.scale_in_place(0.5) - secant_line = Line(*points) - secant_line.set_color(VELOCITY_COLOR) - secant_line.scale_in_place( - self.secant_line_length/secant_line.get_length() - ) - - interim_point = points[1][0]*RIGHT + points[0][1]*UP - dt_line = Line(points[0], interim_point, color = TIME_COLOR) - ds_line = Line(interim_point, points[1], color = DISTANCE_COLOR) - dt = TexMobject("dt") - dt.set_color(TIME_COLOR) - if dt.get_width() > dt_line.get_width(): - dt.scale( - dt_line.get_width()/dt.get_width(), - about_point = dt.get_top() - ) - dt.next_to(dt_line, DOWN, buff = SMALL_BUFF) - ds = TexMobject("ds") - ds.set_color(DISTANCE_COLOR) - if ds.get_height() > ds_line.get_height(): - ds.scale( - ds_line.get_height()/ds.get_height(), - about_point = ds.get_left() - ) - ds.next_to(ds_line, RIGHT, buff = SMALL_BUFF) - - group = VGroup( - secant_line, - ds_line, dt_line, - ds, dt, - *dots - ) - if animate: - self.play( - ShowCreation(dt_line), - Write(dt), - ShowCreation(dots[0]), - ) - self.play( - ShowCreation(ds_line), - Write(ds), - ShowCreation(dots[1]), - ) - self.play( - ShowCreation(secant_line), - Animation(VGroup(*dots)) - ) - return group - - def add_graph(self): - def double_smooth_graph_function(t): - if t < 5: - return 50*smooth(t/5.) - else: - return 50*(1+smooth((t-5)/5.)) - self.graph = self.get_graph(double_smooth_graph_function) - self.graph_label = self.get_graph_label( - self.graph, "s(t)", - x_val = self.x_max, - direction = DOWN+RIGHT, - buff = SMALL_BUFF, - ) - - def add_derivative_definition(self, target_upper_left): - deriv_frac = self.get_fraction("dt") - lhs = TexMobject("\\frac{ds}{dt}(t)=") - VGroup(*lhs[:2]).set_color(DISTANCE_COLOR) - VGroup(*lhs[3:5]).set_color(TIME_COLOR) - lhs.next_to(deriv_frac, LEFT) - brace, text = self.get_brace_and_text(deriv_frac) - deriv_def = VGroup(lhs, deriv_frac, brace, text) - deriv_word = TextMobject("Derivative") - deriv_word.next_to(deriv_def, UP, buff = MED_LARGE_BUFF) - deriv_def.add(deriv_word) - rect = Rectangle(color = WHITE) - rect.replace(deriv_def, stretch = True) - rect.scale_in_place(1.2) - deriv_def.add(rect) - deriv_def.scale(0.7) - deriv_def.move_to(target_upper_left, UP+LEFT) - self.add(deriv_def) - return deriv_def - - def draw_axes(self): - self.x_axis.remove(self.x_axis_label_mob) - self.y_axis.remove(self.y_axis_label_mob) - self.play(Write( - VGroup( - self.x_axis, self.y_axis, - self.graph, self.graph_label - ), - run_time = 4 - )) - self.wait() - - def show_tangent_line(self): - self.curr_time = self.start_time - - ds_dt_group = self.get_ds_dt_group(2, animate = True) - self.wait() - def update_ds_dt_group(ds_dt_group, alpha): - new_dt = interpolate(self.start_dt, self.end_dt, alpha) - new_group = self.get_ds_dt_group(new_dt) - Transform(ds_dt_group, new_group).update(1) - self.play( - UpdateFromAlphaFunc(ds_dt_group, update_ds_dt_group), - run_time = 15 - ) - self.wait() - def update_as_tangent_line(ds_dt_group, alpha): - self.curr_time = interpolate(self.start_time, self.end_time, alpha) - new_group = self.get_ds_dt_group(self.end_dt) - Transform(ds_dt_group, new_group).update(1) - self.play( - UpdateFromAlphaFunc(ds_dt_group, update_as_tangent_line), - run_time = 8, - rate_func = there_and_back - ) - self.wait() - what_dt_is_not_text = self.what_this_is_not_saying() - self.wait() - self.play( - UpdateFromAlphaFunc(ds_dt_group, update_ds_dt_group), - run_time = 8, - rate_func = lambda t : 1-there_and_back(t) - ) - self.wait() - self.play(FadeOut(what_dt_is_not_text)) - - v_line = self.get_vertical_line_to_graph( - self.curr_time, - self.graph, - line_class = Line, - line_kwargs = { - "color" : MAROON_B, - "stroke_width" : 3 - } - ) - def v_line_update(v_line): - v_line.put_start_and_end_on( - self.coords_to_point(self.curr_time, 0), - self.input_to_graph_point(self.curr_time, self.graph), - ) - return v_line - self.play(ShowCreation(v_line)) - self.wait() - - original_end_time = self.end_time - for end_time in self.alt_end_time, original_end_time, self.start_time: - self.end_time = end_time - self.play( - UpdateFromAlphaFunc(ds_dt_group, update_as_tangent_line), - UpdateFromFunc(v_line, v_line_update), - run_time = abs(self.curr_time-self.end_time), - ) - self.start_time = end_time - self.play(FadeOut(v_line)) - - def what_this_is_not_saying(self): - phrases = [ - TextMobject( - "$dt$", "is", "not", s - ) - for s in ("``infinitely small''", "0") - ] - for phrase in phrases: - phrase[0].set_color(TIME_COLOR) - phrase[2].set_color(RED) - phrases[0].shift(DOWN+2*RIGHT) - phrases[1].next_to(phrases[0], DOWN, aligned_edge = LEFT) - - for phrase in phrases: - self.play(Write(phrase)) - return VGroup(*phrases) - - def best_constant_approximation_around_a_point(self): - words = TextMobject(""" - Best constant - approximation - around a point - """) - words.next_to(self.x_axis, UP, aligned_edge = RIGHT) - circle = Circle( - radius = 0.25, - color = WHITE - ).shift(self.input_to_graph_point(self.curr_time)) - - self.play(Write(words)) - self.play(ShowCreation(circle)) - self.wait() - -class UseOfDImpliesApproaching(TeacherStudentsScene): - def construct(self): - statement = TextMobject(""" - Using ``$d$'' - announces that - $dt \\to 0$ - """) - VGroup(*statement[-4:-2]).set_color(TIME_COLOR) - self.teacher_says(statement) - self.change_student_modes(*["pondering"]*3) - self.wait(4) - -class LeadIntoASpecificExample(TeacherStudentsScene, SecantLineToTangentLine): - def setup(self): - TeacherStudentsScene.setup(self) - - def construct(self): - dot = Dot() #Just to coordinate derivative definition - dot.to_corner(UP+LEFT, buff = SMALL_BUFF) - deriv_def = self.add_derivative_definition(dot) - self.remove(deriv_def) - - self.teacher_says("An example \\\\ should help.") - self.wait() - self.play( - Write(deriv_def), - *it.chain(*[ - [pi.change_mode, "thinking", pi.look_at, dot] - for pi in self.get_students() - ]) - ) - self.random_blink(3) - # self.teacher_says( - # """ - # The idea of - # ``approaching'' - # actually makes - # things easier - # """, - # height = 3, - # target_mode = "hooray" - # ) - # self.wait(2) - -class TCubedExample(SecantLineToTangentLine): - CONFIG = { - "y_axis_label" : "Distance", - "y_min" : 0, - "y_max" : 16, - "y_tick_frequency" : 1, - "y_labeled_nums" : list(range(0, 17, 2)), - "x_min" : 0, - "x_max" : 4, - "x_labeled_nums" : list(range(1, 5)), - "graph_origin" : 2.5*DOWN + 6*LEFT, - "start_time" : 2, - "end_time" : 0.5, - "start_dt" : 0.25, - "end_dt" : 0.001, - "secant_line_length" : 0.01, - } - def construct(self): - self.draw_graph() - self.show_vertical_lines() - self.bear_with_me() - self.add_ds_dt_group() - self.brace_for_details() - self.show_expansion() - self.react_to_simplicity() - - def draw_graph(self): - self.setup_axes(animate = False) - self.x_axis_label_mob.shift(0.5*DOWN) - # self.y_axis_label_mob.next_to(self.y_axis, UP) - graph = self.graph_function(lambda t : t**3, animate = True) - self.label_graph( - graph, - label = "s(t) = t^3", - proportion = 0.62, - direction = LEFT, - buff = SMALL_BUFF - ) - self.wait() - - def show_vertical_lines(self): - for t in 1, 2: - v_line = self.get_vertical_line_to_graph( - t, line_kwargs = {"color" : WHITE} - ) - brace = Brace(v_line, RIGHT) - text = TexMobject("%d^3 = %d"%(t, t**3)) - text.next_to(brace, RIGHT) - text.shift(0.2*UP) - group = VGroup(v_line, brace, text) - if t == 1: - self.play(ShowCreation(v_line)) - self.play( - GrowFromCenter(brace), - Write(text) - ) - last_group = group - else: - self.play(Transform(last_group, group)) - self.wait() - self.play(FadeOut(last_group)) - - def bear_with_me(self): - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - - self.play(FadeIn(morty)) - self.play(PiCreatureSays( - morty, "Bear with \\\\ me here", - target_mode = "sassy" - )) - self.play(Blink(morty)) - self.wait() - self.play(*list(map( - FadeOut, - [morty, morty.bubble, morty.bubble.content] - ))) - - def add_ds_dt_group(self): - self.curr_time = self.start_time - self.curr_dt = self.start_dt - ds_dt_group = self.get_ds_dt_group(dt = self.start_dt) - v_lines = self.get_vertical_lines() - - lhs = TexMobject("\\frac{ds}{dt}(2) = ") - lhs.next_to(ds_dt_group, UP+RIGHT, buff = MED_LARGE_BUFF) - ds = VGroup(*lhs[:2]) - dt = VGroup(*lhs[3:5]) - ds.set_color(DISTANCE_COLOR) - dt.set_color(TIME_COLOR) - ds.target, dt.target = ds_dt_group[3:5] - for mob in ds, dt: - mob.save_state() - mob.move_to(mob.target) - - nonzero_size = TextMobject("Nonzero size...for now") - nonzero_size.set_color(TIME_COLOR) - nonzero_size.next_to(dt, DOWN+2*RIGHT, buff = LARGE_BUFF) - arrow = Arrow(nonzero_size, dt) - - rhs = TexMobject( - "\\frac{s(2+dt) - s(2)}{dt}" - ) - rhs.next_to(lhs[-1]) - VGroup(*rhs[4:6]).set_color(TIME_COLOR) - VGroup(*rhs[-2:]).set_color(TIME_COLOR) - numerator = VGroup(*rhs[:-3]) - non_numerator = VGroup(*rhs[-3:]) - numerator_non_minus = VGroup(*numerator) - numerator_non_minus.remove(rhs[7]) - s_pair = rhs[0], rhs[8] - lp_pair = rhs[6], rhs[11] - for s, lp in zip(s_pair, lp_pair): - s.target = TexMobject("3").scale(0.7) - s.target.move_to(lp.get_corner(UP+RIGHT), LEFT) - - - - self.play(Write(ds_dt_group, run_time = 2)) - self.play( - FadeIn(lhs), - *[mob.restore for mob in (ds, dt)] - ) - self.play(ShowCreation(v_lines[0])) - self.wait() - self.play( - ShowCreation(arrow), - Write(nonzero_size), - ) - self.wait(2) - self.play(*list(map(FadeOut, [arrow, nonzero_size]))) - self.play(Write(numerator)) - self.play(ShowCreation(v_lines[1])) - self.wait() - self.play( - v_lines[0].set_color, YELLOW, - rate_func = there_and_back - ) - self.wait() - self.play(Write(non_numerator)) - self.wait(2) - self.play( - *list(map(MoveToTarget, s_pair)), - **{ - "path_arc" : -np.pi/2 - } - ) - self.play(numerator_non_minus.shift, 0.2*LEFT) - self.wait() - - self.vertical_lines = v_lines - self.ds_dt_group = ds_dt_group - self.lhs = lhs - self.rhs = rhs - - def get_vertical_lines(self): - return VGroup(*[ - self.get_vertical_line_to_graph( - time, - line_class = DashedLine, - line_kwargs = { - "color" : WHITE, - "dash_length" : 0.05, - } - ) - for time in (self.start_time, self.start_time+self.start_dt) - ]) - - def brace_for_details(self): - morty = Mortimer() - morty.next_to(self.rhs, DOWN, buff = LARGE_BUFF) - - self.play(FadeIn(morty)) - self.play( - morty.change_mode, "hooray", - morty.look_at, self.rhs - ) - self.play(Blink(morty)) - self.wait() - self.play( - morty.change_mode, "sassy", - morty.look, OUT - ) - self.play(Blink(morty)) - self.play(morty.change_mode, "pondering") - self.wait() - self.play(FadeOut(morty)) - - def show_expansion(self): - expression = TexMobject(""" - \\frac{ - 2^3 + - 3 (2)^2 dt - + 3 (2)(dt)^2 + - (dt)^3 - - 2^3 - }{dt} - """) - expression.set_width( - VGroup(self.lhs, self.rhs).get_width() - ) - expression.next_to( - self.lhs, DOWN, - aligned_edge = LEFT, - buff = LARGE_BUFF - ) - term_lens = [ - len("23+"), - len("3(2)2dt"), - len("+3(2)(dt)2+"), - len("(dt)3"), - len("-23"), - len("_"),#frac bar - len("dt"), - ] - terms = [ - VGroup(*expression[i1:i2]) - for i1, i2 in zip( - [0]+list(np.cumsum(term_lens)), - np.cumsum(term_lens) - ) - ] - - dts = [ - VGroup(*terms[1][-2:]), - VGroup(*terms[2][6:8]), - VGroup(*terms[3][1:3]), - terms[-1] - ] - VGroup(*dts).set_color(TIME_COLOR) - - two_cubed_terms = terms[0], terms[4] - - for term in terms: - self.play(FadeIn(term)) - self.wait() - - #Cancel out two_cubed terms - self.play(*it.chain(*[ - [ - tc.scale, 1.3, tc.get_corner(vect), - tc.set_color, RED - ] - for tc, vect in zip( - two_cubed_terms, - [DOWN+RIGHT, DOWN+LEFT] - ) - ])) - self.play(*list(map(FadeOut, two_cubed_terms))) - numerator = VGroup(*terms[1:4]) - self.play( - numerator.scale, 1.4, numerator.get_bottom(), - terms[-1].scale, 1.4, terms[-1].get_top() - ) - self.wait(2) - - #Cancel out dt - #This is all way too hacky... - faders = VGroup( - terms[-1], - VGroup(*terms[1][-2:]), #"3(2)^2 dt" - terms[2][-2], # "+3(2)(dt)2+" - terms[3][-1], # "(dt)3" - ) - new_exp = TexMobject("2").replace(faders[-1], dim_to_match = 1) - self.play( - faders.set_color, BLACK, - FadeIn(new_exp), - run_time = 2, - ) - self.wait() - terms[3].add(new_exp) - shift_val = 0.4*DOWN - self.play( - FadeOut(terms[-2]),#frac_line - terms[1].shift, shift_val + 0.45*RIGHT, - terms[2].shift, shift_val, - terms[3].shift, shift_val, - ) - - #Isolate dominant term - arrow = Arrow( - self.lhs[4].get_bottom(), terms[1][2].get_top(), - color = WHITE, - buff = MED_SMALL_BUFF - ) - brace = Brace(VGroup(terms[2][0], terms[3][-1]), DOWN) - brace_text = brace.get_text("Contains $dt$") - VGroup(*brace_text[-2:]).set_color(TIME_COLOR) - - self.play(ShowCreation(arrow)) - self.wait() - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait(2) - - #Shink dt - faders = VGroup(*terms[2:4] + [brace, brace_text]) - def ds_dt_group_update(group, alpha): - new_dt = interpolate(self.start_dt, self.end_dt, alpha) - new_group = self.get_ds_dt_group(new_dt) - Transform(group, new_group).update(1) - self.play(FadeOut(self.vertical_lines)) - self.secant_line_length = 10 - self.play(Transform( - self.ds_dt_group, - self.get_ds_dt_group(self.start_dt) - )) - self.play( - UpdateFromAlphaFunc(self.ds_dt_group, ds_dt_group_update), - faders.fade, 0.7, - run_time = 5 - ) - self.wait(2) - - #Show as derivative - deriv_term = VGroup(*terms[1][:5]) - deriv_term.generate_target() - lhs_copy = self.lhs.copy() - lhs_copy.generate_target() - lhs_copy.target.shift(3*DOWN) - #hack a little, hack a lot - deriv_term.target.scale(1.1) - deriv_term.target.next_to(lhs_copy.target) - deriv_term.target.shift(0.07*DOWN) - - self.play( - FadeOut(arrow), - FadeOut(faders), - MoveToTarget(deriv_term), - MoveToTarget(lhs_copy), - ) - arrow = Arrow( - self.rhs.get_bottom(), deriv_term.target.get_top(), - buff = MED_SMALL_BUFF, - color = WHITE - ) - approach_text = TextMobject("As $dt \\to 0$") - approach_text.next_to(arrow.get_center(), RIGHT) - VGroup(*approach_text[2:4]).set_color(TIME_COLOR) - self.play( - ShowCreation(arrow), - Write(approach_text) - ) - self.wait(2) - self.wait() - - #Ephasize slope - v_line = self.vertical_lines[0] - slope_text = TextMobject("Slope = $12$") - slope_text.set_color(VELOCITY_COLOR) - slope_text.next_to(v_line.get_end(), LEFT) - self.play(Write(slope_text)) - self.play( - self.ds_dt_group.rotate_in_place, np.pi/24, - rate_func = wiggle - ) - self.play(ShowCreation(v_line)) - self.wait() - self.play(FadeOut(v_line)) - self.play(FadeOut(slope_text)) - - #Generalize to more t - twos = [ - self.lhs[6], - self.rhs[2], - self.rhs[10], - lhs_copy[6], - deriv_term[2] - ] - for two in twos: - two.target = TexMobject("t") - two.target.replace(two, dim_to_match = 1) - self.play(*list(map(MoveToTarget, twos))) - def update_as_tangent_line(group, alpha): - self.curr_time = interpolate(self.start_time, self.end_time, alpha) - new_group = self.get_ds_dt_group(self.end_dt) - Transform(group, new_group).update(1) - self.play( - UpdateFromAlphaFunc(self.ds_dt_group, update_as_tangent_line), - run_time = 5, - rate_func = there_and_back - ) - self.wait(2) - - self.lhs_copy = lhs_copy - self.deriv_term = deriv_term - self.approach_text = approach_text - - def react_to_simplicity(self): - morty = Mortimer().flip().to_corner(DOWN+LEFT) - - self.play(FadeIn(morty)) - self.play(PiCreatureSays( - morty, "That's \\\\ beautiful!", - target_mode = "hooray" - )) - self.play(Blink(morty)) - self.play( - morty.change_mode, 'happy', - *list(map(FadeOut, [morty.bubble, morty.bubble.content])) - ) - - numerator = VGroup(*self.rhs[:12]) - denominator = VGroup(*self.rhs[-2:]) - for mob in numerator, denominator, self.approach_text, self.deriv_term: - mob.generate_target() - mob.target.scale_in_place(1.2) - mob.target.set_color(MAROON_B) - self.play( - MoveToTarget( - mob, rate_func = there_and_back, - run_time = 1.5, - ), - morty.look_at, mob - ) - self.wait() - self.play(Blink(morty)) - self.wait() - -class YouWouldntDoThisEveryTime(TeacherStudentsScene): - def construct(self): - self.change_student_modes( - "pleading", "guilty", "hesitant", - run_time = 0 - ) - self.teacher_says( - "You wouldn't do this \\\\ every time" - ) - self.change_student_modes(*["happy"]*3) - self.wait(2) - self.student_thinks( - "$\\frac{d(t^3)}{dt} = 3t^2$", - ) - self.wait(3) - - series = VideoSeries() - series.set_width(FRAME_WIDTH-1) - series.to_edge(UP) - this_video = series[1] - next_video = series[2] - this_video.save_state() - this_video.set_color(YELLOW) - self.play(FadeIn(series, lag_ratio = 0.5)) - self.play( - this_video.restore, - next_video.set_color, YELLOW, - next_video.shift, 0.5*DOWN - ) - self.wait(2) - -class ContrastConcreteDtWithLimit(Scene): - def construct(self): - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - self.add(v_line) - - l_title = TextMobject(""" - If $dt$ has a - specific size. - """) - VGroup(*l_title[2:4]).set_color(TIME_COLOR) - r_title = TexMobject("dt \\to 0") - VGroup(*r_title[:2]).set_color(TIME_COLOR) - for title, vect in (l_title, LEFT), (r_title, RIGHT): - title.to_edge(UP) - title.shift(FRAME_X_RADIUS*vect/2) - self.add(title) - - l_formula = TexMobject(""" - \\frac{d(t^3)}{dt} = - \\frac{ - t^3+ - 3t^2 \\, dt + - 3t \\, (dt)^2 + - (dt)^3 - - t^3 - }{dt} - """) - VGroup(*it.chain( - l_formula[6:8], - l_formula[15:17], - l_formula[21:23], - l_formula[27:29], - l_formula[35:37], - )).set_color(TIME_COLOR) - l_formula.set_width(FRAME_X_RADIUS-MED_LARGE_BUFF) - l_formula.to_edge(LEFT) - - l_brace = Brace(l_formula, DOWN) - l_text = l_brace.get_text("Messy") - l_text.set_color(RED) - - r_formula = TexMobject( - "\\frac{d(t^3)}{dt} = 3t^2" - ) - VGroup(*r_formula[6:8]).set_color(TIME_COLOR) - r_formula.shift(FRAME_X_RADIUS*RIGHT/2) - r_brace = Brace(r_formula, DOWN) - r_text = r_brace.get_text("Simple") - r_text.set_color(GREEN) - - triplets = [ - (l_formula, l_brace, l_text), - (r_formula, r_brace, r_text), - ] - for formula, brace, text in triplets: - self.play(Write(formula, run_time = 1)) - self.play( - GrowFromCenter(brace), - Write(text) - ) - self.wait(2) - -class TimeForAnActualParadox(TeacherStudentsScene): - def construct(self): - words = TextMobject("``Instantaneous rate of change''") - paradoxes = TextMobject("Paradoxes") - arrow = Arrow(ORIGIN, DOWN, buff = 0) - group = VGroup(words, arrow, paradoxes) - group.arrange(DOWN) - group.to_edge(UP) - - teacher = self.get_teacher() - self.play( - teacher.change_mode, "raise_right_hand", - teacher.look_at, words, - Write(words) - ) - self.play(*list(map(Write, [arrow, paradoxes]))) - self.play(*it.chain(*[ - [pi.change_mode, mode, pi.look_at, words] - for pi, mode in zip( - self.get_students(), - ["pondering", "happy", "hesitant"] - ) - ])) - self.wait(4) - -class ParadoxAtTEquals0(TCubedExample): - CONFIG = { - "tangent_line_length" : 20, - } - def construct(self): - self.draw_graph() - self.ask_question() - self.show_derivative_text() - self.show_tangent_line() - self.if_not_then_when() - self.single_out_question() - - def draw_graph(self): - self.setup_axes(animate = False) - self.x_axis_label_mob.set_fill(opacity = 0) - graph = self.graph_function(lambda t : t**3, animate = False) - graph_x_max = 3.0 - graph.pointwise_become_partial(graph, 0, graph_x_max/self.x_max) - - origin = self.coords_to_point(0, 0) - h_line = Line(LEFT, RIGHT, color = TIME_COLOR) - v_line = Line(UP, DOWN, color = DISTANCE_COLOR) - VGroup(h_line, v_line).set_stroke(width = 2) - - def h_line_update(h_line): - point = graph.point_from_proportion(1) - y_axis_point = origin[0]*RIGHT + point[1]*UP - h_line.put_start_and_end_on(y_axis_point, point) - return h_line - - def v_line_update(v_line): - point = graph.point_from_proportion(1) - x_axis_point = point[0]*RIGHT + origin[1]*UP - v_line.put_start_and_end_on(x_axis_point, point) - return v_line - - car = Car() - car.rotate(np.pi/2) - car.move_to(origin) - self.add(car) - #Should be 0, 1, but for some reason I don't know - #the car was lagging the graph. - car_target_point = self.coords_to_point(0, 1.15) - - self.play( - MoveCar( - car, car_target_point, - rate_func = lambda t : (t*graph_x_max)**3 - ), - ShowCreation(graph, rate_func=linear), - UpdateFromFunc(h_line, h_line_update), - UpdateFromFunc(v_line, v_line_update), - run_time = 5 - ) - self.play(*list(map(FadeOut, [h_line, v_line]))) - - self.label_graph( - graph, - label = "s(t) = t^3", - proportion = 0.8, - direction = RIGHT, - buff = SMALL_BUFF - ) - self.wait() - - self.car = car - - def ask_question(self): - question = TextMobject( - "At time $t=0$,", - "is \\\\ the car moving?" - ) - VGroup(*question[0][-4:-1]).set_color(RED) - question.next_to( - self.coords_to_point(0, 10), - RIGHT - ) - origin = self.coords_to_point(0, 0) - arrow = Arrow(question.get_bottom(), origin) - - self.play(Write(question[0], run_time = 1)) - self.play(MoveCar(self.car, origin)) - self.wait() - self.play(Write(question[1])) - self.play(ShowCreation(arrow)) - self.wait(2) - - self.question = question - - def show_derivative_text(self): - derivative = TexMobject( - "\\frac{ds}{dt}(t) = 3t^2", - "= 3(0)^2", - "= 0", - "\\frac{\\text{m}}{\\text{s}}", - ) - VGroup(*derivative[0][:2]).set_color(DISTANCE_COLOR) - VGroup(*derivative[0][3:5]).set_color(TIME_COLOR) - derivative[1][3].set_color(RED) - derivative[-1].scale_in_place(0.7) - derivative.to_edge(RIGHT, buff = LARGE_BUFF) - derivative.shift(2*UP) - - self.play(Write(derivative[0])) - self.wait() - self.play(FadeIn(derivative[1])) - self.play(*list(map(FadeIn, derivative[2:]))) - self.wait(2) - - self.derivative = derivative - - def show_tangent_line(self): - dot = Dot() - line = Line(ORIGIN, RIGHT, color = VELOCITY_COLOR) - line.scale(self.tangent_line_length) - - start_time = 2 - end_time = 0 - - def get_time_and_point(alpha): - time = interpolate(start_time, end_time, alpha) - point = self.input_to_graph_point(time) - return time, point - - def dot_update(dot, alpha): - dot.move_to(get_time_and_point(alpha)[1]) - - def line_update(line, alpha): - time, point = get_time_and_point(alpha) - line.rotate( - self.angle_of_tangent(time)-line.get_angle() - ) - line.move_to(point) - - dot_update(dot, 0) - line_update(line, 0) - self.play( - ShowCreation(line), - ShowCreation(dot) - ) - self.play( - UpdateFromAlphaFunc(line, line_update), - UpdateFromAlphaFunc(dot, dot_update), - run_time = 4 - ) - self.wait(2) - - self.tangent_line = line - - def if_not_then_when(self): - morty = Mortimer() - morty.scale(0.7) - morty.to_corner(DOWN+RIGHT) - - self.play(FadeIn(morty)) - self.play(PiCreatureSays( - morty, "If not at $t=0$, when?", - target_mode = "maybe" - )) - self.play(Blink(morty)) - self.play(MoveCar( - self.car, self.coords_to_point(0, 1), - rate_func = lambda t : (3*t)**3, - run_time = 5 - )) - self.play( - morty.change_mode, "pondering", - FadeOut(morty.bubble), - FadeOut(morty.bubble.content), - ) - self.play(MoveCar(self.car, self.coords_to_point(0, 0))) - self.play(Blink(morty)) - self.wait(2) - - self.morty = morty - - def single_out_question(self): - morty, question = self.morty, self.question - - #Shouldn't need this - morty.bubble.content.set_fill(opacity = 0) - morty.bubble.set_fill(opacity = 0) - morty.bubble.set_stroke(width = 0) - - change_word = VGroup(*question[1][-7:-1]) - moment_word = question[0] - - brace = Brace(VGroup(*self.derivative[1:])) - brace_text = brace.get_text("Best constant \\\\ approximation") - - self.remove(question, morty) - pre_everything = Mobject(*self.get_mobjects()) - everything = Mobject(*pre_everything.family_members_with_points()) - everything.save_state() - - self.play( - everything.fade, 0.8, - question.center, - morty.change_mode, "confused", - morty.look_at, ORIGIN - ) - self.play(Blink(morty)) - for word in change_word, moment_word: - self.play( - word.scale_in_place, 1.2, - word.set_color, YELLOW, - rate_func = there_and_back, - run_time = 1.5 - ) - self.wait(2) - self.play( - everything.restore, - FadeOut(question), - morty.change_mode, "raise_right_hand", - morty.look_at, self.derivative - ) - self.play( - GrowFromCenter(brace), - FadeIn(brace_text) - ) - self.wait() - self.play( - self.tangent_line.rotate_in_place, np.pi/24, - rate_func = wiggle, - run_time = 1 - ) - self.play(Blink(morty)) - self.wait() - -class TinyMovement(ZoomedScene): - CONFIG = { - "distance" : 0.05, - "distance_label" : "(0.1)^3 = 0.001", - "time_label" : "0.1", - } - def construct(self): - self.activate_zooming() - self.show_initial_motion() - self.show_ratios() - - def show_initial_motion(self): - car = Car() - car.move_to(ORIGIN) - car_points = car.get_all_points() - lowest_to_highest_indices = np.argsort(car_points[:,1]) - wheel_point = car_points[lowest_to_highest_indices[2]] - target_wheel_point = wheel_point+self.distance*RIGHT - - dots = VGroup(*[ - Dot(point, radius = self.distance/10) - for point in (wheel_point, target_wheel_point) - ]) - brace = Brace(Line(ORIGIN, RIGHT)) - distance_label = TexMobject(self.distance_label) - distance_label.next_to(brace, DOWN) - distance_label.set_color(DISTANCE_COLOR) - brace.add(distance_label) - brace.scale(self.distance) - brace.next_to(dots, DOWN, buff = self.distance/5) - - zoom_rect = self.little_rectangle - zoom_rect.scale(2) - zoom_rect.move_to(wheel_point) - - time_label = TextMobject("Time $t = $") - time_label.next_to(car, UP, buff = LARGE_BUFF) - start_time = TexMobject("0") - end_time = TexMobject(self.time_label) - for time in start_time, end_time: - time.set_color(TIME_COLOR) - time.next_to(time_label, RIGHT) - - self.add(car, time_label, start_time) - self.play( - zoom_rect.scale_in_place, - 10*self.distance / zoom_rect.get_width() - ) - self.play(ShowCreation(dots[0])) - self.play(Transform(start_time, end_time)) - self.play(MoveCar(car, self.distance*RIGHT)) - self.play(ShowCreation(dots[1])) - self.play(Write(brace, run_time = 1)) - self.play( - zoom_rect.scale, 0.5, - zoom_rect.move_to, brace - ) - self.wait() - - def show_ratios(self): - ratios = [ - self.get_ratio(n) - for n in range(1, 5) - ] - ratio = ratios[0] - self.play(FadeIn(ratio)) - self.wait(2) - for new_ratio in ratios[1:]: - self.play(Transform(ratio, new_ratio)) - self.wait() - - def get_ratio(self, power = 1): - dt = "0.%s1"%("0"*(power-1)) - ds_dt = "0.%s1"%("0"*(2*power-1)) - expression = TexMobject(""" - \\frac{(%s)^3 \\text{ meters}}{%s \\text{ seconds}} - = %s \\frac{\\text{meters}}{\\text{second}} - """%(dt, dt, ds_dt)) - expression.next_to(ORIGIN, DOWN, buff = LARGE_BUFF) - lengths = [ - 0, - len("("), - len(dt), - len(")3meters_"), - len(dt), - len("seconds="), - len(ds_dt), - len("meters_second") - ] - result = VGroup(*[ - VGroup(*expression[i1:i2]) - for i1, i2 in zip( - np.cumsum(lengths), - np.cumsum(lengths)[1:], - ) - ]) - result[1].set_color(DISTANCE_COLOR) - result[3].set_color(TIME_COLOR) - result[5].set_color(VELOCITY_COLOR) - - return result - -class NextVideos(TeacherStudentsScene): - def construct(self): - series = VideoSeries() - series.set_width(FRAME_WIDTH - 1) - series.to_edge(UP) - series[1].set_color(YELLOW) - self.add(series) - - brace = Brace(VGroup(*series[2:6])) - brace_text = brace.get_text("More derivative stuffs") - - - self.play( - GrowFromCenter(brace), - self.get_teacher().change_mode, "raise_right_hand" - ) - self.play( - Write(brace_text), - *it.chain(*[ - [pi.look_at, brace] - for pi in self.get_students() - ]) - ) - self.wait(2) - self.change_student_modes(*["thinking"]*3) - self.wait(3) - -class Chapter2PatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Meshal Alshammari", - "Ali Yahya", - "CrypticSwarm ", - "Yu Jun", - "Shelby Doolittle", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Markus Persson", - "Dan Buchoff", - "Derek Dai", - "Joseph Cox", - "Luc Ritchie", - "Mark Govea", - "Guido Gambardella", - "Vecht", - "Jonathan Eppele", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Kirk Werklund", - "Ripta Pasay", - "Felipe Diniz", - ] - } - -class Promotion(PiCreatureScene): - CONFIG = { - "camera_class" : ThreeDCamera, - "seconds_to_blink" : 5, - } - def construct(self): - aops_logo = AoPSLogo() - aops_logo.next_to(self.pi_creature, UP+LEFT) - url = TextMobject( - "AoPS.com/", "3blue1brown", - arg_separator = "" - ) - url.to_corner(UP+LEFT) - url_rect = Rectangle(color = BLUE) - url_rect.replace( - url.get_part_by_tex("3blue1brown"), - stretch = True - ) - - url_rect.stretch_in_place(1.1, dim = 1) - - rect = Rectangle(height = 9, width = 16) - rect.set_height(4.5) - rect.next_to(url, DOWN) - rect.to_edge(LEFT) - mathy = Mathematician() - mathy.flip() - mathy.to_corner(DOWN+RIGHT) - morty = self.pi_creature - morty.save_state() - book_spot = mathy.get_corner(UP+LEFT) + UP+LEFT - mathy.get_center = mathy.get_top - - self.play( - self.pi_creature.change_mode, "raise_right_hand", - *[ - DrawBorderThenFill( - submob, - run_time = 3, - rate_func = squish_rate_func(double_smooth, a, a+0.5) - ) - for submob, a in zip(aops_logo, np.linspace(0, 0.5, len(aops_logo))) - ] - ) - self.play(Write(url)) - self.play( - morty.change_mode, "plain", - morty.flip, - morty.scale, 0.7, - morty.next_to, mathy, LEFT, LARGE_BUFF, - morty.to_edge, DOWN, - FadeIn(mathy), - ) - self.play( - PiCreatureSays( - mathy, "", - bubble_kwargs = {"width" : 5}, - look_at_arg = morty.eyes, - ), - aops_logo.shift, 1.5*UP + 0.5*RIGHT - ) - self.change_mode("happy") - self.wait(2) - self.play(Blink(mathy)) - self.wait() - self.play( - RemovePiCreatureBubble( - mathy, target_mode = "happy" - ), - aops_logo.to_corner, UP+RIGHT, - aops_logo.shift, MED_SMALL_BUFF*DOWN, - ) - self.play( - mathy.look_at, morty.eyes, - morty.look_at, mathy.eyes, - ) - self.wait(2) - self.play( - Animation(VectorizedPoint(book_spot)), - mathy.change, "raise_right_hand", book_spot, - morty.change, "pondering", - ) - self.wait(3) - self.play(Blink(mathy)) - self.wait(7) - self.play( - ShowCreation(rect), - morty.restore, - morty.change, "happy", rect, - FadeOut(mathy), - ) - self.wait(10) - self.play(ShowCreation(url_rect)) - self.play( - FadeOut(url_rect), - url.get_part_by_tex("3blue1brown").set_color, BLUE, - ) - self.wait(3) - -class Thumbnail(SecantLineToTangentLine): - def construct(self): - self.setup_axes(animate = False) - self.add_graph() - self.curr_time = 6 - ds_dt_group = self.get_ds_dt_group(1) - self.add(ds_dt_group) - self.remove(self.x_axis_label_mob) - self.remove(self.y_axis_label_mob) - VGroup(*self.get_mobjects()).fade(0.4) - - title = TextMobject("Derivative paradox") - title.set_width(FRAME_WIDTH-1) - title.to_edge(UP) - title.add_background_rectangle() - title.set_color_by_gradient(GREEN, YELLOW) - - randy = Randolph(mode = "confused") - randy.scale(1.7) - randy.to_corner(DOWN+LEFT) - randy.shift(RIGHT) - - deriv = TexMobject("\\frac{ds}{dt}(t)") - VGroup(*deriv[:2]).set_color(DISTANCE_COLOR) - VGroup(*deriv[3:5]).set_color(TIME_COLOR) - deriv.scale(3) - # deriv.next_to(randy, RIGHT, buff = 2) - deriv.to_edge(RIGHT, buff = LARGE_BUFF) - randy.look_at(deriv) - - self.add(title, randy, deriv) - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eoc/chapter3.py b/from_3b1b/old/eoc/chapter3.py deleted file mode 100644 index 736bf0dd..00000000 --- a/from_3b1b/old/eoc/chapter3.py +++ /dev/null @@ -1,2825 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eoc.chapter2 import DISTANCE_COLOR, TIME_COLOR, \ - VELOCITY_COLOR, Car, MoveCar - -OUTPUT_COLOR = DISTANCE_COLOR -INPUT_COLOR = TIME_COLOR -DERIVATIVE_COLOR = VELOCITY_COLOR - -class Chapter3OpeningQuote(OpeningQuote): - CONFIG = { - "quote" : [ - "You know, for a mathematician, he did not have \\\\ enough", - "imagination.", - "But he has become a poet and \\\\ now he is fine.", - ], - "highlighted_quote_terms" : { - "imagination." : BLUE, - }, - "author" : "David Hilbert" - } - -class PoseAbstractDerivative(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Given $f(x) = x^2 \\sin(x)$, \\\\ - compute $\\frac{df}{dx}(x)$ - """) - content_copy = self.teacher.bubble.content.copy() - self.change_student_modes("sad", "confused", "erm") - self.wait() - self.student_says( - "Why?", target_mode = "sassy", - added_anims = [ - content_copy.scale, 0.8, - content_copy.to_corner, UP+LEFT - ] - ) - self.play(self.teacher.change_mode, "pondering") - self.wait(2) - -class ContrastAbstractAndConcrete(Scene): - def construct(self): - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - l_title = TextMobject("Abstract functions") - l_title.shift(FRAME_X_RADIUS*LEFT/2) - l_title.to_edge(UP) - r_title = TextMobject("Applications") - r_title.shift(FRAME_X_RADIUS*RIGHT/2) - r_title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.shift((r_title.get_bottom()[1]-MED_SMALL_BUFF)*UP) - - functions = VGroup(*list(map(TexMobject, [ - "f(x) = 2x^2 - x^3", - "f(x) = \\sin(x)", - "f(x) = e^x", - "\\v_dots" - ]))) - functions.arrange( - DOWN, - aligned_edge = LEFT, - buff = LARGE_BUFF - ) - functions.shift(FRAME_X_RADIUS*LEFT/2) - functions[-1].shift(MED_LARGE_BUFF*RIGHT) - - self.add(l_title, r_title) - self.play(*list(map(ShowCreation, [h_line, v_line]))) - self.play(Write(functions)) - self.wait() - anims = [ - method(func_mob) - for func_mob, method in zip(functions, [ - self.get_car_anim, - self.get_spring_anim, - self.get_population_anim, - ]) - ] - for anim in anims: - self.play(FadeIn(anim.mobject)) - self.play(anim) - self.play(FadeOut(anim.mobject)) - - - def get_car_anim(self, alignement_mob): - car = Car() - point = 2*RIGHT + alignement_mob.get_bottom()[1]*UP - target_point = point + 5*RIGHT - car.move_to(point) - return MoveCar( - car, target_point, - run_time = 5, - ) - - def get_spring_anim(self, alignement_mob): - compact_spring, extended_spring = [ - ParametricFunction( - lambda t : (t/denom)*RIGHT+np.sin(t)*UP+np.cos(t)*OUT, - t_max = 12*np.pi, - ) - for denom in (12.0, 4.0) - ] - for spring in compact_spring, extended_spring: - spring.scale(0.5) - spring.rotate(np.pi/6, UP) - spring.set_color(GREY) - spring.next_to(ORIGIN, RIGHT) - spring.shift( - alignement_mob.get_center()[1]*UP + SMALL_BUFF*RIGHT \ - -spring.points[0] - ) - weight = Square( - side_length = 0.5, - stroke_width = 0, - fill_color = LIGHT_GREY, - fill_opacity = 1, - ) - weight.move_to(spring.points[-1]) - spring.add(weight) - - return Transform( - compact_spring, extended_spring, - rate_func = lambda t : 1+np.sin(6*np.pi*t), - run_time = 5 - ) - - def get_population_anim(self, alignement_mob): - colors = color_gradient([BLUE_B, BLUE_E], 12) - pis = VGroup(*[ - Randolph( - mode = "happy", - color = random.choice(colors) - ).shift( - 4*x*RIGHT + 4*y*UP + \ - 2*random.random()*RIGHT + \ - 2*random.random()*UP - ) - for x in range(20) - for y in range(10) - ]) - pis.set_height(3) - pis.center() - pis.to_edge(DOWN, buff = SMALL_BUFF) - pis.shift(FRAME_X_RADIUS*RIGHT/2.) - - anims = [] - for index, pi in enumerate(pis): - if index < 2: - anims.append(FadeIn(pi)) - continue - mom_index, dad_index = random.choice( - list(it.combinations(list(range(index)), 2)) - ) - pi.parents = VGroup(pis[mom_index], pis[dad_index]).copy() - pi.parents.set_fill(opacity = 0) - exp = 1 - while 2**exp < len(pis): - low_index = 2**exp - high_index = min(2**(exp+1), len(pis)) - these_pis = pis[low_index:high_index] - anims.append(Transform( - VGroup(*[pi.parents for pi in these_pis]), - VGroup(*[VGroup(pi, pi.copy()) for pi in these_pis]), - lag_ratio = 0.5, - run_time = 2, - )) - exp += 1 - - return Succession(*anims, rate_func=linear) - -class ApplicationNames(Scene): - def construct(self): - for name in "Velocity", "Oscillation", "Population growth": - mob = TextMobject(name) - mob.scale(2) - self.play(Write(mob)) - self.wait(2) - self.play(FadeOut(mob)) - -class ListOfRules(PiCreatureScene): - CONFIG = { - "use_morty" : False, - } - def construct(self): - rules = VGroup(*list(map(TexMobject, [ - "\\frac{d}{dx} x^n = nx^{n-1}", - "\\frac{d}{dx} \\sin(x) = \\cos(x)", - "\\frac{d}{dx} \\cos(x) = -\\sin(x)", - "\\frac{d}{dx} a^x = \\ln(a) a^x", - "\\vdots" - ]))) - rules.arrange( - DOWN, buff = MED_LARGE_BUFF, - aligned_edge = LEFT, - ) - rules[-1].shift(MED_LARGE_BUFF*RIGHT) - rules.set_height(FRAME_HEIGHT-1) - rules.next_to(self.pi_creature, RIGHT) - rules.to_edge(DOWN) - - self.play( - Write(rules), - self.pi_creature.change_mode, "pleading", - ) - self.change_mode("tired") - self.wait() - -class DerivativeOfXSquaredAsGraph(GraphScene, ZoomedScene, PiCreatureScene): - CONFIG = { - "start_x" : 2, - "big_x" : 3, - "dx" : 0.1, - "x_min" : -9, - "x_labeled_nums" : list(range(-8, 0, 2)) + list(range(2, 10, 2)), - "y_labeled_nums" : list(range(2, 12, 2)), - "little_rect_nudge" : 0.5*(1.5*UP+RIGHT), - "graph_origin" : 2.5*DOWN + LEFT, - "zoomed_canvas_corner" : UP+LEFT, - "zoomed_canvas_frame_shape" : (4, 4), - } - def construct(self): - self.draw_graph() - self.ask_about_df_dx() - self.show_differing_slopes() - self.mention_alternate_view() - - def draw_graph(self): - self.setup_axes(animate = True) - graph = self.get_graph(lambda x : x**2) - label = self.get_graph_label( - graph, "f(x) = x^2", - ) - self.play(ShowCreation(graph)) - self.play(Write(label)) - self.wait() - self.graph = graph - - def ask_about_df_dx(self): - ss_group = self.get_secant_slope_group( - self.start_x, self.graph, - dx = self.dx, - dx_label = "dx", - df_label = "df", - ) - secant_line = ss_group.secant_line - ss_group.remove(secant_line) - - v_line, nudged_v_line = [ - self.get_vertical_line_to_graph( - x, self.graph, - line_class = DashedLine, - color = RED, - dash_length = 0.025 - ) - for x in (self.start_x, self.start_x+self.dx) - ] - - df_dx = TexMobject("\\frac{df}{dx} ?") - VGroup(*df_dx[:2]).set_color(ss_group.df_line.get_color()) - VGroup(*df_dx[3:5]).set_color(ss_group.dx_line.get_color()) - df_dx.next_to( - self.input_to_graph_point(self.start_x, self.graph), - DOWN+RIGHT, - buff = MED_SMALL_BUFF - ) - - derivative_q = TextMobject("Derivative?") - derivative_q.next_to(self.pi_creature.get_corner(UP+LEFT), UP) - - - self.play( - Write(derivative_q, run_time = 1), - self.pi_creature.change_mode, "speaking" - ) - self.wait() - self.play( - FadeOut(derivative_q), - self.pi_creature.change_mode, "plain" - ) - self.play(ShowCreation(v_line)) - self.wait() - self.play(Transform(v_line.copy(), nudged_v_line)) - self.remove(self.get_mobjects_from_last_animation()[0]) - self.add(nudged_v_line) - self.wait() - self.activate_zooming() - self.little_rectangle.replace(self.big_rectangle) - self.play( - FadeIn(self.little_rectangle), - FadeIn(self.big_rectangle), - ) - self.play( - ApplyFunction( - lambda r : self.position_little_rectangle(r, ss_group), - self.little_rectangle - ), - self.pi_creature.change_mode, "pondering", - self.pi_creature.look_at, ss_group - ) - self.play( - ShowCreation(ss_group.dx_line), - Write(ss_group.dx_label), - ) - self.wait() - self.play( - ShowCreation(ss_group.df_line), - Write(ss_group.df_label), - ) - self.wait() - self.play(Write(df_dx)) - self.wait() - self.play(*list(map(FadeOut, [ - v_line, nudged_v_line, - ]))) - self.ss_group = ss_group - - def position_little_rectangle(self, rect, ss_group): - rect.set_width(3*self.dx) - rect.move_to( - ss_group.dx_line.get_left() - ) - rect.shift( - self.dx*self.little_rect_nudge - ) - return rect - - def show_differing_slopes(self): - ss_group = self.ss_group - def rect_update(rect): - self.position_little_rectangle(rect, ss_group) - - self.play( - ShowCreation(ss_group.secant_line), - self.pi_creature.change_mode, "thinking" - ) - ss_group.add(ss_group.secant_line) - self.wait() - for target_x in self.big_x, -self.dx/2, 1, 2: - self.animate_secant_slope_group_change( - ss_group, target_x = target_x, - added_anims = [ - UpdateFromFunc(self.little_rectangle, rect_update) - ] - ) - self.wait() - - def mention_alternate_view(self): - self.remove(self.pi_creature) - everything = VGroup(*self.get_mobjects()) - self.add(self.pi_creature) - self.disactivate_zooming() - self.play( - ApplyMethod( - everything.shift, FRAME_WIDTH*LEFT, - rate_func = lambda t : running_start(t, -0.1) - ), - self.pi_creature.change_mode, "happy" - ) - self.say("Let's try \\\\ another view.", target_mode = "speaking") - self.wait(2) - -class NudgeSideLengthOfSquare(PiCreatureScene): - CONFIG = { - "square_width" : 3, - "alt_square_width" : 5, - "dx" : 0.25, - "alt_dx" : 0.01, - "square_color" : GREEN, - "square_fill_opacity" : 0.75, - "three_color" : GREEN, - "dx_color" : BLUE_B, - "is_recursing_on_dx" : False, - "is_recursing_on_square_width" : False, - } - def construct(self): - ApplyMethod(self.pi_creature.change_mode, "speaking").update(1) - self.add_function_label() - self.introduce_square() - self.increase_area() - self.write_df_equation() - self.set_color_shapes() - self.examine_thin_rectangles() - self.examine_tiny_square() - self.show_smaller_dx() - self.rule_of_thumb() - self.write_out_derivative() - - def add_function_label(self): - label = TexMobject("f(x) = x^2") - label.next_to(ORIGIN, RIGHT, buff = (self.square_width-3)/2.) - label.to_edge(UP) - self.add(label) - self.function_label = label - - def introduce_square(self): - square = Square( - side_length = self.square_width, - stroke_width = 0, - fill_opacity = self.square_fill_opacity, - fill_color = self.square_color, - ) - square.to_corner(UP+LEFT, buff = LARGE_BUFF) - x_squared = TexMobject("x^2") - x_squared.move_to(square) - - braces = VGroup() - for vect in RIGHT, DOWN: - brace = Brace(square, vect) - text = brace.get_text("$x$") - brace.add(text) - braces.add(brace) - - self.play( - DrawBorderThenFill(square), - self.pi_creature.change_mode, "plain" - ) - self.play(*list(map(GrowFromCenter, braces))) - self.play(Write(x_squared)) - self.change_mode("pondering") - self.wait() - - self.square = square - self.side_braces = braces - - def increase_area(self): - color_kwargs = { - "fill_color" : YELLOW, - "fill_opacity" : self.square_fill_opacity, - "stroke_width" : 0, - } - right_rect = Rectangle( - width = self.dx, - height = self.square_width, - **color_kwargs - ) - bottom_rect = right_rect.copy().rotate(-np.pi/2) - right_rect.next_to(self.square, RIGHT, buff = 0) - bottom_rect.next_to(self.square, DOWN, buff = 0) - corner_square = Square( - side_length = self.dx, - **color_kwargs - ) - corner_square.next_to(self.square, DOWN+RIGHT, buff = 0) - - right_line = Line( - self.square.get_corner(UP+RIGHT), - self.square.get_corner(DOWN+RIGHT), - stroke_width = 0 - ) - bottom_line = Line( - self.square.get_corner(DOWN+RIGHT), - self.square.get_corner(DOWN+LEFT), - stroke_width = 0 - ) - corner_point = VectorizedPoint( - self.square.get_corner(DOWN+RIGHT) - ) - - little_braces = VGroup() - for vect in RIGHT, DOWN: - brace = Brace( - corner_square, vect, - buff = SMALL_BUFF, - ) - text = brace.get_text("$dx$", buff = SMALL_BUFF) - text.set_color(self.dx_color) - brace.add(text) - little_braces.add(brace) - - right_brace, bottom_brace = self.side_braces - self.play( - Transform(right_line, right_rect), - Transform(bottom_line, bottom_rect), - Transform(corner_point, corner_square), - right_brace.next_to, right_rect, RIGHT, SMALL_BUFF, - bottom_brace.next_to, bottom_rect, DOWN, SMALL_BUFF, - ) - self.remove(corner_point, bottom_line, right_line) - self.add(corner_square, bottom_rect, right_rect) - self.play(*list(map(GrowFromCenter, little_braces))) - self.wait() - self.play(*it.chain(*[ - [mob.shift, vect*SMALL_BUFF] - for mob, vect in [ - (right_rect, RIGHT), - (bottom_rect, DOWN), - (corner_square, DOWN+RIGHT), - (right_brace, RIGHT), - (bottom_brace, DOWN), - (little_braces, DOWN+RIGHT) - ] - ])) - self.change_mode("thinking") - self.wait() - self.right_rect = right_rect - self.bottom_rect = bottom_rect - self.corner_square = corner_square - self.little_braces = little_braces - - def write_df_equation(self): - right_rect = self.right_rect - bottom_rect = self.bottom_rect - corner_square = self.corner_square - - df_equation = VGroup( - TexMobject("df").set_color(right_rect.get_color()), - TexMobject("="), - right_rect.copy(), - TextMobject("+"), - right_rect.copy(), - TexMobject("+"), - corner_square.copy() - ) - df_equation.arrange() - df_equation.next_to( - self.function_label, DOWN, - aligned_edge = LEFT, - buff = SMALL_BUFF - ) - df, equals, r1, plus1, r2, plus2, s = df_equation - - pairs = [ - (df, self.function_label[0]), - (r1, right_rect), - (r2, bottom_rect), - (s, corner_square), - ] - for mover, origin in pairs: - mover.save_state() - Transform(mover, origin).update(1) - self.play(df.restore) - self.wait() - self.play( - *[ - mob.restore - for mob in (r1, r2, s) - ]+[ - Write(symbol) - for symbol in (equals, plus1, plus2) - ], - run_time = 2 - ) - self.change_mode("happy") - self.wait() - - self.df_equation = df_equation - - def set_color_shapes(self): - df, equals, r1, plus1, r2, plus2, s = self.df_equation - - tups = [ - (self.right_rect, self.bottom_rect, r1, r2), - (self.corner_square, s) - ] - for tup in tups: - self.play( - *it.chain(*[ - [m.scale_in_place, 1.2, m.set_color, RED] - for m in tup - ]), - rate_func = there_and_back - ) - self.wait() - - def examine_thin_rectangles(self): - df, equals, r1, plus1, r2, plus2, s = self.df_equation - - rects = VGroup(r1, r2) - thin_rect_brace = Brace(rects, DOWN) - text = thin_rect_brace.get_text("$2x \\, dx$") - VGroup(*text[-2:]).set_color(self.dx_color) - text.save_state() - alt_text = thin_rect_brace.get_text("$2(3)(0.01)$") - alt_text[2].set_color(self.three_color) - VGroup(*alt_text[-5:-1]).set_color(self.dx_color) - - example_value = TexMobject("=0.06") - example_value.next_to(alt_text, DOWN) - - self.play(GrowFromCenter(thin_rect_brace)) - self.play( - Write(text), - self.pi_creature.change_mode, "pondering" - ) - self.wait() - - xs = VGroup(*[ - brace[-1] - for brace in self.side_braces - ]) - dxs = VGroup(*[ - brace[-1] - for brace in self.little_braces - ]) - for group, tex, color in (xs, "3", self.three_color), (dxs, "0.01", self.dx_color): - group.save_state() - group.generate_target() - for submob in group.target: - number = TexMobject(tex) - number.set_color(color) - number.move_to(submob, LEFT) - Transform(submob, number).update(1) - self.play(MoveToTarget(xs)) - self.play(MoveToTarget(dxs)) - self.wait() - self.play(Transform(text, alt_text)) - self.wait() - self.play(Write(example_value)) - self.wait() - self.play( - FadeOut(example_value), - *[ - mob.restore - for mob in (xs, dxs, text) - ] - ) - self.remove(text) - text.restore() - self.add(text) - - self.wait() - self.dxs = dxs - self.thin_rect_brace = thin_rect_brace - self.thin_rect_area = text - - def examine_tiny_square(self): - text = TexMobject("dx^2") - VGroup(*text[:2]).set_color(self.dx_color) - text.next_to(self.df_equation[-1], UP) - text.save_state() - alt_text = TextMobject("0.0001") - alt_text.move_to(text) - - self.play(Write(text)) - self.change_mode("surprised") - self.wait() - self.play( - MoveToTarget(self.dxs), - self.pi_creature.change_mode, "plain" - ) - for submob in self.dxs.target: - number = TexMobject("0.01") - number.set_color(self.dx_color) - number.move_to(submob, LEFT) - Transform(submob, number).update(1) - self.play(MoveToTarget(self.dxs)) - self.play( - Transform(text, alt_text), - self.pi_creature.change_mode, "raise_right_hand" - ) - self.wait(2) - self.play(*[ - mob.restore - for mob in (self.dxs, text) - ] + [ - self.pi_creature.change_mode, "erm" - ]) - self.dx_squared = text - - def show_smaller_dx(self): - self.mobjects_at_start_of_show_smaller_dx = [ - mob.copy() for mob in self.get_mobjects() - ] - if self.is_recursing_on_dx: - return - - alt_scene = self.__class__( - skip_animations = True, - dx = self.alt_dx, - is_recursing_on_dx = True - ) - for mob in self.get_mobjects(): - mob.save_state() - self.play(*[ - Transform(*pair) - for pair in zip( - self.get_mobjects(), - alt_scene.mobjects_at_start_of_show_smaller_dx, - ) - ]) - self.wait() - self.play(*[ - mob.restore - for mob in self.get_mobjects() - ]) - self.change_mode("happy") - self.wait() - - def rule_of_thumb(self): - circle = Circle(color = RED) - dx_squared_group = VGroup(self.dx_squared, self.df_equation[-1]) - circle.replace(dx_squared_group, stretch = True) - dx_squared_group.add(self.df_equation[-2]) - circle.scale_in_place(1.5) - safe_to_ignore = TextMobject("Safe to ignore") - safe_to_ignore.next_to(circle, DOWN, aligned_edge = LEFT) - safe_to_ignore.set_color(circle.get_color()) - - self.play(ShowCreation(circle)) - self.play( - Write(safe_to_ignore, run_time = 2), - self.pi_creature.change_mode, "raise_right_hand" - ) - self.play( - FadeOut(circle), - FadeOut(safe_to_ignore), - dx_squared_group.fade, 0.5, - dx_squared_group.to_corner, UP+RIGHT, - self.pi_creature.change_mode, "plain" - ) - self.wait() - - def write_out_derivative(self): - df, equals, r1, plus1, r2, plus2, s = self.df_equation - frac_line = TexMobject("-") - frac_line.stretch_to_fit_width(df.get_width()) - frac_line.move_to(df) - dx = VGroup(*self.thin_rect_area[-2:]) - x = self.thin_rect_area[1] - - self.play( - Transform(r1, self.right_rect), - Transform(r2, self.bottom_rect), - FadeOut(plus1), - FadeOut(self.thin_rect_brace) - ) - self.play( - self.thin_rect_area.next_to, VGroup(df, equals), - RIGHT, MED_SMALL_BUFF, UP, - self.pi_creature.change_mode, "thinking" - ) - self.wait(2) - self.play( - ApplyMethod(df.next_to, frac_line, UP, SMALL_BUFF), - ApplyMethod(dx.next_to, frac_line, DOWN, SMALL_BUFF), - Write(frac_line), - path_arc = -np.pi - ) - self.wait() - - brace_xs = [ - brace[-1] - for brace in self.side_braces - ] - xs = list(brace_xs) + [x] - for x_mob in xs: - number = TexMobject("(%d)"%self.square_width) - number.move_to(x_mob, LEFT) - number.shift( - (x_mob.get_bottom()[1] - number[1].get_bottom()[1])*UP - ) - x_mob.save_state() - x_mob.target = number - self.play(*list(map(MoveToTarget, xs))) - self.wait(2) - - #Recursively transform to what would have happened - #with a wider square width - self.mobjects_at_end_of_write_out_derivative = self.get_mobjects() - if self.is_recursing_on_square_width or self.is_recursing_on_dx: - return - alt_scene = self.__class__( - skip_animations = True, - square_width = self.alt_square_width, - is_recursing_on_square_width = True, - ) - self.play(*[ - Transform(*pair) - for pair in zip( - self.mobjects_at_end_of_write_out_derivative, - alt_scene.mobjects_at_end_of_write_out_derivative - ) - ]) - self.change_mode("happy") - self.wait(2) - -class ChangeInAreaOverChangeInX(Scene): - def construct(self): - fractions = [] - for pair in ("Change in area", "Change in $x$"), ("$d(x^2)$", "$dx$"): - top, bottom = list(map(TextMobject, pair)) - top.set_color(YELLOW) - bottom.set_color(BLUE_B) - frac_line = TexMobject("-") - frac_line.stretch_to_fit_width(top.get_width()) - top.next_to(frac_line, UP, SMALL_BUFF) - bottom.next_to(frac_line, DOWN, SMALL_BUFF) - fractions.append(VGroup( - top, frac_line, bottom - )) - words, symbols = fractions - - self.play(Write(words[0], run_time = 1)) - self.play(*list(map(Write, words[1:])), run_time = 1) - self.wait() - self.play(Transform(words, symbols)) - self.wait() - -class NudgeSideLengthOfCube(Scene): - CONFIG = { - "x_color" : BLUE, - "dx_color" : GREEN, - "df_color" : YELLOW, - "use_morty" : False, - "x" : 3, - "dx" : 0.2, - "alt_dx" : 0.02, - "offset_vect" : OUT, - "pose_angle" : np.pi/12, - "pose_axis" : UP+RIGHT, - "small_piece_scaling_factor" : 0.7, - "allow_recursion" : True, - } - def construct(self): - self.states = dict() - if self.allow_recursion: - self.alt_scene = self.__class__( - skip_animations = True, - allow_recursion = False, - dx = self.alt_dx, - ) - - self.add_title() - self.introduce_cube() - self.write_df_equation() - self.write_derivative() - - def add_title(self): - title = TexMobject("f(x) = x^3") - title.shift(FRAME_X_RADIUS*LEFT/2) - title.to_edge(UP) - self.play(Write(title)) - self.wait() - - def introduce_cube(self): - cube = self.get_cube() - cube.to_edge(LEFT, buff = 2*LARGE_BUFF) - cube.shift(DOWN) - - dv_pieces = self.get_dv_pices(cube) - original_dx = self.dx - self.dx = 0 - alt_dv_pieces = self.get_dv_pices(cube) - self.dx = original_dx - alt_dv_pieces.set_fill(opacity = 0) - - x_brace = Brace(cube, LEFT, buff = SMALL_BUFF) - dx_brace = Brace( - dv_pieces[1], LEFT, buff = SMALL_BUFF, - ) - dx_brace.stretch_in_place(1.5, 1) - for brace, tex in (x_brace, "x"), (dx_brace, "dx"): - brace.scale_in_place(0.95) - brace.rotate_in_place(-np.pi/96) - brace.shift(0.3*(UP+LEFT)) - brace.add(brace.get_text("$%s$"%tex)) - - - cube_group = VGroup(cube, dv_pieces, alt_dv_pieces) - self.pose_3d_mobject(cube_group) - - self.play(DrawBorderThenFill(cube)) - self.play(GrowFromCenter(x_brace)) - self.wait() - self.play(Transform(alt_dv_pieces, dv_pieces)) - self.remove(alt_dv_pieces) - self.add(dv_pieces) - self.play(GrowFromCenter(dx_brace)) - self.wait() - for piece in dv_pieces: - piece.on_cube_state = piece.copy() - self.play(*[ - ApplyMethod( - piece.shift, - 0.5*(piece.get_center()-cube.get_center()) - ) - for piece in dv_pieces - ]+[ - ApplyMethod(dx_brace.shift, 0.7*UP) - ]) - self.wait() - - self.cube = cube - self.dx_brace = dx_brace - self.faces, self.bars, self.corner_cube = [ - VGroup(*[ - piece - for piece in dv_pieces - if piece.type == target_type - ]) - for target_type in ("face", "bar", "corner_cube") - ] - - def write_df_equation(self): - df_equation = VGroup( - TexMobject("df"), - TexMobject("="), - self.organize_faces(self.faces.copy()), - TexMobject("+"), - self.organize_bars(self.bars.copy()), - TexMobject("+"), - self.corner_cube.copy() - ) - df, equals, faces, plus1, bars, plus2, corner_cube = df_equation - df.set_color(self.df_color) - for three_d_mob in faces, bars, corner_cube: - three_d_mob.scale(self.small_piece_scaling_factor) - # self.pose_3d_mobject(three_d_mob) - faces.set_fill(opacity = 0.3) - df_equation.arrange(RIGHT) - df_equation.next_to(ORIGIN, RIGHT) - df_equation.to_edge(UP) - - faces_brace = Brace(faces, DOWN) - derivative = faces_brace.get_tex("3x^2", "\\, dx") - extras_brace = Brace(VGroup(bars, corner_cube), DOWN) - ignore_text = extras_brace.get_text( - "Multiple \\\\ of $dx^2$" - ) - ignore_text.scale_in_place(0.7) - x_squared_dx = TexMobject("x^2", "\\, dx") - - - self.play(*list(map(Write, [df, equals]))) - self.grab_pieces(self.faces, faces) - self.wait() - self.shrink_dx("Faces are introduced") - face = self.faces[0] - face.save_state() - self.play(face.shift, FRAME_X_RADIUS*RIGHT) - x_squared_dx.next_to(face, LEFT) - self.play(Write(x_squared_dx, run_time = 1)) - self.wait() - for submob, sides in zip(x_squared_dx, [face[0], VGroup(*face[1:])]): - self.play( - submob.set_color, RED, - sides.set_color, RED, - rate_func = there_and_back - ) - self.wait() - self.play( - face.restore, - Transform( - x_squared_dx, derivative, - replace_mobject_with_target_in_scene = True - ), - GrowFromCenter(faces_brace) - ) - self.wait() - self.grab_pieces(self.bars, bars, plus1) - self.grab_pieces(self.corner_cube, corner_cube, plus2) - self.play( - GrowFromCenter(extras_brace), - Write(ignore_text) - ) - self.wait() - self.play(*[ - ApplyMethod(mob.fade, 0.7) - for mob in [ - plus1, bars, plus2, corner_cube, - extras_brace, ignore_text - ] - ]) - self.wait() - - self.df_equation = df_equation - self.derivative = derivative - - def write_derivative(self): - df, equals, faces, plus1, bars, plus2, corner_cube = self.df_equation - df = df.copy() - equals = equals.copy() - df_equals = VGroup(df, equals) - - derivative = self.derivative.copy() - dx = derivative[1] - - extra_stuff = TexMobject("+(\\dots)", "dx^2") - dx_squared = extra_stuff[1] - - derivative.generate_target() - derivative.target.shift(2*DOWN) - extra_stuff.next_to(derivative.target) - self.play( - MoveToTarget(derivative), - df_equals.next_to, derivative.target[0], LEFT, - df_equals.shift, 0.07*DOWN - ) - self.play(Write(extra_stuff)) - self.wait() - - frac_line = TexMobject("-") - frac_line.replace(df) - extra_stuff.generate_target() - extra_stuff.target.next_to(derivative[0]) - frac_line2 = TexMobject("-") - frac_line2.stretch_to_fit_width( - extra_stuff.target[1].get_width() - ) - frac_line2.move_to(extra_stuff.target[1]) - extra_stuff.target[1].next_to(frac_line2, UP, buff = SMALL_BUFF) - dx_below_dx_squared = TexMobject("dx") - dx_below_dx_squared.next_to(frac_line2, DOWN, buff = SMALL_BUFF) - self.play( - FadeIn(frac_line), - FadeIn(frac_line2), - df.next_to, frac_line, UP, SMALL_BUFF, - dx.next_to, frac_line, DOWN, SMALL_BUFF, - MoveToTarget(extra_stuff), - Write(dx_below_dx_squared), - path_arc = -np.pi - ) - self.wait() - inner_dx = VGroup(*dx_squared[:-1]) - self.play( - FadeOut(frac_line2), - FadeOut(dx_below_dx_squared), - dx_squared[-1].set_color, BLACK, - inner_dx.next_to, extra_stuff[0], RIGHT, SMALL_BUFF - ) - self.wait() - self.shrink_dx("Derivative is written", restore = False) - self.play(*[ - ApplyMethod(mob.fade, 0.7) - for mob in (extra_stuff, inner_dx) - ]) - self.wait(2) - - anims = [] - for mob in list(self.faces)+list(self.bars)+list(self.corner_cube): - vect = mob.get_center()-self.cube.get_center() - anims += [ - mob.shift, -(1./3)*vect - ] - anims += self.dx_brace.shift, 0.7*DOWN - self.play(*anims) - self.wait() - - def grab_pieces(self, start_pieces, end_pices, to_write = None): - for piece in start_pieces: - piece.generate_target() - piece.target.rotate_in_place( - np.pi/12, piece.get_center()-self.cube.get_center() - ) - piece.target.set_color(RED) - self.play(*list(map(MoveToTarget, start_pieces)), rate_func = wiggle) - self.wait() - added_anims = [] - if to_write is not None: - added_anims.append(Write(to_write)) - self.play( - Transform(start_pieces.copy(), end_pices), - *added_anims - ) - - def shrink_dx(self, state_name, restore = True): - mobjects = self.get_mobjects() - mobjects_with_points = [ - m for m in mobjects - if m.get_num_points() > 0 - ] - #Alt_scene will reach this point, and save copy of self - #in states dict - self.states[state_name] = [ - mob.copy() for mob in mobjects_with_points - ] - if not self.allow_recursion: - return - if restore: - movers = self.states[state_name] - for mob in movers: - mob.save_state() - self.remove(*mobjects) - else: - movers = mobjects_with_points - self.play(*[ - Transform(*pair) - for pair in zip( - movers, - self.alt_scene.states[state_name] - ) - ]) - self.wait() - if restore: - self.play(*[m.restore for m in movers]) - self.remove(*movers) - self.mobjects = mobjects - - def get_cube(self): - cube = self.get_prism(self.x, self.x, self.x) - cube.set_fill(color = BLUE, opacity = 0.3) - cube.set_stroke(color = WHITE, width = 1) - return cube - - def get_dv_pices(self, cube): - pieces = VGroup() - for vect in it.product([0, 1], [0, 1], [0, 1]): - if np.all(vect == ORIGIN): - continue - args = [ - self.x if bit is 0 else self.dx - for bit in vect - ] - piece = self.get_prism(*args) - piece.next_to(cube, np.array(vect), buff = 0) - pieces.add(piece) - if sum(vect) == 1: - piece.type = "face" - elif sum(vect) == 2: - piece.type = "bar" - else: - piece.type = "corner_cube" - - return pieces - - def organize_faces(self, faces): - self.unpose_3d_mobject(faces) - for face in faces: - dimensions = [ - face.length_over_dim(dim) - for dim in range(3) - ] - thin_dim = np.argmin(dimensions) - if thin_dim == 0: - face.rotate(np.pi/2, DOWN) - elif thin_dim == 1: - face.rotate(np.pi/2, RIGHT) - faces.arrange(OUT, buff = LARGE_BUFF) - self.pose_3d_mobject(faces) - return faces - - def organize_bars(self, bars): - self.unpose_3d_mobject(bars) - for bar in bars: - dimensions = [ - bar.length_over_dim(dim) - for dim in range(3) - ] - thick_dim = np.argmax(dimensions) - if thick_dim == 0: - bar.rotate(np.pi/2, OUT) - elif thick_dim == 2: - bar.rotate(np.pi/2, LEFT) - bars.arrange(OUT, buff = LARGE_BUFF) - self.pose_3d_mobject(bars) - return bars - - def get_corner_cube(self): - return self.get_prism(self.dx, self.dx, self.dx) - - def get_prism(self, width, height, depth): - color_kwargs = { - "fill_color" : YELLOW, - "fill_opacity" : 0.4, - "stroke_color" : WHITE, - "stroke_width" : 0.1, - } - front = Rectangle( - width = width, - height = height, - **color_kwargs - ) - face = VGroup(front) - for vect in LEFT, RIGHT, UP, DOWN: - if vect is LEFT or vect is RIGHT: - side = Rectangle( - height = height, - width = depth, - **color_kwargs - ) - else: - side = Rectangle( - height = depth, - width = width, - **color_kwargs - ) - side.next_to(front, vect, buff = 0) - side.rotate( - np.pi/2, rotate_vector(vect, -np.pi/2), - about_point = front.get_edge_center(vect) - ) - face.add(side) - return face - - def pose_3d_mobject(self, mobject): - mobject.rotate_in_place(self.pose_angle, self.pose_axis) - return mobject - - def unpose_3d_mobject(self, mobject): - mobject.rotate_in_place(-self.pose_angle, self.pose_axis) - return mobject - -class ShowCubeDVIn3D(Scene): - def construct(self): - raise Exception("This scene is only here for the stage_scenes script.") - -class GraphOfXCubed(GraphScene): - CONFIG = { - "x_min" : -6, - "x_max" : 6, - "x_axis_width" : FRAME_WIDTH, - "x_labeled_nums" : list(range(-6, 7)), - "y_min" : -35, - "y_max" : 35, - "y_axis_height" : FRAME_HEIGHT, - "y_tick_frequency" : 5, - "y_labeled_nums" : list(range(-30, 40, 10)), - "graph_origin" : ORIGIN, - "dx" : 0.2, - "deriv_x_min" : -3, - "deriv_x_max" : 3, - } - def construct(self): - self.setup_axes(animate = False) - graph = self.get_graph(lambda x : x**3) - label = self.get_graph_label( - graph, "f(x) = x^3", - direction = LEFT, - ) - - - deriv_graph, full_deriv_graph = [ - self.get_derivative_graph( - graph, - color = DERIVATIVE_COLOR, - x_min = low_x, - x_max = high_x, - ) - for low_x, high_x in [ - (self.deriv_x_min, self.deriv_x_max), - (self.x_min, self.x_max), - ] - ] - deriv_label = self.get_graph_label( - deriv_graph, - "\\frac{df}{dx}(x) = 3x^2", - x_val = -3, - direction = LEFT - ) - deriv_label.shift(0.5*DOWN) - - ss_group = self.get_secant_slope_group( - self.deriv_x_min, graph, - dx = self.dx, - dx_line_color = WHITE, - df_line_color = WHITE, - secant_line_color = YELLOW, - ) - - self.play(ShowCreation(graph)) - self.play(Write(label, run_time = 1)) - self.wait() - self.play(Write(deriv_label, run_time = 1)) - self.play(ShowCreation(ss_group, lag_ratio = 0)) - self.animate_secant_slope_group_change( - ss_group, - target_x = self.deriv_x_max, - run_time = 10, - added_anims = [ - ShowCreation(deriv_graph, run_time = 10) - ] - ) - self.play(FadeIn(full_deriv_graph)) - self.wait() - for x_val in -2, -self.dx/2, 2: - self.animate_secant_slope_group_change( - ss_group, - target_x = x_val, - run_time = 2 - ) - if x_val != -self.dx/2: - v_line = self.get_vertical_line_to_graph( - x_val, deriv_graph, - line_class = DashedLine - ) - self.play(ShowCreation(v_line)) - self.play(FadeOut(v_line)) - -class PatternForPowerRule(PiCreatureScene): - CONFIG = { - "num_exponents" : 5, - } - def construct(self): - self.introduce_pattern() - self.generalize_pattern() - self.show_hopping() - - def introduce_pattern(self): - exp_range = list(range(1, 1+self.num_exponents)) - colors = color_gradient([BLUE_D, YELLOW], self.num_exponents) - derivatives = VGroup() - for exponent, color in zip(exp_range, colors): - derivative = TexMobject( - "\\frac{d(x^%d)}{dx} = "%exponent, - "%d x^{%d}"%(exponent, exponent-1) - ) - VGroup(*derivative[0][2:4]).set_color(color) - derivatives.add(derivative) - derivatives.arrange( - DOWN, aligned_edge = LEFT, - buff = MED_LARGE_BUFF - ) - derivatives.set_height(FRAME_HEIGHT-1) - derivatives.to_edge(LEFT) - - self.play(FadeIn(derivatives[0])) - for d1, d2 in zip(derivatives, derivatives[1:]): - self.play(Transform( - d1.copy(), d2, - replace_mobject_with_target_in_scene = True - )) - self.change_mode("thinking") - self.wait() - for derivative in derivatives[-2:]: - derivative.save_state() - self.play( - derivative.scale, 2, - derivative.next_to, derivative, - RIGHT, SMALL_BUFF, DOWN, - ) - self.wait(2) - self.play(derivative.restore) - self.remove(derivative) - derivative.restore() - self.add(derivative) - - self.derivatives = derivatives - self.colors = colors - - def generalize_pattern(self): - derivatives = self.derivatives - - - power_rule = TexMobject( - "\\frac{d (x^n)}{dx} = ", - "nx^{n-1}" - ) - title = TextMobject("``Power rule''") - title.next_to(power_rule, UP, MED_LARGE_BUFF) - lines = VGroup(*[ - Line( - deriv.get_right(), power_rule.get_left(), - buff = MED_SMALL_BUFF, - color = deriv[0][2].get_color() - ) - for deriv in derivatives - ]) - - self.play( - Transform( - VGroup(*[d[0].copy() for d in derivatives]), - VGroup(power_rule[0]), - replace_mobject_with_target_in_scene = True - ), - ShowCreation(lines), - lag_ratio = 0.5, - run_time = 2, - ) - self.wait() - self.play(Write(power_rule[1])) - self.wait() - self.play( - Write(title), - self.pi_creature.change_mode, "speaking" - ) - self.wait() - - def show_hopping(self): - exp_range = list(range(2, 2+self.num_exponents-1)) - self.change_mode("tired") - for exp, color in zip(exp_range, self.colors[1:]): - form = TexMobject( - "x^", - str(exp), - "\\rightarrow", - str(exp), - "x^", - str(exp-1) - ) - form.set_color(color) - form.to_corner(UP+RIGHT, buff = LARGE_BUFF) - lhs = VGroup(*form[:2]) - lhs_copy = lhs.copy() - rhs = VGroup(*form[-2:]) - arrow = form[2] - - self.play(Write(lhs)) - self.play( - lhs_copy.move_to, rhs, DOWN+LEFT, - Write(arrow) - ) - self.wait() - self.play( - ApplyMethod( - lhs_copy[1].replace, form[3], - path_arc = np.pi, - rate_func = running_start, - ), - FadeIn( - form[5], - rate_func = squish_rate_func(smooth, 0.5, 1) - ) - ) - self.wait() - self.play( - self.pi_creature.change_mode, "hesitant", - self.pi_creature.look_at, lhs_copy - ) - self.play(*list(map(FadeOut, [form, lhs_copy]))) - -class PowerRuleAlgebra(Scene): - CONFIG = { - "dx_color" : YELLOW, - "x_color" : BLUE, - } - def construct(self): - x_to_n = TexMobject("x^n") - down_arrow = Arrow(UP, DOWN, buff = MED_LARGE_BUFF) - paren_strings = ["(", "x", "+", "dx", ")"] - x_dx_to_n = TexMobject(*paren_strings +["^n"]) - equals = TexMobject("=") - equals2 = TexMobject("=") - full_product = TexMobject( - *paren_strings*3+["\\cdots"]+paren_strings - ) - - x_to_n.set_color(self.x_color) - for mob in x_dx_to_n, full_product: - mob.set_color_by_tex("dx", self.dx_color) - mob.set_color_by_tex("x", self.x_color) - - nudge_group = VGroup(x_to_n, down_arrow, x_dx_to_n) - nudge_group.arrange(DOWN) - nudge_group.to_corner(UP+LEFT) - down_arrow.next_to(x_to_n[0], DOWN) - equals.next_to(x_dx_to_n) - full_product.next_to(equals) - equals2.next_to(equals, DOWN, 1.5*LARGE_BUFF) - - nudge_brace = Brace(x_dx_to_n, DOWN) - nudged_output = nudge_brace.get_text("Nudged \\\\ output") - product_brace = Brace(full_product, UP) - product_brace.add(product_brace.get_text("$n$ times")) - - self.add(x_to_n) - self.play(ShowCreation(down_arrow)) - self.play( - FadeIn(x_dx_to_n), - GrowFromCenter(nudge_brace), - GrowFromCenter(nudged_output) - ) - self.wait() - self.play( - Write(VGroup(equals, full_product)), - GrowFromCenter( - product_brace, - rate_func = squish_rate_func(smooth, 0.6, 1) - ), - run_time = 3 - ) - self.wait() - self.workout_product(equals2, full_product) - - def workout_product(self, equals, full_product): - product_part_tex_pairs = list(zip(full_product, full_product.expression_parts)) - xs, dxs = [ - VGroup(*[ - submob - for submob, tex in product_part_tex_pairs - if tex == target_tex - ]) - for target_tex in ("x", "dx") - ] - - x_to_n = TexMobject("x^n") - extra_stuff = TexMobject("+(\\text{Multiple of }\\, dx^2)") - # extra_stuff.scale(0.8) - VGroup(*extra_stuff[-4:-2]).set_color(self.dx_color) - - x_to_n.next_to(equals, RIGHT, align_using_submobjects = True) - x_to_n.set_color(self.x_color) - - xs_copy = xs.copy() - full_product.save_state() - self.play(full_product.set_color, WHITE) - self.play(xs_copy.set_color, self.x_color) - self.play( - Write(equals), - Transform(xs_copy, x_to_n) - ) - self.wait() - brace, derivative_term = self.pull_out_linear_terms( - x_to_n, product_part_tex_pairs, xs, dxs - ) - self.wait() - - circle = Circle(color = DERIVATIVE_COLOR) - circle.replace(derivative_term, stretch = True) - circle.scale_in_place(1.4) - circle.rotate_in_place( - Line( - derivative_term.get_corner(DOWN+LEFT), - derivative_term.get_corner(UP+RIGHT), - ).get_angle() - ) - - extra_stuff.next_to(brace, aligned_edge = UP) - - self.play(Write(extra_stuff), full_product.restore) - self.wait() - self.play(ShowCreation(circle)) - self.wait() - - def pull_out_linear_terms(self, x_to_n, product_part_tex_pairs, xs, dxs): - last_group = None - all_linear_terms = VGroup() - for dx_index, dx in enumerate(dxs): - if dx is dxs[-1]: - v_dots = TexMobject("\\vdots") - v_dots.next_to(last_group[0], DOWN) - h_dots_list = [ - submob - for submob, tex in product_part_tex_pairs - if tex == "\\cdots" - ] - h_dots_copy = h_dots_list[0].copy() - self.play(ReplacementTransform( - h_dots_copy, v_dots, - )) - last_group.add(v_dots) - all_linear_terms.add(v_dots) - - dx_copy = dx.copy() - xs_copy = xs.copy() - xs_copy.remove(xs_copy[dx_index]) - self.play( - dx_copy.set_color, self.dx_color, - xs_copy.set_color, self.x_color, - rate_func = squish_rate_func(smooth, 0, 0.5) - ) - - dx_copy.generate_target() - xs_copy.generate_target() - target_list = [dx_copy.target] + list(xs_copy.target) - target_list.sort( - key=lambda m: m.get_center()[0] - ) - dots = TexMobject("+", ".", ".", "\\dots") - for dot_index, dot in enumerate(dots): - target_list.insert(2*dot_index, dot) - group = VGroup(*target_list) - group.arrange(RIGHT, SMALL_BUFF) - if last_group is None: - group.next_to(x_to_n, RIGHT) - else: - group.next_to(last_group, DOWN, aligned_edge = LEFT) - last_group = group - - self.play( - MoveToTarget(dx_copy), - MoveToTarget(xs_copy), - Write(dots) - ) - all_linear_terms.add(dx_copy, xs_copy, dots) - - all_linear_terms.generate_target() - all_linear_terms.target.scale(0.7) - brace = Brace(all_linear_terms.target, UP) - compact = TexMobject("+\\,", "n", "x^{n-1}", "\\,dx") - compact.set_color_by_tex("x^{n-1}", self.x_color) - compact.set_color_by_tex("\\,dx", self.dx_color) - compact.next_to(brace, UP) - brace.add(compact) - derivative_term = VGroup(*compact[1:3]) - - VGroup(brace, all_linear_terms.target).shift( - x_to_n[0].get_right()+MED_LARGE_BUFF*RIGHT - \ - compact[0].get_left() - ) - - self.play(MoveToTarget(all_linear_terms)) - self.play(Write(brace, run_time = 1)) - return brace, derivative_term - -class ReactToFullExpansion(Scene): - def construct(self): - randy = Randolph() - self.add(randy) - - self.play(randy.change_mode, "pleading") - self.play(Blink(randy)) - self.play(randy.change_mode, "angry") - self.wait() - self.play(randy.change_mode, "thinking") - self.play(Blink(randy)) - self.wait() - -class OneOverX(PiCreatureScene, GraphScene): - CONFIG = { - "unit_length" : 3.0, - "graph_origin" : (FRAME_X_RADIUS - LARGE_BUFF)*LEFT + 2*DOWN, - "rectangle_color_kwargs" : { - "fill_color" : BLUE, - "fill_opacity" : 0.5, - "stroke_width" : 1, - "stroke_color" : WHITE, - }, - - "x_axis_label" : "", - "y_axis_label" : "", - "x_min" : 0, - "y_min" : 0, - "x_tick_frequency" : 0.5, - "y_tick_frequency" : 0.5, - "x_labeled_nums" : list(range(0, 4)), - "y_labeled_nums" : [1], - "y_axis_height" : 10, - "morty_scale_val" : 0.8, - "area_label_scale_factor" : 0.75, - "dx" : 0.1, - "start_x_value" : 1.3, - "dx_color" : GREEN, - "df_color" : RED, - } - def setup(self): - for c in self.__class__.__bases__: - c.setup(self) - self.x_max = self.x_axis_width/self.unit_length - self.y_max = self.y_axis_height/self.unit_length - - def construct(self): - self.force_skipping() - - self.introduce_function() - self.introduce_puddle() - self.introduce_graph() - self.perform_nudge() - - def introduce_function(self): - func = TexMobject("f(x) = ", "\\frac{1}{x}") - func.to_edge(UP) - recip_copy = func[1].copy() - x_to_neg_one = TexMobject("x^{-1}") - x_to_neg_one.submobjects.reverse() - neg_one = VGroup(*x_to_neg_one[:2]) - neg_two = TexMobject("-2") - - self.play( - Write(func), - self.pi_creature.change_mode, "pondering" - ) - self.wait() - self.play( - recip_copy.next_to, self.pi_creature, UP+LEFT, - self.pi_creature.change_mode, "raise_right_hand" - ) - x_to_neg_one.move_to(recip_copy) - neg_two.replace(neg_one) - self.play(ReplacementTransform(recip_copy, x_to_neg_one)) - self.wait() - self.play( - neg_one.scale, 1.5, - neg_one.next_to, x_to_neg_one, LEFT, SMALL_BUFF, DOWN, - rate_func = running_start, - path_arc = np.pi - ) - self.play(FadeIn(neg_two)) - self.wait() - self.say( - "More geometry!", - target_mode = "hooray", - added_anims = [ - FadeOut(x_to_neg_one), - FadeOut(neg_two), - ], - run_time = 2 - ) - self.wait() - self.play(RemovePiCreatureBubble(self.pi_creature)) - - def introduce_puddle(self): - rect_group = self.get_rectangle_group(self.start_x_value) - - self.play( - DrawBorderThenFill(rect_group.rectangle), - Write(rect_group.area_label), - self.pi_creature.change_mode, "thinking" - ) - self.play( - GrowFromCenter(rect_group.x_brace), - Write(rect_group.x_label), - ) - self.wait() - self.play( - GrowFromCenter(rect_group.recip_brace), - Write(rect_group.recip_label), - ) - self.setup_axes(animate = True) - self.wait() - - for d in 2, 3: - self.change_rectangle_group( - rect_group, d, - target_group_kwargs = { - "x_label" : str(d), - "one_over_x_label" : "\\frac{1}{%d}"%d, - }, - run_time = 2 - ) - self.wait() - self.change_rectangle_group(rect_group, 3) - self.wait() - - self.rect_group = rect_group - - def introduce_graph(self): - rect_group = self.rect_group - graph = self.get_graph(lambda x : 1./x) - graph.points = np.array(list(reversed(graph.points))) - - self.change_rectangle_group( - rect_group, 0.01, - added_anims = [ - ShowCreation(graph) - ], - run_time = 5, - ) - self.change_mode("happy") - self.change_rectangle_group(rect_group, self.start_x_value) - self.wait() - - self.graph = graph - - def perform_nudge(self): - rect_group = self.rect_group - graph = self.graph - - rect_copy = rect_group.rectangle.copy() - rect_copy.set_fill(opacity = 0) - new_rect = self.get_rectangle( - self.start_x_value + self.dx - ) - - recip_brace = rect_group.recip_brace - recip_brace.generate_target() - recip_brace.target.next_to( - new_rect, RIGHT, - buff = SMALL_BUFF, - aligned_edge = DOWN, - ) - recip_label = rect_group.recip_label - recip_label.generate_target() - recip_label.target.next_to(recip_brace.target, RIGHT) - - h_lines = VGroup(*[ - DashedLine( - ORIGIN, (rect_copy.get_width()+LARGE_BUFF)*RIGHT, - color = self.df_color, - stroke_width = 2 - ).move_to(rect.get_corner(UP+LEFT), LEFT) - for rect in (rect_group.rectangle, new_rect) - ]) - - v_lines = VGroup(*[ - DashedLine( - ORIGIN, (rect_copy.get_height()+MED_LARGE_BUFF)*UP, - color = self.dx_color, - stroke_width = 2 - ).move_to(rect.get_corner(DOWN+RIGHT), DOWN) - for rect in (rect_group.rectangle, new_rect) - ]) - - dx_brace = Brace(v_lines, UP, buff = 0) - dx_label = dx_brace.get_text("$dx$") - dx_brace.add(dx_label) - - df_brace = Brace(h_lines, RIGHT, buff = 0) - df_label = df_brace.get_text("$d\\left(\\frac{1}{x}\\right)$") - df_brace.add(df_label) - - negative = TextMobject("Negative") - negative.set_color(RED) - negative.next_to(df_label, UP+RIGHT) - negative.shift(RIGHT) - negative_arrow = Arrow( - negative.get_left(), - df_label.get_corner(UP+RIGHT), - color = RED - ) - - area_changes = VGroup() - point_pairs = [ - (new_rect.get_corner(UP+RIGHT), rect_copy.get_corner(DOWN+RIGHT)), - (new_rect.get_corner(UP+LEFT), rect_copy.get_corner(UP+RIGHT)) - ] - for color, point_pair in zip([self.dx_color, self.df_color], point_pairs): - area_change_rect = Rectangle( - fill_opacity = 1, - fill_color = color, - stroke_width = 0 - ) - area_change_rect.replace( - VGroup(*list(map(VectorizedPoint, point_pair))), - stretch = True - ) - area_changes.add(area_change_rect) - area_gained, area_lost = area_changes - - area_gained_label = TextMobject("Area gained") - area_gained_label.scale(0.75) - area_gained_label.next_to( - rect_copy.get_corner(DOWN+RIGHT), - UP+LEFT, buff = SMALL_BUFF - ) - area_gained_arrow = Arrow( - area_gained_label.get_top(), - area_gained.get_center(), - buff = 0, - color = WHITE - ) - - area_lost_label = TextMobject("Area lost") - area_lost_label.scale(0.75) - area_lost_label.next_to(rect_copy.get_left(), RIGHT) - area_lost_arrow = Arrow( - area_lost_label.get_top(), - area_lost.get_center(), - buff = 0, - color = WHITE - ) - - question = TexMobject( - "\\frac{d(1/x)}{dx} = ???" - ) - question.next_to( - self.pi_creature.get_corner(UP+LEFT), - UP, buff = MED_SMALL_BUFF, - ) - - self.play( - FadeOut(rect_group.area_label), - ReplacementTransform(rect_copy, new_rect), - MoveToTarget(recip_brace), - MoveToTarget(recip_label), - self.pi_creature.change_mode, "pondering" - ) - self.play( - GrowFromCenter(dx_brace), - *list(map(ShowCreation, v_lines)) - ) - self.wait() - self.play( - GrowFromCenter(df_brace), - *list(map(ShowCreation, h_lines)) - ) - self.change_mode("confused") - self.wait() - - self.play( - FadeIn(area_gained), - Write(area_gained_label, run_time = 2), - ShowCreation(area_gained_arrow) - ) - self.wait() - self.play( - FadeIn(area_lost), - Write(area_lost_label, run_time = 2), - ShowCreation(area_lost_arrow) - ) - self.wait() - self.revert_to_original_skipping_status()### - self.play( - Write(negative), - ShowCreation(negative_arrow) - ) - self.wait() - self.play( - Write(question), - self.pi_creature.change_mode, "raise_right_hand" - ) - self.wait(2) - - - ######## - - def create_pi_creature(self): - morty = PiCreatureScene.create_pi_creature(self) - morty.scale( - self.morty_scale_val, - about_point = morty.get_corner(DOWN+RIGHT) - ) - return morty - - def draw_graph(self): - self.setup_axes() - graph = self.get_graph(lambda x : 1./x) - - rect_group = self.get_rectangle_group(0.5) - - self.add(rect_group) - self.wait() - self.change_rectangle_group( - rect_group, 2, - target_group_kwargs = { - "x_label" : "2", - "one_over_x_label" : "\\frac{1}{2}", - }, - added_anims = [ShowCreation(graph)] - ) - self.wait() - - def get_rectangle_group( - self, x, - x_label = "x", - one_over_x_label = "\\frac{1}{x}" - ): - result = VGroup() - result.x_val = x - result.rectangle = self.get_rectangle(x) - - result.x_brace, result.recip_brace = braces = [ - Brace(result.rectangle, vect) - for vect in (UP, RIGHT) - ] - result.labels = VGroup() - for brace, label in zip(braces, [x_label, one_over_x_label]): - brace.get_text("$%s$"%label) - result.labels.add(brace.get_text("$%s$"%label)) - result.x_label, result.recip_label = result.labels - - area_label = TextMobject("Area = 1") - area_label.scale(self.area_label_scale_factor) - max_width = max(result.rectangle.get_width()-2*SMALL_BUFF, 0) - if area_label.get_width() > max_width: - area_label.set_width(max_width) - area_label.move_to(result.rectangle) - result.area_label = area_label - - result.add( - result.rectangle, - result.x_brace, - result.recip_brace, - result.labels, - result.area_label, - ) - return result - - def get_rectangle(self, x): - try: - y = 1./x - except ZeroDivisionError: - y = 100 - - rectangle = Rectangle( - width = x*self.unit_length, - height = y*self.unit_length, - **self.rectangle_color_kwargs - ) - rectangle.move_to(self.graph_origin, DOWN+LEFT) - return rectangle - - def change_rectangle_group( - self, - rect_group, target_x, - target_group_kwargs = None, - added_anims = [], - **anim_kwargs - ): - target_group_kwargs = target_group_kwargs or {} - if "run_time" not in anim_kwargs: - anim_kwargs["run_time"] = 3 - - target_group = self.get_rectangle_group(target_x, **target_group_kwargs) - target_labels = target_group.labels - labels_transform = Transform( - rect_group.labels, - target_group.labels - ) - - start_x = float(rect_group.x_val) - def update_rect_group(group, alpha): - x = interpolate(start_x, target_x, alpha) - new_group = self.get_rectangle_group(x, **target_group_kwargs) - Transform(group, new_group).update(1) - labels_transform.update(alpha) - for l1, l2 in zip(rect_group.labels, new_group.labels): - l1.move_to(l2) - return rect_group - - self.play( - UpdateFromAlphaFunc(rect_group, update_rect_group), - *added_anims, - **anim_kwargs - ) - rect_group.x_val = target_x - -class AskRecipriocalQuestion(Scene): - def construct(self): - tex = TexMobject( - "(\\text{What number?})", - "\\cdot x = 1" - ) - arrow = Arrow(DOWN+LEFT, UP+RIGHT) - arrow.move_to(tex[0].get_top(), DOWN+LEFT) - self.play(Write(tex)) - self.play(ShowCreation(arrow)) - self.wait() - -class SquareRootOfX(Scene): - CONFIG = { - "square_color_kwargs" : { - "stroke_color" : WHITE, - "stroke_width" : 1, - "fill_color" : BLUE_E, - "fill_opacity" : 1, - }, - "bigger_square_color_kwargs" : { - "stroke_color" : WHITE, - "stroke_width" : 1, - "fill_color" : YELLOW, - "fill_opacity" : 0.7, - }, - "square_corner" : 6*LEFT+3*UP, - "square_width" : 3, - "d_sqrt_x" : 0.3, - } - def construct(self): - self.add_title() - self.introduce_square() - self.nudge_square() - - def add_title(self): - title = TexMobject("f(x) = \\sqrt{x}") - title.next_to(ORIGIN, RIGHT) - title.to_edge(UP) - self.add(title) - - def introduce_square(self): - square = Square( - side_length = self.square_width, - **self.square_color_kwargs - ) - square.move_to(self.square_corner, UP+LEFT) - area_label = TextMobject("Area $ = x$") - area_label.move_to(square) - - bottom_brace, right_brace = braces = VGroup(*[ - Brace(square, vect) - for vect in (DOWN, RIGHT) - ]) - for brace in braces: - brace.add(brace.get_text("$\\sqrt{x}$")) - - - self.play( - DrawBorderThenFill(square), - Write(area_label) - ) - self.play(*list(map(FadeIn, braces))) - self.wait() - - self.square = square - self.area_label = area_label - self.braces = braces - - def nudge_square(self): - square = self.square - area_label = self.area_label - bottom_brace, right_brace = self.braces - - bigger_square = Square( - side_length = self.square_width + self.d_sqrt_x, - **self.bigger_square_color_kwargs - ) - bigger_square.move_to(self.square_corner, UP+LEFT) - - square_copy = square.copy() - - lines = VGroup(*[ - DashedLine( - ORIGIN, - (self.square_width + MED_LARGE_BUFF)*vect, - color = WHITE, - stroke_width = 3 - ).shift(s.get_corner(corner)) - for corner, vect in [(DOWN+LEFT, RIGHT), (UP+RIGHT, DOWN)] - for s in [square, bigger_square] - ]) - little_braces = VGroup(*[ - Brace(VGroup(*line_pair), vect, buff = 0) - for line_pair, vect in [(lines[:2], RIGHT), (lines[2:], DOWN)] - ]) - for brace in little_braces: - tex = brace.get_text("$d\\sqrt{x}$", buff = SMALL_BUFF) - tex.scale_in_place(0.8) - brace.add(tex) - - area_increase = TextMobject("$dx$ = New area") - area_increase.set_color(bigger_square.get_color()) - area_increase.next_to(square, RIGHT, 4) - - question = TexMobject("\\frac{d\\sqrt{x}}{dx} = ???") - VGroup(*question[5:7]).set_color(bigger_square.get_color()) - question.next_to( - area_increase, DOWN, - aligned_edge = LEFT, - buff = LARGE_BUFF - ) - - self.play( - Transform(square_copy, bigger_square), - Animation(square), - Animation(area_label), - bottom_brace.next_to, bigger_square, DOWN, SMALL_BUFF, LEFT, - right_brace.next_to, bigger_square, RIGHT, SMALL_BUFF, UP, - ) - self.play(Write(area_increase)) - self.play(*it.chain( - list(map(ShowCreation, lines)), - list(map(FadeIn, little_braces)) - )) - self.play(Write(question)) - self.wait() - -class MentionSine(TeacherStudentsScene): - def construct(self): - self.teacher_says("Let's tackle $\\sin(\\theta)$") - self.change_student_modes("pondering", "hooray", "erm") - self.wait(2) - self.student_thinks("") - self.zoom_in_on_thought_bubble() - -class NameUnitCircle(Scene): - def construct(self): - words = TextMobject("Unit circle") - words.scale(2) - words.set_color(BLUE) - self.play(Write(words)) - self.wait() - -class DerivativeOfSineIsSlope(Scene): - def construct(self): - tex = TexMobject( - "\\frac{d(\\sin(\\theta))}{d\\theta} = ", - "\\text{Slope of this graph}" - ) - tex.set_width(FRAME_WIDTH-1) - tex.to_edge(DOWN) - VGroup(*tex[0][2:8]).set_color(BLUE) - VGroup(*tex[1][-9:]).set_color(BLUE) - - self.play(Write(tex, run_time = 2)) - self.wait() - -class IntroduceUnitCircleWithSine(GraphScene): - CONFIG = { - "unit_length" : 2.5, - "graph_origin" : ORIGIN, - "x_axis_width" : 15, - "y_axis_height" : 10, - "x_min" : -3, - "x_max" : 3, - "y_min" : -2, - "y_max" : 2, - "x_labeled_nums" : [-2, -1, 1, 2], - "y_labeled_nums" : [-1, 1], - "x_tick_frequency" : 0.5, - "y_tick_frequency" : 0.5, - "circle_color" : BLUE, - "example_radians" : 0.8, - "rotations_per_second" : 0.25, - "include_radial_line_dot" : True, - "remove_angle_label" : True, - "line_class" : DashedLine, - "theta_label" : "= 0.8", - } - def construct(self): - self.setup_axes() - self.add_title() - self.introduce_unit_circle() - self.draw_example_radians() - self.label_sine() - self.walk_around_circle() - - def add_title(self): - title = TexMobject("f(\\theta) = \\sin(\\theta)") - title.to_corner(UP+LEFT) - self.add(title) - self.title = title - - def introduce_unit_circle(self): - circle = self.get_unit_circle() - radial_line = Line(ORIGIN, self.unit_length*RIGHT) - radial_line.set_color(RED) - if self.include_radial_line_dot: - dot = Dot() - dot.move_to(radial_line.get_end()) - radial_line.add(dot) - - self.play(ShowCreation(radial_line)) - self.play( - ShowCreation(circle), - Rotate(radial_line, 2*np.pi), - run_time = 2, - ) - self.wait() - - self.circle = circle - self.radial_line = radial_line - - def draw_example_radians(self): - circle = self.circle - radial_line = self.radial_line - - line = Line( - ORIGIN, self.example_radians*self.unit_length*UP, - color = YELLOW, - ) - line.shift(FRAME_X_RADIUS*RIGHT/3).to_edge(UP) - line.insert_n_curves(10) - line.make_smooth() - - arc = Arc( - self.example_radians, radius = self.unit_length, - color = line.get_color(), - ) - arc_copy = arc.copy().set_color(WHITE) - - brace = Brace(line, RIGHT) - brace_text = brace.get_text("$\\theta%s$"%self.theta_label) - brace_text.set_color(line.get_color()) - theta_copy = brace_text[0].copy() - - self.play( - GrowFromCenter(line), - GrowFromCenter(brace), - ) - self.play(Write(brace_text)) - self.wait() - self.play( - line.move_to, radial_line.get_end(), DOWN, - FadeOut(brace) - ) - self.play(ReplacementTransform(line, arc)) - self.wait() - self.play( - Rotate(radial_line, self.example_radians), - ShowCreation(arc_copy) - ) - self.wait() - arc_copy.generate_target() - arc_copy.target.scale(0.2) - theta_copy.generate_target() - theta_copy.target.next_to( - arc_copy.target, RIGHT, - aligned_edge = DOWN, - buff = SMALL_BUFF - ) - theta_copy.target.shift(SMALL_BUFF*UP) - self.play(*list(map(MoveToTarget, [arc_copy, theta_copy]))) - self.wait() - - self.angle_label = VGroup(arc_copy, theta_copy) - self.example_theta_equation = brace_text - - def label_sine(self): - radial_line = self.radial_line - - drop_point = radial_line.get_end()[0]*RIGHT - v_line = self.line_class(radial_line.get_end(), drop_point) - brace = Brace(v_line, RIGHT) - brace_text = brace.get_text("$\\sin(\\theta)$") - brace_text[-2].set_color(YELLOW) - - self.play(ShowCreation(v_line)) - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait() - faders = [brace, brace_text, self.example_theta_equation] - if self.remove_angle_label: - faders += self.angle_label - self.play(*list(map(FadeOut, faders))) - - self.v_line = v_line - - def walk_around_circle(self): - radial_line = self.radial_line - v_line = self.v_line - - def v_line_update(v_line): - drop_point = radial_line.get_end()[0]*RIGHT - v_line.put_start_and_end_on( - radial_line.get_end(), drop_point - ) - return v_line - filler_arc = self.circle.copy() - filler_arc.set_color(YELLOW) - curr_arc_portion = self.example_radians/(2*np.pi) - filler_portion = 1 - curr_arc_portion - filler_arc.pointwise_become_partial(filler_arc, curr_arc_portion, 1) - - self.play( - Rotate(radial_line, filler_portion*2*np.pi), - ShowCreation(filler_arc), - UpdateFromFunc(v_line, v_line_update), - run_time = filler_portion/self.rotations_per_second, - rate_func=linear, - ) - for x in range(5): - self.play( - Rotate(radial_line, 2*np.pi), - UpdateFromFunc(v_line, v_line_update), - run_time = 1./self.rotations_per_second, - rate_func=linear, - ) - - ############## - - def setup_axes(self): - GraphScene.setup_axes(self) - VGroup(*self.x_axis.numbers[:2]).shift(MED_SMALL_BUFF*LEFT) - VGroup(*self.x_axis.numbers[2:]).shift(SMALL_BUFF*RIGHT) - self.y_axis.numbers[0].shift(MED_SMALL_BUFF*DOWN) - self.y_axis.numbers[1].shift(MED_SMALL_BUFF*UP) - - def get_unit_circle(self): - return Circle( - radius = self.unit_length, - color = self.circle_color, - ) - -class DerivativeIntuitionFromSineGraph(GraphScene): - CONFIG = { - "graph_origin" : 6*LEFT, - "x_axis_width" : 11, - "x_min" : 0, - "x_max" : 4*np.pi, - "x_labeled_nums" : np.arange(0, 4*np.pi, np.pi), - "x_tick_frequency" : np.pi/4, - "x_axis_label" : "$\\theta$", - "y_axis_height" : 6, - "y_min" : -2, - "y_max" : 2, - "y_tick_frequency" : 0.5, - "y_axis_label" : "", - } - def construct(self): - self.setup_axes() - self.draw_sine_graph() - self.draw_derivative_from_slopes() - self.alter_derivative_graph() - - def draw_sine_graph(self): - graph = self.get_graph(np.sin) - v_line = DashedLine(ORIGIN, UP) - rps = IntroduceUnitCircleWithSine.CONFIG["rotations_per_second"] - self.play( - ShowCreation(graph), - UpdateFromFunc(v_line, lambda v : self.v_line_update(v, graph)), - run_time = 2./rps, - rate_func=linear - ) - self.wait() - self.graph = graph - - def draw_derivative_from_slopes(self): - ss_group = self.get_secant_slope_group( - 0, self.graph, - dx = 0.01, - secant_line_color = RED, - ) - deriv_graph = self.get_graph(np.cos, color = DERIVATIVE_COLOR) - v_line = DashedLine( - self.graph_origin, self.coords_to_point(0, 1), - color = RED - ) - - self.play(ShowCreation(ss_group, lag_ratio = 0)) - self.play(ShowCreation(v_line)) - self.wait() - last_theta = 0 - next_theta = np.pi/2 - while last_theta < self.x_max: - deriv_copy = deriv_graph.copy() - self.animate_secant_slope_group_change( - ss_group, - target_x = next_theta, - added_anims = [ - ShowCreation( - deriv_copy, - rate_func = lambda t : interpolate( - last_theta/self.x_max, - next_theta/self.x_max, - smooth(t) - ), - run_time = 3, - ), - UpdateFromFunc( - v_line, - lambda v : self.v_line_update(v, deriv_copy), - run_time = 3 - ), - ] - ) - self.wait() - if next_theta == 2*np.pi: - words = TextMobject("Looks a lot like $\\cos(\\theta)$") - words.next_to(self.graph_origin, RIGHT) - words.to_edge(UP) - arrow = Arrow( - words.get_bottom(), - deriv_graph.point_from_proportion(0.45) - ) - VGroup(words, arrow).set_color(deriv_graph.get_color()) - self.play( - Write(words), - ShowCreation(arrow) - ) - self.remove(deriv_copy) - last_theta = next_theta - next_theta += np.pi/2 - self.add(deriv_copy) - - self.deriv_graph = deriv_copy - - def alter_derivative_graph(self): - func_list = [ - lambda x : 0.5*(np.cos(x)**3 + np.cos(x)), - lambda x : 0.75*(np.sign(np.cos(x))*np.cos(x)**2 + np.cos(x)), - lambda x : 2*np.cos(x), - lambda x : np.cos(x), - ] - for func in func_list: - new_graph = self.get_graph(func, color = DERIVATIVE_COLOR) - self.play( - Transform(self.deriv_graph, new_graph), - run_time = 2 - ) - self.wait() - - ###### - - def v_line_update(self, v_line, graph): - point = graph.point_from_proportion(1) - drop_point = point[0]*RIGHT - v_line.put_start_and_end_on(drop_point, point) - return v_line - - def setup_axes(self): - GraphScene.setup_axes(self) - self.x_axis.remove(self.x_axis.numbers) - self.remove(self.x_axis.numbers) - for x in range(1, 4): - if x == 1: - label = TexMobject("\\pi") - else: - label = TexMobject("%d\\pi"%x) - label.next_to(self.coords_to_point(x*np.pi, 0), DOWN, MED_LARGE_BUFF) - self.add(label) - self.x_axis_label_mob.set_color(YELLOW) - -class LookToFunctionsMeaning(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Look to the function's - actual meaning - """) - self.change_student_modes(*["pondering"]*3) - self.wait(3) - -class DerivativeFromZoomingInOnSine(IntroduceUnitCircleWithSine, ZoomedScene): - CONFIG = { - "zoom_factor" : 10, - "zoomed_canvas_frame_shape" : (3, 4.5), - "include_radial_line_dot" : False, - "remove_angle_label" : False, - "theta_label" : "", - "line_class" : Line, - "example_radians" : 1.0, - "zoomed_canvas_corner_buff" : SMALL_BUFF, - "d_theta" : 0.05, - } - def construct(self): - self.setup_axes() - self.add_title() - self.introduce_unit_circle() - self.draw_example_radians() - self.label_sine() - - self.zoom_in() - self.perform_nudge() - self.show_similar_triangles() - self.analyze_ratios() - - def zoom_in(self): - self.activate_zooming() - self.little_rectangle.next_to(self.radial_line.get_end(), UP, LARGE_BUFF) - self.play(*list(map(FadeIn, [ - self.little_rectangle, self.big_rectangle - ]))) - self.play( - self.little_rectangle.move_to, - self.radial_line.get_end(), DOWN+RIGHT, - self.little_rectangle.shift, - SMALL_BUFF*(DOWN+RIGHT) - ) - self.wait() - - def perform_nudge(self): - d_theta_arc = Arc( - start_angle = self.example_radians, - angle = self.d_theta, - radius = self.unit_length, - color = MAROON_B, - stroke_width = 6 - ) - d_theta_arc.scale(self.zoom_factor) - d_theta_brace = Brace( - d_theta_arc, - rotate_vector(RIGHT, self.example_radians) - ) - d_theta_label = TexMobject("d\\theta") - d_theta_label.next_to( - d_theta_brace.get_center(), d_theta_brace.direction, - MED_LARGE_BUFF - ) - d_theta_label.set_color(d_theta_arc.get_color()) - - group = VGroup(d_theta_arc, d_theta_brace, d_theta_label) - group.scale(1./self.zoom_factor) - - point = self.radial_line.get_end() - nudged_point = d_theta_arc.point_from_proportion(1) - interim_point = nudged_point[0]*RIGHT+point[1]*UP - h_line = DashedLine( - interim_point, point, - dash_length = 0.01 - ) - d_sine_line = Line(interim_point, nudged_point, color = DERIVATIVE_COLOR) - d_sine_brace = Brace(Line(ORIGIN, UP), LEFT) - d_sine_brace.set_height(d_sine_line.get_height()) - d_sine_brace.next_to(d_sine_line, LEFT, buff = SMALL_BUFF/self.zoom_factor) - d_sine = TexMobject("d(\\sin(\\theta))") - d_sine.set_color(d_sine_line.get_color()) - d_sine.set_width(1.5*self.d_theta*self.unit_length) - d_sine.next_to(d_sine_brace, LEFT, SMALL_BUFF/self.zoom_factor) - - self.play(ShowCreation(d_theta_arc)) - self.play( - GrowFromCenter(d_theta_brace), - FadeIn(d_theta_label) - ) - self.wait() - self.play( - ShowCreation(h_line), - ShowCreation(d_sine_line) - ) - self.play( - GrowFromCenter(d_sine_brace), - Write(d_sine) - ) - self.wait() - - self.little_triangle = Polygon( - nudged_point, point, interim_point - ) - self.d_theta_group = VGroup(d_theta_brace, d_theta_label) - self.d_sine_group = VGroup(d_sine_brace, d_sine) - - def show_similar_triangles(self): - little_triangle = self.little_triangle - big_triangle = Polygon( - self.graph_origin, - self.radial_line.get_end(), - self.radial_line.get_end()[0]*RIGHT, - ) - for triangle in little_triangle, big_triangle: - triangle.set_color(GREEN) - triangle.set_fill(GREEN, opacity = 0.5) - big_triangle_copy = big_triangle.copy() - big_triangle_copy.next_to(ORIGIN, UP+LEFT) - - new_angle_label = self.angle_label.copy() - new_angle_label.scale( - little_triangle.get_width()/big_triangle.get_height() - ) - new_angle_label.rotate(-np.pi/2) - new_angle_label.shift(little_triangle.points[0]) - new_angle_label[1].rotate_in_place(np.pi/2) - - little_triangle_lines = VGroup(*[ - Line(*list(map(little_triangle.get_corner, pair))) - for pair in [ - (DOWN+RIGHT, UP+LEFT), - (UP+LEFT, DOWN+LEFT) - ] - ]) - little_triangle_lines.set_color(little_triangle.get_color()) - - self.play(DrawBorderThenFill(little_triangle)) - self.play( - little_triangle.scale, 2, little_triangle.get_corner(DOWN+RIGHT), - little_triangle.set_color, YELLOW, - rate_func = there_and_back - ) - self.wait() - groups = [self.d_theta_group, self.d_sine_group] - for group, line in zip(groups, little_triangle_lines): - self.play(ApplyMethod( - line.rotate_in_place, np.pi/12, - rate_func = wiggle, - remover = True, - )) - self.play( - group.scale, 1.2, group.get_corner(DOWN+RIGHT), - group.set_color, YELLOW, - rate_func = there_and_back, - ) - self.wait() - - self.play(ReplacementTransform( - little_triangle.copy().set_fill(opacity = 0), - big_triangle_copy, - path_arc = np.pi/2, - run_time = 2 - )) - self.wait() - self.play( - ReplacementTransform(big_triangle_copy, big_triangle), - Animation(self.angle_label) - ) - self.wait() - self.play( - self.radial_line.rotate_in_place, np.pi/12, - Animation(big_triangle), - rate_func = wiggle, - ) - self.wait() - self.play( - ReplacementTransform( - big_triangle.copy().set_fill(opacity = 0), - little_triangle, - path_arc = -np.pi/2, - run_time = 3, - ), - ReplacementTransform( - self.angle_label.copy(), - new_angle_label, - path_arc = -np.pi/2, - run_time = 3, - ), - ) - self.play( - new_angle_label.scale_in_place, 2, - new_angle_label.set_color, RED, - rate_func = there_and_back - ) - self.wait() - - def analyze_ratios(self): - d_ratio = TexMobject("\\frac{d(\\sin(\\theta))}{d\\theta} = ") - VGroup(*d_ratio[:9]).set_color(GREEN) - VGroup(*d_ratio[10:12]).set_color(MAROON_B) - trig_ratio = TexMobject("\\frac{\\text{Adj.}}{\\text{Hyp.}}") - VGroup(*trig_ratio[:4]).set_color(GREEN) - VGroup(*trig_ratio[5:9]).set_color(MAROON_B) - cos = TexMobject("= \\cos(\\theta)") - cos.add_background_rectangle() - - group = VGroup(d_ratio, trig_ratio, cos) - group.arrange() - group.next_to( - self.title, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - - for mob in group: - self.play(Write(mob)) - self.wait() - -class TryWithCos(Scene): - def construct(self): - words = TextMobject("What about $\\cos(\\theta)$?") - words.set_color(YELLOW) - self.play(Write(words)) - self.wait() - -class NextVideo(TeacherStudentsScene): - def construct(self): - series = VideoSeries() - next_video = series[3] - series.to_edge(UP) - - d_sum = TexMobject("\\frac{d}{dx}(x^3 + x^2)") - d_product = TexMobject("\\frac{d}{dx} \\sin(x)x^2") - d_composition = TexMobject("\\frac{d}{dx} \\cos\\left(\\frac{1}{x}\\right)") - - group = VGroup(d_sum, d_product, d_composition) - group.arrange(RIGHT, buff = 2*LARGE_BUFF) - group.next_to(VGroup(*self.get_pi_creatures()), UP, buff = LARGE_BUFF) - - self.play( - FadeIn( - series, - lag_ratio = 0.5, - run_time = 3, - ), - *[ - ApplyMethod(pi.look_at, next_video) - for pi in self.get_pi_creatures() - ] - ) - self.play( - next_video.set_color, YELLOW, - next_video.shift, MED_LARGE_BUFF*DOWN - ) - self.wait() - for mob in group: - self.play( - Write(mob, run_time = 1), - *[ - ApplyMethod(pi.look_at, mob) - for pi in self.get_pi_creatures() - ] - ) - self.wait(3) - -class Chapter3PatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "CrypticSwarm ", - "Yu Jun", - "Shelby Doolittle", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Markus Persson", - "Dan Buchoff", - "Derek Dai", - "Joseph John Cox", - "Luc Ritchie", - "Guido Gambardella", - "Jerry Ling", - "Mark Govea", - "Vecht ", - "Jonathan Eppele", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Kirk Werklund", - "Ripta Pasay", - "Felipe Diniz", - ] - } - -class Promotion(PiCreatureScene): - CONFIG = { - "seconds_to_blink" : 5, - } - def construct(self): - url = TextMobject("https://brilliant.org/3b1b/") - url.to_corner(UP+LEFT) - - rect = Rectangle(height = 9, width = 16) - rect.set_height(5.5) - rect.next_to(url, DOWN) - rect.to_edge(LEFT) - - self.play( - Write(url), - self.pi_creature.change, "raise_right_hand" - ) - self.play(ShowCreation(rect)) - self.wait(2) - self.change_mode("thinking") - self.wait() - self.look_at(url) - self.wait(10) - self.change_mode("happy") - self.wait(10) - self.change_mode("raise_right_hand") - self.wait(10) - -class Thumbnail(NudgeSideLengthOfCube): - def construct(self): - self.introduce_cube() - VGroup(*self.get_mobjects()).to_edge(DOWN) - - formula = TexMobject( - "\\frac{d(x^3)}{dx} = 3x^2" - ) - VGroup(*formula[:5]).set_color(YELLOW) - VGroup(*formula[-3:]).set_color(GREEN_B) - formula.set_width(FRAME_X_RADIUS-1) - formula.to_edge(RIGHT) - self.add(formula) - - title = TextMobject("Geometric derivatives") - title.set_width(FRAME_WIDTH-1) - title.to_edge(UP) - self.add(title) - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eoc/chapter4.py b/from_3b1b/old/eoc/chapter4.py deleted file mode 100644 index c36b606c..00000000 --- a/from_3b1b/old/eoc/chapter4.py +++ /dev/null @@ -1,2286 +0,0 @@ -from manimlib.imports import * - -SINE_COLOR = BLUE -X_SQUARED_COLOR = GREEN -SUM_COLOR = YELLOW -PRODUCT_COLOR = YELLOW - -class Chapter4OpeningQuote(OpeningQuote): - CONFIG = { - "quote" : [ - "Using the chain rule is like peeling an onion: ", - "you have to deal with each layer at a time, and ", - "if it is too big you will start crying." - ], - "author" : "(Anonymous professor)" - } - -class TransitionFromLastVideo(TeacherStudentsScene): - def construct(self): - simple_rules = VGroup(*list(map(TexMobject, [ - "\\frac{d(x^3)}{dx} = 3x^2", - "\\frac{d(\\sin(x))}{dx} = \\cos(x)", - "\\frac{d(1/x)}{dx} = -\\frac{1}{x^2}", - ]))) - - combination_rules = VGroup(*[ - TexMobject("\\frac{d}{dx}\\left(%s\\right)"%tex) - for tex in [ - "\\sin(x) + x^2", - "\\sin(x)(x^2)", - "\\sin(x^2)", - ] - ]) - for rules in simple_rules, combination_rules: - rules.arrange(buff = LARGE_BUFF) - rules.next_to(self.get_teacher(), UP, buff = MED_LARGE_BUFF) - rules.to_edge(LEFT) - - series = VideoSeries() - series.to_edge(UP) - last_video = series[2] - last_video.save_state() - this_video = series[3] - brace = Brace(last_video) - - #Simple rules - self.add(series) - self.play( - FadeIn(brace), - last_video.set_color, YELLOW - ) - for rule in simple_rules: - self.play( - Write(rule, run_time = 2), - self.get_teacher().change_mode, "raise_right_hand", - *[ - ApplyMethod(pi.change_mode, "pondering") - for pi in self.get_students() - ] - ) - self.wait() - self.wait(2) - self.play(simple_rules.replace, last_video) - self.play( - last_video.restore, - Animation(simple_rules), - brace.next_to, this_video, DOWN, - this_video.set_color, YELLOW - ) - - #Combination rules - self.play( - Write(combination_rules), - *[ - ApplyMethod(pi.change_mode, "confused") - for pi in self.get_students() - ] - ) - self.wait(2) - for rule in combination_rules: - interior = VGroup(*rule[5:-1]) - added_anims = [] - if rule is combination_rules[-1]: - inner_func = VGroup(*rule[-4:-2]) - self.play(inner_func.shift, 0.5*UP) - added_anims = [ - inner_func.shift, 0.5*DOWN, - inner_func.set_color, YELLOW - ] - self.play( - interior.set_color, YELLOW, - *added_anims, - lag_ratio = 0.5 - ) - self.wait() - self.wait() - - #Address subtraction and division - subtraction = TexMobject("\\sin(x)", "-", "x^2") - decomposed_subtraction = TexMobject("\\sin(x)", "+(-1)\\cdot", "x^2") - pre_division = TexMobject("\\frac{\\sin(x)}{x^2}") - division = VGroup( - VGroup(*pre_division[:6]), - VGroup(*pre_division[6:7]), - VGroup(*pre_division[7:]), - ) - pre_decomposed_division = TexMobject("\\sin(x)\\cdot\\frac{1}{x^2}") - decomposed_division = VGroup( - VGroup(*pre_decomposed_division[:6]), - VGroup(*pre_decomposed_division[6:9]), - VGroup(*pre_decomposed_division[9:]), - ) - for mob in subtraction, decomposed_subtraction, division, decomposed_division: - mob.next_to( - VGroup(self.get_teacher(), self.get_students()[-1]), - UP, buff = MED_LARGE_BUFF - ) - - top_group = VGroup(series, simple_rules, brace) - combination_rules.save_state() - self.play( - top_group.next_to, FRAME_Y_RADIUS*UP, UP, - combination_rules.to_edge, UP, - ) - pairs = [ - (subtraction, decomposed_subtraction), - (division, decomposed_division) - ] - for question, answer in pairs: - self.play( - Write(question), - combination_rules.fade, 0.2, - self.get_students()[2].change_mode, "raise_right_hand", - self.get_teacher().change_mode, "plain", - ) - self.wait() - answer[1].set_color(GREEN) - self.play( - Transform(question, answer), - self.get_teacher().change_mode, "hooray", - self.get_students()[2].change_mode, "plain", - ) - self.wait() - self.play(FadeOut(question)) - - #Monstrous expression - monster = TexMobject( - "\\Big(", - "e^{\\sin(x)} \\cdot", - "\\cos\\Big(", - "\\frac{1}{x^3}", - " + x^3", - "\\Big)", - "\\Big)^4" - ) - monster.next_to(self.get_pi_creatures(), UP) - parts = [ - VGroup(*monster[3][2:]), - VGroup(*monster[3][:2]), - monster[4], - VGroup(monster[2], monster[5]), - monster[1], - VGroup(monster[0], monster[6]) - ] - modes = 3*["erm"] + 3*["pleading"] - for part, mode in zip(parts, modes): - self.play( - FadeIn(part, lag_ratio = 0.5), - self.get_teacher().change_mode, "raise_right_hand", - *[ - ApplyMethod(pi.change_mode, mode) - for pi in self.get_students() - ] - ) - self.wait() - self.change_student_modes(*["happy"]*3) - words = list(map(TextMobject, [ - "composition", "product", - "composition", "sum", - "composition" - ])) - - for word, part in zip(words, reversed(parts)): - word.set_color(YELLOW) - word.next_to(monster, UP) - self.play( - FadeIn(word), - part.scale_in_place, 1.2, - part.set_color, YELLOW - ) - self.wait() - self.play(*list(map(FadeOut, [word, part]))) - self.play(FadeOut(parts[0])) - - #Bring back combinations - self.play( - combination_rules.restore, - *[ - ApplyMethod(pi_creature.change_mode, "pondering") - for pi_creature in self.get_pi_creatures() - ] - ) - self.wait(2) - -class DampenedSpring(Scene): - def construct(self): - compact_spring, extended_spring = [ - ParametricFunction( - lambda t : (t/denom)*RIGHT+np.sin(t)*UP+np.cos(t)*OUT, - t_max = 12*np.pi, - color = GREY, - ).shift(3*LEFT) - for denom in (12.0, 2.0) - ] - for spring in compact_spring, extended_spring: - spring.scale(0.5) - spring.rotate(np.pi/6, UP) - spring.set_color(GREY) - spring.shift(-spring.points[0] + 3*LEFT) - - moving_spring = compact_spring.copy() - - def update_spring(spring, a): - spring.interpolate( - compact_spring, - extended_spring, - 0.5*(np.exp(-4*a)*np.cos(40*a)+1) - ) - - equation = TexMobject( - "\\text{Length} = 2 + e^{-4t}\\cos(20t)" - ) - equation.to_edge(UP) - - - self.add(moving_spring, equation) - self.play(UpdateFromAlphaFunc( - moving_spring, update_spring, run_time = 10, - rate_func=linear - )) - self.wait() - -class ComingUp(Scene): - def construct(self): - rect = Rectangle(height = 9, width = 16) - rect.set_stroke(WHITE) - rect.set_height(FRAME_HEIGHT-2) - title = TextMobject("Coming up...") - title.to_edge(UP) - rect.next_to(title, DOWN) - - self.play(Write(title)) - self.play(ShowCreation(rect)) - self.wait() - -class PreSumRuleDiscussion(Scene): - def construct(self): - title = TextMobject("Sum rule") - title.to_edge(UP) - self.add(title) - - specific = TexMobject( - "\\frac{d}{dx}(", "\\sin(x)", "+", "x^2", ")", - "=", "\\cos(x)", "+", "2x" - ) - general = TexMobject( - "\\frac{d}{dx}(", "g(x)", "+", "h(x)", ")", - "=", "\\frac{dg}{dx}", "+", "\\frac{dh}{dx}" - ) - for formula in specific, general: - formula[1].set_color(SINE_COLOR) - formula[6].set_color(SINE_COLOR) - formula[3].set_color(X_SQUARED_COLOR) - formula[8].set_color(X_SQUARED_COLOR) - VGroup(specific, general).arrange(DOWN, buff = LARGE_BUFF) - - #Add on rules - self.add(specific) - for i in 0, 4, 5: - self.add(general[i]) - self.wait(2) - for indices in [(1, 2, 3), (6,), (7, 8)]: - self.play(*[ - ReplacementTransform( - specific[i].copy(), general[i] - ) - for i in indices - ]) - self.wait() - - #Highlight parts - for i in 1, 3, -1, 6, 8: - if i < 0: - self.wait() - else: - part = specific[i] - self.play( - part.set_color, YELLOW, - part.scale_in_place, 1.2, - rate_func = there_and_back - ) - self.wait() - -class SumRule(GraphScene): - CONFIG = { - "x_labeled_nums" : [], - "y_labeled_nums" : [], - "y_axis_label" : "", - "x_max" : 4, - "x_axis_width" : FRAME_WIDTH, - "y_max" : 3, - "graph_origin" : 2.5*DOWN + 2.5*LEFT, - "graph_label_x_value" : 1.5, - "example_input" : 0.5, - "example_input_string" : "0.5", - "dx" : 0.05, - "v_lines_x_min" : -1, - "v_lines_x_max" : 2, - "graph_scale_factor" : 2, - "tex_scale_factor" : 0.8, - } - def construct(self): - self.write_function() - self.add_graphs() - self.zoom_in_on_graph() - self.show_example_stacking() - self.show_df() - self.expand_derivative() - - def write_function(self): - func_mob = TexMobject("f(x)", "=", "\\sin(x)", "+", "x^2") - func_mob.scale(self.tex_scale_factor) - func_mob.set_color_by_tex("f(x)", SUM_COLOR) - func_mob.set_color_by_tex("\\sin(x)", SINE_COLOR) - func_mob.set_color_by_tex("x^2", X_SQUARED_COLOR) - func_mob.to_corner(UP+LEFT) - self.add(func_mob) - - self.func_mob = func_mob - - def add_graphs(self): - self.setup_axes() - sine_graph = self.get_graph(np.sin, color = SINE_COLOR) - parabola = self.get_graph(lambda x : x**2, color = X_SQUARED_COLOR) - sum_graph = self.get_graph( - lambda x : np.sin(x) + x**2, - color = SUM_COLOR - ) - sine_label = self.get_graph_label( - sine_graph, "\\sin(x)", - x_val = self.graph_label_x_value, - direction = UP+RIGHT, - buff = 0, - ) - sine_label.scale_in_place(self.tex_scale_factor) - parabola_label = self.get_graph_label( - parabola, "x^2", x_val = self.graph_label_x_value, - ) - parabola_label.scale_in_place(self.tex_scale_factor) - - graphs = VGroup(sine_graph, parabola) - labels = VGroup(sine_label, parabola_label) - for label in labels: - label.add_background_rectangle() - - for graph, label in zip(graphs, labels): - self.play( - ShowCreation(graph), - Write(label) - ) - self.wait() - - num_lines = (self.v_lines_x_max-self.v_lines_x_min)/self.dx - sine_v_lines, parabox_v_lines = v_line_sets = [ - self.get_vertical_lines_to_graph( - graph, - x_min = self.v_lines_x_min, - x_max = self.v_lines_x_max, - num_lines = num_lines, - stroke_width = 2 - ) - for graph in graphs - ] - sine_v_lines.shift(0.02*RIGHT) - for v_lines in v_line_sets: - self.play(ShowCreation(v_lines), Animation(labels)) - self.wait() - self.play(*it.chain( - [ - ApplyMethod(l2.move_to, l1.get_top(), DOWN) - for l1, l2, in zip(*v_line_sets) - ], - [graph.fade for graph in graphs], - [Animation(labels)] - )) - self.wait() - - self.play(ShowCreation(sum_graph)) - self.wait() - - self.sum_graph = sum_graph - self.parabola = parabola - self.sine_graph = sine_graph - self.graph_labels = labels - self.v_line_sets = v_line_sets - - def zoom_in_on_graph(self): - graph_parts = VGroup( - self.axes, - self.sine_graph, self.parabola, self.sum_graph, - *self.v_line_sets - ) - graph_parts.remove(self.func_mob, *self.graph_labels) - graph_parts.generate_target() - self.graph_labels.generate_target() - for mob in graph_parts, self.graph_labels: - mob.target.scale( - self.graph_scale_factor, - about_point = self.graph_origin, - ) - for mob in self.graph_labels.target: - mob.scale( - 1./self.graph_scale_factor, - about_point = mob.get_bottom() - ) - mob.shift_onto_screen() - self.play(*list(map(MoveToTarget, [ - graph_parts, self.graph_labels - ]))) - self.wait() - - def show_example_stacking(self): - v_line_sets = self.v_line_sets - num_lines = len(v_line_sets[0]) - example_v_lines, nudged_v_lines = [ - VGroup(*[v_lines[index] for v_lines in v_line_sets]) - for index in (num_lines/2, num_lines/2+1) - ] - for line in nudged_v_lines: - line.save_state() - sine_lines, parabola_lines = [ - VGroup(example_v_lines[i], nudged_v_lines[i]) - for i in (0, 1) - ] - faders = VGroup(*[line for line in it.chain(*v_line_sets) if line not in example_v_lines]) - label_groups = [] - for line, tex, vect in zip(sine_lines, ["", "+dx"], [LEFT, RIGHT]): - dot = Dot(line.get_bottom(), radius = 0.03, color = YELLOW) - label = TexMobject( - "x=" + str(self.example_input) + tex - ) - label.next_to(dot, DOWN+vect, buff = MED_LARGE_BUFF) - arrow = Arrow( - label.get_corner(UP-vect), dot, - buff = SMALL_BUFF, - color = WHITE, - tip_length = 0.1 - ) - label_groups.append(VGroup(label, arrow, dot)) - - line_tex_direction_triplets = [ - (sine_lines[0], "\\sin(0.5)", LEFT), - (sine_lines[1], "\\sin(0.5+dx)", RIGHT), - (parabola_lines[0], "(0.5)^2", LEFT), - (parabola_lines[1], "(0.5+dx)^2", RIGHT), - ] - for line, tex, direction in line_tex_direction_triplets: - line.brace = Brace( - line, direction, - buff = SMALL_BUFF, - min_num_quads = 2, - ) - line.brace.set_color(line.get_color()) - line.brace.add_background_rectangle() - line.brace_text = line.brace.get_text("$%s$"%tex) - line.brace_text.scale( - self.tex_scale_factor, - about_point = line.brace_text.get_edge_center(-direction) - ) - line.brace_text.add_background_rectangle() - line.brace_anim = MaintainPositionRelativeTo( - VGroup(line.brace, line.brace_text), line - ) - - ##Look at example lines - self.play( - example_v_lines.set_stroke, None, 4, - faders.fade, - Animation(self.graph_labels), - Write(label_groups[0]), - ) - for line in example_v_lines: - line.save_state() - self.wait() - self.play( - GrowFromCenter(sine_lines[0].brace), - Write(sine_lines[0].brace_text), - ) - self.wait() - self.play( - sine_lines[0].shift, UP+4*LEFT, - sine_lines[0].brace_anim, - parabola_lines[0].move_to, sine_lines[0], DOWN - ) - self.wait() - parabola_lines[0].brace_anim.update(1) - self.play( - GrowFromCenter(parabola_lines[0].brace), - Write(parabola_lines[0].brace_text), - ) - self.wait() - self.play(*it.chain(*[ - [line.restore, line.brace_anim] - for line in example_v_lines - ])) - - ## Nudged_lines - self.play( - Write(label_groups[1]), - *it.chain(*[ - [line.restore, line.set_stroke, None, 4] - for line in nudged_v_lines - ]) - ) - self.wait() - for line in nudged_v_lines: - self.play( - GrowFromCenter(line.brace), - Write(line.brace_text) - ) - self.wait() - - self.sine_lines = sine_lines - self.parabola_lines = parabola_lines - - def show_df(self): - sine_lines = self.sine_lines - parabola_lines = self.parabola_lines - - df, equals, d_sine, plus, d_x_squared = deriv_mob = TexMobject( - "df", "=", "d(\\sin(x))", "+", "d(x^2)" - ) - df.set_color(SUM_COLOR) - d_sine.set_color(SINE_COLOR) - d_x_squared.set_color(X_SQUARED_COLOR) - deriv_mob.scale(self.tex_scale_factor) - deriv_mob.next_to( - self.func_mob, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - for submob in deriv_mob: - submob.add_to_back(BackgroundRectangle(submob)) - - df_lines = self.show_difference(parabola_lines, df, equals) - self.wait() - self.play(FadeOut(df_lines)) - self.play( - parabola_lines[0].shift, - (parabola_lines[1].get_bottom()[1]-parabola_lines[0].get_bottom()[1])*UP, - parabola_lines[0].brace_anim - ) - d_sine_lines = self.show_difference(sine_lines, d_sine, plus) - d_x_squared_lines = self.show_difference(parabola_lines, d_x_squared, VGroup()) - self.wait() - - self.deriv_mob = deriv_mob - self.d_sine_lines = d_sine_lines - self.d_x_squared_lines = d_x_squared_lines - - def show_difference(self, v_lines, target_tex, added_tex): - distance = v_lines[1].get_top()[1]-v_lines[0].get_top()[1] - h_lines = VGroup(*[ - DashedLine(ORIGIN, 2*RIGHT, stroke_width = 3) - for x in range(2) - ]) - h_lines.arrange(DOWN, buff = distance) - h_lines.move_to(v_lines[1].get_top(), UP+RIGHT) - - brace = Brace(h_lines, LEFT) - brace_text = target_tex.copy() - brace_text.next_to(brace, LEFT) - - self.play(ShowCreation(h_lines)) - self.play(GrowFromCenter(brace), Write(brace_text)) - self.wait() - self.play( - ReplacementTransform(brace_text.copy(), target_tex), - Write(added_tex) - ) - return VGroup(h_lines, brace, brace_text) - - def expand_derivative(self): - expanded_deriv = TexMobject( - "df", "=", "\\cos(x)", "\\,dx", "+", "2x", "\\,dx" - ) - expanded_deriv.set_color_by_tex("df", SUM_COLOR) - VGroup(*expanded_deriv[2:4]).set_color(SINE_COLOR) - VGroup(*expanded_deriv[5:7]).set_color(X_SQUARED_COLOR) - expanded_deriv.scale(self.tex_scale_factor) - expanded_deriv.next_to( - self.deriv_mob, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - background_rect = BackgroundRectangle(expanded_deriv) - - rearranged_deriv = TexMobject( - "{df \\over", "dx}", "=", "\\cos(x)", "+", "2x" - ) - rearranged_deriv[0].set_color(SUM_COLOR) - rearranged_deriv[3].set_color(SINE_COLOR) - rearranged_deriv[5].set_color(X_SQUARED_COLOR) - rearranged_deriv.scale(self.tex_scale_factor) - rearranged_deriv.move_to(expanded_deriv, UP+LEFT) - deriv_target_indices = [0, 2, 3, 1, 4, 5, 1] - - self.play( - FadeIn( - background_rect, - rate_func = squish_rate_func(smooth, 0.6, 1) - ), - Write(expanded_deriv) - ) - self.wait() - - tex_group_pairs = [ - ("\\cos(0.5)dx", self.d_sine_lines), - ("2(0.5)dx", self.d_x_squared_lines), - ] - def indicate(mob): - self.play( - mob.set_color, YELLOW, - mob.scale_in_place, 1.2, - rate_func = there_and_back - ) - for tex, group in tex_group_pairs: - old_label = group[-1] - new_label = TexMobject(tex) - pre_dx = VGroup(*new_label[:-2]) - dx = VGroup(*new_label[-2:]) - new_label.add_background_rectangle() - new_label.scale(self.tex_scale_factor) - new_label.move_to(old_label, RIGHT) - new_label.set_color(old_label.get_color()) - - self.play(FocusOn(old_label)) - indicate(old_label) - self.wait() - self.play(FadeOut(old_label)) - self.play(FadeIn(new_label)) - self.wait() - indicate(dx) - self.wait() - indicate(pre_dx) - self.wait() - self.wait() - self.play(*[ - Transform( - expanded_deriv[i], rearranged_deriv[j], - path_arc = -np.pi/2 - ) - for i, j in enumerate(deriv_target_indices) - ]) - self.wait() - -class DiscussProducts(TeacherStudentsScene): - def construct(self): - wrong_product_rule = TexMobject( - "\\frac{d(\\sin(x)x^2)}{dx}", - "\\ne", - "\\left(\\frac{d(\\sin(x))}{dx}\\right)", - "\\left(\\frac{d(x^2)}{dx}\\right)", - ) - not_equals = wrong_product_rule[1] - wrong_product_rule[2].set_color(SINE_COLOR) - wrong_product_rule[3].set_color(X_SQUARED_COLOR) - wrong_product_rule.next_to( - self.get_teacher().get_corner(UP+LEFT), - UP, - buff = MED_LARGE_BUFF - ).shift_onto_screen() - - self.teacher_says( - "Products are a bit different", - target_mode = "sassy" - ) - self.wait(2) - self.play(RemovePiCreatureBubble( - self.get_teacher(), - target_mode = "raise_right_hand" - )) - self.play(Write(wrong_product_rule)) - self.change_student_modes( - "pondering", "confused", "erm", - added_anims = [ - not_equals.scale_in_place, 1.3, - not_equals.set_color, RED - ] - ) - self.wait() - self.teacher_says( - "Think about the \\\\ underlying meaning", - bubble_kwargs = {"height" : 3}, - added_anims = [ - wrong_product_rule.scale, 0.7, - wrong_product_rule.to_corner, UP+LEFT - ] - ) - self.change_student_modes(*["pondering"]*3) - self.wait(2) - -class NotGraphsForProducts(GraphScene): - CONFIG = { - "y_max" : 25, - "x_max" : 7, - } - def construct(self): - self.setup_axes() - sine_graph = self.get_graph(np.sin, color = SINE_COLOR) - sine_graph.label = self.get_graph_label( - sine_graph, "\\sin(x)", - x_val = 3*np.pi/2, - direction = DOWN - ) - parabola = self.get_graph( - lambda x : x**2, color = X_SQUARED_COLOR - ) - parabola.label = self.get_graph_label( - parabola, "x^2", - x_val = 2.5, - direction = UP+LEFT, - ) - product_graph = self.get_graph( - lambda x : np.sin(x)*(x**2), color = PRODUCT_COLOR - ) - product_graph.label = self.get_graph_label( - product_graph, "\\sin(x)x^2", - x_val = 2.8, - direction = UP+RIGHT, - buff = 0 - ) - - graphs = [sine_graph, parabola, product_graph] - for graph in graphs: - self.play( - ShowCreation(graph), - Write(graph.label, run_time = 2) - ) - self.wait() - - everything = VGroup(*[m for m in self.get_mobjects() if not m.is_subpath]) - words = TextMobject("Not the best visualization") - words.scale(1.5) - words.shift(FRAME_Y_RADIUS*UP/2) - words.add_background_rectangle() - words.set_color(RED) - self.play( - everything.fade, - Write(words) - ) - self.wait() - -class ConfusedMorty(Scene): - def construct(self): - morty = Mortimer() - self.add(morty) - self.wait() - self.play(morty.change_mode, "confused") - self.play(Blink(morty)) - self.wait(2) - -class IntroduceProductAsArea(ReconfigurableScene): - CONFIG = { - "top_func" : np.sin, - "top_func_label" : "\\sin(x)", - "top_func_nudge_label" : "d(\\sin(x))", - "top_func_derivative" : "\\cos(x)", - "side_func" : lambda x : x**2, - "side_func_label" : "x^2", - "side_func_nudge_label" : "d(x^2)", - "side_func_derivative" : "2x", - "x_unit_to_space_unit" : 3, - "box_kwargs" : { - "fill_color" : YELLOW, - "fill_opacity" : 0.75, - "stroke_width" : 1, - }, - "df_box_kwargs" : { - "fill_color" : GREEN, - "fill_opacity" : 0.75, - "stroke_width" : 0, - }, - "box_corner_location" : 6*LEFT+2.5*UP, - "slider_center" : 3.5*RIGHT+2*DOWN, - "slider_width" : 6, - "slider_x_max" : 3, - "x_slider_handle_height" : 0.25, - "slider_handle_color" : BLUE, - "default_x" : .75, - "dx" : 0.1, - "tiny_dx" : 0.01, - } - def construct(self): - self.introduce_box() - self.talk_though_sine() - self.define_f_of_x() - self.nudge_x() - self.write_df() - self.show_thinner_dx() - self.expand_derivative() - self.write_derivative_abstractly() - self.write_mneumonic() - - def introduce_box(self): - box, labels = self.box_label_group = self.get_box_label_group(self.default_x) - self.x_slider = self.get_x_slider(self.default_x) - - self.play(Write(labels)) - self.play(DrawBorderThenFill(box)) - self.wait() - for mob in self.x_slider: - self.play(Write(mob, run_time = 1)) - self.wait() - for new_x in 0.5, 2, self.default_x: - self.animate_x_change( - new_x, run_time = 2 - ) - self.wait() - - def talk_though_sine(self): - x_axis = self.x_slider[0] - graph = FunctionGraph( - np.sin, x_min = 0, x_max = np.pi, - color = SINE_COLOR - ) - scale_factor = self.x_slider.get_width()/self.slider_x_max - graph.scale(scale_factor) - graph.move_to(x_axis.number_to_point(0), DOWN+LEFT) - - label = TexMobject("\\sin(x)") - label.set_color(SINE_COLOR) - label.next_to(graph, UP) - - y_axis = x_axis.copy() - y_axis.remove(*y_axis.numbers) - - v_line = Line(ORIGIN, UP, color = WHITE, stroke_width = 2) - def v_line_update(v_line): - x = x_axis.point_to_number(self.x_slider[1].get_top()) - v_line.set_height(np.sin(x)*scale_factor) - v_line.move_to(x_axis.number_to_point(x), DOWN) - v_line_update(v_line) - - self.play( - Rotate(y_axis, np.pi/2, about_point = y_axis.get_left()), - Animation(x_axis) - ) - self.play( - ShowCreation(graph), - Write(label, run_time = 1) - ) - self.play(ShowCreation(v_line)) - for x, rt in zip([0.25, np.pi/2, 3, self.default_x], [2, 4, 4, 2]): - self.animate_x_change( - x, run_time = rt, - added_anims = [ - UpdateFromFunc(v_line, v_line_update) - ] - ) - self.wait() - self.play(*it.chain( - list(map(FadeOut, [y_axis, graph, label, v_line])), - [Animation(x_axis)] - )) - self.wait() - for x in 1, 0.5, self.default_x: - self.animate_x_change(x) - self.wait() - - def define_f_of_x(self): - f_def = TexMobject( - "f(x)", "=", - self.top_func_label, - self.side_func_label, - "=", - "\\text{Area}" - ) - f_def.to_corner(UP+RIGHT) - f_def[-1].set_color(self.box_kwargs["fill_color"]) - - box, labels = self.box_label_group - - self.play(Write(VGroup(*f_def[:-1]))) - self.play(Transform( - box.copy().set_fill(opacity = 0), f_def[-1], - run_time = 1.5, - )) - self.wait() - - self.f_def = f_def - - def nudge_x(self): - box, labels = self.box_label_group - nudge_label_group = self.get_nudge_label_group() - original_dx = self.dx - self.dx = self.tiny_dx - thin_df_boxes = self.get_df_boxes() - self.dx = original_dx - df_boxes = self.get_df_boxes() - right_box, corner_box, right_box = df_boxes - - self.play(FocusOn(nudge_label_group)) - self.play(*list(map(GrowFromCenter, nudge_label_group))) - self.animate_x_change( - self.default_x+self.dx, - rate_func = there_and_back, - run_time = 2, - added_anims = [Animation(nudge_label_group)] - ) - self.wait() - self.play( - ReplacementTransform(thin_df_boxes, df_boxes), - VGroup(*labels[1]).shift, right_box.get_width()*RIGHT, - ) - self.play( - df_boxes.space_out_submobjects, 1.1, - df_boxes.move_to, box, UP+LEFT, - ) - self.wait() - - self.df_boxes = df_boxes - self.df_box_labels = self.get_df_box_labels(df_boxes) - self.x_slider.add(nudge_label_group) - - def get_nudge_label_group(self): - line, triangle, x_mob = self.x_slider - dx_line = Line(*[ - line.number_to_point(self.x_slider.x_val + num) - for num in (0, self.dx,) - ]) - dx_line.set_stroke( - self.df_box_kwargs["fill_color"], - width = 6 - ) - brace = Brace(dx_line, UP, buff = SMALL_BUFF) - brace.stretch_to_fit_height(0.2) - brace.next_to(dx_line, UP, buff = SMALL_BUFF) - brace.set_stroke(width = 1) - dx = TexMobject("dx") - dx.scale(0.7) - dx.next_to(brace, UP, buff = SMALL_BUFF) - dx.set_color(dx_line.get_color()) - - return VGroup(dx_line, brace, dx) - - def get_df_boxes(self): - box, labels = self.box_label_group - alt_box = self.get_box(self.x_slider.x_val + self.dx) - - h, w = box.get_height(), box.get_width() - dh, dw = alt_box.get_height()-h, alt_box.get_width()-w - heights_and_widths = [(dh, w), (dh, dw), (h, dw)] - vects = [DOWN, DOWN+RIGHT, RIGHT] - df_boxes = VGroup(*[ - Rectangle( - height = height, width = width, **self.df_box_kwargs - ).next_to(box, vect, buff = 0) - for (height, width), vect in zip( - heights_and_widths, vects - ) - ]) - return df_boxes - - def get_df_box_labels(self, df_boxes): - bottom_box, corner_box, right_box = df_boxes - result = VGroup() - quads = [ - (right_box, UP, self.top_func_nudge_label, LEFT), - (corner_box, RIGHT, self.side_func_nudge_label, ORIGIN), - ] - for box, vect, label_tex, aligned_edge in quads: - brace = Brace(box, vect) - label = TexMobject(label_tex) - label.next_to( - brace, vect, - aligned_edge = aligned_edge, - buff = SMALL_BUFF - ) - label.set_color(df_boxes[0].get_color()) - result.add(VGroup(brace, label)) - return result - - def write_df(self): - deriv = TexMobject( - "df", "=", - self.top_func_label, - self.side_func_nudge_label, - "+", - self.side_func_label, - self.top_func_nudge_label, - ) - deriv.scale(0.9) - deriv.next_to(self.f_def, DOWN, buff = LARGE_BUFF) - deriv.to_edge(RIGHT) - for submob, tex in zip(deriv, deriv.expression_parts): - if tex.startswith("d"): - submob.set_color(self.df_box_kwargs["fill_color"]) - bottom_box_area = VGroup(*deriv[2:4]) - right_box_area = VGroup(*deriv[5:7]) - - bottom_box, corner_box, right_box = self.df_boxes - plus = TexMobject("+").set_fill(opacity = 0) - df_boxes_copy = VGroup( - bottom_box.copy(), - plus, - right_box.copy(), - plus.copy(), - corner_box.copy(), - ) - - self.deriv = deriv - self.df_boxes_copy = df_boxes_copy - box, labels = self.box_label_group - self.full_box_parts = VGroup(*it.chain( - [box], self.df_boxes, labels, self.df_box_labels - )) - - self.play(Write(VGroup(*deriv[:2]))) - self.play( - df_boxes_copy.arrange, - df_boxes_copy.set_fill, None, self.df_box_kwargs["fill_opacity"], - df_boxes_copy.next_to, deriv[1] - ) - deriv.submobjects[4] = df_boxes_copy[1] - self.wait() - - self.set_color_right_boxes() - self.set_color_bottom_boxes() - self.describe_bottom_box(bottom_box_area) - self.describe_right_box(right_box_area) - self.ignore_corner() - - # self.add(deriv) - - def set_color_boxes_and_label(self, boxes, label): - boxes.save_state() - label.save_state() - - self.play(GrowFromCenter(label)) - self.play( - boxes.set_color, RED, - label.set_color, RED, - ) - self.play( - label[1].scale_in_place, 1.1, - rate_func = there_and_back - ) - self.play(boxes.restore, label.restore) - self.wait() - - def set_color_right_boxes(self): - self.set_color_boxes_and_label( - VGroup(*self.df_boxes[1:]), - self.df_box_labels[0] - ) - - def set_color_bottom_boxes(self): - self.set_color_boxes_and_label( - VGroup(*self.df_boxes[:-1]), - self.df_box_labels[1] - ) - - def describe_bottom_box(self, bottom_box_area): - bottom_box = self.df_boxes[0] - bottom_box_copy = self.df_boxes_copy[0] - other_box_copies = VGroup(*self.df_boxes_copy[1:]) - top_label = self.box_label_group[1][0] - right_label = self.df_box_labels[1] - - faders = VGroup(*[m for m in self.full_box_parts if m not in [bottom_box, top_label, right_label]]) - faders.save_state() - - self.play(faders.fade, 0.8) - self.wait() - self.play(FocusOn(bottom_box_copy)) - self.play( - ReplacementTransform(bottom_box_copy, bottom_box_area), - other_box_copies.next_to, bottom_box_area, RIGHT - ) - self.wait() - self.play(faders.restore) - - def describe_right_box(self, right_box_area): - right_box = self.df_boxes[2] - right_box_copy = self.df_boxes_copy[2] - right_box_area.next_to(self.df_boxes_copy[1]) - other_box_copies = VGroup(*self.df_boxes_copy[3:]) - top_label = self.df_box_labels[0] - right_label = self.box_label_group[1][1] - - faders = VGroup(*[m for m in self.full_box_parts if m not in [right_box, top_label, right_label]]) - faders.save_state() - - self.play(faders.fade, 0.8) - self.wait() - self.play(FocusOn(right_box_copy)) - self.play( - ReplacementTransform(right_box_copy, right_box_area), - other_box_copies.next_to, right_box_area, DOWN, - MED_SMALL_BUFF, RIGHT, - - ) - self.wait() - self.play(faders.restore) - - def ignore_corner(self): - corner = self.df_boxes[1] - corner.save_state() - corner_copy = VGroup(*self.df_boxes_copy[-2:]) - words = TextMobject("Ignore") - words.set_color(RED) - words.next_to(corner_copy, LEFT, buff = LARGE_BUFF) - words.shift(MED_SMALL_BUFF*DOWN) - arrow = Arrow(words, corner_copy, buff = SMALL_BUFF, color = RED) - - self.play( - corner.set_color, RED, - corner_copy.set_color, RED, - ) - self.wait() - self.play(Write(words), ShowCreation(arrow)) - self.wait() - self.play(*list(map(FadeOut, [words, arrow, corner_copy]))) - self.wait() - corner_copy.set_color(BLACK) - - def show_thinner_dx(self): - self.transition_to_alt_config(dx = self.tiny_dx) - - def expand_derivative(self): - # self.play( - # self.deriv.next_to, self.f_def, DOWN, MED_LARGE_BUFF, - # self.deriv.shift_onto_screen - # ) - # self.wait() - - expanded_deriv = TexMobject( - "df", "=", - self.top_func_label, - self.side_func_derivative, - "\\,dx", - "+", - self.side_func_label, - self.top_func_derivative, - "\\,dx" - ) - final_deriv = TexMobject( - "{df \\over ", "dx}", "=", - self.top_func_label, - self.side_func_derivative, - "+", - self.side_func_label, - self.top_func_derivative, - ) - color = self.deriv[0].get_color() - for new_deriv in expanded_deriv, final_deriv: - for submob, tex in zip(new_deriv, new_deriv.expression_parts): - for substr in "df", "dx", self.top_func_derivative, self.side_func_derivative: - if substr in tex: - submob.set_color(color) - new_deriv.scale(0.9) - new_deriv.next_to(self.deriv, DOWN, buff = MED_LARGE_BUFF) - new_deriv.shift_onto_screen() - - def indicate(mob): - self.play( - mob.scale_in_place, 1.2, - mob.set_color, YELLOW, - rate_func = there_and_back - ) - - - for index in 6, 3: - self.deriv.submobjects.insert( - index+1, self.deriv[index].copy() - ) - non_deriv_indices = list(range(len(expanded_deriv))) - for indices in [(3, 4), (7, 8)]: - top_part = VGroup() - bottom_part = VGroup() - for i in indices: - non_deriv_indices.remove(i) - top_part.add(self.deriv[i].copy()) - bottom_part.add(expanded_deriv[i]) - self.play(top_part.move_to, bottom_part) - self.wait() - indicate(top_part) - self.wait() - self.play(ReplacementTransform(top_part, bottom_part)) - self.wait() - top_part = VGroup() - bottom_part = VGroup() - for i in non_deriv_indices: - top_part.add(self.deriv[i].copy()) - bottom_part.add(expanded_deriv[i]) - self.play(ReplacementTransform( - top_part, bottom_part - )) - - self.wait() - self.play(*[ - ReplacementTransform( - expanded_deriv[i], final_deriv[j], - path_arc = -np.pi/2 - ) - for i, j in [ - (0, 0), - (1, 2), - (2, 3), - (3, 4), - (4, 1), - (5, 5), - (6, 6), - (7, 7), - (8, 1), - ] - ]) - self.wait() - for index in 0, 1, 3, 4, 6, 7: - indicate(final_deriv[index]) - self.wait() - - def write_derivative_abstractly(self): - self.transition_to_alt_config( - return_to_original_configuration = False, - top_func_label = "g(x)", - top_func_nudge_label = "dg", - top_func_derivative = "\\frac{dg}{dx}", - side_func_label = "h(x)", - side_func_nudge_label = "dh", - side_func_derivative = "\\frac{dh}{dx}", - ) - self.wait() - - def write_mneumonic(self): - morty = Mortimer() - morty.scale(0.7) - morty.to_edge(DOWN) - morty.shift(2*LEFT) - words = TextMobject( - "``Left ", "d(Right) ", "+", " Right ", "d(Left)", "''", - arg_separator = "" - ) - VGroup(words[1], words[4]).set_color(self.df_boxes[0].get_color()) - words.scale(0.7) - words.next_to(morty.get_corner(UP+LEFT), UP) - words.shift_onto_screen() - - self.play(FadeIn(morty)) - self.play( - morty.change_mode, "raise_right_hand", - Write(words) - ) - self.wait() - - ############### - - def animate_x_change( - self, target_x, - box_label_group = None, - x_slider = None, - **kwargs - ): - box_label_group = box_label_group or self.box_label_group - x_slider = x_slider or self.x_slider - kwargs["run_time"] = kwargs.get("run_time", 2) - added_anims = kwargs.get("added_anims", []) - - start_x = x_slider.x_val - def update_box_label_group(box_label_group, alpha): - new_x = interpolate(start_x, target_x, alpha) - new_box_label_group = self.get_box_label_group(new_x) - Transform(box_label_group, new_box_label_group).update(1) - - def update_x_slider(x_slider, alpha): - new_x = interpolate(start_x, target_x, alpha) - new_x_slider = self.get_x_slider(new_x) - Transform(x_slider, new_x_slider).update(1) - - self.play( - UpdateFromAlphaFunc( - box_label_group, - update_box_label_group - ), - UpdateFromAlphaFunc( - x_slider, - update_x_slider - ), - *added_anims, - **kwargs - ) - x_slider.x_val = target_x - - def get_x_slider(self, x): - numbers = list(range(int(self.slider_x_max) + 1)) - line = NumberLine( - x_min = 0, - x_max = self.slider_x_max, - unit_size = float(self.slider_width)/self.slider_x_max, - color = GREY, - numbers_with_elongated_ticks = numbers, - tick_frequency = 0.25, - ) - line.add_numbers(*numbers) - line.numbers.next_to(line, UP, buff = SMALL_BUFF) - for number in line.numbers: - number.add_background_rectangle() - line.move_to(self.slider_center) - - triangle = RegularPolygon( - 3, start_angle = np.pi/2, - fill_color = self.slider_handle_color, - fill_opacity = 0.8, - stroke_width = 0, - ) - triangle.set_height(self.x_slider_handle_height) - triangle.move_to(line.number_to_point(x), UP) - - x_mob = TexMobject("x") - x_mob.next_to(triangle, DOWN, buff = SMALL_BUFF) - - result = VGroup(line, triangle, x_mob) - result.x_val = x - return result - - def get_box_label_group(self, x): - box = self.get_box(x) - labels = self.get_box_labels(box) - return VGroup(box, labels) - - def get_box(self, x): - box = Rectangle( - width = self.x_unit_to_space_unit * self.top_func(x), - height = self.x_unit_to_space_unit * self.side_func(x), - **self.box_kwargs - ) - box.move_to(self.box_corner_location, UP+LEFT) - return box - - def get_box_labels(self, box): - result = VGroup() - for label_tex, vect in (self.top_func_label, UP), (self.side_func_label, RIGHT): - brace = Brace(box, vect, min_num_quads = 5) - label = TexMobject(label_tex) - label.next_to(brace, vect, buff = SMALL_BUFF) - result.add(VGroup(brace, label)) - return result - -class WriteDXSquared(Scene): - def construct(self): - term = TexMobject("(...)(dx)^2") - term.set_color(RED) - self.play(Write(term)) - self.wait() - -class MneumonicExample(TeacherStudentsScene): - def construct(self): - d, left, right, rp = deriv_q = TexMobject( - "\\frac{d}{dx}(", "\\sin(x)", "x^2", ")" - ) - deriv_q.to_edge(UP) - - words = TextMobject( - "Left ", "d(Right) ", "+", " Right ", "d(Left)", - arg_separator = "" - ) - deriv = TexMobject("\\sin(x)", "2x", "+", "x^2", "\\cos(x)") - for mob in words, deriv: - VGroup(mob[1], mob[4]).set_color(GREEN) - mob.next_to(deriv_q, DOWN, buff = MED_LARGE_BUFF) - deriv.shift(words[2].get_center()-deriv[2].get_center()) - - self.add(words) - self.play( - Write(deriv_q), - self.get_teacher().change_mode, "raise_right_hand" - ) - self.change_student_modes(*["pondering"]*3) - - left_words = VGroup(*words[:2]) - left_terms = VGroup(*deriv[:2]) - self.play( - left_words.next_to, left_terms, DOWN, - MED_LARGE_BUFF, RIGHT - ) - self.play(ReplacementTransform( - left_words.copy(), left_terms - )) - self.wait() - self.play(*list(map(Indicate, [left, left_words[0], left_terms[0]]))) - self.wait() - self.play(*list(map(Indicate, [right, left_words[1], left_terms[1]]))) - self.wait() - - right_words = VGroup(*words[2:]) - right_terms = VGroup(*deriv[2:]) - self.play( - right_words.next_to, right_terms, DOWN, - MED_LARGE_BUFF, LEFT - ) - self.play(ReplacementTransform( - right_words.copy(), right_terms - )) - self.wait() - self.play(*list(map(Indicate, [right, right_words[1], right_terms[1]]))) - self.wait() - self.play(*list(map(Indicate, [left, right_words[2], right_terms[2]]))) - self.wait(3) - - self.play(self.get_teacher().change_mode, "shruggie") - self.wait() - self.change_student_modes(*["confused"]*3) - self.wait(3) - -class ConstantMultiplication(TeacherStudentsScene): - def construct(self): - question = TextMobject("What about $\\dfrac{d}{dx}(2\\sin(x))$?") - answer = TextMobject("2\\cos(x)") - self.teacher_says(question) - self.wait() - self.student_says( - answer, target_mode = "hooray", - added_anims = [question.copy().to_edge, UP] - ) - self.play(self.get_teacher().change_mode, "happy") - self.change_student_modes("pondering", "hooray", "pondering") - self.wait(3) - -class ConstantMultiplicationFigure(IntroduceProductAsArea): - CONFIG = { - "side_func" : lambda x : 1, - "side_func_label" : "\\text{Constant}", - "side_func_nudge_label" : "", - "side_func_derivative" : "", - "x_unit_to_space_unit" : 3, - "default_x" : 0.5, - "dx" : 0.1 - } - def construct(self): - self.box_label_group = self.get_box_label_group(self.default_x) - self.x_slider = self.get_x_slider(self.default_x) - # df_boxes = self.get_df_boxes() - # df_box_labels = self.get_df_box_labels(df_boxes) - - self.add(self.box_label_group, self.x_slider) - self.nudge_x() - -class ShoveXSquaredInSine(Scene): - def construct(self): - title = TextMobject("Function composition") - title.to_edge(UP) - - sine = TexMobject("g(", "x", ")", "=", "\\sin(", "x", ")") - sine.set_color(SINE_COLOR) - x_squared = TexMobject("h(x)", "=", "x^2") - x_squared.set_color(X_SQUARED_COLOR) - group = VGroup(sine, x_squared) - group.arrange(buff = LARGE_BUFF) - group.shift(UP) - composition = TexMobject( - "g(", "h(x)", ")", "=", "\\sin(", "x^2", ")" - ) - for i in 0, 2, 4, 6: - composition[i].set_color(SINE_COLOR) - for i in 1, 5: - composition[i].set_color(X_SQUARED_COLOR) - composition.next_to(group, DOWN, buff = LARGE_BUFF) - - brace = Brace(VGroup(*composition[-3:]), DOWN) - deriv_q = brace.get_text("Derivative?") - - self.add(group) - self.play(Write(title)) - self.wait() - triplets = [ - [sine, (0, 2), (0, 2)], - [x_squared, (0,), (1,)], - [sine, (3, 4, 6), (3, 4, 6)], - [x_squared, (2,), (5,)] - ] - for premob, pre_indices, comp_indicies in triplets: - self.play(*[ - ReplacementTransform( - premob[i].copy(), composition[j] - ) - for i, j in zip(pre_indices, comp_indicies) - ]) - self.wait() - self.wait() - self.play( - GrowFromCenter(brace), - Write(deriv_q) - ) - self.wait() - -class ThreeLinesChainRule(ReconfigurableScene): - CONFIG = { - "start_x" : 0.5, - "max_x" : 1, - "min_x" : 0, - "top_x" : 3, - "example_x" : 1.5, - "dx" : 0.1, - "line_configs" : [ - { - "func" : lambda x : x, - "func_label" : "x", - "triangle_color" : WHITE, - "center_y" : 3, - "x_min" : 0, - "x_max" : 3, - "numbers_to_show" : list(range(4)), - "numbers_with_elongated_ticks" : list(range(4)), - "tick_frequency" : 0.25, - }, - { - "func" : lambda x : x**2, - "func_label" : "x^2", - "triangle_color" : X_SQUARED_COLOR, - "center_y" : 0.5, - "x_min" : 0, - "x_max" : 10, - "numbers_to_show" : list(range(0, 11)), - "numbers_with_elongated_ticks" : list(range(0, 11, 1)), - "tick_frequency" : 0.25, - }, - { - "func" : lambda x : np.sin(x**2), - "func_label" : "\\sin(x^2)", - "triangle_color" : SINE_COLOR, - "center_y" : -2, - "x_min" : -2, - "x_max" : 2, - "numbers_to_show" : list(range(-2, 3)), - "numbers_with_elongated_ticks" : list(range(-2, 3)), - "tick_frequency" : 0.25, - }, - ], - "line_width" : 8, - "triangle_height" : 0.25, - } - def construct(self): - self.introduce_line_group() - self.draw_function_arrows() - self.talk_through_movement() - self.nudge_x() - self.give_example_of_meaning() - - def introduce_line_group(self): - self.line_group = self.get_line_group(self.start_x) - lines, labels = self.line_group - - for line in lines: - self.play(Write(line, run_time = 2)) - self.wait() - last_label = labels[0].copy() - last_label.to_corner(UP+LEFT) - last_label.set_fill(opacity = 0) - for label in labels: - self.play(ReplacementTransform( - last_label.copy(), label - )) - self.wait() - last_label = label - for x in self.max_x, self.min_x, self.start_x: - self.animate_x_change(x, run_time = 1) - self.wait() - - def draw_function_arrows(self): - lines, line_labels = self.line_group - labels = VGroup(*[ - TexMobject("(\\dots)^2").set_color(X_SQUARED_COLOR), - TexMobject("\\sin(\\dots)").set_color(SINE_COLOR) - ]) - arrows = VGroup() - for lines_subset, label in zip([lines[:2], lines[1:]], labels): - arrow = Arc(start_angle = np.pi/3, angle = -2*np.pi/3) - arrow.add_tip() - arrow.set_color(label.get_color()) - arrow.next_to(VGroup(*lines_subset)) - arrows.add(arrow) - label.next_to(arrow, RIGHT) - - self.play( - ShowCreation(arrow), - Write(label) - ) - self.wait() - self.arrows = arrows - self.arrow_labels = labels - - def talk_through_movement(self): - lines, labels = self.line_group - - self.animate_x_change(self.top_x, run_time = 4) - self.wait() - for label in labels[0], labels[1]: - oval = Circle(color = YELLOW) - oval.replace(label, stretch = True) - oval.scale(2.5) - oval.move_to(label.get_bottom()) - self.play(ShowCreation(oval)) - self.wait() - self.play(FadeOut(oval)) - sine_text = TexMobject("\\sin(9) \\approx 0.412") - sine_text.move_to(labels[-1][-1]) - sine_text.to_edge(DOWN) - sine_arrow = Arrow( - sine_text.get_top(), - labels[-1][0].get_bottom(), - buff = SMALL_BUFF, - ) - self.play( - FadeIn(sine_text), - ShowCreation(sine_arrow) - ) - self.wait(2) - self.play(*list(map(FadeOut, [sine_text, sine_arrow]))) - self.animate_x_change(self.example_x, run_time = 3) - - def nudge_x(self): - lines, labels = self.line_group - def get_value_points(): - return [ - label[0].get_bottom() - for label in labels - ] - starts = get_value_points() - self.animate_x_change(self.example_x + self.dx, run_time = 0) - ends = get_value_points() - self.animate_x_change(self.example_x, run_time = 0) - - nudge_lines = VGroup() - braces = VGroup() - numbers = VGroup() - for start, end, line, label, config in zip(starts, ends, lines, labels, self.line_configs): - color = label[0].get_color() - nudge_line = Line(start, end) - nudge_line.set_stroke(color, width = 6) - brace = Brace(nudge_line, DOWN, buff = SMALL_BUFF) - brace.set_color(color) - func_label = config["func_label"] - if len(func_label) == 1: - text = "$d%s$"%func_label - else: - text = "$d(%s)$"%func_label - brace.text = brace.get_text(text, buff = SMALL_BUFF) - brace.text.set_color(color) - brace.add(brace.text) - - line.add(nudge_line) - nudge_lines.add(nudge_line) - braces.add(brace) - numbers.add(line.numbers) - line.remove(*line.numbers) - dx_brace, dx_squared_brace, dsine_brace = braces - - x_value = str(self.example_x) - x_value_label = TexMobject("=%s"%x_value) - x_value_label.next_to(labels[0][1], RIGHT) - dx_squared_value = TexMobject( - "= 2x\\,dx ", "\\\\ = 2(%s)dx"%x_value - ) - dx_squared_value.shift( - dx_squared_brace.text.get_right()+MED_SMALL_BUFF*RIGHT - \ - dx_squared_value[0].get_left() - ) - dsine_value = TextMobject( - "$=\\cos(%s)$"%self.line_configs[1]["func_label"], - dx_squared_brace.text.get_tex_string() - ) - dsine_value.next_to(dsine_brace.text) - less_than_zero = TexMobject("<0") - less_than_zero.next_to(dsine_brace.text) - - all_x_squared_relevant_labels = VGroup( - dx_squared_brace, dsine_brace, - labels[1], labels[2], - dsine_value, - ) - all_x_squared_relevant_labels.save_state() - - self.play(FadeOut(numbers)) - self.animate_x_change( - self.example_x + self.dx, - run_time = 1, - added_anims = it.chain( - [GrowFromCenter(dx_brace)], - list(map(ShowCreation, nudge_lines)) - ) - ) - self.animate_x_change(self.example_x) - self.wait() - self.play(Write(x_value_label)) - self.wait() - self.play(FocusOn(dx_squared_brace)) - self.play(Write(dx_squared_brace)) - self.wiggle_by_dx() - self.wait() - for part in dx_squared_value: - self.play(Write(part)) - self.wait() - self.play(FadeOut(dx_squared_value)) - self.wait() - #Needs to be part of everything for the reconfiguraiton - dsine_brace.set_fill(opacity = 0) - dsine_value.set_fill(opacity = 0) - self.add(dsine_brace, dsine_value) - self.replace_x_squared_with_h() - self.wait() - self.play(dsine_brace.set_fill, None, 1) - self.discuss_dsine_sign(less_than_zero) - self.wait() - dsine_value.set_fill(opacity = 1) - self.play(Write(dsine_value)) - self.wait() - self.play( - all_x_squared_relevant_labels.restore, - lag_ratio = 0.5, - run_time = 3, - ) - self.__dict__.update(self.__class__.CONFIG) - self.wait() - for mob in dsine_value: - self.play(Indicate(mob)) - self.wait() - - two_x_dx = dx_squared_value[0] - dx_squared = dsine_value[1] - two_x_dx_copy = VGroup(*two_x_dx[1:]).copy() - self.play(FocusOn(two_x_dx)) - self.play(Write(two_x_dx)) - self.play( - two_x_dx_copy.move_to, dx_squared, LEFT, - dx_squared.next_to, dx_squared, UP, - run_time = 2 - ) - self.play(FadeOut(dx_squared)) - for sublist in two_x_dx_copy[:2], two_x_dx_copy[2:]: - self.play(Indicate(VGroup(*sublist))) - self.wait() - self.wait(2) - - self.final_derivative = dsine_value - - def discuss_dsine_sign(self, less_than_zero): - self.wiggle_by_dx() - self.wait() - for x in self.example_x+self.dx, self.example_x: - self.animate_x_change(x, run_time = 2) - self.wait() - if less_than_zero not in self.get_mobjects(): - self.play(Write(less_than_zero)) - else: - self.play(FadeOut(less_than_zero)) - - def replace_x_squared_with_h(self): - new_config = copy.deepcopy(self.__class__.CONFIG) - new_config["line_configs"][1]["func_label"] = "h" - new_config["line_configs"][2]["func_label"] = "\\sin(h)" - self.transition_to_alt_config( - return_to_original_configuration = False, - **new_config - ) - - def give_example_of_meaning(self): - words = TextMobject("For example,") - expression = TexMobject("\\cos(1.5^2)\\cdot 2(1.5)\\,dx") - group = VGroup(words, expression) - group.arrange(DOWN, aligned_edge = LEFT) - group.scale(0.8) - group.to_edge(RIGHT) - arrow = Arrow(group.get_bottom(), self.final_derivative[0].get_top()) - - self.play(*list(map(FadeOut, [self.arrows, self.arrow_labels]))) - self.play(FadeIn(group)) - self.play(ShowCreation(arrow)) - self.wait() - self.wiggle_by_dx() - self.wait() - - - ######## - - def wiggle_by_dx(self, **kwargs): - kwargs["run_time"] = kwargs.get("run_time", 1) - kwargs["rate_func"] = kwargs.get("rate_func", there_and_back) - target_x = self.line_group.x_val + self.dx - self.animate_x_change(target_x, **kwargs) - - def animate_x_change(self, target_x, **kwargs): - #Assume fixed lines, only update labels - kwargs["run_time"] = kwargs.get("run_time", 2) - added_anims = kwargs.get("added_anims", []) - start_x = self.line_group.x_val - def update(line_group, alpha): - lines, labels = line_group - new_x = interpolate(start_x, target_x, alpha) - for line, label, config in zip(lines, labels, self.line_configs): - new_label = self.get_line_label( - line, new_x, **config - ) - Transform(label, new_label).update(1) - line_group.x_val = new_x - self.play( - UpdateFromAlphaFunc(self.line_group, update), - *added_anims, - **kwargs - ) - - def get_line_group(self, x): - group = VGroup() - group.lines, group.labels = VGroup(), VGroup() - for line_config in self.line_configs: - number_line = self.get_number_line(**line_config) - label = self.get_line_label(number_line, x, **line_config) - group.lines.add(number_line) - group.labels.add(label) - group.add(group.lines, group.labels) - group.x_val = x - return group - - def get_number_line( - self, center_y, **number_line_config - ): - number_line = NumberLine(color = GREY, **number_line_config) - number_line.stretch_to_fit_width(self.line_width) - number_line.add_numbers() - number_line.shift(center_y*UP) - number_line.to_edge(LEFT, buff = LARGE_BUFF) - - return number_line - - def get_line_label( - self, number_line, x, func, func_label, triangle_color, - **spillover_kwargs - ): - triangle = RegularPolygon( - n=3, start_angle = -np.pi/2, - fill_color = triangle_color, - fill_opacity = 0.75, - stroke_width = 0, - ) - triangle.set_height(self.triangle_height) - triangle.move_to( - number_line.number_to_point(func(x)), DOWN - ) - - label_mob = TexMobject(func_label) - label_mob.next_to(triangle, UP, buff = SMALL_BUFF, aligned_edge = LEFT) - - return VGroup(triangle, label_mob) - -class GeneralizeChainRule(Scene): - def construct(self): - example = TexMobject( - "\\frac{d}{dx}", "\\sin(", "x^2", ")", "=", - "\\cos(", "x^2", ")", "\\,2x", - ) - general = TexMobject( - "\\frac{d}{dx}", "g(", "h(x)", ")", "=", - "{dg \\over ", " dh}", "(", "h(x)", ")", "{dh \\over", " dx}", "(x)" - ) - example.to_edge(UP, buff = LARGE_BUFF) - example.shift(RIGHT) - general.next_to(example, DOWN, buff = 1.5*LARGE_BUFF) - for mob in example, general: - mob.set_color(SINE_COLOR) - mob[0].set_color(WHITE) - for tex in "x^2", "2x", "(x)", "{dh", " dx}": - mob.set_color_by_tex(tex, X_SQUARED_COLOR, substring = True) - - example_outer = VGroup(*example[1:4]) - example_inner = example[2] - d_example_outer = VGroup(*example[5:8]) - d_example_inner = example[6] - d_example_d_inner = example[8] - - general_outer = VGroup(*general[1:4]) - general_inner = general[2] - d_general_outer = VGroup(*general[5:10]) - d_general_inner = general[8] - d_general_d_inner = VGroup(*general[10:13]) - - example_outer_brace = Brace(example_outer) - example_inner_brace = Brace(example_inner, UP, buff = SMALL_BUFF) - d_example_outer_brace = Brace(d_example_outer) - d_example_inner_brace = Brace(d_example_inner, buff = SMALL_BUFF) - d_example_d_inner_brace = Brace(d_example_d_inner, UP, buff = SMALL_BUFF) - - general_outer_brace = Brace(general_outer) - general_inner_brace = Brace(general_inner, UP, buff = SMALL_BUFF) - d_general_outer_brace = Brace(d_general_outer) - d_general_inner_brace = Brace(d_general_inner, buff = SMALL_BUFF) - d_general_d_inner_brace = Brace(d_general_d_inner, UP, buff = SMALL_BUFF) - - for brace in example_outer_brace, general_outer_brace: - brace.text = brace.get_text("Outer") - for brace in example_inner_brace, general_inner_brace: - brace.text = brace.get_text("Inner") - for brace in d_example_outer_brace, d_general_outer_brace: - brace.text = brace.get_text("d(Outer)") - brace.text.shift(SMALL_BUFF*LEFT) - for brace in d_example_d_inner_brace, d_general_d_inner_brace: - brace.text = brace.get_text("d(Inner)", buff = SMALL_BUFF) - - #d(out)d(in) for example - self.add(example) - braces = VGroup( - example_outer_brace, - example_inner_brace, - d_example_outer_brace - ) - for brace in braces: - self.play(GrowFromCenter(brace)) - self.play(Write(brace.text, run_time = 1)) - self.wait() - self.wait() - self.play(*it.chain(*[ - [mob.scale_in_place, 1.2, mob.set_color, YELLOW] - for mob in (example_inner, d_example_inner) - ]), rate_func = there_and_back) - self.play(Transform( - example_inner.copy(), d_example_inner, - path_arc = -np.pi/2, - remover = True - )) - self.wait() - self.play( - GrowFromCenter(d_example_d_inner_brace), - Write(d_example_d_inner_brace.text) - ) - self.play(Transform( - VGroup(*reversed(example_inner.copy())), - d_example_d_inner, - path_arc = -np.pi/2, - run_time = 2, - remover = True - )) - self.wait() - - #Generalize - self.play(*list(map(FadeIn, general[:5]))) - self.wait() - self.play( - Transform(example_outer_brace, general_outer_brace), - Transform(example_outer_brace.text, general_outer_brace.text), - Transform(example_inner_brace, general_inner_brace), - Transform(example_inner_brace.text, general_inner_brace.text), - ) - self.wait() - self.play( - Transform(d_example_outer_brace, d_general_outer_brace), - Transform(d_example_outer_brace.text, d_general_outer_brace.text), - ) - self.play(Write(d_general_outer)) - self.wait(2) - self.play( - Transform(d_example_d_inner_brace, d_general_d_inner_brace), - Transform(d_example_d_inner_brace.text, d_general_d_inner_brace.text), - ) - self.play(Write(d_general_d_inner)) - self.wait(2) - - #Name chain rule - name = TextMobject("``Chain rule''") - name.scale(1.2) - name.set_color(YELLOW) - name.to_corner(UP+LEFT) - self.play(Write(name)) - self.wait() - - #Point out dh bottom - morty = Mortimer().flip() - morty.to_corner(DOWN+LEFT) - d_general_outer_copy = d_general_outer.copy() - morty.set_fill(opacity = 0) - self.play( - morty.set_fill, None, 1, - morty.change_mode, "raise_left_hand", - morty.look, UP+LEFT, - d_general_outer_copy.next_to, - morty.get_corner(UP+LEFT), UP, MED_LARGE_BUFF, - d_general_outer_copy.shift_onto_screen - ) - self.wait() - circle = Circle(color = YELLOW) - circle.replace(d_general_outer_copy[1]) - circle.scale_in_place(1.4) - self.play(ShowCreation(circle)) - self.play(Blink(morty)) - self.wait() - inner = d_general_outer_copy[3] - self.play( - morty.change_mode, "hooray", - morty.look_at, inner, - inner.shift, UP - ) - self.play(inner.shift, DOWN) - self.wait() - self.play(morty.change_mode, "pondering") - self.play(Blink(morty)) - self.wait() - self.play(*list(map(FadeOut, [ - d_general_outer_copy, inner, circle - ]))) - - #Show cancelation - braces = [ - d_example_d_inner_brace, - d_example_outer_brace, - example_inner_brace, - example_outer_brace, - ] - texts = [brace.text for brace in braces] - self.play(*list(map(FadeOut, braces+texts))) - - to_collapse = VGroup(VGroup(*general[7:10]), general[12]) - dg_dh = VGroup(*general[5:7]) - dh_dx = VGroup(*general[10:12]) - to_collapse.generate_target() - points = VGroup(*list(map(VectorizedPoint, - [m.get_left() for m in to_collapse] - ))) - self.play( - Transform(to_collapse, points), - dh_dx.next_to, dg_dh, - morty.look_at, dg_dh, - ) - self.wait() - for mob in list(dg_dh)+list(dh_dx): - circle = Circle(color = YELLOW) - circle.replace(mob) - circle.scale_in_place(1.3) - self.play(ShowCreation(circle)) - self.wait() - self.play(FadeOut(circle)) - - strikes = VGroup() - for dh in dg_dh[1], dh_dx[0]: - strike = TexMobject("/") - strike.stretch(2, dim = 0) - strike.rotate(-np.pi/12) - strike.move_to(dh) - strike.set_color(RED) - strikes.add(strike) - self.play(Write(strikes)) - self.play(morty.change_mode, "hooray") - equals_dg_dx = TexMobject("= \\frac{dg}{dx}") - equals_dg_dx.next_to(dh_dx) - self.play(Write(equals_dg_dx)) - self.play(Blink(morty)) - self.wait(2) - - ##More than a notational trick - self.play( - PiCreatureSays(morty, """ - This is more than a - notational trick - """), - VGroup( - dg_dh, dh_dx, equals_dg_dx, strikes, - *general[:5] - ).shift, DOWN, - FadeOut(example) - ) - self.wait() - self.play(Blink(morty)) - self.wait() - -class WatchingVideo(PiCreatureScene): - def construct(self): - laptop = Laptop() - laptop.scale(2) - laptop.to_corner(UP+RIGHT) - randy = self.get_primary_pi_creature() - randy.move_to(laptop, DOWN+LEFT) - randy.shift(MED_SMALL_BUFF*UP) - randy.look_at(laptop.screen) - - - formulas = VGroup(*[ - TexMobject("\\frac{d}{dx}\\left( %s \\right)"%s) - for s in [ - "e^x \\sin(x)", - "\\sin(x) \\cdot \\frac{1}{\\cos(x)}", - "\\cos(3x)^2", - "e^x(x^2 + 3x + 2)", - ] - ]) - formulas.arrange( - DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - formulas.next_to(randy, LEFT, buff = MED_LARGE_BUFF) - formulas.shift_onto_screen() - - self.add(randy, laptop) - self.wait() - self.play(randy.change_mode, "erm") - self.play(Blink(randy)) - self.wait() - self.play(randy.change_mode, "maybe") - self.wait() - self.play(Blink(randy)) - for formula in formulas: - self.play( - Write(formula, run_time = 2), - randy.change_mode, "thinking" - ) - self.wait() - - def create_pi_creatures(self): - return [Randolph().shift(DOWN+RIGHT)] - -class NextVideo(TeacherStudentsScene): - def construct(self): - series = VideoSeries() - series.to_edge(UP) - next_video = series[4] - - pre_expression = TexMobject( - "x", "^2", "+", "y", "^2", "=", "1" - ) - d_expression = TexMobject( - "2", "x", "\\,dx", "+", "2", "y", "\\,dy", "=", "0" - ) - expression_to_d_expression_indices = [ - 1, 0, 0, 2, 4, 3, 3, 5, 6 - ] - expression = VGroup() - for i, j in enumerate(expression_to_d_expression_indices): - submob = pre_expression[j].copy() - if d_expression.expression_parts[i] == "2": - two = TexMobject("2") - two.replace(submob) - expression.add(two) - else: - expression.add(submob) - - for mob in expression, d_expression: - mob.scale(1.2) - mob.next_to( - self.get_teacher().get_corner(UP+LEFT), UP, - buff = MED_LARGE_BUFF - ) - mob.shift_onto_screen() - - axes = Axes(x_min = -3, x_max = 3, color = GREY) - axes.add(Circle(color = YELLOW)) - line = Line(np.sqrt(2)*UP, np.sqrt(2)*RIGHT) - line.scale_in_place(1.5) - axes.add(line) - - axes.scale(0.5) - axes.next_to(d_expression, LEFT) - - self.add(series) - self.play( - next_video.shift, 0.5*DOWN, - next_video.set_color, YELLOW, - self.get_teacher().change_mode, "raise_right_hand" - ) - self.wait() - self.play( - Write(expression), - *[ - ApplyMethod(pi.change_mode, "pondering") - for pi in self.get_students() - ] - ) - self.play(FadeIn(axes)) - self.wait() - self.remove(expression) - self.play(Transform(expression, d_expression, path_arc = np.pi/2)) - self.wait() - self.play( - Rotate( - line, np.pi/4, - about_point = axes.get_center(), - rate_func = wiggle, - run_time = 3 - ) - ) - self.wait(2) - -class Chapter4Thanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "Meshal Alshammari", - "CrypticSwarm ", - "Ankit Agarwal", - "Yu Jun", - "Shelby Doolittle", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Justin Helps", - "Markus Persson", - "Dan Buchoff", - "Derek Dai", - "Joseph John Cox", - "Luc Ritchie", - "Nils Schneider", - "Mathew Bramson", - "Guido Gambardella", - "Jerry Ling", - "Mark Govea", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Kirk Werklund", - "Ripta Pasay", - "Felipe Diniz", - ], - "patron_group_size" : 8, - } - -class Thumbnail(IntroduceProductAsArea): - CONFIG = { - "default_x" : 0.8, - "dx" : 0.05 - } - def construct(self): - self.x_slider = self.get_x_slider(self.default_x) - blg = self.box_label_group = self.get_box_label_group( - self.default_x - ) - df_boxes = self.get_df_boxes() - df_boxes.space_out_submobjects(1.1) - df_boxes.move_to(blg[0], UP+LEFT) - blg[1][1].next_to(df_boxes[-1], RIGHT) - df_box_labels = self.get_df_box_labels(df_boxes) - blg.add(df_boxes, df_box_labels) - blg.set_height(FRAME_HEIGHT-2*MED_LARGE_BUFF) - blg.center() - self.add(blg) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eoc/chapter5.py b/from_3b1b/old/eoc/chapter5.py deleted file mode 100644 index 600b93de..00000000 --- a/from_3b1b/old/eoc/chapter5.py +++ /dev/null @@ -1,2061 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eoc.chapter4 import ThreeLinesChainRule - -class ExpFootnoteOpeningQuote(OpeningQuote): - CONFIG = { - "quote" : [ - "Who has not been amazed to learn that the function", - "$y = e^x$,", "like a phoenix rising again from its own", - "ashes, is its own derivative?", - ], - "highlighted_quote_terms" : { - "$y = e^x$" : MAROON_B - }, - "author" : "Francois le Lionnais" - } - -class LastVideo(TeacherStudentsScene): - def construct(self): - series = VideoSeries() - series.to_edge(UP) - last_video = series[2] - last_video.save_state() - this_video = series[3] - - known_formulas = VGroup(*list(map(TexMobject, [ - "\\frac{d(x^n)}{dx} = nx^{n-1}", - "\\frac{d(\\sin(x))}{dx} = \\cos(x)", - ]))) - known_formulas.arrange( - DOWN, buff = MED_LARGE_BUFF, - ) - known_formulas.set_height(2.5) - exp_question = TexMobject("2^x", ", 7^x, ", "e^x", " ???") - - last_video_brace = Brace(last_video) - known_formulas.next_to(last_video_brace, DOWN) - known_formulas.shift(MED_LARGE_BUFF*LEFT) - last_video_brace.save_state() - last_video_brace.shift(3*LEFT) - last_video_brace.set_fill(opacity = 0) - - self.add(series) - self.play( - last_video_brace.restore, - last_video.set_color, YELLOW, - self.get_teacher().change_mode, "raise_right_hand", - ) - self.play(Write(known_formulas)) - self.wait() - self.student_says( - exp_question, student_index = 1, - added_anims = [self.get_teacher().change_mode, "pondering"] - ) - self.wait(3) - e_to_x = exp_question.get_part_by_tex("e^x") - self.play( - self.teacher.change_mode, "raise_right_hand", - e_to_x.scale, 1.5, - e_to_x.set_color, YELLOW, - e_to_x.next_to, self.teacher.get_corner(UP+LEFT), UP - ) - self.wait(2) - -class PopulationSizeGraphVsPopulationMassGraph(Scene): - def construct(self): - pass - -class DoublingPopulation(PiCreatureScene): - CONFIG = { - "time_color" : YELLOW, - "pi_creature_grid_dimensions" : (8, 8), - "pi_creature_grid_height" : 6, - } - - def construct(self): - self.remove(self.get_pi_creatures()) - self.introduce_expression() - self.introduce_pi_creatures() - self.count_through_days() - self.ask_about_dM_dt() - self.growth_per_day() - self.relate_growth_rate_to_pop_size() - - def introduce_expression(self): - f_x = TexMobject("f(x)", "=", "2^x") - f_t = TexMobject("f(t)", "=", "2^t") - P_t = TexMobject("P(t)", "=", "2^t") - M_t = TexMobject("M(t)", "=", "2^t") - functions = VGroup(f_x, f_t, P_t, M_t) - for function in functions: - function.scale(1.2) - function.to_corner(UP+LEFT) - for function in functions[1:]: - for i, j in (0, 2), (2, 1): - function[i][j].set_color(self.time_color) - - t_expression = TexMobject("t", "=", "\\text{Time (in days)}") - t_expression.to_corner(UP+RIGHT) - t_expression[0].set_color(self.time_color) - - pop_brace, mass_brace = [ - Brace(function[0], DOWN) - for function in (P_t, M_t) - ] - for brace, word in (pop_brace, "size"), (mass_brace, "mass"): - text = brace.get_text("Population %s"%word) - text.to_edge(LEFT) - brace.text = text - - self.play(Write(f_x)) - self.wait() - self.play( - Transform(f_x, f_t), - FadeIn( - t_expression, - run_time = 2, - lag_ratio = 0.5 - ) - ) - self.play(Transform(f_x, P_t)) - self.play( - GrowFromCenter(pop_brace), - Write(pop_brace.text, run_time = 2) - ) - self.wait(2) - - self.function = f_x - self.pop_brace = pop_brace - self.t_expression = t_expression - self.mass_function = M_t - self.mass_brace = mass_brace - - def introduce_pi_creatures(self): - creatures = self.get_pi_creatures() - total_num_days = self.get_num_days() - num_start_days = 4 - - self.reset() - for x in range(num_start_days): - self.let_one_day_pass() - self.wait() - self.play( - Transform(self.function, self.mass_function), - Transform(self.pop_brace, self.mass_brace), - Transform(self.pop_brace.text, self.mass_brace.text), - ) - self.wait() - for x in range(total_num_days-num_start_days): - self.let_one_day_pass() - self.wait() - self.joint_blink(shuffle = False) - self.wait() - - def count_through_days(self): - self.reset() - brace = self.get_population_size_descriptor() - days_to_let_pass = 3 - - self.play(GrowFromCenter(brace)) - self.wait() - for x in range(days_to_let_pass): - self.let_one_day_pass() - new_brace = self.get_population_size_descriptor() - self.play(Transform(brace, new_brace)) - self.wait() - - self.population_size_descriptor = brace - - def ask_about_dM_dt(self): - dM_dt_question = TexMobject("{dM", "\\over dt}", "=", "???") - dM, dt, equals, q_marks = dM_dt_question - dM_dt_question.next_to(self.function, DOWN, buff = LARGE_BUFF) - dM_dt_question.to_edge(LEFT) - - self.play( - FadeOut(self.pop_brace), - FadeOut(self.pop_brace.text), - Write(dM_dt_question) - ) - self.wait(3) - for mob in dM, dt: - self.play(Indicate(mob)) - self.wait() - - self.dM_dt_question = dM_dt_question - - def growth_per_day(self): - day_to_day, frac = self.get_from_day_to_day_label() - - self.play( - FadeOut(self.dM_dt_question), - FadeOut(self.population_size_descriptor), - FadeIn(day_to_day) - ) - rect = self.let_day_pass_and_highlight_new_creatures(frac) - - for x in range(2): - new_day_to_day, new_frac = self.get_from_day_to_day_label() - self.play(*list(map(FadeOut, [rect, frac]))) - frac = new_frac - self.play(Transform(day_to_day, new_day_to_day)) - rect = self.let_day_pass_and_highlight_new_creatures(frac) - self.play(*list(map(FadeOut, [rect, frac, day_to_day]))) - - def let_day_pass_and_highlight_new_creatures(self, frac): - num_new_creatures = 2**self.get_curr_day() - - self.let_one_day_pass() - new_creatures = VGroup( - *self.get_on_screen_pi_creatures()[-num_new_creatures:] - ) - rect = Rectangle( - color = GREEN, - fill_color = BLUE, - fill_opacity = 0.3, - ) - rect.replace(new_creatures, stretch = True) - rect.stretch_to_fit_height(rect.get_height()+MED_SMALL_BUFF) - rect.stretch_to_fit_width(rect.get_width()+MED_SMALL_BUFF) - self.play(DrawBorderThenFill(rect)) - self.play(Write(frac)) - self.wait() - return rect - - def relate_growth_rate_to_pop_size(self): - false_deriv = TexMobject( - "{d(2^t) ", "\\over dt}", "= 2^t" - ) - difference_eq = TexMobject( - "{ {2^{t+1} - 2^t} \\over", "1}", "= 2^t" - ) - real_deriv = TexMobject( - "{ {2^{t+dt} - 2^t} \\over", "dt}", "= \\, ???" - ) - VGroup( - false_deriv[0][3], - false_deriv[2][-1], - difference_eq[0][1], - difference_eq[0][-2], - difference_eq[2][-1], - difference_eq[2][-1], - real_deriv[0][1], - real_deriv[0][-2], - ).set_color(YELLOW) - VGroup( - difference_eq[0][3], - difference_eq[1][-1], - real_deriv[0][3], - real_deriv[0][4], - real_deriv[1][-2], - real_deriv[1][-1], - ).set_color(GREEN) - - expressions = [false_deriv, difference_eq, real_deriv] - text_arg_list = [ - ("Tempting", "...",), - ("Rate of change", "\\\\ over one full day"), - ("Rate of change", "\\\\ in a small time"), - ] - for expression, text_args in zip(expressions, text_arg_list): - expression.next_to( - self.function, DOWN, - buff = LARGE_BUFF, - aligned_edge = LEFT, - ) - expression.brace = Brace(expression, DOWN) - expression.brace_text = expression.brace.get_text(*text_args) - - time = self.t_expression[-1] - new_time = TexMobject("3") - new_time.move_to(time, LEFT) - - fading_creatures = VGroup(*self.get_on_screen_pi_creatures()[8:]) - - - self.play(*list(map(FadeIn, [ - false_deriv, false_deriv.brace, false_deriv.brace_text - ]))) - self.wait() - self.play( - Transform(time, new_time), - FadeOut(fading_creatures) - ) - self.wait() - for x in range(3): - self.let_one_day_pass(run_time = 2) - self.wait(2) - - for expression in difference_eq, real_deriv: - expression.brace_text[1].set_color(GREEN) - self.play( - Transform(false_deriv, expression), - Transform(false_deriv.brace, expression.brace), - Transform(false_deriv.brace_text, expression.brace_text), - ) - self.wait(3) - self.reset() - for x in range(self.get_num_days()): - self.let_one_day_pass() - self.wait() - - rect = Rectangle(color = YELLOW) - rect.replace(real_deriv) - rect.stretch_to_fit_width(rect.get_width()+MED_SMALL_BUFF) - rect.stretch_to_fit_height(rect.get_height()+MED_SMALL_BUFF) - self.play(*list(map(FadeOut, [ - false_deriv.brace, false_deriv.brace_text - ]))) - self.play(ShowCreation(rect)) - self.play(*[ - ApplyFunction( - lambda pi : pi.change_mode("pondering").look_at(real_deriv), - pi, - run_time = 2, - rate_func = squish_rate_func(smooth, a, a+0.5) - ) - for pi in self.get_pi_creatures() - for a in [0.5*random.random()] - ]) - self.wait(3) - - ########### - - def create_pi_creatures(self): - width, height = self.pi_creature_grid_dimensions - creature_array = VGroup(*[ - VGroup(*[ - PiCreature(mode = "plain") - for y in range(height) - ]).arrange(UP, buff = MED_LARGE_BUFF) - for x in range(width) - ]).arrange(RIGHT, buff = MED_LARGE_BUFF) - creatures = VGroup(*it.chain(*creature_array)) - creatures.set_height(self.pi_creature_grid_height) - creatures.to_corner(DOWN+RIGHT) - - colors = color_gradient([BLUE, GREEN, GREY_BROWN], len(creatures)) - random.shuffle(colors) - for creature, color in zip(creatures, colors): - creature.set_color(color) - - return creatures - - def reset(self): - time = self.t_expression[-1] - faders = [time] + list(self.get_on_screen_pi_creatures()) - new_time = TexMobject("0") - new_time.next_to(self.t_expression[-2], RIGHT) - first_creature = self.get_pi_creatures()[0] - - self.play(*list(map(FadeOut, faders))) - self.play(*list(map(FadeIn, [first_creature, new_time]))) - self.t_expression.submobjects[-1] = new_time - - def let_one_day_pass(self, run_time = 2): - all_creatures = self.get_pi_creatures() - on_screen_creatures = self.get_on_screen_pi_creatures() - low_i = len(on_screen_creatures) - high_i = min(2*low_i, len(all_creatures)) - new_creatures = VGroup(*all_creatures[low_i:high_i]) - - to_children_anims = [] - growing_anims = [] - for old_pi, pi in zip(on_screen_creatures, new_creatures): - pi.save_state() - child = pi.copy() - child.scale(0.25, about_point = child.get_bottom()) - child.eyes.scale(1.5, about_point = child.eyes.get_bottom()) - pi.move_to(old_pi) - pi.set_fill(opacity = 0) - - index = list(new_creatures).index(pi) - prop = float(index)/len(new_creatures) - alpha = np.clip(len(new_creatures)/8.0, 0, 0.5) - rate_func = squish_rate_func( - smooth, alpha*prop, alpha*prop+(1-alpha) - ) - - to_child_anim = Transform(pi, child, rate_func = rate_func) - to_child_anim.update(1) - growing_anim = ApplyMethod(pi.restore, rate_func = rate_func) - to_child_anim.update(0) - - to_children_anims.append(to_child_anim) - growing_anims.append(growing_anim) - - time = self.t_expression[-1] - total_new_creatures = len(on_screen_creatures) + len(new_creatures) - new_time = TexMobject(str(int(np.log2(total_new_creatures)))) - new_time.move_to(time, LEFT) - - growing_anims.append(Transform(time, new_time)) - - self.play(*to_children_anims, run_time = run_time/2.0) - self.play(*growing_anims, run_time = run_time/2.0) - - def get_num_pi_creatures_on_screen(self): - mobjects = self.get_mobjects() - return sum([ - pi in mobjects for pi in self.get_pi_creatures() - ]) - - def get_population_size_descriptor(self): - on_screen_creatures = self.get_on_screen_pi_creatures() - brace = Brace(on_screen_creatures, LEFT) - n = len(on_screen_creatures) - label = brace.get_text( - "$2^%d$"%int(np.log2(n)), - "$=%d$"%n, - ) - brace.add(label) - return brace - - def get_num_days(self): - x, y = self.pi_creature_grid_dimensions - return int(np.log2(x*y)) - - def get_curr_day(self): - return int(np.log2(len(self.get_on_screen_pi_creatures()))) - - def get_from_day_to_day_label(self): - curr_day = self.get_curr_day() - top_words = TextMobject( - "From day", str(curr_day), - "to", str(curr_day+1), ":" - ) - top_words.set_width(4) - top_words.next_to( - self.function, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT, - ) - top_words[1].set_color(GREEN) - - bottom_words = TexMobject( - str(2**curr_day), - "\\text{ creatures}", "\\over {1 \\text{ day}}" - ) - bottom_words[0].set_color(GREEN) - bottom_words.next_to(top_words, DOWN, buff = MED_LARGE_BUFF) - - return top_words, bottom_words - -class GraphOfTwoToT(GraphScene): - CONFIG = { - "x_axis_label" : "$t$", - "y_axis_label" : "$M$", - "x_labeled_nums" : list(range(1, 7)), - "y_labeled_nums" : list(range(8, 40, 8)), - "x_max" : 6, - "y_min" : 0, - "y_max" : 32, - "y_tick_frequency" : 2, - "graph_origin" : 2.5*DOWN + 5*LEFT, - } - def construct(self): - self.setup_axes() - example_t = 3 - graph = self.get_graph(lambda t : 2**t, color = BLUE_C) - self.graph = graph - graph_label = self.get_graph_label( - graph, "M(t) = 2^t", - direction = LEFT, - ) - label_group = self.get_label_group(example_t) - v_line, brace, height_label, ss_group, slope_label = label_group - self.animate_secant_slope_group_change( - ss_group, - target_dx = 1, - run_time = 0 - ) - self.remove(ss_group) - - #Draw graph and revert to tangent - self.play(ShowCreation(graph)) - self.play(Write(graph_label)) - self.wait() - self.play(Write(ss_group)) - self.wait() - for target_dx in 0.01, 1, 0.01: - self.animate_secant_slope_group_change( - ss_group, - target_dx = target_dx - ) - self.wait() - - #Mark up with values - - self.play(ShowCreation(v_line)) - self.play( - GrowFromCenter(brace), - Write(height_label, run_time = 1) - ) - self.wait() - self.play( - FadeIn( - slope_label, - run_time = 4, - lag_ratio = 0.5 - ), - ReplacementTransform( - height_label.copy(), - slope_label.get_part_by_tex("2^") - ) - ) - self.wait() - - #Vary value - threes = VGroup(height_label[1], slope_label[2][1]) - ts = VGroup(*[ - TexMobject("t").set_color(YELLOW).scale(0.75).move_to(three) - for three in threes - ]) - self.play(Transform(threes, ts)) - - alt_example_t = example_t+1 - def update_label_group(group, alpha): - t = interpolate(example_t, alt_example_t, alpha) - new_group = self.get_label_group(t) - Transform(group, new_group).update(1) - for t, three in zip(ts, threes): - t.move_to(three) - Transform(threes, ts).update(1) - return group - - self.play(UpdateFromAlphaFunc( - label_group, update_label_group, - run_time = 3, - )) - self.play(UpdateFromAlphaFunc( - label_group, update_label_group, - run_time = 3, - rate_func = lambda t : 1 - 1.5*smooth(t) - )) - - def get_label_group(self, t): - graph = self.graph - - v_line = self.get_vertical_line_to_graph( - t, graph, - color = YELLOW, - ) - brace = Brace(v_line, RIGHT) - height_label = brace.get_text("$2^%d$"%t) - - ss_group = self.get_secant_slope_group( - t, graph, dx = 0.01, - df_label = "dM", - dx_label = "dt", - dx_line_color = GREEN, - secant_line_color = RED, - ) - slope_label = TexMobject( - "\\text{Slope}", "=", - "2^%d"%t, - "(%.7f\\dots)"%np.log(2) - ) - slope_label.next_to( - ss_group.secant_line.point_from_proportion(0.65), - DOWN+RIGHT, - buff = 0 - ) - slope_label.set_color_by_tex("Slope", RED) - return VGroup( - v_line, brace, height_label, - ss_group, slope_label - ) - -class SimpleGraphOfTwoToT(GraphOfTwoToT): - CONFIG = { - "x_axis_label" : "", - "y_axis_label" : "", - } - def construct(self): - self.setup_axes() - func = lambda t : 2**t - graph = self.get_graph(func) - line_pairs = VGroup() - for x in 1, 2, 3, 4, 5: - point = self.coords_to_point(x, func(x)) - x_axis_point = self.coords_to_point(x, 0) - y_axis_point = self.coords_to_point(0, func(x)) - line_pairs.add(VGroup( - DashedLine(x_axis_point, point), - DashedLine(y_axis_point, point), - )) - - - self.play(ShowCreation(graph, run_time = 2)) - for pair in line_pairs: - self.play(ShowCreation(pair)) - self.wait() - -class FakeDiagram(TeacherStudentsScene): - def construct(self): - gs = GraphScene(skip_animations = True) - gs.setup_axes() - background_graph, foreground_graph = graphs = VGroup(*[ - gs.get_graph( - lambda t : np.log(2)*2**t, - x_min = -8, - x_max = 2 + dx - ) - for dx in (0.25, 0) - ]) - for graph in graphs: - end_point = graph.points[-1] - axis_point = end_point[0]*RIGHT + gs.graph_origin[1]*UP - for alpha in np.linspace(0, 1, 20): - point = interpolate(axis_point, graph.points[0], alpha) - graph.add_line_to(point) - graph.set_stroke(width = 1) - graph.set_fill(opacity = 1) - graph.set_color(BLUE_D) - background_graph.set_color(YELLOW) - background_graph.set_stroke(width = 0.5) - - graphs.next_to(self.teacher, UP+LEFT, LARGE_BUFF) - two_to_t = TexMobject("2^t") - two_to_t.next_to( - foreground_graph.get_corner(DOWN+RIGHT), UP+LEFT - ) - corner_line = Line(*[ - graph.get_corner(DOWN+RIGHT) - for graph in graphs - ]) - dt_brace = Brace(corner_line, DOWN, buff = SMALL_BUFF) - dt = dt_brace.get_text("$dt$") - - side_brace = Brace(graphs, RIGHT, buff = SMALL_BUFF) - deriv = side_brace.get_text("$\\frac{d(2^t)}{dt}$") - - circle = Circle(color = RED) - circle.replace(deriv, stretch = True) - circle.scale_in_place(1.5) - - words = TextMobject("Not a real explanation") - words.to_edge(UP) - arrow = Arrow(words.get_bottom(), two_to_t.get_corner(UP+LEFT)) - arrow.set_color(WHITE) - - diagram = VGroup( - graphs, two_to_t, dt_brace, dt, - side_brace, deriv, circle, - words, arrow - ) - - self.play(self.teacher.change_mode, "raise_right_hand") - self.play( - Animation(VectorizedPoint(graphs.get_right())), - DrawBorderThenFill(foreground_graph), - Write(two_to_t) - ) - self.wait() - self.play( - ReplacementTransform( - foreground_graph.copy(), - background_graph, - ), - Animation(foreground_graph), - Animation(two_to_t), - GrowFromCenter(dt_brace), - Write(dt) - ) - self.play(GrowFromCenter(side_brace)) - self.play(Write(deriv, run_time = 2)) - self.wait() - - self.play( - ShowCreation(circle), - self.teacher.change_mode, "hooray" - ) - self.change_student_modes(*["confused"]*3) - self.play( - Write(words), - ShowCreation(arrow), - self.teacher.change_mode, "shruggie" - ) - self.wait(3) - self.play( - FadeOut(diagram), - *[ - ApplyMethod(pi.change_mode, "plain") - for pi in self.get_pi_creatures() - ] - ) - self.teacher_says( - "More numerical \\\\ than visual..." - ) - self.wait(2) - - self.diagram = diagram - -class AnalyzeExponentRatio(PiCreatureScene): - CONFIG = { - "base" : 2, - "base_str" : "2", - } - def construct(self): - base_str = self.base_str - - func_def = TexMobject("M(", "t", ")", "= ", "%s^"%base_str, "t") - func_def.to_corner(UP+LEFT) - self.add(func_def) - - ratio = TexMobject( - "{ {%s^"%base_str, "{t", "+", "dt}", "-", - "%s^"%base_str, "t}", - "\\over \\,", "dt}" - ) - ratio.shift(UP+LEFT) - - lhs = TexMobject("{dM", "\\over \\,", "dt}", "(", "t", ")", "=") - lhs.next_to(ratio, LEFT) - - - two_to_t_plus_dt = VGroup(*ratio[:4]) - two_to_t = VGroup(*ratio[5:7]) - two_to_t_two_to_dt = TexMobject( - "%s^"%base_str, "t", - "%s^"%base_str, "{dt}" - ) - two_to_t_two_to_dt.move_to(two_to_t_plus_dt, DOWN+LEFT) - exp_prop_brace = Brace(two_to_t_two_to_dt, UP) - - one = TexMobject("1") - one.move_to(ratio[5], DOWN) - lp, rp = parens = TexMobject("()") - parens.stretch(1.3, 1) - parens.set_height(ratio.get_height()) - lp.next_to(ratio, LEFT, buff = 0) - rp.next_to(ratio, RIGHT, buff = 0) - - extracted_two_to_t = TexMobject("%s^"%base_str, "t") - extracted_two_to_t.next_to(lp, LEFT, buff = SMALL_BUFF) - - expressions = [ - ratio, two_to_t_two_to_dt, - extracted_two_to_t, lhs, func_def - ] - for expression in expressions: - expression.set_color_by_tex("t", YELLOW) - expression.set_color_by_tex("dt", GREEN) - - #Apply exponential property - self.play( - Write(ratio), Write(lhs), - self.pi_creature.change_mode, "raise_right_hand" - ) - self.wait(2) - self.play( - two_to_t_plus_dt.next_to, exp_prop_brace, UP, - self.pi_creature.change_mode, "pondering" - ) - self.play( - ReplacementTransform( - two_to_t_plus_dt.copy(), two_to_t_two_to_dt, - run_time = 2, - path_arc = np.pi, - ), - FadeIn(exp_prop_brace) - ) - self.wait(2) - - #Talk about exponential property - add_exp_rect, mult_rect = rects = [ - Rectangle( - stroke_color = BLUE, - stroke_width = 2, - ).replace(mob).scale_in_place(1.1) - for mob in [ - VGroup(*two_to_t_plus_dt[1:]), - two_to_t_two_to_dt - ] - ] - words = VGroup(*[ - TextMobject(s, " ideas") - for s in ("Additive", "Multiplicative") - ]) - words[0].move_to(words[1], LEFT) - words.set_color(BLUE) - words.next_to(two_to_t_plus_dt, RIGHT, buff = 1.5*LARGE_BUFF) - arrows = VGroup(*[ - Arrow(word.get_left(), rect, color = words.get_color()) - for word, rect in zip(words, rects) - ]) - - self.play(ShowCreation(add_exp_rect)) - self.wait() - self.play(ReplacementTransform( - add_exp_rect.copy(), mult_rect - )) - self.wait() - self.change_mode("happy") - self.play(Write(words[0], run_time = 2)) - self.play(ShowCreation(arrows[0])) - self.wait() - self.play( - Transform(*words), - Transform(*arrows), - ) - self.wait(2) - self.play(*list(map(FadeOut, [ - words[0], arrows[0], add_exp_rect, mult_rect, - two_to_t_plus_dt, exp_prop_brace, - ]))) - - #Factor out 2^t - self.play(*[ - FadeIn( - mob, - run_time = 2, - rate_func = squish_rate_func(smooth, 0.5, 1) - ) - for mob in (one, lp, rp) - ] + [ - ReplacementTransform( - mob, extracted_two_to_t, - path_arc = np.pi/2, - run_time = 2, - ) - for mob in (two_to_t, VGroup(*two_to_t_two_to_dt[:2])) - ] + [ - lhs.next_to, extracted_two_to_t, LEFT - ]) - self.change_mode("pondering") - shifter = VGroup(ratio[4], one, *two_to_t_two_to_dt[2:]) - stretcher = VGroup(lp, ratio[7], rp) - self.play( - shifter.next_to, ratio[7], UP, - stretcher.stretch_in_place, 0.9, 0 - ) - self.wait(2) - - #Ask about dt -> 0 - brace = Brace(VGroup(extracted_two_to_t, ratio), DOWN) - alt_brace = Brace(parens, DOWN) - dt_to_zero = TexMobject("dt", "\\to 0") - dt_to_zero.set_color_by_tex("dt", GREEN) - dt_to_zero.next_to(brace, DOWN) - - self.play(GrowFromCenter(brace)) - self.play(Write(dt_to_zero)) - self.wait(2) - - #Who cares - randy = Randolph() - randy.scale(0.7) - randy.to_edge(DOWN) - - self.play( - FadeIn(randy), - self.pi_creature.change_mode, "plain", - ) - self.play(PiCreatureSays( - randy, "Who cares?", - bubble_kwargs = {"direction" : LEFT}, - target_mode = "angry", - )) - self.wait(2) - self.play( - RemovePiCreatureBubble(randy), - FadeOut(randy), - self.pi_creature.change_mode, "hooray", - self.pi_creature.look_at, parens - ) - self.play( - Transform(brace, alt_brace), - dt_to_zero.next_to, alt_brace, DOWN - ) - self.wait() - - #Highlight separation - rects = [ - Rectangle( - stroke_color = color, - stroke_width = 2, - ).replace(mob, stretch = True).scale_in_place(1.1) - for mob, color in [ - (VGroup(parens, dt_to_zero), GREEN), - (extracted_two_to_t, YELLOW), - ] - ] - self.play(ShowCreation(rects[0])) - self.wait(2) - self.play(ReplacementTransform(rects[0].copy(), rects[1])) - self.change_mode("happy") - self.wait() - self.play(*list(map(FadeOut, rects))) - - #Plug in specific values - static_constant = self.try_specific_dt_values() - constant = static_constant.copy() - - #Replace with actual constant - limit_term = VGroup( - brace, dt_to_zero, ratio[4], one, rects[0], - *ratio[7:]+two_to_t_two_to_dt[2:] - ) - self.play(FadeIn(rects[0])) - self.play(limit_term.to_corner, DOWN+LEFT) - self.play( - lp.stretch, 0.5, 1, - lp.stretch, 0.8, 0, - lp.next_to, extracted_two_to_t[0], RIGHT, - rp.stretch, 0.5, 1, - rp.stretch, 0.8, 0, - rp.next_to, lp, RIGHT, SMALL_BUFF, - rp.shift, constant.get_width()*RIGHT, - constant.next_to, extracted_two_to_t[0], RIGHT, MED_LARGE_BUFF - ) - self.wait() - self.change_mode("confused") - self.wait() - - #Indicate distinction between dt group and t group again - for mob in limit_term, extracted_two_to_t: - self.play(FocusOn(mob)) - self.play(Indicate(mob)) - self.wait() - - #hold_final_value - derivative = VGroup( - lhs, extracted_two_to_t, parens, constant - ) - func_def_rhs = VGroup(*func_def[-2:]).copy() - func_lp, func_rp = func_parens = TexMobject("()") - func_parens.set_fill(opacity = 0) - func_lp.next_to(func_def_rhs[0], LEFT, buff = 0) - func_rp.next_to(func_lp, RIGHT, buff = func_def_rhs.get_width()) - func_def_rhs.add(func_parens) - M = lhs[0][1] - - self.play( - FadeOut(M), - func_def_rhs.move_to, M, LEFT, - func_def_rhs.set_fill, None, 1, - ) - lhs[0].submobjects[1] = func_def_rhs - self.wait() - self.play( - derivative.next_to, self.pi_creature, UP, - derivative.to_edge, RIGHT, - self.pi_creature.change_mode, "raise_right_hand" - ) - self.wait(2) - for mob in extracted_two_to_t, constant: - self.play(Indicate(mob)) - self.wait() - self.wait(2) - - def try_specific_dt_values(self): - expressions = [] - for num_zeros in [1, 2, 4, 7]: - dt_str = "0." + num_zeros*"0" + "1" - dt_num = float(dt_str) - output_num = (self.base**dt_num - 1) / dt_num - output_str = "%.7f\\dots"%output_num - - expression = TexMobject( - "{%s^"%self.base_str, "{%s}"%dt_str, "-1", - "\\over \\,", "%s}"%dt_str, - "=", output_str - ) - expression.set_color_by_tex(dt_str, GREEN) - expression.set_color_by_tex(output_str, BLUE) - expression.to_corner(UP+RIGHT) - expressions.append(expression) - - curr_expression = expressions[0] - self.play( - Write(curr_expression), - self.pi_creature.change_mode, "pondering" - ) - self.wait(2) - for expression in expressions[1:]: - self.play(Transform(curr_expression, expression)) - self.wait(2) - return curr_expression[-1] - -class ExponentRatioWithThree(AnalyzeExponentRatio): - CONFIG = { - "base" : 3, - "base_str" : "3", - } - -class ExponentRatioWithSeven(AnalyzeExponentRatio): - CONFIG = { - "base" : 7, - "base_str" : "7", - } - -class ExponentRatioWithEight(AnalyzeExponentRatio): - CONFIG = { - "base" : 8, - "base_str" : "8", - } - -class ExponentRatioWithE(AnalyzeExponentRatio): - CONFIG = { - "base" : np.exp(1), - "base_str" : "e", - } - -class CompareTwoConstantToEightConstant(PiCreatureScene): - def construct(self): - two_deriv, eight_deriv = derivs = VGroup(*[ - self.get_derivative_expression(base) - for base in (2, 8) - ]) - - derivs.arrange( - DOWN, buff = 1.5, aligned_edge = LEFT - ) - derivs.to_edge(LEFT, LARGE_BUFF).shift(UP) - arrow = Arrow(*[deriv[-2] for deriv in derivs]) - times_three = TexMobject("\\times 3") - times_three.next_to(arrow, RIGHT) - - why = TextMobject("Why?") - why.next_to(self.pi_creature, UP, MED_LARGE_BUFF) - - self.add(eight_deriv) - self.wait() - self.play(ReplacementTransform( - eight_deriv.copy(), - two_deriv - )) - self.wait() - self.play(ShowCreation(arrow)) - self.play( - Write(times_three), - self.pi_creature.change_mode, "thinking" - ) - self.wait(3) - - self.play( - Animation(derivs), - Write(why), - self.pi_creature.change, "confused", derivs - ) - self.wait() - for deriv in derivs: - for index in -5, -2: - self.play(Indicate(deriv[index])) - self.wait() - self.wait(2) - - def get_derivative_expression(self, base): - base_str = str(base) - const_str = "%.4f\\dots"%np.log(base) - result = TexMobject( - "{d(", base_str, "^t", ")", "\\over", "dt}", - "=", base_str, "^t", "(", const_str, ")" - ) - tex_color_paris = [ - ("t", YELLOW), - ("dt", GREEN), - (const_str, BLUE) - ] - for tex, color in tex_color_paris: - result.set_color_by_tex(tex, color) - return result - - def create_pi_creature(self): - self.pi_creature = Randolph().flip() - self.pi_creature.to_edge(DOWN).shift(3*RIGHT) - return self.pi_creature - -class AskAboutConstantOne(TeacherStudentsScene): - def construct(self): - note = TexMobject( - "{ d(a^", "t", ")", "\\over \\,", "dt}", - "=", "a^", "t", "(\\text{Some constant})" - ) - note.set_color_by_tex("t", YELLOW) - note.set_color_by_tex("dt", GREEN) - note.set_color_by_tex("constant", BLUE) - note.to_corner(UP+LEFT) - self.add(note) - - self.student_says( - "Is there a base where\\\\", - "that constant is 1?" - ) - self.change_student_modes( - "pondering", "raise_right_hand", "thinking", - # look_at_arg = self.get_students()[1].bubble - ) - self.wait(2) - self.play(FadeOut(note[-1], run_time = 3)) - self.wait() - - self.teacher_says( - "There is!\\\\", - "$e = 2.71828\\dots$", - target_mode = "hooray" - ) - self.change_student_modes(*["confused"]*3) - self.wait(3) - -class WhyPi(PiCreatureScene): - def construct(self): - circle = Circle(radius = 1, color = MAROON_B) - circle.rotate(np.pi/2) - circle.to_edge(UP) - ghost_circle = circle.copy() - ghost_circle.set_stroke(width = 1) - diam = Line(circle.get_left(), circle.get_right()) - diam.set_color(YELLOW) - one = TexMobject("1") - one.next_to(diam, UP) - circum = diam.copy() - circum.set_color(circle.get_color()) - circum.scale(np.pi) - circum.next_to(circle, DOWN, LARGE_BUFF) - circum.insert_n_curves(circle.get_num_curves()-2) - circum.make_jagged() - pi = TexMobject("\\pi") - pi.next_to(circum, UP) - why = TextMobject("Why?") - why.next_to(self.pi_creature, UP, MED_LARGE_BUFF) - - self.add(ghost_circle, circle, diam, one) - self.wait() - self.play(Transform(circle, circum, run_time = 2)) - self.play( - Write(pi), - Write(why), - self.pi_creature.change_mode, "confused", - ) - self.wait(3) - - - ####### - - def create_pi_creature(self): - self.pi_creature = Randolph() - self.pi_creature.to_corner(DOWN+LEFT) - return self.pi_creature - -class GraphOfExp(GraphScene): - CONFIG = { - "x_min" : -3, - "x_max" : 3, - "x_tick_frequency" : 1, - "x_axis_label" : "t", - "x_labeled_nums" : list(range(-3, 4)), - "x_axis_width" : 11, - "graph_origin" : 2*DOWN + LEFT, - "example_inputs" : [1, 2], - "small_dx" : 0.01, - } - def construct(self): - self.setup_axes() - self.show_slopes() - - def show_slopes(self): - graph = self.get_graph(np.exp) - graph_label = self.get_graph_label( - graph, "e^t", direction = LEFT - ) - graph_label.shift(MED_SMALL_BUFF*LEFT) - - start_input, target_input = self.example_inputs - ss_group = self.get_secant_slope_group( - start_input, graph, - dx = self.small_dx, - dx_label = "dt", - df_label = "d(e^t)", - secant_line_color = YELLOW, - ) - v_lines = [ - self.get_vertical_line_to_graph( - x, graph, - color = WHITE, - ) - for x in self.example_inputs - ] - height_labels = [ - TexMobject("e^%d"%x).next_to(vl, RIGHT, SMALL_BUFF) - for vl, x in zip(v_lines, self.example_inputs) - ] - slope_labels = [ - TextMobject( - "Slope = $e^%d$"%x - ).next_to(vl.get_top(), UP+RIGHT).shift(0.7*RIGHT/x) - for vl, x in zip(v_lines, self.example_inputs) - ] - - self.play( - ShowCreation(graph, run_time = 2), - Write( - graph_label, - rate_func = squish_rate_func(smooth, 0.5, 1), - ) - ) - self.wait() - self.play(*list(map(ShowCreation, ss_group))) - self.play(Write(slope_labels[0])) - self.play(ShowCreation(v_lines[0])) - self.play(Write(height_labels[0])) - self.wait(2) - self.animate_secant_slope_group_change( - ss_group, - target_x = target_input, - run_time = 2, - added_anims = [ - Transform( - *pair, - path_arc = np.pi/6, - run_time = 2 - ) - for pair in [ - slope_labels, - v_lines, - height_labels, - ] - ] - ) - self.wait(2) - - self.graph = graph - self.ss_group = ss_group - -class Chapter4Wrapper(Scene): - def construct(self): - title = TextMobject("Chapter 4 chain rule intuition") - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9) - rect.set_height(1.5*FRAME_Y_RADIUS) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait(3) - -class ApplyChainRule(TeacherStudentsScene): - def construct(self): - deriv_equation = TexMobject( - "{d(", "e^", "{3", "t}", ")", "\\over", "dt}", - "=", "3", "e^", "{3", "t}", - ) - deriv_equation.next_to(self.teacher, UP+LEFT) - deriv_equation.shift(UP) - deriv_equation.set_color_by_tex("3", BLUE) - deriv = VGroup(*deriv_equation[:7]) - exponent = VGroup(*deriv_equation[-2:]) - circle = Circle(color = YELLOW) - circle.replace(exponent, stretch = True) - circle.scale_in_place(1.5) - - self.teacher_says("Think of the \\\\ chain rule") - self.change_student_modes(*["pondering"]*3) - self.play( - Write(deriv), - RemovePiCreatureBubble( - self.teacher, - target_mode = "raise_right_hand" - ), - ) - self.wait(2) - self.play(*[ - Transform( - *deriv_equation.get_parts_by_tex( - tex, substring = False - ).copy()[:2], - path_arc = -np.pi, - run_time = 2 - ) - for tex in ("e^", "{3", "t}") - ] + [ - Write(deriv_equation.get_part_by_tex("=")) - ]) - self.play(self.teacher.change_mode, "happy") - self.wait() - self.play(ShowCreation(circle)) - self.play(Transform( - *deriv_equation.get_parts_by_tex("3").copy()[-1:-3:-1] - )) - self.play(FadeOut(circle)) - self.wait(3) - -class ChainRuleIntuition(ThreeLinesChainRule): - CONFIG = { - "line_configs" : [ - { - "func" : lambda t : t, - "func_label" : "t", - "triangle_color" : WHITE, - "center_y" : 3, - "x_min" : 0, - "x_max" : 3, - "numbers_to_show" : list(range(4)), - "numbers_with_elongated_ticks" : list(range(4)), - "tick_frequency" : 1, - }, - { - "func" : lambda t : 3*t, - "func_label" : "3t", - "triangle_color" : GREEN, - "center_y" : 0.5, - "x_min" : 0, - "x_max" : 3, - "numbers_to_show" : list(range(0, 4)), - "numbers_with_elongated_ticks" : list(range(4)), - "tick_frequency" : 1, - }, - { - "func" : lambda t : np.exp(3*t), - "func_label" : "e^{3t}", - "triangle_color" : BLUE, - "center_y" : -2, - "x_min" : 0, - "x_max" : 10, - "numbers_to_show" : list(range(0, 11, 3)), - "numbers_with_elongated_ticks" : list(range(11)), - "tick_frequency" : 1, - }, - ], - "example_x" : 0.4, - "start_x" : 0.4, - "max_x" : 0.6, - "min_x" : 0.2, - } - def construct(self): - self.introduce_line_group() - self.nudge_x() - - def nudge_x(self): - lines, labels = self.line_group - def get_value_points(): - return [ - label[0].get_bottom() - for label in labels - ] - starts = get_value_points() - self.animate_x_change(self.example_x + self.dx, run_time = 0) - ends = get_value_points() - self.animate_x_change(self.example_x, run_time = 0) - - nudge_lines = VGroup() - braces = VGroup() - numbers = VGroup() - for start, end, line, label, config in zip(starts, ends, lines, labels, self.line_configs): - color = label[0].get_color() - nudge_line = Line(start, end) - nudge_line.set_stroke(color, width = 6) - brace = Brace(nudge_line, DOWN, buff = SMALL_BUFF) - brace.set_color(color) - func_label = config["func_label"] - if len(func_label) == 1: - text = "$d%s$"%func_label - else: - text = "$d(%s)$"%func_label - brace.text = brace.get_text(text, buff = SMALL_BUFF) - brace.text.set_color(color) - brace.add(brace.text) - - line.add(nudge_line) - nudge_lines.add(nudge_line) - braces.add(brace) - numbers.add(line.numbers) - line.remove(*line.numbers) - dt_brace, d3t_brace, dexp3t_brace = braces - - self.play(*list(map(FadeIn, [nudge_lines, braces]))) - self.wait() - for count in range(3): - for dx in self.dx, 0: - self.animate_x_change( - self.example_x + dx, - run_time = 2 - ) - self.wait() - -class WhyNaturalLogOf2ShowsUp(TeacherStudentsScene): - def construct(self): - self.add_e_to_the_three_t() - self.show_e_to_log_2() - - def add_e_to_the_three_t(self): - exp_c = self.get_exp_C("c") - exp_c.next_to(self.teacher, UP+LEFT) - - self.play( - FadeIn( - exp_c, - run_time = 2, - lag_ratio = 0.5 - ), - self.teacher.change, "raise_right_hand" - ) - self.wait() - self.look_at(4*LEFT + UP) - self.wait(3) - - self.exp_c = exp_c - - def show_e_to_log_2(self): - equation = TexMobject( - "2", "^t", "= e^", "{\\ln(2)", "t}" - ) - equation.move_to(self.exp_c) - t_group = equation.get_parts_by_tex("t") - non_t_group = VGroup(*equation) - non_t_group.remove(*t_group) - - log_words = TextMobject("``$e$ to the ", "\\emph{what}", "equals 2?''") - log_words.set_color_by_tex("what", BLUE) - log_words.next_to(equation, UP+LEFT) - log_words_arrow = Arrow( - log_words.get_right(), - equation.get_part_by_tex("ln(2)").get_corner(UP+LEFT), - color = BLUE, - ) - - derivative = TexMobject( - "\\ln(2)", "2", "^t", "=", "\\ln(2)", "e^", "{\\ln(2)", "t}" - ) - derivative.move_to(equation) - for tex_mob in equation, derivative: - tex_mob.set_color_by_tex("ln(2)", BLUE) - tex_mob.set_color_by_tex("t", YELLOW) - derivative_arrow = Arrow(1.5*UP, ORIGIN, buff = 0) - derivative_arrow.set_color(WHITE) - derivative_arrow.next_to( - derivative.get_parts_by_tex("="), UP - ) - derivative_symbol = TextMobject("Derivative") - derivative_symbol.next_to(derivative_arrow, RIGHT) - - self.play( - Write(non_t_group), - self.exp_c.next_to, equation, LEFT, 2*LARGE_BUFF, - self.exp_c.to_edge, UP, - ) - self.change_student_modes("confused", "sassy", "erm") - self.play( - Write(log_words), - ShowCreation( - log_words_arrow, - run_time = 2, - rate_func = squish_rate_func(smooth, 0.5, 1) - ) - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = log_words - ) - self.wait(2) - - t_group.save_state() - t_group.shift(UP) - t_group.set_fill(opacity = 0) - self.play( - ApplyMethod( - t_group.restore, - run_time = 2, - lag_ratio = 0.5, - ), - self.teacher.change_mode, "speaking" - ) - self.wait(2) - self.play(FocusOn(self.exp_c)) - self.play(Indicate(self.exp_c, scale_factor = 1.05)) - self.wait(2) - - self.play( - equation.next_to, derivative_arrow, UP, - equation.shift, MED_SMALL_BUFF*RIGHT, - FadeOut(VGroup(log_words, log_words_arrow)), - self.teacher.change_mode, "raise_right_hand", - ) - self.play( - ShowCreation(derivative_arrow), - Write(derivative_symbol), - Write(derivative) - ) - self.wait(3) - self.play(self.teacher.change_mode, "happy") - self.wait(2) - - student = self.get_students()[1] - ln = derivative.get_part_by_tex("ln(2)").copy() - rhs = TexMobject("=%s"%self.get_log_str(2)) - self.play( - ln.next_to, student, UP+LEFT, MED_LARGE_BUFF, - student.change_mode, "raise_left_hand", - ) - rhs.next_to(ln, RIGHT) - self.play(Write(rhs)) - self.wait(2) - - - ###### - - def get_exp_C(self, C): - C_str = str(C) - result = TexMobject( - "{d(", "e^", "{%s"%C_str, "t}", ")", "\\over", "dt}", - "=", C_str, "e^", "{%s"%C_str, "t}", - ) - result.set_color_by_tex(C_str, BLUE) - result.C_str = C_str - return result - - def get_a_to_t(self, a): - a_str = str(a) - log_str = self.get_log_str(a) - result = TexMobject( - "{d(", a_str, "^t", ")", "\\over", "dt}", - "=", log_str, a_str, "^t" - ) - result.set_color_by_tex(log_str, BLUE) - return result - - def get_log_str(self, a): - return "%.4f\\dots"%np.log(float(a)) - -class CompareWaysToWriteExponentials(GraphScene): - CONFIG = { - "y_max" : 50, - "y_tick_frequency" : 5, - "x_max" : 7, - } - def construct(self): - self.setup_axes() - bases = list(range(2, 7)) - graphs = [ - self.get_graph(lambda t : base**t, color = GREEN) - for base in bases - ] - graph = graphs[0] - - a_to_t = TexMobject("a^t") - a_to_t.move_to(self.coords_to_point(6, 45)) - - cross = TexMobject("\\times") - cross.set_color(RED) - cross.replace(a_to_t, stretch = True) - e_to_ct = TexMobject("e^", "{c", "t}") - e_to_ct.set_color_by_tex("c", BLUE) - e_to_ct.scale(1.5) - e_to_ct.next_to(a_to_t, DOWN) - - equations = VGroup() - for base in bases: - log_str = "%.4f\\dots"%np.log(base) - equation = TexMobject( - str(base), "^t", "=", - "e^", "{(%s)"%log_str, "t}", - ) - equation.set_color_by_tex(log_str, BLUE) - equation.scale(1.2) - equations.add(equation) - - equation = equations[0] - equations.next_to(e_to_ct, DOWN, LARGE_BUFF, LEFT) - - self.play( - ShowCreation(graph), - Write( - a_to_t, - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - run_time = 2 - ) - self.play(Write(cross, run_time = 2)) - self.play(Write(e_to_ct, run_time = 2)) - self.wait(2) - self.play(Write(equation)) - self.wait(2) - for new_graph, new_equation in zip(graphs, equations)[1:]: - self.play( - Transform(graph, new_graph), - Transform(equation, new_equation) - ) - self.wait(2) - self.wait() - -class ManyExponentialForms(TeacherStudentsScene): - def construct(self): - lhs = TexMobject("2", "^t") - rhs_list = [ - TexMobject("=", "%s^"%tex, "{(%.5f\\dots)"%log, "t}") - for tex, log in [ - ("e", np.log(2)), - ("\\pi", np.log(2)/np.log(np.pi)), - ("42", np.log(2)/np.log(42)), - ] - ] - group = VGroup(lhs, *rhs_list) - group.arrange(RIGHT) - group.set_width(FRAME_WIDTH - LARGE_BUFF) - group.next_to(self.get_pi_creatures(), UP, 2*LARGE_BUFF) - for part in group: - part.set_color_by_tex("t", YELLOW) - const = part.get_part_by_tex("dots") - if const: - const.set_color(BLUE) - brace = Brace(const, UP) - log = brace.get_text( - "$\\log_{%s}(2)$"%part[1].get_tex_string()[:-1] - ) - log.set_color(BLUE) - part.add(brace, log) - exp = VGroup(*rhs_list[0][1:4]) - rect = BackgroundRectangle(group) - - self.add(lhs, rhs_list[0]) - self.wait() - for rhs in rhs_list[1:]: - self.play(FadeIn( - rhs, - run_time = 2, - lag_ratio = 0.5, - )) - self.wait(2) - self.wait() - self.play( - FadeIn(rect), - exp.next_to, self.teacher, UP+LEFT, - self.teacher.change, "raise_right_hand", - ) - self.play(*[ - ApplyFunction( - lambda m : m.shift(SMALL_BUFF*UP).set_color(RED), - part, - run_time = 2, - rate_func = squish_rate_func(there_and_back, a, a+0.3) - ) - for part, a in zip(exp[1], np.linspace(0, 0.7, len(exp[1]))) - ]) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = exp - ) - self.wait(3) - -class TooManySymbols(TeacherStudentsScene): - def construct(self): - self.student_says( - "Too symbol heavy!", - target_mode = "pleading" - ) - self.play(self.teacher.change_mode, "guilty") - self.wait(3) - -class TemperatureOverTimeOfWarmWater(GraphScene): - CONFIG = { - "x_min" : 0, - "x_axis_label" : "$t$", - "y_axis_label" : "Temperature", - "T_room" : 4, - "include_solution" : False, - } - def construct(self): - self.setup_axes() - graph = self.get_graph( - lambda t : 3*np.exp(-0.3*t) + self.T_room, - color = RED - ) - h_line = DashedLine(*[ - self.coords_to_point(x, self.T_room) - for x in (self.x_min, self.x_max) - ]) - T_room_label = TexMobject("T_{\\text{room}}") - T_room_label.next_to(h_line, LEFT) - - ode = TexMobject( - "\\frac{d\\Delta T}{dt} = -k \\Delta T" - ) - ode.to_corner(UP+RIGHT) - - solution = TexMobject( - "\\Delta T(", "t", ") = e", "^{-k", "t}" - ) - solution.next_to(ode, DOWN, MED_LARGE_BUFF) - solution.set_color_by_tex("t", YELLOW) - solution.set_color_by_tex("Delta", WHITE) - - delta_T_brace = Brace(graph, RIGHT) - delta_T_label = TexMobject("\\Delta T") - delta_T_group = VGroup(delta_T_brace, delta_T_label) - def update_delta_T_group(group): - brace, label = group - v_line = Line( - graph.points[-1], - graph.points[-1][0]*RIGHT + h_line.get_center()[1]*UP - ) - brace.set_height(v_line.get_height()) - brace.next_to(v_line, RIGHT, SMALL_BUFF) - label.set_height(min( - label.get_height(), - brace.get_height() - )) - label.next_to(brace, RIGHT, SMALL_BUFF) - - self.add(ode) - self.play( - Write(T_room_label), - ShowCreation(h_line, run_time = 2) - ) - if self.include_solution: - self.play(Write(solution)) - graph_growth = ShowCreation(graph, rate_func=linear) - delta_T_group_update = UpdateFromFunc( - delta_T_group, update_delta_T_group - ) - self.play( - GrowFromCenter(delta_T_brace), - Write(delta_T_label), - ) - self.play(graph_growth, delta_T_group_update, run_time = 15) - self.wait(2) - -class TemperatureOverTimeOfWarmWaterWithSolution(TemperatureOverTimeOfWarmWater): - CONFIG = { - "include_solution" : True - } - -class InvestedMoney(Scene): - def construct(self): - # cash_str = "\\$\\$\\$" - cash_str = "M" - equation = TexMobject( - "{d", cash_str, "\\over", "dt}", - "=", "(1 + r)", cash_str - ) - equation.set_color_by_tex(cash_str, GREEN) - equation.next_to(ORIGIN, LEFT) - equation.to_edge(UP) - - arrow = Arrow(LEFT, RIGHT, color = WHITE) - arrow.next_to(equation) - - solution = TexMobject( - cash_str, "(", "t", ")", "=", "e^", "{(1+r)", "t}" - ) - solution.set_color_by_tex("t", YELLOW) - solution.set_color_by_tex(cash_str, GREEN) - solution.next_to(arrow, RIGHT) - - cash = TexMobject("\\$") - cash_pile = VGroup(*[ - cash.copy().shift( - x*(1+MED_SMALL_BUFF)*cash.get_width()*RIGHT +\ - y*(1+MED_SMALL_BUFF)*cash.get_height()*UP - ) - for x in range(40) - for y in range(8) - ]) - cash_pile.set_color(GREEN) - cash_pile.center() - cash_pile.shift(DOWN) - - anims = [] - cash_size = len(cash_pile) - run_time = 10 - const = np.log(cash_size)/run_time - for i, cash in enumerate(cash_pile): - start_time = np.log(i+1)/const - prop = start_time/run_time - rate_func = squish_rate_func( - smooth, - np.clip(prop-0.5/run_time, 0, 1), - np.clip(prop+0.5/run_time, 0, 1), - ) - anims.append(GrowFromCenter( - cash, rate_func = rate_func, - )) - - self.add(equation) - self.play(*anims, run_time = run_time) - self.wait() - self.play(ShowCreation(arrow)) - self.play(Write(solution, run_time = 2)) - self.wait() - self.play(FadeOut(cash_pile)) - self.play(*anims, run_time = run_time) - self.wait() - -class NaturalLog(Scene): - def construct(self): - expressions = VGroup(*list(map(self.get_expression, [2, 3, 7]))) - expressions.arrange(DOWN, buff = MED_SMALL_BUFF) - expressions.to_edge(LEFT) - - self.play(FadeIn( - expressions, - run_time = 3, - lag_ratio = 0.5 - )) - self.wait() - self.play( - expressions.set_fill, None, 1, - run_time = 2, - lag_ratio = 0.5 - ) - self.wait() - for i in 0, 2, 1: - self.show_natural_loggedness(expressions[i]) - - def show_natural_loggedness(self, expression): - base, constant = expression[1], expression[-3] - - log_constant, exp_constant = constant.copy(), constant.copy() - log_base, exp_base = base.copy(), base.copy() - log_equals, exp_equals = list(map(TexMobject, "==")) - - ln = TexMobject("\\ln(2)") - log_base.move_to(ln[-2]) - ln.remove(ln[-2]) - log_equals.next_to(ln, LEFT) - log_constant.next_to(log_equals, LEFT) - log_expression = VGroup( - ln, log_constant, log_equals, log_base - ) - - e = TexMobject("e") - exp_constant.scale(0.7) - exp_constant.next_to(e, UP+RIGHT, buff = 0) - exp_base.next_to(exp_equals, RIGHT) - VGroup(exp_base, exp_equals).next_to( - VGroup(e, exp_constant), RIGHT, - aligned_edge = DOWN - ) - exp_expression = VGroup( - e, exp_constant, exp_equals, exp_base - ) - - for group, vect in (log_expression, UP), (exp_expression, DOWN): - group.to_edge(RIGHT) - group.shift(vect) - - self.play( - ReplacementTransform(base.copy(), log_base), - ReplacementTransform(constant.copy(), log_constant), - run_time = 2 - ) - self.play(Write(ln), Write(log_equals)) - self.wait() - self.play( - ReplacementTransform( - log_expression.copy(), - exp_expression, - run_time = 2, - ) - ) - self.wait(2) - - ln_a = expression[-1] - self.play( - FadeOut(expression[-2]), - FadeOut(constant), - ln_a.move_to, constant, LEFT, - ln_a.set_color, BLUE - ) - self.wait() - self.play(*list(map(FadeOut, [log_expression, exp_expression]))) - self.wait() - - def get_expression(self, base): - expression = TexMobject( - "{d(", "%d^"%base, "t", ")", "\\over \\,", "dt}", - "=", "%d^"%base, "t", "(%.4f\\dots)"%np.log(base), - ) - expression.set_color_by_tex("t", YELLOW) - expression.set_color_by_tex("dt", GREEN) - expression.set_color_by_tex("\\dots", BLUE) - - brace = Brace(expression.get_part_by_tex("\\dots"), UP) - brace_text = brace.get_text("$\\ln(%d)$"%base) - for mob in brace, brace_text: - mob.set_fill(opacity = 0) - - expression.add(brace, brace_text) - return expression - -class NextVideo(TeacherStudentsScene): - def construct(self): - series = VideoSeries() - series.to_edge(UP) - this_video = series[3] - next_video = series[4] - brace = Brace(this_video, DOWN) - this_video.save_state() - this_video.set_color(YELLOW) - - this_tex = TexMobject( - "{d(", "a^t", ") \\over dt} = ", "a^t", "\\ln(a)" - ) - this_tex[1][1].set_color(YELLOW) - this_tex[3][1].set_color(YELLOW) - this_tex.next_to(brace, DOWN) - - next_tex = VGroup(*list(map(TextMobject, [ - "Chain rule", "Product rule", "$\\vdots$" - ]))) - next_tex.arrange(DOWN) - next_tex.next_to(brace, DOWN) - next_tex.shift( - next_video.get_center()[0]*RIGHT\ - -next_tex.get_center()[0]*RIGHT - ) - - self.add(series, brace, *this_tex[:3]) - self.change_student_modes( - "confused", "pondering", "erm", - look_at_arg = this_tex - ) - self.play(ReplacementTransform( - this_tex[1].copy(), this_tex[3] - )) - self.wait() - self.play( - Write(this_tex[4]), - ReplacementTransform( - this_tex[3][0].copy(), - this_tex[4][3], - path_arc = np.pi, - remover = True - ) - ) - self.wait(2) - self.play(this_tex.replace, this_video) - self.play( - brace.next_to, next_video, DOWN, - this_video.restore, - Animation(this_tex), - next_video.set_color, YELLOW, - Write(next_tex), - self.get_teacher().change_mode, "raise_right_hand" - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = next_tex - ) - self.wait(3) - -class ExpPatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "Meshal Alshammari", - "CrypticSwarm ", - "Kathryn Schmiedicke", - "Nathan Pellegrin", - "Karan Bhargava", - "Justin Helps", - "Ankit Agarwal", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Justin Helps", - "Markus Persson", - "Dan Buchoff", - "Derek Dai", - "Joseph John Cox", - "Luc Ritchie", - "Mustafa Mahdi", - "Daan Smedinga", - "Jonathan Eppele", - "Albert Nguyen", - "Nils Schneider", - "Mustafa Mahdi", - "Mathew Bramson", - "Guido Gambardella", - "Jerry Ling", - "Mark Govea", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Kirk Werklund", - "Ripta Pasay", - "Felipe Diniz", - ] - } - -class Thumbnail(GraphOfTwoToT): - CONFIG = { - "x_axis_label" : "", - "y_axis_label" : "", - "x_labeled_nums" : None, - "y_labeled_nums" : None, - "y_max" : 32, - "y_tick_frequency" : 4, - "graph_origin" : 3*DOWN + 5*LEFT, - "x_axis_width" : 12, - } - def construct(self): - derivative = TexMobject( - "\\frac{d(a^t)}{dt} =", "a^t \\ln(a)" - ) - derivative[0][3].set_color(YELLOW) - derivative[1][1].set_color(YELLOW) - derivative[0][2].set_color(BLUE) - derivative[1][0].set_color(BLUE) - derivative[1][5].set_color(BLUE) - derivative.scale(2) - derivative.add_background_rectangle() - derivative.to_corner(DOWN+RIGHT, buff = MED_SMALL_BUFF) - - # brace = Brace(Line(LEFT, RIGHT), UP) - # brace.set_width(derivative[1].get_width()) - # brace.next_to(derivative[1], UP) - # question = TextMobject("Why?") - # question.scale(2.5) - # question.next_to(brace, UP) - - # randy = Randolph() - # randy.scale(1.3) - # randy.next_to(ORIGIN, LEFT).to_edge(DOWN) - # randy.change_mode("pondering") - - question = TextMobject("What is $e\\,$?") - e = question[-2] - e.scale(1.2, about_point = e.get_bottom()) - e.set_color(BLUE) - question.scale(3) - question.add_background_rectangle() - question.to_edge(UP) - # randy.look_at(question) - - self.setup_axes() - graph = self.get_graph(np.exp) - graph.set_stroke(YELLOW, 8) - - self.add(graph, question, derivative) - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eoc/chapter6.py b/from_3b1b/old/eoc/chapter6.py deleted file mode 100644 index 4ad37a07..00000000 --- a/from_3b1b/old/eoc/chapter6.py +++ /dev/null @@ -1,2671 +0,0 @@ -from manimlib.imports import * - -SPACE_UNIT_TO_PLANE_UNIT = 0.75 - -class Chapter6OpeningQuote(OpeningQuote): - CONFIG = { - "quote" : [ - "Do not ask whether a ", - "statement is true until", - "you know what it means." - ], - "author" : "Errett Bishop" - } - -class ThisWasConfusing(TeacherStudentsScene): - def construct(self): - words = TextMobject("Implicit differentiation") - words.move_to(self.get_teacher().get_corner(UP+LEFT), DOWN+RIGHT) - words.set_fill(opacity = 0) - - self.play( - self.get_teacher().change_mode, "raise_right_hand", - words.set_fill, None, 1, - words.shift, 0.5*UP - ) - self.change_student_modes( - *["confused"]*3, - look_at_arg = words, - added_anims = [Animation(self.get_teacher())] - ) - self.wait() - self.play( - self.get_teacher().change_mode, "confused", - self.get_teacher().look_at, words, - ) - self.wait(3) - -class SlopeOfCircleExample(ZoomedScene): - CONFIG = { - "plane_kwargs" : { - "x_radius" : FRAME_X_RADIUS/SPACE_UNIT_TO_PLANE_UNIT, - "y_radius" : FRAME_Y_RADIUS/SPACE_UNIT_TO_PLANE_UNIT, - "space_unit_to_x_unit" : SPACE_UNIT_TO_PLANE_UNIT, - "space_unit_to_y_unit" : SPACE_UNIT_TO_PLANE_UNIT, - }, - "example_point" : (3, 4), - "circle_radius" : 5, - "circle_color" : YELLOW, - "example_color" : MAROON_B, - "zoom_factor" : 20, - "zoomed_canvas_corner" : UP+LEFT, - "zoomed_canvas_corner_buff" : MED_SMALL_BUFF, - } - def construct(self): - self.setup_plane() - self.introduce_circle() - self.talk_through_pythagorean_theorem() - self.draw_example_slope() - self.show_perpendicular_radius() - self.show_dx_and_dy() - self.write_slope_as_dy_dx() - self.point_out_this_is_not_a_graph() - self.perform_implicit_derivative() - self.show_final_slope() - - def setup_plane(self): - self.plane = NumberPlane(**self.plane_kwargs) - self.planes.fade() - self.plane.add(self.plane.get_axis_labels()) - self.plane.add_coordinates() - - self.add(self.plane) - - def introduce_circle(self): - circle = Circle( - radius = self.circle_radius*SPACE_UNIT_TO_PLANE_UNIT, - color = self.circle_color, - ) - equation = TexMobject("x^2 + y^2 = 5^2") - equation.add_background_rectangle() - equation.next_to( - circle.point_from_proportion(1./8), - UP+RIGHT - ) - equation.to_edge(RIGHT) - - self.play(ShowCreation(circle, run_time = 2)) - self.play(Write(equation)) - self.wait() - - self.circle = circle - self.circle_equation = equation - - def talk_through_pythagorean_theorem(self): - point = self.plane.num_pair_to_point(self.example_point) - x_axis_point = point[0]*RIGHT - dot = Dot(point, color = self.example_color) - - x_line = Line(ORIGIN, x_axis_point, color = GREEN) - y_line = Line(x_axis_point, point, color = RED) - radial_line = Line(ORIGIN, point, color = self.example_color) - lines = VGroup(radial_line, x_line, y_line) - labels = VGroup() - - self.play(ShowCreation(dot)) - for line, tex in zip(lines, "5xy"): - label = TexMobject(tex) - label.set_color(line.get_color()) - label.add_background_rectangle() - label.next_to( - line.get_center(), - rotate_vector(UP, line.get_angle()), - buff = SMALL_BUFF - ) - self.play( - ShowCreation(line), - Write(label) - ) - labels.add(label) - - full_group = VGroup(dot, lines, labels) - start_angle = angle_of_vector(point) - end_angle = np.pi/12 - spatial_radius = get_norm(point) - def update_full_group(group, alpha): - dot, lines, labels = group - angle = interpolate(start_angle, end_angle, alpha) - new_point = spatial_radius*rotate_vector(RIGHT, angle) - new_x_axis_point = new_point[0]*RIGHT - dot.move_to(new_point) - - radial_line, x_line, y_line = lines - x_line.put_start_and_end_on(ORIGIN, new_x_axis_point) - y_line.put_start_and_end_on(new_x_axis_point, new_point) - radial_line.put_start_and_end_on(ORIGIN, new_point) - for line, label in zip(lines, labels): - label.next_to( - line.get_center(), - rotate_vector(UP, line.get_angle()), - buff = SMALL_BUFF - ) - return group - - self.play(UpdateFromAlphaFunc( - full_group, update_full_group, - rate_func = there_and_back, - run_time = 5, - )) - self.wait(2) - - #Move labels to equation - movers = labels.copy() - pairs = list(zip( - [movers[1], movers[2], movers[0]], - self.circle_equation[1][0:-1:3] - )) - self.play(*[ - ApplyMethod(m1.replace, m2) - for m1, m2 in pairs - ]) - self.wait() - - self.play(*list(map(FadeOut, [lines, labels, movers]))) - self.remove(full_group) - self.add(dot) - self.wait() - - self.example_point_dot = dot - - def draw_example_slope(self): - point = self.example_point_dot.get_center() - line = Line(ORIGIN, point) - line.set_color(self.example_color) - line.rotate(np.pi/2) - line.scale(2) - line.move_to(point) - - word = TextMobject("Slope?") - word.next_to(line.get_start(), UP, aligned_edge = LEFT) - word.add_background_rectangle() - - coords = TexMobject("(%d, %d)"%self.example_point) - coords.add_background_rectangle() - coords.scale(0.7) - coords.next_to(point, LEFT) - coords.shift(SMALL_BUFF*DOWN) - coords.set_color(self.example_color) - - self.play(GrowFromCenter(line)) - self.play(Write(word)) - self.wait() - self.play(Write(coords)) - self.wait() - - self.tangent_line = line - self.slope_word = word - self.example_point_coords_mob = coords - - def show_perpendicular_radius(self): - point = self.example_point_dot.get_center() - radial_line = Line(ORIGIN, point, color = RED) - - perp_mark = VGroup( - Line(UP, UP+RIGHT), - Line(UP+RIGHT, RIGHT), - ) - perp_mark.scale(0.2) - perp_mark.set_stroke(width = 2) - perp_mark.rotate(radial_line.get_angle()+np.pi) - perp_mark.shift(point) - - self.play(ShowCreation(radial_line)) - self.play(ShowCreation(perp_mark)) - self.wait() - self.play(Indicate(perp_mark)) - self.wait() - - morty = Mortimer().flip().to_corner(DOWN+LEFT) - self.play(FadeIn(morty)) - self.play(PiCreatureBubbleIntroduction( - morty, "Suppose you \\\\ don't know this.", - )) - to_fade =self.get_mobjects_from_last_animation() - self.play(Blink(morty)) - self.wait() - - self.play(*list(map(FadeOut, to_fade))) - self.play(*list(map(FadeOut, [radial_line, perp_mark]))) - self.wait() - - def show_dx_and_dy(self): - dot = self.example_point_dot - point = dot.get_center() - step_vect = rotate_vector(point, np.pi/2) - step_length = 1./self.zoom_factor - step_vect *= step_length/get_norm(step_vect) - - step_line = Line(ORIGIN, LEFT) - step_line.set_color(WHITE) - brace = Brace(step_line, DOWN) - step_text = brace.get_text("Step", buff = SMALL_BUFF) - step_group = VGroup(step_line, brace, step_text) - step_group.rotate(angle_of_vector(point) - np.pi/2) - step_group.scale(1./self.zoom_factor) - step_group.shift(point) - - interim_point = step_line.get_corner(UP+RIGHT) - dy_line = Line(point, interim_point) - dx_line = Line(interim_point, point+step_vect) - dy_line.set_color(RED) - dx_line.set_color(GREEN) - for line, tex in (dx_line, "dx"), (dy_line, "dy"): - label = TexMobject(tex) - label.scale(1./self.zoom_factor) - next_to_vect = np.round( - rotate_vector(DOWN, line.get_angle()) - ) - label.next_to( - line, next_to_vect, - buff = MED_SMALL_BUFF/self.zoom_factor - ) - label.set_color(line.get_color()) - line.label = label - - self.activate_zooming() - self.little_rectangle.move_to(step_line.get_center()) - self.little_rectangle.save_state() - self.little_rectangle.scale_in_place(self.zoom_factor) - self.wait() - self.play( - self.little_rectangle.restore, - dot.scale_in_place, 1./self.zoom_factor, - run_time = 2 - ) - self.wait() - self.play(ShowCreation(step_line)) - self.play(GrowFromCenter(brace)) - self.play(Write(step_text)) - self.wait() - for line in dy_line, dx_line: - self.play(ShowCreation(line)) - self.play(Write(line.label)) - self.wait() - self.wait() - - self.step_group = step_group - self.dx_line = dx_line - self.dy_line = dy_line - - def write_slope_as_dy_dx(self): - slope_word = self.slope_word - new_slope_word = TextMobject("Slope =") - new_slope_word.add_background_rectangle() - new_slope_word.next_to(ORIGIN, RIGHT) - new_slope_word.shift(slope_word.get_center()[1]*UP) - - dy_dx = TexMobject("\\frac{dy}{dx}") - VGroup(*dy_dx[:2]).set_color(RED) - VGroup(*dy_dx[-2:]).set_color(GREEN) - dy_dx.next_to(new_slope_word, RIGHT) - dy_dx.add_background_rectangle() - - self.play(Transform(slope_word, new_slope_word)) - self.play(Write(dy_dx)) - self.wait() - - self.dy_dx = dy_dx - - def point_out_this_is_not_a_graph(self): - equation = self.circle_equation - x = equation[1][0] - y = equation[1][3] - brace = Brace(equation, DOWN) - brace_text = brace.get_text( - "Not $y = f(x)$", - buff = SMALL_BUFF - ) - brace_text.set_color(RED) - alt_brace_text = brace.get_text("Implicit curve") - for text in brace_text, alt_brace_text: - text.add_background_rectangle() - text.to_edge(RIGHT, buff = MED_SMALL_BUFF) - - new_circle = self.circle.copy() - new_circle.set_color(BLUE) - - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait() - self.play(Indicate(x)) - self.wait() - self.play(Indicate(y)) - self.wait() - self.play(Transform(brace_text, alt_brace_text)) - self.wait() - self.play( - ShowCreation(new_circle, run_time = 2), - Animation(brace_text) - ) - self.play(new_circle.set_stroke, None, 0) - self.wait() - self.play(*list(map(FadeOut, [brace, brace_text]))) - self.wait() - - def perform_implicit_derivative(self): - equation = self.circle_equation - morty = Mortimer() - morty.flip() - morty.next_to(ORIGIN, LEFT) - morty.to_edge(DOWN, buff = SMALL_BUFF) - q_marks = TexMobject("???") - q_marks.next_to(morty, UP) - - rect = Rectangle( - width = FRAME_X_RADIUS - SMALL_BUFF, - height = FRAME_Y_RADIUS - SMALL_BUFF, - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 0.8, - ) - rect.to_corner(DOWN+RIGHT, buff = 0) - - derivative = TexMobject("2x\\,dx + 2y\\,dy = 0") - dx = VGroup(*derivative[2:4]) - dy = VGroup(*derivative[7:9]) - dx.set_color(GREEN) - dy.set_color(RED) - - - - self.play( - FadeIn(rect), - FadeIn(morty), - equation.next_to, ORIGIN, DOWN, MED_LARGE_BUFF, - equation.shift, FRAME_X_RADIUS*RIGHT/2, - ) - self.play( - morty.change_mode, "confused", - morty.look_at, equation - ) - self.play(Blink(morty)) - derivative.next_to(equation, DOWN) - derivative.shift( - equation[1][-3].get_center()[0]*RIGHT - \ - derivative[-2].get_center()[0]*RIGHT - ) - - - #Differentiate - self.play( - morty.look_at, derivative[0], - *[ - ReplacementTransform( - equation[1][i].copy(), - derivative[j], - ) - for i, j in ((1, 0), (0, 1)) - ] - ) - self.play(Write(dx, run_time = 1)) - self.wait() - self.play(*[ - ReplacementTransform( - equation[1][i].copy(), - derivative[j], - ) - for i, j in ((2, 4), (3, 6), (4, 5)) - ]) - self.play(Write(dy, run_time = 1)) - self.play(Blink(morty)) - self.play(*[ - ReplacementTransform( - equation[1][i].copy(), - derivative[j], - ) - for i, j in ((-3, -2), (-2, -1), (-1, -1)) - ]) - self.wait() - - #React - self.play(morty.change_mode, "erm") - self.play(Blink(morty)) - self.play(Write(q_marks)) - self.wait() - self.play(Indicate(dx), morty.look_at, dx) - self.play(Indicate(dy), morty.look_at, dy) - self.wait() - self.play( - morty.change_mode, "shruggie", - FadeOut(q_marks) - ) - self.play(Blink(morty)) - self.play( - morty.change_mode, "pondering", - morty.look_at, derivative, - ) - - #Rearrange - x, y, eq = np.array(derivative)[[1, 6, 9]] - final_form = TexMobject( - "\\frac{dy}{dx} = \\frac{-x}{y}" - ) - new_dy = VGroup(*final_form[:2]) - new_dx = VGroup(*final_form[3:5]) - new_dy.set_color(dy.get_color()) - new_dx.set_color(dx.get_color()) - new_dy.add(final_form[2]) - new_x = VGroup(*final_form[6:8]) - new_y = VGroup(*final_form[8:10]) - new_eq = final_form[5] - - final_form.next_to(derivative, DOWN) - final_form.shift((eq.get_center()[0]-new_eq.get_center()[0])*RIGHT) - - - self.play(*[ - ReplacementTransform( - mover.copy(), target, - run_time = 2, - path_arc = np.pi/2, - ) - for mover, target in [ - (dy, new_dy), - (dx, new_dx), - (eq, new_eq), - (x, new_x), - (y, new_y) - ] - ] + [ - morty.look_at, final_form - ]) - self.wait(2) - - self.morty = morty - self.neg_x_over_y = VGroup(*final_form[6:]) - - def show_final_slope(self): - morty = self.morty - dy_dx = self.dy_dx - coords = self.example_point_coords_mob - x, y = coords[1][1].copy(), coords[1][3].copy() - - frac = self.neg_x_over_y.copy() - frac.generate_target() - eq = TexMobject("=") - eq.add_background_rectangle() - eq.next_to(dy_dx, RIGHT) - frac.target.next_to(eq, RIGHT) - frac.target.shift(SMALL_BUFF*DOWN) - rect = BackgroundRectangle(frac.target) - - self.play( - FadeIn(rect), - MoveToTarget(frac), - Write(eq), - morty.look_at, rect, - run_time = 2, - ) - self.wait() - self.play(FocusOn(coords), morty.look_at, coords) - self.play(Indicate(coords)) - scale_factor = 1.4 - self.play( - x.scale, scale_factor, - x.set_color, GREEN, - x.move_to, frac[1], - FadeOut(frac[1]), - y.scale, scale_factor, - y.set_color, RED, - y.move_to, frac[3], DOWN, - y.shift, SMALL_BUFF*UP, - FadeOut(frac[3]), - morty.look_at, frac, - run_time = 2 - ) - self.wait() - self.play(Blink(morty)) - -class NameImplicitDifferentation(TeacherStudentsScene): - def construct(self): - title = TextMobject("``Implicit differentiation''") - - equation = TexMobject("x^2", "+", "y^2", "=", "5^2") - derivative = TexMobject( - "2x\\,dx", "+", "2y\\,dy", "=", "0" - ) - VGroup(*derivative[0][2:]).set_color(GREEN) - VGroup(*derivative[2][2:]).set_color(RED) - arrow = Arrow(ORIGIN, DOWN, buff = SMALL_BUFF) - group = VGroup(title, equation, arrow, derivative) - group.arrange(DOWN) - group.to_edge(UP) - - self.add(title, equation) - self.play( - self.get_teacher().change_mode, "raise_right_hand", - ShowCreation(arrow) - ) - self.change_student_modes( - *["confused"]*3, - look_at_arg = derivative, - added_anims = [ReplacementTransform(equation.copy(), derivative)] - ) - self.wait(2) - self.teacher_says( - "Don't worry...", - added_anims = [ - group.scale, 0.7, - group.to_corner, UP+LEFT, - ] - ) - self.change_student_modes(*["happy"]*3) - self.wait(3) - -class Ladder(VMobject): - CONFIG = { - "height" : 4, - "width" : 1, - "n_rungs" : 7, - } - def init_points(self): - left_line, right_line = [ - Line(ORIGIN, self.height*UP).shift(self.width*vect/2.0) - for vect in (LEFT, RIGHT) - ] - rungs = [ - Line( - left_line.point_from_proportion(a), - right_line.point_from_proportion(a), - ) - for a in np.linspace(0, 1, 2*self.n_rungs+1)[1:-1:2] - ] - self.add(left_line, right_line, *rungs) - self.center() - -class RelatedRatesExample(ThreeDScene): - CONFIG = { - "start_x" : 3.0, - "start_y" : 4.0, - "wall_dimensions" : [0.3, 5, 5], - "wall_color" : color_gradient([GREY_BROWN, BLACK], 4)[1], - "wall_center" : 1.5*LEFT+0.5*UP, - } - def construct(self): - self.introduce_ladder() - self.write_related_rates() - self.measure_ladder() - self.slide_ladder() - self.ponder_question() - self.write_equation() - self.isolate_x_of_t() - self.discuss_lhs_as_function() - self.let_dt_pass() - self.take_derivative_of_rhs() - self.take_derivative_of_lhs() - self.bring_back_velocity_arrows() - self.replace_terms_in_final_form() - self.write_final_solution() - - def introduce_ladder(self): - ladder = Ladder(height = self.get_ladder_length()) - - wall = Prism( - dimensions = self.wall_dimensions, - fill_color = self.wall_color, - fill_opacity = 1, - ) - wall.rotate(np.pi/12, UP) - wall.shift(self.wall_center) - - ladder.generate_target() - ladder.fallen = ladder.copy() - ladder.target.rotate(self.get_ladder_angle(), LEFT) - ladder.fallen.rotate(np.pi/2, LEFT) - for ladder_copy in ladder.target, ladder.fallen: - ladder_copy.rotate(-5*np.pi/12, UP) - ladder_copy.next_to(wall, LEFT, 0, DOWN) - ladder_copy.shift(0.8*RIGHT) ##BAD! - - - self.play( - ShowCreation(ladder, run_time = 2) - ) - self.wait() - - self.play( - DrawBorderThenFill(wall), - MoveToTarget(ladder), - run_time = 2 - ) - self.wait() - - self.ladder = ladder - - def write_related_rates(self): - words = TextMobject("Related rates") - words.to_corner(UP+RIGHT) - self.play(Write(words)) - self.wait() - - self.related_rates_words = words - - def measure_ladder(self): - ladder = self.ladder - ladder_brace = self.get_ladder_brace(ladder) - - x_and_y_lines = self.get_x_and_y_lines(ladder) - x_line, y_line = x_and_y_lines - - y_label = TexMobject("%dm"%int(self.start_y)) - y_label.next_to(y_line, LEFT, buff = SMALL_BUFF) - y_label.set_color(y_line.get_color()) - - x_label = TexMobject("%dm"%int(self.start_x)) - x_label.next_to(x_line, UP) - x_label.set_color(x_line.get_color()) - - self.play(Write(ladder_brace)) - self.wait() - self.play(ShowCreation(y_line), Write(y_label)) - self.wait() - self.play(ShowCreation(x_line), Write(x_label)) - self.wait(2) - self.play(*list(map(FadeOut, [x_label, y_label]))) - - self.ladder_brace = ladder_brace - self.x_and_y_lines = x_and_y_lines - self.numerical_x_and_y_labels = VGroup(x_label, y_label) - - def slide_ladder(self): - ladder = self.ladder - brace = self.ladder_brace - x_and_y_lines = self.x_and_y_lines - x_line, y_line = x_and_y_lines - - down_arrow, left_arrow = [ - Arrow(ORIGIN, vect, color = YELLOW, buff = 0) - for vect in (DOWN, LEFT) - ] - down_arrow.shift(y_line.get_start()+MED_SMALL_BUFF*RIGHT) - left_arrow.shift(x_line.get_start()+SMALL_BUFF*DOWN) - - # speed_label = TexMobject("1 \\text{m}/\\text{s}") - speed_label = TexMobject("1 \\frac{\\text{m}}{\\text{s}}") - speed_label.next_to(down_arrow, RIGHT, buff = SMALL_BUFF) - - q_marks = TexMobject("???") - q_marks.next_to(left_arrow, DOWN, buff = SMALL_BUFF) - - - added_anims = [ - UpdateFromFunc(brace, self.update_brace), - UpdateFromFunc(x_and_y_lines, self.update_x_and_y_lines), - Animation(down_arrow), - ] - self.play(ShowCreation(down_arrow)) - self.play(Write(speed_label)) - self.let_ladder_fall(ladder, *added_anims) - self.wait() - self.reset_ladder(ladder, *added_anims) - self.play(ShowCreation(left_arrow)) - self.play(Write(q_marks)) - self.wait() - self.let_ladder_fall(ladder, *added_anims) - self.wait() - self.reset_ladder(ladder, *added_anims) - self.wait() - - self.dy_arrow = down_arrow - self.dy_label = speed_label - self.dx_arrow = left_arrow - self.dx_label = q_marks - - def ponder_question(self): - randy = Randolph(mode = "pondering") - randy.flip() - randy.to_corner(DOWN+RIGHT) - - self.play(FadeIn(randy)) - self.play(Blink(randy)) - self.wait() - self.play( - randy.change_mode, "confused", - randy.look_at, self.ladder.get_top() - ) - self.play(randy.look_at, self.ladder.get_bottom()) - self.play(randy.look_at, self.ladder.get_top()) - self.play(Blink(randy)) - self.wait() - self.play(PiCreatureSays( - randy, "Give names" - )) - self.play(Blink(randy)) - self.play(*list(map(FadeOut, [ - randy, randy.bubble, randy.bubble.content - ]))) - - def write_equation(self): - self.x_and_y_labels = self.get_x_and_y_labels() - x_label, y_label = self.x_and_y_labels - - equation = TexMobject( - "x(t)", "^2", "+", "y(t)", "^2", "=", "5^2" - ) - equation[0].set_color(GREEN) - equation[3].set_color(RED) - equation.next_to(self.related_rates_words, DOWN, buff = MED_LARGE_BUFF) - equation.to_edge(RIGHT, buff = LARGE_BUFF) - - self.play(Write(y_label)) - self.wait() - self.let_ladder_fall( - self.ladder, - y_label.shift, self.start_y*DOWN/2, - *self.get_added_anims_for_ladder_fall()[:-1], - rate_func = lambda t : 0.2*there_and_back(t), - run_time = 3 - ) - self.play(FocusOn(x_label)) - self.play(Write(x_label)) - self.wait(2) - self.play( - ReplacementTransform(x_label.copy(), equation[0]), - ReplacementTransform(y_label.copy(), equation[3]), - Write(VGroup(*np.array(equation)[[1, 2, 4, 5, 6]])) - ) - self.wait(2) - self.let_ladder_fall( - self.ladder, - *self.get_added_anims_for_ladder_fall(), - rate_func = there_and_back, - run_time = 6 - ) - self.wait() - - self.equation = equation - - def isolate_x_of_t(self): - alt_equation = TexMobject( - "x(t)", "=", "\\big(5^2", "-", "y(t)", "^2 \\big)", "^{1/2}", - ) - alt_equation[0].set_color(GREEN) - alt_equation[4].set_color(RED) - alt_equation.next_to(self.equation, DOWN, buff = MED_LARGE_BUFF) - alt_equation.to_edge(RIGHT) - - randy = Randolph() - randy.next_to( - alt_equation, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT, - ) - randy.look_at(alt_equation) - - find_dx_dt = TexMobject("\\text{Find } \\,", "\\frac{dx}{dt}") - find_dx_dt.next_to(randy, RIGHT, aligned_edge = UP) - find_dx_dt[1].set_color(GREEN) - - self.play(FadeIn(randy)) - self.play( - randy.change_mode, "raise_right_hand", - randy.look_at, alt_equation, - *[ - ReplacementTransform( - self.equation[i].copy(), - alt_equation[j], - path_arc = np.pi/2, - run_time = 3, - rate_func = squish_rate_func( - smooth, j/12.0, (j+6)/12.0 - ) - ) - for i, j in enumerate([0, 6, 3, 4, 5, 1, 2]) - ] - ) - self.play(Blink(randy)) - self.wait() - self.play( - Write(find_dx_dt), - randy.change_mode, "pondering", - randy.look_at, find_dx_dt, - ) - self.let_ladder_fall( - self.ladder, *self.get_added_anims_for_ladder_fall(), - run_time = 8, - rate_func = there_and_back - ) - self.play(*list(map(FadeOut, [ - randy, find_dx_dt, alt_equation - ]))) - self.wait() - - def discuss_lhs_as_function(self): - equation = self.equation - lhs = VGroup(*equation[:5]) - brace = Brace(lhs, DOWN) - function_of_time = brace.get_text( - "Function of time" - ) - constant_words = TextMobject( - """that happens to - be constant""" - ) - constant_words.set_color(YELLOW) - constant_words.next_to(function_of_time, DOWN) - - derivative = TexMobject( - "\\frac{d\\left(x(t)^2 + y(t)^2 \\right)}{dt}" - ) - derivative.next_to(equation, DOWN, buff = MED_LARGE_BUFF) - derivative.shift( ##Align x terms - equation[0][0].get_center()[0]*RIGHT-\ - derivative[2].get_center()[0]*RIGHT - ) - derivative_interior = lhs.copy() - derivative_interior.move_to(VGroup(*derivative[2:13])) - derivative_scaffold = VGroup( - *list(derivative[:2])+list(derivative[13:]) - ) - - self.play( - GrowFromCenter(brace), - Write(function_of_time) - ) - self.wait() - self.play(Write(constant_words)) - self.let_ladder_fall( - self.ladder, *self.get_added_anims_for_ladder_fall(), - run_time = 6, - rate_func = lambda t : 0.5*there_and_back(t) - ) - self.wait() - self.play(*list(map(FadeOut, [ - brace, constant_words, function_of_time - ]))) - self.play( - ReplacementTransform(lhs.copy(), derivative_interior), - Write(derivative_scaffold), - ) - self.wait() - - self.derivative = VGroup( - derivative_scaffold, derivative_interior - ) - - def let_dt_pass(self): - dt_words = TextMobject("After", "$dt$", "seconds...") - dt_words.to_corner(UP+LEFT) - dt = dt_words[1] - dt.set_color(YELLOW) - dt_brace = Brace(dt, buff = SMALL_BUFF) - dt_brace_text = dt_brace.get_text("Think 0.01", buff = SMALL_BUFF) - dt_brace_text.set_color(dt.get_color()) - - shadow_ladder = self.ladder.copy() - shadow_ladder.fade(0.5) - - x_line, y_line = self.x_and_y_lines - y_top = y_line.get_start() - x_left = x_line.get_start() - - self.play(Write(dt_words, run_time = 2)) - self.play( - GrowFromCenter(dt_brace), - Write(dt_brace_text, run_time = 2) - ) - self.play(*list(map(FadeOut, [ - self.dy_arrow, self.dy_label, - self.dx_arrow, self.dx_label, - ]))) - self.add(shadow_ladder) - self.let_ladder_fall( - self.ladder, *self.get_added_anims_for_ladder_fall(), - rate_func = lambda t : 0.1*smooth(t), - run_time = 1 - ) - - new_y_top = y_line.get_start() - new_x_left = x_line.get_start() - - dy_line = Line(y_top, new_y_top) - dy_brace = Brace(dy_line, RIGHT, buff = SMALL_BUFF) - dy_label = dy_brace.get_text("$dy$", buff = SMALL_BUFF) - dy_label.set_color(RED) - - dx_line = Line(x_left, new_x_left) - dx_brace = Brace(dx_line, DOWN, buff = SMALL_BUFF) - dx_label = dx_brace.get_text("$dx$") - dx_label.set_color(GREEN) - - VGroup(dy_line, dx_line).set_color(YELLOW) - - for line, brace, label in (dy_line, dy_brace, dy_label), (dx_line, dx_brace, dx_label): - self.play( - ShowCreation(line), - GrowFromCenter(brace), - Write(label), - run_time = 1 - ) - self.wait() - self.play(Indicate(self.derivative[1])) - self.wait() - - self.dy_group = VGroup(dy_line, dy_brace, dy_label) - self.dx_group = VGroup(dx_line, dx_brace, dx_label) - self.shadow_ladder = shadow_ladder - - def take_derivative_of_rhs(self): - derivative = self.derivative - equals_zero = TexMobject("= 0") - equals_zero.next_to(derivative) - - rhs = self.equation[-1] - - self.play(Write(equals_zero)) - self.wait() - self.play(FocusOn(rhs)) - self.play(Indicate(rhs)) - self.wait() - self.reset_ladder( - self.ladder, - *self.get_added_anims_for_ladder_fall()+[ - Animation(self.dy_group), - Animation(self.dx_group), - ], - rate_func = there_and_back, - run_time = 3 - ) - self.wait() - - self.equals_zero = equals_zero - - def take_derivative_of_lhs(self): - derivative_scaffold, equation = self.derivative - equals_zero_copy = self.equals_zero.copy() - - lhs_derivative = TexMobject( - "2", "x(t)", "\\frac{dx}{dt}", "+", - "2", "y(t)", "\\frac{dy}{dt}", - ) - lhs_derivative[1].set_color(GREEN) - VGroup(*lhs_derivative[2][:2]).set_color(GREEN) - lhs_derivative[5].set_color(RED) - VGroup(*lhs_derivative[6][:2]).set_color(RED) - lhs_derivative.next_to( - derivative_scaffold, DOWN, - aligned_edge = RIGHT, - buff = MED_LARGE_BUFF - ) - equals_zero_copy.next_to(lhs_derivative, RIGHT) - - pairs = [ - (0, 1), (1, 0), #x^2 -> 2x - (2, 3), (3, 5), (4, 4), #+y^2 -> +2y - ] - def perform_replacement(index_pairs): - self.play(*[ - ReplacementTransform( - equation[i].copy(), lhs_derivative[j], - path_arc = np.pi/2, - run_time = 2 - ) - for i, j in index_pairs - ]) - - perform_replacement(pairs[:2]) - self.play(Write(lhs_derivative[2])) - self.wait() - self.play(Indicate( - VGroup( - *list(lhs_derivative[:2])+\ - list(lhs_derivative[2][:2]) - ), - run_time = 2 - )) - self.play(Indicate(VGroup(*lhs_derivative[2][3:]))) - self.wait(2) - perform_replacement(pairs[2:]) - self.play(Write(lhs_derivative[6])) - self.wait() - - self.play(FocusOn(self.equals_zero)) - self.play(ReplacementTransform( - self.equals_zero.copy(), - equals_zero_copy - )) - self.wait(2) - - lhs_derivative.add(equals_zero_copy) - self.lhs_derivative = lhs_derivative - - def bring_back_velocity_arrows(self): - dx_dy_group = VGroup(self.dx_group, self.dy_group) - arrow_group = VGroup( - self.dy_arrow, self.dy_label, - self.dx_arrow, self.dx_label, - ) - ladder_fall_args = [self.ladder] + self.get_added_anims_for_ladder_fall() - - - self.reset_ladder(*ladder_fall_args + [ - FadeOut(dx_dy_group), - FadeOut(self.derivative), - FadeOut(self.equals_zero), - self.lhs_derivative.shift, 2*UP, - ]) - self.remove(self.shadow_ladder) - self.play(FadeIn(arrow_group)) - self.let_ladder_fall(*ladder_fall_args) - self.wait() - self.reset_ladder(*ladder_fall_args) - self.wait() - - def replace_terms_in_final_form(self): - x_label, y_label = self.x_and_y_labels - num_x_label, num_y_label = self.numerical_x_and_y_labels - - new_lhs_derivative = TexMobject( - "2", "(%d)"%int(self.start_x), "\\frac{dx}{dt}", "+", - "2", "(%d)"%int(self.start_y), "(-1)", - "= 0" - ) - new_lhs_derivative[1].set_color(GREEN) - VGroup(*new_lhs_derivative[2][:2]).set_color(GREEN) - new_lhs_derivative[5].set_color(RED) - new_lhs_derivative.next_to( - self.lhs_derivative, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = RIGHT - ) - def fill_in_equation_part(*indices): - self.play(*[ - ReplacementTransform( - self.lhs_derivative[i].copy(), - new_lhs_derivative[i], - run_time = 2 - ) - for i in indices - ]) - - self.play(FadeOut(y_label), FadeIn(num_y_label)) - fill_in_equation_part(3, 4, 5) - self.play(FadeOut(x_label), FadeIn(num_x_label)) - for indices in [(0, 1), (6,), (2, 7)]: - fill_in_equation_part(*indices) - self.wait() - self.wait() - - self.new_lhs_derivative = new_lhs_derivative - - def write_final_solution(self): - solution = TexMobject( - "\\frac{dx}{dt} = \\frac{4}{3}" - ) - for i in 0, 1, -1: - solution[i].set_color(GREEN) - solution[-3].set_color(RED) - solution.next_to( - self.new_lhs_derivative, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = RIGHT - ) - - box = Rectangle(color = YELLOW) - box.replace(solution) - box.scale_in_place(1.5) - - self.play(Write(solution)) - self.wait() - self.play(ShowCreation(box)) - self.wait() - - ######### - - def get_added_anims_for_ladder_fall(self): - return [ - UpdateFromFunc(self.ladder_brace, self.update_brace), - UpdateFromFunc(self.x_and_y_lines, self.update_x_and_y_lines), - UpdateFromFunc(self.x_and_y_labels, self.update_x_and_y_labels), - ] - - def let_ladder_fall(self, ladder, *added_anims, **kwargs): - kwargs["run_time"] = kwargs.get("run_time", self.start_y) - kwargs["rate_func"] = kwargs.get("rate_func", None) - self.play( - Transform(ladder, ladder.fallen), - *added_anims, - **kwargs - ) - - def reset_ladder(self, ladder, *added_anims, **kwargs): - kwargs["run_time"] = kwargs.get("run_time", 2) - self.play( - Transform(ladder, ladder.target), - *added_anims, - **kwargs - ) - - def update_brace(self, brace): - Transform( - brace, self.get_ladder_brace(self.ladder) - ).update(1) - return brace - - def update_x_and_y_lines(self, x_and_y_lines): - Transform( - x_and_y_lines, - self.get_x_and_y_lines(self.ladder) - ).update(1) - return x_and_y_lines - - def update_x_and_y_labels(self, x_and_y_labels): - Transform( - x_and_y_labels, - self.get_x_and_y_labels() - ).update(1) - return x_and_y_labels - - def get_ladder_brace(self, ladder): - vect = rotate_vector(LEFT, -self.get_ladder_angle()) - brace = Brace(ladder, vect) - length_string = "%dm"%int(self.get_ladder_length()) - length_label = brace.get_text( - length_string, use_next_to = False - ) - brace.add(length_label) - brace.length_label = length_label - return brace - - def get_x_and_y_labels(self): - x_line, y_line = self.x_and_y_lines - - x_label = TexMobject("x(t)") - x_label.set_color(x_line.get_color()) - x_label.next_to(x_line, DOWN, buff = SMALL_BUFF) - - y_label = TexMobject("y(t)") - y_label.set_color(y_line.get_color()) - y_label.next_to(y_line, LEFT, buff = SMALL_BUFF) - - return VGroup(x_label, y_label) - - def get_x_and_y_lines(self, ladder): - bottom_point, top_point = np.array(ladder[1].get_start_and_end()) - interim_point = top_point[0]*RIGHT + bottom_point[1]*UP - interim_point += SMALL_BUFF*DOWN - y_line = Line(top_point, interim_point) - y_line.set_color(RED) - x_line = Line(bottom_point, interim_point) - x_line.set_color(GREEN) - - return VGroup(x_line, y_line) - - def get_ladder_angle(self): - if hasattr(self, "ladder"): - c1 = self.ladder.get_corner(UP+RIGHT) - c2 = self.ladder.get_corner(DOWN+LEFT) - vect = c1-c2 - return np.pi/2 - angle_of_vector(vect) - else: - return np.arctan(self.start_x/self.start_y) - - def get_ladder_length(self): - return get_norm([self.start_x, self.start_y]) - -class LightweightLadderScene(RelatedRatesExample): - CONFIG = { - "skip_animations" : True - } - def construct(self): - self.introduce_ladder() - self.measure_ladder() - self.add(self.numerical_x_and_y_labels) - -class LightweightCircleExample(SlopeOfCircleExample): - CONFIG = { - "skip_animations" : True, - "plane_kwargs" : { - "x_radius" : 5, - "y_radius" : 5, - "space_unit_to_x_unit" : SPACE_UNIT_TO_PLANE_UNIT, - "space_unit_to_y_unit" : SPACE_UNIT_TO_PLANE_UNIT, - }, - } - def construct(self): - self.setup_plane() - self.introduce_circle() - self.talk_through_pythagorean_theorem() - self.draw_example_slope() - - self.remove(self.circle_equation) - self.remove(self.slope_word) - self.example_point_coords_mob.scale( - 1.5, about_point = self.example_point_coords_mob.get_corner(UP+RIGHT) - ) - self.plane.axis_labels[0].shift(3*LEFT) - -class CompareLadderAndCircle(PiCreatureScene, ThreeDScene): - def construct(self): - self.introduce_both_scenes() - self.show_derivatives() - self.comment_on_ladder_derivative() - self.comment_on_circle_derivative() - - def introduce_both_scenes(self): - ladder_scene = LightweightLadderScene() - ladder_mobs = VGroup(*ladder_scene.get_top_level_mobjects()) - circle_scene = LightweightCircleExample() - circle_mobs = VGroup(*circle_scene.get_top_level_mobjects()) - for mobs, vect in (ladder_mobs, LEFT), (circle_mobs, RIGHT): - mobs.set_height(FRAME_Y_RADIUS-MED_LARGE_BUFF) - mobs.next_to( - self.pi_creature.get_corner(UP+vect), UP, - buff = SMALL_BUFF, - aligned_edge = -vect - ) - - ladder_mobs.save_state() - ladder_mobs.fade(1) - ladder_mobs.rotate_in_place(np.pi/3, UP) - self.play( - self.pi_creature.change_mode, "raise_right_hand", - self.pi_creature.look_at, ladder_mobs, - ApplyMethod(ladder_mobs.restore, run_time = 2) - ) - self.play( - self.pi_creature.change_mode, "raise_left_hand", - self.pi_creature.look_at, circle_mobs, - Write(circle_mobs, run_time = 2) - ) - self.wait(2) - self.play( - circle_mobs.to_edge, RIGHT, - ladder_mobs.to_edge, LEFT, - ) - - def show_derivatives(self): - equation = TexMobject( - "x", "^2", "+", "y", "^2", "= 5^2" - ) - derivative = TexMobject( - "2", "x", "dx", "+", "2", "y", "dy", "=0" - ) - self.color_equations(equation, derivative) - equation.to_edge(UP) - equation.shift(MED_LARGE_BUFF*LEFT) - derivative.next_to(equation, DOWN, buff = MED_LARGE_BUFF) - - self.play( - Write(equation), - self.pi_creature.change_mode, "plain", - self.pi_creature.look_at, equation - ) - self.wait() - self.play(*[ - ReplacementTransform( - equation[i].copy(), derivative[j], - path_arc = np.pi/2 - ) - for i, j in enumerate([1, 0, 3, 5, 4, 7]) - ]+[ - Write(derivative[j]) - for j in (2, 6) - ]) - self.play( - self.pi_creature.change_mode, "pondering", - self.pi_creature.look_at, derivative - ) - self.wait() - - self.equation = equation - self.derivative = derivative - - def comment_on_ladder_derivative(self): - equation = self.equation - derivative = self.derivative - - time_equation = TexMobject( - "x(t)", "^2", "+", "y(t)", "^2", "= 5^2" - ) - time_derivative = TexMobject( - "2", "x(t)", "\\frac{dx}{dt}", "+", - "2", "y(t)", "\\frac{dy}{dt}", "=0" - ) - self.color_equations(time_equation, time_derivative) - time_equation.move_to(equation) - time_derivative.move_to(derivative, UP) - - brace = Brace(time_derivative) - brace_text = brace.get_text("A rate") - - equation.save_state() - derivative.save_state() - - self.play(Transform(equation, time_equation)) - self.wait() - self.play(Transform(derivative, time_derivative)) - self.wait() - self.play(GrowFromCenter(brace)) - self.play(Write(brace_text)) - self.change_mode("hooray") - self.wait(2) - self.play( - equation.restore, - derivative.restore, - FadeOut(brace), - FadeOut(brace_text), - self.pi_creature.change_mode, "confused" - ) - self.wait() - - def comment_on_circle_derivative(self): - derivative = self.derivative - dx = derivative.get_part_by_tex("dx") - dy = derivative.get_part_by_tex("dy") - - for mob in dx, dy: - brace = Brace(mob) - brace.next_to(mob[0], DOWN, buff = SMALL_BUFF, aligned_edge = LEFT) - text = brace.get_text("No $dt$", buff = SMALL_BUFF) - text.set_color(YELLOW) - - self.play( - GrowFromCenter(brace), - Write(text) - ) - self.wait() - - self.play( - self.pi_creature.change_mode, "pondering", - self.pi_creature.look, DOWN+LEFT - ) - self.wait(2) - - ####### - - def create_pi_creature(self): - self.pi_creature = Mortimer().to_edge(DOWN) - return self.pi_creature - - def color_equations(self, equation, derivative): - for mob in equation[0], derivative[1]: - mob.set_color(GREEN) - for mob in equation[3], derivative[5]: - mob.set_color(RED) - -class TwoVariableFunctionAndDerivative(SlopeOfCircleExample): - CONFIG = { - "zoomed_canvas_corner" : DOWN+RIGHT, - "zoomed_canvas_frame_shape" : (3, 4), - } - def construct(self): - self.setup_plane() - self.write_equation() - self.show_example_point() - self.shift_example_point((4, 4)) - self.shift_example_point((3, 3)) - self.shift_example_point(self.example_point) - self.take_derivative_symbolically() - self.show_dx_dy_step() - self.plug_in_example_values() - self.ask_about_equalling_zero() - self.show_tangent_step() - self.point_out_equalling_zero() - self.show_tangent_line() - - def write_equation(self): - equation = TexMobject("x", "^2", "+", "y", "^2") - equation.add_background_rectangle() - - brace = Brace(equation, UP, buff = SMALL_BUFF) - s_expression = self.get_s_expression("x", "y") - s_rect, s_of_xy = s_expression - s, xy = s_of_xy - s_expression.next_to(brace, UP, buff = SMALL_BUFF) - - group = VGroup(equation, s_expression, brace) - group.shift(FRAME_WIDTH*LEFT/3) - group.to_edge(UP, buff = MED_SMALL_BUFF) - - s.save_state() - s.next_to(brace, UP) - - - self.play(Write(equation)) - self.play(GrowFromCenter(brace)) - self.play(Write(s)) - self.wait() - self.play( - FadeIn(s_rect), - s.restore, - GrowFromCenter(xy) - ) - self.wait() - - self.equation = equation - self.s_expression = s_expression - - def show_example_point(self): - point = self.plane.num_pair_to_point(self.example_point) - dot = Dot(point, color = self.example_color) - new_s_expression = self.get_s_expression(*self.example_point) - new_s_expression.next_to(dot, UP+RIGHT, buff = 0) - new_s_expression.set_color(self.example_color) - equals_25 = TexMobject("=%d"%int(get_norm(self.example_point)**2)) - equals_25.set_color(YELLOW) - equals_25.next_to(new_s_expression, RIGHT, align_using_submobjects = True) - equals_25.add_background_rectangle() - - circle = Circle( - radius = self.circle_radius*self.plane.space_unit_to_x_unit, - color = self.circle_color, - ) - - self.play( - ReplacementTransform( - self.s_expression.copy(), - new_s_expression - ), - ShowCreation(dot) - ) - self.play(ShowCreation(circle), Animation(dot)) - self.play(Write(equals_25)) - self.wait() - - self.example_point_dot = dot - self.example_point_label = VGroup( - new_s_expression, equals_25 - ) - - def shift_example_point(self, coords): - point = self.plane.num_pair_to_point(coords) - s_expression = self.get_s_expression(*coords) - s_expression.next_to(point, UP+RIGHT, buff = SMALL_BUFF) - s_expression.set_color(self.example_color) - result = coords[0]**2 + coords[1]**2 - rhs = TexMobject("=%d"%int(result)) - rhs.add_background_rectangle() - rhs.set_color(YELLOW) - rhs.next_to(s_expression, RIGHT, align_using_submobjects = True) - point_label = VGroup(s_expression, rhs) - - self.play( - self.example_point_dot.move_to, point, - Transform(self.example_point_label, point_label) - ) - self.wait(2) - - def take_derivative_symbolically(self): - equation = self.equation - derivative = TexMobject( - "dS =", "2", "x", "\\,dx", "+", "2", "y", "\\,dy", - ) - derivative[2].set_color(GREEN) - derivative[6].set_color(RED) - derivative.next_to(equation, DOWN, buff = MED_LARGE_BUFF) - derivative.add_background_rectangle() - derivative.to_edge(LEFT) - - self.play(*[ - FadeIn(derivative[0]) - ]+[ - ReplacementTransform( - self.s_expression[1][0].copy(), - derivative[1][0], - ) - ]+[ - Write(derivative[1][j]) - for j in (3, 7) - ]) - self.play(*[ - ReplacementTransform( - equation[1][i].copy(), derivative[1][j], - path_arc = np.pi/2, - run_time = 2, - rate_func = squish_rate_func(smooth, (j-1)/12., (j+6)/12.) - ) - for i, j in enumerate([2, 1, 4, 6, 5]) - ]) - self.wait(2) - - self.derivative = derivative - - def show_dx_dy_step(self): - dot = self.example_point_dot - s_label = self.example_point_label - rhs = s_label[-1] - s_label.remove(rhs) - - point = dot.get_center() - vect = 2*LEFT + DOWN - new_point = point + vect*0.6/self.zoom_factor - interim_point = new_point[0]*RIGHT + point[1]*UP - - dx_line = Line(point, interim_point, color = GREEN) - dy_line = Line(interim_point, new_point, color = RED) - for line, tex, vect in (dx_line, "dx", UP), (dy_line, "dy", LEFT): - label = TexMobject(tex) - label.set_color(line.get_color()) - label.next_to(line, vect, buff = SMALL_BUFF) - label.add_background_rectangle() - label.scale( - 1./self.zoom_factor, - about_point = line.get_center() - ) - line.label = label - - self.activate_zooming() - lil_rect = self.little_rectangle - lil_rect.move_to(dot) - lil_rect.shift(0.05*lil_rect.get_width()*LEFT) - lil_rect.shift(0.2*lil_rect.get_height()*DOWN) - lil_rect.save_state() - lil_rect.set_height(FRAME_Y_RADIUS - MED_LARGE_BUFF) - lil_rect.move_to(s_label, UP) - lil_rect.shift(MED_SMALL_BUFF*UP) - self.wait() - self.play( - FadeOut(rhs), - dot.scale, 1./self.zoom_factor, point, - s_label.scale, 1./self.zoom_factor, point, - lil_rect.restore, - run_time = 2 - ) - self.wait() - for line in dx_line, dy_line: - self.play(ShowCreation(line)) - self.play(Write(line.label, run_time = 1)) - self.wait() - - new_dot = Dot(color = dot.get_color()) - new_s_label = self.get_s_expression( - "%d + dx"%int(self.example_point[0]), - "%d + dy"%int(self.example_point[1]), - ) - new_dot.set_height(dot.get_height()) - new_dot.move_to(new_point) - new_s_label.set_height(s_label.get_height()) - new_s_label.scale(0.8) - new_s_label.next_to( - new_dot, DOWN, - buff = SMALL_BUFF/self.zoom_factor, - aligned_edge = LEFT - ) - new_s_label.shift(MED_LARGE_BUFF*LEFT/self.zoom_factor) - new_s_label.set_color(self.example_color) - VGroup(*new_s_label[1][1][3:5]).set_color(GREEN) - VGroup(*new_s_label[1][1][-3:-1]).set_color(RED) - - self.play(ShowCreation(new_dot)) - self.play(Write(new_s_label)) - self.wait() - - ds = self.derivative[1][0] - self.play(FocusOn(ds)) - self.play(Indicate(ds)) - self.wait() - - self.tiny_step_group = VGroup( - dx_line, dx_line.label, - dy_line, dy_line.label, - s_label, new_s_label, new_dot - ) - - def plug_in_example_values(self): - deriv_example = TexMobject( - "dS =", "2", "(3)", "\\,(-0.02)", "+", "2", "(4)", "\\,(-0.01)", - ) - - deriv_example[2].set_color(GREEN) - deriv_example[6].set_color(RED) - deriv_example.add_background_rectangle() - deriv_example.scale(0.8) - deriv_example.next_to(ORIGIN, UP, buff = SMALL_BUFF) - deriv_example.to_edge(LEFT, buff = MED_SMALL_BUFF) - - def add_example_parts(*indices): - self.play(*[ - ReplacementTransform( - self.derivative[1][i].copy(), - deriv_example[1][i], - run_time = 2 - ) - for i in indices - ]) - - self.play(FadeIn(deriv_example[0])) - add_example_parts(0) - self.wait() - add_example_parts(1, 2, 4, 5, 6) - self.wait(2) - add_example_parts(3) - self.wait() - add_example_parts(7) - self.wait() - - #React - randy = Randolph() - randy.next_to(ORIGIN, LEFT) - randy.to_edge(DOWN) - - self.play(FadeIn(randy)) - self.play( - randy.change_mode, "pondering", - randy.look_at, deriv_example.get_left() - ) - self.play(Blink(randy)) - self.play(randy.look_at, deriv_example.get_right()) - self.wait() - self.play( - Indicate(self.equation), - randy.look_at, self.equation - ) - self.play(Blink(randy)) - self.play( - randy.change_mode, "thinking", - randy.look_at, self.big_rectangle - ) - self.wait(2) - self.play(PiCreatureSays( - randy, "Approximately", - target_mode = "sassy" - )) - self.wait() - self.play(RemovePiCreatureBubble(randy)) - self.play(randy.look_at, deriv_example) - self.play(FadeOut(deriv_example)) - self.wait() - - self.randy = randy - - def ask_about_equalling_zero(self): - dot = self.example_point_dot - randy = self.randy - - equals_zero = TexMobject("=0") - equals_zero.set_color(YELLOW) - equals_zero.add_background_rectangle() - equals_zero.next_to(self.derivative, RIGHT) - - self.play( - Write(equals_zero), - randy.change_mode, "confused", - randy.look_at, equals_zero - ) - self.wait() - self.play(Blink(randy)) - self.play( - randy.change_mode, "plain", - randy.look_at, self.big_rectangle, - ) - self.play( - FadeOut(self.tiny_step_group), - self.little_rectangle.move_to, dot, - ) - self.wait() - - self.equals_zero = equals_zero - - def show_tangent_step(self): - dot = self.example_point_dot - randy = self.randy - - point = dot.get_center() - step_vect = rotate_vector(point, np.pi/2)/get_norm(point) - new_point = point + step_vect/self.zoom_factor - interim_point = point[0]*RIGHT + new_point[1]*UP - new_dot = dot.copy().move_to(new_point) - - step_line = Line(point, new_point, color = WHITE) - dy_line = Line(point, interim_point, color = RED) - dx_line = Line(interim_point, new_point, color = GREEN) - - s_label = TexMobject("S = 25") - s_label.set_color(self.example_color) - s_label.next_to( - point, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = RIGHT - ) - s_label.scale(1./self.zoom_factor, about_point = point) - arrow1, arrow2 = [ - Arrow( - s_label.get_top(), mob, - preserve_tip_size_when_scaling = False, - color = self.example_color, - buff = SMALL_BUFF/self.zoom_factor, - tip_length = 0.15/self.zoom_factor - ) - for mob in (dot, new_dot) - ] - - for line, tex, vect in (dy_line, "dy", RIGHT), (dx_line, "dx", UP): - label = TexMobject(tex) - label.set_color(line.get_color()) - label.next_to(line, vect) - label.scale( - 1./self.zoom_factor, - about_point = line.get_center() - ) - line.label = label - - self.play(ShowCreation(line)) - self.play(Write(label)) - self.play(ShowCreation(new_dot)) - self.play( - randy.change_mode, "pondering", - randy.look_at, self.big_rectangle - ) - self.play(Blink(randy)) - self.wait() - - self.play(Write(s_label)) - self.play(ShowCreation(arrow1)) - self.wait() - self.play(ReplacementTransform(arrow1.copy(), arrow2)) - self.wait(2) - - def point_out_equalling_zero(self): - derivative = self.derivative - equals_zero = self.equals_zero - randy = self.randy - - self.play( - FocusOn(equals_zero), - self.randy.look_at, equals_zero - ) - self.play(Indicate(equals_zero, color = RED)) - self.play(Blink(randy)) - self.play(randy.change_mode, "happy") - self.play(randy.look_at, self.big_rectangle) - self.wait(2) - - def show_tangent_line(self): - randy = self.randy - point = self.example_point_dot.get_center() - line = Line(ORIGIN, 5*RIGHT) - line.rotate(angle_of_vector(point)+np.pi/2) - line.move_to(point) - - self.play(PiCreatureSays( - randy, "Approximately...", - )) - self.play(Blink(randy)) - self.play(RemovePiCreatureBubble(randy)) - self.play( - GrowFromCenter(line), - randy.look_at, line - ) - self.wait(2) - self.play( - self.little_rectangle.scale_in_place, self.zoom_factor/2, - run_time = 4, - rate_func = there_and_back - ) - self.wait(2) - - ############ - - def get_s_expression(self, x, y): - result = TexMobject("S", "(%s, %s)"%(str(x), str(y))) - result.add_background_rectangle() - return result - -class TryOtherExamples(TeacherStudentsScene): - def construct(self): - formula = TexMobject("\\sin(x)y^2 = x") - formula.next_to( - self.get_teacher().get_corner(UP+LEFT), UP, - buff = MED_LARGE_BUFF, - aligned_edge = RIGHT - ) - - - self.teacher_says( - """Nothing special - about $x^2 + y^2 = 25$""" - ) - self.wait() - self.play(RemovePiCreatureBubble( - self.get_teacher(), - target_mode = "raise_right_hand" - )) - self.play(Write(formula, run_time = 1)) - self.change_student_modes(*["pondering"]*3) - self.wait(2) - self.play(formula.to_corner, UP+LEFT) - self.wait() - -class AlternateExample(ZoomedScene): - CONFIG = { - "example_color" : MAROON_B, - "zoom_factor" : 10, - "zoomed_canvas_corner" : DOWN+RIGHT, - "zoomed_canvas_frame_shape" : (3, 4), - } - def construct(self): - self.add_plane() - self.draw_graph() - self.emphasize_meaning_of_points() - self.zoom_in() - self.show_tiny_step() - self.ask_about_derivatives() - self.differentiate_lhs() - self.differentiate_rhs() - self.put_step_on_curve() - self.emphasize_equality() - self.manipulate_to_find_dy_dx() - - def add_plane(self): - formula = TexMobject("\\sin(x)y^2 = x") - formula.to_corner(UP+LEFT) - formula.add_background_rectangle() - - plane = NumberPlane( - space_unit_to_x_unit = 0.75, - x_radius = FRAME_WIDTH, - ) - plane.fade() - plane.add_coordinates() - - self.add(formula) - self.play(Write(plane, run_time = 2), Animation(formula)) - self.wait() - - self.plane = plane - self.formula = formula - self.lhs = VGroup(*formula[1][:8]) - self.rhs = VGroup(formula[1][-1]) - - def draw_graph(self): - graphs = VGroup(*[ - FunctionGraph( - lambda x : u*np.sqrt(x/np.sin(x)), - num_steps = 200, - x_min = x_min+0.1, - x_max = x_max-0.1, - ) - for u in [-1, 1] - for x_min, x_max in [ - (-4*np.pi, -2*np.pi), - (-np.pi, np.pi), - (2*np.pi, 4*np.pi), - ] - ]) - graphs.stretch(self.plane.space_unit_to_x_unit, dim = 0) - - self.play( - ShowCreation( - graphs, - run_time = 3, - lag_ratio = 0 - ), - Animation(self.formula) - ) - self.wait() - - self.graphs = graphs - - def emphasize_meaning_of_points(self): - graph = self.graphs[4] - dot = Dot(color = self.example_color) - label = TexMobject("(x, y)") - label.add_background_rectangle() - label.set_color(self.example_color) - - def update_dot(dot, alpha): - prop = interpolate(0.9, 0.1, alpha) - point = graph.point_from_proportion(prop) - dot.move_to(point) - return dot - - def update_label(label): - point = dot.get_center() - vect = np.array(point)/get_norm(point) - vect[0] *= 2 - vect[1] *= -1 - label.move_to( - point + vect*0.4*label.get_width() - ) - - update_dot(dot, 0) - update_label(label) - - self.play( - ShowCreation(dot), - Write(label), - run_time = 1 - ) - self.play( - UpdateFromAlphaFunc(dot, update_dot), - UpdateFromFunc(label, update_label), - run_time = 3, - ) - self.wait() - self.play(*[ - ApplyMethod( - label[1][i].copy().move_to, self.formula[1][j], - run_time = 3, - rate_func = squish_rate_func(smooth, count/6., count/6.+2./3) - ) - for count, (i, j) in enumerate([(1, 4), (1, 9), (3, 6)]) - ]) - movers = self.get_mobjects_from_last_animation() - self.wait() - self.play( - UpdateFromAlphaFunc(dot, update_dot), - UpdateFromFunc(label, update_label), - run_time = 3, - rate_func = lambda t : 1-smooth(t) - ) - self.wait() - self.play(*[ - ApplyMethod(mover.set_fill, None, 0, remover = True) - for mover in movers - ]) - - self.dot = dot - self.label = label - - def zoom_in(self): - dot = self.dot - label = self.label - - self.activate_zooming() - self.little_rectangle.scale(self.zoom_factor) - self.little_rectangle.move_to(dot) - self.wait() - for mob in VGroup(dot, label), self.little_rectangle: - self.play( - ApplyMethod( - mob.scale, 1./self.zoom_factor, - method_kwargs = {"about_point" : dot.get_center()}, - run_time = 1, - ) - ) - self.wait() - - def show_tiny_step(self): - dot = self.dot - label = self.label - point = dot.get_center() - step_vect = 1.2*(UP+LEFT)/float(self.zoom_factor) - new_point = point + step_vect - interim_point = new_point[0]*RIGHT + point[1]*UP - - dx_line = Line(point, interim_point, color = GREEN) - dy_line = Line(interim_point, new_point, color = RED) - for line, tex, vect in (dx_line, "dx", DOWN), (dy_line, "dy", LEFT): - label = TexMobject(tex) - label.next_to(line, vect, buff = SMALL_BUFF) - label.set_color(line.get_color()) - label.scale(1./self.zoom_factor, about_point = line.get_center()) - label.add_background_rectangle() - line.label = label - - arrow = Arrow( - point, new_point, buff = 0, - tip_length = 0.15/self.zoom_factor, - color = WHITE - ) - - self.play(ShowCreation(arrow)) - for line in dx_line, dy_line: - self.play(ShowCreation(line), Animation(arrow)) - self.play(Write(line.label, run_time = 1)) - self.wait() - - self.step_group = VGroup( - arrow, dx_line, dx_line.label, dy_line, dy_line.label - ) - - def ask_about_derivatives(self): - formula = self.formula - lhs, rhs = self.lhs, self.rhs - - word = TextMobject("Change?") - word.add_background_rectangle() - word.next_to( - Line(lhs.get_center(), rhs.get_center()), - DOWN, buff = 1.5*LARGE_BUFF - ) - - arrows = VGroup(*[ - Arrow(word, part) - for part in (lhs, rhs) - ]) - - self.play(FocusOn(formula)) - self.play( - Write(word), - ShowCreation(arrows) - ) - self.wait() - self.play(*list(map(FadeOut, [word, arrows]))) - - def differentiate_lhs(self): - formula = self.formula - lhs = self.lhs - brace = Brace(lhs, DOWN, buff = SMALL_BUFF) - sine_x = VGroup(*lhs[:6]) - sine_rect = BackgroundRectangle(sine_x) - y_squared = VGroup(*lhs[6:]) - - mnemonic = TextMobject( - "``", - "Left", " d-Right", " + ", - "Right", " d-Left" - "''", - arg_separator = "" - ) - mnemonic.set_color_by_tex("d-Right", RED) - mnemonic.set_color_by_tex("d-Left", GREEN) - mnemonic.add_background_rectangle() - mnemonic.set_width(FRAME_X_RADIUS-2*MED_LARGE_BUFF) - mnemonic.next_to(ORIGIN, UP) - mnemonic.to_edge(LEFT) - - derivative = TexMobject( - "\\sin(x)", "(2y\\,dy)", "+", - "y^2", "(\\cos(x)\\,dx)", - ) - derivative.set_color_by_tex("dx", GREEN) - derivative.set_color_by_tex("dy", RED) - derivative.set_width(FRAME_X_RADIUS - 2*MED_LARGE_BUFF) - derivative.next_to( - brace, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - derivative_rects = [ - BackgroundRectangle(VGroup(*subset)) - for subset in (derivative[:2], derivative[2:]) - ] - derivative_rects[1].stretch(1.05, dim = 0) - - self.play(GrowFromCenter(brace)) - self.play(Write(mnemonic)) - self.wait() - pairs = [ - (sine_rect, derivative_rects[0]), - (sine_x, derivative[0]), - (y_squared, derivative[1]), - (sine_rect, derivative_rects[1]), - (y_squared, derivative[2]), - (y_squared, derivative[3]), - (sine_x, derivative[4]), - ] - for pairs_subset in pairs[:3], pairs[3:]: - self.play(*[ - ReplacementTransform(m1.copy(), m2, path_arc = np.pi/2) - for m1, m2 in pairs_subset - ]) - self.wait() - self.play(Indicate(pairs_subset[-1][1])) - self.wait() - self.wait() - self.play(FadeOut(mnemonic)) - - self.lhs_derivative = VGroup(*derivative_rects+[derivative]) - self.lhs_brace = brace - - def differentiate_rhs(self): - lhs_derivative = self.lhs_derivative - lhs_brace = self.lhs_brace - rhs = self.rhs - - equals, dx = equals_dx = TexMobject("=", "dx") - equals_dx.scale(0.9) - equals_dx.set_color_by_tex("dx", GREEN) - equals_dx.add_background_rectangle() - equals_dx.next_to(lhs_derivative, RIGHT, buff = SMALL_BUFF) - - circle = Circle(color = GREEN) - circle.replace(self.rhs) - circle.scale_in_place(1.7) - - arrow = Arrow(rhs.get_right(), dx.get_top()) - arrow.set_color(GREEN) - - self.play(ReplacementTransform(lhs_brace, circle)) - self.play(ShowCreation(arrow)) - self.play(Write(equals_dx)) - self.wait() - self.play(*list(map(FadeOut, [circle, arrow]))) - - self.equals_dx = equals_dx - - def put_step_on_curve(self): - dot = self.dot - point = dot.get_center() - graph = self.graphs[4] - arrow, dx_line, dx_line.label, dy_line, dy_line.label = self.step_group - - #Find graph point at right x_val - arrow_end = arrow.get_end() - graph_points = [ - graph.point_from_proportion(alpha) - for alpha in np.linspace(0, 1, 1000) - ] - distances = np.apply_along_axis( - lambda p : np.abs(p[0] - arrow_end[0]), - 1, graph_points - ) - index = np.argmin(distances) - new_end_point = graph_points[index] - - lil_rect = self.little_rectangle - self.play( - arrow.put_start_and_end_on, point, new_end_point, - dy_line.put_start_and_end_on, - dy_line.get_start(), new_end_point, - MaintainPositionRelativeTo(dy_line.label, dy_line), - lil_rect.shift, lil_rect.get_height()*DOWN/3, - run_time = 2 - ) - self.wait(2) - - def emphasize_equality(self): - self.play(FocusOn(self.lhs)) - self.wait() - for mob in self.lhs, self.rhs: - self.play(Indicate(mob)) - self.wait() - - def manipulate_to_find_dy_dx(self): - full_derivative = VGroup( - self.lhs_derivative, self.equals_dx - ) - brace = Brace(full_derivative, DOWN, buff = SMALL_BUFF) - words = brace.get_text( - "Commonly, solve for", "$dy/dx$", - buff = SMALL_BUFF - ) - VGroup(*words[1][:2]).set_color(RED) - VGroup(*words[1][3:]).set_color(GREEN) - words.add_background_rectangle() - - self.play(GrowFromCenter(brace)) - self.play(Write(words)) - self.wait() - - randy = Randolph() - randy.to_corner(DOWN+LEFT) - randy.look_at(full_derivative) - - self.play(FadeIn(randy)) - self.play(randy.change_mode, "confused") - self.play(Blink(randy)) - self.wait(2) - self.play(randy.change_mode, "pondering") - self.play(Blink(randy)) - self.wait() - -class AskAboutNaturalLog(TeacherStudentsScene): - def construct(self): - exp_deriv = TexMobject("\\frac{d(e^x)}{dx} = e^x") - for i in 2, 3, 9, 10: - exp_deriv[i].set_color(BLUE) - log_deriv = TexMobject("\\frac{d(\\ln(x))}{dx} = ???") - VGroup(*log_deriv[2:2+5]).set_color(GREEN) - - for deriv in exp_deriv, log_deriv: - deriv.next_to(self.get_teacher().get_corner(UP+LEFT), UP) - - self.teacher_says( - """We can find - new derivatives""", - target_mode = "hooray" - ) - self.change_student_modes(*["happy"]*3) - self.play(RemovePiCreatureBubble( - self.get_teacher(), - target_mode = "raise_right_hand" - )) - self.play(Write(exp_deriv)) - self.wait() - self.play( - Write(log_deriv), - exp_deriv.next_to, log_deriv, UP, LARGE_BUFF, - *[ - ApplyMethod(pi.change_mode, "confused") - for pi in self.get_pi_creatures() - ] - ) - self.wait(3) - -class DerivativeOfNaturalLog(ZoomedScene): - CONFIG = { - "zoom_factor" : 10, - "zoomed_canvas_corner" : DOWN+RIGHT, - "example_color" : MAROON_B, - } - def construct(self): - should_skip_animations = self.skip_animations - self.skip_animations = True - - self.add_plane() - self.draw_graph() - self.describe_as_implicit_curve() - self.slope_gives_derivative() - self.rearrange_equation() - self.take_derivative() - self.show_tiny_nudge() - self.note_derivatives() - self.solve_for_dy_dx() - self.skip_animations = should_skip_animations - self.show_slope_above_x() - - def add_plane(self): - plane = NumberPlane() - plane.fade() - plane.add_coordinates() - self.add(plane) - self.plane = plane - - def draw_graph(self): - graph = FunctionGraph( - np.log, - x_min = 0.01, - x_max = FRAME_X_RADIUS, - num_steps = 100 - ) - formula = TexMobject("y = \\ln(x)") - formula.next_to(ORIGIN, LEFT, buff = MED_LARGE_BUFF) - formula.to_edge(UP) - formula.add_background_rectangle() - - self.add(formula) - self.play(ShowCreation(graph)) - self.wait() - - self.formula = formula - self.graph = graph - - def describe_as_implicit_curve(self): - formula = self.formula #y = ln(x) - graph = self.graph - - dot = Dot(color = self.example_color) - label = TexMobject("(x, y)") - label.add_background_rectangle() - label.set_color(self.example_color) - - def update_dot(dot, alpha): - prop = interpolate(0.1, 0.7, alpha) - point = graph.point_from_proportion(prop) - dot.move_to(point) - return dot - - def update_label(label): - point = dot.get_center() - vect = point - FRAME_Y_RADIUS*(DOWN+RIGHT) - vect = vect/get_norm(vect) - label.move_to( - point + vect*0.5*label.get_width() - ) - - update_dot(dot, 0) - update_label(label) - - self.play(*list(map(FadeIn, [dot, label]))) - self.play( - UpdateFromAlphaFunc(dot, update_dot), - UpdateFromFunc(label, update_label), - run_time = 3, - ) - self.wait() - xy_start = VGroup(label[1][1], label[1][3]).copy() - xy_end = VGroup(formula[1][5], formula[1][0]).copy() - xy_end.set_color(self.example_color) - self.play(Transform( - xy_start, xy_end, - run_time = 2, - )) - self.wait() - self.play( - UpdateFromAlphaFunc(dot, update_dot), - UpdateFromFunc(label, update_label), - run_time = 3, - rate_func = lambda t : 1-0.6*smooth(t), - ) - self.play(*list(map(FadeOut, [xy_start, label]))) - - self.dot = dot - - def slope_gives_derivative(self): - dot = self.dot - point = dot.get_center() - line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - slope = 1./point[0] - line.rotate(np.arctan(slope)) - line.move_to(point) - - new_point = line.point_from_proportion(0.6) - interim_point = point[0]*RIGHT + new_point[1]*UP - dy_line = Line(point, interim_point, color = RED) - dx_line = Line(interim_point, new_point, color = GREEN) - - equation = TexMobject( - "\\text{Slope} = ", - "\\frac{dy}{dx} = ", - "\\frac{d(\\ln(x))}{dx}", - ) - VGroup(*equation[1][:2]).set_color(RED) - VGroup(*equation[2][:8]).set_color(RED) - VGroup(*equation[1][3:5]).set_color(GREEN) - VGroup(*equation[2][-2:]).set_color(GREEN) - for part in equation: - rect = BackgroundRectangle(part) - rect.stretch_in_place(1.2, 0) - part.add_to_back(rect) - equation.scale(0.8) - equation.next_to(ORIGIN, RIGHT) - equation.to_edge(UP, buff = MED_SMALL_BUFF) - - - self.play( - GrowFromCenter(line), - Animation(dot) - ) - self.play(ShowCreation(VGroup(dy_line, dx_line))) - for part in equation: - self.play(Write(part, run_time = 2)) - self.wait() - - self.dx_line, self.dy_line = dx_line, dy_line - self.slope_equation = equation - self.tangent_line = line - - def rearrange_equation(self): - formula = self.formula - y, eq = formula[1][:2] - ln = VGroup(*formula[1][2:4]) - x = formula[1][5] - - new_formula = TexMobject("e", "^y", "=", "x") - e, new_y, new_eq, new_x = new_formula - new_formula.next_to( - formula, DOWN, - buff = MED_LARGE_BUFF, - ) - rect = BackgroundRectangle(new_formula) - - y = new_y.copy().replace(y) - self.play(Indicate(formula, scale_factor = 1)) - self.play(ReplacementTransform(ln.copy(), e)) - self.play(ReplacementTransform(y, new_y)) - self.play(ReplacementTransform(eq.copy(), new_eq)) - self.play( - FadeIn(rect), - Animation(VGroup(e, new_y, new_eq)), - ReplacementTransform(x.copy(), new_x) - ) - self.wait(2) - for mob in e, new_y, new_x: - self.play(Indicate(mob)) - self.wait() - - self.new_formula = new_formula - - def take_derivative(self): - new_formula = self.new_formula - e, y, eq, x = new_formula - derivative = TexMobject("e", "^y", "\\,dy", "=", "dx") - new_e, new_y, dy, new_eq, dx = derivative - derivative.next_to(new_formula, DOWN, MED_LARGE_BUFF) - derivative.add_background_rectangle() - dx.set_color(GREEN) - dy.set_color(RED) - - pairs = [ - (VGroup(e, y), VGroup(new_e, new_y)), - (new_y, dy), - (eq, new_eq), - (x, dx) - ] - for start, target in pairs: - self.play( - ReplacementTransform(start.copy(), target) - ) - self.play(FadeIn(derivative[0]), Animation(derivative[1])) - self.remove(derivative, pairs[0][1]) - self.add(derivative) - self.wait() - - self.derivative = derivative - - def show_tiny_nudge(self): - dot = self.dot - point = dot.get_center() - dx_line = self.dx_line - dy_line = self.dy_line - group = VGroup(dot, dx_line, dy_line) - self.play(group.scale, 1./self.zoom_factor, point) - for line, tex, vect in (dx_line, "dx", UP), (dy_line, "dy", LEFT): - label = TexMobject(tex) - label.add_background_rectangle() - label.next_to(line, vect, buff = SMALL_BUFF) - label.set_color(line.get_color()) - label.scale( - 1./self.zoom_factor, - about_point = line.get_center() - ) - line.label = label - - self.activate_zooming() - lil_rect = self.little_rectangle - lil_rect.move_to(group) - lil_rect.scale_in_place(self.zoom_factor) - self.play(lil_rect.scale_in_place, 1./self.zoom_factor) - self.play(Write(dx_line.label)) - self.play(Write(dy_line.label)) - self.wait() - - def note_derivatives(self): - e, y, dy, eq, dx = self.derivative[1] - - self.play(FocusOn(e)) - self.play(Indicate(VGroup(e, y, dy))) - self.wait() - self.play(Indicate(dx)) - self.wait() - - def solve_for_dy_dx(self): - e, y, dy, eq, dx = self.derivative[1] - ey_group = VGroup(e, y) - original_rect = self.derivative[0] - - rearranged = TexMobject( - "{dy \\over ", " dx}", "=", "{1 \\over ", "e", "^y}" - ) - new_dy, new_dx, new_eq, one_over, new_e, new_y = rearranged - new_ey_group = VGroup(new_e, new_y) - new_dx.set_color(GREEN) - new_dy.set_color(RED) - rearranged.shift(eq.get_center() - new_eq.get_center()) - rearranged.shift(MED_SMALL_BUFF*DOWN) - new_rect = BackgroundRectangle(rearranged) - - self.play(*[ - ReplacementTransform( - m1, m2, - run_time = 2, - path_arc = -np.pi/2, - ) - for m1, m2 in [ - (original_rect, new_rect), - (dx, new_dx), - (dy, new_dy), - (eq, new_eq), - (ey_group, new_ey_group), - (e.copy(), one_over) - ] - ]) - self.wait() - - #Change denominator - e, y, eq, x = self.new_formula - ey_group = VGroup(e, y).copy() - ey_group.set_color(YELLOW) - x_copy = x.copy() - - self.play(new_ey_group.set_color, YELLOW) - self.play(Transform(new_ey_group, ey_group)) - self.play( - new_ey_group.set_color, WHITE, - x_copy.set_color, YELLOW - ) - self.play(x_copy.next_to, one_over, DOWN, MED_SMALL_BUFF) - self.wait(2) - - equals_one_over_x = VGroup( - new_eq, one_over, x_copy - ).copy() - rect = BackgroundRectangle(equals_one_over_x) - rect.stretch_in_place(1.1, dim = 0) - equals_one_over_x.add_to_back(rect) - - self.play( - equals_one_over_x.next_to, - self.slope_equation, RIGHT, 0, - run_time = 2 - ) - self.wait() - - def show_slope_above_x(self): - line = self.tangent_line - start_x = line.get_center()[0] - target_x = 0.2 - graph = FunctionGraph( - lambda x : 1./x, - x_min = 0.1, - x_max = FRAME_X_RADIUS, - num_steps = 100, - color = PINK, - ) - - def update_line(line, alpha): - x = interpolate(start_x, target_x, alpha) - point = x*RIGHT + np.log(x)*UP - angle = np.arctan(1./x) - line.rotate(angle - line.get_angle()) - line.move_to(point) - - self.play(UpdateFromAlphaFunc( - line, update_line, - rate_func = there_and_back, - run_time = 6 - )) - self.wait() - self.play(ShowCreation(graph, run_time = 3)) - self.wait() - self.play(UpdateFromAlphaFunc( - line, update_line, - rate_func = there_and_back, - run_time = 6 - )) - self.wait() - -class FinalWords(TeacherStudentsScene): - def construct(self): - words = TextMobject( - "This is a peek into \\\\", - "Multivariable", "calculus" - ) - mvc = VGroup(*words[1:]) - words.set_color_by_tex("Multivariable", YELLOW) - formula = TexMobject("f(x, y) = \\sin(x)y^2") - formula.next_to(self.get_teacher().get_corner(UP+LEFT), UP) - - self.teacher_says(words) - self.change_student_modes("erm", "hooray", "sassy") - self.play( - FadeOut(self.teacher.bubble), - FadeOut(VGroup(*words[:1])), - mvc.to_corner, UP+LEFT, - self.teacher.change_mode, "raise_right_hand", - self.teacher.look_at, formula, - Write(formula, run_time = 2), - ) - self.change_student_modes("pondering", "confused", "thinking") - self.wait(3) - - ##Show series - series = VideoSeries() - series.to_edge(UP) - video = series[5] - lim = TexMobject("\\lim_{h \\to 0} \\frac{f(x+h)-f(x)}{h}") - self.play( - FadeOut(mvc), - FadeOut(formula), - self.teacher.change_mode, "plain", - FadeIn( - series, run_time = 2, - lag_ratio = 0.5, - ), - ) - self.play( - video.set_color, YELLOW, - video.shift, video.get_height()*DOWN/2 - ) - lim.next_to(video, DOWN) - self.play( - Write(lim), - *it.chain(*[ - [pi.change_mode, "pondering", pi.look_at, lim] - for pi in self.get_pi_creatures() - ]) - ) - self.wait(3) - -class Chapter6PatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "Meshal Alshammari", - "CrypticSwarm ", - "Nathan Pellegrin", - "Karan Bhargava", - "Justin Helps", - "Ankit Agarwal", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Justin Helps", - "Markus Persson", - "Dan Buchoff", - "Derek Dai", - "Joseph John Cox", - "Luc Ritchie", - "Daan Smedinga", - "Jonathan Eppele", - "Nils Schneider", - "Albert Nguyen", - "Mustafa Mahdi", - "Mathew Bramson", - "Guido Gambardella", - "Jerry Ling", - "Mark Govea", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Kirk Werklund", - "Ripta Pasay", - "Felipe Diniz", - ] - } - -class Thumbnail(AlternateExample): - def construct(self): - title = VGroup(*list(map(TextMobject, [ - "Implicit", "Differentiation" - ]))) - title.arrange(DOWN) - title.scale(3) - title.next_to(ORIGIN, UP) - - for word in title: - word.add_background_rectangle() - - self.add_plane() - self.draw_graph() - self.graphs.set_stroke(width = 8) - self.remove(self.formula) - self.add(title) diff --git a/from_3b1b/old/eoc/chapter7.py b/from_3b1b/old/eoc/chapter7.py deleted file mode 100644 index 4f5c17b8..00000000 --- a/from_3b1b/old/eoc/chapter7.py +++ /dev/null @@ -1,2943 +0,0 @@ -# -*- coding: utf-8 -*- -from manimlib.imports import * - -class Chapter7OpeningQuote(OpeningQuote): - CONFIG = { - "quote" : [ - " Calculus required ", - "continuity", - ", and ", - "continuity ", - "was supposed to require the ", - "infinitely little", - "; but nobody could discover what the ", - "infinitely little", - " might be. ", - ], - "quote_arg_separator" : "", - "highlighted_quote_terms" : { - "continuity" : BLUE, - "infinitely" : GREEN, - }, - "author" : "Bertrand Russell", - } - -class ThisVideo(TeacherStudentsScene): - def construct(self): - series = VideoSeries() - series.to_edge(UP) - deriv_videos = VGroup(*series[1:6]) - this_video = series[6] - integral_videos = VGroup(*series[7:9]) - video_groups = [deriv_videos, this_video, integral_videos] - - braces = list(map(Brace, video_groups)) - deriv_brace, this_brace, integral_brace = braces - - tex_mobs = [ - TexMobject(*args) - for args in [ - ("{df ", " \\over \\, ", " dx}"), - ("\\lim_{h \\to 0}",), - ("\\int ", "f(x)", "\\,dx"), - ] - ] - deriv_tex, this_tex, integral_tex = tex_mobs - for tex_mob, brace in zip(tex_mobs, braces): - tex_mob.set_color_by_tex("f", GREEN) - tex_mob.set_color_by_tex("dx", YELLOW) - tex_mob.next_to(brace, DOWN) - integral_tex.shift(LARGE_BUFF*RIGHT) - - lim_to_deriv_arrow = Arrow(this_tex, deriv_tex, color = WHITE) - - self.add(series) - for index in 0, 2: - videos = video_groups[index] - brace = braces[index] - tex_mob = tex_mobs[index] - self.play(ApplyWave( - videos, - direction = DOWN, - )) - self.play( - GrowFromCenter(brace), - Write(tex_mob, run_time = 2) - ) - self.play( - this_video.set_color, YELLOW, - GrowFromCenter(this_brace), - self.get_teacher().change_mode, "raise_right_hand", - self.get_teacher().look_at, this_video - ) - self.play(Write(this_tex)) - self.wait(2) - self.play(self.get_teacher().change_mode, "sassy") - self.wait(2) - -class LimitJustMeansApproach(PiCreatureScene): - CONFIG = { - "dx_color" : GREEN, - "max_num_zeros" : 7, - } - def construct(self): - limit_expression = self.get_limit_expression() - limit_expression.shift(2*LEFT) - limit_expression.to_edge(UP) - - evaluated_expressions = self.get_evaluated_expressions() - evaluated_expressions.next_to(limit_expression, DOWN, buff = LARGE_BUFF) - brace = Brace(evaluated_expressions[0][-1], DOWN) - question = TextMobject("What does this ``approach''?") - question.next_to(brace, DOWN) - - point = VectorizedPoint(limit_expression.get_right()) - expression = VGroup( - limit_expression[1].copy(), - point, point.copy() - ) - self.add(limit_expression) - self.change_mode("raise_right_hand") - for next_expression in evaluated_expressions: - next_expression.move_to(evaluated_expressions[0], RIGHT) - self.play( - Transform( - expression, next_expression, - lag_ratio = 0.5, - ), - self.pi_creature.look_at, next_expression[-1] - ) - if brace not in self.get_mobjects(): - self.play( - GrowFromCenter(brace), - Write(question) - ) - self.wait(0.5) - self.wait(2) - - def create_pi_creature(self): - self.pi_creature = Mortimer().flip() - self.pi_creature.to_corner(DOWN+LEFT) - return self.pi_creature - - def get_limit_expression(self): - lim = TexMobject("\\lim_", "{dx", " \\to 0}") - lim.set_color_by_tex("dx", self.dx_color) - ratio = self.get_expression("dx") - ratio.next_to(lim, RIGHT) - limit_expression = VGroup(lim, ratio) - return limit_expression - - def get_evaluated_expressions(self): - result = VGroup() - for num_zeros in range(1, self.max_num_zeros+1): - dx_str = "0." + "0"*num_zeros + "1" - expression = self.get_expression(dx_str) - dx = float(dx_str) - ratio = ((2+dx)**3-2**3)/dx - ratio_mob = TexMobject("%.6f\\dots"%ratio) - group = VGroup(expression, TexMobject("="), ratio_mob) - group.arrange(RIGHT) - result.add(group) - return result - - def get_expression(self, dx): - result = TexMobject( - "{(2 + ", str(dx), ")^3 - 2^3 \\over", str(dx) - ) - result.set_color_by_tex(dx, self.dx_color) - return result - -class Goals(Scene): - def construct(self): - goals = [ - TextMobject("Goal %d:"%d, s) - for d, s in [ - (1, "Formal definition of derivatives"), - (2, "$(\\epsilon, \\delta)$ definition of a limit"), - (3, "L'Hôpital's rule"), - ] - ] - for goal in goals: - goal.scale(1.3) - goal.shift(3*DOWN).to_edge(LEFT) - - curr_goal = goals[0] - self.play(FadeIn(curr_goal)) - self.wait(2) - for goal in goals[1:]: - self.play(Transform(curr_goal, goal)) - self.wait(2) - -class RefreshOnDerivativeDefinition(GraphScene): - CONFIG = { - "start_x" : 2, - "start_dx" : 0.7, - "df_color" : YELLOW, - "dx_color" : GREEN, - "secant_line_color" : MAROON_B, - } - def construct(self): - self.setup_axes() - def func(x): - u = 0.3*x - 1.5 - return -u**3 + 5*u + 7 - graph = self.get_graph(func) - graph_label = self.get_graph_label(graph) - start_x_v_line, nudged_x_v_line = [ - self.get_vertical_line_to_graph( - self.start_x + nudge, graph, - line_class = DashedLine, - color = RED - ) - for nudge in (0, self.start_dx) - ] - nudged_x_v_line.save_state() - ss_group = self.get_secant_slope_group( - self.start_x, graph, - dx = self.start_dx, - dx_label = "dx", - df_label = "df", - df_line_color = self.df_color, - dx_line_color = self.dx_color, - secant_line_color = self.secant_line_color, - ) - derivative = TexMobject( - "{df", "\\over \\,", "dx}", "(", str(self.start_x), ")" - ) - derivative.set_color_by_tex("df", self.df_color) - derivative.set_color_by_tex("dx", self.dx_color) - derivative.set_color_by_tex(str(self.start_x), RED) - df = derivative.get_part_by_tex("df") - dx = derivative.get_part_by_tex("dx") - input_x = derivative.get_part_by_tex(str(self.start_x)) - derivative.move_to(self.coords_to_point(7, 4)) - derivative.save_state() - deriv_brace = Brace(derivative) - dx_to_0 = TexMobject("dx", "\\to 0") - dx_to_0.set_color_by_tex("dx", self.dx_color) - dx_to_0.next_to(deriv_brace, DOWN) - - #Introduce graph - self.play(ShowCreation(graph)) - self.play(Write(graph_label, run_time = 1)) - self.play(Write(derivative)) - self.wait() - input_copy = input_x.copy() - self.play( - input_copy.next_to, - self.coords_to_point(self.start_x, 0), - DOWN - ) - self.play(ShowCreation(start_x_v_line)) - self.wait() - - #ss_group_development - self.play( - ShowCreation(ss_group.dx_line), - ShowCreation(ss_group.dx_label), - ) - self.wait() - self.play(ShowCreation(ss_group.df_line)) - self.play(Write(ss_group.df_label)) - self.wait(2) - self.play( - ReplacementTransform(ss_group.dx_label.copy(), dx), - ReplacementTransform(ss_group.df_label.copy(), df), - run_time = 2 - ) - self.play(ShowCreation(ss_group.secant_line)) - self.wait() - - #Let dx approach 0 - self.play( - GrowFromCenter(deriv_brace), - Write(dx_to_0), - ) - self.animate_secant_slope_group_change( - ss_group, - target_dx = 0.01, - run_time = 5, - ) - self.wait() - - #Write out fuller limit - new_deriv = TexMobject( - "{f", "(", str(self.start_x), "+", "dx", ")", - "-", "f", "(", str(self.start_x), ")", - "\\over \\,", "dx" - ) - new_deriv.set_color_by_tex("dx", self.dx_color) - new_deriv.set_color_by_tex("f", self.df_color) - new_deriv.set_color_by_tex(str(self.start_x), RED) - deriv_to_new_deriv = dict([ - ( - VGroup(derivative.get_part_by_tex(s)), - VGroup(*new_deriv.get_parts_by_tex(s)) - ) - for s in ["f", "over", "dx", "(", str(self.start_x), ")"] - ]) - covered_new_deriv_parts = list(it.chain(*list(deriv_to_new_deriv.values()))) - uncovered_new_deriv_parts = [part for part in new_deriv if part not in covered_new_deriv_parts] - new_deriv.move_to(derivative) - new_brace = Brace(new_deriv, DOWN) - - self.animate_secant_slope_group_change( - ss_group, - target_dx = self.start_dx, - run_time = 2 - ) - self.play(ShowCreation(nudged_x_v_line)) - self.wait() - self.play(*[ - ReplacementTransform(*pair, run_time = 2) - for pair in list(deriv_to_new_deriv.items()) - ]+[ - Transform(deriv_brace, new_brace), - dx_to_0.next_to, new_brace, DOWN - ]) - self.play(Write(VGroup(*uncovered_new_deriv_parts), run_time = 2)) - self.wait() - - #Introduce limit notation - lim = TexMobject("\\lim").scale(1.3) - dx_to_0.generate_target() - dx_to_0.target.scale(0.7) - dx_to_0.target.next_to(lim, DOWN, buff = SMALL_BUFF) - lim_group = VGroup(lim, dx_to_0.target) - lim_group.move_to(new_deriv, LEFT) - - self.play( - ReplacementTransform(deriv_brace, lim), - MoveToTarget(dx_to_0), - new_deriv.next_to, lim_group, RIGHT, - run_time = 2 - ) - for sf, color in (1.2, YELLOW), (1/1.2, WHITE): - self.play( - lim.scale_in_place, sf, - lim.set_color, color, - lag_ratio = 0.5 - ) - self.wait(2) - self.animate_secant_slope_group_change( - ss_group, target_dx = 0.01, - run_time = 5, - added_anims = [ - Transform(nudged_x_v_line, start_x_v_line, run_time = 5) - ] - ) - self.wait(2) - - #Record attributes for DiscussLowercaseDs below - digest_locals(self) - -class RantOpenAndClose(Scene): - def construct(self): - opening, closing = [ - TextMobject( - start, "Rant on infinitesimals", "$>$", - arg_separator = "" - ) - for start in ("$<$", "$<$/") - ] - self.play(FadeIn(opening)) - self.wait(2) - self.play(Transform(opening, closing)) - self.wait(2) - -class DiscussLowercaseDs(RefreshOnDerivativeDefinition, PiCreatureScene, ZoomedScene): - CONFIG = { - "zoomed_canvas_corner" : UP+LEFT - } - def construct(self): - self.skip_superclass_anims() - self.replace_dx_terms() - self.compare_rhs_and_lhs() - self.h_is_finite() - - def skip_superclass_anims(self): - self.remove(self.pi_creature) - self.force_skipping() - RefreshOnDerivativeDefinition.construct(self) - self.revert_to_original_skipping_status() - self.animate_secant_slope_group_change( - self.ss_group, target_dx = self.start_dx, - added_anims = [ - self.nudged_x_v_line.restore, - Animation(self.ss_group.df_line) - ], - run_time = 1 - ) - everything = self.get_top_level_mobjects() - everything.remove(self.derivative) - self.play(*[ - ApplyMethod(mob.shift, 2.5*LEFT) - for mob in everything - ] + [ - FadeIn(self.pi_creature) - ]) - - def replace_dx_terms(self): - dx_list = [self.dx_to_0[0]] - dx_list += self.new_deriv.get_parts_by_tex("dx") - mover = dx_list[0] - mover_scale_val = 1.5 - mover.initial_right = mover.get_right() - - self.play( - mover.scale, mover_scale_val, - mover.next_to, self.pi_creature.get_corner(UP+LEFT), - UP, MED_SMALL_BUFF, - self.pi_creature.change_mode, "sassy", - path_arc = np.pi/2, - ) - self.blink() - self.wait() - for tex in "\\Delta x", "h": - dx_list_replacement = [ - TexMobject( - tex - ).set_color(self.dx_color).move_to(dx, DOWN) - for dx in dx_list - ] - self.play( - Transform( - VGroup(*dx_list), - VGroup(*dx_list_replacement), - ), - self.pi_creature.change_mode, "raise_right_hand" - ) - self.wait() - self.play( - mover.scale, 0.9, - mover.move_to, mover.initial_right, RIGHT, - self.pi_creature.change_mode, "happy", - ) - self.play( - self.dx_to_0.next_to, self.lim, DOWN, SMALL_BUFF, - ) - self.wait() - - def compare_rhs_and_lhs(self): - self.derivative.restore() - lhs = self.derivative - equals = TexMobject("=") - rhs = VGroup(self.lim, self.dx_to_0, self.new_deriv) - rhs.generate_target() - rhs.target.next_to(self.pi_creature, UP, MED_LARGE_BUFF) - rhs.target.to_edge(RIGHT) - equals.next_to(rhs.target, LEFT) - lhs.next_to(equals, LEFT) - - d_circles = VGroup(*[ - Circle(color = BLUE_B).replace( - lhs.get_part_by_tex(tex)[0], - stretch = True, - ).scale_in_place(1.5).rotate_in_place(-np.pi/12) - for tex in ("df", "dx") - ]) - d_words = TextMobject(""" - Limit idea is - built in - """) - d_words.next_to(d_circles, DOWN) - d_words.set_color(d_circles[0].get_color()) - - lhs_rect, rhs_rect = rects = [ - Rectangle(color = GREEN_B).replace( - mob, stretch = True - ) - for mob in (lhs, rhs.target) - ] - for rect in rects: - rect.stretch_to_fit_width(rect.get_width()+2*MED_SMALL_BUFF) - rect.stretch_to_fit_height(rect.get_height()+2*MED_SMALL_BUFF) - formal_definition_words = TextMobject(""" - Formal derivative definition - """) - formal_definition_words.set_width(rhs_rect.get_width()) - formal_definition_words.next_to(rhs_rect, UP) - formal_definition_words.set_color(rhs_rect.get_color()) - formal_definition_words.add_background_rectangle() - - df = VGroup(lhs.get_part_by_tex("df")) - df_target = VGroup(*self.new_deriv.get_parts_by_tex("f")) - - self.play( - MoveToTarget(rhs), - Write(lhs), - Write(equals), - ) - self.play( - ShowCreation(d_circles, run_time = 2), - self.pi_creature.change_mode, "pondering" - ) - self.play(Write(d_words)) - self.animate_secant_slope_group_change( - self.ss_group, target_dx = 0.01, - added_anims = [ - Transform( - self.nudged_x_v_line, self.start_x_v_line, - run_time = 3 - ) - ] - ) - self.change_mode("thinking") - self.wait(2) - self.play( - ShowCreation(lhs_rect), - FadeOut(d_circles), - FadeOut(d_words), - ) - self.wait(2) - self.play( - ReplacementTransform(lhs_rect, rhs_rect), - self.pi_creature.change_mode, "raise_right_hand" - ) - self.wait(2) - self.play(ReplacementTransform( - df.copy(), df_target, - path_arc = -np.pi/2, - run_time = 2 - )) - self.wait(2) - self.play(Indicate( - VGroup(*rhs[:2]), - run_time = 2 - )) - self.wait() - - self.play(Write(formal_definition_words)) - self.play( - self.pi_creature.change_mode, "happy", - self.pi_creature.look_at, formal_definition_words - ) - self.wait(2) - - lhs.add_background_rectangle() - self.add(rhs_rect, rhs) - self.definition_group = VGroup( - lhs, equals, rhs_rect, rhs, formal_definition_words - ) - self.lhs, self.rhs, self.rhs_rect = lhs, rhs, rhs_rect - - def h_is_finite(self): - self.play( - FadeOut(self.graph_label), - self.definition_group.center, - self.definition_group.to_corner, UP+RIGHT, - self.pi_creature.change_mode, "sassy", - self.pi_creature.look_at, 4*UP - ) - self.wait() - - - words = TextMobject("No ``infinitely small''") - words.next_to( - self.definition_group, DOWN, - buff = LARGE_BUFF, - ) - arrow = Arrow(words.get_top(), self.rhs_rect.get_bottom()) - arrow.set_color(WHITE) - - h_group = VGroup( - self.rhs[1].get_part_by_tex("dx"), - *self.rhs[2].get_parts_by_tex("dx") - ) - moving_h = h_group[0] - moving_h.original_center = moving_h.get_center() - dx_group = VGroup() - for h in h_group: - dx = TexMobject("dx") - dx.set_color(h.get_color()) - dx.replace(h, dim_to_match = 1) - dx_group.add(dx) - moving_dx = dx_group[0] - - self.play(Write(words), ShowCreation(arrow)) - self.wait(2) - - self.play( - moving_h.next_to, self.pi_creature.get_corner(UP+RIGHT), UP, - self.pi_creature.change_mode, "raise_left_hand", - ) - self.wait() - moving_dx.move_to(moving_h) - h_group.save_state() - self.play(Transform( - h_group, dx_group, - path_arc = np.pi, - )) - self.wait(2) - self.play(h_group.restore, path_arc = np.pi) - self.play( - moving_h.move_to, moving_h.original_center, - self.pi_creature.change_mode, "plain" - ) - self.wait() - - #Zoom in - self.activate_zooming() - lil_rect = self.little_rectangle - lil_rect.move_to(self.ss_group) - lil_rect.scale_in_place(3) - lil_rect.save_state() - self.wait() - self.add(self.rhs) - self.play( - lil_rect.set_width, - self.ss_group.dx_line.get_width()*4, - run_time = 4 - ) - self.wait() - dx = self.ss_group.dx_label - dx.save_state() - h = TexMobject("h") - h.set_color(dx.get_color()) - h.replace(dx, dim_to_match = 1) - self.play(Transform(dx, h, path_arc = np.pi)) - self.play(Indicate(dx)) - self.wait() - self.play(dx.restore, path_arc = np.pi) - self.play(lil_rect.restore, run_time = 4) - self.wait() - self.disactivate_zooming() - self.wait() - - #Last approaching reference - for target_dx in 3, 0.01, -2, 0.01: - self.animate_secant_slope_group_change( - self.ss_group, target_dx = target_dx, - run_time = 4, - ) - self.wait() - -class OtherViewsOfDx(TeacherStudentsScene): - def construct(self): - definition = TexMobject( - "{df", "\\over \\,", "dx}", "(", "2", ")", "=", - "\\lim", "_{h", "\\to", "0}", - "{f", "(", "2", "+", "h", ")", "-", "f", "(", "2", ")", - "\\over \\,", "h}" - ) - tex_to_color = { - "df" : YELLOW, - "f" : YELLOW, - "dx" : GREEN, - "h" : GREEN, - "2" : RED - } - for tex, color in list(tex_to_color.items()): - definition.set_color_by_tex(tex, color) - definition.scale(0.8) - definition.to_corner(UP+LEFT) - dx_group = VGroup(*definition.get_parts_by_tex("dx")) - h_group = VGroup(*definition.get_parts_by_tex("h")) - self.add(definition) - - statements = [ - TextMobject(*args) - for args in [ - ("Why the new \\\\ variable", "$h$", "?"), - ("$dx$", "is more $\\dots$ contentious."), - ("$dx$", "is infinitely small"), - ("$dx$", "is nothing more \\\\ than a symbol"), - ] - ] - for statement in statements: - statement.h, statement.dx = [ - VGroup(*statement.get_parts_by_tex( - tex, substring = False - )).set_color(GREEN) - for tex in ("$h$", "$dx$") - ] - - #Question - self.student_says( - statements[0], - student_index = 1, - target_mode = "confused" - ) - self.play(ReplacementTransform( - statements[0].h.copy(), h_group, - run_time = 2, - lag_ratio = 0.5, - )) - self.wait() - - #Teacher answer - self.teacher_says( - statements[1], - target_mode = "hesitant", - bubble_creation_class = FadeIn, - ) - self.play(ReplacementTransform( - statements[1].dx.copy(), dx_group, - run_time = 2, - )) - self.wait() - - #First alternate view - moving_dx = dx_group.copy() - bubble_intro = PiCreatureBubbleIntroduction( - self.get_students()[2], - statements[2], - target_mode = "hooray", - bubble_creation_class = FadeIn, - ) - bubble_intro.update(1) - dx_movement = Transform( - moving_dx, statements[2].dx, - run_time = 2 - ) - bubble_intro.update(0) - self.play( - bubble_intro, dx_movement, - RemovePiCreatureBubble(self.get_teacher()), - ) - self.play(self.get_teacher().change_mode, "erm") - self.wait() - - #Next alternate view - bubble_intro = PiCreatureBubbleIntroduction( - self.get_students()[0], - statements[3], - target_mode = "maybe", - look_at_arg = 3*UP, - bubble_creation_class = FadeIn, - ) - bubble_intro.update(1) - dx_movement = Transform( - moving_dx, statements[3].dx, - run_time = 2 - ) - bubble_intro.update(0) - last_bubble = self.get_students()[2].bubble - self.play( - bubble_intro, dx_movement, - FadeOut(last_bubble), - FadeOut(last_bubble.content), - *it.chain(*[ - [ - pi.change_mode, "pondering", - pi.look_at, bubble_intro.mobject - ] - for pi in self.get_students()[1:] - ]) - ) - self.wait(3) - -class GoalsListed(Scene): - def construct(self): - goals = VGroup(*[ - TextMobject("Goal %d: %s"%(d, s)) - for d, s in zip(it.count(1), [ - "Formal definition of a derivative", - "$(\\epsilon, \\delta)$ definition of limits", - "L'Hôpital's rule", - ]) - ]) - goals.arrange( - DOWN, buff = LARGE_BUFF, aligned_edge = LEFT - ) - - for goal in goals: - self.play(FadeIn(goal)) - self.wait() - for i, goal in enumerate(goals): - anims = [goal.set_color, YELLOW] - if i > 0: - anims += [goals[i-1].set_color, WHITE] - self.play(*anims) - self.wait() - -class GraphLimitExpression(GraphScene): - CONFIG = { - "start_x" : 2, - "h_color" : GREEN, - "f_color" : YELLOW, - "two_color" : RED, - "graph_origin" : 3*DOWN+LEFT, - "x_min" : -8, - "x_max" : 5, - "x_axis_label" : "$h$", - "x_labeled_nums" : list(range(-8, 6, 2)), - "y_min" : 0, - "y_max" : 20, - "y_tick_frequency" : 1, - "y_labeled_nums" : list(range(5, 25, 5)), - "y_axis_label" : "", - "big_delta" : 0.7, - "small_delta" : 0.01, - } - def construct(self): - self.func = lambda h : 3*(2**2) + 3*2*h + h**2 - self.setup_axes() - self.introduce_function() - self.emphasize_non_definedness_at_0() - self.draw_limit_point_hole() - self.show_limit() - self.skeptic_asks() - self.show_epsilon_delta_intuition() - - def introduce_function(self): - expression = TexMobject( - "{(", "2", "+", "h", ")", "^3", - "-", "(", "2", ")", "^3", - "\\over \\,", "h}", - arg_separator = "", - ) - limit = TexMobject("\\lim", "_{h", "\\to 0}") - derivative = TexMobject( - "{d(x^3)", "\\over \\,", "dx}", "(", "2", ")" - ) - tex_to_color = { - "h" : self.h_color, - "dx" : self.h_color, - "2" : self.two_color - } - for tex_mob in expression, limit, derivative: - for tex, color in list(tex_to_color.items()): - tex_mob.set_color_by_tex(tex, color) - tex_mob.next_to(ORIGIN, RIGHT, LARGE_BUFF) - tex_mob.to_edge(UP) - - expression.save_state() - expression.generate_target() - expression.target.next_to(limit, RIGHT) - brace = Brace(VGroup(limit, expression.target)) - derivative.next_to(brace, DOWN) - - - - indices = [0, 6, 11, 13] - funcs = [ - lambda h : (2+h)**3, - lambda h : (2+h)**3 - 2**3, - self.func - ] - graph = None - for i, j, func in zip(indices, indices[1:], funcs): - anims = [FadeIn( - VGroup(*expression[i:j]), - lag_ratio = 0.5, - )] - new_graph = self.get_graph(func, color = BLUE) - if graph is None: - graph = new_graph - anims.append(FadeIn(graph)) - else: - anims.append(Transform(graph, new_graph)) - self.play(*anims) - self.wait() - self.wait() - self.play( - MoveToTarget(expression), - FadeIn(limit, lag_ratio = 0.5), - GrowFromCenter(brace) - ) - self.play(Write(derivative)) - self.wait(2) - self.play( - expression.restore, - *list(map(FadeOut, [derivative, brace, limit])) - ) - self.wait() - - colored_graph = graph.copy().set_color(YELLOW) - self.play(ShowCreation(colored_graph)) - self.wait() - self.play(ShowCreation(graph)) - self.remove(colored_graph) - self.wait() - - self.expression = expression - self.limit = limit - self.graph = graph - - def emphasize_non_definedness_at_0(self): - expression = self.expression - - dot = Dot(self.graph_origin, color = GREEN) - h_equals_0 = TexMobject("h", "=", "0", "?") - h_equals_0.next_to(self.graph_origin, UP+RIGHT, LARGE_BUFF) - for tex in "h", "0": - h_equals_0.set_color_by_tex(tex, GREEN) - - arrow = Arrow(h_equals_0.get_left(), self.graph_origin) - arrow.set_color(WHITE) - - new_expression = expression.deepcopy() - h_group = VGroup(*new_expression.get_parts_by_tex("h")) - for h in h_group: - zero = TexMobject("0") - zero.set_color(h.get_color()) - zero.replace(h, dim_to_match = 1) - Transform(h, zero).update(1) - rhs = TexMobject("=", "{\\, 0\\,", "\\over \\,", "0\\,}") - rhs.set_color_by_tex("0", GREEN) - rhs.next_to(new_expression, RIGHT) - equation = VGroup(new_expression, rhs) - equation.next_to(expression, DOWN, buff = LARGE_BUFF) - - ud_brace = Brace(VGroup(*rhs[1:]), DOWN) - undefined = TextMobject("Undefined") - undefined.next_to(ud_brace, DOWN) - undefined.to_edge(RIGHT) - - self.play(Write(h_equals_0, run_time = 2)) - self.play(*list(map(ShowCreation, [arrow, dot]))) - self.wait() - self.play(ReplacementTransform( - expression.copy(), new_expression - )) - self.wait() - self.play(Write(rhs)) - self.wait() - self.play( - GrowFromCenter(ud_brace), - Write(undefined) - ) - self.wait(2) - - self.point_to_zero_group = VGroup( - h_equals_0, arrow, dot - ) - self.plug_in_zero_group = VGroup( - new_expression, rhs, ud_brace, undefined - ) - - def draw_limit_point_hole(self): - dx = 0.07 - color = self.graph.get_color() - circle = Circle( - radius = dx, - stroke_color = color, - fill_color = BLACK, - fill_opacity = 1, - ) - circle.move_to(self.coords_to_point(0, 12)) - colored_circle = circle.copy() - colored_circle.set_stroke(YELLOW) - colored_circle.set_fill(opacity = 0) - - self.play(GrowFromCenter(circle)) - self.wait() - self.play(ShowCreation(colored_circle)) - self.play(ShowCreation( - circle.copy().set_fill(opacity = 0), - remover = True - )) - self.remove(colored_circle) - self.play( - circle.scale_in_place, 0.3, - run_time = 2, - rate_func = wiggle - ) - self.wait() - - self.limit_point_hole = circle - - def show_limit(self): - dot = self.point_to_zero_group[-1] - ed_group = self.get_epsilon_delta_group(self.big_delta) - - left_v_line, right_v_line = ed_group.delta_lines - bottom_h_line, top_h_line = ed_group.epsilon_lines - ed_group.delta_lines.save_state() - ed_group.epsilon_lines.save_state() - - brace = Brace(ed_group.input_range, UP) - brace_text = brace.get_text("Inputs around 0", buff = SMALL_BUFF) - brace_text.add_background_rectangle() - brace_text.shift(RIGHT) - - limit_point_hole_copy = self.limit_point_hole.copy() - limit_point_hole_copy.set_stroke(YELLOW) - h_zero_hole = limit_point_hole_copy.copy() - h_zero_hole.move_to(self.graph_origin) - - ed_group.input_range.add(h_zero_hole) - ed_group.output_range.add(limit_point_hole_copy) - - #Show range around 0 - self.play( - FadeOut(self.plug_in_zero_group), - FadeOut(VGroup(*self.point_to_zero_group[:-1])), - ) - self.play( - GrowFromCenter(brace), - Write(brace_text), - ReplacementTransform(dot, ed_group.input_range), - ) - self.add(h_zero_hole) - self.wait() - self.play( - ReplacementTransform( - ed_group.input_range.copy(), - ed_group.output_range, - run_time = 2 - ), - ) - self.remove(self.limit_point_hole) - - #Show approaching - self.play(*list(map(FadeOut, [brace, brace_text]))) - for v_line, h_line in (right_v_line, top_h_line), (left_v_line, bottom_h_line): - self.play( - ShowCreation(v_line), - ShowCreation(h_line) - ) - self.wait() - self.play( - v_line.move_to, self.coords_to_point(0, 0), DOWN, - h_line.move_to, self.coords_to_point(0, self.func(0)), - run_time = 3 - ) - self.play( - VGroup(h_line, v_line).set_stroke, GREY, 2, - ) - self.wait() - - #Write limit - limit = self.limit - limit.next_to(self.expression, LEFT) - equals, twelve = rhs = TexMobject("=", "12") - rhs.next_to(self.expression, RIGHT) - twelve_copy = twelve.copy() - limit_group = VGroup(limit, rhs) - - self.play(Write(limit_group)) - self.wait() - self.play(twelve_copy.next_to, top_h_line, RIGHT) - self.wait() - - self.twelve_copy = twelve_copy - self.rhs = rhs - self.ed_group = ed_group - self.input_range_brace_group = VGroup(brace, brace_text) - - def skeptic_asks(self): - randy = Randolph() - randy.scale(0.9) - randy.to_edge(DOWN) - - self.play(FadeIn(randy)) - self.play(PiCreatureSays( - randy, """ - What \\emph{exactly} do you - mean by ``approach'' - """, - bubble_kwargs = { - "height" : 3, - "width" : 5, - "fill_opacity" : 1, - "direction" : LEFT, - }, - target_mode = "sassy" - )) - self.remove(self.twelve_copy) - self.play(randy.look, OUT) - self.play(Blink(randy)) - self.wait() - self.play(RemovePiCreatureBubble( - randy, target_mode = "pondering", - look_at_arg = self.limit_point_hole - )) - self.play( - self.ed_group.delta_lines.restore, - self.ed_group.epsilon_lines.restore, - Animation(randy), - rate_func = there_and_back, - run_time = 5, - ) - self.play(Blink(randy)) - self.play(FadeOut(randy)) - - def show_epsilon_delta_intuition(self): - self.play( - FadeOut(self.ed_group.epsilon_lines), - FadeIn(self.input_range_brace_group) - ) - self.ed_group.epsilon_lines.restore() - self.wait() - self.play( - self.ed_group.delta_lines.restore, - Animation(self.input_range_brace_group), - run_time = 2 - ) - self.play(FadeOut(self.input_range_brace_group)) - self.play( - ReplacementTransform( - self.ed_group.input_range.copy(), - self.ed_group.output_range, - run_time = 2 - ) - ) - self.wait() - self.play(*list(map(GrowFromCenter, self.ed_group.epsilon_lines))) - self.play(*[ - ApplyMethod( - line.copy().set_stroke(GREY, 2).move_to, - self.coords_to_point(0, self.func(0)), - run_time = 3, - rate_func = there_and_back, - remover = True, - ) - for line in self.ed_group.epsilon_lines - ]) - self.wait() - - holes = VGroup( - self.ed_group.input_range.submobjects.pop(), - self.ed_group.output_range.submobjects.pop(), - ) - holes.save_state() - self.animate_epsilon_delta_group_change( - self.ed_group, - target_delta = self.small_delta, - run_time = 8, - rate_func = lambda t : smooth(t, 2), - added_anims = [ - ApplyMethod( - hole.scale_in_place, 0.5, - run_time = 8 - ) - for hole in holes - ] - ) - self.wait() - - self.holes = holes - - ######### - - def get_epsilon_delta_group( - self, - delta, - limit_x = 0, - dashed_line_stroke_width = 3, - dashed_line_length = FRAME_HEIGHT, - input_range_color = YELLOW, - input_range_stroke_width = 6, - ): - kwargs = dict(locals()) - result = VGroup() - kwargs.pop("self") - result.delta = kwargs.pop("delta") - result.limit_x = kwargs.pop("limit_x") - result.kwargs = kwargs - dashed_line = DashedLine( - ORIGIN, dashed_line_length*RIGHT, - stroke_width = dashed_line_stroke_width - ) - x_values = [limit_x-delta, limit_x+delta] - x_axis_points = [self.coords_to_point(x, 0) for x in x_values] - result.delta_lines = VGroup(*[ - dashed_line.copy().rotate(np.pi/2).move_to( - point, DOWN - ) - for point in x_axis_points - ]) - if self.func(limit_x) < 0: - result.delta_lines.rotate( - np.pi, RIGHT, - about_point = result.delta_lines.get_bottom() - ) - basically_zero = 0.00001 - result.input_range, result.output_range = [ - VGroup(*[ - self.get_graph( - func, - color = input_range_color, - x_min = x_min, - x_max = x_max, - ) - for x_min, x_max in [ - (limit_x-delta, limit_x-basically_zero), - (limit_x+basically_zero, limit_x+delta), - ] - ]).set_stroke(width = input_range_stroke_width) - for func in ((lambda h : 0), self.func) - ] - result.epsilon_lines = VGroup(*[ - dashed_line.copy().move_to( - self.coords_to_point(limit_x, 0)[0]*RIGHT+\ - result.output_range.get_edge_center(vect)[1]*UP - ) - for vect in (DOWN, UP) - ]) - - result.digest_mobject_attrs() - return result - - def animate_epsilon_delta_group_change( - self, epsilon_delta_group, target_delta, - **kwargs - ): - added_anims = kwargs.get("added_anims", []) - limit_x = epsilon_delta_group.limit_x - start_delta = epsilon_delta_group.delta - ed_group_kwargs = epsilon_delta_group.kwargs - def update_ed_group(ed_group, alpha): - delta = interpolate(start_delta, target_delta, alpha) - new_group = self.get_epsilon_delta_group( - delta, limit_x = limit_x, - **ed_group_kwargs - ) - Transform(ed_group, new_group).update(1) - return ed_group - - self.play( - UpdateFromAlphaFunc( - epsilon_delta_group, update_ed_group, - **kwargs - ), - *added_anims - ) - -class LimitCounterExample(GraphLimitExpression): - CONFIG = { - "x_min" : -8, - "x_max" : 8, - "x_labeled_nums" : list(range(-8, 10, 2)), - "x_axis_width" : FRAME_WIDTH - LARGE_BUFF, - "y_min" : -4, - "y_max" : 4, - "y_labeled_nums" : list(range(-2, 4, 1)), - "y_axis_height" : FRAME_HEIGHT+2*LARGE_BUFF, - "graph_origin" : DOWN, - "graph_color" : BLUE, - "hole_radius" : 0.075, - "smaller_hole_radius" : 0.04, - "big_delta" : 1.5, - "small_delta" : 0.05, - } - def construct(self): - self.add_func() - self.setup_axes() - self.draw_graph() - self.approach_zero() - self.write_limit_not_defined() - self.show_epsilon_delta_intuition() - - def add_func(self): - def func(h): - square = 0.25*h**2 - if h < 0: - return -square + 1 - else: - return square + 2 - self.func = func - - def draw_graph(self): - epsilon = 0.1 - left_graph, right_graph = [ - self.get_graph( - self.func, - color = self.graph_color, - x_min = x_min, - x_max = x_max, - ) - for x_min, x_max in [ - (self.x_min, -epsilon), - (epsilon, self.x_max), - ] - ] - left_hole = self.get_hole(0, 1, color = self.graph_color) - right_hole = self.get_hole(0, 2, color = self.graph_color) - graph = VGroup( - left_graph, left_hole, - right_hole, right_graph - ) - - self.play(ShowCreation(graph, run_time = 5)) - self.wait() - self.play(ReplacementTransform( - left_hole.copy().set_stroke(YELLOW), right_hole - )) - self.wait() - - self.graph = graph - self.graph_holes = VGroup(left_hole, right_hole) - - def approach_zero(self): - ed_group = self.get_epsilon_delta_group(self.big_delta) - left_v_line, right_v_line = ed_group.delta_lines - bottom_h_line, top_h_line = ed_group.epsilon_lines - ed_group.delta_lines.save_state() - ed_group.epsilon_lines.save_state() - - right_lines = VGroup(right_v_line, top_h_line) - left_lines = VGroup(left_v_line, bottom_h_line) - - basically_zero = 0.00001 - def update_lines(lines, alpha): - v_line, h_line = lines - sign = 1 if v_line is right_v_line else -1 - x_val = interpolate(sign*self.big_delta, sign*basically_zero, alpha) - v_line.move_to(self.coords_to_point(x_val, 0), DOWN) - h_line.move_to(self.coords_to_point(0, self.func(x_val))) - return lines - - for lines in right_lines, left_lines: - self.play(*list(map(ShowCreation, lines))) - self.play(UpdateFromAlphaFunc( - lines, update_lines, - run_time = 3 - )) - self.play(lines.set_stroke, GREY, 3) - self.wait() - - self.ed_group = ed_group - - def write_limit_not_defined(self): - limit = TexMobject( - "\\lim", "_{h", "\\to 0}", "f(", "h", ")" - ) - limit.set_color_by_tex("h", GREEN) - limit.move_to(self.coords_to_point(2, 1.5)) - - words = TextMobject("is not defined") - words.set_color(RED) - words.next_to(limit, RIGHT, align_using_submobjects = True) - - limit_group = VGroup(limit, words) - - self.play(Write(limit)) - self.wait() - self.play(Write(words)) - self.wait() - self.play(limit_group.to_corner, UP+LEFT) - self.wait() - - def show_epsilon_delta_intuition(self): - ed_group = self.ed_group - self.play( - ed_group.delta_lines.restore, - ed_group.epsilon_lines.restore, - ) - self.play(ShowCreation(ed_group.input_range)) - self.wait() - self.play(ReplacementTransform( - ed_group.input_range.copy(), - ed_group.output_range, - run_time = 2 - )) - self.graph.remove(*self.graph_holes) - self.remove(*self.graph_holes) - self.wait() - self.animate_epsilon_delta_group_change( - ed_group, target_delta = self.small_delta, - run_time = 6 - ) - self.hole_radius = self.smaller_hole_radius - - brace = Brace(self.ed_group.epsilon_lines, RIGHT, buff = SMALL_BUFF) - brace_text = brace.get_text("Can't get \\\\ smaller", buff = SMALL_BUFF) - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait() - run_time_rate_func_pairs = [ - (3, lambda t : 1 - there_and_back(t)), - (1, lambda t : 1 - 0.2*there_and_back(3*t % 1)), - (1, lambda t : 1 - 0.2*there_and_back(5*t % 1)), - ] - for run_time, rate_func in run_time_rate_func_pairs: - self.animate_epsilon_delta_group_change( - ed_group, target_delta = self.small_delta, - run_time = run_time, - rate_func = rate_func, - ) - self.wait() - - ##### - - def get_epsilon_delta_group(self, delta, **kwargs): - ed_group = GraphLimitExpression.get_epsilon_delta_group(self, delta, **kwargs) - color = ed_group.kwargs["input_range_color"] - radius = min(delta/2, self.hole_radius) - pairs = [ - (ed_group.input_range[0], (0, 0)), - (ed_group.input_range[1], (0, 0)), - (ed_group.output_range[0], (0, 1)), - (ed_group.output_range[1], (0, 2)), - ] - for mob, coords in pairs: - mob.add(self.get_hole( - *coords, - color = color, - radius = radius - )) - return ed_group - - def get_hole(self, *coords, **kwargs): - color = kwargs.get("color", BLUE) - radius = kwargs.get("radius", self.hole_radius) - return Circle( - radius = radius, - stroke_color = color, - fill_color = BLACK, - fill_opacity = 1, - ).move_to(self.coords_to_point(*coords)) - -class PrefaceToEpsilonDeltaDefinition(TeacherStudentsScene): - def construct(self): - title = TexMobject("(\\epsilon, \\delta) \\text{ definition}") - title.next_to(self.get_teacher().get_corner(UP+LEFT), UP) - title.save_state() - title.shift(DOWN) - title.set_fill(opacity = 0) - - self.play( - title.restore, - self.get_teacher().change_mode, "raise_right_hand", - ) - self.change_student_modes(*["confused"]*3) - self.wait() - self.student_says( - "Isn't that pretty \\\\ technical?", - target_mode = "guilty", - added_anims = [ - title.to_edge, UP, - self.get_teacher().change_mode, "plain", - self.get_teacher().look_at, self.get_students()[1].eyes - ] - ) - self.look_at(self.get_teacher().eyes, self.get_students()) - self.wait() - self.teacher_says("", bubble_kwargs = {"stroke_width" : 0}) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = UP+LEFT, - added_anims = [self.get_teacher().look_at, UP+LEFT] - ) - self.wait(3) - words = TextMobject( - "It's a glimpse of\\\\", - "real analysis" - ) - words.set_color_by_tex("real", YELLOW) - self.teacher_says( - words, - bubble_kwargs = {"height" : 3, "width" : 6} - ) - self.change_student_modes(*["happy"]*3) - self.wait(6) - -class EpsilonDeltaExample(GraphLimitExpression, ZoomedScene): - CONFIG = { - "epsilon_list" : [2, 1, 0.5], - "zoomed_canvas_corner" : DOWN+RIGHT, - } - def construct(self): - self.delta_list = [ - epsilon/6.0 for epsilon in self.epsilon_list - ] - self.skip_superclass_anims() - self.introduce_epsilon() - self.match_epsilon() - self.zoom_in() - self.introduce_delta() - self.smaller_epsilon() - - def skip_superclass_anims(self): - self.force_skipping() - GraphLimitExpression.construct(self) - self.animate_epsilon_delta_group_change( - self.ed_group, - target_delta = self.big_delta, - ) - self.holes.restore() - self.add(self.holes) - self.revert_to_original_skipping_status() - - def introduce_epsilon(self): - epsilon_group, small_epsilon_group = list(map( - self.get_epsilon_group, - self.epsilon_list[:2] - )) - - twelve_line = epsilon_group.limit_line - twelve = self.rhs[-1] - twelve_copy = twelve.copy() - twelve_copy.next_to(twelve_line) - - distance = TextMobject("Distance") - distance.next_to(epsilon_group.labels, DOWN, LARGE_BUFF) - distance.to_edge(RIGHT) - arrows = VGroup(*[ - Arrow(distance.get_top(), label.get_right()) - for label in epsilon_group.labels - ]) - - self.play(ShowCreation(twelve_line)) - self.play(Write(twelve_copy)) - self.play(ReplacementTransform(twelve_copy, twelve)) - self.wait() - - self.play(*it.chain( - [ - ReplacementTransform(twelve_line.copy(), line) - for line in epsilon_group.epsilon_lines - ], - list(map(GrowFromCenter, epsilon_group.braces)), - )) - self.play(*list(map(Write, epsilon_group.labels))) - self.play( - Write(distance), - ShowCreation(arrows) - ) - self.wait() - self.play(*list(map(FadeOut, [distance, arrows]))) - self.play(Transform( - epsilon_group, small_epsilon_group, - run_time = 2 - )) - self.wait() - - self.epsilon_group = epsilon_group - - def match_epsilon(self): - self.animate_epsilon_delta_group_change( - self.ed_group, target_delta = self.delta_list[1], - run_time = 2, - added_anims = [ - ApplyMethod( - hole.scale_in_place, 0.25, - run_time = 2 - ) - for hole in self.holes - ] - ) - self.ed_group.delta = self.delta_list[1] - self.ed_group.input_range.make_jagged() - self.wait() - - def zoom_in(self): - self.ed_group.input_range.make_jagged() - - self.activate_zooming() - lil_rect = self.little_rectangle - lil_rect.move_to(self.graph_origin) - lil_rect.scale_in_place(self.zoom_factor) - self.add(self.holes) - self.wait() - self.play(lil_rect.scale_in_place, 1./self.zoom_factor) - self.wait() - - def introduce_delta(self): - delta_group = self.get_delta_group(self.delta_list[1]) - self.play(*list(map(GrowFromCenter, delta_group.braces))) - self.play(*list(map(Write, delta_group.labels))) - self.wait() - self.play( - ReplacementTransform( - self.ed_group.input_range.copy(), - self.ed_group.output_range, - run_time = 2 - ), - Animation(self.holes), - ) - self.play(ApplyWave( - VGroup(self.ed_group.output_range, self.holes[1]), - direction = RIGHT - )) - self.wait(2) - - self.delta_group = delta_group - - def smaller_epsilon(self): - new_epsilon = self.epsilon_list[-1] - new_delta = self.delta_list[-1] - self.play(Transform( - self.epsilon_group, - self.get_epsilon_group(new_epsilon) - )) - self.wait() - self.animate_epsilon_delta_group_change( - self.ed_group, target_delta = new_delta, - added_anims = [ - Transform( - self.delta_group, - self.get_delta_group(new_delta) - ) - ] + [ - ApplyMethod(hole.scale_in_place, 0.5) - for hole in self.holes - ] - ) - self.ed_group.input_range.make_jagged() - self.wait(2) - - ## - - def get_epsilon_group(self, epsilon, limit_value = 12): - result = VGroup() - line_length = FRAME_HEIGHT - lines = [ - Line( - ORIGIN, line_length*RIGHT, - ).move_to(self.coords_to_point(0, limit_value+nudge)) - for nudge in (0, -epsilon, epsilon) - ] - result.limit_line = lines[0] - result.limit_line.set_stroke(RED, width = 3) - result.epsilon_lines = VGroup(*lines[1:]) - result.epsilon_lines.set_stroke(MAROON_B, width = 2) - brace = Brace(Line(ORIGIN, 0.5*UP), RIGHT) - result.braces = VGroup(*[ - brace.copy().set_height( - group.get_height() - ).next_to(group, RIGHT, SMALL_BUFF) - for i in (1, 2) - for group in [VGroup(lines[0], lines[i])] - ]) - result.labels = VGroup(*[ - brace.get_text("\\Big $\\epsilon$", buff = SMALL_BUFF) - for brace in result.braces - ]) - for label, brace in zip(result.labels, result.braces): - label.set_height(min( - label.get_height(), - 0.8*brace.get_height() - )) - - result.digest_mobject_attrs() - return result - - def get_delta_group(self, delta): - result = VGroup() - brace = Brace(Line(ORIGIN, RIGHT), DOWN) - brace.set_width( - (self.coords_to_point(delta, 0)-self.graph_origin)[0] - ) - result.braces = VGroup(*[ - brace.copy().move_to(self.coords_to_point(x, 0)) - for x in (-delta/2, delta/2) - ]) - result.braces.shift(self.holes[0].get_height()*DOWN) - result.labels = VGroup(*[ - TexMobject("\\delta").scale( - 1./self.zoom_factor - ) - for brace in result.braces - ]) - for label, brace in zip(result.labels, result.braces): - label.next_to( - brace, DOWN, - buff = SMALL_BUFF/self.zoom_factor - ) - - result.digest_mobject_attrs() - return result - -class EpsilonDeltaCounterExample(LimitCounterExample, EpsilonDeltaExample): - def construct(self): - self.hole_radius = 0.04 - self.add_func() - self.setup_axes() - self.draw_graph() - self.introduce_epsilon() - self.introduce_epsilon_delta_group() - self.move_epsilon_group_up_and_down() - - def introduce_epsilon(self): - epsilon_group = self.get_epsilon_group(0.4, 1.5) - rhs = TexMobject("=0.4") - label = epsilon_group.labels[1] - rhs.next_to(label, RIGHT) - epsilon_group.add(rhs) - - self.play(ShowCreation(epsilon_group.limit_line)) - self.play(*it.chain( - [ - ReplacementTransform( - epsilon_group.limit_line.copy(), - line - ) - for line in epsilon_group.epsilon_lines - ], - list(map(GrowFromCenter, epsilon_group.braces)) - )) - self.play(*list(map(Write, epsilon_group.labels))) - self.play(Write(rhs)) - self.wait() - - self.epsilon_group = epsilon_group - - def introduce_epsilon_delta_group(self): - ed_group = self.get_epsilon_delta_group(self.big_delta) - - self.play(*list(map(ShowCreation, ed_group.delta_lines))) - self.play(ShowCreation(ed_group.input_range)) - self.play(ReplacementTransform( - ed_group.input_range.copy(), - ed_group.output_range, - run_time = 2 - )) - self.remove(self.graph_holes) - self.play(*list(map(GrowFromCenter, ed_group.epsilon_lines))) - self.wait(2) - self.animate_epsilon_delta_group_change( - ed_group, target_delta = self.small_delta, - run_time = 3 - ) - ed_group.delta = self.small_delta - self.wait() - - self.ed_group = ed_group - - def move_epsilon_group_up_and_down(self): - vects = [ - self.coords_to_point(0, 2) - self.coords_to_point(0, 1.5), - self.coords_to_point(0, 1) - self.coords_to_point(0, 2), - self.coords_to_point(0, 1.5) - self.coords_to_point(0, 1), - ] - for vect in vects: - self.play(self.epsilon_group.shift, vect) - self.wait() - self.shake_ed_group() - self.wait() - - ## - - def shake_ed_group(self): - self.animate_epsilon_delta_group_change( - self.ed_group, target_delta = self.big_delta, - rate_func = lambda t : 0.2*there_and_back(2*t%1) - ) - -class TheoryHeavy(TeacherStudentsScene): - def construct(self): - lhs = TexMobject( - "{df", "\\over \\,", "dx}", "(", "x", ")" - ) - equals = TexMobject("=") - rhs = TexMobject( - "\\lim", "_{h", "\\to 0}", - "{f", "(", "x", "+", "h", ")", "-", "f", "(", "x", ")", - "\\over \\,", "h}" - ) - derivative = VGroup(lhs, equals, rhs) - derivative.arrange(RIGHT) - for tex_mob in derivative: - tex_mob.set_color_by_tex("x", RED) - tex_mob.set_color_by_tex("h", GREEN) - tex_mob.set_color_by_tex("dx", GREEN) - tex_mob.set_color_by_tex("f", YELLOW) - derivative.next_to(self.get_pi_creatures(), UP, buff = MED_LARGE_BUFF) - - lim = rhs.get_part_by_tex("lim") - epsilon_delta = TexMobject("(\\epsilon, \\delta)") - epsilon_delta.next_to(lim, UP, buff = 1.5*LARGE_BUFF) - arrow = Arrow(epsilon_delta, lim, color = WHITE) - - - self.student_says( - "Too much theory!", - target_mode = "angry", - content_introduction_kwargs = {"run_time" : 2}, - ) - self.wait() - student = self.get_students()[1] - Scene.play(self, - Write(lhs), - FadeOut(student.bubble), - FadeOut(student.bubble.content), - *[ - ApplyFunction( - lambda pi : pi.change_mode("pondering").look_at(epsilon_delta), - pi - ) - for pi in self.get_pi_creatures() - ] - ) - student.bubble = None - part_tex_pairs = [ - ("df", "f"), - ("over", "+"), - ("over", "-"), - ("over", "to"), - ("over", "over"), - ("dx", "h"), - ("(", "("), - ("x", "x"), - (")", ")"), - ] - self.play(Write(equals), Write(lim), *[ - ReplacementTransform( - VGroup(*lhs.get_parts_by_tex(t1)).copy(), - VGroup(*rhs.get_parts_by_tex(t2)), - run_time = 2, - rate_func = squish_rate_func(smooth, alpha, alpha+0.5) - ) - for (t1, t2), alpha in zip( - part_tex_pairs, - np.linspace(0, 0.5, len(part_tex_pairs)) - ) - ]) - self.wait(2) - self.play( - Write(epsilon_delta), - ShowCreation(arrow) - ) - self.wait(3) - derivative.add(epsilon_delta, arrow) - self.student_says( - "How do you \\\\ compute limits?", - student_index = 2, - added_anims = [ - derivative.scale, 0.8, - derivative.to_corner, UP+LEFT - ] - ) - self.play(self.get_teacher().change_mode, "happy") - self.wait(2) - -class LHopitalExample(LimitCounterExample, PiCreatureScene, ZoomedScene, ReconfigurableScene): - CONFIG = { - "graph_origin" : ORIGIN, - "x_axis_width" : FRAME_WIDTH, - "x_min" : -5, - "x_max" : 5, - "x_labeled_nums" : list(range(-6, 8, 2)), - "x_axis_label" : "$x$", - "y_axis_height" : FRAME_HEIGHT, - "y_min" : -3.1, - "y_max" : 3.1, - "y_bottom_tick" : -4, - "y_labeled_nums" : list(range(-2, 4, 2)), - "y_axis_label" : "", - "x_color" : RED, - "hole_radius" : 0.07, - "big_delta" : 0.5, - "small_delta" : 0.01, - "dx" : 0.06, - "dx_color" : WHITE, - "tex_scale_value" : 0.9, - "sin_color" : GREEN, - "parabola_color" : YELLOW, - "zoomed_canvas_corner" : DOWN+LEFT, - "zoom_factor" : 10, - "zoomed_canvas_frame_shape" : (5, 5), - "zoomed_canvas_corner_buff" : MED_SMALL_BUFF, - "zoomed_rect_center_coords" : (1 + 0.1, -0.03), - } - def construct(self): - self.setup_axes() - self.introduce_function() - self.show_non_definedness_at_one() - self.plug_in_value_close_to_one() - self.ask_about_systematic_process() - self.show_graph_of_numerator_and_denominator() - self.zoom_in_to_trouble_point() - self.talk_through_sizes_of_nudges() - self.show_final_ratio() - self.show_final_height() - - def setup(self): - PiCreatureScene.setup(self) - ZoomedScene.setup(self) - ReconfigurableScene.setup(self) - self.remove(*self.get_pi_creatures()) - - def setup_axes(self): - GraphScene.setup_axes(self) - self.x_axis_label_mob.set_color(self.x_color) - - def introduce_function(self): - graph = self.get_graph(self.func) - colored_graph = graph.copy().set_color(YELLOW) - func_label = self.get_func_label() - func_label.next_to(ORIGIN, RIGHT, buff = LARGE_BUFF) - func_label.to_edge(UP) - - x_copy = self.x_axis_label_mob.copy() - - self.play( - Write(func_label), - Transform( - x_copy, VGroup(*func_label.get_parts_by_tex("x")), - remover = True - ) - ) - self.play(ShowCreation( - graph, - run_time = 3, - rate_func=linear - )) - self.wait(4) ## Overly oscillation - self.play(ShowCreation(colored_graph, run_time = 2)) - self.wait() - self.play(ShowCreation(graph, run_time = 2)) - self.remove(colored_graph) - self.wait() - - self.graph = graph - self.func_label = func_label - - def show_non_definedness_at_one(self): - morty = self.get_primary_pi_creature() - words = TexMobject("\\text{Try }", "x", "=1") - words.set_color_by_tex("x", self.x_color, substring = False) - - v_line, alt_v_line = [ - self.get_vertical_line_to_graph( - x, self.graph, - line_class = DashedLine, - color = self.x_color - ) - for x in (1, -1) - ] - hole, alt_hole = [ - self.get_hole(x, self.func(x)) - for x in (1, -1) - ] - ed_group = self.get_epsilon_delta_group( - self.big_delta, limit_x = 1, - ) - - func_1 = self.get_func_label("1") - func_1.next_to(self.func_label, DOWN, buff = MED_LARGE_BUFF) - rhs = TexMobject("\\Rightarrow \\frac{0}{0}") - rhs.next_to(func_1, RIGHT) - func_1_group = VGroup(func_1, *rhs) - func_1_group.add_to_back(BackgroundRectangle(func_1_group)) - - lim = TexMobject("\\lim", "_{x", "\\to 1}") - lim.set_color_by_tex("x", self.x_color) - lim.move_to(self.func_label, LEFT) - self.func_label.generate_target() - self.func_label.target.next_to(lim, RIGHT) - equals_q = TexMobject("=", "???") - equals_q.next_to(self.func_label.target, RIGHT) - - self.play(FadeIn(morty)) - self.play(PiCreatureSays(morty, words)) - self.play( - Blink(morty), - ShowCreation(v_line) - ) - self.play( - RemovePiCreatureBubble( - morty, target_mode = "pondering", - look_at_arg = func_1 - ), - ReplacementTransform( - self.func_label.copy(), - func_1 - ) - ) - self.wait(2) - self.play(Write(VGroup(*rhs[:-1]))) - self.wait() - self.play(Write(rhs[-1])) - self.wait() - self.play(GrowFromCenter(hole)) - self.wait() - - self.play(ShowCreation(alt_v_line)) - self.play(GrowFromCenter(alt_hole)) - self.wait() - alt_group = VGroup(alt_v_line, alt_hole) - self.play(alt_group.set_stroke, GREY, 2) - self.play(FocusOn(hole)) - self.wait() - - self.play(GrowFromCenter(ed_group.input_range)) - self.play( - ReplacementTransform( - ed_group.input_range.copy(), - ed_group.output_range - ), - *list(map(ShowCreation, ed_group.delta_lines)) - ) - self.play(*list(map(GrowFromCenter, ed_group.epsilon_lines))) - self.play(morty.change_mode, "thinking") - self.animate_epsilon_delta_group_change( - ed_group, target_delta = self.small_delta, - run_time = 4 - ) - self.wait() - self.play( - Write(lim), - MoveToTarget(self.func_label), - Write(equals_q), - morty.change_mode, "confused", - morty.look_at, lim - ) - self.wait(2) - self.play( - func_1_group.to_corner, UP+LEFT, - *list(map(FadeOut, [morty, ed_group])) - ) - self.wait() - - self.lim_group = VGroup(lim, self.func_label, equals_q) - for part in self.lim_group: - part.add_background_rectangle() - self.func_1_group = func_1_group - self.v_line = v_line - - def plug_in_value_close_to_one(self): - num = 1.00001 - result = self.func(num) - label = self.get_func_label(num) - label.add_background_rectangle() - rhs = TexMobject("= %.4f\\dots"%result) - rhs.next_to(label, RIGHT) - approx_group = VGroup(label, rhs) - approx_group.set_width(FRAME_X_RADIUS-2*MED_LARGE_BUFF) - approx_group.next_to(ORIGIN, UP, buff = MED_LARGE_BUFF) - approx_group.to_edge(RIGHT) - - self.play(ReplacementTransform( - self.func_label.copy(), - label - )) - self.wait() - self.play(Write(rhs)) - self.wait() - - self.approx_group = approx_group - - def ask_about_systematic_process(self): - morty = self.pi_creature - morty.change_mode("plain") - - self.func_1_group.save_state() - to_fade = VGroup( - *self.x_axis.numbers[:len(self.x_axis.numbers)/2] - ) - - self.play( - FadeIn(morty), - FadeOut(to_fade) - ) - self.x_axis.remove(*to_fade) - - self.pi_creature_says( - morty, "Is there a \\\\ better way?", - bubble_kwargs = { - "height" : 3, - "width" : 4, - }, - ) - self.wait(2) - self.play( - RemovePiCreatureBubble( - morty, target_mode = "raise_left_hand", - look_at_arg = self.func_1_group - ), - self.func_1_group.scale, self.tex_scale_value, - self.func_1_group.move_to, - morty.get_corner(UP+LEFT), DOWN+LEFT, - self.func_1_group.shift, MED_LARGE_BUFF*UP, - ) - self.wait(2) - self.play( - morty.change_mode, "raise_right_hand", - morty.look, UP+RIGHT, - FadeOut(self.approx_group), - self.func_1_group.restore, - self.lim_group.next_to, - morty.get_corner(UP+RIGHT), RIGHT, - ) - self.wait(2) - self.play( - FadeOut(self.func_1_group), - self.lim_group.scale, self.tex_scale_value, - self.lim_group.to_corner, UP+LEFT, - # self.lim_group.next_to, ORIGIN, UP, MED_LARGE_BUFF, - # self.lim_group.to_edge, LEFT, - morty.change_mode, "plain" - ) - self.wait() - self.play(FadeOut(morty)) - - def show_graph_of_numerator_and_denominator(self): - sine_graph = self.get_graph( - lambda x : np.sin(np.pi*x), - color = self.sin_color - ) - sine_label = self.get_graph_label( - sine_graph, "\\sin(\\pi x)", - x_val = 4.5, - direction = UP - ) - parabola = self.get_graph( - lambda x : x**2 - 1, - color = self.parabola_color - ) - parabola_label = self.get_graph_label( - parabola, "x^2 - 1" - ) - fader = VGroup(*[ - Rectangle( - width = FRAME_WIDTH, - height = FRAME_HEIGHT, - stroke_width = 0, - fill_opacity = 0.75, - fill_color = BLACK, - ).next_to(self.coords_to_point(1, 0), vect, MED_LARGE_BUFF) - for vect in (LEFT, RIGHT) - ]) - - self.play( - ShowCreation(sine_graph, run_time = 2), - Animation(self.lim_group) - ) - self.play(FadeIn(sine_label)) - self.wait() - self.play(ShowCreation(parabola, run_time = 2)) - self.play(FadeIn(parabola_label)) - self.wait() - self.play(FadeIn(fader, run_time = 2)) - self.wait() - self.play(FadeOut(fader)) - - self.sine_graph = sine_graph - self.sine_label = sine_label - self.parabola = parabola - self.parabola_label = parabola_label - - def zoom_in_to_trouble_point(self): - self.activate_zooming() - lil_rect = self.little_rectangle - lil_rect.scale(self.zoom_factor) - lil_rect.move_to(self.coords_to_point( - *self.zoomed_rect_center_coords - )) - self.wait() - self.play(lil_rect.scale_in_place, 1./self.zoom_factor) - self.wait() - - def talk_through_sizes_of_nudges(self): - arrow_tip_length = 0.15/self.zoom_factor - zoom_tex_scale_factor = min( - 0.75/self.zoom_factor, - 1.5*self.dx - ) - - dx_arrow = Arrow( - self.coords_to_point(1, 0), - self.coords_to_point(1+self.dx, 0), - tip_length = arrow_tip_length, - color = WHITE, - ) - dx_label = TexMobject("dx") - dx_label.scale(zoom_tex_scale_factor) - dx_label.next_to(dx_arrow, UP, buff = SMALL_BUFF/self.zoom_factor) - - d_sine_arrow, d_parabola_arrow = [ - Arrow( - self.coords_to_point(1+self.dx, 0), - self.coords_to_point( - 1+self.dx, - graph.underlying_function(1+self.dx) - ), - tip_length = arrow_tip_length, - color = graph.get_color() - ) - for graph in (self.sine_graph, self.parabola) - ] - tex_arrow_pairs = [ - [("d\\big(", "\\sin(", "\\pi", "x", ")", "\\big)"), d_sine_arrow], - [("d\\big(", "x", "^2", "-1", "\\big)"), d_parabola_arrow], - [("\\cos(", "\\pi", "x", ")", "\\pi ", "\\, dx"), d_sine_arrow], - [("2", "x", "\\, dx"), d_parabola_arrow], - ] - d_labels = [] - for tex_args, arrow in tex_arrow_pairs: - label = TexMobject(*tex_args) - label.set_color_by_tex("x", self.x_color) - label.set_color_by_tex("dx", self.dx_color) - label.scale(zoom_tex_scale_factor) - label.next_to(arrow, RIGHT, buff = SMALL_BUFF/self.zoom_factor) - d_labels.append(label) - d_sine_label, d_parabola_label, cos_dx, two_x_dx = d_labels - - #Show dx - self.play(ShowCreation(dx_arrow)) - self.play(Write(dx_label)) - self.wait() - - #Show d_sine bump - point = VectorizedPoint(self.coords_to_point(1, 0)) - self.play(ReplacementTransform(point, d_sine_arrow)) - self.wait() - self.play(ReplacementTransform( - VGroup(dx_label[1].copy()), - d_sine_label, - run_time = 2 - )) - self.wait(2) - self.play( - d_sine_label.shift, d_sine_label.get_height()*UP - ) - tex_pair_lists = [ - [ - ("sin", "cos"), - ("pi", "pi"), - ("x", "x"), - (")", ")"), - ], - [ - ("pi", "\\pi "), #Space there is important, though hacky - ], - [ - ("d\\big(", "dx"), - ("\\big)", "dx"), - ] - ] - for tex_pairs in tex_pair_lists: - self.play(*[ - ReplacementTransform( - d_sine_label.get_part_by_tex(t1).copy(), - cos_dx.get_part_by_tex(t2) - ) - for t1, t2 in tex_pairs - ]) - self.wait() - self.play(FadeOut(d_sine_label)) - self.wait() - - #Substitute x = 1 - self.substitute_x_equals_1(cos_dx, zoom_tex_scale_factor) - - #Proportionality constant - cos_pi = VGroup(*cos_dx[:-1]) - cos = VGroup(*cos_dx[:-2]) - brace = Brace(Line(LEFT, RIGHT), UP) - brace.set_width(cos_pi.get_width()) - brace.move_to(cos_pi.get_top(), DOWN) - brace_text = TextMobject( - """ - \\begin{flushleft} - Proportionality - constant - \\end{flushleft} - """ - ) - brace_text.scale(0.9*zoom_tex_scale_factor) - brace_text.add_background_rectangle() - brace_text.next_to(brace, UP, SMALL_BUFF/self.zoom_factor, LEFT) - neg_one = TexMobject("-", "1") - neg_one.add_background_rectangle() - neg_one.scale(zoom_tex_scale_factor) - - self.play(GrowFromCenter(brace)) - self.play(Write(brace_text)) - self.wait(2) - self.play( - brace.set_width, cos.get_width(), - brace.next_to, cos, UP, SMALL_BUFF/self.zoom_factor, - FadeOut(brace_text) - ) - neg_one.next_to(brace, UP, SMALL_BUFF/self.zoom_factor) - self.play(Write(neg_one)) - self.wait() - self.play(FadeOut(cos)) - neg = neg_one.get_part_by_tex("-").copy() - self.play(neg.next_to, cos_dx[-2], LEFT, SMALL_BUFF/self.zoom_factor) - self.play(*list(map(FadeOut, [neg_one, brace]))) - neg_pi_dx = VGroup(neg, *cos_dx[-2:]) - self.play( - neg_pi_dx.next_to, d_sine_arrow, - RIGHT, SMALL_BUFF/self.zoom_factor - ) - self.wait() - - #Show d_parabola bump - point = VectorizedPoint(self.coords_to_point(1, 0)) - self.play(ReplacementTransform(point, d_parabola_arrow)) - self.play(ReplacementTransform( - VGroup(dx_label[1].copy()), - d_parabola_label, - run_time = 2 - )) - self.wait(2) - self.play( - d_parabola_label.shift, d_parabola_label.get_height()*UP - ) - tex_pair_lists = [ - [ - ("2", "2"), - ("x", "x"), - ], - [ - ("d\\big(", "dx"), - ("\\big)", "dx"), - ] - ] - for tex_pairs in tex_pair_lists: - self.play(*[ - ReplacementTransform( - d_parabola_label.get_part_by_tex(t1).copy(), - two_x_dx.get_part_by_tex(t2) - ) - for t1, t2 in tex_pairs - ]) - self.wait() - self.play(FadeOut(d_parabola_label)) - self.wait() - - #Substitute x = 1 - self.substitute_x_equals_1(two_x_dx, zoom_tex_scale_factor) - - def substitute_x_equals_1(self, tex_mob, zoom_tex_scale_factor): - x = tex_mob.get_part_by_tex("x") - equation = TexMobject("x", "=", "1") - eq_x, equals, one = equation - equation.scale(zoom_tex_scale_factor) - equation.next_to( - x, UP, - buff = MED_SMALL_BUFF/self.zoom_factor, - aligned_edge = LEFT - ) - equation.set_color_by_tex("x", self.x_color) - equation.set_color_by_tex("1", self.x_color) - dot_one = TexMobject("\\cdot", "1") - dot_one.scale(zoom_tex_scale_factor) - dot_one.set_color(self.x_color) - dot_one.move_to(x, DOWN+LEFT) - - self.play(x.move_to, eq_x) - self.wait() - self.play( - ReplacementTransform(x.copy(), eq_x), - Transform(x, one), - Write(equals) - ) - self.wait() - self.play(Transform(x, dot_one)) - self.wait() - self.play(*list(map(FadeOut, [eq_x, equals]))) - self.wait() - - def show_final_ratio(self): - lim, ratio, equals_q = self.lim_group - self.remove(self.lim_group) - self.add(*self.lim_group) - numerator = VGroup(*ratio[1][:3]) - denominator = VGroup(*ratio[1][-2:]) - rhs = TexMobject( - "\\approx", - "{-\\pi", "\\, dx", "\\over \\,", "2", "\\, dx}" - ) - rhs.add_background_rectangle() - rhs.move_to(equals_q, LEFT) - equals = TexMobject("=") - approx = rhs.get_part_by_tex("approx") - equals.move_to(approx) - - dxs = VGroup(*rhs.get_parts_by_tex("dx")) - circles = VGroup(*[ - Circle(color = GREEN).replace(dx).scale_in_place(1.3) - for dx in dxs - ]) - - #Show numerator and denominator - self.play(FocusOn(ratio)) - for mob in numerator, denominator: - self.play(ApplyWave( - mob, direction = UP+RIGHT, amplitude = 0.1 - )) - self.wait() - self.play(ReplacementTransform(equals_q, rhs)) - self.wait() - - #Cancel dx's - self.play(*list(map(ShowCreation, circles)), run_time = 2) - self.wait() - self.play(dxs.fade, 0.75, FadeOut(circles)) - self.wait() - - #Shrink dx - self.transition_to_alt_config( - transformation_kwargs = {"run_time" : 2}, - dx = self.dx/10 - ) - self.wait() - self.play(Transform(approx, equals)) - self.play(Indicate(approx)) - self.wait() - - self.final_ratio = rhs - - def show_final_height(self): - brace = Brace(self.v_line, LEFT) - height = brace.get_text("$\\dfrac{-\\pi}{2}$") - height.add_background_rectangle() - - self.disactivate_zooming() - self.play(*list(map(FadeOut, [ - self.sine_graph, self.sine_label, - self.parabola, self.parabola_label, - ])) + [ - Animation(self.final_ratio) - ]) - self.play(GrowFromCenter(brace)) - self.play(Write(height)) - self.wait(2) - - ## - - def create_pi_creature(self): - self.pi_creature = Mortimer().flip().to_corner(DOWN+LEFT) - return self.pi_creature - - def func(self, x): - if abs(x) != 1: - return np.sin(x*np.pi) / (x**2 - 1) - else: - return np.pi*np.cos(x*np.pi) / (2*x) - - def get_func_label(self, argument = "x"): - in_tex = "{%s}"%str(argument) - result = TexMobject( - "{\\sin(\\pi ", in_tex, ")", " \\over \\,", - in_tex, "^2 - 1}" - ) - result.set_color_by_tex(in_tex, self.x_color) - return result - - def get_epsilon_delta_group(self, delta, **kwargs): - ed_group = GraphLimitExpression.get_epsilon_delta_group(self, delta, **kwargs) - color = ed_group.kwargs["input_range_color"] - radius = min(delta/2, self.hole_radius) - pairs = [ - # (ed_group.input_range[0], (1, 0)), - (ed_group.input_range[1], (1, 0)), - # (ed_group.output_range[0], (1, self.func(1))), - (ed_group.output_range[1], (1, self.func(1))), - ] - for mob, coords in pairs: - mob.add(self.get_hole( - *coords, - color = color, - radius = radius - )) - return ed_group - -class DerivativeLimitReciprocity(Scene): - def construct(self): - arrow = Arrow(LEFT, RIGHT, color = WHITE) - lim = TexMobject("\\lim", "_{h", "\\to 0}") - lim.set_color_by_tex("h", GREEN) - lim.next_to(arrow, LEFT) - deriv = TexMobject("{df", "\\over\\,", "dx}") - deriv.set_color_by_tex("dx", GREEN) - deriv.set_color_by_tex("df", YELLOW) - deriv.next_to(arrow, RIGHT) - - self.play(FadeIn(lim, lag_ratio = 0.5)) - self.play(ShowCreation(arrow)) - self.play(FadeIn(deriv, lag_ratio = 0.5)) - self.wait() - self.play(Rotate(arrow, np.pi, run_time = 2)) - self.wait() - -class GeneralLHoptial(LHopitalExample): - CONFIG = { - "f_color" : BLUE, - "g_color" : YELLOW, - "a_value" : 2.5, - "zoomed_rect_center_coords" : (2.55, 0), - "zoom_factor" : 15, - "image_height" : 3, - } - def construct(self): - self.setup_axes() - self.add_graphs() - self.zoom_in() - self.show_limit_in_symbols() - self.show_tiny_nudge() - self.show_derivative_ratio() - self.show_example() - self.show_bernoulli_and_lHopital() - - def add_graphs(self): - f_graph = self.get_graph(self.f, self.f_color) - f_label = self.get_graph_label( - f_graph, "f(x)", - x_val = 3, - direction = RIGHT - ) - g_graph = ParametricFunction( - lambda y : self.coords_to_point(np.exp(y)+self.a_value-1, y), - t_min = self.y_min, - t_max = self.y_max, - color = self.g_color - ) - g_graph.underlying_function = self.g - g_label = self.get_graph_label( - g_graph, "g(x)", x_val = 4, direction = UP - ) - - a_dot = Dot(self.coords_to_point(self.a_value, 0)) - a_label = TexMobject("x = a") - a_label.next_to(a_dot, UP, LARGE_BUFF) - a_arrow = Arrow(a_label.get_bottom(), a_dot, buff = SMALL_BUFF) - VGroup(a_dot, a_label, a_arrow).set_color(self.x_color) - - self.play(ShowCreation(f_graph), Write(f_label)) - self.play(ShowCreation(g_graph), Write(g_label)) - self.wait() - - self.play( - Write(a_label), - ShowCreation(a_arrow), - ShowCreation(a_dot), - ) - self.wait() - self.play(*list(map(FadeOut, [a_label, a_arrow]))) - - self.a_dot = a_dot - self.f_graph = f_graph - self.f_label = f_label - self.g_graph = g_graph - self.g_label = g_label - - def zoom_in(self): - self.activate_zooming() - lil_rect = self.little_rectangle - lil_rect.scale(self.zoom_factor) - lil_rect.move_to(self.coords_to_point( - *self.zoomed_rect_center_coords - )) - self.wait() - self.play( - lil_rect.scale_in_place, 1./self.zoom_factor, - self.a_dot.scale_in_place, 1./self.zoom_factor, - run_time = 3, - ) - self.wait() - - def show_limit_in_symbols(self): - frac_a = self.get_frac("a", self.x_color) - frac_x = self.get_frac("x") - lim = TexMobject("\\lim", "_{x", "\\to", "a}") - lim.set_color_by_tex("a", self.x_color) - equals_zero_over_zero = TexMobject( - "=", "{\\, 0 \\,", "\\over \\,", "0 \\,}" - ) - equals_q = TexMobject(*"=???") - frac_x.next_to(lim, RIGHT, SMALL_BUFF) - VGroup(lim, frac_x).to_corner(UP+LEFT) - frac_a.move_to(frac_x) - equals_zero_over_zero.next_to(frac_a, RIGHT) - equals_q.next_to(frac_a, RIGHT) - - self.play( - ReplacementTransform( - VGroup(*self.f_label).copy(), - VGroup(frac_a.numerator) - ), - ReplacementTransform( - VGroup(*self.g_label).copy(), - VGroup(frac_a.denominator) - ), - Write(frac_a.over), - run_time = 2 - ) - self.wait() - self.play(Write(equals_zero_over_zero)) - self.wait(2) - self.play( - ReplacementTransform( - VGroup(*frac_a.get_parts_by_tex("a")), - VGroup(lim.get_part_by_tex("a")) - ) - ) - self.play(Write(VGroup(*lim[:-1]))) - self.play(ReplacementTransform( - VGroup(*lim.get_parts_by_tex("x")).copy(), - VGroup(*frac_x.get_parts_by_tex("x")) - )) - self.play(ReplacementTransform( - equals_zero_over_zero, equals_q - )) - self.wait() - - self.remove(frac_a) - self.add(frac_x) - self.frac_x = frac_x - self.remove(equals_q) - self.add(*equals_q) - self.equals_q = equals_q - - def show_tiny_nudge(self): - arrow_tip_length = 0.15/self.zoom_factor - zoom_tex_scale_factor = min( - 0.75/self.zoom_factor, - 1.5*self.dx - ) - z_small_buff = SMALL_BUFF/self.zoom_factor - - dx_arrow = Arrow( - self.coords_to_point(self.a_value, 0), - self.coords_to_point(self.a_value+self.dx, 0), - tip_length = arrow_tip_length, - color = WHITE, - ) - dx_label = TexMobject("dx") - dx_label.scale(zoom_tex_scale_factor) - dx_label.next_to(dx_arrow, UP, buff = z_small_buff) - dx_label.shift(z_small_buff*RIGHT) - - df_arrow, dg_arrow = [ - Arrow( - self.coords_to_point(self.a_value+self.dx, 0), - self.coords_to_point( - self.a_value+self.dx, - graph.underlying_function(self.a_value+self.dx) - ), - tip_length = arrow_tip_length, - color = graph.get_color() - ) - for graph in (self.f_graph, self.g_graph) - ] - v_labels = [] - for char, arrow in ("f", df_arrow), ("g", dg_arrow): - label = TexMobject( - "\\frac{d%s}{dx}"%char, "(", "a", ")", "\\,dx" - ) - label.scale(zoom_tex_scale_factor) - label.set_color_by_tex("a", self.x_color) - label.set_color_by_tex("frac", arrow.get_color()) - label.next_to(arrow, RIGHT, z_small_buff) - v_labels.append(label) - df_label, dg_label = v_labels - - self.play(ShowCreation(dx_arrow)) - self.play(Write(dx_label)) - self.play(Indicate(dx_label)) - self.wait(2) - - self.play(ShowCreation(df_arrow)) - self.play(Write(df_label)) - self.wait() - - self.play(ShowCreation(dg_arrow)) - self.play(Write(dg_label)) - self.wait() - - def show_derivative_ratio(self): - q_marks = VGroup(*self.equals_q[1:]) - - deriv_ratio = TexMobject( - "{ \\frac{df}{dx}", "(", "a", ")", "\\,dx", - "\\over \\,", - "\\frac{dg}{dx}", "(", "a", ")", "\\,dx}", - ) - deriv_ratio.set_color_by_tex("a", self.x_color) - deriv_ratio.set_color_by_tex("df", self.f_color) - deriv_ratio.set_color_by_tex("dg", self.g_color) - deriv_ratio.move_to(q_marks, LEFT) - - dxs = VGroup(*deriv_ratio.get_parts_by_tex("\\,dx")) - circles = VGroup(*[ - Circle(color = GREEN).replace(dx).scale_in_place(1.3) - for dx in dxs - ]) - - self.play(FadeOut(q_marks)) - self.play(Write(deriv_ratio)) - self.wait(2) - - self.play(FadeIn(circles)) - self.wait() - self.play(FadeOut(circles), dxs.fade, 0.75) - self.wait(2) - - self.transition_to_alt_config( - transformation_kwargs = {"run_time" : 2}, - dx = self.dx/10, - ) - self.wait() - - def show_example(self): - lhs = TexMobject( - "\\lim", "_{x \\to", "0}", - "{\\sin(", "x", ")", "\\over \\,", "x}", - ) - rhs = TexMobject( - "=", - "{\\cos(", "0", ")", "\\over \\,", "1}", - "=", "1" - ) - rhs.next_to(lhs, RIGHT) - equation = VGroup(lhs, rhs) - equation.to_corner(UP+RIGHT) - for part in equation: - part.set_color_by_tex("0", self.x_color) - brace = Brace(lhs, DOWN) - brace_text = brace.get_text("Looks like 0/0") - brace_text.add_background_rectangle() - - name = TextMobject( - "``", "L'Hôpital's", " rule", "''", - arg_separator = "" - ) - name.shift(FRAME_X_RADIUS*RIGHT/2) - name.to_edge(UP) - - self.play(Write(lhs)) - self.wait() - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait() - self.play(Write(rhs[0]), ReplacementTransform( - VGroup(*lhs[3:6]).copy(), - VGroup(*rhs[1:4]) - )) - self.wait() - self.play(ReplacementTransform( - VGroup(*lhs[6:8]).copy(), - VGroup(*rhs[4:6]), - )) - self.wait() - self.play(Write(VGroup(*rhs[6:]))) - self.wait(2) - - ##Slide away - example = VGroup(lhs, rhs, brace, brace_text) - self.play( - example.scale, 0.7, - example.to_corner, DOWN+RIGHT, SMALL_BUFF, - path_arc = 7*np.pi/6, - ) - self.play(Write(name)) - self.wait(2) - - self.rule_name = name - - def show_bernoulli_and_lHopital(self): - lhopital_name = self.rule_name.get_part_by_tex("L'Hôpital's") - strike = Line( - lhopital_name.get_left(), - lhopital_name.get_right(), - color = RED - ) - bernoulli_name = TextMobject("Bernoulli's") - bernoulli_name.next_to(lhopital_name, DOWN) - - bernoulli_image = ImageMobject("Johann_Bernoulli2") - lhopital_image = ImageMobject("Guillaume_de_L'Hopital") - for image in bernoulli_image, lhopital_image: - image.set_height(self.image_height) - image.to_edge(UP) - - arrow = Arrow(ORIGIN, DOWN, buff = 0, color = GREEN) - arrow.next_to(lhopital_image, DOWN, buff = SMALL_BUFF) - dollars = VGroup(*[TexMobject("\\$") for x in range(5)]) - for dollar, alpha in zip(dollars, np.linspace(0, 1, len(dollars))): - angle = alpha*np.pi - dollar.move_to(np.sin(angle)*RIGHT + np.cos(angle)*UP) - dollars.set_color(GREEN) - dollars.next_to(arrow, RIGHT, MED_LARGE_BUFF) - dollars[0].set_fill(opacity = 0) - dollars.save_state() - - self.play(ShowCreation(strike)) - self.play( - Write(bernoulli_name), - FadeIn(bernoulli_image) - ) - self.wait() - self.play( - FadeIn(lhopital_image), - bernoulli_image.next_to, arrow, DOWN, SMALL_BUFF, - ShowCreation(arrow), - FadeIn(dollars) - ) - - for x in range(10): - dollars.restore() - self.play(*[ - Transform(*pair) - for pair in zip(dollars, dollars[1:]) - ] + [ - FadeOut(dollars[-1]) - ]) - - #### - - def f(self, x): - return -0.1*(x-self.a_value)*x*(x+4.5) - - def g(self, x): - return np.log(x-self.a_value+1) - - def get_frac(self, input_tex, color = WHITE): - result = TexMobject( - "{f", "(", input_tex, ")", "\\over \\,", - "g", "(", input_tex, ")}" - ) - result.set_color_by_tex("f", self.f_color) - result.set_color_by_tex("g", self.g_color) - result.set_color_by_tex(input_tex, color) - - result.numerator = VGroup(*result[:4]) - result.denominator = VGroup(*result[-4:]) - result.over = result.get_part_by_tex("over") - - return result - -class CannotUseLHopital(TeacherStudentsScene): - def construct(self): - deriv = TexMobject( - "{d(e^x)", "\\over \\,", "dx}", "(", "x", ")", "=", - "\\lim", "_{h", "\\to 0}", - "{e^{", "x", "+", "h}", - "-", "e^", "x", - "\\over \\,", "h}" - ) - deriv.to_edge(UP) - deriv.set_color_by_tex("x", RED) - deriv.set_color_by_tex("dx", GREEN) - deriv.set_color_by_tex("h", GREEN) - deriv.set_color_by_tex("e^", YELLOW) - - self.play( - Write(deriv), - *it.chain(*[ - [pi.change_mode, "pondering", pi.look_at, deriv] - for pi in self.get_pi_creatures() - ]) - ) - self.wait() - self.student_says( - "Use L'Hôpital's rule!", - target_mode = "hooray" - ) - self.wait() - answer = TexMobject( - "\\text{That requires knowing }", - "{d(e^x)", "\\over \\,", "dx}" - ) - answer.set_color_by_tex("e^", YELLOW) - answer.set_color_by_tex("dx", GREEN) - self.teacher_says( - answer, - bubble_kwargs = {"height" : 2.5}, - target_mode = "hesitant" - ) - self.change_student_modes(*["confused"]*3) - self.wait(3) - -class NextVideo(TeacherStudentsScene): - def construct(self): - series = VideoSeries() - series.to_edge(UP) - next_video = series[7] - brace = Brace(next_video, DOWN) - - integral = TexMobject("\\int", "f(x)", "dx") - ftc = TexMobject( - "F(b)", "-", "F(a)", "=", "\\int_a^b", - "{dF", "\\over \\,", "dx}", "(x)", "dx" - ) - for tex_mob in integral, ftc: - tex_mob.set_color_by_tex("dx", GREEN) - tex_mob.set_color_by_tex("f", YELLOW) - tex_mob.set_color_by_tex("F", YELLOW) - tex_mob.next_to(brace, DOWN) - - self.add(series) - self.play( - GrowFromCenter(brace), - next_video.set_color, YELLOW, - self.get_teacher().change_mode, "raise_right_hand", - self.get_teacher().look_at, next_video - ) - self.play(Write(integral)) - self.wait(2) - self.play(*[ - ReplacementTransform( - VGroup(*integral.get_parts_by_tex(p1)), - VGroup(*ftc.get_parts_by_tex(p2)), - run_time = 2, - path_arc = np.pi/2, - rate_func = squish_rate_func(smooth, alpha, alpha+0.5) - ) - for alpha, (p1, p2) in zip(np.linspace(0, 0.5, 3), [ - ("int", "int"), - ("f", "F"), - ("dx", "dx"), - ]) - ]+[ - Write(VGroup(*ftc.get_parts_by_tex(part))) - for part in ("-", "=", "over", "(x)") - ]) - self.change_student_modes(*["pondering"]*3) - self.wait(3) - -class Chapter7PatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "Meshal Alshammari", - "CrypticSwarm ", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Nathan Pellegrin", - "Karan Bhargava", - "Ankit Agarwal", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Justin Helps", - "Markus Persson", - "Dan Buchoff", - "Derek Dai", - "Joseph John Cox", - "Luc Ritchie", - "Daan Smedinga", - "Jonathan Eppele", - "Albert Nguyen", - "Nils Schneider", - "Mustafa Mahdi", - "Mathew Bramson", - "Guido Gambardella", - "Jerry Ling", - "Mark Govea", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - "Felipe Diniz", - ] - } - -class Thumbnail(Scene): - def construct(self): - lim = TexMobject("\\lim", "_{h", "\\to 0}") - lim.set_color_by_tex("h", GREEN) - lim.set_height(5) - self.add(lim) - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eoc/chapter8.py b/from_3b1b/old/eoc/chapter8.py deleted file mode 100644 index b08fce49..00000000 --- a/from_3b1b/old/eoc/chapter8.py +++ /dev/null @@ -1,2774 +0,0 @@ -import scipy -from manimlib.imports import * -from from_3b1b.old.eoc.chapter1 import Thumbnail as Chapter1Thumbnail -from from_3b1b.old.eoc.chapter2 import Car, MoveCar, ShowSpeedometer, \ - IncrementNumber, GraphCarTrajectory, SecantLineToTangentLine, \ - VELOCITY_COLOR, TIME_COLOR, DISTANCE_COLOR - -def v_rate_func(t): - return 4*t - 4*(t**2) - -def s_rate_func(t): - return 3*(t**2) - 2*(t**3) - -def v_func(t): - return t*(8-t) - -def s_func(t): - return 4*t**2 - (t**3)/3. - - -class Chapter8OpeningQuote(OpeningQuote, PiCreatureScene): - CONFIG = { - "quote" : [ - " One should never try to prove anything that \\\\ is not ", - "almost obvious", ". " - ], - "quote_arg_separator" : "", - "highlighted_quote_terms" : { - "almost obvious" : BLUE, - }, - "author" : "Alexander Grothendieck" - } - def construct(self): - self.remove(self.pi_creature) - OpeningQuote.construct(self) - - words_copy = self.quote.get_part_by_tex("obvious").copy() - author = self.author - author.save_state() - formula = self.get_formula() - formula.next_to(author, DOWN, MED_LARGE_BUFF) - formula.to_edge(LEFT) - - self.revert_to_original_skipping_status() - self.play(FadeIn(self.pi_creature)) - self.play( - author.next_to, self.pi_creature.get_corner(UP+LEFT), UP, - self.pi_creature.change_mode, "raise_right_hand" - ) - self.wait(3) - self.play( - author.restore, - self.pi_creature.change_mode, "plain" - ) - self.play( - words_copy.next_to, self.pi_creature, - LEFT, MED_SMALL_BUFF, UP, - self.pi_creature.change_mode, "thinking" - ) - self.wait(2) - self.play( - Write(formula), - self.pi_creature.change_mode, "confused" - ) - self.wait() - - def get_formula(self): - result = TexMobject( - "{d(\\sin(\\theta)) \\over \\,", "d\\theta}", "=", - "\\lim_{", "h", " \\to 0}", - "{\\sin(\\theta+", "h", ") - \\sin(\\theta) \\over", " h}", "=", - "\\lim_{", "h", " \\to 0}", - "{\\big[ \\sin(\\theta)\\cos(", "h", ") + ", - "\\sin(", "h", ")\\cos(\\theta)\\big] - \\sin(\\theta) \\over", "h}", - "= \\dots" - ) - result.set_color_by_tex("h", GREEN, substring = False) - result.set_color_by_tex("d\\theta", GREEN) - - result.set_width(FRAME_WIDTH - 2*MED_SMALL_BUFF) - return result - -class ThisVideo(TeacherStudentsScene): - def construct(self): - series = VideoSeries() - series.to_edge(UP) - this_video = series[7] - this_video.save_state() - next_video = series[8] - - deriv, integral, v_t, dt, equals, v_T = formula = TexMobject( - "\\frac{d}{dT}", - "\\int_0^T", "v(t)", "\\,dt", - "=", "v(T)" - ) - formula.set_color_by_tex("v", VELOCITY_COLOR) - formula.next_to(self.teacher.get_corner(UP+LEFT), UP, MED_LARGE_BUFF) - - self.play(FadeIn(series, lag_ratio = 0.5)) - self.play( - this_video.shift, this_video.get_height()*DOWN/2, - this_video.set_color, YELLOW, - self.teacher.change_mode, "raise_right_hand", - ) - self.play(Write(VGroup(integral, v_t, dt))) - self.change_student_modes(*["erm"]*3) - self.wait() - self.play(Write(VGroup(deriv, equals, v_T)), ) - self.change_student_modes(*["confused"]*3) - self.wait(3) - self.play( - this_video.restore, - next_video.shift, next_video.get_height()*DOWN/2, - next_video.set_color, YELLOW, - integral[0].copy().next_to, next_video, DOWN, MED_LARGE_BUFF, - FadeOut(formula), - *it.chain(*[ - [pi.change_mode, "plain", pi.look_at, next_video] - for pi in self.pi_creatures - ]) - ) - self.wait(2) - -class InCarRestrictedView(ShowSpeedometer): - CONFIG = { - "speedometer_title_text" : "Your view", - } - def construct(self): - car = Car() - car.move_to(self.point_A) - self.car = car - car.randy.save_state() - Transform(car.randy, Randolph()).update(1) - car.randy.next_to(car, RIGHT, MED_LARGE_BUFF) - car.randy.look_at(car) - - window = car[1][6].copy() - window.is_subpath = False - window.set_fill(BLACK, opacity = 0.75) - window.set_stroke(width = 0) - - square = Square(stroke_color = WHITE) - square.replace(VGroup(self.speedometer, self.speedometer_title)) - square.scale_in_place(1.5) - square.pointwise_become_partial(square, 0.25, 0.75) - - time_label = TextMobject("Time (in seconds):", "0") - time_label.shift(2*UP) - - dots = VGroup(*list(map(Dot, [self.point_A, self.point_B]))) - line = Line(*dots, buff = 0) - line.set_color(DISTANCE_COLOR) - brace = Brace(line, DOWN) - brace_text = brace.get_text("Distance traveled?") - - - #Sit in car - self.add(car) - self.play(Blink(car.randy)) - self.play(car.randy.restore, Animation(car)) - self.play(ShowCreation(window, run_time = 2)) - self.wait() - - #Show speedometer - self.introduce_added_mobjects() - self.play(ShowCreation(square)) - self.wait() - - #Travel - self.play(FadeIn(time_label)) - self.play( - MoveCar(car, self.point_B, rate_func = s_rate_func), - IncrementNumber(time_label[1], run_time = 8), - MaintainPositionRelativeTo(window, car), - *self.get_added_movement_anims( - rate_func = v_rate_func, - radians = -(16.0/70)*4*np.pi/3 - ), - run_time = 8 - ) - eight = TexMobject("8").move_to(time_label[1]) - self.play(Transform( - time_label[1], eight, - rate_func = squish_rate_func(smooth, 0, 0.5) - )) - self.wait() - - #Ask about distance - self.play(*list(map(ShowCreation, dots))) - self.play(ShowCreation(line)) - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait(2) - -class GraphDistanceVsTime(GraphCarTrajectory): - CONFIG = { - "y_min" : 0, - "y_max" : 100, - "y_axis_height" : 6, - "y_tick_frequency" : 10, - "y_labeled_nums" : list(range(10, 100, 10)), - "y_axis_label" : "Distance (in meters)", - "x_min" : -1, - "x_max" : 9, - "x_axis_width" : 9, - "x_tick_frequency" : 1, - "x_leftmost_tick" : None, #Change if different from x_min - "x_labeled_nums" : list(range(1, 9)), - "x_axis_label" : "$t$", - "time_of_journey" : 8, - "care_movement_rate_func" : s_rate_func, - "num_graph_anchor_points" : 100 - } - def construct(self): - self.setup_axes() - graph = self.get_graph( - s_func, - color = DISTANCE_COLOR, - x_min = 0, - x_max = 8, - ) - origin = self.coords_to_point(0, 0) - graph_label = self.get_graph_label( - graph, "s(t)", color = DISTANCE_COLOR - ) - self.introduce_graph(graph, origin) - -class PlotVelocity(GraphScene): - CONFIG = { - "x_min" : -1, - "x_max" : 9, - "x_axis_width" : 9, - "x_tick_frequency" : 1, - "x_labeled_nums" : list(range(1, 9)), - "x_axis_label" : "$t$", - "y_min" : 0, - "y_max" : 25, - "y_axis_height" : 6, - "y_tick_frequency" : 5, - "y_labeled_nums" : list(range(5, 30, 5)), - "y_axis_label" : "Velocity in $\\frac{\\text{meters}}{\\text{second}}$", - "num_graph_anchor_points" : 50, - } - def construct(self): - self.setup_axes() - self.add_speedometer() - self.plot_points() - self.draw_curve() - - def add_speedometer(self): - speedometer = Speedometer() - speedometer.next_to(self.y_axis_label_mob, RIGHT, LARGE_BUFF) - speedometer.to_edge(UP) - - self.play(DrawBorderThenFill( - speedometer, - lag_ratio = 0.5, - rate_func=linear, - )) - - self.speedometer = speedometer - - def plot_points(self): - times = list(range(0, 9)) - points = [ - self.coords_to_point(t, v_func(t)) - for t in times - ] - dots = VGroup(*[Dot(p, radius = 0.07) for p in points]) - dots.set_color(VELOCITY_COLOR) - - pre_dots = VGroup() - dot_intro_anims = [] - - for time, dot in zip(times, dots): - pre_dot = dot.copy() - self.speedometer.move_needle_to_velocity(v_func(time)) - pre_dot.move_to(self.speedometer.get_needle_tip()) - pre_dot.set_fill(opacity = 0) - pre_dots.add(pre_dot) - dot_intro_anims += [ - ApplyMethod( - pre_dot.set_fill, YELLOW, 1, - run_time = 0.1, - ), - ReplacementTransform( - pre_dot, dot, - run_time = 0.9, - ) - ] - self.speedometer.move_needle_to_velocity(0) - - self.play( - Succession( - *dot_intro_anims, rate_func=linear - ), - ApplyMethod( - self.speedometer.move_needle_to_velocity, - v_func(4), - rate_func = squish_rate_func( - lambda t : 1-v_rate_func(t), - 0, 0.95, - ) - ), - run_time = 5 - ) - self.wait() - - def draw_curve(self): - graph, label = self.get_v_graph_and_label() - - self.revert_to_original_skipping_status() - self.play(ShowCreation(graph, run_time = 3)) - self.play(Write(graph_label)) - self.wait() - - ## - - def get_v_graph_and_label(self): - graph = self.get_graph( - v_func, - x_min = 0, - x_max = 8, - color = VELOCITY_COLOR - ) - graph_label = TexMobject("v(t)", "=t(8-t)") - graph_label.set_color_by_tex("v(t)", VELOCITY_COLOR) - graph_label.next_to( - graph.point_from_proportion(7./8.), - UP+RIGHT - ) - self.v_graph = graph - self.v_graph_label = graph_label - return graph, graph_label - -class Chapter2Wrapper(Scene): - CONFIG = { - "title" : "Chapter 2: The paradox of the derivative", - } - def construct(self): - title = TextMobject(self.title) - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = WHITE) - rect.set_height(1.5*FRAME_Y_RADIUS) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait(3) - -class GivenDistanceWhatIsVelocity(GraphCarTrajectory): - def construct(self): - self.force_skipping() - self.setup_axes() - graph = self.graph_sigmoid_trajectory_function() - origin = self.coords_to_point(0, 0) - - self.introduce_graph(graph, origin) - self.comment_on_slope(graph, origin) - self.revert_to_original_skipping_status() - self.show_velocity_graph() - -class DerivativeOfDistance(SecantLineToTangentLine): - def construct(self): - self.setup_axes() - self.remove(self.y_axis_label_mob, self.x_axis_label_mob) - self.add_derivative_definition(self.y_axis_label_mob) - self.add_graph() - self.draw_axes() - self.show_tangent_line() - -class AskAboutAntiderivative(PlotVelocity): - def construct(self): - self.setup_axes() - self.add_v_graph() - self.write_s_formula() - self.write_antiderivative() - - - def add_v_graph(self): - graph, label = self.get_v_graph_and_label() - self.play(ShowCreation(graph)) - self.play(Write(label)) - - self.graph = graph - self.graph_label = label - - def write_s_formula(self): - ds_dt = TexMobject("ds", "\\over\\,", "dt") - ds_dt.set_color_by_tex("ds", DISTANCE_COLOR) - ds_dt.set_color_by_tex("dt", TIME_COLOR) - ds_dt.next_to(self.graph_label, UP, LARGE_BUFF) - - v_t = self.graph_label.get_part_by_tex("v(t)") - arrow = Arrow( - ds_dt.get_bottom(), v_t.get_top(), - color = WHITE, - ) - - self.play( - Write(ds_dt, run_time = 2), - ShowCreation(arrow) - ) - self.wait() - - def write_antiderivative(self): - randy = Randolph() - randy.to_corner(DOWN+LEFT) - randy.shift(2*RIGHT) - words = TexMobject( - "{d(", "???", ") \\over \\,", "dt}", "=", "t(8-t)" - ) - words.set_color_by_tex("t(8-t)", VELOCITY_COLOR) - words.set_color_by_tex("???", DISTANCE_COLOR) - words.set_color_by_tex("dt", TIME_COLOR) - words.scale(0.7) - - self.play(FadeIn(randy)) - self.play(PiCreatureSays( - randy, words, - target_mode = "confused", - bubble_kwargs = {"height" : 3, "width" : 4}, - )) - self.play(Blink(randy)) - self.wait() - -class Antiderivative(PiCreatureScene): - def construct(self): - functions = self.get_functions("t^2", "2t") - alt_functions = self.get_functions("???", "t(8-t)") - top_arc, bottom_arc = arcs = self.get_arcs(functions) - derivative, antiderivative = self.get_arc_labels(arcs) - group = VGroup(functions, arcs, derivative, antiderivative) - - self.add(functions, top_arc, derivative) - self.wait() - self.play( - ShowCreation(bottom_arc), - Write(antiderivative), - self.pi_creature.change_mode, "raise_right_hand" - ) - self.wait(2) - for pair in reversed(list(zip(functions, alt_functions))): - self.play( - Transform(*pair), - self.pi_creature.change_mode, "pondering" - ) - self.wait(2) - - self.pi_creature_says( - "But first!", - target_mode = "surprised", - look_at_arg = 50*OUT, - added_anims = [group.to_edge, LEFT], - run_time = 1, - ) - self.wait() - - def get_functions(self, left_tex, right_tex): - left = TexMobject(left_tex) - left.shift(2*LEFT) - left.set_color(DISTANCE_COLOR) - right = TexMobject(right_tex) - right.shift(2*RIGHT) - right.set_color(VELOCITY_COLOR) - result = VGroup(left, right) - result.shift(UP) - return result - - def get_arcs(self, functions): - f1, f2 = functions - top_line = Line(f1.get_corner(UP+RIGHT), f2.get_corner(UP+LEFT)) - bottom_line = Line(f1.get_corner(DOWN+RIGHT), f2.get_corner(DOWN+LEFT)) - top_arc = Arc(start_angle = 5*np.pi/6, angle = -2*np.pi/3) - bottom_arc = top_arc.copy() - bottom_arc.rotate(np.pi) - arcs = VGroup(top_arc, bottom_arc) - arcs.set_width(top_line.get_width()) - for arc in arcs: - arc.add_tip() - top_arc.next_to(top_line, UP) - bottom_arc.next_to(bottom_line, DOWN) - bottom_arc.set_color(MAROON_B) - - return arcs - - def get_arc_labels(self, arcs): - top_arc, bottom_arc = arcs - derivative = TextMobject("Derivative") - derivative.next_to(top_arc, UP) - antiderivative = TextMobject("``Antiderivative''") - antiderivative.next_to(bottom_arc, DOWN) - antiderivative.set_color(bottom_arc.get_color()) - - return VGroup(derivative, antiderivative) - -class AreaUnderVGraph(PlotVelocity): - def construct(self): - self.setup_axes() - self.add(*self.get_v_graph_and_label()) - self.show_rects() - - def show_rects(self): - rect_list = self.get_riemann_rectangles_list( - self.v_graph, 7, - max_dx = 1.0, - x_min = 0, - x_max = 8, - ) - flat_graph = self.get_graph(lambda t : 0) - rects = self.get_riemann_rectangles( - flat_graph, x_min = 0, x_max = 8, dx = 1.0 - ) - - for new_rects in rect_list: - new_rects.set_fill(opacity = 0.8) - rects.align_submobjects(new_rects) - for alt_rect in rects[::2]: - alt_rect.set_fill(opacity = 0) - self.play(Transform( - rects, new_rects, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait() - -class ConstantVelocityCar(Scene): - def construct(self): - car = Car() - car.move_to(5*LEFT + 3*DOWN) - - self.add(car) - self.wait() - self.play(MoveCar( - car, 7*RIGHT+3*DOWN, - run_time = 5, - rate_func=linear, - )) - self.wait() - -class ConstantVelocityPlot(PlotVelocity): - CONFIG = { - "x_axis_label" : "Time", - "units_of_area_color" : BLUE_E, - } - def construct(self): - self.setup_axes() - self.x_axis_label_mob.shift(DOWN) - self.draw_graph() - self.show_product() - self.comment_on_area_wierdness() - self.note_units() - - def draw_graph(self): - graph = self.get_graph( - lambda t : 10, - x_min = 0, - x_max = 8, - color = VELOCITY_COLOR - ) - - self.play(ShowCreation(graph, rate_func=linear, run_time = 3)) - self.wait() - - self.graph = graph - - def show_product(self): - rect = Rectangle( - stroke_width = 0, - fill_color = DISTANCE_COLOR, - fill_opacity = 0.5 - ) - rect.replace( - VGroup(self.graph, VectorizedPoint(self.graph_origin)), - stretch = True - ) - - right_brace = Brace(rect, RIGHT) - top_brace = Brace(rect, UP) - v_label = right_brace.get_text( - "$10 \\frac{\\text{meters}}{\\text{second}}$", - ) - v_label.set_color(VELOCITY_COLOR) - t_label = top_brace.get_text( - "8 seconds" - ) - t_label.set_color(TIME_COLOR) - - s_label = TexMobject("10", "\\times", "8", "\\text{ meters}") - s_label.set_color_by_tex("10", VELOCITY_COLOR) - s_label.set_color_by_tex("8", TIME_COLOR) - s_label.move_to(rect) - - self.play( - GrowFromCenter(right_brace), - Write(v_label), - ) - self.play( - GrowFromCenter(top_brace), - Write(t_label), - ) - self.play( - FadeIn(rect), - Write(s_label), - Animation(self.graph) - ) - self.wait(2) - - self.area_rect = rect - self.s_label = s_label - - def comment_on_area_wierdness(self): - randy = Randolph() - randy.to_corner(DOWN+LEFT) - bubble = randy.get_bubble( - "Distance \\\\ is area?", - bubble_class = ThoughtBubble, - height = 3, - width = 4, - fill_opacity = 1, - ) - bubble.content.scale_in_place(0.8) - bubble.content.shift(SMALL_BUFF*UP) - VGroup(bubble[-1], bubble.content).shift(1.5*LEFT) - - self.play(FadeIn(randy)) - self.play(randy.change_mode, "pondering") - self.play( - self.area_rect.set_color, YELLOW, - *list(map(Animation, self.get_mobjects())), - rate_func = there_and_back - ) - self.play(Blink(randy)) - self.play( - randy.change_mode, "confused", - randy.look_at, randy.bubble, - ShowCreation(bubble), - Write(bubble.content), - ) - self.wait() - self.play(Blink(randy)) - self.wait() - self.play( - randy.change_mode, "pondering", - FadeOut(bubble), - FadeOut(bubble.content), - ) - - self.randy = randy - - def note_units(self): - x_line, y_line = lines = VGroup(*[ - axis.copy() - for axis in (self.x_axis, self.y_axis) - ]) - lines.set_color(TIME_COLOR) - square = Square( - stroke_color = BLACK, - stroke_width = 1, - fill_color = self.units_of_area_color, - fill_opacity = 1, - ) - square.replace( - VGroup(*[ - VectorizedPoint(self.coords_to_point(i, i)) - for i in (0, 1) - ]), - stretch = True - ) - units_of_area = VGroup(*[ - square.copy().move_to( - self.coords_to_point(x, y), - DOWN+LEFT - ) - for x in range(8) - for y in range(10) - ]) - - self.play(ShowCreation(x_line)) - self.play(Indicate(self.x_axis_label_mob)) - self.play(FadeOut(x_line)) - self.play( - ShowCreation(y_line), - self.randy.look_at, self.y_axis_label_mob - ) - self.play(Indicate(self.y_axis_label_mob)) - self.play(FadeOut(y_line)) - - for FadeClass in FadeIn, FadeOut: - self.play( - FadeClass( - units_of_area, - lag_ratio = 0.5, - run_time = 3 - ), - Animation(self.s_label), - self.randy.look_at, self.area_rect - ) - self.play(Blink(self.randy)) - self.wait() - -class PiecewiseConstantCar(Scene): - def construct(self): - car = Car() - start_point = 5*LEFT - car.move_to(start_point) - - self.add(car) - self.wait() - for shift in 2, 6, 12: - car.randy.rotate_in_place(np.pi/8) - anim = MoveCar( - car, start_point+shift*RIGHT, - rate_func=linear - ) - - anim.target_mobject[0].rotate_in_place(-np.pi/8) - # for mob in anim.starting_mobject, anim.mobject: - # mob.randy.rotate_in_place(np.pi/6) - self.play(anim) - self.wait() - -class PiecewiseConstantPlot(PlotVelocity): - CONFIG = { - "y_axis_label" : "", - "min_graph_proportion" : 0.1, - "max_graph_proportion" : 0.8, - "num_riemann_approximations" : 7, - "riemann_rect_fill_opacity" : 0.75, - "tick_size" : 0.2, - } - def construct(self): - self.setup_graph() - self.always_changing() - self.show_piecewise_constant_graph() - self.compute_distance_on_each_interval() - self.approximate_original_curve() - self.revert_to_specific_approximation() - self.show_specific_rectangle() - self.show_v_dt_for_all_rectangles() - self.write_integral_symbol() - self.roles_of_dt() - self.what_does_sum_approach() - self.label_integral() - - def setup_graph(self): - self.setup_axes() - self.add(*self.get_v_graph_and_label()) - - def always_changing(self): - dot = Dot() - arrow = Arrow(LEFT, RIGHT) - words = TextMobject("Always changing") - group = VGroup(dot, arrow, words) - def update_group(group, alpha): - dot, arrow, words = group - prop = interpolate( - self.min_graph_proportion, - self.max_graph_proportion, - alpha - ) - graph_point = self.v_graph.point_from_proportion(prop) - dot.move_to(graph_point) - x_val = self.x_axis.point_to_number(graph_point) - angle = self.angle_of_tangent(x_val, self.v_graph) - angle += np.pi/2 - vect = rotate_vector(RIGHT, angle) - arrow.rotate(angle - arrow.get_angle() + np.pi) - arrow.shift( - graph_point + MED_SMALL_BUFF*vect - arrow.get_end() - ) - words.next_to(arrow.get_start(), UP) - return group - update_group(group, 0) - - self.play( - Write(words), - ShowCreation(arrow), - DrawBorderThenFill(dot), - run_time = 1 - ) - self.play(UpdateFromAlphaFunc( - group, update_group, - rate_func = there_and_back, - run_time = 5 - )) - self.wait() - self.play(FadeOut(group)) - - def show_piecewise_constant_graph(self): - pw_constant_graph = self.get_pw_constant_graph() - alt_lines = [ - line.copy().set_color(YELLOW) - for line in pw_constant_graph[:4] - ] - for line in alt_lines: - line.start_dot = Dot(line.get_start()) - line.end_dot = Dot(line.get_end()) - VGroup(line.start_dot, line.end_dot).set_color(line.get_color()) - line = alt_lines[0] - - faders = [self.v_graph, self.v_graph_label] - for mob in faders: - mob.save_state() - mob.generate_target() - mob.target.fade(0.7) - - self.play(*list(map(MoveToTarget, faders))) - self.play(ShowCreation(pw_constant_graph, run_time = 2)) - self.wait() - self.play(ShowCreation(line)) - self.wait() - for new_line in alt_lines[1:]: - for mob in line.end_dot, new_line.start_dot, new_line: - self.play(Transform( - line, mob, - run_time = 1./3 - )) - self.remove(line) - self.add(new_line) - self.wait(2) - line = new_line - self.play(FadeOut(line)) - - self.pw_constant_graph = pw_constant_graph - - def compute_distance_on_each_interval(self): - rect_list = self.get_riemann_rectangles_list( - self.v_graph, self.num_riemann_approximations, - max_dx = 1, - x_min = 0, - x_max = 8, - ) - for rects in rect_list: - rects.set_fill(opacity = self.riemann_rect_fill_opacity) - flat_rects = self.get_riemann_rectangles( - self.get_graph(lambda t : 0), - x_min = 0, x_max = 8, dx = 1 - ) - rects = rect_list[0] - rect = rects[1] - flat_rects.submobjects[1] = rect.copy() - - right_brace = Brace(rect, RIGHT) - top_brace = Brace(rect, UP) - right_brace.label = right_brace.get_text("$7\\frac{\\text{m}}{\\text{s}}$") - top_brace.label = top_brace.get_text("$1$s") - - self.play(FadeIn(rect)) - for brace in right_brace, top_brace: - self.play( - GrowFromCenter(brace), - Write(brace.label, run_time = 1), - ) - brace.add(brace.label) - self.wait() - self.play( - ReplacementTransform( - flat_rects, rects, - run_time = 2, - lag_ratio = 0.5, - ), - Animation(right_brace) - ) - self.play(*list(map(FadeOut, [top_brace, right_brace]))) - self.wait() - - self.rects = rects - self.rect_list = rect_list - - def approximate_original_curve(self): - rects = self.rects - self.play( - FadeOut(self.pw_constant_graph), - *[ - m.restore - for m in (self.v_graph, self.v_graph_label) - ]+[Animation(self.rects)] - ) - for new_rects in self.rect_list[1:]: - self.transform_between_riemann_rects(rects, new_rects) - self.wait() - - def revert_to_specific_approximation(self): - rects = self.rects - rects.save_state() - target_rects = self.rect_list[2] - target_rects.set_fill(opacity = 1) - - ticks = self.get_ticks(target_rects) - tick_pair = VGroup(*ticks[4:6]) - brace = Brace(tick_pair, DOWN, buff = 0) - dt_label = brace.get_text("$dt$", buff = SMALL_BUFF) - - example_text = TextMobject( - "For example, \\\\", - "$dt$", "$=0.25$" - ) - example_text.to_corner(UP+RIGHT) - example_text.set_color_by_tex("dt", YELLOW) - - self.play(ReplacementTransform( - rects, target_rects, - run_time = 2, - lag_ratio = 0.5 - )) - rects.restore() - self.wait() - self.play( - ShowCreation(ticks), - FadeOut(self.x_axis.numbers) - ) - self.play( - GrowFromCenter(brace), - Write(dt_label) - ) - self.wait() - self.play( - FadeIn( - example_text, - run_time = 2, - lag_ratio = 0.5, - ), - ReplacementTransform( - dt_label.copy(), - example_text.get_part_by_tex("dt") - ) - ) - self.wait() - - self.rects = rects = target_rects - self.ticks = ticks - self.dt_brace = brace - self.dt_label = dt_label - self.dt_example_text = example_text - - def show_specific_rectangle(self): - rects = self.rects - rect = rects[4].copy() - rect_top = Line( - rect.get_corner(UP+LEFT), - rect.get_corner(UP+RIGHT), - color = self.v_graph.get_color() - ) - - t_vals = [1, 1.25] - t_labels = VGroup(*[ - TexMobject("t=%s"%str(t)) - for t in t_vals - ]) - t_labels.scale(0.7) - t_labels.next_to(rect, DOWN) - for vect, label in zip([LEFT, RIGHT], t_labels): - label.shift(1.5*vect) - label.add(Arrow( - label.get_edge_center(-vect), - rect.get_corner(DOWN+vect), - buff = SMALL_BUFF, - tip_length = 0.15, - color = WHITE - )) - - v_lines = VGroup() - h_lines = VGroup() - height_labels = VGroup() - for t in t_vals: - v_line = self.get_vertical_line_to_graph( - t, self.v_graph, - color = YELLOW - ) - y_axis_point = self.graph_origin[0]*RIGHT - y_axis_point += v_line.get_end()[1]*UP - h_line = DashedLine(v_line.get_end(), y_axis_point) - label = TexMobject("%.1f"%v_func(t)) - label.scale(0.5) - label.next_to(h_line, LEFT, SMALL_BUFF) - v_lines.add(v_line) - h_lines.add(h_line) - height_labels.add(label) - - circle = Circle(radius = 0.25, color = WHITE) - circle.move_to(rect.get_top()) - - self.play( - rects.set_fill, None, 0.25, - Animation(rect) - ) - self.wait() - for label in t_labels: - self.play(FadeIn(label)) - self.wait() - for v_line, h_line, label in zip(v_lines, h_lines, height_labels): - self.play(ShowCreation(v_line)) - self.play(ShowCreation(h_line)) - self.play(Write(label, run_time = 1)) - self.wait() - self.wait() - t_label_copy = t_labels[0].copy() - self.play( - t_label_copy.scale, 1./0.7, - t_label_copy.next_to, self.v_graph_label, DOWN+LEFT, 0 - ) - self.wait() - self.play(FadeOut(t_label_copy)) - self.wait() - - self.play(ShowCreation(circle)) - self.play(ShowCreation(rect_top)) - self.play(FadeOut(circle)) - rect.add(rect_top) - self.wait() - for x in range(2): - self.play( - rect.stretch_to_fit_height, v_lines[1].get_height(), - rect.move_to, rect.get_bottom(), DOWN, - Animation(v_lines), - run_time = 4, - rate_func = there_and_back - ) - - self.play(*list(map(FadeOut, [ - group[1] - for group in (v_lines, h_lines, height_labels) - ]))) - self.play( - v_lines[0].set_color, RED, - rate_func = there_and_back, - ) - self.wait() - - area = TextMobject( - "7$\\frac{\\text{m}}{\\text{s}}$", - "$\\times$", - "0.25s", - "=", - "1.75m" - ) - area.next_to(rect, RIGHT, LARGE_BUFF) - arrow = Arrow( - area.get_left(), rect.get_center(), - buff = 0, - color = WHITE - ) - area.shift(SMALL_BUFF*RIGHT) - - self.play( - Write(area), - ShowCreation(arrow) - ) - self.wait(2) - self.play(*list(map(FadeOut, [ - area, arrow, - v_lines[0], h_lines[0], height_labels[0], - rect, t_labels - ]))) - - def show_v_dt_for_all_rectangles(self): - dt_brace_group = VGroup(self.dt_brace, self.dt_label) - rects_subset = self.rects[10:20] - - last_rect = None - for rect in rects_subset: - brace = Brace(rect, LEFT, buff = 0) - v_t = TexMobject("v(t)") - v_t.next_to(brace, LEFT, SMALL_BUFF) - anims = [ - rect.set_fill, None, 1, - dt_brace_group.next_to, rect, DOWN, SMALL_BUFF - ] - if last_rect is not None: - anims += [ - last_rect.set_fill, None, 0.25, - ReplacementTransform(last_brace, brace), - ReplacementTransform(last_v_t, v_t), - ] - else: - anims += [ - GrowFromCenter(brace), - Write(v_t) - ] - self.play(*anims) - self.wait() - - last_rect = rect - last_brace = brace - last_v_t = v_t - - self.v_t = last_v_t - self.v_t_brace = last_brace - - def write_integral_symbol(self): - integral = TexMobject( - "\\int", "^8", "_0", "v(t)", "\\,dt" - ) - integral.to_corner(UP+RIGHT) - int_copy = integral.get_part_by_tex("int").copy() - bounds = list(map(integral.get_part_by_tex, ["0", "8"])) - - sum_word = TextMobject("``Sum''") - sum_word.next_to(integral, DOWN, MED_LARGE_BUFF, LEFT) - alt_sum_word = sum_word.copy() - int_symbol = TexMobject("\\int") - int_symbol.replace(alt_sum_word[1], dim_to_match = 1) - alt_sum_word.submobjects[1] = int_symbol - - self.play(FadeOut(self.dt_example_text)) - self.play(Write(integral.get_part_by_tex("int"))) - self.wait() - self.play(Transform(int_copy, int_symbol)) - self.play(Write(alt_sum_word), Animation(int_copy)) - self.remove(int_copy) - self.play(ReplacementTransform(alt_sum_word, sum_word)) - self.wait() - - for bound in bounds: - self.play(Write(bound)) - self.wait() - for bound, num in zip(bounds, [0, 8]): - bound_copy = bound.copy() - point = self.coords_to_point(num, 0) - self.play( - bound_copy.scale, 1.5, - bound_copy.next_to, point, DOWN, MED_LARGE_BUFF - ) - self.play(ApplyWave(self.ticks, direction = UP)) - self.wait() - - for mob, tex in (self.v_t, "v(t)"), (self.dt_label, "dt"): - self.play(ReplacementTransform( - mob.copy().set_color(YELLOW), - integral.get_part_by_tex(tex), - run_time = 2 - )) - self.wait() - - self.integral = integral - self.sum_word = sum_word - - def roles_of_dt(self): - rects = self.rects - next_rects = self.rect_list[3] - - morty = Mortimer().flip() - morty.to_corner(DOWN+LEFT) - int_dt = self.integral.get_part_by_tex("dt") - dt_copy = int_dt.copy() - - self.play(FadeIn(morty)) - self.play( - morty.change_mode, "raise_right_hand", - morty.look, UP+RIGHT, - dt_copy.next_to, morty.get_corner(UP+RIGHT), UP, - dt_copy.set_color, YELLOW - ) - self.play(Blink(morty)) - self.play( - ReplacementTransform( - dt_copy.copy(), int_dt, - run_time = 2 - ), - morty.look_at, int_dt - ) - self.wait(2) - self.play( - ReplacementTransform(dt_copy.copy(), self.dt_label), - morty.look_at, self.dt_label - ) - self.play(*[ - ApplyMethod( - tick.shift, tick.get_height()*UP/2, - run_time = 2, - rate_func = squish_rate_func( - there_and_back, - alpha, alpha+0.2, - ) - ) - for tick, alpha in zip( - self.ticks, - np.linspace(0, 0.8, len(self.ticks)) - ) - ]) - self.wait() - - #Shrink dt just a bit - self.play( - morty.change_mode, "pondering", - rects.set_fill, None, 0.75, - *list(map(FadeOut, [ - dt_copy, self.v_t, self.v_t_brace - ])) - ) - rects.align_submobjects(next_rects) - for every_other_rect in rects[::2]: - every_other_rect.set_fill(opacity = 0) - self.play( - self.dt_brace.stretch, 0.5, 0, - self.dt_brace.move_to, self.dt_brace, LEFT, - ReplacementTransform( - rects, next_rects, - run_time = 2, - lag_ratio = 0.5 - ), - Transform( - self.ticks, self.get_ticks(next_rects), - run_time = 2, - lag_ratio = 0.5, - ), - ) - self.rects = rects = next_rects - self.wait() - self.play(Blink(morty)) - self.play(*[ - ApplyFunction( - lambda r : r.shift(0.2*UP).set_fill(None, 1), - rect, - run_time = 2, - rate_func = squish_rate_func( - there_and_back, - alpha, alpha+0.2, - ) - ) - for rect, alpha in zip( - rects, - np.linspace(0, 0.8, len(rects)) - ) - ]+[ - morty.change_mode, "thinking", - ]) - self.wait() - - self.morty = morty - - def what_does_sum_approach(self): - morty = self.morty - rects = self.rects - - cross = TexMobject("\\times") - cross.replace(self.sum_word, stretch = True) - cross.set_color(RED) - brace = Brace(self.integral, DOWN) - dt_to_0 = brace.get_text("$dt \\to 0$") - - distance_words = TextMobject( - "Area", "= Distance traveled" - ) - distance_words.next_to(rects, UP) - arrow = Arrow( - distance_words[0].get_bottom(), - rects.get_center(), - color = WHITE - ) - - self.play(PiCreatureSays( - morty, "Why not $\\Sigma$?", - target_mode = "sassy" - )) - self.play(Blink(morty)) - self.wait() - self.play(Write(cross)) - self.wait() - self.play( - RemovePiCreatureBubble(morty, target_mode = "plain"), - *list(map(FadeOut, [ - cross, self.sum_word, self.ticks, - self.dt_brace, self.dt_label, - ])) - ) - self.play(FadeIn(brace), FadeIn(dt_to_0)) - for new_rects in self.rect_list[4:]: - rects.align_submobjects(new_rects) - for every_other_rect in rects[::2]: - every_other_rect.set_fill(opacity = 0) - self.play( - Transform( - rects, new_rects, - run_time = 2, - lag_ratio = 0.5 - ), - morty.look_at, rects, - ) - self.wait() - - self.play( - Write(distance_words), - ShowCreation(arrow), - morty.change_mode, "pondering", - morty.look_at, distance_words, - ) - self.wait() - self.play(Blink(morty)) - self.wait() - - self.area_arrow = arrow - - def label_integral(self): - words = TextMobject("``Integral of $v(t)$''") - words.to_edge(UP) - arrow = Arrow( - words.get_right(), - self.integral.get_left() - ) - - self.play(Indicate(self.integral)) - self.play(Write(words, run_time = 2)) - self.play(ShowCreation(arrow)) - self.wait() - self.play(*[ - ApplyFunction( - lambda r : r.shift(0.2*UP).set_fill(None, 1), - rect, - run_time = 3, - rate_func = squish_rate_func( - there_and_back, - alpha, alpha+0.2, - ) - ) - for rect, alpha in zip( - self.rects, - np.linspace(0, 0.8, len(self.rects)) - ) - ]+[ - Animation(self.area_arrow), - self.morty.change_mode, "happy", - self.morty.look_at, self.rects, - ]) - self.wait() - - ##### - - def get_pw_constant_graph(self): - result = VGroup() - for left_x in range(8): - xs = [left_x, left_x+1] - y = self.v_graph.underlying_function(left_x) - line = Line(*[ - self.coords_to_point(x, y) - for x in xs - ]) - line.set_color(self.v_graph.get_color()) - result.add(line) - return result - - def get_ticks(self, rects): - ticks = VGroup(*[ - Line( - point+self.tick_size*UP/2, - point+self.tick_size*DOWN/2 - ) - for t in np.linspace(0, 8, len(rects)+1) - for point in [self.coords_to_point(t, 0)] - ]) - ticks.set_color(YELLOW) - return ticks - -class DontKnowHowToHandleNonConstant(TeacherStudentsScene): - def construct(self): - self.play(*[ - ApplyMethod(pi.change, "maybe", UP) - for pi in self.get_pi_creatures() - ]) - self.wait(3) - -class CarJourneyApproximation(Scene): - CONFIG = { - "n_jumps" : 5, - "bottom_words" : "Approximated motion (5 jumps)", - } - def construct(self): - points = [5*LEFT + v for v in (UP, 2*DOWN)] - cars = [Car().move_to(point) for point in points] - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - words = [ - TextMobject("Real motion (smooth)").shift(3*UP), - TextMobject(self.bottom_words).shift(0.5*DOWN), - ] - words[1].set_color(GREEN) - - - self.add(h_line, *cars + words) - self.wait() - self.play(*[ - MoveCar( - car, point+10*RIGHT, - run_time = 5, - rate_func = rf - ) - for car, point, rf in zip(cars, points, [ - s_rate_func, - self.get_approximated_rate_func(self.n_jumps) - ]) - ]) - self.wait() - - def get_approximated_rate_func(self, n): - new_v_rate_func = lambda t : v_rate_func(np.floor(t*n)/n) - max_integral, err = scipy.integrate.quad( - v_rate_func, 0, 1 - ) - def result(t): - integral, err = scipy.integrate.quad(new_v_rate_func, 0, t) - return integral/max_integral - return result - -class LessWrongCarJourneyApproximation(CarJourneyApproximation): - CONFIG = { - "n_jumps" : 20, - "bottom_words" : "Better approximation (20 jumps)", - } - -class TellMeThatsNotSurprising(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Tell me that's \\\\ not surprising!", - target_mode = "hooray", - run_time = 1 - ) - self.wait(3) - -class HowDoesThisHelp(TeacherStudentsScene): - def construct(self): - self.student_says( - "How does this help\\textinterrobang", - target_mode = "angry", - run_time = 1 - ) - self.change_student_modes( - "confused", "angry", "confused", - ) - self.wait(2) - self.teacher_says( - "You're right.", - target_mode = "shruggie", - run_time = 1 - ) - self.change_student_modes(*["sassy"]*3) - self.wait(2) - -class AreaUnderACurve(GraphScene): - CONFIG = { - "y_max" : 4, - "y_min" : 0, - "num_iterations" : 7 - } - def construct(self): - self.setup_axes() - graph = self.get_graph(self.func) - rect_list = self.get_riemann_rectangles_list( - graph, self.num_iterations - ) - VGroup(*rect_list).set_fill(opacity = 0.8) - rects = rect_list[0] - - self.play(ShowCreation(graph)) - self.play(Write(rects)) - for new_rects in rect_list[1:]: - rects.align_submobjects(new_rects) - for every_other_rect in rects[::2]: - every_other_rect.set_fill(opacity = 0) - self.play(Transform( - rects, new_rects, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait() - - - def func(self, x): - return np.sin(x) + 1 - -class AltAreaUnderCurve(AreaUnderACurve): - CONFIG = { - "graph_origin" : 2*DOWN, - "x_min" : -3, - "x_max" : 3, - "x_axis_width" : 12, - "y_max" : 2, - "y_axis_height" : 4, - } - def func(self, x): - return np.exp(-x**2) - -class Chapter1Wrapper(Chapter2Wrapper): - CONFIG = { - "title" : "Essence of calculus, chapter 1", - } - -class AreaIsDerivative(PlotVelocity, ReconfigurableScene): - CONFIG = { - "y_axis_label" : "", - "num_rects" : 400, - "dT" : 0.25, - "variable_point_label" : "T", - "area_opacity" : 0.8, - } - def setup(self): - PlotVelocity.setup(self) - ReconfigurableScene.setup(self) - self.setup_axes() - self.add(*self.get_v_graph_and_label()) - self.x_axis_label_mob.shift(MED_LARGE_BUFF*DOWN) - self.v_graph_label.shift(MED_LARGE_BUFF*DOWN) - self.foreground_mobjects = [] - - def construct(self): - self.introduce_variable_area() - self.write_integral() - self.nudge_input() - self.show_rectangle_approximation() - - def introduce_variable_area(self): - area = self.area = self.get_area(0, 6) - x_nums = self.x_axis.numbers - - self.play(Write(area, run_time = 2)) - self.play(FadeOut(self.x_axis.numbers)) - self.add_T_label(6) - self.change_area_bounds( - new_t_max = 4, - rate_func = there_and_back, - run_time = 2 - ) - self.wait() - - def write_integral(self): - integral = TexMobject("\\int", "^T", "_0", "v(t)", "\\,dt") - integral.to_corner(UP+RIGHT) - integral.shift(2*LEFT) - top_T = integral.get_part_by_tex("T") - moving_T = self.T_label_group[0] - - s_T = TexMobject("s(T)", "= ") - s_T.set_color_by_tex("s", DISTANCE_COLOR) - s_T.next_to(integral, LEFT) - - int_arrow, s_arrow = [ - Arrow( - mob.get_left(), self.area.get_center(), - color = WHITE - ) - for mob in (integral, s_T) - ] - - distance_word = TextMobject("Distance") - distance_word.move_to(self.area) - - self.play(Write(integral)) - self.play(ShowCreation(int_arrow)) - self.foreground_mobjects.append(int_arrow) - self.wait() - self.change_area_bounds( - new_t_max = 8, - rate_func = there_and_back, - run_time = 3, - ) - self.play(Indicate(top_T)) - self.play(ReplacementTransform( - top_T.copy(), moving_T - )) - self.change_area_bounds( - new_t_max = 3, - rate_func = there_and_back, - run_time = 3 - ) - self.wait() - self.play(Write(distance_word, run_time = 2)) - self.play( - ReplacementTransform(int_arrow, s_arrow), - FadeIn(s_T) - ) - self.wait() - self.play(FadeOut(distance_word)) - self.change_area_bounds(new_t_max = 0, run_time = 2) - self.change_area_bounds( - new_t_max = 8, - rate_func=linear, - run_time = 7.9, - ) - self.wait() - self.change_area_bounds(new_t_max = 5) - self.wait() - - def nudge_input(self): - dark_area = self.area.copy() - dark_area.set_fill(BLACK, opacity = 0.5) - curr_T = self.x_axis.point_to_number(self.area.get_right()) - new_T = curr_T + self.dT - - rect = Rectangle( - stroke_width = 0, - fill_color = YELLOW, - fill_opacity = 0.75 - ) - rect.replace( - VGroup( - VectorizedPoint(self.coords_to_point(new_T, 0)), - self.right_v_line, - ), - stretch = True - ) - - dT_brace = Brace(rect, DOWN, buff = 0) - dT_label = dT_brace.get_text("$dT$", buff = SMALL_BUFF) - dT_label_group = VGroup(dT_label, dT_brace) - - ds_label = TexMobject("ds") - ds_label.next_to(rect, RIGHT, LARGE_BUFF, UP) - ds_label.set_color(DISTANCE_COLOR) - ds_arrow = Arrow(ds_label.get_left(), rect.get_left()) - ds_arrow.set_color(WHITE) - - v_brace = Brace(rect, LEFT, buff = SMALL_BUFF) - v_T_label = v_brace.get_text("$v(T)$", buff = SMALL_BUFF) - - self.change_area_bounds(new_t_max = new_T) - self.play( - FadeIn(dark_area), - *list(map(Animation, self.foreground_mobjects)) - ) - self.play( - FadeOut(self.T_label_group), - FadeIn(dT_label_group) - ) - self.wait() - self.play(Write(ds_label)) - self.play(ShowCreation(ds_arrow)) - self.wait(2) - self.play(GrowFromCenter(v_brace)) - self.play(ReplacementTransform( - self.v_graph_label.get_part_by_tex("v").copy(), - v_T_label, - run_time = 2 - )) - self.wait() - self.play(Indicate(dT_label)) - self.wait() - - self.rect = rect - self.dT_label_group = dT_label_group - self.v_T_label_group = VGroup(v_T_label, v_brace) - self.dark_area = dark_area - self.ds_label = ds_label - self.ds_arrow = ds_arrow - - def show_rectangle_approximation(self): - formula1 = TexMobject("ds", "=", "v(T)", "dT") - formula2 = TexMobject("{ds", "\\over\\,", "dT}", "=", "v(T)") - for formula in formula1, formula2: - formula.next_to(self.v_graph_label, UP, LARGE_BUFF) - formula.set_color_by_tex("ds", DISTANCE_COLOR) - - self.play( - DrawBorderThenFill(self.rect), - Animation(self.ds_arrow) - ) - self.wait() - self.play(*[ - ReplacementTransform( - mob, formula1.get_part_by_tex(tex), - run_time = 2 - ) - for mob, tex in [ - (self.ds_label, "ds"), - (self.ds_arrow, "="), - (self.v_T_label_group[0].copy(), "v(T)"), - (self.dT_label_group[0].copy(), "dT"), - ] - ]) - self.wait() - self.transition_to_alt_config( - dT = self.dT/5.0, - transformation_kwargs = {"run_time" : 2}, - ) - self.wait() - self.play(*[ - ReplacementTransform( - formula1.get_part_by_tex(tex), - formula2.get_part_by_tex(tex), - ) - for tex in ("ds", "=", "v(T)", "dT") - ] + [ - Write(formula2.get_part_by_tex("over")) - ]) - self.wait() - - - #### - - def add_T_label(self, x_val, **kwargs): - triangle = RegularPolygon(n=3, start_angle = np.pi/2) - triangle.set_height(MED_SMALL_BUFF) - triangle.move_to(self.coords_to_point(x_val, 0), UP) - triangle.set_fill(WHITE, 1) - triangle.set_stroke(width = 0) - T_label = TexMobject(self.variable_point_label) - T_label.next_to(triangle, DOWN) - v_line = self.get_vertical_line_to_graph( - x_val, self.v_graph, - color = YELLOW - ) - - self.play( - DrawBorderThenFill(triangle), - ShowCreation(v_line), - Write(T_label, run_time = 1), - **kwargs - ) - - self.T_label_group = VGroup(T_label, triangle) - self.right_v_line = v_line - - def get_area(self, t_min, t_max): - numerator = max(t_max - t_min, 0.01) - dx = float(numerator) / self.num_rects - return self.get_riemann_rectangles( - self.v_graph, - x_min = t_min, - x_max = t_max, - dx = dx, - stroke_width = 0, - ).set_fill(opacity = self.area_opacity) - - def change_area_bounds(self, new_t_min = None, new_t_max = None, **kwargs): - curr_t_min = self.x_axis.point_to_number(self.area.get_left()) - curr_t_max = self.x_axis.point_to_number(self.area.get_right()) - if new_t_min is None: - new_t_min = curr_t_min - if new_t_max is None: - new_t_max = curr_t_max - - group = VGroup(self.area, self.right_v_line, self.T_label_group) - def update_group(group, alpha): - area, v_line, T_label = group - t_min = interpolate(curr_t_min, new_t_min, alpha) - t_max = interpolate(curr_t_max, new_t_max, alpha) - new_area = self.get_area(t_min, t_max) - new_v_line = self.get_vertical_line_to_graph( - t_max, self.v_graph - ) - new_v_line.set_color(v_line.get_color()) - T_label.move_to(new_v_line.get_bottom(), UP) - - #Fade close to 0 - T_label[0].set_fill(opacity = min(1, t_max)) - - Transform(area, new_area).update(1) - Transform(v_line, new_v_line).update(1) - return group - - self.play( - UpdateFromAlphaFunc(group, update_group), - *list(map(Animation, self.foreground_mobjects)), - **kwargs - ) - -class DirectInterpretationOfDsDt(TeacherStudentsScene): - def construct(self): - equation = TexMobject("{ds", "\\over\\,", "dT}", "(T)", "=", "v(T)") - ds, over, dt, of_T, equals, v = equation - equation.next_to(self.get_pi_creatures(), UP, LARGE_BUFF) - equation.shift(RIGHT) - v.set_color(VELOCITY_COLOR) - - s_words = TextMobject("Tiny change in", "distance") - s_words.next_to(ds, UP+LEFT, LARGE_BUFF) - s_words.shift_onto_screen() - s_arrow = Arrow(s_words[1].get_bottom(), ds.get_left()) - s_words.add(s_arrow) - s_words.set_color(DISTANCE_COLOR) - - t_words = TextMobject("Tiny change in", "time") - t_words.next_to(dt, DOWN+LEFT) - t_words.to_edge(LEFT) - t_arrow = Arrow(t_words[1].get_top(), dt.get_left()) - t_words.add(t_arrow) - t_words.set_color(TIME_COLOR) - - self.add(ds, over, dt, of_T) - for words, part in (s_words, ds), (t_words, dt): - self.play( - FadeIn( - words, - run_time = 2, - lag_ratio = 0.5, - ), - self.students[1].change_mode, "raise_right_hand" - ) - self.play(part.set_color, words.get_color()) - self.wait() - self.play(Write(VGroup(equals, v))) - self.change_student_modes(*["pondering"]*3) - self.wait(3) - -class FindAntiderivative(Antiderivative): - def construct(self): - self.introduce() - self.first_part() - self.second_part() - self.combine() - self.add_plus_C() - - def introduce(self): - q_marks, rhs = functions = self.get_functions("???", "t(8-t)") - expanded_rhs = TexMobject("8t - t^2") - expanded_rhs.move_to(rhs, LEFT) - expanded_rhs.set_color(rhs.get_color()) - self.v_part1 = VGroup(*expanded_rhs[:2]) - self.v_part2 = VGroup(*expanded_rhs[2:]) - for part in self.v_part1, self.v_part2: - part.save_state() - - top_arc, bottom_arc = arcs = self.get_arcs(functions) - derivative, antiderivative = words = self.get_arc_labels(arcs) - - self.add(functions) - self.play(*list(map(ShowCreation, arcs))) - for word in words: - self.play(FadeIn(word, lag_ratio = 0.5)) - self.wait() - self.change_mode("confused") - self.wait(2) - self.play(*[ - ReplacementTransform( - rhs[i], expanded_rhs[j], - run_time = 2, - path_arc = np.pi - ) - for i, j in enumerate([1, 4, 0, 2, 3, 4]) - ]+[ - self.pi_creature.change_mode, "hesitant" - ]) - self.wait() - - self.q_marks = q_marks - self.arcs = arcs - self.words = words - - def first_part(self): - four_t_squared, two_t = self.get_functions("4t^2", "2t") - four = four_t_squared[0] - four.shift(UP) - four.set_fill(opacity = 0) - t_squared = VGroup(*four_t_squared[1:]) - two_t.move_to(self.v_part1, LEFT) - - self.play(self.v_part2.to_corner, UP+RIGHT) - self.play( - self.pi_creature.change, "plain", self.v_part1 - ) - self.play(ApplyWave( - self.q_marks, - direction = UP, - amplitude = SMALL_BUFF - )) - self.wait(2) - self.play( - FadeOut(self.q_marks), - FadeIn(t_squared), - self.v_part1.shift, DOWN+RIGHT, - ) - self.play(*[ - ReplacementTransform( - t_squared[i].copy(), two_t[1-i], - run_time = 2, - path_arc = -np.pi/6. - ) - for i in (0, 1) - ]) - self.change_mode("thinking") - self.wait() - self.play(four.set_fill, YELLOW, 1) - self.play(four.shift, DOWN) - self.play(FadeOut(two_t)) - self.play(self.v_part1.restore) - self.play(four.set_color, DISTANCE_COLOR) - self.wait(2) - - self.s_part1 = four_t_squared - - def second_part(self): - self.arcs_copy = self.arcs.copy() - self.words_copy = self.words.copy() - part1_group = VGroup( - self.s_part1, self.v_part1, - self.arcs_copy, self.words_copy - ) - - neg_third_t_cubed, three_t_squared = self.get_functions( - "- \\frac{1}{3} t^3", "3t^2" - ) - three_t_squared.move_to(self.v_part1, LEFT) - neg = neg_third_t_cubed[0] - third = VGroup(*neg_third_t_cubed[1:4]) - t_cubed = VGroup(*neg_third_t_cubed[4:]) - three = three_t_squared[0] - t_squared = VGroup(*three_t_squared[1:]) - - self.play( - part1_group.scale, 0.5, - part1_group.to_corner, UP+LEFT, - self.pi_creature.change_mode, "plain" - ) - self.play( - self.v_part2.restore, - self.v_part2.shift, LEFT - ) - self.play(FadeIn(self.q_marks)) - self.wait() - - self.play( - FadeOut(self.q_marks), - FadeIn(t_cubed), - self.v_part2.shift, DOWN+RIGHT - ) - self.play(*[ - ReplacementTransform( - t_cubed[i].copy(), three_t_squared[j], - path_arc = -np.pi/6, - run_time = 2, - ) - for i, j in [(0, 1), (1, 0), (1, 2)] - ]) - self.wait() - self.play(FadeIn(third)) - self.play(FadeOut(three)) - self.wait(2) - self.play(Write(neg)) - self.play( - FadeOut(t_squared), - self.v_part2.shift, UP+LEFT - ) - self.wait(2) - - self.s_part2 = neg_third_t_cubed - - def combine(self): - self.play( - self.v_part1.restore, - self.v_part2.restore, - self.s_part1.scale, 2, - self.s_part1.next_to, self.s_part2, LEFT, - FadeOut(self.arcs_copy), - FadeOut(self.words_copy), - run_time = 2, - ) - self.change_mode("happy") - self.wait(2) - - def add_plus_C(self): - s_group = VGroup(self.s_part1, self.s_part2) - plus_Cs = [ - TexMobject("+%d"%d) - for d in range(1, 8) - ] - for plus_C in plus_Cs: - plus_C.set_color(YELLOW) - plus_C.move_to(s_group, RIGHT) - plus_C = plus_Cs[0] - - self.change_mode("sassy") - self.wait() - self.play( - s_group.next_to, plus_C.copy(), LEFT, - GrowFromCenter(plus_C), - ) - self.wait() - for new_plus_C in plus_Cs[1:]: - self.play(Transform(plus_C, new_plus_C)) - self.wait() - -class GraphSPlusC(GraphDistanceVsTime): - CONFIG = { - "y_axis_label" : "Distance" - } - def construct(self): - self.setup_axes() - graph = self.get_graph( - s_func, - color = DISTANCE_COLOR, - x_min = 0, - x_max = 8, - ) - tangent = self.get_secant_slope_group( - 6, graph, dx = 0.01 - ).secant_line - v_line = self.get_vertical_line_to_graph( - 6, graph, line_class = DashedLine - ) - v_line.scale_in_place(2) - v_line.set_color(WHITE) - graph_label, plus_C = full_label = TexMobject( - "s(t) = 4t^2 - \\frac{1}{3}t^3", "+C" - ) - plus_C.set_color(YELLOW) - full_label.next_to(graph.points[-1], DOWN) - full_label.to_edge(RIGHT) - - self.play(ShowCreation(graph)) - self.play(FadeIn(graph_label)) - self.wait() - self.play( - graph.shift, UP, - run_time = 2, - rate_func = there_and_back - ) - self.play(ShowCreation(tangent)) - graph.add(tangent) - self.play(ShowCreation(v_line)) - self.play( - graph.shift, 2*DOWN, - run_time = 4, - rate_func = there_and_back, - ) - self.play(Write(plus_C)) - self.play( - graph.shift, 2*UP, - rate_func = there_and_back, - run_time = 4, - ) - self.wait() - -class LowerBound(AreaIsDerivative): - CONFIG = { - "graph_origin" : 2.5*DOWN + 6*LEFT - } - - def construct(self): - self.add_integral_and_area() - self.mention_lower_bound() - self.drag_right_endpoint_to_zero() - self.write_antiderivative_difference() - self.show_alternate_antiderivative_difference() - self.add_constant_to_antiderivative() - - def add_integral_and_area(self): - self.area = self.get_area(0, 6) - self.integral = self.get_integral("0", "T") - self.remove(self.x_axis.numbers) - self.add(self.area, self.integral) - self.add_T_label(6, run_time = 0) - - def mention_lower_bound(self): - lower_bound = self.integral.get_part_by_tex("0") - circle = Circle(color = YELLOW) - circle.replace(lower_bound) - circle.scale_in_place(3) - zero_label = lower_bound.copy() - - self.play(ShowCreation(circle)) - self.play(Indicate(lower_bound)) - self.play( - zero_label.scale, 1.5, - zero_label.next_to, self.graph_origin, DOWN, MED_LARGE_BUFF, - FadeOut(circle) - ) - self.wait() - - self.zero_label = zero_label - - def drag_right_endpoint_to_zero(self): - zero_integral = self.get_integral("0", "0") - zero_integral[1].set_color(YELLOW) - zero_int_bounds = list(reversed( - zero_integral.get_parts_by_tex("0") - )) - for bound in zero_int_bounds: - circle = Circle(color = YELLOW) - circle.replace(bound) - circle.scale_in_place(3) - bound.circle = circle - self.integral.save_state() - equals_zero = TexMobject("=0") - equals_zero.next_to(zero_integral, RIGHT) - equals_zero.set_color(GREEN) - - self.change_area_bounds(0, 0, run_time = 3) - self.play(ReplacementTransform( - self.zero_label.copy(), equals_zero - )) - self.play(Transform(self.integral, zero_integral)) - self.wait(2) - for bound in zero_int_bounds: - self.play(ShowCreation(bound.circle)) - self.play(FadeOut(bound.circle)) - self.play(*[ - ReplacementTransform( - bound.copy(), VGroup(equals_zero[1]) - ) - for bound in zero_int_bounds - ]) - self.wait(2) - self.change_area_bounds(0, 5) - self.play( - self.integral.restore, - FadeOut(equals_zero) - ) - - self.zero_integral = zero_integral - - def write_antiderivative_difference(self): - antideriv_diff = self.get_antiderivative_difference("0", "T") - equals, at_T, minus, at_zero = antideriv_diff - antideriv_diff_at_eight = self.get_antiderivative_difference("0", "8") - at_eight = antideriv_diff_at_eight.left_part - integral_at_eight = self.get_integral("0", "8") - - for part in at_T, at_zero, at_eight: - part.brace = Brace(part, DOWN, buff = SMALL_BUFF) - part.brace.save_state() - - antideriv_text = at_T.brace.get_text("Antiderivative", buff = SMALL_BUFF) - antideriv_text.set_color(MAROON_B) - value_at_eight = at_eight.brace.get_text( - "%.2f"%s_func(8) - ) - happens_to_be_zero = at_zero.brace.get_text(""" - Happens to - equal 0 - """) - - big_brace = Brace(VGroup(at_T, at_zero)) - cancel_text = big_brace.get_text("Cancels when $T=0$") - - self.play(*list(map(Write, [equals, at_T]))) - self.play( - GrowFromCenter(at_T.brace), - Write(antideriv_text, run_time = 2) - ) - self.change_area_bounds(0, 5.5, rate_func = there_and_back) - self.wait() - self.play( - ReplacementTransform(at_T.copy(), at_zero), - Write(minus) - ) - self.wait() - self.play( - ReplacementTransform(at_T.brace, big_brace), - ReplacementTransform(antideriv_text, cancel_text) - ) - self.change_area_bounds(0, 0, run_time = 4) - self.wait() - self.play( - ReplacementTransform(big_brace, at_zero.brace), - ReplacementTransform(cancel_text, happens_to_be_zero), - ) - self.wait(2) - self.change_area_bounds(0, 8, run_time = 2) - self.play( - Transform(self.integral, integral_at_eight), - Transform(antideriv_diff, antideriv_diff_at_eight), - MaintainPositionRelativeTo(at_zero.brace, at_zero), - MaintainPositionRelativeTo(happens_to_be_zero, at_zero.brace), - ) - self.play( - GrowFromCenter(at_eight.brace), - Write(value_at_eight) - ) - self.wait(2) - self.play(*list(map(FadeOut, [ - at_eight.brace, value_at_eight, - at_zero.brace, happens_to_be_zero, - ]))) - - self.antideriv_diff = antideriv_diff - - def show_alternate_antiderivative_difference(self): - new_integral = self.get_integral("1", "7") - new_antideriv_diff = self.get_antiderivative_difference("1", "7") - numbers = [ - TexMobject("%d"%d).next_to( - self.coords_to_point(d, 0), - DOWN, MED_LARGE_BUFF - ) - for d in (1, 7) - ] - tex_mobs = [new_integral]+new_antideriv_diff[1::2]+numbers - for tex_mob in tex_mobs: - tex_mob.set_color_by_tex("1", RED) - tex_mob.set_color_by_tex("7", GREEN) - tex_mob.set_color_by_tex("\\frac{1}{3}", WHITE) - - self.change_area_bounds(1, 7, run_time = 2) - self.play( - self.T_label_group[0].set_fill, None, 0, - *list(map(FadeIn, numbers)) - ) - self.play( - Transform(self.integral, new_integral), - Transform(self.antideriv_diff, new_antideriv_diff), - ) - self.wait(3) - for part in self.antideriv_diff[1::2]: - self.play(Indicate(part, scale_factor = 1.1)) - self.wait() - - def add_constant_to_antiderivative(self): - antideriv_diff = self.antideriv_diff - plus_fives = VGroup(*[TexMobject("+5") for i in range(2)]) - plus_fives.set_color(YELLOW) - for five, part in zip(plus_fives, antideriv_diff[1::2]): - five.next_to(part, DOWN) - group = VGroup( - plus_fives[0], - antideriv_diff[2].copy(), - plus_fives[1] - ) - - self.play(Write(plus_fives, run_time = 2)) - self.wait(2) - self.play( - group.arrange, - group.next_to, antideriv_diff, DOWN, MED_LARGE_BUFF - ) - self.wait() - self.play(FadeOut(group, run_time = 2)) - self.wait() - - ##### - - def get_integral(self, lower_bound, upper_bound): - result = TexMobject( - "\\int", "^"+upper_bound, "_"+lower_bound, - "t(8-t)", "\\,dt" - ) - result.next_to(self.graph_origin, RIGHT, MED_LARGE_BUFF) - result.to_edge(UP) - return result - - def get_antiderivative_difference(self, lower_bound, upper_bound): - strings = [] - for bound in upper_bound, lower_bound: - try: - d = int(bound) - strings.append("(%d)"%d) - except: - strings.append(bound) - parts = [] - for s in strings: - part = TexMobject( - "\\left(", - "4", s, "^2", "-", "\\frac{1}{3}", s, "^3" - "\\right))" - ) - part.set_color_by_tex(s, YELLOW, substring = False) - parts.append(part) - result = VGroup( - TexMobject("="), parts[0], - TexMobject("-"), parts[1], - ) - result.left_part, result.right_part = parts - result.arrange(RIGHT) - result.scale(0.9) - result.next_to(self.integral, RIGHT) - return result - -class FundamentalTheorem(GraphScene): - CONFIG = { - "lower_bound" : 1, - "upper_bound" : 7, - "lower_bound_color" : RED, - "upper_bound_color" : GREEN, - "n_riemann_iterations" : 6, - } - - def construct(self): - self.add_graph_and_integral() - self.show_f_dx_sum() - self.show_rects_approaching_area() - self.write_antiderivative() - self.write_fundamental_theorem_of_calculus() - self.show_integral_considering_continuum() - self.show_antiderivative_considering_bounds() - - def add_graph_and_integral(self): - self.setup_axes() - integral = TexMobject("\\int", "^b", "_a", "f(x)", "\\,dx") - integral.next_to(ORIGIN, LEFT) - integral.to_edge(UP) - integral.set_color_by_tex("a", self.lower_bound_color) - integral.set_color_by_tex("b", self.upper_bound_color) - graph = self.get_graph( - lambda x : -0.01*x*(x-3)*(x-6)*(x-12) + 3, - ) - self.add(integral, graph) - self.graph = graph - self.integral = integral - - self.bound_labels = VGroup() - self.v_lines = VGroup() - for bound, tex in (self.lower_bound, "a"), (self.upper_bound, "b"): - label = integral.get_part_by_tex(tex).copy() - label.scale(1.5) - label.next_to(self.coords_to_point(bound, 0), DOWN) - v_line = self.get_vertical_line_to_graph( - bound, graph, color = label.get_color() - ) - - self.bound_labels.add(label) - self.v_lines.add(v_line) - self.add(label, v_line) - - def show_f_dx_sum(self): - kwargs = { - "x_min" : self.lower_bound, - "x_max" : self.upper_bound, - "fill_opacity" : 0.75, - "stroke_width" : 0.25, - } - low_opacity = 0.25 - start_rect_index = 3 - num_shown_sum_steps = 5 - last_rect_index = start_rect_index + num_shown_sum_steps + 1 - - self.rect_list = self.get_riemann_rectangles_list( - self.graph, self.n_riemann_iterations, **kwargs - ) - rects = self.rects = self.rect_list[0] - rects.save_state() - - start_rect = rects[start_rect_index] - f_brace = Brace(start_rect, LEFT, buff = 0) - dx_brace = Brace(start_rect, DOWN, buff = 0) - f_brace.label = f_brace.get_text("$f(x)$") - dx_brace.label = dx_brace.get_text("$dx$") - - flat_rects = self.get_riemann_rectangles( - self.get_graph(lambda x : 0), dx = 0.5, **kwargs - ) - - self.transform_between_riemann_rects( - flat_rects, rects, - replace_mobject_with_target_in_scene = True, - ) - self.play(*[ - ApplyMethod( - rect.set_fill, None, - 1 if rect is start_rect else low_opacity - ) - for rect in rects - ]) - self.play(*it.chain( - list(map(GrowFromCenter, [f_brace, dx_brace])), - list(map(Write, [f_brace.label, dx_brace.label])), - )) - self.wait() - for i in range(start_rect_index+1, last_rect_index): - self.play( - rects[i-1].set_fill, None, low_opacity, - rects[i].set_fill, None, 1, - f_brace.set_height, rects[i].get_height(), - f_brace.next_to, rects[i], LEFT, 0, - dx_brace.next_to, rects[i], DOWN, 0, - *[ - MaintainPositionRelativeTo(brace.label, brace) - for brace in (f_brace, dx_brace) - ] - ) - self.wait() - self.play(*it.chain( - list(map(FadeOut, [ - f_brace, dx_brace, - f_brace.label, dx_brace.label - ])), - [rects.set_fill, None, kwargs["fill_opacity"]] - )) - - def show_rects_approaching_area(self): - for new_rects in self.rect_list: - self.transform_between_riemann_rects( - self.rects, new_rects - ) - - def write_antiderivative(self): - deriv = TexMobject( - "{d", "F", "\\over\\,", "dx}", "(x)", "=", "f(x)" - ) - deriv_F = deriv.get_part_by_tex("F") - deriv.next_to(self.integral, DOWN, MED_LARGE_BUFF) - rhs = TexMobject(*"=F(b)-F(a)") - rhs.set_color_by_tex("a", self.lower_bound_color) - rhs.set_color_by_tex("b", self.upper_bound_color) - rhs.next_to(self.integral, RIGHT) - - self.play(Write(deriv)) - self.wait(2) - self.play(*it.chain( - [ - ReplacementTransform(deriv_F.copy(), part) - for part in rhs.get_parts_by_tex("F") - ], - [ - Write(VGroup(*rhs.get_parts_by_tex(tex))) - for tex in "=()-" - ] - )) - for tex in "b", "a": - self.play(ReplacementTransform( - self.integral.get_part_by_tex(tex).copy(), - rhs.get_part_by_tex(tex) - )) - self.wait() - self.wait(2) - - self.deriv = deriv - self.rhs = rhs - - def write_fundamental_theorem_of_calculus(self): - words = TextMobject(""" - Fundamental - theorem of - calculus - """) - words.to_edge(RIGHT) - - self.play(Write(words)) - self.wait() - - def show_integral_considering_continuum(self): - self.play(*[ - ApplyMethod(mob.set_fill, None, 0.2) - for mob in (self.deriv, self.rhs) - ]) - self.play( - self.rects.restore, - run_time = 3, - rate_func = there_and_back - ) - self.wait() - for x in range(2): - self.play(*[ - ApplyFunction( - lambda m : m.shift(MED_SMALL_BUFF*UP).set_fill(opacity = 1), - rect, - run_time = 3, - rate_func = squish_rate_func( - there_and_back, - alpha, alpha+0.2 - ) - ) - for rect, alpha in zip( - self.rects, - np.linspace(0, 0.8, len(self.rects)) - ) - ]) - self.wait() - - def show_antiderivative_considering_bounds(self): - self.play( - self.integral.set_fill, None, 0.5, - self.deriv.set_fill, None, 1, - self.rhs.set_fill, None, 1, - ) - for label, line in reversed(list(zip(self.bound_labels, self.v_lines))): - new_line = line.copy().set_color(YELLOW) - label.save_state() - self.play(label.set_color, YELLOW) - self.play(ShowCreation(new_line)) - self.play(ShowCreation(line)) - self.remove(new_line) - self.play(label.restore) - self.wait() - self.play(self.integral.set_fill, None, 1) - self.wait(3) - -class LetsRecap(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Let's recap", - target_mode = "hesitant", - ) - self.change_student_modes(*["happy"]*3) - self.wait(3) - -class NegativeArea(GraphScene): - CONFIG = { - "x_axis_label" : "Time", - "y_axis_label" : "Velocity", - "graph_origin" : 1.5*DOWN + 5*LEFT, - "y_min" : -3, - "y_max" : 7, - "small_dx" : 0.01, - "sample_input" : 5, - } - def construct(self): - self.setup_axes() - self.add_graph_and_area() - self.write_negative_area() - self.show_negative_point() - self.show_car_going_backwards() - self.write_v_dt() - self.show_rectangle() - self.write_signed_area() - - def add_graph_and_area(self): - graph = self.get_graph( - lambda x : -0.02*(x+1)*(x-3)*(x-7)*(x-10), - x_min = 0, - x_max = 8, - color = VELOCITY_COLOR - ) - area = self.get_riemann_rectangles( - graph, - x_min = 0, - x_max = 8, - dx = self.small_dx, - start_color = BLUE_D, - end_color = BLUE_D, - fill_opacity = 0.75, - stroke_width = 0, - ) - - self .play( - ShowCreation(graph), - FadeIn( - area, - run_time = 2, - lag_ratio = 0.5, - ) - ) - - self.graph = graph - self.area = area - - def write_negative_area(self): - words = TextMobject("Negative area") - words.set_color(RED) - words.next_to( - self.coords_to_point(7, -2), - RIGHT, - ) - arrow = Arrow(words, self.coords_to_point( - self.sample_input, -1, - )) - - self.play( - Write(words, run_time = 2), - ShowCreation(arrow) - ) - self.wait(2) - self.play(*list(map(FadeOut, [self.area, arrow]))) - - self.negative_area_words = words - - def show_negative_point(self): - v_line = self.get_vertical_line_to_graph( - self.sample_input, self.graph, - color = RED - ) - self.play(ShowCreation(v_line)) - self.wait() - self.v_line = v_line - - def show_car_going_backwards(self): - car = Car() - start_point = 3*RIGHT + 2*UP - end_point = start_point + LEFT - nudged_end_point = end_point + MED_SMALL_BUFF*LEFT - car.move_to(start_point) - arrow = Arrow(RIGHT, LEFT, color = RED) - arrow.next_to(car, UP+LEFT) - arrow.shift(MED_LARGE_BUFF*RIGHT) - - self.play(FadeIn(car)) - self.play(ShowCreation(arrow)) - self.play(MoveCar( - car, end_point, - moving_forward = False, - run_time = 3 - )) - self.wait() - ghost_car = car.copy().fade() - right_nose_line = self.get_car_nose_line(car) - self.play(ShowCreation(right_nose_line)) - self.add(ghost_car) - self.play(MoveCar( - car, nudged_end_point, - moving_forward = False - )) - left_nose_line = self.get_car_nose_line(car) - self.play(ShowCreation(left_nose_line)) - - self.nose_lines = VGroup(left_nose_line, right_nose_line) - self.car = car - self.ghost_car = ghost_car - - def write_v_dt(self): - brace = Brace(self.nose_lines, DOWN, buff = 0) - equation = TexMobject("ds", "=", "v(t)", "dt") - equation.next_to(brace, DOWN, SMALL_BUFF, LEFT) - equation.set_color_by_tex("ds", DISTANCE_COLOR) - equation.set_color_by_tex("dt", TIME_COLOR) - - negative = TextMobject("Negative") - negative.set_color(RED) - negative.next_to(equation.get_corner(UP+RIGHT), UP, LARGE_BUFF) - ds_arrow, v_arrow = arrows = VGroup(*[ - Arrow( - negative.get_bottom(), - equation.get_part_by_tex(tex).get_top(), - color = RED, - ) - for tex in ("ds", "v(t)") - ]) - - self.play( - GrowFromCenter(brace), - Write(equation) - ) - self.wait() - self.play(FadeIn(negative)) - self.play(ShowCreation(v_arrow)) - self.wait(2) - self.play(ReplacementTransform( - v_arrow.copy(), - ds_arrow - )) - self.wait(2) - - self.ds_equation = equation - self.negative_word = negative - self.negative_word_arrows = arrows - - def show_rectangle(self): - rect_list = self.get_riemann_rectangles_list( - self.graph, x_min = 0, x_max = 8, - n_iterations = 6, - start_color = BLUE_D, - end_color = BLUE_D, - fill_opacity = 0.75, - ) - rects = rect_list[0] - rect = rects[len(rects)*self.sample_input//8] - - dt_brace = Brace(rect, UP, buff = 0) - v_brace = Brace(rect, LEFT, buff = 0) - dt_label = dt_brace.get_text("$dt$", buff = SMALL_BUFF) - dt_label.set_color(YELLOW) - v_label = v_brace.get_text("$v(t)$", buff = SMALL_BUFF) - v_label.add_background_rectangle() - - self.play(FadeOut(self.v_line), FadeIn(rect)) - self.play( - GrowFromCenter(dt_brace), - GrowFromCenter(v_brace), - Write(dt_label), - Write(v_label), - ) - self.wait(2) - self.play(*it.chain( - [FadeIn(r) for r in rects if r is not rect], - list(map(FadeOut, [ - dt_brace, v_brace, dt_label, v_label - ])) - )) - self.wait() - for new_rects in rect_list[1:]: - self.transform_between_riemann_rects(rects, new_rects) - self.wait() - - def write_signed_area(self): - words = TextMobject("``Signed area''") - words.next_to(self.coords_to_point(self.sample_input, 0), UP) - symbols = VGroup(*[ - TexMobject(sym).move_to(self.coords_to_point(*coords)) - for sym, coords in [ - ("+", (1, 2)), - ("-", (5, -1)), - ("+", (7.6, 0.5)), - ] - ]) - self.play(Write(words)) - self.play(Write(symbols)) - self.wait() - - #### - - def get_car_nose_line(self, car): - line = DashedLine(car.get_top(), car.get_bottom()) - line.move_to(car.get_right()) - return line - -class NextVideo(TeacherStudentsScene): - def construct(self): - series = VideoSeries() - series.to_edge(UP) - next_video = series[8] - integral = TexMobject("\\int") - integral.next_to(next_video, DOWN, LARGE_BUFF) - - self.play(FadeIn(series, lag_ratio = 0.5)) - self.play( - next_video.set_color, YELLOW, - next_video.shift, next_video.get_height()*DOWN/2, - self.teacher.change_mode, "raise_right_hand" - ) - self.play(Write(integral)) - self.wait(5) - -class Chapter8PatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "CrypticSwarm", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Karan Bhargava", - "Ankit Agarwal", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Markus Persson", - "Dan Buchoff", - "Derek Dai", - "Joseph John Cox", - "Luc Ritchie", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "Nils Schneider", - "James Thornton", - "Mustafa Mahdi", - "Jonathan Eppele", - "Mathew Bramson", - "Jerry Ling", - "Mark Govea", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ] - } - -class Thumbnail(Chapter1Thumbnail): - CONFIG = { - "x_axis_label" : "", - "y_axis_label" : "", - "graph_origin" : 1.5*DOWN + 4*LEFT, - "y_axis_height" : 5, - "x_max" : 5, - "x_axis_width" : 11, - } - def construct(self): - self.setup_axes() - self.remove(*self.x_axis.numbers) - self.remove(*self.y_axis.numbers) - graph = self.get_graph(self.func) - rects = self.get_riemann_rectangles( - graph, - x_min = 0, - x_max = 4, - dx = 0.25, - ) - words = TextMobject("Integrals") - words.set_width(8) - words.to_edge(UP) - - self.add(graph, rects, words) - - - - - - - - - - - - diff --git a/from_3b1b/old/eoc/chapter9.py b/from_3b1b/old/eoc/chapter9.py deleted file mode 100644 index c2a7f015..00000000 --- a/from_3b1b/old/eoc/chapter9.py +++ /dev/null @@ -1,2168 +0,0 @@ -import scipy -import fractions - -from manimlib.imports import * - -class Chapter9OpeningQuote(OpeningQuote): - CONFIG = { - "quote" : [ - "We often hear that mathematics consists mainly of", - "proving theorems.", - "Is a writer's job mainly that of\\\\", - "writing sentences?" - ], - "highlighted_quote_terms" : { - "proving theorems." : MAROON_B, - "writing sentences?" : MAROON_B, - }, - "author" : "Gian-Carlo Rota", - } - -class AverageOfContinuousVariable(GraphScene): - CONFIG = { - "bounds" : [1, 7], - "bound_colors" : [RED, GREEN], - } - def construct(self): - self.setup_axes() - graph = self.get_graph( - lambda x : 0.1*x*(x-3)*(x-6) + 4 - ) - graph_label = self.get_graph_label(graph, "f(x)") - boundary_lines = self.get_vertical_lines_to_graph( - graph, *self.bounds, num_lines = 2, - line_class = DashedLine - ) - for line, color in zip(boundary_lines, self.bound_colors): - line.set_color(color) - v_line = self.get_vertical_line_to_graph( - self.bounds[0], graph, color = YELLOW, - ) - - question = TextMobject( - "What is the average \\\\ value of $f(x)$?" - ) - question.next_to(boundary_lines, UP) - - self.play(ShowCreation(graph), Write(graph_label)) - self.play(ShowCreation(boundary_lines)) - self.play(FadeIn( - question, - run_time = 2, - lag_ratio = 0.5, - )) - self.play(ShowCreation(v_line)) - for bound in reversed(self.bounds): - self.play(self.get_v_line_change_anim( - v_line, graph, bound, - run_time = 3, - )) - self.wait() - self.wait() - - def get_v_line_change_anim(self, v_line, graph, target_x, **kwargs): - start_x = self.x_axis.point_to_number(v_line.get_bottom()) - def update(v_line, alpha): - new_x = interpolate(start_x, target_x, alpha) - v_line.put_start_and_end_on( - self.coords_to_point(new_x, 0), - self.input_to_graph_point(new_x, graph) - ) - return v_line - return UpdateFromAlphaFunc(v_line, update, **kwargs) - -class ThisVideo(TeacherStudentsScene): - def construct(self): - series = VideoSeries() - series.to_edge(UP) - this_video = series[8] - - self.play(FadeIn(series, lag_ratio = 0.5)) - self.teacher_says( - "A new view of \\\\ the fundamental theorem", - bubble_kwargs = {"height" : 3}, - added_anims = [ - this_video.shift, this_video.get_height()*DOWN/2, - this_video.set_color, YELLOW, - ] - ) - self.change_student_modes(*["pondering"]*3) - self.wait(3) - -class AverageOfSineStart(AverageOfContinuousVariable): - CONFIG = { - "y_min" : -2, - "y_max" : 2, - "x_min" : -1, - "x_max" : 2.5*np.pi, - "x_leftmost_tick" : 0, - "x_tick_frequency" : np.pi/4, - "x_axis_width" : 12, - "graph_origin" : 5*LEFT, - "x_label_scale_val" : 0.75, - "func" : np.sin, - "graph_color" : BLUE, - "bounds" : [0, np.pi], - } - def construct(self): - self.setup_axes() - self.add_graph() - self.ask_about_average() - - def add_graph(self, run_time = 1): - graph = self.get_graph(self.func, color = self.graph_color) - graph_label = self.get_graph_label( - graph, "\\sin(x)", - direction = UP - ) - self.play( - ShowCreation(graph), - Write(graph_label), - run_time = run_time - ) - - self.graph = graph - self.graph_label = graph_label - - def ask_about_average(self): - half_period_graph = self.get_graph_portion_between_bounds() - question = TextMobject("Average height?") - question.to_edge(UP) - arrow = Arrow(question.get_bottom(), half_period_graph.get_top()) - midpoint = np.mean(self.bounds) - v_line = self.get_vertical_line_to_graph( - midpoint, self.graph, - line_class = DashedLine, - color = WHITE - ) - - self.play(FadeIn(half_period_graph)) - self.play( - Write(question, run_time = 2), - ShowCreation(arrow) - ) - self.play(ShowCreation(v_line)) - for bound in self.bounds + [midpoint]: - self.play(self.get_v_line_change_anim( - v_line, self.graph, bound, - run_time = 3 - )) - - ######### - - def get_graph_portion_between_bounds(self): - self.graph_portion_between_bounds = self.get_graph( - self.func, - x_min = self.bounds[0], - x_max = self.bounds[1], - color = YELLOW - ) - return self.graph_portion_between_bounds - - def setup_axes(self): - GraphScene.setup_axes(self) - self.add_x_axis_labels() - - def add_x_axis_labels(self): - labels_and_x_values = [ - ("\\pi/2", np.pi/2), - ("\\pi", np.pi), - ("3\\pi/2", 3*np.pi/2), - ("2\\pi", 2*np.pi), - ] - self.x_axis_labels = VGroup() - for label, x in labels_and_x_values: - tex_mob = TexMobject(label) - tex_mob.scale(self.x_label_scale_val) - tex_mob.move_to( - self.coords_to_point(x, -3*self.x_axis.tick_size), - ) - self.add(tex_mob) - self.x_axis_labels.add(tex_mob) - -class LengthOfDayGraph(GraphScene): - CONFIG = { - "x_min" : 0, - "x_max" : 365, - "x_axis_width" : 12, - "x_tick_frequency" : 25, - "x_labeled_nums" : list(range(50, 365, 50)), - "x_axis_label" : "Days since March 21", - "y_min" : 0, - "y_max" : 16, - "y_axis_height" : 6, - "y_tick_frequency" : 1, - "y_labeled_nums" : list(range(2, 15, 2)), - "y_axis_label" : "Hours of daylight", - "graph_origin" : 6*LEFT + 3*DOWN, - "camera_class" : ThreeDCamera, - "camera_config" : { - "shading_factor" : 1, - } - } - def construct(self): - self.setup_axes() - self.add_graph() - self.show_solar_pannel() - self.set_color_summer_months() - self.mention_constants() - - def add_graph(self): - x_label = self.x_axis_label_mob - y_label = self.y_axis_label_mob - - graph = self.get_graph( - lambda x : 2.7*np.sin((2*np.pi)*x/365 ) + 12.4, - color = GREEN, - ) - graph_label = TexMobject("2.7\\sin(2\\pi x/365) + 12.4") - graph_label.to_corner(UP+RIGHT).shift(LEFT) - VGroup(*graph_label[3:6]).set_color(graph.get_color()) - graph_label[9].set_color(YELLOW) - - self.remove(x_label, y_label) - for label in y_label, x_label: - self.play(FadeIn( - label, - run_time = 2, - lag_ratio = 0.5 - )) - self.play( - ShowCreation(graph, rate_func=linear), - FadeIn( - graph_label, - rate_func = squish_rate_func(smooth, 0.5, 1), - lag_ratio = 0.5 - ), - run_time = 3, - ) - self.wait() - - self.graph = graph - self.graph_label = graph_label - - def show_solar_pannel(self): - randy = Randolph() - randy.to_edge(DOWN) - panel = ThreeDMobject(*[ - Rectangle( - height = 0.7, width = 0.25, - fill_color = DARK_GREY, - fill_opacity = 1, - stroke_width = 1, - stroke_color = GREY, - ) - for x in range(6) - ]) - panel.arrange(RIGHT, buff = SMALL_BUFF) - panel.center() - panels = ThreeDMobject(panel, panel.copy(), panel.copy()) - panels.arrange(DOWN) - panels.rotate(4*np.pi/12, DOWN) - panels.rotate(-np.pi/6, OUT) - side_vect = RIGHT - side_vect = rotate_vector(side_vect, 4*np.pi/12, DOWN) - side_vect = rotate_vector(side_vect, -np.pi/3, OUT) - panels.next_to(randy.get_corner(UP+RIGHT), RIGHT) - - self.play(FadeIn(randy)) - self.play( - randy.change, "thinking", panels.get_right(), - FadeIn( - panels, - run_time = 2, - lag_ratio = 0.5 - ) - ) - for angle in -np.pi/4, np.pi/4: - self.play(*[ - Rotate( - panel, angle, - axis = side_vect, - in_place = True, - run_time = 2, - rate_func = squish_rate_func(smooth, a, a+0.8) - ) - for panel, a in zip(panels, np.linspace(0, 0.2, len(panels))) - ]) - self.play(Blink(randy)) - self.play(*list(map(FadeOut, [randy, panels]))) - - def set_color_summer_months(self): - summer_rect = Rectangle() - summer_rect.set_stroke(width = 0) - summer_rect.set_fill(YELLOW, opacity = 0.25) - summer_rect.replace(Line( - self.graph_origin, - self.coords_to_point(365/2, 15.5) - ), stretch = True) - - winter_rect = Rectangle() - winter_rect.set_stroke(width = 0) - winter_rect.set_fill(BLUE, opacity = 0.25) - winter_rect.replace(Line( - self.coords_to_point(365/2, 15.5), - self.coords_to_point(365, 0), - ), stretch = True) - - - summer_words, winter_words = [ - TextMobject("%s \\\\ months"%s).move_to(rect) - for s, rect in [ - ("Summer", summer_rect), - ("Winter", winter_rect), - ] - ] - - for rect, words in (summer_rect, summer_words), (winter_rect, winter_words): - self.play( - FadeIn(rect), - Write(words, run_time = 2) - ) - self.wait() - - def mention_constants(self): - #2.7\\sin(2\\pi t/365) + 12.4 - constants = VGroup(*[ - VGroup(*self.graph_label[i:j]) - for i, j in [(0, 3), (7, 9), (11, 14), (16, 20)] - ]) - - self.play(*[ - ApplyFunction( - lambda c : c.scale_in_place(0.9).shift(SMALL_BUFF*DOWN).set_color(RED), - constant, - run_time = 3, - rate_func = squish_rate_func(there_and_back, a, a+0.7) - ) - for constant, a in zip( - constants, - np.linspace(0, 0.3, len(constants)) - ) - ]) - self.wait() - - - ##### - -class AskAboutAverageOfContinuousVariables(TeacherStudentsScene): - def construct(self): - self.student_says( - "The average \\dots of a \\\\ continuous thing?", - target_mode = "sassy", - ) - self.change_student_modes("confused", "sassy", "confused") - self.play(self.teacher.change_mode, "happy") - self.wait(2) - -class AverageOfFiniteSet(Scene): - CONFIG = { - "lengths" : [1, 4, 2, 5] - } - def construct(self): - lengths = self.lengths - lines = VGroup(*[ - Line(ORIGIN, length*RIGHT) - for length in lengths - ]) - colors = Color(BLUE).range_to(RED, len(lengths)) - lines.set_color_by_gradient(*colors) - lines.arrange(RIGHT) - lines.generate_target() - lines.target.arrange(RIGHT, buff = 0) - for mob in lines, lines.target: - mob.shift(UP) - brace = Brace(lines.target, UP) - - labels = VGroup(*[ - TexMobject(str(d)).next_to(line, UP).set_color(line.get_color()) - for d, line in zip(lengths, lines) - ]) - plusses = [TexMobject("+") for x in range(len(lengths)-1)] - symbols = VGroup(* - plusses + [TexMobject("=")] - ) - symbols.set_fill(opacity = 0) - - labels.generate_target() - symbols.generate_target() - symbols.target.set_fill(opacity = 1) - sum_eq = VGroup(*it.chain(*list(zip(labels.target, symbols.target)))) - sum_eq.arrange(RIGHT) - sum_eq.next_to(brace, UP) - - sum_mob = TexMobject(str(sum(lengths))) - sum_mob.next_to(sum_eq, RIGHT) - - dividing_lines = VGroup(*[ - DashedLine(p + MED_SMALL_BUFF*UP, p + MED_LARGE_BUFF*DOWN) - for alpha in np.linspace(0, 1, len(lengths)+1) - for p in [interpolate( - lines.target.get_left(), - lines.target.get_right(), - alpha - )] - ]) - - lower_braces = VGroup(*[ - Brace(VGroup(*dividing_lines[i:i+2]), DOWN) - for i in range(len(lengths)) - ]) - averages = VGroup(*[ - lb.get_text("$%d/%d$"%(sum(lengths), len(lengths))) - for lb in lower_braces - ]) - circle = Circle(color = YELLOW) - circle.replace(averages[1], stretch = True) - circle.scale_in_place(1.5) - - self.add(lines) - self.play(FadeIn( - labels, - lag_ratio = 0.5, - run_time = 3 - )) - self.wait() - self.play( - GrowFromCenter(brace), - *list(map(MoveToTarget, [lines, labels, symbols])), - run_time = 2 - ) - self.play(Write(sum_mob)) - self.wait() - self.play(ShowCreation(dividing_lines, run_time = 2)) - self.play(*it.chain( - list(map(Write, averages)), - list(map(GrowFromCenter, lower_braces)) - )) - self.play(ShowCreation(circle)) - self.wait(2) - -class TryToAddInfinitelyManyPoints(AverageOfSineStart): - CONFIG = { - "max_denominator" : 40, - } - def construct(self): - self.add_graph() - self.try_to_add_infinitely_many_values() - self.show_continuum() - self.mention_integral() - - def add_graph(self): - self.setup_axes() - AverageOfSineStart.add_graph(self, run_time = 0) - self.add(self.get_graph_portion_between_bounds()) - self.graph_label.to_edge(RIGHT) - self.graph_label.shift(DOWN) - - def try_to_add_infinitely_many_values(self): - v_lines = VGroup(*[ - self.get_vertical_line_to_graph( - numerator*np.pi/denominator, self.graph, - color = YELLOW, - stroke_width = 6./denominator - ) - for denominator in range(self.max_denominator) - for numerator in range(1, denominator) - if fractions.gcd(numerator, denominator) == 1 - ]) - ghost_lines = v_lines.copy().set_stroke(GREY) - - v_lines.generate_target() - start_lines = VGroup(*v_lines.target[:15]) - end_lines = VGroup(*v_lines.target[15:]) - - plusses = VGroup(*[TexMobject("+") for x in start_lines]) - sum_eq = VGroup(*it.chain(*list(zip(start_lines, plusses)))) - sum_eq.add(*end_lines) - sum_eq.arrange(RIGHT) - sum_eq.next_to(v_lines[0], UP, aligned_edge = LEFT) - sum_eq.to_edge(UP, buff = MED_SMALL_BUFF) - - h_line = Line(LEFT, RIGHT) - h_line.set_width(start_lines.get_width()) - h_line.set_color(WHITE) - h_line.next_to(sum_eq, DOWN, aligned_edge = LEFT) - - infinity = TexMobject("\\infty") - infinity.next_to(h_line, DOWN) - - self.play(ShowCreation( - v_lines, - run_time = 3, - )) - self.add(ghost_lines, v_lines) - self.wait(2) - self.play( - MoveToTarget( - v_lines, - run_time = 3, - lag_ratio = 0.5 - ), - Write(plusses) - ) - self.play(ShowCreation(h_line)) - self.play(Write(infinity)) - self.wait() - - def show_continuum(self): - arrow = Arrow(ORIGIN, UP+LEFT) - input_range = Line(*[ - self.coords_to_point(bound, 0) - for bound in self.bounds - ]) - VGroup(arrow, input_range).set_color(RED) - - self.play(FadeIn(arrow)) - self.play( - arrow.next_to, input_range.get_start(), - DOWN+RIGHT, SMALL_BUFF - ) - self.play( - arrow.next_to, input_range.copy().get_end(), - DOWN+RIGHT, SMALL_BUFF, - ShowCreation(input_range), - run_time = 3, - ) - self.play( - arrow.next_to, input_range.get_start(), - DOWN+RIGHT, SMALL_BUFF, - run_time = 3 - ) - self.play(FadeOut(arrow)) - self.wait() - - def mention_integral(self): - randy = Randolph() - randy.to_edge(DOWN) - randy.shift(3*LEFT) - - self.play(FadeIn(randy)) - self.play(PiCreatureBubbleIntroduction( - randy, "Use an integral!", - bubble_class = ThoughtBubble, - target_mode = "hooray" - )) - self.play(Blink(randy)) - curr_bubble = randy.bubble - new_bubble = randy.get_bubble("Somehow...") - self.play( - Transform(curr_bubble, new_bubble), - Transform(curr_bubble.content, new_bubble.content), - randy.change_mode, "shruggie", - ) - self.play(Blink(randy)) - self.wait() - -class FiniteSample(TryToAddInfinitelyManyPoints): - CONFIG = { - "dx" : 0.1, - "graph_origin" : 6*LEFT + 0.5*DOWN, - } - def construct(self): - self.add_graph() - self.show_finite_sample() - - def show_finite_sample(self): - v_lines = self.get_sample_lines(dx = self.dx) - summed_v_lines = v_lines.copy() - plusses = VGroup(*[ - TexMobject("+").scale(0.75) - for l in v_lines - ]) - numerator = VGroup(*it.chain(*list(zip(summed_v_lines, plusses)))) - for group in numerator, plusses: - group.remove(plusses[-1]) - numerator.arrange( - RIGHT, - buff = SMALL_BUFF, - aligned_edge = DOWN - ) - # numerator.set_width(FRAME_X_RADIUS) - numerator.scale(0.5) - numerator.move_to(self.coords_to_point(3*np.pi/2, 0)) - numerator.to_edge(UP) - frac_line = TexMobject("\\over \\,") - frac_line.stretch_to_fit_width(numerator.get_width()) - frac_line.next_to(numerator, DOWN) - denominator = TextMobject("(Num. samples)") - denominator.next_to(frac_line, DOWN) - - self.play(ShowCreation(v_lines, run_time = 3)) - self.wait() - self.play( - ReplacementTransform( - v_lines.copy(), - summed_v_lines, - run_time = 3, - lag_ratio = 0.5 - ), - Write( - plusses, - rate_func = squish_rate_func(smooth, 0.3, 1) - ) - ) - self.play(Write(frac_line, run_time = 1)) - self.play(Write(denominator)) - self.wait() - - self.plusses = plusses - self.average = VGroup(numerator, frac_line, denominator) - self.v_lines = v_lines - - ### - - def get_sample_lines(self, dx, color = YELLOW, stroke_width = 2): - return VGroup(*[ - self.get_vertical_line_to_graph( - x, self.graph, - color = color, - stroke_width = stroke_width, - ) - for x in np.arange( - self.bounds[0]+dx, - self.bounds[1], - dx - ) - ]) - -class FiniteSampleWithMoreSamplePoints(FiniteSample): - CONFIG = { - "dx" : 0.05 - } - -class FeelsRelatedToAnIntegral(TeacherStudentsScene): - def construct(self): - self.student_says( - "Seems integral-ish...", - target_mode = "maybe" - ) - self.play(self.teacher.change_mode, "happy") - self.wait(2) - -class IntegralOfSine(FiniteSample): - CONFIG = { - "thin_dx" : 0.01, - "rect_opacity" : 0.75, - } - def construct(self): - self.force_skipping() - FiniteSample.construct(self) - self.remove(self.y_axis_label_mob) - self.remove(*self.x_axis_labels[::2]) - self.revert_to_original_skipping_status() - - self.put_average_in_corner() - self.write_integral() - self.show_riemann_rectangles() - self.let_dx_approach_zero() - self.bring_back_average() - self.distribute_dx() - self.let_dx_approach_zero(restore = False) - self.write_area_over_width() - self.show_moving_v_line() - - def put_average_in_corner(self): - self.average.save_state() - self.plusses.set_stroke(width = 0.5) - self.play( - self.average.scale, 0.75, - self.average.to_corner, DOWN+RIGHT, - ) - - def write_integral(self): - integral = TexMobject("\\int_0^\\pi", "\\sin(x)", "\\,dx") - integral.move_to(self.graph_portion_between_bounds) - integral.to_edge(UP) - - self.play(Write(integral)) - self.wait(2) - - self.integral = integral - - def show_riemann_rectangles(self): - kwargs = { - "dx" : self.dx, - "x_min" : self.bounds[0], - "x_max" : self.bounds[1], - "fill_opacity" : self.rect_opacity, - } - rects = self.get_riemann_rectangles(self.graph, **kwargs) - rects.set_stroke(YELLOW, width = 1) - flat_rects = self.get_riemann_rectangles( - self.get_graph(lambda x : 0), - **kwargs - ) - thin_kwargs = dict(kwargs) - thin_kwargs["dx"] = self.thin_dx - thin_kwargs["stroke_width"] = 0 - self.thin_rects = self.get_riemann_rectangles( - self.graph, - **thin_kwargs - ) - - - start_index = 20 - end_index = start_index + 5 - low_opacity = 0.5 - high_opacity = 1 - - start_rect = rects[start_index] - side_brace = Brace(start_rect, LEFT, buff = SMALL_BUFF) - bottom_brace = Brace(start_rect, DOWN, buff = SMALL_BUFF) - sin_x = TexMobject("\\sin(x)") - sin_x.next_to(side_brace, LEFT, SMALL_BUFF) - dx = bottom_brace.get_text("$dx$", buff = SMALL_BUFF) - - self.transform_between_riemann_rects( - flat_rects, rects, - replace_mobject_with_target_in_scene = True, - ) - self.remove(self.v_lines) - self.wait() - - rects.save_state() - self.play(*it.chain( - [ - ApplyMethod( - rect.set_style_data, BLACK, 1, - None, #Fill color - high_opacity if rect is start_rect else low_opacity - ) - for rect in rects - ], - list(map(GrowFromCenter, [side_brace, bottom_brace])), - list(map(Write, [sin_x, dx])), - )) - self.wait() - for i in range(start_index+1, end_index): - self.play( - rects[i-1].set_fill, None, low_opacity, - rects[i].set_fill, None, high_opacity, - side_brace.set_height, rects[i].get_height(), - side_brace.next_to, rects[i], LEFT, SMALL_BUFF, - bottom_brace.next_to, rects[i], DOWN, SMALL_BUFF, - MaintainPositionRelativeTo(sin_x, side_brace), - MaintainPositionRelativeTo(dx, bottom_brace), - ) - self.wait() - self.play( - rects.restore, - *list(map(FadeOut, [sin_x, dx, side_brace, bottom_brace])) - ) - - self.rects = rects - self.dx_brace = bottom_brace - self.dx_label = dx - - def let_dx_approach_zero(self, restore = True): - start_state = self.rects.copy() - self.transform_between_riemann_rects( - self.rects, self.thin_rects, - run_time = 3 - ) - self.wait(2) - if restore: - self.transform_between_riemann_rects( - self.rects, start_state.copy(), - run_time = 2, - ) - self.remove(self.rects) - self.rects = start_state - self.rects.set_fill(opacity = 1) - self.play( - self.rects.set_fill, None, - self.rect_opacity, - ) - self.wait() - - def bring_back_average(self): - num_samples = self.average[-1] - - example_dx = TexMobject("0.1") - example_dx.move_to(self.dx_label) - - input_range = Line(*[ - self.coords_to_point(bound, 0) - for bound in self.bounds - ]) - input_range.set_color(RED) - - #Bring back average - self.play( - self.average.restore, - self.average.center, - self.average.to_edge, UP, - self.integral.to_edge, DOWN, - run_time = 2 - ) - self.wait() - self.play( - Write(self.dx_brace), - Write(self.dx_label), - ) - self.wait() - self.play( - FadeOut(self.dx_label), - FadeIn(example_dx) - ) - self.play(Indicate(example_dx)) - self.wait() - self.play(ShowCreation(input_range)) - self.play(FadeOut(input_range)) - self.wait() - - #Ask how many there are - num_samples_copy = num_samples.copy() - v_lines = self.v_lines - self.play(*[ - ApplyFunction( - lambda l : l.shift(0.5*UP).set_color(GREEN), - line, - rate_func = squish_rate_func( - there_and_back, a, a+0.3 - ), - run_time = 3, - ) - for line, a in zip( - self.v_lines, - np.linspace(0, 0.7, len(self.v_lines)) - ) - ] + [ - num_samples_copy.set_color, GREEN - ]) - self.play(FadeOut(v_lines)) - self.wait() - - #Count number of samples - num_samples_copy.generate_target() - num_samples_copy.target.shift(DOWN + 0.5*LEFT) - rhs = TexMobject("\\approx", "\\pi", "/", "0.1") - rhs.next_to(num_samples_copy.target, RIGHT) - self.play( - MoveToTarget(num_samples_copy), - Write(rhs.get_part_by_tex("approx")), - ) - self.play(ShowCreation(input_range)) - self.play(ReplacementTransform( - self.x_axis_labels[1].copy(), - rhs.get_part_by_tex("pi") - )) - self.play(FadeOut(input_range)) - self.play( - ReplacementTransform( - example_dx.copy(), - rhs.get_part_by_tex("0.1") - ), - Write(rhs.get_part_by_tex("/")) - ) - self.wait(2) - - #Substitute number of samples - self.play(ReplacementTransform( - example_dx, self.dx_label - )) - dx = rhs.get_part_by_tex("0.1") - self.play(Transform( - dx, TexMobject("dx").move_to(dx) - )) - self.wait(2) - approx = rhs.get_part_by_tex("approx") - rhs.remove(approx) - self.play( - FadeOut(num_samples), - FadeOut(num_samples_copy), - FadeOut(approx), - rhs.next_to, self.average[1], DOWN - ) - self.wait() - - self.pi_over_dx = rhs - - def distribute_dx(self): - numerator, frac_line, denominator = self.average - pi, over, dx = self.pi_over_dx - integral = self.integral - - dx.generate_target() - lp, rp = parens = TexMobject("()") - parens.set_height(numerator.get_height()) - lp.next_to(numerator, LEFT) - rp.next_to(numerator, RIGHT) - dx.target.next_to(rp, RIGHT) - - self.play( - MoveToTarget(dx, path_arc = np.pi/2), - Write(parens), - frac_line.stretch_to_fit_width, - parens.get_width() + dx.get_width(), - frac_line.shift, dx.get_width()*RIGHT/2, - FadeOut(over) - ) - self.wait(2) - - average = VGroup(parens, numerator, dx, frac_line, pi) - integral.generate_target() - over_pi = TexMobject("\\frac{\\phantom{\\int \\sin(x)\\dx}}{\\pi}") - integral.target.set_width(over_pi.get_width()) - integral.target.next_to(over_pi, UP) - integral_over_pi = VGroup(integral.target, over_pi) - integral_over_pi.to_corner(UP+RIGHT) - arrow = Arrow(LEFT, RIGHT) - arrow.next_to(integral.target, LEFT) - - self.play( - average.scale, 0.9, - average.next_to, arrow, LEFT, - average.shift_onto_screen, - ShowCreation(arrow), - Write(over_pi), - MoveToTarget(integral, run_time = 2) - ) - self.wait(2) - self.play(*list(map(FadeOut, [self.dx_label, self.dx_brace]))) - - self.integral_over_pi = VGroup(integral, over_pi) - self.average = average - self.average_arrow = arrow - - def write_area_over_width(self): - self.play( - self.integral_over_pi.shift, 2*LEFT, - *list(map(FadeOut, [self.average, self.average_arrow])) - ) - - average_height = TextMobject("Average height = ") - area_over_width = TexMobject( - "{\\text{Area}", "\\over\\,", "\\text{Width}}", "=" - ) - area_over_width.get_part_by_tex("Area").set_color_by_gradient( - BLUE, GREEN - ) - area_over_width.next_to(self.integral_over_pi[1][0], LEFT) - average_height.next_to(area_over_width, LEFT) - - self.play(*list(map(FadeIn, [average_height, area_over_width]))) - self.wait() - - def show_moving_v_line(self): - mean = np.mean(self.bounds) - v_line = self.get_vertical_line_to_graph( - mean, self.graph, - line_class = DashedLine, - color = WHITE, - ) - self.play(ShowCreation(v_line)) - for count in range(2): - for x in self.bounds + [mean]: - self.play(self.get_v_line_change_anim( - v_line, self.graph, x, - run_time = 3 - )) - -class Approx31(Scene): - def construct(self): - tex = TexMobject("\\approx 31") - tex.set_width(FRAME_WIDTH - LARGE_BUFF) - tex.to_edge(LEFT) - self.play(Write(tex)) - self.wait(3) - -class LetsSolveThis(TeacherStudentsScene): - def construct(self): - expression = TexMobject( - "{\\int_0^\\pi ", " \\sin(x)", "\\,dx \\over \\pi}" - ) - expression.to_corner(UP+LEFT) - question = TextMobject( - "What's the antiderivative \\\\ of", - "$\\sin(x)$", - "?" - ) - for tex_mob in expression, question: - tex_mob.set_color_by_tex("sin", BLUE) - self.add(expression) - - self.teacher_says("Let's compute it.") - self.wait() - self.student_thinks(question) - self.wait(2) - -class Antiderivative(AverageOfSineStart): - CONFIG = { - "antideriv_color" : GREEN, - "deriv_color" : BLUE, - "riemann_rect_dx" : 0.01, - "y_axis_label" : "", - "graph_origin" : 4*LEFT + DOWN, - } - def construct(self): - self.setup_axes() - self.add_x_axis_labels() - self.negate_derivative_of_cosine() - self.walk_through_slopes() - self.apply_ftoc() - self.show_difference_in_antiderivative() - self.comment_on_area() - self.divide_by_pi() - self.set_color_antiderivative_fraction() - self.show_slope() - self.bring_back_derivative() - self.show_tangent_slope() - - def add_x_axis_labels(self): - AverageOfSineStart.add_x_axis_labels(self) - self.remove(*self.x_axis_labels[::2]) - - def negate_derivative_of_cosine(self): - cos, neg_cos, sin, neg_sin = graphs = [ - self.get_graph(func) - for func in [ - np.cos, - lambda x : -np.cos(x), - np.sin, - lambda x : -np.sin(x), - ] - ] - VGroup(cos, neg_cos).set_color(self.antideriv_color) - VGroup(sin, neg_sin).set_color(self.deriv_color) - labels = ["\\cos(x)", "-\\cos(x)", "\\sin(x)", "-\\sin(x)"] - x_vals = [2*np.pi, 2*np.pi, 5*np.pi/2, 5*np.pi/2] - vects = [UP, DOWN, UP, DOWN] - for graph, label, x_val, vect in zip(graphs, labels, x_vals, vects): - graph.label = self.get_graph_label( - graph, label, - x_val = x_val, - direction = vect, - buff = SMALL_BUFF - ) - - derivs = [] - for F, f in ("\\cos", "-\\sin"), ("-\\cos", "\\sin"): - deriv = TexMobject( - "{d(", F, ")", "\\over\\,", "dx}", "(x)", - "=", f, "(x)" - ) - deriv.set_color_by_tex(F, self.antideriv_color) - deriv.set_color_by_tex(f, self.deriv_color) - deriv.to_edge(UP) - derivs.append(deriv) - cos_deriv, neg_cos_deriv = derivs - - self.add(cos_deriv) - for graph in cos, neg_sin: - self.play( - ShowCreation(graph, rate_func = smooth), - Write( - graph.label, - rate_func = squish_rate_func(smooth, 0.3, 1) - ), - run_time = 2 - ) - self.wait() - self.wait() - self.play(*[ - ReplacementTransform(*pair) - for pair in [ - (derivs), - (cos, neg_cos), - (cos.label, neg_cos.label), - (neg_sin, sin), - (neg_sin.label, sin.label), - ] - ]) - self.wait(2) - - self.neg_cos = neg_cos - self.sin = sin - self.deriv = neg_cos_deriv - - def walk_through_slopes(self): - neg_cos = self.neg_cos - sin = self.sin - - faders = sin, sin.label - for mob in faders: - mob.save_state() - sin_copy = self.get_graph( - np.sin, - x_min = 0, - x_max = 2*np.pi, - color = BLUE, - ) - v_line = self.get_vertical_line_to_graph( - 0, neg_cos, - line_class = DashedLine, - color = WHITE - ) - - ss_group = self.get_secant_slope_group( - 0, neg_cos, - dx = 0.001, - secant_line_color = YELLOW - ) - def quad_smooth(t): - return 0.25*(np.floor(4*t) + smooth((4*t) % 1)) - - self.play(*[ - ApplyMethod(m.fade, 0.6) - for m in faders - ]) - self.wait() - self.play(*list(map(ShowCreation, ss_group)), run_time = 2) - kwargs = { - "run_time" : 20, - "rate_func" : quad_smooth, - } - v_line_anim = self.get_v_line_change_anim( - v_line, sin_copy, 2*np.pi, - **kwargs - ) - self.animate_secant_slope_group_change( - ss_group, - target_x = 2*np.pi, - added_anims = [ - ShowCreation(sin_copy, **kwargs), - v_line_anim - ], - **kwargs - ) - self.play( - *list(map(FadeOut, [ss_group, v_line, sin_copy])) - ) - self.wait() - - self.ss_group = ss_group - - def apply_ftoc(self): - deriv = self.deriv - integral = TexMobject( - "\\int", "^\\pi", "_0", "\\sin(x)", "\\,dx" - ) - rhs = TexMobject( - "=", "\\big(", "-\\cos", "(", "\\pi", ")", "\\big)", - "-", "\\big(", "-\\cos", "(", "0", ")", "\\big)", - ) - rhs.next_to(integral, RIGHT) - equation = VGroup(integral, rhs) - equation.to_corner(UP+RIGHT, buff = MED_SMALL_BUFF) - (start_pi, end_pi), (start_zero, end_zero) = start_end_pairs = [ - [ - m.get_part_by_tex(tex) - for m in (integral, rhs) - ] - for tex in ("\\pi", "0") - ] - - for tex_mob in integral, rhs: - tex_mob.set_color_by_tex("sin", self.deriv_color) - tex_mob.set_color_by_tex("cos", self.antideriv_color) - tex_mob.set_color_by_tex("0", YELLOW) - tex_mob.set_color_by_tex("\\pi", YELLOW) - - self.play( - Write(integral), - self.deriv.scale, 0.5, - self.deriv.center, - self.deriv.to_edge, LEFT, MED_SMALL_BUFF, - self.deriv.shift, UP, - ) - self.wait() - - self.play(FadeIn( - VGroup(*[part for part in rhs if part not in [end_pi, end_zero]]), - lag_ratio = 0.5, - run_time = 2, - )) - self.wait() - for start, end in start_end_pairs: - self.play(ReplacementTransform( - start.copy(), end, - path_arc = np.pi/6, - run_time = 2 - )) - self.wait() - - self.integral = integral - self.rhs = rhs - - def show_difference_in_antiderivative(self): - pi_point, zero_point = points = [ - self.input_to_graph_point(x, self.neg_cos) - for x in reversed(self.bounds) - ] - interim_point = pi_point[0]*RIGHT + zero_point[1]*UP - pi_dot, zero_dot = dots = [ - Dot(point, color = YELLOW) - for point in points - ] - v_line = DashedLine(pi_point, interim_point) - h_line = DashedLine(interim_point, zero_point) - v_line_brace = Brace(v_line, RIGHT) - two_height_label = v_line_brace.get_text( - "$2$", buff = SMALL_BUFF - ) - two_height_label.add_background_rectangle() - - pi = self.x_axis_labels[1] - #Horrible hack - black_pi = pi.copy().set_color(BLACK) - self.add(black_pi, pi) - - cos_tex = self.rhs.get_part_by_tex("cos") - - self.play(ReplacementTransform( - cos_tex.copy(), pi_dot - )) - self.wait() - moving_dot = pi_dot.copy() - self.play( - ShowCreation(v_line), - # Animation(pi), - pi.shift, 0.8*pi.get_width()*(LEFT+UP), - moving_dot.move_to, interim_point, - ) - self.play( - ShowCreation(h_line), - ReplacementTransform(moving_dot, zero_dot) - ) - self.play(GrowFromCenter(v_line_brace)) - self.wait(2) - self.play(Write(two_height_label)) - self.wait(2) - - self.v_line = v_line - self.h_line = h_line - self.pi_dot = pi_dot - self.zero_dot = zero_dot - self.v_line_brace = v_line_brace - self.two_height_label = two_height_label - - def comment_on_area(self): - rects = self.get_riemann_rectangles( - self.sin, - dx = self.riemann_rect_dx, - stroke_width = 0, - fill_opacity = 0.7, - x_min = self.bounds[0], - x_max = self.bounds[1], - ) - area_two = TexMobject("2").replace( - self.two_height_label - ) - - self.play(Write(rects)) - self.wait() - self.play(area_two.move_to, rects) - self.wait(2) - - self.rects = rects - self.area_two = area_two - - def divide_by_pi(self): - integral = self.integral - rhs = self.rhs - equals = rhs[0] - rhs_without_eq = VGroup(*rhs[1:]) - frac_lines = VGroup(*[ - TexMobject("\\over\\,").stretch_to_fit_width( - mob.get_width() - ).move_to(mob) - for mob in (integral, rhs_without_eq) - ]) - frac_lines.shift( - (integral.get_height()/2 + SMALL_BUFF)*DOWN - ) - pi_minus_zeros = VGroup(*[ - TexMobject("\\pi", "-", "0").next_to(line, DOWN) - for line in frac_lines - ]) - for tex_mob in pi_minus_zeros: - for tex in "pi", "0": - tex_mob.set_color_by_tex(tex, YELLOW) - - answer = TexMobject(" = \\frac{2}{\\pi}") - answer.next_to( - frac_lines, RIGHT, - align_using_submobjects = True - ) - answer.shift_onto_screen() - - self.play( - equals.next_to, frac_lines[0].copy(), RIGHT, - rhs_without_eq.next_to, frac_lines[1].copy(), UP, - *list(map(Write, frac_lines)) - ) - self.play(*[ - ReplacementTransform( - integral.get_part_by_tex(tex).copy(), - pi_minus_zeros[0].get_part_by_tex(tex) - ) - for tex in ("\\pi","0") - ] + [ - Write(pi_minus_zeros[0].get_part_by_tex("-")) - ]) - self.play(*[ - ReplacementTransform( - rhs.get_part_by_tex( - tex, substring = False - ).copy(), - pi_minus_zeros[1].get_part_by_tex(tex) - ) - for tex in ("\\pi", "-", "0") - ]) - self.wait(2) - - full_equation = VGroup( - integral, frac_lines, rhs, pi_minus_zeros - ) - background_rect = BackgroundRectangle(full_equation, fill_opacity = 1) - background_rect.stretch_in_place(1.2, dim = 1) - full_equation.add_to_back(background_rect) - self.play( - full_equation.shift, - (answer.get_width()+MED_LARGE_BUFF)*LEFT - ) - self.play(Write(answer)) - self.wait() - - self.antiderivative_fraction = VGroup( - rhs_without_eq, - frac_lines[1], - pi_minus_zeros[1] - ) - self.integral_fraction = VGroup( - integral, - frac_lines[0], - pi_minus_zeros[0], - equals - ) - - def set_color_antiderivative_fraction(self): - fraction = self.antiderivative_fraction - big_rect = Rectangle( - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 0.75, - ) - big_rect.set_width(FRAME_WIDTH) - big_rect.set_height(FRAME_HEIGHT) - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - - self.play( - FadeIn(big_rect), - FadeIn(morty), - Animation(fraction) - ) - self.play(morty.change, "raise_right_hand", fraction) - self.play(Blink(morty)) - self.wait(2) - self.play( - FadeOut(big_rect), - FadeOut(morty), - Animation(fraction) - ) - - def show_slope(self): - line = Line( - self.zero_dot.get_center(), - self.pi_dot.get_center(), - ) - line.set_color(RED) - line.scale_in_place(1.2) - - new_v_line = self.v_line.copy().set_color(RED) - new_h_line = self.h_line.copy().set_color(RED) - - pi = TexMobject("\\pi") - pi.next_to(self.h_line, DOWN) - - self.play( - FadeOut(self.rects), - FadeOut(self.area_two) - ) - self.play(ShowCreation(new_v_line)) - self.play( - ShowCreation(new_h_line), - Write(pi) - ) - self.wait() - self.play(ShowCreation(line, run_time = 2)) - self.wait() - - def bring_back_derivative(self): - self.play( - FadeOut(self.integral_fraction), - self.deriv.scale, 1.7, - self.deriv.to_corner, UP+LEFT, MED_LARGE_BUFF, - self.deriv.shift, MED_SMALL_BUFF*DOWN, - ) - self.wait() - - def show_tangent_slope(self): - ss_group = self.get_secant_slope_group( - 0, self.neg_cos, - dx = 0.001, - secant_line_color = YELLOW, - secant_line_length = 4, - ) - - self.play(*list(map(ShowCreation, ss_group)), run_time = 2) - for count in range(2): - for x in reversed(self.bounds): - self.animate_secant_slope_group_change( - ss_group, - target_x = x, - run_time = 6, - ) - -class GeneralAverage(AverageOfContinuousVariable): - CONFIG = { - "bounds" : [1, 6], - "bound_colors" : [GREEN, RED], - "graph_origin" : 5*LEFT + 2*DOWN, - "num_rect_iterations" : 4, - "max_dx" : 0.25, - } - def construct(self): - self.setup_axes() - self.add_graph() - self.ask_about_average() - self.show_signed_area() - self.put_area_away() - self.show_finite_sample() - self.show_improving_samples() - - def add_graph(self): - graph = self.get_graph(self.func) - graph_label = self.get_graph_label(graph, "f(x)") - v_lines = VGroup(*[ - self.get_vertical_line_to_graph( - x, graph, line_class = DashedLine - ) - for x in self.bounds - ]) - for line, color in zip(v_lines, self.bound_colors): - line.set_color(color) - labels = list(map(TexMobject, "ab")) - for line, label in zip(v_lines, labels): - vect = line.get_start()-line.get_end() - label.next_to(line, vect/get_norm(vect)) - label.set_color(line.get_color()) - - self.y_axis_label_mob.shift(0.7*LEFT) - - self.play( - ShowCreation(graph), - Write( - graph_label, - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - run_time = 2 - ) - for line, label in zip(v_lines, labels): - self.play( - Write(label), - ShowCreation(line) - ) - self.wait() - - self.graph = graph - self.graph_label = graph_label - self.bounds_labels = labels - self.bound_lines = v_lines - - def ask_about_average(self): - v_line = self.get_vertical_line_to_graph( - self.bounds[0], self.graph, - line_class = DashedLine, - color = WHITE - ) - average = TextMobject("Average = ") - fraction = TexMobject( - "{\\displaystyle \\int", "^b", "_a", "f(x)", "\\,dx", - "\\over", "b", "-", "a}" - ) - for color, tex in zip(self.bound_colors, "ab"): - fraction.set_color_by_tex(tex, color) - fraction.set_color_by_tex("displaystyle", WHITE) - integral = VGroup(*fraction[:5]) - denominator = VGroup(*fraction[5:]) - average.next_to(fraction.get_part_by_tex("over"), LEFT) - group = VGroup(average, fraction) - group.center().to_edge(UP).shift(LEFT) - - self.count = 0 - def next_v_line_anim(): - target = self.bounds[0] if self.count%2 == 1 else self.bounds[1] - self.count += 1 - return self.get_v_line_change_anim( - v_line, self.graph, target, - run_time = 4, - ) - - self.play( - next_v_line_anim(), - Write(average, run_time = 2), - ) - self.play( - next_v_line_anim(), - Write( - VGroup(*[ - fraction.get_part_by_tex(tex) - for tex in ("int", "f(x)", "dx", "over") - ]), - rate_func = squish_rate_func(smooth, 0.25, 0.75), - run_time = 4 - ), - *[ - ReplacementTransform( - label.copy(), - fraction.get_part_by_tex(tex, substring = False), - run_time = 2 - ) - for label, tex in zip( - self.bounds_labels, - ["_a", "^b"] - ) - ] - ) - self.play( - next_v_line_anim(), - Write( - fraction.get_part_by_tex("-"), - run_time = 4, - rate_func = squish_rate_func(smooth, 0.5, 0.75), - ), - *[ - ReplacementTransform( - label.copy(), - fraction.get_part_by_tex(tex, substring = False), - run_time = 4, - rate_func = squish_rate_func(smooth, 0.25, 0.75) - ) - for label, tex in zip( - self.bounds_labels, - ["a}", "b"] - ) - ] - - ) - self.play(next_v_line_anim()) - self.play(FadeOut(v_line)) - - self.average_expression = VGroup(average, fraction) - - def show_signed_area(self): - rect_list = self.get_riemann_rectangles_list( - self.graph, - self.num_rect_iterations, - max_dx = self.max_dx, - x_min = self.bounds[0], - x_max = self.bounds[1], - end_color = BLUE, - fill_opacity = 0.75, - stroke_width = 0.25, - ) - rects = rect_list[0] - plus = TexMobject("+") - plus.move_to(self.coords_to_point(2, 2)) - minus = TexMobject("-") - minus.move_to(self.coords_to_point(5.24, -1)) - - self.play(FadeIn( - rects, - run_time = 2, - lag_ratio = 0.5 - )) - for new_rects in rect_list[1:]: - self.transform_between_riemann_rects(rects, new_rects) - self.play(Write(plus)) - self.play(Write(minus)) - self.wait(2) - - self.area = VGroup(rects, plus, minus) - - def put_area_away(self): - self.play( - FadeOut(self.area), - self.average_expression.scale, 0.75, - self.average_expression.to_corner, DOWN+RIGHT, - ) - self.wait() - - def show_finite_sample(self): - v_lines = self.get_vertical_lines_to_graph( - self.graph, - x_min = self.bounds[0], - x_max = self.bounds[1], - color = GREEN - ) - for line in v_lines: - if self.y_axis.point_to_number(line.get_end()) < 0: - line.set_color(RED) - line.save_state() - - line_pair = VGroup(*v_lines[6:8]) - brace = Brace(line_pair, DOWN) - dx = brace.get_text("$dx$") - - num_samples = TextMobject("Num. samples") - approx = TexMobject("\\approx") - rhs = TexMobject("{b", "-", "a", "\\over", "dx}") - for tex, color in zip("ab", self.bound_colors): - rhs.set_color_by_tex(tex, color) - expression = VGroup(num_samples, approx, rhs) - expression.arrange(RIGHT) - expression.next_to(self.y_axis, RIGHT) - rhs_copy = rhs.copy() - - f_brace = Brace(line_pair, LEFT, buff = 0) - f_x = f_brace.get_text("$f(x)$") - add_up_f_over = TexMobject("\\text{Add up $f(x)$}", "\\over") - add_up_f_over.next_to(num_samples, UP) - add_up_f_over.to_edge(UP) - - - self.play(ShowCreation(v_lines, run_time = 2)) - self.play(*list(map(Write, [brace, dx]))) - self.wait() - self.play(Write(VGroup(num_samples, approx, *rhs[:-1]))) - self.play(ReplacementTransform( - dx.copy(), rhs.get_part_by_tex("dx") - )) - self.wait(2) - - self.play( - FadeIn(add_up_f_over), - *[ - ApplyFunction( - lambda m : m.fade().set_stroke(width = 2), - v_line - ) - for v_line in v_lines - ] - ) - self.play(*[ - ApplyFunction( - lambda m : m.restore().set_stroke(width = 5), - v_line, - run_time = 3, - rate_func = squish_rate_func( - there_and_back, a, a+0.2 - ) - ) - for v_line, a in zip(v_lines, np.linspace(0, 0.8, len(v_lines))) - ]) - self.play(*[vl.restore for vl in v_lines]) - self.play(rhs_copy.next_to, add_up_f_over, DOWN) - self.wait(2) - frac_line = add_up_f_over[1] - self.play( - FadeOut(rhs_copy.get_part_by_tex("over")), - rhs_copy.get_part_by_tex("dx").next_to, - add_up_f_over[0], RIGHT, SMALL_BUFF, - frac_line.scale_about_point, 1.2, frac_line.get_left(), - frac_line.stretch_to_fit_height, frac_line.get_height(), - ) - rhs_copy.remove(rhs_copy.get_part_by_tex("over")) - self.wait(2) - - int_fraction = self.average_expression[1].copy() - int_fraction.generate_target() - int_fraction.target.next_to(add_up_f_over, RIGHT, LARGE_BUFF) - int_fraction.target.shift_onto_screen() - double_arrow = TexMobject("\\Leftrightarrow") - double_arrow.next_to( - int_fraction.target.get_part_by_tex("over"), LEFT - ) - - self.play( - MoveToTarget(int_fraction), - VGroup(add_up_f_over, rhs_copy).shift, 0.4*DOWN - ) - self.play(Write(double_arrow)) - self.play(*list(map(FadeOut, [ - dx, brace, num_samples, approx, rhs - ]))) - self.wait() - - self.v_lines = v_lines - - def show_improving_samples(self): - stroke_width = self.v_lines[0].get_stroke_width() - new_v_lines_list = [ - self.get_vertical_lines_to_graph( - self.graph, - x_min = self.bounds[0], - x_max = self.bounds[1], - num_lines = len(self.v_lines)*(2**n), - color = GREEN, - stroke_width = float(stroke_width)/n - ) - for n in range(1, 4) - ] - for new_v_lines in new_v_lines_list: - for line in new_v_lines: - if self.y_axis.point_to_number(line.get_end()) < 0: - line.set_color(RED) - - for new_v_lines in new_v_lines_list: - self.play(Transform( - self.v_lines, new_v_lines, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait() - - #### - - def func(self, x): - return 0.09*(x+1)*(x-4)*(x-8) - -class GeneralAntiderivative(GeneralAverage): - def construct(self): - self.force_skipping() - self.setup_axes() - self.add_graph() - self.revert_to_original_skipping_status() - - self.fade_existing_graph() - self.add_fraction() - self.add_antiderivative_graph() - self.show_average_in_terms_of_F() - self.draw_slope() - self.show_tangent_line_slopes() - - def fade_existing_graph(self): - self.graph.fade(0.3) - self.graph_label.fade(0.3) - self.bound_lines.fade(0.3) - - def add_fraction(self): - fraction = TexMobject( - "{\\displaystyle \\int", "^b", "_a", "f(x)", "\\,dx", - "\\over", "b", "-", "a}" - ) - for tex, color in zip("ab", self.bound_colors): - fraction.set_color_by_tex(tex, color) - fraction.set_color_by_tex("display", WHITE) - - fraction.scale(0.8) - fraction.next_to(self.y_axis, RIGHT) - fraction.to_edge(UP, buff = MED_SMALL_BUFF) - self.add(fraction) - - self.fraction = fraction - - def add_antiderivative_graph(self): - x_max = 9.7 - antideriv_graph = self.get_graph( - lambda x : scipy.integrate.quad( - self.graph.underlying_function, - 1, x - )[0], - color = YELLOW, - x_max = x_max, - ) - antideriv_graph_label = self.get_graph_label( - antideriv_graph, "F(x)", - x_val = x_max - ) - - deriv = TexMobject( - "{dF", "\\over", "dx}", "(x)", "=", "f(x)" - ) - deriv.set_color_by_tex("dF", antideriv_graph.get_color()) - deriv.set_color_by_tex("f(x)", BLUE) - deriv.next_to( - antideriv_graph_label, DOWN, MED_LARGE_BUFF, LEFT - ) - - self.play( - ShowCreation(antideriv_graph), - Write( - antideriv_graph_label, - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - run_time = 2, - ) - self.wait() - self.play(Write(deriv)) - self.wait() - - self.antideriv_graph = antideriv_graph - - def show_average_in_terms_of_F(self): - new_fraction = TexMobject( - "=", - "{F", "(", "b", ")", "-", "F", "(", "a", ")", - "\\over", - "b", "-", "a}" - ) - for tex, color in zip("abF", self.bound_colors+[YELLOW]): - new_fraction.set_color_by_tex(tex, color) - new_fraction.next_to( - self.fraction.get_part_by_tex("over"), RIGHT, - align_using_submobjects = True - ) - - to_write = VGroup(*it.chain(*[ - new_fraction.get_parts_by_tex(tex) - for tex in "=F()-" - ])) - to_write.remove(new_fraction.get_parts_by_tex("-")[-1]) - - numerator = VGroup(*new_fraction[1:10]) - denominator = VGroup(*new_fraction[-3:]) - - self.play(Write(to_write)) - self.play(*[ - ReplacementTransform( - label.copy(), - new_fraction.get_part_by_tex(tex), - run_time = 2, - rate_func = squish_rate_func(smooth, a, a+0.7) - ) - for label, tex, a in zip( - self.bounds_labels, "ab", [0, 0.3] - ) - ]) - self.wait() - - self.show_change_in_height(numerator.copy()) - self.shift_antideriv_graph_up_and_down() - self.play(Write(VGroup(*new_fraction[-4:]))) - self.wait() - - h_line_brace = Brace(self.h_line, DOWN) - denominator_copy = denominator.copy() - a_label = self.bounds_labels[0] - self.play( - GrowFromCenter(h_line_brace), - a_label.shift, 0.7*a_label.get_width()*LEFT, - a_label.shift, 2.2*a_label.get_height()*UP, - ) - self.play( - denominator_copy.next_to, h_line_brace, - DOWN, SMALL_BUFF - ) - self.wait(3) - - def show_change_in_height(self, numerator): - numerator.add_to_back(BackgroundRectangle(numerator)) - a_point, b_point = points = [ - self.input_to_graph_point(x, self.antideriv_graph) - for x in self.bounds - ] - interim_point = b_point[0]*RIGHT + a_point[1]*UP - - v_line = Line(b_point, interim_point) - h_line = Line(interim_point, a_point) - VGroup(v_line, h_line).set_color(WHITE) - brace = Brace(v_line, RIGHT, buff = SMALL_BUFF) - - graph_within_bounds = self.get_graph( - self.antideriv_graph.underlying_function, - x_min = self.bounds[0], - x_max = self.bounds[1], - color = self.antideriv_graph.get_color() - ) - - b_label = self.bounds_labels[1] - self.play( - self.antideriv_graph.fade, 0.7, - Animation(graph_within_bounds) - ) - self.play( - ShowCreation(v_line), - b_label.shift, b_label.get_width()*RIGHT, - b_label.shift, 1.75*b_label.get_height()*DOWN, - ) - self.play(ShowCreation(h_line)) - self.play( - numerator.scale, 0.75, - numerator.next_to, brace.copy(), RIGHT, SMALL_BUFF, - GrowFromCenter(brace), - ) - self.wait(2) - - self.antideriv_graph.add( - graph_within_bounds, v_line, h_line, numerator, brace - ) - self.h_line = h_line - self.graph_points_at_bounds = points - - def shift_antideriv_graph_up_and_down(self): - for vect in 2*UP, 3*DOWN, UP: - self.play( - self.antideriv_graph.shift, vect, - run_time = 2 - ) - self.wait() - - def draw_slope(self): - line = Line(*self.graph_points_at_bounds) - line.set_color(PINK) - line.scale_in_place(1.3) - - self.play(ShowCreation(line, run_time = 2)) - self.wait() - - def show_tangent_line_slopes(self): - ss_group = self.get_secant_slope_group( - x = self.bounds[0], - graph = self.antideriv_graph, - dx = 0.001, - secant_line_color = BLUE, - secant_line_length = 2, - ) - - self.play(*list(map(ShowCreation, ss_group))) - for x in range(2): - for bound in reversed(self.bounds): - self.animate_secant_slope_group_change( - ss_group, - target_x = bound, - run_time = 5, - ) - -class LastVideoWrapper(Scene): - def construct(self): - title = TextMobject("Chapter 8: Integrals") - title.to_edge(UP) - rect = Rectangle(height = 9, width = 16) - rect.set_stroke(WHITE) - rect.set_height(1.5*FRAME_Y_RADIUS) - rect.next_to(title, DOWN) - - self.play(Write(title), ShowCreation(rect)) - self.wait(5) - -class ASecondIntegralSensation(TeacherStudentsScene): - def construct(self): - finite_average = TexMobject("{1+5+4+2 \\over 4}") - numbers = VGroup(*finite_average[0:7:2]) - plusses = VGroup(*finite_average[1:7:2]) - denominator = VGroup(*finite_average[7:]) - finite_average.to_corner(UP+LEFT) - finite_average.to_edge(LEFT) - - continuum = UnitInterval( - color = GREY, - unit_size = 6 - ) - continuum.next_to(finite_average, RIGHT, 2) - line = Line(continuum.get_left(), continuum.get_right()) - line.set_color(YELLOW) - arrow = Arrow(DOWN+RIGHT, ORIGIN) - arrow.next_to(line.get_start(), DOWN+RIGHT, SMALL_BUFF) - - sigma_to_integral = TexMobject( - "\\sum \\Leftrightarrow \\int" - ) - sigma_to_integral.next_to( - self.teacher.get_corner(UP+LEFT), UP, MED_LARGE_BUFF - ) - - self.teacher_says( - "A second integral \\\\ sensation" - ) - self.change_student_modes(*["erm"]*3) - self.wait() - self.play( - Write(numbers), - RemovePiCreatureBubble(self.teacher), - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = numbers - ) - self.play(Write(plusses)) - self.wait() - self.play(Write(denominator)) - self.wait() - - self.change_student_modes( - *["confused"]*3, - look_at_arg = continuum, - added_anims = [Write(continuum, run_time = 2)] - ) - self.play(ShowCreation(arrow)) - arrow.save_state() - self.play( - arrow.next_to, line.copy().get_end(), DOWN+RIGHT, SMALL_BUFF, - ShowCreation(line), - run_time = 2 - ) - self.play(*list(map(FadeOut, [arrow]))) - self.wait(2) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = sigma_to_integral, - added_anims = [ - Write(sigma_to_integral), - self.teacher.change_mode, "raise_right_hand" - ] - ) - self.wait(3) - -class Chapter9PatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "CrypticSwarm", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Karan Bhargava", - "Ankit Agarwal", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Markus Persson", - "Dan Buchoff", - "Derek Dai", - "Joseph John Cox", - "Luc Ritchie", - "Zac Wentzell", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "Nils Schneider", - "James Thornton", - "Mustafa Mahdi", - "Jonathan Eppele", - "Mathew Bramson", - "Jerry Ling", - "Mark Govea", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ], - } - -class Thumbnail(GraphScene): - CONFIG = { - "x_min" : -0.2, - "x_max" : 3.5, - "x_leftmost_tick" : 0, - "x_tick_frequency" : np.pi/4, - "x_axis_label" : "", - "y_min" : -0.75, - "y_max" : 0.75, - "y_axis_height" : 4.5, - "y_tick_frequency" : 0.25, - "y_axis_label" : "" - } - def construct(self): - self.setup_axes() - self.remove(self.axes) - - sine = self.get_graph(np.sin) - rects = self.get_riemann_rectangles( - sine, - x_min = 0, - x_max = np.pi, - dx = 0.01, - stroke_width = 0, - ) - sine.add_to_back(rects) - sine.add(self.axes.copy()) - sine.to_corner(UP+LEFT, buff = SMALL_BUFF) - sine.scale(0.9) - - area = TextMobject("Area") - area.scale(3) - area.move_to(rects) - - cosine = self.get_graph(lambda x : -np.cos(x)) - cosine.set_stroke(GREEN, 8) - line = self.get_secant_slope_group( - 0.75*np.pi, cosine, - dx = 0.01 - ).secant_line - line.set_stroke(PINK, 7) - cosine.add(line) - cosine.add(self.axes.copy()) - cosine.scale(0.7) - cosine.to_corner(DOWN+RIGHT, buff = MED_SMALL_BUFF) - - slope = TextMobject("Slope") - slope.scale(3) - # slope.next_to(cosine, LEFT, buff = 0) - # slope.to_edge(DOWN) - slope.to_corner(DOWN+RIGHT) - - double_arrow = DoubleArrow( - area.get_bottom(), - slope.get_left(), - color = YELLOW, - tip_length = 0.75, - buff = MED_LARGE_BUFF - ) - double_arrow.set_stroke(width = 18) - - triangle = Polygon( - ORIGIN, UP, UP+RIGHT, - stroke_width = 0, - fill_color = BLUE_E, - fill_opacity = 0.5, - ) - triangle.stretch_to_fit_width(FRAME_WIDTH) - triangle.stretch_to_fit_height(FRAME_HEIGHT) - triangle.to_corner(UP+LEFT, buff = 0) - - alt_triangle = triangle.copy() - alt_triangle.rotate(np.pi) - alt_triangle.set_fill(BLACK, 1) - - self.add( - triangle, sine, area, - alt_triangle, cosine, slope, - double_arrow, - ) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eoc/footnote.py b/from_3b1b/old/eoc/footnote.py deleted file mode 100644 index 444324bd..00000000 --- a/from_3b1b/old/eoc/footnote.py +++ /dev/null @@ -1,917 +0,0 @@ -import scipy -import math - -from manimlib.imports import * -from from_3b1b.old.eoc.chapter1 import Car, MoveCar -from from_3b1b.old.eoc.chapter10 import derivative - -#revert_to_original_skipping_status - - -######## - -class Introduce(TeacherStudentsScene): - def construct(self): - words = TextMobject("Next up is \\\\", "Taylor series") - words.set_color_by_tex("Taylor", BLUE) - derivs = VGroup(*[ - TexMobject( - "{d", "^%d"%n, "f \\over dx", "^%d}"%n - ).set_color_by_tex(str(n), YELLOW) - for n in range(2, 5) - ]) - derivs.next_to(self.teacher, UP, LARGE_BUFF) - second_deriv = derivs[0] - second_deriv.save_state() - card_dot = Dot(radius = SMALL_BUFF) - screen_rect = ScreenRectangle(height = 4) - screen_rect.move_to(3*UP, UP) - - self.teacher_says(words, run_time = 2) - taylor_series = words.get_part_by_tex("Taylor").copy() - self.change_student_modes(*["happy"]*3) - self.play( - RemovePiCreatureBubble( - self.teacher, - target_mode = "raise_right_hand" - ), - taylor_series.next_to, screen_rect.copy(), UP, - ShowCreation(screen_rect) - ) - card_dot.move_to(taylor_series) - card_dot.generate_target() - card_dot.target.set_fill(opacity = 0) - card_dot.target.to_edge(RIGHT, buff = MED_SMALL_BUFF) - arrow = Arrow( - taylor_series, card_dot.target, - buff = MED_SMALL_BUFF, - color = WHITE - ) - self.play(FadeIn(second_deriv)) - self.wait(2) - self.play(Transform(second_deriv, derivs[1])) - self.wait(2) - self.play(MoveToTarget(card_dot)) - self.play(ShowCreation(arrow)) - self.wait() - self.play(Transform(second_deriv, derivs[2])) - self.change_student_modes(*["erm"]*3) - self.wait() - self.play(second_deriv.restore) - self.wait(2) - -class SecondDerivativeGraphically(GraphScene): - CONFIG = { - "x1" : 0, - "x2" : 4, - "x3" : 8, - "y" : 4, - "deriv_color" : YELLOW, - "second_deriv_color" : GREEN, - } - def construct(self): - self.force_skipping() - - self.setup_axes() - self.draw_f() - self.show_derivative() - self.write_second_derivative() - self.show_curvature() - - self.revert_to_original_skipping_status() - self.contrast_big_and_small_concavity() - - def draw_f(self): - def func(x): - return 0.1*(x-self.x1)*(x-self.x2)*(x-self.x3) + self.y - - graph = self.get_graph(func) - graph_label = self.get_graph_label(graph, "f(x)") - - self.play( - ShowCreation(graph, run_time = 2), - Write( - graph_label, - run_time = 2, - rate_func = squish_rate_func(smooth, 0.5, 1) - ) - ) - self.wait() - - self.graph = graph - self.graph_label = graph_label - - def show_derivative(self): - deriv = TexMobject("\\frac{df}{dx}") - deriv.next_to(self.graph_label, DOWN, MED_LARGE_BUFF) - deriv.set_color(self.deriv_color) - ss_group = self.get_secant_slope_group( - 1, self.graph, - dx = 0.01, - secant_line_color = self.deriv_color - ) - - self.play( - Write(deriv), - *list(map(ShowCreation, ss_group)) - ) - self.animate_secant_slope_group_change( - ss_group, target_x = self.x3, - run_time = 5 - ) - self.wait() - self.animate_secant_slope_group_change( - ss_group, target_x = self.x2, - run_time = 3 - ) - self.wait() - - self.ss_group = ss_group - self.deriv = deriv - - def write_second_derivative(self): - second_deriv = TexMobject("\\frac{d^2 f}{dx^2}") - second_deriv.next_to(self.deriv, DOWN, MED_LARGE_BUFF) - second_deriv.set_color(self.second_deriv_color) - points = [ - self.input_to_graph_point(x, self.graph) - for x in (self.x2, self.x3) - ] - words = TextMobject("Change to \\\\ slope") - words.next_to( - center_of_mass(points), UP, 1.5*LARGE_BUFF - ) - arrows = [ - Arrow(words.get_bottom(), p, color = WHITE) - for p in points - ] - - self.play(Write(second_deriv)) - self.wait() - self.play( - Write(words), - ShowCreation( - arrows[0], - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - run_time = 2 - ) - self.animate_secant_slope_group_change( - self.ss_group, target_x = self.x3, - run_time = 3, - added_anims = [ - Transform( - *arrows, - run_time = 3, - path_arc = 0.75*np.pi - ), - ] - ) - self.play(FadeOut(arrows[0])) - self.animate_secant_slope_group_change( - self.ss_group, target_x = self.x2, - run_time = 3, - ) - - self.second_deriv_words = words - self.second_deriv = second_deriv - - def show_curvature(self): - positive_curve, negative_curve = [ - self.get_graph( - self.graph.underlying_function, - x_min = x_min, - x_max = x_max, - color = color, - ).set_stroke(width = 6) - for x_min, x_max, color in [ - (self.x2, self.x3, PINK), - (self.x1, self.x2, RED), - ] - ] - dot = Dot() - def get_dot_update_func(curve): - def update_dot(dot): - dot.move_to(curve.points[-1]) - return dot - return update_dot - - self.play( - ShowCreation(positive_curve, run_time = 3), - UpdateFromFunc(dot, get_dot_update_func(positive_curve)) - ) - self.play(FadeOut(dot)) - self.wait() - self.animate_secant_slope_group_change( - self.ss_group, target_x = self.x3, - run_time = 4, - added_anims = [Animation(positive_curve)] - ) - - self.play(*list(map(FadeOut, [self.ss_group, positive_curve]))) - self.animate_secant_slope_group_change( - self.ss_group, target_x = self.x1, - run_time = 0 - ) - self.play(FadeIn(self.ss_group)) - self.play( - ShowCreation(negative_curve, run_time = 3), - UpdateFromFunc(dot, get_dot_update_func(negative_curve)) - ) - self.play(FadeOut(dot)) - self.animate_secant_slope_group_change( - self.ss_group, target_x = self.x2, - run_time = 4, - added_anims = [Animation(negative_curve)] - ) - self.wait(2) - self.play(*list(map(FadeOut, [ - self.graph, self.ss_group, - negative_curve, self.second_deriv_words - ]))) - - def contrast_big_and_small_concavity(self): - colors = color_gradient([GREEN, WHITE], 3) - x0, y0 = 4, 2 - graphs = [ - self.get_graph(func, color = color) - for color, func in zip(colors, [ - lambda x : 5*(x - x0)**2 + y0, - lambda x : 0.2*(x - x0)**2 + y0, - lambda x : (x-x0) + y0, - ]) - ] - arg_rhs_list = [ - TexMobject("(", str(x0), ")", "=", str(rhs)) - for rhs in (10, 0.4, 0) - ] - for graph, arg_rhs in zip(graphs, arg_rhs_list): - graph.ss_group = self.get_secant_slope_group( - x0-1, graph, - dx = 0.001, - secant_line_color = YELLOW - ) - arg_rhs.move_to(self.second_deriv.get_center(), LEFT) - graph.arg_rhs = arg_rhs - graph = graphs[0] - - v_line = DashedLine(*[ - self.coords_to_point(x0, 0), - self.coords_to_point(x0, y0), - ]) - input_label = TexMobject(str(x0)) - input_label.next_to(v_line, DOWN) - - self.play(ShowCreation(graph, run_time = 2)) - self.play( - Write(input_label), - ShowCreation(v_line) - ) - self.play( - ReplacementTransform( - input_label.copy(), - graph.arg_rhs.get_part_by_tex(str(x0)) - ), - self.second_deriv.next_to, graph.arg_rhs.copy(), LEFT, SMALL_BUFF, - Write(VGroup(*[ - submob - for submob in graph.arg_rhs - if submob is not graph.arg_rhs.get_part_by_tex(str(x0)) - ])) - ) - self.wait() - self.play(FadeIn(graph.ss_group)) - self.animate_secant_slope_group_change( - graph.ss_group, target_x = x0 + 1, - run_time = 3, - ) - self.play(FadeOut(graph.ss_group)) - self.wait() - for new_graph in graphs[1:]: - self.play(Transform(graph, new_graph)) - self.play(Transform( - graph.arg_rhs, - new_graph.arg_rhs, - )) - self.play(FadeIn(new_graph.ss_group)) - self.animate_secant_slope_group_change( - new_graph.ss_group, target_x = x0 + 1, - run_time = 3, - ) - self.play(FadeOut(new_graph.ss_group)) - -class IntroduceNotation(TeacherStudentsScene): - def construct(self): - clunky_deriv = TexMobject( - "{d", "\\big(", "{df", "\\over", "dx}", "\\big)", - "\\over", "dx }" - ) - over_index = clunky_deriv.index_of_part( - clunky_deriv.get_parts_by_tex("\\over")[1] - ) - numerator = VGroup(*clunky_deriv[:over_index]) - denominator = VGroup(*clunky_deriv[over_index+1:]) - - - rp = clunky_deriv.get_part_by_tex("(") - lp = clunky_deriv.get_part_by_tex(")") - dfs, overs, dxs = list(map(clunky_deriv.get_parts_by_tex, [ - "df", "over", "dx" - ])) - df_over_dx = VGroup(dfs[0], overs[0], dxs[0]) - d = clunky_deriv.get_part_by_tex("d") - d_over_dx = VGroup(d, overs[1], dxs[1]) - - d2f_over_dx2 = TexMobject("{d^2 f", "\\over", "dx", "^2}") - d2f_over_dx2.set_color_by_tex("dx", YELLOW) - - for mob in clunky_deriv, d2f_over_dx2: - mob.next_to(self.teacher, UP+LEFT) - - for mob in numerator, denominator: - circle = Circle(color = YELLOW) - circle.replace(mob, stretch = True) - circle.scale_in_place(1.3) - mob.circle = circle - dx_to_zero = TexMobject("dx \\to 0") - dx_to_zero.set_color(YELLOW) - dx_to_zero.next_to(clunky_deriv, UP+LEFT) - - self.student_says( - "What's that notation?", - target_mode = "raise_left_hand" - ) - self.change_student_modes("confused", "raise_left_hand", "confused") - self.play( - FadeIn( - clunky_deriv, - run_time = 2, - lag_ratio = 0.5 - ), - RemovePiCreatureBubble(self.get_students()[1]), - self.teacher.change_mode, "raise_right_hand" - ) - self.wait() - self.play(ShowCreation(numerator.circle)) - self.wait() - self.play(ReplacementTransform( - numerator.circle, - denominator.circle, - )) - self.wait() - self.play( - FadeOut(denominator.circle), - Write(dx_to_zero), - dxs.set_color, YELLOW - ) - self.wait() - self.play( - FadeOut(dx_to_zero), - *[ApplyMethod(pi.change, "plain") for pi in self.get_pi_creatures()] - ) - self.play( - df_over_dx.scale, dxs[1].get_height()/dxs[0].get_height(), - df_over_dx.move_to, d_over_dx, RIGHT, - FadeOut(VGroup(lp, rp)), - d_over_dx.shift, 0.8*LEFT + 0.05*UP, - ) - self.wait() - self.play(*[ - ReplacementTransform( - group, - VGroup(d2f_over_dx2.get_part_by_tex(tex)) - ) - for group, tex in [ - (VGroup(d, dfs[0]), "d^2"), - (overs, "over"), - (dxs, "dx"), - (VGroup(dxs[1].copy()), "^2}"), - ] - ]) - self.wait(2) - self.student_says( - "How does one... \\\\ read that?", - student_index = 0, - ) - self.play(self.teacher.change, "happy") - self.wait(2) - -class HowToReadNotation(GraphScene, ReconfigurableScene): - CONFIG = { - "x_max" : 5, - "dx" : 0.4, - "x" : 2, - "graph_origin" : 2.5*DOWN + 5*LEFT, - } - def setup(self): - for base in self.__class__.__bases__: - base.setup(self) - - def construct(self): - self.force_skipping() - - self.add_graph() - self.take_two_steps() - self.change_step_size() - self.show_dfs() - self.show_ddf() - - self.revert_to_original_skipping_status() - self.show_proportionality_to_dx_squared() - return - - self.write_second_derivative() - - def add_graph(self): - self.setup_axes() - graph = self.get_graph(lambda x : x**2) - graph_label = self.get_graph_label( - graph, "f(x)", - direction = LEFT, - x_val = 3.3 - ) - self.add(graph, graph_label) - - self.graph = graph - - def take_two_steps(self): - v_lines = [ - self.get_vertical_line_to_graph( - self.x + i*self.dx, self.graph, - line_class = DashedLine, - color = WHITE - ) - for i in range(3) - ] - braces = [ - Brace(VGroup(*v_lines[i:i+2]), buff = 0) - for i in range(2) - ] - for brace in braces: - brace.dx = TexMobject("dx") - max_width = 0.7*brace.get_width() - if brace.dx.get_width() > max_width: - brace.dx.set_width(max_width) - brace.dx.next_to(brace, DOWN, SMALL_BUFF) - - self.play(ShowCreation(v_lines[0])) - self.wait() - for brace, line in zip(braces, v_lines[1:]): - self.play( - ReplacementTransform( - VectorizedPoint(brace.get_corner(UP+LEFT)), - brace, - ), - Write(brace.dx, run_time = 1), - ) - self.play(ShowCreation(line)) - self.wait() - - self.v_lines = v_lines - self.braces = braces - - def change_step_size(self): - self.transition_to_alt_config(dx = 0.6) - self.transition_to_alt_config(dx = 0.01, run_time = 3) - - def show_dfs(self): - dx_lines = VGroup() - df_lines = VGroup() - df_dx_groups = VGroup() - df_labels = VGroup() - for i, v_line1, v_line2 in zip(it.count(1), self.v_lines, self.v_lines[1:]): - dx_line = Line( - v_line1.get_bottom(), - v_line2.get_bottom(), - color = GREEN - ) - dx_line.move_to(v_line1.get_top(), LEFT) - dx_lines.add(dx_line) - - df_line = Line( - dx_line.get_right(), - v_line2.get_top(), - color = YELLOW - ) - df_lines.add(df_line) - df_label = TexMobject("df_%d"%i) - df_label.set_color(YELLOW) - df_label.scale(0.8) - df_label.next_to(df_line.get_center(), UP+LEFT, MED_LARGE_BUFF) - df_arrow = Arrow( - df_label.get_bottom(), - df_line.get_center(), - buff = SMALL_BUFF, - ) - df_line.label = df_label - df_line.arrow = df_arrow - df_labels.add(df_label) - - df_dx_groups.add(VGroup(df_line, dx_line)) - - for brace, dx_line, df_line in zip(self.braces, dx_lines, df_lines): - self.play( - VGroup(brace, brace.dx).next_to, - dx_line, DOWN, SMALL_BUFF, - FadeIn(dx_line), - ) - self.play(ShowCreation(df_line)) - self.play( - ShowCreation(df_line.arrow), - Write(df_line.label) - ) - self.wait(2) - - self.df_dx_groups = df_dx_groups - self.df_labels = df_labels - - def show_ddf(self): - df_dx_groups = self.df_dx_groups.copy() - df_dx_groups.generate_target() - df_dx_groups.target.arrange( - RIGHT, - buff = MED_LARGE_BUFF, - aligned_edge = DOWN - ) - df_dx_groups.target.next_to( - self.df_dx_groups, RIGHT, - buff = 3, - aligned_edge = DOWN - ) - - df_labels = self.df_labels.copy() - df_labels.generate_target() - h_lines = VGroup() - for group, label in zip(df_dx_groups.target, df_labels.target): - label.next_to(group.get_right(), LEFT, SMALL_BUFF) - width = df_dx_groups.target.get_width() + MED_SMALL_BUFF - h_line = DashedLine(ORIGIN, width*RIGHT) - h_line.move_to( - group.get_corner(UP+RIGHT)[1]*UP + \ - df_dx_groups.target.get_right()[0]*RIGHT, - RIGHT - ) - h_lines.add(h_line) - max_height = 0.8*group.get_height() - if label.get_height() > max_height: - label.set_height(max_height) - - - ddf_brace = Brace(h_lines, LEFT, buff = SMALL_BUFF) - ddf = ddf_brace.get_tex("d(df)", buff = SMALL_BUFF) - ddf.scale( - df_labels[0].get_height()/ddf.get_height(), - about_point = ddf.get_right() - ) - ddf.set_color(MAROON_B) - - self.play( - *list(map(MoveToTarget, [df_dx_groups, df_labels])), - run_time = 2 - ) - self.play(ShowCreation(h_lines, run_time = 2)) - self.play(GrowFromCenter(ddf_brace)) - self.play(Write(ddf)) - self.wait(2) - - self.ddf = ddf - - def show_proportionality_to_dx_squared(self): - ddf = self.ddf.copy() - ddf.generate_target() - ddf.target.next_to(self.ddf, UP, LARGE_BUFF) - rhs = TexMobject( - "\\approx", "(\\text{Some constant})", "(dx)^2" - ) - rhs.scale(0.8) - rhs.next_to(ddf.target, RIGHT) - - example_dx = TexMobject( - "dx = 0.01 \\Rightarrow (dx)^2 = 0.0001" - ) - example_dx.scale(0.8) - example_dx.to_corner(UP+RIGHT) - - self.play(MoveToTarget(ddf)) - self.play(Write(rhs)) - self.wait() - self.play(Write(example_dx)) - self.wait(2) - self.play(FadeOut(example_dx)) - - self.ddf = ddf - self.dx_squared = rhs.get_part_by_tex("dx") - - def write_second_derivative(self): - ddf_over_dx_squared = TexMobject( - "{d(df)", "\\over", "(dx)^2}" - ) - ddf_over_dx_squared.scale(0.8) - ddf_over_dx_squared.move_to(self.ddf, RIGHT) - ddf_over_dx_squared.set_color_by_tex("df", self.ddf.get_color()) - parens = VGroup( - ddf_over_dx_squared[0][1], - ddf_over_dx_squared[0][4], - ddf_over_dx_squared[2][0], - ddf_over_dx_squared[2][3], - ) - - right_shifter = ddf_over_dx_squared[0][0] - left_shifter = ddf_over_dx_squared[2][4] - - exp_two = TexMobject("2") - exp_two.set_color(self.ddf.get_color()) - exp_two.scale(0.5) - exp_two.move_to(right_shifter.get_corner(UP+RIGHT), LEFT) - exp_two.shift(MED_SMALL_BUFF*RIGHT) - pre_exp_two = VGroup(ddf_over_dx_squared[0][2]) - - self.play( - Write(ddf_over_dx_squared.get_part_by_tex("over")), - *[ - ReplacementTransform( - mob, - ddf_over_dx_squared.get_part_by_tex(tex), - path_arc = -np.pi/2, - ) - for mob, tex in [(self.ddf, "df"), (self.dx_squared, "dx")] - ] - ) - self.wait(2) - self.play(FadeOut(parens)) - self.play( - left_shifter.shift, 0.2*LEFT, - right_shifter.shift, 0.2*RIGHT, - ReplacementTransform(pre_exp_two, exp_two), - ddf_over_dx_squared.get_part_by_tex("over").scale_in_place, 0.8 - ) - self.wait(2) - -class Footnote(Scene): - def construct(self): - self.add(TextMobject(""" - Interestingly, there is a notion in math - called the ``exterior derivative'' which - treats this ``d'' as having a more independent - meaning, though it's less related to the - intuitions I've introduced in this series. - """, alignment = "")) - -class TrajectoryGraphScene(GraphScene): - CONFIG = { - "x_min" : 0, - "x_max" : 10, - "x_axis_label" : "t", - "y_axis_label" : "s", - # "func" : lambda x : 10*smooth(x/10.0), - "func" : lambda t : 10*bezier([0, 0, 0, 1, 1, 1])(t/10.0), - "color" : BLUE, - } - def construct(self): - self.setup_axes() - self.graph = self.get_graph( - self.func, - color = self.color - ) - self.add(self.graph) - -class SecondDerivativeAsAcceleration(Scene): - CONFIG = { - "car_run_time" : 6, - } - def construct(self): - self.init_car_and_line() - self.introduce_acceleration() - self.show_functions() - - def init_car_and_line(self): - line = Line(5.5*LEFT, 4.5*RIGHT) - line.shift(2*DOWN) - car = Car() - car.move_to(line.get_left()) - self.add(line, car) - self.car = car - self.start_car_copy = car.copy() - self.line = line - - def introduce_acceleration(self): - a_words = TexMobject( - "{d^2 s \\over dt^2}(t)", "\\Leftrightarrow", - "\\text{Acceleration}" - ) - a_words.set_color_by_tex("d^2 s", MAROON_B) - a_words.set_color_by_tex("Acceleration", YELLOW) - a_words.to_corner(UP+RIGHT ) - self.add(a_words) - self.show_car_movement() - self.wait() - - self.a_words = a_words - - def show_functions(self): - def get_deriv(n): - return lambda x : derivative( - s_scene.graph.underlying_function, x, n - ) - s_scene = TrajectoryGraphScene() - v_scene = TrajectoryGraphScene( - func = get_deriv(1), - color = GREEN, - y_max = 4, - y_axis_label = "v", - ) - a_scene = TrajectoryGraphScene( - func = get_deriv(2), - color = MAROON_B, - y_axis_label = "a", - y_min = -2, - y_max = 2, - ) - j_scene = TrajectoryGraphScene( - func = get_deriv(3), - color = PINK, - y_axis_label = "j", - y_min = -2, - y_max = 2, - ) - s_graph, v_graph, a_graph, j_graph = graphs = [ - VGroup(*scene.get_top_level_mobjects()) - for scene in (s_scene, v_scene, a_scene, j_scene) - ] - for i, graph in enumerate(graphs): - graph.set_height(FRAME_Y_RADIUS) - graph.to_corner(UP+LEFT) - graph.shift(i*DOWN/2.0) - - s_words = TexMobject( - "s(t)", "\\Leftrightarrow", "\\text{Displacement}" - ) - s_words.set_color_by_tex("s(t)", s_scene.graph.get_color()) - v_words = TexMobject( - "\\frac{ds}{dt}(t)", "\\Leftrightarrow", - "\\text{Velocity}" - ) - v_words.set_color_by_tex("ds", v_scene.graph.get_color()) - j_words = TexMobject( - "\\frac{d^3 s}{dt^3}(t)", "\\Leftrightarrow", - "\\text{Jerk}" - ) - j_words.set_color_by_tex("d^3", j_scene.graph.get_color()) - self.a_words.generate_target() - words_group = VGroup(s_words, v_words, self.a_words.target, j_words) - words_group.arrange( - DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - words_group.to_corner(UP+RIGHT) - j_graph.scale(0.3).next_to(j_words, LEFT) - - positive_rect = Rectangle() - positive_rect.set_stroke(width = 0) - positive_rect.set_fill(GREEN, 0.5) - positive_rect.replace( - Line( - a_scene.coords_to_point(0, -1), - a_scene.coords_to_point(5, 1), - ), - stretch = True - ) - negative_rect = Rectangle() - negative_rect.set_stroke(width = 0) - negative_rect.set_fill(RED, 0.5) - negative_rect.replace( - Line( - a_scene.coords_to_point(5, 1), - a_scene.coords_to_point(10, -1), - ), - stretch = True - ) - - self.show_car_movement( - MoveToTarget(self.a_words), - FadeIn(s_words), - FadeIn(s_graph), - ) - self.play( - s_graph.scale, 0.3, - s_graph.next_to, s_words, LEFT - ) - self.play(*list(map(FadeIn, [v_graph, v_words])) ) - self.wait(2) - self.play( - v_graph.scale, 0.3, - v_graph.next_to, v_words, LEFT - ) - self.wait(2) - self.play( - Indicate(self.a_words), - FadeIn(a_graph), - ) - self.wait() - self.play(FadeIn(positive_rect)) - for x in range(2): - self.show_car_movement( - run_time = 3, - rate_func = lambda t : smooth(t/2.0) - ) - self.wait() - self.play(FadeIn(negative_rect)) - self.wait() - self.play(MoveCar( - self.car, self.line.get_end(), - run_time = 3, - rate_func = lambda t : 2*smooth((t+1)/2.0) - 1 - )) - self.wait() - self.play( - a_graph.scale, 0.3, - a_graph.next_to, self.a_words, LEFT, - *list(map(FadeOut, [positive_rect, negative_rect])) - ) - self.play( - FadeOut(self.car), - FadeIn(j_words), - FadeIn(j_graph), - self.line.scale, 0.5, self.line.get_left(), - self.line.shift, LEFT, - ) - self.car.scale(0.5) - self.car.move_to(self.line.get_start()) - self.play(FadeIn(self.car)) - self.show_car_movement() - self.wait(2) - - ########## - - def show_car_movement(self, *added_anims, **kwargs): - distance = get_norm( - self.car.get_center() - self.start_car_copy.get_center() - ) - if distance > 1: - self.play(FadeOut(self.car)) - self.car.move_to(self.line.get_left()) - self.play(FadeIn(self.car)) - - kwargs["run_time"] = kwargs.get("run_time", self.car_run_time) - self.play( - MoveCar(self.car, self.line.get_right(), **kwargs), - *added_anims - ) - -class NextVideo(Scene): - def construct(self): - title = TextMobject("Chapter 10: Taylor series") - title.to_edge(UP) - rect = ScreenRectangle(height = 6) - rect.next_to(title, DOWN) - self.add(rect) - self.play(Write(title)) - self.wait() - -class Thumbnail(SecondDerivativeGraphically): - CONFIG = { - "graph_origin" : 5*LEFT + 3*DOWN, - "x_axis_label" : "", - "y_axis_label" : "", - } - def construct(self): - self.setup_axes() - self.force_skipping() - self.draw_f() - self.remove(self.graph_label) - self.graph.set_stroke(GREEN, width = 8) - - tex = TexMobject("{d^n f", "\\over", "dx^n}") - tex.set_color_by_tex("d^n", YELLOW) - tex.set_color_by_tex("dx", BLUE) - tex.set_height(4) - tex.to_edge(UP) - - self.add(tex) - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eoc/old_chapter1.py b/from_3b1b/old/eoc/old_chapter1.py deleted file mode 100644 index d3356d5f..00000000 --- a/from_3b1b/old/eoc/old_chapter1.py +++ /dev/null @@ -1,2551 +0,0 @@ -from manimlib.imports import * - - -#### Warning, scenes here not updated based on most recent GraphScene changes ####### - -class CircleScene(PiCreatureScene): - CONFIG = { - "radius" : 1.5, - "stroke_color" : WHITE, - "fill_color" : BLUE_E, - "fill_opacity" : 0.5, - "radial_line_color" : MAROON_B, - "outer_ring_color" : GREEN_E, - "dR" : 0.1, - "dR_color" : YELLOW, - "unwrapped_tip" : ORIGIN, - "include_pi_creature" : False, - "circle_corner" : UP+LEFT - } - def setup(self): - self.circle = Circle( - radius = self.radius, - stroke_color = self.stroke_color, - fill_color = self.fill_color, - fill_opacity = self.fill_opacity, - ) - self.circle.to_corner(self.circle_corner, buff = MED_LARGE_BUFF) - self.radius_line = Line( - self.circle.get_center(), - self.circle.get_right(), - color = self.radial_line_color - ) - self.radius_brace = Brace(self.radius_line, buff = SMALL_BUFF) - self.radius_label = self.radius_brace.get_text("$R$", buff = SMALL_BUFF) - - self.add( - self.circle, self.radius_line, - self.radius_brace, self.radius_label - ) - - self.pi_creature = self.create_pi_creature() - if self.include_pi_creature: - self.add(self.pi_creature) - else: - self.pi_creature.set_fill(opacity = 0) - - def create_pi_creature(self): - return Mortimer().to_corner(DOWN+RIGHT) - - def introduce_circle(self, added_anims = []): - self.remove(self.circle) - self.play( - ShowCreation(self.radius_line), - GrowFromCenter(self.radius_brace), - Write(self.radius_label), - ) - self.circle.set_fill(opacity = 0) - - self.play( - Rotate( - self.radius_line, 2*np.pi-0.001, - about_point = self.circle.get_center(), - ), - ShowCreation(self.circle), - *added_anims, - run_time = 2 - ) - self.play( - self.circle.set_fill, self.fill_color, self.fill_opacity, - Animation(self.radius_line), - Animation(self.radius_brace), - Animation(self.radius_label), - ) - - def increase_radius(self, numerical_dr = True, run_time = 2): - radius_mobs = VGroup( - self.radius_line, self.radius_brace, self.radius_label - ) - nudge_line = Line( - self.radius_line.get_right(), - self.radius_line.get_right() + self.dR*RIGHT, - color = self.dR_color - ) - nudge_arrow = Arrow( - nudge_line.get_center() + 0.5*RIGHT+DOWN, - nudge_line.get_center(), - color = YELLOW, - buff = SMALL_BUFF, - tip_length = 0.2, - ) - if numerical_dr: - nudge_label = TexMobject("%.01f"%self.dR) - else: - nudge_label = TexMobject("dr") - nudge_label.set_color(self.dR_color) - nudge_label.scale(0.75) - nudge_label.next_to(nudge_arrow.get_start(), DOWN) - - radius_mobs.add(nudge_line, nudge_arrow, nudge_label) - - outer_ring = self.get_outer_ring() - - self.play( - FadeIn(outer_ring), - ShowCreation(nudge_line), - ShowCreation(nudge_arrow), - Write(nudge_label), - run_time = run_time/2. - ) - self.wait(run_time/2.) - self.nudge_line = nudge_line - self.nudge_arrow = nudge_arrow - self.nudge_label = nudge_label - self.outer_ring = outer_ring - return outer_ring - - def get_ring(self, radius, dR, color = GREEN): - ring = Circle(radius = radius + dR).center() - inner_ring = Circle(radius = radius) - inner_ring.rotate(np.pi, RIGHT) - ring.append_vectorized_mobject(inner_ring) - ring.set_stroke(width = 0) - ring.set_fill(color) - ring.move_to(self.circle) - ring.R = radius - ring.dR = dR - return ring - - def get_outer_ring(self): - return self.get_ring( - radius = self.radius, dR = self.dR, - color = self.outer_ring_color - ) - - def unwrap_ring(self, ring, **kwargs): - self.unwrap_rings(ring, **kwargs) - - def unwrap_rings(self, *rings, **kwargs): - added_anims = kwargs.get("added_anims", []) - rings = VGroup(*rings) - unwrapped = VGroup(*[ - self.get_unwrapped(ring, **kwargs) - for ring in rings - ]) - self.play( - rings.rotate, np.pi/2, - rings.next_to, unwrapped.get_bottom(), UP, - run_time = 2, - path_arc = np.pi/2 - ) - self.play( - Transform(rings, unwrapped, run_time = 3), - *added_anims - ) - - def get_unwrapped(self, ring, to_edge = LEFT, **kwargs): - R = ring.R - R_plus_dr = ring.R + ring.dR - n_anchors = ring.get_num_curves() - result = VMobject() - result.set_points_as_corners([ - interpolate(np.pi*R_plus_dr*LEFT, np.pi*R_plus_dr*RIGHT, a) - for a in np.linspace(0, 1, n_anchors/2) - ]+[ - interpolate(np.pi*R*RIGHT+ring.dR*UP, np.pi*R*LEFT+ring.dR*UP, a) - for a in np.linspace(0, 1, n_anchors/2) - ]) - result.set_style_data( - stroke_color = ring.get_stroke_color(), - stroke_width = ring.get_stroke_width(), - fill_color = ring.get_fill_color(), - fill_opacity = ring.get_fill_opacity(), - ) - result.move_to(self.unwrapped_tip, aligned_edge = DOWN) - result.shift(R_plus_dr*DOWN) - result.to_edge(to_edge) - - return result - -###################### - -class PatronsOnly(Scene): - def construct(self): - morty = Mortimer() - morty.shift(2*DOWN) - title = TextMobject(""" - This is a draft - for patrons only - """) - title.set_color(RED) - title.scale(2) - title.to_edge(UP) - - self.add(morty) - self.play( - Write(title), - morty.change_mode, "wave_1" - ) - self.play(Blink(morty)) - self.play( - morty.change_mode, "pondering", - morty.look_at, title - ) - self.play(Blink(morty)) - self.wait() - -class Introduction(TeacherStudentsScene): - def construct(self): - self.show_series() - self.look_to_center() - self.go_through_students() - self.zoom_in_on_first() - - def show_series(self): - series = VideoSeries() - series.to_edge(UP) - this_video = series[0] - this_video.set_color(YELLOW) - this_video.save_state() - this_video.set_fill(opacity = 0) - this_video.center() - this_video.set_height(FRAME_HEIGHT) - self.this_video = this_video - - words = TextMobject( - "Welcome to \\\\", - "Essence of calculus" - ) - words.set_color_by_tex("Essence of calculus", YELLOW) - self.remove(self.teacher) - self.teacher.change_mode("happy") - self.add(self.teacher) - self.play( - FadeIn( - series, - lag_ratio = 0.5, - run_time = 2 - ), - Blink(self.get_teacher()) - ) - self.teacher_says(words, target_mode = "hooray") - self.play( - ApplyMethod(this_video.restore, run_time = 3), - *[ - ApplyFunction( - lambda p : p.change_mode("hooray").look_at(series[1]), - pi - ) - for pi in self.get_pi_creatures() - ] - ) - def homotopy(x, y, z, t): - alpha = (0.7*x + FRAME_X_RADIUS)/(FRAME_WIDTH) - beta = squish_rate_func(smooth, alpha-0.15, alpha+0.15)(t) - return (x, y - 0.3*np.sin(np.pi*beta), z) - self.play( - Homotopy( - homotopy, series, - apply_function_kwargs = {"maintain_smoothness" : False}, - ), - *[ - ApplyMethod(pi.look_at, series[-1]) - for pi in self.get_pi_creatures() - ], - run_time = 5 - ) - self.play( - FadeOut(self.teacher.bubble), - FadeOut(self.teacher.bubble.content), - *[ - ApplyMethod(pi.change_mode, "happy") - for pi in self.get_pi_creatures() - ] - ) - - def look_to_center(self): - anims = [] - for pi in self.get_pi_creatures(): - anims += [ - pi.change_mode, "pondering", - pi.look_at, 2*UP - ] - self.play(*anims) - self.random_blink(6) - self.play(*[ - ApplyMethod(pi.change_mode, "happy") - for pi in self.get_pi_creatures() - ]) - - def go_through_students(self): - pi1, pi2, pi3 = self.get_students() - for pi in pi1, pi2, pi3: - pi.save_state() - bubble = pi1.get_bubble(width = 5) - bubble.set_fill(BLACK, opacity = 1) - remembered_symbols = VGroup( - TexMobject("\\int_0^1 \\frac{1}{1-x^2}\\,dx").shift(UP+LEFT), - TexMobject("\\frac{d}{dx} e^x = e^x").shift(DOWN+RIGHT), - ) - cant_wait = TextMobject("I literally \\\\ can't wait") - big_derivative = TexMobject(""" - \\frac{d}{dx} \\left( \\sin(x^2)2^{\\sqrt{x}} \\right) - """) - - self.play( - pi1.change_mode, "confused", - pi1.look_at, bubble.get_right(), - ShowCreation(bubble), - pi2.fade, - pi3.fade, - ) - bubble.add_content(remembered_symbols) - self.play(Write(remembered_symbols)) - self.play(ApplyMethod( - remembered_symbols.fade, 0.7, - lag_ratio = 0.5, - run_time = 3 - )) - self.play( - pi1.restore, - pi1.fade, - pi2.restore, - pi2.change_mode, "hooray", - pi2.look_at, bubble.get_right(), - bubble.pin_to, pi2, - FadeOut(remembered_symbols), - ) - bubble.add_content(cant_wait) - self.play(Write(cant_wait, run_time = 2)) - self.play(Blink(pi2)) - self.play( - pi2.restore, - pi2.fade, - pi3.restore, - pi3.change_mode, "pleading", - pi3.look_at, bubble.get_right(), - bubble.pin_to, pi3, - FadeOut(cant_wait) - ) - bubble.add_content(big_derivative) - self.play(Write(big_derivative)) - self.play(Blink(pi3)) - self.wait() - - def zoom_in_on_first(self): - this_video = self.this_video - self.remove(this_video) - this_video.generate_target() - this_video.target.set_height(FRAME_HEIGHT) - this_video.target.center() - this_video.target.set_fill(opacity = 0) - - everything = VGroup(*self.get_mobjects()) - self.play( - FadeOut(everything), - MoveToTarget(this_video, run_time = 2) - ) - -class IntroduceCircle(Scene): - def construct(self): - circle = Circle(radius = 3, color = WHITE) - circle.to_edge(LEFT) - radius = Line(circle.get_center(), circle.get_right()) - radius.set_color(MAROON_B) - R = TexMobject("R").next_to(radius, UP) - - area, circumference = words = VGroup(*list(map(TextMobject, [ - "Area =", "Circumference =" - ]))) - area.set_color(BLUE) - circumference.set_color(YELLOW) - - words.arrange(DOWN, aligned_edge = LEFT) - words.next_to(circle, RIGHT) - words.to_edge(UP) - pi_R, pre_squared = TexMobject("\\pi R", "{}^2") - squared = TexMobject("2").replace(pre_squared) - area_form = VGroup(pi_R, squared) - area_form.next_to(area, RIGHT) - two, pi_R = TexMobject("2", "\\pi R") - circum_form = VGroup(pi_R, two) - circum_form.next_to(circumference, RIGHT) - - derivative = TexMobject( - "\\frac{d}{dR}", "\\pi R^2", "=", "2\\pi R" - ) - integral = TexMobject( - "\\int_0^R", "2\\pi r", "\\, dR = ", "\\pi R^2" - ) - up_down_arrow = TexMobject("\\Updownarrow") - calc_stuffs = VGroup(derivative, up_down_arrow, integral) - calc_stuffs.arrange(DOWN) - calc_stuffs.next_to(words, DOWN, buff = LARGE_BUFF, aligned_edge = LEFT) - - brace = Brace(calc_stuffs, RIGHT) - to_be_explained = brace.get_text("To be \\\\ explained") - VGroup(brace, to_be_explained).set_color(GREEN) - - self.play(ShowCreation(radius), Write(R)) - self.play( - Rotate(radius, 2*np.pi, about_point = circle.get_center()), - ShowCreation(circle) - ) - self.play( - FadeIn(area), - Write(area_form), - circle.set_fill, area.get_color(), 0.5, - Animation(radius), - Animation(R), - ) - self.wait() - self.play( - circle.set_stroke, circumference.get_color(), - FadeIn(circumference), - Animation(radius), - Animation(R), - ) - self.play(Transform( - area_form.copy(), - circum_form, - path_arc = -np.pi/2, - run_time = 3 - )) - self.wait() - self.play( - area_form.copy().replace, derivative[1], - circum_form.copy().replace, derivative[3], - Write(derivative[0]), - Write(derivative[2]), - run_time = 1 - ) - self.wait() - self.play( - area_form.copy().replace, integral[3], - Transform(circum_form.copy(), integral[1]), - Write(integral[0]), - Write(integral[2]), - run_time = 1 - ) - self.wait() - self.play(Write(up_down_arrow)) - self.wait() - self.play( - GrowFromCenter(brace), - Write(to_be_explained) - ) - self.wait() - -class HeartOfCalculus(GraphScene): - CONFIG = { - "x_labeled_nums" : [], - "y_labeled_nums" : [], - } - def construct(self): - self.setup_axes() - self.graph_function(lambda x : 3*np.sin(x/2) + x) - rect_sets = [ - self.get_riemann_rectangles( - 0, self.x_max, 1./(2**n), stroke_width = 1./(n+1) - ) - for n in range(6) - ] - - rects = rect_sets.pop(0) - rects.save_state() - rects.stretch_to_fit_height(0) - rects.shift( - (self.graph_origin[1] - rects.get_center()[1])*UP - ) - self.play( - rects.restore, - lag_ratio = 0.5, - run_time = 3 - ) - while rect_sets: - self.play( - Transform(rects, rect_sets.pop(0)), - run_time = 2 - ) - -class PragmatismToArt(Scene): - def construct(self): - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - morty.shift(LEFT) - pragmatism = TextMobject("Pragmatism") - art = TextMobject("Art") - pragmatism.move_to(morty.get_corner(UP+LEFT), aligned_edge = DOWN) - art.move_to(morty.get_corner(UP+RIGHT), aligned_edge = DOWN) - art.shift(0.2*(LEFT+UP)) - - circle1 = Circle( - radius = 2, - fill_opacity = 1, - fill_color = BLUE_E, - stroke_width = 0, - ) - circle2 = Circle( - radius = 2, - stroke_color = YELLOW - ) - arrow = DoubleArrow(LEFT, RIGHT, color = WHITE) - circle_group = VGroup(circle1, arrow, circle2) - circle_group.arrange() - circle_group.to_corner(UP+LEFT) - circle2.save_state() - circle2.move_to(circle1) - q_marks = TextMobject("???").next_to(arrow, UP) - - - self.play( - morty.change_mode, "raise_right_hand", - morty.look_at, pragmatism, - Write(pragmatism, run_time = 1), - ) - self.play(Blink(morty)) - self.play( - morty.change_mode, "raise_left_hand", - morty.look_at, art, - Transform( - VectorizedPoint(morty.get_corner(UP+RIGHT)), - art - ), - pragmatism.fade, 0.7, - pragmatism.rotate_in_place, np.pi/4, - pragmatism.shift, DOWN+LEFT - ) - self.play(Blink(morty)) - self.play( - GrowFromCenter(circle1), - morty.look_at, circle1 - ) - self.play(ShowCreation(circle2)) - self.play( - ShowCreation(arrow), - Write(q_marks), - circle2.restore - ) - self.play(Blink(morty)) - -class IntroduceTinyChangeInArea(CircleScene): - CONFIG = { - "include_pi_creature" : True, - } - def construct(self): - new_area_form, minus, area_form = expression = TexMobject( - "\\pi (R + 0.1)^2", "-", "\\pi R^2" - ) - VGroup(*new_area_form[4:7]).set_color(self.dR_color) - expression_brace = Brace(expression, UP) - change_in_area = expression_brace.get_text("Change in area") - change_in_area.set_color(self.outer_ring_color) - area_brace = Brace(area_form) - area_word = area_brace.get_text("Area") - area_word.set_color(BLUE) - new_area_brace = Brace(new_area_form) - new_area_word = new_area_brace.get_text("New area") - group = VGroup( - expression, expression_brace, change_in_area, - area_brace, area_word, new_area_brace, new_area_word - ) - group.to_edge(UP).shift(RIGHT) - group.save_state() - area_group = VGroup(area_form, area_brace, area_word) - area_group.save_state() - area_group.next_to(self.circle, RIGHT, buff = LARGE_BUFF) - - self.introduce_circle( - added_anims = [self.pi_creature.change_mode, "speaking"] - ) - self.play(Write(area_group)) - self.change_mode("happy") - outer_ring = self.increase_radius() - self.wait() - self.play( - area_group.restore, - GrowFromCenter(expression_brace), - Write(new_area_form), - Write(minus), - Write(change_in_area), - self.pi_creature.change_mode, "confused", - ) - self.play( - Write(new_area_word), - GrowFromCenter(new_area_brace) - ) - self.wait(2) - self.play( - group.fade, 0.7, - self.pi_creature.change_mode, "happy" - ) - self.wait() - self.play( - outer_ring.set_color, YELLOW, - Animation(self.nudge_arrow), - Animation(self.nudge_line), - rate_func = there_and_back - ) - self.show_unwrapping(outer_ring) - self.play(group.restore) - self.work_out_expression(group) - self.second_unwrapping(outer_ring) - insignificant = TextMobject("Insignificant") - insignificant.set_color(self.dR_color) - insignificant.move_to(self.error_words) - self.play(Transform(self.error_words, insignificant)) - self.wait() - - big_rect = Rectangle( - width = FRAME_WIDTH, - height = FRAME_HEIGHT, - fill_color = BLACK, - fill_opacity = 0.85, - stroke_width = 0, - ) - self.play( - FadeIn(big_rect), - area_form.set_color, BLUE, - self.two_pi_R.set_color, GREEN, - self.pi_creature.change_mode, "happy" - ) - - def show_unwrapping(self, outer_ring): - almost_rect = outer_ring.copy() - self.unwrap_ring( - almost_rect, - added_anims = [self.pi_creature.change_mode, "pondering"] - ) - - circum_brace = Brace(almost_rect, UP).scale_in_place(0.95) - dR_brace = TexMobject("\\}") - dR_brace.stretch(0.5, 1) - dR_brace.next_to(almost_rect, RIGHT) - two_pi_R = circum_brace.get_text("$2\\pi R$") - dR = TexMobject("$0.1$").scale(0.7).next_to(dR_brace, RIGHT) - dR.set_color(self.dR_color) - - two_pi_R.generate_target() - dR.generate_target() - lp, rp = TexMobject("()") - change_in_area = TextMobject( - "Change in area $\\approx$" - ) - final_area = VGroup( - change_in_area, - two_pi_R.target, lp, dR.target.scale(1./0.7), rp - ) - final_area.arrange(RIGHT, buff = SMALL_BUFF) - final_area.next_to(almost_rect, DOWN, buff = MED_LARGE_BUFF) - final_area.set_color(GREEN_A) - final_area[3].set_color(self.dR_color) - change_in_area.shift(0.1*LEFT) - - self.play( - GrowFromCenter(circum_brace), - Write(two_pi_R) - ) - self.wait() - self.play( - GrowFromCenter(dR_brace), - Write(dR) - ) - self.wait() - self.play( - MoveToTarget(two_pi_R.copy()), - MoveToTarget(dR.copy()), - Write(change_in_area, run_time = 1), - Write(lp), - Write(rp), - ) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(final_area) - self.play( - self.pi_creature.change_mode, "happy", - self.pi_creature.look_at, final_area - ) - self.wait() - group = VGroup( - almost_rect, final_area, two_pi_R, dR, - circum_brace, dR_brace - ) - self.play(group.fade) - - def work_out_expression(self, expression_group): - exp, exp_brace, title, area_brace, area_word, new_area_brace, new_area_word = expression_group - new_area_form, minus, area_form = exp - - expanded = TexMobject( - "\\pi R^2", "+", "2\\pi R (0.1)", - "+", "\\pi (0.1)^2", "-", "\\pi R^2", - ) - pi_R_squared, plus, two_pi_R_dR, plus2, pi_dR_squared, minus2, pi_R_squared2 = expanded - for subset in two_pi_R_dR[4:7], pi_dR_squared[2:5]: - VGroup(*subset).set_color(self.dR_color) - expanded.next_to(new_area_form, DOWN, aligned_edge = LEFT, buff = MED_SMALL_BUFF) - expanded.shift(LEFT/2.) - - faders = [area_brace, area_word, new_area_brace, new_area_word] - self.play(*list(map(FadeOut, faders))) - trips = [ - ([0, 2, 8], pi_R_squared, plus), - ([8, 0, 2, 1, 4, 5, 6, 7], two_pi_R_dR, plus2), - ([0, 1, 4, 5, 6, 7, 8], pi_dR_squared, VGroup()), - ] - to_remove = [] - for subset, target, writer in trips: - starter = VGroup( - *np.array(list(new_area_form.copy()))[subset] - ) - self.play( - Transform(starter, target, run_time = 2), - Write(writer) - ) - to_remove += self.get_mobjects_from_last_animation() - self.wait() - self.play( - Transform(minus.copy(), minus2), - Transform(area_form.copy(), pi_R_squared2), - ) - to_remove += self.get_mobjects_from_last_animation() - self.remove(*to_remove) - self.add(self.pi_creature, *expanded) - self.wait(2) - self.play(*[ - ApplyMethod(mob.set_color, RED) - for mob in (pi_R_squared, pi_R_squared2) - ]) - self.wait() - self.play(*[ - ApplyMethod(mob.fade, 0.7) - for mob in (plus, pi_R_squared, pi_R_squared2, minus2) - ]) - self.wait() - - approx_brace = Brace(two_pi_R_dR) - error_brace = Brace(pi_dR_squared, buff = SMALL_BUFF) - error_words = error_brace.get_text("Error", buff = SMALL_BUFF) - error_words.set_color(RED) - self.error_words = error_words - - self.play( - GrowFromCenter(approx_brace), - self.pi_creature.change_mode, "hooray" - ) - self.wait() - self.play( - GrowFromCenter(error_brace), - Write(error_words), - self.pi_creature.change_mode, "confused" - ) - self.wait() - self.two_pi_R = VGroup(*two_pi_R_dR[:3]) - - def second_unwrapping(self, outer_ring): - almost_rect = outer_ring.copy() - rect = Rectangle( - width = 2*np.pi*self.radius, - height = self.dR, - fill_color = self.outer_ring_color, - fill_opacity = 1, - stroke_width = 0, - ) - self.play( - almost_rect.set_color, YELLOW, - self.pi_creature.change_mode, "pondering" - ) - self.unwrap_ring(almost_rect) - self.wait() - rect.move_to(almost_rect) - self.play(FadeIn(rect)) - self.wait() - - def create_pi_creature(self): - morty = Mortimer() - morty.scale(0.7) - morty.to_corner(DOWN+RIGHT) - return morty - -class CleanUpABit(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Let's clean that - up a bit - """) - self.random_blink(2) - -class BuildToDADR(CircleScene): - CONFIG = { - "include_pi_creature" : True, - } - def construct(self): - self.outer_ring = self.increase_radius() - self.write_initial_terms() - self.show_fractions() - self.transition_to_dR() - self.elaborate_on_d() - self.not_infinitely_small() - - def create_pi_creature(self): - morty = Mortimer() - morty.flip() - morty.to_corner(DOWN+LEFT) - return morty - - def write_initial_terms(self): - change = TextMobject("Change in area") - change.set_color(GREEN_B) - equals, two_pi_R, dR, plus, pi, dR2, squared = rhs = TexMobject( - "=", "2 \\pi R", "(0.1)", "+", "\\pi", "(0.1)", "^2" - ) - VGroup(dR, dR2).set_color(self.dR_color) - change.next_to(self.circle, buff = LARGE_BUFF) - rhs.next_to(change) - - circum_brace = Brace(two_pi_R, UP) - circum_text = circum_brace.get_text("Circumference") - error_brace = Brace(VGroup(pi, squared), UP) - error_text = error_brace.get_text("Error") - error_text.set_color(RED) - - self.play( - Write(change, run_time = 1), - self.pi_creature.change_mode, "pondering", - ) - self.wait() - self.play(*it.chain( - list(map(Write, [equals, two_pi_R, dR])), - list(map(FadeIn, [circum_text, circum_brace])) - )) - self.wait() - self.play(*it.chain( - list(map(Write, [plus, pi, dR2, squared])), - list(map(FadeIn, [error_brace, error_text])) - )) - self.wait(2) - self.change = change - self.circum_term = VGroup(two_pi_R, dR) - self.circum_term.label = VGroup(circum_brace, circum_text) - self.error_term = VGroup(pi, dR2, squared) - self.error_term.label = VGroup(error_brace, error_text) - self.equals = equals - self.plus = plus - - def show_fractions(self): - terms = [self.change, self.circum_term, self.error_term] - for term in terms: - term.frac_line = TexMobject("\\frac{\\quad}{\\quad}") - term.frac_line.stretch_to_fit_width(term.get_width()) - term.frac_line.next_to(term, DOWN, buff = SMALL_BUFF) - term.denom = TexMobject("(0.1)") - term.denom.next_to(term.frac_line, DOWN, buff = SMALL_BUFF) - term.denom.set_color(self.dR_color) - term.denom.save_state() - term.denom.replace(self.nudge_label) - - self.equals.generate_target() - self.equals.target.next_to(self.change.frac_line, RIGHT) - self.plus.generate_target() - self.plus.target.next_to(self.circum_term.frac_line, RIGHT) - - self.play(*it.chain( - [Write(term.frac_line) for term in terms], - list(map(MoveToTarget, [self.equals, self.plus])) - )) - self.play(*[term.denom.restore for term in terms]) - self.wait(2) - self.play( - self.outer_ring.set_color, YELLOW, - rate_func = there_and_back - ) - self.play( - self.nudge_label.scale_in_place, 2, - rate_func = there_and_back - ) - self.wait(2) - canceleres = VGroup(self.circum_term[1], self.circum_term.denom) - self.play(canceleres.set_color, RED) - self.play(FadeOut(canceleres)) - self.remove(self.circum_term) - self.play( - self.circum_term[0].move_to, self.circum_term.frac_line, LEFT, - self.circum_term[0].shift, 0.1*UP, - FadeOut(self.circum_term.frac_line), - MaintainPositionRelativeTo( - self.circum_term.label, - self.circum_term[0] - ) - ) - self.circum_term = self.circum_term[0] - self.wait(2) - self.play( - FadeOut(self.error_term[-1]), - FadeOut(self.error_term.denom) - ) - self.error_term.remove(self.error_term[-1]) - self.play( - self.error_term.move_to, self.error_term.frac_line, - self.error_term.shift, 0.3*LEFT + 0.15*UP, - FadeOut(self.error_term.frac_line), - self.plus.shift, 0.7*LEFT + 0.1*UP, - MaintainPositionRelativeTo( - self.error_term.label, - self.error_term - ) - ) - self.wait() - - def transition_to_dR(self): - dRs = VGroup( - self.nudge_label, - self.change.denom, - self.error_term[1], - ) - error_brace, error_text = self.error_term.label - for s, width in ("(0.01)", 0.05), ("(0.001)", 0.03), ("dR", 0.03): - new_dRs = VGroup(*[ - TexMobject(s).move_to(mob, LEFT) - for mob in dRs - ]) - new_dRs.set_color(self.dR_color) - new_outer_ring = self.get_ring(self.radius, width) - new_nudge_line = self.nudge_line.copy() - new_nudge_line.set_width(width) - new_nudge_line.move_to(self.nudge_line, LEFT) - error_brace.target = error_brace.copy() - error_brace.target.stretch_to_fit_width( - VGroup(self.error_term[0], new_dRs[-1]).get_width() - ) - error_brace.target.move_to(error_brace, LEFT) - self.play( - MoveToTarget(error_brace), - Transform(self.outer_ring, new_outer_ring), - Transform(self.nudge_line, new_nudge_line), - *[ - Transform(*pair) - for pair in zip(dRs, new_dRs) - ] - ) - self.wait() - if s == "(0.001)": - self.plus.generate_target() - self.plus.target.next_to(self.circum_term) - self.error_term.generate_target() - self.error_term.target.next_to(self.plus.target) - error_brace.target = Brace(self.error_term.target) - error_text.target = error_brace.target.get_text("Truly tiny") - error_text.target.set_color(error_text.get_color()) - self.play(*list(map(MoveToTarget, [ - error_brace, error_text, self.plus, self.error_term - ]))) - self.wait() - - difference_text = TextMobject( - "``Tiny " , "d", "ifference in ", "$R$", "''", - arg_separator = "" - - ) - difference_text.set_color_by_tex("d", self.dR_color) - difference_text.next_to(self.pi_creature, UP+RIGHT) - difference_arrow = Arrow(difference_text, self.change.denom) - self.play( - Write(difference_text, run_time = 2), - ShowCreation(difference_arrow), - self.pi_creature.change_mode, "speaking" - ) - self.wait() - - dA = TexMobject("dA") - dA.set_color(self.change.get_color()) - frac_line = self.change.frac_line - frac_line.generate_target() - frac_line.target.stretch_to_fit_width(dA.get_width()) - frac_line.target.next_to(self.equals, LEFT) - dA.next_to(frac_line.target, UP, 2*SMALL_BUFF) - self.change.denom.generate_target() - self.change.denom.target.next_to(frac_line.target, DOWN, 2*SMALL_BUFF) - A = TexMobject("A").replace(difference_text[3]) - difference_arrow.target = Arrow(difference_text, dA.get_left()) - self.play( - Transform(self.change, dA), - MoveToTarget(frac_line), - MoveToTarget(self.change.denom), - Transform(difference_text[3], A), - difference_text[1].set_color, dA.get_color(), - MoveToTarget(difference_arrow), - ) - self.wait(2) - self.play(*list(map(FadeOut, [difference_text, difference_arrow]))) - - def elaborate_on_d(self): - arc = Arc(-np.pi, start_angle = -np.pi/2) - arc.set_height( - self.change.get_center()[1]-self.change.denom.get_center()[1] - ) - arc.next_to(self.change.frac_line, LEFT) - arc.add_tip() - - self.play( - ShowCreation(arc), - self.pi_creature.change_mode, "sassy" - ) - self.wait() - self.play(self.pi_creature.shrug) - self.play(FadeOut(arc)) - self.wait() - - d = TextMobject("``$d$''") - arrow = TexMobject("\\Rightarrow") - arrow.next_to(d) - ignore_error = TextMobject("Ignore error") - d_group = VGroup(d, arrow, ignore_error) - d_group.arrange() - d_group.next_to( - self.pi_creature.get_corner(UP+RIGHT), - buff = LARGE_BUFF - ) - error_group = VGroup( - self.plus, self.error_term, self.error_term.label - ) - - self.play( - Write(d), - self.pi_creature.change_mode, "speaking" - ) - self.play(*list(map(Write, [arrow, ignore_error]))) - self.play(error_group.fade, 0.8) - self.wait(2) - equality_brace = Brace(VGroup(self.change.denom, self.circum_term)) - equal_word = equality_brace.get_text("Equality") - VGroup(equality_brace, equal_word).set_color(BLUE) - self.play( - GrowFromCenter(equality_brace), - Write(equal_word, run_time = 1) - ) - self.wait(2) - self.play(*list(map(FadeOut, [equality_brace, equal_word]))) - - less_wrong_philosophy = TextMobject("``Less wrong'' philosophy") - less_wrong_philosophy.move_to(ignore_error, LEFT) - self.play(Transform(ignore_error, less_wrong_philosophy)) - self.wait() - - big_dR = 0.3 - big_outer_ring = self.get_ring(self.radius, big_dR) - big_nudge_line = self.nudge_line.copy() - big_nudge_line.stretch_to_fit_width(big_dR) - big_nudge_line.move_to(self.nudge_line, LEFT) - new_nudge_arrow = Arrow(self.nudge_label, big_nudge_line, buff = SMALL_BUFF) - self.outer_ring.save_state() - self.nudge_line.save_state() - self.nudge_arrow.save_state() - self.play( - Transform(self.outer_ring, big_outer_ring), - Transform(self.nudge_line, big_nudge_line), - Transform(self.nudge_arrow, new_nudge_arrow), - ) - self.play( - *[ - mob.restore - for mob in [ - self.outer_ring, - self.nudge_line, - self.nudge_arrow, - ] - ], - rate_func=linear, - run_time = 7 - ) - self.play(self.pi_creature.change_mode, "hooray") - self.less_wrong_philosophy = VGroup( - d, arrow, ignore_error - ) - - def not_infinitely_small(self): - randy = Randolph().flip() - randy.scale(0.7) - randy.to_corner(DOWN+RIGHT) - bubble = SpeechBubble() - bubble.write("$dR$ is infinitely small") - bubble.resize_to_content() - bubble.stretch(0.7, 1) - bubble.pin_to(randy) - bubble.set_fill(BLACK, opacity = 1) - bubble.add_content(bubble.content) - self.play(FadeIn(randy)) - self.play( - randy.change_mode, "speaking", - ShowCreation(bubble), - Write(bubble.content), - self.pi_creature.change_mode, "confused" - ) - self.wait() - - to_infs = [self.change, self.change.denom, self.nudge_label] - for mob in to_infs: - mob.save_state() - mob.inf = TexMobject("1/\\infty") - mob.inf.set_color(mob.get_color()) - mob.inf.move_to(mob) - self.play(*[ - Transform(mob, mob.inf) - for mob in to_infs - ]) - self.wait() - self.play(self.pi_creature.change_mode, "pleading") - self.wait() - self.play(*it.chain( - [mob.restore for mob in to_infs], - list(map(FadeOut, [bubble, bubble.content])), - [randy.change_mode, "erm"], - [self.pi_creature.change_mode, "happy"], - )) - for n in range(7): - target = TexMobject("0.%s1"%("0"*n)) - target.set_color(self.nudge_label.get_color()) - target.move_to(self.nudge_label, LEFT) - self.outer_ring.target = self.get_ring(self.radius, 0.1/(n+1)) - self.nudge_line.get_center = self.nudge_line.get_left - self.play( - Transform(self.nudge_label, target), - MoveToTarget(self.outer_ring), - self.nudge_line.stretch_to_fit_width, 0.1/(n+1) - ) - self.wait() - bubble.write("Wrong!") - bubble.resize_to_content() - bubble.stretch(0.7, 1) - bubble.pin_to(randy) - bubble.add_content(bubble.content) - self.play( - FadeIn(bubble), - Write(bubble.content, run_time = 1), - randy.change_mode, "angry", - ) - self.play(randy.set_color, RED) - self.play(self.pi_creature.change_mode, "guilty") - self.wait() - - new_bubble = self.pi_creature.get_bubble(SpeechBubble) - new_bubble.set_fill(BLACK, opacity = 0.8) - new_bubble.write("But it gets \\\\ less wrong!") - new_bubble.resize_to_content() - new_bubble.pin_to(self.pi_creature) - - self.play( - FadeOut(bubble), - FadeOut(bubble.content), - ShowCreation(new_bubble), - Write(new_bubble.content), - randy.change_mode, "erm", - randy.set_color, BLUE_E, - self.pi_creature.change_mode, "shruggie" - ) - self.wait(2) - -class NameDerivative(IntroduceTinyChangeInArea): - def construct(self): - self.increase_radius(run_time = 0) - self.change_nudge_label() - self.name_derivative_for_cricle() - self.interpret_geometrically() - self.show_limiting_process() - self.reference_approximation() - self.emphasize_equality() - - def change_nudge_label(self): - new_label = TexMobject("dR") - new_label.move_to(self.nudge_label) - new_label.to_edge(UP) - new_label.set_color(self.nudge_label.get_color()) - new_arrow = Arrow(new_label, self.nudge_line) - - self.remove(self.nudge_label, self.nudge_arrow) - self.nudge_label = new_label - self.nudge_arrow = new_arrow - self.add(self.nudge_label, self.nudge_arrow) - self.wait() - - def name_derivative_for_cricle(self): - dA_dR, equals, d_formula_dR, equals2, two_pi_R = dArea_fom = TexMobject( - "\\frac{dA}{dR}", - "=", "\\frac{d(\\pi R^2)}{dR}", - "=", "2\\pi R" - ) - dArea_fom.to_edge(UP, buff = MED_LARGE_BUFF).shift(RIGHT) - dA, frac_line, dR = VGroup(*dA_dR[:2]), dA_dR[2], VGroup(*dA_dR[3:]) - dA.set_color(GREEN_B) - dR.set_color(self.dR_color) - VGroup(*d_formula_dR[7:]).set_color(self.dR_color) - - - dA_dR_circle = Circle() - dA_dR_circle.replace(dA_dR, stretch = True) - dA_dR_circle.scale_in_place(1.5) - dA_dR_circle.set_color(BLUE) - - words = TextMobject( - "``Derivative'' of $A$\\\\", - "with respect to $R$" - ) - words.next_to(dA_dR_circle, DOWN, buff = 1.5*LARGE_BUFF) - words.shift(0.5*LEFT) - arrow = Arrow(words, dA_dR_circle) - arrow.set_color(dA_dR_circle.get_color()) - - self.play(Transform(self.outer_ring.copy(), dA, run_time = 2)) - self.play( - Transform(self.nudge_line.copy(), dR, run_time = 2), - Write(frac_line) - ) - self.wait() - self.play( - ShowCreation(dA_dR_circle), - ShowCreation(arrow), - Write(words) - ) - self.wait() - self.play(Write(VGroup(equals, d_formula_dR))) - self.wait() - self.play(Write(VGroup(equals2, two_pi_R))) - self.wait() - self.dArea_fom = dArea_fom - self.words = words - self.two_pi_R = two_pi_R - - def interpret_geometrically(self): - target_formula = TexMobject( - "\\frac{d \\quad}{dR} = " - ) - VGroup(*target_formula[2:4]).set_color(self.dR_color) - target_formula.scale(1.3) - target_formula.next_to(self.dArea_fom, DOWN) - target_formula.shift(2*RIGHT + 0.5*DOWN) - - area_form = VGroup(*self.dArea_fom[2][2:5]).copy() - area_form.set_color(BLUE_D) - circum_form = self.dArea_fom[-1] - - circle_width = 1 - area_circle = self.circle.copy() - area_circle.set_stroke(width = 0) - area_circle.generate_target() - area_circle.target.set_width(circle_width) - area_circle.target.next_to(target_formula[0], RIGHT, buff = 0) - area_circle.target.set_color(BLUE_D) - circum_circle = self.circle.copy() - circum_circle.set_fill(opacity = 0) - circum_circle.generate_target() - circum_circle.target.set_width(circle_width) - circum_circle.target.next_to(target_formula) - - self.play( - Write(target_formula), - MoveToTarget(area_circle), - MoveToTarget( - circum_circle, - run_time = 2, - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - self.pi_creature.change_mode, "hooray" - ) - self.wait() - self.play(Transform(area_circle.copy(), area_form)) - self.remove(area_form) - self.play(Transform(circum_circle.copy(), circum_form)) - self.change_mode("happy") - - def show_limiting_process(self): - big_dR = 0.3 - small_dR = 0.01 - big_ring = self.get_ring(self.radius, big_dR) - small_ring = self.get_ring(self.radius, small_dR) - big_nudge_line = self.nudge_line.copy().set_width(big_dR) - small_nudge_line = self.nudge_line.copy().set_width(small_dR) - for line in big_nudge_line, small_nudge_line: - line.move_to(self.nudge_line, LEFT) - new_nudge_arrow = Arrow(self.nudge_label, big_nudge_line) - small_nudge_arrow = Arrow(self.nudge_label, small_nudge_line) - - ring_group = VGroup(self.outer_ring, self.nudge_line, self.nudge_arrow) - ring_group.save_state() - big_group = VGroup(big_ring, big_nudge_line, new_nudge_arrow) - small_group = VGroup(small_ring, small_nudge_line, small_nudge_arrow) - - fracs = VGroup() - sample_dRs = [0.3, 0.1, 0.01] - for dR in sample_dRs: - dA = 2*np.pi*dR + np.pi*(dR**2) - frac = TexMobject("\\frac{%.3f}{%.2f}"%(dA, dR)) - VGroup(*frac[:5]).set_color(self.outer_ring.get_color()) - VGroup(*frac[6:]).set_color(self.dR_color) - fracs.add(frac) - fracs.add(TexMobject("\\cdots \\rightarrow")) - fracs.add(TexMobject("???")) - fracs[-1].set_color_by_gradient(self.dR_color, self.outer_ring.get_color()) - fracs.arrange(RIGHT, buff = MED_LARGE_BUFF) - fracs.to_corner(DOWN+LEFT) - - arrows = VGroup() - for frac in fracs[:len(sample_dRs)] + [fracs[-1]]: - arrow = Arrow(self.words.get_bottom(), frac.get_top()) - arrow.set_color(WHITE) - if frac is fracs[-1]: - check = TexMobject("\\checkmark") - check.set_color(GREEN) - check.next_to(arrow.get_center(), UP+RIGHT, SMALL_BUFF) - arrow.add(check) - else: - cross = TexMobject("\\times") - cross.set_color(RED) - cross.move_to(arrow.get_center()) - cross.set_stroke(RED, width = 5) - arrow.add(cross) - arrows.add(arrow) - - - self.play( - Transform(ring_group, big_group), - self.pi_creature.change_mode, "sassy" - ) - for n, frac in enumerate(fracs): - anims = [FadeIn(frac)] - num_fracs = len(sample_dRs) - if n < num_fracs: - anims.append(ShowCreation(arrows[n])) - anims.append(Transform( - ring_group, small_group, - rate_func = lambda t : t*(1./(num_fracs-n)), - run_time = 2 - )) - elif n > num_fracs: - anims.append(ShowCreation(arrows[-1])) - self.play(*anims) - self.wait(2) - self.play( - FadeOut(arrows), - ring_group.restore, - self.pi_creature.change_mode, "happy", - ) - self.wait() - - def reference_approximation(self): - ring_copy = self.outer_ring.copy() - self.unwrap_ring(ring_copy) - self.wait() - self.last_mover = ring_copy - - def emphasize_equality(self): - equals = self.dArea_fom[-2] - - self.play(Transform(self.last_mover, equals)) - self.remove(self.last_mover) - self.play( - equals.scale_in_place, 1.5, - equals.set_color, GREEN, - rate_func = there_and_back, - run_time = 2 - ) - self.play( - self.two_pi_R.set_stroke, YELLOW, 3, - rate_func = there_and_back, - run_time = 2 - ) - self.wait() - - new_words = TextMobject( - "Systematically\\\\", - "ignore error" - ) - new_words.move_to(self.words) - self.play(Transform(self.words, new_words)) - self.wait() - -class DerivativeAsTangentLine(ZoomedScene): - CONFIG = { - "zoomed_canvas_frame_shape" : (4, 4), - "zoom_factor" : 10, - "R_min" : 0, - "R_max" : 2.5, - "R_to_zoom_in_on" : 2, - "little_rect_nudge" : 0.075*(UP+RIGHT), - } - def construct(self): - self.setup_axes() - self.show_zoomed_in_steps() - self.show_tangent_lines() - self.state_commonality() - - def setup_axes(self): - x_axis = NumberLine( - x_min = -0.25, - x_max = 4, - unit_size = 2, - tick_frequency = 0.25, - leftmost_tick = -0.25, - numbers_with_elongated_ticks = [0, 1, 2, 3, 4], - color = GREY - ) - x_axis.shift(2.5*DOWN) - x_axis.shift(4*LEFT) - x_axis.add_numbers(1, 2, 3, 4) - x_label = TexMobject("R") - x_label.next_to(x_axis, RIGHT+UP, buff = SMALL_BUFF) - self.x_axis_label = x_label - - y_axis = NumberLine( - x_min = -2, - x_max = 20, - unit_size = 0.3, - tick_frequency = 2.5, - leftmost_tick = 0, - longer_tick_multiple = -2, - numbers_with_elongated_ticks = [0, 5, 10, 15, 20], - color = GREY - ) - y_axis.shift(x_axis.number_to_point(0)-y_axis.number_to_point(0)) - y_axis.rotate(np.pi/2, about_point = y_axis.number_to_point(0)) - y_axis.add_numbers(5, 10, 15, 20) - y_axis.numbers.shift(0.4*UP+0.5*LEFT) - y_label = TexMobject("A") - y_label.next_to(y_axis.get_top(), RIGHT, buff = MED_LARGE_BUFF) - - def func(alpha): - R = interpolate(self.R_min, self.R_max, alpha) - x = x_axis.number_to_point(R)[0] - output = np.pi*(R**2) - y = y_axis.number_to_point(output)[1] - return x*RIGHT + y*UP - - graph = ParametricFunction(func, color = BLUE) - graph_label = TexMobject("A(R) = \\pi R^2") - graph_label.set_color(BLUE) - graph_label.next_to( - graph.point_from_proportion(2), LEFT - ) - - self.play(Write(VGroup(x_axis, y_axis))) - self.play(ShowCreation(graph)) - self.play(Write(graph_label)) - self.play(Write(VGroup(x_label, y_label))) - self.wait() - - self.x_axis, self.y_axis = x_axis, y_axis - self.graph = graph - self.graph_label = graph_label - - def graph_point(self, R): - alpha = (R - self.R_min)/(self.R_max - self.R_min) - return self.graph.point_from_proportion(alpha) - - def angle_of_tangent(self, R, dR = 0.01): - vect = self.graph_point(R + dR) - self.graph_point(R) - return angle_of_vector(vect) - - def show_zoomed_in_steps(self): - R = self.R_to_zoom_in_on - dR = 0.05 - graph_point = self.graph_point(R) - nudged_point = self.graph_point(R+dR) - interim_point = nudged_point[0]*RIGHT + graph_point[1]*UP - - - self.activate_zooming() - dot = Dot(color = YELLOW) - dot.scale(0.1) - dot.move_to(graph_point) - - self.play(*list(map(FadeIn, [ - self.little_rectangle, - self.big_rectangle - ]))) - self.play( - self.little_rectangle.move_to, - graph_point+self.little_rect_nudge - ) - self.play(FadeIn(dot)) - - dR_line = Line(graph_point, interim_point) - dR_line.set_color(YELLOW) - dA_line = Line(interim_point, nudged_point) - dA_line.set_color(GREEN) - tiny_buff = SMALL_BUFF/self.zoom_factor - for line, vect, char in (dR_line, DOWN, "R"), (dA_line, RIGHT, "A"): - line.brace = Brace(Line(LEFT, RIGHT)) - line.brace.scale(1./self.zoom_factor) - line.brace.stretch_to_fit_width(line.get_length()) - line.brace.rotate(line.get_angle()) - line.brace.next_to(line, vect, buff = tiny_buff) - line.text = TexMobject("d%s"%char) - line.text.scale(1./self.zoom_factor) - line.text.set_color(line.get_color()) - line.text.next_to(line.brace, vect, buff = tiny_buff) - self.play(ShowCreation(line)) - self.play(Write(VGroup(line.brace, line.text))) - self.wait() - - deriv_is_slope = TexMobject( - "\\frac{dA}{dR} =", "\\text{Slope}" - ) - self.slope_word = deriv_is_slope[1] - VGroup(*deriv_is_slope[0][:2]).set_color(GREEN) - VGroup(*deriv_is_slope[0][3:5]).set_color(YELLOW) - deriv_is_slope.next_to(self.y_axis, RIGHT) - deriv_is_slope.shift(UP) - - self.play(Write(deriv_is_slope)) - self.wait() - - ### Whoa boy, this aint' gonna be pretty - self.dot = dot - self.small_step_group = VGroup( - dR_line, dR_line.brace, dR_line.text, - dA_line, dA_line.brace, dA_line.text, - ) - def update_small_step_group(group): - R = self.x_axis.point_to_number(dot.get_center()) - graph_point = self.graph_point(R) - nudged_point = self.graph_point(R+dR) - interim_point = nudged_point[0]*RIGHT + graph_point[1]*UP - - dR_line.put_start_and_end_on(graph_point, interim_point) - dA_line.put_start_and_end_on(interim_point, nudged_point) - - dR_line.brace.stretch_to_fit_width(dR_line.get_width()) - dR_line.brace.next_to(dR_line, DOWN, buff = tiny_buff) - dR_line.text.next_to(dR_line.brace, DOWN, buff = tiny_buff) - - dA_line.brace.stretch_to_fit_height(dA_line.get_height()) - dA_line.brace.next_to(dA_line, RIGHT, buff = tiny_buff) - dA_line.text.next_to(dA_line.brace, RIGHT, buff = tiny_buff) - self.update_small_step_group = update_small_step_group - - def show_tangent_lines(self): - R = self.R_to_zoom_in_on - line = Line(LEFT, RIGHT).scale(FRAME_Y_RADIUS) - line.set_color(MAROON_B) - line.rotate(self.angle_of_tangent(R)) - line.move_to(self.graph_point(R)) - x_axis_y = self.x_axis.number_to_point(0)[1] - two_pi_R = TexMobject("= 2\\pi R") - two_pi_R.next_to(self.slope_word, DOWN, aligned_edge = RIGHT) - two_pi_R.shift(0.5*LEFT) - - def line_update_func(line): - R = self.x_axis.point_to_number(self.dot.get_center()) - line.rotate( - self.angle_of_tangent(R) - line.get_angle() - ) - line.move_to(self.dot) - def update_little_rect(rect): - R = self.x_axis.point_to_number(self.dot.get_center()) - rect.move_to(self.graph_point(R) + self.little_rect_nudge) - - self.play(ShowCreation(line)) - self.wait() - self.note_R_value_of_point() - - alphas = np.arange(0, 1, 0.01) - graph_points = list(map(self.graph.point_from_proportion, alphas)) - curr_graph_point = self.graph_point(R) - self.last_alpha = alphas[np.argmin([ - get_norm(point - curr_graph_point) - for point in graph_points - ])] - def shift_everything_to_alpha(alpha, run_time = 3): - self.play( - MoveAlongPath( - self.dot, self.graph, - rate_func = lambda t : interpolate(self.last_alpha, alpha, smooth(t)) - ), - UpdateFromFunc(line, line_update_func), - UpdateFromFunc(self.small_step_group, self.update_small_step_group), - UpdateFromFunc(self.little_rectangle, update_little_rect), - run_time = run_time - ) - self.last_alpha = alpha - - for alpha in 0.95, 0.2: - shift_everything_to_alpha(alpha) - self.wait() - self.play(Write(two_pi_R)) - self.wait() - shift_everything_to_alpha(0.8, 4) - self.wait() - - def note_R_value_of_point(self): - R = self.R_to_zoom_in_on - point = self.graph_point(R) - R_axis_point = point[0]*RIGHT + 2.5*DOWN - - dashed_line = DashedLine(point, R_axis_point, color = RED) - dot = Dot(R_axis_point, color = RED) - arrow = Arrow( - self.x_axis_label.get_left(), - dot, - buff = SMALL_BUFF - ) - self.play(ShowCreation(dashed_line)) - self.play(ShowCreation(dot)) - self.play(ShowCreation(arrow)) - self.play(dot.scale_in_place, 2, rate_func = there_and_back) - self.wait() - self.play(*list(map(FadeOut, [dashed_line, dot, arrow]))) - - def state_commonality(self): - morty = Mortimer() - morty.scale(0.7) - morty.to_edge(DOWN).shift(2*RIGHT) - bubble = morty.get_bubble(SpeechBubble, height = 2) - bubble.set_fill(BLACK, opacity = 0.8) - bubble.shift(0.5*DOWN) - bubble.write("This is the standard view") - - self.play(FadeIn(morty)) - self.play( - ShowCreation(bubble), - Write(bubble.content), - morty.change_mode, "surprised" - ) - self.play(Blink(morty)) - self.wait() - new_words = TextMobject("Which is...fine...") - new_words.move_to(bubble.content, RIGHT) - self.play( - bubble.stretch_to_fit_width, 5, - bubble.shift, RIGHT, - Transform(bubble.content, new_words), - morty.change_mode, "hesitant" - ) - self.play(Blink(morty)) - self.wait() - -class SimpleConfusedPi(Scene): - def construct(self): - randy = Randolph() - confused = Randolph(mode = "confused") - for pi in randy, confused: - pi.flip() - pi.look(UP+LEFT) - pi.scale(2) - pi.rotate(np.pi/2) - self.play(Transform(randy, confused)) - self.wait() - -class TangentLinesAreNotEverything(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Tangent lines are just - one way to visualize - derivatives - """) - self.change_student_modes("raise_left_hand", "pondering", "erm") - self.random_blink(3) - -class OnToIntegrals(TeacherStudentsScene): - def construct(self): - self.teacher_says("On to integrals!", target_mode = "hooray") - self.change_student_modes(*["happy"]*3) - self.random_blink(3) - -class IntroduceConcentricRings(CircleScene): - CONFIG = { - "radius" : 2.5, - "special_ring_index" : 10, - "include_pi_creature" : True, - } - def construct(self): - self.build_up_rings() - self.add_up_areas() - self.unwrap_special_ring() - self.write_integral() - self.ask_about_approx() - - def create_pi_creature(self): - morty = Mortimer() - morty.scale(0.7) - morty.to_corner(DOWN+RIGHT) - return morty - - def build_up_rings(self): - self.circle.set_fill(opacity = 0) - rings = VGroup(*[ - self.get_ring(r, self.dR) - for r in np.arange(0, self.radius, self.dR) - ]) - rings.set_color_by_gradient(BLUE_E, GREEN_E) - rings.set_stroke(BLACK, width = 1) - outermost_ring = rings[-1] - dr_line = Line( - rings[-2].get_top(), - rings[-1].get_top(), - color = YELLOW - ) - dr_text = TexMobject("dr") - dr_text.move_to(self.circle.get_corner(UP+RIGHT)) - dr_text.shift(LEFT) - dr_text.set_color(YELLOW) - dr_arrow = Arrow(dr_text, dr_line, buff = SMALL_BUFF) - self.dr_group = VGroup(dr_text, dr_arrow, dr_line) - - - foreground_group = VGroup(self.radius_brace, self.radius_label, self.radius_line) - self.play( - FadeIn(outermost_ring), - Animation(foreground_group) - ) - self.play( - Write(dr_text), - ShowCreation(dr_arrow), - ShowCreation(dr_line) - ) - foreground_group.add(dr_line, dr_arrow, dr_text) - self.change_mode("pondering") - self.wait() - self.play( - FadeIn( - VGroup(*rings[:-1]), - lag_ratio=1, - run_time = 5 - ), - Animation(foreground_group) - ) - self.wait() - - self.foreground_group = foreground_group - self.rings = rings - - def add_up_areas(self): - start_rings = VGroup(*self.rings[:4]) - moving_rings = start_rings.copy() - moving_rings.generate_target() - moving_rings.target.set_stroke(width = 0) - plusses = VGroup(*[TexMobject("+") for ring in moving_rings]) - area_sum = VGroup(*it.chain(*list(zip( - [ring for ring in moving_rings.target], - plusses - )))) - dots_equals_area = TexMobject("\\dots", "=", "\\pi R^2") - area_sum.add(*dots_equals_area) - area_sum.arrange() - area_sum.to_edge(RIGHT) - area_sum.to_edge(UP, buff = MED_SMALL_BUFF) - dots_equals_area[-1].shift(0.1*UP) - self.area_sum_rhs = dots_equals_area[-1] - - # start_rings.set_fill(opacity = 0.3) - self.play( - MoveToTarget( - moving_rings, - lag_ratio = 0.5, - ), - Write( - VGroup(plusses, dots_equals_area), - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - Animation(self.foreground_group), - run_time = 5, - ) - self.wait() - self.area_sum = area_sum - - def unwrap_special_ring(self): - rings = self.rings - foreground_group = self.foreground_group - special_ring = rings[self.special_ring_index] - special_ring.save_state() - - radius = (special_ring.get_width()-2*self.dR)/2. - radial_line = Line(ORIGIN, radius*RIGHT) - radial_line.rotate(np.pi/4) - radial_line.shift(self.circle.get_center()) - radial_line.set_color(YELLOW) - r_label = TexMobject("r") - r_label.next_to(radial_line.get_center(), UP+LEFT, buff = SMALL_BUFF) - - rings.generate_target() - rings.save_state() - rings.target.set_fill(opacity = 0.3) - rings.target.set_stroke(BLACK) - rings.target[self.special_ring_index].set_fill(opacity = 1) - self.play( - MoveToTarget(rings), - Animation(foreground_group) - ) - self.play(ShowCreation(radial_line)) - self.play(Write(r_label)) - self.foreground_group.add(radial_line, r_label) - self.wait() - self.unwrap_ring(special_ring, to_edge = RIGHT) - - brace = Brace(special_ring, UP) - brace.stretch_in_place(0.9, 0) - two_pi_r = brace.get_text("$2\\pi r$") - left_brace = TexMobject("\\{") - left_brace.stretch_to_fit_height(1.5*self.dR) - left_brace.next_to(special_ring, LEFT, buff = SMALL_BUFF) - dr = TexMobject("dr") - dr.next_to(left_brace, LEFT, buff = SMALL_BUFF) - self.play( - GrowFromCenter(brace), - Write(two_pi_r) - ) - self.play(GrowFromCenter(left_brace), Write(dr)) - self.wait() - - think_concrete = TextMobject("Think $dr = 0.1$") - think_concrete.next_to(dr, DOWN+LEFT, buff = LARGE_BUFF) - arrow = Arrow(think_concrete.get_top(), dr) - self.play( - Write(think_concrete), - ShowCreation(arrow), - self.pi_creature.change_mode, "speaking" - ) - self.wait() - - less_wrong = TextMobject(""" - Approximations get - less wrong - """) - less_wrong.next_to(self.pi_creature, LEFT, aligned_edge = UP) - self.play(Write(less_wrong)) - self.wait() - - self.special_ring = special_ring - self.radial_line = radial_line - self.r_label = r_label - self.to_fade = VGroup( - brace, left_brace, two_pi_r, dr, - think_concrete, arrow, less_wrong - ) - self.two_pi_r = two_pi_r.copy() - self.dr = dr.copy() - - def write_integral(self): - brace = Brace(self.area_sum) - formula_q = brace.get_text("Nice formula?") - int_sym, R, zero = def_int = TexMobject("\\int", "_0", "^R") - self.two_pi_r.generate_target() - self.dr.generate_target() - equals_pi_R_squared = TexMobject("= \\pi R^2") - integral_expression = VGroup( - def_int, self.two_pi_r.target, - self.dr.target, equals_pi_R_squared - ) - integral_expression.arrange() - integral_expression.next_to(brace, DOWN) - self.integral_expression = VGroup(*integral_expression[:-1]) - - self.play( - GrowFromCenter(brace), - Write(formula_q), - self.pi_creature.change_mode, "pondering" - ) - self.wait(2) - - last = VMobject() - last.save_state() - for ring in self.rings: - ring.save_state() - target = ring.copy() - target.set_fill(opacity = 1) - self.play( - last.restore, - Transform(ring, target), - Animation(self.foreground_group), - run_time = 0.5 - ) - last = ring - self.play(last.restore) - self.wait() - - ghost = self.rings.copy() - for mob in self.area_sum_rhs, self.two_pi_r: - ghost.set_fill(opacity = 0.1) - self.play(Transform(ghost, mob)) - self.wait() - self.remove(ghost) - - self.wait() - self.play(FadeOut(formula_q)) - self.play(Write(int_sym)) - self.wait() - self.rings.generate_target() - self.rings.target.set_fill(opacity = 1) - self.play( - MoveToTarget(self.rings, rate_func = there_and_back), - Animation(self.foreground_group) - ) - self.wait() - self.grow_and_shrink_r_line(zero, R) - self.wait() - self.play( - MoveToTarget(self.two_pi_r), - MoveToTarget(self.dr), - run_time = 2 - ) - self.wait() - self.play( - FadeOut(self.to_fade), - ApplyMethod(self.rings.restore, run_time = 2), - Animation(self.foreground_group) - ) - self.wait() - self.play(Write(equals_pi_R_squared)) - self.wait() - self.equals = equals_pi_R_squared[0] - self.integral_terms = VGroup( - self.integral_expression[1], - self.integral_expression[2], - self.int_lower_bound, - self.int_upper_bound, - VGroup(*equals_pi_R_squared[1:]) - ) - - def grow_and_shrink_r_line(self, zero_target, R_target): - self.radial_line.get_center = self.circle.get_center - self.radial_line.save_state() - self.radial_line.generate_target() - self.radial_line.target.scale_in_place( - 0.1 / self.radial_line.get_length() - ) - self.r_label.generate_target() - self.r_label.save_state() - equals_0 = TexMobject("=0") - r_equals_0 = VGroup(self.r_label.target, equals_0) - r_equals_0.arrange(buff = SMALL_BUFF) - r_equals_0.next_to(self.radial_line.target, UP+LEFT, buff = SMALL_BUFF) - self.play( - MoveToTarget(self.radial_line), - MoveToTarget(self.r_label), - GrowFromCenter(equals_0) - ) - self.play(equals_0[-1].copy().replace, zero_target) - self.remove(self.get_mobjects_from_last_animation()[0]) - self.add(zero_target) - self.wait() - self.radial_line.target.scale_in_place( - self.radius/self.radial_line.get_length() - ) - equals_0.target = TexMobject("=R") - equals_0.target.next_to( - self.radial_line.target.get_center_of_mass(), - UP+LEFT, buff = SMALL_BUFF - ) - self.r_label.target.next_to(equals_0.target, LEFT, buff = SMALL_BUFF) - self.play( - MoveToTarget(self.radial_line), - MoveToTarget(self.r_label), - MoveToTarget(equals_0) - ) - self.play(equals_0[-1].copy().replace, R_target) - self.remove(self.get_mobjects_from_last_animation()[0]) - self.add(R_target) - self.wait() - self.play( - self.radial_line.restore, - self.r_label.restore, - FadeOut(equals_0) - ) - self.int_lower_bound, self.int_upper_bound = zero_target, R_target - - def ask_about_approx(self): - approx = TexMobject("\\approx").replace(self.equals) - self.equals.save_state() - question = TextMobject( - "Should this be\\\\", - "an approximation?" - ) - question.next_to(approx, DOWN, buff = 1.3*LARGE_BUFF) - arrow = Arrow(question, approx, buff = MED_SMALL_BUFF) - approach_words = TextMobject("Consider\\\\", "$dr \\to 0$") - approach_words.move_to(question, RIGHT) - int_brace = Brace(self.integral_expression) - integral_word = int_brace.get_text("``Integral''") - - self.play( - Transform(self.equals, approx), - Write(question), - ShowCreation(arrow), - self.pi_creature.change_mode, "confused" - ) - self.wait(2) - self.play(*[ - ApplyMethod(ring.set_stroke, ring.get_color(), width = 1) - for ring in self.rings - ] + [ - FadeOut(self.dr_group), - Animation(self.foreground_group) - ]) - self.wait() - self.play( - Transform(question, approach_words), - Transform(arrow, Arrow(approach_words, approx)), - self.equals.restore, - self.pi_creature.change_mode, "happy" - ) - self.wait(2) - self.play( - self.integral_expression.set_color_by_gradient, BLUE, GREEN, - GrowFromCenter(int_brace), - Write(integral_word) - ) - self.wait() - for term in self.integral_terms: - term.save_state() - self.play(term.set_color, YELLOW) - self.play(term.restore) - self.wait(3) - -class AskAboutGeneralCircles(TeacherStudentsScene): - def construct(self): - self.student_says(""" - What about integrals - beyond this circle - example? - """) - self.change_student_modes("confused") - self.random_blink(2) - self.teacher_says( - "All in due time", - ) - self.change_student_modes(*["happy"]*3) - self.random_blink(2) - -class GraphIntegral(GraphScene): - CONFIG = { - "x_min" : -0.25, - "x_max" : 4, - "x_tick_frequency" : 0.25, - "x_leftmost_tick" : -0.25, - "x_labeled_nums" : list(range(1, 5)), - "x_axis_label" : "r", - "y_min" : -2, - "y_max" : 25, - "y_tick_frequency" : 2.5, - "y_bottom_tick" : 0, - "y_labeled_nums" : list(range(5, 30, 5)), - "y_axis_label" : "", - "dr" : 0.125, - "R" : 3.5, - } - def construct(self): - self.func = lambda r : 2*np.pi*r - integral = TexMobject("\\int_0^R 2\\pi r \\, dr") - integral.to_edge(UP).shift(LEFT) - self.little_r = integral[5] - - self.play(Write(integral)) - self.wait() - self.setup_axes() - self.show_horizontal_axis() - self.add_rectangles() - self.thinner_rectangles() - self.ask_about_area() - - def show_horizontal_axis(self): - arrows = [ - Arrow(self.little_r, self.coords_to_point(*coords)) - for coords in ((0, 0), (self.x_max, 0)) - ] - moving_arrow = arrows[0].copy() - self.play( - ShowCreation(moving_arrow), - self.little_r.set_color, YELLOW - ) - for arrow in reversed(arrows): - self.play(Transform(moving_arrow, arrow, run_time = 4)) - self.play( - FadeOut(moving_arrow), - self.little_r.set_color, WHITE - ) - - def add_rectangles(self): - tick_height = 0.2 - special_tick_index = 12 - ticks = VGroup(*[ - Line(UP, DOWN).move_to(self.coords_to_point(x, 0)) - for x in np.arange(0, self.R+self.dr, self.dr) - ]) - ticks.stretch_to_fit_height(tick_height) - ticks.set_color(YELLOW) - R_label = TexMobject("R") - R_label.next_to(self.coords_to_point(self.R, 0), DOWN) - - values_words = TextMobject("Values of $r$") - values_words.shift(UP) - arrows = VGroup(*[ - Arrow( - values_words.get_bottom(), - tick.get_center(), - tip_length = 0.15 - ) - for tick in ticks - ]) - - dr_brace = Brace( - VGroup(*ticks[special_tick_index:special_tick_index+2]), - buff = SMALL_BUFF - ) - dr_text = dr_brace.get_text("$dr$", buff = SMALL_BUFF) - # dr_text.set_color(YELLOW) - - rectangles = self.get_rectangles(self.dr) - special_rect = rectangles[special_tick_index] - left_brace = Brace(special_rect, LEFT) - height_label = left_brace.get_text("$2\\pi r$") - - self.play( - ShowCreation(ticks, lag_ratio = 0.5), - Write(R_label) - ) - self.play( - Write(values_words), - ShowCreation(arrows) - ) - self.wait() - self.play( - GrowFromCenter(dr_brace), - Write(dr_text) - ) - self.wait() - rectangles.save_state() - rectangles.stretch_to_fit_height(0) - rectangles.move_to(self.graph_origin, DOWN+LEFT) - self.play(*list(map(FadeOut, [arrows, values_words]))) - self.play( - rectangles.restore, - Animation(ticks), - run_time = 2 - ) - self.wait() - self.play(*[ - ApplyMethod(rect.fade, 0.7) - for rect in rectangles - if rect is not special_rect - ] + [Animation(ticks)]) - self.play( - GrowFromCenter(left_brace), - Write(height_label) - ) - self.wait() - - graph = self.graph_function( - lambda r : 2*np.pi*r, - animate = False - ) - graph_label = self.label_graph( - self.graph, "f(r) = 2\\pi r", - proportion = 0.5, - direction = LEFT, - animate = False - ) - self.play( - rectangles.restore, - Animation(ticks), - FadeOut(left_brace), - Transform(height_label, graph_label), - ShowCreation(graph) - ) - self.wait(3) - self.play(*list(map(FadeOut, [ticks, dr_brace, dr_text]))) - self.rectangles = rectangles - - def thinner_rectangles(self): - for x in range(2, 8): - new_rects = self.get_rectangles( - dr = self.dr/x, stroke_width = 1./x - ) - self.play(Transform(self.rectangles, new_rects)) - self.wait() - - def ask_about_area(self): - question = TextMobject("What's this \\\\ area") - question.to_edge(RIGHT).shift(2*UP) - arrow = Arrow( - question.get_bottom(), - self.rectangles, - buff = SMALL_BUFF - ) - self.play( - Write(question), - ShowCreation(arrow) - ) - self.wait() - - def get_rectangles(self, dr, stroke_width = 1): - return self.get_riemann_rectangles( - 0, self.R, dr, stroke_width = stroke_width - ) - -class MoreOnThisLater(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - More details on - integrals later - """) - self.change_student_modes( - "raise_right_hand", - "raise_left_hand", - "raise_left_hand", - ) - self.random_blink(2) - self.teacher_says(""" - This is just - a preview - """) - self.random_blink(2) - -class FundamentalTheorem(CircleScene): - CONFIG = { - "circle_corner" : ORIGIN, - "radius" : 1.5, - "area_color" : BLUE, - "circum_color" : WHITE, - "unwrapped_tip" : 2.5*UP, - "include_pi_creature" : False - } - def setup(self): - CircleScene.setup(self) - group = VGroup( - self.circle, self.radius_line, - self.radius_brace, self.radius_label - ) - self.remove(*group) - group.shift(DOWN) - - self.foreground_group = VGroup( - self.radius_line, - self.radius_brace, - self.radius_label, - ) - - def create_pi_creature(self): - morty = Mortimer() - morty.scale(0.7) - morty.to_corner(DOWN+RIGHT) - return morty - - def construct(self): - self.add_derivative_terms() - self.add_integral_terms() - self.think_about_it() - self.bring_in_circle() - self.show_outer_ring() - self.show_all_rings() - self.emphasize_oposites() - - def add_derivative_terms(self): - symbolic = TexMobject( - "\\frac{d(\\pi R^2)}{dR} =", "2\\pi R" - ) - VGroup(*symbolic[0][2:5]).set_color(self.area_color) - VGroup(*symbolic[0][7:9]).set_color(self.dR_color) - symbolic[1].set_color(self.circum_color) - - geometric = TexMobject("\\frac{d \\quad}{dR}=") - VGroup(*geometric[2:4]).set_color(self.dR_color) - radius = geometric[0].get_height() - area_circle = Circle( - stroke_width = 0, - fill_color = self.area_color, - fill_opacity = 0.5, - radius = radius - ) - area_circle.next_to(geometric[0], buff = SMALL_BUFF) - circum_circle = Circle( - color = self.circum_color, - radius = radius - ) - circum_circle.next_to(geometric, RIGHT) - geometric.add(area_circle, circum_circle) - self.derivative_terms = VGroup(symbolic, geometric) - self.derivative_terms.arrange( - DOWN, buff = LARGE_BUFF, aligned_edge = LEFT - ) - self.derivative_terms.next_to(ORIGIN, LEFT, buff = LARGE_BUFF) - - self.play( - Write(self.derivative_terms), - self.pi_creature.change_mode, "hooray" - ) - self.wait() - - def add_integral_terms(self): - symbolic = TexMobject( - "\\int_0^R", "2\\pi r", "\\cdot", "dr", "=", "\\pi R^2" - ) - symbolic.set_color_by_tex("2\\pi r", self.circum_color) - symbolic.set_color_by_tex("dr", self.dR_color) - symbolic.set_color_by_tex("\\pi R^2", self.area_color) - - geometric = symbolic.copy() - area_circle = Circle( - radius = geometric[-1].get_width()/2, - stroke_width = 0, - fill_color = self.area_color, - fill_opacity = 0.5 - ) - area_circle.move_to(geometric[-1]) - circum_circle = Circle( - radius = geometric[1].get_width()/2, - color = self.circum_color - ) - circum_circle.move_to(geometric[1]) - geometric.submobjects[1] = circum_circle - geometric.submobjects[-1] = area_circle - - self.integral_terms = VGroup(symbolic, geometric) - self.integral_terms.arrange( - DOWN, - buff = LARGE_BUFF, - aligned_edge = LEFT - ) - self.integral_terms.next_to(ORIGIN, RIGHT, buff = LARGE_BUFF) - - self.play(Write(self.integral_terms)) - self.wait() - - def think_about_it(self): - for mode in "confused", "pondering", "surprised": - self.change_mode(mode) - self.wait() - - def bring_in_circle(self): - self.play( - FadeOut(self.derivative_terms[0]), - FadeOut(self.integral_terms[0]), - self.derivative_terms[1].to_corner, UP+LEFT, MED_LARGE_BUFF, - self.integral_terms[1].to_corner, UP+RIGHT, MED_LARGE_BUFF, - self.pi_creature.change_mode, "speaking" - ) - self.introduce_circle() - - def show_outer_ring(self): - self.increase_radius(numerical_dr = False) - self.foreground_group.add(self.nudge_line, self.nudge_arrow) - self.wait() - ring_copy = self.outer_ring.copy() - ring_copy.save_state() - self.unwrap_ring(ring_copy, to_edge = LEFT) - brace = Brace(ring_copy, UP) - brace.stretch_in_place(0.95, 0) - deriv = brace.get_text("$\\dfrac{dA}{dR}$") - VGroup(*deriv[:2]).set_color(self.outer_ring.get_color()) - VGroup(*deriv[-2:]).set_color(self.dR_color) - self.play( - GrowFromCenter(brace), - Write(deriv), - self.pi_creature.change_mode, "happy" - ) - self.to_fade = VGroup(deriv, brace) - self.to_restore = ring_copy - - def show_all_rings(self): - rings = VGroup(*[ - self.get_ring(radius = r, dR = self.dR) - for r in np.arange(0, self.radius, self.dR) - ]) - rings.set_color_by_gradient(BLUE_E, GREEN_E) - rings.save_state() - integrand = self.integral_terms[1][1] - for ring in rings: - Transform(ring, integrand).update(1) - - self.play( - ApplyMethod( - rings.restore, - lag_ratio = 0.5, - run_time = 5 - ), - Animation(self.foreground_group), - ) - - def emphasize_oposites(self): - self.play( - FadeOut(self.to_fade), - self.to_restore.restore, - Animation(self.foreground_group), - run_time = 2 - ) - arrow = DoubleArrow( - self.derivative_terms[1], - self.integral_terms[1], - ) - opposites = TextMobject("Opposites") - opposites.next_to(arrow, DOWN) - - self.play( - ShowCreation(arrow), - Write(opposites) - ) - self.wait() - -class NameTheFundamentalTheorem(TeacherStudentsScene): - def construct(self): - symbols = TexMobject( - "\\frac{d}{dx} \\int_0^x f(t)dt = f(x)", - ) - symbols.to_corner(UP+LEFT) - brace = Brace(symbols) - abstract = brace.get_text("Abstract version") - self.add(symbols) - self.play( - GrowFromCenter(brace), - Write(abstract), - *[ - ApplyMethod(pi.look_at, symbols) - for pi in self.get_pi_creatures() - ] - ) - self.change_student_modes("pondering", "confused", "erm") - self.random_blink() - self.teacher_says(""" - This is known as - the ``fundamental - theorem of calculus'' - """, width = 5, height = 5, target_mode = "hooray") - self.random_blink(3) - self.teacher_says(""" - We'll get here - in due time. - """) - self.change_student_modes(*["happy"]*3) - self.wait(2) - -class CalculusInANutshell(CircleScene): - CONFIG = { - "circle_corner" : ORIGIN, - "radius" : 3, - } - def construct(self): - self.clear() - self.morph_word() - self.show_remainder_of_series() - - def morph_word(self): - calculus = TextMobject("Calculus") - calculus.scale(1.5) - calculus.to_edge(UP) - dR = self.radius/float(len(calculus.split())) - rings = VGroup(*[ - self.get_ring(rad, 0.95*dR) - for rad in np.arange(0, self.radius, dR) - ]) - for ring in rings: - ring.add(ring.copy().rotate(np.pi)) - for mob in calculus, rings: - mob.set_color_by_gradient(BLUE, GREEN) - rings.set_stroke(width = 0) - - self.play(Write(calculus)) - self.wait() - self.play(Transform( - calculus, rings, - lag_ratio = 0.5, - run_time = 5 - )) - self.wait() - - def show_remainder_of_series(self): - series = VideoSeries() - first = series[0] - first.set_fill(YELLOW) - first.save_state() - first.center() - first.set_height(FRAME_Y_RADIUS*2) - first.set_fill(opacity = 0) - everything = VGroup(*self.get_mobjects()) - everything.generate_target() - everything.target.scale(series[1].get_height()/first.get_height()) - everything.target.shift(first.saved_state.get_center()) - everything.target.set_fill(opacity = 0.1) - - second = series[1] - brace = Brace(second) - derivatives = brace.get_text("Derivatives") - - self.play( - MoveToTarget(everything), - first.restore, - run_time = 2 - ) - self.play(FadeIn( - VGroup(*series[1:]), - lag_ratio = 0.5, - run_time = 2, - )) - self.wait() - self.play( - GrowFromCenter(brace), - Write(derivatives) - ) - self.wait() - -class Thumbnail(CircleScene): - CONFIG = { - "radius" : 2, - "circle_corner" : ORIGIN - } - def construct(self): - self.clear() - title = TextMobject("Essence of \\\\ calculus") - title.scale(2) - title.to_edge(UP) - - area_circle = Circle( - fill_color = BLUE, - fill_opacity = 0.5, - stroke_width = 0, - ) - circum_circle = Circle( - color = YELLOW - ) - - deriv_eq = TexMobject("\\frac{d \\quad}{dR} = ") - int_eq = TexMobject("\\int_0^R \\quad = ") - target_height = deriv_eq[0].get_height()*2 - area_circle.set_height(target_height) - circum_circle.set_height(target_height) - - area_circle.next_to(deriv_eq[0], buff = SMALL_BUFF) - circum_circle.next_to(deriv_eq) - deriv_eq.add(area_circle.copy(), circum_circle.copy()) - - area_circle.next_to(int_eq) - circum_circle.next_to(int_eq[-1], LEFT) - int_eq.add(area_circle, circum_circle) - - for mob in deriv_eq, int_eq: - mob.scale(1.5) - - arrow = TexMobject("\\Leftrightarrow").scale(2) - arrow.shift(DOWN) - deriv_eq.next_to(arrow, LEFT) - int_eq.next_to(arrow, RIGHT) - - self.add(title, arrow, deriv_eq, int_eq) - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/chapter0.py b/from_3b1b/old/eola/chapter0.py deleted file mode 100644 index 550db873..00000000 --- a/from_3b1b/old/eola/chapter0.py +++ /dev/null @@ -1,1001 +0,0 @@ -from manimlib.imports import * -from once_useful_constructs import * - -EXAMPLE_TRANFORM = [[0, 1], [-1, 1]] -TRANFORMED_VECTOR = [[1], [2]] - -def matrix_multiplication(): - return TexMobject(""" - \\left[ - \\begin{array}{cc} - a & b \\\\ - c & d - \\end{array} - \\right] - \\left[ - \\begin{array}{cc} - e & f \\\\ - g & h - \\end{array} - \\right] - = - \\left[ - \\begin{array}{cc} - ae + bg & af + bh \\\\ - ce + dg & cf + dh - \\end{array} - \\right] - """) - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject( - """ - ``There is hardly any theory which is more elementary - than linear algebra, in spite of the fact that generations - of professors and textbook writers have obscured its - simplicity by preposterous calculations with matrices.'' - """, - organize_left_to_right = False - ) - words.set_width(2*(FRAME_X_RADIUS-1)) - words.to_edge(UP) - for mob in words.submobjects[48:49+13]: - mob.set_color(GREEN) - author = TextMobject("-Jean Dieudonn\\'e") - author.set_color(YELLOW) - author.next_to(words, DOWN) - - self.play(FadeIn(words)) - self.wait(3) - self.play(Write(author, run_time = 5)) - self.wait() - -class VideoIcon(SVGMobject): - def __init__(self, **kwargs): - SVGMobject.__init__(self, "video_icon", **kwargs) - self.center() - self.set_width(FRAME_WIDTH/12.) - self.set_stroke(color = WHITE, width = 0) - self.set_fill(color = WHITE, opacity = 1) - - - -class UpcomingSeriesOfVidoes(Scene): - def construct(self): - icons = [VideoIcon() for x in range(10)] - colors = Color(BLUE_A).range_to(BLUE_D, len(icons)) - for icon, color in zip(icons, colors): - icon.set_fill(color, opacity = 1) - icons = VMobject(*icons) - icons.arrange(RIGHT) - icons.to_edge(LEFT) - icons.shift(UP) - icons = icons.split() - - def rate_func_creator(offset): - return lambda a : min(max(2*(a-offset), 0), 1) - self.play(*[ - FadeIn( - icon, - run_time = 5, - rate_func = rate_func_creator(offset) - ) - for icon, offset in zip(icons, np.linspace(0, 0.5, len(icons))) - ]) - self.wait() - - -class AboutLinearAlgebra(Scene): - def construct(self): - self.show_dependencies() - self.to_thought_bubble() - - def show_dependencies(self): - linalg = TextMobject("Linear Algebra") - subjects = list(map(TextMobject, [ - "Computer science", - "Physics", - "Electrical engineering", - "Mechanical engineering", - "Statistics", - "\\vdots" - ])) - prev = subjects[0] - for subject in subjects[1:]: - subject.next_to(prev, DOWN, aligned_edge = LEFT) - prev = subject - all_subs = VMobject(*subjects) - linalg.to_edge(LEFT) - all_subs.next_to(linalg, RIGHT, buff = 2) - arrows = VMobject(*[ - Arrow(linalg, sub) - for sub in subjects - ]) - - self.play(Write(linalg, run_time = 1)) - self.wait() - self.play( - ShowCreation(arrows, lag_ratio = 0.5), - FadeIn(all_subs), - run_time = 2 - ) - self.wait() - self.linalg = linalg - - def to_thought_bubble(self): - linalg = self.linalg - all_else = list(self.mobjects) - all_else.remove(linalg) - randy = Randolph() - randy.to_corner() - bubble = randy.get_bubble(width = 10) - new_linalg = bubble.position_mobject_inside(linalg.copy()) - q_marks = TextMobject("???").next_to(randy, UP) - - self.play(*list(map(FadeOut, all_else))) - self.remove(*all_else) - self.play( - Transform(linalg, new_linalg), - Write(bubble), - FadeIn(randy) - ) - self.wait() - - topics = [ - self.get_matrix_multiplication(), - self.get_determinant(), - self.get_cross_product(), - self.get_eigenvalue(), - ] - questions = [ - self.get_matrix_multiplication_question(), - self.get_cross_product_question(), - self.get_eigen_question(), - ] - for count, topic in enumerate(topics + questions): - bubble.position_mobject_inside(topic) - if count == len(topics): - self.play(FadeOut(linalg)) - self.play( - ApplyMethod(randy.change_mode, "confused"), - Write(q_marks, run_time = 1) - ) - linalg = VectorizedPoint(linalg.get_center()) - if count > len(topics): - self.remove(linalg) - self.play(FadeIn(topic)) - linalg = topic - else: - self.play(Transform(linalg, topic)) - - if count %3 == 0: - self.play(Blink(randy)) - self.wait() - else: - self.wait(2) - - - def get_matrix_multiplication(self): - return matrix_multiplication() - - def get_determinant(self): - return TexMobject(""" - \\text{Det}\\left( - \\begin{array}{cc} - a & b \\\\ - c & d - \\end{array} - \\right) - = - ad - bc - """) - - def get_cross_product(self): - return TexMobject(""" - \\vec{\\textbf{v}} \\times \\textbf{w} = - \\text{Det}\\left( - \\begin{array}{ccc} - \\hat{\imath} & \\hat{\jmath} & \\hat{k} \\\\ - v_1 & v_2 & v_3 \\\\ - w_1 & w_2 & w_3 \\\\ - \\end{array} - \\right) - """) - - def get_eigenvalue(self): - result = TexMobject("\\text{Det}\\left(A - \\lambda I \\right) = 0") - result.submobjects[0][-5].set_color(YELLOW) - return result - - def get_matrix_multiplication_question(self): - why = TextMobject("Why?").set_color(BLUE) - mult = self.get_matrix_multiplication() - why.next_to(mult, UP) - result = VMobject(why, mult) - result.get_center = lambda : mult.get_center() - return result - - def get_cross_product_question(self): - cross = TexMobject("\\vec{v} \\times \\vec{w}") - left_right_arrow = DoubleArrow(Point(LEFT), Point(RIGHT)) - det = TextMobject("Det") - q_mark = TextMobject("?") - left_right_arrow.next_to(cross) - det.next_to(left_right_arrow) - q_mark.next_to(left_right_arrow, UP) - cross_question = VMobject(cross, left_right_arrow, q_mark, det) - cross_question.get_center = lambda : left_right_arrow.get_center() - return cross_question - - def get_eigen_question(self): - result = TextMobject( - "What the heck \\\\ does ``eigen'' mean?", - - ) - for mob in result.submobjects[-11:-6]: - mob.set_color(YELLOW) - return result - - -class NumericVsGeometric(Scene): - def construct(self): - self.setup() - self.specifics_concepts() - self.clear_way_for_geometric() - self.list_geometric_benefits() - - def setup(self): - numeric = TextMobject("Numeric operations") - geometric = TextMobject("Geometric intuition") - for mob in numeric, geometric: - mob.to_corner(UP+LEFT) - geometric.shift(FRAME_X_RADIUS*RIGHT) - hline = Line(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) - hline.next_to(numeric, DOWN) - hline.to_edge(LEFT, buff = 0) - vline = Line(FRAME_Y_RADIUS*UP, FRAME_Y_RADIUS*DOWN) - for mob in hline, vline: - mob.set_color(GREEN) - - self.play(ShowCreation(VMobject(hline, vline))) - digest_locals(self) - - def specifics_concepts(self): - matrix_vector_product = TexMobject(" ".join([ - matrix_to_tex_string(EXAMPLE_TRANFORM), - matrix_to_tex_string(TRANFORMED_VECTOR), - "&=", - matrix_to_tex_string([ - ["1 \\cdot 1 + 0 \\cdot 2"], - ["1 \\cdot 1 + (-1)\\cdot 2"] - ]), - "\\\\ &=", - matrix_to_tex_string([[1], [-1]]), - ])) - matrix_vector_product.set_width(FRAME_X_RADIUS-0.5) - matrix_vector_product.next_to(self.vline, LEFT) - - self.play( - Write(self.numeric), - FadeIn(matrix_vector_product), - run_time = 2 - ) - self.wait() - self.play(Write(self.geometric, run_time = 2)) - ### Paste in linear transformation - self.wait() - digest_locals(self) - - def clear_way_for_geometric(self): - new_line = Line(FRAME_Y_RADIUS*LEFT, FRAME_Y_RADIUS*RIGHT) - new_line.shift((FRAME_Y_RADIUS+1)*DOWN) - self.play( - Transform(self.vline, new_line), - Transform(self.hline, new_line), - ApplyMethod(self.numeric.shift, (FRAME_HEIGHT+1)*DOWN), - ApplyMethod( - self.matrix_vector_product.shift, - (FRAME_HEIGHT+1)*DOWN - ), - ApplyMethod(self.geometric.to_edge, LEFT) - ) - - def list_geometric_benefits(self): - follow_words = TextMobject("is helpful for \\dots") - follow_words.next_to(self.geometric) - #Ugly hack - diff = follow_words.submobjects[0].get_bottom()[1] - \ - self.geometric.submobjects[0].get_bottom()[1] - follow_words.shift(diff*DOWN) - randys = [ - Randolph(mode = "speaking"), - Randolph(mode = "surprised"), - Randolph(mode = "pondering") - ] - bulb = SVGMobject("light_bulb") - bulb.set_height(1) - bulb.set_color(YELLOW) - thoughts = [ - matrix_to_mobject(EXAMPLE_TRANFORM), - bulb, - TextMobject("So therefore...").scale(0.5) - ] - - self.play(Write(follow_words, run_time = 1.5)) - curr_randy = None - for randy, thought in zip(randys, thoughts): - randy.shift(DOWN) - thought.next_to(randy, UP+RIGHT, buff = 0) - if curr_randy: - self.play( - Transform(curr_randy, randy), - Transform(curr_thought, thought) - ) - else: - self.play( - FadeIn(randy), - Write(thought, run_time = 1) - ) - curr_randy = randy - curr_thought = thought - self.wait(1.5) - - -class ExampleTransformation(LinearTransformationScene): - def construct(self): - self.setup() - self.add_vector(np.array(TRANFORMED_VECTOR).flatten()) - self.apply_matrix(EXAMPLE_TRANFORM) - self.wait() - - -class NumericToComputations(Scene): - def construct(self): - top = TextMobject("Numeric understanding") - arrow = Arrow(UP, DOWN) - bottom = TextMobject("Actual computations") - top.next_to(arrow, UP) - bottom.next_to(arrow, DOWN) - - self.add(top) - self.play(ShowCreation(arrow)) - self.play(FadeIn(bottom)) - self.wait() - - - -class LinAlgPyramid(Scene): - def construct(self): - rects = self.get_rects() - words = self.place_words_in_rects([ - "Geometric understanding", - "Computations", - "Uses" - ], rects) - for word, rect in zip(words, rects): - self.play( - Write(word), - ShowCreation(rect), - run_time = 1 - ) - self.wait() - self.play(*[ - ApplyMethod(m.set_color, DARK_GREY) - for m in (words[0], rects[0]) - ]) - self.wait() - self.list_applications(rects[-1]) - - def get_rects(self): - height = 1 - rects = [ - Rectangle(height = height, width = width) - for width in (8, 5, 2) - ] - rects[0].shift(2*DOWN) - for i in 1, 2: - rects[i].next_to(rects[i-1], UP, buff = 0) - return rects - - def place_words_in_rects(self, words, rects): - result = [] - for word, rect in zip(words, rects): - tex_mob = TextMobject(word) - tex_mob.shift(rect.get_center()) - result.append(tex_mob) - return result - - def list_applications(self, top_mob): - subjects = [ - TextMobject(word).to_corner(UP+RIGHT) - for word in [ - "computer science", - "engineering", - "statistics", - "economics", - "pure math", - ] - ] - arrow = Arrow(top_mob, subjects[0].get_bottom(), color = RED) - - self.play(ShowCreation(arrow)) - curr_subject = None - for subject in subjects: - if curr_subject: - subject.shift(curr_subject.get_center()-subject.get_center()) - self.play(Transform(curr_subject, subject, run_time = 0.5)) - else: - curr_subject = subject - self.play(FadeIn(curr_subject, run_time = 0.5)) - self.wait() - - -class IntimidatingProf(Scene): - def construct(self): - randy = Randolph().to_corner() - morty = Mortimer().to_corner(DOWN+RIGHT) - morty.shift(3*LEFT) - morty_name1 = TextMobject("Professor") - morty_name2 = TextMobject("Coworker") - for name in morty_name1, morty_name2: - name.to_edge(RIGHT) - name.shift(2*UP) - arrow = Arrow(morty_name1.get_bottom(), morty) - speech_bubble = SpeechBubble(height = 3).flip() - speech_bubble.pin_to(morty) - speech_bubble.shift(RIGHT) - speech_bubble.write("And of course $B^{-1}AB$ will \\\\ also have positive eigenvalues...") - thought_bubble = ThoughtBubble(width = 6, height = 5) - thought_bubble.next_to(morty, UP) - thought_bubble.to_edge(RIGHT, buff = -1) - thought_bubble.make_green_screen() - q_marks = TextMobject("???") - q_marks.next_to(randy, UP) - randy_bubble = randy.get_bubble() - randy_bubble.add_content(matrix_multiplication()) - - self.add(randy, morty) - self.play( - FadeIn(morty_name1), - ShowCreation(arrow) - ) - self.play(Transform(morty_name1, morty_name2)) - self.wait() - self.play(FadeOut(morty_name1), FadeOut(arrow)) - self.play( - FadeIn(speech_bubble), - ApplyMethod(morty.change_mode, "speaking") - ) - self.play(FadeIn(thought_bubble)) - self.wait() - self.play( - ApplyMethod(randy.change_mode, "confused"), - Write(q_marks, run_time = 1) - ) - self.play(FadeOut(VMobject(speech_bubble, thought_bubble))) - self.play(FadeIn(randy_bubble)) - self.wait() - - -class ThoughtBubbleTransformation(LinearTransformationScene): - def construct(self): - self.setup() - rotation = rotation_about_z(np.pi/3) - self.apply_matrix( - np.linalg.inv(rotation), - path_arc = -np.pi/3, - ) - self.apply_matrix(EXAMPLE_TRANFORM) - self.apply_matrix( - rotation, - path_arc = np.pi/3, - ) - self.wait() - - -class SineApproximations(Scene): - def construct(self): - series = self.get_series() - one_approx = self.get_approx_series("1", 1) - one_approx.set_color(YELLOW) - pi_sixts_approx = self.get_approx_series("\\pi/6", np.pi/6) - pi_sixts_approx.set_color(RED) - words = TextMobject("(How calculators compute sine)") - words.set_color(GREEN) - - series.to_edge(UP) - one_approx.next_to(series, DOWN, buff = 1.5) - pi_sixts_approx.next_to(one_approx, DOWN, buff = 1.5) - - self.play(Write(series)) - self.wait() - self.play(FadeIn(words)) - self.wait(2) - self.play(FadeOut(words)) - self.remove(words) - self.wait() - self.play(Write(one_approx)) - self.play(Write(pi_sixts_approx)) - self.wait() - - def get_series(self): - return TexMobject(""" - \\sin(x) = x - \\dfrac{x^3}{3!} + \\dfrac{x^5}{5!} - + \\cdots + (-1)^n \\dfrac{x^{2n+1}}{(2n+1)!} + \\cdots - """) - - def get_approx_series(self, val_str, val): - #Default to 3 terms - approximation = val - (val**3)/6. + (val**5)/120. - return TexMobject(""" - \\sin(%s) \\approx - %s - \\dfrac{(%s)^3}{3!} + \\dfrac{(%s)^5}{5!} \\approx - %.04f - """%(val_str, val_str, val_str, val_str, approximation)) - - -class LooseConnectionToTriangles(Scene): - def construct(self): - sine = TexMobject("\\sin(x)") - triangle = Polygon(ORIGIN, 2*RIGHT, 2*RIGHT+UP) - arrow = DoubleArrow(LEFT, RIGHT) - sine.next_to(arrow, LEFT) - triangle.next_to(arrow, RIGHT) - - q_mark = TextMobject("?").scale(1.5) - q_mark.next_to(arrow, UP) - - self.add(sine) - self.play(ShowCreation(arrow)) - self.play(ShowCreation(triangle)) - self.play(Write(q_mark)) - self.wait() - - -class PhysicsExample(Scene): - def construct(self): - title = TextMobject("Physics") - title.to_corner(UP+LEFT) - parabola = FunctionGraph( - lambda x : (3-x)*(3+x)/4, - x_min = -4, - x_max = 4 - ) - - self.play(Write(title)) - self.projectile(parabola) - self.velocity_vector(parabola) - self.approximate_sine() - - def projectile(self, parabola): - dot = Dot(radius = 0.15) - kwargs = { - "run_time" : 3, - "rate_func" : None - } - self.play( - MoveAlongPath(dot, parabola.copy(), **kwargs), - ShowCreation(parabola, **kwargs) - ) - self.wait() - - - def velocity_vector(self, parabola): - alpha = 0.7 - d_alpha = 0.01 - vector_length = 3 - - p1 = parabola.point_from_proportion(alpha) - p2 = parabola.point_from_proportion(alpha + d_alpha) - vector = vector_length*(p2-p1)/get_norm(p2-p1) - v_mob = Vector(vector, color = YELLOW) - vx = Vector(vector[0]*RIGHT, color = GREEN_B) - vy = Vector(vector[1]*UP, color = RED) - v_mob.shift(p1) - vx.shift(p1) - vy.shift(vx.get_end()) - - arc = Arc( - angle_of_vector(vector), - radius = vector_length / 4. - ) - arc.shift(p1) - theta = TexMobject("\\theta").scale(0.75) - theta.next_to(arc, RIGHT, buff = 0.1) - - v_label = TexMobject("\\vec{v}") - v_label.shift(p1 + RIGHT*vector[0]/4 + UP*vector[1]/2) - v_label.set_color(v_mob.get_color()) - vx_label = TexMobject("||\\vec{v}|| \\cos(\\theta)") - vx_label.next_to(vx, UP) - vx_label.set_color(vx.get_color()) - vy_label = TexMobject("||\\vec{v}|| \\sin(\\theta)") - vy_label.next_to(vy, RIGHT) - vy_label.set_color(vy.get_color()) - - for v in v_mob, vx, vy: - self.play( - ShowCreation(v) - ) - self.play( - ShowCreation(arc), - Write(theta, run_time = 1) - ) - for label in v_label, vx_label, vy_label: - self.play(Write(label, run_time = 1)) - self.wait() - - def approximate_sine(self): - approx = TexMobject("\\sin(\\theta) \\approx 0.7\\text{-ish}") - morty = Mortimer(mode = "speaking") - morty.flip() - morty.to_corner() - bubble = SpeechBubble(width = 4, height = 3) - bubble.set_fill(BLACK, opacity = 1) - bubble.pin_to(morty) - bubble.position_mobject_inside(approx) - - self.play( - FadeIn(morty), - ShowCreation(bubble), - Write(approx), - run_time = 2 - ) - self.wait() - - -class LinearAlgebraIntuitions(Scene): - def construct(self): - title = TextMobject("Preview of core visual intuitions") - title.to_edge(UP) - h_line = Line(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) - h_line.next_to(title, DOWN) - h_line.set_color(BLUE_E) - intuitions = [ - "Matrices transform space", - "Matrix multiplication corresponds to applying " + - "one transformation after another", - "The determinant gives the factor by which areas change", - ] - - self.play( - Write(title), - ShowCreation(h_line), - run_time = 2 - ) - - for count, intuition in enumerate(intuitions, 3): - intuition += " (details coming in chapter %d)"%count - mob = TextMobject(intuition) - mob.scale(0.7) - mob.next_to(h_line, DOWN) - self.play(FadeIn(mob)) - self.wait(4) - self.play(FadeOut(mob)) - self.remove(mob) - self.wait() - -class MatricesAre(Scene): - def construct(self): - matrix = matrix_to_mobject([[1, -1], [1, 2]]) - matrix.set_height(6) - arrow = Arrow(LEFT, RIGHT, stroke_width = 8, preserve_tip_size_when_scaling = False) - arrow.scale(2) - arrow.to_edge(RIGHT) - matrix.next_to(arrow, LEFT) - - self.play(Write(matrix, run_time = 1)) - self.play(ShowCreation(arrow)) - self.wait() - -class ExampleTransformationForIntuitionList(LinearTransformationScene): - def construct(self): - self.setup() - self.apply_matrix([[1, -1], [1, 2]]) - self.wait() - -class MatrixMultiplicationIs(Scene): - def construct(self): - matrix1 = matrix_to_mobject([[1, -1], [1, 2]]) - matrix1.set_color(BLUE) - matrix2 = matrix_to_mobject([[2, 1], [1, 2]]) - matrix2.set_color(GREEN) - for m in matrix1, matrix2: - m.set_height(3) - arrow = Arrow(LEFT, RIGHT, stroke_width = 6, preserve_tip_size_when_scaling = False) - arrow.scale(2) - arrow.to_edge(RIGHT) - matrix1.next_to(arrow, LEFT) - matrix2.next_to(matrix1, LEFT) - brace1 = Brace(matrix1, UP) - apply_first = TextMobject("Apply first").next_to(brace1, UP) - brace2 = Brace(matrix2, DOWN) - apply_second = TextMobject("Apply second").next_to(brace2, DOWN) - - self.play( - Write(matrix1), - ShowCreation(arrow), - GrowFromCenter(brace1), - Write(apply_first), - run_time = 1 - ) - self.wait() - self.play( - Write(matrix2), - GrowFromCenter(brace2), - Write(apply_second), - run_time = 1 - ) - self.wait() - -class ComposedTransformsForIntuitionList(LinearTransformationScene): - def construct(self): - self.setup() - self.apply_matrix([[1, -1], [1, 2]]) - self.wait() - self.apply_matrix([[2, 1], [1, 2]]) - self.wait() - -class DeterminantsAre(Scene): - def construct(self): - tex_mob = TexMobject(""" - \\text{Det}\\left(\\left[ - \\begin{array}{cc} - 1 & -1 \\\\ - 1 & 2 - \\end{array} - \\right]\\right) - """) - tex_mob.set_height(4) - arrow = Arrow(LEFT, RIGHT, stroke_width = 8, preserve_tip_size_when_scaling = False) - arrow.scale(2) - arrow.to_edge(RIGHT) - tex_mob.next_to(arrow, LEFT) - - self.play( - Write(tex_mob), - ShowCreation(arrow), - run_time = 1 - ) - -class TransformationForDeterminant(LinearTransformationScene): - def construct(self): - self.setup() - square = Square(side_length = 1) - square.shift(-square.get_corner(DOWN+LEFT)) - square.set_fill(YELLOW_A, 0.5) - self.add_transformable_mobject(square) - self.apply_matrix([[1, -1], [1, 2]]) - -class ProfessorsTry(Scene): - def construct(self): - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - morty.shift(3*LEFT) - speech_bubble = morty.get_bubble(SpeechBubble, height = 4, width = 8) - speech_bubble.shift(RIGHT) - words = TextMobject( - "It really is beautiful! I want you to \\\\" + \ - "see it the way I do...", - ) - speech_bubble.position_mobject_inside(words) - thought_bubble = ThoughtBubble(width = 4, height = 3.5) - thought_bubble.next_to(morty, UP) - thought_bubble.to_edge(RIGHT) - thought_bubble.make_green_screen() - randy = Randolph() - randy.scale(0.8) - randy.to_corner() - - self.add(randy, morty) - self.play( - ApplyMethod(morty.change_mode, "speaking"), - FadeIn(speech_bubble), - FadeIn(words) - ) - self.play(Blink(randy)) - self.play(FadeIn(thought_bubble)) - self.play(Blink(morty)) - - -class ExampleMatrixMultiplication(NumericalMatrixMultiplication): - CONFIG = { - "left_matrix" : [[-3, 1], [2, 5]], - "right_matrix" : [[5, 3], [7, -3]] - } - -class TableOfContents(Scene): - def construct(self): - title = TextMobject("Essence of Linear Algebra") - title.set_color(BLUE) - title.to_corner(UP+LEFT) - h_line = Line(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) - h_line.next_to(title, DOWN) - h_line.to_edge(LEFT, buff = 0) - chapters = VMobject(*list(map(TextMobject, [ - "Chapter 1: Vectors, what even are they?", - "Chapter 2: Linear combinations, span and bases", - "Chapter 3: Matrices as linear transformations", - "Chapter 4: Matrix multiplication as composition", - "Chapter 5: The determinant", - "Chapter 6: Inverse matrices, column space and null space", - "Chapter 7: Dot products and cross products", - "Chapter 8: Change of basis", - "Chapter 9: Eigenvectors and eigenvalues", - "Chapter 10: Abstract vector spaces", - ]))) - chapters.arrange(DOWN) - chapters.scale(0.7) - chapters.next_to(h_line, DOWN) - - self.play( - Write(title), - ShowCreation(h_line) - ) - for chapter in chapters.split(): - chapter.to_edge(LEFT, buff = 1) - self.play(FadeIn(chapter)) - self.wait(2) - - entry3 = chapters.split()[2] - added_words = TextMobject("(Personally, I'm most excited \\\\ to do this one)") - added_words.scale(0.5) - added_words.set_color(YELLOW) - added_words.next_to(h_line, DOWN) - added_words.to_edge(RIGHT) - arrow = Arrow(added_words.get_bottom(), entry3) - - self.play( - ApplyMethod(entry3.set_color, YELLOW), - ShowCreation(arrow), - Write(added_words), - run_time = 1 - ) - self.wait() - removeable = VMobject(added_words, arrow, h_line, title) - self.play(FadeOut(removeable)) - self.remove(removeable) - - self.series_of_videos(chapters) - - def series_of_videos(self, chapters): - icon = SVGMobject("video_icon") - icon.center() - icon.set_width(FRAME_WIDTH/12.) - icon.set_stroke(color = WHITE, width = 0) - icons = [icon.copy() for chapter in chapters.split()] - colors = Color(BLUE_A).range_to(BLUE_D, len(icons)) - for icon, color in zip(icons, colors): - icon.set_fill(color, opacity = 1) - icons = VMobject(*icons) - icons.arrange(RIGHT) - icons.to_edge(LEFT) - icons.shift(UP) - - randy = Randolph() - randy.to_corner() - bubble = randy.get_bubble() - new_icons = icons.copy().scale(0.2) - bubble.position_mobject_inside(new_icons) - - self.play(Transform( - chapters, icons, - path_arc = np.pi/2, - )) - self.clear() - self.add(icons) - self.play(FadeIn(randy)) - self.play(Blink(randy)) - self.wait() - self.play( - ShowCreation(bubble), - Transform(icons, new_icons) - ) - self.remove(icons) - bubble.make_green_screen() - self.wait() - - -class ResourceForTeachers(Scene): - def construct(self): - morty = Mortimer(mode = "speaking") - morty.to_corner(DOWN + RIGHT) - bubble = morty.get_bubble(SpeechBubble) - bubble.write("I'm assuming you \\\\ know linear algebra\\dots") - words = bubble.content - bubble.clear() - randys = VMobject(*[ - Randolph(color = c) - for c in (BLUE_D, BLUE_C, BLUE_E) - ]) - randys.arrange(RIGHT) - randys.scale(0.8) - randys.to_corner(DOWN+LEFT) - - self.add(randys, morty) - self.play(FadeIn(bubble), Write(words), run_time = 3) - for randy in np.array(randys.split())[[2,0,1]]: - self.play(Blink(randy)) - self.wait() - -class AboutPacing(Scene): - def construct(self): - words = TextMobject("About pacing...") - dots = words.split()[-3:] - words.remove(*dots) - self.play(FadeIn(words)) - self.play(Write(VMobject(*dots))) - self.wait() - -class DifferingBackgrounds(Scene): - def construct(self): - words = list(map(TextMobject, [ - "Just brushing up", - "Has yet to take the course", - "Supplementing course concurrently", - ])) - students = VMobject(*[ - Randolph(color = c) - for c in (BLUE_D, BLUE_C, BLUE_E) - ]) - modes = ["pondering", "speaking_looking_left", "sassy"] - students.arrange(RIGHT) - students.scale(0.8) - students.center().to_edge(DOWN) - - last_word, last_arrow = None, None - for word, student, mode in zip(words, students.split(), modes): - word.shift(2*UP) - arrow = Arrow(word, student) - if last_word: - word_anim = Transform(last_word, word) - arrow_anim = Transform(last_arrow, arrow) - else: - word_anim = Write(word, run_time = 1) - arrow_anim = ShowCreation(arrow) - last_word = word - last_arrow = arrow - self.play( - word_anim, arrow_anim, - ApplyMethod(student.change_mode, mode) - ) - self.play(Blink(student)) - self.wait() - self.wait() - - - -class PauseAndPonder(Scene): - def construct(self): - pause = TexMobject("=").rotate(np.pi/2) - pause.stretch(0.5, 1) - pause.set_height(1.5) - bubble = ThoughtBubble().set_height(2) - pause.shift(LEFT) - bubble.next_to(pause, RIGHT, buff = 1) - - self.play(FadeIn(pause)) - self.play(ShowCreation(bubble)) - self.wait() - - -class NextVideo(Scene): - def construct(self): - title = TextMobject("Next video: Vectors, what even are they?") - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - - - - - - - diff --git a/from_3b1b/old/eola/chapter1.py b/from_3b1b/old/eola/chapter1.py deleted file mode 100644 index 8f0b8efa..00000000 --- a/from_3b1b/old/eola/chapter1.py +++ /dev/null @@ -1,1279 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eola.chapter0 import UpcomingSeriesOfVidoes - -import random - - -def plane_wave_homotopy(x, y, z, t): - norm = get_norm([x, y]) - tau = interpolate(5, -5, t) + norm/FRAME_X_RADIUS - alpha = sigmoid(tau) - return [x, y + 0.5*np.sin(2*np.pi*alpha)-t*SMALL_BUFF/2, z] - -class Physicist(PiCreature): - CONFIG = { - "color" : PINK, - } - -class ComputerScientist(PiCreature): - CONFIG = { - "color" : PURPLE_E, - "flip_at_start" : True, - } - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject( - "``The introduction of numbers as \\\\ coordinates is an act of violence.''", - ) - words.to_edge(UP) - for mob in words.submobjects[27:27+11]: - mob.set_color(GREEN) - author = TextMobject("-Hermann Weyl") - author.set_color(YELLOW) - author.next_to(words, DOWN, buff = 0.5) - - self.play(FadeIn(words)) - self.wait(1) - self.play(Write(author, run_time = 4)) - self.wait() - - -class DifferentConceptions(Scene): - def construct(self): - physy = Physicist() - mathy = Mathematician(mode = "pondering") - compy = ComputerScientist() - creatures = [physy, compy, mathy] - physy.title = TextMobject("Physics student").to_corner(DOWN+LEFT) - compy.title = TextMobject("CS student").to_corner(DOWN+RIGHT) - mathy.title = TextMobject("Mathematician").to_edge(DOWN) - names = VMobject(physy.title, mathy.title, compy.title) - names.arrange(RIGHT, buff = 1) - names.to_corner(DOWN+LEFT) - for pi in creatures: - pi.next_to(pi.title, UP) - - vector, symbol, coordinates = self.intro_vector() - for pi in creatures: - self.play( - Write(pi.title), - FadeIn(pi), - run_time = 1 - ) - self.wait(2) - self.remove(symbol, coordinates) - self.physics_conception(creatures, vector) - self.cs_conception(creatures) - self.handle_mathy(creatures) - - def intro_vector(self): - plane = NumberPlane() - labels = VMobject(*plane.get_coordinate_labels()) - vector = Vector(RIGHT+2*UP, color = YELLOW) - coordinates = vector_coordinate_label(vector) - symbol = TexMobject("\\vec{\\textbf{v}}") - symbol.shift(0.5*(RIGHT+UP)) - - self.play(ShowCreation( - plane, - lag_ratio=1, - run_time = 3 - )) - self.play(ShowCreation( - vector, - )) - self.play( - Write(labels), - Write(coordinates), - Write(symbol) - ) - self.wait(2) - self.play( - FadeOut(plane), - FadeOut(labels), - ApplyMethod(vector.shift, 4*LEFT+UP), - ApplyMethod(coordinates.shift, 2.5*RIGHT+0.5*DOWN), - ApplyMethod(symbol.shift, 0.5*(UP+LEFT)) - ) - self.remove(plane, labels) - return vector, symbol, coordinates - - def physics_conception(self, creatures, original_vector): - self.fade_all_but(creatures, 0) - physy, compy, mathy = creatures - - vector = Vector(2*RIGHT) - vector.next_to(physy, UP+RIGHT) - brace = Brace(vector, DOWN) - length = TextMobject("Length") - length.next_to(brace, DOWN) - group = VMobject(vector, brace, length) - group.rotate_in_place(np.pi/6) - vector.get_center = lambda : vector.get_start() - - direction = TextMobject("Direction") - direction.next_to(vector, RIGHT) - direction.shift(UP) - - two_dimensional = TextMobject("Two-dimensional") - three_dimensional = TextMobject("Three-dimensional") - two_dimensional.to_corner(UP+RIGHT) - three_dimensional.to_corner(UP+RIGHT) - - random_vectors = VMobject(*[ - Vector( - random.uniform(-2, 2)*RIGHT + \ - random.uniform(-2, 2)*UP - ).shift( - random.uniform(0, 4)*RIGHT + \ - random.uniform(-1, 2)*UP - ).set_color(random_color()) - for x in range(5) - ]) - - self.play( - Transform(original_vector, vector), - ApplyMethod(physy.change_mode, "speaking") - ) - self.remove(original_vector) - self.add(vector ) - self.wait() - self.play( - GrowFromCenter(brace), - Write(length), - run_time = 1 - ) - self.wait() - self.remove(brace, length) - self.play( - Rotate(vector, np.pi/3, in_place = True), - Write(direction), - run_time = 1 - ) - for angle in -2*np.pi/3, np.pi/3: - self.play(Rotate( - vector, angle, - in_place = True, - run_time = 1 - )) - self.play(ApplyMethod(physy.change_mode, "plain")) - self.remove(direction) - for point in 2*UP, 4*RIGHT, ORIGIN: - self.play(ApplyMethod(vector.move_to, point)) - self.wait() - self.play( - Write(two_dimensional), - ApplyMethod(physy.change_mode, "pondering"), - ShowCreation(random_vectors, lag_ratio = 0.5), - run_time = 1 - ) - self.wait(2) - self.remove(random_vectors, vector) - self.play(Transform(two_dimensional, three_dimensional)) - self.wait(5) - self.remove(two_dimensional) - self.restore_creatures(creatures) - - def cs_conception(self, creatures): - self.fade_all_but(creatures, 1) - physy, compy, mathy = creatures - - title = TextMobject("Vectors $\\Leftrightarrow$ lists of numbers") - title.to_edge(UP) - - vectors = VMobject(*list(map(matrix_to_mobject, [ - [2, 1], - [5, 0, 0, -3], - [2.3, -7.1, 0.1], - ]))) - vectors.arrange(RIGHT, buff = 1) - vectors.to_edge(LEFT) - - self.play( - ApplyMethod(compy.change_mode, "sassy"), - Write(title, run_time = 1) - ) - self.play(Write(vectors)) - self.wait() - self.play(ApplyMethod(compy.change_mode, "pondering")) - self.house_example(vectors, title) - self.restore_creatures(creatures) - - - def house_example(self, starter_mobject, title): - house = SVGMobject("house") - house.set_stroke(width = 0) - house.set_fill(BLUE_C, opacity = 1) - house.set_height(3) - house.center() - square_footage_words = TextMobject("Square footage:") - price_words = TextMobject("Price: ") - square_footage = TexMobject("2{,}600\\text{ ft}^2") - price = TextMobject("\\$300{,}000") - - house.to_edge(LEFT).shift(UP) - square_footage_words.next_to(house, RIGHT) - square_footage_words.shift(0.5*UP) - square_footage_words.set_color(RED) - price_words.next_to(square_footage_words, DOWN, aligned_edge = LEFT) - price_words.set_color(GREEN) - square_footage.next_to(square_footage_words) - square_footage.set_color(RED) - price.next_to(price_words) - price.set_color(GREEN) - - vector = Matrix([square_footage.copy(), price.copy()]) - vector.next_to(house, RIGHT).shift(0.25*UP) - new_square_footage, new_price = vector.get_mob_matrix().flatten() - not_equals = TexMobject("\\ne") - not_equals.next_to(vector) - alt_vector = Matrix([ - TextMobject("300{,}000\\text{ ft}^2").set_color(RED), - TextMobject("\\$2{,}600").set_color(GREEN) - ]) - alt_vector.next_to(not_equals) - - brace = Brace(vector, RIGHT) - two_dimensional = TextMobject("2 dimensional") - two_dimensional.next_to(brace) - brackets = vector.get_brackets() - - self.play(Transform(starter_mobject, house)) - self.remove(starter_mobject) - self.add(house) - self.add(square_footage_words) - self.play(Write(square_footage, run_time = 2)) - self.add(price_words) - self.play(Write(price, run_time = 2)) - self.wait() - self.play( - FadeOut(square_footage_words), FadeOut(price_words), - Transform(square_footage, new_square_footage), - Transform(price, new_price), - Write(brackets), - run_time = 1 - ) - self.remove(square_footage_words, price_words) - self.wait() - self.play( - Write(not_equals), - Write(alt_vector), - run_time = 1 - ) - self.wait() - self.play(FadeOut(not_equals), FadeOut(alt_vector)) - self.remove(not_equals, alt_vector) - self.wait() - self.play( - GrowFromCenter(brace), - Write(two_dimensional), - run_time = 1 - ) - self.wait() - - everything = VMobject( - house, square_footage, price, brackets, brace, - two_dimensional, title - ) - self.play(ApplyMethod(everything.shift, FRAME_WIDTH*LEFT)) - self.remove(everything) - - - def handle_mathy(self, creatures): - self.fade_all_but(creatures, 2) - physy, compy, mathy = creatures - - v_color = YELLOW - w_color = BLUE - sum_color = GREEN - - v_arrow = Vector([1, 1]) - w_arrow = Vector([2, 1]) - w_arrow.shift(v_arrow.get_end()) - sum_arrow = Vector(w_arrow.get_end()) - arrows = VMobject(v_arrow, w_arrow, sum_arrow) - arrows.scale(0.7) - arrows.to_edge(LEFT, buff = 2) - - v_array = matrix_to_mobject([3, -5]) - w_array = matrix_to_mobject([2, 1]) - sum_array = matrix_to_mobject(["3+2", "-5+1"]) - arrays = VMobject( - v_array, TexMobject("+"), w_array, TexMobject("="), sum_array - ) - arrays.arrange(RIGHT) - arrays.scale(0.75) - arrays.to_edge(RIGHT).shift(UP) - - v_sym = TexMobject("\\vec{\\textbf{v}}") - w_sym = TexMobject("\\vec{\\textbf{w}}") - syms = VMobject(v_sym, TexMobject("+"), w_sym) - syms.arrange(RIGHT) - syms.center().shift(2*UP) - - statement = TextMobject("We'll ignore him \\\\ for now") - statement.set_color(PINK) - statement.set_width(arrays.get_width()) - statement.next_to(arrays, DOWN, buff = 1.5) - circle = Circle() - circle.shift(syms.get_bottom()) - - VMobject(v_arrow, v_array, v_sym).set_color(v_color) - VMobject(w_arrow, w_array, w_sym).set_color(w_color) - VMobject(sum_arrow, sum_array).set_color(sum_color) - - self.play( - Write(syms), Write(arrays), - ShowCreation(arrows), - ApplyMethod(mathy.change_mode, "pondering"), - run_time = 2 - ) - self.play(Blink(mathy)) - self.add_scaling(arrows, syms, arrays) - self.play(Write(statement)) - self.play(ApplyMethod(mathy.change_mode, "sad")) - self.wait() - self.play( - ShowCreation(circle), - ApplyMethod(mathy.change_mode, "plain") - ) - self.wait() - - - def add_scaling(self, arrows, syms, arrays): - s_arrows = VMobject( - TexMobject("2"), Vector([1, 1]).set_color(YELLOW), - TexMobject("="), Vector([2, 2]).set_color(WHITE) - ) - s_arrows.arrange(RIGHT) - s_arrows.scale(0.75) - s_arrows.next_to(arrows, DOWN) - - s_arrays = VMobject( - TexMobject("2"), - matrix_to_mobject([3, -5]).set_color(YELLOW), - TextMobject("="), - matrix_to_mobject(["2(3)", "2(-5)"]) - ) - s_arrays.arrange(RIGHT) - s_arrays.scale(0.75) - s_arrays.next_to(arrays, DOWN) - - s_syms = TexMobject(["2", "\\vec{\\textbf{v}}"]) - s_syms.split()[-1].set_color(YELLOW) - s_syms.next_to(syms, DOWN) - - self.play( - Write(s_arrows), Write(s_arrays), Write(s_syms), - run_time = 2 - ) - self.wait() - - - - def fade_all_but(self, creatures, index): - self.play(*[ - FadeOut(VMobject(pi, pi.title)) - for pi in creatures[:index] + creatures[index+1:] - ]) - - def restore_creatures(self, creatures): - self.play(*[ - ApplyFunction(lambda m : m.change_mode("plain").set_color(m.color), pi) - for pi in creatures - ] + [ - ApplyMethod(pi.title.set_fill, WHITE, 1.0) - for pi in creatures - ]) - - -class ThreeDVectorField(Scene): - pass - - -class HelpsToHaveOneThought(Scene): - def construct(self): - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - morty.look(DOWN+LEFT) - new_morty = morty.copy().change_mode("speaking") - new_morty.look(DOWN+LEFT) - - randys = VMobject(*[ - Randolph(color = color).scale(0.8) - for color in (BLUE_D, BLUE_C, BLUE_E) - ]) - randys.arrange(RIGHT) - randys.to_corner(DOWN+LEFT) - randy = randys.split()[1] - - speech_bubble = morty.get_bubble(SpeechBubble) - words = TextMobject("Think of some vector...") - speech_bubble.position_mobject_inside(words) - thought_bubble = randy.get_bubble() - arrow = Vector([2, 1]).scale(0.7) - or_word = TextMobject("or") - array = Matrix([2, 1]).scale(0.5) - q_mark = TextMobject("?") - thought = VMobject(arrow, or_word, array, q_mark) - thought.arrange(RIGHT, buff = 0.2) - thought_bubble.position_mobject_inside(thought) - thought_bubble.set_fill(BLACK, opacity = 1) - - - self.add(morty, randys) - self.play( - ShowCreation(speech_bubble), - Transform(morty, new_morty), - Write(words) - ) - self.wait(2) - self.play( - FadeOut(speech_bubble), - FadeOut(words), - ApplyMethod(randy.change_mode, "pondering"), - ShowCreation(thought_bubble), - Write(thought) - ) - self.wait(2) - - -class HowIWantYouToThinkAboutVectors(Scene): - def construct(self): - vector = Vector([-2, 3]) - plane = NumberPlane() - axis_labels = plane.get_axis_labels() - other_vectors = VMobject(*list(map(Vector, [ - [1, 2], [2, -1], [4, 0] - ]))) - colors = [GREEN_B, MAROON_B, PINK] - for v, color in zip(other_vectors.split(), colors): - v.set_color(color) - shift_val = 4*RIGHT+DOWN - - dot = Dot(radius = 0.1) - dot.set_color(RED) - tail_word = TextMobject("Tail") - tail_word.shift(0.5*DOWN+2.5*LEFT) - line = Line(tail_word, dot) - - self.play(ShowCreation(vector)) - self.wait(2) - self.play( - ShowCreation(plane, lag_ratio=0.5), - Animation(vector) - ) - self.play(Write(axis_labels, run_time = 1)) - self.wait() - self.play( - GrowFromCenter(dot), - ShowCreation(line), - Write(tail_word, run_time = 1) - ) - self.wait() - self.play( - FadeOut(tail_word), - ApplyMethod(VMobject(dot, line).scale, 0.01) - ) - self.remove(tail_word, line, dot) - self.wait() - - self.play(ApplyMethod( - vector.shift, shift_val, - path_arc = 3*np.pi/2, - run_time = 3 - )) - self.play(ApplyMethod( - vector.shift, -shift_val, - rate_func = rush_into, - run_time = 0.5 - )) - self.wait(3) - - self.play(ShowCreation( - other_vectors, - run_time = 3 - )) - self.wait(3) - - x_axis, y_axis = plane.get_axes().split() - x_label = axis_labels.split()[0] - x_axis = x_axis.copy() - x_label = x_label.copy() - everything = VMobject(*self.mobjects) - self.play( - FadeOut(everything), - Animation(x_axis), Animation(x_label) - ) - - -class ListsOfNumbersAddOn(Scene): - def construct(self): - arrays = VMobject(*list(map(matrix_to_mobject, [ - [-2, 3], [1, 2], [2, -1], [4, 0] - ]))) - arrays.arrange(buff = 0.4) - arrays.scale(2) - self.play(Write(arrays)) - self.wait(2) - - -class CoordinateSystemWalkthrough(VectorScene): - def construct(self): - self.introduce_coordinate_plane() - self.show_vector_coordinates() - self.coords_to_vector([3, -1]) - self.vector_to_coords([-2, -1.5], integer_labels = False) - - def introduce_coordinate_plane(self): - plane = NumberPlane() - x_axis, y_axis = plane.get_axes().copy().split() - x_label, y_label = plane.get_axis_labels().split() - number_line = NumberLine(tick_frequency = 1) - x_tick_marks = number_line.get_tick_marks() - y_tick_marks = x_tick_marks.copy().rotate(np.pi/2) - tick_marks = VMobject(x_tick_marks, y_tick_marks) - tick_marks.set_color(WHITE) - plane_lines = [m for m in plane.get_family() if isinstance(m, Line)] - origin_words = TextMobject("Origin") - origin_words.shift(2*UP+2*LEFT) - dot = Dot(radius = 0.1).set_color(RED) - line = Line(origin_words.get_bottom(), dot.get_corner(UP+LEFT)) - - unit_brace = Brace(Line(RIGHT, 2*RIGHT)) - one = TexMobject("1").next_to(unit_brace, DOWN) - - self.add(x_axis, x_label) - self.wait() - self.play(ShowCreation(y_axis)) - self.play(Write(y_label, run_time = 1)) - self.wait(2) - self.play( - Write(origin_words), - GrowFromCenter(dot), - ShowCreation(line), - run_time = 1 - ) - self.wait(2) - self.play( - FadeOut(VMobject(origin_words, dot, line)) - ) - self.remove(origin_words, dot, line) - self.wait() - self.play( - ShowCreation(tick_marks) - ) - self.play( - GrowFromCenter(unit_brace), - Write(one, run_time = 1) - ) - self.wait(2) - self.remove(unit_brace, one) - self.play( - *list(map(GrowFromCenter, plane_lines)) + [ - Animation(x_axis), Animation(y_axis) - ]) - self.wait() - self.play( - FadeOut(plane), - Animation(VMobject(x_axis, y_axis, tick_marks)) - ) - self.remove(plane) - self.add(tick_marks) - - def show_vector_coordinates(self): - starting_mobjects = list(self.mobjects) - - vector = Vector([-2, 3]) - x_line = Line(ORIGIN, -2*RIGHT) - y_line = Line(-2*RIGHT, -2*RIGHT+3*UP) - x_line.set_color(X_COLOR) - y_line.set_color(Y_COLOR) - - array = vector_coordinate_label(vector) - x_label, y_label = array.get_mob_matrix().flatten() - x_label_copy = x_label.copy() - x_label_copy.set_color(X_COLOR) - y_label_copy = y_label.copy() - y_label_copy.set_color(Y_COLOR) - - point = Dot(4*LEFT+2*UP) - point_word = TextMobject("(-4, 2) as \\\\ a point") - point_word.scale(0.7) - point_word.next_to(point, DOWN) - point.add(point_word) - - self.play(ShowCreation(vector)) - self.play(Write(array)) - self.wait(2) - self.play(ApplyMethod(x_label_copy.next_to, x_line, DOWN)) - self.play(ShowCreation(x_line)) - self.wait(2) - self.play(ApplyMethod(y_label_copy.next_to, y_line, LEFT)) - self.play(ShowCreation(y_line)) - self.wait(2) - self.play(FadeIn(point)) - self.wait() - self.play(ApplyFunction( - lambda m : m.scale_in_place(1.25).set_color(YELLOW), - array.get_brackets(), - rate_func = there_and_back - )) - self.wait() - self.play(FadeOut(point)) - self.remove(point) - self.wait() - self.clear() - self.add(*starting_mobjects) - -class LabeledThreeDVector(Scene): - pass - -class WriteZ(Scene): - def construct(self): - z = TexMobject("z").set_color(Z_COLOR) - z.set_height(4) - self.play(Write(z, run_time = 2)) - self.wait(3) - - -class Write3DVector(Scene): - def construct(self): - array = Matrix([2, 1, 3]).scale(2) - x, y, z = array.get_mob_matrix().flatten() - brackets = array.get_brackets() - x.set_color(X_COLOR) - y.set_color(Y_COLOR) - z.set_color(Z_COLOR) - - self.add(brackets) - for mob in x, y, z: - self.play(Write(mob), run_time = 2) - self.wait() - - -class VectorAddition(VectorScene): - def construct(self): - self.add_plane() - vects = self.define_addition() - # vects = map(Vector, [[1, 2], [3, -1], [4, 1]]) - self.ask_why(*vects) - self.answer_why(*vects) - - def define_addition(self): - v1 = self.add_vector([1, 2]) - v2 = self.add_vector([3, -1], color = MAROON_B) - l1 = self.label_vector(v1, "v") - l2 = self.label_vector(v2, "w") - self.wait() - self.play(ApplyMethod(v2.shift, v1.get_end())) - self.wait() - v_sum = self.add_vector(v2.get_end(), color = PINK) - sum_tex = "\\vec{\\textbf{v}} + \\vec{\\textbf{w}}" - self.label_vector(v_sum, sum_tex, rotate = True) - self.wait(3) - return v1, v2, v_sum - - def ask_why(self, v1, v2, v_sum): - why = TextMobject("Why?") - why_not_this = TextMobject("Why not \\\\ this?") - new_v2 = v2.copy().shift(-v2.get_start()) - new_v_sum = v_sum.copy() - alt_vect_sum = new_v2.get_end() - v1.get_end() - new_v_sum.shift(-new_v_sum.get_start()) - new_v_sum.rotate( - angle_of_vector(alt_vect_sum) - new_v_sum.get_angle() - ) - new_v_sum.scale(get_norm(alt_vect_sum)/new_v_sum.get_length()) - new_v_sum.shift(v1.get_end()) - new_v_sum.submobjects.reverse()#No idea why I have to do this - original_v_sum = v_sum.copy() - - why.next_to(v2, RIGHT) - why_not_this.next_to(new_v_sum, RIGHT) - why_not_this.shift(0.5*UP) - - self.play(Write(why, run_time = 1)) - self.wait(2) - self.play( - Transform(v2, new_v2), - Transform(v_sum, new_v_sum), - Transform(why, why_not_this) - ) - self.wait(2) - self.play( - FadeOut(why), - Transform(v_sum, original_v_sum) - ) - self.remove(why) - self.wait() - - def answer_why(self, v1, v2, v_sum): - randy = Randolph(color = PINK) - randy.shift(-randy.get_bottom()) - self.remove(v1, v2, v_sum) - for v in v1, v2, v_sum: - self.add(v) - self.show_ghost_movement(v) - self.remove(v) - self.add(v1, v2 ) - self.wait() - self.play(ApplyMethod(randy.scale, 0.3)) - self.play(ApplyMethod(randy.shift, v1.get_end())) - self.wait() - self.play(ApplyMethod(v2.shift, v1.get_end())) - self.play(ApplyMethod(randy.move_to, v2.get_end())) - self.wait() - self.remove(randy) - randy.move_to(ORIGIN) - self.play(FadeIn(v_sum)) - self.play(ApplyMethod(randy.shift, v_sum.get_end())) - self.wait() - - -class AddingNumbersOnNumberLine(Scene): - def construct(self): - number_line = NumberLine() - number_line.add_numbers() - two_vect = Vector([2, 0]) - five_vect = Vector([5, 0], color = MAROON_B) - seven_vect = Vector([7, 0], color = PINK) - five_vect.shift(two_vect.get_end()) - seven_vect.shift(0.5*DOWN) - vects = [two_vect, five_vect, seven_vect] - - two, five, seven = list(map(TexMobject, ["2", "5", "7"])) - two.next_to(two_vect, UP) - five.next_to(five_vect, UP) - seven.next_to(seven_vect, DOWN) - nums = [two, five, seven] - - sum_mob = TexMobject("2 + 5").shift(3*UP) - - self.play(ShowCreation(number_line)) - self.wait() - self.play(Write(sum_mob, run_time = 2)) - self.wait() - for vect, num in zip(vects, nums): - self.play( - ShowCreation(vect), - Write(num, run_time = 1) - ) - self.wait() - - -class VectorAdditionNumerically(VectorScene): - def construct(self): - plus = TexMobject("+") - equals = TexMobject("=") - randy = Randolph() - randy.set_height(1) - randy.shift(-randy.get_bottom()) - - axes = self.add_axes() - x_axis, y_axis = axes.split() - - v1 = self.add_vector([1, 2]) - coords1, x_line1, y_line1 = self.vector_to_coords(v1, clean_up = False) - self.play(ApplyFunction( - lambda m : m.next_to(y_axis, RIGHT).to_edge(UP), - coords1 - )) - plus.next_to(coords1, RIGHT) - - v2 = self.add_vector([3, -1], color = MAROON_B) - coords2, x_line2, y_line2 = self.vector_to_coords(v2, clean_up = False) - self.wait() - self.play( - ApplyMethod(coords2.next_to, plus, RIGHT), - Write(plus, run_time = 1), - *[ - ApplyMethod(mob.shift, v1.get_end()) - for mob in (v2, x_line2, y_line2) - ] - ) - equals.next_to(coords2, RIGHT) - self.wait() - - self.play(FadeIn(randy)) - for step in [RIGHT, 2*UP, 3*RIGHT, DOWN]: - self.play(ApplyMethod(randy.shift, step, run_time = 1.5)) - self.wait() - self.play(ApplyMethod(randy.shift, -randy.get_bottom())) - - self.play(ApplyMethod(x_line2.shift, 2*DOWN)) - self.play(ApplyMethod(y_line1.shift, 3*RIGHT)) - for step in [4*RIGHT, 2*UP, DOWN]: - self.play(ApplyMethod(randy.shift, step)) - self.play(FadeOut(randy)) - self.remove(randy) - one_brace = Brace(x_line1) - three_brace = Brace(x_line2) - one = TexMobject("1").next_to(one_brace, DOWN) - three = TexMobject("3").next_to(three_brace, DOWN) - self.play( - GrowFromCenter(one_brace), - GrowFromCenter(three_brace), - Write(one), - Write(three), - run_time = 1 - ) - self.wait() - - two_brace = Brace(y_line1, RIGHT) - two = TexMobject("2").next_to(two_brace, RIGHT) - new_y_line = Line(4*RIGHT, 4*RIGHT+UP, color = Y_COLOR) - two_minus_one_brace = Brace(new_y_line, RIGHT) - two_minus_one = TexMobject("2+(-1)").next_to(two_minus_one_brace, RIGHT) - self.play( - GrowFromCenter(two_brace), - Write(two, run_time = 1) - ) - self.wait() - self.play( - Transform(two_brace, two_minus_one_brace), - Transform(two, two_minus_one), - Transform(y_line1, new_y_line), - Transform(y_line2, new_y_line) - ) - self.wait() - self.add_vector(v2.get_end(), color = PINK ) - - sum_coords = Matrix(["1+3", "2+(-1)"]) - sum_coords.set_height(coords1.get_height()) - sum_coords.next_to(equals, RIGHT) - brackets = sum_coords.get_brackets() - x1, y1 = coords1.get_mob_matrix().flatten() - x2, y2 = coords2.get_mob_matrix().flatten() - sum_x, sum_y = sum_coords.get_mob_matrix().flatten() - sum_x_start = VMobject(x1, x2).copy() - sum_y_start = VMobject(y1, y2).copy() - self.play( - Write(brackets), - Write(equals), - Transform(sum_x_start, sum_x), - run_time = 1 - ) - self.play(Transform(sum_y_start, sum_y)) - self.wait(2) - - starters = [x1, y1, x2, y2, sum_x_start, sum_y_start] - variables = list(map(TexMobject, [ - "x_1", "y_1", "x_2", "y_2", "x_1+y_1", "x_2+y_2" - ])) - for i, (var, starter) in enumerate(zip(variables, starters)): - if i%2 == 0: - var.set_color(X_COLOR) - else: - var.set_color(Y_COLOR) - var.scale(VECTOR_LABEL_SCALE_FACTOR) - var.move_to(starter) - self.play( - Transform( - VMobject(*starters[:4]), - VMobject(*variables[:4]) - ), - FadeOut(sum_x_start), - FadeOut(sum_y_start) - ) - sum_x_end, sum_y_end = variables[-2:] - self.wait(2) - self.play( - Transform(VMobject(x1, x2).copy(), sum_x_end) - ) - self.play( - Transform(VMobject(y1, y2).copy(), sum_y_end) - ) - self.wait(3) - -class MultiplicationByANumberIntro(Scene): - def construct(self): - v = TexMobject("\\vec{\\textbf{v}}") - v.set_color(YELLOW) - nums = list(map(TexMobject, ["2", "\\dfrac{1}{3}", "-1.8"])) - for mob in [v] + nums: - mob.scale(1.5) - - self.play(Write(v, run_time = 1)) - last = None - for num in nums: - num.next_to(v, LEFT) - if last: - self.play(Transform(last, num)) - else: - self.play(FadeIn(num)) - last = num - self.wait() - -class ShowScalarMultiplication(VectorScene): - def construct(self): - plane = self.add_plane() - v = self.add_vector([3, 1]) - label = self.label_vector(v, "v", add_to_vector = False) - - self.scale_vector(v, 2, label) - self.scale_vector(v, 1./3, label, factor_tex = "\\dfrac{1}{3}") - self.scale_vector(v, -1.8, label) - self.remove(label) - self.describe_scalars(v, plane) - - - def scale_vector(self, v, factor, v_label, - v_name = "v", factor_tex = None): - starting_mobjects = list(self.mobjects) - - if factor_tex is None: - factor_tex = str(factor) - scaled_vector = self.add_vector( - factor*v.get_end(), animate = False - ) - self.remove(scaled_vector) - label_tex = "%s\\vec{\\textbf{%s}}"%(factor_tex, v_name) - label = self.label_vector( - scaled_vector, label_tex, animate = False, - add_to_vector = False - ) - self.remove(label) - factor_mob = TexMobject(factor_tex) - if factor_mob.get_height() > 1: - factor_mob.set_height(0.9) - if factor_mob.get_width() > 1: - factor_mob.set_width(0.9) - factor_mob.shift(1.5*RIGHT+2.5*UP) - - num_factor_parts = len(factor_mob.split()) - factor_mob_parts_in_label = label.split()[:num_factor_parts] - label_remainder_parts = label.split()[num_factor_parts:] - factor_in_label = VMobject(*factor_mob_parts_in_label) - label_remainder = VMobject(*label_remainder_parts) - - - self.play(Write(factor_mob, run_time = 1)) - self.wait() - self.play( - ApplyMethod(v.copy().set_color, DARK_GREY), - ApplyMethod(v_label.copy().set_color, DARK_GREY), - Transform(factor_mob, factor_in_label), - Transform(v.copy(), scaled_vector), - Transform(v_label.copy(), label_remainder), - ) - self.wait(2) - - self.clear() - self.add(*starting_mobjects) - - def describe_scalars(self, v, plane): - axes = plane.get_axes() - long_v = Vector(2*v.get_end()) - long_minus_v = Vector(-2*v.get_end()) - original_v = v.copy() - scaling_word = TextMobject("``Scaling''").to_corner(UP+LEFT) - scaling_word.shift(2*RIGHT) - scalars = VMobject(*list(map(TexMobject, [ - "2,", "\\dfrac{1}{3},", "-1.8,", "\\dots" - ]))) - scalars.arrange(RIGHT, buff = 0.4) - scalars.next_to(scaling_word, DOWN, aligned_edge = LEFT) - scalars_word = TextMobject("``Scalars''") - scalars_word.next_to(scalars, DOWN, aligned_edge = LEFT) - - self.remove(plane) - self.add(axes) - self.play( - Write(scaling_word), - Transform(v, long_v), - run_time = 1.5 - ) - self.play(Transform(v, long_minus_v, run_time = 3)) - self.play(Write(scalars)) - self.wait() - self.play(Write(scalars_word)) - self.play(Transform(v, original_v), run_time = 3) - self.wait(2) - -class ScalingNumerically(VectorScene): - def construct(self): - two_dot = TexMobject("2\\cdot") - equals = TexMobject("=") - self.add_axes() - v = self.add_vector([3, 1]) - v_coords, vx_line, vy_line = self.vector_to_coords(v, clean_up = False) - self.play(ApplyMethod(v_coords.to_edge, UP)) - two_dot.next_to(v_coords, LEFT) - equals.next_to(v_coords, RIGHT) - two_v = self.add_vector([6, 2], animate = False) - self.remove(two_v) - self.play( - Transform(v.copy(), two_v), - Write(two_dot, run_time = 1) - ) - two_v_coords, two_v_x_line, two_v_y_line = self.vector_to_coords( - two_v, clean_up = False - ) - self.play( - ApplyMethod(two_v_coords.next_to, equals, RIGHT), - Write(equals, run_time = 1) - ) - self.wait(2) - - x, y = v_coords.get_mob_matrix().flatten() - two_v_elems = two_v_coords.get_mob_matrix().flatten() - x_sym, y_sym = list(map(TexMobject, ["x", "y"])) - two_x_sym, two_y_sym = list(map(TexMobject, ["2x", "2y"])) - VMobject(x_sym, two_x_sym).set_color(X_COLOR) - VMobject(y_sym, two_y_sym).set_color(Y_COLOR) - syms = [x_sym, y_sym, two_x_sym, two_y_sym] - VMobject(*syms).scale(VECTOR_LABEL_SCALE_FACTOR) - for sym, num in zip(syms, [x, y] + list(two_v_elems)): - sym.move_to(num) - self.play( - Transform(x, x_sym), - Transform(y, y_sym), - FadeOut(VMobject(*two_v_elems)) - ) - self.wait() - self.play( - Transform( - VMobject(two_dot.copy(), x.copy()), - two_x_sym - ), - Transform( - VMobject(two_dot.copy(), y.copy() ), - two_y_sym - ) - ) - self.wait(2) - - - -class FollowingVideos(UpcomingSeriesOfVidoes): - def construct(self): - v_sum = VMobject( - Vector([1, 1], color = YELLOW), - Vector([3, 1], color = BLUE).shift(RIGHT+UP), - Vector([4, 2], color = GREEN), - ) - scalar_multiplication = VMobject( - TexMobject("2 \\cdot "), - Vector([1, 1]), - TexMobject("="), - Vector([2, 2], color = WHITE) - ) - scalar_multiplication.arrange(RIGHT) - both = VMobject(v_sum, scalar_multiplication) - both.arrange(RIGHT, buff = 1) - both.shift(2*DOWN) - self.add(both) - - UpcomingSeriesOfVidoes.construct(self) - last_video = self.mobjects[-1] - self.play(ApplyMethod(last_video.set_color, YELLOW)) - self.wait() - everything = VMobject(*self.mobjects) - everything.remove(last_video) - big_last_video = last_video.copy() - big_last_video.center() - big_last_video.set_height(2.5*FRAME_Y_RADIUS) - big_last_video.set_fill(opacity = 0) - self.play( - ApplyMethod(everything.shift, FRAME_WIDTH*LEFT), - Transform(last_video, big_last_video), - run_time = 2 - ) - - - -class ItDoesntMatterWhich(Scene): - def construct(self): - physy = Physicist() - compy = ComputerScientist() - physy.title = TextMobject("Physics student").to_corner(DOWN+LEFT) - compy.title = TextMobject("CS student").to_corner(DOWN+RIGHT) - for pi in physy, compy: - pi.next_to(pi.title, UP) - self.add(pi, pi.title) - compy_speech = compy.get_bubble(SpeechBubble) - physy_speech = physy.get_bubble(SpeechBubble) - arrow = Vector([2, 1]) - array = matrix_to_mobject([2, 1]) - goes_to = TexMobject("\\Rightarrow") - physy_statement = VMobject(arrow, goes_to, array) - physy_statement.arrange(RIGHT) - compy_statement = physy_statement.copy() - compy_statement.arrange(LEFT) - physy_speech.position_mobject_inside(physy_statement) - compy_speech.position_mobject_inside(compy_statement) - - new_arrow = Vector([2, 1]) - x_line = Line(ORIGIN, 2*RIGHT, color = X_COLOR) - y_line = Line(2*RIGHT, 2*RIGHT+UP, color = Y_COLOR) - x_mob = TexMobject("2").next_to(x_line, DOWN) - y_mob = TexMobject("1").next_to(y_line, RIGHT) - new_arrow.add(x_line, y_line, x_mob, y_mob) - back_and_forth = VMobject( - new_arrow, - TexMobject("\\Leftrightarrow"), - matrix_to_mobject([2, 1]) - ) - back_and_forth.arrange(LEFT).center() - - - self.wait() - self.play( - ApplyMethod(physy.change_mode, "speaking"), - ShowCreation(physy_speech), - Write(physy_statement), - run_time = 1 - ) - self.play(Blink(compy)) - self.play( - ApplyMethod(physy.change_mode, "sassy"), - ApplyMethod(compy.change_mode, "speaking"), - FadeOut(physy_speech), - ShowCreation(compy_speech), - Transform(physy_statement, compy_statement, path_arc = np.pi) - ) - self.wait(2) - self.play( - ApplyMethod(physy.change_mode, "pondering"), - ApplyMethod(compy.change_mode, "pondering"), - Transform(compy_speech, VectorizedPoint(compy_speech.get_tip())), - Transform(physy_statement, back_and_forth) - ) - self.wait() - - -class DataAnalyst(Scene): - def construct(self): - plane = NumberPlane() - ellipse = ParametricFunction( - lambda x : 2*np.cos(x)*(UP+RIGHT) + np.sin(x)*(UP+LEFT), - color = PINK, - t_max = 2*np.pi - ) - ellipse_points = [ - ellipse.point_from_proportion(x) - for x in np.arange(0, 1, 1./20) - ] - string_vects = [ - matrix_to_mobject(("%.02f %.02f"%tuple(ep[:2])).split()) - for ep in ellipse_points - ] - string_vects_matrix = Matrix( - np.array(string_vects).reshape((4, 5)) - ) - string_vects = string_vects_matrix.get_mob_matrix().flatten() - string_vects = VMobject(*string_vects) - - vects = VMobject(*list(map(Vector, ellipse_points))) - - self.play(Write(string_vects)) - self.wait(2) - self.play( - FadeIn(plane), - Transform(string_vects, vects) - ) - self.remove(string_vects) - self.add(vects) - self.wait() - self.play( - ApplyMethod(plane.fade, 0.7), - ApplyMethod(vects.set_color, DARK_GREY), - ShowCreation(ellipse) - ) - self.wait(3) - - -class ManipulateSpace(LinearTransformationScene): - CONFIG = { - "include_background_plane" : False, - "show_basis_vectors" : False, - } - - def construct(self): - matrix_rule = TexMobject(""" - \\left[ - \\begin{array}{c} - x \\\\ y - \\end{array} - \\right] - \\rightarrow - \\left[ - \\begin{array}{c} - 2x + y \\\\ y + 2x - \\end{array} - \\right] - """) - - self.setup() - pi_creature = PiCreature(color = PINK).scale(0.5) - pi_creature.shift(-pi_creature.get_corner(DOWN+LEFT)) - self.plane.prepare_for_nonlinear_transform() - - self.play(ShowCreation( - self.plane, - run_time = 2 - )) - self.play(FadeIn(pi_creature)) - self.play(Blink(pi_creature)) - self.plane.add(pi_creature) - self.play(Homotopy(plane_wave_homotopy, self.plane, run_time = 3)) - self.wait(2) - self.apply_matrix([[2, 1], [1, 2]]) - self.wait() - self.play( - FadeOut(self.plane), - Write(matrix_rule), - run_time = 2 - ) - self.wait() - -class CodingMathyAnimation(Scene): - pass - -class NextVideo(Scene): - def construct(self): - title = TextMobject("Next video: Linear combinations, span, and bases") - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/chapter10.py b/from_3b1b/old/eola/chapter10.py deleted file mode 100644 index 8b2c71f7..00000000 --- a/from_3b1b/old/eola/chapter10.py +++ /dev/null @@ -1,2299 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eola.chapter1 import plane_wave_homotopy -from from_3b1b.old.eola.chapter3 import ColumnsToBasisVectors -from from_3b1b.old.eola.chapter5 import get_det_text -from from_3b1b.old.eola.chapter9 import get_small_bubble - - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject( - "``Last time, I asked: `What does", - "mathematics", - """ mean to you?', and some people answered: `The - manipulation of numbers, the manipulation of structures.' - And if I had asked what""", - "music", - """means to you, would you have answered: `The - manipulation of notes?' '' """, - enforce_new_line_structure = False, - alignment = "", - ) - words.set_color_by_tex("mathematics", BLUE) - words.set_color_by_tex("music", BLUE) - words.set_width(FRAME_WIDTH - 2) - words.to_edge(UP) - author = TextMobject("-Serge Lang") - author.set_color(YELLOW) - author.next_to(words, DOWN, buff = 0.5) - - self.play(Write(words, run_time = 10)) - self.wait() - self.play(FadeIn(author)) - self.wait(3) - -class StudentsFindThisConfusing(TeacherStudentsScene): - def construct(self): - title = TextMobject("Eigenvectors and Eigenvalues") - title.to_edge(UP) - students = self.get_students() - - self.play( - Write(title), - *[ - ApplyMethod(pi.look_at, title) - for pi in self.get_pi_creatures() - ] - ) - self.play( - self.get_teacher().look_at, students[-1].eyes, - students[0].change_mode, "confused", - students[1].change_mode, "tired", - students[2].change_mode, "sassy", - ) - self.random_blink() - self.student_says( - "Why are we doing this?", - student_index = 0, - run_time = 2, - ) - question1 = students[0].bubble.content.copy() - self.student_says( - "What does this actually mean?", - student_index = 2, - added_anims = [ - question1.scale_in_place, 0.8, - question1.to_edge, LEFT, - question1.shift, DOWN, - ] - ) - question2 = students[2].bubble.content.copy() - question2.target = question2.copy() - question2.target.next_to( - question1, DOWN, - aligned_edge = LEFT, - buff = MED_SMALL_BUFF - ) - equation = TexMobject( - "\\det\\left( %s \\right)=0"%matrix_to_tex_string([ - ["a-\\lambda", "b"], - ["c", "d-\\lambda"], - ]) - ) - equation.set_color(YELLOW) - self.teacher_says( - equation, - added_anims = [MoveToTarget(question2)] - ) - self.change_student_modes(*["confused"]*3) - self.random_blink(3) - -class ShowComments(Scene): - pass #TODO - -class EigenThingsArentAllThatBad(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Eigen-things aren't \\\\ actually so bad", - target_mode = "hooray" - ) - self.change_student_modes( - "pondering", "pondering", "erm" - ) - self.random_blink(4) - -class ManyPrerequisites(Scene): - def construct(self): - title = TextMobject("Many prerequisites") - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - self.add(title) - self.play(ShowCreation(h_line)) - - rect = Rectangle(height = 9, width = 16, color = BLUE) - rect.set_width(FRAME_X_RADIUS-2) - rects = [rect]+[rect.copy() for i in range(3)] - words = [ - "Linear transformations", - "Determinants", - "Linear systems", - "Change of basis", - ] - for rect, word in zip(rects, words): - word_mob = TextMobject(word) - word_mob.next_to(rect, UP, buff = MED_SMALL_BUFF) - rect.add(word_mob) - - Matrix(np.array(rects).reshape((2, 2))) - rects = VGroup(*rects) - rects.set_height(FRAME_HEIGHT - 1.5) - rects.next_to(h_line, DOWN, buff = MED_SMALL_BUFF) - - self.play(Write(rects[0])) - self.wait() - self.play(*list(map(FadeIn, rects[1:]))) - self.wait() - -class ExampleTranformationScene(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[3, 0], [1, 2]] - } - def setup(self): - LinearTransformationScene.setup(self) - self.add_matrix() - - def add_matrix(self): - matrix = Matrix(self.t_matrix.T) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.next_to(ORIGIN, LEFT, buff = MED_SMALL_BUFF) - matrix.to_edge(UP) - matrix.rect = BackgroundRectangle(matrix) - matrix.add_to_back(matrix.rect) - self.add_foreground_mobject(matrix) - self.matrix = matrix - - def remove_matrix(self): - self.remove(self.matrix) - self.foreground_mobjects.remove(self.matrix) - -class IntroduceExampleTransformation(ExampleTranformationScene): - def construct(self): - self.remove_matrix() - i_coords = Matrix(self.t_matrix[0]) - j_coords = Matrix(self.t_matrix[1]) - - self.apply_transposed_matrix(self.t_matrix) - for coords, vect in (i_coords, self.i_hat), (j_coords, self.j_hat): - coords.set_color(vect.get_color()) - coords.scale(0.8) - coords.rect = BackgroundRectangle(coords) - coords.add_to_back(coords.rect) - coords.next_to(vect.get_end(), RIGHT) - self.play(Write(coords)) - self.wait() - i_coords_copy = i_coords.copy() - self.play(*[ - Transform(*pair) - for pair in [ - (i_coords_copy.rect, self.matrix.rect), - (i_coords_copy.get_brackets(), self.matrix.get_brackets()), - ( - i_coords_copy.get_entries(), - VGroup(*self.matrix.get_mob_matrix()[:,0]) - ) - ] - ]) - to_remove = self.get_mobjects_from_last_animation() - self.play(Transform( - j_coords.copy().get_entries(), - VGroup(*self.matrix.get_mob_matrix()[:,1]) - )) - to_remove += self.get_mobjects_from_last_animation() - self.wait() - self.remove(*to_remove) - self.add(self.matrix) - -class VectorKnockedOffSpan(ExampleTranformationScene): - def construct(self): - vector = Vector([2, 1]) - line = Line(vector.get_end()*-4, vector.get_end()*4, color = MAROON_B) - vector.scale(0.7) - top_words, off, span_label = all_words = TextMobject( - "\\centering Vector gets knocked", "\\\\ off", "Span" - ) - all_words.shift( - line.point_from_proportion(0.75) - \ - span_label.get_corner(DOWN+RIGHT) + \ - MED_SMALL_BUFF*LEFT - ) - for text in all_words: - text.add_to_back(BackgroundRectangle(text)) - - self.add_vector(vector) - self.wait() - self.play( - ShowCreation(line), - Write(span_label), - Animation(vector), - ) - self.add_foreground_mobject(span_label) - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.play(Animation(span_label.copy()), Write(all_words)) - self.wait() - -class VectorRemainsOnSpan(ExampleTranformationScene): - def construct(self): - vector = Vector([1, -1]) - v_end = vector.get_end() - line = Line(-4*v_end, 4*v_end, color = MAROON_B) - words = TextMobject("Vector remains on", "\\\\its own span") - words.next_to(ORIGIN, DOWN+LEFT) - for part in words: - part.add_to_back(BackgroundRectangle(part)) - - self.add_vector(vector) - self.play(ShowCreation(line), Animation(vector)) - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.play(Write(words)) - self.wait() - target_vectors = [ - vector.copy().scale(scalar) - for scalar in (2, -2, 1) - ] - for target, time in zip(target_vectors, [1, 2, 2]): - self.play(Transform(vector, target, run_time = time)) - self.wait() - -class IHatAsEigenVector(ExampleTranformationScene): - def construct(self): - self.set_color_first_column() - self.set_color_x_axis() - self.apply_transposed_matrix(self.t_matrix, path_arc = 0) - self.label_i_hat_landing_spot() - - def set_color_first_column(self): - faders = VGroup(self.plane, self.i_hat, self.j_hat) - faders.save_state() - column1 = VGroup(*self.matrix.get_mob_matrix()[:,0]) - - self.play(faders.fade, 0.7, Animation(self.matrix)) - self.play(column1.scale_in_place, 1.3, rate_func = there_and_back) - self.wait() - self.play(faders.restore, Animation(self.matrix)) - self.wait() - - def set_color_x_axis(self): - x_axis = self.plane.axes[0] - targets = [ - self.i_hat.copy().scale(val) - for val in (-FRAME_X_RADIUS, FRAME_X_RADIUS, 1) - ] - lines = [ - Line(v1.get_end(), v2.get_end(), color = YELLOW) - for v1, v2 in adjacent_pairs([self.i_hat]+targets) - ] - for target, line in zip(targets, lines): - self.play( - ShowCreation(line), - Transform(self.i_hat, target), - ) - self.wait() - self.remove(*lines) - x_axis.set_color(YELLOW) - - def label_i_hat_landing_spot(self): - array = Matrix(self.t_matrix[0]) - array.set_color(X_COLOR) - array.rect = BackgroundRectangle(array) - array.add_to_back(array.rect) - brace = Brace(self.i_hat, buff = 0) - brace.put_at_tip(array) - - self.play(GrowFromCenter(brace)) - matrix = self.matrix.copy() - self.play( - Transform(matrix.rect, array.rect), - Transform(matrix.get_brackets(), array.get_brackets()), - Transform( - VGroup(*matrix.get_mob_matrix()[:,0]), - array.get_entries() - ), - ) - self.wait() - -class AllXAxisVectorsAreEigenvectors(ExampleTranformationScene): - def construct(self): - vectors = VGroup(*[ - self.add_vector(u*x*RIGHT, animate = False) - for x in reversed(list(range(1, int(FRAME_X_RADIUS)+1))) - for u in [-1, 1] - ]) - vectors.set_color_by_gradient(YELLOW, X_COLOR) - self.play(ShowCreation(vectors)) - self.wait() - self.apply_transposed_matrix(self.t_matrix, path_arc = 0) - self.wait() - -class SneakierEigenVector(ExampleTranformationScene): - def construct(self): - coords = [-1, 1] - vector = Vector(coords) - array = Matrix(coords) - array.scale(0.7) - array.set_color(vector.get_color()) - array.add_to_back(BackgroundRectangle(array)) - array.target = array.copy() - array.next_to(vector.get_end(), LEFT) - array.target.next_to(2*vector.get_end(), LEFT) - two_times = TexMobject("2 \\cdot") - two_times.add_background_rectangle() - two_times.next_to(array.target, LEFT) - span_line = Line(-4*vector.get_end(), 4*vector.get_end()) - span_line.set_color(MAROON_B) - - self.matrix.shift(-2*self.matrix.get_center()[0]*RIGHT) - - self.add_vector(vector) - self.play(Write(array)) - self.play( - ShowCreation(span_line), - Animation(vector), - Animation(array), - ) - self.wait() - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [ - MoveToTarget(array), - Transform(VectorizedPoint(array.get_left()), two_times) - ], - path_arc = 0, - ) - self.wait() - -class FullSneakyEigenspace(ExampleTranformationScene): - def construct(self): - self.matrix.shift(-2*self.matrix.get_center()[0]*RIGHT) - vectors = VGroup(*[ - self.add_vector(u*x*(LEFT+UP), animate = False) - for x in reversed(np.arange(0.5, 5, 0.5)) - for u in [-1, 1] - ]) - vectors.set_color_by_gradient(MAROON_B, YELLOW) - words = TextMobject("Stretch by 2") - words.add_background_rectangle() - words.next_to(ORIGIN, DOWN+LEFT, buff = MED_SMALL_BUFF) - words.shift(MED_SMALL_BUFF*LEFT) - words.rotate(vectors[0].get_angle()) - words.start = words.copy() - words.start.scale(0.5) - words.start.set_fill(opacity = 0) - - self.play(ShowCreation(vectors)) - self.wait() - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [Transform(words.start, words)], - path_arc = 0 - ) - self.wait() - -class NameEigenvectorsAndEigenvalues(ExampleTranformationScene): - CONFIG = { - "show_basis_vectors" : False - } - def construct(self): - self.remove(self.matrix) - self.foreground_mobjects.remove(self.matrix) - x_vectors = VGroup(*[ - self.add_vector(u*x*RIGHT, animate = False) - for x in range(int(FRAME_X_RADIUS)+1, 0, -1) - for u in [-1, 1] - ]) - x_vectors.set_color_by_gradient(YELLOW, X_COLOR) - self.remove(x_vectors) - sneak_vectors = VGroup(*[ - self.add_vector(u*x*(LEFT+UP), animate = False) - for x in np.arange(int(FRAME_Y_RADIUS), 0, -0.5) - for u in [-1, 1] - ]) - sneak_vectors.set_color_by_gradient(MAROON_B, YELLOW) - self.remove(sneak_vectors) - - x_words = TextMobject("Stretched by 3") - sneak_words = TextMobject("Stretched by 2") - for words in x_words, sneak_words: - words.add_background_rectangle() - words.next_to(x_vectors, DOWN) - words.next_to(words.get_center(), LEFT, buff = 1.5) - eigen_word = TextMobject("Eigenvectors") - eigen_word.add_background_rectangle() - eigen_word.replace(words) - words.target = eigen_word - eigen_val_words = TextMobject( - "with eigenvalue ", - "%s"%words.get_tex_string()[-1] - ) - eigen_val_words.add_background_rectangle() - eigen_val_words.next_to(words, DOWN, aligned_edge = RIGHT) - words.eigen_val_words = eigen_val_words - x_words.eigen_val_words.set_color(X_COLOR) - sneak_words.eigen_val_words.set_color(YELLOW) - - VGroup( - sneak_words, - sneak_words.target, - sneak_words.eigen_val_words, - ).rotate(sneak_vectors[0].get_angle()) - - non_eigen = Vector([1, 1], color = PINK) - non_eigen_span = Line( - -FRAME_Y_RADIUS*non_eigen.get_end(), - FRAME_Y_RADIUS*non_eigen.get_end(), - color = RED - ) - non_eigen_words = TextMobject(""" - Knocked off - its own span - """) - non_eigen_words.add_background_rectangle() - non_eigen_words.scale(0.7) - non_eigen_words.next_to(non_eigen.get_end(), RIGHT) - non_eigen_span.fade() - - for vectors in x_vectors, sneak_vectors: - self.play(ShowCreation(vectors, run_time = 1)) - self.wait() - for words in x_words, sneak_words: - self.play(Write(words, run_time = 1.5)) - self.add_foreground_mobject(words) - self.wait() - self.play(ShowCreation(non_eigen)) - self.play( - ShowCreation(non_eigen_span), - Write(non_eigen_words), - Animation(non_eigen) - ) - self.add_vector(non_eigen, animate = False) - self.wait() - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [FadeOut(non_eigen_words)], - path_arc = 0, - ) - self.wait(2) - self.play(*list(map(FadeOut, [non_eigen, non_eigen_span]))) - self.play(*list(map(MoveToTarget, [x_words, sneak_words]))) - self.wait() - for words in x_words, sneak_words: - self.play(Write(words.eigen_val_words), run_time = 2) - self.wait() - -class CanEigenvaluesBeNegative(TeacherStudentsScene): - def construct(self): - self.student_says("Can eigenvalues be negative?") - self.random_blink() - self.teacher_says("But of course!", target_mode = "hooray") - self.random_blink() - -class EigenvalueNegativeOneHalf(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[0.5, -1], [-1, 0.5]], - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_WIDTH, - "secondary_line_ratio" : 0 - }, - "include_background_plane" : False - } - def construct(self): - matrix = Matrix(self.t_matrix.T) - matrix.add_to_back(BackgroundRectangle(matrix)) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.next_to(ORIGIN, LEFT) - matrix.to_edge(UP) - self.add_foreground_mobject(matrix) - - vector = self.add_vector([1, 1]) - words = TextMobject("Eigenvector with \\\\ eigenvalue $-\\frac{1}{2}$") - words.add_background_rectangle() - words.next_to(vector.get_end(), RIGHT) - span = Line( - -FRAME_Y_RADIUS*vector.get_end(), - FRAME_Y_RADIUS*vector.get_end(), - color = MAROON_B - ) - - self.play(Write(words)) - self.play( - ShowCreation(span), - Animation(vector), - Animation(words), - ) - self.wait() - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [FadeOut(words)] - ) - self.wait() - self.play( - Rotate(span, np.pi/12, rate_func = wiggle), - Animation(vector), - ) - self.wait() - -class ThreeDRotationTitle(Scene): - def construct(self): - title = TextMobject("3D Rotation") - title.scale(2) - self.play(Write(title)) - self.wait() - -class ThreeDShowRotation(Scene): - pass - -class ThreeDShowRotationWithEigenvector(Scene): - pass - -class EigenvectorToAxisOfRotation(Scene): - def construct(self): - words = [ - TextMobject("Eigenvector"), - TextMobject("Axis of rotation"), - ] - for word in words: - word.scale(2) - self.play(Write(words[0])) - self.wait() - self.play(Transform(*words)) - self.wait() - -class EigenvalueOne(Scene): - def construct(self): - text = TextMobject("Eigenvalue = $1$") - text.set_color(MAROON_B) - self.play(Write(text)) - self.wait() - -class ContrastMatrixUnderstandingWithEigenvalue(TeacherStudentsScene): - def construct(self): - axis_and_rotation = TextMobject( - "Rotate", "$30^\\circ$", "around", - "$%s$"%matrix_to_tex_string([2, 3, 1]) - ) - axis_and_rotation[1].set_color(BLUE) - axis_and_rotation[-1].set_color(MAROON_B) - - matrix = Matrix([ - [ - "\\cos(\\theta)\\cos(\\phi)", - "-\\sin(\\phi)", - "\\cos(\\theta)\\sin(\\phi)", - ], - [ - "\\sin(\\theta)\\cos(\\phi)", - "\\cos(\\theta)", - "\\sin(\\theta)\\sin(\\phi)", - ], - [ - "-\\sin(\\phi)", - "0", - "\\cos(\\phi)" - ] - ]) - matrix.scale(0.7) - for mob in axis_and_rotation, matrix: - mob.to_corner(UP+RIGHT) - - everyone = self.get_pi_creatures() - self.play( - Write(axis_and_rotation), - *it.chain(*list(zip( - [pi.change_mode for pi in everyone], - ["hooray"]*4, - [pi.look_at for pi in everyone], - [axis_and_rotation]*4, - ))), - run_time = 2 - ) - self.random_blink(2) - self.play( - Transform(axis_and_rotation, matrix), - *it.chain(*list(zip( - [pi.change_mode for pi in everyone], - ["confused"]*4, - [pi.look_at for pi in everyone], - [matrix]*4, - ))) - - ) - self.random_blink(3) - -class CommonPattern(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - This is a common pattern - in linear algebra. - """) - self.random_blink(2) - -class DeduceTransformationFromMatrix(ColumnsToBasisVectors): - def construct(self): - self.setup() - self.move_matrix_columns([[3, 0], [1, 2]]) - words = TextMobject(""" - This gives too much weight - to our coordinate system - """) - words.add_background_rectangle() - words.next_to(ORIGIN, DOWN+LEFT, buff = MED_SMALL_BUFF) - words.shift_onto_screen() - self.play(Write(words)) - self.wait() - -class WordsOnComputation(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "I won't cover the full\\\\", - "details of computation...", - target_mode = "guilty" - ) - self.change_student_modes("angry", "sassy", "angry") - self.random_blink() - self.teacher_says( - "...but I'll hit the \\\\", - "important parts" - ) - self.change_student_modes(*["happy"]*3) - self.random_blink(3) - -class SymbolicEigenvectors(Scene): - def construct(self): - self.introduce_terms() - self.contrast_multiplication_types() - self.rewrite_righthand_side() - self.reveal_as_linear_system() - self.bring_in_determinant() - - def introduce_terms(self): - self.expression = TexMobject( - "A", "\\vec{\\textbf{v}}", "=", - "\\lambda", "\\vec{\\textbf{v}}" - ) - self.expression.scale(1.5) - self.expression.shift(UP+2*LEFT) - A, v1, equals, lamb, v2 = self.expression - vs = VGroup(v1, v2) - vs.set_color(YELLOW) - lamb.set_color(MAROON_B) - - A_brace = Brace(A, UP, buff = 0) - A_text = TextMobject("Transformation \\\\ matrix") - A_text.next_to(A_brace, UP) - - lamb_brace = Brace(lamb, UP, buff = 0) - lamb_text = TextMobject("Eigenvalue") - lamb_text.set_color(lamb.get_color()) - lamb_text.next_to(lamb_brace, UP, aligned_edge = LEFT) - - v_text = TextMobject("Eigenvector") - v_text.set_color(vs.get_color()) - v_text.next_to(vs, DOWN, buff = 1.5*LARGE_BUFF) - v_arrows = VGroup(*[ - Arrow(v_text.get_top(), v.get_bottom()) - for v in vs - ]) - - self.play(Write(self.expression)) - self.wait() - self.play( - GrowFromCenter(A_brace), - Write(A_text) - ) - self.wait() - self.play( - Write(v_text), - ShowCreation(v_arrows) - ) - self.wait() - self.play( - GrowFromCenter(lamb_brace), - Write(lamb_text) - ) - self.wait(2) - self.play(*list(map(FadeOut, [ - A_brace, A_text, - lamb_brace, lamb_text, - v_text, v_arrows - ]))) - - def contrast_multiplication_types(self): - A, v1, equals, lamb, v2 = self.expression - - left_group = VGroup(A, v1) - left_group.brace = Brace(left_group, UP) - left_group.text = left_group.brace.get_text("Matrix-vector multiplication") - - right_group = VGroup(lamb, v2) - right_group.brace = Brace(right_group, DOWN) - right_group.text = right_group.brace.get_text("Scalar multiplication") - right_group.text.set_color(lamb.get_color()) - - for group in left_group, right_group: - self.play( - GrowFromCenter(group.brace), - Write(group.text, run_time = 2) - ) - self.play(group.scale_in_place, 1.2, rate_func = there_and_back) - self.wait() - - morty = Mortimer().to_edge(DOWN) - morty.change_mode("speaking") - bubble = morty.get_bubble(SpeechBubble, width = 5, direction = LEFT) - VGroup(morty, bubble).to_edge(RIGHT) - solve_text = TextMobject( - "Solve for \\\\", - "$\\lambda$", "and", "$\\vec{\\textbf{v}}$" - ) - solve_text.set_color_by_tex("$\\lambda$", lamb.get_color()) - solve_text.set_color_by_tex("$\\vec{\\textbf{v}}$", v1.get_color()) - bubble.add_content(solve_text) - - self.play( - FadeIn(morty), - FadeIn(bubble), - Write(solve_text) - ) - self.play(Blink(morty)) - self.wait(2) - bubble.write("Fix different", "\\\\ multiplication", "types") - self.play( - Transform(solve_text, bubble.content), - morty.change_mode, "sassy" - ) - self.play(Blink(morty)) - self.wait() - self.play(*list(map(FadeOut, [ - left_group.brace, left_group.text, - right_group.brace, right_group.text, - morty, bubble, solve_text - ]))) - - def rewrite_righthand_side(self): - A, v1, equals, lamb, v2 = self.expression - - lamb_copy = lamb.copy() - scaling_by = VGroup( - TextMobject("Scaling by "), lamb_copy - ) - scaling_by.arrange() - arrow = TexMobject("\\Updownarrow") - matrix_multiplication = TextMobject( - "Matrix multiplication by" - ) - matrix = Matrix(np.identity(3, dtype = int)) - - corner_group = VGroup( - scaling_by, arrow, matrix_multiplication, matrix - ) - corner_group.arrange(DOWN) - corner_group.to_corner(UP+RIGHT) - - q_marks = VGroup(*[ - TexMobject("?").replace(entry) - for entry in matrix.get_entries() - ]) - q_marks.set_color_by_gradient(X_COLOR, Y_COLOR, Z_COLOR) - diag_entries = VGroup(*[ - matrix.get_mob_matrix()[i,i] - for i in range(3) - ]) - diag_entries.save_state() - for entry in diag_entries: - new_lamb = TexMobject("\\lambda") - new_lamb.move_to(entry) - new_lamb.set_color(lamb.get_color()) - Transform(entry, new_lamb).update(1) - new_lamb = lamb.copy() - new_lamb.next_to(matrix, LEFT) - id_brace = Brace(matrix) - id_text = TexMobject("I").scale(1.5) - id_text.next_to(id_brace, DOWN) - - self.play( - Transform(lamb.copy(), lamb_copy), - Write(scaling_by) - ) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(scaling_by) - self.play(Write(VGroup( - matrix_multiplication, - arrow, - matrix.get_brackets(), - q_marks, - ), run_time = 2 - )) - self.wait() - self.play(Transform( - q_marks, matrix.get_entries(), - lag_ratio = 0.5, - run_time = 2 - )) - self.remove(q_marks) - self.add(*matrix.get_entries()) - self.wait() - self.play( - Transform(diag_entries.copy(), new_lamb), - diag_entries.restore - ) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(new_lamb, diag_entries) - self.play( - GrowFromCenter(id_brace), - Write(id_text) - ) - self.wait() - id_text_copy = id_text.copy() - self.add(id_text_copy) - - l_paren, r_paren = parens = TexMobject("()").scale(1.5) - for mob in lamb, id_text, v2: - mob.target = mob.copy() - VGroup( - l_paren, lamb.target, id_text.target, - r_paren, v2.target - ).arrange().next_to(equals).shift(SMALL_BUFF*UP) - self.play( - Write(parens), - *list(map(MoveToTarget, [lamb, id_text, v2])) - ) - self.wait() - self.play(*list(map(FadeOut, [ - corner_group, id_brace, id_text_copy, new_lamb - ]))) - self.expression = VGroup( - A, v1, equals, - VGroup(l_paren, lamb, id_text, r_paren), - v2 - ) - - def reveal_as_linear_system(self): - A, v1, equals, lamb_group, v2 = self.expression - l_paren, lamb, I, r_paren = lamb_group - zero = TexMobject("\\vec{\\textbf{0}}") - zero.scale(1.3) - zero.next_to(equals, RIGHT) - zero.shift(SMALL_BUFF*UP/2) - minus = TexMobject("-").scale(1.5) - movers = A, v1, lamb_group, v2 - for mob in movers: - mob.target = mob.copy() - VGroup( - A.target, v1.target, minus, - lamb_group.target, v2.target - ).arrange().next_to(equals, LEFT) - self.play( - Write(zero), - Write(minus), - *list(map(MoveToTarget, movers)), - path_arc = np.pi/3 - ) - self.wait() - A.target.next_to(minus, LEFT) - l_paren.target = l_paren.copy() - l_paren.target.next_to(A.target, LEFT) - self.play( - MoveToTarget(A), - MoveToTarget(l_paren), - Transform(v1, v2, path_arc = np.pi/3) - ) - self.remove(v1) - self.wait() - - brace = Brace(VGroup(l_paren, r_paren)) - brace.text = TextMobject("This matrix looks \\\\ something like") - brace.text.next_to(brace, DOWN) - brace.text.to_edge(LEFT) - matrix = Matrix([ - ["3-\\lambda", "1", "4"], - ["1", "5-\\lambda", "9"], - ["2", "6", "5-\\lambda"], - ]) - matrix.scale(0.7) - VGroup( - matrix.get_brackets()[1], - *matrix.get_mob_matrix()[:,2] - ).shift(0.5*RIGHT) - matrix.next_to(brace.text, DOWN) - for entry in matrix.get_entries(): - if len(entry.get_tex_string()) > 1: - entry[-1].set_color(lamb.get_color()) - self.play( - GrowFromCenter(brace), - Write(brace.text), - Write(matrix) - ) - self.wait() - - vect_words = TextMobject( - "We want a nonzero solution for" - ) - v_copy = v2.copy().next_to(vect_words) - vect_words.add(v_copy) - vect_words.to_corner(UP+LEFT) - arrow = Arrow(vect_words.get_bottom(), v2.get_top()) - self.play( - Write(vect_words), - ShowCreation(arrow) - ) - self.wait() - - def bring_in_determinant(self): - randy = Randolph(mode = "speaking").to_edge(DOWN) - randy.flip() - randy.look_at(self.expression) - bubble = randy.get_bubble(SpeechBubble, direction = LEFT, width = 5) - words = TextMobject("We need") - equation = TexMobject( - "\\det(A-", "\\lambda", "I)", "=0" - ) - equation.set_color_by_tex("\\lambda", MAROON_B) - equation.next_to(words, DOWN) - words.add(equation) - bubble.add_content(words) - - self.play( - FadeIn(randy), - ShowCreation(bubble), - Write(words) - ) - self.play(Blink(randy)) - self.wait() - everything = self.get_mobjects() - equation_copy = equation.copy() - self.play( - FadeOut(VGroup(*everything)), - Animation(equation_copy) - ) - self.play( - equation_copy.center, - equation_copy.scale, 1.5 - ) - self.wait() - -class NonZeroSolutionsVisually(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[1, 1], [2, 2]], - "v_coords" : [2, -1] - } - def construct(self): - equation = TexMobject( - "(A-", "\\lambda", "I)", "\\vec{\\textbf{v}}", - "= \\vec{\\textbf{0}}" - ) - equation_matrix = VGroup(*equation[:3]) - equation.set_color_by_tex("\\lambda", MAROON_B) - equation.set_color_by_tex("\\vec{\\textbf{v}}", YELLOW) - equation.add_background_rectangle() - equation.next_to(ORIGIN, DOWN, buff = MED_SMALL_BUFF) - equation.to_edge(LEFT) - - det_equation = TexMobject( - "\\text{Squishification} \\Rightarrow", - "\\det", "(A-", "\\lambda", "I", ")", "=0" - ) - det_equation_matrix = VGroup(*det_equation[2:2+4]) - det_equation.set_color_by_tex("\\lambda", MAROON_B) - det_equation.next_to(equation, DOWN, buff = MED_SMALL_BUFF) - det_equation.to_edge(LEFT) - det_equation.add_background_rectangle() - - - self.add_foreground_mobject(equation) - v = self.add_vector(self.v_coords) - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.wait() - transform = Transform( - equation_matrix.copy(), det_equation_matrix - ) - self.play(Write(det_equation), transform) - self.wait() - -class TweakLambda(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[2, 1], [2, 3]], - "include_background_plane" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_WIDTH, - "secondary_line_ratio" : 1 - }, - } - def construct(self): - matrix = Matrix([ - ["2-0.00", "2"], - ["1", "3-0.00"], - ]) - matrix.add_to_back(BackgroundRectangle(matrix)) - matrix.next_to(ORIGIN, LEFT, buff = LARGE_BUFF) - matrix.to_edge(UP) - - self.lambda_vals = [] - for i in range(2): - entry = matrix.get_mob_matrix()[i,i] - place_holders = VGroup(*entry[2:]) - entry.remove(*place_holders) - place_holders.set_color(MAROON_B) - self.lambda_vals.append(place_holders) - - - brace = Brace(matrix) - brace_text = TexMobject("(A-", "\\lambda", "I)") - brace_text.set_color_by_tex("\\lambda", MAROON_B) - brace_text.next_to(brace, DOWN) - brace_text.add_background_rectangle() - - det_text = get_det_text(matrix) - equals = TexMobject("=").next_to(det_text) - det = DecimalNumber(np.linalg.det(self.t_matrix)) - det.set_color(YELLOW) - det.next_to(equals) - det.rect = BackgroundRectangle(det) - - self.det = det - - self.matrix = VGroup(matrix, brace, brace_text) - self.add_foreground_mobject( - self.matrix, *self.lambda_vals - ) - self.add_unit_square() - self.plane_mobjects = [ - self.plane, self.square, - ] - for mob in self.plane_mobjects: - mob.save_state() - self.apply_transposed_matrix(self.t_matrix) - self.play( - Write(det_text), - Write(equals), - ShowCreation(det.rect), - Write(det) - ) - self.matrix.add(det_text, equals, det.rect) - self.wait() - - self.range_lambda(0, 3, run_time = 5) - self.wait() - self.range_lambda(3, 0.5, run_time = 5) - self.wait() - self.range_lambda(0.5, 1.5, run_time = 3) - self.wait() - self.range_lambda(1.5, 1, run_time = 2) - self.wait() - - def get_t_matrix(self, lambda_val): - return self.t_matrix - lambda_val*np.identity(2) - - def range_lambda(self, start_val, end_val, run_time = 3): - alphas = np.linspace(0, 1, run_time/self.frame_duration) - matrix_transform = self.get_matrix_transformation( - self.get_t_matrix(end_val) - ) - transformations = [] - for mob in self.plane_mobjects: - mob.target = mob.copy().restore().apply_function(matrix_transform) - transformations.append(MoveToTarget(mob)) - transformations += [ - Transform( - self.i_hat, - Vector(matrix_transform(RIGHT), color = X_COLOR) - ), - Transform( - self.j_hat, - Vector(matrix_transform(UP), color = Y_COLOR) - ), - ] - for alpha in alphas: - self.clear() - val = interpolate(start_val, end_val, alpha) - new_t_matrix = self.get_t_matrix(val) - for transformation in transformations: - transformation.update(alpha) - self.add(transformation.mobject) - self.add(self.matrix) - - new_lambda_vals = [] - for lambda_val in self.lambda_vals: - new_lambda = DecimalNumber(val) - new_lambda.move_to(lambda_val, aligned_edge = LEFT) - new_lambda.set_color(lambda_val.get_color()) - new_lambda_vals.append(new_lambda) - self.lambda_vals = new_lambda_vals - self.add(*self.lambda_vals) - - new_det = DecimalNumber( - np.linalg.det([ - self.i_hat.get_end()[:2], - self.j_hat.get_end()[:2], - ]) - ) - new_det.move_to(self.det, aligned_edge = LEFT) - new_det.set_color(self.det.get_color()) - self.det = new_det - self.add(self.det) - - self.wait(self.frame_duration) - -class ShowEigenVectorAfterComputing(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[2, 1], [2, 3]], - "v_coords" : [2, -1], - "include_background_plane" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_WIDTH, - "secondary_line_ratio" : 1 - }, - } - def construct(self): - matrix = Matrix(self.t_matrix.T) - matrix.add_to_back(BackgroundRectangle(matrix)) - matrix.next_to(ORIGIN, RIGHT) - matrix.shift(self.v_coords[0]*RIGHT) - self.add_foreground_mobject(matrix) - - v_label = TexMobject( - "\\vec{\\textbf{v}}", - "=", - "1", - "\\vec{\\textbf{v}}", - ) - v_label.next_to(matrix, RIGHT) - v_label.set_color_by_tex("\\vec{\\textbf{v}}", YELLOW) - v_label.set_color_by_tex("1", MAROON_B) - v_label.add_background_rectangle() - - v = self.add_vector(self.v_coords) - eigenvector = TextMobject("Eigenvector") - eigenvector.add_background_rectangle() - eigenvector.next_to(ORIGIN, DOWN+RIGHT) - eigenvector.rotate(v.get_angle()) - self.play(Write(eigenvector)) - self.add_foreground_mobject(eigenvector) - - line = Line(v.get_end()*(-4), v.get_end()*4, color = MAROON_B) - self.play(Write(v_label)) - self.add_foreground_mobject(v_label) - self.play(ShowCreation(line), Animation(v)) - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.wait() - -class LineOfReasoning(Scene): - def construct(self): - v_tex = "\\vec{\\textbf{v}}" - expressions = VGroup(*it.starmap(TexMobject, [ - ("A", v_tex, "=", "\\lambda", v_tex), - ("A", v_tex, "-", "\\lambda", "I", v_tex, "=", "0"), - ("(", "A", "-", "\\lambda", "I)", v_tex, "=", "0"), - ("\\det(A-", "\\lambda", "I)", "=", "0") - ])) - expressions.arrange(DOWN, buff = LARGE_BUFF/2.) - for expression in expressions: - for i, expression_part in enumerate(expression.expression_parts): - if expression_part == "=": - equals = expression[i] - expression.shift(equals.get_center()[0]*LEFT) - break - expression.set_color_by_tex(v_tex, YELLOW) - expression.set_color_by_tex("\\lambda", MAROON_B) - self.play(FadeIn(expression)) - self.wait() - -class IfYouDidntKnowDeterminants(TeacherStudentsScene): - def construct(self): - expression = TexMobject("\\det(A-", "\\lambda", "I" ")=0") - expression.set_color_by_tex("\\lambda", MAROON_B) - expression.scale(1.3) - self.teacher_says(expression) - self.random_blink() - student = self.get_students()[0] - bubble = get_small_bubble(student) - bubble.write("Wait...why?") - self.play( - ShowCreation(bubble), - Write(bubble.content), - student.change_mode, "confused" - ) - self.random_blink(4) - -class RevisitExampleTransformation(ExampleTranformationScene): - def construct(self): - self.introduce_matrix() - - seeking_eigenvalue = TextMobject("Seeking eigenvalue") - seeking_eigenvalue.add_background_rectangle() - lamb = TexMobject("\\lambda") - lamb.set_color(MAROON_B) - words = VGroup(seeking_eigenvalue, lamb) - words.arrange() - words.next_to(self.matrix, DOWN, buff = LARGE_BUFF) - self.play(Write(words)) - self.wait() - self.play(*self.get_lambda_to_diag_movements(lamb.copy())) - self.add_foreground_mobject(*self.get_mobjects_from_last_animation()) - self.wait() - self.show_determinant(to_fade = words) - self.show_diagonally_altered_transform() - self.show_unaltered_transform() - - def introduce_matrix(self): - self.matrix.shift(2*LEFT) - for mob in self.plane, self.i_hat, self.j_hat: - mob.save_state() - self.remove_matrix() - i_coords = Matrix(self.t_matrix[0]) - j_coords = Matrix(self.t_matrix[1]) - - self.apply_transposed_matrix(self.t_matrix) - for coords, vect in (i_coords, self.i_hat), (j_coords, self.j_hat): - coords.set_color(vect.get_color()) - coords.scale(0.8) - coords.rect = BackgroundRectangle(coords) - coords.add_to_back(coords.rect) - coords.next_to( - vect.get_end(), - RIGHT+DOWN if coords is i_coords else RIGHT - ) - self.play( - Write(i_coords), - Write(j_coords), - ) - self.wait() - self.play(*[ - Transform(*pair) - for pair in [ - (i_coords.rect, self.matrix.rect), - (i_coords.get_brackets(), self.matrix.get_brackets()), - ( - i_coords.get_entries(), - VGroup(*self.matrix.get_mob_matrix()[:,0]) - ) - ] - ]) - to_remove = self.get_mobjects_from_last_animation() - self.play( - FadeOut(j_coords.get_brackets()), - FadeOut(j_coords.rect), - Transform( - j_coords.get_entries(), - VGroup(*self.matrix.get_mob_matrix()[:,1]) - ), - ) - to_remove += self.get_mobjects_from_last_animation() - self.wait() - self.remove(*to_remove) - self.add_foreground_mobject(self.matrix) - - def get_lambda_to_diag_movements(self, lamb): - three, two = [self.matrix.get_mob_matrix()[i, i] for i in range(2)] - l_bracket, r_bracket = self.matrix.get_brackets() - rect = self.matrix.rect - lamb_copy = lamb.copy() - movers = [rect, three, two, l_bracket, r_bracket, lamb, lamb_copy] - for mover in movers: - mover.target = mover.copy() - minus1, minus2 = [TexMobject("-") for x in range(2)] - new_three = VGroup(three.target, minus1, lamb.target) - new_three.arrange() - new_three.move_to(three) - new_two = VGroup(two.target, minus2, lamb_copy.target) - new_two.arrange() - new_two.move_to(two) - l_bracket.target.next_to(VGroup(new_three, new_two), LEFT) - r_bracket.target.next_to(VGroup(new_three, new_two), RIGHT) - rect.target = BackgroundRectangle( - VGroup(l_bracket.target, r_bracket.target) - ) - result = list(map(MoveToTarget, movers)) - result += list(map(Write, [minus1, minus2])) - result += list(map(Animation, [ - self.matrix.get_mob_matrix()[i, 1-i] - for i in range(2) - ])) - self.diag_entries = [ - VGroup(three, minus1, lamb), - VGroup(two, minus2, lamb_copy), - ] - return result - - def show_determinant(self, to_fade = None): - det_text = get_det_text(self.matrix) - equals = TexMobject("=").next_to(det_text) - three_minus_lamb, two_minus_lamb = diag_entries = [ - entry.copy() for entry in self.diag_entries - ] - one = self.matrix.get_mob_matrix()[0, 1].copy() - zero = self.matrix.get_mob_matrix()[1, 0].copy() - for entry in diag_entries + [one, zero]: - entry.target = entry.copy() - lp1, rp1, lp2, rp2 = parens = TexMobject("()()") - minus = TexMobject("-") - cdot = TexMobject("\\cdot") - VGroup( - lp1, three_minus_lamb.target, rp1, - lp2, two_minus_lamb.target, rp2, - minus, one.target, cdot, zero.target - ).arrange().next_to(equals) - - parens.add_background_rectangle() - new_rect = BackgroundRectangle(VGroup(minus, zero.target)) - - brace = Brace(new_rect, buff = 0) - brace_text = brace.get_text("Equals 0, so ", "ignore") - brace_text.add_background_rectangle() - - brace.target = Brace(parens) - brace_text.target = brace.target.get_text( - "Quadratic polynomial in ", "$\\lambda$" - ) - brace_text.target.set_color_by_tex("$\\lambda$", MAROON_B) - brace_text.target.add_background_rectangle() - - equals_0 = TexMobject("=0") - equals_0.next_to(parens, RIGHT) - equals_0.add_background_rectangle() - - final_brace = Brace(VGroup(parens, equals_0)) - final_text = TexMobject( - "\\lambda", "=2", "\\text{ or }", - "\\lambda", "=3" - ) - final_text.set_color_by_tex("\\lambda", MAROON_B) - final_text.next_to(final_brace, DOWN) - lambda_equals_two = VGroup(*final_text[:2]).copy() - lambda_equals_two.add_to_back(BackgroundRectangle(lambda_equals_two)) - final_text.add_background_rectangle() - - self.play( - Write(det_text), - Write(equals) - ) - self.wait() - self.play( - Write(parens), - MoveToTarget(three_minus_lamb), - MoveToTarget(two_minus_lamb), - run_time = 2 - ) - self.wait() - self.play( - FadeIn(new_rect), - MoveToTarget(one), - MoveToTarget(zero), - Write(minus), - Write(cdot), - run_time = 2 - ) - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait() - self.play(*it.chain( - list(map(MoveToTarget, [brace, brace_text])), - list(map(FadeOut, [one, zero, minus, cdot, new_rect])) - )) - self.wait() - self.play(Write(equals_0)) - self.wait() - self.play( - Transform(brace, final_brace), - Transform(brace_text, final_text) - ) - self.wait() - faders = [ - det_text, equals, parens, - three_minus_lamb, two_minus_lamb, - brace, brace_text, equals_0, - ] - if to_fade is not None: - faders.append(to_fade) - self.play(*it.chain( - list(map(FadeOut, faders)), - [ - lambda_equals_two.scale_in_place, 1.3, - lambda_equals_two.next_to, self.matrix, DOWN - ] - )) - self.add_foreground_mobject(lambda_equals_two) - self.lambda_equals_two = lambda_equals_two - self.wait() - - def show_diagonally_altered_transform(self): - for entry in self.diag_entries: - lamb = entry[-1] - two = TexMobject("2") - two.set_color(lamb.get_color()) - two.move_to(lamb) - self.play(Transform(lamb, two)) - self.play(*it.chain( - [mob.restore for mob in (self.plane, self.i_hat, self.j_hat)], - list(map(Animation, self.foreground_mobjects)), - )) - - xy_array = Matrix(["x", "y"]) - xy_array.set_color(YELLOW) - zero_array = Matrix([0, 0]) - for array in xy_array, zero_array: - array.set_height(self.matrix.get_height()) - array.add_to_back(BackgroundRectangle(array)) - xy_array.next_to(self.matrix) - equals = TexMobject("=").next_to(xy_array) - equals.add_background_rectangle() - zero_array.next_to(equals) - self.play(*list(map(Write, [xy_array, equals, zero_array]))) - self.wait() - - vectors = VGroup(*[ - self.add_vector(u*x*(LEFT+UP), animate = False) - for x in range(4, 0, -1) - for u in [-1, 1] - ]) - vectors.set_color_by_gradient(MAROON_B, YELLOW) - vectors.save_state() - self.play( - ShowCreation( - vectors, - lag_ratio = 0.5, - run_time = 2 - ), - *list(map(Animation, self.foreground_mobjects)) - ) - self.wait() - self.apply_transposed_matrix( - self.t_matrix - 2*np.identity(2) - ) - self.wait() - self.play(*it.chain( - [mob.restore for mob in (self.plane, self.i_hat, self.j_hat, vectors)], - list(map(FadeOut, [xy_array, equals, zero_array])), - list(map(Animation, self.foreground_mobjects)) - )) - - def show_unaltered_transform(self): - movers = [] - faders = [] - for entry in self.diag_entries: - mover = entry[0] - faders += list(entry[1:]) - mover.target = mover.copy() - mover.target.move_to(entry) - movers.append(mover) - brace = Brace(self.matrix) - brace_text = brace.get_text("Unaltered matrix") - brace_text.add_background_rectangle() - self.lambda_equals_two.target = brace_text - movers.append(self.lambda_equals_two) - self.play(*it.chain( - list(map(MoveToTarget, movers)), - list(map(FadeOut, faders)), - [GrowFromCenter(brace)] - )) - VGroup(*faders).set_fill(opacity = 0) - self.add_foreground_mobject(brace) - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.wait() - -class ThereMightNotBeEigenvectors(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - There could be - \\emph{no} eigenvectors - """) - self.random_blink(3) - -class Rotate90Degrees(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[0, 1], [-1, 0]], - "example_vector_coords" : None, - } - def setup(self): - LinearTransformationScene.setup(self) - matrix = Matrix(self.t_matrix.T) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.next_to(ORIGIN, LEFT) - matrix.to_edge(UP) - matrix.rect = BackgroundRectangle(matrix) - matrix.add_to_back(matrix.rect) - self.add_foreground_mobject(matrix) - self.matrix = matrix - if self.example_vector_coords is not None: - v = self.add_vector(self.example_vector_coords, animate = False) - line = Line(v.get_end()*(-4), v.get_end()*4, color = MAROON_B) - self.play(ShowCreation(line), Animation(v)) - self.add_foreground_mobject(line) - - def construct(self): - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.wait() - -class Rotate90DegreesWithVector(Rotate90Degrees): - CONFIG = { - "example_vector_coords" : [1, 2] - } - -class SolveRotationEigenvalues(Rotate90Degrees): - def construct(self): - self.apply_transposed_matrix(self.t_matrix, run_time = 0) - self.wait() - diag_entries = [ - self.matrix.get_mob_matrix()[i, i] - for i in range(2) - ] - off_diag_entries = [ - self.matrix.get_mob_matrix()[i, 1-i] - for i in range(2) - ] - for entry in diag_entries: - minus_lambda = TexMobject("-\\lambda") - minus_lambda.set_color(MAROON_B) - minus_lambda.move_to(entry) - self.play(Transform(entry, minus_lambda)) - self.wait() - - det_text = get_det_text(self.matrix) - equals = TexMobject("=").next_to(det_text) - self.play(*list(map(Write, [det_text, equals]))) - self.wait() - minus = TexMobject("-") - for entries, sym in (diag_entries, equals), (off_diag_entries, minus): - lp1, rp1, lp2, rp2 = parens = TexMobject("()()") - for entry in entries: - entry.target = entry.copy() - group = VGroup( - lp1, entries[0].target, rp1, - lp2, entries[1].target, rp2, - ) - group.arrange() - group.next_to(sym) - parens.add_background_rectangle() - self.play( - Write(parens), - *[MoveToTarget(entry.copy()) for entry in entries], - run_time = 2 - ) - self.wait() - if entries == diag_entries: - minus.next_to(parens) - self.play(Write(minus)) - polynomial = TexMobject( - "=", "\\lambda^2", "+1=0" - ) - polynomial.set_color_by_tex("\\lambda^2", MAROON_B) - polynomial.add_background_rectangle() - polynomial.next_to(equals, DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT) - self.play(Write(polynomial)) - self.wait() - - result = TexMobject( - "\\lambda", "= i", "\\text{ or }", - "\\lambda", "= -i" - ) - result.set_color_by_tex("\\lambda", MAROON_B) - result.add_background_rectangle() - result.next_to(polynomial, DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT) - self.play(Write(result)) - self.wait() - - interesting_tidbit = TextMobject(""" - Interestingly, though, the fact that multiplication by i - in the complex plane looks like a 90 degree rotation is - related to the fact that i is an eigenvalue of this - transformation of 2d real vectors. The specifics of this - are a little beyond what I want to talk about today, but - note that that eigenvalues which are complex numbers - generally correspond to some kind of rotation in the - transformation. - """, alignment = "") - interesting_tidbit.add_background_rectangle() - interesting_tidbit.set_height(FRAME_Y_RADIUS-0.5) - interesting_tidbit.to_corner(DOWN+RIGHT) - self.play(FadeIn(interesting_tidbit)) - self.wait() - -class ShearExample(RevisitExampleTransformation): - CONFIG = { - "t_matrix" : [[1, 0], [1, 1]], - "include_background_plane" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_HEIGHT, - "secondary_line_ratio" : 1 - }, - } - def construct(self): - self.plane.fade() - self.introduce_matrix() - self.point_out_eigenvectors() - lamb = TexMobject("\\lambda") - lamb.set_color(MAROON_B) - lamb.next_to(self.matrix, DOWN) - self.play(FadeIn(lamb)) - self.play(*self.get_lambda_to_diag_movements(lamb)) - self.add_foreground_mobject(*self.get_mobjects_from_last_animation()) - self.wait() - self.show_determinant() - - def point_out_eigenvectors(self): - vectors = VGroup(*[ - self.add_vector(u*x*RIGHT, animate = False) - for x in range(int(FRAME_X_RADIUS)+1, 0, -1) - for u in [-1, 1] - ]) - vectors.set_color_by_gradient(YELLOW, X_COLOR) - words = VGroup( - TextMobject("Eigenvectors"), - TextMobject("with eigenvalue", "1") - ) - for word in words: - word.set_color_by_tex("1", MAROON_B) - word.add_to_back(BackgroundRectangle(word)) - words.arrange(DOWN, buff = MED_SMALL_BUFF) - words.next_to(ORIGIN, DOWN+RIGHT, buff = MED_SMALL_BUFF) - self.play(ShowCreation(vectors), run_time = 2) - self.play(Write(words)) - self.wait() - - def show_determinant(self): - det_text = get_det_text(self.matrix) - equals = TexMobject("=").next_to(det_text) - three_minus_lamb, two_minus_lamb = diag_entries = [ - entry.copy() for entry in self.diag_entries - ] - one = self.matrix.get_mob_matrix()[0, 1].copy() - zero = self.matrix.get_mob_matrix()[1, 0].copy() - for entry in diag_entries + [one, zero]: - entry.target = entry.copy() - lp1, rp1, lp2, rp2 = parens = TexMobject("()()") - minus = TexMobject("-") - cdot = TexMobject("\\cdot") - VGroup( - lp1, three_minus_lamb.target, rp1, - lp2, two_minus_lamb.target, rp2, - minus, one.target, cdot, zero.target - ).arrange().next_to(equals) - - parens.add_background_rectangle() - new_rect = BackgroundRectangle(VGroup(minus, zero.target)) - - brace = Brace(new_rect, buff = 0) - brace_text = brace.get_text("Equals 0, so ", "ignore") - brace_text.add_background_rectangle() - - brace.target = Brace(parens) - brace_text.target = brace.target.get_text( - "Quadratic polynomial in ", "$\\lambda$" - ) - brace_text.target.set_color_by_tex("$\\lambda$", MAROON_B) - brace_text.target.add_background_rectangle() - - equals_0 = TexMobject("=0") - equals_0.next_to(parens, RIGHT) - equals_0.add_background_rectangle() - - final_brace = Brace(VGroup(parens, equals_0)) - final_text = TexMobject("\\lambda", "=1") - final_text.set_color_by_tex("\\lambda", MAROON_B) - final_text.next_to(final_brace, DOWN) - lambda_equals_two = VGroup(*final_text[:2]).copy() - lambda_equals_two.add_to_back(BackgroundRectangle(lambda_equals_two)) - final_text.add_background_rectangle() - - self.play( - Write(det_text), - Write(equals) - ) - self.wait() - self.play( - Write(parens), - MoveToTarget(three_minus_lamb), - MoveToTarget(two_minus_lamb), - run_time = 2 - ) - self.wait() - self.play( - FadeIn(new_rect), - MoveToTarget(one), - MoveToTarget(zero), - Write(minus), - Write(cdot), - run_time = 2 - ) - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait() - self.play(* list(map(FadeOut, [ - one, zero, minus, cdot, new_rect, brace, brace_text - ]))) - self.wait() - self.play(Write(equals_0)) - self.wait() - self.play( - FadeIn(final_brace), - FadeIn(final_text) - ) - self.wait() - # faders = [ - # det_text, equals, parens, - # three_minus_lamb, two_minus_lamb, - # brace, brace_text, equals_0, - # ] - # if to_fade is not None: - # faders.append(to_fade) - # self.play(*it.chain( - # map(FadeOut, faders), - # [ - # lambda_equals_two.scale_in_place, 1.3, - # lambda_equals_two.next_to, self.matrix, DOWN - # ] - # )) - # self.add_foreground_mobject(lambda_equals_two) - # self.lambda_equals_two = lambda_equals_two - # self.wait() - -class EigenvalueCanHaveMultipleEigenVectors(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - A single eigenvalue can - have more that a line - full of eigenvectors - """) - self.change_student_modes(*["pondering"]*3) - self.random_blink(2) - -class ScalingExample(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[2, 0], [0, 2]] - } - def construct(self): - matrix = Matrix(self.t_matrix.T) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.add_to_back(BackgroundRectangle(matrix)) - matrix.next_to(ORIGIN, LEFT) - matrix.to_edge(UP) - words = TextMobject("Scale everything by 2") - words.add_background_rectangle() - words.next_to(matrix, RIGHT) - self.add_foreground_mobject(matrix, words) - for coords in [2, 1], [-2.5, -1], [1, -1]: - self.add_vector(coords, color = random_color()) - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.wait() - -class IntroduceEigenbasis(TeacherStudentsScene): - def construct(self): - words1, words2 = list(map(TextMobject, [ - "Finish with ``eigenbasis.''", - """Make sure you've - watched the last video""" - ])) - words1.set_color(YELLOW) - self.teacher_says(words1) - self.change_student_modes( - "pondering", "raise_right_hand", "erm" - ) - self.random_blink() - new_words = VGroup(words1.copy(), words2) - new_words.arrange(DOWN, buff = MED_SMALL_BUFF) - new_words.scale(0.8) - self.teacher.bubble.add_content(new_words) - self.play( - self.get_teacher().change_mode, "sassy", - Write(words2), - Transform(words1, new_words[0]) - ) - student = self.get_students()[0] - self.play( - student.change_mode, "guilty", - student.look, LEFT - ) - self.random_blink(2) - -class BasisVectorsAreEigenvectors(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[-1, 0], [0, 2]] - } - def construct(self): - matrix = Matrix(self.t_matrix.T) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.next_to(ORIGIN, LEFT) - matrix.to_edge(UP) - - words = TextMobject( - "What if both basis vectors \\\\", - "are eigenvectors?" - ) - for word in words: - word.add_to_back(BackgroundRectangle(word)) - words.to_corner(UP+RIGHT) - - self.play(Write(words)) - self.add_foreground_mobject(words) - self.wait() - self.apply_transposed_matrix([self.t_matrix[0], [0, 1]]) - self.wait() - self.apply_transposed_matrix([[1, 0], self.t_matrix[1]]) - self.wait() - - i_coords = Matrix(self.t_matrix[0]) - i_coords.next_to(self.i_hat.get_end(), DOWN+LEFT) - i_coords.set_color(X_COLOR) - j_coords = Matrix(self.t_matrix[1]) - j_coords.next_to(self.j_hat.get_end(), RIGHT) - j_coords.set_color(Y_COLOR) - - for array in matrix, i_coords, j_coords: - array.rect = BackgroundRectangle(array) - array.add_to_back(array.rect) - self.play(*list(map(Write, [i_coords, j_coords]))) - self.wait() - self.play( - Transform(i_coords.rect, matrix.rect), - Transform(i_coords.get_brackets(), matrix.get_brackets()), - i_coords.get_entries().move_to, VGroup( - *matrix.get_mob_matrix()[:,0] - ) - ) - self.play( - FadeOut(j_coords.rect), - FadeOut(j_coords.get_brackets()), - j_coords.get_entries().move_to, VGroup( - *matrix.get_mob_matrix()[:,1] - ) - ) - self.remove(i_coords, j_coords) - self.add(matrix) - self.wait() - - - diag_entries = VGroup(*[ - matrix.get_mob_matrix()[i, i] - for i in range(2) - ]) - off_diag_entries = VGroup(*[ - matrix.get_mob_matrix()[1-i, i] - for i in range(2) - ]) - for entries in diag_entries, off_diag_entries: - self.play( - entries.scale_in_place, 1.3, - entries.set_color, YELLOW, - run_time = 2, - rate_func = there_and_back - ) - self.wait() - -class DefineDiagonalMatrix(Scene): - def construct(self): - n_dims = 4 - numerical_matrix = np.identity(n_dims, dtype = 'int') - for x in range(n_dims): - numerical_matrix[x, x] = random.randint(-9, 9) - matrix = Matrix(numerical_matrix) - diag_entries = VGroup(*[ - matrix.get_mob_matrix()[i,i] - for i in range(n_dims) - ]) - off_diag_entries = VGroup(*[ - matrix.get_mob_matrix()[i, j] - for i in range(n_dims) - for j in range(n_dims) - if i != j - ]) - - title = TextMobject("``Diagonal matrix''") - title.to_edge(UP) - - self.add(matrix) - self.wait() - for entries in off_diag_entries, diag_entries: - self.play( - entries.scale_in_place, 1.1, - entries.set_color, YELLOW, - rate_func = there_and_back, - ) - self.wait() - self.play(Write(title)) - self.wait() - self.play( - matrix.set_column_colors, - X_COLOR, Y_COLOR, Z_COLOR, YELLOW - ) - self.wait() - self.play(diag_entries.set_color, MAROON_B) - self.play( - diag_entries.scale_in_place, 1.1, - rate_func = there_and_back, - ) - self.wait() - -class RepeatedMultiplicationInAction(Scene): - def construct(self): - vector = Matrix(["x", "y"]) - vector.set_color(YELLOW) - vector.scale(1.2) - vector.shift(RIGHT) - matrix, scalars = self.get_matrix(vector) - #First multiplication - for v_entry, scalar in zip(vector.get_entries(), scalars): - scalar.target = scalar.copy() - scalar.target.next_to(v_entry, LEFT) - l_bracket = vector.get_brackets()[0] - l_bracket.target = l_bracket.copy() - l_bracket.target.next_to(VGroup(*[ - scalar.target for scalar in scalars - ]), LEFT) - - self.add(vector) - self.play(*list(map(FadeIn, [matrix]+scalars))) - self.wait() - self.play( - FadeOut(matrix), - *list(map(MoveToTarget, scalars + [l_bracket])) - ) - self.wait() - #nth multiplications - for scalar in scalars: - scalar.exp = VectorizedPoint(scalar.get_corner(UP+RIGHT)) - scalar.exp.shift(SMALL_BUFF*RIGHT/2.) - for new_exp in range(2, 6): - matrix, new_scalars = self.get_matrix(vector) - new_exp_mob = TexMobject(str(new_exp)).scale(0.7) - movers = [] - to_remove = [] - for v_entry, scalar, new_scalar in zip(vector.get_entries(), scalars, new_scalars): - scalar.exp.target = new_exp_mob.copy() - scalar.exp.target.set_color(scalar.get_color()) - scalar.exp.target.move_to(scalar.exp, aligned_edge = LEFT) - new_scalar.target = scalar.exp.target - scalar.target = scalar.copy() - VGroup(scalar.target, scalar.exp.target).next_to( - v_entry, LEFT, aligned_edge = DOWN - ) - movers += [scalar, scalar.exp, new_scalar] - to_remove.append(new_scalar) - l_bracket.target.next_to(VGroup(*[ - scalar.target for scalar in scalars - ]), LEFT) - movers.append(l_bracket) - - self.play(*list(map(FadeIn, [matrix]+new_scalars))) - self.wait() - self.play( - FadeOut(matrix), - *list(map(MoveToTarget, movers)) - ) - self.remove(*to_remove) - self.wait() - - - def get_matrix(self, vector): - matrix = Matrix([[3, 0], [0, 2]]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.next_to(vector, LEFT) - scalars = [matrix.get_mob_matrix()[i, i] for i in range(2)] - matrix.remove(*scalars) - return matrix, scalars - -class RepeatedMultilpicationOfMatrices(Scene): - CONFIG = { - "matrix" : [[3, 0], [0, 2]], - "diagonal" : True, - } - def construct(self): - vector = Matrix(["x", "y"]) - vector.set_color(YELLOW) - matrix = Matrix(self.matrix) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrices = VGroup(*[ - matrix.copy(), - TexMobject("\\dots\\dots"), - matrix.copy(), - matrix.copy(), - matrix.copy(), - ]) - last_matrix = matrices[-1] - group = VGroup(*list(matrices) + [vector]) - group.arrange() - - brace = Brace(matrices) - brace_text = brace.get_text("100", "times") - hundred = brace_text[0] - hundred_copy = hundred.copy() - - self.add(vector) - for matrix in reversed(list(matrices)): - self.play(FadeIn(matrix)) - self.wait() - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait() - - if self.diagonal: - last_matrix.target = last_matrix.copy() - for i, hund in enumerate([hundred, hundred_copy]): - entry = last_matrix.target.get_mob_matrix()[i, i] - hund.target = hund.copy() - hund.target.scale(0.5) - hund.target.next_to(entry, UP+RIGHT, buff = 0) - hund.target.set_color(entry.get_color()) - VGroup(hund.target, entry).move_to(entry, aligned_edge = DOWN) - lb, rb = last_matrix.target.get_brackets() - lb.shift(SMALL_BUFF*LEFT) - rb.shift(SMALL_BUFF*RIGHT) - VGroup( - last_matrix.target, hundred.target, hundred_copy.target - ).next_to(vector, LEFT) - - self.play(*it.chain( - list(map(FadeOut, [brace, brace_text[1]] + list(matrices[:-1]))), - list(map(MoveToTarget, [hundred, hundred_copy, last_matrix])) - ), run_time = 2) - self.wait() - else: - randy = Randolph().to_corner() - self.play(FadeIn(randy)) - self.play(randy.change_mode, "angry") - self.play(Blink(randy)) - self.wait() - self.play(Blink(randy)) - self.wait() - -class RepeatedMultilpicationOfNonDiagonalMatrices(RepeatedMultilpicationOfMatrices): - CONFIG = { - "matrix" : [[3, 4], [1, 1]], - "diagonal" : False, - } - -class WhatAreTheOddsOfThat(TeacherStudentsScene): - def construct(self): - self.student_says(""" - Sure, but what are the - odds of that happening? - """) - self.random_blink() - self.change_student_modes("pondering") - self.random_blink(3) - -class LastVideo(Scene): - def construct(self): - title = TextMobject("Last chapter: Change of basis") - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN, buff = MED_SMALL_BUFF) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - -class ChangeToEigenBasis(ExampleTranformationScene): - CONFIG = { - "show_basis_vectors" : False, - "include_background_plane" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_HEIGHT, - "secondary_line_ratio" : 0 - }, - } - def construct(self): - self.plane.fade() - self.introduce_eigenvectors() - self.write_change_of_basis_matrix() - self.ask_about_power() - - def introduce_eigenvectors(self): - x_vectors, v_vectors = [ - VGroup(*[ - self.add_vector(u*x*vect, animate = False) - for x in range(num, 0, -1) - for u in [-1, 1] - ]) - for vect, num in [(RIGHT, 7), (UP+LEFT, 4)] - ] - x_vectors.set_color_by_gradient(YELLOW, X_COLOR) - v_vectors.set_color_by_gradient(MAROON_B, YELLOW) - self.remove(x_vectors, v_vectors) - self.play(ShowCreation(x_vectors, run_time = 2)) - self.play(ShowCreation(v_vectors, run_time = 2)) - self.wait() - self.plane.save_state() - self.apply_transposed_matrix( - self.t_matrix, - rate_func = there_and_back, - path_arc = 0 - ) - - x_vector = x_vectors[-1] - v_vector = v_vectors[-1] - x_vectors.remove(x_vector) - v_vectors.remove(v_vector) - words = TextMobject("Eigenvectors span space") - new_words = TextMobject("Use eigenvectors as basis") - for text in words, new_words: - text.add_background_rectangle() - text.next_to(ORIGIN, DOWN+LEFT, buff = MED_SMALL_BUFF) - # text.to_edge(RIGHT) - - self.play(Write(words)) - self.play( - FadeOut(x_vectors), - FadeOut(v_vectors), - Animation(x_vector), - Animation(v_vector), - ) - self.wait() - self.play(Transform(words, new_words)) - self.wait() - self.b1, self.b2 = x_vector, v_vector - self.moving_vectors = [self.b1, self.b2] - self.to_fade = [words] - - def write_change_of_basis_matrix(self): - b1, b2 = self.b1, self.b2 - for vect in b1, b2: - vect.coords = vector_coordinate_label(vect) - vect.coords.set_color(vect.get_color()) - vect.entries = vect.coords.get_entries() - vect.entries.target = vect.entries.copy() - b1.coords.next_to(b1.get_end(), DOWN+RIGHT) - b2.coords.next_to(b2.get_end(), LEFT) - for vect in b1, b2: - self.play(Write(vect.coords)) - self.wait() - - cob_matrix = Matrix(np.array([ - list(vect.entries.target) - for vect in (b1, b2) - ]).T) - cob_matrix.rect = BackgroundRectangle(cob_matrix) - cob_matrix.add_to_back(cob_matrix.rect) - cob_matrix.set_height(self.matrix.get_height()) - cob_matrix.next_to(self.matrix) - brace = Brace(cob_matrix) - brace_text = brace.get_text("Change of basis matrix") - brace_text.next_to(brace, DOWN, aligned_edge = LEFT) - brace_text.add_background_rectangle() - - copies = [vect.coords.copy() for vect in (b1, b2)] - self.to_fade += copies - self.add(*copies) - self.play( - Transform(b1.coords.rect, cob_matrix.rect), - Transform(b1.coords.get_brackets(), cob_matrix.get_brackets()), - MoveToTarget(b1.entries) - ) - to_remove = self.get_mobjects_from_last_animation() - self.play(MoveToTarget(b2.entries)) - to_remove += self.get_mobjects_from_last_animation() - self.remove(*to_remove) - self.add(cob_matrix) - self.to_fade += [b2.coords] - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.to_fade += [brace, brace_text] - self.wait() - - inv_cob = cob_matrix.copy() - inv_cob.target = inv_cob.copy() - neg_1 = TexMobject("-1") - neg_1.add_background_rectangle() - inv_cob.target.next_to( - self.matrix, LEFT, buff = neg_1.get_width()+2*SMALL_BUFF - ) - neg_1.next_to( - inv_cob.target.get_corner(UP+RIGHT), - RIGHT, - ) - self.play( - MoveToTarget(inv_cob, path_arc = -np.pi/2), - Write(neg_1) - ) - self.wait() - self.add_foreground_mobject(cob_matrix, inv_cob, neg_1) - self.play(*list(map(FadeOut, self.to_fade))) - self.wait() - self.play(FadeOut(self.plane)) - cob_transform = self.get_matrix_transformation([[1, 0], [-1, 1]]) - ApplyMethod(self.plane.apply_function, cob_transform).update(1) - self.planes.set_color(BLUE_D) - self.plane.axes.set_color(WHITE) - self.play( - FadeIn(self.plane), - *list(map(Animation, self.foreground_mobjects+self.moving_vectors)) - ) - self.add(self.plane.copy().set_color(GREY).set_stroke(width = 2)) - self.apply_transposed_matrix(self.t_matrix) - - equals = TexMobject("=").next_to(cob_matrix) - final_matrix = Matrix([[3, 0], [0, 2]]) - final_matrix.add_to_back(BackgroundRectangle(final_matrix)) - for i in range(2): - final_matrix.get_mob_matrix()[i, i].set_color(MAROON_B) - final_matrix.next_to(equals, RIGHT) - self.play( - Write(equals), - Write(final_matrix) - ) - self.wait() - - eigenbasis = TextMobject("``Eigenbasis''") - eigenbasis.add_background_rectangle() - eigenbasis.next_to(ORIGIN, DOWN) - self.play(Write(eigenbasis)) - self.wait() - - def ask_about_power(self): - morty = Mortimer() - morty.to_edge(DOWN).shift(LEFT) - bubble = morty.get_bubble( - "speech", height = 3, width = 5, direction = RIGHT - ) - bubble.set_fill(BLACK, opacity = 1) - matrix_copy = self.matrix.copy().scale(0.7) - hundred = TexMobject("100").scale(0.7) - hundred.next_to(matrix_copy.get_corner(UP+RIGHT), RIGHT) - compute = TextMobject("Compute") - compute.next_to(matrix_copy, LEFT, buff = MED_SMALL_BUFF) - words = VGroup(compute, matrix_copy, hundred) - bubble.add_content(words) - - self.play(FadeIn(morty)) - self.play( - morty.change_mode, "speaking", - ShowCreation(bubble), - Write(words) - ) - for x in range(2): - self.play(Blink(morty)) - self.wait(2) - -class CannotDoWithWithAllTransformations(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Not all matrices - can become diagonal - """) - self.change_student_modes(*["tired"]*3) - self.random_blink(2) - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/chapter11.py b/from_3b1b/old/eola/chapter11.py deleted file mode 100644 index 93005282..00000000 --- a/from_3b1b/old/eola/chapter11.py +++ /dev/null @@ -1,2572 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eola.chapter1 import plane_wave_homotopy -from from_3b1b.old.eola.chapter3 import ColumnsToBasisVectors -from from_3b1b.old.eola.chapter5 import NameDeterminant, Blob -from from_3b1b.old.eola.chapter9 import get_small_bubble -from from_3b1b.old.eola.chapter10 import ExampleTranformationScene - -class Student(PiCreature): - CONFIG = { - "name" : "Student" - } - def get_name(self): - text = TextMobject(self.name) - text.add_background_rectangle() - text.next_to(self, DOWN) - return text - -class PhysicsStudent(Student): - CONFIG = { - "color" : PINK, - "name" : "Physics student" - } - -class CSStudent(Student): - CONFIG = { - "color" : PURPLE_E, - "flip_at_start" : True, - "name" : "CS Student" - } - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject( - "``Such", - "axioms,", - "together with other unmotivated definitions,", - "serve mathematicians mainly by making it", - "difficult for the uninitiated", - "to master their subject, thereby elevating its authority.''", - enforce_new_line_structure = False, - alignment = "", - ) - words.set_color_by_tex("axioms,", BLUE) - words.set_color_by_tex("difficult for the uninitiated", RED) - words.set_width(FRAME_WIDTH - 2) - words.to_edge(UP) - author = TextMobject("-Vladmir Arnold") - author.set_color(YELLOW) - author.next_to(words, DOWN, buff = MED_LARGE_BUFF) - - self.play(Write(words, run_time = 8)) - self.wait() - self.play(FadeIn(author)) - self.wait(3) - -class RevisitOriginalQuestion(TeacherStudentsScene): - def construct(self): - self.teacher_says("Let's revisit ", "\\\\ an old question") - self.random_blink() - question = TextMobject("What are ", "vectors", "?", arg_separator = "") - question.set_color_by_tex("vectors", YELLOW) - self.teacher_says( - question, - added_anims = [ - ApplyMethod(self.get_students()[i].change_mode, mode) - for i, mode in enumerate([ - "pondering", "raise_right_hand", "erm" - ]) - ] - ) - self.random_blink(2) - -class WhatIsA2DVector(LinearTransformationScene): - CONFIG = { - "v_coords" : [1, 2], - "show_basis_vectors" : False, - "include_background_plane" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_HEIGHT, - "secondary_line_ratio" : 1 - }, - } - def construct(self): - self.plane.fade() - self.introduce_vector_and_space() - self.bring_in_students() - - def introduce_vector_and_space(self): - v = Vector(self.v_coords) - coords = Matrix(self.v_coords) - coords.add_to_back(BackgroundRectangle(coords)) - coords.next_to(v.get_end(), RIGHT) - - two_d_vector = TextMobject( - "``Two-dimensional ", "vector", "''", - arg_separator = "" - ) - two_d_vector.set_color_by_tex("vector", YELLOW) - two_d_vector.add_background_rectangle() - two_d_vector.to_edge(UP) - - self.play( - Write(two_d_vector), - ShowCreation(v), - Write(coords), - run_time = 2 - ) - self.wait() - self.v, self.coords = v, coords - - def bring_in_students(self): - everything = self.get_mobjects() - v, coords = self.v, self.coords - physics_student = PhysicsStudent() - cs_student = CSStudent() - students = [physics_student, cs_student] - for student, vect in zip(students, [LEFT, RIGHT]): - student.change_mode("confused") - student.to_corner(DOWN+vect, buff = MED_LARGE_BUFF) - student.look_at(v) - student.bubble = get_small_bubble( - student, height = 4, width = 4, - ) - self.play(*list(map(FadeIn, students))) - self.play(Blink(physics_student)) - self.wait() - for student, vect in zip(students, [RIGHT, LEFT]): - for mob in v, coords: - mob.target = mob.copy() - mob.target.scale(0.7) - arrow = TexMobject("\\Rightarrow") - group = VGroup(v.target, arrow, coords.target) - group.arrange(vect) - student.bubble.add_content(group) - student.v, student.coords = v.copy(), coords.copy() - student.arrow = arrow - - self.play( - student.change_mode, "pondering", - ShowCreation(student.bubble), - Write(arrow), - Transform(student.v, v.target), - Transform(student.coords, coords.target), - ) - self.play(Blink(student)) - self.wait() - anims = [] - for student in students: - v, coords = student.v, student.coords - v.target = v.copy() - coords.target = coords.copy() - group = VGroup(v.target, coords.target) - group.arrange(DOWN) - group.set_height(coords.get_height()) - group.next_to(student.arrow, RIGHT) - student.q_marks = TexMobject("???") - student.q_marks.set_color_by_gradient(BLUE, YELLOW) - student.q_marks.next_to(student.arrow, LEFT) - anims += [ - Write(student.q_marks), - MoveToTarget(v), - MoveToTarget(coords), - student.change_mode, "erm", - student.look_at, student.bubble - ] - cs_student.v.save_state() - cs_student.coords.save_state() - self.play(*anims) - for student in students: - self.play(Blink(student)) - self.wait() - self.play(*it.chain( - list(map(FadeOut, everything + [ - physics_student.bubble, - physics_student.v, - physics_student.coords, - physics_student.arrow, - physics_student.q_marks, - cs_student.q_marks, - ])), - [ApplyMethod(s.change_mode, "plain") for s in students], - list(map(Animation, [cs_student.bubble, cs_student.arrow])), - [mob.restore for mob in (cs_student.v, cs_student.coords)], - )) - bubble = cs_student.get_bubble(SpeechBubble, width = 4, height = 3) - bubble.set_fill(BLACK, opacity = 1) - bubble.next_to(cs_student, UP+LEFT) - bubble.write("Consider higher \\\\ dimensions") - self.play( - cs_student.change_mode, "speaking", - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(Blink(physics_student)) - self.wait() - -class HigherDimensionalVectorsNumerically(Scene): - def construct(self): - words = VGroup(*list(map(TextMobject, [ - "4D vector", - "5D vector", - "100D vector", - ]))) - words.arrange(RIGHT, buff = LARGE_BUFF*2) - words.to_edge(UP) - vectors = VGroup(*list(map(Matrix, [ - [3, 1, 4, 1], - [5, 9, 2, 6, 5], - [3, 5, 8, "\\vdots", 0, 8, 6] - ]))) - colors = [YELLOW, MAROON_B, GREEN] - for word, vector, color in zip(words, vectors, colors): - vector.shift(word.get_center()[0]*RIGHT) - word.set_color(color) - vector.set_color(color) - - for word in words: - self.play(FadeIn(word)) - self.play(Write(vectors)) - self.wait() - for index, dim, direction in (0, 4, RIGHT), (2, 100, LEFT): - v = vectors[index] - v.target = v.copy() - brace = Brace(v, direction) - brace.move_to(v) - v.target.next_to(brace, -direction) - text = brace.get_text("%d numbers"%dim) - self.play( - MoveToTarget(v), - GrowFromCenter(brace), - Write(text) - ) - entries = v.get_entries() - num_entries = len(list(entries)) - self.play(*[ - Transform( - entries[i], - entries[i].copy().scale_in_place(1.2).set_color(WHITE), - rate_func = squish_rate_func( - there_and_back, - i/(2.*num_entries), - i/(2.*num_entries)+0.5 - ), - run_time = 2 - ) - for i in range(num_entries) - ]) - self.wait() - -class HyperCube(VMobject): - CONFIG = { - "color" : BLUE_C, - "color2" : BLUE_D, - "dims" : 4, - } - def init_points(self): - corners = np.array(list(map(np.array, it.product(*[(-1, 1)]*self.dims)))) - def project(four_d_array): - result = four_d_array[:3] - w = four_d_array[self.dims-1] - scalar = interpolate(0.8, 1.2 ,(w+1)/2.) - return scalar*result - for a1, a2 in it.combinations(corners, 2): - if sum(a1==a2) != self.dims-1: - continue - self.add(Line(project(a1), project(a2))) - self.pose_at_angle() - self.set_color_by_gradient(self.color, self.color2) - -class AskAbout4DPhysicsStudent(Scene): - def construct(self): - physy = PhysicsStudent().to_edge(DOWN).shift(2*LEFT) - compy = CSStudent().to_edge(DOWN).shift(2*RIGHT) - for pi1, pi2 in (physy, compy), (compy, physy): - pi1.look_at(pi2.eyes) - physy.bubble = physy.get_bubble(SpeechBubble, width = 5, height = 4.5) - - line = Line(LEFT, RIGHT, color = BLUE_B) - square = Square(color = BLUE_C) - square.scale_in_place(0.5) - cube = HyperCube(color = BLUE_D, dims = 3) - hyper_cube = HyperCube() - thought_mobs = [] - for i, mob in enumerate([line, square, cube, hyper_cube]): - mob.set_height(2) - tex = TexMobject("%dD"%(i+1)) - tex.next_to(mob, UP) - group = VGroup(mob, tex) - thought_mobs.append(group) - group.shift( - physy.bubble.get_top() -\ - tex.get_top() + MED_SMALL_BUFF*DOWN - ) - line.shift(DOWN) - curr_mob = thought_mobs[0] - - self.add(compy, physy) - self.play( - compy.change_mode, "confused", - physy.change_mode, "hooray", - ShowCreation(physy.bubble), - Write(curr_mob, run_time = 1), - ) - self.play(Blink(compy)) - for i, mob in enumerate(thought_mobs[1:]): - self.play(Transform(curr_mob, mob)) - self.remove(curr_mob) - curr_mob = mob - self.add(curr_mob) - if i%2 == 1: - self.play(Blink(physy)) - else: - self.wait() - self.play(Blink(compy)) - self.wait() - -class ManyCoordinateSystems(LinearTransformationScene): - CONFIG = { - "v_coords" : [2, 1], - "include_background_plane" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_WIDTH, - "secondary_line_ratio" : 1 - }, - } - def construct(self): - self.title = TextMobject("Many possible coordinate systems") - self.title.add_background_rectangle() - self.title.to_edge(UP) - self.add_foreground_mobject(self.title) - self.v = Vector(self.v_coords) - self.play(ShowCreation(self.v)) - self.add_foreground_mobject(self.v) - - t_matrices = [ - [[0.5, 0.5], [-0.5, 0.5]], - [[1, -1], [-3, -1]], - [[-1, 2], [-0.5, -1]], - ] - movers = [self.plane, self.i_hat, self.j_hat] - for mover in movers: - mover.save_state() - for t_matrix in t_matrices: - self.animate_coordinates() - self.play(*it.chain( - list(map(FadeOut, movers)), - list(map(Animation, self.foreground_mobjects)) - )) - for mover in movers: - mover.restore() - self.apply_transposed_matrix(t_matrix, run_time = 0) - self.play(*it.chain( - list(map(FadeIn, movers)), - list(map(Animation, self.foreground_mobjects)) - )) - self.animate_coordinates() - - - def animate_coordinates(self): - self.i_hat.save_state() - self.j_hat.save_state() - cob_matrix = np.array([ - self.i_hat.get_end()[:2], - self.j_hat.get_end()[:2] - ]).T - inv_cob = np.linalg.inv(cob_matrix) - coords = np.dot(inv_cob, self.v_coords) - array = Matrix(list(map(DecimalNumber, coords))) - array.get_entries()[0].set_color(X_COLOR) - array.get_entries()[1].set_color(Y_COLOR) - array.add_to_back(BackgroundRectangle(array)) - for entry in array.get_entries(): - entry.add_to_back(BackgroundRectangle(entry)) - array.next_to(self.title, DOWN) - - self.i_hat.target = self.i_hat.copy().scale(coords[0]) - self.j_hat.target = self.j_hat.copy().scale(coords[1]) - coord1, coord2 = array.get_entries().copy() - for coord, vect in (coord1, self.i_hat), (coord2, self.j_hat): - coord.target = coord.copy().next_to( - vect.target.get_end()/2, - rotate_vector(vect.get_end(), -np.pi/2) - ) - - self.play(Write(array, run_time = 1)) - self.wait() - self.play(*list(map(MoveToTarget, [self.i_hat, coord1]))) - self.play(*list(map(MoveToTarget, [self.j_hat, coord2]))) - self.play(VGroup(self.j_hat, coord2).shift, self.i_hat.get_end()) - self.wait(2) - self.play( - self.i_hat.restore, - self.j_hat.restore, - *list(map(FadeOut, [array, coord1, coord2])) - ) - -class DeterminantAndEigenvectorDontCare(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[3, 1], [1, 2]], - "include_background_plane" : False, - "show_basis_vectors" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_HEIGHT, - "secondary_line_ratio" : 1 - }, - } - def construct(self): - words = TextMobject( - "Determinant", - "and", - "eigenvectors", - "don't \\\\ care about the coordinate system" - ) - words.set_color_by_tex("Determinant", YELLOW) - words.set_color_by_tex("eigenvectors", MAROON_B) - words.add_background_rectangle() - words.to_edge(UP) - dark_yellow = Color(rgb = interpolate( - color_to_rgb(YELLOW), - color_to_rgb(BLACK), - 0.5 - )) - - blob = Blob( - stroke_color = YELLOW, - fill_color = dark_yellow, - fill_opacity = 1, - ) - blob.shift(2*LEFT+UP) - det_label = TexMobject("A") - det_label = VGroup( - VectorizedPoint(det_label.get_left()).set_color(WHITE), - det_label - ) - det_label_target = TexMobject("\\det(M)\\cdot", "A") - det_label.move_to(blob) - - eigenvectors = VGroup(*self.get_eigenvectors()) - - self.add_foreground_mobject(words) - self.wait() - self.play( - FadeIn(blob), - Write(det_label) - ) - self.play( - ShowCreation( - eigenvectors, - run_time = 2, - ), - Animation(words) - ) - self.wait() - - self.add_transformable_mobject(blob) - self.add_moving_mobject(det_label, det_label_target) - for vector in eigenvectors: - self.add_vector(vector, animate = False) - self.remove(self.plane) - non_plane_mobs = self.get_mobjects() - self.add(self.plane, *non_plane_mobs) - - cob_matrices = [ - None, - [[1, -1], [-3, -1]], - [[-1, 2], [-0.5, -1]], - ] - def special_rate_func(t): - if t < 0.3: - return smooth(t/0.3) - if t > 0.7: - return smooth((1-t)/0.3) - return 1 - for cob_matrix in cob_matrices: - if cob_matrix is not None: - self.play( - FadeOut(self.plane), - *list(map(Animation, non_plane_mobs)) - ) - transform = self.get_matrix_transformation(cob_matrix) - self.plane.apply_function(transform) - self.play( - FadeIn(self.plane), - *list(map(Animation, non_plane_mobs)) - ) - self.wait() - self.apply_transposed_matrix( - self.t_matrix, - rate_func = special_rate_func, - run_time = 8 - ) - - - - - def get_eigenvectors(self): - vals, (eig_matrix) = np.linalg.eig(self.t_matrix.T) - v1, v2 = eig_matrix.T - result = [] - for v in v1, v2: - vectors = VGroup(*[ - Vector(u*x*v) - for x in range(7, 0, -1) - for u in [-1, 1] - ]) - vectors.set_color_by_gradient(MAROON_A, MAROON_C) - result += list(vectors) - return result - -class WhatIsSpace(Scene): - def construct(self): - physy = PhysicsStudent() - compy = CSStudent() - physy.to_edge(DOWN).shift(4*LEFT) - compy.to_edge(DOWN).shift(4*RIGHT) - physy.make_eye_contact(compy) - - physy.bubble = get_small_bubble(physy) - vector = Vector([1, 2]) - physy.bubble.add_content(vector) - compy.bubble = compy.get_bubble(SpeechBubble, width = 6, height = 4) - compy.bubble.set_fill(BLACK, opacity = 1) - compy.bubble.write("What exactly do\\\\ you mean by ``space''?") - - self.add(compy, physy) - self.play( - physy.change_mode, "pondering", - ShowCreation(physy.bubble), - ShowCreation(vector) - ) - self.play( - compy.change_mode, "sassy", - ShowCreation(compy.bubble), - Write(compy.bubble.content) - ) - self.play(Blink(physy)) - self.wait() - self.play(Blink(compy)) - self.wait() - -class OtherVectorishThings(TeacherStudentsScene): - def construct(self): - words = TextMobject( - "There are other\\\\", - "vectorish", - "things..." - ) - words.set_color_by_tex("vectorish", YELLOW) - self.teacher_says(words) - self.change_student_modes( - "pondering", "raise_right_hand", "erm" - ) - self.random_blink(2) - words = TextMobject("...like", "functions") - words.set_color_by_tex("functions", PINK) - self.teacher_says(words) - self.change_student_modes(*["pondering"]*3) - self.random_blink(2) - self.teacher_thinks("") - self.zoom_in_on_thought_bubble(self.get_teacher().bubble) - -class FunctionGraphScene(Scene): - CONFIG = { - "graph_colors" : [RED, YELLOW, PINK], - "default_functions" : [ - lambda x : (x**3 - 9*x)/20., - lambda x : -(x**2)/8.+1 - ], - "default_names" : ["f", "g", "h"], - "x_min" : -4, - "x_max" : 4, - "line_to_line_buff" : 0.03 - } - def setup(self): - self.axes = Axes( - x_min = self.x_min, - x_max = self.x_max, - ) - self.add(self.axes) - self.graphs = [] - - def get_function_graph(self, func = None, animate = True, - add = True, **kwargs): - index = len(self.graphs) - if func is None: - func = self.default_functions[ - index%len(self.default_functions) - ] - default_color = self.graph_colors[index%len(self.graph_colors)] - kwargs["color"] = kwargs.get("color", default_color) - kwargs["x_min"] = kwargs.get("x_min", self.x_min) - kwargs["x_max"] = kwargs.get("x_max", self.x_max) - graph = FunctionGraph(func, **kwargs) - if animate: - self.play(ShowCreation(graph)) - if add: - self.add(graph) - self.graphs.append(graph) - return graph - - def get_index(self, function_graph): - if function_graph not in self.graphs: - self.graphs.append(function_graph) - return self.graphs.index(function_graph) - - def get_output_lines(self, function_graph, num_steps = None, nudge = True): - index = self.get_index(function_graph) - num_steps = num_steps or function_graph.num_steps - lines = VGroup() - nudge_size = index*self.line_to_line_buff - x_min, x_max = function_graph.x_min, function_graph.x_max - for x in np.linspace(x_min, x_max, num_steps): - if nudge: - x += nudge_size - y = function_graph.function(x) - lines.add(Line(x*RIGHT, x*RIGHT+y*UP)) - lines.set_color(function_graph.get_color()) - return lines - - def add_lines(self, output_lines): - self.play(ShowCreation( - output_lines, - lag_ratio = 0.5, - run_time = 2 - )) - - - def label_graph(self, function_graph, name = None, animate = True): - index = self.get_index(function_graph) - name = name or self.default_names[index%len(self.default_names)] - label = TexMobject("%s(x)"%name) - label.next_to(function_graph.point_from_proportion(1), RIGHT) - label.shift_onto_screen() - label.set_color(function_graph.get_color()) - if animate: - self.play(Write(label)) - else: - self.add(label) - return label - -class AddTwoFunctions(FunctionGraphScene): - def construct(self): - f_graph = self.get_function_graph() - g_graph = self.get_function_graph() - def sum_func(x): - return f_graph.get_function()(x)+g_graph.get_function()(x) - sum_graph = self.get_function_graph(sum_func, animate = False) - self.remove(sum_graph) - f_label = self.label_graph(f_graph) - g_label = self.label_graph(g_graph) - - f_lines = self.get_output_lines(f_graph) - g_lines = self.get_output_lines(g_graph) - sum_lines = self.get_output_lines(sum_graph, nudge = False) - - curr_x_point = f_lines[0].get_start() - sum_def = self.get_sum_definition(DecimalNumber(curr_x_point[0])) - # sum_def.set_width(FRAME_X_RADIUS-1) - sum_def.to_corner(UP+LEFT) - arrow = Arrow(sum_def[2].get_bottom(), curr_x_point, color = WHITE) - prefix = sum_def[0] - suffix = VGroup(*sum_def[1:]) - rect = BackgroundRectangle(sum_def) - brace = Brace(prefix) - brace.add(brace.get_text("New function").shift_onto_screen()) - - self.play( - Write(prefix, run_time = 2), - FadeIn(brace) - ) - self.wait() - for lines in f_lines, g_lines: - self.add_lines(lines) - self.play(*list(map(FadeOut, [f_graph, g_graph]))) - self.wait() - self.play(FadeOut(brace)) - fg_group = VGroup(*list(f_label)+list(g_label)) - self.play( - FadeIn(rect), - Animation(prefix), - Transform(fg_group, suffix), - ) - self.remove(prefix, fg_group) - self.add(sum_def) - self.play(ShowCreation(arrow)) - - self.show_line_addition(f_lines[0], g_lines[0], sum_lines[0]) - self.wait() - - curr_x_point = f_lines[1].get_start() - new_sum_def = self.get_sum_definition(DecimalNumber(curr_x_point[0])) - new_sum_def.to_corner(UP+LEFT) - new_arrow = Arrow(sum_def[2].get_bottom(), curr_x_point, color = WHITE) - self.play( - Transform(sum_def, new_sum_def), - Transform(arrow, new_arrow), - ) - self.show_line_addition(f_lines[1], g_lines[1], sum_lines[1]) - self.wait() - - final_sum_def = self.get_sum_definition(TexMobject("x")) - final_sum_def.to_corner(UP+LEFT) - self.play( - FadeOut(rect), - Transform(sum_def, final_sum_def), - FadeOut(arrow) - ) - self.show_line_addition(*it.starmap(VGroup, [ - f_lines[2:], g_lines[2:], sum_lines[2:] - ])) - self.play(ShowCreation(sum_graph)) - - def get_sum_definition(self, input_mob): - result = VGroup(*it.chain( - TexMobject("(f+g)", "("), - [input_mob.copy()], - TexMobject(")", "=", "f("), - [input_mob.copy()], - TexMobject(")", "+", "g("), - [input_mob.copy()], - TexMobject(")") - )) - result.arrange() - result[0].set_color(self.graph_colors[2]) - VGroup(result[5], result[7]).set_color(self.graph_colors[0]) - VGroup(result[9], result[11]).set_color(self.graph_colors[1]) - return result - - - def show_line_addition(self, f_lines, g_lines, sum_lines): - g_lines.target = g_lines.copy() - dots = VGroup() - dots.target = VGroup() - for f_line, g_line in zip(f_lines, g_lines.target): - align_perfectly = f_line.get_end()[1]*g_line.get_end()[1] > 0 - dot = Dot(g_line.get_end(), radius = 0.07) - g_line.shift(f_line.get_end()-g_line.get_start()) - dot.target = Dot(g_line.get_end()) - if not align_perfectly: - g_line.shift(self.line_to_line_buff*RIGHT) - dots.add(dot) - dots.target.add(dot.target) - for group in dots, dots.target: - group.set_color(sum_lines[0].get_color()) - self.play(ShowCreation(dots)) - if len(list(g_lines)) == 1: - kwargs = {} - else: - kwargs = { - "lag_ratio" : 0.5, - "run_time" : 3 - } - self.play(*[ - MoveToTarget(mob, **kwargs) - for mob in (g_lines, dots) - ]) - # self.play( - # *[mob.fade for mob in g_lines, f_lines]+[ - # Animation(dots) - # ]) - self.wait() - -class AddVectorsCoordinateByCoordinate(Scene): - def construct(self): - v1 = Matrix(["x_1", "y_1", "z_1"]) - v2 = Matrix(["x_2", "y_2", "z_2"]) - v_sum = Matrix(["x_1 + x_2", "y_1 + y_2", "z_1 + z_2"]) - for v in v1, v2, v_sum: - v.get_entries()[0].set_color(X_COLOR) - v.get_entries()[1].set_color(Y_COLOR) - v.get_entries()[2].set_color(Z_COLOR) - plus, equals = TexMobject("+=") - VGroup(v1, plus, v2, equals, v_sum).arrange() - - self.add(v1, plus, v2) - self.wait() - self.play( - Write(equals), - Write(v_sum.get_brackets()) - ) - self.play( - Transform(v1.get_entries().copy(), v_sum.get_entries()), - Transform(v2.get_entries().copy(), v_sum.get_entries()), - ) - self.wait() - -class ScaleFunction(FunctionGraphScene): - def construct(self): - graph = self.get_function_graph( - lambda x : self.default_functions[0](x), - animate = False - ) - scaled_graph = self.get_function_graph( - lambda x : graph.get_function()(x)*2, - animate = False, add = False - ) - graph_lines = self.get_output_lines(graph) - scaled_lines = self.get_output_lines(scaled_graph, nudge = False) - - f_label = self.label_graph(graph, "f", animate = False) - two_f_label = self.label_graph(scaled_graph, "(2f)", animate = False) - self.remove(two_f_label) - - title = TexMobject("(2f)", "(x) = 2", "f", "(x)") - title.set_color_by_tex("(2f)", scaled_graph.get_color()) - title.set_color_by_tex("f", graph.get_color()) - title.next_to(ORIGIN, LEFT, buff = MED_SMALL_BUFF) - title.to_edge(UP) - self.add(title) - - self.add_lines(graph_lines) - self.wait() - self.play(Transform(graph_lines, scaled_lines)) - self.play(ShowCreation(scaled_graph)) - self.play(Write(two_f_label)) - self.play(FadeOut(graph_lines)) - self.wait() - -class ScaleVectorByCoordinates(Scene): - def construct(self): - two, dot, equals = TexMobject("2 \\cdot =") - v1 = Matrix(list("xyz")) - v1.get_entries().set_color_by_gradient(X_COLOR, Y_COLOR, Z_COLOR) - v2 = v1.copy() - two_targets = VGroup(*[ - two.copy().next_to(entry, LEFT) - for entry in v2.get_entries() - ]) - v2.get_brackets()[0].next_to(two_targets, LEFT) - v2.add(two_targets) - VGroup(two, dot, v1, equals, v2).arrange() - - self.add(two, dot, v1) - self.play( - Write(equals), - Write(v2.get_brackets()) - ) - self.play( - Transform(two.copy(), two_targets), - Transform(v1.get_entries().copy(), v2.get_entries()) - ) - self.wait() - -class ShowSlopes(Animation): - CONFIG = { - "line_color" : YELLOW, - "dx" : 0.01, - "rate_func" : None, - "run_time" : 5 - } - def __init__(self, graph, **kwargs): - digest_config(self, kwargs, locals()) - line = Line(LEFT, RIGHT, color = self.line_color) - line.save_state() - Animation.__init__(self, line, **kwargs) - - def interpolate_mobject(self, alpha): - f = self.graph.point_from_proportion - low, high = list(map(f, np.clip([alpha-self.dx, alpha+self.dx], 0, 1))) - slope = (high[1]-low[1])/(high[0]-low[0]) - self.mobject.restore() - self.mobject.rotate(np.arctan(slope)) - self.mobject.move_to(f(alpha)) - -class FromVectorsToFunctions(VectorScene): - def construct(self): - self.show_vector_addition_and_scaling() - self.bring_in_functions() - self.show_derivative() - - def show_vector_addition_and_scaling(self): - self.plane = self.add_plane() - self.plane.fade() - words1 = TextMobject("Vector", "addition") - words2 = TextMobject("Vector", "scaling") - for words in words1, words2: - words.add_background_rectangle() - words.next_to(ORIGIN, RIGHT).to_edge(UP) - self.add(words1) - - v = self.add_vector([2, -1], color = MAROON_B) - w = self.add_vector([3, 2], color = YELLOW) - w.save_state() - self.play(w.shift, v.get_end()) - vw_sum = self.add_vector(w.get_end(), color = PINK) - self.wait() - self.play( - Transform(words1, words2), - FadeOut(vw_sum), - w.restore - ) - self.add( - v.copy().fade(), - w.copy().fade() - ) - self.play(v.scale, 2) - self.play(w.scale, -0.5) - self.wait() - - def bring_in_functions(self): - everything = VGroup(*self.get_mobjects()) - axes = Axes() - axes.shift(FRAME_WIDTH*LEFT) - - fg_scene_config = FunctionGraphScene.CONFIG - graph = FunctionGraph(fg_scene_config["default_functions"][0]) - graph.set_color(MAROON_B) - func_tex = TexMobject("\\frac{1}{9}x^3 - x") - func_tex.set_color(graph.get_color()) - func_tex.shift(5.5*RIGHT+2*UP) - - words = VGroup(*[ - TextMobject(words).add_background_rectangle() - for words in [ - "Linear transformations", - "Null space", - "Dot products", - "Eigen-everything", - ] - ]) - words.set_color_by_gradient(BLUE_B, BLUE_D) - words.arrange(DOWN, aligned_edge = LEFT) - words.to_corner(UP+LEFT) - self.play(FadeIn( - words, - lag_ratio = 0.5, - run_time = 3 - )) - self.wait() - self.play(*[ - ApplyMethod(mob.shift, FRAME_WIDTH*RIGHT) - for mob in (axes, everything) - ] + [Animation(words)] - ) - self.play(ShowCreation(graph), Animation(words)) - self.play(Write(func_tex, run_time = 2)) - self.wait(2) - - top_word = words[0] - words.remove(top_word) - self.play( - FadeOut(words), - top_word.shift, top_word.get_center()[0]*LEFT - ) - self.wait() - self.func_tex = func_tex - self.graph = graph - - def show_derivative(self): - func_tex, graph = self.func_tex, self.graph - new_graph = FunctionGraph(lambda x : (x**2)/3.-1) - new_graph.set_color(YELLOW) - - func_tex.generate_target() - lp, rp = parens = TexMobject("()") - parens.set_height(func_tex.get_height()) - L, equals = TexMobject("L=") - deriv = TexMobject("\\frac{d}{dx}") - new_func = TexMobject("\\frac{1}{3}x^2 - 1") - new_func.set_color(YELLOW) - group = VGroup( - L, lp, func_tex.target, rp, - equals, new_func - ) - group.arrange() - group.shift(2*UP).to_edge(LEFT, buff = MED_LARGE_BUFF) - rect = BackgroundRectangle(group) - group.add_to_back(rect) - deriv.move_to(L, aligned_edge = RIGHT) - - self.play( - MoveToTarget(func_tex), - *list(map(Write, [L, lp, rp, equals, new_func])) - ) - self.remove(func_tex) - self.add(func_tex.target) - self.wait() - faded_graph = graph.copy().fade() - self.add(faded_graph) - self.play( - Transform(graph, new_graph, run_time = 2), - Animation(group) - ) - self.wait() - self.play(Transform(L, deriv)) - self.play(ShowSlopes(faded_graph)) - self.wait() - -class TransformationsAndOperators(TeacherStudentsScene): - def construct(self): - self.student_says(""" - Are these the same - as ``linear operators''? - """, student_index = 0) - self.random_blink() - teacher = self.get_teacher() - bubble = teacher.get_bubble(SpeechBubble, height = 2, width = 2) - bubble.set_fill(BLACK, opacity = 1) - bubble.write("Yup!") - self.play( - teacher.change_mode, "hooray", - ShowCreation(bubble), - Write(bubble.content, run_time = 1) - ) - self.random_blink(2) - -class ManyFunctions(FunctionGraphScene): - def construct(self): - randy = Randolph().to_corner(DOWN+LEFT) - self.add(randy) - for i in range(100): - if i < 3: - run_time = 1 - self.wait() - elif i < 10: - run_time = 0.4 - else: - run_time = 0.2 - added_anims = [] - if i == 3: - added_anims = [randy.change_mode, "confused"] - if i == 10: - added_anims = [randy.change_mode, "pleading"] - self.add_random_function( - run_time = run_time, - added_anims = added_anims - ) - - def add_random_function(self, run_time = 1, added_anims = []): - coefs = np.random.randint(-3, 3, np.random.randint(3, 8)) - def func(x): - return sum([c*x**(i) for i, c, in enumerate(coefs)]) - graph = self.get_function_graph(func, animate = False) - if graph.get_height() > FRAME_HEIGHT: - graph.stretch_to_fit_height(FRAME_HEIGHT) - graph.shift(graph.point_from_proportion(0.5)[1]*DOWN) - graph.shift(interpolate(-3, 3, random.random())*UP) - graph.set_color(random_bright_color()) - self.play( - ShowCreation(graph, run_time = run_time), - *added_anims - ) - -class WhatDoesLinearMean(TeacherStudentsScene): - def construct(self): - words = TextMobject(""" - What does it mean for - a transformation of functions - to be """, "linear", "?", - arg_separator = "" - ) - words.set_color_by_tex("linear", BLUE) - self.student_says(words) - self.change_student_modes("pondering") - self.random_blink(4) - -class FormalDefinitionOfLinear(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "include_background_plane" : False, - "t_matrix" : [[1, 1], [-0.5, 1]], - "w_coords" : [1, 1], - "v_coords" : [1, -2], - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_HEIGHT, - "secondary_line_ratio" : 1 - }, - } - def construct(self): - self.plane.fade() - self.write_properties() - self.show_additive_property() - self.show_scaling_property() - self.add_words() - - def write_properties(self): - title = TextMobject( - "Formal definition of linearity" - ) - title.add_background_rectangle() - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - - v_tex, w_tex = ["\\vec{\\textbf{%s}}"%s for s in "vw"] - tex_sets = [ - [ - ("\\text{Additivity: }",), - ("L(", v_tex, "+", w_tex, ")"), - ("=", "L(", v_tex, ")", "+", "L(", w_tex, ")"), - ], - [ - ("\\text{Scaling: }",), - ("L(", "c", v_tex, ")"), - ("=", "c", "L(", v_tex, ")"), - ], - ] - properties = VGroup() - for tex_set in tex_sets: - words = VGroup(*it.starmap(TexMobject, tex_set)) - for word in words: - word.set_color_by_tex(v_tex, YELLOW) - word.set_color_by_tex(w_tex, MAROON_B) - word.set_color_by_tex("c", GREEN) - words.arrange() - words.lhs = words[1] - words.rhs = words[2] - words.add_to_back(BackgroundRectangle(words)) - # words.scale(0.8) - properties.add(words) - properties.arrange(DOWN, aligned_edge = LEFT, buff = MED_SMALL_BUFF) - properties.next_to(h_line, DOWN, buff = MED_LARGE_BUFF).to_edge(LEFT) - - self.play(Write(title), ShowCreation(h_line)) - self.wait() - for words in properties: - self.play(Write(words)) - self.wait() - self.add_foreground_mobject(title, h_line, *properties) - self.additivity, self.scaling = properties - - def show_additive_property(self): - self.plane.save_state() - - v = self.add_vector(self.v_coords) - v_label = self.add_transformable_label(v, "v", direction = "right") - w = self.add_vector(self.w_coords, color = MAROON_B) - w_label = self.add_transformable_label(w, "w", direction = "left") - w_group = VGroup(w, w_label) - w_group.save_state() - self.play(w_group.shift, v.get_end()) - vw_sum = self.add_vector(w.get_end(), color = PINK) - v_label_copy, w_label_copy = v_label.copy(), w_label.copy() - v_label_copy.generate_target() - w_label_copy.generate_target() - plus = TexMobject("+") - vw_label = VGroup(v_label_copy.target, plus, w_label_copy.target) - vw_label.arrange() - vw_label.next_to(vw_sum.get_end(), RIGHT) - self.play( - MoveToTarget(v_label_copy), - MoveToTarget(w_label_copy), - Write(plus) - ) - vw_label_copy = vw_label.copy() - vw_label = VGroup( - VectorizedPoint(vw_label.get_left()), - vw_label, - VectorizedPoint(vw_label.get_right()), - ) - self.remove(v_label_copy, w_label_copy, plus) - self.add(vw_label) - self.play( - w_group.restore, - ) - vw_label.target = VGroup( - TexMobject("L(").scale(0.8), - vw_label_copy, - TexMobject(")").scale(0.8), - ) - vw_label.target.arrange() - for mob in vw_label, vw_label.target: - mob.add_to_back(BackgroundRectangle(mob)) - - transform = self.get_matrix_transformation(self.t_matrix) - point = transform(vw_sum.get_end()) - vw_label.target.next_to(point, UP) - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [MoveToTarget(vw_label)] - ) - self.wait() - self.play(w_group.shift, v.get_end()) - v_label_copy, w_label_copy = v_label.copy(), w_label.copy() - v_label_copy.generate_target() - w_label_copy.generate_target() - equals, plus = TexMobject("=+") - rhs = VGroup( - equals, v_label_copy.target, - plus, w_label_copy.target - ) - rhs.arrange() - rhs.next_to(vw_label, RIGHT) - rect = BackgroundRectangle(rhs) - self.play(*it.chain( - list(map(Write, [rect, equals, plus])), - list(map(MoveToTarget, [v_label_copy, w_label_copy])), - )) - to_fade = [self.plane, v, v_label, w_group, vw_label, vw_sum] - to_fade += self.get_mobjects_from_last_animation() - - self.wait() - self.play(*it.chain( - list(map(FadeOut, to_fade)), - list(map(Animation, self.foreground_mobjects)) - )) - self.plane.restore() - self.play(FadeIn(self.plane), *list(map(Animation, self.foreground_mobjects))) - self.transformable_mobjects = [] - self.moving_vectors = [] - self.transformable_labels = [] - self.moving_mobjects = [] - self.add_transformable_mobject(self.plane) - self.add(*self.foreground_mobjects) - - def show_scaling_property(self): - v = self.add_vector([1, -1]) - v_label = self.add_transformable_label(v, "v") - scaled_v = v.copy().scale(2) - scaled_v_label = TexMobject("c\\vec{\\textbf{v}}") - scaled_v_label.set_color(YELLOW) - scaled_v_label[0].set_color(GREEN) - scaled_v_label.next_to(scaled_v.get_end(), RIGHT) - scaled_v_label.add_background_rectangle() - v_copy, v_label_copy = v.copy(), v_label.copy() - self.play( - Transform(v_copy, scaled_v), - Transform(v_label_copy, scaled_v_label), - ) - self.remove(v_copy, v_label_copy) - self.add(scaled_v_label) - self.add_vector(scaled_v, animate = False) - self.wait() - - transform = self.get_matrix_transformation(self.t_matrix) - point = transform(scaled_v.get_end()) - scaled_v_label.target = TexMobject("L(", "c", "\\vec{\\textbf{v}}", ")") - scaled_v_label.target.set_color_by_tex("c", GREEN) - scaled_v_label.target.set_color_by_tex("\\vec{\\textbf{v}}", YELLOW) - scaled_v_label.target.scale(0.8) - scaled_v_label.target.next_to(point, RIGHT) - scaled_v_label.target.add_background_rectangle() - - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [MoveToTarget(scaled_v_label)] - ) - self.wait() - scaled_v = v.copy().scale(2) - rhs = TexMobject("=", "c", "L(", "\\vec{\\textbf{v}}", ")") - rhs.set_color_by_tex("c", GREEN) - rhs.set_color_by_tex("\\vec{\\textbf{v}}", YELLOW) - rhs.add_background_rectangle() - rhs.scale(0.8) - rhs.next_to(scaled_v_label, RIGHT) - v_copy = v.copy() - self.add(v_copy) - self.play(Transform(v, scaled_v)) - self.play(Write(rhs)) - self.wait() - faders = [ - scaled_v_label, scaled_v, v_copy, - v, rhs - ] + self.transformable_labels + self.moving_vectors - self.play(*list(map(FadeOut, faders))) - - def add_words(self): - randy = Randolph().shift(LEFT).to_edge(DOWN) - bubble = randy.get_bubble(SpeechBubble, width = 6, height = 4) - bubble.set_fill(BLACK, opacity = 0.8) - bubble.shift(0.5*DOWN) - VGroup(randy, bubble).to_edge(RIGHT, buff = 0) - words = TextMobject( - "Linear transformations\\\\", - "preserve", - "addition and \\\\ scalar multiplication", - ) - words.scale(0.9) - words.set_color_by_tex("preserve", YELLOW) - bubble.add_content(words) - - self.play(FadeIn(randy)) - self.play( - ShowCreation(bubble), - Write(words), - randy.change_mode, "speaking", - ) - self.play(Blink(randy)) - self.wait() - -class CalcStudentsKnowThatDerivIsLinear(TeacherStudentsScene): - def construct(self): - words = TextMobject( - """Calc students subconsciously - know that""", - "$\\dfrac{d}{dx}$", - "is linear" - ) - words.set_color_by_tex("$\\dfrac{d}{dx}$", BLUE) - self.teacher_says(words) - self.change_student_modes( - "pondering", "confused", "erm" - ) - self.random_blink(3) - -class DerivativeIsLinear(Scene): - def construct(self): - self.add_title() - self.prepare_text() - self.show_additivity() - self.show_scaling() - - def add_title(self): - title = TextMobject("Derivative is linear") - title.to_edge(UP) - self.add(title) - - def prepare_text(self): - v_tex, w_tex = ["\\vec{\\textbf{%s}}"%s for s in "vw"] - additivity = TexMobject( - "L(", v_tex, "+", w_tex, ")", "=", - "L(", v_tex, ")+L(", w_tex, ")" - ) - scaling = TexMobject( - "L(", "c", v_tex, ")=", "c", "L(", v_tex, ")" - ) - for text in additivity, scaling: - text.set_color_by_tex(v_tex, YELLOW) - text.set_color_by_tex(w_tex, MAROON_B) - text.set_color_by_tex("c", GREEN) - - deriv_tex = "\\dfrac{d}{dx}" - deriv_additivity = TexMobject( - deriv_tex, "(", "x^3", "+", "x^2", ")", "=", - deriv_tex, "(", "x^3", ")", "+", - deriv_tex, "(", "x^2", ")" - ) - deriv_scaling = TexMobject( - deriv_tex, "(", "4", "x^3", ")", "=", - "4", deriv_tex, "(", "x^3", ")" - ) - for text in deriv_additivity, deriv_scaling: - text.set_color_by_tex("x^3", YELLOW) - text.set_color_by_tex("x^2", MAROON_B) - text.set_color_by_tex("4", GREEN) - - self.additivity = additivity - self.scaling = scaling - self.deriv_additivity = deriv_additivity - self.deriv_scaling = deriv_scaling - - def show_additivity(self): - general, deriv = self.additivity, self.deriv_additivity - group = VGroup(general, deriv ) - group.arrange(DOWN, buff = 1.5) - - inner_sum = VGroup(*deriv[2:2+3]) - outer_sum_deriv = VGroup(deriv[0], deriv[1], deriv[5]) - inner_func1 = deriv[9] - outer_deriv1 = VGroup(deriv[7], deriv[8], deriv[10]) - plus = deriv[11] - inner_func2 = deriv[14] - outer_deriv2 = VGroup(deriv[12], deriv[13], deriv[15]) - - self.play(FadeIn(group)) - self.wait() - self.point_out(inner_sum) - self.point_out(outer_sum_deriv) - self.wait() - self.point_out(outer_deriv1, outer_deriv2) - self.point_out(inner_func1, inner_func2) - self.point_out(plus) - self.wait() - self.play(FadeOut(group)) - - def show_scaling(self): - general, deriv = self.scaling, self.deriv_scaling - group = VGroup(general, deriv) - group.arrange(DOWN, buff = 1.5) - - inner_scaling = VGroup(*deriv[2:4]) - lhs_deriv = VGroup(deriv[0], deriv[1], deriv[4]) - rhs_deriv = VGroup(*deriv[7:]) - outer_scaling = deriv[6] - - self.play(FadeIn(group)) - self.wait() - self.point_out(inner_scaling) - self.point_out(lhs_deriv) - self.wait() - self.point_out(rhs_deriv) - self.point_out(outer_scaling) - self.wait() - - def point_out(self, *terms): - anims = [] - for term in terms: - anims += [ - term.scale_in_place, 1.2, - term.set_color, RED, - ] - self.play( - *anims, - run_time = 1, - rate_func = there_and_back - ) - -class ProposeDerivativeAsMatrix(TeacherStudentsScene): - def construct(self): - self.teacher_says( - """ - Let's describe the - derivative with - a matrix - """, - target_mode = "hooray" - ) - self.random_blink() - self.change_student_modes("pondering", "confused", "erm") - self.random_blink(3) - -class PolynomialsHaveArbitrarilyLargeDegree(Scene): - def construct(self): - polys = VGroup(*list(map(TexMobject, [ - "x^{300} + 9x^2", - "4x^{4{,}000{,}000{,}000} + 1", - "3x^{\\left(10^{100}\\right)}", - "\\vdots" - ]))) - polys.set_color_by_gradient(BLUE_B, BLUE_D) - polys.arrange(DOWN, buff = MED_LARGE_BUFF) - polys.scale(1.3) - - arrow = TexMobject("\\Rightarrow").scale(1.5) - - brace = Brace( - Line(UP, DOWN).scale(FRAME_Y_RADIUS).shift(FRAME_X_RADIUS*RIGHT), - LEFT - ) - words = TextMobject("Infinitely many") - words.scale(1.5) - words.next_to(brace, LEFT) - arrow.next_to(words, LEFT) - polys.next_to(arrow, LEFT) - - self.play(Write(polys)) - self.wait() - self.play( - FadeIn(arrow), - Write(words), - GrowFromCenter(brace) - ) - self.wait() - -class GeneneralPolynomialCoordinates(Scene): - def construct(self): - poly = TexMobject( - "a_n", "x^n", "+", - "a_{n-1}", "x^{n-1}", "+", - "\\cdots", - "a_1", "x", "+", - "a_0", - ) - poly.set_color_by_tex("a_n", YELLOW) - poly.set_color_by_tex("a_{n-1}", MAROON_B) - poly.set_color_by_tex("a_1", RED) - poly.set_color_by_tex("a_0", GREEN) - poly.scale(1.3) - - array = Matrix( - ["a_0", "a_1", "\\vdots", "a_{n-1}", "a_n", "0", "\\vdots"] - ) - array.get_entries()[0].set_color(GREEN) - array.get_entries()[1].set_color(RED) - array.get_entries()[3].set_color(MAROON_B) - array.get_entries()[4].set_color(YELLOW) - array.scale(1.2) - - equals = TexMobject("=").scale(1.3) - group = VGroup(poly, equals, array) - group.arrange() - group.to_edge(RIGHT) - - pre_entries = VGroup( - poly[-1], poly[-4], poly[-5], - poly[3], poly[0], - VectorizedPoint(poly.get_left()), - VectorizedPoint(poly.get_left()), - ) - - self.add(poly, equals, array.get_brackets()) - self.wait() - self.play( - Transform(pre_entries.copy(), array.get_entries()) - ) - self.wait() - -class SimplePolynomialCoordinates(Scene): - def construct(self): - matrix = Matrix(["5", "3", "1", "0", "\\vdots"]) - matrix.to_edge(LEFT) - self.play(Write(matrix)) - self.wait() - -class IntroducePolynomialSpace(Scene): - def construct(self): - self.add_title() - self.show_polynomial_cloud() - self.split_individual_polynomial() - self.list_basis_functions() - self.show_example_coordinates() - self.derivative_as_matrix() - - def add_title(self): - title = TextMobject("Our current space: ", "All polynomials") - title.to_edge(UP) - title[1].set_color(BLUE) - self.play(Write(title)) - self.wait() - self.title = title - - def show_polynomial_cloud(self): - cloud = ThoughtBubble()[-1] - cloud.stretch_to_fit_height(6) - cloud.center() - - - polys = VGroup( - TexMobject("x^2", "+", "3", "x", "+", "5"), - TexMobject("4x^7-5x^2"), - TexMobject("x^{100}+2x^{99}+3x^{98}"), - TexMobject("3x-7"), - TexMobject("x^{1{,}000{,}000{,}000}+1"), - TexMobject("\\vdots"), - ) - polys.set_color_by_gradient(BLUE_B, BLUE_D) - polys.arrange(DOWN, buff = MED_SMALL_BUFF) - polys.next_to(cloud.get_top(), DOWN, buff = MED_LARGE_BUFF) - - self.play(ShowCreation(cloud)) - for poly in polys: - self.play(Write(poly), run_time = 1) - self.wait() - self.poly1, self.poly2 = polys[0], polys[1] - polys.remove(self.poly1) - self.play( - FadeOut(cloud), - FadeOut(polys), - self.poly1.next_to, ORIGIN, LEFT, - self.poly1.set_color, WHITE - ) - - def split_individual_polynomial(self): - leading_coef = TexMobject("1") - leading_coef.next_to(self.poly1[0], LEFT, aligned_edge = DOWN) - self.poly1.add_to_back(leading_coef) - one = TexMobject("\\cdot", "1") - one.next_to(self.poly1[-1], RIGHT, aligned_edge = DOWN) - self.poly1.add(one) - for mob in leading_coef, one: - mob.set_color(BLACK) - - brace = Brace(self.poly1) - brace.text = brace.get_text("Already written as \\\\ a linear combination") - - index_to_color = { - 0 : WHITE, - 1 : Z_COLOR, - 4 : Y_COLOR, - 7 : X_COLOR, - } - self.play( - GrowFromCenter(brace), - Write(brace.text), - *[ - ApplyMethod(self.poly1[index].set_color, color) - for index, color in list(index_to_color.items()) - ] - ) - self.wait() - self.brace = brace - - def list_basis_functions(self): - title = TextMobject("Basis functions") - title.next_to(self.title, DOWN, buff = MED_SMALL_BUFF) - title.to_edge(RIGHT) - h_line = Line(ORIGIN, RIGHT).scale(title.get_width()) - h_line.next_to(title, DOWN) - - x_cubed = TexMobject("x^3") - x_cubed.set_color(MAROON_B) - x_cubed.to_corner(DOWN+RIGHT).shift(2*(DOWN+RIGHT)) - basis_group = VGroup( - self.poly1[7][1], - self.poly1[4], - self.poly1[1], - x_cubed - ).copy() - basis_group.generate_target() - basis_group.target.arrange( - DOWN, buff = 0.75*LARGE_BUFF, aligned_edge = LEFT - ) - basis_group.target.to_edge(RIGHT, buff = MED_LARGE_BUFF) - dots = TexMobject("\\vdots") - dots.next_to(basis_group.target, DOWN, buff = MED_SMALL_BUFF, aligned_edge = LEFT) - - basis_functions = [ - TexMobject("b_%d(x)"%i, "=") - for i in range(len(list(basis_group))) - ] - for basis_func, term in zip(basis_functions, basis_group.target): - basis_func.set_color(term.get_color()) - basis_func.next_to(term, LEFT) - for i in 2, 3: - basis_functions[i].shift(SMALL_BUFF*DOWN) - - self.play( - FadeIn(title), - ShowCreation(h_line), - MoveToTarget(basis_group), - Write(dots) - ) - for basis_func in basis_functions: - self.play(Write(basis_func, run_time = 1)) - self.play(Write(dots)) - self.wait() - self.basis = basis_group - self.basis_functions = basis_functions - - def show_example_coordinates(self): - coords = Matrix(["5", "3", "1", "0", "0", "\\vdots"]) - for i, color in enumerate([X_COLOR, Y_COLOR, Z_COLOR]): - coords[i].set_color(color) - self.poly1.generate_target() - equals = TexMobject("=").next_to(coords, LEFT) - self.poly1.target.next_to(equals, LEFT) - entries = coords.get_entries() - entries.save_state() - entries.set_fill(opacity = 0) - - self.play( - MoveToTarget(self.poly1), - Write(equals), - FadeOut(self.brace), - FadeOut(self.brace.text) - ) - for entry, index in zip(entries, [6, 3, 0]): - entry.move_to(self.poly1[index]) - self.play(Write(coords.get_brackets())) - self.play( - entries.restore, - lag_ratio = 0.5, - run_time = 3 - ) - self.wait() - target = self.poly1.copy() - terms = [ - VGroup(*target[6:8]), - VGroup(target[5], *target[3:5]), - VGroup(target[2], *target[0:2]), - ] - target[5].next_to(target[3], LEFT) - target[2].next_to(target[0], LEFT) - more_terms = [ - TexMobject("+0", "x^3").set_color_by_tex("x^3", MAROON_B), - TexMobject("+0", "x^4").set_color_by_tex("x^4", YELLOW), - TexMobject("\\vdots") - ] - for entry, term in zip(entries, terms+more_terms): - term.next_to(entry, LEFT, buff = LARGE_BUFF) - more_terms[-1].shift(MED_SMALL_BUFF*LEFT) - - self.play(Transform(self.poly1, target)) - self.wait() - self.play(FadeIn( - VGroup(*more_terms), - lag_ratio = 0.5, - run_time = 2 - )) - self.wait() - - self.play(*list(map(FadeOut, [self.poly1]+more_terms))) - self.poly2.next_to(equals, LEFT) - self.poly2.shift(MED_SMALL_BUFF*UP) - self.poly2.set_color(WHITE) - self.poly2[0].set_color(TEAL) - VGroup(*self.poly2[3:5]).set_color(Z_COLOR) - new_coords = Matrix(["0", "0", "-5", "0", "0", "0", "0", "4", "\\vdots"]) - new_coords.get_entries()[2].set_color(Z_COLOR) - new_coords.get_entries()[7].set_color(TEAL) - new_coords.set_height(6) - new_coords.move_to(coords, aligned_edge = LEFT) - self.play( - Write(self.poly2), - Transform(coords, new_coords) - ) - self.wait() - for i, mob in (2, VGroup(*self.poly2[3:5])), (7, self.poly2[0]): - self.play( - new_coords.get_entries()[i].scale_in_place, 1.3, - mob.scale_in_place, 1.3, - rate_func = there_and_back - ) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(self.poly2) - self.wait() - self.play(*list(map(FadeOut, [self.poly2, coords, equals]))) - - def derivative_as_matrix(self): - matrix = Matrix([ - [ - str(j) if j == i+1 else "0" - for j in range(4) - ] + ["\\cdots"] - for i in range(4) - ] + [ - ["\\vdots"]*4 + ["\\ddots"] - ]) - matrix.shift(2*LEFT) - diag_entries = VGroup(*[ - matrix.get_mob_matrix()[i, i+1] - for i in range(3) - ]) - ##Horrible - last_col = VGroup(*matrix.get_mob_matrix()[:,-1]) - last_col_top = last_col.get_top() - last_col.arrange(DOWN, buff = 0.83) - last_col.move_to(last_col_top, aligned_edge = UP+RIGHT) - ##End horrible - matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR, MAROON_B) - - deriv = TexMobject("\\dfrac{d}{dx}") - equals = TexMobject("=") - equals.next_to(matrix, LEFT) - deriv.next_to(equals, LEFT) - - self.play(FadeIn(deriv), FadeIn(equals)) - self.play(Write(matrix)) - self.wait() - diag_entries.save_state() - diag_entries.generate_target() - diag_entries.target.scale_in_place(1.2) - diag_entries.target.set_color(YELLOW) - for anim in MoveToTarget(diag_entries), diag_entries.restore: - self.play( - anim, - lag_ratio = 0.5, - run_time = 1.5, - ) - self.wait() - matrix.generate_target() - matrix.target.to_corner(DOWN+LEFT).shift(0.25*UP) - deriv.generate_target() - deriv.target.next_to( - matrix.target, UP, - buff = MED_SMALL_BUFF, - aligned_edge = LEFT - ) - deriv.target.shift(0.25*RIGHT) - self.play( - FadeOut(equals), - *list(map(MoveToTarget, [matrix, deriv])) - ) - - poly = TexMobject( - "(", "1", "x^3", "+", - "5", "x^2", "+", - "4", "x", "+", - "5", ")" - ) - coefs = VGroup(*np.array(poly)[[10, 7, 4, 1]]) - VGroup(*poly[1:3]).set_color(MAROON_B) - VGroup(*poly[4:6]).set_color(Z_COLOR) - VGroup(*poly[7:9]).set_color(Y_COLOR) - VGroup(*poly[10:11]).set_color(X_COLOR) - poly.next_to(deriv) - self.play(FadeIn(poly)) - - array = Matrix(list(coefs.copy()) + [TexMobject("\\vdots")]) - array.next_to(matrix, RIGHT) - self.play(Write(array.get_brackets())) - to_remove = [] - for coef, entry in zip(coefs, array.get_entries()): - self.play(Transform(coef.copy(), entry)) - to_remove += self.get_mobjects_from_last_animation() - self.play(Write(array.get_entries()[-1])) - to_remove += self.get_mobjects_from_last_animation() - self.remove(*to_remove) - self.add(array) - - eq1, eq2 = TexMobject("="), TexMobject("=") - eq1.next_to(poly) - eq2.next_to(array) - - poly_result = TexMobject( - "3", "x^2", "+", - "10", "x", "+", - "4" - ) - poly_result.next_to(eq1) - brace = Brace(poly_result, buff = 0) - - self.play(*list(map(Write, [eq1, eq2, brace]))) - - result_coefs = VGroup(*np.array(poly_result)[[6, 3, 0]]) - VGroup(*poly_result[0:2]).set_color(MAROON_B) - VGroup(*poly_result[3:5]).set_color(Z_COLOR) - VGroup(*poly_result[6:]).set_color(Y_COLOR) - result_terms = [ - VGroup(*poly_result[6:]), - VGroup(*poly_result[3:6]), - VGroup(*poly_result[0:3]), - ] - relevant_entries = VGroup(*array.get_entries()[1:4]) - dots = [TexMobject("\\cdot") for x in range(3)] - result_entries = [] - for entry, diag_entry, dot in zip(relevant_entries, diag_entries, dots): - entry.generate_target() - diag_entry.generate_target() - group = VGroup(diag_entry.target, dot, entry.target) - group.arrange() - result_entries.append(group) - result_array = Matrix( - result_entries + [ - TexMobject("0"), - TexMobject("\\vdots") - ] - ) - result_array.next_to(eq2) - - rects = [ - Rectangle( - color = YELLOW - ).replace( - VGroup(*matrix.get_mob_matrix()[i,:]), - stretch = True - ).stretch_in_place(1.1, 0).stretch_in_place(1.3, 1) - for i in range(3) - ] - vert_rect = Rectangle(color = YELLOW) - vert_rect.replace(array.get_entries(), stretch = True) - vert_rect.stretch_in_place(1.1, 1) - vert_rect.stretch_in_place(1.5, 0) - tuples = list(zip( - relevant_entries, - diag_entries, - result_entries, - rects, - result_terms, - coefs[1:] - )) - self.play(Write(result_array.get_brackets())) - for entry, diag_entry, result_entry, rect, result_term, coef in tuples: - self.play(FadeIn(rect), FadeIn(vert_rect)) - self.wait() - self.play( - entry.scale_in_place, 1.2, - diag_entry.scale_in_place, 1.2, - ) - diag_entry_target, dot, entry_target = result_entry - self.play( - Transform(entry.copy(), entry_target), - Transform(diag_entry.copy(), diag_entry_target), - entry.scale_in_place, 1/1.2, - diag_entry.scale_in_place, 1/1.2, - Write(dot) - ) - self.wait() - self.play(Transform(coef.copy(), VGroup(result_term))) - self.wait() - self.play(FadeOut(rect), FadeOut(vert_rect)) - self.play(*list(map(Write, result_array.get_entries()[3:]))) - self.wait() - -class MatrixVectorMultiplicationAndDerivative(TeacherStudentsScene): - def construct(self): - mv_mult = VGroup( - Matrix([[3, 1], [0, 2]]).set_column_colors(X_COLOR, Y_COLOR), - Matrix(["x", "y"]).set_column_colors(YELLOW) - ) - mv_mult.arrange() - mv_mult.scale(0.75) - arrow = TexMobject("\\Leftrightarrow") - deriv = TexMobject("\\dfrac{df}{dx}") - group = VGroup(mv_mult, arrow, deriv) - group.arrange(buff = MED_SMALL_BUFF) - arrow.set_color(BLACK) - - teacher = self.get_teacher() - bubble = teacher.get_bubble(SpeechBubble, height = 4) - bubble.add_content(group) - - self.play( - teacher.change_mode, "speaking", - ShowCreation(bubble), - Write(group) - ) - self.random_blink() - group.generate_target() - group.target.scale(0.8) - words = TextMobject("Linear transformations") - h_line = Line(ORIGIN, RIGHT).scale(words.get_width()) - h_line.next_to(words, DOWN) - group.target.next_to(h_line, DOWN, buff = MED_SMALL_BUFF) - group.target[1].set_color(WHITE) - new_group = VGroup(words, h_line, group.target) - bubble.add_content(new_group) - - self.play( - MoveToTarget(group), - ShowCreation(h_line), - Write(words), - self.get_teacher().change_mode, "hooray" - ) - self.change_student_modes(*["pondering"]*3) - self.random_blink(3) - -class CompareTermsInLinearAlgebraToFunction(Scene): - def construct(self): - l_title = TextMobject("Linear algebra \\\\ concepts") - r_title = TextMobject("Alternate names when \\\\ applied to functions") - for title, vect in (l_title, LEFT), (r_title, RIGHT): - title.to_edge(UP) - title.shift(vect*FRAME_X_RADIUS/2) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.shift( - VGroup(l_title, r_title).get_bottom()[1]*UP + SMALL_BUFF*DOWN - ) - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - VGroup(h_line, v_line).set_color(BLUE) - - self.add(l_title, r_title) - self.play(*list(map(ShowCreation, [h_line, v_line]))) - self.wait() - - lin_alg_concepts = VGroup(*list(map(TextMobject, [ - "Linear transformations", - "Dot products", - "Eigenvectors", - ]))) - function_concepts = VGroup(*list(map(TextMobject, [ - "Linear operators", - "Inner products", - "Eigenfunctions", - ]))) - for concepts, vect in (lin_alg_concepts, LEFT), (function_concepts, RIGHT): - concepts.arrange(DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT) - concepts.next_to(h_line, DOWN, buff = LARGE_BUFF) - concepts.shift(vect*FRAME_X_RADIUS/2) - concepts.set_color_by_gradient(YELLOW_B, YELLOW_C) - - for concept in concepts: - self.play(Write(concept, run_time = 1)) - self.wait() - -class BackToTheQuestion(TeacherStudentsScene): - def construct(self): - self.student_says( - """ - Wait...so how does - this relate to what vectors - really are? - """, - target_mode = "confused" - ) - self.random_blink(2) - self.teacher_says( - """ - There are many different - vector-ish things - """ - ) - self.random_blink(2) - -class YouAsAMathematician(Scene): - def construct(self): - mathy = Mathematician() - mathy.to_corner(DOWN+LEFT) - words = TextMobject("You as a mathematician") - words.shift(2*UP) - arrow = Arrow(words.get_bottom(), mathy.get_corner(UP+RIGHT)) - bubble = mathy.get_bubble() - - equations = self.get_content() - bubble.add_content(equations) - - self.add(mathy) - self.play(Write(words, run_time = 2)) - self.play( - ShowCreation(arrow), - mathy.change_mode, "wave_1", - mathy.look, OUT - ) - self.play(Blink(mathy)) - self.wait() - self.play( - FadeOut(words), - FadeOut(arrow), - mathy.change_mode, "pondering", - ShowCreation(bubble), - ) - self.play(Write(equations)) - self.play(Blink(mathy)) - self.wait() - - bubble.write("Does this make any sense \\\\ for functions too?") - self.play( - equations.next_to, mathy.eyes, RIGHT, 2*LARGE_BUFF, - mathy.change_mode, "confused", - mathy.look, RIGHT, - Write(bubble.content) - ) - self.wait() - self.play(Blink(mathy)) - - def get_content(self): - v_tex = "\\vec{\\textbf{v}}" - eigen_equation = TexMobject("A", v_tex, "=", "\\lambda", v_tex) - v_ne_zero = TexMobject(v_tex, "\\ne \\vec{\\textbf{0}}") - det_equation = TexMobject("\\det(A-", "\\lambda", "I)=0") - arrow = TexMobject("\\Rightarrow") - - for tex in eigen_equation, v_ne_zero, det_equation: - tex.set_color_by_tex(v_tex, YELLOW) - tex.set_color_by_tex("\\lambda", MAROON_B) - - lhs = VGroup(eigen_equation, v_ne_zero) - lhs.arrange(DOWN) - group = VGroup(lhs, arrow, det_equation) - group.arrange(buff = MED_SMALL_BUFF) - return group - -class ShowVectorSpaces(Scene): - def construct(self): - title = TextMobject("Vector spaces") - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - - v_lines = [ - Line( - h_line.get_center(), FRAME_Y_RADIUS*DOWN - ).shift(vect*FRAME_X_RADIUS/3.) - for vect in (LEFT, RIGHT) - ] - vectors = self.get_vectors() - vectors.shift(LEFT*FRAME_X_RADIUS*(2./3)) - arrays = self.get_arrays() - functions = self.get_functions() - functions.shift(RIGHT*FRAME_X_RADIUS*(2./3)) - - self.add(h_line, *v_lines) - self.play(ShowCreation( - vectors, - run_time = 3 - )) - self.play(Write(arrays)) - self.play(Write(functions)) - self.wait() - self.play(Write(title)) - - def get_vectors(self, n_vectors = 10): - vectors = VGroup(*[ - Vector(RIGHT).scale(scalar).rotate(angle) - for scalar, angle in zip( - 2*np.random.random(n_vectors)+0.5, - np.linspace(0, 6, n_vectors) - ) - ]) - vectors.set_color_by_gradient(YELLOW, MAROON_B) - return vectors - - def get_arrays(self): - arrays = VGroup(*[ - VGroup(*[ - Matrix(np.random.randint(-9, 9, 2)) - for x in range(4) - ]) - for x in range(3) - ]) - for subgroup in arrays: - subgroup.arrange(DOWN, buff = MED_SMALL_BUFF) - arrays.arrange(RIGHT) - arrays.scale(0.7) - arrays.set_color_by_gradient(YELLOW, MAROON_B) - return arrays - - def get_functions(self): - axes = Axes() - axes.scale(0.3) - functions = VGroup(*[ - FunctionGraph(func, x_min = -4, x_max = 4) - for func in [ - lambda x : x**3 - 9*x, - lambda x : x**3 - 4*x, - lambda x : x**2 - 1, - ] - ]) - functions.stretch_to_fit_width(FRAME_X_RADIUS/2.) - functions.stretch_to_fit_height(6) - functions.set_color_by_gradient(YELLOW, MAROON_B) - functions.center() - return VGroup(axes, functions) - -class ToolsOfLinearAlgebra(Scene): - def construct(self): - words = VGroup(*list(map(TextMobject, [ - "Linear transformations", - "Null space", - "Eigenvectors", - "Dot products", - "$\\vdots$" - ]))) - words.arrange(DOWN, aligned_edge = LEFT, buff = MED_SMALL_BUFF) - words[-1].next_to(words[-2], DOWN) - self.play(FadeIn( - words, - lag_ratio = 0.5, - run_time = 3 - )) - self.wait() - -class MathematicianSpeakingToAll(Scene): - def construct(self): - mathy = Mathematician().to_corner(DOWN+LEFT) - others = VGroup(*[ - Randolph().flip().set_color(color) - for color in (BLUE_D, GREEN_E, GOLD_E, BLUE_C) - ]) - others.arrange() - others.scale(0.8) - others.to_corner(DOWN+RIGHT) - - bubble = mathy.get_bubble(SpeechBubble) - bubble.write(""" - I don't want to think - about all y'all's crazy - vector spaces - """) - - self.add(mathy, others) - self.play( - ShowCreation(bubble), - Write(bubble.content), - mathy.change_mode, "sassy", - mathy.look_at, others - ) - self.play(Blink(others[3])) - self.wait() - thought_bubble = mathy.get_bubble(ThoughtBubble) - self.play( - FadeOut(bubble.content), - Transform(bubble, thought_bubble), - mathy.change_mode, "speaking", - mathy.look_at, bubble, - *[ApplyMethod(pi.look_at, bubble) for pi in others] - ) - - vect = -bubble.get_bubble_center() - def func(point): - centered = point+vect - return 10*centered/get_norm(centered) - self.play(*[ - ApplyPointwiseFunction(func, mob) - for mob in self.get_mobjects() - ]) - -class ListAxioms(Scene): - def construct(self): - title = TextMobject("Rules for vectors addition and scaling") - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - self.add(title, h_line) - - u_tex, v_tex, w_tex = ["\\vec{\\textbf{%s}}"%s for s in "uvw"] - axioms = VGroup(*it.starmap(TexMobject, [ - ( - "1. \\,", - u_tex, "+", "(", v_tex, "+", w_tex, ")=(", - u_tex, "+", v_tex, ")+", w_tex - ), - ( "2. \\,", - v_tex, "+", w_tex, "=", w_tex, "+", v_tex - ), - ( - "3. \\,", - "\\text{There is a vector }", "\\textbf{0}", - "\\text{ such that }", "\\textbf{0}+", v_tex, - "=", v_tex, "\\text{ for all }", v_tex - ), - ( - "4. \\,", - "\\text{For every vector }", v_tex, - "\\text{ there is a vector }", "-", v_tex, - "\\text{ so that }", v_tex, "+", "(-", v_tex, ")=\\textbf{0}" - ), - ( "5. \\,", - "a", "(", "b", v_tex, ")=(", "a", "b", ")", v_tex - ), - ( - "6. \\,", - "1", v_tex, "=", v_tex - ), - ( - "7. \\,", - "a", "(", v_tex, "+", w_tex, ")", "=", - "a", v_tex, "+", "a", w_tex - ), - ( - "8. \\,", - "(", "a", "+", "b", ")", v_tex, "=", - "a", v_tex, "+", "b", v_tex - ), - ])) - tex_color_pairs = [ - (v_tex, YELLOW), - (w_tex, MAROON_B), - (u_tex, PINK), - ("a", BLUE), - ("b", GREEN) - - ] - for axiom in axioms: - for tex, color in tex_color_pairs: - axiom.set_color_by_tex(tex, color) - axioms.arrange( - DOWN, buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - axioms.set_width(FRAME_WIDTH-1) - axioms.next_to(h_line, DOWN, buff = MED_SMALL_BUFF) - - self.play(FadeIn( - axioms, - lag_ratio = 0.5, - run_time = 5 - )) - self.wait() - axioms_word = TextMobject("``Axioms''") - axioms_word.set_color(YELLOW) - axioms_word.scale(2) - axioms_word.shift(FRAME_X_RADIUS*RIGHT/2, FRAME_Y_RADIUS*DOWN/2) - self.play(Write(axioms_word, run_time = 3)) - self.wait() - -class AxiomsAreInterface(Scene): - def construct(self): - mathy = Mathematician().to_edge(LEFT) - mathy.change_mode("pondering") - others = [ - Randolph().flip().set_color(color) - for color in (BLUE_D, GREEN_E, GOLD_E, BLUE_C) - ] - others = VGroup( - VGroup(*others[:2]), - VGroup(*others[2:]), - ) - for group in others: - group.arrange(RIGHT) - others.arrange(DOWN) - others.scale(0.8) - others.to_edge(RIGHT) - VGroup(mathy, others).to_edge(DOWN) - double_arrow = DoubleArrow(mathy, others) - - axioms, are, rules_of_nature = words = TextMobject( - "Axioms", "are", "rules of nature" - ) - words.to_edge(UP) - axioms.set_color(YELLOW) - an_interface = TextMobject("an interface") - an_interface.next_to(rules_of_nature, DOWN) - red_line = Line( - rules_of_nature.get_left(), - rules_of_nature.get_right(), - color = RED - ) - - self.play(Write(words)) - self.wait() - self.play(ShowCreation(red_line)) - self.play(Transform( - rules_of_nature.copy(), - an_interface - )) - self.wait() - self.play(FadeIn(mathy)) - self.play( - ShowCreation(double_arrow), - FadeIn(others, lag_ratio = 0.5, run_time = 2) - ) - self.play(axioms.copy().next_to, double_arrow, UP) - self.play(Blink(mathy)) - self.wait() - -class VectorSpaceOfPiCreatures(Scene): - def construct(self): - creatures = self.add_creatures() - self.show_sum(creatures) - - def add_creatures(self): - creatures = VGroup(*[ - VGroup(*[ - PiCreature() - for x in range(4) - ]).arrange(RIGHT, buff = 1.5) - for y in range(4) - ]).arrange(DOWN, buff = 1.5) - creatures = VGroup(*it.chain(*creatures)) - creatures.set_height(FRAME_HEIGHT-1) - for pi in creatures: - pi.change_mode(random.choice([ - "pondering", "pondering", - "happy", "happy", "happy", - "confused", - "angry", "erm", "sassy", "hooray", - "speaking", "tired", - "plain", "plain" - ])) - if random.random() < 0.5: - pi.flip() - pi.shift(0.5*(random.random()-0.5)*RIGHT) - pi.shift(0.5*(random.random()-0.5)*UP) - pi.set_color(random.choice([ - BLUE_B, BLUE_C, BLUE_D, BLUE_E, - MAROON_B, MAROON_C, MAROON_D, MAROON_E, - GREY_BROWN, GREY_BROWN, GREY, - YELLOW_C, YELLOW_D, YELLOW_E - ])) - pi.scale_in_place(random.random()+0.5) - - self.play(FadeIn( - creatures, - lag_ratio = 0.5, - run_time = 3 - )) - self.wait() - return creatures - - def show_sum(self, creatures): - def is_valid(pi1, pi2, pi3): - if len(set([pi.get_color() for pi in (pi1, pi2, pi3)])) < 3: - return False - if pi1.is_flipped()^pi2.is_flipped(): - return False - return True - pi1, pi2, pi3 = pis = [random.choice(creatures) for x in range(3)] - while not is_valid(pi1, pi2, pi3): - pi1, pi2, pi3 = pis = [random.choice(creatures) for x in range(3)] - creatures.remove(*pis) - - transform = Transform(pi1.copy(), pi2.copy()) - transform.update(0.5) - sum_pi = transform.mobject - sum_pi.set_height(pi1.get_height()+pi2.get_height()) - for pi in pis: - pi.generate_target() - plus, equals = TexMobject("+=") - sum_equation = VGroup( - pi1.target, plus, pi2.target, - equals, sum_pi - ) - sum_equation.arrange().center() - - scaled_pi3 = pi3.copy().scale(2) - equals2 = TexMobject("=") - two = TexMobject("2 \\cdot") - scale_equation = VGroup( - two, pi3.target, equals2, scaled_pi3 - ) - scale_equation.arrange() - - VGroup(sum_equation, scale_equation).arrange( - DOWN, buff = MED_SMALL_BUFF - ) - - self.play(FadeOut(creatures)) - self.play(*it.chain( - list(map(MoveToTarget, [pi1, pi2, pi3])), - list(map(Write, [plus, equals, two, equals2])), - )) - self.play( - Transform(pi1.copy(), sum_pi), - Transform(pi2.copy(), sum_pi), - Transform(pi3.copy(), scaled_pi3) - ) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(sum_pi, scaled_pi3) - for pi in pi1, sum_pi, scaled_pi3, pi3: - self.play(Blink(pi)) - -class MathematicianDoesntHaveToThinkAboutThat(Scene): - def construct(self): - mathy = Mathematician().to_corner(DOWN+LEFT) - bubble = mathy.get_bubble(ThoughtBubble, height = 4) - words = TextMobject("I don't have to worry", "\\\\ about that madness!") - bubble.add_content(words) - new_words = TextMobject("So long as I", "\\\\ work abstractly") - bubble.add_content(new_words) - - self.play( - mathy.change_mode, "hooray", - ShowCreation(bubble), - Write(words) - ) - self.play(Blink(mathy)) - self.wait() - self.play( - mathy.change_mode, "pondering", - Transform(words, new_words) - ) - self.play(Blink(mathy)) - self.wait() - -class TextbooksAreAbstract(TeacherStudentsScene): - def construct(self): - self.student_says( - """ - All the textbooks I found - are pretty abstract. - """, - target_mode = "pleading" - ) - self.random_blink(3) - self.teacher_says( - """ - For each new concept, - contemplate it for 2d space - with grid lines... - """ - ) - self.change_student_modes("pondering") - self.random_blink(2) - self.teacher_says( - "...then in some different\\\\", - "context, like a function space" - ) - self.change_student_modes(*["pondering"]*2) - self.random_blink() - self.teacher_says( - "Only then should you\\\\", - "think from the axioms", - target_mode = "surprised" - ) - self.change_student_modes(*["pondering"]*3) - self.random_blink() - -class LastAskWhatAreVectors(TeacherStudentsScene): - def construct(self): - self.student_says( - "So...what are vectors?", - target_mode = "erm" - ) - self.random_blink() - self.teacher_says( - """ - The form they take - doesn't really matter - """ - ) - self.random_blink() - -class WhatIsThree(Scene): - def construct(self): - what_is, three, q_mark = words = TextMobject( - "What is ", "3", "?", - arg_separator = "" - ) - words.scale(1.5) - self.play(Write(words)) - self.wait() - self.play( - FadeOut(what_is), - FadeOut(q_mark), - three.center - ) - - triplets = [ - VGroup(*[ - PiCreature(color = color).scale(0.4) - for color in (BLUE_E, BLUE_C, BLUE_D) - ]), - VGroup(*[HyperCube().scale(0.3) for x in range(3)]), - VGroup(*[Vector(RIGHT) for x in range(3)]), - TexMobject(""" - \\Big\\{ - \\emptyset, - \\{\\emptyset\\}, - \\{\\{\\emptyset\\}, \\emptyset\\} - \\Big\\} - """) - ] - directions = [UP+LEFT, UP+RIGHT, DOWN+LEFT, DOWN+RIGHT] - for group, vect in zip(triplets, directions): - if isinstance(group, TexMobject): - pass - elif isinstance(group[0], Vector): - group.arrange(RIGHT) - group.set_color_by_gradient(YELLOW, MAROON_B) - else: - m1, m2, m3 = group - m2.next_to(m1, buff = MED_SMALL_BUFF) - m3.next_to(VGroup(m1, m2), DOWN, buff = MED_SMALL_BUFF) - group.next_to(three, vect, buff = LARGE_BUFF) - self.play(FadeIn(group)) - self.wait() - self.play(*[ - Transform( - trip, three, - lag_ratio = 0.5, - run_time = 2 - ) - for trip in triplets - ]) - -class IStillRecommendConcrete(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - I still recommend - thinking concretely - """) - self.random_blink(2) - self.student_thinks("") - self.zoom_in_on_thought_bubble() - -class AbstractionIsThePrice(Scene): - def construct(self): - words = TextMobject( - "Abstractness", "is the price\\\\" - "of", "generality" - ) - words.set_color_by_tex("Abstractness", YELLOW) - words.set_color_by_tex("generality", BLUE) - self.play(Write(words)) - self.wait() - -class ThatsAWrap(TeacherStudentsScene): - def construct(self): - self.teacher_says("That's all for now!") - self.random_blink(2) - -class GoodLuck(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Good luck with \\\\ your future learning!", - target_mode = "hooray" - ) - self.change_student_modes(*["happy"]*3) - self.random_blink(3) - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/chapter2.py b/from_3b1b/old/eola/chapter2.py deleted file mode 100644 index 4fadb2d5..00000000 --- a/from_3b1b/old/eola/chapter2.py +++ /dev/null @@ -1,1265 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eola.chapter1 import plane_wave_homotopy - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject(""" - Mathematics requires a small dose, not of genius, - but of an imaginative freedom which, in a larger - dose, would be insanity. - """) - words.to_edge(UP) - for mob in words.submobjects[49:49+18]: - mob.set_color(GREEN) - author = TextMobject("-Angus K. Rodgers") - author.set_color(YELLOW) - author.next_to(words, DOWN, buff = 0.5) - - self.play(FadeIn(words)) - self.wait(3) - self.play(Write(author, run_time = 3)) - self.wait() - - -class CoordinatesWereFamiliar(TeacherStudentsScene): - def construct(self): - self.setup() - self.student_says("I know this already") - self.random_blink() - self.teacher_says("Ah, but there is a subtlety") - self.random_blink() - self.wait() - - -class CoordinatesAsScalars(VectorScene): - CONFIG = { - "vector_coords" : [3, -2] - } - - def construct(self): - self.lock_in_faded_grid() - - vector = self.add_vector(self.vector_coords) - array, x_line, y_line = self.vector_to_coords(vector) - self.add(array) - self.wait() - new_array = self.general_idea_of_scalars(array, vector) - self.scale_basis_vectors(new_array) - self.show_symbolic_sum(new_array, vector) - - def general_idea_of_scalars(self, array, vector): - starting_mobjects = self.get_mobjects() - - title = TextMobject("Think of each coordinate as a scalar") - title.to_edge(UP) - - x, y = array.get_mob_matrix().flatten() - new_x = x.copy().scale(2).set_color(X_COLOR) - new_x.move_to(3*LEFT+2*UP) - new_y = y.copy().scale(2).set_color(Y_COLOR) - new_y.move_to(3*RIGHT+2*UP) - - i_hat, j_hat = self.get_basis_vectors() - new_i_hat = Vector( - self.vector_coords[0]*i_hat.get_end(), - color = X_COLOR - ) - new_j_hat = Vector( - self.vector_coords[1]*j_hat.get_end(), - color = Y_COLOR - ) - VMobject(i_hat, new_i_hat).shift(3*LEFT) - VMobject(j_hat, new_j_hat).shift(3*RIGHT) - - new_array = Matrix([new_x.copy(), new_y.copy()]) - new_array.scale(0.5) - new_array.shift( - -new_array.get_boundary_point(-vector.get_end()) + \ - 1.1*vector.get_end() - ) - - self.remove(*starting_mobjects) - self.play( - Transform(x, new_x), - Transform(y, new_y), - Write(title), - ) - self.play(FadeIn(i_hat), FadeIn(j_hat)) - self.wait() - self.play( - Transform(i_hat, new_i_hat), - Transform(j_hat, new_j_hat), - run_time = 3 - ) - self.wait() - starting_mobjects.remove(array) - - new_x, new_y = new_array.get_mob_matrix().flatten() - self.play( - Transform(x, new_x), - Transform(y, new_y), - FadeOut(i_hat), - FadeOut(j_hat), - Write(new_array.get_brackets()), - FadeIn(VMobject(*starting_mobjects)), - FadeOut(title) - ) - self.remove(x, y) - self.add(new_array) - return new_array - - def scale_basis_vectors(self, new_array): - i_hat, j_hat = self.get_basis_vectors() - self.add_vector(i_hat) - i_hat_label = self.label_vector( - i_hat, "\\hat{\\imath}", - color = X_COLOR, - label_scale_factor = 1 - ) - self.add_vector(j_hat) - j_hat_label = self.label_vector( - j_hat, "\\hat{\\jmath}", - color = Y_COLOR, - label_scale_factor = 1 - ) - self.wait() - - x, y = new_array.get_mob_matrix().flatten() - for coord, v, label, factor, shift_right in [ - (x, i_hat, i_hat_label, self.vector_coords[0], False), - (y, j_hat, j_hat_label, self.vector_coords[1], True) - ]: - faded_v = v.copy().fade(0.7) - scaled_v = Vector(factor*v.get_end(), color = v.get_color()) - - scaled_label = VMobject(coord.copy(), label.copy()) - scaled_label.arrange(RIGHT, buff = 0.1) - scaled_label.move_to(label, DOWN+RIGHT) - scaled_label.shift((scaled_v.get_end()-v.get_end())/2) - coord_copy = coord.copy() - self.play( - Transform(v.copy(), faded_v), - Transform(v, scaled_v), - Transform(VMobject(coord_copy, label), scaled_label), - ) - self.wait() - if shift_right: - group = VMobject(v, coord_copy, label) - self.play(ApplyMethod( - group.shift, self.vector_coords[0]*RIGHT - )) - self.wait() - - - def show_symbolic_sum(self, new_array, vector): - new_mob = TexMobject([ - "(%d)\\hat{\\imath}"%self.vector_coords[0], - "+", - "(%d)\\hat{\\jmath}"%self.vector_coords[1] - ]) - new_mob.move_to(new_array) - new_mob.shift_onto_screen() - i_hat, plus, j_hat = new_mob.split() - i_hat.set_color(X_COLOR) - j_hat.set_color(Y_COLOR) - - self.play(Transform(new_array, new_mob)) - self.wait() - - - -class CoordinatesAsScalarsExample2(CoordinatesAsScalars): - CONFIG = { - "vector_coords" : [-5, 2] - } - - def construct(self): - self.lock_in_faded_grid() - - basis_vectors = self.get_basis_vectors() - labels = self.get_basis_vector_labels() - self.add(*basis_vectors) - self.add(*labels) - text = TextMobject(""" - $\\hat{\\imath}$ and $\\hat{\\jmath}$ - are the ``basis vectors'' \\\\ - of the $xy$ coordinate system - """) - text.set_width(FRAME_X_RADIUS-1) - text.to_corner(UP+RIGHT) - VMobject(*text.split()[:2]).set_color(X_COLOR) - VMobject(*text.split()[5:7]).set_color(Y_COLOR) - self.play(Write(text)) - self.wait(2) - self.remove(*basis_vectors + labels) - CoordinatesAsScalars.construct(self) - - -class WhatIfWeChoseADifferentBasis(Scene): - def construct(self): - self.play(Write( - "What if we chose different basis vectors?", - run_time = 2 - )) - self.wait(2) - -class ShowVaryingLinearCombinations(VectorScene): - CONFIG = { - "vector1" : [1, 2], - "vector2" : [3, -1], - "vector1_color" : MAROON_C, - "vector2_color" : BLUE, - "vector1_label" : "v", - "vector2_label" : "w", - "sum_color" : PINK, - "scalar_pairs" : [ - (1.5, 0.6), - (0.7, 1.3), - (-1, -1.5), - (1, -1.13), - (1.25, 0.5), - (-0.8, 1.3), - ], - "leave_sum_vector_copies" : False, - "start_with_non_sum_scaling" : True, - "finish_with_standard_basis_comparison" : True, - "finish_by_drawing_lines" : False, - } - def construct(self): - self.lock_in_faded_grid() - v1 = self.add_vector(self.vector1, color = self.vector1_color) - v2 = self.add_vector(self.vector2, color = self.vector2_color) - v1_label = self.label_vector( - v1, self.vector1_label, color = self.vector1_color, - buff_factor = 3 - ) - v2_label = self.label_vector( - v2, self.vector2_label, color = self.vector2_color, - buff_factor = 3 - ) - label_anims = [ - MaintainPositionRelativeTo(label, v) - for v, label in [(v1, v1_label), (v2, v2_label)] - ] - scalar_anims = self.get_scalar_anims(v1, v2, v1_label, v2_label) - self.last_scalar_pair = (1, 1) - - if self.start_with_non_sum_scaling: - self.initial_scaling(v1, v2, label_anims, scalar_anims) - self.show_sum(v1, v2, label_anims, scalar_anims) - self.scale_with_sum(v1, v2, label_anims, scalar_anims) - if self.finish_with_standard_basis_comparison: - self.standard_basis_comparison(label_anims, scalar_anims) - if self.finish_by_drawing_lines: - self.draw_lines(v1, v2, label_anims, scalar_anims) - - def get_scalar_anims(self, v1, v2, v1_label, v2_label): - def get_val_func(vect): - original_vect = np.array(vect.get_end()-vect.get_start()) - square_norm = get_norm(original_vect)**2 - return lambda a : np.dot( - original_vect, vect.get_end()-vect.get_start() - )/square_norm - return [ - RangingValues( - tracked_mobject = label, - tracked_mobject_next_to_kwargs = { - "direction" : LEFT, - "buff" : 0.1 - }, - scale_factor = 0.75, - value_function = get_val_func(v) - ) - for v, label in [(v1, v1_label), (v2, v2_label)] - ] - - def get_rate_func_pair(self): - return [ - squish_rate_func(smooth, a, b) - for a, b in [(0, 0.7), (0.3, 1)] - ] - - def initial_scaling(self, v1, v2, label_anims, scalar_anims): - scalar_pair = self.scalar_pairs.pop(0) - anims = [ - ApplyMethod(v.scale, s, rate_func = rf) - for v, s, rf in zip( - [v1, v2], - scalar_pair, - self.get_rate_func_pair() - ) - ] - anims += [ - ApplyMethod(v.copy().fade, 0.7) - for v in (v1, v2) - ] - anims += label_anims + scalar_anims - self.play(*anims, **{"run_time" : 2}) - self.wait() - self.last_scalar_pair = scalar_pair - - def show_sum(self, v1, v2, label_anims, scalar_anims): - self.play( - ApplyMethod(v2.shift, v1.get_end()), - *label_anims + scalar_anims - ) - self.sum_vector = self.add_vector( - v2.get_end(), color = self.sum_color - ) - self.wait() - - def scale_with_sum(self, v1, v2, label_anims, scalar_anims): - v2_anim, sum_anim = self.get_sum_animations(v1, v2) - while self.scalar_pairs: - scalar_pair = self.scalar_pairs.pop(0) - anims = [ - ApplyMethod(v.scale, s/s_old, rate_func = rf) - for v, s, s_old, rf in zip( - [v1, v2], - scalar_pair, - self.last_scalar_pair, - self.get_rate_func_pair() - ) - ] - anims += [v2_anim, sum_anim] + label_anims + scalar_anims - self.play(*anims, **{"run_time" : 2}) - if self.leave_sum_vector_copies: - self.add(self.sum_vector.copy()) - self.wait() - self.last_scalar_pair = scalar_pair - - def get_sum_animations(self, v1, v2): - v2_anim = UpdateFromFunc( - v2, lambda m : m.shift(v1.get_end()-m.get_start()) - ) - sum_anim = UpdateFromFunc( - self.sum_vector, - lambda v : v.put_start_and_end_on(v1.get_start(), v2.get_end()) - ) - return v2_anim, sum_anim - - def standard_basis_comparison(self, label_anims, scalar_anims): - everything = self.get_mobjects() - everything.remove(self.sum_vector) - everything = VMobject(*everything) - alt_coords = [a.mobject for a in scalar_anims] - array = Matrix([ - mob.copy().set_color(color) - for mob, color in zip( - alt_coords, - [self.vector1_color, self.vector2_color] - ) - ]) - array.scale(0.8) - array.to_edge(UP) - array.shift(RIGHT) - brackets = array.get_brackets() - - anims = [ - Transform(*pair) - for pair in zip(alt_coords, array.get_mob_matrix().flatten()) - ] - # anims += [ - # FadeOut(a.mobject) - # for a in label_anims - # ] - self.play(*anims + [Write(brackets)]) - self.wait() - self.remove(brackets, *alt_coords) - self.add(array) - self.play( - FadeOut(everything), - Animation(array), - ) - - self.add_axes(animate = True) - ij_array, x_line, y_line = self.vector_to_coords( - self.sum_vector, integer_labels = False - ) - self.add(ij_array, x_line, y_line) - x, y = ij_array.get_mob_matrix().flatten() - self.play( - ApplyMethod(x.set_color, X_COLOR), - ApplyMethod(y.set_color, Y_COLOR), - ) - neq = TexMobject("\\neq") - neq.next_to(array) - self.play( - ApplyMethod(ij_array.next_to, neq), - Write(neq) - ) - self.wait() - - def draw_lines(self, v1, v2, label_anims, scalar_anims): - sum_anims = self.get_sum_animations(v1, v2) - aux_anims = list(sum_anims) + label_anims + scalar_anims - - scale_factor = 2 - for w1, w2 in (v2, v1), (v1, v2): - w1_vect = w1.get_end()-w1.get_start() - w2_vect = w2.get_end()-w2.get_start() - for num in scale_factor, -1, -1./scale_factor: - curr_tip = self.sum_vector.get_end() - line = Line(ORIGIN, curr_tip) - self.play( - ApplyMethod(w2.scale, num), - UpdateFromFunc( - line, lambda l : l.put_start_and_end_on(curr_tip, self.sum_vector.get_end()) - ), - *aux_anims - ) - self.add(line, v2) - self.wait() - - - -class AltShowVaryingLinearCombinations(ShowVaryingLinearCombinations): - CONFIG = { - "scalar_pairs" : [ - (1.5, 0.3), - (0.64, 1.3), - (-1, -1.5), - (1, 1.13), - (1.25, 0.5), - (-0.8, 1.14), - ], - "finish_with_standard_basis_comparison" : False - } - - -class NameLinearCombinations(Scene): - def construct(self): - v_color = MAROON_C - w_color = BLUE - words = TextMobject([ - "``Linear combination'' of", - "$\\vec{\\textbf{v}}$", - "and", - "$\\vec{\\textbf{w}}$" - ]) - words.split()[1].set_color(v_color) - words.split()[3].set_color(w_color) - words.set_width(FRAME_WIDTH - 1) - words.to_edge(UP) - - equation = TexMobject([ - "a", "\\vec{\\textbf{v}}", "+", "b", "\\vec{\\textbf{w}}" - ]) - equation.arrange(buff = 0.1, aligned_edge = DOWN) - equation.split()[1].set_color(v_color) - equation.split()[4].set_color(w_color) - a, b = np.array(equation.split())[[0, 3]] - equation.scale(2) - equation.next_to(words, DOWN, buff = 1) - - scalars_word = TextMobject("Scalars") - scalars_word.scale(1.5) - scalars_word.next_to(equation, DOWN, buff = 2) - arrows = [ - Arrow(scalars_word, letter) - for letter in (a, b) - ] - - self.add(equation) - self.play(Write(words)) - self.play( - ShowCreation(VMobject(*arrows)), - Write(scalars_word) - ) - self.wait(2) - - -class LinearCombinationsDrawLines(ShowVaryingLinearCombinations): - CONFIG = { - "scalar_pairs" : [ - (1.5, 0.6), - (0.7, 1.3), - (1, 1), - ], - "start_with_non_sum_scaling" : False, - "finish_with_standard_basis_comparison" : False, - "finish_by_drawing_lines" : True, - } - - -class LinearCombinationsWithSumCopies(ShowVaryingLinearCombinations): - CONFIG = { - "scalar_pairs" : [ - (1.5, 0.6), - (0.7, 1.3), - (-1, -1.5), - (1, -1.13), - (1.25, 0.5), - (-0.8, 1.3), - (-0.9, 1.4), - (0.9, 2), - ], - "leave_sum_vector_copies" : True, - "start_with_non_sum_scaling" : False, - "finish_with_standard_basis_comparison" : False, - "finish_by_drawing_lines" : False, - } - - - -class LinearDependentVectors(ShowVaryingLinearCombinations): - CONFIG = { - "vector1" : [1, 2], - "vector2" : [0.5, 1], - "vector1_color" : MAROON_C, - "vector2_color" : BLUE, - "vector1_label" : "v", - "vector2_label" : "w", - "sum_color" : PINK, - "scalar_pairs" : [ - (1.5, 0.6), - (0.7, 1.3), - (-1, -1.5), - (1.25, 0.5), - (-0.8, 1.3), - (-0.9, 1.4), - (0.9, 2), - ], - "leave_sum_vector_copies" : False, - "start_with_non_sum_scaling" : False, - "finish_with_standard_basis_comparison" : False, - "finish_by_drawing_lines" : False, - } - - def get_sum_animations(self, v1, v2): - v2_anim, sum_anim = ShowVaryingLinearCombinations.get_sum_animations(self, v1, v2) - self.remove(self.sum_vector) - return v2_anim, Animation(VMobject()) - -class WhenVectorsLineUp(LinearDependentVectors): - CONFIG = { - "vector1" : [3, 2], - "vector2" : [1.5, 1], - "scalar_pairs" : [ - (1.5, 0.6), - (0.7, 1.3), - ], - } - -class AnimationUnderSpanDefinition(ShowVaryingLinearCombinations): - CONFIG = { - "scalar_pairs" : [ - (1.5, 0.6), - (0.7, 1.3), - (-1, -1.5), - (1.25, 0.5), - (0.8, 1.3), - (0.93, -1.4), - (-2, -0.5), - ], - "leave_sum_vector_copies" : True, - "start_with_non_sum_scaling" : False, - "finish_with_standard_basis_comparison" : False, - "finish_by_drawing_lines" : False, - } - - -class BothVectorsCouldBeZero(VectorScene): - def construct(self): - plane = self.add_plane() - plane.fade(0.7) - v1 = self.add_vector([1, 2], color = MAROON_C) - v2 = self.add_vector([3, -1], color = BLUE) - self.play(Transform(v1, Dot(ORIGIN))) - self.play(Transform(v2, Dot(ORIGIN))) - self.wait() - - -class DefineSpan(Scene): - def construct(self): - v_color = MAROON_C - w_color = BLUE - - definition = TextMobject(""" - The ``span'' of $\\vec{\\textbf{v}}$ and - $\\vec{\\textbf{w}}$ is the \\\\ set of all their - linear combinations. - """) - definition.set_width(FRAME_WIDTH-1) - definition.to_edge(UP) - def_mobs = np.array(definition.split()) - VMobject(*def_mobs[4:4+4]).set_color(PINK) - VMobject(*def_mobs[11:11+2]).set_color(v_color) - VMobject(*def_mobs[16:16+2]).set_color(w_color) - VMobject(*def_mobs[-19:-1]).set_color(YELLOW) - - equation = TexMobject([ - "a", "\\vec{\\textbf{v}}", "+", "b", "\\vec{\\textbf{w}}" - ]) - equation.arrange(buff = 0.1, aligned_edge = DOWN) - equation.split()[1].set_color(v_color) - equation.split()[4].set_color(w_color) - a, b = np.array(equation.split())[[0, 3]] - equation.scale(2) - equation.next_to(definition, DOWN, buff = 1) - - vary_words = TextMobject( - "Let $a$ and $b$ vary \\\\ over all real numbers" - ) - vary_words.scale(1.5) - vary_words.next_to(equation, DOWN, buff = 2) - arrows = [ - Arrow(vary_words, letter) - for letter in (a, b) - ] - - self.play(Write(definition)) - self.play(Write(equation)) - self.wait() - self.play( - FadeIn(vary_words), - ShowCreation(VMobject(*arrows)) - ) - self.wait() - - -class VectorsVsPoints(Scene): - def construct(self): - self.play(Write("Vectors vs. Points")) - self.wait(2) - - -class VectorsToDotsScene(VectorScene): - CONFIG = { - "num_vectors" : 16, - "start_color" : PINK, - "end_color" : BLUE_E, - } - def construct(self): - self.lock_in_faded_grid() - - vectors = self.get_vectors() - colors = Color(self.start_color).range_to( - self.end_color, len(vectors) - ) - for vect, color in zip(vectors, colors): - vect.set_color(color) - prototype_vector = vectors[3*len(vectors)/4] - - vector_group = VMobject(*vectors) - self.play( - ShowCreation( - vector_group, - run_time = 3 - ) - ) - vectors.sort(key=lambda v: v.get_length()) - self.add(*vectors) - def v_to_dot(vector): - return Dot(vector.get_end(), fill_color = vector.get_stroke_color()) - self.wait() - vectors.remove(prototype_vector) - self.play(*list(map(FadeOut, vectors))+[Animation(prototype_vector)]) - self.remove(vector_group) - self.add(prototype_vector) - self.wait() - self.play(Transform(prototype_vector, v_to_dot(prototype_vector))) - self.wait() - self.play(*list(map(FadeIn, vectors)) + [Animation(prototype_vector)]) - rate_functions = [ - squish_rate_func(smooth, float(x)/(len(vectors)+2), 1) - for x in range(len(vectors)) - ] - self.play(*[ - Transform(v, v_to_dot(v), rate_func = rf, run_time = 2) - for v, rf in zip(vectors, rate_functions) - ]) - self.wait() - self.remove(prototype_vector) - self.play_final_animation(vectors, rate_functions) - self.wait() - - def get_vectors(self): - raise Exception("Not implemented") - - def play_final_animation(self, vectors, rate_functions): - raise Exception("Not implemented") - - -class VectorsOnALine(VectorsToDotsScene): - def get_vectors(self): - return [ - Vector(a*np.array([1.5, 1])) - for a in np.linspace( - -FRAME_Y_RADIUS, FRAME_Y_RADIUS, self.num_vectors - ) - ] - - def play_final_animation(self, vectors, rate_functions): - line_copies = [ - Line(vectors[0].get_end(), vectors[-1].get_end()) - for v in vectors - ] - self.play(*[ - Transform(v, mob, rate_func = rf, run_time = 2) - for v, mob, rf in zip(vectors, line_copies, rate_functions) - ]) - - -class VectorsInThePlane(VectorsToDotsScene): - CONFIG = { - "num_vectors" : 16, - "start_color" : PINK, - "end_color" : BLUE_E, - } - def get_vectors(self): - return [ - Vector([x, y]) - for x in np.arange(-int(FRAME_X_RADIUS)-0.5, int(FRAME_X_RADIUS)+0.5) - for y in np.arange(-int(FRAME_Y_RADIUS)-0.5, int(FRAME_Y_RADIUS)+0.5) - ] - - def play_final_animation(self, vectors, rate_functions): - h_line = Line( - FRAME_X_RADIUS*RIGHT, FRAME_X_RADIUS*LEFT, - stroke_width = 0.5, - color = BLUE_E - ) - v_line = Line( - FRAME_Y_RADIUS*UP, FRAME_Y_RADIUS*DOWN, - stroke_width = 0.5, - color = BLUE_E - ) - line_pairs = [ - VMobject(h_line.copy().shift(y), v_line.copy().shift(x)) - for v in vectors - for x, y, z in [v.get_center()] - ] - plane = NumberPlane() - self.play( - ShowCreation(plane), - *[ - Transform(v, p, rate_func = rf) - for v, p, rf in zip(vectors, line_pairs, rate_functions) - ] - ) - self.remove(*vectors) - self.wait() - - -class HowToThinkVectorsVsPoint(Scene): - def construct(self): - randy = Randolph().to_corner() - bubble = randy.get_bubble(height = 3.8) - text1 = TextMobject("Think of individual vectors as arrows") - text2 = TextMobject("Think of sets of vectors as points") - for text in text1, text2: - text.to_edge(UP) - - single_vector = Vector([2, 1]) - vector_group = VMobject(*[ - Vector([x, 1]) - for x in np.linspace(-2, 2, 5) - ]) - bubble.position_mobject_inside(single_vector) - bubble.position_mobject_inside(vector_group) - dots = VMobject(*[ - Dot(v.get_end()) - for v in vector_group.split() - ]) - - self.add(randy) - self.play( - ApplyMethod(randy.change_mode, "pondering"), - ShowCreation(bubble) - ) - self.play(FadeIn(text1)) - self.play(ShowCreation(single_vector)) - self.wait(3) - self.play( - Transform(text1, text2), - Transform(single_vector, vector_group) - ) - self.remove(single_vector) - self.add(vector_group) - self.wait() - self.play(Transform(vector_group, dots)) - self.wait() - - -class IntroduceThreeDSpan(Scene): - pass - -class AskAboutThreeDSpan(Scene): - def construct(self): - self.play(Write("What does the span of two 3d vectors look like?")) - self.wait(2) - -class ThreeDVectorSpan(Scene): - pass - - -class LinearCombinationOfThreeVectors(Scene): - pass - -class VaryingLinearCombinationOfThreeVectors(Scene): - pass - -class LinearCombinationOfThreeVectorsText(Scene): - def construct(self): - text = TextMobject(""" - Linear combination of - $\\vec{\\textbf{v}}$, - $\\vec{\\textbf{w}}$, and - $\\vec{\\textbf{u}}$: - """) - VMobject(*text.split()[-12:-10]).set_color(MAROON_C) - VMobject(*text.split()[-9:-7]).set_color(BLUE) - VMobject(*text.split()[-3:-1]).set_color(RED_C) - VMobject(*text.split()[:17]).set_color(GREEN) - text.set_width(FRAME_WIDTH - 1) - text.to_edge(UP) - - equation = TextMobject("""$ - a\\vec{\\textbf{v}} + - b\\vec{\\textbf{w}} + - c\\vec{\\textbf{u}} - $""") - VMobject(*equation.split()[-10:-8]).set_color(MAROON_C) - VMobject(*equation.split()[-6:-4]).set_color(BLUE) - VMobject(*equation.split()[-2:]).set_color(RED_C) - - a, b, c = np.array(equation.split())[[0, 4, 8]] - - equation.scale(1.5) - equation.next_to(text, DOWN, buff = 1) - - span_comment = TextMobject("For span, let these constants vary") - span_comment.scale(1.5) - span_comment.next_to(equation, DOWN, buff = 2) - VMobject(*span_comment.split()[3:7]).set_color(YELLOW) - arrows = VMobject(*[ - Arrow(span_comment, var) - for var in (a, b, c) - ]) - - self.play(Write(text)) - self.play(Write(equation)) - self.wait() - self.play( - ShowCreation(arrows), - Write(span_comment) - ) - self.wait() - - -class ThirdVectorOnSpanOfFirstTwo(Scene): - pass - -class ThirdVectorOutsideSpanOfFirstTwo(Scene): - pass - -class SpanCasesWords(Scene): - def construct(self): - words1 = TextMobject(""" - Case 1: $\\vec{\\textbf{u}}$ is in the span of - $\\vec{\\textbf{v}}$ and $\\vec{\\textbf{u}}$ - """) - VMobject(*words1.split()[6:8]).set_color(RED_C) - VMobject(*words1.split()[-7:-5]).set_color(MAROON_C) - VMobject(*words1.split()[-2:]).set_color(BLUE) - - words2 = TextMobject(""" - Case 2: $\\vec{\\textbf{u}}$ is not in the span of - $\\vec{\\textbf{v}}$ and $\\vec{\\textbf{u}}$ - """) - VMobject(*words2.split()[6:8]).set_color(RED_C) - VMobject(*words2.split()[-7:-5]).set_color(MAROON_C) - VMobject(*words2.split()[-2:]).set_color(BLUE) - VMobject(*words2.split()[10:13]).set_color(RED) - - for words in words1, words2: - words.set_width(FRAME_WIDTH - 1) - self.play(Write(words1)) - self.wait() - self.play(Transform(words1, words2)) - self.wait() - - - -class LinearDependentWords(Scene): - def construct(self): - words1 = TextMobject([ - "$\\vec{\\textbf{v}}$", - "and", - "$\\vec{\\textbf{w}}$", - "are", - "``Linearly dependent'' ", - ]) - v, _and, w, are, rest = words1.split() - v.set_color(MAROON_C) - w.set_color(BLUE) - rest.set_color(YELLOW) - - words2 = TextMobject([ - "$\\vec{\\textbf{v}}$,", - "$\\vec{\\textbf{w}}$", - "and", - "$\\vec{\\textbf{u}}$", - "are", - "``Linearly dependent'' ", - ]) - v, w, _and, u, are, rest = words2.split() - v.set_color(MAROON_C) - w.set_color(BLUE) - u.set_color(RED_C) - rest.set_color(YELLOW) - - for words in words1, words2: - words.set_width(FRAME_WIDTH - 1) - - self.play(Write(words1)) - self.wait() - self.play(Transform(words1, words2)) - self.wait() - - -class LinearDependentEquations(Scene): - def construct(self): - title = TextMobject("``Linearly dependent'' ") - title.set_color(YELLOW) - title.scale(2) - title.to_edge(UP) - self.add(title) - - equation1 = TexMobject([ - "\\vec{\\textbf{w}}", - "=", - "a", - "\\vec{\\textbf{v}}", - ]) - w, eq, a, v = equation1.split() - w.set_color(BLUE) - v.set_color(MAROON_C) - equation1.scale(2) - eq1_copy = equation1.copy() - - low_words1 = TextMobject("For some value of $a$") - low_words1.scale(2) - low_words1.to_edge(DOWN) - arrow = Arrow(low_words1, a) - arrow_copy = arrow.copy() - - equation2 = TexMobject([ - "\\vec{\\textbf{u}}", - "=", - "a", - "\\vec{\\textbf{v}}", - "+", - "b", - "\\vec{\\textbf{w}}", - ]) - u, eq, a, v, plus, b, w = equation2.split() - u.set_color(RED) - w.set_color(BLUE) - v.set_color(MAROON_C) - equation2.scale(2) - eq2_copy = equation2.copy() - - low_words2 = TextMobject("For some values of a and b") - low_words2.scale(2) - low_words2.to_edge(DOWN) - arrows = VMobject(*[ - Arrow(low_words2, var) - for var in (a, b) - ]) - - self.play(Write(equation1)) - self.play( - ShowCreation(arrow), - Write(low_words1) - ) - self.wait() - - self.play( - Transform(equation1, equation2), - Transform(low_words1, low_words2), - Transform(arrow, arrows) - ) - self.wait(2) - - new_title = TextMobject("``Linearly independent'' ") - new_title.set_color(GREEN) - new_title.replace(title) - - for eq_copy in eq1_copy, eq2_copy: - neq = TexMobject("\\neq") - neq.replace(eq_copy.submobjects[1]) - eq_copy.submobjects[1] = neq - - new_low_words1 = TextMobject(["For", "all", "values of a"]) - new_low_words2 = TextMobject(["For", "all", "values of a and b"]) - for low_words in new_low_words1, new_low_words2: - low_words.split()[1].set_color(GREEN) - low_words.scale(2) - low_words.to_edge(DOWN) - - self.play( - Transform(title, new_title), - Transform(equation1, eq1_copy), - Transform(arrow, arrow_copy), - Transform(low_words1, new_low_words1) - ) - self.wait() - self.play( - Transform(equation1, eq2_copy), - Transform(arrow, arrows), - Transform(low_words1, new_low_words2) - ) - self.wait() - - - -class AlternateDefOfLinearlyDependent(Scene): - def construct(self): - title1 = TextMobject([ - "$\\vec{\\textbf{v}}$,", - "$\\vec{\\textbf{w}}$", - "and", - "$\\vec{\\textbf{u}}$", - "are", - "linearly dependent", - "if", - ]) - title2 = TextMobject([ - "$\\vec{\\textbf{v}}$,", - "$\\vec{\\textbf{w}}$", - "and", - "$\\vec{\\textbf{u}}$", - "are", - "linearly independent", - "if", - ]) - for title in title1, title2: - v, w, _and, u, are, ld, _if = title.split() - v.set_color(MAROON_C) - w.set_color(BLUE) - u.set_color(RED_C) - title.to_edge(UP) - title1.split()[-2].set_color(YELLOW) - title2.split()[-2].set_color(GREEN) - - subtitle = TextMobject("the only solution to") - subtitle.next_to(title2, DOWN, aligned_edge = LEFT) - - self.add(title1) - - equations = self.get_equations() - added_words1 = TextMobject( - "where at least one of $a$, $b$ and $c$ is not $0$" - ) - added_words2 = TextMobject( - "is a = b = c = 0" - ) - - scalar_specification = TextMobject( - "For some choice of $a$ and $b$" - ) - scalar_specification.shift(1.5*DOWN) - scalar_specification.add(*[ - Arrow(scalar_specification, equations[0].split()[i]) - for i in (2, 5) - ]) - - brace = Brace(VMobject(*equations[2].split()[2:])) - brace_words = TextMobject("Linear combination") - brace_words.next_to(brace, DOWN) - - equation = equations[0] - for added_words in added_words1, added_words2: - added_words.next_to(title, DOWN, buff = 3.5, aligned_edge = LEFT) - self.play(Write(equation)) - for i, new_eq in enumerate(equations): - if i == 0: - self.play(FadeIn(scalar_specification)) - self.wait(2) - self.play(FadeOut(scalar_specification)) - elif i == 3: - self.play( - GrowFromCenter(brace), - Write(brace_words) - ) - self.wait(3) - self.play(FadeOut(brace), FadeOut(brace_words)) - self.play(Transform( - equation, new_eq, - path_arc = (np.pi/2 if i == 1 else 0) - )) - self.wait(3) - self.play(Write(added_words1)) - self.wait(2) - self.play( - Transform(title1, title2), - Write(subtitle), - Transform(added_words1, added_words2), - ) - self.wait(3) - everything = VMobject(*self.get_mobjects()) - self.play(ApplyFunction( - lambda m : m.scale(0.5).to_corner(UP+LEFT), - everything - )) - self.wait() - - - def get_equations(self): - equation1 = TexMobject([ - "\\vec{\\textbf{u}}", - "=", - "a", - "\\vec{\\textbf{v}}", - "+", - "b", - "\\vec{\\textbf{w}}", - ]) - u = equation1.split()[0] - equation1.submobjects = list(it.chain( - [VectorizedPoint(u.get_center())], - equation1.submobjects[1:], - [VectorizedPoint(u.get_left())], - [u] - )) - equation2 = TexMobject([ - "\\left[\\begin{array}{c} 0 \\\\ 0 \\\\ 0 \\end{array} \\right]", - "=", - "a", - "\\vec{\\textbf{v}}", - "+", - "b", - "\\vec{\\textbf{w}}", - "-", - "\\vec{\\textbf{u}}", - ]) - equation3 = TexMobject([ - "\\vec{\\textbf{0}}", - "=", - "a", - "\\vec{\\textbf{v}}", - "+", - "b", - "\\vec{\\textbf{w}}", - "-", - "\\vec{\\textbf{u}}", - ]) - equation4 = TexMobject([ - "\\vec{\\textbf{0}}", - "=", - "0", - "\\vec{\\textbf{v}}", - "+", - "0", - "\\vec{\\textbf{w}}", - "+0", - "\\vec{\\textbf{u}}", - ]) - equation5 = TexMobject([ - "\\vec{\\textbf{0}}", - "=", - "a", - "\\vec{\\textbf{v}}", - "+", - "b", - "\\vec{\\textbf{w}}", - "+(-1)", - "\\vec{\\textbf{u}}", - ]) - equation5.split()[-2].set_color(YELLOW) - equation6 = TexMobject([ - "\\vec{\\textbf{0}}", - "=", - "a", - "\\vec{\\textbf{v}}", - "+", - "b", - "\\vec{\\textbf{w}}", - "+c", - "\\vec{\\textbf{u}}", - ]) - result = [equation1, equation2, equation3, equation4, equation5, equation6] - for eq in result: - eq.split()[3].set_color(MAROON_C) - eq.split()[6].set_color(BLUE) - eq.split()[-1].set_color(RED_C) - eq.scale(1.5) - eq.shift(UP) - return result - - - -class MathematiciansLikeToConfuse(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says(""" - We wouldn't want things to \\\\ - be \\emph{understandable} would we? - """) - modes = "pondering", "sassy", "confused" - self.play(*[ - ApplyMethod(student.change_mode, mode) - for student, mode in zip(self.get_students(), modes) - ]) - self.wait(2) - -class CheckYourUnderstanding(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says("Quiz time!") - self.random_blink() - self.wait() - self.random_blink() - - -class TechnicalDefinitionOfBasis(Scene): - def construct(self): - title = TextMobject("Technical definition of basis:") - title.to_edge(UP) - definition = TextMobject([ - "The", - "basis", - "of a vector space is a set of", - "linearly independent", - "vectors that", - "span", - "the full space", - ]) - t, b, oavsiaso, li, vt, s, tfs = definition.split() - b.set_color(BLUE) - li.set_color(GREEN) - s.set_color(YELLOW) - definition.set_width(FRAME_WIDTH-1) - - self.add(title) - self.play(Write(definition)) - self.wait() - -class NextVideo(Scene): - def construct(self): - title = TextMobject("Next video: Matrices as linear transformations") - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/chapter3.py b/from_3b1b/old/eola/chapter3.py deleted file mode 100644 index eb826b00..00000000 --- a/from_3b1b/old/eola/chapter3.py +++ /dev/null @@ -1,1753 +0,0 @@ -from manimlib.imports import * - -def curvy_squish(point): - x, y, z = point - return (x+np.cos(y))*RIGHT + (y+np.sin(x))*UP - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject([ - "Unfortunately, no one can be told what the", - "Matrix", - "is. You have to", - "see it for yourself.", - ]) - words.set_width(FRAME_WIDTH - 2) - words.to_edge(UP) - words.split()[1].set_color(GREEN) - words.split()[3].set_color(BLUE) - author = TextMobject("-Morpheus") - author.set_color(YELLOW) - author.next_to(words, DOWN, buff = 0.5) - comment = TextMobject(""" - (Surprisingly apt words on the importance - of understanding matrix operations visually.) - """) - comment.next_to(author, DOWN, buff = 1) - - self.play(FadeIn(words)) - self.wait(3) - self.play(Write(author, run_time = 3)) - self.wait() - self.play(Write(comment)) - self.wait() - -class Introduction(TeacherStudentsScene): - def construct(self): - title = TextMobject(["Matrices as", "Linear transformations"]) - title.to_edge(UP) - title.set_color(YELLOW) - linear_transformations = title.split()[1] - self.add(*title.split()) - self.setup() - self.teacher_says(""" - Listen up folks, this one is - particularly important - """, height = 3) - self.random_blink(2) - self.teacher_thinks("", height = 3) - self.remove(linear_transformations) - everything = VMobject(*self.get_mobjects()) - def spread_out(p): - p = p + 2*DOWN - return (FRAME_X_RADIUS+FRAME_Y_RADIUS)*p/get_norm(p) - self.play( - ApplyPointwiseFunction(spread_out, everything), - ApplyFunction( - lambda m : m.center().to_edge(UP), - linear_transformations - ) - ) - -class MatrixVectorMechanicalMultiplication(NumericalMatrixMultiplication): - CONFIG = { - "left_matrix" : [[1, -3], [2, 4]], - "right_matrix" : [[5], [7]] - } - -class PostponeHigherDimensions(TeacherStudentsScene): - def construct(self): - self.setup() - self.student_says("What about 3 dimensions?") - self.random_blink() - self.teacher_says("All in due time,\\\\ young padawan") - self.random_blink() - -class DescribeTransformation(Scene): - def construct(self): - self.add_title() - self.show_function() - - def add_title(self): - title = TextMobject(["Linear", "transformation"]) - title.to_edge(UP) - linear, transformation = title.split() - brace = Brace(transformation, DOWN) - function = TextMobject("function").next_to(brace, DOWN) - function.set_color(YELLOW) - - self.play(Write(title)) - self.wait() - self.play( - GrowFromCenter(brace), - Write(function), - ApplyMethod(linear.fade) - ) - - def show_function(self): - f_of_x = TexMobject("f(x)") - L_of_v = TexMobject("L(\\vec{\\textbf{v}})") - nums = [5, 2, -3] - num_inputs = VMobject(*list(map(TexMobject, list(map(str, nums))))) - num_outputs = VMobject(*[ - TexMobject(str(num**2)) - for num in nums - ]) - for mob in num_inputs, num_outputs: - mob.arrange(DOWN, buff = 1) - num_inputs.next_to(f_of_x, LEFT, buff = 1) - num_outputs.next_to(f_of_x, RIGHT, buff = 1) - f_point = VectorizedPoint(f_of_x.get_center()) - - input_vect = Matrix([5, 7]) - input_vect.next_to(L_of_v, LEFT, buff = 1) - output_vect = Matrix([2, -3]) - output_vect.next_to(L_of_v, RIGHT, buff = 1) - - vector_input_words = TextMobject("Vector input") - vector_input_words.set_color(MAROON_C) - vector_input_words.next_to(input_vect, DOWN) - vector_output_words = TextMobject("Vector output") - vector_output_words.set_color(BLUE) - vector_output_words.next_to(output_vect, DOWN) - - self.play(Write(f_of_x, run_time = 1)) - self.play(Write(num_inputs, run_time = 2)) - self.wait() - for mob in f_point, num_outputs: - self.play(Transform( - num_inputs, mob, - lag_ratio = 0.5 - )) - self.wait() - - self.play( - FadeOut(num_inputs), - Transform(f_of_x, L_of_v) - ) - self.play( - Write(input_vect), - Write(vector_input_words) - ) - self.wait() - for mob in f_point, output_vect: - self.play(Transform( - input_vect, mob, - lag_ratio = 0.5 - )) - self.play(Write(vector_output_words)) - self.wait() - -class WhyConfuseWithTerminology(TeacherStudentsScene): - def construct(self): - self.setup() - self.student_says("Why confuse us with \\\\ redundant terminology?") - other_students = [self.get_students()[i] for i in (0, 2)] - self.play(*[ - ApplyMethod(student.change_mode, "confused") - for student in other_students - ]) - self.random_blink() - self.wait() - statement = TextMobject([ - "The word", - "``transformation''", - "suggests \\\\ that you think using", - "movement", - ]) - statement.split()[1].set_color(BLUE) - statement.split()[-1].set_color(YELLOW) - self.teacher_says(statement, width = 10) - self.play(*[ - ApplyMethod(student.change_mode, "happy") - for student in other_students - ]) - self.random_blink() - self.wait() - -class ThinkinfOfFunctionsAsGraphs(VectorScene): - def construct(self): - axes = self.add_axes() - graph = FunctionGraph(lambda x : x**2, x_min = -2, x_max = 2) - name = TexMobject("f(x) = x^2") - name.next_to(graph, RIGHT).to_edge(UP) - point = Dot(graph.point_from_proportion(0.8)) - point_label = TexMobject("(2, f(2))") - point_label.next_to(point.get_center(), DOWN+RIGHT, buff = 0.1) - - self.play(ShowCreation(graph)) - self.play(Write(name, run_time = 1)) - self.play( - ShowCreation(point), - Write(point_label), - run_time = 1 - ) - self.wait() - - def collapse_func(p): - return np.dot(p, [RIGHT, RIGHT, OUT]) + (FRAME_Y_RADIUS+1)*DOWN - self.play( - ApplyPointwiseFunction(collapse_func, axes), - ApplyPointwiseFunction(collapse_func, graph), - ApplyMethod(point.shift, 10*DOWN), - ApplyMethod(point_label.shift, 10*DOWN), - ApplyPointwiseFunction(collapse_func, name), - run_time = 2 - ) - self.clear() - words = TextMobject(["Instead think about", "\\emph{movement}"]) - words.split()[-1].set_color(YELLOW) - self.play(Write(words)) - self.wait() - -class TransformJustOneVector(VectorScene): - def construct(self): - self.lock_in_faded_grid() - v1_coords = [-3, 1] - t_matrix = [[0, -1], [2, -1]] - v1 = Vector(v1_coords) - v2 = Vector( - np.dot(np.array(t_matrix).transpose(), v1_coords), - color = PINK - ) - for v, word in (v1, "Input"), (v2, "Output"): - v.label = TextMobject("%s vector"%word) - v.label.next_to(v.get_end(), UP) - v.label.set_color(v.get_color()) - self.play(ShowCreation(v)) - self.play(Write(v.label)) - self.wait() - self.remove(v2) - self.play( - Transform( - v1.copy(), v2, - path_arc = -np.pi/2, run_time = 3 - ), - ApplyMethod(v1.fade) - ) - self.wait() - -class TransformManyVectors(LinearTransformationScene): - CONFIG = { - "transposed_matrix" : [[2, 1], [1, 2]], - "use_dots" : False, - } - def construct(self): - self.lock_in_faded_grid() - vectors = VMobject(*[ - Vector([x, y]) - for x in np.arange(-int(FRAME_X_RADIUS)+0.5, int(FRAME_X_RADIUS)+0.5) - for y in np.arange(-int(FRAME_Y_RADIUS)+0.5, int(FRAME_Y_RADIUS)+0.5) - ]) - vectors.set_submobject_colors_by_gradient(PINK, YELLOW) - t_matrix = self.transposed_matrix - transformed_vectors = VMobject(*[ - Vector( - np.dot(np.array(t_matrix).transpose(), v.get_end()[:2]), - color = v.get_color() - ) - for v in vectors.split() - ]) - - self.play(ShowCreation(vectors, lag_ratio = 0.5)) - self.wait() - if self.use_dots: - self.play(Transform( - vectors, self.vectors_to_dots(vectors), - run_time = 3, - lag_ratio = 0.5 - )) - transformed_vectors = self.vectors_to_dots(transformed_vectors) - self.wait() - self.play(Transform( - vectors, transformed_vectors, - run_time = 3, - path_arc = -np.pi/2 - )) - self.wait() - if self.use_dots: - self.play(Transform( - vectors, self.dots_to_vectors(vectors), - run_time = 2, - lag_ratio = 0.5 - )) - self.wait() - - def vectors_to_dots(self, vectors): - return VMobject(*[ - Dot(v.get_end(), color = v.get_color()) - for v in vectors.split() - ]) - - def dots_to_vectors(self, dots): - return VMobject(*[ - Vector(dot.get_center(), color = dot.get_color()) - for dot in dots.split() - ]) - -class TransformManyVectorsAsPoints(TransformManyVectors): - CONFIG = { - "use_dots" : True - } - -class TransformInfiniteGrid(LinearTransformationScene): - CONFIG = { - "include_background_plane" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_HEIGHT, - }, - "show_basis_vectors" : False - } - def construct(self): - self.setup() - self.play(ShowCreation( - self.plane, run_time = 3, lag_ratio = 0.5 - )) - self.wait() - self.apply_transposed_matrix([[2, 1], [1, 2]]) - self.wait() - -class TransformInfiniteGridWithBackground(TransformInfiniteGrid): - CONFIG = { - "include_background_plane" : True, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_HEIGHT, - "secondary_line_ratio" : 0 - }, - - } - -class ApplyComplexFunction(LinearTransformationScene): - CONFIG = { - "function" : lambda z : 0.5*z**2, - "show_basis_vectors" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_X_RADIUS, - "y_radius" : FRAME_Y_RADIUS, - "secondary_line_ratio" : 0 - }, - } - def construct(self): - self.setup() - self.plane.prepare_for_nonlinear_transform(100) - self.wait() - self.play(ApplyMethod( - self.plane.apply_complex_function, self.function, - run_time = 5, - path_arc = np.pi/2 - )) - self.wait() - -class ExponentialTransformation(ApplyComplexFunction): - CONFIG = { - "function" : np.exp, - } - -class CrazyTransformation(ApplyComplexFunction): - CONFIG = { - "function" : lambda z : np.sin(z)**2 + np.sinh(z) - } - -class LookToWordLinear(Scene): - def construct(self): - title = TextMobject(["Linear ", "transformations"]) - title.to_edge(UP) - faded_title = title.copy().fade() - linear, transformation = title.split() - faded_linear, faded_transformation = faded_title.split() - linear_brace = Brace(linear, DOWN) - transformation_brace = Brace(transformation, DOWN) - function = TextMobject("function") - function.set_color(YELLOW) - function.next_to(transformation_brace, DOWN) - new_sub_word = TextMobject("What does this mean?") - new_sub_word.set_color(BLUE) - new_sub_word.next_to(linear_brace, DOWN) - - self.add( - faded_linear, transformation, - transformation_brace, function - ) - self.wait() - self.play( - Transform(faded_linear, linear), - Transform(transformation, faded_transformation), - Transform(transformation_brace, linear_brace), - Transform(function, new_sub_word), - lag_ratio = 0.5 - ) - self.wait() - -class IntroduceLinearTransformations(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - } - def construct(self): - self.setup() - self.wait() - self.apply_transposed_matrix([[2, 1], [1, 2]]) - self.wait() - - lines_rule = TextMobject("Lines remain lines") - lines_rule.shift(2*UP).to_edge(LEFT) - origin_rule = TextMobject("Origin remains fixed") - origin_rule.shift(2*UP).to_edge(RIGHT) - arrow = Arrow(origin_rule, ORIGIN) - dot = Dot(ORIGIN, radius = 0.1, color = RED) - - for rule in lines_rule, origin_rule: - rule.add_background_rectangle() - - self.play( - Write(lines_rule, run_time = 2), - ) - self.wait() - self.play( - Write(origin_rule, run_time = 2), - ShowCreation(arrow), - GrowFromCenter(dot) - ) - self.wait() - -class ToThePedants(Scene): - def construct(self): - words = TextMobject([ - "To the pedants:\\\\", - """ - Yeah yeah, I know that's not the formal definition - for linear transformations, as seen in textbooks, - but I'm building visual intuition here, and what - I've said is equivalent to the formal definition - (which I'll get to later in the series). - """]) - words.split()[0].set_color(RED) - words.to_edge(UP) - self.add(words) - self.wait() - -class SimpleLinearTransformationScene(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "transposed_matrix" : [[2, 1], [1, 2]] - } - def construct(self): - self.setup() - self.wait() - self.apply_transposed_matrix(self.transposed_matrix) - self.wait() - -class SimpleNonlinearTransformationScene(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "words" : "Not linear: some lines get curved" - } - def construct(self): - self.setup() - self.wait() - self.apply_nonlinear_transformation(self.func) - words = TextMobject(self.words) - words.to_corner(UP+RIGHT) - words.set_color(RED) - words.add_background_rectangle() - self.play(Write(words)) - self.wait() - - def func(self, point): - return curvy_squish(point) - -class MovingOrigin(SimpleNonlinearTransformationScene): - CONFIG = { - "words" : "Not linear: Origin moves" - } - def setup(self): - LinearTransformationScene.setup(self) - dot = Dot(ORIGIN, color = RED) - self.add_transformable_mobject(dot) - - def func(self, point): - matrix_transform = self.get_matrix_transformation([[2, 0], [1, 1]]) - return matrix_transform(point) + 2*UP+3*LEFT - -class SneakyNonlinearTransformation(SimpleNonlinearTransformationScene): - CONFIG = { - "words" : "\\dots" - } - def func(self, point): - x, y, z = point - new_x = np.sign(x)*FRAME_X_RADIUS*smooth(abs(x) / FRAME_X_RADIUS) - new_y = np.sign(y)*FRAME_Y_RADIUS*smooth(abs(y) / FRAME_Y_RADIUS) - return [new_x, new_y, 0] - -class SneakyNonlinearTransformationExplained(SneakyNonlinearTransformation): - CONFIG = { - "words" : "Not linear: diagonal lines get curved" - } - def setup(self): - LinearTransformationScene.setup(self) - diag = Line( - FRAME_Y_RADIUS*LEFT+FRAME_Y_RADIUS*DOWN, - FRAME_Y_RADIUS*RIGHT + FRAME_Y_RADIUS*UP - ) - diag.insert_n_curves(20) - diag.make_smooth() - diag.set_color(YELLOW) - self.play(ShowCreation(diag)) - self.add_transformable_mobject(diag) - -class GridLinesRemainParallel(SimpleLinearTransformationScene): - CONFIG = { - "transposed_matrix" : [ - [3, 0], - [1, 2] - ] - } - def construct(self): - SimpleLinearTransformationScene.construct(self) - text = TextMobject([ - "Grid lines remain", - "parallel", - "and", - "evenly spaced", - ]) - glr, p, a, es = text.split() - p.set_color(YELLOW) - es.set_color(GREEN) - text.add_background_rectangle() - text.shift(-text.get_bottom()) - self.play(Write(text)) - self.wait() - -class Rotation(SimpleLinearTransformationScene): - CONFIG = { - "angle" : np.pi/3, - } - def construct(self): - self.transposed_matrix = [ - [np.cos(self.angle), np.sin(self.angle)], - [-np.sin(self.angle), np.cos(self.angle)] - ] - SimpleLinearTransformationScene.construct(self) - -class YetAnotherLinearTransformation(SimpleLinearTransformationScene): - CONFIG = { - "transposed_matrix" : [ - [-1, 1], - [3, 2], - ] - } - def construct(self): - SimpleLinearTransformationScene.construct(self) - words = TextMobject(""" - How would you describe - one of these numerically? - """ - ) - words.add_background_rectangle() - words.to_edge(UP) - words.set_color(GREEN) - formula = TexMobject([ - matrix_to_tex_string(["x_\\text{in}", "y_\\text{in}"]), - "\\rightarrow ???? \\rightarrow", - matrix_to_tex_string(["x_\\text{out}", "y_{\\text{out}}"]) - ]) - formula.add_background_rectangle() - - self.play(Write(words)) - self.wait() - self.play( - ApplyMethod(self.plane.fade, 0.7), - ApplyMethod(self.background_plane.fade, 0.7), - Write(formula, run_time = 2), - Animation(words) - ) - self.wait() - -class FollowIHatJHat(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False - } - def construct(self): - self.setup() - i_hat = self.add_vector([1, 0], color = X_COLOR) - i_label = self.label_vector( - i_hat, "\\hat{\\imath}", - color = X_COLOR, - label_scale_factor = 1 - ) - j_hat = self.add_vector([0, 1], color = Y_COLOR) - j_label = self.label_vector( - j_hat, "\\hat{\\jmath}", - color = Y_COLOR, - label_scale_factor = 1 - ) - - self.wait() - self.play(*list(map(FadeOut, [i_label, j_label]))) - self.apply_transposed_matrix([[-1, 1], [-2, -1]]) - self.wait() - -class TrackBasisVectorsExample(LinearTransformationScene): - CONFIG = { - "transposed_matrix" : [[1, -2], [3, 0]], - "v_coords" : [-1, 2], - "v_coord_strings" : ["-1", "2"], - "result_coords_string" : """ - = - \\left[ \\begin{array}{c} - -1(1) + 2(3) \\\\ - -1(-2) + 2(0) - \\end{arary}\\right] - = - \\left[ \\begin{array}{c} - 5 \\\\ - 2 - \\end{arary}\\right] - """ - } - def construct(self): - self.setup() - self.label_bases() - self.introduce_vector() - self.wait() - self.apply_transposed_matrix(self.transposed_matrix) - self.wait() - self.show_linear_combination(clean_up = False) - self.write_linear_map_rule() - self.show_basis_vector_coords() - - def label_bases(self): - triplets = [ - (self.i_hat, "\\hat{\\imath}", X_COLOR), - (self.j_hat, "\\hat{\\jmath}", Y_COLOR), - ] - label_mobs = [] - for vect, label, color in triplets: - label_mobs.append(self.add_transformable_label( - vect, label, "\\text{Transformed } " + label, - color = color, - direction = "right", - )) - self.i_label, self.j_label = label_mobs - - def introduce_vector(self): - v = self.add_vector(self.v_coords) - coords = Matrix(self.v_coords) - coords.scale(VECTOR_LABEL_SCALE_FACTOR) - coords.next_to(v.get_end(), np.sign(self.v_coords[0])*RIGHT) - - self.play(Write(coords, run_time = 1)) - v_def = self.get_v_definition() - pre_def = VMobject( - VectorizedPoint(coords.get_center()), - VMobject(*[ - mob.copy() - for mob in coords.get_mob_matrix().flatten() - ]) - ) - self.play(Transform( - pre_def, v_def, - run_time = 2, - lag_ratio = 0 - )) - self.remove(pre_def) - self.add_foreground_mobject(v_def) - self.wait() - self.show_linear_combination() - self.remove(coords) - - def show_linear_combination(self, clean_up = True): - i_hat_copy, j_hat_copy = [m.copy() for m in (self.i_hat, self.j_hat)] - self.play(ApplyFunction( - lambda m : m.scale(self.v_coords[0]).fade(0.3), - i_hat_copy - )) - self.play(ApplyFunction( - lambda m : m.scale(self.v_coords[1]).fade(0.3), - j_hat_copy - )) - self.play(ApplyMethod(j_hat_copy.shift, i_hat_copy.get_end())) - self.wait(2) - if clean_up: - self.play(FadeOut(i_hat_copy), FadeOut(j_hat_copy)) - - - def get_v_definition(self): - v_def = TexMobject([ - "\\vec{\\textbf{v}}", - " = %s"%self.v_coord_strings[0], - "\\hat{\\imath}", - "+%s"%self.v_coord_strings[1], - "\\hat{\\jmath}", - ]) - v, equals_neg_1, i_hat, plus_2, j_hat = v_def.split() - v.set_color(YELLOW) - i_hat.set_color(X_COLOR) - j_hat.set_color(Y_COLOR) - v_def.add_background_rectangle() - v_def.to_corner(UP + LEFT) - self.v_def = v_def - return v_def - - def write_linear_map_rule(self): - rule = TexMobject([ - "\\text{Transformed } \\vec{\\textbf{v}}", - " = %s"%self.v_coord_strings[0], - "(\\text{Transformed }\\hat{\\imath})", - "+%s"%self.v_coord_strings[1], - "(\\text{Transformed } \\hat{\\jmath})", - ]) - v, equals_neg_1, i_hat, plus_2, j_hat = rule.split() - v.set_color(YELLOW) - i_hat.set_color(X_COLOR) - j_hat.set_color(Y_COLOR) - rule.scale(0.85) - rule.next_to(self.v_def, DOWN, buff = 0.2) - rule.to_edge(LEFT) - rule.add_background_rectangle() - - self.play(Write(rule, run_time = 2)) - self.wait() - self.linear_map_rule = rule - - - def show_basis_vector_coords(self): - i_coords = matrix_to_mobject(self.transposed_matrix[0]) - j_coords = matrix_to_mobject(self.transposed_matrix[1]) - i_coords.set_color(X_COLOR) - j_coords.set_color(Y_COLOR) - for coords in i_coords, j_coords: - coords.add_background_rectangle() - coords.scale(0.7) - i_coords.next_to(self.i_hat.get_end(), RIGHT) - j_coords.next_to(self.j_hat.get_end(), RIGHT) - - calculation = TexMobject([ - " = %s"%self.v_coord_strings[0], - matrix_to_tex_string(self.transposed_matrix[0]), - "+%s"%self.v_coord_strings[1], - matrix_to_tex_string(self.transposed_matrix[1]), - ]) - equals_neg_1, i_hat, plus_2, j_hat = calculation.split() - i_hat.set_color(X_COLOR) - j_hat.set_color(Y_COLOR) - calculation.scale(0.8) - calculation.next_to(self.linear_map_rule, DOWN) - calculation.to_edge(LEFT) - calculation.add_background_rectangle() - - result = TexMobject(self.result_coords_string) - result.scale(0.8) - result.add_background_rectangle() - result.next_to(calculation, DOWN) - result.to_edge(LEFT) - - self.play(Write(i_coords, run_time = 1)) - self.wait() - self.play(Write(j_coords, run_time = 1)) - self.wait() - self.play(Write(calculation)) - self.wait() - self.play(Write(result)) - self.wait() - -class WatchManyVectorsMove(TransformManyVectors): - def construct(self): - self.setup() - vectors = VMobject(*[ - Vector([x, y]) - for x in np.arange(-int(FRAME_X_RADIUS)+0.5, int(FRAME_X_RADIUS)+0.5) - for y in np.arange(-int(FRAME_Y_RADIUS)+0.5, int(FRAME_Y_RADIUS)+0.5) - ]) - vectors.set_submobject_colors_by_gradient(PINK, YELLOW) - dots = self.vectors_to_dots(vectors) - self.play(ShowCreation(dots, lag_ratio = 0.5)) - self.play(Transform( - dots, vectors, - lag_ratio = 0.5, - run_time = 2 - )) - self.remove(dots) - for v in vectors.split(): - self.add_vector(v, animate = False) - self.apply_transposed_matrix([[1, -2], [3, 0]]) - self.wait() - self.play( - ApplyMethod(self.plane.fade), - FadeOut(vectors), - Animation(self.i_hat), - Animation(self.j_hat), - ) - self.wait() - -class NowWithoutWatching(Scene): - def construct(self): - text = TextMobject("Now without watching...") - text.to_edge(UP) - randy = Randolph(mode = "pondering") - self.add(randy) - self.play(Write(text, run_time = 1)) - self.play(ApplyMethod(randy.blink)) - self.wait(2) - -class DeduceResultWithGeneralCoordinates(Scene): - def construct(self): - i_hat_to = TexMobject("\\hat{\\imath} \\rightarrow") - j_hat_to = TexMobject("\\hat{\\jmath} \\rightarrow") - i_coords = Matrix([1, -2]) - j_coords = Matrix([3, 0]) - i_coords.next_to(i_hat_to, RIGHT, buff = 0.1) - j_coords.next_to(j_hat_to, RIGHT, buff = 0.1) - i_group = VMobject(i_hat_to, i_coords) - j_group = VMobject(j_hat_to, j_coords) - i_group.set_color(X_COLOR) - j_group.set_color(Y_COLOR) - i_group.next_to(ORIGIN, LEFT, buff = 1).to_edge(UP) - j_group.next_to(ORIGIN, RIGHT, buff = 1).to_edge(UP) - - vect = Matrix(["x", "y"]) - x, y = vect.get_mob_matrix().flatten() - VMobject(x, y).set_color(YELLOW) - rto = TexMobject("\\rightarrow") - equals = TexMobject("=") - plus = TexMobject("+") - row1 = TexMobject("1x + 3y") - row2 = TexMobject("-2x + 0y") - VMobject( - row1.split()[0], row2.split()[0], row2.split()[1] - ).set_color(X_COLOR) - VMobject( - row1.split()[1], row1.split()[4], row2.split()[2], row2.split()[5] - ).set_color(YELLOW) - VMobject( - row1.split()[3], row2.split()[4] - ).set_color(Y_COLOR) - result = Matrix([row1, row2]) - result.show() - vect_group = VMobject( - vect, rto, - x.copy(), i_coords.copy(), plus, - y.copy(), j_coords.copy(), equals, - result - ) - vect_group.arrange(RIGHT, buff = 0.1) - - self.add(i_group, j_group) - for mob in vect_group.split(): - self.play(Write(mob)) - self.wait() - -class MatrixVectorMultiplication(LinearTransformationScene): - CONFIG = { - "abstract" : False - } - def construct(self): - self.setup() - matrix = self.build_to_matrix() - self.label_matrix(matrix) - vector, formula = self.multiply_by_vector(matrix) - self.reposition_matrix_and_vector(matrix, vector, formula) - - def build_to_matrix(self): - self.wait() - self.apply_transposed_matrix([[3, -2], [2, 1]]) - self.wait() - i_coords = vector_coordinate_label(self.i_hat) - j_coords = vector_coordinate_label(self.j_hat) - if self.abstract: - new_i_coords = Matrix(["a", "c"]) - new_j_coords = Matrix(["b", "d"]) - new_i_coords.move_to(i_coords) - new_j_coords.move_to(j_coords) - i_coords = new_i_coords - j_coords = new_j_coords - i_coords.set_color(X_COLOR) - j_coords.set_color(Y_COLOR) - i_brackets = i_coords.get_brackets() - j_brackets = j_coords.get_brackets() - for coords in i_coords, j_coords: - rect = BackgroundRectangle(coords) - coords.rect = rect - - abstract_matrix = np.append( - i_coords.get_mob_matrix(), - j_coords.get_mob_matrix(), - axis = 1 - ) - concrete_matrix = Matrix( - copy.deepcopy(abstract_matrix), - add_background_rectangles_to_entries = True - ) - concrete_matrix.to_edge(UP) - if self.abstract: - m = concrete_matrix.get_mob_matrix()[1, 0] - m.shift(m.get_height()*DOWN/2) - matrix_brackets = concrete_matrix.get_brackets() - - self.play(ShowCreation(i_coords.rect), Write(i_coords)) - self.play(ShowCreation(j_coords.rect), Write(j_coords)) - self.wait() - self.remove(i_coords.rect, j_coords.rect) - self.play( - Transform( - VMobject(*abstract_matrix.flatten()), - VMobject(*concrete_matrix.get_mob_matrix().flatten()), - ), - Transform(i_brackets, matrix_brackets), - Transform(j_brackets, matrix_brackets), - run_time = 2, - lag_ratio = 0 - ) - everything = VMobject(*self.get_mobjects()) - self.play( - FadeOut(everything), - Animation(concrete_matrix) - ) - return concrete_matrix - - def label_matrix(self, matrix): - title = TextMobject("``2x2 Matrix''") - title.to_edge(UP+LEFT) - col_circles = [] - for i, color in enumerate([X_COLOR, Y_COLOR]): - col = VMobject(*matrix.get_mob_matrix()[:,i]) - col_circle = Circle(color = color) - col_circle.stretch_to_fit_width(matrix.get_width()/3) - col_circle.stretch_to_fit_height(matrix.get_height()) - col_circle.move_to(col) - col_circles.append(col_circle) - i_circle, j_circle = col_circles - i_message = TextMobject("Where $\\hat{\\imath}$ lands") - j_message = TextMobject("Where $\\hat{\\jmath}$ lands") - i_message.set_color(X_COLOR) - j_message.set_color(Y_COLOR) - i_message.next_to(i_circle, DOWN, buff = 2, aligned_edge = RIGHT) - j_message.next_to(j_circle, DOWN, buff = 2, aligned_edge = LEFT) - i_arrow = Arrow(i_message, i_circle) - j_arrow = Arrow(j_message, j_circle) - - self.play(Write(title)) - self.wait() - self.play(ShowCreation(i_circle)) - self.play( - Write(i_message, run_time = 1.5), - ShowCreation(i_arrow), - ) - self.wait() - self.play(ShowCreation(j_circle)) - self.play( - Write(j_message, run_time = 1.5), - ShowCreation(j_arrow) - ) - self.wait() - self.play(*list(map(FadeOut, [ - i_message, i_circle, i_arrow, j_message, j_circle, j_arrow - ]))) - - - def multiply_by_vector(self, matrix): - vector = Matrix(["x", "y"]) if self.abstract else Matrix([5, 7]) - vector.set_height(matrix.get_height()) - vector.next_to(matrix, buff = 2) - brace = Brace(vector, DOWN) - words = TextMobject("Any ol' vector") - words.next_to(brace, DOWN) - - self.play( - Write(vector), - GrowFromCenter(brace), - Write(words), - run_time = 1 - ) - self.wait() - - v1, v2 = vector.get_mob_matrix().flatten() - mob_matrix = matrix.copy().get_mob_matrix() - col1 = Matrix(mob_matrix[:,0]) - col2 = Matrix(mob_matrix[:,1]) - formula = VMobject( - v1.copy(), col1, TexMobject("+"), v2.copy(), col2 - ) - formula.arrange(RIGHT, buff = 0.1) - formula.center() - formula_start = VMobject( - v1.copy(), - VMobject(*matrix.copy().get_mob_matrix()[:,0]), - VectorizedPoint(), - v2.copy(), - VMobject(*matrix.copy().get_mob_matrix()[:,1]), - ) - - self.play( - FadeOut(brace), - FadeOut(words), - Transform( - formula_start, formula, - run_time = 2, - lag_ratio = 0 - ) - ) - self.wait() - self.show_result(formula) - return vector, formula - - def show_result(self, formula): - if self.abstract: - row1 = ["a", "x", "+", "b", "y"] - row2 = ["c", "x", "+", "d", "y"] - else: - row1 = ["3", "(5)", "+", "2", "(7)"] - row2 = ["-2", "(5)", "+", "1", "(7)"] - row1 = VMobject(*list(map(TexMobject, row1))) - row2 = VMobject(*list(map(TexMobject, row2))) - for row in row1, row2: - row.arrange(RIGHT, buff = 0.1) - final_sum = Matrix([row1, row2]) - row1, row2 = final_sum.get_mob_matrix().flatten() - row1.split()[0].set_color(X_COLOR) - row2.split()[0].set_color(X_COLOR) - row1.split()[3].set_color(Y_COLOR) - row2.split()[3].set_color(Y_COLOR) - equals = TexMobject("=") - equals.next_to(formula, RIGHT) - final_sum.next_to(equals, RIGHT) - - self.play( - Write(equals, run_time = 1), - Write(final_sum) - ) - self.wait() - - - def reposition_matrix_and_vector(self, matrix, vector, formula): - start_state = VMobject(matrix, vector) - end_state = start_state.copy() - end_state.arrange(RIGHT, buff = 0.1) - equals = TexMobject("=") - equals.next_to(formula, LEFT) - end_state.next_to(equals, LEFT) - brace = Brace(formula, DOWN) - brace_words = TextMobject("Where all the intuition is") - brace_words.next_to(brace, DOWN) - brace_words.set_color(YELLOW) - - self.play( - Transform( - start_state, end_state, - lag_ratio = 0 - ), - Write(equals, run_time = 1) - ) - self.wait() - self.play( - FadeIn(brace), - FadeIn(brace_words), - lag_ratio = 0.5 - ) - self.wait() - -class MatrixVectorMultiplicationAbstract(MatrixVectorMultiplication): - CONFIG = { - "abstract" : True, - } - -class ColumnsToBasisVectors(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[3, 1], [1, 2]] - } - def construct(self): - self.setup() - vector_coords = [-1, 2] - - vector = self.move_matrix_columns(self.t_matrix, vector_coords) - self.scale_and_add(vector, vector_coords) - self.wait(3) - - def move_matrix_columns(self, transposed_matrix, vector_coords = None): - matrix = np.array(transposed_matrix).transpose() - matrix_mob = Matrix(matrix) - matrix_mob.to_corner(UP+LEFT) - matrix_mob.add_background_to_entries() - col1 = VMobject(*matrix_mob.get_mob_matrix()[:,0]) - col1.set_color(X_COLOR) - col2 = VMobject(*matrix_mob.get_mob_matrix()[:,1]) - col2.set_color(Y_COLOR) - matrix_brackets = matrix_mob.get_brackets() - matrix_background = BackgroundRectangle(matrix_mob) - self.add_foreground_mobject(matrix_background, matrix_mob) - - if vector_coords is not None: - vector = Matrix(vector_coords) - VMobject(*vector.get_mob_matrix().flatten()).set_color(YELLOW) - vector.set_height(matrix_mob.get_height()) - vector.next_to(matrix_mob, RIGHT) - vector_background = BackgroundRectangle(vector) - self.add_foreground_mobject(vector_background, vector) - - new_i = Vector(matrix[:,0]) - new_j = Vector(matrix[:,1]) - i_label = vector_coordinate_label(new_i).set_color(X_COLOR) - j_label = vector_coordinate_label(new_j).set_color(Y_COLOR) - i_coords = VMobject(*i_label.get_mob_matrix().flatten()) - j_coords = VMobject(*j_label.get_mob_matrix().flatten()) - i_brackets = i_label.get_brackets() - j_brackets = j_label.get_brackets() - i_label_background = BackgroundRectangle(i_label) - j_label_background = BackgroundRectangle(j_label) - i_coords_start = VMobject( - matrix_background.copy(), - col1.copy(), - matrix_brackets.copy() - ) - i_coords_end = VMobject( - i_label_background, - i_coords, - i_brackets, - ) - j_coords_start = VMobject( - matrix_background.copy(), - col2.copy(), - matrix_brackets.copy() - ) - j_coords_end = VMobject( - j_label_background, - j_coords, - j_brackets, - ) - - transform_matrix1 = np.array(matrix) - transform_matrix1[:,1] = [0, 1] - transform_matrix2 = np.dot( - matrix, - np.linalg.inv(transform_matrix1) - ) - - self.wait() - self.apply_transposed_matrix( - transform_matrix1.transpose(), - added_anims = [Transform(i_coords_start, i_coords_end)], - path_arc = np.pi/2, - ) - self.add_foreground_mobject(i_coords_start) - self.apply_transposed_matrix( - transform_matrix2.transpose(), - added_anims = [Transform(j_coords_start, j_coords_end) ], - path_arc = np.pi/2, - ) - self.add_foreground_mobject(j_coords_start) - self.wait() - - self.matrix = VGroup(matrix_background, matrix_mob) - self.i_coords = i_coords_start - self.j_coords = j_coords_start - - return vector if vector_coords is not None else None - - - def scale_and_add(self, vector, vector_coords): - i_copy = self.i_hat.copy() - j_copy = self.j_hat.copy() - i_target = i_copy.copy().scale(vector_coords[0]).fade(0.3) - j_target = j_copy.copy().scale(vector_coords[1]).fade(0.3) - - coord1, coord2 = vector.copy().get_mob_matrix().flatten() - coord1.add_background_rectangle() - coord2.add_background_rectangle() - - self.play( - Transform(i_copy, i_target), - ApplyMethod(coord1.next_to, i_target.get_center(), DOWN) - ) - self.play( - Transform(j_copy, j_target), - ApplyMethod(coord2.next_to, j_target.get_center(), LEFT) - ) - j_copy.add(coord2) - self.play(ApplyMethod(j_copy.shift, i_copy.get_end())) - self.add_vector(j_copy.get_end()) - self.wait() - -class Describe90DegreeRotation(LinearTransformationScene): - CONFIG = { - "transposed_matrix" : [[0, 1], [-1, 0]], - "title" : "$90^\\circ$ rotation counterclockwise", - } - def construct(self): - self.setup() - title = TextMobject(self.title) - title.shift(DOWN) - title.add_background_rectangle() - matrix = Matrix(np.array(self.transposed_matrix).transpose()) - matrix.to_corner(UP+LEFT) - matrix_background = BackgroundRectangle(matrix) - col1 = VMobject(*matrix.get_mob_matrix()[:,0]) - col2 = VMobject(*matrix.get_mob_matrix()[:,1]) - col1.set_color(X_COLOR) - col2.set_color(Y_COLOR) - self.add_foreground_mobject(matrix_background, matrix.get_brackets()) - - self.wait() - self.apply_transposed_matrix(self.transposed_matrix) - self.wait() - self.play(Write(title)) - self.add_foreground_mobject(title) - - for vect, color, col in [(self.i_hat, X_COLOR, col1), (self.j_hat, Y_COLOR, col2)]: - label = vector_coordinate_label(vect) - label.set_color(color) - background = BackgroundRectangle(label) - coords = VMobject(*label.get_mob_matrix().flatten()) - brackets = label.get_brackets() - - self.play(ShowCreation(background), Write(label)) - self.wait() - self.play( - ShowCreation(background, rate_func = lambda t : smooth(1-t)), - ApplyMethod(coords.replace, col), - FadeOut(brackets), - ) - self.remove(label) - self.add_foreground_mobject(coords) - self.wait() - self.show_vector(matrix) - - def show_vector(self, matrix): - vector = Matrix(["x", "y"]) - VMobject(*vector.get_mob_matrix().flatten()).set_color(YELLOW) - vector.set_height(matrix.get_height()) - vector.next_to(matrix, RIGHT) - v_background = BackgroundRectangle(vector) - - matrix = np.array(self.transposed_matrix).transpose() - inv = np.linalg.inv(matrix) - self.apply_transposed_matrix(inv.transpose(), run_time = 0.5) - self.add_vector([1, 2]) - self.wait() - self.apply_transposed_matrix(self.transposed_matrix) - self.play(ShowCreation(v_background), Write(vector)) - self.wait() - -class DescribeShear(Describe90DegreeRotation): - CONFIG = { - "transposed_matrix" : [[1, 0], [1, 1]], - "title" : "``Shear''", - } - -class OtherWayAround(Scene): - def construct(self): - self.play(Write("What about the other way around?")) - self.wait(2) - -class DeduceTransformationFromMatrix(ColumnsToBasisVectors): - def construct(self): - self.setup() - self.move_matrix_columns([[1, 2], [3, 1]]) - -class LinearlyDependentColumns(ColumnsToBasisVectors): - def construct(self): - self.setup() - title = TextMobject("Linearly dependent") - subtitle = TextMobject("columns") - title.add_background_rectangle() - subtitle.add_background_rectangle() - subtitle.next_to(title, DOWN) - title.add(subtitle) - title.shift(UP).to_edge(LEFT) - title.set_color(YELLOW) - self.add_foreground_mobject(title) - self.move_matrix_columns([[2, 1], [-2, -1]]) - -class NextVideo(Scene): - def construct(self): - title = TextMobject("Next video: Matrix multiplication as composition") - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - -class FinalSlide(Scene): - def construct(self): - text = TextMobject(""" - \\footnotesize - Technically, the definition of ``linear'' is as follows: - A transformation L is linear if it satisfies these - two properties: - - \\begin{align*} - L(\\vec{\\textbf{v}} + \\vec{\\textbf{w}}) - &= L(\\vec{\\textbf{v}}) + L(\\vec{\\textbf{w}}) - & & \\text{``Additivity''} \\\\ - L(c\\vec{\\textbf{v}}) &= c L(\\vec{\\textbf{v}}) - & & \\text{``Scaling''} - \\end{align*} - - I'll talk about these properties later on, but I'm a big - believer in first understanding things visually. - Once you do, it becomes much more intuitive why these - two properties make sense. So for now, you can - feel fine thinking of linear transformations as those - which keep grid lines parallel and evenly spaced - (and which fix the origin in place), since this visual - definition is actually equivalent to the two properties - above. - """, enforce_new_line_structure = False) - text.set_height(FRAME_HEIGHT - 2) - text.to_edge(UP) - self.add(text) - self.wait() - -### Old scenes - -class RotateIHat(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False - } - def construct(self): - self.setup() - i_hat, j_hat = self.get_basis_vectors() - i_label, j_label = self.get_basis_vector_labels() - self.add_vector(i_hat) - self.play(Write(i_label, run_time = 1)) - self.wait() - self.play(FadeOut(i_label)) - self.apply_transposed_matrix([[0, 1], [-1, 0]]) - self.wait() - self.play(Write(j_label, run_time = 1)) - self.wait() - -class TransformationsAreFunctions(Scene): - def construct(self): - title = TextMobject([ - """Linear transformations are a - special kind of""", - "function" - ]) - title_start, function = title.split() - function.set_color(YELLOW) - title.to_edge(UP) - - equation = TexMobject([ - "L", - "(", - "\\vec{\\textbf{v}}", - ") = ", - "\\vec{\\textbf{w}}", - ]) - L, lp, _input, equals, _output = equation.split() - L.set_color(YELLOW) - _input.set_color(MAROON_C) - _output.set_color(BLUE) - equation.scale(2) - equation.next_to(title, DOWN, buff = 1) - - starting_vector = TextMobject("Starting vector") - starting_vector.shift(DOWN+3*LEFT) - starting_vector.set_color(MAROON_C) - ending_vector = TextMobject("The vector where it lands") - ending_vector.shift(DOWN).to_edge(RIGHT) - ending_vector.set_color(BLUE) - - func_arrow = Arrow(function.get_bottom(), L.get_top(), color = YELLOW) - start_arrow = Arrow(starting_vector.get_top(), _input.get_bottom(), color = MAROON_C) - ending_arrow = Arrow(ending_vector, _output, color = BLUE) - - - self.add(title) - self.play( - Write(equation), - ShowCreation(func_arrow) - ) - for v, a in [(starting_vector, start_arrow), (ending_vector, ending_arrow)]: - self.play(Write(v), ShowCreation(a), run_time = 1) - self.wait() - -class UsedToThinkinfOfFunctionsAsGraphs(VectorScene): - def construct(self): - self.show_graph() - self.show_inputs_and_output() - - def show_graph(self): - axes = self.add_axes() - graph = FunctionGraph(lambda x : x**2, x_min = -2, x_max = 2) - name = TexMobject("f(x) = x^2") - name.next_to(graph, RIGHT).to_edge(UP) - point = Dot(graph.point_from_proportion(0.8)) - point_label = TexMobject("(x, x^2)") - point_label.next_to(point, DOWN+RIGHT, buff = 0.1) - - self.play(ShowCreation(graph)) - self.play(Write(name, run_time = 1)) - self.play( - ShowCreation(point), - Write(point_label), - run_time = 1 - ) - self.wait() - - def collapse_func(p): - return np.dot(p, [RIGHT, RIGHT, OUT]) + (FRAME_Y_RADIUS+1)*DOWN - self.play( - ApplyPointwiseFunction( - collapse_func, axes, - lag_ratio = 0, - ), - ApplyPointwiseFunction(collapse_func, graph), - ApplyMethod(point.shift, 10*DOWN), - ApplyMethod(point_label.shift, 10*DOWN), - ApplyFunction(lambda m : m.center().to_edge(UP), name), - run_time = 1 - ) - self.clear() - self.add(name) - self.wait() - - def show_inputs_and_output(self): - numbers = list(range(-3, 4)) - inputs = VMobject(*list(map(TexMobject, list(map(str, numbers))))) - inputs.arrange(DOWN, buff = 0.5, aligned_edge = RIGHT) - arrows = VMobject(*[ - Arrow(LEFT, RIGHT).next_to(mob) - for mob in inputs.split() - ]) - outputs = VMobject(*[ - TexMobject(str(num**2)).next_to(arrow) - for num, arrow in zip(numbers, arrows.split()) - ]) - everyone = VMobject(inputs, arrows, outputs) - everyone.center().to_edge(UP, buff = 1.5) - - self.play(Write(inputs, run_time = 1)) - self.wait() - self.play( - Transform(inputs.copy(), outputs), - ShowCreation(arrows) - ) - self.wait() - -class TryingToVisualizeFourDimensions(Scene): - def construct(self): - randy = Randolph().to_corner() - bubble = randy.get_bubble() - formula = TexMobject(""" - L\\left(\\left[ - \\begin{array}{c} - x \\\\ - y - \\end{array} - \\right]\\right) = - \\left[ - \\begin{array}{c} - 2x + y \\\\ - x + 2y - \\end{array} - \\right] - """) - formula.next_to(randy, RIGHT) - formula.split()[3].set_color(X_COLOR) - formula.split()[4].set_color(Y_COLOR) - VMobject(*formula.split()[9:9+4]).set_color(MAROON_C) - VMobject(*formula.split()[13:13+4]).set_color(BLUE) - thought = TextMobject(""" - Do I imagine plotting - $(x, y, 2x+y, x+2y)$??? - """) - thought.split()[-17].set_color(X_COLOR) - thought.split()[-15].set_color(Y_COLOR) - VMobject(*thought.split()[-13:-13+4]).set_color(MAROON_C) - VMobject(*thought.split()[-8:-8+4]).set_color(BLUE) - - bubble.position_mobject_inside(thought) - thought.shift(0.2*UP) - - self.add(randy) - - self.play( - ApplyMethod(randy.look, DOWN+RIGHT), - Write(formula) - ) - self.play( - ApplyMethod(randy.change_mode, "pondering"), - ShowCreation(bubble), - Write(thought) - ) - self.play(Blink(randy)) - self.wait() - self.remove(thought) - bubble.make_green_screen() - self.wait() - self.play(Blink(randy)) - self.play(ApplyMethod(randy.change_mode, "confused")) - self.wait() - self.play(Blink(randy)) - self.wait() - -class ForgetAboutGraphs(Scene): - def construct(self): - self.play(Write("You must unlearn graphs")) - self.wait() - -class ThinkAboutFunctionAsMovingVector(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "leave_ghost_vectors" : True, - } - def construct(self): - self.setup() - vector = self.add_vector([2, 1]) - label = self.add_transformable_label(vector, "v") - self.wait() - self.apply_transposed_matrix([[1, 1], [-3, 1]]) - self.wait() - -class PrepareForFormalDefinition(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says("Get ready for a formal definition!") - self.wait(3) - bubble = self.student_thinks("") - bubble.make_green_screen() - self.wait(3) - -class AdditivityProperty(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "give_title" : True, - "transposed_matrix" : [[2, 0], [1, 1]], - "nonlinear_transformation" : None, - "vector_v" : [2, 1], - "vector_w" : [1, -2], - "proclaim_sum" : True, - } - def construct(self): - self.setup() - added_anims = [] - if self.give_title: - title = TextMobject(""" - First fundamental property of - linear transformations - """) - title.to_edge(UP) - title.set_color(YELLOW) - title.add_background_rectangle() - self.play(Write(title)) - added_anims.append(Animation(title)) - self.wait() - self.play(ApplyMethod(self.plane.fade), *added_anims) - - v, w = self.draw_all_vectors() - self.apply_transformation(added_anims) - self.show_final_sum(v, w) - - def draw_all_vectors(self): - v = self.add_vector(self.vector_v, color = MAROON_C) - v_label = self.add_transformable_label(v, "v") - w = self.add_vector(self.vector_w, color = GREEN) - w_label = self.add_transformable_label(w, "w") - new_w = w.copy().fade(0.4) - self.play(ApplyMethod(new_w.shift, v.get_end())) - sum_vect = self.add_vector(new_w.get_end(), color = PINK) - sum_label = self.add_transformable_label( - sum_vect, - "%s + %s"%(v_label.expression, w_label.expression), - rotate = True - ) - self.play(FadeOut(new_w)) - return v, w - - def apply_transformation(self, added_anims): - if self.nonlinear_transformation: - self.apply_nonlinear_transformation(self.nonlinear_transformation) - else: - self.apply_transposed_matrix( - self.transposed_matrix, - added_anims = added_anims - ) - self.wait() - - def show_final_sum(self, v, w): - new_w = w.copy() - self.play(ApplyMethod(new_w.shift, v.get_end())) - self.wait() - if self.proclaim_sum: - text = TextMobject("It's still their sum!") - text.add_background_rectangle() - text.move_to(new_w.get_end(), aligned_edge = -new_w.get_end()) - text.shift_onto_screen() - self.play(Write(text)) - self.wait() - -class NonlinearLacksAdditivity(AdditivityProperty): - CONFIG = { - "give_title" : False, - "nonlinear_transformation" : curvy_squish, - "vector_v" : [3, 2], - "vector_w" : [2, -3], - "proclaim_sum" : False, - } - -class SecondAdditivityExample(AdditivityProperty): - CONFIG = { - "give_title" : False, - "transposed_matrix" : [[1, -1], [2, 1]], - "vector_v" : [-2, 2], - "vector_w" : [3, 0], - "proclaim_sum" : False, - } - -class ShowGridCreation(Scene): - def construct(self): - plane = NumberPlane() - coords = VMobject(*plane.get_coordinate_labels()) - self.play(ShowCreation(plane, run_time = 3)) - self.play(Write(coords, run_time = 3)) - self.wait() - -class MoveAroundAllVectors(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "focus_on_one_vector" : False, - "include_background_plane" : False, - } - def construct(self): - self.setup() - vectors = VMobject(*[ - Vector([x, y]) - for x in np.arange(-int(FRAME_X_RADIUS)+0.5, int(FRAME_X_RADIUS)+0.5) - for y in np.arange(-int(FRAME_Y_RADIUS)+0.5, int(FRAME_Y_RADIUS)+0.5) - ]) - vectors.set_submobject_colors_by_gradient(PINK, YELLOW) - dots = self.get_dots(vectors) - - self.wait() - self.play(ShowCreation(dots)) - self.wait() - self.play(Transform(dots, vectors)) - self.wait() - self.remove(dots) - if self.focus_on_one_vector: - vector = vectors.split()[43]#yeah, great coding Grant - self.remove(vectors) - self.add_vector(vector) - self.play(*[ - FadeOut(v) - for v in vectors.split() - if v is not vector - ]) - self.wait() - self.add(vector.copy().set_color(DARK_GREY)) - else: - for vector in vectors.split(): - self.add_vector(vector, animate = False) - self.apply_transposed_matrix([[3, 0], [1, 2]]) - self.wait() - dots = self.get_dots(vectors) - self.play(Transform(vectors, dots)) - self.wait() - - def get_dots(self, vectors): - return VMobject(*[ - Dot(v.get_end(), color = v.get_color()) - for v in vectors.split() - ]) - -class ReasonForThinkingAboutArrows(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False - } - def construct(self): - self.setup() - self.plane.fade() - v_color = MAROON_C - w_color = BLUE - - v = self.add_vector([3, 1], color = v_color) - w = self.add_vector([1, -2], color = w_color) - vectors = VMobject(v, w) - - self.to_and_from_dots(vectors) - self.scale_and_add(vectors) - self.apply_transposed_matrix([[1, 1], [-1, 0]]) - self.scale_and_add(vectors) - - def to_and_from_dots(self, vectors): - vectors_copy = vectors.copy() - dots = VMobject(*[ - Dot(v.get_end(), color = v.get_color()) - for v in vectors.split() - ]) - - self.wait() - self.play(Transform(vectors, dots)) - self.wait() - self.play(Transform(vectors, vectors_copy)) - self.wait() - - def scale_and_add(self, vectors): - vectors_copy = vectors.copy() - v, w, = vectors.split() - scaled_v = Vector(0.5*v.get_end(), color = v.get_color()) - scaled_w = Vector(1.5*w.get_end(), color = w.get_color()) - shifted_w = scaled_w.copy().shift(scaled_v.get_end()) - sum_vect = Vector(shifted_w.get_end(), color = PINK) - - self.play( - ApplyMethod(v.scale, 0.5), - ApplyMethod(w.scale, 1.5), - ) - self.play(ApplyMethod(w.shift, v.get_end())) - self.add_vector(sum_vect) - self.wait() - self.play(Transform( - vectors, vectors_copy, - lag_ratio = 0 - )) - self.wait() - -class LinearTransformationWithOneVector(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - } - def construct(self): - self.setup() - v = self.add_vector([3, 1]) - self.vector_to_coords(v) - self.apply_transposed_matrix([[-1, 1], [-2, -1]]) - self.vector_to_coords(v) - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/chapter4.py b/from_3b1b/old/eola/chapter4.py deleted file mode 100644 index d2058586..00000000 --- a/from_3b1b/old/eola/chapter4.py +++ /dev/null @@ -1,1082 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eola.chapter3 import MatrixVectorMultiplicationAbstract - - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject([ - "It is my experience that proofs involving", - "matrices", - "can be shortened by 50\\% if one", - "throws the matrices out." - ]) - words.set_width(FRAME_WIDTH - 2) - words.to_edge(UP) - words.split()[1].set_color(GREEN) - words.split()[3].set_color(BLUE) - author = TextMobject("-Emil Artin") - author.set_color(YELLOW) - author.next_to(words, DOWN, buff = 0.5) - - self.play(FadeIn(words)) - self.wait(2) - self.play(Write(author, run_time = 3)) - self.wait() - -class MatrixToBlank(Scene): - def construct(self): - matrix = Matrix([[3, 1], [0, 2]]) - arrow = Arrow(LEFT, RIGHT) - matrix.to_edge(LEFT) - arrow.next_to(matrix, RIGHT) - matrix.add(arrow) - self.play(Write(matrix)) - self.wait() - -class ExampleTransformation(LinearTransformationScene): - def construct(self): - self.setup() - self.apply_transposed_matrix([[3, 0], [1, 2]]) - self.wait(2) - -class RecapTime(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says("Quick recap time!") - self.random_blink() - self.wait() - student = self.get_students()[0] - everyone = self.get_mobjects() - everyone.remove(student) - everyone = VMobject(*everyone) - self.play( - ApplyMethod(everyone.fade, 0.7), - ApplyMethod(student.change_mode, "confused") - ) - self.play(Blink(student)) - self.wait() - self.play(ApplyFunction( - lambda m : m.change_mode("pondering").look(LEFT), - student - )) - self.play(Blink(student)) - self.wait() - -class DeterminedByTwoBasisVectors(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False - } - def construct(self): - self.setup() - i_hat = self.add_vector([1, 0], color = X_COLOR) - self.add_transformable_label( - i_hat, "\\hat{\\imath}", "\\hat{\\imath}", - color = X_COLOR - ) - j_hat = self.add_vector([0, 1], color = Y_COLOR) - self.add_transformable_label( - j_hat, "\\hat{\\jmath}", "\\hat{\\jmath}", - color = Y_COLOR - ) - - t_matrix = np.array([[2, 2], [-2, 1]]) - matrix = t_matrix.transpose() - matrix1 = np.array(matrix) - matrix1[:,1] = [0, 1] - matrix2 = np.dot(matrix, np.linalg.inv(matrix1)) - - self.wait() - self.apply_transposed_matrix(matrix1.transpose()) - self.apply_transposed_matrix(matrix2.transpose()) - self.wait() - -class FollowLinearCombination(LinearTransformationScene): - def construct(self): - vect_coords = [-1, 2] - t_matrix = np.array([[2, 2], [-2, 1]]) - - #Draw vectors - self.setup() - i_label = self.add_transformable_label( - self.i_hat, "\\hat{\\imath}", animate = False, - direction = "right", color = X_COLOR - ) - j_label = self.add_transformable_label( - self.j_hat, "\\hat{\\jmath}", animate = False, - direction = "right", color = Y_COLOR - ) - vect = self.add_vector(vect_coords) - vect_array = Matrix(["x", "y"], add_background_rectangles_to_entries = True) - v_equals = TexMobject(["\\vec{\\textbf{v}}", "="]) - v_equals.split()[0].set_color(YELLOW) - v_equals.next_to(vect_array, LEFT) - vect_array.add(v_equals) - vect_array.to_edge(UP, buff = 0.2) - background_rect = BackgroundRectangle(vect_array) - vect_array.get_entries().set_color(YELLOW) - self.play(ShowCreation(background_rect), Write(vect_array)) - self.add_foreground_mobject(background_rect, vect_array) - - #Show scaled vectors - x, y = vect_array.get_entries().split() - scaled_i_label = VMobject(x.copy(), i_label) - scaled_j_label = VMobject(y.copy(), j_label) - scaled_i = self.i_hat.copy().scale(vect_coords[0]) - scaled_j = self.j_hat.copy().scale(vect_coords[1]) - for mob in scaled_i, scaled_j: - mob.fade(0.3) - scaled_i_label_target = scaled_i_label.copy() - scaled_i_label_target.arrange(buff = 0.1) - scaled_i_label_target.next_to(scaled_i, DOWN) - scaled_j_label_target = scaled_j_label.copy() - scaled_j_label_target.arrange(buff = 0.1) - scaled_j_label_target.next_to(scaled_j, LEFT) - - self.show_scaled_vectors(vect_array, vect_coords, i_label, j_label) - self.apply_transposed_matrix(t_matrix) - self.show_scaled_vectors(vect_array, vect_coords, i_label, j_label) - self.record_basis_coordinates(vect_array, vect) - - def show_scaled_vectors(self, vect_array, vect_coords, i_label, j_label): - x, y = vect_array.get_entries().split() - scaled_i_label = VMobject(x.copy(), i_label.copy()) - scaled_j_label = VMobject(y.copy(), j_label.copy()) - scaled_i = self.i_hat.copy().scale(vect_coords[0]) - scaled_j = self.j_hat.copy().scale(vect_coords[1]) - for mob in scaled_i, scaled_j: - mob.fade(0.3) - scaled_i_label_target = scaled_i_label.copy() - scaled_i_label_target.arrange(buff = 0.1) - scaled_i_label_target.next_to(scaled_i.get_center(), DOWN) - scaled_j_label_target = scaled_j_label.copy() - scaled_j_label_target.arrange(buff = 0.1) - scaled_j_label_target.next_to(scaled_j.get_center(), LEFT) - - self.play( - Transform(self.i_hat.copy(), scaled_i), - Transform(scaled_i_label, scaled_i_label_target) - ) - scaled_i = self.get_mobjects_from_last_animation()[0] - self.play( - Transform(self.j_hat.copy(), scaled_j), - Transform(scaled_j_label, scaled_j_label_target) - ) - scaled_j = self.get_mobjects_from_last_animation()[0] - self.play(*[ - ApplyMethod(mob.shift, scaled_i.get_end()) - for mob in (scaled_j, scaled_j_label) - ]) - self.wait() - self.play(*list(map(FadeOut, [ - scaled_i, scaled_j, scaled_i_label, scaled_j_label, - ]))) - - def record_basis_coordinates(self, vect_array, vect): - i_label = vector_coordinate_label(self.i_hat) - i_label.set_color(X_COLOR) - j_label = vector_coordinate_label(self.j_hat) - j_label.set_color(Y_COLOR) - for mob in i_label, j_label: - mob.scale_in_place(0.8) - background = BackgroundRectangle(mob) - self.play(ShowCreation(background), Write(mob)) - - self.wait() - x, y = vect_array.get_entries().split() - pre_formula = VMobject( - x, i_label, TexMobject("+"), - y, j_label - ) - post_formula = pre_formula.copy() - pre_formula.split()[2].fade(1) - post_formula.arrange(buff = 0.1) - post_formula.next_to(vect, DOWN) - background = BackgroundRectangle(post_formula) - everything = self.get_mobjects() - everything.remove(vect) - self.play(*[ - ApplyMethod(m.fade) for m in everything - ] + [ - ShowCreation(background, run_time = 2, rate_func = squish_rate_func(smooth, 0.5, 1)), - Transform(pre_formula.copy(), post_formula, run_time = 2), - ApplyMethod(vect.set_stroke, width = 7) - ]) - self.wait() - -class MatrixVectorMultiplicationCopy(MatrixVectorMultiplicationAbstract): - pass ## Here just for stage_animations.py purposes - -class RecapOver(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says("Recap over!") - -class TwoSuccessiveTransformations(LinearTransformationScene): - CONFIG = { - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_WIDTH, - "secondary_line_ratio" : 0 - }, - } - def construct(self): - self.setup() - self.apply_transposed_matrix([[2, 1],[1, 2]]) - self.apply_transposed_matrix([[-1, -0.5],[0, -0.5]]) - self.wait() - -class RotationThenShear(LinearTransformationScene): - CONFIG = { - "foreground_plane_kwargs" : { - "x_radius" : FRAME_X_RADIUS, - "y_radius" : FRAME_WIDTH, - "secondary_line_ratio" : 0 - }, - } - def construct(self): - self.setup() - rot_words = TextMobject("$90^\\circ$ rotation counterclockwise") - shear_words = TextMobject("followed by a shear") - rot_words.set_color(YELLOW) - shear_words.set_color(PINK) - VMobject(rot_words, shear_words).arrange(DOWN).to_edge(UP) - for words in rot_words, shear_words: - words.add_background_rectangle() - - self.play(Write(rot_words, run_time = 1)) - self.add_foreground_mobject(rot_words) - self.apply_transposed_matrix([[0, 1], [-1, 0]]) - - self.play(Write(shear_words, run_time = 1)) - self.add_foreground_mobject(shear_words) - self.apply_transposed_matrix([[1, 0], [1, 1]]) - self.wait() - -class IntroduceIdeaOfComposition(RotationThenShear): - def construct(self): - self.setup() - self.show_composition() - matrix = self.track_basis_vectors() - self.show_overall_effect(matrix) - - def show_composition(self): - words = TextMobject([ - "``Composition''", - "of a", - "rotation", - "and a", - "shear" - ]) - words.split()[0].set_submobject_colors_by_gradient(YELLOW, PINK, use_color_range_to = False) - words.split()[2].set_color(YELLOW) - words.split()[4].set_color(PINK) - words.add_background_rectangle() - words.to_edge(UP) - - self.apply_transposed_matrix([[0, 1], [-1, 0]], run_time = 2) - self.apply_transposed_matrix([[1, 0], [1, 1]], run_time = 2) - self.play( - ApplyMethod(self.plane.fade), - Write(words), - Animation(self.i_hat), - Animation(self.j_hat), - ) - self.wait() - - def track_basis_vectors(self): - last_words = self.get_mobjects_from_last_animation()[1] - words = TextMobject([ - "Record where", - "$\\hat{\\imath}$", - "and", - "$\\hat{\\jmath}$", - "land:" - ]) - rw, i_hat, a, j_hat, l = words.split() - i_hat.set_color(X_COLOR) - j_hat.set_color(Y_COLOR) - words.add_background_rectangle() - words.next_to(last_words, DOWN) - - i_coords = vector_coordinate_label(self.i_hat) - j_coords = vector_coordinate_label(self.j_hat) - i_coords.set_color(X_COLOR) - j_coords.set_color(Y_COLOR) - i_background = BackgroundRectangle(i_coords) - j_background = BackgroundRectangle(j_coords) - - matrix = Matrix(np.append( - i_coords.copy().get_mob_matrix(), - j_coords.copy().get_mob_matrix(), - axis = 1 - )) - matrix.next_to(words, RIGHT, aligned_edge = UP) - col1, col2 = [ - VMobject(*matrix.get_mob_matrix()[:,i]) - for i in (0, 1) - ] - matrix_background = BackgroundRectangle(matrix) - - self.play(Write(words)) - self.wait() - self.play(ShowCreation(i_background), Write(i_coords), run_time = 2) - self.wait() - self.play( - Transform(i_background.copy(), matrix_background), - Transform(i_coords.copy().get_brackets(), matrix.get_brackets()), - ApplyMethod(i_coords.copy().get_entries().move_to, col1) - ) - self.wait() - self.play(ShowCreation(j_background), Write(j_coords), run_time = 2) - self.wait() - self.play( - ApplyMethod(j_coords.copy().get_entries().move_to, col2) - ) - self.wait() - matrix = VMobject(matrix_background, matrix) - return matrix - - def show_overall_effect(self, matrix): - everything = self.get_mobjects() - everything = list_difference_update( - everything, matrix.get_family() - ) - self.play(*list(map(FadeOut, everything)) + [Animation(matrix)]) - new_matrix = matrix.copy() - new_matrix.center().to_edge(UP) - self.play(Transform(matrix, new_matrix)) - self.wait() - self.remove(matrix) - - self.setup() - everything = self.get_mobjects() - self.play(*list(map(FadeIn, everything)) + [Animation(matrix)]) - func = self.get_matrix_transformation([[1, 1], [-1, 0]]) - bases = VMobject(self.i_hat, self.j_hat) - new_bases = VMobject(*[ - Vector(func(v.get_end()), color = v.get_color()) - for v in bases.split() - ]) - self.play( - ApplyPointwiseFunction(func, self.plane), - Transform(bases, new_bases), - Animation(matrix), - run_time = 3 - ) - self.wait() - -class PumpVectorThroughRotationThenShear(RotationThenShear): - def construct(self): - self.setup() - self.add_vector([2, 3]) - self.apply_transposed_matrix([[0, 1], [-1, 0]], run_time = 2) - self.apply_transposed_matrix([[1, 0], [1, 1]], run_time = 2) - self.wait() - -class ExplainWhyItsMatrixMultiplication(Scene): - def construct(self): - vect = Matrix(["x", "y"]) - vect.get_entries().set_color(YELLOW) - - rot_matrix = Matrix([[0, -1], [1, 0]]) - rot_matrix.set_color(TEAL) - shear_matrix = Matrix([[1, 1], [0, 1]]) - shear_matrix.set_color(PINK) - l_paren, r_paren = list(map(TexMobject, ["\\Big(", "\\Big)"])) - for p in l_paren, r_paren: - p.set_height(1.4*vect.get_height()) - long_way = VMobject( - shear_matrix, l_paren, rot_matrix, vect, r_paren - ) - long_way.arrange(buff = 0.1) - long_way.to_edge(LEFT).shift(UP) - - equals = TexMobject("=").next_to(long_way, RIGHT) - - comp_matrix = Matrix([[1, -1], [1, 0]]) - comp_matrix.set_column_colors(X_COLOR, Y_COLOR) - vect_copy = vect.copy() - short_way = VMobject(comp_matrix, vect_copy) - short_way.arrange(buff = 0.1) - short_way.next_to(equals, RIGHT) - - pairs = [ - (rot_matrix, "Rotation"), - (shear_matrix, "Shear"), - (comp_matrix, "Composition"), - ] - for matrix, word in pairs: - brace = Brace(matrix) - text = TextMobject(word).next_to(brace, DOWN) - brace.set_color(matrix.get_color()) - text.set_color(matrix.get_color()) - matrix.add(brace, text) - comp_matrix.split()[-1].set_submobject_colors_by_gradient(TEAL, PINK) - - self.add(vect) - groups = [ - [rot_matrix], - [l_paren, r_paren, shear_matrix], - [equals, comp_matrix, vect_copy], - ] - for group in groups: - self.play(*list(map(Write, group))) - self.wait() - self.play(*list(map(FadeOut, [l_paren, r_paren, vect, vect_copy]))) - comp_matrix.add(equals) - matrices = VMobject(shear_matrix, rot_matrix, comp_matrix) - self.play(ApplyMethod( - matrices.arrange, buff = 0.1, - aligned_edge = UP - )) - self.wait() - - arrow = Arrow(rot_matrix.get_right(), shear_matrix.get_left()) - arrow.shift((rot_matrix.get_top()[1]+0.2)*UP) - words = TextMobject("Read right to left") - words.submobjects.reverse() - words.next_to(arrow, UP) - functions = TexMobject("f(g(x))") - functions.next_to(words, UP) - - self.play(ShowCreation(arrow)) - self.play(Write(words)) - self.wait() - self.play(Write(functions)) - self.wait() - -class MoreComplicatedExampleVisually(LinearTransformationScene): - CONFIG = { - "t_matrix1" : [[1, 1], [-2, 0]], - "t_matrix2" : [[0, 1], [2, 0]], - } - def construct(self): - self.setup() - t_matrix1 = np.array(self.t_matrix1) - t_matrix2 = np.array(self.t_matrix2) - t_m1_inv = np.linalg.inv(t_matrix1.transpose()).transpose() - t_m2_inv = np.linalg.inv(t_matrix2.transpose()).transpose() - - m1_mob, m2_mob, comp_matrix = self.get_matrices() - - self.play(Write(m1_mob)) - self.add_foreground_mobject(m1_mob) - self.wait() - self.apply_transposed_matrix(t_matrix1) - self.wait() - self.play(Write(m1_mob.label)) - self.add_foreground_mobject(m1_mob.label) - self.wait() - self.apply_transposed_matrix(t_m1_inv, run_time = 0) - self.wait() - - self.play(Write(m2_mob)) - self.add_foreground_mobject(m2_mob) - self.wait() - self.apply_transposed_matrix(t_matrix2) - self.wait() - self.play(Write(m2_mob.label)) - self.add_foreground_mobject(m2_mob.label) - self.wait() - self.apply_transposed_matrix(t_m2_inv, run_time = 0) - self.wait() - - for matrix in t_matrix1, t_matrix2: - self.apply_transposed_matrix(matrix, run_time = 1) - self.play(Write(comp_matrix)) - self.add_foreground_mobject(comp_matrix) - self.wait() - self.play(*list(map(FadeOut, [ - self.background_plane, - self.plane, - self.i_hat, - self.j_hat, - ])) + [ - Animation(m) for m in self.foreground_mobjects - ]) - self.remove(self.i_hat, self.j_hat) - self.wait() - - def get_matrices(self): - m1_mob = Matrix(np.array(self.t_matrix1).transpose()) - m2_mob = Matrix(np.array(self.t_matrix2).transpose()) - comp_matrix = Matrix([["?", "?"], ["?", "?"]]) - m1_mob.set_color(YELLOW) - m2_mob.set_color(PINK) - comp_matrix.get_entries().set_submobject_colors_by_gradient(YELLOW, PINK) - - equals = TexMobject("=") - equals.next_to(comp_matrix, LEFT) - comp_matrix.add(equals) - m1_mob = VMobject(BackgroundRectangle(m1_mob), m1_mob) - m2_mob = VMobject(BackgroundRectangle(m2_mob), m2_mob) - comp_matrix = VMobject(BackgroundRectangle(comp_matrix), comp_matrix) - VMobject( - m2_mob, m1_mob, comp_matrix - ).arrange(buff = 0.1).to_corner(UP+LEFT).shift(DOWN) - - for i, mob in enumerate([m1_mob, m2_mob]): - brace = Brace(mob, UP) - text = TexMobject("M_%d"%(i+1)) - text.next_to(brace, UP) - brace.add_background_rectangle() - text.add_background_rectangle() - brace.add(text) - mob.label = brace - return m1_mob, m2_mob, comp_matrix - -class MoreComplicatedExampleNumerically(MoreComplicatedExampleVisually): - def get_result(self): - return np.dot(self.t_matrix1, self.t_matrix2).transpose() - - def construct(self): - m1_mob, m2_mob, comp_matrix = self.get_matrices() - self.add(m1_mob, m2_mob, m1_mob.label, m2_mob.label, comp_matrix) - result = self.get_result() - - col1, col2 = [ - VMobject(*m1_mob.split()[1].get_mob_matrix()[:,i]) - for i in (0, 1) - ] - col1.target_color = X_COLOR - col2.target_color = Y_COLOR - for col in col1, col2: - circle = Circle() - circle.stretch_to_fit_height(m1_mob.get_height()) - circle.stretch_to_fit_width(m1_mob.get_width()/2.5) - circle.set_color(col.target_color) - circle.move_to(col) - col.circle = circle - - triplets = [ - (col1, "i", X_COLOR), - (col2, "j", Y_COLOR), - ] - for i, (col, char, color) in enumerate(triplets): - self.add(col) - start_state = self.get_mobjects() - question = TextMobject( - "Where does $\\hat{\\%smath}$ go?"%char - ) - question.split()[-4].set_color(color) - question.split()[-5].set_color(color) - question.scale(1.2) - question.shift(DOWN) - first = TextMobject("First here") - first.set_color(color) - first.shift(DOWN+LEFT) - first_arrow = Arrow( - first, col.circle.get_bottom(), color = color - ) - second = TextMobject("Then to whatever this is") - second.set_color(color) - second.to_edge(RIGHT).shift(DOWN) - - m2_copy = m2_mob.copy() - m2_target = m2_mob.copy() - m2_target.next_to(m2_mob, DOWN, buff = 1) - col_vect = Matrix(col.copy().split()) - col_vect.set_color(color) - col_vect.next_to(m2_target, RIGHT, buff = 0.1) - second_arrow = Arrow(second, col_vect, color = color) - - new_m2_copy = m2_mob.copy().split()[1] - intermediate = VMobject( - TexMobject("="), - col_vect.copy().get_entries().split()[0], - Matrix(new_m2_copy.get_mob_matrix()[:,0]), - TexMobject("+"), - col_vect.copy().get_entries().split()[1], - Matrix(new_m2_copy.get_mob_matrix()[:,1]), - TexMobject("=") - ) - intermediate.arrange(buff = 0.1) - intermediate.next_to(col_vect, RIGHT) - - product = Matrix(result[:,i]) - product.next_to(intermediate, RIGHT) - - comp_col = VMobject(*comp_matrix.split()[1].get_mob_matrix()[:,i]) - - self.play(Write(question, run_time = 1 )) - self.wait() - self.play( - Transform(question, first), - ShowCreation(first_arrow), - ShowCreation(col.circle), - ApplyMethod(col.set_color, col.target_color) - ) - self.wait() - self.play( - Transform(m2_copy, m2_target, run_time = 2), - ApplyMethod(col.copy().move_to, col_vect, run_time = 2), - Write(col_vect.get_brackets()), - Transform(first_arrow, second_arrow), - Transform(question, second), - ) - self.wait() - self.play(*list(map(FadeOut, [question, first_arrow]))) - self.play(Write(intermediate)) - self.wait() - self.play(Write(product)) - self.wait() - product_entries = product.get_entries() - self.play( - ApplyMethod(comp_col.set_color, BLACK), - ApplyMethod(product_entries.move_to, comp_col) - ) - self.wait() - - start_state.append(product_entries) - self.play(*[ - FadeOut(mob) - for mob in self.get_mobjects() - if mob not in start_state - ] + [ - Animation(product_entries) - ]) - self.wait() - -class GeneralMultiplication(MoreComplicatedExampleNumerically): - def get_result(self): - entries = list(map(TexMobject, [ - "ae+bg", "af+bh", "ce+dg", "cf+dh" - ])) - for mob in entries: - mob.split()[0].set_color(PINK) - mob.split()[3].set_color(PINK) - for mob in entries[0], entries[2]: - mob.split()[1].set_color(X_COLOR) - mob.split()[4].set_color(X_COLOR) - for mob in entries[1], entries[3]: - mob.split()[1].set_color(Y_COLOR) - mob.split()[4].set_color(Y_COLOR) - return np.array(entries).reshape((2, 2)) - - def get_matrices(self): - m1, m2, comp = MoreComplicatedExampleNumerically.get_matrices(self) - self.add(m1, m2, m1.label, m2.label, comp) - m1_entries = m1.split()[1].get_entries() - m2_entries = m2.split()[1].get_entries() - m2_entries_target = VMobject(*[ - TexMobject(char).move_to(entry).set_color(entry.get_color()) - for entry, char in zip(m2_entries.split(), "abcd") - ]) - m1_entries_target = VMobject(*[ - TexMobject(char).move_to(entry).set_color(entry.get_color()) - for entry, char in zip(m1_entries.split(), "efgh") - ]) - - words = TextMobject("This method works generally") - self.play(Write(words, run_time = 2)) - self.play(Transform( - m1_entries, m1_entries_target, - lag_ratio = 0.5 - )) - self.play(Transform( - m2_entries, m2_entries_target, - lag_ratio = 0.5 - )) - self.wait() - - new_comp = Matrix(self.get_result()) - new_comp.next_to(comp.split()[1].submobjects[-1], RIGHT) - new_comp.get_entries().set_color(BLACK) - self.play( - Transform(comp.split()[1].get_brackets(), new_comp.get_brackets()), - *[ - ApplyMethod(q_mark.move_to, entry) - for q_mark, entry in zip( - comp.split()[1].get_entries().split(), - new_comp.get_entries().split() - ) - ] - ) - self.wait() - self.play(FadeOut(words)) - return m1, m2, comp - -class MoreComplicatedExampleWithJustIHat(MoreComplicatedExampleVisually): - CONFIG = { - "show_basis_vectors" : False, - "v_coords" : [1, 0], - "v_color" : X_COLOR, - } - def construct(self): - self.setup() - self.add_vector(self.v_coords, self.v_color) - self.apply_transposed_matrix(self.t_matrix1) - self.wait() - self.apply_transposed_matrix(self.t_matrix2) - self.wait() - -class MoreComplicatedExampleWithJustJHat(MoreComplicatedExampleWithJustIHat): - CONFIG = { - "v_coords" : [0, 1], - "v_color" : Y_COLOR, - } - -class RoteMatrixMultiplication(NumericalMatrixMultiplication): - CONFIG = { - "left_matrix" : [[-3, 1], [2, 5]], - "right_matrix" : [[5, 3], [7, -3]] - } - -class NeverForget(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says("Never forget what \\\\ this represents!") - self.random_blink() - self.student_thinks("", student_index = 0) - def warp(point): - point += 2*DOWN+RIGHT - return 20*point/get_norm(point) - self.play(ApplyPointwiseFunction( - warp, - VMobject(*self.get_mobjects()) - )) - -class AskAboutCommutativity(Scene): - def construct(self): - l_m1, l_m2, eq, r_m2, r_m1 = TexMobject([ - "M_1", "M_2", "=", "M_2", "M_1" - ]).scale(1.5).split() - VMobject(l_m1, r_m1).set_color(YELLOW) - VMobject(l_m2, r_m2).set_color(PINK) - q_marks = TextMobject("???") - q_marks.set_color(TEAL) - q_marks.next_to(eq, UP) - neq = TexMobject("\\neq") - neq.move_to(eq) - - self.play(*list(map(Write, [l_m1, l_m2, eq]))) - self.play( - Transform(l_m1.copy(), r_m1), - Transform(l_m2.copy(), r_m2), - path_arc = -np.pi, - run_time = 2 - ) - self.play(Write(q_marks)) - self.wait() - self.play(Transform( - VMobject(eq, q_marks), - VMobject(neq), - lag_ratio = 0.5 - )) - self.wait() - -class ShowShear(LinearTransformationScene): - CONFIG = { - "title" : "Shear", - "title_color" : PINK, - "t_matrix" : [[1, 0], [1, 1]] - } - def construct(self): - self.setup() - title = TextMobject(self.title) - title.scale(1.5).to_edge(UP) - title.set_color(self.title_color) - title.add_background_rectangle() - self.add_foreground_mobject(title) - - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.wait() - -class ShowRotation(ShowShear): - CONFIG = { - "title" : "$90^\\circ$ rotation", - "title_color" : YELLOW, - "t_matrix" : [[0, 1], [-1, 0]] - } - -class FirstShearThenRotation(LinearTransformationScene): - CONFIG = { - "title" : "First shear then rotation", - "t_matrix1" : [[1, 0], [1, 1]], - "t_matrix2" : [[0, 1], [-1, 0]], - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_WIDTH, - "secondary_line_ratio" : 0 - }, - } - def construct(self): - self.setup() - title_parts = self.title.split(" ") - title = TextMobject(title_parts) - for i, part in enumerate(title_parts): - if part == "rotation": - title.split()[i].set_color(YELLOW) - elif part == "shear": - title.split()[i].set_color(PINK) - title.scale(1.5) - self.add_title(title) - - self.apply_transposed_matrix(self.t_matrix1) - self.apply_transposed_matrix(self.t_matrix2) - self.i_hat.rotate(-0.01)##Laziness - self.wait() - self.write_vector_coordinates(self.i_hat, color = X_COLOR) - self.wait() - self.write_vector_coordinates(self.j_hat, color = Y_COLOR) - self.wait() - -class RotationThenShear(FirstShearThenRotation): - CONFIG = { - "title" : "First rotation then shear", - "t_matrix1" : [[0, 1], [-1, 0]], - "t_matrix2" : [[1, 0], [1, 1]], - } - -class NoticeTheLackOfComputations(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says(""" - Notice the lack - of computations! - """) - self.random_blink() - - students = self.get_students() - random.shuffle(students) - unit = np.array([-0.5, 0.5]) - self.play(*[ - ApplyMethod( - pi.change_mode, "pondering", - rate_func = squish_rate_func(smooth, *np.clip(unit+0.5*i, 0, 1)) - ) - for i, pi in enumerate(students) - ]) - self.random_blink() - self.wait() - -class AskAssociativityQuestion(Scene): - def construct(self): - morty = Mortimer() - morty.scale(0.8) - morty.to_corner(DOWN+RIGHT) - morty.shift(0.5*LEFT) - title = TextMobject("Associativity:") - title.to_corner(UP+LEFT) - - lhs = TexMobject(list("(AB)C")) - lp, a, b, rp, c = lhs.split() - rhs = VMobject(*[m.copy() for m in (a, lp, b, c, rp)]) - point = VectorizedPoint() - start = VMobject(*[m.copy() for m in (point, a, b, point, c)]) - for mob in lhs, rhs, start: - mob.arrange(buff = 0.1) - a, lp, b, c, rp = rhs.split() - rhs = VMobject(lp, a, b, rp, c)##Align order to lhs - eq = TexMobject("=") - q_marks = TextMobject("???") - q_marks.set_submobject_colors_by_gradient(TEAL_B, TEAL_D) - q_marks.next_to(eq, UP) - lhs.next_to(eq, LEFT) - rhs.next_to(eq, RIGHT) - start.move_to(lhs) - - - self.add(morty, title) - self.wait() - self.play(Blink(morty)) - self.play(Write(start)) - self.wait() - self.play(Transform(start, lhs)) - self.wait() - self.play( - Transform(lhs, rhs, path_arc = -np.pi), - Write(eq) - ) - self.play(Write(q_marks)) - self.play(Blink(morty)) - self.play(morty.change_mode, "pondering") - - lp, a, b, rp, c = start.split() - self.show_full_matrices(morty, a, b, c, title) - - def show_full_matrices(self, morty, a, b, c, title): - everything = self.get_mobjects() - everything.remove(morty) - everything.remove(title) - everything = VMobject(*everything) - - matrices = list(map(matrix_to_mobject, [ - np.array(list(m)).reshape((2, 2)) - for m in ("abcd", "efgh", "ijkl") - ])) - VMobject(*matrices).arrange() - - self.play(everything.to_edge, UP) - for letter, matrix in zip([a, b, c], matrices): - self.play(Transform( - letter.copy(), matrix, - lag_ratio = 0.5 - )) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(matrix) - self.wait() - self.move_matrix_parentheses(morty, matrices) - - def move_matrix_parentheses(self, morty, matrices): - m1, m2, m3 = matrices - parens = TexMobject(["(", ")"]) - parens.set_height(1.2*m1.get_height()) - lp, rp = parens.split() - state1 = VMobject( - VectorizedPoint(m1.get_left()), - m1, m2, - VectorizedPoint(m2.get_right()), - m3 - ) - state2 = VMobject(*[ - m.copy() for m in (lp, m1, m2, rp, m3) - ]) - state3 = VMobject(*[ - m.copy() for m in (m1, lp, m2, m3, rp) - ]) - for state in state2, state3: - state.arrange(RIGHT, buff = 0.1) - m1, lp, m2, m3, rp = state3.split() - state3 = VMobject(lp, m1, m2, rp, m3) - - self.play(morty.change_mode, "angry") - for state in state2, state3: - self.play(Transform(state1, state)) - self.wait() - self.play(morty.change_mode, "confused") - self.wait() - -class ThreeSuccessiveTransformations(LinearTransformationScene): - CONFIG = { - "t_matrices" : [ - [[2, 1], [1, 2]], - [[np.cos(-np.pi/6), np.sin(-np.pi/6)], [-np.sin(-np.pi/6), np.cos(-np.pi/6)]], - [[1, 0], [1, 1]] - ], - "symbols_str" : "A(BC)", - "include_background_plane" : False, - } - def construct(self): - self.setup() - symbols = TexMobject(list(self.symbols_str)) - symbols.scale(1.5) - symbols.to_edge(UP) - a, b, c = None, None, None - for mob, letter in zip(symbols.split(), self.symbols_str): - if letter == "A": - a = mob - elif letter == "B": - b = mob - elif letter == "C": - c = mob - - symbols.add_background_rectangle() - self.add_foreground_mobject(symbols) - - brace = Brace(c, DOWN) - words = TextMobject("Apply this transformation") - words.add_background_rectangle() - words.next_to(brace, DOWN) - brace.add(words) - - self.play(Write(brace, run_time = 1)) - self.add_foreground_mobject(brace) - - last = VectorizedPoint() - for t_matrix, sym in zip(self.t_matrices, [c, b, a]): - self.play( - brace.next_to, sym, DOWN, - sym.set_color, YELLOW, - last.set_color, WHITE - ) - self.apply_transposed_matrix(t_matrix, run_time = 1) - last = sym - self.wait() - -class ThreeSuccessiveTransformationsAltParens(ThreeSuccessiveTransformations): - CONFIG = { - "symbols_str" : "(AB)C" - } - -class ThreeSuccessiveTransformationsSimple(ThreeSuccessiveTransformations): - CONFIG = { - "symbols_str" : "ABC" - } - -class ExplanationTrumpsProof(Scene): - def construct(self): - greater = TexMobject(">") - greater.shift(RIGHT) - explanation = TextMobject("Good explanation") - explanation.set_color(BLUE) - proof = TextMobject("Symbolic proof") - proof.set_color(LIGHT_BROWN) - explanation.next_to(greater, LEFT) - proof.next_to(greater, RIGHT) - explanation.get_center = lambda : explanation.get_right() - proof.get_center = lambda : proof.get_left() - - self.play( - Write(explanation), - Write(greater), - Write(proof), - run_time = 1 - ) - self.play( - explanation.scale_in_place, 1.5, - proof.scale_in_place, 0.7 - ) - self.wait() - -class GoPlay(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says("Go play!", height = 3, width = 5) - self.play(*[ - ApplyMethod(student.change_mode, "happy") - for student in self.get_students() - ]) - self.random_blink() - student = self.get_students()[-1] - bubble = ThoughtBubble(direction = RIGHT, width = 6, height = 5) - bubble.pin_to(student, allow_flipping = False) - bubble.make_green_screen() - self.play( - ShowCreation(bubble), - student.look, UP+LEFT, - ) - self.play(student.change_mode, "pondering") - for x in range(3): - self.random_blink() - self.wait(2) - -class NextVideo(Scene): - def construct(self): - title = TextMobject(""" - Next video: Linear transformations in three dimensions - """) - title.set_width(FRAME_WIDTH - 2) - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/chapter5.py b/from_3b1b/old/eola/chapter5.py deleted file mode 100644 index 60e5b607..00000000 --- a/from_3b1b/old/eola/chapter5.py +++ /dev/null @@ -1,1131 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eola.chapter3 import MatrixVectorMultiplicationAbstract - - -class Blob(Circle): - CONFIG = { - "stroke_color" : TEAL, - "fill_color" : BLUE_E, - "fill_opacity" : 1, - "random_seed" : 1, - "random_nudge_size" : 0.5, - "height" : 2, - } - def __init__(self, **kwargs): - Circle.__init__(self, **kwargs) - random.seed(self.random_seed) - self.apply_complex_function( - lambda z : z*(1+self.random_nudge_size*(random.random()-0.5)) - ) - self.set_height(self.height).center() - - def probably_contains(self, point): - border_points = np.array(self.get_anchors_and_handles()[0]) - distances = [get_norm(p-point) for p in border_points] - min3 = border_points[np.argsort(distances)[:3]] - center_direction = self.get_center() - point - in_center_direction = [np.dot(p-point, center_direction) > 0 for p in min3] - return sum(in_center_direction) <= 2 - -class RightHand(VMobject): - def __init__(self, **kwargs): - hand = SVGMobject("RightHandOutline") - self.inlines = VMobject(*hand.split()[:-4]) - self.outline = VMobject(*hand.split()[-4:]) - self.outline.set_stroke(color = WHITE, width = 5) - self.inlines.set_stroke(color = DARK_GREY, width = 3) - VMobject.__init__(self, self.outline, self.inlines) - self.center().set_height(3) - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject([ - "``The purpose of computation is \\\\", - "insight", - ", not ", - "numbers.", - "''", - ], arg_separator = "") - # words.set_width(FRAME_WIDTH - 2) - words.to_edge(UP) - words.split()[1].set_color(BLUE) - words.split()[3].set_color(GREEN) - author = TextMobject("-Richard Hamming") - author.set_color(YELLOW) - author.next_to(words, DOWN, buff = 0.5) - - self.play(FadeIn(words)) - self.wait(2) - self.play(Write(author, run_time = 3)) - self.wait() - -class MovingForward(TeacherStudentsScene): - def construct(self): - self.setup() - student = self.get_students()[1] - bubble = student.get_bubble(direction = RIGHT, width = 5) - bubble.rotate(-np.pi/12) - bubble.next_to(student, UP, aligned_edge = RIGHT) - bubble.shift(0.5*LEFT) - bubble.make_green_screen() - - self.teacher_says(""" - Y'all know about linear - transformations, right? - """, width = 7) - self.play( - ShowCreation(bubble), - student.change_mode, "pondering" - ) - self.wait(2) - -class StretchingTransformation(LinearTransformationScene): - def construct(self): - self.setup() - self.add_title("Generally stretches space") - self.apply_transposed_matrix([[3, 1], [-1, 2]]) - self.wait() - -class SquishingTransformation(LinearTransformationScene): - CONFIG = { - "foreground_plane_kwargs" : { - "x_radius" : 3*FRAME_X_RADIUS, - "y_radius" : 3*FRAME_X_RADIUS, - "secondary_line_ratio" : 0 - }, - } - def construct(self): - self.setup() - self.add_title("Generally squishes space") - self.apply_transposed_matrix([[1./2, -0.5], [1, 1./3]]) - self.wait() - -class AskAboutStretching(LinearTransformationScene): - def construct(self): - self.setup() - words = TextMobject(""" - Exactly how much are - things being stretched? - """) - words.add_background_rectangle() - words.to_corner(UP+RIGHT) - words.set_color(YELLOW) - self.apply_transposed_matrix( - [[2, 1], [-1, 3]], - added_anims = [Write(words)] - ) - self.wait() - -class AskAboutStretchingSpecifically(LinearTransformationScene): - def construct(self): - self.setup() - self.add_title(["How much are", "areas", "scaled?"]) - hma, areas, scaled = self.title.split()[1].split() - areas.set_color(YELLOW) - blob = Blob().shift(UP+RIGHT) - - label = TextMobject("Area") - label.set_color(YELLOW) - label = VMobject(VectorizedPoint(label.get_left()), label) - label.move_to(blob) - target_label = TexMobject(["c \\cdot", "\\text{Area}"]) - target_label.split()[1].set_color(YELLOW) - - self.add_transformable_mobject(blob) - self.add_moving_mobject(label, target_label) - self.wait() - self.apply_transposed_matrix([[2, -1], [1, 1]]) - arrow = Arrow(scaled, label.target.split()[0]) - self.play(ShowCreation(arrow)) - self.wait() - -class BeautyNowUsesLater(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says("Beauty now, uses later") - self.wait() - -class DiagonalExample(LinearTransformationScene): - CONFIG = { - "show_square" : False, - "show_coordinates" : True, - "transposed_matrix" : [[3, 0], [0, 2]] - } - def construct(self): - self.setup() - matrix = Matrix(np.array(self.transposed_matrix).transpose()) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.next_to(ORIGIN, LEFT).to_edge(UP) - matrix_background = BackgroundRectangle(matrix) - self.play(ShowCreation(matrix_background), Write(matrix)) - if self.show_square: - self.add_unit_square(animate = True) - self.add_foreground_mobject(matrix_background, matrix) - self.wait() - self.apply_transposed_matrix([self.transposed_matrix[0], [0, 1]]) - self.apply_transposed_matrix([[1, 0], self.transposed_matrix[1]]) - self.wait() - if self.show_square: - - - bottom_brace = Brace(self.i_hat, DOWN) - right_brace = Brace(self.square, RIGHT) - width = TexMobject(str(self.transposed_matrix[0][0])) - height = TexMobject(str(self.transposed_matrix[1][1])) - width.next_to(bottom_brace, DOWN) - height.next_to(right_brace, RIGHT) - for mob in bottom_brace, width, right_brace, height: - mob.add_background_rectangle() - self.play(Write(mob, run_time = 0.5)) - self.wait() - - width_target, height_target = width.copy(), height.copy() - det = np.linalg.det(self.transposed_matrix) - times, eq_det = list(map(TexMobject, ["\\times", "=%d"%det])) - words = TextMobject("New area $=$") - equation = VMobject( - words, width_target, times, height_target, eq_det - ) - equation.arrange(RIGHT, buff = 0.2) - equation.next_to(self.square, UP, aligned_edge = LEFT) - equation.shift(0.5*RIGHT) - background_rect = BackgroundRectangle(equation) - - self.play( - ShowCreation(background_rect), - Transform(width.copy(), width_target), - Transform(height.copy(), height_target), - *list(map(Write, [words, times, eq_det])) - ) - self.wait() - -class DiagonalExampleWithSquare(DiagonalExample): - CONFIG = { - "show_square" : True - } - -class ShearExample(DiagonalExample): - CONFIG = { - "show_square" : False, - "show_coordinates" : True, - "transposed_matrix" : [[1, 0], [1, 1]] - } - -class ShearExampleWithSquare(DiagonalExample): - CONFIG = { - "show_square" : True, - "show_coordinates" : True, - "show_coordinates" : False, - "transposed_matrix" : [[1, 0], [1, 1]] - } - -class ThisSquareTellsEverything(LinearTransformationScene): - def construct(self): - self.setup() - self.add_unit_square() - words = TextMobject(""" - This square gives you - everything you need. - """) - words.to_corner(UP+RIGHT) - words.set_color(YELLOW) - words.add_background_rectangle() - arrow = Arrow( - words.get_bottom(), self.square.get_right(), - color = WHITE - ) - - self.play(Write(words, run_time = 2)) - self.play(ShowCreation(arrow)) - self.add_foreground_mobject(words, arrow) - self.wait() - self.apply_transposed_matrix([[1.5, -0.5], [1, 1.5]]) - self.wait() - -class WhatHappensToOneSquareHappensToAll(LinearTransformationScene): - def construct(self): - self.setup() - self.add_unit_square() - pairs = [ - (2*RIGHT+UP, 1), - (3*LEFT, 2), - (2*LEFT+DOWN, 0.5), - (3.5*RIGHT+2.5*UP, 1.5), - (RIGHT+2*DOWN, 0.25), - (3*LEFT+3*DOWN, 1), - ] - squares = VMobject() - for position, side_length in pairs: - square = self.square.copy() - square.scale(side_length) - square.shift(position) - squares.add(square) - self.play(FadeIn( - squares, lag_ratio = 0.5, - run_time = 3 - )) - self.add_transformable_mobject(squares) - self.apply_transposed_matrix([[1, -1], [0.5, 1]]) - self.wait() - -class BreakBlobIntoGridSquares(LinearTransformationScene): - CONFIG = { - "square_size" : 0.5, - "blob_height" : 3, - } - def construct(self): - self.setup() - blob = Blob( - height = self.blob_height, - random_seed = 5, - random_nudge_size = 0.2, - ) - blob.next_to(ORIGIN, UP+RIGHT) - self.add_transformable_mobject(blob) - arange = np.arange( - 0, self.blob_height + self.square_size, - self.square_size - ) - square = Square(side_length = self.square_size) - square.set_stroke(YELLOW, width = 2) - square.set_fill(YELLOW, opacity = 0.3) - squares = VMobject() - for x, y in it.product(*[arange]*2): - point = x*RIGHT + y*UP - if blob.probably_contains(point): - squares.add(square.copy().shift(point)) - self.play(ShowCreation( - squares, lag_ratio = 0.5, - run_time = 2, - )) - self.add_transformable_mobject(squares) - self.wait() - self.apply_transposed_matrix([[1, -1], [0.5, 1]]) - self.wait() - -class BreakBlobIntoGridSquaresGranular(BreakBlobIntoGridSquares): - CONFIG = { - "square_size" : 0.25 - } - -class BreakBlobIntoGridSquaresMoreGranular(BreakBlobIntoGridSquares): - CONFIG = { - "square_size" : 0.15 - } - -class BreakBlobIntoGridSquaresVeryGranular(BreakBlobIntoGridSquares): - CONFIG = { - "square_size" : 0.1 - } - -class NameDeterminant(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[3, 0], [2, 2]] - } - def construct(self): - self.setup() - self.plane.fade(0.3) - self.add_unit_square(color = YELLOW_E, opacity = 0.5) - self.add_title( - ["The", "``determinant''", "of a transformation"], - scale_factor = 1 - ) - self.title.split()[1].split()[1].set_color(YELLOW) - - matrix_background, matrix, det_text = self.get_matrix() - self.add_foreground_mobject(matrix_background, matrix) - - A = TexMobject("A") - area_label = VMobject(A.copy(), A.copy(), A) - area_label.move_to(self.square) - det = np.linalg.det(self.t_matrix) - if np.round(det) == det: - det = int(det) - area_label_target = VMobject( - TexMobject(str(det)), TexMobject("\\cdot"), A.copy() - ) - if det < 1 and det > 0: - area_label_target.scale(det) - area_label_target.arrange(RIGHT, buff = 0.1) - self.add_moving_mobject(area_label, area_label_target) - - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.wait() - det_mob_copy = area_label.split()[0].copy() - new_det_mob = det_mob_copy.copy().set_height( - det_text.split()[0].get_height() - ) - new_det_mob.next_to(det_text, RIGHT, buff = 0.2) - new_det_mob.add_background_rectangle() - det_mob_copy.add_background_rectangle(opacity = 0) - self.play(Write(det_text)) - self.play(Transform(det_mob_copy, new_det_mob)) - self.wait() - - - def get_matrix(self): - matrix = Matrix(np.array(self.t_matrix).transpose()) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.next_to(self.title, DOWN, buff = 0.5) - matrix.shift(2*LEFT) - matrix_background = BackgroundRectangle(matrix) - det_text = get_det_text(matrix, 0) - det_text.remove(det_text.split()[-1]) - return matrix_background, matrix, det_text - -class SecondDeterminantExample(NameDeterminant): - CONFIG = { - "t_matrix" : [[-1, -1], [1, -1]] - } - -class DeterminantIsThree(NameDeterminant): - CONFIG = { - "t_matrix" : [[0, -1.5], [2, 1]] - } - -class DeterminantIsOneHalf(NameDeterminant): - CONFIG = { - "t_matrix" : [[0.5, -0.5], [0.5, 0.5]], - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_WIDTH, - "secondary_line_ratio" : 0 - }, - } - -class DeterminantIsZero(NameDeterminant): - CONFIG = { - "t_matrix" : [[4, 2], [2, 1]], - } - -class SecondDeterminantIsZeroExamlpe(NameDeterminant): - CONFIG = { - "t_matrix" : [[0, 0], [0, 0]], - "show_basis_vectors" : False - } - -class NextFewVideos(Scene): - def construct(self): - icon = SVGMobject("video_icon") - icon.center() - icon.set_width(FRAME_WIDTH/12.) - icon.set_stroke(color = WHITE, width = 0) - icon.set_fill(WHITE, opacity = 1) - icons = VMobject(*[icon.copy() for x in range(10)]) - icons.set_submobject_colors_by_gradient(BLUE_A, BLUE_D) - icons.arrange(RIGHT) - icons.to_edge(LEFT) - - self.play( - FadeIn(icons, lag_ratio = 0.5), - run_time = 3 - ) - self.wait() - -class UnderstandingBeforeApplication(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says(""" - Just the visual - understanding for now - """) - self.random_blink() - self.wait() - -class WhatIveSaidSoFar(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says(""" - What I've said so far - is not quite right... - """) - self.wait() - -class NegativeDeterminant(Scene): - def construct(self): - numerical_matrix = [[1, 2], [3, 4]] - matrix = Matrix(numerical_matrix) - matrix.set_column_colors(X_COLOR, Y_COLOR) - det_text = get_det_text(matrix, np.linalg.det(numerical_matrix)) - words = TextMobject(""" - How can you scale area - by a negative number? - """) - words.set_color(YELLOW) - words.to_corner(UP+RIGHT) - det_num = det_text.split()[-1] - arrow = Arrow(words.get_bottom(), det_num) - - self.add(matrix) - self.play(Write(det_text)) - self.wait() - self.play( - Write(words, run_time = 2), - ShowCreation(arrow) - ) - self.play(det_num.set_color, YELLOW) - self.wait() - -class FlipSpaceOver(Scene): - def construct(self): - plane1 = NumberPlane(y_radius = FRAME_X_RADIUS) - plane2 = NumberPlane( - y_radius = FRAME_X_RADIUS, - color = RED_D, secondary_color = RED_E - ) - axis = UP - for word, plane in ("Front", plane1), ("Back", plane2): - text = TextMobject(word) - if word == "Back": - text.rotate(np.pi, axis = axis) - text.scale(2) - text.next_to(ORIGIN, RIGHT).to_edge(UP) - text.add_background_rectangle() - plane.add(text) - - self.play(ShowCreation( - plane1, lag_ratio = 0.5, - run_time = 1 - )) - self.wait() - self.play(Rotate( - plane1, axis = axis, - rate_func = lambda t : smooth(t/2), - run_time = 1.5, - path_arc = np.pi/2, - )) - self.remove(plane1) - self.play(Rotate( - plane2, axis = axis, - rate_func = lambda t : smooth((t+1)/2), - run_time = 1.5, - path_arc = np.pi/2, - )) - self.wait() - -class RandyThinking(Scene): - def construct(self): - randy = Randolph().to_corner() - bubble = randy.get_bubble() - bubble.make_green_screen() - - self.play( - randy.change_mode, "pondering", - ShowCreation(bubble) - ) - self.wait() - self.play(Blink(randy)) - self.wait(2) - self.play(Blink(randy)) - -class NegativeDeterminantTransformation(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[1, 1], [2, -1]], - } - def construct(self): - self.setup() - self.add_title("Feels like flipping space") - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.wait() - -class ThinkAboutFlippingPaper(Scene): - def construct(self): - pass - -class NegativeDeterminantTransformation2(NegativeDeterminantTransformation): - CONFIG ={ - "t_matrix" : [[-2, 1], [2, 1]] - } - -class IHatJHatOrientation(NegativeDeterminantTransformation): - def construct(self): - self.setup() - i_label, j_label = self.get_basis_vector_labels() - self.add_transformable_label(self.i_hat, i_label, color = X_COLOR) - self.add_transformable_label(self.j_hat, j_label, color = Y_COLOR) - - arc = Arc(start_angle = 0, angle = np.pi/2, color = YELLOW) - arc.shift(0.5*(RIGHT+UP)).scale(1/1.6) - arc.add_tip() - words1 = TextMobject([ - "$\\hat{\\jmath}$", - "is to the", - "left", - "of", - "$\\hat{\\imath}$", - ]) - words1.split()[0].set_color(Y_COLOR) - words1.split()[2].set_color(YELLOW) - words1.split()[-1].set_color(X_COLOR) - words1.add_background_rectangle() - words1.next_to(arc, UP+RIGHT) - - words2 = TextMobject([ - "$L(\\hat{\\jmath})$", - "is to the \\\\", - "\\emph{right}", - "of", - "$L(\\hat{\\imath})$", - ]) - words2.split()[0].set_color(Y_COLOR) - words2.split()[2].set_color(YELLOW) - words2.split()[-1].set_color(X_COLOR) - words2.add_background_rectangle() - - - self.play(ShowCreation(arc)) - self.play(Write(words1)) - self.wait() - self.remove(words1, arc) - self.apply_transposed_matrix(self.t_matrix) - arc.submobjects = [] - arc.apply_function(self.get_matrix_transformation(self.t_matrix)) - arc.add_tip() - words2.next_to(arc, RIGHT) - self.play( - ShowCreation(arc), - Write(words2, run_time = 2), - ) - self.wait() - title = TextMobject("Orientation has been reversed") - title.to_edge(UP) - title.add_background_rectangle() - self.play(Write(title, run_time = 1)) - self.wait() - -class WriteNegativeDeterminant(NegativeDeterminantTransformation): - def construct(self): - self.setup() - self.add_unit_square() - matrix = Matrix(np.array(self.t_matrix).transpose()) - matrix.next_to(ORIGIN, LEFT) - matrix.to_edge(UP) - matrix.set_column_colors(X_COLOR, Y_COLOR) - - det_text = get_det_text( - matrix, determinant = np.linalg.det(self.t_matrix) - ) - three = VMobject(*det_text.split()[-1].split()[1:]) - for mob in det_text.split(): - if isinstance(mob, TexMobject): - mob.add_background_rectangle() - matrix_background = BackgroundRectangle(matrix) - self.play( - ShowCreation(matrix_background), - Write(matrix), - Write(det_text), - ) - self.add_foreground_mobject(matrix_background, matrix, det_text) - self.wait() - self.apply_transposed_matrix(self.t_matrix) - - self.play(three.copy().move_to, self.square) - self.wait() - -class AltWriteNegativeDeterminant(WriteNegativeDeterminant): - CONFIG = { - "t_matrix" : [[2, -1], [1, -3]] - } - -class WhyNegativeScaling(TeacherStudentsScene): - def construct(self): - self.setup() - self.student_says(""" - Why does negative area - relate to orientation-flipping? - """) - other_students = np.array(self.get_students())[[0, 2]] - self.play(*[ - ApplyMethod(student.change_mode, "confused") - for student in other_students - ]) - self.random_blink() - self.wait() - self.random_blink() - -class SlowlyRotateIHat(LinearTransformationScene): - def construct(self): - self.setup() - self.add_unit_square() - self.apply_transposed_matrix( - [[-1, 0], [0, 1]], - path_arc = np.pi, - run_time = 30, - rate_func=linear, - ) - -class DeterminantGraphForRotatingIHat(Scene): - def construct(self): - t_axis = NumberLine( - numbers_with_elongated_ticks = [], - x_min = 0, - x_max = 10, - color = WHITE, - ) - det_axis = NumberLine( - numbers_with_elongated_ticks = [], - x_min = -2, - x_max = 2, - color = WHITE - ) - det_axis.rotate(np.pi/2) - t_axis.next_to(ORIGIN, RIGHT, buff = 0) - det_axis.move_to(t_axis.get_left()) - axes = VMobject(det_axis, t_axis) - graph = FunctionGraph(np.cos, x_min = 0, x_max = np.pi) - graph.next_to(det_axis, RIGHT, buff = 0) - graph.set_color(YELLOW) - det_word = TextMobject("Det") - det_word.next_to(det_axis, RIGHT, aligned_edge = UP) - time_word = TextMobject("time") - time_word.next_to(t_axis, UP) - time_word.to_edge(RIGHT) - everything = VMobject(axes, det_word, time_word, graph) - everything.scale(1.5) - - self.add(axes, det_word, time_word) - self.play(ShowCreation( - graph, rate_func=linear, run_time = 10 - )) - -class WhatAboutThreeDimensions(TeacherStudentsScene): - def construct(self): - self.setup() - self.student_says(""" - What about 3D - transformations? - """) - self.random_blink() - self.wait() - self.random_blink() - -class Transforming3DCube(Scene): - def construct(self): - pass - -class NameParallelepiped(Scene): - def construct(self): - word = TextMobject("``Parallelepiped''") - word.scale(2) - pp_part1 = VMobject(*word.split()[:len(word.split())/2]) - pp_part2 = VMobject(*word.split()[len(word.split())/2:]) - pp_part1.set_submobject_colors_by_gradient(X_COLOR, Y_COLOR) - pp_part2.set_submobject_colors_by_gradient(Y_COLOR, Z_COLOR) - self.play(Write(word)) - self.wait(2) - -class DeterminantIsVolumeOfParallelepiped(Scene): - def construct(self): - matrix = Matrix([[1, 0, 0.5], [0.5, 1, 0], [1, 0, 1]]) - matrix.shift(3*LEFT) - matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR) - det_text = get_det_text(matrix) - eq = TexMobject("=") - eq.next_to(det_text, RIGHT) - words = TextMobject([ - "Volume of this\\\\", - "parallelepiped" - ]) - pp = words.split()[1] - pp_part1 = VMobject(*pp.split()[:len(pp.split())/2]) - pp_part2 = VMobject(*pp.split()[len(pp.split())/2:]) - pp_part1.set_submobject_colors_by_gradient(X_COLOR, Y_COLOR) - pp_part2.set_submobject_colors_by_gradient(Y_COLOR, Z_COLOR) - - words.next_to(eq, RIGHT) - - self.play(Write(matrix)) - self.wait() - self.play(Write(det_text), Write(words), Write(eq)) - self.wait() - -class Degenerate3DTransformation(Scene): - def construct(self): - pass - -class WriteZeroDeterminant(Scene): - def construct(self): - matrix = Matrix([[1, 0, 1], [0.5, 1, 1.5], [1, 0, 1]]) - matrix.shift(2*LEFT) - matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR) - det_text = get_det_text(matrix, 0) - brace = Brace(matrix, DOWN) - words = TextMobject(""" - Columns must be - linearly dependent - """) - words.set_color(YELLOW) - words.next_to(brace, DOWN) - - self.play(Write(matrix)) - self.wait() - self.play(Write(det_text)) - self.wait() - self.play( - GrowFromCenter(brace), - Write(words, run_time = 2) - ) - self.wait() - -class AskAboutNegaive3DDeterminant(TeacherStudentsScene): - def construct(self): - self.setup() - self.student_says(""" - What would det$(M) < 0$ mean? - """) - self.random_blink() - self.play(self.teacher.change_mode, "pondering") - self.wait() - self.random_blink() - -class OrientationReversing3DTransformation(Scene): - def construct(self): - pass - -class RightHandRule(Scene): - CONFIG = { - "flip" : False, - "labels_tex" : ["\\hat{\\imath}", "\\hat{\\jmath}", "\\hat{k}"], - "colors" : [X_COLOR, Y_COLOR, Z_COLOR], - } - def construct(self): - hand = RightHand() - v1 = Vector([-1.75, 0.5]) - v2 = Vector([-1.4, -0.7]) - v3 = Vector([0, 1.7]) - vects = [v1, v2, v3] - if self.flip: - VMobject(hand, *vects).flip() - - v1_label, v2_label, v3_label = [ - TexMobject(label_tex).scale(1.5) - for label_tex in self.labels_tex - ] - v1_label.next_to(v1.get_end(), UP) - v2_label.next_to(v2.get_end(), DOWN) - v3_label.next_to(v3.get_end(), UP) - - labels = [v1_label, v2_label, v3_label] - - # self.add(NumberPlane()) - self.play( - ShowCreation(hand.outline, run_time = 2, rate_func=linear), - FadeIn(hand.inlines) - ) - self.wait() - for vect, label, color in zip(vects, labels, self.colors): - vect.set_color(color) - label.set_color(color) - vect.set_stroke(width = 8) - self.play(ShowCreation(vect)) - self.play(Write(label)) - self.wait() - -class LeftHandRule(RightHandRule): - CONFIG = { - "flip" : True - } - -class AskHowToCompute(TeacherStudentsScene): - def construct(self): - self.setup() - student = self.get_students()[1] - self.student_says("How do you \\\\ compute this?") - self.play(student.change_mode, "confused") - self.random_blink() - self.wait() - self.random_blink() - -class TwoDDeterminantFormula(Scene): - def construct(self): - eq = TextMobject("=") - matrix = Matrix([["a", "b"], ["c", "d"]]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - ma, mb, mc, md = matrix.get_entries().split() - ma.shift(0.1*DOWN) - mc.shift(0.7*mc.get_height()*DOWN) - det_text = get_det_text(matrix) - VMobject(matrix, det_text).next_to(eq, LEFT) - formula = TexMobject(list("ad-bc")) - formula.next_to(eq, RIGHT) - formula.shift(0.1*UP) - - a, d, minus, b, c = formula.split() - VMobject(a, c).set_color(X_COLOR) - VMobject(b, d).set_color(Y_COLOR) - - for mob in mb, mc, b, c: - if mob is c: - mob.zero = TexMobject("\\cdot 0") - else: - mob.zero = TexMobject("0") - mob.zero.move_to(mob, aligned_edge = DOWN+LEFT) - mob.zero.set_color(mob.get_color()) - mob.original = mob.copy() - c.zero.shift(0.1*RIGHT) - - self.add(matrix) - self.play(Write(det_text, run_time = 1)) - self.play(Write(eq), Write(formula)) - self.wait() - self.play(*[ - Transform(m, m.zero) - for m in (mb, mc, b, c) - ]) - self.wait() - for pair in (mb, b), (mc, c): - self.play(*[ - Transform(m, m.original) - for m in pair - ]) - self.wait() - -class TwoDDeterminantFormulaIntuition(LinearTransformationScene): - def construct(self): - self.setup() - self.add_unit_square() - a, b, c, d = 3, 2, 3.5, 2 - - self.wait() - self.apply_transposed_matrix([[a, 0], [0, 1]]) - i_brace = Brace(self.i_hat, DOWN) - width = TexMobject("a").scale(1.5) - i_brace.put_at_tip(width) - width.set_color(X_COLOR) - width.add_background_rectangle() - self.play(GrowFromCenter(i_brace), Write(width)) - self.wait() - - self.apply_transposed_matrix([[1, 0], [0, d]]) - side_brace = Brace(self.square, RIGHT) - height = TexMobject("d").scale(1.5) - side_brace.put_at_tip(height) - height.set_color(Y_COLOR) - height.add_background_rectangle() - self.play(GrowFromCenter(side_brace), Write(height)) - self.wait() - - self.apply_transposed_matrix( - [[1, 0], [float(b)/d, 1]], - added_anims = [ - ApplyMethod(m.shift, b*RIGHT) - for m in (side_brace, height) - ] - ) - self.wait() - self.play(*list(map(FadeOut, [i_brace, side_brace, width, height]))) - matrix1 = np.dot( - [[a, b], [c, d]], - np.linalg.inv([[a, b], [0, d]]) - ) - matrix2 = np.dot( - [[a, b], [-c, d]], - np.linalg.inv([[a, b], [c, d]]) - ) - self.apply_transposed_matrix(matrix1.transpose(), path_arc = 0) - self.wait() - self.apply_transposed_matrix(matrix2.transpose(), path_arc = 0) - self.wait() - -class FullFormulaExplanation(LinearTransformationScene): - def construct(self): - self.setup() - self.add_unit_square() - self.apply_transposed_matrix([[3, 1], [1, 2]], run_time = 0) - self.add_braces() - self.add_polygons() - self.show_formula() - - def get_matrix(self): - matrix = Matrix([["a", "b"], ["c", "d"]]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - ma, mb, mc, md = matrix.get_entries().split() - ma.shift(0.1*DOWN) - mc.shift(0.7*mc.get_height()*DOWN) - matrix.shift(2*DOWN+4*LEFT) - return matrix - - def add_polygons(self): - a = self.i_hat.get_end()[0]*RIGHT - b = self.j_hat.get_end()[0]*RIGHT - c = self.i_hat.get_end()[1]*UP - d = self.j_hat.get_end()[1]*UP - - shapes_colors_and_tex = [ - (Polygon(ORIGIN, a, a+c), MAROON, "ac/2"), - (Polygon(ORIGIN, d+b, d, d), TEAL, "\\dfrac{bd}{2}"), - (Polygon(a+c, a+b+c, a+b+c, a+b+c+d), TEAL, "\\dfrac{bd}{2}"), - (Polygon(b+d, a+b+c+d, b+c+d), MAROON, "ac/2"), - (Polygon(a, a+b, a+b+c, a+c), PINK, "bc"), - (Polygon(d, d+b, d+b+c, d+c), PINK, "bc"), - ] - everyone = VMobject() - for shape, color, tex in shapes_colors_and_tex: - shape.set_stroke(width = 0) - shape.set_fill(color = color, opacity = 0.7) - tex_mob = TexMobject(tex) - tex_mob.scale(0.7) - tex_mob.move_to(shape.get_center_of_mass()) - everyone.add(shape, tex_mob) - self.play(FadeIn( - everyone, - lag_ratio = 0.5, - run_time = 1 - )) - - - - def add_braces(self): - a = self.i_hat.get_end()[0]*RIGHT - b = self.j_hat.get_end()[0]*RIGHT - c = self.i_hat.get_end()[1]*UP - d = self.j_hat.get_end()[1]*UP - - quads = [ - (ORIGIN, a, DOWN, "a"), - (a, a+b, DOWN, "b"), - (a+b, a+b+c, RIGHT, "c"), - (a+b+c, a+b+c+d, RIGHT, "d"), - (a+b+c+d, b+c+d, UP, "a"), - (b+c+d, d+c, UP, "b"), - (d+c, d, LEFT, "c"), - (d, ORIGIN, LEFT, "d"), - ] - everyone = VMobject() - for p1, p2, direction, char in quads: - line = Line(p1, p2) - brace = Brace(line, direction, buff = 0) - text = brace.get_text(char) - text.add_background_rectangle() - if char in ["a", "c"]: - text.set_color(X_COLOR) - else: - text.set_color(Y_COLOR) - everyone.add(brace, text) - self.play(Write(everyone), run_time = 1) - - - def show_formula(self): - matrix = self.get_matrix() - det_text = get_det_text(matrix) - f_str = "=(a+b)(c+d)-ac-bd-2bc=ad-bc" - formula = TexMobject(f_str) - - formula.next_to(det_text, RIGHT) - everyone = VMobject(det_text, matrix, formula) - everyone.set_width(FRAME_WIDTH - 1) - everyone.next_to(DOWN, DOWN) - background_rect = BackgroundRectangle(everyone) - self.play( - ShowCreation(background_rect), - Write(everyone) - ) - self.wait() - -class ThreeDDetFormula(Scene): - def construct(self): - matrix = Matrix([list("abc"), list("def"), list("ghi")]) - matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR) - m1 = Matrix([["e", "f"], ["h", "i"]]) - m1.set_column_colors(Y_COLOR, Z_COLOR) - m2 = Matrix([["d", "f"], ["g", "i"]]) - m2.set_column_colors(X_COLOR, Z_COLOR) - m3 = Matrix([["d", "e"], ["g", "h"]]) - m3.set_column_colors(X_COLOR, Y_COLOR) - - for m in matrix, m1, m2, m3: - m.add(get_det_text(m)) - a, b, c = matrix.get_entries().split()[:3] - parts = it.starmap(VMobject, [ - [matrix], - [TexMobject("="), a.copy(), m1], - [TexMobject("-"), b.copy(), m2], - [TexMobject("+"), c.copy(), m3], - ]) - parts = list(parts) - for part in parts: - part.arrange(RIGHT, buff = 0.2) - parts[1].next_to(parts[0], RIGHT) - parts[2].next_to(parts[1], DOWN, aligned_edge = LEFT) - parts[3].next_to(parts[2], DOWN, aligned_edge = LEFT) - everyone = VMobject(*parts) - everyone.center().to_edge(UP) - for part in parts: - self.play(Write(part)) - self.wait(2) - -class QuizTime(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says("Quiz time!") - self.random_blink() - self.wait() - self.random_blink() - -class ProductProperty(Scene): - def construct(self): - lhs = TexMobject([ - "\\text{det}(", - "M_1", - "M_2", - ")" - ]) - det, m1, m2, rp = lhs.split() - m1.set_color(TEAL) - m2.set_color(PINK) - - rhs = TexMobject([ - "=\\text{det}(", - "M_1", - ")\\text{det}(", - "M_2", - ")" - ]) - rhs.split()[1].set_color(TEAL) - rhs.split()[3].set_color(PINK) - - rhs.next_to(lhs, RIGHT) - formula = VMobject(lhs, rhs) - formula.center() - - title = TextMobject("Explain in one sentence") - title.set_color(YELLOW) - title.next_to(formula, UP, buff = 0.5) - - self.play(Write(m1)) - self.play(Write(m2)) - self.wait() - self.play(Write(det), Write(rp)) - self.play(Write(rhs)) - self.wait(2) - self.play(Write(title)) - self.wait(2) - -class NextVideo(Scene): - def construct(self): - title = TextMobject(""" - Next video: Inverse matrices, column space and null space - """) - title.set_width(FRAME_WIDTH - 2) - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/chapter6.py b/from_3b1b/old/eola/chapter6.py deleted file mode 100644 index 9ed9c229..00000000 --- a/from_3b1b/old/eola/chapter6.py +++ /dev/null @@ -1,2039 +0,0 @@ -from manimlib.imports import * -from ka_playgrounds.circuits import Resistor, Source, LongResistor - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject( - "To ask the", - "right question\\\\", - "is harder than to answer it." - ) - words.to_edge(UP) - words[1].set_color(BLUE) - author = TextMobject("-Georg Cantor") - author.set_color(YELLOW) - author.next_to(words, DOWN, buff = 0.5) - - self.play(FadeIn(words)) - self.wait(2) - self.play(Write(author, run_time = 3)) - self.wait() - -class ListTerms(Scene): - def construct(self): - title = TextMobject("Under the light of linear transformations") - title.set_color(YELLOW) - title.to_edge(UP) - randy = Randolph().to_corner() - words = VMobject(*list(map(TextMobject, [ - "Inverse matrices", - "Column space", - "Rank", - "Null space", - ]))) - words.arrange(DOWN, aligned_edge = LEFT) - words.next_to(title, DOWN, aligned_edge = LEFT) - words.shift(RIGHT) - - self.add(title, randy) - for i, word in enumerate(words.split()): - self.play(Write(word), run_time = 1) - if i%2 == 0: - self.play(Blink(randy)) - else: - self.wait() - self.wait() - -class NoComputations(TeacherStudentsScene): - def construct(self): - self.setup() - self.student_says( - "Will you cover \\\\ computations?", - target_mode = "raise_left_hand" - ) - self.random_blink() - self.teacher_says( - "Well...uh...no", - target_mode = "guilty", - ) - self.play(*[ - ApplyMethod(student.change_mode, mode) - for student, mode in zip( - self.get_students(), - ["dejected", "confused", "angry"] - ) - ]) - self.random_blink() - self.wait() - new_words = self.teacher.bubble.position_mobject_inside( - TextMobject([ - "Search", - "``Gaussian elimination'' \\\\", - "and", - "``Row echelon form''", - ]) - ) - new_words.split()[1].set_color(YELLOW) - new_words.split()[3].set_color(GREEN) - self.play( - Transform(self.teacher.bubble.content, new_words), - self.teacher.change_mode, "speaking" - ) - self.play(*[ - ApplyMethod(student.change_mode, "pondering") - for student in self.get_students() - ]) - self.random_blink() - -class PuntToSoftware(Scene): - def construct(self): - self.play(Write("Let the computers do the computing")) - -class UsefulnessOfMatrices(Scene): - def construct(self): - title = TextMobject("Usefulness of matrices") - title.set_color(YELLOW) - title.to_edge(UP) - self.add(title) - self.wait(3) #Play some 3d linear transform over this - - equations = TexMobject(""" - 6x - 3y + 2z &= 7 \\\\ - x + 2y + 5z &= 0 \\\\ - 2x - 8y - z &= -2 \\\\ - """) - equations.to_edge(RIGHT, buff = 2) - syms = VMobject(*np.array(equations.split())[[1, 4, 7]]) - new_syms = VMobject(*[ - m.copy().set_color(c) - for m, c in zip(syms.split(), [X_COLOR, Y_COLOR, Z_COLOR]) - ]) - new_syms.arrange(RIGHT, buff = 0.5) - new_syms.next_to(equations, LEFT, buff = 3) - sym_brace = Brace(new_syms, DOWN) - unknowns = sym_brace.get_text("Unknown variables") - eq_brace = Brace(equations, DOWN) - eq_words = eq_brace.get_text("Equations") - - self.play(Write(equations)) - self.wait() - self.play(Transform(syms.copy(), new_syms, path_arc = np.pi/2)) - for brace, words in (sym_brace, unknowns), (eq_brace, eq_words): - self.play( - GrowFromCenter(brace), - Write(words) - ) - self.wait() - -class CircuitDiagram(Scene): - def construct(self): - self.add(TextMobject("Voltages").to_edge(UP)) - - source = Source() - p1, p2 = source.get_top(), source.get_bottom() - r1 = Resistor(p1, p1+2*RIGHT) - r2 = LongResistor(p1+2*RIGHT, p2+2*RIGHT) - r3 = Resistor(p1+2*RIGHT, p1+2*2*RIGHT) - l1 = Line(p1+2*2*RIGHT, p2+2*2*RIGHT) - l2 = Line(p2+2*2*RIGHT, p2) - circuit = VMobject(source, r1, r2, r3, l1, l2) - circuit.center() - v1 = TexMobject("v_1").next_to(r1, UP) - v2 = TexMobject("v_2").next_to(r2, RIGHT) - v3 = TexMobject("v_3").next_to(r3, UP) - unknowns = VMobject(v1, v2, v3) - unknowns.set_color(BLUE) - - self.play(ShowCreation(circuit)) - self.wait() - self.play(Write(unknowns)) - self.wait() - -class StockLine(VMobject): - CONFIG = { - "num_points" : 15, - "step_range" : 2 - } - def init_points(self): - points = [ORIGIN] - for x in range(self.num_points): - step_size = self.step_range*(random.random() - 0.5) - points.append(points[-1] + 0.5*RIGHT + step_size*UP) - self.set_points_as_corners(points) - -class StockPrices(Scene): - def construct(self): - self.add(TextMobject("Stock prices").to_edge(UP)) - - x_axis = Line(ORIGIN, FRAME_X_RADIUS*RIGHT) - y_axis = Line(ORIGIN, FRAME_Y_RADIUS*UP) - everyone = VMobject(x_axis, y_axis) - stock_lines = [] - for color in TEAL, PINK, YELLOW, RED, BLUE: - sl = StockLine(color = color) - sl.move_to(y_axis.get_center(), aligned_edge = LEFT) - everyone.add(sl) - stock_lines.append(sl) - everyone.center() - - self.add(x_axis, y_axis) - self.play(ShowCreation( - VMobject(*stock_lines), - run_time = 3, - lag_ratio = 0.5 - )) - self.wait() - -class MachineLearningNetwork(Scene): - def construct(self): - self.add(TextMobject("Machine learning parameters").to_edge(UP)) - - layers = [] - for i, num_nodes in enumerate([3, 4, 4, 1]): - layer = VMobject(*[ - Circle(radius = 0.5, color = YELLOW) - for x in range(num_nodes) - ]) - for j, mob in enumerate(layer.split()): - sym = TexMobject("x_{%d, %d}"%(i, j)) - sym.move_to(mob) - mob.add(sym) - layer.arrange(DOWN, buff = 0.5) - layer.center() - layers.append(layer) - VMobject(*layers).arrange(RIGHT, buff = 1.5) - lines = VMobject() - for l_layer, r_layer in zip(layers, layers[1:]): - for l_node, r_node in it.product(l_layer.split(), r_layer.split()): - lines.add(Line(l_node, r_node)) - lines.set_submobject_colors_by_gradient(BLUE_E, BLUE_A) - for mob in VMobject(*layers), lines: - self.play(Write(mob), run_time = 2) - self.wait() - -class ComplicatedSystem(Scene): - def construct(self): - system = TexMobject(""" - \\begin{align*} - \\dfrac{1}{1-e^{2x-3y+4z}} &= 1 \\\\ \\\\ - \\sin(xy) + z^2 &= \\sqrt{y} \\\\ \\\\ - x^2 + y^2 &= e^{-z} - \\end{align*} - """) - system.to_edge(UP) - randy = Randolph().to_corner(DOWN+LEFT) - - self.add(randy) - self.play(Write(system, run_time = 1)) - self.play(randy.change_mode, "sassy") - self.play(Blink(randy)) - self.wait() - -class SystemOfEquations(Scene): - def construct(self): - equations = self.get_equations() - self.show_linearity_rules(equations) - self.describe_organization(equations) - self.factor_into_matrix(equations) - - def get_equations(self): - matrix = Matrix([ - [2, 5, 3], - [4, 0, 8], - [1, 3, 0] - ]) - mob_matrix = matrix.get_mob_matrix() - rhs = list(map(TexMobject, list(map(str, [-3, 0, 2])))) - variables = list(map(TexMobject, list("xyz"))) - for v, color in zip(variables, [X_COLOR, Y_COLOR, Z_COLOR]): - v.set_color(color) - equations = VMobject() - for row in mob_matrix: - equation = VMobject(*it.chain(*list(zip( - row, - [v.copy() for v in variables], - list(map(TexMobject, list("++="))) - )))) - equation.arrange( - RIGHT, buff = 0.1, - aligned_edge = DOWN - ) - equation.split()[4].shift(0.1*DOWN) - equation.split()[-1].next_to(equation.split()[-2], RIGHT) - equations.add(equation) - equations.arrange(DOWN, aligned_edge = RIGHT) - for eq, rhs_elem in zip(equations.split(), rhs): - rhs_elem.next_to(eq, RIGHT) - eq.add(rhs_elem) - equations.center() - self.play(Write(equations)) - self.add(equations) - return equations - - def show_linearity_rules(self, equations): - top_equation = equations.split()[0] - other_equations = VMobject(*equations.split()[1:]) - other_equations.save_state() - scaled_vars = VMobject(*[ - VMobject(*top_equation.split()[3*i:3*i+2]) - for i in range(3) - ]) - scaled_vars.save_state() - isolated_scaled_vars = scaled_vars.copy() - isolated_scaled_vars.scale(1.5) - isolated_scaled_vars.next_to(top_equation, UP) - scalars = VMobject(*[m.split()[0] for m in scaled_vars.split()]) - plusses = np.array(top_equation.split())[[2, 5]] - - self.play(other_equations.fade, 0.7) - self.play(Transform(scaled_vars, isolated_scaled_vars)) - self.play(scalars.set_color, YELLOW, lag_ratio = 0.5) - self.play(*[ - ApplyMethod(m.scale_in_place, 1.2, rate_func = there_and_back) - for m in scalars.split() - ]) - self.wait() - self.remove(scalars) - self.play(scaled_vars.restore) - self.play(*[ - ApplyMethod(p.scale_in_place, 1.5, rate_func = there_and_back) - for p in plusses - ]) - self.wait() - self.show_nonlinearity_examples() - self.play(other_equations.restore) - - def show_nonlinearity_examples(self): - squared = TexMobject("x^2") - squared.split()[0].set_color(X_COLOR) - sine = TexMobject("\\sin(x)") - sine.split()[-2].set_color(X_COLOR) - product = TexMobject("xy") - product.split()[0].set_color(X_COLOR) - product.split()[1].set_color(Y_COLOR) - - - words = TextMobject("Not allowed!") - words.set_color(RED) - words.to_corner(UP+LEFT, buff = 1) - arrow = Vector(RIGHT, color = RED) - arrow.next_to(words, RIGHT) - for mob in squared, sine, product: - mob.scale(1.7) - mob.next_to(arrow.get_end(), RIGHT, buff = 0.5) - circle_slash = Circle(color = RED) - line = Line(LEFT, RIGHT, color = RED) - line.rotate(np.pi/4) - circle_slash.add(line) - circle_slash.next_to(arrow, RIGHT) - def draw_circle_slash(mob): - circle_slash.replace(mob) - circle_slash.scale_in_place(1.4) - self.play(ShowCreation(circle_slash), run_time = 0.5) - self.wait(0.5) - self.play(FadeOut(circle_slash), run_time = 0.5) - - self.play( - Write(squared), - Write(words, run_time = 1), - ShowCreation(arrow), - ) - draw_circle_slash(squared) - for mob in sine, product: - self.play(Transform(squared, mob)) - draw_circle_slash(mob) - self.play(*list(map(FadeOut, [words, arrow, squared]))) - self.wait() - - - def describe_organization(self, equations): - variables = VMobject(*it.chain(*[ - eq.split()[:-2] - for eq in equations.split() - ])) - variables.words = "Throw variables on the left" - constants = VMobject(*[ - eq.split()[-1] - for eq in equations.split() - ]) - constants.words = "Lingering constants on the right" - xs, ys, zs = [ - VMobject(*[ - eq.split()[i] - for eq in equations.split() - ]) - for i in (1, 4, 7) - ] - ys.words = "Vertically align variables" - colors = [PINK, YELLOW, BLUE_B, BLUE_C, BLUE_D] - for mob, color in zip([variables, constants, xs, ys, zs], colors): - mob.square = Square(color = color) - mob.square.replace(mob, stretch = True) - mob.square.scale_in_place(1.1) - if hasattr(mob, "words"): - mob.words = TextMobject(mob.words) - mob.words.set_color(color) - mob.words.next_to(mob.square, UP) - ys.square.add(xs.square, zs.square) - zero_circles = VMobject(*[ - Circle().replace(mob).scale_in_place(1.3) - for mob in [ - VMobject(*equations.split()[i].split()[j:j+2]) - for i, j in [(1, 3), (2, 6)] - ] - ]) - zero_circles.set_color(PINK) - zero_circles.words = TextMobject("Add zeros as needed") - zero_circles.words.set_color(zero_circles.get_color()) - zero_circles.words.next_to(equations, UP) - - for mob in variables, constants, ys: - self.play( - FadeIn(mob.square), - FadeIn(mob.words) - ) - self.wait() - self.play(*list(map(FadeOut, [mob.square, mob.words]))) - self.play( - ShowCreation(zero_circles), - Write(zero_circles.words, run_time = 1) - ) - self.wait() - self.play(*list(map(FadeOut, [zero_circles, zero_circles.words]))) - self.wait() - title = TextMobject("``Linear system of equations''") - title.scale(1.5) - title.to_edge(UP) - self.play(Write(title)) - self.wait() - self.play(FadeOut(title)) - - - def factor_into_matrix(self, equations): - coefficients = np.array([ - np.array(eq.split())[[0, 3, 6]] - for eq in equations.split() - ]) - variable_arrays = np.array([ - np.array(eq.split())[[1, 4, 7]] - for eq in equations.split() - ]) - rhs_entries = np.array([ - eq.split()[-1] - for eq in equations.split() - ]) - - matrix = Matrix(copy.deepcopy(coefficients)) - x_array = Matrix(copy.deepcopy(variable_arrays[0])) - v_array = Matrix(copy.deepcopy(rhs_entries)) - equals = TexMobject("=") - ax_equals_v = VMobject(matrix, x_array, equals, v_array) - ax_equals_v.arrange(RIGHT) - ax_equals_v.to_edge(RIGHT) - all_brackets = [ - mob.get_brackets() - for mob in (matrix, x_array, v_array) - ] - - self.play(equations.to_edge, LEFT) - arrow = Vector(RIGHT, color = YELLOW) - arrow.next_to(ax_equals_v, LEFT) - self.play(ShowCreation(arrow)) - self.play(*it.chain(*[ - [ - Transform( - m1.copy(), m2, - run_time = 2, - path_arc = -np.pi/2 - ) - for m1, m2 in zip( - start_array.flatten(), - matrix_mobject.get_entries().split() - ) - ] - for start_array, matrix_mobject in [ - (coefficients, matrix), - (variable_arrays[0], x_array), - (variable_arrays[1], x_array), - (variable_arrays[2], x_array), - (rhs_entries, v_array) - ] - ])) - self.play(*[ - Write(mob) - for mob in all_brackets + [equals] - ]) - self.wait() - self.label_matrix_product(matrix, x_array, v_array) - - def label_matrix_product(self, matrix, x_array, v_array): - matrix.words = "Coefficients" - matrix.symbol = "A" - x_array.words = "Variables" - x_array.symbol = "\\vec{\\textbf{x}}" - v_array.words = "Constants" - v_array.symbol = "\\vec{\\textbf{v}}" - parts = matrix, x_array, v_array - for mob in parts: - mob.brace = Brace(mob, UP) - mob.words = mob.brace.get_text(mob.words) - mob.words.shift_onto_screen() - mob.symbol = TexMobject(mob.symbol) - mob.brace.put_at_tip(mob.symbol) - x_array.words.set_submobject_colors_by_gradient( - X_COLOR, Y_COLOR, Z_COLOR - ) - x_array.symbol.set_color(PINK) - v_array.symbol.set_color(YELLOW) - for mob in parts: - self.play( - GrowFromCenter(mob.brace), - FadeIn(mob.words) - ) - self.wait() - self.play(*list(map(FadeOut, [mob.brace, mob.words]))) - self.wait() - for mob in parts: - self.play( - FadeIn(mob.brace), - Write(mob.symbol) - ) - compact_equation = VMobject(*[ - mob.symbol for mob in parts - ]) - compact_equation.submobjects.insert( - 2, TexMobject("=").next_to(x_array, RIGHT) - ) - compact_equation.target = compact_equation.copy() - compact_equation.target.arrange(buff = 0.1) - compact_equation.target.to_edge(UP) - - self.play(Transform( - compact_equation.copy(), - compact_equation.target - )) - self.wait() - -class LinearSystemTransformationScene(LinearTransformationScene): - def setup(self): - LinearTransformationScene.setup(self) - equation = TexMobject([ - "A", - "\\vec{\\textbf{x}}", - "=", - "\\vec{\\textbf{v}}", - ]) - equation.scale(1.5) - equation.next_to(ORIGIN, LEFT).to_edge(UP) - equation.add_background_rectangle() - self.add_foreground_mobject(equation) - self.equation = equation - self.A, self.x, eq, self.v = equation.split()[1].split() - self.x.set_color(PINK) - self.v.set_color(YELLOW) - -class MentionThatItsATransformation(LinearSystemTransformationScene): - CONFIG = { - "t_matrix" : np.array([[2, 1], [2, 3]]) - } - def construct(self): - self.setup() - brace = Brace(self.A) - words = brace.get_text("Transformation") - words.add_background_rectangle() - self.play(GrowFromCenter(brace), Write(words, run_time = 1)) - self.add_foreground_mobject(words, brace) - self.apply_transposed_matrix(self.t_matrix) - self.wait() - -class LookForX(MentionThatItsATransformation): - CONFIG = { - "show_basis_vectors" : False - } - def construct(self): - self.setup() - v = [-4, - 1] - x = np.linalg.solve(self.t_matrix.T, v) - v = Vector(v, color = YELLOW) - x = Vector(x, color = PINK) - v_label = self.get_vector_label(v, "v", color = YELLOW) - x_label = self.get_vector_label(x, "x", color = PINK) - for label in x_label, v_label: - label.add_background_rectangle() - self.play( - ShowCreation(v), - Write(v_label) - ) - self.add_foreground_mobject(v_label) - x = self.add_vector(x, animate = False) - self.play( - ShowCreation(x), - Write(x_label) - ) - self.wait() - self.add(VMobject(x, x_label).copy().fade()) - self.apply_transposed_matrix(self.t_matrix) - self.wait() - -class ThinkAboutWhatsHappening(Scene): - def construct(self): - randy = Randolph() - randy.to_corner() - bubble = randy.get_bubble(height = 5) - bubble.add_content(TexMobject(""" - 3x + 1y + 4z &= 1 \\\\ - 5x + 9y + 2z &= 6 \\\\ - 5x + 3y + 5z &= 8 - """)) - - self.play(randy.change_mode, "pondering") - self.play(ShowCreation(bubble)) - self.play(Write(bubble.content, run_time = 2)) - self.play(Blink(randy)) - self.wait() - everything = VMobject(*self.get_mobjects()) - self.play( - ApplyFunction( - lambda m : m.shift(2*DOWN).scale(5), - everything - ), - bubble.content.set_color, BLACK, - run_time = 2 - ) - -class SystemOfTwoEquationsTwoUnknowns(Scene): - def construct(self): - system = TexMobject(""" - 2x + 2y &= -4 \\\\ - 1x + 3y &= -1 - """) - system.to_edge(UP) - for indices, color in ((1, 9), X_COLOR), ((4, 12), Y_COLOR): - for i in indices: - system.split()[i].set_color(color) - matrix = Matrix([[2, 2], [1, 3]]) - v = Matrix([-4, -1]) - x = Matrix(["x", "y"]) - x.get_entries().set_submobject_colors_by_gradient(X_COLOR, Y_COLOR) - matrix_system = VMobject( - matrix, x, TexMobject("="), v - ) - matrix_system.arrange(RIGHT) - matrix_system.next_to(system, DOWN, buff = 1) - matrix.label = "A" - matrix.label_color = WHITE - x.label = "\\vec{\\textbf{x}}" - x.label_color = PINK - v.label = "\\vec{\\textbf{v}}" - v.label_color = YELLOW - for mob in matrix, x, v: - brace = Brace(mob) - label = brace.get_text("$%s$"%mob.label) - label.set_color(mob.label_color) - brace.add(label) - mob.brace = brace - - self.add(system) - self.play(Write(matrix_system)) - self.wait() - for mob in matrix, v, x: - self.play(Write(mob.brace)) - self.wait() - -class ShowBijectivity(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "t_matrix" : np.array([[0, -1], [2, 1]]) - } - def construct(self): - self.setup() - vectors = VMobject(*[ - Vector([x, y]) - for x, y in it.product(*[ - np.arange(-int(val)+0.5, int(val)+0.5) - for val in (FRAME_X_RADIUS, FRAME_Y_RADIUS) - ]) - ]) - vectors.set_submobject_colors_by_gradient(BLUE_E, PINK) - dots = VMobject(*[ - Dot(v.get_end(), color = v.get_color()) - for v in vectors.split() - ]) - titles = [ - TextMobject([ - "Each vector lands on\\\\", - "exactly one vector" - ]), - TextMobject([ - "Every vector has \\\\", - "been landed on" - ]) - ] - for title in titles: - title.to_edge(UP) - background = BackgroundRectangle(VMobject(*titles)) - self.add_foreground_mobject(background, titles[0]) - - kwargs = { - "lag_ratio" : 0.5, - "run_time" : 2 - } - anims = list(map(Animation, self.foreground_mobjects)) - self.play(ShowCreation(vectors, **kwargs), *anims) - self.play(Transform(vectors, dots, **kwargs), *anims) - self.wait() - self.add_transformable_mobject(vectors) - self.apply_transposed_matrix(self.t_matrix) - self.wait() - self.play(Transform(*titles)) - self.wait() - self.apply_transposed_matrix( - np.linalg.inv(self.t_matrix.T).T - ) - self.wait() - -class LabeledExample(LinearSystemTransformationScene): - CONFIG = { - "title" : "", - "t_matrix" : [[0, 0], [0, 0]], - "show_square" : False, - } - def setup(self): - LinearSystemTransformationScene.setup(self) - title = TextMobject(self.title) - title.next_to(self.equation, DOWN, buff = 1) - title.add_background_rectangle() - title.shift_onto_screen() - self.add_foreground_mobject(title) - self.title = title - if self.show_square: - self.add_unit_square() - - def construct(self): - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.wait() - -class SquishExmapleWithWords(LabeledExample): - CONFIG = { - "title" : "$A$ squishes things to a lower dimension", - "t_matrix" : [[-2, -1], [2, 1]] - } - -class FullRankExmapleWithWords(LabeledExample): - CONFIG = { - "title" : "$A$ keeps things 2D", - "t_matrix" : [[3, 0], [2, 1]] - } - -class SquishExmapleDet(SquishExmapleWithWords): - CONFIG = { - "title" : "$\\det(A) = 0$", - "show_square" : True, - } - -class FullRankExmapleDet(FullRankExmapleWithWords): - CONFIG = { - "title" : "$\\det(A) \\ne 0$", - "show_square" : True, - } - -class StartWithNonzeroDetCase(TeacherStudentsScene): - def construct(self): - words = TextMobject( - "Let's start with \\\\", - "the", "$\\det(A) \\ne 0$", "case" - ) - words[2].set_color(TEAL) - self.teacher_says(words) - self.random_blink() - self.play( - random.choice(self.get_students()).change_mode, - "happy" - ) - self.wait() - -class DeclareNewTransformation(TeacherStudentsScene): - def construct(self): - words = TextMobject( - "Playing a transformation in\\\\", - "reverse gives a", "new transformation" - ) - words[-1].set_color(GREEN) - self.teacher_says(words) - self.change_student_modes("pondering", "sassy") - self.random_blink() - -class PlayInReverse(FullRankExmapleDet): - CONFIG = { - "show_basis_vectors" : False - } - def construct(self): - FullRankExmapleDet.construct(self) - v = self.add_vector([-4, -1], color = YELLOW) - v_label = self.label_vector(v, "v", color = YELLOW) - self.add(v.copy()) - self.apply_inverse_transpose(self.t_matrix) - self.play(v.set_color, PINK) - self.label_vector(v, "x", color = PINK) - self.wait() - -class DescribeInverse(LinearTransformationScene): - CONFIG = { - "show_actual_inverse" : False, - "matrix_label" : "$A$", - "inv_label" : "$A^{-1}$", - } - def construct(self): - title = TextMobject("Transformation:") - new_title = TextMobject("Inverse transformation:") - matrix = Matrix(self.t_matrix.T) - if not self.show_actual_inverse: - inv_matrix = matrix.copy() - neg_1 = TexMobject("-1") - neg_1.move_to( - inv_matrix.get_corner(UP+RIGHT), - aligned_edge = LEFT - ) - neg_1.shift(0.1*RIGHT) - inv_matrix.add(neg_1) - matrix.add(VectorizedPoint(matrix.get_corner(UP+RIGHT))) - else: - inv_matrix = Matrix(np.linalg.inv(self.t_matrix.T).astype('int')) - matrix.label = self.matrix_label - inv_matrix.label = self.inv_label - for m, text in (matrix, title), (inv_matrix, new_title): - m.add_to_back(BackgroundRectangle(m)) - text.add_background_rectangle() - m.next_to(text, RIGHT) - brace = Brace(m) - label_mob = brace.get_text(m.label) - label_mob.add_background_rectangle() - m.add(brace, label_mob) - text.add(m) - if text.get_width() > FRAME_WIDTH-1: - text.set_width(FRAME_WIDTH-1) - text.center().to_corner(UP+RIGHT) - matrix.set_color(PINK) - inv_matrix.set_color(YELLOW) - - self.add_foreground_mobject(title) - self.apply_transposed_matrix(self.t_matrix) - self.wait() - self.play(Transform(title, new_title)) - self.apply_inverse_transpose(self.t_matrix) - self.wait() - -class ClockwiseCounterclockwise(DescribeInverse): - CONFIG = { - "t_matrix" : [[0, 1], [-1, 0]], - "show_actual_inverse" : True, - "matrix_label" : "$90^\\circ$ Couterclockwise", - "inv_label" : "$90^\\circ$ Clockwise", - } - -class ShearInverseShear(DescribeInverse): - CONFIG = { - "t_matrix" : [[1, 0], [1, 1]], - "show_actual_inverse" : True, - "matrix_label" : "Rightward shear", - "inv_label" : "Leftward shear", - } - -class MultiplyToIdentity(LinearTransformationScene): - def construct(self): - self.setup() - lhs = TexMobject("A^{-1}", "A", "=") - lhs.scale(1.5) - A_inv, A, eq = lhs.split() - identity = Matrix([[1, 0], [0, 1]]) - identity.set_column_colors(X_COLOR, Y_COLOR) - identity.next_to(eq, RIGHT) - VMobject(lhs, identity).center().to_corner(UP+RIGHT) - for mob in A, A_inv, eq: - mob.add_to_back(BackgroundRectangle(mob)) - identity.background = BackgroundRectangle(identity) - - col1 = VMobject(*identity.get_mob_matrix()[:,0]) - col2 = VMobject(*identity.get_mob_matrix()[:,1]) - - A.text = "Transformation" - A_inv.text = "Inverse transformation" - product = VMobject(A, A_inv) - product.text = "Matrix multiplication" - identity.text = "The transformation \\\\ that does nothing" - for mob in A, A_inv, product, identity: - mob.brace = Brace(mob) - mob.text = mob.brace.get_text(mob.text) - mob.text.shift_onto_screen() - mob.text.add_background_rectangle() - - self.add_foreground_mobject(A, A_inv) - brace, text = A.brace, A.text - self.play(GrowFromCenter(brace), Write(text), run_time = 1) - self.add_foreground_mobject(brace, text) - self.apply_transposed_matrix(self.t_matrix) - self.play( - Transform(brace, A_inv.brace), - Transform(text, A_inv.text), - ) - self.apply_inverse_transpose(self.t_matrix) - self.wait() - self.play( - Transform(brace, product.brace), - Transform(text, product.text) - ) - self.wait() - self.play( - Write(identity.background), - Write(identity.get_brackets()), - Write(eq), - Transform(brace, identity.brace), - Transform(text, identity.text) - ) - self.wait() - self.play(Write(col1)) - self.wait() - self.play(Write(col2)) - self.wait() - -class ThereAreComputationMethods(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - There are methods - to compute $A^{-1}$ - """) - self.random_blink() - self.wait() - -class TwoDInverseFormula(Scene): - def construct(self): - title = TextMobject("If you're curious...") - title.set_color(YELLOW) - title.to_edge(UP) - morty = Mortimer().to_corner(DOWN+RIGHT) - self.add(title, morty) - matrix = [["a", "b"], ["c", "d"]] - scaled_inv = [["d", "-b"], ["-c", "a"]] - formula = TexMobject(""" - %s^{-1} = \\dfrac{1}{ad-bc} %s - """%( - matrix_to_tex_string(matrix), - matrix_to_tex_string(scaled_inv) - )) - self.play(Write(formula)) - self.play(morty.change_mode, "confused") - self.play(Blink(morty)) - -class SymbolicInversion(Scene): - def construct(self): - vec = lambda s : "\\vec{\\textbf{%s}}"%s - - words = TextMobject("Once you have this:") - words.to_edge(UP, buff = 2) - inv = TexMobject("A^{-1}") - inv.set_color(GREEN) - inv.next_to(words.split()[-1], RIGHT, aligned_edge = DOWN) - inv2 = inv.copy() - - start = TexMobject("A", vec("x"), "=", vec("v")) - interim = TexMobject("A^{-1}", "A", vec("x"), "=", "A^{-1}", vec("v")) - end = TexMobject(vec("x"), "=", "A^{-1}", vec("v")) - - A, x, eq, v = start.split() - x.set_color(PINK) - v.set_color(YELLOW) - interim_mobs = [inv, A, x, eq, inv2, v] - for i, mob in enumerate(interim_mobs): - mob.interim = mob.copy().move_to(interim.split()[i]) - - self.add(start) - self.play(Write(words), FadeIn(inv), run_time = 1) - self.wait() - self.play( - FadeOut(words), - *[Transform(m, m.interim) for m in interim_mobs] - ) - self.wait() - - product = VMobject(A, inv) - product.brace = Brace(product) - product.words = product.brace.get_text( - "The ``do nothing'' matrix" - ) - product.words.set_color(BLUE) - self.play( - GrowFromCenter(product.brace), - Write(product.words, run_time = 1), - product.set_color, BLUE - ) - self.wait() - self.play(*[ - ApplyMethod(m.set_color, BLACK) - for m in (product, product.brace, product.words) - ]) - self.wait() - self.play(ApplyFunction( - lambda m : m.center().to_edge(UP), - VMobject(x, eq, inv2, v) - )) - self.wait() - -class PlayInReverseWithSolution(PlayInReverse): - CONFIG = { - "t_matrix" : [[2, 1], [2, 3]] - } - def setup(self): - LinearTransformationScene.setup(self) - equation = TexMobject([ - "\\vec{\\textbf{x}}", - "=", - "A^{-1}", - "\\vec{\\textbf{v}}", - ]) - equation.to_edge(UP) - equation.add_background_rectangle() - self.add_foreground_mobject(equation) - self.equation = equation - self.x, eq, self.inv, self.v = equation.split()[1].split() - self.x.set_color(PINK) - self.v.set_color(YELLOW) - self.inv.set_color(GREEN) - -class OneUniqueSolution(Scene): - def construct(self): - system = TexMobject(""" - \\begin{align*} - ax + cy &= e \\\\ - bx + dy &= f - \\end{align*} - """) - VMobject(*np.array(system.split())[[1, 8]]).set_color(X_COLOR) - VMobject(*np.array(system.split())[[4, 11]]).set_color(Y_COLOR) - brace = Brace(system, UP) - brace.set_color(YELLOW) - words = brace.get_text("One unique solution \\dots", "probably") - words.set_color(YELLOW) - words.split()[1].set_color(GREEN) - - self.add(system) - self.wait() - self.play( - GrowFromCenter(brace), - Write(words.split()[0]) - ) - self.wait() - self.play(Write(words.split()[1], run_time = 1)) - self.wait() - -class ThreeDTransformXToV(Scene): - pass - -class ThreeDTransformAndReverse(Scene): - pass - -class DetNEZeroRule(Scene): - def construct(self): - text = TexMobject("\\det(A) \\ne 0") - text.shift(2*UP) - A_inv = TextMobject("$A^{-1}$ exists") - A_inv.shift(DOWN) - arrow = Arrow(text, A_inv) - self.play(Write(text)) - self.wait() - self.play(ShowCreation(arrow)) - self.play(Write(A_inv, run_time = 1)) - self.wait() - - -class ThreeDInverseRule(Scene): - def construct(self): - form = TexMobject("A^{-1} A = ") - form.scale(2) - matrix = Matrix(np.identity(3, 'int')) - matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR) - matrix.next_to(form, RIGHT) - self.add(form) - self.play(Write(matrix)) - self.wait() - -class ThreeDApplyReverseToV(Scene): - pass - -class InversesDontAlwaysExist(TeacherStudentsScene): - def construct(self): - self.teacher_says("$A^{-1}$ doesn't always exist") - self.random_blink() - self.wait() - self.random_blink() - -class InvertNonInvertable(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[2, 1], [-2, -1]] - } - def setup(self): - LinearTransformationScene.setup(self) - det_text = TexMobject("\\det(A) = 0") - det_text.scale(1.5) - det_text.to_corner(UP+LEFT) - det_text.add_background_rectangle() - self.add_foreground_mobject(det_text) - - def construct(self): - no_func = TextMobject("No function does this") - no_func.shift(2*UP) - no_func.set_color(RED) - no_func.add_background_rectangle() - grid = VMobject(self.plane, self.i_hat, self.j_hat) - grid.save_state() - self.apply_transposed_matrix(self.t_matrix, path_arc = 0) - self.wait() - self.play(Write(no_func, run_time = 1)) - self.add_foreground_mobject(no_func) - self.play( - grid.restore, - *list(map(Animation, self.foreground_mobjects)), - run_time = 3 - ) - self.wait() - -class OneInputMultipleOutputs(InvertNonInvertable): - def construct(self): - input_vectors = VMobject(*[ - Vector([x+2, x]) for x in np.arange(-4, 4.5, 0.5) - ]) - input_vectors.set_submobject_colors_by_gradient(PINK, YELLOW) - output_vector = Vector([4, 2], color = YELLOW) - - grid = VMobject(self.plane, self.i_hat, self.j_hat) - grid.save_state() - - self.apply_transposed_matrix(self.t_matrix, path_arc = 0) - self.play(ShowCreation(output_vector)) - single_input = TextMobject("Single vector") - single_input.add_background_rectangle() - single_input.next_to(output_vector.get_end(), UP) - single_input.set_color(YELLOW) - self.play(Write(single_input)) - self.wait() - self.remove(single_input, output_vector) - self.play( - grid.restore, - *[ - Transform(output_vector.copy(), input_vector) - for input_vector in input_vectors.split() - ] + list(map(Animation, self.foreground_mobjects)), - run_time = 3 - ) - multiple_outputs = TextMobject( - "Must map to \\\\", - "multiple vectors" - ) - multiple_outputs.split()[1].set_submobject_colors_by_gradient(YELLOW, PINK) - multiple_outputs.next_to(ORIGIN, DOWN).to_edge(RIGHT) - multiple_outputs.add_background_rectangle() - self.play(Write(multiple_outputs, run_time = 2)) - self.wait() - -class SolutionsCanStillExist(TeacherStudentsScene): - def construct(self): - words = TextMobject(""" - Solutions can still - exist when""", "$\\det(A) = 0$" - ) - words[1].set_color(TEAL) - self.teacher_says(words) - self.random_blink(2) - -class ShowVInAndOutOfColumnSpace(LinearSystemTransformationScene): - CONFIG = { - "t_matrix" : [[2, 1], [-2, -1]] - } - def construct(self): - v_out = Vector([1, -1]) - v_in = Vector([-4, -2]) - v_out.words = "No solution exists" - v_in.words = "Solutions exist" - v_in.words_color = YELLOW - v_out.words_color = RED - - - self.apply_transposed_matrix(self.t_matrix, path_arc = 0) - self.wait() - for v in v_in, v_out: - self.add_vector(v, animate = True) - words = TextMobject(v.words) - words.set_color(v.words_color) - words.next_to(v.get_end(), DOWN+RIGHT) - words.add_background_rectangle() - self.play(Write(words), run_time = 2) - self.wait() - -class NotAllSquishesAreCreatedEqual(TeacherStudentsScene): - def construct(self): - self.student_says(""" - Some squishes feel - ...squishier - """) - self.random_blink(2) - -class PrepareForRank(Scene): - def construct(self): - new_term, rank = words = TextMobject( - "New terminology: ", - "rank" - ) - rank.set_color(TEAL) - self.play(Write(words)) - self.wait() - -class DefineRank(Scene): - def construct(self): - rank = TextMobject("``Rank''") - rank.set_color(TEAL) - arrow = DoubleArrow(LEFT, RIGHT) - dims = TextMobject( - "Number of\\\\", "dimensions \\\\", - "in the output" - ) - dims[1].set_color(rank.get_color()) - - rank.next_to(arrow, LEFT) - dims.next_to(arrow, RIGHT) - - self.play(Write(rank)) - self.play( - ShowCreation(arrow), - *list(map(Write, dims)) - ) - self.wait() - -class DefineColumnSpace(Scene): - def construct(self): - left_words = TextMobject( - "Set of all possible\\\\", - "outputs", - "$A\\vec{\\textbf{v}}$", - ) - left_words[1].set_color(TEAL) - VMobject(*left_words[-1][1:]).set_color(YELLOW) - arrow = DoubleArrow(LEFT, RIGHT).to_edge(UP) - right_words = TextMobject("``Column space''", "of $A$") - right_words[0].set_color(left_words[1].get_color()) - - everyone = VMobject(left_words, arrow, right_words) - everyone.arrange(RIGHT) - everyone.to_edge(UP) - - self.play(Write(left_words)) - self.wait() - self.play( - ShowCreation(arrow), - Write(right_words) - ) - self.wait() - -class ColumnsRepresentBasisVectors(Scene): - def construct(self): - matrix = Matrix([[3, 1], [4, 1]]) - matrix.shift(UP) - i_hat_words, j_hat_words = [ - TextMobject("Where $\\hat{\\%smath}$ lands"%char) - for char in ("i", "j") - ] - i_hat_words.set_color(X_COLOR) - i_hat_words.next_to(ORIGIN, LEFT).to_edge(UP) - j_hat_words.set_color(Y_COLOR) - j_hat_words.next_to(ORIGIN, RIGHT).to_edge(UP) - - self.add(matrix) - self.wait() - for i, words in enumerate([i_hat_words, j_hat_words]): - - arrow = Arrow( - words.get_bottom(), - matrix.get_mob_matrix()[0,i].get_top(), - color = words.get_color() - ) - self.play( - Write(words, run_time = 1), - ShowCreation(arrow), - *[ - ApplyMethod(m.set_color, words.get_color()) - for m in matrix.get_mob_matrix()[:,i] - ] - ) - self.wait() - -class ThreeDOntoPlane(Scene): - pass - -class ThreeDOntoLine(Scene): - pass - -class ThreeDOntoPoint(Scene): - pass - -class TowDColumnsDontSpan(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[2, 1], [-2, -1]] - } - def construct(self): - matrix = Matrix(self.t_matrix.T) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.add_to_back(BackgroundRectangle(matrix)) - self.add_foreground_mobject(matrix) - brace = Brace(matrix) - words = VMobject( - TextMobject("Span", "of columns"), - TexMobject("\\Updownarrow"), - TextMobject("``Column space''") - ) - words.arrange(DOWN, buff = 0.1) - words.next_to(brace, DOWN) - words[0][0].set_color(PINK) - words[2].set_color(TEAL) - words[0].add_background_rectangle() - words[2].add_background_rectangle() - VMobject(matrix, brace, words).to_corner(UP+LEFT) - - self.apply_transposed_matrix(self.t_matrix, path_arc = 0) - self.play( - GrowFromCenter(brace), - Write(words, run_time = 2) - ) - self.wait() - self.play(ApplyFunction( - lambda m : m.scale(-1).shift(self.i_hat.get_end()), - self.j_hat - )) - bases = [self.i_hat, self.j_hat] - for mob in bases: - mob.original = mob.copy() - for x in range(8): - for mob in bases: - mob.target = mob.original.copy() - mob.target.set_stroke(width = 6) - target_len = random.uniform(0.5, 1.5) - target_len *= random.choice([-1, 1]) - mob.target.scale(target_len) - self.j_hat.target.shift( - -self.j_hat.target.get_start()+ \ - self.i_hat.target.get_end() - ) - self.play(Transform( - VMobject(*bases), - VMobject(*[m.target for m in bases]), - run_time = 2 - )) - -class FullRankWords(Scene): - def construct(self): - self.play(Write(TextMobject("Full rank").scale(2))) - -class ThreeDColumnsDontSpan(Scene): - def construct(self): - matrix = Matrix(np.array([ - [1, 1, 0], - [0, 1, 1], - [-1, -2, -1], - ]).T) - matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR) - brace = Brace(matrix) - words = brace.get_text( - "Columns don't", - "span \\\\", - "full output space" - ) - words[1].set_color(PINK) - - self.add(matrix) - self.play( - GrowFromCenter(brace), - Write(words, run_time = 2) - ) - self.wait() - -class NameColumnSpace(Scene): - def construct(self): - matrix = Matrix(np.array([ - [1, 1, 0], - [0, 1, 1], - [-1, -2, -1], - ]).T) - matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR) - matrix.to_corner(UP+LEFT) - cols = list(matrix.copy().get_mob_matrix().T) - col_arrays = list(map(Matrix, cols)) - - span_text = TexMobject( - "\\text{Span}", - "\\Big(", - matrix_to_tex_string([1, 2, 3]), - ",", - matrix_to_tex_string([1, 2, 3]), - ",", - matrix_to_tex_string([1, 2, 3]), - "\\big)" - ) - for i in 1, -1: - span_text[i].stretch(1.5, 1) - span_text[i].do_in_place( - span_text[i].set_height, - span_text.get_height() - ) - for col_array, index in zip(col_arrays, [2, 4, 6]): - col_array.replace(span_text[index], dim_to_match = 1) - span_text.submobjects[index] = col_array - span_text.arrange(RIGHT, buff = 0.2) - - arrow = DoubleArrow(LEFT, RIGHT) - column_space = TextMobject("``Column space''") - for mob in column_space, arrow: - mob.set_color(TEAL) - text = VMobject(span_text, arrow, column_space) - text.arrange(RIGHT) - text.next_to(matrix, DOWN, buff = 1, aligned_edge = LEFT) - - self.add(matrix) - self.wait() - self.play(*[ - Transform( - VMobject(*matrix.copy().get_mob_matrix()[:,i]), - col_arrays[i].get_entries() - ) - for i in range(3) - ]) - self.play( - Write(span_text), - *list(map(Animation, self.get_mobjects_from_last_animation())) - ) - self.play( - ShowCreation(arrow), - Write(column_space) - ) - self.wait() - self.play(FadeOut(matrix)) - self.clear() - self.add(text) - - words = TextMobject( - "To solve", - "$A\\vec{\\textbf{x}} = \\vec{\\textbf{v}}$,\\\\", - "$\\vec{\\textbf{v}}$", - "must be in \\\\ the", - "column space." - ) - VMobject(*words[1][1:3]).set_color(PINK) - VMobject(*words[1][4:6]).set_color(YELLOW) - words[2].set_color(YELLOW) - words[4].set_color(TEAL) - words.to_corner(UP+LEFT) - - self.play(Write(words)) - self.wait(2) - self.play(FadeOut(words)) - - brace = Brace(column_space, UP) - rank_words = brace.get_text( - "Number of dimensions \\\\ is called", - "``rank''" - ) - rank_words[1].set_color(MAROON) - self.play( - GrowFromCenter(brace), - Write(rank_words) - ) - self.wait() - self.cycle_through_span_possibilities(span_text) - - def cycle_through_span_possibilities(self, span_text): - span_text.save_state() - two_d_span = span_text.copy() - for index, arr, c in (2, [1, 1], X_COLOR), (4, [0, 1], Y_COLOR): - col = Matrix(arr) - col.replace(two_d_span[index]) - two_d_span.submobjects[index] = col - col.get_entries().set_color(c) - for index in 5, 6: - two_d_span[index].scale(0) - two_d_span.arrange(RIGHT, buff = 0.2) - two_d_span[-1].next_to(two_d_span[4], RIGHT, buff = 0.2) - two_d_span.move_to(span_text, aligned_edge = RIGHT) - mob_matrix = np.array([ - two_d_span[i].get_entries().split() - for i in (2, 4) - ]) - - self.play(Transform(span_text, two_d_span)) - #horrible hack - span_text.shift(10*DOWN) - span_text = span_text.copy().restore() - ### - self.add(two_d_span) - self.wait() - self.replace_number_matrix(mob_matrix, [[1, 1], [1, 1]]) - self.wait() - self.replace_number_matrix(mob_matrix, [[0, 0], [0, 0]]) - self.wait() - self.play(Transform(two_d_span, span_text)) - self.wait() - self.remove(two_d_span) - self.add(span_text) - mob_matrix = np.array([ - span_text[i].get_entries().split() - for i in (2, 4, 6) - ]) - self.replace_number_matrix(mob_matrix, [[1, 1, 0], [0, 1, 1], [1, 0, 1]]) - self.wait() - self.replace_number_matrix(mob_matrix, [[1, 1, 0], [0, 1, 1], [-1, -2, -1]]) - self.wait() - self.replace_number_matrix(mob_matrix, [[1, 1, 0], [2, 2, 0], [3, 3, 0]]) - self.wait() - self.replace_number_matrix(mob_matrix, np.zeros((3, 3)).astype('int')) - self.wait() - - - def replace_number_matrix(self, matrix, new_numbers): - starters = matrix.flatten() - targets = list(map(TexMobject, list(map(str, np.array(new_numbers).flatten())))) - for start, target in zip(starters, targets): - target.move_to(start) - target.set_color(start.get_color()) - self.play(*[ - Transform(*pair, path_arc = np.pi) - for pair in zip(starters, targets) - ]) - -class IHatShear(LinearTransformationScene): - CONFIG = { - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_WIDTH, - "secondary_line_ratio" : 0 - }, - } - def construct(self): - self.apply_transposed_matrix([[1, 1], [0, 1]]) - self.wait() - -class DiagonalDegenerate(LinearTransformationScene): - def construct(self): - self.apply_transposed_matrix([[1, 1], [1, 1]]) - -class ZeroMatirx(LinearTransformationScene): - def construct(self): - origin = Dot(ORIGIN) - self.play(Transform( - VMobject(self.plane, self.i_hat, self.j_hat), - origin, - run_time = 3 - )) - self.wait() - -class RankNumber(Scene): - CONFIG = { - "number" : 3, - "color" : BLUE - } - def construct(self): - words = TextMobject("Rank", "%d"%self.number) - words[1].set_color(self.color) - self.add(words) - -class RankNumber2(RankNumber): - CONFIG = { - "number" : 2, - "color" : RED, - } - -class RankNumber1(RankNumber): - CONFIG = { - "number" : 1, - "color" : GREEN - } - -class RankNumber0(RankNumber): - CONFIG = { - "number" : 0, - "color" : GREY - } - -class NameFullRank(Scene): - def construct(self): - matrix = Matrix([[2, 5, 1], [3, 1, 4], [-4, 0, 0]]) - matrix.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR) - matrix.to_edge(UP) - brace = Brace(matrix) - top_words = brace.get_text( - "When", "rank", "$=$", "number of columns", - ) - top_words[1].set_color(MAROON) - low_words = TextMobject( - "matrix is", "``full rank''" - ) - low_words[1].set_color(MAROON) - low_words.next_to(top_words, DOWN) - VMobject(matrix, brace, top_words, low_words).to_corner(UP+LEFT) - self.add(matrix) - self.play( - GrowFromCenter(brace), - Write(top_words) - ) - self.wait() - self.play(Write(low_words)) - self.wait() - -class OriginIsAlwaysInColumnSpace(LinearTransformationScene): - def construct(self): - vector = Matrix([0, 0]).set_color(YELLOW) - words = TextMobject("is always in the", "column space") - words[1].set_color(TEAL) - words.next_to(vector, RIGHT) - vector.add_to_back(BackgroundRectangle(vector)) - words.add_background_rectangle() - VMobject(vector, words).center().to_edge(UP) - arrow = Arrow(vector.get_bottom(), ORIGIN) - dot = Dot(ORIGIN, color = YELLOW) - - self.play(Write(vector), Write(words)) - self.play(ShowCreation(arrow)) - self.play(ShowCreation(dot, run_time = 0.5)) - self.add_foreground_mobject(vector, words, arrow, dot) - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.wait() - self.apply_transposed_matrix([[1./3, -1./2], [-1./3, 1./2]]) - self.wait() - -class FullRankCase(LinearTransformationScene): - CONFIG = { - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_WIDTH, - "secondary_line_ratio" : 0 - }, - } - def construct(self): - t_matrices = [ - [[2, 1], [-3, 2]], - [[1./2, 1], [1./3, -1./2]] - ] - vector = Matrix([0, 0]).set_color(YELLOW) - title = VMobject( - TextMobject("Only"), vector, - TextMobject("lands on"), vector.copy() - ) - title.arrange(buff = 0.2) - title.to_edge(UP) - for mob in title: - mob.add_to_back(BackgroundRectangle(mob)) - arrow = Arrow(vector.get_bottom(), ORIGIN) - dot = Dot(ORIGIN, color = YELLOW) - - words_on = False - for t_matrix in t_matrices: - self.apply_transposed_matrix(t_matrix) - if not words_on: - self.play(Write(title)) - self.play(ShowCreation(arrow)) - self.play(ShowCreation(dot, run_time = 0.5)) - self.add_foreground_mobject(title, arrow, dot) - words_on = True - self.apply_inverse_transpose(t_matrix) - self.wait() - -class NameNullSpace(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "t_matrix" : [[1, -1], [-1, 1]] - } - def construct(self): - vectors = self.get_vectors() - dot = Dot(ORIGIN, color = YELLOW) - line = Line(vectors[0].get_end(), vectors[-1].get_end()) - line.set_color(YELLOW) - null_space_label = TextMobject("``Null space''") - kernel_label = TextMobject("``Kernel''") - null_space_label.move_to(vectors[13].get_end(), aligned_edge = UP+LEFT) - kernel_label.next_to(null_space_label, DOWN) - for mob in null_space_label, kernel_label: - mob.set_color(YELLOW) - mob.add_background_rectangle() - - self.play(ShowCreation(vectors, run_time = 3)) - self.wait() - vectors.save_state() - self.plane.save_state() - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [Transform(vectors, dot)], - path_arc = 0 - ) - self.wait() - self.play( - vectors.restore, - self.plane.restore, - *list(map(Animation, self.foreground_mobjects)), - run_time = 2 - ) - self.play(Transform( - vectors, line, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait() - for label in null_space_label, kernel_label: - self.play(Write(label)) - self.wait() - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [ - Transform(vectors, dot), - ApplyMethod(null_space_label.scale, 0), - ApplyMethod(kernel_label.scale, 0), - ], - path_arc = 0 - ) - self.wait() - - def get_vectors(self, offset = 0): - vect = np.array(UP+RIGHT) - vectors = VMobject(*[ - Vector(a*vect + offset) - for a in np.linspace(-5, 5, 18) - ]) - vectors.set_submobject_colors_by_gradient(PINK, YELLOW) - return vectors - -class ThreeDNullSpaceIsLine(Scene): - pass - -class ThreeDNullSpaceIsPlane(Scene): - pass - -class NullSpaceSolveForVEqualsZero(NameNullSpace): - def construct(self): - vec = lambda s : "\\vec{\\textbf{%s}}"%s - equation = TexMobject("A", vec("x"), "=", vec("v")) - A, x, eq, v = equation - x.set_color(PINK) - v.set_color(YELLOW) - zero_vector = Matrix([0, 0]) - zero_vector.set_color(YELLOW) - zero_vector.scale(0.7) - zero_vector.move_to(v, aligned_edge = LEFT) - VMobject(equation, zero_vector).next_to(ORIGIN, LEFT).to_edge(UP) - zero_vector_rect = BackgroundRectangle(zero_vector) - equation.add_background_rectangle() - - self.play(Write(equation)) - self.wait() - self.play( - ShowCreation(zero_vector_rect), - Transform(v, zero_vector) - ) - self.wait() - self.add_foreground_mobject(zero_vector_rect, equation) - NameNullSpace.construct(self) - -class OffsetNullSpace(NameNullSpace): - def construct(self): - x = Vector([-2, 1], color = RED) - vectors = self.get_vectors() - offset_vectors = self.get_vectors(offset = x.get_end()) - dots = VMobject(*[ - Dot(v.get_end(), color = v.get_color()) - for v in offset_vectors - ]) - dot = Dot( - self.get_matrix_transformation(self.t_matrix)(x.get_end()), - color = RED - ) - circle = Circle(color = YELLOW).replace(dot) - circle.scale_in_place(5) - words = TextMobject(""" - All vectors still land - on the same spot - """) - words.set_color(YELLOW) - words.add_background_rectangle() - words.next_to(circle) - x_copies = VMobject(*[ - x.copy().shift(v.get_end()) - for v in vectors - ]) - - self.play(FadeIn(vectors)) - self.wait() - self.add_vector(x, animate = True) - self.wait() - x_copy = VMobject(x.copy()) - self.play(Transform(x_copy, x_copies)) - self.play( - Transform(vectors, offset_vectors), - *[ - Transform(v, VectorizedPoint(v.get_end())) - for v in x_copy - ] - ) - self.remove(x_copy) - self.wait() - self.play(Transform(vectors, dots)) - self.wait() - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [Transform(vectors, dot)] - ) - self.wait() - self.play( - ShowCreation(circle), - Write(words) - ) - self.wait() - -class ShowAdditivityProperty(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "t_matrix" : [[1, 0.5], [-1, 1]], - "include_background_plane" : False, - } - def construct(self): - v = Vector([2, -1]) - w = Vector([1, 2]) - v.set_color(YELLOW) - w.set_color(MAROON_B) - sum_vect = Vector(v.get_end()+w.get_end(), color = PINK) - form = TexMobject( - "A(", - "\\vec{\\textbf{v}}", - "+", - "\\vec{\\textbf{w}}", - ")", - "=A", - "\\vec{\\textbf{v}}", - "+A", - "\\vec{\\textbf{w}}", - ) - form.to_corner(UP+RIGHT) - VMobject(form[1], form[6]).set_color(YELLOW) - VMobject(form[3], form[8]).set_color(MAROON_B) - initial_sum = VMobject(*form[1:4]) - transformer = VMobject(form[0], form[4]) - final_sum = VMobject(*form[5:]) - form_rect = BackgroundRectangle(form) - - self.add(form_rect) - self.add_vector(v, animate = True) - self.add_vector(w, animate = True) - w_copy = w.copy() - self.play(w_copy.shift, v.get_end()) - self.add_vector(sum_vect, animate = True) - self.play( - Write(initial_sum), - FadeOut(w_copy) - ) - self.add_foreground_mobject(form_rect, initial_sum) - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [Write(transformer)] - ) - self.wait() - self.play(w.copy().shift, v.get_end()) - self.play(Write(final_sum)) - self.wait() - -class AddJustOneNullSpaceVector(NameNullSpace): - def construct(self): - vectors = self.get_vectors() - self.add(vectors) - null_vector = vectors[int(0.7*len(vectors.split()))] - vectors.remove(null_vector) - null_vector.label = "$\\vec{\\textbf{n}}$" - x = Vector([-1, 1], color = RED) - x.label = "$\\vec{\\textbf{x}}$" - sum_vect = Vector( - x.get_end() + null_vector.get_end(), - color = PINK - ) - for v in x, null_vector: - v.label = TextMobject(v.label) - v.label.set_color(v.get_color()) - v.label.next_to(v.get_end(), UP) - v.label.add_background_rectangle() - dot = Dot(ORIGIN, color = null_vector.get_color()) - - form = TexMobject( - "A(", - "\\vec{\\textbf{x}}", - "+", - "\\vec{\\textbf{n}}", - ")", - "=A", - "\\vec{\\textbf{x}}", - "+A", - "\\vec{\\textbf{n}}", - ) - form.to_corner(UP+RIGHT) - VMobject(form[1], form[6]).set_color(x.get_color()) - VMobject(form[3], form[8]).set_color(null_vector.get_color()) - initial_sum = VMobject(*form[1:4]) - transformer = VMobject(form[0], form[4]) - final_sum = VMobject(*form[5:]) - brace = Brace(VMobject(*form[-2:])) - brace.add(brace.get_text("+0").add_background_rectangle()) - form_rect = BackgroundRectangle(form) - sum_vect.label = initial_sum.copy() - sum_vect.label.next_to(sum_vect.get_end(), UP) - - self.add_vector(x, animate = True) - self.play(Write(x.label)) - self.wait() - self.play( - FadeOut(vectors), - Animation(null_vector) - ) - self.play(Write(null_vector.label)) - self.wait() - x_copy = x.copy() - self.play(x_copy.shift, null_vector.get_end()) - self.add_vector(sum_vect, animate = True) - self.play( - FadeOut(x_copy), - Write(sum_vect.label) - ) - self.wait() - self.play( - ShowCreation(form_rect), - sum_vect.label.replace, initial_sum - ) - self.add_foreground_mobject(form_rect, sum_vect.label) - self.remove(x.label, null_vector.label) - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [ - Transform(null_vector, dot), - Write(transformer) - ] - ) - self.play(Write(final_sum)) - self.wait() - self.play(Write(brace)) - self.wait() - words = TextMobject( - "$\\vec{\\textbf{x}}$", - "and the", - "$\\vec{\\textbf{x}} + \\vec{\\textbf{n}}$\\\\", - "land on the same spot" - ) - words[0].set_color(x.get_color()) - VMobject(*words[2][:2]).set_color(x.get_color()) - VMobject(*words[2][3:]).set_color(null_vector.get_color()) - words.next_to(brace, DOWN) - words.to_edge(RIGHT) - self.play(Write(words)) - self.wait() - -class NullSpaceOffsetRule(Scene): - def construct(self): - vec = lambda s : "\\vec{\\textbf{%s}}"%s - equation = TexMobject("A", vec("x"), "=", vec("v")) - A, x, equals, v = equation - x.set_color(PINK) - v.set_color(YELLOW) - A_text = TextMobject( - "When $A$ is not", "full rank" - ) - A_text[1].set_color(MAROON_C) - A_text.next_to(A, UP+LEFT, buff = 1) - A_text.shift_onto_screen() - A_arrow = Arrow(A_text.get_bottom(), A, color = WHITE) - v_text = TextMobject( - "If", "$%s$"%vec("v"), "is in the", - "column space", "of $A$" - ) - v_text[1].set_color(YELLOW) - v_text[3].set_color(TEAL) - v_text.next_to(v, DOWN+RIGHT, buff = 1) - v_text.shift_onto_screen() - v_arrow = Arrow(v_text.get_top(), v, color = YELLOW) - - - self.add(equation) - self.play(Write(A_text, run_time = 2)) - self.play(ShowCreation(A_arrow)) - self.wait() - self.play(Write(v_text, run_time = 2)) - self.play(ShowCreation(v_arrow)) - self.wait() - -class MuchLeftToLearn(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "That's the high \\\\", - "level overview" - ) - self.random_blink() - self.wait() - self.teacher_says( - "There is still \\\\", - "much to learn" - ) - for pi in self.get_students(): - target_mode = random.choice([ - "raise_right_hand", "raise_left_hand" - ]) - self.play(pi.change_mode, target_mode) - self.random_blink() - self.wait() - -class NotToLearnItAllNow(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - The goal is not to - learn it all now - """) - self.random_blink() - self.wait() - self.random_blink() - -class NextVideo(Scene): - def construct(self): - title = TextMobject(""" - Next video: Nonsquare matrices - """) - title.set_width(FRAME_WIDTH - 2) - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - -class WhatAboutNonsquareMatrices(TeacherStudentsScene): - def construct(self): - self.student_says( - "What about \\\\ nonsquare matrices?", - target_mode = "raise_right_hand" - ) - self.play(self.get_students()[0].change_mode, "confused") - self.random_blink(6) - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/chapter7.py b/from_3b1b/old/eola/chapter7.py deleted file mode 100644 index 2da6c5df..00000000 --- a/from_3b1b/old/eola/chapter7.py +++ /dev/null @@ -1,2310 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eola.footnote2 import TwoDTo1DTransformWithDots - - -V_COLOR = YELLOW -W_COLOR = MAROON_B -SUM_COLOR = PINK - - -def get_projection(vector_to_project, stable_vector): - v1, v2 = stable_vector, vector_to_project - return v1*np.dot(v1, v2)/(get_norm(v1)**2) - -def get_vect_mob_projection(vector_to_project, stable_vector): - return Vector( - get_projection( - vector_to_project.get_end(), - stable_vector.get_end() - ), - color = vector_to_project.get_color() - ).fade() - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject( - "\\small Calvin:", - "You know, I don't think math is a science, I think it's a religion.", - "\\\\Hobbes:", - "A religion?", - "\\\\Calvin:" , - "Yeah. All these equations are like miracles." - "You take two numbers and when you add them, " - "they magically become one NEW number! " - "No one can say how it happens. " - "You either believe it or you don't.", - ) - words.set_width(FRAME_WIDTH - 1) - words.to_edge(UP) - words[0].set_color(YELLOW) - words[2].set_color("#fd9c2b") - words[4].set_color(YELLOW) - - for i in range(3): - speaker, quote = words[2*i:2*i+2] - self.play(FadeIn(speaker, run_time = 0.5)) - rt = max(1, len(quote.split())/18) - self.play(Write( - quote, - run_time = rt, - )) - self.wait(2) - -class TraditionalOrdering(RandolphScene): - def construct(self): - title = TextMobject("Traditional ordering:") - title.set_color(YELLOW) - title.scale(1.2) - title.to_corner(UP+LEFT) - topics = VMobject(*list(map(TextMobject, [ - "Topic 1: Vectors", - "Topic 2: Dot products", - "\\vdots", - "(everything else)", - "\\vdots", - ]))) - topics.arrange(DOWN, aligned_edge = LEFT, buff = SMALL_BUFF) - # topics.next_to(title, DOWN+RIGHT) - - self.play( - Write(title, run_time = 1), - FadeIn( - topics, - run_time = 3, - lag_ratio = 0.5 - ), - ) - self.play(topics[1].set_color, PINK) - self.wait() - -class ThisSeriesOrdering(RandolphScene): - def construct(self): - title = TextMobject("Essence of linear algebra") - self.randy.rotate(np.pi, UP) - title.scale(1.2).set_color(BLUE) - title.to_corner(UP+LEFT) - line = Line(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT, color = WHITE) - line.next_to(title, DOWN, buff = SMALL_BUFF) - line.to_edge(LEFT, buff = 0) - - chapters = VMobject(*[ - TextMobject("\\small " + text) - for text in [ - "Chapter 1: Vectors, what even are they?", - "Chapter 2: Linear combinations, span and bases", - "Chapter 3: Matrices as linear transformations", - "Chapter 4: Matrix multiplication as composition", - "Chapter 5: The determinant", - "Chapter 6: Inverse matrices, column space and null space", - "Chapter 7: Dot products and duality", - "Chapter 8: Cross products via transformations", - "Chapter 9: Change of basis", - "Chapter 10: Eigenvectors and eigenvalues", - "Chapter 11: Abstract vector spaces", - ] - ]) - chapters.arrange( - DOWN, buff = SMALL_BUFF, aligned_edge = LEFT - ) - chapters.set_height(1.5*FRAME_Y_RADIUS) - chapters.next_to(line, DOWN, buff = SMALL_BUFF) - chapters.to_edge(RIGHT) - - self.add(title) - self.play( - ShowCreation(line), - ) - self.play( - FadeIn( - chapters, - lag_ratio = 0.5, - run_time = 3 - ), - self.randy.change_mode, "sassy" - ) - self.play(self.randy.look, UP+LEFT) - self.play(chapters[6].set_color, PINK) - self.wait(6) - -class OneMustViewThroughTransformations(TeacherStudentsScene): - def construct(self): - words = TextMobject( - "Only with" , "transformations", - "\n can we truly understand", - ) - words.set_color_by_tex("transformations", BLUE) - self.teacher_says(words) - self.change_student_modes( - "pondering", - "plain", - "raise_right_hand" - ) - self.random_blink(2) - words = TextMobject( - "First, the ", - "standard view...", - arg_separator = "\n" - ) - self.teacher_says(words) - self.random_blink(2) - -class ShowNumericalDotProduct(Scene): - CONFIG = { - "v1" : [2, 7, 1], - "v2" : [8, 2, 8], - "write_dot_product_words" : True, - } - def construct(self): - v1 = Matrix(self.v1) - v2 = Matrix(self.v2) - inter_array_dot = TexMobject("\\cdot").scale(1.5) - dot_product = VGroup(v1, inter_array_dot, v2) - dot_product.arrange(RIGHT, buff = MED_SMALL_BUFF/2) - dot_product.to_edge(LEFT) - pairs = list(zip(v1.get_entries(), v2.get_entries())) - - for pair, color in zip(pairs, [X_COLOR, Y_COLOR, Z_COLOR, PINK]): - VGroup(*pair).set_color(color) - - dot = TexMobject("\\cdot") - products = VGroup(*[ - VGroup( - p1.copy(), dot.copy(), p2.copy() - ).arrange(RIGHT, buff = SMALL_BUFF) - for p1, p2 in pairs - ]) - products.arrange(DOWN, buff = LARGE_BUFF) - products.next_to(dot_product, RIGHT, buff = LARGE_BUFF) - - - products.target = products.copy() - plusses = ["+"]*(len(self.v1)-1) - symbols = VGroup(*list(map(TexMobject, ["="] + plusses))) - final_sum = VGroup(*it.chain(*list(zip( - symbols, products.target - )))) - final_sum.arrange(RIGHT, buff = SMALL_BUFF) - final_sum.next_to(dot_product, RIGHT) - - title = TextMobject("Two vectors of the same dimension") - title.to_edge(UP) - - arrow = Arrow(DOWN, UP).next_to(inter_array_dot, DOWN) - dot_product_words = TextMobject("Dot product") - dot_product_words.set_color(YELLOW) - dot_product_words.next_to(arrow, DOWN) - dot_product_words.shift_onto_screen() - - self.play( - Write(v1), - Write(v2), - FadeIn(inter_array_dot), - FadeIn(title) - ) - self.wait() - if self.write_dot_product_words: - self.play( - inter_array_dot.set_color, YELLOW, - ShowCreation(arrow), - Write(dot_product_words, run_time = 2) - ) - self.wait() - self.play(Transform( - VGroup(*it.starmap(Group, pairs)).copy(), - products, - path_arc = -np.pi/2, - run_time = 2 - )) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(products) - self.wait() - - self.play( - Write(symbols), - Transform(products, products.target, path_arc = np.pi/2) - ) - self.wait() - -class TwoDDotProductExample(ShowNumericalDotProduct): - CONFIG = { - "v1" : [1, 2], - "v2" : [3, 4], - } - -class FourDDotProductExample(ShowNumericalDotProduct): - CONFIG = { - "v1" : [6, 2, 8, 3], - "v2" : [1, 8, 5, 3], - "write_dot_product_words" : False, - } - -class GeometricInterpretation(VectorScene): - CONFIG = { - "v_coords" : [4, 1], - "w_coords" : [2, -1], - "v_color" : V_COLOR, - "w_color" : W_COLOR, - "project_onto_v" : True, - } - def construct(self): - self.lock_in_faded_grid() - self.add_symbols() - self.add_vectors() - self.line() - self.project() - self.show_lengths() - self.handle_possible_negative() - - - def add_symbols(self): - v = matrix_to_mobject(self.v_coords).set_color(self.v_color) - w = matrix_to_mobject(self.w_coords).set_color(self.w_color) - v.add_background_rectangle() - w.add_background_rectangle() - dot = TexMobject("\\cdot") - eq = VMobject(v, dot, w) - eq.arrange(RIGHT, buff = SMALL_BUFF) - eq.to_corner(UP+LEFT) - self.play(Write(eq), run_time = 1) - for array, char in zip([v, w], ["v", "w"]): - brace = Brace(array, DOWN) - label = brace.get_text("$\\vec{\\textbf{%s}}$"%char) - label.set_color(array.get_color()) - self.play( - GrowFromCenter(brace), - Write(label), - run_time = 1 - ) - self.dot_product = eq - - - def add_vectors(self): - self.v = Vector(self.v_coords, color = self.v_color) - self.w = Vector(self.w_coords, color = self.w_color) - self.play(ShowCreation(self.v)) - self.play(ShowCreation(self.w)) - for vect, char, direction in zip( - [self.v, self.w], ["v", "w"], [DOWN+RIGHT, DOWN] - ): - label = TexMobject("\\vec{\\textbf{%s}}"%char) - label.next_to(vect.get_end(), direction) - label.set_color(vect.get_color()) - self.play(Write(label, run_time = 1)) - self.stable_vect = self.v if self.project_onto_v else self.w - self.proj_vect = self.w if self.project_onto_v else self.v - - def line(self): - line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - line.rotate(self.stable_vect.get_angle()) - self.play(ShowCreation(line), Animation(self.stable_vect)) - self.wait() - - def project(self): - dot_product = np.dot(self.v.get_end(), self.w.get_end()) - v_norm, w_norm = [ - get_norm(vect.get_end()) - for vect in (self.v, self.w) - ] - projected = Vector( - self.stable_vect.get_end()*dot_product/( - self.stable_vect.get_length()**2 - ), - color = self.proj_vect.get_color() - ) - projection_line = Line( - self.proj_vect.get_end(), projected.get_end(), - color = GREY - ) - - self.play(ShowCreation(projection_line)) - self.add(self.proj_vect.copy().fade()) - self.play(Transform(self.proj_vect, projected)) - self.wait() - - def show_lengths(self): - stable_char = "v" if self.project_onto_v else "w" - proj_char = "w" if self.project_onto_v else "v" - product = TextMobject( - "=", - "(", - "Length of projected $\\vec{\\textbf{%s}}$"%proj_char, - ")", - "(", - "Length of $\\vec{\\textbf{%s}}$"%stable_char, - ")", - arg_separator = "" - ) - product.scale(0.9) - product.next_to(self.dot_product, RIGHT) - proj_words = product[2] - proj_words.set_color(self.proj_vect.get_color()) - stable_words = product[5] - stable_words.set_color(self.stable_vect.get_color()) - product.remove(proj_words, stable_words) - for words in stable_words, proj_words: - words.add_to_back(BackgroundRectangle(words)) - words.start = words.copy() - - proj_brace, stable_brace = braces = [ - Brace(Line(ORIGIN, vect.get_length()*RIGHT*sgn), UP) - for vect in (self.proj_vect, self.stable_vect) - for sgn in [np.sign(np.dot(vect.get_end(), self.stable_vect.get_end()))] - ] - proj_brace.put_at_tip(proj_words.start) - proj_brace.words = proj_words.start - stable_brace.put_at_tip(stable_words.start) - stable_brace.words = stable_words.start - for brace in braces: - brace.rotate(self.stable_vect.get_angle()) - brace.words.rotate(self.stable_vect.get_angle()) - - self.play( - GrowFromCenter(proj_brace), - Write(proj_words.start, run_time = 2) - ) - self.wait() - self.play( - Transform(proj_words.start, proj_words), - FadeOut(proj_brace) - ) - self.play( - GrowFromCenter(stable_brace), - Write(stable_words.start, run_time = 2), - Animation(self.stable_vect) - ) - self.wait() - self.play( - Transform(stable_words.start, stable_words), - Write(product) - ) - self.wait() - - product.add(stable_words.start, proj_words.start) - self.product = product - - def handle_possible_negative(self): - if np.dot(self.w.get_end(), self.v.get_end()) > 0: - return - neg = TexMobject("-").set_color(RED) - neg.next_to(self.product[0], RIGHT) - words = TextMobject("Should be negative") - words.set_color(RED) - words.next_to( - VMobject(*self.product[2:]), - DOWN, - buff = LARGE_BUFF, - aligned_edge = LEFT - ) - words.add_background_rectangle() - arrow = Arrow(words.get_left(), neg, color = RED) - - self.play( - Write(neg), - ShowCreation(arrow), - VMobject(*self.product[1:]).next_to, neg, - Write(words) - ) - self.wait() - -class GeometricInterpretationNegative(GeometricInterpretation): - CONFIG = { - "v_coords" : [3, 1], - "w_coords" : [-1, -2], - "v_color" : YELLOW, - "w_color" : MAROON_B, - } - -class ShowQualitativeDotProductValues(VectorScene): - def construct(self): - self.lock_in_faded_grid() - v_sym, dot, w_sym, comp, zero = ineq = TexMobject( - "\\vec{\\textbf{v}}", - "\\cdot", - "\\vec{\\textbf{w}}", - ">", - "0", - ) - ineq.to_edge(UP) - ineq.add_background_rectangle() - comp.set_color(GREEN) - equals = TexMobject("=").set_color(PINK).move_to(comp) - less_than = TexMobject("<").set_color(RED).move_to(comp) - v_sym.set_color(V_COLOR) - w_sym.set_color(W_COLOR) - words = list(map(TextMobject, [ - "Similar directions", - "Perpendicular", - "Opposing directions" - ])) - for word, sym in zip(words, [comp, equals, less_than]): - word.add_background_rectangle() - word.next_to(sym, DOWN, aligned_edge = LEFT, buff = MED_SMALL_BUFF) - word.set_color(sym.get_color()) - - v = Vector([1.5, 1.5], color = V_COLOR) - w = Vector([2, 2], color = W_COLOR) - w.rotate(-np.pi/6) - shadow = Vector( - v.get_end()*np.dot(v.get_end(), w.get_end())/(v.get_length()**2), - color = MAROON_E, - preserve_tip_size_when_scaling = False - ) - shadow_opposite = shadow.copy().scale(-1) - line = Line(LEFT, RIGHT, color = WHITE) - line.scale(FRAME_X_RADIUS) - line.rotate(v.get_angle()) - proj_line = Line(w.get_end(), shadow.get_end(), color = GREY) - - - word = words[0] - - self.add(ineq) - for mob in v, w, line, proj_line: - self.play( - ShowCreation(mob), - Animation(v) - ) - self.play(Transform(w.copy(), shadow)) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(shadow) - self.play(FadeOut(proj_line)) - - self.play(Write(word, run_time = 1)) - self.wait() - self.play( - Rotate(w, -np.pi/3), - shadow.scale, 0 - ) - self.play( - Transform(comp, equals), - Transform(word, words[1]) - ) - self.wait() - self.play( - Rotate(w, -np.pi/3), - Transform(shadow, shadow_opposite) - ) - self.play( - Transform(comp, less_than), - Transform(word, words[2]) - ) - self.wait() - -class AskAboutSymmetry(TeacherStudentsScene): - def construct(self): - v, w = "\\vec{\\textbf{v}}", "\\vec{\\textbf{w}}", - question = TexMobject( - "\\text{Why does }", - v, "\\cdot", w, "=", w, "\\cdot", v, - "\\text{?}" - ) - VMobject(question[1], question[7]).set_color(V_COLOR) - VMobject(question[3], question[5]).set_color(W_COLOR) - self.student_says( - question, - target_mode = "raise_left_hand" - ) - self.change_student_modes("confused") - self.play(self.get_teacher().change_mode, "pondering") - self.play(self.get_teacher().look, RIGHT) - self.play(self.get_teacher().look, LEFT) - self.random_blink() - -class GeometricInterpretationSwapVectors(GeometricInterpretation): - CONFIG = { - "project_onto_v" : False, - } - -class SymmetricVAndW(VectorScene): - def construct(self): - self.lock_in_faded_grid() - v = Vector([3, 1], color = V_COLOR) - w = Vector([1, 3], color = W_COLOR) - for vect, char in zip([v, w], ["v", "w"]): - vect.label = TexMobject("\\vec{\\textbf{%s}}"%char) - vect.label.set_color(vect.get_color()) - vect.label.next_to(vect.get_end(), DOWN+RIGHT) - for v1, v2 in (v, w), (w, v): - v1.proj = get_vect_mob_projection(v1, v2) - v1.proj_line = Line( - v1.get_end(), v1.proj.get_end(), color = GREY - ) - line_of_symmetry = DashedLine(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) - line_of_symmetry.rotate(np.mean([v.get_angle(), w.get_angle()])) - line_of_symmetry_words = TextMobject("Line of symmetry") - line_of_symmetry_words.add_background_rectangle() - line_of_symmetry_words.next_to(ORIGIN, UP+LEFT) - line_of_symmetry_words.rotate(line_of_symmetry.get_angle()) - - for vect in v, w: - self.play(ShowCreation(vect)) - self.play(Write(vect.label, run_time = 1)) - self.wait() - angle = (v.get_angle()-w.get_angle())/2 - self.play( - Rotate(w, angle), - Rotate(v, -angle), - rate_func = there_and_back, - run_time = 2 - ) - self.wait() - self.play( - ShowCreation(line_of_symmetry), - Write(line_of_symmetry_words, run_time = 1) - ) - self.wait(0.5) - self.remove(line_of_symmetry_words) - self.play(*list(map(Uncreate, line_of_symmetry_words))) - for vect in w, v: - self.play(ShowCreation(vect.proj_line)) - vect_copy = vect.copy() - self.play(Transform(vect_copy, vect.proj)) - self.remove(vect_copy) - self.add(vect.proj) - self.wait() - self.play(*list(map(FadeOut,[ - v.proj, v.proj_line, w.proj, w.proj_line - ]))) - self.show_doubling(v, w) - - def show_doubling(self, v, w): - scalar = 2 - new_v = v.copy().scale(scalar) - new_v.label = VMobject(TexMobject("2"), v.label.copy()) - new_v.label.arrange(aligned_edge = DOWN) - new_v.label.next_to(new_v.get_end(), DOWN+RIGHT) - new_v.proj = v.proj.copy().scale(scalar) - new_v.proj.fade() - new_v.proj_line = Line( - new_v.get_end(), new_v.proj.get_end(), - color = GREY - ) - - v_tex, w_tex = ["\\vec{\\textbf{%s}}"%c for c in ("v", "w")] - equation = TexMobject( - "(", "2", v_tex, ")", "\\cdot", w_tex, - "=", - "2(", v_tex, "\\cdot", w_tex, ")" - ) - equation.set_color_by_tex(v_tex, V_COLOR) - equation.set_color_by_tex(w_tex, W_COLOR) - equation.next_to(ORIGIN, DOWN).to_edge(RIGHT) - - words = TextMobject("Symmetry is broken") - words.next_to(ORIGIN, LEFT) - words.to_edge(UP) - - v.save_state() - v.proj.save_state() - v.proj_line.save_state() - self.play(Transform(*[ - VGroup(mob, mob.label) - for mob in (v, new_v) - ]), run_time = 2) - last_mob = self.get_mobjects_from_last_animation()[0] - self.remove(last_mob) - self.add(*last_mob) - Transform( - VGroup(v.proj, v.proj_line), - VGroup(new_v.proj, new_v.proj_line) - ).update(1)##Hacky - - self.play(Write(words)) - self.wait() - - two_v_parts = equation[1:3] - equation.remove(*two_v_parts) - - for full_v, projector, stable in zip([False, True], [w, v], [v, w]): - self.play( - Transform(projector.copy(), projector.proj), - ShowCreation(projector.proj_line) - ) - self.remove(self.get_mobjects_from_last_animation()[0]) - self.add(projector.proj) - self.wait() - if equation not in self.get_mobjects(): - self.play( - Write(equation), - Transform(new_v.label.copy(), VMobject(*two_v_parts)) - ) - self.wait() - v_parts = [v] - if full_v: - v_parts += [v.proj, v.proj_line] - self.play( - *[v_part.restore for v_part in v_parts], - rate_func = there_and_back, - run_time = 2 - ) - self.wait() - self.play(*list(map(FadeOut, [ - projector.proj, projector.proj_line - ]))) - -class LurkingQuestion(TeacherStudentsScene): - def construct(self): - # self.teacher_says("That's the standard intro") - # self.wait() - # self.student_says( - # """ - # Wait, why are the - # two views connected? - # """, - # student_index = 2, - # target_mode = "raise_left_hand", - # width = 6, - # ) - self.change_student_modes( - "raise_right_hand", "confused", "raise_left_hand" - ) - self.random_blink(5) - answer = TextMobject(""" - The most satisfactory - answer comes from""", - "duality" - ) - answer[-1].set_color_by_gradient(BLUE, YELLOW) - self.teacher_says(answer) - self.random_blink(2) - self.teacher_thinks("") - everything = VMobject(*self.get_mobjects()) - self.play(ApplyPointwiseFunction( - lambda p : 10*(p+2*DOWN)/get_norm(p+2*DOWN), - everything - )) - -class TwoDToOneDScene(LinearTransformationScene): - CONFIG = { - "include_background_plane" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_X_RADIUS, - "y_radius" : FRAME_Y_RADIUS, - "secondary_line_ratio" : 1 - }, - "t_matrix" : [[2, 0], [1, 0]] - } - def setup(self): - self.number_line = NumberLine() - self.add(self.number_line) - LinearTransformationScene.setup(self) - -class Introduce2Dto1DLinearTransformations(TwoDToOneDScene): - def construct(self): - number_line_words = TextMobject("Number line") - number_line_words.next_to(self.number_line, UP, buff = MED_SMALL_BUFF) - numbers = VMobject(*self.number_line.get_number_mobjects()) - - self.remove(self.number_line) - self.apply_transposed_matrix(self.t_matrix) - self.play( - ShowCreation(number_line), - *[Animation(v) for v in (self.i_hat, self.j_hat)] - ) - self.play(*list(map(Write, [numbers, number_line_words]))) - self.wait() - -class Symbolic2To1DTransform(Scene): - def construct(self): - func = TexMobject("L(", "\\vec{\\textbf{v}}", ")") - input_array = Matrix([2, 7]) - input_array.set_color(YELLOW) - in_arrow = Arrow(LEFT, RIGHT, color = input_array.get_color()) - func[1].set_color(input_array.get_color()) - output_array = Matrix([1.8]) - output_array.set_color(PINK) - out_arrow = Arrow(LEFT, RIGHT, color = output_array.get_color()) - VMobject( - input_array, in_arrow, func, out_arrow, output_array - ).arrange(RIGHT, buff = SMALL_BUFF) - - input_brace = Brace(input_array, DOWN) - input_words = input_brace.get_text("2d input") - output_brace = Brace(output_array, UP) - output_words = output_brace.get_text("1d output") - input_words.set_color(input_array.get_color()) - output_words.set_color(output_array.get_color()) - - special_words = TextMobject("Linear", "functions are quite special") - special_words.set_color_by_tex("Linear", BLUE) - special_words.to_edge(UP) - - - self.add(func, input_array) - self.play( - GrowFromCenter(input_brace), - Write(input_words) - ) - mover = input_array.copy() - self.play( - Transform(mover, Dot().move_to(func)), - ShowCreation(in_arrow), - rate_func = rush_into, - run_time = 0.5 - ) - self.play( - Transform(mover, output_array), - ShowCreation(out_arrow), - rate_func = rush_from, - run_time = 0.5 - ) - self.play( - GrowFromCenter(output_brace), - Write(output_words) - ) - self.wait() - self.play(Write(special_words)) - self.wait() - -class RecommendChapter3(Scene): - def construct(self): - title = TextMobject(""" - Definitely watch Chapter 3 - if you haven't already - """) - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - -class OkayToIgnoreFormalProperties(Scene): - def construct(self): - title = TextMobject("Formal linearity properties") - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - v_tex, w_tex = ["\\vec{\\textbf{%s}}"%s for s in ("v", "w")] - additivity = TexMobject( - "L(", v_tex, "+", w_tex, ") = ", - "L(", v_tex, ") + L(", w_tex, ")", - ) - scaling = TexMobject( - "L(", "c", v_tex, ") =", "c", "L(", v_tex, ")", - ) - for tex_mob in additivity, scaling: - tex_mob.set_color_by_tex(v_tex, V_COLOR) - tex_mob.set_color_by_tex(w_tex, W_COLOR) - tex_mob.set_color_by_tex("c", GREEN) - additivity.next_to(h_line, DOWN, buff = MED_SMALL_BUFF) - scaling.next_to(additivity, DOWN, buff = MED_SMALL_BUFF) - words = TextMobject("We'll ignore these") - words.set_color(RED) - arrow = Arrow(DOWN, UP, color = RED) - arrow.next_to(scaling, DOWN) - words.next_to(arrow, DOWN) - - randy = Randolph().to_corner(DOWN+LEFT) - morty = Mortimer().to_corner(DOWN+RIGHT) - - self.add(randy, morty, title) - self.play(ShowCreation(h_line)) - for tex_mob in additivity, scaling: - self.play(Write(tex_mob, run_time = 2)) - self.play( - FadeIn(words), - ShowCreation(arrow), - ) - self.play( - randy.look, LEFT, - morty.look, RIGHT, - ) - self.wait() - -class FormalVsVisual(Scene): - def construct(self): - title = TextMobject("Linearity") - title.set_color(BLUE) - title.to_edge(UP) - line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - line.next_to(title, DOWN) - v_line = Line(line.get_center(), FRAME_Y_RADIUS*DOWN) - - formal = TextMobject("Formal definition") - visual = TextMobject("Visual intuition") - formal.next_to(line, DOWN).shift(FRAME_X_RADIUS*LEFT/2) - visual.next_to(line, DOWN).shift(FRAME_X_RADIUS*RIGHT/2) - - v_tex, w_tex = ["\\vec{\\textbf{%s}}"%c for c in ("v", "w")] - additivity = TexMobject( - "L(", v_tex, "+", w_tex, ") = ", - "L(", v_tex, ")+", "L(", w_tex, ")" - ) - additivity.set_color_by_tex(v_tex, V_COLOR) - additivity.set_color_by_tex(w_tex, W_COLOR) - scaling = TexMobject( - "L(", "c", v_tex, ")=", "c", "L(", v_tex, ")" - ) - scaling.set_color_by_tex(v_tex, V_COLOR) - scaling.set_color_by_tex("c", GREEN) - - visual_statement = TextMobject(""" - Line of dots evenly spaced - dots remains evenly spaced - """) - visual_statement.set_submobject_colors_by_gradient(YELLOW, MAROON_B) - - properties = VMobject(additivity, scaling) - properties.arrange(DOWN, buff = MED_SMALL_BUFF) - - for text, mob in (formal, properties), (visual, visual_statement): - mob.scale(0.75) - mob.next_to(text, DOWN, buff = MED_SMALL_BUFF) - - self.add(title) - self.play(*list(map(ShowCreation, [line, v_line]))) - for mob in formal, visual, additivity, scaling, visual_statement: - self.play(Write(mob, run_time = 2)) - self.wait() - -class AdditivityProperty(TwoDToOneDScene): - CONFIG = { - "show_basis_vectors" : False, - "sum_before" : True - } - def construct(self): - v = Vector([2, 1], color = V_COLOR) - w = Vector([-1, 1], color = W_COLOR) - - - L, sum_tex, r_paren = symbols = self.get_symbols() - symbols.shift(4*RIGHT+2*UP) - self.add_foreground_mobject(sum_tex) - self.play(ShowCreation(v)) - self.play(ShowCreation(w)) - if self.sum_before: - sum_vect = self.play_sum(v, w) - else: - self.add_vector(v, animate = False) - self.add_vector(w, animate = False) - self.apply_transposed_matrix(self.t_matrix) - if not self.sum_before: - sum_vect = self.play_sum(v, w) - symbols.target = symbols.copy().next_to(sum_vect, UP) - VGroup(L, r_paren).set_color(BLACK) - self.play(Transform(symbols, symbols.target)) - self.wait() - - def play_sum(self, v, w): - sum_vect = Vector(v.get_end()+w.get_end(), color = SUM_COLOR) - self.play(w.shift, v.get_end(), path_arc = np.pi/4) - self.play(ShowCreation(sum_vect)) - for vect in v, w: - vect.target = vect.copy() - vect.target.set_fill(opacity = 0) - vect.target.set_stroke(width = 0) - sum_vect.target = sum_vect - self.play(*[ - Transform(mob, mob.target) - for mob in (v, w, sum_vect) - ]) - self.add_vector(sum_vect, animate = False) - return sum_vect - - def get_symbols(self): - v_tex, w_tex = ["\\vec{\\textbf{%s}}"%c for c in ("v", "w")] - if self.sum_before: - tex_mob = TexMobject( - "L(", v_tex, "+", w_tex, ")" - ) - result = VGroup( - tex_mob[0], - VGroup(*tex_mob[1:4]), - tex_mob[4] - ) - else: - tex_mob = TexMobject( - "L(", v_tex, ")", "+", "L(", w_tex, ")" - ) - result = VGroup( - VectorizedPoint(tex_mob.get_left()), - tex_mob, - VectorizedPoint(tex_mob.get_right()), - ) - tex_mob.set_color_by_tex(v_tex, V_COLOR) - tex_mob.set_color_by_tex(w_tex, W_COLOR) - result[1].add_to_back(BackgroundRectangle(result[1])) - return result - -class AdditivityPropertyPart2(AdditivityProperty): - CONFIG = { - "sum_before" : False - } - -class ScalingProperty(TwoDToOneDScene): - CONFIG = { - "show_basis_vectors" : False, - "scale_before" : True, - "scalar" : 2, - } - def construct(self): - v = Vector([-1, 1], color = V_COLOR) - - self.play(ShowCreation(v)) - if self.scale_before: - scaled_vect = self.show_scaling(v) - self.add_vector(v, animate = False) - self.apply_transposed_matrix(self.t_matrix) - if not self.scale_before: - scaled_vect = self.show_scaling(v) - self.wait() - self.write_symbols(scaled_vect) - - def show_scaling(self, v): - self.add_vector(v.copy().fade(), animate = False) - self.play(v.scale, self.scalar) - return v - - def write_symbols(self, scaled_vect): - v_tex = "\\vec{\\textbf{v}}" - if self.scale_before: - tex_mob = TexMobject( - "L(", "c", v_tex, ")" - ) - tex_mob.next_to(scaled_vect, UP) - else: - tex_mob = TexMobject( - "c", "L(", v_tex, ")", - ) - tex_mob.next_to(scaled_vect, DOWN) - tex_mob.set_color_by_tex(v_tex, V_COLOR) - tex_mob.set_color_by_tex("c", GREEN) - - self.play(Write(tex_mob)) - self.wait() - -class ScalingPropertyPart2(ScalingProperty): - CONFIG = { - "scale_before" : False - } - -class ThisTwoDTo1DTransformWithDots(TwoDTo1DTransformWithDots): - pass - -class NonLinearFailsDotTest(TwoDTo1DTransformWithDots): - def construct(self): - line = NumberLine() - self.add(line, *self.get_mobjects()) - offset = LEFT+DOWN - vect = 2*RIGHT+UP - dots = VMobject(*[ - Dot(offset + a*vect, radius = 0.075) - for a in np.linspace(-2, 3, 18) - ]) - dots.set_submobject_colors_by_gradient(YELLOW_B, YELLOW_C) - func = lambda p : (p[0]**2 - p[1]**2)*RIGHT - new_dots = VMobject(*[ - Dot( - func(dot.get_center()), - color = dot.get_color(), - radius = dot.radius - ) - for dot in dots - ]) - words = TextMobject( - "Line of dots", "do not", "remain evenly spaced" - ) - words.set_color_by_tex("do not", RED) - words.next_to(line, UP, buff = MED_SMALL_BUFF) - array_tex = matrix_to_tex_string(["x", "y"]) - equation = TexMobject( - "f", "\\left(%s \\right)"%array_tex, " = x^2 - y^2" - ) - for part in equation: - part.add_to_back(BackgroundRectangle(part)) - equation.to_corner(UP+LEFT) - self.add_foreground_mobject(equation) - - self.play(Write(dots)) - self.apply_nonlinear_transformation( - func, - added_anims = [Transform(dots, new_dots)] - ) - self.play(Write(words)) - self.wait() - -class AlwaysfollowIHatJHat(TeacherStudentsScene): - def construct(self): - i_tex, j_tex = ["$\\hat{\\%smath}$"%c for c in ("i", "j")] - words = TextMobject( - "Always follow", i_tex, "and", j_tex - ) - words.set_color_by_tex(i_tex, X_COLOR) - words.set_color_by_tex(j_tex, Y_COLOR) - self.teacher_says(words) - students = VMobject(*self.get_students()) - ponderers = VMobject(*[ - pi.copy().change_mode("pondering") - for pi in students - ]) - self.play(Transform( - students, ponderers, - lag_ratio = 0.5, - run_time = 2 - )) - self.random_blink(2) - -class ShowMatrix(TwoDToOneDScene): - def construct(self): - self.apply_transposed_matrix(self.t_matrix) - self.play(Write(self.number_line.get_numbers())) - self.show_matrix() - - def show_matrix(self): - for vect, char in zip([self.i_hat, self.j_hat], ["i", "j"]): - vect.words = TextMobject( - "$\\hat\\%smath$ lands on"%char, - str(int(vect.get_end()[0])) - ) - direction = UP if vect is self.i_hat else DOWN - vect.words.next_to(vect.get_end(), direction, buff = LARGE_BUFF) - vect.words.set_color(vect.get_color()) - matrix = Matrix([[1, 2]]) - matrix_words = TextMobject("Transformation matrix: ") - matrix_group = VMobject(matrix_words, matrix) - matrix_group.arrange() - matrix_group.to_edge(UP) - entries = matrix.get_entries() - - self.play( - Write(matrix_words), - Write(matrix.get_brackets()), - run_time = 1 - ) - for i, vect in enumerate([self.i_hat, self.j_hat]): - self.play( - Write(vect.words, run_time = 1), - ApplyMethod(vect.shift, 0.5*UP, rate_func = there_and_back) - ) - self.wait() - self.play(vect.words[1].copy().move_to, entries[i]) - self.wait() - -class FollowVectorViaCoordinates(TwoDToOneDScene): - CONFIG = { - "t_matrix" : [[1, 0], [-2, 0]], - "v_coords" : [3, 3], - "written_v_coords" : ["x", "y"], - "concrete" : False, - } - def construct(self): - v = Vector(self.v_coords) - array = Matrix(self.v_coords if self.concrete else self.written_v_coords) - array.get_entries().set_color_by_gradient(X_COLOR, Y_COLOR) - array.add_to_back(BackgroundRectangle(array)) - v_label = TexMobject("\\vec{\\textbf{v}}", "=") - v_label[0].set_color(YELLOW) - v_label.next_to(v.get_end(), RIGHT) - v_label.add_background_rectangle() - array.next_to(v_label, RIGHT) - - bases = self.i_hat, self.j_hat - basis_labels = self.get_basis_vector_labels(direction = "right") - scaling_anim_tuples = self.get_scaling_anim_tuples( - basis_labels, array, [DOWN, RIGHT] - ) - - self.play(*list(map(Write, basis_labels))) - self.play( - ShowCreation(v), - Write(array), - Write(v_label) - ) - self.add_foreground_mobject(v_label, array) - self.add_vector(v, animate = False) - self.wait() - to_fade = basis_labels - for i, anim_tuple in enumerate(scaling_anim_tuples): - self.play(*anim_tuple) - movers = self.get_mobjects_from_last_animation() - to_fade += movers[:-1] - if i == 1: - self.play(*[ - ApplyMethod(m.shift, self.v_coords[0]*RIGHT) - for m in movers - ]) - self.wait() - self.play( - *list(map(FadeOut, to_fade)) + [ - vect.restore - for vect in (self.i_hat, self.j_hat) - ] - ) - - self.apply_transposed_matrix(self.t_matrix) - self.play(Write(self.number_line.get_numbers(), run_time = 1)) - self.play( - self.i_hat.shift, 0.5*UP, - self.j_hat.shift, DOWN, - ) - if self.concrete: - new_labels = [ - TexMobject("(%d)"%num) - for num in self.t_matrix[:,0] - ] - else: - new_labels = [ - TexMobject("L(\\hat{\\%smath})"%char) - for char in ("i", "j") - ] - - new_labels[0].set_color(X_COLOR) - new_labels[1].set_color(Y_COLOR) - - new_labels.append( - TexMobject("L(\\vec{\\textbf{v}})").set_color(YELLOW) - ) - for label, vect, direction in zip(new_labels, list(bases) + [v], [UP, DOWN, UP]): - label.next_to(vect, direction) - - self.play(*list(map(Write, new_labels))) - self.wait() - scaling_anim_tuples = self.get_scaling_anim_tuples( - new_labels, array, [UP, DOWN] - ) - for i, anim_tuple in enumerate(scaling_anim_tuples): - self.play(*anim_tuple) - movers = VMobject(*self.get_mobjects_from_last_animation()) - self.wait() - self.play(movers.shift, self.i_hat.get_end()[0]*RIGHT) - self.wait() - if self.concrete: - final_label = TexMobject(str(int(v.get_end()[0]))) - final_label.move_to(new_labels[-1]) - final_label.set_color(new_labels[-1].get_color()) - self.play(Transform(new_labels[-1], final_label)) - self.wait() - - - def get_scaling_anim_tuples(self, labels, array, directions): - scaling_anim_tuples = [] - bases = self.i_hat, self.j_hat - quints = list(zip( - bases, self.v_coords, labels, - array.get_entries(), directions - )) - for basis, scalar, label, entry, direction in quints: - basis.save_state() - basis.scaled = basis.copy().scale(scalar) - basis.scaled.shift(basis.get_start() - basis.scaled.get_start()) - scaled_label = VMobject(entry.copy(), label.copy()) - entry.target, label.target = scaled_label.split() - entry.target.next_to(label.target, LEFT) - scaled_label.next_to( - basis.scaled, - direction - ) - - scaling_anim_tuples.append(( - ApplyMethod(label.move_to, label.target), - ApplyMethod(entry.copy().move_to, entry.target), - Transform(basis, basis.scaled), - )) - return scaling_anim_tuples - -class FollowVectorViaCoordinatesConcrete(FollowVectorViaCoordinates): - CONFIG = { - "v_coords" : [4, 3], - "concrete" : True - } - -class TwoDOneDMatrixMultiplication(Scene): - CONFIG = { - "matrix" : [[1, -2]], - "vector" : [4, 3], - "order_left_to_right" : False, - } - def construct(self): - matrix = Matrix(self.matrix) - matrix.label = "Transform" - vector = Matrix(self.vector) - vector.label = "Vector" - matrix.next_to(vector, LEFT, buff = 0.2) - self.color_matrix_and_vector(matrix, vector) - for m, vect in zip([matrix, vector], [UP, DOWN]): - m.brace = Brace(m, vect) - m.label = m.brace.get_text(m.label) - matrix.label.set_color(BLUE) - vector.label.set_color(MAROON_B) - - for m in vector, matrix: - self.play(Write(m)) - self.play( - GrowFromCenter(m.brace), - Write(m.label), - run_time = 1 - ) - self.wait() - self.show_product(matrix, vector) - - def show_product(self, matrix, vector): - starter_pairs = list(zip(vector.get_entries(), matrix.get_entries())) - pairs = [ - VMobject( - e1.copy(), TexMobject("\\cdot"), e2.copy() - ).arrange( - LEFT if self.order_left_to_right else RIGHT, - ) - for e1, e2 in starter_pairs - ] - symbols = list(map(TexMobject, ["=", "+"])) - equation = VMobject(*it.chain(*list(zip(symbols, pairs)))) - equation.arrange(align_using_submobjects = True) - equation.next_to(vector, RIGHT) - - self.play(Write(VMobject(*symbols))) - for starter_pair, pair in zip(starter_pairs, pairs): - self.play(Transform( - VMobject(*starter_pair).copy(), - pair, - path_arc = -np.pi/2 - )) - self.wait() - - def color_matrix_and_vector(self, matrix, vector): - for m in matrix, vector: - x, y = m.get_entries() - x.set_color(X_COLOR) - y.set_color(Y_COLOR) - -class AssociationBetweenMatricesAndVectors(Scene): - CONFIG = { - "matrices" : [ - [[2, 7]], - [[1, -2]] - ] - } - def construct(self): - matrices_words = TextMobject("$1\\times 2$ matrices") - matrices_words.set_color(BLUE) - vectors_words = TextMobject("2d vectors") - vectors_words.set_color(YELLOW) - arrow = DoubleArrow(LEFT, RIGHT, color = WHITE) - VGroup( - matrices_words, arrow, vectors_words - ).arrange(buff = MED_SMALL_BUFF) - - matrices = VGroup(*list(map(Matrix, self.matrices))) - vectors = VGroup(*list(map(Matrix, [m[0] for m in self.matrices]))) - for m in list(matrices) + list(vectors): - x, y = m.get_entries() - x.set_color(X_COLOR) - y.set_color(Y_COLOR) - matrices.words = matrices_words - vectors.words = vectors_words - for group in matrices, vectors: - for m, direction in zip(group, [UP, DOWN]): - m.next_to(group.words, direction, buff = MED_SMALL_BUFF) - - self.play(*list(map(Write, [matrices_words, vectors_words]))) - self.play(ShowCreation(arrow)) - self.wait() - self.play(FadeIn(vectors)) - vectors.save_state() - self.wait() - self.play(Transform( - vectors, matrices, - path_arc = np.pi/2, - lag_ratio = 0.5, - run_time = 2, - )) - self.wait() - self.play( - vectors.restore, - path_arc = -np.pi/2, - lag_ratio = 0.5, - run_time = 2 - ) - self.wait() - -class WhatAboutTheGeometricView(TeacherStudentsScene): - def construct(self): - self.student_says(""" - What does this association - mean geometrically? - """, - target_mode = "raise_right_hand" - ) - self.change_student_modes("pondering", "raise_right_hand", "pondering") - self.random_blink(2) - -class SomeKindOfConnection(Scene): - CONFIG = { - "v_coords" : [2, 3] - } - def construct(self): - width = FRAME_X_RADIUS-1 - plane = NumberPlane(x_radius = 4, y_radius = 6) - squish_plane = plane.copy() - i_hat = Vector([1, 0], color = X_COLOR) - j_hat = Vector([0, 1], color = Y_COLOR) - vect = Vector(self.v_coords, color = YELLOW) - plane.add(vect, i_hat, j_hat) - plane.set_width(FRAME_X_RADIUS) - plane.to_edge(LEFT, buff = 0) - plane.remove(vect, i_hat, j_hat) - - squish_plane.apply_function( - lambda p : np.dot(p, [4, 1, 0])*RIGHT - ) - squish_plane.add(Vector(self.v_coords[1]*RIGHT, color = Y_COLOR)) - squish_plane.add(Vector(self.v_coords[0]*RIGHT, color = X_COLOR)) - squish_plane.scale(width/(FRAME_WIDTH)) - plane.add(j_hat, i_hat) - - number_line = NumberLine().stretch_to_fit_width(width) - number_line.to_edge(RIGHT) - squish_plane.move_to(number_line) - - numbers = number_line.get_numbers(*list(range(-6, 8, 2))) - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - v_line.set_color(GREY) - v_line.set_stroke(width = 10) - - matrix = Matrix([self.v_coords]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.next_to(number_line, UP, buff = LARGE_BUFF) - v_coords = Matrix(self.v_coords) - v_coords.set_column_colors(YELLOW) - v_coords.scale(0.75) - v_coords.next_to(vect.get_end(), RIGHT) - for array in matrix, v_coords: - array.add_to_back(BackgroundRectangle(array)) - - - self.play(*list(map(ShowCreation, [ - plane, number_line, v_line - ]))+[ - Write(numbers, run_time = 2) - ]) - self.play(Write(matrix, run_time = 1)) - mover = plane.copy() - interim = plane.copy().scale(0.8).move_to(number_line) - for target in interim, squish_plane: - self.play( - Transform(mover, target), - Animation(plane), - run_time = 1, - ) - self.wait() - self.play(ShowCreation(vect)) - self.play(Transform(matrix.copy(), v_coords)) - self.wait() - -class AnExampleWillClarify(TeacherStudentsScene): - def construct(self): - self.teacher_says("An example will clarify...") - self.change_student_modes(*["happy"]*3) - self.random_blink(3) - -class ImagineYouDontKnowThis(Scene): - def construct(self): - words = TextMobject("Imagine you don't know this") - words.set_color(RED) - words.scale(1.5) - self.play(Write(words)) - self.wait() - -class ProjectOntoUnitVectorNumberline(VectorScene): - CONFIG = { - "randomize_dots" : True, - "animate_setup" : True, - "zoom_factor" : 1, - "u_hat_color" : YELLOW, - "tilt_angle" : np.pi/6, - } - def setup(self): - plane = self.add_plane() - plane.fade() - - u_hat = Vector([1, 0], color = self.u_hat_color) - u_brace = Brace(u_hat, UP) - u_hat.rotate(self.tilt_angle) - u_hat.label = TexMobject("\\hat{\\textbf{u}}") - u_hat.label.set_color(u_hat.get_color()) - u_hat.label.next_to(u_hat.get_end(), UP+LEFT) - one = TexMobject("1") - u_brace.put_at_tip(one) - u_brace.add(one) - u_brace.rotate(u_hat.get_angle()) - one.rotate_in_place(-u_hat.get_angle()) - - number_line = NumberLine(x_min = -9, x_max = 9) - numbers = number_line.get_numbers() - VGroup(number_line, numbers).rotate(u_hat.get_angle()) - if self.animate_setup: - self.play( - ShowCreation(number_line), - Write(numbers), - run_time = 3 - ) - self.wait() - self.play(ShowCreation(u_hat)) - self.play(FadeIn(u_brace)) - self.play(FadeOut(u_brace)) - self.play(Write(u_hat.label)) - self.wait() - else: - self.add(number_line, numbers, u_hat) - - if self.zoom_factor != 1: - for mob in plane, u_hat: - mob.target = mob.copy().scale(self.zoom_factor) - number_line.target = number_line.copy() - number_line.target.rotate(-u_hat.get_angle()) - number_line.target.stretch(self.zoom_factor, 0) - numbers.target = number_line.target.get_numbers() - number_line.target.rotate(u_hat.get_angle()) - numbers.target.rotate(u_hat.get_angle()) - self.play(*[ - Transform(mob, mob.target) - for mob in self.get_mobjects() - ]) - self.wait() - self.number_line, self.numbers, self.u_hat = number_line, numbers, u_hat - - - def construct(self): - vectors = self.get_vectors(randomize = self.randomize_dots) - dots = self.get_dots(vectors) - proj_dots = self.get_proj_dots(dots) - proj_lines = self.get_proj_lines(dots, proj_dots) - - self.wait() - self.play(FadeIn(vectors, lag_ratio = 0.5)) - self.wait() - self.play(Transform(vectors, dots)) - self.wait() - self.play(ShowCreation(proj_lines)) - self.wait() - self.play( - self.number_line.set_stroke, None, 2, - Transform(vectors, proj_dots), - Transform(proj_lines, proj_dots), - Animation(self.u_hat), - lag_ratio = 0.5, - run_time = 2 - ) - self.wait() - - - def get_vectors(self, num_vectors = 10, randomize = True): - x_max = FRAME_X_RADIUS - 1 - y_max = FRAME_Y_RADIUS - 1 - x_vals = np.linspace(-x_max, x_max, num_vectors) - y_vals = np.linspace(y_max, -y_max, num_vectors) - if randomize: - random.shuffle(y_vals) - vectors = VGroup(*[ - Vector(x*RIGHT + y*UP) - for x, y in zip(x_vals, y_vals) - ]) - vectors.set_color_by_gradient(PINK, MAROON_B) - return vectors - - def get_dots(self, vectors): - return VGroup(*[ - Dot(v.get_end(), color = v.get_color(), radius = 0.075) - for v in vectors - ]) - - def get_proj_dots(self, dots): - return VGroup(*[ - dot.copy().move_to(get_projection( - dot.get_center(), self.u_hat.get_end() - )) - for dot in dots - ]) - - def get_proj_lines(self, dots, proj_dots): - return VGroup(*[ - DashedLine( - d1.get_center(), d2.get_center(), - buff = 0, color = d1.get_color(), - dash_length = 0.15 - ) - for d1, d2 in zip(dots, proj_dots) - ]) - -class ProjectionFunctionSymbol(Scene): - def construct(self): - v_tex = "\\vec{\\textbf{v}}" - equation = TexMobject( - "P(", v_tex, ")=", - "\\text{number }", v_tex, "\\text{ lands on}" - ) - equation.set_color_by_tex(v_tex, YELLOW) - equation.shift(2*UP) - words = TextMobject( - "This projection function is", "linear" - ) - words.set_color_by_tex("linear", BLUE) - arrow = Arrow( - words.get_top(), equation[0].get_bottom(), - color = BLUE - ) - - self.add(VGroup(*equation[:3])) - self.play(Write(VGroup(*equation[3:]))) - self.wait() - self.play(Write(words), ShowCreation(arrow)) - self.wait() - -class ProjectLineOfDots(ProjectOntoUnitVectorNumberline): - CONFIG = { - "randomize_dots" : False, - "animate_setup" : False, - } - -class ProjectSingleVectorOnUHat(ProjectOntoUnitVectorNumberline): - CONFIG = { - "animate_setup" : False - } - def construct(self): - v = Vector([-3, 1], color = PINK) - v.proj = get_vect_mob_projection(v, self.u_hat) - v.proj_line = DashedLine(v.get_end(), v.proj.get_end()) - v.proj_line.set_color(v.get_color()) - v_tex = "\\vec{\\textbf{v}}" - u_tex = self.u_hat.label.get_tex_string() - v.label = TexMobject(v_tex) - v.label.set_color(v.get_color()) - v.label.next_to(v.get_end(), LEFT) - dot_product = TexMobject(v_tex, "\\cdot", u_tex) - dot_product.set_color_by_tex(v_tex, v.get_color()) - dot_product.set_color_by_tex(u_tex, self.u_hat.get_color()) - dot_product.next_to(ORIGIN, UP, buff = MED_SMALL_BUFF) - dot_product.rotate(self.tilt_angle) - dot_product.shift(v.proj.get_end()) - dot_product.add_background_rectangle() - v.label.add_background_rectangle() - - self.play( - ShowCreation(v), - Write(v.label), - ) - self.wait() - self.play( - ShowCreation(v.proj_line), - Transform(v.copy(), v.proj) - ) - self.wait() - self.play( - FadeOut(v), - FadeOut(v.proj_line), - FadeOut(v.label), - Write(dot_product) - ) - self.wait() - -class AskAboutProjectionMatrix(Scene): - def construct(self): - matrix = Matrix([["?", "?"]]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - words = TextMobject("Projection matrix:") - VMobject(words, matrix).arrange(buff = MED_SMALL_BUFF).shift(UP) - basis_words = [ - TextMobject("Where", "$\\hat{\\%smath}$"%char, "lands") - for char in ("i", "j") - ] - for b_words, q_mark, direction in zip(basis_words, matrix.get_entries(), [UP, DOWN]): - b_words.next_to(q_mark, direction, buff = 1.5) - b_words.arrow = Arrow(b_words, q_mark, color = q_mark.get_color()) - b_words.set_color(q_mark.get_color()) - - self.play( - Write(words), - Write(matrix) - ) - self.wait() - for b_words in basis_words: - self.play( - Write(b_words), - ShowCreation(b_words.arrow) - ) - self.wait() - -class ProjectBasisVectors(ProjectOntoUnitVectorNumberline): - CONFIG = { - "animate_setup" : False, - "zoom_factor" : 3, - "u_hat_color" : YELLOW, - "tilt_angle" : np.pi/5, - } - def construct(self): - basis_vectors = self.get_basis_vectors() - i_hat, j_hat = basis_vectors - for vect in basis_vectors: - vect.scale(self.zoom_factor) - dots = self.get_dots(basis_vectors) - proj_dots = self.get_proj_dots(dots) - proj_lines = self.get_proj_lines(dots, proj_dots) - for dot in proj_dots: - dot.scale_in_place(0.1) - proj_basis = VGroup(*[ - get_vect_mob_projection(vector, self.u_hat) - for vector in basis_vectors - ]) - - i_tex, j_tex = ["$\\hat{\\%smath}$"%char for char in ("i", "j")] - question = TextMobject( - "Where do", i_tex, "and", j_tex, "land?" - ) - question.set_color_by_tex(i_tex, X_COLOR) - question.set_color_by_tex(j_tex, Y_COLOR) - question.add_background_rectangle() - matrix = Matrix([["u_x", "u_y"]]) - VGroup(question, matrix).arrange(DOWN).to_corner( - UP+LEFT, buff = MED_SMALL_BUFF/2 - ) - matrix_rect = BackgroundRectangle(matrix) - - - i_label = TexMobject(i_tex[1:-1]) - j_label = TexMobject(j_tex[1:-1]) - u_label = TexMobject("\\hat{\\textbf{u}}") - trips = list(zip( - (i_label, j_label, u_label), - (i_hat, j_hat, self.u_hat), - (DOWN+RIGHT, UP+LEFT, UP), - )) - for label, vect, direction in trips: - label.set_color(vect.get_color()) - label.scale(1.2) - label.next_to(vect.get_end(), direction, buff = MED_SMALL_BUFF/2) - - self.play(Write(u_label, run_time = 1)) - self.play(*list(map(ShowCreation, basis_vectors))) - self.play(*list(map(Write, [i_label, j_label])), run_time = 1) - self.play(ShowCreation(proj_lines)) - self.play( - Write(question), - ShowCreation(matrix_rect), - Write(matrix.get_brackets()), - run_time = 2, - ) - to_remove = [proj_lines] - quads = list(zip(basis_vectors, proj_basis, proj_lines, proj_dots)) - for vect, proj_vect, proj_line, proj_dot in quads: - self.play(Transform(vect.copy(), proj_vect)) - to_remove += self.get_mobjects_from_last_animation() - self.wait() - self.play(*list(map(FadeOut, to_remove))) - - # self.show_u_coords(u_label) - u_x, u_y = [ - TexMobject("u_%s"%c).set_color(self.u_hat.get_color()) - for c in ("x", "y") - ] - matrix_x, matrix_y = matrix.get_entries() - self.remove(j_hat, j_label) - self.show_symmetry(i_hat, u_x, matrix_x) - self.add(j_hat, j_label) - self.remove(i_hat, i_label) - self.show_symmetry(j_hat, u_y, matrix_y) - - # def show_u_coords(self, u_label): - # coords = Matrix(["u_x", "u_y"]) - # x, y = coords.get_entries() - # x.set_color(X_COLOR) - # y.set_color(Y_COLOR) - # coords.add_to_back(BackgroundRectangle(coords)) - # eq = TexMobject("=") - # eq.next_to(u_label, RIGHT) - # coords.next_to(eq, RIGHT) - # self.play(*map(FadeIn, [eq, coords])) - # self.wait() - # self.u_coords = coords - - def show_symmetry(self, vect, coord, coord_landing_spot): - starting_mobjects = list(self.get_mobjects()) - line = DashedLine(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) - words = TextMobject("Line of symmetry") - words.next_to(ORIGIN, UP+LEFT) - words.shift(LEFT) - words.add_background_rectangle() - angle = np.mean([vect.get_angle(), self.u_hat.get_angle()]) - VGroup(line, words).rotate(angle) - - self.play(ShowCreation(line)) - if vect.get_end()[0] > 0.1:#is ihat - self.play(Write(words, run_time = 1)) - - vect.proj = get_vect_mob_projection(vect, self.u_hat) - self.u_hat.proj = get_vect_mob_projection(self.u_hat, vect) - for v in vect, self.u_hat: - v.proj_line = DashedLine( - v.get_end(), v.proj.get_end(), - color = v.get_color() - ) - v.proj_line.fade() - v.tick = Line(0.1*DOWN, 0.1*UP, color = WHITE) - v.tick.rotate(v.proj.get_angle()) - v.tick.shift(v.proj.get_end()) - v.q_mark = TextMobject("?") - v.q_mark.next_to(v.tick, v.proj.get_end()-v.get_end()) - - self.play(ShowCreation(v.proj_line)) - self.play(Transform(v.copy(), v.proj)) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(v.proj) - self.wait() - for v in vect, self.u_hat: - self.play( - ShowCreation(v.tick), - Write(v.q_mark) - ) - self.wait() - for v in self.u_hat, vect: - coord_copy = coord.copy().move_to(v.q_mark) - self.play(Transform(v.q_mark, coord_copy)) - self.wait() - final_coord = coord_copy.copy() - self.play(final_coord.move_to, coord_landing_spot) - - added_mobjects = [ - mob - for mob in self.get_mobjects() - if mob not in starting_mobjects + [final_coord] - ] - self.play(*list(map(FadeOut, added_mobjects))) - -class ShowSingleProjection(ProjectBasisVectors): - CONFIG = { - "zoom_factor" : 1 - } - def construct(self): - vector = Vector([5, - 1], color = MAROON_B) - proj = get_vect_mob_projection(vector, self.u_hat) - proj_line = DashedLine( - vector.get_end(), proj.get_end(), - color = vector.get_color() - ) - coords = Matrix(["x", "y"]) - coords.get_entries().set_color(vector.get_color()) - coords.add_to_back(BackgroundRectangle(coords)) - coords.next_to(vector.get_end(), RIGHT) - - u_label = TexMobject("\\hat{\\textbf{u}}") - u_label.next_to(self.u_hat.get_end(), UP) - u_label.add_background_rectangle() - self.add(u_label) - - self.play(ShowCreation(vector)) - self.play(Write(coords)) - self.wait() - self.play(ShowCreation(proj_line)) - self.play( - Transform(vector.copy(), proj), - Animation(self.u_hat) - ) - self.wait() - -class GeneralTwoDOneDMatrixMultiplication(TwoDOneDMatrixMultiplication): - CONFIG = { - "matrix" : [["u_x", "u_y"]], - "vector" : ["x", "y"], - "order_left_to_right" : True, - } - def construct(self): - TwoDOneDMatrixMultiplication.construct(self) - everything = VGroup(*self.get_mobjects()) - to_fade = [m for m in everything if isinstance(m, Brace) or isinstance(m, TextMobject)] - - u = Matrix(self.matrix[0]) - v = Matrix(self.vector) - self.color_matrix_and_vector(u, v) - dot_product = VGroup(u, TexMobject("\\cdot"), v) - dot_product.arrange() - dot_product.shift(2*RIGHT+DOWN) - words = VGroup( - TextMobject("Matrix-vector product"), - TexMobject("\\Updownarrow"), - TextMobject("Dot product") - ) - words[0].set_color(BLUE) - words[2].set_color(GREEN) - words.arrange(DOWN) - words.to_edge(LEFT) - - - - self.play( - everything.to_corner, UP+RIGHT, - *list(map(FadeOut, to_fade)) - ) - self.remove(everything) - self.add(*everything) - self.remove(*to_fade) - - self.play(Write(words, run_time = 2)) - self.play(ShowCreation(dot_product)) - self.show_product(u, v) - - - - def color_matrix_and_vector(self, matrix, vector): - colors = [X_COLOR, Y_COLOR] - for coord, color in zip(matrix.get_entries(), colors): - coord[0].set_color(YELLOW) - coord[1].set_color(color) - vector.get_entries().set_color(MAROON_B) - -class UHatIsTransformInDisguise(Scene): - def construct(self): - u_tex = "$\\hat{\\textbf{u}}$" - words = TextMobject( - u_tex, - "is secretly a \\\\", - "transform", - "in disguise", - ) - words.set_color_by_tex(u_tex, YELLOW) - words.set_color_by_tex("transform", BLUE) - words.scale(2) - - self.play(Write(words)) - self.wait() - -class AskAboutNonUnitVectors(TeacherStudentsScene): - def construct(self): - self.student_says( - "What about \\\\ non-unit vectors", - target_mode = "raise_left_hand" - ) - self.random_blink(2) - -class ScaleUpUHat(ProjectOntoUnitVectorNumberline) : - CONFIG = { - "animate_setup" : False, - "u_hat_color" : YELLOW, - "tilt_angle" : np.pi/6, - "scalar" : 3, - } - def construct(self): - self.scale_u_hat() - self.show_matrix() - self.transform_basis_vectors() - self.transform_some_vector() - - def scale_u_hat(self): - self.u_hat.coords = Matrix(["u_x", "u_y"]) - new_u = self.u_hat.copy().scale(self.scalar) - new_u.coords = Matrix([ - "%du_x"%self.scalar, - "%du_y"%self.scalar, - ]) - for v in self.u_hat, new_u: - v.coords.get_entries().set_color(YELLOW) - v.coords.add_to_back(BackgroundRectangle(v.coords)) - v.coords.next_to(v.get_end(), UP+LEFT) - - self.play(Write(self.u_hat.coords)) - self.play( - Transform(self.u_hat, new_u), - Transform(self.u_hat.coords, new_u.coords) - ) - self.wait() - - def show_matrix(self): - matrix = Matrix([list(self.u_hat.coords.get_entries().copy())]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.add_to_back(BackgroundRectangle(matrix)) - brace = Brace(matrix) - words = TextMobject( - "\\centering Associated\\\\", - "transformation" - ) - words.set_color_by_tex("transformation", BLUE) - words.add_background_rectangle() - brace.put_at_tip(words) - VGroup(matrix, brace, words).to_corner(UP+LEFT) - - self.play(Transform( - self.u_hat.coords, matrix - )) - self.play( - GrowFromCenter(brace), - Write(words, run_time = 2) - ) - self.wait() - self.matrix_words = words - - def transform_basis_vectors(self): - start_state = list(self.get_mobjects()) - bases = self.get_basis_vectors() - for b, char in zip(bases, ["x", "y"]): - b.proj = get_vect_mob_projection(b, self.u_hat) - b.proj_line = DashedLine( - b.get_end(), b.proj.get_end(), - dash_length = 0.05 - ) - b.proj.label = TexMobject("u_%s"%char) - b.proj.label.set_color(b.get_color()) - b.scaled_proj = b.proj.copy().scale(self.scalar) - b.scaled_proj.label = TexMobject("3u_%s"%char) - b.scaled_proj.label.set_color(b.get_color()) - for v, direction in zip([b.proj, b.scaled_proj], [UP, UP+LEFT]): - v.label.add_background_rectangle() - v.label.next_to(v.get_end(), direction) - - self.play(*list(map(ShowCreation, bases))) - for b in bases: - mover = b.copy() - self.play(ShowCreation(b.proj_line)) - self.play(Transform(mover, b.proj)) - self.play(Write(b.proj.label)) - self.wait() - self.play( - Transform(mover, b.scaled_proj), - Transform(b.proj.label, b.scaled_proj.label) - ) - self.wait() - self.play(*list(map(FadeOut, [ - mob - for mob in self.get_mobjects() - if mob not in start_state - ]))) - - def transform_some_vector(self): - words = TextMobject( - "\\centering Project\\\\", - "then scale" - ) - project, then_scale = words.split() - words.add_background_rectangle() - words.move_to(self.matrix_words, aligned_edge = UP) - - v = Vector([3, -1], color = MAROON_B) - proj = get_vect_mob_projection(v, self.u_hat) - proj_line = DashedLine( - v.get_end(), proj.get_end(), - color = v.get_color() - ) - mover = v.copy() - - self.play(ShowCreation(v)) - self.play(Transform(self.matrix_words, words)) - self.play(ShowCreation(proj_line)) - self.play( - Transform(mover, proj), - project.set_color, YELLOW - ) - self.wait() - self.play( - mover.scale, self.scalar, - then_scale.set_color, YELLOW - ) - self.wait() - -class NoticeWhatHappenedHere(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Notice what - happened here - """) - self.change_student_modes(*["pondering"]*3) - self.random_blink() - -class AbstractNumericAssociation(AssociationBetweenMatricesAndVectors): - CONFIG = { - "matrices" : [ - [["u_x", "u_y"]] - ] - } - -class TwoDOneDTransformationSeparateSpace(Scene): - CONFIG = { - "v_coords" : [4, 1] - } - def construct(self): - width = FRAME_X_RADIUS-1 - plane = NumberPlane(x_radius = 6, y_radius = 7) - squish_plane = plane.copy() - i_hat = Vector([1, 0], color = X_COLOR) - j_hat = Vector([0, 1], color = Y_COLOR) - vect = Vector(self.v_coords, color = YELLOW) - plane.add(vect, i_hat, j_hat) - plane.set_width(FRAME_X_RADIUS) - plane.to_edge(LEFT, buff = 0) - plane.remove(vect, i_hat, j_hat) - - squish_plane.apply_function( - lambda p : np.dot(p, [4, 1, 0])*RIGHT - ) - squish_plane.add(Vector(self.v_coords[0]*RIGHT, color = X_COLOR)) - squish_plane.add(Vector(self.v_coords[1]*RIGHT, color = Y_COLOR)) - squish_plane.scale(width/(FRAME_WIDTH)) - plane.add(i_hat, j_hat) - - number_line = NumberLine().stretch_to_fit_width(width) - number_line.to_edge(RIGHT) - squish_plane.move_to(number_line) - - numbers = number_line.get_numbers(*list(range(-6, 8, 2))) - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - v_line.set_color(GREY) - v_line.set_stroke(width = 10) - - matrix = Matrix([self.v_coords]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.next_to(number_line, UP, buff = LARGE_BUFF) - v_coords = Matrix(self.v_coords) - v_coords.set_column_colors(YELLOW) - v_coords.scale(0.75) - v_coords.next_to(vect.get_end(), RIGHT) - for array in matrix, v_coords: - array.add_to_back(BackgroundRectangle(array)) - - start_words = TextMobject( - "\\centering Any time you have a \\\\", - "2d-to-1d linear transform..." - ) - end_words = TextMobject( - "\\centering ...it's associated \\\\", - "with some vector", - ) - for words in start_words, end_words: - words.add_background_rectangle() - words.scale(0.8) - start_words.next_to(ORIGIN, RIGHT, buff = MED_SMALL_BUFF).to_edge(UP) - end_words.next_to(ORIGIN, DOWN+LEFT, buff = MED_SMALL_BUFF/2) - - self.play(*list(map(ShowCreation, [ - plane, number_line, v_line - ]))+[ - Write(numbers, run_time = 2) - ]) - self.play(Write(start_words, run_time = 2)) - self.play(Write(matrix, run_time = 1)) - mover = plane.copy() - interim = plane.copy().scale(0.8).move_to(number_line) - for target in interim, squish_plane: - self.play( - Transform(mover, target), - Animation(plane), - Animation(start_words), - run_time = 1, - ) - self.wait() - self.play(Transform(start_words.copy(), end_words)) - self.play(ShowCreation(vect)) - self.play(Transform(matrix.copy(), v_coords)) - self.wait() - -class IsntThisBeautiful(TeacherStudentsScene): - def construct(self): - self.teacher.look(DOWN+LEFT) - self.teacher_says( - "Isn't this", "beautiful", - target_mode = "surprised" - ) - for student in self.get_students(): - self.play(student.change_mode, "happy") - self.random_blink() - duality_words = TextMobject( - "It's called", "duality" - ) - duality_words[1].set_color_by_gradient(BLUE, YELLOW) - self.teacher_says(duality_words) - self.random_blink() - -class RememberGraphDuality(Scene): - def construct(self): - words = TextMobject(""" - \\centering - Some of you may remember an - early video I did on graph duality - """) - words.to_edge(UP) - self.play(Write(words)) - self.wait() - -class LooseDualityDescription(Scene): - def construct(self): - duality = TextMobject("Duality") - duality.set_color_by_gradient(BLUE, YELLOW) - arrow = TexMobject("\\Leftrightarrow") - words = TextMobject("Natural-but-surprising", "correspondence") - words[1].set_color_by_gradient(BLUE, YELLOW) - VGroup(duality, arrow, words).arrange(buff = MED_SMALL_BUFF) - - self.add(duality) - self.play(Write(arrow)) - self.play(Write(words)) - self.wait() - -class DualOfAVector(ScaleUpUHat): - pass #Exact copy - -class DualOfATransform(TwoDOneDTransformationSeparateSpace): - pass #Exact copy - -class UnderstandingProjection(ProjectOntoUnitVectorNumberline): - pass ##Copy - -class ShowQualitativeDotProductValuesCopy(ShowQualitativeDotProductValues): - pass - -class TranslateToTheWorldOfTransformations(TwoDOneDMatrixMultiplication): - CONFIG = { - "order_left_to_right" : True, - } - def construct(self): - v1, v2 = [ - Matrix(["x_%d"%n, "y_%d"%n]) - for n in (1, 2) - ] - v1.set_column_colors(V_COLOR) - v2.set_column_colors(W_COLOR) - dot = TexMobject("\\cdot") - - matrix = Matrix([["x_1", "y_1"]]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - - dot_product = VGroup(v1, dot, v2) - dot_product.arrange(RIGHT) - matrix.next_to(v2, LEFT) - - brace = Brace(matrix, UP) - word = TextMobject("Transform") - word.set_width(brace.get_width()) - brace.put_at_tip(word) - word.set_color(BLUE) - - self.play(Write(dot_product)) - self.wait() - self.play( - dot.set_fill, BLACK, 0, - Transform(v1, matrix), - ) - self.play( - GrowFromCenter(brace), - Write(word) - ) - self.wait() - self.show_product(v1, v2) - self.wait() - -class NumericalAssociationSilliness(GeneralTwoDOneDMatrixMultiplication): - pass #copy - -class YouMustKnowPersonality(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - You should learn a - vector's personality - """) - self.random_blink() - self.change_student_modes("pondering") - self.random_blink() - self.change_student_modes("pondering", "plain", "pondering") - self.random_blink() - -class WhatTheVectorWantsToBe(Scene): - CONFIG = { - "v_coords" : [2, 4] - } - def construct(self): - width = FRAME_X_RADIUS-1 - plane = NumberPlane(x_radius = 6, y_radius = 7) - squish_plane = plane.copy() - i_hat = Vector([1, 0], color = X_COLOR) - j_hat = Vector([0, 1], color = Y_COLOR) - vect = Vector(self.v_coords, color = YELLOW) - plane.add(vect, i_hat, j_hat) - plane.set_width(FRAME_X_RADIUS) - plane.to_edge(LEFT, buff = 0) - plane.remove(vect, i_hat, j_hat) - - squish_plane.apply_function( - lambda p : np.dot(p, [4, 1, 0])*RIGHT - ) - squish_plane.add(Vector(self.v_coords[1]*RIGHT, color = Y_COLOR)) - squish_plane.add(Vector(self.v_coords[0]*RIGHT, color = X_COLOR)) - squish_plane.scale(width/(FRAME_WIDTH)) - plane.add(j_hat, i_hat) - - number_line = NumberLine().stretch_to_fit_width(width) - number_line.to_edge(RIGHT) - squish_plane.move_to(number_line) - - numbers = number_line.get_numbers(*list(range(-6, 8, 2))) - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - v_line.set_color(GREY) - v_line.set_stroke(width = 10) - - matrix = Matrix([self.v_coords]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.next_to(number_line, UP, buff = LARGE_BUFF) - v_coords = Matrix(self.v_coords) - v_coords.set_column_colors(YELLOW) - v_coords.scale(0.75) - v_coords.next_to(vect.get_end(), RIGHT) - for array in matrix, v_coords: - array.add_to_back(BackgroundRectangle(array)) - - words = TextMobject( - "What the vector", - "\\\\ wants", - "to be" - ) - words[1].set_color(BLUE) - words.next_to(matrix, UP, buff = MED_SMALL_BUFF) - - self.add(plane, v_line, number_line, numbers) - self.play(ShowCreation(vect)) - self.play(Write(v_coords)) - self.wait() - self.play( - Transform(v_coords.copy(), matrix), - Write(words) - ) - self.play( - Transform(plane.copy(), squish_plane, run_time = 3), - *list(map(Animation, [ - words, - matrix, - plane, - vect, - v_coords - ])) - ) - self.wait() - -class NextVideo(Scene): - def construct(self): - title = TextMobject(""" - Next video: Cross products in the - light of linear transformations - """) - title.set_height(1.2) - title.to_edge(UP, buff = MED_SMALL_BUFF/2) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - VGroup(title, rect).show() - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/chapter8.py b/from_3b1b/old/eola/chapter8.py deleted file mode 100644 index 44aaef4a..00000000 --- a/from_3b1b/old/eola/chapter8.py +++ /dev/null @@ -1,1706 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eola.chapter5 import get_det_text, RightHandRule - - -U_COLOR = ORANGE -V_COLOR = YELLOW -W_COLOR = MAROON_B -P_COLOR = RED - -def get_vect_tex(*strings): - result = ["\\vec{\\textbf{%s}}"%s for s in strings] - if len(result) == 1: - return result[0] - else: - return result - -def get_perm_sign(*permutation): - identity = np.identity(len(permutation)) - return np.linalg.det(identity[list(permutation)]) - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject("``Every dimension is special.''") - words.to_edge(UP) - author = TextMobject("-Jeff Lagarias") - author.set_color(YELLOW) - author.next_to(words, DOWN, buff = 0.5) - - self.play(FadeIn(words)) - self.wait(1) - self.play(Write(author, run_time = 3)) - self.wait() - -class LastVideo(Scene): - def construct(self): - title = TextMobject(""" - Last video: Dot products and duality - """) - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - -class DoTheSameForCross(TeacherStudentsScene): - def construct(self): - words = TextMobject("Let's do the same \\\\ for", "cross products") - words.set_color_by_tex("cross products", YELLOW) - self.teacher_says(words, target_mode = "surprised") - self.random_blink(2) - self.change_student_modes("pondering") - self.random_blink() - -class ListSteps(Scene): - CONFIG = { - "randy_corner" : DOWN+RIGHT - } - def construct(self): - title = TextMobject("Two part chapter") - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - randy = Randolph().flip().to_corner(DOWN+RIGHT) - randy.look(UP+LEFT) - - step_1 = TextMobject("This video: Standard introduction") - step_2 = TextMobject("Next video: Deeper understanding with ", "linear transformations") - step_2.set_color_by_tex("linear transformations", BLUE) - steps = VGroup(step_1, step_2) - steps.arrange(DOWN, aligned_edge = LEFT, buff = LARGE_BUFF) - steps.next_to(randy, UP) - steps.to_edge(LEFT, buff = LARGE_BUFF) - - self.add(title) - self.play(ShowCreation(h_line)) - for step in steps: - self.play(Write(step)) - self.wait() - for step in steps: - target = step.copy() - target.scale_in_place(1.1) - target.set_color(YELLOW) - target.set_color_by_tex("linear transformations", BLUE) - step.target = target - step.save_state() - self.play(FadeIn(randy)) - self.play(Blink(randy)) - self.play( - MoveToTarget(step_1), - step_2.fade, - randy.change_mode, "happy" - ) - self.play(Blink(randy)) - self.play( - Transform(step_1, step_1.copy().restore().fade()), - MoveToTarget(step_2), - randy.look, LEFT - ) - self.play(randy.change_mode, "erm") - self.wait(2) - self.play(randy.change_mode, "pondering") - self.play(Blink(randy)) - -class SimpleDefine2dCrossProduct(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "v_coords" : [3, 2], - "w_coords" : [2, -1], - } - def construct(self): - self.add_vectors() - self.show_area() - self.write_area_words() - self.show_sign() - self.swap_v_and_w() - - def add_vectors(self): - self.plane.fade() - v = self.add_vector(self.v_coords, color = V_COLOR) - w = self.add_vector(self.w_coords, color = W_COLOR) - for vect, name, direction in (v, "v", "left"), (w, "w", "right"): - color = vect.get_color() - vect.label = self.label_vector( - vect, name, color = color, direction = direction, - ) - self.v, self.w = v, w - - def show_area(self): - self.add_unit_square() - transform = self.get_matrix_transformation(np.array([ - self.v_coords, - self.w_coords, - ])) - self.square.apply_function(transform) - self.play( - ShowCreation(self.square), - *list(map(Animation, [self.v, self.w])) - ) - self.wait() - self.play(FadeOut(self.square)) - v_copy = self.v.copy() - w_copy = self.w.copy() - self.play(v_copy.shift, self.w.get_end()) - self.play(w_copy.shift, self.v.get_end()) - self.wait() - self.play( - FadeIn(self.square), - *list(map(Animation, [self.v, self.w, v_copy, w_copy])) - ) - self.wait() - self.play(*list(map(FadeOut, [v_copy, w_copy]))) - - def write_area_words(self): - times = TexMobject("\\times") - for vect in self.v, self.w: - vect.label.target = vect.label.copy() - vect.label.target.save_state() - cross = VGroup(self.v.label.target, times, self.w.label.target) - cross.arrange(aligned_edge = DOWN) - cross.scale(1.5) - cross.shift(2.5*UP).to_edge(LEFT) - cross_rect = BackgroundRectangle(cross) - equals = TexMobject("=") - equals.add_background_rectangle() - equals.next_to(cross, buff = MED_SMALL_BUFF/2) - words = TextMobject("Area of parallelogram") - words.add_background_rectangle() - words.next_to(equals, buff = MED_SMALL_BUFF/2) - arrow = Arrow( - words.get_bottom(), - self.square.get_center(), - color = WHITE - ) - - self.play( - FadeIn(cross_rect), - Write(times), - *[ - ApplyMethod( - vect.label.target.restore, - rate_func = lambda t : smooth(1-t) - ) - for vect in (self.v, self.w) - ] - ) - self.wait() - self.play(ApplyFunction( - lambda m : m.scale_in_place(1.2).set_color(RED), - times, - rate_func = there_and_back - )) - self.wait() - self.play(Write(words), Write(equals)) - self.play(ShowCreation(arrow)) - self.wait() - self.play(FadeOut(arrow)) - - self.area_words = words - self.cross = cross - - def show_sign(self): - for vect, angle in (self.v, -np.pi/2), (self.w, np.pi/2): - vect.add(vect.label) - vect.save_state() - vect.target = vect.copy() - vect.target.rotate(angle) - vect.target.label.rotate_in_place(-angle) - vect.target.label.background_rectangle.set_fill(opacity = 0) - square = self.square - square.save_state() - self.add_unit_square(animate = False, opacity = 0.15) - transform = self.get_matrix_transformation([ - self.v.target.get_end()[:2], - self.w.target.get_end()[:2], - ]) - self.square.apply_function(transform) - self.remove(self.square) - square.target = self.square - self.square = square - - positive = TextMobject("Positive").set_color(GREEN) - negative = TextMobject("Negative").set_color(RED) - for word in positive, negative: - word.add_background_rectangle() - word.arrow = Arrow( - word.get_top(), word.get_top() + 1.5*UP, - color = word.get_color() - ) - VGroup(word, word.arrow).next_to( - self.area_words, DOWN, - aligned_edge = LEFT, - buff = SMALL_BUFF - ) - minus_sign = TexMobject("-") - minus_sign.set_color(RED) - minus_sign.move_to(self.area_words, aligned_edge = LEFT) - self.area_words.target = self.area_words.copy() - self.area_words.target.next_to(minus_sign, RIGHT) - - self.play(*list(map(MoveToTarget, [square, self.v, self.w]))) - arc = self.get_arc(self.v, self.w, radius = 1.5) - arc.set_color(GREEN) - self.play(ShowCreation(arc)) - self.wait() - self.play(Write(positive), ShowCreation(positive.arrow)) - self.remove(arc) - self.play( - FadeOut(positive), - FadeOut(positive.arrow), - *[mob.restore for mob in (square, self.v, self.w)] - ) - arc = self.get_arc(self.v, self.w, radius = 1.5) - arc.set_color(RED) - self.play(ShowCreation(arc)) - self.play( - Write(negative), - ShowCreation(negative.arrow), - Write(minus_sign), - MoveToTarget(self.area_words) - ) - self.wait() - self.play(*list(map(FadeOut, [negative, negative.arrow, arc]))) - - def swap_v_and_w(self): - new_cross = self.cross.copy() - new_cross.arrange(LEFT, aligned_edge = DOWN) - new_cross.move_to(self.area_words, aligned_edge = LEFT) - for vect in self.v, self.w: - vect.remove(vect.label) - - self.play( - FadeOut(self.area_words), - Transform(self.cross.copy(), new_cross, path_arc = np.pi/2) - ) - self.wait() - - curr_matrix = np.array([self.v.get_end()[:2], self.w.get_end()[:2]]) - new_matrix = np.array(list(reversed(curr_matrix))) - transform = self.get_matrix_transformation( - np.dot(new_matrix.T, np.linalg.inv(curr_matrix.T)).T - ) - self.square.target = self.square.copy().apply_function(transform) - self.play( - MoveToTarget(self.square), - Transform(self.v, self.w), - Transform(self.w, self.v), - rate_func = there_and_back, - run_time = 3 - ) - self.wait() - - - def get_arc(self, v, w, radius = 2): - v_angle, w_angle = v.get_angle(), w.get_angle() - nudge = 0.05 - arc = Arc( - (1-2*nudge)*(w_angle - v_angle), - start_angle = interpolate(v_angle, w_angle, nudge), - radius = radius, - stroke_width = 8, - ) - arc.add_tip() - return arc - -class CrossBasisVectors(LinearTransformationScene): - def construct(self): - self.plane.fade() - i_label = self.get_vector_label( - self.i_hat, "\\hat{\\imath}", - direction = "right", - color = X_COLOR, - ) - j_label = self.get_vector_label( - self.j_hat, "\\hat{\\jmath}", - direction = "left", - color = Y_COLOR, - ) - for label in i_label, j_label: - self.play(Write(label)) - label.target = label.copy() - i_label.target.scale(1.5) - j_label.target.scale(1.2) - - self.wait() - - times = TexMobject("\\times") - cross = VGroup(i_label.target, times, j_label.target) - cross.arrange() - cross.next_to(ORIGIN).shift(1.5*UP) - cross_rect = BackgroundRectangle(cross) - eq = TexMobject("= + 1") - eq.add_background_rectangle() - eq.next_to(cross, RIGHT) - - self.play( - ShowCreation(cross_rect), - MoveToTarget(i_label.copy()), - MoveToTarget(j_label.copy()), - Write(times), - ) - self.play(Write(eq)) - self.wait() - arc = self.get_arc(self.i_hat, self.j_hat, radius = 1) - # arc.set_color(GREEN) - self.play(ShowCreation(arc)) - self.wait() - - - def get_arc(self, v, w, radius = 2): - v_angle, w_angle = v.get_angle(), w.get_angle() - nudge = 0.05 - arc = Arc( - (1-2*nudge)*(w_angle - v_angle), - start_angle = interpolate(v_angle, w_angle, nudge), - radius = radius, - stroke_width = 8, - ) - arc.add_tip() - return arc - -class VisualExample(SimpleDefine2dCrossProduct): - CONFIG = { - "show_basis_vectors" : False, - "v_coords" : [3, 1], - "w_coords" : [1, -2], - } - def construct(self): - self.add_vectors() - # self.show_coords() - self.show_area() - self.write_area_words() - - result = np.linalg.det([self.v_coords, self.w_coords]) - val = TexMobject(str(int(abs(result)))).scale(2) - val.move_to(self.square.get_center()) - arc = self.get_arc(self.v, self.w, radius = 1) - arc.set_color(RED) - minus = TexMobject("-").set_color(RED) - minus.scale(1.5) - minus.move_to(self.area_words, aligned_edge = LEFT) - - self.play(ShowCreation(val)) - self.wait() - self.play(ShowCreation(arc)) - self.wait() - self.play(FadeOut(self.area_words)) - self.play( - Transform(arc, minus), - val.next_to, minus, RIGHT - ) - self.wait() - - def show_coords(self): - for vect, edge in (self.v, DOWN), (self.w, UP): - color = vect.get_color() - vect.coord_array = vector_coordinate_label( - vect, color = color, - ) - vect.coord_array.move_to( - vect.coord_array.get_center(), - aligned_edge = edge - ) - self.play(Write(vect.coord_array, run_time = 1)) - -class HowDoYouCompute(TeacherStudentsScene): - def construct(self): - self.student_says( - "How do you \\\\ compute this?", - target_mode = "raise_left_hand" - ) - self.random_blink(2) - -class ContrastDotAndCross(Scene): - def construct(self): - self.add_t_chart() - self.add_dot_products() - self.add_cross_product() - self.add_2d_cross_product() - self.emphasize_output_type() - - def add_t_chart(self): - for word, vect, color in ("Dot", LEFT, BLUE_C), ("Cross", RIGHT, YELLOW): - title = TextMobject("%s product"%word) - title.shift(vect*FRAME_X_RADIUS/2) - title.to_edge(UP) - title.set_color(color) - self.add(title) - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - l_h_line = Line(LEFT, ORIGIN).scale(FRAME_X_RADIUS) - r_h_line = Line(ORIGIN, RIGHT).scale(FRAME_X_RADIUS) - r_h_line.next_to(title, DOWN) - l_h_line.next_to(r_h_line, LEFT, buff = 0) - self.add(v_line, l_h_line, r_h_line) - self.l_h_line, self.r_h_line = l_h_line, r_h_line - - def add_dot_products(self, max_width = FRAME_X_RADIUS-1, dims = [2, 5]): - colors = [X_COLOR, Y_COLOR, Z_COLOR, MAROON_B, TEAL] - last_mob = self.l_h_line - dot_products = [] - for dim in dims: - arrays = [ - [random.randint(0, 9) for in_count in range(dim)] - for out_count in range(2) - ] - m1, m2 = list(map(Matrix, arrays)) - for matrix in m1, m2: - for entry, color in zip(matrix.get_entries(), colors): - entry.set_color(color) - entry.target = entry.copy() - syms = VGroup(*list(map(TexMobject, ["="] + ["+"]*(dim-1)))) - def get_dot(): - dot = TexMobject("\\cdot") - syms.add(dot) - return dot - result = VGroup(*it.chain(*list(zip( - syms, - [ - VGroup( - e1.target, get_dot(), e2.target - ).arrange() - for e1, e2 in zip(m1.get_entries(), m2.get_entries()) - ] - )))) - result.arrange(RIGHT) - dot_prod = VGroup( - m1, TexMobject("\\cdot"), m2, result - ) - dot_prod.arrange(RIGHT) - if dot_prod.get_width() > max_width: - dot_prod.set_width(max_width) - dot_prod.next_to(last_mob, DOWN, buff = MED_SMALL_BUFF) - last_mob = dot_prod - dot_prod.to_edge(LEFT) - dot_prod.remove(result) - dot_prod.syms = syms - dot_prod.entries = list(m1.get_entries())+list(m2.get_entries()) - dot_products.append(dot_prod) - self.add(*dot_products) - for dot_prod in dot_products: - self.play( - Write(dot_prod.syms), - *[ - Transform( - e.copy(), e.target, - path_arc = -np.pi/6 - ) - for e in dot_prod.entries - ], - run_time = 2 - ) - self.wait() - - def add_cross_product(self): - colors = [X_COLOR, Y_COLOR, Z_COLOR] - - arrays = [ - [random.randint(0, 9) for in_count in range(3)] - for out_count in range(2) - ] - matrices = list(map(Matrix, arrays)) - for matrix in matrices: - for entry, color in zip(matrix.get_entries(), colors): - entry.set_color(color) - m1, m2 = matrices - cross_product = VGroup(m1, TexMobject("\\times"), m2) - cross_product.arrange() - - index_to_cross_enty = {} - syms = VGroup() - movement_sets = [] - for a, b, c in it.permutations(list(range(3))): - e1, e2 = m1.get_entries()[b], m2.get_entries()[c] - for e in e1, e2: - e.target = e.copy() - movement_sets.append([e1, e1.target, e2, e2.target]) - dot = TexMobject("\\cdot") - syms.add(dot) - cross_entry = VGroup(e1.target, dot, e2.target) - cross_entry.arrange() - if a not in index_to_cross_enty: - index_to_cross_enty[a] = [] - index_to_cross_enty[a].append(cross_entry) - result_entries = [] - for a in range(3): - prod1, prod2 = index_to_cross_enty[a] - if a == 1: - prod1, prod2 = prod2, prod1 - prod2.arrange(LEFT) - minus = TexMobject("-") - syms.add(minus) - entry = VGroup(prod1, minus, prod2) - entry.arrange(RIGHT) - result_entries.append(entry) - - result = Matrix(result_entries) - full_cross_product = VGroup( - cross_product, TexMobject("="), result - ) - full_cross_product.arrange() - full_cross_product.scale(0.75) - full_cross_product.next_to(self.r_h_line, DOWN, buff = MED_SMALL_BUFF/2) - full_cross_product.remove(result) - self.play( - Write(full_cross_product), - ) - movements = [] - for e1, e1_target, e2, e2_target in movement_sets: - movements += [ - e1.copy().move_to, e1_target, - e2.copy().move_to, e2_target, - ] - - brace = Brace(cross_product) - brace_text = brace.get_text("Only 3d") - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - - self.play( - Write(result.get_brackets()), - Write(syms), - *movements, - run_time = 2 - ) - self.wait() - - self.cross_result = result - self.only_3d_text = brace_text - - def add_2d_cross_product(self): - h_line = DashedLine(ORIGIN, FRAME_X_RADIUS*RIGHT) - h_line.next_to(self.only_3d_text, DOWN, buff = MED_SMALL_BUFF/2) - h_line.to_edge(RIGHT, buff = 0) - arrays = np.random.randint(0, 9, (2, 2)) - m1, m2 = matrices = list(map(Matrix, arrays)) - for m in matrices: - for e, color in zip(m.get_entries(), [X_COLOR, Y_COLOR]): - e.set_color(color) - cross_product = VGroup(m1, TexMobject("\\times"), m2) - cross_product.arrange() - (x1, x2), (x3, x4) = tuple(m1.get_entries()), tuple(m2.get_entries()) - entries = [x1, x2, x3, x4] - for entry in entries: - entry.target = entry.copy() - eq, dot1, minus, dot2 = syms = list(map(TexMobject, - ["=", "\\cdot", "-", "\\cdot"] - )) - result = VGroup( - eq, x1.target, dot1, x4.target, - minus, x3.target, dot2, x2.target, - ) - result.arrange(RIGHT) - full_cross_product = VGroup(cross_product, result) - full_cross_product.arrange(RIGHT) - full_cross_product.next_to(h_line, DOWN, buff = MED_SMALL_BUFF/2) - - self.play(ShowCreation(h_line)) - self.play(Write(cross_product)) - self.play( - Write(VGroup(*syms)), - *[ - Transform(entry.copy(), entry.target) - for entry in entries - ] - ) - self.wait() - self.two_d_result = VGroup(*result[1:]) - - def emphasize_output_type(self): - three_d_brace = Brace(self.cross_result) - two_d_brace = Brace(self.two_d_result) - vector = three_d_brace.get_text("Vector") - number = two_d_brace.get_text("Number") - - self.play( - GrowFromCenter(two_d_brace), - Write(number) - ) - self.wait() - self.play( - GrowFromCenter(three_d_brace), - Write(vector) - ) - self.wait() - -class PrereqDeterminant(Scene): - def construct(self): - title = TextMobject(""" - Prerequisite: Understanding determinants - """) - title.set_width(FRAME_WIDTH - 2) - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - -class Define2dCrossProduct(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "v_coords" : [3, 1], - "w_coords" : [2, -1], - } - def construct(self): - self.initial_definition() - self.show_transformation() - self.transform_square() - self.show_orientation_rule() - - - def initial_definition(self): - self.plane.save_state() - self.plane.fade() - v = self.add_vector(self.v_coords, color = V_COLOR) - w = self.add_vector(self.w_coords, color = W_COLOR) - self.moving_vectors.remove(v) - self.moving_vectors.remove(w) - for vect, name, direction in (v, "v", "left"), (w, "w", "right"): - color = vect.get_color() - vect.label = self.label_vector( - vect, name, color = color, direction = direction, - ) - vect.coord_array = vector_coordinate_label( - vect, color = color, - ) - vect.coords = vect.coord_array.get_entries() - for vect, edge in (v, DOWN), (w, UP): - vect.coord_array.move_to( - vect.coord_array.get_center(), - aligned_edge = edge - ) - self.play(Write(vect.coord_array, run_time = 1)) - movers = [v.label, w.label, v.coords, w.coords] - for mover in movers: - mover.target = mover.copy() - times = TexMobject("\\times") - cross_product = VGroup( - v.label.target, times, w.label.target - ) - - cross_product.arrange() - matrix = Matrix(np.array([ - list(v.coords.target), - list(w.coords.target) - ]).T) - det_text = get_det_text(matrix) - full_det = VGroup(det_text, matrix) - equals = TexMobject("=") - equation = VGroup(cross_product, equals, full_det) - equation.arrange() - equation.to_corner(UP+LEFT) - - matrix_background = BackgroundRectangle(matrix) - cross_background = BackgroundRectangle(cross_product) - - disclaimer = TextMobject("$^*$ See ``Note on conventions'' in description") - disclaimer.scale(0.7) - disclaimer.set_color(RED) - disclaimer.next_to( - det_text.get_corner(UP+RIGHT), RIGHT, buff = 0 - ) - disclaimer.add_background_rectangle() - - self.play( - FadeIn(cross_background), - Transform(v.label.copy(), v.label.target), - Transform(w.label.copy(), w.label.target), - Write(times), - ) - self.wait() - self.play( - ShowCreation(matrix_background), - Write(matrix.get_brackets()), - run_time = 1 - ) - self.play(Transform(v.coords.copy(), v.coords.target)) - self.play(Transform(w.coords.copy(), w.coords.target)) - matrix.add_to_back(matrix_background) - self.wait() - self.play( - Write(equals), - Write(det_text), - Animation(matrix), - ) - self.wait() - self.play(FadeIn(disclaimer)) - self.wait() - self.play(FadeOut(disclaimer)) - self.wait() - - cross_product.add_to_back(cross_background) - cross_product.add(equals) - self.cross_product = cross_product - self.matrix = matrix - self.det_text = det_text - self.v, self.w = v, w - - def show_transformation(self): - matrix = self.matrix.copy() - everything = self.get_mobjects() - everything.remove(self.plane) - everything.remove(self.background_plane) - self.play( - *list(map(FadeOut, everything)) + [ - Animation(self.background_plane), - self.plane.restore, - Animation(matrix), - ]) - i_hat, j_hat = self.get_basis_vectors() - for vect in i_hat, j_hat: - vect.save_state() - basis_labels = self.get_basis_vector_labels() - self.play( - ShowCreation(i_hat), - ShowCreation(j_hat), - Write(basis_labels) - ) - self.wait() - - side_brace = Brace(matrix, RIGHT) - transform_words = side_brace.get_text("Linear transformation") - transform_words.add_background_rectangle() - - col1, col2 = [ - VGroup(*matrix.get_mob_matrix()[i,:]) - for i in (0, 1) - ] - - both_words = [] - for char, color, col in ("i", X_COLOR, col1), ("j", Y_COLOR, col2): - words = TextMobject("Where $\\hat\\%smath$ lands"%char) - words.set_color(color) - words.add_background_rectangle() - words.next_to(col, DOWN, buff = LARGE_BUFF) - words.arrow = Arrow(words.get_top(), col.get_bottom(), color = color) - both_words.append(words) - i_words, j_words = both_words - - self.play( - GrowFromCenter(side_brace), - Write(transform_words) - ) - self.play( - Write(i_words), - ShowCreation(i_words.arrow), - col1.set_color, X_COLOR - ) - self.wait() - self.play( - Transform(i_words, j_words), - Transform(i_words.arrow, j_words.arrow), - col2.set_color, Y_COLOR - ) - self.wait() - self.play(*list(map(FadeOut, [i_words, i_words.arrow, basis_labels]))) - - self.add_vector(i_hat, animate = False) - self.add_vector(j_hat, animate = False) - self.play(*list(map(FadeOut, [side_brace, transform_words]))) - self.add_foreground_mobject(matrix) - self.apply_transposed_matrix([self.v_coords, self.w_coords]) - self.wait() - self.play( - FadeOut(self.plane), - *list(map(Animation, [ - self.background_plane, - matrix, - i_hat, - j_hat, - ])) - ) - self.play( - ShowCreation(self.v), - ShowCreation(self.w), - FadeIn(self.v.label), - FadeIn(self.w.label), - FadeIn(self.v.coord_array), - FadeIn(self.w.coord_array), - matrix.set_column_colors, V_COLOR, W_COLOR - ) - self.wait() - self.i_hat, self.j_hat = i_hat, j_hat - self.matrix = matrix - - - def transform_square(self): - self.play(Write(self.det_text)) - self.matrix.add(self.det_text) - - vect_stuffs = VGroup(*it.chain(*[ - [m, m.label, m.coord_array] - for m in (self.v, self.w) - ])) - to_restore = [self.plane, self.i_hat, self.j_hat] - for mob in to_restore: - mob.fade(1) - - self.play(*list(map(FadeOut, vect_stuffs))) - self.play( - *[m.restore for m in to_restore] + [ - Animation(self.matrix) - ] - ) - self.add_unit_square(animate = True, opacity = 0.2) - self.square.save_state() - self.wait() - self.apply_transposed_matrix( - [self.v_coords, self.w_coords] - ) - self.wait() - self.play( - FadeOut(self.plane), - Animation(self.matrix), - *list(map(FadeIn, vect_stuffs)) - ) - self.play(Write(self.cross_product)) - - det_text_brace = Brace(self.det_text) - area_words = det_text_brace.get_text("Area of this parallelogram") - area_words.add_background_rectangle() - area_arrow = Arrow( - area_words.get_bottom(), - self.square.get_center(), - color = WHITE - ) - self.play( - GrowFromCenter(det_text_brace), - Write(area_words), - ShowCreation(area_arrow) - ) - self.wait() - - pm = VGroup(*list(map(TexMobject, ["+", "-"]))) - pm.set_color_by_gradient(GREEN, RED) - pm.arrange(DOWN, buff = SMALL_BUFF) - pm.add_to_back(BackgroundRectangle(pm)) - pm.next_to(area_words[0], LEFT, aligned_edge = DOWN) - self.play( - Transform(self.square.get_point_mobject(), pm), - path_arc = -np.pi/2 - ) - self.wait() - self.play(*list(map(FadeOut, [ - area_arrow, self.v.coord_array, self.w.coord_array - ]))) - - def show_orientation_rule(self): - self.remove(self.i_hat, self.j_hat) - for vect in self.v, self.w: - vect.add(vect.label) - vect.target = vect.copy() - angle = np.pi/3 - self.v.target.rotate(-angle) - self.w.target.rotate(angle) - self.v.target.label.rotate_in_place(angle) - self.w.target.label.rotate_in_place(-angle) - for vect in self.v, self.w: - vect.target.label[0].set_fill(opacity = 0) - self.square.target = self.square.copy().restore() - transform = self.get_matrix_transformation([ - self.v.target.get_end()[:2], - self.w.target.get_end()[:2], - ]) - self.square.target.apply_function(transform) - - movers = VGroup(self.square, self.v, self.w) - movers.target = VGroup(*[m.target for m in movers]) - movers.save_state() - self.remove(self.square) - self.play(Transform(movers, movers.target)) - self.wait() - - v_tex, w_tex = ["\\vec{\\textbf{%s}}"%s for s in ("v", "w")] - positive_words, negative_words = words_list = [ - TexMobject(v_tex, "\\times", w_tex, "\\text{ is }", word) - for word in ("\\text{positive}", "\\text{negative}") - ] - for words in words_list: - words.set_color_by_tex(v_tex, V_COLOR) - words.set_color_by_tex(w_tex, W_COLOR) - words.set_color_by_tex("\\text{positive}", GREEN) - words.set_color_by_tex("\\text{negative}", RED) - words.add_background_rectangle() - words.next_to(self.square, UP) - arc = self.get_arc(self.v, self.w) - arc.set_color(GREEN) - self.play( - Write(positive_words), - ShowCreation(arc) - ) - self.wait() - self.remove(arc) - self.play(movers.restore) - arc = self.get_arc(self.v, self.w) - arc.set_color(RED) - self.play( - Transform(positive_words, negative_words), - ShowCreation(arc) - ) - self.wait() - - anticommute = TexMobject( - v_tex, "\\times", w_tex, "=-", w_tex, "\\times", v_tex - ) - anticommute.shift(FRAME_X_RADIUS*RIGHT/2) - anticommute.to_edge(UP) - anticommute.set_color_by_tex(v_tex, V_COLOR) - anticommute.set_color_by_tex(w_tex, W_COLOR) - anticommute.add_background_rectangle() - for v1, v2 in (self.v, self.w), (self.w, self.v): - v1.label[0].set_fill(opacity = 0) - v1.target = v1.copy() - v1.target.label.rotate_in_place(v1.get_angle()-v2.get_angle()) - v1.target.label.scale_in_place(v1.get_length()/v2.get_length()) - v1.target.rotate(v2.get_angle()-v1.get_angle()) - v1.target.scale(v2.get_length()/v1.get_length()) - v1.target.label.move_to(v2.label) - self.play( - FadeOut(arc), - Transform(positive_words, anticommute) - ) - self.play( - Transform(self.v, self.v.target), - Transform(self.w, self.w.target), - rate_func = there_and_back, - run_time = 2, - ) - self.wait() - - def get_arc(self, v, w, radius = 2): - v_angle, w_angle = v.get_angle(), w.get_angle() - nudge = 0.05 - arc = Arc( - (1-2*nudge)*(w_angle - v_angle), - start_angle = interpolate(v_angle, w_angle, nudge), - radius = radius, - stroke_width = 8, - ) - arc.add_tip() - return arc - -class TwoDCrossProductExample(Define2dCrossProduct): - CONFIG = { - "v_coords" : [-3, 1], - "w_coords" : [2, 1], - } - def construct(self): - self.plane.fade() - v = Vector(self.v_coords, color = V_COLOR) - w = Vector(self.w_coords, color = W_COLOR) - - v.coords = Matrix(self.v_coords) - w.coords = Matrix(self.w_coords) - v.coords.next_to(v.get_end(), LEFT) - w.coords.next_to(w.get_end(), RIGHT) - v.coords.set_color(v.get_color()) - w.coords.set_color(w.get_color()) - for coords in v.coords, w.coords: - coords.background_rectangle = BackgroundRectangle(coords) - coords.add_to_back(coords.background_rectangle) - - - v.label = self.get_vector_label(v, "v", "left", color = v.get_color()) - w.label = self.get_vector_label(w, "w", "right", color = w.get_color()) - - matrix = Matrix(np.array([ - list(v.coords.copy().get_entries()), - list(w.coords.copy().get_entries()), - ]).T) - matrix_background = BackgroundRectangle(matrix) - col1, col2 = it.starmap(Group, matrix.get_mob_matrix().T) - det_text = get_det_text(matrix) - v_tex, w_tex = get_vect_tex("v", "w") - cross_product = TexMobject(v_tex, "\\times", w_tex, "=") - cross_product.set_color_by_tex(v_tex, V_COLOR) - cross_product.set_color_by_tex(w_tex, W_COLOR) - cross_product.add_background_rectangle() - equation_start = VGroup( - cross_product, - VGroup(matrix_background, det_text, matrix) - ) - equation_start.arrange() - equation_start.next_to(ORIGIN, DOWN).to_edge(LEFT) - - - for vect in v, w: - self.play( - ShowCreation(vect), - Write(vect.coords), - Write(vect.label) - ) - self.wait() - self.play( - Transform(v.coords.background_rectangle, matrix_background), - Transform(w.coords.background_rectangle, matrix_background), - Transform(v.coords.get_entries(), col1), - Transform(w.coords.get_entries(), col2), - Transform(v.coords.get_brackets(), matrix.get_brackets()), - Transform(w.coords.get_brackets(), matrix.get_brackets()), - ) - self.play(*list(map(Write, [det_text, cross_product]))) - - - v1, v2 = v.coords.get_entries() - w1, w2 = w.coords.get_entries() - entries = v1, v2, w1, w2 - for entry in entries: - entry.target = entry.copy() - det = np.linalg.det([self.v_coords, self.w_coords]) - equals, dot1, minus, dot2, equals_result = syms = VGroup(*list(map( - TexMobject, - ["=", "\\cdot", "-", "\\cdot", "=%d"%det] - ))) - - equation_end = VGroup( - equals, v1.target, dot1, w2.target, - minus, w1.target, dot2, v2.target, equals_result - ) - equation_end.arrange() - equation_end.next_to(equation_start) - syms_rect = BackgroundRectangle(syms) - syms.add_to_back(syms_rect) - equation_end.add_to_back(syms_rect) - syms.remove(equals_result) - - self.play( - Write(syms), - Transform( - VGroup(v1, w2).copy(), VGroup(v1.target, w2.target), - rate_func = squish_rate_func(smooth, 0, 1./3), - path_arc = np.pi/2 - ), - Transform( - VGroup(v2, w1).copy(), VGroup(v2.target, w1.target), - rate_func = squish_rate_func(smooth, 2./3, 1), - path_arc = np.pi/2 - ), - run_time = 3 - ) - self.wait() - self.play(Write(equals_result)) - - self.add_foreground_mobject(equation_start, equation_end) - self.show_transformation(v, w) - det_sym = TexMobject(str(int(abs(det)))) - det_sym.scale(1.5) - det_sym.next_to(v.get_end()+w.get_end(), DOWN+RIGHT, buff = MED_SMALL_BUFF/2) - arc = self.get_arc(v, w, radius = 1) - arc.set_color(RED) - self.play(Write(det_sym)) - self.play(ShowCreation(arc)) - self.wait() - - - def show_transformation(self, v, w): - i_hat, j_hat = self.get_basis_vectors() - self.play(*list(map(ShowCreation, [i_hat, j_hat]))) - self.add_unit_square(animate = True, opacity = 0.2) - self.apply_transposed_matrix( - [v.get_end()[:2], w.get_end()[:2]], - added_anims = [ - Transform(i_hat, v), - Transform(j_hat, w) - ] - ) - -class PlayAround(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" \\centering - Play with the idea if - you wish to understand it - """) - self.change_student_modes("pondering", "happy", "happy") - self.random_blink(2) - self.student_thinks("", student_index = 0) - self.zoom_in_on_thought_bubble() - -class BiggerWhenPerpendicular(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - } - def construct(self): - self.lock_in_faded_grid() - self.add_unit_square(animate = False) - square = self.square - self.remove(square) - - start_words = TextMobject("More perpendicular") - end_words = TextMobject("Similar direction") - arrow = TextMobject("\\Rightarrow") - v_tex, w_tex = get_vect_tex("v", "w") - cross_is = TexMobject(v_tex, "\\times", w_tex, "\\text{ is }") - cross_is.set_color_by_tex(v_tex, V_COLOR) - cross_is.set_color_by_tex(w_tex, W_COLOR) - bigger = TextMobject("bigger") - smaller = TextMobject("smaller") - bigger.scale(1.5) - smaller.scale(0.75) - bigger.set_color(PINK) - smaller.set_color(TEAL) - group = VGroup(start_words, arrow, cross_is, bigger) - group.arrange() - group.to_edge(UP) - end_words.move_to(start_words, aligned_edge = RIGHT) - smaller.next_to(cross_is, buff = MED_SMALL_BUFF/2, aligned_edge = DOWN) - for mob in list(group) + [end_words, smaller]: - mob.add_background_rectangle() - - v = Vector([2, 2], color = V_COLOR) - w = Vector([2, -2], color = W_COLOR) - v.target = v.copy().rotate(-np.pi/5) - w.target = w.copy().rotate(np.pi/5) - transforms = [ - self.get_matrix_transformation([v1.get_end()[:2], v2.get_end()[:2]]) - for v1, v2 in [(v, w), (v.target, w.target)] - ] - start_square, end_square = [ - square.copy().apply_function(transform) - for transform in transforms - ] - - for vect in v, w: - self.play(ShowCreation(vect)) - group.remove(bigger) - self.play( - FadeIn(group), - ShowCreation(start_square), - *list(map(Animation, [v, w])) - ) - self.play(GrowFromCenter(bigger)) - self.wait() - self.play( - Transform(start_square, end_square), - Transform(v, v.target), - Transform(w, w.target), - ) - self.play( - Transform(start_words, end_words), - Transform(bigger, smaller) - ) - self.wait() - -class ScalingRule(LinearTransformationScene): - CONFIG = { - "v_coords" : [2, -1], - "w_coords" : [1, 1], - "show_basis_vectors" : False - } - def construct(self): - self.lock_in_faded_grid() - self.add_unit_square(animate = False) - self.remove(self.square) - square = self.square - - v = Vector(self.v_coords, color = V_COLOR) - w = Vector(self.w_coords, color = W_COLOR) - v.label = self.get_vector_label(v, "v", "right", color = V_COLOR) - w.label = self.get_vector_label(w, "w", "left", color = W_COLOR) - new_v = v.copy().scale(3) - new_v.label = self.get_vector_label( - new_v, "3\\vec{\\textbf{v}}", "right", color = V_COLOR - ) - for vect in v, w, new_v: - vect.add(vect.label) - - transform = self.get_matrix_transformation( - [self.v_coords, self.w_coords] - ) - square.apply_function(transform) - new_squares = VGroup(*[ - square.copy().shift(m*v.get_end()) - for m in range(3) - ]) - - v_tex, w_tex = get_vect_tex("v", "w") - cross_product = TexMobject(v_tex, "\\times", w_tex) - rhs = TexMobject("=3(", v_tex, "\\times", w_tex, ")") - three_v = TexMobject("(3", v_tex, ")") - for tex_mob in cross_product, rhs, three_v: - tex_mob.set_color_by_tex(v_tex, V_COLOR) - tex_mob.set_color_by_tex(w_tex, W_COLOR) - equation = VGroup(cross_product, rhs) - equation.arrange() - equation.to_edge(UP) - v_tex_mob = cross_product[0] - three_v.move_to(v_tex_mob, aligned_edge = RIGHT) - for tex_mob in cross_product, rhs: - tex_mob.add_background_rectangle() - - self.add(cross_product) - self.play(ShowCreation(v)) - self.play(ShowCreation(w)) - self.play( - ShowCreation(square), - *list(map(Animation, [v, w])) - ) - self.wait() - self.play( - Transform(v, new_v), - Transform(v_tex_mob, three_v), - ) - self.wait() - self.play( - Transform(square, new_squares), - *list(map(Animation, [v, w])), - path_arc = -np.pi/6 - ) - self.wait() - self.play(Write(rhs)) - self.wait() - -class TechnicallyNotTheDotProduct(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - That was technically - not the cross product - """) - self.change_student_modes("confused") - self.change_student_modes("confused", "angry") - self.change_student_modes("confused", "angry", "sassy") - self.random_blink(3) - -class ThreeDShowParallelogramAndCrossProductVector(Scene): - pass - -class WriteAreaOfParallelogram(Scene): - def construct(self): - words = TextMobject( - "Area of ", "parallelogram", " $=$ ", "$2.5$", - arg_separator = "" - ) - words.set_color_by_tex("parallelogram", BLUE) - words.set_color_by_tex("$2.5$", BLUE) - result = words[-1] - words.remove(result) - - self.play(Write(words)) - self.wait() - self.play(Write(result, run_time = 1)) - self.wait() - -class WriteCrossProductProperties(Scene): - def construct(self): - v_tex, w_tex, p_tex = texs = get_vect_tex(*"vwp") - v_cash, w_cash, p_cash = ["$%s$"%tex for tex in texs] - cross_product = TexMobject(v_tex, "\\times", w_tex, "=", p_tex) - cross_product.set_color_by_tex(v_tex, V_COLOR) - cross_product.set_color_by_tex(w_tex, W_COLOR) - cross_product.set_color_by_tex(p_tex, P_COLOR) - cross_product.to_edge(UP, buff = LARGE_BUFF) - p_mob = cross_product[-1] - brace = Brace(p_mob) - brace.do_in_place(brace.stretch, 2, 0) - vector = brace.get_text("vector") - vector.set_color(P_COLOR) - length_words = TextMobject( - "Length of ", p_cash, "\\\\ = ", - "(parallelogram's area)" - ) - length_words.set_color_by_tex(p_cash, P_COLOR) - length_words.set_width(FRAME_X_RADIUS - 1) - length_words.set_color_by_tex("(parallelogram's area)", BLUE) - length_words.next_to(VGroup(cross_product, vector), DOWN, buff = LARGE_BUFF) - perpendicular = TextMobject( - "\\centering Perpendicular to", - v_cash, "and", w_cash - ) - perpendicular.set_width(FRAME_X_RADIUS - 1) - perpendicular.set_color_by_tex(v_cash, V_COLOR) - perpendicular.set_color_by_tex(w_cash, W_COLOR) - perpendicular.next_to(length_words, DOWN, buff = LARGE_BUFF) - - - self.play(Write(cross_product)) - self.play( - GrowFromCenter(brace), - Write(vector, run_time = 1) - ) - self.wait() - self.play(Write(length_words, run_time = 1)) - self.wait() - self.play(Write(perpendicular)) - self.wait() - -def get_cross_product_right_hand_rule_labels(): - v_tex, w_tex = get_vect_tex(*"vw") - return [ - v_tex, w_tex, - "%s \\times %s"%(v_tex, w_tex) - ] - -class CrossProductRightHandRule(RightHandRule): - CONFIG = { - "flip" : False, - "labels_tex" : get_cross_product_right_hand_rule_labels(), - "colors" : [U_COLOR, W_COLOR, P_COLOR], - } - -class LabelingExampleVectors(Scene): - def construct(self): - v_tex, w_tex = texs = get_vect_tex(*"vw") - colors = [U_COLOR, W_COLOR, P_COLOR] - equations = [ - TexMobject(v_tex, "=%s"%matrix_to_tex_string([0, 0, 2])), - TexMobject(w_tex, "=%s"%matrix_to_tex_string([0, 2, 0])), - TexMobject( - v_tex, "\\times", w_tex, - "=%s"%matrix_to_tex_string([-4, 0, 0]) - ), - ] - for eq, color in zip(equations, colors): - eq.set_color(color) - eq.scale(2) - - area_words = TextMobject("Area", "=4") - area_words[0].set_color(BLUE) - area_words.scale(2) - for mob in equations[:2] + [area_words, equations[2]]: - self.fade_in_out(mob) - - def fade_in_out(self, mob): - self.play(FadeIn(mob)) - self.wait() - self.play(FadeOut(mob)) - -class ThreeDTwoPossiblePerpendicularVectors(Scene): - pass - -class ThreeDCrossProductExample(Scene): - pass - -class ShowCrossProductFormula(Scene): - def construct(self): - colors = [X_COLOR, Y_COLOR, Z_COLOR] - - arrays = [ - ["%s_%d"%(s, i) for i in range(1, 4)] - for s in ("v", "w") - ] - matrices = list(map(Matrix, arrays)) - for matrix in matrices: - for entry, color in zip(matrix.get_entries(), colors): - entry.set_color(color) - m1, m2 = matrices - cross_product = VGroup(m1, TexMobject("\\times"), m2) - cross_product.arrange() - cross_product.shift(2*LEFT) - - entry_dicts = [{} for x in range(3)] - movement_sets = [] - for a, b, c in it.permutations(list(range(3))): - sign = get_perm_sign(a, b, c) - e1, e2 = m1.get_entries()[b], m2.get_entries()[c] - for e in e1, e2: - e.target = e.copy() - dot = TexMobject("\\cdot") - syms = VGroup(dot) - - if sign < 0: - minus = TexMobject("-") - syms.add(minus) - cross_entry = VGroup(minus, e2.target, dot, e1.target) - cross_entry.arrange() - entry_dicts[a]["negative"] = cross_entry - else: - cross_entry = VGroup(e1.target, dot, e2.target) - cross_entry.arrange() - entry_dicts[a]["positive"] = cross_entry - cross_entry.arrange() - movement_sets.append([ - e1, e1.target, - e2, e2.target, - syms - ]) - - result = Matrix([ - VGroup( - entry_dict["positive"], - entry_dict["negative"], - ).arrange() - for entry_dict in entry_dicts - ]) - equals = TexMobject("=").next_to(cross_product) - result.next_to(equals) - - self.play(FadeIn(cross_product)) - self.play( - Write(equals), - Write(result.get_brackets()) - ) - self.wait() - movement_sets[2], movement_sets[3] = movement_sets[3], movement_sets[2] - for e1, e1_target, e2, e2_target, syms in movement_sets: - e1.save_state() - e2.save_state() - self.play( - e1.scale_in_place, 1.5, - e2.scale_in_place, 1.5, - ) - self.play( - Transform(e1.copy(), e1_target), - Transform(e2.copy(), e2_target), - Write(syms), - e1.restore, - e2.restore, - path_arc = -np.pi/2 - ) - self.wait() - -class ThisGetsWeird(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "This gets weird...", - target_mode = "sassy" - ) - self.random_blink(2) - -class DeterminantTrick(Scene): - def construct(self): - v_terms, w_terms = [ - ["%s_%d"%(s, d) for d in range(1, 4)] - for s in ("v", "w") - ] - v = Matrix(v_terms) - w = Matrix(w_terms) - v.set_color(V_COLOR) - w.set_color(W_COLOR) - matrix = Matrix(np.array([ - [ - TexMobject("\\hat{%s}"%s) - for s in ("\\imath", "\\jmath", "k") - ], - list(v.get_entries().copy()), - list(w.get_entries().copy()), - ]).T) - colors = [X_COLOR, Y_COLOR, Z_COLOR] - col1, col2, col3 = it.starmap(Group, matrix.get_mob_matrix().T) - i, j, k = col1 - v1, v2, v3 = col2 - w1, w2, w3 = col3 - ##Really should fix Matrix mobject... - j.shift(0.1*UP) - k.shift(0.2*UP) - VGroup(v2, w2).shift(0.1*DOWN) - VGroup(v3, w3).shift(0.2*DOWN) - ## - - for color, entry in zip(colors, col1): - entry.set_color(color) - det_text = get_det_text(matrix) - equals = TexMobject("=") - equation = VGroup( - v, TexMobject("\\times"), w, - equals, VGroup(det_text, matrix) - ) - equation.arrange() - - self.add(*equation[:-2]) - self.wait() - self.play(Write(matrix.get_brackets())) - for col, vect in (col2, v), (col3, w): - col.save_state() - col.move_to(vect.get_entries()) - self.play( - col.restore, - path_arc = -np.pi/2, - ) - for entry in col1: - self.play(Write(entry)) - self.wait() - self.play(*list(map(Write, [equals, det_text]))) - self.wait() - - disclaimer = TextMobject("$^*$ See ``Note on conventions'' in description") - disclaimer.scale(0.7) - disclaimer.set_color(RED) - disclaimer.next_to(equation, DOWN) - self.play(FadeIn(disclaimer)) - self.wait() - self.play(FadeOut(disclaimer)) - - circle = Circle() - circle.stretch_to_fit_height(col1.get_height()+1) - circle.stretch_to_fit_width(col1.get_width()+1) - circle.move_to(col1) - randy = Randolph() - randy.scale(0.9) - randy.to_corner() - randy.to_edge(DOWN, buff = SMALL_BUFF) - self.play(FadeIn(randy)) - self.play( - randy.change_mode, "confused", - ShowCreation(circle) - ) - self.play(randy.look, RIGHT) - self.wait() - self.play(FadeOut(circle)) - - self.play( - equation.to_corner, UP+LEFT, - ApplyFunction( - lambda r : r.change_mode("plain").look(UP+RIGHT), - randy - ) - ) - quints = [ - (i, v2, w3, v3, w2), - (j, v3, w1, v1, w3), - (k, v1, w2, v2, w1), - ] - last_mob = None - paren_sets = [] - for quint in quints: - for mob in quint: - mob.t = mob.copy() - mob.save_state() - basis = quint[0] - basis.t.scale(1/0.8) - lp, minus, rp = syms = VGroup(*list(map(TexMobject, "(-)"))) - term = VGroup( - basis.t, lp, - quint[1].t, quint[2].t, minus, - quint[3].t, quint[4].t, rp - ) - term.arrange() - if last_mob: - plus = TexMobject("+") - syms.add(plus) - plus.next_to(term, LEFT, buff = MED_SMALL_BUFF/2) - term.add_to_back(plus) - term.next_to(last_mob, RIGHT, buff = MED_SMALL_BUFF/2) - else: - term.next_to(equation, DOWN, buff = MED_SMALL_BUFF, aligned_edge = LEFT) - last_mob = term - self.play(*it.chain(*[ - [mob.scale_in_place, 1.2] - for mob in quint - ])) - self.wait() - self.play(*[ - Transform(mob.copy(), mob.t) - for mob in quint - ] + [ - mob.restore for mob in quint - ] + [ - Write(syms) - ], - run_time = 2 - ) - self.wait() - paren_sets.append(VGroup(lp, rp)) - self.wait() - self.play(randy.change_mode, "pondering") - for parens in paren_sets: - brace = Brace(parens) - text = brace.get_text("Some number") - text.set_width(brace.get_width()) - self.play( - GrowFromCenter(brace), - Write(text, run_time = 2) - ) - self.wait() - -class ThereIsAReason(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "\\centering Sure, it's a \\\\", "notational", "trick", - ) - self.random_blink(2) - words = TextMobject( - "\\centering but there is a\\\\", - "reason", "for doing it" - ) - words.set_color_by_tex("reason", YELLOW) - self.teacher_says(words, target_mode = "surprised") - self.change_student_modes( - "raise_right_hand", "confused", "raise_left_hand" - ) - self.random_blink() - -class RememberDuality(TeacherStudentsScene): - def construct(self): - words = TextMobject("Remember ", "duality", "?", arg_separator = "") - words[1].set_color_by_gradient(BLUE, YELLOW) - self.teacher_says(words, target_mode = "sassy") - self.random_blink(2) - -class NextVideo(Scene): - def construct(self): - title = TextMobject(""" - Next video: Cross products in the - light of linear transformations - """) - title.set_height(1.2) - title.to_edge(UP, buff = MED_SMALL_BUFF/2) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - -class CrossAndDualWords(Scene): - def construct(self): - v_tex, w_tex, p_tex = get_vect_tex(*"vwp") - vector_word = TextMobject("Vector:") - transform_word = TextMobject("Dual transform:") - - cross = TexMobject( - p_tex, "=", v_tex, "\\times", w_tex - ) - for tex, color in zip([v_tex, w_tex, p_tex], [U_COLOR, W_COLOR, P_COLOR]): - cross.set_color_by_tex(tex, color) - input_array_tex = matrix_to_tex_string(["x", "y", "z"]) - func = TexMobject("L\\left(%s\\right) = "%input_array_tex) - matrix = Matrix(np.array([ - ["x", "y", "z"], - ["v_1", "v_2", "v_3"], - ["w_1", "w_2", "w_3"], - ]).T) - matrix.set_column_colors(WHITE, U_COLOR, W_COLOR) - det_text = get_det_text(matrix, background_rect = False) - det_text.add(matrix) - dot_with_cross = TexMobject( - "%s \\cdot ( "%input_array_tex, - v_tex, "\\times", w_tex, ")" - ) - dot_with_cross.set_color_by_tex(v_tex, U_COLOR) - dot_with_cross.set_color_by_tex(w_tex, W_COLOR) - transform = VGroup(func, det_text) - transform.arrange() - - VGroup(transform, dot_with_cross).scale(0.7) - VGroup(vector_word, cross).arrange( - RIGHT, buff = MED_SMALL_BUFF - ).center().shift(LEFT).to_edge(UP) - transform_word.next_to(vector_word, DOWN, buff = MED_SMALL_BUFF, aligned_edge = LEFT) - transform.next_to(transform_word, DOWN, buff = MED_SMALL_BUFF, aligned_edge = LEFT) - dot_with_cross.next_to(func, RIGHT) - - self.add(vector_word) - self.play(Write(cross)) - self.wait() - self.play(FadeIn(transform_word)) - self.play(Write(transform)) - self.wait() - self.play(Transform(det_text, dot_with_cross)) - self.wait() - - - - - - - - - - diff --git a/from_3b1b/old/eola/chapter8p2.py b/from_3b1b/old/eola/chapter8p2.py deleted file mode 100644 index e0a5c80b..00000000 --- a/from_3b1b/old/eola/chapter8p2.py +++ /dev/null @@ -1,985 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eola.chapter5 import get_det_text -from from_3b1b.old.eola.chapter8 import * - - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject( - "From [Grothendieck], I have also learned not", - "to take glory in the ", - "difficulty of a proof:", - "difficulty means we have not understood.", - "The idea is to be able to ", - "paint a landscape", - "in which the proof is obvious.", - arg_separator = " " - ) - words.set_color_by_tex("difficulty of a proof:", RED) - words.set_color_by_tex("paint a landscape", GREEN) - words.set_width(FRAME_WIDTH - 2) - words.to_edge(UP) - author = TextMobject("-Pierre Deligne") - author.set_color(YELLOW) - author.next_to(words, DOWN, buff = 0.5) - - self.play(FadeIn(words)) - self.wait(4) - self.play(Write(author, run_time = 3)) - self.wait() - -class CrossProductSymbols(Scene): - def construct(self): - v_tex, w_tex, p_tex = get_vect_tex(*"vwp") - equation = TexMobject( - v_tex, "\\times", w_tex, "=", p_tex - ) - equation.set_color_by_tex(v_tex, V_COLOR) - equation.set_color_by_tex(w_tex, W_COLOR) - equation.set_color_by_tex(p_tex, P_COLOR) - brace = Brace(equation[-1]) - brace.stretch_to_fit_width(0.7) - vector_text = brace.get_text("Vector") - vector_text.set_color(RED) - self.add(equation) - self.play(*list(map(Write, [brace, vector_text]))) - self.wait() - -class DeterminantTrickCopy(DeterminantTrick): - pass - -class BruteForceVerification(Scene): - def construct(self): - v = Matrix(["v_1", "v_2", "v_3"]) - w = Matrix(["w_1", "w_2", "w_3"]) - v1, v2, v3 = v.get_entries() - w1, w2, w3 = w.get_entries() - v.set_color(V_COLOR) - w.set_color(W_COLOR) - def get_term(e1, e2, e3, e4): - group = VGroup( - e1.copy(), e2.copy(), - TexMobject("-"), - e3.copy(), e4.copy(), - ) - group.arrange() - return group - cross = Matrix(list(it.starmap(get_term, [ - (v2, w3, v3, w2), - (v3, w1, v1, w3), - (v2, w3, v3, w2), - ]))) - cross_product = VGroup( - v.copy(), TexMobject("\\times"), w.copy(), - TexMobject("="), cross.copy() - ) - cross_product.arrange() - cross_product.scale(0.75) - - formula_word = TextMobject("Numerical formula") - computation_words = TextMobject(""" - Facts you could (painfully) - verify computationally - """) - computation_words.scale(0.75) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - computation_words.to_edge(UP, buff = MED_SMALL_BUFF/2) - h_line.next_to(computation_words, DOWN) - formula_word.next_to(h_line, UP, buff = MED_SMALL_BUFF) - computation_words.shift(FRAME_X_RADIUS*RIGHT/2) - formula_word.shift(FRAME_X_RADIUS*LEFT/2) - - cross_product.next_to(formula_word, DOWN, buff = LARGE_BUFF) - - self.add(formula_word, computation_words) - self.play( - ShowCreation(h_line), - ShowCreation(v_line), - Write(cross_product) - ) - - v_tex, w_tex = get_vect_tex(*"vw") - v_dot, w_dot = [ - TexMobject( - tex, "\\cdot", - "(", v_tex, "\\times", w_tex, ")", - "= 0" - ) - for tex in (v_tex, w_tex) - ] - theta_def = TexMobject( - "\\theta", - "= \\cos^{-1} \\big(", v_tex, "\\cdot", w_tex, "/", - "(||", v_tex, "||", "\\cdot", "||", w_tex, "||)", "\\big)" - ) - - length_check = TexMobject( - "||", "(", v_tex, "\\times", w_tex, ")", "|| = ", - "(||", v_tex, "||)", - "(||", w_tex, "||)", - "\\sin(", "\\theta", ")" - ) - last_point = h_line.get_center()+FRAME_X_RADIUS*RIGHT/2 - max_width = FRAME_X_RADIUS-1 - for mob in v_dot, w_dot, theta_def, length_check: - mob.set_color_by_tex(v_tex, V_COLOR) - mob.set_color_by_tex(w_tex, W_COLOR) - mob.set_color_by_tex("\\theta", GREEN) - mob.next_to(last_point, DOWN, buff = MED_SMALL_BUFF) - if mob.get_width() > max_width: - mob.set_width(max_width) - last_point = mob - self.play(FadeIn(mob)) - self.wait() - -class ButWeCanDoBetter(TeacherStudentsScene): - def construct(self): - self.teacher_says("But we can do \\\\ better than that") - self.change_student_modes(*["happy"]*3) - self.random_blink(3) - -class Prerequisites(Scene): - def construct(self): - title = TextMobject("Prerequisites") - title.to_edge(UP) - title.set_color(YELLOW) - - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_width(FRAME_X_RADIUS - 1) - left_rect, right_rect = [ - rect.copy().shift(DOWN/2).to_edge(edge) - for edge in (LEFT, RIGHT) - ] - chapter5 = TextMobject(""" - \\centering - Chapter 5 - Determinants - """) - chapter7 = TextMobject(""" - \\centering - Chapter 7: - Dot products and duality - """) - - self.add(title) - for chapter, rect in (chapter5, left_rect), (chapter7, right_rect): - if chapter.get_width() > rect.get_width(): - chapter.set_width(rect.get_width()) - chapter.next_to(rect, UP) - self.play( - Write(chapter5), - ShowCreation(left_rect) - ) - self.play( - Write(chapter7), - ShowCreation(right_rect) - ) - self.wait() - -class DualityReview(TeacherStudentsScene): - def construct(self): - words = TextMobject("Quick", "duality", "review") - words[1].set_color_by_gradient(BLUE, YELLOW) - self.teacher_says(words, target_mode = "surprised") - self.change_student_modes("pondering") - self.random_blink(2) - -class DotProductToTransformSymbol(Scene): - CONFIG = { - "vect_coords" : [2, 1] - } - def construct(self): - v_mob = TexMobject(get_vect_tex("v")) - v_mob.set_color(V_COLOR) - - matrix = Matrix([self.vect_coords]) - vector = Matrix(self.vect_coords) - matrix.set_column_colors(X_COLOR, Y_COLOR) - vector.set_column_colors(YELLOW) - _input = Matrix(["x", "y"]) - _input.get_entries().set_color_by_gradient(X_COLOR, Y_COLOR) - left_input, right_input = [_input.copy() for x in range(2)] - dot, equals = list(map(TexMobject, ["\\cdot", "="])) - equation = VGroup( - vector, dot, left_input, equals, - matrix, right_input - ) - equation.arrange() - left_brace = Brace(VGroup(vector, left_input)) - right_brace = Brace(matrix, UP) - left_words = left_brace.get_text("Dot product") - right_words = right_brace.get_text("Transform") - right_words.set_width(right_brace.get_width()) - - right_v_brace = Brace(right_input, UP) - right_v_mob = v_mob.copy() - right_v_brace.put_at_tip(right_v_mob) - right_input.add(right_v_brace, right_v_mob) - left_v_brace = Brace(left_input, UP) - left_v_mob = v_mob.copy() - left_v_brace.put_at_tip(left_v_mob) - left_input.add(left_v_brace, left_v_mob) - - - self.add(matrix, right_input) - self.play( - GrowFromCenter(right_brace), - Write(right_words, run_time = 1) - ) - self.wait() - self.play( - Write(equals), - Write(dot), - Transform(matrix.copy(), vector), - Transform(right_input.copy(), left_input) - ) - self.play( - GrowFromCenter(left_brace), - Write(left_words, run_time = 1) - ) - self.wait() - -class MathematicalWild(Scene): - def construct(self): - title = TextMobject("In the mathematical wild") - title.to_edge(UP) - self.add(title) - - randy = Randolph() - randy.shift(DOWN) - bubble = ThoughtBubble(width = 5, height = 4) - bubble.write(""" - \\centering - Some linear - transformation - to the number line - """) - bubble.content.set_color(BLUE) - bubble.content.shift(MED_SMALL_BUFF*UP/2) - bubble.remove(*bubble[:-1]) - bubble.add(bubble.content) - bubble.next_to(randy.get_corner(UP+RIGHT), RIGHT) - vector = Vector([1, 2]) - vector.move_to(randy.get_corner(UP+LEFT), aligned_edge = DOWN+LEFT) - dual_words = TextMobject("Dual vector") - dual_words.set_color_by_gradient(BLUE, YELLOW) - dual_words.next_to(vector, LEFT) - - self.add(randy) - self.play(Blink(randy)) - self.play(FadeIn(bubble)) - self.play(randy.change_mode, "sassy") - self.play(Blink(randy)) - self.wait() - self.play(randy.look, UP+LEFT) - self.play( - ShowCreation(vector), - randy.change_mode, "raise_right_hand" - ) - self.wait() - self.play(Write(dual_words)) - self.play(Blink(randy)) - self.wait() - -class ThreeStepPlan(Scene): - def construct(self): - title = TextMobject("The plan") - title.set_color(YELLOW) - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - - v_tex, w_tex = get_vect_tex(*"vw") - v_text, w_text, cross_text = [ - "$%s$"%s - for s in (v_tex, w_tex, v_tex + "\\times" + w_tex) - ] - steps = [ - TextMobject( - "1. Define a 3d-to-1d", "linear \\\\", "transformation", - "in terms of", v_text, "and", w_text - ), - TextMobject( - "2. Find its", "dual vector" - ), - TextMobject( - "3. Show that this dual is", cross_text - ) - ] - linear, transformation = steps[0][1:1+2] - steps[0].set_color_by_tex(v_text, V_COLOR) - steps[0].set_color_by_tex(w_text, W_COLOR) - steps[1][1].set_color_by_gradient(BLUE, YELLOW) - steps[2].set_color_by_tex(cross_text, P_COLOR) - VGroup(*steps).arrange( - DOWN, aligned_edge = LEFT, buff = LARGE_BUFF - ).next_to(h_line, DOWN, buff = MED_SMALL_BUFF) - - self.add(title) - self.play(ShowCreation(h_line)) - for step in steps: - self.play(Write(step, run_time = 2)) - self.wait() - - linear_transformation = TextMobject("Linear", "transformation") - linear_transformation.next_to(h_line, DOWN, MED_SMALL_BUFF) - det = self.get_det() - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(3.5) - left_right_arrow = TexMobject("\\Leftrightarrow") - left_right_arrow.shift(DOWN) - det.next_to(left_right_arrow, LEFT) - rect.next_to(left_right_arrow, RIGHT) - - steps[0].remove(linear, transformation) - self.play( - Transform( - VGroup(linear, transformation), - linear_transformation - ), - *list(map(FadeOut, steps)) - ) - self.wait() - self.play(Write(left_right_arrow)) - self.play(Write(det)) - self.play(ShowCreation(rect)) - self.wait(0) - - def get_det(self): - matrix = Matrix(np.array([ - ["\\hat{\\imath}", "\\hat{\\jmath}", "\\hat{k}"], - ["v_%d"%d for d in range(1, 4)], - ["w_%d"%d for d in range(1, 4)], - ]).T) - matrix.set_column_colors(X_COLOR, V_COLOR, W_COLOR) - matrix.get_mob_matrix()[1, 0].set_color(Y_COLOR) - matrix.get_mob_matrix()[2, 0].set_color(Z_COLOR) - VGroup(*matrix.get_mob_matrix()[1, 1:]).shift(0.15*DOWN) - VGroup(*matrix.get_mob_matrix()[2, 1:]).shift(0.35*DOWN) - det_text = get_det_text(matrix) - det_text.add(matrix) - return det_text - -class DefineDualTransform(Scene): - def construct(self): - self.add_title() - self.show_triple_cross_product() - self.write_function() - self.introduce_dual_vector() - self.expand_dot_product() - self.ask_question() - - def add_title(self): - title = TextMobject("What a student might think") - title.not_real = TextMobject("Not the real cross product") - for mob in title, title.not_real: - mob.set_width(FRAME_X_RADIUS - 1) - mob.set_color(RED) - mob.to_edge(UP) - self.add(title) - self.title = title - - def show_triple_cross_product(self): - colors = [WHITE, ORANGE, W_COLOR] - tex_mobs = list(map(TexMobject, get_vect_tex(*"uvw"))) - u_tex, v_tex, w_tex = tex_mobs - arrays = [ - Matrix(["%s_%d"%(s, d) for d in range(1, 4)]) - for s in "uvw" - ] - defs_equals = VGroup() - definitions = VGroup() - for array, tex_mob, color in zip(arrays, tex_mobs, colors): - array.set_column_colors(color) - tex_mob.set_color(color) - equals = TexMobject("=") - definition = VGroup(tex_mob, equals, array) - definition.arrange(RIGHT) - definitions.add(definition) - defs_equals.add(equals) - definitions.arrange(buff = MED_SMALL_BUFF) - definitions.shift(2*DOWN) - - mobs_with_targets = list(it.chain( - tex_mobs, *[a.get_entries() for a in arrays] - )) - for mob in mobs_with_targets: - mob.target = mob.copy() - matrix = Matrix(np.array([ - [e.target for e in array.get_entries()] - for array in arrays - ]).T) - det_text = get_det_text(matrix, background_rect = False) - syms = times1, times2, equals = [ - TexMobject(sym) - for sym in ("\\times", "\\times", "=",) - ] - triple_cross = VGroup( - u_tex.target, times1, v_tex.target, times2, w_tex.target, equals - ) - triple_cross.arrange() - - final_mobs = VGroup(triple_cross, VGroup(det_text, matrix)) - final_mobs.arrange() - final_mobs.next_to(self.title, DOWN, buff = MED_SMALL_BUFF) - - for mob in definitions, final_mobs: - mob.set_width(FRAME_X_RADIUS - 1) - - for array in arrays: - brackets = array.get_brackets() - brackets.target = matrix.get_brackets() - mobs_with_targets.append(brackets) - for def_equals in defs_equals: - def_equals.target = equals - mobs_with_targets.append(def_equals) - - self.play(FadeIn( - definitions, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait(2) - self.play(*[ - Transform(mob.copy(), mob.target) - for mob in tex_mobs - ] + [ - Write(times1), - Write(times2), - ]) - triple_cross.add(*self.get_mobjects_from_last_animation()[:3]) - self.play(*[ - Transform(mob.copy(), mob.target) - for mob in mobs_with_targets - if mob not in tex_mobs - ]) - u_entries = self.get_mobjects_from_last_animation()[:3] - v_entries = self.get_mobjects_from_last_animation()[3:6] - w_entries = self.get_mobjects_from_last_animation()[6:9] - self.play(Write(det_text)) - self.wait(2) - - self.det_text = det_text - self.definitions = definitions - self.u_entries = u_entries - self.v_entries = v_entries - self.w_entries = w_entries - self.matrix = matrix - self.triple_cross = triple_cross - self.v_tex, self.w_tex = v_tex, w_tex - self.equals = equals - - def write_function(self): - brace = Brace(self.det_text, DOWN) - number_text = brace.get_text("Number") - self.play(Transform(self.title, self.title.not_real)) - self.wait() - self.play(FadeOut(self.definitions)) - self.play( - GrowFromCenter(brace), - Write(number_text) - ) - self.wait() - - x, y, z = variables = list(map(TexMobject, "xyz")) - for var, entry in zip(variables, self.u_entries): - var.scale(0.8) - var.move_to(entry) - entry.target = var - brace.target = Brace(z) - brace.target.stretch_to_fit_width(0.5) - number_text.target = brace.target.get_text("Variable") - v_brace = Brace(self.matrix.get_mob_matrix()[0, 1], UP) - w_brace = Brace(self.matrix.get_mob_matrix()[0, 2], UP) - for vect_brace, tex in (v_brace, self.v_tex), (w_brace, self.w_tex): - vect_brace.stretch_to_fit_width(brace.target.get_width()) - new_tex = tex.copy() - vect_brace.put_at_tip(new_tex) - vect_brace.tex = new_tex - func_tex = TexMobject( - "f\\left(%s\\right)"%matrix_to_tex_string(list("xyz")) - ) - func_tex.scale(0.7) - func_input = Matrix(list("xyz")) - func_input_template = VGroup(*func_tex[3:-2]) - func_input.set_height(func_input_template.get_height()) - func_input.next_to(VGroup(*func_tex[:3]), RIGHT) - VGroup(*func_tex[-2:]).next_to(func_input, RIGHT) - func_tex[0].scale_in_place(1.5) - - func_tex = VGroup( - VGroup(*[func_tex[i] for i in (0, 1, 2, -2, -1)]), - func_input - ) - func_tex.next_to(self.equals, LEFT) - - self.play( - FadeOut(self.title), - FadeOut(self.triple_cross), - *[ - Transform(mob, mob.target) - for mob in [brace, number_text] - ] - ) - self.play(*[ - Transform(mob, mob.target) - for mob in self.u_entries - ]) - self.play(*[ - Write(VGroup(vect_brace, vect_brace.tex)) - for vect_brace in (v_brace, w_brace) - ]) - self.wait() - self.play(Write(func_tex)) - self.wait() - - self.func_tex = func_tex - self.variables_text = VGroup(brace, number_text) - - def introduce_dual_vector(self): - everything = VGroup(*self.get_mobjects()) - colors = [X_COLOR, Y_COLOR, Z_COLOR] - q_marks = VGroup(*list(map(TextMobject, "???"))) - q_marks.scale(2) - q_marks.set_color_by_gradient(*colors) - - title = VGroup(TextMobject("This function is linear")) - title.set_color(GREEN) - title.to_edge(UP) - matrix = Matrix([list(q_marks.copy())]) - matrix.set_height(self.func_tex.get_height()/2) - dual_vector = Matrix(list(q_marks)) - dual_vector.set_height(self.func_tex.get_height()) - dual_vector.get_brackets()[0].shift(0.2*LEFT) - dual_vector.get_entries().shift(0.1*LEFT) - dual_vector.scale(1.25) - dual_dot = VGroup( - dual_vector, - TexMobject("\\cdot").next_to(dual_vector) - ) - matrix_words = TextMobject(""" - $1 \\times 3$ matrix encoding the - 3d-to-1d linear transformation - """) - - self.play( - Write(title, run_time = 2), - everything.shift, DOWN - ) - self.remove(everything) - self.add(*everything) - self.wait() - - func, func_input = self.func_tex - func_input.target = func_input.copy() - func_input.target.scale(1.2) - func_input.target.move_to(self.func_tex, aligned_edge = RIGHT) - matrix.next_to(func_input.target, LEFT) - dual_dot.next_to(func_input.target, LEFT) - matrix_words.next_to(matrix, DOWN, buff = 1.5) - matrix_words.shift_onto_screen() - matrix_arrow = Arrow( - matrix_words.get_top(), - matrix.get_bottom(), - color = WHITE - ) - - self.play( - Transform(func, matrix), - MoveToTarget(func_input), - FadeOut(self.variables_text), - ) - self.wait() - self.play( - Write(matrix_words), - ShowCreation(matrix_arrow) - ) - self.wait(2) - self.play(*list(map(FadeOut, [matrix_words, matrix_arrow]))) - self.play( - Transform(func, dual_vector), - Write(dual_dot[1]) - ) - self.wait() - - p_coords = VGroup(*list(map(TexMobject, [ - "p_%d"%d for d in range(1, 4) - ]))) - p_coords.set_color(RED) - p_array = Matrix(list(p_coords)) - p_array.set_height(dual_vector.get_height()) - p_array.move_to(dual_vector, aligned_edge = RIGHT) - p_brace = Brace(p_array, UP) - p_tex = TexMobject(get_vect_tex("p")) - p_tex.set_color(P_COLOR) - p_brace.put_at_tip(p_tex) - - self.play( - GrowFromCenter(p_brace), - Write(p_tex) - ) - self.play(Transform( - func, p_array, - run_time = 2, - lag_ratio = 0.5 - )) - self.remove(func) - self.add(p_array) - self.wait() - self.play(FadeOut(title)) - self.wait() - - self.p_array = p_array - self.input_array = func_input - - def expand_dot_product(self): - everything = VGroup(*self.get_mobjects()) - self.play(everything.to_edge, UP) - self.remove(everything) - self.add(*everything) - to_fade = VGroup() - - p_entries = self.p_array.get_entries() - input_entries = self.input_array.get_entries() - dot_components = VGroup() - for p, x, i in zip(p_entries, input_entries, it.count()): - if i == 2: - x.sym = TexMobject("=") - else: - x.sym = TexMobject("+") - p.sym = TexMobject("\\cdot") - p.target = p.copy().scale(2) - x.target = x.copy().scale(2) - component = VGroup(p.target, p.sym, x.target, x.sym) - component.arrange() - dot_components.add(component) - dot_components.arrange() - dot_components.next_to(ORIGIN, LEFT) - dot_components.shift(1.5*DOWN) - dot_arrow = Arrow(self.p_array.get_corner(DOWN+RIGHT), dot_components) - to_fade.add(dot_arrow) - self.play(ShowCreation(dot_arrow)) - new_ps = VGroup() - for p, x in zip(p_entries, input_entries): - self.play( - MoveToTarget(p.copy()), - MoveToTarget(x.copy()), - Write(p.sym), - Write(x.sym) - ) - mobs = self.get_mobjects_from_last_animation() - new_ps.add(mobs[0]) - to_fade.add(*mobs[1:]) - self.wait() - - x, y, z = self.u_entries - v1, v2, v3 = self.v_entries - w1, w2, w3 = self.w_entries - cross_components = VGroup() - quints = [ - (x, v2, w3, v3, w2), - (y, v3, w1, v1, w3), - (z, v1, w2, v2, w1), - ] - quints = [ - [m.copy() for m in quint] - for quint in quints - ] - for i, quint in enumerate(quints): - sym_strings = ["(", "\\cdot", "-", "\\cdot", ")"] - if i < 2: - sym_strings[-1] += "+" - syms = list(map(TexMobject, sym_strings)) - for mob, sym in zip(quint, syms): - mob.target = mob.copy() - mob.target.scale(1.5) - mob.sym = sym - quint_targets = [mob.target for mob in quint] - component = VGroup(*it.chain(*list(zip(quint_targets, syms)))) - component.arrange() - cross_components.add(component) - to_fade.add(syms[0], syms[-1], quint[0]) - cross_components.arrange(DOWN, aligned_edge = LEFT, buff = MED_SMALL_BUFF) - cross_components.next_to(dot_components, RIGHT) - for quint in quints: - self.play(*[ - ApplyMethod(mob.set_color, YELLOW) - for mob in quint - ]) - self.wait(0.5) - self.play(*[ - MoveToTarget(mob) - for mob in quint - ] + [ - Write(mob.sym) - for mob in quint - ]) - self.wait() - self.play( - ApplyFunction( - lambda m : m.arrange( - DOWN, buff = MED_SMALL_BUFF+SMALL_BUFF - ).next_to(cross_components, LEFT), - new_ps - ), - *list(map(FadeOut, to_fade)) - ) - self.play(*[ - Write(TexMobject("=").next_to(p, buff = 2*SMALL_BUFF)) - for p in new_ps - ]) - equals = self.get_mobjects_from_last_animation() - self.wait(2) - - everything = everything.copy() - self.play( - FadeOut(VGroup(*self.get_mobjects())), - Animation(everything) - ) - self.clear() - self.add(everything) - - def ask_question(self): - everything = VGroup(*self.get_mobjects()) - p_tex = "$%s$"%get_vect_tex("p") - question = TextMobject( - "What vector", - p_tex, - "has \\\\ the property that" - ) - question.to_edge(UP) - question.set_color(YELLOW) - question.set_color_by_tex(p_tex, P_COLOR) - everything.target = everything.copy() - everything.target.next_to( - question, DOWN, buff = MED_SMALL_BUFF - ) - self.play( - MoveToTarget(everything), - Write(question) - ) - self.wait() - -class WhyAreWeDoingThis(TeacherStudentsScene): - def construct(self): - self.student_says( - "Um...why are \\\\ we doing this?", - target_mode = "confused" - ) - self.random_blink() - self.play(self.get_teacher().change_mode, "erm") - self.change_student_modes("plain", "confused", "raise_left_hand") - self.random_blink() - self.change_student_modes("pondering", "confused", "raise_left_hand") - self.random_blink(5) - -class ThreeDTripleCrossProduct(Scene): - pass #Simple parallelepiped - -class ThreeDMovingVariableVector(Scene): - pass #white u moves around - -class ThreeDMovingVariableVectorWithCrossShowing(Scene): - pass #white u moves around, red p is present - -class NowForTheCoolPart(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Now for the\\\\", - "cool part" - ) - self.change_student_modes(*["happy"]*3) - self.random_blink(2) - self.teacher_says( - "Let's answer the same question,\\\\", - "but this time geometrically" - ) - self.change_student_modes(*["pondering"]*3) - self.random_blink(2) - -class ThreeDDotProductProjection(Scene): - pass # - -class DotProductWords(Scene): - def construct(self): - p_tex = "$%s$"%get_vect_tex("p") - p_mob = TextMobject(p_tex) - p_mob.scale(1.5) - p_mob.set_color(P_COLOR) - input_array = Matrix(list("xyz")) - dot_product = VGroup(p_mob, Dot(radius = 0.07), input_array) - dot_product.arrange(buff = MED_SMALL_BUFF/2) - equals = TexMobject("=") - dot_product.next_to(equals, LEFT) - words = VGroup(*it.starmap(TextMobject, [ - ("(Length of projection)",), - ("(Length of ", p_tex, ")",) - ])) - times = TexMobject("\\times") - words[1].set_color_by_tex(p_tex, P_COLOR) - words[0].next_to(equals, RIGHT) - words[1].next_to(words[0], DOWN, aligned_edge = LEFT) - times.next_to(words[0], RIGHT) - - everyone = VGroup(dot_product, equals, times, words) - everyone.center().set_width(FRAME_X_RADIUS - 1) - self.add(dot_product) - self.play(Write(equals)) - self.play(Write(words[0])) - self.wait() - self.play( - Write(times), - Write(words[1]) - ) - self.wait() - -class ThreeDProjectToPerpendicular(Scene): - pass # - -class GeometricVolumeWords(Scene): - def construct(self): - v_tex, w_tex = [ - "$%s$"%s - for s in get_vect_tex(*"vw") - ] - - words = VGroup( - TextMobject("(Area of", "parallelogram", ")$\\times$"), - TextMobject( - "(Component of $%s$"%matrix_to_tex_string(list("xyz")), - "perpendicular to", v_tex, "and", w_tex, ")" - ) - ) - words[0].set_color_by_tex("parallelogram", BLUE) - words[1].set_color_by_tex(v_tex, ORANGE) - words[1].set_color_by_tex(w_tex, W_COLOR) - words.arrange(RIGHT) - words.set_width(FRAME_WIDTH - 1) - words.to_edge(DOWN, buff = SMALL_BUFF) - for word in words: - self.play(Write(word)) - self.wait() - -class WriteXYZ(Scene): - def construct(self): - self.play(Write(Matrix(list("xyz")))) - self.wait() - -class ThreeDDotProductWithCross(Scene): - pass - -class CrossVectorEmphasisWords(Scene): - def construct(self): - v_tex, w_tex = ["$%s$"%s for s in get_vect_tex(*"vw")] - words = [ - TextMobject("Perpendicular to", v_tex, "and", w_tex), - TextMobject("Length = (Area of ", "parallelogram", ")") - ] - for word in words: - word.set_color_by_tex(v_tex, ORANGE) - word.set_color_by_tex(w_tex, W_COLOR) - word.set_color_by_tex("parallelogram", BLUE) - self.play(Write(word)) - self.wait() - self.play(FadeOut(word)) - -class NextVideo(Scene): - def construct(self): - title = TextMobject(""" - Next video: Change of basis - """) - title.to_edge(UP, buff = MED_SMALL_BUFF/2) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - -class ChangeOfBasisPreview(LinearTransformationScene): - CONFIG = { - "include_background_plane" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_WIDTH, - "secondary_line_ratio" : 0 - }, - "t_matrix" : [[2, 1], [-1, 1]], - "i_target_color" : YELLOW, - "j_target_color" : MAROON_B, - "sum_color" : PINK, - "vector" : [-1, 2], - } - def construct(self): - randy = Randolph() - pinky = Mortimer(color = PINK) - randy.to_corner(DOWN+LEFT) - pinky.to_corner(DOWN+RIGHT) - self.plane.fade() - - self.add_foreground_mobject(randy, pinky) - coords = Matrix(self.vector) - coords.add_to_back(BackgroundRectangle(coords)) - self.add_foreground_mobject(coords) - coords.move_to( - randy.get_corner(UP+RIGHT), - aligned_edge = DOWN+LEFT - ) - coords.target = coords.copy() - coords.target.move_to( - pinky.get_corner(UP+LEFT), - aligned_edge = DOWN+RIGHT - ) - self.play( - Write(coords), - randy.change_mode, "speaking" - ) - self.scale_basis_vectors() - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [ - MoveToTarget(coords), - ApplyMethod(pinky.change_mode, "speaking"), - ApplyMethod(randy.change_mode, "plain"), - ] - ) - self.play( - randy.change_mode, "erm", - self.i_hat.set_color, self.i_target_color, - self.j_hat.set_color, self.j_target_color, - ) - self.i_hat.color = self.i_target_color - self.j_hat.color = self.j_target_color - self.scale_basis_vectors() - - def scale_basis_vectors(self): - for vect in self.i_hat, self.j_hat: - vect.save_state() - self.play(self.i_hat.scale, self.vector[0]) - self.play(self.j_hat.scale, self.vector[1]) - self.play(self.j_hat.shift, self.i_hat.get_end()) - sum_vect = Vector(self.j_hat.get_end(), color = self.sum_color) - self.play(ShowCreation(sum_vect)) - self.wait(2) - self.play( - FadeOut(sum_vect), - self.i_hat.restore, - self.j_hat.restore, - ) - self.wait() - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/chapter9.py b/from_3b1b/old/eola/chapter9.py deleted file mode 100644 index 04735bb0..00000000 --- a/from_3b1b/old/eola/chapter9.py +++ /dev/null @@ -1,1821 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eola.chapter1 import plane_wave_homotopy - -V_COLOR = YELLOW - -class Jennifer(PiCreature): - CONFIG = { - "color" : PINK, - "start_corner" : DOWN+LEFT, - } - -class You(PiCreature): - CONFIG = { - "color" : BLUE_E, - "start_corner" : DOWN+RIGHT, - "flip_at_start" : True, - } - -def get_small_bubble(pi_creature, height = 4, width = 3): - pi_center_x = pi_creature.get_center()[0] - kwargs = { - "height" : 4, - "bubble_center_adjustment_factor" : 1./6, - } - bubble = ThoughtBubble(**kwargs) - bubble.stretch_to_fit_width(3)##Canonical width - bubble.rotate(np.pi/4) - bubble.stretch_to_fit_width(width) - bubble.stretch_to_fit_height(height) - if pi_center_x < 0: - bubble.flip() - bubble.next_to(pi_creature, UP, buff = MED_SMALL_BUFF) - bubble.shift_onto_screen() - bubble.set_fill(BLACK, opacity = 0.8) - return bubble - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject( - "\\centering ``Mathematics is the art of giving the \\\\", - "same name ", - "to ", - "different things", - ".''", - arg_separator = " " - ) - words.set_color_by_tex("same name ", BLUE) - words.set_color_by_tex("different things", MAROON_B) - # words.set_width(FRAME_WIDTH - 2) - words.to_edge(UP) - author = TextMobject("-Henri Poincar\\'e.") - author.set_color(YELLOW) - author.next_to(words, DOWN, buff = 0.5) - - self.play(FadeIn(words)) - self.wait(2) - self.play(Write(author, run_time = 3)) - self.wait(2) - -class LinearCombinationScene(LinearTransformationScene): - CONFIG = { - "include_background_plane" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_X_RADIUS, - "y_radius" : FRAME_Y_RADIUS, - "secondary_line_ratio" : 1 - }, - } - def setup(self): - LinearTransformationScene.setup(self) - self.i_hat.label = self.get_vector_label( - self.i_hat, "\\hat{\\imath}", "right" - ) - self.j_hat.label = self.get_vector_label( - self.j_hat, "\\hat{\\jmath}", "left" - ) - - def show_linear_combination(self, numerical_coords, - basis_vectors, - coord_mobs, - revert_to_original = True, - show_sum_vect = False, - sum_vect_color = V_COLOR, - ): - for basis in basis_vectors: - if not hasattr(basis, "label"): - basis.label = VectorizedPoint() - direction = np.round(rotate_vector( - basis.get_end(), np.pi/2 - )) - basis.label.next_to(basis.get_center(), direction) - basis.save_state() - basis.label.save_state() - if coord_mobs is None: - coord_mobs = list(map(TexMobject, list(map(str, numerical_coords)))) - VGroup(*coord_mobs).set_fill(opacity = 0) - for coord, basis in zip(coord_mobs, basis_vectors): - coord.next_to(basis.label, LEFT) - for coord, basis, scalar in zip(coord_mobs, basis_vectors, numerical_coords): - basis.target = basis.copy().scale(scalar) - basis.label.target = basis.label.copy() - coord.target = coord.copy() - new_label = VGroup(coord.target, basis.label.target) - new_label.arrange(aligned_edge = DOWN) - new_label.move_to( - basis.label, - aligned_edge = basis.get_center()-basis.label.get_center() - ) - new_label.shift( - basis.target.get_center() - basis.get_center() - ) - coord.target.next_to(basis.label.target, LEFT) - coord.target.set_fill(basis.get_color(), opacity = 1) - self.play(*list(map(MoveToTarget, [ - coord, basis, basis.label - ]))) - self.wait() - self.play(*[ - ApplyMethod(m.shift, basis_vectors[0].get_end()) - for m in self.get_mobjects_from_last_animation() - ]) - if show_sum_vect: - sum_vect = Vector( - basis_vectors[1].get_end(), - color = sum_vect_color - ) - self.play(ShowCreation(sum_vect)) - self.wait(2) - if revert_to_original: - self.play(*it.chain( - [basis.restore for basis in basis_vectors], - [basis.label.restore for basis in basis_vectors], - [FadeOut(coord) for coord in coord_mobs], - [FadeOut(sum_vect) for x in [1] if show_sum_vect], - )) - if show_sum_vect: - return sum_vect - -class RemindOfCoordinates(LinearCombinationScene): - CONFIG = { - "vector_coords" : [3, 2] - } - def construct(self): - self.remove(self.i_hat, self.j_hat) - - v = self.add_vector(self.vector_coords, color = V_COLOR) - coords = self.write_vector_coordinates(v) - self.show_standard_coord_meaning(*coords.get_entries().copy()) - self.show_abstract_scalar_idea(*coords.get_entries().copy()) - self.scale_basis_vectors(*coords.get_entries().copy()) - self.list_implicit_assumptions(*coords.get_entries()) - - - def show_standard_coord_meaning(self, x_coord, y_coord): - x, y = self.vector_coords - x_line = Line(ORIGIN, x*RIGHT, color = GREEN) - y_line = Line(ORIGIN, y*UP, color = RED) - y_line.shift(x_line.get_end()) - for line, coord, direction in (x_line, x_coord, DOWN), (y_line, y_coord, LEFT): - self.play( - coord.set_color, line.get_color(), - coord.next_to, line.get_center(), direction, - ShowCreation(line), - ) - self.wait() - self.wait() - self.play(*list(map(FadeOut, [x_coord, y_coord, x_line, y_line]))) - - - def show_abstract_scalar_idea(self, x_coord, y_coord): - x_shift, y_shift = 4*LEFT, 4*RIGHT - to_save = x_coord, y_coord, self.i_hat, self.j_hat - for mob in to_save: - mob.save_state() - everything = VGroup(*self.get_mobjects()) - words = TextMobject("Think of coordinates \\\\ as", "scalars") - words.set_color_by_tex("scalars", YELLOW) - words.to_edge(UP) - - x, y = self.vector_coords - scaled_i = self.i_hat.copy().scale(x) - scaled_j = self.j_hat.copy().scale(y) - VGroup(self.i_hat, scaled_i).shift(x_shift) - VGroup(self.j_hat, scaled_j).shift(y_shift) - - self.play( - FadeOut(everything), - x_coord.scale_in_place, 1.5, - x_coord.move_to, x_shift + 3*UP, - y_coord.scale_in_place, 1.5, - y_coord.move_to, y_shift + 3*UP, - Write(words) - ) - self.play(*list(map(FadeIn, [self.i_hat, self.j_hat]))) - self.wait() - self.play(Transform(self.i_hat, scaled_i)) - self.play(Transform(self.j_hat, scaled_j)) - self.wait() - self.play( - FadeOut(words), - FadeIn(everything), - *[mob.restore for mob in to_save] - ) - self.wait() - - def scale_basis_vectors(self, x_coord, y_coord): - self.play(*list(map(Write, [self.i_hat.label, self.j_hat.label]))) - self.show_linear_combination( - self.vector_coords, - basis_vectors = [self.i_hat, self.j_hat], - coord_mobs = [x_coord, y_coord] - ) - - def list_implicit_assumptions(self, x_coord, y_coord): - everything = VGroup(*self.get_mobjects()) - title = TextMobject("Implicit assumptions") - h_line = Line(title.get_left(), title.get_right()) - h_line.set_color(YELLOW) - h_line.next_to(title, DOWN) - title.add(h_line) - - ass1 = TextMobject("-First coordinate") - ass1 = VGroup(ass1, self.i_hat.copy()) - ass1.arrange(buff = MED_SMALL_BUFF) - - ass2 = TextMobject("-Second coordinate") - ass2 = VGroup(ass2, self.j_hat.copy()) - ass2.arrange(buff = MED_SMALL_BUFF) - - ass3 = TextMobject("-Unit of distance") - - group = VGroup(title, ass1, ass2, ass3) - group.arrange(DOWN, aligned_edge = LEFT, buff = MED_SMALL_BUFF) - group.to_corner(UP+LEFT) - # VGroup(*group[1:]).shift(0.5*DOWN) - for words in group: - words.add_to_back(BackgroundRectangle(words)) - - self.play(Write(title)) - self.wait() - self.play( - Write(ass1), - ApplyFunction( - lambda m : m.rotate_in_place(np.pi/6).set_color(X_COLOR), - x_coord, - rate_func = wiggle - ) - ) - self.wait() - self.play( - Write(ass2), - ApplyFunction( - lambda m : m.rotate_in_place(np.pi/6).set_color(Y_COLOR), - y_coord, - rate_func = wiggle - ) - ) - self.wait() - self.play(Write(ass3)) - self.wait(2) - keepers = VGroup(*[ - self.i_hat, self.j_hat, - self.i_hat.label, self.j_hat.label - ]) - self.play( - FadeOut(everything), - Animation(keepers.copy()), - Animation(group) - ) - self.wait() - -class NameCoordinateSystem(Scene): - def construct(self): - vector = Vector([3, 2]) - coords = Matrix([3, 2]) - arrow = TexMobject("\\Rightarrow") - vector.next_to(arrow, RIGHT, buff = 0) - coords.next_to(arrow, LEFT, buff = MED_LARGE_BUFF) - group = VGroup(coords, arrow, vector) - group.shift(2*UP) - coordinate_system = TextMobject("``Coordinate system''") - coordinate_system.next_to(arrow, UP, buff = LARGE_BUFF) - - i_hat, j_hat = Vector([1, 0]), Vector([0, 1]) - i_hat.set_color(X_COLOR) - j_hat.set_color(Y_COLOR) - i_label = TexMobject("\\hat{\\imath}") - i_label.set_color(X_COLOR) - i_label.next_to(i_hat, DOWN) - j_label = TexMobject("\\hat{\\jmath}") - j_label.set_color(Y_COLOR) - j_label.next_to(j_hat, LEFT) - basis_group = VGroup(i_hat, j_hat, i_label, j_label) - basis_group.shift(DOWN) - basis_words = TextMobject("``Basis vectors''") - basis_words.shift(basis_group.get_bottom()[1]*UP+MED_SMALL_BUFF*DOWN) - - self.play(Write(coords)) - self.play(Write(arrow), ShowCreation(vector)) - self.wait() - self.play(Write(coordinate_system)) - self.wait(2) - self.play(Write(basis_group)) - self.play(Write(basis_words)) - self.wait() - -class WhatAboutOtherBasis(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - \\centering What if we used - different basis vectors - """) - self.random_blink() - self.change_student_modes("pondering") - self.random_blink(2) - -class JenniferScene(LinearCombinationScene): - CONFIG = { - "b1_coords" : [2, 1], - "b2_coords" : [-1, 1], - "foreground_plane_kwargs" : { - "x_radius" : FRAME_X_RADIUS, - "y_radius" : FRAME_X_RADIUS, - }, - } - def setup(self): - LinearCombinationScene.setup(self) - self.remove(self.plane, self.i_hat, self.j_hat) - self.jenny = Jennifer() - self.you = You() - self.b1 = Vector(self.b1_coords, color = X_COLOR) - self.b2 = Vector(self.b2_coords, color = Y_COLOR) - for i, vect in enumerate([self.b1, self.b2]): - vect.label = TexMobject("\\vec{\\textbf{b}}_%d"%(i+1)) - vect.label.scale(0.7) - vect.label.add_background_rectangle() - vect.label.set_color(vect.get_color()) - self.b1.label.next_to( - self.b1.get_end()*0.4, UP+LEFT, SMALL_BUFF/2 - ) - self.b2.label.next_to( - self.b2.get_end(), DOWN+LEFT, buff = SMALL_BUFF - ) - self.basis_vectors = VGroup( - self.b1, self.b2, self.b1.label, self.b2.label - ) - - transform = self.get_matrix_transformation(self.cob_matrix().T) - self.jenny_plane = self.plane.copy() - self.jenny_plane.apply_function(transform) - - def cob_matrix(self): - return np.array([self.b1_coords, self.b2_coords]).T - - def inv_cob_matrix(self): - return np.linalg.inv(self.cob_matrix()) - -class IntroduceJennifer(JenniferScene): - CONFIG = { - "v_coords" : [3, 2] - } - def construct(self): - for plane in self.plane, self.jenny_plane: - plane.fade() - self.introduce_jenny() - self.add_basis_vectors() - self.show_v_from_both_perspectives() - self.how_we_label_her_basis() - - def introduce_jenny(self): - jenny = self.jenny - name = TextMobject("Jennifer") - name.next_to(jenny, UP) - name.shift_onto_screen() - - self.add(jenny) - self.play( - jenny.change_mode, "wave_1", - jenny.look, OUT, - Write(name) - ) - self.play( - jenny.change_mode, "happy", - jenny.look, UP+RIGHT, - FadeOut(name) - ) - self.wait() - - def add_basis_vectors(self): - words = TextMobject("Alternate basis vectors") - words.shift(2.5*UP) - self.play(Write(words, run_time = 2)) - for vect in self.b1, self.b2: - self.play( - ShowCreation(vect), - Write(vect.label) - ) - self.wait() - self.play(FadeOut(words)) - - def show_v_from_both_perspectives(self): - v = Vector(self.v_coords) - jenny = self.jenny - you = self.you - - you.coords = Matrix([3, 2]) - jenny.coords = Matrix(["(5/3)", "(1/3)"]) - for pi in you, jenny: - pi.bubble = get_small_bubble(pi) - pi.bubble.set_fill(BLACK, opacity = 0.7) - pi.bubble.add_content(pi.coords) - jenny.coords.scale_in_place(0.7) - - new_coords = [-1, 2] - new_coords_mob = Matrix(new_coords) - new_coords_mob.set_height(jenny.coords.get_height()) - new_coords_mob.move_to(jenny.coords) - - for coords in you.coords, jenny.coords, new_coords_mob: - for entry in coords.get_entries(): - entry.add_background_rectangle() - - self.play(ShowCreation(v)) - self.wait() - self.play(*it.chain( - list(map(FadeIn, [ - self.plane, self.i_hat, self.j_hat, - self.i_hat.label, self.j_hat.label, - you - ])), - list(map(Animation, [jenny, v])), - list(map(FadeOut, self.basis_vectors)), - )) - self.play( - ShowCreation(you.bubble), - Write(you.coords) - ) - self.play(you.change_mode, "speaking") - self.show_linear_combination( - self.v_coords, - basis_vectors = [self.i_hat, self.j_hat], - coord_mobs = you.coords.get_entries().copy(), - ) - self.play(*it.chain( - list(map(FadeOut, [ - self.plane, self.i_hat, self.j_hat, - self.i_hat.label, self.j_hat.label, - you.bubble, you.coords - ])), - list(map(FadeIn, [self.jenny_plane, self.basis_vectors])), - list(map(Animation, [v, you, jenny])), - )) - self.play( - ShowCreation(jenny.bubble), - Write(jenny.coords), - jenny.change_mode, "speaking", - ) - self.play(you.change_mode, "erm") - self.show_linear_combination( - np.dot(self.inv_cob_matrix(), self.v_coords), - basis_vectors = [self.b1, self.b2], - coord_mobs = jenny.coords.get_entries().copy(), - ) - self.play( - FadeOut(v), - jenny.change_mode, "plain" - ) - self.play( - Transform(jenny.coords, new_coords_mob), - Blink(jenny), - ) - self.hacked_show_linear_combination( - new_coords, - basis_vectors = [self.b1, self.b2], - coord_mobs = jenny.coords.get_entries().copy(), - show_sum_vect = True, - ) - - def hacked_show_linear_combination( - self, numerical_coords, - basis_vectors, - coord_mobs = None, - show_sum_vect = False, - sum_vect_color = V_COLOR, - ): - for coord, basis, scalar in zip(coord_mobs, basis_vectors, numerical_coords): - basis.save_state() - basis.label.save_state() - basis.target = basis.copy().scale(scalar) - basis.label.target = basis.label.copy() - coord.target = coord.copy() - new_label = VGroup(coord.target, basis.label.target) - new_label.arrange(aligned_edge = DOWN) - new_label.move_to( - basis.label, - aligned_edge = basis.get_center()-basis.label.get_center() - ) - new_label.shift( - basis.target.get_center() - basis.get_center() - ) - coord.target.next_to(basis.label.target, LEFT) - coord.target.set_fill(basis.get_color(), opacity = 1) - self.play(*list(map(MoveToTarget, [ - coord, basis, basis.label - ]))) - self.wait() - self.play(*[ - ApplyMethod(m.shift, basis_vectors[0].get_end()) - for m in self.get_mobjects_from_last_animation() - ]) - if show_sum_vect: - sum_vect = Vector( - basis_vectors[1].get_end(), - color = sum_vect_color - ) - self.play(ShowCreation(sum_vect)) - self.wait(2) - - - b1, b2 = basis_vectors - self.jenny_plane.save_state() - self.jenny.bubble.save_state() - - self.jenny.coords.target = self.jenny.coords.copy() - self.you.bubble.add_content(self.jenny.coords.target) - - x, y = numerical_coords - b1.target = self.i_hat.copy().scale(x) - b2.target = self.j_hat.copy().scale(y) - b2.target.shift(b1.target.get_end()) - new_label1 = VGroup(coord_mobs[0], b1.label) - new_label2 = VGroup(coord_mobs[1], b2.label) - new_label1.target = new_label1.copy().next_to(b1.target, DOWN) - new_label2.target = new_label2.copy().next_to(b2.target, LEFT) - i_sym = TexMobject("\\hat{\\imath}").add_background_rectangle() - j_sym = TexMobject("\\hat{\\jmath}").add_background_rectangle() - i_sym.set_color(X_COLOR).move_to(new_label1.target[1], aligned_edge = LEFT) - j_sym.set_color(Y_COLOR).move_to(new_label2.target[1], aligned_edge = LEFT) - Transform(new_label1.target[1], i_sym).update(1) - Transform(new_label2.target[1], j_sym).update(1) - sum_vect.target = Vector(numerical_coords) - self.play( - Transform(self.jenny_plane, self.plane), - Transform(self.jenny.bubble, self.you.bubble), - self.you.change_mode, "speaking", - self.jenny.change_mode, "erm", - *list(map(MoveToTarget, [ - self.jenny.coords, - b1, b2, new_label1, new_label2, sum_vect - ])) - ) - self.play(Blink(self.you)) - self.wait() - - self.play(*it.chain( - list(map(FadeOut, [ - self.jenny.bubble, self.jenny.coords, - coord_mobs, sum_vect - ])), - [ - ApplyMethod(pi.change_mode, "plain") - for pi in (self.jenny, self.you) - ], - [mob.restore for mob in (b1, b2, b1.label, b2.label)] - )) - self.jenny.bubble.restore() - - def how_we_label_her_basis(self): - you, jenny = self.you, self.jenny - b1_coords = Matrix(self.b1_coords) - b2_coords = Matrix(self.b2_coords) - for coords in b1_coords, b2_coords: - coords.add_to_back(BackgroundRectangle(coords)) - coords.scale(0.7) - coords.add_to_back(BackgroundRectangle(coords)) - you.bubble.add_content(coords) - coords.mover = coords.copy() - - self.play(jenny.change_mode, "erm") - self.play( - ShowCreation(you.bubble), - Write(b1_coords), - you.change_mode, "speaking" - ) - self.play( - b1_coords.mover.next_to, self.b1.get_end(), RIGHT, - b1_coords.mover.set_color, X_COLOR - ) - self.play(Blink(you)) - self.wait() - self.play(Transform(b1_coords, b2_coords)) - self.play( - b2_coords.mover.next_to, self.b2.get_end(), LEFT, - b2_coords.mover.set_color, Y_COLOR - ) - self.play(Blink(jenny)) - for coords, array in (b1_coords, [1, 0]), (b2_coords, [0, 1]): - mover = coords.mover - array_mob = Matrix(array) - array_mob.set_color(mover.get_color()) - array_mob.set_height(mover.get_height()) - array_mob.move_to(mover) - array_mob.add_to_back(BackgroundRectangle(array_mob)) - mover.target = array_mob - self.play( - self.jenny_plane.restore, - FadeOut(self.you.bubble), - FadeOut(b1_coords), - self.jenny.change_mode, "speaking", - self.you.change_mode, "confused", - *list(map(Animation, [ - self.basis_vectors, - b1_coords.mover, - b2_coords.mover, - ])) - ) - self.play(MoveToTarget(b1_coords.mover)) - self.play(MoveToTarget(b2_coords.mover)) - self.play(Blink(self.jenny)) - -class SpeakingDifferentLanguages(JenniferScene): - def construct(self): - jenny, you = self.jenny, self.you - title = TextMobject("Different languages") - title.to_edge(UP) - - vector = Vector([3, 2]) - vector.center().shift(DOWN) - you.coords = Matrix([3, 2]) - you.text = TextMobject("Looks to be") - jenny.coords = Matrix(["5/3", "1/3"]) - jenny.text = TextMobject("Non, c'est") - for pi in jenny, you: - pi.bubble = pi.get_bubble(SpeechBubble, width = 4.5, height = 3.5) - if pi is you: - pi.bubble.shift(MED_SMALL_BUFF*RIGHT) - else: - pi.coords.scale(0.8) - pi.bubble.shift(MED_SMALL_BUFF*LEFT) - pi.coords.next_to(pi.text, buff = MED_SMALL_BUFF) - pi.coords.add(pi.text) - pi.bubble.add_content(pi.coords) - - self.add(you, jenny) - self.play(Write(title)) - self.play( - ShowCreation(vector), - you.look_at, vector, - jenny.look_at, vector, - ) - for pi in you, jenny: - self.play( - pi.change_mode, "speaking" if pi is you else "sassy", - ShowCreation(pi.bubble), - Write(pi.coords) - ) - self.play(Blink(pi)) - self.wait() - -class ShowGrid(LinearTransformationScene): - CONFIG = { - "include_background_plane" : False, - } - def construct(self): - self.remove(self.i_hat, self.j_hat) - self.wait() - self.plane.prepare_for_nonlinear_transform() - self.plane.save_state() - self.play(Homotopy(plane_wave_homotopy, self.plane)) - self.play(self.plane.restore) - for vect in self.i_hat, self.j_hat: - self.play(ShowCreation(vect)) - self.wait() - -class GridIsAConstruct(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - \\centering - The grid is - just a construct - """) - self.change_student_modes(*["pondering"]*3) - self.random_blink(2) - -class SpaceHasNoGrid(LinearTransformationScene): - CONFIG = { - "include_background_plane" : False - } - def construct(self): - words = TextMobject("Space has no grid") - words.to_edge(UP) - self.play( - Write(words), - FadeOut(self.plane), - *list(map(Animation, [self.i_hat, self.j_hat])) - ) - self.wait() - -class JennysGrid(JenniferScene): - def construct(self): - self.add(self.jenny) - self.jenny.shift(3*RIGHT) - bubble = self.jenny.get_bubble(SpeechBubble, width = 4) - bubble.flip() - bubble.set_fill(BLACK, opacity = 0.8) - bubble.to_edge(LEFT) - bubble.write(""" - This grid is also - just a construct - """) - coords = [1.5, -3] - coords_mob = Matrix(coords) - coords_mob.add_background_to_entries() - bubble.position_mobject_inside(coords_mob) - - for vect in self.b1, self.b2: - self.play( - ShowCreation(vect), - Write(vect.label) - ) - self.wait() - self.play( - ShowCreation( - self.jenny_plane, - run_time = 3, - lag_ratio = 0.5 - ), - self.jenny.change_mode, "speaking", - self.jenny.look_at, ORIGIN, - ShowCreation(bubble), - Write(bubble.content), - Animation(self.basis_vectors) - ) - self.play(Blink(self.jenny)) - self.play( - FadeOut(bubble.content), - FadeIn(coords_mob) - ) - self.show_linear_combination( - numerical_coords = coords, - basis_vectors = [self.b1, self.b2], - coord_mobs = coords_mob.get_entries().copy(), - show_sum_vect = True - ) - -class ShowOriginOfGrid(JenniferScene): - def construct(self): - for plane in self.plane, self.jenny_plane: - plane.fade(0.3) - self.add(self.jenny_plane) - self.jenny_plane.save_state() - - origin_word = TextMobject("Origin") - origin_word.shift(2*RIGHT+2.5*UP) - origin_word.add_background_rectangle() - arrow = Arrow(origin_word, ORIGIN, color = RED) - origin_dot = Dot(ORIGIN, radius = 0.1, color = RED) - coords = Matrix([0, 0]) - coords.add_to_back(BackgroundRectangle(coords)) - coords.next_to(ORIGIN, DOWN+LEFT) - vector = Vector([3, -2], color = PINK) - - self.play( - Write(origin_word), - ShowCreation(arrow) - ) - self.play(ShowCreation(origin_dot)) - self.wait() - self.play( - Transform(self.jenny_plane, self.plane), - *list(map(Animation, [origin_word, origin_dot, arrow])) - ) - self.wait() - self.play(Write(coords)) - self.wait() - self.play(FadeIn(vector)) - self.wait() - self.play(Transform(vector, Mobject.scale(vector.copy(), 0))) - self.wait() - self.play( - self.jenny_plane.restore, - *list(map(Animation, [origin_word, origin_dot, arrow, coords])) - ) - for vect in self.b1, self.b2: - self.play( - ShowCreation(vect), - Write(vect.label) - ) - self.wait() - -class AskAboutTranslation(TeacherStudentsScene): - def construct(self): - self.student_says( - "\\centering How do you translate \\\\ between coordinate systems?", - target_mode = "raise_right_hand" - ) - self.random_blink(3) - -class TranslateFromJenny(JenniferScene): - CONFIG = { - "coords" : [-1, 2] - } - def construct(self): - self.add_players() - self.ask_question() - self.establish_coordinates() - self.perform_arithmetic() - - def add_players(self): - for plane in self.jenny_plane, self.plane: - plane.fade() - self.add( - self.jenny_plane, - self.jenny, self.you, - self.basis_vectors - ) - self.jenny.coords = Matrix(self.coords) - self.you.coords = Matrix(["?", "?"]) - self.you.coords.get_entries().set_color_by_gradient(X_COLOR, Y_COLOR) - for pi in self.jenny, self.you: - pi.bubble = get_small_bubble(pi) - pi.bubble.set_fill(BLACK, opacity = 0.8) - pi.coords.scale(0.8) - pi.coords.add_background_to_entries() - pi.bubble.add_content(pi.coords) - - def ask_question(self): - self.play( - self.jenny.change_mode, "pondering", - ShowCreation(self.jenny.bubble), - Write(self.jenny.coords) - ) - coord_mobs = self.jenny.coords.get_entries().copy() - self.basis_vectors_copy = self.basis_vectors.copy() - self.basis_vectors_copy.fade(0.3) - self.add(self.basis_vectors_copy, self.basis_vectors) - sum_vect = self.show_linear_combination( - numerical_coords = self.coords, - basis_vectors = [self.b1, self.b2], - coord_mobs = coord_mobs, - revert_to_original = False, - show_sum_vect = True, - ) - self.wait() - everything = self.get_mobjects() - for submob in self.jenny_plane.get_family(): - everything.remove(submob) - self.play( - Transform(self.jenny_plane, self.plane), - *list(map(Animation, everything)) - ) - self.play( - self.you.change_mode, "confused", - ShowCreation(self.you.bubble), - Write(self.you.coords) - ) - self.wait() - - def establish_coordinates(self): - b1, b2 = self.basis_vectors_copy[:2] - b1_coords = Matrix(self.b1_coords).set_color(X_COLOR) - b2_coords = Matrix(self.b2_coords).set_color(Y_COLOR) - for coords in b1_coords, b2_coords: - coords.scale(0.7) - coords.add_to_back(BackgroundRectangle(coords)) - b1_coords.next_to(b1.get_end(), RIGHT) - b2_coords.next_to(b2.get_end(), UP) - - for coords in b1_coords, b2_coords: - self.play(Write(coords)) - self.b1_coords_mob, self.b2_coords_mob = b1_coords, b2_coords - - def perform_arithmetic(self): - jenny_x, jenny_y = self.jenny.coords.get_entries().copy() - equals, plus, equals2 = syms = list(map(TexMobject, list("=+="))) - result = Matrix([-4, 1]) - result.set_height(self.you.coords.get_height()) - for mob in syms + [self.you.coords, self.jenny.coords, result]: - mob.add_to_back(BackgroundRectangle(mob)) - movers = [ - self.you.coords, equals, - jenny_x, self.b1_coords_mob, plus, - jenny_y, self.b2_coords_mob, - equals2, result - ] - for mover in movers: - mover.target = mover.copy() - mover_targets = VGroup(*[mover.target for mover in movers]) - mover_targets.arrange() - mover_targets.to_edge(UP) - for mob in syms + [result]: - mob.move_to(mob.target) - mob.set_fill(BLACK, opacity = 0) - - mover_sets = [ - [jenny_x, self.b1_coords_mob], - [plus, jenny_y, self.b2_coords_mob], - [self.you.coords, equals], - ] - for mover_set in mover_sets: - self.play(*list(map(MoveToTarget, mover_set))) - self.wait() - self.play( - MoveToTarget(equals2), - Transform(self.b1_coords_mob.copy(), result.target), - Transform(self.b2_coords_mob.copy(), result.target), - ) - self.remove(*self.get_mobjects_from_last_animation()) - result = result.target - self.add(equals2, result) - self.wait() - - result_copy = result.copy() - self.you.bubble.add_content(result_copy) - self.play( - self.you.change_mode, "hooray", - Transform(result.copy(), result_copy) - ) - self.play(Blink(self.you)) - self.wait() - - matrix = Matrix(np.array([self.b1_coords, self.b2_coords]).T) - matrix.set_column_colors(X_COLOR, Y_COLOR) - self.jenny.coords.target = self.jenny.coords.copy() - self.jenny.coords.target.next_to(equals, LEFT) - matrix.set_height(self.jenny.coords.get_height()) - matrix.next_to(self.jenny.coords.target, LEFT) - matrix.add_to_back(BackgroundRectangle(matrix)) - - self.play( - FadeOut(self.jenny.bubble), - FadeOut(self.you.coords), - self.jenny.change_mode, "plain", - MoveToTarget(self.jenny.coords), - FadeIn(matrix) - ) - self.wait() - -class WatchChapter3(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - You've all watched - chapter 3, right? - """) - self.random_blink() - self.play( - self.get_students()[0].look, LEFT, - self.get_students()[1].change_mode, "happy", - self.get_students()[2].change_mode, "happy", - ) - self.random_blink(2) - -class TalkThroughChangeOfBasisMatrix(JenniferScene): - def construct(self): - self.add(self.plane, self.jenny, self.you) - self.plane.fade() - self.jenny_plane.fade() - for pi in self.jenny, self.you: - pi.bubble = get_small_bubble(pi) - - matrix = Matrix(np.array([self.b1_coords, self.b2_coords]).T) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.next_to(ORIGIN, RIGHT, buff = MED_SMALL_BUFF).to_edge(UP) - - b1_coords = Matrix(self.b1_coords) - b1_coords.set_color(X_COLOR) - b1_coords.next_to(self.b1.get_end(), RIGHT) - b2_coords = Matrix(self.b2_coords) - b2_coords.set_color(Y_COLOR) - b2_coords.next_to(self.b2.get_end(), UP) - for coords in b1_coords, b2_coords: - coords.scale_in_place(0.7) - - basis_coords_pair = VGroup( - Matrix([1, 0]).set_color(X_COLOR).scale(0.7), - TexMobject(","), - Matrix([0, 1]).set_color(Y_COLOR).scale(0.7), - ) - basis_coords_pair.arrange(aligned_edge = DOWN) - self.you.bubble.add_content(basis_coords_pair) - - t_matrix1 = np.array([self.b1_coords, [0, 1]]) - t_matrix2 = np.dot( - self.cob_matrix(), - np.linalg.inv(t_matrix1.T) - ).T - - for mob in matrix, b1_coords, b2_coords: - mob.rect = BackgroundRectangle(mob) - mob.add_to_back(mob.rect) - - self.play(Write(matrix)) - for vect in self.i_hat, self.j_hat: - self.play( - ShowCreation(vect), - Write(vect.label) - ) - self.play( - self.you.change_mode, "pondering", - ShowCreation(self.you.bubble), - Write(basis_coords_pair) - ) - self.play(Blink(self.you)) - self.wait() - - self.add_foreground_mobject( - self.jenny, self.you, self.you.bubble, - basis_coords_pair, matrix - ) - matrix_copy = matrix.copy() - matrix_copy.rect.set_fill(opacity = 0) - self.apply_transposed_matrix( - t_matrix1, - added_anims = [ - Transform(self.i_hat, self.b1), - Transform(self.i_hat.label, self.b1.label), - Transform(matrix_copy.rect, b1_coords.rect), - Transform( - matrix_copy.get_brackets(), - b1_coords.get_brackets(), - ), - Transform( - VGroup(*matrix_copy.get_mob_matrix()[:,0]), - b1_coords.get_entries() - ), - ] - ) - self.remove(matrix_copy) - self.add_foreground_mobject(b1_coords) - matrix_copy = matrix.copy() - matrix_copy.rect.set_fill(opacity = 0) - self.apply_transposed_matrix( - t_matrix2, - added_anims = [ - Transform(self.j_hat, self.b2), - Transform(self.j_hat.label, self.b2.label), - Transform(matrix_copy.rect, b2_coords.rect), - Transform( - matrix_copy.get_brackets(), - b2_coords.get_brackets(), - ), - Transform( - VGroup(*matrix_copy.get_mob_matrix()[:,1]), - b2_coords.get_entries() - ), - ] - ) - self.remove(matrix_copy) - self.add_foreground_mobject(b2_coords) - basis_coords_pair.target = basis_coords_pair.copy() - self.jenny.bubble.add_content(basis_coords_pair.target) - self.wait() - self.play( - FadeOut(b1_coords), - FadeOut(b2_coords), - self.jenny.change_mode, "speaking", - Transform(self.you.bubble, self.jenny.bubble), - MoveToTarget(basis_coords_pair), - ) - -class ChangeOfBasisExample(JenniferScene): - CONFIG = { - "v_coords" : [-1, 2] - } - def construct(self): - self.add( - self.plane, self.i_hat, self.j_hat, - self.i_hat.label, self.j_hat.label, - ) - self.j_hat.label.next_to(self.j_hat, RIGHT) - v = self.add_vector(self.v_coords) - v_coords = Matrix(self.v_coords) - v_coords.scale(0.8) - v_coords.add_to_back(BackgroundRectangle(v_coords)) - v_coords.to_corner(UP+LEFT) - v_coords.add_background_to_entries() - for pi in self.you, self.jenny: - pi.change_mode("pondering") - pi.bubble = get_small_bubble(pi) - pi.bubble.add_content(v_coords.copy()) - pi.add(pi.bubble, pi.bubble.content) - - start_words = TextMobject("How", "we", "think of") - start_words.add_background_rectangle() - start_group = VGroup(start_words, v_coords) - start_group.arrange(buff = MED_SMALL_BUFF) - start_group.next_to(self.you, LEFT, buff = 0) - start_group.to_edge(UP) - end_words = TextMobject("How", "Jennifer", "thinks of") - end_words.add_background_rectangle() - end_words.move_to(start_words, aligned_edge = RIGHT) - - - self.play( - Write(start_group), - FadeIn(self.you), - ) - self.add_foreground_mobject(start_group, self.you) - - self.show_linear_combination( - numerical_coords = self.v_coords, - basis_vectors = [self.i_hat, self.j_hat], - coord_mobs = v_coords.get_entries().copy(), - ) - self.play(*list(map(FadeOut, [self.i_hat.label, self.j_hat.label]))) - self.apply_transposed_matrix(self.cob_matrix().T) - VGroup(self.i_hat, self.j_hat).fade() - self.add(self.b1, self.b2) - self.play( - Transform(start_words, end_words), - Transform(self.you, self.jenny), - *list(map(Write, [self.b1.label, self.b2.label])) - ) - self.play(Blink(self.you)) - self.show_linear_combination( - numerical_coords = self.v_coords, - basis_vectors = [self.b1, self.b2], - coord_mobs = v_coords.get_entries().copy(), - ) - -class FeelsBackwards(Scene): - def construct(self): - matrix = Matrix(np.array([ - JenniferScene.CONFIG["b1_coords"], - JenniferScene.CONFIG["b2_coords"], - ]).T) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.shift(UP) - top_arrow = Arrow(matrix.get_left(), matrix.get_right()) - bottom_arrow = top_arrow.copy().rotate(np.pi) - top_arrow.next_to(matrix, UP, buff = LARGE_BUFF) - bottom_arrow.next_to(matrix, DOWN, buff = LARGE_BUFF) - top_arrow.set_color(BLUE) - - jenny_grid = TextMobject("Jennifer's grid").set_color(BLUE) - our_grid = TextMobject("Our grid").set_color(BLUE) - jenny_language = TextMobject("Jennifer's language") - our_language = TextMobject("Our language") - - our_grid.next_to(top_arrow, LEFT) - jenny_grid.next_to(top_arrow, RIGHT) - jenny_language.next_to(bottom_arrow, RIGHT) - our_language.next_to(bottom_arrow, LEFT) - - self.add(matrix) - self.play(Write(our_grid)) - self.play( - ShowCreation(top_arrow), - Write(jenny_grid) - ) - self.wait() - self.play(Write(jenny_language)) - self.play( - ShowCreation(bottom_arrow), - Write(our_language) - ) - self.wait() - - ##Swap things - inverse_word = TextMobject("Inverse") - inverse_word.next_to(matrix, LEFT, buff = MED_SMALL_BUFF) - inverse_exponent = TexMobject("-1") - inverse_exponent.next_to(matrix.get_corner(UP+RIGHT), RIGHT) - self.play(*list(map(Write, [inverse_word, inverse_exponent]))) - self.play( - Swap(jenny_grid, our_grid), - top_arrow.scale_in_place, 0.8, - top_arrow.shift, 0.8*RIGHT, - top_arrow.set_color, BLUE, - ) - self.play( - Swap(jenny_language, our_language), - bottom_arrow.scale_in_place, 0.8, - bottom_arrow.shift, 0.8*RIGHT - ) - self.wait() - -class AskAboutOtherWayAround(TeacherStudentsScene): - def construct(self): - self.student_says(""" - What about the - other way around? - """) - self.random_blink(3) - -class RecallInverse(JenniferScene): - def construct(self): - numerical_t_matrix = np.array([self.b1_coords, self.b2_coords]) - matrix = Matrix(numerical_t_matrix.T) - matrix.add_to_back(BackgroundRectangle(matrix)) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.to_corner(UP+LEFT, buff = MED_LARGE_BUFF) - # matrix.shift(MED_SMALL_BUFF*DOWN) - inverse_exponent = TexMobject("-1") - inverse_exponent.next_to(matrix.get_corner(UP+RIGHT), RIGHT) - inverse_exponent.add_background_rectangle() - brace = Brace(VGroup(matrix, inverse_exponent)) - inverse_word = brace.get_text("Inverse") - inverse_word.add_background_rectangle() - - equals = TexMobject("=") - equals.add_background_rectangle() - inv_matrix = Matrix([ - ["1/3", "1/3"], - ["-1/3", "2/3"] - ]) - inv_matrix.set_height(matrix.get_height()) - inv_matrix.add_to_back(BackgroundRectangle(inv_matrix)) - equals.next_to(matrix, RIGHT, buff = 0.7) - inv_matrix.next_to(equals, RIGHT, buff = MED_SMALL_BUFF) - - self.add_foreground_mobject(matrix) - self.apply_transposed_matrix(numerical_t_matrix) - self.play( - GrowFromCenter(brace), - Write(inverse_word), - Write(inverse_exponent) - ) - self.add_foreground_mobject(*self.get_mobjects_from_last_animation()) - self.wait() - self.apply_inverse_transpose(numerical_t_matrix) - self.wait() - self.play( - Write(equals), - Transform(matrix.copy(), inv_matrix) - ) - self.remove(*self.get_mobjects_from_last_animation()) - self.add_foreground_mobject(equals, inv_matrix) - self.wait() - for mob in self.plane, self.i_hat, self.j_hat: - self.add(mob.copy().fade(0.7)) - self.apply_transposed_matrix(numerical_t_matrix) - self.play(FadeIn(self.jenny)) - self.play(self.jenny.change_mode, "speaking") - #Little hacky now - inv_matrix.set_column_colors(X_COLOR) - self.play(*[ - ApplyMethod( - mob.scale_in_place, 1.2, - rate_func = there_and_back - ) - for mob in inv_matrix.get_mob_matrix()[:,0] - ]) - self.wait() - inv_matrix.set_column_colors(X_COLOR, Y_COLOR) - self.play(*[ - ApplyMethod( - mob.scale_in_place, 1.2, - rate_func = there_and_back - ) - for mob in inv_matrix.get_mob_matrix()[:,1] - ]) - self.wait() - -class WorkOutInverseComputation(Scene): - def construct(self): - our_vector = Matrix([3, 2]) - her_vector = Matrix(["5/3", "1/3"]) - matrix = Matrix([["1/3", "1/3"], ["-1/3", "2/3"]]) - our_vector.set_color(BLUE_D) - her_vector.set_color(MAROON_B) - equals = TexMobject("=") - equation = VGroup( - matrix, our_vector, equals, her_vector - ) - for mob in equation: - if isinstance(mob, Matrix): - mob.set_height(2) - equation.arrange() - - matrix_brace = Brace(matrix, UP) - our_vector_brace = Brace(our_vector) - her_vector_brace = Brace(her_vector, UP) - matrix_text = matrix_brace.get_text(""" - \\centering - Inverse - change of basis - matrix - """) - our_text = our_vector_brace.get_text(""" - \\centering - Written in - our language - """) - our_text.set_color(our_vector.get_color()) - her_text = her_vector_brace.get_text(""" - \\centering - Same vector - in her language - """) - her_text.set_color(her_vector.get_color()) - for text in our_text, her_text: - text.scale_in_place(0.7) - - self.add(our_vector) - self.play( - GrowFromCenter(our_vector_brace), - Write(our_text) - ) - self.wait() - self.play( - FadeIn(matrix), - GrowFromCenter(matrix_brace), - Write(matrix_text) - ) - self.wait() - self.play( - Write(equals), - Write(her_vector) - ) - self.play( - GrowFromCenter(her_vector_brace), - Write(her_text) - ) - self.wait() - -class SoThatsTranslation(TeacherStudentsScene): - def construct(self): - self.teacher_says("So that's translation") - self.random_blink(3) - -class SummarizeTranslationProcess(Scene): - def construct(self): - self.define_matrix() - self.show_translation() - - def define_matrix(self): - matrix = Matrix([[2, -1], [1, 1]]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - A, equals = list(map(TexMobject, list("A="))) - equation = VGroup(A, equals, matrix) - equation.arrange() - equation.to_corner(UP+LEFT) - equation.shift(RIGHT) - words = TextMobject(""" - Jennifer's basis vectors, - written in our coordinates - """) - words.to_edge(LEFT) - mob_matrix = matrix.get_mob_matrix() - arrow1 = Arrow(words, mob_matrix[1, 0], color = X_COLOR) - arrow2 = Arrow(words, mob_matrix[1, 1], color = Y_COLOR) - - self.add(A, equals, matrix) - self.play( - Write(words), - *list(map(ShowCreation, [arrow1, arrow2])) - ) - self.A_copy = A.copy() - - def show_translation(self): - our_vector = Matrix(["x_o", "y_o"]) - her_vector = Matrix(["x_j", "y_j"]) - for vector, color in (our_vector, BLUE_D), (her_vector, MAROON_B): - # vector.set_height(1.5) - vector.set_color(color) - A = TexMobject("A") - A_inv = TexMobject("A^{-1}") - equals = TexMobject("=") - - equation = VGroup(A, her_vector, equals, our_vector) - equation.arrange() - equation.to_edge(RIGHT) - equation.shift(0.5*UP) - A_inv.next_to(our_vector, LEFT) - - her_words = TextMobject("Vector in her coordinates") - her_words.set_color(her_vector.get_color()) - her_words.scale(0.8).to_corner(UP+RIGHT) - her_arrow = Arrow( - her_words, her_vector, - color = her_vector.get_color() - ) - our_words = TextMobject("Same vector in\\\\ our coordinates") - our_words.set_color(our_vector.get_color()) - our_words.scale(0.8).to_edge(RIGHT).shift(2*DOWN) - our_words.shift_onto_screen() - our_arrow = Arrow( - our_words.get_top(), our_vector.get_bottom(), - color = our_vector.get_color() - ) - - self.play( - Write(equation), - Transform(self.A_copy, A) - ) - self.remove(self.A_copy) - self.play( - Write(her_words), - ShowCreation(her_arrow) - ) - self.play( - Write(our_words), - ShowCreation(our_arrow) - ) - self.wait(2) - self.play( - VGroup(her_vector, equals).next_to, A_inv, LEFT, - her_arrow.rotate_in_place, -np.pi/6, - her_arrow.shift, MED_SMALL_BUFF*LEFT, - Transform(A, A_inv, path_arc = np.pi) - ) - self.wait() - -class VectorsAreNotTheOnlyOnes(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - \\centering - Vectors aren't the - only thing with coordinates - """) - self.change_student_modes("pondering", "confused", "erm") - self.random_blink(3) - -class Prerequisites(Scene): - def construct(self): - title = TextMobject("Prerequisites") - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - - self.add(title, h_line) - prereqs = list(map(TextMobject, [ - "Linear transformations", - "Matrix multiplication", - ])) - for direction, words in zip([LEFT, RIGHT], prereqs): - rect = Rectangle(height = 9, width = 16) - rect.set_height(3.5) - rect.next_to(ORIGIN, direction, buff = MED_SMALL_BUFF) - rect.set_color(BLUE) - words.next_to(rect, UP, buff = MED_SMALL_BUFF) - self.play( - Write(words), - ShowCreation(rect) - ) - self.wait() - -class RotationExample(LinearTransformationScene): - CONFIG = { - "t_matrix" : [[0, 1], [-1, 0]] - } - def construct(self): - words = TextMobject("$90^\\circ$ rotation") - words.scale(1.2) - words.add_background_rectangle() - words.to_edge(UP) - - matrix = Matrix(self.t_matrix.T) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.rect = BackgroundRectangle(matrix) - matrix.add_to_back(matrix.rect) - matrix.next_to(words, DOWN) - matrix.shift(2*RIGHT) - - self.play(Write(words)) - self.add_foreground_mobject(words) - self.wait() - self.apply_transposed_matrix(self.t_matrix) - self.wait() - self.play( - self.i_hat.rotate, np.pi/12, - self.j_hat.rotate, -np.pi/12, - rate_func = wiggle, - run_time = 2 - ) - self.wait() - - i_coords, j_coords = coord_arrays = list(map(Matrix, self.t_matrix)) - for coords, vect in zip(coord_arrays, [self.i_hat, self.j_hat]): - coords.scale(0.7) - coords.rect = BackgroundRectangle(coords) - coords.add_to_back(coords.rect) - coords.set_color(vect.get_color()) - direction = UP if vect is self.j_hat else RIGHT - coords.next_to(vect.get_end(), direction, buff = MED_SMALL_BUFF) - self.play(Write(coords)) - self.wait() - - self.play( - Transform(i_coords.rect, matrix.rect), - Transform(i_coords.get_brackets(), matrix.get_brackets()), - Transform( - i_coords.get_entries(), - VGroup(*matrix.get_mob_matrix()[:, 0]) - ), - ) - self.play( - FadeOut(j_coords.rect), - FadeOut(j_coords.get_brackets()), - Transform( - j_coords.get_entries(), - VGroup(*matrix.get_mob_matrix()[:, 1]) - ), - ) - self.wait() - self.add_words(matrix) - - def add_words(self, matrix): - follow_basis = TextMobject( - "Follow", "our choice", - "\\\\ of basis vectors" - ) - follow_basis.set_color_by_tex("our choice", YELLOW) - follow_basis.add_background_rectangle() - follow_basis.next_to( - matrix, LEFT, - buff = MED_SMALL_BUFF, - ) - - record = TextMobject( - "Record using \\\\", - "our coordinates" - ) - record.set_color_by_tex("our coordinates", YELLOW) - record.add_background_rectangle() - record.next_to( - matrix, DOWN, - buff = MED_SMALL_BUFF, - aligned_edge = LEFT - ) - - self.play(Write(follow_basis)) - self.wait() - self.play(Write(record)) - self.wait() - -class JennyWatchesRotation(JenniferScene): - def construct(self): - jenny = self.jenny - self.add(self.jenny_plane.copy().fade()) - self.add(self.jenny_plane) - self.add(jenny) - for vect in self.b1, self.b2: - self.add_vector(vect) - - matrix = Matrix([["?", "?"], ["?", "?"]]) - matrix.get_entries().set_color_by_gradient(X_COLOR, Y_COLOR) - jenny.bubble = get_small_bubble(jenny) - jenny.bubble.add_content(matrix) - matrix.scale_in_place(0.8) - - self.play( - jenny.change_mode, "sassy", - ShowCreation(jenny.bubble), - Write(matrix) - ) - self.play(*it.chain( - [ - Rotate(mob, np.pi/2, run_time = 3) - for mob in (self.jenny_plane, self.b1, self.b2) - ], - list(map(Animation, [jenny, jenny.bubble, matrix])) - )) - self.play(jenny.change_mode, "pondering") - self.play(Blink(jenny)) - self.wait() - -class AksAboutTranslatingColumns(TeacherStudentsScene): - def construct(self): - matrix = Matrix([[0, -1], [1, 0]]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.scale(0.7) - words = TextMobject("Translate columns of") - matrix.next_to(words, DOWN) - words.add(matrix) - self.student_says(words, student_index = 0) - self.random_blink(2) - - student = self.get_students()[0] - bubble = get_small_bubble(student) - bubble.set_fill(opacity = 0) - matrix.target = matrix.copy() - bubble.add_content(matrix.target) - self.play( - Transform(student.bubble, bubble), - FadeOut(student.bubble.content), - MoveToTarget(matrix.copy()), - student.change_mode, "pondering", - ) - self.remove(student.bubble) - student.bubble = None - self.add(bubble, matrix.target) - - self.random_blink() - words = TextMobject( - "\\centering Those columns still \\\\ represent ", - "our basis", ", not ", "hers", - arg_separator = "" - ) - words.set_color_by_tex("our basis", BLUE) - words.set_color_by_tex("hers", MAROON_B) - self.teacher_says(words) - self.change_student_modes("erm", "pondering", "pondering") - self.random_blink() - -class HowToTranslateAMatrix(Scene): - def construct(self): - self.add_title() - - arrays = VGroup(*list(map(Matrix, [ - [["1/3", "-2/3"], ["5/3", "-1/3"]], - [-1, 2], - [[2, -1], [1, 1]], - [[0, -1], [1, 0]], - [[2, -1], [1, 1]], - ]))) - result, her_vector, cob_matrix, transform, inv_cob = arrays - neg_1 = TexMobject("-1") - neg_1.next_to(inv_cob.get_corner(UP+RIGHT), RIGHT) - inv_cob.add(neg_1) - arrays.arrange(LEFT) - arrays.to_edge(LEFT, buff = LARGE_BUFF/2.) - for array in arrays: - array.brace = Brace(array) - array.top_brace = Brace(VGroup(array, her_vector), UP) - for array in cob_matrix, inv_cob: - submobs = array.split() - submobs.sort(key=lambda m: m.get_center()[0]) - array.submobjects = submobs - her_vector.set_color(MAROON_B) - cob_matrix.set_color_by_gradient(BLUE, MAROON_B) - transform.set_column_colors(X_COLOR, Y_COLOR) - transform.get_brackets().set_color(BLUE) - inv_cob.set_color_by_gradient(MAROON_B, BLUE) - result.set_column_colors(X_COLOR, Y_COLOR) - result.get_brackets().set_color(MAROON_B) - - final_top_brace = Brace(VGroup(cob_matrix, inv_cob), UP) - - brace_text_pairs = [ - (her_vector.brace, ("Vector in \\\\", "Jennifer's language")), - (her_vector.top_brace, ("",)), - (cob_matrix.brace, ("Change of basis \\\\", "matrix")), - (cob_matrix.top_brace, ("Same vector \\\\", "in", "our", "language")), - (transform.brace, ("Transformation matrix \\\\", "in", "our", "language")), - (transform.top_brace, ("Transformed vector \\\\", "in", "our", "language")), - (inv_cob.brace, ("Inverse \\\\", "change of basis \\\\", "matrix")), - (inv_cob.top_brace, ("Transformed vector \\\\", "in", "her", "language")), - (final_top_brace, ("Transformation matrix \\\\", "in", "her", "language")) - ] - for brace, text_args in brace_text_pairs: - text_args = list(text_args) - text_args[0] = "\\centering " + text_args[0] - text = TextMobject(*text_args) - text.set_color_by_tex("our", BLUE) - text.set_color_by_tex("her", MAROON_B) - brace.put_at_tip(text) - brace.text = text - - brace = her_vector.brace - bottom_words = her_vector.brace.text - top_brace = cob_matrix.top_brace - top_words = cob_matrix.top_brace.text - def introduce(array): - self.play( - Write(array), - Transform(brace, array.brace), - Transform(bottom_words, array.brace.text) - ) - self.wait() - def echo_introduce(array): - self.play( - Transform(top_brace, array.top_brace), - Transform(top_words, array.top_brace.text) - ) - self.wait() - - self.play(Write(her_vector)) - self.play( - GrowFromCenter(brace), - Write(bottom_words) - ) - self.wait() - introduce(cob_matrix) - self.play( - GrowFromCenter(top_brace), - Write(top_words) - ) - self.wait() - introduce(transform) - echo_introduce(transform) - introduce(inv_cob), - echo_introduce(inv_cob) - - #Genearlize to single matrix - v = TexMobject("\\vec{\\textbf{v}}") - v.set_color(her_vector.get_color()) - v.move_to(her_vector, aligned_edge = LEFT) - self.play( - Transform(her_vector, v), - FadeOut(bottom_words), - FadeOut(brace), - ) - self.wait() - self.play( - Transform(top_brace, final_top_brace), - Transform(top_brace.text, final_top_brace.text), - ) - self.wait() - - equals = TexMobject("=") - equals.replace(v) - result.next_to(equals, RIGHT) - self.play( - Transform(her_vector, equals), - Write(result) - ) - self.wait(2) - - everything = VGroup(*self.get_mobjects()) - self.play( - FadeOut(everything), - result.to_corner, UP+LEFT - ) - self.add(result) - self.wait() - - - def add_title(self): - title = TextMobject("How to translate a matrix") - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - self.add(title) - self.play(ShowCreation(h_line)) - self.wait() - -class JennyWatchesRotationWithMatrixAndVector(JenniferScene): - def construct(self): - self.add(self.jenny_plane.copy().fade(0.8)) - self.add(self.jenny_plane, self.jenny, self.b1, self.b2) - - matrix = Matrix([["1/3", "-2/3"], ["5/3", "-1/3"]]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - matrix.to_corner(UP+LEFT) - - vector_coords = [1, 2] - vector_array = Matrix(vector_coords) - vector_array.set_color(YELLOW) - vector_array.next_to(matrix, RIGHT) - - result = Matrix([-1, 1]) - equals = TexMobject("=") - equals.next_to(vector_array) - result.next_to(equals) - - for array in matrix, vector_array, result: - array.add_to_back(BackgroundRectangle(array)) - - vector = Vector(np.dot(self.cob_matrix(), vector_coords)) - - self.add(matrix) - self.play(Write(vector_array)) - self.play(ShowCreation(vector)) - self.play(Blink(self.jenny)) - self.play(*it.chain( - [ - Rotate(mob, np.pi/2, run_time = 3) - for mob in (self.jenny_plane, self.b1, self.b2, vector) - ], - list(map(Animation, [self.jenny, matrix, vector_array])), - )) - self.play( - self.jenny.change_mode, "pondering", - Write(equals), - Write(result) - ) - self.play(Blink(self.jenny)) - self.wait() - -class MathematicalEmpathy(TeacherStudentsScene): - def construct(self): - words = TextMobject( - "\\centering An expression like", - "$A^{-1} M A$", - "\\\\ suggests a mathematical \\\\", - "sort of empathy" - ) - A1, neg, one, M, A2 = words[1] - As = VGroup(A1, neg, one, A2) - VGroup(As, M).set_color(YELLOW) - - self.teacher_says(words) - self.random_blink() - for mob, color in (M, BLUE), (As, MAROON_B): - self.play(mob.set_color, color) - self.play(mob.scale_in_place, 1.2, rate_func = there_and_back) - self.random_blink(2) - -class NextVideo(Scene): - def construct(self): - title = TextMobject(""" - Next video: Eigenvectors and eigenvalues - """) - title.to_edge(UP, buff = MED_SMALL_BUFF) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/footnote.py b/from_3b1b/old/eola/footnote.py deleted file mode 100644 index 765f8b99..00000000 --- a/from_3b1b/old/eola/footnote.py +++ /dev/null @@ -1,429 +0,0 @@ -from manimlib.imports import * -from functools import reduce - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject([ - "Lisa:", - "Well, where's my dad?\\\\ \\\\", - "Frink:", - """Well, it should be obvious to even the most - dimwitted individual who holds an advanced degree - in hyperbolic topology that Homer Simpson has stumbled - into...(dramatic pause)...""", - "the third dimension." - ]) - words.set_width(FRAME_WIDTH - 2) - words.to_edge(UP) - words.split()[0].set_color(YELLOW) - words.split()[2].set_color(YELLOW) - - three_d = words.submobjects.pop() - three_d.set_color(BLUE) - self.play(FadeIn(words)) - self.play(Write(three_d)) - self.wait(2) - -class QuickFootnote(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says("Quick footnote here...") - self.random_blink() - self.play( - random.choice(self.get_students()).change_mode, "happy" - ) - self.random_blink() - -class PeakOutsideFlatland(TeacherStudentsScene): - def construct(self): - self.setup() - self.teacher_says("Peak outside flatland") - self.wait() - student = self.get_students()[0] - self.student_thinks(student_index = 0) - student.bubble.make_green_screen() - self.wait() - -class SymbolicThreeDTransform(Scene): - CONFIG = { - "input_coords" : [2, 6, -1], - "output_coords" : [3, 2, 0], - "title" : "Three-dimensional transformation", - } - def construct(self): - in_vect = Matrix(self.input_coords) - out_vect = Matrix(self.output_coords) - in_vect.set_color(BLUE) - out_vect.set_color(GREEN) - func = TexMobject("L(\\vec{\\textbf{v}})") - point = VectorizedPoint(func.get_center()) - in_vect.next_to(func, LEFT, buff = 1) - out_vect.next_to(func, RIGHT, buff = 1) - in_words = TextMobject("Input") - in_words.next_to(in_vect, DOWN) - in_words.set_color(BLUE_C) - out_words = TextMobject("Output") - out_words.next_to(out_vect, DOWN) - out_words.set_color(GREEN_C) - - - title = TextMobject(self.title) - title.to_edge(UP) - self.add(title) - - self.play(Write(func)) - self.play(Write(in_vect), Write(in_words)) - self.wait() - self.add(in_vect.copy()) - self.play(Transform(in_vect, point, lag_ratio = 0.5)) - self.play(Transform(in_vect, out_vect, lag_ratio = 0.5)) - self.add(out_words) - self.wait() - -class ThreeDLinearTransformExample(Scene): - pass - -class SingleVectorToOutput(Scene): - pass - -class InputWordOutputWord(Scene): - def construct(self): - self.add(TextMobject("Input").scale(2)) - self.wait() - self.clear() - self.add(TextMobject("Output").scale(2)) - self.wait() - -class TransformOnlyBasisVectors(Scene): - pass - -class IHatJHatKHatWritten(Scene): - def construct(self): - for char, color in zip(["\\imath", "\\jmath", "k"], [X_COLOR, Y_COLOR, Z_COLOR]): - sym = TexMobject("{\\hat{%s}}"%char) - sym.scale(3) - sym.set_color(color) - self.play(Write(sym)) - self.wait() - self.clear() - -class PutTogether3x3Matrix(Scene): - CONFIG = { - "col1" : [1, 0, -1], - "col2" : [1, 1, 0], - "col3" : [1, 0, 1], - } - def construct(self): - i_to = TexMobject("\\hat{\\imath} \\to").set_color(X_COLOR) - j_to = TexMobject("\\hat{\\jmath} \\to").set_color(Y_COLOR) - k_to = TexMobject("\\hat{k} \\to").set_color(Z_COLOR) - i_array = Matrix(self.col1) - j_array = Matrix(self.col2) - k_array = Matrix(self.col3) - everything = VMobject( - i_to, i_array, TexMobject("=").set_color(BLACK), - j_to, j_array, TexMobject("=").set_color(BLACK), - k_to, k_array, TexMobject("=").set_color(BLACK), - ) - everything.arrange(RIGHT, buff = 0.1) - everything.set_width(FRAME_WIDTH-1) - everything.to_edge(DOWN) - - i_array.set_color(X_COLOR) - j_array.set_color(Y_COLOR) - k_array.set_color(Z_COLOR) - arrays = [i_array, j_array, k_array] - matrix = Matrix(reduce( - lambda a1, a2 : np.append(a1, a2, axis = 1), - [m.copy().get_mob_matrix() for m in arrays] - )) - matrix.to_edge(DOWN) - - start_entries = reduce(op.add, [a.get_entries().split() for a in arrays]) - target_entries = matrix.get_mob_matrix().transpose().flatten() - start_l_bracket = i_array.get_brackets().split()[0] - start_r_bracket = k_array.get_brackets().split()[1] - start_brackets = VMobject(start_l_bracket, start_r_bracket) - target_bracketes = matrix.get_brackets() - - for mob in everything.split(): - self.play(Write(mob, run_time = 1)) - self.wait() - self.play( - FadeOut(everything), - Transform(VMobject(*start_entries), VMobject(*target_entries)), - Transform(start_brackets, target_bracketes) - ) - self.wait() - -class RotateSpaceAboutYAxis(Scene): - pass - -class RotateOnlyBasisVectorsAboutYAxis(Scene): - pass - -class PutTogetherRotationMatrix(PutTogether3x3Matrix): - CONFIG = { - "col1" : [0, 0, -1], - "col2" : [0, 1, 0], - "col3" : [1, 0, 0] - } - -class ScaleAndAddBeforeTransformation(Scene): - pass - -class ShowVCoordinateMeaning(Scene): - CONFIG = { - "v_str" : "\\vec{\\textbf{v}}", - "i_str" : "\\hat{\\imath}", - "j_str" : "\\hat{\\jmath}", - "k_str" : "\\hat{k}", - "post_transform" : False, - } - def construct(self): - v = TexMobject(self.v_str) - v.set_color(YELLOW) - eq = TexMobject("=") - coords = Matrix(["x", "y", "z"]) - eq2 = eq.copy() - if self.post_transform: - L, l_paren, r_paren = list(map(TexMobject, "L()")) - parens = VMobject(l_paren, r_paren) - parens.scale(2) - parens.stretch_to_fit_height( - coords.get_height() - ) - VMobject(L, l_paren, coords, r_paren).arrange(buff = 0.1) - coords.submobjects = [L, l_paren] + coords.submobjects + [r_paren] - - lin_comb = VMobject(*list(map(TexMobject, [ - "x", self.i_str, "+", - "y", self.j_str, "+", - "z", self.k_str, - ]))) - lin_comb.arrange( - RIGHT, buff = 0.1, - aligned_edge = ORIGIN if self.post_transform else DOWN - ) - lin_comb_parts = np.array(lin_comb.split()) - new_x, new_y, new_z = lin_comb_parts[[0, 3, 6]] - i, j, k = lin_comb_parts[[1, 4, 7]] - plusses = lin_comb_parts[[2, 5]] - i.set_color(X_COLOR) - j.set_color(Y_COLOR) - k.set_color(Z_COLOR) - - everything = VMobject(v, eq, coords, eq2, lin_comb) - everything.arrange(buff = 0.2) - everything.set_width(FRAME_WIDTH - 1) - everything.to_edge(DOWN) - if not self.post_transform: - lin_comb.shift(0.35*UP) - - self.play(*list(map(Write, [v, eq, coords]))) - self.wait() - self.play( - Transform( - coords.get_entries().copy(), - VMobject(new_x, new_y, new_z), - path_arc = -np.pi, - lag_ratio = 0.5 - ), - Write(VMobject(*[eq2, i, j, k] + list(plusses))), - run_time = 3 - ) - self.wait() - -class ScaleAndAddAfterTransformation(Scene): - pass - -class ShowVCoordinateMeaningAfterTransform(ShowVCoordinateMeaning): - CONFIG = { - "v_str" : "L(\\vec{\\textbf{v}})", - "i_str" : "L(\\hat{\\imath})", - "j_str" : "L(\\hat{\\jmath})", - "k_str" : "L(\\hat{k})", - "post_transform" : True, - } - -class ShowMatrixVectorMultiplication(Scene): - def construct(self): - matrix = Matrix(np.arange(9).reshape((3, 3))) - vect = Matrix(list("xyz")) - vect.set_height(matrix.get_height()) - col1, col2, col3 = columns = [ - Matrix(col) - for col in matrix.copy().get_mob_matrix().transpose() - ] - coords = x, y, z = [m.copy() for m in vect.get_entries().split()] - eq, plus1, plus2 = list(map(TexMobject, list("=++"))) - everything = VMobject( - matrix, vect, eq, - x, col1, plus1, - y, col2, plus2, - z, col3 - ) - everything.arrange(buff = 0.1) - everything.set_width(FRAME_WIDTH-1) - result = VMobject(x, col1, plus1, y, col2, plus2, z, col3) - - trips = [ - (matrix, DOWN, "Transformation"), - (vect, UP, "Input vector"), - (result, DOWN, "Output vector"), - ] - braces = [] - for mob, direction, text in trips: - brace = Brace(mob, direction) - words = TextMobject(text) - words.next_to(brace, direction) - brace.add(words) - braces.append(brace) - matrix_brace, vect_brace, result_brace = braces - - - self.play(*list(map(Write, [matrix, vect])), run_time = 2) - self.play(Write(matrix_brace, run_time = 1)) - self.play(Write(vect_brace, run_time = 1)) - sexts = list(zip( - matrix.get_mob_matrix().transpose(), - columns, - vect.get_entries().split(), - coords, - [eq, plus1, plus2], - [X_COLOR, Y_COLOR, Z_COLOR] - )) - for o_col, col, start_coord, coord, sym, color in sexts: - o_col = VMobject(*o_col) - self.play( - start_coord.set_color, YELLOW, - o_col.set_color, color - ) - coord.set_color(YELLOW) - col.set_color(color) - self.play( - Write(col.get_brackets()), - Transform( - o_col.copy(), col.get_entries(), - path_arc = -np.pi - ), - Transform( - start_coord.copy(), coord, - path_arc = -np.pi - ), - Write(sym) - ) - self.wait() - self.play(Write(result_brace, run_time = 1)) - self.wait() - -class ShowMatrixMultiplication(Scene): - def construct(self): - right = Matrix(np.arange(9).reshape((3, 3))) - left = Matrix(np.random.random_integers(-5, 5, (3, 3))) - VMobject(left, right).arrange(buff = 0.1) - right.set_column_colors(X_COLOR, Y_COLOR, Z_COLOR) - left.set_color(PINK) - - trips = [ - (right, DOWN, "First transformation"), - (left, UP, "Second transformation"), - ] - braces = [] - for mob, direction, text in trips: - brace = Brace(mob, direction) - words = TextMobject(text) - words.next_to(brace, direction) - brace.add(words) - braces.append(brace) - right_brace, left_brace = braces - - VMobject(*self.get_mobjects()).set_width(FRAME_WIDTH-1) - - self.add(right, left) - self.play(Write(right_brace)) - self.play(Write(left_brace)) - self.wait() - -class ApplyTwoSuccessiveTransforms(Scene): - pass - -class ComputerGraphicsAndRobotics(Scene): - def construct(self): - mob = VMobject( - TextMobject("Computer graphics"), - TextMobject("Robotics") - ) - mob.arrange(DOWN, buff = 1) - self.play(Write(mob, run_time = 1)) - self.wait() - -class ThreeDRotation(Scene): - pass - -class ThreeDRotationBrokenUp(Scene): - pass - -class SymbolicTwoDToThreeDTransform(SymbolicThreeDTransform): - CONFIG = { - "input_coords" : [2, 6], - "output_coords" : [3, 2, 0], - "title" : "Two dimensions to three dimensions", - } - -class SymbolicThreeDToTwoDTransform(SymbolicThreeDTransform): - CONFIG = { - "input_coords" : [4, -3, 1], - "output_coords" : [8, 4], - "title" : "Three dimensions to two dimensions", - } - -class QuestionsToPonder(Scene): - def construct(self): - title = TextMobject("Questions to ponder") - title.set_color(YELLOW).to_edge(UP) - self.add(title) - questions = VMobject(*list(map(TextMobject, [ - "1. Can you visualize these transformations?", - "2. Can you represent them with matrices?", - "3. How many rows and columns?", - "4. When can you multiply these matrices?", - ]))) - questions.arrange(DOWN, buff = 1, aligned_edge = LEFT) - questions.to_edge(LEFT) - for question in questions.split(): - self.play(Write(question, run_time = 1)) - self.wait() - -class NextVideo(Scene): - def construct(self): - title = TextMobject(""" - Next video: The determinant - """) - title.set_width(FRAME_WIDTH - 2) - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/footnote2.py b/from_3b1b/old/eola/footnote2.py deleted file mode 100644 index 6f2d7dd5..00000000 --- a/from_3b1b/old/eola/footnote2.py +++ /dev/null @@ -1,642 +0,0 @@ -from manimlib.imports import * - -from ka_playgrounds.circuits import Resistor, Source, LongResistor - -class OpeningQuote(Scene): - def construct(self): - words = TextMobject( - "``On this quiz, I asked you to find the determinant of a", - "2x3 matrix.", - "Some of you, to my great amusement, actually tried to do this.''" - ) - words.set_width(FRAME_WIDTH - 2) - words.to_edge(UP) - words[1].set_color(GREEN) - author = TextMobject("-(Via mathprofessorquotes.com, no name listed)") - author.set_color(YELLOW) - author.scale(0.7) - author.next_to(words, DOWN, buff = 0.5) - - self.play(FadeIn(words)) - self.wait(2) - self.play(Write(author, run_time = 3)) - self.wait() - -class AnotherFootnote(TeacherStudentsScene): - def construct(self): - self.teacher.look(LEFT) - self.teacher_says( - "More footnotes!", - target_mode = "surprised", - run_time = 1 - ) - self.random_blink(2) - -class ColumnsRepresentBasisVectors(Scene): - def construct(self): - matrix = Matrix([[3, 1], [4, 1], [5, 9]]) - i_hat_words, j_hat_words = [ - TextMobject("Where $\\hat{\\%smath}$ lands"%char) - for char in ("i", "j") - ] - i_hat_words.set_color(X_COLOR) - i_hat_words.next_to(ORIGIN, LEFT).to_edge(UP) - j_hat_words.set_color(Y_COLOR) - j_hat_words.next_to(ORIGIN, RIGHT).to_edge(UP) - question = TextMobject("How to interpret?") - question.next_to(matrix, UP) - question.set_color(YELLOW) - - self.add(matrix) - self.play(Write(question, run_time = 2)) - self.wait() - self.play(FadeOut(question)) - for i, words in enumerate([i_hat_words, j_hat_words]): - arrow = Arrow( - words.get_bottom(), - matrix.get_mob_matrix()[0,i].get_top(), - color = words.get_color() - ) - self.play( - Write(words, run_time = 1), - ShowCreation(arrow), - *[ - ApplyMethod(m.set_color, words.get_color()) - for m in matrix.get_mob_matrix()[:,i] - ] - ) - self.wait(2) - self.put_in_thought_bubble() - - def put_in_thought_bubble(self): - everything = VMobject(*self.get_mobjects()) - randy = Randolph().to_corner() - bubble = randy.get_bubble() - - self.play(FadeIn(randy)) - self.play( - ApplyFunction( - lambda m : bubble.position_mobject_inside( - m.set_height(2.5) - ), - everything - ), - ShowCreation(bubble), - randy.change_mode, "pondering" - ) - self.play(Blink(randy)) - self.wait() - self.play(randy.change_mode, "surprised") - self.wait() - -class Symbolic2To3DTransform(Scene): - def construct(self): - func = TexMobject("L(", "\\vec{\\textbf{v}}", ")") - input_array = Matrix([2, 7]) - input_array.set_color(YELLOW) - in_arrow = Arrow(LEFT, RIGHT, color = input_array.get_color()) - func[1].set_color(input_array.get_color()) - output_array = Matrix([1, 8, 2]) - output_array.set_color(PINK) - out_arrow = Arrow(LEFT, RIGHT, color = output_array.get_color()) - VMobject( - input_array, in_arrow, func, out_arrow, output_array - ).arrange(RIGHT, buff = SMALL_BUFF) - - input_brace = Brace(input_array, DOWN) - input_words = input_brace.get_text("2d input") - output_brace = Brace(output_array, UP) - output_words = output_brace.get_text("3d output") - input_words.set_color(input_array.get_color()) - output_words.set_color(output_array.get_color()) - - - self.add(func, input_array) - self.play( - GrowFromCenter(input_brace), - Write(input_words) - ) - mover = input_array.copy() - self.play( - Transform(mover, Dot().move_to(func)), - ShowCreation(in_arrow), - rate_func = rush_into - ) - self.play( - Transform(mover, output_array), - ShowCreation(out_arrow), - rate_func = rush_from - ) - self.play( - GrowFromCenter(output_brace), - Write(output_words) - ) - self.wait() - -class PlaneStartState(LinearTransformationScene): - def construct(self): - self.add_title("Input space") - labels = self.get_basis_vector_labels() - self.play(*list(map(Write, labels))) - self.wait() - -class OutputIn3dWords(Scene): - def construct(self): - words = TextMobject("Output in 3d") - words.scale(1.5) - self.play(Write(words)) - self.wait() - -class OutputIn3d(Scene): - pass - -class ShowSideBySide2dTo3d(Scene): - pass - -class AnimationLaziness(Scene): - def construct(self): - self.add(TextMobject("But there is some animation laziness...")) - -class DescribeColumnsInSpecificTransformation(Scene): - def construct(self): - matrix = Matrix([ - [2, 0], - [-1, 1], - [-2, 1], - ]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - mob_matrix = matrix.get_mob_matrix() - i_col, j_col = [VMobject(*mob_matrix[:,i]) for i in (0, 1)] - for col, char, vect in zip([i_col, j_col], ["i", "j"], [UP, DOWN]): - color = col[0].get_color() - col.words = TextMobject("Where $\\hat\\%smath$ lands"%char) - col.words.next_to(matrix, vect, buff = LARGE_BUFF) - col.words.set_color(color) - col.arrow = Arrow( - col.words.get_edge_center(-vect), - col.get_edge_center(vect), - color = color - ) - - self.play(Write(matrix.get_brackets())) - self.wait() - for col in i_col, j_col: - self.play( - Write(col), - ShowCreation(col.arrow), - Write(col.words, run_time = 1) - ) - self.wait() - -class CountRowsAndColumns(Scene): - def construct(self): - matrix = Matrix([ - [2, 0], - [-1, 1], - [-2, 1], - ]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - rows_brace = Brace(matrix, LEFT) - rows_words = rows_brace.get_text("3", "rows") - rows_words.set_color(PINK) - cols_brace = Brace(matrix, UP) - cols_words = cols_brace.get_text("2", "columns") - cols_words.set_color(TEAL) - title = TexMobject("3", "\\times", "2", "\\text{ matrix}") - title.to_edge(UP) - - self.add(matrix) - self.play( - GrowFromCenter(rows_brace), - Write(rows_words, run_time = 2) - ) - self.play( - GrowFromCenter(cols_brace), - Write(cols_words, run_time = 2) - ) - self.wait() - self.play( - rows_words[0].copy().move_to, title[0], - cols_words[0].copy().move_to, title[2], - Write(VMobject(title[1], title[3])) - ) - self.wait() - -class WriteColumnSpaceDefinition(Scene): - def construct(self): - matrix = Matrix([ - [2, 0], - [-1, 1], - [-2, 1], - ]) - matrix.set_column_colors(X_COLOR, Y_COLOR) - - brace = Brace(matrix) - words = VMobject( - TextMobject("Span", "of columns"), - TexMobject("\\Updownarrow"), - TextMobject("``Column space''") - ) - words.arrange(DOWN, buff = 0.1) - words.next_to(brace, DOWN) - words[0][0].set_color(PINK) - words[2].set_color(TEAL) - words[0].add_background_rectangle() - words[2].add_background_rectangle() - VMobject(matrix, brace, words).center() - - self.add(matrix) - self.play( - GrowFromCenter(brace), - Write(words, run_time = 2) - ) - self.wait() - -class MatrixInTheWild(Scene): - def construct(self): - randy = Randolph(color = PINK) - randy.look(LEFT) - randy.to_corner() - matrix = Matrix([ - [3, 1], - [4, 1], - [5, 9], - ]) - matrix.next_to(randy, RIGHT, buff = LARGE_BUFF, aligned_edge = DOWN) - bubble = randy.get_bubble(height = 4) - bubble.make_green_screen() - VMobject(randy, bubble, matrix).to_corner(UP+LEFT, buff = MED_SMALL_BUFF) - - self.add(randy) - self.play(Write(matrix)) - self.play(randy.look, RIGHT, run_time = 0.5) - self.play(randy.change_mode, "sassy") - self.play(Blink(randy)) - self.play( - ShowCreation(bubble), - randy.change_mode, "pondering" - ) - # self.play(matrix.set_column_colors, X_COLOR, Y_COLOR) - self.wait() - for x in range(3): - self.play(Blink(randy)) - self.wait(2) - new_matrix = Matrix([[3, 1, 4], [1, 5, 9]]) - new_matrix.move_to(matrix, aligned_edge = UP+LEFT) - self.play( - Transform(matrix, new_matrix), - FadeOut(bubble) - ) - self.remove(matrix) - matrix = new_matrix - self.add(matrix) - self.play(randy.look, DOWN+RIGHT, run_time = 0.5) - self.play(randy.change_mode, "confused") - self.wait() - self.play(Blink(randy)) - self.wait() - - top_brace = Brace(matrix, UP) - top_words = top_brace.get_text("3 basis vectors") - top_words.set_submobject_colors_by_gradient(GREEN, RED, BLUE) - side_brace = Brace(matrix, RIGHT) - side_words = side_brace.get_text(""" - 2 coordinates for - each landing spots - """) - side_words.set_color(YELLOW) - - self.play( - GrowFromCenter(top_brace), - Write(top_words), - matrix.set_column_colors, X_COLOR, Y_COLOR, Z_COLOR - ) - self.play(randy.change_mode, "happy") - self.play( - GrowFromCenter(side_brace), - Write(side_words, run_time = 2) - ) - self.play(Blink(randy)) - self.wait() - -class ThreeDToTwoDInput(Scene): - pass - -class ThreeDToTwoDInputWords(Scene): - def construct(self): - words = TextMobject("3d input") - words.scale(2) - self.play(Write(words)) - self.wait() - -class ThreeDToTwoDOutput(LinearTransformationScene): - CONFIG = { - "show_basis_vectors" : False, - "foreground_plane_kwargs" : { - "color" : GREY, - "x_radius" : FRAME_X_RADIUS, - "y_radius" : FRAME_Y_RADIUS, - "secondary_line_ratio" : 0 - }, - } - def construct(self): - title = TextMobject("Output in 2d") - title.to_edge(UP, buff = SMALL_BUFF) - subwords = TextMobject(""" - (only showing basis vectors, - full 3d grid would be a mess) - """) - subwords.scale(0.75) - subwords.next_to(title, DOWN) - for words in title, subwords: - words.add_background_rectangle() - - self.add(title) - i, j, k = it.starmap(self.add_vector, [ - ([3, 1], X_COLOR), - ([1, 2], Y_COLOR), - ([-2, -2], Z_COLOR) - ]) - pairs = [ - (i, "L(\\hat\\imath)"), - (j, "L(\\hat\\jmath)"), - (k, "L(\\hat k)") - ] - for v, tex in pairs: - self.label_vector(v, tex) - self.play(Write(subwords)) - self.wait() - -class ThreeDToTwoDSideBySide(Scene): - pass - -class Symbolic2To1DTransform(Scene): - def construct(self): - func = TexMobject("L(", "\\vec{\\textbf{v}}", ")") - input_array = Matrix([2, 7]) - input_array.set_color(YELLOW) - in_arrow = Arrow(LEFT, RIGHT, color = input_array.get_color()) - func[1].set_color(input_array.get_color()) - output_array = Matrix([1.8]) - output_array.set_color(PINK) - out_arrow = Arrow(LEFT, RIGHT, color = output_array.get_color()) - VMobject( - input_array, in_arrow, func, out_arrow, output_array - ).arrange(RIGHT, buff = SMALL_BUFF) - - input_brace = Brace(input_array, DOWN) - input_words = input_brace.get_text("2d input") - output_brace = Brace(output_array, UP) - output_words = output_brace.get_text("1d output") - input_words.set_color(input_array.get_color()) - output_words.set_color(output_array.get_color()) - - - self.add(func, input_array) - self.play( - GrowFromCenter(input_brace), - Write(input_words) - ) - mover = input_array.copy() - self.play( - Transform(mover, Dot().move_to(func)), - ShowCreation(in_arrow), - rate_func = rush_into, - run_time = 0.5 - ) - self.play( - Transform(mover, output_array), - ShowCreation(out_arrow), - rate_func = rush_from, - run_time = 0.5 - ) - self.play( - GrowFromCenter(output_brace), - Write(output_words) - ) - self.wait() - -class TwoDTo1DTransform(LinearTransformationScene): - CONFIG = { - "include_background_plane" : False, - "foreground_plane_kwargs" : { - "x_radius" : FRAME_X_RADIUS, - "y_radius" : FRAME_Y_RADIUS, - "secondary_line_ratio" : 1 - }, - "t_matrix" : [[1, 0], [2, 0]], - } - def construct(self): - line = NumberLine() - plane_words = TextMobject("2d space") - plane_words.next_to(self.j_hat, UP, buff = MED_SMALL_BUFF) - plane_words.add_background_rectangle() - line_words = TextMobject("1d space (number line)") - line_words.next_to(line, UP) - - - self.play(Write(plane_words)) - self.wait() - self.remove(plane_words) - mobjects = self.get_mobjects() - self.play( - *list(map(FadeOut, mobjects)) + [ShowCreation(line)] - ) - self.play(Write(line_words)) - self.wait() - self.remove(line_words) - self.play(*list(map(FadeIn, mobjects))) - self.apply_transposed_matrix(self.t_matrix) - self.play(Write(VMobject(*line.get_number_mobjects()))) - self.wait() - self.show_matrix() - - def show_matrix(self): - for vect, char in zip([self.i_hat, self.j_hat], ["i", "j"]): - vect.words = TextMobject( - "$\\hat\\%smath$ lands on"%char, - str(int(vect.get_end()[0])) - ) - direction = UP if vect is self.i_hat else DOWN - vect.words.next_to(vect.get_end(), direction, buff = LARGE_BUFF) - vect.words.set_color(vect.get_color()) - matrix = Matrix([[1, 2]]) - matrix_words = TextMobject("Transformation matrix: ") - matrix_group = VMobject(matrix_words, matrix) - matrix_group.arrange(buff = MED_SMALL_BUFF) - matrix_group.to_edge(UP) - entries = matrix.get_entries() - - self.play(Write(matrix_words), Write(matrix.get_brackets())) - for i, vect in enumerate([self.i_hat, self.j_hat]): - self.play(vect.rotate, np.pi/12, rate_func = wiggle) - self.play(Write(vect.words)) - self.wait() - self.play(vect.words[1].copy().move_to, entries[i]) - self.wait() - -class TwoDTo1DTransformWithDots(TwoDTo1DTransform): - def construct(self): - line = NumberLine() - self.add(line, *self.get_mobjects()) - offset = LEFT+DOWN - vect = 2*RIGHT+UP - dots = VMobject(*[ - Dot(offset + a*vect, radius = 0.075) - for a in np.linspace(-2, 3, 18) - ]) - dots.set_submobject_colors_by_gradient(YELLOW_B, YELLOW_C) - func = self.get_matrix_transformation(self.t_matrix) - new_dots = VMobject(*[ - Dot( - func(dot.get_center()), - color = dot.get_color(), - radius = dot.radius - ) - for dot in dots - ]) - words = TextMobject( - "Line of dots remains evenly spaced" - ) - words.next_to(line, UP, buff = MED_SMALL_BUFF) - - self.play(Write(dots)) - self.apply_transposed_matrix( - self.t_matrix, - added_anims = [Transform(dots, new_dots)] - ) - self.play(Write(words)) - self.wait() - -class NextVideo(Scene): - def construct(self): - title = TextMobject(""" - Next video: Dot products and duality - """) - title.set_width(FRAME_WIDTH - 2) - title.to_edge(UP) - rect = Rectangle(width = 16, height = 9, color = BLUE) - rect.set_height(6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - -class DotProductPreview(VectorScene): - CONFIG = { - "v_coords" : [3, 1], - "w_coords" : [2, -1], - "v_color" : YELLOW, - "w_color" : MAROON_C, - - } - def construct(self): - self.lock_in_faded_grid() - self.add_symbols() - self.add_vectors() - self.grow_number_line() - self.project_w() - self.show_scaling() - - - def add_symbols(self): - v = matrix_to_mobject(self.v_coords).set_color(self.v_color) - w = matrix_to_mobject(self.w_coords).set_color(self.w_color) - v.add_background_rectangle() - w.add_background_rectangle() - dot = TexMobject("\\cdot") - eq = VMobject(v, dot, w) - eq.arrange(RIGHT, buff = SMALL_BUFF) - eq.to_corner(UP+LEFT) - self.play(Write(eq), run_time = 1) - - def add_vectors(self): - self.v = Vector(self.v_coords, color = self.v_color) - self.w = Vector(self.w_coords, color = self.w_color) - self.play(ShowCreation(self.v)) - self.play(ShowCreation(self.w)) - - def grow_number_line(self): - line = NumberLine(stroke_width = 2).add_numbers() - line.rotate(self.v.get_angle()) - self.play(Write(line), Animation(self.v)) - self.play( - line.set_color, self.v.get_color(), - Animation(self.v), - rate_func = there_and_back - ) - self.wait() - - def project_w(self): - dot_product = np.dot(self.v.get_end(), self.w.get_end()) - v_norm, w_norm = [ - get_norm(vect.get_end()) - for vect in (self.v, self.w) - ] - projected_w = Vector( - self.v.get_end()*dot_product/(v_norm**2), - color = self.w.get_color() - ) - projection_line = Line( - self.w.get_end(), projected_w.get_end(), - color = GREY - ) - - self.play(ShowCreation(projection_line)) - self.add(self.w.copy().fade()) - self.play(Transform(self.w, projected_w)) - self.wait() - - def show_scaling(self): - dot_product = np.dot(self.v.get_end(), self.w.get_end()) - start_brace, interim_brace, final_brace = braces = [ - Brace( - Line(ORIGIN, norm*RIGHT), - UP - ) - for norm in (1, self.v.get_length(), dot_product) - ] - length_texs = list(it.starmap(TexMobject, [ - ("1",), - ("\\text{Scale by }", "||\\vec{\\textbf{v}}||",), - ("\\text{Length of}", "\\text{ scaled projection}",), - ])) - length_texs[1][1].set_color(self.v_color) - length_texs[2][1].set_color(self.w_color) - for brace, tex_mob in zip(braces, length_texs): - tex_mob.add_background_rectangle() - brace.put_at_tip(tex_mob, buff = SMALL_BUFF) - brace.add(tex_mob) - brace.rotate(self.v.get_angle()) - new_w = self.w.copy().scale(self.v.get_length()) - - self.play(Write(start_brace)) - self.wait() - self.play( - Transform(start_brace, interim_brace), - Transform(self.w, new_w) - ) - self.wait() - self.play( - Transform(start_brace, final_brace) - ) - self.wait() - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eola/thumbnails.py b/from_3b1b/old/eola/thumbnails.py deleted file mode 100644 index 1510bd6b..00000000 --- a/from_3b1b/old/eola/thumbnails.py +++ /dev/null @@ -1,152 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.eola.chapter9 import Jennifer, You - -class Chapter0(LinearTransformationScene): - CONFIG = { - "include_background_plane" : False, - "t_matrix" : [[3, 1], [2, -1]] - } - def construct(self): - self.setup() - self.plane.fade() - for mob in self.get_mobjects(): - mob.set_stroke(width = 6) - self.apply_transposed_matrix(self.t_matrix, run_time = 0) - -class Chapter1(Scene): - def construct(self): - arrow = Vector(2*UP+RIGHT) - vs = TextMobject("vs.") - array = Matrix([1, 2]) - array.set_color(TEAL) - everyone = VMobject(arrow, vs, array) - everyone.arrange(RIGHT, buff = 0.5) - everyone.set_height(4) - self.add(everyone) - -class Chapter2(LinearTransformationScene): - def construct(self): - self.lock_in_faded_grid() - vectors = VMobject(*[ - Vector([x, y]) - for x in np.arange(-int(FRAME_X_RADIUS)+0.5, int(FRAME_X_RADIUS)+0.5) - for y in np.arange(-int(FRAME_Y_RADIUS)+0.5, int(FRAME_Y_RADIUS)+0.5) - ]) - vectors.set_submobject_colors_by_gradient(PINK, BLUE_E) - words = TextMobject("Span") - words.scale(3) - words.to_edge(UP) - words.add_background_rectangle() - self.add(vectors, words) - - -class Chapter3(Chapter0): - CONFIG = { - "t_matrix" : [[3, 0], [2, -1]] - } - -class Chapter4p1(Chapter0): - CONFIG = { - "t_matrix" : [[1, 0], [1, 1]] - } - -class Chapter4p2(Chapter0): - CONFIG = { - "t_matrix" : [[1, 2], [-1, 1]] - } - -class Chapter5(LinearTransformationScene): - def construct(self): - self.plane.fade() - self.add_unit_square() - self.plane.set_stroke(width = 6) - VMobject(self.i_hat, self.j_hat).set_stroke(width = 10) - self.square.set_fill(YELLOW, opacity = 0.7) - self.square.set_stroke(width = 0) - self.apply_transposed_matrix(self.t_matrix, run_time = 0) - -class Chapter9(Scene): - def construct(self): - you = You() - jenny = Jennifer() - you.change_mode("erm") - jenny.change_mode("speaking") - you.shift(LEFT) - jenny.shift(2*RIGHT) - - vector = Vector([3, 2]) - vector.center().shift(2*DOWN) - vector.set_stroke(width = 8) - vector.tip.scale_in_place(2) - - you.coords = Matrix([3, 2]) - jenny.coords = Matrix(["5/3", "1/3"]) - for pi in jenny, you: - pi.bubble = pi.get_bubble(SpeechBubble, width = 3, height = 3) - if pi is you: - pi.bubble.shift(MED_SMALL_BUFF*RIGHT) - else: - pi.coords.scale(0.8) - pi.bubble.shift(MED_SMALL_BUFF*LEFT) - pi.bubble.add_content(pi.coords) - pi.add(pi.bubble, pi.coords) - pi.look_at(vector) - - self.add(you, jenny, vector) - -class Chapter10(LinearTransformationScene): - CONFIG = { - "foreground_plane_kwargs" : { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_HEIGHT, - "secondary_line_ratio" : 1 - - }, - "include_background_plane" : False, - } - - def construct(self): - v_tex = "\\vec{\\textbf{v}}" - eq = TexMobject("A", v_tex, "=", "\\lambda", v_tex) - eq.set_color_by_tex(v_tex, YELLOW) - eq.set_color_by_tex("\\lambda", MAROON_B) - eq.scale(3) - eq.add_background_rectangle() - eq.shift(2*DOWN) - - title = TextMobject( - "Eigen", "vectors \\\\", - "Eigen", "values" - , arg_separator = "") - title.scale(2.5) - title.to_edge(UP) - # title.set_color_by_tex("Eigen", MAROON_B) - title[0].set_color(YELLOW) - title[2].set_color(MAROON_B) - title.add_background_rectangle() - - - self.add_vector([-1, 1], color = YELLOW, animate = False) - self.apply_transposed_matrix([[3, 0], [1, 2]]) - self.plane.fade() - self.remove(self.j_hat) - self.add(eq, title) - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/eulers_characteristic_formula.py b/from_3b1b/old/eulers_characteristic_formula.py deleted file mode 100644 index 3a680bf5..00000000 --- a/from_3b1b/old/eulers_characteristic_formula.py +++ /dev/null @@ -1,1186 +0,0 @@ -#!/usr/bin/env python - - -import numpy as np -import itertools as it -from copy import deepcopy -import sys - - -from animation import * -from mobject import * -from constants import * -from mobject.region import * -import displayer as disp -from scene.scene import Scene, GraphScene -from scene.graphs import * -from .moser_main import EulersFormula -from script_wrapper import command_line_create_scene - -MOVIE_PREFIX = "ecf_graph_scenes/" -RANDOLPH_SCALE_FACTOR = 0.3 -EDGE_ANNOTATION_SCALE_FACTOR = 0.7 -DUAL_CYCLE = [3, 4, 5, 6, 1, 0, 2, 3] - -class EulersFormulaWords(Scene): - def construct(self): - self.add(TexMobject("V-E+F=2")) - -class TheTheoremWords(Scene): - def construct(self): - self.add(TextMobject("The Theorem:")) - -class ProofAtLastWords(Scene): - def construct(self): - self.add(TextMobject("The Proof At Last...")) - -class DualSpanningTreeWords(Scene): - def construct(self): - self.add(TextMobject("Spanning trees have duals too!")) - -class PreferOtherProofDialogue(Scene): - def construct(self): - teacher = Face("talking").shift(2*LEFT) - student = Face("straight").shift(2*RIGHT) - teacher_bubble = SpeechBubble(LEFT).speak_from(teacher) - student_bubble = SpeechBubble(RIGHT).speak_from(student) - teacher_bubble.write("Look at this \\\\ elegant proof!") - student_bubble.write("I prefer the \\\\ other proof.") - - self.add(student, teacher, teacher_bubble, teacher_bubble.text) - self.wait(2) - self.play(Transform( - Dot(student_bubble.tip).set_color("black"), - Mobject(student_bubble, student_bubble.text) - )) - self.wait(2) - self.remove(teacher_bubble.text) - teacher_bubble.write("Does that make this \\\\ any less elegant?") - self.add(teacher_bubble.text) - self.wait(2) - -class IllustrateDuality(GraphScene): - def construct(self): - GraphScene.construct(self) - self.generate_dual_graph() - - self.add(TextMobject("Duality").to_edge(UP)) - self.remove(*self.vertices) - def special_alpha(t): - if t > 0.5: - t = 1 - t - if t < 0.25: - return smooth(4*t) - else: - return 1 - kwargs = { - "run_time" : 5.0, - "rate_func" : special_alpha - } - self.play(*[ - Transform(*edge_pair, **kwargs) - for edge_pair in zip(self.edges, self.dual_edges) - ] + [ - Transform( - Mobject(*[ - self.vertices[index] - for index in cycle - ]), - dv, - **kwargs - ) - for cycle, dv in zip( - self.graph.region_cycles, - self.dual_vertices - ) - ]) - self.wait() - -class IntroduceGraph(GraphScene): - def construct(self): - GraphScene.construct(self) - tweaked_graph = deepcopy(self.graph) - for index in 2, 4: - tweaked_graph.vertices[index] += 2.8*RIGHT + 1.8*DOWN - tweaked_self = GraphScene(tweaked_graph) - edges_to_remove = [ - self.edges[self.graph.edges.index(pair)] - for pair in [(4, 5), (0, 5), (1, 5), (7, 1), (8, 3)] - ] - - connected, planar, graph = TextMobject([ - "Connected ", "Planar ", "Graph" - ]).to_edge(UP).split() - not_okay = TextMobject("Not Okay").set_color("red") - planar_explanation = TextMobject(""" - (``Planar'' just means we can draw it without - intersecting lines) - """, size = "\\small") - planar_explanation.shift(planar.get_center() + 0.5*DOWN) - - self.draw_vertices() - self.draw_edges() - self.clear() - self.add(*self.vertices + self.edges) - self.wait() - self.add(graph) - self.wait() - kwargs = { - "rate_func" : there_and_back, - "run_time" : 5.0 - } - self.add(not_okay) - self.play(*[ - Transform(*pair, **kwargs) - for pair in zip( - self.edges + self.vertices, - tweaked_self.edges + tweaked_self.vertices, - ) - ]) - self.remove(not_okay) - self.add(planar, planar_explanation) - self.wait(2) - self.remove(planar_explanation) - self.add(not_okay) - self.remove(*edges_to_remove) - self.play(ShowCreation( - Mobject(*edges_to_remove), - rate_func = lambda t : 1 - t, - run_time = 1.0 - )) - self.wait(2) - self.remove(not_okay) - self.add(connected, *edges_to_remove) - self.wait() - - -class OldIntroduceGraphs(GraphScene): - def construct(self): - GraphScene.construct(self) - self.draw_vertices() - self.draw_edges() - self.wait() - self.clear() - self.add(*self.edges) - self.replace_vertices_with(Face().scale(0.4)) - friends = TextMobject("Friends").scale(EDGE_ANNOTATION_SCALE_FACTOR) - self.annotate_edges(friends.shift((0, friends.get_height()/2, 0))) - self.play(*[ - CounterclockwiseTransform(vertex, Dot(point)) - for vertex, point in zip(self.vertices, self.points) - ]+[ - Transform(ann, line) - for ann, line in zip( - self.edge_annotations, - self.edges - ) - ]) - self.wait() - -class PlanarGraphDefinition(Scene): - def construct(self): - Not, quote, planar, end_quote = TextMobject([ - "Not \\\\", "``", "Planar", "''", - # "no matter how \\\\ hard you try" - ]).split() - shift_val = Mobject(Not, planar).to_corner().get_center() - Not.set_color("red").shift(shift_val) - graphs = [ - Mobject(*GraphScene(g).mobjects) - for g in [ - CubeGraph(), - CompleteGraph(5), - OctohedronGraph() - ] - ] - - self.add(quote, planar, end_quote) - self.wait() - self.play( - FadeOut(quote), - FadeOut(end_quote), - ApplyMethod(planar.shift, shift_val), - FadeIn(graphs[0]), - run_time = 1.5 - ) - self.wait() - self.remove(graphs[0]) - self.add(graphs[1]) - planar.set_color("red") - self.add(Not) - self.wait(2) - planar.set_color("white") - self.remove(Not) - self.remove(graphs[1]) - self.add(graphs[2]) - self.wait(2) - - -class TerminologyFromPolyhedra(GraphScene): - args_list = [(CubeGraph(),)] - def construct(self): - GraphScene.construct(self) - rot_kwargs = { - "radians" : np.pi / 3, - "run_time" : 5.0 - } - vertices = [ - point / 2 + OUT if abs(point[0]) == 2 else point + IN - for point in self.points - ] - cube = Mobject(*[ - Line(vertices[edge[0]], vertices[edge[1]]) - for edge in self.graph.edges - ]) - cube.rotate(-np.pi/3, [0, 0, 1]) - cube.rotate(-np.pi/3, [0, 1, 0]) - dots_to_vertices = TextMobject("Dots $\\to$ Vertices").to_corner() - lines_to_edges = TextMobject("Lines $\\to$ Edges").to_corner() - regions_to_faces = TextMobject("Regions $\\to$ Faces").to_corner() - - self.clear() - # self.play(TransformAnimations( - # Rotating(Dodecahedron(), **rot_kwargs), - # Rotating(cube, **rot_kwargs) - # )) - self.play(Rotating(cube, **rot_kwargs)) - self.clear() - self.play(*[ - Transform(l1, l2) - for l1, l2 in zip(cube.split(), self.edges) - ]) - self.wait() - self.add(dots_to_vertices) - self.play(*[ - ShowCreation(dot, run_time = 1.0) - for dot in self.vertices - ]) - self.wait(2) - self.remove(dots_to_vertices, *self.vertices) - self.add(lines_to_edges) - self.play(ApplyMethod( - Mobject(*self.edges).set_color, "yellow" - )) - self.wait(2) - self.clear() - self.add(*self.edges) - self.add(regions_to_faces) - self.generate_regions() - for region in self.regions: - self.set_color_region(region) - self.wait(3.0) - - -class ThreePiecesOfTerminology(GraphScene): - def construct(self): - GraphScene.construct(self) - terms = cycles, spanning_trees, dual_graphs = [ - TextMobject(phrase).shift(y*UP).to_edge() - for phrase, y in [ - ("Cycles", 3), - ("Spanning Trees", 1), - ("Dual Graphs", -1), - ] - ] - self.generate_spanning_tree() - scale_factor = 1.2 - def accent(mobject, color = "yellow"): - return mobject.scale_in_place(scale_factor).set_color(color) - def tone_down(mobject): - return mobject.scale_in_place(1.0/scale_factor).set_color("white") - - self.add(accent(cycles)) - self.trace_cycle(run_time = 1.0) - self.wait() - tone_down(cycles) - self.remove(self.traced_cycle) - - self.add(accent(spanning_trees)) - self.play(ShowCreation(self.spanning_tree), run_time = 1.0) - self.wait() - tone_down(spanning_trees) - self.remove(self.spanning_tree) - - self.add(accent(dual_graphs, "red")) - self.generate_dual_graph() - for mob in self.mobjects: - mob.fade - self.play(*[ - ShowCreation(mob, run_time = 1.0) - for mob in self.dual_vertices + self.dual_edges - ]) - self.wait() - - self.clear() - self.play(ApplyMethod( - Mobject(*terms).center - )) - self.wait() - -class WalkingRandolph(GraphScene): - args_list = [ - (SampleGraph(), [0, 1, 7, 8]), - ] - @staticmethod - def args_to_string(graph, path): - return str(graph) + "".join(map(str, path)) - - def __init__(self, graph, path, *args, **kwargs): - self.path = path - GraphScene.__init__(self, graph, *args, **kwargs) - - def construct(self): - GraphScene.construct(self) - point_path = [self.points[i] for i in self.path] - randy = Randolph() - randy.scale(RANDOLPH_SCALE_FACTOR) - randy.move_to(point_path[0]) - for next, last in zip(point_path[1:], point_path): - self.play( - WalkPiCreature(randy, next), - ShowCreation(Line(last, next).set_color("yellow")), - run_time = 2.0 - ) - self.randy = randy - - -class PathExamples(GraphScene): - args_list = [(SampleGraph(),)] - def construct(self): - GraphScene.construct(self) - paths = [ - (1, 2, 4, 5, 6), - (6, 7, 1, 3), - ] - non_paths = [ - [(0, 1), (7, 8), (5, 6),], - [(5, 0), (0, 2), (0, 1)], - ] - valid_path = TextMobject("Valid \\\\ Path").set_color("green") - not_a_path = TextMobject("Not a \\\\ Path").set_color("red") - for mob in valid_path, not_a_path: - mob.to_edge(UP) - kwargs = {"run_time" : 1.0} - for path, non_path in zip(paths, non_paths): - path_lines = Mobject(*[ - Line( - self.points[path[i]], - self.points[path[i+1]] - ).set_color("yellow") - for i in range(len(path) - 1) - ]) - non_path_lines = Mobject(*[ - Line( - self.points[pp[0]], - self.points[pp[1]], - ).set_color("yellow") - for pp in non_path - ]) - - self.remove(not_a_path) - self.add(valid_path) - self.play(ShowCreation(path_lines, **kwargs)) - self.wait(2) - self.remove(path_lines) - - self.remove(valid_path) - self.add(not_a_path) - self.play(ShowCreation(non_path_lines, **kwargs)) - self.wait(2) - self.remove(non_path_lines) - -class IntroduceCycle(WalkingRandolph): - args_list = [ - (SampleGraph(), [0, 1, 3, 2, 0]) - ] - def construct(self): - WalkingRandolph.construct(self) - self.remove(self.randy) - encompassed_cycles = [c for c in self.graph.region_cycles if set(c).issubset(self.path)] - regions = [ - self.region_from_cycle(cycle) - for cycle in encompassed_cycles - ] - for region in regions: - self.set_color_region(region) - self.wait() - - - -class IntroduceRandolph(GraphScene): - def construct(self): - GraphScene.construct(self) - randy = Randolph().move_to((-3, 0, 0)) - name = TextMobject("Randolph") - self.play(Transform( - randy, - deepcopy(randy).scale(RANDOLPH_SCALE_FACTOR).move_to(self.points[0]), - )) - self.wait() - name.shift((0, 1, 0)) - self.add(name) - self.wait() - -class DefineSpanningTree(GraphScene): - def construct(self): - GraphScene.construct(self) - randy = Randolph() - randy.scale(RANDOLPH_SCALE_FACTOR).move_to(self.points[0]) - dollar_signs = TextMobject("\\$\\$") - dollar_signs.scale(EDGE_ANNOTATION_SCALE_FACTOR) - dollar_signs = Mobject(*[ - deepcopy(dollar_signs).shift(edge.get_center()) - for edge in self.edges - ]) - unneeded = TextMobject("unneeded!") - unneeded.scale(EDGE_ANNOTATION_SCALE_FACTOR) - self.generate_spanning_tree() - def green_dot_at_index(index): - return Dot( - self.points[index], - radius = 2*Dot.DEFAULT_RADIUS, - color = "lightgreen", - ) - def out_of_spanning_set(point_pair): - stip = self.spanning_tree_index_pairs - return point_pair not in stip and \ - tuple(reversed(point_pair)) not in stip - - self.add(randy) - self.accent_vertices(run_time = 2.0) - self.add(dollar_signs) - self.wait(2) - self.remove(dollar_signs) - run_time_per_branch = 0.5 - self.play( - ShowCreation(green_dot_at_index(0)), - run_time = run_time_per_branch - ) - for pair in self.spanning_tree_index_pairs: - self.play(ShowCreation( - Line( - self.points[pair[0]], - self.points[pair[1]] - ).set_color("yellow"), - run_time = run_time_per_branch - )) - self.play(ShowCreation( - green_dot_at_index(pair[1]), - run_time = run_time_per_branch - )) - self.wait(2) - - unneeded_edges = list(filter(out_of_spanning_set, self.graph.edges)) - for edge, limit in zip(unneeded_edges, list(range(5))): - line = Line(self.points[edge[0]], self.points[edge[1]]) - line.set_color("red") - self.play(ShowCreation(line, run_time = 1.0)) - self.add(unneeded.center().shift(line.get_center() + 0.2*UP)) - self.wait() - self.remove(line, unneeded) - -class NamingTree(GraphScene): - def construct(self): - GraphScene.construct(self) - self.generate_spanning_tree() - self.generate_treeified_spanning_tree() - branches = self.spanning_tree.split() - branches_copy = deepcopy(branches) - treeified_branches = self.treeified_spanning_tree.split() - tree = TextMobject("``Tree''").to_edge(UP) - spanning_tree = TextMobject("``Spanning Tree''").to_edge(UP) - - self.add(*branches) - self.play( - FadeOut(Mobject(*self.edges + self.vertices)), - Animation(Mobject(*branches)), - ) - self.clear() - self.add(tree, *branches) - self.wait() - self.play(*[ - Transform(b1, b2, run_time = 2) - for b1, b2 in zip(branches, treeified_branches) - ]) - self.wait() - self.play(*[ - FadeIn(mob) - for mob in self.edges + self.vertices - ] + [ - Transform(b1, b2, run_time = 2) - for b1, b2 in zip(branches, branches_copy) - ]) - self.accent_vertices(run_time = 2) - self.remove(tree) - self.add(spanning_tree) - self.wait(2) - -class DualGraph(GraphScene): - def construct(self): - GraphScene.construct(self) - self.generate_dual_graph() - self.add(TextMobject("Dual Graph").to_edge(UP).shift(2*LEFT)) - self.play(*[ - ShowCreation(mob) - for mob in self.dual_edges + self.dual_vertices - ]) - self.wait() - -class FacebookLogo(Scene): - def construct(self): - im = ImageMobject("facebook_full_logo", invert = False) - self.add(im.scale(0.7)) - - -class FacebookGraph(GraphScene): - def construct(self): - GraphScene.construct(self) - account = ImageMobject("facebook_silhouette", invert = False) - account.scale(0.05) - logo = ImageMobject("facebook_logo", invert = False) - logo.scale(0.1) - logo.shift(0.2*LEFT + 0.1*UP) - account.add(logo).center() - account.shift(0.2*LEFT + 0.1*UP) - friends = TexMobject( - "\\leftarrow \\text{friends} \\rightarrow" - ).scale(0.5*EDGE_ANNOTATION_SCALE_FACTOR) - - self.clear() - accounts = [ - deepcopy(account).shift(point) - for point in self.points - ] - self.add(*accounts) - self.wait() - self.annotate_edges(friends) - self.wait() - self.play(*[ - CounterclockwiseTransform(account, vertex) - for account, vertex in zip(accounts, self.vertices) - ]) - self.wait() - self.play(*[ - Transform(ann, edge) - for ann, edge in zip(self.edge_annotations, self.edges) - ]) - self.wait() - -class FacebookGraphAsAbstractSet(Scene): - def construct(self): - names = [ - "Louis", - "Randolph", - "Mortimer", - "Billy Ray", - "Penelope", - ] - friend_pairs = [ - (0, 1), - (0, 2), - (1, 2), - (3, 0), - (4, 0), - (1, 3), - (1, 2), - ] - names_string = "\\\\".join(names + ["$\\vdots$"]) - friends_string = "\\\\".join([ - "\\text{%s}&\\leftrightarrow\\text{%s}"%(names[i],names[j]) - for i, j in friend_pairs - ] + ["\\vdots"]) - names_mob = TextMobject(names_string).shift(3*LEFT) - friends_mob = TexMobject( - friends_string, size = "\\Large" - ).shift(3*RIGHT) - accounts = TextMobject("\\textbf{Accounts}") - accounts.shift(3*LEFT).to_edge(UP) - friendships = TextMobject("\\textbf{Friendships}") - friendships.shift(3*RIGHT).to_edge(UP) - lines = Mobject( - Line(UP*FRAME_Y_RADIUS, DOWN*FRAME_Y_RADIUS), - Line(LEFT*FRAME_X_RADIUS + 3*UP, RIGHT*FRAME_X_RADIUS + 3*UP) - ).set_color("white") - - self.add(accounts, friendships, lines) - self.wait() - for mob in names_mob, friends_mob: - self.play(ShowCreation( - mob, run_time = 1.0 - )) - self.wait() - - -class ExamplesOfGraphs(GraphScene): - def construct(self): - buff = 0.5 - self.graph.vertices = [v + DOWN + RIGHT for v in self.graph.vertices] - GraphScene.construct(self) - self.generate_regions() - objects, notions = Mobject(*TextMobject( - ["Objects \\quad\\quad ", "Thing that connects objects"] - )).to_corner().shift(0.5*RIGHT).split() - horizontal_line = Line( - (-FRAME_X_RADIUS, FRAME_Y_RADIUS-1, 0), - (max(notions.points[:,0]), FRAME_Y_RADIUS-1, 0) - ) - vert_line_x_val = min(notions.points[:,0]) - buff - vertical_line = Line( - (vert_line_x_val, FRAME_Y_RADIUS, 0), - (vert_line_x_val,-FRAME_Y_RADIUS, 0) - ) - objects_and_notions = [ - ("Facebook accounts", "Friendship"), - ("English Words", "Differ by One Letter"), - ("Mathematicians", "Coauthorship"), - ("Neurons", "Synapses"), - ( - "Regions our graph \\\\ cuts the plane into", - "Shared edges" - ) - ] - - - self.clear() - self.add(objects, notions, horizontal_line, vertical_line) - for (obj, notion), height in zip(objects_and_notions, it.count(2, -1)): - obj_mob = TextMobject(obj, size = "\\small").to_edge(LEFT) - not_mob = TextMobject(notion, size = "\\small").to_edge(LEFT) - not_mob.shift((vert_line_x_val + FRAME_X_RADIUS)*RIGHT) - obj_mob.shift(height*UP) - not_mob.shift(height*UP) - - if obj.startswith("Regions"): - self.handle_dual_graph(obj_mob, not_mob) - elif obj.startswith("English"): - self.handle_english_words(obj_mob, not_mob) - else: - self.add(obj_mob) - self.wait() - self.add(not_mob) - self.wait() - - def handle_english_words(self, words1, words2): - words = list(map(TextMobject, ["graph", "grape", "gape", "gripe"])) - words[0].shift(RIGHT) - words[1].shift(3*RIGHT) - words[2].shift(3*RIGHT + 2*UP) - words[3].shift(3*RIGHT + 2*DOWN) - lines = [ - Line(*pair) - for pair in [ - ( - words[0].get_center() + RIGHT*words[0].get_width()/2, - words[1].get_center() + LEFT*words[1].get_width()/2 - ),( - words[1].get_center() + UP*words[1].get_height()/2, - words[2].get_center() + DOWN*words[2].get_height()/2 - ),( - words[1].get_center() + DOWN*words[1].get_height()/2, - words[3].get_center() + UP*words[3].get_height()/2 - ) - ] - ] - - comp_words = Mobject(*words) - comp_lines = Mobject(*lines) - self.add(words1) - self.play(ShowCreation(comp_words, run_time = 1.0)) - self.wait() - self.add(words2) - self.play(ShowCreation(comp_lines, run_time = 1.0)) - self.wait() - self.remove(comp_words, comp_lines) - - - def handle_dual_graph(self, words1, words2): - words1.set_color("yellow") - words2.set_color("yellow") - connected = TextMobject("Connected") - connected.set_color("lightgreen") - not_connected = TextMobject("Not Connected") - not_connected.set_color("red") - for mob in connected, not_connected: - mob.shift(self.points[3] + UP) - - self.play(*[ - ShowCreation(mob, run_time = 1.0) - for mob in self.edges + self.vertices - ]) - self.wait() - for region in self.regions: - self.set_color_region(region) - self.add(words1) - self.wait() - self.reset_background() - self.add(words2) - - region_pairs = it.combinations(self.graph.region_cycles, 2) - for x in range(6): - want_matching = (x%2 == 0) - found = False - while True: - try: - cycle1, cycle2 = next(region_pairs) - except: - return - shared = set(cycle1).intersection(cycle2) - if len(shared) == 2 and want_matching: - break - if len(shared) != 2 and not want_matching: - break - for cycle in cycle1, cycle2: - index = self.graph.region_cycles.index(cycle) - self.set_color_region(self.regions[index]) - if want_matching: - self.remove(not_connected) - self.add(connected) - tup = tuple(shared) - if tup not in self.graph.edges: - tup = tuple(reversed(tup)) - edge = deepcopy(self.edges[self.graph.edges.index(tup)]) - edge.set_color("red") - self.play(ShowCreation(edge), run_time = 1.0) - self.wait() - self.remove(edge) - else: - self.remove(connected) - self.add(not_connected) - self.wait(2) - self.reset_background() - - - - - -class DrawDualGraph(GraphScene): - def construct(self): - GraphScene.construct(self) - self.generate_regions() - self.generate_dual_graph() - region_mobs = [ - ImageMobject(disp.paint_region(reg, self.background), invert = False) - for reg in self.regions - ] - for region, mob in zip(self.regions, region_mobs): - self.set_color_region(region, mob.get_color()) - outer_region = self.regions.pop() - outer_region_mob = region_mobs.pop() - outer_dual_vertex = self.dual_vertices.pop() - internal_edges = [e for e in self.dual_edges if abs(e.start[0]) < FRAME_X_RADIUS and \ - abs(e.end[0]) < FRAME_X_RADIUS and \ - abs(e.start[1]) < FRAME_Y_RADIUS and \ - abs(e.end[1]) < FRAME_Y_RADIUS] - external_edges = [e for e in self.dual_edges if e not in internal_edges] - - self.wait() - self.reset_background() - self.set_color_region(outer_region, outer_region_mob.get_color()) - self.play(*[ - Transform(reg_mob, dot) - for reg_mob, dot in zip(region_mobs, self.dual_vertices) - ]) - self.wait() - self.reset_background() - self.play(ApplyFunction( - lambda p : (FRAME_X_RADIUS + FRAME_Y_RADIUS)*p/get_norm(p), - outer_region_mob - )) - self.wait() - for edges in internal_edges, external_edges: - self.play(*[ - ShowCreation(edge, run_time = 2.0) - for edge in edges - ]) - self.wait() - -class EdgesAreTheSame(GraphScene): - def construct(self): - GraphScene.construct(self) - self.generate_dual_graph() - self.remove(*self.vertices) - self.add(*self.dual_edges) - self.wait() - self.play(*[ - Transform(*pair, run_time = 2.0) - for pair in zip(self.dual_edges, self.edges) - ]) - self.wait() - self.add( - TextMobject(""" - (Or at least I would argue they should \\\\ - be thought of as the same thing.) - """, size = "\\small").to_edge(UP) - ) - self.wait() - -class ListOfCorrespondances(Scene): - def construct(self): - buff = 0.5 - correspondances = [ - ["Regions cut out by", "Vertices of"], - ["Edges of", "Edges of"], - ["Cycles of", "Connected components of"], - ["Connected components of", "Cycles of"], - ["Spanning tree in", "Complement of spanning tree in"], - ["", "Dual of"], - ] - for corr in correspondances: - corr[0] += " original graph" - corr[1] += " dual graph" - arrow = TexMobject("\\leftrightarrow", size = "\\large") - lines = [] - for corr, height in zip(correspondances, it.count(3, -1)): - left = TextMobject(corr[0], size = "\\small") - right = TextMobject(corr[1], size = "\\small") - this_arrow = deepcopy(arrow) - for mob in left, right, this_arrow: - mob.shift(height*UP) - arrow_xs = this_arrow.points[:,0] - left.to_edge(RIGHT) - left.shift((min(arrow_xs) - FRAME_X_RADIUS, 0, 0)) - right.to_edge(LEFT) - right.shift((max(arrow_xs) + FRAME_X_RADIUS, 0, 0)) - lines.append(Mobject(left, right, this_arrow)) - last = None - for line in lines: - self.add(line.set_color("yellow")) - if last: - last.set_color("white") - last = line - self.wait(1) - - -class CyclesCorrespondWithConnectedComponents(GraphScene): - args_list = [(SampleGraph(),)] - def construct(self): - GraphScene.construct(self) - self.generate_regions() - self.generate_dual_graph() - cycle = [4, 2, 1, 5, 4] - enclosed_regions = [0, 2, 3, 4] - dual_cycle = DUAL_CYCLE - enclosed_vertices = [0, 1] - randy = Randolph() - randy.scale(RANDOLPH_SCALE_FACTOR) - randy.move_to(self.points[cycle[0]]) - - lines_to_remove = [] - for last, next in zip(cycle, cycle[1:]): - line = Line(self.points[last], self.points[next]) - line.set_color("yellow") - self.play( - ShowCreation(line), - WalkPiCreature(randy, self.points[next]), - run_time = 1.0 - ) - lines_to_remove.append(line) - self.wait() - self.remove(randy, *lines_to_remove) - for region in np.array(self.regions)[enclosed_regions]: - self.set_color_region(region) - self.wait(2) - self.reset_background() - lines = Mobject(*[ - Line(self.dual_points[last], self.dual_points[next]) - for last, next in zip(dual_cycle, dual_cycle[1:]) - ]).set_color("red") - self.play(ShowCreation(lines)) - self.play(*[ - Transform(v, Dot( - v.get_center(), - radius = 3*Dot.DEFAULT_RADIUS - ).set_color("green")) - for v in np.array(self.vertices)[enclosed_vertices] - ] + [ - ApplyMethod(self.edges[0].set_color, "green") - ]) - self.wait() - - -class IntroduceMortimer(GraphScene): - args_list = [(SampleGraph(),)] - def construct(self): - GraphScene.construct(self) - self.generate_dual_graph() - self.generate_regions() - randy = Randolph().shift(LEFT) - morty = Mortimer().shift(RIGHT) - name = TextMobject("Mortimer") - name.shift(morty.get_center() + 1.2*UP) - randy_path = (0, 1, 3) - morty_path = (-2, -3, -4) - morty_crossed_lines = [ - Line(self.points[i], self.points[j]).set_color("red") - for i, j in [(7, 1), (1, 5)] - ] - kwargs = {"run_time" : 1.0} - - self.clear() - self.add(randy) - self.wait() - self.add(morty, name) - self.wait() - self.remove(name) - small_randy = deepcopy(randy).scale(RANDOLPH_SCALE_FACTOR) - small_morty = deepcopy(morty).scale(RANDOLPH_SCALE_FACTOR) - small_randy.move_to(self.points[randy_path[0]]) - small_morty.move_to(self.dual_points[morty_path[0]]) - self.play(*[ - FadeIn(mob) - for mob in self.vertices + self.edges - ] + [ - Transform(randy, small_randy), - Transform(morty, small_morty), - ], **kwargs) - self.wait() - - - self.set_color_region(self.regions[morty_path[0]]) - for last, next in zip(morty_path, morty_path[1:]): - self.play(WalkPiCreature(morty, self.dual_points[next]),**kwargs) - self.set_color_region(self.regions[next]) - self.wait() - for last, next in zip(randy_path, randy_path[1:]): - line = Line(self.points[last], self.points[next]) - line.set_color("yellow") - self.play( - WalkPiCreature(randy, self.points[next]), - ShowCreation(line), - **kwargs - ) - self.wait() - self.play(*[ - ApplyMethod( - line.rotate_in_place, - np.pi/10, - rate_func = wiggle) - for line in morty_crossed_lines - ], **kwargs) - - self.wait() - -class RandolphMortimerSpanningTreeGame(GraphScene): - args_list = [(SampleGraph(),)] - def construct(self): - GraphScene.construct(self) - self.generate_spanning_tree() - self.generate_dual_graph() - self.generate_regions() - randy = Randolph().scale(RANDOLPH_SCALE_FACTOR) - morty = Mortimer().scale(RANDOLPH_SCALE_FACTOR) - randy.move_to(self.points[0]) - morty.move_to(self.dual_points[0]) - attempted_dual_point_index = 2 - region_ordering = [0, 1, 7, 2, 3, 5, 4, 6] - dual_edges = [1, 3, 4, 7, 11, 9, 13] - time_per_dual_edge = 0.5 - - self.add(randy, morty) - self.play(ShowCreation(self.spanning_tree)) - self.wait() - self.play(WalkPiCreature( - morty, self.dual_points[attempted_dual_point_index], - rate_func = lambda t : 0.3 * there_and_back(t), - run_time = 2.0, - )) - self.wait() - for index in range(len(self.regions)): - # if index > 0: - # edge = self.edges[dual_edges[index-1]] - # midpoint = edge.get_center() - # self.play(*[ - # ShowCreation(Line( - # midpoint, - # tip - # ).set_color("red")) - # for tip in edge.start, edge.end - # ], run_time = time_per_dual_edge) - self.set_color_region(self.regions[region_ordering[index]]) - self.wait(time_per_dual_edge) - self.wait() - - - cycle_index = region_ordering[-1] - cycle = self.graph.region_cycles[cycle_index] - self.set_color_region(self.regions[cycle_index], "black") - self.play(ShowCreation(Mobject(*[ - Line(self.points[last], self.points[next]).set_color("green") - for last, next in zip(cycle, list(cycle)[1:] + [cycle[0]]) - ]))) - self.wait() - -class MortimerCannotTraverseCycle(GraphScene): - args_list = [(SampleGraph(),)] - def construct(self): - GraphScene.construct(self) - self.generate_dual_graph() - dual_cycle = DUAL_CYCLE - trapped_points = [0, 1] - morty = Mortimer().scale(RANDOLPH_SCALE_FACTOR) - morty.move_to(self.dual_points[dual_cycle[0]]) - time_per_edge = 0.5 - text = TextMobject(""" - One of these lines must be included - in the spanning tree if those two inner - vertices are to be reached. - """).scale(0.7).to_edge(UP) - - all_lines = [] - matching_edges = [] - kwargs = {"run_time" : time_per_edge, "rate_func" : None} - for last, next in zip(dual_cycle, dual_cycle[1:]): - line = Line(self.dual_points[last], self.dual_points[next]) - line.set_color("red") - self.play( - WalkPiCreature(morty, self.dual_points[next], **kwargs), - ShowCreation(line, **kwargs), - ) - all_lines.append(line) - center = line.get_center() - distances = [get_norm(center - e.get_center()) for e in self.edges] - matching_edges.append( - self.edges[distances.index(min(distances))] - ) - self.play(*[ - Transform(v, Dot( - v.get_center(), - radius = 3*Dot.DEFAULT_RADIUS, - color = "green" - )) - for v in np.array(self.vertices)[trapped_points] - ]) - self.add(text) - self.play(*[ - Transform(line, deepcopy(edge).set_color(line.get_color())) - for line, edge in zip(all_lines, matching_edges) - ]) - self.wait() - -class TwoPropertiesOfSpanningTree(Scene): - def construct(self): - spanning, tree = TextMobject( - ["Spanning ", "Tree"], - size = "\\Huge" - ).split() - spanning_explanation = TextMobject(""" - Touches every vertex - """).shift(spanning.get_center() + 2*DOWN) - tree_explanation = TextMobject(""" - No Cycles - """).shift(tree.get_center() + 2*UP) - - self.add(spanning, tree) - self.wait() - for word, explanation, vect in [ - (spanning, spanning_explanation, 0.5*UP), - (tree, tree_explanation, 0.5*DOWN) - ]: - self.add(explanation) - self.add(Arrow( - explanation.get_center() + vect, - tail = word.get_center() - vect, - )) - self.play(ApplyMethod(word.set_color, "yellow")) - self.wait() - - -class DualSpanningTree(GraphScene): - def construct(self): - GraphScene.construct(self) - self.generate_dual_graph() - self.generate_spanning_tree() - randy = Randolph() - randy.scale(RANDOLPH_SCALE_FACTOR) - randy.move_to(self.points[0]) - morty = Mortimer() - morty.scale(RANDOLPH_SCALE_FACTOR) - morty.move_to(self.dual_points[0]) - dual_edges = [1, 3, 4, 7, 11, 9, 13] - words = TextMobject(""" - The red edges form a spanning tree of the dual graph! - """).to_edge(UP) - - self.add(self.spanning_tree, randy, morty) - self.play(ShowCreation(Mobject( - *np.array(self.edges)[dual_edges] - ).set_color("red"))) - self.add(words) - self.wait() - -class TreeCountFormula(Scene): - def construct(self): - time_per_branch = 0.5 - text = TextMobject(""" - In any tree: - $$E + 1 = V$$ - """) - gs = GraphScene(SampleGraph()) - gs.generate_treeified_spanning_tree() - branches = gs.treeified_spanning_tree.to_edge(LEFT).split() - - all_dots = [Dot(branches[0].points[0])] - self.add(text, all_dots[0]) - for branch in branches: - self.play( - ShowCreation(branch), - run_time = time_per_branch - ) - dot = Dot(branch.points[-1]) - self.add(dot) - all_dots.append(dot) - self.wait() - self.remove(*all_dots) - self.play( - FadeOut(text), - FadeIn(Mobject(*gs.edges + gs.vertices)), - *[ - Transform(*pair) - for pair in zip(branches,gs.spanning_tree.split()) - ] - ) - - -class FinalSum(Scene): - def construct(self): - lines = TexMobject([ - "(\\text{Number of Randolph's Edges}) + 1 &= V \\\\ \n", - "(\\text{Number of Mortimer's Edges}) + 1 &= F \\\\ \n", - " \\Downarrow \\\\", "E","+","2","&=","V","+","F", - ], size = "\\large").split() - for line in lines[:2] + [Mobject(*lines[2:])]: - self.add(line) - self.wait() - self.wait() - - symbols = V, minus, E, plus, F, equals, two = TexMobject( - "V - E + F = 2".split(" ") - ) - plus = TexMobject("+") - anims = [] - for mob, index in zip(symbols, [-3, -2, -7, -6, -1, -4, -5]): - copy = plus if index == -2 else deepcopy(mob) - copy.center().shift(lines[index].get_center()) - copy.scale_in_place(lines[index].get_width()/mob.get_width()) - anims.append(CounterclockwiseTransform(copy, mob)) - self.clear() - self.play(*anims, run_time = 2.0) - self.wait() - - - - - -if __name__ == "__main__": - command_line_create_scene(MOVIE_PREFIX) - - - - - - - - - - - diff --git a/from_3b1b/old/fc1.py b/from_3b1b/old/fc1.py deleted file mode 100644 index 8943e25f..00000000 --- a/from_3b1b/old/fc1.py +++ /dev/null @@ -1,272 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.efvgt import get_confetti_animations - - -class CrossingOneMillion(TeacherStudentsScene): - def construct(self): - self.increment_count() - self.comment_on_real_milestone() - self.reflect() - - def increment_count(self): - number = self.number = Integer(0) - number.move_to(UP, LEFT) - number.scale(3) - self.look_at(number, run_time=0) - - confetti_spirils = self.confetti_spirils = list(map( - turn_animation_into_updater, - get_confetti_animations(50) - )) - self.add(*confetti_spirils) - self.play( - ChangeDecimalToValue( - number, 10**6, - position_update_func=lambda m: m.move_to( - UP, LEFT - ), - rate_func=bezier([0, 0, 0, 1, 1, 1]), - run_time=5, - ), - LaggedStartMap( - ApplyMethod, self.get_pi_creatures(), - lambda m: (m.change, "hooray", number), - rate_func=squish_rate_func(smooth, 0, 0.5), - run_time=4, - ), - ) - self.wait() - - def comment_on_real_milestone(self): - number = self.number - remainder = Integer(2**20 - 10**6) - words = TextMobject( - "Just", - "{:,}".format(remainder.number), - "to go \\\\ before the real milestone", - ) - self.student_says( - words, - added_anims=[ - ApplyMethod(self.teacher.change, "hesitant"), - self.get_student_changes( - "sassy", "speaking", "happy" - ), - number.scale, 0.5, - number.center, - number.to_edge, UP, - ] - ) - self.wait() - self.remove(*self.confetti_spirils) - remainder.replace(words[1]) - words.submobjects[1] = remainder - self.play( - ChangeDecimalToValue(number, 2**20, run_time=3), - ChangeDecimalToValue(remainder, 0.1, run_time=3), - self.teacher.change, "pondering", number, - self.get_student_changes( - *["pondering"] * 3, - look_at_arg=number - ), - ) - self.play( - FadeOut(self.students[1].bubble), - FadeOut(self.students[1].bubble.content), - ) - self.wait(2) - - def reflect(self): - bubble = ThoughtBubble( - direction=RIGHT, - height=4, - width=7, - ) - bubble.pin_to(self.teacher) - q_marks = TexMobject("???") - q_marks.scale(2) - q_marks.set_color_by_gradient(BLUE_D, BLUE_B) - q_marks.next_to(bubble[-1].get_top(), DOWN) - arrow = Vector(0.5 * DOWN, color=WHITE) - arrow.next_to(q_marks, DOWN) - number = self.number - number.generate_target() - number.target.next_to(arrow, DOWN) - - self.play( - ShowCreation( - bubble, - rate_func=squish_rate_func(smooth, 0, 0.3) - ), - Write(q_marks), - GrowArrow(arrow), - MoveToTarget(number), - run_time=3 - ) - self.wait() - - -class ShareWithFriends(PiCreatureScene): - def construct(self): - randy, morty = self.pi_creatures - - self.pi_creature_says( - randy, - "Wanna see why \\\\" + - "$1 - \\frac{1}{3} + \\frac{1}{5}" + - "- \\frac{1}{7} + \\cdots = \\frac{\\pi}{4}$?", - target_mode="tease", - added_anims=[morty.look, UP] - ) - self.play(morty.change, "maybe", UP) - self.wait() - - def create_pi_creatures(self): - randy = Randolph(color=GREEN) - morty = Mortimer(color=RED_E) - randy.to_edge(DOWN).shift(4 * LEFT) - morty.to_edge(DOWN) - return randy, morty - - -class AllFeaturedCreators(MortyPiCreatureScene): - def construct(self): - morty = self.pi_creature - title = Title("Featured creators") - - dots = VGroup(*[Dot(color=WHITE) for x in range(4)]) - dots.arrange(DOWN, buff=LARGE_BUFF) - dots.to_edge(LEFT, buff=2) - - creators = VGroup(*list(map(TextMobject, [ - "Think Twice", - "LeiosOS", - "Welch Labs", - "Infinity plus one", - ]))) - - for creator, dot in zip(creators, dots): - creator.next_to(dot, RIGHT) - dot.save_state() - dot.scale(4) - dot.set_fill(opacity=0) - - rects = VGroup(*list(map(SurroundingRectangle, creators))) - rects.set_stroke(WHITE, 2) - rects.set_fill(BLUE_E, 1) - - think_words = VGroup(*list(map(TextMobject, [ - "(thinks visually)", - "(thinks in terms of communities)", - "(thinks in terms of series)", - "(thinks playfully)", - ]))) - for word, creator in zip(think_words, creators): - # word.move_to(creator, RIGHT) - # word.align_to(RIGHT, LEFT) - word.next_to(creator, RIGHT) - word.set_color(YELLOW) - - self.play( - morty.change, "raise_right_hand", - Write(title) - ) - self.wait() - self.play(LaggedStartMap( - ApplyMethod, dots, - lambda m: (m.restore,) - )) - self.play( - LaggedStartMap(FadeIn, rects, lag_ratio=0.7), - morty.change, "happy" - ) - self.add(creators, rects) - self.wait() - - modes = ["hooray", "tease", "raise_right_hand", "hooray"] - for rect, word, mode in zip(rects, think_words, modes): - self.play( - self.get_rect_removal(rect), - morty.change, mode, - ) - self.wait() - self.play(Write(word)) - self.wait() - - self.add(think_words) - - def get_rect_removal(self, rect): - rect.generate_target() - rect.target.stretch(0, 0, about_edge=LEFT) - rect.target.set_stroke(width=0) - return MoveToTarget(rect) - - -class GeneralWrapper(Scene): - CONFIG = { - "title_text": "" - } - - def construct(self): - title = TextMobject(self.title_text) - title.to_edge(UP) - rect = ScreenRectangle(height=6.5) - rect.next_to(title, DOWN) - self.play(Write(title), ShowCreation(rect)) - self.wait() - - -class ThinkTwiceWrapper(GeneralWrapper): - CONFIG = {"title_text": "Think Twice"} - - -class LeiosOSWrapper(GeneralWrapper): - CONFIG = {"title_text": "LeiosOS"} - - -class WelchLabsWrapper(GeneralWrapper): - CONFIG = {"title_text": "Welch Labs"} - - -class InfinityPlusOneWrapper(GeneralWrapper): - CONFIG = {"title_text": "Infinity Plus One"} - - -class EndScreen(PiCreatureScene): - CONFIG = { - "seconds_to_blink": 3, - } - - def construct(self): - words = TextMobject("Clicky stuffs") - words.scale(1.5) - words.next_to(self.pi_creature, UP) - words.to_edge(UP) - - self.play( - FadeIn( - words, - run_time=2, - lag_ratio=0.5 - ), - self.pi_creature.change_mode, "hooray" - ) - self.wait() - mode_point_pairs = [ - ("raise_left_hand", 5 * LEFT + 3 * UP), - ("raise_right_hand", 5 * RIGHT + 3 * UP), - ("thinking", 5 * LEFT + 2 * DOWN), - ("thinking", 5 * RIGHT + 2 * DOWN), - ("thinking", 5 * RIGHT + 2 * DOWN), - ("happy", 5 * LEFT + 3 * UP), - ("raise_right_hand", 5 * RIGHT + 3 * UP), - ] - for mode, point in mode_point_pairs: - self.play(self.pi_creature.change, mode, point) - self.wait() - self.wait(3) - - def create_pi_creature(self): - self.pi_creature = Randolph() - self.pi_creature.shift(2 * DOWN + 1.5 * LEFT) - return self.pi_creature diff --git a/from_3b1b/old/for_flammy.py b/from_3b1b/old/for_flammy.py deleted file mode 100644 index fbd7045f..00000000 --- a/from_3b1b/old/for_flammy.py +++ /dev/null @@ -1,339 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.sphere_area import * - - -class MadAtMathologer(PiCreatureScene): - def create_pi_creature(self): - return Mortimer().to_corner(DR) - - def construct(self): - morty = self.pi_creature - self.play(morty.change, "angry") - self.wait(3) - self.play(morty.change, "heistant") - self.wait(2) - self.play(morty.change, "shruggie") - self.wait(3) - - -class JustTheIntegral(Scene): - def construct(self): - tex = TexMobject("\\int_0^{\\pi / 2} \\cos(\\theta)d\\theta") - tex.scale(2) - self.add(tex) - - -class SphereVideoWrapper(Scene): - def construct(self): - title = TextMobject("Surface area of a sphere") - title.scale(1.5) - title.to_edge(UP) - rect = ScreenRectangle(height=6) - rect.next_to(title, DOWN) - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - -class SphereRings(SecondProof): - CONFIG = { - "sphere_config": { - "resolution": (60, 60), - }, - } - - def construct(self): - self.setup_shapes() - self.grow_rings() - self.show_one_ring() - self.show_radial_line() - self.show_thickness() - self.flash_through_rings() - - def grow_rings(self): - sphere = self.sphere - rings = self.rings - north_rings = rings[:len(rings) // 2] - sphere.set_fill(opacity=0) - sphere.set_stroke(WHITE, 0.5, opacity=0.5) - southern_mesh = VGroup(*[ - face.copy() for face in sphere - if face.get_center()[2] < 0 - ]) - southern_mesh.set_stroke(WHITE, 0.1, 0.5) - - self.play(Write(sphere)) - self.wait() - self.play( - FadeOut(sphere), - FadeIn(southern_mesh), - FadeIn(north_rings), - ) - self.wait(4) - - self.north_rings = north_rings - self.southern_mesh = southern_mesh - - def show_one_ring(self): - north_rings = self.north_rings - index = len(north_rings) // 2 - ring = north_rings[index] - to_fade = VGroup(*[ - nr for nr in north_rings - if nr is not ring - ]) - - north_rings.save_state() - - circle = Circle() - circle.set_stroke(PINK, 5) - circle.set_width(ring.get_width()) - circle.move_to(ring, IN) - - thickness = ring.get_depth() * np.sqrt(2) - brace = Brace(Line(ORIGIN, 0.2 * RIGHT), UP) - brace.set_width(thickness) - brace.rotate(90 * DEGREES, RIGHT) - brace.rotate(45 * DEGREES, UP) - brace.move_to(1.5 * (RIGHT + OUT)) - brace.set_stroke(WHITE, 1) - word = TextMobject("Thickness") - word.rotate(90 * DEGREES, RIGHT) - word.next_to(brace, RIGHT + OUT, buff=0) - - self.play( - to_fade.set_fill, {"opacity": 0.2}, - to_fade.set_stroke, {"opacity": 0.0}, - ) - self.move_camera( - phi=0, theta=-90 * DEGREES, - run_time=2, - ) - self.stop_ambient_camera_rotation() - self.play(ShowCreation(circle)) - self.play(FadeOut(circle)) - self.move_camera( - phi=70 * DEGREES, - theta=-100 * DEGREES, - run_time=2, - ) - self.begin_ambient_camera_rotation(0.02) - self.play( - GrowFromCenter(brace), - Write(word), - ) - self.wait(2) - self.play(FadeOut(VGroup(brace, word))) - - self.circum_circle = circle - self.thickness_label = VGroup(brace, word) - self.ring = ring - - def show_radial_line(self): - ring = self.ring - - point = ring.get_corner(RIGHT + IN) - R_line = Line(ORIGIN, point) - xy_line = Line(ORIGIN, self.sphere.get_right()) - theta = np.arccos(np.dot( - normalize(R_line.get_vector()), - normalize(xy_line.get_vector()) - )) - arc = Arc(angle=theta, radius=0.5) - arc.rotate(90 * DEGREES, RIGHT, about_point=ORIGIN) - - theta = TexMobject("\\theta") - theta.rotate(90 * DEGREES, RIGHT) - theta.next_to(arc, RIGHT) - theta.shift(SMALL_BUFF * (LEFT + OUT)) - - R_label = TexMobject("R") - R_label.rotate(90 * DEGREES, RIGHT) - R_label.next_to( - R_line.get_center(), OUT + LEFT, - buff=SMALL_BUFF - ) - VGroup(R_label, R_line).set_color(YELLOW) - - z_axis_point = np.array(point) - z_axis_point[:2] = 0 - r_line = DashedLine(z_axis_point, point) - r_line.set_color(RED) - r_label = TexMobject("R\\cos(\\theta)") - r_label.rotate(90 * DEGREES, RIGHT) - r_label.scale(0.7) - r_label.match_color(r_line) - r_label.set_stroke(width=0, background=True) - r_label.next_to(r_line, OUT, 0.5 * SMALL_BUFF) - - VGroup( - R_label, xy_line, arc, R_label, - r_line, r_label, - ).set_shade_in_3d(True) - - # self.stop_ambient_camera_rotation() - self.move_camera( - phi=85 * DEGREES, - theta=-100 * DEGREES, - added_anims=[ - ring.set_fill, {"opacity": 0.5}, - ring.set_stroke, {"opacity": 0.1}, - ShowCreation(R_line), - FadeInFrom(R_label, IN), - ] - ) - self.wait() - self.play( - FadeIn(xy_line), - ShowCreation(arc), - Write(theta), - ) - self.wait() - self.play( - ShowCreation(r_line), - FadeInFrom(r_label, IN), - ) - self.wait() - self.move_camera( - phi=70 * DEGREES, - theta=-110 * DEGREES, - run_time=3 - ) - self.wait(2) - - def show_thickness(self): - brace, word = self.thickness_label - R_dtheta = TexMobject("R \\, d\\theta") - R_dtheta.rotate(90 * DEGREES, RIGHT) - R_dtheta.move_to(word, LEFT) - - self.play( - GrowFromCenter(brace), - Write(R_dtheta) - ) - self.wait(3) - - def flash_through_rings(self): - rings = self.north_rings.copy() - rings.fade(1) - rings.sort(lambda p: p[2]) - - for x in range(8): - self.play(LaggedStartMap( - ApplyMethod, rings, - lambda m: (m.set_fill, PINK, 0.5), - rate_func=there_and_back, - lag_ratio=0.1, - run_time=2, - )) - - -class IntegralSymbols(Scene): - def construct(self): - int_sign = TexMobject("\\displaystyle \\int") - int_sign.set_height(1.5) - int_sign.move_to(5 * LEFT) - - circumference, times, thickness = ctt = TextMobject( - "circumference", "$\\times$", "thickness" - ) - circumference.set_color(MAROON_B) - ctt.next_to(int_sign, RIGHT, SMALL_BUFF) - area_brace = Brace(ctt, DOWN) - area_text = area_brace.get_text("Area of a ring") - - all_rings = TextMobject("All rings") - all_rings.scale(0.5) - all_rings.next_to(int_sign, DOWN, SMALL_BUFF) - all_rings.shift(SMALL_BUFF * LEFT) - - circum_formula = TexMobject( - "2\\pi", "R\\cos(\\theta)", - ) - circum_formula[1].set_color(RED) - circum_formula.move_to(circumference) - circum_brace = Brace(circum_formula, UP) - - R_dtheta = TexMobject("R \\, d\\theta") - R_dtheta.move_to(thickness, LEFT) - R_dtheta_brace = Brace(R_dtheta, UP) - - zero, pi_halves = bounds = TexMobject("0", "\\pi / 2") - bounds.scale(0.5) - zero.move_to(all_rings) - pi_halves.next_to(int_sign, UP, SMALL_BUFF) - pi_halves.shift(SMALL_BUFF * RIGHT) - - self.add(int_sign) - self.play( - GrowFromCenter(area_brace), - FadeInFrom(area_text, UP), - ) - self.wait() - self.play(FadeInFromDown(circumference)) - self.play( - FadeInFromDown(thickness), - Write(times) - ) - self.play(Write(all_rings)) - self.wait() - - self.play( - circumference.next_to, circum_brace, UP, MED_SMALL_BUFF, - circumference.shift, SMALL_BUFF * UR, - GrowFromCenter(circum_brace), - ) - self.play(FadeInFrom(circum_formula, UP)) - self.wait() - self.play( - thickness.next_to, circumference, RIGHT, MED_SMALL_BUFF, - GrowFromCenter(R_dtheta_brace), - area_brace.stretch, 0.84, 0, {"about_edge": LEFT}, - MaintainPositionRelativeTo(area_text, area_brace), - ) - self.play(FadeInFrom(R_dtheta, UP)) - self.wait() - self.play(ReplacementTransform(all_rings, bounds)) - self.wait() - - # RHS - rhs = TexMobject( - "\\displaystyle =", "2\\pi R^2", "\\int_0^{\\pi / 2}", - "\\cos(\\theta)", "d\\theta", - ) - rhs.set_color_by_tex("cos", RED) - rhs.next_to(R_dtheta, RIGHT) - int_brace = Brace(rhs[2:], DOWN) - q_marks = int_brace.get_text("???") - one = TexMobject("1") - one.move_to(q_marks) - - self.play(FadeInFrom(rhs, 4 * LEFT)) - self.wait() - self.play(ShowCreationThenFadeAround(rhs[1])) - self.wait() - self.play(ShowCreationThenFadeAround(rhs[2:])) - self.wait() - self.play( - GrowFromCenter(int_brace), - LaggedStartMap( - FadeInFrom, q_marks, - lambda m: (m, UP), - ) - ) - self.wait() - self.play(ReplacementTransform(q_marks, one)) - self.wait() - - -class ShamelessPlug(TeacherStudentsScene): - def construct(self): - self.student_says( - "But why $4\\pi R^2$?", - target_mode="maybe" - ) - self.change_student_modes( - "erm", "maybe", "happy", - added_anims=[self.teacher.change, "happy"] - ) - self.wait(3) diff --git a/from_3b1b/old/fourier.py b/from_3b1b/old/fourier.py deleted file mode 100644 index 43fa1a4c..00000000 --- a/from_3b1b/old/fourier.py +++ /dev/null @@ -1,4291 +0,0 @@ -# -*- coding: utf-8 -*- -from constants import * -import scipy.integrate - -from manimlib.imports import * - -USE_ALMOST_FOURIER_BY_DEFAULT = True -NUM_SAMPLES_FOR_FFT = 1000 -DEFAULT_COMPLEX_TO_REAL_FUNC = lambda z : z.real - - -def get_fourier_graph( - axes, time_func, t_min, t_max, - n_samples = NUM_SAMPLES_FOR_FFT, - complex_to_real_func = lambda z : z.real, - color = RED, - ): - # N = n_samples - # T = time_range/n_samples - time_range = float(t_max - t_min) - time_step_size = time_range/n_samples - time_samples = np.vectorize(time_func)(np.linspace(t_min, t_max, n_samples)) - fft_output = np.fft.fft(time_samples) - frequencies = np.linspace(0.0, n_samples/(2.0*time_range), n_samples//2) - # #Cycles per second of fouier_samples[1] - # (1/time_range)*n_samples - # freq_step_size = 1./time_range - graph = VMobject() - graph.set_points_smoothly([ - axes.coords_to_point( - x, complex_to_real_func(y)/n_samples, - ) - for x, y in zip(frequencies, fft_output[:n_samples//2]) - ]) - graph.set_color(color) - f_min, f_max = [ - axes.x_axis.point_to_number(graph.points[i]) - for i in (0, -1) - ] - graph.underlying_function = lambda f : axes.y_axis.point_to_number( - graph.point_from_proportion((f - f_min)/(f_max - f_min)) - ) - return graph - -def get_fourier_transform( - func, t_min, t_max, - complex_to_real_func = DEFAULT_COMPLEX_TO_REAL_FUNC, - use_almost_fourier = USE_ALMOST_FOURIER_BY_DEFAULT, - **kwargs ##Just eats these - ): - scalar = 1./(t_max - t_min) if use_almost_fourier else 1.0 - def fourier_transform(f): - z = scalar*scipy.integrate.quad( - lambda t : func(t)*np.exp(complex(0, -TAU*f*t)), - t_min, t_max - )[0] - return complex_to_real_func(z) - return fourier_transform - -## - -class Introduction(TeacherStudentsScene): - def construct(self): - title = TextMobject("Fourier Transform") - title.scale(1.2) - title.to_edge(UP, buff = MED_SMALL_BUFF) - - func = lambda t : np.cos(2*TAU*t) + np.cos(3*TAU*t) - graph = FunctionGraph(func, x_min = 0, x_max = 5) - graph.stretch(0.25, 1) - graph.next_to(title, DOWN) - graph.to_edge(LEFT) - graph.set_color(BLUE) - fourier_graph = FunctionGraph( - get_fourier_transform(func, 0, 5), - x_min = 0, x_max = 5 - ) - fourier_graph.move_to(graph) - fourier_graph.to_edge(RIGHT) - fourier_graph.set_color(RED) - arrow = Arrow(graph, fourier_graph, color = WHITE) - self.add(title, graph) - - self.student_thinks( - "What's that?", - look_at_arg = title, - target_mode = "confused", - student_index = 1, - ) - self.play( - GrowArrow(arrow), - ReplacementTransform(graph.copy(), fourier_graph) - ) - self.wait(2) - self.student_thinks( - "Pssht, I got this", - target_mode = "tease", - student_index = 2, - added_anims = [RemovePiCreatureBubble(self.students[1])] - ) - self.play(self.teacher.change, "hesitant") - self.wait(2) - -class TODOInsertUnmixingSound(TODOStub): - CONFIG = { - "message" : "Show unmixing sound" - } - -class OtherContexts(PiCreatureScene): - def construct(self): - items = VGroup(*list(map(TextMobject, [ - "Extracting frequencies from sound", - "Uncertainty principle", - "Riemann Zeta function and primes", - "Differential equations", - ]))) - items.arrange( - DOWN, buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - items.to_corner(UP+LEFT) - items[1:].set_fill(opacity = 0.2) - - morty = self.pi_creature - morty.to_corner(UP+RIGHT) - - self.add(items) - for item in items[1:]: - self.play( - LaggedStartMap( - ApplyMethod, item, - lambda m : (m.set_fill, {"opacity" : 1}), - ), - morty.change, "thinking", - ) - self.wait() - -class TODOInsertCosineWrappingAroundCircle(TODOStub): - CONFIG = { - "message" : "Give a picture-in-picture \\\\ of cosine wrapping around circle", - } - -class AddingPureFrequencies(PiCreatureScene): - CONFIG = { - "A_frequency" : 2.1, - "A_color" : YELLOW, - "D_color" : PINK, - "F_color" : TEAL, - "C_color" : RED, - "sum_color" : GREEN, - "equilibrium_height" : 1.5, - } - def construct(self): - self.add_speaker() - self.play_A440() - self.measure_air_pressure() - self.play_lower_pitch() - self.play_mix() - self.separate_out_parts() - self.draw_sum_at_single_point() - self.draw_full_sum() - self.add_more_notes() - - def add_speaker(self): - speaker = SVGMobject(file_name = "speaker") - speaker.to_edge(DOWN) - - self.add(speaker) - self.speaker = speaker - - def play_A440(self): - randy = self.pi_creature - A_label = TextMobject("A440") - A_label.set_color(self.A_color) - A_label.next_to(self.speaker, UP) - - self.broadcast( - FadeIn(A_label), - Succession( - ApplyMethod, randy.change, "pondering", - Animation, randy, - Blink, randy - ) - ) - - self.set_variables_as_attrs(A_label) - - def measure_air_pressure(self): - randy = self.pi_creature - axes = Axes( - y_min = -2, y_max = 2, - x_min = 0, x_max = 10, - axis_config = {"include_tip" : False}, - ) - axes.stretch_to_fit_height(2) - axes.to_corner(UP+LEFT) - axes.shift(LARGE_BUFF*DOWN) - eh = self.equilibrium_height - equilibrium_line = DashedLine( - axes.coords_to_point(0, eh), - axes.coords_to_point(axes.x_max, eh), - stroke_width = 2, - stroke_color = LIGHT_GREY - ) - - frequency = self.A_frequency - graph = self.get_wave_graph(frequency, axes) - func = graph.underlying_function - graph.set_color(self.A_color) - pressure = TextMobject("Pressure") - time = TextMobject("Time") - for label in pressure, time: - label.scale_in_place(0.8) - pressure.next_to(axes.y_axis, UP) - pressure.to_edge(LEFT, buff = MED_SMALL_BUFF) - time.next_to(axes.x_axis.get_right(), DOWN+LEFT) - axes.labels = VGroup(pressure, time) - - n = 10 - brace = Brace(Line( - axes.coords_to_point(n/frequency, func(n/frequency)), - axes.coords_to_point((n+1)/frequency, func((n+1)/frequency)), - ), UP) - words = brace.get_text("Imagine 440 per second", buff = SMALL_BUFF) - words.scale(0.8, about_point = words.get_bottom()) - - self.play( - FadeIn(pressure), - ShowCreation(axes.y_axis) - ) - self.play( - Write(time), - ShowCreation(axes.x_axis) - ) - self.broadcast( - ShowCreation(graph, run_time = 4, rate_func=linear), - ShowCreation(equilibrium_line), - ) - axes.add(equilibrium_line) - self.play( - randy.change, "erm", graph, - GrowFromCenter(brace), - Write(words) - ) - self.wait() - graph.save_state() - self.play( - FadeOut(brace), - FadeOut(words), - VGroup(axes, graph, axes.labels).shift, 0.8*UP, - graph.fade, 0.85, - graph.shift, 0.8*UP, - ) - - graph.saved_state.move_to(graph) - self.set_variables_as_attrs(axes, A_graph = graph) - - def play_lower_pitch(self): - axes = self.axes - randy = self.pi_creature - - frequency = self.A_frequency*(2.0/3.0) - graph = self.get_wave_graph(frequency, axes) - graph.set_color(self.D_color) - - D_label = TextMobject("D294") - D_label.set_color(self.D_color) - D_label.move_to(self.A_label) - - self.play( - FadeOut(self.A_label), - GrowFromCenter(D_label), - ) - self.broadcast( - ShowCreation(graph, run_time = 4, rate_func=linear), - randy.change, "happy", - n_circles = 6, - ) - self.play(randy.change, "confused", graph) - self.wait(2) - - self.set_variables_as_attrs( - D_label, - D_graph = graph - ) - - def play_mix(self): - self.A_graph.restore() - self.broadcast( - self.get_broadcast_animation(n_circles = 6), - self.pi_creature.change, "thinking", - *[ - ShowCreation(graph, run_time = 4, rate_func=linear) - for graph in (self.A_graph, self.D_graph) - ] - ) - self.wait() - - def separate_out_parts(self): - axes = self.axes - speaker = self.speaker - randy = self.pi_creature - - A_axes = axes.deepcopy() - A_graph = self.A_graph - A_label = self.A_label - D_axes = axes.deepcopy() - D_graph = self.D_graph - D_label = self.D_label - movers = [A_axes, A_graph, A_label, D_axes, D_graph, D_label] - for mover in movers: - mover.generate_target() - D_target_group = VGroup(D_axes.target, D_graph.target) - A_target_group = VGroup(A_axes.target, A_graph.target) - D_target_group.next_to(axes, DOWN, MED_LARGE_BUFF) - A_target_group.next_to(D_target_group, DOWN, MED_LARGE_BUFF) - A_label.fade(1) - A_label.target.next_to(A_graph.target, UP) - D_label.target.next_to(D_graph.target, UP) - - self.play(*it.chain( - list(map(MoveToTarget, movers)), - [ - ApplyMethod(mob.shift, FRAME_Y_RADIUS*DOWN, remover = True) - for mob in (randy, speaker) - ] - )) - self.wait() - - self.set_variables_as_attrs(A_axes, D_axes) - - def draw_sum_at_single_point(self): - axes = self.axes - A_axes = self.A_axes - D_axes = self.D_axes - A_graph = self.A_graph - D_graph = self.D_graph - - x = 2.85 - A_line = self.get_A_graph_v_line(x) - D_line = self.get_D_graph_v_line(x) - lines = VGroup(A_line, D_line) - sum_lines = lines.copy() - sum_lines.generate_target() - self.stack_v_lines(x, sum_lines.target) - - top_axes_point = axes.coords_to_point(x, self.equilibrium_height) - x_point = np.array(top_axes_point) - x_point[1] = 0 - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS).move_to(x_point) - - self.revert_to_original_skipping_status() - self.play(GrowFromCenter(v_line)) - self.play(FadeOut(v_line)) - self.play(*list(map(ShowCreation, lines))) - self.wait() - self.play(MoveToTarget(sum_lines, path_arc = np.pi/4)) - self.wait(2) - # self.play(*[ - # Transform( - # line, - # VectorizedPoint(axes.coords_to_point(0, self.equilibrium_height)), - # remover = True - # ) - # for line, axes in [ - # (A_line, A_axes), - # (D_line, D_axes), - # (sum_lines, axes), - # ] - # ]) - self.lines_to_fade = VGroup(A_line, D_line, sum_lines) - - def draw_full_sum(self): - axes = self.axes - - def new_func(x): - result = self.A_graph.underlying_function(x) - result += self.D_graph.underlying_function(x) - result -= self.equilibrium_height - return result - - sum_graph = axes.get_graph(new_func) - sum_graph.set_color(self.sum_color) - thin_sum_graph = sum_graph.copy().fade() - - A_graph = self.A_graph - D_graph = self.D_graph - D_axes = self.D_axes - - rect = Rectangle( - height = 2.5*FRAME_Y_RADIUS, - width = MED_SMALL_BUFF, - stroke_width = 0, - fill_color = YELLOW, - fill_opacity = 0.4 - ) - - self.play( - ReplacementTransform(A_graph.copy(), thin_sum_graph), - ReplacementTransform(D_graph.copy(), thin_sum_graph), - # FadeOut(self.lines_to_fade) - ) - self.play( - self.get_graph_line_animation(self.A_axes, self.A_graph), - self.get_graph_line_animation(self.D_axes, self.D_graph), - self.get_graph_line_animation(axes, sum_graph.deepcopy()), - ShowCreation(sum_graph), - run_time = 15, - rate_func=linear - ) - self.remove(thin_sum_graph) - self.wait() - for x in 2.85, 3.57: - rect.move_to(D_axes.coords_to_point(x, 0)) - self.play(GrowFromPoint(rect, rect.get_top())) - self.wait() - self.play(FadeOut(rect)) - - self.sum_graph = sum_graph - - def add_more_notes(self): - axes = self.axes - - A_group = VGroup(self.A_axes, self.A_graph, self.A_label) - D_group = VGroup(self.D_axes, self.D_graph, self.D_label) - squish_group = VGroup(A_group, D_group) - squish_group.generate_target() - squish_group.target.stretch(0.5, 1) - squish_group.target.next_to(axes, DOWN, buff = -SMALL_BUFF) - for group in squish_group.target: - label = group[-1] - bottom = label.get_bottom() - label.stretch_in_place(0.5, 0) - label.move_to(bottom, DOWN) - - self.play( - MoveToTarget(squish_group), - FadeOut(self.lines_to_fade), - ) - - F_axes = self.D_axes.deepcopy() - C_axes = self.A_axes.deepcopy() - VGroup(F_axes, C_axes).next_to(squish_group, DOWN) - F_graph = self.get_wave_graph(self.A_frequency*4.0/5, F_axes) - F_graph.set_color(self.F_color) - C_graph = self.get_wave_graph(self.A_frequency*6.0/5, C_axes) - C_graph.set_color(self.C_color) - - F_label = TextMobject("F349") - C_label = TextMobject("C523") - for label, graph in (F_label, F_graph), (C_label, C_graph): - label.scale(0.5) - label.set_color(graph.get_stroke_color()) - label.next_to(graph, UP, SMALL_BUFF) - - graphs = VGroup(self.A_graph, self.D_graph, F_graph, C_graph) - def new_sum_func(x): - result = sum([ - graph.underlying_function(x) - self.equilibrium_height - for graph in graphs - ]) - result *= 0.5 - return result + self.equilibrium_height - new_sum_graph = self.axes.get_graph( - new_sum_func, - num_graph_points = 200 - ) - new_sum_graph.set_color(BLUE_C) - thin_new_sum_graph = new_sum_graph.copy().fade() - - self.play(*it.chain( - list(map(ShowCreation, [F_axes, C_axes, F_graph, C_graph])), - list(map(Write, [F_label, C_label])), - list(map(FadeOut, [self.sum_graph])) - )) - self.play(ReplacementTransform( - graphs.copy(), thin_new_sum_graph - )) - kwargs = {"rate_func" : None, "run_time" : 10} - self.play(ShowCreation(new_sum_graph.copy(), **kwargs), *[ - self.get_graph_line_animation(curr_axes, graph, **kwargs) - for curr_axes, graph in [ - (self.A_axes, self.A_graph), - (self.D_axes, self.D_graph), - (F_axes, F_graph), - (C_axes, C_graph), - (axes, new_sum_graph), - ] - ]) - self.wait() - - #### - - def broadcast(self, *added_anims, **kwargs): - self.play(self.get_broadcast_animation(**kwargs), *added_anims) - - def get_broadcast_animation(self, **kwargs): - kwargs["run_time"] = kwargs.get("run_time", 5) - kwargs["n_circles"] = kwargs.get("n_circles", 10) - return Broadcast(self.speaker[1], **kwargs) - - def get_wave_graph(self, frequency, axes): - tail_len = 3.0 - x_min, x_max = axes.x_min, axes.x_max - def func(x): - value = 0.7*np.cos(2*np.pi*frequency*x) - if x - x_min < tail_len: - value *= smooth((x-x_min)/tail_len) - if x_max - x < tail_len: - value *= smooth((x_max - x )/tail_len) - return value + self.equilibrium_height - ngp = 2*(x_max - x_min)*frequency + 1 - graph = axes.get_graph(func, num_graph_points = int(ngp)) - return graph - - def get_A_graph_v_line(self, x): - return self.get_graph_v_line(x, self.A_axes, self.A_graph) - - def get_D_graph_v_line(self, x): - return self.get_graph_v_line(x, self.D_axes, self.D_graph) - - def get_graph_v_line(self, x, axes, graph): - result = Line( - axes.coords_to_point(x, self.equilibrium_height), - # axes.coords_to_point(x, graph.underlying_function(x)), - graph.point_from_proportion(float(x)/axes.x_max), - color = WHITE, - buff = 0, - ) - return result - - def stack_v_lines(self, x, lines): - point = self.axes.coords_to_point(x, self.equilibrium_height) - A_line, D_line = lines - A_line.shift(point - A_line.get_start()) - D_line.shift(A_line.get_end()-D_line.get_start()) - A_line.set_color(self.A_color) - D_line.set_color(self.D_color) - return lines - - def create_pi_creature(self): - return Randolph().to_corner(DOWN+LEFT) - - def get_graph_line_animation(self, axes, graph, **kwargs): - line = self.get_graph_v_line(0, axes, graph) - x_max = axes.x_max - def update_line(line, alpha): - x = alpha*x_max - Transform(line, self.get_graph_v_line(x, axes, graph)).update(1) - return line - - return UpdateFromAlphaFunc(line, update_line, **kwargs) - -class BreakApartSum(Scene): - CONFIG = { - "frequencies" : [0.5, 1.5, 2, 2.5, 5], - "equilibrium_height" : 2.0, - } - def construct(self): - self.show_initial_sound() - self.decompose_sound() - self.ponder_question() - - def show_initial_sound(self): - def func(x): - return self.equilibrium_height + 0.2*np.sum([ - np.cos(2*np.pi*f*x) - for f in self.frequencies - ]) - axes = Axes( - x_min = 0, x_max = 5, - y_min = -1, y_max = 5, - x_axis_config = { - "include_tip" : False, - "unit_size" : 2.0, - }, - y_axis_config = { - "include_tip" : False, - "unit_size" : 0.5, - }, - ) - axes.stretch_to_fit_width(FRAME_WIDTH - 2) - axes.stretch_to_fit_height(3) - axes.center() - axes.to_edge(LEFT) - graph = axes.get_graph(func, num_graph_points = 200) - graph.set_color(YELLOW) - - v_line = Line(ORIGIN, 4*UP) - v_line.move_to(axes.coords_to_point(0, 0), DOWN) - dot = Dot(color = PINK) - dot.move_to(graph.point_from_proportion(0)) - - self.add(axes, graph) - self.play( - v_line.move_to, axes.coords_to_point(5, 0), DOWN, - MoveAlongPath(dot, graph), - run_time = 8, - rate_func=linear, - ) - self.play(*list(map(FadeOut, [dot, v_line]))) - - self.set_variables_as_attrs(axes, graph) - - def decompose_sound(self): - axes, graph = self.axes, self.graph - - pure_graphs = VGroup(*[ - axes.get_graph( - lambda x : 0.5*np.cos(2*np.pi*freq*x), - num_graph_points = 100, - ) - for freq in self.frequencies - ]) - pure_graphs.set_color_by_gradient(BLUE, RED) - pure_graphs.arrange(DOWN, buff = MED_LARGE_BUFF) - h_line = DashedLine(6*LEFT, 6*RIGHT) - - self.play( - FadeOut(axes), - graph.to_edge, UP - ) - pure_graphs.next_to(graph, DOWN, LARGE_BUFF) - h_line.next_to(graph, DOWN, MED_LARGE_BUFF) - self.play(ShowCreation(h_line)) - for pure_graph in reversed(pure_graphs): - self.play(ReplacementTransform(graph.copy(), pure_graph)) - self.wait() - - self.all_graphs = VGroup(graph, h_line, pure_graphs) - self.pure_graphs = pure_graphs - - def ponder_question(self): - all_graphs = self.all_graphs - pure_graphs = self.pure_graphs - randy = Randolph() - randy.to_corner(DOWN+LEFT) - - self.play( - FadeIn(randy), - all_graphs.scale, 0.75, - all_graphs.to_corner, UP+RIGHT, - ) - self.play(randy.change, "pondering", all_graphs) - self.play(Blink(randy)) - rect = SurroundingRectangle(pure_graphs, color = WHITE) - self.play( - ShowCreation(rect), - LaggedStartMap( - ApplyFunction, pure_graphs, - lambda g : (lambda m : m.shift(SMALL_BUFF*UP).set_color(YELLOW), g), - rate_func = wiggle - ) - ) - self.play(FadeOut(rect)) - self.play(Blink(randy)) - self.wait() - -class Quadrant(VMobject): - CONFIG = { - "radius" : 2, - "stroke_width": 0, - "fill_opacity" : 1, - "density" : 50, - "density_exp" : 2.0, - } - def init_points(self): - points = [r*RIGHT for r in np.arange(0, self.radius, 1./self.density)] - points += [ - self.radius*(np.cos(theta)*RIGHT + np.sin(theta)*UP) - for theta in np.arange(0, TAU/4, 1./(self.radius*self.density)) - ] - points += [r*UP for r in np.arange(self.radius, 0, -1./self.density)] - self.set_points_smoothly(points) - -class UnmixMixedPaint(Scene): - CONFIG = { - "colors" : [BLUE, RED, YELLOW, GREEN], - } - def construct(self): - angles = np.arange(4)*np.pi/2 - quadrants = VGroup(*[ - Quadrant().rotate(angle, about_point = ORIGIN).set_color(color) - for color, angle in zip(self.colors, angles) - ]) - quadrants.add(*it.chain(*[ - quadrants.copy().rotate(angle) - for angle in np.linspace(0, 0.02*TAU, 10) - ])) - quadrants.set_fill(opacity = 0.5) - - mud_color = average_color(*self.colors) - mud_circle = Circle(radius = 2, stroke_width = 0) - mud_circle.set_fill(mud_color, 1) - mud_circle.save_state() - mud_circle.scale(0) - - def update_quadrant(quadrant, alpha): - points = quadrant.get_anchors() - dt = 0.03 #Hmm, this has no dependency on frame rate... - norms = np.apply_along_axis(get_norm, 1, points) - - points[:,0] -= dt*points[:,1]/np.clip(norms, 0.1, np.inf) - points[:,1] += dt*points[:,0]/np.clip(norms, 0.1, np.inf) - - new_norms = np.apply_along_axis(get_norm, 1, points) - new_norms = np.clip(new_norms, 0.001, np.inf) - radius = np.max(norms) - multiplier = norms/new_norms - multiplier = multiplier.reshape((len(multiplier), 1)) - multiplier.repeat(points.shape[1], axis = 1) - points *= multiplier - quadrant.set_points_smoothly(points) - - self.add(quadrants) - run_time = 30 - self.play( - *[ - UpdateFromAlphaFunc(quadrant, update_quadrant) - for quadrant in quadrants - ] + [ - ApplyMethod(mud_circle.restore, rate_func=linear) - ], - run_time = run_time - ) - -#Incomplete, and probably not useful -class MachineThatTreatsOneFrequencyDifferently(Scene): - def construct(self): - graph = self.get_cosine_graph(0.5) - frequency_mob = DecimalNumber(220, num_decimal_places = 0) - frequency_mob.next_to(graph, UP, buff = MED_LARGE_BUFF) - - self.graph = graph - self.frequency_mob = frequency_mob - self.add(graph, frequency_mob) - - arrow1, q_marks, arrow2 = group = VGroup( - Vector(DOWN), TextMobject("???").scale(1.5), Vector(DOWN) - ) - group.set_color(WHITE) - group.arrange(DOWN) - group.next_to(graph, DOWN) - self.add(group) - - self.change_graph_frequency(1) - graph.set_color(GREEN) - self.wait() - graph.set_color(YELLOW) - self.change_graph_frequency(2) - self.wait() - - - def change_graph_frequency(self, frequency, run_time = 2): - graph = self.graph - frequency_mob = self.frequency_mob - curr_frequency = graph.frequency - self.play( - UpdateFromAlphaFunc( - graph, self.get_signal_update_func(graph, frequency), - ), - ChangingDecimal( - frequency_mob, - lambda a : 440*interpolate(curr_frequency, frequency, a) - ), - run_time = run_time, - ) - graph.frequency = frequency - - def get_signal_update_func(self, graph, target_frequency): - curr_frequency = graph.frequency - def update(graph, alpha): - frequency = interpolate(curr_frequency, target_frequency, alpha) - new_graph = self.get_cosine_graph(frequency) - Transform(graph, new_graph).update(1) - return graph - return update - - def get_cosine_graph(self, frequency, num_steps = 200, color = YELLOW): - result = FunctionGraph( - lambda x : 0.5*np.cos(2*np.pi*frequency*x), - num_steps = num_steps - ) - result.frequency = frequency - result.shift(2*UP) - return result - -class FourierMachineScene(Scene): - CONFIG = { - "time_axes_config" : { - "x_min" : 0, - "x_max" : 4.4, - "x_axis_config" : { - "unit_size" : 3, - "tick_frequency" : 0.25, - "numbers_with_elongated_ticks" : [1, 2, 3], - }, - "y_min" : 0, - "y_max" : 2, - "y_axis_config" : {"unit_size" : 0.8}, - }, - "time_label_t" : 3.4, - "circle_plane_config" : { - "x_radius" : 2.1, - "y_radius" : 2.1, - "x_unit_size" : 1, - "y_unit_size" : 1, - }, - "frequency_axes_config" : { - "axis_config" : { - "color" : TEAL, - }, - "x_min" : 0, - "x_max" : 5.0, - "x_axis_config" : { - "unit_size" : 1.4, - "numbers_to_show" : list(range(1, 6)), - }, - "y_min" : -1.0, - "y_max" : 1.0, - "y_axis_config" : { - "unit_size" : 1.8, - "tick_frequency" : 0.5, - "label_direction" : LEFT, - }, - "color" : TEAL, - }, - "frequency_axes_box_color" : TEAL_E, - "text_scale_val" : 0.75, - "default_graph_config" : { - "num_graph_points" : 100, - "color" : YELLOW, - }, - "equilibrium_height" : 1, - "default_y_vector_animation_config" : { - "run_time" : 5, - "rate_func" : None, - "remover" : True, - }, - "default_time_sweep_config" : { - "rate_func" : None, - "run_time" : 5, - }, - "default_num_v_lines_indicating_periods" : 20, - } - - def get_time_axes(self): - time_axes = Axes(**self.time_axes_config) - time_axes.x_axis.add_numbers() - time_label = TextMobject("Time") - intensity_label = TextMobject("Intensity") - labels = VGroup(time_label, intensity_label) - for label in labels: - label.scale(self.text_scale_val) - time_label.next_to( - time_axes.coords_to_point(self.time_label_t,0), - DOWN - ) - intensity_label.next_to(time_axes.y_axis.get_top(), RIGHT) - time_axes.labels = labels - time_axes.add(labels) - time_axes.to_corner(UP+LEFT) - self.time_axes = time_axes - return time_axes - - def get_circle_plane(self): - circle_plane = NumberPlane(**self.circle_plane_config) - circle_plane.to_corner(DOWN+LEFT) - circle = DashedLine(ORIGIN, TAU*UP).apply_complex_function(np.exp) - circle.scale(circle_plane.x_unit_size) - circle.move_to(circle_plane.coords_to_point(0, 0)) - circle_plane.circle = circle - circle_plane.add(circle) - circle_plane.fade() - self.circle_plane = circle_plane - return circle_plane - - def get_frequency_axes(self): - frequency_axes = Axes(**self.frequency_axes_config) - frequency_axes.x_axis.add_numbers() - frequency_axes.y_axis.add_numbers( - *frequency_axes.y_axis.get_tick_numbers() - ) - box = SurroundingRectangle( - frequency_axes, - buff = MED_SMALL_BUFF, - color = self.frequency_axes_box_color, - ) - frequency_axes.box = box - frequency_axes.add(box) - frequency_axes.to_corner(DOWN+RIGHT, buff = MED_SMALL_BUFF) - - frequency_label = TextMobject("Frequency") - frequency_label.scale(self.text_scale_val) - frequency_label.next_to( - frequency_axes.x_axis.get_right(), DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = RIGHT, - ) - frequency_axes.label = frequency_label - frequency_axes.add(frequency_label) - - self.frequency_axes = frequency_axes - return frequency_axes - - def get_time_graph(self, func, **kwargs): - if not hasattr(self, "time_axes"): - self.get_time_axes() - config = dict(self.default_graph_config) - config.update(kwargs) - graph = self.time_axes.get_graph(func, **config) - return graph - - def get_cosine_wave(self, freq = 1, shift_val = 1, scale_val = 0.9): - return self.get_time_graph( - lambda t : shift_val + scale_val*np.cos(TAU*freq*t) - ) - - def get_fourier_transform_graph(self, time_graph, **kwargs): - if not hasattr(self, "frequency_axes"): - self.get_frequency_axes() - func = time_graph.underlying_function - t_axis = self.time_axes.x_axis - t_min = t_axis.point_to_number(time_graph.points[0]) - t_max = t_axis.point_to_number(time_graph.points[-1]) - f_max = self.frequency_axes.x_max - # result = get_fourier_graph( - # self.frequency_axes, func, t_min, t_max, - # **kwargs - # ) - # too_far_right_point_indices = [ - # i - # for i, point in enumerate(result.points) - # if self.frequency_axes.x_axis.point_to_number(point) > f_max - # ] - # if too_far_right_point_indices: - # i = min(too_far_right_point_indices) - # prop = float(i)/len(result.points) - # result.pointwise_become_partial(result, 0, prop) - # return result - return self.frequency_axes.get_graph( - get_fourier_transform(func, t_min, t_max, **kwargs), - color = self.center_of_mass_color, - **kwargs - ) - - def get_polarized_mobject(self, mobject, freq = 1.0): - if not hasattr(self, "circle_plane"): - self.get_circle_plane() - polarized_mobject = mobject.copy() - polarized_mobject.apply_function(lambda p : self.polarize_point(p, freq)) - # polarized_mobject.make_smooth() - mobject.polarized_mobject = polarized_mobject - polarized_mobject.frequency = freq - return polarized_mobject - - def polarize_point(self, point, freq = 1.0): - t, y = self.time_axes.point_to_coords(point) - z = y*np.exp(complex(0, -2*np.pi*freq*t)) - return self.circle_plane.coords_to_point(z.real, z.imag) - - def get_polarized_animation(self, mobject, freq = 1.0): - p_mob = self.get_polarized_mobject(mobject, freq = freq) - def update_p_mob(p_mob): - Transform( - p_mob, - self.get_polarized_mobject(mobject, freq = freq) - ).update(1) - mobject.polarized_mobject = p_mob - return p_mob - return UpdateFromFunc(p_mob, update_p_mob) - - def animate_frequency_change(self, mobjects, new_freq, **kwargs): - kwargs["run_time"] = kwargs.get("run_time", 3.0) - added_anims = kwargs.get("added_anims", []) - self.play(*[ - self.get_frequency_change_animation(mob, new_freq, **kwargs) - for mob in mobjects - ] + added_anims) - - def get_frequency_change_animation(self, mobject, new_freq, **kwargs): - if not hasattr(mobject, "polarized_mobject"): - mobject.polarized_mobject = self.get_polarized_mobject(mobject) - start_freq = mobject.polarized_mobject.frequency - def update(pm, alpha): - freq = interpolate(start_freq, new_freq, alpha) - new_pm = self.get_polarized_mobject(mobject, freq) - Transform(pm, new_pm).update(1) - mobject.polarized_mobject = pm - mobject.polarized_mobject.frequency = freq - return pm - return UpdateFromAlphaFunc(mobject.polarized_mobject, update, **kwargs) - - def get_time_graph_y_vector_animation(self, graph, **kwargs): - config = dict(self.default_y_vector_animation_config) - config.update(kwargs) - vector = Vector(UP, color = WHITE) - graph_copy = graph.copy() - x_axis = self.time_axes.x_axis - x_min = x_axis.point_to_number(graph.points[0]) - x_max = x_axis.point_to_number(graph.points[-1]) - def update_vector(vector, alpha): - x = interpolate(x_min, x_max, alpha) - vector.put_start_and_end_on( - self.time_axes.coords_to_point(x, 0), - self.time_axes.input_to_graph_point(x, graph_copy) - ) - return vector - return UpdateFromAlphaFunc(vector, update_vector, **config) - - def get_polarized_vector_animation(self, polarized_graph, **kwargs): - config = dict(self.default_y_vector_animation_config) - config.update(kwargs) - vector = Vector(RIGHT, color = WHITE) - origin = self.circle_plane.coords_to_point(0, 0) - graph_copy = polarized_graph.copy() - def update_vector(vector, alpha): - # Not sure why this is needed, but without smoothing - # out the alpha like this, the vector would occasionally - # jump around - point = center_of_mass([ - graph_copy.point_from_proportion(alpha+d) - for d in np.linspace(-0.001, 0.001, 5) - ]) - vector.put_start_and_end_on_with_projection(origin, point) - return vector - return UpdateFromAlphaFunc(vector, update_vector, **config) - - def get_vector_animations(self, graph, draw_polarized_graph = True, **kwargs): - config = dict(self.default_y_vector_animation_config) - config.update(kwargs) - anims = [ - self.get_time_graph_y_vector_animation(graph, **config), - self.get_polarized_vector_animation(graph.polarized_mobject, **config), - ] - if draw_polarized_graph: - new_config = dict(config) - new_config["remover"] = False - anims.append(ShowCreation(graph.polarized_mobject, **new_config)) - return anims - - def animate_time_sweep(self, freq, n_repeats = 1, t_max = None, **kwargs): - added_anims = kwargs.pop("added_anims", []) - config = dict(self.default_time_sweep_config) - config.update(kwargs) - circle_plane = self.circle_plane - time_axes = self.time_axes - ctp = time_axes.coords_to_point - t_max = t_max or time_axes.x_max - v_line = DashedLine( - ctp(0, 0), ctp(0, time_axes.y_max), - stroke_width = 6, - ) - v_line.set_color(RED) - - for x in range(n_repeats): - v_line.move_to(ctp(0, 0), DOWN) - self.play( - ApplyMethod( - v_line.move_to, - ctp(t_max, 0), DOWN - ), - self.get_polarized_animation(v_line, freq = freq), - *added_anims, - **config - ) - self.remove(v_line.polarized_mobject) - self.play(FadeOut(VGroup(v_line, v_line.polarized_mobject))) - - def get_v_lines_indicating_periods(self, freq, n_lines = None): - if n_lines is None: - n_lines = self.default_num_v_lines_indicating_periods - period = np.divide(1., max(freq, 0.01)) - v_lines = VGroup(*[ - DashedLine(ORIGIN, 1.5*UP).move_to( - self.time_axes.coords_to_point(n*period, 0), - DOWN - ) - for n in range(1, n_lines + 1) - ]) - v_lines.set_stroke(LIGHT_GREY) - return v_lines - - def get_period_v_lines_update_anim(self): - def update_v_lines(v_lines): - freq = self.graph.polarized_mobject.frequency - Transform( - v_lines, - self.get_v_lines_indicating_periods(freq) - ).update(1) - return UpdateFromFunc( - self.v_lines_indicating_periods, update_v_lines - ) - -class WrapCosineGraphAroundCircle(FourierMachineScene): - CONFIG = { - "initial_winding_frequency" : 0.5, - "signal_frequency" : 3.0, - } - def construct(self): - self.show_initial_signal() - self.show_finite_interval() - self.wrap_around_circle() - self.show_time_sweeps() - self.compare_two_frequencies() - self.change_wrapping_frequency() - - def show_initial_signal(self): - axes = self.get_time_axes() - graph = self.get_cosine_wave(freq = self.signal_frequency) - self.graph = graph - braces = VGroup(*self.get_peak_braces()[3:6]) - v_lines = VGroup(*[ - DashedLine( - ORIGIN, 2*UP, color = RED - ).move_to(axes.coords_to_point(x, 0), DOWN) - for x in (1, 2) - ]) - words = self.get_bps_label() - words.save_state() - words.next_to(axes.coords_to_point(1.5, 0), DOWN, MED_LARGE_BUFF) - - self.add(axes) - self.play(ShowCreation(graph, run_time = 2, rate_func=linear)) - self.play( - FadeIn(words), - LaggedStartMap(FadeIn, braces), - *list(map(ShowCreation, v_lines)) - ) - self.wait() - self.play( - FadeOut(VGroup(braces, v_lines)), - words.restore, - ) - self.wait() - - self.beats_per_second_label = words - self.graph = graph - - def show_finite_interval(self): - axes = self.time_axes - v_line = DashedLine( - axes.coords_to_point(0, 0), - axes.coords_to_point(0, axes.y_max), - color = RED, - stroke_width = 6, - ) - h_line = Line( - axes.coords_to_point(0, 0), - axes.coords_to_point(axes.x_max, 0), - ) - rect = Rectangle( - stroke_width = 0, - fill_color = TEAL, - fill_opacity = 0.5, - ) - rect.match_height(v_line) - rect.match_width(h_line, stretch = True) - rect.move_to(v_line, DOWN+LEFT) - right_v_line = v_line.copy() - right_v_line.move_to(rect, RIGHT) - - rect.save_state() - rect.stretch(0, 0, about_edge = ORIGIN) - self.play(rect.restore, run_time = 2) - self.play(FadeOut(rect)) - for line in v_line, right_v_line: - self.play(ShowCreation(line)) - self.play(FadeOut(line)) - self.wait() - - def wrap_around_circle(self): - graph = self.graph - freq = self.initial_winding_frequency - low_freq = freq/3 - polarized_graph = self.get_polarized_mobject(graph, low_freq) - circle_plane = self.get_circle_plane() - moving_graph = graph.copy() - - self.play(ShowCreation(circle_plane, lag_ratio = 0)) - self.play(ReplacementTransform( - moving_graph, - polarized_graph, - run_time = 3, - path_arc = -TAU/2 - )) - self.animate_frequency_change([graph], freq) - self.wait() - pg_copy = polarized_graph.copy() - self.remove(polarized_graph) - self.play(pg_copy.fade, 0.75) - self.play(*self.get_vector_animations(graph), run_time = 15) - self.remove(pg_copy) - self.wait() - - def show_time_sweeps(self): - freq = self.initial_winding_frequency - graph = self.graph - - v_lines = self.get_v_lines_indicating_periods(freq) - winding_freq_label = self.get_winding_frequency_label() - - self.animate_time_sweep( - freq = freq, - t_max = 4, - run_time = 6, - added_anims = [FadeIn(v_lines)] - ) - self.play( - FadeIn(winding_freq_label), - *self.get_vector_animations(graph) - ) - self.wait() - - self.v_lines_indicating_periods = v_lines - - def compare_two_frequencies(self): - bps_label = self.beats_per_second_label - wps_label = self.winding_freq_label - for label in bps_label, wps_label: - label.rect = SurroundingRectangle( - label, color = RED - ) - graph = self.graph - freq = self.initial_winding_frequency - braces = self.get_peak_braces(buff = 0) - - self.play(ShowCreation(bps_label.rect)) - self.play(FadeOut(bps_label.rect)) - self.play(LaggedStartMap(FadeIn, braces, run_time = 3)) - self.play(FadeOut(braces)) - self.play(ShowCreation(wps_label.rect)) - self.play(FadeOut(wps_label.rect)) - self.animate_time_sweep(freq = freq, t_max = 4) - self.wait() - - def change_wrapping_frequency(self): - graph = self.graph - v_lines = self.v_lines_indicating_periods - freq_label = self.winding_freq_label[0] - - count = 0 - for target_freq in [1.23, 0.2, 0.79, 1.55, self.signal_frequency]: - self.play( - Transform( - v_lines, - self.get_v_lines_indicating_periods(target_freq) - ), - ChangeDecimalToValue(freq_label, target_freq), - self.get_frequency_change_animation(graph, target_freq), - run_time = 4, - ) - self.wait() - if count == 2: - self.play(LaggedStartMap( - ApplyFunction, v_lines, - lambda mob : ( - lambda m : m.shift(0.25*UP).set_color(YELLOW), - mob - ), - rate_func = there_and_back - )) - self.animate_time_sweep(target_freq, t_max = 2) - count += 1 - self.wait() - self.play( - *self.get_vector_animations(graph, False), - run_time = 15 - ) - - ## - - def get_winding_frequency_label(self): - freq = self.initial_winding_frequency - winding_freq_label = VGroup( - DecimalNumber(freq, num_decimal_places=2), - TextMobject("cycles/second") - ) - winding_freq_label.arrange(RIGHT) - winding_freq_label.next_to( - self.circle_plane, RIGHT, aligned_edge = UP - ) - self.winding_freq_label = winding_freq_label - return winding_freq_label - - def get_peak_braces(self, **kwargs): - peak_points = [ - self.time_axes.input_to_graph_point(x, self.graph) - for x in np.arange(0, 3.5, 1./self.signal_frequency) - ] - return VGroup(*[ - Brace(Line(p1, p2), UP, **kwargs) - for p1, p2 in zip(peak_points, peak_points[1:]) - ]) - - def get_bps_label(self, freq = 3): - braces = VGroup(*self.get_peak_braces()[freq:2*freq]) - words = TextMobject("%d beats/second"%freq) - words.set_width(0.9*braces.get_width()) - words.move_to(braces, DOWN) - return words - -class DrawFrequencyPlot(WrapCosineGraphAroundCircle, PiCreatureScene): - CONFIG = { - "initial_winding_frequency" : 3.0, - "center_of_mass_color" : RED, - "center_of_mass_multiple" : 1, - } - def construct(self): - self.remove(self.pi_creature) - self.setup_graph() - self.indicate_weight_of_wire() - self.show_center_of_mass_dot() - self.change_to_various_frequencies() - self.introduce_frequency_plot() - self.draw_full_frequency_plot() - self.recap_objects_on_screen() - self.lower_graph() - self.label_as_almost_fourier() - - def setup_graph(self): - self.add(self.get_time_axes()) - self.add(self.get_circle_plane()) - self.graph = self.get_cosine_wave(self.signal_frequency) - self.add(self.graph) - self.add(self.get_polarized_mobject( - self.graph, self.initial_winding_frequency - )) - self.add(self.get_winding_frequency_label()) - self.beats_per_second_label = self.get_bps_label() - self.add(self.beats_per_second_label) - self.v_lines_indicating_periods = self.get_v_lines_indicating_periods( - self.initial_winding_frequency - ) - self.add(self.v_lines_indicating_periods) - self.change_frequency(1.03) - self.wait() - - def indicate_weight_of_wire(self): - graph = self.graph - pol_graph = graph.polarized_mobject.copy() - pol_graph.save_state() - morty = self.pi_creature - morty.change("raise_right_hand") - morty.save_state() - morty.change("plain") - morty.fade(1) - - self.play( - morty.restore, - pol_graph.scale, 0.5, - pol_graph.next_to, morty.get_corner(UP+LEFT), UP, -SMALL_BUFF, - ) - self.play( - morty.change, "lower_right_hand", pol_graph.get_bottom(), - pol_graph.shift, 0.45*DOWN, - rate_func = there_and_back, - run_time = 2, - ) - self.wait() - - metal_wire = pol_graph.copy().set_stroke(LIGHT_GREY) - self.play( - ShowCreationThenDestruction(metal_wire), - run_time = 2, - ) - self.play( - pol_graph.restore, - morty.change, "pondering" - ) - self.remove(pol_graph) - - def show_center_of_mass_dot(self): - color = self.center_of_mass_color - dot = self.get_center_of_mass_dot() - dot.save_state() - arrow = Vector(DOWN+2*LEFT, color = color) - arrow.next_to(dot.get_center(), UP+RIGHT, buff = SMALL_BUFF) - dot.move_to(arrow.get_start()) - words = TextMobject("Center of mass") - words.next_to(arrow.get_start(), RIGHT) - words.set_color(color) - - self.play( - GrowArrow(arrow), - dot.restore, - ) - self.play(Write(words)) - self.play(FadeOut(arrow), FadeOut(self.pi_creature)) - self.wait() - - self.generate_center_of_mass_dot_update_anim() - self.center_of_mass_label = words - - def change_to_various_frequencies(self): - self.change_frequency( - 3.0, run_time = 30, - rate_func = bezier([0, 0, 1, 1]) - ) - self.wait() - self.play( - *self.get_vector_animations(self.graph), - run_time = 15 - ) - - def introduce_frequency_plot(self): - wps_label = self.winding_freq_label - wps_label.add_to_back(BackgroundRectangle(wps_label)) - com_label = self.center_of_mass_label - com_label.add_background_rectangle() - frequency_axes = self.get_frequency_axes() - x_coord_label = TextMobject("$x$-coordinate for center of mass") - x_coord_label.set_color(self.center_of_mass_color) - x_coord_label.scale(self.text_scale_val) - x_coord_label.next_to( - frequency_axes.y_axis.get_top(), - RIGHT, aligned_edge = UP, buff = LARGE_BUFF - ) - x_coord_label.add_background_rectangle() - flower_path = ParametricFunction( - lambda t : self.circle_plane.coords_to_point( - np.sin(2*t)*np.cos(t), - np.sin(2*t)*np.sin(t), - ), - t_min = 0, t_max = TAU, - ) - flower_path.move_to(self.center_of_mass_dot) - - self.play( - wps_label.move_to, self.circle_plane.get_top(), - com_label.move_to, self.circle_plane, DOWN, - ) - self.play(LaggedStartMap(FadeIn, frequency_axes)) - self.wait() - self.play(MoveAlongPath( - self.center_of_mass_dot, flower_path, - run_time = 4, - )) - self.play(ReplacementTransform( - com_label.copy(), x_coord_label - )) - self.wait() - - self.x_coord_label = x_coord_label - - def draw_full_frequency_plot(self): - graph = self.graph - fourier_graph = self.get_fourier_transform_graph(graph) - fourier_graph.save_state() - fourier_graph_update = self.get_fourier_graph_drawing_update_anim( - fourier_graph - ) - v_line = DashedLine( - self.frequency_axes.coords_to_point(0, 0), - self.frequency_axes.coords_to_point(0, 1), - stroke_width = 6, - color = fourier_graph.get_color() - ) - - self.change_frequency(0.0) - self.generate_fourier_dot_transform(fourier_graph) - self.wait() - self.play(ShowCreation(v_line)) - self.play( - GrowFromCenter(self.fourier_graph_dot), - FadeOut(v_line) - ) - f_max = int(self.frequency_axes.x_max) - for freq in [0.2, 1.5, 3.0, 4.0, 5.0]: - fourier_graph.restore() - self.change_frequency( - freq, - added_anims = [fourier_graph_update], - run_time = 8, - ) - self.wait() - self.fourier_graph = fourier_graph - - def recap_objects_on_screen(self): - rect = FullScreenFadeRectangle() - time_group = VGroup( - self.graph, - self.time_axes, - self.beats_per_second_label, - ).copy() - circle_group = VGroup( - self.graph.polarized_mobject, - self.circle_plane, - self.winding_freq_label, - self.center_of_mass_label, - self.center_of_mass_dot, - ).copy() - frequency_group = VGroup( - self.fourier_graph, - self.frequency_axes, - self.x_coord_label, - ).copy() - groups = [time_group, circle_group, frequency_group] - - self.play(FadeIn(rect)) - self.wait() - for group in groups: - graph_copy = group[0].copy().set_color(PINK) - self.play(FadeIn(group)) - self.play(ShowCreation(graph_copy)) - self.play(FadeOut(graph_copy)) - self.wait() - self.play(FadeOut(group)) - self.wait() - self.play(FadeOut(rect)) - - def lower_graph(self): - graph = self.graph - time_axes = self.time_axes - shift_vect = time_axes.coords_to_point(0, 1) - shift_vect -= time_axes.coords_to_point(0, 0) - fourier_graph = self.fourier_graph - new_graph = self.get_cosine_wave( - self.signal_frequency, shift_val = 0 - ) - new_fourier_graph = self.get_fourier_transform_graph(new_graph) - for mob in graph, time_axes, fourier_graph: - mob.save_state() - - new_freq = 0.03 - self.change_frequency(new_freq) - self.wait() - self.play( - time_axes.shift, shift_vect/2, - graph.shift, -shift_vect/2, - self.get_frequency_change_animation( - self.graph, new_freq - ), - self.center_of_mass_dot_anim, - self.get_period_v_lines_update_anim(), - Transform(fourier_graph, new_fourier_graph), - self.fourier_graph_dot.move_to, - self.frequency_axes.coords_to_point(new_freq, 0), - run_time = 2 - ) - self.wait() - self.remove(self.fourier_graph_dot) - self.generate_fourier_dot_transform(new_fourier_graph) - self.change_frequency(3.0, run_time = 15, rate_func=linear) - self.wait() - self.play( - graph.restore, - time_axes.restore, - self.get_frequency_change_animation( - self.graph, 3.0 - ), - self.center_of_mass_dot_anim, - self.get_period_v_lines_update_anim(), - fourier_graph.restore, - Animation(self.fourier_graph_dot), - run_time = 2 - ) - self.generate_fourier_dot_transform(self.fourier_graph) - self.wait() - self.play(FocusOn(self.fourier_graph_dot)) - self.wait() - - def label_as_almost_fourier(self): - x_coord_label = self.x_coord_label - almost_fourier_label = TextMobject( - "``Almost Fourier Transform''", - ) - almost_fourier_label.move_to(x_coord_label, UP+LEFT) - x_coord_label.generate_target() - x_coord_label.target.next_to(almost_fourier_label, DOWN) - - self.play( - MoveToTarget(x_coord_label), - Write(almost_fourier_label) - ) - self.wait(2) - - ## - - def get_center_of_mass_dot(self): - dot = Dot( - self.get_pol_graph_center_of_mass(), - color = self.center_of_mass_color - ) - self.center_of_mass_dot = dot - return dot - - def get_pol_graph_center_of_mass(self): - pg = self.graph.polarized_mobject - result = center_of_mass(pg.get_anchors()) - if self.center_of_mass_multiple != 1: - mult = self.center_of_mass_multiple - origin = self.circle_plane.coords_to_point(0, 0) - result = mult*(result - origin) + origin - return result - - def generate_fourier_dot_transform(self, fourier_graph): - self.fourier_graph_dot = Dot(color = WHITE, radius = 0.05) - def update_dot(dot): - f = self.graph.polarized_mobject.frequency - dot.move_to(self.frequency_axes.input_to_graph_point( - f, fourier_graph - )) - self.fourier_graph_dot_anim = UpdateFromFunc( - self.fourier_graph_dot, update_dot - ) - self.fourier_graph_dot_anim.update(0) - - def get_fourier_graph_drawing_update_anim(self, fourier_graph): - fourier_graph_copy = fourier_graph.copy() - max_freq = self.frequency_axes.x_max - def update_fourier_graph(fg): - freq = self.graph.polarized_mobject.frequency - fg.pointwise_become_partial( - fourier_graph_copy, - 0, freq/max_freq - ) - return fg - self.fourier_graph_drawing_update_anim = UpdateFromFunc( - fourier_graph, update_fourier_graph - ) - return self.fourier_graph_drawing_update_anim - - def generate_center_of_mass_dot_update_anim(self, multiplier = 1): - origin = self.circle_plane.coords_to_point(0, 0) - com = self.get_pol_graph_center_of_mass - self.center_of_mass_dot_anim = UpdateFromFunc( - self.center_of_mass_dot, - lambda d : d.move_to( - multiplier*(com()-origin)+origin - ) - ) - - def change_frequency(self, new_freq, **kwargs): - kwargs["run_time"] = kwargs.get("run_time", 3) - rate_func = kwargs.pop("rate_func", None) - if rate_func is None: - rate_func = bezier([0, 0, 1, 1]) - added_anims = kwargs.get("added_anims", []) - anims = [self.get_frequency_change_animation(self.graph, new_freq)] - if hasattr(self, "winding_freq_label"): - freq_label = [ - sm for sm in self.winding_freq_label - if isinstance(sm, DecimalNumber) - ][0] - self.add(freq_label) - anims.append( - ChangeDecimalToValue(freq_label, new_freq) - ) - if hasattr(self, "v_lines_indicating_periods"): - anims.append(self.get_period_v_lines_update_anim()) - if hasattr(self, "center_of_mass_dot"): - anims.append(self.center_of_mass_dot_anim) - if hasattr(self, "fourier_graph_dot"): - anims.append(self.fourier_graph_dot_anim) - if hasattr(self, "fourier_graph_drawing_update_anim"): - anims.append(self.fourier_graph_drawing_update_anim) - for anim in anims: - anim.rate_func = rate_func - anims += added_anims - self.play(*anims, **kwargs) - - def create_pi_creature(self): - return Mortimer().to_corner(DOWN+RIGHT) - -class StudentsHorrifiedAtScene(TeacherStudentsScene): - def construct(self): - self.change_student_modes( - *3*["horrified"], - look_at_arg = 2*UP + 3*LEFT - ) - self.wait(4) - -class AskAboutAlmostFouierName(TeacherStudentsScene): - def construct(self): - self.student_says( - "``Almost'' Fourier transform?", - target_mode = "sassy" - ) - self.change_student_modes("confused", "sassy", "confused") - self.wait() - self.teacher_says( - "We'll get to the real \\\\ one in a few minutes", - added_anims = [self.get_student_changes(*["plain"]*3)] - ) - self.wait(2) - -class ShowLowerFrequency(DrawFrequencyPlot): - CONFIG = { - "signal_frequency" : 2.0, - "higher_signal_frequency" : 3.0, - "lower_signal_color" : PINK, - } - def construct(self): - self.setup_all_axes() - self.show_lower_frequency_signal() - self.play_with_lower_frequency_signal() - - def setup_all_axes(self): - self.add(self.get_time_axes()) - self.add(self.get_circle_plane()) - self.add(self.get_frequency_axes()) - self.remove(self.pi_creature) - - def show_lower_frequency_signal(self): - axes = self.time_axes - start_graph = self.get_cosine_wave(freq = self.higher_signal_frequency) - graph = self.get_cosine_wave( - freq = self.signal_frequency, - ) - graph.set_color(self.lower_signal_color) - self.graph = graph - ratio = float(self.higher_signal_frequency)/self.signal_frequency - - braces = VGroup(*self.get_peak_braces()[2:4]) - v_lines = VGroup(*[ - DashedLine(ORIGIN, 1.5*UP).move_to( - axes.coords_to_point(x, 0), DOWN - ) - for x in (1, 2) - ]) - bps_label = self.get_bps_label(2) - bps_label.save_state() - bps_label.next_to(braces, UP, SMALL_BUFF) - - - # self.add(start_graph) - self.play( - start_graph.stretch, ratio, 0, {"about_edge" : LEFT}, - start_graph.set_color, graph.get_color(), - ) - self.play(FadeOut(start_graph), Animation(graph)) - self.remove(start_graph) - self.play( - Write(bps_label), - LaggedStartMap(FadeIn, braces), - *list(map(ShowCreation, v_lines)), - run_time = 1 - ) - self.wait() - self.play( - FadeOut(v_lines), - FadeOut(braces), - bps_label.restore, - ) - - def play_with_lower_frequency_signal(self): - freq = 0.1 - - #Wind up graph - graph = self.graph - pol_graph = self.get_polarized_mobject(graph, freq) - v_lines = self.get_v_lines_indicating_periods(freq) - self.v_lines_indicating_periods = v_lines - wps_label = self.get_winding_frequency_label() - ChangeDecimalToValue(wps_label[0], freq).update(1) - wps_label.add_to_back(BackgroundRectangle(wps_label)) - wps_label.move_to(self.circle_plane, UP) - - self.play( - ReplacementTransform( - graph.copy(), pol_graph, - run_time = 2, - path_arc = -TAU/4, - ), - FadeIn(wps_label), - ) - self.change_frequency(freq, run_time = 0) - self.change_frequency(0.7) - self.wait() - - #Show center of mass - dot = Dot( - self.get_pol_graph_center_of_mass(), - color = self.center_of_mass_color - ) - dot.save_state() - self.center_of_mass_dot = dot - com_words = TextMobject("Center of mass") - com_words.add_background_rectangle() - com_words.move_to(self.circle_plane, DOWN) - arrow = Arrow( - com_words.get_top(), - dot.get_center(), - buff = SMALL_BUFF, - color = self.center_of_mass_color - ) - dot.move_to(arrow.get_start()) - self.generate_center_of_mass_dot_update_anim() - - self.play( - GrowArrow(arrow), - dot.restore, - Write(com_words) - ) - self.wait() - self.play(*list(map(FadeOut, [arrow, com_words]))) - self.change_frequency(0.0) - self.wait() - - #Show fourier graph - fourier_graph = self.get_fourier_transform_graph(graph) - fourier_graph_update = self.get_fourier_graph_drawing_update_anim( - fourier_graph - ) - x_coord_label = TextMobject( - "x-coordinate of center of mass" - ) - x_coord_label.scale(self.text_scale_val) - x_coord_label.next_to( - self.frequency_axes.input_to_graph_point( - self.signal_frequency, fourier_graph - ), UP - ) - x_coord_label.set_color(self.center_of_mass_color) - self.generate_fourier_dot_transform(fourier_graph) - - self.play(Write(x_coord_label)) - self.change_frequency( - self.signal_frequency, - run_time = 10, - rate_func = smooth, - ) - self.wait() - self.change_frequency( - self.frequency_axes.x_max, - run_time = 15, - rate_func = smooth, - ) - self.wait() - - self.set_variables_as_attrs( - fourier_graph, - fourier_graph_update, - ) - -class MixingUnmixingTODOStub(TODOStub): - CONFIG = { - "message" : "Insert mixing and unmixing of signals" - } - -class ShowLinearity(DrawFrequencyPlot): - CONFIG = { - "high_freq_color": YELLOW, - "low_freq_color": PINK, - "sum_color": GREEN, - "low_freq" : 3.0, - "high_freq" : 4.0, - "circle_plane_config" : { - "x_radius" : 2.5, - "y_radius" : 2.7, - "x_unit_size" : 0.8, - "y_unit_size" : 0.8, - }, - } - def construct(self): - self.remove(self.pi_creature) - self.show_sum_of_signals() - self.show_winding_with_sum_graph() - self.show_vector_rotation() - - def show_sum_of_signals(self): - low_freq, high_freq = self.low_freq, self.high_freq - axes = self.get_time_axes() - axes_copy = axes.copy() - low_freq_graph, high_freq_graph = [ - self.get_cosine_wave( - freq = freq, - scale_val = 0.5, - shift_val = 0.55, - ) - for freq in (low_freq, high_freq) - ] - sum_graph = self.get_time_graph( - lambda t : sum([ - low_freq_graph.underlying_function(t), - high_freq_graph.underlying_function(t), - ]) - ) - VGroup(axes_copy, high_freq_graph).next_to( - axes, DOWN, MED_LARGE_BUFF - ) - - low_freq_label = TextMobject("%d Hz"%int(low_freq)) - high_freq_label = TextMobject("%d Hz"%int(high_freq)) - sum_label = TextMobject( - "%d Hz"%int(low_freq), "+", - "%d Hz"%int(high_freq) - ) - trips = [ - (low_freq_label, low_freq_graph, self.low_freq_color), - (high_freq_label, high_freq_graph, self.high_freq_color), - (sum_label, sum_graph, self.sum_color), - ] - for label, graph, color in trips: - label.next_to(graph, UP) - graph.set_color(color) - label.set_color(color) - sum_label[0].match_color(low_freq_graph) - sum_label[2].match_color(high_freq_graph) - - self.add(axes, low_freq_graph) - self.play( - FadeIn(axes_copy), - ShowCreation(high_freq_graph), - ) - self.play(LaggedStartMap( - FadeIn, VGroup(high_freq_label, low_freq_label) - )) - self.wait() - self.play( - ReplacementTransform(axes_copy, axes), - ReplacementTransform(high_freq_graph, sum_graph), - ReplacementTransform(low_freq_graph, sum_graph), - ReplacementTransform( - VGroup(low_freq_label, high_freq_label), - sum_label - ) - ) - self.wait() - self.graph = graph - - def show_winding_with_sum_graph(self): - graph = self.graph - circle_plane = self.get_circle_plane() - frequency_axes = self.get_frequency_axes() - pol_graph = self.get_polarized_mobject(graph, freq = 0.0) - - wps_label = self.get_winding_frequency_label() - ChangeDecimalToValue(wps_label[0], 0.0).update(1) - wps_label.add_to_back(BackgroundRectangle(wps_label)) - wps_label.move_to(circle_plane, UP) - - v_lines = self.get_v_lines_indicating_periods(0.001) - self.v_lines_indicating_periods = v_lines - - dot = Dot( - self.get_pol_graph_center_of_mass(), - color = self.center_of_mass_color - ) - self.center_of_mass_dot = dot - self.generate_center_of_mass_dot_update_anim() - - fourier_graph = self.get_fourier_transform_graph(graph) - fourier_graph_update = self.get_fourier_graph_drawing_update_anim( - fourier_graph - ) - x_coord_label = TextMobject( - "x-coordinate of center of mass" - ) - x_coord_label.scale(self.text_scale_val) - x_coord_label.next_to( - self.frequency_axes.input_to_graph_point( - self.signal_frequency, fourier_graph - ), UP - ) - x_coord_label.set_color(self.center_of_mass_color) - almost_fourier_label = TextMobject( - "``Almost-Fourier transform''" - ) - - self.generate_fourier_dot_transform(fourier_graph) - - self.play(LaggedStartMap( - FadeIn, VGroup( - circle_plane, wps_label, - frequency_axes, x_coord_label, - ), - run_time = 1, - )) - self.play( - ReplacementTransform(graph.copy(), pol_graph), - GrowFromCenter(dot) - ) - freqs = [ - self.low_freq, self.high_freq, - self.frequency_axes.x_max - ] - for freq in freqs: - self.change_frequency( - freq, - run_time = 8, - rate_func = bezier([0, 0, 1, 1]), - ) - - def show_vector_rotation(self): - self.fourier_graph_drawing_update_anim = Animation(Mobject()) - self.change_frequency(self.low_freq) - self.play(*self.get_vector_animations( - self.graph, draw_polarized_graph = False, - run_time = 20, - )) - self.wait() - -class ShowCommutativeDiagram(ShowLinearity): - CONFIG = { - "time_axes_config" : { - "x_max" : 1.9, - "y_max" : 2.0, - "y_min" : -2.0, - "y_axis_config" : { - "unit_size" : 0.5, - }, - "x_axis_config" : { - "numbers_to_show" : [1], - } - }, - "time_label_t" : 1.5, - "frequency_axes_config" : { - "x_min" : 0.0, - "x_max" : 4.0, - "y_min" : -0.1, - "y_max" : 0.5, - "y_axis_config" : { - "unit_size" : 1.5, - "tick_frequency" : 0.5, - }, - } - } - def construct(self): - self.show_diagram() - self.point_out_spikes() - - def show_diagram(self): - self.remove(self.pi_creature) - - #Setup axes - time_axes = self.get_time_axes() - time_axes.scale(0.8) - ta_group = VGroup( - time_axes, time_axes.deepcopy(), time_axes.deepcopy(), - ) - ta_group.arrange(DOWN, buff = MED_LARGE_BUFF) - ta_group.to_corner(UP+LEFT, buff = MED_SMALL_BUFF) - - frequency_axes = Axes(**self.frequency_axes_config) - frequency_axes.set_color(TEAL) - freq_label = TextMobject("Frequency") - freq_label.scale(self.text_scale_val) - freq_label.next_to(frequency_axes.x_axis, DOWN, SMALL_BUFF, RIGHT) - frequency_axes.label = freq_label - frequency_axes.add(freq_label) - frequency_axes.scale(0.8) - fa_group = VGroup( - frequency_axes, frequency_axes.deepcopy(), frequency_axes.deepcopy() - ) - VGroup(ta_group[1], fa_group[1]).shift(MED_LARGE_BUFF*UP) - for ta, fa in zip(ta_group, fa_group): - fa.next_to( - ta.x_axis, RIGHT, - submobject_to_align = fa.x_axis - ) - fa.to_edge(RIGHT) - ta.remove(ta.labels) - fa.remove(fa.label) - - ## Add graphs - funcs = [ - lambda t : np.cos(2*TAU*t), - lambda t : np.cos(3*TAU*t), - ] - funcs.append(lambda t : funcs[0](t)+funcs[1](t)) - colors = [ - self.low_freq_color, - self.high_freq_color, - self.sum_color, - ] - labels = [ - TextMobject("2 Hz"), - TextMobject("3 Hz"), - # TextMobject("2 Hz", "+", "3 Hz"), - VectorizedPoint() - ] - for func, color, label, ta, fa in zip(funcs, colors, labels, ta_group, fa_group): - time_graph = ta.get_graph(func) - time_graph.set_color(color) - label.set_color(color) - label.scale(0.75) - label.next_to(time_graph, UP, SMALL_BUFF) - fourier = get_fourier_transform( - func, ta.x_min, 4*ta.x_max - ) - fourier_graph = fa.get_graph(fourier) - fourier_graph.set_color(self.center_of_mass_color) - - arrow = Arrow( - ta.x_axis, fa.x_axis, - color = WHITE, - buff = MED_LARGE_BUFF, - ) - words = TextMobject("Almost-Fourier \\\\ transform") - words.scale(0.6) - words.next_to(arrow, UP) - arrow.words = words - - ta.graph = time_graph - ta.graph_label = label - ta.arrow = arrow - ta.add(time_graph, label) - fa.graph = fourier_graph - fa.add(fourier_graph) - # labels[-1][0].match_color(labels[0]) - # labels[-1][2].match_color(labels[1]) - - - #Add arrows - sum_arrows = VGroup() - for group in ta_group, fa_group: - arrow = Arrow( - group[1].graph, group[2].graph, - color = WHITE, - buff = SMALL_BUFF - ) - arrow.scale(0.8, about_edge = UP) - arrow.words = TextMobject("Sum").scale(0.75) - arrow.words.next_to(arrow, RIGHT, buff = MED_SMALL_BUFF) - sum_arrows.add(arrow) - - def apply_transform(index): - ta = ta_group[index].deepcopy() - fa = fa_group[index] - anims = [ - ReplacementTransform( - getattr(ta, attr), getattr(fa, attr) - ) - for attr in ("x_axis", "y_axis", "graph") - ] - anims += [ - GrowArrow(ta.arrow), - Write(ta.arrow.words), - ] - if index == 0: - anims.append(ReplacementTransform( - ta.labels[0], - fa.label - )) - self.play(*anims, run_time = 1.5) - - - #Animations - self.add(*ta_group[:2]) - self.add(ta_group[0].labels) - self.wait() - apply_transform(0) - apply_transform(1) - self.wait() - self.play( - GrowArrow(sum_arrows[1]), - Write(sum_arrows[1].words), - *[ - ReplacementTransform( - fa.copy(), fa_group[2] - ) - for fa in fa_group[:2] - ] - ) - self.wait(2) - self.play( - GrowArrow(sum_arrows[0]), - Write(sum_arrows[0].words), - *[ - ReplacementTransform( - mob.copy(), ta_group[2], - run_time = 1 - ) - for mob in ta_group[:2] - ] - ) - self.wait() - apply_transform(2) - self.wait() - - self.time_axes_group = ta_group - self.frequency_axes_group = fa_group - - def point_out_spikes(self): - fa_group = self.frequency_axes_group - freqs = self.low_freq, self.high_freq - flat_rects = VGroup() - for freq, axes in zip(freqs, fa_group[:2]): - flat_rect = SurroundingRectangle(axes.x_axis) - flat_rect.stretch(0.5, 1) - spike_rect = self.get_spike_rect(axes, freq) - flat_rect.match_style(spike_rect) - flat_rect.target = spike_rect - flat_rects.add(flat_rect) - - self.play(LaggedStartMap(GrowFromCenter, flat_rects)) - self.wait() - self.play(LaggedStartMap(MoveToTarget, flat_rects)) - self.wait() - - sum_spike_rects = VGroup(*[ - self.get_spike_rect(fa_group[2], freq) - for freq in freqs - ]) - self.play(ReplacementTransform( - flat_rects, sum_spike_rects - )) - self.play(LaggedStartMap( - WiggleOutThenIn, sum_spike_rects, - run_time = 1, - lag_ratio = 0.7, - )) - self.wait() - - ## - - def get_spike_rect(self, axes, freq): - peak_point = axes.input_to_graph_point( - freq, axes.graph - ) - f_axis_point = axes.coords_to_point(freq, 0) - line = Line(f_axis_point, peak_point) - spike_rect = SurroundingRectangle(line) - spike_rect.set_stroke(width = 0) - spike_rect.set_fill(YELLOW, 0.5) - return spike_rect - -class PauseAndPonder(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Pause and \\\\ ponder!", - target_mode = "hooray" - ) - self.change_student_modes(*["thinking"]*3) - self.wait(4) - -class BeforeGettingToTheFullMath(TeacherStudentsScene): - def construct(self): - formula = TexMobject( - "\\hat{g}(f) = \\int_{-\\infty}^{\\infty}" + \ - "g(t)e^{-2\\pi i f t}dt" - ) - formula.next_to(self.teacher, UP+LEFT) - - self.play( - Write(formula), - self.teacher.change, "raise_right_hand", - self.get_student_changes(*["confused"]*3) - ) - self.wait() - self.play( - ApplyMethod( - formula.next_to, FRAME_X_RADIUS*RIGHT, RIGHT, - path_arc = TAU/16, - rate_func = running_start, - ), - self.get_student_changes(*["pondering"]*3) - ) - self.teacher_says("Consider sound editing\\dots") - self.wait(3) - -class FilterOutHighPitch(AddingPureFrequencies, ShowCommutativeDiagram): - def construct(self): - self.add_speaker() - self.play_sound() - self.show_intensity_vs_time_graph() - self.take_fourier_transform() - self.filter_out_high_pitch() - self.mention_inverse_transform() - - def play_sound(self): - randy = self.pi_creature - - self.play( - Succession( - ApplyMethod, randy.look_at, self.speaker, - Animation, randy, - ApplyMethod, randy.change, "telepath", randy, - Animation, randy, - Blink, randy, - Animation, randy, {"run_time" : 2}, - ), - *self.get_broadcast_anims(), - run_time = 7 - ) - self.play(randy.change, "angry", self.speaker) - self.wait() - - def show_intensity_vs_time_graph(self): - randy = self.pi_creature - - axes = Axes( - x_min = 0, - x_max = 12, - y_min = -6, - y_max = 6, - y_axis_config = { - "unit_size" : 0.15, - "tick_frequency" : 3, - } - ) - axes.set_stroke(width = 2) - axes.to_corner(UP+LEFT) - time_label = TextMobject("Time") - intensity_label = TextMobject("Intensity") - labels = VGroup(time_label, intensity_label) - labels.scale(0.75) - time_label.next_to( - axes.x_axis, DOWN, - aligned_edge = RIGHT, - buff = SMALL_BUFF - ) - intensity_label.next_to( - axes.y_axis, RIGHT, - aligned_edge = UP, - buff = SMALL_BUFF - ) - axes.labels = labels - - func = lambda t : sum([ - np.cos(TAU*f*t) - for f in (0.5, 0.7, 1.0, 1.2, 3.0,) - ]) - graph = axes.get_graph(func) - graph.set_color(BLUE) - - self.play( - FadeIn(axes), - FadeIn(axes.labels), - randy.change, "pondering", axes, - ShowCreation( - graph, run_time = 4, - rate_func = bezier([0, 0, 1, 1]) - ), - *self.get_broadcast_anims(run_time = 6) - ) - self.wait() - - self.time_axes = axes - self.time_graph = graph - - def take_fourier_transform(self): - time_axes = self.time_axes - time_graph = self.time_graph - randy = self.pi_creature - speaker = self.speaker - - frequency_axes = Axes( - x_min = 0, - x_max = 3.5, - x_axis_config = {"unit_size" : 3.5}, - y_min = 0, - y_max = 1, - y_axis_config = {"unit_size" : 2}, - ) - frequency_axes.set_color(TEAL) - frequency_axes.next_to(time_axes, DOWN, LARGE_BUFF, LEFT) - freq_label = TextMobject("Frequency") - freq_label.scale(0.75) - freq_label.next_to(frequency_axes.x_axis, DOWN, MED_SMALL_BUFF, RIGHT) - frequency_axes.label = freq_label - - fourier_func = get_fourier_transform( - time_graph.underlying_function, - t_min = 0, t_max = 30, - ) - # def alt_fourier_func(t): - # bell = smooth(t)*0.3*np.exp(-0.8*(t-0.9)**2) - # return bell + (smooth(t/3)+0.2)*fourier_func(t) - fourier_graph = frequency_axes.get_graph( - fourier_func, num_graph_points = 150, - ) - fourier_graph.set_color(RED) - frequency_axes.graph = fourier_graph - - arrow = Arrow(time_graph, fourier_graph, color = WHITE) - ft_words = TextMobject("Fourier \\\\ transform") - ft_words.next_to(arrow, RIGHT) - - spike_rect = self.get_spike_rect(frequency_axes, 3) - spike_rect.stretch(2, 0) - - self.play( - ReplacementTransform(time_axes.copy(), frequency_axes), - ReplacementTransform(time_graph.copy(), fourier_graph), - ReplacementTransform(time_axes.labels[0].copy(), freq_label), - GrowArrow(arrow), - Write(ft_words), - VGroup(randy, speaker).shift, FRAME_Y_RADIUS*DOWN, - ) - self.remove(randy, speaker) - self.wait() - self.play(DrawBorderThenFill(spike_rect)) - self.wait() - - self.frequency_axes = frequency_axes - self.fourier_graph = fourier_graph - self.spike_rect = spike_rect - self.to_fourier_arrow = arrow - - def filter_out_high_pitch(self): - fourier_graph = self.fourier_graph - spike_rect = self.spike_rect - frequency_axes = self.frequency_axes - - def filtered_func(f): - result = fourier_graph.underlying_function(f) - result *= np.clip(smooth(3-f), 0, 1) - return result - - new_graph = frequency_axes.get_graph( - filtered_func, num_graph_points = 300 - ) - new_graph.set_color(RED) - - self.play(spike_rect.stretch, 4, 0) - self.play( - Transform(fourier_graph, new_graph), - spike_rect.stretch, 0.01, 1, { - "about_point" : frequency_axes.coords_to_point(0, 0) - }, - run_time = 2 - ) - self.wait() - - def mention_inverse_transform(self): - time_axes = self.time_axes - time_graph = self.time_graph - fourier_graph = self.fourier_graph - frequency_axes = self.frequency_axes - f_min = frequency_axes.x_min - f_max = frequency_axes.x_max - - filtered_graph = time_axes.get_graph( - lambda t : time_graph.underlying_function(t)-np.cos(TAU*3*t) - ) - filtered_graph.set_color(BLUE_C) - - to_fourier_arrow = self.to_fourier_arrow - arrow = to_fourier_arrow.copy() - arrow.rotate(TAU/2, about_edge = LEFT) - arrow.shift(MED_SMALL_BUFF*LEFT) - inv_fourier_words = TextMobject("Inverse Fourier \\\\ transform") - inv_fourier_words.next_to(arrow, LEFT) - VGroup(arrow, inv_fourier_words).set_color(MAROON_B) - - self.play( - GrowArrow(arrow), - Write(inv_fourier_words) - ) - self.wait() - self.play( - time_graph.fade, 0.9, - ReplacementTransform( - fourier_graph.copy(), filtered_graph - ) - ) - self.wait() - - ## - - def get_broadcast_anims(self, run_time = 7, **kwargs): - return [ - self.get_broadcast_animation( - n_circles = n, - run_time = run_time, - big_radius = 7, - start_stroke_width = 5, - **kwargs - ) - for n in (5, 7, 10, 12) - ] - -class AskAboutInverseFourier(TeacherStudentsScene): - def construct(self): - self.student_says("Inverse Fourier?") - self.change_student_modes("confused", "raise_right_hand", "confused") - self.wait(2) - -class ApplyFourierToFourier(DrawFrequencyPlot): - CONFIG = { - "time_axes_config" : { - "y_min" : -1.5, - "y_max" : 1.5, - "x_max" : 5, - "x_axis_config" : { - "numbers_to_show" : list(range(1, 5)), - "unit_size" : 2.5, - }, - }, - "frequency_axes_config" : { - "y_min" : -0.6, - "y_max" : 0.6, - }, - "circle_plane_config" : { - "x_radius" : 1.5, - "y_radius" : 1.35, - "x_unit_size" : 1.5, - "y_unit_size" : 1.5, - }, - "default_num_v_lines_indicating_periods" : 0, - "signal_frequency" : 2, - } - def construct(self): - self.setup_fourier_display() - self.swap_graphs() - - def setup_fourier_display(self): - self.force_skipping() - self.setup_graph() - self.show_center_of_mass_dot() - self.introduce_frequency_plot() - self.draw_full_frequency_plot() - self.time_axes.remove(self.time_axes.labels) - self.remove(self.beats_per_second_label) - VGroup( - self.time_axes, self.graph, - self.frequency_axes, self.fourier_graph, - self.x_coord_label, - self.fourier_graph_dot, - ).to_edge(UP, buff = MED_SMALL_BUFF) - self.revert_to_original_skipping_status() - - def swap_graphs(self): - fourier_graph = self.fourier_graph - time_graph = self.graph - wound_up_graph = time_graph.polarized_mobject - time_axes = self.time_axes - frequency_axes = self.frequency_axes - - f_max = self.frequency_axes.x_max - new_fourier_graph = time_axes.get_graph( - lambda t : 2*fourier_graph.underlying_function(t) - ) - new_fourier_graph.match_style(fourier_graph) - - self.remove(fourier_graph) - self.play( - ReplacementTransform( - fourier_graph.copy(), - new_fourier_graph - ), - ApplyMethod( - time_graph.shift, 3*UP+10*LEFT, - remover = True, - ), - ) - self.play( - wound_up_graph.next_to, FRAME_X_RADIUS*LEFT, LEFT, - remover = True - ) - self.wait() - - self.graph = new_fourier_graph - wound_up_graph = self.get_polarized_mobject(new_fourier_graph, freq = 0) - double_fourier_graph = frequency_axes.get_graph( - lambda t : 0.25*np.cos(TAU*2*t) - ).set_color(PINK) - self.fourier_graph = double_fourier_graph - self.remove(self.fourier_graph_dot) - self.get_fourier_graph_drawing_update_anim(double_fourier_graph) - self.generate_fourier_dot_transform(double_fourier_graph) - self.center_of_mass_dot.set_color(PINK) - self.generate_center_of_mass_dot_update_anim() - def new_get_pol_graph_center_of_mass(): - result = DrawFrequencyPlot.get_pol_graph_center_of_mass(self) - result -= self.circle_plane.coords_to_point(0, 0) - result *= 25 - result += self.circle_plane.coords_to_point(0, 0) - return result - self.get_pol_graph_center_of_mass = new_get_pol_graph_center_of_mass - - self.play( - ReplacementTransform(self.graph.copy(), wound_up_graph), - ChangeDecimalToValue( - self.winding_freq_label[1], 0.0, - run_time = 0.2, - ) - ) - self.change_frequency(5.0, run_time = 15, rate_func=linear) - self.wait() - - ## - - def get_cosine_wave(self, freq, **kwargs): - kwargs["shift_val"] = 0 - kwargs["scale_val"] = 1.0 - return DrawFrequencyPlot.get_cosine_wave(self, freq, **kwargs) - -class WriteComplexExponentialExpression(DrawFrequencyPlot): - CONFIG = { - "signal_frequency" : 2.0, - "default_num_v_lines_indicating_periods" : 0, - "time_axes_scale_val" : 0.7, - "initial_winding_frequency" : 0.1, - "circle_plane_config" : { - "unit_size" : 2, - "y_radius" : FRAME_Y_RADIUS+LARGE_BUFF, - "x_radius" : FRAME_X_RADIUS+LARGE_BUFF - } - } - def construct(self): - self.remove(self.pi_creature) - self.setup_plane() - self.setup_graph() - self.show_winding_with_both_coordinates() - self.show_plane_as_complex_plane() - self.show_eulers_formula() - self.show_winding_graph_expression() - self.find_center_of_mass() - - def setup_plane(self): - circle_plane = ComplexPlane(**self.circle_plane_config) - circle_plane.shift(DOWN+LEFT) - circle = DashedLine(ORIGIN, TAU*UP) - circle.apply_complex_function( - lambda z : R3_to_complex( - circle_plane.number_to_point(np.exp(z)) - ) - ) - circle_plane.add(circle) - - time_axes = self.get_time_axes() - time_axes.background_rectangle = BackgroundRectangle( - time_axes, - fill_opacity = 0.9, - buff = MED_SMALL_BUFF, - ) - time_axes.add_to_back(time_axes.background_rectangle) - time_axes.scale(self.time_axes_scale_val) - time_axes.to_corner(UP+LEFT, buff = 0) - time_axes.set_stroke(color = WHITE, width = 1) - - self.add(circle_plane) - self.add(time_axes) - - self.circle_plane = circle_plane - self.time_axes = time_axes - - def setup_graph(self): - plane = self.circle_plane - graph = self.graph = self.get_cosine_wave( - freq = self.signal_frequency, - scale_val = 0.5, - shift_val = 0.75, - ) - freq = self.initial_winding_frequency - pol_graph = self.get_polarized_mobject(graph, freq = freq) - wps_label = self.get_winding_frequency_label() - ChangeDecimalToValue(wps_label[0], freq).update(1) - wps_label.add_to_back(BackgroundRectangle(wps_label)) - wps_label.next_to(plane.coords_to_point(0, 1), DOWN) - wps_label.to_edge(LEFT) - self.get_center_of_mass_dot() - self.generate_center_of_mass_dot_update_anim() - - self.add(graph, pol_graph, wps_label) - self.set_variables_as_attrs(pol_graph, wps_label) - self.time_axes_group = VGroup(self.time_axes, graph) - - def show_winding_with_both_coordinates(self): - com_dot = self.center_of_mass_dot - plane = self.circle_plane - v_line = Line(ORIGIN, UP) - h_line = Line(ORIGIN, RIGHT) - lines = VGroup(v_line, h_line) - lines.set_color(PINK) - def lines_update(lines): - point = com_dot.get_center() - x, y = plane.point_to_coords(point) - h_line.put_start_and_end_on( - plane.coords_to_point(0, y), point - ) - v_line.put_start_and_end_on( - plane.coords_to_point(x, 0), point - ) - lines_update_anim = Mobject.add_updater(lines, lines_update) - lines_update_anim.update(0) - self.add(lines_update_anim) - - self.change_frequency( - 2.04, - added_anims = [ - self.center_of_mass_dot_anim, - ], - run_time = 15, - rate_func = bezier([0, 0, 1, 1]) - ) - self.wait() - - self.dot_component_anim = lines_update_anim - - def show_plane_as_complex_plane(self): - to_fade = VGroup( - self.time_axes_group, self.pol_graph, self.wps_label - ) - plane = self.circle_plane - dot = self.center_of_mass_dot - complex_plane_title = TextMobject("Complex plane") - complex_plane_title.add_background_rectangle() - complex_plane_title.to_edge(UP) - coordinate_labels = plane.get_coordinate_labels() - number_label = DecimalNumber( - 0, include_background_rectangle = True, - ) - number_label_update_anim = ContinualChangingDecimal( - number_label, - lambda a : plane.point_to_number(dot.get_center()), - position_update_func = lambda l : l.next_to( - dot, DOWN+RIGHT, - buff = SMALL_BUFF - ), - ) - number_label_update_anim.update(0) - flower_path = ParametricFunction( - lambda t : plane.coords_to_point( - np.sin(2*t)*np.cos(t), - np.sin(2*t)*np.sin(t), - ), - t_min = 0, t_max = TAU, - ) - flower_path.move_to(self.center_of_mass_dot) - - self.play(FadeOut(to_fade)) - self.play(Write(complex_plane_title)) - self.play(Write(coordinate_labels)) - self.wait() - self.play(FadeIn(number_label)) - self.add(number_label_update_anim) - self.play(MoveAlongPath( - dot, flower_path, - run_time = 10, - rate_func = bezier([0, 0, 1, 1]) - )) - self.wait() - self.play(ShowCreation( - self.pol_graph, run_time = 3, - )) - self.play(FadeOut(self.pol_graph)) - self.wait() - self.play(FadeOut(VGroup( - dot, self.dot_component_anim.mobject, number_label - ))) - self.remove(self.dot_component_anim) - self.remove(number_label_update_anim) - - self.set_variables_as_attrs( - number_label, - number_label_update_anim, - complex_plane_title, - ) - - def show_eulers_formula(self): - plane = self.circle_plane - - ghost_dot = Dot(ORIGIN, fill_opacity = 0) - def get_t(): - return ghost_dot.get_center()[0] - def get_circle_point(scalar = 1, t_shift = 0): - return plane.number_to_point( - scalar*np.exp(complex(0, get_t()+t_shift)) - ) - vector = Vector(plane.number_to_point(1), color = GREEN) - exp_base = TexMobject("e").scale(1.3) - exp_base.add_background_rectangle() - exp_decimal = DecimalNumber(0, unit = "i", include_background_rectangle = True) - exp_decimal.scale(0.75) - VGroup(exp_base, exp_decimal).match_color(vector) - exp_decimal_update = ContinualChangingDecimal( - exp_decimal, lambda a : get_t(), - position_update_func = lambda d : d.move_to( - exp_base.get_corner(UP+RIGHT), DOWN+LEFT - ) - ) - exp_base_update = Mobject.add_updater( - exp_base, lambda e : e.move_to(get_circle_point( - scalar = 1.1, t_shift = 0.01*TAU - )) - ) - vector_update = Mobject.add_updater( - vector, lambda v : v.put_start_and_end_on( - plane.number_to_point(0), get_circle_point() - ) - ) - updates = [exp_base_update, exp_decimal_update, vector_update] - for update in updates: - update.update(0) - - #Show initial vector - self.play( - GrowArrow(vector), - FadeIn(exp_base), - Write(exp_decimal) - ) - self.add(*updates) - self.play(ghost_dot.shift, 2*RIGHT, run_time = 3) - self.wait() - - #Show arc - arc, circle = [ - Line(ORIGIN, t*UP) - for t in (get_t(), TAU) - ] - for mob in arc, circle: - mob.insert_n_curves(20) - mob.set_stroke(RED, 4) - mob.apply_function( - lambda p : plane.number_to_point( - np.exp(R3_to_complex(p)) - ) - ) - distance_label = DecimalNumber( - exp_decimal.number, - unit = "\\text{units}" - ) - distance_label[-1].shift(SMALL_BUFF*RIGHT) - distance_label.match_color(arc) - distance_label.add_background_rectangle() - distance_label.move_to( - plane.number_to_point( - 1.1*np.exp(complex(0, 0.4*get_t())), - ), - DOWN+LEFT - ) - - self.play(ShowCreation(arc)) - self.play(ReplacementTransform( - exp_decimal.copy(), distance_label - )) - self.wait() - self.play(FadeOut(distance_label)) - - #Show full cycle - self.remove(arc) - self.play( - ghost_dot.move_to, TAU*RIGHT, - ShowCreation( - circle, - rate_func = lambda a : interpolate( - 2.0/TAU, 1, smooth(a) - ), - ), - run_time = 6, - ) - self.wait() - - #Write exponential expression - exp_expression = TexMobject("e", "^{-", "2\\pi i", "f", "t}") - e, minus, two_pi_i, f, t = exp_expression - exp_expression.next_to( - plane.coords_to_point(1, 1), - UP+RIGHT - ) - f.set_color(RED) - t.set_color(YELLOW) - exp_expression.add_background_rectangle() - two_pi_i_f_t_group = VGroup(two_pi_i, f, t) - two_pi_i_f_t_group.save_state() - two_pi_i_f_t_group.move_to(minus, LEFT) - exp_expression[1].remove(minus) - t.save_state() - t.align_to(f, LEFT) - exp_expression[1].remove(f) - - labels = VGroup() - for sym, word in (t, "Time"), (f, "Frequency"): - label = TextMobject(word) - label.match_style(sym) - label.next_to(sym, UP, buff = MED_LARGE_BUFF) - label.add_background_rectangle() - label.arrow = Arrow(label, sym, buff = SMALL_BUFF) - label.arrow.match_style(sym) - labels.add(label) - time_label, frequency_label = labels - example_frequency = TexMobject("f = 1/10") - example_frequency.add_background_rectangle() - example_frequency.match_style(frequency_label) - example_frequency.move_to(frequency_label, DOWN) - - self.play(ReplacementTransform( - VGroup(exp_base[1], exp_decimal[1]).copy(), - exp_expression - )) - self.play(FadeOut(circle)) - self.wait() - - ghost_dot.move_to(ORIGIN) - always_shift(ghost_dot, rate = TAU) - self.add(ghost_dot) - - self.play( - Write(time_label), - GrowArrow(time_label.arrow), - ) - self.wait(12.5) #Leave time to say let's slow down - self.remove(ghost_dot) - self.play( - FadeOut(time_label), - FadeIn(frequency_label), - t.restore, - GrowFromPoint(f, frequency_label.get_center()), - ReplacementTransform( - time_label.arrow, - frequency_label.arrow, - ) - ) - ghost_dot.move_to(ORIGIN) - ghost_dot.clear_updaters() - always_shift(ghost_dot, rate=0.1*TAU) - self.add(ghost_dot) - self.wait(3) - self.play( - FadeOut(frequency_label), - FadeIn(example_frequency) - ) - self.wait(15) #Give time to reference other video - #Reverse directions - ghost_dot.clear_updaters() - always_shift(ghost_dot, rate=-0.1 * TAU) - self.play( - FadeOut(example_frequency), - FadeOut(frequency_label.arrow), - GrowFromCenter(minus), - two_pi_i_f_t_group.restore - ) - self.wait(4) - - ghost_dot.clear_updaters() - self.remove(*updates) - self.play(*list(map(FadeOut, [ - update.mobject - for update in updates - if update.mobject is not vector - ]))) - self.play(ghost_dot.move_to, ORIGIN) - - exp_expression[1].add(minus, f) - exp_expression[1].sort(lambda p : p[0]) - - self.set_variables_as_attrs( - ambient_ghost_dot_movement, ghost_dot, - vector, vector_update, exp_expression - ) - - def show_winding_graph_expression(self): - ambient_ghost_dot_movement = self.ambient_ghost_dot_movement - ghost_dot = self.ghost_dot - vector = self.vector - exp_expression = self.exp_expression - plane = self.circle_plane - time_axes_group = self.time_axes_group - graph = self.graph - pol_graph = self.get_polarized_mobject(graph, freq = 0.2) - g_label = TexMobject("g(t)") - g_label.match_color(graph) - g_label.next_to(graph, UP) - g_label.add_background_rectangle() - g_scalar = g_label.copy() - g_scalar.move_to(exp_expression, DOWN+LEFT) - - vector_animations = self.get_vector_animations(graph) - vector_animations[1].mobject = vector - graph_y_vector = vector_animations[0].mobject - - self.play( - FadeIn(time_axes_group), - FadeOut(self.complex_plane_title) - ) - self.play(Write(g_label)) - self.wait() - self.play( - ReplacementTransform(g_label.copy(), g_scalar), - exp_expression.next_to, g_scalar, RIGHT, SMALL_BUFF, - exp_expression.shift, 0.5*SMALL_BUFF*UP, - ) - self.play(*vector_animations, run_time = 15) - self.add(*self.mobjects_from_last_animation) - self.wait() - - integrand = VGroup(g_scalar, exp_expression) - rect = SurroundingRectangle(integrand) - morty = Mortimer() - morty.next_to(rect, DOWN+RIGHT) - morty.shift_onto_screen() - self.play( - ShowCreation(rect), - FadeIn(morty) - ) - self.play(morty.change, "raise_right_hand") - self.play(Blink(morty)) - self.play(morty.change, "hooray", rect) - self.wait(2) - self.play(*list(map(FadeOut, [ - morty, rect, graph_y_vector, vector - ]))) - - self.integrand = integrand - - def find_center_of_mass(self): - integrand = self.integrand - integrand.generate_target() - integrand.target.to_edge(RIGHT, buff = LARGE_BUFF) - integrand.target.shift(MED_LARGE_BUFF*DOWN) - sum_expr = TexMobject( - "{1", "\\over", "N}", - "\\sum", "_{k = 1}", "^N", - ) - sum_expr.add_background_rectangle() - sum_expr.shift(SMALL_BUFF*(UP+5*RIGHT)) - sum_expr.next_to(integrand.target, LEFT) - - integral = TexMobject( - "{1", "\\over", "t_2 - t_1}", - "\\int", "_{t_1}", "^{t_2}" - ) - integral.move_to(sum_expr, RIGHT) - time_interval_indicator = SurroundingRectangle(integral[2]) - integral.add_background_rectangle() - axes = self.time_axes - time_interval = Line( - axes.coords_to_point(axes.x_min, 0), - axes.coords_to_point(axes.x_max, 0), - ) - time_interval.match_style(time_interval_indicator) - time_interval_indicator.add(time_interval) - dt_mob = TexMobject("dt") - dt_mob.next_to(integrand.target, RIGHT, SMALL_BUFF, DOWN) - dt_mob.add_background_rectangle() - - dots = self.show_center_of_mass_sampling(20) - self.wait() - self.play( - Write(sum_expr), - MoveToTarget(integrand), - ) - - #Add k subscript to t's - t1 = integrand[0][1][2] - t2 = integrand[1][1][-1] - t_mobs = VGroup(t1, t2) - t_mobs.save_state() - t_mobs.generate_target() - for i, t_mob in enumerate(t_mobs.target): - k = TexMobject("k") - k.match_style(t_mob) - k.match_height(t_mob) - k.scale(0.5) - k.move_to(t_mob.get_corner(DOWN+RIGHT), LEFT) - k.add_background_rectangle() - t_mob.add(k) - if i == 0: - t_mob.shift(0.5*SMALL_BUFF*LEFT) - - self.play(MoveToTarget(t_mobs)) - self.play(LaggedStartMap( - Indicate, dots[1], - rate_func = there_and_back, - color = TEAL, - )) - self.show_center_of_mass_sampling(100) - dots = self.show_center_of_mass_sampling(500) - self.wait() - self.play(FadeOut(dots)) - self.play( - ReplacementTransform(sum_expr, integral), - FadeIn(dt_mob), - t_mobs.restore, - ) - self.wait() - self.play(ShowCreation(time_interval_indicator)) - self.wait() - self.play(FadeOut(time_interval_indicator)) - self.wait() - - #Show confusion - randy = Randolph() - randy.flip() - randy.next_to(integrand, DOWN, LARGE_BUFF) - randy.to_edge(RIGHT) - full_expression_rect = SurroundingRectangle( - VGroup(integral, dt_mob), color = RED - ) - com_dot = self.center_of_mass_dot - self.center_of_mass_dot_anim.update(0) - com_arrow = Arrow( - full_expression_rect.get_left(), com_dot, - buff = SMALL_BUFF - ) - com_arrow.match_color(com_dot) - - - self.play(FadeIn(randy)) - self.play(randy.change, "confused", integral) - self.play(Blink(randy)) - self.wait(2) - self.play(ShowCreation(full_expression_rect)) - self.play( - randy.change, "thinking", self.pol_graph, - GrowArrow(com_arrow), - GrowFromCenter(com_dot), - ) - self.play(Blink(randy)) - self.wait(2) - - def show_center_of_mass_sampling(self, n_dots): - time_graph = self.graph - pol_graph = self.graph.polarized_mobject - axes = self.time_axes - - dot = Dot(radius = 0.05, color = PINK) - pre_dots = VGroup(*[ - dot.copy().move_to(axes.coords_to_point(t, 0)) - for t in np.linspace(axes.x_min, axes.x_max, n_dots) - ]) - pre_dots.set_fill(opacity = 0) - for graph in time_graph, pol_graph: - if hasattr(graph, "dots"): - graph.dot_fade_anims = [FadeOut(graph.dots)] - else: - graph.dot_fade_anims = [] - graph.save_state() - graph.generate_target() - if not hasattr(graph, "is_faded"): - graph.target.fade(0.7) - graph.is_faded = True - graph.dots = VGroup(*[ - dot.copy().move_to(graph.point_from_proportion(a)) - for a in np.linspace(0, 1, n_dots) - ]) - - self.play( - ReplacementTransform( - pre_dots, time_graph.dots, - lag_ratio = 0.5, - run_time = 2, - ), - MoveToTarget(time_graph), - *time_graph.dot_fade_anims - ) - self.play( - ReplacementTransform( - time_graph.copy(), pol_graph.target - ), - MoveToTarget(pol_graph), - ReplacementTransform( - time_graph.dots.copy(), - pol_graph.dots, - ), - *pol_graph.dot_fade_anims, - run_time = 2 - ) - return VGroup(time_graph.dots, pol_graph.dots) - -class EulersFormulaViaGroupTheoryWrapper(Scene): - def construct(self): - title = TextMobject("Euler's formula with introductory group theory") - title.to_edge(UP) - screen_rect = ScreenRectangle(height = 6) - screen_rect.next_to(title, DOWN) - self.add(title) - self.play(ShowCreation(screen_rect)) - self.wait(2) - -class WhyAreYouTellingUsThis(TeacherStudentsScene): - def construct(self): - self.student_says("Why are you \\\\ telling us this?") - self.play(self.teacher.change, "happy") - self.wait(2) - -class BuildUpExpressionStepByStep(TeacherStudentsScene): - def construct(self): - expression = TexMobject( - "\\frac{1}{t_2 - t_1}", "\\int_{t_1}^{t_2}", - "g(t)", "e", "^{-2\\pi i", "f", "t}", "dt" - ) - frac, integral, g, e, two_pi_i, f, t, dt = expression - expression.next_to(self.teacher, UP+LEFT) - t.set_color(YELLOW) - g[2].set_color(YELLOW) - dt[1].set_color(YELLOW) - f.set_color(GREEN) - t.save_state() - t.move_to(f, LEFT) - - self.play( - self.teacher.change, "raise_right_hand", - FadeIn(e), - FadeIn(two_pi_i), - ) - self.play( - self.get_student_changes(*["pondering"]*3), - FadeIn(t), - ) - self.play( - FadeIn(f), - t.restore, - ) - self.wait() - self.play(FadeIn(g), Blink(self.students[1])) - self.wait() - self.play( - FadeIn(integral), - FadeIn(frac), - FadeIn(dt), - ) - self.wait(3) - self.teacher_says( - "Just one final \\\\ distinction.", - bubble_kwargs = {"height" : 2.5, "width" : 3.5}, - added_anims = [expression.to_corner, UP+RIGHT] - ) - self.wait(3) - -class ScaleUpCenterOfMass(WriteComplexExponentialExpression): - CONFIG = { - "time_axes_scale_val" : 0.6, - "initial_winding_frequency" : 2.05 - } - def construct(self): - self.remove(self.pi_creature) - self.setup_plane() - self.setup_graph() - self.add_center_of_mass_dot() - self.add_expression() - - self.cross_out_denominator() - self.scale_up_center_of_mass() - self.comment_on_current_signal() - - def add_center_of_mass_dot(self): - self.center_of_mass_dot = self.get_center_of_mass_dot() - self.generate_center_of_mass_dot_update_anim() - self.add(self.center_of_mass_dot) - - def add_expression(self): - expression = TexMobject( - "\\frac{1}{t_2 - t_1}", "\\int_{t_1}^{t_2}", - "g(t)", "e", "^{-2\\pi i", "f", "t}", "dt" - ) - frac, integral, g, e, two_pi_i, f, t, dt = expression - expression.to_corner(UP+RIGHT) - t.set_color(YELLOW) - g[2].set_color(YELLOW) - dt[1].set_color(YELLOW) - f.set_color(GREEN) - self.expression = expression - self.add(expression) - - self.winding_freq_label.to_edge(RIGHT) - self.winding_freq_label[1].match_color(f) - self.winding_freq_label.align_to( - self.circle_plane.coords_to_point(0, 0.1), DOWN - ) - - def cross_out_denominator(self): - frac = self.expression[0] - integral = self.expression[1:] - for mob in frac, integral: - mob.add_to_back(BackgroundRectangle(mob)) - self.add(mob) - cross = Cross(frac) - brace = Brace(integral, DOWN) - label = brace.get_text("The actual \\\\ Fourier transform") - label.add_background_rectangle() - label.shift_onto_screen() - rect = SurroundingRectangle(integral) - - self.play(ShowCreation(cross)) - self.wait() - self.play(ShowCreation(rect)) - self.play( - GrowFromCenter(brace), - FadeIn(label) - ) - self.wait(2) - - self.integral = integral - self.frac = frac - self.frac_cross = cross - self.integral_rect = rect - self.integral_brace = brace - self.integral_label = label - - def scale_up_center_of_mass(self): - plane = self.circle_plane - origin = plane.coords_to_point(0, 0) - com_dot = self.center_of_mass_dot - com_vector = Arrow( - origin, com_dot.get_center(), - buff = 0 - ) - com_vector.match_style(com_dot) - vector_to_scale = com_vector.copy() - def get_com_vector_copies(n): - com_vector_copies = VGroup(*[ - com_vector.copy().shift(x*com_vector.get_vector()) - for x in range(1, n+1) - ]) - com_vector_copies.set_color(TEAL) - return com_vector_copies - com_vector_update = UpdateFromFunc( - com_vector, - lambda v : v.put_start_and_end_on(origin, com_dot.get_center()) - ) - - circle = Circle(color = TEAL) - circle.surround(com_dot, buffer_factor = 1.2) - - time_span = Rectangle( - stroke_width = 0, - fill_color = TEAL, - fill_opacity = 0.4 - ) - axes = self.time_axes - time_span.replace( - Line(axes.coords_to_point(0, 0), axes.coords_to_point(3, 1.5)), - stretch = True - ) - time_span.save_state() - time_span.stretch(0, 0, about_edge = LEFT) - - graph = self.graph - short_graph, long_graph = [ - axes.get_graph( - graph.underlying_function, x_min = 0, x_max = t_max, - ).match_style(graph) - for t_max in (3, 6) - ] - for g in short_graph, long_graph: - self.get_polarized_mobject(g, freq = self.initial_winding_frequency) - - self.play( - FocusOn(circle, run_time = 2), - Succession( - ShowCreation, circle, - FadeOut, circle, - ), - ) - self.play( - com_dot.fade, 0.5, - FadeIn(vector_to_scale) - ) - self.wait() - self.play(vector_to_scale.scale, 4, {"about_point" : origin}) - self.wait() - self.play( - FadeOut(vector_to_scale), - FadeIn(com_vector), - ) - self.remove(graph.polarized_mobject) - self.play( - com_dot.move_to, - center_of_mass(short_graph.polarized_mobject.points), - com_vector_update, - time_span.restore, - ShowCreation(short_graph.polarized_mobject), - ) - self.wait() - # dot = Dot(fill_opacity = 0.5).move_to(time_span) - # self.play( - # dot.move_to, com_vector, - # dot.set_fill, {"opacity" : 0}, - # remover = True - # ) - com_vector_copies = get_com_vector_copies(2) - self.play(*[ - ReplacementTransform( - com_vector.copy(), cvc, - path_arc = -TAU/10 - ) - for cvc in com_vector_copies - ]) - self.wait() - - #Squish_graph - to_squish = VGroup( - axes, graph, - time_span, - ) - to_squish.generate_target() - squish_factor = 0.75 - to_squish.target.stretch(squish_factor, 0, about_edge = LEFT) - pairs = list(zip( - to_squish.family_members_with_points(), - to_squish.target.family_members_with_points() - )) - to_unsquish = list(axes.x_axis.numbers) + list(axes.labels) - for sm, tsm in pairs: - if sm in to_unsquish: - tsm.stretch(1/squish_factor, 0) - if sm is axes.background_rectangle: - tsm.stretch(1/squish_factor, 0, about_edge = LEFT) - - long_graph.stretch(squish_factor, 0) - self.play( - MoveToTarget(to_squish), - FadeOut(com_vector_copies) - ) - long_graph.move_to(graph, LEFT) - self.play( - com_dot.move_to, - center_of_mass(long_graph.polarized_mobject.points), - com_vector_update, - time_span.stretch, 2, 0, {"about_edge" : LEFT}, - *[ - ShowCreation( - mob, - rate_func = lambda a : interpolate( - 0.5, 1, smooth(a) - ) - ) - for mob in (long_graph, long_graph.polarized_mobject) - ], - run_time = 2 - ) - self.remove(graph, short_graph.polarized_mobject) - self.graph = long_graph - self.wait() - self.play(FocusOn(com_dot)) - com_vector_copies = get_com_vector_copies(5) - self.play(*[ - ReplacementTransform( - com_vector.copy(), cvc, - path_arc = -TAU/10 - ) - for cvc in com_vector_copies - ]) - self.wait() - - # Scale graph out even longer - to_shift = VGroup(self.integral, self.integral_rect) - to_fade = VGroup( - self.integral_brace, self.integral_label, - self.frac, self.frac_cross - ) - self.play( - to_shift.shift, 2*DOWN, - FadeOut(to_fade), - axes.background_rectangle.stretch, 2, 0, {"about_edge" : LEFT}, - Animation(axes), - Animation(self.graph), - FadeOut(com_vector_copies), - ) - self.change_frequency(2.0, added_anims = [com_vector_update]) - very_long_graph = axes.get_graph( - graph.underlying_function, - x_min = 0, x_max = 12, - ) - very_long_graph.match_style(graph) - self.get_polarized_mobject(very_long_graph, freq = 2.0) - self.play( - com_dot.move_to, - center_of_mass(very_long_graph.polarized_mobject.points), - com_vector_update, - ShowCreation( - very_long_graph, - rate_func = lambda a : interpolate(0.5, 1, a) - ), - ShowCreation(very_long_graph.polarized_mobject) - ) - self.remove(graph, graph.polarized_mobject) - self.graph = very_long_graph - self.wait() - self.play( - com_vector.scale, 12, {"about_point" : origin}, - run_time = 2 - ) - # com_vector_copies = get_com_vector_copies(11) - # self.play(ReplacementTransform( - # VGroup(com_vector.copy()), - # com_vector_copies, - # path_arc = TAU/10, - # run_time = 1.5, - # lag_ratio = 0.5 - # )) - self.wait() - - self.com_vector = com_vector - self.com_vector_update = com_vector_update - self.com_vector_copies = com_vector_copies - - def comment_on_current_signal(self): - graph = self.graph - com_dot = self.center_of_mass_dot - com_vector = self.com_vector - com_vector_update = self.com_vector_update - axes = self.time_axes - origin = self.circle_plane.coords_to_point(0, 0) - wps_label = self.winding_freq_label - - new_com_vector_update = UpdateFromFunc( - com_vector, lambda v : v.put_start_and_end_on( - origin, com_dot.get_center() - ).scale(12, about_point = origin) - ) - - v_lines = self.get_v_lines_indicating_periods( - freq = 1.0, n_lines = 3 - )[:2] - graph_portion = axes.get_graph( - graph.underlying_function, x_min = 1, x_max = 2 - ) - graph_portion.set_color(TEAL) - bps_label = TextMobject("2 beats per second") - bps_label.scale(0.75) - bps_label.next_to(graph_portion, UP, aligned_edge = LEFT) - bps_label.shift(SMALL_BUFF*RIGHT) - bps_label.add_background_rectangle() - - self.play( - ShowCreation(v_lines, lag_ratio = 0), - ShowCreation(graph_portion), - FadeIn(bps_label), - ) - self.wait() - self.play(ReplacementTransform( - bps_label[1][0].copy(), wps_label[1] - )) - self.wait() - self.play( - com_vector.scale, 0.5, {"about_point" : origin}, - rate_func = there_and_back, - run_time = 2 - ) - self.wait(2) - self.change_frequency(2.5, - added_anims = [new_com_vector_update], - run_time = 20, - rate_func=linear, - ) - self.wait() - -class TakeAStepBack(TeacherStudentsScene): - def construct(self): - self.student_says( - "Hang on, go over \\\\ that again?", - target_mode = "confused" - ), - self.change_student_modes(*["confused"]*3) - self.play(self.teacher.change, "happy") - self.wait(3) - -class SimpleCosineWrappingAroundCircle(WriteComplexExponentialExpression): - CONFIG = { - "initial_winding_frequency" : 0, - "circle_plane_config" : { - "unit_size" : 3, - }, - } - def construct(self): - self.setup_plane() - self.setup_graph() - self.remove(self.pi_creature) - self.winding_freq_label.shift(7*LEFT) - VGroup(self.time_axes, self.graph).shift(4*UP) - VGroup( - self.circle_plane, - self.graph.polarized_mobject - ).move_to(ORIGIN) - self.add(self.get_center_of_mass_dot()) - self.generate_center_of_mass_dot_update_anim() - - self.change_frequency( - 2.0, - rate_func=linear, - run_time = 30 - ) - self.wait() - -class SummarizeTheFullTransform(DrawFrequencyPlot): - CONFIG = { - "time_axes_config" : { - "x_max" : 4.5, - "x_axis_config" : { - "unit_size" : 1.2, - "tick_frequency" : 0.5, - # "numbers_with_elongated_ticks" : range(0, 10, 2), - # "numbers_to_show" : range(0, 10, 2), - } - }, - "frequency_axes_config" : { - "x_max" : 5, - "x_axis_config" : { - "unit_size" : 1, - "numbers_to_show" : list(range(1, 5)), - }, - "y_max" : 2, - "y_min" : -2, - "y_axis_config" : { - "unit_size" : 0.75, - "tick_frequency" : 1, - }, - }, - } - def construct(self): - self.setup_all_axes() - self.show_transform_function() - self.show_winding() - - def setup_all_axes(self): - time_axes = self.get_time_axes() - time_label, intensity_label = time_axes.labels - time_label.next_to( - time_axes.x_axis.get_right(), - DOWN, SMALL_BUFF - ) - intensity_label.next_to(time_axes.y_axis, UP, buff = SMALL_BUFF) - intensity_label.to_edge(LEFT) - - frequency_axes = self.get_frequency_axes() - frequency_axes.to_corner(UP+RIGHT) - frequency_axes.shift(RIGHT) - fy_axis = frequency_axes.y_axis - for number in fy_axis.numbers: - number.add_background_rectangle() - fy_axis.remove(*fy_axis.numbers[1::2]) - frequency_axes.remove(frequency_axes.box) - frequency_axes.label.shift_onto_screen() - - circle_plane = self.get_circle_plane() - - self.set_variables_as_attrs(time_axes, frequency_axes, circle_plane) - self.add(time_axes) - - def show_transform_function(self): - time_axes = self.time_axes - frequency_axes = self.frequency_axes - def func(t): - return 0.5*(2+np.cos(2*TAU*t) + np.cos(3*TAU*t)) - fourier_func = get_fourier_transform( - func, - t_min = time_axes.x_min, - t_max = time_axes.x_max, - use_almost_fourier = False, - ) - - graph = time_axes.get_graph(func) - graph.set_color(GREEN) - fourier_graph = frequency_axes.get_graph(fourier_func) - fourier_graph.set_color(RED) - - g_t = TexMobject("g(t)") - g_t[-2].match_color(graph) - g_t.next_to(graph, UP) - g_hat_f = TexMobject("\\hat g(f)") - g_hat_f[-2].match_color(fourier_graph) - g_hat_f.next_to( - frequency_axes.input_to_graph_point(2, fourier_graph), - UP - ) - - morty = self.pi_creature - - time_label = time_axes.labels[0] - frequency_label = frequency_axes.label - for label in time_label, frequency_label: - label.rect = SurroundingRectangle(label) - time_label.rect.match_style(graph) - frequency_label.rect.match_style(fourier_graph) - - self.add(graph) - g_t.save_state() - g_t.move_to(morty, UP+LEFT) - g_t.fade(1) - self.play( - morty.change, "raise_right_hand", - g_t.restore, - ) - self.wait() - self.play(Write(frequency_axes, run_time = 1)) - self.play( - ReplacementTransform(graph.copy(), fourier_graph), - ReplacementTransform(g_t.copy(), g_hat_f), - ) - self.wait(2) - for label in time_label, frequency_label: - self.play( - ShowCreation(label.rect), - morty.change, "thinking" - ) - self.play(FadeOut(label.rect)) - self.wait() - - self.set_variables_as_attrs( - graph, fourier_graph, - g_t, g_hat_f - ) - - def show_winding(self): - plane = self.circle_plane - graph = self.graph - fourier_graph = self.fourier_graph - morty = self.pi_creature - g_hat_f = self.g_hat_f - g_hat_f_rect = SurroundingRectangle(g_hat_f) - g_hat_f_rect.set_color(TEAL) - g_hat_rect = SurroundingRectangle(g_hat_f[0]) - g_hat_rect.match_style(g_hat_f_rect) - - g_hat_f.generate_target() - g_hat_f.target.next_to(plane, RIGHT) - g_hat_f.target.shift(UP) - arrow = Arrow( - g_hat_f.target.get_left(), - plane.coords_to_point(0, 0), - color = self.center_of_mass_color, - ) - - frequency_axes = self.frequency_axes - imaginary_fourier_graph = frequency_axes.get_graph( - get_fourier_transform( - graph.underlying_function, - t_min = self.time_axes.x_min, - t_max = self.time_axes.x_max, - real_part = False, - use_almost_fourier = False, - ) - ) - imaginary_fourier_graph.set_color(BLUE) - imaginary_fourier_graph.shift( - frequency_axes.x_axis.get_right() - \ - imaginary_fourier_graph.points[-1], - ) - - real_part = TextMobject( - "Real part of", "$\\hat g(f)$" - ) - real_part[1].match_style(g_hat_f) - real_part.move_to(g_hat_f) - real_part.to_edge(RIGHT) - - self.get_polarized_mobject(graph, freq = 0) - update_pol_graph = UpdateFromFunc( - graph.polarized_mobject, - lambda m : m.set_stroke(width = 2) - ) - com_dot = self.get_center_of_mass_dot() - - winding_run_time = 40.0 - g_hat_f_indication = Succession( - Animation, Mobject(), {"run_time" : 4}, - FocusOn, g_hat_f, - ShowCreation, g_hat_f_rect, - Animation, Mobject(), - Transform, g_hat_f_rect, g_hat_rect, - Animation, Mobject(), - FadeOut, g_hat_f_rect, - Animation, Mobject(), - MoveToTarget, g_hat_f, - UpdateFromAlphaFunc, com_dot, lambda m, a : m.set_fill(opacity = a), - Animation, Mobject(), {"run_time" : 2}, - GrowArrow, arrow, - FadeOut, arrow, - Animation, Mobject(), {"run_time" : 5}, - Write, real_part, {"run_time" : 2}, - Animation, Mobject(), {"run_time" : 3}, - ShowCreation, imaginary_fourier_graph, {"run_time" : 3}, - rate_func = squish_rate_func( - lambda x : x, 0, 31./winding_run_time - ), - run_time = winding_run_time - ) - - self.play( - FadeIn(plane), - ReplacementTransform( - graph.copy(), graph.polarized_mobject - ), - morty.change, "happy", - ) - self.generate_center_of_mass_dot_update_anim(multiplier = 4.5) - self.generate_fourier_dot_transform(fourier_graph) - self.change_frequency( - 5.0, - rate_func=linear, - run_time = winding_run_time, - added_anims = [ - g_hat_f_indication, - update_pol_graph, - Animation(frequency_axes.x_axis.numbers), - Animation(self.fourier_graph_dot), - ] - ) - self.wait() - -class SummarizeFormula(Scene): - def construct(self): - expression = self.get_expression() - screen_rect = ScreenRectangle(height = 5) - screen_rect.to_edge(DOWN) - - exp_rect, g_exp_rect, int_rect = [ - SurroundingRectangle(VGroup( - expression.get_part_by_tex(p1), - expression.get_part_by_tex(p2), - )) - for p1, p2 in [("e", "t}"), ("g({}", "t}"), ("\\int", "dt")] - ] - - self.add(expression) - self.wait() - self.play( - ShowCreation(screen_rect), - ShowCreation(exp_rect), - ) - self.wait(2) - self.play(Transform(exp_rect, g_exp_rect)) - self.wait(2) - self.play(Transform(exp_rect, int_rect)) - self.wait(2) - - def get_expression(self): - expression = TexMobject( - "\\hat g(", "f", ")", "=", "\\int", "_{t_1}", "^{t_2}", - "g({}", "t", ")", "e", "^{-2\\pi i", "f", "t}", "dt" - ) - expression.set_color_by_tex( - "t", YELLOW, substring = False, - ) - expression.set_color_by_tex("t}", YELLOW) - expression.set_color_by_tex( - "f", RED, substring = False, - ) - expression.scale(1.2) - expression.to_edge(UP) - return expression - -class OneSmallNote(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Just one \\\\ small note...", - # target_mode = - ) - self.change_student_modes("erm", "happy", "sassy") - self.wait(2) - -class BoundsAtInfinity(SummarizeFormula): - def construct(self): - expression = self.get_expression() - self.add(expression) - self.add_graph() - axes = self.axes - graph = self.graph - - time_interval = self.get_time_interval(-2, 2) - wide_interval = self.get_time_interval(-FRAME_X_RADIUS, FRAME_X_RADIUS) - bounds = VGroup(*reversed(expression.get_parts_by_tex("t_"))) - bound_rects = VGroup(*[ - SurroundingRectangle(b, buff = 0.5*SMALL_BUFF) - for b in bounds - ]) - bound_rects.set_color(TEAL) - inf_bounds = VGroup(*[ - VGroup(TexMobject(s + "\\infty")) - for s in ("-", "+") - ]) - decimal_bounds = VGroup(*[DecimalNumber(0) for x in range(2)]) - for bound, inf_bound, d_bound in zip(bounds, inf_bounds, decimal_bounds): - for new_bound in inf_bound, d_bound: - new_bound.scale(0.7) - new_bound.move_to(bound, LEFT) - new_bound.bound = bound - def get_db_num_update(vect): - return lambda a : axes.x_axis.point_to_number( - time_interval.get_edge_center(vect) - ) - decimal_updates = [ - ChangingDecimal( - db, get_db_num_update(vect), - position_update_func = lambda m : m.move_to( - m.bound, LEFT - ) - ) - for db, vect in zip(decimal_bounds, [LEFT, RIGHT]) - ] - for update in decimal_updates: - update.update(1) - - time_interval.save_state() - self.wait() - self.play(ReplacementTransform( - self.get_time_interval(0, 0.01), time_interval - )) - self.play(LaggedStartMap(ShowCreation, bound_rects)) - self.wait() - self.play(FadeOut(bound_rects)) - self.play(ReplacementTransform(bounds, inf_bounds)) - self.play(Transform( - time_interval, wide_interval, - run_time = 4, - rate_func = there_and_back - )) - self.play( - ReplacementTransform(inf_bounds, decimal_bounds), - time_interval.restore, - ) - self.play( - VGroup(axes, graph).stretch, 0.05, 0, - Transform(time_interval, wide_interval), - UpdateFromAlphaFunc( - axes.x_axis.numbers, - lambda m, a : m.set_fill(opacity = 1-a) - ), - *decimal_updates, - run_time = 12, - rate_func = bezier([0, 0, 1, 1]) - ) - self.wait() - - - def add_graph(self): - axes = Axes( - x_min = -140, - x_max = 140, - y_min = -2, - y_max = 2, - axis_config = { - "include_tip" : False, - }, - ) - axes.x_axis.add_numbers(*list(filter( - lambda x : x != 0, - list(range(-8, 10, 2)), - ))) - axes.shift(DOWN) - self.add(axes) - - def func(x): - return np.exp(-0.1*x**2)*(1 + np.cos(TAU*x)) - graph = axes.get_graph(func) - self.add(graph) - graph.set_color(YELLOW) - - self.set_variables_as_attrs(axes, graph) - - def get_time_interval(self, t1, t2): - line = Line(*[ - self.axes.coords_to_point(t, 0) - for t in (t1, t2) - ]) - rect = Rectangle( - stroke_width = 0, - fill_color = TEAL, - fill_opacity = 0.5, - ) - rect.match_width(line) - rect.stretch_to_fit_height(2.5) - rect.move_to(line, DOWN) - return rect - -class MoreToCover(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Much more to say...", - target_mode = "hooray", - run_time = 1, - ) - self.wait() - self.teacher_says( - "SO MUCH!", - target_mode = "surprised", - added_anims = [self.get_student_changes(*3*["happy"])], - run_time = 0.5 - ) - self.wait(2) - -class ShowUncertaintyPrinciple(Scene): - def construct(self): - title = TextMobject("Uncertainty principle") - self.add(title) - top_axes = Axes( - x_min = -FRAME_X_RADIUS, - x_max = FRAME_X_RADIUS, - y_min = 0, - y_max = 3, - y_axis_config = { - "unit_size" : 0.6, - "include_tip" : False, - } - ) - bottom_axes = top_axes.deepcopy() - arrow = Vector(DOWN, color = WHITE) - group = VGroup( - title, top_axes, arrow, bottom_axes - ) - group.arrange(DOWN) - title.shift(MED_SMALL_BUFF*UP) - group.to_edge(UP) - fourier_word = TextMobject("Fourier transform") - fourier_word.next_to(arrow, RIGHT) - self.add(group, fourier_word) - - ghost_dot = Dot(RIGHT, fill_opacity = 0) - def get_bell_func(factor = 1): - return lambda x : 2*np.exp(-factor*x**2) - top_graph = top_axes.get_graph(get_bell_func()) - top_graph.set_color(YELLOW) - bottom_graph = bottom_axes.get_graph(get_bell_func()) - bottom_graph.set_color(RED) - def get_update_func(axes): - def update_graph(graph): - f = ghost_dot.get_center()[0] - if axes == bottom_axes: - f = 1./f - new_graph = axes.get_graph(get_bell_func(f)) - graph.points = new_graph.points - return update_graph - - factors = [0.3, 0.1, 2, 10, 100, 0.01, 0.5] - - self.play(ShowCreation(top_graph)) - self.play(ReplacementTransform( - top_graph.copy(), - bottom_graph, - )) - self.wait(2) - self.add(*[ - Mobject.add_updater(graph, get_update_func(axes)) - for graph, axes in [(top_graph, top_axes), (bottom_graph, bottom_axes)] - ]) - for factor in factors: - self.play( - ghost_dot.move_to, factor*RIGHT, - run_time = 2 - ) - self.wait() - -class XCoordinateLabelTypoFix(Scene): - def construct(self): - words = TextMobject("$x$-coordinate for center of mass") - words.set_color(RED) - self.add(words) - -class NextVideoWrapper(Scene): - def construct(self): - title = TextMobject("Next video") - title.to_edge(UP) - screen_rect = ScreenRectangle(height = 6) - screen_rect.next_to(title, DOWN) - self.add(title) - self.play(ShowCreation(screen_rect)) - self.wait(2) - -class SubscribeOrBinge(PiCreatureScene): - def construct(self): - morty = self.pi_creature - morty.center().to_edge(DOWN, LARGE_BUFF) - subscribe = TextMobject("Subscribe") - subscribe.set_color(RED) - subscribe.next_to(morty, UP+RIGHT) - binge = TextMobject("Binge") - binge.set_color(BLUE) - binge.next_to(morty, UP+LEFT) - - videos = VGroup(*[VideoIcon() for x in range(30)]) - colors = it.cycle([BLUE_D, BLUE_E, BLUE_C, GREY_BROWN]) - for video, color in zip(videos, colors): - video.set_color(color) - videos.move_to(binge.get_bottom(), UP) - video_anim = LaggedStartMap( - Succession, videos, - lambda v : ( - FadeIn, v, - ApplyMethod, v.shift, 5*DOWN, {"run_time" : 6}, - ), - run_time = 10 - ) - sub_arrow = Arrow( - subscribe.get_bottom(), - Dot().to_corner(DOWN+RIGHT, buff = LARGE_BUFF), - color = RED - ) - - for word in subscribe, binge: - word.save_state() - word.shift(DOWN) - word.set_fill(opacity = 0) - - self.play( - subscribe.restore, - morty.change, "raise_left_hand" - ) - self.play(GrowArrow(sub_arrow)) - self.wait() - self.play( - video_anim, - Succession( - AnimationGroup( - ApplyMethod(binge.restore), - ApplyMethod(morty.change, "raise_right_hand", binge), - ), - Blink, morty, - ApplyMethod, morty.change, "shruggie", videos, - Animation, Mobject(), {"run_time" : 2}, - Blink, morty, - Animation, Mobject(), {"run_time" : 4} - ) - ) - -class CloseWithAPuzzle(TeacherStudentsScene): - def construct(self): - self.teacher_says("Close with a puzzle!", run_time = 1) - self.change_student_modes(*["hooray"]*3) - self.wait(3) - -class PuzzleDescription(Scene): - def construct(self): - lines = VGroup( - TextMobject("Convex set", "$C$", "in $\\mathds{R}^3$"), - TextMobject("Boundary", "$B$", "$=$", "$\\partial C$"), - TextMobject("$D$", "$=\\{p+q | p, q \\in B\\}$"), - TextMobject("Prove that", "$D$", "is convex") - ) - for line in lines: - line.set_color_by_tex_to_color_map({ - "$C$" : BLUE_D, - "\\partial C" : BLUE_D, - "$B$" : BLUE_C, - "$D$" : YELLOW, - }) - VGroup(lines[2][1][2], lines[2][1][6]).set_color(RED) - VGroup(lines[2][1][4], lines[2][1][8]).set_color(MAROON_B) - lines[2][1][10].set_color(BLUE_C) - lines.scale(1.25) - lines.arrange(DOWN, buff = LARGE_BUFF, aligned_edge = LEFT) - - lines.to_corner(UP+RIGHT) - - for line in lines: - self.play(Write(line)) - self.wait(2) - -class SponsorScreenGrab(PiCreatureScene): - def construct(self): - morty = self.pi_creature - screen = ScreenRectangle(height = 5) - screen.to_corner(UP+LEFT) - screen.shift(MED_LARGE_BUFF*DOWN) - url = TextMobject("janestreet.com/3b1b") - url.next_to(screen, UP) - - self.play( - morty.change, "raise_right_hand", - ShowCreation(screen) - ) - self.play(Write(url)) - self.wait(2) - for mode in "happy", "thinking", "pondering", "thinking": - self.play(morty.change, mode, screen) - self.wait(4) - -class FourierEndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons" : [ - "CrypticSwarm", - "Ali Yahya", - "Juan Benet", - "Markus Persson", - "Damion Kistler", - "Burt Humburg", - "Yu Jun", - "Dave Nicponski", - "Kaustuv DeBiswas", - "Joseph John Cox", - "Luc Ritchie", - "Achille Brighton", - "Rish Kundalia", - "Yana Chernobilsky", - "Shìmín Kuang", - "Mathew Bramson", - "Jerry Ling", - "Mustafa Mahdi", - "Meshal Alshammari", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Robert Teed", - "One on Epsilon", - "Samantha D. Suplee", - "Mark Govea", - "John Haley", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Boris Veselinovich", - "Ryan Dahl", - "Ripta Pasay", - "Eric Lavault", - "Mads Elvheim", - "Andrew Busey", - "Randall Hunt", - "Desmos", - "Tianyu Ge", - "Awoo", - "Dr David G. Stork", - "Linh Tran", - "Jason Hise", - "Bernd Sing", - "Ankalagon", - "Mathias Jansson", - "David Clark", - "Ted Suzman", - "Eric Chow", - "Michael Gardner", - "Jonathan Eppele", - "Clark Gaebel", - "David Kedmey", - "Jordan Scales", - "Ryan Atallah", - "supershabam", - "1stViewMaths", - "Jacob Magnuson", - "Thomas Tarler", - "Isak Hietala", - "James Thornton", - "Egor Gumenuk", - "Waleed Hamied", - "Oliver Steele", - "Yaw Etse", - "David B", - "Julio Cesar Campo Neto", - "Delton Ding", - "George Chiesa", - "Chloe Zhou", - "Alexander Nye", - "Ross Garber", - "Wang HaoRan", - "Felix Tripier", - "Arthur Zey", - "Norton", - "Kevin Le", - "Alexander Feldman", - "David MacCumber", - ], - } - -class Thumbnail(Scene): - def construct(self): - title = TextMobject("Fourier\\\\", "Visualized") - title.set_color(YELLOW) - title.set_stroke(RED, 2) - title.scale(2.5) - title.add_background_rectangle() - - def func(t): - return np.cos(2*TAU*t) + np.cos(3*TAU*t) + np.cos(5*t) - fourier = get_fourier_transform(func, -5, 5) - - graph = FunctionGraph(func, x_min = -5, x_max = 5) - graph.set_color(BLUE) - fourier_graph = FunctionGraph(fourier, x_min = 0, x_max = 6) - fourier_graph.set_color(YELLOW) - for g in graph, fourier_graph: - g.stretch_to_fit_height(2) - g.stretch_to_fit_width(10) - g.set_stroke(width = 8) - - pol_graphs = VGroup() - for f in np.linspace(1.98, 2.02, 7): - pol_graph = ParametricFunction( - lambda t : complex_to_R3( - (2+np.cos(2*TAU*t)+np.cos(3*TAU*t))*np.exp(-complex(0, TAU*f*t)) - ), - t_min = -5, - t_max = 5, - num_graph_points = 200, - ) - pol_graph.match_color(graph) - pol_graph.set_height(2) - pol_graphs.add(pol_graph) - pol_graphs.arrange(RIGHT, buff = LARGE_BUFF) - pol_graphs.set_color_by_gradient(BLUE_C, YELLOW) - pol_graphs.match_width(graph) - pol_graphs.set_stroke(width = 2) - - - self.clear() - title.center().to_edge(UP) - pol_graphs.set_width(FRAME_WIDTH - 1) - pol_graphs.center() - title.move_to(pol_graphs) - title.shift(SMALL_BUFF*LEFT) - graph.next_to(title, UP) - fourier_graph.next_to(title, DOWN) - self.add(pol_graphs, title, graph, fourier_graph) - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/fractal_charm.py b/from_3b1b/old/fractal_charm.py deleted file mode 100644 index 33a2d899..00000000 --- a/from_3b1b/old/fractal_charm.py +++ /dev/null @@ -1,111 +0,0 @@ -from manimlib.imports import * - -class FractalCreation(Scene): - CONFIG = { - "fractal_class" : PentagonalFractal, - "max_order" : 5, - "transform_kwargs" : { - "path_arc" : np.pi/6, - "lag_ratio" : 0.5, - "run_time" : 2, - }, - "fractal_kwargs" : {}, - } - def construct(self): - fractal = self.fractal_class(order = 0, **self.fractal_kwargs) - self.play(FadeIn(fractal)) - for order in range(1, self.max_order+1): - new_fractal = self.fractal_class( - order = order, - **self.fractal_kwargs - ) - fractal.align_data(new_fractal) - self.play(Transform( - fractal, new_fractal, - **self.transform_kwargs - )) - self.wait() - self.wait() - self.fractal = fractal - -class PentagonalFractalCreation(FractalCreation): - pass - -class DiamondFractalCreation(FractalCreation): - CONFIG = { - "fractal_class" : DiamondFractal, - "max_order" : 6, - "fractal_kwargs" : {"height" : 6} - } - -class PiCreatureFractalCreation(FractalCreation): - CONFIG = { - "fractal_class" : PiCreatureFractal, - "max_order" : 6, - "fractal_kwargs" : {"height" : 6}, - "transform_kwargs" : { - "lag_ratio" : 0, - "run_time" : 2, - }, - } - def construct(self): - FractalCreation.construct(self) - fractal = self.fractal - smallest_pi = fractal[0][0] - zoom_factor = 0.1/smallest_pi.get_height() - fractal.generate_target() - fractal.target.shift(-smallest_pi.get_corner(UP+LEFT)) - fractal.target.scale(zoom_factor) - self.play(MoveToTarget(fractal, run_time = 10)) - self.wait() - -class QuadraticKochFractalCreation(FractalCreation): - CONFIG = { - "fractal_class" : QuadraticKoch, - "max_order" : 5, - "fractal_kwargs" : {"radius" : 10}, - # "transform_kwargs" : { - # "lag_ratio" : 0, - # "run_time" : 2, - # }, - } - -class KochSnowFlakeFractalCreation(FractalCreation): - CONFIG = { - "fractal_class" : KochSnowFlake, - "max_order" : 6, - "fractal_kwargs" : { - "radius" : 6, - "num_submobjects" : 100, - }, - "transform_kwargs" : { - "lag_ratio" : 0.5, - "path_arc" : np.pi/6, - "run_time" : 2, - }, - } - -class WonkyHexagonFractalCreation(FractalCreation): - CONFIG = { - "fractal_class" : WonkyHexagonFractal, - "max_order" : 5, - "fractal_kwargs" : {"height" : 6}, - } - -class SierpinskiFractalCreation(FractalCreation): - CONFIG = { - "fractal_class" : Sierpinski, - "max_order" : 6, - "fractal_kwargs" : {"height" : 6}, - "transform_kwargs" : { - "path_arc" : 0, - }, - } - -class CircularFractalCreation(FractalCreation): - CONFIG = { - "fractal_class" : CircularFractal, - "max_order" : 5, - "fractal_kwargs" : {"height" : 6}, - } - diff --git a/from_3b1b/old/fractal_dimension.py b/from_3b1b/old/fractal_dimension.py deleted file mode 100644 index 4b5d567d..00000000 --- a/from_3b1b/old/fractal_dimension.py +++ /dev/null @@ -1,2931 +0,0 @@ - -from manimlib.imports import * -from functools import reduce - -def break_up(mobject, factor = 1.3): - mobject.scale_in_place(factor) - for submob in mobject: - submob.scale_in_place(1./factor) - return mobject - -class Britain(SVGMobject): - CONFIG = { - "file_name" : "Britain.svg", - "stroke_width" : 0, - "fill_color" : BLUE_D, - "fill_opacity" : 1, - "height" : 5, - "mark_paths_closed" : True, - } - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - self.points = self[0].points - self.submobjects = [] - self.set_height(self.height) - self.center() - -class Norway(Britain): - CONFIG = { - "file_name" : "Norway", - "mark_paths_closed" : False - } - -class KochTest(Scene): - def construct(self): - koch = KochCurve(order = 5, stroke_width = 2) - - self.play(ShowCreation(koch, run_time = 3)) - self.play( - koch.scale, 3, koch.get_left(), - koch.set_stroke, None, 4 - ) - self.wait() - -class SierpinskiTest(Scene): - def construct(self): - sierp = Sierpinski( - order = 5, - ) - - self.play(FadeIn( - sierp, - run_time = 5, - lag_ratio = 0.5, - )) - self.wait() - # self.play(sierp.scale, 2, sierp.get_top()) - # self.wait(3) - - - - -################################### - - -class ZoomInOnFractal(PiCreatureScene): - CONFIG = { - "fractal_order" : 6, - "num_zooms" : 5, - "fractal_class" : DiamondFractal, - "index_to_replace" : 0, - } - def construct(self): - morty = self.pi_creature - - fractal = self.fractal_class(order = self.fractal_order) - fractal.show() - - fractal = self.introduce_fractal() - self.change_mode("thinking") - self.blink() - self.zoom_in(fractal) - - - def introduce_fractal(self): - fractal = self.fractal_class(order = 0) - self.play(FadeIn(fractal)) - for order in range(1, self.fractal_order+1): - new_fractal = self.fractal_class(order = order) - self.play( - Transform(fractal, new_fractal, run_time = 2), - self.pi_creature.change_mode, "hooray" - ) - return fractal - - def zoom_in(self, fractal): - grower = fractal[self.index_to_replace] - grower_target = fractal.copy() - - for x in range(self.num_zooms): - self.tweak_fractal_subpart(grower_target) - grower_family = grower.family_members_with_points() - everything = VGroup(*[ - submob - for submob in fractal.family_members_with_points() - if not submob.is_off_screen() - if submob not in grower_family - ]) - everything.generate_target() - everything.target.shift( - grower_target.get_center()-grower.get_center() - ) - everything.target.scale( - grower_target.get_height()/grower.get_height() - ) - - self.play( - Transform(grower, grower_target), - MoveToTarget(everything), - self.pi_creature.change_mode, "thinking", - run_time = 2 - ) - self.wait() - grower_target = grower.copy() - grower = grower[self.index_to_replace] - - - def tweak_fractal_subpart(self, subpart): - subpart.rotate_in_place(np.pi/4) - -class WhatAreFractals(TeacherStudentsScene): - def construct(self): - self.student_says( - "But what \\emph{is} a fractal?", - student_index = 2, - width = 6 - ) - self.change_student_modes("thinking", "pondering", None) - self.wait() - - name = TextMobject("Benoit Mandelbrot") - name.to_corner(UP+LEFT) - # picture = Rectangle(height = 4, width = 3) - picture = ImageMobject("Mandelbrot") - picture.set_height(4) - picture.next_to(name, DOWN) - self.play( - Write(name, run_time = 2), - FadeIn(picture), - *[ - ApplyMethod(pi.look_at, name) - for pi in self.get_pi_creatures() - ] - ) - self.wait(2) - question = TextMobject("Aren't they", "self-similar", "shapes?") - question.set_color_by_tex("self-similar", YELLOW) - self.student_says(question) - self.play(self.get_teacher().change_mode, "happy") - self.wait(2) - -class IntroduceVonKochCurve(Scene): - CONFIG = { - "order" : 5, - "stroke_width" : 3, - } - def construct(self): - snowflake = self.get_snowflake() - name = TextMobject("Von Koch Snowflake") - name.to_edge(UP) - - self.play(ShowCreation(snowflake, run_time = 3)) - self.play(Write(name, run_time = 2)) - curve = self.isolate_one_curve(snowflake) - self.wait() - - self.zoom_in_on(curve) - self.zoom_in_on(curve) - self.zoom_in_on(curve) - - def get_snowflake(self): - triangle = RegularPolygon(n = 3, start_angle = np.pi/2) - triangle.set_height(4) - curves = VGroup(*[ - KochCurve( - order = self.order, - stroke_width = self.stroke_width - ) - for x in range(3) - ]) - for index, curve in enumerate(curves): - width = curve.get_width() - curve.move_to( - (np.sqrt(3)/6)*width*UP, DOWN - ) - curve.rotate(-index*2*np.pi/3) - curves.set_color_by_gradient(BLUE, WHITE, BLUE) - - return curves - - def isolate_one_curve(self, snowflake): - self.play(*[ - ApplyMethod(curve.shift, curve.get_center()/2) - for curve in snowflake - ]) - self.wait() - self.play( - snowflake.scale, 2.1, - snowflake.next_to, UP, DOWN - ) - self.remove(*snowflake[1:]) - return snowflake[0] - - def zoom_in_on(self, curve): - larger_curve = KochCurve( - order = self.order+1, - stroke_width = self.stroke_width - ) - larger_curve.replace(curve) - larger_curve.scale(3, about_point = curve.get_corner(DOWN+LEFT)) - larger_curve.set_color_by_gradient( - curve[0].get_color(), - curve[-1].get_color(), - ) - - self.play(Transform(curve, larger_curve, run_time = 2)) - n_parts = len(curve.split()) - sub_portion = VGroup(*curve[:n_parts/4]) - self.play( - sub_portion.set_color, YELLOW, - rate_func = there_and_back - ) - self.wait() - -class IntroduceSierpinskiTriangle(PiCreatureScene): - CONFIG = { - "order" : 7, - } - def construct(self): - sierp = Sierpinski(order = self.order) - sierp.save_state() - - self.play(FadeIn( - sierp, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait() - self.play( - self.pi_creature.change_mode, "pondering", - *[ - ApplyMethod(submob.shift, submob.get_center()) - for submob in sierp - ] - ) - self.wait() - for submob in sierp: - self.play(sierp.shift, -submob.get_center()) - self.wait() - self.play(sierp.restore) - self.change_mode("happy") - self.wait() - -class SelfSimilarFractalsAsSubset(Scene): - CONFIG = { - "fractal_width" : 1.5 - } - def construct(self): - self.add_self_similar_fractals() - self.add_general_fractals() - - def add_self_similar_fractals(self): - fractals = VGroup( - DiamondFractal(order = 5), - KochSnowFlake(order = 3), - Sierpinski(order = 5), - ) - for submob in fractals: - submob.set_width(self.fractal_width) - fractals.arrange(RIGHT) - fractals[-1].next_to(VGroup(*fractals[:-1]), DOWN) - - title = TextMobject("Self-similar fractals") - title.next_to(fractals, UP) - - small_rect = Rectangle() - small_rect.replace(VGroup(fractals, title), stretch = True) - small_rect.scale_in_place(1.2) - self.small_rect = small_rect - - group = VGroup(fractals, title, small_rect) - group.to_corner(UP+LEFT, buff = MED_LARGE_BUFF) - - self.play( - Write(title), - ShowCreation(fractals), - run_time = 3 - ) - self.play(ShowCreation(small_rect)) - self.wait() - - def add_general_fractals(self): - big_rectangle = Rectangle( - width = FRAME_WIDTH - MED_LARGE_BUFF, - height = FRAME_HEIGHT - MED_LARGE_BUFF, - ) - title = TextMobject("Fractals") - title.scale(1.5) - title.next_to(ORIGIN, RIGHT, buff = LARGE_BUFF) - title.to_edge(UP, buff = MED_LARGE_BUFF) - - britain = Britain( - fill_opacity = 0, - stroke_width = 2, - stroke_color = WHITE, - ) - britain.next_to(self.small_rect, RIGHT) - britain.shift(2*DOWN) - - randy = Randolph().flip().scale(1.4) - randy.next_to(britain, buff = SMALL_BUFF) - randy.generate_target() - randy.target.change_mode("pleading") - fractalify(randy.target, order = 2) - - self.play( - ShowCreation(big_rectangle), - Write(title), - ) - self.play(ShowCreation(britain), run_time = 5) - self.play( - britain.set_fill, BLUE, 1, - britain.set_stroke, None, 0, - run_time = 2 - ) - self.play(FadeIn(randy)) - self.play(MoveToTarget(randy, run_time = 2)) - self.wait(2) - -class ConstrastSmoothAndFractal(Scene): - CONFIG = { - "britain_zoom_point_proportion" : 0.45, - "scale_factor" : 50, - "fractalification_order" : 2, - "fractal_dimension" : 1.21, - } - def construct(self): - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - smooth = TextMobject("Smooth") - smooth.shift(FRAME_X_RADIUS*LEFT/2) - fractal = TextMobject("Fractal") - fractal.shift(FRAME_X_RADIUS*RIGHT/2) - VGroup(smooth, fractal).to_edge(UP) - background_rectangle = Rectangle( - height = FRAME_HEIGHT, - width = FRAME_X_RADIUS, - ) - background_rectangle.to_edge(RIGHT, buff = 0) - background_rectangle.set_fill(BLACK, 1) - background_rectangle.set_stroke(width = 0) - self.add(v_line, background_rectangle, smooth, fractal) - - britain = Britain( - fill_opacity = 0, - stroke_width = 2, - stroke_color = WHITE, - )[0] - anchors = britain.get_anchors() - smooth_britain = VMobject() - smooth_britain.set_points_smoothly(anchors[::10]) - smooth_britain.center().shift(FRAME_X_RADIUS*LEFT/2) - index = np.argmax(smooth_britain.get_anchors()[:,0]) - smooth_britain.zoom_point = smooth_britain.point_from_proportion( - self.britain_zoom_point_proportion - ) - - britain.shift(FRAME_X_RADIUS*RIGHT/2) - britain.zoom_point = britain.point_from_proportion( - self.britain_zoom_point_proportion - ) - fractalify( - britain, - order = self.fractalification_order, - dimension = self.fractal_dimension, - ) - - britains = VGroup(britain, smooth_britain) - self.play(*[ - ShowCreation(mob, run_time = 3) - for mob in britains - ]) - self.play( - britains.set_fill, BLUE, 1, - britains.set_stroke, None, 0, - ) - self.wait() - self.play( - ApplyMethod( - smooth_britain.scale, - self.scale_factor, - smooth_britain.zoom_point - ), - Animation(v_line), - Animation(background_rectangle), - ApplyMethod( - britain.scale, - self.scale_factor, - britain.zoom_point - ), - Animation(smooth), - Animation(fractal), - run_time = 10, - ) - self.wait(2) - -class InfiniteKochZoom(Scene): - CONFIG = { - "order" : 6, - "left_point" : 3*LEFT, - } - def construct(self): - small_curve = self.get_curve(self.order) - larger_curve = self.get_curve(self.order + 1) - larger_curve.scale(3, about_point = small_curve.points[0]) - self.play(Transform(small_curve, larger_curve, run_time = 2)) - self.repeat_frames(5) - - - - def get_curve(self, order): - koch_curve = KochCurve( - monochromatic = True, - order = order, - color = BLUE, - stroke_width = 2, - ) - koch_curve.set_width(18) - koch_curve.shift( - self.left_point - koch_curve.points[0] - ) - return koch_curve - -class ShowIdealizations(Scene): - def construct(self): - arrow = DoubleArrow(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) - arrow.shift(DOWN) - left_words = TextMobject("Idealization \\\\ as smooth") - middle_words = TextMobject("Nature") - right_words = TextMobject(""" - Idealization - as perfectly - self-similar - """) - for words in left_words, middle_words, right_words: - words.scale(0.8) - words.next_to(arrow, DOWN) - left_words.to_edge(LEFT) - right_words.to_edge(RIGHT) - self.add(arrow, left_words, middle_words, right_words) - - britain = Britain()[0] - britain.set_height(4) - britain.next_to(arrow, UP) - - anchors = britain.get_anchors() - smooth_britain = VMobject() - smooth_britain.set_points_smoothly(anchors[::10]) - smooth_britain.set_stroke(width = 0) - smooth_britain.set_fill(BLUE_D, opacity = 1) - smooth_britain.next_to(arrow, UP) - smooth_britain.to_edge(LEFT) - - koch_snowflake = KochSnowFlake(order = 5, monochromatic = True) - koch_snowflake.set_stroke(width = 0) - koch_snowflake.set_fill(BLUE_D, opacity = 1) - koch_snowflake.set_height(3) - koch_snowflake.rotate(2*np.pi/3) - koch_snowflake.next_to(arrow, UP) - koch_snowflake.to_edge(RIGHT) - - VGroup(smooth_britain, britain, koch_snowflake).set_color_by_gradient( - BLUE_B, BLUE_D - ) - - self.play(FadeIn(britain)) - self.wait() - self.play(Transform(britain.copy(), smooth_britain)) - self.wait() - self.play(Transform(britain.copy(), koch_snowflake)) - self.wait() - self.wait(2) - -class SayFractalDimension(TeacherStudentsScene): - def construct(self): - self.teacher_says("Fractal dimension") - self.change_student_modes("confused", "hesitant", "pondering") - self.wait(3) - -class ExamplesOfDimension(Scene): - def construct(self): - labels = VGroup(*[ - TextMobject("%s-dimensional"%s) - for s in ("1.585", "1.262", "1.21") - ]) - fractals = VGroup(*[ - Sierpinski(order = 7), - KochSnowFlake(order = 5), - Britain(stroke_width = 2, fill_opacity = 0) - ]) - for fractal, vect in zip(fractals, [LEFT, ORIGIN, RIGHT]): - fractal.to_edge(vect) - fractals[2].shift(0.5*UP) - fractals[1].shift(0.5*RIGHT) - for fractal, label, vect in zip(fractals, labels, [DOWN, UP, DOWN]): - label.next_to(fractal, vect) - label.shift_onto_screen() - self.play( - ShowCreation(fractal), - Write(label), - run_time = 3 - ) - self.wait() - self.wait() - -class FractalDimensionIsNonsense(Scene): - def construct(self): - morty = Mortimer().shift(DOWN+3*RIGHT) - mathy = Mathematician().shift(DOWN+3*LEFT) - morty.make_eye_contact(mathy) - - self.add(morty, mathy) - self.play( - PiCreatureSays( - mathy, "It's 1.585-dimensional!", - target_mode = "hooray" - ), - morty.change_mode, "hesitant" - ) - self.play(Blink(morty)) - self.play( - PiCreatureSays(morty, "Nonsense!", target_mode = "angry"), - FadeOut(mathy.bubble), - FadeOut(mathy.bubble.content), - mathy.change_mode, "guilty" - ) - self.play(Blink(mathy)) - self.wait() - -class DimensionForNaturalNumbers(Scene): - def construct(self): - labels = VGroup(*[ - TextMobject("%d-dimensional"%d) - for d in (1, 2, 3) - ]) - for label, vect in zip(labels, [LEFT, ORIGIN, RIGHT]): - label.to_edge(vect) - labels.shift(2*DOWN) - - line = Line(DOWN+LEFT, 3*UP+RIGHT, color = BLUE) - line.next_to(labels[0], UP) - - self.play( - Write(labels[0]), - ShowCreation(line) - ) - self.wait() - for label in labels[1:]: - self.play(Write(label)) - self.wait() - -class Show2DPlanein3D(Scene): - def construct(self): - pass - -class ShowCubeIn3D(Scene): - def construct(self): - pass - -class OfCourseItsMadeUp(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Fractal dimension - \\emph{is} a made up concept... - """) - self.change_student_modes(*["hesitant"]*3) - self.wait(2) - self.teacher_says( - """But it's useful!""", - target_mode = "hooray" - ) - self.change_student_modes(*["happy"]*3) - self.wait(3) - -class FourSelfSimilarShapes(Scene): - CONFIG = { - "shape_width" : 2, - "sierpinski_order" : 6, - } - def construct(self): - titles = self.get_titles() - shapes = self.get_shapes(titles) - - self.introduce_shapes(titles, shapes) - self.show_self_similarity(shapes) - self.mention_measurements() - - def get_titles(self): - titles = VGroup(*list(map(TextMobject, [ - "Line", "Square", "Cube", "Sierpinski" - ]))) - for title, x in zip(titles, np.linspace(-0.75, 0.75, 4)): - title.shift(x*FRAME_X_RADIUS*RIGHT) - titles.to_edge(UP) - return titles - - def get_shapes(self, titles): - line = VGroup( - Line(LEFT, ORIGIN), - Line(ORIGIN, RIGHT) - ) - line.set_color(BLUE_C) - - square = VGroup(*[ - Square().next_to(ORIGIN, vect, buff = 0) - for vect in compass_directions(start_vect = DOWN+LEFT) - ]) - square.set_stroke(width = 0) - square.set_fill(BLUE, 0.7) - - cube = TextMobject("TODO") - cube.set_fill(opacity = 0) - - sierpinski = Sierpinski(order = self.sierpinski_order) - - shapes = VGroup(line, square, cube, sierpinski) - for shape, title in zip(shapes, titles): - shape.set_width(self.shape_width) - shape.next_to(title, DOWN, buff = MED_SMALL_BUFF) - line.shift(DOWN) - - return shapes - - def introduce_shapes(self, titles, shapes): - line, square, cube, sierpinski = shapes - - brace = Brace(VGroup(*shapes[:3]), DOWN) - brace_text = brace.get_text("Not fractals") - - self.play(ShowCreation(line)) - self.play(GrowFromCenter(square)) - self.play(FadeIn(cube)) - self.play(ShowCreation(sierpinski)) - self.wait() - self.play( - GrowFromCenter(brace), - FadeIn(brace_text) - ) - self.wait() - self.play(*list(map(FadeOut, [brace, brace_text]))) - self.wait() - - for title in titles: - self.play(Write(title, run_time = 1)) - self.wait(2) - - def show_self_similarity(self, shapes): - shapes_copy = shapes.copy() - self.shapes_copy = shapes_copy - line, square, cube, sierpinski = shapes_copy - - self.play(line.shift, 3*DOWN) - self.play(ApplyFunction(break_up, line)) - self.wait() - brace = Brace(line[0], DOWN) - brace_text = brace.get_text("1/2") - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - brace.add(brace_text) - self.wait() - - self.play(square.next_to, square, DOWN, LARGE_BUFF) - self.play(ApplyFunction(break_up, square)) - subsquare = square[0] - subsquare.save_state() - self.play(subsquare.replace, shapes[1]) - self.wait() - self.play(subsquare.restore) - self.play(brace.next_to, subsquare, DOWN) - self.wait() - - self.wait(5)#Handle cube - - self.play(sierpinski.next_to, sierpinski, DOWN, LARGE_BUFF) - self.play(ApplyFunction(break_up, sierpinski)) - self.wait() - self.play(brace.next_to, sierpinski[0], DOWN) - self.wait(2) - self.play(FadeOut(brace)) - - def mention_measurements(self): - line, square, cube, sierpinski = self.shapes_copy - - labels = list(map(TextMobject, [ - "$1/2$ length", - "$1/4$ area", - "$1/8$ volume", - "You'll see...", - ])) - for label, shape in zip(labels, self.shapes_copy): - label.next_to(shape, DOWN) - label.to_edge(DOWN, buff = MED_LARGE_BUFF) - if label is labels[-1]: - label.shift(0.1*UP) #Dumb - - self.play( - Write(label, run_time = 1), - shape[0].set_color, YELLOW - ) - self.wait() - -class BreakUpCubeIn3D(Scene): - def construct(self): - pass - -class BrokenUpCubeIn3D(Scene): - def construct(self): - pass - -class GeneralWordForMeasurement(Scene): - def construct(self): - measure = TextMobject("``Measure''") - measure.to_edge(UP) - mass = TextMobject("Mass") - mass.move_to(measure) - - words = VGroup(*list(map(TextMobject, [ - "Length", "Area", "Volume" - ]))) - words.arrange(RIGHT, buff = 2*LARGE_BUFF) - words.next_to(measure, DOWN, buff = 2*LARGE_BUFF) - colors = color_gradient([BLUE_B, BLUE_D], len(words)) - for word, color in zip(words, colors): - word.set_color(color) - lines = VGroup(*[ - Line( - measure.get_bottom(), word.get_top(), - color = word.get_color(), - buff = MED_SMALL_BUFF - ) - for word in words - ]) - - for word in words: - self.play(FadeIn(word)) - self.play(ShowCreation(lines, run_time = 2)) - self.wait() - self.play(Write(measure)) - self.wait(2) - self.play(Transform(measure, mass)) - self.wait(2) - -class ImagineShapesAsMetal(FourSelfSimilarShapes): - def construct(self): - titles = VGroup(*list(map(VGroup, self.get_titles()))) - shapes = self.get_shapes(titles) - shapes.shift(DOWN) - descriptions = VGroup(*[ - TextMobject(*words, arg_separator = "\\\\") - for shape, words in zip(shapes, [ - ["Thin", "wire"], - ["Flat", "sheet"], - ["Solid", "cube"], - ["Sierpinski", "mesh"] - ]) - ]) - for title, description in zip(titles, descriptions): - description.move_to(title, UP) - title.target = description - - self.add(titles, shapes) - for shape in shapes: - shape.generate_target() - shape.target.set_color(LIGHT_GREY) - shapes[-1].target.set_color_by_gradient(GREY, WHITE) - for shape, title in zip(shapes, titles): - self.play( - MoveToTarget(title), - MoveToTarget(shape) - ) - self.wait() - self.wait() - - for shape in shapes: - self.play( - shape.scale, 0.5, shape.get_top(), - run_time = 3, - rate_func = there_and_back - ) - self.wait() - -class ScaledLineMass(Scene): - CONFIG = { - "title" : "Line", - "mass_scaling_factor" : "\\frac{1}{2}", - "shape_width" : 2, - "break_up_factor" : 1.3, - "vert_distance" : 2, - "brace_direction" : DOWN, - "shape_to_shape_buff" : 2*LARGE_BUFF, - } - def construct(self): - title = TextMobject(self.title) - title.to_edge(UP) - scaling_factor_label = TextMobject( - "Scaling factor:", "$\\frac{1}{2}$" - ) - scaling_factor_label[1].set_color(YELLOW) - scaling_factor_label.to_edge(LEFT).shift(UP) - mass_scaling_label = TextMobject( - "Mass scaling factor:", "$%s$"%self.mass_scaling_factor - ) - mass_scaling_label[1].set_color(GREEN) - mass_scaling_label.next_to( - scaling_factor_label, DOWN, - aligned_edge = LEFT, - buff = LARGE_BUFF - ) - - shape = self.get_shape() - shape.set_width(self.shape_width) - shape.center() - shape.shift(FRAME_X_RADIUS*RIGHT/2 + self.vert_distance*UP) - - big_brace = Brace(shape, self.brace_direction) - big_brace_text = big_brace.get_text("$1$") - - shape_copy = shape.copy() - shape_copy.next_to(shape, DOWN, buff = self.shape_to_shape_buff) - shape_copy.scale_in_place(self.break_up_factor) - for submob in shape_copy: - submob.scale_in_place(1./self.break_up_factor) - - little_brace = Brace(shape_copy[0], self.brace_direction) - little_brace_text = little_brace.get_text("$\\frac{1}{2}$") - - self.add(title, scaling_factor_label, mass_scaling_label[0]) - self.play(GrowFromCenter(shape)) - self.play( - GrowFromCenter(big_brace), - Write(big_brace_text) - ) - self.wait() - - self.play( - shape.copy().replace, shape_copy[0] - ) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(shape_copy[0]) - self.play( - GrowFromCenter(little_brace), - Write(little_brace_text) - ) - self.wait() - - self.play(Write(mass_scaling_label[1], run_time = 1)) - self.wait() - self.play(FadeIn( - VGroup(*shape_copy[1:]), - lag_ratio = 0.5 - )) - self.wait() - self.play(Transform( - shape_copy.copy(), shape - )) - self.wait() - - def get_shape(self): - return VGroup( - Line(LEFT, ORIGIN), - Line(ORIGIN, RIGHT) - ).set_color(BLUE) - -class ScaledSquareMass(ScaledLineMass): - CONFIG = { - "title" : "Square", - "mass_scaling_factor" : "\\frac{1}{4} = \\left( \\frac{1}{2} \\right)^2", - "brace_direction" : LEFT, - } - def get_shape(self): - return VGroup(*[ - Square( - stroke_width = 0, - fill_color = BLUE, - fill_opacity = 0.7 - ).shift(vect) - for vect in compass_directions(start_vect = DOWN+LEFT) - ]) - -class ScaledCubeMass(ScaledLineMass): - CONFIG = { - "title" : "Cube", - "mass_scaling_factor" : "\\frac{1}{8} = \\left( \\frac{1}{2} \\right)^3", - } - def get_shape(self): - return VectorizedPoint() - -class FormCubeFromSubcubesIn3D(Scene): - def construct(self): - pass - -class ScaledSierpinskiMass(ScaledLineMass): - CONFIG = { - "title" : "Sierpinski", - "mass_scaling_factor" : "\\frac{1}{3}", - "vert_distance" : 2.5, - "shape_to_shape_buff" : 1.5*LARGE_BUFF, - } - def get_shape(self): - return Sierpinski(order = 6) - -class DefineTwoDimensional(PiCreatureScene): - CONFIG = { - "dimension" : "2", - "length_color" : GREEN, - "dimension_color" : YELLOW, - "shape_width" : 2, - "scale_factor" : 0.5, - "bottom_shape_buff" : MED_SMALL_BUFF, - "scalar" : "s", - } - def construct(self): - self.add_title() - self.add_h_line() - self.add_shape() - self.add_width_mass_labels() - self.show_top_length() - self.change_mode("thinking") - self.perform_scaling() - self.show_dimension() - - def add_title(self): - title = TextMobject( - self.dimension, "-dimensional", - arg_separator = "" - ) - self.dimension_in_title = title[0] - self.dimension_in_title.set_color(self.dimension_color) - title.to_edge(UP) - self.add(title) - - self.title = title - - def add_h_line(self): - self.h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - self.add(self.h_line) - - def add_shape(self): - shape = self.get_shape() - shape.set_width(self.shape_width) - shape.next_to(self.title, DOWN, buff = MED_LARGE_BUFF) - # self.shape.shift(FRAME_Y_RADIUS*UP/2) - self.mass_color = shape.get_color() - self.add(shape) - - self.shape = shape - - def add_width_mass_labels(self): - top_length = TextMobject("Length:", "$L$") - top_mass = TextMobject("Mass:", "$M$") - bottom_length = TextMobject( - "Length: ", "$%s$"%self.scalar, "$L$", - arg_separator = "" - ) - bottom_mass = TextMobject( - "Mass: ", - "$%s^%s$"%(self.scalar, self.dimension), - "$M$", - arg_separator = "" - ) - self.dimension_in_exp = VGroup( - *bottom_mass[1][-len(self.dimension):] - ) - self.dimension_in_exp.set_color(self.dimension_color) - - top_group = VGroup(top_length, top_mass) - bottom_group = VGroup(bottom_length, bottom_mass) - for group in top_group, bottom_group: - group.arrange( - DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - group[0][-1].set_color(self.length_color) - group[1][-1].set_color(self.mass_color) - - top_group.next_to(self.h_line, UP, buff = LARGE_BUFF) - bottom_group.next_to(self.h_line, DOWN, buff = LARGE_BUFF) - for group in top_group, bottom_group: - group.to_edge(LEFT) - - self.add(top_group, bottom_group) - - self.top_L = top_length[-1] - self.bottom_L = VGroup(*bottom_length[-2:]) - self.bottom_mass = bottom_mass - - def show_top_length(self): - brace = Brace(self.shape, LEFT) - top_L = self.top_L.copy() - - self.play(GrowFromCenter(brace)) - self.play(top_L.next_to, brace, LEFT) - self.wait() - - self.brace = brace - - def perform_scaling(self): - group = VGroup(self.shape, self.brace).copy() - self.play( - group.shift, - (group.get_top()[1]+self.bottom_shape_buff)*DOWN - ) - - shape, brace = group - bottom_L = self.bottom_L.copy() - shape.generate_target() - shape.target.scale_in_place( - self.scale_factor, - ) - brace.target = Brace(shape.target, LEFT) - self.play(*list(map(MoveToTarget, group))) - self.play(bottom_L.next_to, brace, LEFT) - self.wait() - - def show_dimension(self): - top_dimension = self.dimension_in_title.copy() - self.play(self.pi_creature.look_at, top_dimension) - self.play(Transform( - top_dimension, - self.dimension_in_exp, - run_time = 2, - )) - self.wait(3) - - def get_shape(self): - return Square( - stroke_width = 0, - fill_color = BLUE, - fill_opacity = 0.7, - ) - -class DefineThreeDimensional(DefineTwoDimensional): - CONFIG = { - "dimension" : "3", - } - def get_shape(self): - return Square( - stroke_width = 0, - fill_opacity = 0 - ) - -class DefineSierpinskiDimension(DefineTwoDimensional): - CONFIG = { - "dimension" : "D", - "scalar" : "\\left( \\frac{1}{2} \\right)", - "sierpinski_order" : 6, - "equation_scale_factor" : 1.3, - } - def construct(self): - DefineTwoDimensional.construct(self) - self.change_mode("confused") - self.wait() - self.add_one_third() - self.isolate_equation() - - def add_one_third(self): - equation = TextMobject( - "$= \\left(\\frac{1}{3}\\right)$", "$M$", - arg_separator = "" - ) - equation.set_color_by_tex("$M$", self.mass_color) - equation.next_to(self.bottom_mass) - - self.play(Write(equation)) - self.change_mode("pondering") - self.wait() - - self.equation = VGroup(self.bottom_mass, equation) - self.distilled_equation = VGroup( - self.bottom_mass[1], - equation[0] - ).copy() - - def isolate_equation(self): - # everything = VGroup(*self.get_mobjects()) - keepers = [self.pi_creature, self.equation] - for mob in keepers: - mob.save_state() - keepers_copies = [mob.copy() for mob in keepers] - self.play( - *[ - ApplyMethod(mob.fade, 0.5) - for mob in self.get_mobjects() - ] + [ - Animation(mob) - for mob in keepers_copies - ] - ) - self.remove(*keepers_copies) - for mob in keepers: - ApplyMethod(mob.restore).update(1) - self.add(*keepers) - self.play( - self.pi_creature.change_mode, "confused", - self.pi_creature.look_at, self.equation - ) - self.wait() - - equation = self.distilled_equation - self.play( - equation.arrange, RIGHT, - equation.scale, self.equation_scale_factor, - equation.to_corner, UP+RIGHT, - run_time = 2 - ) - self.wait(2) - - simpler_equation = TexMobject("2^D = 3") - simpler_equation[1].set_color(self.dimension_color) - simpler_equation.scale(self.equation_scale_factor) - simpler_equation.next_to(equation, DOWN, buff = MED_LARGE_BUFF) - - log_expression = TexMobject("\\log_2(3) \\approx", "1.585") - log_expression[-1].set_color(self.dimension_color) - log_expression.scale(self.equation_scale_factor) - log_expression.next_to(simpler_equation, DOWN, buff = MED_LARGE_BUFF) - log_expression.shift_onto_screen() - - self.play(Write(simpler_equation)) - self.change_mode("pondering") - self.wait(2) - self.play(Write(log_expression)) - self.play( - self.pi_creature.change_mode, "hooray", - self.pi_creature.look_at, log_expression - ) - self.wait(2) - - def get_shape(self): - return Sierpinski( - order = self.sierpinski_order, - color = RED, - ) - -class ShowSierpinskiCurve(Scene): - CONFIG = { - "max_order" : 8, - } - def construct(self): - curve = self.get_curve(2) - self.play(ShowCreation(curve, run_time = 2)) - for order in range(3, self.max_order + 1): - self.play(Transform( - curve, self.get_curve(order), - run_time = 2 - )) - self.wait() - - def get_curve(self, order): - curve = SierpinskiCurve(order = order, monochromatic = True) - curve.set_color(RED) - return curve - -class LengthAndAreaOfSierpinski(ShowSierpinskiCurve): - CONFIG = { - "curve_start_order" : 5, - "sierpinski_start_order" : 4, - "n_iterations" : 3, - } - def construct(self): - length = TextMobject("Length = $\\infty$") - length.shift(FRAME_X_RADIUS*LEFT/2).to_edge(UP) - area = TextMobject("Area = $0$") - area.shift(FRAME_X_RADIUS*RIGHT/2).to_edge(UP) - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - self.add(length, area, v_line) - - curve = self.get_curve(order = self.curve_start_order) - sierp = self.get_sierpinski(order = self.sierpinski_start_order) - self.add(curve, sierp) - self.wait() - - for x in range(self.n_iterations): - new_curve = self.get_curve(order = self.curve_start_order+x+1) - alpha = (x+1.0)/self.n_iterations - stroke_width = interpolate(3, 1, alpha) - new_curve.set_stroke(width = stroke_width) - - new_sierp = self.get_sierpinski( - order = self.sierpinski_start_order+x+1 - ) - self.play( - Transform(curve, new_curve), - Transform(sierp, new_sierp), - run_time = 2 - ) - self.play(sierp.set_fill, None, 0) - self.wait() - - def get_curve(self, order): - # curve = ShowSierpinskiCurve.get_curve(self, order) - curve = SierpinskiCurve(order = order) - curve.set_height(4).center() - curve.shift(FRAME_X_RADIUS*LEFT/2) - return curve - - def get_sierpinski(self, order): - result = Sierpinski(order = order) - result.shift(FRAME_X_RADIUS*RIGHT/2) - return result - -class FractionalAnalogOfLengthAndArea(Scene): - def construct(self): - last_sc = LengthAndAreaOfSierpinski(skip_animations = True) - self.add(*last_sc.get_mobjects()) - - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - self.play(FadeIn(morty)) - self.play(PiCreatureSays( - morty, - """ - Better described with a - 1.585-dimensional measure. - """ - )) - self.play(Blink(morty)) - self.wait() - -class DimensionOfKoch(Scene): - CONFIG = { - "scaling_factor_color" : YELLOW, - "mass_scaling_color" : BLUE, - "dimension_color" : GREEN_A, - "curve_class" : KochCurve, - "scaling_factor" : 3, - "mass_scaling_factor" : 4, - "num_subparts" : 4, - "koch_curve_order" : 5, - "koch_curve_width" : 5, - "break_up_factor" : 1.5, - "down_shift" : 3*DOWN, - "dimension_rhs" : "\\approx 1.262", - } - def construct(self): - self.add_labels() - self.add_curve() - self.break_up_curve() - self.compare_sizes() - self.show_dimension() - - def add_labels(self): - scaling_factor = TextMobject( - "Scaling factor:", - "$\\frac{1}{%d}$"%self.scaling_factor, - ) - scaling_factor.next_to(ORIGIN, UP) - scaling_factor.to_edge(LEFT) - scaling_factor[1].set_color(self.scaling_factor_color) - self.add(scaling_factor[0]) - - mass_scaling = TextMobject( - "Mass scaling factor:", - "$\\frac{1}{%d}$"%self.mass_scaling_factor - ) - mass_scaling.next_to(ORIGIN, DOWN) - mass_scaling.to_edge(LEFT) - mass_scaling[1].set_color(self.mass_scaling_color) - self.add(mass_scaling[0]) - - self.scaling_factor_mob = scaling_factor[1] - self.mass_scaling_factor_mob = mass_scaling[1] - - def add_curve(self): - curve = self.curve_class(order = self.koch_curve_order) - curve.set_width(self.koch_curve_width) - curve.to_corner(UP+RIGHT, LARGE_BUFF) - - self.play(ShowCreation(curve, run_time = 2)) - self.wait() - self.curve = curve - - def break_up_curve(self): - curve_copy = self.curve.copy() - length = len(curve_copy) - n_parts = self.num_subparts - broken_curve = VGroup(*[ - VGroup(*curve_copy[i*length/n_parts:(i+1)*length/n_parts]) - for i in range(n_parts) - ]) - self.play(broken_curve.shift, self.down_shift) - - broken_curve.generate_target() - break_up(broken_curve.target, self.break_up_factor) - broken_curve.target.shift_onto_screen - self.play(MoveToTarget(broken_curve)) - self.wait() - - self.add(broken_curve) - self.broken_curve = broken_curve - - def compare_sizes(self): - big_brace = Brace(self.curve, DOWN) - one = big_brace.get_text("$1$") - little_brace = Brace(self.broken_curve[0], DOWN) - one_third = little_brace.get_text("1/%d"%self.scaling_factor) - one_third.set_color(self.scaling_factor_color) - - self.play( - GrowFromCenter(big_brace), - GrowFromCenter(little_brace), - Write(one), - Write(one_third), - ) - self.wait() - self.play(Write(self.scaling_factor_mob)) - self.wait() - self.play(Write(self.mass_scaling_factor_mob)) - self.wait() - - def show_dimension(self): - raw_formula = TexMobject(""" - \\left( \\frac{1}{%s} \\right)^D - = - \\left( \\frac{1}{%s} \\right) - """%(self.scaling_factor, self.mass_scaling_factor)) - formula = VGroup( - VGroup(*raw_formula[:5]), - VGroup(raw_formula[5]), - VGroup(raw_formula[6]), - VGroup(*raw_formula[7:]), - ) - formula.to_corner(UP+LEFT) - - simpler_formula = TexMobject( - str(self.scaling_factor), - "^D", "=", - str(self.mass_scaling_factor) - ) - simpler_formula.move_to(formula, UP) - - for mob in formula, simpler_formula: - mob[0].set_color(self.scaling_factor_color) - mob[1].set_color(self.dimension_color) - mob[3].set_color(self.mass_scaling_color) - - log_expression = TexMobject( - "D = \\log_%d(%d) %s"%( - self.scaling_factor, - self.mass_scaling_factor, - self.dimension_rhs - ) - ) - log_expression[0].set_color(self.dimension_color) - log_expression[5].set_color(self.scaling_factor_color) - log_expression[7].set_color(self.mass_scaling_color) - log_expression.next_to( - simpler_formula, DOWN, - aligned_edge = LEFT, - buff = MED_LARGE_BUFF - ) - - third = self.scaling_factor_mob.copy() - fourth = self.mass_scaling_factor_mob.copy() - for mob in third, fourth: - mob.add(VectorizedPoint(mob.get_right())) - mob.add_to_back(VectorizedPoint(mob.get_left())) - - - - self.play( - Transform(third, formula[0]), - Transform(fourth, formula[-1]), - ) - self.play(*list(map(FadeIn, formula[1:-1]))) - self.remove(third, fourth) - self.add(formula) - self.wait(2) - self.play(Transform(formula, simpler_formula)) - self.wait(2) - self.play(Write(log_expression)) - self.wait(2) - -class DimensionOfQuadraticKoch(DimensionOfKoch): - CONFIG = { - "curve_class" : QuadraticKoch, - "scaling_factor" : 4, - "mass_scaling_factor" : 8, - "num_subparts" : 8, - "koch_curve_order" : 4, - "koch_curve_width" : 4, - "break_up_factor" : 1.7, - "down_shift" : 4*DOWN, - "dimension_rhs" : "= \\frac{3}{2} = 1.5", - } - def construct(self): - self.add_labels() - self.add_curve() - self.set_color_curve_subparts() - self.show_dimension() - - def get_curve(self, order): - curve = self.curve_class( - order = order, - monochromatic = True - ) - curve.set_width(self.koch_curve_width) - alpha = float(order) / self.koch_curve_order - stroke_width = interpolate(3, 1, alpha) - curve.set_stroke(width = stroke_width) - return curve - - def add_curve(self): - seed_label = TextMobject("Seed") - seed_label.shift(FRAME_X_RADIUS*RIGHT/2).to_edge(UP) - seed = self.get_curve(order = 1) - seed.next_to(seed_label, DOWN) - - curve = seed.copy() - - resulting_fractal = TextMobject("Resulting fractal") - resulting_fractal.shift(FRAME_X_RADIUS*RIGHT/2) - - self.add(seed_label, seed) - self.wait() - self.play( - curve.next_to, resulting_fractal, DOWN, MED_LARGE_BUFF, - Write(resulting_fractal, run_time = 1) - ) - for order in range(2, self.koch_curve_order+1): - new_curve = self.get_curve(order) - new_curve.move_to(curve) - n_curve_parts = curve.get_num_curves() - curve.insert_n_curves(6 * n_curve_parts) - curve.make_jagged() - self.play(Transform(curve, new_curve, run_time = 2)) - self.wait() - - self.curve = curve - - def set_color_curve_subparts(self): - n_parts = self.num_subparts - colored_curve = self.curve_class( - order = self.koch_curve_order, - stroke_width = 1 - ) - colored_curve.replace(self.curve) - length = len(colored_curve) - broken_curve = VGroup(*[ - VGroup(*colored_curve[i*length/n_parts:(i+1)*length/n_parts]) - for i in range(n_parts) - ]) - colors = it.cycle([WHITE, RED]) - for subpart, color in zip(broken_curve, colors): - subpart.set_color(color) - self.play( - FadeOut(self.curve), - FadeIn(colored_curve) - ) - self.play( - ApplyFunction( - lambda m : break_up(m, self.break_up_factor), - broken_curve, - rate_func = there_and_back, - run_time = 2 - ) - ) - self.wait() - self.play(Write(self.scaling_factor_mob)) - self.play(Write(self.mass_scaling_factor_mob)) - self.wait(2) - -class ThisIsSelfSimilarityDimension(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - This is called - ``self-similarity dimension'' - """) - self.change_student_modes(*["pondering"]*3) - self.wait(2) - -class ShowSeveralSelfSimilarityDimensions(Scene): - def construct(self): - vects = [ - 4*LEFT, - ORIGIN, - 4*RIGHT, - ] - fractal_classes = [ - PentagonalFractal, - QuadraticKoch, - DiamondFractal, - ] - max_orders = [ - 4, - 4, - 5, - ] - dimensions = [ - 1.668, - 1.500, - 1.843, - ] - - title = TextMobject("``Self-similarity dimension''") - title.to_edge(UP) - title.set_color(YELLOW) - self.add(title) - - - def get_curves(order): - curves = VGroup() - for Class, vect in zip(fractal_classes, vects): - curve = Class(order = order) - curve.set_width(2), - curve.shift(vect) - curves.add(curve) - return curves - curves = get_curves(1) - self.add(curves) - - for curve, dimension, u in zip(curves, dimensions, [1, -1, 1]): - label = TextMobject("%.3f-dimensional"%dimension) - label.scale(0.85) - label.next_to(curve, u*UP, buff = LARGE_BUFF) - self.add(label) - - self.wait() - - for order in range(2, max(max_orders)+1): - anims = [] - for curve, max_order in zip(curves, max_orders): - if order <= max_order: - new_curve = curve.__class__(order = order) - new_curve.replace(curve) - anims.append(Transform(curve, new_curve)) - self.play(*anims, run_time = 2) - self.wait() - self.curves = curves - -class SeparateFractals(Scene): - def construct(self): - last_sc = ShowSeveralSelfSimilarityDimensions(skip_animations = True) - self.add(*last_sc.get_mobjects()) - quad_koch = last_sc.curves[1] - length = len(quad_koch) - new_quad_koch = VGroup(*[ - VGroup(*quad_koch[i*length/8:(i+1)*length/8]) - for i in range(8) - ]) - curves = list(last_sc.curves) - curves[1] = new_quad_koch - curves = VGroup(*curves) - curves.save_state() - self.play(*[ - ApplyFunction( - lambda m : break_up(m, 2), - curve - ) - for curve in curves - ]) - self.wait(2) - self.play(curves.restore) - self.wait() - -class ShowDiskScaling(Scene): - def construct(self): - self.show_non_self_similar_shapes() - self.isolate_disk() - self.scale_disk() - self.write_mass_scaling_factor() - self.try_fitting_small_disks() - - def show_non_self_similar_shapes(self): - title = TextMobject( - "Most shapes are not self-similar" - ) - title.to_edge(UP) - self.add(title) - - hexagon = RegularPolygon(n = 6) - disk = Circle() - blob = VMobject().set_points_smoothly([ - RIGHT, RIGHT+UP, ORIGIN, RIGHT+DOWN, LEFT, UP, RIGHT - ]) - britain = Britain() - shapes = VGroup(hexagon, blob, disk, britain) - for shape in shapes: - shape.set_width(1.5) - shape.set_stroke(width = 0) - shape.set_fill(opacity = 1) - shapes.set_color_by_gradient(BLUE_B, BLUE_E) - shapes.arrange(RIGHT, buff = LARGE_BUFF) - shapes.next_to(title, DOWN) - for shape in shapes: - self.play(FadeIn(shape)) - self.wait(2) - - self.disk = disk - self.to_fade = VGroup( - title, hexagon, blob, britain - ) - - def isolate_disk(self): - disk = self.disk - self.play( - FadeOut(self.to_fade), - disk.set_width, 2, - disk.next_to, ORIGIN, LEFT, 2, - disk.set_fill, BLUE_D, 0.7 - ) - - radius = Line( - disk.get_center(), disk.get_right(), - color = YELLOW - ) - one = TexMobject("1").next_to(radius, DOWN, SMALL_BUFF) - - self.play(ShowCreation(radius)) - self.play(Write(one)) - self.wait() - - self.disk.add(radius, one) - - def scale_disk(self): - scaled_disk = self.disk.copy() - scaled_disk.generate_target() - scaled_disk.target.scale(2) - scaled_disk.target.next_to(ORIGIN, RIGHT) - - one = scaled_disk.target[-1] - two = TexMobject("2") - two.move_to(one, UP) - scaled_disk.target.submobjects[-1] = two - - self.play(MoveToTarget(scaled_disk)) - self.wait() - - self.scaled_disk = scaled_disk - - def write_mass_scaling_factor(self): - mass_scaling = TextMobject( - "Mass scaling factor: $2^2 = 4$" - ) - mass_scaling.next_to(self.scaled_disk, UP) - mass_scaling.to_edge(UP) - self.play(Write(mass_scaling)) - self.wait() - - def try_fitting_small_disks(self): - disk = self.disk.copy() - disk.submobjects = [] - disk.set_fill(opacity = 0.5) - foursome = VGroup(*[ - disk.copy().next_to(ORIGIN, vect, buff = 0) - for vect in compass_directions(start_vect = UP+RIGHT) - ]) - foursome.move_to(self.scaled_disk) - - self.play(Transform(disk, foursome)) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(foursome) - self.wait() - self.play(ApplyFunction( - lambda m : break_up(m, 0.2), - foursome, - rate_func = there_and_back, - run_time = 4, - )) - self.wait() - self.play(FadeOut(foursome)) - self.wait() - -class WhatDoYouMeanByMass(TeacherStudentsScene): - def construct(self): - self.student_says( - "What do you mean \\\\ by mass?", - target_mode = "sassy" - ) - self.change_student_modes("pondering", "sassy", "confused") - self.wait() - self.play(self.get_teacher().change_mode, "thinking") - self.wait(2) - self.teacher_thinks("") - self.zoom_in_on_thought_bubble() - -class BoxCountingScene(Scene): - CONFIG = { - "box_width" : 0.25, - "box_color" : YELLOW, - "box_opacity" : 0.5, - "num_boundary_check_points" : 200, - "corner_rect_left_extension" : 0, - } - def setup(self): - self.num_rows = 2*int(FRAME_Y_RADIUS/self.box_width)+1 - self.num_cols = 2*int(FRAME_X_RADIUS/self.box_width)+1 - - def get_grid(self): - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - v_lines = VGroup(*[ - v_line.copy().shift(u*x*self.box_width*RIGHT) - for x in range(self.num_cols/2+1) - for u in [-1, 1] - ]) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_lines = VGroup(*[ - h_line.copy().shift(u*y*self.box_width*UP) - for y in range(self.num_rows/2+1) - for u in [-1, 1] - ]) - - grid = VGroup(v_lines, h_lines) - if self.box_width > 0.2: - grid.set_stroke(width = 1) - else: - grid.set_stroke(width = 0.5) - return grid - - def get_highlighted_boxes(self, vmobject): - points = [] - if vmobject.stroke_width > 0: - for submob in vmobject.family_members_with_points(): - alphas = np.linspace(0, 1, self.num_boundary_check_points) - points += [ - submob.point_from_proportion(alpha) - for alpha in alphas - ] - if vmobject.fill_opacity > 0: - camera = Camera(**LOW_QUALITY_CAMERA_CONFIG) - camera.capture_mobject(vmobject) - box_centers = self.get_box_centers() - pixel_coords = camera.points_to_pixel_coords(box_centers) - for index, (x, y) in enumerate(pixel_coords): - try: - rgb = camera.pixel_array[y, x] - if not np.all(rgb == np.zeros(3)): - points.append(box_centers[index]) - except: - pass - return self.get_boxes(points) - - def get_box_centers(self): - bottom_left = reduce(op.add, [ - self.box_width*(self.num_cols/2)*LEFT, - self.box_width*(self.num_rows/2)*DOWN, - self.box_width*RIGHT/2, - self.box_width*UP/2, - ]) - return np.array([ - bottom_left + (x*RIGHT+y*UP)*self.box_width - for x in range(self.num_cols) - for y in range(self.num_rows) - ]) - - def get_boxes(self, points): - points = np.array(points) - rounded_points = np.floor(points/self.box_width)*self.box_width - unique_rounded_points = np.vstack({ - tuple(row) for - row in rounded_points - }) - - return VGroup(*[ - Square( - side_length = self.box_width, - stroke_width = 0, - fill_color = self.box_color, - fill_opacity = self.box_opacity, - ).move_to(point, DOWN+LEFT) - for point in unique_rounded_points - ]) - - def get_corner_rect(self): - rect = Rectangle( - height = FRAME_Y_RADIUS/2, - width = FRAME_X_RADIUS+self.corner_rect_left_extension, - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 0.8 - ) - rect.to_corner(UP+RIGHT, buff = 0) - return rect - - def get_counting_label(self): - label = TextMobject("Boxes touched:") - label.next_to(ORIGIN, RIGHT) - label.to_edge(UP) - label.shift(self.corner_rect_left_extension*LEFT) - self.counting_num_reference = label[-1] - rect = BackgroundRectangle(label) - rect.stretch(1.3, 0) - rect.move_to(label, LEFT) - label.add_to_back(rect) - return label - - def count_boxes(self, boxes): - num = DecimalNumber(len(boxes), num_decimal_places = 0) - num.next_to(boxes, RIGHT) - num.add_to_back(BackgroundRectangle(num)) - - self.play(ShowCreation(boxes, run_time = 3)) - self.play(Write(num)) - self.play( - num.next_to, self.counting_num_reference, RIGHT, MED_SMALL_BUFF, DOWN, - num.set_color, YELLOW - ) - return num - -class BoxCountingWithDisk(BoxCountingScene): - CONFIG = { - "box_width" : 0.25, - "num_boundary_check_points" : 200, - "corner_rect_left_extension" : 2, - "disk_opacity" : 0.5, - "disk_stroke_width" : 0.5, - "decimal_string" : "= %.2f", - } - def construct(self): - disk = Circle(radius = 1) - disk.set_fill(BLUE, opacity = self.disk_opacity) - disk.set_stroke(BLUE, width = self.disk_stroke_width) - disk.shift(0.1*np.sqrt(2)*(UP+RIGHT)) - - radius = Line(disk.get_center(), disk.get_right()) - disk.add(radius) - one = TexMobject("1").next_to(radius, DOWN, SMALL_BUFF) - - boxes = self.get_highlighted_boxes(disk) - small_box_num = len(boxes) - grid = self.get_grid() - corner_rect = self.get_corner_rect() - counting_label = self.get_counting_label() - - prop_words = TextMobject("Proportional to", "$\\pi r^2$") - prop_words[1].set_color(BLUE) - prop_words.next_to(counting_label, DOWN, aligned_edge = LEFT) - - self.add(disk, one) - self.play( - ShowCreation(grid), - Animation(disk), - ) - self.wait() - self.play( - FadeIn(corner_rect), - FadeIn(counting_label) - ) - counting_mob = self.count_boxes(boxes) - self.wait() - self.play(Write(prop_words, run_time = 2)) - self.wait(2) - self.play(FadeOut(prop_words)) - - - disk.generate_target() - disk.target.scale(2, about_point = disk.get_top()) - two = TexMobject("2").next_to(disk.target[1], DOWN, SMALL_BUFF) - self.play( - MoveToTarget(disk), - Transform(one, two), - FadeOut(boxes), - ) - self.play(counting_mob.next_to, counting_mob, DOWN) - boxes = self.get_highlighted_boxes(disk) - large_box_count = len(boxes) - new_counting_mob = self.count_boxes(boxes) - self.wait() - - frac_line = TexMobject("-") - frac_line.set_color(YELLOW) - frac_line.stretch_to_fit_width(new_counting_mob.get_width()) - frac_line.next_to(new_counting_mob, DOWN, buff = SMALL_BUFF) - decimal = TexMobject(self.decimal_string%(float(large_box_count)/small_box_num)) - decimal.next_to(frac_line, RIGHT) - approx = TexMobject("\\approx 2^2") - approx.next_to(decimal, RIGHT, aligned_edge = DOWN) - approx.shift_onto_screen() - self.play(*list(map(Write, [frac_line, decimal]))) - self.play(Write(approx)) - self.wait() - - randy = Randolph().shift(3*RIGHT).to_edge(DOWN) - self.play(FadeIn(randy)) - self.play(PiCreatureSays( - randy, "Is it?", - target_mode = "sassy", - bubble_kwargs = {"direction" : LEFT} - )) - self.play(Blink(randy)) - self.wait() - -class FinerBoxCountingWithDisk(BoxCountingWithDisk): - CONFIG = { - "box_width" : 0.03, - "num_boundary_check_points" : 1000, - "disk_stroke_width" : 0.5, - "decimal_string" : "= %.2f", - } - -class PlotDiskBoxCounting(GraphScene): - CONFIG = { - "x_axis_label" : "Scaling factor", - "y_axis_label" : "Number of boxes \\\\ touched", - "x_labeled_nums" : [], - "y_labeled_nums" : [], - "x_min" : 0, - "y_min" : 0, - "y_max" : 30, - "func" : lambda x : 0.5*x**2, - "func_label" : "f(x) = cx^2", - } - def construct(self): - self.plot_points() - self.describe_better_fit() - - def plot_points(self): - self.setup_axes() - self.graph_function(self.func) - self.remove(self.graph) - - data_points = [ - self.input_to_graph_point(x) + ((random.random()-0.5)/x)*UP - for x in np.arange(2, 10, 0.5) - ] - data_dots = VGroup(*[ - Dot(point, radius = 0.05, color = YELLOW) - for point in data_points - ]) - - self.play(ShowCreation(data_dots)) - self.wait() - self.play(ShowCreation(self.graph)) - self.label_graph( - self.graph, - self.func_label, - direction = RIGHT+DOWN, - buff = SMALL_BUFF, - color = WHITE, - ) - self.wait() - - def describe_better_fit(self): - words = TextMobject("Better fit at \\\\ higher inputs") - arrow = Arrow(2*LEFT, 2*RIGHT) - arrow.next_to(self.x_axis_label_mob, UP) - arrow.shift(2*LEFT) - words.next_to(arrow, UP) - - self.play(ShowCreation(arrow)) - self.play(Write(words)) - self.wait(2) - -class FineGridSameAsLargeScaling(BoxCountingScene): - CONFIG = { - "box_width" : 0.25/6, - "scale_factor" : 6 - } - def construct(self): - disk = Circle(radius = 1) - disk.set_fill(BLUE, opacity = 0.5) - disk.set_stroke(BLUE, width = 1) - - grid = self.get_grid() - grid.scale(self.scale_factor) - - self.add(grid, disk) - self.wait() - self.play(disk.scale, self.scale_factor) - self.wait() - self.play( - grid.scale, 1./self.scale_factor, - disk.scale, 1./self.scale_factor, - disk.set_stroke, None, 0.5, - ) - self.wait() - boxes = self.get_highlighted_boxes(disk) - self.play(ShowCreation(boxes, run_time = 3)) - self.wait(2) - -class BoxCountingSierpinski(BoxCountingScene): - CONFIG = { - "box_width" : 0.1, - "sierpinski_order" : 7, - "sierpinski_width" : 3, - "num_boundary_check_points" : 6, - "corner_rect_left_extension" : 2, - } - def construct(self): - self.add(self.get_grid()) - sierp = Sierpinski(order = self.sierpinski_order) - sierp.set_fill(opacity = 0) - sierp.move_to(3*DOWN, DOWN+RIGHT) - sierp.set_width(self.sierpinski_width) - boxes = self.get_highlighted_boxes(sierp) - - corner_rect = self.get_corner_rect() - counting_label = self.get_counting_label() - - self.play(ShowCreation(sierp)) - self.play(*list(map(FadeIn, [corner_rect, counting_label]))) - self.wait() - counting_mob = self.count_boxes(boxes) - self.wait() - self.play( - FadeOut(boxes), - sierp.scale, 2, sierp.get_corner(DOWN+RIGHT), - ) - self.play(counting_mob.next_to, counting_mob, DOWN) - boxes = self.get_highlighted_boxes(sierp) - new_counting_mob = self.count_boxes(boxes) - self.wait() - - frac_line = TexMobject("-") - frac_line.set_color(YELLOW) - frac_line.stretch_to_fit_width(new_counting_mob.get_width()) - frac_line.next_to(new_counting_mob, DOWN, buff = SMALL_BUFF) - approx_three = TexMobject("\\approx 3") - approx_three.next_to(frac_line, RIGHT) - equals_exp = TexMobject("= 2^{1.585...}") - equals_exp.next_to(approx_three, RIGHT, aligned_edge = DOWN) - equals_exp.shift_onto_screen() - - self.play(*list(map(Write, [frac_line, approx_three]))) - self.wait() - self.play(Write(equals_exp)) - self.wait() - -class PlotSierpinskiBoxCounting(PlotDiskBoxCounting): - CONFIG = { - "func" : lambda x : 0.5*x**1.585, - "func_label" : "f(x) = cx^{1.585}", - } - def construct(self): - self.plot_points() - -class BoxCountingWithBritain(BoxCountingScene): - CONFIG = { - "box_width" : 0.1, - "num_boundary_check_points" : 5000, - "corner_rect_left_extension" : 1, - } - def construct(self): - self.show_box_counting() - self.show_formula() - - def show_box_counting(self): - self.add(self.get_grid()) - britain = Britain( - stroke_width = 2, - fill_opacity = 0 - ) - britain = fractalify(britain, order = 1, dimension = 1.21) - britain.shift(DOWN+LEFT) - boxes = self.get_highlighted_boxes(britain) - - self.play(ShowCreation(britain, run_time = 3)) - self.wait() - self.play(ShowCreation(boxes, run_time = 3)) - self.wait() - self.play(FadeOut(boxes)) - self.play(britain.scale, 2.5, britain.get_corner(DOWN+RIGHT)) - boxes = self.get_highlighted_boxes(britain) - self.play(ShowCreation(boxes, run_time = 2)) - self.wait() - - def show_formula(self): - corner_rect = self.get_corner_rect() - equation = TextMobject(""" - Number of boxes $\\approx$ - \\quad $c(\\text{scaling factor})^{1.21}$ - """) - equation.next_to( - corner_rect.get_corner(UP+LEFT), DOWN+RIGHT - ) - - N = equation[0].copy() - word_len = len("Numberofboxes") - approx = equation[word_len].copy() - c = equation[word_len+1].copy() - s = equation[word_len+3].copy() - dim = VGroup(*equation[-len("1.21"):]).copy() - - N.set_color(YELLOW) - s.set_color(BLUE) - dim.set_color(GREEN) - - simpler_eq = VGroup(N, approx, c, s, dim) - simpler_eq.generate_target() - simpler_eq.target.arrange(buff = SMALL_BUFF) - simpler_eq.target.move_to(N, LEFT) - simpler_eq.target[-1].next_to( - simpler_eq.target[-2].get_corner(UP+RIGHT), - RIGHT, - buff = SMALL_BUFF - ) - - self.play( - FadeIn(corner_rect), - Write(equation) - ) - self.wait(2) - self.play(FadeIn(simpler_eq)) - self.wait() - self.play( - FadeOut(equation), - Animation(simpler_eq) - ) - self.play(MoveToTarget(simpler_eq)) - self.wait(2) - - log_expression1 = TexMobject( - "\\log(", "N", ")", "=", - "\\log(", "c", "s", "^{1.21}", ")" - ) - log_expression2 = TexMobject( - "\\log(", "N", ")", "=", - "\\log(", "c", ")", "+", - "1.21", "\\log(", "s", ")" - ) - for log_expression in log_expression1, log_expression2: - log_expression.next_to(simpler_eq, DOWN, aligned_edge = LEFT) - log_expression.set_color_by_tex("N", N.get_color()) - log_expression.set_color_by_tex("s", s.get_color()) - log_expression.set_color_by_tex("^{1.21}", dim.get_color()) - log_expression.set_color_by_tex("1.21", dim.get_color()) - rewired_log_expression1 = VGroup(*[ - log_expression1[index].copy() - for index in [ - 0, 1, 2, 3, #match with log_expression2 - 4, 5, 8, 8, - 7, 4, 6, 8 - ] - ]) - - self.play(Write(log_expression1)) - self.remove(log_expression1) - self.add(rewired_log_expression1) - self.wait() - self.play(Transform( - rewired_log_expression1, - log_expression2, - run_time = 2 - )) - self.wait(2) - - self.final_expression = VGroup( - simpler_eq, rewired_log_expression1 - ) - -class GiveShapeAndPonder(Scene): - def construct(self): - morty = Mortimer() - randy = Randolph() - morty.next_to(ORIGIN, DOWN).shift(3*RIGHT) - randy.next_to(ORIGIN, DOWN).shift(3*LEFT) - - norway = Norway(fill_opacity = 0, stroke_width = 1) - norway.set_width(2) - norway.next_to(morty, UP+LEFT, buff = -MED_SMALL_BUFF) - - self.play( - morty.change_mode, "raise_right_hand", - morty.look_at, norway, - randy.look_at, norway, - ShowCreation(norway) - ) - self.play(Blink(morty)) - self.play(randy.change_mode, "pondering") - self.play(Blink(randy)) - self.wait() - -class CheapBoxCountingWithBritain(BoxCountingWithBritain): - CONFIG = { - "skip_animations" : True, - } - def construct(self): - self.show_formula() - -class ConfusedAtParabolicData(PlotDiskBoxCounting): - CONFIG = { - "func" : lambda x : 0.5*x**1.6, - "func_label" : "f(x) = cx^{1.21}", - } - - def construct(self): - self.plot_points() - randy = Randolph() - randy.to_corner(DOWN+LEFT) - randy.shift(RIGHT) - - self.play(FadeIn(randy)) - self.play(randy.change_mode, "confused") - self.play(randy.look_at, self.x_axis_label_mob) - self.play(Blink(randy)) - self.wait(2) - -class IntroduceLogLogPlot(GraphScene): - CONFIG = { - "x_axis_label" : "\\log(s)", - "y_axis_label" : "\\log(N)", - "x_labeled_nums" : [], - "y_labeled_nums" : [], - "graph_origin" : 2.5*DOWN+6*LEFT, - "dimension" : 1.21, - "y_intercept" : 2, - "x_max" : 16, - } - def construct(self): - last_scene = CheapBoxCountingWithBritain() - expression = last_scene.final_expression - box = Rectangle( - stroke_color = WHITE, - fill_color = BLACK, - fill_opacity = 0.7, - ) - box.replace(expression, stretch = True) - box.scale_in_place(1.2) - expression.add_to_back(box) - self.add(expression) - - self.setup_axes(animate = False) - self.x_axis_label_mob[-2].set_color(BLUE) - self.y_axis_label_mob[-2].set_color(YELLOW) - graph = self.graph_function( - lambda x : self.y_intercept+self.dimension*x - ) - self.remove(graph) - p1 = self.input_to_graph_point(2) - p2 = self.input_to_graph_point(3) - interim_point = p2[0]*RIGHT + p1[1]*UP - h_line = Line(p1, interim_point) - v_line = Line(interim_point, p2) - slope_lines = VGroup(h_line, v_line) - slope_lines.set_color(GREEN) - - slope = TextMobject("Slope = ", "$%.2f$"%self.dimension) - slope[-1].set_color(GREEN) - slope.next_to(slope_lines, RIGHT) - - self.wait() - data_points = [ - self.input_to_graph_point(x) + ((random.random()-0.5)/x)*UP - for x in np.arange(1, 8, 0.7) - ] - data_dots = VGroup(*[ - Dot(point, radius = 0.05, color = YELLOW) - for point in data_points - ]) - self.play(ShowCreation(data_dots, run_time = 3)) - self.wait() - - self.play( - ShowCreation(graph), - Animation(expression) - ) - self.wait() - self.play(ShowCreation(slope_lines)) - self.play(Write(slope)) - self.wait() - -class ManyBritainCounts(BoxCountingWithBritain): - CONFIG = { - "box_width" : 0.1, - "num_boundary_check_points" : 10000, - "corner_rect_left_extension" : 1, - } - def construct(self): - britain = Britain( - stroke_width = 2, - fill_opacity = 0 - ) - britain = fractalify(britain, order = 1, dimension = 1.21) - britain.next_to(ORIGIN, LEFT) - self.add(self.get_grid()) - self.add(britain) - - for x in range(5): - self.play(britain.scale, 2, britain.point_from_proportion(0.8)) - boxes = self.get_highlighted_boxes(britain) - self.play(ShowCreation(boxes)) - self.wait() - self.play(FadeOut(boxes)) - -class ReadyForRealDefinition(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Now for what - fractals really are. - """) - self.change_student_modes(*["hooray"]*3) - self.wait(2) - -class DefineFractal(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Fractals are shapes - with a non-integer dimension. - """) - self.change_student_modes("thinking", "happy", "erm") - self.wait(3) - self.teacher_says( - "Kind of...", - target_mode = "sassy" - ) - self.change_student_modes(*["pondering"]*3) - self.play(*[ - ApplyMethod(pi.look, DOWN) - for pi in self.get_pi_creatures() - ]) - self.wait(3) - -class RoughnessAndFractionalDimension(Scene): - def construct(self): - title = TextMobject( - "Non-integer dimension $\\Leftrightarrow$ Roughness" - ) - title.to_edge(UP) - self.add(title) - - randy = Randolph().scale(2) - randy.to_corner(DOWN+RIGHT) - self.add(randy) - - target = randy.copy() - target.change_mode("hooray") - ponder_target = randy.copy() - ponder_target.change_mode("pondering") - for mob in target, ponder_target: - fractalify(mob, order = 2) - - dimension_label = TextMobject("Boundary dimension = ", "1") - dimension_label.to_edge(LEFT) - one = dimension_label[1] - one.set_color(BLUE) - new_dim = TexMobject("1.2") - new_dim.move_to(one, DOWN+LEFT) - new_dim.set_color(one.get_color()) - self.add(dimension_label) - - self.play(Blink(randy)) - self.play( - Transform(randy, target, run_time = 2), - Transform(one, new_dim) - ) - self.wait() - self.play(Blink(randy)) - self.play(randy.look, DOWN+RIGHT) - self.wait() - self.play(randy.look, DOWN+LEFT) - self.play(Blink(randy)) - self.wait() - self.play(Transform(randy, ponder_target)) - self.wait() - -class DifferentSlopesAtDifferentScales(IntroduceLogLogPlot): - def construct(self): - self.setup_axes(animate = False) - self.x_axis_label_mob[-2].set_color(BLUE) - self.y_axis_label_mob[-2].set_color(YELLOW) - self.graph_function( - lambda x : 0.01*(x-5)**3 + 0.3*x + 3 - ) - self.remove(self.graph) - - words = TextMobject(""" - Different slopes - at different scales - """) - words.to_edge(RIGHT) - arrows = VGroup(*[ - Arrow(words.get_left(), self.input_to_graph_point(x)) - for x in (1, 7, 12) - ]) - - - data_points = [ - self.input_to_graph_point(x) + (0.3*(random.random()-0.5))*UP - for x in np.arange(1, self.x_max, 0.7) - ] - data_dots = VGroup(*[ - Dot(point, radius = 0.05, color = YELLOW) - for point in data_points - ]) - - self.play(ShowCreation(data_dots, run_time = 2)) - self.play(ShowCreation(self.graph)) - self.wait() - self.play( - Write(words), - ShowCreation(arrows) - ) - self.wait() - -class HoldUpCoilExample(TeacherStudentsScene): - def construct(self): - point = UP+RIGHT - self.play( - self.get_teacher().change_mode, "raise_right_hand", - self.get_teacher().look_at, point - ) - self.play(*[ - ApplyMethod(pi.look_at, point) - for pi in self.get_students() - ]) - self.wait(5) - self.change_student_modes(*["thinking"]*3) - self.play(*[ - ApplyMethod(pi.look_at, point) - for pi in self.get_students() - ]) - self.wait(5) - -class SmoothHilbertZoom(Scene): - def construct(self): - hilbert = HilbertCurve( - order = 7, - color = MAROON_B, - monochromatic = True - ) - hilbert.make_smooth() - self.add(hilbert) - - two_d_title = TextMobject("2D at a distance...") - one_d_title = TextMobject("1D up close") - for title in two_d_title, one_d_title: - title.to_edge(UP) - - self.add(two_d_title) - self.wait() - self.play( - ApplyMethod( - hilbert.scale, 100, - hilbert.point_from_proportion(0.3), - ), - Transform( - two_d_title, one_d_title, - rate_func = squish_rate_func(smooth) - ), - run_time = 3 - ) - self.wait() - -class ListDimensionTypes(PiCreatureScene): - CONFIG = { - "use_morty" : False, - } - def construct(self): - types = VGroup(*list(map(TextMobject, [ - "Box counting dimension", - "Information dimension", - "Hausdorff dimension", - "Packing dimension" - ]))) - types.arrange(DOWN, aligned_edge = LEFT) - for text in types: - self.play( - Write(text, run_time = 1), - self.pi_creature.change_mode, "pondering" - ) - self.wait(3) - -class ZoomInOnBritain(Scene): - CONFIG = { - "zoom_factor" : 1000 - } - def construct(self): - britain = Britain() - fractalify(britain, order = 3, dimension = 1.21) - anchors = britain.get_anchors() - - key_value = int(0.3*len(anchors)) - point = anchors[key_value] - thinning_factor = 100 - num_neighbors_kept = 1000 - - britain.set_points_as_corners(reduce( - lambda a1, a2 : np.append(a1, a2, axis = 0), - [ - anchors[:key_value-num_neighbors_kept:thinning_factor,:], - anchors[key_value-num_neighbors_kept:key_value+num_neighbors_kept,:], - anchors[key_value+num_neighbors_kept::thinning_factor,:], - ] - )) - - self.add(britain) - self.wait() - self.play( - britain.scale, self.zoom_factor, point, - run_time = 10 - ) - self.wait() - -class NoteTheConstantSlope(Scene): - def construct(self): - words = TextMobject("Note the \\\\ constant slope") - words.set_color(YELLOW) - self.play(Write(words)) - self.wait(2) - -class FromHandwavyToQuantitative(Scene): - def construct(self): - randy = Randolph() - morty = Mortimer() - for pi in randy, morty: - pi.next_to(ORIGIN, DOWN) - randy.shift(2*LEFT) - morty.shift(2*RIGHT) - randy.make_eye_contact(morty) - - self.add(randy, morty) - self.play(PiCreatureSays( - randy, "Fractals are rough", - target_mode = "shruggie" - )) - self.play(morty.change_mode, "sassy") - self.play(Blink(morty)) - self.play( - PiCreatureSays( - morty, "We can make \\\\ that quantitative!", - target_mode = "hooray" - ), - FadeOut(randy.bubble), - FadeOut(randy.bubble.content), - randy.change_mode, "happy" - ) - self.play(Blink(randy)) - self.wait() - -class WhatSlopeDoesLogLogPlotApproach(IntroduceLogLogPlot): - CONFIG = { - "words" : "What slope does \\\\ this approach?", - "x_max" : 20, - "y_max" : 15, - } - def construct(self): - self.setup_axes(animate = False) - self.x_axis_label_mob[-2].set_color(BLUE) - self.y_axis_label_mob[-2].set_color(YELLOW) - - spacing = 0.5 - x_range = np.arange(1, self.x_max, spacing) - randomness = [ - 0.5*np.exp(-x/2)+spacing*(0.8 + random.random()/(x**(0.5))) - for x in x_range - ] - cum_sums = np.cumsum(randomness) - data_points = [ - self.coords_to_point(x, cum_sum) - for x, cum_sum in zip(x_range, cum_sums) - ] - data_dots = VGroup(*[ - Dot(point, radius = 0.025, color = YELLOW) - for point in data_points - ]) - - words = TextMobject(self.words) - p1, p2 = [ - data_dots[int(alpha*len(data_dots))].get_center() - for alpha in (0.3, 0.5) - ] - words.rotate(Line(p1, p2).get_angle()) - words.next_to(p1, RIGHT, aligned_edge = DOWN, buff = 1.5) - - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - self.add(morty) - - self.play(ShowCreation(data_dots, run_time = 7)) - self.play( - Write(words), - morty.change_mode, "speaking" - ) - self.play(Blink(morty)) - self.wait() - -class BritainBoxCountHighZoom(BoxCountingWithBritain): - def construct(self): - britain = Britain( - stroke_width = 2, - fill_opacity = 0 - ) - britain = fractalify(britain, order = 2, dimension = 1.21) - self.add(self.get_grid()) - self.add(britain) - - for x in range(2): - self.play( - britain.scale, 10, britain.point_from_proportion(0.3), - run_time = 2 - ) - if x == 0: - a, b = 0.2, 0.5 - else: - a, b = 0.25, 0.35 - britain.pointwise_become_partial(britain, a, b) - self.count_britain(britain) - self.wait() - - def count_britain(self, britain): - boxes = self.get_highlighted_boxes(britain) - self.play(ShowCreation(boxes)) - self.wait() - self.play(FadeOut(boxes)) - -class IfBritainWasEventuallySmooth(Scene): - def construct(self): - britain = Britain() - britain.make_smooth() - point = britain.point_from_proportion(0.3) - - self.add(britain) - self.wait() - self.play( - britain.scale, 200, point, - run_time = 10 - ) - self.wait() - -class SmoothBritainLogLogPlot(IntroduceLogLogPlot): - CONFIG = { - } - def construct(self): - self.setup_axes() - self.graph_function( - lambda x : (1 + np.exp(-x/5.0))*x - ) - self.remove(self.graph) - - p1, p2, p3, p4 = [ - self.input_to_graph_point(x) - for x in (1, 2, 7, 8) - ] - interim_point1 = p2[0]*RIGHT + p1[1]*UP - interim_point2 = p4[0]*RIGHT + p3[1]*UP - - print(self.func(2)) - - slope_lines1, slope_lines2 = VMobject(), VMobject() - slope_lines1.set_points_as_corners( - [p1, interim_point1, p2] - ) - slope_lines2.set_points_as_corners( - [p3, interim_point2, p4] - ) - slope_lines_group = VGroup(slope_lines1, slope_lines2) - slope_lines_group.set_color(GREEN) - - slope_label1 = TextMobject("Slope $> 1$") - slope_label2 = TextMobject("Slope $= 1$") - slope_label1.next_to(slope_lines1) - slope_label2.next_to(slope_lines2) - - data_points = [ - self.input_to_graph_point(x) + ((random.random()-0.5)/x)*UP - for x in np.arange(1, 12, 0.7) - ] - data_dots = VGroup(*[ - Dot(point, radius = 0.05, color = YELLOW) - for point in data_points - ]) - - self.play(ShowCreation(data_dots, run_time = 3)) - self.play(ShowCreation(self.graph)) - self.wait() - self.play( - ShowCreation(slope_lines1), - Write(slope_label1) - ) - self.wait() - self.play( - ShowCreation(slope_lines2), - Write(slope_label2) - ) - self.wait() - -class SlopeAlwaysAboveOne(WhatSlopeDoesLogLogPlotApproach): - CONFIG = { - "words" : "Slope always $> 1$", - "x_max" : 20, - } - -class ChangeWorldview(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - This changes how - you see the world. - """) - self.change_student_modes(*["thinking"]*3) - self.wait(3) - -class CompareBritainAndNorway(Scene): - def construct(self): - norway = Norway( - fill_opacity = 0, - stroke_width = 2, - ) - norway.to_corner(UP+RIGHT, buff = 0) - fractalify(norway, order = 1, dimension = 1.5) - anchors = list(norway.get_anchors()) - anchors.append(FRAME_X_RADIUS*RIGHT+FRAME_Y_RADIUS*UP) - norway.set_points_as_corners(anchors) - - britain = Britain( - fill_opacity = 0, - stroke_width = 2 - ) - britain.shift(FRAME_X_RADIUS*LEFT/2) - britain.to_edge(UP) - fractalify(britain, order = 1, dimension = 1.21) - - britain_label = TextMobject(""" - Britain coast: - 1.21-dimensional - """) - norway_label = TextMobject(""" - Norway coast: - 1.52-dimensional - """) - britain_label.next_to(britain, DOWN) - norway_label.next_to(britain_label, RIGHT, aligned_edge = DOWN) - norway_label.to_edge(RIGHT) - - self.add(britain_label, norway_label) - self.play( - *list(map(ShowCreation, [norway, britain])), - run_time = 3 - ) - self.wait() - self.play(*it.chain(*[ - [ - mob.set_stroke, None, 0, - mob.set_fill, BLUE, 1 - ] - for mob in (britain, norway) - ])) - self.wait(2) - -class CompareOceansLabels(Scene): - def construct(self): - label1 = TextMobject("Dimension $\\approx 2.05$") - label2 = TextMobject("Dimension $\\approx 2.3$") - - label1.shift(FRAME_X_RADIUS*LEFT/2).to_edge(UP) - label2.shift(FRAME_X_RADIUS*RIGHT/2).to_edge(UP) - - self.play(Write(label1)) - self.wait() - self.play(Write(label2)) - self.wait() - -class CompareOceans(Scene): - def construct(self): - pass - -class FractalNonFractalFlowChart(Scene): - def construct(self): - is_fractal = TextMobject("Is it a \\\\ fractal?") - nature = TextMobject("Probably from \\\\ nature") - man_made = TextMobject("Probably \\\\ man-made") - - is_fractal.to_edge(UP) - nature.shift(FRAME_X_RADIUS*LEFT/2) - man_made.shift(FRAME_X_RADIUS*RIGHT/2) - - yes_arrow = Arrow( - is_fractal.get_bottom(), - nature.get_top() - ) - no_arrow = Arrow( - is_fractal.get_bottom(), - man_made.get_top() - ) - - yes = TextMobject("Yes") - no = TextMobject("No") - yes.set_color(GREEN) - no.set_color(RED) - - for word, arrow in (yes, yes_arrow), (no, no_arrow): - word.next_to(ORIGIN, UP) - word.rotate(arrow.get_angle()) - if word is yes: - word.rotate(np.pi) - word.shift(arrow.get_center()) - - britain = Britain() - britain.set_height(3) - britain.to_corner(UP+LEFT) - self.add(britain) - - randy = Randolph() - randy.set_height(3) - randy.to_corner(UP+RIGHT) - self.add(randy) - - self.add(is_fractal) - self.wait() - for word, arrow, answer in (yes, yes_arrow, nature), (no, no_arrow, man_made): - self.play( - ShowCreation(arrow), - Write(word, run_time = 1) - ) - self.play(Write(answer, run_time = 1)) - if word is yes: - self.wait() - else: - self.play(Blink(randy)) - -class ShowPiCreatureFractalCreation(FractalCreation): - CONFIG = { - "fractal_class" : PentagonalPiCreatureFractal, - "max_order" : 4, - } - -class FractalPatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Meshal Alshammari", - "Ali Yahya", - "CrypticSwarm ", - "Yu Jun", - "Shelby Doolittle", - "Dave Nicponski", - "Damion Kistler", - "Juan Batiz-Benet", - "Othman Alikhan", - "Markus Persson", - "Dan Buchoff", - "Derek Dai", - "Joseph John Cox", - "Luc Ritchie", - "Jerry Ling", - "Mark Govea", - "Guido Gambardella", - "Vecht ", - "Jonathan Eppele", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Kirk Werklund", - "Ripta Pasay", - "Felipe Diniz", - ] - } - -class AffirmLogo(SVGMobject): - CONFIG = { - "fill_color" : "#0FA0EA", - "fill_opacity" : 1, - "stroke_color" : "#0FA0EA", - "stroke_width" : 0, - "file_name" : "affirm_logo", - "width" : 3, - } - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - self.set_width(self.width) - -class MortyLookingAtRectangle(Scene): - def construct(self): - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - url = TextMobject("affirmjobs.3b1b.co") - url.to_corner(UP+LEFT) - rect = Rectangle(height = 9, width = 16) - rect.set_height(5) - rect.next_to(url, DOWN) - rect.shift_onto_screen() - url.save_state() - url.next_to(morty.get_corner(UP+LEFT), UP) - - affirm_logo = AffirmLogo()[0] - affirm_logo.to_corner(UP+RIGHT, buff = MED_LARGE_BUFF) - affirm_logo.shift(0.5*DOWN) - - self.add(morty) - affirm_logo.save_state() - affirm_logo.shift(DOWN) - affirm_logo.set_fill(opacity = 0) - self.play( - ApplyMethod(affirm_logo.restore, run_time = 2), - morty.look_at, affirm_logo, - ) - self.play( - morty.change_mode, "raise_right_hand", - morty.look_at, url, - ) - self.play(Write(url)) - self.play(Blink(morty)) - self.wait() - self.play( - url.restore, - morty.change_mode, "happy" - ) - self.play(ShowCreation(rect)) - self.wait() - self.play(Blink(morty)) - for mode in ["wave_2", "hooray", "happy", "pondering", "happy"]: - self.play(morty.change_mode, mode) - self.wait(2) - self.play(Blink(morty)) - self.wait(2) - - - -class Thumbnail(Scene): - def construct(self): - title = TextMobject("1.5-dimensional") - title.scale(2) - title.to_edge(UP) - - - koch_curve = QuadraticKoch(order = 6, monochromatic = True) - koch_curve.set_stroke(width = 0) - koch_curve.set_fill(BLUE) - koch_curve.set_height(1.5*FRAME_Y_RADIUS) - koch_curve.to_edge(DOWN, buff = SMALL_BUFF) - - self.add(koch_curve, title) diff --git a/from_3b1b/old/generate_logo.py b/from_3b1b/old/generate_logo.py deleted file mode 100644 index 6e4744ef..00000000 --- a/from_3b1b/old/generate_logo.py +++ /dev/null @@ -1,66 +0,0 @@ -from manimlib.imports import * - -## Warning, much of what is in this class -## likely not supported anymore. - -class LogoGeneration(Scene): - CONFIG = { - "radius" : 1.5, - "inner_radius_ratio" : 0.55, - "circle_density" : 100, - "circle_blue" : "skyblue", - "circle_brown" : DARK_BROWN, - "circle_repeats" : 5, - "sphere_density" : 50, - "sphere_blue" : DARK_BLUE, - "sphere_brown" : LIGHT_BROWN, - "interpolation_factor" : 0.3, - "frame_duration" : 0.03, - "run_time" : 3, - } - def construct(self): - digest_config(self, {}) - ## Usually shouldn't need this... - self.frame_duration = self.CONFIG["frame_duration"] - ## - digest_config(self, {}) - circle = Circle( - density = self.circle_density, - color = self.circle_blue - ) - circle.repeat(self.circle_repeats) - circle.scale(self.radius) - sphere = Sphere( - density = self.sphere_density, - color = self.sphere_blue - ) - sphere.scale(self.radius) - sphere.rotate(-np.pi / 7, [1, 0, 0]) - sphere.rotate(-np.pi / 7) - iris = Mobject() - iris.interpolate( - circle, sphere, - self.interpolation_factor - ) - for mob, color in [(iris, self.sphere_brown), (circle, self.circle_brown)]: - mob.set_color(color, lambda x_y_z : x_y_z[0] < 0 and x_y_z[1] > 0) - mob.set_color( - "black", - lambda point: get_norm(point) < \ - self.inner_radius_ratio*self.radius - ) - self.name_mob = TextMobject("3Blue1Brown").center() - self.name_mob.set_color("grey") - self.name_mob.shift(2*DOWN) - - self.play(Transform( - circle, iris, - run_time = self.run_time - )) - self.frames = drag_pixels(self.frames) - self.save_image(IMAGE_DIR) - self.logo = MobjectFromPixelArray(self.frames[-1]) - self.add(self.name_mob) - self.wait() - - diff --git a/from_3b1b/old/gradient.py b/from_3b1b/old/gradient.py deleted file mode 100644 index 3e4178f0..00000000 --- a/from_3b1b/old/gradient.py +++ /dev/null @@ -1,399 +0,0 @@ -from manimlib.imports import * - - -# Warning, this file uses ContinualChangingDecimal, -# which has since been been deprecated. Use a mobject -# updater instead - - -class GradientDescentWrapper(Scene): - def construct(self): - title = TextMobject("Gradient descent") - title.to_edge(UP) - rect = ScreenRectangle(height=6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - -class ShowSimpleMultivariableFunction(Scene): - def construct(self): - scale_val = 1.5 - - func_tex = TexMobject( - "C(", "x_1,", "x_2,", "\\dots,", "x_n", ")", "=", - ) - func_tex.scale(scale_val) - func_tex.shift(2 * LEFT) - alt_func_tex = TexMobject( - "C(", "x,", "y", ")", "=" - ) - alt_func_tex.scale(scale_val) - for tex in func_tex, alt_func_tex: - tex.set_color_by_tex_to_color_map({ - "C(": RED, - ")": RED, - }) - alt_func_tex.move_to(func_tex, RIGHT) - inputs = func_tex[1:-2] - self.add(func_tex) - - many_inputs = TexMobject(*[ - "x_{%d}, " % d for d in range(1, 25) - ]) - many_inputs.set_width(FRAME_WIDTH) - many_inputs.to_edge(UL) - - inputs_brace = Brace(inputs, UP) - inputs_brace_text = inputs_brace.get_text("Multiple inputs") - - decimal = DecimalNumber(0) - decimal.scale(scale_val) - decimal.next_to(tex, RIGHT) - value_tracker = ValueTracker(0) - always_shift(value_tracker, rate=0.5) - self.add(value_tracker) - decimal_change = ContinualChangingDecimal( - decimal, - lambda a: 1 + np.sin(value_tracker.get_value()) - ) - self.add(decimal_change) - - output_brace = Brace(decimal, DOWN) - output_brace_text = output_brace.get_text("Single output") - - self.wait(2) - self.play(GrowFromCenter(inputs_brace)) - self.play(Write(inputs_brace_text)) - self.play(GrowFromCenter(output_brace)) - self.play(Write(output_brace_text)) - self.wait(3) - self.play( - ReplacementTransform( - inputs, - many_inputs[:len(inputs)] - ), - LaggedStartMap( - FadeIn, - many_inputs[len(inputs):] - ), - FadeOut(inputs_brace), - FadeOut(inputs_brace_text), - ) - self.wait() - self.play( - ReplacementTransform( - func_tex[0], alt_func_tex[0] - ), - Write(alt_func_tex[1:3]), - LaggedStartMap(FadeOutAndShiftDown, many_inputs) - ) - self.wait(3) - - -class ShowGraphWithVectors(ExternallyAnimatedScene): - pass - - -class ShowFunction(Scene): - def construct(self): - func = TexMobject( - "f(x, y) = e^{-x^2 + \\cos(2y)}", - tex_to_color_map={ - "x": BLUE, - "y": RED, - } - ) - func.scale(1.5) - self.play(FadeInFromDown(func)) - self.wait() - - -class ShowExampleFunctionGraph(ExternallyAnimatedScene): - pass - - -class ShowGradient(Scene): - def construct(self): - lhs = TexMobject( - "\\nabla f(x, y)=", - tex_to_color_map={"x": BLUE, "y": RED} - ) - vector = Matrix([ - ["\\partial f / \\partial x"], - ["\\partial f / \\partial y"], - ], v_buff=1) - gradient = VGroup(lhs, vector) - gradient.arrange(RIGHT, buff=SMALL_BUFF) - gradient.scale(1.5) - - del_x, del_y = partials = vector.get_entries() - background_rects = VGroup() - for partial, color in zip(partials, [BLUE, RED]): - partial[-1].set_color(color) - partial.rect = SurroundingRectangle( - partial, buff=MED_SMALL_BUFF - ) - partial.rect.set_stroke(width=0) - partial.rect.set_fill(color=color, opacity=0.5) - background_rects.add(partial.rect.copy()) - background_rects.set_fill(opacity=0.1) - - partials.set_fill(opacity=0) - - self.play( - LaggedStartMap(FadeIn, gradient), - LaggedStartMap( - FadeIn, background_rects, - rate_func=squish_rate_func(smooth, 0.5, 1) - ) - ) - self.wait() - for partial in partials: - self.play(DrawBorderThenFill(partial.rect)) - self.wait() - self.play(FadeOut(partial.rect)) - self.wait() - for partial in partials: - self.play(Write(partial)) - self.wait() - - -class ExampleGraphHoldXConstant(ExternallyAnimatedScene): - pass - - -class ExampleGraphHoldYConstant(ExternallyAnimatedScene): - pass - - -class TakePartialDerivatives(Scene): - def construct(self): - tex_to_color_map = { - "x": BLUE, - "y": RED, - } - func_tex = TexMobject( - "f", "(", "x", ",", "y", ")", "=", - "e^{", "-x^2", "+ \\cos(2y)}", - tex_to_color_map=tex_to_color_map - ) - partial_x = TexMobject( - "{\\partial", "f", "\\over", "\\partial", "x}", "=", - "\\left(", "e^", "{-x^2", "+ \\cos(2y)}", "\\right)", - "(", "-2", "x", ")", - tex_to_color_map=tex_to_color_map, - ) - partial_y = TexMobject( - "{\\partial", "f", "\\over", "\\partial", "y}", "=", - "\\left(", "e^", "{-x^2", "+ \\cos(", "2", "y)}", "\\right)", - "(", "-\\sin(", "2", "y)", "\\cdot 2", ")", - tex_to_color_map=tex_to_color_map, - ) - partials = VGroup(partial_x, partial_y) - for mob in func_tex, partials: - mob.scale(1.5) - - func_tex.move_to(2 * UP + 3 * LEFT) - for partial in partials: - partial.next_to(func_tex, DOWN, buff=LARGE_BUFF) - top_eq_x = func_tex.get_part_by_tex("=").get_center()[0] - low_eq_x = partial.get_part_by_tex("=").get_center()[0] - partial.shift((top_eq_x - low_eq_x) * RIGHT) - - index = func_tex.index_of_part_by_tex("e^") - exp_rect = SurroundingRectangle(func_tex[index + 1:], buff=0) - exp_rect.set_stroke(width=0) - exp_rect.set_fill(GREEN, opacity=0.5) - - xs = func_tex.get_parts_by_tex("x", substring=False) - ys = func_tex.get_parts_by_tex("y", substring=False) - for terms in xs, ys: - terms.rects = VGroup(*[ - SurroundingRectangle(term, buff=0.5 * SMALL_BUFF) - for term in terms - ]) - terms.arrows = VGroup(*[ - Vector(0.5 * DOWN).next_to(rect, UP, SMALL_BUFF) - for rect in terms.rects - ]) - treat_as_constant = TextMobject("Treat as a constant") - treat_as_constant.next_to(ys.arrows[1], UP) - - # Start to show partial_x - self.play(FadeInFromDown(func_tex)) - self.wait() - self.play( - ReplacementTransform(func_tex[0].copy(), partial_x[1]), - Write(partial_x[0]), - Write(partial_x[2:4]), - Write(partial_x[6]), - ) - self.play( - ReplacementTransform(func_tex[2].copy(), partial_x[4]) - ) - self.wait() - - # Label y as constant - self.play(LaggedStartMap(ShowCreation, ys.rects)) - self.play( - LaggedStartMap(GrowArrow, ys.arrows, lag_ratio=0.8), - Write(treat_as_constant) - ) - self.wait(2) - - # Perform partial_x derivative - self.play(FadeIn(exp_rect), Animation(func_tex)) - self.wait() - pxi1 = 8 - pxi2 = 15 - self.play( - ReplacementTransform( - func_tex[7:].copy(), - partial_x[pxi1:pxi2], - ), - FadeIn(partial_x[pxi1 - 1:pxi1]), - FadeIn(partial_x[pxi2]), - ) - self.wait(2) - self.play( - ReplacementTransform( - partial_x[10:12].copy(), - partial_x[pxi2 + 2:pxi2 + 4], - path_arc=-(TAU / 4) - ), - FadeIn(partial_x[pxi2 + 1]), - FadeIn(partial_x[-1]), - ) - self.wait(2) - - # Swap out partial_x for partial_y - self.play( - FadeOutAndShiftDown(partial_x), - FadeOut(ys.rects), - FadeOut(ys.arrows), - FadeOut(treat_as_constant), - FadeOut(exp_rect), - Animation(func_tex) - ) - self.play(FadeInFromDown(partial_y[:7])) - self.wait() - - treat_as_constant.next_to(xs.arrows[1], UP, SMALL_BUFF) - self.play( - LaggedStartMap(ShowCreation, xs.rects), - LaggedStartMap(GrowArrow, xs.arrows), - Write(treat_as_constant), - lag_ratio=0.8 - ) - self.wait() - - # Show same outer derivative - self.play( - ReplacementTransform( - func_tex[7:].copy(), - partial_x[pxi1:pxi2], - ), - FadeIn(partial_x[pxi1 - 2:pxi1]), - FadeIn(partial_x[pxi2]), - ) - self.wait() - self.play( - ReplacementTransform( - partial_y[12:16].copy(), - partial_y[pxi2 + 3:pxi2 + 7], - path_arc=-(TAU / 4) - ), - FadeIn(partial_y[pxi2 + 2]), - FadeIn(partial_y[-1]), - ) - self.wait() - self.play(ReplacementTransform( - partial_y[-5].copy(), - partial_y[-2], - path_arc=-PI - )) - self.wait() - - -class ShowDerivativeAtExamplePoint(Scene): - def construct(self): - tex_to_color_map = { - "x": BLUE, - "y": RED, - } - func_tex = TexMobject( - "f", "(", "x", ",", "y", ")", "=", - "e^{", "-x^2", "+ \\cos(2y)}", - tex_to_color_map=tex_to_color_map - ) - gradient_tex = TexMobject( - "\\nabla", "f", "(", "x", ",", "y", ")", "=", - tex_to_color_map=tex_to_color_map - ) - - partial_vect = Matrix([ - ["{\\partial f / \\partial x}"], - ["{\\partial f / \\partial y}"], - ]) - partial_vect.get_mob_matrix()[0, 0][-1].set_color(BLUE) - partial_vect.get_mob_matrix()[1, 0][-1].set_color(RED) - result_vector = self.get_result_vector("x", "y") - - gradient = VGroup( - gradient_tex, - partial_vect, - TexMobject("="), - result_vector - ) - gradient.arrange(RIGHT, buff=SMALL_BUFF) - - func_tex.to_edge(UP) - gradient.next_to(func_tex, DOWN, buff=LARGE_BUFF) - - example_lhs = TexMobject( - "\\nabla", "f", "(", "1", ",", "3", ")", "=", - tex_to_color_map={"1": BLUE, "3": RED}, - ) - example_result_vector = self.get_result_vector("1", "3") - example_rhs = DecimalMatrix([[-1.92], [0.54]]) - example = VGroup( - example_lhs, - example_result_vector, - TexMobject("="), - example_rhs, - ) - example.arrange(RIGHT, buff=SMALL_BUFF) - example.next_to(gradient, DOWN, LARGE_BUFF) - - self.add(func_tex, gradient) - self.wait() - self.play( - ReplacementTransform(gradient_tex.copy(), example_lhs), - ReplacementTransform(result_vector.copy(), example_result_vector), - ) - self.wait() - self.play(Write(example[2:])) - self.wait() - - def get_result_vector(self, x, y): - result_vector = Matrix([ - ["e^{-%s^2 + \\cos(2\\cdot %s)} (-2\\cdot %s)" % (x, y, x)], - ["e^{-%s^2 + \\cos(2\\cdot %s)} \\big(-\\sin(2\\cdot %s) \\cdot 2\\big)" % (x, y, y)], - ], v_buff=1.2, element_alignment_corner=ORIGIN) - - x_terms = VGroup( - result_vector.get_mob_matrix()[0, 0][2], - result_vector.get_mob_matrix()[1, 0][2], - result_vector.get_mob_matrix()[0, 0][-2], - ) - y_terms = VGroup( - result_vector.get_mob_matrix()[0, 0][11], - result_vector.get_mob_matrix()[1, 0][11], - result_vector.get_mob_matrix()[1, 0][-5], - ) - x_terms.set_color(BLUE) - y_terms.set_color(RED) - return result_vector diff --git a/from_3b1b/old/hanoi.py b/from_3b1b/old/hanoi.py deleted file mode 100644 index 0891887e..00000000 --- a/from_3b1b/old/hanoi.py +++ /dev/null @@ -1,3388 +0,0 @@ -from manimlib.imports import * - -class CountingScene(Scene): - CONFIG = { - "base" : 10, - "power_colors" : [YELLOW, MAROON_B, RED, GREEN, BLUE, PURPLE_D], - "counting_dot_starting_position" : (FRAME_X_RADIUS-1)*RIGHT + (FRAME_Y_RADIUS-1)*UP, - "count_dot_starting_radius" : 0.5, - "dot_configuration_height" : 2, - "ones_configuration_location" : UP+2*RIGHT, - "num_scale_factor" : 2, - "num_start_location" : 2*DOWN, - } - def setup(self): - self.dots = VGroup() - self.number = 0 - self.number_mob = VGroup(TexMobject(str(self.number))) - self.number_mob.scale(self.num_scale_factor) - self.number_mob.shift(self.num_start_location) - self.digit_width = self.number_mob.get_width() - - self.initialize_configurations() - self.arrows = VGroup() - self.add(self.number_mob) - - def get_template_configuration(self): - #This should probably be replaced for non-base-10 counting scenes - down_right = (0.5)*RIGHT + (np.sqrt(3)/2)*DOWN - result = [] - for down_right_steps in range(5): - for left_steps in range(down_right_steps): - result.append( - down_right_steps*down_right + left_steps*LEFT - ) - return reversed(result[:self.base]) - - def get_dot_template(self): - #This should be replaced for non-base-10 counting scenes - down_right = (0.5)*RIGHT + (np.sqrt(3)/2)*DOWN - dots = VGroup(*[ - Dot( - point, - radius = 0.25, - fill_opacity = 0, - stroke_width = 2, - stroke_color = WHITE, - ) - for point in self.get_template_configuration() - ]) - dots[-1].set_stroke(width = 0) - dots.set_height(self.dot_configuration_height) - return dots - - def initialize_configurations(self): - self.dot_templates = [] - self.dot_template_iterators = [] - self.curr_configurations = [] - - def add_configuration(self): - new_template = self.get_dot_template() - new_template.move_to(self.ones_configuration_location) - left_vect = (new_template.get_width()+LARGE_BUFF)*LEFT - new_template.shift( - left_vect*len(self.dot_templates) - ) - self.dot_templates.append(new_template) - self.dot_template_iterators.append( - it.cycle(new_template) - ) - self.curr_configurations.append(VGroup()) - - def count(self, max_val, run_time_per_anim = 1): - for x in range(max_val): - self.increment(run_time_per_anim) - - def increment(self, run_time_per_anim = 1, added_anims = [], total_run_time = None): - run_all_at_once = (total_run_time is not None) - if run_all_at_once: - num_rollovers = self.get_num_rollovers() - run_time_per_anim = float(total_run_time)/(num_rollovers+1) - moving_dot = Dot( - self.counting_dot_starting_position, - radius = self.count_dot_starting_radius, - color = self.power_colors[0], - ) - moving_dot.generate_target() - moving_dot.set_fill(opacity = 0) - - continue_rolling_over = True - place = 0 - self.number += 1 - added_anims = list(added_anims) #Silly python objects... - added_anims += self.get_new_configuration_animations() - while continue_rolling_over: - moving_dot.target.replace( - next(self.dot_template_iterators[place]) - ) - if run_all_at_once: - denom = float(num_rollovers+1) - start_t = place/denom - def get_modified_rate_func(anim): - return lambda t : anim.original_rate_func( - start_t + t/denom - ) - for anim in added_anims: - if not hasattr(anim, "original_rate_func"): - anim.original_rate_func = anim.rate_func - anim.rate_func = get_modified_rate_func(anim) - self.play( - MoveToTarget(moving_dot), - *added_anims, - run_time = run_time_per_anim - ) - self.curr_configurations[place].add(moving_dot) - if not run_all_at_once: - added_anims = [] - - - if len(self.curr_configurations[place].split()) == self.base: - full_configuration = self.curr_configurations[place] - self.curr_configurations[place] = VGroup() - place += 1 - center = full_configuration.get_center_of_mass() - radius = 0.6*max( - full_configuration.get_width(), - full_configuration.get_height(), - ) - circle = Circle( - radius = radius, - stroke_width = 0, - fill_color = self.power_colors[place], - fill_opacity = 0.5, - ) - circle.move_to(center) - moving_dot = VGroup(circle, full_configuration) - moving_dot.generate_target() - moving_dot[0].set_fill(opacity = 0) - else: - continue_rolling_over = False - self.play(*self.get_digit_increment_animations()) - - def get_new_configuration_animations(self): - if self.is_perfect_power(): - self.add_configuration() - return [FadeIn(self.dot_templates[-1])] - else: - return [] - - def get_digit_increment_animations(self): - result = [] - new_number_mob = self.get_number_mob(self.number) - new_number_mob.move_to(self.number_mob, RIGHT) - if self.is_perfect_power(): - place = len(new_number_mob.split())-1 - arrow = Arrow( - new_number_mob[place].get_top(), - self.dot_templates[place].get_bottom(), - color = self.power_colors[place] - ) - self.arrows.add(arrow) - result.append(ShowCreation(arrow)) - result.append(Transform( - self.number_mob, new_number_mob, - lag_ratio = 0.5 - )) - return result - - def get_number_mob(self, num): - result = VGroup() - place = 0 - while num > 0: - digit = TexMobject(str(num % self.base)) - if place >= len(self.power_colors): - self.power_colors += self.power_colors - digit.set_color(self.power_colors[place]) - digit.scale(self.num_scale_factor) - digit.move_to(result, RIGHT) - digit.shift(place*(self.digit_width+SMALL_BUFF)*LEFT) - result.add(digit) - num /= self.base - place += 1 - return result - - def is_perfect_power(self): - number = self.number - while number > 1: - if number%self.base != 0: - return False - number /= self.base - return True - - def get_num_rollovers(self): - next_number = self.number + 1 - result = 0 - while next_number%self.base == 0: - result += 1 - next_number /= self.base - return result - -class BinaryCountingScene(CountingScene): - CONFIG = { - "base" : 2, - "dot_configuration_height" : 1, - "ones_configuration_location" : UP+5*RIGHT - } - def get_template_configuration(self): - return [ORIGIN, UP] - -class CountInDecimal(CountingScene): - def construct(self): - for x in range(11): - self.increment() - for x in range(85): - self.increment(0.25) - for x in range(20): - self.increment() - -class CountInTernary(CountingScene): - CONFIG = { - "base" : 3, - "dot_configuration_height" : 1, - "ones_configuration_location" : UP+4*RIGHT - } - def construct(self): - self.count(27) - - # def get_template_configuration(self): - # return [ORIGIN, UP] - -class CountTo27InTernary(CountInTernary): - def construct(self): - for x in range(27): - self.increment() - self.wait() - -class CountInBinaryTo256(BinaryCountingScene): - def construct(self): - self.count(256, 0.25) - -class TowersOfHanoiScene(Scene): - CONFIG = { - "disk_start_and_end_colors" : [BLUE_E, BLUE_A], - "num_disks" : 5, - "peg_width" : 0.25, - "peg_height" : 2.5, - "peg_spacing" : 4, - "include_peg_labels" : True, - "middle_peg_bottom" : 0.5*DOWN, - "disk_height" : 0.4, - "disk_min_width" : 1, - "disk_max_width" : 3, - "default_disk_run_time_off_peg" : 1, - "default_disk_run_time_on_peg" : 2, - } - def setup(self): - self.add_pegs() - self.add_disks() - - def add_pegs(self): - peg = Rectangle( - height = self.peg_height, - width = self.peg_width, - stroke_width = 0, - fill_color = GREY_BROWN, - fill_opacity = 1, - ) - peg.move_to(self.middle_peg_bottom, DOWN) - self.pegs = VGroup(*[ - peg.copy().shift(vect) - for vect in (self.peg_spacing*LEFT, ORIGIN, self.peg_spacing*RIGHT) - ]) - self.add(self.pegs) - if self.include_peg_labels: - self.peg_labels = VGroup(*[ - TexMobject(char).next_to(peg, DOWN) - for char, peg in zip("ABC", self.pegs) - ]) - self.add(self.peg_labels) - - def add_disks(self): - self.disks = VGroup(*[ - Rectangle( - height = self.disk_height, - width = width, - fill_color = color, - fill_opacity = 0.8, - stroke_width = 0, - ) - for width, color in zip( - np.linspace( - self.disk_min_width, - self.disk_max_width, - self.num_disks - ), - color_gradient( - self.disk_start_and_end_colors, - self.num_disks - ) - ) - ]) - for number, disk in enumerate(self.disks): - label = TexMobject(str(number)) - label.set_color(BLACK) - label.set_height(self.disk_height/2) - label.move_to(disk) - disk.add(label) - disk.label = label - self.reset_disks(run_time = 0) - - self.add(self.disks) - - def reset_disks(self, **kwargs): - self.disks.generate_target() - self.disks.target.arrange(DOWN, buff = 0) - self.disks.target.move_to(self.pegs[0], DOWN) - self.play( - MoveToTarget(self.disks), - **kwargs - ) - self.disk_tracker = [ - set(range(self.num_disks)), - set([]), - set([]) - ] - - def disk_index_to_peg_index(self, disk_index): - for index, disk_set in enumerate(self.disk_tracker): - if disk_index in disk_set: - return index - raise Exception("Somehow this disk wasn't accounted for...") - - def min_disk_index_on_peg(self, peg_index): - disk_index_set = self.disk_tracker[peg_index] - if disk_index_set: - return min(self.disk_tracker[peg_index]) - else: - return self.num_disks - - def bottom_point_for_next_disk(self, peg_index): - min_disk_index = self.min_disk_index_on_peg(peg_index) - if min_disk_index >= self.num_disks: - return self.pegs[peg_index].get_bottom() - else: - return self.disks[min_disk_index].get_top() - - def get_next_disk_0_peg(self): - curr_peg_index = self.disk_index_to_peg_index(0) - return (curr_peg_index+1)%3 - - def get_available_peg(self, disk_index): - if disk_index == 0: - return self.get_next_disk_0_peg() - for index in range(len(list(self.pegs))): - if self.min_disk_index_on_peg(index) > disk_index: - return index - raise Exception("Tower's of Honoi rule broken: No available disks") - - def set_disk_config(self, peg_indices): - assert(len(peg_indices) == self.num_disks) - self.disk_tracker = [set([]) for x in range(3)] - for n, peg_index in enumerate(peg_indices): - disk_index = self.num_disks - n - 1 - disk = self.disks[disk_index] - peg = self.pegs[peg_index] - disk.move_to(peg.get_bottom(), DOWN) - n_disks_here = len(self.disk_tracker[peg_index]) - disk.shift(disk.get_height()*n_disks_here*UP) - self.disk_tracker[peg_index].add(disk_index) - - def move_disk(self, disk_index, **kwargs): - next_peg_index = self.get_available_peg(disk_index) - self.move_disk_to_peg(disk_index, next_peg_index, **kwargs) - - def move_subtower_to_peg(self, num_disks, next_peg_index, **kwargs): - disk_indices = list(range(num_disks)) - peg_indices = list(map(self.disk_index_to_peg_index, disk_indices)) - if len(set(peg_indices)) != 1: - warnings.warn("These disks don't make up a tower right now") - self.move_disks_to_peg(disk_indices, next_peg_index, **kwargs) - - def move_disk_to_peg(self, disk_index, next_peg_index, **kwargs): - self.move_disks_to_peg([disk_index], next_peg_index, **kwargs) - - def move_disks_to_peg(self, disk_indices, next_peg_index, run_time = None, stay_on_peg = True, added_anims = []): - if run_time is None: - if stay_on_peg is True: - run_time = self.default_disk_run_time_on_peg - else: - run_time = self.default_disk_run_time_off_peg - disks = VGroup(*[self.disks[index] for index in disk_indices]) - max_disk_index = max(disk_indices) - next_peg = self.pegs[next_peg_index] - curr_peg_index = self.disk_index_to_peg_index(max_disk_index) - curr_peg = self.pegs[curr_peg_index] - if self.min_disk_index_on_peg(curr_peg_index) != max_disk_index: - warnings.warn("Tower's of Hanoi rule broken: disk has crap on top of it") - target_bottom_point = self.bottom_point_for_next_disk(next_peg_index) - path_arc = np.sign(curr_peg_index-next_peg_index)*np.pi/3 - if stay_on_peg: - self.play( - Succession( - ApplyMethod(disks.next_to, curr_peg, UP, 0), - ApplyMethod(disks.next_to, next_peg, UP, 0, path_arc = path_arc), - ApplyMethod(disks.move_to, target_bottom_point, DOWN), - ), - *added_anims, - run_time = run_time, - rate_func = lambda t : smooth(t, 2) - ) - else: - self.play( - ApplyMethod(disks.move_to, target_bottom_point, DOWN), - *added_anims, - path_arc = path_arc*2, - run_time = run_time, - rate_func = lambda t : smooth(t, 2) - ) - for disk_index in disk_indices: - self.disk_tracker[curr_peg_index].remove(disk_index) - self.disk_tracker[next_peg_index].add(disk_index) - -class ConstrainedTowersOfHanoiScene(TowersOfHanoiScene): - def get_next_disk_0_peg(self): - if not hasattr(self, "total_disk_0_movements"): - self.total_disk_0_movements = 0 - curr_peg_index = self.disk_index_to_peg_index(0) - if (self.total_disk_0_movements/2)%2 == 0: - result = curr_peg_index + 1 - else: - result = curr_peg_index - 1 - self.total_disk_0_movements += 1 - return result - -def get_ruler_sequence(order = 4): - if order == -1: - return [] - else: - smaller = get_ruler_sequence(order - 1) - return smaller + [order] + smaller - -def get_ternary_ruler_sequence(order = 4): - if order == -1: - return [] - else: - smaller = get_ternary_ruler_sequence(order-1) - return smaller+[order]+smaller+[order]+smaller - -class SolveHanoi(TowersOfHanoiScene): - def construct(self): - self.wait() - for x in get_ruler_sequence(self.num_disks-1): - self.move_disk(x, stay_on_peg = False) - self.wait() - -class SolveConstrainedHanoi(ConstrainedTowersOfHanoiScene): - def construct(self): - self.wait() - for x in get_ternary_ruler_sequence(self.num_disks-1): - self.move_disk(x, run_time = 0.5, stay_on_peg = False) - self.wait() - -class Keith(PiCreature): - CONFIG = { - "color" : GREEN_D - } - -def get_binary_tex_mobs(num_list): - result = VGroup() - zero_width = TexMobject("0").get_width() - nudge = zero_width + SMALL_BUFF - for num in num_list: - bin_string = bin(num)[2:]#Strip off the "0b" prefix - bits = VGroup(*list(map(TexMobject, bin_string))) - for n, bit in enumerate(bits): - bit.shift(n*nudge*RIGHT) - bits.move_to(ORIGIN, RIGHT) - result.add(bits) - return result - -def get_base_b_tex_mob(number, base, n_digits): - assert(number < base**n_digits) - curr_digit = n_digits - 1 - zero = TexMobject("0") - zero_width = zero.get_width() - zero_height = zero.get_height() - result = VGroup() - for place in range(n_digits): - remainder = number%base - digit_mob = TexMobject(str(remainder)) - digit_mob.set_height(zero_height) - digit_mob.shift(place*(zero_width+SMALL_BUFF)*LEFT) - result.add(digit_mob) - number = (number - remainder)/base - return result.center() - -def get_binary_tex_mob(number, n_bits = 4): - return get_base_b_tex_mob(number, 2, n_bits) - -def get_ternary_tex_mob(number, n_trits = 4): - return get_base_b_tex_mob(number, 3, n_trits) - - -#################### - -class IntroduceKeith(Scene): - def construct(self): - morty = Mortimer(mode = "happy") - keith = Keith(mode = "dance_kick") - keith_image = ImageMobject("keith_schwarz", invert = False) - # keith_image = Rectangle() - keith_image.set_height(FRAME_HEIGHT - 2) - keith_image.next_to(ORIGIN, LEFT) - keith.move_to(keith_image, DOWN+RIGHT) - morty.next_to(keith, buff = LARGE_BUFF, aligned_edge = DOWN) - morty.make_eye_contact(keith) - randy = Randolph().next_to(keith, LEFT, LARGE_BUFF, aligned_edge = DOWN) - randy.shift_onto_screen() - - bubble = keith.get_bubble(SpeechBubble, width = 7) - bubble.write("01101011 $\\Rightarrow$ Towers of Hanoi") - zero_width = bubble.content[0].get_width() - one_width = bubble.content[1].get_width() - for mob in bubble.content[:8]: - if abs(mob.get_width() - zero_width) < 0.01: - mob.set_color(GREEN) - else: - mob.set_color(YELLOW) - - bubble.resize_to_content() - bubble.pin_to(keith) - VGroup(bubble, bubble.content).shift(DOWN) - - randy.bubble = randy.get_bubble(SpeechBubble, height = 3) - randy.bubble.write("Wait, what's \\\\ Towers of Hanoi?") - - title = TextMobject("Keith Schwarz (Computer scientist)") - title.to_edge(UP) - - self.add(keith_image, morty) - self.play(Write(title)) - self.play(FadeIn(keith, run_time = 2)) - self.play(FadeOut(keith_image), Animation(keith)) - self.play(Blink(morty)) - self.play( - keith.change_mode, "speaking", - keith.set_height, morty.get_height(), - keith.next_to, morty, LEFT, LARGE_BUFF, - run_time = 1.5 - ) - self.play( - ShowCreation(bubble), - Write(bubble.content) - ) - self.play( - morty.change_mode, "pondering", - morty.look_at, bubble - ) - self.play(Blink(keith)) - self.wait() - original_content = bubble.content - bubble.write("I'm usually meh \\\\ on puzzles") - self.play( - keith.change_mode, "hesitant", - Transform(original_content, bubble.content), - ) - self.play( - morty.change_mode, "happy", - morty.look_at, keith.eyes - ) - self.play(Blink(keith)) - bubble.write("But \\emph{analyzing} puzzles!") - VGroup(*bubble.content[3:12]).set_color(YELLOW) - self.play( - keith.change_mode, "hooray", - Transform(original_content, bubble.content) - ) - self.play(Blink(morty)) - self.wait() - self.play(FadeIn(randy)) - self.play( - randy.change_mode, "confused", - randy.look_at, keith.eyes, - keith.change_mode, "plain", - keith.look_at, randy.eyes, - morty.change_mode, "plain", - morty.look_at, randy.eyes, - FadeOut(bubble), - FadeOut(original_content), - ShowCreation(randy.bubble), - Write(randy.bubble.content) - ) - self.play(Blink(keith)) - self.play( - keith.change_mode, "hooray", - keith.look_at, randy.eyes - ) - self.wait() - -class IntroduceTowersOfHanoi(TowersOfHanoiScene): - def construct(self): - self.clear() - self.add_title() - self.show_setup() - self.note_disk_labels() - self.show_more_disk_possibility() - self.move_full_tower() - self.move_single_disk() - self.cannot_move_disk_onto_smaller_disk() - - def add_title(self): - title = TextMobject("Towers of Hanoi") - title.to_edge(UP) - self.add(title) - self.title = title - - def show_setup(self): - self.pegs.save_state() - bottom = self.pegs.get_bottom() - self.pegs.stretch_to_fit_height(0) - self.pegs.move_to(bottom) - self.play( - ApplyMethod( - self.pegs.restore, - lag_ratio = 0.5, - run_time = 2 - ), - Write(self.peg_labels) - ) - self.wait() - self.bring_in_disks() - self.wait() - - def bring_in_disks(self): - peg = self.pegs[0] - disk_groups = VGroup() - for disk in self.disks: - top = Circle(radius = disk.get_width()/2) - inner = Circle(radius = self.peg_width/2) - inner.flip() - top.add_subpath(inner.points) - top.set_stroke(width = 0) - top.set_fill(disk.get_color()) - top.rotate(np.pi/2, RIGHT) - top.move_to(disk, UP) - bottom = top.copy() - bottom.move_to(disk, DOWN) - group = VGroup(disk, top, bottom) - group.truly_original_state = group.copy() - group.next_to(peg, UP, 0) - group.rotate_in_place(-np.pi/24, RIGHT) - group.save_state() - group.rotate_in_place(-11*np.pi/24, RIGHT) - disk.set_fill(opacity = 0) - disk_groups.add(group) - disk_groups.arrange() - disk_groups.next_to(self.peg_labels, DOWN) - - self.play(FadeIn( - disk_groups, - run_time = 2, - lag_ratio = 0.5 - )) - for group in reversed(list(disk_groups)): - self.play(group.restore) - self.play(Transform(group, group.truly_original_state)) - self.remove(disk_groups) - self.add(self.disks) - - def note_disk_labels(self): - labels = [disk.label for disk in self.disks] - last = VGroup().save_state() - for label in labels: - label.save_state() - self.play( - label.scale_in_place, 2, - label.set_color, YELLOW, - last.restore, - run_time = 0.5 - ) - last = label - self.play(last.restore) - self.wait() - - def show_more_disk_possibility(self): - original_num_disks = self.num_disks - original_disk_height = self.disk_height - original_disks = self.disks - original_disks_copy = original_disks.copy() - - #Hacky - self.num_disks = 10 - self.disk_height = 0.3 - self.add_disks() - new_disks = self.disks - self.disks = original_disks - self.remove(new_disks) - - self.play(Transform(self.disks, new_disks)) - self.wait() - self.play(Transform(self.disks, original_disks_copy)) - - self.remove(self.disks) - self.disks = original_disks_copy - self.add(self.disks) - self.wait() - - self.num_disks = original_num_disks - self.disk_height = original_disk_height - - def move_full_tower(self): - self.move_subtower_to_peg(self.num_disks, 1, run_time = 2) - self.wait() - self.reset_disks(run_time = 1, lag_ratio = 0.5) - self.wait() - - def move_single_disk(self): - for x in 0, 1, 0: - self.move_disk(x) - self.wait() - - def cannot_move_disk_onto_smaller_disk(self): - also_not_allowed = TextMobject("Not allowed") - also_not_allowed.to_edge(UP) - also_not_allowed.set_color(RED) - cross = TexMobject("\\times") - cross.set_fill(RED, opacity = 0.5) - - disk = self.disks[2] - disk.save_state() - self.move_disks_to_peg([2], 2, added_anims = [ - Transform(self.title, also_not_allowed, run_time = 1) - ]) - cross.replace(disk) - self.play(FadeIn(cross)) - self.wait() - self.play( - FadeOut(cross), - FadeOut(self.title), - disk.restore - ) - self.wait() - -class ExampleFirstMoves(TowersOfHanoiScene): - def construct(self): - ruler_sequence = get_ruler_sequence(4) - cross = TexMobject("\\times") - cross.set_fill(RED, 0.7) - - self.wait() - self.play( - self.disks[0].set_fill, YELLOW, - self.disks[0].label.set_color, BLACK - ) - self.wait() - self.move_disk(0) - self.wait() - self.play( - self.disks[1].set_fill, YELLOW_D, - self.disks[1].label.set_color, BLACK - ) - self.move_disk_to_peg(1, 1) - cross.replace(self.disks[1]) - self.play(FadeIn(cross)) - self.wait() - self.move_disk_to_peg(1, 2, added_anims = [FadeOut(cross)]) - self.wait() - for x in ruler_sequence[2:9]: - self.move_disk(x) - for x in ruler_sequence[9:]: - self.move_disk(x, run_time = 0.5, stay_on_peg = False) - self.wait() - -class KeithShowingBinary(Scene): - def construct(self): - keith = Keith() - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - keith.next_to(morty, LEFT, buff = 2*LARGE_BUFF) - randy = Randolph() - randy.next_to(keith, LEFT, buff = 2*LARGE_BUFF) - randy.bubble = randy.get_bubble(SpeechBubble) - randy.bubble.set_fill(BLACK, opacity = 1) - randy.bubble.write("Hold on...how does \\\\ binary work again?") - - binary_tex_mobs = get_binary_tex_mobs(list(range(16))) - binary_tex_mobs.shift(keith.get_corner(UP+LEFT)) - binary_tex_mobs.shift(0.5*(UP+RIGHT)) - bits_list = binary_tex_mobs.split() - bits = bits_list.pop(0) - - def get_bit_flip(): - return Transform( - bits, bits_list.pop(0), - rate_func = squish_rate_func(smooth, 0, 0.7) - ) - - self.play( - keith.change_mode, "wave_1", - keith.look_at, bits, - morty.look_at, bits, - Write(bits) - ) - for x in range(2): - self.play(get_bit_flip()) - self.play( - morty.change_mode, "pondering", - morty.look_at, bits, - get_bit_flip() - ) - while bits_list: - added_anims = [] - if random.random() < 0.2: - if random.random() < 0.5: - added_anims.append(Blink(keith)) - else: - added_anims.append(Blink(morty)) - self.play(get_bit_flip(), *added_anims) - self.wait() - self.play( - FadeIn(randy), - morty.change_mode, "plain", - morty.look_at, randy.eyes, - keith.change_mode, "plain", - keith.look_at, randy.eyes, - ) - self.play( - randy.change_mode, "confused", - ShowCreation(randy.bubble), - Write(randy.bubble.content) - ) - self.play(Blink(randy)) - self.wait() - self.play(morty.change_mode, "hooray") - self.play(Blink(morty)) - self.wait() - -class FocusOnRhythm(Scene): - def construct(self): - title = TextMobject("Focus on rhythm") - title.scale(1.5) - letters = list(reversed(title[-6:])) - self.play(Write(title, run_time = 1)) - sequence = get_ruler_sequence(5) - for x in sequence: - movers = VGroup(*letters[:x+1]) - self.play( - movers.shift, 0.2*DOWN, - rate_func = there_and_back, - run_time = 0.25 - ) - -class IntroduceBase10(Scene): - def construct(self): - self.expand_example_number() - self.list_digits() - - def expand_example_number(self): - title = TextMobject("``Base 10''") - title.to_edge(UP) - number = TexMobject("137") - number.next_to(title, DOWN) - number.shift(2*LEFT) - - colors = [RED, MAROON_B, YELLOW] - expansion = TexMobject( - "1(100) + ", - "3(10) + ", - "7" - ) - expansion.next_to(number, DOWN, buff = LARGE_BUFF, aligned_edge = RIGHT) - arrows = VGroup() - number.generate_target() - - for color, digit, term in zip(colors, number.target, expansion): - digit.set_color(color) - term.set_color(color) - arrow = Arrow(digit, term.get_top()) - arrow.set_color(color) - arrows.add(arrow) - expansion.save_state() - for digit, term in zip(number, expansion): - Transform(term, digit).update(1) - - self.play( - MoveToTarget(number), - ShowCreation(arrows), - ApplyMethod( - expansion.restore, lag_ratio = 0.5), - run_time = 2 - ) - self.play(Write(title)) - self.wait() - self.title = title - - def list_digits(self): - digits = TextMobject(""" - 0, 1, 2, 3, 4, - 5, 6, 7, 8, 9 - """) - digits.next_to(self.title, DOWN, buff = LARGE_BUFF) - digits.shift(2*RIGHT) - self.play(Write(digits)) - self.wait() - -class RhythmOfDecimalCounting(CountingScene): - CONFIG = { - "ones_configuration_location" : 2*UP+2*RIGHT, - "num_start_location" : DOWN - } - def construct(self): - for x in range(10): - self.increment() - brace = Brace(self.number_mob) - two_digits = brace.get_text("Two digits") - one_brace = Brace(self.number_mob[-1]) - tens_place = one_brace.get_text("Ten's place") - ten_group = self.curr_configurations[1][0] - - self.play( - GrowFromCenter(brace), - Write(two_digits, run_time = 1) - ) - self.wait(2) - self.play( - Transform(brace, one_brace), - Transform(two_digits, tens_place) - ) - self.wait() - ten_group.save_state() - self.play( - ten_group.scale_in_place, 7, - ten_group.shift, 2*(DOWN+LEFT), - ) - self.wait() - self.play( - ten_group.restore, - *list(map(FadeOut, [brace, two_digits])) - ) - - for x in range(89): - self.increment(run_time_per_anim = 0.25) - self.increment(run_time_per_anim = 1) - self.wait() - - hundred_group = self.curr_configurations[2][0] - hundred_group.save_state() - self.play( - hundred_group.scale, 14, - hundred_group.to_corner, DOWN+LEFT - ) - self.wait() - self.play(hundred_group.restore) - self.wait() - groups = [ - VGroup(*pair) - for pair in zip(self.dot_templates, self.curr_configurations) - ] - self.play( - groups[2].to_edge, RIGHT, - MaintainPositionRelativeTo(groups[1], groups[2]), - MaintainPositionRelativeTo(groups[0], groups[2]), - self.number_mob.to_edge, RIGHT, LARGE_BUFF, - FadeOut(self.arrows) - ) - -class DecimalCountingAtHundredsScale(CountingScene): - CONFIG = { - "power_colors" : [RED, GREEN, BLUE, PURPLE_D], - "counting_dot_starting_position" : (FRAME_X_RADIUS+1)*RIGHT + (FRAME_Y_RADIUS-2)*UP, - "ones_configuration_location" : 2*UP+5.7*RIGHT, - "num_start_location" : DOWN + 3*RIGHT - } - def construct(self): - added_zeros = TexMobject("00") - added_zeros.scale(self.num_scale_factor) - added_zeros.next_to(self.number_mob, RIGHT, SMALL_BUFF, aligned_edge = DOWN) - added_zeros.set_color_by_gradient(MAROON_B, YELLOW) - self.add(added_zeros) - self.increment(run_time_per_anim = 0) - - VGroup(self.number_mob, added_zeros).to_edge(RIGHT, buff = LARGE_BUFF) - VGroup(self.dot_templates[0], self.curr_configurations[0]).to_edge(RIGHT) - Transform( - self.arrows[0], - Arrow(self.number_mob, self.dot_templates[0], color = self.power_colors[0]) - ).update(1) - - for x in range(10): - this_range = list(range(8)) if x == 0 else list(range(9)) - for y in this_range: - self.increment(run_time_per_anim = 0.25) - self.increment(run_time_per_anim = 1) - -class IntroduceBinaryCounting(BinaryCountingScene): - CONFIG = { - "ones_configuration_location" : UP+5*RIGHT, - "num_start_location" : DOWN+2*RIGHT - } - def construct(self): - self.introduce_name() - self.initial_counting() - self.show_self_similarity() - - def introduce_name(self): - title = TextMobject("Binary (base 2):", "0, 1") - title.to_edge(UP) - self.add(title) - self.number_mob.set_fill(opacity = 0) - - brace = Brace(title[1], buff = SMALL_BUFF) - bits = TextMobject("bi", "ts", arg_separator = "") - bits.submobjects.insert(1, VectorizedPoint(bits.get_center())) - binary_digits = TextMobject("bi", "nary digi", "ts", arg_separator = "") - for mob in bits, binary_digits: - mob.next_to(brace, DOWN, buff = SMALL_BUFF) - VGroup(brace, bits, binary_digits).set_color(BLUE) - binary_digits[1].set_color(BLUE_E) - self.play( - GrowFromCenter(brace), - Write(bits) - ) - self.wait() - bits.save_state() - self.play(Transform(bits, binary_digits)) - self.wait() - self.play(bits.restore) - self.wait() - - def initial_counting(self): - randy = Randolph().to_corner(DOWN+LEFT) - bubble = randy.get_bubble(ThoughtBubble, height = 3.4, width = 5) - bubble.write( - "Not ten, not ten \\\\", - "\\quad not ten, not ten..." - ) - - self.play(self.number_mob.set_fill, self.power_colors[0], 1) - self.increment() - self.wait() - self.start_dot = self.curr_configurations[0][0] - - ##Up to 10 - self.increment() - brace = Brace(self.number_mob[1]) - twos_place = brace.get_text("Two's place") - self.play( - GrowFromCenter(brace), - Write(twos_place) - ) - self.play( - FadeIn(randy), - ShowCreation(bubble) - ) - self.play( - randy.change_mode, "hesitant", - randy.look_at, self.number_mob, - Write(bubble.content) - ) - self.wait() - curr_content = bubble.content - bubble.write("$1 \\! \\cdot \\! 2+$", "$0$") - bubble.content[0][0].set_color(self.power_colors[1]) - self.play( - Transform(curr_content, bubble.content), - randy.change_mode, "pondering", - randy.look_at, self.number_mob - ) - self.remove(curr_content) - self.add(bubble.content) - - #Up to 11 - zero = bubble.content[-1] - zero.set_color(self.power_colors[0]) - one = TexMobject("1").replace(zero, dim_to_match = 1) - one.set_color(zero.get_color()) - self.play(Blink(randy)) - self.increment(added_anims = [Transform(zero, one)]) - self.wait() - - #Up to 100 - curr_content = bubble.content - bubble.write( - "$1 \\!\\cdot\\! 4 + $", - "$0 \\!\\cdot\\! 2 + $", - "$0$", - ) - colors = reversed(self.power_colors[:3]) - for piece, color in zip(bubble.content.submobjects, colors): - piece[0].set_color(color) - self.increment(added_anims = [Transform(curr_content, bubble.content)]) - four_brace = Brace(self.number_mob[-1]) - fours_place = four_brace.get_text("Four's place") - self.play( - Transform(brace, four_brace), - Transform(twos_place, fours_place), - ) - self.play(Blink(randy)) - self.play(*list(map(FadeOut, [bubble, curr_content]))) - - #Up to 1000 - for x in range(4): - self.increment() - brace.target = Brace(self.number_mob[-1]) - twos_place.target = brace.get_text("Eight's place") - self.play( - randy.change_mode, "happy", - randy.look_at, self.number_mob, - *list(map(MoveToTarget, [brace, twos_place])) - ) - for x in range(8): - self.increment(total_run_time = 1) - self.wait() - for x in range(8): - self.increment(total_run_time = 1.5) - - def show_self_similarity(self): - cover_rect = Rectangle() - cover_rect.set_width(FRAME_WIDTH) - cover_rect.set_height(FRAME_HEIGHT) - cover_rect.set_stroke(width = 0) - cover_rect.set_fill(BLACK, opacity = 0.85) - big_dot = self.curr_configurations[-1][0].copy() - self.play( - FadeIn(cover_rect), - Animation(big_dot) - ) - self.play( - big_dot.center, - big_dot.set_height, FRAME_HEIGHT-2, - big_dot.to_edge, LEFT, - run_time = 5 - ) - -class BinaryCountingAtEveryScale(Scene): - CONFIG = { - "num_bits" : 4, - "show_title" : False, - } - def construct(self): - title = TextMobject("Count to %d (which is %s in binary)"%( - 2**self.num_bits-1, bin(2**self.num_bits-1)[2:] - )) - title.to_edge(UP) - if self.show_title: - self.add(title) - - bit_mobs = [ - get_binary_tex_mob(n, self.num_bits) - for n in range(2**self.num_bits) - ] - curr_bits = bit_mobs[0] - - lower_brace = Brace(VGroup(*curr_bits[1:])) - do_a_thing = lower_brace.get_text("Do a thing") - VGroup(lower_brace, do_a_thing).set_color(YELLOW) - upper_brace = Brace(curr_bits, UP) - roll_over = upper_brace.get_text("Roll over") - VGroup(upper_brace, roll_over).set_color(MAROON_B) - again = TextMobject("again") - again.next_to(do_a_thing, RIGHT, 2*SMALL_BUFF) - again.set_color(YELLOW) - - self.add(curr_bits, lower_brace, do_a_thing) - - def get_run_through(mobs): - return Succession(*[ - Transform( - curr_bits, mob, - rate_func = squish_rate_func(smooth, 0, 0.5) - ) - for mob in mobs - ], run_time = 1) - - for bit_mob in bit_mobs: - curr_bits.align_data(bit_mob) - bit_mob.set_color(YELLOW) - bit_mob[0].set_color(MAROON_B) - self.play(get_run_through(bit_mobs[1:2**(self.num_bits-1)])) - self.play(*list(map(FadeIn, [upper_brace, roll_over]))) - self.play(Transform( - VGroup(*reversed(list(curr_bits))), - VGroup(*reversed(list(bit_mobs[2**(self.num_bits-1)]))), - lag_ratio = 0.5, - )) - self.wait() - self.play( - get_run_through(bit_mobs[2**(self.num_bits-1)+1:]), - Write(again) - ) - self.wait() - -class BinaryCountingAtSmallestScale(BinaryCountingAtEveryScale): - CONFIG = { - "num_bits" : 2, - "show_title" : True, - } - -class BinaryCountingAtMediumScale(BinaryCountingAtEveryScale): - CONFIG = { - "num_bits" : 4, - "show_title" : True, - } - -class BinaryCountingAtLargeScale(BinaryCountingAtEveryScale): - CONFIG = { - "num_bits" : 8, - "show_title" : True, - } - -class IntroduceSolveByCounting(TowersOfHanoiScene): - CONFIG = { - "num_disks" : 4 - } - def construct(self): - self.initialize_bit_mobs() - for disk in self.disks: - disk.original_fill_color = disk.get_color() - braces = [ - Brace(VGroup(*self.curr_bit_mob[:n])) - for n in range(1, self.num_disks+1) - ] - word_list = [ - brace.get_text(text) - for brace, text in zip(braces, [ - "Only flip last bit", - "Roll over once", - "Roll over twice", - "Roll over three times", - ]) - ] - brace = braces[0].copy() - words = word_list[0].copy() - - ##First increment - self.play(self.get_increment_animation()) - self.play( - GrowFromCenter(brace), - Write(words, run_time = 1) - ) - disk = self.disks[0] - last_bit = self.curr_bit_mob[0] - last_bit.save_state() - self.play( - disk.set_fill, YELLOW, - disk[1].set_fill, BLACK, - last_bit.set_fill, YELLOW, - ) - self.wait() - self.move_disk(0, run_time = 2) - self.play( - last_bit.restore, - disk.set_fill, disk.original_fill_color, - self.disks[0][1].set_fill, BLACK - ) - - ##Second increment - self.play( - self.get_increment_animation(), - Transform(words, word_list[1]), - Transform(brace, braces[1]), - ) - disk = self.disks[1] - twos_bit = self.curr_bit_mob[1] - twos_bit.save_state() - self.play( - disk.set_fill, MAROON_B, - disk[1].set_fill, BLACK, - twos_bit.set_fill, MAROON_B, - ) - self.move_disk(1, run_time = 2) - self.wait() - self.move_disk_to_peg(1, 1, stay_on_peg = False) - cross = TexMobject("\\times") - cross.replace(disk) - cross.set_fill(RED, opacity = 0.5) - self.play(FadeIn(cross)) - self.wait() - self.move_disk_to_peg( - 1, 2, stay_on_peg = False, - added_anims = [FadeOut(cross)] - ) - self.play( - disk.set_fill, disk.original_fill_color, - disk[1].set_fill, BLACK, - twos_bit.restore, - Transform(brace, braces[0]), - Transform(words, word_list[0]), - ) - self.move_disk( - 0, - added_anims = [self.get_increment_animation()], - run_time = 2 - ) - self.wait() - - ##Fourth increment - self.play( - Transform(brace, braces[2]), - Transform(words, word_list[2]), - ) - self.play(self.get_increment_animation()) - disk = self.disks[2] - fours_bit = self.curr_bit_mob[2] - fours_bit.save_state() - self.play( - disk.set_fill, RED, - disk[1].set_fill, BLACK, - fours_bit.set_fill, RED - ) - self.move_disk(2, run_time = 2) - self.play( - disk.set_fill, disk.original_fill_color, - disk[1].set_fill, BLACK, - fours_bit.restore, - FadeOut(brace), - FadeOut(words) - ) - self.wait() - for disk_index in 0, 1, 0: - self.play(self.get_increment_animation()) - self.move_disk(disk_index) - self.wait() - - ##Eighth incremement - brace = braces[3] - words = word_list[3] - self.play( - self.get_increment_animation(), - GrowFromCenter(brace), - Write(words, run_time = 1) - ) - disk = self.disks[3] - eights_bit = self.curr_bit_mob[3] - eights_bit.save_state() - self.play( - disk.set_fill, GREEN, - disk[1].set_fill, BLACK, - eights_bit.set_fill, GREEN - ) - self.move_disk(3, run_time = 2) - self.play( - disk.set_fill, disk.original_fill_color, - disk[1].set_fill, BLACK, - eights_bit.restore, - ) - self.play(*list(map(FadeOut, [brace, words]))) - for disk_index in get_ruler_sequence(2): - self.play(self.get_increment_animation()) - self.move_disk(disk_index, stay_on_peg = False) - self.wait() - - def initialize_bit_mobs(self): - bit_mobs = VGroup(*[ - get_binary_tex_mob(n, self.num_disks) - for n in range(2**(self.num_disks)) - ]) - bit_mobs.scale(2) - self.bit_mobs_iter = it.cycle(bit_mobs) - self.curr_bit_mob = next(self.bit_mobs_iter) - - for bit_mob in bit_mobs: - bit_mob.align_data(self.curr_bit_mob) - for bit, disk in zip(bit_mob, reversed(list(self.disks))): - bit.set_color(disk.get_color()) - bit_mobs.next_to(self.peg_labels, DOWN) - - self.add(self.curr_bit_mob) - - def get_increment_animation(self): - return Succession( - Transform( - self.curr_bit_mob, next(self.bit_mobs_iter), - lag_ratio = 0.5, - path_arc = -np.pi/3 - ), - Animation(self.curr_bit_mob) - ) - -class SolveSixDisksByCounting(IntroduceSolveByCounting): - CONFIG = { - "num_disks" : 6, - "stay_on_peg" : False, - "run_time_per_move" : 0.5, - } - def construct(self): - self.initialize_bit_mobs() - for disk_index in get_ruler_sequence(self.num_disks-1): - self.play( - self.get_increment_animation(), - run_time = self.run_time_per_move, - ) - self.move_disk( - disk_index, - stay_on_peg = self.stay_on_peg, - run_time = self.run_time_per_move, - ) - self.wait() - -class RecursionTime(Scene): - def construct(self): - keith = Keith().shift(2*DOWN+3*LEFT) - morty = Mortimer().shift(2*DOWN+3*RIGHT) - keith.make_eye_contact(morty) - - keith_kick = keith.copy().change_mode("dance_kick") - keith_kick.scale_in_place(1.3) - keith_kick.shift(0.5*LEFT) - keith_kick.look_at(morty.eyes) - keith_hooray = keith.copy().change_mode("hooray") - - self.add(keith, morty) - - bubble = keith.get_bubble(SpeechBubble, height = 2) - bubble.write("Recursion time!!!") - VGroup(bubble, bubble.content).shift(UP) - - self.play( - Transform(keith, keith_kick), - morty.change_mode, "happy", - ShowCreation(bubble), - Write(bubble.content, run_time = 1) - ) - self.play( - morty.change_mode, "hooray", - Transform(keith, keith_hooray), - bubble.content.set_color_by_gradient, BLUE_A, BLUE_E - ) - self.play(Blink(morty)) - self.wait() - -class RecursiveSolution(TowersOfHanoiScene): - CONFIG = { - "num_disks" : 4, - "middle_peg_bottom" : 2*DOWN, - } - def construct(self): - # VGroup(*self.get_mobjects()).shift(1.5*DOWN) - big_disk = self.disks[-1] - self.eyes = Eyes(big_disk) - title = TextMobject("Move 4-tower") - sub_steps = TextMobject( - "Move 3-tower,", - "Move disk 3,", - "Move 3-tower", - ) - sub_steps[1].set_color(GREEN) - sub_step_brace = Brace(sub_steps, UP) - sub_sub_steps = TextMobject( - "Move 2-tower,", - "Move disk 2,", - "Move 2-tower", - ) - sub_sub_steps[1].set_color(RED) - sub_sub_steps_brace = Brace(sub_sub_steps, UP) - steps = VGroup( - title, sub_step_brace, sub_steps, - sub_sub_steps_brace, sub_sub_steps - ) - steps.arrange(DOWN) - steps.scale(0.7) - steps.to_edge(UP) - VGroup(sub_sub_steps_brace, sub_sub_steps).next_to(sub_steps[-1], DOWN) - - self.add(title) - - ##Big disk is frustrated - self.play( - FadeIn(self.eyes), - big_disk.set_fill, GREEN, - big_disk.label.set_fill, BLACK, - ) - big_disk.add(self.eyes) - self.blink() - self.wait() - self.change_mode("angry") - for x in range(2): - self.wait() - self.shake(big_disk) - self.blink() - self.wait() - self.change_mode("plain") - self.look_at(self.peg_labels[2]) - self.look_at(self.disks[0]) - self.blink() - - #Subtower move - self.move_subtower_to_peg(3, 1, run_time = 2, added_anims = [ - self.eyes.look_at_anim(self.pegs[1]), - FadeIn(sub_step_brace), - Write(sub_steps[0], run_time = 1) - ]) - self.wait() - self.move_disk_to_peg(0, 0, run_time = 2, added_anims = [ - self.eyes.look_at_anim(self.pegs[0].get_top()) - ]) - self.shake(big_disk) - self.move_disk_to_peg(0, 2, run_time = 2, added_anims = [ - self.eyes.look_at_anim(self.pegs[2].get_bottom()) - ]) - self.change_mode("angry") - self.move_disk_to_peg(0, 1, run_time = 2, added_anims = [ - self.eyes.look_at_anim(self.disks[1].get_top()) - ]) - self.blink() - - #Final moves for big case - self.move_disk(3, run_time = 2, added_anims = [ - Write(sub_steps[1]) - ]) - self.look_at(self.disks[1]) - self.blink() - bubble = SpeechBubble() - bubble.write("I'm set!") - bubble.resize_to_content() - bubble.pin_to(big_disk) - bubble.add_content(bubble.content) - bubble.set_fill(BLACK, opacity = 0.7) - self.play( - ShowCreation(bubble), - Write(bubble.content) - ) - self.wait() - self.blink() - self.play(*list(map(FadeOut, [bubble, bubble.content]))) - big_disk.remove(self.eyes) - self.move_subtower_to_peg(3, 2, run_time = 2, added_anims = [ - self.eyes.look_at_anim(self.pegs[2].get_top()), - Write(sub_steps[2]) - ]) - self.play(FadeOut(self.eyes)) - self.wait() - - #Highlight subproblem - self.play( - VGroup(*self.disks[:3]).move_to, self.pegs[1], DOWN - ) - self.disk_tracker = [set([]), set([0, 1, 2]), set([3])] - arc = Arc(-5*np.pi/6, start_angle = 5*np.pi/6) - arc.add_tip() - arc.set_color(YELLOW) - arc.set_width( - VGroup(*self.pegs[1:]).get_width()*0.8 - ) - arc.next_to(self.disks[0], UP+RIGHT, buff = SMALL_BUFF) - q_mark = TextMobject("?") - q_mark.next_to(arc, UP) - self.play( - ShowCreation(arc), - Write(q_mark), - sub_steps[-1].set_color, YELLOW - ) - self.wait() - self.play( - GrowFromCenter(sub_sub_steps_brace), - *list(map(FadeOut, [arc, q_mark])) - ) - - #Disk 2 frustration - big_disk = self.disks[2] - self.eyes.move_to(big_disk.get_top(), DOWN) - self.play( - FadeIn(self.eyes), - big_disk.set_fill, RED, - big_disk.label.set_fill, BLACK - ) - big_disk.add(self.eyes) - self.change_mode("sad") - self.look_at(self.pegs[1].get_top()) - self.shake(big_disk) - self.blink() - - #Move sub-sub-tower - self.move_subtower_to_peg(2, 0, run_time = 2, added_anims = [ - self.eyes.look_at_anim(self.pegs[0].get_bottom()), - Write(sub_sub_steps[0]) - ]) - self.blink() - self.move_disk_to_peg(2, 2, run_time = 2, added_anims = [ - Write(sub_sub_steps[1]) - ]) - self.look_at(self.disks[0]) - big_disk.remove(self.eyes) - self.move_subtower_to_peg(2, 2, run_time = 2, added_anims = [ - self.eyes.look_at_anim(self.pegs[2].get_top()), - Write(sub_sub_steps[2]) - ]) - self.blink() - self.look_at(self.disks[-1]) - - #Move eyes - self.play(FadeOut(self.eyes)) - self.eyes.move_to(self.disks[1].get_top(), DOWN) - self.play(FadeIn(self.eyes)) - self.blink() - self.play(FadeOut(self.eyes)) - self.eyes.move_to(self.disks[3].get_top(), DOWN) - self.play(FadeIn(self.eyes)) - - #Show process one last time - big_disk = self.disks[3] - big_disk.add(self.eyes) - self.move_subtower_to_peg(3, 1, run_time = 2, added_anims = [ - self.eyes.look_at_anim(self.pegs[0]) - ]) - self.move_disk_to_peg(3, 0, run_time = 2) - big_disk.remove(self.eyes) - self.move_subtower_to_peg(3, 0, run_time = 2, added_anims = [ - self.eyes.look_at_anim(self.pegs[0].get_top()) - ]) - self.blink() - - def shake(self, mobject, direction = UP, added_anims = []): - self.play( - mobject.shift, 0.2*direction, rate_func = wiggle, - *added_anims - ) - - def blink(self): - self.play(self.eyes.blink_anim()) - - def look_at(self, point_or_mobject): - self.play(self.eyes.look_at_anim(point_or_mobject)) - - def change_mode(self, mode): - self.play(self.eyes.change_mode_anim(mode)) - -class KeithSaysBigToSmall(Scene): - def construct(self): - keith = Keith() - keith.shift(2.5*DOWN + 3*LEFT) - bubble = keith.get_bubble(SpeechBubble, height = 4.5) - bubble.write(""" - Big problem - $\\Downarrow$ - Smaller problem - """) - - self.add(keith) - self.play(Blink(keith)) - self.play( - keith.change_mode, "speaking", - ShowCreation(bubble), - Write(bubble.content) - ) - self.wait() - self.play(Blink(keith)) - self.wait() - -class CodeThisUp(Scene): - def construct(self): - keith = Keith() - keith.shift(2*DOWN+3*LEFT) - morty = Mortimer() - morty.shift(2*DOWN+3*RIGHT) - keith.make_eye_contact(morty) - point = 2*UP+3*RIGHT - bubble = keith.get_bubble(SpeechBubble, width = 4.5, height = 3) - bubble.write("This is the \\\\ most efficient") - self.add(morty, keith) - - self.play( - keith.change_mode, "speaking", - keith.look_at, point - ) - self.play( - morty.change_mode, "pondering", - morty.look_at, point - ) - self.play(Blink(keith)) - self.wait(2) - self.play(Blink(morty)) - self.wait() - self.play( - keith.change_mode, "hooray", - keith.look_at, morty.eyes - ) - self.play(Blink(keith)) - self.wait() - self.play( - keith.change_mode, "speaking", - keith.look_at, morty.eyes, - ShowCreation(bubble), - Write(bubble.content), - morty.change_mode, "happy", - morty.look_at, keith.eyes, - ) - self.wait() - self.play(Blink(morty)) - self.wait() - -class HanoiSolutionCode(Scene): - def construct(self): - pass - -class NoRoomForInefficiency(Scene): - def construct(self): - morty = Mortimer().flip() - morty.shift(2.5*DOWN+3*LEFT) - bubble = morty.get_bubble(SpeechBubble, width = 4) - bubble.write("No room for \\\\ inefficiency") - VGroup(morty, bubble, bubble.content).to_corner(DOWN+RIGHT) - - self.add(morty) - self.play( - morty.change_mode, "speaking", - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(Blink(morty)) - self.wait() - -class WhyDoesBinaryAchieveThis(Scene): - def construct(self): - keith = Keith() - keith.shift(2*DOWN+3*LEFT) - morty = Mortimer() - morty.shift(2*DOWN+3*RIGHT) - keith.make_eye_contact(morty) - bubble = morty.get_bubble(SpeechBubble, width = 5, height = 3) - bubble.write(""" - Why does counting - in binary work? - """) - self.add(morty, keith) - - self.play( - morty.change_mode, "confused", - morty.look_at, keith.eyes, - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(keith.change_mode, "happy") - self.wait() - self.play(Blink(morty)) - self.wait() - -class BothAreSelfSimilar(Scene): - def construct(self): - morty = Mortimer().flip() - morty.shift(2.5*DOWN+3*LEFT) - bubble = morty.get_bubble(SpeechBubble) - bubble.write("Both are self-similar") - - self.add(morty) - self.play( - morty.change_mode, "hooray", - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(Blink(morty)) - self.wait() - -class LargeScaleHanoiDecomposition(TowersOfHanoiScene): - CONFIG = { - "num_disks" : 8, - "peg_height" : 3.5, - "middle_peg_bottom" : 2*DOWN, - "disk_max_width" : 4, - } - def construct(self): - self.move_subtower_to_peg(7, 1, stay_on_peg = False) - self.wait() - self.move_disk(7, stay_on_peg = False) - self.wait() - self.move_subtower_to_peg(7, 2, stay_on_peg = False) - self.wait() - -class SolveTwoDisksByCounting(SolveSixDisksByCounting): - CONFIG = { - "num_disks" : 2, - "stay_on_peg" : False, - "run_time_per_move" : 1, - "disk_max_width" : 1.5, - } - def construct(self): - self.initialize_bit_mobs() - for disk_index in 0, 1, 0: - self.play(self.get_increment_animation()) - self.move_disk( - disk_index, - stay_on_peg = False, - ) - self.wait() - -class ShowFourDiskFourBitsParallel(IntroduceSolveByCounting): - CONFIG = { - "num_disks" : 4, - "subtask_run_time" : 1, - } - def construct(self): - self.initialize_bit_mobs() - self.counting_subtask() - self.wait() - self.disk_subtask() - self.wait() - self.play(self.get_increment_animation()) - self.move_disk( - self.num_disks-1, - stay_on_peg = False, - ) - self.wait() - self.counting_subtask() - self.wait() - self.disk_subtask() - self.wait() - - def disk_subtask(self): - sequence = get_ruler_sequence(self.num_disks-2) - run_time = float(self.subtask_run_time)/len(sequence) - for disk_index in get_ruler_sequence(self.num_disks-2): - self.move_disk( - disk_index, - run_time = run_time, - stay_on_peg = False, - ) - # curr_peg = self.disk_index_to_peg_index(0) - # self.move_subtower_to_peg(self.num_disks-1, curr_peg+1) - - def counting_subtask(self): - num_tasks = 2**(self.num_disks-1)-1 - run_time = float(self.subtask_run_time)/num_tasks - # for x in range(num_tasks): - # self.play( - # self.get_increment_animation(), - # run_time = run_time - # ) - self.play( - Succession(*[ - self.get_increment_animation() - for x in range(num_tasks) - ]), - rate_func=linear, - run_time = self.subtask_run_time - ) - - def get_increment_animation(self): - return Transform( - self.curr_bit_mob, next(self.bit_mobs_iter), - path_arc = -np.pi/3, - ) - -class ShowThreeDiskThreeBitsParallel(ShowFourDiskFourBitsParallel): - CONFIG = { - "num_disks" : 3, - "subtask_run_time" : 1 - } - -class ShowFiveDiskFiveBitsParallel(ShowFourDiskFourBitsParallel): - CONFIG = { - "num_disks" : 5, - "subtask_run_time" : 2 - } - -class ShowSixDiskSixBitsParallel(ShowFourDiskFourBitsParallel): - CONFIG = { - "num_disks" : 6, - "subtask_run_time" : 2 - } - -class CoolRight(Scene): - def construct(self): - morty = Mortimer() - morty.shift(2*DOWN) - bubble = SpeechBubble() - bubble.write("Cool! right?") - bubble.resize_to_content() - bubble.pin_to(morty) - - self.play( - morty.change_mode, "surprised", - morty.look, OUT, - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(Blink(morty)) - self.wait() - curr_content = bubble.content - bubble.write("It gets \\\\ better...") - self.play( - Transform(curr_content, bubble.content), - morty.change_mode, "hooray", - morty.look, OUT - ) - self.wait() - self.play(Blink(morty)) - self.wait() - -############ Part 2 ############ - -class MentionLastVideo(Scene): - def construct(self): - keith = Keith() - keith.shift(2*DOWN+3*LEFT) - morty = Mortimer() - morty.shift(2*DOWN+3*RIGHT) - keith.make_eye_contact(morty) - point = 2*UP - - name = TextMobject(""" - Keith Schwarz - (Computer Scientist) - """) - name.to_corner(UP+LEFT) - arrow = Arrow(name.get_bottom(), keith.get_top()) - - self.add(morty, keith) - self.play( - keith.change_mode, "raise_right_hand", - keith.look_at, point, - morty.change_mode, "pondering", - morty.look_at, point - ) - self.play(Blink(keith)) - self.play(Write(name)) - self.play(ShowCreation(arrow)) - self.play(Blink(morty)) - self.wait(2) - self.play( - morty.change_mode, "confused", - morty.look_at, point - ) - self.play(Blink(keith)) - self.wait(2) - self.play( - morty.change_mode, "surprised" - ) - self.wait() - -class IntroduceConstrainedTowersOfHanoi(ConstrainedTowersOfHanoiScene): - CONFIG = { - "middle_peg_bottom" : 2*DOWN, - } - def construct(self): - title = TextMobject("Constrained", "Towers of Hanoi") - title.set_color_by_tex("Constrained", YELLOW) - title.to_edge(UP) - - self.play(Write(title)) - self.add_arcs() - self.disks.save_state() - for index in 0, 0, 1, 0: - self.move_disk(index) - self.wait() - self.wait() - - self.play(self.disks.restore) - self.disk_tracker = [set(range(self.num_disks)), set([]), set([])] - self.wait() - self.move_disk_to_peg(0, 1) - self.move_disk_to_peg(1, 2) - self.play(ShowCreation(self.big_curved_arrow)) - cross = TexMobject("\\times") - cross.scale(2) - cross.set_fill(RED) - cross.move_to(self.big_curved_arrow.get_top()) - big_cross = cross.copy() - big_cross.replace(self.disks[1]) - big_cross.set_fill(opacity = 0.5) - self.play(FadeIn(cross)) - self.play(FadeIn(big_cross)) - self.wait() - - - def add_arcs(self): - arc = Arc(start_angle = np.pi/6, angle = 2*np.pi/3) - curved_arrow1 = VGroup(arc, arc.copy().flip()) - curved_arrow2 = curved_arrow1.copy() - curved_arrows = [curved_arrow1, curved_arrow2] - for curved_arrow in curved_arrows: - for arc in curved_arrow: - arc.add_tip(tip_length = 0.15) - arc.set_color(YELLOW) - peg_sets = (self.pegs[:2], self.pegs[1:]) - for curved_arrow, pegs in zip(curved_arrows, peg_sets): - peg_group = VGroup(*pegs) - curved_arrow.set_width(0.7*peg_group.get_width()) - curved_arrow.next_to(peg_group, UP) - - self.play(ShowCreation(curved_arrow1)) - self.play(ShowCreation(curved_arrow2)) - self.wait() - - big_curved_arrow = Arc(start_angle = 5*np.pi/6, angle = -2*np.pi/3) - big_curved_arrow.set_width(0.9*self.pegs.get_width()) - big_curved_arrow.next_to(self.pegs, UP) - big_curved_arrow.add_tip(tip_length = 0.4) - big_curved_arrow.set_color(WHITE) - self.big_curved_arrow = big_curved_arrow - -class StillRecruse(Scene): - def construct(self): - keith = Keith() - keith.shift(2*DOWN+3*LEFT) - morty = Mortimer() - morty.shift(2*DOWN+3*RIGHT) - keith.make_eye_contact(morty) - point = 2*UP+3*RIGHT - bubble = keith.get_bubble(SpeechBubble, width = 4.5, height = 3) - bubble.write("You can still\\\\ use recursion") - self.add(morty, keith) - - self.play( - keith.change_mode, "speaking", - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(morty.change_mode, "hooray") - self.play(Blink(keith)) - self.wait() - self.play(Blink(morty)) - self.wait() - -class RecursiveSolutionToConstrained(RecursiveSolution): - CONFIG = { - "middle_peg_bottom" : 2*DOWN, - "num_disks" : 4, - } - def construct(self): - big_disk = self.disks[-1] - self.eyes = Eyes(big_disk) - - #Define steps breakdown text - title = TextMobject("Move 4-tower") - subdivisions = [ - TextMobject( - "\\tiny Move %d-tower,"%d, - "Move disk %d,"%d, - "\\, Move %d-tower, \\,"%d, - "Move disk %d,"%d, - "Move %d-tower"%d, - ).set_color_by_tex("Move disk %d,"%d, color) - for d, color in [(3, GREEN), (2, RED), (1, BLUE_C)] - ] - sub_steps, sub_sub_steps = subdivisions[:2] - for steps in subdivisions: - steps.set_width(FRAME_WIDTH-1) - subdivisions.append( - TextMobject("\\tiny Move disk 0, Move disk 0").set_color(BLUE) - ) - braces = [ - Brace(steps, UP) - for steps in subdivisions - ] - sub_steps_brace, sub_sub_steps_brace = braces[:2] - steps = VGroup(title, *it.chain(*list(zip( - braces, subdivisions - )))) - steps.arrange(DOWN) - steps.to_edge(UP) - - steps_to_fade = VGroup( - title, sub_steps_brace, - *list(sub_steps[:2]) + list(sub_steps[3:]) - ) - self.add(title) - - #Initially move big disk - self.play( - FadeIn(self.eyes), - big_disk.set_fill, GREEN, - big_disk.label.set_fill, BLACK - ) - big_disk.add(self.eyes) - big_disk.save_state() - self.blink() - self.look_at(self.pegs[2]) - self.move_disk_to_peg(self.num_disks-1, 2, stay_on_peg = False) - self.look_at(self.pegs[0]) - self.blink() - self.play(big_disk.restore, path_arc = np.pi/3) - self.disk_tracker = [set(range(self.num_disks)), set([]), set([])] - self.look_at(self.pegs[0].get_top()) - self.change_mode("angry") - self.shake(big_disk) - self.wait() - - #Talk about tower blocking - tower = VGroup(*self.disks[:self.num_disks-1]) - blocking = TextMobject("Still\\\\", "Blocking") - blocking.set_color(RED) - blocking.to_edge(LEFT) - blocking.shift(2*UP) - arrow = Arrow(blocking.get_bottom(), tower.get_top(), buff = SMALL_BUFF) - new_arrow = Arrow(blocking.get_bottom(), self.pegs[1], buff = SMALL_BUFF) - VGroup(arrow, new_arrow).set_color(RED) - - self.play( - Write(blocking[1]), - ShowCreation(arrow) - ) - self.shake(tower, RIGHT, added_anims = [Animation(big_disk)]) - self.blink() - self.shake(big_disk) - self.wait() - self.move_subtower_to_peg(self.num_disks-1, 1, added_anims = [ - Transform(arrow, new_arrow), - self.eyes.look_at_anim(self.pegs[1]) - ]) - self.play(Write(blocking[0])) - self.shake(big_disk, RIGHT) - self.wait() - self.blink() - self.wait() - self.play(FadeIn(sub_steps_brace)) - self.move_subtower_to_peg(self.num_disks-1, 2, added_anims = [ - FadeOut(blocking), - FadeOut(arrow), - self.eyes.change_mode_anim("plain", thing_to_look_at = self.pegs[2]), - Write(sub_steps[0], run_time = 1), - ]) - self.blink() - - #Proceed through actual process - self.move_disk_to_peg(self.num_disks-1, 1, added_anims = [ - Write(sub_steps[1], run_time = 1), - ]) - self.wait() - self.move_subtower_to_peg(self.num_disks-1, 0, added_anims = [ - self.eyes.look_at_anim(self.pegs[0]), - Write(sub_steps[2], run_time = 1), - ]) - self.blink() - self.move_disk_to_peg(self.num_disks-1, 2, added_anims = [ - Write(sub_steps[3], run_time = 1), - ]) - self.wait() - big_disk.remove(self.eyes) - self.move_subtower_to_peg(self.num_disks-1, 2, added_anims = [ - self.eyes.look_at_anim(self.pegs[2].get_top()), - Write(sub_steps[4], run_time = 1), - ]) - self.blink() - self.play(FadeOut(self.eyes)) - - #Ask about subproblem - sub_sub_steps_brace.set_color(WHITE) - self.move_subtower_to_peg(self.num_disks-1, 0, added_anims = [ - steps_to_fade.fade, 0.7, - sub_steps[2].set_color, WHITE, - sub_steps[2].scale_in_place, 1.2, - FadeIn(sub_sub_steps_brace) - ]) - num_disks = self.num_disks-1 - big_disk = self.disks[num_disks-1] - self.eyes.move_to(big_disk.get_top(), DOWN) - self.play( - FadeIn(self.eyes), - big_disk.set_fill, RED, - big_disk.label.set_fill, BLACK, - ) - big_disk.add(self.eyes) - self.blink() - - #Solve subproblem - self.move_subtower_to_peg(num_disks-1, 2, added_anims = [ - self.eyes.look_at_anim(self.pegs[2]), - Write(sub_sub_steps[0], run_time = 1) - ]) - self.blink() - self.move_disk_to_peg(num_disks-1, 1, added_anims = [ - Write(sub_sub_steps[1], run_time = 1) - ]) - self.wait() - self.move_subtower_to_peg(num_disks-1, 0, added_anims = [ - self.eyes.look_at_anim(self.pegs[0]), - Write(sub_sub_steps[2], run_time = 1) - ]) - self.blink() - self.move_disk_to_peg(num_disks-1, 2, added_anims = [ - Write(sub_sub_steps[3], run_time = 1) - ]) - self.wait() - big_disk.remove(self.eyes) - self.move_subtower_to_peg(num_disks-1, 2, added_anims = [ - self.eyes.look_at_anim(self.pegs[2].get_top()), - Write(sub_sub_steps[4], run_time = 1) - ]) - self.wait() - - #Show smallest subdivisions - smaller_subdivision = VGroup( - *list(subdivisions[2:]) + \ - list(braces[2:]) - ) - last_subdivisions = [VGroup(braces[-1], subdivisions[-1])] - for vect in LEFT, RIGHT: - group = last_subdivisions[0].copy() - group.to_edge(vect) - steps.add(group) - smaller_subdivision.add(group) - last_subdivisions.append(group) - smaller_subdivision.set_fill(opacity = 0) - self.play( - steps.shift, - (FRAME_Y_RADIUS-sub_sub_steps.get_top()[1]-MED_SMALL_BUFF)*UP, - self.eyes.look_at_anim(steps) - ) - self.play(ApplyMethod( - VGroup(VGroup(braces[-2], subdivisions[-2])).set_fill, None, 1, - run_time = 3, - lag_ratio = 0.5, - )) - self.blink() - for mob in last_subdivisions: - self.play( - ApplyMethod(mob.set_fill, None, 1), - self.eyes.look_at_anim(mob) - ) - self.blink() - self.play(FadeOut(self.eyes)) - self.wait() - - #final movements - movements = [ - (0, 1), - (0, 0), - (1, 1), - (0, 1), - (0, 2), - (1, 0), - (0, 1), - (0, 0), - ] - for disk_index, peg_index in movements: - self.move_disk_to_peg( - disk_index, peg_index, - stay_on_peg = False - ) - self.wait() - -class SimpleConstrainedBreakdown(TowersOfHanoiScene): - CONFIG = { - "num_disks" : 4 - } - def construct(self): - self.move_subtower_to_peg(self.num_disks-1, 2) - self.wait() - self.move_disk(self.num_disks-1) - self.wait() - self.move_subtower_to_peg(self.num_disks-1, 0) - self.wait() - self.move_disk(self.num_disks-1) - self.wait() - self.move_subtower_to_peg(self.num_disks-1, 2) - self.wait() - -class SolveConstrainedByCounting(ConstrainedTowersOfHanoiScene): - CONFIG = { - "num_disks" : 5, - "ternary_mob_scale_factor" : 2, - } - def construct(self): - ternary_mobs = VGroup() - for num in range(3**self.num_disks): - ternary_mob = get_ternary_tex_mob(num, self.num_disks) - ternary_mob.scale(self.ternary_mob_scale_factor) - ternary_mob.set_color_by_gradient(*self.disk_start_and_end_colors) - ternary_mobs.add(ternary_mob) - ternary_mobs.next_to(self.peg_labels, DOWN) - self.ternary_mob_iter = it.cycle(ternary_mobs) - self.curr_ternary_mob = next(self.ternary_mob_iter) - self.add(self.curr_ternary_mob) - - for index in get_ternary_ruler_sequence(self.num_disks-1): - self.move_disk(index, stay_on_peg = False, added_anims = [ - self.increment_animation() - ]) - - def increment_animation(self): - return Succession( - Transform( - self.curr_ternary_mob, next(self.ternary_mob_iter), - lag_ratio = 0.5, - path_arc = np.pi/6, - ), - Animation(self.curr_ternary_mob), - ) - -class CompareNumberSystems(Scene): - def construct(self): - base_ten = TextMobject("Base ten") - base_ten.to_corner(UP+LEFT).shift(RIGHT) - binary = TextMobject("Binary") - binary.to_corner(UP+RIGHT).shift(LEFT) - ternary = TextMobject("Ternary") - ternary.to_edge(UP) - ternary.set_color(YELLOW) - titles = [base_ten, binary, ternary] - - zero_to_nine = TextMobject(""" - 0, 1, 2, 3, 4, - 5, 6, 7, 8, 9 - """) - zero_to_nine.next_to(base_ten, DOWN, buff = LARGE_BUFF) - zero_one = TextMobject("0, 1") - zero_one.next_to(binary, DOWN, buff = LARGE_BUFF) - zero_one_two = TextMobject("0, 1, 2") - zero_one_two.next_to(ternary, DOWN, buff = LARGE_BUFF) - zero_one_two.set_color_by_gradient(BLUE, GREEN) - - symbols = [zero_to_nine, zero_one, zero_one_two] - names = ["Digits", "Bits", "Trits?"] - for mob, text in zip(symbols, names): - mob.brace = Brace(mob) - mob.name = mob.brace.get_text(text) - zero_one_two.name.set_color_by_gradient(BLUE, GREEN) - dots = TexMobject("\\dots") - dots.next_to(zero_one.name, RIGHT, aligned_edge = DOWN, buff = SMALL_BUFF) - - keith = Keith() - keith.shift(2*DOWN+3*LEFT) - keith.look_at(zero_one_two) - morty = Mortimer() - morty.shift(2*DOWN+3*RIGHT) - - for title, symbol in zip(titles, symbols): - self.play(FadeIn(title)) - added_anims = [] - if title is not ternary: - added_anims += [ - FadeIn(symbol.brace), - FadeIn(symbol.name) - ] - self.play(Write(symbol, run_time = 2), *added_anims) - self.wait() - self.play(FadeIn(keith)) - self.play(keith.change_mode, "confused") - self.play(keith.look_at, zero_to_nine) - self.play(keith.look_at, zero_one) - self.play( - GrowFromCenter(zero_one_two.brace), - Write(zero_one_two.name), - keith.look_at, zero_one_two, - ) - self.play(keith.change_mode, "sassy") - self.play(Blink(keith)) - self.play(FadeIn(morty)) - self.play( - morty.change_mode, "sassy", - morty.look_at, zero_one_two - ) - self.play(Blink(morty)) - self.wait() - self.play( - morty.shrug, - morty.look_at, keith.eyes, - keith.shrug, - keith.look_at, morty.eyes - ) - self.wait() - self.play( - morty.change_mode, "hesitant", - morty.look_at, zero_one.name, - keith.change_mode, "erm", - keith.look_at, zero_one.name - ) - self.play(Blink(morty)) - self.play(Write(dots, run_time = 3)) - self.wait() - -class IntroduceTernaryCounting(CountingScene): - CONFIG = { - "base" : 3, - "counting_dot_starting_position" : (FRAME_X_RADIUS-1)*RIGHT + (FRAME_Y_RADIUS-1)*UP, - "count_dot_starting_radius" : 0.5, - "dot_configuration_height" : 1, - "ones_configuration_location" : UP+2*RIGHT, - "num_scale_factor" : 2, - "num_start_location" : DOWN+RIGHT, - } - def construct(self): - for x in range(2): - self.increment() - self.wait() - self.increment() - brace = Brace(self.number_mob[-1]) - threes_place = brace.get_text("Three's place") - self.play( - GrowFromCenter(brace), - Write(threes_place) - ) - self.wait() - for x in range(6): - self.increment() - self.wait() - new_brace = Brace(self.number_mob[-1]) - nines_place = new_brace.get_text("Nine's place") - self.play( - Transform(brace, new_brace), - Transform(threes_place, nines_place), - ) - self.wait() - for x in range(9): - self.increment() - -class TernaryCountingSelfSimilarPattern(Scene): - CONFIG = { - "num_trits" : 3, - "colors" : CountingScene.CONFIG["power_colors"][:3], - } - def construct(self): - colors = self.colors - - title = TextMobject("Count to " + "2"*self.num_trits) - for i, color in enumerate(colors): - title[-i-1].set_color(color) - steps = VGroup(*list(map(TextMobject, [ - "Count to %s,"%("2"*(self.num_trits-1)), - "Roll over,", - "Count to %s,"%("2"*(self.num_trits-1)), - "Roll over,", - "Count to %s,"%("2"*(self.num_trits-1)), - ]))) - steps.arrange(RIGHT) - for step in steps[::2]: - for i, color in enumerate(colors[:-1]): - step[-i-2].set_color(color) - VGroup(*steps[1::2]).set_color(colors[-1]) - steps.set_width(FRAME_WIDTH-1) - brace = Brace(steps, UP) - word_group = VGroup(title, brace, steps) - word_group.arrange(DOWN) - word_group.to_edge(UP) - - ternary_mobs = VGroup(*[ - get_ternary_tex_mob(n, n_trits = self.num_trits) - for n in range(3**self.num_trits) - ]) - ternary_mobs.scale(2) - ternary_mob_iter = it.cycle(ternary_mobs) - curr_ternary_mob = next(ternary_mob_iter) - - for trits in ternary_mobs: - trits.align_data(curr_ternary_mob) - for trit, color in zip(trits, colors): - trit.set_color(color) - def get_increment(): - return Transform( - curr_ternary_mob, next(ternary_mob_iter), - lag_ratio = 0.5, - path_arc = -np.pi/3 - ) - - self.add(curr_ternary_mob, title) - self.play(GrowFromCenter(brace)) - for i, step in enumerate(steps): - self.play(Write(step, run_time = 1)) - if i%2 == 0: - self.play( - Succession(*[ - get_increment() - for x in range(3**(self.num_trits-1)-1) - ]), - run_time = 1 - ) - else: - self.play(get_increment()) - self.wait() - -class TernaryCountingSelfSimilarPatternFiveTrits(TernaryCountingSelfSimilarPattern): - CONFIG = { - "num_trits" : 5, - "colors" : color_gradient([YELLOW, PINK, RED], 5), - } - -class CountInTernary(IntroduceTernaryCounting): - def construct(self): - for x in range(9): - self.increment() - self.wait() - -class SolveConstrainedWithTernaryCounting(ConstrainedTowersOfHanoiScene): - CONFIG = { - "num_disks" : 4, - } - def construct(self): - for x in range(3**self.num_disks-1): - self.increment(run_time = 0.75) - self.wait() - - def setup(self): - ConstrainedTowersOfHanoiScene.setup(self) - ternary_mobs = VGroup(*[ - get_ternary_tex_mob(x) - for x in range(3**self.num_disks) - ]) - ternary_mobs.scale(2) - ternary_mobs.next_to(self.peg_labels, DOWN) - - for trits in ternary_mobs: - trits.align_data(ternary_mobs[0]) - trits.set_color_by_gradient(*self.disk_start_and_end_colors) - self.ternary_mob_iter = it.cycle(ternary_mobs) - self.curr_ternary_mob = self.ternary_mob_iter.next().copy() - self.disk_index_iter = it.cycle( - get_ternary_ruler_sequence(self.num_disks-1) - ) - self.ternary_mobs = ternary_mobs - - def increment(self, run_time = 1, stay_on_peg = False): - self.increment_number(run_time) - self.move_next_disk(run_time, stay_on_peg) - - def increment_number(self, run_time = 1): - self.play(Transform( - self.curr_ternary_mob, next(self.ternary_mob_iter), - path_arc = -np.pi/3, - lag_ratio = 0.5, - run_time = run_time, - )) - - def move_next_disk(self, run_time = None, stay_on_peg = False): - self.move_disk( - next(self.disk_index_iter), - run_time = run_time, - stay_on_peg = stay_on_peg - ) - -class DescribeSolutionByCountingToConstrained(SolveConstrainedWithTernaryCounting): - def construct(self): - braces = [ - Brace(VGroup(*self.curr_ternary_mob[:n+1])) - for n in range(self.num_disks) - ] - words = [ - brace.get_text(text) - for brace, text in zip(braces, [ - "Only flip last trit", - "Roll over once", - "Roll over twice", - "Roll over three times", - ]) - ] - - #Count 1, 2 - color = YELLOW - brace = braces[0] - word = words[0] - words[0].set_color(color) - self.increment_number() - self.play( - FadeIn(brace), - Write(word, run_time = 1), - self.curr_ternary_mob[0].set_color, color - ) - self.wait() - self.play( - self.disks[0].set_fill, color, - self.disks[0].label.set_fill, BLACK, - ) - self.move_next_disk(stay_on_peg = True) - self.wait() - self.ternary_mobs[2][0].set_color(color) - self.increment_number() - self.move_next_disk(stay_on_peg = True) - self.wait() - - #Count 10 - color = MAROON_B - words[1].set_color(color) - self.increment_number() - self.play( - Transform(brace, braces[1]), - Transform(word, words[1]), - self.curr_ternary_mob[1].set_color, color - ) - self.wait() - self.play( - self.disks[1].set_fill, color, - self.disks[1].label.set_fill, BLACK, - ) - self.move_next_disk(stay_on_peg = True) - self.wait() - self.play(*list(map(FadeOut, [brace, word]))) - - #Count up to 22 - for x in range(5): - self.increment() - self.wait() - - #Count to 100 - color = RED - words[2].set_color(color) - - self.wait() - self.increment_number() - self.play( - FadeIn(braces[2]), - Write(words[2], run_time = 1), - self.curr_ternary_mob[2].set_fill, color, - self.disks[2].set_fill, color, - self.disks[2].label.set_fill, BLACK, - ) - self.wait() - self.move_next_disk(stay_on_peg = True) - self.wait() - self.play(*list(map(FadeOut, [braces[2], words[2]]))) - - for x in range(20): - self.increment() - -class Describe2222(Scene): - def construct(self): - ternary_mob = TexMobject("2222").scale(1.5) - brace = Brace(ternary_mob) - description = brace.get_text("$3^4 - 1 = 80$") - VGroup(ternary_mob, brace, description).scale(1.5) - - self.add(ternary_mob) - self.wait() - self.play(GrowFromCenter(brace)) - self.play(Write(description)) - self.wait() - -class KeithAsksAboutConfigurations(Scene): - def construct(self): - keith = Keith().shift(2*DOWN+3*LEFT) - morty = Mortimer().shift(2*DOWN+3*RIGHT) - keith.make_eye_contact(morty) - bubble = keith.get_bubble(SpeechBubble) - bubble.write("Think about how many \\\\ configurations there are.") - - self.add(keith, morty) - self.play(Blink(keith)) - self.play( - keith.change_mode, "speaking", - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(Blink(morty)) - self.play(morty.change_mode, "pondering") - self.wait() - -class AskAboutConfigurations(SolveConstrainedWithTernaryCounting): - def construct(self): - question = TextMobject("How many configurations?") - question.scale(1.5) - question.to_edge(UP) - self.add(question) - - for x in range(15): - self.remove(self.curr_ternary_mob) - self.wait(2) - for y in range(7): - self.increment(run_time = 0) - -class AnswerConfigurationsCount(TowersOfHanoiScene): - CONFIG = { - "middle_peg_bottom" : 2.5*DOWN, - "num_disks" : 4, - "peg_height" : 1.5, - } - def construct(self): - answer = TextMobject("$3^4 = 81$ configurations") - answer.to_edge(UP) - self.add(answer) - - parentheticals = self.get_parentheticals(answer) - - self.prepare_disks() - - for parens, disk in zip(parentheticals, reversed(list(self.disks))): - VGroup(parens, parens.brace, parens.three).set_color(disk.get_color()) - self.play( - Write(parens, run_time = 1), - FadeIn(disk) - ) - self.play( - ApplyMethod( - disk.next_to, self.pegs[2], UP, - run_time = 2 - ), - GrowFromCenter(parens.brace), - Write(parens.three, run_time = 1) - ) - x_diff = disk.saved_state.get_center()[0]-disk.get_center()[0] - self.play( - disk.shift, x_diff*RIGHT - ) - self.play(disk.restore) - self.wait() - - def get_parentheticals(self, top_mob): - parentheticals = VGroup(*reversed([ - TexMobject(""" - \\left( - \\begin{array}{c} - \\text{Choices for} \\\\ - \\text{disk %d} - \\end{array} - \\right) - """%d) - for d in range(self.num_disks) - ])) - parentheticals.arrange() - parentheticals.set_width(FRAME_WIDTH-1) - parentheticals.next_to(top_mob, DOWN) - for parens in parentheticals: - brace = Brace(parens) - three = brace.get_text("$3$") - parens.brace = brace - parens.three = three - return parentheticals - - def prepare_disks(self): - configuration = [1, 2, 1, 0] - for n, peg_index in enumerate(configuration): - disk_index = self.num_disks-n-1 - disk = self.disks[disk_index] - top = Circle(radius = disk.get_width()/2) - inner = Circle(radius = self.peg_width/2) - inner.flip() - top.add_subpath(inner.points) - top.set_stroke(width = 0) - top.set_fill(disk.get_color()) - top.rotate(np.pi/2, RIGHT) - top.move_to(disk, UP) - bottom = top.copy() - bottom.move_to(disk, DOWN) - disk.remove(disk.label) - disk.add(top, bottom, disk.label) - self.move_disk_to_peg(disk_index, peg_index, run_time = 0) - if disk_index == 0: - disk.move_to(self.pegs[peg_index].get_bottom(), DOWN) - for disk in self.disks: - disk.save_state() - disk.rotate(np.pi/30, RIGHT) - disk.next_to(self.pegs[0], UP) - self.remove(self.disks) - -class ThisIsMostEfficientText(Scene): - def construct(self): - text = TextMobject("This is the most efficient solution") - text.set_width(FRAME_WIDTH - 1) - text.to_edge(DOWN) - self.play(Write(text)) - self.wait(2) - -class RepeatingConfiguraiton(Scene): - def construct(self): - dots = VGroup(*[Dot() for x in range(10)]) - arrows = VGroup(*[Arrow(LEFT, RIGHT) for x in range(9)]) - arrows.add(VGroup()) - arrows.scale(0.5) - group = VGroup(*it.chain(*list(zip(dots, arrows)))) - group.arrange() - title = TextMobject("Same state twice") - title.shift(3*UP) - special_dots = VGroup(dots[2], dots[6]) - special_arrows = VGroup(*[ - Arrow(title.get_bottom(), dot, color = RED) - for dot in special_dots - ]) - mid_mobs = VGroup(*group[5:14]) - mid_arrow = Arrow(dots[2], dots[7], tip_length = 0.125) - more_efficient = TextMobject("More efficient") - more_efficient.next_to(mid_arrow, UP) - - self.play(ShowCreation(group, run_time = 2)) - self.play(Write(title)) - self.play( - ShowCreation(special_arrows), - special_dots.set_color, RED - ) - self.wait() - self.play( - mid_mobs.shift, 2*DOWN, - FadeOut(special_arrows) - ) - self.play( - ShowCreation(mid_arrow), - Write(more_efficient) - ) - self.wait() - -class ShowSomeGraph(Scene): - def construct(self): - title = TextMobject("Graphs") - title.scale(2) - title.to_edge(UP) - - nodes = VGroup(*list(map(Dot, [ - 2*LEFT, - UP, - DOWN, - 2*RIGHT, - 2*RIGHT+2*UP, - 2*RIGHT+2*DOWN, - 4*RIGHT+2*UP, - ]))) - edge_pairs = [ - (0, 1), - (0, 2), - (1, 3), - (2, 3), - (3, 4), - (3, 5), - (4, 6), - ] - edges = VGroup() - for i, j in edge_pairs: - edges.add(Line( - nodes[i].get_center(), - nodes[j].get_center(), - )) - - self.play(Write(title)) - for mob in nodes, edges: - mob.set_color_by_gradient(YELLOW, MAROON_B) - self.play(ShowCreation( - mob, - lag_ratio = 0.5, - run_time = 2, - )) - self.wait() - -class SierpinskiGraphScene(Scene): - CONFIG = { - "num_disks" : 3, - "towers_config" : { - "num_disks" : 3, - "peg_height" : 1.5, - "peg_spacing" : 2, - "include_peg_labels" : False, - "disk_min_width" : 1, - "disk_max_width" : 2, - }, - "preliminary_node_radius" : 1, - "center_to_island_length" : 2.0, - "include_towers" : True, - "start_color" : RED, - "end_color" : GREEN, - "graph_stroke_width" : 2, - } - def setup(self): - self.initialize_nodes() - self.add(self.nodes) - - self.initialize_edges() - self.add(self.edges) - - def initialize_nodes(self): - circles = self.get_node_circles(self.num_disks) - circles.set_color_by_gradient(self.start_color, self.end_color) - circles.set_fill(BLACK, opacity = 0.7) - circles.set_stroke(width = self.graph_stroke_width) - - self.nodes = VGroup() - for circle in circles.get_family(): - if not isinstance(circle, Circle): - continue - node = VGroup() - node.add(circle) - node.circle = circle - self.nodes.add(node) - if self.include_towers: - self.add_towers_to_nodes() - self.nodes.set_height(FRAME_HEIGHT-2) - self.nodes.to_edge(UP) - - def get_node_circles(self, order = 3): - if order == 0: - return Circle(radius = self.preliminary_node_radius) - islands = [self.get_node_circles(order-1) for x in range(3)] - for island, angle in (islands[0], np.pi/6), (islands[2], 5*np.pi/6): - island.rotate( - np.pi, - rotate_vector(RIGHT, angle), - about_point = island.get_center_of_mass() - ) - for n, island in enumerate(islands): - vect = rotate_vector(RIGHT, -5*np.pi/6-n*2*np.pi/3) - island.scale(0.5) - island.shift(vect) - return VGroup(*islands) - - def add_towers_to_nodes(self): - towers_scene = ConstrainedTowersOfHanoiScene(**self.towers_config) - tower_scene_group = VGroup(*towers_scene.get_mobjects()) - ruler_sequence = get_ternary_ruler_sequence(self.num_disks-1) - self.disks = VGroup(*[VGroup() for x in range(self.num_disks)]) - - for disk_index, node in zip(ruler_sequence+[0], self.nodes): - towers = tower_scene_group.copy() - for mob in towers: - if hasattr(mob, "label"): - self.disks[int(mob.label.tex_string)].add(mob) - towers.set_width(0.85*node.get_width()) - towers.move_to(node) - node.towers = towers - node.add(towers) - towers_scene.move_disk(disk_index, run_time = 0) - - def distance_between_nodes(self, i, j): - return get_norm( - self.nodes[i].get_center()-\ - self.nodes[j].get_center() - ) - - def initialize_edges(self): - edges = VGroup() - self.edge_dict = {} - min_distance = self.distance_between_nodes(0, 1) - min_distance *= 1.1 ##Just a little buff to be sure - node_radius = self.nodes[0].get_width()/2 - for i, j in it.combinations(list(range(3**self.num_disks)), 2): - center1 = self.nodes[i].get_center() - center2 = self.nodes[j].get_center() - vect = center1-center2 - distance = get_norm(center1 - center2) - if distance < min_distance: - edge = Line( - center1 - (vect/distance)*node_radius, - center2 + (vect/distance)*node_radius, - color = self.nodes[i].circle.get_stroke_color(), - stroke_width = self.graph_stroke_width, - ) - edges.add(edge) - self.edge_dict[self.node_indices_to_key(i, j)] = edge - self.edges = edges - - def node_indices_to_key(self, i, j): - return ",".join(map(str, sorted([i, j]))) - - def node_indices_to_edge(self, i, j): - key = self.node_indices_to_key(i, j) - if key not in self.edge_dict: - warnings.warn("(%d, %d) is not an edge"%(i, j)) - return VGroup() - return self.edge_dict[key] - - def zoom_into_node(self, node_index, order = 0): - start_index = node_index - node_index%(3**order) - node_indices = [start_index + r for r in range(3**order)] - self.zoom_into_nodes(node_indices) - - def zoom_into_nodes(self, node_indices): - nodes = VGroup(*[ - self.nodes[index].circle - for index in node_indices - ]) - everything = VGroup(*self.get_mobjects()) - if nodes.get_width()/nodes.get_height() > FRAME_X_RADIUS/FRAME_Y_RADIUS: - scale_factor = (FRAME_WIDTH-2)/nodes.get_width() - else: - scale_factor = (FRAME_HEIGHT-2)/nodes.get_height() - self.play( - everything.shift, -nodes.get_center(), - everything.scale, scale_factor - ) - self.remove(everything) - self.add(*everything) - self.wait() - -class IntroduceGraphStructure(SierpinskiGraphScene): - CONFIG = { - "include_towers" : True, - "graph_stroke_width" : 3, - "num_disks" : 3, - } - def construct(self): - self.remove(self.nodes, self.edges) - self.introduce_nodes() - self.define_edges() - self.tour_structure() - - def introduce_nodes(self): - self.play(FadeIn( - self.nodes, - run_time = 3, - lag_ratio = 0.5, - )) - vect = LEFT - for index in 3, 21, 8, 17, 10, 13: - node = self.nodes[index] - node.save_state() - self.play( - node.set_height, FRAME_HEIGHT-2, - node.next_to, ORIGIN, vect - ) - self.wait() - self.play(node.restore) - node.saved_state = None - vect = -vect - - def define_edges(self): - nodes = [self.nodes[i] for i in (12, 14)] - for node, vect in zip(nodes, [LEFT, RIGHT]): - node.save_state() - node.generate_target() - node.target.set_height(5) - node.target.center() - node.target.to_edge(vect) - arc = Arc(angle = -2*np.pi/3, start_angle = 5*np.pi/6) - if vect is RIGHT: - arc.flip() - arc.set_width(0.8*node.target.towers.get_width()) - arc.next_to(node.target.towers, UP) - arc.add_tip() - arc.set_color(YELLOW) - node.arc = arc - - self.play(*list(map(MoveToTarget, nodes))) - edge = Line( - nodes[0].get_right(), nodes[1].get_left(), - color = YELLOW, - stroke_width = 6, - ) - edge.target = self.node_indices_to_edge(12, 14) - self.play(ShowCreation(edge)) - self.wait() - for node in nodes: - self.play(ShowCreation(node.arc)) - self.wait() - self.play(*[ - FadeOut(node.arc) - for node in nodes - ]) - self.play( - MoveToTarget(edge), - *[node.restore for node in nodes] - ) - self.wait() - self.play(ShowCreation(self.edges, run_time = 3)) - self.wait() - - def tour_structure(self): - for n in range(3): - self.zoom_into_node(n) - self.zoom_into_node(0, 1) - self.play( - self.disks[0].set_color, YELLOW, - *[ - ApplyMethod(disk.label.set_color, BLACK) - for disk in self.disks[0] - ] - ) - self.wait() - self.zoom_into_node(0, 3) - self.zoom_into_node(15, 1) - self.wait() - self.zoom_into_node(20, 1) - self.wait() - -class DescribeTriforcePattern(SierpinskiGraphScene): - CONFIG = { - "index_pairs" : [(7, 1), (2, 3), (5, 6)], - "scale" : 2, - "disk_color" : MAROON_B, - "include_towers" : True, - "first_connect_0_and_2_islands" : True, #Dumb that I have to do this - } - def construct(self): - index_pair = self.index_pairs[0] - self.zoom_into_node(index_pair[0], self.scale) - self.play( - self.disks[self.scale-1].set_color, self.disk_color, - *[ - ApplyMethod(disk.label.set_color, BLACK) - for disk in self.disks[self.scale-1] - ] - ) - - nodes = [self.nodes[i] for i in index_pair] - for node, vect in zip(nodes, [LEFT, RIGHT]): - node.save_state() - node.generate_target() - node.target.set_height(6) - node.target.center().next_to(ORIGIN, vect) - - self.play(*list(map(MoveToTarget, nodes))) - self.wait() - self.play(*[node.restore for node in nodes]) - bold_edges = [ - self.node_indices_to_edge(*pair).copy().set_stroke(self.disk_color, 6) - for pair in self.index_pairs - ] - self.play(ShowCreation(bold_edges[0])) - self.wait() - self.play(*list(map(ShowCreation, bold_edges[1:]))) - self.wait() - - power_of_three = 3**(self.scale-1) - index_sets = [ - list(range(0, power_of_three)), - list(range(power_of_three, 2*power_of_three)), - list(range(2*power_of_three, 3*power_of_three)), - ] - if self.first_connect_0_and_2_islands: - index_sets = [index_sets[0], index_sets[2], index_sets[1]] - islands = [ - VGroup(*[self.nodes[i] for i in index_set]) - for index_set in index_sets - ] - def wiggle_island(island): - return ApplyMethod( - island.rotate_in_place, np.pi/12, - run_time = 1, - rate_func = wiggle - ) - self.play(*list(map(wiggle_island, islands[:2]))) - self.wait() - self.play(wiggle_island(islands[2])) - self.wait() - for index_set in index_sets: - self.zoom_into_nodes(index_set) - self.zoom_into_nodes(list(it.chain(*index_sets))) - self.wait() - -class TriforcePatternWord(Scene): - def construct(self): - word = TextMobject("Triforce \\\\ pattern") - word.scale(2) - word.to_corner(DOWN+RIGHT) - self.play(Write(word)) - self.wait(2) - -class DescribeOrderTwoPattern(DescribeTriforcePattern): - CONFIG = { - "index_pairs" : [(8, 9), (17, 18), (4, 22)], - "scale" : 3, - "disk_color" : RED, - "first_connect_0_and_2_islands" : False, - } - -class BiggerTowers(SierpinskiGraphScene): - CONFIG = { - "num_disks" : 6, - "include_towers" : False - } - def construct(self): - for order in range(3, 7): - self.zoom_into_node(0, order) - -class ShowPathThroughGraph(SierpinskiGraphScene): - CONFIG = { - "include_towers" : True - } - def construct(self): - arrows = VGroup(*[ - Arrow( - n1.get_center(), - n2.get_center(), - tip_length = 0.15, - buff = 0.15 - ) - for n1, n2 in zip(self.nodes, self.nodes[1:]) - ]) - self.wait() - self.play(ShowCreation( - arrows, - rate_func=linear, - run_time = 5 - )) - self.wait(2) - for index in range(9): - self.zoom_into_node(index) - -class MentionFinalAnimation(Scene): - def construct(self): - morty = Mortimer() - morty.shift(2*DOWN+3*RIGHT) - bubble = morty.get_bubble(SpeechBubble, width = 6) - bubble.write("Before the final\\\\ animation...") - - self.add(morty) - self.wait() - self.play( - morty.change_mode, "speaking", - morty.look_at, bubble.content, - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(Blink(morty)) - self.wait(2) - self.play(Blink(morty)) - self.wait(2) - -class PatreonThanks(Scene): - CONFIG = { - "specific_patrons" : [ - "CrypticSwarm", - "Ali Yahya", - "Juan Batiz-Benet", - "Yu Jun", - "Othman Alikhan", - "Joseph John Cox", - "Luc Ritchie", - "Einar Johansen", - "Rish Kundalia", - "Achille Brighton", - "Kirk Werklund", - "Ripta Pasay", - "Felipe Diniz", - ] - } - def construct(self): - morty = Mortimer() - morty.next_to(ORIGIN, DOWN) - - n_patrons = len(self.specific_patrons) - special_thanks = TextMobject("Special thanks to:") - special_thanks.set_color(YELLOW) - special_thanks.shift(3*UP) - - left_patrons = VGroup(*list(map(TextMobject, - self.specific_patrons[:n_patrons/2] - ))) - right_patrons = VGroup(*list(map(TextMobject, - self.specific_patrons[n_patrons/2:] - ))) - for patrons, vect in (left_patrons, LEFT), (right_patrons, RIGHT): - patrons.arrange(DOWN, aligned_edge = LEFT) - patrons.next_to(special_thanks, DOWN) - patrons.to_edge(vect, buff = LARGE_BUFF) - - self.play(morty.change_mode, "gracious") - self.play(Write(special_thanks, run_time = 1)) - self.play( - Write(left_patrons), - morty.look_at, left_patrons - ) - self.play( - Write(right_patrons), - morty.look_at, right_patrons - ) - self.play(Blink(morty)) - for patrons in left_patrons, right_patrons: - for index in 0, -1: - self.play(morty.look_at, patrons[index]) - self.wait() - -class MortyLookingAtRectangle(Scene): - def construct(self): - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - url = TextMobject("www.desmos.com/careers") - url.to_corner(UP+LEFT) - rect = Rectangle(height = 9, width = 16) - rect.set_height(5) - rect.next_to(url, DOWN) - rect.shift_onto_screen() - url.save_state() - url.next_to(morty.get_corner(UP+LEFT), UP) - - self.play(morty.change_mode, "raise_right_hand") - self.play(Write(url)) - self.play(Blink(morty)) - self.wait() - self.play( - url.restore, - morty.change_mode, "happy" - ) - self.play(ShowCreation(rect)) - self.wait() - self.play(Blink(morty)) - self.wait() - -class ShowSierpinskiCurvesOfIncreasingOrder(Scene): - CONFIG = { - "sierpinski_graph_scene_config" :{ - "include_towers" : False - }, - "min_order" : 2, - "max_order" : 7, - "path_stroke_width" : 7, - } - def construct(self): - graph_scenes = [ - SierpinskiGraphScene( - num_disks = order, - **self.sierpinski_graph_scene_config - ) - for order in range(self.min_order, self.max_order+1) - ] - paths = [self.get_path(scene) for scene in graph_scenes] - graphs = [] - for scene in graph_scenes: - graphs.append(scene.nodes) - for graph in graphs: - graph.set_fill(opacity = 0) - - graph, path = graphs[0], paths[0] - - self.add(graph) - self.wait() - self.play(ShowCreation(path, run_time = 3, rate_func=linear)) - self.wait() - self.play(graph.fade, 0.5, Animation(path)) - for other_graph in graphs[1:]: - other_graph.fade(0.5) - self.wait() - for new_graph, new_path in zip(graphs[1:], paths[1:]): - self.play( - Transform(graph, new_graph), - Transform(path, new_path), - run_time = 2 - ) - self.wait() - self.path = path - - def get_path(self, graph_scene): - path = VGroup() - nodes = graph_scene.nodes - for n1, n2, n3 in zip(nodes, nodes[1:], nodes[2:]): - segment = VMobject() - segment.set_points_as_corners([ - n1.get_center(), - n2.get_center(), - n3.get_center(), - ]) - path.add(segment) - path.set_color_by_gradient( - graph_scene.start_color, - graph_scene.end_color, - ) - path.set_stroke( - width = self.path_stroke_width - graph_scene.num_disks/2 - ) - return path - -class Part1Thumbnail(Scene): - CONFIG = { - "part_number" : 1, - "sierpinski_order" : 5 - } - def construct(self): - toh_scene = TowersOfHanoiScene( - peg_spacing = 2, - part_number = 1, - ) - toh_scene.remove(toh_scene.peg_labels) - toh_scene.pegs[2].set_fill(opacity = 0.5) - toh = VGroup(*toh_scene.get_mobjects()) - toh.scale(2) - toh.to_edge(DOWN) - self.add(toh) - - sierpinski_scene = ShowSierpinskiCurvesOfIncreasingOrder( - min_order = self.sierpinski_order, - max_order = self.sierpinski_order, - skip_animations = True, - ) - sierpinski_scene.path.set_stroke(width = 10) - sierpinski = VGroup(*sierpinski_scene.get_mobjects()) - sierpinski.scale(0.9) - sierpinski.to_corner(DOWN+RIGHT) - self.add(sierpinski) - - binary = TexMobject("01011") - binary.set_color_by_tex("0", GREEN) - binary.set_color_by_tex("1", BLUE) - binary.set_color_by_gradient(GREEN, RED) - binary.add_background_rectangle() - binary.background_rectangle.set_fill(opacity = 0.5) - # binary.set_fill(opacity = 0.5) - binary.scale(4) - binary.to_corner(UP+LEFT) - self.add(binary) - - part = TextMobject("Part %d"%self.part_number) - part.scale(4) - part.to_corner(UP+RIGHT) - part.add_background_rectangle() - self.add(part) - -class Part2Thumbnail(Part1Thumbnail): - CONFIG = { - "part_number" : 2 - } - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/highD.py b/from_3b1b/old/highD.py deleted file mode 100644 index 77b63330..00000000 --- a/from_3b1b/old/highD.py +++ /dev/null @@ -1,3628 +0,0 @@ -from manimlib.imports import * - -########## -#force_skipping -#revert_to_original_skipping_status - -########## - -class Slider(NumberLine): - CONFIG = { - "color" : WHITE, - "x_min" : -1, - "x_max" : 1, - "unit_size" : 2, - "center_value" : 0, - "number_scale_val" : 0.75, - "label_scale_val" : 1, - "numbers_with_elongated_ticks" : [], - "line_to_number_vect" : LEFT, - "line_to_number_buff" : MED_LARGE_BUFF, - "dial_radius" : 0.1, - "dial_color" : YELLOW, - "include_real_estate_ticks" : True, - } - def __init__(self, **kwargs): - NumberLine.__init__(self, **kwargs) - self.rotate(np.pi/2) - self.init_dial() - if self.include_real_estate_ticks: - self.add_real_estate_ticks() - - def init_dial(self): - dial = Dot( - radius = self.dial_radius, - color = self.dial_color, - ) - dial.move_to(self.number_to_point(self.center_value)) - re_dial = dial.copy() - re_dial.set_fill(opacity = 0) - self.add(dial, re_dial) - - self.dial = dial - self.re_dial = re_dial - self.last_sign = -1 - - def add_label(self, tex): - label = TexMobject(tex) - label.scale(self.label_scale_val) - label.move_to(self.get_top()) - label.shift(MED_LARGE_BUFF*UP) - self.add(label) - self.label = label - - def add_real_estate_ticks( - self, - re_per_tick = 0.05, - colors = [BLUE, RED], - max_real_estate = 1, - ): - self.real_estate_ticks = VGroup(*[ - self.get_tick(self.center_value + u*np.sqrt(x + re_per_tick)) - for x in np.arange(0, max_real_estate, re_per_tick) - for u in [-1, 1] - ]) - self.real_estate_ticks.set_stroke(width = 3) - self.real_estate_ticks.set_color_by_gradient(*colors) - self.add(self.real_estate_ticks) - self.add(self.dial) - return self.real_estate_ticks - - def set_value(self, x): - re = (x - self.center_value)**2 - for dial, val in (self.dial, x), (self.re_dial, re): - dial.move_to(self.number_to_point(val)) - return self - - def set_center_value(self, x): - self.center_value = x - return self - - def change_real_estate(self, d_re): - left_over = 0 - curr_re = self.get_real_estate() - if d_re < -curr_re: - left_over = d_re + curr_re - d_re = -curr_re - self.set_real_estate(curr_re + d_re) - return left_over - - def set_real_estate(self, target_re): - if target_re < 0: - raise Exception("Cannot set real estate below 0") - self.re_dial.move_to(self.number_to_point(target_re)) - self.update_dial_by_re_dial() - return self - - def get_dial_supplement_animation(self): - return UpdateFromFunc(self.dial, self.update_dial_by_re_dial) - - def update_dial_by_re_dial(self, dial = None): - dial = dial or self.dial - re = self.get_real_estate() - sign = np.sign(self.get_value() - self.center_value) - if sign == 0: - sign = -self.last_sign - self.last_sign *= -1 - dial.move_to(self.number_to_point( - self.center_value + sign*np.sqrt(abs(re)) - )) - return dial - - def get_value(self): - return self.point_to_number(self.dial.get_center()) - - def get_real_estate(self): - return self.point_to_number(self.re_dial.get_center()) - - def copy(self): - return self.deepcopy() - -class SliderScene(Scene): - CONFIG = { - "n_sliders" : 4, - "slider_spacing" : MED_LARGE_BUFF, - "slider_config" : {}, - "center_point" : None, - "total_real_estate" : 1, - "ambiently_change_sliders" : False, - "ambient_velocity_magnitude" : 1.0, - "ambient_acceleration_magnitude" : 1.0, - "ambient_jerk_magnitude" : 1.0/2, - } - def setup(self): - if self.center_point is None: - self.center_point = np.zeros(self.n_sliders) - sliders = VGroup(*[ - Slider(center_value = cv, **self.slider_config) - for cv in self.center_point - ]) - sliders.arrange(RIGHT, buff = self.slider_spacing) - sliders[0].add_numbers() - sliders[0].set_value( - self.center_point[0] + np.sqrt(self.total_real_estate) - ) - self.sliders = sliders - - self.add_labels_to_sliders() - self.add(sliders) - - def add_labels_to_sliders(self): - if len(self.sliders) <= 4: - for slider, char in zip(self.sliders, "xyzw"): - slider.add_label(char) - for slider in self.sliders[1:]: - slider.label.align_to(self.sliders[0].label, UP) - else: - for i, slider in enumerate(self.sliders): - slider.add_label("x_{%d}"%(i+1)) - return self - - def reset_dials(self, values, run_time = 1, **kwargs): - target_vector = self.get_target_vect_from_subset_of_values(values, **kwargs) - - radius = np.sqrt(self.total_real_estate) - def update_sliders(sliders): - curr_vect = self.get_vector() - curr_vect -= self.center_point - curr_vect *= radius/get_norm(curr_vect) - curr_vect += self.center_point - self.set_to_vector(curr_vect) - return sliders - - self.play(*[ - ApplyMethod(slider.set_value, value) - for value, slider in zip(target_vector, self.sliders) - ] + [ - UpdateFromFunc(self.sliders, update_sliders) - ], run_time = run_time) - - def get_target_vect_from_subset_of_values(self, values, fixed_indices = None): - if fixed_indices is None: - fixed_indices = [] - curr_vector = self.get_vector() - target_vector = np.array(self.center_point, dtype = 'float') - unspecified_vector = np.array(self.center_point, dtype = 'float') - unspecified_indices = [] - for i in range(len(curr_vector)): - if i < len(values) and values[i] is not None: - target_vector[i] = values[i] - else: - unspecified_indices.append(i) - unspecified_vector[i] = curr_vector[i] - used_re = get_norm(target_vector - self.center_point)**2 - left_over_re = self.total_real_estate - used_re - if left_over_re < -0.001: - raise Exception("Overspecified reset") - uv_norm = get_norm(unspecified_vector - self.center_point) - if uv_norm == 0 and left_over_re > 0: - unspecified_vector[unspecified_indices] = 1 - uv_norm = get_norm(unspecified_vector - self.center_point) - if uv_norm > 0: - unspecified_vector -= self.center_point - unspecified_vector *= np.sqrt(left_over_re)/uv_norm - unspecified_vector += self.center_point - return target_vector + unspecified_vector - self.center_point - - def set_to_vector(self, vect): - assert len(vect) == len(self.sliders) - for slider, value in zip(self.sliders, vect): - slider.set_value(value) - - def get_vector(self): - return np.array([slider.get_value() for slider in self.sliders]) - - def get_center_point(self): - return np.array([slider.center_value for slider in self.sliders]) - - def set_center_point(self, new_center_point): - self.center_point = np.array(new_center_point) - for x, slider in zip(new_center_point, self.sliders): - slider.set_center_value(x) - return self - - def get_current_total_real_estate(self): - return sum([ - slider.get_real_estate() - for slider in self.sliders - ]) - - def get_all_dial_supplement_animations(self): - return [ - slider.get_dial_supplement_animation() - for slider in self.sliders - ] - - def initialize_ambiant_slider_movement(self): - self.ambiently_change_sliders = True - self.ambient_change_end_time = np.inf - self.ambient_change_time = 0 - self.ambient_velocity, self.ambient_acceleration, self.ambient_jerk = [ - self.get_random_vector(magnitude) - for magnitude in [ - self.ambient_velocity_magnitude, - self.ambient_acceleration_magnitude, - self.ambient_jerk_magnitude, - ] - ] - ##Ensure counterclockwise rotations in 2D - if len(self.ambient_velocity) == 2: - cross = np.cross(self.get_vector(), self.ambient_velocity) - if cross < 0: - self.ambient_velocity *= -1 - self.add_foreground_mobjects(self.sliders) - - def wind_down_ambient_movement(self, time = 1, wait = True): - self.ambient_change_end_time = self.ambient_change_time + time - if wait: - self.wait(time) - if self.skip_animations: - self.ambient_change_time += time - - def ambient_slider_movement_update(self): - #Set velocity_magnitude based on start up or wind down - velocity_magnitude = float(self.ambient_velocity_magnitude) - if self.ambient_change_time <= 1: - velocity_magnitude *= smooth(self.ambient_change_time) - time_until_end = self.ambient_change_end_time - self.ambient_change_time - if time_until_end <= 1: - velocity_magnitude *= smooth(time_until_end) - if time_until_end < 0: - self.ambiently_change_sliders = False - return - - center_point = self.get_center_point() - target_vector = self.get_vector() - center_point - if get_norm(target_vector) == 0: - return - vectors_and_magnitudes = [ - (self.ambient_acceleration, self.ambient_acceleration_magnitude), - (self.ambient_velocity, velocity_magnitude), - (target_vector, np.sqrt(self.total_real_estate)), - ] - jerk = self.get_random_vector(self.ambient_jerk_magnitude) - deriv = jerk - for vect, mag in vectors_and_magnitudes: - vect += self.frame_duration*deriv - if vect is self.ambient_velocity: - unit_r_vect = target_vector / get_norm(target_vector) - vect -= np.dot(vect, unit_r_vect)*unit_r_vect - vect *= mag/get_norm(vect) - deriv = vect - - self.set_to_vector(target_vector + center_point) - self.ambient_change_time += self.frame_duration - - def get_random_vector(self, magnitude): - result = 2*np.random.random(len(self.sliders)) - 1 - result *= magnitude / get_norm(result) - return result - - def update_frame(self, *args, **kwargs): - if self.ambiently_change_sliders: - self.ambient_slider_movement_update() - Scene.update_frame(self, *args, **kwargs) - - def wait(self, time = 1): - if self.ambiently_change_sliders: - self.play(Animation(self.sliders, run_time = time)) - else: - Scene.wait(self,time) - -########## - -class MathIsATease(Scene): - def construct(self): - randy = Randolph() - lashes = VGroup() - for eye in randy.eyes: - for angle in np.linspace(-np.pi/3, np.pi/3, 12): - lash = Line(ORIGIN, RIGHT) - lash.set_stroke(DARK_GREY, 2) - lash.set_width(0.27) - lash.next_to(ORIGIN, RIGHT, buff = 0) - lash.rotate(angle + np.pi/2) - lash.shift(eye.get_center()) - lashes.add(lash) - lashes.do_in_place(lashes.stretch, 0.8, 1) - lashes.shift(0.04*DOWN) - - - fan = SVGMobject( - file_name = "fan", - fill_opacity = 1, - fill_color = YELLOW, - stroke_width = 2, - stroke_color = YELLOW, - height = 0.7, - ) - VGroup(*fan[-12:]).set_fill(YELLOW_E) - fan.rotate(-np.pi/4) - fan.move_to(randy) - fan.shift(0.85*UP+0.25*LEFT) - - self.add(randy) - self.play( - ShowCreation(lashes, lag_ratio = 0), - randy.change, "tease", - randy.look, OUT, - ) - self.add_foreground_mobjects(fan) - eye_bottom_y = randy.eyes.get_bottom()[1] - self.play( - ApplyMethod( - lashes.apply_function, - lambda p : [p[0], eye_bottom_y, p[2]], - rate_func = Blink.CONFIG["rate_func"], - ), - Blink(randy), - DrawBorderThenFill(fan), - ) - self.play( - ApplyMethod( - lashes.apply_function, - lambda p : [p[0], eye_bottom_y, p[2]], - rate_func = Blink.CONFIG["rate_func"], - ), - Blink(randy), - ) - self.wait() - -class TODODeterminants(TODOStub): - CONFIG = { - "message" : "Determinants clip" - } - -class CircleToPairsOfPoints(Scene): - def construct(self): - plane = NumberPlane(written_coordinate_height = 0.3) - plane.scale(2) - plane.add_coordinates(y_vals = [-1, 1]) - background_plane = plane.copy() - background_plane.set_color(GREY) - background_plane.fade() - circle = Circle(radius = 2, color = YELLOW) - - x, y = [np.sqrt(2)/2]*2 - dot = Dot(2*x*RIGHT + 2*y*UP, color = LIGHT_GREY) - - equation = TexMobject("x", "^2", "+", "y", "^2", "=", "1") - equation.set_color_by_tex("x", GREEN) - equation.set_color_by_tex("y", RED) - equation.to_corner(UP+LEFT) - equation.add_background_rectangle() - - coord_pair = TexMobject("(", "-%.02f"%x, ",", "-%.02f"%y, ")") - fixed_numbers = coord_pair.get_parts_by_tex("-") - fixed_numbers.set_fill(opacity = 0) - coord_pair.add_background_rectangle() - coord_pair.next_to(dot, UP+RIGHT, SMALL_BUFF) - numbers = VGroup(*[ - DecimalNumber(val).replace(num, dim_to_match = 1) - for val, num in zip([x, y], fixed_numbers) - ]) - numbers[0].set_color(GREEN) - numbers[1].set_color(RED) - - def get_update_func(i): - return lambda t : dot.get_center()[i]/2.0 - - - self.add(background_plane, plane) - self.play(ShowCreation(circle)) - self.play( - FadeIn(coord_pair), - Write(numbers, run_time = 1), - ShowCreation(dot), - ) - self.play( - Write(equation), - *[ - Transform( - number.copy(), - equation.get_parts_by_tex(tex), - remover = True - ) - for tex, number in zip("xy", numbers) - ] - ) - self.play(FocusOn(dot, run_time = 1)) - self.play( - Rotating( - dot, run_time = 7, in_place = False, - rate_func = smooth, - ), - MaintainPositionRelativeTo(coord_pair, dot), - *[ - ChangingDecimal( - num, get_update_func(i), - tracked_mobject = fixed_num - ) - for num, i, fixed_num in zip( - numbers, (0, 1), fixed_numbers - ) - ] - ) - self.wait() - - ######### Rotation equations ########## - - rot_equation = TexMobject( - "\\Rightarrow" - "\\big(\\cos(\\theta)x - \\sin(\\theta)y\\big)^2 + ", - "\\big(\\sin(\\theta)x + \\cos(\\theta)y\\big)^2 = 1", - ) - rot_equation.scale(0.9) - rot_equation.next_to(equation, RIGHT) - rot_equation.add_background_rectangle() - - words = TextMobject("Rotational \\\\ symmetry") - words.next_to(ORIGIN, UP) - words.to_edge(RIGHT) - words.add_background_rectangle() - - arrow = Arrow( - words.get_left(), rot_equation.get_bottom(), - path_arc = -np.pi/6 - ) - randy = Randolph(color = GREY_BROWN) - randy.to_corner(DOWN+LEFT) - - self.play( - Write(rot_equation, run_time = 2), - FadeOut(coord_pair), - FadeOut(numbers), - FadeOut(dot), - FadeIn(randy) - ) - self.play(randy.change, "confused", rot_equation) - self.play(Blink(randy)) - self.play( - Write(words, run_time = 1), - ShowCreation(arrow), - randy.look_at, words - ) - plane.remove(*plane.coordinate_labels) - self.play( - Rotate( - plane, np.pi/3, - run_time = 4, - rate_func = there_and_back - ), - Animation(equation), - Animation(rot_equation), - Animation(words), - Animation(arrow), - Animation(circle), - randy.change, "hooray" - ) - self.wait() - -class GreatSourceOfMaterial(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "It's a great source \\\\ of material.", - target_mode = "hooray" - ) - self.change_student_modes(*["happy"]*3) - self.wait(3) - -class CirclesSpheresSumsSquares(ExternallyAnimatedScene): - pass - -class BackAndForth(Scene): - def construct(self): - analytic = TextMobject("Analytic") - analytic.shift(FRAME_X_RADIUS*LEFT/2) - analytic.to_edge(UP, buff = MED_SMALL_BUFF) - geometric = TextMobject("Geometric") - geometric.shift(FRAME_X_RADIUS*RIGHT/2) - geometric.to_edge(UP, buff = MED_SMALL_BUFF) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.to_edge(UP, LARGE_BUFF) - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - self.add(analytic, geometric, h_line, v_line) - - pair = TexMobject("(", "x", ",", "y", ")") - pair.shift(FRAME_X_RADIUS*LEFT/2 + FRAME_Y_RADIUS*UP/3) - triplet = TexMobject("(", "x", ",", "y", ",", "z", ")") - triplet.shift(FRAME_X_RADIUS*LEFT/2 + FRAME_Y_RADIUS*DOWN/2) - for mob in pair, triplet: - arrow = DoubleArrow(LEFT, RIGHT) - arrow.move_to(mob) - arrow.shift(2*RIGHT) - mob.arrow = arrow - circle_eq = TexMobject("x", "^2", "+", "y", "^2", "=", "1") - circle_eq.move_to(pair) - sphere_eq = TexMobject("x", "^2", "+", "y", "^2", "+", "z", "^2", "=", "1") - sphere_eq.move_to(triplet) - - plane = NumberPlane(x_unit_size = 2, y_unit_size = 2) - circle = Circle(radius = 2, color = YELLOW) - plane_group = VGroup(plane, circle) - plane_group.scale(0.4) - plane_group.next_to(h_line, DOWN, SMALL_BUFF) - plane_group.shift(FRAME_X_RADIUS*RIGHT/2) - - - self.play(Write(pair)) - # self.play(ShowCreation(pair.arrow)) - self.play(ShowCreation(plane, run_time = 3)) - self.play(Write(triplet)) - # self.play(ShowCreation(triplet.arrow)) - self.wait(3) - for tup, eq, to_draw in (pair, circle_eq, circle), (triplet, sphere_eq, VMobject()): - for mob in tup, eq: - mob.xyz = VGroup(*[sm for sm in map(mob.get_part_by_tex, "xyz") if sm is not None]) - self.play( - ReplacementTransform(tup.xyz, eq.xyz), - FadeOut(VGroup(*[sm for sm in tup if sm not in tup.xyz])), - ) - self.play( - Write(VGroup(*[sm for sm in eq if sm not in eq.xyz])), - ShowCreation(to_draw) - ) - self.wait(3) - -class SphereForming(ExternallyAnimatedScene): - pass - -class PreviousVideos(Scene): - def construct(self): - titles = VGroup(*list(map(TextMobject, [ - "Pi hiding in prime regularities", - "Visualizing all possible pythagorean triples", - "Borsuk-Ulam theorem", - ]))) - titles.to_edge(UP, buff = MED_SMALL_BUFF) - screen = ScreenRectangle(height = 6) - screen.next_to(titles, DOWN) - - title = titles[0] - self.add(title, screen) - self.wait(2) - for new_title in titles[1:]: - self.play(Transform(title, new_title)) - self.wait(2) - -class TODOTease(TODOStub): - CONFIG = { - "message" : "Tease" - } - -class AskAboutLongerLists(TeacherStudentsScene): - def construct(self): - question = TextMobject( - "What about \\\\", - "$(x_1, x_2, x_3, x_4)?$" - ) - tup = question[1] - alt_tups = list(map(TextMobject, [ - "$(x_1, x_2, x_3, x_4, x_5)?$", - "$(x_1, x_2, \\dots, x_{99}, x_{100})?$" - ])) - - self.student_says(question, run_time = 1) - self.wait() - for alt_tup in alt_tups: - alt_tup.move_to(tup) - self.play(Transform(tup, alt_tup)) - self.wait() - self.wait() - self.play( - RemovePiCreatureBubble(self.students[1]), - self.teacher.change, "raise_right_hand" - ) - self.change_student_modes( - *["confused"]*3, - look_at_arg = self.teacher.get_top() + 2*UP - ) - self.play(self.teacher.look, UP) - self.wait(5) - self.student_says( - "I...don't see it.", - target_mode = "maybe", - student_index = 0 - ) - self.wait(3) - -class FourDCubeRotation(ExternallyAnimatedScene): - pass - -class HypersphereRotation(ExternallyAnimatedScene): - pass - -class FourDSurfaceRotating(ExternallyAnimatedScene): - pass - -class Professionals(PiCreatureScene): - def construct(self): - self.introduce_characters() - self.add_equation() - self.analogies() - - def introduce_characters(self): - titles = VGroup(*list(map(TextMobject, [ - "Mathematician", - "Computer scientist", - "Physicist", - ]))) - self.remove(*self.pi_creatures) - for title, pi in zip(titles, self.pi_creatures): - title.next_to(pi, DOWN) - self.play( - Animation(VectorizedPoint(pi.eyes.get_center())), - FadeIn(pi), - Write(title, run_time = 1), - ) - self.wait() - - def add_equation(self): - quaternion = TexMobject( - "\\frac{1}{2}", "+", - "0", "\\textbf{i}", "+", - "\\frac{\\sqrt{6}}{4}", "\\textbf{j}", "+", - "\\frac{\\sqrt{6}}{4}", "\\textbf{k}", - ) - quaternion.scale(0.7) - quaternion.next_to(self.mathy, UP) - quaternion.set_color_by_tex_to_color_map({ - "i" : RED, - "j" : GREEN, - "k" : BLUE, - }) - - array = TexMobject("[a_1, a_2, \\dots, a_{100}]") - array.next_to(self.compy, UP) - - kets = TexMobject( - "\\alpha", - "|\\!\\uparrow\\rangle + ", - "\\beta", - "|\\!\\downarrow\\rangle" - ) - kets.set_color_by_tex_to_color_map({ - "\\alpha" : GREEN, - "\\beta" : RED, - }) - kets.next_to(self.physy, UP) - - - terms = VGroup(quaternion, array, kets) - for term, pi in zip(terms, self.pi_creatures): - self.play( - Write(term, run_time = 1), - pi.change, "pondering", term - ) - self.wait(2) - - self.terms = terms - - def analogies(self): - examples = VGroup() - plane = ComplexPlane( - x_radius = 2.5, - y_radius = 1.5, - ) - plane.add_coordinates() - plane.add(Circle(color = YELLOW)) - plane.scale(0.75) - examples.add(plane) - examples.add(Circle()) - examples.arrange(RIGHT, buff = 2) - examples.to_edge(UP, buff = LARGE_BUFF) - labels = VGroup(*list(map(TextMobject, ["2D", "3D"]))) - - title = TextMobject("Fly by instruments") - title.scale(1.5) - title.to_edge(UP) - - for label, example in zip(labels, examples): - label.next_to(example, DOWN) - self.play( - ShowCreation(example), - Write(label, run_time = 1) - ) - example.add(label) - self.wait() - self.wait() - self.play( - FadeOut(examples), - self.terms.shift, UP, - Write(title, run_time = 2) - ) - self.play(*[ - ApplyMethod( - pi.change, mode, self.terms.get_left(), - run_time = 2, - rate_func = squish_rate_func(smooth, a, a+0.5) - ) - for pi, mode, a in zip( - self.pi_creatures, - ["confused", "sassy", "erm"], - np.linspace(0, 0.5, len(self.pi_creatures)) - ) - ]) - self.wait() - self.play(Animation(self.terms[-1])) - self.wait(2) - - ###### - - def create_pi_creatures(self): - self.mathy = Mathematician() - self.physy = PiCreature(color = PINK) - self.compy = PiCreature(color = PURPLE) - pi_creatures = VGroup(self.mathy, self.compy, self.physy) - for pi in pi_creatures: - pi.scale(0.7) - pi_creatures.arrange(RIGHT, buff = 3) - pi_creatures.to_edge(DOWN, buff = LARGE_BUFF) - return pi_creatures - -class OfferAHybrid(SliderScene): - CONFIG = { - "n_sliders" : 3, - } - def construct(self): - self.remove(self.sliders) - titles = self.get_titles() - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(titles, DOWN) - v_lines = VGroup(*[ - Line(UP, DOWN).scale(FRAME_Y_RADIUS) - for x in range(2) - ]) - v_lines.generate_target() - for line, vect in zip(v_lines.target, [LEFT, RIGHT]): - line.shift(vect*FRAME_X_RADIUS/3) - - equation = TexMobject("x^2 + y^2 + z^2 = 1") - equation.generate_target() - equation.shift(FRAME_X_RADIUS*LEFT/2) - equation.target.shift(FRAME_WIDTH*LEFT/3) - - self.add(titles, h_line, v_lines, equation) - self.wait() - self.play(*list(map(MoveToTarget, [titles, v_lines, equation]))) - self.play(Write(self.sliders, run_time = 1)) - self.initialize_ambiant_slider_movement() - self.wait(10) - self.wind_down_ambient_movement() - self.wait() - - def get_titles(self): - titles = VGroup(*list(map(TextMobject, [ - "Analytic", "Hybrid", "Geometric" - ]))) - titles.to_edge(UP) - titles[1].set_color(BLUE) - titles.generate_target() - titles[1].scale_in_place(0.001) - titles[0].shift(FRAME_X_RADIUS*LEFT/2) - titles.target[0].shift(FRAME_WIDTH*LEFT/3) - titles[2].shift(FRAME_X_RADIUS*RIGHT/2) - titles.target[2].shift(FRAME_WIDTH*RIGHT/3) - return titles - -class TODOBoxExample(TODOStub): - CONFIG = { - "message" : "Box Example" - } - -class RotatingSphereWithWanderingPoint(ExternallyAnimatedScene): - pass - -class DismissProjection(PiCreatureScene): - CONFIG = { - "screen_rect_color" : WHITE, - "example_vect" : np.array([0.52, 0.26, 0.53, 0.60]), - } - def construct(self): - self.remove(self.pi_creature) - self.show_all_spheres() - self.discuss_4d_sphere_definition() - self.talk_through_animation() - self.transition_to_next_scene() - - def show_all_spheres(self): - equations = VGroup(*list(map(TexMobject, [ - "x^2 + y^2 = 1", - "x^2 + y^2 + z^2 = 1", - "x^2 + y^2 + z^2 + w^2 = 1", - ]))) - colors = [YELLOW, GREEN, BLUE] - for equation, edge, color in zip(equations, [LEFT, ORIGIN, RIGHT], colors): - equation.set_color(color) - equation.shift(3*UP) - equation.to_edge(edge) - equations[1].shift(LEFT) - - spheres = VGroup( - self.get_circle(equations[0]), - self.get_sphere_screen(equations[1], DOWN), - self.get_sphere_screen(equations[2], DOWN), - ) - - for equation, sphere in zip(equations, spheres): - self.play( - Write(equation), - LaggedStartMap(ShowCreation, sphere), - ) - self.wait() - - self.equations = equations - self.spheres = spheres - - def get_circle(self, equation): - result = VGroup( - NumberPlane( - x_radius = 2.5, - y_radius = 2, - ).fade(0.4), - Circle(color = YELLOW, radius = 1), - ) - result.scale(0.7) - result.next_to(equation, DOWN) - return result - - def get_sphere_screen(self, equation, vect): - square = Rectangle() - square.set_width(equation.get_width()) - square.stretch_to_fit_height(3) - square.next_to(equation, vect) - square.set_color(self.screen_rect_color) - return square - - def discuss_4d_sphere_definition(self): - sphere = self.spheres[-1] - equation = self.equations[-1] - - sphere_words = TextMobject("``4-dimensional sphere''") - sphere_words.next_to(sphere, DOWN+LEFT, buff = LARGE_BUFF) - arrow = Arrow( - sphere_words.get_right(), sphere.get_bottom(), - path_arc = np.pi/3, - color = BLUE - ) - descriptor = TexMobject( - "\\text{Just lists of numbers like }", - "(%.02f \\,, %.02f \\,, %.02f \\,, %.02f \\,)"%tuple(self.example_vect) - ) - descriptor[1].set_color(BLUE) - descriptor.next_to(sphere_words, DOWN) - dot = Dot(descriptor[1].get_top()) - dot.set_fill(WHITE, opacity = 0.75) - - self.play( - Write(sphere_words), - ShowCreation( - arrow, - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - run_time = 3, - ) - self.wait() - self.play(Write(descriptor, run_time = 2)) - self.wait() - self.play( - dot.move_to, equation.get_left(), - dot.set_fill, None, 0, - path_arc = -np.pi/12 - ) - self.wait(2) - - self.sphere_words = sphere_words - self.sphere_arrow = arrow - self.descriptor = descriptor - - def talk_through_animation(self): - sphere = self.spheres[-1] - - morty = self.pi_creature - alt_dims = VGroup(*list(map(TextMobject, ["5D", "6D", "7D"]))) - alt_dims.next_to(morty.eyes, UP, SMALL_BUFF) - alt_dim = alt_dims[0] - - self.play(FadeIn(morty)) - self.play(morty.change, "raise_right_hand", sphere) - self.wait(3) - self.play(morty.change, "confused", sphere) - self.wait(3) - self.play( - morty.change, "erm", alt_dims, - FadeIn(alt_dim) - ) - for new_alt_dim in alt_dims[1:]: - self.wait() - self.play(Transform(alt_dim, new_alt_dim)) - self.wait() - self.play(morty.change, "concerned_musician") - self.play(FadeOut(alt_dim)) - self.wait() - self.play(morty.change, "angry", sphere) - self.wait(2) - - def transition_to_next_scene(self): - equation = self.equations[-1] - self.equations.remove(equation) - tup = self.descriptor[1] - self.descriptor.remove(tup) - - equation.generate_target() - equation.target.center().to_edge(UP) - tup.generate_target() - tup.target.next_to(equation.target, DOWN) - tup.target.set_color(WHITE) - - self.play(LaggedStartMap(FadeOut, VGroup(*[ - self.equations, self.spheres, - self.sphere_words, self.sphere_arrow, - self.descriptor, - self.pi_creature - ]))) - self.play(*list(map(MoveToTarget, [equation, tup]))) - self.wait() - - ### - - def create_pi_creature(self): - return Mortimer().scale(0.8).to_corner(DOWN+RIGHT) - -class RotatingSphere(ExternallyAnimatedScene): - pass - -class Introduce4DSliders(SliderScene): - CONFIG = { - "slider_config" : { - "include_real_estate_ticks" : False, - "numbers_with_elongated_ticks" : [-1, 0, 1], - "tick_frequency" : 0.25, - "tick_size" : 0.05, - "dial_color" : YELLOW, - }, - "slider_spacing" : LARGE_BUFF, - } - def construct(self): - self.match_last_scene() - self.introduce_sliders() - self.ask_about_constraint() - - def match_last_scene(self): - self.start_vect = DismissProjection.CONFIG["example_vect"] - self.remove(self.sliders) - - equation = TexMobject("x^2 + y^2 + z^2 + w^2 = 1") - x, y, z, w = self.start_vect - tup = TexMobject( - "(", "%.02f \\,"%x, - ",", "%.02f \\,"%y, - ",", "%.02f \\,"%z, - ",", "%.02f \\,"%w, ")" - ) - equation.center().to_edge(UP) - equation.set_color(BLUE) - tup.next_to(equation, DOWN) - - self.sliders.next_to(tup, DOWN) - self.sliders.shift(0.8*LEFT) - - self.add(equation, tup) - self.wait() - self.equation = equation - self.tup = tup - - def introduce_sliders(self): - self.set_to_vector(self.start_vect) - - numbers = self.tup.get_parts_by_tex(".") - self.tup.remove(*numbers) - dials = VGroup(*[slider.dial for slider in self.sliders]) - dial_copies = dials.copy() - dials.set_fill(opacity = 0) - - self.play(LaggedStartMap(FadeIn, self.sliders)) - self.play(*[ - Transform( - num, dial, - run_time = 3, - rate_func = squish_rate_func(smooth, a, a+0.5), - remover = True - ) - for num, dial, a in zip( - numbers, dial_copies, - np.linspace(0, 0.5, len(numbers)) - ) - ]) - dials.set_fill(opacity = 1) - self.initialize_ambiant_slider_movement() - self.play(FadeOut(self.tup)) - self.wait(10) - - def ask_about_constraint(self): - equation = self.equation - rect = SurroundingRectangle(equation, color = GREEN) - randy = Randolph().scale(0.5) - randy.next_to(rect, DOWN+LEFT, LARGE_BUFF) - - self.play(ShowCreation(rect)) - self.play(FadeIn(randy)) - self.play(randy.change, "pondering", rect) - self.wait() - for mob in self.sliders, rect: - self.play(randy.look_at, mob) - self.play(Blink(randy)) - self.wait() - self.wait() - -class TwoDimensionalCase(Introduce4DSliders): - CONFIG = { - "n_sliders" : 2, - } - def setup(self): - SliderScene.setup(self) - self.sliders.shift(RIGHT) - for number in self.sliders[0].numbers: - value = int(number.get_tex_string()) - number.move_to(center_of_mass([ - slider.number_to_point(value) - for slider in self.sliders - ])) - - plane = NumberPlane( - x_radius = 2.5, - y_radius = 2.5, - ) - plane.fade(0.25) - plane.axes.set_color(GREY) - plane.add_coordinates() - plane.to_edge(LEFT) - origin = plane.coords_to_point(0, 0) - - circle = Circle(radius = 1, color = WHITE) - circle.move_to(plane.coords_to_point(*self.center_point)) - - dot = Dot(color = YELLOW) - dot.move_to(plane.coords_to_point(1, 0)) - - equation = TexMobject("x^2 + y^2 = 1") - equation.to_corner(UP + RIGHT) - - self.add(plane, circle, dot, equation) - self.add_foreground_mobjects(dot) - - self.plane = plane - self.circle = circle - self.dot = dot - self.equation = equation - - def construct(self): - self.let_values_wander() - self.introduce_real_estate() - self.let_values_wander(6) - self.comment_on_cheap_vs_expensive_real_estate() - self.nudge_x_from_one_example() - self.note_circle_steepness() - self.add_tick_marks() - self.write_distance_squared() - - def let_values_wander(self, total_time = 5): - self.initialize_ambiant_slider_movement() - self.wait(total_time - 1) - self.wind_down_ambient_movement() - - def introduce_real_estate(self): - x_squared_mob = VGroup(*self.equation[:2]) - y_squared_mob = VGroup(*self.equation[3:5]) - x_rect = SurroundingRectangle(x_squared_mob) - y_rect = SurroundingRectangle(y_squared_mob) - rects = VGroup(x_rect, y_rect) - - decimals = VGroup(*[ - DecimalNumber(num**2) - for num in self.get_vector() - ]) - decimals.arrange(RIGHT, buff = LARGE_BUFF) - decimals.next_to(rects, DOWN, LARGE_BUFF) - - real_estate_word = TextMobject("``Real estate''") - real_estate_word.next_to(decimals, DOWN, MED_LARGE_BUFF) - self.play(FadeIn(real_estate_word)) - - colors = GREEN, RED - arrows = VGroup() - for rect, decimal, color in zip(rects, decimals, colors): - rect.set_color(color) - decimal.set_color(color) - arrow = Arrow( - rect.get_bottom()+SMALL_BUFF*UP, decimal.get_top(), - tip_length = 0.2, - ) - arrow.set_color(color) - arrows.add(arrow) - - self.play(ShowCreation(rect)) - self.play( - ShowCreation(arrow), - Write(decimal) - ) - self.wait() - - sliders = self.sliders - def create_update_func(i): - return lambda alpha : sliders[i].get_real_estate() - - self.add_foreground_mobjects(decimals) - self.decimals = decimals - self.decimal_update_anims = [ - ChangingDecimal(decimal, create_update_func(i)) - for i, decimal in enumerate(decimals) - ] - self.real_estate_word = real_estate_word - - def comment_on_cheap_vs_expensive_real_estate(self): - blue_rects = VGroup() - red_rects = VGroup() - for slider in self.sliders: - for x1, x2 in (-0.5, 0.5), (0.75, 1.0), (-1.0, -0.75): - p1, p2 = list(map(slider.number_to_point, [x1, x2])) - rect = Rectangle( - stroke_width = 0, - fill_opacity = 0.5, - width = 0.25, - height = (p2-p1)[1] - ) - rect.move_to((p1+p2)/2) - if np.mean([x1, x2]) == 0: - rect.set_color(BLUE) - blue_rects.add(rect) - else: - rect.set_color(RED) - red_rects.add(rect) - - blue_rects.save_state() - self.play(DrawBorderThenFill(blue_rects)) - self.wait() - self.play(ReplacementTransform(blue_rects, red_rects)) - self.wait() - self.play(FadeOut(red_rects)) - - blue_rects.restore() - self.real_estate_rects = VGroup(blue_rects, red_rects) - - def nudge_x_from_one_example(self): - x_re = self.decimals[0] - rect = SurroundingRectangle(x_re) - - self.reset_dials([1, 0]) - self.wait() - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.play(FocusOn(self.dot)) - self.wait() - self.reset_dials([0.9, -np.sqrt(0.19)]) - - x_brace, y_brace = [ - Brace( - VGroup(slider.dial, Dot(slider.number_to_point(0))), - vect - ) - for slider, vect in zip(self.sliders, [LEFT, RIGHT]) - ] - x_text = x_brace.get_tex("0.9") - y_text = y_brace.get_tex("%.02f"%self.sliders[1].get_value()) - - self.play( - GrowFromCenter(x_brace), - Write(x_text) - ) - self.play(ReplacementTransform( - VGroup(x_text.copy()), x_re - )) - self.wait(2) - self.play( - GrowFromCenter(y_brace), - Write(y_text), - ) - self.wait(2) - self.play(FadeIn(self.real_estate_rects)) - self.reset_dials([1, 0], run_time = 1) - self.reset_dials([0.9, -np.sqrt(0.19)], run_time = 2) - self.play(FadeOut(self.real_estate_rects)) - self.play(*list(map(FadeOut, [x_brace, y_brace, x_text, y_text]))) - self.wait() - - def note_circle_steepness(self): - line = Line( - self.plane.coords_to_point(0.5, 1), - self.plane.coords_to_point(1.5, -1), - ) - rect = Rectangle( - stroke_width = 0, - fill_color = BLUE, - fill_opacity = 0.5, - ) - rect.replace(line, stretch = True) - - self.play(DrawBorderThenFill(rect, stroke_color = YELLOW)) - for x, u in (1, 1), (0.8, 1), (1, 1), (0.8, -1), (1, 1): - self.reset_dials([x, u*np.sqrt(1 - x**2)]) - self.play(FadeOut(rect)) - - def add_tick_marks(self): - self.remove_foreground_mobjects(self.sliders) - self.add(self.sliders) - old_ticks = VGroup() - all_ticks = VGroup() - for slider in self.sliders: - slider.tick_size = 0.1 - slider.add_real_estate_ticks() - slider.remove(slider.get_tick_marks()) - all_ticks.add(*slider.real_estate_ticks) - old_ticks.add(*slider.get_tick_marks()[:-3]) - - self.play( - FadeOut(old_ticks), - ShowCreation(all_ticks, run_time = 3), - Animation(VGroup(*[slider.dial for slider in self.sliders])), - ) - self.add_foreground_mobjects(self.sliders) - self.wait() - for x in np.arange(0.95, 0.05, -0.05): - self.reset_dials( - [np.sqrt(x), np.sqrt(1-x)], - run_time = 0.5 - ) - self.wait(0.5) - self.initialize_ambiant_slider_movement() - self.wait(10) - - def write_distance_squared(self): - d_squared = TexMobject("(\\text{Distance})^2") - d_squared.next_to(self.real_estate_word, DOWN) - d_squared.set_color(YELLOW) - - self.play(Write(d_squared)) - self.wait(3) - - ##### - - def update_frame(self, *args, **kwargs): - if hasattr(self, "dot"): - x, y = self.get_vector() - self.dot.move_to(self.plane.coords_to_point(x, y)) - if hasattr(self, "decimals"): - for anim in self.decimal_update_anims: - anim.update(0) - SliderScene.update_frame(self, *args, **kwargs) - -class TwoDimensionalCaseIntro(TwoDimensionalCase): - def construct(self): - self.initialize_ambiant_slider_movement() - self.wait(10) - -class ThreeDCase(TwoDimensionalCase): - CONFIG = { - "n_sliders" : 3, - "slider_config" : { - "include_real_estate_ticks" : True, - "numbers_with_elongated_ticks" : [], - "tick_frequency" : 1, - "tick_size" : 0.1, - }, - } - def setup(self): - SliderScene.setup(self) - self.equation = TexMobject( - "x^2", "+", "y^2", "+", "z^2", "=", "1" - ) - self.equation.to_corner(UP+RIGHT) - self.add(self.equation) - - def construct(self): - self.force_skipping() - - self.add_real_estate_decimals() - self.initialize_ambiant_slider_movement() - self.point_out_third_slider() - self.wait(3) - self.hold_x_at(0.5, 12) - self.revert_to_original_skipping_status() - self.hold_x_at(0.85, 12) - return - self.hold_x_at(1, 5) - - def add_real_estate_decimals(self): - rects = VGroup(*[ - SurroundingRectangle(self.equation.get_part_by_tex(char)) - for char in "xyz" - ]) - - decimals = VGroup(*[ - DecimalNumber(num**2) - for num in self.get_vector() - ]) - decimals.arrange(RIGHT, buff = LARGE_BUFF) - decimals.next_to(rects, DOWN, LARGE_BUFF) - - colors = [GREEN, RED, BLUE] - arrows = VGroup() - for rect, decimal, color in zip(rects, decimals, colors): - rect.set_color(color) - decimal.set_color(color) - arrow = Arrow( - rect.get_bottom()+SMALL_BUFF*UP, decimal.get_top(), - tip_length = 0.2, - color = color - ) - arrows.add(arrow) - real_estate_word = TextMobject("``Real estate''") - real_estate_word.next_to(decimals, DOWN, MED_LARGE_BUFF) - - sliders = self.sliders - def create_update_func(i): - return lambda alpha : sliders[i].get_real_estate() - self.add_foreground_mobjects(decimals) - self.decimals = decimals - self.decimal_update_anims = [ - ChangingDecimal(decimal, create_update_func(i)) - for i, decimal in enumerate(decimals) - ] - self.add(rects, arrows, real_estate_word) - self.rects = rects - self.arrows = arrows - self.real_estate_word = real_estate_word - - def point_out_third_slider(self): - rect = SurroundingRectangle(self.sliders[-1]) - self.wait(4) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.wait(8) - - def hold_x_at(self, x_val, wait_time): - #Save these - all_sliders = self.sliders - original_total_real_estate = self.total_real_estate - - self.reset_dials([x_val], run_time = 3) - self.sliders = VGroup(*self.sliders[1:]) - self.total_real_estate = self.total_real_estate-x_val**2 - self.initialize_ambiant_slider_movement() - self.wait(wait_time-2) - self.wind_down_ambient_movement() - self.sliders = all_sliders - self.total_real_estate = original_total_real_estate - self.initialize_ambiant_slider_movement() - - #### - -class ThreeDCaseInsert(ThreeDCase): - def construct(self): - self.add_real_estate_decimals() - self.reset_dials([0.85, np.sqrt(1-0.85**2)], run_time = 0) - self.reset_dials([1], run_time = 3) - self.wait() - -class SphereAtRest(ExternallyAnimatedScene): - pass - -class BugOnASurface(TeacherStudentsScene): - def construct(self): - self.teacher_says("You're a bug \\\\ on a surface") - self.wait(3) - -class SphereWithWanderingDotAtX0point5(ExternallyAnimatedScene): - pass - -class MoveSphereSliceFromPoint5ToPoint85(ExternallyAnimatedScene): - pass - -class SphereWithWanderingDotAtX0point85(ExternallyAnimatedScene): - pass - -class MoveSphereSliceFromPoint85To1(ExternallyAnimatedScene): - pass - -class BugOnTheSurfaceSlidersPart(ThreeDCase): - CONFIG = { - "run_time" : 30 - } - def construct(self): - self.add_real_estate_decimals() - self.reset_dials([0.9], run_time = 0) - time_range = np.arange(0, self.run_time, self.frame_duration) - for time in ProgressDisplay(time_range): - t = 0.3*np.sin(2*np.pi*time/7.0) + 1 - u = 0.3*np.sin(4*np.pi*time/7.0) + 1.5 - self.set_to_vector([ - np.cos(u), - np.sin(u)*np.cos(t), - np.sin(u)*np.sin(t), - ]) - self.wait(self.frame_duration) - -class BugOnTheSurfaceSpherePart(ExternallyAnimatedScene): - pass - -class FourDCase(SliderScene, TeacherStudentsScene): - def setup(self): - TeacherStudentsScene.setup(self) - SliderScene.setup(self) - self.sliders.scale(0.9) - self.sliders.to_edge(UP) - self.sliders.shift(2*RIGHT) - self.initialize_ambiant_slider_movement() - - def construct(self): - self.show_initial_exchange() - self.fix_one_slider() - self.ask_now_what() - self.set_aside_sliders() - - def show_initial_exchange(self): - dot = Dot(fill_opacity = 0) - dot.to_corner(UP+LEFT, buff = 2) - self.play(Animation(dot)) - self.wait() - self.play( - Animation(self.sliders), - self.teacher.change, "raise_right_hand", - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = self.sliders - ) - self.wait(4) - - def fix_one_slider(self): - x_slider = self.sliders[0] - dial = x_slider.dial - self.wind_down_ambient_movement(wait = False) - self.play(self.teacher.change, "speaking") - self.sliders.remove(x_slider) - self.total_real_estate = get_norm(self.get_vector())**2 - self.initialize_ambiant_slider_movement() - arrow = Arrow(LEFT, RIGHT, color = GREEN) - arrow.next_to(dial, LEFT) - self.play( - ShowCreation(arrow), - dial.set_color, arrow.get_color() - ) - self.change_student_modes( - "erm", "confused", "hooray", - look_at_arg = self.sliders, - added_anims = [self.teacher.change, "plain"] - ) - self.wait(5) - - self.x_slider = x_slider - self.x_arrow = arrow - - def ask_now_what(self): - self.student_says( - "Okay...now what?", - target_mode = "raise_left_hand", - student_index = 0, - added_anims = [self.teacher.change, "plain"] - ) - self.change_student_modes( - None, "pondering", "pondering", - look_at_arg = self.students[0].bubble, - ) - self.wait(4) - self.play(RemovePiCreatureBubble(self.students[0])) - - def set_aside_sliders(self): - self.sliders.add(self.x_slider) - self.total_real_estate = 1 - self.initialize_ambiant_slider_movement() - self.play( - self.sliders.scale, 0.5, - self.sliders.to_corner, UP+RIGHT, - FadeOut(self.x_arrow) - ) - self.teacher_says( - "Time for some \\\\ high-dimensional \\\\ strangeness!", - target_mode = "hooray", - ) - self.wait(7) - - ##### - def non_blink_wait(self, time = 1): - SliderScene.wait(self, time) - -class TwoDBoxExample(Scene): - def setup(self): - scale_factor = 1.7 - self.plane = NumberPlane() - self.plane.scale(scale_factor) - self.plane.add_coordinates() - self.plane.axes.set_color(GREY) - self.add(self.plane) - - def construct(self): - self.add_box() - self.label_corner_coordinates() - self.add_corner_circles() - self.add_center_circle() - self.compute_radius() - - def add_box(self): - box = Square(color = RED, stroke_width = 6) - line = Line( - self.plane.coords_to_point(-1, -1), - self.plane.coords_to_point(1, 1), - ) - box.replace(line, stretch = True) - self.play(ShowCreation(box)) - self.wait() - - def label_corner_coordinates(self): - corner_dots = VGroup() - coords_group = VGroup() - for x, y in it.product(*[[1, -1]]*2): - point = self.plane.coords_to_point(x, y) - dot = Dot(point, color = WHITE) - coords = TexMobject("(%d, %d)"%(x, y)) - coords.add_background_rectangle() - coords.next_to(point, point, SMALL_BUFF) - corner_dots.add(dot) - coords_group.add(coords) - - self.play( - ShowCreation(dot), - Write(coords, run_time = 1) - ) - - self.add_foreground_mobjects(coords_group) - self.corner_dots = corner_dots - self.coords_group = coords_group - - def add_corner_circles(self): - line = Line( - self.plane.coords_to_point(-1, 0), - self.plane.coords_to_point(1, 0), - ) - circle = Circle(color = YELLOW) - circle.replace(line, dim_to_match = 0) - circles = VGroup(*[ - circle.copy().move_to(dot) - for dot in self.corner_dots - ]) - - radius = Line(ORIGIN, self.plane.coords_to_point(1, 0)) - radius.set_stroke(GREY, 6) - radius.rotate(-np.pi/4) - c0_center = circles[0].get_center() - radius.shift(c0_center) - r_equals_1 = TexMobject("r = 1") - r_equals_1.add_background_rectangle() - r_equals_1.next_to( - radius.point_from_proportion(0.75), - UP+RIGHT, SMALL_BUFF - ) - - self.play(LaggedStartMap(ShowCreation, circles)) - self.play( - ShowCreation(radius), - Write(r_equals_1) - ) - for angle in -np.pi/4, -np.pi/2, 3*np.pi/4: - self.play(Rotating( - radius, about_point = c0_center, - radians = angle, - run_time = 1, - rate_func = smooth, - )) - self.wait(0.5) - self.play(*list(map(FadeOut, [radius, r_equals_1]))) - self.wait() - - self.corner_radius = radius - self.corner_circles = circles - - def add_center_circle(self): - r = np.sqrt(2) - 1 - radius = Line(ORIGIN, self.plane.coords_to_point(r, 0)) - radius.set_stroke(WHITE) - circle = Circle(color = GREEN) - circle.replace( - VGroup(radius, radius.copy().rotate(np.pi)), - dim_to_match = 0 - ) - radius.rotate(np.pi/4) - r_equals_q = TexMobject("r", "= ???") - r_equals_q[1].add_background_rectangle() - r_equals_q.next_to(radius, RIGHT, buff = -SMALL_BUFF) - - self.play(GrowFromCenter(circle, run_time = 2)) - self.play(circle.scale, 1.2, rate_func = wiggle) - self.play(ShowCreation(radius)) - self.play(Write(r_equals_q)) - self.wait(2) - self.play(FadeOut(r_equals_q[1])) - - self.inner_radius = radius - self.inner_circle = circle - self.inner_r = r_equals_q[0] - - def compute_radius(self): - triangle = Polygon( - ORIGIN, - self.plane.coords_to_point(1, 0), - self.plane.coords_to_point(1, 1), - fill_color = BLUE, - fill_opacity = 0.5, - stroke_width = 6, - stroke_color = WHITE, - ) - bottom_one = TexMobject("1") - bottom_one.next_to(triangle.get_bottom(), UP, SMALL_BUFF) - bottom_one.shift(MED_SMALL_BUFF*RIGHT) - side_one = TexMobject("1") - side_one.next_to(triangle, RIGHT) - sqrt_1_plus_1 = TexMobject("\\sqrt", "{1^2 + 1^2}") - sqrt_2 = TexMobject("\\sqrt", "{2}") - for sqrt in sqrt_1_plus_1, sqrt_2: - sqrt.add_background_rectangle() - sqrt.next_to(ORIGIN, UP, SMALL_BUFF) - sqrt.rotate(np.pi/4) - sqrt.shift(triangle.get_center()) - - root_2_value = TexMobject("\\sqrt{2} \\approx 1.414") - root_2_value.to_corner(UP+RIGHT) - root_2_value.add_background_rectangle() - root_2_minus_1_value = TexMobject( - "\\sqrt{2} - 1 \\approx 0.414" - ) - root_2_minus_1_value.next_to(root_2_value, DOWN) - root_2_minus_1_value.to_edge(RIGHT) - root_2_minus_1_value.add_background_rectangle() - - corner_radius = self.corner_radius - c0_center = self.corner_circles[0].get_center() - corner_radius.rotate(-np.pi/2, about_point = c0_center) - - rhs = TexMobject("=", "\\sqrt", "{2}", "-1") - rhs.next_to(self.inner_r, RIGHT, SMALL_BUFF, DOWN) - rhs.shift(0.5*SMALL_BUFF*DOWN) - sqrt_2_target = VGroup(*rhs[1:3]) - rhs.add_background_rectangle() - - self.play( - FadeIn(triangle), - Write(VGroup(bottom_one, side_one, sqrt_1_plus_1)) - ) - self.wait(2) - self.play(ReplacementTransform(sqrt_1_plus_1, sqrt_2)) - self.play( - Write(root_2_value, run_time = 1), - *list(map(FadeOut, [bottom_one, side_one])) - ) - self.wait() - self.play(ShowCreation(corner_radius)) - self.play(Rotating( - corner_radius, about_point = c0_center, - run_time = 2, - rate_func = smooth - )) - self.play(FadeOut(triangle), Animation(corner_radius)) - self.wait() - self.play( - Write(rhs), - Transform(sqrt_2, sqrt_2_target), - ) - self.play(Write(root_2_minus_1_value)) - self.wait(2) - -class ThreeDBoxExample(ExternallyAnimatedScene): - pass - -class ThreeDCubeCorners(Scene): - def construct(self): - coordinates = VGroup(*[ - TexMobject("(%d,\\, %d,\\, %d)"%(x, y, z)) - for x, y, z in it.product(*3*[[1, -1]]) - ]) - coordinates.arrange(DOWN, aligned_edge = LEFT) - name = TextMobject("Corners: ") - name.next_to(coordinates[0], LEFT) - group = VGroup(name, coordinates) - group.set_height(FRAME_HEIGHT - 1) - group.to_edge(LEFT) - - self.play(Write(name, run_time = 2)) - self.play(LaggedStartMap(FadeIn, coordinates, run_time = 3)) - self.wait() - -class ShowDistanceFormula(TeacherStudentsScene): - def construct(self): - rule = TexMobject( - "||(", "x_1", ", ", "x_2", "\\dots, ", "x_n", ")||", - "=", - "\\sqrt", "{x_1^2", " + ", "x_2^2", " +\\cdots", "x_n^2", "}" - ) - rule.set_color_by_tex_to_color_map({ - "x_1" : GREEN, - "x_2" : RED, - "x_n" : BLUE, - }) - for part in rule.get_parts_by_tex("x_"): - if len(part) > 2: - part[1].set_color(WHITE) - rule.next_to(self.teacher, UP, LARGE_BUFF) - rule.to_edge(RIGHT) - rule.shift(UP) - - rule.save_state() - rule.shift(2*DOWN) - rule.set_fill(opacity = 0) - - self.play( - rule.restore, - self.teacher.change, "raise_right_hand", - ) - self.wait(3) - self.student_says("Why?", student_index = 0) - self.play(self.teacher.change, "thinking") - self.wait(3) - -class GeneralizePythagoreanTheoremBeyondTwoD(ThreeDScene): - def construct(self): - tex_to_color_map = { - "x" : GREEN, - "y" : RED, - "z" : BLUE, - } - rect = Rectangle( - height = 4, width = 5, - fill_color = WHITE, - fill_opacity = 0.2, - ) - diag = Line( - rect.get_corner(DOWN+LEFT), - rect.get_corner(UP+RIGHT), - color = YELLOW - ) - bottom = Line(rect.get_left(), rect.get_right()) - bottom.move_to(rect.get_bottom()) - bottom.set_color(tex_to_color_map["x"]) - side = Line(rect.get_bottom(), rect.get_top()) - side.move_to(rect.get_right()) - side.set_color(tex_to_color_map["y"]) - - x = TexMobject("x") - x.next_to(rect.get_bottom(), UP, SMALL_BUFF) - y = TexMobject("y") - y.next_to(rect.get_right(), LEFT, SMALL_BUFF) - hyp = TexMobject("\\sqrt", "{x", "^2 + ", "y", "^2}") - hyp.set_color_by_tex_to_color_map(tex_to_color_map) - hyp.next_to(ORIGIN, UP) - hyp.rotate(diag.get_angle()) - hyp.shift(diag.get_center()) - group = VGroup(rect, bottom, side, diag, x, y, hyp) - - self.add(rect) - for line, tex in (bottom, x), (side, y), (diag, hyp): - self.play( - ShowCreation(line), - Write(tex, run_time = 1) - ) - self.wait() - self.play( - group.rotate, 0.45*np.pi, LEFT, - group.shift, 2*DOWN - ) - - corner = diag.get_end() - z_line = Line(corner, corner + 3*UP) - z_line.set_color(tex_to_color_map["z"]) - z = TexMobject("z") - z.set_color(tex_to_color_map["z"]) - z.next_to(z_line, RIGHT) - dot = Dot(z_line.get_end()) - three_d_diag = Line(diag.get_start(), z_line.get_end()) - three_d_diag.set_color(MAROON_B) - - self.play( - ShowCreation(z_line), - ShowCreation(dot), - Write(z, run_time = 1) - ) - self.play(ShowCreation(three_d_diag)) - self.wait() - - full_group = VGroup(group, z_line, z, three_d_diag, dot) - self.play(Rotating( - full_group, radians = -np.pi/6, - axis = UP, - run_time = 10, - )) - self.wait() - -class ThreeDBoxFormulas(Scene): - def construct(self): - question = TexMobject( - "||(1, 1, 1)||", "=", "???" - ) - answer = TexMobject( - "||(1, 1, 1)||", "&=", "\\sqrt{1^2 + 1^2 + 1^2}\\\\", - "&= \\sqrt{3}\\\\", "&\\approx", "1.73", - ) - for mob in question, answer: - mob.to_corner(UP+LEFT) - inner_r = TexMobject( - "\\text{Inner radius}", "&=", "\\sqrt{3} - 1\\\\", - "&\\approx", "0.73" - ) - inner_r.next_to(answer, DOWN, LARGE_BUFF, LEFT) - inner_r.set_color(GREEN_C) - VGroup(question, answer).shift(0.55*RIGHT) - - self.play(Write(question)) - self.wait(2) - self.play(ReplacementTransform(question, answer)) - self.wait(2) - self.play(Write(inner_r)) - self.wait(2) - -class AskAboutHigherDimensions(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "What happens for \\\\ higher dimensions?" - ) - self.change_student_modes(*["pondering"]*3) - self.wait(2) - self.student_thinks( - "$\\sqrt{N} - 1$", - target_mode = "happy", - student_index = 1 - ) - self.wait() - pi = self.students[1] - self.play(pi.change, "confused", pi.bubble) - self.wait(3) - -class TenSliders(SliderScene): - CONFIG = { - "n_sliders" : 10, - "run_time": 30, - "slider_spacing" : 0.75, - "ambient_acceleration_magnitude" : 2.0, - } - def construct(self): - self.initialize_ambiant_slider_movement() - self.wait(self.run_time) - self.wind_down_ambient_movement() - -class TwoDBoxWithSliders(TwoDimensionalCase): - CONFIG = { - "slider_config" : { - "include_real_estate_ticks" : True, - "tick_frequency" : 1, - "numbers_with_elongated_ticks" : [], - "tick_size" : 0.1, - "dial_color" : YELLOW, - "x_min" : -2, - "x_max" : 2, - "unit_size" : 1.5, - }, - "center_point" : [1, -1], - } - def setup(self): - TwoDimensionalCase.setup(self) - ##Correct from previous setup - self.remove(self.equation) - self.sliders.shift(RIGHT) - VGroup(*self.get_top_level_mobjects()).shift(RIGHT) - x_slider = self.sliders[0] - for number in x_slider.numbers: - value = int(number.get_tex_string()) - number.next_to( - x_slider.number_to_point(value), - LEFT, MED_SMALL_BUFF - ) - self.plane.axes.set_color(BLUE) - - ##Add box material - corner_circles = VGroup(*[ - self.circle.copy().move_to( - self.plane.coords_to_point(*coords) - ).set_color(GREY) - for coords in ((1, 1), (-1, 1), (-1, -1)) - ]) - line = Line( - self.plane.coords_to_point(-1, -1), - self.plane.coords_to_point(1, 1), - ) - box = Square(color = RED) - box.replace(line, stretch = True) - - self.add(box, corner_circles) - self.box = box - self.corner_circles = corner_circles - - def construct(self): - self.ask_about_off_center_circle() - self.recenter_circle() - self.write_x_and_y_real_estate() - self.swap_with_top_right_circle() - self.show_center_circle() - self.describe_tangent_point() - self.perterb_point() - self.wander_on_inner_circle() - self.ask_about_inner_real_estate() - - def ask_about_off_center_circle(self): - question = TextMobject("Off-center circles?") - question.next_to(self.plane, UP) - - self.initialize_ambiant_slider_movement() - self.play(Write(question)) - self.wait(4) - self.wind_down_ambient_movement() - - self.question = question - - def recenter_circle(self): - original_center_point = self.center_point - - self.play( - self.circle.move_to, self.plane.coords_to_point(0, 0), - Animation(self.sliders), - *[ - ApplyMethod( - mob.shift, - slider.number_to_point(0)-slider.number_to_point(slider.center_value) - ) - for slider in self.sliders - for mob in [slider.real_estate_ticks, slider.dial] - ] - ) - self.center_point = [0, 0] - for x, slider in zip(self.center_point, self.sliders): - slider.center_value = x - self.initialize_ambiant_slider_movement() - self.wait(7) - self.wind_down_ambient_movement() - self.play( - self.circle.move_to, - self.plane.coords_to_point(*original_center_point), - Animation(self.sliders), - *[ - ApplyMethod( - mob.shift, - slider.number_to_point(x)-slider.number_to_point(0) - ) - for x, slider in zip(original_center_point, self.sliders) - for mob in [slider.real_estate_ticks, slider.dial] - ] - ) - self.center_point = original_center_point - for x, slider in zip(self.center_point, self.sliders): - slider.center_value = x - - self.initialize_ambiant_slider_movement() - self.wait(5) - - def write_x_and_y_real_estate(self): - phrases = VGroup( - TextMobject("$x$", "real estate:", "$(x-1)^2$"), - TextMobject("$y$", "real estate:", "$(y+1)^2$"), - ) - phrases.next_to(self.plane, UP) - phrases[0].set_color_by_tex("x", GREEN) - phrases[1].set_color_by_tex("y", RED) - x_brace, y_brace = [ - Brace(slider.real_estate_ticks, RIGHT) - for slider in self.sliders - ] - x_brace.set_color(GREEN) - y_brace.set_color(RED) - - self.play(FadeOut(self.question)) - self.play( - Write(phrases[0]), - GrowFromCenter(x_brace) - ) - self.wait(3) - self.play( - Transform(*phrases), - Transform(x_brace, y_brace) - ) - self.wait(5) - self.wind_down_ambient_movement(wait = False) - self.play(*list(map(FadeOut, [x_brace, phrases[0]]))) - - def swap_with_top_right_circle(self): - alt_circle = self.corner_circles[0] - slider = self.sliders[1] - - self.play( - self.circle.move_to, alt_circle, - alt_circle.move_to, self.circle, - Animation(slider), - *[ - ApplyMethod( - mob.shift, - slider.number_to_point(1) - slider.number_to_point(-1) - ) - for mob in (slider.real_estate_ticks, slider.dial) - ] - ) - slider.center_value = 1 - self.center_point[1] = 1 - self.initialize_ambiant_slider_movement() - self.wait(3) - - def show_center_circle(self): - origin = self.plane.coords_to_point(0, 0) - radius = get_norm( - self.plane.coords_to_point(np.sqrt(2)-1, 0) - origin - ) - circle = Circle(radius = radius, color = GREEN) - circle.move_to(origin) - - self.play(FocusOn(circle)) - self.play(GrowFromCenter(circle, run_time = 2)) - self.wait(3) - - def describe_tangent_point(self): - target_vector = np.array([ - 1-np.sqrt(2)/2, 1-np.sqrt(2)/2 - ]) - point = self.plane.coords_to_point(*target_vector) - origin = self.plane.coords_to_point(0, 0) - h_line = Line(point[1]*UP + origin[0]*RIGHT, point) - v_line = Line(point[0]*RIGHT+origin[1]*UP, point) - - while get_norm(self.get_vector()-target_vector) > 0.5: - self.wait() - self.wind_down_ambient_movement(0) - self.reset_dials(target_vector) - self.play(*list(map(ShowCreation, [h_line, v_line]))) - self.wait() - - re_line = DashedLine( - self.sliders[0].dial.get_left() + MED_SMALL_BUFF*LEFT, - self.sliders[1].dial.get_right() + MED_SMALL_BUFF*RIGHT, - ) - words = TextMobject("Evenly shared \\\\ real estate") - words.scale(0.8) - words.next_to(re_line, RIGHT) - self.play(ShowCreation(re_line)) - self.play(Write(words)) - self.wait() - - self.evenly_shared_words = words - self.re_line = re_line - - def perterb_point(self): - #Perturb dials - target_vector = np.array([ - 1 - np.sqrt(0.7), - 1 - np.sqrt(0.3), - ]) - ghost_dials = VGroup(*[ - slider.dial.copy() - for slider in self.sliders - ]) - ghost_dials.set_fill(WHITE, opacity = 0.75) - - self.add_foreground_mobjects(ghost_dials) - self.reset_dials(target_vector) - self.wait() - - #Comment on real estate exchange - x_words = TextMobject("Gain expensive \\\\", "real estate") - y_words = TextMobject("Give up cheap \\\\", "real estate") - VGroup(x_words, y_words).scale(0.8) - x_words.next_to(self.re_line, UP+LEFT) - x_words.shift(SMALL_BUFF*(DOWN+LEFT)) - y_words.next_to(self.re_line, UP+RIGHT) - y_words.shift(MED_LARGE_BUFF*UP) - - x_arrow, y_arrow = [ - Arrow( - words[1].get_edge_center(vect), self.sliders[i].dial, - tip_length = 0.15, - ) - for i, words, vect in zip( - (0, 1), [x_words, y_words], [RIGHT, LEFT] - ) - ] - - self.play( - Write(x_words, run_time = 2), - ShowCreation(x_arrow) - ) - self.wait() - self.play(FadeOut(self.evenly_shared_words)) - self.play( - Write(y_words, run_time = 2), - ShowCreation(y_arrow) - ) - self.wait(2) - - #Swap perspective - word_starts = VGroup(y_words[0], x_words[0]) - crosses = VGroup() - new_words = VGroup() - for w1, w2 in zip(word_starts, reversed(word_starts)): - crosses.add(Cross(w1)) - w1_copy = w1.copy() - w1_copy.generate_target() - w1_copy.target.next_to(w2, UP, SMALL_BUFF) - new_words.add(w1_copy) - - self.play(*[ - ApplyMethod( - slider.real_estate_ticks.shift, - slider.number_to_point(0)-slider.number_to_point(1) - ) - for slider in self.sliders - ]) - self.wait() - self.play(ShowCreation(crosses)) - self.play( - LaggedStartMap(MoveToTarget, new_words), - Animation(crosses) - ) - self.wait(3) - - #Return to original position - target_vector = np.array(2*[1-np.sqrt(0.5)]) - self.play(LaggedStartMap(FadeOut, VGroup(*[ - ghost_dials, - x_words, y_words, - x_arrow, y_arrow, - crosses, new_words, - ]))) - self.remove_foreground_mobjects(ghost_dials) - self.reset_dials(target_vector) - self.center_point = np.zeros(2) - for x, slider in zip(self.center_point, self.sliders): - slider.center_value = x - self.set_to_vector(target_vector) - self.total_real_estate = self.get_current_total_real_estate() - self.wait(2) - - def wander_on_inner_circle(self): - self.initialize_ambiant_slider_movement() - self.wait(9) - - def ask_about_inner_real_estate(self): - question = TextMobject("What is \\\\ $x^2 + y^2$?") - question.next_to(self.re_line, RIGHT) - - rhs = TexMobject("<0.5^2 + 0.5^2") - rhs.scale(0.8) - rhs.next_to(question, DOWN) - rhs.to_edge(RIGHT) - - half_line = Line(*[ - slider.number_to_point(0.5) + MED_LARGE_BUFF*vect - for slider, vect in zip(self.sliders, [LEFT, RIGHT]) - ]) - half = TexMobject("0.5") - half.scale(self.sliders[0].number_scale_val) - half.next_to(half_line, LEFT, SMALL_BUFF) - - target_vector = np.array(2*[1-np.sqrt(0.5)]) - while get_norm(target_vector - self.get_vector()) > 0.5: - self.wait() - self.wind_down_ambient_movement(0) - self.reset_dials(target_vector) - self.play(Write(question)) - self.wait(3) - self.play( - ShowCreation(half_line), - Write(half) - ) - self.wait() - self.play(Write(rhs)) - self.wait(3) - -class AskWhy(TeacherStudentsScene): - def construct(self): - self.student_says( - "Wait, why?", - target_mode = "confused" - ) - self.wait(3) - -class MentionComparisonToZeroPointFive(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Comparing to $0.5$ will \\\\"+\ - "be surprisingly useful!", - target_mode = "hooray" - ) - self.change_student_modes(*["happy"]*3) - self.wait(3) - -class ThreeDBoxExampleWithSliders(SliderScene): - CONFIG = { - "n_sliders" : 3, - "slider_config" : { - "x_min" : -2, - "x_max" : 2, - "unit_size" : 1.5, - }, - "center_point" : np.ones(3), - } - def setup(self): - SliderScene.setup(self) - self.sliders.shift(2*RIGHT) - - def construct(self): - self.initialize_ambiant_slider_movement() - self.name_corner_sphere() - self.point_out_closest_point() - self.compare_to_halfway_point() - self.reframe_as_inner_sphere_point() - self.place_bound_on_inner_real_estate() - self.comment_on_inner_sphere_smallness() - - def name_corner_sphere(self): - sphere_name = TextMobject( - """Sphere with radius 1\\\\ - centered at (1, 1, 1)""" - ) - sphere_name.to_corner(UP+LEFT) - arrow = Arrow( - sphere_name, VGroup(*self.sliders[0].numbers[-2:]), - color = BLUE - ) - - self.play( - LaggedStartMap(FadeIn, sphere_name,), - ShowCreation(arrow, rate_func = squish_rate_func(smooth, 0.7, 1)), - run_time = 3 - ) - self.wait(5) - - self.sphere_name = sphere_name - self.arrow = arrow - - def point_out_closest_point(self): - target_x = 1-np.sqrt(1./3) - target_vector = np.array(3*[target_x]) - re_words = TextMobject( - "$x$, $y$ and $z$ each have \\\\", - "$\\frac{1}{3}$", "units of real estate" - ) - re_words.to_corner(UP+LEFT) - re_line = DashedLine(*[ - self.sliders[i].number_to_point(target_x) + MED_SMALL_BUFF*vect - for i, vect in [(0, LEFT), (2, RIGHT)] - ]) - new_arrow = Arrow( - re_words.get_corner(DOWN+RIGHT), re_line.get_left(), - color = BLUE - ) - - self.wind_down_ambient_movement() - self.play(*[ - ApplyMethod(slider.set_value, x) - for x, slider in zip(target_vector, self.sliders) - ]) - self.play(ShowCreation(re_line)) - self.play( - FadeOut(self.sphere_name), - Transform(self.arrow, new_arrow), - ) - self.play(LaggedStartMap(FadeIn, re_words)) - self.wait(2) - - self.re_words = re_words - self.re_line = re_line - - def compare_to_halfway_point(self): - half_line = Line(*[ - self.sliders[i].number_to_point(0.5)+MED_SMALL_BUFF*vect - for i, vect in [(0, LEFT), (2, RIGHT)] - ]) - half_line.set_stroke(MAROON_B, 6) - half_label = TexMobject("0.5") - half_label.scale(self.sliders[0].number_scale_val) - half_label.next_to(half_line, LEFT, MED_SMALL_BUFF) - half_label.set_color(half_line.get_color()) - - curr_vector = self.get_vector() - target_vector = 0.5*np.ones(3) - ghost_dials = VGroup(*[ - slider.dial.copy().set_fill(WHITE, 0.5) - for slider in self.sliders - ]) - - cross = Cross(self.re_words.get_parts_by_tex("frac")) - new_re = TexMobject("(0.5)^2 = 0.25") - new_re.next_to(cross, DOWN, MED_SMALL_BUFF, LEFT) - new_re.set_color(MAROON_B) - - self.play( - FadeOut(self.arrow), - Write(half_label, run_time = 1), - ShowCreation(half_line) - ) - self.wait() - self.add(ghost_dials) - self.play(*[ - ApplyMethod(slider.set_value, 0.5) - for slider in self.sliders - ]) - self.play(ShowCreation(cross)) - self.play(Write(new_re)) - self.wait(3) - self.play( - FadeOut(new_re), - FadeOut(cross), - *[ - ApplyMethod(slider.set_value, x) - for x, slider in zip(curr_vector, self.sliders) - ] - ) - - def reframe_as_inner_sphere_point(self): - s = self.sliders[0] - shift_vect = s.number_to_point(0)-s.number_to_point(1) - curr_vector = self.get_vector() - self.set_center_point(np.zeros(3)) - self.set_to_vector(curr_vector) - self.total_real_estate = self.get_current_total_real_estate() - - all_re_ticks = VGroup(*[ - slider.real_estate_ticks - for slider in self.sliders - ]) - inner_sphere_words = TextMobject( - "Also a point on \\\\", "the inner sphere" - ) - inner_sphere_words.next_to(self.re_line, RIGHT) - question = TextMobject("How much \\\\", "real estate?") - question.next_to(self.re_line, RIGHT, MED_LARGE_BUFF) - - self.play( - Animation(self.sliders), - FadeOut(self.re_words), - LaggedStartMap( - ApplyMethod, all_re_ticks, - lambda m : (m.shift, shift_vect) - ) - ) - self.initialize_ambiant_slider_movement() - self.play(Write(inner_sphere_words)) - self.wait(5) - self.wind_down_ambient_movement(0) - self.play(*[ - ApplyMethod(slider.set_value, x) - for slider, x in zip(self.sliders, curr_vector) - ]) - self.play(ReplacementTransform( - inner_sphere_words, question - )) - self.wait(2) - - self.re_question = question - - def place_bound_on_inner_real_estate(self): - bound = TexMobject( - "&< 3(0.5)^2 ", - "= 0.75" - ) - bound.next_to(self.re_question, DOWN) - bound.to_edge(RIGHT) - - self.play(Write(bound)) - self.wait(2) - - def comment_on_inner_sphere_smallness(self): - self.initialize_ambiant_slider_movement() - self.wait(15) - -class Rotating3DCornerSphere(ExternallyAnimatedScene): - pass - -class FourDBoxExampleWithSliders(ThreeDBoxExampleWithSliders): - CONFIG = { - "n_sliders" : 4, - "center_point" : np.ones(4), - } - def construct(self): - self.list_corner_coordinates() - self.show_16_corner_spheres() - self.show_closest_point() - self.show_real_estate_at_closest_point() - self.reframe_as_inner_sphere_point() - self.compute_inner_radius_numerically() - self.inner_sphere_touches_box() - - def list_corner_coordinates(self): - title = TextMobject( - "$2 \\!\\times\\! 2 \\!\\times\\! 2 \\!\\times\\! 2$ box vertices:" - ) - title.shift(FRAME_X_RADIUS*LEFT/2) - title.to_edge(UP) - - coordinates = list(it.product(*4*[[1, -1]])) - coordinate_mobs = VGroup(*[ - TexMobject("(%d, %d, %d, %d)"%tup) - for tup in coordinates - ]) - coordinate_mobs.arrange(DOWN, aligned_edge = LEFT) - coordinate_mobs.scale(0.8) - left_column = VGroup(*coordinate_mobs[:8]) - right_column = VGroup(*coordinate_mobs[8:]) - right_column.next_to(left_column, RIGHT) - coordinate_mobs.next_to(title, DOWN) - - self.play(Write(title)) - self.play(LaggedStartMap(FadeIn, coordinate_mobs)) - self.wait() - - self.coordinate_mobs = coordinate_mobs - self.coordinates = coordinates - self.box_vertices_title = title - - def show_16_corner_spheres(self): - sphere_words = VGroup(TextMobject("Sphere centered at")) - sphere_words.scale(0.8) - sphere_words.next_to(self.sliders, RIGHT) - sphere_words.shift(2*UP) - - self.add(sphere_words) - pairs = list(zip(self.coordinate_mobs, self.coordinates)) - for coord_mob, coords in pairs[1:] + [pairs[0]]: - coord_mob.set_color(GREEN) - coord_mob_copy = coord_mob.copy() - coord_mob_copy.next_to(sphere_words, DOWN) - for slider, x in zip(self.sliders, coords): - point = slider.number_to_point(x) - slider.real_estate_ticks.move_to(point) - slider.dial.move_to(point) - self.sliders[0].dial.move_to( - self.sliders[0].number_to_point(coords[0]+1) - ) - self.add(coord_mob_copy) - self.wait() - self.remove(coord_mob_copy) - coord_mob.set_color(WHITE) - self.add(coord_mob_copy) - sphere_words.add(coord_mob_copy) - self.sphere_words = sphere_words - - self.play( - self.sliders.center, - sphere_words.shift, LEFT, - *list(map(FadeOut, [ - self.coordinate_mobs, self.box_vertices_title - ])) - ) - self.initialize_ambiant_slider_movement() - self.wait(4) - - def show_closest_point(self): - target_vector = 0.5*np.ones(4) - re_line = DashedLine(*[ - self.sliders[i].number_to_point(0.5)+MED_SMALL_BUFF*vect - for i, vect in [(0, LEFT), (-1, RIGHT)] - ]) - half_label = TexMobject("0.5") - half_label.scale(self.sliders[0].number_scale_val) - half_label.next_to(re_line, LEFT, MED_SMALL_BUFF) - half_label.set_color(MAROON_B) - - self.wind_down_ambient_movement() - self.play(*[ - ApplyMethod(slider.set_value, x) - for x, slider in zip(target_vector, self.sliders) - ]) - self.play(ShowCreation(re_line)) - self.play(Write(half_label)) - self.wait(2) - - self.re_line = re_line - self.half_label = half_label - - def show_real_estate_at_closest_point(self): - words = TextMobject("Total real estate:") - value = TexMobject("4(0.5)^2 = 4(0.25) = 1") - value.next_to(words, DOWN) - re_words = VGroup(words, value) - re_words.scale(0.8) - re_words.next_to(self.sphere_words, DOWN, MED_LARGE_BUFF) - - re_rects = VGroup() - for slider in self.sliders: - rect = Rectangle( - width = 2*slider.tick_size, - height = 0.5*slider.unit_size, - stroke_width = 0, - fill_color = MAROON_B, - fill_opacity = 0.75, - ) - rect.move_to(slider.number_to_point(0.75)) - re_rects.add(rect) - - self.play(FadeIn(re_words)) - self.play(LaggedStartMap(DrawBorderThenFill, re_rects, run_time = 3)) - self.wait(2) - - self.re_words = re_words - self.re_rects = re_rects - - def reframe_as_inner_sphere_point(self): - sphere_words = self.sphere_words - sphere_words.generate_target() - sphere_words.target.shift(2*DOWN) - old_coords = sphere_words.target[1] - new_coords = TexMobject("(0, 0, 0, 0)") - new_coords.replace(old_coords, dim_to_match = 1) - new_coords.set_color(old_coords.get_color()) - Transform(old_coords, new_coords).update(1) - - self.play(Animation(self.sliders), *[ - ApplyMethod( - s.real_estate_ticks.move_to, s.number_to_point(0), - run_time = 2, - rate_func = squish_rate_func(smooth, a, a+0.5) - ) - for s, a in zip(self.sliders, np.linspace(0, 0.5, 4)) - ]) - self.play( - MoveToTarget(sphere_words), - self.re_words.next_to, sphere_words.target, UP, MED_LARGE_BUFF, - path_arc = np.pi - ) - self.wait(2) - re_shift_vect = 0.5*self.sliders[0].unit_size*DOWN - self.play(LaggedStartMap( - ApplyMethod, self.re_rects, - lambda m : (m.shift, re_shift_vect), - path_arc = np.pi - )) - self.wait() - re_words_rect = SurroundingRectangle(self.re_words) - self.play(ShowCreation(re_words_rect)) - self.wait() - self.play(FadeOut(re_words_rect)) - self.wait() - - self.set_center_point(np.zeros(4)) - self.initialize_ambiant_slider_movement() - self.wait(4) - - def compute_inner_radius_numerically(self): - computation = TexMobject( - "R_\\text{Inner}", - "&= ||(1, 1, 1, 1)|| - 1 \\\\", - # "&= \\sqrt{1^2 + 1^2 + 1^2 + 1^2} - 1 \\\\", - "&= \\sqrt{4} - 1 \\\\", - "&= 1" - ) - computation.scale(0.8) - computation.to_corner(UP+LEFT) - computation.shift(DOWN) - brace = Brace(VGroup(*computation[1][1:-2]), UP) - brace_text = brace.get_text("Distance to corner") - brace_text.scale(0.8, about_point = brace_text.get_bottom()) - VGroup(brace, brace_text).set_color(RED) - - self.play(LaggedStartMap(FadeIn, computation, run_time = 3)) - self.play(GrowFromCenter(brace)) - self.play(Write(brace_text, run_time = 2)) - self.wait(16) - - computation.add(brace, brace_text) - self.computation = computation - - def inner_sphere_touches_box(self): - touching_words = TextMobject( - "This point touches\\\\", - "the $2 \\!\\times\\! 2 \\!\\times\\! 2 \\!\\times\\! 2$ box!" - ) - touching_words.to_corner(UP+LEFT) - arrow = Arrow(MED_SMALL_BUFF*DOWN, 3*RIGHT+DOWN) - arrow.set_color(BLUE) - arrow.shift(touching_words.get_bottom()) - - self.wind_down_ambient_movement(wait = False) - self.play(FadeOut(self.computation)) - self.reset_dials([1]) - self.play(Write(touching_words)) - self.play(ShowCreation(arrow)) - self.wait(2) - -class TwoDInnerSphereTouchingBox(TwoDBoxWithSliders, PiCreatureScene): - def setup(self): - TwoDBoxWithSliders.setup(self) - PiCreatureScene.setup(self) - self.remove(self.sliders) - self.remove(self.dot) - self.circle.set_color(GREY) - self.randy.next_to(self.plane, RIGHT, LARGE_BUFF, DOWN) - - def construct(self): - little_inner_circle, big_inner_circle = [ - Circle( - radius = radius*self.plane.x_unit_size, - color = GREEN - ).move_to(self.plane.coords_to_point(0, 0)) - for radius in (np.sqrt(2)-1, 1) - ] - randy = self.randy - tangency_points = VGroup(*[ - Dot(self.plane.coords_to_point(x, y)) - for x, y in [(1, 0), (0, 1), (-1, 0), (0, -1)] - ]) - tangency_points.set_fill(YELLOW, 0.5) - - self.play( - ShowCreation(little_inner_circle), - randy.change, "pondering", little_inner_circle - ) - self.wait() - self.play( - ReplacementTransform( - little_inner_circle.copy(), big_inner_circle - ), - little_inner_circle.fade, - randy.change, "confused" - ) - big_inner_circle.save_state() - self.play(big_inner_circle.move_to, self.circle) - self.play(big_inner_circle.restore) - self.wait() - self.play(LaggedStartMap( - DrawBorderThenFill, tangency_points, - rate_func = double_smooth - )) - self.play(randy.change, "maybe") - self.play(randy.look_at, self.circle) - self.wait() - self.play(randy.look_at, little_inner_circle) - self.wait() - - #### - - def create_pi_creature(self): - self.randy = Randolph().flip() - return self.randy - -class FiveDBoxExampleWithSliders(FourDBoxExampleWithSliders): - CONFIG = { - "n_sliders" : 5, - "center_point" : np.ones(5), - } - def setup(self): - FourDBoxExampleWithSliders.setup(self) - self.sliders.center() - - def construct(self): - self.show_32_corner_spheres() - self.show_closest_point() - self.show_halfway_point() - self.reframe_as_inner_sphere_point() - self.compute_radius() - self.poke_out_of_box() - - def show_32_corner_spheres(self): - sphere_words = VGroup(TextMobject("Sphere centered at")) - sphere_words.next_to(self.sliders, RIGHT, MED_LARGE_BUFF) - sphere_words.shift(2.5*UP) - self.add(sphere_words) - - n_sphere_words = TextMobject("32 corner spheres") - n_sphere_words.to_edge(LEFT) - n_sphere_words.shift(2*UP) - self.add(n_sphere_words) - - for coords in it.product(*5*[[-1, 1]]): - s = str(tuple(coords)) - s = s.replace("1", "+1") - s = s.replace("-+1", "-1") - coords_mob = TexMobject(s) - coords_mob.set_color(GREEN) - coords_mob.next_to(sphere_words, DOWN) - for slider, x in zip(self.sliders, coords): - for mob in slider.real_estate_ticks, slider.dial: - mob.move_to(slider.number_to_point(x)) - self.sliders[0].dial.move_to( - self.sliders[0].number_to_point(coords[0]+1) - ) - self.add(coords_mob) - self.wait(0.25) - self.remove(coords_mob) - self.add(coords_mob) - sphere_words.add(coords_mob) - self.sphere_words = sphere_words - - self.initialize_ambiant_slider_movement() - self.play(FadeOut(n_sphere_words)) - self.wait(3) - - def show_closest_point(self): - target_x = 1-np.sqrt(0.2) - re_line = DashedLine(*[ - self.sliders[i].number_to_point(target_x)+MED_SMALL_BUFF*vect - for i, vect in [(0, LEFT), (-1, RIGHT)] - ]) - re_words = TextMobject( - "$0.2$", "units of real \\\\ estate each" - ) - re_words.next_to(self.sphere_words, DOWN, MED_LARGE_BUFF) - - re_rects = VGroup() - for slider in self.sliders: - rect = Rectangle( - width = 2*slider.tick_size, - height = (1-target_x)*slider.unit_size, - stroke_width = 0, - fill_color = GREEN, - fill_opacity = 0.75, - ) - rect.move_to(slider.number_to_point(1), UP) - re_rects.add(rect) - - self.wind_down_ambient_movement() - self.reset_dials(5*[target_x]) - self.play( - ShowCreation(re_line), - Write(re_words, run_time = 2) - ) - self.play(LaggedStartMap( - DrawBorderThenFill, re_rects, - rate_func = double_smooth - )) - self.wait() - - self.re_rects = re_rects - self.re_words = re_words - self.re_line = re_line - - def show_halfway_point(self): - half_line = Line(*[ - self.sliders[i].number_to_point(0.5)+MED_SMALL_BUFF*vect - for i, vect in [(0, LEFT), (-1, RIGHT)] - ]) - half_line.set_color(MAROON_B) - half_label = TexMobject("0.5") - half_label.scale(self.sliders[0].number_scale_val) - half_label.next_to(half_line, LEFT, MED_SMALL_BUFF) - half_label.set_color(half_line.get_color()) - - curr_vector = self.get_vector() - ghost_dials = VGroup(*[ - slider.dial.copy().set_fill(WHITE, 0.75) - for slider in self.sliders - ]) - point_25 = TexMobject("0.25") - point_25.set_color(half_label.get_color()) - point_25.move_to(self.re_words[0], RIGHT) - self.re_words.save_state() - - self.play( - Write(half_label), - ShowCreation(half_line) - ) - self.wait(2) - self.add(ghost_dials) - self.play(*[ - ApplyMethod(slider.set_value, 0.5) - for slider in self.sliders - ]) - self.play(Transform(self.re_words[0], point_25)) - self.wait(2) - self.play(*[ - ApplyMethod(slider.set_value, x) - for x, slider in zip(curr_vector, self.sliders) - ]) - self.play(self.re_words.restore) - - def reframe_as_inner_sphere_point(self): - s = self.sliders[0] - shift_vect = s.number_to_point(0)-s.number_to_point(1) - re_ticks = VGroup(*[ - slider.real_estate_ticks - for slider in self.sliders - ]) - - re_rects = self.re_rects - re_rects.generate_target() - for rect, slider in zip(re_rects.target, self.sliders): - height = slider.unit_size*(1-np.sqrt(0.2)) - rect.set_height(height) - rect.move_to(slider.number_to_point(0), DOWN) - - self.sphere_words.generate_target() - old_coords = self.sphere_words.target[1] - new_coords = TexMobject(str(tuple(5*[0]))) - new_coords.replace(old_coords, dim_to_match = 1) - new_coords.set_color(old_coords.get_color()) - Transform(old_coords, new_coords).update(1) - - self.re_words.generate_target() - new_re = TexMobject("0.31") - new_re.set_color(GREEN) - old_re = self.re_words.target[0] - new_re.move_to(old_re, RIGHT) - Transform(old_re, new_re).update(1) - - self.play( - Animation(self.sliders), - LaggedStartMap( - ApplyMethod, re_ticks, - lambda m : (m.shift, shift_vect), - path_arc = np.pi - ), - MoveToTarget(self.sphere_words), - ) - self.play( - MoveToTarget( - re_rects, - run_time = 2, - lag_ratio = 0.5, - path_arc = np.pi - ), - MoveToTarget(self.re_words), - ) - self.wait(2) - - self.set_center_point(np.zeros(5)) - self.total_real_estate = (np.sqrt(5)-1)**2 - self.initialize_ambiant_slider_movement() - self.wait(12) - - def compute_radius(self): - computation = TexMobject( - "R_{\\text{inner}} &= \\sqrt{5}-1 \\\\", - "&\\approx 1.24" - ) - computation.to_corner(UP+LEFT) - - self.play(Write(computation, run_time = 2)) - self.wait(12) - - def poke_out_of_box(self): - self.wind_down_ambient_movement(0) - self.reset_dials([np.sqrt(5)-1]) - - words = TextMobject("Poking outside \\\\ the box!") - words.to_edge(LEFT) - words.set_color(RED) - arrow = Arrow( - words.get_top(), - self.sliders[0].dial, - path_arc = -np.pi/3, - color = words.get_color() - ) - - self.play( - ShowCreation(arrow), - Write(words) - ) - self.wait(2) - -class SkipAheadTo10(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Let's skip ahead \\\\ to 10 dimensions", - target_mode = "hooray" - ) - self.change_student_modes( - "pleading", "confused", "horrified" - ) - self.wait(3) - -class TenDBoxExampleWithSliders(FiveDBoxExampleWithSliders): - CONFIG = { - "n_sliders" : 10, - "center_point" : np.ones(10), - "ambient_velocity_magnitude" : 2.0, - "ambient_acceleration_magnitude" : 3.0, - } - def setup(self): - FourDBoxExampleWithSliders.setup(self) - self.sliders.to_edge(RIGHT) - - def construct(self): - self.initial_wandering() - self.show_closest_point() - self.reframe_as_inner_sphere_point() - self.compute_inner_radius_numerically() - self.wander_on_inner_sphere() - self.poke_outside_outer_box() - - def initial_wandering(self): - self.initialize_ambiant_slider_movement() - self.wait(9) - - def show_closest_point(self): - target_x = 1-np.sqrt(1./self.n_sliders) - re_line = DashedLine(*[ - self.sliders[i].number_to_point(target_x)+MED_SMALL_BUFF*vect - for i, vect in [(0, LEFT), (-1, RIGHT)] - ]) - - re_rects = VGroup() - for slider in self.sliders: - rect = Rectangle( - width = 2*slider.tick_size, - height = (1-target_x)*slider.unit_size, - stroke_width = 0, - fill_color = GREEN, - fill_opacity = 0.75, - ) - rect.move_to(slider.number_to_point(1), UP) - re_rects.add(rect) - - self.wind_down_ambient_movement() - self.reset_dials(self.n_sliders*[target_x]) - self.play(ShowCreation(re_line)) - self.play(LaggedStartMap( - DrawBorderThenFill, re_rects, - rate_func = double_smooth - )) - self.wait(2) - - self.re_line = re_line - self.re_rects = re_rects - - def reframe_as_inner_sphere_point(self): - s = self.sliders[0] - shift_vect = s.number_to_point(0)-s.number_to_point(1) - re_ticks = VGroup(*[ - slider.real_estate_ticks - for slider in self.sliders - ]) - - re_rects = self.re_rects - re_rects.generate_target() - for rect, slider in zip(re_rects.target, self.sliders): - height = slider.unit_size*(1-np.sqrt(1./self.n_sliders)) - rect.stretch_to_fit_height(height) - rect.move_to(slider.number_to_point(0), DOWN) - - self.play( - Animation(self.sliders), - LaggedStartMap( - ApplyMethod, re_ticks, - lambda m : (m.shift, shift_vect), - path_arc = np.pi - ), - ) - self.play( - MoveToTarget( - re_rects, - run_time = 2, - lag_ratio = 0.5, - path_arc = np.pi - ), - ) - self.wait(2) - - self.set_center_point(np.zeros(self.n_sliders)) - self.total_real_estate = (np.sqrt(self.n_sliders)-1)**2 - self.initialize_ambiant_slider_movement() - self.wait(5) - - def compute_inner_radius_numerically(self): - computation = TexMobject( - "R_{\\text{inner}} &= \\sqrt{10}-1 \\\\", - "&\\approx 2.16" - ) - computation.to_corner(UP+LEFT) - - self.play(Write(computation, run_time = 2)) - - def wander_on_inner_sphere(self): - self.wait(10) - - def poke_outside_outer_box(self): - self.wind_down_ambient_movement() - self.reset_dials([np.sqrt(10)-1]) - - words = TextMobject( - "Outside the \\emph{outer} \\\\", - "bounding box!" - ) - words.to_edge(LEFT) - words.set_color(RED) - arrow = Arrow( - words.get_top(), - self.sliders[0].dial, - path_arc = -np.pi/3, - color = words.get_color() - ) - self.play( - Write(words, run_time = 2), - ShowCreation(arrow) - ) - self.wait(3) - -class TwoDOuterBox(TwoDInnerSphereTouchingBox): - def construct(self): - words = TextMobject("$4 \\!\\times\\! 4$ outer bounding box") - words.next_to(self.plane, UP) - words.set_color(MAROON_B) - line = Line( - self.plane.coords_to_point(-2, -2), - self.plane.coords_to_point(2, 2), - ) - box = Square(color = words.get_color()) - box.replace(line, stretch = True) - box.set_stroke(width = 8) - - self.play( - Write(words), - ShowCreation(box), - self.randy.change, "pondering", - ) - self.wait(3) - - self.outer_box = box - -class ThreeDOuterBoundingBox(ExternallyAnimatedScene): - pass - -class ThreeDOuterBoundingBoxWords(Scene): - def construct(self): - words = TextMobject( - "$4 \\!\\times\\! 4\\!\\times\\! 4$ outer\\\\", - "bounding box" - ) - words.set_width(FRAME_WIDTH-1) - words.to_edge(DOWN) - words.set_color(MAROON_B) - - self.play(Write(words)) - self.wait(4) - -class FaceDistanceDoesntDependOnDimension(TwoDOuterBox): - def construct(self): - self.force_skipping() - TwoDOuterBox.construct(self) - self.randy.change("confused") - self.revert_to_original_skipping_status() - - line = Line( - self.plane.coords_to_point(0, 0), - self.outer_box.get_right(), - buff = 0, - stroke_width = 6, - color = YELLOW - ) - length_words = TextMobject("Always 2, in all dimensions") - length_words.next_to(self.plane, RIGHT, MED_LARGE_BUFF, UP) - arrow = Arrow(length_words[4].get_bottom(), line.get_center()) - - self.play(ShowCreation(line)) - self.play( - Write(length_words), - ShowCreation(arrow) - ) - self.play(self.randy.change, "thinking") - self.wait(3) - -class TenDCornerIsVeryFarAway(TenDBoxExampleWithSliders): - CONFIG = { - "center_point" : np.zeros(10) - } - def construct(self): - self.show_re_rects() - - def show_re_rects(self): - re_rects = VGroup() - for slider in self.sliders: - rect = Rectangle( - width = 2*slider.tick_size, - height = slider.unit_size, - stroke_width = 0, - fill_color = GREEN, - fill_opacity = 0.75, - ) - rect.move_to(slider.number_to_point(0), DOWN) - re_rects.add(rect) - rect.save_state() - rect.stretch_to_fit_height(0) - rect.move_to(rect.saved_state, DOWN) - - self.set_to_vector(np.zeros(10)) - self.play( - LaggedStartMap( - ApplyMethod, re_rects, - lambda m : (m.restore,), - lag_ratio = 0.3, - ), - LaggedStartMap( - ApplyMethod, self.sliders, - lambda m : (m.set_value, 1), - lag_ratio = 0.3, - ), - run_time = 10, - ) - self.wait() - -class InnerRadiusIsUnbounded(TeacherStudentsScene): - def construct(self): - self.teacher_says("Inner radius \\\\ is unbounded") - self.change_student_modes(*["erm"]*3) - self.wait(3) - -class ProportionOfSphereInBox(GraphScene): - CONFIG = { - "x_axis_label" : "Dimension", - "y_axis_label" : "", - "y_max" : 1.5, - "y_min" : 0, - "y_tick_frequency" : 0.25, - "y_labeled_nums" : np.linspace(0.25, 1, 4), - "x_min" : 0, - "x_max" : 50, - "x_tick_frequency" : 5, - "x_labeled_nums" : list(range(10, 50, 10)), - "num_graph_anchor_points" : 100, - } - def construct(self): - self.setup_axes() - title = TextMobject( - "Proportion of inner sphere \\\\ inside box" - ) - title.next_to(self.y_axis, RIGHT, MED_SMALL_BUFF, UP) - self.add(title) - - graph = self.get_graph(lambda x : np.exp(0.1*(9-x))) - max_y = self.coords_to_point(0, 1)[1] - too_high = graph.points[:,1] > max_y - graph.points[too_high, 1] = max_y - - footnote = TextMobject(""" - \\begin{flushleft} - *I may or may not have used an easy-to-compute \\\\ - but not-totally-accurate curve here, due to \\\\ - the surprising difficulty in computing the real \\\\ - proportion :) - \\end{flushleft} - """,) - footnote.scale(0.75) - footnote.next_to( - graph.point_from_proportion(0.3), - UP+RIGHT, SMALL_BUFF - ) - footnote.set_color(YELLOW) - - self.play(ShowCreation(graph, run_time = 5, rate_func=linear)) - self.wait() - self.add(footnote) - self.wait(0.25) - -class ShowingToFriend(PiCreatureScene, SliderScene): - CONFIG = { - "n_sliders" : 10, - "ambient_acceleration_magnitude" : 3.0, - "seconds_to_blink" : 4, - } - def setup(self): - PiCreatureScene.setup(self) - SliderScene.setup(self) - self.sliders.scale(0.75) - self.sliders.next_to( - self.morty.get_corner(UP+LEFT), UP, MED_LARGE_BUFF - ) - self.initialize_ambiant_slider_movement() - - def construct(self): - morty, randy = self.morty, self.randy - self.play(morty.change, "raise_right_hand", self.sliders) - self.play(randy.change, "happy", self.sliders) - self.wait(7) - self.play(randy.change, "skeptical", morty.eyes) - self.wait(3) - self.play(randy.change, "thinking", self.sliders) - self.wait(6) - - ### - - def create_pi_creatures(self): - self.morty = Mortimer() - self.morty.to_edge(DOWN).shift(4*RIGHT) - self.randy = Randolph() - self.randy.to_edge(DOWN).shift(4*LEFT) - return VGroup(self.morty, self.randy) - - def non_blink_wait(self, time = 1): - SliderScene.wait(self, time) - -class QuestionsFromStudents(TeacherStudentsScene): - def construct(self): - self.student_says( - "Is 10-dimensional \\\\ space real?", - target_mode = "sassy", - run_time = 2, - ) - self.wait() - self.teacher_says( - "No less real \\\\ than reals", - target_mode = "shruggie", - content_introduction_class = FadeIn, - ) - self.wait(2) - self.student_says( - "How do you think \\\\ about volume?", - student_index = 0, - content_introduction_class = FadeIn, - ) - self.wait() - self.student_says( - "How do cubes work?", - student_index = 2, - run_time = 2, - ) - self.wait(2) - -class FunHighDSpherePhenomena(Scene): - def construct(self): - title = TextMobject( - "Fun high-D sphere phenomena" - ) - title.to_edge(UP) - title.set_color(BLUE) - h_line = Line(LEFT, RIGHT).scale(5) - h_line.next_to(title, DOWN) - self.add(title, h_line) - - items = VGroup(*list(map(TextMobject, [ - "$\\cdot$ Most volume is near the equator", - "$\\cdot$ Most volume is near the surface", - "$\\cdot$ Sphere packing in 8 dimensions", - "$\\cdot$ Sphere packing in 24 dimensions", - ]))) - items.arrange( - DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT - ) - items.next_to(h_line, DOWN) - - for item in items: - self.play(LaggedStartMap(FadeIn, item, run_time = 2)) - self.wait() - -class TODOBugOnSurface(TODOStub): - CONFIG = { - "message" : "Bug on surface" - } - -class CoordinateFree(PiCreatureScene): - def construct(self): - plane = NumberPlane(x_radius = 2.5, y_radius = 2.5) - plane.add_coordinates() - plane.to_corner(UP+LEFT) - self.add(plane) - - circles = VGroup(*[ - Circle(color = YELLOW).move_to( - plane.coords_to_point(*coords) - ) - for coords in it.product(*2*[[-1, 1]]) - ]) - inner_circle = Circle( - radius = np.sqrt(2)-1, - color = GREEN - ).move_to(plane.coords_to_point(0, 0)) - - self.add_foreground_mobjects(circles, inner_circle) - - self.play(PiCreatureSays( - self.pi_creature, "Lose the \\\\ coordinates!", - target_mode = "hooray" - )) - self.play(FadeOut(plane, run_time = 2)) - self.wait(3) - -class Skeptic(TeacherStudentsScene, SliderScene): - def setup(self): - SliderScene.setup(self) - TeacherStudentsScene.setup(self) - - self.sliders.scale(0.7) - self.sliders.next_to(self.teacher, UP, aligned_edge = LEFT) - self.sliders.to_edge(UP) - self.initialize_ambiant_slider_movement() - - def construct(self): - analytic_thought = VGroup(TextMobject("No different from")) - equation = TexMobject( - "x", "^2 + ", "y", "^2 + ", "z", "^2 + ", "w", "^2 = 1" - ) - variables = VGroup(*[ - equation.get_part_by_tex(tex) - for tex in "xyzw" - ]) - slider_labels = VGroup(*[ - slider.label for slider in self.sliders - ]) - equation.next_to(analytic_thought, DOWN) - analytic_thought.add(equation) - - all_real_estate_ticks = VGroup(*it.chain(*[ - slider.real_estate_ticks - for slider in self.sliders - ])) - - box = Square(color = RED) - box.next_to(self.sliders, LEFT) - line = Line(box.get_center(), box.get_corner(UP+RIGHT)) - line.set_color(YELLOW) - - self.student_says( - analytic_thought, - student_index = 0, - target_mode = "sassy", - added_anims = [self.teacher.change, "guilty"] - ) - self.wait(2) - equation.remove(*variables) - self.play(ReplacementTransform(variables, slider_labels)) - self.play( - self.teacher.change, "pondering", slider_labels, - RemovePiCreatureBubble( - self.students[0], target_mode = "hesitant" - ), - ) - self.wait(4) - bubble = self.teacher.get_bubble( - "It's much \\\\ more playful!", - bubble_class = SpeechBubble - ) - bubble.resize_to_content() - VGroup(bubble, bubble.content).next_to(self.teacher, UP+LEFT) - self.play( - self.teacher.change, "hooray", - ShowCreation(bubble), - Write(bubble.content) - ) - self.wait(3) - self.play( - RemovePiCreatureBubble( - self.teacher, target_mode = "raise_right_hand", - look_at_arg = self.sliders - ), - *[ - ApplyMethod(pi.change, "pondering") - for pi in self.students - ] - ) - self.play(Animation(self.sliders), LaggedStartMap( - ApplyMethod, all_real_estate_ticks, - lambda m : (m.shift, SMALL_BUFF*LEFT), - rate_func = wiggle, - lag_ratio = 0.3, - run_time = 4, - )) - self.play( - ShowCreation(box), - self.teacher.change, "happy" - ) - self.play(ShowCreation(line)) - self.wait(3) - - ##### - def non_blink_wait(self, time = 1): - SliderScene.wait(self, time) - -class ClipFrom4DBoxExampleTODO(TODOStub): - CONFIG = { - "message" : "Clip from 4d box example" - } - -class JustBecauseYouCantVisualize(Scene): - def construct(self): - phrase = "\\raggedright " - phrase += "Just because you can't visualize\\\\ " - phrase += "something doesn't mean you can't\\\\ " - phrase += "still think about it visually." - phrase_mob = TextMobject(*phrase.split(" ")) - phrase_mob.set_color_by_tex("visual", YELLOW) - phrase_mob.next_to(ORIGIN, UP) - - for part in phrase_mob: - self.play(LaggedStartMap( - FadeIn, part, - run_time = 0.05*len(part) - )) - self.wait(2) - -class Announcements(TeacherStudentsScene): - def construct(self): - title = TextMobject("Announcements") - title.scale(1.5) - title.to_edge(UP, buff = MED_SMALL_BUFF) - h_line = Line(LEFT, RIGHT).scale(3) - h_line.next_to(title, DOWN) - self.add(title, h_line) - - items = VGroup(*list(map(TextMobject, [ - "$\\cdot$ Where to learn more", - "$\\cdot$ Q\\&A Followup (podcast!)", - ]))) - items.arrange(DOWN, aligned_edge = LEFT) - items.next_to(h_line, DOWN) - - self.play( - Write(items[0], run_time = 2), - ) - self.play(*[ - ApplyMethod(pi.change, "hooray", items) - for pi in self.pi_creatures - ]) - self.play(Write(items[1], run_time = 2)) - self.wait(2) - -class Promotion(PiCreatureScene): - CONFIG = { - "seconds_to_blink" : 5, - } - def construct(self): - url = TextMobject("https://brilliant.org/3b1b/") - url.to_corner(UP+LEFT) - - rect = Rectangle(height = 9, width = 16) - rect.set_height(5.5) - rect.next_to(url, DOWN) - rect.to_edge(LEFT) - - self.play( - Write(url), - self.pi_creature.change, "raise_right_hand" - ) - self.play(ShowCreation(rect)) - self.wait(2) - self.change_mode("thinking") - self.wait() - self.look_at(url) - self.wait(10) - self.change_mode("happy") - self.wait(10) - self.change_mode("raise_right_hand") - self.wait(10) - - self.remove(rect) - self.play( - url.next_to, self.pi_creature, UP+LEFT - ) - url_rect = SurroundingRectangle(url) - self.play(ShowCreation(url_rect)) - self.play(FadeOut(url_rect)) - self.wait(3) - -class BrilliantGeometryQuiz(ExternallyAnimatedScene): - pass - -class BrilliantScrollThroughCourses(ExternallyAnimatedScene): - pass - -class Podcast(TeacherStudentsScene): - def construct(self): - title = TextMobject("Podcast!") - title.scale(1.5) - title.to_edge(UP) - title.shift(FRAME_X_RADIUS*LEFT/2) - self.add(title) - - q_and_a = TextMobject("Q\\&A Followup") - q_and_a.next_to(self.teacher.get_corner(UP+LEFT), UP, LARGE_BUFF) - - self.play( - LaggedStartMap( - ApplyMethod, self.pi_creatures, - lambda pi : (pi.change, "hooray", title) - ), - Write(title) - ) - self.wait(5) - self.play( - Write(q_and_a), - self.teacher.change, "raise_right_hand", - ) - self.wait(4) - -class HighDPatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Desmos", - "Burt Humburg", - "CrypticSwarm", - "Juan Benet", - "Ali Yahya", - "William", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Samantha D. Suplee", - "James Park", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Yu Jun", - "dave nicponski", - "Damion Kistler", - "Markus Persson", - "Yoni Nazarathy", - "Corey Ogburn", - "Ed Kellett", - "Joseph John Cox", - "Dan Buchoff", - "Luc Ritchie", - "Erik Sundell", - "Xueqi Li", - "David Stork", - "Tianyu Ge", - "Ted Suzman", - "Amir Fayazi", - "Linh Tran", - "Andrew Busey", - "Michael McGuffin", - "John Haley", - "Ankalagon", - "Eric Lavault", - "Tomohiro Furusawa", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Ryan Dahl", - "Mark Govea", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "Nils Schneider", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ] - } - -class Thumbnail(SliderScene): - CONFIG = { - "n_sliders" : 10, - } - def construct(self): - for slider in self.sliders: - self.remove(slider.label) - slider.remove(slider.label) - vect = np.random.random(10) - 0.5 - vect /= get_norm(vect) - self.set_to_vector(vect) - - title = TextMobject("10D Sphere?") - title.scale(2) - title.to_edge(UP) - self.add(title) - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/hilbert/fractal_porn.py b/from_3b1b/old/hilbert/fractal_porn.py deleted file mode 100644 index fa9335b0..00000000 --- a/from_3b1b/old/hilbert/fractal_porn.py +++ /dev/null @@ -1,338 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.hilbert.curves import * - -class Intro(TransformOverIncreasingOrders): - @staticmethod - def args_to_string(*args): - return "" - - @staticmethod - def string_to_args(string): - raise Exception("string_to_args Not Implemented!") - - def construct(self): - words1 = TextMobject( - "If you watched my video about Hilbert's space-filling curve\\dots" - ) - words2 = TextMobject( - "\\dots you might be curious to see what a few other space-filling curves look like." - ) - words2.scale(0.8) - for words in words1, words2: - words.to_edge(UP, buff = 0.2) - - self.setup(HilbertCurve) - self.play(ShimmerIn(words1)) - for x in range(4): - self.increase_order() - self.remove(words1) - self.increase_order( - ShimmerIn(words2) - ) - for x in range(4): - self.increase_order() - - - -class BringInPeano(Intro): - def construct(self): - words1 = TextMobject(""" - For each one, see if you can figure out what - the pattern of construction is. - """) - words2 = TextMobject(""" - This one is the Peano curve. - """) - words3 = TextMobject(""" - It is the original space-filling curve. - """) - self.setup(PeanoCurve) - self.play(ShimmerIn(words1)) - self.wait(5) - self.remove(words1) - self.add(words2.to_edge(UP)) - for x in range(3): - self.increase_order() - self.remove(words2) - self.increase_order(ShimmerIn(words3.to_edge(UP))) - for x in range(2): - self.increase_order() - - -class FillOtherShapes(Intro): - def construct(self): - words1 = TextMobject(""" - But of course, there's no reason we should limit - ourselves to filling in squares. - """) - words2 = TextMobject(""" - Here's a simple triangle-filling curve I defined - in a style reflective of a Hilbert curve. - """) - words1.to_edge(UP) - words2.scale(0.8).to_edge(UP, buff = 0.2) - - self.setup(TriangleFillingCurve) - self.play(ShimmerIn(words1)) - for x in range(3): - self.increase_order() - self.remove(words1) - self.add(words2) - for x in range(5): - self.increase_order() - -class SmallerFlowSnake(FlowSnake): - CONFIG = { - "radius" : 4 - } - -class MostDelightfulName(Intro): - def construct(self): - words1 = TextMobject(""" - This one has the most delightful name, - thanks to mathematician/programmer Bill Gosper: - """) - words2 = TextMobject("``Flow Snake''") - words3 = TextMobject(""" - What makes this one particularly interesting - is that the boundary itself is a fractal. - """) - for words in words1, words2, words3: - words.to_edge(UP) - - self.setup(SmallerFlowSnake) - self.play(ShimmerIn(words1)) - for x in range(3): - self.increase_order() - self.remove(words1) - self.add(words2) - for x in range(3): - self.increase_order() - self.remove(words2) - self.play(ShimmerIn(words3)) - - - -class SurpriseFractal(Intro): - def construct(self): - words = TextMobject(""" - It might come as a surprise how some well-known - fractals can be described with curves. - """) - words.to_edge(UP) - - self.setup(Sierpinski) - self.add(TextMobject("Speaking of other fractals\\dots")) - self.wait(3) - self.clear() - self.play(ShimmerIn(words)) - for x in range(9): - self.increase_order() - - -class IntroduceKoch(Intro): - def construct(self): - words = list(map(TextMobject, [ - "This is another famous fractal.", - "The ``Koch Snowflake''", - "Let's finish things off by seeing how to turn \ - this into a space-filling curve" - ])) - for text in words: - text.to_edge(UP) - - self.setup(KochCurve) - self.add(words[0]) - for x in range(3): - self.increase_order() - self.remove(words[0]) - self.add(words[1]) - for x in range(4): - self.increase_order() - self.remove(words[1]) - self.add(words[2]) - self.wait(6) - -class StraightKoch(KochCurve): - CONFIG = { - "axiom" : "A" - } - -class SharperKoch(StraightKoch): - CONFIG = { - "angle" : 0.9*np.pi/2, - } - -class DullerKoch(StraightKoch): - CONFIG = { - "angle" : np.pi/6, - } - -class SpaceFillingKoch(StraightKoch): - CONFIG = { - "angle" : np.pi/2, - } - - - -class FromKochToSpaceFilling(Scene): - def construct(self): - self.max_order = 7 - - self.revisit_koch() - self.show_angles() - self.show_change_side_by_side() - - - def revisit_koch(self): - words = list(map(TextMobject, [ - "First, look at how one section of this curve is made.", - "This pattern of four lines is the ``seed''", - "With each iteration, every straight line is \ - replaced with an appropriately small copy of the seed", - ])) - for text in words: - text.to_edge(UP) - - self.add(words[0]) - curve = StraightKoch(order = self.max_order) - self.play(Transform( - curve, - StraightKoch(order = 1), - run_time = 5 - )) - self.remove(words[0]) - self.add(words[1]) - self.wait(4) - self.remove(words[1]) - self.add(words[2]) - self.wait(3) - for order in range(2, self.max_order): - self.play(Transform( - curve, - StraightKoch(order = order) - )) - if order == 2: - self.wait(2) - elif order == 3: - self.wait() - self.clear() - - - - def show_angles(self): - words = TextMobject(""" - Let's see what happens as we change - the angle in this seed - """) - words.to_edge(UP) - koch, sharper_koch, duller_koch = curves = [ - CurveClass(order = 1) - for CurveClass in (StraightKoch, SharperKoch, DullerKoch) - ] - arcs = [ - Arc( - 2*(np.pi/2 - curve.angle), - radius = r, - start_angle = np.pi+curve.angle - ).shift(curve.points[curve.get_num_points()/2]) - for curve, r in zip(curves, [0.6, 0.7, 0.4]) - ] - theta = TexMobject("\\theta") - theta.shift(arcs[0].get_center()+2.5*DOWN) - arrow = Arrow(theta, arcs[0]) - - self.add(words, koch) - self.play(ShowCreation(arcs[0])) - self.play( - ShowCreation(arrow), - ShimmerIn(theta) - ) - self.wait(2) - self.remove(theta, arrow) - self.play( - Transform(koch, duller_koch), - Transform(arcs[0], arcs[2]), - ) - self.play( - Transform(koch, sharper_koch), - Transform(arcs[0], arcs[1]), - ) - self.clear() - - def show_change_side_by_side(self): - - seed = TextMobject("Seed") - seed.shift(3*LEFT+2*DOWN) - fractal = TextMobject("Fractal") - fractal.shift(3*RIGHT+2*DOWN) - words = list(map(TextMobject, [ - "A sharper angle results in a richer curve", - "A more obtuse angle gives a sparser curve", - "And as the angle approaches 0\\dots", - "We have a new space-filling curve." - ])) - for text in words: - text.to_edge(UP) - sharper, duller, space_filling = [ - CurveClass(order = 1).shift(3*LEFT) - for CurveClass in (SharperKoch, DullerKoch, SpaceFillingKoch) - ] - shaper_f, duller_f, space_filling_f = [ - CurveClass(order = self.max_order).shift(3*RIGHT) - for CurveClass in (SharperKoch, DullerKoch, SpaceFillingKoch) - ] - - self.add(words[0]) - left_curve = SharperKoch(order = 1) - right_curve = SharperKoch(order = 1) - self.play( - Transform(left_curve, sharper), - ApplyMethod(right_curve.shift, 3*RIGHT), - ) - self.play( - Transform( - right_curve, - SharperKoch(order = 2).shift(3*RIGHT) - ), - ShimmerIn(seed), - ShimmerIn(fractal) - ) - for order in range(3, self.max_order): - self.play(Transform( - right_curve, - SharperKoch(order = order).shift(3*RIGHT) - )) - self.remove(words[0]) - self.add(words[1]) - kwargs = { - "run_time" : 4, - } - self.play( - Transform(left_curve, duller, **kwargs), - Transform(right_curve, duller_f, **kwargs) - ) - self.wait() - kwargs["run_time"] = 7 - kwargs["rate_func"] = None - self.remove(words[1]) - self.add(words[2]) - self.play( - Transform(left_curve, space_filling, **kwargs), - Transform(right_curve, space_filling_f, **kwargs) - ) - self.remove(words[2]) - self.add(words[3]) - self.wait() - - - - - - - - - - - - diff --git a/from_3b1b/old/hilbert/section1.py b/from_3b1b/old/hilbert/section1.py deleted file mode 100644 index 2757ba4e..00000000 --- a/from_3b1b/old/hilbert/section1.py +++ /dev/null @@ -1,1065 +0,0 @@ -from manimlib.imports import * - -import displayer as disp - -from hilbert.curves import \ - TransformOverIncreasingOrders, FlowSnake, HilbertCurve, \ - SnakeCurve - - -from constants import * - - - -def get_grid(): - return Grid(64, 64) - -def get_freq_line(): - return UnitInterval().shift(2*DOWN) ##Change? - -def get_mathy_and_bubble(): - mathy = Mathematician() - mathy.to_edge(DOWN).shift(4*LEFT) - bubble = SpeechBubble(initial_width = 8) - bubble.pin_to(mathy) - return mathy, bubble - -class AboutSpaceFillingCurves(TransformOverIncreasingOrders): - @staticmethod - def args_to_string(): - return "" - - @staticmethod - def string_to_args(arg_str): - return () - - def construct(self): - self.bubble = ThoughtBubble().ingest_submobjects() - self.bubble.scale(1.5) - - TransformOverIncreasingOrders.construct(self, FlowSnake, 7) - self.play(Transform(self.curve, self.bubble)) - self.show_infinite_objects() - self.pose_question() - self.wait() - - def show_infinite_objects(self): - sigma, summand, equals, result = TexMobject([ - "\\sum_{n = 1}^{\\infty}", - "\\dfrac{1}{n^2}", - "=", - "\\dfrac{\pi^2}{6}" - ]).split() - alt_summand = TexMobject("n").replace(summand) - alt_result = TexMobject("-\\dfrac{1}{12}").replace(result) - - rationals, other_equals, naturals = TexMobject([ - "|\\mathds{Q}|", - "=", - "|\\mathds{N}|" - ]).scale(2).split() - infinity = TexMobject("\\infty").scale(2) - local_mobjects = list(filter( - lambda m : isinstance(m, Mobject), - list(locals().values()), - )) - for mob in local_mobjects: - mob.sort_points(get_norm) - - self.play(ShimmerIn(infinity)) - self.wait() - self.play( - ShimmerIn(summand), - ShimmerIn(equals), - ShimmerIn(result), - DelayByOrder(Transform(infinity, sigma)) - ) - self.wait() - self.play( - Transform(summand, alt_summand), - Transform(result, alt_result), - ) - self.wait() - self.remove(infinity) - self.play(*[ - CounterclockwiseTransform( - Mobject(summand, equals, result, sigma), - Mobject(rationals, other_equals, naturals) - ) - ]) - self.wait() - self.clear() - self.add(self.bubble) - - def pose_question(self): - infinity, rightarrow, N = TexMobject([ - "\\infty", "\\rightarrow", "N" - ]).scale(2).split() - question_mark = TextMobject("?").scale(2) - - self.add(question_mark) - self.wait() - self.play(*[ - ShimmerIn(mob) - for mob in (infinity, rightarrow, N) - ] + [ - ApplyMethod(question_mark.next_to, rightarrow, UP), - ]) - self.wait() - - - -class PostponePhilosophizing(Scene): - def construct(self): - abstract, arrow, concrete = TextMobject([ - "Abstract", " $\\rightarrow$ ", "Concrete" - ]).scale(2).split() - - self.add(abstract, arrow, concrete) - self.wait() - self.play(*[ - ApplyMethod( - word1.replace, word2, - path_func = path_along_arc(np.pi/2) - ) - for word1, word2 in it.permutations([abstract, concrete]) - ]) - self.wait() - - -class GrowHilbertWithName(Scene): - def construct(self): - curve = HilbertCurve(order = 1) - words = TextMobject("``Hilbert Curve''") - words.to_edge(UP, buff = 0.2) - self.play( - ShimmerIn(words), - Transform(curve, HilbertCurve(order = 2)), - run_time = 2 - ) - for n in range(3, 8): - self.play( - Transform(curve, HilbertCurve(order = n)), - run_time = 5. /n - ) - - -class SectionOne(Scene): - def construct(self): - self.add(TextMobject("Section 1: Seeing with your ears")) - self.wait() - -class WriteSomeSoftware(Scene): - pass #Done viea screen capture, written here for organization - - - -class ImageToSound(Scene): - def construct(self): - string = Vibrate(color = BLUE_D, run_time = 5) - picture = ImageMobject("lion", invert = False) - picture.scale(0.8) - picture_copy = picture.copy() - picture.sort_points(get_norm) - string.mobject.sort_points(lambda p : -get_norm(p)) - - self.add(picture) - self.wait() - self.play(Transform( - picture, string.mobject, - run_time = 3, - rate_func = rush_into - )) - self.remove(picture) - self.play(string) - - for mob in picture_copy, string.mobject: - mob.sort_points(lambda p : get_norm(p)%1) - - self.play(Transform( - string.mobject, picture_copy, - run_time = 5, - rate_func = rush_from - )) - -class LinksInDescription(Scene): - def construct(self): - text = TextMobject(""" - See links in the description for more on - sight via sound. - """) - self.play(ShimmerIn(text)) - self.play(ShowCreation(Arrow(text, 3*DOWN))) - self.wait(2) - - -class ImageDataIsTwoDimensional(Scene): - def construct(self): - image = ImageMobject("lion", invert = False) - image.scale(0.5) - image.shift(2*LEFT) - - self.add(image) - for vect, num in zip([DOWN, RIGHT], [1, 2]): - brace = Brace(image, vect) - words_mob = TextMobject("Dimension %d"%num) - words_mob.next_to(image, vect, buff = 1) - self.play( - Transform(Point(brace.get_center()), brace), - ShimmerIn(words_mob), - run_time = 2 - ) - self.wait() - - -class SoundDataIsOneDimensional(Scene): - def construct(self): - overtones = 5 - floor = 2*DOWN - main_string = Vibrate(color = BLUE_D) - component_strings = [ - Vibrate( - num_periods = k+1, - overtones = 1, - color = color, - center = 2*DOWN + UP*k - ) - for k, color in zip( - list(range(overtones)), - Color(BLUE_E).range_to(WHITE, overtones) - ) - ] - dots = [ - Dot( - string.mobject.get_center(), - color = string.mobject.get_color() - ) - for string in component_strings - ] - - freq_line = get_freq_line() - freq_line.shift(floor) - freq_line.sort_points(get_norm) - brace = Brace(freq_line, UP) - words = TextMobject("Range of frequency values") - words.next_to(brace, UP) - - - self.play(*[ - TransformAnimations( - main_string.copy(), - string, - run_time = 5 - ) - for string in component_strings - ]) - self.clear() - self.play(*[ - TransformAnimations( - string, - Animation(dot) - ) - for string, dot in zip(component_strings, dots) - ]) - self.clear() - self.play( - ShowCreation(freq_line), - GrowFromCenter(brace), - ShimmerIn(words), - *[ - Transform( - dot, - dot.copy().scale(2).rotate(-np.pi/2).shift(floor), - path_func = path_along_arc(np.pi/3) - ) - for dot in dots - ] - ) - self.wait(0.5) - -class GridOfPixels(Scene): - def construct(self): - low_res = ImageMobject("low_resolution_lion", invert = False) - high_res = ImageMobject("Lion", invert = False) - grid = get_grid().scale(0.8) - for mob in low_res, high_res: - mob.replace(grid, stretch = True) - side_brace = Brace(low_res, LEFT) - top_brace = Brace(low_res, UP) - top_words = TextMobject("256 Px", size = "\\normal") - side_words = top_words.copy().rotate(np.pi/2) - top_words.next_to(top_brace, UP) - side_words.next_to(side_brace, LEFT) - - self.add(high_res) - self.wait() - self.play(DelayByOrder(Transform(high_res, low_res))) - self.wait() - self.play( - GrowFromCenter(top_brace), - GrowFromCenter(side_brace), - ShimmerIn(top_words), - ShimmerIn(side_words) - ) - self.wait() - for mob in grid, high_res: - mob.sort_points(get_norm) - self.play(DelayByOrder(Transform(high_res, grid))) - self.wait() - - -class ShowFrequencySpace(Scene): - def construct(self): - freq_line = get_freq_line() - - self.add(freq_line) - self.wait() - for tex, vect in zip(["20 Hz", "20{,}000 Hz"], [LEFT, RIGHT]): - tex_mob = TextMobject(tex) - tex_mob.to_edge(vect) - tex_mob.shift(UP) - arrow = Arrow(tex_mob, freq_line.get_edge_center(vect)) - self.play( - ShimmerIn(tex_mob), - ShowCreation(arrow) - ) - self.wait() - - - -class AssociatePixelWithFrequency(Scene): - def construct(self): - big_grid_dim = 20. - small_grid_dim = 6. - big_grid = Grid(64, 64, height = big_grid_dim, width = big_grid_dim) - big_grid.to_corner(UP+RIGHT, buff = 2) - small_grid = big_grid.copy() - small_grid.scale(small_grid_dim/big_grid_dim) - small_grid.center() - pixel = MobjectFromRegion( - region_from_polygon_vertices(*0.2*np.array([ - RIGHT+DOWN, - RIGHT+UP, - LEFT+UP, - LEFT+DOWN - ])) - ) - pixel.set_color(WHITE) - pixel_width = big_grid.width/big_grid.columns - pixel.set_width(pixel_width) - pixel.to_corner(UP+RIGHT, buff = 2) - pixel.shift(5*pixel_width*(2*LEFT+DOWN)) - - freq_line = get_freq_line() - dot = Dot() - dot.shift(freq_line.get_center() + 2*RIGHT) - string = Line(LEFT, RIGHT, color = GREEN) - arrow = Arrow(dot, string.get_center()) - vibration_config = { - "overtones" : 1, - "spatial_period" : 2, - } - vibration, loud_vibration, quiet_vibration = [ - Vibrate(string.copy(), amplitude = a, **vibration_config) - for a in [0.5, 1., 0.25] - ] - - self.add(small_grid) - self.wait() - self.play( - Transform(small_grid, big_grid) - ) - self.play(FadeIn(pixel)) - self.wait() - self.play( - FadeOut(small_grid), - ShowCreation(freq_line) - ) - self.remove(small_grid) - self.play( - Transform(pixel, dot), - ) - self.wait() - self.play(ShowCreation(arrow)) - self.play(loud_vibration) - self.play( - TransformAnimations(loud_vibration, quiet_vibration), - ApplyMethod(dot.fade, 0.9) - ) - self.clear() - self.add(freq_line, dot, arrow) - self.play(quiet_vibration) - - -class ListenToAllPixels(Scene): - def construct(self): - grid = get_grid() - grid.sort_points(get_norm) - freq_line = get_freq_line() - freq_line.sort_points(lambda p : p[0]) - red, blue = Color(RED), Color(BLUE) - freq_line.set_color_by_gradient(red, blue) - - colors = [ - Color(rgb = interpolate( - np.array(red.rgb), - np.array(blue.rgb), - alpha - )) - for alpha in np.arange(4)/3. - ] - string = Line(3*LEFT, 3*RIGHT, color = colors[1]) - vibration = Vibrate(string) - vibration_copy = vibration.copy() - vibration_copy.mobject.stroke_width = 1 - sub_vibrations = [ - Vibrate( - string.copy().shift((n-1)*UP).set_color(colors[n]), - overtones = 1, - spatial_period = 6./(n+1), - temporal_period = 1./(n+1), - amplitude = 0.5/(n+1) - ) - for n in range(4) - ] - words = TexMobject("&\\vdots \\\\ \\text{thousands }& \\text{of frequencies} \\\\ &\\vdots") - words.to_edge(UP, buff = 0.1) - - self.add(grid) - self.wait() - self.play(DelayByOrder(ApplyMethod( - grid.set_color_by_gradient, red, blue - ))) - self.play(Transform(grid, freq_line)) - self.wait() - self.play( - ShimmerIn( - words, - rate_func = squish_rate_func(smooth, 0, 0.2) - ), - *sub_vibrations, - run_time = 5 - ) - self.play( - *[ - TransformAnimations( - sub_vib, vibration - ) - for sub_vib in sub_vibrations - ]+[FadeOut(words)] - ) - self.clear() - self.add(freq_line) - self.play(vibration) - - -class LayAsideSpeculation(Scene): - def construct(self): - words = TextMobject("Would this actually work?") - grid = get_grid() - grid.set_width(6) - grid.to_edge(LEFT) - freq_line = get_freq_line() - freq_line.set_width(6) - freq_line.center().to_edge(RIGHT) - mapping = Mobject( - grid, freq_line, Arrow(grid, freq_line) - ) - mapping.ingest_submobjects() - lower_left = Point().to_corner(DOWN+LEFT, buff = 0) - lower_right = Point().to_corner(DOWN+RIGHT, buff = 0) - - self.add(words) - self.wait() - self.play( - Transform(words, lower_right), - Transform(lower_left, mapping) - ) - self.wait() - - -class RandomMapping(Scene): - def construct(self): - grid = get_grid() - grid.set_width(6) - grid.to_edge(LEFT) - freq_line = get_freq_line() - freq_line.set_width(6) - freq_line.center().to_edge(RIGHT) - # for mob in grid, freq_line: - # indices = np.arange(mob.get_num_points()) - # random.shuffle(indices) - # mob.points = mob.points[indices] - stars = Stars(stroke_width = grid.stroke_width) - - self.add(grid) - targets = [stars, freq_line] - alphas = [not_quite_there(rush_into), rush_from] - for target, rate_func in zip(targets, alphas): - self.play(Transform( - grid, target, - run_time = 3, - rate_func = rate_func, - path_func = path_along_arc(-np.pi/2) - )) - self.wait() - - - -class DataScrambledAnyway(Scene): - def construct(self): - self.add(TextMobject("Data is scrambled anyway, right?")) - self.wait() - - -class LeverageExistingIntuitions(Scene): - def construct(self): - self.add(TextMobject("Leverage existing intuitions")) - self.wait() - - - - -class ThinkInTermsOfReverseMapping(Scene): - def construct(self): - grid = get_grid() - grid.set_width(6) - grid.to_edge(LEFT) - freq_line = get_freq_line() - freq_line.set_width(6) - freq_line.center().to_edge(RIGHT) - arrow = Arrow(grid, freq_line) - - color1, color2 = YELLOW_C, RED - square_length = 0.01 - dot1 = Dot(color = color1) - dot1.shift(3*RIGHT) - dot2 = Dot(color = color2) - dot2.shift(3.1*RIGHT) - arrow1 = Arrow(2*RIGHT+UP, dot1, color = color1, buff = 0.1) - arrow2 = Arrow(4*RIGHT+UP, dot2, color = color2, buff = 0.1) - dot3, arrow3 = [ - mob.copy().shift(5*LEFT+UP) - for mob in (dot1, arrow1) - ] - dot4, arrow4 = [ - mob.copy().shift(5*LEFT+0.9*UP) - for mob in (dot2, arrow2) - ] - - self.add(grid, freq_line, arrow) - self.wait() - self.play(ApplyMethod( - arrow.rotate, np.pi, - path_func = clockwise_path() - )) - self.wait() - self.play(ShowCreation(arrow1)) - self.add(dot1) - self.play(ShowCreation(arrow2)) - self.add(dot2) - self.wait() - self.remove(arrow1, arrow2) - self.play( - Transform(dot1, dot3), - Transform(dot2, dot4) - ) - self.play( - ApplyMethod(grid.fade, 0.8), - Animation(Mobject(dot3, dot4)) - ) - self.play(ShowCreation(arrow3)) - self.play(ShowCreation(arrow4)) - self.wait() - - -class WeaveLineThroughPixels(Scene): - @staticmethod - def args_to_string(order): - return str(order) - - @staticmethod - def string_to_args(order_str): - return int(order_str) - - def construct(self, order): - start_color, end_color = RED, GREEN - curve = HilbertCurve(order = order) - line = Line(5*LEFT, 5*RIGHT) - for mob in curve, line: - mob.set_color_by_gradient(start_color, end_color) - freq_line = get_freq_line() - freq_line.replace(line, stretch = True) - - unit = 6./(2**order) #sidelength of pixel - up = unit*UP - right = unit*RIGHT - lower_left = 3*(LEFT+DOWN) - squares = Mobject(*[ - Square( - side_length = unit, - color = WHITE - ).shift(x*right+y*up) - for x, y in it.product(list(range(2**order)), list(range(2**order))) - ]) - squares.center() - targets = Mobject() - for square in squares.submobjects: - center = square.get_center() - distances = np.apply_along_axis( - lambda p : get_norm(p-center), - 1, - curve.points - ) - index_along_curve = np.argmin(distances) - fraction_along_curve = index_along_curve/float(curve.get_num_points()) - target = square.copy().center().scale(0.8/(2**order)) - line_index = int(fraction_along_curve*line.get_num_points()) - target.shift(line.points[line_index]) - targets.add(target) - - - self.add(squares) - self.play(ShowCreation( - curve, - run_time = 5, - rate_func=linear - )) - self.wait() - self.play( - Transform(curve, line), - Transform(squares, targets), - run_time = 3 - ) - self.wait() - self.play(ShowCreation(freq_line)) - self.wait() - - -class WellPlayedGameOfSnake(Scene): - def construct(self): - grid = Grid(16, 16).fade() - snake_curve = SnakeCurve(order = 4) - words = TextMobject("``Snake Curve''") - words.next_to(grid, UP) - - self.add(grid) - self.play(ShowCreation( - snake_curve, - run_time = 7, - rate_func=linear - )) - self.wait() - self.play(ShimmerIn(words)) - self.wait() - - -class TellMathematicianFriend(Scene): - def construct(self): - mathy, bubble = get_mathy_and_bubble() - squiggle_mouth = mathy.mouth.copy() - squiggle_mouth.apply_function( - lambda x_y_z : (x_y_z[0], x_y_z[1]+0.02*np.sin(50*x_y_z[0]), x_y_z[2]) - ) - bubble.ingest_submobjects() - bubble.write("Why not use a Hilbert curve \\textinterrobang ") - words1 = bubble.content - bubble.write("So, it's not one curve but an infinite family of curves \\dots") - words2 = bubble.content - bubble.write("Well, no, it \\emph{is} just one thing, but I need \\\\ \ - to tell you about a certain infinite family first.") - words3 = bubble.content - description = TextMobject("Mathematician friend", size = "\\small") - description.next_to(mathy, buff = 2) - arrow = Arrow(description, mathy) - - self.add(mathy) - self.play( - ShowCreation(arrow), - ShimmerIn(description) - ) - self.wait() - point = Point(bubble.get_tip()) - self.play( - Transform(point, bubble), - ) - self.remove(point) - self.add(bubble) - self.play(ShimmerIn(words1)) - self.wait() - self.remove(description, arrow) - self.play( - Transform(mathy.mouth, squiggle_mouth), - ApplyMethod(mathy.arm.wag, 0.2*RIGHT, LEFT), - ) - self.remove(words1) - self.add(words2) - self.wait(2) - self.remove(words2) - self.add(words3) - self.wait(2) - self.play( - ApplyPointwiseFunction( - lambda p : 15*p/get_norm(p), - bubble - ), - ApplyMethod(mathy.shift, 5*(DOWN+LEFT)), - FadeOut(words3), - run_time = 3 - ) - - -class Order1PseudoHilbertCurve(Scene): - def construct(self): - words, s = TextMobject(["Pseudo-Hilbert Curve", "s"]).split() - pre_words = TextMobject("Order 1") - pre_words.next_to(words, LEFT, buff = 0.5) - s.next_to(words, RIGHT, buff = 0.05, aligned_edge = DOWN) - cluster = Mobject(pre_words, words, s) - cluster.center() - cluster.scale(0.7) - cluster.to_edge(UP, buff = 0.3) - cluster.set_color(GREEN) - grid1 = Grid(1, 1) - grid2 = Grid(2, 2) - curve = HilbertCurve(order = 1) - - self.add(words, s) - self.wait() - self.play(Transform( - s, pre_words, - path_func = path_along_arc(-np.pi/3) - )) - self.wait() - self.play(ShowCreation(grid1)) - self.wait() - self.play(ShowCreation(grid2)) - self.wait() - kwargs = { - "run_time" : 5, - "rate_func" : None - } - self.play(ShowCreation(curve, **kwargs)) - self.wait() - -class Order2PseudoHilbertCurve(Scene): - def construct(self): - words = TextMobject("Order 2 Pseudo-Hilbert Curve") - words.to_edge(UP, buff = 0.3) - words.set_color(GREEN) - grid2 = Grid(2, 2) - grid4 = Grid(4, 4, stroke_width = 2) - # order_1_curve = HilbertCurve(order = 1) - # squaggle_curve = order_1_curve.copy().apply_function( - # lambda (x, y, z) : (x + np.cos(3*y), y + np.sin(3*x), z) - # ) - # squaggle_curve.show() - mini_curves = [ - HilbertCurve(order = 1).scale(0.5).shift(1.5*vect) - for vect in [ - LEFT+DOWN, - LEFT+UP, - RIGHT+UP, - RIGHT+DOWN - ] - ] - last_curve = mini_curves[0] - naive_curve = Mobject(last_curve) - for mini_curve in mini_curves[1:]: - line = Line(last_curve.points[-1], mini_curve.points[0]) - naive_curve.add(line, mini_curve) - last_curve = mini_curve - naive_curve.ingest_submobjects() - naive_curve.set_color_by_gradient(RED, GREEN) - order_2_curve = HilbertCurve(order = 2) - - self.add(words, grid2) - self.wait() - self.play(ShowCreation(grid4)) - self.play(*[ - ShowCreation(mini_curve) - for mini_curve in mini_curves - ]) - self.wait() - self.play(ShowCreation(naive_curve, run_time = 5)) - self.remove(*mini_curves) - self.wait() - self.play(Transform(naive_curve, order_2_curve)) - self.wait() - - -class Order3PseudoHilbertCurve(Scene): - def construct(self): - words = TextMobject("Order 3 Pseudo-Hilbert Curve") - words.set_color(GREEN) - words.to_edge(UP) - grid4 = Mobject( - Grid(2, 2), - Grid(4, 4, stroke_width = 2) - ) - grid8 = Grid(8, 8, stroke_width = 1) - order_3_curve = HilbertCurve(order = 3) - mini_curves = [ - HilbertCurve(order = 2).scale(0.5).shift(1.5*vect) - for vect in [ - LEFT+DOWN, - LEFT+UP, - RIGHT+UP, - RIGHT+DOWN - ] - ] - - self.add(words, grid4) - self.wait() - self.play(ShowCreation(grid8)) - self.wait() - self.play(*list(map(GrowFromCenter, mini_curves))) - self.wait() - self.clear() - self.add(words, grid8, *mini_curves) - self.play(*[ - ApplyMethod(curve.rotate_in_place, np.pi, axis) - for curve, axis in [ - (mini_curves[0], UP+RIGHT), - (mini_curves[3], UP+LEFT) - ] - ]) - self.play(ShowCreation(order_3_curve, run_time = 5)) - self.wait() - -class GrowToOrder8PseudoHilbertCurve(Scene): - def construct(self): - self.curve = HilbertCurve(order = 1) - self.add(self.curve) - self.wait() - while self.curve.order < 8: - self.increase_order() - - - def increase_order(self): - mini_curves = [ - self.curve.copy().scale(0.5).shift(1.5*vect) - for vect in [ - LEFT+DOWN, - LEFT+UP, - RIGHT+UP, - RIGHT+DOWN - ] - ] - self.remove(self.curve) - self.play( - Transform(self.curve.copy(), mini_curves[0]) - ) - self.play(*[ - GrowFromCenter(mini_curve) - for mini_curve in mini_curves[1:] - ]) - self.wait() - self.clear() - self.add(*mini_curves) - self.play(*[ - ApplyMethod(curve.rotate_in_place, np.pi, axis) - for curve, axis in [ - (mini_curves[0], UP+RIGHT), - (mini_curves[3], UP+LEFT) - ] - ]) - self.curve = HilbertCurve(order = self.curve.order+1) - self.play(ShowCreation(self.curve, run_time = 2)) - self.remove(*mini_curves) - self.wait() - - -class UseOrder8(Scene): - def construct(self): - mathy, bubble = get_mathy_and_bubble() - bubble.write("For a 256x256 pixel array...") - words = TextMobject("Order 8 Pseudo-Hilbert Curve") - words.set_color(GREEN) - words.to_edge(UP, buff = 0.3) - curve = HilbertCurve(order = 8) - - self.add(mathy, bubble) - self.play(ShimmerIn(bubble.content)) - self.wait() - self.clear() - self.add(words) - self.play(ShowCreation( - curve, run_time = 7, rate_func=linear - )) - self.wait() - - - -class HilbertBetterThanSnakeQ(Scene): - def construct(self): - hilbert_curves, snake_curves = [ - [ - CurveClass(order = n) - for n in range(2, 7) - ] - for CurveClass in (HilbertCurve, SnakeCurve) - ] - for curve in hilbert_curves+snake_curves: - curve.scale(0.8) - for curve in hilbert_curves: - curve.to_edge(LEFT) - for curve in snake_curves: - curve.to_edge(RIGHT) - greater_than = TexMobject(">") - question_mark = TextMobject("?") - question_mark.next_to(greater_than, UP) - - self.add(greater_than, question_mark) - hilbert_curve = hilbert_curves[0] - snake_curve = snake_curves[0] - for new_hc, new_sc in zip(hilbert_curves[1:], snake_curves[1:]): - self.play(*[ - Transform(hilbert_curve, new_hc), - Transform(snake_curve, new_sc) - ]) - self.wait() - - -class ImagineItWorks(Scene): - def construct(self): - self.add(TextMobject("Imagine your project succeeds...")) - self.wait() - - -class RandyWithHeadphones(Scene): - def construct(self): - headphones = ImageMobject("Headphones.png") - headphones.scale(0.1) - headphones.stretch(2, 0) - headphones.shift(1.2*UP+0.05*LEFT) - headphones.set_color(GREY) - randy = Randolph() - - self.add(randy, headphones) - self.wait(2) - self.play(ApplyMethod(randy.blink)) - self.wait(4) - - -class IncreaseResolution(Scene): - def construct(self): - grids = [ - Grid( - 2**order, 2**order, - stroke_width = 1 - ).shift(0.3*DOWN) - for order in (6, 7) - ] - grid = grids[0] - side_brace = Brace(grid, LEFT) - top_brace = Brace(grid, UP) - top_words = TextMobject("256") - new_top_words = TextMobject("512") - side_words = top_words.copy() - new_side_words = new_top_words.copy() - for words in top_words, new_top_words: - words.next_to(top_brace, UP, buff = 0.1) - for words in side_words, new_side_words: - words.next_to(side_brace, LEFT) - - self.add(grid) - self.play( - GrowFromCenter(side_brace), - GrowFromCenter(top_brace), - ShimmerIn(top_words), - ShimmerIn(side_words) - ) - self.wait() - self.play( - DelayByOrder(Transform(*grids)), - Transform(top_words, new_top_words), - Transform(side_words, new_side_words) - ) - self.wait() - - -class IncreasingResolutionWithSnakeCurve(Scene): - def construct(self): - start_curve = SnakeCurve(order = 6) - end_curve = SnakeCurve(order = 7) - start_dots, end_dots = [ - Mobject(*[ - Dot( - curve.points[int(x*curve.get_num_points())], - color = color - ) - for x, color in [ - (0.202, GREEN), - (0.48, BLUE), - (0.7, RED) - ] - ]) - for curve in (start_curve, end_curve) - ] - self.add(start_curve) - self.wait() - self.play( - ShowCreation(start_dots, run_time = 2), - ApplyMethod(start_curve.fade) - ) - end_curve.fade() - self.play( - Transform(start_curve, end_curve), - Transform(start_dots, end_dots) - ) - self.wait() - - -class TrackSpecificCurvePoint(Scene): - CURVE_CLASS = None #Fillin - def construct(self): - line = get_freq_line().center() - line.sort_points(lambda p : p[0]) - curves = [ - self.CURVE_CLASS(order = order) - for order in range(3, 10) - ] - alpha = 0.48 - dot = Dot(UP) - start_dot = Dot(0.1*LEFT) - dots = [ - Dot(curve.points[alpha*curve.get_num_points()]) - for curve in curves - ] - - self.play(ShowCreation(line)) - self.play(Transform(dot, start_dot)) - self.wait() - for new_dot, curve in zip(dots, curves): - self.play( - Transform(line, curve), - Transform(dot, new_dot) - ) - self.wait() - - -class TrackSpecificSnakeCurvePoint(TrackSpecificCurvePoint): - CURVE_CLASS = SnakeCurve - - -class NeedToRelearn(Scene): - def construct(self): - top_words = TextMobject("Different pixel-frequency association") - bottom_words = TextMobject("Need to relearn sight-via-sound") - top_words.shift(UP) - bottom_words.shift(DOWN) - arrow = Arrow(top_words, bottom_words) - - self.play(ShimmerIn(top_words)) - self.wait() - self.play(ShowCreation(arrow)) - self.play(ShimmerIn(bottom_words)) - self.wait() - - -class TrackSpecificHilbertCurvePoint(TrackSpecificCurvePoint): - CURVE_CLASS = HilbertCurve - - - diff --git a/from_3b1b/old/hilbert/section2.py b/from_3b1b/old/hilbert/section2.py deleted file mode 100644 index 8c5dde12..00000000 --- a/from_3b1b/old/hilbert/section2.py +++ /dev/null @@ -1,1063 +0,0 @@ -from manimlib.imports import * -import displayer as disp -from hilbert.curves import \ - TransformOverIncreasingOrders, FlowSnake, HilbertCurve, \ - SnakeCurve, PeanoCurve -from hilbert.section1 import get_mathy_and_bubble -from scipy.spatial.distance import cdist - - -def get_time_line(): - length = 2.6*FRAME_WIDTH - year_range = 400 - time_line = NumberLine( - numerical_radius = year_range/2, - unit_length_to_spatial_width = length/year_range, - tick_frequency = 10, - leftmost_tick = 1720, - number_at_center = 1870, - numbers_with_elongated_ticks = list(range(1700, 2100, 100)) - ) - time_line.sort_points(lambda p : p[0]) - time_line.set_color_by_gradient( - PeanoCurve.CONFIG["start_color"], - PeanoCurve.CONFIG["end_color"] - ) - time_line.add_numbers( - 2020, *list(range(1800, 2050, 50)) - ) - return time_line - - -class SectionTwo(Scene): - def construct(self): - self.add(TextMobject("Section 2: Filling space")) - self.wait() - -class HilbertCurveIsPerfect(Scene): - def construct(self): - curve = HilbertCurve(order = 6) - curve.set_color(WHITE) - colored_curve = curve.copy() - colored_curve.thin_out(3) - lion = ImageMobject("lion", invert = False) - lion.replace(curve, stretch = True) - sparce_lion = lion.copy() - sparce_lion.thin_out(100) - distance_matrix = cdist(colored_curve.points, sparce_lion.points) - closest_point_indices = np.apply_along_axis( - np.argmin, 1, distance_matrix - ) - colored_curve.rgbas = sparce_lion.rgbas[closest_point_indices] - line = Line(5*LEFT, 5*RIGHT) - Mobject.align_data(line, colored_curve) - line.rgbas = colored_curve.rgbas - - self.add(lion) - self.play(ShowCreation(curve, run_time = 3)) - self.play( - FadeOut(lion), - Transform(curve, colored_curve), - run_time = 3 - ) - self.wait() - self.play(Transform(curve, line, run_time = 5)) - self.wait() - - -class AskMathematicianFriend(Scene): - def construct(self): - mathy, bubble = get_mathy_and_bubble() - bubble.sort_points(lambda p : np.dot(p, UP+RIGHT)) - - self.add(mathy) - self.wait() - self.play(ApplyMethod( - mathy.blink, - rate_func = squish_rate_func(there_and_back) - )) - self.wait() - self.play(ShowCreation(bubble)) - self.wait() - self.play( - ApplyMethod(mathy.shift, 3*(DOWN+LEFT)), - ApplyPointwiseFunction( - lambda p : 15*p/get_norm(p), - bubble - ), - run_time = 3 - ) - -class TimeLineAboutSpaceFilling(Scene): - def construct(self): - curve = PeanoCurve(order = 5) - curve.stretch_to_fit_width(FRAME_WIDTH) - curve.stretch_to_fit_height(FRAME_HEIGHT) - curve_start = curve.copy() - curve_start.apply_over_attr_arrays( - lambda arr : arr[:200] - ) - time_line = get_time_line() - time_line.shift(-time_line.number_to_point(2000)) - - self.add(time_line) - self.play(ApplyMethod( - time_line.shift, - -time_line.number_to_point(1900), - run_time = 3 - )) - brace = Brace( - Mobject( - Point(time_line.number_to_point(1865)), - Point(time_line.number_to_point(1888)), - ), - UP - ) - words = TextMobject(""" - Cantor drives himself (and the \\\\ - mathematical community at large) \\\\ - crazy with research on infinity. - """) - words.next_to(brace, UP) - self.play( - GrowFromCenter(brace), - ShimmerIn(words) - ) - self.wait() - self.play( - Transform(time_line, curve_start), - FadeOut(brace), - FadeOut(words) - ) - self.play(ShowCreation( - curve, - run_time = 5, - rate_func=linear - )) - self.wait() - - - -class NotPixelatedSpace(Scene): - def construct(self): - grid = Grid(64, 64) - space_region = Region() - space_mobject = MobjectFromRegion(space_region, DARK_GREY) - curve = PeanoCurve(order = 5).replace(space_mobject) - line = Line(5*LEFT, 5*RIGHT) - line.set_color_by_gradient(curve.start_color, curve.end_color) - for mob in grid, space_mobject: - mob.sort_points(get_norm) - infinitely = TextMobject("Infinitely") - detailed = TextMobject("detailed") - extending = TextMobject("extending") - detailed.next_to(infinitely, RIGHT) - extending.next_to(infinitely, RIGHT) - Mobject(extending, infinitely, detailed).center() - arrows = Mobject(*[ - Arrow(2*p, 4*p) - for theta in np.arange(np.pi/6, 2*np.pi, np.pi/3) - for p in [rotate_vector(RIGHT, theta)] - ]) - - self.add(grid) - self.wait() - self.play(Transform(grid, space_mobject, run_time = 5)) - self.remove(grid) - self.set_color_region(space_region, DARK_GREY) - self.wait() - self.add(infinitely, detailed) - self.wait() - self.play(DelayByOrder(Transform(detailed, extending))) - self.play(ShowCreation(arrows)) - self.wait() - self.clear() - self.set_color_region(space_region, DARK_GREY) - self.play(ShowCreation(line)) - self.play(Transform(line, curve, run_time = 5)) - - - -class HistoryOfDiscover(Scene): - def construct(self): - time_line = get_time_line() - time_line.shift(-time_line.number_to_point(1900)) - hilbert_curve = HilbertCurve(order = 3) - peano_curve = PeanoCurve(order = 2) - for curve in hilbert_curve, peano_curve: - curve.scale(0.5) - hilbert_curve.to_corner(DOWN+RIGHT) - peano_curve.to_corner(UP+LEFT) - squares = Mobject(*[ - Square(side_length=3, color=WHITE).replace(curve) - for curve in (hilbert_curve, peano_curve) - ]) - - - self.add(time_line) - self.wait() - for year, curve, vect, text in [ - (1890, peano_curve, UP, "Peano Curve"), - (1891, hilbert_curve, DOWN, "Hilbert Curve"), - ]: - point = time_line.number_to_point(year) - point[1] = 0.2 - arrow = Arrow(point+2*vect, point, buff = 0.1) - arrow.set_color_by_gradient(curve.start_color, curve.end_color) - year_mob = TexMobject(str(year)) - year_mob.next_to(arrow, vect) - words = TextMobject(text) - words.next_to(year_mob, vect) - - self.play( - ShowCreation(arrow), - ShimmerIn(year_mob), - ShimmerIn(words) - ) - self.play(ShowCreation(curve)) - self.wait() - self.play(ShowCreation(squares)) - self.wait() - self.play(ApplyMethod( - Mobject(*self.mobjects).shift, 20*(DOWN+RIGHT) - )) - - - -class DefinitionOfCurve(Scene): - def construct(self): - start_words = TextMobject([ - "``", "Space Filling", "Curve ''", - ]).to_edge(TOP, buff = 0.25) - quote, space_filling, curve_quote = start_words.copy().split() - curve_quote.shift( - space_filling.get_left()-\ - curve_quote.get_left() - ) - space_filling = Point(space_filling.get_center()) - end_words = Mobject(*[ - quote, space_filling, curve_quote - ]).center().to_edge(TOP, buff = 0.25) - space_filling_fractal = TextMobject(""" - ``Space Filling Fractal'' - """).to_edge(UP) - curve = HilbertCurve(order = 2).shift(DOWN) - fine_curve = HilbertCurve(order = 8) - fine_curve.replace(curve) - dots = Mobject(*[ - Dot( - curve.points[n*curve.get_num_points()/15], - color = YELLOW_C - ) - for n in range(1, 15) - if n not in [4, 11] - ]) - - start_words.shift(2*(UP+LEFT)) - self.play( - ApplyMethod(start_words.shift, 2*(DOWN+RIGHT)) - ) - self.wait() - self.play(Transform(start_words, end_words)) - self.wait() - self.play(ShowCreation(curve)) - self.wait() - self.play(ShowCreation( - dots, - run_time = 3, - )) - self.wait() - self.clear() - self.play(ShowCreation(fine_curve, run_time = 5)) - self.wait() - self.play(ShimmerIn(space_filling_fractal)) - self.wait() - - -class PseudoHilbertCurvesDontFillSpace(Scene): - def construct(self): - curve = HilbertCurve(order = 1) - grid = Grid(2, 2, stroke_width=1) - self.add(grid, curve) - for order in range(2, 6): - self.wait() - new_grid = Grid(2**order, 2**order, stroke_width=1) - self.play( - ShowCreation(new_grid), - Animation(curve) - ) - self.remove(grid) - grid = new_grid - self.play(Transform( - curve, HilbertCurve(order = order) - )) - - - square = Square(side_length = 6, color = WHITE) - square.corner = Mobject1D() - square.corner.add_line(3*DOWN, ORIGIN) - square.corner.add_line(ORIGIN, 3*RIGHT) - square.digest_mobject_attrs() - square.scale(2**(-5)) - square.corner.set_color( - Color(rgb = curve.rgbas[curve.get_num_points()/3]) - ) - square.shift( - grid.get_corner(UP+LEFT)-\ - square.get_corner(UP+LEFT) - ) - - - self.wait() - self.play( - FadeOut(grid), - FadeOut(curve), - FadeIn(square) - ) - self.play( - ApplyMethod(square.replace, grid) - ) - self.wait() - - -class HilbertCurveIsLimit(Scene): - def construct(self): - mathy, bubble = get_mathy_and_bubble() - bubble.write( - "A Hilbert curve is the \\\\ limit of all these \\dots" - ) - - self.add(mathy, bubble) - self.play(ShimmerIn(bubble.content)) - self.wait() - - -class DefiningCurves(Scene): - def construct(self): - words = TextMobject( - ["One does not simply define the limit \\\\ \ - of a sequence of","curves","\\dots"] - ) - top_words = TextMobject([ - "curves", "are functions" - ]).to_edge(UP) - curves1 = words.split()[1] - curves2 = top_words.split()[0] - words.ingest_submobjects() - number = TexMobject("0.27") - pair = TexMobject("(0.53, 0.02)") - pair.next_to(number, buff = 2) - arrow = Arrow(number, pair) - Mobject(number, arrow, pair).center().shift(UP) - number_line = UnitInterval() - number_line.stretch_to_fit_width(5) - number_line.to_edge(LEFT).shift(DOWN) - grid = Grid(4, 4).scale(0.4) - grid.next_to(number_line, buff = 2) - low_arrow = Arrow(number_line, grid) - - self.play(ShimmerIn(words)) - self.wait() - self.play( - FadeOut(words), - ApplyMethod(curves1.replace, curves2), - ShimmerIn(top_words.split()[1]) - ) - self.wait() - self.play(FadeIn(number)) - self.play(ShowCreation(arrow)) - self.play(FadeIn(pair)) - self.wait() - self.play(ShowCreation(number_line)) - self.play(ShowCreation(low_arrow)) - self.play(ShowCreation(grid)) - self.wait() - - -class PseudoHilbertCurveAsFunctionExample(Scene): - args_list = [(2,), (3,)] - - # For subclasses to turn args in the above - # list into stings which can be appended to the name - @staticmethod - def args_to_string(order): - return "Order%d"%order - - @staticmethod - def string_to_args(order_str): - return int(order_str) - - - def construct(self, order): - if order == 2: - result_tex = "(0.125, 0.75)" - elif order == 3: - result_tex = "(0.0758, 0.6875)" - - phc, arg, result = TexMobject([ - "\\text{PHC}_%d"%order, - "(0.3)", - "= %s"%result_tex - ]).to_edge(UP).split() - function = TextMobject("Function", size = "\\normal") - function.shift(phc.get_center()+DOWN+2*LEFT) - function_arrow = Arrow(function, phc) - - line = Line(5*LEFT, 5*RIGHT) - curve = HilbertCurve(order = order) - line.match_colors(curve) - grid = Grid(2**order, 2**order) - grid.fade() - for mob in curve, grid: - mob.scale(0.7) - index = int(0.3*line.get_num_points()) - dot1 = Dot(line.points[index]) - arrow1 = Arrow(arg, dot1, buff = 0.1) - dot2 = Dot(curve.points[index]) - arrow2 = Arrow(result.get_bottom(), dot2, buff = 0.1) - - self.add(phc) - self.play( - ShimmerIn(function), - ShowCreation(function_arrow) - ) - self.wait() - self.remove(function_arrow, function) - self.play(ShowCreation(line)) - self.wait() - self.play( - ShimmerIn(arg), - ShowCreation(arrow1), - ShowCreation(dot1) - ) - self.wait() - self.remove(arrow1) - self.play( - FadeIn(grid), - Transform(line, curve), - Transform(dot1, dot2), - run_time = 2 - ) - self.wait() - self.play( - ShimmerIn(result), - ShowCreation(arrow2) - ) - self.wait() - - - -class ContinuityRequired(Scene): - def construct(self): - words = TextMobject([ - "A function must be", - "\\emph{continuous}", - "if it is to represent a curve." - ]) - words.split()[1].set_color(YELLOW_C) - self.add(words) - self.wait() - - - - -class FormalDefinitionOfContinuity(Scene): - def construct(self): - self.setup() - self.label_spaces() - self.move_dot() - self.label_jump() - self.draw_circles() - self.vary_circle_sizes() - self.discontinuous_point() - - - def setup(self): - self.input_color = YELLOW_C - self.output_color = RED - def spiril(t): - theta = 2*np.pi*t - return t*np.cos(theta)*RIGHT+t*np.sin(theta)*UP - - self.spiril1 = ParametricFunction( - lambda t : 1.5*RIGHT + DOWN + 2*spiril(t), - density = 5*DEFAULT_POINT_DENSITY_1D, - ) - self.spiril2 = ParametricFunction( - lambda t : 5.5*RIGHT + UP - 2*spiril(1-t), - density = 5*DEFAULT_POINT_DENSITY_1D, - ) - Mobject.align_data(self.spiril1, self.spiril2) - self.output = Mobject(self.spiril1, self.spiril2) - self.output.ingest_submobjects() - self.output.set_color(GREEN_A) - - self.interval = UnitInterval() - self.interval.set_width(FRAME_X_RADIUS-1) - self.interval.to_edge(LEFT) - - self.input_dot = Dot(color = self.input_color) - self.output_dot = self.input_dot.copy().set_color(self.output_color) - left, right = self.interval.get_left(), self.interval.get_right() - self.input_homotopy = lambda x_y_z_t : (x_y_z_t[0], x_y_z_t[1], x_y_z_t[3]) + interpolate(left, right, x_y_z_t[3]) - output_size = self.output.get_num_points()-1 - output_points = self.output.points - self.output_homotopy = lambda x_y_z_t1 : (x_y_z_t1[0], x_y_z_t1[1], x_y_z_t1[2]) + output_points[int(x_y_z_t1[3]*output_size)] - - def get_circles_and_points(self, min_input, max_input): - input_left, input_right = [ - self.interval.number_to_point(num) - for num in (min_input, max_input) - ] - input_circle = Circle( - radius = get_norm(input_left-input_right)/2, - color = WHITE - ) - input_circle.shift((input_left+input_right)/2) - - input_points = Line( - input_left, input_right, - color = self.input_color - ) - output_points = Mobject(color = self.output_color) - n = self.output.get_num_points() - output_points.add_points( - self.output.points[int(min_input*n):int(max_input*n)] - ) - output_center = output_points.points[int(0.5*output_points.get_num_points())] - max_distance = get_norm(output_center-output_points.points[-1]) - output_circle = Circle( - radius = max_distance, - color = WHITE - ) - output_circle.shift(output_center) - return ( - input_circle, - input_points, - output_circle, - output_points - ) - - - def label_spaces(self): - input_space = TextMobject("Input Space") - input_space.to_edge(UP) - input_space.shift(LEFT*FRAME_X_RADIUS/2) - output_space = TextMobject("Output Space") - output_space.to_edge(UP) - output_space.shift(RIGHT*FRAME_X_RADIUS/2) - line = Line( - UP*FRAME_Y_RADIUS, DOWN*FRAME_Y_RADIUS, - color = WHITE - ) - self.play( - ShimmerIn(input_space), - ShimmerIn(output_space), - ShowCreation(line), - ShowCreation(self.interval), - ) - self.wait() - - def move_dot(self): - kwargs = { - "rate_func" : None, - "run_time" : 3 - } - self.play( - Homotopy(self.input_homotopy, self.input_dot, **kwargs), - Homotopy(self.output_homotopy, self.output_dot, **kwargs), - ShowCreation(self.output, **kwargs) - ) - self.wait() - - def label_jump(self): - jump_points = Mobject( - Point(self.spiril1.points[-1]), - Point(self.spiril2.points[0]) - ) - self.brace = Brace(jump_points, RIGHT) - self.jump = TextMobject("Jump") - self.jump.next_to(self.brace, RIGHT) - self.play( - GrowFromCenter(self.brace), - ShimmerIn(self.jump) - ) - self.wait() - self.remove(self.brace, self.jump) - - - def draw_circles(self): - input_value = 0.45 - input_radius = 0.04 - for dot in self.input_dot, self.output_dot: - dot.center() - kwargs = { - "rate_func" : lambda t : interpolate(1, input_value, smooth(t)) - } - self.play( - Homotopy(self.input_homotopy, self.input_dot, **kwargs), - Homotopy(self.output_homotopy, self.output_dot, **kwargs) - ) - - A, B = list(map(Mobject.get_center, [self.input_dot, self.output_dot])) - A_text = TextMobject("A") - A_text.shift(A+2*(LEFT+UP)) - A_arrow = Arrow( - A_text, self.input_dot, - color = self.input_color - ) - B_text = TextMobject("B") - B_text.shift(B+2*RIGHT+DOWN) - B_arrow = Arrow( - B_text, self.output_dot, - color = self.output_color - ) - tup = self.get_circles_and_points( - input_value-input_radius, - input_value+input_radius - ) - input_circle, input_points, output_circle, output_points = tup - - for text, arrow in [(A_text, A_arrow), (B_text, B_arrow)]: - self.play( - ShimmerIn(text), - ShowCreation(arrow) - ) - self.wait() - self.remove(A_text, A_arrow, B_text, B_arrow) - self.play(ShowCreation(input_circle)) - self.wait() - self.play(ShowCreation(input_points)) - self.wait() - input_points_copy = input_points.copy() - self.play( - Transform(input_points_copy, output_points), - run_time = 2 - ) - self.wait() - self.play(ShowCreation(output_circle)) - self.wait() - self.wait() - self.remove(*[ - input_circle, input_points, - output_circle, input_points_copy - ]) - - - def vary_circle_sizes(self): - input_value = 0.45 - radius = 0.04 - vary_circles = VaryCircles( - self, input_value, radius, - run_time = 5, - ) - self.play(vary_circles) - self.wait() - text = TextMobject("Function is ``Continuous at A''") - text.shift(2*UP).to_edge(LEFT) - arrow = Arrow(text, self.input_dot) - self.play( - ShimmerIn(text), - ShowCreation(arrow) - ) - self.wait() - self.remove(vary_circles.mobject, text, arrow) - - def discontinuous_point(self): - point_description = TextMobject( - "Point where the function jumps" - ) - point_description.shift(3*RIGHT) - discontinuous_at_A = TextMobject( - "``Discontinuous at A''", - size = "\\Large" - ) - discontinuous_at_A.shift(2*UP).to_edge(LEFT) - text = TextMobject(""" - Circle around ouput \\\\ - points can never \\\\ - be smaller than \\\\ - the jump - """) - text.scale(0.75) - text.shift(3.5*RIGHT) - - input_value = 0.5 - input_radius = 0.04 - vary_circles = VaryCircles( - self, input_value, input_radius, - run_time = 5, - ) - for dot in self.input_dot, self.output_dot: - dot.center() - kwargs = { - "rate_func" : lambda t : interpolate(0.45, input_value, smooth(t)) - } - self.play( - Homotopy(self.input_homotopy, self.input_dot, **kwargs), - Homotopy(self.output_homotopy, self.output_dot, **kwargs) - ) - discontinuous_arrow = Arrow(discontinuous_at_A, self.input_dot) - arrow = Arrow( - point_description, self.output_dot, - buff = 0.05, - color = self.output_color - ) - self.play( - ShimmerIn(point_description), - ShowCreation(arrow) - ) - self.wait() - self.remove(point_description, arrow) - - tup = self.get_circles_and_points( - input_value-input_radius, - input_value+input_radius - ) - input_circle, input_points, output_circle, output_points = tup - input_points_copy = input_points.copy() - self.play(ShowCreation(input_circle)) - self.play(ShowCreation(input_points)) - self.play( - Transform(input_points_copy, output_points), - run_time = 2 - ) - self.play(ShowCreation(output_circle)) - self.wait() - self.play(ShimmerIn(text)) - self.remove(input_circle, input_points, output_circle, input_points_copy) - self.play(vary_circles) - self.wait() - self.play( - ShimmerIn(discontinuous_at_A), - ShowCreation(discontinuous_arrow) - ) - self.wait(3) - self.remove(vary_circles.mobject, discontinuous_at_A, discontinuous_arrow) - - def continuous_point(self): - pass - - - -class VaryCircles(Animation): - def __init__(self, scene, input_value, radius, **kwargs): - digest_locals(self) - Animation.__init__(self, Mobject(), **kwargs) - - def interpolate_mobject(self, alpha): - radius = self.radius + 0.9*self.radius*np.sin(1.5*np.pi*alpha) - self.mobject = Mobject(*self.scene.get_circles_and_points( - self.input_value-radius, - self.input_value+radius - )).ingest_submobjects() - - -class FunctionIsContinuousText(Scene): - def construct(self): - all_points = TextMobject("$f$ is continuous at every input point") - continuous = TextMobject("$f$ is continuous") - all_points.shift(UP) - continuous.shift(DOWN) - arrow = Arrow(all_points, continuous) - - self.play(ShimmerIn(all_points)) - self.play(ShowCreation(arrow)) - self.play(ShimmerIn(continuous)) - self.wait() - - -class DefineActualHilbertCurveText(Scene): - def construct(self): - self.add(TextMobject(""" - Finally define a Hilbert Curve\\dots - """)) - self.wait() - - -class ReliesOnWonderfulProperty(Scene): - def construct(self): - self.add(TextMobject(""" - \\dots which relies on a certain property - of Pseudo-Hilbert-curves. - """)) - self.wait() - - -class WonderfulPropertyOfPseudoHilbertCurves(Scene): - def construct(self): - val = 0.3 - text = TextMobject([ - "PHC", "$_n", "(", "%3.1f"%val, ")$", - " has a ", "limit point ", "as $n \\to \\infty$" - ]) - func_parts = text.copy().split()[:5] - Mobject(*func_parts).center().to_edge(UP) - num_str, val_str = func_parts[1], func_parts[3] - curve = UnitInterval() - curve.sort_points(lambda p : p[0]) - dot = Dot().shift(curve.number_to_point(val)) - arrow = Arrow(val_str, dot, buff = 0.1) - curve.add_numbers(0, 1) - - self.play(ShowCreation(curve)) - self.play( - ShimmerIn(val_str), - ShowCreation(arrow), - ShowCreation(dot) - ) - self.wait() - self.play( - FadeOut(arrow), - *[ - FadeIn(func_parts[i]) - for i in (0, 1, 2, 4) - ] - ) - for num in range(2,9): - new_curve = HilbertCurve(order = num) - new_curve.scale(0.8) - new_dot = Dot(new_curve.points[int(val*new_curve.get_num_points())]) - new_num_str = TexMobject(str(num)).replace(num_str) - self.play( - Transform(curve, new_curve), - Transform(dot, new_dot), - Transform(num_str, new_num_str) - ) - self.wait() - - text.to_edge(UP) - text_parts = text.split() - for index in 1, -1: - text_parts[index].set_color() - starters = Mobject(*func_parts + [ - Point(mob.get_center(), stroke_width=1) - for mob in text_parts[5:] - ]) - self.play(Transform(starters, text)) - arrow = Arrow(text_parts[-2].get_bottom(), dot, buff = 0.1) - self.play(ShowCreation(arrow)) - self.wait() - -class FollowManyPoints(Scene): - def construct(self): - text = TextMobject([ - "PHC", "_n", "(", "x", ")$", - " has a limit point ", "as $n \\to \\infty$", - "\\\\ for all $x$" - ]) - parts = text.split() - parts[-1].next_to(Mobject(*parts[:-1]), DOWN) - parts[-1].set_color(BLUE) - parts[3].set_color(BLUE) - parts[1].set_color() - parts[-2].set_color() - text.to_edge(UP) - curve = UnitInterval() - curve.sort_points(lambda p : p[0]) - vals = np.arange(0.1, 1, 0.1) - dots = Mobject(*[ - Dot(curve.number_to_point(val)) - for val in vals - ]) - curve.add_numbers(0, 1) - starter_dots = dots.copy().ingest_submobjects() - starter_dots.shift(2*UP) - - self.add(curve, text) - self.wait() - self.play(DelayByOrder(ApplyMethod(starter_dots.shift, 2*DOWN))) - self.wait() - self.remove(starter_dots) - self.add(dots) - for num in range(1, 10): - new_curve = HilbertCurve(order = num) - new_curve.scale(0.8) - new_dots = Mobject(*[ - Dot(new_curve.points[int(val*new_curve.get_num_points())]) - for val in vals - ]) - self.play( - Transform(curve, new_curve), - Transform(dots, new_dots), - ) - # self.wait() - - -class FormalDefinitionOfHilbertCurve(Scene): - def construct(self): - val = 0.7 - text = TexMobject([ - "\\text{HC}(", "x", ")", - "=\\lim_{n \\to \\infty}\\text{PHC}_n(", "x", ")" - ]) - text.to_edge(UP) - x1 = text.split()[1] - x2 = text.split()[-2] - x2.set_color(BLUE) - explanation = TextMobject("Actual Hilbert curve function") - exp_arrow = Arrow(explanation, text.split()[0]) - curve = UnitInterval() - dot = Dot(curve.number_to_point(val)) - x_arrow = Arrow(x1.get_bottom(), dot, buff = 0) - curve.sort_points(lambda p : p[0]) - curve.add_numbers(0, 1) - - self.add(*text.split()[:3]) - self.play( - ShimmerIn(explanation), - ShowCreation(exp_arrow) - ) - self.wait() - self.remove(explanation, exp_arrow) - self.play(ShowCreation(curve)) - self.play( - ApplyMethod(x1.set_color, BLUE), - ShowCreation(x_arrow), - ShowCreation(dot) - ) - self.wait() - self.remove(x_arrow) - limit = Mobject(*text.split()[3:]).ingest_submobjects() - limit.stroke_width = 1 - self.play(ShimmerIn(limit)) - for num in range(1, 9): - new_curve = HilbertCurve(order = num) - new_curve.scale(0.8) - new_dot = Dot(new_curve.points[int(val*new_curve.get_num_points())]) - self.play( - Transform(curve, new_curve), - Transform(dot, new_dot), - ) - - -class CouldNotDefineForSnakeCurve(Scene): - def construct(self): - self.add(TextMobject(""" - You could not define a limit curve from - snake curves. - """)) - self.wait() - -class ThreeThingsToProve(Scene): - def construct(self): - definition = TexMobject([ - "\\text{HC}(", "x", ")", - "=\\lim_{n \\to \\infty}\\text{PHC}_n(", "x", ")" - ]) - definition.to_edge(UP) - definition.split()[1].set_color(BLUE) - definition.split()[-2].set_color(BLUE) - intro = TextMobject("Three things need to be proven") - prove_that = TextMobject("Prove that HC is $\\dots$") - prove_that.scale(0.7) - prove_that.to_edge(LEFT) - items = TextMobject([ - "\\begin{enumerate}", - "\\item Well-defined: ", - "Points on Pseudo-Hilbert-curves really do converge", - "\\item A Curve: ", - "HC is continuous", - "\\item Space-filling: ", - "Each point in the unit square is an output of HC", - "\\end{enumerate}", - ]).split() - items[1].set_color(GREEN) - items[3].set_color(YELLOW_C) - items[5].set_color(MAROON) - Mobject(*items).to_edge(RIGHT) - - self.add(definition) - self.play(ShimmerIn(intro)) - self.wait() - self.play(Transform(intro, prove_that)) - for item in items[1:-1]: - self.play(ShimmerIn(item)) - self.wait() - - - -class TilingSpace(Scene): - def construct(self): - coords_set = [ORIGIN] - for n in range(int(FRAME_WIDTH)): - for vect in UP, RIGHT: - for k in range(n): - new_coords = coords_set[-1]+((-1)**n)*vect - coords_set.append(new_coords) - square = Square(side_length = 1, color = WHITE) - squares = Mobject(*[ - square.copy().shift(coords) - for coords in coords_set - ]).ingest_submobjects() - self.play( - DelayByOrder(FadeIn(squares)), - run_time = 3 - ) - curve = HilbertCurve(order = 6).scale(1./6) - all_curves = Mobject(*[ - curve.copy().shift(coords) - for coords in coords_set - ]).ingest_submobjects() - all_curves.thin_out(10) - self.play(ShowCreation( - all_curves, - rate_func=linear, - run_time = 15 - )) - - -class ColorIntervals(Scene): - def construct(self): - number_line = NumberLine( - numerical_radius = 5, - number_at_center = 5, - leftmost_tick = 0, - density = 2*DEFAULT_POINT_DENSITY_1D - ) - number_line.shift(2*RIGHT) - number_line.add_numbers() - number_line.scale(2) - brace = Brace(Mobject( - *number_line.submobjects[:2] - )) - - self.add(number_line) - for n in range(0, 10, 2): - if n == 0: - brace_anim = GrowFromCenter(brace) - else: - brace_anim = ApplyMethod(brace.shift, 2*RIGHT) - self.play( - ApplyMethod( - number_line.set_color, - RED, - lambda p : p[0] > n-6.2 and p[0] < n-4 and p[1] > -0.4 - ), - brace_anim - ) - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/hilbert/section3.py b/from_3b1b/old/hilbert/section3.py deleted file mode 100644 index 7c754c61..00000000 --- a/from_3b1b/old/hilbert/section3.py +++ /dev/null @@ -1,283 +0,0 @@ -from manimlib.imports import * -import displayer as disp - -from hilbert.curves import \ - TransformOverIncreasingOrders, FlowSnake, HilbertCurve, \ - SnakeCurve, Sierpinski -from hilbert.section1 import get_mathy_and_bubble - - - - -class SectionThree(Scene): - def construct(self): - self.add(TextMobject("A few words on the usefulness of infinite math")) - self.wait() - - -class InfiniteResultsFiniteWorld(Scene): - def construct(self): - left_words = TextMobject("Infinite result") - right_words = TextMobject("Finite world") - for words in left_words, right_words: - words.scale(0.8) - left_formula = TexMobject( - "\\sum_{n = 0}^{\\infty} 2^n = -1" - ) - right_formula = TexMobject("111\\cdots111") - for formula in left_formula, right_formula: - formula.add( - Brace(formula, UP), - ) - formula.ingest_submobjects() - right_overwords = TextMobject( - "\\substack{\ - \\text{How computers} \\\\ \ - \\text{represent $-1$}\ - }" - ).scale(1.5) - - left_mobs = [left_words, left_formula] - right_mobs = [right_words, right_formula] - for mob in left_mobs: - mob.to_edge(RIGHT, buff = 1) - mob.shift(FRAME_X_RADIUS*LEFT) - for mob in right_mobs: - mob.to_edge(LEFT, buff = 1) - mob.shift(FRAME_X_RADIUS*RIGHT) - arrow = Arrow(left_words, right_words) - right_overwords.next_to(right_formula, UP) - - self.play(ShimmerIn(left_words)) - self.play(ShowCreation(arrow)) - self.play(ShimmerIn(right_words)) - self.wait() - self.play( - ShimmerIn(left_formula), - ApplyMethod(left_words.next_to, left_formula, UP) - ) - self.wait() - self.play( - ShimmerIn(right_formula), - Transform(right_words, right_overwords) - ) - self.wait() - self.finite_analog( - Mobject(left_formula, left_words), - arrow, - Mobject(right_formula, right_words) - ) - - - def finite_analog(self, left_mob, arrow, right_mob): - self.clear() - self.add(left_mob, arrow, right_mob) - ex = TextMobject("\\times") - ex.set_color(RED) - # ex.shift(arrow.get_center()) - middle = TexMobject( - "\\sum_{n=0}^N 2^n \\equiv -1 \\mod 2^{N+1}" - ) - finite_analog = TextMobject("Finite analog") - finite_analog.scale(0.8) - brace = Brace(middle, UP) - finite_analog.next_to(brace, UP) - new_left = left_mob.copy().to_edge(LEFT) - new_right = right_mob.copy().to_edge(RIGHT) - left_arrow, right_arrow = [ - Arrow( - mob1.get_right()[0]*RIGHT, - mob2.get_left()[0]*RIGHT, - buff = 0 - ) - for mob1, mob2 in [ - (new_left, middle), - (middle, new_right) - ] - ] - for mob in ex, middle: - mob.sort_points(get_norm) - - self.play(GrowFromCenter(ex)) - self.wait() - self.play( - Transform(left_mob, new_left), - Transform(arrow.copy(), left_arrow), - DelayByOrder(Transform(ex, middle)), - Transform(arrow, right_arrow), - Transform(right_mob, new_right) - ) - self.play( - GrowFromCenter(brace), - ShimmerIn(finite_analog) - ) - self.wait() - self.equivalence( - left_mob, - left_arrow, - Mobject(middle, brace, finite_analog) - ) - - def equivalence(self, left_mob, arrow, right_mob): - self.clear() - self.add(left_mob, arrow, right_mob) - words = TextMobject("is equivalent to") - words.shift(0.25*LEFT) - words.set_color(BLUE) - new_left = left_mob.copy().shift(RIGHT) - new_right = right_mob.copy() - new_right.shift( - (words.get_right()[0]-\ - right_mob.get_left()[0]+\ - 0.5 - )*RIGHT - ) - for mob in arrow, words: - mob.sort_points(get_norm) - - self.play( - ApplyMethod(left_mob.shift, RIGHT), - Transform(arrow, words), - ApplyMethod(right_mob.to_edge, RIGHT) - ) - self.wait() - - -class HilbertCurvesStayStable(Scene): - def construct(self): - scale_factor = 0.9 - grid = Grid(4, 4, stroke_width = 1) - curve = HilbertCurve(order = 2) - for mob in grid, curve: - mob.scale(scale_factor) - words = TextMobject(""" - Sequence of curves is stable - $\\leftrightarrow$ existence of limit curve - """, size = "\\normal") - words.scale(1.25) - words.to_edge(UP) - - self.add(curve, grid) - self.wait() - for n in range(3, 7): - if n == 5: - self.play(ShimmerIn(words)) - new_grid = Grid(2**n, 2**n, stroke_width = 1) - new_curve = HilbertCurve(order = n) - for mob in new_grid, new_curve: - mob.scale(scale_factor) - self.play( - ShowCreation(new_grid), - Animation(curve) - ) - self.remove(grid) - grid = new_grid - self.play(Transform(curve, new_curve)) - self.wait() - - - -class InfiniteObjectsEncapsulateFiniteObjects(Scene): - def get_triangles(self): - triangle = Polygon( - LEFT/np.sqrt(3), - UP, - RIGHT/np.sqrt(3), - color = GREEN - ) - triangles = Mobject( - triangle.copy().scale(0.5).shift(LEFT), - triangle, - triangle.copy().scale(0.3).shift(0.5*UP+RIGHT) - ) - triangles.center() - return triangles - - def construct(self): - words =[ - TextMobject(text, size = "\\large") - for text in [ - "Truths about infinite objects", - " encapsulate ", - "facts about finite objects" - ] - ] - - words[0].set_color(RED) - words[1].next_to(words[0]) - words[2].set_color(GREEN).next_to(words[1]) - Mobject(*words).center().to_edge(UP) - infinite_objects = [ - TexMobject( - "\\sum_{n=0}^\\infty", - size = "\\normal" - ).set_color(RED_E), - Sierpinski(order = 8).scale(0.3), - TextMobject( - "$\\exists$ something infinite $\\dots$" - ).set_color(RED_B) - ] - finite_objects = [ - TexMobject( - "\\sum_{n=0}^N", - size = "\\normal" - ).set_color(GREEN_E), - self.get_triangles(), - TextMobject( - "$\\forall$ finite somethings $\\dots$" - ).set_color(GREEN_B) - ] - for infinite, finite, n in zip(infinite_objects, finite_objects, it.count(1, 2)): - infinite.next_to(words[0], DOWN, buff = n) - finite.next_to(words[2], DOWN, buff = n) - - self.play(ShimmerIn(words[0])) - self.wait() - self.play(ShimmerIn(infinite_objects[0])) - self.play(ShowCreation(infinite_objects[1])) - self.play(ShimmerIn(infinite_objects[2])) - self.wait() - self.play(ShimmerIn(words[1]), ShimmerIn(words[2])) - self.play(ShimmerIn(finite_objects[0])) - self.play(ShowCreation(finite_objects[1])) - self.play(ShimmerIn(finite_objects[2])) - self.wait() - - -class StatementRemovedFromReality(Scene): - def construct(self): - mathy, bubble = get_mathy_and_bubble() - bubble.stretch_to_fit_width(4) - mathy.to_corner(DOWN+LEFT) - bubble.pin_to(mathy) - bubble.shift(LEFT) - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - morty_bubble = SpeechBubble() - morty_bubble.stretch_to_fit_width(4) - morty_bubble.pin_to(morty) - bubble.write(""" - Did you know a curve \\\\ - can fill all space? - """) - morty_bubble.write("Who cares?") - - self.add(mathy, morty) - for bub, buddy in [(bubble, mathy), (morty_bubble, morty)]: - self.play(Transform( - Point(bub.get_tip()), - bub - )) - self.play(ShimmerIn(bub.content)) - self.play(ApplyMethod( - buddy.blink, - rate_func = squish_rate_func(there_and_back) - )) - - - - - - - - diff --git a/from_3b1b/old/hyperdarts.py b/from_3b1b/old/hyperdarts.py deleted file mode 100644 index 23e76e7f..00000000 --- a/from_3b1b/old/hyperdarts.py +++ /dev/null @@ -1,2826 +0,0 @@ -from manimlib.imports import * - - -OUTPUT_DIRECTORY = "hyperdarts" -BROWN_PAPER = "#958166" - - -class HyperdartScene(MovingCameraScene): - CONFIG = { - "square_width": 6, - "square_style": { - "stroke_width": 2, - "fill_color": BLUE, - "fill_opacity": 0.5, - }, - "circle_style": { - "fill_color": RED_E, - "fill_opacity": 1, - "stroke_width": 0, - }, - "circle_center_dot_radius": 0.025, - "default_line_style": { - "stroke_width": 2, - "stroke_color": WHITE, - }, - "default_dot_config": { - "fill_color": WHITE, - "background_stroke_width": 1, - "background_stroke_color": BLACK, - "radius": 0.5 * DEFAULT_DOT_RADIUS, - }, - "dart_sound": "dart_low", - "default_bullseye_shadow_opacity": 0.35, - } - - def setup(self): - MovingCameraScene.setup(self) - self.square = self.get_square() - self.circle = self.get_circle() - self.circle_center_dot = self.get_circle_center_dot() - - self.add(self.square) - self.add(self.circle) - self.add(self.circle_center_dot) - - def get_square(self): - return Square( - side_length=self.square_width, - **self.square_style - ) - - def get_circle(self, square=None): - square = square or self.square - circle = Circle(**self.circle_style) - circle.replace(square) - return circle - - def get_circle_center_dot(self, circle=None): - circle = circle or self.circle - return Dot( - circle.get_center(), - radius=self.circle_center_dot_radius, - fill_color=BLACK, - ) - - def get_number_plane(self): - square = self.square - unit_size = square.get_width() / 2 - plane = NumberPlane( - axis_config={ - "unit_size": unit_size, - } - ) - plane.add_coordinates() - plane.shift(square.get_center() - plane.c2p(0, 0)) - return plane - - def get_random_points(self, n): - square = self.square - points = np.random.uniform(-1, 1, 3 * n).reshape((n, 3)) - - points[:, 0] *= square.get_width() / 2 - points[:, 1] *= square.get_height() / 2 - points[:, 2] = 0 - points += square.get_center() - return points - - def get_random_point(self): - return self.get_random_points(1)[0] - - def get_dot(self, point): - return Dot(point, **self.default_dot_config) - - # Hit transform rules - def is_inside(self, point, circle=None): - circle = circle or self.circle - return get_norm(point - circle.get_center()) <= circle.get_width() / 2 - - def get_new_radius(self, point, circle=None): - circle = circle or self.circle - center = circle.get_center() - radius = circle.get_width() / 2 - p_dist = get_norm(point - center) - return np.sqrt(radius**2 - p_dist**2) - - def get_hit_distance_line(self, point, circle=None): - circle = circle or self.circle - - line = Line( - circle.get_center(), point, - **self.default_line_style - ) - return line - - def get_chord(self, point, circle=None): - circle = circle or self.circle - - center = circle.get_center() - p_angle = angle_of_vector(point - center) - chord = Line(DOWN, UP) - new_radius = self.get_new_radius(point, circle) - chord.scale(new_radius) - chord.rotate(p_angle) - chord.move_to(point) - chord.set_style(**self.default_line_style) - return chord - - def get_radii_to_chord(self, chord, circle=None): - circle = circle or self.circle - - center = circle.get_center() - radii = VGroup(*[ - DashedLine(center, point) - for point in chord.get_start_and_end() - ]) - radii.set_style(**self.default_line_style) - return radii - - def get_all_hit_lines(self, point, circle=None): - h_line = self.get_hit_distance_line(point, circle) - chord = self.get_chord(point, circle) - # radii = self.get_radii_to_chord(chord, circle) - - elbow = Elbow(width=0.15) - elbow.set_stroke(WHITE, 2) - elbow.rotate(h_line.get_angle() - PI, about_point=ORIGIN) - elbow.shift(point) - - return VGroup(h_line, chord, elbow) - - def get_dart(self, length=1.5): - dart = SVGMobject(file_name="dart") - dart.rotate(135 * DEGREES) - dart.set_width(length) - dart.rotate(45 * DEGREES, UP) - dart.rotate(-10 * DEGREES) - - dart.set_fill(GREY) - dart.set_sheen(2, UL) - dart.set_stroke(BLACK, 0.5, background=True) - dart.set_stroke(width=0) - return dart - - # New circle - def get_new_circle_from_point(self, point, circle=None): - return self.get_new_circle( - self.get_new_radius(point, circle), - circle, - ) - - def get_new_circle_from_chord(self, chord, circle=None): - return self.get_new_circle( - chord.get_length() / 2, - circle, - ) - - def get_new_circle(self, new_radius, circle=None): - circle = circle or self.circle - new_circle = self.get_circle() - new_circle.set_width(2 * new_radius) - new_circle.move_to(circle) - return new_circle - - # Sound - def add_dart_sound(self, time_offset=0, gain=-20, **kwargs): - self.add_sound( - self.dart_sound, - time_offset=time_offset, - gain=-20, - **kwargs, - ) - - # Animations - def show_full_hit_process(self, point, pace="slow", with_dart=True): - assert(pace in ["slow", "fast"]) - - to_fade = VGroup() - - if with_dart: - dart, dot = self.show_hit_with_dart(point) - to_fade.add(dart, dot) - else: - dot = self.show_hit(point) - to_fade.add(dot) - - if pace == "slow": - self.wait(0.5) - - # TODO, automatically act based on hit or miss? - - lines = self.show_geometry(point, pace) - chord_and_shadow = self.show_circle_shrink(lines[1], pace=pace) - - to_fade.add_to_back(chord_and_shadow, lines) - - self.play( - FadeOut(to_fade), - run_time=(1 if pace == "slow" else 0.5) - ) - - def show_hits_with_darts(self, points, run_time=0.5, added_anims=None): - if added_anims is None: - added_anims = [] - - darts = VGroup(*[ - self.get_dart().move_to(point, DR) - for point in points - ]) - dots = VGroup(*[ - self.get_dot(point) - for point in points - ]) - - for dart in darts: - dart.save_state() - dart.set_x(-(FRAME_WIDTH + dart.get_width()) / 2) - dart.rotate(20 * DEGREES) - - n_points = len(points) - self.play( - ShowIncreasingSubsets( - dots, - rate_func=squish_rate_func(linear, 0.5, 1), - ), - LaggedStart(*[ - Restore( - dart, - path_arc=-20 * DEGREES, - rate_func=linear, - run_time=run_time, - ) - for dart in darts - ], lag_ratio=(1 / n_points)), - *added_anims, - run_time=run_time - ) - for n in range(n_points): - self.add_dart_sound( - time_offset=(-n / (2 * n_points)) - ) - - return darts, dots - - def show_hit_with_dart(self, point, run_time=0.25, **kwargs): - darts, dots = self.show_hits_with_darts([point], run_time, **kwargs) - return darts[0], dots[0] - - def show_hit(self, point, pace="slow", added_anims=None): - assert(pace in ["slow", "fast"]) - if added_anims is None: - added_anims = [] - - dot = self.get_dot(point) - if pace == "slow": - self.play( - FadeInFromLarge(dot, rate_func=rush_into), - *added_anims, - run_time=0.5, - ) - elif pace == "fast": - self.add(dot) - # self.add_dart_sound() - return dot - - def show_geometry(self, point, pace="slow"): - assert(pace in ["slow", "fast"]) - - lines = self.get_all_hit_lines(point, self.circle) - h_line, chord, elbow = lines - - # Note, note animating radii anymore...does that mess anything up? - if pace == "slow": - self.play( - ShowCreation(h_line), - GrowFromCenter(chord), - ) - self.play(ShowCreation(elbow)) - elif pace == "fast": - self.play( - ShowCreation(h_line), - GrowFromCenter(chord), - ShowCreation(elbow), - run_time=0.5 - ) - # return VGroup(h_line, chord) - return lines - - def show_circle_shrink(self, chord, pace="slow", shadow_opacity=None): - circle = self.circle - chord_copy = chord.copy() - new_circle = self.get_new_circle_from_chord(chord) - to_fade = VGroup(chord_copy) - - if shadow_opacity is None: - shadow_opacity = self.default_bullseye_shadow_opacity - if shadow_opacity > 0: - shadow = circle.copy() - shadow.set_opacity(shadow_opacity) - to_fade.add_to_back(shadow) - if circle in self.mobjects: - index = self.mobjects.index(circle) - self.mobjects.insert(index, shadow) - else: - self.add(shadow, self.circle_center_dot) - - outline = VGroup(*[ - VMobject().pointwise_become_partial(new_circle, a, b) - for (a, b) in [(0, 0.5), (0.5, 1)] - ]) - outline.rotate(chord.get_angle()) - outline.set_fill(opacity=0) - outline.set_stroke(YELLOW, 2) - - assert(pace in ["slow", "fast"]) - - if pace == "slow": - self.play( - chord_copy.move_to, circle.get_center(), - circle.set_opacity, 0.5, - ) - self.play( - Rotating( - chord_copy, - radians=PI, - ), - ShowCreation( - outline, - lag_ratio=0 - ), - run_time=1, - rate_func=smooth, - ) - self.play( - Transform(circle, new_circle), - FadeOut(outline), - ) - elif pace == "fast": - outline = new_circle.copy() - outline.set_fill(opacity=0) - outline.set_stroke(YELLOW, 2) - outline.move_to(chord) - outline.generate_target() - outline.target.move_to(circle) - self.play( - chord_copy.move_to, circle, - Transform(circle, new_circle), - # MoveToTarget( - # outline, - # remover=True - # ) - ) - # circle.become(new_circle) - # circle.become(new_circle) - # self.remove(new_circle) - return to_fade - - def show_miss(self, point, with_dart=True): - square = self.square - miss = TextMobject("Miss!") - miss.next_to(point, UP) - to_fade = VGroup(miss) - - if with_dart: - dart, dot = self.show_hit_with_dart(point) - to_fade.add(dart, dot) - else: - dot = self.show_hit(point) - to_fade.add(dot) - self.play( - ApplyMethod( - square.set_color, YELLOW, - rate_func=lambda t: (1 - t), - ), - GrowFromCenter(miss), - run_time=0.25 - ) - return to_fade - - def show_game_over(self): - game_over = TextMobject("GAME OVER") - game_over.set_width(FRAME_WIDTH - 1) - rect = FullScreenFadeRectangle(opacity=0.25) - - self.play( - FadeIn(rect), - FadeInFromLarge(game_over), - ) - return VGroup(rect, game_over) - - -class Dartboard(VGroup): - CONFIG = { - "radius": 3, - "n_sectors": 20, - } - - def __init__(self, **kwargs): - super().__init__(**kwargs) - n_sectors = self.n_sectors - angle = TAU / n_sectors - - segments = VGroup(*[ - VGroup(*[ - AnnularSector( - inner_radius=in_r, - outer_radius=out_r, - start_angle=n * angle, - angle=angle, - color=color, - ) - for n, color in zip( - range(n_sectors), - it.cycle(colors) - ) - ]) - for colors, in_r, out_r in [ - ([LIGHT_GREY, DARKER_GREY], 0, 1), - ([GREEN_E, RED_E], 0.5, 0.55), - ([GREEN_E, RED_E], 0.95, 1), - ] - ]) - segments.rotate(-angle / 2) - bullseyes = VGroup(*[ - Circle(radius=r) - for r in [0.07, 0.035] - ]) - bullseyes.set_fill(opacity=1) - bullseyes.set_stroke(width=0) - bullseyes[0].set_color(GREEN_E) - bullseyes[1].set_color(RED_E) - - self.bullseye = bullseyes[1] - self.add(*segments, *bullseyes) - self.scale(self.radius) - - -# Scenes to overlay on Numerphile - -class TableOfContents(Scene): - def construct(self): - rect = FullScreenFadeRectangle(opacity=0.75) - self.add(rect) - - parts = VGroup( - TextMobject("The game"), - TextMobject("The puzzle"), - TextMobject("The micropuzzles"), - TextMobject("The answer"), - ) - - parts.scale(1.5) - parts.arrange(DOWN, buff=LARGE_BUFF, aligned_edge=LEFT) - parts.to_edge(LEFT, buff=2) - - parts.set_opacity(0.5) - self.add(parts) - - for part in parts: - dot = Dot() - dot.next_to(part, LEFT, SMALL_BUFF) - dot.match_style(part) - self.add(dot) - - last_part = VMobject() - last_part.save_state() - - for part in parts: - part.save_state() - self.play( - part.scale, 1.5, {"about_edge": LEFT}, - part.set_opacity, 1, - Restore(last_part) - ) - self.wait() - last_part = part - - -class ShowGiantBullseye(HyperdartScene): - def construct(self): - square = self.square - circle = self.circle - - self.remove(square, circle) - board = Dartboard() - board.replace(circle) - bullseye = board.bullseye - bullseye_border = bullseye.copy() - bullseye_border.set_fill(opacity=0) - bullseye_border.set_stroke(YELLOW, 3) - - self.add(board) - - # Label - label = TextMobject("``", "Bullseye", "''") - label.scale(1.5) - label.next_to(square, LEFT, aligned_edge=UP) - label.set_color(RED) - arrow = Arrow( - label.get_bottom(), - bullseye.get_corner(DR) - ) - - self.play( - FadeInFromDown(label[1]), - ShowCreation(arrow), - ) - self.play( - bullseye.match_width, board, - ApplyMethod( - arrow.scale, 0.4, - {"about_point": arrow.get_start()} - ), - run_time=2, - ) - self.play(Write(label[::2])) - self.wait() - - -class ShowExampleHit(HyperdartScene): - def construct(self): - square = self.square - circle = self.circle - circle.set_fill(BROWN_PAPER, opacity=0.95) - old_board = VGroup(square, circle) - self.remove(square) - - board = Dartboard() - board.replace(old_board) - self.add(board, circle) - - # Show hit - point = 0.75 * UP - dart, dot = self.show_hit_with_dart(point) - - # Draw lines (with labels) - lines = self.get_all_hit_lines(point) - h_line, chord, elbow = lines - h_label = TexMobject("h") - h_label.next_to(h_line, LEFT, SMALL_BUFF) - - chord_word = TextMobject("Chord") - chord_word.next_to(chord.get_center(), UR, SMALL_BUFF) - - self.add(h_line, dot) - self.play(ShowCreation(h_line)) - self.play(Write(h_label)) - self.wait() - self.play( - ShowCreation(chord), - ShowCreation(elbow), - Write(chord_word, run_time=1) - ) - self.wait() - - # Show shrinkage - chord_copy = chord.copy() - chord_copy.move_to(ORIGIN) - new_circle = circle.copy() - new_circle.set_fill(RED, 1) - new_circle.match_width(chord_copy) - new_circle.move_to(ORIGIN) - - new_diam_word = TextMobject("New diameter") - new_diam_word.next_to(chord_copy, DOWN, SMALL_BUFF) - - outline = VGroup( - Arc(start_angle=0, angle=PI), - Arc(start_angle=PI, angle=PI), - ) - outline.set_stroke(YELLOW, 3) - outline.set_fill(opacity=0) - outline.replace(new_circle) - - self.play( - circle.set_color, DARK_GREY, - TransformFromCopy(chord, chord_copy), - FadeInFrom(new_diam_word, UP) - ) - self.play( - Rotate(chord_copy, PI), - ShowCreation(outline, lag_ratio=0), - ) - self.play() - - # Show variable hit_point - self.remove(lines) - point_tracker = VectorizedPoint(point) - self.remove(lines, *lines) - lines = always_redraw( - lambda: self.get_all_hit_lines(point_tracker.get_location()) - ) - dot.add_updater(lambda m: m.move_to(point_tracker)) - dart.add_updater(lambda m: m.move_to(point_tracker, DR)) - chord_copy.add_updater( - lambda m: m.match_width(lines[1]).move_to(ORIGIN) - ) - new_circle.add_updater(lambda m: m.match_width(chord_copy).move_to(ORIGIN)) - h_label.add_updater(lambda m: m.next_to(lines[0], LEFT, SMALL_BUFF)) - - chord_word.add_updater(lambda m: m.next_to(lines[1].get_center(), UR, SMALL_BUFF)) - ndw_width = new_diam_word.get_width() - new_diam_word.add_updater( - lambda m: m.set_width( - min(ndw_width, chord_copy.get_width()) - ).next_to(chord_copy, DOWN, SMALL_BUFF) - ) - - self.add(new_circle, chord_copy, lines, h_label, dart, dot, chord_word, new_diam_word) - self.play( - FadeOut(outline), - FadeIn(new_circle) - ) - self.wait() - - self.play( - point_tracker.shift, 2.1 * UP, - run_time=9, - rate_func=there_and_back_with_pause, - ) - - -class QuicklyAnimatedShrinking(HyperdartScene): - def construct(self): - # square = self.square - # circle = self.circle - - for x in range(5): - point = self.get_random_point() - while not self.is_inside(point): - point = self.get_random_point() - self.show_full_hit_process(point, pace="fast") - # self.show_game_over() - - -class SimulateRealGame(HyperdartScene): - CONFIG = { - "circle_style": { - # "fill_color": BROWN_PAPER, - } - } - - def construct(self): - board = Dartboard() - board.set_opacity(0.5) - self.remove(self.square) - self.square.set_opacity(0) - self.add(board, self.circle) - - points = [ - 0.5 * UP, - 2.0 * UP, - 1.9 * LEFT + 0.4 * DOWN, - ] - - for point in points: - self.show_full_hit_process(point) - self.show_miss(1.8 * DL) - self.show_game_over() - - -class GameOver(HyperdartScene): - def construct(self): - self.clear() - self.show_game_over() - - -class SquareAroundTheDartBoard(HyperdartScene): - def construct(self): - square = self.square - circle = self.circle - VGroup(square, circle).to_edge(DOWN, MED_SMALL_BUFF) - self.clear() - board = Dartboard() - board.replace(square) - - title = TextMobject("Square around the dart board") - title.scale(1.5) - title.next_to(square, UP, MED_LARGE_BUFF) - - self.add(board) - self.play(FadeInFromDown(title)) - self.add(square, board) - self.play(DrawBorderThenFill(square, run_time=2)) - self.wait() - - -class ContrastDistributions(HyperdartScene): - def construct(self): - square = self.square - circle = self.circle - board = Dartboard() - board.replace(circle) - - group = VGroup(square, circle, board) - group.to_edge(LEFT) - group.scale(0.8, about_edge=DOWN) - - group_copy = group.copy() - square_copy, circle_copy, board_copy = group_copy - group_copy.set_x(-group.get_center()[0]) - - v_line = DashedLine(FRAME_HEIGHT * UP / 2, FRAME_HEIGHT * DOWN / 2) - - left_label = TextMobject("Our distribution\\\\(uniform in the square)") - left_label.match_x(group) - left_label.to_edge(UP) - right_label = TextMobject("More realistic distribution") - right_label.match_x(group_copy) - right_label.to_edge(UP) - - n_points = 2000 - left_points = self.get_random_points(n_points) - right_points = np.random.multivariate_normal( - mean=board_copy.get_center(), - cov=0.6 * np.identity(3), - size=n_points - ) - - left_dots, right_dots = [ - VGroup(*[ - Dot(p, radius=0.02) for p in points - ]) - for points in [left_points, right_points] - ] - - left_rect = FullScreenFadeRectangle(opacity=0.75) - left_rect.stretch(0.49, 0, about_edge=LEFT) - right_rect = left_rect.copy() - right_rect.to_edge(RIGHT, buff=0) - - self.add(group, board_copy) - self.add(left_label, right_label) - self.add(v_line) - self.add(left_rect) - - self.play( - LaggedStartMap(FadeInFromLarge, right_dots), - run_time=5 - ) - self.wait() - self.play( - FadeOut(left_rect), - FadeIn(right_rect), - ) - self.play( - LaggedStartMap(FadeInFromLarge, left_dots), - run_time=5 - ) - self.wait() - - -class ChooseXThenYUniformly(Scene): - def construct(self): - # Setup - unit_size = 3 - axes = Axes( - x_min=-1.25, - x_max=1.25, - y_min=-1.25, - y_max=1.25, - axis_config={ - "tick_frequency": 0.25, - "unit_size": unit_size, - }, - ) - numbers = [-1, -0.5, 0.5, 1] - num_config = { - "num_decimal_places": 1, - "background_stroke_width": 3, - } - axes.x_axis.add_numbers( - *numbers, - number_config=num_config, - ) - axes.y_axis.add_numbers( - *numbers, - number_config=num_config, - direction=LEFT, - ) - - circle = Circle(radius=unit_size) - circle.set_stroke(WHITE, 0) - circle.set_fill(RED, 0.7) - - square = Square() - square.replace(circle) - square.set_stroke(LIGHT_GREY, 1) - square = DashedVMobject(square, num_dashes=101) - - self.add(square, circle) - self.add(axes) - - # x and y stuff - x_tracker = ValueTracker(-1) - y_tracker = ValueTracker(-1) - - get_x = x_tracker.get_value - get_y = y_tracker.get_value - - x_tip = ArrowTip(start_angle=PI / 2, color=BLUE) - y_tip = ArrowTip(start_angle=0, color=YELLOW) - for tip in [x_tip, y_tip]: - tip.scale(0.5) - x_tip.add_updater(lambda m: m.move_to(axes.c2p(get_x(), 0), UP)) - y_tip.add_updater(lambda m: m.move_to(axes.c2p(0, get_y()), RIGHT)) - - x_eq = VGroup(TexMobject("x = "), DecimalNumber(0)) - x_eq.arrange(RIGHT, SMALL_BUFF) - x_eq[1].match_y(x_eq[0][0][1]) - x_eq[1].add_updater(lambda m: m.set_value(get_x())) - x_eq.match_color(x_tip) - - y_eq = VGroup(TexMobject("y = "), DecimalNumber(0)) - y_eq.arrange(RIGHT, SMALL_BUFF) - y_eq[1].match_y(y_eq[0][0][1]) - y_eq[1].add_updater(lambda m: m.set_value(get_y())) - y_eq.match_color(y_tip) - - eqs = VGroup(x_eq, y_eq) - eqs.arrange(DOWN, buff=MED_LARGE_BUFF) - eqs.to_edge(UR) - - self.add(x_tip) - self.add(x_eq) - - # Choose x - self.play( - x_tracker.set_value, 1, - run_time=2, - ) - self.play( - x_tracker.set_value, np.random.random(), - run_time=1, - ) - - # Choose y - self.play( - FadeIn(y_tip), - FadeIn(y_eq), - ) - self.play( - y_tracker.set_value, 1, - run_time=2, - ) - self.play( - y_tracker.set_value, np.random.random(), - run_time=1, - ) - - point = axes.c2p(get_x(), get_y()) - dot = Dot(point) - x_line = DashedLine(axes.c2p(0, get_y()), point) - y_line = DashedLine(axes.c2p(get_x(), 0), point) - lines = VGroup(x_line, y_line) - lines.set_stroke(WHITE, 2) - - self.play(*map(ShowCreation, lines)) - self.play(DrawBorderThenFill(dot)) - self.wait() - - points = [ - axes.c2p(*np.random.uniform(-1, 1, size=2)) - for n in range(2000) - ] - dots = VGroup(*[ - Dot(point, radius=0.02) - for point in points - ]) - self.play( - LaggedStartMap(FadeInFromLarge, dots), - run_time=3, - ) - self.wait() - - -class ShowDistributionOfScores(Scene): - CONFIG = { - "axes_config": { - "x_min": -1, - "x_max": 10, - "x_axis_config": { - "unit_size": 1.2, - "tick_frequency": 1, - }, - "y_min": 0, - "y_max": 100, - "y_axis_config": { - "unit_size": 0.065, - "tick_frequency": 10, - "include_tip": False, - }, - }, - "random_seed": 1, - } - - def construct(self): - # Add axes - axes = self.get_axes() - self.add(axes) - - # setup scores - n_scores = 10000 - scores = np.array([self.get_random_score() for x in range(n_scores)]) - index_tracker = ValueTracker(n_scores) - - def get_index(): - value = np.clip(index_tracker.get_value(), 0, n_scores - 1) - return int(value) - - # Setup histogram - bars = self.get_histogram_bars(axes) - bars.add_updater( - lambda b: self.set_histogram_bars( - b, scores[:get_index()], axes - ) - ) - self.add(bars) - - # Add score label - score_label = VGroup( - TextMobject("Last score: "), - Integer(1) - ) - score_label.scale(1.5) - score_label.arrange(RIGHT) - score_label[1].align_to(score_label[0][0][-1], DOWN) - - score_label[1].add_updater( - lambda m: m.set_value(scores[get_index() - 1]) - ) - score_label[1].add_updater( - lambda m: m.set_fill(bars[scores[get_index() - 1]].get_fill_color()) - ) - - n_trials_label = VGroup( - TextMobject("\\# Games: "), - Integer(0), - ) - n_trials_label.scale(1.5) - n_trials_label.arrange(RIGHT, aligned_edge=UP) - n_trials_label[1].add_updater( - lambda m: m.set_value(get_index()) - ) - - n_trials_label.to_corner(UR, buff=LARGE_BUFF) - score_label.next_to( - n_trials_label, DOWN, - buff=LARGE_BUFF, - aligned_edge=LEFT, - ) - - self.add(score_label) - self.add(n_trials_label) - - # Add curr_score_arrow - curr_score_arrow = Arrow(0.25 * UP, ORIGIN, buff=0) - curr_score_arrow.set_stroke(WHITE, 5) - curr_score_arrow.add_updater( - lambda m: m.next_to(bars[scores[get_index() - 1] - 1], UP, SMALL_BUFF) - ) - self.add(curr_score_arrow) - - # Add mean bar - mean_line = DashedLine(ORIGIN, 4 * UP) - mean_line.set_stroke(YELLOW, 2) - - def get_mean(): - return np.mean(scores[:get_index()]) - - mean_line.add_updater( - lambda m: m.move_to(axes.c2p(get_mean(), 0), DOWN) - ) - mean_label = VGroup( - TextMobject("Mean = "), - DecimalNumber(num_decimal_places=3), - ) - mean_label.arrange(RIGHT) - mean_label.match_color(mean_line) - mean_label.add_updater(lambda m: m.next_to(mean_line, UP, SMALL_BUFF)) - mean_label[1].add_updater(lambda m: m.set_value(get_mean())) - - # Show many runs - index_tracker.set_value(1) - for value in [10, 100, 1000, 10000]: - anims = [ - ApplyMethod( - index_tracker.set_value, value, - rate_func=linear, - run_time=5, - ), - ] - if value == 10: - anims.append( - FadeIn( - VGroup(mean_line, mean_label), - rate_func=squish_rate_func(smooth, 0.5, 1), - run_time=2, - ), - ) - self.play(*anims) - self.wait() - - # - def get_axes(self): - axes = Axes(**self.axes_config) - axes.to_corner(DL) - - axes.x_axis.add_numbers(*range(1, 12)) - axes.y_axis.add_numbers( - *range(20, 120, 20), - number_config={ - "unit": "\\%" - } - ) - x_label = TextMobject("Score") - x_label.next_to(axes.x_axis.get_right(), UR, buff=0.5) - x_label.shift_onto_screen() - axes.x_axis.add(x_label) - - y_label = TextMobject("Relative proportion") - y_label.next_to(axes.y_axis.get_top(), RIGHT, buff=0.75) - y_label.to_edge(UP, buff=MED_SMALL_BUFF) - axes.y_axis.add(y_label) - - return axes - - def get_histogram_bars(self, axes): - bars = VGroup() - for x in range(1, 10): - bar = Rectangle(width=axes.x_axis.unit_size) - bar.move_to(axes.c2p(x, 0), DOWN) - bar.x = x - bars.add(bar) - bars.set_fill(opacity=0.7) - bars.set_color_by_gradient(BLUE, YELLOW, RED) - bars.set_stroke(WHITE, 1) - return bars - - def get_relative_proportion_map(self, all_scores): - scores = set(all_scores) - n_scores = len(all_scores) - return dict([ - (s, np.sum(all_scores == s) / n_scores) - for s in set(scores) - ]) - - def set_histogram_bars(self, bars, scores, axes): - prop_map = self.get_relative_proportion_map(scores) - epsilon = 1e-6 - for bar in bars: - prop = prop_map.get(bar.x, epsilon) - bar.set_height( - prop * axes.y_axis.unit_size * 100, - stretch=True, - about_edge=DOWN, - ) - - def get_random_score(self): - score = 1 - radius = 1 - while True: - point = np.random.uniform(-1, 1, size=2) - hit_radius = get_norm(point) - if hit_radius > radius: - return score - else: - score += 1 - radius = np.sqrt(radius**2 - hit_radius**2) - - -class ExactBullseye(HyperdartScene): - def construct(self): - board = Dartboard() - board.replace(self.square) - - lines = VGroup(Line(DOWN, UP), Line(LEFT, RIGHT)) - lines.set_stroke(WHITE, 1) - lines.replace(self.square) - - self.add(board, lines) - dart, dot = self.show_hit_with_dart(0.0037 * DOWN) - self.play(FadeOut(dot)) - - frame = self.camera_frame - self.play(frame.scale, 0.02, run_time=5) - self.wait() - - -class ShowProbabilityForFirstShot(HyperdartScene): - def construct(self): - square = self.square - circle = self.circle - VGroup(square, circle).to_edge(LEFT) - - r_line = DashedLine(circle.get_center(), circle.get_right()) - r_label = TexMobject("r = 1") - r_label.next_to(r_line, DOWN, SMALL_BUFF) - self.add(r_line, r_label) - - points = self.get_random_points(3000) - dots = VGroup(*[Dot(point, radius=0.02) for point in points]) - dots.set_fill(WHITE, 0.5) - - p_label = TexMobject("P", "(S > 1)", "= ") - square_frac = VGroup( - circle.copy().set_height(0.5), - Line(LEFT, RIGHT).set_width(0.7), - square.copy().set_height(0.5).set_stroke(width=0) - ) - square_frac.arrange(DOWN, buff=SMALL_BUFF) - result = TexMobject("=", "{\\pi \\over 4}") - - equation = VGroup(p_label, square_frac, result) - equation.arrange(RIGHT) - equation.scale(1.4) - equation.to_edge(RIGHT, buff=MED_LARGE_BUFF) - - brace = Brace(p_label[1], UP, buff=SMALL_BUFF) - brace_label = brace.get_text("At least one\\\\``bullseye''") - - self.add(equation, brace, brace_label) - self.play( - LaggedStartMap(FadeInFromLarge, dots), - run_time=5, - ) - self.play( - ReplacementTransform( - circle.copy().set_fill(opacity=0).set_stroke(WHITE, 1), - square_frac[0] - ), - ) - self.play( - ReplacementTransform( - square.copy().set_fill(opacity=0), - square_frac[2] - ), - ) - self.wait(2) - - # Dar on the line - x = np.random.random() - y = np.sqrt(1 - x**2) - unit = circle.get_width() / 2 - point = circle.get_center() + unit * x * RIGHT + unit * y * UP - point += 0.004 * DOWN - - frame = self.camera_frame - dart, dot = self.show_hit_with_dart(point) - self.remove(dot) - self.play( - frame.scale, 0.05, - frame.move_to, point, - run_time=5, - ) - - -class SamplingFourRandomNumbers(Scene): - CONFIG = { - "n_terms": 4, - "title_tex": "P\\left(x_0{}^2 + y_0{}^2 + x_1{}^2 + y_1{}^2 < 1\\right) = \\, ???", - "nl_to_nl_buff": 0.75, - "to_floor_buff": 0.5, - "tip_scale_factor": 0.75, - "include_half_labels": True, - "include_title": True, - } - - def construct(self): - texs = ["x_0", "y_0", "x_1", "y_1", "x_2", "y_2"][:self.n_terms] - colors = [BLUE, YELLOW, BLUE_B, YELLOW_B, BLUE_A, YELLOW_A][:self.n_terms] - t2c = dict([(t, c) for t, c in zip(texs, colors)]) - - # Title - if self.include_title: - title = TexMobject( - self.title_tex, - tex_to_color_map=t2c - ) - title.scale(1.5) - title.to_edge(UP) - - h_line = DashedLine(title.get_left(), title.get_right()) - h_line.next_to(title, DOWN, MED_SMALL_BUFF) - - self.add(title, h_line) - - # Number lines - number_lines = VGroup(*[ - NumberLine( - x_min=-1, - x_max=1, - tick_frequency=0.25, - unit_size=3, - ) - for x in range(self.n_terms) - ]) - for line in number_lines: - line.add_numbers(-1, 0, 1) - if self.include_half_labels: - line.add_numbers( - -0.5, 0.5, - number_config={"num_decimal_places": 1}, - ) - number_lines.arrange(DOWN, buff=self.nl_to_nl_buff) - number_lines.to_edge(LEFT, buff=0.5) - number_lines.to_edge(DOWN, buff=self.to_floor_buff) - - self.add(number_lines) - - # Trackers - trackers = Group(*[ValueTracker(0) for x in range(self.n_terms)]) - tips = VGroup(*[ - ArrowTip( - start_angle=-PI / 2, - color=color - ).scale(self.tip_scale_factor) - for color in colors - ]) - labels = VGroup(*[ - TexMobject(tex) - for tex in texs - ]) - - for tip, tracker, line, label in zip(tips, trackers, number_lines, labels): - tip.line = line - tip.tracker = tracker - tip.add_updater(lambda t: t.move_to( - t.line.n2p(t.tracker.get_value()), DOWN - )) - - label.tip = tip - label.match_color(tip) - label.arrange(RIGHT, buff=MED_SMALL_BUFF) - label.add_updater(lambda l: l.next_to(l.tip, UP, SMALL_BUFF)) - # label.add_updater(lambda l: l[1].set_value(l.tip.tracker.get_value())) - - self.add(tips, labels) - - # Write bit sum - summands = VGroup(*[ - TexMobject("\\big(", "+0.00", "\\big)^2").set_color(color) - for color in colors - ]) - summands.arrange(DOWN) - summands.to_edge(RIGHT, buff=3) - for summand, tracker in zip(summands, trackers): - dec = DecimalNumber(include_sign=True) - dec.match_color(summand) - dec.tracker = tracker - dec.add_updater(lambda d: d.set_value(d.tracker.get_value())) - dec.move_to(summand[1]) - summand.submobjects[1] = dec - - h_line = Line(LEFT, RIGHT) - h_line.set_width(3) - h_line.next_to(summands, DOWN, aligned_edge=RIGHT) - plus = TexMobject("+") - plus.next_to(h_line.get_left(), UR) - h_line.add(plus) - - total = DecimalNumber() - total.scale(1.5) - total.next_to(h_line, DOWN) - total.match_x(summands) - total.add_updater(lambda d: d.set_value(np.sum([ - t.get_value()**2 for t in trackers - ]))) - - VGroup(summands, h_line, total).shift_onto_screen() - self.add(summands, h_line, total) - - # < or > 1 - lt, gt = signs = VGroup( - TexMobject("< 1 \\quad \\checkmark"), - TexMobject("\\ge 1 \\quad"), - ) - for sign in signs: - sign.scale(1.5) - sign.next_to(total, RIGHT, MED_LARGE_BUFF) - lt.set_color(GREEN) - gt.set_color(RED) - - def update_signs(signs): - i = int(total.get_value() > 1) - signs[1 - i].set_opacity(0) - signs[i].set_opacity(1) - - signs.add_updater(update_signs) - - self.add(signs) - - # Run simulation - for x in range(9): - trackers.generate_target() - for t in trackers.target: - t.set_value(np.random.uniform(-1, 1)) - - if x == 8: - for t in trackers.target: - t.set_value(np.random.uniform(-0.5, 0.5)) - - self.remove(signs) - self.play(MoveToTarget(trackers)) - self.add(signs) - self.wait() - - # Less than 0.5 - nl = number_lines[0] - line = Line(nl.n2p(-0.5), nl.n2p(0.5)) - rect = Rectangle(height=0.25) - rect.set_stroke(width=0) - rect.set_fill(GREEN, 0.5) - rect.match_width(line, stretch=True) - rects = VGroup(*[ - rect.copy().move_to(line.n2p(0)) - for line in number_lines - ]) - - self.play(LaggedStartMap(GrowFromCenter, rects)) - self.wait() - self.play(LaggedStartMap(FadeOut, rects)) - - # Set one to 0.5 - self.play(trackers[0].set_value, 0.9) - self.play(ShowCreationThenFadeAround(summands[0])) - self.wait() - self.play(LaggedStart(*[ - ShowCreationThenFadeAround(summand) - for summand in summands[1:] - ])) - self.play(*[ - ApplyMethod(tracker.set_value, 0.1) - for tracker in trackers[1:] - ]) - self.wait(10) - - -class SamplingTwoRandomNumbers(SamplingFourRandomNumbers): - CONFIG = { - "n_terms": 2, - "title_tex": "P\\left(x_0{}^2 + y_0{}^2 < 1\\right) = \\, ???", - "nl_to_nl_buff": 1, - "to_floor_buff": 2, - "random_seed": 1, - } - - -class SamplingSixRandomNumbers(SamplingFourRandomNumbers): - CONFIG = { - "n_terms": 6, - "nl_to_nl_buff": 0.5, - "include_half_labels": False, - "include_title": False, - "tip_scale_factor": 0.5, - } - - -class SamplePointIn3d(SpecialThreeDScene): - def construct(self): - axes = self.axes = self.get_axes() - sphere = self.get_sphere() - sphere.set_fill(BLUE_E, 0.25) - sphere.set_stroke(opacity=0.5) - - cube = Cube() - cube.replace(sphere) - cube.set_fill(GREY, 0.2) - cube.set_stroke(WHITE, 1, opacity=0.5) - - self.set_camera_orientation( - phi=80 * DEGREES, - theta=-120 * DEGREES, - ) - self.begin_ambient_camera_rotation(rate=0.03) - - dot = Sphere() - # dot = Dot() - dot.set_shade_in_3d(True) - dot.set_width(0.1) - - dot.move_to(axes.c2p(*np.random.uniform(0, 1, size=3))) - lines = always_redraw(lambda: self.get_lines(dot.get_center())) - labels = always_redraw(lambda: self.get_labels(lines)) - - self.add(axes) - self.add(cube) - - for line, label in zip(lines, labels): - self.play( - ShowCreation(line), - FadeIn(label) - ) - self.add(lines, labels) - self.play(GrowFromCenter(dot)) - self.play(DrawBorderThenFill(sphere, stroke_width=1)) - self.wait(2) - - n_points = 3000 - points = [ - axes.c2p(*np.random.uniform(-1, 1, 3)) - for x in range(n_points) - ] - # point_cloud = PMobject().add_points(points) - dots = VGroup(*[ - Dot( - point, - radius=0.01, - shade_in_3d=True, - ) - for point in points - ]) - dots.set_stroke(WHITE, 2) - dots.set_opacity(0.5) - self.play(ShowIncreasingSubsets(dots, run_time=9)) - # self.play(ShowCreation(point_cloud, run_time=3)) - self.wait(4) - return - - for x in range(6): - self.play( - point.move_to, - axes.c2p(*np.random.uniform(-1, 1, size=3)) - ) - self.wait(2) - self.wait(7) - - def get_lines(self, point): - axes = self.axes - x, y, z = axes.p2c(point) - p0 = axes.c2p(0, 0, 0) - p1 = axes.c2p(x, 0, 0) - p2 = axes.c2p(x, y, 0) - p3 = axes.c2p(x, y, z) - x_line = DashedLine(p0, p1, color=GREEN) - y_line = DashedLine(p1, p2, color=RED) - z_line = DashedLine(p2, p3, color=BLUE) - lines = VGroup(x_line, y_line, z_line) - lines.set_shade_in_3d(True) - return lines - - def get_labels(self, lines): - x_label = TexMobject("x") - y_label = TexMobject("y") - z_label = TexMobject("z") - result = VGroup(x_label, y_label, z_label) - result.rotate(90 * DEGREES, RIGHT) - result.set_shade_in_3d(True) - - x_line, y_line, z_line = lines - - x_label.match_color(x_line) - y_label.match_color(y_line) - z_label.match_color(z_line) - - x_label.next_to(x_line, IN, SMALL_BUFF) - y_label.next_to(y_line, RIGHT + OUT, SMALL_BUFF) - z_label.next_to(z_line, RIGHT, SMALL_BUFF) - - return result - - -class OverlayToPointIn3d(Scene): - def construct(self): - t2c = { - "{x}": GREEN, - "{y}": RED, - "{z}": BLUE, - } - ineq = TexMobject( - "{x}^2 + {y}^2 + {z}^2 < 1", - tex_to_color_map=t2c, - ) - ineq.scale(1.5) - ineq.move_to(FRAME_WIDTH * LEFT / 4) - ineq.to_edge(UP) - - equiv = TexMobject("\\Leftrightarrow") - equiv.scale(2) - equiv.match_y(ineq) - - rhs = TextMobject( - "$({x}, {y}, {z})$", - " lies within a\\\\sphere with radius 1" - ) - rhs[0][1].set_color(GREEN) - rhs[0][3].set_color(RED) - rhs[0][5].set_color(BLUE) - rhs.scale(1.3) - rhs.next_to(equiv, RIGHT) - rhs.to_edge(UP) - - self.add(ineq) - self.wait() - self.play(Write(equiv)) - self.wait() - self.play(FadeIn(rhs)) - self.wait() - - -class TwoDPlusTwoDEqualsFourD(HyperdartScene): - def construct(self): - board = VGroup(*self.mobjects) - - unit_size = 1.5 - axes = Axes( - x_min=-1.25, - x_max=1.25, - y_min=-1.25, - y_max=1.25, - axis_config={ - "unit_size": unit_size, - "tick_frequency": 0.5, - "include_tip": False, - } - ) - board.set_height(2 * unit_size) - axes.move_to(board) - axes.set_stroke(width=1) - - board.add(axes) - board.to_edge(LEFT) - self.add(board) - - # Set up titles - kw = { - "tex_to_color_map": { - "x_0": WHITE, - "y_0": WHITE, - "x_1": WHITE, - "y_1": WHITE, - } - } - title1 = VGroup( - TextMobject("First shot"), - TexMobject("(x_0, y_0)", **kw), - ) - title2 = VGroup( - TextMobject("Second shot"), - TexMobject("(x_1, y_1)", **kw), - ) - title3 = VGroup( - TextMobject("Point in 4d space"), - TexMobject("(x_0, y_0, x_1, y_1)", **kw) - ) - titles = VGroup(title1, title2, title3) - for title in titles: - title.arrange(DOWN) - plus = TexMobject("+").scale(2) - equals = TexMobject("=").scale(2) - - label1 = TexMobject("(x_0, y_0)") - label2 = TexMobject("(x_1, y_1)") - VGroup(label1, label2).scale(0.8) - - title1.next_to(board, UP) - - # First hit - point1 = axes.c2p(0.5, 0.7) - dart1, dot1 = self.show_hit_with_dart(point1) - label1.next_to(dot1, UR, buff=0) - self.add(title1, label1) - # lines1 = self.show_geometry(point1, pace="fast") - # chord_and_shadow1 = self.show_circle_shrink(lines1[1], pace="fast") - - board_copy = board.copy() - board_copy.next_to(board, RIGHT, buff=LARGE_BUFF) - self.square = board_copy[0] - - title2.next_to(board_copy, UP) - plus.move_to(titles[:2]) - - self.play(ReplacementTransform(board.copy().fade(1), board_copy)) - point2 = self.get_random_point() - dart2, dot2 = self.show_hit_with_dart(point2) - label2.next_to(dot2, UR, buff=0) - self.add(plus, title2, label2) - self.wait() - - # Set up the other titles - title3.to_edge(RIGHT) - title3.match_y(title2) - - equals.move_to(midpoint(title2.get_right(), title3.get_left())) - - randy = Randolph(height=2.5) - randy.next_to(title3, DOWN, buff=LARGE_BUFF) - randy.look_at(title3) - - kw = {"path_arc": -20 * DEGREES} - self.play( - LaggedStart( - *[ - TransformFromCopy( - title1[1].get_part_by_tex(tex), - title3[1].get_part_by_tex(tex), - **kw - ) - for tex in ["(", "x_0", ",", "y_0"] - ], - *[ - TransformFromCopy( - title2[1].get_part_by_tex(tex), - title3[1].get_parts_by_tex(tex)[-1], - **kw - ) - for tex in ["x_1", ",", "y_1", ")"] - ], - TransformFromCopy( - title2[1].get_part_by_tex(","), - title3[1].get_parts_by_tex(",")[1], - **kw - ), - lag_ratio=0.01, - ), - Write(equals), - ) - self.play( - FadeInFromDown(title3[0]), - FadeIn(randy), - ) - self.play(randy.change, "horrified") - self.play(Blink(randy)) - self.wait() - self.play(randy.change, "confused") - self.play(Blink(randy)) - self.wait() - - -class ExpectedValueComputation(Scene): - def construct(self): - t2c = { - "0": MAROON_C, - "1": BLUE, - "2": GREEN, - "3": YELLOW, - "4": RED, - } - - line1 = TexMobject( - "E[S]", "=", - "1 \\cdot", "P(S = 1)", "+", - "2 \\cdot", "P(S = 2)", "+", - "3 \\cdot", "P(S = 3)", "+", - "\\cdots", - tex_to_color_map=t2c - ) - line2 = TexMobject( - "=&\\phantom{-}", - "1 \\cdot", "\\big(", "P(S > 0)", "-", "P(S > 1)", "\\big)", "\\\\&+", - "2 \\cdot", "\\big(", "P(S > 1)", "-", "P(S > 2)", "\\big)", "\\\\&+", - "3 \\cdot", "\\big(", "P(S > 2)", "-", "P(S > 3)", "\\big)", "\\\\&+", - "\\cdots", - tex_to_color_map=t2c - ) - line2[1:12].align_to(line2[13], LEFT) - line3 = TexMobject( - "=", - "P(S > 0)", "+", - "P(S > 1)", "+", - "P(S > 2)", "+", - "P(S > 3)", "+", - "\\cdots", - tex_to_color_map=t2c, - ) - - line1.to_corner(UL) - line2.next_to(line1, DOWN, buff=MED_LARGE_BUFF) - line2.align_to(line1[1], LEFT) - line3.next_to(line2, DOWN, buff=MED_LARGE_BUFF) - line3.align_to(line1[1], LEFT) - - # Write line 1 - self.add(line1[:2]) - self.play(Write(line1[2:7])) - self.wait() - self.play(FadeIn(line1[7])) - self.play(Write(line1[8:13])) - self.wait() - self.play(FadeIn(line1[13])) - self.play(Write(line1[14:19])) - self.wait() - self.play(Write(line1[19:])) - self.wait() - - # line 2 scaffold - kw = { - "path_arc": 90 * DEGREES - } - bigs = line2.get_parts_by_tex("big") - self.play( - LaggedStart( - TransformFromCopy( - line1.get_part_by_tex("="), - line2.get_part_by_tex("="), - **kw - ), - TransformFromCopy( - line1.get_parts_by_tex("\\cdot"), - line2.get_parts_by_tex("\\cdot"), - **kw - ), - TransformFromCopy( - line1.get_parts_by_tex("+"), - line2.get_parts_by_tex("+"), - **kw - ), - TransformFromCopy( - line1.get_part_by_tex("1"), - line2.get_part_by_tex("1"), - **kw - ), - TransformFromCopy( - line1.get_part_by_tex("2"), - line2.get_part_by_tex("2"), - **kw - ), - TransformFromCopy( - line1.get_part_by_tex("3"), - line2.get_part_by_tex("3"), - **kw - ), - run_time=3, - lag_ratio=0, - ), - LaggedStart(*[ - GrowFromCenter(bigs[i:i + 2]) - for i in range(0, len(bigs), 2) - ]) - ) - self.wait() - - # Expand out sum - for n in range(3): - i = 6 * n - j = 12 * n - - rect1 = SurroundingRectangle(line1[i + 4:i + 7]) - rect2 = SurroundingRectangle(line2[j + 4:j + 11]) - color = line1[i + 5].get_color() - VGroup(rect1, rect2).set_stroke(color, 2) - - self.play(ShowCreation(rect1)) - self.play( - TransformFromCopy( - line1[i + 4:i + 7], - line2[j + 4:j + 7], - ), - TransformFromCopy( - line1[i + 4:i + 7], - line2[j + 8:j + 11], - ), - FadeIn(line2[j + 7]), - ReplacementTransform(rect1, rect2), - ) - self.play(FadeOut(rect2)) - - # Show telescoping - line2.generate_target() - line2.target.set_opacity(0.2) - line2.target[4:7].set_opacity(1) - - self.play(MoveToTarget(line2)) - self.wait() - self.play( - TransformFromCopy(line2[0], line3[0]), - TransformFromCopy(line2[4:7], line3[1:4]), - ) - self.wait() - - line2.target.set_opacity(0.2) - VGroup( - line2.target[1:4], - line2.target[7:12], - line2.target[12:19], - line2.target[23], - ).set_opacity(1) - - self.play(MoveToTarget(line2)) - self.wait() - self.play( - TransformFromCopy(line2[12], line3[4]), - TransformFromCopy(line2[16:19], line3[5:8]), - ) - self.wait() - - n = 12 - line2.target.set_opacity(0.2) - VGroup( - line2.target[n + 1:n + 4], - line2.target[n + 7:n + 12], - line2.target[n + 12:n + 19], - line2.target[n + 23], - ).set_opacity(1) - - self.play(MoveToTarget(line2)) - self.wait() - self.play( - TransformFromCopy(line2[n + 12], line3[8]), - TransformFromCopy(line2[n + 16:n + 19], line3[9:12]), - ) - self.wait() - self.play(Write(line3[12:])) - self.wait() - - rect = SurroundingRectangle(line3, buff=MED_SMALL_BUFF) - rect.set_stroke(WHITE, 2) - self.play(ShowCreation(rect)) - self.wait() - - self.wait(3) - - -class SubtractHistogramParts(ShowDistributionOfScores): - def construct(self): - n_scores = 10000 - scores = np.array([self.get_random_score() for x in range(n_scores)]) - axes = self.get_axes() - bars = self.get_histogram_bars(axes) - self.set_histogram_bars(bars, scores, axes) - - self.add(axes) - self.add(bars) - - # P(S = 2) - p2_arrow = Vector( - 0.75 * DOWN, - max_stroke_width_to_length_ratio=10, - max_tip_length_to_length_ratio=0.35, - ) - p2_arrow.next_to(bars[1], UP, SMALL_BUFF) - p2_arrow = VGroup( - p2_arrow.copy().set_stroke(BLACK, 9), - p2_arrow, - ) - - p2_label = TexMobject("P(S = 2)") - p2_label.next_to(p2_arrow, UP, SMALL_BUFF) - p2_label.set_color(bars[1].get_fill_color()) - - self.play( - GrowFromPoint(p2_arrow, p2_arrow.get_top()), - FadeInFromDown(p2_label), - bars[0].set_opacity, 0.1, - bars[2:].set_opacity, 0.1, - ) - self.wait() - - # Culumative probabilities - rhs = TexMobject("=", "P(S > 1)", "-", "P(S > 2)") - rhs[1].set_color(YELLOW) - rhs[3].set_color(bars[2].get_fill_color()) - rhs[2:].set_opacity(0.2) - rhs.next_to(p2_label, RIGHT) - - brace1 = Brace(bars[1:5], UP)[0] - brace1.next_to(rhs[1], DOWN) - brace1.match_color(rhs[1]) - - rf = 3.5 - lf = 1.4 - brace1[:2].stretch(rf, 0, about_edge=LEFT) - brace1[0].stretch(1 / rf, 0, about_edge=LEFT) - brace1[4:].stretch(lf, 0, about_edge=RIGHT) - brace1[5:].stretch(1 / lf, 0, about_edge=RIGHT) - - brace2 = Brace(bars[2:], UP) - brace2.match_color(rhs[3]) - brace2.set_width(10, about_edge=LEFT) - brace2.shift(1.5 * UP) - - self.add(brace1, p2_arrow) - self.play( - FadeIn(rhs), - bars[2:].set_opacity, 1, - GrowFromPoint(brace1, rhs[1].get_bottom()), - p2_arrow.set_opacity, 0.5, - ) - self.wait() - self.play( - rhs[:2].set_opacity, 0.2, - brace1.set_opacity, 0.2, - rhs[2:].set_opacity, 1, - bars[1].set_opacity, 0.1, - GrowFromCenter(brace2), - ) - self.wait() - - self.play( - bars[2:].set_opacity, 0.1, - bars[1].set_opacity, 1, - rhs.set_opacity, 1, - brace1.set_opacity, 1, - p2_arrow.set_opacity, 1, - ) - self.wait() - - - # for i, part in enumerate(brace1): - # self.add(Integer(i).scale(0.5).move_to(part)) - - -class GameWithSpecifiedScore(HyperdartScene): - CONFIG = { - "score": 1, - "random_seed": 1, - } - - def construct(self): - board = VGroup(self.square, self.circle, self.circle_center_dot) - board.to_edge(DOWN, buff=0.5) - - score_label = VGroup( - TextMobject("Score: "), - Integer(1) - ) - score_label.scale(2) - score_label.arrange(RIGHT, aligned_edge=DOWN) - score_label.to_edge(UP, buff=0.25) - - self.add(score_label) - - score = 1 - pace = "fast" - while True: - point = self.get_random_point() - want_to_continue = (score < self.score) - if want_to_continue: - while not self.is_inside(point): - point = self.get_random_point() - - dart, dot = self.show_hit_with_dart(point) - score_label[1].increment_value() - lines = self.show_geometry(point, pace) - chord_and_shadow = self.show_circle_shrink(lines[1], pace=pace) - - self.play( - FadeOut(VGroup(dart, dot, lines, chord_and_shadow)), - run_time=0.5, - ) - score += 1 - else: - while self.is_inside(point): - point = self.get_random_point() - self.show_miss(point) - self.play(ShowCreationThenFadeAround(score_label[1])) - self.wait() - return - - -class Score1Game(GameWithSpecifiedScore): - CONFIG = { - "score": 1, - } - - -class Score2Game(GameWithSpecifiedScore): - CONFIG = { - "score": 2, - } - - -class Score3Game(GameWithSpecifiedScore): - CONFIG = { - "score": 3, - } - - -class Score4Game(GameWithSpecifiedScore): - CONFIG = { - "score": 4, - } - - -class HistogramScene(ShowDistributionOfScores): - CONFIG = { - "n_scores": 10000, - "mean_line_height": 4, - } - - def setup(self): - self.scores = np.array([ - self.get_random_score() - for x in range(self.n_scores) - ]) - self.axes = self.get_axes() - self.bars = self.get_histogram_bars(self.axes) - self.set_histogram_bars(self.bars, self.scores, self.axes) - - self.add(self.axes) - self.add(self.bars) - - def get_mean_label(self): - mean_line = DashedLine(ORIGIN, self.mean_line_height * UP) - mean_line.set_stroke(YELLOW, 2) - - mean = np.mean(self.scores) - mean_line.move_to(self.axes.c2p(mean, 0), DOWN) - mean_label = VGroup( - *TextMobject("E[S]", "="), - DecimalNumber(mean, num_decimal_places=3), - ) - mean_label.arrange(RIGHT) - mean_label.match_color(mean_line) - mean_label.next_to( - mean_line.get_end(), UP, SMALL_BUFF, - index_of_submobject_to_align=0, - ) - - return VGroup(mean_line, *mean_label) - - -class ExpectedValueFromBars(HistogramScene): - def construct(self): - axes = self.axes - bars = self.bars - mean_label = self.get_mean_label() - mean_label.remove(mean_label[-1]) - - equation = TexMobject( - "P(S = 1)", "\\cdot", "1", "+", - "P(S = 2)", "\\cdot", "2", "+", - "P(S = 3)", "\\cdot", "3", "+", - "\\cdots" - ) - equation.scale(0.9) - equation.next_to(mean_label[-1], RIGHT) - equation.shift(LEFT) - - for i in range(3): - equation.set_color_by_tex( - str(i + 1), bars[i].get_fill_color() - ) - - equation[4:].set_opacity(0.2) - - self.add(mean_label) - self.play( - mean_label[1:].shift, LEFT, - FadeInFrom(equation, LEFT) - ) - - p_parts = VGroup() - p_part_copies = VGroup() - for i in range(3): - bar = bars[i] - num = axes.x_axis.numbers[i] - p_part = equation[4 * i] - s_part = equation[4 * i + 2] - - p_part_copy = p_part.copy() - p_part_copy.set_width(0.8 * bar.get_width()) - p_part_copy.next_to(bar, UP, SMALL_BUFF) - p_part_copy.set_opacity(1) - - self.remove(mean_label[0]) - self.play( - bars[:i + 1].set_opacity, 1, - bars[i + 1:].set_opacity, 0.2, - equation[:4 * (i + 1)].set_opacity, 1, - FadeInFromDown(p_part_copy), - Animation(mean_label[0]), - ) - kw = { - "surrounding_rectangle_config": { - "color": bar.get_fill_color(), - "buff": 0.5 * SMALL_BUFF, - } - } - self.play( - LaggedStart( - AnimationGroup( - ShowCreationThenFadeAround(p_part, **kw), - ShowCreationThenFadeAround(p_part_copy, **kw), - ), - AnimationGroup( - ShowCreationThenFadeAround(s_part, **kw), - ShowCreationThenFadeAround(num, **kw), - ), - lag_ratio=0.5, - ) - ) - self.wait() - p_parts.add(p_part) - p_part_copies.add(p_part_copy) - - self.add(bars, mean_label) - self.play( - bars.set_opacity, 1, - equation.set_opacity, 1, - FadeOut(p_part_copies) - ) - - braces = VGroup(*[ - Brace(p_part, UP) - for p_part in p_parts - ]) - for brace in braces: - brace.add(brace.get_text("???")) - - self.play(LaggedStartMap(FadeIn, braces)) - self.wait() - - -class ProbabilitySGtOne(HistogramScene): - def construct(self): - axes = self.axes - bars = self.bars - - brace = Brace(bars[1:], UP) - label = brace.get_tex("P(S > 1)") - brace[0][:2].stretch(1.5, 0, about_edge=LEFT) - - outlines = bars[1:].copy() - for bar in outlines: - bar.set_stroke(bar.get_fill_color(), 2) - bar.set_fill(opacity=0) - - self.play( - GrowFromEdge(brace, LEFT), - bars[0].set_opacity, 0.2, - bars[1:].set_opacity, 0.8, - ShowCreationThenFadeOut(outlines), - FadeInFrom(label, LEFT), - ) - self.wait() - - square = Square() - square.set_fill(BLUE, 0.75) - square.set_stroke(WHITE, 1) - square.set_height(0.5) - - circle = Circle() - circle.set_fill(RED, 0.75) - circle.set_stroke(WHITE, 1) - circle.set_height(0.5) - - bar = Line(LEFT, RIGHT) - bar.set_stroke(WHITE, 3) - bar.set_width(0.5) - - geo_frac = VGroup(circle, bar, square) - geo_frac.arrange(DOWN, SMALL_BUFF, buff=SMALL_BUFF) - - rhs = VGroup( - TexMobject("="), - geo_frac, - TexMobject("= \\frac{\\pi}{4}") - ) - rhs.arrange(RIGHT) - rhs.next_to(label) - - shift_val = 2.05 * LEFT + 0.25 * UP - rhs.shift(shift_val) - - self.play( - label.shift, shift_val, - FadeInFrom(rhs, LEFT) - ) - self.wait() - - # P(S > 2) - new_brace = brace.copy() - new_brace.next_to( - bars[2], UP, - buff=SMALL_BUFF, - aligned_edge=LEFT, - ) - self.add(new_brace) - - new_label = TexMobject( - "P(S > 2)", "=", "\\,???" - ) - new_label.next_to(new_brace[0][2], UP) - - self.play( - bars[1].set_opacity, 0.2, - label.set_opacity, 0.5, - rhs.set_opacity, 0.5, - brace.set_opacity, 0.5, - GrowFromEdge(new_brace, LEFT), - ReplacementTransform( - new_label.copy().fade(1).move_to(label, LEFT), - new_label, - ) - ) - self.wait() - - new_rhs = TexMobject( - "{\\text{4d ball}", " \\over", " \\text{4d cube}}", - # "=", - # "{\\pi^2 / 2", "\\over", "2^4}" - ) - new_rhs[0].set_color(RED) - new_rhs[2].set_color(BLUE) - new_rhs.move_to(new_label[-1], LEFT) - shift_val = 0.75 * LEFT + 0.15 * UP - - new_rhs.shift(shift_val) - - new_label.generate_target() - new_label.target.shift(shift_val) - new_label.target[-1].set_opacity(0) - - self.play( - MoveToTarget(new_label), - FadeInFrom(new_rhs, LEFT) - ) - self.wait() - - # P(S > 3) - final_brace = brace.copy() - final_brace.set_opacity(1) - final_brace.next_to( - bars[3], UP, - buff=SMALL_BUFF, - aligned_edge=LEFT, - ) - self.add(final_brace) - - final_label = TexMobject("P(S > 3)") - final_label.next_to(final_brace[0][2], UP, SMALL_BUFF) - - self.play( - bars[2].set_opacity, 0.2, - new_label[:-1].set_opacity, 0.5, - new_rhs.set_opacity, 0.5, - new_brace.set_opacity, 0.5, - GrowFromEdge(final_brace, LEFT), - ReplacementTransform( - final_label.copy().fade(1).move_to(new_label, LEFT), - final_label, - ), - axes.x_axis[-1].set_opacity, 0, - ) - self.wait() - - -class VolumsOfNBalls(Scene): - def construct(self): - title, alt_title = [ - TextMobject( - "Volumes of " + tex + "-dimensional balls", - tex_to_color_map={tex: YELLOW}, - ) - for tex in ["$N$", "$2n$"] - ] - for mob in [title, alt_title]: - mob.scale(1.5) - mob.to_edge(UP) - - formulas = VGroup(*[ - TexMobject( - tex, - tex_to_color_map={"R": WHITE} - ) - for tex in [ - "2R", - "\\pi R^2", - "\\frac{4}{3} \\pi R^3", - "\\frac{1}{2} \\pi^2 R^4", - "\\frac{8}{15} \\pi^2 R^5", - "\\frac{1}{6} \\pi^3 R^6", - "\\frac{16}{105} \\pi^3 R^7", - "\\frac{1}{24} \\pi^4 R^8", - "\\frac{32}{945} \\pi^4 R^9", - "\\frac{1}{120} \\pi^5 R^{10}", - "\\frac{64}{10{,}395} \\pi^5 R^{11}", - "\\frac{1}{720} \\pi^6 R^{12}", - ] - ]) - - formulas.arrange(RIGHT, buff=LARGE_BUFF) - formulas.scale(0.9) - formulas.to_edge(LEFT) - - lines = VGroup() - d_labels = VGroup() - for dim, formula in zip(it.count(1), formulas): - label = VGroup(Integer(dim), TexMobject("D")) - label.arrange(RIGHT, buff=0, aligned_edge=DOWN) - label[0].set_color(YELLOW) - label.move_to(formula) - label.shift(UP) - - line = Line(UP, DOWN) - line.set_stroke(WHITE, 1) - line.next_to(formula, RIGHT, buff=MED_LARGE_BUFF) - line.shift(0.5 * UP) - - d_labels.add(label) - lines.add(line) - # coefs.add(formula[0]) - formula[0].set_color(BLUE_B) - lines.remove(lines[-1]) - line = Line(formulas.get_left(), formulas.get_right()) - line.set_stroke(WHITE, 1) - line.next_to(d_labels, DOWN, MED_SMALL_BUFF) - lines.add(line) - - chart = VGroup(lines, d_labels, formulas) - chart.save_state() - - self.add(title) - self.add(d_labels) - self.add(lines) - - self.play(LaggedStartMap(FadeInFromDown, formulas, run_time=3, lag_ratio=0.1)) - self.play(chart.to_edge, RIGHT, {"buff": MED_SMALL_BUFF}, run_time=5) - self.wait() - self.play(Restore(chart)) - self.play(FadeOut(formulas[4:])) - - rect1 = SurroundingRectangle(formulas[2][0][-1]) - rect2 = SurroundingRectangle(formulas[3][0][-2:]) - self.play(ShowCreation(rect1)) - self.play(TransformFromCopy(rect1, rect2)) - self.play(FadeOut(VGroup(rect1, rect2))) - - arrows = VGroup(*[ - Arrow( - formulas[i].get_bottom(), - formulas[i + 1].get_bottom(), - path_arc=150 * DEGREES, - ) - for i in (1, 2) - ]) - - for arrow in arrows: - self.play(ShowCreation(arrow)) - self.wait() - self.play( - FadeOut(arrows), - FadeIn(formulas[4:]), - ) - - # General formula for even dimensions - braces = VGroup(*[ - Brace(formula, DOWN) - for formula in formulas[1::2] - ]) - gen_form = TexMobject("{\\pi^n \\over n!}", "R^{2n}") - gen_form[0].set_color(BLUE_B) - gen_form.scale(1.5) - gen_form.to_edge(DOWN) - - self.play( - formulas[::2].set_opacity, 0.25, - ReplacementTransform(title, alt_title) - ) - for brace in braces[:3]: - self.play(GrowFromCenter(brace)) - self.wait() - self.play( - FadeOut(braces[:3]), - FadeInFrom(gen_form, UP), - ) - self.wait() - - -class RepeatedSamplesGame(Scene): - def construct(self): - pass - - -# Old scenes, before decision to collaborate with numberphile -class IntroduceGame(HyperdartScene): - CONFIG = { - "random_seed": 0, - "square_width": 5, - "num_darts_in_initial_flurry": 5, - } - - def construct(self): - self.show_flurry_of_points() - self.show_board_dimensions() - self.introduce_bullseye() - self.show_miss_example() - self.show_shrink_rule() - - def show_flurry_of_points(self): - square = self.square - circle = self.circle - - title = TextMobject("Hyperdarts") - title.scale(1.5) - title.to_edge(UP) - - n = self.num_darts_in_initial_flurry - points = np.random.normal(size=n * 3).reshape((n, 3)) - points[:, 2] = 0 - points *= 0.75 - - board = Dartboard() - board.match_width(square) - board.move_to(square) - - pre_square = Circle(color=WHITE) - pre_square.replace(square) - - self.remove(circle, square) - self.add(board) - - darts, dots = self.show_hits_with_darts( - points, - added_anims=[FadeInFromDown(title)] - ) - self.wait() - - def func(p): - theta = angle_of_vector(p) % (TAU / 4) - if theta > TAU / 8: - theta = TAU / 4 - theta - p *= 1 / np.cos(theta) - return p - - self.play( - *[ - ApplyPointwiseFunction(func, pieces, run_time=1) - for pieces in [*board[:3], *dots] - ], - *[ - MaintainPositionRelativeTo(dart, dot) - for dart, dot in zip(darts, dots) - ] - ) - - self.flurry_dots = dots - self.darts = darts - self.title = title - self.board = board - - def show_board_dimensions(self): - square = self.square - - labels = VGroup(*[ - TextMobject("2 ft").next_to( - square.get_edge_center(vect), vect, - ) - for vect in [DOWN, RIGHT] - ]) - labels.set_color(YELLOW) - - h_line, v_line = lines = VGroup(*[ - DashedLine( - square.get_edge_center(v1), - square.get_edge_center(-v1), - ).next_to(label, v2) - for label, v1, v2 in zip(labels, [LEFT, UP], [UP, LEFT]) - ]) - lines.match_color(labels) - - self.play( - LaggedStartMap(ShowCreation, lines), - LaggedStartMap(FadeInFromDown, labels), - lag_ratio=0.5 - ) - self.wait() - - self.square_dimensions = VGroup(lines, labels) - - def introduce_bullseye(self): - square = self.square - circle = self.circle - board = self.board - circle.save_state() - circle.replace(board[-1]) - - label = TextMobject("Bullseye") - label.scale(1.5) - label.next_to(square, LEFT, aligned_edge=UP) - label.set_color(RED) - arrow = Arrow( - label.get_bottom(), - circle.get_corner(DR) - ) - - radius = DashedLine( - square.get_center(), - square.get_left(), - stroke_width=2, - ) - radius_label = TextMobject("1 ft") - radius_label.next_to(radius, DOWN, SMALL_BUFF) - - self.add(circle, self.square_dimensions) - self.play( - FadeInFromLarge(circle), - FadeInFromDown(label), - ShowCreation(arrow), - LaggedStartMap(FadeOut, self.flurry_dots, run_time=1), - LaggedStartMap(FadeOut, self.darts, run_time=1), - ) - self.wait() - self.add(square, board, arrow, circle) - self.play( - Restore(circle), - ApplyMethod( - arrow.scale, 0.4, - {"about_point": arrow.get_start()} - ), - ) - self.add(radius, self.circle_center_dot) - self.play( - ShowCreation(radius), - FadeInFrom(radius_label, RIGHT), - FadeIn(self.circle_center_dot), - ) - self.play( - FadeOut(label), - Uncreate(arrow), - FadeOut(board) - ) - self.wait() - - s_lines, s_labels = self.square_dimensions - self.play( - FadeOut(s_lines), - FadeOut(radius), - FadeOut(radius_label), - FadeOut(self.title), - ) - - self.circle_dimensions = VGroup( - radius, radius_label, - ) - - def show_miss_example(self): - square = self.square - point = square.get_corner(UL) + 0.5 * DR - - miss_word = TextMobject("Miss!") - miss_word.scale(1.5) - miss_word.next_to(point, UP, LARGE_BUFF) - - dart, dot = self.show_hit_with_dart(point) - self.play(FadeInFromDown(miss_word)) - self.wait() - game_over = self.show_game_over() - self.wait() - self.play( - *map(FadeOut, [dart, dot, miss_word, game_over]) - ) - - def show_shrink_rule(self): - circle = self.circle - point = 0.5 * circle.point_from_proportion(0.2) - - # First example - self.show_full_hit_process(point) - self.wait() - - # Close to border - label = TextMobject("Bad shot $\\Rightarrow$ much shrinkage") - label.scale(1.5) - label.to_edge(UP) - - point = 0.98 * circle.point_from_proportion(3 / 8) - circle.save_state() - self.play(FadeInFromDown(label)) - self.show_full_hit_process(point) - self.wait() - self.play(Restore(circle)) - - # Close to center - new_label = TextMobject("Good shot $\\Rightarrow$ less shrinkage") - new_label.scale(1.5) - new_label.to_edge(UP) - point = 0.2 * circle.point_from_proportion(3 / 8) - self.play( - FadeInFromDown(new_label), - FadeOutAndShift(label, UP), - ) - self.show_full_hit_process(point) - self.wait() - self.play(FadeOut(new_label)) - - # Play on - for x in range(3): - r1, r2 = np.random.random(size=2) - point = r1 * circle.point_from_proportion(r2) - self.show_full_hit_process(point) - point = circle.get_right() + 0.5 * UR - self.show_miss(point) - self.wait() - self.show_game_over() - - -class ShowScoring(HyperdartScene): - def setup(self): - super().setup() - self.add_score_counter() - - def construct(self): - self.comment_on_score() - self.show_several_hits() - - def comment_on_score(self): - score_label = self.score_label - comment = TextMobject("\\# Bullseyes") - # rect = SurroundingRectangle(comment) - # rect.set_stroke(width=1) - # comment.add(rect) - comment.set_color(YELLOW) - comment.next_to(score_label, DOWN, LARGE_BUFF) - comment.set_x(midpoint( - self.square.get_left(), - LEFT_SIDE, - )[0]) - arrow = Arrow( - comment.get_top(), - score_label[1].get_bottom(), - buff=0.2, - ) - arrow.match_color(comment) - - self.play( - FadeInFromDown(comment), - GrowArrow(arrow), - ) - - def show_several_hits(self): - points = [UR, DL, 0.5 * UL, 0.5 * DR] - for point in points: - self.show_full_hit_process(point, pace="fast") - self.show_miss(2 * UR) - self.wait() - - # - def add_score_counter(self): - score = Integer(0) - score_label = VGroup( - TextMobject("Score: "), - score - ) - score_label.arrange(RIGHT, aligned_edge=DOWN) - score_label.scale(1.5) - score_label.to_corner(UL) - - self.add(score_label) - - self.score = score - self.score_label = score_label - - def increment_score(self): - score = self.score - new_score = score.copy() - new_score.increment_value(1) - self.play( - FadeOutAndShift(score, UP), - FadeInFrom(new_score, DOWN), - run_time=1, - ) - self.remove(new_score) - score.increment_value() - score.move_to(new_score) - self.add(score) - - def show_hit(self, point, *args, **kwargs): - result = super().show_hit(point, *args, **kwargs) - if self.is_inside(point): - self.increment_score() - return result - - def show_hit_with_dart(self, point, *args, **kwargs): - result = super().show_hit_with_dart(point, *args, **kwargs) - if self.is_inside(point): - self.increment_score() - return result - - -class ShowSeveralRounds(ShowScoring): - CONFIG = { - "n_rounds": 5, - } - - def construct(self): - for x in range(self.n_rounds): - self.show_single_round() - self.reset_board() - - def show_single_round(self, pace="fast"): - while True: - point = self.get_random_point() - if self.is_inside(point): - self.show_full_hit_process(point, pace=pace) - else: - to_fade = self.show_miss(point) - self.wait(0.5) - self.play( - ShowCreationThenFadeAround(self.score_label), - FadeOut(to_fade) - ) - return - - def reset_board(self): - score = self.score - new_score = score.copy() - new_score.set_value(0) - self.play( - self.circle.match_width, self.square, - FadeOutAndShift(score, UP), - FadeInFrom(new_score, DOWN), - ) - score.set_value(0) - self.add(score) - self.remove(new_score) - - -class ShowSeveralRoundsQuickly(ShowSeveralRounds): - CONFIG = { - "n_rounds": 15, - } - - def show_full_hit_process(self, point, *args, **kwargs): - lines = self.get_all_hit_lines(point) - - dart = self.show_hit_with_dart(point) - self.add(lines) - self.score.increment_value(1) - to_fade = self.show_circle_shrink(lines[1], pace="fast") - to_fade.add(*lines, *dart) - self.play(FadeOut(to_fade), run_time=0.5) - - def increment_score(self): - pass # Handled elsewhere - - -class ShowSeveralRoundsVeryQuickly(ShowSeveralRoundsQuickly): - def construct(self): - pass - - -class ShowUniformDistribution(HyperdartScene): - CONFIG = { - "dart_sound": "dart_high", - "n_points": 1000, - } - - def construct(self): - self.add_title() - self.show_random_points() - self.exchange_titles() - self.show_random_points() - - def get_square(self): - return super().get_square().to_edge(DOWN) - - def add_title(self): - # square = self.square - title = TextMobject("All points in the square are equally likely") - title.scale(1.5) - title.to_edge(UP) - - new_title = TextMobject("``Uniform distribution'' on the square") - new_title.scale(1.5) - new_title.to_edge(UP) - - self.play(FadeInFromDown(title)) - - self.title = title - self.new_title = new_title - - def show_random_points(self): - points = self.get_random_points(self.n_points) - dots = VGroup(*[ - Dot(point, radius=0.02) - for point in points - ]) - dots.set_fill(opacity=0.75) - - run_time = 5 - self.play(LaggedStartMap( - FadeInFromLarge, dots, - run_time=run_time, - )) - for x in range(1000): - self.add_dart_sound( - time_offset=-run_time * np.random.random(), - gain=-10, - gain_to_background=-5, - ) - self.wait() - - def exchange_titles(self): - self.play( - FadeInFromDown(self.new_title), - FadeOutAndShift(self.title, UP), - ) - - -class ExpectedScoreEqualsQMark(Scene): - def construct(self): - equation = TextMobject( - "\\textbf{E}[Score] = ???", - tex_to_color_map={ - "???": YELLOW, - } - ) - aka = TextMobject("a.k.a. Long-term average") - aka.next_to(equation, DOWN) - - self.play(Write(equation)) - self.wait(2) - self.play(FadeInFrom(aka, UP)) - self.wait() - diff --git a/from_3b1b/old/inventing_math.py b/from_3b1b/old/inventing_math.py deleted file mode 100644 index 162f8803..00000000 --- a/from_3b1b/old/inventing_math.py +++ /dev/null @@ -1,2159 +0,0 @@ -#!/usr/bin/env python - -import numpy as np -import itertools as it -from copy import deepcopy -import sys -import operator as op -from random import sample - -from manimlib.imports import * -from script_wrapper import command_line_create_scene -from functools import reduce - -# from inventing_math_images import * - -MOVIE_PREFIX = "inventing_math/" -DIVERGENT_SUM_TEXT = [ - "1", - "+2", - "+4", - "+8", - "+\\cdots", - "+2^n", - "+\\cdots", - "= -1", -] - -CONVERGENT_SUM_TEXT = [ - "\\frac{1}{2}", - "+\\frac{1}{4}", - "+\\frac{1}{8}", - "+\\frac{1}{16}", - "+\\cdots", - "+\\frac{1}{2^n}", - "+\\cdots", - "=1", -] - -CONVERGENT_SUM_TERMS = [ - "\\frac{1}{2}", - "\\frac{1}{4}", - "\\frac{1}{8}", - "\\frac{1}{16}", -] - -PARTIAL_CONVERGENT_SUMS_TEXT = [ - "\\frac{1}{2}", - "", "", ",\\quad", - "\\frac{1}{2} + \\frac{1}{4}", - "=", "\\frac{3}{4}", ",\\quad", - "\\frac{1}{2} + \\frac{1}{4} + \\frac{1}{8}", - "=", "\\frac{7}{8}", ",\\quad", - "\\frac{1}{2} + \\frac{1}{4} + \\frac{1}{8} + \\frac{1}{16}", - "=", "\\frac{15}{16}", ",\\dots" -] - -def partial_sum(n): - return sum([1.0/2**(k+1) for k in range(n)]) - -ALT_PARTIAL_SUM_TEXT = reduce(op.add, [ - [str(partial_sum(n)), "&=", "+".join(CONVERGENT_SUM_TERMS[:n])+"\\\\"] - for n in range(1, len(CONVERGENT_SUM_TERMS)+1) -])+ [ - "\\vdots", "&", "\\\\", - "1.0", "&=", "+".join(CONVERGENT_SUM_TERMS)+"+\\cdots+\\frac{1}{2^n}+\\cdots" -] - - -NUM_WRITTEN_TERMS = 4 -INTERVAL_RADIUS = 5 -NUM_INTERVAL_TICKS = 16 - - -def divergent_sum(): - return TexMobject(DIVERGENT_SUM_TEXT, size = "\\large").scale(2) - -def convergent_sum(): - return TexMobject(CONVERGENT_SUM_TEXT, size = "\\large").scale(2) - -def Underbrace(left, right): - result = TexMobject("\\Underbrace{%s}"%(14*"\\quad")) - result.stretch_to_fit_width(right[0]-left[0]) - result.shift(left - result.points[0]) - return result - -def zero_to_one_interval(): - interval = NumberLine( - radius = INTERVAL_RADIUS, - interval_size = 2.0*INTERVAL_RADIUS/NUM_INTERVAL_TICKS - ) - interval.elongate_tick_at(-INTERVAL_RADIUS, 4) - interval.elongate_tick_at(INTERVAL_RADIUS, 4) - zero = TexMobject("0").shift(INTERVAL_RADIUS*LEFT+DOWN) - one = TexMobject("1").shift(INTERVAL_RADIUS*RIGHT+DOWN) - return Mobject(interval, zero, one) - -def draw_you(with_bubble = False): - result = PiCreature() - result.give_straight_face().set_color("grey") - result.to_corner(LEFT+DOWN) - result.rewire_part_attributes() - if with_bubble: - bubble = ThoughtBubble() - bubble.stretch_to_fit_width(11) - bubble.pin_to(result) - return result, bubble - return result - -def get_room_colors(): - return list(Color("yellow").range_to("red", 4)) - -def power_of_divisor(n, d): - result = 0 - while n%d == 0: - result += 1 - n /= d - return result - -class FlipThroughNumbers(Animation): - def __init__(self, function = lambda x : x, - start = 0, end = 10, - start_center = ORIGIN, - end_center = ORIGIN, - **kwargs): - self.function = function - self.start = start - self.end = end - self.start_center = start_center - self.end_center = end_center - self.current_number = function(start) - mobject = TexMobject(str(self.current_number)).shift(start_center) - Animation.__init__(self, mobject, **kwargs) - - def interpolate_mobject(self, alpha): - new_number = self.function( - self.start + int(alpha *(self.end-self.start)) - ) - if new_number != self.current_number: - self.current_number = new_number - self.mobject = TexMobject(str(new_number)).shift(self.start_center) - if not all(self.start_center == self.end_center): - self.mobject.center().shift( - (1-alpha)*self.start_center + alpha*self.end_center - ) - - -###################################### - -class IntroduceDivergentSum(Scene): - def construct(self): - equation = divergent_sum().split() - sum_value = None - brace = Underbrace( - equation[0].get_boundary_point(DOWN+LEFT), - equation[1].get_boundary_point(DOWN+RIGHT) - ).shift(0.2*DOWN) - min_x_coord = min(equation[0].points[:,0]) - for x in range(NUM_WRITTEN_TERMS): - self.add(equation[x]) - if x == 0: - self.wait(0.75) - continue - brace.stretch_to_fit_width( - max(equation[x].points[:,0]) - min_x_coord - ) - brace.to_edge(LEFT, buff = FRAME_X_RADIUS+min_x_coord) - if sum_value: - self.remove(sum_value) - sum_value = TexMobject(str(2**(x+1) - 1)) - sum_value.shift(brace.get_center() + 0.5*DOWN) - self.add(brace, sum_value) - self.wait(0.75) - self.remove(sum_value) - ellipses = Mobject( - *[equation[NUM_WRITTEN_TERMS + i] for i in range(3)] - ) - end_brace = deepcopy(brace).stretch_to_fit_width( - max(ellipses.points[:,0])-min_x_coord - ).to_edge(LEFT, buff = FRAME_X_RADIUS+min_x_coord) - kwargs = {"run_time" : 5.0, "rate_func" : rush_into} - flip_through = FlipThroughNumbers( - lambda x : 2**(x+1)-1, - start = NUM_WRITTEN_TERMS-1, - end = 50, - start_center = brace.get_center() + 0.5*DOWN, - end_center = end_brace.get_center() + 0.5*DOWN, - **kwargs - ) - self.add(ellipses) - self.play( - Transform(brace, end_brace, **kwargs), - flip_through, - ) - self.clear() - self.add(*equation) - self.wait() - -class ClearlyNonsense(Scene): - def construct(self): - number_line = NumberLine().add_numbers() - div_sum = divergent_sum() - this_way = TextMobject("Sum goes this way...") - this_way.to_edge(LEFT).shift(RIGHT*(FRAME_X_RADIUS+1) + DOWN) - how_here = TextMobject("How does it end up here?") - how_here.shift(1.5*UP+LEFT) - neg_1_arrow = Arrow( - (-1, 0.3, 0), - tail=how_here.get_center()+0.5*DOWN - ) - right_arrow = Arrow( - (FRAME_X_RADIUS-0.5)*RIGHT + DOWN, - tail = (max(this_way.points[:,0]), -1, 0) - ) - how_here.set_color("red") - neg_1_arrow.set_color("red") - this_way.set_color("yellow") - right_arrow.set_color("yellow") - - self.play(Transform( - div_sum, - deepcopy(div_sum).scale(0.5).shift(3*UP) - )) - self.play(ShowCreation(number_line)) - self.wait() - self.add(how_here) - self.play(ShowCreation(neg_1_arrow)) - self.wait() - self.add(this_way) - self.play(ShowCreation(right_arrow)) - self.wait() - -class OutlineOfVideo(Scene): - def construct(self): - conv_sum = convergent_sum().scale(0.5) - div_sum = divergent_sum().scale(0.5) - overbrace = Underbrace( - conv_sum.get_left(), - conv_sum.get_right() - ).rotate(np.pi, RIGHT).shift(0.75*UP*conv_sum.get_height()) - dots = conv_sum.split()[-2].set_color("green") - dots.sort_points() - arrow = Arrow( - dots.get_bottom(), - direction = UP+LEFT - ) - u_brace = Underbrace(div_sum.get_left(), div_sum.get_right()) - u_brace.shift(1.5*div_sum.get_bottom()) - for mob in conv_sum, overbrace, arrow, dots: - mob.shift(2*UP) - for mob in div_sum, u_brace: - mob.shift(DOWN) - texts = [ - TextMobject(words).set_color("yellow") - for words in [ - "1. Discover this", - "2. Clarify what this means", - "3. Discover this", - ["4. Invent ", "\\textbf{new math}"] - ] - ] - last_one_split = texts[-1].split() - last_one_split[1].set_color("skyblue") - texts[-1] = Mobject(*last_one_split) - texts[0].shift(overbrace.get_top()+texts[0].get_height()*UP) - texts[1].shift(sum([ - arrow.get_boundary_point(DOWN+RIGHT), - texts[1].get_height()*DOWN - ])) - texts[2].shift(u_brace.get_bottom()+texts[3].get_height()*DOWN) - texts[3].to_edge(DOWN) - - groups = [ - [texts[0], overbrace, conv_sum], - [texts[1], arrow, dots], - [texts[2], u_brace, div_sum], - [texts[3]] - ] - for group in groups: - self.play(*[ - DelayByOrder(FadeIn(element)) - for element in group - ]) - self.wait() - -# # class ReasonsForMakingVideo(Scene): -# # def construct(self): -# # text = TextMobject([ -# # """ -# # \\begin{itemize} -# # \\item Understand what ``$ -# # """, -# # "".join(DIVERGENT_SUM_TEXT), -# # """ -# # $'' is saying. -# # """, -# # """ -# # \\item Nonsense-Driven Construction -# # \\end{itemize} -# # """ -# # ], size = "\\Small") -# # text.scale(1.5).to_edge(LEFT).shift(UP).set_color("white") -# # text.set_color("green", lambda (x, y, z) : x < -FRAME_X_RADIUS + 1) -# # line_one_first, equation, line_one_last, line_two = text.split() -# # line_two.shift(2*DOWN) -# # div_sum = divergent_sum().scale(0.5).shift(3*UP) - -# # self.add(div_sum) -# # self.play( -# # ApplyMethod(div_sum.replace, equation), -# # FadeIn(line_one_first), -# # FadeIn(line_one_last) -# # ) -# # self.wait() -# # self.add(line_two) -# # self.wait() - -# class DiscoverAndDefine(Scene): -# def construct(self): -# sum_mob = TexMobject("\\sum_{n = 1}^\\infty a_n") -# discover = TextMobject("What does it feel like to discover these?") -# define = TextMobject([ -# "What does it feel like to", -# "\\emph{define} ", -# "them?" -# ]) -# sum_mob.shift(2*UP) -# define.shift(2*DOWN) -# define_parts = define.split() -# define_parts[1].set_color("skyblue") - -# self.add(sum_mob) -# self.play(FadeIn(discover)) -# self.wait() -# self.play(FadeIn(Mobject(*define_parts))) -# self.wait() - -class YouAsMathematician(Scene): - def construct(self): - you, bubble = draw_you(with_bubble = True) - explanation = TextMobject( - "You as a (questionably accurate portrayal of a) mathematician.", - size = "\\small" - ).shift([2, you.get_center()[1], 0]) - arrow = Arrow(you.get_center(), direction = LEFT) - arrow.nudge(you.get_width()) - for mob in arrow, explanation: - mob.set_color("yellow") - equation = convergent_sum() - bubble.add_content(equation) - equation_parts = equation.split() - equation.shift(0.5*RIGHT) - bubble.clear() - dot_pair = [ - Dot(density = 3*DEFAULT_POINT_DENSITY_1D).shift(x+UP) - for x in (LEFT, RIGHT) - ] - self.add(you, explanation) - self.play( - ShowCreation(arrow), - BlinkPiCreature(you) - ) - self.wait() - self.play(ShowCreation(bubble)) - for part in equation_parts: - self.play(DelayByOrder(FadeIn(part)), run_time = 0.5) - self.wait() - self.play( - BlinkPiCreature(you), - FadeOut(explanation), - FadeOut(arrow) - ) - self.remove(bubble, *equation_parts) - self.disapproving_friend() - self.add(bubble, equation) - self.play(Transform(equation, Mobject(*dot_pair))) - self.remove(equation) - self.add(*dot_pair) - two_arrows = [ - Arrow(x, direction = x).shift(UP).nudge() - for x in (LEFT, RIGHT) - ] - self.play(*[ShowCreation(a) for a in two_arrows]) - self.play(BlinkPiCreature(you)) - self.remove(*dot_pair+two_arrows) - everything = Mobject(*self.mobjects) - self.clear() - self.play( - ApplyPointwiseFunction( - lambda p : 3*FRAME_X_RADIUS*p/get_norm(p), - everything - ), - *[ - Transform(dot, deepcopy(dot).shift(DOWN).scale(3)) - for dot in dot_pair - ], - run_time = 2.0 - ) - self.wait() - - def disapproving_friend(self): - friend = Mortimer().to_corner(DOWN+RIGHT) - bubble = SpeechBubble().pin_to(friend) - bubble.write("It's simply not rigorous!") - bubble.content.sort_points(lambda p : np.dot(p, DOWN+RIGHT)) - - self.add(friend, bubble) - self.play(DelayByOrder(FadeIn(bubble.content))) - self.wait() - self.remove(friend, bubble, bubble.content) - - -class DotsGettingCloser(Scene): - def construct(self): - dots = [ - Dot(radius = 3*Dot.DEFAULT_RADIUS).shift(3*x) - for x in (LEFT, RIGHT) - ] - self.add(*dots) - self.wait() - for x in range(10): - distance = min(dots[1].points[:,0])-max(dots[0].points[:,0]) - self.play(ApplyMethod(dots[0].shift, 0.5*distance*RIGHT)) - - -class ZoomInOnInterval(Scene): - def construct(self): - number_line = NumberLine(density = 10*DEFAULT_POINT_DENSITY_1D) - number_line.add_numbers() - interval = zero_to_one_interval().split() - - new_line = deepcopy(number_line) - new_line.set_color("black", lambda x_y_z1 : x_y_z1[0] < 0 or x_y_z1[0] > 1 or x_y_z1[1] < -0.2) - # height = new_line.get_height() - new_line.scale(2*INTERVAL_RADIUS) - new_line.shift(INTERVAL_RADIUS*LEFT) - # new_line.stretch_to_fit_height(height) - - self.add(number_line) - self.wait() - self.play(Transform(number_line, new_line)) - self.clear() - squish = lambda p : (p[0], 0, 0) - self.play( - ApplyMethod(new_line.apply_function, squish), - ApplyMethod( - interval[0].apply_function, squish, - rate_func = lambda t : 1-t - ), - *[FadeIn(interval[x]) for x in [1, 2]] - ) - self.clear() - self.add(*interval) - self.wait() - -class DanceDotOnInterval(Scene): - def construct(self, mode): - num_written_terms = NUM_WRITTEN_TERMS - prop = 0.5 - sum_terms = [ - "\\frac{1}{2}", - "\\frac{1}{4}", - "\\frac{1}{8}", - "\\frac{1}{16}", - ] - num_height = 1.3*DOWN - interval = zero_to_one_interval() - dots = [ - Dot(radius = 3*Dot.DEFAULT_RADIUS).shift(INTERVAL_RADIUS*x+UP) - for x in (LEFT, RIGHT) - ] - color_range = Color("green").range_to("yellow", num_written_terms) - conv_sum = TexMobject(sum_terms, size = "\\large").split() - - self.add(interval) - self.play(*[ - ApplyMethod(dot.shift, DOWN) - for dot in dots - ]) - self.wait() - for count in range(num_written_terms): - shift_val = 2*RIGHT*INTERVAL_RADIUS*(1-prop)*(prop**count) - start = dots[0].get_center() - line = Line(start, start + shift_val*RIGHT) - line.set_color(next(color_range)) - self.play( - ApplyMethod(dots[0].shift, shift_val), - ShowCreation(line) - ) - num = conv_sum[count] - num.shift(RIGHT*(line.get_center()[0]-num.get_center()[0])) - num.shift(num_height) - arrow = Mobject() - if num.get_width() > line.get_length(): - num.center().shift(3*DOWN+2*(count-2)*RIGHT) - arrow = Arrow( - line.get_center()+2*DOWN, - tail = num.get_center()+0.5*num.get_height()*UP - ) - self.play( - ApplyMethod(line.shift, 2*DOWN), - FadeIn(num), - FadeIn(arrow), - ) - self.wait() - self.write_partial_sums() - self.wait() - - def write_partial_sums(self): - partial_sums = TexMobject(PARTIAL_CONVERGENT_SUMS_TEXT, size = "\\small") - partial_sums.scale(1.5).to_edge(UP) - partial_sum_parts = partial_sums.split() - partial_sum_parts[0].set_color("yellow") - - for x in range(0, len(partial_sum_parts), 4): - partial_sum_parts[x+2].set_color("yellow") - self.play(*[ - FadeIn(partial_sum_parts[y]) - for y in range(x, x+4) - ]) - self.wait(2) - -class OrganizePartialSums(Scene): - def construct(self): - partial_sums = TexMobject(PARTIAL_CONVERGENT_SUMS_TEXT, size = "\\small") - partial_sums.scale(1.5).to_edge(UP) - partial_sum_parts = partial_sums.split() - for x in [0] + list(range(2, len(partial_sum_parts), 4)): - partial_sum_parts[x].set_color("yellow") - pure_sums = [ - partial_sum_parts[x] - for x in range(0, len(partial_sum_parts), 4) - ] - new_pure_sums = deepcopy(pure_sums) - for pure_sum, count in zip(new_pure_sums, it.count(3, -1.2)): - pure_sum.center().scale(1/1.25).set_color("white") - pure_sum.to_edge(LEFT).shift(2*RIGHT+count*UP) - - self.add(*partial_sum_parts) - self.wait() - self.play(*[ - ClockwiseTransform(*pair) - for pair in zip(pure_sums, new_pure_sums) - ]+[ - FadeOut(mob) - for mob in partial_sum_parts - if mob not in pure_sums - ]) - down_arrow = TexMobject("\\downarrow") - down_arrow.to_edge(LEFT).shift(2*RIGHT+2*DOWN) - dots = TexMobject("\\vdots") - dots.shift(down_arrow.get_center()+down_arrow.get_height()*UP) - infinite_sum = TexMobject("".join(CONVERGENT_SUM_TEXT[:-1]), size = "\\samll") - infinite_sum.scale(1.5/1.25) - infinite_sum.to_corner(DOWN+LEFT).shift(2*RIGHT) - - self.play(ShowCreation(dots)) - self.wait() - self.play(FadeIn(Mobject(down_arrow, infinite_sum))) - self.wait() - -class SeeNumbersApproachOne(Scene): - def construct(self): - interval = zero_to_one_interval() - arrow = Arrow(INTERVAL_RADIUS*RIGHT, tail=ORIGIN).nudge() - arrow.shift(DOWN).set_color("yellow") - num_dots = 6 - colors = Color("green").range_to("yellow", num_dots) - dots = Mobject(*[ - Dot( - density = 2*DEFAULT_POINT_DENSITY_1D - ).scale(1+1.0/2.0**x).shift( - INTERVAL_RADIUS*RIGHT +\ - (INTERVAL_RADIUS/2.0**x)*LEFT - ).set_color(next(colors)) - for x in range(num_dots) - ]) - - self.add(interval) - self.play( - ShowCreation(arrow), - ShowCreation(dots), - run_time = 2.0 - ) - self.wait() - -class OneAndInfiniteSumAreTheSameThing(Scene): - def construct(self): - one, equals, inf_sum = TexMobject([ - "1", "=", "\\sum_{n=1}^\\infty \\frac{1}{2^n}" - ]).split() - point = Point(equals.get_center()).set_color("black") - - self.add(one.shift(LEFT)) - self.wait() - self.add(inf_sum.shift(RIGHT)) - self.wait() - self.play( - ApplyMethod(one.shift, RIGHT), - ApplyMethod(inf_sum.shift, LEFT), - CounterclockwiseTransform(point, equals) - ) - self.wait() - - -class HowDoYouDefineInfiniteSums(Scene): - def construct(self): - you = draw_you().center().rewire_part_attributes() - text = TextMobject( - ["How", " do", " you,\\\\", "\\emph{define}"], - size = "\\Huge" - ).shift(UP).split() - text[-1].shift(3*DOWN).set_color("skyblue") - sum_mob = TexMobject("\\sum_{n=0}^\\infty{a_n}") - text[-1].shift(LEFT) - sum_mob.shift(text[-1].get_center()+2*RIGHT) - - self.add(you) - self.wait() - for mob in text[:-1]: - self.add(mob) - self.wait(0.1) - self.play(BlinkPiCreature(you)) - self.wait() - self.add(text[-1]) - self.wait() - self.add(sum_mob) - self.wait() - - -class LessAboutNewThoughts(Scene): - def construct(self): - words = generating, new, thoughts, to, definitions = TextMobject([ - "Generating", " new", " thoughts", "$\\rightarrow$", - "useful definitions" - ], size = "\\large").split() - gen_cross = TexMobject("\\hline").set_color("red") - new_cross = deepcopy(gen_cross) - for cross, mob in [(gen_cross, generating), (new_cross, new)]: - cross.replace(mob) - cross.stretch_to_fit_height(0.03) - disecting = TextMobject("Disecting").set_color("green") - disecting.shift(generating.get_center() + 0.6*UP) - old = TextMobject("old").set_color("green") - old.shift(new.get_center()+0.6*UP) - - kwargs = {"run_time" : 0.25} - self.add(*words) - self.wait() - self.play(ShowCreation(gen_cross, **kwargs)) - self.play(ShowCreation(new_cross, **kwargs)) - self.wait() - self.add(disecting) - self.wait(0.5) - self.add(old) - self.wait() - -class ListOfPartialSums(Scene): - def construct(self): - all_terms = np.array(TexMobject( - ALT_PARTIAL_SUM_TEXT, - size = "\\large" - ).split()) - numbers, equals, sums = [ - all_terms[list(range(k, 12, 3))] - for k in (0, 1, 2) - ] - dots = all_terms[12] - one = all_terms[-3] - last_equal = all_terms[-2] - infinite_sum = all_terms[-1] - - self.count( - numbers, - mode = "show", - display_numbers = False, - run_time = 1.0 - ) - self.play(ShowCreation(dots)) - self.wait() - self.play( - FadeIn(Mobject(*equals)), - *[ - Transform(deepcopy(number), finite_sum) - for number, finite_sum in zip(numbers, sums) - ] - ) - self.wait() - self.play(*[ - ApplyMethod(s.set_color, "yellow", rate_func = there_and_back) - for s in sums - ]) - self.wait() - self.add(one.set_color("green")) - self.wait() - - -class ShowDecreasingDistance(Scene): - args_list = [(1,), (2,)] - @staticmethod - def args_to_string(num): - return str(num) - - def construct(self, num): - number_line = NumberLine(interval_size = 1).add_numbers() - vert_line0 = Line(0.5*UP+RIGHT, UP+RIGHT) - vert_line1 = Line(0.5*UP+2*num*RIGHT, UP+2*num*RIGHT) - horiz_line = Line(vert_line0.end, vert_line1.end) - lines = [vert_line0, vert_line1, horiz_line] - for line in lines: - line.set_color("green") - dots = Mobject(*[ - Dot().scale(1.0/(n+1)).shift((1+partial_sum(n))*RIGHT) - for n in range(10) - ]) - - self.add(dots.split()[0]) - self.add(number_line, *lines) - self.wait() - self.play( - ApplyMethod(vert_line0.shift, RIGHT), - Transform( - horiz_line, - Line(vert_line0.end+RIGHT, vert_line1.end).set_color("green") - ), - ShowCreation(dots), - run_time = 2.5 - ) - self.wait() - -class CircleZoomInOnOne(Scene): - def construct(self): - number_line = NumberLine(interval_size = 1).add_numbers() - dots = Mobject(*[ - Dot().scale(1.0/(n+1)).shift((1+partial_sum(n))*RIGHT) - for n in range(10) - ]) - circle = Circle().shift(2*RIGHT) - text = TextMobject( - "All but finitely many dots fall inside even the tiniest circle." - ) - numbers = [TexMobject("\\frac{1}{%s}"%s) for s in ["100", "1,000,000", "g_{g_{64}}"]] - for num in numbers + [text]: - num.shift(2*UP) - num.sort_points(lambda p : np.dot(p, DOWN+RIGHT)) - curr_num = numbers[0] - arrow = Arrow(2*RIGHT, direction = 1.5*(DOWN+RIGHT)).nudge() - - self.add(number_line, dots) - self.play( - Transform(circle, Point(2*RIGHT).set_color("white")), - run_time = 5.0 - ) - - self.play(*[ - DelayByOrder(FadeIn(mob)) - for mob in (arrow, curr_num) - ]) - self.wait() - for num in numbers[1:] + [text]: - curr_num.points = np.array(list(reversed(curr_num.points))) - self.play( - ShowCreation( - curr_num, - rate_func = lambda t : smooth(1-t) - ), - ShowCreation(num) - ) - self.remove(curr_num) - curr_num = num - self.wait() - -class ZoomInOnOne(Scene): - def construct(self): - num_iterations = 8 - number_line = NumberLine(interval_size = 1, radius = FRAME_X_RADIUS+2) - number_line.filter_out(lambda x_y_z2:abs(x_y_z2[1])>0.1) - nl_with_nums = deepcopy(number_line).add_numbers() - self.play(ApplyMethod(nl_with_nums.shift, 2*LEFT)) - zero, one, two = [ - TexMobject(str(n)).scale(0.5).shift(0.4*DOWN+2*(-1+n)*RIGHT) - for n in (0, 1, 2) - ] - self.play( - FadeOut(nl_with_nums), - *[Animation(mob) for mob in (zero, one, two, number_line)] - ) - self.remove(nl_with_nums, number_line, zero, two) - powers_of_10 = [10**(-n) for n in range(num_iterations+1)] - number_pairs = [(1-epsilon, 1+epsilon) for epsilon in powers_of_10] - for count in range(num_iterations): - self.zoom_with_numbers(number_pairs[count], number_pairs[count+1]) - self.clear() - self.add(one) - - def zoom_with_numbers(self, numbers, next_numbers): - all_numbers = [TexMobject(str(n_u[0])).scale(0.5).shift(0.4*DOWN+2*n_u[1]*RIGHT) for n_u in zip(numbers+next_numbers, it.cycle([-1, 1]))] - - num_levels = 3 - scale_factor = 10 - number_lines = [ - NumberLine( - interval_size = 1, - density = scale_factor*DEFAULT_POINT_DENSITY_1D - ).filter_out( - lambda x_y_z:abs(x_y_z[1])>0.1 - ).scale(1.0/scale_factor**x) - for x in range(num_levels) - ] - kwargs = {"rate_func" : None} - self.play(*[ - ApplyMethod(number_lines[x].scale, scale_factor, **kwargs) - for x in range(1, num_levels) - ]+[ - ApplyMethod(number_lines[0].stretch, scale_factor, 0, **kwargs), - ]+[ - ApplyMethod( - all_numbers[i].shift, - 2*LEFT*(scale_factor-1)*(-1)**i, - **kwargs - ) - for i in (0, 1) - ]+[ - Transform(Point(0.4*DOWN + u*0.2*RIGHT), num, **kwargs) - for u, num in zip([-1, 1], all_numbers[2:]) - ]) - self.remove(*all_numbers) - - -class DefineInfiniteSum(Scene): - def construct(self): - self.put_expression_in_corner() - self.list_partial_sums() - self.wait() - - def put_expression_in_corner(self): - buff = 0.24 - define, infinite_sum = TexMobject([ - "\\text{\\emph{Define} }", - "\\sum_{n = 0}^\\infty a_n = X" - ]).split() - define.set_color("skyblue") - expression = Mobject(define, infinite_sum) - - self.add(expression) - self.wait() - self.play(ApplyFunction( - lambda mob : mob.scale(0.5).to_corner(UP+LEFT, buff = buff), - expression - )) - bottom = (min(expression.points[:,1]) - buff)*UP - side = (max(expression.points[:,0]) + buff)*RIGHT - lines = [ - Line(FRAME_X_RADIUS*LEFT+bottom, side+bottom), - Line(FRAME_Y_RADIUS*UP+side, side+bottom) - ] - self.play(*[ - ShowCreation(line.set_color("white")) - for line in lines - ]) - self.wait() - - def list_partial_sums(self): - num_terms = 10 - term_strings = reduce(op.add, [ - [ - "s_%d"%n, - "&=", - "+".join(["a_%d"%k for k in range(n+1)])+"\\\\" - ] - for n in range(num_terms) - ]) - terms = TexMobject(term_strings, size = "\\large").split() - number_line = NumberLine() - ex_point = 2*RIGHT - ex = TexMobject("X").shift(ex_point + LEFT + UP) - arrow = Arrow(ex_point, tail = ex.points[-1]).nudge() - - for term, count in zip(terms, it.count()): - self.add(term) - self.wait(0.1) - if count % 3 == 2: - self.wait(0.5) - self.wait() - esses = np.array(terms)[list(range(0, len(terms), 3))] - other_terms = [m for m in terms if m not in esses] - self.play(*[ - ApplyMethod(ess.set_color, "yellow") - for ess in esses - ]) - - def move_s(s, n): - s.center() - s.scale(1.0/(n+1)) - s.shift(ex_point-RIGHT*2.0/2**n) - return s - self.play(*[ - FadeOut(term) - for term in other_terms - ]+[ - ApplyFunction(lambda s : move_s(s, n), ess) - for ess, n in zip(esses, it.count()) - ]+[ - FadeIn(number_line), - FadeIn(ex), - FadeIn(arrow) - ]) - - lines = [ - Line(x+0.25*DOWN, x+0.25*UP).set_color("white") - for y in [-1, -0.01, 1, 0.01] - for x in [ex_point+y*RIGHT] - ] - self.play(*[ - Transform(lines[x], lines[x+1], run_time = 3.0) - for x in (0, 2) - ]) - - -class YouJustInventedSomeMath(Scene): - def construct(self): - text = TextMobject([ - "You ", "just ", "invented\\\\", "some ", "math" - ]).split() - for mob in text[:3]: - mob.shift(UP) - for mob in text[3:]: - mob.shift(1.3*DOWN) - # you = draw_you().center().rewire_part_attributes() - # smile = PiCreature().mouth.center().shift(you.mouth.get_center()) - you = PiCreature().set_color("grey") - you.center().rewire_part_attributes() - - self.add(you) - for mob in text: - self.add(mob) - self.wait(0.2) - self.play(WaveArm(you)) - self.play(BlinkPiCreature(you)) - - - -class SeekMoreGeneralTruths(Scene): - def construct(self): - summands = [ - "\\frac{1}{3^n}", - "2^n", - "\\frac{1}{n^2}", - "\\frac{(-1)^n}{n}", - "\\frac{(-1)^n}{(2n)!}", - "\\frac{2\sqrt{2}}{99^2}\\frac{(4n)!}{(n!)^4} \\cdot \\frac{26390n + 1103}{396^{4k}}", - ] - sums = TexMobject([ - "&\\sum_{n = 0}^\\infty" + summand + "= ? \\\\" - for summand in summands - ], size = "") - sums.stretch_to_fit_height(FRAME_HEIGHT-1) - sums.shift((FRAME_Y_RADIUS-0.5-max(sums.points[:,1]))*UP) - - for qsum in sums.split(): - qsum.sort_points(lambda p : np.dot(p, DOWN+RIGHT)) - self.play(DelayByOrder(FadeIn(qsum))) - self.wait(0.5) - self.wait() - -class ChopIntervalInProportions(Scene): - args_list = [("9", ), ("p", )] - @staticmethod - def args_to_string(*args): - return args[0] - - def construct(self, mode): - if mode == "9": - prop = 0.1 - num_terms = 2 - left_terms, right_terms = [ - [ - TexMobject("\\frac{%d}{%d}"%(k, (10**(count+1)))) - for count in range(num_terms) - ] - for k in (9, 1) - ] - if mode == "p": - num_terms = 4 - prop = 0.7 - left_terms = list(map(TexMobject, ["(1-p)", ["p","(1-p)"]]+[ - ["p^%d"%(count), "(1-p)"] - for count in range(2, num_terms) - ])) - right_terms = list(map(TexMobject, ["p"] + [ - ["p", "^%d"%(count+1)] - for count in range(1, num_terms) - ])) - interval = zero_to_one_interval() - left = INTERVAL_RADIUS*LEFT - right = INTERVAL_RADIUS*RIGHT - left_paren = TexMobject("(") - right_paren = TexMobject(")").shift(right + 1.1*UP) - curr = left.astype("float") - brace_to_replace = None - term_to_replace = None - - self.add(interval) - additional_anims = [] - for lt, rt, count in zip(left_terms, right_terms, it.count()): - last = deepcopy(curr) - curr += 2*RIGHT*INTERVAL_RADIUS*(1-prop)*(prop**count) - braces = [ - Underbrace(a, b).rotate(np.pi, RIGHT) - for a, b in [(last, curr), (curr, right)] - ] - for term, brace, count2 in zip([lt, rt], braces, it.count()): - term.scale(0.75) - term.shift(brace.get_center()+UP) - if term.get_width() > brace.get_width(): - term.shift(UP+1.5*(count-2)*RIGHT) - arrow = Arrow( - brace.get_center()+0.3*UP, - tail = term.get_center()+0.5*DOWN - ) - arrow.points = np.array(list(reversed(arrow.points))) - additional_anims = [ShowCreation(arrow)] - if brace_to_replace is not None: - if mode == "p": - lt, rt = lt.split(), rt.split() - if count == 1: - new_term_to_replace = deepcopy(term_to_replace) - new_term_to_replace.center().shift(last+UP+0.3*LEFT) - left_paren.center().shift(last+1.1*UP) - self.play( - FadeIn(lt[1]), - FadeIn(rt[0]), - Transform( - brace_to_replace.repeat(2), - Mobject(*braces) - ), - FadeIn(left_paren), - FadeIn(right_paren), - Transform(term_to_replace, new_term_to_replace), - *additional_anims - ) - self.wait() - self.play( - Transform( - term_to_replace, - Mobject(lt[0], rt[1]) - ), - FadeOut(left_paren), - FadeOut(right_paren) - ) - self.remove(left_paren, right_paren) - else: - self.play( - FadeIn(lt[1]), - FadeIn(rt[0]), - Transform( - brace_to_replace.repeat(2), - Mobject(*braces) - ), - Transform( - term_to_replace, - Mobject(lt[0], rt[1]) - ), - *additional_anims - ) - self.remove(*lt+rt) - lt, rt = Mobject(*lt), Mobject(*rt) - self.add(lt, rt) - else: - self.play( - Transform( - brace_to_replace.repeat(2), - Mobject(*braces) - ), - Transform( - term_to_replace, - Mobject(lt, rt) - ), - *additional_anims - ) - self.remove(brace_to_replace, term_to_replace) - self.add(lt, rt, *braces) - else: - self.play(*[ - FadeIn(mob) - for mob in braces + [lt, rt] - ] + additional_anims) - self.wait() - brace_to_replace = braces[1] - term_to_replace = rt - if mode == "9": - split_100 = TexMobject("\\frac{9}{1000}+\\frac{1}{1000}") - split_100.scale(0.5) - split_100.shift(right_terms[-1].get_center()) - split_100.to_edge(RIGHT) - split_100.sort_points() - right_terms[-1].sort_points() - self.play(Transform( - right_terms[-1], split_100 - )) - self.wait() - - - -class GeometricSum(RearrangeEquation): - def construct(self): - start_terms = "(1-p) + p (1-p) + p^2 (1-p) + p^3 (1-p) + \\cdots = 1" - end_terms = "1 + p + p^2 + p^3 + \\cdots = \\frac{1}{(1-p)}" - index_map = { - 0 : 0, - # 0 : -1, #(1-p) - 1 : 1, #+ - 2 : 2, #p - # 3 : -1, #(1-p) - 4 : 3, #+ - 5 : 4, #p^2 - # 6 : -1, #(1-p) - 7 : 5, #+ - 8 : 6, #p^3 - # 9 : -1, #(1-p) - 10: 7, #+ - 11: 8, #\\cdots - 12: 9, #= - 13: 10, #1 - } - def start_transform(mob): - return mob.scale(1.3).shift(2*UP) - def end_transform(mob): - return mob.scale(1.3).shift(DOWN) - - RearrangeEquation.construct( - self, - start_terms.split(" "), end_terms.split(" "), - index_map, size = "\\large", - path = counterclockwise_path(), - start_transform = start_transform, - end_transform = end_transform, - leave_start_terms = True, - transform_kwargs = {"run_time" : 2.0} - ) - -class PointNineRepeating(RearrangeEquation): - def construct(self): - start_terms = [ - "\\frac{9}{10}", - "+", - "\\frac{9}{100}", - "+", - "\\frac{9}{1000}", - "+", - "\\cdots=1", - ] - end_terms = "0 . 9 9 9 \\cdots=1".split(" ") - index_map = { - 0 : 2, - 2 : 3, - 4 : 4, - 6 : 5, - } - for term in TexMobject(start_terms).split(): - self.add(term) - self.wait(0.5) - self.clear() - RearrangeEquation.construct( - self, - start_terms, - end_terms, - index_map, - path = straight_path - ) - - -class PlugNumbersIntoRightside(Scene): - def construct(self): - scale_factor = 1.5 - lhs, rhs = TexMobject( - ["1 + p + p^2 + p^3 + \\cdots = ", "\\frac{1}{1-p}"], - size = "\\large" - ).scale(scale_factor).split() - rhs = TexMobject( - ["1 \\over 1 - ", "p"], - size = "\\large" - ).replace(rhs).split() - num_strings = [ - "0.5", "3", "\pi", "(-1)", "3.7", "2", - "0.2", "27", "i" - ] - nums = [ - TexMobject(num_string, size="\\large") - for num_string in num_strings - ] - for num, num_string in zip(nums, num_strings): - num.scale(scale_factor) - num.shift(rhs[1].get_center()) - num.shift(0.1*RIGHT + 0.08*UP) - num.set_color("green") - if num_string == "(-1)": - num.shift(0.3*RIGHT) - right_words = TextMobject( - "This side makes sense for almost any value of $p$," - ).shift(2*UP) - left_words = TextMobject( - "even if it seems like this side will not." - ).shift(2*DOWN) - right_words.add(Arrow( - rhs[0].get_center(), - tail = right_words.get_center()+DOWN+RIGHT - ).nudge(0.5)) - left_words.add(Arrow( - lhs.get_center() + 0.3*DOWN, - tail = left_words.get_center() + 0.3*UP - )) - right_words.set_color("green") - left_words.set_color("yellow") - - - self.add(lhs, *rhs) - self.wait() - self.play(FadeIn(right_words)) - curr = rhs[1] - for num, count in zip(nums, it.count()): - self.play(CounterclockwiseTransform(curr, num)) - self.wait() - if count == 2: - self.play(FadeIn(left_words)) - - -class PlugInNegativeOne(RearrangeEquation): - def construct(self): - num_written_terms = 4 - start_terms = reduce(op.add, [ - ["(-", "1", ")^%d"%n, "+"] - for n in range(num_written_terms) - ]) + ["\\cdots=", "\\frac{1}{1-(-1)}"] - end_terms = "1 - 1 + 1 - 1 + \\cdots= \\frac{1}{2}".split(" ") - index_map = dict([ - (4*n + 1, 2*n) - for n in range(num_written_terms) - ]+[ - (4*n + 3, 2*n + 1) - for n in range(num_written_terms) - ]) - index_map[-2] = -2 - index_map[-1] = -1 - RearrangeEquation.construct( - self, - start_terms, - end_terms, - index_map, - path = straight_path, - start_transform = lambda m : m.shift(2*UP), - leave_start_terms = True, - ) - -class PlugInTwo(RearrangeEquation): - def construct(self): - num_written_terms = 4 - start_terms = reduce(op.add, [ - ["2", "^%d"%n, "+"] - for n in range(num_written_terms) - ]) + ["\\cdots=", "\\frac{1}{1-2}"] - end_terms = "1 + 2 + 4 + 8 + \\cdots= -1".split(" ") - index_map = dict([ - (3*n, 2*n) - for n in range(num_written_terms) - ]+[ - (3*n + 2, 2*n + 1) - for n in range(num_written_terms) - ]) - index_map[-2] = -2 - index_map[-1] = -1 - RearrangeEquation.construct( - self, - start_terms, - end_terms, - index_map, - size = "\\Huge", - path = straight_path, - start_transform = lambda m : m.shift(2*UP), - leave_start_terms = True, - ) - -class ListPartialDivergentSums(Scene): - args_list = [ - ( - lambda n : "1" if n%2 == 0 else "(-1)", - lambda n : "1" if n%2 == 0 else "0" - ), - ( - lambda n : "2^%d"%n if n > 1 else ("1" if n==0 else "2"), - lambda n : str(2**(n+1)-1) - ) - ] - @staticmethod - def args_to_string(*args): - if args[0](1) == "(-1)": - return "Negative1" - else: - return args[0](1) - def construct(self, term_func, partial_sum_func): - num_lines = 8 - rhss = [ - partial_sum_func(n) - for n in range(num_lines) - ] - lhss = [ - "&=" + \ - "+".join([term_func(k) for k in range(n+1)]) + \ - "\\\\" - for n in range(num_lines) - ] - terms = TexMobject( - list(it.chain.from_iterable(list(zip(rhss, lhss)))) + ["\\vdots&", ""], - size = "\\large" - ).shift(RIGHT).split() - words = TextMobject("These numbers don't \\\\ approach anything") - words.to_edge(LEFT) - arrow = Arrow(3*DOWN+2*LEFT, direction = DOWN, length = 6) - - for x in range(0, len(terms), 2): - self.play(FadeIn(terms[x]), FadeIn(terms[x+1])) - self.play(FadeIn(words), ShowCreation(arrow)) - for x in range(0, len(terms), 2): - self.play( - ApplyMethod(terms[x].set_color, "green"), - run_time = 0.1 - ) - self.wait() - -class NotARobot(Scene): - def construct(self): - you = draw_you().center() - top_words = TextMobject("You are a mathematician,") - low_words = TextMobject("not a robot.") - top_words.shift(1.5*UP) - low_words.shift(1.5*DOWN) - - self.add(you) - self.play(ShimmerIn(top_words)) - self.play(ShimmerIn(low_words)) - - -class SumPowersOfTwoAnimation(Scene): - def construct(self): - iterations = 5 - dot = Dot(density = 3*DEFAULT_POINT_DENSITY_1D).scale(1.5) - dot_width = dot.get_width()*RIGHT - dot_buff = 0.2*RIGHT - left = (FRAME_X_RADIUS-1)*LEFT - right = left + 2*dot_width + dot_buff - top_brace_left = left+dot_width+dot_buff+0.3*DOWN - bottom_brace_left = left + 0.3*DOWN - circle = Circle().scale(dot_width[0]/2).shift(left+dot_width/2) - curr_dots = deepcopy(dot).shift(left+1.5*dot_width+dot_buff) - topbrace = Underbrace(top_brace_left, right).rotate(np.pi, RIGHT) - bottombrace = Underbrace(bottom_brace_left, right) - colors = Color("yellow").range_to("purple", iterations) - curr_dots.set_color(next(colors)) - equation = TexMobject( - "1+2+4+\\cdots+2^n=2^{n+1} - 1", - size = "\\Huge" - ).shift(3*UP) - full_top_sum = TexMobject(["1", "+2", "+4", "+8", "+16"]).split() - - self.add(equation) - self.wait() - self.add(circle, curr_dots, topbrace, bottombrace) - for n in range(1,iterations): - bottom_num = TexMobject(str(2**n)) - new_bottom_num = TexMobject(str(2**(n+1))) - bottom_num.shift(bottombrace.get_center()+0.5*DOWN) - - top_sum = Mobject(*full_top_sum[:n]).center() - top_sum_end = deepcopy(full_top_sum[n]).center() - top_sum.shift(topbrace.get_center()+0.5*UP) - new_top_sum = Mobject(*full_top_sum[:(n+1)]).center() - self.add(top_sum, bottom_num) - - if n == iterations: - continue - new_dot = deepcopy(dot).shift(circle.get_center()) - shift_val = (2**n)*(dot_width+dot_buff) - right += shift_val - new_dots = Mobject(new_dot, curr_dots) - new_dots.set_color(next(colors)).shift(shift_val) - alt_bottombrace = deepcopy(bottombrace).shift(shift_val) - alt_bottom_num = deepcopy(bottom_num).shift(shift_val) - alt_topbrace = deepcopy(alt_bottombrace).rotate(np.pi, RIGHT) - top_sum_end.shift(alt_topbrace.get_center()+0.5*UP) - new_topbrace = Underbrace(top_brace_left, right).rotate(np.pi, RIGHT) - new_bottombrace = Underbrace(bottom_brace_left, right) - new_bottom_num.shift(new_bottombrace.get_center()+0.5*DOWN) - new_top_sum.shift(new_topbrace.get_center()+0.5*UP) - for exp, brace in [ - (top_sum, topbrace), - (top_sum_end, alt_topbrace), - (new_top_sum, new_topbrace), - ]: - if exp.get_width() > brace.get_width(): - exp.stretch_to_fit_width(brace.get_width()) - new_top_sum = new_top_sum.split() - new_top_sum_start = Mobject(*new_top_sum[:-1]) - new_top_sum_end = new_top_sum[-1] - - self.wait() - self.play(*[ - FadeIn(mob) - for mob in [ - new_dots, - alt_topbrace, - alt_bottombrace, - top_sum_end, - alt_bottom_num, - ] - ]) - self.wait() - self.play( - Transform(topbrace, new_topbrace), - Transform(alt_topbrace, new_topbrace), - Transform(bottombrace, new_bottombrace), - Transform(alt_bottombrace, new_bottombrace), - Transform(bottom_num, new_bottom_num), - Transform(alt_bottom_num, new_bottom_num), - Transform(top_sum, new_top_sum_start), - Transform(top_sum_end, new_top_sum_end) - ) - self.remove( - bottom_num, alt_bottom_num, top_sum, - top_sum_end, new_top_sum_end, - alt_topbrace, alt_bottombrace - ) - curr_dots = Mobject(curr_dots, new_dots) - - -class PretendTheyDoApproachNegativeOne(RearrangeEquation): - def construct(self): - num_lines = 6 - da = "\\downarrow" - columns = [ - TexMobject("\\\\".join([ - n_func(n) - for n in range(num_lines) - ]+last_bits), size = "\\Large").to_corner(UP+LEFT) - for n_func, last_bits in [ - (lambda n : str(2**(n+1)-1), ["\\vdots", da, "-1"]), - (lambda n : "+1", ["", "", "+1"]), - (lambda n : "=", ["", "", "="]), - (lambda n : str(2**(n+1)), ["\\vdots", da, "0"]), - ] - ] - columns[-1].set_color() - columns[2].shift(0.2*DOWN) - shift_val = 3*RIGHT - for column in columns: - column.shift(shift_val) - shift_val = shift_val + (column.get_width()+0.2)*RIGHT - self.play(ShimmerIn(columns[0])) - self.wait() - self.add(columns[1]) - self.wait() - self.play( - DelayByOrder(Transform(deepcopy(columns[0]), columns[-1])), - FadeIn(columns[2]) - ) - self.wait() - -class DistanceBetweenRationalNumbers(Scene): - def construct(self): - locii = [2*LEFT, 2*RIGHT] - nums = [ - TexMobject(s).shift(1.3*d) - for s, d in zip(["\\frac{1}{2}", "3"], locii) - ] - arrows = [ - Arrow(direction, tail = ORIGIN) - for direction in locii - ] - dist = TexMobject("\\frac{5}{2}").scale(0.5).shift(0.5*UP) - text = TextMobject("How we define distance between rational numbers") - text.to_edge(UP) - self.add(text, *nums) - self.play(*[ShowCreation(arrow) for arrow in arrows]) - self.play(ShimmerIn(dist)) - self.wait() - -class NotTheOnlyWayToOrganize(Scene): - def construct(self): - self.play(ShowCreation(NumberLine().add_numbers())) - self.wait() - words = "Is there any other reasonable way to organize numbers?" - self.play(FadeIn(TextMobject(words).shift(2*UP))) - self.wait() - -class DistanceIsAFunction(Scene): - args_list = [ - ("Euclidian",), - ("Random",), - ("2adic",), - ] - @staticmethod - def args_to_string(word): - return word - - def construct(self, mode): - if mode == "Euclidian": - dist_text = "dist" - elif mode == "Random": - dist_text = "random\\_dist" - elif mode == "2adic": - dist_text = "2\\_adic\\_dist" - dist, r_paren, arg0, comma, arg1, l_paren, equals, result = TextMobject([ - dist_text, "(", "000", ",", "000", ")", "=", "000" - ]).split() - point_origin = comma.get_center()+0.2*UP - if mode == "Random": - examples = [ - ("2", "3", "7"), - ("\\frac{1}{2}", "100", "\\frac{4}{5}"), - ] - dist.set_color("orange") - self.add(dist) - self.wait() - elif mode == "Euclidian": - examples = [ - ("1", "5", "4"), - ("\\frac{1}{2}", "3", "\\frac{5}{2}"), - ("-3", "3", "6"), - ("2", "3", "1"), - ("0", "4", "x"), - ("1", "5", "x"), - ("2", "6", "x"), - ] - elif mode == "2adic": - examples = [ - ("2", "0", "\\frac{1}{2}"), - ("-1", "15", "\\frac{1}{16}"), - ("3", "7", "\\frac{1}{4}"), - ("\\frac{3}{2}", "1", "2"), - ] - dist.set_color("green") - self.add(dist) - self.wait() - example_mobs = [ - ( - TexMobject(tup[0]).shift(arg0.get_center()), - TexMobject(tup[1]).shift(arg1.get_center()), - TexMobject(tup[2]).shift(result.get_center()) - ) - for tup in examples - ] - - self.add(dist, r_paren, comma, l_paren, equals) - previous = None - kwargs = {"run_time" : 0.5} - for mobs in example_mobs: - if previous: - self.play(*[ - DelayByOrder(Transform(prev, mob, **kwargs)) - for prev, mob in zip(previous, mobs)[:-1] - ]) - self.play(DelayByOrder(Transform( - previous[-1], mobs[-1], **kwargs - ))) - self.remove(*previous) - self.add(*mobs) - previous = mobs - self.wait() - -class ShiftInvarianceNumberLine(Scene): - def construct(self): - number_line = NumberLine().add_numbers() - topbrace = Underbrace(ORIGIN, 2*RIGHT).rotate(np.pi, RIGHT) - dist0 = TextMobject(["dist(", "$0$", ",", "$2$",")"]) - dist1 = TextMobject(["dist(", "$2$", ",", "$4$",")"]) - for dist in dist0, dist1: - dist.shift(topbrace.get_center()+0.3*UP) - dist1.shift(2*RIGHT) - footnote = TextMobject(""" - \\begin{flushleft} - *yeah yeah, I know I'm still drawing them on a line, - but until a few minutes from now I have no other way - to draw them - \\end{flushright} - """).scale(0.5).to_corner(DOWN+RIGHT) - - self.add(number_line, topbrace, dist0, footnote) - self.wait() - self.remove(dist0) - self.play( - ApplyMethod(topbrace.shift, 2*RIGHT), - *[ - Transform(*pair) - for pair in zip(dist0.split(), dist1.split()) - ] - ) - self.wait() - -class NameShiftInvarianceProperty(Scene): - def construct(self): - prop = TextMobject([ - "dist($A$, $B$) = dist(", - "$A+x$, $B+x$", - ") \\quad for all $x$" - ]) - mid_part = prop.split()[1] - u_brace = Underbrace( - mid_part.get_boundary_point(DOWN+LEFT), - mid_part.get_boundary_point(DOWN+RIGHT) - ).shift(0.3*DOWN) - label = TextMobject("Shifted values") - label.shift(u_brace.get_center()+0.5*DOWN) - name = TextMobject("``Shift Invariance''") - name.set_color("green").to_edge(UP) - for mob in u_brace, label: - mob.set_color("yellow") - - self.add(prop) - self.play(ShimmerIn(label), ShimmerIn(u_brace)) - self.wait() - self.play(ShimmerIn(name)) - self.wait() - - -class TriangleInequality(Scene): - def construct(self): - symbols = ["A", "B", "C"] - locations = [2*(DOWN+LEFT), UP, 4*RIGHT] - ab, plus, bc, greater_than, ac = TextMobject([ - "dist($A$, $B$)", - "$+$", - "dist($B$, $C$)", - "$>$", - "dist($A$, $C$)", - ]).to_edge(UP).split() - all_dists = [ab, ac, bc] - ab_line, ac_line, bc_line = all_lines = [ - Line(*pair).scale_in_place(0.8) - for pair in it.combinations(locations, 2) - ] - def put_on_line(mob, line): - result = deepcopy(mob).center().scale(0.5) - result.rotate(np.arctan(line.get_slope())) - result.shift(line.get_center()+0.2*UP) - return result - ab_copy, ac_copy, bc_copy = all_copies = [ - put_on_line(dist, line) - for dist, line in zip(all_dists, all_lines) - ] - for symbol, loc in zip(symbols, locations): - self.add(TexMobject(symbol).shift(loc)) - self.play(ShowCreation(ac_line), FadeIn(ac_copy)) - self.wait() - self.play(*[ - ShowCreation(line) for line in (ab_line, bc_line) - ]+[ - FadeIn(dist) for dist in (ab_copy, bc_copy) - ]) - self.wait() - self.play(*[ - Transform(*pair) - for pair in zip(all_copies, all_dists) - ]+[ - FadeIn(mob) - for mob in (plus, greater_than) - ]) - self.wait() - - - -class StruggleToFindFrameOfMind(Scene): - def construct(self): - you, bubble = draw_you(with_bubble = True) - questions = TextMobject("???", size = "\\Huge").scale(1.5) - contents = [ - TexMobject("2, 4, 8, 16, 32, \\dots \\rightarrow 0"), - TextMobject("dist(0, 2) $<$ dist(0, 64)"), - NumberLine().sort_points(lambda p : -p[1]).add( - TextMobject("Not on a line?").shift(UP) - ), - ] - kwargs = {"run_time" : 0.5} - self.add(you, bubble) - bubble.add_content(questions) - for mob in contents: - curr = bubble.content - self.remove(curr) - bubble.add_content(mob) - for first, second in [(curr, questions), (questions, mob)]: - copy = deepcopy(first) - self.play(DelayByOrder(Transform( - copy, second, **kwargs - ))) - self.remove(copy) - self.add(mob) - self.wait() - - -class RoomsAndSubrooms(Scene): - def construct(self): - colors = get_room_colors() - a_set = [3*RIGHT, 3*LEFT] - b_set = [1.5*UP, 1.5*DOWN] - c_set = [LEFT, RIGHT] - rectangle_groups = [ - [Rectangle(7, 12).set_color(colors[0])], - [ - Rectangle(6, 5).shift(a).set_color(colors[1]) - for a in a_set - ], - [ - Rectangle(2, 4).shift(a + b).set_color(colors[2]) - for a in a_set - for b in b_set - ], - [ - Rectangle(1, 1).shift(a+b+c).set_color(colors[3]) - for a in a_set - for b in b_set - for c in c_set - ] - ] - - for group in rectangle_groups: - mob = Mobject(*group) - mob.sort_points(get_norm) - self.play(ShowCreation(mob)) - - self.wait() - - -class RoomsAndSubroomsWithNumbers(Scene): - def construct(self): - zero_local = (FRAME_X_RADIUS-0.5)*LEFT - zero_one_width = FRAME_X_RADIUS-0.3 - - zero, power_mobs = self.draw_numbers(zero_local, zero_one_width) - self.wait() - rectangles = self.draw_first_rectangles(zero_one_width) - rect_clusters = self.draw_remaining_rectangles(rectangles) - self.adjust_power_mobs(zero, power_mobs, rect_clusters[-1]) - self.wait() - num_mobs = self.draw_remaining_numbers( - zero, power_mobs, rect_clusters - ) - self.wait() - self.add_negative_one(num_mobs) - self.wait() - self.show_distances(num_mobs, rect_clusters) - - - def draw_numbers(self, zero_local, zero_one_width): - num_numbers = 5 - zero = TexMobject("0").shift(zero_local) - self.add(zero) - nums = [] - for n in range(num_numbers): - num = TexMobject(str(2**n)) - num.scale(1.0/(n+1)) - num.shift( - zero_local+\ - RIGHT*zero_one_width/(2.0**n)+\ - LEFT*0.05*n+\ - (0.4*RIGHT if n == 0 else ORIGIN) #Stupid - ) - self.play(FadeIn(num, run_time = 0.5)) - nums.append(num) - return zero, nums - - def draw_first_rectangles(self, zero_one_width): - side_buff = 0.05 - upper_buff = 0.5 - colors = get_room_colors() - rectangles = [] - for n in range(4): - rect = Rectangle( - FRAME_HEIGHT-(n+2)*upper_buff, - zero_one_width/(2**n)-0.85*(n+1)*side_buff - ) - rect.sort_points(get_norm) - rect.to_edge(LEFT, buff = 0.2).shift(n*side_buff*RIGHT) - rect.set_color(colors[n]) - rectangles.append(rect) - for rect in rectangles: - self.play(ShowCreation(rect)) - self.wait() - return rectangles - - def draw_remaining_rectangles(self, rectangles): - clusters = [] - centers = [ORIGIN] + list(map(Mobject.get_center, rectangles)) - shift_vals = [ - 2*(c2 - c1)[0]*RIGHT - for c1, c2 in zip(centers[1:], centers) - ] - for rectangle, count in zip(rectangles, it.count(1)): - cluster = [rectangle] - for shift_val in shift_vals[:count]: - cluster += [mob.shift(shift_val) for mob in deepcopy(cluster)] - clusters.append(cluster) - for rect in cluster[1:]: - self.play(FadeIn(rect, run_time = 0.6**(count-1))) - return clusters - - def adjust_power_mobs(self, zero, power_mobs, small_rects): - new_zero = deepcopy(zero) - self.center_in_closest_rect(new_zero, small_rects) - new_power_mobs = deepcopy(power_mobs) - for mob, count in zip(new_power_mobs, it.count(1)): - self.center_in_closest_rect(mob, small_rects) - new_power_mobs[-1].shift(DOWN) - dots = TexMobject("\\vdots") - dots.scale(0.5).shift(new_zero.get_center()+0.5*DOWN) - self.play( - Transform(zero, new_zero), - FadeIn(dots), - *[ - Transform(old, new) - for old, new in zip(power_mobs, new_power_mobs) - ] - ) - - def draw_remaining_numbers(self, zero, power_mobs, rect_clusters): - small_rects = rect_clusters[-1] - max_width = 0.8*small_rects[0].get_width() - max_power = 4 - num_mobs = [None]*(2**max_power + 1) - num_mobs[0] = zero - powers = [2**k for k in range(max_power+1)] - for p, index in zip(powers, it.count()): - num_mobs[p] = power_mobs[index] - for power, count in zip(powers[1:-1], it.count(1)): - zero_copy = deepcopy(zero) - power_mob_copy = deepcopy(num_mobs[power]) - def transform(mob): - self.center_in_closest_rect(mob, small_rects) - mob.shift(UP) - return mob - self.play(*[ - ApplyFunction(transform, mob) - for mob in (zero_copy, power_mob_copy) - ]) - last_left_mob = zero - for n in range(power+1, 2*power): - left_mob = num_mobs[n-power] - shift_val = left_mob.get_center()-last_left_mob.get_center() - self.play(*[ - ApplyMethod(mob.shift, shift_val) - for mob in (zero_copy, power_mob_copy) - ]) - num_mobs[n] = TexMobject(str(n)) - num_mobs[n].scale(1.0/(power_of_divisor(n, 2)+1)) - width_ratio = max_width / num_mobs[n].get_width() - if width_ratio < 1: - num_mobs[n].scale(width_ratio) - num_mobs[n].shift(power_mob_copy.get_center()+DOWN) - self.center_in_closest_rect(num_mobs[n], small_rects) - point = Point(power_mob_copy.get_center()) - self.play(Transform(point, num_mobs[n])) - self.remove(point) - self.add(num_mobs[n]) - last_left_mob = left_mob - self.remove(zero_copy, power_mob_copy) - self.wait() - return num_mobs - - @staticmethod - def center_in_closest_rect(mobject, rectangles): - center = mobject.get_center() - diffs = [r.get_center()-center for r in rectangles] - mobject.shift(diffs[np.argmin(list(map(get_norm, diffs)))]) - - def add_negative_one(self, num_mobs): - neg_one = TexMobject("-1").scale(0.5) - shift_val = num_mobs[15].get_center()-neg_one.get_center() - neg_one.shift(UP) - self.play(ApplyMethod(neg_one.shift, shift_val)) - - def show_distances(self, num_mobs, rect_clusters): - self.remove(*[r for c in rect_clusters for r in c]) - text = None - for cluster, count in zip(rect_clusters, it.count()): - if text is not None: - self.remove(text) - if count == 0: - dist_string = "1" - else: - dist_string = "$\\frac{1}{%d}$"%(2**count) - text = TextMobject( - "Any of these pairs are considered to be a distance " +\ - dist_string +\ - " away from each other" - ).shift(2*UP) - self.add(text) - self.clear_way_for_text(text, cluster) - self.add(*cluster) - pairs = [a_b for a_b in it.combinations(list(range(16)), 2) if (a_b[0]-a_b[1])%(2**count) == 0 and (a_b[0]-a_b[1])%(2**(count+1)) != 0] - for pair in sample(pairs, min(10, len(pairs))): - for index in pair: - num_mobs[index].set_color("green") - self.play(*[ - ApplyMethod( - num_mobs[index].rotate_in_place, np.pi/10, - rate_func = wiggle - ) - for index in pair - ]) - self.wait() - for index in pair: - num_mobs[index].set_color("white") - - @staticmethod - def clear_way_for_text(text, mobjects): - right, top, null = np.max(text.points, 0) - left, bottom, null = np.min(text.points, 0) - def filter_func(xxx_todo_changeme): - (x, y, z) = xxx_todo_changeme - return x>left and xbottom and y 1: - self.remove(texts[n-2]) - else: - self.remove(u_brace, *texts) - self.remove(*last_args) - self.add(*new_args) - self.wait(rest_time) - last_args = new_args - - -class OtherRationalNumbers(Scene): - def construct(self): - import random - self.add(TextMobject("Where do other \\\\ rational numbers fall?")) - pairs = [ - (1, 2), - (1, 3), - (4, 9), - (-7, 13), - (3, 1001), - ] - locii = [ - 4*LEFT+2*UP, - 4*RIGHT, - 5*RIGHT+UP, - 4*LEFT+2*DOWN, - 3*DOWN, - ] - for pair, locus in zip(pairs, locii): - fraction = TexMobject("\\frac{%d}{%d}"%pair).shift(locus) - self.play(ShimmerIn(fraction)) - self.wait() - -class PAdicMetric(Scene): - def construct(self): - p_str, text = TextMobject(["$p$", "-adic metric"]).shift(2*UP).split() - primes = [TexMobject(str(p)) for p in [2, 3, 5, 7, 11, 13, 17, 19, 23]] - p_str.set_color("yellow") - colors = Color("green").range_to("skyblue", len(primes)) - new_numbers = TextMobject("Completely new types of numbers!") - new_numbers.set_color("skyblue").shift(2.3*DOWN) - arrow = Arrow(2*DOWN, tail = 1.7*UP) - - curr = deepcopy(p_str) - self.add(curr, text) - self.wait() - for prime, count in zip(primes, it.count()): - prime.scale(1.0).set_color(next(colors)) - prime.shift(center_of_mass([p_str.get_top(), p_str.get_center()])) - self.play(DelayByOrder(Transform(curr, prime))) - self.wait() - if count == 2: - self.spill(Mobject(curr, text), arrow, new_numbers) - self.remove(curr) - curr = prime - self.play(DelayByOrder(Transform(curr, p_str))) - self.wait() - - def spill(self, start, arrow, end): - start.sort_points(lambda p : p[1]) - self.play( - ShowCreation( - arrow, - rate_func = squish_rate_func(smooth, 0.5, 1.0) - ), - DelayByOrder(Transform( - start, - Point(arrow.points[0]).set_color("white") - )) - ) - self.play(ShimmerIn(end)) - - -class FuzzyDiscoveryToNewMath(Scene): - def construct(self): - fuzzy = TextMobject("Fuzzy Discovery") - fuzzy.to_edge(UP).shift(FRAME_X_RADIUS*LEFT/2) - new_math = TextMobject("New Math") - new_math.to_edge(UP).shift(FRAME_X_RADIUS*RIGHT/2) - lines = Mobject( - Line(DOWN*FRAME_Y_RADIUS, UP*FRAME_Y_RADIUS), - Line(3*UP+LEFT*FRAME_X_RADIUS, 3*UP+RIGHT*FRAME_X_RADIUS) - ) - fuzzy_discoveries = [ - TexMobject("a^2 + b^2 = c^2"), - TexMobject("".join(CONVERGENT_SUM_TEXT)), - TexMobject("".join(DIVERGENT_SUM_TEXT)), - TexMobject("e^{\pi i} = -1"), - ] - triangle_lines = [ - Line(ORIGIN, LEFT), - Line(LEFT, UP), - Line(UP, ORIGIN), - ] - for line, char in zip(triangle_lines, ["a", "c", "b"]): - line.set_color("blue") - char_mob = TexMobject(char).scale(0.25) - line.add(char_mob.shift(line.get_center())) - triangle = Mobject(*triangle_lines) - triangle.center().shift(1.5*fuzzy_discoveries[0].get_right()) - how_length = TextMobject("But how is length defined?").scale(0.5) - how_length.shift(0.75*DOWN) - fuzzy_discoveries[0].add(triangle, how_length) - new_maths = [ - TextMobject(""" - Define distance between points $(x_0, y_0)$ and - $(x_1, y_1)$ as $\\sqrt{(x_1-x_0)^2 + (y_1-y_0)^2}$ - """), - TextMobject("Define ``approach'' and infinite sums"), - TextMobject("Discover $2$-adic numbers"), - TextMobject( - "Realize exponentiation is doing something much \ - different from repeated multiplication" - ) - ] - midpoints = [] - triplets = list(zip(fuzzy_discoveries, new_maths, it.count(2, -1.75))) - for disc, math, count in triplets: - math.scale(0.65) - for mob in disc, math: - mob.to_edge(LEFT).shift(count*UP) - math.shift(FRAME_X_RADIUS*RIGHT) - midpoints.append(count*UP) - - self.add(fuzzy, lines) - self.play(*list(map(ShimmerIn, fuzzy_discoveries))) - self.wait() - self.play(DelayByOrder(Transform( - deepcopy(fuzzy), new_math - ))) - self.play(*[ - DelayByOrder(Transform(deepcopy(disc), math)) - for disc, math in zip(fuzzy_discoveries, new_maths) - ]) - self.wait() - - -class DiscoveryAndInvention(Scene): - def construct(self): - invention, vs, discovery = TextMobject([ - "Invention ", "vs. ", "Discovery" - ]).split() - nrd = TextMobject( - "Non-rigorous truths" - ).shift(2*UP) - rt = TextMobject( - "Rigorous terms" - ).shift(2*DOWN) - - arrows = [] - self.add(discovery, vs, invention) - self.wait() - arrow = Arrow( - nrd.get_bottom(), - tail = discovery.get_top() - ) - self.play( - FadeIn(nrd), - ShowCreation(arrow) - ) - arrows.append(arrow) - self.wait() - arrow = Arrow( - invention.get_top(), - tail = nrd.get_bottom() - ) - self.play(ShowCreation(arrow)) - arrows.append(arrow) - self.wait() - arrow = Arrow( - rt.get_top(), - tail = invention.get_bottom() - ) - self.play( - FadeIn(rt), - ShowCreation(arrow) - ) - arrows.append(arrow) - self.wait() - arrow = Arrow( - discovery.get_bottom(), - tail = rt.get_top() - ) - self.play(ShowCreation(arrow)) - self.wait() - arrows.append(arrow) - for color in Color("yellow").range_to("red", 4): - for arrow in arrows: - self.play( - ShowCreation(deepcopy(arrow).set_color(color)), - run_time = 0.25 - ) - - - - -if __name__ == "__main__": - command_line_create_scene(MOVIE_PREFIX) - diff --git a/from_3b1b/old/inventing_math_images.py b/from_3b1b/old/inventing_math_images.py deleted file mode 100644 index 08c02d86..00000000 --- a/from_3b1b/old/inventing_math_images.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python - - -import numpy as np -import itertools as it -from copy import deepcopy -import sys - -from manimlib.imports import * -from script_wrapper import command_line_create_scene -from .inventing_math import divergent_sum, draw_you - - - -class SimpleText(Scene): - args_list = [ - ("Build the foundation of what we know",), - ("What would that feel like?",), - ("Arbitrary decisions hinder generality",), - ("Section 1: Discovering and Defining Infinite Sums",), - ("Section 2: Seeking Generality",), - ("Section 3: Redefining Distance",), - ("``Approach''?",), - ("Rigor would dictate you ignore these",), - ("dist($A$, $B$) = dist($A+x$, $B+x$) \\quad for all $x$",), - ("How does a useful distance function differ from a random function?",), - ("Pause now, if you like, and see if you can invent your own distance function from this.",), - ("$p$-adic metrics \\\\ ($p$ is any prime number)",), - ("This is not meant to match the history of discoveries",), - ] - @staticmethod - def args_to_string(text): - return initials([c for c in text if c in string.letters + " "]) - - def construct(self, text): - self.add(TextMobject(text)) - - -class SimpleTex(Scene): - args_list = [ - ( - "\\frac{9}{10}+\\frac{9}{100}+\\frac{9}{1000}+\\cdots = 1", - "SumOf9s" - ), - ( - "0 < p < 1", - "PBetween0And1" - ), - ] - @staticmethod - def args_to_string(expression, words): - return words - - def construct(self, expression, words): - self.add(TexMobject(expression)) - - -class OneMinusOnePoem(Scene): - def construct(self): - verse1 = TextMobject(""" - \\begin{flushleft} - When one takes one from one \\\\ - plus one from one plus one \\\\ - and on and on but ends \\\\ - anon then starts again, \\\\ - then some sums sum to one, \\\\ - to zero other ones. \\\\ - One wonders who'd have won \\\\ - had stopping not been done; \\\\ - had he summed every bit \\\\ - until the infinite. \\\\ - \\end{flushleft} - """).scale(0.5).to_corner(UP+LEFT) - verse2 = TextMobject(""" - \\begin{flushleft} - Lest you should think that such \\\\ - less well-known sums are much \\\\ - ado about nonsense \\\\ - I do give these two cents: \\\\ - The universe has got \\\\ - an answer which is not \\\\ - what most would first surmise, \\\\ - it is a compromise, \\\\ - and though it seems a laugh \\\\ - the universe gives ``half''. \\\\ - \\end{flushleft} - """).scale(0.5).to_corner(DOWN+LEFT) - equation = TexMobject( - "1-1+1-1+\\cdots = \\frac{1}{2}" - ) - self.add(verse1, verse2, equation) - -class DivergentSum(Scene): - def construct(self): - self.add(divergent_sum().scale(0.75)) - - -class PowersOfTwoSmall(Scene): - def construct(self): - you, bubble = draw_you(with_bubble=True) - bubble.write( - "Is there any way in which apparently \ - large powers of two can be considered small?" - ) - self.add(you, bubble, bubble.content) - - -class FinalSlide(Scene): - def construct(self): - self.add(TextMobject(""" - \\begin{flushleft} - Needless to say, what I said here only scratches the - surface of the tip of the iceberg of the p-adic metric. - What is this new form of number I referred to? - Why were distances in the 2-adic metric all powers of - $\\frac{1}{2}$ and not some other base? - Why does it only work for prime numbers? \\\\ - \\quad \\\\ - I highly encourage anyone who has not seen p-adic numbers - to look them up and learn more, but even more edifying than - looking them up will be to explore this idea for yourself directly. - What properties make a distance function useful, and why? - What do I mean by ``useful''? Useful for what purpose? - Can you find infinite sums or sequences which feel like - they should converge in the 2-adic metric, but don't converge - to a rational number? Go on! Search! Invent! - \\end{flushleft} - """, size = "\\small")) - - - - diff --git a/from_3b1b/old/leibniz.py b/from_3b1b/old/leibniz.py deleted file mode 100644 index 1c58e744..00000000 --- a/from_3b1b/old/leibniz.py +++ /dev/null @@ -1,4826 +0,0 @@ -from manimlib.imports import * -from functools import reduce - -# revert_to_original_skipping_status - -def chi_func(n): - if n%2 == 0: - return 0 - if n%4 == 1: - return 1 - else: - return -1 - -class LatticePointScene(Scene): - CONFIG = { - "y_radius" : 6, - "x_radius" : None, - "plane_center" : ORIGIN, - "max_lattice_point_radius" : 6, - "dot_radius" : 0.075, - "secondary_line_ratio" : 0, - "plane_color" : BLUE_E, - "dot_color" : YELLOW, - "dot_drawing_stroke_color" : PINK, - "circle_color" : MAROON_D, - "radial_line_color" : RED, - } - def setup(self): - if self.x_radius is None: - self.x_radius = self.y_radius*FRAME_X_RADIUS/FRAME_Y_RADIUS - plane = ComplexPlane( - y_radius = self.y_radius, - x_radius = self.x_radius, - secondary_line_ratio = self.secondary_line_ratio, - radius = self.plane_color - ) - plane.set_height(FRAME_HEIGHT) - plane.shift(self.plane_center) - self.add(plane) - self.plane = plane - - self.setup_lattice_points() - - def setup_lattice_points(self): - M = self.max_lattice_point_radius - int_range = list(range(-M, M+1)) - self.lattice_points = VGroup() - for x, y in it.product(*[int_range]*2): - r_squared = x**2 + y**2 - if r_squared > M**2: - continue - dot = Dot( - self.plane.coords_to_point(x, y), - color = self.dot_color, - radius = self.dot_radius, - ) - dot.r_squared = r_squared - self.lattice_points.add(dot) - self.lattice_points.sort( - lambda p : get_norm(p - self.plane_center) - ) - - def get_circle(self, radius = None, color = None): - if radius is None: - radius = self.max_lattice_point_radius - if color is None: - color = self.circle_color - radius *= self.plane.get_space_unit_to_y_unit() - circle = Circle( - color = color, - radius = radius, - ) - circle.move_to(self.plane.get_center()) - return circle - - def get_radial_line_with_label(self, radius = None, color = None): - if radius is None: - radius = self.max_lattice_point_radius - if color is None: - color = self.radial_line_color - radial_line = Line( - self.plane_center, - self.plane.coords_to_point(radius, 0), - color = color - ) - r_squared = int(np.round(radius**2)) - root_label = TexMobject("\\sqrt{%d}"%r_squared) - root_label.add_background_rectangle() - root_label.next_to(radial_line, UP, SMALL_BUFF) - - return radial_line, root_label - - def get_lattice_points_on_r_squared_circle(self, r_squared): - points = VGroup(*[dot for dot in self.lattice_points if dot.r_squared == r_squared]) - points.sort( - lambda p : angle_of_vector(p-self.plane_center)%(2*np.pi) - ) - return points - - def draw_lattice_points(self, points = None, run_time = 4): - if points is None: - points = self.lattice_points - self.play(*[ - DrawBorderThenFill( - dot, - stroke_width = 4, - stroke_color = self.dot_drawing_stroke_color, - run_time = run_time, - rate_func = squish_rate_func( - double_smooth, a, a + 0.25 - ), - ) - for dot, a in zip( - points, - np.linspace(0, 0.75, len(points)) - ) - ]) - - def add_axis_labels(self, spacing = 2): - x_max = int(self.plane.point_to_coords(FRAME_X_RADIUS*RIGHT)[0]) - y_max = int(self.plane.point_to_coords(FRAME_Y_RADIUS*UP)[1]) - x_range = list(range(spacing, x_max, spacing)) - y_range = list(range(spacing, y_max, spacing)) - for r in x_range, y_range: - r += [-n for n in r] - tick = Line(ORIGIN, MED_SMALL_BUFF*UP) - x_ticks = VGroup(*[ - tick.copy().move_to(self.plane.coords_to_point(x, 0)) - for x in x_range - ]) - tick.rotate(-np.pi/2) - y_ticks = VGroup(*[ - tick.copy().move_to(self.plane.coords_to_point(0, y)) - for y in y_range - ]) - x_labels = VGroup(*[ - TexMobject(str(x)) - for x in x_range - ]) - y_labels = VGroup(*[ - TexMobject(str(y) + "i") - for y in y_range - ]) - - for labels, ticks in (x_labels, x_ticks), (y_labels, y_ticks): - labels.scale(0.6) - for tex_mob, tick in zip(labels, ticks): - tex_mob.add_background_rectangle() - tex_mob.next_to( - tick, - tick.get_start() - tick.get_end(), - SMALL_BUFF - ) - self.add(x_ticks, y_ticks, x_labels, y_labels) - digest_locals(self, [ - "x_ticks", "y_ticks", - "x_labels", "y_labels", - ]) - - def point_to_int_coords(self, point): - x, y = self.plane.point_to_coords(point)[:2] - return (int(np.round(x)), int(np.round(y))) - - def dot_to_int_coords(self, dot): - return self.point_to_int_coords(dot.get_center()) - - -###### - -class Introduction(PiCreatureScene): - def construct(self): - self.introduce_three_objects() - self.show_screen() - - def introduce_three_objects(self): - primes = self.get_primes() - primes.to_corner(UP+RIGHT) - primes.shift(DOWN) - plane = self.get_complex_numbers() - plane.shift(2*LEFT) - pi_group = self.get_pi_group() - pi_group.next_to(primes, DOWN, buff = MED_LARGE_BUFF) - pi_group.shift_onto_screen() - - morty = self.get_primary_pi_creature() - video = VideoIcon() - video.set_color(TEAL) - video.next_to(morty.get_corner(UP+LEFT), UP) - - self.play( - morty.change_mode, "raise_right_hand", - DrawBorderThenFill(video) - ) - self.wait() - self.play( - Write(primes, run_time = 2), - morty.change_mode, "happy", - video.set_height, FRAME_WIDTH, - video.center, - video.set_fill, None, 0 - ) - self.wait() - self.play( - Write(plane, run_time = 2), - morty.change, "raise_right_hand" - ) - self.wait() - self.remove(morty) - morty = morty.copy() - self.add(morty) - self.play( - ReplacementTransform( - morty.body, - pi_group.get_part_by_tex("pi"), - run_time = 1 - ), - FadeOut(VGroup(morty.eyes, morty.mouth)), - Write(VGroup(*pi_group[1:])) - ) - self.wait(2) - self.play( - plane.set_width, pi_group.get_width(), - plane.next_to, pi_group, DOWN, MED_LARGE_BUFF - ) - - def show_screen(self): - screen = ScreenRectangle(height = 4.3) - screen.to_edge(LEFT) - titles = VGroup( - TextMobject("From zeta video"), - TextMobject("Coming up") - ) - for title in titles: - title.next_to(screen, UP) - title.set_color(YELLOW) - self.play( - ShowCreation(screen), - FadeIn(titles[0]) - ) - self.show_frame() - self.wait(2) - self.play(Transform(*titles)) - self.wait(3) - - def get_primes(self): - return TexMobject("2, 3, 5, 7, 11, 13, \\dots") - - def get_complex_numbers(self): - plane = ComplexPlane( - x_radius = 3, - y_radius = 2.5, - ) - plane.add_coordinates() - point = plane.number_to_point(complex(1, 2)) - dot = Dot(point, radius = YELLOW) - label = TexMobject("1 + 2i") - label.add_background_rectangle() - label.next_to(dot, UP+RIGHT, buff = SMALL_BUFF) - label.set_color(YELLOW) - plane.label = label - plane.add(dot, label) - return plane - - def get_pi_group(self): - result = TexMobject("\\pi", "=", "%.8f\\dots"%np.pi) - pi = result.get_part_by_tex("pi") - pi.scale(2, about_point = pi.get_right()) - pi.set_color(MAROON_B) - return result - -class ShowSum(TeacherStudentsScene): - CONFIG = { - "num_terms_to_add" : 40, - } - def construct(self): - self.say_words() - self.show_sum() - - def say_words(self): - self.teacher_says("This won't be easy") - self.change_student_modes( - "hooray", "sassy", "angry" - ) - self.wait(2) - - def show_sum(self): - line = UnitInterval() - line.add_numbers(0, 1) - # line.shift(UP) - sum_point = line.number_to_point(np.pi/4) - - numbers = [0] + [ - ((-1)**n)/(2.0*n + 1) - for n in range(self.num_terms_to_add) - ] - partial_sums = np.cumsum(numbers) - points = list(map(line.number_to_point, partial_sums)) - arrows = [ - Arrow( - p1, p2, - tip_length = 0.2*min(1, get_norm(p1-p2)), - buff = 0 - ) - for p1, p2 in zip(points, points[1:]) - ] - dot = Dot(points[0]) - - sum_mob = TexMobject( - "1", "-\\frac{1}{3}", - "+\\frac{1}{5}", "-\\frac{1}{7}", - "+\\frac{1}{9}", "-\\frac{1}{11}", - "+\\cdots" - ) - sum_mob.to_corner(UP+RIGHT) - lhs = TexMobject( - "\\frac{\\pi}{4}", "=", - ) - lhs.next_to(sum_mob, LEFT) - lhs.set_color_by_tex("pi", YELLOW) - sum_arrow = Arrow( - lhs.get_part_by_tex("pi").get_bottom(), - sum_point - ) - fading_terms = [ - TexMobject(sign + "\\frac{1}{%d}"%(2*n + 1)) - for n, sign in zip( - list(range(self.num_terms_to_add)), - it.cycle("+-") - ) - ] - for fading_term, arrow in zip(fading_terms, arrows): - fading_term.next_to(arrow, UP) - - terms = it.chain(sum_mob, it.repeat(None)) - last_arrows = it.chain([None], arrows) - last_fading_terms = it.chain([None], fading_terms) - - self.change_student_modes( - *["pondering"]*3, - look_at_arg = line, - added_anims = [ - FadeIn(VGroup(line, dot)), - FadeIn(lhs), - RemovePiCreatureBubble( - self.teacher, - target_mode = "raise_right_hand" - ) - ] - - ) - run_time = 1 - for term, arrow, last_arrow, fading_term, last_fading_term in zip( - terms, arrows, last_arrows, fading_terms, last_fading_terms - ): - anims = [] - if term: - anims.append(Write(term)) - if last_arrow: - anims.append(FadeOut(last_arrow)) - if last_fading_term: - anims.append(FadeOut(last_fading_term)) - dot_movement = ApplyMethod(dot.move_to, arrow.get_end()) - anims.append(ShowCreation(arrow)) - anims.append(dot_movement) - anims.append(FadeIn(fading_term)) - self.play(*anims, run_time = run_time) - if term: - self.wait() - else: - run_time *= 0.8 - self.play( - FadeOut(arrow), - FadeOut(fading_term), - dot.move_to, sum_point - ) - self.play(ShowCreation(sum_arrow)) - self.wait() - self.change_student_modes("erm", "confused", "maybe") - self.play(self.teacher.change_mode, "happy") - self.wait(2) - -class FermatsDreamExcerptWrapper(Scene): - def construct(self): - words = TextMobject( - "From ``Fermat's dream'' by Kato, Kurokawa and Saito" - ) - words.scale(0.8) - words.to_edge(UP) - self.add(words) - self.wait() - -class ShowCalculus(PiCreatureScene): - def construct(self): - frac_sum = TexMobject( - "1 - \\frac{1}{3} + \\frac{1}{5} - \\frac{1}{7} + \\cdots", - ) - int1 = TexMobject( - "= \\int_0^1 (1 - x^2 + x^4 - \\dots )\\,dx" - ) - int2 = TexMobject( - "= \\int_0^1 \\frac{1}{1+x^2}\\,dx" - ) - arctan = TexMobject("= \\tan^{-1}(1)") - pi_fourths = TexMobject("= \\frac{\\pi}{4}") - - frac_sum.to_corner(UP+LEFT) - frac_sum.shift(RIGHT) - rhs_group = VGroup(int1, int2, arctan, pi_fourths) - rhs_group.arrange( - DOWN, buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - rhs_group.shift( - frac_sum.get_right() + MED_SMALL_BUFF*RIGHT \ - -int1[0].get_left() - ) - - self.add(frac_sum) - modes = it.chain(["plain"], it.cycle(["confused"])) - for rhs, mode in zip(rhs_group, modes): - self.play( - FadeIn(rhs), - self.pi_creature.change, mode - ) - self.wait() - self.change_mode("maybe") - self.wait() - self.look_at(rhs_group[-1]) - self.wait() - self.pi_creature_says( - "Where's the \\\\ circle?", - bubble_kwargs = {"width" : 4, "height" : 3}, - target_mode = "maybe" - ) - self.look_at(rhs_group[0]) - self.wait() - - def create_pi_creature(self): - return Randolph(color = BLUE_C).to_corner(DOWN+LEFT) - -class CertainRegularityInPrimes(LatticePointScene): - CONFIG = { - "y_radius" : 8, - "x_radius" : 20, - "max_lattice_point_radius" : 8, - "plane_center" : 2.5*RIGHT, - "primes" : [5, 13, 17, 29, 37, 41, 53], - "include_pi_formula" : True, - } - def construct(self): - if self.include_pi_formula: - self.add_pi_formula() - self.walk_through_primes() - - def add_pi_formula(self): - formula = TexMobject( - "\\frac{\\pi}{4}", "=", - "1", "-", "\\frac{1}{3}", - "+", "\\frac{1}{5}", "-", "\\frac{1}{7}", - "+\\cdots" - ) - formula.set_color_by_tex("pi", YELLOW) - formula.add_background_rectangle() - formula.to_corner(UP+LEFT, buff = MED_SMALL_BUFF) - self.add_foreground_mobject(formula) - - def walk_through_primes(self): - primes = self.primes - lines_and_labels = [ - self.get_radial_line_with_label(np.sqrt(p)) - for p in primes - ] - lines, labels = list(zip(*lines_and_labels)) - circles = [ - self.get_circle(np.sqrt(p)) - for p in primes - ] - dots_list = [ - self.get_lattice_points_on_r_squared_circle(p) - for p in primes - ] - groups = [ - VGroup(*mobs) - for mobs in zip(lines, labels, circles, dots_list) - ] - - curr_group = groups[0] - self.play(Write(curr_group, run_time = 2)) - self.wait() - for group in groups[1:]: - self.play(Transform(curr_group, group)) - self.wait(2) - -class Outline(PiCreatureScene): - def construct(self): - self.generate_list() - self.wonder_at_pi() - self.count_lattice_points() - self.write_steps_2_and_3() - self.show_chi() - self.show_complicated_formula() - self.show_last_step() - - def generate_list(self): - steps = VGroup( - TextMobject("1. Count lattice points"), - TexMobject("2. \\text{ Things like }17 = ", "4", "^2 + ", "1", "^2"), - TexMobject("3. \\text{ Things like }17 = (", "4", " + ", "i", ")(", "4", " - ", "i", ")"), - TextMobject("4. Introduce $\\chi$"), - TextMobject("5. Shift perspective"), - ) - for step in steps[1:3]: - step.set_color_by_tex("1", RED, substring = False) - step.set_color_by_tex("i", RED, substring = False) - step.set_color_by_tex("4", GREEN, substring = False) - steps.arrange( - DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - steps.to_corner(UP+LEFT) - - self.steps = steps - - def wonder_at_pi(self): - question = TexMobject("\\pi", "=???") - pi = question.get_part_by_tex("pi") - pi.scale(2, about_point = pi.get_right()) - pi.set_color(YELLOW) - question.next_to(self.pi_creature.body, LEFT, aligned_edge = UP) - self.think( - "Who am I really?", - look_at_arg = question, - added_anims = [ - FadeIn(question) - ] - ) - self.wait(2) - self.play( - RemovePiCreatureBubble(self.pi_creature), - question.to_corner, UP+RIGHT - ) - - self.question = question - self.pi = question.get_part_by_tex("pi") - - def count_lattice_points(self): - step = self.steps[0] - plane = NumberPlane( - x_radius = 10, y_radius = 10, - secondary_line_ratio = 0, - color = BLUE_E, - ) - plane.set_height(6) - plane.next_to(step, DOWN) - plane.to_edge(LEFT) - circle = Circle( - color = YELLOW, - radius = get_norm( - plane.coords_to_point(10, 0) - \ - plane.coords_to_point(0, 0) - ) - ) - plane_center = plane.coords_to_point(0, 0) - circle.move_to(plane_center) - lattice_points = VGroup(*[ - Dot( - plane.coords_to_point(a, b), - radius = 0.05, - color = PINK, - ) - for a in range(-10, 11) - for b in range(-10, 11) - if a**2 + b**2 <= 10**2 - ]) - lattice_points.sort( - lambda p : get_norm(p - plane_center) - ) - lattice_group = VGroup(plane, circle, lattice_points) - - self.play(ShowCreation(circle)) - self.play(Write(plane, run_time = 2), Animation(circle)) - self.play( - *[ - DrawBorderThenFill( - dot, - stroke_width = 4, - stroke_color = YELLOW, - run_time = 4, - rate_func = squish_rate_func( - double_smooth, a, a + 0.25 - ) - ) - for dot, a in zip( - lattice_points, - np.linspace(0, 0.75, len(lattice_points)) - ) - ] - ) - self.play( - FadeIn(step) - ) - self.wait() - self.play( - lattice_group.set_height, 2.5, - lattice_group.next_to, self.question, DOWN, - lattice_group.to_edge, RIGHT - ) - - def write_steps_2_and_3(self): - for step in self.steps[1:3]: - self.play(FadeIn(step)) - self.wait(2) - self.wait() - - def show_chi(self): - input_range = list(range(1, 7)) - chis = VGroup(*[ - TexMobject("\\chi(%d)"%n) - for n in input_range - ]) - chis.arrange(RIGHT, buff = LARGE_BUFF) - chis.set_stroke(WHITE, width = 1) - numerators = VGroup() - arrows = VGroup() - for chi, n in zip(chis, input_range): - arrow = TexMobject("\\Downarrow") - arrow.next_to(chi, DOWN, SMALL_BUFF) - arrows.add(arrow) - value = TexMobject(str(chi_func(n))) - value.set_color_by_tex("1", BLUE) - value.set_color_by_tex("-1", GREEN) - value.next_to(arrow, DOWN) - numerators.add(value) - group = VGroup(chis, arrows, numerators) - group.set_width(1.3*FRAME_X_RADIUS) - group.to_corner(DOWN+LEFT) - - self.play(FadeIn(self.steps[3])) - self.play(*[ - FadeIn( - mob, - run_time = 3, - lag_ratio = 0.5 - ) - for mob in [chis, arrows, numerators] - ]) - self.change_mode("pondering") - self.wait() - - self.chis = chis - self.arrows = arrows - self.numerators = numerators - - def show_complicated_formula(self): - rhs = TexMobject( - " = \\lim_{N \\to \\infty}", - " \\frac{4}{N}", - "\\sum_{n = 1}^N", - "\\sum_{d | n} \\chi(d)", - ) - pi = self.pi - self.add(pi.copy()) - pi.generate_target() - pi.target.next_to(self.steps[3], RIGHT, MED_LARGE_BUFF) - pi.target.shift(MED_LARGE_BUFF*DOWN) - rhs.next_to(pi.target, RIGHT) - - self.play( - MoveToTarget(pi), - Write(rhs) - ) - self.change_mode("confused") - self.wait(2) - - self.complicated_formula = rhs - - def show_last_step(self): - expression = TexMobject( - "=", "\\frac{\\quad}{1}", - *it.chain(*[ - ["+", "\\frac{\\quad}{%d}"%d] - for d in range(2, len(self.numerators)+1) - ] + [["+ \\cdots"]]) - ) - over_four = TexMobject("\\quad \\over 4") - over_four.to_corner(DOWN+LEFT) - over_four.shift(UP) - pi = self.pi - pi.generate_target() - pi.target.scale(0.75) - pi.target.next_to(over_four, UP) - expression.next_to(over_four, RIGHT, align_using_submobjects = True) - self.numerators.generate_target() - for num, denom in zip(self.numerators.target, expression[1::2]): - num.scale(1.2) - num.next_to(denom, UP, MED_SMALL_BUFF) - - self.play( - MoveToTarget(self.numerators), - MoveToTarget(pi), - Write(over_four), - FadeOut(self.chis), - FadeOut(self.arrows), - FadeOut(self.complicated_formula), - ) - self.play( - Write(expression), - self.pi_creature.change_mode, "pondering" - ) - self.wait(3) - - ######## - def create_pi_creature(self): - return Randolph(color = BLUE_C).flip().to_corner(DOWN+RIGHT) - -class CountLatticePoints(LatticePointScene): - CONFIG = { - "y_radius" : 11, - "max_lattice_point_radius" : 10, - "dot_radius" : 0.05, - "example_coords" : (7, 5), - } - def construct(self): - self.introduce_lattice_point() - self.draw_lattice_points_in_circle() - self.turn_points_int_units_of_area() - self.write_pi_R_squared() - self.allude_to_alternate_counting_method() - - - def introduce_lattice_point(self): - x, y = self.example_coords - example_dot = Dot( - self.plane.coords_to_point(x, y), - color = self.dot_color, - radius = 1.5*self.dot_radius, - ) - label = TexMobject(str(self.example_coords)) - label.add_background_rectangle() - label.next_to(example_dot, UP+RIGHT, buff = 0) - h_line = Line( - ORIGIN, self.plane.coords_to_point(x, 0), - color = GREEN - ) - v_line = Line( - h_line.get_end(), self.plane.coords_to_point(x, y), - color = RED - ) - lines = VGroup(h_line, v_line) - - dots = self.lattice_points.copy() - random.shuffle(dots.submobjects) - - self.play(*[ - ApplyMethod( - dot.set_fill, None, 0, - run_time = 3, - rate_func = squish_rate_func( - lambda t : 1 - there_and_back(t), - a, a + 0.5 - ), - remover = True - ) - for dot, a in zip(dots, np.linspace(0, 0.5, len(dots))) - ]) - self.play( - Write(label), - ShowCreation(lines), - DrawBorderThenFill(example_dot), - run_time = 2, - ) - self.wait(2) - self.play(*list(map(FadeOut, [label, lines, example_dot]))) - - def draw_lattice_points_in_circle(self): - circle = self.get_circle() - radius = Line(ORIGIN, circle.get_right()) - radius.set_color(RED) - brace = Brace(radius, DOWN, buff = SMALL_BUFF) - radius_label = brace.get_text( - str(self.max_lattice_point_radius), - buff = SMALL_BUFF - ) - radius_label.add_background_rectangle() - brace.add(radius_label) - - self.play( - ShowCreation(circle), - Rotating(radius, about_point = ORIGIN), - run_time = 2, - rate_func = smooth, - ) - self.play(FadeIn(brace)) - self.add_foreground_mobject(brace) - self.draw_lattice_points() - self.wait() - self.play(*list(map(FadeOut, [brace, radius]))) - - self.circle = circle - - def turn_points_int_units_of_area(self): - square = Square(fill_opacity = 0.9) - unit_line = Line( - self.plane.coords_to_point(0, 0), - self.plane.coords_to_point(1, 0), - ) - square.set_width(unit_line.get_width()) - squares = VGroup(*[ - square.copy().move_to(point) - for point in self.lattice_points - ]) - squares.set_color_by_gradient(BLUE_E, GREEN_E) - squares.set_stroke(WHITE, 1) - point_copies = self.lattice_points.copy() - - self.play( - ReplacementTransform( - point_copies, squares, - run_time = 3, - lag_ratio = 0.5, - ), - Animation(self.lattice_points) - ) - self.wait() - self.play(FadeOut(squares), Animation(self.lattice_points)) - - def write_pi_R_squared(self): - equations = VGroup(*[ - VGroup( - TextMobject( - "\\# Lattice points\\\\", - "within radius ", R, - alignment = "" - ), - TexMobject( - "\\approx \\pi", "(", R, ")^2" - ) - ).arrange(RIGHT) - for R in ("10", "1{,}000{,}000", "R") - ]) - radius_10_eq, radius_million_eq, radius_R_eq = equations - for eq in equations: - for tex_mob in eq: - tex_mob.set_color_by_tex("0", BLUE) - radius_10_eq.to_corner(UP+LEFT) - radius_million_eq.next_to(radius_10_eq, DOWN, LARGE_BUFF) - radius_million_eq.to_edge(LEFT) - brace = Brace(radius_million_eq, DOWN) - brace.add(brace.get_text("More accurate")) - brace.set_color(YELLOW) - - background = FullScreenFadeRectangle(opacity = 0.9) - - self.play( - FadeIn(background), - Write(radius_10_eq) - ) - self.wait(2) - self.play(ReplacementTransform( - radius_10_eq.copy(), - radius_million_eq - )) - self.play(FadeIn(brace)) - self.wait(3) - - self.radius_10_eq = radius_10_eq - self.million_group = VGroup(radius_million_eq, brace) - self.radius_R_eq = radius_R_eq - - def allude_to_alternate_counting_method(self): - alt_count = TextMobject( - "(...something else...)", "$R^2$", "=", - arg_separator = "" - ) - alt_count.to_corner(UP+LEFT) - alt_count.set_color_by_tex("something", MAROON_B) - self.radius_R_eq.next_to(alt_count, RIGHT) - - final_group = VGroup( - alt_count.get_part_by_tex("something"), - self.radius_R_eq[1].get_part_by_tex("pi"), - ).copy() - - self.play( - FadeOut(self.million_group), - Write(alt_count), - ReplacementTransform( - self.radius_10_eq, - self.radius_R_eq - ) - ) - self.wait(2) - self.play( - final_group.arrange, RIGHT, - final_group.next_to, ORIGIN, UP - ) - rect = BackgroundRectangle(final_group) - self.play(FadeIn(rect), Animation(final_group)) - self.wait(2) - -class SoYouPlay(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "So you play!", - run_time = 2 - ) - self.change_student_modes("happy", "thinking", "hesitant") - self.wait() - self.look_at(Dot().to_corner(UP+LEFT)) - self.wait(3) - -class CountThroughRings(LatticePointScene): - CONFIG = { - "example_coords" : (3, 2), - "num_rings_to_show_explicitly" : 7, - "x_radius" : 15, - - "plane_center" : 2*RIGHT, - "max_lattice_point_radius" : 5, - } - def construct(self): - self.add_lattice_points() - self.preview_rings() - self.isolate_single_ring() - self.show_specific_lattice_point_distance() - self.count_through_rings() - - def add_lattice_points(self): - big_circle = self.get_circle() - self.add(big_circle) - self.add(self.lattice_points) - self.big_circle = big_circle - - def preview_rings(self): - radii = list(set([ - np.sqrt(p.r_squared) for p in self.lattice_points - ])) - radii.sort() - circles = VGroup(*[ - self.get_circle(radius = r) - for r in radii - ]) - circles.set_stroke(width = 2) - - self.add_foreground_mobject(self.lattice_points) - self.play(FadeIn(circles)) - self.play(LaggedStartMap( - ApplyMethod, - circles, - arg_creator = lambda m : (m.set_stroke, PINK, 4), - rate_func = there_and_back, - )) - self.wait() - self.remove_foreground_mobject(self.lattice_points) - - digest_locals(self, ["circles", "radii"]) - - def isolate_single_ring(self): - x, y = self.example_coords - example_circle = self.circles[ - self.radii.index(np.sqrt(x**2 + y**2)) - ] - self.circles.remove(example_circle) - points_on_example_circle = self.get_lattice_points_on_r_squared_circle( - x**2 + y**2 - ).copy() - - self.play( - FadeOut(self.circles), - self.lattice_points.set_fill, GREY, 0.5, - Animation(points_on_example_circle) - ) - self.wait() - - digest_locals(self, ["points_on_example_circle", "example_circle"]) - - def show_specific_lattice_point_distance(self): - x, y = self.example_coords - dot = Dot( - self.plane.coords_to_point(x, y), - color = self.dot_color, - radius = self.dot_radius - ) - label = TexMobject("(a, b)") - num_label = TexMobject(str(self.example_coords)) - for mob in label, num_label: - mob.add_background_rectangle() - mob.next_to(dot, UP + RIGHT, SMALL_BUFF) - a, b = label[1][1].copy(), label[1][3].copy() - - x_spot = self.plane.coords_to_point(x, 0) - radial_line = Line(self.plane_center, dot) - h_line = Line(self.plane_center, x_spot) - h_line.set_color(GREEN) - v_line = Line(x_spot, dot) - v_line.set_color(RED) - - distance = TexMobject("\\sqrt{a^2 + b^2}") - distance_num = TexMobject("\\sqrt{%d}"%(x**2 + y**2)) - for mob in distance, distance_num: - mob.scale(0.75) - mob.add_background_rectangle() - mob.next_to(radial_line.get_center(), UP, SMALL_BUFF) - mob.rotate( - radial_line.get_angle(), - about_point = mob.get_bottom() - ) - - self.play(Write(label)) - self.play( - ApplyMethod(a.next_to, h_line, DOWN, SMALL_BUFF), - ApplyMethod(b.next_to, v_line, RIGHT, SMALL_BUFF), - ShowCreation(h_line), - ShowCreation(v_line), - ) - self.play(ShowCreation(radial_line)) - self.play(Write(distance)) - self.wait(2) - - a_num, b_num = [ - TexMobject(str(coord))[0] - for coord in self.example_coords - ] - a_num.move_to(a, UP) - b_num.move_to(b, LEFT) - self.play( - Transform(label, num_label), - Transform(a, a_num), - Transform(b, b_num), - ) - self.wait() - self.play(Transform(distance, distance_num)) - self.wait(3) - self.play(*list(map(FadeOut, [ - self.example_circle, self.points_on_example_circle, - distance, a, b, - radial_line, h_line, v_line, - label - ]))) - - def count_through_rings(self): - counts = [ - len(self.get_lattice_points_on_r_squared_circle(N)) - for N in range(self.max_lattice_point_radius**2 + 1) - ] - left_list = VGroup(*[ - TexMobject( - "\\sqrt{%d} \\Rightarrow"%n, str(count) - ) - for n, count in zip( - list(range(self.num_rings_to_show_explicitly)), - counts - ) - ]) - left_counts = VGroup() - left_roots = VGroup() - for mob in left_list: - mob[1].set_color(YELLOW) - left_counts.add(VGroup(mob[1])) - mob.add_background_rectangle() - left_roots.add(VGroup(mob[0], mob[1][0])) - - left_list.arrange( - DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT, - ) - left_list.to_corner(UP + LEFT) - - top_list = VGroup(*[ - TexMobject("%d, "%count) - for count in counts - ]) - top_list.set_color(YELLOW) - top_list.arrange(RIGHT, aligned_edge = DOWN) - top_list.set_width(FRAME_WIDTH - MED_LARGE_BUFF) - top_list.to_edge(UP, buff = SMALL_BUFF) - top_rect = BackgroundRectangle(top_list) - - for r_squared, count_mob, root in zip(it.count(), left_counts, left_roots): - self.show_ring_count( - r_squared, - count_mob, - added_anims = [FadeIn(root)] - ) - self.wait(2) - self.play( - FadeOut(left_roots), - FadeIn(top_rect), - *[ - ReplacementTransform( - lc, VGroup(tc), - path_arc = np.pi/2 - ) - for lc, tc in zip(left_counts, top_list) - ] - ) - for r_squared in range(len(left_counts), self.max_lattice_point_radius**2 + 1): - self.show_ring_count( - r_squared, top_list[r_squared], - ) - self.wait(3) - - - def show_ring_count( - self, radius_squared, target, - added_anims = None, - run_time = 1 - ): - added_anims = added_anims or [] - radius = np.sqrt(radius_squared) - points = self.get_lattice_points_on_r_squared_circle(radius_squared) - points.save_state() - circle = self.get_circle(radius) - radial_line = Line( - self.plane_center, self.plane.coords_to_point(radius, 0), - color = RED - ) - root = TexMobject("\\sqrt{%d}"%radius_squared) - root.add_background_rectangle() - root.set_width( - min(0.7*radial_line.get_width(), root.get_width()) - ) - root.next_to(radial_line, DOWN, SMALL_BUFF) - if not hasattr(self, "little_circle"): - self.little_circle = circle - if not hasattr(self, "radial_line"): - self.radial_line = radial_line - if not hasattr(self, "root"): - self.root = root - if hasattr(self, "last_points"): - added_anims += [self.last_points.restore] - self.last_points = points - - if radius_squared == 0: - points.set_fill(YELLOW, 1) - self.play( - DrawBorderThenFill(points, stroke_color = PINK), - *added_anims, - run_time = run_time - ) - self.play(ReplacementTransform( - points.copy(), target - )) - return - points.set_fill(YELLOW, 1) - self.play( - Transform(self.little_circle, circle), - Transform(self.radial_line, radial_line), - Transform(self.root, root), - DrawBorderThenFill( - points, - stroke_width = 4, - stroke_color = PINK, - ), - *added_anims, - run_time = run_time - ) - self.wait(run_time) - if len(points) > 0: - mover = points.copy() - else: - mover = VectorizedPoint(self.plane_center) - self.play(ReplacementTransform(mover, target, run_time = run_time)) - -class LookAtExampleRing(LatticePointScene): - CONFIG = { - "dot_radius" : 0.1, - "plane_center" : 2*LEFT, - "x_radius" : 17, - "y_radius" : 7, - } - def construct(self): - self.analyze_25() - self.analyze_11() - - def analyze_25(self): - x_color = GREEN - y_color = RED - circle = self.get_circle(radius = 5) - points = self.get_lattice_points_on_r_squared_circle(25) - radius, root_label = self.get_radial_line_with_label(5) - coords_list = [(5, 0), (4, 3), (3, 4), (0, 5), (-3, 4), (-4, 3)] - labels = [ - TexMobject("(", str(x), ",", str(y), ")") - for x, y in coords_list - ] - for label in labels: - label.x = label[1] - label.y = label[3] - label.x.set_color(x_color) - label.y.set_color(y_color) - label.add_background_rectangle() - - for label, point in zip(labels, points): - x_coord = (point.get_center() - self.plane_center)[0] - vect = UP+RIGHT if x_coord >= 0 else UP+LEFT - label.next_to(point, vect, SMALL_BUFF) - label.point = point - - def special_str(n): - return "(%d)"%n if n < 0 else str(n) - - sums_of_squares = [ - TexMobject( - special_str(x), "^2", "+", - special_str(y), "^2", "= 25" - ) - for x, y in coords_list - ] - for tex_mob in sums_of_squares: - tex_mob.x = tex_mob[0] - tex_mob.y = tex_mob[3] - tex_mob.x.set_color(x_color) - tex_mob.y.set_color(y_color) - tex_mob.add_background_rectangle() - tex_mob.to_corner(UP+RIGHT) - - self.play( - ShowCreation(radius), - Write(root_label) - ) - self.play( - ShowCreation(circle), - Rotating( - radius, - about_point = self.plane_center, - rate_func = smooth, - ), - FadeIn(points, lag_ratio = 0.5), - run_time = 2, - ) - self.wait() - - curr_label = labels[0] - curr_sum_of_squares = sums_of_squares[0] - self.play( - Write(curr_label), - curr_label.point.set_color, PINK - ) - x, y = curr_label.x.copy(), curr_label.y.copy() - self.play( - Transform(x, curr_sum_of_squares.x), - Transform(y, curr_sum_of_squares.y), - ) - self.play( - Write(curr_sum_of_squares), - Animation(VGroup(x, y)) - ) - self.remove(x, y) - self.wait() - - for label, sum_of_squares in zip(labels, sums_of_squares)[1:]: - self.play( - ReplacementTransform(curr_label, label), - label.point.set_color, PINK, - curr_label.point.set_color, self.dot_color - ) - curr_label = label - self.play( - ReplacementTransform( - curr_sum_of_squares, sum_of_squares - ) - ) - curr_sum_of_squares = sum_of_squares - self.wait() - - points.save_state() - points.generate_target() - for i, point in enumerate(points.target): - point.move_to( - self.plane.coords_to_point(i%3, i//3) - ) - points.target.next_to(circle, RIGHT) - - self.play(MoveToTarget( - points, - run_time = 2, - )) - self.wait() - self.play(points.restore, run_time = 2) - self.wait() - self.play(*list(map(FadeOut, [ - curr_label, curr_sum_of_squares, - circle, points, - radius, root_label - ]))) - - def analyze_11(self): - R = np.sqrt(11) - circle = self.get_circle(radius = R) - radius, root_label = self.get_radial_line_with_label(R) - equation = TexMobject("11 \\ne ", "a", "^2", "+", "b", "^2") - equation.set_color_by_tex("a", GREEN) - equation.set_color_by_tex("b", RED) - equation.add_background_rectangle() - equation.to_corner(UP+RIGHT) - - self.play( - Write(root_label), - ShowCreation(radius), - run_time = 1 - ) - self.play( - ShowCreation(circle), - Rotating( - radius, - about_point = self.plane_center, - rate_func = smooth, - ), - run_time = 2, - ) - self.wait() - self.play(Write(equation)) - self.wait(3) - -class Given2DThinkComplex(TeacherStudentsScene): - def construct(self): - tex = TextMobject("2D $\\Leftrightarrow$ Complex numbers") - plane = ComplexPlane( - x_radius = 0.6*FRAME_X_RADIUS, - y_radius = 0.6*FRAME_Y_RADIUS, - ) - plane.add_coordinates() - plane.set_height(FRAME_Y_RADIUS) - plane.to_corner(UP+LEFT) - - self.teacher_says(tex) - self.change_student_modes("pondering", "confused", "erm") - self.wait() - self.play( - Write(plane), - RemovePiCreatureBubble( - self.teacher, - target_mode = "raise_right_hand" - ) - ) - self.change_student_modes( - *["thinking"]*3, - look_at_arg = plane - ) - self.wait(3) - -class IntroduceComplexConjugate(LatticePointScene): - CONFIG = { - "y_radius" : 20, - "x_radius" : 30, - "plane_scale_factor" : 1.7, - "plane_center" : 2*LEFT, - "example_coords" : (3, 4), - "x_color" : GREEN, - "y_color" : RED, - } - def construct(self): - self.resize_plane() - self.write_points_with_complex_coords() - self.introduce_complex_conjugate() - self.show_confusion() - self.expand_algebraically() - self.discuss_geometry() - self.show_geometrically() - - def resize_plane(self): - self.plane.scale( - self.plane_scale_factor, - about_point = self.plane_center - ) - self.plane.set_stroke(width = 1) - self.plane.axes.set_stroke(width = 3) - - def write_points_with_complex_coords(self): - x, y = self.example_coords - x_color = self.x_color - y_color = self.y_color - - point = self.plane.coords_to_point(x, y) - dot = Dot(point, color = self.dot_color) - x_point = self.plane.coords_to_point(x, 0) - h_arrow = Arrow(self.plane_center, x_point, buff = 0) - v_arrow = Arrow(x_point, point, buff = 0) - h_arrow.set_color(x_color) - v_arrow.set_color(y_color) - x_coord = TexMobject(str(x)) - x_coord.next_to(h_arrow, DOWN, SMALL_BUFF) - x_coord.set_color(x_color) - x_coord.add_background_rectangle() - y_coord = TexMobject(str(y)) - imag_y_coord = TexMobject(str(y) + "i") - for coord in y_coord, imag_y_coord: - coord.next_to(v_arrow, RIGHT, SMALL_BUFF) - coord.set_color(y_color) - coord.add_background_rectangle() - - tuple_label = TexMobject(str((x, y))) - tuple_label[1].set_color(x_color) - tuple_label[3].set_color(y_color) - complex_label = TexMobject("%d+%di"%(x, y)) - complex_label[0].set_color(x_color) - complex_label[2].set_color(y_color) - for label in tuple_label, complex_label: - label.add_background_rectangle() - label.next_to(dot, UP+RIGHT, buff = 0) - - y_range = list(range(-9, 10, 3)) - ticks = VGroup(*[ - Line( - ORIGIN, MED_SMALL_BUFF*RIGHT - ).move_to(self.plane.coords_to_point(0, y)) - for y in y_range - ]) - imag_coords = VGroup() - for y, tick in zip(y_range, ticks): - if y == 0: - continue - if y == 1: - tex = "i" - elif y == -1: - tex = "-i" - else: - tex = "%di"%y - imag_coord = TexMobject(tex) - imag_coord.scale(0.75) - imag_coord.add_background_rectangle() - imag_coord.next_to(tick, LEFT, SMALL_BUFF) - imag_coords.add(imag_coord) - - self.add(dot) - self.play( - ShowCreation(h_arrow), - Write(x_coord) - ) - self.play( - ShowCreation(v_arrow), - Write(y_coord) - ) - self.play(FadeIn(tuple_label)) - self.wait() - self.play(*list(map(FadeOut, [tuple_label, y_coord]))) - self.play(*list(map(FadeIn, [complex_label, imag_y_coord]))) - self.play(*list(map(Write, [imag_coords, ticks]))) - self.wait() - self.play(*list(map(FadeOut, [ - v_arrow, h_arrow, - x_coord, imag_y_coord, - ]))) - - self.complex_label = complex_label - self.example_dot = dot - - def introduce_complex_conjugate(self): - x, y = self.example_coords - equation = VGroup( - TexMobject("25 = ", str(x), "^2", "+", str(y), "^2", "="), - TexMobject("(", str(x), "+", str(y), "i", ")"), - TexMobject("(", str(x), "-", str(y), "i", ")"), - ) - equation.arrange( - RIGHT, buff = SMALL_BUFF, - ) - VGroup(*equation[-2:]).shift(0.5*SMALL_BUFF*DOWN) - equation.scale(0.9) - equation.to_corner(UP+RIGHT, buff = MED_SMALL_BUFF) - equation.shift(MED_LARGE_BUFF*DOWN) - for tex_mob in equation: - tex_mob.set_color_by_tex(str(x), self.x_color) - tex_mob.set_color_by_tex(str(y), self.y_color) - tex_mob.add_background_rectangle() - - dot = Dot( - self.plane.coords_to_point(x, -y), - color = self.dot_color - ) - label = TexMobject("%d-%di"%(x, y)) - label[0].set_color(self.x_color) - label[2].set_color(self.y_color) - label.add_background_rectangle() - label.next_to(dot, DOWN+RIGHT, buff = 0) - - brace = Brace(equation[-1], DOWN) - conjugate_words = TextMobject("Complex \\\\ conjugate") - conjugate_words.scale(0.8) - conjugate_words.add_background_rectangle() - conjugate_words.next_to(brace, DOWN) - - self.play(FadeIn( - equation, - run_time = 3, - lag_ratio = 0.5 - )) - self.wait(2) - self.play( - GrowFromCenter(brace), - Write(conjugate_words, run_time = 2) - ) - self.wait() - self.play(*[ - ReplacementTransform(m1.copy(), m2) - for m1, m2 in [ - (self.example_dot, dot), - (self.complex_label, label), - ] - ]) - self.wait(2) - - self.conjugate_label = VGroup(brace, conjugate_words) - self.equation = equation - self.conjugate_dot = dot - - def show_confusion(self): - randy = Randolph(color = BLUE_C).to_corner(DOWN+LEFT) - morty = Mortimer().to_edge(DOWN) - randy.make_eye_contact(morty) - - self.play(*list(map(FadeIn, [randy, morty]))) - self.play(PiCreatureSays( - randy, "Wait \\dots why?", - target_mode = "confused", - )) - self.play(Blink(randy)) - self.wait(2) - self.play( - RemovePiCreatureBubble( - randy, target_mode = "erm", - ), - PiCreatureSays( - morty, "Now it's a \\\\ factoring problem!", - target_mode = "hooray", - bubble_kwargs = {"width" : 5, "height" : 3} - ) - ) - self.play( - morty.look_at, self.equation, - randy.look_at, self.equation, - ) - self.play(Blink(morty)) - self.play(randy.change_mode, "pondering") - self.play(RemovePiCreatureBubble(morty)) - self.play(*list(map(FadeOut, [randy, morty]))) - - def expand_algebraically(self): - x, y = self.example_coords - expansion = VGroup( - TexMobject(str(x), "^2"), - TexMobject("-", "(", str(y), "i", ")^2") - ) - expansion.arrange(RIGHT, buff = SMALL_BUFF) - expansion.next_to( - VGroup(*self.equation[-2:]), - DOWN, LARGE_BUFF - ) - alt_y_term = TexMobject("+", str(y), "^2") - alt_y_term.move_to(expansion[1], LEFT) - for tex_mob in list(expansion) + [alt_y_term]: - tex_mob.set_color_by_tex(str(x), self.x_color) - tex_mob.set_color_by_tex(str(y), self.y_color) - tex_mob.rect = BackgroundRectangle(tex_mob) - - x1 = self.equation[-2][1][1] - x2 = self.equation[-1][1][1] - y1 = VGroup(*self.equation[-2][1][3:5]) - y2 = VGroup(*self.equation[-1][1][2:5]) - vect = MED_LARGE_BUFF*UP - - self.play(FadeOut(self.conjugate_label)) - group = VGroup(x1, x2) - self.play(group.shift, -vect) - self.play( - FadeIn(expansion[0].rect), - ReplacementTransform(group.copy(), expansion[0]), - ) - self.play(group.shift, vect) - group = VGroup(x1, y2) - self.play(group.shift, -vect) - self.wait() - self.play(group.shift, vect) - group = VGroup(x2, y1) - self.play(group.shift, -vect) - self.wait() - self.play(group.shift, vect) - group = VGroup(*it.chain(y1, y2)) - self.play(group.shift, -vect) - self.wait() - self.play( - FadeIn(expansion[1].rect), - ReplacementTransform(group.copy(), expansion[1]), - ) - self.play(group.shift, vect) - self.wait(2) - self.play( - Transform(expansion[1].rect, alt_y_term.rect), - Transform(expansion[1], alt_y_term), - ) - self.wait() - self.play(*list(map(FadeOut, [ - expansion[0].rect, - expansion[1].rect, - expansion - ]))) - - def discuss_geometry(self): - randy = Randolph(color = BLUE_C) - randy.scale(0.8) - randy.to_corner(DOWN+LEFT) - morty = Mortimer() - morty.set_height(randy.get_height()) - morty.next_to(randy, RIGHT) - randy.make_eye_contact(morty) - screen = ScreenRectangle(height = 3.5) - screen.to_corner(DOWN+RIGHT, buff = MED_SMALL_BUFF) - - self.play(*list(map(FadeIn, [randy, morty]))) - self.play(PiCreatureSays( - morty, "More geometry!", - target_mode = "hooray", - run_time = 2, - bubble_kwargs = {"height" : 2, "width" : 4} - )) - self.play(Blink(randy)) - self.play( - RemovePiCreatureBubble( - morty, target_mode = "plain", - ), - PiCreatureSays( - randy, "???", - target_mode = "maybe", - bubble_kwargs = {"width" : 3, "height" : 2} - ) - ) - self.play( - ShowCreation(screen), - morty.look_at, screen, - randy.look_at, screen, - ) - self.play(Blink(morty)) - self.play(RemovePiCreatureBubble(randy, target_mode = "pondering")) - self.wait() - self.play(*list(map(FadeOut, [randy, morty, screen]))) - - def show_geometrically(self): - dots = [self.example_dot, self.conjugate_dot] - top_dot, low_dot = dots - for dot in dots: - dot.line = Line( - self.plane_center, dot.get_center(), - color = BLUE - ) - dot.angle = dot.line.get_angle() - dot.arc = Arc( - dot.angle, - radius = 0.75, - color = YELLOW - ) - dot.arc.shift(self.plane_center) - dot.arc.add_tip(tip_length = 0.2) - dot.rotate_word = TextMobject("Rotate") - dot.rotate_word.scale(0.5) - dot.rotate_word.next_to(dot.arc, RIGHT, SMALL_BUFF) - dot.magnitude_word = TextMobject("Length 5") - dot.magnitude_word.scale(0.6) - dot.magnitude_word.next_to( - ORIGIN, - np.sign(dot.get_center()[1])*UP, - buff = SMALL_BUFF - ) - dot.magnitude_word.add_background_rectangle() - dot.magnitude_word.rotate(dot.angle) - dot.magnitude_word.shift(dot.line.get_center()) - twenty_five_label = TexMobject("25") - twenty_five_label.add_background_rectangle() - twenty_five_label.next_to( - self.plane.coords_to_point(25, 0), - DOWN - ) - - self.play(ShowCreation(top_dot.line)) - mover = VGroup( - top_dot.line.copy().set_color(PINK), - top_dot.copy() - ) - self.play(FadeIn( - top_dot.magnitude_word, - lag_ratio = 0.5 - )) - self.wait() - self.play(ShowCreation(top_dot.arc)) - self.wait(2) - self.play(ShowCreation(low_dot.line)) - self.play( - ReplacementTransform( - top_dot.arc, - low_dot.arc - ), - FadeIn(low_dot.rotate_word) - ) - self.play( - Rotate( - mover, low_dot.angle, - about_point = self.plane_center - ), - run_time = 2 - ) - self.play( - FadeOut(low_dot.arc), - FadeOut(low_dot.rotate_word), - FadeIn(low_dot.magnitude_word), - ) - self.play( - mover[0].scale_about_point, 5, self.plane_center, - mover[1].move_to, self.plane.coords_to_point(25, 0), - run_time = 2 - ) - self.wait() - self.play(Write(twenty_five_label)) - self.wait(3) - -class NameGaussianIntegers(LatticePointScene): - CONFIG = { - "max_lattice_point_radius" : 15, - "dot_radius" : 0.05, - "plane_center" : 2*LEFT, - "x_radius" : 15, - } - def construct(self): - self.add_axis_labels() - self.add_a_plus_bi() - self.draw_lattice_points() - self.add_name() - self.restrict_to_one_circle() - self.show_question_algebraically() - - def add_a_plus_bi(self): - label = TexMobject( - "a", "+", "b", "i" - ) - a = label.get_part_by_tex("a") - b = label.get_part_by_tex("b") - a.set_color(GREEN) - b.set_color(RED) - label.add_background_rectangle() - label.to_corner(UP+RIGHT) - integers = TextMobject("Integers") - integers.next_to(label, DOWN, LARGE_BUFF) - integers.add_background_rectangle() - arrows = VGroup(*[ - Arrow(integers.get_top(), mob, tip_length = 0.15) - for mob in (a, b) - ]) - self.add_foreground_mobjects(label, integers, arrows) - - self.a_plus_bi = label - self.integers_label = VGroup(integers, arrows) - - def add_name(self): - gauss_name = TextMobject( - "Carl Friedrich Gauss" - ) - gauss_name.add_background_rectangle() - gauss_name.next_to(ORIGIN, UP, MED_LARGE_BUFF) - gauss_name.to_edge(LEFT) - - gaussian_integers = TextMobject("``Gaussian integers'': ") - gaussian_integers.scale(0.9) - gaussian_integers.next_to(self.a_plus_bi, LEFT) - gaussian_integers.add_background_rectangle() - - self.play(FadeIn(gaussian_integers)) - self.add_foreground_mobject(gaussian_integers) - self.play(FadeIn( - gauss_name, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait(3) - self.play(FadeOut(gauss_name)) - - self.gaussian_integers = gaussian_integers - - def restrict_to_one_circle(self): - dots = self.get_lattice_points_on_r_squared_circle(25).copy() - for dot in dots: - dot.scale_in_place(2) - circle = self.get_circle(5) - radius, root_label = self.get_radial_line_with_label(5) - - self.play( - FadeOut(self.lattice_points), - ShowCreation(circle), - Rotating( - radius, - run_time = 1, rate_func = smooth, - about_point = self.plane_center - ), - *list(map(GrowFromCenter, dots)) - ) - self.play(Write(root_label)) - self.wait() - - self.circle_dots = dots - - def show_question_algebraically(self): - for i, dot in enumerate(self.circle_dots): - x, y = self.dot_to_int_coords(dot) - x_str = str(x) - y_str = str(y) if y >= 0 else "(%d)"%y - label = TexMobject(x_str, "+", y_str, "i") - label.scale(0.8) - label.next_to( - dot, - dot.get_center()-self.plane_center + SMALL_BUFF*(UP+RIGHT), - buff = 0, - ) - label.add_background_rectangle() - dot.label = label - - equation = TexMobject( - "25 = " - "(", x_str, "+", y_str, "i", ")", - "(", x_str, "-", y_str, "i", ")", - ) - equation.scale(0.9) - equation.add_background_rectangle() - equation.to_corner(UP + RIGHT) - dot.equation = equation - - for mob in label, equation: - mob.set_color_by_tex(x_str, GREEN, substring = False) - mob.set_color_by_tex(y_str, RED, substring = False) - - dot.line_pair = VGroup(*[ - Line( - self.plane_center, - self.plane.coords_to_point(x, u*y), - color = PINK, - ) - for u in (1, -1) - ]) - dot.conjugate_dot = self.circle_dots[-i] - - self.play(*list(map(FadeOut, [ - self.a_plus_bi, self.integers_label, - self.gaussian_integers, - ]))) - - last_dot = None - for dot in self.circle_dots: - anims = [ - dot.set_color, PINK, - dot.conjugate_dot.set_color, PINK, - ] - if last_dot is None: - anims += [ - FadeIn(dot.equation), - FadeIn(dot.label), - ] - anims += list(map(ShowCreation, dot.line_pair)) - else: - anims += [ - last_dot.set_color, self.dot_color, - last_dot.conjugate_dot.set_color, self.dot_color, - ReplacementTransform(last_dot.equation, dot.equation), - ReplacementTransform(last_dot.label, dot.label), - ReplacementTransform(last_dot.line_pair, dot.line_pair), - ] - self.play(*anims) - self.wait() - last_dot = dot - -class FactorOrdinaryNumber(TeacherStudentsScene): - def construct(self): - equation = TexMobject( - "2{,}250", "=", "2 \\cdot 3^2 \\cdot 5^3" - ) - equation.next_to(self.get_pi_creatures(), UP, LARGE_BUFF) - number = equation[0] - alt_rhs_list = list(it.starmap(TexMobject, [ - ("\\ne", "2^2 \\cdot 563"), - ("\\ne", "2^2 \\cdot 3 \\cdot 11 \\cdot 17"), - ("\\ne", "2 \\cdot 7^2 \\cdot 23"), - ("=", "(-2) \\cdot (-3) \\cdot (3) \\cdot 5^3"), - ("=", "2 \\cdot (-3) \\cdot (3) \\cdot (-5) \\cdot 5^2"), - ])) - for alt_rhs in alt_rhs_list: - if "\\ne" in alt_rhs.get_tex_string(): - alt_rhs.set_color(RED) - else: - alt_rhs.set_color(GREEN) - alt_rhs.move_to(equation.get_right()) - number.save_state() - number.next_to(self.teacher, UP+LEFT) - title = TextMobject("Almost", "Unique factorization") - title.set_color_by_tex("Almost", YELLOW) - title.to_edge(UP) - - self.play( - self.teacher.change_mode, "raise_right_hand", - Write(number) - ) - self.wait(2) - self.play( - number.restore, - Write(VGroup(*equation[1:])), - Write(title[1]) - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = equation, - added_anims = [self.teacher.change_mode, "happy"] - ) - self.wait() - last_alt_rhs = None - for alt_rhs in alt_rhs_list: - equation.generate_target() - equation.target.next_to(alt_rhs, LEFT) - anims = [MoveToTarget(equation)] - if last_alt_rhs: - anims += [ReplacementTransform(last_alt_rhs, alt_rhs)] - else: - anims += [FadeIn(alt_rhs)] - self.play(*anims) - if alt_rhs is alt_rhs_list[-2]: - self.change_student_modes( - *["sassy"]*3, - look_at_arg = alt_rhs, - added_anims = [Write(title[0])] - ) - self.wait(2) - last_alt_rhs = alt_rhs - - self.play( - FadeOut(VGroup(equation, alt_rhs)), - PiCreatureSays( - self.teacher, - "It's similar for \\\\ Gaussian integers", - bubble_kwargs = {"height" : 3.5} - ) - ) - self.change_student_modes(*["happy"]*3) - self.wait(3) - -class IntroduceGaussianPrimes(LatticePointScene, PiCreatureScene): - CONFIG = { - "plane_center" : LEFT, - "x_radius" : 13, - } - def create_pi_creature(self): - morty = Mortimer().flip() - morty.scale(0.7) - morty.next_to(ORIGIN, UP, buff = 0) - morty.to_edge(LEFT) - return morty - - def setup(self): - LatticePointScene.setup(self) - PiCreatureScene.setup(self) - self.remove(self.pi_creature) - - def construct(self): - self.plane.set_stroke(width = 2) - morty = self.pi_creature - dots = [ - Dot(self.plane.coords_to_point(*coords)) - for coords in [ - (5, 0), - (2, 1), (2, -1), - (-1, 2), (-1, -2), - (-2, -1), (-2, 1), - ] - ] - five_dot = dots[0] - five_dot.set_color(YELLOW) - p_dots = VGroup(*dots[1:]) - p1_dot, p2_dot, p3_dot, p4_dot, p5_dot, p6_dot = p_dots - VGroup(p1_dot, p3_dot, p5_dot).set_color(PINK) - VGroup(p2_dot, p4_dot, p6_dot).set_color(RED) - - labels = [ - TexMobject(tex).add_background_rectangle() - for tex in ("5", "2+i", "2-i", "-1+2i", "-1-2i", "-2-i", "-2+i") - ] - five_label, p1_label, p2_label, p3_label, p4_label, p5_label, p6_label = labels - vects = [ - DOWN, - UP+RIGHT, DOWN+RIGHT, - UP+LEFT, DOWN+LEFT, - DOWN+LEFT, UP+LEFT, - ] - for dot, label, vect in zip(dots, labels, vects): - label.next_to(dot, vect, SMALL_BUFF) - - arc_angle = 0.8*np.pi - times_i_arc = Arrow( - p1_dot.get_top(), p3_dot.get_top(), - path_arc = arc_angle - ) - times_neg_i_arc = Arrow( - p2_dot.get_bottom(), p4_dot.get_bottom(), - path_arc = -arc_angle - ) - times_i = TexMobject("\\times i") - times_i.add_background_rectangle() - times_i.next_to( - times_i_arc.point_from_proportion(0.5), - UP - ) - times_neg_i = TexMobject("\\times (-i)") - times_neg_i.add_background_rectangle() - times_neg_i.next_to( - times_neg_i_arc.point_from_proportion(0.5), - DOWN - ) - VGroup( - times_i, times_neg_i, times_i_arc, times_neg_i_arc - ).set_color(MAROON_B) - - gaussian_prime = TextMobject("$\\Rightarrow$ ``Gaussian prime''") - gaussian_prime.add_background_rectangle() - gaussian_prime.scale(0.9) - gaussian_prime.next_to(p1_label, RIGHT) - - factorization = TexMobject( - "5", "= (2+i)(2-i)" - ) - factorization.to_corner(UP+RIGHT) - factorization.shift(1.5*LEFT) - factorization.add_background_rectangle() - neg_alt_factorization = TexMobject("=(-2-i)(-2+i)") - i_alt_factorization = TexMobject("=(-1+2i)(-1-2i)") - for alt_factorization in neg_alt_factorization, i_alt_factorization: - alt_factorization.next_to( - factorization.get_part_by_tex("="), DOWN, - aligned_edge = LEFT - ) - alt_factorization.add_background_rectangle() - - for dot in dots: - dot.add(Line( - self.plane_center, - dot.get_center(), - color = dot.get_color() - )) - - self.add(factorization) - self.play( - DrawBorderThenFill(five_dot), - FadeIn(five_label) - ) - self.wait() - self.play( - ReplacementTransform( - VGroup(five_dot).copy(), - VGroup(p1_dot, p2_dot) - ) - ) - self.play(*list(map(Write, [p1_label, p2_label]))) - self.wait() - self.play(Write(gaussian_prime)) - self.wait() - - #Show morty - self.play(FadeIn(morty)) - self.play(PiCreatureSays( - morty, "\\emph{Almost} unique", - bubble_kwargs = {"height" : 2, "width" : 5}, - )) - self.wait() - self.play(RemovePiCreatureBubble(morty, target_mode = "pondering")) - - #Show neg_alternate expression - movers = [p1_dot, p2_dot, p1_label, p2_label] - for mover in movers: - mover.save_state() - self.play( - Transform(p1_dot, p5_dot), - Transform(p1_label, p5_label), - ) - self.play( - Transform(p2_dot, p6_dot), - Transform(p2_label, p6_label), - ) - self.play(Write(neg_alt_factorization)) - self.wait() - self.play( - FadeOut(neg_alt_factorization), - *[m.restore for m in movers] - ) - self.wait() - - ##Show i_alternate expression - self.play( - ShowCreation(times_i_arc), - FadeIn(times_i), - *[ - ReplacementTransform( - mob1.copy(), mob2, - path_arc = np.pi/2 - ) - for mob1, mob2 in [ - (p1_dot, p3_dot), - (p1_label, p3_label), - ] - ] - ) - self.wait() - self.play( - ShowCreation(times_neg_i_arc), - FadeIn(times_neg_i), - *[ - ReplacementTransform( - mob1.copy(), mob2, - path_arc = -np.pi/2 - ) - for mob1, mob2 in [ - (p2_dot, p4_dot), - (p2_label, p4_label), - ] - ] - ) - self.wait() - self.play(Write(i_alt_factorization)) - self.change_mode("hesitant") - self.wait(3) - -class FromIntegerFactorsToGaussianFactors(TeacherStudentsScene): - def construct(self): - expression = TexMobject( - "30", "=", "2", "\\cdot", "3", "\\cdot", "5" - ) - expression.shift(2*UP) - two = expression.get_part_by_tex("2") - five = expression.get_part_by_tex("5") - two.set_color(BLUE) - five.set_color(GREEN) - two.factors = TexMobject("(1+i)", "(1-i)") - five.factors = TexMobject("(2+i)", "(2-i)") - for mob, vect in (two, DOWN), (five, UP): - mob.factors.next_to(mob, vect, LARGE_BUFF) - mob.factors.set_color(mob.get_color()) - mob.arrows = VGroup(*[ - Arrow( - mob.get_edge_center(vect), - factor.get_edge_center(-vect), - color = mob.get_color(), - tip_length = 0.15 - ) - for factor in mob.factors - ]) - - self.add(expression) - for mob in two, five: - self.play( - ReplacementTransform( - mob.copy(), - mob.factors - ), - *list(map(ShowCreation, mob.arrows)) - ) - self.wait() - self.play(*[ - ApplyMethod(pi.change, "pondering", expression) - for pi in self.get_pi_creatures() - ]) - self.wait(5) - group = VGroup( - expression, - two.arrows, two.factors, - five.arrows, five.factors, - ) - self.teacher_says( - "Now for a \\\\ surprising fact...", - added_anims = [FadeOut(group)] - ) - self.wait(2) - -class FactorizationPattern(Scene): - def construct(self): - self.force_skipping() - - self.add_number_line() - self.show_one_mod_four_primes() - self.show_three_mod_four_primes() - self.ask_why_this_is_true() - self.show_two() - - def add_number_line(self): - line = NumberLine( - x_min = 0, - x_max = 36, - unit_size = 0.4, - numbers_to_show = list(range(0, 33, 4)), - numbers_with_elongated_ticks = list(range(0, 33, 4)), - ) - line.shift(2*DOWN) - line.to_edge(LEFT) - line.add_numbers() - - self.add(line) - self.number_line = line - - def show_one_mod_four_primes(self): - primes = [5, 13, 17, 29] - dots = VGroup(*[ - Dot(self.number_line.number_to_point(prime)) - for prime in primes - ]) - dots.set_color(GREEN) - prime_mobs = VGroup(*list(map(TexMobject, list(map(str, primes))))) - arrows = VGroup() - for prime_mob, dot in zip(prime_mobs, dots): - prime_mob.next_to(dot, UP, LARGE_BUFF) - prime_mob.set_color(dot.get_color()) - arrow = Arrow(prime_mob, dot, buff = SMALL_BUFF) - arrow.set_color(dot.get_color()) - arrows.add(arrow) - - factorizations = VGroup(*[ - TexMobject("=(%d+%si)(%d-%si)"%(x, y_str, x, y_str)) - for x, y in [(2, 1), (3, 2), (4, 1), (5, 2)] - for y_str in [str(y) if y is not 1 else ""] - ]) - factorizations.arrange(DOWN, aligned_edge = LEFT) - factorizations.to_corner(UP+LEFT) - factorizations.shift(RIGHT) - movers = VGroup() - for p_mob, factorization in zip(prime_mobs, factorizations): - mover = p_mob.copy() - mover.generate_target() - mover.target.next_to(factorization, LEFT) - movers.add(mover) - v_dots = TexMobject("\\vdots") - v_dots.next_to(factorizations[-1][0], DOWN) - factorization.add(v_dots) - - self.play(*it.chain( - list(map(Write, prime_mobs)), - list(map(ShowCreation, arrows)), - list(map(DrawBorderThenFill, dots)), - )) - self.wait() - self.play(*[ - MoveToTarget( - mover, - run_time = 2, - path_arc = np.pi/2, - ) - for mover in movers - ]) - self.play(FadeIn( - factorizations, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait(4) - self.play(*list(map(FadeOut, [movers, factorizations]))) - - def show_three_mod_four_primes(self): - primes = [3, 7, 11, 19, 23, 31] - dots = VGroup(*[ - Dot(self.number_line.number_to_point(prime)) - for prime in primes - ]) - dots.set_color(RED) - prime_mobs = VGroup(*list(map(TexMobject, list(map(str, primes))))) - arrows = VGroup() - for prime_mob, dot in zip(prime_mobs, dots): - prime_mob.next_to(dot, UP, LARGE_BUFF) - prime_mob.set_color(dot.get_color()) - arrow = Arrow(prime_mob, dot, buff = SMALL_BUFF) - arrow.set_color(dot.get_color()) - arrows.add(arrow) - - words = TextMobject("Already Gaussian primes") - words.to_corner(UP+LEFT) - word_arrows = VGroup(*[ - Line( - words.get_bottom(), p_mob.get_top(), - color = p_mob.get_color(), - buff = MED_SMALL_BUFF - ) - for p_mob in prime_mobs - ]) - - self.play(*it.chain( - list(map(Write, prime_mobs)), - list(map(ShowCreation, arrows)), - list(map(DrawBorderThenFill, dots)), - )) - self.wait() - self.play( - Write(words), - *list(map(ShowCreation, word_arrows)) - ) - self.wait(4) - self.play(*list(map(FadeOut, [words, word_arrows]))) - - def ask_why_this_is_true(self): - randy = Randolph(color = BLUE_C) - randy.scale(0.7) - randy.to_edge(LEFT) - randy.shift(0.8*UP) - - links_text = TextMobject("(See links in description)") - links_text.scale(0.7) - links_text.to_corner(UP+RIGHT) - links_text.shift(DOWN) - - self.play(FadeIn(randy)) - self.play(PiCreatureBubbleIntroduction( - randy, "Wait...why?", - bubble_class = ThoughtBubble, - bubble_kwargs = {"height" : 2, "width" : 3}, - target_mode = "confused", - look_at_arg = self.number_line, - )) - self.play(Blink(randy)) - self.wait() - self.play(FadeIn(links_text)) - self.wait(2) - self.play(*list(map(FadeOut, [ - randy, randy.bubble, randy.bubble.content, - links_text - ]))) - - def show_two(self): - two_dot = Dot(self.number_line.number_to_point(2)) - two = TexMobject("2") - two.next_to(two_dot, UP, LARGE_BUFF) - arrow = Arrow(two, two_dot, buff = SMALL_BUFF) - VGroup(two_dot, two, arrow).set_color(YELLOW) - - mover = two.copy() - mover.generate_target() - mover.target.to_corner(UP+LEFT) - factorization = TexMobject("=", "(1+i)", "(1-i)") - factorization.next_to(mover.target, RIGHT) - factors = VGroup(*factorization[1:]) - - time_i_arrow = Arrow( - factors[1].get_bottom(), - factors[0].get_bottom(), - path_arc = -np.pi - ) - times_i = TexMobject("\\times i") - # times_i.scale(1.5) - times_i.next_to(time_i_arrow, DOWN) - times_i.set_color(time_i_arrow.get_color()) - words = TextMobject("You'll see why this matters...") - words.next_to(times_i, DOWN) - words.shift_onto_screen() - - self.play( - Write(two), - ShowCreation(arrow), - DrawBorderThenFill(two_dot) - ) - self.wait() - self.play( - MoveToTarget(mover), - Write(factorization) - ) - - self.revert_to_original_skipping_status() - self.wait(2) - self.play(ShowCreation(time_i_arrow)) - self.play(Write(times_i)) - self.wait(2) - self.play(FadeIn(words)) - self.wait(2) - -class RingsWithOneModFourPrimes(CertainRegularityInPrimes): - CONFIG = { - "plane_center" : ORIGIN, - "primes" : [5, 13, 17, 29, 37, 41, 53], - "include_pi_formula" : False, - } - -class RingsWithThreeModFourPrimes(CertainRegularityInPrimes): - CONFIG = { - "plane_center" : ORIGIN, - "primes" : [3, 7, 11, 19, 23, 31, 43], - "include_pi_formula" : False, - } - -class FactorTwo(LatticePointScene): - CONFIG = { - "y_radius" : 3, - } - def construct(self): - two_dot = Dot(self.plane.coords_to_point(2, 0)) - two_dot.set_color(YELLOW) - factor_dots = VGroup(*[ - Dot(self.plane.coords_to_point(1, u)) - for u in (1, -1) - ]) - two_label = TexMobject("2").next_to(two_dot, DOWN) - two_label.set_color(YELLOW) - two_label.add_background_rectangle() - factor_labels = VGroup(*[ - TexMobject(tex).add_background_rectangle().next_to(dot, vect) - for tex, dot, vect in zip( - ["1+i", "1-i"], factor_dots, [UP, DOWN] - ) - ]) - VGroup(factor_labels, factor_dots).set_color(MAROON_B) - - for dot in it.chain(factor_dots, [two_dot]): - line = Line(self.plane_center, dot.get_center()) - line.set_color(dot.get_color()) - dot.add(line) - - self.play( - ShowCreation(two_dot), - Write(two_label), - ) - self.play(*[ - ReplacementTransform( - VGroup(mob1.copy()), mob2 - ) - for mob1, mob2 in [ - (two_label, factor_labels), - (two_dot, factor_dots), - ] - ]) - self.wait(2) - dot_copy = factor_dots[1].copy() - dot_copy.set_color(RED) - for angle in np.pi/2, -np.pi/2: - self.play(Rotate(dot_copy, angle, run_time = 2)) - self.wait(2) - -class CountThroughRingsCopy(CountThroughRings): - pass - -class NameGaussianIntegersCopy(NameGaussianIntegers): - pass - -class IntroduceRecipe(Scene): - CONFIG = { - "N_string" : "25", - "integer_factors" : [5, 5], - "gaussian_factors" : [ - complex(2, 1), complex(2, -1), - complex(2, 1), complex(2, -1), - ], - "x_color" : GREEN, - "y_color" : RED, - "N_color" : WHITE, - "i_positive_color" : BLUE, - "i_negative_color" : YELLOW, - "i_zero_color" : MAROON_B, - "T_chart_width" : 8, - "T_chart_height" : 6, - } - def construct(self): - self.add_title() - self.show_ordinary_factorization() - self.subfactor_ordinary_factorization() - self.organize_factors_into_columns() - self.mention_conjugate_rule() - self.take_product_of_columns() - self.mark_left_product_as_result() - self.swap_factors() - - def add_title(self): - title = TexMobject( - "\\text{Recipe for }", - "a", "+", "b", "i", - "\\text{ satisfying }", - "(", "a", "+", "b", "i", ")", - "(", "a", "-", "b", "i", ")", - "=", self.N_string - ) - strings = ("a", "b", self.N_string) - colors = (self.x_color, self.y_color, self.N_color) - for tex, color in zip(strings, colors): - title.set_color_by_tex(tex, color, substring = False) - title.to_edge(UP, buff = MED_SMALL_BUFF) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - self.add(title, h_line) - N_mob = title.get_part_by_tex(self.N_string) - digest_locals(self, ["title", "h_line", "N_mob"]) - - def show_ordinary_factorization(self): - N_mob = self.N_mob.copy() - N_mob.generate_target() - N_mob.target.next_to(self.h_line, DOWN) - N_mob.target.to_edge(LEFT) - - factors = self.integer_factors - symbols = ["="] + ["\\cdot"]*(len(factors)-1) - factorization = TexMobject(*it.chain(*list(zip( - symbols, list(map(str, factors)) - )))) - factorization.next_to(N_mob.target, RIGHT) - - self.play(MoveToTarget( - N_mob, - run_time = 2, - path_arc = -np.pi/6 - )) - self.play(Write(factorization)) - self.wait() - - self.factored_N_mob = N_mob - self.integer_factorization = factorization - - def subfactor_ordinary_factorization(self): - factors = self.gaussian_factors - factorization = TexMobject( - "=", *list(map(self.complex_number_to_tex, factors)) - ) - max_width = FRAME_WIDTH - 2 - if factorization.get_width() > max_width: - factorization.set_width(max_width) - factorization.next_to( - self.integer_factorization, DOWN, - aligned_edge = LEFT - ) - for factor, mob in zip(factors, factorization[1:]): - mob.underlying_number = factor - y = complex(factor).imag - if y == 0: - mob.set_color(self.i_zero_color) - elif y > 0: - mob.set_color(self.i_positive_color) - elif y < 0: - mob.set_color(self.i_negative_color) - movers = VGroup() - mover = self.integer_factorization[0].copy() - mover.target = factorization[0] - movers.add(mover) - index = 0 - for prime_mob in self.integer_factorization[1::2]: - gauss_prime = factors[index] - gauss_prime_mob = factorization[index+1] - mover = prime_mob.copy() - mover.target = gauss_prime_mob - movers.add(mover) - if abs(complex(gauss_prime).imag) > 0: - index += 1 - mover = prime_mob.copy() - mover.target = factorization[index+1] - movers.add(mover) - index += 1 - - self.play(LaggedStartMap( - MoveToTarget, - movers, - replace_mobject_with_target_in_scene = True - )) - self.wait() - - self.gaussian_factorization = factorization - - def organize_factors_into_columns(self): - T_chart = self.get_T_chart() - factors = self.gaussian_factorization.copy()[1:] - left_factors, right_factors = self.get_left_and_right_factors() - for group in left_factors, right_factors: - group.generate_target() - group.target.arrange(DOWN) - left_factors.target.next_to(T_chart.left_h_line, DOWN) - right_factors.target.next_to(T_chart.right_h_line, DOWN) - - self.play(ShowCreation(T_chart)) - self.wait() - self.play(MoveToTarget(left_factors)) - self.play(MoveToTarget(right_factors)) - self.wait() - - digest_locals(self, ["left_factors", "right_factors"]) - - def mention_conjugate_rule(self): - left_factors = self.left_factors - right_factors = self.right_factors - double_arrows = VGroup() - for lf, rf in zip(left_factors.target, right_factors.target): - arrow = DoubleArrow( - lf, rf, - buff = SMALL_BUFF, - tip_length = SMALL_BUFF, - color = GREEN - ) - word = TextMobject("Conjugates") - word.scale(0.75) - word.add_background_rectangle() - word.next_to(arrow, DOWN, SMALL_BUFF) - arrow.add(word) - double_arrows.add(arrow) - main_arrow = double_arrows[0] - - self.play(Write(main_arrow, run_time = 1)) - self.wait() - for new_arrow in double_arrows[1:]: - self.play(Transform(main_arrow, new_arrow)) - self.wait() - self.wait() - self.play(FadeOut(main_arrow)) - - def take_product_of_columns(self): - arrows = self.get_product_multiplication_lines() - products = self.get_product_mobjects() - factor_groups = [self.left_factors, self.right_factors] - - for arrow, product, group in zip(arrows, products, factor_groups): - self.play(ShowCreation(arrow)) - self.play(ReplacementTransform( - group.copy(), VGroup(product) - )) - self.wait() - self.wait(3) - - def mark_left_product_as_result(self): - rect = self.get_result_surrounding_rect() - words = TextMobject("Output", " of recipe") - words.next_to(rect, DOWN, buff = MED_LARGE_BUFF) - words.to_edge(LEFT) - arrow = Arrow(words.get_top(), rect.get_left()) - - self.play(ShowCreation(rect)) - self.play( - Write(words, run_time = 2), - ShowCreation(arrow) - ) - self.wait(3) - self.play(*list(map(FadeOut, [words, arrow]))) - - self.output_label_group = VGroup(words, arrow) - - def swap_factors(self): - for i in range(len(self.left_factors)): - self.swap_factors_at_index(i) - self.wait() - - ######### - - def get_left_and_right_factors(self): - factors = self.gaussian_factorization.copy()[1:] - return VGroup(*factors[::2]), VGroup(*factors[1::2]) - - def get_T_chart(self): - T_chart = VGroup() - h_lines = VGroup(*[ - Line(ORIGIN, self.T_chart_width*RIGHT/2.0) - for x in range(2) - ]) - h_lines.arrange(RIGHT, buff = 0) - h_lines.shift(UP) - v_line = Line(self.T_chart_height*UP, ORIGIN) - v_line.move_to(h_lines.get_center(), UP) - - T_chart.left_h_line, T_chart.right_h_line = h_lines - T_chart.v_line = v_line - T_chart.digest_mobject_attrs() - - return T_chart - - def complex_number_to_tex(self, z): - z = complex(z) - x, y = z.real, z.imag - if y == 0: - return "(%d)"%x - y_sign_tex = "+" if y >= 0 else "-" - if abs(y) == 1: - y_str = y_sign_tex + "i" - else: - y_str = y_sign_tex + "%di"%abs(y) - return "(%d%s)"%(x, y_str) - - def get_product_multiplication_lines(self): - lines = VGroup() - for factors in self.left_factors, self.right_factors: - line = Line(ORIGIN, 3*RIGHT) - line.next_to(factors, DOWN, SMALL_BUFF) - times = TexMobject("\\times") - times.next_to(line.get_left(), UP+RIGHT, SMALL_BUFF) - line.add(times) - lines.add(line) - self.multiplication_lines = lines - return lines - - def get_product_mobjects(self): - factor_groups = [self.left_factors, self.right_factors] - product_mobjects = VGroup() - for factors, line in zip(factor_groups, self.multiplication_lines): - product = reduce(op.mul, [ - factor.underlying_number - for factor in factors - ]) - color = average_color(*[ - factor.get_color() - for factor in factors - ]) - product_mob = TexMobject( - self.complex_number_to_tex(product) - ) - product_mob.set_color(color) - product_mob.next_to(line, DOWN) - product_mobjects.add(product_mob) - self.product_mobjects = product_mobjects - return product_mobjects - - def swap_factors_at_index(self, index): - factor_groups = self.left_factors, self.right_factors - factors_to_swap = [group[index] for group in factor_groups] - self.play(*[ - ApplyMethod( - factors_to_swap[i].move_to, factors_to_swap[1-i], - path_arc = np.pi/2, - ) - for i in range(2) - ]) - for i, group in enumerate(factor_groups): - group.submobjects[index] = factors_to_swap[1-i] - self.play(FadeOut(self.product_mobjects)) - self.get_product_mobjects() - rect = self.result_surrounding_rect - new_rect = self.get_result_surrounding_rect() - self.play(*[ - ReplacementTransform(group.copy(), VGroup(product)) - for group, product in zip( - factor_groups, self.product_mobjects, - ) - ]+[ - ReplacementTransform(rect, new_rect) - ]) - self.wait() - - def get_result_surrounding_rect(self, product = None): - if product is None: - product = self.product_mobjects[0] - rect = SurroundingRectangle(product) - self.result_surrounding_rect = rect - return rect - - def write_last_step(self): - output_words, arrow = self.output_label_group - final_step = TextMobject( - "Multiply by $1$, $i$, $-1$ or $-i$" - ) - final_step.scale(0.9) - final_step.next_to(arrow.get_start(), DOWN, SMALL_BUFF) - final_step.shift_onto_screen() - - anims = [Write(final_step)] - if arrow not in self.get_mobjects(): - # arrow = Arrow( - # final_step.get_top(), - # self.result_surrounding_rect.get_left() - # ) - anims += [ShowCreation(arrow)] - self.play(*anims) - self.wait(2) - -class StateThreeChoices(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "$5^2$ gives 3 choices." - ) - self.wait(3) - -class ThreeOutputsAsLatticePoints(LatticePointScene): - CONFIG = { - "coords_list" : [(3, 4), (5, 0), (3, -4)], - "dot_radius" : 0.1, - "colors" : [YELLOW, GREEN, PINK, MAROON_B], - } - def construct(self): - self.add_circle() - self.add_dots_and_labels() - - def add_circle(self): - radius = np.sqrt(self.radius_squared) - circle = self.get_circle(radius) - radial_line, root_label = self.get_radial_line_with_label(radius) - self.add(radial_line, root_label, circle) - self.add_foreground_mobject(root_label) - - def add_dots_and_labels(self): - dots = VGroup(*[ - Dot( - self.plane.coords_to_point(*coords), - radius = self.dot_radius, - color = self.colors[0], - ) - for coords in self.coords_list - ]) - labels = VGroup() - for x, y in self.coords_list: - if y == 0: - y_str = "" - vect = DOWN+RIGHT - elif y > 1: - y_str = "+%di"%y - vect = UP+RIGHT - else: - y_str = "%di"%y - vect = DOWN+RIGHT - label = TexMobject("%d%s"%(x, y_str)) - label.add_background_rectangle() - point = self.plane.coords_to_point(x, y) - label.next_to(point, vect) - labels.add(label) - - for dot, label in zip(dots, labels): - self.play( - FadeIn(label), - DrawBorderThenFill( - dot, - stroke_color = PINK, - stroke_width = 4 - ) - ) - self.wait(2) - - self.original_dots = dots - -class LooksLikeYoureMissingSome(TeacherStudentsScene): - def construct(self): - self.student_says( - "Looks like you're \\\\ missing a few", - target_mode = "sassy", - student_index = 0, - ) - self.play(self.teacher.change, "guilty") - self.wait(3) - -class ShowAlternateFactorizationOfTwentyFive(IntroduceRecipe): - CONFIG = { - "gaussian_factors" : [ - complex(-1, 2), complex(-1, -2), - complex(2, 1), complex(2, -1), - ], - } - def construct(self): - self.add_title() - self.show_ordinary_factorization() - self.subfactor_ordinary_factorization() - self.organize_factors_into_columns() - self.take_product_of_columns() - self.mark_left_product_as_result() - self.swap_factors() - -class WriteAlternateLastStep(IntroduceRecipe): - def construct(self): - self.force_skipping() - self.add_title() - self.show_ordinary_factorization() - self.subfactor_ordinary_factorization() - self.organize_factors_into_columns() - self.take_product_of_columns() - self.mark_left_product_as_result() - self.revert_to_original_skipping_status() - - self.cross_out_output_words() - self.write_last_step() - - def cross_out_output_words(self): - output_words, arrow = self.output_label_group - cross = TexMobject("\\times") - cross.replace(output_words, stretch = True) - cross.set_color(RED) - - self.add(output_words, arrow) - self.play(Write(cross)) - output_words.add(cross) - self.play(output_words.to_edge, DOWN) - -class ThreeOutputsAsLatticePointsContinued(ThreeOutputsAsLatticePoints): - def construct(self): - self.force_skipping() - ThreeOutputsAsLatticePoints.construct(self) - self.revert_to_original_skipping_status() - - self.show_multiplication_by_units() - - def show_multiplication_by_units(self): - original_dots = self.original_dots - lines = VGroup() - for dot in original_dots: - line = Line(self.plane_center, dot.get_center()) - line.set_stroke(dot.get_color(), 6) - lines.add(line) - dot.add(line) - words_group = VGroup(*[ - TextMobject("Multiply by $%s$"%s) - for s in ("1", "i", "-1", "-i") - ]) - for words, color in zip(words_group, self.colors): - words.add_background_rectangle() - words.set_color(color) - words_group.arrange(DOWN, aligned_edge = LEFT) - words_group.to_corner(UP+LEFT, buff = MED_SMALL_BUFF) - angles = [np.pi/2, np.pi, -np.pi/2] - - self.play( - FadeIn(words_group[0]), - *list(map(ShowCreation, lines)) - ) - for words, angle, color in zip(words_group[1:], angles, self.colors[1:]): - self.play(FadeIn(words)) - dots_copy = original_dots.copy() - self.play( - dots_copy.rotate, angle, - dots_copy.set_color, color, - path_arc = angle - ) - self.wait() - self.wait(2) - -class RecipeFor125(IntroduceRecipe): - CONFIG = { - "N_string" : "125", - "integer_factors" : [5, 5, 5], - "gaussian_factors" : [ - complex(2, -1), complex(2, 1), - complex(2, -1), complex(2, 1), - complex(2, -1), complex(2, 1), - ], - } - def construct(self): - self.force_skipping() - - self.add_title() - self.show_ordinary_factorization() - self.subfactor_ordinary_factorization() - - self.revert_to_original_skipping_status() - self.organize_factors_into_columns() - # self.take_product_of_columns() - # self.mark_left_product_as_result() - # self.swap_factors() - # self.write_last_step() - -class StateFourChoices(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "$5^3$ gives 4 choices." - ) - self.wait(3) - -class Show125Circle(ThreeOutputsAsLatticePointsContinued): - CONFIG = { - "radius_squared" : 125, - "coords_list" : [(2, 11), (10, 5), (10, -5), (2, -11)], - "y_radius" : 15, - } - def construct(self): - self.draw_circle() - self.add_dots_and_labels() - self.show_multiplication_by_units() - self.ask_about_two() - - def draw_circle(self): - self.plane.scale(2) - radius = np.sqrt(self.radius_squared) - circle = self.get_circle(radius) - radial_line, root_label = self.get_radial_line_with_label(radius) - - self.play( - Write(root_label), - ShowCreation(radial_line) - ) - self.add_foreground_mobject(root_label) - self.play( - Rotating( - radial_line, - rate_func = smooth, - about_point = self.plane_center - ), - ShowCreation(circle), - run_time = 2, - ) - group = VGroup( - self.plane, radial_line, circle, root_label - ) - self.play(group.scale, 0.5) - -class RecipeFor375(IntroduceRecipe): - CONFIG = { - "N_string" : "375", - "integer_factors" : [3, 5, 5, 5], - "gaussian_factors" : [ - 3, - complex(2, 1), complex(2, -1), - complex(2, 1), complex(2, -1), - complex(2, 1), complex(2, -1), - ], - } - def construct(self): - self.add_title() - self.show_ordinary_factorization() - self.subfactor_ordinary_factorization() - self.organize_factors_into_columns() - self.express_trouble_with_three() - self.take_product_of_columns() - - def express_trouble_with_three(self): - morty = Mortimer().flip().to_corner(DOWN+LEFT) - three = self.gaussian_factorization[1].copy() - three.generate_target() - three.target.next_to(morty, UP, MED_LARGE_BUFF) - - self.play(FadeIn(morty)) - self.play( - MoveToTarget(three), - morty.change, "angry", three.target - ) - self.play(Blink(morty)) - self.wait() - for factors in self.left_factors, self.right_factors: - self.play( - three.next_to, factors, DOWN, - morty.change, "sassy", factors.get_bottom() - ) - self.wait() - self.right_factors.add(three) - self.play(morty.change_mode, "pondering") - - #### - - def get_left_and_right_factors(self): - factors = self.gaussian_factorization.copy()[1:] - return VGroup(*factors[1::2]), VGroup(*factors[2::2]) - -class Show375Circle(LatticePointScene): - CONFIG = { - "y_radius" : 20, - } - def construct(self): - radius = np.sqrt(375) - circle = self.get_circle(radius) - radial_line, root_label = self.get_radial_line_with_label(radius) - - self.play( - ShowCreation(radial_line), - Write(root_label, run_time = 1) - ) - self.add_foreground_mobject(root_label) - self.play( - Rotating( - radial_line, - rate_func = smooth, - about_point = self.plane_center - ), - ShowCreation(circle), - run_time = 2, - ) - group = VGroup( - self.plane, radial_line, root_label, circle - ) - self.wait(2) - -class RecipeFor1125(IntroduceRecipe): - CONFIG = { - "N_string" : "1125", - "integer_factors" : [3, 3, 5, 5, 5], - "gaussian_factors" : [ - 3, 3, - complex(2, 1), complex(2, -1), - complex(2, 1), complex(2, -1), - complex(2, 1), complex(2, -1), - ], - } - def construct(self): - self.add_title() - self.show_ordinary_factorization() - self.subfactor_ordinary_factorization() - self.organize_factors_into_columns() - self.mention_conjugate_rule() - self.take_product_of_columns() - self.mark_left_product_as_result() - self.swap_factors() - self.write_last_step() - - def write_last_step(self): - words = TextMobject( - "Multiply by \\\\ ", - "$1$, $i$, $-1$ or $-i$" - ) - words.scale(0.7) - words.to_corner(DOWN+LEFT) - product = self.product_mobjects[0] - - self.play(Write(words)) - self.wait() - -class Show125CircleSimple(LatticePointScene): - CONFIG = { - "radius_squared" : 125, - "y_radius" : 12, - "max_lattice_point_radius" : 12, - } - def construct(self): - self.plane.set_stroke(width = 1) - radius = np.sqrt(self.radius_squared) - circle = self.get_circle(radius) - radial_line, root_label = self.get_radial_line_with_label(radius) - dots = self.get_lattice_points_on_r_squared_circle(self.radius_squared) - - self.play( - ShowCreation(radial_line), - Write(root_label, run_time = 1) - ) - self.add_foreground_mobject(root_label) - self.play( - Rotating( - radial_line, - rate_func = smooth, - about_point = self.plane_center - ), - ShowCreation(circle), - LaggedStartMap( - DrawBorderThenFill, - dots, - stroke_width = 4, - stroke_color = PINK, - ), - run_time = 2, - ) - self.wait(2) - -class Show1125Circle(Show125CircleSimple): - CONFIG = { - "radius_squraed" : 1125, - "y_radius" : 35, - "max_lattice_point_radius" : 35, - } - -class SummarizeCountingRule(Show125Circle): - CONFIG = { - "dot_radius" : 0.075, - "N_str" : "N", - "rect_opacity" : 1, - } - def construct(self): - self.add_count_words() - self.draw_circle() - self.add_full_screen_rect() - self.talk_through_rules() - self.ask_about_two() - - def add_count_words(self): - words = TextMobject( - "\\# Lattice points \\\\ on $\\sqrt{%s}$ circle"%self.N_str - ) - words.to_corner(UP+LEFT) - words.add_background_rectangle() - self.add(words) - self.count_words = words - - def draw_circle(self): - radius = np.sqrt(self.radius_squared) - circle = self.get_circle(radius) - radial_line, num_root_label = self.get_radial_line_with_label(radius) - root_label = TexMobject("\\sqrt{%s}"%self.N_str) - root_label.next_to(radial_line, UP, SMALL_BUFF) - dots = VGroup(*[ - Dot( - self.plane.coords_to_point(*coords), - radius = self.dot_radius, - color = self.dot_color - ) - for coords in self.coords_list - ]) - for angle in np.pi/2, np.pi, -np.pi/2: - dots.add(*dots.copy().rotate(angle)) - - self.play( - Write(root_label), - ShowCreation(radial_line) - ) - self.play( - Rotating( - radial_line, - rate_func = smooth, - about_point = self.plane_center - ), - ShowCreation(circle), - run_time = 2, - ) - self.play(LaggedStartMap( - DrawBorderThenFill, - dots, - stroke_width = 4, - stroke_color = PINK - )) - self.wait(2) - - def add_full_screen_rect(self): - rect = FullScreenFadeRectangle( - fill_opacity = self.rect_opacity - ) - self.play( - FadeIn(rect), - Animation(self.count_words) - ) - - def talk_through_rules(self): - factorization = TexMobject( - "N =", - "3", "^4", "\\cdot", - "5", "^3", "\\cdot", - "13", "^2" - ) - factorization.next_to(ORIGIN, RIGHT) - factorization.to_edge(UP) - - three, five, thirteen = [ - factorization.get_part_by_tex(str(n), substring = False) - for n in (3, 5, 13) - ] - three_power = factorization.get_part_by_tex("^4") - five_power = factorization.get_part_by_tex("^3") - thirteen_power = factorization.get_part_by_tex("^2") - alt_three_power = five_power.copy().move_to(three_power) - - three_brace = Brace(VGroup(*factorization[1:3]), DOWN) - five_brace = Brace(VGroup(*factorization[3:6]), DOWN) - thirteen_brace = Brace(VGroup(*factorization[6:9]), DOWN) - - three_choices = three_brace.get_tex("(", "1", ")") - five_choices = five_brace.get_tex( - "(", "3", "+", "1", ")" - ) - thirteen_choices = thirteen_brace.get_tex( - "(", "2", "+", "1", ")" - ) - all_choices = VGroup(three_choices, five_choices, thirteen_choices) - for choices in all_choices: - choices.scale(0.75, about_point = choices.get_top()) - thirteen_choices.next_to(five_choices, RIGHT) - three_choices.next_to(five_choices, LEFT) - alt_three_choices = TexMobject("(", "0", ")") - alt_three_choices.scale(0.75) - alt_three_choices.move_to(three_choices, RIGHT) - - - self.play(FadeIn(factorization)) - self.wait() - self.play( - five.set_color, GREEN, - thirteen.set_color, GREEN, - FadeIn(five_brace), - FadeIn(thirteen_brace), - ) - self.wait() - for choices, power in (five_choices, five_power), (thirteen_choices, thirteen_power): - self.play( - Write(VGroup(choices[0], *choices[2:])), - ReplacementTransform( - power.copy(), choices[1] - ) - ) - self.wait() - self.play( - three.set_color, RED, - FadeIn(three_brace) - ) - self.wait() - self.play( - Write(VGroup(three_choices[0], three_choices[2])), - ReplacementTransform( - three_power.copy(), three_choices[1] - ) - ) - self.wait() - - movers = three_power, three_choices - for mob in movers: - mob.save_state() - self.play( - Transform( - three_power, alt_three_power, - path_arc = np.pi - ), - Transform(three_choices, alt_three_choices) - ) - self.wait() - self.play( - *[mob.restore for mob in movers], - path_arc = -np.pi - ) - self.wait() - - equals_four = TexMobject("=", "4") - four = equals_four.get_part_by_tex("4") - four.set_color(YELLOW) - final_choice_words = TextMobject( - "Mutiply", "by $1$, $i$, $-1$ or $-i$" - ) - final_choice_words.set_color(YELLOW) - final_choice_words.next_to(four, DOWN, LARGE_BUFF, LEFT) - final_choice_words.to_edge(RIGHT) - final_choice_arrow = Arrow( - final_choice_words[0].get_top(), - four.get_bottom(), - buff = SMALL_BUFF - ) - - choices_copy = all_choices.copy() - choices_copy.generate_target() - - choices_copy.target.scale(1./0.75) - choices_copy.target.arrange(RIGHT, buff = SMALL_BUFF) - choices_copy.target.next_to(equals_four, RIGHT, SMALL_BUFF) - choices_copy.target.shift(0.25*SMALL_BUFF*DOWN) - self.play( - self.count_words.next_to, equals_four, LEFT, - MoveToTarget(choices_copy), - FadeIn(equals_four) - ) - self.play(*list(map(FadeIn, [final_choice_words, final_choice_arrow]))) - self.wait() - - def ask_about_two(self): - randy = Randolph(color = BLUE_C) - randy.scale(0.7) - randy.to_edge(LEFT) - - self.play(FadeIn(randy)) - self.play(PiCreatureBubbleIntroduction( - randy, "What about \\\\ factors of 2?", - bubble_class = ThoughtBubble, - bubble_kwargs = {"height" : 3, "width" : 3}, - target_mode = "confused", - look_at_arg = self.count_words - )) - self.play(Blink(randy)) - self.wait() - -class ThisIsTheHardestPart(TeacherStudentsScene): - def construct(self): - self.change_student_modes("horrified", "confused", "pleading") - self.teacher_says("This is the \\\\ hardest part") - self.change_student_modes("thinking", "happy", "pondering") - self.wait(2) - -class RecipeFor10(IntroduceRecipe): - CONFIG = { - "N_string" : "10", - "integer_factors" : [2, 5], - "gaussian_factors" : [ - complex(1, 1), complex(1, -1), - complex(2, 1), complex(2, -1), - ], - } - def construct(self): - self.add_title() - self.show_ordinary_factorization() - self.subfactor_ordinary_factorization() - self.organize_factors_into_columns() - self.take_product_of_columns() - self.mark_left_product_as_result() - self.swap_two_factors() - self.write_last_step() - - def swap_two_factors(self): - left = self.left_factors[0] - right = self.right_factors[0] - arrow = Arrow(right, left, buff = SMALL_BUFF) - times_i = TexMobject("\\times i") - times_i.next_to(arrow, DOWN, 0) - times_i.add_background_rectangle() - curr_product = self.product_mobjects[0].copy() - - for x in range(2): - self.swap_factors_at_index(0) - self.play( - ShowCreation(arrow), - Write(times_i, run_time = 1) - ) - self.wait() - self.play(curr_product.to_edge, LEFT) - self.swap_factors_at_index(0) - new_arrow = Arrow( - self.result_surrounding_rect, curr_product, - buff = SMALL_BUFF - ) - self.play( - Transform(arrow, new_arrow), - MaintainPositionRelativeTo(times_i, arrow) - ) - self.wait(2) - self.play(*list(map(FadeOut, [arrow, times_i, curr_product]))) - -class FactorsOfTwoNeitherHelpNorHurt(TeacherStudentsScene): - def construct(self): - words = TextMobject( - "Factors of", "$2^k$", "neither \\\\ help nor hurt" - ) - words.set_color_by_tex("2", YELLOW) - self.teacher_says(words) - self.change_student_modes(*["pondering"]*3) - self.wait(3) - -class EffectOfPowersOfTwo(LatticePointScene): - CONFIG = { - "y_radius" : 9, - "max_lattice_point_radius" : 9, - "square_radii" : [5, 10, 20, 40, 80], - } - def construct(self): - radii = list(map(np.sqrt, self.square_radii)) - circles = list(map(self.get_circle, radii)) - radial_lines, root_labels = list(zip(*list(map( - self.get_radial_line_with_label, radii - )))) - dots_list = list(map( - self.get_lattice_points_on_r_squared_circle, - self.square_radii - )) - groups = [ - VGroup(*mobs) - for mobs in zip(radial_lines, circles, root_labels, dots_list) - ] - group = groups[0] - - self.add(group) - self.play(LaggedStartMap( - DrawBorderThenFill, dots_list[0], - stroke_width = 4, - stroke_color = PINK - )) - self.wait() - for new_group in groups[1:]: - self.play(Transform(group, new_group)) - self.wait(2) - -class NumberTheoryAtItsBest(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Number theory at its best!", - target_mode = "hooray", - run_time = 2, - ) - self.change_student_modes(*["hooray"]*3) - self.wait(3) - -class IntroduceChi(FactorizationPattern): - CONFIG = { - "numbers_list" : [ - list(range(i, 36, d)) - for i, d in [(1, 4), (3, 4), (2, 2)] - ], - "colors" : [GREEN, RED, YELLOW] - } - def construct(self): - self.add_number_line() - self.add_define_chi_label() - for index in range(3): - self.describe_values(index) - self.fade_out_labels() - self.cyclic_pattern() - self.write_multiplicative_label() - self.show_multiplicative() - - - def add_define_chi_label(self): - label = TextMobject("Define $\\chi(n)$:") - chi_expressions = VGroup(*[ - self.get_chi_expression(numbers, color) - for numbers, color in zip( - self.numbers_list, - self.colors - ) - ]) - chi_expressions.scale(0.9) - chi_expressions.arrange( - DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT - ) - chi_expressions.to_corner(UP+RIGHT) - brace = Brace(chi_expressions, LEFT) - label.next_to(brace, LEFT) - - self.play( - Write(label), - GrowFromCenter(brace) - ) - self.define_chi_label = label - self.chi_expressions = chi_expressions - - def describe_values(self, index): - numbers = self.numbers_list[index] - color = self.colors[index] - dots, arrows, labels = self.get_dots_arrows_and_labels( - numbers, color - ) - chi_expression = self.chi_expressions[index] - - self.introduce_dots_arrows_and_labels(dots, arrows, labels) - self.wait() - self.play( - Write(VGroup(*[ - part - for part in chi_expression - if part not in chi_expression.inputs - ])), - *[ - ReplacementTransform(label.copy(), num_mob) - for label, num_mob in zip( - labels, chi_expression.inputs - ) - ]) - self.wait() - - def fade_out_labels(self): - self.play(*list(map(FadeOut, [ - self.last_dots, self.last_arrows, self.last_labels, - self.number_line - ]))) - - def cyclic_pattern(self): - input_range = list(range(1, 9)) - chis = VGroup(*[ - TexMobject("\\chi(%d)"%n) - for n in input_range - ]) - chis.arrange(RIGHT, buff = LARGE_BUFF) - chis.set_stroke(WHITE, width = 1) - numbers = VGroup() - arrows = VGroup() - for chi, n in zip(chis, input_range): - arrow = TexMobject("\\Uparrow") - arrow.next_to(chi, UP, SMALL_BUFF) - arrows.add(arrow) - value = TexMobject(str(chi_func(n))) - for tex, color in zip(["1", "-1", "0"], self.colors): - value.set_color_by_tex(tex, color) - value.next_to(arrow, UP) - numbers.add(value) - group = VGroup(chis, arrows, numbers) - group.set_width(FRAME_WIDTH - LARGE_BUFF) - group.to_edge(DOWN, buff = LARGE_BUFF) - - self.play(*[ - FadeIn( - mob, - run_time = 3, - lag_ratio = 0.5 - ) - for mob in [chis, arrows, numbers] - ]) - - self.play(LaggedStartMap( - ApplyMethod, - numbers, - lambda m : (m.shift, MED_SMALL_BUFF*UP), - rate_func = there_and_back, - lag_ratio = 0.2, - run_time = 6 - )) - - self.wait() - self.play(*list(map(FadeOut, [chis, arrows, numbers]))) - - def write_multiplicative_label(self): - morty = Mortimer() - morty.scale(0.7) - morty.to_corner(DOWN+RIGHT) - - self.play(PiCreatureSays( - morty, "$\\chi$ is ``multiplicative''", - bubble_kwargs = {"height" : 2.5, "width" : 5} - )) - self.play(Blink(morty)) - self.morty = morty - - def show_multiplicative(self): - pairs = [(3, 5), (5, 5), (2, 13), (3, 11)] - expressions = VGroup() - for x, y in pairs: - expression = TexMobject( - "\\chi(%d)"%x, - "\\cdot", - "\\chi(%d)"%y, - "=", - "\\chi(%d)"%(x*y) - ) - braces = [ - Brace(expression[i], UP) - for i in (0, 2, 4) - ] - for brace, n in zip(braces, [x, y, x*y]): - output = chi_func(n) - label = brace.get_tex(str(output)) - label.set_color(self.number_to_color(output)) - brace.add(label) - expression.add(brace) - expressions.add(expression) - - expressions.next_to(ORIGIN, LEFT) - expressions.shift(DOWN) - expression = expressions[0] - - self.play( - FadeIn(expression), - self.morty.change, "pondering", expression - ) - self.wait(2) - for new_expression in expressions[1:]: - self.play(Transform(expression, new_expression)) - self.wait(2) - - - - - ######### - - def get_dots_arrows_and_labels(self, numbers, color): - dots = VGroup() - arrows = VGroup() - labels = VGroup() - for number in numbers: - point = self.number_line.number_to_point(number) - dot = Dot(point) - label = TexMobject(str(number)) - label.scale(0.8) - label.next_to(dot, UP, LARGE_BUFF) - arrow = Arrow(label, dot, buff = SMALL_BUFF) - VGroup(dot, label, arrow).set_color(color) - dots.add(dot) - arrows.add(arrow) - labels.add(label) - return dots, arrows, labels - - def introduce_dots_arrows_and_labels(self, dots, arrows, labels): - if hasattr(self, "last_dots"): - self.play( - ReplacementTransform(self.last_dots, dots), - ReplacementTransform(self.last_arrows, arrows), - ReplacementTransform(self.last_labels, labels), - ) - else: - self.play( - Write(labels), - FadeIn(arrows, lag_ratio = 0.5), - LaggedStartMap( - DrawBorderThenFill, dots, - stroke_width = 4, - stroke_color = YELLOW - ), - run_time = 2 - ) - self.last_dots = dots - self.last_arrows = arrows - self.last_labels = labels - - def get_chi_expression(self, numbers, color, num_terms = 4): - truncated_numbers = numbers[:num_terms] - output = str(chi_func(numbers[0])) - result = TexMobject(*it.chain(*[ - ["\\chi(", str(n), ")", "="] - for n in truncated_numbers - ] + [ - ["\\cdots =", output] - ])) - result.inputs = VGroup() - for n in truncated_numbers: - num_mob = result.get_part_by_tex(str(n), substring = False) - num_mob.set_color(color) - result.inputs.add(num_mob) - result.set_color_by_tex(output, color, substring = False) - return result - - def number_to_color(self, n): - output = chi_func(n) - if n == 1: - return self.colors[0] - elif n == -1: - return self.colors[1] - else: - return self.colors[2] - -class WriteCountingRuleWithChi(SummarizeCountingRule): - CONFIG = { - "colors" : [GREEN, RED, YELLOW] - } - def construct(self): - self.add_count_words() - self.draw_circle() - self.add_full_screen_rect() - - self.add_factorization_and_rule() - self.write_chi_expression() - self.walk_through_expression_terms() - self.circle_four() - - def add_factorization_and_rule(self): - factorization = TexMobject( - "N", "=", - "2", "^2", "\\cdot", - "3", "^4", "\\cdot", - "5", "^3", - ) - for tex, color in zip(["5", "3", "2"], self.colors): - factorization.set_color_by_tex(tex, color, substring = False) - factorization.to_edge(UP) - factorization.shift(LEFT) - - count = VGroup( - TexMobject("=", "4"), - TexMobject("(", "1", ")"), - TexMobject("(", "1", ")"), - TexMobject("(", "3+1", ")"), - ) - count.arrange(RIGHT, buff = SMALL_BUFF) - for i, color in zip([3, 2, 1], self.colors): - count[i][1].set_color(color) - count.next_to( - factorization.get_part_by_tex("="), DOWN, - buff = LARGE_BUFF, - aligned_edge = LEFT - ) - - self.play( - FadeIn(factorization), - self.count_words.next_to, count, LEFT - ) - self.wait() - self.play(*[ - ReplacementTransform( - VGroup(factorization.get_part_by_tex( - tex, substring = False - )).copy(), - part - ) - for tex, part in zip(["=", "2", "3", "5"], count) - ]) - self.wait() - - self.factorization = factorization - self.count = count - - def write_chi_expression(self): - equals_four = TexMobject("=", "4") - expression = VGroup(equals_four) - for n, k, color in zip([2, 3, 5], [2, 4, 3], reversed(self.colors)): - args = ["(", "\\chi(", "1", ")", "+"] - for i in range(1, k+1): - args += ["\\chi(", str(n), "^%d"%i, ")", "+"] - args[-1] = ")" - factor = TexMobject(*args) - factor.set_color_by_tex(str(n), color, substring = False) - factor.set_color_by_tex("1", color, substring = False) - factor.scale(0.8) - expression.add(factor) - expression.arrange( - DOWN, buff = MED_SMALL_BUFF, aligned_edge = LEFT - ) - equals_four.next_to(expression[1], LEFT, SMALL_BUFF) - expression.shift( - self.count[0].get_center() + LARGE_BUFF*DOWN -\ - equals_four.get_center() - ) - - count_copy = self.count.copy() - self.play(*[ - ApplyMethod( - c_part.move_to, e_part, LEFT, - path_arc = -np.pi/2, - run_time = 2 - ) - for c_part, e_part in zip(count_copy, expression) - ]) - self.wait() - self.play(ReplacementTransform( - count_copy, expression, - run_time = 2 - )) - self.wait() - - self.chi_expression = expression - - def walk_through_expression_terms(self): - rect = FullScreenFadeRectangle() - groups = [ - VGroup( - self.chi_expression[index], - self.count[index], - self.factorization.get_part_by_tex(tex1, substring = False), - self.factorization.get_part_by_tex(tex2, substring = False), - ) - for index, tex1, tex2 in [ - (-1, "5", "^3"), (-2, "3", "^4"), (-3, "2", "^2") - ] - ] - evaluation_strings = [ - "(1+1+1+1)", - "(1-1+1-1+1)", - "(1+0+0)", - ] - - for group, tex in zip(groups, evaluation_strings): - chi_sum, count, base, exp = group - brace = Brace(chi_sum, DOWN) - evaluation = brace.get_tex(*tex) - evaluation.set_color(base.get_color()) - evaluation_rect = BackgroundRectangle(evaluation) - - self.play(FadeIn(rect), Animation(group)) - self.play(GrowFromCenter(brace)) - self.play( - FadeIn(evaluation_rect), - ReplacementTransform(chi_sum.copy(), evaluation), - ) - self.wait(2) - self.play(Indicate(count, color = PINK)) - self.wait() - if base.get_tex_string() is "3": - new_exp = TexMobject("3") - new_exp.replace(exp) - count_num = count[1] - new_count = TexMobject("0") - new_count.replace(count_num, dim_to_match = 1) - new_count.set_color(count_num.get_color()) - evaluation_point = VectorizedPoint(evaluation[-4].get_right()) - chi_sum_point = VectorizedPoint(chi_sum[-7].get_right()) - new_brace = Brace(VGroup(*chi_sum[:-6]), DOWN) - - to_save = [brace, exp, evaluation, count_num, chi_sum] - for mob in to_save: - mob.save_state() - - self.play(FocusOn(exp)) - self.play(Transform(exp, new_exp)) - self.play( - Transform(brace, new_brace), - Transform( - VGroup(*evaluation[-3:-1]), - evaluation_point - ), - evaluation[-1].next_to, evaluation_point, RIGHT, SMALL_BUFF, - Transform( - VGroup(*chi_sum[-6:-1]), - chi_sum_point - ), - chi_sum[-1].next_to, chi_sum_point, RIGHT, SMALL_BUFF - ) - self.play(Transform(count_num, new_count)) - self.play(Indicate(count_num, color = PINK)) - self.wait() - self.play(*[mob.restore for mob in to_save]) - - self.play( - FadeOut(VGroup( - rect, brace, evaluation_rect, evaluation - )), - Animation(group) - ) - - def circle_four(self): - four = self.chi_expression[0][1] - rect = SurroundingRectangle(four) - - self.revert_to_original_skipping_status() - self.play(ShowCreation(rect)) - self.wait(3) - -class WeAreGettingClose(TeacherStudentsScene): - def construct(self): - self.teacher_says("We're getting close...") - self.change_student_modes(*["hooray"]*3) - self.wait(2) - -class ExpandCountWith45(SummarizeCountingRule): - CONFIG = { - "N_str" : "45", - "coords_list" : [(3, 6), (6, 3)], - "radius_squared" : 45, - "y_radius" : 7, - "rect_opacity" : 0.75, - } - def construct(self): - self.add_count_words() - self.draw_circle() - self.add_full_screen_rect() - self.add_factorization_and_count() - self.expand_expression() - self.show_divisor_sum() - - - def add_factorization_and_count(self): - factorization = TexMobject( - "45", "=", "3", "^2", "\\cdot", "5", - ) - for tex, color in zip(["5", "3",], [GREEN, RED]): - factorization.set_color_by_tex(tex, color, substring = False) - factorization.to_edge(UP) - factorization.shift(1.7*LEFT) - - equals_four = TexMobject("=", "4") - expression = VGroup(equals_four) - for n, k, color in zip([3, 5], [2, 1], [RED, GREEN]): - args = ["("] - ["\\chi(1)", "+"] - for i in range(k+1): - if i == 0: - input_str = "1" - elif i == 1: - input_str = str(n) - else: - input_str = "%d^%d"%(n, i) - args += ["\\chi(%s)"%input_str, "+"] - args[-1] = ")" - factor = TexMobject(*args) - for part in factor[1::2]: - part[2].set_color(color) - factor.scale(0.8) - expression.add(factor) - expression.arrange(RIGHT, buff = SMALL_BUFF) - expression.next_to( - factorization[1], DOWN, - buff = LARGE_BUFF, - aligned_edge = LEFT, - ) - braces = VGroup(*[ - Brace(part, UP) - for part in expression[1:] - ]) - for brace, num, color in zip(braces, [1, 2], [RED, GREEN]): - num_mob = brace.get_tex(str(num), buff = SMALL_BUFF) - num_mob.set_color(color) - brace.add(num_mob) - - self.play( - FadeIn(factorization), - self.count_words.next_to, expression, LEFT - ) - self.wait() - self.play(*[ - ReplacementTransform( - VGroup(factorization.get_part_by_tex( - tex, substring = False - )).copy(), - part - ) - for tex, part in zip(["=", "3", "5"], expression) - ]) - self.play(FadeIn(braces)) - self.wait() - - self.chi_expression = expression - self.factorization = factorization - - def expand_expression(self): - equals, four, lp, rp = list(map(TexMobject, [ - "=", "4", "\\big(", "\\big)" - ])) - expansion = VGroup(equals, four, lp) - chi_pairs = list(it.product(*[ - factor[1::2] - for factor in self.chi_expression[1:] - ])) - num_pairs = list(it.product([1, 3, 9], [1, 5])) - products = list(it.starmap(op.mul, num_pairs)) - sorted_indices = np.argsort(products) - mover_groups = [VGroup(), VGroup()] - plusses = VGroup() - prime_pairs = VGroup() - for index in sorted_indices: - pair = chi_pairs[index] - prime_pair = VGroup() - for chi, movers in zip(pair, mover_groups): - mover = chi.copy() - mover.generate_target() - expansion.add(mover.target) - movers.add(mover) - prime_pair.add(mover.target[2]) - prime_pairs.add(prime_pair) - if index != sorted_indices[-1]: - plus = TexMobject("+") - plusses.add(plus) - expansion.add(plus) - expansion.add(rp) - expansion.arrange(RIGHT, buff = SMALL_BUFF) - expansion.set_width(FRAME_WIDTH - LARGE_BUFF) - expansion.next_to(ORIGIN, UP) - rect = BackgroundRectangle(expansion) - rect.stretch_in_place(1.5, 1) - - self.play( - FadeIn(rect), - *[ - ReplacementTransform( - self.chi_expression[i][j].copy(), - mob - ) - for i, j, mob in [ - (0, 0, equals), - (0, 1, four), - (1, 0, lp), - (2, -1, rp), - ] - ] - ) - for movers in mover_groups: - self.wait() - self.play(movers.next_to, rect, DOWN) - self.play(*list(map(MoveToTarget, movers))) - self.play(Write(plusses)) - self.wait() - - self.expansion = expansion - self.prime_pairs = prime_pairs - - def show_divisor_sum(self): - equals, four, lp, rp = list(map(TexMobject, [ - "=", "4", "\\big(", "\\big)" - ])) - divisor_sum = VGroup(equals, four, lp) - - num_pairs = list(it.product([1, 3, 9], [1, 5])) - products = list(it.starmap(op.mul, num_pairs)) - products.sort() - color = BLACK - product_mobs = VGroup() - chi_mobs = VGroup() - for product in products: - chi_mob = TexMobject("\\chi(", str(product), ")") - product_mob = chi_mob.get_part_by_tex(str(product)) - product_mob.set_color(color) - product_mobs.add(product_mob) - divisor_sum.add(chi_mob) - chi_mobs.add(chi_mob) - if product != products[-1]: - divisor_sum.add(TexMobject("+")) - divisor_sum.add(rp) - divisor_sum.arrange(RIGHT, buff = SMALL_BUFF) - divisor_sum.next_to(self.expansion, DOWN, MED_LARGE_BUFF) - rect = BackgroundRectangle(divisor_sum) - - prime_pairs = self.prime_pairs.copy() - for prime_pair, product_mob in zip(prime_pairs, product_mobs): - prime_pair.target = product_mob.copy() - prime_pair.target.set_color(YELLOW) - - braces = VGroup(*[Brace(m, DOWN) for m in chi_mobs]) - for brace, product in zip(braces, products): - value = brace.get_tex(str(chi_func(product))) - brace.add(value) - - self.play( - FadeIn(rect), - Write(divisor_sum, run_time = 2) - ) - self.play(LaggedStartMap( - MoveToTarget, prime_pairs, - run_time = 4, - lag_ratio = 0.25, - )) - self.remove(prime_pairs) - product_mobs.set_color(YELLOW) - self.wait(2) - self.play(LaggedStartMap( - ApplyMethod, - product_mobs, - lambda m : (m.shift, MED_LARGE_BUFF*DOWN), - rate_func = there_and_back - )) - self.play(FadeIn( - braces, - run_time = 2, - lag_ratio = 0.5, - )) - self.wait(2) - -class CountLatticePointsInBigCircle(LatticePointScene): - CONFIG = { - "y_radius" : 2*11, - "max_lattice_point_radius" : 10, - "dot_radius" : 0.05 - } - def construct(self): - self.resize_plane() - self.introduce_points() - self.show_rings() - self.ignore_center_dot() - - def resize_plane(self): - self.plane.set_stroke(width = 2) - self.plane.scale(2) - self.lattice_points.scale(2) - for point in self.lattice_points: - point.scale_in_place(0.5) - - def introduce_points(self): - circle = self.get_circle(radius = self.max_lattice_point_radius) - radius = Line(ORIGIN, circle.get_right()) - radius.set_color(RED) - R = TexMobject("R").next_to(radius, UP) - R_rect = BackgroundRectangle(R) - R_group = VGroup(R_rect, R) - pi_R_squared = TexMobject("\\pi", "R", "^2") - pi_R_squared.next_to(ORIGIN, UP) - pi_R_squared.to_edge(RIGHT) - pi_R_squared_rect = BackgroundRectangle(pi_R_squared) - pi_R_squared_group = VGroup(pi_R_squared_rect, pi_R_squared) - - self.play(*list(map(FadeIn, [circle, radius, R_group]))) - self.add_foreground_mobject(R_group) - self.draw_lattice_points() - self.wait() - self.play( - FadeOut(R_rect), - FadeIn(pi_R_squared_rect), - ReplacementTransform(R, pi_R_squared.get_part_by_tex("R")), - Write(VGroup(*[ - part for part in pi_R_squared - if part is not pi_R_squared.get_part_by_tex("R") - ])) - ) - self.remove(R_group) - self.add_foreground_mobject(pi_R_squared_group) - self.wait() - - self.circle = circle - self.radius = radius - - def show_rings(self): - N_range = list(range(self.max_lattice_point_radius**2)) - rings = VGroup(*[ - self.get_circle(radius = np.sqrt(N)) - for N in N_range - ]) - rings.set_color_by_gradient(TEAL, GREEN) - rings.set_stroke(width = 2) - dot_groups = VGroup(*[ - self.get_lattice_points_on_r_squared_circle(N) - for N in N_range - ]) - radicals = self.get_radicals() - - self.play( - LaggedStartMap(FadeIn, rings), - Animation(self.lattice_points), - LaggedStartMap(FadeIn, radicals), - run_time = 3 - ) - self.add_foreground_mobject(radicals) - self.play( - LaggedStartMap( - ApplyMethod, - dot_groups, - lambda m : (m.set_stroke, PINK, 5), - rate_func = there_and_back, - run_time = 4, - lag_ratio = 0.1, - ), - ) - self.wait() - - self.rings = rings - - def ignore_center_dot(self): - center_dot = self.lattice_points[0] - circle = Circle(color = RED) - circle.replace(center_dot) - circle.scale_in_place(2) - arrow = Arrow(ORIGIN, UP+RIGHT, color = RED) - arrow.next_to(circle, DOWN+LEFT, SMALL_BUFF) - new_max = 2*self.max_lattice_point_radius - new_dots = VGroup(*[ - Dot( - self.plane.coords_to_point(x, y), - color = self.dot_color, - radius = self.dot_radius, - ) - for x in range(-new_max, new_max+1) - for y in range(-new_max, new_max+1) - if (x**2 + y**2) > self.max_lattice_point_radius**2 - if (x**2 + y**2) < new_max**2 - ]) - new_dots.sort(get_norm) - - self.play(*list(map(ShowCreation, [circle, arrow]))) - self.play(*list(map(FadeOut, [circle, arrow]))) - self.play(FadeOut(center_dot)) - self.lattice_points.remove(center_dot) - self.wait() - self.play(*[ - ApplyMethod(m.scale, 0.5) - for m in [ - self.plane, - self.circle, - self.radius, - self.rings, - self.lattice_points - ] - ]) - new_dots.scale(0.5) - self.play(FadeOut(self.rings)) - self.play( - ApplyMethod( - VGroup(self.circle, self.radius).scale_in_place, 2, - rate_func=linear, - ), - LaggedStartMap( - DrawBorderThenFill, - new_dots, - stroke_width = 4, - stroke_color = PINK, - lag_ratio = 0.2, - ), - run_time = 4, - ) - self.wait(2) - - ##### - - @staticmethod - def get_radicals(): - radicals = VGroup(*[ - TexMobject("\\sqrt{%d}"%N) - for N in range(1, 13) - ]) - radicals.add( - TexMobject("\\vdots"), - TexMobject("\\sqrt{R^2}") - ) - radicals.arrange(DOWN, buff = MED_SMALL_BUFF) - radicals.set_height(FRAME_HEIGHT - MED_LARGE_BUFF) - radicals.to_edge(DOWN, buff = MED_SMALL_BUFF) - radicals.to_edge(LEFT) - for radical in radicals: - radical.add_background_rectangle() - return radicals - -class AddUpGrid(Scene): - def construct(self): - self.add_radicals() - self.add_row_lines() - self.add_chi_sums() - self.put_four_in_corner() - self.talk_through_rows() - self.organize_into_columns() - self.add_count_words() - self.collapse_columns() - self.factor_out_R() - self.show_chi_sum_values() - self.compare_to_pi_R_squared() - self.celebrate() - - def add_radicals(self): - self.radicals = CountLatticePointsInBigCircle.get_radicals() - self.add(self.radicals) - - def add_row_lines(self): - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS - MED_LARGE_BUFF) - h_line.set_stroke(WHITE, 1) - row_lines = VGroup(*[ - h_line.copy().next_to( - radical, DOWN, - buff = SMALL_BUFF, - aligned_edge = LEFT - ) - for radical in self.radicals - ]) - row_lines[-2].shift( - row_lines[-1].get_left()[0]*RIGHT -\ - row_lines[-2].get_left()[0]*RIGHT - ) - - self.play(LaggedStartMap(ShowCreation, row_lines)) - self.wait() - - self.row_lines = row_lines - - def add_chi_sums(self): - chi_sums = VGroup() - chi_mobs = VGroup() - plusses = VGroup() - fours = VGroup() - parens = VGroup() - arrows = VGroup() - for N, radical in zip(list(range(1, 13)), self.radicals): - arrow, four, lp, rp = list(map(TexMobject, [ - "\\Rightarrow", "4", "\\big(", "\\big)" - ])) - fours.add(four) - parens.add(lp, rp) - arrows.add(arrow) - chi_sum = VGroup(arrow, four, lp) - for d in range(1, N+1): - if N%d != 0: - continue - chi_mob = TexMobject("\\chi(", str(d), ")") - chi_mob[1].set_color(YELLOW) - chi_mob.d = d - chi_mobs.add(chi_mob) - chi_sum.add(chi_mob) - if d != N: - plus = TexMobject("+") - plus.chi_mob = chi_mob - plusses.add(plus) - chi_sum.add(plus) - chi_sum.add(rp) - chi_sum.arrange(RIGHT, buff = SMALL_BUFF) - chi_sum.scale(0.7) - chi_sum.next_to(radical, RIGHT) - chi_sums.add(chi_sum) - radical.chi_sum = chi_sum - - self.play(LaggedStartMap( - Write, chi_sums, - run_time = 5, - rate_func = lambda t : t, - )) - self.wait() - - digest_locals(self, [ - "chi_sums", "chi_mobs", "plusses", - "fours", "parens", "arrows", - ]) - - def put_four_in_corner(self): - corner_four = TexMobject("4") - corner_four.to_corner(DOWN+RIGHT, buff = MED_SMALL_BUFF) - rect = SurroundingRectangle(corner_four, color = BLUE) - corner_four.rect = rect - - self.play( - ReplacementTransform( - self.fours, VGroup(corner_four), - run_time = 2, - ), - FadeOut(self.parens) - ) - self.play(ShowCreation(rect)) - - self.corner_four = corner_four - - def talk_through_rows(self): - rect = Rectangle( - stroke_width = 0, - fill_color = BLUE_C, - fill_opacity = 0.3, - ) - rect.stretch_to_fit_width( - VGroup(self.radicals, self.chi_mobs).get_width() - ) - rect.stretch_to_fit_height(self.radicals[0].get_height()) - - composite_rects, prime_rects = [ - VGroup(*[ - rect.copy().move_to(self.radicals[N-1], LEFT) - for N in numbers - ]) - for numbers in ([6, 12], [2, 3, 5, 7, 11]) - ] - prime_rects.set_color(GREEN) - - randy = Randolph().flip() - randy.next_to(self.chi_mobs, RIGHT) - - self.play(FadeIn(randy)) - self.play(randy.change_mode, "pleading") - self.play( - FadeIn(composite_rects), - randy.look_at, composite_rects.get_bottom() - ) - self.wait(2) - self.play( - FadeOut(composite_rects), - FadeIn(prime_rects), - randy.look_at, prime_rects.get_top(), - ) - self.play(Blink(randy)) - self.wait() - self.play(*list(map(FadeOut, [prime_rects, randy]))) - - def organize_into_columns(self): - left_x = self.arrows.get_right()[0] + SMALL_BUFF - spacing = self.chi_mobs[-1].get_width() + SMALL_BUFF - for chi_mob in self.chi_mobs: - y = chi_mob.get_left()[1] - x = left_x + (chi_mob.d - 1)*spacing - chi_mob.generate_target() - chi_mob.target.move_to(x*RIGHT + y*UP, LEFT) - for plus in self.plusses: - plus.generate_target() - plus.target.scale(0.5) - plus.target.next_to( - plus.chi_mob.target, RIGHT, SMALL_BUFF - ) - - self.play(*it.chain( - list(map(MoveToTarget, self.chi_mobs)), - list(map(MoveToTarget, self.plusses)), - ), run_time = 2) - self.wait() - - def add_count_words(self): - rect = Rectangle( - stroke_color = WHITE, - stroke_width = 2, - fill_color = average_color(BLUE_E, BLACK), - fill_opacity = 1, - height = 1.15, - width = FRAME_WIDTH - 2*MED_SMALL_BUFF, - ) - rect.move_to(3*LEFT, LEFT) - rect.to_edge(UP, buff = SMALL_BUFF) - words = TextMobject("Total") - words.scale(0.8) - words.next_to(rect.get_left(), RIGHT, SMALL_BUFF) - approx = TexMobject("\\approx") - approx.scale(0.7) - approx.next_to(words, RIGHT, SMALL_BUFF) - words.add(approx) - - self.play(*list(map(FadeIn, [rect, words]))) - self.wait() - - self.count_rect = rect - self.count_words = words - - def collapse_columns(self): - chi_mob_columns = [VGroup() for i in range(12)] - for chi_mob in self.chi_mobs: - chi_mob_columns[chi_mob.d - 1].add(chi_mob) - - full_sum = VGroup() - for d in range(1, 7): - R_args = ["{R^2"] - if d != 1: - R_args.append("\\over %d}"%d) - term = VGroup( - TexMobject(*R_args), - TexMobject("\\chi(", str(d), ")"), - TexMobject("+") - ) - term.arrange(RIGHT, SMALL_BUFF) - term[1][1].set_color(YELLOW) - full_sum.add(term) - full_sum.arrange(RIGHT, SMALL_BUFF) - full_sum.scale(0.7) - full_sum.next_to(self.count_words, RIGHT, SMALL_BUFF) - - for column, term in zip(chi_mob_columns, full_sum): - rect = SurroundingRectangle(column) - rect.stretch_to_fit_height(FRAME_HEIGHT) - rect.move_to(column, UP) - rect.set_stroke(width = 0) - rect.set_fill(YELLOW, 0.3) - - self.play(FadeIn(rect)) - self.wait() - self.play( - ReplacementTransform( - column.copy(), - VGroup(term[1]), - run_time = 2 - ), - Write(term[0]), - Write(term[2]), - ) - self.wait() - if term is full_sum[2]: - vect = sum([ - self.count_rect.get_left()[0], - FRAME_X_RADIUS, - -MED_SMALL_BUFF, - ])*LEFT - self.play(*[ - ApplyMethod(m.shift, vect) - for m in [ - self.count_rect, - self.count_words, - ]+list(full_sum[:3]) - ]) - VGroup(*full_sum[3:]).shift(vect) - self.play(FadeOut(rect)) - - self.full_sum = full_sum - - def factor_out_R(self): - self.corner_four.generate_target() - R_squared = TexMobject("R^2") - dots = TexMobject("\\cdots") - lp, rp = list(map(TexMobject, ["\\big(", "\\big)"])) - new_sum = VGroup( - self.corner_four.target, R_squared, lp - ) - - R_fracs, chi_terms, plusses = full_sum_parts = [ - VGroup(*[term[i] for term in self.full_sum]) - for i in range(3) - ] - targets = [] - for part in full_sum_parts: - part.generate_target() - targets.append(part.target) - for R_frac, chi_term, plus in zip(*targets): - chi_term.scale(0.9) - chi_term.move_to(R_frac[0], DOWN) - if R_frac is R_fracs.target[0]: - new_sum.add(chi_term) - else: - new_sum.add(VGroup(chi_term, R_frac[1])) - new_sum.add(plus) - new_sum.add(dots) - new_sum.add(rp) - new_sum.arrange(RIGHT, buff = SMALL_BUFF) - new_sum.next_to(self.count_words, RIGHT, SMALL_BUFF) - R_squared.shift(0.5*SMALL_BUFF*UP) - R_movers = VGroup() - for R_frac in R_fracs.target: - if R_frac is R_fracs.target[0]: - mover = R_frac - else: - mover = R_frac[0] - Transform(mover, R_squared).update(1) - R_movers.add(mover) - - self.play(*it.chain( - list(map(Write, [lp, rp, dots])), - list(map(MoveToTarget, full_sum_parts)), - ), run_time = 2) - self.remove(R_movers) - self.add(R_squared) - self.wait() - self.play( - MoveToTarget(self.corner_four, run_time = 2), - FadeOut(self.corner_four.rect) - ) - self.wait(2) - self.remove(self.full_sum, self.corner_four) - self.add(new_sum) - - self.new_sum = new_sum - - def show_chi_sum_values(self): - alt_rhs = TexMobject( - "\\approx", "4", "R^2", - "\\left(1 - \\frac{1}{3} + \\frac{1}{5}" + \ - "-\\frac{1}{7} + \\frac{1}{9} - \\frac{1}{11}" + \ - "+ \\cdots \\right)", - ) - alt_rhs.scale(0.9) - alt_rhs.next_to( - self.count_words[-1], DOWN, - buff = LARGE_BUFF, - aligned_edge = LEFT - ) - - self.play( - *list(map(FadeOut, [ - self.chi_mobs, self.plusses, self.arrows, - self.radicals, self.row_lines - ])) + [ - FadeOut(self.count_rect), - Animation(self.new_sum), - Animation(self.count_words), - ] - ) - self.play(Write(alt_rhs)) - self.wait(2) - - self.alt_rhs = alt_rhs - - def compare_to_pi_R_squared(self): - approx, pi, R_squared = area_rhs = TexMobject( - "\\approx", "\\pi", "R^2" - ) - area_rhs.next_to(self.alt_rhs, RIGHT) - - brace = Brace( - VGroup(self.alt_rhs, area_rhs), DOWN - ) - brace.add(brace.get_text( - "Arbitrarily good as $R \\to \\infty$" - )) - - pi_sum = TexMobject( - "4", self.alt_rhs[-1].get_tex_string(), - "=", "\\pi" - ) - pi_sum.scale(0.9) - pi = pi_sum.get_part_by_tex("pi") - pi.scale(2, about_point = pi.get_left()) - pi.set_color(YELLOW) - pi_sum.shift( - self.alt_rhs[-1].get_bottom(), - MED_SMALL_BUFF*DOWN, - -pi_sum[1].get_top() - ) - - self.play(Write(area_rhs)) - self.wait() - self.play(FadeIn(brace)) - self.wait(2) - self.play(FadeOut(brace)) - self.play(*[ - ReplacementTransform(m.copy(), pi_sum_part) - for pi_sum_part, m in zip(pi_sum, [ - self.alt_rhs.get_part_by_tex("4"), - self.alt_rhs[-1], - area_rhs[0], - area_rhs[1], - ]) - ]) - - def celebrate(self): - creatures = TeacherStudentsScene().get_pi_creatures() - self.play(FadeIn(creatures)) - self.play(*[ - ApplyMethod(pi.change, "hooray", self.alt_rhs) - for pi in creatures - ]) - self.wait() - for i in 0, 2, 3: - self.play(Blink(creatures[i])) - self.wait() - -class IntersectionOfTwoFields(TeacherStudentsScene): - def construct(self): - circles = VGroup() - for vect, color, adj in (LEFT, BLUE, "Algebraic"), (RIGHT, YELLOW, "Analytic"): - circle = Circle(color = WHITE) - circle.set_fill(color, opacity = 0.3) - circle.stretch_to_fit_width(7) - circle.stretch_to_fit_height(4) - circle.shift(FRAME_X_RADIUS*vect/3.0 + LEFT) - title = TextMobject("%s \\\\ number theory"%adj) - title.scale(0.7) - title.move_to(circle) - title.to_edge(UP, buff = SMALL_BUFF) - circle.next_to(title, DOWN, SMALL_BUFF) - title.set_color(color) - circle.title = title - circles.add(circle) - new_number_systems = TextMobject( - "New \\\\ number systems" - ) - gaussian_integers = TextMobject( - "e.g. Gaussian \\\\ integers" - ) - new_number_systems.next_to(circles[0].get_top(), DOWN, MED_SMALL_BUFF) - new_number_systems.shift(MED_LARGE_BUFF*(DOWN+2*LEFT)) - gaussian_integers.next_to(new_number_systems, DOWN) - gaussian_integers.set_color(BLUE) - circles[0].words = VGroup(new_number_systems, gaussian_integers) - - zeta = TexMobject("\\zeta(s) = \\sum_{n=1}^\\infty \\frac{1}{n^s}") - L_function = TexMobject( - "L(s, \\chi) = \\sum_{n=1}^\\infty \\frac{\\chi(n)}{n^s}" - ) - for mob in zeta, L_function: - mob.scale(0.8) - zeta.next_to(circles[1].get_top(), DOWN, MED_LARGE_BUFF) - zeta.shift(MED_LARGE_BUFF*RIGHT) - L_function.next_to(zeta, DOWN, MED_LARGE_BUFF) - L_function.set_color(YELLOW) - circles[1].words = VGroup(zeta, L_function) - - mid_words = TextMobject("Where\\\\ we \\\\ were") - mid_words.scale(0.7) - mid_words.move_to(circles) - - for circle in circles: - self.play( - Write(circle.title, run_time = 2), - DrawBorderThenFill(circle, run_time = 2), - self.teacher.change_mode, "raise_right_hand" - ) - self.wait() - for circle in circles: - for word in circle.words: - self.play( - Write(word, run_time = 2), - self.teacher.change, "speaking", - *[ - ApplyMethod(pi.change, "pondering") - for pi in self.get_students() - ] - ) - self.wait() - self.play( - Write(mid_words), - self.teacher.change, "raise_right_hand" - ) - self.change_student_modes( - *["thinking"]*3, - look_at_arg = mid_words - ) - self.wait(3) - -class LeibnizPatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "Burt Humburg", - "CrypticSwarm", - "Erik Sundell", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Karan Bhargava", - "Ankit Agarwal", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Markus Persson", - "Yoni Nazarathy", - "Joseph John Cox", - "Dan Buchoff", - "Luc Ritchie", - "Guido Gambardella", - "Julian Pulgarin", - "John Haley", - "Jeff Linse", - "Suraj Pratap", - "Cooper Jones", - "Ryan Dahl", - "Ahmad Bamieh", - "Mark Govea", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "Nils Schneider", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ], - } - -class Sponsorship(PiCreatureScene): - def construct(self): - morty = self.pi_creature - logo = SVGMobject( - file_name = "remix_logo", - ) - logo.set_height(1) - logo.center() - logo.set_stroke(width = 0) - logo.set_fill(BLUE_D, 1) - VGroup(*logo[6:]).set_color_by_gradient(BLUE_B, BLUE_E) - logo.next_to(morty.get_corner(UP+LEFT), UP) - - url = TextMobject("www.remix.com") - url.to_corner(UP+LEFT) - rect = ScreenRectangle(height = 5) - rect.next_to(url, DOWN, aligned_edge = LEFT) - - self.play( - morty.change_mode, "raise_right_hand", - LaggedStartMap(DrawBorderThenFill, logo, run_time = 3) - ) - self.wait() - self.play( - ShowCreation(rect), - logo.scale, 0.8, - logo.to_corner, UP+RIGHT, - morty.change, "happy" - ) - self.wait() - self.play(Write(url)) - self.wait(3) - for mode in "confused", "pondering", "happy": - self.play(morty.change_mode, mode) - self.wait(3) - -class Thumbnail(Scene): - def construct(self): - randy = Randolph() - randy.set_height(5) - body_copy = randy.body.copy() - body_copy.set_stroke(YELLOW, width = 3) - body_copy.set_fill(opacity = 0) - self.add(randy) - - primes = [ - n for n in range(2, 1000) - if all(n%k != 0 for k in list(range(2, n))) - ] - prime_mobs = VGroup() - x_spacing = 1.7 - y_spacing = 1.5 - n_rows = 10 - n_cols = 8 - for i, prime in enumerate(primes[:n_rows*n_cols]): - prime_mob = Integer(prime) - prime_mob.scale(1.5) - x = i%n_cols - y = i//n_cols - prime_mob.shift(x*x_spacing*RIGHT + y*y_spacing*DOWN) - prime_mobs.add(prime_mob) - prime_mob.set_color({ - -1 : YELLOW, - 0 : RED, - 1 : BLUE_C, - }[chi_func(prime)]) - prime_mobs.center().to_edge(UP) - for i in range(7): - self.add(SurroundingRectangle( - VGroup(*prime_mobs[n_cols*i:n_cols*(i+1)]), - fill_opacity = 0.7, - fill_color = BLACK, - stroke_width = 0, - buff = 0, - )) - self.add(prime_mobs) - self.add(body_copy) - - - - - - - - - - - - diff --git a/from_3b1b/old/lost_lecture.py b/from_3b1b/old/lost_lecture.py deleted file mode 100644 index caaef694..00000000 --- a/from_3b1b/old/lost_lecture.py +++ /dev/null @@ -1,4298 +0,0 @@ - -from manimlib.imports import * - -from from_3b1b.old.div_curl import VectorField -from from_3b1b.old.div_curl import get_force_field_func - -COBALT = "#0047AB" - - -# Warning, this file uses ContinualChangingDecimal, -# which has since been been deprecated. Use a mobject -# updater instead - - -# TODO, this is untested after turning it from a -# ContinualAnimation into a VGroup -class Orbiting(VGroup): - CONFIG = { - "rate": 7.5, - } - - def __init__(self, planet, star, ellipse, **kwargs): - VGroup.__init__(self, **kwargs) - self.add(planet) - self.planet = planet - self.star = star - self.ellipse = ellipse - # Proportion of the way around the ellipse - self.proportion = 0 - planet.move_to(ellipse.point_from_proportion(0)) - - self.add_updater(lambda m, dt: m.update(dt)) - - def update(self, dt): - # time = self.internal_time - - planet = self.planet - star = self.star - ellipse = self.ellipse - - rate = self.rate - radius_vector = planet.get_center() - star.get_center() - rate *= 1.0 / get_norm(radius_vector) - - prop = self.proportion - d_prop = 0.001 - ds = get_norm(op.add( - ellipse.point_from_proportion((prop + d_prop) % 1), - -ellipse.point_from_proportion(prop), - )) - - delta_prop = (d_prop / ds) * rate * dt - - self.proportion = (self.proportion + delta_prop) % 1 - planet.move_to( - ellipse.point_from_proportion(self.proportion) - ) - - -# TODO, this is untested after turning it from a -# ContinualAnimation into a Group -class SunAnimation(Group): - CONFIG = { - "rate": 0.2, - "angle": 60 * DEGREES, - } - - def __init__(self, sun, **kwargs): - Group.__init__(self, **kwargs) - self.sun = sun - self.rotated_sun = sun.deepcopy() - self.rotated_sun.rotate(60 * DEGREES) - self.time = 0 - - self.add(self.sun, self.rotated_sun) - self.add_updater(lambda m, dt: m.update(dt)) - - def update(self, dt): - time = self.time - self.time += dt - a = (np.sin(self.rate * time * TAU) + 1) / 2.0 - self.rotated_sun.rotate(-self.angle) - self.rotated_sun.move_to(self.sun) - self.rotated_sun.rotate(self.angle) - self.rotated_sun.pixel_array = np.array( - a * self.sun.pixel_array, - dtype=self.sun.pixel_array.dtype - ) - - -class ShowWord(Animation): - CONFIG = { - "time_per_char": 0.06, - "rate_func": linear, - } - - def __init__(self, word, **kwargs): - assert(isinstance(word, SingleStringTexMobject)) - digest_config(self, kwargs) - run_time = kwargs.pop( - "run_time", - self.time_per_char * len(word) - ) - self.stroke_width = word.get_stroke_width() - Animation.__init__(self, word, run_time=run_time, **kwargs) - - def interpolate_mobject(self, alpha): - word = self.mobject - stroke_width = self.stroke_width - count = int(alpha * len(word)) - remainder = (alpha * len(word)) % 1 - word[:count].set_fill(opacity=1) - word[:count].set_stroke(width=stroke_width) - if count < len(word): - word[count].set_fill(opacity=remainder) - word[count].set_stroke(width=remainder * stroke_width) - word[count + 1:].set_fill(opacity=0) - word[count + 1:].set_stroke(width=0) - -# Animations - - -class TakeOver(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs": { - "color": GREY_BROWN, - "flip_at_start": True, - }, - "default_pi_creature_start_corner": DR, - } - - def construct(self): - gradient = ImageMobject("white_black_gradient") - gradient.set_height(FRAME_HEIGHT) - self.add(gradient) - - morty = self.pi_creatures - henry = ImageMobject("Henry_As_Stick") - henry.set_height(4) - henry.to_edge(LEFT) - henry.to_edge(DOWN) - - self.add(morty, henry) - self.pi_creature_says( - "Muahaha! All \\\\ mine now.", - bubble_kwargs={"fill_opacity": 0.5}, - bubble_creation_class=FadeIn, - target_mode="conniving", - added_anims=[henry.rotate, 5 * DEGREES] - ) - self.wait(2) - - -class ShowEmergingEllipse(Scene): - CONFIG = { - "circle_radius": 3, - "circle_color": BLUE, - "num_lines": 150, - "lines_stroke_width": 1, - "eccentricity_vector": 2 * RIGHT, - "ghost_lines_stroke_color": LIGHT_GREY, - "ghost_lines_stroke_width": 0.5, - "ellipse_color": PINK, - } - - def construct(self): - circle = self.get_circle() - e_point = self.get_eccentricity_point() - e_dot = Dot(e_point, color=YELLOW) - lines = self.get_lines() - ellipse = self.get_ellipse() - - fade_rect = FullScreenFadeRectangle() - - line = lines[len(lines) // 5] - line_dot = Dot(line.get_center(), color=YELLOW) - line_dot.scale(0.5) - - ghost_line = self.get_ghost_lines(line) - ghost_lines = self.get_ghost_lines(lines) - - rot_words = TextMobject("Rotate $90^\\circ$ \\\\ about center") - rot_words.next_to(line_dot, RIGHT) - - elbow = self.get_elbow(line) - - eccentric_words = TextMobject("``Eccentric'' point") - eccentric_words.next_to(circle.get_center(), DOWN) - - ellipse_words = TextMobject("Perfect ellipse") - ellipse_words.next_to(ellipse, UP, SMALL_BUFF) - - for text in rot_words, ellipse_words: - text.add_to_back(text.copy().set_stroke(BLACK, 5)) - - shuffled_lines = VGroup(*lines) - random.shuffle(shuffled_lines.submobjects) - - self.play(ShowCreation(circle)) - self.play( - FadeInFrom(e_dot, LEFT), - Write(eccentric_words, run_time=1) - ) - self.wait() - self.play( - LaggedStartMap(ShowCreation, shuffled_lines), - Animation(VGroup(e_dot, circle)), - FadeOut(eccentric_words) - ) - self.add(ghost_lines) - self.add(e_dot, circle) - self.wait() - self.play( - FadeIn(fade_rect), - Animation(line), - GrowFromCenter(line_dot), - FadeInFromDown(rot_words), - ) - self.wait() - self.add(ghost_line) - self.play( - MoveToTarget(line, path_arc=90 * DEGREES), - Animation(rot_words), - ShowCreation(elbow) - ) - self.wait() - self.play( - FadeOut(fade_rect), - FadeOut(line_dot), - FadeOut(rot_words), - FadeOut(elbow), - Animation(line), - Animation(ghost_line) - ) - self.play( - LaggedStartMap(MoveToTarget, lines, run_time=4), - Animation(VGroup(e_dot, circle)) - ) - self.wait() - self.play( - ShowCreation(ellipse), - FadeInFromDown(ellipse_words) - ) - self.wait() - - def get_circle(self): - circle = self.circle = Circle( - radius=self.circle_radius, - color=self.circle_color - ) - return circle - - def get_eccentricity_point(self): - return self.circle.get_center() + self.eccentricity_vector - - def get_lines(self): - center = self.circle.get_center() - radius = self.circle.get_width() / 2 - e_point = self.get_eccentricity_point() - lines = VGroup(*[ - Line( - e_point, - center + rotate_vector(radius * RIGHT, angle) - ) - for angle in np.linspace(0, TAU, self.num_lines) - ]) - lines.set_stroke(width=self.lines_stroke_width) - for line in lines: - line.generate_target() - line.target.rotate(90 * DEGREES) - return lines - - def get_ghost_lines(self, lines): - return lines.copy().set_stroke( - color=self.ghost_lines_stroke_color, - width=self.ghost_lines_stroke_width - ) - - def get_elbow(self, line): - elbow = VGroup(Line(UP, UL), Line(UL, LEFT)) - elbow.set_stroke(width=1) - elbow.scale(0.2, about_point=ORIGIN) - elbow.rotate( - line.get_angle() - 90 * DEGREES, - about_point=ORIGIN - ) - elbow.shift(line.get_center()) - return elbow - - def get_ellipse(self): - center = self.circle.get_center() - e_point = self.get_eccentricity_point() - radius = self.circle.get_width() / 2 - - # Ellipse parameters - a = radius / 2 - c = get_norm(e_point - center) / 2 - b = np.sqrt(a**2 - c**2) - - result = Circle(radius=b, color=self.ellipse_color) - result.stretch(a / b, 0) - result.move_to(Line(center, e_point)) - return result - - -class ShowFullStory(Scene): - def construct(self): - directory = os.path.join( - MEDIA_DIR, - "animations/active_projects/lost_lecture/images" - ) - scene_names = [ - "ShowEmergingEllipse", - "ShowFullStory", - "FeynmanFameStart", - "TheMotionOfPlanets", - "FeynmanElementaryQuote", - "DrawingEllipse", - "ShowEllipseDefiningProperty", - "ProveEllipse", - "KeplersSecondLaw", - "AngularMomentumArgument", - "HistoryOfAngularMomentum", - "FeynmanRecountingNewton", - "IntroduceShapeOfVelocities", - "ShowEqualAngleSlices", - "PonderOverOffCenterDiagram", - "UseVelocityDiagramToDeduceCurve", - ] - images = Group(*[ - ImageMobject(os.path.join(directory, name + ".png")) - for name in scene_names - ]) - for image in images: - image.add( - SurroundingRectangle(image, buff=0, color=WHITE) - ) - images.arrange_in_grid(n_rows=4) - - images.scale( - 1.01 * FRAME_WIDTH / images[0].get_width() - ) - images.shift(-images[0].get_center()) - - self.play( - images.set_width, FRAME_WIDTH - 1, - images.center, - run_time=3, - ) - self.wait() - self.play( - images.shift, -images[2].get_center(), - images.scale, FRAME_WIDTH / images[2].get_width(), - {"about_point": ORIGIN}, - run_time=3, - ) - self.wait() - - -class FeynmanAndOrbitingPlannetOnEllipseDiagram(ShowEmergingEllipse): - def construct(self): - circle = self.get_circle() - lines = self.get_lines() - ghost_lines = self.get_ghost_lines(lines) - for line in lines: - MoveToTarget(line).update(1) - ellipse = self.get_ellipse() - e_dot = Dot(self.get_eccentricity_point()) - e_dot.set_color(YELLOW) - - comet = ImageMobject("earth") - comet.set_width(0.3) - - feynman = ImageMobject("Feynman") - feynman.set_height(6) - feynman.next_to(ORIGIN, LEFT) - feynman.to_edge(UP) - feynman_name = TextMobject("Richard Feynman") - feynman_name.next_to(feynman, DOWN) - feynman.save_state() - feynman.shift(2 * DOWN) - feynman_rect = BackgroundRectangle( - feynman, fill_opacity=1 - ) - - group = VGroup(circle, ghost_lines, lines, e_dot, ellipse) - - self.add(group) - self.add(Orbiting(comet, e_dot, ellipse)) - self.add_foreground_mobjects(comet) - self.wait() - self.play( - feynman.restore, - MaintainPositionRelativeTo(feynman_rect, feynman), - VFadeOut(feynman_rect), - group.to_edge, RIGHT, - ) - self.play(Write(feynman_name)) - self.wait() - self.wait(10) - - -class FeynmanFame(Scene): - def construct(self): - books = VGroup( - ImageMobject("Feynman_QED_cover"), - ImageMobject("Surely_Youre_Joking_cover"), - ImageMobject("Feynman_Lectures_cover"), - ) - for book in books: - book.set_height(6) - book.move_to(FRAME_WIDTH * LEFT / 4) - - feynman_diagram = self.get_feynman_diagram() - feynman_diagram.next_to(ORIGIN, RIGHT) - fd_parts = VGroup(*reversed(feynman_diagram.family_members_with_points())) - - # As a physicist - self.play(self.get_book_intro(books[0])) - self.play(LaggedStartMap( - Write, feynman_diagram, - run_time=4 - )) - self.wait() - self.play( - self.get_book_intro(books[1]), - self.get_book_outro(books[0]), - LaggedStartMap( - ApplyMethod, fd_parts, - lambda m: (m.scale, 0), - run_time=1 - ), - ) - self.remove(feynman_diagram) - self.wait() - - # As a public figure - safe = SVGMobject(file_name="safe", height=2) - safe_rect = SurroundingRectangle(safe, buff=0) - safe_rect.set_stroke(width=0) - safe_rect.set_fill(DARK_GREY, 1) - safe.add_to_back(safe_rect) - - bongo = SVGMobject(file_name="bongo") - bongo.set_height(1) - bongo.set_color(WHITE) - bongo.next_to(safe, RIGHT, LARGE_BUFF) - - objects = VGroup(safe, bongo) - - feynman_smile = ImageMobject("Feynman_Los_Alamos") - feynman_smile.set_height(4) - feynman_smile.next_to(objects, DOWN) - - VGroup(objects, feynman_smile).next_to(ORIGIN, RIGHT) - - joke = TextMobject( - "``Science is the belief \\\\ in the ignorance of \\\\ experts.''" - ) - joke.move_to(objects) - - self.play(LaggedStartMap( - DrawBorderThenFill, objects, - lag_ratio=0.75 - )) - self.play(self.get_book_intro(feynman_smile)) - self.wait() - self.play( - objects.shift, 2 * UP, - VFadeOut(objects) - ) - self.play(Write(joke)) - self.wait(2) - - self.play( - self.get_book_intro(books[2]), - self.get_book_outro(books[1]), - LaggedStartMap(FadeOut, joke, run_time=1), - ApplyMethod( - feynman_smile.shift, FRAME_HEIGHT * DOWN, - remover=True - ) - ) - - # As a teacher - feynman_teacher = ImageMobject("Feynman_teaching") - feynman_teacher.set_width(FRAME_WIDTH / 2 - 1) - feynman_teacher.next_to(ORIGIN, RIGHT) - - self.play(self.get_book_intro(feynman_teacher)) - self.wait(3) - - def get_book_animation(self, book, - initial_shift, - animated_shift, - opacity_func - ): - rect = BackgroundRectangle(book, fill_opacity=1) - book.shift(initial_shift) - - return AnimationGroup( - ApplyMethod(book.shift, animated_shift), - UpdateFromAlphaFunc( - rect, lambda r, a: r.move_to(book).set_fill( - opacity=opacity_func(a) - ), - remover=True - ) - ) - - def get_book_intro(self, book): - return self.get_book_animation( - book, 2 * DOWN, 2 * UP, lambda a: 1 - a - ) - - def get_book_outro(self, book): - return ApplyMethod(book.shift, FRAME_HEIGHT * UP, remover=True) - - def get_feynman_diagram(self): - x_min = -1.5 - x_max = 1.5 - arrow = Arrow(LEFT, RIGHT, buff=0) - arrow.tip.move_to(arrow.get_center()) - arrows = VGroup(*[ - arrow.copy().rotate(angle).next_to(point, vect, buff=0) - for (angle, point, vect) in [ - (-45 * DEGREES, x_min * RIGHT, UL), - (-135 * DEGREES, x_min * RIGHT, DL), - (-135 * DEGREES, x_max * RIGHT, UR), - (-45 * DEGREES, x_max * RIGHT, DR), - ] - ]) - labels = VGroup(*[ - TexMobject(tex) - for tex in ["e^-", "e^+", "\\text{\\=q}", "q"] - ]) - vects = [UR, DR, UL, DL] - for arrow, label, vect in zip(arrows, labels, vects): - label.next_to(arrow.get_center(), vect, buff=SMALL_BUFF) - - wave = FunctionGraph( - lambda x: 0.2 * np.sin(2 * TAU * x), - x_min=x_min, - x_max=x_max, - ) - wave_label = TexMobject("\\gamma") - wave_label.next_to(wave, UP, SMALL_BUFF) - labels.add(wave_label) - - squiggle = ParametricFunction( - lambda t: np.array([ - t + 0.5 * np.sin(TAU * t), - 0.5 * np.cos(TAU * t), - 0, - ]), - t_min=0, - t_max=4, - ) - squiggle.scale(0.25) - squiggle.set_color(BLUE) - squiggle.rotate(-30 * DEGREES) - squiggle.next_to( - arrows[2].point_from_proportion(0.75), - DR, buff=0 - ) - squiggle_label = TexMobject("g") - squiggle_label.next_to(squiggle, UR, buff=-MED_SMALL_BUFF) - labels.add(squiggle_label) - - return VGroup(arrows, wave, squiggle, labels) - - -class FeynmanLecturesScreenCaptureFrame(Scene): - def construct(self): - url = TextMobject("http://www.feynmanlectures.caltech.edu/") - url.to_edge(UP) - - screen_rect = ScreenRectangle(height=6) - screen_rect.next_to(url, DOWN) - - self.add(url) - self.play(ShowCreation(screen_rect)) - self.wait() - - -class TheMotionOfPlanets(Scene): - CONFIG = { - "camera_config": {"background_opacity": 1}, - "random_seed": 2, - } - - def construct(self): - self.add_title() - self.setup_orbits() - - def add_title(self): - title = TextMobject("``The motion of planets around the sun''") - title.set_color(YELLOW) - title.to_edge(UP) - title.add_to_back(title.copy().set_stroke(BLACK, 5)) - self.add(title) - self.title = title - - def setup_orbits(self): - sun = ImageMobject("sun") - sun.set_height(0.7) - planets, ellipses, orbits = self.get_planets_ellipses_and_orbits(sun) - - archivist_words = TextMobject( - "Judith Goodstein (Caltech archivist)" - ) - archivist_words.to_corner(UL) - archivist_words.shift(1.5 * DOWN) - archivist_words.add_background_rectangle() - alt_name = TextMobject("David Goodstein (Caltech physicist)") - alt_name.next_to(archivist_words, DOWN, aligned_edge=LEFT) - alt_name.add_background_rectangle() - - book = ImageMobject("Lost_Lecture_cover") - book.set_height(4) - book.next_to(alt_name, DOWN) - - self.add(SunAnimation(sun)) - self.add(ellipses, planets) - self.add(self.title) - self.add(*orbits) - self.add_foreground_mobjects(planets) - self.wait(10) - self.play( - VGroup(ellipses, sun).shift, 3 * RIGHT, - FadeInFromDown(archivist_words), - Animation(self.title) - ) - self.add_foreground_mobjects(archivist_words) - self.wait(3) - self.play(FadeInFromDown(alt_name)) - self.add_foreground_mobjects(alt_name) - self.wait() - self.play(FadeInFromDown(book)) - self.wait(15) - - def get_planets_ellipses_and_orbits(self, sun): - planets = VGroup( - ImageMobject("mercury"), - ImageMobject("venus"), - ImageMobject("earth"), - ImageMobject("mars"), - ImageMobject("comet") - ) - sizes = [0.383, 0.95, 1.0, 0.532, 0.3] - orbit_radii = [0.254, 0.475, 0.656, 1.0, 3.0] - orbit_eccentricies = [0.206, 0.006, 0.0167, 0.0934, 0.967] - - for planet, size in zip(planets, sizes): - planet.set_height(0.5) - planet.scale(size) - - ellipses = VGroup(*[ - Circle(radius=r, color=WHITE, stroke_width=1) - for r in orbit_radii - ]) - for circle, ec in zip(ellipses, orbit_eccentricies): - a = circle.get_height() / 2 - c = ec * a - b = np.sqrt(a**2 - c**2) - circle.stretch(b / a, 1) - c = np.sqrt(a**2 - b**2) - circle.shift(c * RIGHT) - for circle in ellipses: - circle.rotate( - TAU * np.random.random(), - about_point=ORIGIN - ) - - ellipses.scale(3.5, about_point=ORIGIN) - - orbits = [ - Orbiting( - planet, sun, circle, - rate=0.25 * r**(2 / 3) - ) - for planet, circle, r in zip(planets, ellipses, orbit_radii) - ] - orbits[-1].proportion = 0.15 - orbits[-1].rate = 0.5 - - return planets, ellipses, orbits - - -class TeacherHoldingUp(TeacherStudentsScene): - def construct(self): - self.play( - self.teacher.change, "raise_right_hand" - ) - self.change_all_student_modes("pondering") - self.look_at(ORIGIN) - self.wait(5) - - -class AskAboutEllipses(TheMotionOfPlanets): - CONFIG = { - "camera_config": {"background_opacity": 1}, - "sun_height": 0.5, - "sun_center": ORIGIN, - "animate_sun": True, - "a": 3.5, - "b": 2.0, - "ellipse_color": WHITE, - "ellipse_stroke_width": 1, - "comet_height": 0.2, - } - - def construct(self): - self.add_title() - self.add_sun() - self.add_orbit() - self.add_focus_lines() - self.add_force_labels() - self.comment_on_imperfections() - self.set_up_differential_equations() - - def add_title(self): - title = Title("Why are orbits ellipses?") - self.add(title) - self.title = title - - def add_sun(self): - sun = ImageMobject("sun", height=self.sun_height) - sun.move_to(self.sun_center) - self.sun = sun - self.add(sun) - if self.animate_sun: - sun_animation = SunAnimation(sun) - self.add(sun_animation) - self.add_foreground_mobjects( - sun_animation.mobject - ) - else: - self.add_foreground_mobjects(sun) - - def add_orbit(self): - sun = self.sun - comet = self.get_comet() - ellipse = self.get_ellipse() - orbit = Orbiting(comet, sun, ellipse) - - self.add(ellipse) - self.add(orbit) - - self.ellipse = ellipse - self.comet = comet - self.orbit = orbit - - def add_focus_lines(self): - f1, f2 = self.focus_points - comet = self.comet - lines = VGroup(Line(LEFT, RIGHT), Line(LEFT, RIGHT)) - lines.set_stroke(LIGHT_GREY, 1) - - def update_lines(lines): - l1, l2 = lines - P = comet.get_center() - l1.put_start_and_end_on(f1, P) - l2.put_start_and_end_on(f2, P) - return lines - - animation = Mobject.add_updater( - lines, update_lines - ) - self.add(animation) - self.wait(8) - - self.focus_lines = lines - self.focus_lines_animation = animation - - def add_force_labels(self): - radial_line = self.focus_lines[0] - - # Radial line measurement - radius_measurement_kwargs = { - "num_decimal_places": 3, - "color": BLUE, - } - radius_measurement = DecimalNumber(1, **radius_measurement_kwargs) - - def update_radial_measurement(measurement): - angle = -radial_line.get_angle() + np.pi - radial_line.rotate(angle, about_point=ORIGIN) - new_decimal = DecimalNumber( - radial_line.get_length(), - **radius_measurement_kwargs - ) - max_width = 0.6 * radial_line.get_width() - if new_decimal.get_width() > max_width: - new_decimal.set_width(max_width) - new_decimal.next_to(radial_line, UP, SMALL_BUFF) - VGroup(new_decimal, radial_line).rotate( - -angle, about_point=ORIGIN - ) - Transform(measurement, new_decimal).update(1) - - radius_measurement_animation = Mobject.add_updater( - radius_measurement, update_radial_measurement - ) - - # Force equation - force_equation = TexMobject( - "F = {GMm \\over (0.000)^2}", - tex_to_color_map={ - "F": YELLOW, - "0.000": BLACK, - } - ) - force_equation.next_to(self.title, DOWN) - force_equation.to_edge(RIGHT) - radius_in_denominator_ref = force_equation.get_part_by_tex("0.000") - radius_in_denominator = DecimalNumber( - 0, **radius_measurement_kwargs - ) - radius_in_denominator.scale(0.95) - update_radius_in_denominator = ContinualChangingDecimal( - radius_in_denominator, - lambda a: radial_line.get_length(), - position_update_func=lambda mob: mob.move_to( - radius_in_denominator_ref, LEFT - ) - ) - - # Force arrow - force_arrow, force_arrow_animation = self.get_force_arrow_and_update( - self.comet - ) - - inverse_square_law_words = TextMobject( - "``Inverse square law''" - ) - inverse_square_law_words.next_to(force_equation, DOWN, MED_LARGE_BUFF) - inverse_square_law_words.to_edge(RIGHT) - force_equation.next_to(inverse_square_law_words, UP, MED_LARGE_BUFF) - - def v_fade_in(mobject): - return UpdateFromAlphaFunc( - mobject, - lambda mob, alpha: mob.set_fill(opacity=alpha) - ) - - self.add(update_radius_in_denominator) - self.add(radius_measurement_animation) - self.play( - FadeIn(force_equation), - v_fade_in(radius_in_denominator), - v_fade_in(radius_measurement) - ) - self.add(force_arrow_animation) - self.play(v_fade_in(force_arrow)) - self.wait(8) - self.play(Write(inverse_square_law_words)) - self.wait(9) - - self.force_equation = force_equation - self.inverse_square_law_words = inverse_square_law_words - self.force_arrow = force_arrow - self.radius_measurement = radius_measurement - - def comment_on_imperfections(self): - planets, ellipses, orbits = self.get_planets_ellipses_and_orbits(self.sun) - orbits.pop(-1) - ellipses.submobjects.pop(-1) - planets.submobjects.pop(-1) - - scale_factor = 20 - center = self.sun.get_center() - ellipses.save_state() - ellipses.scale(scale_factor, about_point=center) - - self.add(*orbits) - self.play(ellipses.restore, Animation(planets)) - self.wait(7) - self.play( - ellipses.scale, scale_factor, {"about_point": center}, - Animation(planets) - ) - self.remove(*orbits) - self.remove(planets, ellipses) - self.wait(2) - - def set_up_differential_equations(self): - d_dt = TexMobject("{d \\over dt}") - in_vect = Matrix(np.array([ - "x(t)", - "y(t)", - "\\dot{x}(t)", - "\\dot{y}(t)", - ])) - equals = TexMobject("=") - out_vect = Matrix(np.array([ - "\\dot{x}(t)", - "\\dot{y}(t)", - "-x(t) / (x(t)^2 + y(t)^2)^{3/2}", - "-y(t) / (x(t)^2 + y(t)^2)^{3/2}", - ]), element_alignment_corner=ORIGIN) - - equation = VGroup(d_dt, in_vect, equals, out_vect) - equation.arrange(RIGHT, buff=SMALL_BUFF) - equation.set_width(6) - - equation.to_corner(DR, buff=MED_LARGE_BUFF) - cross = Cross(equation) - - self.play(Write(equation)) - self.wait(6) - self.play(ShowCreation(cross)) - self.wait(6) - - # Helpers - def get_comet(self): - comet = ImageMobject("comet") - comet.set_height(self.comet_height) - return comet - - def get_ellipse(self): - a = self.a - b = self.b - c = np.sqrt(a**2 - b**2) - ellipse = Circle(radius=a) - ellipse.set_stroke( - self.ellipse_color, - self.ellipse_stroke_width, - ) - ellipse.stretch(fdiv(b, a), dim=1) - ellipse.move_to( - self.sun.get_center() + c * LEFT, - ) - self.focus_points = [ - self.sun.get_center(), - self.sun.get_center() + 2 * c * LEFT, - ] - return ellipse - - def get_force_arrow_and_update(self, comet, scale_factor=1): - force_arrow = Arrow(LEFT, RIGHT, color=YELLOW) - sun = self.sun - - def update_force_arrow(arrow): - radial_line = Line( - sun.get_center(), comet.get_center() - ) - radius = radial_line.get_length() - # target_length = 1 / radius**2 - target_length = scale_factor / radius # Lies! - arrow.scale( - target_length / arrow.get_length() - ) - arrow.rotate( - np.pi + radial_line.get_angle() - arrow.get_angle() - ) - arrow.shift( - radial_line.get_end() - arrow.get_start() - ) - force_arrow_animation = Mobject.add_updater( - force_arrow, update_force_arrow - ) - - return force_arrow, force_arrow_animation - - def get_radial_line_and_update(self, comet): - line = Line(LEFT, RIGHT) - line.set_stroke(LIGHT_GREY, 1) - line_update = Mobject.add_updater( - line, lambda l: l.put_start_and_end_on( - self.sun.get_center(), - comet.get_center(), - ) - ) - return line, line_update - - -class FeynmanSaysItBest(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Feynman says \\\\ it best", - added_anims=[ - self.get_student_changes( - "hooray", "happy", "erm" - ) - ] - ) - self.wait(3) - - -class FeynmanElementaryQuote(Scene): - def construct(self): - quote_text = """ - \\large - I am going to give what I will call an - \\emph{elementary} demonstration. But elementary - does not mean easy to understand. Elementary - means that very little is required - to know ahead of time in order to understand it, - except to have an infinite amount of intelligence. - """ - quote_parts = [s for s in quote_text.split(" ") if s] - quote = TextMobject( - *quote_parts, - tex_to_color_map={ - "\\emph{elementary}": BLUE, - "elementary": BLUE, - "Elementary": BLUE, - "infinite": YELLOW, - "amount": YELLOW, - "of": YELLOW, - "intelligence": YELLOW, - "very": RED, - "little": RED, - }, - alignment="" - ) - quote[-1].shift(2 * SMALL_BUFF * LEFT) - quote.set_width(FRAME_WIDTH - 1) - quote.to_edge(UP) - quote.get_part_by_tex("of").set_color(WHITE) - - nothing = TextMobject("nothing") - nothing.scale(0.9) - very = quote.get_part_by_tex("very") - nothing.shift(very[0].get_left() - nothing[0].get_left()) - nothing.set_color(RED) - - for word in quote: - if word is very: - self.add_foreground_mobjects(nothing) - self.play(ShowWord(nothing)) - self.wait(0.2) - nothing.sort(lambda p: -p[0]) - self.play(LaggedStartMap( - FadeOut, nothing, - run_time=1 - )) - self.remove_foreground_mobject(nothing) - back_word = word.copy().set_stroke(BLACK, 5) - self.add_foreground_mobjects(back_word, word) - self.play( - ShowWord(back_word), - ShowWord(word), - ) - self.wait(0.005 * len(word)**1.5) - self.wait() - - # Show thumbnails - images = Group( - ImageMobject("Calculus_Thumbnail"), - ImageMobject("Fourier_Thumbnail"), - ) - for image in images: - image.set_height(3) - images.arrange(RIGHT, buff=LARGE_BUFF) - images.to_edge(DOWN, buff=LARGE_BUFF) - images[1].move_to(images[0]) - crosses = VGroup(*list(map(Cross, images))) - crosses.set_stroke("RED", 10) - - for image, cross in zip(images, crosses): - image.rect = SurroundingRectangle( - image, - stroke_width=3, - stroke_color=WHITE, - buff=0 - ) - cross.scale(1.1) - self.play( - FadeInFromDown(images[0]), - FadeInFromDown(images[0].rect) - ) - self.play(ShowCreation(crosses[0])) - self.wait() - self.play( - FadeOutAndShiftDown(images[0]), - FadeOutAndShiftDown(images[0].rect), - FadeOutAndShiftDown(crosses[0]), - FadeInFromDown(images[1]), - FadeInFromDown(images[1].rect), - ) - self.play(ShowCreation(crosses[1])) - self.wait() - - -class LostLecturePicture(TODOStub): - CONFIG = {"camera_config": {"background_opacity": 1}} - - def construct(self): - picture = ImageMobject("Feynman_teaching") - picture.set_height(FRAME_WIDTH) - picture.to_corner(UL, buff=0) - picture.fade(0.5) - - self.play( - picture.to_corner, DR, {"buff": 0}, - picture.shift, 1.5 * DOWN, - path_arc=60 * DEGREES, - run_time=20, - rate_func=bezier([0, 0, 1, 1]) - ) - - -class AskAboutInfiniteIntelligence(TeacherStudentsScene): - def construct(self): - self.student_says( - "Infinite intelligence?", - target_mode="confused" - ) - self.play( - self.get_student_changes("horrified", "confused", "sad"), - self.teacher.change, "happy", - ) - self.wait() - self.teacher_says( - "Stay focused, \\\\ go full screen, \\\\ and you'll be fine.", - added_anims=[self.get_student_changes(*["happy"] * 3)] - ) - self.wait() - self.look_at(self.screen) - self.wait(5) - - -class TableOfContents(Scene): - def construct(self): - items = VGroup( - TextMobject("How the ellipse will arise"), - TextMobject("Kepler's 2nd law"), - TextMobject("The shape of velocities"), - ) - items.arrange( - DOWN, buff=LARGE_BUFF, aligned_edge=LEFT - ) - items.to_edge(LEFT, buff=1.5) - for item in items: - item.add(Dot().next_to(item, LEFT)) - item.generate_target() - item.target.set_fill(GREY, opacity=0.5) - - title = Title("The plan") - scale_factor = 1.2 - - self.add(title) - self.play(LaggedStartMap( - FadeIn, items, - run_time=1, - lag_ratio=0.7, - )) - self.wait() - for item in items: - other_items = VGroup(*[m for m in items if m is not item]) - new_item = item.copy() - new_item.scale(scale_factor, about_edge=LEFT) - new_item.set_fill(WHITE, 1) - self.play( - Transform(item, new_item), - *list(map(MoveToTarget, other_items)) - ) - self.wait() - - -class DrawEllipseOverlay(Scene): - def construct(self): - ellipse = Circle() - ellipse.stretch_to_fit_width(7.0) - ellipse.stretch_to_fit_height(3.8) - ellipse.shift(1.05 * UP + 0.48 * LEFT) - ellipse.set_stroke(RED, 8) - - image = ImageMobject( - os.path.join( - get_image_output_directory(self.__class__), - "HeldUpEllipse.jpg" - ) - ) - image.set_height(FRAME_HEIGHT) - - # self.add(image) - self.play(ShowCreation(ellipse)) - self.wait() - self.play(FadeOut(ellipse)) - - -class ShowEllipseDefiningProperty(Scene): - CONFIG = { - "camera_config": {"background_opacity": 1}, - "ellipse_color": BLUE, - "a": 4.0, - "b": 3.0, - "distance_labels_scale_factor": 1.0, - } - - def construct(self): - self.add_ellipse() - self.add_focal_lines() - self.add_distance_labels() - self.label_foci() - self.label_focal_sum() - - def add_ellipse(self): - a = self.a - b = self.b - ellipse = Circle(radius=a, color=self.ellipse_color) - ellipse.stretch(fdiv(b, a), dim=1) - ellipse.to_edge(LEFT, buff=LARGE_BUFF) - self.ellipse = ellipse - self.add(ellipse) - - def add_focal_lines(self): - push_pins = VGroup(*[ - SVGMobject( - file_name="push_pin", - color=LIGHT_GREY, - fill_opacity=0.8, - height=0.5, - ).move_to(point, DR).shift(0.05 * RIGHT) - for point in self.get_foci() - ]) - - dot = Dot() - dot.scale(0.5) - position_tracker = ValueTracker(0.125) - dot_update = Mobject.add_updater( - dot, - lambda d: d.move_to( - self.ellipse.point_from_proportion( - position_tracker.get_value() % 1 - ) - ) - ) - always_shift(position_tracker, rate=0.05) - - lines, lines_update_animation = self.get_focal_lines_and_update( - self.get_foci, dot - ) - - self.add_foreground_mobjects(push_pins, dot) - self.add(dot_update) - self.play(LaggedStartMap( - FadeInFrom, push_pins, - lambda m: (m, 2 * UP + LEFT), - run_time=1, - lag_ratio=0.75 - )) - self.play(ShowCreation(lines)) - self.add(lines_update_animation) - self.add(position_tracker) - self.wait(2) - - self.position_tracker = position_tracker - self.focal_lines = lines - - def add_distance_labels(self): - lines = self.focal_lines - colors = [YELLOW, PINK] - - distance_labels, distance_labels_animation = \ - self.get_distance_labels_and_update(lines, colors) - - sum_expression, numbers, number_updates = \ - self.get_sum_expression_and_update( - lines, colors, lambda mob: mob.to_corner(UR) - ) - - sum_expression_fading_rect = BackgroundRectangle( - sum_expression, fill_opacity=1 - ) - - sum_rect = SurroundingRectangle(numbers[-1]) - constant_words = TextMobject("Stays constant") - constant_words.next_to(sum_rect, DOWN, aligned_edge=RIGHT) - VGroup(sum_rect, constant_words).set_color(BLUE) - - self.add(distance_labels_animation) - self.add(*number_updates) - self.add(sum_expression) - self.add_foreground_mobjects(sum_expression_fading_rect) - self.play( - VFadeIn(distance_labels), - FadeOut(sum_expression_fading_rect), - ) - self.remove_foreground_mobject(sum_expression_fading_rect) - self.wait(7) - self.play( - ShowCreation(sum_rect), - Write(constant_words) - ) - self.wait(7) - self.play(FadeOut(sum_rect), FadeOut(constant_words)) - - self.sum_expression = sum_expression - self.sum_rect = sum_rect - - def label_foci(self): - foci = self.get_foci() - focus_words = VGroup(*[ - TextMobject("Focus").next_to(focus, DOWN) - for focus in foci - ]) - foci_word = TextMobject("Foci") - foci_word.move_to(focus_words) - foci_word.shift(MED_SMALL_BUFF * UP) - connecting_lines = VGroup(*[ - Arrow( - foci_word.get_edge_center(-edge), - focus_word.get_edge_center(edge), - buff=MED_SMALL_BUFF, - stroke_width=2, - ) - for focus_word, edge in zip(focus_words, [LEFT, RIGHT]) - ]) - - translation = TextMobject( - "``Foco'' $\\rightarrow$ Fireplace" - ) - translation.to_edge(RIGHT) - translation.shift(UP) - sun = ImageMobject("sun", height=0.5) - sun.move_to(foci[0]) - sun_animation = SunAnimation(sun) - - self.play(FadeInFromDown(focus_words)) - self.wait(2) - self.play( - ReplacementTransform(focus_words.copy(), foci_word), - ) - self.play(*list(map(ShowCreation, connecting_lines))) - for word in list(focus_words) + [foci_word]: - word.add_background_rectangle() - self.add_foreground_mobjects(word) - self.wait(4) - self.play(Write(translation)) - self.wait(2) - self.play(GrowFromCenter(sun)) - self.add(sun_animation) - self.wait(8) - - def label_focal_sum(self): - sum_rect = self.sum_rect - - focal_sum = TextMobject("``Focal sum''") - focal_sum.scale(1.5) - focal_sum.next_to(sum_rect, DOWN, aligned_edge=RIGHT) - VGroup(sum_rect, focal_sum).set_color(RED) - - footnote = TextMobject( - """ - \\Large - *This happens to equal the longest distance - across the ellipse, so perhaps the more standard - terminology would be ``major axis'', but I want - some terminology that conveys the idea of adding - two distances to the foci. - """, - alignment="", - ) - footnote.set_width(5) - footnote.to_corner(DR) - footnote.set_stroke(WHITE, 0.5) - - self.play(FadeInFromDown(focal_sum)) - self.play(Write(sum_rect)) - self.wait() - self.play(FadeIn(footnote)) - self.wait(2) - self.play(FadeOut(footnote)) - self.wait(8) - - # Helpers - def get_foci(self): - ellipse = self.ellipse - a = ellipse.get_width() / 2 - b = ellipse.get_height() / 2 - c = np.sqrt(a**2 - b**2) - center = ellipse.get_center() - return [ - center + c * RIGHT, - center + c * LEFT, - ] - - def get_focal_lines_and_update(self, get_foci, focal_sum_point): - lines = VGroup(Line(LEFT, RIGHT), Line(LEFT, RIGHT)) - lines.set_stroke(width=2) - - def update_lines(lines): - foci = get_foci() - for line, focus in zip(lines, foci): - line.put_start_and_end_on( - focus, focal_sum_point.get_center() - ) - lines[1].rotate(np.pi) - lines_update_animation = Mobject.add_updater( - lines, update_lines - ) - return lines, lines_update_animation - - def get_distance_labels_and_update(self, lines, colors): - distance_labels = VGroup( - DecimalNumber(0), DecimalNumber(0), - ) - for label in distance_labels: - label.scale(self.distance_labels_scale_factor) - - def update_distance_labels(labels): - for label, line, color in zip(labels, lines, colors): - angle = -line.get_angle() - if np.abs(angle) > 90 * DEGREES: - angle = 180 * DEGREES + angle - line.rotate(angle, about_point=ORIGIN) - new_decimal = DecimalNumber(line.get_length()) - new_decimal.scale( - self.distance_labels_scale_factor - ) - max_width = 0.6 * line.get_width() - if new_decimal.get_width() > max_width: - new_decimal.set_width(max_width) - new_decimal.next_to(line, UP, SMALL_BUFF) - new_decimal.set_color(color) - new_decimal.add_to_back( - new_decimal.copy().set_stroke(BLACK, 5) - ) - VGroup(new_decimal, line).rotate( - -angle, about_point=ORIGIN - ) - label.submobjects = list(new_decimal.submobjects) - - distance_labels_animation = Mobject.add_updater( - distance_labels, update_distance_labels - ) - - return distance_labels, distance_labels_animation - - def get_sum_expression_and_update(self, lines, colors, sum_position_func): - sum_expression = TexMobject("0.00", "+", "0.00", "=", "0.00") - sum_position_func(sum_expression) - number_refs = sum_expression.get_parts_by_tex("0.00") - number_refs.set_fill(opacity=0) - numbers = VGroup(*[DecimalNumber(0) for ref in number_refs]) - for number, color in zip(numbers, colors): - number.set_color(color) - - # Not the most elegant... - number_updates = [ - ContinualChangingDecimal( - numbers[0], lambda a: lines[0].get_length(), - position_update_func=lambda m: m.move_to( - number_refs[1], LEFT - ) - ), - ContinualChangingDecimal( - numbers[1], lambda a: lines[1].get_length(), - position_update_func=lambda m: m.move_to( - number_refs[0], LEFT - ) - ), - ContinualChangingDecimal( - numbers[2], lambda a: sum(map(Line.get_length, lines)), - position_update_func=lambda m: m.move_to( - number_refs[2], LEFT - ) - ), - ] - - return sum_expression, numbers, number_updates - - -class GeometryProofLand(Scene): - CONFIG = { - "colors": [ - PINK, RED, YELLOW, GREEN, GREEN_A, BLUE, - MAROON_E, MAROON_B, YELLOW, BLUE, - ], - "text": "Geometry proof land", - } - - def construct(self): - word = self.get_geometry_proof_land_word() - word_outlines = word.deepcopy() - word_outlines.set_fill(opacity=0) - word_outlines.set_stroke(WHITE, 1) - colors = list(self.colors) - random.shuffle(colors) - word_outlines.set_color_by_gradient(*colors) - word_outlines.set_stroke(width=5) - - circles = VGroup() - for letter in word: - circle = Circle() - # circle = letter.copy() - circle.replace(letter, dim_to_match=1) - circle.scale(3) - circle.set_stroke(WHITE, 0) - circle.set_fill(letter.get_color(), 0) - circles.add(circle) - circle.target = letter - - self.play( - LaggedStartMap(MoveToTarget, circles), - run_time=2 - ) - self.add(word_outlines, circles) - self.play(LaggedStartMap( - FadeIn, word_outlines, - run_time=3, - rate_func=there_and_back, - ), Animation(circles)) - self.wait() - - def get_geometry_proof_land_word(self): - word = TextMobject(self.text) - word.rotate(-90 * DEGREES) - word.scale(0.25) - word.shift(3 * RIGHT) - word.apply_complex_function(np.exp) - word.rotate(90 * DEGREES) - word.set_width(9) - word.center() - word.to_edge(UP) - word.set_color_by_gradient(*self.colors) - word.set_background_stroke(width=0) - return word - - -class ProveEllipse(ShowEmergingEllipse, ShowEllipseDefiningProperty): - CONFIG = { - "eccentricity_vector": 1.5 * RIGHT, - "ellipse_color": PINK, - "distance_labels_scale_factor": 0.7, - } - - def construct(self): - self.setup_ellipse() - self.hypothesize_foci() - self.setup_and_show_focal_sum() - self.show_circle_radius() - self.limit_to_just_one_line() - self.look_at_perpendicular_bisector() - self.show_orbiting_planet() - - def setup_ellipse(self): - circle = self.circle = self.get_circle() - circle.to_edge(LEFT) - ep = self.get_eccentricity_point() - ep_dot = self.ep_dot = Dot(ep, color=YELLOW) - lines = self.lines = self.get_lines() - for line in lines: - line.save_state() - ghost_lines = self.ghost_lines = self.get_ghost_lines(lines) - ellipse = self.ellipse = self.get_ellipse() - - self.add(ghost_lines, circle, lines, ep_dot) - self.play( - LaggedStartMap(MoveToTarget, lines), - Animation(ep_dot), - ) - self.play(ShowCreation(ellipse)) - self.wait() - - def hypothesize_foci(self): - circle = self.circle - ghost_lines = self.ghost_lines - ghost_lines_copy = ghost_lines.copy().set_stroke(YELLOW, 3) - - center = circle.get_center() - center_dot = Dot(center, color=RED) - # ep = self.get_eccentricity_point() - ep_dot = self.ep_dot - dots = VGroup(center_dot, ep_dot) - - center_label = TextMobject("Circle center") - ep_label = TextMobject("Eccentric point") - labels = VGroup(center_label, ep_label) - vects = [UL, DR] - arrows = VGroup() - for label, dot, vect in zip(labels, dots, vects): - label.next_to(dot, vect, MED_LARGE_BUFF) - label.match_color(dot) - label.add_to_back( - label.copy().set_stroke(BLACK, 5) - ) - arrow = Arrow( - label.get_corner(-vect), - dot.get_corner(vect), - buff=SMALL_BUFF - ) - arrow.match_color(dot) - arrow.add_to_back(arrow.copy().set_stroke(BLACK, 5)) - arrows.add(arrow) - - labels_target = labels.copy() - labels_target.arrange( - DOWN, aligned_edge=LEFT - ) - guess_start = TextMobject("Guess: Foci = ") - brace = Brace(labels_target, LEFT) - full_guess = VGroup(guess_start, brace, labels_target) - full_guess.arrange(RIGHT) - full_guess.to_corner(UR) - - self.play( - FadeInFromDown(labels[1]), - GrowArrow(arrows[1]), - ) - self.play(LaggedStartMap( - ShowPassingFlash, ghost_lines_copy - )) - self.wait() - self.play(ReplacementTransform(circle.copy(), center_dot)) - self.add_foreground_mobjects(dots) - self.play( - FadeInFromDown(labels[0]), - GrowArrow(arrows[0]), - ) - self.wait() - self.play( - Write(guess_start), - GrowFromCenter(brace), - run_time=1 - ) - self.play( - ReplacementTransform(labels.copy(), labels_target) - ) - self.wait() - self.play(FadeOut(labels), FadeOut(arrows)) - - self.center_dot = center_dot - - def setup_and_show_focal_sum(self): - circle = self.circle - ellipse = self.ellipse - - focal_sum_point = VectorizedPoint() - focal_sum_point.move_to(circle.get_top()) - dots = [self.ep_dot, self.center_dot] - colors = list(map(Mobject.get_color, dots)) - - def get_foci(): - return list(map(Mobject.get_center, dots)) - - focal_lines, focal_lines_update_animation = \ - self.get_focal_lines_and_update(get_foci, focal_sum_point) - distance_labels, distance_labels_update_animation = \ - self.get_distance_labels_and_update(focal_lines, colors) - sum_expression, numbers, number_updates = \ - self.get_sum_expression_and_update( - focal_lines, colors, - lambda mob: mob.to_edge(RIGHT).shift(UP) - ) - - to_add = self.focal_sum_things_to_add = [ - focal_lines_update_animation, - distance_labels_update_animation, - sum_expression, - ] + list(number_updates) - - self.play( - ShowCreation(focal_lines), - Write(distance_labels), - FadeIn(sum_expression), - Write(numbers), - run_time=1 - ) - self.wait() - self.add(*to_add) - - points = [ - ellipse.get_top(), - circle.point_from_proportion(0.2), - ellipse.point_from_proportion(0.2), - ellipse.point_from_proportion(0.4), - ] - for point in points: - self.play( - focal_sum_point.move_to, point - ) - self.wait() - self.remove(*to_add) - self.play(*list(map(FadeOut, [ - focal_lines, distance_labels, - sum_expression, numbers - ]))) - - self.set_variables_as_attrs( - focal_lines, focal_lines_update_animation, - focal_sum_point, - distance_labels, distance_labels_update_animation, - sum_expression, - numbers, number_updates - ) - - def show_circle_radius(self): - circle = self.circle - center = circle.get_center() - point = circle.get_right() - color = GREEN - radius = Line(center, point, color=color) - radius_measurement = DecimalNumber(radius.get_length()) - radius_measurement.set_color(color) - radius_measurement.next_to(radius, UP, SMALL_BUFF) - radius_measurement.add_to_back( - radius_measurement.copy().set_stroke(BLACK, 5) - ) - group = VGroup(radius, radius_measurement) - group.rotate(30 * DEGREES, about_point=center) - - self.play(ShowCreation(radius)) - self.play(Write(radius_measurement)) - self.wait() - self.play(Rotating( - group, - rate_func=smooth, - run_time=7, - about_point=center - )) - self.play(FadeOut(group)) - - def limit_to_just_one_line(self): - lines = self.lines - ghost_lines = self.ghost_lines - ep_dot = self.ep_dot - - index = int(0.2 * len(lines)) - line = lines[index] - ghost_line = ghost_lines[index] - to_fade = VGroup(*list(lines) + list(ghost_lines)) - to_fade.remove(line, ghost_line) - - P_dot = Dot(line.saved_state.get_end()) - P_label = TexMobject("P") - P_label.next_to(P_dot, UP, SMALL_BUFF) - - self.add_foreground_mobjects(self.ellipse) - self.play(LaggedStartMap(Restore, lines)) - self.play( - FadeOut(to_fade), - ghost_line.set_stroke, YELLOW, 3, - line.set_stroke, WHITE, 3, - ReplacementTransform(ep_dot.copy(), P_dot), - ) - self.play(FadeInFromDown(P_label)) - self.wait() - - for l in lines: - l.generate_target() - l.target.rotate( - 90 * DEGREES, - about_point=l.get_center() - ) - - self.set_variables_as_attrs( - line, ghost_line, - P_dot, P_label - ) - - def look_at_perpendicular_bisector(self): - # Alright, this method's gonna blow up. Let's go! - circle = self.circle - ellipse = self.ellipse - ellipse.save_state() - lines = self.lines - line = self.line - ghost_lines = self.ghost_lines - ghost_line = self.ghost_line - P_dot = self.P_dot - P_label = self.P_label - - elbow = self.get_elbow(line) - self.play( - MoveToTarget(line, path_arc=90 * DEGREES), - ShowCreation(elbow) - ) - - # Perpendicular bisector label - label = TextMobject("``Perpendicular bisector''") - label.scale(0.75) - label.set_color(YELLOW) - label.next_to(ORIGIN, UP, MED_SMALL_BUFF) - label.add_background_rectangle() - angle = line.get_angle() + np.pi - label.rotate(angle, about_point=ORIGIN) - label.shift(line.get_center()) - - # Dot defining Q point - Q_dot = Dot(color=GREEN) - Q_dot.move_to(self.focal_sum_point) - focal_sum_point_animation = turn_animation_into_updater( - MaintainPositionRelativeTo( - self.focal_sum_point, Q_dot - ) - ) - self.add(focal_sum_point_animation) - Q_dot.move_to(line.point_from_proportion(0.9)) - Q_dot.save_state() - - Q_label = TexMobject("Q") - Q_label.scale(0.7) - Q_label.match_color(Q_dot) - Q_label.add_to_back(Q_label.copy().set_stroke(BLACK, 5)) - Q_label.next_to(Q_dot, UL, buff=0) - Q_label_animation = turn_animation_into_updater( - MaintainPositionRelativeTo(Q_label, Q_dot) - ) - - # Pretty hacky... - def distance_label_shift_update(label): - line = self.focal_lines[0] - if line.get_end()[0] > line.get_start()[0]: - vect = label.get_center() - line.get_center() - label.shift(-2 * vect) - distance_label_shift_update_animation = Mobject.add_updater( - self.distance_labels[0], - distance_label_shift_update - ) - self.focal_sum_things_to_add.append( - distance_label_shift_update_animation - ) - - # Define QP line - QP_line = Line(LEFT, RIGHT) - QP_line.match_style(self.focal_lines) - QP_line_update = Mobject.add_updater( - QP_line, lambda l: l.put_start_and_end_on( - Q_dot.get_center(), P_dot.get_center(), - ) - ) - - QE_line = Line(LEFT, RIGHT) - QE_line.set_stroke(YELLOW, 3) - QE_line_update = Mobject.add_updater( - QE_line, lambda l: l.put_start_and_end_on( - Q_dot.get_center(), - self.get_eccentricity_point() - ) - ) - - # Define similar triangles - triangles = VGroup(*[ - Polygon( - Q_dot.get_center(), - line.get_center(), - end_point, - fill_opacity=1, - ) - for end_point in [ - P_dot.get_center(), - self.get_eccentricity_point() - ] - ]) - triangles.set_color_by_gradient(RED_C, COBALT) - triangles.set_stroke(WHITE, 2) - - # Add even more distant label updates - def distance_label_rotate_update(label): - QE_line_update.update(0) - angle = QP_line.get_angle() - QE_line.get_angle() - label.rotate(angle, about_point=Q_dot.get_center()) - return label - - distance_label_rotate_update_animation = Mobject.add_updater( - self.distance_labels[0], - distance_label_rotate_update - ) - - # Hook up line to P to P_dot - radial_line = DashedLine(ORIGIN, 3 * RIGHT) - radial_line_update = UpdateFromFunc( - radial_line, lambda l: l.put_start_and_end_on( - circle.get_center(), - P_dot.get_center() - ) - ) - - def put_dot_at_intersection(dot): - point = line_intersection( - line.get_start_and_end(), - radial_line.get_start_and_end() - ) - dot.move_to(point) - return dot - - keep_Q_dot_at_intersection = UpdateFromFunc( - Q_dot, put_dot_at_intersection - ) - Q_dot.restore() - - ghost_line_update_animation = UpdateFromFunc( - ghost_line, lambda l: l.put_start_and_end_on( - self.get_eccentricity_point(), - P_dot.get_center() - ) - ) - - def update_perp_bisector(line): - line.scale(ghost_line.get_length() / line.get_length()) - line.rotate(ghost_line.get_angle() - line.get_angle()) - line.rotate(90 * DEGREES) - line.move_to(ghost_line) - perp_bisector_update_animation = UpdateFromFunc( - line, update_perp_bisector - ) - elbow_update_animation = UpdateFromFunc( - elbow, - lambda e: Transform(e, self.get_elbow(ghost_line)).update(1) - ) - - P_dot_movement_updates = [ - radial_line_update, - keep_Q_dot_at_intersection, - MaintainPositionRelativeTo( - P_label, P_dot - ), - ghost_line_update_animation, - perp_bisector_update_animation, - elbow_update_animation, - ] - - # Comment for tangency - sum_rect = SurroundingRectangle( - self.numbers[-1] - ) - tangency_comment = TextMobject( - "Always $\\ge$ radius" - ) - tangency_comment.next_to( - sum_rect, DOWN, - aligned_edge=RIGHT - ) - VGroup(sum_rect, tangency_comment).set_color(GREEN) - - # Why is this needed?!? - self.add(*self.focal_sum_things_to_add) - self.wait(0.01) - self.remove(*self.focal_sum_things_to_add) - - # Show label - self.play(Write(label)) - self.wait() - - # Show Q_dot moving about a little - self.play( - FadeOut(label), - FadeIn(self.focal_lines), - FadeIn(self.distance_labels), - FadeIn(self.sum_expression), - FadeIn(self.numbers), - ellipse.set_stroke, {"width": 0.5}, - ) - self.add(*self.focal_sum_things_to_add) - self.play( - FadeInFromDown(Q_label), - GrowFromCenter(Q_dot) - ) - self.wait() - self.add_foreground_mobjects(Q_dot) - self.add(Q_label_animation) - self.play( - Q_dot.move_to, line.point_from_proportion(0.05), - rate_func=there_and_back, - run_time=4 - ) - self.wait() - - # Show similar triangles - self.play( - FadeIn(triangles[0]), - ShowCreation(QP_line), - Animation(elbow), - ) - self.add(QP_line_update) - for i in range(3): - self.play( - FadeIn(triangles[(i + 1) % 2]), - FadeOut(triangles[i % 2]), - Animation(self.distance_labels), - Animation(elbow) - ) - self.play( - FadeOut(triangles[1]), - Animation(self.distance_labels) - ) - - # Move first distance label - # (boy, this got messy...hopefully no one ever has - # to read this.) - angle = QP_line.get_angle() - QE_line.get_angle() - Q_point = Q_dot.get_center() - for x in range(2): - self.play(ShowCreationThenDestruction(QE_line)) - distance_label_copy = self.distance_labels[0].copy() - self.play( - ApplyFunction( - distance_label_rotate_update, - distance_label_copy, - path_arc=angle - ), - Rotate(QE_line, angle, about_point=Q_point) - ) - self.play(FadeOut(QE_line)) - self.remove(distance_label_copy) - self.add(distance_label_rotate_update_animation) - self.focal_sum_things_to_add.append( - distance_label_rotate_update_animation - ) - self.wait() - self.play( - Q_dot.move_to, line.point_from_proportion(0), - run_time=4, - rate_func=there_and_back - ) - - # Trace out ellipse - self.play(ShowCreation(radial_line)) - self.wait() - self.play( - ApplyFunction(put_dot_at_intersection, Q_dot), - run_time=3, - ) - self.wait() - self.play( - Rotating( - P_dot, - about_point=circle.get_center(), - rate_func=bezier([0, 0, 1, 1]), - run_time=10, - ), - ellipse.restore, - *P_dot_movement_updates - ) - self.wait() - - # Talk through tangency - self.play( - ShowCreation(sum_rect), - Write(tangency_comment), - ) - points = [line.get_end(), line.get_start(), Q_dot.get_center()] - run_times = [1, 3, 2] - for point, run_time in zip(points, run_times): - self.play(Q_dot.move_to, point, run_time=run_time) - self.wait() - - self.remove(*self.focal_sum_things_to_add) - self.play(*list(map(FadeOut, [ - radial_line, - QP_line, - P_dot, P_label, - Q_dot, Q_label, - elbow, - self.distance_labels, - self.numbers, - self.sum_expression, - sum_rect, - tangency_comment, - ]))) - self.wait() - - # Show all lines - lines.remove(line) - ghost_lines.remove(ghost_line) - for line in lines: - line.generate_target() - line.target.rotate(90 * DEGREES) - self.play( - LaggedStartMap(FadeIn, ghost_lines), - LaggedStartMap(FadeIn, lines), - ) - self.play(LaggedStartMap(MoveToTarget, lines)) - self.wait() - - def show_orbiting_planet(self): - ellipse = self.ellipse - ep_dot = self.ep_dot - planet = ImageMobject("earth") - planet.set_height(0.25) - orbit = Orbiting(planet, ep_dot, ellipse) - - lines = self.lines - - def update_lines(lines): - for gl, line in zip(self.ghost_lines, lines): - intersection = line_intersection( - [self.circle.get_center(), gl.get_end()], - line.get_start_and_end() - ) - distance = get_norm( - intersection - planet.get_center() - ) - if distance < 0.025: - line.set_stroke(BLUE, 3) - self.add(line) - else: - line.set_stroke(WHITE, 1) - - lines_update_animation = Mobject.add_updater( - lines, update_lines - ) - - self.add(orbit) - self.add(lines_update_animation) - self.add_foreground_mobjects(planet) - self.wait(12) - - -class Enthusiast(Scene): - def construct(self): - randy = Randolph(color=BLUE_C) - randy.flip() - self.play(randy.change, "surprised") - self.play(Blink(randy)) - self.wait() - - -class SimpleThinking(Scene): - def construct(self): - randy = Randolph(color=BLUE_C) - randy.flip() - self.play(randy.change, "thinking", 3 * UP) - self.play(Blink(randy)) - self.wait() - self.play(randy.change, "hooray", 3 * UP) - self.play(Blink(randy)) - self.wait() - - -class EndOfGeometryProofiness(GeometryProofLand): - def construct(self): - geometry_word = self.get_geometry_proof_land_word() - orbital_mechanics = TextMobject("Orbital Mechanics") - orbital_mechanics.scale(1.5) - orbital_mechanics.to_edge(UP) - underline = Line(LEFT, RIGHT) - underline.match_width(orbital_mechanics) - underline.next_to(orbital_mechanics, DOWN, SMALL_BUFF) - - self.play(LaggedStartMap(FadeOutAndShiftDown, geometry_word)) - self.play(FadeInFromDown(orbital_mechanics)) - self.play(ShowCreation(underline)) - self.wait() - - -class KeplersSecondLaw(AskAboutEllipses): - CONFIG = { - "sun_center": 4 * RIGHT + 0.75 * DOWN, - "animate_sun": True, - "a": 5.0, - "b": 3.0, - "ellipse_stroke_width": 2, - "area_color": COBALT, - "area_opacity": 0.75, - "arc_color": YELLOW, - "arc_stroke_width": 3, - "n_sample_sweeps": 5, - "fade_sample_areas": True, - } - - def construct(self): - self.add_title() - self.add_sun() - self.add_orbit() - self.add_foreground_mobjects(self.comet) - - self.show_several_sweeps() - self.contrast_close_to_far() - - def add_title(self): - title = TextMobject("Kepler's 2nd law:") - title.scale(1.0) - title.to_edge(UP) - self.add(title) - self.title = title - - subtitle = TextMobject( - "Orbits sweep a constant area per unit time" - ) - subtitle.next_to(title, DOWN, buff=0.2) - subtitle.set_color(BLUE) - self.add(subtitle) - - def show_several_sweeps(self): - shown_areas = VGroup() - for x in range(self.n_sample_sweeps): - self.wait() - area = self.show_area_sweep() - shown_areas.add(area) - self.wait() - if self.fade_sample_areas: - self.play(FadeOut(shown_areas)) - - def contrast_close_to_far(self): - orbit = self.orbit - sun_point = self.sun.get_center() - - start_prop = 0.9 - self.wait_until_proportion(start_prop) - self.show_area_sweep() - end_prop = orbit.proportion - arc = self.get_arc(start_prop, end_prop) - radius = Line(sun_point, arc.points[0]) - radius.set_color(WHITE) - - radius_words = self.get_radius_words(radius, "Short") - radius_words.next_to(radius.get_center(), LEFT, SMALL_BUFF) - - arc_words = TextMobject("Long arc") - arc_words.rotate(90 * DEGREES) - arc_words.scale(0.5) - arc_words.next_to(RIGHT, RIGHT) - arc_words.apply_complex_function(np.exp) - arc_words.scale(0.8) - arc_words.next_to( - arc, RIGHT, buff=-SMALL_BUFF - ) - arc_words.shift(4 * SMALL_BUFF * DOWN) - arc_words.match_color(arc) - - # Show stubby arc - # self.remove(orbit) - # self.add(self.comet) - self.play( - ShowCreation(radius), - Write(radius_words), - ) - self.play( - ShowCreation(arc), - Write(arc_words) - ) - - # Show narrow arc - # (Code repetition...uck) - start_prop = 0.475 - self.wait_until_proportion(start_prop) - self.show_area_sweep() - end_prop = orbit.proportion - short_arc = self.get_arc(start_prop, end_prop) - long_radius = Line(sun_point, short_arc.points[0]) - long_radius.set_color(WHITE) - long_radius_words = self.get_radius_words(long_radius, "Long") - - short_arc_words = TextMobject("Short arc") - short_arc_words.scale(0.5) - short_arc_words.rotate(90 * DEGREES) - short_arc_words.next_to(short_arc, LEFT, SMALL_BUFF) - short_arc_words.match_color(short_arc) - - self.play( - ShowCreation(long_radius), - Write(long_radius_words), - ) - self.play( - ShowCreation(short_arc), - Write(short_arc_words) - ) - self.wait(15) - - # Helpers - def show_area_sweep(self, time=1.0): - orbit = self.orbit - start_prop = orbit.proportion - area = self.get_area(start_prop, start_prop) - area_update = UpdateFromFunc( - area, - lambda a: Transform( - a, self.get_area(start_prop, orbit.proportion) - ).update(1) - ) - - self.play(area_update, run_time=time) - - return area - - def get_area(self, prop1, prop2): - """ - Return a mobject illustrating the area swept - out between a point prop1 of the way along - the ellipse, and prop2 of the way. - """ - sun_point = self.sun.get_center() - arc = self.get_arc(prop1, prop2) - - # Add lines from start - result = VMobject() - result.append_vectorized_mobject( - Line(sun_point, arc.points[0]) - ) - result.append_vectorized_mobject(arc) - result.append_vectorized_mobject( - Line(arc.points[-1], sun_point) - ) - - result.set_stroke(WHITE, width=0) - result.set_fill( - self.area_color, - self.area_opacity, - ) - return result - - def get_arc(self, prop1, prop2): - ellipse = self.get_ellipse() - prop1 = prop1 % 1.0 - prop2 = prop2 % 1.0 - - if prop2 > prop1: - arc = VMobject().pointwise_become_partial( - ellipse, prop1, prop2 - ) - elif prop1 > prop2: - arc, arc_extension = [ - VMobject().pointwise_become_partial( - ellipse, p1, p2 - ) - for p1, p2 in [(prop1, 1.0), (0.0, prop2)] - ] - arc.append_vectorized_mobject(arc_extension) - else: - arc = VectorizedPoint( - ellipse.point_from_proportion(prop1) - ) - - arc.set_stroke( - self.arc_color, - self.arc_stroke_width, - ) - - return arc - - def wait_until_proportion(self, prop): - if self.skip_animations: - self.orbit.proportion = prop - else: - while (self.orbit.proportion % 1) < prop: - self.wait(self.frame_duration) - - def get_radius_words(self, radius, adjective): - radius_words = TextMobject( - "%s radius" % adjective, - ) - min_width = 0.8 * radius.get_length() - if radius_words.get_width() > min_width: - radius_words.set_width(min_width) - radius_words.match_color(radius) - radius_words.next_to(ORIGIN, UP, SMALL_BUFF) - angle = radius.get_angle() - angle = ((angle + PI) % TAU) - PI - if np.abs(angle) > PI / 2: - angle += PI - radius_words.rotate(angle, about_point=ORIGIN) - radius_words.shift(radius.get_center()) - return radius_words - - -class NonEllipticalKeplersLaw(KeplersSecondLaw): - CONFIG = { - "animate_sun": True, - "sun_center": 2 * RIGHT, - "n_sample_sweeps": 10, - } - - def construct(self): - self.add_sun() - self.add_orbit() - self.show_several_sweeps() - - def add_orbit(self): - sun = self.sun - comet = ImageMobject("comet", height=0.5) - orbit_shape = self.get_ellipse() - - orbit = self.orbit = Orbiting(comet, sun, orbit_shape) - self.add(orbit_shape) - self.add(orbit) - - arrow, arrow_update = self.get_force_arrow_and_update( - comet - ) - alt_arrow_update = Mobject.add_updater( - arrow, lambda a: a.scale( - 1.0 / a.get_length(), - about_point=a.get_start() - ) - ) - self.add(arrow_update, alt_arrow_update) - self.add_foreground_mobjects(comet, arrow) - - self.ellipse = orbit_shape - - def get_ellipse(self): - orbit_shape = ParametricFunction( - lambda t: (1 + 0.2 * np.sin(5 * TAU * t)) * np.array([ - np.cos(TAU * t), - np.sin(TAU * t), - 0 - ]) - ) - orbit_shape.set_height(7) - orbit_shape.stretch(1.5, 0) - orbit_shape.shift(LEFT) - orbit_shape.set_stroke(LIGHT_GREY, 1) - return orbit_shape - - -class AngularMomentumArgument(KeplersSecondLaw): - CONFIG = { - "animate_sun": False, - "sun_center": 4 * RIGHT + DOWN, - "comet_start_point": 4 * LEFT, - "comet_end_point": 5 * LEFT + DOWN, - "comet_height": 0.3, - } - - def construct(self): - self.add_sun() - self.show_small_sweep() - self.show_sweep_dimensions() - self.show_conservation_of_angular_momentum() - - def show_small_sweep(self): - sun_center = self.sun_center - comet_start = self.comet_start_point - comet_end = self.comet_end_point - triangle = Polygon( - sun_center, comet_start, comet_end, - fill_opacity=1, - fill_color=COBALT, - stroke_width=0, - ) - triangle.save_state() - alt_triangle = Polygon( - sun_center, - interpolate(comet_start, comet_end, 0.9), - comet_end - ) - alt_triangle.match_style(triangle) - - comet = self.get_comet() - comet.move_to(comet_start) - - velocity_vector = Arrow( - comet_start, comet_end, - color=WHITE, - buff=0 - ) - velocity_vector_label = TexMobject("\\vec{\\textbf{v}}") - velocity_vector_label.next_to( - velocity_vector.get_center(), UL, - buff=SMALL_BUFF - ) - - small_time_label = TextMobject( - "Small", "time", "$\\Delta t$", - ) - small_time_label.to_edge(UP) - small = small_time_label.get_part_by_tex("Small") - small_rect = SurroundingRectangle(small) - - self.add_foreground_mobjects(comet) - self.play( - ShowCreation( - triangle, - rate_func=lambda t: interpolate(1.0 / 3, 2.0 / 3, t) - ), - MaintainPositionRelativeTo( - velocity_vector, comet - ), - MaintainPositionRelativeTo( - velocity_vector_label, - velocity_vector, - ), - ApplyMethod( - comet.move_to, comet_end, - rate_func=linear, - ), - run_time=2, - ) - self.play(Write(small_time_label), run_time=2) - self.wait() - self.play( - Transform(triangle, alt_triangle), - ShowCreation(small_rect), - small.set_color, YELLOW, - ) - self.wait() - self.play( - Restore(triangle), - FadeOut(small_rect), - small.set_color, WHITE, - ) - self.wait() - - self.triangle = triangle - self.comet = comet - self.delta_t = small_time_label.get_part_by_tex( - "$\\Delta t$" - ) - self.velocity_vector = velocity_vector - self.small_time_label = small_time_label - - def show_sweep_dimensions(self): - triangle = self.triangle - # velocity_vector = self.velocity_vector - delta_t = self.delta_t - comet = self.comet - - triangle_points = triangle.get_anchors()[:3] - top = triangle_points[1] - - area_label = TexMobject( - "\\text{Area}", "=", "\\frac{1}{2}", - "\\text{Base}", "\\times", "\\text{Height}", - ) - area_label.set_color_by_tex_to_color_map({ - "Base": PINK, - "Height": YELLOW, - }) - area_label.to_edge(UP) - equals = area_label.get_part_by_tex("=") - area_expression = TexMobject( - "=", "\\frac{1}{2}", "R", "\\times", - "\\vec{\\textbf{v}}_\\perp", - "\\Delta t", - ) - area_expression.set_color_by_tex_to_color_map({ - "R": PINK, - "textbf{v}": YELLOW, - }) - area_expression.next_to(area_label, DOWN) - area_expression.align_to(equals, LEFT) - - self.R_v_perp = VGroup(*area_expression[-4:-1]) - self.R_v_perp_rect = SurroundingRectangle( - self.R_v_perp, - stroke_color=BLUE, - fill_color=BLACK, - fill_opacity=1, - ) - - base = Line(triangle_points[2], triangle_points[0]) - base.set_stroke(PINK, 3) - base_point = line_intersection( - base.get_start_and_end(), - [top, top + DOWN] - ) - height = Line(top, base_point) - height.set_stroke(YELLOW, 3) - - radius_label = TextMobject("Radius") - radius_label.next_to(base, DOWN, SMALL_BUFF) - radius_label.match_color(base) - - R_term = area_expression.get_part_by_tex("R") - R_term.save_state() - R_term.move_to(radius_label[0]) - R_term.set_fill(opacity=0.5) - - v_perp = Arrow(*height.get_start_and_end(), buff=0) - v_perp.set_color(YELLOW) - v_perp.shift(comet.get_center() - v_perp.get_start()) - v_perp_label = TexMobject( - "\\vec{\\textbf{v}}_\\perp" - ) - v_perp_label.set_color(YELLOW) - v_perp_label.next_to(v_perp, RIGHT, buff=SMALL_BUFF) - - v_perp_delta_t = VGroup(v_perp_label.copy(), delta_t.copy()) - v_perp_delta_t.generate_target() - v_perp_delta_t.target.arrange(RIGHT, buff=SMALL_BUFF) - v_perp_delta_t.target.next_to(height, RIGHT, SMALL_BUFF) - self.small_time_label.add(v_perp_delta_t[1]) - - self.play( - FadeInFromDown(area_label), - self.small_time_label.scale, 0.5, - self.small_time_label.to_corner, UL, - ) - self.wait() - self.play( - ShowCreation(base), - Write(radius_label), - ) - self.wait() - self.play(ShowCreation(height)) - self.wait() - self.play( - GrowArrow(v_perp), - Write(v_perp_label, run_time=1), - ) - self.wait() - self.play(MoveToTarget(v_perp_delta_t)) - self.wait() - self.play(*[ - ReplacementTransform( - area_label.get_part_by_tex(tex).copy(), - area_expression.get_part_by_tex(tex), - ) - for tex in ("=", "\\frac{1}{2}", "\\times") - ]) - self.play(Restore(R_term)) - self.play(ReplacementTransform( - v_perp_delta_t.copy(), - VGroup(*area_expression[-2:]) - )) - self.wait() - - def show_conservation_of_angular_momentum(self): - R_v_perp = self.R_v_perp - R_v_perp_rect = self.R_v_perp_rect - sun_center = self.sun_center - comet = self.comet - comet.save_state() - - vector_field = VectorField( - get_force_field_func((sun_center, -1)) - ) - vector_field.set_fill(opacity=0.8) - vector_field.sort( - lambda p: -get_norm(p - sun_center) - ) - - stays_constant = TextMobject("Stays constant") - stays_constant.next_to( - R_v_perp_rect, DR, buff=MED_LARGE_BUFF - ) - stays_constant.match_color(R_v_perp_rect) - stays_constant_arrow = Arrow( - stays_constant.get_left(), - R_v_perp_rect.get_bottom(), - color=R_v_perp_rect.get_color() - ) - - sun_dot = Dot(sun_center, fill_opacity=0.25) - big_dot = Dot(fill_opacity=0, radius=FRAME_WIDTH) - - R_v_perp.save_state() - R_v_perp.generate_target() - R_v_perp.target.to_edge(LEFT, buff=MED_LARGE_BUFF) - lp, rp = parens = TexMobject("()") - lp.next_to(R_v_perp.target, LEFT) - rp.next_to(R_v_perp.target, RIGHT) - - self.play(Transform( - big_dot, sun_dot, - run_time=1, - remover=True - )) - self.wait() - self.play( - DrawBorderThenFill(R_v_perp_rect), - Animation(R_v_perp), - Write(stays_constant, run_time=1), - GrowArrow(stays_constant_arrow), - ) - self.wait() - foreground = VGroup(*self.get_mobjects()) - self.play( - LaggedStartMap(GrowArrow, vector_field), - Animation(foreground) - ) - for x in range(3): - self.play( - LaggedStartMap( - ApplyFunction, vector_field, - lambda mob: (lambda m: m.scale(1.1).set_fill(opacity=1), mob), - rate_func=there_and_back, - run_time=1 - ), - Animation(foreground) - ) - self.play( - FadeIn(parens), - MoveToTarget(R_v_perp), - ) - self.play( - comet.scale, 2, - comet.next_to, parens, RIGHT, {"buff": SMALL_BUFF} - ) - self.wait() - self.play( - FadeOut(parens), - R_v_perp.restore, - comet.restore, - ) - self.wait(3) - - -class KeplersSecondLawImage(KeplersSecondLaw): - CONFIG = { - "animate_sun": False, - "n_sample_sweeps": 8, - "fade_sample_areas": False, - } - - def construct(self): - self.add_sun() - self.add_foreground_mobjects(self.sun) - self.add_orbit() - self.add_foreground_mobjects(self.comet) - self.show_several_sweeps() - - -class HistoryOfAngularMomentum(TeacherStudentsScene): - CONFIG = { - "camera_config": {"fill_opacity": 1} - } - - def construct(self): - am = VGroup(TextMobject("Angular momentum")) - k2l = TextMobject("Kepler's 2nd law") - arrow = Arrow(ORIGIN, RIGHT) - - group = VGroup(am, arrow, k2l) - group.arrange(RIGHT) - group.next_to(self.hold_up_spot, UL) - - k2l_image = ImageMobject("Kepler2ndLaw") - k2l_image.match_width(k2l) - k2l_image.next_to(k2l, UP) - k2l.add(k2l_image) - - angular_momentum_formula = TexMobject( - "R", "\\times", "m", "\\vec{\\textbf{v}}_\\perp", - ) - angular_momentum_formula.set_color_by_tex_to_color_map({ - "R": PINK, - "v": YELLOW, - }) - angular_momentum_formula.next_to(am, UP) - am.add(angular_momentum_formula) - - self.play( - self.teacher.change, "raise_right_hand", - FadeInFromDown(group), - self.get_student_changes(*3 * ["pondering"]) - ) - self.wait() - self.play( - am.next_to, arrow, RIGHT, - {"index_of_submobject_to_align": 0}, - k2l.next_to, arrow, LEFT, - {"index_of_submobject_to_align": 0}, - path_arc=90 * DEGREES, - run_time=2 - ) - self.wait(3) - - -class FeynmanRecountingNewton(Scene): - CONFIG = { - "camera_config": {"background_opacity": 1}, - } - - def construct(self): - feynman_teaching = ImageMobject("Feynman_teaching") - feynman_teaching.set_width(FRAME_WIDTH) - - newton = ImageMobject("Newton") - principia = ImageMobject("Principia_equal_area") - images = [newton, principia] - for image in images: - image.set_height(5) - newton.to_corner(UL) - principia.next_to(newton, RIGHT) - for image in images: - image.rect = SurroundingRectangle( - image, color=WHITE, buff=0, - ) - - self.play(FadeInFromDown(feynman_teaching, run_time=2)) - self.wait() - self.play( - FadeInFromDown(newton), - FadeInFromDown(newton.rect), - ) - self.wait() - self.play(*[ - FadeInFrom( - mob, direction=3 * LEFT - ) - for mob in (principia, principia.rect) - ]) - self.wait() - - -class IntroduceShapeOfVelocities(AskAboutEllipses, MovingCameraScene): - CONFIG = { - "animate_sun": True, - "sun_center": 2 * RIGHT, - "a": 4.0, - "b": 3.5, - "num_vectors": 25, - } - - def construct(self): - self.setup_orbit() - self.warp_orbit() - self.reference_inverse_square_law() - self.show_velocity_vectors() - self.collect_velocity_vectors() - - def setup_orbit(self): - self.add_sun() - self.add_orbit() - self.add_foreground_mobjects(self.comet) - - def warp_orbit(self): - def func(z, c=3.5): - return 1 * (np.exp((1.0 / c) * (z) + 1) - np.exp(1)) - - ellipse = self.ellipse - ellipse.save_state() - ellipse.generate_target() - ellipse.target.stretch(0.7, 1) - ellipse.target.apply_complex_function(func) - ellipse.target.replace(ellipse, dim_to_match=1) - - self.wait(5) - self.play(MoveToTarget(ellipse, run_time=2)) - self.wait(5) - - def reference_inverse_square_law(self): - ellipse = self.ellipse - force_equation = TexMobject( - "F", "=", "{G", "M", "m", "\\over", "R^2}" - ) - force_equation.move_to(ellipse) - force_equation.set_color_by_tex("F", YELLOW) - - force_arrow, force_arrow_update = self.get_force_arrow_and_update( - self.comet, scale_factor=3, - ) - radial_line, radial_line_update = self.get_radial_line_and_update( - self.comet - ) - - self.add(radial_line_update) - self.add(force_arrow_update) - self.play( - Restore(ellipse), - Write(force_equation), - UpdateFromAlphaFunc( - force_arrow, - lambda m, a: m.set_fill(opacity=a) - ), - UpdateFromAlphaFunc( - radial_line, - lambda m, a: m.set_stroke(width=a) - ), - ) - self.wait(10) - - def show_velocity_vectors(self): - alphas = np.linspace(0, 1, self.num_vectors, endpoint=False) - vectors = VGroup(*[ - self.get_velocity_vector(alpha) - for alpha in alphas - ]) - - moving_vector, moving_vector_animation = self.get_velocity_vector_and_update() - - self.play(LaggedStartMap( - ShowCreation, vectors, - lag_ratio=0.2, - run_time=3, - )) - self.wait(5) - - self.add(moving_vector_animation) - self.play( - FadeOut(vectors), - VFadeIn(moving_vector) - ) - self.wait(10) - vectors.set_fill(opacity=0.5) - self.play( - LaggedStartMap(ShowCreation, vectors), - Animation(moving_vector) - ) - self.wait(5) - - self.velocity_vectors = vectors - self.moving_vector = moving_vector - - def collect_velocity_vectors(self): - vectors = self.velocity_vectors.copy() - frame = self.camera_frame - ellipse = self.ellipse - - frame_shift = 2.5 * LEFT - root_point = ellipse.get_left() + 3 * LEFT + 1 * UP - vector_targets = VGroup() - for vector in vectors: - vector.target = Arrow( - root_point, - root_point + vector.get_vector(), - buff=0, - rectangular_stem_width=0.025, - tip_length=0.2, - color=vector.get_color(), - ) - vector.target.add_to_back( - vector.target.copy().set_stroke(BLACK, 5) - ) - vector_targets.add(vector.target) - - circle = Circle(color=YELLOW) - circle.replace(vector_targets) - circle.scale(1.04) - - velocity_space = TextMobject("Velocity space") - velocity_space.next_to(circle, UP) - - rect = SurroundingRectangle( - VGroup(circle, velocity_space), - buff=MED_LARGE_BUFF, - color=WHITE, - ) - - self.play( - ApplyMethod( - frame.shift, frame_shift, - run_time=2, - ), - LaggedStartMap( - MoveToTarget, vectors, - run_time=4, - ), - FadeInFromDown(velocity_space), - FadeInFromDown(rect), - ) - self.wait(2) - self.play( - ShowCreation(circle), - Animation(vectors) - ) - self.wait(24) - - # Helpers - def get_velocity_vector(self, alpha, d_alpha=0.01, scalar=3.0): - norm = get_norm - ellipse = self.ellipse - sun_center = self.sun.get_center() - - min_length = 0.1 * scalar - max_length = 0.5 * scalar - - p1, p2 = [ - ellipse.point_from_proportion(a) - for a in (alpha, alpha + d_alpha) - ] - vector = Arrow( - p1, p2, buff=0 - ) - radius_vector = p1 - sun_center - curr_v_perp = norm(np.cross( - vector.get_vector(), - radius_vector / norm(radius_vector) - )) - vector.scale( - scalar / (norm(curr_v_perp) * norm(radius_vector)), - about_point=vector.get_start() - ) - vector.set_color( - interpolate_color( - BLUE, RED, inverse_interpolate( - min_length, max_length, - vector.get_length() - ) - ) - ) - vector.add_to_back( - vector.copy().set_stroke(BLACK, 5) - ) - return vector - - def get_velocity_vector_and_update(self): - moving_vector = self.get_velocity_vector(0) - - def update_moving_vector(vector): - new_vector = self.get_velocity_vector( - self.orbit.proportion, - ) - Transform(vector, new_vector).update(1) - - moving_vector_animation = Mobject.add_updater( - moving_vector, update_moving_vector - ) - return moving_vector, moving_vector_animation - - -class AskWhy(TeacherStudentsScene): - def construct(self): - self.student_says( - "Um...why?", - target_mode="confused", - student_index=2, - bubble_kwargs={"direction": LEFT}, - ) - self.play( - self.teacher.change, "happy", - self.get_student_changes( - "raise_left_hand", "sassy", "confused" - ) - ) - self.wait(5) - - -class FeynmanConfusedByNewton(Scene): - def construct(self): - pass - - -class ShowEqualAngleSlices(IntroduceShapeOfVelocities): - CONFIG = { - "animate_sun": True, - "theta": 30 * DEGREES, - } - - def construct(self): - self.setup_orbit() - self.show_equal_angle_slices() - self.ask_about_time_per_slice() - self.areas_are_proportional_to_radius_squared() - self.show_inverse_square_law() - self.directly_compare_velocity_vectors() - - def setup_orbit(self): - IntroduceShapeOfVelocities.setup_orbit(self) - self.remove(self.orbit) - self.add(self.comet) - - def show_equal_angle_slices(self): - sun_center = self.sun.get_center() - ellipse = self.ellipse - - def get_cos_angle_diff(v1, v2): - return np.dot( - v1 / get_norm(v1), - v2 / get_norm(v2), - ) - - lines = VGroup() - angle_arcs = VGroup() - thetas = VGroup() - angles = np.arange(0, TAU, self.theta) - for angle in angles: - prop = angle / TAU - vect = rotate_vector(RIGHT, angle) - end_point = ellipse.point_from_proportion(prop) - curr_cos = get_cos_angle_diff( - end_point - sun_center, vect - ) - coss_diff = (1 - curr_cos) - while abs(coss_diff) > 0.00001: - d_prop = 0.001 - alt_end = ellipse.point_from_proportion( - (prop + d_prop) % 1 - ) - alt_cos = get_cos_angle_diff(alt_end - sun_center, vect) - d_cos = (alt_cos - curr_cos) - - delta_prop = (coss_diff / d_cos) * d_prop - prop += delta_prop - end_point = ellipse.point_from_proportion(prop) - curr_cos = get_cos_angle_diff(end_point - sun_center, vect) - coss_diff = 1 - curr_cos - - line = Line(sun_center, end_point) - line.prop = prop - lines.add(line) - - angle_arc = AnnularSector( - angle=self.theta, - inner_radius=1, - outer_radius=1.05, - ) - angle_arc.rotate(angle, about_point=ORIGIN) - angle_arc.scale(0.5, about_point=ORIGIN) - angle_arc.shift(sun_center) - angle_arc.mid_angle = angle + self.theta / 2 - angle_arcs.add(angle_arc) - - theta = TexMobject("\\theta") - theta.scale(0.6) - vect = rotate_vector(RIGHT, angle_arc.mid_angle) - theta.move_to( - angle_arc.get_center() + 0.2 * vect - ) - thetas.add(theta) - - arcs = VGroup() - wedges = VGroup() - for l1, l2 in adjacent_pairs(lines): - arc = VMobject() - arc.pointwise_become_partial( - ellipse, l1.prop, (l2.prop or 1.0) - ) - arcs.add(arc) - - wedge = VMobject() - wedge.append_vectorized_mobject( - Line(sun_center, arc.points[0]) - ) - wedge.append_vectorized_mobject(arc) - wedge.append_vectorized_mobject( - Line(arc.points[-1], sun_center) - ) - wedges.add(wedge) - - lines.set_stroke(LIGHT_GREY, 2) - angle_arcs.set_color_by_gradient( - YELLOW, BLUE, RED, PINK, YELLOW - ) - arcs.set_color_by_gradient(BLUE, YELLOW) - wedges.set_stroke(width=0) - wedges.set_fill(opacity=1) - wedges.set_color_by_gradient(BLUE, COBALT, BLUE_E, BLUE) - - kwargs = { - "run_time": 6, - "lag_ratio": 0.2, - "rate_func": there_and_back, - } - faders = VGroup(wedges, angle_arcs, thetas) - faders.set_fill(opacity=0.4) - thetas.set_fill(opacity=0) - - self.play( - FadeIn(faders), - *list(map(ShowCreation, lines)) - ) - self.play(*[ - LaggedStartMap( - ApplyMethod, fader, - lambda m: (m.set_fill, {"opacity": 1}), - **kwargs - ) - for fader in faders - ] + [Animation(lines)]) - self.wait() - - self.lines = lines - self.wedges = wedges - self.arcs = arcs - self.angle_arcs = angle_arcs - self.thetas = thetas - - def ask_about_time_per_slice(self): - wedge1 = self.wedges[0] - wedge2 = self.wedges[len(self.wedges) / 2] - arc1 = self.arcs[0] - arc2 = self.arcs[len(self.arcs) / 2] - comet = self.comet - frame = self.camera_frame - - words1 = TextMobject( - "Time spent \\\\ traversing \\\\ this slice?" - ) - words2 = TextMobject("How about \\\\ this one?") - - words1.to_corner(UR) - words2.next_to(wedge2, LEFT, MED_LARGE_BUFF) - - arrow1 = Arrow( - words1.get_bottom(), - wedge1.get_center() + wedge1.get_height() * DOWN / 2, - color=WHITE, - ) - arrow2 = Arrow( - words2.get_right(), - wedge2.get_center() + wedge2.get_height() * UL / 4, - color=WHITE - ) - - foreground = VGroup( - self.ellipse, self.angle_arcs, - self.lines, comet, - ) - self.play( - Write(words1), - wedge1.set_fill, {"opacity": 1}, - GrowArrow(arrow1), - Animation(foreground), - frame.scale, 1.2, - ) - self.play(MoveAlongPath(comet, arc1, rate_func=linear)) - self.play( - Write(words2), - wedge2.set_fill, {"opacity": 1}, - Write(arrow2), - Animation(foreground), - ) - self.play(MoveAlongPath(comet, arc2, rate_func=linear, run_time=3)) - self.wait() - - self.area_questions = VGroup(words1, words2) - self.area_question_arrows = VGroup(arrow1, arrow2) - self.highlighted_wedges = VGroup(wedge1, wedge2) - - def areas_are_proportional_to_radius_squared(self): - wedges = self.highlighted_wedges - wedge = wedges[1] - frame = self.camera_frame - ellipse = self.ellipse - sun_center = self.sun.get_center() - - line = self.lines[len(self.lines) / 2] - thick_line = line.copy().set_stroke(PINK, 4) - radius_word = TextMobject("Radius") - radius_word.next_to(thick_line, UP, SMALL_BUFF) - radius_word.match_color(thick_line) - - arc = self.arcs[len(self.arcs) / 2] - thick_arc = arc.copy().set_stroke(RED, 4) - - scaling_group = VGroup( - wedge, thick_line, radius_word, thick_arc - ) - - expression = TextMobject( - "Time", "$\\propto$", - "Area", "$\\propto$", "$(\\text{Radius})^2$" - ) - expression.next_to(ellipse, UP, LARGE_BUFF) - - prop_to_brace = Brace(expression[1], DOWN, buff=SMALL_BUFF) - prop_to_words = TextMobject("(proportional to)") - prop_to_words.scale(0.7) - prop_to_words.next_to(prop_to_brace, DOWN, SMALL_BUFF) - VGroup(prop_to_words, prop_to_brace).set_color(GREEN) - - self.play( - Write(expression[:3]), - frame.shift, 0.5 * UP, - FadeInFromDown(prop_to_words), - GrowFromCenter(prop_to_brace), - ) - self.wait(2) - self.play( - ShowCreation(thick_line), - FadeInFromDown(radius_word) - ) - self.wait() - self.play(ShowCreationThenDestruction(thick_arc)) - self.play(ShowCreation(thick_arc)) - self.wait() - self.play(Write(expression[3:])) - self.play( - scaling_group.scale, 0.5, - {"about_point": sun_center}, - Animation(self.area_question_arrows), - Animation(self.comet), - rate_func=there_and_back, - run_time=4, - ) - self.wait() - - expression.add(prop_to_brace, prop_to_words) - self.proportionality_expression = expression - - def show_inverse_square_law(self): - prop_exp = self.proportionality_expression - comet = self.comet - frame = self.camera_frame - ellipse = self.ellipse - orbit = self.orbit - next_line = self.lines[(len(self.lines) / 2) + 1] - - arc = self.arcs[len(self.arcs) / 2] - - force_expression = TexMobject( - "ma", "=", "\\text{Force}", - "\\propto", "\\frac{1}{(\\text{Radius})^2}" - ) - force_expression.next_to(ellipse, LEFT, MED_LARGE_BUFF) - force_expression.align_to(prop_exp, UP) - force_expression.set_color_by_tex("Force", YELLOW) - - acceleration_expression = TexMobject( - "a", "=", "{\\Delta v", - "\\over", "\\Delta t}", - "\\propto", "{1 \\over (\\text{Radius})^2}" - ) - acceleration_expression.next_to( - force_expression, DOWN, buff=0.75, - aligned_edge=LEFT - ) - - delta_v_expression = TexMobject( - "\\Delta v}", "\\propto", - "{\\Delta t", "\\over", "(\\text{Radius})^2}" - ) - delta_v_expression.next_to( - acceleration_expression, DOWN, buff=0.75, - aligned_edge=LEFT - ) - delta_t_numerator = delta_v_expression.get_part_by_tex( - "\\Delta t" - ) - moving_R_squared = prop_exp.get_part_by_tex("Radius").copy() - moving_R_squared.generate_target() - moving_R_squared.target.move_to(delta_t_numerator, DOWN) - moving_R_squared.target.set_color(GREEN) - - randy = Randolph() - randy.next_to(force_expression, DOWN, LARGE_BUFF) - - force_vector, force_vector_update = self.get_force_arrow_and_update( - comet, scale_factor=3, - ) - moving_vector, moving_vector_animation = self.get_velocity_vector_and_update() - - self.play( - FadeOut(self.area_questions), - FadeOut(self.area_question_arrows), - FadeInFromDown(force_expression), - frame.shift, 2 * LEFT, - ) - self.remove(*self.area_questions) - self.play( - randy.change, "confused", force_expression, - VFadeIn(randy) - ) - self.wait(2) - self.play( - randy.change, "pondering", force_expression[0], - ShowPassingFlashAround(force_expression[:2]), - ) - self.play(Blink(randy)) - self.play( - FadeInFromDown(acceleration_expression), - randy.change, "hooray", force_expression, - randy.shift, 2 * DOWN, - ) - self.wait(2) - self.play(Blink(randy)) - self.play(randy.change, "thinking") - self.wait() - - self.play( - comet.move_to, arc.points[0], - path_arc=90 * DEGREES - ) - force_vector_update.update(0) - self.play( - Blink(randy), - GrowArrow(force_vector) - ) - self.add(force_vector_update) - self.add_foreground_mobjects(comet) - # Slightly hacky orbit treatment here... - orbit.proportion = 0.5 - moving_vector_animation.update(0) - start_velocity_vector = moving_vector.copy() - self.play( - GrowArrow(start_velocity_vector), - randy.look_at, moving_vector - ) - self.add(moving_vector_animation) - self.add(orbit) - while orbit.proportion < next_line.prop: - self.wait(self.frame_duration) - self.remove(orbit) - self.add_foreground_mobjects(comet) - self.wait(2) - self.play( - randy.change, "pondering", force_expression, - randy.shift, 2 * DOWN, - FadeInFromDown(delta_v_expression) - ) - self.play(Blink(randy)) - self.wait(2) - self.play( - delta_t_numerator.scale, 1.5, {"about_edge": DOWN}, - delta_t_numerator.set_color, YELLOW - ) - self.play(ShowCreationThenFadeAround(prop_exp[:-2])) - self.play( - delta_t_numerator.fade, 1, - MoveToTarget(moving_R_squared), - randy.change, "happy", delta_v_expression - ) - delta_v_expression.add(moving_R_squared) - self.wait() - self.play(FadeOut(randy)) - - self.start_velocity_vector = start_velocity_vector - self.end_velocity_vector = moving_vector.copy() - self.moving_vector = moving_vector - self.force_expressions = VGroup( - force_expression, - acceleration_expression, - delta_v_expression, - ) - - def directly_compare_velocity_vectors(self): - ellipse = self.ellipse - lines = self.lines - expressions = self.force_expressions - vectors = VGroup(*[ - self.get_velocity_vector(line.prop) - for line in lines - ]) - index = len(vectors) / 2 - v1 = vectors[index] - v2 = vectors[index + 1] - - root_point = ellipse.get_left() + 3 * LEFT + DOWN - root_dot = Dot(root_point) - - for vector in vectors: - vector.save_state() - vector.target = Arrow( - *vector.get_start_and_end(), - color=vector.get_color(), - buff=0 - ) - vector.target.scale(2) - vector.target.shift( - root_point - vector.target.get_start() - ) - vector.target.add_to_back( - vector.target.copy().set_stroke(BLACK, 5) - ) - - difference_vectors = VGroup() - external_angle_lines = VGroup() - external_angle_arcs = VGroup() - for vect1, vect2 in adjacent_pairs(vectors): - diff_vect = Arrow( - vect1.target.get_end(), - vect2.target.get_end(), - buff=0, - color=YELLOW, - rectangular_stem_width=0.025, - tip_length=0.15 - ) - diff_vect.add_to_back( - diff_vect.copy().set_stroke(BLACK, 2) - ) - difference_vectors.add(diff_vect) - - line = Line( - diff_vect.get_start(), - diff_vect.get_start() + 2 * diff_vect.get_vector(), - ) - external_angle_lines.add(line) - - arc = Arc(self.theta, stroke_width=2) - arc.rotate(line.get_angle(), about_point=ORIGIN) - arc.scale(0.4, about_point=ORIGIN) - arc.shift(line.get_center()) - external_angle_arcs.add(arc) - external_angle_lines.set_stroke(LIGHT_GREY, 2) - diff_vect = difference_vectors[index] - - polygon = Polygon(*[ - vect.target.get_end() - for vect in vectors - ]) - polygon.set_fill(BLUE_E, opacity=0.8) - polygon.set_stroke(WHITE, 3) - - self.play(ShowCreationThenFadeAround(v1)) - self.play( - MoveToTarget(v1), - GrowFromCenter(root_dot), - expressions.scale, 0.5, {"about_edge": UL} - ) - self.wait() - self.play( - ReplacementTransform( - v1.saved_state.copy(), v2.saved_state, - path_arc=self.theta - ) - ) - self.play(MoveToTarget(v2), Animation(root_dot)) - self.wait() - self.play(GrowArrow(diff_vect)) - self.wait() - - n = len(vectors) - for i in range(n - 1): - v1 = vectors[(i + index + 1) % n] - v2 = vectors[(i + index + 2) % n] - diff_vect = difference_vectors[(i + index + 1) % n] - # TODO, v2.saved_state is on screen untracked - self.play(ReplacementTransform( - v1.saved_state.copy(), v2.saved_state, - path_arc=self.theta - )) - self.play( - MoveToTarget(v2), - GrowArrow(diff_vect) - ) - self.add(self.orbit) - self.wait() - self.play( - LaggedStartMap(ShowCreation, external_angle_lines), - LaggedStartMap(ShowCreation, external_angle_arcs), - Animation(difference_vectors), - ) - self.add_foreground_mobjects(difference_vectors) - self.wait(2) - self.play(FadeIn(polygon)) - self.wait(5) - self.play(FadeOut(polygon)) - self.wait(15) - self.play(FadeIn(polygon)) - - -class ShowEqualAngleSlices15DegreeSlices(ShowEqualAngleSlices): - CONFIG = { - "animate_sun": True, - "theta": 15 * DEGREES, - } - - -class ShowEqualAngleSlices5DegreeSlices(ShowEqualAngleSlices): - CONFIG = { - "animate_sun": True, - "theta": 5 * DEGREES, - } - - -class IKnowThisIsTricky(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "All you need is \\\\ infinite intelligence", - bubble_kwargs={ - "width": 4, - "height": 3, - }, - added_anims=[ - self.get_student_changes( - *3 * ["horrified"], - look_at_arg=self.screen - ) - ] - ) - self.look_at(self.screen) - self.wait(3) - - -class PonderOverOffCenterDiagram(PiCreatureScene): - def construct(self): - randy, morty = self.pi_creatures - velocity_diagram = self.get_velocity_diagram() - bubble = randy.get_bubble() - - rect = SurroundingRectangle( - velocity_diagram, - buff=MED_LARGE_BUFF, - color=LIGHT_GREY - ) - rect.stretch(1.2, 1, about_edge=DOWN) - words = TextMobject("Velocity space") - words.next_to(rect.get_top(), DOWN) - - self.play( - LaggedStartMap(GrowFromCenter, velocity_diagram), - randy.change, "pondering", - morty.change, "confused", - ) - self.wait(2) - self.play(ShowCreation(bubble)) - self.wait(2) - self.play( - FadeOut(bubble), - randy.change, "confused", - morty.change, "pondering", - ShowCreation(rect) - ) - self.play(Write(words)) - self.wait(2) - - def create_pi_creatures(self): - randy = Randolph(height=2.5) - randy.to_corner(DL) - morty = Mortimer(height=2.5) - morty.to_corner(DR) - return randy, morty - - def get_velocity_diagram(self): - circle = Circle(color=WHITE, radius=2) - circle.rotate(90 * DEGREES) - circle.to_edge(DOWN, buff=LARGE_BUFF) - root_point = interpolate( - circle.get_center(), - circle.get_bottom(), - 0.5, - ) - dot = Dot(root_point) - vectors = VGroup() - for a in np.arange(0, 1, 1.0 / 24): - end_point = circle.point_from_proportion(a) - vector = Arrow(root_point, end_point, buff=0) - vector.set_color(interpolate_color( - BLUE, RED, - inverse_interpolate( - 1, 3, vector.get_length(), - ) - )) - vector.add_to_back(vector.copy().set_stroke(BLACK, 5)) - vectors.add(vector) - - vectors.add_to_back(circle) - vectors.add(dot) - return vectors - - -class OneMoreTrick(TeacherStudentsScene): - def construct(self): - for student in self.students: - student.change("tired") - self.teacher_says("Just one more \\\\ tricky bit!") - self.change_all_student_modes("hooray") - self.wait(3) - - -class UseVelocityDiagramToDeduceCurve(ShowEqualAngleSlices): - CONFIG = { - "animate_sun": True, - "theta": 15 * DEGREES, - "index": 6, - } - - def construct(self): - self.setup_orbit() - self.setup_velocity_diagram() - self.show_theta_degrees() - self.match_velocity_vector_to_tangency() - self.replace_vectors_with_lines() - self.not_that_velocity_vector_is_theta() - self.ask_about_curve() - self.show_90_degree_rotation() - self.show_geometry_of_rotated_diagram() - - def setup_orbit(self): - ShowEqualAngleSlices.setup_orbit(self) - self.force_skipping() - self.show_equal_angle_slices() - self.revert_to_original_skipping_status() - - orbit_word = self.orbit_word = TextMobject("Orbit") - orbit_word.scale(1.5) - orbit_word.next_to(self.ellipse, UP, LARGE_BUFF) - self.add(orbit_word) - - def setup_velocity_diagram(self): - ellipse = self.ellipse - root_point = ellipse.get_left() + 4 * LEFT + DOWN - frame = self.camera_frame - - root_dot = Dot(root_point) - vectors = VGroup() - original_vectors = VGroup() - for line in self.lines: - vector = self.get_velocity_vector(line.prop) - vector.save_state() - original_vectors.add(vector.copy()) - vector.target = self.get_velocity_vector( - line.prop, scalar=8.0 - ) - vector.target.shift( - root_point - vector.target.get_start() - ) - - vectors.add(vector) - - circle = Circle() - circle.rotate(92 * DEGREES) - circle.replace(VGroup(*[v.target for v in vectors])) - circle.set_stroke(WHITE, 2) - circle.shift( - (root_point[0] - circle.get_center()[0]) * RIGHT - ) - circle.shift(0.035 * LEFT) # ?!? - - velocities_word = TextMobject("Velocities") - velocities_word.scale(1.5) - velocities_word.next_to(circle, UP) - velocities_word.align_to(self.orbit_word, DOWN) - - frame.scale(1.2) - frame.shift(3 * LEFT + 0.5 * UP) - - self.play(ApplyWave(ellipse)) - self.play(*list(map(GrowArrow, vectors))) - self.play( - LaggedStartMap( - MoveToTarget, vectors, - lag_ratio=1, - run_time=2 - ), - GrowFromCenter(root_dot), - FadeInFromDown(velocities_word), - ) - self.add_foreground_mobjects(root_dot) - self.play( - ShowCreation(circle), - Animation(vectors), - ) - self.wait() - - self.vectors = vectors - self.original_vectors = original_vectors - self.velocity_circle = circle - self.root_dot = root_dot - self.circle = circle - - def show_theta_degrees(self): - lines = self.lines - ellipse = self.ellipse - circle = self.circle - vectors = self.vectors - comet = self.comet - sun_center = self.sun.get_center() - - index = self.index - angle = fdiv(index, len(lines)) * TAU - thick_line = lines[index].copy() - thick_line.set_stroke(RED, 3) - horizontal = lines[0].copy() - horizontal.set_stroke(WHITE, 3) - - ellipse_arc = VMobject() - ellipse_arc.pointwise_become_partial( - ellipse, 0, thick_line.prop - ) - ellipse_arc.set_stroke(YELLOW, 3) - - ellipse_wedge = self.get_wedge(ellipse_arc, sun_center) - ellipse_wedge_start = self.get_wedge( - VectorizedPoint(ellipse.get_right()), sun_center - ) - - ellipse_angle_arc = Arc( - self.theta * index, - radius=0.5 - ) - ellipse_angle_arc.shift(sun_center) - ellipse_theta = TexMobject("\\theta") - ellipse_theta.next_to(ellipse_angle_arc, RIGHT, MED_SMALL_BUFF) - ellipse_theta.shift(2 * SMALL_BUFF * UL) - - vector = vectors[index].deepcopy() - vector.set_fill(YELLOW) - vector.save_state() - Transform(vector, vectors[0]).update(1) - vector.set_fill(YELLOW) - circle_arc = VMobject() - circle_arc.pointwise_become_partial( - circle, 0, fdiv(index, len(vectors)) - ) - circle_arc.set_stroke(RED, 4) - circle_theta = TexMobject("\\theta") - circle_theta.scale(1.5) - circle_theta.next_to(circle_arc, UP, SMALL_BUFF) - circle_theta.shift(SMALL_BUFF * DL) - - circle_wedge = self.get_wedge(circle_arc, circle.get_center()) - circle_wedge.set_fill(PINK) - circle_wedge_start = self.get_wedge( - Line(circle.get_top(), circle.get_top()), - circle.get_center() - ).match_style(circle_wedge) - - circle_center_dot = Dot(circle.get_center()) - # circle_center_dot.set_color(BLUE) - - self.play(FocusOn(comet)) - self.play( - ReplacementTransform( - ellipse_wedge_start, ellipse_wedge, - path_arc=angle, - ), - FadeIn(ellipse_arc), - ShowCreation(ellipse_angle_arc), - Write(ellipse_theta), - ReplacementTransform( - lines[0].copy(), thick_line, - path_arc=angle - ), - MoveAlongPath(comet, ellipse_arc), - run_time=2 - ) - self.wait() - self.play( - ReplacementTransform( - circle_wedge_start, circle_wedge, - path_arc=angle - ), - ShowCreation(circle_arc), - Write(circle_theta), - Restore(vector, path_arc=angle), - GrowFromCenter(circle_center_dot), - FadeIn(horizontal), - run_time=2 - ) - self.wait() - - self.set_variables_as_attrs( - index, - ellipse_wedge, ellipse_arc, - ellipse_angle_arc, ellipse_theta, - thick_line, horizontal, - circle_wedge, circle_arc, - circle_theta, circle_center_dot, - highlighted_vector=vector - ) - - def match_velocity_vector_to_tangency(self): - vector = self.highlighted_vector - comet = self.comet - original_vector = self.original_vectors[self.index].copy() - original_vector.set_fill(YELLOW) - - tangent_line = Line( - *original_vector.get_start_and_end() - ) - tangent_line.set_stroke(LIGHT_GREY, 3) - tangent_line.scale(5) - tangent_line.move_to(comet) - - self.play( - ReplacementTransform( - vector.copy(), original_vector, - run_time=2 - ), - Animation(comet), - ) - self.wait() - self.play( - ShowCreation(tangent_line), - Animation(original_vector), - Animation(comet), - ) - self.wait() - - self.set_variables_as_attrs( - example_tangent_line=tangent_line, - example_tangent_vector=original_vector, - ) - - def replace_vectors_with_lines(self): - vectors = self.vectors - original_vectors = self.original_vectors - root_dot = self.root_dot - highlighted_vector = self.highlighted_vector - - lines = VGroup() - tangent_lines = VGroup() - for vect, o_vect in zip(vectors, original_vectors): - line = Line(*vect.get_start_and_end()) - t_line = Line(*o_vect.get_start_and_end()) - t_line.scale(5) - t_line.move_to(o_vect.get_start()) - - lines.add(line) - tangent_lines.add(t_line) - - vect.generate_target() - vect.target.scale(0, about_point=root_dot.get_center()) - - lines.set_stroke(GREEN, 2) - tangent_lines.set_stroke(GREEN, 2) - - highlighted_line = Line( - *highlighted_vector.get_start_and_end(), - stroke_color=YELLOW, - stroke_width=4 - ) - - self.play( - LaggedStartMap(MoveToTarget, vectors), - highlighted_vector.scale, 0, - {"about_point": root_dot.get_center()}, - Animation(highlighted_vector), - Animation(self.circle_wedge), - Animation(self.circle_arc), - Animation(self.circle), - Animation(self.circle_center_dot), - ) - self.remove(vectors, highlighted_vector) - self.play( - LaggedStartMap(ShowCreation, lines), - ShowCreation(highlighted_line), - Animation(highlighted_vector), - ) - self.wait() - self.play( - ReplacementTransform( - lines.copy(), - tangent_lines, - run_time=3, - ) - ) - self.wait() - self.play(FadeOut(tangent_lines)) - - self.eccentric_lines = lines - self.highlighted_line = highlighted_line - - def not_that_velocity_vector_is_theta(self): - vector = self.example_tangent_vector - v_line = Line(DOWN, UP) - v_line.move_to(vector.get_start(), DOWN) - angle = vector.get_angle() - 90 * DEGREES - arc = Arc(angle, radius=0.5) - arc.rotate(90 * DEGREES, about_point=ORIGIN) - arc.shift(vector.get_start()) - - theta_q = TexMobject("\\theta ?") - theta_q.next_to(arc, UP) - theta_q.shift(SMALL_BUFF * LEFT) - cross = Cross(theta_q) - - self.play(ShowCreation(v_line)) - self.play( - ShowCreation(arc), - FadeInFromDown(theta_q), - ) - self.wait() - self.play(ShowCreation(cross)) - self.wait() - self.play(*list(map(FadeOut, [v_line, arc, theta_q, cross]))) - self.wait() - self.play( - ReplacementTransform( - self.ellipse_theta.copy(), self.circle_theta, - ), - ReplacementTransform( - self.ellipse_angle_arc.copy(), self.circle_arc, - ), - run_time=2, - ) - self.wait() - self.play( - ReplacementTransform( - self.circle.copy(), - self.circle_center_dot, - ) - ) - self.wait() - - def ask_about_curve(self): - ellipse = self.ellipse - circle = self.circle - line = self.highlighted_line.copy() - vector = self.example_tangent_vector - - morty = Mortimer(height=2.5) - morty.move_to(ellipse.get_corner(UL)) - morty.shift(MED_SMALL_BUFF * LEFT) - - self.play(FadeIn(morty)) - self.play( - morty.change, "confused", ellipse, - ShowCreationThenDestruction( - ellipse.copy().set_stroke(BLUE, 3), - run_time=2 - ) - ) - self.play( - Blink(morty), - ApplyWave(ellipse), - ) - self.play(morty.look_at, circle) - self.play(morty.change, "pondering", circle) - self.play(Blink(morty)) - self.play(morty.look_at, ellipse) - self.play(morty.change, "maybe", ellipse) - self.play( - line.move_to, vector, run_time=2 - ) - self.play(FadeOut(line)) - self.wait() - self.play(morty.look_at, circle) - self.wait() - self.play(FadeOut(morty)) - - def show_90_degree_rotation(self): - circle = self.circle - circle_setup = VGroup( - circle, self.eccentric_lines, - self.circle_wedge, - self.circle_arc, - self.highlighted_line, - self.circle_center_dot, - self.root_dot, - self.circle_theta, - ) - circle_setup.generate_target() - angle = -90 * DEGREES - circle_setup.target.rotate( - angle, - about_point=circle.get_center() - ) - circle_setup.target[-1].rotate(-angle) - circle_setup.target[2].set_fill(opacity=0) - circle_setup.target[2].set_stroke(WHITE, 4) - - self.play(MoveToTarget(circle_setup, path_arc=angle)) - self.wait() - - lines = self.eccentric_lines - highlighted_line = self.highlighted_line - ghost_lines = lines.copy() - ghost_lines.set_stroke(width=1) - ghost_lines[self.index].set_stroke(YELLOW, 4) - for mob in list(lines) + [highlighted_line]: - mob.generate_target() - mob.save_state() - mob.target.rotate(-angle) - - foci = [ - self.root_dot.get_center(), - circle.get_center(), - ] - a = circle.get_width() / 4 - c = get_norm(foci[1] - foci[0]) / 2 - b = np.sqrt(a**2 - c**2) - little_ellipse = Circle(radius=a) - little_ellipse.stretch(b / a, 1) - little_ellipse.move_to(center_of_mass(foci)) - little_ellipse.set_stroke(PINK, 4) - - self.add(ghost_lines) - self.play( - LaggedStartMap( - MoveToTarget, lines, - lag_ratio=0.1, - run_time=8, - ), - MoveToTarget(highlighted_line), - path_arc=-angle, - ) - self.play(ShowCreation(little_ellipse)) - self.wait(2) - self.play( - little_ellipse.replace, self.ellipse, - run_time=4, - rate_func=there_and_back_with_pause - ) - self.wait(2) - - self.play(*[ - Restore( - mob, - path_arc=angle, - run_time=4, - rate_func=there_and_back_with_pause - ) - for mob in list(lines) + [highlighted_line] - ] + [Animation(little_ellipse)]) - - self.ghost_lines = ghost_lines - self.little_ellipse = little_ellipse - - def show_geometry_of_rotated_diagram(self): - ellipse = self.ellipse - little_ellipse = self.little_ellipse - circle = self.circle - perp_line = self.highlighted_line.copy() - perp_line.rotate(PI) - circle_arc = self.circle_arc - arc_copy = circle_arc.copy() - center = circle.get_center() - velocity_vector = self.example_tangent_vector - - e_line = perp_line.copy().rotate(90 * DEGREES) - c_line = Line(center, e_line.get_end()) - c_line.set_stroke(WHITE, 4) - - # lines = [c_line, e_line, perp_line] - - tangency_point = line_intersection( - perp_line.get_start_and_end(), - c_line.get_start_and_end(), - ) - tangency_point_dot = Dot(tangency_point) - tangency_point_dot.set_color(BLUE) - tangency_point_dot.save_state() - tangency_point_dot.scale(5) - tangency_point_dot.set_fill(opacity=0) - - def indicate(line): - red_copy = line.copy().set_stroke(RED, 5) - return ShowPassingFlash(red_copy, run_time=2) - - self.play( - self.ghost_lines.set_stroke, {"width": 0.5}, - self.eccentric_lines.set_stroke, {"width": 0.5}, - *list(map(WiggleOutThenIn, [e_line, c_line])) - ) - for x in range(3): - self.play( - indicate(e_line), - indicate(c_line), - ) - self.play(WiggleOutThenIn(perp_line)) - for x in range(2): - self.play(indicate(perp_line)) - self.play(Restore(tangency_point_dot)) - self.add_foreground_mobjects(tangency_point_dot) - self.wait(2) - self.play( - arc_copy.scale, 0.15, {"about_point": center}, - run_time=2 - ) - self.wait(2) - self.play( - perp_line.move_to, velocity_vector, - run_time=4, - rate_func=there_and_back_with_pause - ) - self.wait() - self.play( - little_ellipse.replace, ellipse, - run_time=4, - rate_func=there_and_back_with_pause - ) - self.wait() - - # Helpers - def get_wedge(self, arc, center_point, opacity=0.8): - wedge = VMobject() - wedge.append_vectorized_mobject( - Line(center_point, arc.points[0]) - ) - wedge.append_vectorized_mobject(arc) - wedge.append_vectorized_mobject( - Line(arc.points[-1], center_point) - ) - wedge.set_stroke(width=0) - wedge.set_fill(COBALT, opacity=opacity) - return wedge - - -class ShowSunVectorField(Scene): - def construct(self): - sun_center = IntroduceShapeOfVelocities.CONFIG["sun_center"] - vector_field = VectorField( - get_force_field_func((sun_center, -1)) - ) - vector_field.set_fill(opacity=0.8) - vector_field.sort( - lambda p: -get_norm(p - sun_center) - ) - - for vector in vector_field: - vector.generate_target() - vector.target.set_fill(opacity=1) - vector.target.set_stroke(YELLOW, 0.5) - - for x in range(3): - self.play(LaggedStartMap( - MoveToTarget, vector_field, - rate_func=there_and_back, - lag_ratio=0.5, - )) - - -class TryToRememberProof(PiCreatureScene): - def construct(self): - randy = self.pi_creature - - words = TextMobject("Oh god...how \\\\ did it go?") - words.next_to(randy, UP) - words.shift_onto_screen() - - self.play( - randy.change, "telepath", - FadeInFromDown(words) - ) - self.look_at(ORIGIN) - self.wait(2) - self.play(randy.change, "concerned_musician") - self.look_at(ORIGIN) - self.wait(3) - - -class PatYourselfOnTheBack(TeacherStudentsScene): - CONFIG = { - "camera_config": {"background_opacity": 1}, - } - - def construct(self): - self.teacher_says( - "Pat yourself \\\\ on the back!", - target_mode="hooray" - ) - self.change_all_student_modes("happy") - self.wait(3) - self.play( - RemovePiCreatureBubble( - self.teacher, - target_mode="raise_right_hand" - ), - self.get_student_changes( - *3 * ["pondering"], - look_at_arg=self.screen - ) - ) - self.look_at(UP) - self.wait(8) - self.change_student_modes(*3 * ["thinking"]) - self.look_at(UP) - self.wait(12) - self.teacher_says("I just love this!") - - feynman = ImageMobject("Feynman", height=4) - feynman.to_corner(UL) - chess = ImageMobject("ChessGameOfTheCentury") - chess.set_height(4) - chess.next_to(feynman) - - self.play(FadeInFromDown(feynman)) - self.wait() - self.play( - RemovePiCreatureBubble(self.teacher, target_mode="happy"), - FadeInFromDown(chess) - ) - self.wait(2) - - -class Thumbnail(ShowEmergingEllipse): - CONFIG = { - "num_lines": 50, - } - - def construct(self): - background = ImageMobject("Feynman_teaching") - background.set_width(FRAME_WIDTH) - background.scale(1.05) - background.to_corner(UR, buff=0) - background.shift(2 * UP) - - self.add(background) - - circle = self.get_circle() - circle.set_stroke(width=6) - circle.set_height(6.5) - circle.to_corner(UL) - circle.set_fill(BLACK, 0.9) - lines = self.get_lines() - lines.set_stroke(YELLOW, 5) - lines.set_color_by_gradient(YELLOW, RED) - ghost_lines = self.get_ghost_lines(lines) - for line in lines: - line.rotate(90 * DEGREES) - ellipse = self.get_ellipse() - ellipse.set_stroke(BLUE, 6) - sun = ImageMobject("sun", height=0.5) - sun.move_to(self.get_eccentricity_point()) - - circle_group = VGroup(circle, ghost_lines, lines, ellipse, sun) - self.add(circle_group) - - l1 = Line( - circle.point_from_proportion(0.175), - 6.25 * RIGHT + 0.75 * DOWN - ) - l2 = Line( - circle.point_from_proportion(0.75), - 6.25 * RIGHT + 2.5 * DOWN - ) - l2a = VMobject().pointwise_become_partial(l2, 0, 0.56) - l2b = VMobject().pointwise_become_partial(l2, 0.715, 1) - expand_lines = VGroup(l1, l2a, l2b) - - expand_lines.set_stroke("RED", 5) - self.add(expand_lines) - self.add(circle_group) - - small_group = circle_group.copy() - small_group.scale(0.2) - small_group.stretch(1.35, 1) - small_group.move_to(6.2 * RIGHT + 1.6 * DOWN) - for mob in small_group: - if isinstance(mob, VMobject) and mob.get_stroke_width() > 1: - mob.set_stroke(width=1) - small_group[0].set_fill(opacity=0.25) - self.add(small_group) - - title = TextMobject( - "Feynman's \\\\", "Lost \\\\", "Lecture", - alignment="" - ) - title.scale(2.4) - for part in title: - part.add_to_back( - part.copy().set_stroke(BLACK, 12).set_fill(BLACK, 1) - ) - title.to_corner(UR) - title[2].to_edge(RIGHT) - title[1].shift(0.9 * RIGHT) - title.shift(0.5 * LEFT) - self.add(title) diff --git a/from_3b1b/old/matrix_as_transform_2d.py b/from_3b1b/old/matrix_as_transform_2d.py deleted file mode 100644 index 38e8a35e..00000000 --- a/from_3b1b/old/matrix_as_transform_2d.py +++ /dev/null @@ -1,551 +0,0 @@ -#!/usr/bin/env python - -import numpy as np -import itertools as it -from copy import deepcopy -import sys - -from manimlib.imports import * - -ARROW_CONFIG = {"stroke_width" : 2*DEFAULT_STROKE_WIDTH} -LIGHT_RED = RED_E - -def matrix_to_string(matrix): - return "--".join(["-".join(map(str, row)) for row in matrix]) - -def matrix_mobject(matrix): - return TextMobject( - """ - \\left( - \\begin{array}{%s} - %d & %d \\\\ - %d & %d - \\end{array} - \\right) - """%tuple(["c"*matrix.shape[1]] + list(matrix.flatten())), - size = "\\Huge" - ) - -class ShowMultiplication(NumberLineScene): - args_list = [ - (2, True), - (0.5, True), - (-3, True), - (-3, True), - (2, True), - (6, True), - ] - @staticmethod - def args_to_string(num, show_original_line): - end_string = "WithCopiedOriginalLine" if show_original_line else "" - return str(num) + end_string - @staticmethod - def string_to_args(string): - parts = string.split() - if len(parts) == 2: - num, original_line = parts - show_original_line = original_line == "WithCopiedOriginalLine" - return float(num), False - else: - return float(parts[0]), False - - def construct(self, num, show_original_line): - config = { - "density" : max(abs(num), 1)*DEFAULT_POINT_DENSITY_1D, - "stroke_width" : 2*DEFAULT_STROKE_WIDTH - } - if abs(num) < 1: - config["numerical_radius"] = FRAME_X_RADIUS/num - - NumberLineScene.construct(self, **config) - if show_original_line: - self.copy_original_line() - self.wait() - self.show_multiplication(num, run_time = 1.5) - self.wait() - - def copy_original_line(self): - copied_line = deepcopy(self.number_line) - copied_num_mobs = deepcopy(self.number_mobs) - self.play( - ApplyFunction( - lambda m : m.shift(DOWN).set_color("lightgreen"), - copied_line - ), *[ - ApplyMethod(mob.shift, DOWN) - for mob in copied_num_mobs - ] - ) - self.wait() - -class ExamplesOfOneDimensionalLinearTransforms(ShowMultiplication): - args_list = [] - @staticmethod - def args_to_string(): - return "" - - def construct(self): - for num in [2, 0.5, -3]: - self.clear() - ShowMultiplication.construct(self, num, False) - - - -class ExamplesOfNonlinearOneDimensionalTransforms(NumberLineScene): - def construct(self): - def sinx_plux_x(x_y_z): - (x, y, z) = x_y_z - return (np.sin(x) + 1.2*x, y, z) - - def shift_zero(x_y_z): - (x, y, z) = x_y_z - return (2*x+4, y, z) - - self.nonlinear = TextMobject("Not a Linear Transform") - self.nonlinear.set_color(LIGHT_RED).to_edge(UP, buff = 1.5) - pairs = [ - (sinx_plux_x, "numbers don't remain evenly spaced"), - (shift_zero, "zero does not remain fixed") - ] - for func, explanation in pairs: - self.run_function(func, explanation) - self.wait(3) - - def run_function(self, function, explanation): - self.clear() - self.add(self.nonlinear) - config = { - "stroke_width" : 2*DEFAULT_STROKE_WIDTH, - "density" : 5*DEFAULT_POINT_DENSITY_1D, - } - NumberLineScene.construct(self, **config) - words = TextMobject(explanation).set_color(LIGHT_RED) - words.next_to(self.nonlinear, DOWN, buff = 0.5) - self.add(words) - - self.play( - ApplyPointwiseFunction(function, self.number_line), - *[ - ApplyMethod( - mob.shift, - function(mob.get_center()) - mob.get_center() - ) - for mob in self.number_mobs - ], - run_time = 2.0 - ) - - -class ShowTwoThenThree(ShowMultiplication): - args_list = [] - @staticmethod - def args_to_string(): - return "" - - def construct(self): - config = { - "stroke_width" : 2*DEFAULT_STROKE_WIDTH, - "density" : 6*DEFAULT_POINT_DENSITY_1D, - } - NumberLineScene.construct(self, **config) - self.copy_original_line() - self.show_multiplication(2) - self.wait() - self.show_multiplication(3) - self.wait() - - -######################################################## - -class TransformScene2D(Scene): - def add_number_plane(self, density_factor = 1, use_faded_lines = True): - config = { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_WIDTH, - "density" : DEFAULT_POINT_DENSITY_1D*density_factor, - "stroke_width" : 2*DEFAULT_STROKE_WIDTH - } - if not use_faded_lines: - config["x_faded_line_frequency"] = None - config["y_faded_line_frequency"] = None - self.number_plane = NumberPlane(**config) - self.add(self.number_plane) - - def add_background(self): - grey_plane = NumberPlane(color = "grey") - num_mobs = grey_plane.get_coordinate_labels() - self.paint_into_background(grey_plane, *num_mobs) - - def add_x_y_arrows(self): - self.x_arrow = Arrow( - ORIGIN, - self.number_plane.num_pair_to_point((1, 0)), - color = "lightgreen", - **ARROW_CONFIG - ) - self.y_arrow = Arrow( - ORIGIN, - self.number_plane.num_pair_to_point((0, 1)), - color = LIGHT_RED, - **ARROW_CONFIG - ) - self.add(self.x_arrow, self.y_arrow) - self.number_plane.filter_out( - lambda x_y_z : (0 < x_y_z[0]) and (x_y_z[0] < 1) and (abs(x_y_z[1]) < 0.1) - ) - self.number_plane.filter_out( - lambda x_y_z1 : (0 < x_y_z1[1]) and (x_y_z1[1] < 1) and (abs(x_y_z1[0]) < 0.1) - ) - return self - - -class ShowMatrixTransform(TransformScene2D): - args_list = [ - ([[1, 3], [-2, 0]], False, False), - ([[1, 3], [-2, 0]], True, False), - ([[1, 0.5], [0.5, 1]], True, False), - ([[2, 0], [0, 2]], True, False), - ([[0.5, 0], [0, 0.5]], True, False), - ([[-1, 0], [0, -1]], True, False), - ([[0, 1], [1, 0]], True, False), - ([[-2, 0], [-1, -1]], True, False), - ([[0, -1], [1, 0]], True, False), - ] - @staticmethod - def args_to_string(matrix, with_background, show_matrix): - background_string = "WithBackground" if with_background else "WithoutBackground" - show_string = "ShowingMatrix" if show_matrix else "" - return matrix_to_string(matrix) + background_string + show_string - - def construct(self, matrix, with_background, show_matrix): - matrix = np.array(matrix) - number_plane_config = { - "density_factor" : self.get_density_factor(matrix) - } - if with_background: - self.add_background() - number_plane_config["use_faded_lines"] = False - self.add_number_plane(**number_plane_config) - self.add_x_y_arrows() - else: - self.add_number_plane(**number_plane_config) - if show_matrix: - self.add(matrix_mobject(matrix).to_corner(UP+LEFT)) - def func(mobject): - mobject.points[:, :2] = np.dot(mobject.points[:, :2], np.transpose(matrix)) - return mobject - - self.wait() - kwargs = { - "run_time" : 2.0, - "path_func" : self.get_path_func(matrix) - } - anims = [ApplyFunction(func, self.number_plane, **kwargs)] - if hasattr(self, "x_arrow") and hasattr(self, "y_arrow"): - for arrow, index in (self.x_arrow, 0), (self.y_arrow, 1): - new_arrow = Arrow( - ORIGIN, - self.number_plane.num_pair_to_point(matrix[:,index]), - color = arrow.get_color(), - **ARROW_CONFIG - ) - arrow.remove_tip() - new_arrow.remove_tip() - Mobject.align_data(arrow, new_arrow) - arrow.add_tip() - new_arrow.add_tip() - anims.append(Transform(arrow, new_arrow, **kwargs)) - self.play(*anims) - self.wait() - - def get_density_factor(self, matrix): - max_norm = max([ - abs(get_norm(column)) - for column in np.transpose(matrix) - ]) - return max(max_norm, 1) - - def get_path_func(self, matrix): - rotational_components = np.array([ - np.log(multiplier*complex(*matrix[:,i])).imag - for i, multiplier in [(0, 1), (1, complex(0, -1))] - ]) - rotational_components[rotational_components == -np.pi] = np.pi - return path_along_arc(np.mean(rotational_components)) - - -class ExamplesOfTwoDimensionalLinearTransformations(ShowMatrixTransform): - args_list = [] - @staticmethod - def args_to_string(): - return "" - - def construct(self): - matrices = [ - [[1, 0.5], - [0.5, 1]], - [[0, -1], - [2, 0]], - [[1, 3], - [-2, 0]], - ] - for matrix in matrices: - self.clear() - ShowMatrixTransform.construct(self, matrix, False, False) - - -class ExamplesOfNonlinearTwoDimensionalTransformations(Scene): - def construct(self): - Scene.construct(self) - def squiggle(x_y_z): - (x, y, z) = x_y_z - return (x+np.sin(y), y+np.cos(x), z) - - def shift_zero(x_y_z): - (x, y, z) = x_y_z - return (2*x + 3*y + 4, -1*x+y+2, z) - - self.nonlinear = TextMobject("Nonlinear Transform") - self.nonlinear.set_color(LIGHT_RED) - self.nonlinear.to_edge(UP, buff = 1.5) - pairs = [ - (squiggle, "lines do not remain straight"), - (shift_zero, "the origin does not remain fixed") - ] - self.get_blackness() - for function, explanation in pairs: - self.apply_function(function, explanation) - - - def apply_function(self, function, explanation): - self.clear() - config = { - "x_radius" : FRAME_WIDTH, - "y_radius" : FRAME_WIDTH, - "density" : 3*DEFAULT_POINT_DENSITY_1D, - "stroke_width" : 2*DEFAULT_STROKE_WIDTH - } - number_plane = NumberPlane(**config) - numbers = number_plane.get_coordinate_labels() - words = TextMobject(explanation) - words.set_color(LIGHT_RED) - words.next_to(self.nonlinear, DOWN, buff = 0.5) - - self.add(number_plane, *numbers) - self.add(self.blackness, self.nonlinear, words) - self.wait() - self.play( - ApplyPointwiseFunction(function, number_plane), - *[ - ApplyMethod( - mob.shift, - function(mob.get_center())-mob.get_center() - ) - for mob in numbers - ] + [ - Animation(self.blackness), - Animation(words), - Animation(self.nonlinear) - ], - run_time = 2.0 - ) - self.wait(3) - - def get_blackness(self): - vertices = [ - 3.5*LEFT+1.05*UP, - 3.5*RIGHT+1.05*UP, - 3.5*RIGHT+2.75*UP, - 3.5*LEFT+2.75*UP, - ] - - region = region_from_polygon_vertices(*vertices) - image = disp.paint_region(region, color = WHITE) - self.blackness = TextMobject("") - ImageMobject.generate_points_from_image_array(self.blackness, image) - self.blackness.set_color(BLACK) - rectangle = Rectangle(width = 7, height=1.7) - rectangle.set_color(WHITE) - rectangle.shift(self.blackness.get_center()) - self.blackness.add(rectangle) - self.blackness.scale_in_place(0.95) - - -class TrickyExamplesOfNonlinearTwoDimensionalTransformations(Scene): - def construct(self): - config = { - "x_radius" : 0.6*FRAME_WIDTH, - "y_radius" : 0.6*FRAME_WIDTH, - "density" : 10*DEFAULT_POINT_DENSITY_1D, - "stroke_width" : 2*DEFAULT_STROKE_WIDTH - } - number_plane = NumberPlane(**config) - phrase1, phrase2 = TextMobject([ - "These might look like they keep lines straight...", - "but diagonal lines get curved" - ]).to_edge(UP, buff = 1.5).split() - phrase2.set_color(LIGHT_RED) - diagonal = Line( - DOWN*FRAME_Y_RADIUS+LEFT*FRAME_X_RADIUS, - UP*FRAME_Y_RADIUS+RIGHT*FRAME_X_RADIUS, - density = 10*DEFAULT_POINT_DENSITY_1D - ) - - def sunrise(x_y_z): - (x, y, z) = x_y_z - return ((FRAME_Y_RADIUS+y)*x, y, z) - - def squished(x_y_z): - (x, y, z) = x_y_z - return (x + np.sin(x), y+np.sin(y), z) - - self.get_blackness() - - self.run_function(sunrise, number_plane, phrase1) - self.run_function(squished, number_plane, phrase1) - phrase1.add(phrase2) - self.add(phrase1) - self.play(ShowCreation(diagonal)) - self.remove(diagonal) - number_plane.add(diagonal) - self.run_function(sunrise, number_plane, phrase1) - self.run_function(squished, number_plane, phrase1, False) - - - def run_function(self, function, plane, phrase, remove_plane = True): - number_plane = deepcopy(plane) - self.add(number_plane, self.blackness, phrase) - self.wait() - self.play( - ApplyPointwiseFunction(function, number_plane, run_time = 2.0), - Animation(self.blackness), - Animation(phrase), - ) - self.wait(3) - if remove_plane: - self.remove(number_plane) - - def get_blackness(self): - vertices = [ - 4.5*LEFT+1.25*UP, - 4.5*RIGHT+1.25*UP, - 4.5*RIGHT+2.75*UP, - 4.5*LEFT+2.75*UP, - ] - - region = region_from_polygon_vertices(*vertices) - image = disp.paint_region(region, color = WHITE) - self.blackness = TextMobject("") - ImageMobject.generate_points_from_image_array(self.blackness, image) - self.blackness.set_color(BLACK) - rectangle = Rectangle(width = 9, height=1.5) - rectangle.set_color(WHITE) - rectangle.shift(self.blackness.get_center()) - self.blackness.add(rectangle) - self.blackness.scale_in_place(0.95) - - -############# HORRIBLE! ########################## -class ShowMatrixTransformWithDot(TransformScene2D): - args_list = [ - ([[1, 3], [-2, 0]], True, False), - ] - @staticmethod - def args_to_string(matrix, with_background, show_matrix): - background_string = "WithBackground" if with_background else "WithoutBackground" - show_string = "ShowingMatrix" if show_matrix else "" - return matrix_to_string(matrix) + background_string + show_string - - def construct(self, matrix, with_background, show_matrix): - matrix = np.array(matrix) - number_plane_config = { - "density_factor" : self.get_density_factor(matrix), - } - if with_background: - self.add_background() - number_plane_config["use_faded_lines"] = False - self.add_number_plane(**number_plane_config) - self.add_x_y_arrows() - else: - self.add_number_plane(**number_plane_config) - if show_matrix: - self.add(matrix_mobject(matrix).to_corner(UP+LEFT)) - def func(mobject): - mobject.points[:, :2] = np.dot(mobject.points[:, :2], np.transpose(matrix)) - return mobject - dot = Dot((-1, 2, 0), color = "yellow") - self.add(dot) - x_arrow_copy = deepcopy(self.x_arrow) - y_arrow_copy = Arrow(LEFT, LEFT+2*UP, color = LIGHT_RED, **ARROW_CONFIG) - - self.play(ApplyMethod(x_arrow_copy.rotate, np.pi)) - self.play(ShowCreation(y_arrow_copy)) - self.wait() - self.remove(x_arrow_copy, y_arrow_copy) - kwargs = { - "run_time" : 2.0, - "path_func" : self.get_path_func(matrix) - } - anims = [ - ApplyFunction(func, self.number_plane, **kwargs), - ApplyMethod(dot.shift, func(deepcopy(dot)).get_center()-dot.get_center(), **kwargs), - ] - if hasattr(self, "x_arrow") and hasattr(self, "y_arrow"): - for arrow, index in (self.x_arrow, 0), (self.y_arrow, 1): - new_arrow = Arrow( - ORIGIN, - self.number_plane.num_pair_to_point(matrix[:,index]), - color = arrow.get_color(), - **ARROW_CONFIG - ) - arrow.remove_tip() - new_arrow.remove_tip() - Mobject.align_data(arrow, new_arrow) - arrow.add_tip() - new_arrow.add_tip() - anims.append(Transform(arrow, new_arrow, **kwargs)) - self.play(*anims) - self.wait() - - x_arrow_copy = deepcopy(self.x_arrow) - y_arrow_copy = Arrow(LEFT+2*UP, 5*RIGHT+2*UP, color = LIGHT_RED, **ARROW_CONFIG) - self.play(ApplyMethod(x_arrow_copy.rotate, np.pi)) - self.play(ShowCreation(y_arrow_copy)) - self.wait(3) - self.remove(x_arrow_copy, y_arrow_copy) - - def get_density_factor(self, matrix): - max_norm = max([ - abs(get_norm(column)) - for column in np.transpose(matrix) - ]) - return max(max_norm, 1) - - def get_path_func(self, matrix): - rotational_components = [ - sign*np.arccos(matrix[i,i]/get_norm(matrix[:,i])) - for i in [0, 1] - for sign in [((-1)**i)*np.sign(matrix[1-i, i])] - ] - average_rotation = sum(rotational_components)/2 - if abs(average_rotation) < np.pi / 2: - return straight_path - elif average_rotation > 0: - return counterclockwise_path() - else: - return clockwise_path() - - -class Show90DegreeRotation(TransformScene2D): - def construct(self): - self.add_number_plane() - self.add_background() - self.add_x_y_arrows() - - self.wait() - self.play(*[ - RotationAsTransform(mob, run_time = 2.0) - for mob in (self.number_plane, self.x_arrow, self.y_arrow) - ]) - self.wait() - diff --git a/from_3b1b/old/moser_intro.py b/from_3b1b/old/moser_intro.py deleted file mode 100644 index a57a81f6..00000000 --- a/from_3b1b/old/moser_intro.py +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/env python - - -import numpy as np -import itertools as it -import operator as op -from copy import deepcopy - -from manimlib.imports import * - -RADIUS = FRAME_Y_RADIUS - 0.1 -CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS - - -def logo_to_circle(): - from .generate_logo import DARK_BROWN, LOGO_RADIUS - sc = Scene() - small_circle = Circle( - density = CIRCLE_DENSITY, - color = 'skyblue' - ).scale(LOGO_RADIUS).set_color( - DARK_BROWN, lambda x_y_z : x_y_z[0] < 0 and x_y_z[1] > 0 - ) - big_circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS) - sc.add(small_circle) - sc.wait() - sc.animate(Transform(small_circle, big_circle)) - return sc - -def count_sections(*radians): - sc = Scene() - circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS) - sc.add(circle) - points = [ - (RADIUS * np.cos(angle), RADIUS * np.sin(angle), 0) - for angle in radians - ] - dots = [Dot(point) for point in points] - interior = Region(lambda x, y : x**2 + y**2 < RADIUS**2) - for x in range(1, len(points)): - if x == 1: - sc.animate(ShowCreation(dots[0]), ShowCreation(dots[1])) - sc.add(dots[0], dots[1]) - else: - sc.animate(ShowCreation(dots[x])) - sc.add(dots[x]) - new_lines = Mobject(*[ - Line(points[x], points[y]) for y in range(x) - ]) - sc.animate(Transform(deepcopy(dots[x]), new_lines, run_time = 2.0)) - sc.add(new_lines) - sc.wait() - regions = plane_partition_from_points(*points[:x+1]) - for reg in regions: - reg.intersect(interior) - regions = [reg for reg in regions if reg.bool_grid.any()] - - last_num = None - for reg, count in zip(regions, it.count(1)): - number = TexMobject(str(count)).shift((RADIUS, 3, 0)) - sc.set_color_region(reg) - rt = 1.0 / (x**0.8) - sc.add(number) - sc.remove(last_num) - last_num = number - sc.wait(rt) - sc.reset_background() - sc.remove(last_num) - sc.animate(Transform(last_num, deepcopy(last_num).center())) - sc.wait() - sc.remove(last_num) - return sc - -def summarize_pattern(*radians): - sc = Scene() - circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS) - sc.add(circle) - points = [ - (RADIUS * np.cos(angle), RADIUS * np.sin(angle), 0) - for angle in radians - ] - dots = [Dot(point) for point in points] - last_num = None - for x in range(len(points)): - new_lines = Mobject(*[ - Line(points[x], points[y]) for y in range(x) - ]) - num = TexMobject(str(moser_function(x + 1))).center() - sc.animate( - Transform(last_num, num) if last_num else ShowCreation(num), - FadeIn(new_lines), - FadeIn(dots[x]), - run_time = 0.5, - ) - sc.remove(last_num) - last_num = num - sc.add(num, dots[x], new_lines) - sc.wait() - return sc - -def connect_points(*radians): - sc = Scene() - circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS) - sc.add(circle) - points = [ - (RADIUS * np.cos(angle), RADIUS * np.sin(angle), 0) - for angle in radians - ] - dots = [Dot(point) for point in points] - sc.add(*dots) - anims = [] - all_lines = [] - for x in range(len(points)): - lines = [Line(points[x], points[y]) for y in range(len(points))] - lines = Mobject(*lines) - anims.append(Transform(deepcopy(dots[x]), lines, run_time = 3.0)) - all_lines.append(lines) - sc.animate(*anims) - sc.add(*all_lines) - sc.wait() - return sc - -def interesting_problems(): - sc = Scene() - locales = [(6, 2, 0), (6, -2, 0), (-5, -2, 0)] - fermat = Mobject(*TexMobjects(["x^n","+","y^n","=","z^n"])) - fermat.scale(0.5).shift((-2.5, 0.7, 0)) - face = SimpleFace() - tb = ThoughtBubble().shift((-1.5, 1, 0)) - sb = SpeechBubble().shift((-2.4, 1.3, 0)) - fermat_copies, face_copies, tb_copies, sb_copies = ( - Mobject(*[ - deepcopy(mob).scale(0.5).shift(locale) - for locale in locales - ]) - for mob in [fermat, face, tb, sb] - ) - - sc.add(face, tb) - sc.animate(ShowCreation(fermat, run_time = 1)) - sc.add(fermat) - sc.wait() - sc.animate( - Transform( - deepcopy(fermat).repeat(len(locales)), - fermat_copies - ), - FadeIn(face_copies, run_time = 1.0) - ) - sc.animate(FadeIn(tb_copies)) - sc.wait() - sc.animate( - Transform(tb, sb), - Transform(tb_copies, sb_copies) - ) - return sc - -def response_invitation(): - sc = Scene() - video_icon = VideoIcon() - mini_videos = Mobject(*[ - deepcopy(video_icon).scale(0.5).shift((3, y, 0)) - for y in [-2, 0, 2] - ]) - comments = Mobject(*[ - Line((-1.2, y, 0), (1.2, y, 0), color = 'white') - for y in [-1.5, -1.75, -2] - ]) - - sc.add(video_icon) - sc.wait() - sc.animate(Transform(deepcopy(video_icon).repeat(3), mini_videos)) - sc.add(mini_videos) - sc.wait() - sc.animate(ShowCreation(comments, run_time = 1.0)) - return sc - -def different_points(radians1, radians2): - sc = Scene() - circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS) - sc.add(circle) - points1, points2 = ( - [ - (RADIUS * np.cos(angle), RADIUS * np.sin(angle), 0) - for angle in radians - ] - for radians in (radians1, radians2) - ) - dots1, dots2 = ( - Mobject(*[Dot(point) for point in points]) - for points in (points1, points2) - ) - lines1, lines2 = ( - [ - Line(point1, point2) - for point1, point2 in it.combinations(points, 2) - ] - for points in (points1, points2) - ) - sc.add(dots1, *lines1) - sc.animate( - Transform(dots1, dots2, run_time = 3), - *[ - Transform(line1, line2, run_time = 3) - for line1, line2 in zip(lines1, lines2) - ] - ) - sc.wait() - return sc - -def next_few_videos(*radians): - sc = Scene() - circle = Circle(density = CIRCLE_DENSITY).scale(RADIUS) - points = [ - (RADIUS * np.cos(angle), RADIUS * np.sin(angle), 0) - for angle in radians - ] - dots = Mobject(*[ - Dot(point) for point in points - ]) - lines = Mobject(*[ - Line(point1, point2) - for point1, point2 in it.combinations(points, 2) - ]) - thumbnail = Mobject(circle, dots, lines) - frame = VideoIcon().set_color( - "black", - lambda point : get_norm(point) < 0.5 - ) - big_frame = deepcopy(frame).scale(FRAME_X_RADIUS) - frame.shift((-5, 0, 0)) - - sc.add(thumbnail) - sc.wait() - sc.animate( - Transform(big_frame, frame), - Transform( - thumbnail, - deepcopy(thumbnail).scale(0.15).shift((-5, 0, 0)) - ) - ) - sc.add(frame, thumbnail) - sc.wait() - last = frame - for x in [-2, 1, 4]: - vi = VideoIcon().shift((x, 0, 0)) - sc.animate( - Transform(deepcopy(last), vi), - Animation(thumbnail)#Keeps it from getting burried - ) - sc.add(vi) - last = vi - return sc - - - -if __name__ == '__main__': - radians = [1, 3, 5, 2, 4, 6] - more_radians = radians + [10, 13, 20, 17, 15, 21, 18.5] - different_radians = [1.7, 4.8, 3.2, 3.5, 2.1, 5.5] - # logo_to_circle().write_to_movie("moser/LogoToCircle") - # count_sections(*radians).write_to_movie("moser/CountingSections") - # summarize_pattern(*radians).write_to_movie("moser/SummarizePattern") - # interesting_problems().write_to_movie("moser/InterestingProblems") - # summarize_pattern(*more_radians).write_to_movie("moser/ExtendedPattern") - # connect_points(*radians).write_to_movie("moser/ConnectPoints") - # response_invitation().write_to_movie("moser/ResponseInvitation") - # different_points(radians, different_radians).write_to_movie("moser/DifferentPoints") - # next_few_videos(*radians).write_to_movie("moser/NextFewVideos") - # summarize_pattern(*different_radians).write_to_movie("moser/PatternWithDifferentPoints") - - #Images - # TexMobject(r""" - # \Underbrace{1, 2, 4, 8, 16, 31, \dots}_\text{What?} - # """).save_image("moser/NumberList31") - # TexMobject(""" - # 1, 2, 4, 8, 16, 32, 63, \dots - # """).save_image("moser/NumberList63") - # TexMobject(""" - # 1, 2, 4, 8, 15, \dots - # """).save_image("moser/NumberList15") - - - - - - - - - - - - - - diff --git a/from_3b1b/old/moser_main.py b/from_3b1b/old/moser_main.py deleted file mode 100644 index 9165a495..00000000 --- a/from_3b1b/old/moser_main.py +++ /dev/null @@ -1,1694 +0,0 @@ -#!/usr/bin/env python - -import numpy as np -import itertools as it -import operator as op -from copy import deepcopy -from random import random, randint -import sys -import inspect - -from manimlib.imports import * -from script_wrapper import command_line_create_scene -from functools import reduce - -RADIUS = FRAME_Y_RADIUS - 0.1 -CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS -MOVIE_PREFIX = "moser/" -RADIANS = np.arange(0, 6, 6.0/7) -MORE_RADIANS = np.append(RADIANS, RADIANS + 0.5) -N_PASCAL_ROWS = 7 -BIG_N_PASCAL_ROWS = 11 - -def int_list_to_string(int_list): - return "-".join(map(str, int_list)) - -def moser_function(n): - return choose(n, 4) + choose(n, 2) + 1 - -########################################### - -class CircleScene(Scene): - args_list = [ - (RADIANS[:x],) - for x in range(1, len(RADIANS)+1) - ] - @staticmethod - def args_to_string(*args): - return str(len(args[0])) #Length of radians - - def __init__(self, radians, radius = RADIUS, *args, **kwargs): - Scene.__init__(self, *args, **kwargs) - self.radius = radius - self.circle = Circle(density = CIRCLE_DENSITY).scale(self.radius) - self.points = [ - (self.radius * np.cos(angle), self.radius * np.sin(angle), 0) - for angle in radians - ] - self.dots = [Dot(point) for point in self.points] - self.lines = [Line(p1, p2) for p1, p2 in it.combinations(self.points, 2)] - self.n_equals = TexMobject( - "n=%d"%len(radians), - ).shift((-FRAME_X_RADIUS+1, FRAME_Y_RADIUS-1.5, 0)) - self.add(self.circle, self.n_equals, *self.dots + self.lines) - - - def generate_intersection_dots(self): - """ - Generates and adds attributes intersection_points and - intersection_dots, but does not yet add them to the scene - """ - self.intersection_points = [ - intersection((p[0], p[2]), (p[1], p[3])) - for p in it.combinations(self.points, 4) - ] - self.intersection_dots = [ - Dot(point) for point in self.intersection_points - ] - - def chop_lines_at_intersection_points(self): - if not hasattr(self, "intersection_dots"): - self.generate_intersection_dots() - self.remove(*self.lines) - self.lines = [] - for point_pair in it.combinations(self.points, 2): - int_points = [p for p in self.intersection_points if is_on_line(p, *point_pair)] - points = list(point_pair) + int_points - points = [(p[0], p[1], 0) for p in points] - points.sort(cmp = lambda x,y: cmp(x[0], y[0])) - self.lines += [ - Line(points[i], points[i+1]) - for i in range(len(points)-1) - ] - self.add(*self.lines) - - def chop_circle_at_points(self): - self.remove(self.circle) - self.circle_pieces = [] - self.smaller_circle_pieces = [] - for i in range(len(self.points)): - pp = self.points[i], self.points[(i+1)%len(self.points)] - transform = np.array([ - [pp[0][0], pp[1][0], 0], - [pp[0][1], pp[1][1], 0], - [0, 0, 1] - ]) - circle = deepcopy(self.circle) - smaller_circle = deepcopy(self.circle) - for c in circle, smaller_circle: - c.points = np.dot( - c.points, - np.transpose(np.linalg.inv(transform)) - ) - c.filter_out( - lambda p : p[0] < 0 or p[1] < 0 - ) - if c == smaller_circle: - c.filter_out( - lambda p : p[0] > 4*p[1] or p[1] > 4*p[0] - ) - c.points = np.dot( - c.points, - np.transpose(transform) - ) - self.circle_pieces.append(circle) - self.smaller_circle_pieces.append(smaller_circle) - self.add(*self.circle_pieces) - - def generate_regions(self): - self.regions = plane_partition_from_points(*self.points) - interior = Region(lambda x, y : x**2 + y**2 < self.radius**2) - list(map(lambda r : r.intersect(interior), self.regions)) - self.exterior = interior.complement() - - -class CountSections(CircleScene): - def __init__(self, *args, **kwargs): - CircleScene.__init__(self, *args, **kwargs) - self.remove(*self.lines) - self.play(*[ - Transform(Dot(points[i]),Line(points[i], points[1-i])) - for points in it.combinations(self.points, 2) - for i in (0, 1) - ], run_time = 2.0) - regions = plane_partition_from_points(*self.points) - interior = Region(lambda x, y : x**2 + y**2 < self.radius**2) - list(map(lambda r : r.intersect(interior), regions)) - regions = [r for r in regions if r.bool_grid.any()] - self.count_regions(regions, num_offset = ORIGIN) - -class MoserPattern(CircleScene): - args_list = [(MORE_RADIANS,)] - def __init__(self, radians, *args, **kwargs): - CircleScene.__init__(self, radians, *args, **kwargs) - self.remove(*self.dots + self.lines + [self.n_equals]) - n_equals, num = TexMobject(["n=", "10"]).split() - for mob in n_equals, num: - mob.shift((-FRAME_X_RADIUS + 1.5, FRAME_Y_RADIUS - 1.5, 0)) - self.add(n_equals) - for n in range(1, len(radians)+1): - self.add(*self.dots[:n]) - self.add(*[Line(p[0], p[1]) for p in it.combinations(self.points[:n], 2)]) - tex_stuffs = [ - TexMobject(str(moser_function(n))), - TexMobject(str(n)).shift(num.get_center()) - ] - self.add(*tex_stuffs) - self.wait(0.5) - self.remove(*tex_stuffs) - -def hpsq_taylored_alpha(t): - return 0.3*np.sin(5*t-5)*np.exp(-20*(t-0.6)**2) + smooth(t) - -class HardProblemsSimplerQuestions(Scene): - def __init__(self, *args, **kwargs): - Scene.__init__(self, *args, **kwargs) - right_center = np.array((4, 1, 0)) - left_center = np.array((-5, 1, 0)) - scale_factor = 0.7 - fermat = dict([ - ( - sym, - Mobject(*TexMobjects( - ["x","^"+sym,"+","y","^"+sym,"=","z","^"+sym] - )) - ) - for sym in ["n", "2", "3"] - ]) - # not_that_hard = TextMobject("(maybe not that hard...)").scale(0.5) - fermat2, fermat2_jargon = TexMobject([ - r"&x^2 + y^2 = z^2 \\", - r""" - &(3, 4, 5) \\ - &(5, 12, 13) \\ - &(15, 8, 17) \\ - &\quad \vdots \\ - (m^2 - &n^2, 2mn, m^2 + n^2) \\ - &\quad \vdots - """ - ]).split() - fermat3, fermat3_jargon = TexMobject([ - r"&x^3 + y^3 = z^3\\", - r""" - &y^3 = (z - x)(z - \omega x)(z - \omega^2 x) \\ - &\mathds{Z}[\omega] \text{ is a UFD...} - """ - ]).split() - for mob in [fermat2, fermat3, fermat["2"], fermat["3"], - fermat2_jargon, fermat3_jargon]: - mob.scale(scale_factor) - fermat["2"].shift(right_center) - fermat["3"].shift(left_center) - fermat["n"].shift((0, FRAME_Y_RADIUS - 1, 0)) - shift_val = right_center - fermat2.get_center() - fermat2.shift(shift_val) - fermat2_jargon.shift(shift_val) - shift_val = left_center - fermat3.get_center() - fermat3.shift(shift_val) - fermat3_jargon.shift(shift_val) - - copies = [ - deepcopy(fermat["n"]).center().scale(scale_factor).shift(c) - for c in [left_center, right_center] - ] - self.add(fermat["n"]) - self.play(*[ - Transform(deepcopy(fermat["n"]), f_copy) - for f_copy in copies - ]) - self.remove(*self.mobjects) - self.add(fermat["n"]) - self.play(*[ - CounterclockwiseTransform(mobs[0], mobs[1]) - for f_copy, sym in zip(copies, ["3", "2"]) - for mobs in zip(f_copy.split(), fermat[sym].split()) - ]) - self.remove(*self.mobjects) - self.add(fermat["n"], fermat2, fermat3) - self.wait() - - circle_grid = Mobject( - Circle(), - Grid(radius = 2), - TexMobject(r"\mathds{R}^2").shift((2, -2, 0)) - ) - start_line = Line((-1, 0, 0), (-1, 2, 0)) - end_line = Line((-1, 0, 0), (-1, -2, 0)) - for mob in circle_grid, start_line, end_line: - mob.scale(0.5).shift(right_center + (0, 2, 0)) - - other_grid = Mobject( - Grid(radius = 2), - TexMobject(r"\mathds{C}").shift((2, -2, 0)) - ) - omega = np.array((0.5, 0.5*np.sqrt(3), 0)) - dots = Mobject(*[ - Dot(t*np.array((1, 0, 0)) + s * omega) - for t, s in it.product(list(range(-2, 3)), list(range(-2, 3))) - ]) - for mob in dots, other_grid: - mob.scale(0.5).shift(left_center + (0, 2, 0)) - - self.add(circle_grid, other_grid) - self.play( - FadeIn(fermat2_jargon), - FadeIn(fermat3_jargon), - CounterclockwiseTransform(start_line, end_line), - ShowCreation(dots) - ) - self.wait() - all_mobjects = Mobject(*self.mobjects) - self.remove(*self.mobjects) - self.play( - Transform( - all_mobjects, - Point((FRAME_X_RADIUS, 0, 0)) - ), - Transform( - Point((-FRAME_X_RADIUS, 0, 0)), - Mobject(*CircleScene(RADIANS).mobjects) - ) - ) - -class CountLines(CircleScene): - def __init__(self, radians, *args, **kwargs): - CircleScene.__init__(self, radians, *args, **kwargs) - #TODO, Count things explicitly? - text_center = (self.radius + 1, self.radius -1, 0) - scale_factor = 0.4 - text = TexMobject(r"\text{How Many Lines?}", size = r"\large") - n = len(radians) - formula, answer = TexMobject([ - r"{%d \choose 2} = \frac{%d(%d - 1)}{2} = "%(n, n, n), - str(choose(n, 2)) - ]) - text.scale(scale_factor).shift(text_center) - x = text_center[0] - new_lines = [ - Line((x-1, y, 0), (x+1, y, 0)) - for y in np.arange( - -(self.radius - 1), - self.radius - 1, - (2*self.radius - 2)/len(self.lines) - ) - ] - self.add(text) - self.wait() - self.play(*[ - Transform(line1, line2, run_time = 2) - for line1, line2 in zip(self.lines, new_lines) - ]) - self.wait() - self.remove(text) - self.count(new_lines) - anims = [FadeIn(formula)] - for mob in self.mobjects: - if mob == self.number: - anims.append(Transform(mob, answer)) - else: - anims.append(FadeOut(mob)) - self.play(*anims, run_time = 1) - -class CountIntersectionPoints(CircleScene): - def __init__(self, radians, *args, **kwargs): - radians = [r % (2*np.pi) for r in radians] - radians.sort() - CircleScene.__init__(self, radians, *args, **kwargs) - - intersection_points = [ - intersection((p[0], p[2]), (p[1], p[3])) - for p in it.combinations(self.points, 4) - ] - intersection_dots = [Dot(point) for point in intersection_points] - text_center = (self.radius + 0.5, self.radius -0.5, 0) - size = r"\large" - scale_factor = 0.4 - text = TexMobject(r"\text{How Many Intersection Points?}", size = size) - n = len(radians) - formula, answer = TexMobject([ - r"{%d \choose 4} = \frac{%d(%d - 1)(%d - 2)(%d-3)}{1\cdot 2\cdot 3 \cdot 4}="%(n, n, n, n, n), - str(choose(n, 4)) - ]).split() - text.scale(scale_factor).shift(text_center) - self.add(text) - self.count(intersection_dots, mode="show", num_offset = ORIGIN) - self.wait() - anims = [] - for mob in self.mobjects: - if mob == self.number: #put in during count - anims.append(Transform(mob, answer)) - else: - anims.append(FadeOut(mob)) - anims.append(Animation(formula)) - self.play(*anims, run_time = 1) - -class NonGeneralPosition(CircleScene): - args_list = [] - @staticmethod - def args_to_string(*args): - return "" - - def __init__(self, *args, **kwargs): - radians = np.arange(1, 7) - new_radians = (np.pi/3)*radians - CircleScene.__init__(self, radians, *args, **kwargs) - - new_cs = CircleScene(new_radians) - center_region = reduce( - Region.intersect, - [ - HalfPlane((self.points[x], self.points[(x+3)%6])) - for x in [0, 4, 2]#Ya know, trust it - ] - ) - center_region - text = TexMobject(r"\text{This region disappears}", size = r"\large") - text.center().scale(0.5).shift((-self.radius, self.radius-0.3, 0)) - arrow = Arrow( - point = (-0.35, -0.1, 0), - direction = (1, -1, 0), - length = self.radius + 1, - color = "white", - ) - - self.set_color_region(center_region, "green") - self.add(text, arrow) - self.wait(2) - self.remove(text, arrow) - self.reset_background() - self.play(*[ - Transform(mob1, mob2, run_time = DEFAULT_ANIMATION_RUN_TIME) - for mob1, mob2 in zip(self.mobjects, new_self.mobjects) - ]) - -class GeneralPositionRule(Scene): - def __init__(self, *args, **kwargs): - Scene.__init__(self, *args, **kwargs) - tuples = [ - ( - np.arange(0, 2*np.pi, np.pi/3), - "Not okay", - list(zip(list(range(3)), list(range(3, 6)))) - ), - ( - RADIANS, - "Okay", - [], - ), - ( - np.arange(0, 2*np.pi, np.pi/4), - "Not okay", - list(zip(list(range(4)), list(range(4, 8)))) - ), - ( - [2*np.pi*random() for x in range(5)], - "Okay", - [], - ), - ] - first_time = True - for radians, words, pairs in tuples: - cs = CircleScene(radians) - self.add(*cs.mobjects) - words_mob = TextMobject(words).scale(2).shift((5, 3, 0)) - if not first_time: - self.add(words_mob) - if words == "Okay": - words_mob.set_color("green") - self.wait(2) - else: - words_mob.set_color() - intersecting_lines = [ - line.scale_in_place(0.3).set_color() - for i, j in pairs - for line in [Line(cs.points[i], cs.points[j])] - ] - self.play(*[ - ShowCreation(line, run_time = 1.0) - for line in intersecting_lines - ]) - if first_time: - self.play(Transform( - Mobject(*intersecting_lines), - words_mob - )) - first_time = False - self.wait() - self.remove(*self.mobjects) - - -class LineCorrespondsWithPair(CircleScene): - args_list = [ - (RADIANS, 2, 5), - (RADIANS, 0, 4) - ] - @staticmethod - def args_to_string(*args): - return int_list_to_string(args[1:]) - - def __init__(self, radians, dot0_index, dot1_index, - *args, **kwargs): - CircleScene.__init__(self, radians, *args, **kwargs) - #Remove from self.lines list, so they won't be faded out - radians = list(radians) - r1, r2 = radians[dot0_index], radians[dot1_index] - line_index = list(it.combinations(radians, 2)).index((r1, r2)) - line, dot0, dot1 = self.lines[line_index], self.dots[dot0_index], self.dots[dot1_index] - self.lines.remove(line) - self.dots.remove(dot0) - self.dots.remove(dot1) - self.wait() - self.play(*[ - ApplyMethod(mob.fade, 0.2) - for mob in self.lines + self.dots - ]) - self.play(*[ - Transform( - dot, Dot(dot.get_center(), 3*dot.radius), - rate_func = there_and_back - ) - for dot in (dot0, dot1) - ]) - self.play(Transform(line, dot0)) - -class IllustrateNChooseK(Scene): - args_list = [ - (n, k) - for n in range(1, 10) - for k in range(n+1) - ] - @staticmethod - def args_to_string(*args): - return int_list_to_string(args) - - def __init__(self, n, k, *args, **kwargs): - Scene.__init__(self, *args, **kwargs) - nrange = list(range(1, n+1)) - tuples = list(it.combinations(nrange, k)) - nrange_mobs = TexMobject([str(n) + r'\;' for n in nrange]).split() - tuple_mobs = TexMobjects( - [ - (r'\\&' if c%(20//k) == 0 else r'\;\;') + str(p) - for p, c in zip(tuples, it.count()) - ], - size = r"\small", - )#TODO, scale these up! - tuple_terms = { - 2 : "pairs", - 3 : "triplets", - 4 : "quadruplets", - } - tuple_term = tuple_terms[k] if k in tuple_terms else "tuples" - if k == 2: - str1 = r"{%d \choose %d} = \frac{%d(%d - 1)}{2}="%(n, k, n, n) - elif k == 4: - str1 = r""" - {%d \choose %d} = - \frac{%d(%d - 1)(%d-2)(%d-3)}{1\cdot 2\cdot 3 \cdot 4}= - """%(n, k, n, n, n, n) - else: - str1 = r"{%d \choose %d} ="%(n, k) - form1, count, form2 = TexMobject([ - str1, - "%d"%choose(n, k), - r" \text{ total %s}"%tuple_term - ]) - pronunciation = TextMobject( - "(pronounced ``%d choose %d\'\')"%(n, k) - ) - for mob in nrange_mobs: - mob.shift((0, 2, 0)) - for mob in form1, count, form2: - mob.scale(0.75).shift((0, -FRAME_Y_RADIUS + 1, 0)) - count_center = count.get_center() - for mob in tuple_mobs: - mob.scale(0.6) - pronunciation.shift( - form1.get_center() + (0, 1, 0) - ) - - self.add(*nrange_mobs) - self.wait() - run_time = 6.0 - frame_time = run_time / len(tuples) - for tup, count in zip(tuples, it.count()): - count_mob = TexMobject(str(count+1)) - count_mob.center().shift(count_center) - self.add(count_mob) - tuple_copy = Mobject(*[nrange_mobs[index-1] for index in tup]) - tuple_copy.set_color() - self.add(tuple_copy) - self.add(tuple_mobs[count]) - self.wait(frame_time) - self.remove(count_mob) - self.remove(tuple_copy) - self.add(count_mob) - self.play(FadeIn(Mobject(form1, form2, pronunciation))) - -class IntersectionPointCorrespondances(CircleScene): - args_list = [ - (RADIANS, list(range(0, 7, 2))), - ] - @staticmethod - def args_to_string(*args): - return int_list_to_string(args[1]) - - def __init__(self, radians, indices, *args, **kwargs): - assert(len(indices) == 4) - indices.sort() - CircleScene.__init__(self, radians, *args, **kwargs) - intersection_point = intersection( - (self.points[indices[0]], self.points[indices[2]]), - (self.points[indices[1]], self.points[indices[3]]) - ) - if len(intersection_point) == 2: - intersection_point = list(intersection_point) + [0] - intersection_dot = Dot(intersection_point) - intersection_dot_arrow = Arrow(intersection_point).nudge() - self.add(intersection_dot) - pairs = list(it.combinations(list(range(len(radians))), 2)) - lines_to_save = [ - self.lines[pairs.index((indices[p0], indices[p1]))] - for p0, p1 in [(0, 2), (1, 3)] - ] - dots_to_save = [ - self.dots[p] - for p in indices - ] - line_statement = TexMobject(r"\text{Pair of Lines}") - dots_statement = TexMobject(r"&\text{Quadruplet of} \\ &\text{outer dots}") - for mob in line_statement, dots_statement: - mob.center() - mob.scale(0.7) - mob.shift((FRAME_X_RADIUS-2, FRAME_Y_RADIUS - 1, 0)) - fade_outs = [] - line_highlights = [] - dot_highlights = [] - dot_pointers = [] - for mob in self.mobjects: - if mob in lines_to_save: - line_highlights.append(Highlight(mob)) - elif mob in dots_to_save: - dot_highlights.append(Highlight(mob)) - dot_pointers.append(Arrow(mob.get_center()).nudge()) - elif mob != intersection_dot: - fade_outs.append(FadeOut(mob, rate_func = not_quite_there)) - - self.add(intersection_dot_arrow) - self.play(Highlight(intersection_dot)) - self.remove(intersection_dot_arrow) - self.play(*fade_outs) - self.wait() - self.add(line_statement) - self.play(*line_highlights) - self.remove(line_statement) - self.wait() - self.add(dots_statement, *dot_pointers) - self.play(*dot_highlights) - self.remove(dots_statement, *dot_pointers) - -class LinesIntersectOutside(CircleScene): - args_list = [ - (RADIANS, [2, 4, 5, 6]), - ] - @staticmethod - def args_to_string(*args): - return int_list_to_string(args[1]) - - def __init__(self, radians, indices, *args, **kwargs): - assert(len(indices) == 4) - indices.sort() - CircleScene.__init__(self, radians, *args, **kwargs) - intersection_point = intersection( - (self.points[indices[0]], self.points[indices[1]]), - (self.points[indices[2]], self.points[indices[3]]) - ) - intersection_point = tuple(list(intersection_point) + [0]) - intersection_dot = Dot(intersection_point) - pairs = list(it.combinations(list(range(len(radians))), 2)) - lines_to_save = [ - self.lines[pairs.index((indices[p0], indices[p1]))] - for p0, p1 in [(0, 1), (2, 3)] - ] - self.play(*[ - FadeOut(mob, rate_func = not_quite_there) - for mob in self.mobjects if mob not in lines_to_save - ]) - self.play(*[ - Transform( - Line(self.points[indices[p0]], self.points[indices[p1]]), - Line(self.points[indices[p0]], intersection_point)) - for p0, p1 in [(0, 1), (3, 2)] - ] + [ShowCreation(intersection_dot)]) - -class QuadrupletsToIntersections(CircleScene): - def __init__(self, radians, *args, **kwargs): - CircleScene.__init__(self, radians, *args, **kwargs) - quadruplets = it.combinations(list(range(len(radians))), 4) - frame_time = 1.0 - for quad in quadruplets: - intersection_dot = Dot(intersection( - (self.points[quad[0]], self.points[quad[2]]), - (self.points[quad[1]], self.points[quad[3]]) - )).repeat(3) - dot_quad = [deepcopy(self.dots[i]) for i in quad] - for dot in dot_quad: - dot.scale_in_place(2) - dot_quad = Mobject(*dot_quad) - dot_quad.set_color() - self.add(dot_quad) - self.wait(frame_time / 3) - self.play(Transform( - dot_quad, - intersection_dot, - run_time = 3*frame_time/2 - )) - -class GraphsAndEulersFormulaJoke(Scene): - def __init__(self, *args, **kwargs): - Scene.__init__(self, *args, **kwargs) - axes = Mobject( - NumberLine(), - NumberLine().rotate(np.pi / 2) - ) - graph = ParametricFunction( - lambda t : (10*t, ((10*t)**3 - 10*t), 0), - expected_measure = 40.0 - ) - graph.filter_out(lambda x_y_z : abs(x_y_z[1]) > FRAME_Y_RADIUS) - self.add(axes) - self.play(ShowCreation(graph), run_time = 1.0) - eulers = TexMobject("e^{\pi i} = -1").shift((0, 3, 0)) - self.play(CounterclockwiseTransform( - deepcopy(graph), eulers - )) - self.wait() - self.remove(*self.mobjects) - self.add(eulers) - self.play(CounterclockwiseTransform( - Mobject(axes, graph), - Point((-FRAME_X_RADIUS, FRAME_Y_RADIUS, 0)) - )) - self.play(CounterclockwiseTransform( - eulers, - Point((FRAME_X_RADIUS, FRAME_Y_RADIUS, 0)) - )) - -class DefiningGraph(GraphScene): - def __init__(self, *args, **kwargs): - GraphScene.__init__(self, *args, **kwargs) - word_center = (0, 3, 0) - vertices_word = TextMobject("``Vertices\"").shift(word_center) - edges_word = TextMobject("``Edges\"").shift(word_center) - dots, lines = self.vertices, self.edges - self.remove(*dots + lines) - all_dots = Mobject(*dots) - self.play(ShowCreation(all_dots)) - self.remove(all_dots) - self.add(*dots) - self.play(FadeIn(vertices_word)) - self.wait() - self.remove(vertices_word) - self.play(*[ - ShowCreation(line) for line in lines - ]) - self.play(FadeIn(edges_word)) - - #Move to new graph - # new_graph = deepcopy(self.graph) - # new_graph.vertices = [ - # (v[0] + 3*random(), v[1] + 3*random(), 0) - # for v in new_graph.vertices - # ] - # new_graph_scene = GraphScene(new_graph) - # self.play(*[ - # Transform(m[0], m[1]) - # for m in zip(self.mobjects, new_graph_scene.mobjects) - # ], run_time = 7.0) - -class IntersectCubeGraphEdges(GraphScene): - args_list = [] - @staticmethod - def args_to_string(*args): - return "" - def __init__(self, *args, **kwargs): - GraphScene.__init__(self, CubeGraph(), *args, **kwargs) - self.remove(self.edges[0], self.edges[4]) - self.play(*[ - Transform( - Line(self.points[i], self.points[j]), - CurvedLine(self.points[i], self.points[j]), - ) - for i, j in [(0, 1), (5, 4)] - ]) - - -class DoubledEdges(GraphScene): - def __init__(self, *args, **kwargs): - GraphScene.__init__(self, *args, **kwargs) - lines_to_double = self.edges[:9:3] - crazy_lines = [ - ( - line, - Line(line.end, line.start), - CurvedLine(line.start, line.end) , - CurvedLine(line.end, line.start) - ) - for line in lines_to_double - ] - anims = [] - outward_curved_lines = [] - kwargs = {"run_time" : 3.0} - for straight, backwards, inward, outward in crazy_lines: - anims += [ - Transform(straight, inward, **kwargs), - Transform(backwards, outward, **kwargs), - ] - outward_curved_lines.append(outward) - self.play(*anims) - self.wait() - self.remove(*outward_curved_lines) - -class EulersFormula(GraphScene): - def __init__(self, *args, **kwargs): - GraphScene.__init__(self, *args, **kwargs) - terms = "V - E + F =2".split(" ") - form = dict([ - (key, mob) - for key, mob in zip(terms, TexMobjects(terms)) - ]) - for mob in list(form.values()): - mob.shift((0, FRAME_Y_RADIUS-0.7, 0)) - formula = Mobject(*[form[k] for k in list(form.keys()) if k != "=2"]) - new_form = dict([ - (key, deepcopy(mob).shift((0, -0.7, 0))) - for key, mob in zip(list(form.keys()), list(form.values())) - ]) - self.add(formula) - colored_dots = [ - deepcopy(d).scale_in_place(1.5).set_color("red") - for d in self.dots - ] - colored_edges = [ - Mobject( - Line(midpoint, start), - Line(midpoint, end), - ).set_color("red") - for e in self.edges - for start, end, midpoint in [ - (e.start, e.end, (e.start + e.end)/2.0) - ] - ] - frame_time = 0.3 - - self.generate_regions() - parameters = [ - (colored_dots, "V", "mobject", "-", "show"), - (colored_edges, "E", "mobject", "+", "show"), - (self.regions, "F", "region", "=2", "show_all") - ] - for items, letter, item_type, symbol, mode in parameters: - self.count( - items, - item_type = item_type, - mode = mode, - num_offset = new_form[letter].get_center(), - run_time = frame_time*len(items) - ) - self.wait() - if item_type == "mobject": - self.remove(*items) - self.add(new_form[symbol]) - -class CannotDirectlyApplyEulerToMoser(CircleScene): - def __init__(self, radians, *args, **kwargs): - CircleScene.__init__(self, radians, *args, **kwargs) - self.remove(self.n_equals) - n_equals, intersection_count = TexMobject([ - r"&n = %d\\"%len(radians), - r"&{%d \choose 4} = %d"%(len(radians), choose(len(radians), 4)) - ]).split() - shift_val = self.n_equals.get_center() - n_equals.get_center() - for mob in n_equals, intersection_count: - mob.shift(shift_val) - self.add(n_equals) - yellow_dots = [ - d.set_color("yellow").scale_in_place(2) - for d in deepcopy(self.dots) - ] - yellow_lines = Mobject(*[ - l.set_color("yellow") for l in deepcopy(self.lines) - ]) - self.play(*[ - ShowCreation(dot) for dot in yellow_dots - ], run_time = 1.0) - self.wait() - self.remove(*yellow_dots) - self.play(ShowCreation(yellow_lines)) - self.wait() - self.remove(yellow_lines) - cannot_intersect = TextMobject(r""" - Euler's formula does not apply to \\ - graphs whose edges intersect! - """ - ) - cannot_intersect.center() - for mob in self.mobjects: - mob.fade(0.3) - self.add(cannot_intersect) - self.wait() - self.remove(cannot_intersect) - for mob in self.mobjects: - mob.fade(1/0.3) - self.generate_intersection_dots() - self.play(FadeIn(intersection_count), *[ - ShowCreation(dot) for dot in self.intersection_dots - ]) - -class ShowMoserGraphLines(CircleScene): - def __init__(self, radians, *args, **kwargs): - radians = list(set([x%(2*np.pi) for x in radians])) - radians.sort() - CircleScene.__init__(self, radians, *args, **kwargs) - n, plus_n_choose_4 = TexMobject(["n", "+{n \\choose 4}"]).split() - n_choose_2, plus_2_n_choose_4, plus_n = TexMobject([ - r"{n \choose 2}",r"&+2{n \choose 4}\\",r"&+n" - ]).split() - for mob in n, plus_n_choose_4, n_choose_2, plus_2_n_choose_4, plus_n: - mob.shift((FRAME_X_RADIUS - 2, FRAME_Y_RADIUS-1, 0)) - self.chop_lines_at_intersection_points() - self.add(*self.intersection_dots) - small_lines = [ - deepcopy(line).scale_in_place(0.5) - for line in self.lines - ] - - for mobs, symbol in [ - (self.dots, n), - (self.intersection_dots, plus_n_choose_4), - (self.lines, n_choose_2) - ]: - self.add(symbol) - compound = Mobject(*mobs) - if mobs in (self.dots, self.intersection_dots): - self.remove(*mobs) - self.play(CounterclockwiseTransform( - compound, - deepcopy(compound).scale(1.05), - rate_func = there_and_back, - run_time = 2.0, - )) - else: - compound.set_color("yellow") - self.play(ShowCreation(compound)) - self.remove(compound) - if mobs == self.intersection_dots: - self.remove(n, plus_n_choose_4) - - self.play(*[ - Transform(line, small_line, run_time = 3.0) - for line, small_line in zip(self.lines, small_lines) - ]) - yellow_lines = Mobject(*[ - line.set_color("yellow") for line in small_lines - ]) - self.add(plus_2_n_choose_4) - self.play(ShowCreation(yellow_lines)) - self.wait() - self.remove(yellow_lines) - self.chop_circle_at_points() - self.play(*[ - Transform(p, sp, run_time = 3.0) - for p, sp in zip(self.circle_pieces, self.smaller_circle_pieces) - ]) - self.add(plus_n) - self.play(ShowCreation(Mobject(*[ - mob.set_color("yellow") for mob in self.circle_pieces - ]))) - -class HowIntersectionChopsLine(CircleScene): - args_list = [ - (RADIANS, list(range(0, 7, 2))), - ] - @staticmethod - def args_to_string(*args): - return int_list_to_string(args[1]) - - def __init__(self, radians, indices, *args, **kwargs): - assert(len(indices) == 4) - indices.sort() - CircleScene.__init__(self, radians, *args, **kwargs) - intersection_point = intersection( - (self.points[indices[0]], self.points[indices[2]]), - (self.points[indices[1]], self.points[indices[3]]) - ) - if len(intersection_point) == 2: - intersection_point = list(intersection_point) + [0] - pairs = list(it.combinations(list(range(len(radians))), 2)) - lines = [ - Line(self.points[indices[p0]], self.points[indices[p1]]) - for p0, p1 in [(2, 0), (1, 3)] - ] - self.remove(*[ - self.lines[pairs.index((indices[p0], indices[p1]))] - for p0, p1 in [(0, 2), (1, 3)] - ]) - self.add(*lines) - self.play(*[ - FadeOut(mob) - for mob in self.mobjects - if mob not in lines - ]) - new_lines = [ - Line(line.start, intersection_point) - for line in lines - ] + [ - Line(intersection_point, line.end) - for line in lines - ] - self.play(*[ - Transform( - line, - Line( - (-3, h, 0), - (3, h, 0) - ), - rate_func = there_and_back, - run_time = 3.0 - ) - for line, h in zip(lines, (-1, 1)) - ]) - self.remove(*lines) - self.play(*[ - Transform( - line, - deepcopy(line).scale(1.1).scale_in_place(1/1.1), - run_time = 1.5 - ) - for line in new_lines - ]) - - -class ApplyEulerToMoser(CircleScene): - def __init__(self, *args, **kwargs): - radius = 2 - CircleScene.__init__(self, *args, radius = radius, **kwargs) - self.generate_intersection_dots() - self.chop_lines_at_intersection_points() - self.chop_circle_at_points() - self.generate_regions() - for dot in self.dots + self.intersection_dots: - dot.scale_in_place(radius / RADIUS) - self.remove(*self.mobjects) - - V = {} - minus = {} - minus1 = {} - E = {} - plus = {} - plus1 = {} - plus2 = {} - F = {} - equals = {} - two = {} - two1 = {} - n = {} - n1 = {} - nc2 = {} - nc4 = {} - nc41 = {} - dicts = [V, minus, minus1, E, plus, plus1, plus2, F, - equals, two, two1, n, n1, nc2, nc4, nc41] - - V[1], minus[1], E[1], plus[1], F[1], equals[1], two[1] = \ - TexMobject(["V", "-", "E", "+", "F", "=", "2"]).split() - F[2], equals[2], E[2], minus[2], V[2], plus[2], two[2] = \ - TexMobject(["F", "=", "E", "-", "V", "+", "2"]).split() - F[3], equals[3], E[3], minus[3], n[3], minus1[3], nc4[3], plus[3], two[3] = \ - TexMobject(["F", "=", "E", "-", "n", "-", r"{n \choose 4}", "+", "2"]).split() - F[4], equals[4], nc2[4], plus1[4], two1[4], nc41[4], plus2[4], n1[4], minus[4], n[4], minus1[4], nc4[4], plus[4], two[4] = \ - TexMobject(["F", "=", r"{n \choose 2}", "+", "2", r"{n \choose 4}", "+", "n","-", "n", "-", r"{n \choose 4}", "+", "2"]).split() - F[5], equals[5], nc2[5], plus1[5], two1[5], nc41[5], minus1[5], nc4[5], plus[5], two[5] = \ - TexMobject(["F", "=", r"{n \choose 2}", "+", "2", r"{n \choose 4}", "-", r"{n \choose 4}", "+", "2"]).split() - F[6], equals[6], nc2[6], plus1[6], nc4[6], plus[6], two[6] = \ - TexMobject(["F", "=", r"{n \choose 2}", "+", r"{n \choose 4}", "+", "2"]).split() - F[7], equals[7], two[7], plus[7], nc2[7], plus1[7], nc4[7] = \ - TexMobject(["F", "=", "2", "+", r"{n \choose 2}", "+", r"{n \choose 4}"]).split() - shift_val = (0, 3, 0) - for d in dicts: - if not d: - continue - main_key = list(d.keys())[0] - for key in list(d.keys()): - d[key].shift(shift_val) - main_center = d[main_key].get_center() - for key in list(d.keys()): - d[key] = deepcopy(d[main_key]).shift( - d[key].get_center() - main_center - ) - - self.play(*[ - CounterclockwiseTransform(d[1], d[2], run_time = 2.0) - for d in [V, minus, E, plus, F, equals, two] - ]) - self.wait() - F[1].set_color() - self.add(*self.lines + self.circle_pieces) - for region in self.regions: - self.set_color_region(region) - self.set_color_region(self.exterior, "blue") - self.wait() - self.reset_background() - F[1].set_color("white") - E[1].set_color() - self.remove(*self.lines + self.circle_pieces) - self.play(*[ - Transform( - deepcopy(line), - deepcopy(line).scale_in_place(0.5), - run_time = 2.0, - ) - for line in self.lines - ] + [ - Transform( - deepcopy(cp), scp, - run_time = 2.0 - ) - for cp, scp in zip(self.circle_pieces, self.smaller_circle_pieces) - ]) - self.wait() - E[1].set_color("white") - V[1].set_color() - self.add(*self.dots + self.intersection_dots) - self.remove(*self.lines + self.circle_pieces) - self.play(*[ - Transform( - deepcopy(dot), - deepcopy(dot).scale_in_place(1.4).set_color("yellow") - ) - for dot in self.dots + self.intersection_dots - ] + [ - Transform( - deepcopy(line), - deepcopy(line).fade(0.4) - ) - for line in self.lines + self.circle_pieces - ]) - self.wait() - all_mobs = [mob for mob in self.mobjects] - self.remove(*all_mobs) - self.add(*[d[1] for d in [V, minus, E, plus, F, equals, two]]) - V[1].set_color("white") - two[1].set_color() - self.wait() - self.add(*all_mobs) - self.remove(*[d[1] for d in [V, minus, E, plus, F, equals, two]]) - self.play( - Transform(V[2].repeat(2), Mobject(n[3], minus1[3], nc4[3])), - *[ - Transform(d[2], d[3]) - for d in [F, equals, E, minus, plus, two] - ] - ) - self.wait() - self.remove(*self.mobjects) - self.play( - Transform(E[3], Mobject( - nc2[4], plus1[4], two1[4], nc41[4], plus2[4], n1[4] - )), - *[ - Transform(d[3], d[4]) - for d in [F, equals, minus, n, minus1, nc4, plus, two] - ] + [ - Transform( - deepcopy(line), - deepcopy(line).scale_in_place(0.5), - ) - for line in self.lines - ] + [ - Transform(deepcopy(cp), scp) - for cp, scp in zip(self.circle_pieces, self.smaller_circle_pieces) - ], - run_time = 2.0 - ) - self.wait() - self.remove(*self.mobjects) - self.play( - Transform( - Mobject(plus2[4], n1[4], minus[4], n[4]), - Point((FRAME_X_RADIUS, FRAME_Y_RADIUS, 0)) - ), - *[ - Transform(d[4], d[5]) - for d in [F, equals, nc2, plus1, two1, - nc41, minus1, nc4, plus, two] - ] - ) - self.wait() - self.remove(*self.mobjects) - self.play( - Transform(nc41[5], nc4[6]), - Transform(two1[5], Point(nc4[6].get_center())), - *[ - Transform(d[5], d[6]) - for d in [F, equals, nc2, plus1, nc4, plus, two] - ] - ) - self.wait() - self.remove(*self.mobjects) - self.play( - CounterclockwiseTransform(two[6], two[7]), - CounterclockwiseTransform(plus[6], plus[7]), - *[ - Transform(d[6], d[7]) - for d in [F, equals, nc2, plus1, nc4] - ] - ) - self.wait() - self.add(*self.lines + self.circle_pieces) - for region in self.regions: - self.set_color_region(region) - self.wait() - self.set_color_region(self.exterior, "blue") - self.wait() - self.set_color_region(self.exterior, "black") - self.remove(two[6]) - two = two[7] - one = TexMobject("1").shift(two.get_center()) - two.set_color("red") - self.add(two) - self.play(CounterclockwiseTransform(two, one)) - -class FormulaRelatesToPowersOfTwo(Scene): - def __init__(self, *args, **kwargs): - Scene.__init__(self, *args, **kwargs) - pof2_range = [1, 2, 3, 4, 5, 10] - strings = [ - [ - r"&1 + {%d \choose 2} + {%d \choose 4} ="%(n, n), - r"1 + %d + %d ="%(choose(n, 2), choose(n, 4)), - r"%d \\"%moser_function(n) - ] - for n in [1, 2, 3, 4, 5, 10] - ] - everything = TexMobjects(sum(strings, []), size = r"\large") - scale_factor = 1 - for mob in everything: - mob.scale(scale_factor) - Mobject(*everything).show() - forms = everything[0::3] - sums = everything[1::3] - results = everything[2::3] - self.add(*forms) - self.play(*[ - FadeIn(s) for s in sums - ]) - self.wait() - self.play(*[ - Transform(deepcopy(s), result) - for s, result in zip(sums, results) - ]) - powers_of_two = [ - TexMobject("2^{%d}"%(i-1) - ).scale(scale_factor - ).shift(result.get_center() - ).set_color() - for i, result in zip(pof2_range, results) - ] - self.wait() - self.remove(*self.mobjects) - self.add(*forms + sums + results) - self.play(*[ - CounterclockwiseTransform(result, pof2) - for result, pof2 in zip(results, powers_of_two) - ]) - -class DrawPascalsTriangle(PascalsTriangleScene): - def __init__(self, *args, **kwargs): - PascalsTriangleScene.__init__(self, *args, **kwargs) - self.remove(*self.mobjects) - self.add(self.coords_to_mobs[0][0]) - for n in range(1, nrows): - starts = [deepcopy(self.coords_to_mobs[n-1][0])] - starts += [ - Mobject( - self.coords_to_mobs[n-1][k-1], - self.coords_to_mobs[n-1][k] - ) - for k in range(1, n) - ] - starts.append(deepcopy(self.coords_to_mobs[n-1][n-1])) - self.play(*[ - Transform(starts[i], self.coords_to_mobs[n][i], - run_time = 1.5, black_out_extra_points = False) - for i in range(n+1) - ]) - -class PascalRuleExample(PascalsTriangleScene): - def __init__(self, nrows, *args, **kwargs): - assert(nrows > 1) - PascalsTriangleScene.__init__(self, nrows, *args, **kwargs) - self.wait() - n = randint(2, nrows-1) - k = randint(1, n-1) - self.coords_to_mobs[n][k].set_color("green") - self.wait() - plus = TexMobject("+").scale(0.5) - nums_above = [self.coords_to_mobs[n-1][k-1], self.coords_to_mobs[n-1][k]] - plus.center().shift(sum(map(Mobject.get_center, nums_above)) / 2) - self.add(plus) - for mob in nums_above + [plus]: - mob.set_color("yellow") - self.wait() - -class PascalsTriangleWithNChooseK(PascalsTriangleScene): - def __init__(self, *args, **kwargs): - PascalsTriangleScene.__init__(self, *args, **kwargs) - self.generate_n_choose_k_mobs() - mob_dicts = (self.coords_to_mobs, self.coords_to_n_choose_k) - for i in [0, 1]: - self.wait() - self.remove(*self.mobjects) - self.play(*[ - CounterclockwiseTransform( - deepcopy(mob_dicts[i][n][k]), - mob_dicts[1-i][n][k] - ) - for n, k in self.coords - ]) - self.remove(*self.mobjects) - self.add(*[mob_dicts[1-i][n][k] for n, k in self.coords]) - -class PascalsTriangleNChooseKExample(PascalsTriangleScene): - args_list = [ - (N_PASCAL_ROWS, 5, 3), - ] - @staticmethod - def args_to_string(nrows, n, k): - return "%d_n=%d_k=%d"%(nrows, n, k) - - def __init__(self, nrows, n, k, *args, **kwargs): - PascalsTriangleScene.__init__(self, nrows, *args, **kwargs) - wait_time = 0.5 - triangle_terms = [self.coords_to_mobs[a][b] for a, b in self.coords] - formula_terms = left, n_mob, k_mob, right = TexMobject([ - r"\left(", str(n), r"\atop %d"%k, r"\right)" - ]) - formula_center = (FRAME_X_RADIUS - 1, FRAME_Y_RADIUS - 1, 0) - self.remove(*triangle_terms) - self.add(*formula_terms) - self.wait() - self.play(* - [ - ShowCreation(mob) for mob in triangle_terms - ]+[ - ApplyMethod(mob.shift, formula_center) - for mob in formula_terms - ], - run_time = 1.0 - ) - self.remove(n_mob, k_mob) - for a in range(n+1): - row = [self.coords_to_mobs[a][b] for b in range(a+1)] - a_mob = TexMobject(str(a)) - a_mob.shift(n_mob.get_center()) - a_mob.set_color("green") - self.add(a_mob) - for mob in row: - mob.set_color("green") - self.wait(wait_time) - if a < n: - for mob in row: - mob.set_color("white") - self.remove(a_mob) - self.wait() - for b in range(k+1): - b_mob = TexMobject(str(b)) - b_mob.shift(k_mob.get_center()) - b_mob.set_color("yellow") - self.add(b_mob) - self.coords_to_mobs[n][b].set_color("yellow") - self.wait(wait_time) - if b < k: - self.coords_to_mobs[n][b].set_color("green") - self.remove(b_mob) - self.play(*[ - ApplyMethod(mob.fade, 0.2) - for mob in triangle_terms - if mob != self.coords_to_mobs[n][k] - ]) - self.wait() - -class PascalsTriangleSumRows(PascalsTriangleScene): - def __init__(self, *args, **kwargs): - PascalsTriangleScene.__init__(self, *args, **kwargs) - pluses = [] - powers_of_two = [] - equalses = [] - powers_of_two_symbols = [] - plus = TexMobject("+") - desired_plus_width = self.coords_to_mobs[0][0].get_width() - if plus.get_width() > desired_plus_width: - plus.scale(desired_plus_width / plus.get_width()) - for n, k in self.coords: - if k == 0: - continue - new_plus = deepcopy(plus) - new_plus.center().shift(self.coords_to_mobs[n][k].get_center()) - new_plus.shift((-self.cell_width / 2.0, 0, 0)) - pluses.append(new_plus) - equals = TexMobject("=") - equals.scale(min(1, 0.7 * self.cell_height / equals.get_width())) - for n in range(self.nrows): - new_equals = deepcopy(equals) - pof2 = TexMobjects(str(2**n)) - symbol = TexMobject("2^{%d}"%n) - desired_center = np.array(( - self.diagram_width / 2.0, - self.coords_to_mobs[n][0].get_center()[1], - 0 - )) - new_equals.shift(desired_center - new_equals.get_center()) - desired_center += (1.5*equals.get_width(), 0, 0) - scale_factor = self.coords_to_mobs[0][0].get_height() / pof2.get_height() - for mob in pof2, symbol: - mob.center().scale(scale_factor).shift(desired_center) - symbol.shift((0, 0.5*equals.get_height(), 0)) #FUAH! Stupid - powers_of_two.append(pof2) - equalses.append(new_equals) - powers_of_two_symbols.append(symbol) - self.play(FadeIn(Mobject(*pluses))) - run_time = 0.5 - to_remove = [] - for n in range(self.nrows): - start = Mobject(*[self.coords_to_mobs[n][k] for k in range(n+1)]) - to_remove.append(start) - self.play( - Transform(start, powers_of_two[n]), - FadeIn(equalses[n]), - run_time = run_time - ) - self.wait() - self.remove(*to_remove) - self.add(*powers_of_two) - for n in range(self.nrows): - self.play(CounterclockwiseTransform( - powers_of_two[n], powers_of_two_symbols[n], - run_time = run_time - )) - self.remove(powers_of_two[n]) - self.add(powers_of_two_symbols[n]) - - -class MoserSolutionInPascal(PascalsTriangleScene): - args_list = [ - (N_PASCAL_ROWS, n) - for n in range(3, 8) - ] + [ - (BIG_N_PASCAL_ROWS, 10) - ] - @staticmethod - def args_to_string(nrows, n): - return "%d_n=%d"%(nrows,n) - - def __init__(self, nrows, n, *args, **kwargs): - PascalsTriangleScene.__init__(self, nrows, *args, **kwargs) - term_color = "green" - self.generate_n_choose_k_mobs() - self.remove(*[self.coords_to_mobs[n0][k0] for n0, k0 in self.coords]) - terms = one, plus0, n_choose_2, plus1, n_choose_4 = TexMobject([ - "1", "+", r"{%d \choose 2}"%n, "+", r"{%d \choose 4}"%n - ]).split() - target_terms = [] - for k in range(len(terms)): - if k%2 == 0 and k <= n: - new_term = deepcopy(self.coords_to_n_choose_k[n][k]) - new_term.set_color(term_color) - else: - new_term = Point( - self.coords_to_center(n, k) - ) - target_terms.append(new_term) - self.add(*terms) - self.wait() - self.play(* - [ - FadeIn(self.coords_to_n_choose_k[n0][k0]) - for n0, k0 in self.coords - if (n0, k0) not in [(n, 0), (n, 2), (n, 4)] - ]+[ - Transform(term, target_term) - for term, target_term in zip(terms, target_terms) - ] - ) - self.wait() - term_range = list(range(0, min(4, n)+1, 2)) - target_terms = dict([ - (k, deepcopy(self.coords_to_mobs[n][k]).set_color(term_color)) - for k in term_range - ]) - self.play(* - [ - CounterclockwiseTransform( - self.coords_to_n_choose_k[n0][k0], - self.coords_to_mobs[n0][k0] - ) - for n0, k0 in self.coords - if (n0, k0) not in [(n, k) for k in term_range] - ]+[ - CounterclockwiseTransform(terms[k], target_terms[k]) - for k in term_range - ] - ) - self.wait() - for k in term_range: - if k == 0: - above_terms = [self.coords_to_n_choose_k[n-1][k]] - elif k == n: - above_terms = [self.coords_to_n_choose_k[n-1][k-1]] - else: - above_terms = [ - self.coords_to_n_choose_k[n-1][k-1], - self.coords_to_n_choose_k[n-1][k], - ] - self.add(self.coords_to_mobs[n][k]) - self.play(Transform( - terms[k], - Mobject(*above_terms).set_color(term_color) - )) - self.remove(*above_terms) - self.wait() - terms_sum = TexMobject(str(moser_function(n))) - terms_sum.shift((FRAME_X_RADIUS-1, terms[0].get_center()[1], 0)) - terms_sum.set_color(term_color) - self.play(Transform(Mobject(*terms), terms_sum)) - -class RotatingPolyhedra(Scene): - args_list = [ - ([Cube, Dodecahedron],) - ] - @staticmethod - def args_to_string(class_list): - return "".join([c.__name__ for c in class_list]) - - def __init__(self, polyhedra_classes, *args, **kwargs): - Scene.__init__(self, *args, **kwargs) - rot_kwargs = { - "radians" : np.pi / 2, - "run_time" : 5.0, - "axis" : [0, 1, 0] - } - polyhedra = [ - Class().scale(1.5).shift((1, 0, 0)) - for Class in polyhedra_classes - ] - curr_mob = polyhedra.pop() - for mob in polyhedra: - self.play(TransformAnimations( - Rotating(curr_mob, **rot_kwargs), - Rotating(mob, **rot_kwargs) - )) - for m in polyhedra: - m.rotate(rot_kwargs["radians"], rot_kwargs["axis"]) - self.play(Rotating(curr_mob, **rot_kwargs)) - -class ExplainNChoose2Formula(Scene): - args_list = [(7,2,6)] - @staticmethod - def args_to_string(n, a, b): - return "n=%d_a=%d_b=%d"%(n, a, b) - - def __init__(self, n, a, b, *args, **kwargs): - Scene.__init__(self, *args, **kwargs) - r_paren, a_mob, comma, b_mob, l_paren = TexMobjects( - ("( %d , %d )"%(a, b)).split(" ") - ) - parens = Mobject(r_paren, comma, l_paren) - nums = [TexMobject(str(k)) for k in range(1, n+1)] - height = 1.5*nums[0].get_height() - for x in range(n): - nums[x].shift((0, x*height, 0)) - nums_compound = Mobject(*nums) - nums_compound.shift(a_mob.get_center() - nums[0].get_center()) - n_mob, n_minus_1, over_2 = TexMobject([ - str(n), "(%d-1)"%n, r"\over{2}" - ]).split() - for part in n_mob, n_minus_1, over_2: - part.shift((FRAME_X_RADIUS-1.5, FRAME_Y_RADIUS-1, 0)) - - self.add(parens, n_mob) - up_unit = np.array((0, height, 0)) - self.play(ApplyMethod(nums_compound.shift, -(n-1)*up_unit)) - self.play(ApplyMethod(nums_compound.shift, (n-a)*up_unit)) - self.remove(nums_compound) - nums = nums_compound.split() - a_mob = nums.pop(a-1) - nums_compound = Mobject(*nums) - self.add(a_mob, nums_compound) - self.wait() - right_shift = b_mob.get_center() - a_mob.get_center() - right_shift[1] = 0 - self.play( - ApplyMethod(nums_compound.shift, right_shift), - FadeIn(n_minus_1) - ) - self.play(ApplyMethod(nums_compound.shift, (a-b)*up_unit)) - self.remove(nums_compound) - nums = nums_compound.split() - b_mob = nums.pop(b-2 if a < b else b-1) - self.add(b_mob) - self.play(*[ - CounterclockwiseTransform( - mob, - Point(mob.get_center()).set_color("black") - ) - for mob in nums - ]) - self.play(*[ - ApplyMethod(mob.shift, (0, 1, 0)) - for mob in (parens, a_mob, b_mob) - ]) - parens_copy = deepcopy(parens).shift((0, -2, 0)) - a_center = a_mob.get_center() - b_center = b_mob.get_center() - a_copy = deepcopy(a_mob).center().shift(b_center - (0, 2, 0)) - b_copy = deepcopy(b_mob).center().shift(a_center - (0, 2, 0)) - self.add(over_2, deepcopy(a_mob), deepcopy(b_mob)) - self.play( - CounterclockwiseTransform(a_mob, a_copy), - CounterclockwiseTransform(b_mob, b_copy), - FadeIn(parens_copy), - FadeIn(TextMobject("is considered the same as")) - ) - -class ExplainNChoose4Formula(Scene): - args_list = [(7,)] - @staticmethod - def args_to_string(n): - return "n=%d"%n - - def __init__(self, n, *args, **kwargs): - Scene.__init__(self, *args, **kwargs) - # quad = list(it.combinations(range(1,n+1), 4))[randint(0, choose(n, 4)-1)] - quad = (4, 2, 5, 1) - tuple_mobs = TexMobjects( - ("( %d , %d , %d , %d )"%quad).split(" ") - ) - quad_mobs = tuple_mobs[1::2] - parens = Mobject(*tuple_mobs[0::2]) - form_mobs = TexMobject([ - str(n), "(%d-1)"%n, "(%d-2)"%n,"(%d-3)"%n, - r"\over {4 \cdot 3 \cdot 2 \cdot 1}" - ]).split() - form_mobs = Mobject(*form_mobs).scale(0.7).shift((4, 3, 0)).split() - nums = [TexMobject(str(k)) for k in range(1, n+1)] - height = 1.5*nums[0].get_height() - for x in range(n): - nums[x].shift((0, x*height, 0)) - nums_compound = Mobject(*nums) - nums_compound.shift(quad_mobs[0].get_center() - nums[0].get_center()) - curr_num = 1 - self.add(parens) - up_unit = np.array((0, height, 0)) - for i in range(4): - self.add(form_mobs[i]) - self.play(ApplyMethod( - nums_compound.shift, (curr_num-quad[i])*up_unit)) - self.remove(nums_compound) - nums = nums_compound.split() - chosen = nums[quad[i]-1] - nums[quad[i]-1] = Point(chosen.get_center()).set_color("black") - nums_compound = Mobject(*nums) - self.add(chosen) - if i < 3: - right_shift = quad_mobs[i+1].get_center() - chosen.get_center() - right_shift[1] = 0 - self.play( - ApplyMethod(nums_compound.shift, right_shift) - ) - else: - self.play(*[ - CounterclockwiseTransform( - mob, - Point(mob.get_center()).set_color("black") - ) - for mob in nums - ]) - curr_num = quad[i] - self.remove(*self.mobjects) - num_perms_explain = TextMobject( - r"There are $(4 \cdot 3 \cdot 2 \cdot 1)$ total permutations" - ).shift((0, -2, 0)) - self.add(parens, num_perms_explain, *form_mobs) - perms = list(it.permutations(list(range(4)))) - for count in range(6): - perm = perms[randint(0, 23)] - new_quad_mobs = [ - deepcopy(quad_mobs[i]).shift( - quad_mobs[perm[i]].get_center() - \ - quad_mobs[i].get_center() - ) - for i in range(4) - ] - compound_quad = Mobject(*quad_mobs) - self.play(CounterclockwiseTransform( - compound_quad, - Mobject(*new_quad_mobs) - )) - self.remove(compound_quad) - quad_mobs = new_quad_mobs - -class IntersectionChoppingExamples(Scene): - def __init__(self, *args, **kwargs): - Scene.__init__(self, *args, **kwargs) - pairs1 = [ - ((-1,-1, 0), (-1, 0, 0)), - ((-1, 0, 0), (-1, 1, 0)), - ((-2, 0, 0), (-1, 0, 0)), - ((-1, 0, 0), ( 1, 0, 0)), - (( 1, 0, 0), ( 2, 0, 0)), - (( 1,-1, 0), ( 1, 0, 0)), - (( 1, 0, 0), ( 1, 1, 0)), - ] - pairs2 = pairs1 + [ - (( 1, 1, 0), ( 1, 2, 0)), - (( 0, 1, 0), ( 1, 1, 0)), - (( 1, 1, 0), ( 2, 1, 0)), - ] - for pairs, exp in [(pairs1, "3 + 2(2) = 7"), - (pairs2, "4 + 2(3) = 10")]: - lines = [Line(*pair).scale(2) for pair in pairs] - self.add(TexMobject(exp).shift((0, FRAME_Y_RADIUS-1, 0))) - self.add(*lines) - self.wait() - self.play(*[ - Transform(line, deepcopy(line).scale(1.2).scale_in_place(1/1.2)) - for line in lines - ]) - self.count(lines, run_time = 3.0, num_offset = ORIGIN) - self.wait() - self.remove(*self.mobjects) - - - - diff --git a/from_3b1b/old/mug.py b/from_3b1b/old/mug.py deleted file mode 100644 index 3e2f2850..00000000 --- a/from_3b1b/old/mug.py +++ /dev/null @@ -1,2086 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from manimlib.imports import * -from from_3b1b.old.efvgt import ConfettiSpiril - -#revert_to_original_skipping_status - - -class HappyHolidays(TeacherStudentsScene): - def construct(self): - hats = VGroup(*list(map( - self.get_hat, self.pi_creatures - ))) - self.add(self.get_snowflakes()) - self.change_student_modes( - *["hooray"]*3, - look_at_arg = FRAME_Y_RADIUS*UP, - added_anims = [self.teacher.change, "hooray"] - ) - self.play(LaggedStartMap( - DrawBorderThenFill, hats - ), Animation(self.pi_creatures)) - self.change_student_modes( - "happy", "wave_2", "wave_1", - look_at_arg = FRAME_Y_RADIUS*UP, - ) - self.look_at(self.teacher.get_corner(UP+LEFT)) - self.wait(2) - self.play(self.teacher.change, "happy") - self.wait(2) - - def get_hat(self, pi): - hat = SVGMobject( - file_name = "santa_hat", - height = 0.5*pi.get_height() - ) - hat.rotate(-np.pi/12) - vect = RIGHT - if not pi.is_flipped(): - hat.flip() - vect = LEFT - hat.set_fill(RED_D) - hat.set_stroke(width=0) - hat[0].points = hat[0].points[8 * 4:] - hat[0].set_fill("#DDDDDD") - hat[2].set_fill(WHITE) - hat.add(hat[0]) - hat.next_to(pi.body, UP, buff = SMALL_BUFF) - hat.shift(SMALL_BUFF*vect) - return hat - - def get_snowflakes(self, n_flakes = 50): - snowflakes = VGroup(*[ - SVGMobject( - file_name = "snowflake", - height = 0.5, - stroke_width = 0, - fill_opacity = 0.75, - fill_color = WHITE, - ).rotate(np.pi/12, RIGHT) - for x in range(n_flakes) - ]) - def random_confetti_spiral(mob, **kwargs): - return ConfettiSpiril( - mob, x_start = 2*random.random()*FRAME_X_RADIUS - FRAME_X_RADIUS, - **kwargs - ) - snowflake_spirils = LaggedStartMap( - random_confetti_spiral, snowflakes, - run_time = 10, - rate_func = lambda x : x, - ) - return turn_animation_into_updater(snowflake_spirils) - -class UtilitiesPuzzleScene(Scene): - CONFIG = { - "object_height" : 0.75, - "h_distance" : 2, - "v_distance" : 2, - "line_width" : 4, - } - def setup_configuration(self): - houses = VGroup() - for x in range(3): - house = SVGMobject(file_name = "house") - house.set_height(self.object_height) - house.set_fill(LIGHT_GREY) - house.move_to(x*self.h_distance*RIGHT) - houses.add(house) - houses.move_to(self.v_distance*UP/2) - - utilities = VGroup(*[ - self.get_utility(u, c).move_to(x*self.h_distance*RIGHT) - for x, u, c in zip( - it.count(), - ["fire", "electricity", "water"], - [RED_D, YELLOW_C, BLUE_D] - ) - ]) - utilities.move_to(self.v_distance*DOWN/2) - objects = VGroup(houses, utilities) - bounding_box = SurroundingRectangle( - objects, - buff = MED_LARGE_BUFF, - stroke_width = 0 - ) - objects.add(bounding_box) - self.add_foreground_mobjects(objects) - self.set_variables_as_attrs( - houses, utilities, objects, bounding_box - ) - - def get_utility(self, name, color): - circle = Circle( - fill_color = color, - fill_opacity = 1, - stroke_width = 0, - ) - utility = SVGMobject( - file_name = name, - height = 0.65*circle.get_height(), - fill_color = WHITE, - ) - if color == YELLOW: - utility.set_fill(DARK_GREY) - utility.move_to(circle) - circle.add(utility) - circle.set_height(self.object_height) - return circle - - def get_line( - self, utility_index, house_index, - *midpoints, - **kwargs - ): - prop = kwargs.pop("prop", 1.0) - utility = self.utilities[utility_index] - points = [utility.get_center()] - points += list(midpoints) - points += [self.houses[house_index].get_center()] - line = Line( - points[0], points[-1], - color = utility[0].get_color(), - stroke_width = self.line_width - ) - line.set_points_smoothly(points) - line.pointwise_become_partial(line, 0, prop) - return line - - def get_almost_solution_lines(self): - bb = self.bounding_box - return VGroup( - VGroup( - self.get_line(0, 0), - self.get_line(0, 1), - self.get_line(0, 2, bb.get_top()), - ), - VGroup( - self.get_line(1, 0, bb.get_corner(DOWN+LEFT)), - self.get_line(1, 1), - self.get_line(1, 2, bb.get_corner(DOWN+RIGHT)), - ), - VGroup( - self.get_line(2, 0, bb.get_top()), - self.get_line(2, 1), - self.get_line(2, 2), - ), - ) - - def get_straight_lines(self): - return VGroup(*[ - VGroup(*[self.get_line(i, j) for j in range(3)]) - for i in range(3) - ]) - - def get_no_crossing_words(self): - arrow = Vector(DOWN) - arrow.next_to(self.bounding_box.get_top(), UP, SMALL_BUFF) - words = TextMobject("No crossing!") - words.next_to(arrow, UP, buff = SMALL_BUFF) - result = VGroup(words, arrow) - result.set_color("RED") - return result - - def get_region(self, *bounding_edges): - region = VMobject(mark_paths_closed = True) - for i, edge in enumerate(bounding_edges): - new_edge = edge.copy() - if i%2 == 1: - new_edge.points = new_edge.points[::-1] - region.append_vectorized_mobject(new_edge) - region.set_stroke(width = 0) - region.set_fill(WHITE, opacity = 1) - return region - - def convert_objects_to_dots(self, run_time = 2): - group = VGroup(*it.chain(self.houses, self.utilities)) - for mob in group: - mob.target = Dot(color = mob.get_color()) - mob.target.scale(2) - mob.target.move_to(mob) - self.play(LaggedStartMap(MoveToTarget, group, run_time = run_time)) - -class PauseIt(PiCreatureScene): - def construct(self): - morty = self.pi_creatures - morty.center().to_edge(DOWN) - self.pi_creature_says( - "Pause it!", target_mode = "surprised" - ) - self.wait(2) - -class AboutToyPuzzles(UtilitiesPuzzleScene, TeacherStudentsScene, ThreeDScene): - def construct(self): - self.remove(self.pi_creatures) - self.lay_out_puzzle() - self.point_to_abstractions() - self.show_this_video() - - def lay_out_puzzle(self): - self.setup_configuration() - houses, utilities = self.houses, self.utilities - lines = VGroup(*it.chain(*self.get_almost_solution_lines())) - no_crossing_words = self.get_no_crossing_words() - - self.remove(self.objects) - self.play( - ReplacementTransform( - VGroup(houses[1], houses[1]).copy().fade(1), - VGroup(houses[0], houses[2]), - rate_func = squish_rate_func(smooth, 0.35, 1), - run_time = 2, - ), - FadeIn(houses[1]), - LaggedStartMap(DrawBorderThenFill, utilities, run_time = 2) - ) - self.play( - LaggedStartMap( - ShowCreation, lines, - run_time = 3 - ), - Animation(self.objects) - ) - self.play( - Write(no_crossing_words[0]), - GrowArrow(no_crossing_words[1]), - ) - self.wait() - - self.objects.add_to_back(lines, no_crossing_words) - - def point_to_abstractions(self): - objects = self.objects - objects.generate_target() - objects.target.scale(0.5) - objects.target.move_to( - (FRAME_Y_RADIUS*DOWN + FRAME_X_RADIUS*LEFT)/2 - ) - - eulers = TexMobject(*"V-E+F=2") - eulers.set_color_by_tex_to_color_map({ - "V" : RED, - "E" : GREEN, - "F" : BLUE, - }) - eulers.to_edge(UP, buff = 2) - - cube = Cube() - cube.set_stroke(WHITE, 2) - cube.set_height(0.75) - cube.pose_at_angle() - cube.next_to(eulers, UP) - - tda = TextMobject("Topological \\\\ Data Analysis") - tda.move_to(DOWN + 4*RIGHT) - - arrow_from_eulers = Arrow( - eulers.get_bottom(), tda.get_corner(UP+LEFT), - color = WHITE - ) - - self.play( - objects.scale, 0.5, - objects.move_to, DOWN + 4*LEFT, - ) - arrow_to_eulers = Arrow( - self.houses[2].get_corner(UP+LEFT), - eulers.get_bottom(), - color = WHITE - ) - always_rotate(cube, axis=UP) - self.play( - GrowArrow(arrow_to_eulers), - Write(eulers), - FadeIn(cube) - ) - self.wait(5) - self.play( - GrowArrow(arrow_from_eulers), - Write(tda) - ) - self.wait(2) - - self.set_variables_as_attrs( - eulers, cube, tda, - arrows = VGroup(arrow_to_eulers, arrow_from_eulers), - ) - - def show_this_video(self): - screen_rect = FullScreenFadeRectangle( - stroke_color = WHITE, - stroke_width = 2, - fill_opacity = 0, - ) - everything = VGroup( - self.objects, self.arrows, self.eulers, - self.cube, self.tda, - screen_rect, - ) - - self.teacher.save_state() - self.teacher.fade(1) - - self.play( - everything.scale, 0.5, - everything.next_to, self.teacher, UP+LEFT, - self.teacher.restore, - self.teacher.change, "raise_right_hand", UP+LEFT, - LaggedStartMap(FadeIn, self.students) - ) - self.change_student_modes( - *["pondering"]*3, look_at_arg = everything - ) - self.wait(5) - -class ThisPuzzleIsHard(UtilitiesPuzzleScene, PiCreatureScene): - def construct(self): - self.introduce_components() - self.failed_attempts() - self.ask_meta_puzzle() - - def introduce_components(self): - randy = self.pi_creature - - try_it = TextMobject("Try it yourself!") - try_it.to_edge(UP) - - self.setup_configuration() - houses, utilities = self.houses, self.utilities - self.remove(self.objects) - house = houses[0] - - puzzle_words = TextMobject(""" - Puzzle: Connect each house to \\\\ - each utility without crossing lines. - """) - # puzzle_words.next_to(self.objects, UP) - puzzle_words.to_edge(UP) - - self.add(try_it) - self.play(Animation(try_it)) - self.play( - LaggedStartMap(DrawBorderThenFill, houses), - LaggedStartMap(GrowFromCenter, utilities), - try_it.set_width, house.get_width(), - try_it.fade, 1, - try_it.move_to, house, - self.pi_creature.change, "happy", - ) - self.play(LaggedStartMap(FadeIn, puzzle_words)) - - self.add_foreground_mobjects(self.objects) - self.set_variables_as_attrs(puzzle_words) - - def failed_attempts(self): - bb = self.bounding_box - utilities = self.utilities - houses = self.houses - randy = self.pi_creature - - line_sets = [ - [ - self.get_line(0, 0), - self.get_line(1, 1), - self.get_line(2, 2), - self.get_line(0, 1), - self.get_line(2, 1), - self.get_line(0, 2, bb.get_corner(UP+LEFT)), - self.get_line( - 2, 0, bb.get_corner(DOWN+LEFT), - prop = 0.85, - ), - self.get_line( - 2, 0, bb.get_corner(UP+RIGHT), bb.get_top(), - prop = 0.73, - ), - ], - [ - self.get_line(0, 0), - self.get_line(1, 1), - self.get_line(2, 2), - self.get_line(0, 1), - self.get_line(1, 2), - self.get_line( - 2, 0, - bb.get_bottom(), - bb.get_corner(DOWN+LEFT) - ), - self.get_line(0, 2, bb.get_top()), - self.get_line( - 2, 1, - utilities[1].get_bottom() + MED_SMALL_BUFF*DOWN, - utilities[1].get_left() + MED_SMALL_BUFF*LEFT, - ), - self.get_line( - 1, 0, houses[2].get_corner(UP+LEFT) + MED_LARGE_BUFF*LEFT, - prop = 0.49, - ), - self.get_line( - 1, 2, bb.get_right(), - prop = 0.25, - ), - ], - [ - self.get_line(0, 0), - self.get_line(0, 1), - self.get_line(0, 2, bb.get_top()), - self.get_line(1, 0, bb.get_corner(DOWN+LEFT)), - self.get_line(1, 1), - self.get_line(1, 2, bb.get_corner(DOWN+RIGHT)), - self.get_line(2, 1), - self.get_line(2, 2), - self.get_line(2, 0, bb.get_top(), prop = 0.45), - self.get_line(2, 0, prop = 0.45), - ], - ] - modes = ["confused", "sassy", "angry"] - - for line_set, mode in zip(line_sets, modes): - good_lines = VGroup(*line_set[:-2]) - bad_lines = line_set[-2:] - self.play(LaggedStartMap(ShowCreation, good_lines)) - for bl in bad_lines: - self.play( - ShowCreation( - bl, - rate_func = bezier([0, 0, 0, 1, 1, 1, 1, 1, 0.3, 1, 1]), - run_time = 1.5 - ), - randy.change, mode, - ) - self.play(ShowCreation( - bl, rate_func = lambda t : smooth(1-t), - )) - self.remove(bl) - self.play(LaggedStartMap(FadeOut, good_lines)) - - def ask_meta_puzzle(self): - randy = self.pi_creature - group = VGroup( - self.puzzle_words, - self.objects, - ) - rect = SurroundingRectangle(group, color = BLUE, buff = MED_LARGE_BUFF) - group.add(rect) - group.generate_target() - group.target.scale(0.75) - group.target.shift(DOWN) - group[-1].set_stroke(width = 0) - - meta_puzzle_words = TextMobject(""" - Meta-puzzle: Prove that this\\\\ - is impossible. - """) - meta_puzzle_words.next_to(group.target, UP) - meta_puzzle_words.set_color(BLUE) - - self.play( - MoveToTarget(group), - randy.change, "pondering" - ) - self.play(Write(meta_puzzle_words)) - self.play(randy.change, "confused") - - straight_lines = self.get_straight_lines() - almost_solution_lines = self.get_almost_solution_lines() - self.play(LaggedStartMap( - ShowCreation, straight_lines, - run_time = 2, - lag_ratio = 0.8 - ), Blink(randy)) - self.play(Transform( - straight_lines, almost_solution_lines, - run_time = 3, - lag_ratio = 0.5 - )) - self.wait() - - ###### - - def create_pi_creature(self): - return Randolph().to_corner(DOWN+LEFT) - -class IntroduceGraph(PiCreatureScene): - def construct(self): - pi_creatures = self.pi_creatures - dots = VGroup(*[ - Dot(color = pi.get_color()).scale(2).move_to(pi) - for pi in pi_creatures - ]) - lines = VGroup(*[ - Line(pi1.get_center(), pi2.get_center()) - for pi1, pi2 in it.combinations(pi_creatures, 2) - ]) - - graph_word = TextMobject("``", "", "Graph", "''", arg_separator = "") - graph_word.to_edge(UP) - planar_graph_word = TextMobject("``", "Planar", " graph", "''", arg_separator = "") - planar_graph_word.move_to(graph_word) - - vertices_word = TextMobject("Vertices") - vertices_word.to_edge(RIGHT, buff = LARGE_BUFF) - vertices_word.set_color(YELLOW) - - vertex_arrows = VGroup(*[ - Arrow(vertices_word.get_left(), dot) - for dot in dots[-2:] - ]) - - edge_word = TextMobject("Edge") - edge_word.next_to(lines, LEFT, LARGE_BUFF) - edge_arrow = Arrow( - edge_word, lines, buff = SMALL_BUFF, - color = WHITE - ) - - self.play(LaggedStartMap(GrowFromCenter, pi_creatures)) - self.play( - LaggedStartMap(ShowCreation, lines), - LaggedStartMap( - ApplyMethod, pi_creatures, - lambda pi : (pi.change, "pondering", lines) - ) - ) - self.play(Write(graph_word)) - self.play(ReplacementTransform( - pi_creatures, dots, - run_time = 2, - lag_ratio = 0.5 - )) - self.add_foreground_mobjects(dots) - self.play( - FadeIn(vertex_arrows), - FadeIn(vertices_word), - ) - self.wait() - self.play(LaggedStartMap( - ApplyMethod, lines, - lambda l : (l.rotate_in_place, np.pi/12), - rate_func = wiggle - )) - self.play( - FadeIn(edge_word), - GrowArrow(edge_arrow), - ) - self.wait(2) - line = lines[2] - self.play( - line.set_points_smoothly, [ - line.get_start(), - dots.get_left() + MED_SMALL_BUFF*LEFT, - dots.get_corner(DOWN+LEFT) + MED_SMALL_BUFF*(DOWN+LEFT), - dots.get_bottom() + MED_SMALL_BUFF*DOWN, - line.get_end(), - ], - VGroup(edge_word, edge_arrow).shift, MED_LARGE_BUFF*LEFT, - ) - self.wait() - self.play(ReplacementTransform(graph_word, planar_graph_word)) - self.wait(2) - - ### - - def create_pi_creatures(self): - pis = VGroup( - PiCreature(color = BLUE_D), - PiCreature(color = GREY_BROWN), - PiCreature(color = BLUE_C).flip(), - PiCreature(color = BLUE_E).flip(), - ) - pis.scale(0.5) - pis.arrange_in_grid(buff = 2) - return pis - -class IsK33Planar(UtilitiesPuzzleScene): - def construct(self): - self.setup_configuration() - self.objects.shift(MED_LARGE_BUFF*DOWN) - - straight_lines = self.get_straight_lines() - almost_solution_lines = self.get_almost_solution_lines() - - question = TextMobject("Is", "this graph", "planar?") - question.set_color_by_tex("this graph", YELLOW) - question.to_edge(UP) - brace = Brace(question.get_part_by_tex("graph"), DOWN, buff = SMALL_BUFF) - fancy_name = brace.get_text( - "``Complete bipartite graph $K_{3, 3}$''", - buff = SMALL_BUFF - ) - fancy_name.set_color(YELLOW) - - self.add(question) - self.convert_objects_to_dots() - self.play(LaggedStartMap(ShowCreation, straight_lines)) - self.play( - GrowFromCenter(brace), - LaggedStartMap(FadeIn, fancy_name), - ) - self.play(ReplacementTransform( - straight_lines, almost_solution_lines, - run_time = 3, - lag_ratio = 0.5 - )) - self.wait(2) - -class TwoKindsOfViewers(PiCreatureScene, UtilitiesPuzzleScene): - def construct(self): - self.setup_configuration() - objects = self.objects - objects.remove(self.bounding_box) - lines = self.get_straight_lines() - objects.add_to_back(lines) - objects.scale(0.75) - objects.next_to(ORIGIN, RIGHT, LARGE_BUFF) - self.remove(objects) - - pi1, pi2 = self.pi_creatures - words = TextMobject( - "$(V-E+F)$", "kinds of viewers" - ) - words.to_edge(UP) - eulers = words.get_part_by_tex("V-E+F") - eulers.set_color(GREEN) - non_eulers = VGroup(*[m for m in words if m is not eulers]) - - self.add(words) - self.wait() - self.play( - pi1.shift, 2*LEFT, - pi2.shift, 2*RIGHT, - ) - - know_eulers = TextMobject("Know about \\\\ Euler's formula") - know_eulers.next_to(pi1, DOWN) - know_eulers.set_color(GREEN) - dont = TextMobject("Don't") - dont.next_to(pi2, DOWN) - dont.set_color(RED) - - self.play( - FadeIn(know_eulers), - pi1.change, "hooray", - ) - self.play( - FadeIn(dont), - pi2.change, "maybe", eulers, - ) - self.wait() - self.pi_creature_thinks( - pi1, "", - bubble_kwargs = {"width" : 3, "height" : 2}, - target_mode = "thinking" - ) - self.play(pi2.change, "confused", eulers) - self.wait() - - ### Out of thin air - self.play(*list(map(FadeOut, [ - non_eulers, pi1, pi2, pi1.bubble, - know_eulers, dont - ]))) - self.play(eulers.next_to, ORIGIN, LEFT, LARGE_BUFF) - arrow = Arrow(eulers, objects, color = WHITE) - self.play( - GrowArrow(arrow), - LaggedStartMap(DrawBorderThenFill, VGroup(*it.chain(*objects))) - ) - self.wait() - self.play( - objects.move_to, eulers, RIGHT, - eulers.move_to, objects, LEFT, - path_arc = np.pi, - run_time = 1.5, - ) - self.wait(2) - - ### - - def create_pi_creatures(self): - group = VGroup(Randolph(color = BLUE_C), Randolph()) - group.scale(0.7) - group.shift(MED_LARGE_BUFF*DOWN) - return group - -class IntroduceRegions(UtilitiesPuzzleScene): - def construct(self): - self.setup_configuration() - houses, utilities = self.houses, self.utilities - objects = self.objects - lines, line_groups, regions = self.get_lines_line_groups_and_regions() - back_region = regions[0] - front_regions = VGroup(*regions[1:]) - - self.convert_objects_to_dots(run_time = 0) - self.play(LaggedStartMap( - ShowCreation, lines, - run_time = 3, - )) - self.add_foreground_mobjects(lines, objects) - self.wait() - for region in front_regions: - self.play(FadeIn(region)) - self.play( - FadeIn(back_region), - Animation(front_regions), - ) - self.wait() - self.play(FadeOut(regions)) - - ##Paint bucket - paint_bucket = SVGMobject( - file_name = "paint_bucket", - height = 0.5, - ) - paint_bucket.flip() - paint_bucket.move_to(8*LEFT + 5*UP) - - def click(region): - self.play( - UpdateFromAlphaFunc( - region, - lambda m, a : m.set_fill(opacity = int(2*a)), - ), - ApplyMethod( - paint_bucket.scale_in_place, 0.5, - rate_func = there_and_back, - ), - run_time = 0.25, - ) - - self.play( - paint_bucket.next_to, utilities, DOWN+LEFT, SMALL_BUFF - ) - click(regions[1]) - self.play(paint_bucket.next_to, utilities[1], UP+RIGHT, SMALL_BUFF) - click(regions[2]) - self.play(paint_bucket.next_to, houses[1], RIGHT) - click(regions[3]) - self.play(paint_bucket.move_to, 4*LEFT + 2*UP) - self.add_foreground_mobjects(front_regions, lines, objects) - click(back_region) - self.remove_foreground_mobjects(front_regions) - self.wait() - self.play( - FadeOut(back_region), - FadeOut(front_regions[0]), - FadeOut(paint_bucket), - *list(map(Animation, front_regions[1:])) - ) - - #Line tries to escape - point_sets = [ - [ - VGroup(*houses[1:]).get_center(), - houses[2].get_top() + MED_SMALL_BUFF*UP, - ], - [ - houses[1].get_top() + SMALL_BUFF*UP, - utilities[0].get_center(), - ], - [VGroup(houses[1], utilities[1]).get_center()], - [ - utilities[2].get_center() + 0.75*(DOWN+RIGHT) - ], - ] - escape_lines = VGroup(*[ - Line(LEFT, RIGHT).set_points_smoothly( - [utilities[2].get_center()] + point_set - ) - for point_set in point_sets - ]) - - self.wait() - for line in escape_lines: - self.play(ShowCreation(line, - rate_func = lambda t : 0.8*smooth(t) - )) - self.play(ShowCreation(line, - rate_func = lambda t : smooth(1 - t) - )) - - def get_lines_line_groups_and_regions(self): - lines = self.get_almost_solution_lines() - flat_lines = VGroup(*it.chain(*lines)) - flat_lines.remove(lines[2][0]) - - line_groups = [ - VGroup(*[lines[i][j] for i, j in ij_set]) - for ij_set in [ - [(0, 0), (1, 0), (1, 1), (0, 1)], - [(1, 1), (2, 1), (2, 2), (1, 2)], - [(0, 2), (2, 2), (2, 1), (0, 1)], - [(0, 0), (1, 0), (1, 2), (0, 2)], - ] - ] - regions = VGroup(*[ - self.get_region(*line_group) - for line_group in line_groups - ]) - back_region = FullScreenFadeRectangle(fill_opacity = 1 ) - regions.submobjects.pop() - regions.submobjects.insert(0, back_region) - front_regions = VGroup(*regions[1:]) - - back_region.set_color(BLUE_E) - front_regions.set_color_by_gradient(GREEN_E, MAROON_E) - - return flat_lines, line_groups, regions - -class FromLastVideo(Scene): - def construct(self): - title = TextMobject("From last video") - title.to_edge(UP) - rect = ScreenRectangle(height = 6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait(2) - -class AskAboutRegions(IntroduceRegions): - def construct(self): - self.setup_configuration() - houses, utilities = self.houses, self.utilities - self.convert_objects_to_dots(run_time = 0) - objects = self.objects - lines, line_groups, regions = self.get_lines_line_groups_and_regions() - back_region = regions[0] - front_regions = VGroup(*regions[1:]) - missing_lines = VGroup( - self.get_line(2, 0, self.objects.get_top()), - self.get_line( - 2, 0, - self.objects.get_bottom() + DOWN, - self.objects.get_corner(DOWN+LEFT) + DOWN+LEFT, - ) - ) - missing_lines.set_stroke(width = 5) - - front_regions.save_state() - front_regions.generate_target() - front_regions.target.scale(0.5) - front_regions.target.arrange(RIGHT, buff = LARGE_BUFF) - front_regions.target.to_edge(UP) - - self.add(front_regions) - self.add_foreground_mobjects(lines, objects) - self.wait() - self.play(MoveToTarget(front_regions)) - self.play(LaggedStartMap( - ApplyMethod, front_regions, - lambda m : (m.rotate_in_place, np.pi/12), - rate_func = wiggle, - lag_ratio = 0.75, - run_time = 1 - )) - self.play(front_regions.restore) - self.wait() - - #Show missing lines - for line in missing_lines: - self.play(ShowCreation( - line, - rate_func = there_and_back, - run_time = 2, - )) - - #Count regions - count = TexMobject("1") - count.scale(1.5) - count.to_edge(UP) - self.play( - FadeIn(back_region), - FadeIn(count), - Animation(front_regions) - ) - last_region = None - for n, region in zip(it.count(2), front_regions): - new_count = TexMobject(str(n)) - new_count.replace(count, dim_to_match = 1) - self.remove(count) - self.add(new_count) - count = new_count - - region.save_state() - anims = [ApplyMethod(region.set_color, YELLOW)] - if last_region: - anims.append(ApplyMethod(last_region.restore)) - anims.append(Animation(front_regions)) - self.play(*anims, run_time = 0.25) - self.wait(0.5) - last_region = region - self.play(last_region.restore) - self.wait() - self.play(FadeOut(count)) - - #Count edges per region - fade_rect = FullScreenFadeRectangle(opacity = 0.8) - line_group = line_groups[0].copy() - region = front_regions[0].copy() - self.foreground_mobjects = [] - def show_lines(line_group): - lg_copy = line_group.copy() - lg_copy.set_stroke(WHITE, 6) - self.play(LaggedStartMap( - FadeIn, lg_copy, - run_time = 3, - rate_func = there_and_back, - lag_ratio = 0.4, - remover = True, - )) - - self.play( - FadeIn(fade_rect), - Animation(region), - Animation(line_group), - ) - show_lines(line_group) - last_line_group = line_group - last_region = region - for i in range(1, 3): - line_group = line_groups[i].copy() - region = front_regions[i].copy() - self.play( - FadeOut(last_region), - FadeOut(last_line_group), - FadeIn(region), - FadeIn(line_group), - ) - show_lines(line_group) - last_line_group = line_group - last_region = region - self.play( - FadeOut(fade_rect), - FadeOut(last_region), - FadeOut(last_line_group), - ) - self.wait() - -class NewRegionClosedOnlyForNodesWithEdges(UtilitiesPuzzleScene): - def construct(self): - self.setup_configuration() - self.convert_objects_to_dots(run_time = 0) - objects = self.objects - houses, utilities = self.houses, self.utilities - - bb = self.bounding_box - lines = VGroup( - self.get_line(2, 1), - self.get_line(0, 1), - self.get_line(0, 2, - bb.get_corner(UP+LEFT), - bb.get_top() + MED_LARGE_BUFF*UP, - ), - self.get_line(2, 2), - ) - lit_line = lines[2].copy() - lit_line.points = lit_line.points[::-1] - lit_line.set_stroke(WHITE, 5) - - region = self.get_region(*lines) - region.set_fill(MAROON_E) - - arrow = Vector(DOWN+LEFT, color = WHITE) - arrow.next_to(houses[2], UP+RIGHT, buff = SMALL_BUFF) - words = TextMobject("Already has \\\\ an edge") - words.next_to(arrow.get_start(), UP, SMALL_BUFF) - - for line in lines[:-1]: - self.play(ShowCreation(line)) - lines[-1].pointwise_become_partial(lines[-1], 0, 0.92) - lines[-1].save_state() - self.wait() - self.play(ShowCreation(lines[-1])) - self.add(region, lines, objects) - self.wait() - self.remove(region) - self.play(ShowCreation(lines[-1], - rate_func = lambda t : smooth(1-2*t*(1-t)) - )) - self.add(region, lines, objects) - self.wait() - self.remove(region) - self.play( - ShowCreation(lines[-1], - rate_func = lambda t : smooth(1-0.5*t) - ), - FadeIn(words), - GrowArrow(arrow), - ) - for x in range(2): - self.play(ShowCreationThenDestruction(lit_line)) - self.play(lines[-1].restore) - self.add(region, lines, objects) - self.wait(2) - -class LightUpNodes(IntroduceRegions): - CONFIG = { - "vertices_word" : "Lit vertices", - } - def construct(self): - self.setup_configuration() - self.setup_regions() - self.setup_counters() - self.describe_one_as_lit() - self.show_rule_for_lighting() - - def setup_configuration(self): - IntroduceRegions.setup_configuration(self) - self.convert_objects_to_dots(run_time = 0) - self.objects.shift(DOWN) - - def setup_regions(self): - lines, line_groups, regions = self.get_lines_line_groups_and_regions() - back_region = regions[0] - front_regions = VGroup(*regions[1:]) - self.set_variables_as_attrs( - lines, line_groups, regions, - back_region, front_regions, - ) - - def setup_counters(self): - titles = [ - TextMobject("\\# %s"%self.vertices_word), - TextMobject("\\# Edges"), - TextMobject("\\# Regions"), - ] - for title, vect in zip(titles, [LEFT, ORIGIN, RIGHT]): - title.shift(FRAME_X_RADIUS*vect/2) - title.to_edge(UP) - underline = Line(LEFT, RIGHT) - underline.stretch_to_fit_width(title.get_width()) - underline.next_to(title, DOWN, SMALL_BUFF) - title.add(underline) - self.add(title) - self.count_titles = titles - self.v_count, self.e_count, self.f_count = self.counts = list(map( - Integer, [1, 0, 1] - )) - for count, title in zip(self.counts, titles): - count.next_to(title, DOWN) - self.add(count) - - def describe_one_as_lit(self): - houses, utilities = self.houses, self.utilities - vertices = VGroup(*it.chain(houses, utilities)) - dim_arrows = VGroup() - for vertex in vertices: - arrow = Vector(0.5*(DOWN+LEFT), color = WHITE) - arrow.next_to(vertex, UP+RIGHT, SMALL_BUFF) - vertex.arrow = arrow - dim_arrows.add(arrow) - lit_vertex = utilities[0] - lit_arrow = lit_vertex.arrow - lit_arrow.rotate(np.pi/2, about_point = lit_vertex.get_center()) - dim_arrows.remove(lit_arrow) - lit_word = TextMobject("Lit up") - lit_word.next_to(lit_arrow.get_start(), UP, SMALL_BUFF) - dim_word = TextMobject("Dim") - dim_word.next_to(dim_arrows[1].get_start(), UP, MED_LARGE_BUFF) - - dot = Dot().move_to(self.v_count) - - self.play( - vertices.set_fill, None, 0, - vertices.set_stroke, None, 1, - ) - self.play(ReplacementTransform(dot, lit_vertex)) - self.play( - FadeIn(lit_word), - GrowArrow(lit_arrow) - ) - self.play(*self.get_lit_vertex_animations(lit_vertex)) - self.play( - FadeIn(dim_word), - LaggedStartMap(GrowArrow, dim_arrows) - ) - self.wait() - self.play(*list(map(FadeOut, [ - lit_word, lit_arrow, dim_word, dim_arrows - ]))) - - def show_rule_for_lighting(self): - lines = self.lines - regions = self.regions - line_groups = self.line_groups - objects = self.objects - houses, utilities = self.houses, self.utilities - - #First region, lines 0, 1, 4, 3 - lines[4].rotate_in_place(np.pi) - region = regions[1] - - self.play(ShowCreation(lines[0])) - self.play(*self.get_count_change_animations(0, 1, 0)) - self.play(*it.chain( - self.get_lit_vertex_animations(houses[0]), - self.get_count_change_animations(1, 0, 0) - )) - self.wait() - for line, vertex in (lines[1], houses[1]), (lines[4], utilities[1]): - self.play( - ShowCreation(line), - *self.get_count_change_animations(0, 1, 0) - ) - self.play(*it.chain( - self.get_lit_vertex_animations(vertex), - self.get_count_change_animations(1, 0, 0), - )) - self.wait() - self.play( - ShowCreation(lines[3], run_time = 2), - *self.get_count_change_animations(0, 1, 0) - ) - self.add_foreground_mobjects(line_groups[0]) - self.add_foreground_mobjects(objects) - self.play( - FadeIn(region), - *self.get_count_change_animations(0, 0, 1) - ) - self.wait() - - #Next region, lines 2, 7, 8 - region = regions[3] - lines[6].rotate_in_place(np.pi) - - for line, vertex in (lines[2], houses[2]), (lines[6], utilities[2]): - self.play(ShowCreation(line), *it.chain( - self.get_lit_vertex_animations( - vertex, - run_time = 2, - squish_range = (0.5, 1), - ), - self.get_count_change_animations(1, 1, 0) - )) - self.wait() - self.play( - ShowCreation(lines[7]), - *self.get_count_change_animations(0, 1, 1) - ) - self.add_foreground_mobjects(line_groups[2]) - self.add_foreground_mobjects(objects) - self.play(FadeIn(region)) - self.wait() - - #### - - def get_count_change_animations(self, *changes): - anims = [] - for change, count in zip(changes, self.counts): - if change == 0: - continue - new_count = Integer(count.number + 1) - new_count.move_to(count) - anims.append(Transform( - count, new_count, - run_time = 2, - rate_func = squish_rate_func(smooth, 0.5, 1) - )) - count.number += 1 - anims.append(self.get_plus_one_anim(count)) - - return anims - - def get_plus_one_anim(self, count): - plus_one = TexMobject("+1") - plus_one.set_color(YELLOW) - plus_one.move_to(count) - plus_one.next_to(count, DOWN) - plus_one.generate_target() - plus_one.target.move_to(count) - plus_one.target.set_fill(opacity = 0) - move = MoveToTarget(plus_one, remover = True) - grow = GrowFromCenter(plus_one) - return UpdateFromAlphaFunc( - plus_one, - lambda m, a : ( - (grow if a < 0.5 else move).update(2*a%1) - ), - remover = True, - rate_func = double_smooth, - run_time = 2 - ) - - def get_lit_vertex_animations(self, vertex, run_time = 1, squish_range = (0, 1)): - line = Line( - LEFT, RIGHT, - stroke_width = 0, - stroke_color = BLACK, - ) - line.set_width(0.5*vertex.get_width()) - line.next_to(ORIGIN, buff = 0.75*vertex.get_width()) - lines = VGroup(*[ - line.copy().rotate(angle) - for angle in np.arange(0, 2*np.pi, np.pi/4) - ]) - lines.move_to(vertex) - random.shuffle(lines.submobjects) - return [ - LaggedStartMap( - ApplyMethod, lines, - lambda l : (l.set_stroke, YELLOW, 4), - rate_func = squish_rate_func(there_and_back, *squish_range), - lag_ratio = 0.75, - remover = True, - run_time = run_time - ), - ApplyMethod( - vertex.set_fill, None, 1, - run_time = run_time, - rate_func = squish_rate_func(smooth, *squish_range) - ), - ] - -class ShowRule(TeacherStudentsScene): - def construct(self): - new_edge = TextMobject("New edge") - new_vertex = TextMobject("New (lit) vertex") - new_vertex.next_to(new_edge, UP+RIGHT, MED_LARGE_BUFF) - new_region = TextMobject("New region") - new_region.next_to(new_edge, DOWN+RIGHT, MED_LARGE_BUFF) - VGroup(new_vertex, new_region).shift(RIGHT) - arrows = VGroup(*[ - Arrow( - new_edge.get_right(), mob.get_left(), - color = WHITE, - buff = SMALL_BUFF - ) - for mob in (new_vertex, new_region) - ]) - for word, arrow in zip(["Either", "or"], arrows): - word_mob = TextMobject(word) - word_mob.scale(0.65) - word_mob.next_to(ORIGIN, UP, SMALL_BUFF) - word_mob.rotate(arrow.get_angle()) - word_mob.shift(arrow.get_center()) - word_mob.set_color(GREEN) - arrow.add(word_mob) - new_vertex.set_color(YELLOW) - new_edge.set_color(BLUE) - new_region.set_color(RED) - rule = VGroup(new_edge, arrows, new_vertex, new_region) - rule.center().to_edge(UP) - - nine_total = TextMobject("(9 total)") - nine_total.next_to(new_edge, DOWN) - - self.play( - Animation(rule), - self.teacher.change, "raise_right_hand" - ) - self.change_student_modes( - *["confused"]*3, - look_at_arg = rule - ) - self.wait(2) - self.play( - Write(nine_total), - self.teacher.change, "happy", - ) - self.change_student_modes( - *["thinking"]*3, - look_at_arg = rule - ) - self.wait(3) - -class ConcludeFiveRegions(LightUpNodes): - def construct(self): - self.setup_configuration() - self.setup_regions() - self.setup_counters() - - self.describe_start_setup() - self.show_nine_lines_to_start() - self.show_five_to_lit_up_nodes() - self.relate_four_lines_to_regions() - self.conclude_about_five_regions() - - def describe_start_setup(self): - to_dim = VGroup(*it.chain(self.houses, self.utilities[1:])) - to_dim.set_stroke(width = 1) - to_dim.set_fill(opacity = 0) - - full_screen_rect = FullScreenFadeRectangle( - fill_color = LIGHT_GREY, - fill_opacity = 0.25, - ) - - self.play( - Indicate(self.v_count), - *self.get_lit_vertex_animations(self.utilities[0]) - ) - self.play( - FadeIn( - full_screen_rect, - rate_func = there_and_back, - remover = True, - ), - Indicate(self.f_count), - *list(map(Animation, self.mobjects)) - ) - self.wait() - - def show_nine_lines_to_start(self): - line_sets = self.get_straight_lines() - line_sets.target = VGroup() - for lines in line_sets: - lines.generate_target() - for line in lines.target: - line.rotate(-line.get_angle()) - line.set_width(1.5) - lines.target.arrange(DOWN) - line_sets.target.add(lines.target) - line_sets.target.arrange(DOWN) - line_sets.target.center() - line_sets.target.to_edge(RIGHT) - - for lines in line_sets: - self.play(LaggedStartMap(ShowCreation, lines, run_time = 1)) - self.play(MoveToTarget(lines)) - self.wait() - - ghost_lines = line_sets.copy() - ghost_lines.fade(0.9) - self.add(ghost_lines, line_sets) - self.side_lines = VGroup(*it.chain(*line_sets)) - - def show_five_to_lit_up_nodes(self): - side_lines = self.side_lines - lines = self.lines - vertices = VGroup(*it.chain(self.houses, self.utilities)) - line_indices = [0, 1, 4, 6, 7] - vertex_indices = [0, 1, 4, 5, 2] - - for li, vi in zip(line_indices, vertex_indices): - side_line = side_lines[li] - line = lines[li] - vertex = vertices[vi] - self.play(ReplacementTransform(side_line, line)) - self.play(*it.chain( - self.get_count_change_animations(1, 1, 0), - self.get_lit_vertex_animations(vertex), - )) - - def relate_four_lines_to_regions(self): - f_rect = SurroundingRectangle( - VGroup(self.count_titles[-1], self.f_count) - ) - on_screen_side_lines = VGroup(*[m for m in self.side_lines if m in self.mobjects]) - side_lines_rect = SurroundingRectangle(on_screen_side_lines) - side_lines_rect.set_color(WHITE) - - self.play(ShowCreation(side_lines_rect)) - self.wait() - self.play(ReplacementTransform(side_lines_rect, f_rect)) - self.play(FadeOut(f_rect)) - self.wait() - - def conclude_about_five_regions(self): - lines = self.lines - side_lines = self.side_lines - regions = self.regions[1:] - line_groups = self.line_groups - line_indices = [3, 5, 2] - objects = self.objects - - for region, line_group, li in zip(regions, line_groups, line_indices): - self.play(ReplacementTransform( - side_lines[li], lines[li] - )) - self.play( - FadeIn(region), - Animation(line_group), - Animation(objects), - *self.get_count_change_animations(0, 1, 1) - ) - self.wait() - - #Conclude - words = TextMobject("Last line must \\\\ introduce 5th region") - words.scale(0.8) - words.set_color(BLUE) - rect = SurroundingRectangle(self.f_count) - rect.set_color(BLUE) - words.next_to(rect, DOWN) - randy = Randolph().flip() - randy.scale(0.5) - randy.next_to(words, RIGHT, SMALL_BUFF, DOWN) - self.play(ShowCreation(rect), Write(words)) - self.play(FadeIn(randy)) - self.play(randy.change, "pondering") - self.play(Blink(randy)) - self.wait(2) - -class WhatsWrongWithFive(TeacherStudentsScene): - def construct(self): - self.student_says( - "What's wrong with \\\\ 5 regions?", - target_mode = "maybe" - ) - self.wait(2) - -class CyclesHaveAtLeastFour(UtilitiesPuzzleScene): - def construct(self): - self.setup_configuration() - houses, utilities = self.houses, self.utilities - vertices = VGroup( - houses[0], utilities[0], - houses[1], utilities[1], houses[0], - ) - lines = [ - VectorizedPoint(), - self.get_line(0, 0), - self.get_line(0, 1), - self.get_line(1, 1), - self.get_line(1, 0, self.objects.get_corner(DOWN+LEFT)), - ] - for line in lines[1::2]: - line.points = line.points[::-1] - arrows = VGroup() - for vertex in vertices: - vect = vertices.get_center() - vertex.get_center() - arrow = Vector(vect, color = WHITE) - arrow.next_to(vertex, -vect, buff = 0) - vertex.arrow = arrow - arrows.add(arrow) - word_strings = [ - "Start at a house", - "Go to a utility", - "Go to another house", - "Go to another utility", - "Back to the start", - ] - words = VGroup() - for word_string, arrow in zip(word_strings, arrows): - vect = arrow.get_vector()[1]*UP - word = TextMobject(word_string) - word.next_to(arrow.get_start(), -vect) - words.add(word) - - count = Integer(-1) - count.fade(1) - count.to_edge(UP) - - last_word = None - last_arrow = None - - for line, word, arrow in zip(lines, words, arrows): - anims = [] - for mob in last_word, last_arrow: - if mob: - anims.append(FadeOut(mob)) - new_count = Integer(count.number + 1) - new_count.move_to(count) - anims += [ - FadeIn(word), - GrowArrow(arrow), - ShowCreation(line), - FadeOut(count), - FadeIn(new_count), - ] - self.play(*anims) - self.wait() - last_word = word - last_arrow = arrow - count = new_count - self.wait(2) - -class FiveRegionsFourEdgesEachGraph(Scene): - CONFIG = { - "v_color" : WHITE, - "e_color" : YELLOW, - "f_colors" : (BLUE, RED_E, BLUE_E), - "n_edge_double_count_examples" : 6, - "random_seed" : 1, - } - def construct(self): - self.draw_squares() - self.transition_to_graph() - self.count_edges_per_region() - self.each_edges_has_two_regions() - self.ten_total_edges() - - def draw_squares(self): - words = VGroup( - TextMobject("5", "regions"), - TextMobject("4", "edges each"), - ) - words.arrange(DOWN) - words.to_edge(UP) - words[0][0].set_color(self.f_colors[0]) - words[1][0].set_color(self.e_color) - - squares = VGroup(*[Square() for x in range(5)]) - squares.scale(0.5) - squares.set_stroke(width = 0) - squares.set_fill(opacity = 1) - squares.set_color_by_gradient(*self.f_colors) - squares.arrange(RIGHT, buff = MED_LARGE_BUFF) - squares.next_to(words, DOWN, LARGE_BUFF) - all_edges = VGroup() - all_vertices = VGroup() - for square in squares: - corners = square.get_anchors()[:4] - square.edges = VGroup(*[ - Line(c1, c2, color = self.e_color) - for c1, c2 in adjacent_pairs(corners) - ]) - square.vertices = VGroup(*[ - Dot(color = self.v_color).move_to(c) - for c in corners - ]) - all_edges.add(*square.edges) - all_vertices.add(*square.vertices) - - self.play( - FadeIn(words[0]), - LaggedStartMap(FadeIn, squares, run_time = 1.5) - ) - self.play( - FadeIn(words[1]), - LaggedStartMap(ShowCreation, all_edges), - LaggedStartMap(GrowFromCenter, all_vertices), - ) - self.wait() - - self.add_foreground_mobjects(words) - self.set_variables_as_attrs(words, squares) - - def transition_to_graph(self): - squares = self.squares - words = self.words - - points = np.array([ - UP+LEFT, - UP+RIGHT, - DOWN+RIGHT, - DOWN+LEFT, - 3*(UP+RIGHT), - 3*(DOWN+LEFT), - 3*(DOWN+RIGHT), - ]) - points *= 0.75 - - regions = VGroup(*[ - Square().set_points_as_corners(points[indices]) - for indices in [ - [0, 1, 2, 3], - [0, 4, 2, 1], - [5, 0, 3, 2], - [5, 2, 4, 6], - [6, 4, 0, 5], - ] - ]) - regions.set_stroke(width = 0) - regions.set_fill(opacity = 1) - regions.set_color_by_gradient(*self.f_colors) - - all_edges = VGroup() - all_movers = VGroup() - for region, square in zip(regions, squares): - corners = region.get_anchors()[:4] - region.edges = VGroup(*[ - Line(c1, c2, color = self.e_color) - for c1, c2 in adjacent_pairs(corners) - ]) - all_edges.add(*region.edges) - region.vertices = VGroup(*[ - Dot(color = self.v_color).move_to(c) - for c in corners - ]) - mover = VGroup( - square, square.edges, square.vertices, - ) - mover.target = VGroup( - region, region.edges, region.vertices - ) - all_movers.add(mover) - - back_region = FullScreenFadeRectangle() - back_region.set_fill(regions[-1].get_color(), 0.5) - regions[-1].set_fill(opacity = 0) - back_region.add(regions[-1].copy().set_fill(BLACK, 1)) - back_region.edges = regions[-1].edges - - self.play( - FadeIn( - back_region, - rate_func = squish_rate_func(smooth, 0.7, 1), - run_time = 3, - ), - LaggedStartMap( - MoveToTarget, all_movers, - run_time = 3, - replace_mobject_with_target_in_scene = True, - ), - ) - self.wait(2) - - self.set_variables_as_attrs( - regions, all_edges, back_region, - graph = VGroup(*[m.target for m in all_movers]) - ) - - def count_edges_per_region(self): - all_edges = self.all_edges - back_region = self.back_region - regions = self.regions - graph = self.graph - all_vertices = VGroup(*[r.vertices for r in regions]) - - ghost_edges = all_edges.copy() - ghost_edges.set_stroke(LIGHT_GREY, 1) - - count = Integer(0) - count.scale(2) - count.next_to(graph, RIGHT, buff = 2) - count.set_fill(YELLOW, opacity = 0) - - last_region = VGroup(back_region, *regions[1:]) - last_region.add(all_edges) - - for region in list(regions[:-1]) + [back_region]: - self.play( - FadeIn(region), - Animation(ghost_edges), - FadeOut(last_region), - Animation(count), - Animation(all_vertices), - ) - for edge in region.edges: - new_count = Integer(count.number + 1) - new_count.replace(count, dim_to_match = 1) - new_count.set_color(count.get_color()) - self.play( - ShowCreation(edge), - FadeOut(count), - FadeIn(new_count), - run_time = 0.5 - ) - count = new_count - last_region = VGroup(region, region.edges) - self.wait() - self.add_foreground_mobjects(count) - self.play( - FadeOut(last_region), - Animation(ghost_edges), - Animation(all_vertices), - ) - - self.set_variables_as_attrs(count, ghost_edges, all_vertices) - - def each_edges_has_two_regions(self): - regions = list(self.regions[:-1]) + [self.back_region] - back_region = self.back_region - self.add_foreground_mobjects(self.ghost_edges, self.all_vertices) - - edge_region_pair_groups = [] - for r1, r2 in it.combinations(regions, 2): - for e1 in r1.edges: - for e2 in r2.edges: - diff = e1.get_center()-e2.get_center() - if get_norm(diff) < 0.01: - edge_region_pair_groups.append(VGroup( - e1, r1, r2 - )) - - for x in range(self.n_edge_double_count_examples): - edge, r1, r2 = random.choice(edge_region_pair_groups) - if r2 is back_region: - #Flip again, maybe you're still unlucky, maybe not - edge, r1, r2 = random.choice(edge_region_pair_groups) - self.play(ShowCreation(edge)) - self.add_foreground_mobjects(edge) - self.play(FadeIn(r1), run_time = 0.5) - self.play(FadeIn(r2), Animation(r1), run_time = 0.5) - self.wait(0.5) - self.play(*list(map(FadeOut, [r2, r1, edge])), run_time = 0.5) - self.remove_foreground_mobjects(edge) - - def ten_total_edges(self): - double_count = self.count - brace = Brace(double_count, UP) - words = brace.get_text("Double-counts \\\\ edges") - regions = self.regions - - edges = VGroup(*it.chain( - regions[0].edges, - regions[-1].edges, - [regions[1].edges[1]], - [regions[2].edges[3]], - )) - - count = Integer(0) - count.scale(2) - count.set_fill(WHITE, 0) - count.next_to(self.graph, LEFT, LARGE_BUFF) - - self.play( - GrowFromCenter(brace), - Write(words) - ) - self.wait() - for edge in edges: - new_count = Integer(count.number + 1) - new_count.replace(count, dim_to_match = 1) - self.play( - ShowCreation(edge), - FadeOut(count), - FadeIn(new_count), - run_time = 0.5 - ) - count = new_count - self.wait() - -class EulersFormulaForGeneralPlanarGraph(LightUpNodes, ThreeDScene): - CONFIG = { - "vertices_word" : "Vertices" - } - def construct(self): - self.setup_counters() - self.show_creation_of_graph() - self.show_formula() - self.transform_into_cube() - - def show_creation_of_graph(self): - points = np.array([ - UP+LEFT, - UP+RIGHT, - DOWN+RIGHT, - DOWN+LEFT, - 3*(UP+LEFT), - 3*(UP+RIGHT), - 3*(DOWN+RIGHT), - 3*(DOWN+LEFT), - ]) - points *= 0.75 - points += DOWN - vertices = VGroup(*list(map(Dot, points))) - vertices.set_color(YELLOW) - edges = VGroup(*[ - VGroup(*[ - Line(p1, p2, color = WHITE) - for p2 in points - ]) - for p1 in points - ]) - regions = self.get_cube_faces(points) - regions.set_stroke(width = 0) - regions.set_fill(opacity = 1) - regions.set_color_by_gradient(GREEN, RED, BLUE_E) - regions[-1].set_fill(opacity = 0) - - pairs = [ - (edges[0][1], vertices[1]), - (edges[1][2], vertices[2]), - (edges[2][6], vertices[6]), - (edges[6][5], vertices[5]), - (edges[5][1], regions[2]), - (edges[0][4], vertices[4]), - (edges[4][5], regions[1]), - (edges[0][3], vertices[3]), - (edges[3][2], regions[0]), - (edges[4][7], vertices[7]), - (edges[7][3], regions[4]), - (edges[7][6], regions[3]), - ] - - self.add_foreground_mobjects(vertices[0]) - self.wait() - for edge, obj in pairs: - anims = [ShowCreation(edge)] - if obj in vertices: - obj.save_state() - obj.move_to(edge.get_start()) - anims.append(ApplyMethod(obj.restore)) - anims += self.get_count_change_animations(1, 1, 0) - self.add_foreground_mobjects(obj) - else: - anims = [FadeIn(obj)] + anims - anims += self.get_count_change_animations(0, 1, 1) - self.play(*anims) - self.add_foreground_mobjects(edge) - self.wait() - - self.set_variables_as_attrs(edges, vertices, regions) - - def show_formula(self): - counts = VGroup(*self.counts) - count_titles = VGroup(*self.count_titles) - groups = [count_titles, counts] - - for group in groups: - group.symbols = VGroup(*list(map(TexMobject, ["-", "+", "="]))) - group.generate_target() - line = VGroup(*it.chain(*list(zip(group.target, group.symbols)))) - line.arrange(RIGHT) - line.to_edge(UP, buff = MED_SMALL_BUFF) - VGroup(counts.target, counts.symbols).shift(0.75*DOWN) - for mob in count_titles.target: - mob[-1].fade(1) - count_titles.symbols.shift(0.5*SMALL_BUFF*UP) - twos = VGroup(*[ - TexMobject("2").next_to(group.symbols, RIGHT) - for group in groups - ]) - twos.shift(0.5*SMALL_BUFF*UP) - - words = TextMobject("``Euler's characteristic formula''") - words.next_to(counts.target, DOWN) - words.shift(MED_LARGE_BUFF*RIGHT) - words.set_color(YELLOW) - - for group in groups: - self.play( - MoveToTarget(group), - Write(group.symbols) - ) - self.wait() - self.play(Write(twos)) - self.wait() - self.play(Write(words)) - self.wait() - - self.top_formula = VGroup(count_titles, count_titles.symbols, twos[0]) - self.bottom_formula = VGroup(counts, counts.symbols, twos[1]) - - def transform_into_cube(self): - regions = self.regions - points = np.array([ - UP+LEFT, - UP+RIGHT, - DOWN+RIGHT, - DOWN+LEFT, - UP+LEFT+2*IN, - UP+RIGHT+2*IN, - DOWN+RIGHT+2*IN, - DOWN+LEFT+2*IN, - ]) - cube = self.get_cube_faces(points) - cube.shift(OUT) - cube.rotate_in_place(np.pi/12, RIGHT) - cube.rotate_in_place(np.pi/6, UP) - cube.shift(MED_LARGE_BUFF*DOWN) - shade_in_3d(cube) - - for face, region in zip(cube, regions): - face.set_fill(region.get_color(), opacity = 0.8) - - self.remove(self.edges) - regions.set_stroke(WHITE, 3) - cube.set_stroke(WHITE, 3) - - new_formula = TexMobject("V - E + F = 2") - new_formula.to_edge(UP, buff = MED_SMALL_BUFF) - new_formula.align_to(self.bottom_formula, RIGHT) - - self.play(FadeOut(self.vertices)) - self.play(ReplacementTransform(regions, cube, run_time = 2)) - cube.sort(lambda p : -p[2]) - always_rotate(cube, axis=UP, about_point=ORIGIN) - self.add(cube) - self.wait(3) - self.play( - FadeOut(self.top_formula), - FadeIn(new_formula) - ) - self.wait(10) - - - ### - - def get_cube_faces(self, eight_points): - return VGroup(*[ - Square().set_points_as_corners(eight_points[indices]) - for indices in [ - [0, 1, 2, 3], - [0, 4, 5, 1], - [1, 5, 6, 2], - [2, 6, 7, 3], - [3, 7, 4, 0], - [4, 5, 6, 7], - ] - ]) - -class YouGaveFriendsAnImpossiblePuzzle(TeacherStudentsScene): - def construct(self): - self.student_says( - "You gave friends \\\\ an impossible puzzle?", - target_mode = "sassy", - ) - self.change_student_modes( - "angry", "sassy", "angry", - added_anims = [self.teacher.change, "happy"] - ) - self.wait(2) - -class FunnyStory(TeacherStudentsScene): - def construct(self): - self.teacher_says("Funny story", target_mode = "hooray") - self.wait() - self.change_student_modes( - *["happy"]*3, - added_anims = [RemovePiCreatureBubble( - self.teacher, - target_mode = "raise_right_hand" - )], - look_at_arg = UP+2*RIGHT - ) - self.wait(5) - -class QuestionWrapper(Scene): - def construct(self): - question = TextMobject( - "Where", "\\emph{specifically}", "does\\\\", - "this proof break down?", - ) - question.to_edge(UP) - question.set_color_by_tex("specifically", YELLOW) - screen_rect = ScreenRectangle(height = 5.5) - screen_rect.next_to(question, DOWN) - - self.play(ShowCreation(screen_rect)) - self.wait() - for word in question: - self.play(LaggedStartMap( - FadeIn, word, - run_time = 0.05*len(word) - )) - self.wait(0.05) - self.wait() - -class Homework(TeacherStudentsScene): - def construct(self): - self.teacher_says("Consider this \\\\ homework") - self.change_student_modes(*["pondering"]*3) - self.wait(2) - self.student_says( - "$V-E+F=0$ on \\\\ a torus!", - target_mode = "hooray" - ) - self.wait() - self.teacher_says("Not good enough!", target_mode = "surprised") - self.change_student_modes(*["confused"]*3) - self.wait(2) - -class WantToLearnMore(Scene): - def construct(self): - text = TextMobject("Want to learn more?") - self.play(Write(text)) - self.wait() - -class PatreonThanks(PatreonEndScreen): - CONFIG = { - "specific_patrons" : [ - "Randall Hunt", - "Desmos", - "Burt Humburg", - "CrypticSwarm", - "Juan Benet", - "David Kedmey", - "Ali Yahya", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Jordan Scales", - "Markus Persson", - "Egor Gumenuk", - "Yoni Nazarathy", - "Ryan Atallah", - "Joseph John Cox", - "Luc Ritchie", - "Onuralp Soylemez", - "John Bjorn Nelson", - "Yaw Etse", - "David Barbetta", - "Julio Cesar Campo Neto", - "Waleed Hamied", - "Oliver Steele", - "George Chiesa", - "supershabam", - "James Park", - "Samantha D. Suplee", - "Delton Ding", - "Thomas Tarler", - "Jonathan Eppele", - "Isak Hietala", - "1stViewMaths", - "Jacob Magnuson", - "Mark Govea", - "Dagan Harrington", - "Clark Gaebel", - "Eric Chow", - "Mathias Jansson", - "David Clark", - "Michael Gardner", - "Mads Elvheim", - "Erik Sundell", - "Awoo", - "Dr. David G. Stork", - "Tianyu Ge", - "Ted Suzman", - "Linh Tran", - "Andrew Busey", - "John Haley", - "Ankalagon", - "Eric Lavault", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Ryan Dahl", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ] - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/music_and_measure.py b/from_3b1b/old/music_and_measure.py deleted file mode 100644 index 02b0d1e8..00000000 --- a/from_3b1b/old/music_and_measure.py +++ /dev/null @@ -1,1533 +0,0 @@ -#!/usr/bin/env python - - -import numpy as np -import itertools as it -from copy import deepcopy -import sys -from fractions import Fraction, gcd - -from manimlib.imports import * -from .inventing_math import Underbrace - -import random - -MOVIE_PREFIX = "music_and_measure/" - -INTERVAL_RADIUS = 6 -NUM_INTERVAL_TICKS = 16 -TICK_STRETCH_FACTOR = 4 -INTERVAL_COLOR_PALETTE = [ - "yellow", - "green", - "skyblue", - "#AD1457", - "#6A1B9A", - "#26C6DA", - "#FF8F00", -] - -def rationals(): - curr = Fraction(1, 2) - numerator, denominator = 1, 2 - while True: - yield curr - if curr.numerator < curr.denominator - 1: - new_numerator = curr.numerator + 1 - while gcd(new_numerator, curr.denominator) != 1: - new_numerator += 1 - curr = Fraction(new_numerator, curr.denominator) - else: - curr = Fraction(1, curr.denominator + 1) - -def fraction_mobject(fraction): - n, d = fraction.numerator, fraction.denominator - return TexMobject("\\frac{%d}{%d}"%(n, d)) - -def continued_fraction(int_list): - if len(int_list) == 1: - return int_list[0] - return int_list[0] + Fraction(1, continued_fraction(int_list[1:])) - -def zero_to_one_interval(): - interval = NumberLine( - radius = INTERVAL_RADIUS, - interval_size = 2.0*INTERVAL_RADIUS/NUM_INTERVAL_TICKS - ) - interval.elongate_tick_at(-INTERVAL_RADIUS, TICK_STRETCH_FACTOR) - interval.elongate_tick_at(INTERVAL_RADIUS, TICK_STRETCH_FACTOR) - interval.add(TexMobject("0").shift(INTERVAL_RADIUS*LEFT+DOWN)) - interval.add(TexMobject("1").shift(INTERVAL_RADIUS*RIGHT+DOWN)) - return interval - -class LeftParen(Mobject): - def init_points(self): - self.add(TexMobject("(")) - self.center() - - def get_center(self): - return Mobject.get_center(self) + 0.04*LEFT - -class RightParen(Mobject): - def init_points(self): - self.add(TexMobject(")")) - self.center() - - def get_center(self): - return Mobject.get_center(self) + 0.04*RIGHT - - -class OpenInterval(Mobject): - def __init__(self, center_point = ORIGIN, width = 2, **kwargs): - digest_config(self, kwargs, locals()) - left = LeftParen().shift(LEFT*width/2) - right = RightParen().shift(RIGHT*width/2) - Mobject.__init__(self, left, right, **kwargs) - # scale_factor = width / 2.0 - # self.stretch(scale_factor, 0) - # self.stretch(0.5+0.5*scale_factor, 1) - self.shift(center_point) - -class Piano(ImageMobject): - CONFIG = { - "stroke_width" : 1, - "invert" : False, - "scale_factorue" : 0.5 - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - ImageMobject.__init__(self, "piano_keyboard") - jump = self.get_width()/24 - self.center() - self.half_note_jump = self.get_width()/24 - self.ivory_jump = self.get_width()/14 - - def split(self): - left = self.get_left()[0] - keys = [] - for count in range(14): - key = Mobject( - color = "white", - stroke_width = 1 - ) - x0 = left + count*self.ivory_jump - x1 = x0 + self.ivory_jump - key.add_points( - self.points[ - (self.points[:,0] > x0)*(self.points[:,0] < x1) - ] - ) - keys.append(key) - return keys - - -class Vibrate(Animation): - CONFIG = { - "num_periods" : 1, - "overtones" : 4, - "amplitude" : 0.5, - "radius" : INTERVAL_RADIUS, - "center" : ORIGIN, - "color" : "white", - "run_time" : 3.0, - "rate_func" : None - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - def func(x, t): - return sum([ - (self.amplitude/((k+1)**2.5))*np.sin(2*mult*t)*np.sin(k*mult*x) - for k in range(self.overtones) - for mult in [(self.num_periods+k)*np.pi] - ]) - self.func = func - Animation.__init__(self, Mobject1D(color = self.color), **kwargs) - - def interpolate_mobject(self, alpha): - self.mobject.reset_points() - epsilon = self.mobject.epsilon - self.mobject.add_points([ - [x*self.radius, self.func(x, alpha*self.run_time)+y, 0] - for x in np.arange(-1, 1, epsilon/self.radius) - for y in epsilon*np.arange(3) - ]) - self.mobject.shift(self.center) - - -class IntervalScene(NumberLineScene): - def construct(self): - self.number_line = UnitInterval() - self.displayed_numbers = [0, 1] - self.number_mobs = self.number_line.get_number_mobjects(*self.displayed_numbers) - self.add(self.number_line, *self.number_mobs) - - def show_all_fractions(self, - num_fractions = 27, - pause_time = 1.0, - remove_as_you_go = True): - shrink = not remove_as_you_go - for fraction, count in zip(rationals(), list(range(num_fractions))): - frac_mob, tick = self.add_fraction(fraction, shrink) - self.wait(pause_time) - if remove_as_you_go: - self.remove(frac_mob, tick) - - def add_fraction(self, fraction, shrink = False): - point = self.number_line.number_to_point(fraction) - tick_rad = self.number_line.tick_size*TICK_STRETCH_FACTOR - frac_mob = fraction_mobject(fraction) - if shrink: - scale_factor = 2.0/fraction.denominator - frac_mob.scale(scale_factor) - tick_rad *= scale_factor - frac_mob.shift(point + frac_mob.get_height()*UP) - tick = Line(point + DOWN*tick_rad, point + UP*tick_rad) - tick.set_color("yellow") - self.add(frac_mob, tick) - return frac_mob, tick - - def add_fraction_ticks(self, num_fractions = 1000, run_time = 0): - long_tick_size = self.number_line.tick_size*TICK_STRETCH_FACTOR - all_ticks = [] - for frac, count in zip(rationals(), list(range(num_fractions))): - point = self.number_line.number_to_point(frac) - tick_rad = 2.0*long_tick_size/frac.denominator - tick = Line(point+tick_rad*DOWN, point+tick_rad*UP) - tick.set_color("yellow") - all_ticks.append(tick) - all_ticks = Mobject(*all_ticks) - if run_time > 0: - self.play(ShowCreation(all_ticks)) - else: - self.add(all_ticks) - return all_ticks - - - def cover_fractions(self, - epsilon = 0.3, - num_fractions = 10, - run_time_per_interval = 0.5): - intervals = [] - lines = [] - num_intervals = 0 - all_rationals = rationals() - count = 0 - while True: - fraction = next(all_rationals) - count += 1 - if num_intervals >= num_fractions: - break - if fraction < self.number_line.left_num or fraction > self.number_line.right_num: - continue - num_intervals += 1 - interval, line = self.add_open_interval( - fraction, - epsilon / min(2**count, 2**30), - run_time = run_time_per_interval - ) - intervals.append(interval) - lines.append(line) - return intervals, lines - - def add_open_interval(self, num, width, color = None, run_time = 0): - spatial_width = width*self.number_line.unit_length_to_spatial_width - center_point = self.number_line.number_to_point(num) - open_interval = OpenInterval(center_point, spatial_width) - color = color or "yellow" - interval_line = Line( - center_point+spatial_width*LEFT/2, - center_point+spatial_width*RIGHT/2 - ) - interval_line.do_in_place(interval_line.sort_points, get_norm) - interval_line.set_color(color) - if run_time > 0: - squished_interval = deepcopy(open_interval).stretch_to_fit_width(0) - self.play( - Transform(squished_interval, open_interval), - ShowCreation(interval_line), - run_time = run_time - ) - self.remove(squished_interval) - self.add(open_interval, interval_line) - return open_interval, interval_line - - -class TwoChallenges(Scene): - def construct(self): - two_challenges = TextMobject("Two Challenges", size = "\\Huge").to_edge(UP) - one, two = list(map(TextMobject, ["1.", "2."])) - one.shift(UP).to_edge(LEFT) - two.shift(DOWN).to_edge(LEFT) - notes = ImageMobject("musical_notes").scale(0.3) - notes.next_to(one) - notes.set_color("blue") - measure = TextMobject("Measure Theory").next_to(two) - probability = TextMobject("Probability") - probability.next_to(measure).shift(DOWN+RIGHT) - integration = TexMobject("\\int") - integration.next_to(measure).shift(UP+RIGHT) - arrow_to_prob = Arrow(measure, probability) - arrow_to_int = Arrow(measure, integration) - for arrow in arrow_to_prob, arrow_to_int: - arrow.set_color("yellow") - - - self.add(two_challenges) - self.wait() - self.add(one, notes) - self.wait() - self.add(two, measure) - self.wait() - self.play(ShowCreation(arrow_to_int)) - self.add(integration) - self.wait() - self.play(ShowCreation(arrow_to_prob)) - self.add(probability) - self.wait() - -class MeasureTheoryToHarmony(IntervalScene): - def construct(self): - IntervalScene.construct(self) - self.cover_fractions() - self.wait() - all_mobs = Mobject(*self.mobjects) - all_mobs.sort_points() - self.clear() - radius = self.interval.radius - line = Line(radius*LEFT, radius*RIGHT).set_color("white") - self.play(DelayByOrder(Transform(all_mobs, line))) - self.clear() - self.play(Vibrate(rate_func = smooth)) - self.clear() - self.add(line) - self.wait() - - -class ChallengeOne(Scene): - def construct(self): - title = TextMobject("Challenge #1").to_edge(UP) - start_color = Color("blue") - colors = start_color.range_to("white", 6) - self.bottom_vibration = Vibrate( - num_periods = 1, run_time = 3.0, - center = DOWN, color = start_color - ) - top_vibrations = [ - Vibrate( - num_periods = freq, run_time = 3.0, - center = 2*UP, color = next(colors) - ) - for freq in [1, 2, 5.0/3, 4.0/3, 2] - ] - freq_220 = TextMobject("220 Hz") - freq_r220 = TextMobject("$r\\times$220 Hz") - freq_330 = TextMobject("1.5$\\times$220 Hz") - freq_sqrt2 = TextMobject("$\\sqrt{2}\\times$220 Hz") - freq_220.shift(1.5*DOWN) - for freq in freq_r220, freq_330, freq_sqrt2: - freq.shift(1.5*UP) - r_constraint = TexMobject("(1= 0.0) - z = 0.998*z + 0.001 - return np.log(np.true_divide( - 1.0, (np.true_divide(1.0, z) - 1) - )) - -def ReLU(z): - result = np.array(z) - result[result < 0] = 0 - return result - -def ReLU_prime(z): - return (np.array(z) > 0).astype('int') - -def get_pretrained_network(): - data_file = open(PRETRAINED_DATA_FILE, 'rb') - weights, biases = pickle.load(data_file, encoding='latin1') - sizes = [w.shape[1] for w in weights] - sizes.append(weights[-1].shape[0]) - network = Network(sizes) - network.weights = weights - network.biases = biases - return network - -def save_pretrained_network(epochs = 30, mini_batch_size = 10, eta = 3.0): - network = Network(sizes = DEFAULT_LAYER_SIZES) - training_data, validation_data, test_data = load_data_wrapper() - network.SGD(training_data, epochs, mini_batch_size, eta) - weights_and_biases = (network.weights, network.biases) - data_file = open(PRETRAINED_DATA_FILE, mode = 'w') - pickle.dump(weights_and_biases, data_file) - data_file.close() - -def test_network(): - network = get_pretrained_network() - training_data, validation_data, test_data = load_data_wrapper() - n_right, n_wrong = 0, 0 - for test_in, test_out in test_data: - if np.argmax(network.feedforward(test_in)) == test_out: - n_right += 1 - else: - n_wrong += 1 - print((n_right, n_wrong, float(n_right)/(n_right + n_wrong))) - -def layer_to_image_array(layer): - w = int(np.ceil(np.sqrt(len(layer)))) - if len(layer) < w**2: - layer = np.append(layer, np.zeros(w**2 - len(layer))) - layer = layer.reshape((w, w)) - # return Image.fromarray((255*layer).astype('uint8')) - return (255*layer).astype('int') - -def maximizing_input(network, layer_index, layer_vect, n_steps = 100, seed_guess = None): - pre_sig_layer_vect = sigmoid_inverse(layer_vect) - weights, biases = network.weights, network.biases - # guess = np.random.random(weights[0].shape[1]) - if seed_guess is not None: - pre_sig_guess = sigmoid_inverse(seed_guess.flatten()) - else: - pre_sig_guess = np.random.randn(weights[0].shape[1]) - norms = [] - for step in range(n_steps): - activations = network.get_activation_of_all_layers( - sigmoid(pre_sig_guess), layer_index - ) - jacobian = np.diag(sigmoid_prime(pre_sig_guess).flatten()) - for W, a, b in zip(weights, activations, biases): - jacobian = np.dot(W, jacobian) - a = a.reshape((a.size, 1)) - sp = sigmoid_prime(np.dot(W, a) + b) - jacobian = np.dot(np.diag(sp.flatten()), jacobian) - gradient = np.dot( - np.array(layer_vect).reshape((1, len(layer_vect))), - jacobian - ).flatten() - norm = get_norm(gradient) - if norm == 0: - break - norms.append(norm) - old_pre_sig_guess = np.array(pre_sig_guess) - pre_sig_guess += 0.1*gradient - print(get_norm(old_pre_sig_guess - pre_sig_guess)) - print("") - return sigmoid(pre_sig_guess) - -def save_organized_images(n_images_per_number = 10): - training_data, validation_data, test_data = load_data_wrapper() - image_map = dict([(k, []) for k in range(10)]) - for im, output_arr in training_data: - if min(list(map(len, list(image_map.values())))) >= n_images_per_number: - break - value = int(np.argmax(output_arr)) - if len(image_map[value]) >= n_images_per_number: - continue - image_map[value].append(im) - data_file = open(IMAGE_MAP_DATA_FILE, mode = 'wb') - pickle.dump(image_map, data_file) - data_file.close() - -def get_organized_images(): - data_file = open(IMAGE_MAP_DATA_FILE, mode = 'r') - image_map = pickle.load(data_file, encoding='latin1') - data_file.close() - return image_map - -# def maximizing_input(network, layer_index, layer_vect): -# if layer_index == 0: -# return layer_vect -# W = network.weights[layer_index-1] -# n = max(W.shape) -# W_square = np.identity(n) -# W_square[:W.shape[0], :W.shape[1]] = W -# zeros = np.zeros((n - len(layer_vect), 1)) -# vect = layer_vect.reshape((layer_vect.shape[0], 1)) -# vect = np.append(vect, zeros, axis = 0) -# b = np.append(network.biases[layer_index-1], zeros, axis = 0) -# prev_vect = np.dot( -# np.linalg.inv(W_square), -# (sigmoid_inverse(vect) - b) -# ) -# # print layer_vect, sigmoid(np.dot(W, prev_vect)+b) -# print W.shape -# prev_vect = prev_vect[:W.shape[1]] -# prev_vect /= np.max(np.abs(prev_vect)) -# # prev_vect /= 1.1 -# return maximizing_input(network, layer_index - 1, prev_vect) diff --git a/from_3b1b/old/nn/part1.py b/from_3b1b/old/nn/part1.py deleted file mode 100644 index 2f93ef41..00000000 --- a/from_3b1b/old/nn/part1.py +++ /dev/null @@ -1,4626 +0,0 @@ -import sys -import os.path -import cv2 - -sys.path.append(os.path.join(os.path.dirname(__file__), '..')) -from manimlib.imports import * - -import warnings -warnings.warn(""" - Warning: This file makes use of - ContinualAnimation, which has since - been deprecated -""") - - -from nn.network import * - -#force_skipping -#revert_to_original_skipping_status - - -DEFAULT_GAUSS_BLUR_CONFIG = { - "ksize" : (5, 5), - "sigmaX" : 10, - "sigmaY" : 10, -} - -DEFAULT_CANNY_CONFIG = { - "threshold1" : 100, - "threshold2" : 200, -} - - -def get_edges(image_array): - blurred = cv2.GaussianBlur( - image_array, - **DEFAULT_GAUSS_BLUR_CONFIG - ) - edges = cv2.Canny( - blurred, - **DEFAULT_CANNY_CONFIG - ) - return edges - -class WrappedImage(Group): - CONFIG = { - "rect_kwargs" : { - "color" : BLUE, - "buff" : SMALL_BUFF, - } - } - def __init__(self, image_mobject, **kwargs): - Group.__init__(self, **kwargs) - rect = SurroundingRectangle( - image_mobject, **self.rect_kwargs - ) - self.add(rect, image_mobject) - -class PixelsAsSquares(VGroup): - CONFIG = { - "height" : 2, - } - def __init__(self, image_mobject, **kwargs): - VGroup.__init__(self, **kwargs) - for row in image_mobject.pixel_array: - for rgba in row: - square = Square( - stroke_width = 0, - fill_opacity = rgba[3]/255.0, - fill_color = rgba_to_color(rgba/255.0), - ) - self.add(square) - self.arrange_in_grid( - *image_mobject.pixel_array.shape[:2], - buff = 0 - ) - self.replace(image_mobject) - -class PixelsFromVect(PixelsAsSquares): - def __init__(self, vect, **kwargs): - PixelsAsSquares.__init__(self, - ImageMobject(layer_to_image_array(vect)), - **kwargs - ) - -class MNistMobject(WrappedImage): - def __init__(self, vect, **kwargs): - WrappedImage.__init__(self, - ImageMobject(layer_to_image_array(vect)), - **kwargs - ) - -class NetworkMobject(VGroup): - CONFIG = { - "neuron_radius" : 0.15, - "neuron_to_neuron_buff" : MED_SMALL_BUFF, - "layer_to_layer_buff" : LARGE_BUFF, - "neuron_stroke_color" : BLUE, - "neuron_stroke_width" : 3, - "neuron_fill_color" : GREEN, - "edge_color" : LIGHT_GREY, - "edge_stroke_width" : 2, - "edge_propogation_color" : YELLOW, - "edge_propogation_time" : 1, - "max_shown_neurons" : 16, - "brace_for_large_layers" : True, - "average_shown_activation_of_large_layer" : True, - "include_output_labels" : False, - } - def __init__(self, neural_network, **kwargs): - VGroup.__init__(self, **kwargs) - self.neural_network = neural_network - self.layer_sizes = neural_network.sizes - self.add_neurons() - self.add_edges() - - def add_neurons(self): - layers = VGroup(*[ - self.get_layer(size) - for size in self.layer_sizes - ]) - layers.arrange(RIGHT, buff = self.layer_to_layer_buff) - self.layers = layers - self.add(self.layers) - if self.include_output_labels: - self.add_output_labels() - - def get_layer(self, size): - layer = VGroup() - n_neurons = size - if n_neurons > self.max_shown_neurons: - n_neurons = self.max_shown_neurons - neurons = VGroup(*[ - Circle( - radius = self.neuron_radius, - stroke_color = self.neuron_stroke_color, - stroke_width = self.neuron_stroke_width, - fill_color = self.neuron_fill_color, - fill_opacity = 0, - ) - for x in range(n_neurons) - ]) - neurons.arrange( - DOWN, buff = self.neuron_to_neuron_buff - ) - for neuron in neurons: - neuron.edges_in = VGroup() - neuron.edges_out = VGroup() - layer.neurons = neurons - layer.add(neurons) - - if size > n_neurons: - dots = TexMobject("\\vdots") - dots.move_to(neurons) - VGroup(*neurons[:len(neurons) // 2]).next_to( - dots, UP, MED_SMALL_BUFF - ) - VGroup(*neurons[len(neurons) // 2:]).next_to( - dots, DOWN, MED_SMALL_BUFF - ) - layer.dots = dots - layer.add(dots) - if self.brace_for_large_layers: - brace = Brace(layer, LEFT) - brace_label = brace.get_tex(str(size)) - layer.brace = brace - layer.brace_label = brace_label - layer.add(brace, brace_label) - - return layer - - def add_edges(self): - self.edge_groups = VGroup() - for l1, l2 in zip(self.layers[:-1], self.layers[1:]): - edge_group = VGroup() - for n1, n2 in it.product(l1.neurons, l2.neurons): - edge = self.get_edge(n1, n2) - edge_group.add(edge) - n1.edges_out.add(edge) - n2.edges_in.add(edge) - self.edge_groups.add(edge_group) - self.add_to_back(self.edge_groups) - - def get_edge(self, neuron1, neuron2): - return Line( - neuron1.get_center(), - neuron2.get_center(), - buff = self.neuron_radius, - stroke_color = self.edge_color, - stroke_width = self.edge_stroke_width, - ) - - def get_active_layer(self, layer_index, activation_vector): - layer = self.layers[layer_index].deepcopy() - self.activate_layer(layer, activation_vector) - return layer - - def activate_layer(self, layer, activation_vector): - n_neurons = len(layer.neurons) - av = activation_vector - def arr_to_num(arr): - return (np.sum(arr > 0.1) / float(len(arr)))**(1./3) - - if len(av) > n_neurons: - if self.average_shown_activation_of_large_layer: - indices = np.arange(n_neurons) - indices *= int(len(av)/n_neurons) - indices = list(indices) - indices.append(len(av)) - av = np.array([ - arr_to_num(av[i1:i2]) - for i1, i2 in zip(indices[:-1], indices[1:]) - ]) - else: - av = np.append( - av[:n_neurons/2], - av[-n_neurons/2:], - ) - for activation, neuron in zip(av, layer.neurons): - neuron.set_fill( - color = self.neuron_fill_color, - opacity = activation - ) - return layer - - def activate_layers(self, input_vector): - activations = self.neural_network.get_activation_of_all_layers(input_vector) - for activation, layer in zip(activations, self.layers): - self.activate_layer(layer, activation) - - def deactivate_layers(self): - all_neurons = VGroup(*it.chain(*[ - layer.neurons - for layer in self.layers - ])) - all_neurons.set_fill(opacity = 0) - return self - - def get_edge_propogation_animations(self, index): - edge_group_copy = self.edge_groups[index].copy() - edge_group_copy.set_stroke( - self.edge_propogation_color, - width = 1.5*self.edge_stroke_width - ) - return [ShowCreationThenDestruction( - edge_group_copy, - run_time = self.edge_propogation_time, - lag_ratio = 0.5 - )] - - def add_output_labels(self): - self.output_labels = VGroup() - for n, neuron in enumerate(self.layers[-1].neurons): - label = TexMobject(str(n)) - label.set_height(0.75*neuron.get_height()) - label.move_to(neuron) - label.shift(neuron.get_width()*RIGHT) - self.output_labels.add(label) - self.add(self.output_labels) - -class MNistNetworkMobject(NetworkMobject): - CONFIG = { - "neuron_to_neuron_buff" : SMALL_BUFF, - "layer_to_layer_buff" : 1.5, - "edge_stroke_width" : 1, - "include_output_labels" : True, - } - - def __init__(self, **kwargs): - network = get_pretrained_network() - NetworkMobject.__init__(self, network, **kwargs) - -class NetworkScene(Scene): - CONFIG = { - "layer_sizes" : [8, 6, 6, 4], - "network_mob_config" : {}, - } - def setup(self): - self.add_network() - - def add_network(self): - self.network = Network(sizes = self.layer_sizes) - self.network_mob = NetworkMobject( - self.network, - **self.network_mob_config - ) - self.add(self.network_mob) - - def feed_forward(self, input_vector, false_confidence = False, added_anims = None): - if added_anims is None: - added_anims = [] - activations = self.network.get_activation_of_all_layers( - input_vector - ) - if false_confidence: - i = np.argmax(activations[-1]) - activations[-1] *= 0 - activations[-1][i] = 1.0 - for i, activation in enumerate(activations): - self.show_activation_of_layer(i, activation, added_anims) - added_anims = [] - - def show_activation_of_layer(self, layer_index, activation_vector, added_anims = None): - if added_anims is None: - added_anims = [] - layer = self.network_mob.layers[layer_index] - active_layer = self.network_mob.get_active_layer( - layer_index, activation_vector - ) - anims = [Transform(layer, active_layer)] - if layer_index > 0: - anims += self.network_mob.get_edge_propogation_animations( - layer_index-1 - ) - anims += added_anims - self.play(*anims) - - def remove_random_edges(self, prop = 0.9): - for edge_group in self.network_mob.edge_groups: - for edge in list(edge_group): - if np.random.random() < prop: - edge_group.remove(edge) - -def make_transparent(image_mob): - alpha_vect = np.array( - image_mob.pixel_array[:,:,0], - dtype = 'uint8' - ) - image_mob.set_color(WHITE) - image_mob.pixel_array[:,:,3] = alpha_vect - return image_mob - -############################### - -class ExampleThrees(PiCreatureScene): - def construct(self): - self.show_initial_three() - self.show_alternate_threes() - self.resolve_remaining_threes() - self.show_alternate_digits() - - def show_initial_three(self): - randy = self.pi_creature - - self.three_mobs = self.get_three_mobs() - three_mob = self.three_mobs[0] - three_mob_copy = three_mob[1].copy() - three_mob_copy.sort(lambda p : np.dot(p, DOWN+RIGHT)) - - braces = VGroup(*[Brace(three_mob, v) for v in (LEFT, UP)]) - brace_labels = VGroup(*[ - brace.get_text("28px") - for brace in braces - ]) - - bubble = randy.get_bubble(height = 4, width = 6) - three_mob.generate_target() - three_mob.target.set_height(1) - three_mob.target.next_to(bubble[-1].get_left(), RIGHT, LARGE_BUFF) - arrow = Arrow(LEFT, RIGHT, color = BLUE) - arrow.next_to(three_mob.target, RIGHT) - real_three = TexMobject("3") - real_three.set_height(0.8) - real_three.next_to(arrow, RIGHT) - - self.play( - FadeIn(three_mob[0]), - LaggedStartMap(FadeIn, three_mob[1]) - ) - self.wait() - self.play( - LaggedStartMap( - DrawBorderThenFill, three_mob_copy, - run_time = 3, - stroke_color = WHITE, - remover = True, - ), - randy.change, "sassy", - *it.chain( - list(map(GrowFromCenter, braces)), - list(map(FadeIn, brace_labels)) - ) - ) - self.wait() - self.play( - ShowCreation(bubble), - MoveToTarget(three_mob), - FadeOut(braces), - FadeOut(brace_labels), - randy.change, "pondering" - ) - self.play( - ShowCreation(arrow), - Write(real_three) - ) - self.wait() - - self.bubble = bubble - self.arrow = arrow - self.real_three = real_three - - def show_alternate_threes(self): - randy = self.pi_creature - - three = self.three_mobs[0] - three.generate_target() - three.target[0].set_fill(opacity = 0, family = False) - for square in three.target[1]: - yellow_rgb = color_to_rgb(YELLOW) - square_rgb = color_to_rgb(square.get_fill_color()) - square.set_fill( - rgba_to_color(yellow_rgb*square_rgb), - opacity = 0.5 - ) - - alt_threes = VGroup(*self.three_mobs[1:]) - alt_threes.arrange(DOWN) - alt_threes.set_height(FRAME_HEIGHT - 2) - alt_threes.to_edge(RIGHT) - - for alt_three in alt_threes: - self.add(alt_three) - self.wait(0.5) - self.play( - randy.change, "plain", - *list(map(FadeOut, [ - self.bubble, self.arrow, self.real_three - ])) + [MoveToTarget(three)] - ) - for alt_three in alt_threes[:2]: - self.play(three.replace, alt_three) - self.wait() - for moving_three in three, alt_threes[1]: - moving_three.generate_target() - moving_three.target.next_to(alt_threes, LEFT, LARGE_BUFF) - moving_three.target[0].set_stroke(width = 0) - moving_three.target[1].space_out_submobjects(1.5) - self.play(MoveToTarget( - moving_three, lag_ratio = 0.5 - )) - self.play( - Animation(randy), - moving_three.replace, randy.eyes[1], - moving_three.scale_in_place, 0.7, - run_time = 2, - lag_ratio = 0.5, - ) - self.play( - Animation(randy), - FadeOut(moving_three) - ) - - self.remaining_threes = [alt_threes[0], alt_threes[2]] - - def resolve_remaining_threes(self): - randy = self.pi_creature - - left_three, right_three = self.remaining_threes - equals = TexMobject("=") - equals.move_to(self.arrow) - for three, vect in (left_three, LEFT), (right_three, RIGHT): - three.generate_target() - three.target.set_height(1) - three.target.next_to(equals, vect) - - self.play( - randy.change, "thinking", - ShowCreation(self.bubble), - MoveToTarget(left_three), - MoveToTarget(right_three), - Write(equals), - ) - self.wait() - - self.equals = equals - - def show_alternate_digits(self): - randy = self.pi_creature - cross = Cross(self.equals) - cross.stretch_to_fit_height(0.5) - three = self.remaining_threes[1] - - image_map = get_organized_images() - arrays = [image_map[k][0] for k in range(8, 4, -1)] - alt_mobs = [ - WrappedImage( - PixelsAsSquares(ImageMobject(layer_to_image_array(arr))), - color = LIGHT_GREY, - buff = 0 - ).replace(three) - for arr in arrays - ] - - self.play( - randy.change, "sassy", - Transform(three, alt_mobs[0]), - ShowCreation(cross) - ) - self.wait() - for mob in alt_mobs[1:]: - self.play(Transform(three, mob)) - self.wait() - - ###### - - def create_pi_creature(self): - return Randolph().to_corner(DOWN+LEFT) - - def get_three_mobs(self): - three_arrays = get_organized_images()[3][:4] - three_mobs = VGroup() - for three_array in three_arrays: - im_mob = ImageMobject( - layer_to_image_array(three_array), - height = 4, - ) - pixel_mob = PixelsAsSquares(im_mob) - three_mob = WrappedImage( - pixel_mob, - color = LIGHT_GREY, - buff = 0 - ) - three_mobs.add(three_mob) - return three_mobs - -class BrainAndHow(Scene): - def construct(self): - brain = SVGMobject(file_name = "brain") - brain.set_height(2) - brain.set_fill(LIGHT_GREY) - brain_outline = brain.copy() - brain_outline.set_fill(opacity = 0) - brain_outline.set_stroke(BLUE_B, 3) - - how = TextMobject("How?!?") - how.scale(2) - how.next_to(brain, UP) - - self.add(brain) - self.play(Write(how)) - for x in range(2): - self.play( - ShowPassingFlash( - brain_outline, - time_width = 0.5, - run_time = 2 - ) - ) - self.wait() - -class WriteAProgram(Scene): - def construct(self): - three_array = get_organized_images()[3][0] - im_mob = ImageMobject(layer_to_image_array(three_array)) - three = PixelsAsSquares(im_mob) - three.sort(lambda p : np.dot(p, DOWN+RIGHT)) - three.set_height(6) - three.next_to(ORIGIN, LEFT) - three_rect = SurroundingRectangle( - three, - color = BLUE, - buff = SMALL_BUFF - ) - - numbers = VGroup() - for square in three: - rgb = square.fill_rgb - num = DecimalNumber( - square.fill_rgb[0], - num_decimal_places = 1 - ) - num.set_stroke(width = 1) - color = rgba_to_color(1 - (rgb + 0.2)/1.2) - num.set_color(color) - num.set_width(0.7*square.get_width()) - num.move_to(square) - numbers.add(num) - - arrow = Arrow(LEFT, RIGHT, color = BLUE) - arrow.next_to(three, RIGHT) - - choices = VGroup(*[TexMobject(str(n)) for n in range(10)]) - choices.arrange(DOWN) - choices.set_height(FRAME_HEIGHT - 1) - choices.next_to(arrow, RIGHT) - - self.play( - LaggedStartMap(DrawBorderThenFill, three), - ShowCreation(three_rect) - ) - self.play(Write(numbers)) - self.play( - ShowCreation(arrow), - LaggedStartMap(FadeIn, choices), - ) - - rect = SurroundingRectangle(choices[0], buff = SMALL_BUFF) - q_mark = TexMobject("?") - q_mark.next_to(rect, RIGHT) - self.play(ShowCreation(rect)) - for n in 8, 1, 5, 3: - self.play( - rect.move_to, choices[n], - MaintainPositionRelativeTo(q_mark, rect) - ) - self.wait(1) - choice = choices[3] - choices.remove(choice) - choice.add(rect) - self.play( - choice.scale, 1.5, - choice.next_to, arrow, RIGHT, - FadeOut(choices), - FadeOut(q_mark), - ) - self.wait(2) - -class LayOutPlan(TeacherStudentsScene, NetworkScene): - def setup(self): - TeacherStudentsScene.setup(self) - NetworkScene.setup(self) - self.remove(self.network_mob) - - def construct(self): - self.force_skipping() - - self.show_words() - self.show_network() - self.show_math() - self.ask_about_layers() - self.show_learning() - self.show_videos() - - def show_words(self): - words = VGroup( - TextMobject("Machine", "learning").set_color(GREEN), - TextMobject("Neural network").set_color(BLUE), - ) - words.next_to(self.teacher.get_corner(UP+LEFT), UP) - words[0].save_state() - words[0].shift(DOWN) - words[0].fade(1) - - self.play( - words[0].restore, - self.teacher.change, "raise_right_hand", - self.get_student_changes("pondering", "erm", "sassy") - ) - self.play( - words[0].shift, MED_LARGE_BUFF*UP, - FadeIn(words[1]), - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = words - ) - self.play(words.to_corner, UP+RIGHT) - - self.words = words - - def show_network(self): - network_mob = self.network_mob - network_mob.next_to(self.students, UP) - - self.play( - ReplacementTransform( - VGroup(self.words[1].copy()), - network_mob.layers - ), - self.get_student_changes( - *["confused"]*3, - lag_ratio = 0 - ), - self.teacher.change, "plain", - run_time = 1 - ) - self.play(ShowCreation( - network_mob.edge_groups, - lag_ratio = 0.5, - run_time = 2, - rate_func=linear, - )) - in_vect = np.random.random(self.network.sizes[0]) - self.feed_forward(in_vect) - - def show_math(self): - equation = TexMobject( - "\\textbf{a}_{l+1}", "=", - "\\sigma(", - "W_l", "\\textbf{a}_l", "+", "b_l", - ")" - ) - equation.set_color_by_tex_to_color_map({ - "\\textbf{a}" : GREEN, - }) - equation.move_to(self.network_mob.get_corner(UP+RIGHT)) - equation.to_edge(UP) - - self.play(Write(equation, run_time = 2)) - self.wait() - - self.equation = equation - - def ask_about_layers(self): - self.student_says( - "Why the layers?", - student_index = 2, - bubble_kwargs = {"direction" : LEFT} - ) - self.wait() - self.play(RemovePiCreatureBubble(self.students[2])) - - def show_learning(self): - word = self.words[0][1].copy() - rect = SurroundingRectangle(word, color = YELLOW) - self.network_mob.neuron_fill_color = YELLOW - - layer = self.network_mob.layers[-1] - activation = np.zeros(len(layer.neurons)) - activation[1] = 1.0 - active_layer = self.network_mob.get_active_layer( - -1, activation - ) - word_group = VGroup(word, rect) - word_group.generate_target() - word_group.target.move_to(self.equation, LEFT) - word_group.target[0].set_color(YELLOW) - word_group.target[1].set_stroke(width = 0) - - self.play(ShowCreation(rect)) - self.play( - Transform(layer, active_layer), - FadeOut(self.equation), - MoveToTarget(word_group), - ) - for edge_group in reversed(self.network_mob.edge_groups): - edge_group.generate_target() - for edge in edge_group.target: - edge.set_stroke( - YELLOW, - width = 4*np.random.random()**2 - ) - self.play(MoveToTarget(edge_group)) - self.wait() - - self.learning_word = word - - def show_videos(self): - network_mob = self.network_mob - learning = self.learning_word - structure = TextMobject("Structure") - structure.set_color(YELLOW) - videos = VGroup(*[ - VideoIcon().set_fill(RED) - for x in range(2) - ]) - videos.set_height(1.5) - videos.arrange(RIGHT, buff = LARGE_BUFF) - videos.next_to(self.students, UP, LARGE_BUFF) - - network_mob.generate_target() - network_mob.target.set_height(0.8*videos[0].get_height()) - network_mob.target.move_to(videos[0]) - learning.generate_target() - learning.target.next_to(videos[1], UP) - structure.next_to(videos[0], UP) - structure.shift(0.5*SMALL_BUFF*UP) - - self.revert_to_original_skipping_status() - self.play( - MoveToTarget(network_mob), - MoveToTarget(learning) - ) - self.play( - DrawBorderThenFill(videos[0]), - FadeIn(structure), - self.get_student_changes(*["pondering"]*3) - ) - self.wait() - self.play(DrawBorderThenFill(videos[1])) - self.wait() - -class PreviewMNistNetwork(NetworkScene): - CONFIG = { - "n_examples" : 15, - "network_mob_config" : {}, - } - def construct(self): - self.remove_random_edges(0.7) #Remove? - - training_data, validation_data, test_data = load_data_wrapper() - for data in test_data[:self.n_examples]: - self.feed_in_image(data[0]) - - def feed_in_image(self, in_vect): - image = PixelsFromVect(in_vect) - image.next_to(self.network_mob, LEFT, LARGE_BUFF, UP) - image.shift_onto_screen() - image_rect = SurroundingRectangle(image, color = BLUE) - start_neurons = self.network_mob.layers[0].neurons.copy() - start_neurons.set_stroke(WHITE, width = 0) - start_neurons.set_fill(WHITE, 0) - - self.play(FadeIn(image), FadeIn(image_rect)) - self.feed_forward(in_vect, added_anims = [ - self.get_image_to_layer_one_animation(image, start_neurons) - ]) - n = np.argmax([ - neuron.get_fill_opacity() - for neuron in self.network_mob.layers[-1].neurons - ]) - rect = SurroundingRectangle(VGroup( - self.network_mob.layers[-1].neurons[n], - self.network_mob.output_labels[n], - )) - self.play(ShowCreation(rect)) - self.reset_display(rect, image, image_rect) - - def reset_display(self, answer_rect, image, image_rect): - self.play(FadeOut(answer_rect)) - self.play( - FadeOut(image), - FadeOut(image_rect), - self.network_mob.deactivate_layers, - ) - - def get_image_to_layer_one_animation(self, image, start_neurons): - image_mover = VGroup(*[ - pixel.copy() - for pixel in image - if pixel.fill_rgb[0] > 0.1 - ]) - return Transform( - image_mover, start_neurons, - remover = True, - run_time = 1, - ) - - ### - - def add_network(self): - self.network_mob = MNistNetworkMobject(**self.network_mob_config) - self.network = self.network_mob.neural_network - self.add(self.network_mob) - -class AlternateNeuralNetworks(PiCreatureScene): - def construct(self): - morty = self.pi_creature - examples = VGroup( - VGroup( - TextMobject("Convolutional neural network"), - TextMobject("Good for image recognition"), - ), - VGroup( - TextMobject("Long short-term memory network"), - TextMobject("Good for speech recognition"), - ) - ) - for ex in examples: - arrow = Arrow(LEFT, RIGHT, color = BLUE) - ex[0].next_to(arrow, LEFT) - ex[1].next_to(arrow, RIGHT) - ex.submobjects.insert(1, arrow) - examples.set_width(FRAME_WIDTH - 1) - examples.next_to(morty, UP).to_edge(RIGHT) - - maybe_words = TextMobject("Maybe future videos?") - maybe_words.scale(0.8) - maybe_words.next_to(morty, UP) - maybe_words.to_edge(RIGHT) - maybe_words.set_color(YELLOW) - - self.play( - Write(examples[0], run_time = 2), - morty.change, "raise_right_hand" - ) - self.wait() - self.play( - examples[0].shift, MED_LARGE_BUFF*UP, - FadeIn(examples[1], lag_ratio = 0.5), - ) - self.wait() - self.play( - examples.shift, UP, - FadeIn(maybe_words), - morty.change, "maybe" - ) - self.wait(2) - -class PlainVanillaWrapper(Scene): - def construct(self): - title = TextMobject("Plain vanilla") - subtitle = TextMobject("(aka ``multilayer perceptron'')") - title.scale(1.5) - title.to_edge(UP) - subtitle.next_to(title, DOWN) - - self.add(title) - self.wait(2) - self.play(Write(subtitle, run_time = 2)) - self.wait(2) - -class NotPerfectAddOn(Scene): - def construct(self): - words = TextMobject("Not perfect!") - words.scale(1.5) - arrow = Arrow(UP+RIGHT, DOWN+LEFT, color = RED) - words.set_color(RED) - arrow.to_corner(DOWN+LEFT) - words.next_to(arrow, UP+RIGHT) - - self.play( - Write(words), - ShowCreation(arrow), - run_time = 1 - ) - self.wait(2) - -class MoreAThanI(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "More \\\\ A than I", - target_mode = "hesitant" - ) - self.change_student_modes("sad", "erm", "tired") - self.wait(2) - -class BreakDownName(Scene): - def construct(self): - self.ask_questions() - self.show_neuron() - - def ask_questions(self): - name = TextMobject("Neural", "network") - name.to_edge(UP) - q1 = TextMobject( - "What are \\\\ the ", "neuron", "s?", - arg_separator = "" - ) - q2 = TextMobject("How are \\\\ they connected?") - q1.next_to(name[0].get_bottom(), DOWN, buff = LARGE_BUFF) - q2.next_to(name[1].get_bottom(), DOWN+RIGHT, buff = LARGE_BUFF) - a1 = Arrow(q1.get_top(), name[0].get_bottom()) - a2 = Arrow(q2.get_top(), name.get_corner(DOWN+RIGHT)) - VGroup(q1, a1).set_color(BLUE) - VGroup(q2, a2).set_color(YELLOW) - - randy = Randolph().to_corner(DOWN+LEFT) - brain = SVGMobject(file_name = "brain") - brain.set_fill(LIGHT_GREY, opacity = 0) - brain.replace(randy.eyes, dim_to_match = 1) - - self.add(name) - self.play(randy.change, "pondering") - self.play( - brain.set_height, 2, - brain.shift, 2*UP, - brain.set_fill, None, 1, - randy.look, UP - ) - brain_outline = brain.copy() - brain_outline.set_fill(opacity = 0) - brain_outline.set_stroke(BLUE_B, 3) - self.play( - ShowPassingFlash( - brain_outline, - time_width = 0.5, - run_time = 2 - ) - ) - self.play(Blink(randy)) - self.wait() - self.play( - Write(q1, run_time = 1), - ShowCreation(a1), - name[0].set_color, q1.get_color(), - ) - self.play( - Write(q2, run_time = 1), - ShowCreation(a2), - name[1].set_color, q2.get_color() - ) - self.wait(2) - - self.play(*list(map(FadeOut, [ - name, randy, brain, - q2, a1, a2, - q1[0], q1[2] - ]))) - - self.neuron_word = q1[1] - - def show_neuron(self): - neuron_word = TextMobject("Neuron") - arrow = TexMobject("\\rightarrow") - arrow.shift(LEFT) - description = TextMobject("Thing that holds a number") - neuron_word.set_color(BLUE) - neuron_word.next_to(arrow, LEFT) - neuron_word.shift(0.5*SMALL_BUFF*UP) - description.next_to(arrow, RIGHT) - - neuron = Circle(radius = 0.35, color = BLUE) - neuron.next_to(neuron_word, UP, MED_LARGE_BUFF) - num = TexMobject("0.2") - num.set_width(0.7*neuron.get_width()) - num.move_to(neuron) - num.save_state() - num.move_to(description.get_right()) - num.set_fill(opacity = 1) - - self.play( - ReplacementTransform(self.neuron_word, neuron_word), - ShowCreation(neuron) - ) - self.play( - ShowCreation(arrow), - Write(description, run_time = 1) - ) - self.wait() - self.play( - neuron.set_fill, None, 0.2, - num.restore - ) - self.wait() - for value in 0.8, 0.4, 0.1, 0.5: - mob = TexMobject(str(value)) - mob.replace(num) - self.play( - neuron.set_fill, None, value, - Transform(num, mob) - ) - self.wait() - -class IntroduceEachLayer(PreviewMNistNetwork): - CONFIG = { - "network_mob_config" : { - "neuron_stroke_color" : WHITE, - "neuron_stroke_width" : 2, - "neuron_fill_color" : WHITE, - "average_shown_activation_of_large_layer" : False, - "edge_propogation_color" : YELLOW, - "edge_propogation_time" : 2, - } - } - def construct(self): - self.setup_network_mob() - self.break_up_image_as_neurons() - self.show_activation_of_one_neuron() - self.transform_into_full_network() - self.show_output_layer() - self.show_hidden_layers() - self.show_propogation() - - def setup_network_mob(self): - self.remove(self.network_mob) - - def break_up_image_as_neurons(self): - self.image_map = get_organized_images() - image = self.image_map[9][0] - image_mob = PixelsFromVect(image) - image_mob.set_height(4) - image_mob.next_to(ORIGIN, LEFT) - rect = SurroundingRectangle(image_mob, color = BLUE) - neurons = VGroup() - for pixel in image_mob: - pixel.set_fill(WHITE, opacity = pixel.fill_rgb[0]) - neuron = Circle( - color = WHITE, - stroke_width = 1, - radius = pixel.get_width()/2 - ) - neuron.move_to(pixel) - neuron.set_fill(WHITE, pixel.get_fill_opacity()) - neurons.add(neuron) - neurons.scale_in_place(1.2) - neurons.space_out_submobjects(1.3) - neurons.to_edge(DOWN) - - braces = VGroup(*[Brace(neurons, vect) for vect in (LEFT, UP)]) - labels = VGroup(*[ - brace.get_tex("28", buff = SMALL_BUFF) - for brace in braces - ]) - - equation = TexMobject("28", "\\times", "28", "=", "784") - equation.next_to(neurons, RIGHT, LARGE_BUFF, UP) - - self.corner_image = MNistMobject(image) - self.corner_image.to_corner(UP+LEFT) - - self.add(image_mob, rect) - self.wait() - self.play( - ReplacementTransform(image_mob, neurons), - FadeOut(rect), - FadeIn(braces), - FadeIn(labels), - ) - self.wait() - self.play( - ReplacementTransform(labels[0].copy(), equation[0]), - Write(equation[1]), - ReplacementTransform(labels[1].copy(), equation[2]), - Write(equation[3]), - Write(equation[4]), - ) - self.wait() - - self.neurons = neurons - self.braces = braces - self.brace_labels = labels - self.num_pixels_equation = equation - self.image_vect = image - - def show_activation_of_one_neuron(self): - neurons = self.neurons - numbers = VGroup() - example_neuron = None - example_num = None - for neuron in neurons: - o = neuron.get_fill_opacity() - num = DecimalNumber(o, num_decimal_places = 1) - num.set_width(0.7*neuron.get_width()) - num.move_to(neuron) - if o > 0.8: - num.set_fill(BLACK) - numbers.add(num) - if o > 0.25 and o < 0.75 and example_neuron is None: - example_neuron = neuron - example_num = num - example_neuron.save_state() - example_num.save_state() - example_neuron.generate_target() - example_neuron.target.set_height(1.5) - example_neuron.target.next_to(neurons, RIGHT) - example_num.target = DecimalNumber( - example_neuron.get_fill_opacity() - ) - example_num.target.move_to(example_neuron.target) - - def change_activation(num): - self.play( - example_neuron.set_fill, None, num, - ChangingDecimal( - example_num, - lambda a : example_neuron.get_fill_opacity(), - ), - UpdateFromFunc( - example_num, - lambda m : m.set_fill( - BLACK if example_neuron.get_fill_opacity() > 0.8 else WHITE - ) - ) - ) - - self.play(LaggedStartMap(FadeIn, numbers)) - self.play( - MoveToTarget(example_neuron), - MoveToTarget(example_num) - ) - self.wait() - curr_opacity = example_neuron.get_fill_opacity() - for num in 0.3, 0.01, 1.0, curr_opacity: - change_activation(num) - self.wait() - - rect = SurroundingRectangle(example_num, color = YELLOW) - activation = TextMobject("``Activation''") - activation.next_to(example_neuron, RIGHT) - activation.set_color(rect.get_color()) - self.play(ShowCreation(rect)) - self.play(Write(activation, run_time = 1)) - self.wait() - change_activation(1.0) - self.wait() - change_activation(0.2) - self.wait() - - self.play( - example_neuron.restore, - example_num.restore, - FadeOut(activation), - FadeOut(rect), - ) - self.play(FadeOut(numbers)) - - def transform_into_full_network(self): - network_mob = self.network_mob - neurons = self.neurons - layer = network_mob.layers[0] - layer.save_state() - layer.rotate(np.pi/2) - layer.center() - layer.brace_label.rotate_in_place(-np.pi/2) - n = network_mob.max_shown_neurons/2 - - rows = VGroup(*[ - VGroup(*neurons[28*i:28*(i+1)]) - for i in range(28) - ]) - - self.play( - FadeOut(self.braces), - FadeOut(self.brace_labels), - FadeOut(VGroup(*self.num_pixels_equation[:-1])) - ) - - self.play(rows.space_out_submobjects, 1.2) - self.play( - rows.arrange, RIGHT, buff = SMALL_BUFF, - path_arc = np.pi/2, - run_time = 2 - ) - self.play( - ReplacementTransform( - VGroup(*neurons[:n]), - VGroup(*layer.neurons[:n]), - ), - ReplacementTransform( - VGroup(*neurons[n:-n]), - layer.dots, - ), - ReplacementTransform( - VGroup(*neurons[-n:]), - VGroup(*layer.neurons[-n:]), - ), - ) - self.play( - ReplacementTransform( - self.num_pixels_equation[-1], - layer.brace_label - ), - FadeIn(layer.brace), - ) - self.play(layer.restore, FadeIn(self.corner_image)) - self.wait() - for edge_group, layer in zip(network_mob.edge_groups, network_mob.layers[1:]): - self.play( - LaggedStartMap(FadeIn, layer, run_time = 1), - ShowCreation(edge_group), - ) - self.wait() - - def show_output_layer(self): - layer = self.network_mob.layers[-1] - labels = self.network_mob.output_labels - rect = SurroundingRectangle( - VGroup(layer, labels) - ) - neuron = layer.neurons[-1] - neuron.set_fill(WHITE, 0) - label = labels[-1] - for mob in neuron, label: - mob.save_state() - mob.generate_target() - neuron.target.scale_in_place(4) - neuron.target.shift(1.5*RIGHT) - label.target.scale(1.5) - label.target.next_to(neuron.target, RIGHT) - - activation = DecimalNumber(0) - activation.move_to(neuron.target) - - def change_activation(num): - self.play( - neuron.set_fill, None, num, - ChangingDecimal( - activation, - lambda a : neuron.get_fill_opacity(), - ), - UpdateFromFunc( - activation, - lambda m : m.set_fill( - BLACK if neuron.get_fill_opacity() > 0.8 else WHITE - ) - ) - ) - - self.play(ShowCreation(rect)) - self.play(LaggedStartMap(FadeIn, labels)) - self.wait() - self.play( - MoveToTarget(neuron), - MoveToTarget(label), - ) - self.play(FadeIn(activation)) - for num in 0.5, 0.38, 0.97: - change_activation(num) - self.wait() - self.play( - neuron.restore, - neuron.set_fill, None, 1, - label.restore, - FadeOut(activation), - FadeOut(rect), - ) - self.wait() - - def show_hidden_layers(self): - hidden_layers = VGroup(*self.network_mob.layers[1:3]) - rect = SurroundingRectangle(hidden_layers, color = YELLOW) - name = TextMobject("``Hidden layers''") - name.next_to(rect, UP, SMALL_BUFF) - name.set_color(YELLOW) - q_marks = VGroup() - for layer in hidden_layers: - for neuron in layer.neurons: - q_mark = TextMobject("?") - q_mark.set_height(0.8*neuron.get_height()) - q_mark.move_to(neuron) - q_marks.add(q_mark) - q_marks.set_color_by_gradient(BLUE, YELLOW) - q_mark = TextMobject("?").scale(4) - q_mark.move_to(hidden_layers) - q_mark.set_color(YELLOW) - q_marks.add(q_mark) - - self.play( - ShowCreation(rect), - Write(name) - ) - self.wait() - self.play(Write(q_marks)) - self.wait() - self.play( - FadeOut(q_marks), - Animation(q_marks[-1].copy()) - ) - - def show_propogation(self): - self.revert_to_original_skipping_status() - self.remove_random_edges(0.7) - self.feed_forward(self.image_vect) - -class DiscussChoiceForHiddenLayers(TeacherStudentsScene): - def construct(self): - network_mob = MNistNetworkMobject( - layer_to_layer_buff = 2.5, - neuron_stroke_color = WHITE, - ) - network_mob.set_height(4) - network_mob.to_edge(UP, buff = LARGE_BUFF) - layers = VGroup(*network_mob.layers[1:3]) - rects = VGroup(*list(map(SurroundingRectangle, layers))) - self.add(network_mob) - - two_words = TextMobject("2 hidden layers") - two_words.set_color(YELLOW) - sixteen_words = TextMobject("16 neurons each") - sixteen_words.set_color(MAROON_B) - for words in two_words, sixteen_words: - words.next_to(rects, UP) - - neurons_anim = LaggedStartMap( - Indicate, - VGroup(*it.chain(*[layer.neurons for layer in layers])), - rate_func = there_and_back, - scale_factor = 2, - color = MAROON_B, - ) - - self.play( - ShowCreation(rects), - Write(two_words, run_time = 1), - self.teacher.change, "raise_right_hand", - ) - self.wait() - self.play( - FadeOut(rects), - ReplacementTransform(two_words, sixteen_words), - neurons_anim - ) - self.wait() - self.play(self.teacher.change, "shruggie") - self.change_student_modes("erm", "confused", "sassy") - self.wait() - self.student_says( - "Why 2 \\\\ layers?", - student_index = 1, - bubble_kwargs = {"direction" : RIGHT}, - run_time = 1, - target_mode = "raise_left_hand", - ) - self.play(self.teacher.change, "happy") - self.wait() - self.student_says( - "Why 16?", - student_index = 0, - run_time = 1, - ) - self.play(neurons_anim, run_time = 3) - self.play( - self.teacher.change, "shruggie", - RemovePiCreatureBubble(self.students[0]), - ) - self.wait() - -class MoreHonestMNistNetworkPreview(IntroduceEachLayer): - CONFIG = { - "network_mob_config" : { - "edge_propogation_time" : 1.5, - } - } - def construct(self): - PreviewMNistNetwork.construct(self) - - def get_image_to_layer_one_animation(self, image, start_neurons): - neurons = VGroup() - for pixel in image: - neuron = Circle( - radius = pixel.get_width()/2, - stroke_width = 1, - stroke_color = WHITE, - fill_color = WHITE, - fill_opacity = pixel.fill_rgb[0] - ) - neuron.move_to(pixel) - neurons.add(neuron) - neurons.scale(1.2) - neurons.next_to(image, DOWN) - n = len(start_neurons) - point = VectorizedPoint(start_neurons.get_center()) - target = VGroup(*it.chain( - start_neurons[:n/2], - [point.copy() for x in range(len(neurons)-n)], - start_neurons[n/2:], - )) - mover = image.copy() - self.play(Transform(mover, neurons)) - return Transform( - mover, target, - run_time = 2, - lag_ratio = 0.5, - remover = True - ) - -class AskAboutPropogationAndTraining(TeacherStudentsScene): - def construct(self): - self.student_says( - "How does one layer \\\\ influence the next?", - student_index = 0, - run_time = 1 - ) - self.wait() - self.student_says( - "How does \\\\ training work?", - student_index = 2, - run_time = 1 - ) - self.wait(3) - -class AskAboutLayers(PreviewMNistNetwork): - def construct(self): - self.play( - self.network_mob.scale, 0.8, - self.network_mob.to_edge, DOWN, - ) - - question = TextMobject("Why the", "layers?") - question.to_edge(UP) - neuron_groups = [ - layer.neurons - for layer in self.network_mob.layers - ] - arrows = VGroup(*[ - Arrow( - question[1].get_bottom(), - group.get_top() - ) - for group in neuron_groups - ]) - rects = list(map(SurroundingRectangle, neuron_groups[1:3])) - - self.play( - Write(question, run_time = 1), - LaggedStartMap( - GrowFromPoint, arrows, - lambda a : (a, a.get_start()), - run_time = 2 - ) - ) - self.wait() - self.play(*list(map(ShowCreation, rects))) - self.wait() - -class BreakUpMacroPatterns(IntroduceEachLayer): - CONFIG = { - "camera_config" : {"background_opacity" : 1}, - "prefixes" : [ - "nine", "eight", "four", - "upper_loop", "right_line", - "lower_loop", "horizontal_line", - "upper_left_line" - ] - } - def construct(self): - self.setup_network_mob() - self.setup_needed_patterns() - self.setup_added_patterns() - self.show_nine() - self.show_eight() - self.show_four() - self.show_second_to_last_layer() - self.show_upper_loop_activation() - self.show_what_learning_is_required() - - def setup_needed_patterns(self): - prefixes = self.prefixes - vects = [ - np.array(Image.open( - get_full_raster_image_path("handwritten_" + p), - ))[:,:,0].flatten()/255.0 - for p in prefixes - ] - mobjects = list(map(MNistMobject, vects)) - for mob in mobjects: - image = mob[1] - self.make_transparent(image) - for prefix, mob in zip(prefixes, mobjects): - setattr(self, prefix, mob) - - def setup_added_patterns(self): - image_map = get_organized_images() - two, three, five = mobs = [ - MNistMobject(image_map[n][0]) - for n in (2, 3, 5) - ] - self.added_patterns = VGroup() - for mob in mobs: - for i, j in it.product([0, 14], [0, 14]): - pattern = mob.deepcopy() - pa = pattern[1].pixel_array - temp = np.array(pa[i:i+14,j:j+14,:], dtype = 'uint8') - pa[:,:] = 0 - pa[i:i+14,j:j+14,:] = temp - self.make_transparent(pattern[1]) - pattern[1].set_color(random_bright_color()) - self.added_patterns.add(pattern) - self.image_map = image_map - - def show_nine(self): - nine = self.nine - upper_loop = self.upper_loop - right_line = self.right_line - equation = self.get_equation(nine, upper_loop, right_line) - equation.to_edge(UP) - equation.shift(LEFT) - - parts = [upper_loop[1], right_line[1]] - for mob, color in zip(parts, [YELLOW, RED]): - mob.set_color(color) - mob.save_state() - mob.move_to(nine) - right_line[1].pixel_array[:14,:,3] = 0 - - self.play(FadeIn(nine)) - self.wait() - self.play(*list(map(FadeIn, parts))) - self.wait() - self.play( - Write(equation[1]), - upper_loop[1].restore, - FadeIn(upper_loop[0]) - ) - self.wait() - self.play( - Write(equation[3]), - right_line[1].restore, - FadeIn(right_line[0]), - ) - self.wait() - - self.nine_equation = equation - - def show_eight(self): - eight = self.eight - upper_loop = self.upper_loop.deepcopy() - lower_loop = self.lower_loop - lower_loop[1].set_color(GREEN) - - equation = self.get_equation(eight, upper_loop, lower_loop) - equation.next_to(self.nine_equation, DOWN) - - lower_loop[1].save_state() - lower_loop[1].move_to(eight[1]) - - self.play( - FadeIn(eight), - Write(equation[1]), - ) - self.play(ReplacementTransform( - self.upper_loop.copy(), - upper_loop - )) - self.wait() - self.play(FadeIn(lower_loop[1])) - self.play( - Write(equation[3]), - lower_loop[1].restore, - FadeIn(lower_loop[0]), - ) - self.wait() - - self.eight_equation = equation - - def show_four(self): - four = self.four - upper_left_line = self.upper_left_line - upper_left_line[1].set_color(BLUE) - horizontal_line = self.horizontal_line - horizontal_line[1].set_color(MAROON_B) - right_line = self.right_line.deepcopy() - equation = self.get_equation(four, right_line, upper_left_line, horizontal_line) - equation.next_to( - self.eight_equation, DOWN, aligned_edge = LEFT - ) - - self.play( - FadeIn(four), - Write(equation[1]) - ) - self.play(ReplacementTransform( - self.right_line.copy(), right_line - )) - self.play(LaggedStartMap( - FadeIn, VGroup(*equation[3:]) - )) - self.wait(2) - - self.four_equation = equation - - def show_second_to_last_layer(self): - everything = VGroup(*it.chain( - self.nine_equation, - self.eight_equation, - self.four_equation, - )) - patterns = VGroup( - self.upper_loop, - self.lower_loop, - self.right_line, - self.upper_left_line, - self.horizontal_line, - *self.added_patterns[:11] - ) - for pattern in patterns: - pattern.add_to_back( - pattern[1].copy().set_color(BLACK, alpha = 1) - ) - everything.remove(*patterns) - network_mob = self.network_mob - layer = network_mob.layers[-2] - patterns.generate_target() - for pattern, neuron in zip(patterns.target, layer.neurons): - pattern.set_height(neuron.get_height()) - pattern.next_to(neuron, RIGHT, SMALL_BUFF) - for pattern in patterns[5:]: - pattern.fade(1) - - self.play(*list(map(FadeOut, everything))) - self.play( - FadeIn( - network_mob, - lag_ratio = 0.5, - run_time = 3, - ), - MoveToTarget(patterns) - ) - self.wait(2) - - self.patterns = patterns - - def show_upper_loop_activation(self): - neuron = self.network_mob.layers[-2].neurons[0] - words = TextMobject("Upper loop neuron...maybe...") - words.scale(0.8) - words.next_to(neuron, UP) - words.shift(RIGHT) - rect = SurroundingRectangle(VGroup( - neuron, self.patterns[0] - )) - nine = self.nine - upper_loop = self.upper_loop.copy() - upper_loop.remove(upper_loop[0]) - upper_loop.replace(nine) - nine.add(upper_loop) - nine.to_corner(UP+LEFT) - self.remove_random_edges(0.7) - self.network.get_activation_of_all_layers = lambda v : [ - np.zeros(784), - sigmoid(6*(np.random.random(16)-0.5)), - np.array([1, 0, 1] + 13*[0]), - np.array(9*[0] + [1]) - ] - - self.play(FadeIn(nine)) - self.play( - ShowCreation(rect), - Write(words) - ) - self.add_foreground_mobject(self.patterns) - self.feed_forward(np.random.random(784)) - self.wait(2) - - def show_what_learning_is_required(self): - edge_group = self.network_mob.edge_groups[-1].copy() - edge_group.set_stroke(YELLOW, 4) - for x in range(3): - self.play(LaggedStartMap( - ShowCreationThenDestruction, edge_group, - run_time = 3 - )) - self.wait() - - ###### - - def get_equation(self, *mobs): - equation = VGroup( - mobs[0], TexMobject("=").scale(2), - *list(it.chain(*[ - [m, TexMobject("+").scale(2)] - for m in mobs[1:-1] - ])) + [mobs[-1]] - ) - equation.arrange(RIGHT) - return equation - - def make_transparent(self, image_mob): - return make_transparent(image_mob) - alpha_vect = np.array( - image_mob.pixel_array[:,:,0], - dtype = 'uint8' - ) - image_mob.set_color(WHITE) - image_mob.pixel_array[:,:,3] = alpha_vect - return image_mob - -class GenerallyLoopyPattern(Scene): - def construct(self): - image_map = get_organized_images() - images = list(map(MNistMobject, it.chain( - image_map[8], image_map[9], - ))) - random.shuffle(images) - - for image in images: - image.to_corner(DOWN+RIGHT) - self.add(image) - self.wait(0.2) - self.remove(image) - -class HowWouldYouRecognizeSubcomponent(TeacherStudentsScene): - def construct(self): - self.student_says( - "Okay, but recognizing loops \\\\", - "is just as hard!", - target_mode = "sassy" - ) - self.play( - self.teacher.change, "guilty" - ) - self.wait() - -class BreakUpMicroPatterns(BreakUpMacroPatterns): - CONFIG = { - "prefixes" : [ - "loop", - "loop_edge1", - "loop_edge2", - "loop_edge3", - "loop_edge4", - "loop_edge5", - "right_line", - "right_line_edge1", - "right_line_edge2", - "right_line_edge3", - ] - } - def construct(self): - self.setup_network_mob() - self.setup_needed_patterns() - - self.break_down_loop() - self.break_down_long_line() - - def break_down_loop(self): - loop = self.loop - loop[0].set_color(WHITE) - edges = Group(*[ - getattr(self, "loop_edge%d"%d) - for d in range(1, 6) - ]) - colors = color_gradient([BLUE, YELLOW, RED], 5) - for edge, color in zip(edges, colors): - for mob in edge: - mob.set_color(color) - loop.generate_target() - edges.generate_target() - for edge in edges: - edge[0].set_stroke(width = 0) - edge.save_state() - edge[1].set_opacity(0) - equation = self.get_equation(loop.target, *edges.target) - equation.set_width(FRAME_WIDTH - 1) - equation.to_edge(UP) - symbols = VGroup(*equation[1::2]) - - randy = Randolph() - randy.to_corner(DOWN+LEFT) - - self.add(randy) - self.play( - FadeIn(loop), - randy.change, "pondering", loop - ) - self.play(Blink(randy)) - self.wait() - self.play(LaggedStartMap( - ApplyMethod, edges, - lambda e : (e.restore,), - run_time = 4 - )) - self.wait() - self.play( - MoveToTarget(loop, run_time = 2), - MoveToTarget(edges, run_time = 2), - Write(symbols), - randy.change, "happy", equation, - ) - self.wait() - - self.loop_equation = equation - self.randy = randy - - def break_down_long_line(self): - randy = self.randy - line = self.right_line - line[0].set_color(WHITE) - edges = Group(*[ - getattr(self, "right_line_edge%d"%d) - for d in range(1, 4) - ]) - colors = Color(MAROON_B).range_to(PURPLE, 3) - for edge, color in zip(edges, colors): - for mob in edge: - mob.set_color(color) - equation = self.get_equation(line, *edges) - equation.set_height(self.loop_equation.get_height()) - equation.next_to( - self.loop_equation, DOWN, MED_LARGE_BUFF, LEFT - ) - image_map = get_organized_images() - digits = VGroup(*[ - MNistMobject(image_map[n][1]) - for n in (1, 4, 7) - ]) - digits.arrange(RIGHT) - digits.next_to(randy, RIGHT) - - self.revert_to_original_skipping_status() - self.play( - FadeIn(line), - randy.change, "hesitant", line - ) - self.play(Blink(randy)) - self.play(LaggedStartMap(FadeIn, digits)) - self.wait() - self.play( - LaggedStartMap(FadeIn, Group(*equation[1:])), - randy.change, "pondering", equation - ) - self.wait(3) - -class SecondLayerIsLittleEdgeLayer(IntroduceEachLayer): - CONFIG = { - "camera_config" : { - "background_opacity" : 1, - }, - "network_mob_config" : { - "layer_to_layer_buff" : 2, - "edge_propogation_color" : YELLOW, - } - } - def construct(self): - self.setup_network_mob() - self.setup_activations_and_nines() - - self.describe_second_layer() - self.show_propogation() - self.ask_question() - - def setup_network_mob(self): - self.network_mob.scale(0.7) - self.network_mob.to_edge(DOWN) - self.remove_random_edges(0.7) - - def setup_activations_and_nines(self): - layers = self.network_mob.layers - nine_im, loop_im, line_im = images = [ - Image.open(get_full_raster_image_path("handwritten_%s"%s)) - for s in ("nine", "upper_loop", "right_line") - ] - nine_array, loop_array, line_array = [ - np.array(im)[:,:,0]/255.0 - for im in images - ] - self.nine = MNistMobject(nine_array.flatten()) - self.nine.set_height(1.5) - self.nine[0].set_color(WHITE) - make_transparent(self.nine[1]) - self.nine.next_to(layers[0].neurons, UP) - - self.activations = self.network.get_activation_of_all_layers( - nine_array.flatten() - ) - self.activations[-2] = np.array([1, 0, 1] + 13*[0]) - - - self.edge_colored_nine = Group() - nine_pa = self.nine[1].pixel_array - n, k = 6, 4 - colors = color_gradient([BLUE, YELLOW, RED, MAROON_B, GREEN], 10) - for i, j in it.product(list(range(n)), list(range(k))): - mob = ImageMobject(np.zeros((28, 28, 4), dtype = 'uint8')) - mob.replace(self.nine[1]) - pa = mob.pixel_array - color = colors[(k*i + j)%(len(colors))] - rgb = (255*color_to_rgb(color)).astype('uint8') - pa[:,:,:3] = rgb - i0, i1 = 1+(28/n)*i, 1+(28/n)*(i+1) - j0, j1 = (28/k)*j, (28/k)*(j+1) - pa[i0:i1,j0:j1,3] = nine_pa[i0:i1,j0:j1,3] - self.edge_colored_nine.add(mob) - self.edge_colored_nine.next_to(layers[1], UP) - - loop, line = [ - ImageMobject(layer_to_image_array(array.flatten())) - for array in (loop_array, line_array) - ] - for mob, color in (loop, YELLOW), (line, RED): - make_transparent(mob) - mob.set_color(color) - mob.replace(self.nine[1]) - line.pixel_array[:14,:,:] = 0 - - self.pattern_colored_nine = Group(loop, line) - self.pattern_colored_nine.next_to(layers[2], UP) - - for mob in self.edge_colored_nine, self.pattern_colored_nine: - mob.align_to(self.nine[1], UP) - - def describe_second_layer(self): - layer = self.network_mob.layers[1] - rect = SurroundingRectangle(layer) - words = TextMobject("``Little edge'' layer?") - words.next_to(rect, UP, MED_LARGE_BUFF) - words.set_color(YELLOW) - - self.play( - ShowCreation(rect), - Write(words, run_time = 2) - ) - self.wait() - self.play(*list(map(FadeOut, [rect, words]))) - - def show_propogation(self): - nine = self.nine - edge_colored_nine = self.edge_colored_nine - pattern_colored_nine = self.pattern_colored_nine - activations = self.activations - network_mob = self.network_mob - layers = network_mob.layers - edge_groups = network_mob.edge_groups.copy() - edge_groups.set_stroke(YELLOW, 4) - - v_nine = PixelsAsSquares(nine[1]) - neurons = VGroup() - for pixel in v_nine: - neuron = Circle( - radius = pixel.get_width()/2, - stroke_color = WHITE, - stroke_width = 1, - fill_color = WHITE, - fill_opacity = pixel.get_fill_opacity(), - ) - neuron.rotate(3*np.pi/4) - neuron.move_to(pixel) - neurons.add(neuron) - neurons.set_height(2) - neurons.space_out_submobjects(1.2) - neurons.next_to(network_mob, LEFT) - self.set_neurons_target(neurons, layers[0]) - - pattern_colored_nine.save_state() - pattern_colored_nine.move_to(edge_colored_nine) - edge_colored_nine.save_state() - edge_colored_nine.move_to(nine[1]) - for mob in edge_colored_nine, pattern_colored_nine: - for submob in mob: - submob.set_opacity(0) - - active_layers = [ - network_mob.get_active_layer(i, a) - for i, a in enumerate(activations) - ] - - def activate_layer(i): - self.play( - ShowCreationThenDestruction( - edge_groups[i-1], - run_time = 2, - lag_ratio = 0.5 - ), - FadeIn(active_layers[i]) - ) - - - self.play(FadeIn(nine)) - self.play(ReplacementTransform(v_nine, neurons)) - self.play(MoveToTarget( - neurons, - remover = True, - lag_ratio = 0.5, - run_time = 2 - )) - - activate_layer(1) - self.play(edge_colored_nine.restore) - self.separate_parts(edge_colored_nine) - self.wait() - - activate_layer(2) - self.play(pattern_colored_nine.restore) - self.separate_parts(pattern_colored_nine) - - activate_layer(3) - self.wait(2) - - def ask_question(self): - question = TextMobject( - "Does the network \\\\ actually do this?" - ) - question.to_edge(LEFT) - later = TextMobject("We'll get back \\\\ to this") - later.to_corner(UP+LEFT) - later.set_color(BLUE) - arrow = Arrow(later.get_bottom(), question.get_top()) - arrow.set_color(BLUE) - - self.play(Write(question, run_time = 2)) - self.wait() - self.play( - FadeIn(later), - GrowFromPoint(arrow, arrow.get_start()) - ) - self.wait() - - ### - - def set_neurons_target(self, neurons, layer): - neurons.generate_target() - n = len(layer.neurons)/2 - Transform( - VGroup(*neurons.target[:n]), - VGroup(*layer.neurons[:n]), - ).update(1) - Transform( - VGroup(*neurons.target[-n:]), - VGroup(*layer.neurons[-n:]), - ).update(1) - Transform( - VGroup(*neurons.target[n:-n]), - VectorizedPoint(layer.get_center()) - ).update(1) - - def separate_parts(self, image_group): - vects = compass_directions(len(image_group), UP) - image_group.generate_target() - for im, vect in zip(image_group.target, vects): - im.shift(MED_SMALL_BUFF*vect) - self.play(MoveToTarget( - image_group, - rate_func = there_and_back, - lag_ratio = 0.5, - run_time = 2, - )) - -class EdgeDetection(Scene): - CONFIG = { - "camera_config" : {"background_opacity" : 1} - } - def construct(self): - lion = ImageMobject("Lion") - edges_array = get_edges(lion.pixel_array) - edges = ImageMobject(edges_array) - group = Group(lion, edges) - group.set_height(4) - group.arrange(RIGHT) - lion_copy = lion.copy() - - self.play(FadeIn(lion)) - self.play(lion_copy.move_to, edges) - self.play(Transform(lion_copy, edges, run_time = 3)) - self.wait(2) - -class ManyTasksBreakDownLikeThis(TeacherStudentsScene): - def construct(self): - audio = self.get_wave_form() - audio_label = TextMobject("Raw audio") - letters = TextMobject(" ".join("recognition")) - syllables = TextMobject("$\\cdot$".join([ - "re", "cog", "ni", "tion" - ])) - word = TextMobject( - "re", "cognition", - arg_separator = "" - ) - word[1].set_color(BLUE) - arrows = VGroup() - def get_arrow(): - arrow = Arrow(ORIGIN, RIGHT, color = BLUE) - arrows.add(arrow) - return arrow - sequence = VGroup( - audio, get_arrow(), - letters, get_arrow(), - syllables, get_arrow(), - word - ) - sequence.arrange(RIGHT) - sequence.set_width(FRAME_WIDTH - 1) - sequence.to_edge(UP) - - audio_label.next_to(audio, DOWN) - VGroup(audio, audio_label).set_color(YELLOW) - audio.save_state() - - self.teacher_says( - "Many", "recognition", "tasks\\\\", - "break down like this" - ) - self.change_student_modes(*["pondering"]*3) - self.wait() - content = self.teacher.bubble.content - pre_word = content[1] - content.remove(pre_word) - audio.move_to(pre_word) - self.play( - self.teacher.bubble.content.fade, 1, - ShowCreation(audio), - pre_word.shift, MED_SMALL_BUFF, DOWN - ) - self.wait(2) - self.play( - RemovePiCreatureBubble(self.teacher), - audio.restore, - FadeIn(audio_label), - *[ - ReplacementTransform( - m1, m2 - ) - for m1, m2 in zip(pre_word, letters) - ] - ) - self.play( - GrowFromPoint(arrows[0], arrows[0].get_start()), - ) - self.wait() - self.play( - GrowFromPoint(arrows[1], arrows[1].get_start()), - LaggedStartMap(FadeIn, syllables, run_time = 1) - ) - self.wait() - self.play( - GrowFromPoint(arrows[2], arrows[2].get_start()), - LaggedStartMap(FadeIn, word, run_time = 1) - ) - self.wait() - - def get_wave_form(self): - func = lambda x : abs(sum([ - (1./n)*np.sin((n+3)*x) - for n in range(1, 5) - ])) - result = VGroup(*[ - Line(func(x)*DOWN, func(x)*UP) - for x in np.arange(0, 4, 0.1) - ]) - result.set_stroke(width = 2) - result.arrange(RIGHT, buff = MED_SMALL_BUFF) - result.set_height(1) - - return result - -class AskAboutWhatEdgesAreDoing(IntroduceEachLayer): - CONFIG = { - "network_mob_config" : { - "layer_to_layer_buff" : 2, - } - } - def construct(self): - self.add_question() - self.show_propogation() - - def add_question(self): - self.network_mob.scale(0.8) - self.network_mob.to_edge(DOWN) - edge_groups = self.network_mob.edge_groups - self.remove_random_edges(0.7) - - question = TextMobject( - "What are these connections actually doing?" - ) - question.to_edge(UP) - question.shift(RIGHT) - arrows = VGroup(*[ - Arrow( - question.get_bottom(), - edge_group.get_top() - ) - for edge_group in edge_groups - ]) - - self.add(question, arrows) - - def show_propogation(self): - in_vect = get_organized_images()[6][3] - image = MNistMobject(in_vect) - image.next_to(self.network_mob, LEFT, MED_SMALL_BUFF, UP) - - self.add(image) - self.feed_forward(in_vect) - self.wait() - -class IntroduceWeights(IntroduceEachLayer): - CONFIG = { - "weights_color" : GREEN, - "negative_weights_color" : RED, - } - def construct(self): - self.zoom_in_on_one_neuron() - self.show_desired_pixel_region() - self.ask_about_parameters() - self.show_weights() - self.show_weighted_sum() - self.organize_weights_as_grid() - self.make_most_weights_0() - self.add_negative_weights_around_the_edge() - - def zoom_in_on_one_neuron(self): - self.network_mob.to_edge(LEFT) - layers = self.network_mob.layers - edge_groups = self.network_mob.edge_groups - - neuron = layers[1].neurons[7].deepcopy() - - self.play( - FadeOut(edge_groups), - FadeOut(VGroup(*layers[1:])), - FadeOut(self.network_mob.output_labels), - Animation(neuron), - neuron.edges_in.set_stroke, None, 2, - lag_ratio = 0.5, - run_time = 2 - ) - - self.neuron = neuron - - def show_desired_pixel_region(self): - neuron = self.neuron - d = 28 - - pixels = PixelsAsSquares(ImageMobject( - np.zeros((d, d, 4)) - )) - pixels.set_stroke(width = 0.5) - pixels.set_fill(WHITE, 0) - pixels.set_height(4) - pixels.next_to(neuron, RIGHT, LARGE_BUFF) - rect = SurroundingRectangle(pixels, color = BLUE) - - pixels_to_detect = self.get_pixels_to_detect(pixels) - - self.play( - FadeIn(rect), - ShowCreation( - pixels, - lag_ratio = 0.5, - run_time = 2, - ) - ) - self.play( - pixels_to_detect.set_fill, WHITE, 1, - lag_ratio = 0.5, - run_time = 2 - ) - self.wait(2) - - self.pixels = pixels - self.pixels_to_detect = pixels_to_detect - self.pixels_group = VGroup(rect, pixels) - - def ask_about_parameters(self): - pixels = self.pixels - pixels_group = self.pixels_group - neuron = self.neuron - - question = TextMobject("What", "parameters", "should exist?") - parameter_word = question.get_part_by_tex("parameters") - parameter_word.set_color(self.weights_color) - question.move_to(neuron.edges_in.get_top(), LEFT) - arrow = Arrow( - parameter_word.get_bottom(), - neuron.edges_in[0].get_center(), - color = self.weights_color - ) - - p_labels = VGroup(*[ - TexMobject("p_%d\\!:"%(i+1)).set_color(self.weights_color) - for i in range(8) - ] + [TexMobject("\\vdots")]) - p_labels.arrange(DOWN, aligned_edge = LEFT) - p_labels.next_to(parameter_word, DOWN, LARGE_BUFF) - p_labels[-1].shift(SMALL_BUFF*RIGHT) - - def get_alpha_func(i, start = 0): - # m = int(5*np.sin(2*np.pi*i/128.)) - m = random.randint(1, 10) - return lambda a : start + (1-2*start)*np.sin(np.pi*a*m)**2 - - decimals = VGroup() - changing_decimals = [] - for i, p_label in enumerate(p_labels[:-1]): - decimal = DecimalNumber(0) - decimal.next_to(p_label, RIGHT, MED_SMALL_BUFF) - decimals.add(decimal) - changing_decimals.append(ChangingDecimal( - decimal, get_alpha_func(i + 5) - )) - for i, pixel in enumerate(pixels): - pixel.func = get_alpha_func(i, pixel.get_fill_opacity()) - pixel_updates = [ - UpdateFromAlphaFunc( - pixel, - lambda p, a : p.set_fill(opacity = p.func(a)) - ) - for pixel in pixels - ] - - self.play( - Write(question, run_time = 2), - GrowFromPoint(arrow, arrow.get_start()), - pixels_group.set_height, 3, - pixels_group.to_edge, RIGHT, - LaggedStartMap(FadeIn, p_labels), - LaggedStartMap(FadeIn, decimals), - ) - self.wait() - self.play( - *changing_decimals + pixel_updates, - run_time = 5, - rate_func=linear - ) - - self.question = question - self.weight_arrow = arrow - self.p_labels = p_labels - self.decimals = decimals - - def show_weights(self): - p_labels = self.p_labels - decimals = self.decimals - arrow = self.weight_arrow - question = self.question - neuron = self.neuron - edges = neuron.edges_in - - parameter_word = question.get_part_by_tex("parameters") - question.remove(parameter_word) - weights_word = TextMobject("Weights", "")[0] - weights_word.set_color(self.weights_color) - weights_word.move_to(parameter_word) - - w_labels = VGroup() - for p_label in p_labels: - w_label = TexMobject( - p_label.get_tex_string().replace("p", "w") - ) - w_label.set_color(self.weights_color) - w_label.move_to(p_label) - w_labels.add(w_label) - - edges.generate_target() - random_numbers = 1.5*np.random.random(len(edges))-0.5 - self.make_edges_weighted(edges.target, random_numbers) - def get_alpha_func(r): - return lambda a : (4*r)*a - - self.play( - FadeOut(question), - ReplacementTransform(parameter_word, weights_word), - ReplacementTransform(p_labels, w_labels) - ) - self.play( - MoveToTarget(edges), - *[ - ChangingDecimal( - decimal, - get_alpha_func(r) - ) - for decimal, r in zip(decimals, random_numbers) - ] - ) - self.play(LaggedStartMap( - ApplyMethod, edges, - lambda m : (m.rotate_in_place, np.pi/24), - rate_func = wiggle, - run_time = 2 - )) - self.wait() - - self.w_labels = w_labels - self.weights_word = weights_word - self.random_numbers = random_numbers - - def show_weighted_sum(self): - weights_word = self.weights_word - weight_arrow = self.weight_arrow - w_labels = VGroup(*[ - VGroup(*label[:-1]).copy() - for label in self.w_labels - ]) - layer = self.network_mob.layers[0] - - a_vect = np.random.random(16) - active_layer = self.network_mob.get_active_layer(0, a_vect) - - a_labels = VGroup(*[ - TexMobject("a_%d"%d) - for d in range(1, 5) - ]) - - weighted_sum = VGroup(*it.chain(*[ - [w, a, TexMobject("+")] - for w, a in zip(w_labels, a_labels) - ])) - weighted_sum.add( - TexMobject("\\cdots"), - TexMobject("+"), - TexMobject("w_n").set_color(self.weights_color), - TexMobject("a_n") - ) - weighted_sum.arrange(RIGHT, buff = SMALL_BUFF) - weighted_sum.to_edge(UP) - - self.play(Transform(layer, active_layer)) - self.play( - FadeOut(weights_word), - FadeOut(weight_arrow), - *[ - ReplacementTransform(n.copy(), a) - for n, a in zip(layer.neurons, a_labels) - ] + [ - ReplacementTransform(n.copy(), weighted_sum[-4]) - for n in layer.neurons[4:-1] - ] + [ - ReplacementTransform( - layer.neurons[-1].copy(), - weighted_sum[-1] - ) - ] + [ - Write(weighted_sum[i]) - for i in list(range(2, 12, 3)) + [-4, -3] - ], - run_time = 1.5 - ) - self.wait() - self.play(*[ - ReplacementTransform(w1.copy(), w2) - for w1, w2 in zip(self.w_labels, w_labels)[:4] - ]+[ - ReplacementTransform(w.copy(), weighted_sum[-4]) - for w in self.w_labels[4:-1] - ]+[ - ReplacementTransform( - self.w_labels[-1].copy(), weighted_sum[-2] - ) - ], run_time = 2) - self.wait(2) - - self.weighted_sum = weighted_sum - - def organize_weights_as_grid(self): - pixels = self.pixels - w_labels = self.w_labels - decimals = self.decimals - - weights = 2*np.sqrt(np.random.random(784))-1 - weights[:8] = self.random_numbers[:8] - weights[-8:] = self.random_numbers[-8:] - - weight_grid = PixelsFromVect(np.abs(weights)) - weight_grid.replace(pixels) - weight_grid.next_to(pixels, LEFT) - for weight, pixel in zip(weights, weight_grid): - if weight >= 0: - color = self.weights_color - else: - color = self.negative_weights_color - pixel.set_fill(color, opacity = abs(weight)) - - self.play(FadeOut(w_labels)) - self.play( - FadeIn( - VGroup(*weight_grid[len(decimals):]), - lag_ratio = 0.5, - run_time = 3 - ), - *[ - ReplacementTransform(decimal, pixel) - for decimal, pixel in zip(decimals, weight_grid) - ] - ) - self.wait() - - self.weight_grid = weight_grid - - def make_most_weights_0(self): - weight_grid = self.weight_grid - pixels = self.pixels - pixels_group = self.pixels_group - - weight_grid.generate_target() - for w, p in zip(weight_grid.target, pixels): - if p.get_fill_opacity() > 0.1: - w.set_fill(GREEN, 0.5) - else: - w.set_fill(BLACK, 0.5) - w.set_stroke(WHITE, 0.5) - - digit = self.get_digit() - digit.replace(pixels) - - self.play(MoveToTarget( - weight_grid, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait() - self.play(Transform( - pixels, digit, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait() - self.play(weight_grid.move_to, pixels) - self.wait() - self.play( - ReplacementTransform( - self.pixels_to_detect.copy(), - self.weighted_sum, - run_time = 3, - lag_ratio = 0.5 - ), - Animation(weight_grid), - ) - self.wait() - - def add_negative_weights_around_the_edge(self): - weight_grid = self.weight_grid - pixels = self.pixels - - self.play(weight_grid.next_to, pixels, LEFT) - self.play(*[ - ApplyMethod( - weight_grid[28*y + x].set_fill, - self.negative_weights_color, - 0.5 - ) - for y in (6, 10) - for x in range(14-4, 14+4) - ]) - self.wait(2) - self.play(weight_grid.move_to, pixels) - self.wait(2) - - #### - - def get_digit(self): - digit_vect = get_organized_images()[7][4] - digit = PixelsFromVect(digit_vect) - digit.set_stroke(width = 0.5) - return digit - - def get_pixels_to_detect(self, pixels): - d = int(np.sqrt(len(pixels))) - return VGroup(*it.chain(*[ - pixels[d*n + d/2 - 4 : d*n + d/2 + 4] - for n in range(7, 10) - ])) - - def get_surrounding_pixels_for_edge(self, pixels): - d = int(np.sqrt(len(pixels))) - return VGroup(*it.chain(*[ - pixels[d*n + d/2 - 4 : d*n + d/2 + 4] - for n in (6, 10) - ])) - - def make_edges_weighted(self, edges, weights): - for edge, r in zip(edges, weights): - if r > 0: - color = self.weights_color - else: - color = self.negative_weights_color - edge.set_stroke(color, 6*abs(r)) - -class MotivateSquishing(Scene): - def construct(self): - self.add_weighted_sum() - self.show_real_number_line() - self.show_interval() - self.squish_into_interval() - - def add_weighted_sum(self): - weighted_sum = TexMobject(*it.chain(*[ - ["w_%d"%d, "a_%d"%d, "+"] - for d in range(1, 5) - ] + [ - ["\\cdots", "+", "w_n", "a_n"] - ])) - weighted_sum.set_color_by_tex("w_", GREEN) - weighted_sum.to_edge(UP) - self.add(weighted_sum) - self.weighted_sum = weighted_sum - - def show_real_number_line(self): - weighted_sum = self.weighted_sum - number_line = NumberLine(unit_size = 1.5) - number_line.add_numbers() - number_line.shift(UP) - arrow1, arrow2 = [ - Arrow( - weighted_sum.get_bottom(), - number_line.number_to_point(n), - ) - for n in (-3, 3) - ] - - self.play(Write(number_line)) - self.play(GrowFromPoint(arrow1, arrow1.get_start())) - self.play(Transform( - arrow1, arrow2, - run_time = 5, - rate_func = there_and_back - )) - self.play(FadeOut(arrow1)) - - self.number_line = number_line - - def show_interval(self): - lower_number_line = self.number_line.copy() - lower_number_line.shift(2*DOWN) - lower_number_line.set_color(LIGHT_GREY) - lower_number_line.numbers.set_color(WHITE) - interval = Line( - lower_number_line.number_to_point(0), - lower_number_line.number_to_point(1), - color = YELLOW, - stroke_width = 5 - ) - brace = Brace(interval, DOWN, buff = 0.7) - words = TextMobject("Activations should be in this range") - words.next_to(brace, DOWN, SMALL_BUFF) - - self.play(ReplacementTransform( - self.number_line.copy(), lower_number_line - )) - self.play( - GrowFromCenter(brace), - GrowFromCenter(interval), - ) - self.play(Write(words, run_time = 2)) - self.wait() - - self.lower_number_line = lower_number_line - - def squish_into_interval(self): - line = self.number_line - line.remove(*line.numbers) - ghost_line = line.copy() - ghost_line.fade(0.5) - ghost_line.set_color(BLUE_E) - self.add(ghost_line, line) - lower_line = self.lower_number_line - - line.generate_target() - u = line.unit_size - line.target.apply_function( - lambda p : np.array([u*sigmoid(p[0])]+list(p[1:])) - ) - line.target.move_to(lower_line.number_to_point(0.5)) - - arrow = Arrow( - line.numbers.get_bottom(), - line.target.get_top(), - color = YELLOW - ) - - self.play( - MoveToTarget(line), - GrowFromPoint(arrow, arrow.get_start()) - ) - self.wait(2) - -class IntroduceSigmoid(GraphScene): - CONFIG = { - "x_min" : -5, - "x_max" : 5, - "x_axis_width" : 12, - "y_min" : -1, - "y_max" : 2, - "y_axis_label" : "", - "graph_origin" : DOWN, - "x_labeled_nums" : list(range(-4, 5)), - "y_labeled_nums" : list(range(-1, 3)), - } - def construct(self): - self.setup_axes() - self.add_title() - self.add_graph() - self.show_part(-5, -2, RED) - self.show_part(2, 5, GREEN) - self.show_part(-2, 2, BLUE) - - def add_title(self): - name = TextMobject("Sigmoid") - name.next_to(ORIGIN, RIGHT, LARGE_BUFF) - name.to_edge(UP) - char = self.x_axis_label.replace("$", "") - equation = TexMobject( - "\\sigma(%s) = \\frac{1}{1+e^{-%s}}"%(char, char) - ) - equation.next_to(name, DOWN) - self.add(equation, name) - - self.equation = equation - self.sigmoid_name = name - - def add_graph(self): - graph = self.get_graph( - lambda x : 1./(1+np.exp(-x)), - color = YELLOW - ) - - self.play(ShowCreation(graph)) - self.wait() - - self.sigmoid_graph = graph - - ### - - def show_part(self, x_min, x_max, color): - line, graph_part = [ - self.get_graph( - func, - x_min = x_min, - x_max = x_max, - color = color, - ).set_stroke(width = 4) - for func in (lambda x : 0, sigmoid) - ] - - self.play(ShowCreation(line)) - self.wait() - self.play(Transform(line, graph_part)) - self.wait() - -class IncludeBias(IntroduceWeights): - def construct(self): - self.force_skipping() - self.zoom_in_on_one_neuron() - self.setup_start() - self.revert_to_original_skipping_status() - - self.add_sigmoid_label() - self.words_on_activation() - self.comment_on_need_for_bias() - self.add_bias() - self.summarize_weights_and_biases() - - def setup_start(self): - self.weighted_sum = self.get_weighted_sum() - digit = self.get_digit() - rect = SurroundingRectangle(digit) - d_group = VGroup(digit, rect) - d_group.set_height(3) - d_group.to_edge(RIGHT) - weight_grid = digit.copy() - weight_grid.set_fill(BLACK, 0.5) - self.get_pixels_to_detect(weight_grid).set_fill( - GREEN, 0.5 - ) - self.get_surrounding_pixels_for_edge(weight_grid).set_fill( - RED, 0.5 - ) - weight_grid.move_to(digit) - - edges = self.neuron.edges_in - self.make_edges_weighted( - edges, 1.5*np.random.random(len(edges)) - 0.5 - ) - - Transform( - self.network_mob.layers[0], - self.network_mob.get_active_layer(0, np.random.random(16)) - ).update(1) - - self.add(self.weighted_sum, digit, weight_grid) - self.digit = digit - self.weight_grid = weight_grid - - def add_sigmoid_label(self): - name = TextMobject("Sigmoid") - sigma = self.weighted_sum[0][0] - name.next_to(sigma, UP) - name.to_edge(UP, SMALL_BUFF) - - arrow = Arrow( - name.get_bottom(), sigma.get_top(), - buff = SMALL_BUFF, - use_rectangular_stem = False, - max_tip_length_to_length_ratio = 0.3 - ) - - self.play( - Write(name), - ShowCreation(arrow), - ) - self.sigmoid_name = name - self.sigmoid_arrow = arrow - - def words_on_activation(self): - neuron = self.neuron - weighted_sum = self.weighted_sum - - activation_word = TextMobject("Activation") - activation_word.next_to(neuron, RIGHT) - arrow = Arrow(neuron, weighted_sum.get_bottom()) - arrow.set_color(WHITE) - words = TextMobject("How positive is this?") - words.next_to(self.weighted_sum, UP, SMALL_BUFF) - - self.play( - FadeIn(activation_word), - neuron.set_fill, WHITE, 0.8, - ) - self.wait() - self.play( - GrowArrow(arrow), - ReplacementTransform(activation_word, words), - ) - self.wait(2) - self.play(FadeOut(arrow)) - - self.how_positive_words = words - - def comment_on_need_for_bias(self): - neuron = self.neuron - weight_grid = self.weight_grid - colored_pixels = VGroup( - self.get_pixels_to_detect(weight_grid), - self.get_surrounding_pixels_for_edge(weight_grid), - ) - - words = TextMobject( - "Only activate meaningfully \\\\ when", - "weighted sum", "$> 10$" - ) - words.set_color_by_tex("weighted", GREEN) - words.next_to(neuron, RIGHT) - - self.play(Write(words, run_time = 2)) - self.play(ApplyMethod( - colored_pixels.shift, MED_LARGE_BUFF*UP, - rate_func = there_and_back, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait() - - self.gt_ten = words[-1] - - def add_bias(self): - bias = TexMobject("-10") - wn, rp = self.weighted_sum[-2:] - bias.next_to(wn, RIGHT, SMALL_BUFF) - bias.shift(0.02*UP) - rp.generate_target() - rp.target.next_to(bias, RIGHT, SMALL_BUFF) - - rect = SurroundingRectangle(bias, buff = 0.5*SMALL_BUFF) - name = TextMobject("``bias''") - name.next_to(rect, DOWN) - VGroup(rect, name).set_color(BLUE) - - self.play( - ReplacementTransform( - self.gt_ten.copy(), bias, - run_time = 2 - ), - MoveToTarget(rp), - ) - self.wait(2) - self.play( - ShowCreation(rect), - Write(name) - ) - self.wait(2) - - self.bias_name = name - - def summarize_weights_and_biases(self): - weight_grid = self.weight_grid - bias_name = self.bias_name - - self.play(LaggedStartMap( - ApplyMethod, weight_grid, - lambda p : (p.set_fill, - random.choice([GREEN, GREEN, RED]), - random.random() - ), - rate_func = there_and_back, - lag_ratio = 0.4, - run_time = 4 - )) - self.wait() - self.play(Indicate(bias_name)) - self.wait(2) - - ### - - def get_weighted_sum(self): - args = ["\\sigma \\big("] - for d in range(1, 4): - args += ["w_%d"%d, "a_%d"%d, "+"] - args += ["\\cdots", "+", "w_n", "a_n"] - args += ["\\big)"] - weighted_sum = TexMobject(*args) - weighted_sum.set_color_by_tex("w_", GREEN) - weighted_sum.set_color_by_tex("\\big", YELLOW) - weighted_sum.to_edge(UP, LARGE_BUFF) - weighted_sum.shift(RIGHT) - - return weighted_sum - -class BiasForInactiviyWords(Scene): - def construct(self): - words = TextMobject("Bias for inactivity") - words.set_color(BLUE) - words.set_width(FRAME_WIDTH - 1) - words.to_edge(UP) - - self.play(Write(words)) - self.wait(3) - -class ContinualEdgeUpdate(VGroup): - CONFIG = { - "max_stroke_width" : 3, - "stroke_width_exp" : 7, - "n_cycles" : 5, - "colors" : [GREEN, GREEN, GREEN, RED], - } - def __init__(self, network_mob, **kwargs): - VGroup.__init__(self, **kwargs) - self.internal_time = 0 - n_cycles = self.n_cycles - edges = VGroup(*it.chain(*network_mob.edge_groups)) - self.move_to_targets = [] - for edge in edges: - edge.colors = [ - random.choice(self.colors) - for x in range(n_cycles) - ] - msw = self.max_stroke_width - edge.widths = [ - msw*random.random()**self.stroke_width_exp - for x in range(n_cycles) - ] - edge.cycle_time = 1 + random.random() - - edge.generate_target() - edge.target.set_stroke(edge.colors[0], edge.widths[0]) - edge.become(edge.target) - self.move_to_targets.append(edge) - self.edges = edges - self.add(edges) - self.add_updater(lambda m, dt: self.update_edges(dt)) - - def update_edges(self, dt): - self.internal_time += dt - if self.internal_time < 1: - alpha = smooth(self.internal_time) - for move_to_target in self.move_to_targets: - move_to_target.update(alpha) - return - for edge in self.edges: - t = (self.internal_time-1)/edge.cycle_time - alpha = ((self.internal_time-1)%edge.cycle_time)/edge.cycle_time - low_n = int(t)%len(edge.colors) - high_n = int(t+1)%len(edge.colors) - color = interpolate_color(edge.colors[low_n], edge.colors[high_n], alpha) - width = interpolate(edge.widths[low_n], edge.widths[high_n], alpha) - edge.set_stroke(color, width) - -class ShowRemainingNetwork(IntroduceWeights): - def construct(self): - self.force_skipping() - self.zoom_in_on_one_neuron() - self.revert_to_original_skipping_status() - - self.show_all_of_second_layer() - self.count_in_biases() - self.compute_layer_two_of_weights_and_biases_count() - self.show_remaining_layers() - self.show_final_number() - self.tweak_weights() - - def show_all_of_second_layer(self): - example_neuron = self.neuron - layer = self.network_mob.layers[1] - - neurons = VGroup(*layer.neurons) - neurons.remove(example_neuron) - - words = TextMobject("784", "weights", "per neuron") - words.next_to(layer.neurons[0], RIGHT) - words.to_edge(UP) - - self.play(FadeIn(words)) - last_edges = None - for neuron in neurons[:7]: - edges = neuron.edges_in - added_anims = [] - if last_edges is not None: - added_anims += [ - last_edges.set_stroke, None, 1 - ] - edges.set_stroke(width = 2) - self.play( - ShowCreation(edges, lag_ratio = 0.5), - FadeIn(neuron), - *added_anims, - run_time = 1.5 - ) - last_edges = edges - self.play( - LaggedStartMap( - ShowCreation, VGroup(*[ - n.edges_in for n in neurons[7:] - ]), - run_time = 3, - ), - LaggedStartMap( - FadeIn, VGroup(*neurons[7:]), - run_time = 3, - ), - VGroup(*last_edges[1:]).set_stroke, None, 1 - ) - self.wait() - - self.weights_words = words - - def count_in_biases(self): - neurons = self.network_mob.layers[1].neurons - words = TextMobject("One", "bias","for each") - words.next_to(neurons, RIGHT, buff = 2) - arrows = VGroup(*[ - Arrow( - words.get_left(), - neuron.get_center(), - color = BLUE - ) - for neuron in neurons - ]) - - self.play( - FadeIn(words), - LaggedStartMap( - GrowArrow, arrows, - run_time = 3, - lag_ratio = 0.3, - ) - ) - self.wait() - - self.bias_words = words - self.bias_arrows = arrows - - def compute_layer_two_of_weights_and_biases_count(self): - ww1, ww2, ww3 = weights_words = self.weights_words - bb1, bb2, bb3 = bias_words = self.bias_words - bias_arrows = self.bias_arrows - - times_16 = TexMobject("\\times 16") - times_16.next_to(ww1, RIGHT, SMALL_BUFF) - ww2.generate_target() - ww2.target.next_to(times_16, RIGHT) - - bias_count = TextMobject("16", "biases") - bias_count.next_to(ww2.target, RIGHT, LARGE_BUFF) - - self.play( - Write(times_16), - MoveToTarget(ww2), - FadeOut(ww3) - ) - self.wait() - self.play( - ReplacementTransform(times_16.copy(), bias_count[0]), - FadeOut(bb1), - ReplacementTransform(bb2, bias_count[1]), - FadeOut(bb3), - LaggedStartMap(FadeOut, bias_arrows) - ) - self.wait() - - self.weights_count = VGroup(ww1, times_16, ww2) - self.bias_count = bias_count - - def show_remaining_layers(self): - weights_count = self.weights_count - bias_count = self.bias_count - for count in weights_count, bias_count: - count.generate_target() - count.prefix = VGroup(*count.target[:-1]) - - added_weights = TexMobject( - "+16\\!\\times\\! 16 + 16 \\!\\times\\! 10" - ) - added_weights.to_corner(UP+RIGHT) - weights_count.prefix.next_to(added_weights, LEFT, SMALL_BUFF) - weights_count.target[-1].next_to( - VGroup(weights_count.prefix, added_weights), - DOWN - ) - - added_biases = TexMobject("+ 16 + 10") - group = VGroup(bias_count.prefix, added_biases) - group.arrange(RIGHT, SMALL_BUFF) - group.next_to(weights_count.target[-1], DOWN, LARGE_BUFF) - bias_count.target[-1].next_to(group, DOWN) - - network_mob = self.network_mob - edges = VGroup(*it.chain(*network_mob.edge_groups[1:])) - neurons = VGroup(*it.chain(*[ - layer.neurons for layer in network_mob.layers[2:] - ])) - - self.play( - MoveToTarget(weights_count), - MoveToTarget(bias_count), - Write(added_weights, run_time = 1), - Write(added_biases, run_time = 1), - LaggedStartMap( - ShowCreation, edges, - run_time = 4, - lag_ratio = 0.3, - ), - LaggedStartMap( - FadeIn, neurons, - run_time = 4, - lag_ratio = 0.3, - ) - ) - self.wait(2) - - weights_count.add(added_weights) - bias_count.add(added_biases) - - def show_final_number(self): - group = VGroup( - self.weights_count, - self.bias_count, - ) - group.generate_target() - group.target.scale_in_place(0.8) - rect = SurroundingRectangle(group.target, buff = MED_SMALL_BUFF) - num_mob = TexMobject("13{,}002") - num_mob.scale(1.5) - num_mob.next_to(rect, DOWN) - - self.play( - ShowCreation(rect), - MoveToTarget(group), - ) - self.play(Write(num_mob)) - self.wait() - - self.final_number = num_mob - - def tweak_weights(self): - learning = TextMobject("Learning $\\rightarrow$") - finding_words = TextMobject( - "Finding the right \\\\ weights and biases" - ) - group = VGroup(learning, finding_words) - group.arrange(RIGHT) - group.scale(0.8) - group.next_to(self.final_number, DOWN, MED_LARGE_BUFF) - - self.add(ContinualEdgeUpdate(self.network_mob)) - self.wait(5) - self.play(Write(group)) - self.wait(10) - - ### - - def get_edge_weight_wandering_anim(self, edges): - for edge in edges: - edge.generate_target() - edge.target.set_stroke( - color = random.choice([GREEN, GREEN, GREEN, RED]), - width = 3*random.random()**7 - ) - self.play( - LaggedStartMap( - MoveToTarget, edges, - lag_ratio = 0.6, - run_time = 2, - ), - *added_anims - ) - -class ImagineSettingByHand(Scene): - def construct(self): - randy = Randolph() - randy.scale(0.7) - randy.to_corner(DOWN+LEFT) - - bubble = randy.get_bubble() - network_mob = NetworkMobject( - Network(sizes = [8, 6, 6, 4]), - neuron_stroke_color = WHITE - ) - network_mob.scale(0.7) - network_mob.move_to(bubble.get_bubble_center()) - network_mob.shift(MED_SMALL_BUFF*RIGHT + SMALL_BUFF*(UP+RIGHT)) - - self.add(randy, bubble, network_mob) - self.add(ContinualEdgeUpdate(network_mob)) - self.play(randy.change, "pondering") - self.wait() - self.play(Blink(randy)) - self.wait() - self.play(randy.change, "horrified", network_mob) - self.play(Blink(randy)) - self.wait(10) - -class WhenTheNetworkFails(MoreHonestMNistNetworkPreview): - CONFIG = { - "network_mob_config" : {"layer_to_layer_buff" : 2} - } - def construct(self): - self.setup_network_mob() - self.black_box() - self.incorrect_classification() - self.ask_about_weights() - - def setup_network_mob(self): - self.network_mob.scale(0.8) - self.network_mob.to_edge(DOWN) - - def black_box(self): - network_mob = self.network_mob - layers = VGroup(*network_mob.layers[1:3]) - box = SurroundingRectangle( - layers, - stroke_color = WHITE, - fill_color = BLACK, - fill_opacity = 0.8, - ) - words = TextMobject("...rather than treating this as a black box") - words.next_to(box, UP, LARGE_BUFF) - - self.play( - Write(words, run_time = 2), - DrawBorderThenFill(box) - ) - self.wait() - self.play(*list(map(FadeOut, [words, box]))) - - def incorrect_classification(self): - network = self.network - training_data, validation_data, test_data = load_data_wrapper() - for in_vect, result in test_data[20:]: - network_answer = np.argmax(network.feedforward(in_vect)) - if network_answer != result: - break - self.feed_in_image(in_vect) - - wrong = TextMobject("Wrong!") - wrong.set_color(RED) - wrong.next_to(self.network_mob.layers[-1], UP+RIGHT) - self.play(Write(wrong, run_time = 1)) - - def ask_about_weights(self): - question = TextMobject( - "What weights are used here?\\\\", - "What are they doing?" - ) - question.next_to(self.network_mob, UP) - - self.add(ContinualEdgeUpdate(self.network_mob)) - self.play(Write(question)) - self.wait(10) - - - ### - - def reset_display(self, *args): - pass - -class EvenWhenItWorks(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Even when it works,\\\\", - "dig into why." - ) - self.change_student_modes(*["pondering"]*3) - self.wait(7) - -class IntroduceWeightMatrix(NetworkScene): - CONFIG = { - "network_mob_config" : { - "neuron_stroke_color" : WHITE, - "neuron_fill_color" : WHITE, - "neuron_radius" : 0.35, - "layer_to_layer_buff" : 2, - }, - "layer_sizes" : [8, 6], - } - def construct(self): - self.setup_network_mob() - self.show_weighted_sum() - self.organize_activations_into_column() - self.organize_weights_as_matrix() - self.show_meaning_of_matrix_row() - self.connect_weighted_sum_to_matrix_multiplication() - self.add_bias_vector() - self.apply_sigmoid() - self.write_clean_final_expression() - - def setup_network_mob(self): - self.network_mob.to_edge(LEFT, buff = LARGE_BUFF) - self.network_mob.layers[1].neurons.shift(0.02*RIGHT) - - def show_weighted_sum(self): - self.fade_many_neurons() - self.activate_first_layer() - self.show_first_neuron_weighted_sum() - self.add_bias() - self.add_sigmoid() - ## - - def fade_many_neurons(self): - anims = [] - neurons = self.network_mob.layers[1].neurons - for neuron in neurons[1:]: - neuron.save_state() - neuron.edges_in.save_state() - anims += [ - neuron.fade, 0.8, - neuron.set_fill, None, 0, - neuron.edges_in.fade, 0.8, - ] - anims += [ - Animation(neurons[0]), - Animation(neurons[0].edges_in), - ] - self.play(*anims) - - def activate_first_layer(self): - layer = self.network_mob.layers[0] - activations = 0.7*np.random.random(len(layer.neurons)) - active_layer = self.network_mob.get_active_layer(0, activations) - a_labels = VGroup(*[ - TexMobject("a^{(0)}_%d"%d) - for d in range(len(layer.neurons)) - ]) - for label, neuron in zip(a_labels, layer.neurons): - label.scale(0.75) - label.move_to(neuron) - - self.play( - Transform(layer, active_layer), - Write(a_labels, run_time = 2) - ) - - self.a_labels = a_labels - - def show_first_neuron_weighted_sum(self): - neuron = self.network_mob.layers[1].neurons[0] - a_labels = VGroup(*self.a_labels[:2]).copy() - a_labels.generate_target() - w_labels = VGroup(*[ - TexMobject("w_{0, %d}"%d) - for d in range(len(a_labels)) - ]) - weighted_sum = VGroup() - symbols = VGroup() - for a_label, w_label in zip(a_labels.target, w_labels): - a_label.scale(1./0.75) - plus = TexMobject("+") - weighted_sum.add(w_label, a_label, plus) - symbols.add(plus) - weighted_sum.add( - TexMobject("\\cdots"), - TexMobject("+"), - TexMobject("w_{0, n}"), - TexMobject("a^{(0)}_n"), - ) - - weighted_sum.arrange(RIGHT) - a1_label = TexMobject("a^{(1)}_0") - a1_label.next_to(neuron, RIGHT) - equals = TexMobject("=").next_to(a1_label, RIGHT) - weighted_sum.next_to(equals, RIGHT) - - symbols.add(*weighted_sum[-4:-2]) - w_labels.add(weighted_sum[-2]) - a_labels.add(self.a_labels[-1].copy()) - a_labels.target.add(weighted_sum[-1]) - a_labels.add(VGroup(*self.a_labels[2:-1]).copy()) - a_labels.target.add(VectorizedPoint(weighted_sum[-4].get_center())) - - VGroup(a1_label, equals, weighted_sum).scale( - 0.75, about_point = a1_label.get_left() - ) - - w_labels.set_color(GREEN) - w_labels.shift(0.6*SMALL_BUFF*DOWN) - a_labels.target.shift(0.5*SMALL_BUFF*UP) - - self.play( - Write(a1_label), - Write(equals), - neuron.set_fill, None, 0.3, - run_time = 1 - ) - self.play(MoveToTarget(a_labels, run_time = 1.5)) - self.play( - Write(w_labels), - Write(symbols), - ) - - self.a1_label = a1_label - self.a1_equals = equals - self.w_labels = w_labels - self.a_labels_in_sum = a_labels - self.symbols = symbols - self.weighted_sum = VGroup(w_labels, a_labels, symbols) - - def add_bias(self): - weighted_sum = self.weighted_sum - bias = TexMobject("+\\,", "b_0") - bias.scale(0.75) - bias.next_to(weighted_sum, RIGHT, SMALL_BUFF) - bias.shift(0.5*SMALL_BUFF*DOWN) - name = TextMobject("Bias") - name.scale(0.75) - name.next_to(bias, DOWN, MED_LARGE_BUFF) - arrow = Arrow(name, bias, buff = SMALL_BUFF) - VGroup(name, arrow, bias).set_color(BLUE) - - self.play( - FadeIn(name), - FadeIn(bias), - GrowArrow(arrow), - ) - - self.weighted_sum.add(bias) - - self.bias = bias - self.bias_name = VGroup(name, arrow) - - def add_sigmoid(self): - weighted_sum = self.weighted_sum - weighted_sum.generate_target() - sigma, lp, rp = mob = TexMobject("\\sigma\\big(\\big)") - # mob.scale(0.75) - sigma.move_to(weighted_sum.get_left()) - sigma.shift(0.5*SMALL_BUFF*(DOWN+RIGHT)) - lp.next_to(sigma, RIGHT, SMALL_BUFF) - weighted_sum.target.next_to(lp, RIGHT, SMALL_BUFF) - rp.next_to(weighted_sum.target, RIGHT, SMALL_BUFF) - - name = TextMobject("Sigmoid") - name.next_to(sigma, UP, MED_LARGE_BUFF) - arrow = Arrow(name, sigma, buff = SMALL_BUFF) - sigmoid_name = VGroup(name, arrow) - VGroup(sigmoid_name, mob).set_color(YELLOW) - - self.play( - FadeIn(mob), - MoveToTarget(weighted_sum), - MaintainPositionRelativeTo(self.bias_name, self.bias), - ) - self.play(FadeIn(sigmoid_name)) - - self.sigma = sigma - self.sigma_parens = VGroup(lp, rp) - self.sigmoid_name = sigmoid_name - - ## - - def organize_activations_into_column(self): - a_labels = self.a_labels.copy() - a_labels.generate_target() - column = a_labels.target - a_labels_in_sum = self.a_labels_in_sum - - dots = TexMobject("\\vdots") - mid_as = VGroup(*column[2:-1]) - Transform(mid_as, dots).update(1) - last_a = column[-1] - new_last_a = TexMobject( - last_a.get_tex_string().replace("7", "n") - ) - new_last_a.replace(last_a) - Transform(last_a, new_last_a).update(1) - - VGroup( - *column[:2] + [mid_as] + [column[-1]] - ).arrange(DOWN) - column.shift(DOWN + 3.5*RIGHT) - - pre_brackets = self.get_brackets(a_labels) - post_bracketes = self.get_brackets(column) - pre_brackets.set_fill(opacity = 0) - - self.play(FocusOn(self.a_labels[0])) - self.play(LaggedStartMap( - Indicate, self.a_labels, - rate_func = there_and_back, - run_time = 1 - )) - self.play( - MoveToTarget(a_labels), - Transform(pre_brackets, post_bracketes), - run_time = 2 - ) - self.wait() - self.play(*[ - LaggedStartMap(Indicate, mob, rate_func = there_and_back) - for mob in (a_labels, a_labels_in_sum) - ]) - self.wait() - - self.a_column = a_labels - self.a_column_brackets = pre_brackets - - def organize_weights_as_matrix(self): - a_column = self.a_column - a_column_brackets = self.a_column_brackets - w_brackets = a_column_brackets.copy() - w_brackets.next_to(a_column_brackets, LEFT, SMALL_BUFF) - lwb, rwb = w_brackets - - w_labels = self.w_labels.copy() - w_labels.submobjects.insert( - 2, self.symbols[-2].copy() - ) - w_labels.generate_target() - w_labels.target.arrange(RIGHT) - w_labels.target.next_to(a_column[0], LEFT, buff = 0.8) - lwb.next_to(w_labels.target, LEFT, SMALL_BUFF) - lwb.align_to(rwb, UP) - - row_1, row_k = [ - VGroup(*list(map(TexMobject, [ - "w_{%s, 0}"%i, - "w_{%s, 1}"%i, - "\\cdots", - "w_{%s, n}"%i, - ]))) - for i in ("1", "k") - ] - dots_row = VGroup(*list(map(TexMobject, [ - "\\vdots", "\\vdots", "\\ddots", "\\vdots" - ]))) - - lower_rows = VGroup(row_1, dots_row, row_k) - lower_rows.scale(0.75) - last_row = w_labels.target - for row in lower_rows: - for target, mover in zip(last_row, row): - mover.move_to(target) - if "w" in mover.get_tex_string(): - mover.set_color(GREEN) - row.next_to(last_row, DOWN, buff = 0.45) - last_row = row - - self.play( - MoveToTarget(w_labels), - Write(w_brackets, run_time = 1) - ) - self.play(FadeIn( - lower_rows, - run_time = 3, - lag_ratio = 0.5, - )) - self.wait() - - self.top_matrix_row = w_labels - self.lower_matrix_rows = lower_rows - self.matrix_brackets = w_brackets - - def show_meaning_of_matrix_row(self): - row = self.top_matrix_row - edges = self.network_mob.layers[1].neurons[0].edges_in.copy() - edges.set_stroke(GREEN, 5) - rect = SurroundingRectangle(row, color = GREEN_B) - - self.play(ShowCreation(rect)) - for x in range(2): - self.play(LaggedStartMap( - ShowCreationThenDestruction, edges, - lag_ratio = 0.8 - )) - self.wait() - - self.top_row_rect = rect - - def connect_weighted_sum_to_matrix_multiplication(self): - a_column = self.a_column - a_brackets = self.a_column_brackets - top_row_rect = self.top_row_rect - - column_rect = SurroundingRectangle(a_column) - - equals = TexMobject("=") - equals.next_to(a_brackets, RIGHT) - result_brackets = a_brackets.copy() - result_terms = VGroup() - for i in 0, 1, 4, -1: - a = a_column[i] - if i == 4: - mob = TexMobject("\\vdots") - else: - # mob = Circle(radius = 0.2, color = YELLOW) - mob = TexMobject("?").scale(1.3).set_color(YELLOW) - result_terms.add(mob.move_to(a)) - VGroup(result_brackets, result_terms).next_to(equals, RIGHT) - - brace = Brace( - VGroup(self.w_labels, self.a_labels_in_sum), DOWN - ) - arrow = Arrow( - brace.get_bottom(), - result_terms[0].get_top(), - buff = SMALL_BUFF - ) - - self.play( - GrowArrow(arrow), - GrowFromCenter(brace), - ) - self.play( - Write(equals), - FadeIn(result_brackets), - ) - self.play(ShowCreation(column_rect)) - self.play(ReplacementTransform( - VGroup(top_row_rect, column_rect).copy(), - result_terms[0] - )) - self.play(LaggedStartMap( - FadeIn, VGroup(*result_terms[1:]) - )) - self.wait(2) - self.show_meaning_of_lower_rows( - arrow, brace, top_row_rect, result_terms - ) - self.play(*list(map(FadeOut, [ - result_terms, result_brackets, equals, column_rect - ]))) - - def show_meaning_of_lower_rows(self, arrow, brace, row_rect, result_terms): - n1, n2, nk = neurons = VGroup(*[ - self.network_mob.layers[1].neurons[i] - for i in (0, 1, -1) - ]) - for n in neurons: - n.save_state() - n.edges_in.save_state() - - rect2 = SurroundingRectangle(result_terms[1]) - rectk = SurroundingRectangle(result_terms[-1]) - VGroup(rect2, rectk).set_color(WHITE) - row2 = self.lower_matrix_rows[0] - rowk = self.lower_matrix_rows[-1] - - def show_edges(neuron): - self.play(LaggedStartMap( - ShowCreationThenDestruction, - neuron.edges_in.copy().set_stroke(GREEN, 5), - lag_ratio = 0.7, - run_time = 1, - )) - - self.play( - row_rect.move_to, row2, - n1.fade, - n1.set_fill, None, 0, - n1.edges_in.set_stroke, None, 1, - n2.set_stroke, WHITE, 3, - n2.edges_in.set_stroke, None, 3, - ReplacementTransform(arrow, rect2), - FadeOut(brace), - ) - show_edges(n2) - self.play( - row_rect.move_to, rowk, - n2.restore, - n2.edges_in.restore, - nk.set_stroke, WHITE, 3, - nk.edges_in.set_stroke, None, 3, - ReplacementTransform(rect2, rectk), - ) - show_edges(nk) - self.play( - n1.restore, - n1.edges_in.restore, - nk.restore, - nk.edges_in.restore, - FadeOut(rectk), - FadeOut(row_rect), - ) - - def add_bias_vector(self): - bias = self.bias - bias_name = self.bias_name - a_column_brackets = self.a_column_brackets - a_column = self.a_column - - plus = TexMobject("+") - b_brackets = a_column_brackets.copy() - b_column = VGroup(*list(map(TexMobject, [ - "b_0", "b_1", "\\vdots", "b_n", - ]))) - b_column.scale(0.85) - b_column.arrange(DOWN, buff = 0.35) - b_column.move_to(a_column) - b_column.set_color(BLUE) - plus.next_to(a_column_brackets, RIGHT) - VGroup(b_brackets, b_column).next_to(plus, RIGHT) - - bias_rect = SurroundingRectangle(bias) - - self.play(ShowCreation(bias_rect)) - self.play(FadeOut(bias_rect)) - self.play( - Write(plus), - Write(b_brackets), - Transform(self.bias[1].copy(), b_column[0]), - run_time = 1 - ) - self.play(LaggedStartMap( - FadeIn, VGroup(*b_column[1:]) - )) - self.wait() - - self.bias_plus = plus - self.b_brackets = b_brackets - self.b_column = b_column - - def apply_sigmoid(self): - expression_bounds = VGroup( - self.matrix_brackets[0], self.b_brackets[1] - ) - sigma = self.sigma.copy() - slp, srp = self.sigma_parens.copy() - - big_lp, big_rp = parens = TexMobject("()") - parens.scale(3) - parens.stretch_to_fit_height(expression_bounds.get_height()) - big_lp.next_to(expression_bounds, LEFT, SMALL_BUFF) - big_rp.next_to(expression_bounds, RIGHT, SMALL_BUFF) - parens.set_color(YELLOW) - - self.play( - sigma.scale, 2, - sigma.next_to, big_lp, LEFT, SMALL_BUFF, - Transform(slp, big_lp), - Transform(srp, big_rp), - ) - self.wait(2) - - self.big_sigma_group = VGroup(VGroup(sigma), slp, srp) - - def write_clean_final_expression(self): - self.fade_weighted_sum() - expression = TexMobject( - "\\textbf{a}^{(1)}", - "=", - "\\sigma", - "\\big(", - "\\textbf{W}", - "\\textbf{a}^{(0)}", - "+", - "\\textbf{b}", - "\\big)", - ) - expression.set_color_by_tex_to_color_map({ - "sigma" : YELLOW, - "big" : YELLOW, - "W" : GREEN, - "\\textbf{b}" : BLUE - }) - expression.next_to(self.big_sigma_group, UP, LARGE_BUFF) - a1, equals, sigma, lp, W, a0, plus, b, rp = expression - - neuron_anims = [] - neurons = VGroup(*self.network_mob.layers[1].neurons[1:]) - for neuron in neurons: - neuron_anims += [ - neuron.restore, - neuron.set_fill, None, random.random() - ] - neuron_anims += [ - neuron.edges_in.restore - ] - neurons.add_to_back(self.network_mob.layers[1].neurons[0]) - - self.play(ReplacementTransform( - VGroup( - self.top_matrix_row, self.lower_matrix_rows, - self.matrix_brackets - ).copy(), - VGroup(W), - )) - self.play(ReplacementTransform( - VGroup(self.a_column, self.a_column_brackets).copy(), - VGroup(VGroup(a0)), - )) - self.play( - ReplacementTransform( - VGroup(self.b_column, self.b_brackets).copy(), - VGroup(VGroup(b)) - ), - ReplacementTransform( - self.bias_plus.copy(), plus - ) - ) - self.play(ReplacementTransform( - self.big_sigma_group.copy(), - VGroup(sigma, lp, rp) - )) - self.wait() - self.play(*neuron_anims, run_time = 2) - self.play( - ReplacementTransform(neurons.copy(), a1), - FadeIn(equals) - ) - self.wait(2) - - def fade_weighted_sum(self): - self.play(*list(map(FadeOut, [ - self.a1_label, self.a1_equals, - self.sigma, self.sigma_parens, - self.weighted_sum, - self.bias_name, - self.sigmoid_name, - ]))) - - - ### - - def get_brackets(self, mob): - lb, rb = both = TexMobject("\\big[\\big]") - both.set_width(mob.get_width()) - both.stretch_to_fit_height(1.2*mob.get_height()) - lb.next_to(mob, LEFT, SMALL_BUFF) - rb.next_to(mob, RIGHT, SMALL_BUFF) - return both - -class HorrifiedMorty(Scene): - def construct(self): - morty = Mortimer() - morty.flip() - morty.scale(2) - - for mode in "horrified", "hesitant": - self.play( - morty.change, mode, - morty.look, UP, - ) - self.play(Blink(morty)) - self.wait(2) - -class SigmoidAppliedToVector(Scene): - def construct(self): - tex = TexMobject(""" - \\sigma \\left( - \\left[\\begin{array}{c} - x \\\\ y \\\\ z - \\end{array}\\right] - \\right) = - \\left[\\begin{array}{c} - \\sigma(x) \\\\ \\sigma(y) \\\\ \\sigma(z) - \\end{array}\\right] - """) - tex.set_width(FRAME_WIDTH - 1) - tex.to_edge(DOWN) - indices = it.chain( - [0], list(range(1, 5)), list(range(16, 16+4)), - list(range(25, 25+2)), [25+3], - list(range(29, 29+2)), [29+3], - list(range(33, 33+2)), [33+3], - ) - for i in indices: - tex[i].set_color(YELLOW) - self.add(tex) - self.wait() - -class EoLA3Wrapper(PiCreatureScene): - def construct(self): - morty = self.pi_creature - rect = ScreenRectangle(height = 5) - rect.next_to(morty, UP+LEFT) - rect.to_edge(UP, buff = LARGE_BUFF) - title = TextMobject("Essence of linear algebra") - title.next_to(rect, UP) - - self.play( - ShowCreation(rect), - FadeIn(title), - morty.change, "raise_right_hand", rect - ) - self.wait(4) - -class FeedForwardCode(ExternallyAnimatedScene): - pass - -class NeuronIsFunction(MoreHonestMNistNetworkPreview): - CONFIG = { - "network_mob_config" : { - "layer_to_layer_buff" : 2 - } - } - def construct(self): - self.setup_network_mob() - self.activate_network() - self.write_neuron_holds_a_number() - self.feed_in_new_image(8, 7) - self.neuron_is_function() - self.show_neuron_as_function() - self.fade_network_back_in() - self.network_is_a_function() - self.feed_in_new_image(9, 4) - self.wait(2) - - - def setup_network_mob(self): - self.network_mob.scale(0.7) - self.network_mob.to_edge(DOWN) - self.network_mob.shift(LEFT) - - def activate_network(self): - network_mob = self.network_mob - self.image_map = get_organized_images() - in_vect = self.image_map[3][0] - mnist_mob = MNistMobject(in_vect) - mnist_mob.next_to(network_mob, LEFT, MED_LARGE_BUFF, UP) - activations = self.network.get_activation_of_all_layers(in_vect) - for i, activation in enumerate(activations): - layer = self.network_mob.layers[i] - Transform( - layer, self.network_mob.get_active_layer(i, activation) - ).update(1) - self.add(mnist_mob) - - self.image_rect, self.curr_image = mnist_mob - - def write_neuron_holds_a_number(self): - neuron_word = TextMobject("Neuron") - arrow = Arrow(ORIGIN, DOWN, color = BLUE) - thing_words = TextMobject("Thing that holds \\\\ a number") - group = VGroup(neuron_word, arrow, thing_words) - group.arrange(DOWN) - group.to_corner(UP+RIGHT, buff = LARGE_BUFF) - - neuron = self.network_mob.layers[2].neurons[2] - decimal = DecimalNumber(neuron.get_fill_opacity()) - decimal.set_width(0.7*neuron.get_width()) - decimal.move_to(neuron) - neuron_group = VGroup(neuron, decimal) - neuron_group.save_state() - decimal.set_fill(opacity = 0) - - self.play( - neuron_group.restore, - neuron_group.scale, 3, - neuron_group.next_to, neuron_word, LEFT, - FadeIn(neuron_word), - GrowArrow(arrow), - FadeIn( - thing_words, run_time = 2, - rate_func = squish_rate_func(smooth, 0.3, 1) - ) - ) - self.wait() - self.play(neuron_group.restore) - - self.neuron_word = neuron_word - self.neuron_word_arrow = arrow - self.thing_words = thing_words - self.neuron = neuron - self.decimal = decimal - - def feed_in_new_image(self, digit, choice): - in_vect = self.image_map[digit][choice] - - args = [] - for s in "answer_rect", "curr_image", "image_rect": - if hasattr(self, s): - args.append(getattr(self, s)) - else: - args.append(VectorizedPoint()) - MoreHonestMNistNetworkPreview.reset_display(self, *args) - self.feed_in_image(in_vect) - - def neuron_is_function(self): - thing_words = self.thing_words - cross = Cross(thing_words) - function_word = TextMobject("Function") - function_word.move_to(thing_words, UP) - - self.play( - thing_words.fade, - ShowCreation(cross) - ) - self.play( - FadeIn(function_word), - VGroup(thing_words, cross).to_edge, DOWN, - ) - self.wait() - - self.function_word = function_word - - def show_neuron_as_function(self): - neuron = self.neuron.copy() - edges = neuron.edges_in.copy() - prev_layer = self.network_mob.layers[1].copy() - - arrow = Arrow(ORIGIN, RIGHT, color = BLUE) - arrow.next_to(neuron, RIGHT, SMALL_BUFF) - decimal = DecimalNumber(neuron.get_fill_opacity()) - decimal.next_to(arrow, RIGHT) - - self.play( - FadeOut(self.network_mob), - *list(map(Animation, [neuron, edges, prev_layer])) - ) - self.play(LaggedStartMap( - ShowCreationThenDestruction, - edges.copy().set_stroke(YELLOW, 4), - )) - self.play( - GrowArrow(arrow), - Transform(self.decimal, decimal) - ) - self.wait(2) - - self.non_faded_network_parts = VGroup( - neuron, edges, prev_layer - ) - self.neuron_arrow = arrow - - def fade_network_back_in(self): - anims = [ - FadeIn( - mob, - run_time = 2, - lag_ratio = 0.5 - ) - for mob in (self.network_mob.layers, self.network_mob.edge_groups) - ] - anims += [ - FadeOut(self.neuron_arrow), - FadeOut(self.decimal), - ] - anims.append(Animation(self.non_faded_network_parts)) - - self.play(*anims) - self.remove(self.non_faded_network_parts) - - def network_is_a_function(self): - neuron_word = self.neuron_word - network_word = TextMobject("Network") - network_word.set_color(YELLOW) - network_word.move_to(neuron_word) - - func_tex = TexMobject( - "f(a_0, \\dots, a_{783}) = ", - """\\left[ - \\begin{array}{c} - y_0 \\\\ \\vdots \\\\ y_{9} - \\end{array} - \\right]""" - ) - func_tex.to_edge(UP) - func_tex.shift(MED_SMALL_BUFF*LEFT) - - self.play( - ReplacementTransform(neuron_word, network_word), - FadeIn(func_tex) - ) - - ### - - def reset_display(self, answer_rect, image, image_rect): - #Don't do anything, just record these args - self.answer_rect = answer_rect - self.curr_image = image - self.image_rect = image_rect - return - -class ComplicationIsReassuring(TeacherStudentsScene): - def construct(self): - self.student_says( - "It kind of has to \\\\ be complicated, right?", - target_mode = "speaking", - student_index = 0 - ) - self.play(self.teacher.change, "happy") - self.wait(4) - -class NextVideo(MoreHonestMNistNetworkPreview, PiCreatureScene): - CONFIG = { - "network_mob_config" : { - "neuron_stroke_color" : WHITE, - "layer_to_layer_buff" : 2.5, - "brace_for_large_layers" : False, - } - } - def setup(self): - MoreHonestMNistNetworkPreview.setup(self) - PiCreatureScene.setup(self) - - def construct(self): - self.network_and_data() - self.show_next_video() - self.talk_about_subscription() - self.show_video_neural_network() - - def network_and_data(self): - morty = self.pi_creature - network_mob = self.network_mob - network_mob.to_edge(LEFT) - for obj in network_mob, self: - obj.remove(network_mob.output_labels) - network_mob.scale(0.7) - network_mob.shift(RIGHT) - edge_update = ContinualEdgeUpdate(network_mob) - - training_data, validation_data, test_data = load_data_wrapper() - data_mobs = VGroup() - for vect, num in test_data[:30]: - image = MNistMobject(vect) - image.set_height(0.7) - arrow = Arrow(ORIGIN, RIGHT, color = BLUE) - num_mob = TexMobject(str(num)) - group = Group(image, arrow, num_mob) - group.arrange(RIGHT, buff = SMALL_BUFF) - group.next_to(ORIGIN, RIGHT) - data_mobs.add(group) - - data_mobs.next_to(network_mob, UP) - - self.add(edge_update) - self.play(morty.change, "confused", network_mob) - self.wait(2) - for data_mob in data_mobs: - self.add(data_mob) - self.wait(0.2) - self.remove(data_mob) - - self.content = network_mob - self.edge_update = edge_update - - def show_next_video(self): - morty = self.pi_creature - content = self.content - - video = VideoIcon() - video.set_height(3) - video.set_fill(RED, 0.8) - video.next_to(morty, UP+LEFT) - - rect = SurroundingRectangle(video) - rect.set_stroke(width = 0) - rect.set_fill(BLACK, 0.5) - - words = TextMobject("On learning") - words.next_to(video, UP) - - if self.edge_update.internal_time < 1: - self.edge_update.internal_time = 2 - self.play( - content.set_height, 0.8*video.get_height(), - content.move_to, video, - morty.change, "raise_right_hand", - FadeIn(rect), - FadeIn(video), - ) - self.add_foreground_mobjects(rect, video) - self.wait(2) - self.play(Write(words)) - self.wait(2) - - self.video = Group(content, rect, video, words) - - def talk_about_subscription(self): - morty = self.pi_creature - morty.generate_target() - morty.target.change("hooray") - morty.target.rotate( - np.pi, axis = UP, about_point = morty.get_left() - ) - morty.target.shift(LEFT) - video = self.video - - - subscribe_word = TextMobject( - "Subscribe", "!", - arg_separator = "" - ) - bang = subscribe_word[1] - subscribe_word.to_corner(DOWN+RIGHT) - subscribe_word.shift(3*UP) - q_mark = TextMobject("?") - q_mark.move_to(bang, LEFT) - arrow = Arrow(ORIGIN, DOWN, color = RED, buff = 0) - arrow.next_to(subscribe_word, DOWN) - arrow.shift(MED_LARGE_BUFF * RIGHT) - - self.play( - Write(subscribe_word), - self.video.shift, 3*LEFT, - MoveToTarget(morty), - ) - self.play(GrowArrow(arrow)) - self.wait(2) - self.play(morty.change, "maybe", arrow) - self.play(Transform(bang, q_mark)) - self.wait(3) - - def show_video_neural_network(self): - morty = self.pi_creature - - network_mob, rect, video, words = self.video - network_mob.generate_target(use_deepcopy = True) - network_mob.target.set_height(5) - network_mob.target.to_corner(UP+LEFT) - neurons = VGroup(*network_mob.target.layers[-1].neurons[:2]) - neurons.set_stroke(width = 0) - - video.generate_target() - video.target.set_fill(opacity = 1) - video.target.set_height(neurons.get_height()) - video.target.move_to(neurons, LEFT) - - self.play( - MoveToTarget(network_mob), - MoveToTarget(video), - FadeOut(words), - FadeOut(rect), - morty.change, "raise_left_hand" - ) - - neuron_pairs = VGroup(*[ - VGroup(*network_mob.layers[-1].neurons[2*i:2*i+2]) - for i in range(1, 5) - ]) - for pair in neuron_pairs: - video = video.copy() - video.move_to(pair, LEFT) - pair.target = video - - self.play(LaggedStartMap( - MoveToTarget, neuron_pairs, - run_time = 3 - )) - self.play(morty.change, "shruggie") - self.wait(10) - - ### - -class NNPatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Desmos", - "Burt Humburg", - "CrypticSwarm", - "Juan Benet", - "Ali Yahya", - "William", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Samantha D. Suplee", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Markus Persson", - "Yoni Nazarathy", - "Ed Kellett", - "Joseph John Cox", - "Luc Ritchie", - "Andy Nichols", - "Harsev Singh", - "Mads Elvheim", - "Erik Sundell", - "Xueqi Li", - "David G. Stork", - "Tianyu Ge", - "Ted Suzman", - "Linh Tran", - "Andrew Busey", - "Michael McGuffin", - "John Haley", - "Ankalagon", - "Eric Lavault", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Ryan Dahl", - "Mark Govea", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ] - } - -class PiCreatureGesture(PiCreatureScene): - def construct(self): - self.play(self.pi_creature.change, "raise_right_hand") - self.wait(5) - self.play(self.pi_creature.change, "happy") - self.wait(4) - -class IntroduceReLU(IntroduceSigmoid): - CONFIG = { - "x_axis_label" : "$a$" - } - def construct(self): - self.setup_axes() - self.add_title() - self.add_graph() - self.old_school() - self.show_ReLU() - self.label_input_regions() - - def old_school(self): - sigmoid_graph = self.sigmoid_graph - sigmoid_title = VGroup( - self.sigmoid_name, - self.equation - ) - cross = Cross(sigmoid_title) - old_school = TextMobject("Old school") - old_school.to_corner(UP+RIGHT) - old_school.set_color(RED) - arrow = Arrow( - old_school.get_bottom(), - self.equation.get_right(), - color = RED - ) - - self.play(ShowCreation(cross)) - self.play( - Write(old_school, run_time = 1), - GrowArrow(arrow) - ) - self.wait(2) - self.play( - ApplyMethod( - VGroup(cross, sigmoid_title).shift, - FRAME_X_RADIUS*RIGHT, - rate_func = running_start - ), - FadeOut(old_school), - FadeOut(arrow), - ) - self.play(ShowCreation( - self.sigmoid_graph, - rate_func = lambda t : smooth(1-t), - remover = True - )) - - def show_ReLU(self): - graph = VGroup( - Line( - self.coords_to_point(-7, 0), - self.coords_to_point(0, 0), - ), - Line( - self.coords_to_point(0, 0), - self.coords_to_point(4, 4), - ), - ) - graph.set_color(YELLOW) - char = self.x_axis_label.replace("$", "") - equation = TextMobject("ReLU($%s$) = max$(0, %s)$"%(char, char)) - equation.shift(FRAME_X_RADIUS*LEFT/2) - equation.to_edge(UP) - equation.add_background_rectangle() - name = TextMobject("Rectified linear unit") - name.move_to(equation) - name.add_background_rectangle() - - self.play(Write(equation)) - self.play(ShowCreation(graph), Animation(equation)) - self.wait(2) - self.play( - Write(name), - equation.shift, DOWN - ) - self.wait(2) - - self.ReLU_graph = graph - - def label_input_regions(self): - l1, l2 = self.ReLU_graph - neg_words = TextMobject("Inactive") - neg_words.set_color(RED) - neg_words.next_to(self.coords_to_point(-2, 0), UP) - - pos_words = TextMobject("Same as $f(a) = a$") - pos_words.set_color(GREEN) - pos_words.next_to( - self.coords_to_point(1, 1), - DOWN+RIGHT - ) - - self.revert_to_original_skipping_status() - self.play(ShowCreation(l1.copy().set_color(RED))) - self.play(Write(neg_words)) - self.wait() - self.play(ShowCreation(l2.copy().set_color(GREEN))) - self.play(Write(pos_words)) - self.wait(2) - -class CompareSigmoidReLUOnDeepNetworks(PiCreatureScene): - def construct(self): - morty, lisha = self.morty, self.lisha - sigmoid_graph = FunctionGraph( - sigmoid, - x_min = -5, - x_max = 5, - ) - sigmoid_graph.stretch_to_fit_width(3) - sigmoid_graph.set_color(YELLOW) - sigmoid_graph.next_to(lisha, UP+LEFT) - sigmoid_graph.shift_onto_screen() - sigmoid_name = TextMobject("Sigmoid") - sigmoid_name.next_to(sigmoid_graph, UP) - sigmoid_graph.add(sigmoid_name) - - slow_learner = TextMobject("Slow learner") - slow_learner.set_color(YELLOW) - slow_learner.to_corner(UP+LEFT) - slow_arrow = Arrow( - slow_learner.get_bottom(), - sigmoid_graph.get_top(), - ) - - relu_graph = VGroup( - Line(2*LEFT, ORIGIN), - Line(ORIGIN, np.sqrt(2)*(RIGHT+UP)), - ) - relu_graph.set_color(BLUE) - relu_graph.next_to(lisha, UP+RIGHT) - relu_name = TextMobject("ReLU") - relu_name.move_to(relu_graph, UP) - relu_graph.add(relu_name) - - network_mob = NetworkMobject(Network( - sizes = [6, 4, 5, 4, 3, 5, 2] - )) - network_mob.scale(0.8) - network_mob.to_edge(UP, buff = MED_SMALL_BUFF) - network_mob.shift(RIGHT) - edge_update = ContinualEdgeUpdate( - network_mob, stroke_width_exp = 1, - ) - - self.play( - FadeIn(sigmoid_name), - ShowCreation(sigmoid_graph), - lisha.change, "raise_left_hand", - morty.change, "pondering" - ) - self.play( - Write(slow_learner, run_time = 1), - GrowArrow(slow_arrow) - ) - self.wait() - self.play( - FadeIn(relu_name), - ShowCreation(relu_graph), - lisha.change, "raise_right_hand", - morty.change, "thinking" - ) - self.play(FadeIn(network_mob)) - self.add(edge_update) - self.wait(10) - - - - ### - def create_pi_creatures(self): - morty = Mortimer() - morty.shift(FRAME_X_RADIUS*RIGHT/2).to_edge(DOWN) - lisha = PiCreature(color = BLUE_C) - lisha.shift(FRAME_X_RADIUS*LEFT/2).to_edge(DOWN) - self.morty, self.lisha = morty, lisha - return morty, lisha - -class ShowAmplify(PiCreatureScene): - def construct(self): - morty = self.pi_creature - rect = ScreenRectangle(height = 5) - rect.to_corner(UP+LEFT) - rect.shift(DOWN) - email = TextMobject("3blue1brown@amplifypartners.com") - email.next_to(rect, UP) - - self.play( - ShowCreation(rect), - morty.change, "raise_right_hand" - ) - self.wait(2) - self.play(Write(email)) - self.play(morty.change, "happy", rect) - self.wait(10) - -class Thumbnail(NetworkScene): - CONFIG = { - "network_mob_config" : { - 'neuron_stroke_color' : WHITE, - 'layer_to_layer_buff': 1.25, - }, - } - def construct(self): - network_mob = self.network_mob - network_mob.set_height(FRAME_HEIGHT - 1) - for layer in network_mob.layers: - layer.neurons.set_stroke(width = 5) - - network_mob.set_height(5) - network_mob.to_edge(DOWN) - network_mob.to_edge(LEFT, buff=1) - - subtitle = TextMobject( - "From the\\\\", - "ground up\\\\", - ) - # subtitle.arrange( - # DOWN, - # buff=0.25, - # aligned_edge=LEFT, - # ) - subtitle.set_color(YELLOW) - subtitle.set_height(2.75) - subtitle.next_to(network_mob, RIGHT, buff=MED_LARGE_BUFF) - - edge_update = ContinualEdgeUpdate( - network_mob, - max_stroke_width = 10, - stroke_width_exp = 4, - ) - edge_update.internal_time = 3 - edge_update.update(0) - - for mob in network_mob.family_members_with_points(): - if mob.get_stroke_width() < 2: - mob.set_stroke(width=2) - - - title = TextMobject("Neural Networks") - title.scale(3) - title.to_edge(UP) - - self.add(network_mob) - self.add(subtitle) - self.add(title) - - - - - - - - - - - - - - diff --git a/from_3b1b/old/nn/part2.py b/from_3b1b/old/nn/part2.py deleted file mode 100644 index 9cdf8757..00000000 --- a/from_3b1b/old/nn/part2.py +++ /dev/null @@ -1,3792 +0,0 @@ - -import sys -import os.path -import cv2 - -from manimlib.imports import * - -from nn.network import * -from nn.part1 import * - -POSITIVE_COLOR = BLUE -NEGATIVE_COLOR = RED - -def get_training_image_group(train_in, train_out): - image = MNistMobject(train_in) - image.set_height(1) - arrow = Vector(RIGHT, color = BLUE, buff = 0) - output = np.argmax(train_out) - output_tex = TexMobject(str(output)).scale(1.5) - result = Group(image, arrow, output_tex) - result.arrange(RIGHT) - result.to_edge(UP) - return result - -def get_decimal_vector(nums, with_dots = True): - decimals = VGroup() - for num in nums: - decimal = DecimalNumber(num) - if num > 0: - decimal.set_color(POSITIVE_COLOR) - else: - decimal.set_color(NEGATIVE_COLOR) - decimals.add(decimal) - contents = VGroup(*decimals) - if with_dots: - dots = TexMobject("\\vdots") - contents.submobjects.insert(len(decimals)/2, dots) - contents.arrange(DOWN) - lb, rb = brackets = TexMobject("\\big[", "\\big]") - brackets.scale(2) - brackets.stretch_to_fit_height(1.2*contents.get_height()) - lb.next_to(contents, LEFT, SMALL_BUFF) - rb.next_to(contents, RIGHT, SMALL_BUFF) - - result = VGroup(lb, contents, brackets) - result.lb = lb - result.rb = rb - result.brackets = brackets - result.decimals = decimals - result.contents = contents - if with_dots: - result.dots = dots - return result - - -######## - -class ShowLastVideo(TeacherStudentsScene): - def construct(self): - frame = ScreenRectangle() - frame.set_height(4.5) - frame.to_corner(UP+LEFT) - title = TextMobject("But what \\emph{is} a Neural Network") - title.move_to(frame) - title.to_edge(UP) - frame.next_to(title, DOWN) - - assumption_words = TextMobject( - "I assume you've\\\\ watched this" - ) - assumption_words.move_to(frame) - assumption_words.to_edge(RIGHT) - arrow = Arrow(RIGHT, LEFT, color = BLUE) - arrow.next_to(assumption_words, LEFT) - - - self.play( - ShowCreation(frame), - self.teacher.change, "raise_right_hand" - ) - self.play( - Write(title), - self.get_student_changes(*["thinking"]*3) - ) - self.play( - Animation(title), - GrowArrow(arrow), - FadeIn(assumption_words) - ) - self.wait(5) - -class ShowPlan(Scene): - def construct(self): - title = TextMobject("Plan").scale(1.5) - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.set_color(WHITE) - h_line.next_to(title, DOWN) - self.add(title, h_line) - - items = VGroup(*[ - TextMobject("$\\cdot$ %s"%s) - for s in [ - "Recap", - "Gradient descent", - "Analyze this network", - "Where to learn more", - "Research corner", - ] - ]) - items.arrange(DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT) - items.to_edge(LEFT, buff = LARGE_BUFF) - - rect = SurroundingRectangle(VGroup(*items[1:3])) - - self.add(items) - self.wait() - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - for item in items[1:]: - to_fade = VGroup(*[i for i in items if i is not item]) - self.play( - to_fade.set_fill, None, 0.5, - item.set_fill, None, 1, - ) - self.wait() - -class BeginAndEndRecap(Scene): - def construct(self): - recap = TexMobject( - "\\langle", "\\text{Recap}", "\\rangle" - ) - new_langle = TexMobject("\\langle/") - new_langle.scale(2) - recap.scale(2) - new_langle.move_to(recap[0], RIGHT) - - self.add(recap) - self.wait(2) - self.play(Transform(recap[0], new_langle)) - self.wait(2) - -class PreviewLearning(NetworkScene): - CONFIG = { - "layer_sizes" : DEFAULT_LAYER_SIZES, - "network_mob_config" : { - "neuron_to_neuron_buff" : SMALL_BUFF, - "layer_to_layer_buff" : 2, - "edge_stroke_width" : 1, - "neuron_stroke_color" : WHITE, - "neuron_stroke_width" : 2, - "neuron_fill_color" : WHITE, - "average_shown_activation_of_large_layer" : False, - "edge_propogation_color" : GREEN, - "edge_propogation_time" : 2, - "include_output_labels" : True, - }, - "n_examples" : 15, - "max_stroke_width" : 3, - "stroke_width_exp" : 3, - "eta" : 3.0, - "positive_edge_color" : BLUE, - "negative_edge_color" : RED, - "positive_change_color" : BLUE_C, - "negative_change_color" : average_color(*2*[RED] + [YELLOW]), - "default_activate_run_time" : 1.5, - } - def construct(self): - self.initialize_network() - self.add_training_words() - self.show_training() - - def initialize_network(self): - self.network_mob.scale(0.7) - self.network_mob.to_edge(DOWN) - self.color_network_edges() - - def add_training_words(self): - words = TextMobject("Training in \\\\ progress$\\dots$") - words.scale(1.5) - words.to_corner(UP+LEFT) - - self.add(words) - - def show_training(self): - training_data, validation_data, test_data = load_data_wrapper() - for train_in, train_out in training_data[:self.n_examples]: - image = get_training_image_group(train_in, train_out) - self.activate_network(train_in, FadeIn(image)) - self.backprop_one_example( - train_in, train_out, - FadeOut(image), self.network_mob.layers.restore - ) - - def activate_network(self, train_in, *added_anims, **kwargs): - network_mob = self.network_mob - layers = network_mob.layers - layers.save_state() - activations = self.network.get_activation_of_all_layers(train_in) - active_layers = [ - self.network_mob.get_active_layer(i, vect) - for i, vect in enumerate(activations) - ] - all_edges = VGroup(*it.chain(*network_mob.edge_groups)) - run_time = kwargs.get("run_time", self.default_activate_run_time) - edge_animation = LaggedStartMap( - ShowCreationThenDestruction, - all_edges.copy().set_fill(YELLOW), - run_time = run_time, - lag_ratio = 0.3, - remover = True, - ) - layer_animation = Transform( - VGroup(*layers), VGroup(*active_layers), - run_time = run_time, - lag_ratio = 0.5, - rate_func=linear, - ) - - self.play(edge_animation, layer_animation, *added_anims) - - def backprop_one_example(self, train_in, train_out, *added_outro_anims): - network_mob = self.network_mob - nabla_b, nabla_w = self.network.backprop(train_in, train_out) - neuron_groups = VGroup(*[ - layer.neurons - for layer in network_mob.layers[1:] - ]) - delta_neuron_groups = neuron_groups.copy() - edge_groups = network_mob.edge_groups - delta_edge_groups = VGroup(*[ - edge_group.copy() - for edge_group in edge_groups - ]) - tups = list(zip( - it.count(), nabla_b, nabla_w, - delta_neuron_groups, neuron_groups, - delta_edge_groups, edge_groups - )) - pc_color = self.positive_change_color - nc_color = self.negative_change_color - for i, nb, nw, delta_neurons, neurons, delta_edges, edges in reversed(tups): - shown_nw = self.get_adjusted_first_matrix(nw) - if np.max(shown_nw) == 0: - shown_nw = (2*np.random.random(shown_nw.shape)-1)**5 - max_b = np.max(np.abs(nb)) - max_w = np.max(np.abs(shown_nw)) - for neuron, b in zip(delta_neurons, nb): - color = nc_color if b > 0 else pc_color - # neuron.set_fill(color, abs(b)/max_b) - neuron.set_stroke(color, 3) - for edge, w in zip(delta_edges.split(), shown_nw.T.flatten()): - edge.set_stroke( - nc_color if w > 0 else pc_color, - 3*abs(w)/max_w - ) - edge.rotate_in_place(np.pi) - if i == 2: - delta_edges.submobjects = [ - delta_edges[j] - for j in np.argsort(shown_nw.T.flatten()) - ] - network = self.network - network.weights[i] -= self.eta*nw - network.biases[i] -= self.eta*nb - - self.play( - ShowCreation( - delta_edges, lag_ratio = 0 - ), - FadeIn(delta_neurons), - run_time = 0.5 - ) - edge_groups.save_state() - self.color_network_edges() - self.remove(edge_groups) - self.play(*it.chain( - [ReplacementTransform( - edge_groups.saved_state, edge_groups, - )], - list(map(FadeOut, [delta_edge_groups, delta_neuron_groups])), - added_outro_anims, - )) - - ##### - - def get_adjusted_first_matrix(self, matrix): - n = self.network_mob.max_shown_neurons - if matrix.shape[1] > n: - half = matrix.shape[1]/2 - return matrix[:,half-n/2:half+n/2] - else: - return matrix - - def color_network_edges(self): - layers = self.network_mob.layers - weight_matrices = self.network.weights - for layer, matrix in zip(layers[1:], weight_matrices): - matrix = self.get_adjusted_first_matrix(matrix) - matrix_max = np.max(matrix) - for neuron, row in zip(layer.neurons, matrix): - for edge, w in zip(neuron.edges_in, row): - if w > 0: - color = self.positive_edge_color - else: - color = self.negative_edge_color - msw = self.max_stroke_width - swe = self.stroke_width_exp - sw = msw*(abs(w)/matrix_max)**swe - sw = min(sw, msw) - edge.set_stroke(color, sw) - - def get_edge_animation(self): - edges = VGroup(*it.chain(*self.network_mob.edge_groups)) - return LaggedStartMap( - ApplyFunction, edges, - lambda mob : ( - lambda m : m.rotate_in_place(np.pi/12).set_color(YELLOW), - mob - ), - rate_func = wiggle - ) - -class BackpropComingLaterWords(Scene): - def construct(self): - words = TextMobject("(Backpropagation be \\\\ the next video)") - words.set_width(FRAME_WIDTH-1) - words.to_edge(DOWN) - self.add(words) - -class TrainingVsTestData(Scene): - CONFIG = { - "n_examples" : 10, - "n_new_examples_shown" : 10, - } - def construct(self): - self.initialize_data() - self.introduce_all_data() - self.subdivide_into_training_and_testing() - self.scroll_through_much_data() - - def initialize_data(self): - training_data, validation_data, test_data = load_data_wrapper() - self.data = training_data - self.curr_index = 0 - - def get_examples(self): - ci = self.curr_index - self.curr_index += self.n_examples - group = Group(*it.starmap( - get_training_image_group, - self.data[ci:ci+self.n_examples] - )) - group.arrange(DOWN) - group.scale(0.5) - return group - - def introduce_all_data(self): - training_examples, test_examples = [ - self.get_examples() for x in range(2) - ] - - training_examples.next_to(ORIGIN, LEFT) - test_examples.next_to(ORIGIN, RIGHT) - self.play( - LaggedStartMap(FadeIn, training_examples), - LaggedStartMap(FadeIn, test_examples), - ) - - self.training_examples = training_examples - self.test_examples = test_examples - - def subdivide_into_training_and_testing(self): - training_examples = self.training_examples - test_examples = self.test_examples - for examples in training_examples, test_examples: - examples.generate_target() - training_examples.target.shift(2*LEFT) - test_examples.target.shift(2*RIGHT) - - train_brace = Brace(training_examples.target, LEFT) - train_words = train_brace.get_text("Train on \\\\ these") - test_brace = Brace(test_examples.target, RIGHT) - test_words = test_brace.get_text("Test on \\\\ these") - - bools = [True]*(len(test_examples)-1) + [False] - random.shuffle(bools) - marks = VGroup() - for is_correct, test_example in zip(bools, test_examples.target): - if is_correct: - mark = TexMobject("\\checkmark") - mark.set_color(GREEN) - else: - mark = TexMobject("\\times") - mark.set_color(RED) - mark.next_to(test_example, LEFT) - marks.add(mark) - - self.play( - MoveToTarget(training_examples), - GrowFromCenter(train_brace), - FadeIn(train_words) - ) - self.wait() - self.play( - MoveToTarget(test_examples), - GrowFromCenter(test_brace), - FadeIn(test_words) - ) - self.play(Write(marks)) - self.wait() - - def scroll_through_much_data(self): - training_examples = self.training_examples - colors = color_gradient([BLUE, YELLOW], self.n_new_examples_shown) - for color in colors: - new_examples = self.get_examples() - new_examples.move_to(training_examples) - for train_ex, new_ex in zip(training_examples, new_examples): - self.remove(train_ex) - self.add(new_ex) - new_ex[0][0].set_color(color) - self.wait(1./30) - training_examples = new_examples - -class MNistDescription(Scene): - CONFIG = { - "n_grids" : 5, - "n_rows_per_grid" : 10, - "n_cols_per_grid" : 8, - "time_per_example" : 1./120, - } - def construct(self): - title = TextMobject("MNIST Database") - title.scale(1.5) - title.set_color(BLUE) - authors = TextMobject("LeCun, Cortes and Burges") - authors.next_to(title, DOWN) - link_words = TextMobject("(links in the description)") - link_words.next_to(authors, DOWN, MED_LARGE_BUFF) - arrows = VGroup(*[Vector(DOWN) for x in range(4)]) - arrows.arrange(RIGHT, buff = LARGE_BUFF) - arrows.next_to(link_words, DOWN) - arrows.set_color(BLUE) - - word_group = VGroup(title, authors, link_words, arrows) - word_group.center() - - self.add(title, authors) - self.play( - Write(link_words, run_time = 2), - LaggedStartMap(GrowArrow, arrows), - ) - self.wait() - - training_data, validation_data, test_data = load_data_wrapper() - epc = self.n_rows_per_grid*self.n_cols_per_grid - training_data_groups = [ - training_data[i*epc:(i+1)*epc] - for i in range(self.n_grids) - ] - - for i, td_group in enumerate(training_data_groups): - print(i) - group = Group(*[ - self.get_digit_pair(v_in, v_out) - for v_in, v_out in td_group - ]) - group.arrange_in_grid( - n_rows = self.n_rows_per_grid, - ) - group.set_height(FRAME_HEIGHT - 1) - if i == 0: - self.play( - LaggedStartMap(FadeIn, group), - FadeOut(word_group), - ) - else: - pairs = list(zip(last_group, group)) - random.shuffle(pairs) - time = 0 - for t1, t2 in pairs: - time += self.time_per_example - self.remove(t1) - self.add(t2) - if time > self.frame_duration: - self.wait(self.frame_duration) - time = 0 - last_group = group - - - def get_digit_pair(self, v_in, v_out): - tup = Group(*TexMobject("(", "00", ",", "0", ")")) - tup.scale(2) - # image = PixelsFromVect(v_in) - # image.add(SurroundingRectangle(image, color = BLUE, buff = SMALL_BUFF)) - image = MNistMobject(v_in) - label = TexMobject(str(np.argmax(v_out))) - image.replace(tup[1]) - tup.submobjects[1] = image - label.replace(tup[3], dim_to_match = 1) - tup.submobjects[3] = label - - return tup - -class NotSciFi(TeacherStudentsScene): - def construct(self): - students = self.students - self.student_says( - "Machines learning?!?", - student_index = 0, - target_mode = "pleading", - run_time = 1, - ) - bubble = students[0].bubble - students[0].bubble = None - self.student_says( - "Should we \\\\ be worried?", student_index = 2, - target_mode = "confused", - bubble_kwargs = {"direction" : LEFT}, - run_time = 1, - ) - self.wait() - students[0].bubble = bubble - self.teacher_says( - "It's actually \\\\ just calculus.", - run_time = 1 - ) - self.teacher.bubble = None - self.wait() - self.student_says( - "Even worse!", - target_mode = "horrified", - bubble_kwargs = { - "direction" : LEFT, - "width" : 3, - "height" : 2, - }, - ) - self.wait(2) - -class FunctionMinmization(GraphScene): - CONFIG = { - "x_labeled_nums" : list(range(-1, 10)), - } - def construct(self): - self.setup_axes() - title = TextMobject("Finding minima") - title.to_edge(UP) - self.add(title) - - def func(x): - x -= 4.5 - return 0.03*(x**4 - 16*x**2) + 0.3*x + 4 - graph = self.get_graph(func) - graph_label = self.get_graph_label(graph, "C(x)") - self.add(graph, graph_label) - - dots = VGroup(*[ - Dot().move_to(self.input_to_graph_point(x, graph)) - for x in range(10) - ]) - dots.set_color_by_gradient(YELLOW, RED) - - def update_dot(dot, dt): - x = self.x_axis.point_to_number(dot.get_center()) - slope = self.slope_of_tangent(x, graph) - x -= slope*dt - dot.move_to(self.input_to_graph_point(x, graph)) - - self.add(*[ - Mobject.add_updater(dot, update_dot) - for dot in dots - ]) - self.wait(10) - -class ChangingDecimalWithColor(ChangingDecimal): - def interpolate_mobject(self, alpha): - ChangingDecimal.interpolate_mobject(self, alpha) - num = self.number_update_func(alpha) - self.decimal_number.set_fill( - interpolate_color(BLACK, WHITE, 0.5+num*0.5), - opacity = 1 - ) - -class IntroduceCostFunction(PreviewLearning): - CONFIG = { - "max_stroke_width" : 2, - "full_edges_exp" : 5, - "n_training_examples" : 100, - "bias_color" : MAROON_B - } - def construct(self): - self.network_mob.shift(LEFT) - self.isolate_one_neuron() - self.reminder_of_weights_and_bias() - self.bring_back_rest_of_network() - self.feed_in_example() - self.make_fun_of_output() - self.need_a_cost_function() - self.fade_all_but_last_layer() - self.break_down_cost_function() - self.show_small_cost_output() - self.average_over_all_training_data() - - def isolate_one_neuron(self): - network_mob = self.network_mob - neurons = VGroup(*it.chain(*[ - layer.neurons - for layer in network_mob.layers[1:] - ])) - edges = VGroup(*it.chain(*network_mob.edge_groups)) - neuron = network_mob.layers[1].neurons[7] - neurons.remove(neuron) - edges.remove(*neuron.edges_in) - output_labels = network_mob.output_labels - kwargs = { - "lag_ratio" : 0.5, - "run_time" : 2, - } - self.play( - FadeOut(edges, **kwargs), - FadeOut(neurons, **kwargs), - FadeOut(output_labels, **kwargs), - Animation(neuron), - neuron.edges_in.set_stroke, None, 2, - ) - - self.neuron = neuron - - def reminder_of_weights_and_bias(self): - neuron = self.neuron - layer0 = self.network_mob.layers[0] - active_layer0 = self.network_mob.get_active_layer( - 0, np.random.random(len(layer0.neurons)) - ) - prev_neurons = layer0.neurons - - weighted_edges = VGroup(*[ - self.color_edge_randomly(edge.copy(), exp = 1) - for edge in neuron.edges_in - ]) - - formula = TexMobject( - "=", "\\sigma(", - "w_1", "a_1", "+", - "w_2", "a_2", "+", - "\\cdots", "+", - "w_n", "a_n", "+", "b", ")" - ) - w_labels = formula.get_parts_by_tex("w_") - a_labels = formula.get_parts_by_tex("a_") - b = formula.get_part_by_tex("b") - sigma = VGroup( - formula.get_part_by_tex("\\sigma"), - formula.get_part_by_tex(")"), - ) - symbols = VGroup(*[ - formula.get_parts_by_tex(tex) - for tex in ("=", "+", "dots") - ]) - - w_labels.set_color(self.positive_edge_color) - b.set_color(self.bias_color) - sigma.set_color(YELLOW) - formula.next_to(neuron, RIGHT) - - weights_word = TextMobject("Weights") - weights_word.next_to(neuron.edges_in, RIGHT, aligned_edge = UP) - weights_word.set_color(self.positive_edge_color) - weights_arrow_to_edges = Arrow( - weights_word.get_bottom(), - neuron.edges_in[0].get_center(), - color = self.positive_edge_color - ) - - weights_arrow_to_syms = VGroup(*[ - Arrow( - weights_word.get_bottom(), - w_label.get_top(), - color = self.positive_edge_color - ) - for w_label in w_labels - ]) - - bias_word = TextMobject("Bias") - bias_arrow = Vector(DOWN, color = self.bias_color) - bias_arrow.next_to(b, UP, SMALL_BUFF) - bias_word.next_to(bias_arrow, UP, SMALL_BUFF) - bias_word.set_color(self.bias_color) - - self.play( - Transform(layer0, active_layer0), - neuron.set_fill, None, 0.5, - FadeIn(formula), - run_time = 2, - lag_ratio = 0.5 - ) - self.play(LaggedStartMap( - ShowCreationThenDestruction, - neuron.edges_in.copy().set_stroke(YELLOW, 3), - run_time = 1.5, - lag_ratio = 0.7, - remover = True - )) - self.play( - Write(weights_word), - *list(map(GrowArrow, weights_arrow_to_syms)), - run_time = 1 - ) - self.wait() - self.play( - ReplacementTransform( - w_labels.copy(), weighted_edges, - remover = True - ), - Transform(neuron.edges_in, weighted_edges), - ReplacementTransform( - weights_arrow_to_syms, - VGroup(weights_arrow_to_edges), - ) - ) - self.wait() - self.play( - Write(bias_word), - GrowArrow(bias_arrow), - run_time = 1 - ) - self.wait(2) - - ## Initialize randomly - w_random = TextMobject("Initialize randomly") - w_random.move_to(weights_word, LEFT) - b_random = w_random.copy() - b_random.move_to(bias_word, RIGHT) - - self.play( - Transform(weights_word, w_random), - Transform(bias_word, b_random), - *[ - ApplyFunction(self.color_edge_randomly, edge) - for edge in neuron.edges_in - ] - ) - self.play(LaggedStartMap( - ApplyMethod, neuron.edges_in, - lambda m : (m.rotate_in_place, np.pi/12), - rate_func = wiggle, - run_time = 2 - )) - self.play(*list(map(FadeOut, [ - weights_word, weights_arrow_to_edges, - bias_word, bias_arrow, - formula - ]))) - - def bring_back_rest_of_network(self): - network_mob = self.network_mob - neurons = VGroup(*network_mob.layers[1].neurons) - neurons.remove(self.neuron) - for layer in network_mob.layers[2:]: - neurons.add(*layer.neurons) - neurons.add(*network_mob.output_labels) - - edges = VGroup(*network_mob.edge_groups[0]) - edges.remove(*self.neuron.edges_in) - for edge_group in network_mob.edge_groups[1:]: - edges.add(*edge_group) - - for edge in edges: - self.color_edge_randomly(edge, exp = self.full_edges_exp) - - self.play(*[ - LaggedStartMap( - FadeIn, group, - run_time = 3, - ) - for group in (neurons, edges) - ]) - - def feed_in_example(self): - vect = get_organized_images()[3][5] - image = PixelsFromVect(vect) - image.to_corner(UP+LEFT) - rect = SurroundingRectangle(image, color = BLUE) - neurons = VGroup(*[ - Circle( - stroke_width = 1, - stroke_color = WHITE, - fill_opacity = pixel.fill_rgb[0], - fill_color = WHITE, - radius = pixel.get_height()/2 - ).move_to(pixel) - for pixel in image - ]) - layer0= self.network_mob.layers[0] - n = self.network_mob.max_shown_neurons - neurons.target = VGroup(*it.chain( - VGroup(*layer0.neurons[:n/2]).set_fill(opacity = 0), - [ - VectorizedPoint(layer0.dots.get_center()) - for x in range(len(neurons)-n) - ], - VGroup(*layer0.neurons[-n/2:]).set_fill(opacity = 0), - )) - - self.play( - self.network_mob.shift, 0.5*RIGHT, - ShowCreation(rect), - LaggedStartMap(DrawBorderThenFill, image), - LaggedStartMap(DrawBorderThenFill, neurons), - run_time = 1 - ) - self.play( - MoveToTarget( - neurons, lag_ratio = 0.5, - remover = True - ), - layer0.neurons.set_fill, None, 0, - ) - self.activate_network(vect, run_time = 2) - - self.image = image - self.image_rect = rect - - def make_fun_of_output(self): - last_layer = self.network_mob.layers[-1].neurons - last_layer.add(self.network_mob.output_labels) - rect = SurroundingRectangle(last_layer) - words = TextMobject("Utter trash") - words.next_to(rect, DOWN, aligned_edge = LEFT) - VGroup(rect, words).set_color(YELLOW) - - self.play( - ShowCreation(rect), - Write(words, run_time = 2) - ) - self.wait() - - self.trash_rect = rect - self.trash_words = words - - def need_a_cost_function(self): - vect = np.zeros(10) - vect[3] = 1 - output_labels = self.network_mob.output_labels - desired_layer = self.network_mob.get_active_layer(-1, vect) - layer = self.network_mob.layers[-1] - layer.add(output_labels) - desired_layer.add(output_labels.copy()) - desired_layer.shift(2*RIGHT) - layers = VGroup(layer, desired_layer) - - words = TextMobject( - "What's the", "``cost''\\\\", "of this difference?", - ) - words.set_color_by_tex("cost", RED) - words.next_to(layers, UP) - words.to_edge(UP) - words.shift_onto_screen() - double_arrow = DoubleArrow( - layer.get_right(), - desired_layer.get_left(), - color = RED - ) - - self.play(FadeIn(words)) - self.play(ReplacementTransform(layer.copy(), desired_layer)) - self.play(GrowFromCenter(double_arrow)) - self.wait(2) - - self.desired_last_layer = desired_layer - self.diff_arrow = double_arrow - - def fade_all_but_last_layer(self): - network_mob = self.network_mob - to_fade = VGroup(*it.chain(*list(zip( - network_mob.layers[:-1], - network_mob.edge_groups - )))) - - self.play(LaggedStartMap(FadeOut, to_fade, run_time = 1)) - - def break_down_cost_function(self): - layer = self.network_mob.layers[-1] - desired_layer = self.desired_last_layer - decimal_groups = VGroup(*[ - self.num_vect_to_decimals(self.layer_to_num_vect(l)) - for l in (layer, desired_layer) - ]) - - terms = VGroup() - symbols = VGroup() - for d1, d2 in zip(*decimal_groups): - term = TexMobject( - "(", "0.00", "-", "0.00", ")^2", "+", - ) - term.scale(d1.get_height()/term[1].get_height()) - for d, i in (d1, 1), (d2, 3): - term.submobjects[i] = d.move_to(term[i]) - terms.add(term) - symbols.add(*term) - symbols.remove(d1, d2) - last_plus = term[-1] - for mob in terms[-1], symbols: - mob.remove(last_plus) - terms.arrange( - DOWN, buff = SMALL_BUFF, - aligned_edge = LEFT - ) - terms.set_height(1.5*layer.get_height()) - terms.next_to(layer, LEFT, buff = 2) - - image_group = Group(self.image, self.image_rect) - image_group.generate_target() - image_group.target.scale(0.5) - cost_of = TextMobject("Cost of").set_color(RED) - cost_group = VGroup(cost_of, image_group.target) - cost_group.arrange(RIGHT) - brace = Brace(terms, LEFT) - cost_group.next_to(brace, LEFT) - - self.revert_to_original_skipping_status() - self.play(*[ - ReplacementTransform( - VGroup(*l.neurons[:10]).copy(), dg - ) - for l, dg in zip([layer, desired_layer], decimal_groups) - ]) - self.play( - FadeIn(symbols), - MoveToTarget(image_group), - FadeIn(cost_of), - GrowFromCenter(brace), - ) - self.wait() - - self.decimal_groups = decimal_groups - self.image_group = image_group - self.cost_group = VGroup(cost_of, image_group) - self.brace = brace - - def show_small_cost_output(self): - decimals, desired_decimals = self.decimal_groups - neurons = self.network_mob.layers[-1].neurons - brace = self.brace - cost_group = self.cost_group - - neurons.save_state() - cost_group.save_state() - brace.save_state() - brace.generate_target() - - arrows = VGroup(*[ - Arrow(ORIGIN, LEFT).next_to(d, LEFT, MED_LARGE_BUFF) - for d in decimals - ]) - arrows.set_color(WHITE) - - def generate_term_update_func(decimal, desired_decimal): - return lambda a : (decimal.number - desired_decimal.number)**2 - - terms = VGroup() - term_updates = [] - for arrow, d1, d2 in zip(arrows, *self.decimal_groups): - term = DecimalNumber(0, num_decimal_places = 4) - term.set_height(d1.get_height()) - term.next_to(arrow, LEFT) - term.num_update_func = generate_term_update_func(d1, d2) - terms.add(term) - term_updates.append(ChangingDecimalWithColor( - term, term.num_update_func, - num_decimal_places = 4 - )) - brace.target.next_to(terms, LEFT) - - sum_term = DecimalNumber(0) - sum_term.next_to(brace.target, LEFT) - sum_term.set_color(RED) - def sum_update(alpha): - return sum([ - (d1.number - d2.number)**2 - for d1, d2 in zip(*self.decimal_groups) - ]) - term_updates.append(ChangingDecimal(sum_term, sum_update)) - for update in term_updates: - update.interpolate_mobject(0) - - target_vect = 0.1*np.random.random(10) - target_vect[3] = 0.97 - - def generate_decimal_update_func(start, target): - return lambda a : interpolate(start, target, a) - - update_decimals = [ - ChangingDecimalWithColor( - decimal, - generate_decimal_update_func(decimal.number, t) - ) - for decimal, t in zip(decimals, target_vect) - ] - - self.play( - cost_group.scale, 0.5, - cost_group.to_corner, UP+LEFT, - MoveToTarget(brace), - LaggedStartMap(GrowArrow, arrows), - LaggedStartMap(FadeIn, terms), - FadeIn(sum_term), - Animation(decimals) - ) - self.play(*it.chain( - update_decimals, - term_updates, - [ - ApplyMethod(neuron.set_fill, None, t) - for neuron, t in zip(neurons, target_vect) - ] - )) - self.wait() - self.play(LaggedStartMap(Indicate, decimals, rate_func = there_and_back)) - self.wait() - for update in update_decimals: - update.rate_func = lambda a : smooth(1-a) - self.play(*it.chain( - update_decimals, - term_updates, - [neurons.restore] - ), run_time = 2) - self.wait() - self.play( - cost_group.restore, - brace.restore, - FadeOut(VGroup(terms, sum_term, arrows)), - ) - - def average_over_all_training_data(self): - image_group = self.image_group - decimal_groups = self.decimal_groups - - random_neurons = self.network_mob.layers[-1].neurons - desired_neurons = self.desired_last_layer.neurons - - wait_times = iter(it.chain( - 4*[0.5], - 4*[0.25], - 8*[0.125], - it.repeat(0.1) - )) - - words = TextMobject("Average cost of \\\\ all training data...") - words.set_color(BLUE) - words.to_corner(UP+LEFT) - - self.play( - Write(words, run_time = 1), - ) - - training_data, validation_data, test_data = load_data_wrapper() - for in_vect, out_vect in training_data[:self.n_training_examples]: - random_v = np.random.random(10) - new_decimal_groups = VGroup(*[ - self.num_vect_to_decimals(v) - for v in (random_v, out_vect) - ]) - for ds, nds in zip(decimal_groups, new_decimal_groups): - for old_d, new_d in zip(ds, nds): - new_d.replace(old_d) - self.remove(decimal_groups) - self.add(new_decimal_groups) - decimal_groups = new_decimal_groups - for pair in (random_v, random_neurons), (out_vect, desired_neurons): - for n, neuron in zip(*pair): - neuron.set_fill(opacity = n) - new_image_group = MNistMobject(in_vect) - new_image_group.replace(image_group) - self.remove(image_group) - self.add(new_image_group) - image_group = new_image_group - - self.wait(next(wait_times)) - - #### - - def color_edge_randomly(self, edge, exp = 1): - r = (2*np.random.random()-1)**exp - r *= self.max_stroke_width - pc, nc = self.positive_edge_color, self.negative_edge_color - edge.set_stroke( - color = pc if r > 0 else nc, - width = abs(r), - ) - return edge - - def layer_to_num_vect(self, layer, n_terms = 10): - return [ - n.get_fill_opacity() - for n in layer.neurons - ][:n_terms] - - def num_vect_to_decimals(self, num_vect): - return VGroup(*[ - DecimalNumber(n).set_fill(opacity = 0.5*n + 0.5) - for n in num_vect - ]) - - def num_vect_to_column_vector(self, num_vect, height): - decimals = VGroup(*[ - DecimalNumber(n).set_fill(opacity = 0.5*n + 0.5) - for n in num_vect - ]) - decimals.arrange(DOWN) - decimals.set_height(height) - lb, rb = brackets = TexMobject("[]") - brackets.scale(2) - brackets.stretch_to_fit_height(height + SMALL_BUFF) - lb.next_to(decimals, LEFT) - rb.next_to(decimals, RIGHT) - result = VGroup(brackets, decimals) - result.brackets = brackets - result.decimals = decimals - return result - -class YellAtNetwork(PiCreatureScene, PreviewLearning): - def setup(self): - PiCreatureScene.setup(self) - PreviewLearning.setup(self) - - def construct(self): - randy = self.randy - network_mob, eyes = self.get_network_and_eyes() - - three_vect = get_organized_images()[3][5] - self.activate_network(three_vect) - - image = PixelsFromVect(three_vect) - image.add(SurroundingRectangle(image, color = BLUE)) - arrow = Arrow(LEFT, RIGHT, color = WHITE) - - layer = network_mob.layers[-1] - layer.add(network_mob.output_labels) - layer_copy = layer.deepcopy() - for neuron in layer_copy.neurons: - neuron.set_fill(WHITE, opacity = 0) - layer_copy.neurons[3].set_fill(WHITE, 1) - layer_copy.scale(1.5) - desired = Group(image, arrow, layer_copy) - desired.arrange(RIGHT) - desired.to_edge(UP) - - q_marks = TexMobject("???").set_color(RED) - q_marks.next_to(arrow, UP, SMALL_BUFF) - - self.play( - PiCreatureBubbleIntroduction( - randy, "Bad network!", - target_mode = "angry", - look_at_arg = eyes, - run_time = 1, - ), - eyes.look_at_anim(randy.eyes) - ) - self.play(eyes.change_mode_anim("sad")) - self.play(eyes.look_at_anim(3*DOWN + 3*RIGHT)) - self.play( - FadeIn(desired), - RemovePiCreatureBubble( - randy, target_mode = "sassy", - look_at_arg = desired - ), - eyes.look_at_anim(desired) - ) - self.play(eyes.blink_anim()) - rect = SurroundingRectangle( - VGroup(layer_copy.neurons[3], layer_copy[-1][3]), - ) - self.play(ShowCreation(rect)) - layer_copy.add(rect) - self.wait() - self.play( - layer.copy().replace, layer_copy, 1, - Write(q_marks, run_time = 1), - layer_copy.shift, 1.5*RIGHT, - randy.change, "angry", eyes, - ) - self.play(eyes.look_at_anim(3*RIGHT + 3*RIGHT)) - self.wait() - - #### - - def get_network_and_eyes(self): - randy = self.randy - network_mob = self.network_mob - network_mob.scale(0.5) - network_mob.next_to(randy, RIGHT, LARGE_BUFF) - self.color_network_edges() - eyes = Eyes(network_mob.edge_groups[1]) - return network_mob, eyes - - def create_pi_creature(self): - randy = self.randy = Randolph() - randy.shift(3*LEFT).to_edge(DOWN) - return randy - -class ThisIsVeryComplicated(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Very complicated!", - target_mode = "surprised", - run_time = 1, - ) - self.change_student_modes(*3*["guilty"]) - self.wait(2) - -class EmphasizeComplexityOfCostFunction(IntroduceCostFunction): - CONFIG = { - "stroke_width_exp" : 3, - "n_examples" : 32, - } - def construct(self): - self.setup_sides() - self.show_network_as_a_function() - self.show_cost_function() - - def setup_sides(self): - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - network_mob = self.network_mob - network_mob.set_width(FRAME_X_RADIUS - 1) - network_mob.to_corner(DOWN+LEFT) - - self.add(v_line) - self.color_network_edges() - - def show_network_as_a_function(self): - title = TextMobject("Neural network function") - title.shift(FRAME_X_RADIUS*RIGHT/2) - title.to_edge(UP) - underline = Line(LEFT, RIGHT) - underline.stretch_to_fit_width(title.get_width()) - underline.next_to(title, DOWN, SMALL_BUFF) - self.add(title, underline) - - words = self.get_function_description_words( - "784 numbers (pixels)", - "10 numbers", - "13{,}002 weights/biases", - ) - input_words, output_words, parameter_words = words - for word in words: - self.add(word[0]) - - in_vect = get_organized_images()[7][8] - activations = self.network.get_activation_of_all_layers(in_vect) - image = MNistMobject(in_vect) - image.set_height(1.5) - image_label = TextMobject("Input") - image_label.set_color(input_words[0].get_color()) - image_label.next_to(image, UP, SMALL_BUFF) - - arrow = Arrow(LEFT, RIGHT, color = WHITE) - arrow.next_to(image, RIGHT) - output = self.num_vect_to_column_vector(activations[-1], 2) - output.next_to(arrow, RIGHT) - - group = Group(image, image_label, arrow, output) - group.next_to(self.network_mob, UP, 0, RIGHT) - - dot = Dot() - dot.move_to(input_words.get_right()) - dot.set_fill(opacity = 0.5) - - self.play(FadeIn(input_words[1], lag_ratio = 0.5)) - self.play( - dot.move_to, image, - dot.set_fill, None, 0, - FadeIn(image), - FadeIn(image_label), - ) - self.activate_network(in_vect, - GrowArrow(arrow), - FadeIn(output), - FadeIn(output_words[1]) - ) - self.wait() - self.play( - FadeIn(parameter_words[1]), - self.get_edge_animation() - ) - self.wait(2) - - self.to_fade = group - self.curr_words = words - self.title = title - self.underline = underline - - def show_cost_function(self): - network_mob = self.network_mob - to_fade = self.to_fade - input_words, output_words, parameter_words = self.curr_words - - network_mob.generate_target() - network_mob.target.scale_in_place(0.7) - network_mob.target.to_edge(UP, buff = LARGE_BUFF) - rect = SurroundingRectangle(network_mob.target, color = BLUE) - network_label = TextMobject("Input") - network_label.set_color(input_words[0].get_color()) - network_label.next_to(rect, UP, SMALL_BUFF) - - new_output_word = TextMobject("1 number", "(the cost)") - new_output_word[1].set_color(RED).scale(0.9) - new_output_word.move_to(output_words[1], LEFT) - new_output_word.shift(0.5*SMALL_BUFF*DOWN) - new_parameter_word = TextMobject(""" - \\begin{flushleft} - Many, many, many \\\\ training examples - \\end{flushleft} - """).scale(0.9) - new_parameter_word.move_to(parameter_words[1], UP+LEFT) - - new_title = TextMobject("Cost function") - new_title.set_color(RED) - new_title.move_to(self.title) - - arrow = Arrow(UP, DOWN, color = WHITE) - arrow.next_to(rect, DOWN) - cost = TextMobject("Cost: 5.4") - cost.set_color(RED) - cost.next_to(arrow, DOWN) - - training_data, validation_data, test_data = load_data_wrapper() - training_examples = Group(*list(map( - self.get_training_pair_mob, - training_data[:self.n_examples] - ))) - training_examples.next_to(parameter_words, DOWN, buff = LARGE_BUFF) - - self.play( - FadeOut(to_fade), - FadeOut(input_words[1]), - FadeOut(output_words[1]), - MoveToTarget(network_mob), - FadeIn(rect), - FadeIn(network_label), - Transform(self.title, new_title), - self.underline.stretch_to_fit_width, new_title.get_width() - ) - self.play( - ApplyMethod( - parameter_words[1].move_to, input_words[1], LEFT, - path_arc = np.pi, - ), - self.get_edge_animation() - ) - self.wait() - self.play( - GrowArrow(arrow), - Write(cost, run_time = 1) - ) - self.play(Write(new_output_word, run_time = 1)) - self.wait() - self.play( - FadeIn(new_parameter_word), - FadeIn(training_examples[0]) - ) - self.wait(0.5) - for last_ex, ex in zip(training_examples, training_examples[1:]): - activations = self.network.get_activation_of_all_layers( - ex.in_vect - ) - for i, a in enumerate(activations): - layer = self.network_mob.layers[i] - active_layer = self.network_mob.get_active_layer(i, a) - Transform(layer, active_layer).update(1) - self.remove(last_ex) - self.add(ex) - self.wait(0.25) - - #### - - def get_function_description_words(self, w1, w2, w3): - input_words = TextMobject("Input:", w1) - input_words[0].set_color(BLUE) - output_words = TextMobject("Output:", w2) - output_words[0].set_color(YELLOW) - parameter_words = TextMobject("Parameters:", w3) - parameter_words[0].set_color(GREEN) - words = VGroup(input_words, output_words, parameter_words) - words.arrange(DOWN, aligned_edge = LEFT) - words.scale(0.9) - words.next_to(ORIGIN, RIGHT) - words.shift(UP) - return words - - def get_training_pair_mob(self, data): - in_vect, out_vect = data - image = MNistMobject(in_vect) - image.set_height(1) - comma = TextMobject(",") - comma.next_to(image, RIGHT, SMALL_BUFF, DOWN) - output = TexMobject(str(np.argmax(out_vect))) - output.set_height(0.75) - output.next_to(image, RIGHT, MED_SMALL_BUFF) - lp, rp = parens = TextMobject("()") - parens.scale(2) - parens.stretch_to_fit_height(1.2*image.get_height()) - lp.next_to(image, LEFT, SMALL_BUFF) - rp.next_to(lp, RIGHT, buff = 2) - - result = Group(lp, image, comma, output, rp) - result.in_vect = in_vect - return result - -class NetworkGrowthMindset(YellAtNetwork): - def construct(self): - randy = self.pi_creature - network_mob, eyes = self.get_network_and_eyes() - eyes.look_at_anim(randy.eyes).update(1) - edge_update = ContinualEdgeUpdate( - network_mob, - colors = [BLUE, RED] - ) - - self.play( - PiCreatureSays( - randy, "Awful, just awful!", - target_mode = "angry", - look_at_arg = eyes, - run_time = 1, - ), - eyes.change_mode_anim("concerned_musician") - ) - self.wait() - self.add(edge_update) - self.pi_creature_says( - "But we can do better! \\\\ Growth mindset!", - target_mode = "hooray" - ) - self.play(eyes.change_mode_anim("happy")) - self.wait(3) - -class SingleVariableCostFunction(GraphScene): - CONFIG = { - "x_axis_label" : "$w$", - "y_axis_label" : "", - "x_min" : -5, - "x_max" : 7, - "x_axis_width" : 12, - "graph_origin" : 2.5*DOWN + LEFT, - "tangent_line_color" : YELLOW, - } - def construct(self): - self.reduce_full_function_to_single_variable() - self.show_graph() - self.find_exact_solution() - self.make_function_more_complicated() - self.take_steps() - self.take_steps_based_on_slope() - self.ball_rolling_down_hill() - self.note_step_sizes() - - def reduce_full_function_to_single_variable(self): - name = TextMobject("Cost function") - cf1 = TexMobject("C(", "w_1, w_2, \\dots, w_{13{,}002}", ")") - cf2 = TexMobject("C(", "w", ")") - for cf in cf1, cf2: - VGroup(cf[0], cf[2]).set_color(RED) - big_brace, lil_brace = [ - Brace(cf[1], DOWN) - for cf in (cf1, cf2) - ] - big_brace_text = big_brace.get_text("Weights and biases") - lil_brace_text = lil_brace.get_text("Single input") - - name.next_to(cf1, UP, LARGE_BUFF) - name.set_color(RED) - - self.add(name, cf1) - self.play( - GrowFromCenter(big_brace), - FadeIn(big_brace_text) - ) - self.wait() - self.play( - ReplacementTransform(big_brace, lil_brace), - ReplacementTransform(big_brace_text, lil_brace_text), - ReplacementTransform(cf1, cf2), - ) - - # cf2.add_background_rectangle() - lil_brace_text.add_background_rectangle() - self.brace_group = VGroup(lil_brace, lil_brace_text) - cf2.add(self.brace_group) - self.function_label = cf2 - self.to_fade = name - - def show_graph(self): - function_label = self.function_label - self.setup_axes() - graph = self.get_graph( - lambda x : 0.5*(x - 3)**2 + 2, - color = RED - ) - - self.play( - FadeOut(self.to_fade), - Write(self.axes), - Animation(function_label), - run_time = 1, - ) - self.play( - function_label.next_to, - self.input_to_graph_point(5, graph), RIGHT, - ShowCreation(graph) - ) - self.wait() - - self.graph = graph - - def find_exact_solution(self): - function_label = self.function_label - graph = self.graph - - w_min = TexMobject("w", "_{\\text{min}}", arg_separator = "") - w_min.move_to(function_label[1], UP+LEFT) - w_min[1].fade(1) - x = 3 - dot = Dot( - self.input_to_graph_point(x, graph), - color = YELLOW - ) - line = self.get_vertical_line_to_graph( - x, graph, - line_class = DashedLine, - color = YELLOW - ) - formula = TexMobject("\\frac{dC}{dw}(w) = 0") - formula.next_to(dot, UP, buff = 2) - formula.shift(LEFT) - arrow = Arrow(formula.get_bottom(), dot.get_center()) - - self.play( - w_min.shift, - line.get_bottom() - w_min[0].get_top(), - MED_SMALL_BUFF*DOWN, - w_min.set_fill, WHITE, 1, - ) - self.play(ShowCreation(line)) - self.play(DrawBorderThenFill(dot, run_time = 1)) - self.wait() - self.play(Write(formula, run_time = 2)) - self.play(GrowArrow(arrow)) - self.wait() - - self.dot = dot - self.line = line - self.w_min = w_min - self.deriv_group = VGroup(formula, arrow) - - def make_function_more_complicated(self): - dot = self.dot - line = self.line - w_min = self.w_min - deriv_group = self.deriv_group - function_label = self.function_label - brace_group = function_label[-1] - function_label.remove(brace_group) - - brace = Brace(deriv_group, UP) - words = TextMobject("Sometimes \\\\ infeasible") - words.next_to(deriv_group, UP) - words.set_color(BLUE) - words.next_to(brace, UP) - - graph = self.get_graph( - lambda x : 0.05*((x+2)*(x-1)*(x-3))**2 + 2 + 0.3*(x-3), - color = RED - ) - - self.play( - ReplacementTransform(self.graph, graph), - function_label.shift, 2*UP+1.9*LEFT, - FadeOut(brace_group), - Animation(dot) - ) - self.graph = graph - self.play( - Write(words, run_time = 1), - GrowFromCenter(brace) - ) - self.wait(2) - self.play(FadeOut(VGroup(words, brace, deriv_group))) - - def take_steps(self): - dot = self.dot - line = self.line - w_mob, min_mob = self.w_min - graph = self.graph - - def update_line(line): - x = self.x_axis.point_to_number(w_mob.get_center()) - line.put_start_and_end_on_with_projection( - self.coords_to_point(x, 0), - self.input_to_graph_point(x, graph) - ) - return line - line_update_anim = UpdateFromFunc(line, update_line) - - def update_dot(dot): - dot.move_to(line.get_end()) - return dot - dot_update_anim = UpdateFromFunc(dot, update_dot) - - point = self.coords_to_point(2, 0) - arrows = VGroup() - q_marks = VGroup() - for vect, color in (LEFT, BLUE), (RIGHT, GREEN): - arrow = Arrow(ORIGIN, vect, buff = SMALL_BUFF) - arrow.shift(point + SMALL_BUFF*UP) - arrow.set_color(color) - arrows.add(arrow) - q_mark = TextMobject("?") - q_mark.next_to(arrow, UP, buff = 0) - q_mark.add_background_rectangle() - q_marks.add(q_mark) - - self.play( - w_mob.next_to, point, DOWN, - FadeOut(min_mob), - line_update_anim, - dot_update_anim, - ) - self.wait() - self.play(*it.chain( - list(map(GrowArrow, arrows)), - list(map(FadeIn, q_marks)), - )) - self.wait() - - self.arrow_group = VGroup(arrows, q_marks) - self.line_update_anim = line_update_anim - self.dot_update_anim = dot_update_anim - self.w_mob = w_mob - - def take_steps_based_on_slope(self): - arrows, q_marks = arrow_group = self.arrow_group - line_update_anim = self.line_update_anim - dot_update_anim = self.dot_update_anim - dot = self.dot - w_mob = self.w_mob - graph = self.graph - - x = self.x_axis.point_to_number(w_mob.get_center()) - tangent_line = self.get_tangent_line(x, arrows[0].get_color()) - - self.play( - ShowCreation(tangent_line), - Animation(dot), - ) - self.play(VGroup(arrows[1], q_marks).set_fill, None, 0) - self.play( - w_mob.shift, MED_SMALL_BUFF*LEFT, - MaintainPositionRelativeTo(arrow_group, w_mob), - line_update_anim, dot_update_anim, - ) - self.wait() - - new_x = 0.3 - new_point = self.coords_to_point(new_x, 0) - new_tangent_line = self.get_tangent_line( - new_x, arrows[1].get_color() - ) - self.play( - FadeOut(tangent_line), - w_mob.next_to, new_point, DOWN, - arrow_group.next_to, new_point, UP, SMALL_BUFF, - arrow_group.set_fill, None, 1, - dot_update_anim, - line_update_anim, - ) - self.play( - ShowCreation(new_tangent_line), - Animation(dot), - Animation(arrow_group), - ) - self.wait() - self.play(VGroup(arrows[0], q_marks).set_fill, None, 0) - self.play( - w_mob.shift, MED_SMALL_BUFF*RIGHT, - MaintainPositionRelativeTo(arrow_group, w_mob), - line_update_anim, dot_update_anim, - ) - self.play( - FadeOut(VGroup(new_tangent_line, arrow_group)), - Animation(dot), - ) - self.wait() - for x in 0.8, 1.1, 0.95: - self.play( - w_mob.next_to, self.coords_to_point(x, 0), DOWN, - line_update_anim, - dot_update_anim, - ) - self.wait() - - def ball_rolling_down_hill(self): - ball = self.dot - graph = self.graph - point = VectorizedPoint(self.coords_to_point(-0.5, 0)) - w_mob = self.w_mob - - def update_ball(ball): - x = self.x_axis.point_to_number(ball.point.get_center()) - graph_point = self.input_to_graph_point(x, graph) - vect = rotate_vector(UP, self.angle_of_tangent(x, graph)) - radius = ball.get_width()/2 - ball.move_to(graph_point + radius*vect) - return ball - - def update_point(point, dt): - x = self.x_axis.point_to_number(point.get_center()) - slope = self.slope_of_tangent(x, graph) - if abs(slope) > 0.5: - slope = 0.5 * slope / abs(slope) - x -= slope*dt - point.move_to(self.coords_to_point(x, 0)) - - - ball.generate_target() - ball.target.scale(2) - ball.target.set_fill(opacity = 0) - ball.target.set_stroke(BLUE, 3) - ball.point = point - ball.target.point = point - update_ball(ball.target) - - self.play(MoveToTarget(ball)) - self.play( - point.move_to, w_mob, - UpdateFromFunc(ball, update_ball), - run_time = 3, - ) - self.wait(2) - - points = [ - VectorizedPoint(self.coords_to_point(x, 0)) - for x in np.linspace(-2.7, 3.7, 11) - ] - balls = VGroup() - updates = [] - for point in points: - new_ball = ball.copy() - new_ball.point = point - balls.add(new_ball) - updates += [ - Mobject.add_updater(point, update_point), - Mobject.add_updater(new_ball, update_ball) - ] - balls.set_color_by_gradient(BLUE, GREEN) - - self.play(ReplacementTransform(ball, balls)) - self.add(*updates) - self.wait(5) - self.remove(*updates) - self.remove(*points) - self.play(FadeOut(balls)) - - def note_step_sizes(self): - w_mob = self.w_mob - line_update_anim = self.line_update_anim - - x = -0.5 - target_x = 0.94 - point = VectorizedPoint(self.coords_to_point(x, 0)) - line = self.get_tangent_line(x) - line.scale_in_place(0.5) - def update_line(line): - x = self.x_axis.point_to_number(point.get_center()) - self.make_line_tangent(line, x) - return line - - self.play( - ShowCreation(line), - w_mob.next_to, point, DOWN, - line_update_anim, - ) - for n in range(6): - x = self.x_axis.point_to_number(point.get_center()) - new_x = interpolate(x, target_x, 0.5) - self.play( - point.move_to, self.coords_to_point(new_x, 0), - MaintainPositionRelativeTo(w_mob, point), - line_update_anim, - UpdateFromFunc(line, update_line), - ) - self.wait(0.5) - self.wait() - - ### - - def get_tangent_line(self, x, color = YELLOW): - tangent_line = Line(LEFT, RIGHT).scale(3) - tangent_line.set_color(color) - self.make_line_tangent(tangent_line, x) - return tangent_line - - def make_line_tangent(self, line, x): - graph = self.graph - line.rotate(self.angle_of_tangent(x, graph) - line.get_angle()) - line.move_to(self.input_to_graph_point(x, graph)) - -class LocalVsGlobal(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - Local minimum = Doable \\\\ - Global minimum = Crazy hard - """) - self.change_student_modes(*["pondering"]*3) - self.wait(2) - -class TwoVariableInputSpace(Scene): - def construct(self): - self.add_plane() - self.ask_about_direction() - self.show_gradient() - - def add_plane(self): - plane = NumberPlane( - x_radius = FRAME_X_RADIUS/2 - ) - plane.add_coordinates() - name = TextMobject("Input space") - name.add_background_rectangle() - name.next_to(plane.get_corner(UP+LEFT), DOWN+RIGHT) - x, y = list(map(TexMobject, ["x", "y"])) - x.next_to(plane.coords_to_point(3.25, 0), UP, SMALL_BUFF) - y.next_to(plane.coords_to_point(0, 3.6), RIGHT, SMALL_BUFF) - - self.play( - *list(map(Write, [plane, name, x, y])), - run_time = 1 - ) - self.wait() - - self.plane = plane - - def ask_about_direction(self): - point = self.plane.coords_to_point(2, 1) - dot = Dot(point, color = YELLOW) - dot.save_state() - dot.move_to(FRAME_Y_RADIUS*UP + FRAME_X_RADIUS*RIGHT/2) - dot.fade(1) - arrows = VGroup(*[ - Arrow(ORIGIN, vect).shift(point) - for vect in compass_directions(8) - ]) - arrows.set_color(WHITE) - question = TextMobject( - "Which direction decreases \\\\", - "$C(x, y)$", "most quickly?" - ) - question.scale(0.7) - question.set_color(YELLOW) - question.set_color_by_tex("C(x, y)", RED) - question.add_background_rectangle() - question.next_to(arrows, LEFT) - - self.play(dot.restore) - self.play( - FadeIn(question), - LaggedStartMap(GrowArrow, arrows) - ) - self.wait() - - self.arrows = arrows - self.dot = dot - self.question = question - - def show_gradient(self): - arrows = self.arrows - dot = self.dot - question = self.question - - arrow = arrows[3] - new_arrow = Arrow( - dot.get_center(), arrow.get_end(), - buff = 0, - color = GREEN - ) - new_arrow.set_color(GREEN) - arrow.save_state() - - gradient = TexMobject("\\nabla C(x, y)") - gradient.add_background_rectangle() - gradient.next_to(arrow.get_end(), UP, SMALL_BUFF) - - gradient_words = TextMobject( - "``Gradient'', the direction\\\\ of", - "steepest increase" - ) - gradient_words.scale(0.7) - gradient_words[-1].set_color(GREEN) - gradient_words.next_to(gradient, UP, SMALL_BUFF) - gradient_words.add_background_rectangle(opacity = 1) - gradient_words.shift(LEFT) - - anti_arrow = new_arrow.copy() - anti_arrow.rotate(np.pi, about_point = dot.get_center()) - anti_arrow.set_color(RED) - - self.play( - Transform(arrow, new_arrow), - Animation(dot), - *[FadeOut(a) for a in arrows if a is not arrow] - ) - self.play(FadeIn(gradient)) - self.play(Write(gradient_words, run_time = 2)) - self.wait(2) - self.play( - arrow.fade, - ReplacementTransform( - arrow.copy(), - anti_arrow - ) - ) - self.wait(2) - -class CostSurface(ExternallyAnimatedScene): - pass - -class KhanAcademyMVCWrapper(PiCreatureScene): - def construct(self): - screen = ScreenRectangle(height = 5) - screen.to_corner(UP+LEFT) - morty = self.pi_creature - - self.play( - ShowCreation(screen), - morty.change, "raise_right_hand", - ) - self.wait(3) - self.play(morty.change, "happy", screen) - self.wait(5) - -class KAGradientPreview(ExternallyAnimatedScene): - pass - -class GradientDescentAlgorithm(Scene): - def construct(self): - words = VGroup( - TextMobject("Compute", "$\\nabla C$"), - TextMobject("Small step in", "$-\\nabla C$", "direction"), - TextMobject("Repeat."), - ) - words.arrange(DOWN, aligned_edge = LEFT) - words.set_width(FRAME_WIDTH - 1) - words.to_corner(DOWN+LEFT) - - for word in words[:2]: - word[1].set_color(RED) - - for word in words: - self.play(Write(word, run_time = 1)) - self.wait() - -class GradientDescentName(Scene): - def construct(self): - words = TextMobject("Gradient descent") - words.set_color(BLUE) - words.set_width(FRAME_WIDTH - 1) - words.to_edge(DOWN) - - self.play(Write(words, run_time = 2)) - self.wait() - -class ShowFullCostFunctionGradient(PreviewLearning): - def construct(self): - self.organize_weights_as_column_vector() - self.show_gradient() - - def organize_weights_as_column_vector(self): - network_mob = self.network_mob - edges = VGroup(*it.chain(*network_mob.edge_groups)) - layers = VGroup(*network_mob.layers) - layers.add(network_mob.output_labels) - self.color_network_edges() - - nums = [2.25, -1.57, 1.98, -1.16, 3.82, 1.21] - decimals = VGroup(*[ - DecimalNumber(num).set_color( - BLUE_D if num > 0 else RED - ) - for num in nums - ]) - dots = TexMobject("\\vdots") - decimals.submobjects.insert(3, dots) - decimals.arrange(DOWN) - decimals.shift(2*LEFT + 0.5*DOWN) - lb, rb = brackets = TexMobject("\\big[", "\\big]") - brackets.scale(2) - brackets.stretch_to_fit_height(1.2*decimals.get_height()) - lb.next_to(decimals, LEFT, SMALL_BUFF) - rb.next_to(decimals, RIGHT, SMALL_BUFF) - column_vect = VGroup(lb, decimals, rb) - - edges_target = VGroup(*it.chain( - decimals[:3], - [dots]*(len(edges) - 6), - decimals[-3:] - )) - - words = TextMobject("$13{,}002$ weights and biases") - words.next_to(column_vect, UP) - - lhs = TexMobject("\\vec{\\textbf{W}}", "=") - lhs[0].set_color(YELLOW) - lhs.next_to(column_vect, LEFT) - - self.play( - FadeOut(layers), - edges.space_out_submobjects, 1.2, - ) - self.play( - ReplacementTransform( - edges, edges_target, - run_time = 2, - lag_ratio = 0.5 - ), - LaggedStartMap(FadeIn, words), - ) - self.play(*list(map(Write, [lb, rb, lhs])), run_time = 1) - self.wait() - - self.column_vect = column_vect - - def show_gradient(self): - column_vect = self.column_vect - - lhs = TexMobject( - "-", "\\nabla", "C(", "\\vec{\\textbf{W}}", ")", "=" - ) - lhs.shift(2*RIGHT) - lhs.set_color_by_tex("W", YELLOW) - old_decimals = VGroup(*[m for m in column_vect[1] if isinstance(m, DecimalNumber)]) - new_decimals = VGroup() - new_nums = [0.18, 0.45, -0.51, 0.4, -0.32, 0.82] - for decimal, new_num in zip(old_decimals, new_nums): - new_decimal = DecimalNumber(new_num) - new_decimal.set_color(BLUE if new_num > 0 else RED_B) - new_decimal.move_to(decimal) - new_decimals.add(new_decimal) - rhs = VGroup( - column_vect[0].copy(), - new_decimals, - column_vect[2].copy(), - ) - rhs.to_edge(RIGHT, buff = 1.75) - lhs.next_to(rhs, LEFT) - - words = TextMobject("How to nudge all \\\\ weights and biases") - words.next_to(rhs, UP) - - self.play(Write(VGroup(lhs, rhs))) - self.play(FadeIn(words)) - for od, nd in zip(old_decimals, new_decimals): - nd = nd.deepcopy() - od_num = od.number - nd_num = nd.number - self.play( - nd.move_to, od, - nd.shift, 1.5*RIGHT - ) - self.play( - Transform( - nd, VectorizedPoint(od.get_center()), - lag_ratio = 0.5, - remover = True - ), - ChangingDecimal( - od, - lambda a : interpolate(od_num, od_num+nd_num, a) - ) - ) - self.wait() - -class DotsInsert(Scene): - def construct(self): - dots = TexMobject("\\vdots") - dots.set_height(FRAME_HEIGHT - 1) - self.add(dots) - -class HowMinimizingCostMeansBetterTrainingPerformance(IntroduceCostFunction): - def construct(self): - IntroduceCostFunction.construct(self) - self.improve_last_layer() - - def improve_last_layer(self): - decimals = self.decimal_groups[0] - neurons = self.network_mob.layers[-1].neurons - - values = [d.number for d in decimals] - target_values = 0.1*np.random.random(10) - target_values[3] = 0.98 - - words = TextMobject("Minimize cost $\\dots$") - words.next_to(decimals, UP, MED_LARGE_BUFF) - words.set_color(YELLOW) - # words.shift(LEFT) - - def generate_update(n1, n2): - return lambda a : interpolate(n1, n2, a) - updates = [ - generate_update(n1, n2) - for n1, n2 in zip(values, target_values) - ] - - self.play(LaggedStartMap(FadeIn, words, run_time = 1)) - self.play(*[ - ChangingDecimal(d, update) - for d, update in zip(decimals, updates) - ] + [ - UpdateFromFunc( - d, - lambda mob: mob.set_fill( - interpolate_color(BLACK, WHITE, 0.5+0.5*mob.number), - opacity = 1 - ) - ) - for d in decimals - ] + [ - ApplyMethod(neuron.set_fill, WHITE, target_value) - for neuron, target_value in zip(neurons, target_values) - ], run_time = 3) - self.wait() - - ### - - def average_over_all_training_data(self): - pass #So that IntroduceCostFunction.construct doesn't do this - -class CostSurfaceSteps(ExternallyAnimatedScene): - pass - -class ConfusedAboutHighDimension(TeacherStudentsScene): - def construct(self): - self.student_says( - "13{,}002-dimensional \\\\ nudge?", - target_mode = "confused" - ) - self.change_student_modes(*["confused"]*3) - self.wait(2) - self.teacher_thinks( - "", - bubble_kwargs = {"width" : 6, "height" : 4}, - added_anims = [self.get_student_changes(*["plain"]*3)] - ) - self.zoom_in_on_thought_bubble() - -class NonSpatialGradientIntuition(Scene): - CONFIG = { - "w_color" : YELLOW, - "positive_color" : BLUE, - "negative_color" : RED, - "vect_height" : FRAME_Y_RADIUS - MED_LARGE_BUFF, - "text_scale_value" : 0.7, - } - def construct(self): - self.add_vector() - self.add_gradient() - self.show_sign_interpretation() - self.show_magnitude_interpretation() - - def add_vector(self): - lhs = TexMobject("\\vec{\\textbf{W}}", "=") - lhs[0].set_color(self.w_color) - lhs.to_edge(LEFT) - - ws = VGroup(*[ - VGroup(TexMobject(tex)) - for tex in it.chain( - ["w_%d"%d for d in range(3)], - ["\\vdots"], - ["w_{13{,}00%d}"%d for d in range(3)] - ) - ]) - ws.set_color(self.w_color) - ws.arrange(DOWN) - lb, rb = brackets = TexMobject("\\big[", "\\big]").scale(2) - brackets.stretch_to_fit_height(1.2*ws.get_height()) - lb.next_to(ws, LEFT) - rb.next_to(ws, RIGHT) - vect = VGroup(lb, ws, rb) - - vect.set_height(self.vect_height) - vect.to_edge(UP).shift(2*LEFT) - lhs.next_to(vect, LEFT) - - self.add(lhs, vect) - self.vect = vect - self.top_lhs = lhs - - def add_gradient(self): - lb, ws, rb = vect = self.vect - ws = VGroup(*ws) - dots = ws[len(ws)/2] - ws.remove(dots) - - lhs = TexMobject( - "-\\nabla", "C(", "\\vec{\\textbf{W}}", ")", "=" - ) - lhs.next_to(vect, RIGHT, LARGE_BUFF) - lhs.set_color_by_tex("W", self.w_color) - - decimals = VGroup() - nums = [0.31, 0.03, -1.25, 0.78, -0.37, 0.16] - for num, w in zip(nums, ws): - decimal = DecimalNumber(num) - decimal.scale(self.text_scale_value) - if num > 0: - decimal.set_color(self.positive_color) - else: - decimal.set_color(self.negative_color) - decimal.move_to(w) - decimals.add(decimal) - new_dots = dots.copy() - - grad_content = VGroup(*it.chain( - decimals[:3], new_dots, decimals[3:] - )) - grad_vect = VGroup(lb.copy(), grad_content, rb.copy()) - VGroup(grad_vect[0], grad_vect[-1]).space_out_submobjects(0.8) - grad_vect.set_height(self.vect_height) - grad_vect.next_to(self.vect, DOWN) - lhs.next_to(grad_vect, LEFT) - - brace = Brace(grad_vect, RIGHT) - words = brace.get_text("Example gradient") - - self.wait() - self.play( - ReplacementTransform(self.top_lhs.copy(), lhs), - ReplacementTransform(self.vect.copy(), grad_vect), - GrowFromCenter(brace), - FadeIn(words) - ) - self.wait() - self.play(FadeOut(VGroup(brace, words))) - - self.ws = ws - self.grad_lhs = lhs - self.grad_vect = grad_vect - self.decimals = decimals - - def show_sign_interpretation(self): - ws = self.ws.copy() - decimals = self.decimals - - direction_phrases = VGroup() - for w, decimal in zip(ws, decimals): - if decimal.number > 0: - verb = "increase" - color = self.positive_color - else: - verb = "decrease" - color = self.negative_color - phrase = TextMobject("should", verb) - phrase.scale(self.text_scale_value) - phrase.set_color_by_tex(verb, color) - w.generate_target() - group = VGroup(w.target, phrase) - group.arrange(RIGHT) - w.target.shift(0.7*SMALL_BUFF*DOWN) - group.move_to(decimal.get_center() + RIGHT, LEFT) - direction_phrases.add(phrase) - - self.play( - LaggedStartMap(MoveToTarget, ws), - LaggedStartMap(FadeIn, direction_phrases) - ) - self.wait(2) - - self.direction_phrases = direction_phrases - self.ws = ws - - def show_magnitude_interpretation(self): - direction_phrases = self.direction_phrases - ws = self.ws - decimals = self.decimals - - magnitude_words = VGroup() - rects = VGroup() - for phrase, decimal in zip(direction_phrases, decimals): - if abs(decimal.number) < 0.2: - adj = "a little" - color = interpolate_color(BLACK, WHITE, 0.5) - elif abs(decimal.number) < 0.5: - adj = "somewhat" - color = LIGHT_GREY - else: - adj = "a lot" - color = WHITE - words = TextMobject(adj) - words.scale(self.text_scale_value) - words.set_color(color) - words.next_to(phrase, RIGHT, SMALL_BUFF) - magnitude_words.add(words) - - rect = SurroundingRectangle( - VGroup(*decimal[-4:]), - buff = SMALL_BUFF, - color = LIGHT_GREY - ) - rect.target = words - rects.add(rect) - - self.play(LaggedStartMap(ShowCreation, rects)) - self.play(LaggedStartMap(MoveToTarget, rects)) - self.wait(2) - -class SomeConnectionsMatterMoreThanOthers(PreviewLearning): - def setup(self): - np.random.seed(1) - PreviewLearning.setup(self) - self.color_network_edges() - - ex_in = get_organized_images()[3][4] - image = MNistMobject(ex_in) - image.to_corner(UP+LEFT) - self.add(image) - self.ex_in = ex_in - - def construct(self): - self.activate_network(self.ex_in) - self.fade_edges() - self.show_important_connection() - self.show_unimportant_connection() - - def fade_edges(self): - edges = VGroup(*it.chain(*self.network_mob.edge_groups)) - self.play(*[ - ApplyMethod( - edge.set_stroke, BLACK, 0, - rate_func = lambda a : 0.5*smooth(a) - ) - for edge in edges - ]) - - def show_important_connection(self): - layers = self.network_mob.layers - edge = self.get_edge(2, 3) - edge.set_stroke(YELLOW, 4) - words = TextMobject("This weight \\\\ matters a lot") - words.next_to(layers[-1], UP).to_edge(UP) - words.set_color(YELLOW) - arrow = Arrow(words.get_bottom(), edge.get_center()) - - self.play( - ShowCreation(edge), - GrowArrow(arrow), - FadeIn(words) - ) - self.wait() - - def show_unimportant_connection(self): - color = TEAL - edge = self.get_edge(11, 6) - edge.set_stroke(color, 5) - words = TextMobject("Who even cares \\\\ about this weight?") - words.next_to(self.network_mob.layers[-1], DOWN) - words.to_edge(DOWN) - words.set_color(color) - arrow = Arrow(words.get_top(), edge.get_center(), buff = SMALL_BUFF) - arrow.set_color(color) - - self.play( - ShowCreation(edge), - GrowArrow(arrow), - FadeIn(words) - ) - self.wait() - ### - - def get_edge(self, i1, i2): - layers = self.network_mob.layers - n1 = layers[-2].neurons[i1] - n2 = layers[-1].neurons[i2] - return self.network_mob.get_edge(n1, n2) - -class SpinningVectorWithLabel(Scene): - def construct(self): - plane = NumberPlane( - x_unit_size = 2, - y_unit_size = 2, - ) - plane.add_coordinates() - self.add(plane) - - vector = Vector(2*RIGHT) - label = get_decimal_vector([-1, -1], with_dots = False) - label.add_to_back(BackgroundRectangle(label)) - label.next_to(vector.get_end(), UP+RIGHT) - label.decimals.set_fill(opacity = 0) - decimals = label.decimals.copy() - decimals.set_fill(WHITE, 1) - - cd1 = ChangingDecimal( - decimals[0], - lambda a : np.cos(vector.get_angle()), - tracked_mobject = label.decimals[0], - ) - cd2 = ChangingDecimal( - decimals[1], - lambda a : np.sin(vector.get_angle()), - tracked_mobject = label.decimals[1], - ) - - self.play( - Rotate( - vector, - 0.999*np.pi, - in_place = False, - run_time = 8, - rate_func = there_and_back - ), - UpdateFromFunc( - label, - lambda m : m.next_to(vector.get_end(), UP+RIGHT) - ), - cd1, cd2, - ) - self.wait() - -class TwoGradientInterpretationsIn2D(Scene): - def construct(self): - self.force_skipping() - - self.setup_plane() - self.add_function_definitions() - self.point_out_direction() - self.point_out_relative_importance() - self.wiggle_in_neighborhood() - - def setup_plane(self): - plane = NumberPlane() - plane.add_coordinates() - self.add(plane) - self.plane = plane - - def add_function_definitions(self): - func = TexMobject( - "C(", "x, y", ")", "=", - "\\frac{3}{2}x^2", "+", "\\frac{1}{2}y^2", - ) - func.shift(FRAME_X_RADIUS*LEFT/2).to_edge(UP) - - grad = TexMobject("\\nabla", "C(", "1, 1", ")", "=") - vect = TexMobject( - "\\left[\\begin{array}{c} 3 \\\\ 1 \\end{array}\\right]" - ) - vect.next_to(grad, RIGHT, SMALL_BUFF) - grad_group = VGroup(grad, vect) - grad_group.next_to(ORIGIN, RIGHT).to_edge(UP, buff = MED_SMALL_BUFF) - for mob in grad, vect, func: - mob.add_background_rectangle() - mob.background_rectangle.scale_in_place(1.1) - - self.play(Write(func, run_time = 1)) - self.play(Write(grad_group, run_time = 2)) - self.wait() - - self.func = func - self.grad = grad - self.vect = vect - - def point_out_direction(self): - coords = self.grad.get_part_by_tex("1, 1").copy() - vect = self.vect[1].copy() - coords.set_color(YELLOW) - vect.set_color(GREEN) - - dot = Dot(self.plane.coords_to_point(1, 1)) - dot.set_color(coords.get_color()) - arrow = Arrow( - self.plane.coords_to_point(1, 1), - self.plane.coords_to_point(4, 2), - buff = 0, - color = vect.get_color() - ) - words = TextMobject("Direction of \\\\ steepest ascent") - words.add_background_rectangle() - words.next_to(ORIGIN, DOWN) - words.rotate(arrow.get_angle()) - words.shift(arrow.get_center()) - - self.play(DrawBorderThenFill(coords, run_time = 1)) - self.play(ReplacementTransform(coords.copy(), dot)) - self.play(DrawBorderThenFill(vect, run_time = 1)) - self.play( - ReplacementTransform(vect.copy(), arrow), - Animation(dot) - ) - self.play(Write(words)) - self.wait() - - self.remove(vect) - self.vect[1].set_color(vect.get_color()) - self.remove(coords) - self.grad.get_part_by_tex("1, 1").set_color(coords.get_color()) - - self.steepest_words = words - self.dot = dot - - def point_out_relative_importance(self): - func = self.func - grad_group = VGroup(self.grad, self.vect) - x_part = func.get_part_by_tex("x^2") - y_part = func.get_part_by_tex("y^2") - - self.play(func.shift, 1.5*DOWN) - - x_rect = SurroundingRectangle(x_part, color = YELLOW) - y_rect = SurroundingRectangle(y_part, color = TEAL) - x_words = TextMobject("$x$ has 3 times \\\\ the impact...") - x_words.set_color(x_rect.get_color()) - x_words.add_background_rectangle() - x_words.next_to(x_rect, UP) - # x_words.to_edge(LEFT) - y_words = TextMobject("...as $y$") - y_words.set_color(y_rect.get_color()) - y_words.add_background_rectangle() - y_words.next_to(y_rect, DOWN) - - self.play( - Write(x_words, run_time = 2), - ShowCreation(x_rect) - ) - self.wait() - self.play( - Write(y_words, run_time = 1), - ShowCreation(y_rect) - ) - self.wait(2) - - def wiggle_in_neighborhood(self): - dot = self.dot - steepest_words = self.steepest_words - - neighborhood = Circle( - fill_color = BLUE, - fill_opacity = 0.25, - stroke_width = 0, - radius = 0.5, - ) - neighborhood.move_to(dot) - - self.revert_to_original_skipping_status() - self.play( - FadeOut(steepest_words), - GrowFromCenter(neighborhood) - ) - self.wait() - for vect in RIGHT, UP, 0.3*(3*RIGHT + UP): - self.play( - dot.shift, 0.5*vect, - rate_func = lambda t : wiggle(t, 4), - run_time = 3, - ) - self.wait() - -class ParaboloidGraph(ExternallyAnimatedScene): - pass - -class TODOInsertEmphasizeComplexityOfCostFunctionCopy(TODOStub): - CONFIG = { - "message" : "Insert EmphasizeComplexityOfCostFunction copy" - } - -class GradientNudging(PreviewLearning): - CONFIG = { - "n_steps" : 10, - "n_decimals" : 8, - } - def construct(self): - self.setup_network_mob() - self.add_gradient() - self.change_weights_repeatedly() - - def setup_network_mob(self): - network_mob = self.network_mob - self.color_network_edges() - network_mob.scale(0.7) - network_mob.to_corner(DOWN+RIGHT) - - def add_gradient(self): - lhs = TexMobject( - "-", "\\nabla", "C(", "\\dots", ")", "=" - ) - brace = Brace(lhs.get_part_by_tex("dots"), DOWN) - words = brace.get_text("All weights \\\\ and biases") - words.scale(0.8, about_point = words.get_top()) - np.random.seed(3) - nums = 4*(np.random.random(self.n_decimals)-0.5) - vect = get_decimal_vector(nums) - vect.next_to(lhs, RIGHT) - group = VGroup(lhs, brace, words, vect) - group.to_corner(UP+LEFT) - - self.add(*group) - - self.set_variables_as_attrs( - grad_lhs = lhs, - grad_vect = vect, - grad_arg_words = words, - grad_arg_brace = brace - ) - - def change_weights_repeatedly(self): - network_mob = self.network_mob - edges = VGroup(*reversed(list( - it.chain(*network_mob.edge_groups) - ))) - - decimals = self.grad_vect.decimals - - words = TextMobject( - "Change by some small\\\\", - "multiple of $-\\nabla C(\\dots)$" - ) - words.next_to(network_mob, UP).to_edge(UP) - arrows = VGroup(*[ - Arrow( - words.get_bottom(), - edge_group.get_top(), - color = WHITE - ) - for edge_group in network_mob.edge_groups - ]) - - mover = VGroup(*decimals.family_members_with_points()).copy() - # mover.set_fill(opacity = 0) - mover.set_stroke(width = 0) - target = VGroup(*self.network_mob.edge_groups.family_members_with_points()).copy() - target.set_fill(opacity = 0) - ApplyMethod(target.set_stroke, YELLOW, 2).update(0.3) - self.play( - ReplacementTransform(mover, target), - FadeIn(words), - LaggedStartMap(GrowArrow, arrows, run_time = 1) - ) - self.play(FadeOut(target)) - self.play(self.get_edge_change_anim(edges)) - self.play(*self.get_decimal_change_anims(decimals)) - for x in range(self.n_steps): - self.play(self.get_edge_change_anim(edges)) - self.play(*self.get_decimal_change_anims(decimals)) - self.wait() - - ### - - def get_edge_change_anim(self, edges): - target_nums = 6*(np.random.random(len(edges))-0.5) - edges.generate_target() - for edge, target_num in zip(edges.target, target_nums): - curr_num = edge.get_stroke_width() - if Color(edge.get_stroke_color()) == Color(self.negative_edge_color): - curr_num *= -1 - new_num = interpolate(curr_num, target_num, 0.2) - if new_num > 0: - new_color = self.positive_edge_color - else: - new_color = self.negative_edge_color - edge.set_stroke(new_color, abs(new_num)) - edge.rotate_in_place(np.pi) - return MoveToTarget( - edges, - lag_ratio = 0.5, - run_time = 1.5 - ) - - def get_decimal_change_anims(self, decimals): - words = TextMobject("Recompute \\\\ gradient") - words.next_to(decimals, DOWN, MED_LARGE_BUFF) - def wrf(t): - if t < 1./3: - return smooth(3*t) - elif t < 2./3: - return 1 - else: - return smooth(3 - 3*t) - - changes = 0.2*(np.random.random(len(decimals))-0.5) - def generate_change_func(x, dx): - return lambda a : interpolate(x, x+dx, a) - return [ - ChangingDecimal( - decimal, - generate_change_func(decimal.number, change) - ) - for decimal, change in zip(decimals, changes) - ] + [ - FadeIn(words, rate_func = wrf, run_time = 1.5, remover = True) - ] - -class BackPropWrapper(PiCreatureScene): - def construct(self): - morty = self.pi_creature - screen = ScreenRectangle(height = 5) - screen.to_corner(UP+LEFT) - screen.shift(MED_LARGE_BUFF*DOWN) - - title = TextMobject("Backpropagation", "(next video)") - title.next_to(screen, UP) - - self.play( - morty.change, "raise_right_hand", screen, - ShowCreation(screen) - ) - self.play(Write(title[0], run_time = 1)) - self.wait() - self.play(Write(title[1], run_time = 1)) - self.play(morty.change, "happy", screen) - self.wait(5) - -class TODOInsertCostSurfaceSteps(TODOStub): - CONFIG = { - "message" : "Insert CostSurfaceSteps" - } - -class ContinuouslyRangingNeuron(PreviewLearning): - def construct(self): - self.color_network_edges() - network_mob = self.network_mob - network_mob.scale(0.8) - network_mob.to_edge(DOWN) - neuron = self.network_mob.layers[2].neurons[6] - decimal = DecimalNumber(0) - decimal.set_width(0.8*neuron.get_width()) - decimal.move_to(neuron) - - decimal.generate_target() - neuron.generate_target() - group = VGroup(neuron.target, decimal.target) - group.set_height(1) - group.next_to(network_mob, UP) - decimal.set_fill(opacity = 0) - - def update_decimal_color(decimal): - if neuron.get_fill_opacity() > 0.8: - decimal.set_color(BLACK) - else: - decimal.set_color(WHITE) - decimal_color_anim = UpdateFromFunc(decimal, update_decimal_color) - - self.play(*list(map(MoveToTarget, [neuron, decimal]))) - for x in 0.7, 0.35, 0.97, 0.23, 0.54: - curr_num = neuron.get_fill_opacity() - self.play( - neuron.set_fill, None, x, - ChangingDecimal( - decimal, lambda a : interpolate(curr_num, x, a) - ), - decimal_color_anim - ) - self.wait() - -class AskHowItDoes(TeacherStudentsScene): - def construct(self): - self.student_says( - "How well \\\\ does it do?", - student_index = 0 - ) - self.wait(5) - -class TestPerformance(PreviewLearning): - CONFIG = { - "n_examples" : 300, - "time_per_example" : 0.1, - "wrong_wait_time" : 0.5, - "stroke_width_exp" : 2, - "decimal_kwargs" : { - "num_decimal_places" : 3, - } - } - def construct(self): - self.setup_network_mob() - self.init_testing_data() - self.add_title() - self.add_fraction() - self.run_through_examples() - - def setup_network_mob(self): - self.network_mob.set_height(5) - self.network_mob.to_corner(DOWN+LEFT) - self.network_mob.to_edge(DOWN, buff = MED_SMALL_BUFF) - - def init_testing_data(self): - training_data, validation_data, test_data = load_data_wrapper() - self.test_data = iter(test_data[:self.n_examples]) - - def add_title(self): - title = TextMobject("Testing data") - title.to_edge(UP, buff = MED_SMALL_BUFF) - title.to_edge(LEFT, buff = LARGE_BUFF) - self.add(title) - self.title = title - - def add_fraction(self): - self.n_correct = 0 - self.total = 0 - self.decimal = DecimalNumber(0, **self.decimal_kwargs) - word_frac = TexMobject( - "{\\text{Number correct}", "\\over", - "\\text{total}}", "=", - ) - word_frac[0].set_color(GREEN) - self.frac = self.get_frac() - self.equals = TexMobject("=") - fracs = VGroup( - word_frac, self.frac, - self.equals, self.decimal - ) - fracs.arrange(RIGHT) - fracs.to_corner(UP+RIGHT, buff = LARGE_BUFF) - self.add(fracs) - - def run_through_examples(self): - title = self.title - rects = [ - SurroundingRectangle( - VGroup(neuron, label), - buff = 0.5*SMALL_BUFF - ) - for neuron, label in zip( - self.network_mob.layers[-1].neurons, - self.network_mob.output_labels - ) - ] - rect_wrong = TextMobject("Wrong!") - rect_wrong.set_color(RED) - num_wrong = rect_wrong.copy() - - arrow = Arrow(LEFT, RIGHT, color = WHITE) - guess_word = TextMobject("Guess") - self.add(arrow, guess_word) - - from tqdm import tqdm as ProgressDisplay - for test_in, test_out in ProgressDisplay(list(self.test_data)): - self.total += 1 - - activations = self.activate_layers(test_in) - choice = np.argmax(activations[-1]) - - image = MNistMobject(test_in) - image.set_height(1.5) - choice_mob = TexMobject(str(choice)) - choice_mob.scale(1.5) - group = VGroup(image, arrow, choice_mob) - group.arrange(RIGHT) - group.shift( - self.title.get_bottom()+MED_SMALL_BUFF*DOWN -\ - image.get_top() - ) - self.add(image, choice_mob) - guess_word.next_to(arrow, UP, SMALL_BUFF) - - rect = rects[choice] - self.add(rect) - - correct = (choice == test_out) - if correct: - self.n_correct += 1 - else: - rect_wrong.next_to(rect, RIGHT) - num_wrong.next_to(choice_mob, DOWN) - self.add(rect_wrong, num_wrong) - new_frac = self.get_frac() - new_frac.shift( - self.frac[1].get_left() - \ - new_frac[1].get_left() - ) - self.remove(self.frac) - self.add(new_frac) - self.frac = new_frac - self.equals.next_to(new_frac, RIGHT) - - new_decimal = DecimalNumber( - float(self.n_correct)/self.total, - **self.decimal_kwargs - ) - new_decimal.next_to(self.equals, RIGHT) - self.remove(self.decimal) - self.add(new_decimal) - self.decimal = new_decimal - - self.wait(self.time_per_example) - if not correct: - self.wait(self.wrong_wait_time) - - self.remove(rect, rect_wrong, num_wrong, image, choice_mob) - - self.add(rect, image, choice_mob) - - ### - def add_network(self): - self.network_mob = MNistNetworkMobject(**self.network_mob_config) - self.network_mob.scale(0.8) - self.network_mob.to_edge(DOWN) - self.network = self.network_mob.neural_network - self.add(self.network_mob) - self.color_network_edges() - - def get_frac(self): - frac = TexMobject("{%d"%self.n_correct, "\\over", "%d}"%self.total) - frac[0].set_color(GREEN) - return frac - - def activate_layers(self, test_in): - activations = self.network.get_activation_of_all_layers(test_in) - layers = self.network_mob.layers - for layer, activation in zip(layers, activations)[1:]: - for neuron, a in zip(layer.neurons, activation): - neuron.set_fill(opacity = a) - return activations - -class ReactToPerformance(TeacherStudentsScene): - def construct(self): - title = VGroup( - TextMobject("Play with network structure"), - Arrow(LEFT, RIGHT, color = WHITE), - TextMobject("98\\%", "testing accuracy") - ) - title.arrange(RIGHT) - title.to_edge(UP) - title[-1][0].set_color(GREEN) - self.play(Write(title, run_time = 2)) - - last_words = TextMobject( - "State of the art \\\\ is", - "99.79\\%" - ) - last_words[-1].set_color(GREEN) - - self.teacher_says( - "That's pretty", "good!", - target_mode = "surprised", - run_time = 1 - ) - self.change_student_modes(*["hooray"]*3) - self.wait() - self.teacher_says(last_words, target_mode = "hesitant") - self.change_student_modes( - *["pondering"]*3, - look_at_arg = self.teacher.bubble - ) - self.wait() - -class NoticeWhereItMessesUp(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Look where it \\\\ messes up", - run_time = 1 - ) - self.wait(2) - -class WrongExamples(TestPerformance): - CONFIG = { - "time_per_example" : 0 - } - -class TODOBreakUpNineByPatterns(TODOStub): - CONFIG = { - "message" : "Insert the scene with 9 \\\\ broken up by patterns" - } - -class NotAtAll(TeacherStudentsScene, PreviewLearning): - def setup(self): - TeacherStudentsScene.setup(self) - PreviewLearning.setup(self) - - def construct(self): - words = TextMobject("Well...\\\\", "not at all!") - words[1].set_color(BLACK) - network_mob = self.network_mob - network_mob.set_height(4) - network_mob.to_corner(UP+LEFT) - self.add(network_mob) - self.color_network_edges() - - self.teacher_says( - words, target_mode = "guilty", - run_time = 1 - ) - self.change_student_modes(*["sassy"]*3) - self.play( - self.teacher.change, "concerned_musician", - words[1].set_color, WHITE - ) - self.wait(2) - -class InterpretFirstWeightMatrixRows(TestPerformance): - CONFIG = { - "stroke_width_exp" : 1, - } - def construct(self): - self.slide_network_to_side() - self.prepare_pixel_arrays() - self.show_all_pixel_array() - - def slide_network_to_side(self): - network_mob = self.network_mob - network_mob.generate_target() - to_fade = VGroup(*it.chain( - network_mob.edge_groups[1:], - network_mob.layers[2:], - network_mob.output_labels - )) - to_keep = VGroup(*it.chain( - network_mob.edge_groups[0], - network_mob.layers[:2] - )) - shift_val = FRAME_X_RADIUS*LEFT + MED_LARGE_BUFF*RIGHT - \ - to_keep.get_left() - self.play( - to_fade.shift, shift_val, - to_fade.fade, 1, - to_keep.shift, shift_val - ) - self.remove(to_fade) - - def prepare_pixel_arrays(self): - pixel_arrays = VGroup() - w_matrix = self.network.weights[0] - for row in w_matrix: - max_val = np.max(np.abs(row)) - shades = np.array(row)/max_val - pixel_array = PixelsFromVect(np.zeros(row.size)) - for pixel, shade in zip(pixel_array, shades): - if shade > 0: - color = self.positive_edge_color - else: - color = self.negative_edge_color - pixel.set_fill(color, opacity = abs(shade)**(0.3)) - pixel_arrays.add(pixel_array) - pixel_arrays.arrange_in_grid(buff = MED_LARGE_BUFF) - pixel_arrays.set_height(FRAME_HEIGHT - 2.5) - pixel_arrays.to_corner(DOWN+RIGHT) - - for pixel_array in pixel_arrays: - rect = SurroundingRectangle(pixel_array) - rect.set_color(WHITE) - pixel_array.rect = rect - - words = TextMobject("What second layer \\\\ neurons look for") - words.next_to(pixel_arrays, UP).to_edge(UP) - - self.pixel_arrays = pixel_arrays - self.words = words - - def show_all_pixel_array(self): - edges = self.network_mob.edge_groups[0] - neurons = self.network_mob.layers[1].neurons - edges.remove(neurons[0].edges_in) - - self.play( - VGroup(*neurons[1:]).set_stroke, None, 0.5, - FadeIn(self.words), - neurons[0].edges_in.set_stroke, None, 2, - *[ - ApplyMethod(edge.set_stroke, None, 0.25) - for edge in edges - if edge not in neurons[0].edges_in - ] - ) - self.wait() - last_neuron = None - - for neuron, pixel_array in zip(neurons, self.pixel_arrays): - if last_neuron: - self.play( - last_neuron.edges_in.set_stroke, None, 0.25, - last_neuron.set_stroke, None, 0.5, - neuron.set_stroke, None, 3, - neuron.edges_in.set_stroke, None, 2, - ) - self.play(ReplacementTransform( - neuron.edges_in.copy().set_fill(opacity = 0), - pixel_array, - )) - self.play(ShowCreation(pixel_array.rect)) - last_neuron = neuron - -class InputRandomData(TestPerformance): - def construct(self): - self.color_network_edges() - self.show_random_image() - self.show_expected_outcomes() - self.feed_in_random_data() - self.network_speaks() - - def show_random_image(self): - np.random.seed(4) - rand_vect = np.random.random(28*28) - image = PixelsFromVect(rand_vect) - image.to_edge(LEFT) - image.shift(UP) - rect = SurroundingRectangle(image) - - arrow = Arrow( - rect.get_top(), - self.network_mob.layers[0].neurons.get_top(), - path_arc = -2*np.pi/3, - ) - arrow.tip.set_stroke(width = 3) - - self.play( - ShowCreation(rect), - LaggedStartMap( - DrawBorderThenFill, image, - stroke_width = 0.5 - ) - ) - self.play(ShowCreation(arrow)) - self.wait() - - self.image = image - self.rand_vect = rand_vect - self.image_rect = rect - self.arrow = arrow - - def show_expected_outcomes(self): - neurons = self.network_mob.layers[-1].neurons - - words = TextMobject("What might you expect?") - words.to_corner(UP+RIGHT) - arrow = Arrow( - words.get_bottom(), neurons.get_top(), - color = WHITE - ) - - self.play( - Write(words, run_time = 1), - GrowArrow(arrow) - ) - vects = [np.random.random(10) for x in range(2)] - vects += [np.zeros(10), 0.4*np.ones(10)] - for vect in vects: - neurons.generate_target() - for neuron, o in zip(neurons, vect): - neuron.generate_target() - neuron.target.set_fill(WHITE, opacity = o) - self.play(LaggedStartMap( - MoveToTarget, neurons, - run_time = 1 - )) - self.wait() - self.play(FadeOut(VGroup(words, arrow))) - - def feed_in_random_data(self): - neurons = self.network_mob.layers[0].neurons - rand_vect = self.rand_vect - image = self.image.copy() - output_labels = self.network_mob.output_labels - - opacities = it.chain(rand_vect[:8], rand_vect[-8:]) - target_neurons = neurons.copy() - for n, o in zip(target_neurons, opacities): - n.set_fill(WHITE, opacity = o) - - point = VectorizedPoint(neurons.get_center()) - image.target = VGroup(*it.chain( - target_neurons[:len(neurons)/2], - [point]*(len(image) - len(neurons)), - target_neurons[-len(neurons)/2:] - )) - - self.play(MoveToTarget( - image, - run_time = 2, - lag_ratio = 0.5 - )) - self.activate_network(rand_vect, FadeOut(image)) - - ### React ### - neurons = self.network_mob.layers[-1].neurons - choice = np.argmax([n.get_fill_opacity() for n in neurons]) - rect = SurroundingRectangle(VGroup( - neurons[choice], output_labels[choice] - )) - word = TextMobject("What?!?") - word.set_color(YELLOW) - word.next_to(rect, RIGHT) - - self.play(ShowCreation(rect)) - self.play(Write(word, run_time = 1)) - self.wait() - - self.network_mob.add(rect, word) - self.choice = choice - - def network_speaks(self): - network_mob = self.network_mob - network_mob.generate_target(use_deepcopy = True) - network_mob.target.scale(0.7) - network_mob.target.to_edge(DOWN) - eyes = Eyes( - network_mob.target.edge_groups[1], - height = 0.45, - ) - eyes.shift(0.5*SMALL_BUFF*UP) - - bubble = SpeechBubble( - height = 3, width = 5, - direction = LEFT - ) - bubble.pin_to(network_mob.target.edge_groups[-1]) - bubble.write("Looks like a \\\\ %d to me!"%self.choice) - - self.play( - MoveToTarget(network_mob), - FadeIn(eyes) - ) - self.play(eyes.look_at_anim(self.image)) - self.play( - ShowCreation(bubble), - Write(bubble.content, run_time = 1) - ) - self.play(eyes.blink_anim()) - self.wait() - -class CannotDraw(PreviewLearning): - def construct(self): - network_mob = self.network_mob - self.color_network_edges() - network_mob.scale(0.5) - network_mob.to_corner(DOWN+RIGHT) - eyes = Eyes(network_mob.edge_groups[1]) - eyes.shift(SMALL_BUFF*UP) - self.add(eyes) - - bubble = SpeechBubble( - height = 3, width = 4, - direction = RIGHT - ) - bubble.pin_to(network_mob.edge_groups[0]) - bubble.write("Uh...I'm really \\\\ more of a multiple \\\\ choice guy") - - randy = Randolph() - randy.to_corner(DOWN+LEFT) - eyes.look_at_anim(randy.eyes).update(1) - - self.play(PiCreatureSays( - randy, "Draw a \\\\ 5 for me", - look_at_arg = eyes, - run_time = 1 - )) - self.play(eyes.change_mode_anim("concerned_musician")) - self.play( - ShowCreation(bubble), - Write(bubble.content), - eyes.look_at_anim(network_mob.get_corner(DOWN+RIGHT)) - ) - self.play(eyes.blink_anim()) - self.play(Blink(randy)) - self.wait() - -class TODOShowCostFunctionDef(TODOStub): - CONFIG = { - "message" : "Insert cost function averaging portion" - } - -class TODOBreakUpNineByPatterns2(TODOBreakUpNineByPatterns): - pass - -class SomethingToImproveUpon(PiCreatureScene, TestPerformance): - CONFIG = { - "n_examples" : 15, - "time_per_example" : 0.15, - } - def setup(self): - self.setup_bases() - self.color_network_edges() - self.network_mob.to_edge(LEFT) - edge_update = ContinualEdgeUpdate( - self.network_mob, - colors = [BLUE, BLUE, RED] - ) - edge_update.internal_time = 1 - self.add(edge_update) - - def construct(self): - self.show_path() - self.old_news() - self.recognizing_digits() - self.hidden_layers() - - def show_path(self): - network_mob = self.network_mob - morty = self.pi_creature - - line = Line(LEFT, RIGHT).scale(5) - line.shift(UP) - dots = VGroup(*[ - Dot(line.point_from_proportion(a)) - for a in np.linspace(0, 1, 5) - ]) - dots.set_color_by_gradient(BLUE, YELLOW) - path = VGroup(line, dots) - words = TextMobject("This series") - words.next_to(line, DOWN) - - self.play( - network_mob.scale, 0.25, - network_mob.next_to, path.get_right(), UP, - ShowCreation(path), - Write(words, run_time = 1), - morty.change, "sassy", - ) - self.wait(2) - self.play( - ApplyMethod( - network_mob.next_to, path.get_left(), UP, - path_arc = np.pi/2, - ), - Rotate(path, np.pi, in_place = True), - morty.change, "raise_right_hand" - ) - self.wait(3) - - self.line = line - self.path = path - self.this_series = words - - def old_news(self): - network_mob = self.network_mob - morty = self.pi_creature - line = self.line - - words = TextMobject("Old technology!") - words.to_edge(UP) - arrow = Arrow(words.get_left(), network_mob.get_right()) - - name = TextMobject("``Multilayer perceptron''") - name.next_to(words, DOWN) - - cnn = TextMobject("Convolutional NN") - lstm = TextMobject("LSTM") - cnn.next_to(line.get_center(), UP) - lstm.next_to(line.get_right(), UP) - - modern_variants = VGroup(cnn, lstm) - modern_variants.set_color(YELLOW) - - self.play( - Write(words, run_time = 1), - GrowArrow(arrow), - morty.change_mode, "hesitant" - ) - self.play(Write(name)) - self.wait() - self.play( - FadeIn(modern_variants), - FadeOut(VGroup(words, arrow, name)), - morty.change, "thinking" - ) - self.wait(2) - - self.modern_variants = modern_variants - - def recognizing_digits(self): - training_data, validation_data, test_data = load_data_wrapper() - - for v_in, choice in validation_data[:self.n_examples]: - image = MNistMobject(v_in) - image.set_height(1) - choice = TexMobject(str(choice)) - choice.scale(2) - arrow = Vector(RIGHT, color = WHITE) - group = Group(image, arrow, choice) - group.arrange(RIGHT) - group.next_to(self.line, DOWN, LARGE_BUFF) - group.to_edge(LEFT, buff = LARGE_BUFF) - - self.add(group) - self.wait(self.time_per_example) - self.remove(group) - - def hidden_layers(self): - morty = self.pi_creature - network_mob = self.network_mob - - self.play( - network_mob.scale, 4, - network_mob.center, - network_mob.to_edge, LEFT, - FadeOut(VGroup(self.path, self.modern_variants, self.this_series)), - morty.change, "confused", - ) - - hidden_layers = network_mob.layers[1:3] - rects = VGroup(*list(map(SurroundingRectangle, hidden_layers))) - np.random.seed(0) - - self.play(ShowCreation(rects)) - self.activate_network(np.random.random(28*28)) - self.wait(3) - -class ShiftingFocus(Scene): - def construct(self): - how, do, networks, learn = words = TextMobject( - "How", "do", "neural networks", "learn?" - ) - networks.set_color(BLUE) - cross = Cross(networks) - viewers = TextMobject("video viewers") - viewers.move_to(networks) - viewers.set_color(YELLOW) - cap_do = TextMobject("Do") - cap_do.move_to(do, DOWN+LEFT) - - self.play(Write(words, run_time = 1)) - self.wait() - self.play(ShowCreation(cross)) - self.play( - VGroup(networks, cross).shift, DOWN, - Write(viewers, run_time = 1) - ) - self.wait(2) - self.play( - FadeOut(how), - Transform(do, cap_do) - ) - self.wait(2) - -class PauseAndPonder(TeacherStudentsScene): - def construct(self): - screen = ScreenRectangle(height = 3.5) - screen.to_edge(UP+LEFT) - - self.teacher_says( - "Pause and \\\\ ponder!", - target_mode = "hooray", - run_time = 1 - ) - self.play( - ShowCreation(screen), - self.get_student_changes(*["pondering"]*3), - ) - self.wait(6) - -class ConvolutionalNetworkPreview(Scene): - def construct(self): - vect = get_organized_images()[9][0] - image = PixelsFromVect(vect) - image.set_stroke(width = 1) - image.set_height(FRAME_HEIGHT - 1) - self.add(image) - - kernels = [ - PixelsFromVect(np.zeros(16)) - for x in range(2) - ] - for i, pixel in enumerate(kernels[0]): - x = i%4 - y = i//4 - if x == y: - pixel.set_fill(BLUE, 1) - elif abs(x - y) == 1: - pixel.set_fill(BLUE, 0.5) - for i, pixel in enumerate(kernels[1]): - x = i%4 - if x == 1: - pixel.set_fill(BLUE, 1) - elif x == 2: - pixel.set_fill(RED, 1) - for kernel in kernels: - kernel.set_stroke(width = 1) - kernel.scale(image[0].get_height()/kernel[0].get_height()) - kernel.add(SurroundingRectangle( - kernel, color = YELLOW, buff = 0 - )) - self.add(kernel) - for i, pixel in enumerate(image): - x = i%28 - y = i//28 - if x > 24 or y > 24: - continue - kernel.move_to(pixel, UP+LEFT) - self.wait(self.frame_duration) - self.remove(kernel) - -class RandomlyLabeledImageData(Scene): - CONFIG = { - "image_label_pairs" : [ - ("lion", "Lion"), - ("Newton", "Genius"), - ("Fork", "Fork"), - ("Trilobite", "Trilobite"), - ("Puppy", "Puppy"), - ("Astrolabe", "Astrolabe"), - ("Adele", "Songbird of \\\\ our generation"), - ("Cow", "Cow"), - ("Sculling", "Sculling"), - ("Pierre_de_Fermat", "Tease"), - ] - } - def construct(self): - groups = Group() - labels = VGroup() - for i, (image_name, label_name) in enumerate(self.image_label_pairs): - x = i//5 - y = i%5 - group = self.get_training_group(image_name, label_name) - group.shift(4.5*LEFT + x*FRAME_X_RADIUS*RIGHT) - group.shift(3*UP + 1.5*y*DOWN) - groups.add(group) - labels.add(group[-1]) - permutation = list(range(len(labels))) - while any(np.arange(len(labels)) == permutation): - random.shuffle(permutation) - for label, i in zip(labels, permutation): - label.generate_target() - label.target.move_to(labels[i], LEFT) - label.target.set_color(YELLOW) - - self.play(LaggedStartMap( - FadeIn, groups, - run_time = 3, - lag_ratio = 0.3, - )) - self.wait() - self.play(LaggedStartMap( - MoveToTarget, labels, - run_time = 4, - lag_ratio = 0.5, - path_arc = np.pi/3, - )) - self.wait() - - def get_training_group(self, image_name, label_name): - arrow = Vector(RIGHT, color = WHITE) - image = ImageMobject(image_name) - image.set_height(1.3) - image.next_to(arrow, LEFT) - label = TextMobject(label_name) - label.next_to(arrow, RIGHT) - group = Group(image, arrow, label) - return group - -class TrainOnImages(PreviewLearning, RandomlyLabeledImageData): - CONFIG = { - "layer_sizes" : [17, 17, 17, 17, 17, 17], - "network_mob_config" : { - "brace_for_large_layers" : False, - "include_output_labels" : False, - }, - } - def construct(self): - self.setup_network_mob() - - image_names, label_names = list(zip(*self.image_label_pairs)) - label_names = list(label_names) - random.shuffle(label_names) - groups = [ - self.get_training_group(image_name, label_name) - for image_name, label_name in zip(image_names, label_names) - ] - - - edges = VGroup(*reversed(list( - it.chain(*self.network_mob.edge_groups) - ))) - - for group in groups: - for edge in edges: - edge.generate_target() - edge.target.rotate_in_place(np.pi) - alt_edge = random.choice(edges) - color = alt_edge.get_stroke_color() - width = alt_edge.get_stroke_width() - edge.target.set_stroke(color, width) - - group.to_edge(UP) - self.add(group) - self.play(LaggedStartMap( - MoveToTarget, edges, - lag_ratio = 0.4, - run_time = 2, - )) - self.remove(group) - - def setup_network_mob(self): - self.network_mob.set_height(5) - self.network_mob.to_edge(DOWN) - self.color_network_edges() - -class IntroduceDeepNetwork(Scene): - def construct(self): - pass - -class AskNetworkAboutMemorizing(YellAtNetwork): - def construct(self): - randy = self.pi_creature - network_mob, eyes = self.get_network_and_eyes() - eyes.look_at_anim(randy.eyes).update(1) - self.add(eyes) - - self.pi_creature_says( - "Are you just \\\\ memorizing?", - target_mode = "sassy", - look_at_arg = eyes, - run_time = 2 - ) - self.wait() - self.play(eyes.change_mode_anim("sad")) - self.play(eyes.blink_anim()) - self.wait() - -class CompareLearningCurves(GraphScene): - CONFIG = { - "x_min" : 0, - "y_axis_label" : "Value of \\\\ cost function", - "x_axis_label" : "Number of gradient \\\\ descent steps", - "graph_origin" : 2*DOWN + 3.5*LEFT, - } - def construct(self): - self.setup_axes() - self.x_axis_label_mob.to_edge(DOWN) - self.y_axis_label_mob.to_edge(LEFT) - self.y_axis_label_mob.set_color(RED) - - slow_decrease = self.get_graph( - lambda x : 9 - 0.25*x - ) - faster_decrease = self.get_graph( - lambda x : 4.3*sigmoid(5*(2-x)) + 3 + 0.5*ReLU(3-x) - ) - for decrease, p in (slow_decrease, 0.2), (faster_decrease, 0.07): - y_vals = decrease.get_anchors()[:,1] - y_vals -= np.cumsum(p*np.random.random(len(y_vals))) - decrease.make_jagged() - faster_decrease.move_to(slow_decrease, UP+LEFT) - - slow_label = TextMobject("Randomly-labeled data") - slow_label.set_color(slow_decrease.get_color()) - slow_label.to_corner(UP+RIGHT) - slow_line = Line(ORIGIN, RIGHT) - slow_line.set_stroke(slow_decrease.get_color(), 5) - slow_line.next_to(slow_label, LEFT) - - fast_label = TextMobject("Properly-labeled data") - fast_label.set_color(faster_decrease.get_color()) - fast_label.next_to(slow_label, DOWN) - fast_line = slow_line.copy() - fast_line.set_color(faster_decrease.get_color()) - fast_line.next_to(fast_label, LEFT) - - self.play(FadeIn(slow_label), ShowCreation(slow_line)) - self.play(ShowCreation( - slow_decrease, - run_time = 12, - rate_func=linear, - )) - self.play(FadeIn(fast_label), ShowCreation(fast_line)) - self.play(ShowCreation( - faster_decrease, - run_time = 12, - rate_func=linear, - )) - self.wait() - - #### - - line = Line( - self.coords_to_point(1, 2), - self.coords_to_point(3, 9), - ) - rect = Rectangle() - rect.set_fill(YELLOW, 0.3) - rect.set_stroke(width = 0) - rect.replace(line, stretch = True) - - words = TextMobject("Learns structured data more quickly") - words.set_color(YELLOW) - words.next_to(rect, DOWN) - words.add_background_rectangle() - - self.play(DrawBorderThenFill(rect)) - self.play(Write(words)) - self.wait() - -class ManyMinimaWords(Scene): - def construct(self): - words = TextMobject( - "Many local minima,\\\\", - "roughly equal quality" - ) - words.set_width(FRAME_WIDTH - 1) - words.to_edge(UP) - self.play(Write(words)) - self.wait() - - -class NNPart2PatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Desmos", - "Burt Humburg", - "CrypticSwarm", - "Juan Benet", - "Ali Yahya", - "William", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Samantha D. Suplee", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Markus Persson", - "Yoni Nazarathy", - "Ed Kellett", - "Joseph John Cox", - "Luc Ritchie", - "Eric Chow", - "Mathias Jansson", - "Pedro Perez Sanchez", - "David Clark", - "Michael Gardner", - "Harsev Singh", - "Mads Elvheim", - "Erik Sundell", - "Xueqi Li", - "David G. Stork", - "Tianyu Ge", - "Ted Suzman", - "Linh Tran", - "Andrew Busey", - "John Haley", - "Ankalagon", - "Eric Lavault", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Ryan Dahl", - "Mark Govea", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ] - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/nn/part3.py b/from_3b1b/old/nn/part3.py deleted file mode 100644 index b0e31234..00000000 --- a/from_3b1b/old/nn/part3.py +++ /dev/null @@ -1,4496 +0,0 @@ -from nn.network import * -from nn.part1 import * -from nn.part2 import * - -class LayOutPlan(Scene): - def construct(self): - title = TextMobject("Plan") - title.scale(1.5) - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS - 1) - h_line.next_to(title, DOWN) - - items = BulletedList( - "Recap", - "Intuitive walkthrough", - "Derivatives in \\\\ computational graphs", - ) - items.to_edge(LEFT, buff = LARGE_BUFF) - self.add(items) - - rect = ScreenRectangle() - rect.set_width(FRAME_WIDTH - items.get_width() - 2) - rect.next_to(items, RIGHT, MED_LARGE_BUFF) - - self.play( - Write(title), - ShowCreation(h_line), - ShowCreation(rect), - run_time = 2 - ) - for i in range(len(items)): - self.play(items.fade_all_but, i) - self.wait(2) - -class TODOInsertFeedForwardAnimations(TODOStub): - CONFIG = { - "message" : "Insert Feed Forward Animations" - } - -class TODOInsertStepsDownCostSurface(TODOStub): - CONFIG = { - "message" : "Insert Steps Down Cost Surface" - } - -class TODOInsertDefinitionOfCostFunction(TODOStub): - CONFIG = { - "message" : "Insert Definition of cost function" - } - -class TODOInsertGradientNudging(TODOStub): - CONFIG = { - "message" : "Insert GradientNudging" - } - -class InterpretGradientComponents(GradientNudging): - CONFIG = { - "network_mob_config" : { - "layer_to_layer_buff" : 3, - }, - "stroke_width_exp" : 2, - "n_decimals" : 6, - "n_steps" : 3, - "start_cost" : 3.48, - "delta_cost" : -0.21, - } - def construct(self): - self.setup_network() - self.add_cost() - self.add_gradient() - self.change_weights_repeatedly() - self.ask_about_high_dimensions() - self.circle_magnitudes() - self.isolate_particular_weights() - self.shift_cost_expression() - self.tweak_individual_weights() - - def setup_network(self): - self.network_mob.scale(0.55) - self.network_mob.to_corner(UP+RIGHT) - self.color_network_edges() - - def add_cost(self): - rect = SurroundingRectangle(self.network_mob) - rect.set_color(RED) - arrow = Vector(DOWN, color = RED) - arrow.shift(rect.get_bottom()) - cost = DecimalNumber(self.start_cost) - cost.set_color(RED) - cost.next_to(arrow, DOWN) - - cost_expression = TexMobject( - "C(", "w_0, w_1, \\dots, w_{13{,}001}", ")", "=" - ) - for tex in "()": - cost_expression.set_color_by_tex(tex, RED) - cost_expression.next_to(cost, DOWN) - cost_group = VGroup(cost_expression, cost) - cost_group.arrange(RIGHT) - cost_group.next_to(arrow, DOWN) - - self.add(rect, arrow, cost_group) - - self.set_variables_as_attrs( - cost, cost_expression, cost_group, - network_rect = rect - ) - - def change_weights_repeatedly(self): - decimals = self.grad_vect.decimals - grad_terms = self.grad_vect.contents - edges = VGroup(*reversed(list( - it.chain(*self.network_mob.edge_groups) - ))) - cost = self.cost - - for x in range(self.n_steps): - self.move_grad_terms_into_position( - grad_terms.copy(), - *self.get_weight_adjustment_anims(edges, cost) - ) - self.play(*self.get_decimal_change_anims(decimals)) - - def ask_about_high_dimensions(self): - grad_vect = self.grad_vect - - words = TextMobject( - "Direction in \\\\ ${13{,}002}$ dimensions?!?") - words.set_color(YELLOW) - words.move_to(grad_vect).to_edge(DOWN) - arrow = Arrow( - words.get_top(), - grad_vect.get_bottom(), - buff = SMALL_BUFF - ) - - randy = Randolph() - randy.scale(0.6) - randy.next_to(words, LEFT) - randy.shift_onto_screen() - - self.play( - Write(words, run_time = 2), - GrowArrow(arrow), - ) - self.play(FadeIn(randy)) - self.play(randy.change, "confused", words) - self.play(Blink(randy)) - self.wait() - self.play(*list(map(FadeOut, [randy, words, arrow]))) - - def circle_magnitudes(self): - rects = VGroup() - for decimal in self.grad_vect.decimals: - rects.add(SurroundingRectangle(VGroup(*decimal[-8:]))) - rects.set_color(WHITE) - - self.play(LaggedStartMap(ShowCreation, rects)) - self.play(FadeOut(rects)) - - def isolate_particular_weights(self): - vect_contents = self.grad_vect.contents - w_terms = self.cost_expression[1] - - edges = self.network_mob.edge_groups - edge1 = self.network_mob.layers[1].neurons[3].edges_in[0].copy() - edge2 = self.network_mob.layers[1].neurons[9].edges_in[15].copy() - VGroup(edge1, edge2).set_stroke(width = 4) - d1 = DecimalNumber(3.2) - d2 = DecimalNumber(0.1) - VGroup(edge1, d1).set_color(YELLOW) - VGroup(edge2, d2).set_color(MAROON_B) - new_vect_contents = VGroup( - TexMobject("\\vdots"), - d1, TexMobject("\\vdots"), - d2, TexMobject("\\vdots"), - ) - new_vect_contents.arrange(DOWN) - new_vect_contents.move_to(vect_contents) - - new_w_terms = TexMobject( - "\\dots", "w_n", "\\dots", "w_k", "\\dots" - ) - new_w_terms.move_to(w_terms, DOWN) - new_w_terms[1].set_color(d1.get_color()) - new_w_terms[3].set_color(d2.get_color()) - - for d, edge in (d1, edge1), (d2, edge2): - d.arrow = Arrow( - d.get_right(), edge.get_center(), - color = d.get_color() - ) - - self.play( - FadeOut(vect_contents), - FadeIn(new_vect_contents), - FadeOut(w_terms), - FadeIn(new_w_terms), - edges.set_stroke, LIGHT_GREY, 0.35, - ) - self.play(GrowArrow(d1.arrow)) - self.play(ShowCreation(edge1)) - self.wait() - self.play(GrowArrow(d2.arrow)) - self.play(ShowCreation(edge2)) - self.wait(2) - - self.cost_expression.remove(w_terms) - self.cost_expression.add(new_w_terms) - self.set_variables_as_attrs( - edge1, edge2, new_w_terms, - new_decimals = VGroup(d1, d2) - ) - - def shift_cost_expression(self): - self.play(self.cost_group.shift, DOWN+0.5*LEFT) - - def tweak_individual_weights(self): - cost = self.cost - cost_num = cost.number - edges = VGroup(self.edge1, self.edge2) - decimals = self.new_decimals - changes = (1.0, 1./32) - wn = self.new_w_terms[1] - wk = self.new_w_terms[3] - - number_line_template = NumberLine( - x_min = -1, - x_max = 1, - tick_frequency = 0.25, - numbers_with_elongated_ticks = [], - color = WHITE - ) - for term in wn, wk, cost: - term.number_line = number_line_template.copy() - term.brace = Brace(term.number_line, DOWN, buff = SMALL_BUFF) - group = VGroup(term.number_line, term.brace) - group.next_to(term, UP) - term.dot = Dot() - term.dot.set_color(term.get_color()) - term.dot.move_to(term.number_line.get_center()) - term.dot.save_state() - term.dot.move_to(term) - term.dot.set_fill(opacity = 0) - term.words = TextMobject("Nudge this weight") - term.words.scale(0.7) - term.words.next_to(term.number_line, UP, MED_SMALL_BUFF) - - groups = [ - VGroup(d, d.arrow, edge, w) - for d, edge, w in zip(decimals, edges, [wn, wk]) - ] - for group in groups: - group.save_state() - - for i in range(2): - group1, group2 = groups[i], groups[1-i] - change = changes[i] - edge = edges[i] - w = group1[-1] - added_anims = [] - if i == 0: - added_anims = [ - GrowFromCenter(cost.brace), - ShowCreation(cost.number_line), - cost.dot.restore - ] - self.play( - group1.restore, - group2.fade, 0.7, - GrowFromCenter(w.brace), - ShowCreation(w.number_line), - w.dot.restore, - Write(w.words, run_time = 1), - *added_anims - ) - for x in range(2): - func = lambda a : interpolate( - cost_num, cost_num-change, a - ) - self.play( - ChangingDecimal(cost, func), - cost.dot.shift, change*RIGHT, - w.dot.shift, 0.25*RIGHT, - edge.set_stroke, None, 8, - rate_func = lambda t : wiggle(t, 4), - run_time = 2, - ) - self.wait() - self.play(*list(map(FadeOut, [ - w.dot, w.brace, w.number_line, w.words - ]))) - - - ###### - - def move_grad_terms_into_position(self, grad_terms, *added_anims): - cost_expression = self.cost_expression - w_terms = self.cost_expression[1] - points = VGroup(*[ - VectorizedPoint() - for term in grad_terms - ]) - points.arrange(RIGHT) - points.replace(w_terms, dim_to_match = 0) - - grad_terms.generate_target() - grad_terms.target[len(grad_terms)/2].rotate(np.pi/2) - grad_terms.target.arrange(RIGHT) - grad_terms.target.set_width(cost_expression.get_width()) - grad_terms.target.next_to(cost_expression, DOWN) - - words = TextMobject("Nudge weights") - words.scale(0.8) - words.next_to(grad_terms.target, DOWN) - - self.play( - MoveToTarget(grad_terms), - FadeIn(words) - ) - self.play( - Transform( - grad_terms, points, - remover = True, - lag_ratio = 0.5, - run_time = 1 - ), - FadeOut(words), - *added_anims - ) - - def get_weight_adjustment_anims(self, edges, cost): - start_cost = cost.number - target_cost = start_cost + self.delta_cost - w_terms = self.cost_expression[1] - - return [ - self.get_edge_change_anim(edges), - LaggedStartMap( - Indicate, w_terms, - rate_func = there_and_back, - run_time = 1.5, - ), - ChangingDecimal( - cost, - lambda a : interpolate(start_cost, target_cost, a), - run_time = 1.5 - ) - ] - -class GetLostInNotation(PiCreatureScene): - def construct(self): - morty = self.pi_creature - equations = VGroup( - TexMobject( - "\\delta", "^L", "=", "\\nabla_a", "C", - "\\odot \\sigma'(", "z", "^L)" - ), - TexMobject( - "\\delta", "^l = ((", "w", "^{l+1})^T", - "\\delta", "^{l+1}) \\odot \\sigma'(", "z", "^l)" - ), - TexMobject( - "{\\partial", "C", "\\over \\partial", "b", - "_j^l} =", "\\delta", "_j^l" - ), - TexMobject( - "{\\partial", "C", " \\over \\partial", - "w", "_{jk}^l} = ", "a", "_k^{l-1}", "\\delta", "_j^l" - ), - ) - for equation in equations: - equation.set_color_by_tex_to_color_map({ - "\\delta" : YELLOW, - "C" : RED, - "b" : MAROON_B, - "w" : BLUE, - "z" : TEAL, - }) - equation.set_color_by_tex("nabla", WHITE) - equations.arrange( - DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT - ) - - circle = Circle(radius = 3*FRAME_X_RADIUS) - circle.set_fill(WHITE, 0) - circle.set_stroke(WHITE, 0) - - self.play( - Write(equations), - morty.change, "confused", equations - ) - self.wait() - self.play(morty.change, "pleading") - self.wait(2) - - ## - movers = VGroup(*equations.family_members_with_points()) - random.shuffle(movers.submobjects) - for mover in list(movers): - if mover.is_subpath: - movers.remove(mover) - continue - mover.set_stroke(WHITE, width = 0) - mover.target = Circle() - mover.target.scale(0.5) - mover.target.set_fill(mover.get_color(), opacity = 0) - mover.target.set_stroke(BLACK, width = 1) - mover.target.move_to(mover) - self.play( - LaggedStartMap( - MoveToTarget, movers, - run_time = 2, - ), - morty.change, "pondering", - ) - self.wait() - -class TODOInsertPreviewLearning(TODOStub): - CONFIG = { - "message" : "Insert PreviewLearning" - } - -class ShowAveragingCost(PreviewLearning): - CONFIG = { - "network_scale_val" : 0.8, - "stroke_width_exp" : 1, - "start_examples_time" : 5, - "examples_per_adjustment_time" : 2, - "n_adjustments" : 5, - "time_per_example" : 1./15, - "image_height" : 1.2, - } - def construct(self): - self.setup_network() - self.setup_diff_words() - self.show_many_examples() - - def setup_network(self): - self.network_mob.scale(self.network_scale_val) - self.network_mob.to_edge(DOWN) - self.network_mob.shift(LEFT) - self.color_network_edges() - - def setup_diff_words(self): - last_layer_copy = self.network_mob.layers[-1].deepcopy() - last_layer_copy.add(self.network_mob.output_labels.copy()) - last_layer_copy.shift(1.5*RIGHT) - - double_arrow = DoubleArrow( - self.network_mob.output_labels, - last_layer_copy, - color = RED - ) - brace = Brace( - VGroup(self.network_mob.layers[-1], last_layer_copy), - UP - ) - cost_words = brace.get_text("Cost of \\\\ one example") - cost_words.set_color(RED) - - self.add(last_layer_copy, double_arrow, brace, cost_words) - self.set_variables_as_attrs( - last_layer_copy, double_arrow, brace, cost_words - ) - self.last_layer_copy = last_layer_copy - - def show_many_examples(self): - training_data, validation_data, test_data = load_data_wrapper() - - average_words = TextMobject("Average over all training examples") - average_words.next_to(LEFT, RIGHT) - average_words.to_edge(UP) - self.add(average_words) - - n_start_examples = int(self.start_examples_time/self.time_per_example) - n_examples_per_adjustment = int(self.examples_per_adjustment_time/self.time_per_example) - for train_in, train_out in training_data[:n_start_examples]: - self.show_one_example(train_in, train_out) - self.wait(self.time_per_example) - - #Wiggle all edges - edges = VGroup(*it.chain(*self.network_mob.edge_groups)) - reversed_edges = VGroup(*reversed(edges)) - self.play(LaggedStartMap( - ApplyFunction, edges, - lambda edge : ( - lambda m : m.rotate_in_place(np.pi/12).set_color(YELLOW), - edge, - ), - rate_func = lambda t : wiggle(t, 4), - run_time = 3, - )) - - #Show all, then adjust - words = TextMobject( - "Each step \\\\ uses every \\\\ example\\\\", - "$\\dots$theoretically", - alignment = "" - ) - words.set_color(YELLOW) - words.scale(0.8) - words.to_corner(UP+LEFT) - - for x in range(self.n_adjustments): - if x < 2: - self.play(FadeIn(words[x])) - for train_in, train_out in training_data[:n_examples_per_adjustment]: - self.show_one_example(train_in, train_out) - self.wait(self.time_per_example) - self.play(LaggedStartMap( - ApplyMethod, reversed_edges, - lambda m : (m.rotate_in_place, np.pi), - run_time = 1, - lag_ratio = 0.2, - )) - if x >= 2: - self.wait() - - #### - - def show_one_example(self, train_in, train_out): - if hasattr(self, "curr_image"): - self.remove(self.curr_image) - image = MNistMobject(train_in) - image.set_height(self.image_height) - image.next_to( - self.network_mob.layers[0].neurons, UP, - aligned_edge = LEFT - ) - self.add(image) - self.network_mob.activate_layers(train_in) - - index = np.argmax(train_out) - self.last_layer_copy.neurons.set_fill(opacity = 0) - self.last_layer_copy.neurons[index].set_fill(WHITE, opacity = 1) - self.add(self.last_layer_copy) - - self.curr_image = image - -class FocusOnOneExample(TeacherStudentsScene): - def construct(self): - self.teacher_says("Focus on just \\\\ one example") - self.wait(2) - -class WalkThroughTwoExample(ShowAveragingCost): - CONFIG = { - "random_seed" : 0, - } - def setup(self): - np.random.seed(self.random_seed) - random.seed(self.random_seed) - self.setup_bases() - - def construct(self): - self.force_skipping() - - self.setup_network() - self.setup_diff_words() - self.show_single_example() - self.single_example_influencing_weights() - self.expand_last_layer() - self.show_activation_formula() - self.three_ways_to_increase() - self.note_connections_to_brightest_neurons() - self.fire_together_wire_together() - self.show_desired_increase_to_previous_neurons() - self.only_keeping_track_of_changes() - self.show_other_output_neurons() - self.show_recursion() - - def show_single_example(self): - two_vect = get_organized_images()[2][0] - two_out = np.zeros(10) - two_out[2] = 1.0 - self.show_one_example(two_vect, two_out) - for layer in self.network_mob.layers: - layer.neurons.set_fill(opacity = 0) - - self.activate_network(two_vect) - self.wait() - - def single_example_influencing_weights(self): - two = self.curr_image - two.save_state() - - edge_groups = self.network_mob.edge_groups - def adjust_edge_group_anim(edge_group): - return LaggedStartMap( - ApplyFunction, edge_group, - lambda edge : ( - lambda m : m.rotate_in_place(np.pi/12).set_color(YELLOW), - edge - ), - rate_func = wiggle, - run_time = 1, - ) - - self.play( - two.next_to, edge_groups[0].get_corner(DOWN+RIGHT), DOWN, - adjust_edge_group_anim(edge_groups[0]) - ) - self.play( - ApplyMethod( - two.next_to, edge_groups[1].get_corner(UP+RIGHT), UP, - path_arc = np.pi/6, - ), - adjust_edge_group_anim(VGroup(*reversed(edge_groups[1]))) - ) - self.play( - ApplyMethod( - two.next_to, edge_groups[2].get_corner(DOWN+RIGHT), DOWN, - path_arc = -np.pi/6, - ), - adjust_edge_group_anim(edge_groups[2]) - ) - self.play(two.restore) - self.wait() - - def expand_last_layer(self): - neurons = self.network_mob.layers[-1].neurons - alt_neurons = self.last_layer_copy.neurons - output_labels = self.network_mob.output_labels - alt_output_labels = self.last_layer_copy[-1] - edges = self.network_mob.edge_groups[-1] - - movers = VGroup( - neurons, alt_neurons, - output_labels, alt_output_labels, - *edges - ) - to_fade = VGroup(self.brace, self.cost_words, self.double_arrow) - for mover in movers: - mover.save_state() - mover.generate_target() - mover.target.scale_in_place(2) - neurons[2].save_state() - - neurons.target.to_edge(DOWN, MED_LARGE_BUFF) - output_labels.target.next_to(neurons.target, RIGHT, MED_SMALL_BUFF) - alt_neurons.target.next_to(neurons.target, RIGHT, buff = 2) - alt_output_labels.target.next_to(alt_neurons.target, RIGHT, MED_SMALL_BUFF) - - n_pairs = it.product( - self.network_mob.layers[-2].neurons, - neurons.target - ) - for edge, (n1, n2) in zip(edges, n_pairs): - r1 = n1.get_width()/2.0 - r2 = n2.get_width()/2.0 - c1 = n1.get_center() - c2 = n2.get_center() - vect = c2 - c1 - norm = get_norm(vect) - unit_vect = vect / norm - - edge.target.put_start_and_end_on( - c1 + unit_vect*r1, - c2 - unit_vect*r2 - ) - - self.play( - FadeOut(to_fade), - *list(map(MoveToTarget, movers)) - ) - self.show_decimals(neurons) - self.cannot_directly_affect_activations() - self.show_desired_activation_nudges(neurons, output_labels, alt_output_labels) - self.focus_on_one_neuron(movers) - - def show_decimals(self, neurons): - decimals = VGroup() - for neuron in neurons: - activation = neuron.get_fill_opacity() - decimal = DecimalNumber(activation, num_decimal_places = 1) - decimal.set_width(0.7*neuron.get_width()) - decimal.move_to(neuron) - if activation > 0.8: - decimal.set_color(BLACK) - decimals.add(decimal) - - self.play(Write(decimals, run_time = 2)) - self.wait() - self.decimals = decimals - - def cannot_directly_affect_activations(self): - words = TextMobject("You can only adjust weights and biases") - words.next_to(self.curr_image, RIGHT, MED_SMALL_BUFF, UP) - - edges = VGroup(*self.network_mob.edge_groups.family_members_with_points()) - random.shuffle(edges.submobjects) - for edge in edges: - edge.generate_target() - edge.target.set_stroke( - random.choice([BLUE, RED]), - 2*random.random()**2, - ) - - self.play( - LaggedStartMap( - Transform, edges, - lambda e : (e, e.target), - run_time = 4, - rate_func = there_and_back, - ), - Write(words, run_time = 2) - ) - self.play(FadeOut(words)) - - def show_desired_activation_nudges(self, neurons, output_labels, alt_output_labels): - arrows = VGroup() - rects = VGroup() - for i, neuron, label in zip(it.count(), neurons, alt_output_labels): - activation = neuron.get_fill_opacity() - target_val = 1 if i == 2 else 0 - diff = abs(activation - target_val) - arrow = Arrow( - ORIGIN, diff*neuron.get_height()*DOWN, - color = RED, - ) - arrow.move_to(neuron.get_right()) - arrow.shift(0.175*RIGHT) - if i == 2: - arrow.set_color(BLUE) - arrow.rotate_in_place(np.pi) - arrows.add(arrow) - - rect = SurroundingRectangle(VGroup(neuron, label)) - if i == 2: - rect.set_color(BLUE) - else: - rect.set_color(RED) - rects.add(rect) - - self.play( - output_labels.shift, SMALL_BUFF*RIGHT, - LaggedStartMap(GrowArrow, arrows, run_time = 1) - ) - self.wait() - - #Show changing activations - anims = [] - def get_decimal_update(start, end): - return lambda a : interpolate(start, end, a) - for i in range(10): - target = 1.0 if i == 2 else 0.01 - anims += [neurons[i].set_fill, WHITE, target] - decimal = self.decimals[i] - anims.append(ChangingDecimal( - decimal, - get_decimal_update(decimal.number, target), - num_decimal_places = 1 - )) - anims.append(UpdateFromFunc( - self.decimals[i], - lambda m : m.set_fill(WHITE if m.number < 0.8 else BLACK) - )) - - self.play( - *anims, - run_time = 3, - rate_func = there_and_back - ) - - two_rect = rects[2] - eight_rect = rects[8].copy() - non_two_rects = VGroup(*[r for r in rects if r is not two_rect]) - self.play(ShowCreation(two_rect)) - self.wait() - self.remove(two_rect) - self.play(ReplacementTransform(two_rect.copy(), non_two_rects)) - self.wait() - self.play(LaggedStartMap(FadeOut, non_two_rects, run_time = 1)) - self.play(LaggedStartMap( - ApplyFunction, arrows, - lambda arrow : ( - lambda m : m.scale_in_place(0.5).set_color(YELLOW), - arrow, - ), - rate_func = wiggle - )) - self.play(ShowCreation(two_rect)) - self.wait() - self.play(ReplacementTransform(two_rect, eight_rect)) - self.wait() - self.play(FadeOut(eight_rect)) - - self.arrows = arrows - - def focus_on_one_neuron(self, expanded_mobjects): - network_mob = self.network_mob - neurons = network_mob.layers[-1].neurons - labels = network_mob.output_labels - two_neuron = neurons[2] - neurons.remove(two_neuron) - two_label = labels[2] - labels.remove(two_label) - expanded_mobjects.remove(*two_neuron.edges_in) - two_decimal = self.decimals[2] - self.decimals.remove(two_decimal) - two_arrow = self.arrows[2] - self.arrows.remove(two_arrow) - - to_fade = VGroup(*it.chain( - network_mob.layers[:2], - network_mob.edge_groups[:2], - expanded_mobjects, - self.decimals, - self.arrows - )) - - self.play(FadeOut(to_fade)) - self.wait() - for mob in expanded_mobjects: - if mob in [neurons, labels]: - mob.scale(0.5) - mob.move_to(mob.saved_state) - else: - mob.restore() - for d, a, n in zip(self.decimals, self.arrows, neurons): - d.scale(0.5) - d.move_to(n) - a.scale(0.5) - a.move_to(n.get_right()) - a.shift(SMALL_BUFF*RIGHT) - labels.shift(SMALL_BUFF*RIGHT) - - self.set_variables_as_attrs( - two_neuron, two_label, two_arrow, two_decimal, - ) - - def show_activation_formula(self): - rhs = TexMobject( - "=", "\\sigma(", - "w_0", "a_0", "+", - "w_1", "a_1", "+", - "\\cdots", "+", - "w_{n-1}", "a_{n-1}", "+", - "b", ")" - ) - equals = rhs[0] - sigma = VGroup(rhs[1], rhs[-1]) - w_terms = rhs.get_parts_by_tex("w_") - a_terms = rhs.get_parts_by_tex("a_") - plus_terms = rhs.get_parts_by_tex("+") - b = rhs.get_part_by_tex("b", substring = False) - dots = rhs.get_part_by_tex("dots") - - w_terms.set_color(BLUE) - b.set_color(MAROON_B) - sigma.set_color(YELLOW) - - rhs.to_corner(UP+RIGHT) - sigma.save_state() - sigma.shift(DOWN) - sigma.set_fill(opacity = 0) - - prev_neurons = self.network_mob.layers[-2].neurons - edges = self.two_neuron.edges_in - - neuron_copy = VGroup( - self.two_neuron.copy(), - self.two_decimal.copy(), - ) - - self.play( - neuron_copy.next_to, equals.get_left(), LEFT, - self.curr_image.to_corner, UP+LEFT, - Write(equals) - ) - self.play( - ReplacementTransform(edges.copy(), w_terms), - Write(VGroup(*plus_terms[:-1])), - Write(dots), - run_time = 1.5 - ) - self.wait() - self.play(ReplacementTransform( - prev_neurons.copy(), a_terms, - path_arc = np.pi/2 - )) - self.wait() - self.play( - Write(plus_terms[-1]), - Write(b) - ) - self.wait() - self.play(sigma.restore) - self.wait() - for mob in b, w_terms, a_terms: - self.play( - mob.shift, MED_SMALL_BUFF*DOWN, - rate_func = there_and_back, - lag_ratio = 0.5, - run_time = 1.5 - ) - self.wait() - - self.set_variables_as_attrs( - rhs, w_terms, a_terms, b, - lhs = neuron_copy - ) - - def three_ways_to_increase(self): - w_terms = self.w_terms - a_terms = self.a_terms - b = self.b - increase_words = VGroup( - TextMobject("Increase", "$b$"), - TextMobject("Increase", "$w_i$"), - TextMobject("Change", "$a_i$"), - ) - for words in increase_words: - words.set_color_by_tex_to_color_map({ - "b" : b.get_color(), - "w_" : w_terms.get_color(), - "a_" : a_terms.get_color(), - }) - increase_words.arrange( - DOWN, aligned_edge = LEFT, - buff = LARGE_BUFF - ) - increase_words.to_edge(LEFT) - - mobs = [b, w_terms[0], a_terms[0]] - for words, mob in zip(increase_words, mobs): - self.play( - Write(words[0], run_time = 1), - ReplacementTransform(mob.copy(), words[1]) - ) - self.wait() - - self.increase_words = increase_words - - def note_connections_to_brightest_neurons(self): - w_terms = self.w_terms - a_terms = self.a_terms - increase_words = self.increase_words - prev_neurons = self.network_mob.layers[-2].neurons - edges = self.two_neuron.edges_in - - prev_activations = np.array([n.get_fill_opacity() for n in prev_neurons]) - sorted_indices = np.argsort(prev_activations.flatten()) - bright_neurons = VGroup() - dim_neurons = VGroup() - edges_to_bright_neurons = VGroup() - for i in sorted_indices[:5]: - dim_neurons.add(prev_neurons[i]) - for i in sorted_indices[-4:]: - bright_neurons.add(prev_neurons[i]) - edges_to_bright_neurons.add(edges[i]) - bright_edges = edges_to_bright_neurons.copy() - bright_edges.set_stroke(YELLOW, 4) - - added_words = TextMobject("in proportion to $a_i$") - added_words.next_to( - increase_words[1], DOWN, - 1.5*SMALL_BUFF, LEFT - ) - added_words.set_color(YELLOW) - - terms_rect = SurroundingRectangle( - VGroup(w_terms[0], a_terms[0]), - color = WHITE - ) - - self.play(LaggedStartMap( - ApplyFunction, edges, - lambda edge : ( - lambda m : m.rotate_in_place(np.pi/12).set_stroke(YELLOW), - edge - ), - rate_func = wiggle - )) - self.wait() - self.play( - ShowCreation(bright_edges), - ShowCreation(bright_neurons) - ) - self.play(LaggedStartMap( - ApplyMethod, bright_neurons, - lambda m : (m.shift, MED_LARGE_BUFF*LEFT), - rate_func = there_and_back - )) - self.wait() - self.play( - ReplacementTransform(bright_edges[0].copy(), w_terms[0]), - ReplacementTransform(bright_neurons[0].copy(), a_terms[0]), - ShowCreation(terms_rect) - ) - self.wait() - for x in range(2): - self.play(LaggedStartMap(ShowCreationThenDestruction, bright_edges)) - self.play(LaggedStartMap(ShowCreation, bright_edges)) - self.play(LaggedStartMap( - ApplyMethod, dim_neurons, - lambda m : (m.shift, MED_LARGE_BUFF*LEFT), - rate_func = there_and_back - )) - self.play(FadeOut(terms_rect)) - self.wait() - self.play( - self.curr_image.shift, MED_LARGE_BUFF*RIGHT, - rate_func = wiggle - ) - self.wait() - self.play(Write(added_words)) - self.wait() - - self.set_variables_as_attrs( - bright_neurons, bright_edges, - in_proportion_to_a = added_words - ) - - def fire_together_wire_together(self): - bright_neurons = self.bright_neurons - bright_edges = self.bright_edges - two_neuron = self.two_neuron - two_decimal = self.two_decimal - two_activation = two_decimal.number - - def get_edge_animation(): - return LaggedStartMap( - ShowCreationThenDestruction, bright_edges, - lag_ratio = 0.7 - ) - neuron_arrows = VGroup(*[ - Vector(MED_LARGE_BUFF*RIGHT).next_to(n, LEFT) - for n in bright_neurons - ]) - two_neuron_arrow = Vector(MED_LARGE_BUFF*DOWN) - two_neuron_arrow.next_to(two_neuron, UP) - VGroup(neuron_arrows, two_neuron_arrow).set_color(YELLOW) - - neuron_rects = VGroup(*list(map( - SurroundingRectangle, bright_neurons - ))) - two_neuron_rect = SurroundingRectangle(two_neuron) - seeing_words = TextMobject("Seeing a 2") - seeing_words.scale(0.8) - thinking_words = TextMobject("Thinking about a 2") - thinking_words.scale(0.8) - seeing_words.next_to(neuron_rects, UP) - thinking_words.next_to(two_neuron_arrow, RIGHT) - - morty = Mortimer() - morty.scale(0.8) - morty.to_corner(DOWN+RIGHT) - words = TextMobject(""" - ``Neurons that \\\\ - fire together \\\\ - wire together'' - """) - words.to_edge(RIGHT) - - self.play(FadeIn(morty)) - self.play( - Write(words), - morty.change, "speaking", words - ) - self.play(Blink(morty)) - self.play( - get_edge_animation(), - morty.change, "pondering", bright_edges - ) - self.play(get_edge_animation()) - self.play( - LaggedStartMap(GrowArrow, neuron_arrows), - get_edge_animation(), - ) - self.play( - GrowArrow(two_neuron_arrow), - morty.change, "raise_right_hand", two_neuron - ) - self.play( - ApplyMethod(two_neuron.set_fill, WHITE, 1), - ChangingDecimal( - two_decimal, - lambda a : interpolate(two_activation, 1, a), - num_decimal_places = 1, - ), - UpdateFromFunc( - two_decimal, - lambda m : m.set_color(WHITE if m.number < 0.8 else BLACK), - ), - LaggedStartMap(ShowCreation, bright_edges), - run_time = 2, - ) - self.wait() - self.play( - LaggedStartMap(ShowCreation, neuron_rects), - Write(seeing_words, run_time = 2), - morty.change, "thinking", seeing_words - ) - self.wait() - self.play( - ShowCreation(two_neuron_rect), - Write(thinking_words, run_time = 2), - morty.look_at, thinking_words - ) - self.wait() - self.play(LaggedStartMap(FadeOut, VGroup( - neuron_rects, two_neuron_rect, - seeing_words, thinking_words, - words, morty, - neuron_arrows, two_neuron_arrow, - bright_edges, - ))) - self.play( - ApplyMethod(two_neuron.set_fill, WHITE, two_activation), - ChangingDecimal( - two_decimal, - lambda a : interpolate(1, two_activation, a), - num_decimal_places = 1, - ), - UpdateFromFunc( - two_decimal, - lambda m : m.set_color(WHITE if m.number < 0.8 else BLACK), - ), - ) - - def show_desired_increase_to_previous_neurons(self): - increase_words = self.increase_words - two_neuron = self.two_neuron - two_decimal = self.two_decimal - edges = two_neuron.edges_in - prev_neurons = self.network_mob.layers[-2].neurons - - positive_arrows = VGroup() - negative_arrows = VGroup() - all_arrows = VGroup() - positive_edges = VGroup() - negative_edges = VGroup() - positive_neurons = VGroup() - negative_neurons = VGroup() - for neuron, edge in zip(prev_neurons, edges): - value = self.get_edge_value(edge) - arrow = self.get_neuron_nudge_arrow(edge) - arrow.move_to(neuron.get_left()) - arrow.shift(SMALL_BUFF*LEFT) - all_arrows.add(arrow) - if value > 0: - positive_arrows.add(arrow) - positive_edges.add(edge) - positive_neurons.add(neuron) - else: - negative_arrows.add(arrow) - negative_edges.add(edge) - negative_neurons.add(neuron) - for s_edges in positive_edges, negative_edges: - s_edges.alt_position = VGroup(*[ - Line(LEFT, RIGHT, color = s_edge.get_color()) - for s_edge in s_edges - ]) - s_edges.alt_position.arrange(DOWN, MED_SMALL_BUFF) - s_edges.alt_position.to_corner(DOWN+RIGHT, LARGE_BUFF) - - added_words = TextMobject("in proportion to $w_i$") - added_words.set_color(self.w_terms.get_color()) - added_words.next_to( - increase_words[-1], DOWN, - SMALL_BUFF, aligned_edge = LEFT - ) - - self.play(LaggedStartMap( - ApplyFunction, prev_neurons, - lambda neuron : ( - lambda m : m.scale_in_place(0.5).set_color(YELLOW), - neuron - ), - rate_func = wiggle - )) - self.wait() - for positive in [True, False]: - if positive: - arrows = positive_arrows - s_edges = positive_edges - neurons = positive_neurons - color = self.positive_edge_color - else: - arrows = negative_arrows - s_edges = negative_edges - neurons = negative_neurons - color = self.negative_edge_color - s_edges.save_state() - self.play(Transform(s_edges, s_edges.alt_position)) - self.wait(0.5) - self.play(s_edges.restore) - self.play( - LaggedStartMap(GrowArrow, arrows), - neurons.set_stroke, color - ) - self.play(ApplyMethod( - neurons.set_fill, color, 1, - rate_func = there_and_back, - )) - self.wait() - self.play( - two_neuron.set_fill, None, 0.8, - ChangingDecimal( - two_decimal, - lambda a : two_neuron.get_fill_opacity() - ), - run_time = 3, - rate_func = there_and_back - ) - self.wait() - self.play(*[ - ApplyMethod( - edge.set_stroke, None, 3*edge.get_stroke_width(), - rate_func = there_and_back, - run_time = 2 - ) - for edge in edges - ]) - self.wait() - self.play(Write(added_words, run_time = 1)) - self.play(prev_neurons.set_stroke, WHITE, 2) - - self.set_variables_as_attrs( - in_proportion_to_w = added_words, - prev_neuron_arrows = all_arrows, - ) - - def only_keeping_track_of_changes(self): - arrows = self.prev_neuron_arrows - prev_neurons = self.network_mob.layers[-2].neurons - rect = SurroundingRectangle(VGroup(arrows, prev_neurons)) - - words1 = TextMobject("No direct influence") - words1.next_to(rect, UP) - words2 = TextMobject("Just keeping track") - words2.move_to(words1) - - edges = self.network_mob.edge_groups[-2] - - self.play(ShowCreation(rect)) - self.play(Write(words1)) - self.play(LaggedStartMap( - Indicate, prev_neurons, - rate_func = wiggle - )) - self.wait() - self.play(LaggedStartMap( - ShowCreationThenDestruction, edges - )) - self.play(Transform(words1, words2)) - self.wait() - self.play(FadeOut(VGroup(words1, rect))) - - def show_other_output_neurons(self): - two_neuron = self.two_neuron - two_decimal = self.two_decimal - two_arrow = self.two_arrow - two_label = self.two_label - two_edges = two_neuron.edges_in - - prev_neurons = self.network_mob.layers[-2].neurons - neurons = self.network_mob.layers[-1].neurons - prev_neuron_arrows = self.prev_neuron_arrows - arrows_to_fade = VGroup(prev_neuron_arrows) - - output_labels = self.network_mob.output_labels - quads = list(zip(neurons, self.decimals, self.arrows, output_labels)) - - self.revert_to_original_skipping_status() - self.play( - two_neuron.restore, - two_decimal.scale, 0.5, - two_decimal.move_to, two_neuron.saved_state, - two_arrow.scale, 0.5, - two_arrow.next_to, two_neuron.saved_state, RIGHT, 0.5*SMALL_BUFF, - two_label.scale, 0.5, - two_label.next_to, two_neuron.saved_state, RIGHT, 1.5*SMALL_BUFF, - FadeOut(VGroup(self.lhs, self.rhs)), - *[e.restore for e in two_edges] - ) - for neuron, decimal, arrow, label in quads[:2] + quads[2:5]: - plusses = VGroup() - new_arrows = VGroup() - for edge, prev_arrow in zip(neuron.edges_in, prev_neuron_arrows): - plus = TexMobject("+").scale(0.5) - plus.move_to(prev_arrow) - plus.shift(2*SMALL_BUFF*LEFT) - new_arrow = self.get_neuron_nudge_arrow(edge) - new_arrow.move_to(plus) - new_arrow.shift(2*SMALL_BUFF*LEFT) - plusses.add(plus) - new_arrows.add(new_arrow) - - self.play( - FadeIn(VGroup(neuron, decimal, arrow, label)), - LaggedStartMap(ShowCreation, neuron.edges_in), - ) - self.play( - ReplacementTransform(neuron.edges_in.copy(), new_arrows), - Write(plusses, run_time = 2) - ) - - arrows_to_fade.add(new_arrows, plusses) - prev_neuron_arrows = new_arrows - - all_dots_plus = VGroup() - for arrow in prev_neuron_arrows: - dots_plus = TexMobject("\\cdots +") - dots_plus.scale(0.5) - dots_plus.move_to(arrow.get_center(), RIGHT) - dots_plus.shift(2*SMALL_BUFF*LEFT) - all_dots_plus.add(dots_plus) - arrows_to_fade.add(all_dots_plus) - - self.play( - LaggedStartMap( - FadeIn, VGroup(*it.starmap(VGroup, quads[5:])), - ), - LaggedStartMap( - FadeIn, VGroup(*[n.edges_in for n in neurons[5:]]) - ), - Write(all_dots_plus), - run_time = 3, - ) - self.wait(2) - - ## - words = TextMobject("Propagate backwards") - words.to_edge(UP) - words.set_color(BLUE) - target_arrows = prev_neuron_arrows.copy() - target_arrows.next_to(prev_neurons, RIGHT, SMALL_BUFF) - rect = SurroundingRectangle(VGroup( - self.network_mob.layers[-1], - self.network_mob.output_labels - )) - rect.set_fill(BLACK, 1) - rect.set_stroke(BLACK, 0) - self.play(Write(words)) - self.wait() - self.play( - FadeOut(self.network_mob.edge_groups[-1]), - FadeIn(rect), - ReplacementTransform(arrows_to_fade, VGroup(target_arrows)), - ) - self.prev_neuron_arrows = target_arrows - - def show_recursion(self): - network_mob = self.network_mob - words_to_fade = VGroup( - self.increase_words, - self.in_proportion_to_w, - self.in_proportion_to_a, - ) - edges = network_mob.edge_groups[1] - neurons = network_mob.layers[2].neurons - prev_neurons = network_mob.layers[1].neurons - for neuron in neurons: - neuron.edges_in.save_state() - - self.play( - FadeOut(words_to_fade), - FadeIn(prev_neurons), - LaggedStartMap(ShowCreation, edges), - ) - self.wait() - for neuron, arrow in zip(neurons, self.prev_neuron_arrows): - edge_copies = neuron.edges_in.copy() - for edge in edge_copies: - edge.set_stroke(arrow.get_color(), 2) - edge.rotate_in_place(np.pi) - self.play( - edges.set_stroke, None, 0.15, - neuron.edges_in.restore, - ) - self.play(ShowCreationThenDestruction(edge_copies)) - self.remove(edge_copies) - - - #### - - def get_neuron_nudge_arrow(self, edge): - value = self.get_edge_value(edge) - height = np.sign(value)*0.1 + 0.1*value - arrow = Vector(height*UP, color = edge.get_color()) - return arrow - - def get_edge_value(self, edge): - value = edge.get_stroke_width() - if Color(edge.get_stroke_color()) == Color(self.negative_edge_color): - value *= -1 - return value - -class WriteHebbian(Scene): - def construct(self): - words = TextMobject("Hebbian theory") - words.set_width(FRAME_WIDTH - 1) - words.to_edge(UP) - self.play(Write(words)) - self.wait() - -class NotANeuroScientist(TeacherStudentsScene): - def construct(self): - quote = TextMobject("``Neurons that fire together wire together''") - quote.to_edge(UP) - self.add(quote) - asterisks = TextMobject("***") - asterisks.next_to(quote.get_corner(UP+RIGHT), RIGHT, SMALL_BUFF) - asterisks.set_color(BLUE) - - brain = SVGMobject(file_name = "brain") - brain.set_height(1.5) - self.add(brain) - double_arrow = DoubleArrow(LEFT, RIGHT) - double_arrow.next_to(brain, RIGHT) - q_marks = TextMobject("???") - q_marks.next_to(double_arrow, UP) - - network = NetworkMobject(Network(sizes = [6, 4, 4, 5])) - network.set_height(1.5) - network.next_to(double_arrow, RIGHT) - - group = VGroup(brain, double_arrow, q_marks, network) - group.next_to(self.students, UP, buff = 1.5) - self.add(group) - self.add(ContinualEdgeUpdate( - network, - stroke_width_exp = 0.5, - color = [BLUE, RED], - )) - - rect = SurroundingRectangle(group) - no_claim_words = TextMobject("No claims here...") - no_claim_words.next_to(rect, UP) - no_claim_words.set_color(YELLOW) - - brain_outline = brain.copy() - brain_outline.set_fill(opacity = 0) - brain_outline.set_stroke(BLUE, 3) - brain_anim = ShowCreationThenDestruction(brain_outline) - - words = TextMobject("Definitely not \\\\ a neuroscientist") - words.next_to(self.teacher, UP, buff = 1.5) - words.shift_onto_screen() - arrow = Arrow(words.get_bottom(), self.teacher.get_top()) - - self.play( - Write(words), - GrowArrow(arrow), - self.teacher.change, "guilty", words, - run_time = 1, - ) - self.change_student_modes(*3*["sassy"]) - self.play( - ShowCreation(rect), - Write(no_claim_words, run_time = 1), - brain_anim - ) - self.wait() - self.play(brain_anim) - self.play(FocusOn(asterisks)) - self.play(Write(asterisks, run_time = 1)) - for x in range(2): - self.play(brain_anim) - self.wait() - -class ConstructGradientFromAllTrainingExamples(Scene): - CONFIG = { - "image_height" : 0.9, - "eyes_height" : 0.25, - "n_examples" : 6, - "change_scale_val" : 0.8, - } - def construct(self): - self.setup_grid() - self.setup_weights() - self.show_two_requesting_changes() - self.show_all_examples_requesting_changes() - self.average_together() - self.collapse_into_gradient_vector() - - def setup_grid(self): - h_lines = VGroup(*[ - Line(LEFT, RIGHT).scale(0.85*FRAME_X_RADIUS) - for x in range(6) - ]) - h_lines.arrange(DOWN, buff = 1) - h_lines.set_stroke(LIGHT_GREY, 2) - h_lines.to_edge(DOWN, buff = MED_LARGE_BUFF) - h_lines.to_edge(LEFT, buff = 0) - - v_lines = VGroup(*[ - Line(UP, DOWN).scale(FRAME_Y_RADIUS - MED_LARGE_BUFF) - for x in range(self.n_examples + 1) - ]) - v_lines.arrange(RIGHT, buff = 1.4) - v_lines.set_stroke(LIGHT_GREY, 2) - v_lines.to_edge(LEFT, buff = 2) - - # self.add(h_lines, v_lines) - self.h_lines = h_lines - self.v_lines = v_lines - - def setup_weights(self): - weights = VGroup(*list(map(TexMobject, [ - "w_0", "w_1", "w_2", "\\vdots", "w_{13{,}001}" - ]))) - for i, weight in enumerate(weights): - weight.move_to(self.get_grid_position(i, 0)) - weights.to_edge(LEFT, buff = MED_SMALL_BUFF) - - brace = Brace(weights, RIGHT) - weights_words = brace.get_text("All weights and biases") - - self.add(weights, brace, weights_words) - self.set_variables_as_attrs( - weights, brace, weights_words, - dots = weights[-2] - ) - - def show_two_requesting_changes(self): - two = self.get_example(get_organized_images()[2][0], 0) - self.two = two - self.add(two) - - self.two_changes = VGroup() - for i in list(range(3)) + [4]: - weight = self.weights[i] - bubble, change = self.get_requested_change_bubble(two) - weight.save_state() - weight.generate_target() - weight.target.next_to(two, RIGHT, aligned_edge = DOWN) - - self.play( - MoveToTarget(weight), - two.eyes.look_at_anim(weight.target), - FadeIn(bubble), - Write(change, run_time = 1), - ) - if random.random() < 0.5: - self.play(two.eyes.blink_anim()) - else: - self.wait() - if i == 0: - added_anims = [ - FadeOut(self.brace), - FadeOut(self.weights_words), - ] - elif i == 4: - dots_copy = self.dots.copy() - added_anims = [ - dots_copy.move_to, - self.get_grid_position(3, 0) - ] - self.first_column_dots = dots_copy - else: - added_anims = [] - self.play( - FadeOut(bubble), - weight.restore, - two.eyes.look_at_anim(weight.saved_state), - change.restore, - change.scale, self.change_scale_val, - change.move_to, self.get_grid_position(i, 0), - *added_anims - ) - self.two_changes.add(change) - self.wait() - - def show_all_examples_requesting_changes(self): - training_data, validation_data, test_data = load_data_wrapper() - data = training_data[:self.n_examples-1] - examples = VGroup(*[ - self.get_example(t[0], j) - for t, j in zip(data, it.count(1)) - ]) - h_dots = TexMobject("\\dots") - h_dots.next_to(examples, RIGHT, MED_LARGE_BUFF) - more_h_dots = VGroup(*[ - TexMobject("\\dots").move_to( - self.get_grid_position(i, self.n_examples) - ) - for i in range(5) - ]) - more_h_dots.shift(MED_LARGE_BUFF*RIGHT) - more_h_dots[-2].rotate_in_place(-np.pi/4) - more_v_dots = VGroup(*[ - self.dots.copy().move_to( - self.get_grid_position(3, j) - ) - for j in range(1, self.n_examples) - ]) - - changes = VGroup(*[ - self.get_random_decimal().move_to( - self.get_grid_position(i, j) - ) - for i in list(range(3)) + [4] - for j in range(1, self.n_examples) - ]) - for change in changes: - change.scale_in_place(self.change_scale_val) - - self.play( - LaggedStartMap(FadeIn, examples), - LaggedStartMap(ShowCreation, self.h_lines), - LaggedStartMap(ShowCreation, self.v_lines), - Write( - h_dots, - run_time = 2, - rate_func = squish_rate_func(smooth, 0.7, 1) - ) - ) - self.play( - Write(changes), - Write(more_v_dots), - Write(more_h_dots), - *[ - example.eyes.look_at_anim(random.choice(changes)) - for example in examples - ] - ) - for x in range(2): - self.play(random.choice(examples).eyes.blink_anim()) - - k = self.n_examples - 1 - self.change_rows = VGroup(*[ - VGroup(two_change, *changes[k*i:k*(i+1)]) - for i, two_change in enumerate(self.two_changes) - ]) - for i in list(range(3)) + [-1]: - self.change_rows[i].add(more_h_dots[i]) - - self.all_eyes = VGroup(*[ - m.eyes for m in [self.two] + list(examples) - ]) - - self.set_variables_as_attrs( - more_h_dots, more_v_dots, - h_dots, changes, - ) - - def average_together(self): - rects = VGroup() - arrows = VGroup() - averages = VGroup() - for row in self.change_rows: - rect = SurroundingRectangle(row) - arrow = Arrow(ORIGIN, RIGHT) - arrow.next_to(rect, RIGHT) - rect.arrow = arrow - average = self.get_colored_decimal(3*np.mean([ - m.number for m in row - if isinstance(m, DecimalNumber) - ])) - average.scale(self.change_scale_val) - average.next_to(arrow, RIGHT) - row.target = VGroup(average) - - rects.add(rect) - arrows.add(arrow) - averages.add(average) - - words = TextMobject("Average over \\\\ all training data") - words.scale(0.8) - words.to_corner(UP+RIGHT) - arrow_to_averages = Arrow( - words.get_bottom(), averages.get_top(), - color = WHITE - ) - - dots = self.dots.copy() - dots.move_to(VGroup(*averages[-2:])) - - look_at_anims = self.get_look_at_anims - - self.play(Write(words, run_time = 1), *look_at_anims(words)) - self.play(ShowCreation(rects[0]), *look_at_anims(rects[0])) - self.play( - ReplacementTransform(rects[0].copy(), arrows[0]), - rects[0].set_stroke, WHITE, 1, - ReplacementTransform( - self.change_rows[0].copy(), - self.change_rows[0].target - ), - *look_at_anims(averages[0]) - ) - self.play(GrowArrow(arrow_to_averages)) - self.play( - LaggedStartMap(ShowCreation, VGroup(*rects[1:])), - *look_at_anims(rects[1]) - ) - self.play( - LaggedStartMap( - ReplacementTransform, VGroup(*rects[1:]).copy(), - lambda m : (m, m.arrow), - lag_ratio = 0.7, - ), - VGroup(*rects[1:]).set_stroke, WHITE, 1, - LaggedStartMap( - ReplacementTransform, VGroup(*self.change_rows[1:]).copy(), - lambda m : (m, m.target), - lag_ratio = 0.7, - ), - Write(dots), - *look_at_anims(averages[1]) - ) - self.blink(3) - self.wait() - - averages.add(dots) - self.set_variables_as_attrs( - rects, arrows, averages, - arrow_to_averages - ) - - def collapse_into_gradient_vector(self): - averages = self.averages - lb, rb = brackets = TexMobject("[]") - brackets.scale(2) - brackets.stretch_to_fit_height(1.2*averages.get_height()) - lb.next_to(averages, LEFT, SMALL_BUFF) - rb.next_to(averages, RIGHT, SMALL_BUFF) - brackets.set_fill(opacity = 0) - - shift_vect = 2*LEFT - - lhs = TexMobject( - "-", "\\nabla", "C(", - "w_1,", "w_2,", "\\dots", "w_{13{,}001}", - ")", "=" - ) - lhs.next_to(lb, LEFT) - lhs.shift(shift_vect) - minus = lhs[0] - w_terms = lhs.get_parts_by_tex("w_") - dots_term = lhs.get_part_by_tex("dots") - eta = TexMobject("\\eta") - eta.move_to(minus, RIGHT) - eta.set_color(MAROON_B) - - to_fade = VGroup(*it.chain( - self.h_lines, self.v_lines, - self.more_h_dots, self.more_v_dots, - self.change_rows, - self.first_column_dots, - self.rects, - self.arrows, - )) - arrow = self.arrow_to_averages - - self.play(LaggedStartMap(FadeOut, to_fade)) - self.play( - brackets.shift, shift_vect, - brackets.set_fill, WHITE, 1, - averages.shift, shift_vect, - Transform(arrow, Arrow( - arrow.get_start(), - arrow.get_end() + shift_vect, - buff = 0, - color = arrow.get_color(), - )), - FadeIn(VGroup(*lhs[:3])), - FadeIn(VGroup(*lhs[-2:])), - *self.get_look_at_anims(lhs) - ) - self.play( - ReplacementTransform(self.weights, w_terms), - ReplacementTransform(self.dots, dots_term), - *self.get_look_at_anims(w_terms) - ) - self.blink(2) - self.play( - GrowFromCenter(eta), - minus.shift, MED_SMALL_BUFF*LEFT - ) - self.wait() - - #### - - def get_example(self, in_vect, index): - result = MNistMobject(in_vect) - result.set_height(self.image_height) - - eyes = Eyes(result, height = self.eyes_height) - result.eyes = eyes - result.add(eyes) - result.move_to(self.get_grid_position(0, index)) - result.to_edge(UP, buff = LARGE_BUFF) - return result - - def get_grid_position(self, i, j): - x = VGroup(*self.v_lines[j:j+2]).get_center()[0] - y = VGroup(*self.h_lines[i:i+2]).get_center()[1] - return x*RIGHT + y*UP - - def get_requested_change_bubble(self, example_mob): - change = self.get_random_decimal() - words = TextMobject("Change by") - change.next_to(words, RIGHT) - change.save_state() - content = VGroup(words, change) - - bubble = SpeechBubble(height = 1.5, width = 3) - bubble.add_content(content) - group = VGroup(bubble, content) - group.shift( - example_mob.get_right() + SMALL_BUFF*RIGHT \ - -bubble.get_corner(DOWN+LEFT) - ) - - return VGroup(bubble, words), change - - def get_random_decimal(self): - return self.get_colored_decimal( - 0.3*(random.random() - 0.5) - ) - - def get_colored_decimal(self, number): - result = DecimalNumber(number) - if result.number > 0: - plus = TexMobject("+") - plus.next_to(result, LEFT, SMALL_BUFF) - result.add_to_back(plus) - result.set_color(BLUE) - else: - result.set_color(RED) - return result - - def get_look_at_anims(self, mob): - return [eyes.look_at_anim(mob) for eyes in self.all_eyes] - - def blink(self, n): - for x in range(n): - self.play(random.choice(self.all_eyes).blink_anim()) - -class WatchPreviousScene(TeacherStudentsScene): - def construct(self): - screen = ScreenRectangle(height = 4.5) - screen.to_corner(UP+LEFT) - - self.play( - self.teacher.change, "raise_right_hand", screen, - self.get_student_changes( - *["thinking"]*3, - look_at_arg = screen - ), - ShowCreation(screen) - ) - self.wait(10) - -class OpenCloseSGD(Scene): - def construct(self): - term = TexMobject( - "\\langle", "\\text{Stochastic gradient descent}", - "\\rangle" - ) - alt_term0 = TexMobject("\\langle /") - alt_term0.move_to(term[0], RIGHT) - - term.save_state() - center = term.get_center() - term[0].move_to(center, RIGHT) - term[2].move_to(center, LEFT) - term[1].scale(0.0001).move_to(center) - - self.play(term.restore) - self.wait(2) - self.play(Transform(term[0], alt_term0)) - self.wait(2) - -class OrganizeDataIntoMiniBatches(Scene): - CONFIG = { - "n_rows" : 5, - "n_cols" : 12, - "example_height" : 1, - "random_seed" : 0, - } - def construct(self): - self.seed_random_libraries() - self.add_examples() - self.shuffle_examples() - self.divide_into_minibatches() - self.one_step_per_batch() - - def seed_random_libraries(self): - random.seed(self.random_seed) - np.random.seed(self.random_seed) - - def add_examples(self): - examples = self.get_examples() - self.arrange_examples_in_grid(examples) - for example in examples: - example.save_state() - alt_order_examples = VGroup(*examples) - for mob in examples, alt_order_examples: - random.shuffle(mob.submobjects) - self.arrange_examples_in_grid(examples) - - self.play(LaggedStartMap( - FadeIn, alt_order_examples, - lag_ratio = 0.2, - run_time = 4 - )) - self.wait() - - self.examples = examples - - def shuffle_examples(self): - self.play(LaggedStartMap( - ApplyMethod, self.examples, - lambda m : (m.restore,), - lag_ratio = 0.3, - run_time = 3, - path_arc = np.pi/3, - )) - self.wait() - - def divide_into_minibatches(self): - examples = self.examples - examples.sort(lambda p : -p[1]) - rows = Group(*[ - Group(*examples[i*self.n_cols:(i+1)*self.n_cols]) - for i in range(self.n_rows) - ]) - - mini_batches_words = TextMobject("``Mini-batches''") - mini_batches_words.to_edge(UP) - mini_batches_words.set_color(YELLOW) - - self.play( - rows.space_out_submobjects, 1.5, - rows.to_edge, UP, 1.5, - Write(mini_batches_words, run_time = 1) - ) - - rects = VGroup(*[ - SurroundingRectangle( - row, - stroke_width = 0, - fill_color = YELLOW, - fill_opacity = 0.25, - ) - for row in rows - ]) - self.play(LaggedStartMap( - FadeIn, rects, - lag_ratio = 0.7, - rate_func = there_and_back - )) - self.wait() - - self.set_variables_as_attrs(rows, rects, mini_batches_words) - - def one_step_per_batch(self): - rows = self.rows - brace = Brace(rows[0], UP, buff = SMALL_BUFF) - text = brace.get_text( - "Compute gradient descent step (using backprop)", - buff = SMALL_BUFF - ) - def indicate_row(row): - row.sort(lambda p : p[0]) - return LaggedStartMap( - ApplyFunction, row, - lambda row : ( - lambda m : m.scale_in_place(0.75).set_color(YELLOW), - row - ), - rate_func = wiggle - ) - - self.play( - FadeOut(self.mini_batches_words), - GrowFromCenter(brace), - Write(text, run_time = 2), - ) - self.play(indicate_row(rows[0])) - brace.add(text) - for last_row, row in zip(rows, rows[1:-1]): - self.play( - last_row.shift, UP, - brace.next_to, row, UP, SMALL_BUFF - ) - self.play(indicate_row(row)) - self.wait() - - - ### - - def get_examples(self): - n_examples = self.n_rows*self.n_cols - height = self.example_height - training_data, validation_data, test_data = load_data_wrapper() - return Group(*[ - MNistMobject( - t[0], - rect_kwargs = {"stroke_width" : 2} - ).set_height(height) - for t in training_data[:n_examples] - ]) - # return Group(*[ - # Square( - # color = BLUE, - # stroke_width = 2 - # ).set_height(height) - # for x in range(n_examples) - # ]) - - def arrange_examples_in_grid(self, examples): - examples.arrange_in_grid( - n_rows = self.n_rows, - buff = SMALL_BUFF - ) - -class SGDSteps(ExternallyAnimatedScene): - pass - -class GradientDescentSteps(ExternallyAnimatedScene): - pass - -class SwimmingInTerms(TeacherStudentsScene): - def construct(self): - terms = VGroup( - TextMobject("Cost surface"), - TextMobject("Stochastic gradient descent"), - TextMobject("Mini-batches"), - TextMobject("Backpropagation"), - ) - terms.arrange(DOWN) - terms.to_edge(UP) - self.play( - LaggedStartMap(FadeIn, terms), - self.get_student_changes(*["horrified"]*3) - ) - self.wait() - self.play( - terms[-1].next_to, self.teacher.get_corner(UP+LEFT), UP, - FadeOut(VGroup(*terms[:-1])), - self.teacher.change, "raise_right_hand", - self.get_student_changes(*["pondering"]*3) - ) - self.wait() - -class BackpropCode(ExternallyAnimatedScene): - pass - -class BackpropCodeAddOn(PiCreatureScene): - def construct(self): - words = TextMobject( - "The code you'd find \\\\ in Nielsen's book" - ) - words.to_corner(DOWN+LEFT) - morty = self.pi_creature - morty.next_to(words, UP) - self.add(words) - for mode in ["pondering", "thinking", "happy"]: - self.play( - morty.change, "pondering", - morty.look, UP+LEFT - ) - self.play(morty.look, LEFT) - self.wait(2) - -class CannotFollowCode(TeacherStudentsScene): - def construct(self): - self.student_says( - "I...er...can't follow\\\\ that code at all.", - target_mode = "confused", - student_index = 1 - ) - self.play(self.students[1].change, "sad") - self.change_student_modes( - "angry", "sad", "angry", - look_at_arg = self.teacher.eyes - ) - self.play(self.teacher.change, "hesitant") - self.wait(2) - self.teacher_says( - "Let's get to the \\\\ calculus then", - target_mode = "hooray", - added_anims = [self.get_student_changes(*3*["plain"])], - run_time = 1 - ) - self.wait(2) - -class EOCWrapper(Scene): - def construct(self): - title = TextMobject("Essence of calculus") - title.to_edge(UP) - screen = ScreenRectangle(height = 6) - screen.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(screen)) - self.wait() - -class SimplestNetworkExample(PreviewLearning): - CONFIG = { - "random_seed" : 6, - "z_color" : GREEN, - "cost_color" : RED, - "desired_output_color" : YELLOW, - "derivative_scale_val" : 0.85, - } - def construct(self): - self.seed_random_libraries() - self.collapse_ordinary_network() - self.show_weights_and_biases() - self.focus_just_on_last_two_layers() - self.label_neurons() - self.show_desired_output() - self.show_cost() - self.show_activation_formula() - self.introduce_z() - self.break_into_computational_graph() - self.show_preceding_layer_in_computational_graph() - self.show_number_lines() - self.ask_about_w_sensitivity() - self.show_derivative_wrt_w() - self.show_chain_of_events() - self.show_chain_rule() - self.name_chain_rule() - self.indicate_everything_on_screen() - self.prepare_for_derivatives() - self.compute_derivatives() - self.get_lost_in_formulas() - self.fire_together_wire_together() - self.organize_chain_rule_rhs() - self.show_average_derivative() - self.show_gradient() - self.transition_to_derivative_wrt_b() - self.show_derivative_wrt_b() - self.show_derivative_wrt_a() - self.show_previous_weight_and_bias() - self.animate_long_path() - - def seed_random_libraries(self): - np.random.seed(self.random_seed) - random.seed(self.random_seed) - - def collapse_ordinary_network(self): - network_mob = self.network_mob - config = dict(self.network_mob_config) - config.pop("include_output_labels") - config.update({ - "edge_stroke_width" : 3, - "edge_propogation_color" : YELLOW, - "edge_propogation_time" : 1, - "neuron_radius" : 0.3, - }) - simple_network = Network(sizes = [1, 1, 1, 1]) - simple_network_mob = NetworkMobject(simple_network, **config) - self.color_network_edges() - s_edges = simple_network_mob.edge_groups - for edge, weight_matrix in zip(s_edges, simple_network.weights): - weight = weight_matrix[0][0] - width = 2*abs(weight) - color = BLUE if weight > 0 else RED - edge.set_stroke(color, width) - - def edge_collapse_anims(edges, left_attachment_target): - return [ - ApplyMethod( - e.put_start_and_end_on_with_projection, - left_attachment_target.get_right(), - e.get_end() - ) - for e in edges - ] - - neuron = simple_network_mob.layers[0].neurons[0] - self.play( - ReplacementTransform(network_mob.layers[0], neuron), - *edge_collapse_anims(network_mob.edge_groups[0], neuron) - ) - for i, layer in enumerate(network_mob.layers[1:]): - neuron = simple_network_mob.layers[i+1].neurons[0] - prev_edges = network_mob.edge_groups[i] - prev_edge_target = simple_network_mob.edge_groups[i] - if i+1 < len(network_mob.edge_groups): - edges = network_mob.edge_groups[i+1] - added_anims = edge_collapse_anims(edges, neuron) - else: - added_anims = [FadeOut(network_mob.output_labels)] - self.play( - ReplacementTransform(layer, neuron), - ReplacementTransform(prev_edges, prev_edge_target), - *added_anims - ) - self.remove(network_mob) - self.add(simple_network_mob) - self.network_mob = simple_network_mob - self.network = self.network_mob.neural_network - self.feed_forward(np.array([0.5])) - self.wait() - - def show_weights_and_biases(self): - network_mob = self.network_mob - edges = VGroup(*[eg[0] for eg in network_mob.edge_groups]) - neurons = VGroup(*[ - layer.neurons[0] - for layer in network_mob.layers[1:] - ]) - expression = TexMobject( - "C", "(", - "w_1", ",", "b_1", ",", - "w_2", ",", "b_2", ",", - "w_3", ",", "b_3", - ")" - ) - expression.shift(2*UP) - expression.set_color_by_tex("C", RED) - w_terms = expression.get_parts_by_tex("w_") - for w, edge in zip(w_terms, edges): - w.set_color(edge.get_color()) - b_terms = expression.get_parts_by_tex("b_") - variables = VGroup(*it.chain(w_terms, b_terms)) - other_terms = VGroup(*[m for m in expression if m not in variables]) - random.shuffle(variables.submobjects) - - self.play(ReplacementTransform(edges.copy(), w_terms)) - self.wait() - self.play(ReplacementTransform(neurons.copy(), b_terms)) - self.wait() - self.play(Write(other_terms)) - for x in range(2): - self.play(LaggedStartMap( - Indicate, variables, - rate_func = wiggle, - run_time = 4, - )) - self.wait() - self.play( - FadeOut(other_terms), - ReplacementTransform(w_terms, edges), - ReplacementTransform(b_terms, neurons), - ) - self.remove(expression) - - def focus_just_on_last_two_layers(self): - to_fade = VGroup(*it.chain(*list(zip( - self.network_mob.layers[:2], - self.network_mob.edge_groups[:2], - )))) - for mob in to_fade: - mob.save_state() - self.play(LaggedStartMap( - ApplyMethod, to_fade, - lambda m : (m.fade, 0.9) - )) - self.wait() - - self.prev_layers = to_fade - - def label_neurons(self): - neurons = VGroup(*[ - self.network_mob.layers[i].neurons[0] - for i in (-1, -2) - ]) - decimals = VGroup() - a_labels = VGroup() - a_label_arrows = VGroup() - superscripts = ["L", "L-1"] - superscript_rects = VGroup() - for neuron, superscript in zip(neurons, superscripts): - decimal = self.get_neuron_activation_decimal(neuron) - label = TexMobject("a^{(%s)}"%superscript) - label.next_to(neuron, DOWN, buff = LARGE_BUFF) - superscript_rect = SurroundingRectangle(VGroup(*label[1:])) - arrow = Arrow( - label[0].get_top(), - neuron.get_bottom(), - buff = SMALL_BUFF, - color = WHITE - ) - - decimal.save_state() - decimal.set_fill(opacity = 0) - decimal.move_to(label) - - decimals.add(decimal) - a_labels.add(label) - a_label_arrows.add(arrow) - superscript_rects.add(superscript_rect) - - self.play( - Write(label, run_time = 1), - GrowArrow(arrow), - ) - self.play(decimal.restore) - opacity = neuron.get_fill_opacity() - self.play( - neuron.set_fill, None, 0, - ChangingDecimal( - decimal, - lambda a : interpolate(opacity, 0.01, a) - ), - UpdateFromFunc( - decimal, - lambda d : d.set_fill(WHITE if d.number < 0.8 else BLACK) - ), - run_time = 2, - rate_func = there_and_back, - ) - self.wait() - - not_exponents = TextMobject("Not exponents") - not_exponents.next_to(superscript_rects, DOWN, MED_LARGE_BUFF) - not_exponents.set_color(YELLOW) - - self.play( - LaggedStartMap( - ShowCreation, superscript_rects, - lag_ratio = 0.8, run_time = 1.5 - ), - Write(not_exponents, run_time = 2) - ) - self.wait() - self.play(*list(map(FadeOut, [not_exponents, superscript_rects]))) - - self.set_variables_as_attrs( - a_labels, a_label_arrows, decimals, - last_neurons = neurons - ) - - def show_desired_output(self): - neuron = self.network_mob.layers[-1].neurons[0].copy() - neuron.shift(2*RIGHT) - neuron.set_fill(opacity = 1) - decimal = self.get_neuron_activation_decimal(neuron) - - rect = SurroundingRectangle(neuron) - words = TextMobject("Desired \\\\ output") - words.next_to(rect, UP) - - y_label = TexMobject("y") - y_label.next_to(neuron, DOWN, LARGE_BUFF) - y_label.align_to(self.a_labels, DOWN) - y_label_arrow = Arrow( - y_label, neuron, - color = WHITE, - buff = SMALL_BUFF - ) - VGroup(words, rect, y_label).set_color(self.desired_output_color) - - self.play(*list(map(FadeIn, [neuron, decimal]))) - self.play( - ShowCreation(rect), - Write(words, run_time = 1) - ) - self.wait() - self.play( - Write(y_label, run_time = 1), - GrowArrow(y_label_arrow) - ) - self.wait() - - self.set_variables_as_attrs( - y_label, y_label_arrow, - desired_output_neuron = neuron, - desired_output_decimal = decimal, - desired_output_rect = rect, - desired_output_words = words, - ) - - def show_cost(self): - pre_a = self.a_labels[0].copy() - pre_y = self.y_label.copy() - - cost_equation = TexMobject( - "C_0", "(", "\\dots", ")", "=", - "(", "a^{(L)}", "-", "y", ")", "^2" - ) - cost_equation.to_corner(UP+RIGHT) - C0, a, y = [ - cost_equation.get_part_by_tex(tex) - for tex in ("C_0", "a^{(L)}", "y") - ] - y.set_color(YELLOW) - - cost_word = TextMobject("Cost") - cost_word.next_to(C0[0], LEFT, LARGE_BUFF) - cost_arrow = Arrow( - cost_word, C0, - buff = SMALL_BUFF - ) - VGroup(C0, cost_word, cost_arrow).set_color(self.cost_color) - - expression = TexMobject( - "\\text{For example: }" - "(", "0.00", "-", "0.00", ")", "^2" - ) - numbers = expression.get_parts_by_tex("0.00") - non_numbers = VGroup(*[m for m in expression if m not in numbers]) - expression.next_to(cost_equation, DOWN, aligned_edge = RIGHT) - decimals = VGroup( - self.decimals[0], - self.desired_output_decimal - ).copy() - decimals.generate_target() - for d, n in zip(decimals.target, numbers): - d.replace(n, dim_to_match = 1) - d.set_color(n.get_color()) - - self.play( - ReplacementTransform(pre_a, a), - ReplacementTransform(pre_y, y), - ) - self.play(LaggedStartMap( - FadeIn, VGroup(*[m for m in cost_equation if m not in [a, y]]) - )) - self.play( - MoveToTarget(decimals), - FadeIn(non_numbers) - ) - self.wait() - self.play( - Write(cost_word, run_time = 1), - GrowArrow(cost_arrow) - ) - self.play(C0.shift, MED_SMALL_BUFF*UP, rate_func = wiggle) - self.wait() - self.play(*list(map(FadeOut, [decimals, non_numbers]))) - - self.set_variables_as_attrs( - cost_equation, cost_word, cost_arrow - ) - - def show_activation_formula(self): - neuron = self.network_mob.layers[-1].neurons[0] - edge = self.network_mob.edge_groups[-1][0] - pre_aL, pre_aLm1 = self.a_labels.copy() - - formula = TexMobject( - "a^{(L)}", "=", "\\sigma", "(", - "w^{(L)}", "a^{(L-1)}", "+", "b^{(L)}", ")" - ) - formula.next_to(neuron, UP, MED_LARGE_BUFF, RIGHT) - aL, equals, sigma, lp, wL, aLm1, plus, bL, rp = formula - wL.set_color(edge.get_color()) - weight_label = wL.copy() - bL.set_color(MAROON_B) - bias_label = bL.copy() - sigma_group = VGroup(sigma, lp, rp) - sigma_group.save_state() - sigma_group.set_fill(opacity = 0) - sigma_group.shift(DOWN) - - self.play( - ReplacementTransform(pre_aL, aL), - Write(equals) - ) - self.play(ReplacementTransform( - edge.copy(), wL - )) - self.wait() - self.play(ReplacementTransform(pre_aLm1, aLm1)) - self.wait() - self.play(Write(VGroup(plus, bL), run_time = 1)) - self.wait() - self.play(sigma_group.restore) - self.wait() - - weighted_sum_terms = VGroup(wL, aLm1, plus, bL) - self.set_variables_as_attrs( - formula, weighted_sum_terms - ) - - def introduce_z(self): - terms = self.weighted_sum_terms - terms.generate_target() - terms.target.next_to( - self.formula, UP, - buff = MED_LARGE_BUFF, - aligned_edge = RIGHT - ) - terms.target.shift(MED_LARGE_BUFF*RIGHT) - equals = TexMobject("=") - equals.next_to(terms.target[0][0], LEFT) - - z_label = TexMobject("z^{(L)}") - z_label.next_to(equals, LEFT) - z_label.align_to(terms.target, DOWN) - z_label.set_color(self.z_color) - z_label2 = z_label.copy() - - aL_start = VGroup(*self.formula[:4]) - aL_start.generate_target() - aL_start.target.align_to(z_label, LEFT) - z_label2.next_to(aL_start.target, RIGHT, SMALL_BUFF) - z_label2.align_to(aL_start.target[0], DOWN) - rp = self.formula[-1] - rp.generate_target() - rp.target.next_to(z_label2, RIGHT, SMALL_BUFF) - rp.target.align_to(aL_start.target, DOWN) - - self.play(MoveToTarget(terms)) - self.play(Write(z_label), Write(equals)) - self.play( - ReplacementTransform(z_label.copy(), z_label2), - MoveToTarget(aL_start), - MoveToTarget(rp), - ) - self.wait() - - zL_formula = VGroup(z_label, equals, *terms) - aL_formula = VGroup(*list(aL_start) + [z_label2, rp]) - self.set_variables_as_attrs(z_label, zL_formula, aL_formula) - - def break_into_computational_graph(self): - network_early_layers = VGroup(*it.chain( - self.network_mob.layers[:2], - self.network_mob.edge_groups[:2] - )) - - wL, aL, plus, bL = self.weighted_sum_terms - top_terms = VGroup(wL, aL, bL).copy() - zL = self.z_label.copy() - aL = self.formula[0].copy() - y = self.y_label.copy() - C0 = self.cost_equation[0].copy() - targets = VGroup() - for mob in top_terms, zL, aL, C0: - mob.generate_target() - targets.add(mob.target) - y.generate_target() - top_terms.target.arrange(RIGHT, buff = MED_LARGE_BUFF) - targets.arrange(DOWN, buff = LARGE_BUFF) - targets.center().to_corner(DOWN+LEFT) - y.target.next_to(aL.target, LEFT, LARGE_BUFF, DOWN) - - top_lines = VGroup(*[ - Line( - term.get_bottom(), - zL.target.get_top(), - buff = SMALL_BUFF - ) - for term in top_terms.target - ]) - z_to_a_line, a_to_c_line, y_to_c_line = all_lines = [ - Line( - m1.target.get_bottom(), - m2.target.get_top(), - buff = SMALL_BUFF - ) - for m1, m2 in [ - (zL, aL), - (aL, C0), - (y, C0) - ] - ] - for mob in [top_lines] + all_lines: - yellow_copy = mob.copy().set_color(YELLOW) - mob.flash = ShowCreationThenDestruction(yellow_copy) - - self.play(MoveToTarget(top_terms)) - self.wait() - self.play(MoveToTarget(zL)) - self.play( - ShowCreation(top_lines, lag_ratio = 0), - top_lines.flash - ) - self.wait() - self.play(MoveToTarget(aL)) - self.play( - network_early_layers.fade, 1, - ShowCreation(z_to_a_line), - z_to_a_line.flash - ) - self.wait() - self.play(MoveToTarget(y)) - self.play(MoveToTarget(C0)) - self.play(*it.chain(*[ - [ShowCreation(line), line.flash] - for line in (a_to_c_line, y_to_c_line) - ])) - self.wait(2) - - comp_graph = VGroup() - comp_graph.wL, comp_graph.aLm1, comp_graph.bL = top_terms - comp_graph.top_lines = top_lines - comp_graph.zL = zL - comp_graph.z_to_a_line = z_to_a_line - comp_graph.aL = aL - comp_graph.y = y - comp_graph.a_to_c_line = a_to_c_line - comp_graph.y_to_c_line = y_to_c_line - comp_graph.C0 = C0 - comp_graph.digest_mobject_attrs() - self.comp_graph = comp_graph - - def show_preceding_layer_in_computational_graph(self): - shift_vect = DOWN - comp_graph = self.comp_graph - comp_graph.save_state() - comp_graph.generate_target() - comp_graph.target.shift(shift_vect) - rect = SurroundingRectangle(comp_graph.aLm1) - - attrs = ["wL", "aLm1", "bL", "zL"] - new_terms = VGroup() - for attr in attrs: - term = getattr(comp_graph, attr) - tex = term.get_tex_string() - if "L-1" in tex: - tex = tex.replace("L-1", "L-2") - else: - tex = tex.replace("L", "L-1") - new_term = TexMobject(tex) - new_term.set_color(term.get_color()) - new_term.move_to(term) - new_terms.add(new_term) - new_edges = VGroup( - comp_graph.top_lines.copy(), - comp_graph.z_to_a_line.copy(), - ) - new_subgraph = VGroup(new_terms, new_edges) - new_subgraph.next_to(comp_graph.target, UP, SMALL_BUFF) - self.wLm1 = new_terms[0] - self.zLm1 = new_terms[-1] - - prev_neuron = self.network_mob.layers[1] - prev_neuron.restore() - prev_edge = self.network_mob.edge_groups[1] - prev_edge.restore() - - self.play( - ShowCreation(rect), - FadeIn(prev_neuron), - ShowCreation(prev_edge) - ) - self.play( - ReplacementTransform( - VGroup(prev_neuron, prev_edge).copy(), - new_subgraph - ), - UpdateFromAlphaFunc( - new_terms, - lambda m, a : m.set_fill(opacity = a) - ), - MoveToTarget(comp_graph), - rect.shift, shift_vect - ) - self.wait(2) - self.play( - FadeOut(new_subgraph), - FadeOut(prev_neuron), - FadeOut(prev_edge), - comp_graph.restore, - rect.shift, -shift_vect, - rect.set_stroke, BLACK, 0 - ) - VGroup(prev_neuron, prev_edge).fade(1) - self.remove(rect) - self.wait() - - self.prev_comp_subgraph = new_subgraph - - def show_number_lines(self): - comp_graph = self.comp_graph - wL, aLm1, bL, zL, aL, C0 = [ - getattr(comp_graph, attr) - for attr in ["wL", "aLm1", "bL", "zL", "aL", "C0"] - ] - wL.val = self.network.weights[-1][0][0] - aL.val = self.decimals[0].number - zL.val = sigmoid_inverse(aL.val) - C0.val = (aL.val - 1)**2 - - number_line = UnitInterval( - unit_size = 2, - stroke_width = 2, - tick_size = 0.075, - color = LIGHT_GREY, - ) - - for mob in wL, zL, aL, C0: - mob.number_line = number_line.deepcopy() - if mob is wL: - mob.number_line.next_to(mob, UP, MED_LARGE_BUFF, LEFT) - else: - mob.number_line.next_to(mob, RIGHT) - if mob is C0: - mob.number_line.x_max = 0.5 - for tick_mark in mob.number_line.tick_marks[1::2]: - mob.number_line.tick_marks.remove(tick_mark) - mob.dot = Dot(color = mob.get_color()) - mob.dot.move_to( - mob.number_line.number_to_point(mob.val) - ) - if mob is wL: - path_arc = 0 - dot_spot = mob.dot.get_bottom() - else: - path_arc = -0.7*np.pi - dot_spot = mob.dot.get_top() - if mob is C0: - mob_spot = mob[0].get_corner(UP+RIGHT) - tip_length = 0.15 - else: - mob_spot = mob.get_corner(UP+RIGHT) - tip_length = 0.2 - mob.arrow = Arrow( - mob_spot, dot_spot, - path_arc = path_arc, - tip_length = tip_length, - buff = SMALL_BUFF, - ) - mob.arrow.set_color(mob.get_color()) - mob.arrow.set_stroke(width = 5) - - self.play(ShowCreation( - mob.number_line, - lag_ratio = 0.5 - )) - self.play( - ShowCreation(mob.arrow), - ReplacementTransform( - mob.copy(), mob.dot, - path_arc = path_arc - ) - ) - self.wait() - - def ask_about_w_sensitivity(self): - wL, aLm1, bL, zL, aL, C0 = [ - getattr(self.comp_graph, attr) - for attr in ["wL", "aLm1", "bL", "zL", "aL", "C0"] - ] - aLm1_val = self.last_neurons[1].get_fill_opacity() - bL_val = self.network.biases[-1][0] - - get_wL_val = lambda : wL.number_line.point_to_number( - wL.dot.get_center() - ) - get_zL_val = lambda : get_wL_val()*aLm1_val+bL_val - get_aL_val = lambda : sigmoid(get_zL_val()) - get_C0_val = lambda : (get_aL_val() - 1)**2 - - def generate_dot_update(term, val_func): - def update_dot(dot): - dot.move_to(term.number_line.number_to_point(val_func())) - return dot - return update_dot - - dot_update_anims = [ - UpdateFromFunc(term.dot, generate_dot_update(term, val_func)) - for term, val_func in [ - (zL, get_zL_val), - (aL, get_aL_val), - (C0, get_C0_val), - ] - ] - - def shake_dot(run_time = 2, rate_func = there_and_back): - self.play( - ApplyMethod( - wL.dot.shift, LEFT, - rate_func = rate_func, - run_time = run_time - ), - *dot_update_anims - ) - - wL_line = Line(wL.dot.get_center(), wL.dot.get_center()+LEFT) - del_wL = TexMobject("\\partial w^{(L)}") - del_wL.scale(self.derivative_scale_val) - del_wL.brace = Brace(wL_line, UP, buff = SMALL_BUFF) - del_wL.set_color(wL.get_color()) - del_wL.next_to(del_wL.brace, UP, SMALL_BUFF) - - C0_line = Line(C0.dot.get_center(), C0.dot.get_center()+MED_SMALL_BUFF*RIGHT) - del_C0 = TexMobject("\\partial C_0") - del_C0.scale(self.derivative_scale_val) - del_C0.brace = Brace(C0_line, UP, buff = SMALL_BUFF) - del_C0.set_color(C0.get_color()) - del_C0.next_to(del_C0.brace, UP, SMALL_BUFF) - - for sym in del_wL, del_C0: - self.play( - GrowFromCenter(sym.brace), - Write(sym, run_time = 1) - ) - shake_dot() - self.wait() - - self.set_variables_as_attrs( - shake_dot, del_wL, del_C0, - ) - - def show_derivative_wrt_w(self): - del_wL = self.del_wL - del_C0 = self.del_C0 - cost_word = self.cost_word - cost_arrow = self.cost_arrow - shake_dot = self.shake_dot - wL = self.comp_graph.wL - - dC_dw = TexMobject( - "{\\partial C_0", "\\over", "\\partial w^{(L)} }" - ) - dC_dw[0].set_color(del_C0.get_color()) - dC_dw[2].set_color(del_wL.get_color()) - dC_dw.scale(self.derivative_scale_val) - dC_dw.to_edge(UP, buff = MED_SMALL_BUFF) - dC_dw.shift(3.5*LEFT) - - full_rect = SurroundingRectangle(dC_dw) - full_rect_copy = full_rect.copy() - words = TextMobject("What we want") - words.next_to(full_rect, RIGHT) - words.set_color(YELLOW) - - denom_rect = SurroundingRectangle(dC_dw[2]) - numer_rect = SurroundingRectangle(dC_dw[0]) - - self.play( - ReplacementTransform(del_C0.copy(), dC_dw[0]), - ReplacementTransform(del_wL.copy(), dC_dw[2]), - Write(dC_dw[1], run_time = 1) - ) - self.play( - FadeOut(cost_word), - FadeOut(cost_arrow), - ShowCreation(full_rect), - Write(words, run_time = 1), - ) - self.wait(2) - self.play( - FadeOut(words), - ReplacementTransform(full_rect, denom_rect) - ) - self.play(Transform(dC_dw[2].copy(), del_wL, remover = True)) - shake_dot() - self.play(ReplacementTransform(denom_rect, numer_rect)) - self.play(Transform(dC_dw[0].copy(), del_C0, remover = True)) - shake_dot() - self.wait() - self.play(ReplacementTransform(numer_rect, full_rect_copy)) - self.play(FadeOut(full_rect_copy)) - self.wait() - - self.dC_dw = dC_dw - - def show_chain_of_events(self): - comp_graph = self.comp_graph - wL, zL, aL, C0 = [ - getattr(comp_graph, attr) - for attr in ["wL", "zL", "aL", "C0"] - ] - del_wL = self.del_wL - del_C0 = self.del_C0 - - zL_line = Line(ORIGIN, MED_LARGE_BUFF*LEFT) - zL_line.shift(zL.dot.get_center()) - del_zL = TexMobject("\\partial z^{(L)}") - del_zL.set_color(zL.get_color()) - del_zL.brace = Brace(zL_line, DOWN, buff = SMALL_BUFF) - - aL_line = Line(ORIGIN, MED_SMALL_BUFF*LEFT) - aL_line.shift(aL.dot.get_center()) - del_aL = TexMobject("\\partial a^{(L)}") - del_aL.set_color(aL.get_color()) - del_aL.brace = Brace(aL_line, DOWN, buff = SMALL_BUFF) - - for sym in del_zL, del_aL: - sym.scale(self.derivative_scale_val) - sym.brace.stretch_about_point( - 0.5, 1, sym.brace.get_top(), - ) - sym.shift( - sym.brace.get_bottom()+SMALL_BUFF*DOWN \ - -sym[0].get_corner(UP+RIGHT) - ) - - syms = [del_wL, del_zL, del_aL, del_C0] - for s1, s2 in zip(syms, syms[1:]): - self.play( - ReplacementTransform(s1.copy(), s2), - ReplacementTransform(s1.brace.copy(), s2.brace), - ) - self.shake_dot(run_time = 1.5) - self.wait(0.5) - self.wait() - - self.set_variables_as_attrs(del_zL, del_aL) - - def show_chain_rule(self): - dC_dw = self.dC_dw - del_syms = [ - getattr(self, attr) - for attr in ("del_wL", "del_zL", "del_aL", "del_C0") - ] - - dz_dw = TexMobject( - "{\\partial z^{(L)}", "\\over", "\\partial w^{(L)}}" - ) - da_dz = TexMobject( - "{\\partial a^{(L)}", "\\over", "\\partial z^{(L)}}" - ) - dC_da = TexMobject( - "{\\partial C0}", "\\over", "\\partial a^{(L)}}" - ) - dz_dw[2].set_color(self.del_wL.get_color()) - VGroup(dz_dw[0], da_dz[2]).set_color(self.z_color) - dC_da[0].set_color(self.cost_color) - equals = TexMobject("=") - group = VGroup(equals, dz_dw, da_dz, dC_da) - group.arrange(RIGHT, SMALL_BUFF) - group.scale(self.derivative_scale_val) - group.next_to(dC_dw, RIGHT) - for mob in group[1:]: - target_y = equals.get_center()[1] - y = mob[1].get_center()[1] - mob.shift((target_y - y)*UP) - - self.play(Write(equals, run_time = 1)) - for frac, top_sym, bot_sym in zip(group[1:], del_syms[1:], del_syms): - self.play(Indicate(top_sym, rate_func = wiggle)) - self.play( - ReplacementTransform(top_sym.copy(), frac[0]), - FadeIn(frac[1]), - ) - self.play(Indicate(bot_sym, rate_func = wiggle)) - self.play(ReplacementTransform( - bot_sym.copy(), frac[2] - )) - self.wait() - self.shake_dot() - self.wait() - - self.chain_rule_equation = VGroup(dC_dw, *group) - - def name_chain_rule(self): - graph_parts = self.get_all_comp_graph_parts() - equation = self.chain_rule_equation - rect = SurroundingRectangle(equation) - group = VGroup(equation, rect) - group.generate_target() - group.target.to_corner(UP+LEFT) - words = TextMobject("Chain rule") - words.set_color(YELLOW) - words.next_to(group.target, DOWN) - - self.play(ShowCreation(rect)) - self.play( - MoveToTarget(group), - Write(words, run_time = 1), - graph_parts.scale, 0.7, graph_parts.get_bottom() - ) - self.wait(2) - self.play(*list(map(FadeOut, [rect, words]))) - - def indicate_everything_on_screen(self): - everything = VGroup(*self.get_top_level_mobjects()) - everything = VGroup(*[m for m in everything.family_members_with_points() if not m.is_subpath]) - self.play(LaggedStartMap( - Indicate, everything, - rate_func = wiggle, - lag_ratio = 0.2, - run_time = 5 - )) - self.wait() - - def prepare_for_derivatives(self): - zL_formula = self.zL_formula - aL_formula = self.aL_formula - az_formulas = VGroup(zL_formula, aL_formula) - cost_equation = self.cost_equation - desired_output_words = self.desired_output_words - - az_formulas.generate_target() - az_formulas.target.to_edge(RIGHT) - - index = 4 - cost_eq = cost_equation[index] - z_eq = az_formulas.target[0][1] - x_shift = (z_eq.get_center() - cost_eq.get_center())[0]*RIGHT - cost_equation.generate_target() - Transform( - VGroup(*cost_equation.target[1:index]), - VectorizedPoint(cost_eq.get_left()) - ).update(1) - cost_equation.target[0].next_to(cost_eq, LEFT, SMALL_BUFF) - cost_equation.target.shift(x_shift) - - self.play( - FadeOut(self.all_comp_graph_parts), - FadeOut(self.desired_output_words), - MoveToTarget(az_formulas), - MoveToTarget(cost_equation) - ) - - def compute_derivatives(self): - cost_equation = self.cost_equation - zL_formula = self.zL_formula - aL_formula = self.aL_formula - chain_rule_equation = self.chain_rule_equation.copy() - dC_dw, equals, dz_dw, da_dz, dC_da = chain_rule_equation - - derivs = VGroup(dC_da, da_dz, dz_dw) - deriv_targets = VGroup() - for deriv in derivs: - deriv.generate_target() - deriv_targets.add(deriv.target) - deriv_targets.arrange(DOWN, buff = MED_LARGE_BUFF) - deriv_targets.next_to(dC_dw, DOWN, LARGE_BUFF) - for deriv in derivs: - deriv.equals = TexMobject("=") - deriv.equals.next_to(deriv.target, RIGHT) - - - #dC_da - self.play( - MoveToTarget(dC_da), - Write(dC_da.equals) - ) - index = 4 - cost_rhs = VGroup(*cost_equation[index+1:]) - dC_da.rhs = cost_rhs.copy() - two = dC_da.rhs[-1] - two.scale(1.5) - two.next_to(dC_da.rhs[0], LEFT, SMALL_BUFF) - dC_da.rhs.next_to(dC_da.equals, RIGHT) - dC_da.rhs.shift(0.7*SMALL_BUFF*UP) - cost_equation.save_state() - - self.play( - cost_equation.next_to, dC_da.rhs, - DOWN, MED_LARGE_BUFF, LEFT - ) - self.wait() - self.play(ReplacementTransform( - cost_rhs.copy(), dC_da.rhs, - path_arc = np.pi/2, - )) - self.wait() - self.play(cost_equation.restore) - self.wait() - - #show_difference - neuron = self.last_neurons[0] - decimal = self.decimals[0] - double_arrow = DoubleArrow( - neuron.get_right(), - self.desired_output_neuron.get_left(), - buff = SMALL_BUFF, - color = RED - ) - - moving_decimals = VGroup( - self.decimals[0].copy(), - self.desired_output_decimal.copy() - ) - minus = TexMobject("-") - minus.move_to(moving_decimals) - minus.scale(0.7) - minus.set_fill(opacity = 0) - moving_decimals.submobjects.insert(1, minus) - moving_decimals.generate_target(use_deepcopy = True) - moving_decimals.target.arrange(RIGHT, buff = SMALL_BUFF) - moving_decimals.target.scale(1.5) - moving_decimals.target.next_to( - dC_da.rhs, DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = RIGHT, - ) - moving_decimals.target.set_fill(WHITE, 1) - - self.play(ReplacementTransform( - dC_da.rhs.copy(), double_arrow - )) - self.wait() - self.play(MoveToTarget(moving_decimals)) - opacity = neuron.get_fill_opacity() - for target_o in 0, opacity: - self.wait(2) - self.play( - neuron.set_fill, None, target_o, - *[ - ChangingDecimal(d, lambda a : neuron.get_fill_opacity()) - for d in (decimal, moving_decimals[0]) - ] - ) - self.play(*list(map(FadeOut, [double_arrow, moving_decimals]))) - - #da_dz - self.play( - MoveToTarget(da_dz), - Write(da_dz.equals) - ) - a_rhs = VGroup(*aL_formula[2:]) - da_dz.rhs = a_rhs.copy() - prime = TexMobject("'") - prime.move_to(da_dz.rhs[0].get_corner(UP+RIGHT)) - da_dz.rhs[0].shift(0.5*SMALL_BUFF*LEFT) - da_dz.rhs.add_to_back(prime) - da_dz.rhs.next_to(da_dz.equals, RIGHT) - da_dz.rhs.shift(0.5*SMALL_BUFF*UP) - aL_formula.save_state() - self.play( - aL_formula.next_to, da_dz.rhs, - DOWN, MED_LARGE_BUFF, LEFT - ) - self.wait() - self.play(ReplacementTransform( - a_rhs.copy(), da_dz.rhs, - )) - self.wait() - self.play(aL_formula.restore) - self.wait() - - #dz_dw - self.play( - MoveToTarget(dz_dw), - Write(dz_dw.equals) - ) - z_rhs = VGroup(*zL_formula[2:]) - dz_dw.rhs = z_rhs[1].copy() - dz_dw.rhs.next_to(dz_dw.equals, RIGHT) - dz_dw.rhs.shift(SMALL_BUFF*UP) - zL_formula.save_state() - self.play( - zL_formula.next_to, dz_dw.rhs, - DOWN, MED_LARGE_BUFF, LEFT, - ) - self.wait() - rect = SurroundingRectangle(VGroup(*zL_formula[2:4])) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.play(ReplacementTransform( - z_rhs[1].copy(), dz_dw.rhs, - )) - self.wait() - self.play(zL_formula.restore) - self.wait() - - self.derivative_equations = VGroup(dC_da, da_dz, dz_dw) - - def get_lost_in_formulas(self): - randy = Randolph() - randy.flip() - randy.scale(0.7) - randy.to_edge(DOWN) - randy.shift(LEFT) - - self.play(FadeIn(randy)) - self.play(randy.change, "pleading", self.chain_rule_equation) - self.play(Blink(randy)) - self.play(randy.change, "maybe") - self.play(Blink(randy)) - self.play(FadeOut(randy)) - - def fire_together_wire_together(self): - dz_dw = self.derivative_equations[2] - rhs = dz_dw.rhs - rhs_copy = rhs.copy() - del_wL = dz_dw[2].copy() - rect = SurroundingRectangle(VGroup(dz_dw, dz_dw.rhs)) - edge = self.network_mob.edge_groups[-1][0] - edge.save_state() - neuron = self.last_neurons[1] - decimal = self.decimals[1] - - def get_decimal_anims(): - return [ - ChangingDecimal(decimal, lambda a : neuron.get_fill_opacity()), - UpdateFromFunc( - decimal, lambda m : m.set_color( - WHITE if neuron.get_fill_opacity() < 0.8 \ - else BLACK - ) - ) - ] - - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.play( - del_wL.next_to, edge, UP, SMALL_BUFF - ) - self.play( - edge.set_stroke, None, 10, - rate_func = wiggle, - run_time = 3, - ) - self.wait() - self.play(rhs.shift, MED_LARGE_BUFF*UP, rate_func = wiggle) - self.play( - rhs_copy.move_to, neuron, - rhs_copy.set_fill, None, 0 - ) - self.remove(rhs_copy) - self.play( - neuron.set_fill, None, 0, - *get_decimal_anims(), - run_time = 3, - rate_func = there_and_back - ) - self.wait() - - #Fire together wire together - opacity = neuron.get_fill_opacity() - self.play( - neuron.set_fill, None, 0.99, - *get_decimal_anims() - ) - self.play(edge.set_stroke, None, 8) - self.play( - neuron.set_fill, None, opacity, - *get_decimal_anims() - ) - self.play(edge.restore, FadeOut(del_wL)) - self.wait(3) - - def organize_chain_rule_rhs(self): - fracs = self.derivative_equations - equals_group = VGroup(*[frac.equals for frac in fracs]) - rhs_group = VGroup(*[frac.rhs for frac in reversed(fracs)]) - - chain_rule_equation = self.chain_rule_equation - equals = TexMobject("=") - equals.next_to(chain_rule_equation, RIGHT) - - rhs_group.generate_target() - rhs_group.target.arrange(RIGHT, buff = SMALL_BUFF) - rhs_group.target.next_to(equals, RIGHT) - rhs_group.target.shift(SMALL_BUFF*UP) - - right_group = VGroup( - self.cost_equation, self.zL_formula, self.aL_formula, - self.network_mob, self.decimals, - self.a_labels, self.a_label_arrows, - self.y_label, self.y_label_arrow, - self.desired_output_neuron, - self.desired_output_rect, - self.desired_output_decimal, - ) - - self.play( - MoveToTarget(rhs_group, path_arc = np.pi/2), - Write(equals), - FadeOut(fracs), - FadeOut(equals_group), - right_group.to_corner, DOWN+RIGHT - ) - self.wait() - - rhs_group.add(equals) - self.chain_rule_rhs = rhs_group - - def show_average_derivative(self): - dC0_dw = self.chain_rule_equation[0] - full_derivative = TexMobject( - "{\\partial C", "\\over", "\\partial w^{(L)}}", - "=", "\\frac{1}{n}", "\\sum_{k=0}^{n-1}", - "{\\partial C_k", "\\over", "\\partial w^{(L)}}" - ) - full_derivative.set_color_by_tex_to_color_map({ - "partial C" : self.cost_color, - "partial w" : self.del_wL.get_color() - }) - full_derivative.to_edge(LEFT) - - dCk_dw = VGroup(*full_derivative[-3:]) - lhs = VGroup(*full_derivative[:3]) - rhs = VGroup(*full_derivative[4:]) - lhs_brace = Brace(lhs, DOWN) - lhs_text = lhs_brace.get_text("Derivative of \\\\ full cost function") - rhs_brace = Brace(rhs, UP) - rhs_text = rhs_brace.get_text("Average of all \\\\ training examples") - VGroup( - full_derivative, lhs_brace, lhs_text, rhs_brace, rhs_text - ).to_corner(DOWN+LEFT) - - mover = dC0_dw.copy() - self.play(Transform(mover, dCk_dw)) - self.play(Write(full_derivative, run_time = 2)) - self.remove(mover) - for brace, text in (rhs_brace, rhs_text), (lhs_brace, lhs_text): - self.play( - GrowFromCenter(brace), - Write(text, run_time = 2), - ) - self.wait(2) - self.cycle_through_altnernate_training_examples() - self.play(*list(map(FadeOut, [ - VGroup(*full_derivative[3:]), - lhs_brace, lhs_text, - rhs_brace, rhs_text, - ]))) - - self.dC_dw = lhs - - def cycle_through_altnernate_training_examples(self): - neurons = VGroup( - self.desired_output_neuron, *self.last_neurons - ) - decimals = VGroup( - self.desired_output_decimal, *self.decimals - ) - group = VGroup(neurons, decimals) - group.save_state() - - for x in range(20): - for n, d in zip(neurons, decimals): - o = np.random.random() - if n is self.desired_output_neuron: - o = np.round(o) - n.set_fill(opacity = o) - Transform( - d, self.get_neuron_activation_decimal(n) - ).update(1) - self.wait(0.2) - self.play(group.restore, run_time = 0.2) - - def show_gradient(self): - dC_dw = self.dC_dw - dC_dw.generate_target() - terms = VGroup( - TexMobject("{\\partial C", "\\over", "\\partial w^{(1)}"), - TexMobject("{\\partial C", "\\over", "\\partial b^{(1)}"), - TexMobject("\\vdots"), - dC_dw.target, - TexMobject("{\\partial C", "\\over", "\\partial b^{(L)}"), - ) - for term in terms: - if isinstance(term, TexMobject): - term.set_color_by_tex_to_color_map({ - "partial C" : RED, - "partial w" : BLUE, - "partial b" : MAROON_B, - }) - terms.arrange(DOWN, buff = MED_LARGE_BUFF) - lb, rb = brackets = TexMobject("[]") - brackets.scale(3) - brackets.stretch_to_fit_height(1.1*terms.get_height()) - lb.next_to(terms, LEFT, buff = SMALL_BUFF) - rb.next_to(terms, RIGHT, buff = SMALL_BUFF) - vect = VGroup(lb, terms, rb) - vect.set_height(5) - lhs = TexMobject("\\nabla C", "=") - lhs[0].set_color(RED) - lhs.next_to(vect, LEFT) - VGroup(lhs, vect).to_corner(DOWN+LEFT, buff = LARGE_BUFF) - terms.remove(dC_dw.target) - - self.play( - MoveToTarget(dC_dw), - Write(vect, run_time = 1) - ) - terms.add(dC_dw) - self.play(Write(lhs)) - self.wait(2) - self.play(FadeOut(VGroup(lhs, vect))) - - def transition_to_derivative_wrt_b(self): - all_comp_graph_parts = self.all_comp_graph_parts - all_comp_graph_parts.scale( - 1.3, about_point = all_comp_graph_parts.get_bottom() - ) - comp_graph = self.comp_graph - wL, bL, zL, aL, C0 = [ - getattr(comp_graph, attr) - for attr in ["wL", "bL", "zL", "aL", "C0"] - ] - path_to_C = VGroup(wL, zL, aL, C0) - - top_expression = VGroup( - self.chain_rule_equation, - self.chain_rule_rhs - ) - rect = SurroundingRectangle(top_expression) - - self.play(ShowCreation(rect)) - self.play(FadeIn(comp_graph), FadeOut(rect)) - for x in range(2): - self.play(LaggedStartMap( - Indicate, path_to_C, - rate_func = there_and_back, - run_time = 1.5, - lag_ratio = 0.7, - )) - self.wait() - - def show_derivative_wrt_b(self): - comp_graph = self.comp_graph - dC0_dw = self.chain_rule_equation[0] - dz_dw = self.chain_rule_equation[2] - aLm1 = self.chain_rule_rhs[0] - left_term_group = VGroup(dz_dw, aLm1) - dz_dw_rect = SurroundingRectangle(dz_dw) - - del_w = dC0_dw[2] - del_b = TexMobject("\\partial b^{(L)}") - del_b.set_color(MAROON_B) - del_b.replace(del_w) - - dz_db = TexMobject( - "{\\partial z^{(L)}", "\\over", "\\partial b^{(L)}}" - ) - dz_db.set_color_by_tex_to_color_map({ - "partial z" : self.z_color, - "partial b" : MAROON_B - }) - dz_db.replace(dz_dw) - - one = TexMobject("1") - one.move_to(aLm1, RIGHT) - arrow = Arrow( - dz_db.get_bottom(), - one.get_bottom(), - path_arc = np.pi/2, - color = WHITE, - ) - arrow.set_stroke(width = 2) - - wL, bL, zL, aL, C0 = [ - getattr(comp_graph, attr) - for attr in ["wL", "bL", "zL", "aL", "C0"] - ] - path_to_C = VGroup(bL, zL, aL, C0) - def get_path_animation(): - return LaggedStartMap( - Indicate, path_to_C, - rate_func = there_and_back, - run_time = 1.5, - lag_ratio = 0.7, - ) - - zL_formula = self.zL_formula - b_in_z_formula = zL_formula[-1] - z_formula_rect = SurroundingRectangle(zL_formula) - b_in_z_rect = SurroundingRectangle(b_in_z_formula) - - self.play(get_path_animation()) - self.play(ShowCreation(dz_dw_rect)) - self.play(FadeOut(dz_dw_rect)) - self.play( - left_term_group.shift, DOWN, - left_term_group.fade, 1, - ) - self.remove(left_term_group) - self.chain_rule_equation.remove(dz_dw) - self.chain_rule_rhs.remove(aLm1) - self.play(Transform(del_w, del_b)) - self.play(FadeIn(dz_db)) - self.play(get_path_animation()) - self.wait() - self.play(ShowCreation(z_formula_rect)) - self.wait() - self.play(ReplacementTransform(z_formula_rect, b_in_z_rect)) - self.wait() - self.play( - ReplacementTransform(b_in_z_formula.copy(), one), - FadeOut(b_in_z_rect) - ) - self.play( - ShowCreation(arrow), - ReplacementTransform( - dz_db.copy(), one, - path_arc = arrow.path_arc - ) - ) - self.wait(2) - self.play(*list(map(FadeOut, [dz_db, arrow, one]))) - - self.dz_db = dz_db - - def show_derivative_wrt_a(self): - denom = self.chain_rule_equation[0][2] - numer = VGroup(*self.chain_rule_equation[0][:2]) - del_aLm1 = TexMobject("\\partial a^{(L-1)}") - del_aLm1.scale(0.8) - del_aLm1.move_to(denom) - dz_daLm1 = TexMobject( - "{\\partial z^{(L)}", "\\over", "\\partial a^{(L-1)}}" - ) - dz_daLm1.scale(0.8) - dz_daLm1.next_to(self.chain_rule_equation[1], RIGHT, SMALL_BUFF) - dz_daLm1.shift(0.7*SMALL_BUFF*UP) - dz_daLm1[0].set_color(self.z_color) - dz_daLm1_rect = SurroundingRectangle(dz_daLm1) - wL = self.zL_formula[2].copy() - wL.next_to(self.chain_rule_rhs[0], LEFT, SMALL_BUFF) - - arrow = Arrow( - dz_daLm1.get_bottom(), wL.get_bottom(), - path_arc = np.pi/2, - color = WHITE, - ) - - comp_graph = self.comp_graph - path_to_C = VGroup(*[ - getattr(comp_graph, attr) - for attr in ["aLm1", "zL", "aL", "C0"] - ]) - def get_path_animation(): - return LaggedStartMap( - Indicate, path_to_C, - rate_func = there_and_back, - run_time = 1.5, - lag_ratio = 0.7, - ) - - zL_formula = self.zL_formula - z_formula_rect = SurroundingRectangle(zL_formula) - a_in_z_rect = SurroundingRectangle(VGroup(*zL_formula[2:4])) - wL_in_z = zL_formula[2] - - for x in range(3): - self.play(get_path_animation()) - self.play( - numer.shift, SMALL_BUFF*UP, - Transform(denom, del_aLm1) - ) - self.play( - FadeIn(dz_daLm1), - VGroup(*self.chain_rule_equation[-2:]).shift, SMALL_BUFF*RIGHT, - ) - self.wait() - self.play(ShowCreation(dz_daLm1_rect)) - self.wait() - self.play(ReplacementTransform( - dz_daLm1_rect, z_formula_rect - )) - self.wait() - self.play(ReplacementTransform(z_formula_rect, a_in_z_rect)) - self.play( - ReplacementTransform(wL_in_z.copy(), wL), - FadeOut(a_in_z_rect) - ) - self.play( - ShowCreation(arrow), - ReplacementTransform( - dz_daLm1.copy(), wL, - path_arc = arrow.path_arc - ) - ) - self.wait(2) - - self.chain_rule_rhs.add(wL, arrow) - self.chain_rule_equation.add(dz_daLm1) - - def show_previous_weight_and_bias(self): - to_fade = self.chain_rule_rhs - comp_graph = self.comp_graph - prev_comp_subgraph = self.prev_comp_subgraph - prev_comp_subgraph.scale(0.8) - prev_comp_subgraph.next_to(comp_graph, UP, SMALL_BUFF) - - prev_layer = VGroup( - self.network_mob.layers[1], - self.network_mob.edge_groups[1], - ) - for mob in prev_layer: - mob.restore() - prev_layer.next_to(self.last_neurons, LEFT, buff = 0) - self.remove(prev_layer) - - self.play(LaggedStartMap(FadeOut, to_fade, run_time = 1)) - self.play( - ShowCreation(prev_comp_subgraph, run_time = 1), - self.chain_rule_equation.to_edge, RIGHT - ) - self.play(FadeIn(prev_layer)) - - ### - neuron = self.network_mob.layers[1].neurons[0] - decimal = self.get_neuron_activation_decimal(neuron) - a_label = TexMobject("a^{(L-2)}") - a_label.replace(self.a_labels[1]) - arrow = self.a_label_arrows[1].copy() - VGroup(a_label, arrow).shift( - neuron.get_center() - self.last_neurons[1].get_center() - ) - - self.play( - Write(a_label, run_time = 1), - Write(decimal, run_time = 1), - GrowArrow(arrow), - ) - - def animate_long_path(self): - comp_graph = self.comp_graph - path_to_C = VGroup( - self.wLm1, self.zLm1, - *[ - getattr(comp_graph, attr) - for attr in ["aLm1", "zL", "aL", "C0"] - ] - ) - for x in range(2): - self.play(LaggedStartMap( - Indicate, path_to_C, - rate_func = there_and_back, - run_time = 1.5, - lag_ratio = 0.4, - )) - self.wait(2) - - ### - - def get_neuron_activation_decimal(self, neuron): - opacity = neuron.get_fill_opacity() - decimal = DecimalNumber(opacity, num_decimal_places = 2) - decimal.set_width(0.85*neuron.get_width()) - if decimal.number > 0.8: - decimal.set_fill(BLACK) - decimal.move_to(neuron) - return decimal - - def get_all_comp_graph_parts(self): - comp_graph = self.comp_graph - result = VGroup(comp_graph) - for attr in "wL", "zL", "aL", "C0": - sym = getattr(comp_graph, attr) - result.add( - sym.arrow, sym.number_line, sym.dot - ) - del_sym = getattr(self, "del_" + attr) - result.add(del_sym, del_sym.brace) - - self.all_comp_graph_parts = result - return result - -class IsntThatOverSimplified(TeacherStudentsScene): - def construct(self): - self.student_says( - "Isn't that over-simplified?", - target_mode = "raise_right_hand", - run_time = 1 - ) - self.change_student_modes( - "pondering", "raise_right_hand", "pondering" - ) - self.wait() - self.teacher_says( - "Not that much, actually!", - run_time = 1, - target_mode = "hooray" - ) - self.wait(2) - -class GeneralFormulas(SimplestNetworkExample): - CONFIG = { - "layer_sizes" : [3, 3, 2], - "network_mob_config" : { - "include_output_labels" : False, - "neuron_to_neuron_buff" : LARGE_BUFF, - "neuron_radius" : 0.3, - }, - "edge_stroke_width" : 4, - "stroke_width_exp" : 0.2, - "random_seed" : 9, - } - def setup(self): - self.seed_random_libraries() - self.setup_bases() - - def construct(self): - self.setup_network_mob() - self.show_all_a_labels() - self.only_show_abstract_a_labels() - self.add_desired_output() - self.show_cost() - self.show_example_weight() - self.show_values_between_weight_and_cost() - self.show_weight_chain_rule() - self.show_derivative_wrt_prev_activation() - self.show_multiple_paths_from_prev_layer_neuron() - self.show_previous_layer() - - def setup_network_mob(self): - self.color_network_edges() - self.network_mob.to_edge(LEFT) - self.network_mob.shift(DOWN) - in_vect = np.random.random(self.layer_sizes[0]) - self.network_mob.activate_layers(in_vect) - self.remove(self.network_mob.layers[0]) - self.remove(self.network_mob.edge_groups[0]) - - def show_all_a_labels(self): - Lm1_neurons = self.network_mob.layers[-2].neurons - L_neurons = self.network_mob.layers[-1].neurons - all_arrows = VGroup() - all_labels = VGroup() - all_decimals = VGroup() - all_subscript_rects = VGroup() - for neurons in L_neurons, Lm1_neurons: - is_L = neurons is L_neurons - vect = LEFT if is_L else RIGHT - s = "L" if is_L else "L-1" - arrows = VGroup() - labels = VGroup() - decimals = VGroup() - subscript_rects = VGroup() - for i, neuron in enumerate(neurons): - arrow = Arrow(ORIGIN, vect) - arrow.next_to(neuron, -vect) - arrow.set_fill(WHITE) - label = TexMobject("a^{(%s)}_%d"%(s, i)) - label.next_to(arrow, -vect, SMALL_BUFF) - rect = SurroundingRectangle(label[-1], buff = 0.5*SMALL_BUFF) - decimal = self.get_neuron_activation_decimal(neuron) - neuron.arrow = arrow - neuron.label = label - neuron.decimal = decimal - arrows.add(arrow) - labels.add(label) - decimals.add(decimal) - subscript_rects.add(rect) - all_arrows.add(arrows) - all_labels.add(labels) - all_decimals.add(decimals) - all_subscript_rects.add(subscript_rects) - - start_labels, start_arrows = [ - VGroup(*list(map(VGroup, [group[i][0] for i in (0, 1)]))).copy() - for group in (all_labels, all_arrows) - ] - for label in start_labels: - label[0][-1].set_color(BLACK) - - self.add(all_decimals) - self.play(*it.chain( - list(map(Write, start_labels)), - [GrowArrow(a[0]) for a in start_arrows] - )) - self.wait() - self.play( - ReplacementTransform(start_labels, all_labels), - ReplacementTransform(start_arrows, all_arrows), - ) - self.play(LaggedStartMap( - ShowCreationThenDestruction, - VGroup(*all_subscript_rects.family_members_with_points()), - lag_ratio = 0.7 - )) - self.wait() - - self.set_variables_as_attrs( - L_neurons, Lm1_neurons, - all_arrows, all_labels, - all_decimals, all_subscript_rects, - ) - - def only_show_abstract_a_labels(self): - arrows_to_fade = VGroup() - labels_to_fade = VGroup() - labels_to_change = VGroup() - self.chosen_neurons = VGroup() - rects = VGroup() - for x, layer in enumerate(self.network_mob.layers[-2:]): - for y, neuron in enumerate(layer.neurons): - if (x == 0 and y == 1) or (x == 1 and y == 0): - tex = "k" if x == 0 else "j" - neuron.label.generate_target() - self.replace_subscript(neuron.label.target, tex) - self.chosen_neurons.add(neuron) - labels_to_change.add(neuron.label) - rects.add(SurroundingRectangle( - neuron.label.target[-1], - buff = 0.5*SMALL_BUFF - )) - else: - labels_to_fade.add(neuron.label) - arrows_to_fade.add(neuron.arrow) - - self.play( - LaggedStartMap(FadeOut, labels_to_fade), - LaggedStartMap(FadeOut, arrows_to_fade), - run_time = 1 - ) - for neuron, rect in zip(self.chosen_neurons, rects): - self.play( - MoveToTarget(neuron.label), - ShowCreation(rect) - ) - self.play(FadeOut(rect)) - self.wait() - self.wait() - - def add_desired_output(self): - layer = self.network_mob.layers[-1] - desired_output = layer.deepcopy() - desired_output.shift(3*RIGHT) - desired_output_decimals = VGroup() - arrows = VGroup() - labels = VGroup() - for i, neuron in enumerate(desired_output.neurons): - neuron.set_fill(opacity = i) - decimal = self.get_neuron_activation_decimal(neuron) - neuron.decimal = decimal - neuron.arrow = Arrow(ORIGIN, LEFT, color = WHITE) - neuron.arrow.next_to(neuron, RIGHT) - neuron.label = TexMobject("y_%d"%i) - neuron.label.next_to(neuron.arrow, RIGHT) - neuron.label.set_color(self.desired_output_color) - - desired_output_decimals.add(decimal) - arrows.add(neuron.arrow) - labels.add(neuron.label) - rect = SurroundingRectangle(desired_output, buff = 0.5*SMALL_BUFF) - words = TextMobject("Desired output") - words.next_to(rect, DOWN) - VGroup(words, rect).set_color(self.desired_output_color) - - self.play( - FadeIn(rect), - FadeIn(words), - ReplacementTransform(layer.copy(), desired_output), - FadeIn(labels), - *[ - ReplacementTransform(n1.decimal.copy(), n2.decimal) - for n1, n2 in zip(layer.neurons, desired_output.neurons) - ] + list(map(GrowArrow, arrows)) - ) - self.wait() - - self.set_variables_as_attrs( - desired_output, - desired_output_decimals, - desired_output_rect = rect, - desired_output_words = words, - ) - - def show_cost(self): - aj = self.chosen_neurons[1].label.copy() - yj = self.desired_output.neurons[0].label.copy() - - cost_equation = TexMobject( - "C_0", "=", "\\sum_{j = 0}^{n_L - 1}", - "(", "a^{(L)}_j", "-", "y_j", ")", "^2" - ) - cost_equation.to_corner(UP+RIGHT) - cost_equation[0].set_color(self.cost_color) - aj.target = cost_equation.get_part_by_tex("a^{(L)}_j") - yj.target = cost_equation.get_part_by_tex("y_j") - yj.target.set_color(self.desired_output_color) - to_fade_in = VGroup(*[m for m in cost_equation if m not in [aj.target, yj.target]]) - sum_part = cost_equation.get_part_by_tex("sum") - - self.play(*[ - ReplacementTransform(mob, mob.target) - for mob in (aj, yj) - ]) - self.play(LaggedStartMap(FadeIn, to_fade_in)) - self.wait(2) - self.play(LaggedStartMap( - Indicate, sum_part, - rate_func = wiggle, - )) - self.wait() - for mob in aj.target, yj.target, cost_equation[-1]: - self.play(Indicate(mob)) - self.wait() - - self.set_variables_as_attrs(cost_equation) - - def show_example_weight(self): - edges = self.network_mob.edge_groups[-1] - edge = self.chosen_neurons[1].edges_in[1] - faded_edges = VGroup(*[e for e in edges if e is not edge]) - faded_edges.save_state() - for faded_edge in faded_edges: - faded_edge.save_state() - - w_label = TexMobject("w^{(L)}_{jk}") - subscripts = VGroup(*w_label[-2:]) - w_label.scale(1.2) - w_label.add_background_rectangle() - w_label.next_to(ORIGIN, UP, SMALL_BUFF) - w_label.rotate(edge.get_angle()) - w_label.shift(edge.get_center()) - w_label.set_color(BLUE) - - edges.save_state() - edges.generate_target() - for e in edges.target: - e.rotate(-e.get_angle()) - edges.target.arrange(DOWN) - edges.target.move_to(edges) - edges.target.to_edge(UP) - - self.play(MoveToTarget(edges)) - self.play(LaggedStartMap( - ApplyFunction, edges, - lambda e : ( - lambda m : m.rotate_in_place(np.pi/12).set_color(YELLOW), - e - ), - rate_func = wiggle - )) - self.play(edges.restore) - self.play(faded_edges.fade, 0.9) - for neuron in self.chosen_neurons: - self.play(Indicate(neuron), Animation(neuron.decimal)) - self.play(Write(w_label)) - self.wait() - self.play(Indicate(subscripts)) - for x in range(2): - self.play(Swap(*subscripts)) - self.wait() - - self.set_variables_as_attrs(faded_edges, w_label) - - def show_values_between_weight_and_cost(self): - z_formula = TexMobject( - "z^{(L)}_j", "=", - "w^{(L)}_{j0}", "a^{(L-1)}_0", "+", - "w^{(L)}_{j1}", "a^{(L-1)}_1", "+", - "w^{(L)}_{j2}", "a^{(L-1)}_2", "+", - "b^{(L)}_j" - ) - compact_z_formula = TexMobject( - "z^{(L)}_j", "=", - "\\cdots", "", "+" - "w^{(L)}_{jk}", "a^{(L-1)}_k", "+", - "\\cdots", "", "", "", - ) - for expression in z_formula, compact_z_formula: - expression.to_corner(UP+RIGHT) - expression.set_color_by_tex_to_color_map({ - "z^" : self.z_color, - "w^" : self.w_label.get_color(), - "b^" : MAROON_B, - }) - w_part = z_formula.get_parts_by_tex("w^")[1] - aLm1_part = z_formula.get_parts_by_tex("a^{(L-1)}")[1] - - a_formula = TexMobject( - "a^{(L)}_j", "=", "\\sigma(", "z^{(L)}_j", ")" - ) - a_formula.set_color_by_tex("z^", self.z_color) - a_formula.next_to(z_formula, DOWN, MED_LARGE_BUFF) - a_formula.align_to(self.cost_equation, LEFT) - aL_part = a_formula[0] - - to_fade = VGroup( - self.desired_output, - self.desired_output_decimals, - self.desired_output_rect, - self.desired_output_words, - *[ - VGroup(n.arrow, n.label) - for n in self.desired_output.neurons - ] - ) - - self.play( - FadeOut(to_fade), - self.cost_equation.next_to, a_formula, DOWN, MED_LARGE_BUFF, - self.cost_equation.to_edge, RIGHT, - ReplacementTransform(self.w_label[1].copy(), w_part), - ReplacementTransform( - self.chosen_neurons[0].label.copy(), - aLm1_part - ), - ) - self.play(Write(VGroup(*[m for m in z_formula if m not in [w_part, aLm1_part]]))) - self.wait() - self.play(ReplacementTransform( - self.chosen_neurons[1].label.copy(), - aL_part - )) - self.play( - Write(VGroup(*a_formula[1:3] + [a_formula[-1]])), - ReplacementTransform( - z_formula[0].copy(), - a_formula.get_part_by_tex("z^") - ) - ) - self.wait() - - self.set_variables_as_attrs(z_formula, compact_z_formula, a_formula) - - def show_weight_chain_rule(self): - chain_rule = self.get_chain_rule( - "{\\partial C_0", "\\over", "\\partial w^{(L)}_{jk}}", - "=", - "{\\partial z^{(L)}_j", "\\over", "\\partial w^{(L)}_{jk}}", - "{\\partial a^{(L)}_j", "\\over", "\\partial z^{(L)}_j}", - "{\\partial C_0", "\\over", "\\partial a^{(L)}_j}", - ) - terms = VGroup(*[ - VGroup(*chain_rule[i:i+3]) - for i in range(4,len(chain_rule), 3) - ]) - rects = VGroup(*[ - SurroundingRectangle(term, buff = 0.5*SMALL_BUFF) - for term in terms - ]) - rects.set_color_by_gradient(GREEN, WHITE, RED) - - self.play(Transform( - self.z_formula, self.compact_z_formula - )) - self.play(Write(chain_rule)) - self.wait() - self.play(LaggedStartMap( - ShowCreationThenDestruction, rects, - lag_ratio = 0.7, - run_time = 3 - )) - self.wait() - - self.set_variables_as_attrs(chain_rule) - - def show_derivative_wrt_prev_activation(self): - chain_rule = self.get_chain_rule( - "{\\partial C_0", "\\over", "\\partial a^{(L-1)}_k}", - "=", - "\\sum_{j=0}^{n_L - 1}", - "{\\partial z^{(L)}_j", "\\over", "\\partial a^{(L-1)}_k}", - "{\\partial a^{(L)}_j", "\\over", "\\partial z^{(L)}_j}", - "{\\partial C_0", "\\over", "\\partial a^{(L)}_j}", - ) - formulas = VGroup(self.z_formula, self.a_formula, self.cost_equation) - - n = chain_rule.index_of_part_by_tex("sum") - self.play(ReplacementTransform( - self.chain_rule, VGroup(*chain_rule[:n] + chain_rule[n+1:]) - )) - self.play(Write(chain_rule[n], run_time = 1)) - self.wait() - - self.set_variables_as_attrs(chain_rule) - - def show_multiple_paths_from_prev_layer_neuron(self): - neurons = self.network_mob.layers[-1].neurons - labels, arrows, decimals = [ - VGroup(*[getattr(n, attr) for n in neurons]) - for attr in ("label", "arrow", "decimal") - ] - edges = VGroup(*[n.edges_in[1] for n in neurons]) - labels[0].generate_target() - self.replace_subscript(labels[0].target, "0") - - paths = [ - VGroup( - self.chosen_neurons[0].label, - self.chosen_neurons[0].arrow, - self.chosen_neurons[0], - self.chosen_neurons[0].decimal, - edges[i], - neurons[i], - decimals[i], - arrows[i], - labels[i], - ) - for i in range(2) - ] - path_lines = VGroup() - for path in paths: - points = [path[0].get_center()] - for mob in path[1:]: - if isinstance(mob, DecimalNumber): - continue - points.append(mob.get_center()) - path_line = VMobject() - path_line.set_points_as_corners(points) - path_lines.add(path_line) - path_lines.set_color(YELLOW) - - chain_rule = self.chain_rule - n = chain_rule.index_of_part_by_tex("sum") - brace = Brace(VGroup(*chain_rule[n:]), DOWN, buff = SMALL_BUFF) - words = brace.get_text("Sum over layer L", buff = SMALL_BUFF) - - cost_aL = self.cost_equation.get_part_by_tex("a^{(L)}") - - self.play( - MoveToTarget(labels[0]), - FadeIn(labels[1]), - GrowArrow(arrows[1]), - edges[1].restore, - FadeOut(self.w_label), - ) - for x in range(5): - anims = [ - ShowCreationThenDestruction( - path_line, - run_time = 1.5, - time_width = 0.5, - ) - for path_line in path_lines - ] - if x == 2: - anims += [ - FadeIn(words), - GrowFromCenter(brace) - ] - self.play(*anims) - self.wait() - for path, path_line in zip(paths, path_lines): - label = path[-1] - self.play( - LaggedStartMap( - Indicate, path, - rate_func = wiggle, - run_time = 1, - ), - ShowCreation(path_line), - Animation(label) - ) - self.wait() - group = VGroup(label, cost_aL) - self.play( - group.shift, MED_SMALL_BUFF*UP, - rate_func = wiggle - ) - self.play(FadeOut(path_line)) - self.wait() - - def show_previous_layer(self): - mid_neurons = self.network_mob.layers[1].neurons - layer = self.network_mob.layers[0] - edges = self.network_mob.edge_groups[0] - faded_edges = self.faded_edges - to_fade = VGroup( - self.chosen_neurons[0].label, - self.chosen_neurons[0].arrow, - ) - for neuron in layer.neurons: - neuron.add(self.get_neuron_activation_decimal(neuron)) - - all_edges_out = VGroup(*[ - VGroup(*[n.edges_in[i] for n in mid_neurons]).copy() - for i in range(len(layer.neurons)) - ]) - all_edges_out.set_stroke(YELLOW, 3) - - deriv = VGroup(*self.chain_rule[:3]) - deriv_rect = SurroundingRectangle(deriv) - mid_neuron_outlines = mid_neurons.copy() - mid_neuron_outlines.set_fill(opacity = 0) - mid_neuron_outlines.set_stroke(YELLOW, 5) - - def get_neurons_decimal_anims(neuron): - return [ - ChangingDecimal( - neuron.decimal, - lambda a : neuron.get_fill_opacity(), - ), - UpdateFromFunc( - neuron.decimal, - lambda m : m.set_fill( - WHITE if neuron.get_fill_opacity() < 0.8 else BLACK - ) - ) - ] - - self.play(ShowCreation(deriv_rect)) - self.play(LaggedStartMap( - ShowCreationThenDestruction, - mid_neuron_outlines - )) - self.play(*it.chain(*[ - [ - ApplyMethod(n.set_fill, None, random.random()), - ] + get_neurons_decimal_anims(n) - for n in mid_neurons - ]), run_time = 4, rate_func = there_and_back) - self.play(faded_edges.restore) - self.play( - LaggedStartMap( - GrowFromCenter, layer.neurons, - run_time = 1 - ), - LaggedStartMap(ShowCreation, edges), - FadeOut(to_fade) - ) - for x in range(3): - for edges_out in all_edges_out: - self.play(ShowCreationThenDestruction(edges_out)) - self.wait() - - #### - - def replace_subscript(self, label, tex): - subscript = label[-1] - new_subscript = TexMobject(tex)[0] - new_subscript.replace(subscript, dim_to_match = 1) - label.remove(subscript) - label.add(new_subscript) - return label - - def get_chain_rule(self, *tex): - chain_rule = TexMobject(*tex) - chain_rule.scale(0.8) - chain_rule.to_corner(UP+LEFT) - chain_rule.set_color_by_tex_to_color_map({ - "C_0" : self.cost_color, - "z^" : self.z_color, - "w^" : self.w_label.get_color() - }) - return chain_rule - -class ThatsPrettyMuchIt(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "That's pretty \\\\ much it!", - target_mode = "hooray", - run_time = 1, - ) - self.wait(2) - -class PatYourselfOnTheBack(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Pat yourself on \\\\ the back!", - target_mode = "hooray" - ) - self.change_student_modes(*["hooray"]*3) - self.wait(3) - -class ThatsALotToThinkAbout(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "That's a lot to \\\\ think about!", - target_mode = "surprised" - ) - self.change_student_modes(*["thinking"]*3) - self.wait(4) - -class LayersOfComplexity(Scene): - def construct(self): - chain_rule_equations = self.get_chain_rule_equations() - chain_rule_equations.to_corner(UP+RIGHT) - - brace = Brace(chain_rule_equations, LEFT) - arrow = Vector(LEFT, color = RED) - arrow.next_to(brace, LEFT) - gradient = TexMobject("\\nabla C") - gradient.scale(2) - gradient.set_color(RED) - gradient.next_to(arrow, LEFT) - - self.play(LaggedStartMap(FadeIn, chain_rule_equations)) - self.play(GrowFromCenter(brace)) - self.play(GrowArrow(arrow)) - self.play(Write(gradient)) - self.wait() - - - def get_chain_rule_equations(self): - w_deriv = TexMobject( - "{\\partial C", "\\over", "\\partial w^{(l)}_{jk}}", - "=", - "a^{(l-1)}_k", - "\\sigma'(z^{(l)}_j)", - "{\\partial C", "\\over", "\\partial a^{(l)}_j}", - ) - lil_rect = SurroundingRectangle( - VGroup(*w_deriv[-3:]), - buff = 0.5*SMALL_BUFF - ) - a_deriv = TexMobject( - "\\sum_{j = 0}^{n_{l+1} - 1}", - "w^{(l+1)}_{jk}", - "\\sigma'(z^{(l+1)}_j)", - "{\\partial C", "\\over", "\\partial a^{(l+1)}_j}", - ) - or_word = TextMobject("or") - last_a_deriv = TexMobject("2(a^{(L)}_j - y_j)") - - a_deriv.next_to(w_deriv, DOWN, LARGE_BUFF) - or_word.next_to(a_deriv, DOWN) - last_a_deriv.next_to(or_word, DOWN, MED_LARGE_BUFF) - - big_rect = SurroundingRectangle(VGroup(a_deriv, last_a_deriv)) - arrow = Arrow( - lil_rect.get_corner(DOWN+LEFT), - big_rect.get_top(), - ) - - result = VGroup( - w_deriv, lil_rect, arrow, - big_rect, a_deriv, or_word, last_a_deriv - ) - for expression in w_deriv, a_deriv, last_a_deriv: - expression.set_color_by_tex_to_color_map({ - "C" : RED, - "z^" : GREEN, - "w^" : BLUE, - "b^" : MAROON_B, - }) - return result - -class SponsorFrame(PiCreatureScene): - def construct(self): - morty = self.pi_creature - screen = ScreenRectangle(height = 5) - screen.to_corner(UP+LEFT) - url = TextMobject("http://3b1b.co/crowdflower") - url.move_to(screen, UP+LEFT) - screen.shift(LARGE_BUFF*DOWN) - arrow = Arrow(LEFT, RIGHT, color = WHITE) - arrow.next_to(url, RIGHT) - - t_shirt_words = TextMobject("Free T-Shirt") - t_shirt_words.scale(1.5) - t_shirt_words.set_color(YELLOW) - t_shirt_words.next_to(morty, UP, aligned_edge = RIGHT) - - human_in_the_loop = TextMobject("Human-in-the-loop approach") - human_in_the_loop.next_to(screen, DOWN) - - self.play( - morty.change, "hooray", t_shirt_words, - Write(t_shirt_words, run_time = 2) - ) - self.wait() - self.play( - morty.change, "raise_right_hand", screen, - ShowCreation(screen) - ) - self.play( - t_shirt_words.scale, 1./1.5, - t_shirt_words.next_to, arrow, RIGHT - ) - self.play(Write(url)) - self.play(GrowArrow(arrow)) - self.wait(2) - self.play(morty.change, "thinking", url) - self.wait(3) - self.play(Write(human_in_the_loop)) - self.play(morty.change, "happy", url) - self.play(morty.look_at, screen) - self.wait(7) - t_shirt_words_outline = t_shirt_words.copy() - t_shirt_words_outline.set_fill(opacity = 0) - t_shirt_words_outline.set_stroke(GREEN, 3) - self.play( - morty.change, "hooray", t_shirt_words, - LaggedStartMap(ShowCreation, t_shirt_words_outline), - ) - self.play(FadeOut(t_shirt_words_outline)) - self.play(LaggedStartMap( - Indicate, url, - rate_func = wiggle, - color = PINK, - run_time = 3 - )) - self.wait(3) - -class NN3PatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Randall Hunt", - "Burt Humburg", - "CrypticSwarm", - "Juan Benet", - "David Kedmey", - "Michael Hardwicke", - "Nathan Weeks", - "Marcus Schiebold", - "Ali Yahya", - "William", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Samantha D. Suplee", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Markus Persson", - "Yoni Nazarathy", - "Ed Kellett", - "Joseph John Cox", - "Luc Ritchie", - "1stViewMaths", - "Jacob Magnuson", - "Mark Govea", - "Dagan Harrington", - "Clark Gaebel", - "Eric Chow", - "Mathias Jansson", - "Robert Teed", - "Pedro Perez Sanchez", - "David Clark", - "Michael Gardner", - "Harsev Singh", - "Mads Elvheim", - "Erik Sundell", - "Xueqi Li", - "Dr. David G. Stork", - "Tianyu Ge", - "Ted Suzman", - "Linh Tran", - "Andrew Busey", - "John Haley", - "Ankalagon", - "Eric Lavault", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Ryan Dahl", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ], - "max_patron_group_size" : 25, - "patron_scale_val" : 0.7, - } - -class Thumbnail(PreviewLearning): - CONFIG = { - "layer_sizes" : [8, 6, 6, 4], - "network_mob_config" : { - "neuron_radius" : 0.3, - "neuron_to_neuron_buff" : MED_SMALL_BUFF, - "include_output_labels" : False, - }, - "stroke_width_exp" : 1, - "max_stroke_width" : 5, - "title" : "Backpropagation", - "network_scale_val" : 0.8, - } - def construct(self): - self.color_network_edges() - network_mob = self.network_mob - network_mob.scale( - self.network_scale_val, - about_point = network_mob.get_bottom() - ) - network_mob.activate_layers(np.random.random(self.layer_sizes[0])) - - for edge in it.chain(*network_mob.edge_groups): - arrow = Arrow( - edge.get_end(), edge.get_start(), - buff = 0, - tip_length = 0.1, - color = edge.get_color() - ) - network_mob.add(arrow.tip) - - arrow = Vector( - 3*LEFT, - tip_length = 0.75, - rectangular_stem_width = 0.2, - color = BLUE, - ) - arrow.next_to(network_mob.edge_groups[1], UP, MED_LARGE_BUFF) - - network_mob.add(arrow) - self.add(network_mob) - - title = TextMobject(self.title) - title.scale(2) - title.to_edge(UP) - self.add(title) - -class SupplementThumbnail(Thumbnail): - CONFIG = { - "title" : "Backpropagation \\\\ calculus", - "network_scale_val" : 0.7, - } - def construct(self): - Thumbnail.construct(self) - self.network_mob.to_edge(DOWN, buff = MED_SMALL_BUFF) - - for layer in self.network_mob.layers: - for neuron in layer.neurons: - partial = TexMobject("\\partial") - partial.move_to(neuron) - self.remove(neuron) - self.add(partial) - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/nn/pretrained_weights_and_biases b/from_3b1b/old/nn/pretrained_weights_and_biases deleted file mode 100644 index 7293b04b..00000000 --- a/from_3b1b/old/nn/pretrained_weights_and_biases +++ /dev/null @@ -1,85 +0,0 @@ -((lp1 -cnumpy.core.multiarray -_reconstruct -p2 -(cnumpy -ndarray -p3 -(I0 -tS'b' -tRp4 -(I1 -(I16 -I784 -tcnumpy -dtype -p5 -(S'f8' -I0 -I1 -tRp6 -(I3 -S'<' -NNNI-1 -I-1 -I0 -tbI00 -S'd\xd4\x17\xc3\xfc\xfe\xe8?\x9f\x17\xe8\xbe\xdeB\xf7?"i\x15`\xfbh\xc7\xbfQ\n\x92\x9a\xc1\xb6\xee\xbf\xd7\xa1\xd8p\xe8\x06\xe4?\x92\x9d\xa70X\xcf\xf1\xbf\x00V\xb7]\\\x1b\xc0?\xd5$n\x16\x0e\x17\xff?\xf9\xa6\xb8\xfcE\xab\xfb?8\xfe\xac\x98\x0b\x02\xd0?\tz\xa8!\xe8e\xdd\xbf\xea\xd1\x95$\xd0J\xa3?b\xe2\xaf\x11\xb7\xa1\xef?\xaa\x0c\x9d\xc0\x8d.\xad?\xc1\xa9\x8e\x07V\xe7\x01@\xde]\x81\xbc^\xec\xfd\xbf<\xdc\x9f\xde\xfc|\xf0\xbf\xe7\xe2\xf4\xb3\x18F\xdd?\xd7\x96\t\xd4\x07\xad\xd0?\xf3\xb9\r\xff\xef\xfb\xc4\xbf\x9c\x11k\xfd\x0cO\xbf?<\x120\xdb;\xcf\xb5\xbf\x1c\x81\x8bgxb\xfb?L\rhr`\x84\xe8?\t\xe27A\x19\x9e\xe3?\x18\x8e=>o"\xd7?\xd2\xc2iB\xdd\x08\xe8?(\x04W\xf8\xaa\x16\xcc\xbf\xf3\xbe\xe9+\xb1\x99\xae?\xe9\xbf\x98L\xc4N\x03\xc0\xb8\x9eQ\x9a[\x8a\xc7\xbf\xa4\xd1\x02\x06\xd3\xad\xe2?i[X\xa0\x8c\r\xe3?V\x84\x08(\xff\x83\xec\xbf^j\xc1\x87``\xd5?\xba\xb1\x02\xbe\x8f\xa9\xe5?\x1eB\xe74=D\xf0\xbfN\x8f{j\xed\xac\xe5?(>)\x1a\x85o\xee\xbf\x8fvr[\x06\xc4\xd3?\x9eQ\xb3!\xd2c\xf3?\x8a\xc9\xc4\xc2W\xa1\xfe\xbf6v\xb2\x1e.W\xf2\xbf\xe7\x90\xbc\x1d\rh\x00@\xb3\xec)\x9c`O\xfd?\xaf \x80#\xf1\xc6\xeb?\xf18y\x19\xbfj\xe2\xbf\xec0\xe7w\xd7\xf5\xd5\xbf\x95a\n#A\xe8\xf5\xbf\x05,C\x14\x85\xd2\xac\xbf\x9b\x03y\xfd\\E\xa1\xbf\xf1Qn\xfb\xbe\x04\xab?\x87\\\xe2ak\x07\xe5?\xabp\xee\x96\xfa\xe8\xbfW\xfd\xefJB\x08\xdc?L"X\xafqa\xc1\xbf\xbf\xc5@\x00Q\xf9\xf1\xbfv\xe6\xb1SlS\xfa\xbf\x96\xf7\xff\xe6u\xbb\xb2\xbf\xdbgyG\xce\x08\xc0?\xeb-\xd06\xd1h\xd2\xbfXw%e\x19\x1b\xe6?[\x9d[\xfe\xa7L\xe3\xbfhT\xd7\x11\xc8N\xf0\xbf\x02\xbb\xa5\xc8\x8b,\xf0\xbf3\xa4F\xda\xd7\x9f\xb0\xbf\xc7\x0e\x01Q\x89!\xe2\xbf\xed\xa7\xba\xfcx4\xf5?\xdb\x11\xd4\xc4\xa5\xd2\xef\xbf\xc0\x03\x02\xf9x\x19\xea\xbfX*\t\x89\xe8f\xf1\xbf\x8b\x89\xf9F\xdfT\x85?\xe0\x04\xf8$\xbf\xf7\xd8\xbf\xdb\xd5\xda\x1aH\x99\xc1?M\x8d\xa3_\x94U\xee\xbf\x1f\xdfw\xb6\xae\xa9\xe2?\x1f8\xec\xfc*\x19\xf1\xbfv\xdc\x17\xf4^\xd7\xee?\x06\xb8$\x91\x04\xc1\xd7\xbf"`:\xdd\xf5\xb9\xc9?7\xe5n\xd6\x8a8\xe1?\x92"\xf8\xa8\x98\x13\xee\xbf\xfe\xb6Su\xbdy\xf0\xbf\x81\xd4>VTY\xd5?.R\xdb\xc6\x8ab\xf1\xbf\xa2Z\x19\xcam-\xd2?\x18!\x1f\xa9:\xc1\xd9?\xa1l\x98+\x83_\xec\xbf\xae\xfe\x1c\x07\xe9\xe4\xc3?\xff\xe4\xb7O\xaf\x97\xf3?6\x848x\xe2\xe3\xec\xbf\xa4\xe3\xa0i\xd8\xd8\xcb\xbf{\x94\x90\xa2%\xa0\xee?\x08U`B\xf5\xa4\xd8\xbf=\xae]N\xd6&\xcd\xbf\xd0lu\xbc\xca%\xd7\xbf\xaeKt\x8f\r\xd3\xf7\xbf*\xb5\xc3V\xdb\xa9\xf6\xbf}<\x02\xc1\xfc\xe1\xc5?C\x18\xe0\x83a\xec\xc6\xbf\x88g\xea\x9d\x1e8\xeb?c>U\x83\xfb\x04\xfc\xbf\x1f\xd8l\x9a\xfe\x17\xbc\xbf\'\xe9\xb6\x1e\xdf\x00\xc9?;\xd5\x8f\xebu\x96\xf2\xbf!\xda\xf6\x8c\x0e\x01\xf1?\xc2\xf4\x80\x84o\x93\xd5\xbfF\xc9\x1c\xa1\xee2\xeb?\xb0Ks$\xd0\x87\xe0?^\x00N\x10\xad\xba\xe1\xbfn~\x90B\xe4O\xf5\xbfY\x9eLL\n\x98\xeb?*\xfe\xe9\xd5\xe1A\xf5\xbf\x15\x8d\x95\xb3\xeb}\xc7\xbf1\x96c\xeb\xf6i\xea\xbf\x1f3\xbc*\\\xaa\xca\xbf\xfb\xc9\x8e\x85\xbb\xf0\xdf?\x96\xeea\xad\xec\xa5\xe2?\xfc\xa04\xde,qu\xbf\x11\xed.__F\xd4\xbf\xa4M>\xe8\xdf\x01\xf3\xbf\x9e\xfe\xc7c)\xe1\xea?\xf3\x1cj$\xe3\x7f\xc0\xbf\xdaLE\x897\x8f\xf5?\xb8\xb4\x05\xcc\x8b#\xe1?\x10\xc3[\x05!\x1d\x00\xc0\xc5\x0b8\xaa\xff\xdc\x07\xc0\x7f \xf1\x97\x10\x01\x07\xc0\x9db2\x9a\xce \xfd\xbf\xfb\x03\x1a\x06[\xc6\xe7\xbfN\xc3\xb4\x91\xd6\xdb\xe3\xbf_\xca\xd1\xa6q\x9f\xd2\xbf\xd3\x8fA]\x00V\xe1\xbf|\xd8\x858\x81;\xf7\xbf\xb3\xbfX2\xd8,\xd6\xbf\x9cH\xbc\x93\xd2\xcd\xe4\xbf\x1c\x07\xf1\x8fN3\xf5\xbf\xd8&S\x9c\xa8j\xe8\xbf\xe8;v"\xcc\x9c\xe7?\x03\t\x97\xec\xfd\xf4\xd8?6Q\xc3P+\xce\xf1?\x82QsGC\x94\xfc\xbf\xc9\xab@\xca>}\xfb?P\xee\x08\xfb\x06\xe5\xe1\xbf\xb2\xba\x10\xdc\xa1b\xcc?\x0cX\x15\xc5\xd2 \xf2\xbf\xe1P\xfc\xc9\x13\x94\x07\xc0\xf5\x81C\xeb?\xc4\xd8\xbf\xda\xffz\xb4\xcd\x0e\xe6?\xfa\xd9\xacxO?\xc4\xbf\xc7\x18j\xa9\xe8]\xe3?\xb8\x9d\x0b\xfd\xb9\x05\xf4?\x88\x18%IL\xeb\xb6?\xd7jN\x14\xf2\x93\xf2\xbf/\x9a|\x93\x1b\xbd\xf4\xbf\x8e\xbel\xf77\xab\x10\xc0\xc8d\xf7E\xbbA\xf1\xbf @F\xc3^\xbf\xe3\xbf\x99\xdf\xebr\x9c&\xe8\xbfi\xf0\x97\xf8\xa9\xe1\xfb\xbf\xed\xa6V\xff[\t\xcf\xbf\xb40\xa5\xf5\xbbv\xee\xbf\x17\xb4\x080\xf8Y\xf1\xbf\xf4\xc9\xad\xfa\xe75\xdf?\xde{\xdf\x02vS\xf2\xbfY\xfe~\x86\x94\x00\xfe\xbf\xc6\xfc\xcc\xfc\xe6\x83\xf8\xbf\xc7\x87\x90\xff\xc66\xe2?\xad/j\x19\xf7>\xc1\xbf\xf7\xa0\x84\xf2sf\xef\xbf\xe5lpO}6\xd9\xbf\xd4g\xdf\x83\xd8K\xf1\xbf%Qi\xe6\x85c\xdc\xbfo\xe9\xaa\xa4c7\xf5\xbf\x81\xedb\x86\x0bu\xed\xbfF\xa9%\x1f\xfeE\xe0\xbf\xbdK\xb4W\xcb\xf4\x08@\xb5H\x86-\xe81\x03@\x8bp\x8bb\xd8\xd2\r@r\xe6\x85\x93\xfc\xc4\x15@\x1b\x93usi\xbe\x18@\xe5\xa1\xe7\xa5cd\x13@\x97B\xfaa\xec\xeb\r@BgiB\x90\xe7\x0f@\xe0#\xe7qGP\x06@\xdb\xa1 \x13\x8f\x1b\x03@\xc4\xafa\xa7G\xe5\xcb?\xd9\xb1\xd0l\x1d\x19\xc2\xbf\x87v\x82\xbe\x82\xcc\x0b\xc05\xe4\xbfk6\x89\x00\xc0\xed\xb9\xb0\x1dx\xd8\xf0\xbf\xfc\x15\xeaTO\xc8\xe4\xbf\xfbn60PD\xed\xbf\xa3\xdf\x83\xe1\xec\x83\xc5?)\xc9\xd87\x03y\xe3\xbfV\x8cx6LT\xee?\x91\x15c\xc3\x04$\xed?\xad\xa2e\xcc\xdf&\xe4?\x16\xcd\xb1\xa92l\xf2\xbf\xb0\x1a\xe3\xdb\xce\xbd\x07\xc0\x8a\xfd\x8c\xe0\x18\xce\xfc\xbf/\xc7m\xec\n\xe8\xf3\xbf7\xb9\x02\xaa\xd4\xe4\xb1?<\xad\xb5\xa6\xeb\x89\x06@\xe9\xb3D\x04\xc5%\x02@\x80x\xc2\t\xc8\xb1\x10@h\x1d\x9c\x12\xfe\x0e\x14@O\r\x8a\x9b@\xda\x0f@p\xec\x83F\xdb\x19\x1a@9\xcf\xc6y\xebg\x13@\xb0V_WrF @RF\xb8\xb9YI\x14@x}\x8b4M\xb1\x11@\x94E\xefs\t\xbb\xe0?\xc4]\xe1/\xcf\xe9\x07@\xb8j\x02Fd\xfd\x08@\xb3\xf0\x8e\xe9\xd1\r\x03\xc06g%\xaf\xb9\xf7\x02\xc0)Q\xf8^\x1dZ\t\xc0=\xb2\xbd*\xe7\x94\xe0\xbf\xc9\x90\x85\xe4\xa2|\x8b\xbf\x8dG$\xf1\xd2&\xf4?\xc7W}\xb0!\xba\xef? h\xd0\x97\xa1\x1a\xd7?O\xa0\x182\x82\x12\xe0?WuJm.\x90\xf7\xbf\xb8\xa6\xbfl\x1b\x85\x95\xbf\x8d>%+\x1fh\xd4\xbf\xdd\xc5\x91\xc2g\x19\xed\xbf\xbf\xa9\x10\xbf\x17[\xce\xbf\x9b\x1f\xeb\xeb \x91\xdb?\x96\xd7\x8b\xce\xd9E\xda?\xde:\x1b{\x9e\xf6\xf4?T\xb0\xf6\xe3-/\x0f@\xc8\xa3\xd9\x9d\xd4I\x14@\xcf\xcf\xa2i\xc4z\x17@\x86N\x0f\xb5\xe8\x14\x1d@\xff\xc2\xbd\xb9a\xd6\x15@\xd7\xd9z\x88\xa8\xc1\x19@\xe1b\xf2\xdd\x01\xc0\x0e@\x90\xa0//\x1a\xdc\x01@\xa83\x9b\x1b9\xc7\xf6?\xca\n`\xe7\xae\x94\xb4?\x9d\x8cj\x8e\xdd\xb3\xf0\xbfOm3\x1da\xb4\xb6\xbf\xe8H\x95\xae`\xc4\xf2\xbf\xfa\xc1Y\x8c\x91\xa0\x02\xc0K\xbe\xd9\xf61\x14\xb1\xbf\x98Q\x93\xaf\xcb^\xd2?\xbd\xa4Y\x0bm+\xe0?\xd1E\xd6tG\xa0\xf0\xbf\x95:eBN=\xe1?aW\x87\x83\r\n\xf9?:\x90A\xe0I\x10\xe8?f|\xea\xab\xe7\x19\xf3\xbf\x88\xc6\x95\x8bp9\x03\xc0D@\x9b\xc0O\x06\xf8?\x88\xa7xX\xac\xa6\xf6?I~a\xf2\x1f\xf7\x0b@\x83,\xcer\x9d\xb5\xfb?\xa3h\xd4\xe9Q\x8c\t@\x98\x07\xa8\x10\x1f\xfb\x05@\xeabA\x1aJ\xe1\x08@E\x1d\x07\xfd\xa0\x9d\x13@\xbf\x94P\xe0N\n!@.!\x87}\xfb\xde\x1f@b\x88O\x81T=\xfd?\x9c\x97\xf4\xdf\x8e\xe5\xf0?\xa2\xe5Sy\xec\xb0\xca\xbfis]B\xdc\x18\xf6?\xd6\xdb\xaf\x1b\x95\xfe\xf2?[O=fA#\xf4\xbf9\xb6\xf1{\xd9\x7f\xfa\xbfYY\x0e=\x83\xf8\xe9\xbf\xe8\x82V\x95\xf5\x8e\xf2\xbf\xed\xba\x81{vC\xf4\xbf\xc8\\\xf9\x01\nF\xed?\xf1Kw\xfb\xb1U\xfb?\x8a\'\xd0\xd1\xeb!\xeb?e.N\xc4/\x1e\xe3\xbf\x03\xa8\\\xb9%1\xd8\xbf\x11\x0b=+\xb2\x1b\xef?\xc7\xd36o\xde\xd6\xf8\xbf\xff\x1e3<\xee8\xe6?\xbb\xf1c\xdfPm\x01@\xa3\x91i\xec~~\xed?\x8e\x05\xb1$\x9b:\xd2\xbf\xbc\x02\x93\xc9pF\xec\xbfi\xb3\xa1\x17\x0c\xef\xfa?1\xadi-\x02\xe6\xc7?\x08\x9a\x0f\x80\xeb\x9a\xc6\xbf\x7f\x9dE\xd4-\xce\x11@\xa0\xfa\xdb\x7f\x1a\xc3\x1b@z"\xce\xf9w\xd7\xf7?\xd1\x02{\nA\xba\xfc\xbf\xc4\xcc\x0e(;\xe7\xfe\xbf\xcb\xedh`~\x85\xcc?J\x8b:\xce\x82\xed\xfa\xbf\xf7\xb3\xa2\xae\xf8\xbc\xdb?\x08[C\xb8}\xdf\x04@\xa3b#\xae\x13v\xde\xbfW\xf5\x80\x93\xbew\xfc\xbf\xb8\x9a\xff\xb5P\xb6\xdf\xbf<\x1e\x99\x92\x8e\xdd\xd9\xbfG\x92(E]$\xdb\xbf\x9e\x0e\xd3\x8b]\xda\xf2\xbfnq9P\x7f\x82\xf4?H\xbe\xef\xff\x86I\xcc\xbf\xb0\xeb\x96\x08\xb3\xca\x00\xc0k\xd2\xd6\xe2B\x81\xd3\xbf\r\xe4m\x93\xf9\x08\xed?U\xaa\x87\x8a\x1b\xb2\x02@\xc3\x1c\xbd\xa8q\x8c\x04@\x15\xbe\x8e\x0b\n#\xf0?p\xabd\x17\x17\xa8\xf3?%J\x81\x0f\xf0\x0e\x07\xc0\x8d`i\x9c)S\xf8\xbf\xa9s2\x88\x89\x9e\xfb\xbf\xe4e\xbe\xa0\x8cC\x03\xc0\xe1@sn\xb57\xf5?Wz\xc4\xe4\x02\xbd\xf7?\x8d\xaf\xf4E\xf3p\xe7\xbf\x848\x84\xccV \xfa\xbf\xcc`\x80\xb7\xa3^\x05@\xcd[`U\xb2\x8b\xfd?\x17\xc2\xd4\x1f\xd6\xad\x03@\x7fj\x00 o \x11@\xa8\x1f\x19\xdd\xe0\xf0\r@\xfc\xa8\x86\x94\xee_\x07@\xe1\x00sUc\xda\xe1?l\xf3\xd4\xef\xb5\xa0\xe1?+\xc0\xfeI\xa3\x08\xff?\x8b\xc4\x13\xcfa7\xd7\xbf|>\xbb\xf4\xf1T\xe3?\xd2\x08\x8c51\xf6\xe5?`\x88\xeeq\xb4\x93\xb6\xbf\xd9\x1b2X\xc96\x01@ \x9f#\xe4l+\xf2?\x88\xc56\xc0\x89\xb9\x05@0\x028\xdf\x0b>\xfd?ve\x08\xe0:\xbd\xef?\xb8\xc9\x0eG\xc1\x9b\xe2\xbf\xf3r\x85_\xa3\xf8\xcb\xbf\xc0\x87\xe8\xe3\xaa\x97\xdc\xbf\xeb)\xe3\xd8\xf9X\x04\xc0\xb1\x83L\x85Zj\x12\xc0d\x05Vs\x1d\xb3\x1c\xc0\x9a\x8a{\x8d%"\x15\xc0\x03\xa1i\xd5\xf8~\x02@ r\x99\x0b\xeb?\xee\xbf\xfc_\x19\x94\x076\x01\xc0i\x8aYu\x16\x19\x14@\x12\x1cL\x16\xd9m\t@\xf2\x1d\xc7\xf5\x93\x83\xfe?\xde\xc3\x14D\x17\r\xfd?i\x10\xc2\x1d=\x05\xf3?\x83=\xe2\x11\x8f8\xf3?zn\xf8\xd2V\xa1\x00@\x01p{\xf0\xe3\x85\xd5\xbftjpC\xa4c\xeb\xbf\xa1\xbbq\'\xe3\xd9\xd7\xbf\x06O\xb0p\x8c\xce\x95?\xb4\xa7L\x96\xf7\xe2\xe2?,g8\xb7}\xc8\xda\xbf\x1b5\xb7\xf1\x98Q\xeb?\xd1\xe5\x8c\xd2\xa7\x02\xe0?\x03\x7fks{&\xf8?\x03\xc8\xb4\xdf\xaa\x1c\xe5\xbf\xac\xd8=\xed\x9d.\xf5\xbf>3pn\xbe\xd3\x8d?W\x97\x1b\xc9\xc6+\xf1\xbfCY\xaa\xdf\x88\x8b\x05\xc0\x03jXRv\xff\x0f\xc0\x1a\x93\rPxG\x14\xc0\xbfI\xe9e,\xd0\x14\xc0\xe9<%\x9e\'\\\xca\xbf\xabIF\xf7\xb0\xca\x11@[o\x0c\x92\xab\x02\xe0\xbf^\xdd\xc4\xb3\xf0\x13\xf4\xbf\xba\xbe\xe6\xc0\x93m\xe5\xbfl5\x93\\)-\xff?@\xba\xac\xc6\xe3\xcc\xd2?\xc3q\xbe\xe6\x08\x94\xf9?\xb4\xe5\xe1\xed\xba\xbb\xe2\xbf\xcb\xdd\x0fZ@\xb0\x03\xc0\xe3P\xae\xf6\xba\x1e\xf2\xbf\x17\xe1\xcb\xd43\xbd\xf4\xbf\x95\xdb\xc3\xb7\xb3\xaf\xf3\xbfg\xec\x8db<\xf1\xca\xbf\xba\xad.Cf]\xa5?y\xed\xbb)\xb2\x0c\xf9?\x87\xc6\x97\x07\x05~\xf0\xbf\xbd0\xa8R\x89\x1d\xda?#g\xe3\x02\x9a\r\xed\xbfk\x107\xc2\xa0W\xf1\xbfr%/\xee\x8c_\xd4\xbfw\xdaY\xe2\x1a7\xde\xbf\x8d<\xb2\xfeH\xb5\xed\xbfk\xf0\x12\x80\x8b\xc1\x06\xc0\xb2\x10\xd8;\x98 \x13\xc0\xcb_\xf7\x84~\x89\xf1\xbf\xcc\x81\xb9YHI\r\xc0\xab\xa1\xc8\xe0\x01\xec\x04\xc0\xca\xa3Gm\xc1\xec\xfd\xbfM\xed\x7f\xa3\x0e7\x08@J6\xab\x98W\xc2\xe1?\x9a\\>\xa8\xaa\xc6\x05\xc0\xcf\xf3r\x18\xf4\x18\x01\xc0\x92\xf4\x15\xac)g\x02\xc0.\x9a\x89N\xc3\x05X?Ao\xe9s\xd6{\xf7\xbf\xb9A\x1c\xcdi\xc9\xf0\xbf\xa2\x04\x1a\xd7\xdd8\xe9?J\x88\x9d\xa42\xbe\t\xc0\xf8e\xa9\xb2\x81a\xfd\xbf#y\x15\x90O\x1c\xda?\x8a\xf7\x17\x1bBq\xdc?\x94\x80\x8cV5\x05\xe9?\xf4u\x0c\x84\xbb\xda\xf1?LKdt\xe5\x9b\xeb\xbfSe\xedTI\x9c\xf2\xbf\x08\x97\xf7\xb7#\xc7\xf2\xbf\x02\xc2#29\x99\xea\xbfj\xfbf\x87\xa1\x00\xec\xbf\xd9\xd1\xe5\xebI\xeb\xca\xbf\x9d\xb5\xda|<\xea\x07\xc0\x19qS\xdb\x11S\x12\xc0\xd7\xad\x12\xc6\xa6\xa2\x04\xc08\xa7\nV\xceS\xfe\xbf\xf2N\x91\x87\x9b,\x04\xc0\xfb\xc4\xbdD\xe2\x1f\x07\xc0d\xdb\xb6\xbdV0\xf9\xbfi\x8c\x05\xdd\xcc\x92\x10\xc0G\x16\x8d\xc2\xb9\x8f\xd0?\xce\x92\xa0\xc1Y\xbe\n\xc0\xd7RU\xa1\xcf\xc8\xfa\xbf\xff\x92\x85a\x86<\xce\xbf\xffN\xf0\x8b\x02\xde\x08\xc0\x1a\xcc\x11\xdb\t\xf9\xd7?l\x15p\xe0\xbe\xb2\x00\xc0\xb5i\xeb\xc2\xc2\xba\t\xc0\x1e\xa5T\x0f<\x15\t\xc0&7%\x07\xf30\x05\xc0\xa1\x96\x1cmo\x1e\xd1\xbfx\xce\xf3X\x8a\xd2\xf1?\xc0\xe9]\xe71\xe7\xe0?\xf3\xf4\xbe\xacm\x19\xf6?\x8f3v\x15\xcan\xe2\xbf\xc0\xd2K\x8c{\xb1\xef\xbf;\xd3\x81\xaf\x1f\xf2\xb6\xbf\xde\x95`*=\x03\xfc\xbf}]\xe8\xb9T[\xf4\xbf\xd9\x97\xe3\xbbN8\xfd\xbf\xa4(\xfa\x85$L\xf5\xbf\x7f\xf4Li\x8c,\x00\xc0\xd1\xcbl\xdb\x18|\xca\xbf\xe9{\xa5\xbb\x04\xac\xf6?\xb8\xe3\xb6v\x99G\xec?sG\xcct\x1d<\xdf?\x84]\xf6o\x02\xb8\x0f\xc0\\\xbcE\xd7\xeaW\x13\xc0\xed\xef\x15\xde\x8f\xf7\x19\xc0{\xcaO9\xa1\x10\n\xc063\xf6m\x1c\xd6\x01\xc0\x9a\xe1\xf1\x0b\x05+\x05\xc0d\xe7\x18\xbd\x1d\xf5\x03\xc0F\x987\x92&\x89\x05\xc0\x81.\xe3\x8a?\x13\x10\xc0\xed\x0c\x08y\xf9k\r\xc0&|>\x02\xef\x89\x06\xc0\xc0\xa2\x01\xe0^\n\t\xc0X}n9\xdd\xa9\x00\xc03\xf4\xc5\x9f]Y\xe0\xbf\x0f\xd9\\j\xdf.w?\x82\x15\xdaRw[\xe4?f\xe7\xa7\xbe\x91\x95\xd0\xbfG\xd7bY >\xec?\x7fz\xe4#\xf4C\xe5\xbf\tba\x91\xcb=\xf5\xbf\xb0\x05\xddQo \xf3\xbfw\xe4\xbf>Gg\x9c\xdf\x91\xe1?\xd4\x10f\x15\xb5)\xe2\xbfy\xae~\xd0\x92\xf2\xf1\xbfT\x07fC\xcf\xbe\xe6?ro\xfb\xa3\\\x16\xfc?\xf7\xe4\xab\xc2,\xf3\xc0\xbf\xa4W\xe8\xf0\xca\xc2\xd1\xbf\x81\xd97\x92\x06C\xf3\xbf\xef\\\xdd\xc3\xdd-\xf3\xbfXgR\xbfk[\xf3?V\xe8\xe7\xc3j\xb9\x05@\'\xd3P~`\x1c\xf1\xbf\x16\x92\x11{q\x0c\xf0\xbf\xb9/\xf6\xben\xe7\xe9\xbf\xfb5\x8d\x7f\xc8/\xf9\xbf7\xcchD\xbb2\xe9?\x8a\x94&\r\x8c\x8a\xd6\xbf\x9d\xdc\xcf\xbd\xb9\x13\xf5\xbf\x18\x8fE\x8f9G\xd6\xbf\xb9\xcb\x9b0\xd4\xcc\xf9\xbf@;\xb9\x1a \x03\xf8\xbf\xdd\xc2\x1a\xd0\x00\xe7\xe2\xbfy\xc7\xdd\xcaA\xb8\xe6\xbf\xc2\xf8\x8d\x182\xed\x00\xc0c\xcd\xe1\x9d$\xd2\xf5?Dx\x0b\xf8\x1aV\xee?\xbe\x8d\x0b\x7f\xdd\xc2\xe8\xbf\x00\x0c2h\xdd\xa8\xdc?\xbd\xdf4\x7f$\xfe\xf4?\xa9\xd2\x7fM\xc3#\xf7\xbf\xfb+\xda\xcf\xe5\xe1\xe8?\x0fV38\xee\xd6\xde\xbfs\xd4\x13\xbex|\x00\xc0P2\x85W\xde\xa2\xfe\xbf\xdc\xfd\xab\x7f$\x80\xfb\xbf\xf5p\xb0l\xf4\x04\xe5?\xc0,gRtt\xdc\xbf\xb0O\xc7\x02\xea\x88\xf7?@\x7f\xe8\xbf\xd1\x95\x04@$\xaa\xe7\xe1\x85\xec\xfa?/\xbb[\xa8!\\\xdb\xbf\xbd\x95\x97U\xc9q\xff\xbf\x93\xcb$;SF\xe2\xbf:\xbdl/\xdb\x0e\xef\xbf\xfe\x89_\x83]z\x98?\xd9\x8a/7\x12>\xa6?\xc3\xab/\xde\xc8\x05\xe3\xe5\xbf\xd2<\xf0\xff\x94W\xe0\xbfC\x93\x0b\xe9\xfe[\xee?"?)_;\xab\xe1?\x1d]\xbc\x08\x00\xa6\xfa?t\xdb\xe8\xb9\xf1<\xfb?\x91m+:C\x04\xea?\x94\x9cw\xbe\xca\xde\xfe?\xa4\x10\xc0\xa6\xc3e\xa4?\x08-\x9f\x96L"\xdd?\x0f\x03\x15\t\x80\x8d\xf4?Pd\xe3\x9f\x06\xbb\xd5\xbf^\x99\x08nTB\xdb\xbfoS\xd5\x8b\xda\xb5\xf8\xbf\x1c\xba\x89x=E\xfe\xbf%6\r\x16\x03\x88\x02\xc0\xd9\xcdHH\xc53\xc2?J\xe4b\xab\x9dB\xd7\xbf\xa7\xf0\xe6\x8f(\x0f\xf9?l\xe4\xa3\xec\x01\x07\x07@y\xb1X \x0c\xdc\xce?\xd2\t\x0f)\x05\x82\xce?"\xfb\xfe\xef\xbd\x00\xf3?\xc80Wk\xe7b\xc5\xbf\xf9\x86I\x98\xfb)\xf3\xbf\'\x8a#5\x00f\xe5?\xf1\xa4\xbc6@\x14\xe6\xbf\x04H\x98\xa5j\x16\xe7?Po67K?\xe7?\xd4\x11\xb82\xdb\x12\xe6\xbfd\xa6Ga\x009\xe7\xbf\xda\xbd\xf0gJ\x0e\xeb?\xa6\xdb\x16?\xcb[\xfe?\xdc\xd0\xceA\xc4_\x0e@\xf1\x9a\xac\x19\x84Y\x06@\x15\x8a\xe3\xee\x1e\xa7\xe8?\xa1\xc0\xf8\x88[\x1c\x05@\xb3\x97\x91\x82|\xfd\xec?\x10\x8f\xcd\xf7d[\x05@\x1cj\xc1\xc4\xcf\xc8\xd3?& \x1e\x10\xb4\xc7\xe9\xbf\xf6Y Z>\x16\xe7?\xc8(0\xd8\xf9\x8c\xd3\xbf\xd6\xcf\x12n\x9d(\xc8?\x1d\x01\x87\x83\xd0\x9d\xea?\x1c\xac\x9d\xd5\x936\xe6?\x8a\x18\xac\x9c\xd1t\x02@^\xfc\xd6\x1b\xe4\xf5\xb4?\xafj\xe2R\xaf\xf0\xba\xbf\xb1:\x9f#OL\xac?\x02\x17\xdc\x13\xb2q\xec?\x00\xaa\xb4\xd2d\xd1\xf1\xbf\x8dN6\x82\xe3\xfe\xcb\xbfy\xaf\xc2\x1f\xae\xd2\xdb?!\xf6\xb7\xdf^\xb3\xef\xbfXc\xff\xc8&\x1b\xf0\xbf\xbd}\xb9\xb0\xa5\x05\xb6\xbf\xc1\xcd\xc1\'\x92A\xe9\xbfT?\x95\xd3\x8a\x82\xf2?Cs\xdb-bB\x03@P\x13\x83\r\x86\xaa\x04@\x8d\x97SF\xb6\xc1\x14@S\xc0qH&I\x10@\xdcC$\x11[/\x0b@\x8e\xf6\xf5\x96\x11\xdf\x00@\x98\x01Y\x8e\xff\x91\n@\xfd\x91,a\x97\xc1\x04@e\x9c\xfc\t\xc5\x95\x04@\xcd\x0e\xe4c\x7f\x1b\xef?\xd9\x17\x9b\x15\x17\xc2\x02@\x1ei\x90\x0bi\'\xfc?A\xfep[\xfa\x95\x15@t\xda\xb3\xb6\x11\x99\x05@\xe3 l\xed\xb9\x8b\x06@{\x88\x8e\x99\xc07\xfe?\x12T\xea\x01\x1c\xdd\xf8?\xc5\xfb\xc5\x12&\x88\xf5?;t\xd1E\xccD\xf6?\xcdbn \xae\xfe\xd3\xbf\xc5\x8e\xa8U\x90\xc3\xe0\xbf00y\xe2\x1e\x98\xca\xbf\xddYg\xa3\x15\xd5\xe0\xbf!\xcb.\x80K\xd1\xd3?b\x8e\xdf\xf4W\xcb\xac?\x98\x8dn\x94F\xfe\xe1?\xee?;\x94\x90_\x8b?)f\xca\x80\xec\x11\xd0\xbf\x06\xd6\\^\xe9K\xb9?c\xa6\xb4\xc4]\xd3\xbe\xbf\x10\xe3V\xb92D\x01@|\x83\x061\x824\xf0?\x1c\xedE\x99\x1e\xf5\x07@\x7f\xa0\x88\xe9\x80\x02\xfa?\xe2!\xbaE/j\x0c@"\xf9\xae\x80\xb9\xe0\x03@cRG\x11\xd6\xef\x11@H\xbd\xb0"K\xd9\x13@\x97\xa4\xb2v \x90\x10@\xba\xa2U]V6\x0b@\xbb\x9f\'N\xc5\x86\t@W=b\x87\xa6\xdc\xe5?z\xb2\x8a\x9c4\xc4\xea?Sw\x80\xa8oY\x02@\x8d\x8f\x1b\x13\xa1\x1f\xdb\xbf\xcd\xf0\x917\x19\xbe\x00\xc0Ej\x95?\xf4w\xd9\xbfh\xb94\xac<\xee\xe3\xbf\xe4\xe0\x87\x82\xddP\xd3\xbf\xda9\x1aI\xef\xbe\xe6\xbf\'\x1f\xe21\xe1\xfd\xfd\xbf\xc6\x15\xd4\xd0\xd3\xee\xe5?\xf7R\x18\x82\xac\xd2\xf8?\xfdC\xb5=\xb9\xcc\xfe?2(\x80\x84\xd0G\xe0\xbf6\x80\x0c\x18\xae(\xdb\xbf*\xe5\xf3\xa7\xc2t\xc7?>\xdf\xda\xb2(Y\xc9\xbf\xd9|8x}\x00\xf2?\x1a\xa0\xa2\xe7\xb7I\xe2?\xa9\xc9\x16\xc7\x10\x13\xde\xbf\x03n\xfa\xf2*\xe8\xb8\xbfK\x90\xb3 \x11\xd5\xe7\xbf\x97\xac\xdf\xb6\xce\xc8\xf0\xbf\xd0)L\xb9\x88$\xec\xbf\x1a\x97\xa7T\x86\x93\xb0?\x1f\x8a\xb7\xc3W\xb3\xde?\x9c\xa6\xdc\xed\x0cI\xfc\xbf\xb3b\xa3\xf5\x91\xc1\xc1?\xd9w"t\xfd\xba\xd4?\x07\xde\xd4)\x82\xa2\xe7\xbf\xb4\x86\x9f\xba[\xa4\x04\xc0\xb9,\xfb\x8c\x94[\xf6\xbfbt\x0e\xb2\x02N\xe5\xbfD<~U\xf6\x8e\xf5\xbf\xcd\xd2\x9cw\xac\x81\xef?\'\x8b|\x99\xe5\x17\xdc?\x99\xbb\xfe\xdb\x00\xb3\xe7?\xc3\xd1r;\x03\x0f\xea?F\xfe\xed\x02\xff\xa7\xe7\xbf\xde\xd4%F\xc9@\xe7\xbf(\\\xe9\xf78\x12\xd6\xbf}\x82BN\xba\x1c\xd4?\xb9\r\xdc\xedS=\xd5?\xdcob\xeb\x14X\xdf?\x94|\x80\xb3H\xf9\xe1\xbf\xeb\x03jF1\x02\xf2?l\x0bB"\xe3\xdf\xcc\xbf\xed\x0f\xbb\xee\re\xe4?ps\x91\xb2yr\xe8?kXK\xa7\x93-\xd7\xbfD\x80\xc0\xf1\xa7\xcb\xf2?\xfbQ1\xf8\xdf\xf4\xcc\xbf/\x88~}\x11"\xbc\xbf\xdb\x8c\xe1\xf3\xc2\x01\xe6?0\x940F\x0bV\xfe?\xa1&\xb9\x9f\xf77\xde\xbf7U\xa3\x89\x11\x8e\xf2?\xf0\xb5l\xe3\x8b\xf4\xd0\xbf\x0ci\xa6\xd7l+\xde\xbf\xddK(\x1e\t\xbb\x04@<\x1d\xbe9\n\xa2\xfd?F^\xe9\xd3|\xd4\x03\xc0\xa0\xd8\xd8HaY\xdb\xbf4{!\xa1\xe8\x91\xef\xbf\x11\x93\xa2b\x97J\xe0?C\xfb\xb0\xbd;\xdc\xe7?\x8d\r\xbc\xe3\xa8\xe2\x03\xc0uv\xc1]\x1a#\xf3?L5mm#\xaa\xdf\xbf\xa3H\xd5a\x1e\xe2\xdd\xbf)\xea\xd9\x18Wa\xe4\xbf\xb8\x00\xab\xc7Eg\xed?;\x1em\xac\xfdU\xd4?\x16"\xfb\x99\xfb4\xe5?\x15\x17\xe7%\x84\xc5\xcd\xbf\\;\xfe\xfe\x10+\xd2?\xda1\xff\xdcZ\x94\xe0?7aRuE\xf6\xc6?\xa7\x001\xaa\x8bK\xd0\xbfj\xb5\x992p\x01\xff?\xb7\xf5\xc2c\t\x10\xe7\xbf\x0e\xf2\xcf\xf4y\x82\xd0\xbf\x94\x19\x9e7\xab\x9f\xf4\xbf\xe8F?;\x1b\x94\xf4?\xcb(\n\xb6\xf0W\xda\xbf\x1e$\xab\xd4\xf4h\xcf?\xa5r\xe5\x02\x84\x0e\xc0?z\xe3\x1b\xfaY[\xe7?\xed\x10\xe8TI\xf2\xd2?9:\xf9nW\xab\xfb\xbf\x80\xfc\xdc\xe4\xaaN\xd4?q*\x82p\x19.\xd5\xbf\'\xa84n\xa0\xc2\xf7?\x00\x1c\xd6\x0f(J\xbe\xbf\xaf\xfa\x1em\xe9\x17\xe7\xbf\x02\x0f\x9c\x9f\xa5z\xe0\xbf\x8b\xba\x9c\xe6\x00W\xb6\xbf\xd3\xbf\x9f\xf3\x98\xca\xd3?\xc0\x0f\x10\xb1dV\xc5\xbfe\x13\x12\xa4\xbc\xf5\xd9\xbf\xbeK\xf3h\xd3\xc2\xf3\xbf\'\x05E\xd84I\xe2\xbf\xb0J\xdf\xce2\xde\x03@\xed0\xb3sf\x1c\xc9\xbf\x8cl$\x0e\x0bb\xf5?n\x8a\xf7\t\xe4\x84\xd4?\xdc\xe2\xc3\xaa\x03\xe0\xee?\x11O\xa0!\xdf\x1d\xd5\xbfV\xa3;\x11\xae\x1f\xf2?[\xcd\x90\xb7\x1e2\xdc?L\xa6\xd6~\xf7/\xe5\xbf\x17\xa1\x07\x02\x9f\xa2\xd3\xbf>\r\'g\xd3}\xfb\xbf\xc2qUz[\x1d\xe3\xbf\xa7\xda \xd0?E\xdb?\xd1\x83YW\x84{\xf2\xbf\xb7F\x15\xae\xe1\xdf\xcd?\x9dG\x07ASg\xe8?_%FEf\x97\xd6?\xeb=\xbe\xe2\xe4\xd3\xe2?\x10\xe9Y\xddd\xe2\xa9\xbf\xbc\x11\x92X\x9d&\xe6\xbf\xe0\xb8I\x1c\xfd\xech\xbf!\xd7\x88Hu\x95\xe9?g\x8d\x18\xe27\x82\xde\xbf\x06@\x9f\xd2\xe1G\xe4\xbf;\xa65\xd6\x0e\xec\xe2\xbf\x94\xe6v-@O\xdd\xbfC\xb3\x16D\xc6\xbe\xe3?&\x00I\xe7\xab\xac\xf3\xbf\x1a\xb6\x14\xc49\xaa\xef?\xda%Fy|\xbf\xea\xbf\x0b$\x8e+\xb2\xb1\xc1\xbf\xe0\xc2h\x9aW\x0e\xd0\xbf\xa9Y\xb8\xc5\x18@\xe1?\x92\xe1B\xe5\xd5\xdc\xf6\xbf\xfe\x12 \xa2\xc1}\xc2?x\x8f\x9a\xc75\x13\xf8?|h\x98,\x15\xc3\xea\xbf\xe8\x0c`\x08\xa4Q\xf0?\xc5\xc5\xa3m\x00\xf0\xf4\xbf\xcfQ\x81X"\x91\x0b\xc0 \xb2\xd6\xe9\x85\xf7\xe7\xbf\xbbD\xea\x16\xcc\xd7\xe6\xbf\xe7\xc4\xd1k%)\xf7\xbf\x07\xae\xb6(\xf62\x02@\xf3\xb2\x1eu\xff.\xd4\xbf\x8d/\xed_\xc2\x94\xec\xbfq \x83\\4\xa1\xe4?\x8a\xafA\xa7\xd3\xcf\xd6\xbf\xc0m\xe8\x8b9\x86\xd7?\xfa^\x0e\xf5\xa5\x97\xc8?\x06J\x00j(\xc0\xf1\xbf\x07\xc2\x02u\xb2\xc0\xf2\xbf\x94\x97\xd0,2\x8b\xfb\xbf\x0f\xaf{\xdbFg\xdb?`$\xf9\'\xc4]\xf4\xbf\x8c)\x9bg\xba\x00\n\xc0\xf2\xbe\xec\x1e]\xb1\xef?[\xa0\x80U|e\xc5\xbf\xed\xd2H\x14\xd7\x89\xf2\xbf\\.x%\xa5f\xe9?\xdf\xc3\xd6%k\x1b\xfb\xbf\xf9\xa7Kj\xaa)\xd0\xbfQT0v\xd1\xdd\xef\xbf\x17S\x81\xae\xb7K\xde?\x01\x18Qp\xb3\x8e\x00@\xe9\x81\x9f\x8c\x8fG\x83?4\xbc\nnG\xbc\xee\xbfG\xcc\xe4\x86u\x8b\xf7?\xc9l)\x0bD\xd4\xe9\xbf\xf1rhF\xff\xad\xf5\xbf5S\xac\xde\xd0\x1d\xee\xbf\x80\x7f\xfa\xdeW\xb2\xea\xbf\x16!\t\xfa\x0b\x1d\xe7\xbfF%Ea\xaf\\\xc3\xbf\xd8\x8d\xb0\xe0J\xd2\xb0\xbf\xebl\xc7\\\xcd\xb4\xf0\xbf\x96*\xea\xa8\xc3\xbe\xf8\xbfj|w\x8c\xe7\x90\xf7?\xce\x88\xab\xf0\xf1\xe9\xe1\xbf\x82U\xb5\xacc\xfa\xd9?|HZ\xb21g\xd9\xbf3Z\xa4\x00\xb9\x98\xca?\xed\xa8\xc5\xcc@;\xfd\xbf\xe4Y\xaa\xd4G\xec\xf0?\t8-\x16e\r\xd0\xbfFx\xe7*\xff\x85\xf9\xbfZ\x9eH\x98\xba(\xcb\xbfw<\xc5}I8\xe8?+H\xed\xf4\xc7\xd7\xe6?\xe9z \x1f\xb0\xe5\xe6?\x00\xb6x49\xf8\x03\xbf:\xbd\x8c\xb3^\xdc\xe7?;\xaa$*\x13i\xf4?\t\xa5\xc8$\x87\x94\xb3?a\xbb\x03\x9f\xddn\xc7?B\xa6\xd8j\xd8<\xea?0\xca\xd9\xde0\xeb\xe7?\xc8\xc7\x13\xe6\x16\xf3\xa7\xbf\xc0\x15\x90\x16#N\xed?\xa8\x16rZ\xb2\x86\xc2?J%i\x8d\x93\x90\xd7\xbfRE$LV7\xf4\xbfN\xc3G\xe9:\x04\xf7?\x9f\xaf\xd4\xce\xe6F\xe9\xbfq\x8a\x0fF\x85_\xec?\xc3\xb6\xf1\x9e\x1fV\xf2?\x00\x0f\xff{\xf2\xa8\xff\xbf\xb8_\xc2\x9b\xfa>\xf4?\x0e\xdbjm\x03\xd4\xe2\xbfr\x98\x1d\xc4\x17:\xd2\xbf\xc9U$\xe2$\x0f\xe1?\x83\xb7J\x8b\xcb \xf0\xbfS$\xf3\x1f\x15"\xec\xbfy\xe1\x91\xfb\xfd/\xbb\xbfbWl\x0f\xb1\x96\xee?\x96o\xb7L\x12n\xcd\xbf\x0b>\xef\x10\x14\x82\xe6\xbf\xb0Wy\x861\x8c\xfd?\xc4\\\x0b\x0b\x0e\xd9\x02@\x8c/\nM\xab4\xf0?3\xadg*\xb8\xe8\xf2\xbfC\x7f\xb0\x9a\x01\x82\xd9\xbf\x14Z\xcb\xe8\x02\x8e\xa2?\xaa\x86\x00N\xbd\x02\xd3\xbfB\xf1\xd0P7\x04\xe3\xbf\xf5N0\x9e\xa6\xcd\xd0?\xf3\x90\xa9\x82\x90\xc6\x08\xc0f\xe2B\xff\xd1C\x01\xc0\xbf\xda\x15t5P\x07\xc0\x8d\x9dg\xe9\xbdZ\xfb\xbfMCN\x17\xf6\xd2\xf7?B\xf2e\xe7\\\x13\xad\xbf\xc3\x05=\x05\x99\\\xe4\xbf\x82\xae\xb1\xcc\xd1\x9c\xe6\xbf\xa3-\x07/\xfc\x11\xf9?\xcf8\xf1v\x91X\xf7\xbfU^^\xe3\x04s\x07\xc0\xb0\x87\x13u\xcc)\xdf\xbf\xbc\xb2(\x7fi\x9e\xf1\xbf\x88>?\x89h\xda\xf1\xbf{jZ\x9f\xe9s\xca?\xc5\xb5\xdb\xca\x80\xfd\xd7?\xa4}Z\xa7+~\xf2\xbf\\X\xf4\x8c%]\xb3\xbf\xa9i\x8b\xf3\x87t\xeb?\x8e\xc9}ms\x9e\x06\xc0\x0cxj\x8fK\xf4\x03\xc0f\xb3\x80\x9d\x9b\xce\xdf\xbf\xc12o\xf8e\xd4\xf0?dX\xfa\x1fzE\xf5\xbf!\x10%\xb3\xce\xa9\xf9\xbfXa\xdb\xf0!O\xda?\xe3\x18\x95\xe5\x96\x87\xd1?\x0c\xeeR|b\xcc\xe2\xbf\xb6\x9c\xe1\xd12\xbc\xe8\xbf\xf5\x84\x9f\x9a\xe6\xa7\n\xc0bX\xd4\xc3Ah\x00\xc0\xdd\n\xc2C\x8ay\xe0\xbf9\xc8\xa9\xbd8~\xde?\xc2\x01\xdd\x05\x0e\xbf\xe9?\x9b\xa6\x88\x98K\xca\xf0?1\xa7U\xc9\x0ej\xe1\xbf\x8b\xafz}#Z\xcd\xbfd~c\xec\xbf\xd2\xf4\xbf\xac\x94\x10Y\xc5\x95\xf0\xbfV\x10[\x00FV\xe3\xbf\xa5\xd5w\x14\x148\xfb\xbf\xce\xe7j\xb0Uh\xf2\xbf\x02\x8b\xe45\xcc\x9c\xf2\xbf\x0b\xb0\x931\x05\xb8\xd1?V\xdf\xeb\xc3^\xea\x9a?\xe5e\xecjTV\xfd?0\x15,\xe1\x89I\xe8?+$\x03t\\1\xdf\xbf\xb9\x17~\xfa\xdf\r\xc3\xbfF\x99\xc7\x00\xa6\t\xc7?\xa9oq\xe5)?\xbc\xbfC*\x9c\xb2\xc3\x99\xe8\xbf\xd5\x98\xd8\x98\x7f\xb2\xbe?\x93\xe3\x89\x96B\xd4\xef?\xae\x13\xaf\x0ee$\xfe?\xa0\xed\x95\xf8\x8aW\xf2?S\xb02B`\x98\xea\xbfru\xe1ut\t\xaf\xbf\xa3\xd0\xca\xb0\xbb\xec\xfb\xbf\xc4\x9a\xa3N\xfcl\xd4\xbf\xbf!\x7f\xe9\x05+\xd2\xbf\xc8\xa0\xed\x9aZ6\xf3\xbf\xe5_\xbcS\xea+\xc7\xbf\x1epq\x14\xf3\xd1\xd5?o\xe3?\xf7j\xe9\xea\xbfUQ\xee\x1a\x9a\x04\xd5?\xe8<,\xef\x86\\\xfa?F\x06%\xdbO\x00\xfb\xbf\x83G\x01\xff\x9f\x8b\xf9\xbf\xaf\xb0\xbaq\x01\xde\xd0\xbf\xfc[\xda\xcb\x8e\xa2\xd7?\xbc|\x06\x0c\'\xcf\xf7?\\\x12\xc9Z_\xad\x02@\xbaG\xc2[\xce\x81\t@\xf4\x9a\x06\x0b+\xaa\x06@\x16\xc0\x03\xc6V\xac\x01@\xde3^hz\xc4\xfc?#\x93\xc2\xe0^#\xf2\xbf*S\xb5\xae\xb4\xd3\xc2\xbf\xa4`\x9c\x1a\xfc\xbb\xb5?x\xbe\x9d\x18\x19\xf1\xec\xbf\xabu\xce\xde\x8e\x08\xf7\xbf0%\xb1\xb3/\x13\xfd\xbf\xff\x85X\x97U\x17\xfc?\xe2\xa9\x06p\xd4\xa1\x9c?KI\x04\xa5\xf8\xce\xf8\xbf{\xfb\xdb\x19gV\xeb?\xf1\x8e\xcbp\xee \xf0?\xb4\xf4a?>Z\xa7?\xc3N\xdd\x9b\xdb\xe8\xea\xbf\xbd]3H\xd1\xbe\xbe?\xaa\x86\x83\xa6F\xb6\xe1\xbf\xc5\xba\xc6\xfeuG\xca\xbfQqQ\xc1\\y\xef?p\xd5l_\xe0J\xe6?\x92a\xc76\xc0\x99\xf4?\x7f\xa8\x00\xc0w\xae\x12<7\x1e\xc6\xbf\xb9(QO\xf9E\xc3?\x94\x06Q{\xe6Y\xe4\xbf\xabb\xad\xec\xae\x07\xf7\xbfd\xc5\xecH\xa9%\xdb\xbf\xc7\xac9\xf5\x81^\x04\xc0\xcas\xe8=\x80\x91\n\xc0AT\xd8\x8c\xb6$\xe5\xbft\xfa\xbb\xe9Xl\xda?h\xbd\xf2\xcb\x89\xb8\xf4??(\x13Y\xcf\x93\xf0?\xe9\x87\xbd\xf1F\x11\xf2?\x0c\xd6o3)\x84\xf2?\xb4\xa0Sm\xd3Q\xc7\xbfa\xa5\xacN\xdft\xd8\xbf\xd0!*\xebz\x90\xe8\xbf\xad\xae\x0b\xcf\xdb\xf6\x00\xc0\xdd_z\xd2\xba\xf7\xe4?\x13\xfd1\xd0\x83i\xbc?\x82\x86{C\xa8\xf6\xb0?vZ\xb2\xb9\xb6\x89\xf0\xbf\x91\x81\xdd\x94%\xe6\xd2?\x98\x9d\x16\x8a\xb7\x9e\xed\xbf\x03\n\xbf\xb8\x94\xab\x01\xc0\xce\xf1*\xb3Q1\xe4\xbf\x91!.\xe3\xde\xd1\xfd\xbfe\xd3\x99\xf7\\u\x06\xc0\xdcDh"\x02\xdd\x13\xc0\xd4Ea3\x175\xfb\xbf\x8e\xf0\x9b\x11\x0b:\xf1\xbf\xeen\xe0LeW\xbd\xbf"\x88\x14c\xdc\x1d\xef?\x9a\t\xd9W(\x9f\xfb\xbf \xc3@\x9b>\x89\xea\xbf\xf6\xa5\x86\xdfZ\xf8\xf2\xbf}J\xb4\xe8\x88:\xf5\xbf\xc6g\x8f\x12\xe0@\xfc\xbf\x92\x17p\x9d\xb3\xac\xd0?\x05\xdd\xe5\xdf4\xb7\xd6?\x8eV-\xcd\xd9\x00\x93?\xe2G\x04\x0f\x07\xde\xe6\xbf\x1bj\xaf\xfb\x8b\x8b\xd4\xbf\xcf\r\x9f8\xc0:\xf3\xbf\xf1\xaax\x18\xf5\xfe\xdd?\xa20vT+\xb8\xf4\xbf\xe5c,\x98\x03\x19\xc9\xbf!\xa8\xe2\x8d\xf2\x0e\xb5\xbf\xd6\x95T\xb2\xa1\\\x01\xc0\x00r\xd1\xcft\x18\xf8\xbf. I\xe2i\x9a\x07\xc0\xe9}\xcf\xb3\x0bW\x00\xc0\x1f\x93>>12\x10\xc0\x92@\x93\x9b\x1a\x9d\x0b\xc0\xa4\xc4\r\xab\x9a\xf5\n\xc0\xbd\xb2\xd9\x1bLl\x1a\xc0\x9f\xf5\xaf,F0\x04\xc0\xe4\xbc}\xd68\x98\x00\xc0]\x18>\xb4}\xd1\x01\xc0\xad\xd9\xfd\x85\xf2\x0f\xed?\xc8F\x83\x8b\x1a%\xf1?\xd3\x85\xc3\xd2:.\xe4?\xd9\xcb\xf6\x05\x86\x08\xdf\xbf"\x88\xd6O\x00/\x01\xc0\xf8a\xcc\x0e%\xa4\xf2\xbf\'\xf44\x8d\x9b\xdd\xd7\xbf-FQ\xf86\x9e\x01\xc0\x19\xeb\x1c\x1f\x9a\xde\xe5\xbf-c\xc7o\x0e\xca\x05\xc0|\xc5\x9a\xab\x9du\xea\xbf\xc7\xd9W\x8d}\x8e\xf8?b]\\\xdf\xbd\xef\xf5\xbf9\xa0\x0b\x8cl\xc4\xef?\xc2f\xf8\x00\x1c/\xeb?\xe3<\xb8N\xe7M\xb9\xbf\x9dM\xb9\x9dn\xc6\xef\xbfN\x04\x12\x91\xc9m\xb5?\xce\xc9\xa9\x11\xd8O\xf8\xbf\x83\xc1\xb3v\x13M\xe3\xbfO\x8f}/\xd1\xba\t\xc0\xa0r\n\xb3\xa0f\x0c\xc0J\xe5\xecWg\xb8\xfa\xbf-:o\xf4Y\xe0\x0f\xc0.\x95!\xdf\xe9\xc4\t\xc0\x9f\xf0\xcb\x99fu\xf9\xbfD\xc5\xd0\xd9F\x11\xe0\xbf\'\x918\x92\x17#\xa1\xbf\xdd\xef\xdc\xa5o\x85\xe3?\xa0\xde\xb2(+$\xfc?\xee\xbci|\x9c\x1c\xde\xbfnC\xc2k\x9bw\x0e\xc0A\x8b.\x0b\ns\xfc\xbf\xd5\x8b8\x16\xe3e\x07\xc0L\xa6\xd5[@\xcc\xe5\xbfaaq.4\xdc\xf7\xbf.\r2\xf4\x0e9\x03\xc0a\x9dv0\x84\x03\xe4\xbf\xa7\x01\x81&`\xe1\xfc\xbf\xe9t$\xb3,\x16\xf3\xbf\xa1\x04\x1e!\x9d\x81\xda?p\x9a$\x9f7\xc1\xf0\xbf\xb6\xd8a/\xc1\xfd\xeb?B\x99pV\x8c\xa6\xee?\xb7\x8d\x86\xbf\xf2\xcc\xed?zX\x1f\x93C\x1b\xf3\xbf\xea\x82\xe7\x9cq\xc1\xe9\xbf\x06\xea\xdeA\x8a\xf4\xd6\xbfM\xba\x9e+\x910\xf5\xbfy\xd5\xb7\xde\x01 \xf6\xbf\xbf-\xdd/k\xaa\xfc\xbf\xa4\x02\x7f\xbb\xf5t\xf4?\x19M\x0em\x12\xa0\x07@\x91\x02\x04\x82\xc1y\xfc?|\xb4\xca\xae\xf6P\xb8\xbf\xe7\xf5\x1f.B\xbc\xd0\xbf\xd7*\xca\xe8\x07\x88\x02@\x18\x07\x8b\x14S$\xcd\xbf\x08\xfd\x1f\xc4]\x94\xc9?x\xa0\\ie\xc1\xae?\xfa\x93\xacx\xe6o\xf7\xbf\x11m\x9f\xdbiA\xf4\xbf\xb2A\x13\xd8\xf1\x12\x10\xc0\xf58\\\xea^\x9f\xfc\xbf\xb5\xa2\xa6\xcbk\xf6\xff\xbf\xd78T\x98\xc1)\x02\xc0t\xeb\xaf\xe3\xee\xf8\x01\xc0\xd5\x17\xfc0\xe2\xce\xfc?\xc4g\xe2\x02\x0f\xa9\xdc?T\xa3\xae\xc6\xbf_\xf1\xbf~\xa5\xa5-\x19\x96\xe8\xbf\\\xcf\x04\x11.\x8a\xc4?l"\x89\x02\xd3\x89\xea\xbf\xfaQ\xfa\x98\xe2\x9d\xf4\xbf F?\xe8\x19\x13\xdb?zM82C\x81\xfa\xbf\xb3\xc8[7a\xc7\xc2?\x1cLR\xbc\x8a\xee\xfa\xbfH|\x8d1\xe2\xc2\xe4?lnH\xf3\x16\xf8\xee?\xf9\xaaJ\xd7\xc5\xa2\x07@:\xd7\xc97{p\x01@\xa3uY\xe0\x08\xea\xf8\xbf-U4\x1d\x9f\xc1\xe7?\x01\xff\xc4\xa0\x043\x11@AU\xe9\xcb\xd0\xca\xfe?\x8c`\x06\xdd\\\xbe\xbd\xbf\xce\x0f\xd3\xb8\xc5\xb0\xfc\xbf\n\x0eM\xcdHH\xfe\xbf\x9c\xeeY\xf8\xda\xcc\xf6\xbf\x17\xd3\xc8\x1ah\xb3\xf5\xbf\x1cpH2\xe1\xb1\xe4\xbfO\xc0/\x9c\x13\xf0\xe2?\xf0\xac\xcaD\xf6\xf2\xe9\xbf\xa0\xdb0Oy\xca\xf6\xbfn\xf2\x1a\x81^=\x01@\xc2\xe2,-\xbc\x1e\xde\xbf\x8a\x89\xf7\xb7e{\xdd\xbf7H\x97\x1c\xc67\xf0?&\x1b(\xa7ul\xe0\xbfb\x19%\x96-<\xec\xbf}W\xbd\x0c\x8e\x7f\xf6\xbf3\x8bPm\x97\x1f\xf6?\x0b$\xd9\x1a\x9c\x91\xfa?9H\x996\x8a\xf0\xd5\xbfv\xa8o\xf1\xc9\x05\xc4\xbftR\xbe\x18\xdb\xc8\xfb?N2\xd6\x11\xf5\x9a\x06@\xe1\xf9\xe3\x08c\x0f\x0f@3\xd5\\lp\xab\xff?F\xa5]\xcb\xa4\xd2\xec?\x0b\xb2\x8f\xd28l\xf0?\xc56\xddL\xad\xd5\x06@\x19\xb3\x98ev\xea\xeb?\xb6\xf6\xb93[:\xef\xbf\xcb\x14&Ip;\xf7\xbf\xfdj\xe2*B\x95\x01\xc0d\x99L|\xbb6\xf9\xbf\xae\x93s\xa4j7\xf4\xbf8\xb7\xe2\xd7\x8e\xed\xfb\xbf\x1fH\x0e\x03\xa8\n\xe8?\xa5\xfb\x96\xe7\x02\xa8\x02@\x85\x84\x84\xd2)K\xd8?k\x89\x19F\x1f]\xf6?~uiKB\xb0\x06@[\xdc\xb3A\x80*\xf8\xbfB\xf4\xb0\x01k\x89\xe0?\x8d\xd4\x18\x1eq\x8d\xd2\xbf\x96!\x98H\x06c\xee?\x1f\xf5R\\ @\xe8\xbf\xabk\x96\xf4Os\x04\xc0\xb8\x040u\xbf\xc9\xf4?\xe9\xb3\x04zu"\x00@\xfa\x8a\xf0\xd7\xacU\xf9?1\xd8\xb1\xd3\x91\x93\xef?\x8a&/2%\x7f\x07@\x0c\xa6PX\xe9\x01\xec?\xa7\xe4M\x12\x7fb\x06@\xbe\xe9\xe0\xd8\xe5\xf3\x03@\xb3IH\xd4\x06\x99\xf0?C\t\x89\xae\xd53\xb3\xbfX\xf4\xc9\x8d\x94\xf4\xdb\xbf\xb14\xd6\t\x19\xe4\x04\xc0\x100\xc3-\xb73\xeb?@\xf3\x89\xd7\xd0l\xa6?\x0e\xef\x95\xa3\xf1~\xff\xbf\xd4}B\x1b\xdaF\xdd\xbf\x87\xe6\xb5KMU\xed\xbf"0f\xc3\xb4I\xee?\xc3t\xe1WW\xe2\x03@\xd6W\x13\xdf\xa13\xf0?z\x1c\x01\xa2C\xe2\xe0?\x8f\xf7WZ\x0e{\x07@\xfc\xa2\x82U\x85\xa5\xbf?0\x07\xad\xb7\xf7\xf7\xf3?i\t\xfa\xd7\xd1\x0f\xe3?fb/X\x8b\xa6\xb7\xbfX|\xc3i\xb7\x86\xef\xbf\xa4\xf7W\xfa\xa9\x1a\xf2\xbf\xc0\x00k\xdf\xf6\x12\xe5\xbf\x98\xb0\xd6\xb7\xb2\x87\xf4?\x83\'\xdd\x99_\x9f\xc7\xbf\xa0\xd8\x84\xa3\x7f6\xdc?\xe6\xaa\xa9\xb7\xa8\xd9\x00@\xed\x1e\xbbs\x96\xe1\x12@M/\x88\x0c\x87\x9c\x0c@\xe3\xe0\x1a\xfb{\xda\x00@\xca\x0cs\xdc#+\xb2\xbf\x90SL\xc7]\xae\xef?\x0eS\x04\x10k\xc7\xf3\xbff\xeb\x01\xbf\xdb\xdb\xf1?\xec\x9e\x0f\xdc\xe2d\xfc\xbfZ\x8c@}ki\xd8?\xec|\xf3\xb6\xbf\x1b\xe6?i\xac\xe69""\xcb\xbf\xfa\x019\xfa\xe5\x1f\xf4\xbf\xea\xbbW=\xf9)\xf7?\x81z\xe9\xb8\x11\x1c\xf3?C\x8b\xa8\xc1\x17\x87\xfd?,\x89\xcf\xea\xe7\x18\xfc?E\xe2\xfb\xf7\xe4\x08\xe9?\xdd\x17S\xfbsH\xa5\xbf\xd6\x18kL\x00\x9c\xfd\xbf\xd1 \xb8v>\x8b\xe4?\xcb\xc0\xc1\xcd\xf0\x0c\xb9\xbf_\x1b;\x96\xdc\x15\xf5\xbf>T\x9d\x14\xad\xd0\xb6?\xf0\xefIQ\xe3\xd7\xbfMIZ\xc5<\x11\xd5?\xce=\x1a\xd6\xe3P\xfc\xbf\xdc6T\x88\xc2\x1a\xa7\xbf \xdb\x8dL\x9b\xc0\xe0\xbf\xfd\xee\xe9\x94n\xb4\xff\xbf\xf2t\t{lp\xe3?\xba\x1a\xa9\x82\xfd\xe6\x00@\x8f6#\x8e\xc4$\xe9\xbft\xbc\xbc\x90C\xf3\xe0?S[\xae\xc7mJ\xeb\xbfzwC\x0c\xe8\x9d\xf9?B4V\xe1jq\xeb?u\xeeM\xb8u\xc1\xdd\xbf\xe7]F\x89\x8f\x8f\xe4\xbf\xce\xed(\x19\xbc\xe4\xe1?\x80}\x89\\\xd4\xf9\xe8\xbf\xe5\xce\x00\xf7L}\xd1\xbf\x8af\xd1%\x8fl\xbe?\xf7\x1d\xeb\xd4\xa2\x12\xf0?j\xbc\x0f\xc7H\xcf\xf1\xbf\x04\xfe\xdeU\x8c$\xf1\xbf\x11\x97\xc6\xd8\xfe\xf6\xde\xbf"^\xc0\xf0\xe3\x14\xc0\xbf\x84\\Pt\x95\xce\xdb\xbf\xe5\x9a\xd1\x11-\xa6\xec\xbf\xc8\x9d\xd9\x1cC\x17\xeb\xbf\x1fa\xb1onz\xb1?\x11\xc9\x0cABG\xf8?r\xcd*E\x81\xeb\xd8?[\xee\xbd\xe3\xe5\x93\xf1\xbfZ)\xb7\t\xdd\xeb\xe2\xbf\x13\x87\xcbA/L\x05@%n~\x97"\x94\xde\xbf\x8c\x86WH\xb7A\x9d?=\x1e?\\hd\x00@r\x1a\xe2#?\xf9\xf7\xbf#\xearaRj\xf0?\xb7\xb2 5\x9dN\x01@m*\x99\x02H\xe2\xf3?(\x98\x07\xdb\x9c\xc8\xe0?#i\x17#0\x82\xc6\xbf\xd7I%\xac\x81\xf5}\xbf|\xcc\xbf\xda\xea\x10\xff\xbf\xf9\xc9\xb5\x11aH\xcc\xbfPG\xb4\xf5\xb3\t\xef\xbfhA]\xe8RY\xf1\xbf\xd4~x\xa2\x96[\xcb\xbf6\xe1\xbe`\xd2w\xe7\xbf\x0bX\xd9\xca\xb8o\x01@h\x8e\xee\x8e4z\xf8?\xb1-\x86\r\x93\xf7\xfc?g\xa8!\x82\x9b\xf1\xf6?IS\xef\xdd\x8b=\xb0\xbf\xf5b\xcdJ~8\xd8?\\\x13"\xaf\xf8\xb1\xf1\xbf\x06e;\xd1\x1e\xc8\xe8?\x10Y\x0e\xc9\xf3o\xf7\xbf\x7f`\x98)A\xde\xf9?Wb\x8bH\xdc\x95\xf6?"\xa6\xbd\xb0G\x10\xd0\xbfH\xf9=\x18;\x16\xf4?\xde\xdbw\x9f\xf8]\xf7?\x9c\x10\xf2L\xe3w\xe9?\x02?\xce\xe1\xc9\x04\x00@\xc33%n\xe1/\x01@K\xe6\xe83\x82\x05\xe6?\xd2\xaa\x82;\xc2x\xee?\x8ae Z:\x0e\xcb?\xfeik\x08%>\xc2\xbf\t\x8f\xbap\xe7\xee\xf1?\xc5<5}\x13\xe4\xe0\xbfY\xa8qe{\x81\xcd\xbf\x0e\xb4\xd3\xb4\xcb\xe8\xd5?4\x10\x04q\x8b\xa9\xc9?\xc3\xe9W\t\xdb#\x86\xbf\x14\xf8\x81@\x8f\xec\xba\xbf\x87Y\x8c\xa0\xce`\xe3?\x9d\xc2|\xe2\xc2\x1f\xce?;\xaf\xcc\xaf\x15\xd7\xbc\xbf\xcb7\xaf\x19k$\x02\xc0\x82\xb8\xcb\xb9\xdao\xed?\x86\x98W}\xc8q\xe2\xbf\xf5_\xfe\x14\xb8\x19\xe1\xbf\xfe\xb2\x90\x8d\xd9\x00\xd4?\x07[\xd0\xd8\xeaS\xe3?\xae\x1e\x9eD\xc5\x83\xe8?\xf5a"`\xc5}\x03\xc0\xae\xfe\xdd\xffm\xc5\xe2??\xc1\x13G\xc6\x8b\xe4\xbf\xdfs\x87\x81K\xa6\xee?I\x1ee\xf6D\xb7\xff?\xcc_\xea\xb7\x84\xdd|?\x95\x9f\x8dw\xf0c\xf4?o*+\x08G&\xc9?\x84B9\x1f,\xc9\xce\xbf\x95u\xd4\xb8)?\xe9\xbf\xdcB\'\x9c\xe0#\xfb?M\x04\x16=/\x9d\xec?\nZ7\xff\xd7+\xe1?f\nr\xf1\x87\x18\xfa\xbfP\xf5cu \x86\xe6\xbf\xc4\x13,\xd8\xa6\x18\xb0\xbf\xa0\xa6\x15\xc5\x05v\xf6\xbf[\xa4\x02\\\xa6\xed\xf6\xbf\xf2\x0fe\xed\xb9\x8c\xfd\xbf\x00\x07\x05\xa9\xce\xd1\t\xc0\xd4\x0en\xe7\xe1\xfe\xee\xbfC6c\x12\x05\x95\xf6\xbf\x02\xc8Qcn\x93\xd5\xbfb\xf3;qH\xe3\xf3\xbf\xab7EFMQ\xfd\xbf\x94\x887\xd9Y*z\xbfg\x9bh\x83M\x94\xd5?\x8b\xdf\xd5\xf9\xe5\'\xfd?#\xb0\rv\x86\x9d\xed\xbf\x85\xb1\\|\xfa\xf8\xd3\xbf\xcf\xe9s\xee\xe8,\xef\xbf\x8f.\xd69wx\x00\xc0\xef\xcb \xcf\x04\xb6\xdf?\xb3u,\x194\x86\xe7\xbfDw\xe4?t\xef\xd4?6\xca\x0e\xcd_\x14\xf4\xbf?T<\x16F,\xfc?\x12\x06\x8e\xafR.h?\x18\x9c+-\xf2\xa1\xdf\xbf\x94\xe5+\x11"*\x01@7\xdd\xd3Y\x99S\xfd?\x91\x19-"\t\x0f\xe6\xbf\xf7\x04o\xb4[\x16\xc5?N\x88j\xa3\xd0\xe7\xe3?\xf6\x84\x17"\xe3|\xab?\x80\xd0c\xf3;\xc5\xfe\xbf\x88"\x82N\xca\x0b\xf6\xbf\xa4\x17}\x01\xccy\xf9\xbf\xa3\x04\xea\x1aV\xae\xdd\xbf\xaa\xb6\xf3.\x1e\r\xe4\xbf\x9fFT\xe1\xf7\xbf\xaf?\xd8YQ\xae\xb3\xa7\xf2?\'\xd9d\xda{~\t\xc0\x9cW\xa0z\xa1\x7f\xca?\xa9\x00=\xdd\x06\xf4\xf1\xbfwG\xef+M\x92\xfc\xbf 1WJdZ\xea\xbfX\x8c\x966=\x0c\x01\xc0\xbf\xc4m\xfd\xd77\x11\xc0\x9e#"\xba\xa2#\xec\xbfMu#=ns\x04\xc0D\x07\x9c2\x7fo\xe5\xbf\xcf\xc8\xff\xf5\x98\xf6\x00\xc0\xe6e<\xab\x0b\xf1\xe0\xbf0\x92\x8d3\x16\x0b\x92\xbf\xb6\xda\xed<\xe1B\xde\xbf\x10H\xa5\xc1\xb7\x83\xd7?H\xd9L\r\xbfN\xd1\xbfv\xf9c\xc3\x19\x1e\xf4\xbf\x10\xf6\x9b\xd5\xac\xb8\xca\xbf\x07\x80\xddb\x1a_\xdb\xbf\x8aD\x05\xe22\xf3\xef?p[\xfcvs\xa8\xe3\xbf\xe6CY\xa5\xb1]\xf5\xbf\xbdW\xba\r\x94Y\xd6?7\x8a\xb6;\xaai\xf9?\xe9g\x9f\xcf\x80\xdf\xe0\xbf\x04r\xab3\xd7@\xdc\xbf\x8b\x9a\x8e\xb9\x8b\x9f\xf0?\xa1\xd1X\xc4\xe0\xf1\xec?}\x0f\xea\x87\x15\xa6\xcf\xbf@\n\x81\xce\x19\xa8\xd3?\xcd\xcb1\xe6\x0c\xf6\xe8?9\xde\x82"8\xd8\xe5\xbf"!l\x99v\xa5\xe5?\x07I\x9a\x89|\xc4\xe9\xbf\x8d\x9aq\x88\x8a\x1a\xf8?\xd9_\xe5\x11\r\x01\xf3?\xa9\x9f\xf5^\t_\xdf\xbf\xeb\xd6\xd00y&\xb1?d`k\xa7$\xbb\xe6\xbf\xc1=A\xbd\xf83\xe7\xbf\xc1=\x91\x84\x95\x86\xe3\xbf\xb0\x94\xc0\x10\xf6\xa6\xf1\xbf}\xb9Td8\xae\xf6?\xe7\xcb\xa4\x8c\xce\x86\xa9\xbf\x03\x92\x7f\xec\xdft\xf2?9\x1f\x8cLs\x1a\xc0\xbf\x85\xcbab\xa3\x8d\xdc?ME\x8f\xa4\xaf\x82\xf6?V\xe2\xb7:\x8a\x1f\xe7?/)\x93?\xff\xdb\xec?`\xee\x9a\xf9>\x84\xf5?\xe0&[\xfe8\xcd\xef?4O\x9e4\xd6i\xdc?!\x03\xc7\x88(\xd1\xd1\xbf\x06\x1ezy\xa39\xe7?g\x9d\xfe|\xac\x8f\xd0?1O&7\xe6\xe4\xfc\xbf\x02F\x8a1lT\xa9?4\x80WR-g\xe9?\xe8\xd8\xab\xf0\xfa\xf6\xbf\xbf\x9ek\x08\xf7\xd3\x0f\xdf\xbf\xd1CI\xa78z\xd5?\x14\xd8\xbb\xb2v\x9a\xec\xbfF\x8ae\xe7\x86G\xfc\xbf\xd6\x82BP\x8d\x9c\xe2\xbf\xfe\xbb}\x8bw\r\xe2\xbf3\x96\x18\x83\xf6|\x06@\x07\x97\xcbz\xe1\x8d\xf0?7\xb1K\xe55s\xfe\xbf3,\xd2\xc8\xce\xf6\xdf\xbf\x96P\x8d\xb3X\xc3\xf9?4[\xccO\xed8\xd7?LL\xd6\x92X O\xbf\xd0\xe5\'\xc0\xae\x7f\xdc\xbf\x90p\xaa\xa6\xe5*\xe3\xbfl\x9c3\xc8|U\xef?\xd7\xad?\xc7\xef3\xc3?\xf1z99v\x12\xe3?\\\x9d\tP8-\xbb\xbf\xc2\x01\x07\xd2f\xf2\xfb\xbfa\xe6\xb4\x9d\x90m\xc0?j\x9a\xa9\rU\xa0\xc9?%r\xfc\xbd\xa6t\xdd\xbfF$\x88\xbd@\x1d\xc1?p \x9c\xfc\xafs\xf8?\x8f\x94\x18\x16\x01i\xb7\xbf\xf4\xb5\xedd\xa6\x98\xe9?d\xdc\xd8\x161m\x92\xbf\xf6d!Xx\x0c\xec?\xca\xf7\x8e\x93\xbcG\xe8?)\x7f\xc2\x1a\xe6\x9e\xd1?w\xe4\x9b\xee7\x92\xd3\xbfx\x14W\x07\x1b\x0c\xe2\xbf\xe8\x8d\xed\x84z\x0f\xe7\xbf@K\x92\x85\x14\x87\xcc\xbf\x8e\'\xaf\x906%\xe9?8\xa2\r\xf9_U\x80\xbf\xc7\x08@\xb0-\xa9\xa5?\xa1)\xd4\xf0\xe2\xb9\xe4?\xc2\r\x9d\xa4P\xe8\xfd\xbfM\x92\xdf\r\x99M\xf6?#\xe8\x04\xf87\xf6\x82\xbf\x10\xb3 oi\xd2\xf6?\xc2\xe3\ny\x16\xd6\xe6?\xca\x8a\xe4\x9a\xfd\xb6\xec\xbfA\xdf&u\xabq\xf3?/\xe5\xbde\xf5\xa0\xf1?\xab\xcfR=\x84c\xdf\xbfv\xb7o\xb4\xb0\xcb\xbd?\x19\xe6\xfd+3\x03\xc2?\xf4"@\x7f\x0e\x84\xfd?\xf5\x1f5\xcf\xd5\x04\xeb\xbf\x90n#\xa1\xef\x8f\xe5\xbf\xbb\x1c\x83\xbc\xf6D\xd6\xbf3w\x1b4\xb1\x11\xf0\xbf02\x82x\x0c\xd5\xeb?|,2\x1a+N\xeb?\x95\xecf\x8fJF\xc3??Hs\xd6P\xa5\xd2??\x03\xc6c\xe8\xfc\xe3?\x02\xc2T7J:\x00\xc0>\xd15\xca\xcd\xd7\xef?\xc6\xf7\xbc\xb5\xe7\xca\xe5\xbf\xe9g\xaf\x1b\xbc\xd3\xfa\xbf\xbc\x1by\xb2Y8\xf1?t\x00\xd6\xbfR&\xfd\xbf\xf25\xe8\xc3\x0fA\x03\xc0\xc2\xc0\x8csh\xde\xec\xbf7"\xfbg\x00\xc1\x87\xbf\xd4L\xe6A\\\xde\x05\xc0\x9c\xbc\xf6\xf7\xbf\xbd\xf1?m\x1f\x12\xf7\xf2T\xe0?vG\xda4\xd1\xc1\xe2?\t\x06\xac06I\xe8?{\x83\xe0\xba\xa8\xa8\xf5\xbf\x9371W\xfe\xeb\xfd\xbf\n\xb5\t\xd1r\x04\xc4?\xb4^\xdb\x12i@\xf9\xbfV;\n\xd6h\xdb\x04@\x97^\xb46\xd0\xda\xf7?\xf1#\xf0z\xb2\xeb\xd4?\x06\x1c\xb2\xb0s\x93\xd7\xbf\xfd@\xaa\xe3ty\xdd\xbf\xa5\xaf\x08\xbb\xee\xe4\xdb\xbf\x8dB\xd9\x87\xf1\xda\xcb?\xa0\xe4\xe0\x0bU\xc3\xab\xbf\xc4\x83\xc5\xe4\xac\x83\x01@\x89\xbex!\xc3H\xb2?d\xd0>\x0e({\xcc\xbfP\xc2yU\x9b\xfa\xee\xbf\xa8\xd6\xe0^5\xf8\xf2?G7\xa6R\x8ak\xdb\xbf\x9b\xff\xdd\xcd\\\xc0\xe2?T\xf3D\xb40\xba\xdf\xbf\xee\x1a\xe3\x06y\x18\xf3\xbf\xe2\xc63\x1b\xe0\x80\xf0?\x89+Ux\xc6\xb2\xf1\xbf\r@\xd0\xc6V\xf9\xef\xbf11\xc1\x04\xd4\xcd\xee\xbf|\xf3\x96&\xbb\x11\xee\xbf\xf2\x9a\xb7\x06A\x05\xf7\xbf\xf3!\x84\xc0\x17\xc1\xc5\xbf"\x89\xf3\xaf\x85\xf8\xeb?\xca1#\xfa\xdc\x8c\xe1\xbf\x06\xb3=\x93\xf5\xa8\x04\xc0x,>\')=\xc2\xbf\xf1\xae\xed\x13\x88\x81\xf0\xbfX-\xf1a\xd2)\xe8?S\xac\xfb\x0f\xcfx\xf0\xbfr\xed\xb4pS\x00\xda\xbf\xfaY;\xfd\xce\t\xdc?\xa3b\xad)\x18\xdd\xd1?\x88$b\x93?{\xf3?\x85\xa9\x08Q\x8d\xe4\xb4?\xb3k\x85\x90\x8e\x0b\xca?\x05sKG"\xba\xd0?\xbc\xe4#\x04i\xac\xfd?P\xcc2}\xd1[\xb8\xbfvJo\xcb@.\xf1\xbf\xad\xf7K\x91{\xc9\xb3?\xa6O\xb5\xb2\xf9A\xdd\xbf\x87th]\xba\xad\xdb\xbf\xd4\xb0n\xf6\xc9Y\xd8\xbf\xef\x08\xe2J+\x82\xe3\xbf\xcc\xaf\xa4#\x1dx\xdf?j\xea\xcas5\xc5\xf3?;\x7f\xba\xa6\xb3s\xe4?\xea\xa5g\xbb\xcbn\xc8\xbf=\xfcS~\xa1\xa3\xb1\xbfF\x86\xee\xf6\x81@\xf4\xbf\xb6=>o\xfba\xf5\xbf\x14 X6\xe2\x90\xef\xbf\xf9\x87\xd8(Ag\xe2?\xc5\xb3\x11:\xfc\xcf\xf3\xbf`\xc4\x9b\xcc\xfe\xf3\xe5?0`\xf3>%\\\xc1?8Gp\x82\x03[\xf0\xbf\xd0\x0bv\xe4}\x87\xe0\xbfx\xed\xf5I\xf0\x0f\xe5\xbf\xf1\xa2\xf4\x8c\xf3C\xf1?\xb0\xb7\x82Xs\x8c\xe5?\xb6k~\xc9\xb2j\x04@\x03\xde\r\xac\x01\xf2\xf4\xbf\x07\xba{\x16\x98W\xed?K^\xee\x97\xa7g\xe6?\xef\x90\xe4\x83Lv\xe7\xbf\xf7t\x19\xd7\xaa\x9c\xd3\xbf8\xa9\xef\xd6nZ\xd4?\x93\xed8><\x98\xd0\xbf\x8b\xbb.E\x1c/\xd4\xbf\xf8\'/\xb3\xb3D\xec?(}Pu\xd1!\xf2\xbfV\xdcc\xbe\xa7\xe4\xf0?x\xa24\xb7\x8d\x16\xe2?(\xc5\xfb\xa0\x9b\x80\xe7\xbf\xe1\xc2\xc2\xe8\xb1\xa6\xfd\xbf\xb9D\x80p\xebk\xeb?"\xbd\xe99\x8eb\xe8?v\x8df\x08EN\xf0?YnC\xcc\x83\xe3\xf0\xbf\x8a\xea\xfb>\xc22\xea\xbf\xa0\xf0a;P\x86\xff\xbf+\x8e2_\x96\x9b\xee\xbf_S\x1ap\xad\xb5\xdc?Bw\x1bw3\xc1\xfa\xbf\xb2E\xa4\x98.M\xd2\xbfB\xbb\t\xc7-)\xde\xbf\x92I\xc5\x90\xad\xa7\xf2?\xfb0(H\xcd\x9a\xf5?_\xb4AI\x92O\x04@\x9a\xeb\xbb\xd9\xf9\xa6\xe4?R/\x03<#\x1f\xf5?\xb9\xa0u;\x80C\xe5\xbf\x80\xce\xa7O\xec\xe0\xf1?.*\t0\xe2\xd0\xbd?\xa7\xd8\x89Ign\xf3\xbf\xa9/\x81\x01-\x96\xe5\xbfO\x85-m\xad^\xd8\xbfzQ\x1c\xa1a\xf7\xc4?#\x06\xf8An\x80\xe4?\xad\xf4\xaf\xac\x17I\xb2?B\x0e\x18K\x9b%\xa3?n\xd3\x8c\x80\xc9s\xf1?\xa3\xcek\xc3\xd2/\xf5?[\xe5\xa3S\xa3\xcc\xf3\xbf\xe5\x04G\xc2\xc9\xe1\xef?PA\x98\xb0\xb44\xef?\x94?\x85\xd4`\xa3\xdf\xbf\x0f\xca\x11w43\xf6?\xcf\xf5Ew\xeb\xae\xe4\xbfL\x12\xd9\x01\xcch\xca\xee?\x87K\xb4\xa0\t\xad\xe0?9\x90\xe8kD\xe2\xf0?u\x83\xc8#\xfc\xdc\xd0\xbfF\x1b\xf9\x90\xc8\xc8\xb8?)o\xe4y\x90\x11\xf1?+>\x9f\xe9\xc6\x92\xf3?\x9c\xef\xa7\x97\xbfc\xf4?A\xc9x\\\xa4\xf0\xb2\xbf\x0e\xe6\x1dh)\xdc\x03@h\xf7\x85\xf0\xf9\x1d\xe5?(!\xb5\xa0\xf1\xcb\xe4\xbf[N\xb2[\x0f\n\xd0?\x84+\xee\x07j8\xd3\xbf\xcd\xd9\x8fC\x15&\xb4\xbf\x02_\xd8\xbd\x86\x0c\xe0\xbf\x05\xcb\xd1\x91\x11\xa3\xe4?\xf2*S\xc8\x01\x08\xa2\xbf*N\xf3b\xa4y\xe6?0\xf9\x85\xc7b\\\xf4\xbf\xcc\xf9\xf4@4\xf1\xfe\xbf\x07\x99\x89\x02\x86\x88\xf1\xbf\xb0\xdb\xd61\x1ew\xe9?\xc7\t\x89?V\xe4\xf1?E\xe6\x8cz/<\xed\xbf\x0f\xd4\xb1\xc4\xe2\xd6\xda\xbf\x0f\xc3xz\x1d\xdc\xfa\xbf\xe4}!\x87\x8fZ\xfb?\x82J\n\x9c\xb1\xc9\xfb?\xd0\xac\x131TW\xbb?\xb4!\xd20z\xba\xfa?,\xfd\xc5\x86\xae\xa7\xeb?\xe5^\xef\xa0\xbbe\xe8?_\x87\x11\xb0\xdb\xa0\xba?\xd8\xc1\x01\x10[\x1b\xd6\xbf\r\xbe\xd7\xf3\xf9\x9e\xf2?\xad \x92\'\xec\xfb\x00@j\x81\xb4\x80\xdf\xbb\xe6?\xb9Z\xfem\xf3\xf2\xf0?r\x8a\xa5\xbaC\x86\xde?\xe5*\x19A\xb2\x11\xc0\xbf\xb0"\x1d\x13\xa1+\xf2?v\xfc\xc8\xcb\xa6\xe5\xe7?3\xcf\x0e)\x16\x8e\xf0\xbf\xddC\xae\x8b\xc2\x84\xe8?\xffk\xe3\n\x15V\xeb\xbf<\xd9\xd0D\xb5\x8f\xe4\xbfQ\x8c\xfa\xbe\xc9\xf2\xd8?&\x15\x8a\xd5\x9bA\xfc\xbf]\xfd\x9d\xa6g\x00\xea\xbflU\x90Z\x0cl\xf2?\x13\x1f\xecxH\x89\xf2?\x00\xe7OO\x89\x18\xfb?\x859\xdaj\xdaN\x00@\xd0\xb2Lrq\x11\xd7?ts\x8b\xb9\xbb\x1a\xe9\xbf\x1b\xc0\xb4f\xcfl\x04@S\xb2CK\xc1,\x02@?L%\xb5\x12)\x01@\xb5\x8b\xaf\x83\x01\x96\x06@J\xeb\xcd;\xdf\x0c\xc6?!g6\xceX\x13\xe3\xbf\xa8\x7f\x9c\xab\x076\xd2\xbf\xd9Zpl\x01\xd1\xf9?\xd7uR\xb8\xb7\xf5\xf6?$\x8b\xe3\xc7\x80\x0e\r@\xc8p\xa9X\xee\xde\xf4?\x8bO*\xcb\x01$\xec?F\x1e\xdf\xeaI\x1e\xf1\xbfl`9\x98\xf5P\xe6?\xfb\xa8i\x1d\xc0&\xde?\x9d\xac\xce8\x106\xa6\xbf\xe4\xf5(\xe4\xbf\x07\x02\xc0\xa1\x81\xf1\xce\xccR\xf6\xbf\xae\xcb\xb7\x9d\xab\xe7\xf3\xbf\x8a \xf5n\xd2t\xc7?\xd3\xf4\x86[2$\xa1\xbf\x86\xa0Q!vc\xb5?K\xd1\xe7\x9f\\\x1d\xdb\xbf|x\xbb\x8b\xc2\t\xd5?FC"\rW\x8b\xdd\xbf\xaa\xf4\xbd\xcfx\xf2\xde\xbf\xac\xde\x15"bA\xd2?\xc8E\xbb\xdc\x19\x87\xfa?n\x01|`sI\xf2?Q\x03\xfc\xb9\xea\xfb\xf1?\x83\xe4\xdaj?\xcd\xe5\xbfX\t$\x82\x08\x1c\xe2\xbf\xb9Q\x0c\xf5\xf3\xc1\xec?\xe87\xe5\xe89.\xf4?\x94\x17\xd5,\xa2\xbd\xe4?\x11!.\x1f\x95V\xf0?\xff\x882{\xc7\x99\xfd?d1\xcc\xe4\x89\x01\x05@w\xd92\xfc\x82\xf2\xd8?v\xbc\xa1%\x927\xdc?\xe4\xe8\xc1\xd6j\xdf\xed\xbf\xeb\x19\\.g\xa9\xe3\xbf*\xdd\xb5:\'\x1f\xd7?\x00\xd7%\xa9~9\xe5?~\x04Bj\xd5w\x00\xc0\x13;\x85\xf2\x18\xc0\xf4\xbf\xbb\x8f\xaa)\xb31\xe3\xbf\x87m\x0e+\xc6c\xc6?\xde"\x153*\xd5\xf0\xbf\x7f\xe3\x80\xdd\xf6_\xe0\xbfp\xbb\xb8\x02\xa3P\xbe?U\xce|w\xb1\xea\xd3\xbf\xf6\x9ey;h\\\xc0\xbf"\x19\x1a} \\\xfa\xbf\x7fd\xd44\xab\x88\xeb\xbf&\x8a\xb3g\xba\xff\xf8?%\x88r\x08\x11&\t@\x14\xcdc1\xaeM\xe6\xbf\xab\x84\xb8\xfc\xd8\x1f\xe1?h\x1a\xc4X\xaf&\x03\xc0\xf0#\x81\xde\x89\xf0\xd4\xbf\x13h>\n\xe7\x8e\xcc?ZY\x19\xf9\'m\xed\xbfE\xafwg\xb3\xa6\xf5?\x97XX \xfd?\xf0?r\xd0:\x8d\xe4o\x03@\x04\x15\x05\x06\xd6\xa0\xd8\xbf.\xc6"\\\x13c\xe9?\x9f8L\xdd\x03q\xf2\xbfw\xc9+\xbd\xbf\xf3\xed?O\xfa\xd0\x8atm\xda?\xba\x1e\xe7\xa9\x0f\xdd\xcf\xbf\x8a)\x1eT.\xaf\xc0\xbfB\x83\x19\xf4\xdf(\xf9\xbf\x97o\xe1\xff\x92C\xe1?\xc1JE=\xbf\xad\xf3\xbf\x87\xe0+\td\xfb\xf6\xbfS\x8f\xb2\x8a\xa5j\xf0\xbfT\xa0*\xe9$M\xb4?\xcaH\xa8\xa7\xb1\xfa\xde\xbf\x9aT\xf68w,\xdc?\x9b\x1a\xfb\xe5\x18\x13\xfc?\x15\xb6\x91\x0bZ\x06\x11@.\x81\x03\xc0\r/\x16@<\x1d\xfbL\xb9\x1e\x13@\xb7\xae\x16\xeb\x04\xb4\xe9?K\xb3\x07\xdcH\x80\xd7\xbfB\xc1\x08H\xa57\x04\xc0s\x8c\x90\xde\xbd\xcb\xef\xbf\xe4\xb1g\xa6\xb08\x0c\xc0\xe5vPN\xfeR\xd9\xbf\xf3G*\x11\x1cT\xec\xbf\xcf\xde\x9c;\xe4C\xf6?%\x99E\x00\xb7\xf6\xc4?\xdc\x9d\xc5}\xd0\x10\xf3?\xcb2\xb2O\x0b\x98\xe4?z\x89\xa2\xef\xbe\xe4\x00@\x11O\x15\x8d^}\xe9?|\x8c\xb5\xecP\xbd\xfb?\x90+{)\x9f1\xc2?H\x85|)\x12u\xd7?\x95O\xb7\xa7ex\xfb\xbf\xeb\x9e(\xf5)\xd6\xe7?\x7f9\x07\xf9\x90,\xfb?|\xdbv0ef\xf5?Y\xab\x15\x84q0\xeb?N\r\xf7<\xad$\xda?\x8de\x91zT\r\xf2?\xed\xb8\\\xa5\xdf\xaa\xf0?~\xa9H\xe3\xc8?\x07@\x95b;\x01\xb5p\x10@0\x0c\xc5\xe2!\xcb\x05@\xc1\xe34\x87H\xdf\xeb?\xba\xaa\xcfh\xe0\xbd\xb7?\x02\x1d\x12\x87m\xe5\xb4\xbf\x8aQ\xbclh/\n\xc0\xd9&,n\xd6\x8d\x02\xc0!\xa8\x02\xddw\xea\xd9\xbf\x7f\xc4\xee\xe5\xdc\x1b\xf8\xbf6\x8b\xa3\xb8b\xec\xf9?T\xea\\W\xd2\x1c\xc5\xbf\xaf\xbe\xf3\x0f\xdd\xae\xe1\xbf\xa7\x88\xf6^\x8b\xab\xde?du\x19\xb9\x06q\xe3\xbfj\xe8\x8d\x9c\x84B\xe5?\x8b\xd4\x85\xf6\xce\xda\xef\xbf\xfe]\\\xc5\xa3y\xca\xbf\x8b\xe1sA\xbb\xb4\xe5\xbf\xbaK\x8d\xaaz\x9b\xf8?+\xe7B\xb5\x97\xa1\xea\xbfc\x9dErrN\xe6\xbf\xa1f\x86\x9c{g\xd5?y\x8b\xee|\xf76\xec?\x84q\xd1\xda\xa2{\xcc\xbfHahn\x9e\x9d\xfb?\xc5O,\xb9\xad\x10\xf8?\x86\xbe{v\xc0!\xf1?za{\xc0b\x05\x0f@q\x17\\\xd3\x10\x0c\xf5?F\xfc\xe8\x12\x98\x97\x02@\x1e\xf9\x1dY7@\xd0?\thl\xd6\xa0\x90\x06\xc0\x02\xb7\\W2\xc1\x03\xc0\xd5\xb8T-\xc4s\xe6\xbf8\xad\xa8\xbeG\xd7\xc2\xbf\xe3\x1f\xdb%\x0f\x1c\xff\xbf\xce\xfd\xc5\xca\xec\x05\xee\xbf"\xc2\xc3*(4\xf1?\tA5B\xb6*\xf1?m"\xde:\x96\xa2\xb0\xbfW\xe9_\xcc\xa61\xc7\xbfW4PlC\xed\xbf\xce.\xed\x19M\x07\xfd\xbf~(\xf9Z\x19\xf4\xe3?\xf0.\xfe\x9c\x03B\x00\xc0\xb6\xc7B\x7f\xa1\'\xf5\xbf8\xdb\x82(\xb5\xe4\xb9?\xa9\xe0\rLvR\xe7?oLY#ek\xe5\xbfSKX\xd6\xa1\xa8\xe0?d\xcc^\x19\x0b\x93\x02@\x98\xac\xe9\xdf\xf4\xde\xd5?\xd0\xe1\xf3\xd6\xc9\x9f\xed\xbf0\x7f<\x82\x19.\xe0\xbf0)QX\x05\xd7\xf4?Km\xd0\x9e\xb3%\xd8\xbf\x884\xc8\x87\xc6g\xf0?(\xf1Y\xe3\xa9\xce\x01@g\xcc\x88\xcf1\xed\xb5?z\x95\x96\t\xef&\xd8\xbfMSH]x\xf2\xf8\xbf\xfa>M\x10$/\x01\xc0\x97;:\x8f\x85z\x05\xc0\xba$\x8d\x19\xc6\xd0\xf6\xbf<\xe2\x1a\x7f\xec\xab\x01\xc0`\xa54M{\x06\xeb\xbf\xb6\x0b\x06\'\x0fj\xfc\xbf]\xf0\xae\xb3P\xa3\xd2\xbf5\x9c\xcb\xff\x8d"\xf3?\xf4\'\x16=\xafi\x01\xc0)\xe4\x11o\x13\x97\xf4\xbfiE\xbcr\x95\xb3\xf5\xbf\xa2\xa6O\xa7[\xa6\xc7\xbf\xac\xa4\x0c\xd9\n\x0f\xe1?\x7f\xe3:\xc8x\x89\xe2?\x1d\xffdJ@\\\xd7?\xeeK\xfft\xe9S\xf5?\xd8\xc7\x95\xd1R\xd7\xdc\xbf\xb0Hq\xa5o}\xd6\xbf01I\xd3\x8ci\xfa\xbf\x9f\x15/\x01\x9eB\xf9\xbf{m\x9d0"\xd7\xc7\xbf_KG\xe6\x97\xf7\xff\xbfl\xbeY\xa0U\x0e\xea\xbf\xed\xd6\xa6\xa8\xcd\xc8\x03@\xf6R:\xcad\xf2\x02@\xbf\xd7\x9d\x197\xad\xe6?r\x19y4\x90\xdc\xd2?\x93A\x02~Ic\x07\xc0l\x0c\xeaB\xf6\x1c\x01\xc03\xcc\x11\xa0\x0e\x7f\x10\xc0^3\xf9\x81\x94*\xef\xbf2\xac\xd0w\x80\x1a\r\xc0\xb9\x86\xe5\xfa\xb4\xbd\x07\xc0\xe4K\xbf.\xd2\x13\x03\xc0\x96]\xb9Dr\x95\xd3?\x9b\xbf\x99\xc5\x83\xb1\xe9?\xef\xb4\xe6\xd1\xa7\x95\xe2\xbf\x8a\xbcvL\x0b\xf3\x03\xc0\x8dQe\x88lg\xee\xbf\x80\xcc\xe5%R\x9d\xe0?\xa7Q\xc2\xa2\xdf\xc6\xd8\xbf\xaa\xfc\xa1\x13\r\xb5\xf7?B\xba\xff\x02l\x8f\xe0?f\x8d\xef\xa6\xa6\xc7\xea?\x9c\xbf\xb4+*6\xfd?\xa3\xf1\x1cR\xf9\xcb\xef\xbf\x82\xec\xd0\xbeq\xbf\xc6?\xb4\xfa\xbb\xa0F+\x03\xc0{\x13\xfd{\x07_\xfc\xbf\xa0W%\xe0\xa35\xf0?j\xe2x3\x01?\xcb\xbf\xd5i\x8a=\x84"\xbb?R\x1f|\xff\xfd\xee\xf2?\xe7\xb5\xa0\xd1Dn\xfa\xbfZ\x8e^\x9c[;\x11\xc0)\xfb\xff\xf8;\xbe\x15\xc0|\x93\\\xab\xb7\x07\x04\xc0f\xee\x97\xc6\x96\xd3\t\xc0\xc8u\x97y\xd3\x01\xc1\xbf\xaeN\x0c\xdd\x8du\xe1\xbf\xb3gH+\xb2\x03\xfc\xbf\x9f\x00(\xb7\x07\xf6\xc1\xbf\x81\x8c ;\x83\xc7\xa3?\xefx\x14f\xe2x\xd2?\x9c\xa4\x1et\xeaM\xdd\xbfLj\xadf\x081\xfc\xbf\xe0\x12?\x86`\xd2\x01\xc0\x82\xd4\x8c\xce\xe6(\xc8?\x9c\xe1DG\xef\x03\xfc?\xba\n\x98\x01\xd6\x07\xe4\xbfd>H\x9b\xcf\xb7\xcc\xbf\xc4\xba\xfa8\x17\xd8\xed?\x81\xa4\xfd\xea\xd9\xbe\xf3\xbf\xdb\xc7\x9a\x0eX\x89\xde\xbf\xfd\xf3\x8e\xd2a\xe7\x03@]\x9a\xb5\x96\x89\xc7\xc5\xbf,\x8e\x1b\xc9\xff\xaf\xe9\xbf\x1d\x8a\x81\xb1\x08O\xfd?`$J(6\xa9\xe0?\x80\xd0u[z\xe3\xe1?\x92~\xe4R\x02h\xf0\xbf\xb7\xb4d4\xd3a\x01\xc0\xb5G\x8d1\xbdY\x16\xc0\x14\x14\x8c\x0b\x81\x98\t\xc0*I\xc6P\\\x98\xf7\xbf\x94R\xe1t\x14\xbe\xf3\xbf\xf8\xd8-d\x98\x00\xf3\xbf\xa3|\xc0\xcd\xbe&\xfd\xbf\x9aw\x1e7\xa2\xb7\xa1\xbf\xad@\x0f\xa5\x96\xdb\xd8\xbf\xd2\x1dPO\x17\x9d\xdd\xbf\xa39\x9a\xe9\xc0r\x03\xc0\x8a9^\xdc\xb9S\x05\xc0-\xa9}\x05Z!\x07\xc0\x01[\xefGq\x87\xbf?\xd5\x89C\x8b\xd7\x9c\x00@\xbdEX\x13\x13\x94\xbd?}\xa6y2OF\xcc?\x1e\x8b\x93\xf7\xc7\xe5\xa0\x98\xbc\xbf\x1a\xba\x8cf\x1c\xf5\xef\xbf.b\xeb\x1ch4\xf9\xbf\xb7/n\xdd\x01I\xf4?\xcd\xfc\xa0+\xffN\xf0\xbf\\=\xfa\xa5\xef\x95\xf4\xbf\xa0\xb2\xd7||\xdb\xde?#y(\x1a\x9ci\xf5?\xeb\x9er\x8a\xb0\xcf\xeb\xbf\xd5\xcd:\xd0\x9f:\xd2?\xeb\x01\x8a\x9cr\x7f\x07@\x16\xa8\xdd&j\xb3\x01@\xee(\xd0\x86\x9e\x15\t@M\xa6\xa9\x11\xcd\xc7\x00@Q\xe8\x8c\x03\xa7#\xf5?`\xfd\x10A\x93\x85\x00\xc0{?==?y\xfe\xbf\r\xc7\xd7\xce\x9f/\xd2\xbf\xf7&\\\x17fY\xd5\xbfS\xda\xc4!mR\xc4\xbf\x07\xe6\x0b0\xe6v\xeb?\x04\xfcI\x9c\x05\xa8\xe1?\xc5\x97\x97\xf8\xaa\xe1\xf4?\xf7\xfc\x01\x06T\xd6\xf1?%t8\xa9\xa2\xd2\xff\xbf\x14\x91b\xbb\x1f;\xf2\xbf\xb2\x8a\xd4t \x8a\xaa?\xf2\xd5Z*\xb8\x95\xe3\xbf\xa0n\x1e\xc6\x82\xd7\xfb\xbf\xac\xd6*\'6\x9c\xf2?\xe8\tJ:\xf2\x16\xf3?w\xb9\xc7\t\xf7i\xf3?\xe7m\x8d\xab\xb3\xb1\xb8\xbf\xb4\xc0\xf7\x9f5\xde\xe7?\x87\xf9\xa9\xff\x13\x0c\x01@S\xf9\xe3\x9f\xe5\x1a\x06@\x85\x7f\xae8[v\x02@\xe5\xb4\xf4\xa2F\xca\xa8?tso\x03b\xb3\x00@\xdf\xdd\xc3\xa8\xdf\xee\xe1\xbfkMa\xb8\xb4\x89\x0c@j\x98h4\xc0\x03\x01@\x9b!\x97-\x7ft9?\x91-\x1089\x12\x05@\xec\xbc\xa0\x88\xf5\x94\xaf?\xcfx\x8f\t\x13L\xef\xbf|\x9b\x9f\x1ec\x98\xf5\xbf\x97\x1dgz\x1f\x83\xf4\xbf\xdeR:\xe3\xbb\xed\xe2\xbfH\xb8-\x95W}\xe2?lI\x8d\xf5\xc7\x94\xf1?\x7f\xa3w\x9d\xe6\x82\xfe\xbf\x0f;=96L\xb2?+\xd1#2\xbel\xee\xbfy\xb0\x07\xf1w\xdd\xcd\xbfT\x1bO\xc9\xef\xc7\xf0\xbfn\xdbV\x0c8\xdd\xed\xbf\x95\x9cd\xb3<6\xc4?w\x88\xa5\x15K9\x03@\x03\x0c\x1c\xd5d8\xf2?\r\xd7\xcf~\\\x94\xa6\xbf\xf8\x87\xae\xf03\x8b\xec?(j&a\x84\x95\xde?\xed.\x0c\x8f+\xab\xe7?\xf7jt-?\xd0\x00@\'\xba\xef\xed\x82\r\xf6?\xdb\x1fD\\I\xa4\xf5\xbf9\xc9\x8d\x96\xd94\xe7?\x1e\x97E\xb7\xb2\xa0\xf5?\x9c@\xbd;Ts\xf1?\xd6A\xa9\xbc\xa8\xca\xfd?\xec4\xe3Zc1\xfc?\r\xcc,\x17;\xa9\xf1?9\xf7p\x1fq\xa5\xf5?\xd0\xe1\x87{.l\xd1\xbf\xa5\x0e<8\'2\xe4?\xc7\xd6\xc3\xabN\xc9\xc9\xbfk6\x9c4\xef:\xf0?\'\xeb\xcdQ\xf1\xeb\xe0?#f\x96`g8\x04\xc0\xb0\xce\xdd!t\xa5\xe1\xbf\x97\xd4\x05\xa3\xd8\xa3\xe7\xbf\x1b\'\xf1\xd4\xb9\t\xdd\xbf\xe0\xe3\xd1pz\xd5\xd9\xbfT\x02G\x90\x17\x01\xdb?<\xff\xe1\xedU*\xb2\xbf$;u\'s\xb5\xeb?\xd5\xcf\xdc\x1e\xd6M\xff?;W\xef\xd4\x82+\xeb?\xaah\xe7\xdcSB\x99\xbfT^\xf0\xfb\xac\xbf\xf0?3\x16\x01\xf4 \x03\xfb\xbfi5\xa84}R\xb9?\x1e\xc9\x99\xce\xb7\xb6\xf7?\xda\xa9?\x07\xfd\t\xcf?\xa3\x17\xaa\xcev\xeb\xef?\xf5?\xaf\xa9\xf7\x04\xc2?\xf8u\xcb\xba\x1c\xbc\xf5?\xa8g\x91\xd1\xe1\x1d\t@.C\xb9\xfd\xc1|\xfc?\xfe\xe6\xb3\x0f\xb0\x0c\xff?\xdf_x\x14\\7\x06@\r\xef!\xd4i\xb7\x04@\xcdr\xf7\xa9v\x8b\xd8?B\xec\x85\x93\x97A\xf0?\xc0\xba\xb1o\xaa\x9c\xd8?8 \x12\x05)L\xaa\xbf\xd9\x11\x82\nI\xa1\xc9\xbf\xc7\x1d\xb9\xf5\x07\xd7\xf0?P\xbfy?\xd5\xb1\xb0?\xc3\xbc\x9bd\x99\xc9\xde\xbf&\xab\xd0\x98\xbag\xf1\xbf-\x05T\x053\xee\xca?\xc1\xa5\x19\xd9\xab\xc5\xe0?d\xde4\x9fsE\xdc\xbf\xdbS\xcc\xf3\xa3\xb1\x05@\xee\x03\x97\xaf\x9d\xdb\xdf?\x1e\x96}"\xccl\xf4?\x05S\xec\x81pS\x00@\xa2Q\x85\xfd\xcb7\xe9?\x89?\xfe\x10\x97\xcc\xf5?\x92:\x8f\xca\xba\x01\xd4?:\x19\xd5\x0c\xd6\xdb\xc0\xbf\x8c\xa8\xaeE.\xc2\xd1?\x97\xb9\xfc\xc8\xa1\x80\xe0?\xc1\x0e\x18O\xa5^\xf3?\x82SM\xaa`_\xe0?]\xcc\xe5LhL\xe8?\xa3\xb3\xc8}\x81t\xd7?\xbe\xd5\x8bi\x02\xb5\xf8?\x8e\xa1n6\x8f\xf8\xed\xbf:-\xfc\xcaJ\xfe\xed\xbf\x8bB\xd4\xe6\xdc)\xe6?\x92O\xdf\xe3\x8fo\xc8\xbfmr\xea\x16\x81\x86\xf0?\xf9?O\x907l\xd0?Qd\xcbT\xfdb\xf1\xbf;.\xe2\x04\n\x05\xe6?Y\x1b\r1\xc1\xd0\xe2\xbf\xfc(x\x0eQP\x00@J\xb8\xa5\xdf\xf3\x15\xf9?Tf"\xbc\x89\x86\xea?\xed\x01\x13\xbb\xd2\xd7\xea\xbfL>\n\xcb\xa1R\xf2?\x91\x06\xf8\x8brv\xb9\xbf%8\xcb\xb0\xb5x\xf7\xbfdM\x8f\xfa\xecv\xbe\xbf\x89ZX\x90h\xf7\xec\xbf\x85B\x85o\x80q\xc7\xbf\xc4\x03\x19\x17\xc0w\xc4?\xac\x12\x06\xdcV\xee\xe8\xbf\xb8.\xd54\x03B\xd1?\xe1N\xec\x15\xc6\xf8\xf1\xbf7\xa9\xb78\x9f\xd6\xdb\xbf\xf0\xdd\xcfj\x7fy\xf6\xbf\xcc\xad(\x86\x035\r\xc0+\x96\xa8\x91|\xe0\xb3\xbf\xf4\xe0\xe6H\xcf\x10\xdb\xbf\x88,\xb8<\xbe\xb0\xfd?0ko\xac\x0c\x04\xd4\xbf\xde\xe8b\xc4H#\xf1?\xff\xe5?\xa7\xcd\xcb\xe6\xbf\x1cS~\x91o\xea\xd8\xbf\xb7\xae\xb3G\xa8\'\xa0\xbf\xda\x02-\xd8\x11\xf6\xd7\xbfY\x82\x91lY\x0e\xd7?\xbc\x02{\x01\xa0\xfd\xd8?e\xe5n\xa5\x89!\xee\xbf\xf8\x05J\xc1\xe1S\xe2\xbfB\x1b\xd1\x04\x84\xd4\xf1\xbf\xc1\xc1\xf5\xb4\xe3\xe7\xf1?@\xd2\xf9K\xb5\xed\xfb?]\xe9\n\xcc\x97\x17\xde?\xb8\x86\x04\xd9\xa4\xf9\xd3?\x9fR\xfa\x96\x1e\xb8\xe5\xbf C\xa7K|\x14\xcd\xbf+%\x949\x99\x02\x9e?\xbf\xc4\xcb\xb1T\x8c\xf8?\xf7E\xc8\xcd\x95\xf0\x01\xc0\xbb\x93C\x1d\x7f\xbb\xdf\xbfC\xcckd\xbb\xe2\xf3\xbf\x1e\xdc\xfe\xa7\xf7/\xe5?T\xc4\xac\xdd[\x91\x01\xc0\x9e!\x91~\xf8r\x01@\nCY\xc7\xd7\xc2\xf2\xbf\xe7w\x9fQ\x9a\x85\xdb?0\xeb)0V~\xd4?\r\xb7\xb7\xbd\xe2,\x04\xc0\xbaC\x04\xa9\x97X\xfd?d\x9e\xd5\xf0/o\xf5\xbft\xfd\xd1\xe3\xdf\xaf\xb7?\xbcL\xc8\x04\x05\x07\xf1?\xc7\xdds4\xd35\xfc\xbf\xb5A\x08/\xab=\xb0?h\x1c\x03&\xbb\xf3\xdc\xbf\xe2\xbe\xa8\xa8\xa7u\xf5\xbf#v\x007\x12\xae\xdf?\x16\x9c\xcd\xe1\x7f1\xfa\xbf\x92-7e\x80\xaa\xed?D\x82\x01\xcbp\x94\x00\xc0\xc0\xc9\x99\xef\xffp\xdd?\xa6\x98\xf6AJ\xc7\xd0?\xf9\xc3H\x93\xb1~\xfa?\x80 \xacr{d\xd0\xbf\xf3}\xf1mK\xad\xc4?\xe9\xb6\xad\x06\x1b\x97\xe4?\xe8\x1f\x11\xe7\xdd\x1a\xdd\xbf\x82W\xf4\x19J\xd9\xfa\xbf7\x92\xab_\xcag\xf4?\x08\xdc2\xa9\xc1\xfb\xed\xbf\x06\xf0\x9e\xf3\xa1\xba\x01@\xa2\xbd\x1a%u\x18\xce\xbf\x1c\xf0\x94\x82vj\xe6?\xe4\xbf\xe3M\x8b\xa8\xd2?\xe0\xd7\x8e\xbfw\xd3\xe1\xbf~V\xec\x1e\x87\xab\xf0\xbf:>\x9f\xdc\xdc,\xcc\xbf\x01\xff9{\xea\xc1\xff?x\x1d\x92\xcfpj\xd4?n\xa8\xf9@9t\x03\xc0\xf9\x96#kOF\xba?\x87\x8d)t@\x99\x8a\xbf3\xbe4.\x1b\xf0\xf2?\xab\x9c\xcbD\xe4\x10\xd4\xbf\xc5\x91\xfc\xe0h\xc3\xc3\xbf7\xef\xd35Ym\xc4\xbf\x8e\xfe\x95\xed\x84\xd8\xf4?\xba\xfe\xd5)\xe2\xf8\xbd\xbf\x88\xcf\xae\xde\x14o\xe6\xbf\xfb\xfe+\xa3\xe1t\xfc\xbfB\x9d\x88\x9eq\xf2\xf2\xbfT\x17A\xc0\xa8\xe2\xf9?7%\x93\xe3\xb7,\x95\xbf\x83\x8bopfl\x06\xc0\x86\x05\xce|(\x0e\xe4\xbfpnW\x15}[\xf2?S\xca\xeala\xd6\xd5\xbf\xb1\xc3*\xc8\xd2\x86\xd4?\xd3\xa2\x03Y\xc5Um\xbf\xcc\xabX\x9e\x82\xbe\xdd\xbf\x0f%~\xd0Z\xdd\xf2\xbf\x9aZ+\xb0nd\xcf?-\xa9\x89\x1c\xc2\xeb\xe6?\x06\xa0\xef\xd1\xe0\xf7\xf4\xbf\xd9\x10x\xb1\x81u\xfb\xbf\x84X\xa06\xfe\xe3\xf6?B\xd2\x80\xe3\xea-\xf7\xbfL#\xe5\xa8\xfc\x19\xdd\xbfx+\xd5H+!\xcf\xbf\xa9\xc4\xc0|\x85?\xe2?\x92`3\xd9\n!\x00\xc0\xeeDh\xc5\xa6\x93\xf0\xbf\t\r ;\xfc\r\x01\xc0\x1f@\xb8\xda\xec\x02\xe1\xbfm\xb7bA\xba\xc5\xe1\xbf\x90\xbb\x85+\xfa$\xe2\xbf\x94i6\xb0\xa4X\xce?XUfN\xed\xba\xea\xbf\x7f\x81v0\xb0\xf9\xc3?\n7g\xb5(E\xd7?\xb0:\xab\xb56\xfb\xfb?\xdd\xf28\xdf\xa8\xd4\xe3?\t\xc7\x93Go\x81\xee\xbf\x935P\xb0\xd9\x9e\xf0\xbfM\xc9+\x91\xb7\xf5\xdd?\x9e\xf0H{\xdf\xc7\xbc\xbf\x19o\xb0\xf5A\x8b\xf0?\xf4\xaf\xf2\x0b"\xff\xd1\xbf\xb3\xb7/L\xd0\xac\xf5?\x89Q\x90\xc2\x1b\x07\xdb\xbf@\xe3~\xfc7x\xb0\xbf\xe0$\x90\xb4L^\xf2?\x1e\x19t|\xab\x93\xf1\xbf\xf3\xd0y\x9d\x9b\x18\xf1?\x1e\xcb\xd9\x9b\xfd\x07\x02@^\x84\xeb\x9f\x92\xf2\x8b\xbfd\xacda\xd7\xd1\xf3\xbf\x0e_\xa1\xbfE>\xf3?|\xb0\xdcKk\x00\xdc?\x0b2\x0b\x9bm<\xd3\xbf\x85\xd3\xa2\x9c[\xa5\xe0\xbf\xb7/\xa8\x16a\x8e\xfa\xbf\x92P\xf5v\xd7\xbf\x01@,\xcc\xb3\x9dE{\xfa?\x1e\x99#\xfc;\x05\x00@\x19\x066\xb5[x\xe5?bT\x15\x99A\xb9\xeb?\xe3\xdf=\xa8-\xf1\xd9\xbf\xc6\x83\x14\x8b\x90\xe6\xe1?\xf8{\xbal\x8f(\xca?9hm\xb15&\xe0\xbfK\n\xe0\x10{\xf6\xe6\xbf\xdbS\xa4\x99\xf94\xf2?\xcb,\x8c=)\x9b\xf5?\xca\xb0@\x81`X\xe8?\x0eE\xbc\x13\x16J\xd8\xbf/)\x05\xf4|`\xf7?b\x92\xdaVZC\xf9?\xee \x82\x0c\x95\x11\xcf?E\xaeM\x92\xdf\xfb\xf7?y\xea\xbe}\xf6\x1d\xe5?\xd7\xe3\'\x96el\xe9?w>\xf0\x92X\x04\xee\xbf\xc7\xd0\xb7\x9e\xd8\x83\xfe?\xbbTT\xcb!\xe3\xd3?7\x9d\xe2\\\xb26\xc0?k\x99|\x13l\xe2\xde?\tk\x9b\x9b\xaef\xc4\xbf\x9dM\xb1\xb7I"\xf5?\x9e\xd5i#\x00K\xf0?\xcf\xfc\x87\xd2\xf8\x89\xe6?Dy\x8b/\x07{\xba\xbf\xc4\xb2\x94#\x93u\xd1?\xfet\x8d\xd9\x954\xfe?\x8b\x0b\x97\xc5\x90i\xdb\xbf"\xfd;S4\xb9\xa7\xbf[j\xab\xcc\x07\x1c\xf7?\xc8\x803[\x08\x83\xd6?Hq\x1f\xc0\xb8\x8e\xeb?\x98\xf2>\x17\xc6\xf0\xf2?P\xa3T\x93H\xe8\xf5?*\x0e\x91\xce\xe0\x03\xf1?\xd2\xfa\x0b\xcc\x8c%\xfa?Zf\xe8\xaa\x0f\x97\xb0\xbfo\xc1\xb2\x9f\x05\xc8\xee?w\x80\xa0\x1a|J\xf9?\x05\xad{\x06\x01X\xfb?D9\x9c\x16\xbb\xf4\x0e@\xbf\xbfs\xd2\xcaO\xf9?y\xa8\x17\x11\xfd\\\x07@\xa9\x1f\x136w\x8c\xda\xbf\x1b\x16\xc6\xe0\x16\x0e\xe8?;\x9b\x18\xdb.\xb8\xd1?x\xb1\xb1F\xe4\xd0\xde\xbfB\xe7\x16\x1d\x98\xa5\xe4\xbfU\x9f\x9az3\xee\xc2\xbf\x06\xa09Tj:\xf8\xbft\xabN#l\xfa\xdc\xbf\xc6\xec\xda^\x14\x9c\xfe\xbf\xc1mZ\x0b\xd2\xaf\xf4\xbfD\x0f\x8e\xcb\x86\xda\xd8\xbfq\xa7\xba>&\xcb\xc6\xbf\x1b\xdb\xadcH\xb3\xf7\xbfFl\xf8\xc6&\xbf\xaa?z\x17\xfd\x94bN\xe2\xbfc\xc4m\xed\xeb4\xfc?\xcd\xbdV]_e\xf3?f\x7f\xa2\xe8\x0f\x13\xda?V^.\xb9\x9c\x10\xf0\xbf~1\xac\xd4m@\xeb\xbf)\xdf\x98\x11\xc2\xa6\xf6\xbf\x97\x1c({q\xfe\xfa\xbfn\x94\xedF\x8e\'\xf5\xbf1\nM\x0cl5\x84\xbf\xc2\xfcJ\x85\xd9\x02\xc4?\xd40\x9d\x08\xd3\'\xbd?\xeb\xbd\x05k\xb7\xa9\xd6?x\xbf\xac.\x87\xe9\xf5?\x1f\x04\x1eU\xdf\xee\xe3?!*[;\x16a\xf3?0\xd2\xab\xe1,]\xd5\xbf\xddz\x9d\xb4&\xaf\xe2\xbf}\xaf~\x17\xee\xea\xf7\xbf]X\xf0#X\xcf\xfd\xbf+*\xca\x1e\xfb\xe9\xdb?\x82\x8f\x07\xa6\\Q\xe5\xbf\xff`\xd8[\xa8\xdc\xb3?{;,[\xba\xe1\xeb\xbf\x93\'NN\x97\xf2\xf5?\xbb\xeb\xdbC\xac\xf8\xa2?\xf8\x0fS\x94\xad\r\xf3\xbf\xc4g\xdde\x80\xcb\xfc\xbf\x1e\xf3\xb8\x92\xf7\x91\xd7?]\x1c\x17\x19\xa2\xb8\xa2?\xac\xdf\xf0`\xe1\xbb\xe1?\xb8\x94V\x0c\xc6g\xf1?\x19\x15\x7f\x13\x8d\xb5\x08\xc0_\x0c\xd2\xf8"|\xf2\xbfEQ\xf6\x1b\x1a3\n\xc0 \xb4\xf9\xb2\xaa\x8a\xfb\xbf&\xdc6.\x0e\x81\xfb\xbf\xc8Q\x8a\xbc\x81!\r\xc05\x19=\xef\x8f>\x11\xc0\x80su\x87\xa3\x8d\x02\xc0m\xc7$%\xa7\xef\xec\xbf/M\xfa\xdcv^\xf0\xbf\x94\xe6\x93;#\x11\xdf?^\xc7\xd4\xb5-B\xe8?\xd8\x86m\xcawQ\xfb?J\xcf\xcc\xab\x0b\xc6\xe4\xbf~\x9eB\xa4\xcc\xa2\xf5\xbf.$)_8\x15\xcf?b\xb8[j\xbac\xc8\xbf\x1c):&\x90t\xdd?-\xfa\xb8t\xc1\x9a\xfd?\x02\xc2?D\xac\xf2\xea?\xd8\xcef\xb5m_\xf5\xbf\x8e>\r]J\xdc\xd5\xbf\xba\xcd\xa8%(\x92\xf6\xbfS\x88\x11\x9f\x0e\x1f\xc9\xbfc\xe2\xba\xb2n>\x05\xc0\xee\xc7_\x99\xa9D\xf4\xbf\xfd\xf7P \xea\xe1\xef\xbf\x8c\xfe\xf2\\5\xbf\xf8\xbf\x81\xc1\x9a\xa4\xdb\xc5\xfb\xbf\x184\x19\xa3\x19K\x04\xc0\x07\x89\xbc<\\\xee\xfd\xbf%\xe6D\x00\xa9\xf1\xf4\xbfF,\xa5\xbfl\\\x05\xc0\xc7\xd4O\xaf\xa2_\xf4\xbf\xd6qW\xff\x9f#\xfd\xbf\xf3\x0ck\x16ul\xfb\xbf\xe4\xef\xc6\xbd\t\xfc\x0f\xc0\x8a\xad\xf1<\r\xae\x0f\xc0\x03\xb5p\xce\xedY\xeb\xbfk|\xcc\xa0f\xdd\xd1?;P\x1f\xed`\x0e\xcb?\xcdo\xa9~\xeb\xa0\xee\xbf:\xe5\xc6\x8c\xd5\x13\x03\xc0\x14\x10\xdb\xe0\x89h\xf3\xbf\xf1\xa1Q\x8d\xffz\xea\xbf?\x07\xc5\x9d\xf02\x02\xc0%\x7f\xbc\x9cy\xf2\xe3\xbf\x88\xabW\x10<\x04\xb1?\xce\xa5\xea\xbea!\xf0\xbf\xe0\xf6Y\x99\xed\xb3\xd4\xbfy\xe5L\x89\xcbg\xf5\xbf\xe2\xbc\xf8\x07Zn\xff\xbf\x0f]\xc6F\x08\x91\x0e\xc0~\xbd.V\x89\xd7\xf8\xbf@\xb3\xf3\xb2\xa5\x0c\xf9\xbf\x17\xd2\xa0\xfb\xc8\xda\xd9\xbfA\x05+a\x01\xf0\xe3\xbf\x14x\xec\x95Y\x8d\xf2\xbf\xe8\x9a\xf5 \xe3\xe7\xf7\xbf\xe8\t\xda\xda\xbd\x05\x00@Y~$\xa3\x9b\xad\xe4\xbfp1\xcdO\x7fR\xf1\xbf\t\x10\x98~`\xc1\xe6\xbf\x15\xf9+\x9ce8\xb4?\xf6K\xa6>iW\xf7\xbf\xe2\x89\x1e\x81C-\xe4?\xf3z\xa0!D9\xf1\xbfm\xc9\x0e$\xa2\xf4\xe2\xbf\xbc\xdbnv\xeb\xc6\t\xc0\x8e\x08\xdb\xe9\x19\xb1\xf9\xbf\xff\xed\x13D=7\xf0\xbf\\\xbe0\x93\xa8|\xe0?1\x17\xbc\x11\x96\xbc\xf8\xbf\xe6f2\xfb\x7f\xe9\xeb\xbf*\xf2~k\x9b\x99\xf0?\xca\x18\x128tV\xee?]\xf5\xb2\xde7\xc6\xdb\xbf\xa4\x15/Vv\xa7\xf9\xbf\x16S\x81\xd1b1\x08\xc07f\x8d\x94\xc0/\xe6\xbf\xef\xce\xd0\xb3\xc3\xc0\x0c\xc0\x90up\x8c\xd5\xb1\t\xc0\x0e4G\xc9\xe9\xa2\xf2\xbfK\x04\x10\x89e\xd3\xf3\xbfWS\x86\xa8\xcc$\xf1\xbf\xb2r$\x89\xae\x01\xd2\xbf\xcc`\xc3\x01\xf9\xce\xff\xbf\x7f"\x05\\\xeb\xaa\xef\xbfR\xd5\t\x07#\x90\xfa\xbf\x1b\xf8\x7f\x902\x19\xfc\xbf\xa4c\x15\xd9\xff\xd4\xf0\xbf\xa8\xba\x01\x1f\xecG\xff\xbf\xc2=\x18\xc9V1\r\xc0U\x85\xc5K\xcc\x8b\xf1\xbf\x86\xa1P8wJ\xf9\xbfu\xb8\xa3\xe24H\xfe\xbf\x07\xa1\xde\xc6W\xe0\x04\xc0t7\xf0\x85\xd9\xe9\x11\xc0\x93*\xd2\x03\xa0\xc2\xff\xbf\xf8O\xb3\x7f\xe9\xc2\xd6\xbf.\xa9\xf6\x022\x02\xf1\xbf\xc0\xf0F\xfb\xb2\xd6\xf3\xbf\xd9\x03Mbj\xcf\xc6?1)_*\xf2$\xf0\xbf\x0b\x90\xc52S5\xf2\xbfgY}\xd9\x13\\\xff\xbf\x04\x89b\x9e9e\xf9\xbf/<\xd5\x1ae\xe8\x00\xc0\xac\xa0\x16\xd3\x138\t\xc0\xd2W\x00\xcf?\xe6\xfe\xbfD\x8b\xd6p\x0c?\xff\xbf\xed\xed\xdd#.\x97\xc6\xbf\xa3r\x19l\xeb\xb2\xf4\xbf|\xdb\xb4R\xe3\x86\xfe\xbf\xbf\x05L\xed\xf8A\xc6\xbf\xc1\x08\xaf\xa2d\x11e\xbf\xe7`\x10\xa6\r\x0e\x01@Q3*\xf8\xe2\xc1\xf4\xbfIa\xc2\x1dm\xcf\x10\xc0\xa4h^\xach\xc6\xea\xbf\xd2&\x88\xd4`\r\x03\xc0\x1d88\x88ab\x05\xc0\x88^#\x0c\x80C\xfb\xbf\xf4\x1b\xbb\x99\x81+\x01\xc0\\2\xed4m\x1b\x0f\xc0\x81\xcfx6,\xc3\x01\xc0\xf1\xc2\xcf6\x82h\xf9\xbf\xe9;C\xf0\xea\xc8\xff\xbfih#\x1cA/\xce\xbf\xcf{!\x82\xb7\xd7\xf7?\x91\x17\xbdb\x96\x8d\xff\xbf\xe5\xea\x1dT\x13s\x04\xc0\xd6?\xbe0\xff\xea\xe4\xbf1A\x12>\x98\\\xef\xbf\xd3\xea\x00\x87\\H\xd5\xbf\xb2\xf8\xde\xc1\xee\x1c\xff\xbfVs\xe6b\x0cr\x00\xc0\xa9-\xd6\x9f\xc4\xd6\xe7\xbf\x08\xfd\xd5\xd8d\xc0\xf7\xbfl^\xd8o9\xfd\x0e\xc0Mq\xceRb\x92\x00\xc0\x93\xd7\xff\x05\x1e\xfa\xf2\xbfS\x1f\x0b\xe9\\\xf5\xfb\xbf{\x168\x94=e\xee?\xad}N\x8d8Z\x06@\xca\x05\xfbp\xab\x13\xf5?0\xff\x10V\x028\xfd\xbf\x9e*\xfa\xc0E=\xfb\xbf\x9c=\x95\x0e\xf6\xc8\xf2\xbf\x1d\x94\xa7\xbc\xf9\x85\x0e\xc0\x074}^\xb1\x97\x01\xc0:!\x03\x93f\x1a\n\xc0,\x14\x86z\xe64\t\xc0`j\xe9\xa9k\xca\xfd\xbf\xc3\x98\xa8\x8aI\x1b\xf3\xbf\xfd7\x9e\x92x\xa1\xd2?\xa0\x8f\x85\xcd\xf8&\xc0\xbf\x9f\x0ctOb\xb4\x85?\xca\xf3F\x7fa\x95\xe6?#\xf3\xf3,\xadL\xcf\xbf\xb2\x08\x18x\x1f\xbb\xf6\xbf7\xc2)GF\xf9\xf1\xbf9=cc`>\x02\xc0\xd3\xae\t}\xe4\xaf\xfc\xbf8\xb7\x85\xd5*p\xee?\xb5\xfd\xed\xf7\xe6\n\x02\xc0\xff. +\xb6z\xe4\xbf\xcc3\xf0a\xc7U\xee\xbf\xcdK\xfc\xdaxI\x00\xc0\x0e\xd9g\xb0\xc5\xd1\xe7\xbf\xf5\x9e4\xe2\x1dY\x00\xc0\xd0=\xee\xd6\x19\x1d\x03@\xf9\x9a\x1a\xbfZ\x9d\x14@5\xb0o\x92\x15\xb7\t@\xf5jD\x81\xd0\xf3\xbf\t\x80\xcc\xc5\x13\x9e\xf4?\x9a\x8c\xda%\x06\xcf\xda?g\xf00\xcbcM\xb4\xbf\xeb\x8c\x8b\x12[\xc5\xdd?9%sZ\x81n\xa5\xbf\xe4=\xc4\xdc+\xd6\xed\xbf.\xc6\xfa\xe4\x81\xcf\xd3\xbfb\xa9\x8dB\x9b\xb2\xef?\x01\xf9\xec\xa7S\x03\xe3\xbf\xad[9Zh\xd1\xd5?\xde5\xb1:\xd5\xc8\xd1\xbfS\x8d\xd3\x0b9\xc4\t@\xb8\xae\xce\xe5\xdaC\xf8?}\x00\xa8\xf7\xabl\x06\xc0h:\x07wX\x03\xf3?\xda\xbe\x17\xc0:\xd9\xe1\xbf\xad\n\xb3\xca\x83\x86\xfe\xbf\x8b\x8c\xfd,YA\xec\xbf\x9d\x9a-\xef\x1du\xfa\xbf\xebu\x06\x7f,\x0e\xff\xbf\xb1h2\x1c\x1b3\x01\xc0\xf5\xbc\x96\x91\xf0\xd2\xb2?4\x00\xbaa\x16\xd3\xe8\xbf\x04_\xa2\xb8$\x85\xd5\xbf\xdbg^H\x0c\x91\x02\xc0\xfc\xaf,\x90\x9c\x0e\xe3?\xdb8*\xe4\xd6f\xfe?\xf9\xc3@ \x82\xbd\xe8\xbfX\xc7\xeb\xfa\xff\x04\xad?J\xcc\x0f\xb7hH\xe0\xbf\xcb\xc6+\x1c\xbf-\xee?\xdf\xbe\xda\xd8\x85[\xe9?\x82\xcb\xb3lU\xfa\xf2?\xca\x83\xb2\xfaUd\xf6?q\xe3\xc7x\x8c\x98\xf7?[l<\x02!\xf9\xfb?kY5/\x92\x03\xee?\x1bzR\x10\x7fe\xee?\x84\xa1t\x8b \x95\xff?\x0c]\xdd\x9c\xf6\xc9\xf3?\xb3(\x1b4m\xb8\xe4?\xb7\xa0\xbea\xcf\xa6\xe5?\t\x06~\x93:r\xe7\xbf\x18F\xd9PQ\x02\xe9\xbf4uw\x86\xe42\x01\xc0\xc8h\xe9&\xb1\x8c\xde?\x91\x8f":\xa4A\xf9?\xdf\xa7\x89\xa2&\xd5\xf3?\x91*\xdcx#\xca\xf5?\xf9\xe3\xa1w\xcc\xea\xe2\xbfD:\x0c\xd6qz\xf0?\x88ax\xa8\x82,\xe3?.w\xb4\x11\x10\xbf\xeb?_9\x0b\xee\xcb\xdf\xef?(F\x18\xf8\x93h\xf2\xbf\xf6\x1ag\xabtV\xc3\xbf3\xd8x\xb1\x84E\xf1?9\xb9\xb9\xe8\xe7s\x04@n\r/\x08Rg\xfe?\x0c\x15\x8d\x00u\xee\x10@\x83\xcf\xb7\x16\xe5\xa7\xf9?\x07\x0b\xed\xf0\xb1\xeb\x02@\x19\x9b\xe6\x0f<\x1d\x01@\xda\xf3/|\xf2\xa6\xd1?9WQ\xde\xa2N\x04@\xe7\x8f\xcf\x12o\xff\xfb?|\xd26\xf9\xa2\x9b\xf8\xbf\xc1\xee\x08\x9d\x1f\xdf\xcb?\xd3\xd3\x93\xfe\xcec\xdd?\x94\xd0k@UX\xf0\xbf\xa3kO\xc3$\xf6\xd0\xbft)\xf9t\xc7[\xc3?\x86+~\xec\xb4X\xe0\xbf\xe4\xa9U\xb17\xbb\xdc?\xdeD\xd5b2\x8d\x07@\xfe\xd9\xdc)\\\x0e\x01@$1\xe3\xe7\x1b\xf8\xfb?\xbe\xd0\xd3\xa5\x82o\xd2\xbf\xba\xb57\xb3~\xf7\xdf\xbfjL\xb6E\xb0\xac\xf3?^\xa8\xe4!)U\xe0?F)\x07\x1d\x94\x9b\xca?Q\xa24\xe5\x81\x80\xe8\xbf\x01K\xa3/\x8c\xe1\xf0\xbf\xc8Z\xa0\xb5\x95\xd1\xdd\xbf\xcd\x0e\xe2\xe4\x16@\x04@SZF\xb1S\xe7\t@S\x9a\xf5\xb8I\x14\x0c@\xea\x00LuO\xcd\r@\x1a3`\xe9\xbc:\x03@\'/.8\xee \xfd?:\xe6\xf5\x99\xff!\xd2?47\xb0*\xcd\xf6\xd8?\x16\x1b\xab\xff+o\xf6?;]\x95[d\xdf\x05@\x97\xb6\xeb\x136\x9e\xff?\x97J\x0b<\x837\x03@\xe0\x1d\xc3\x16{\x14\xf5?\x08\t\xaaP%\x91\xdd\xbfJ-I{7\x96\xdd\xbf\x86\x95X\xd0F\x07\xed?\xa2\xda\x12\xa2E\x08\xed?\xd6|\x86{z\x1f\x04@\x1c*\xff\x1d\x8bn\x05@B\xe29\x83\xc1~\xeb?\xedu\x9f\xc0\x8a\x9d\xd7?f\t\x91\x0f\xddt\xdb?\xaeU2\x1c\xce\xd7\x99?\x9f\xb0\xf4B\x82i\xe3\xbf\xf2R\x0bY\xc1T\xea\xbf\x1f\x95\xe4\xb7\xbb#\xa1?S\x1b\xc9x\xddR\xec\xbf+\x9emo\xb6K\xdc?\xce?\xf7\xb8\xa9\x12\xf6\xbf\x01\xc4Q+\xfd\xe9\x11@\xf4\xdb\xa6!\xb5\xcf\x16@\xa95\x7f4W\x8b\x02@\xc9\x1d\xa2\xa8x\xa2\xf7?rw\xf8\x12\xa9\xa5\xfb?\xa3\xdf\x93\xab\xaa\xb5\x00@w\xa1UC\x03\x8f\xf0?q\xe5bNe\x0f\x04@\xc0)\xa7#eW\xf6?\x04\xe4\x0e\xd0hN\x00@\xa7\xf1ncG\xa2\xf2?\xef\x99g\xb1/\x00\xe2\xbfz\xf8\xb6\xc0\xfek\xb9\xbf\xc0PoC\x1a&\xe8?\xa8\x92I\xb6-\xf0\x01@u\x8a\xe3\x7f\xa5\xea\x08@~\x0e\xf2*%\xab\x02@#;\xe1R\xd1\xea\xf2?\xb7mz\xb9\xf1?u\xa46\x913T\xf7?g\x13\xf6\xab\x7f\xe4\xf1?V`]X\xf8\x0b\xfc?\x10<\xe8\xbc\xe0\xe6\x07@g\xd2\xad\tCe\x02@o\x1a\x0e\x80\x9ao\xf2\xbf\xec\xc4 \xa1\x03\xae\x0c\xc0\xb9\x03d\xe3\x19q\xf2\xbf\xb1\x80\x06\xc9\xfa\x03\xe7\xbf\x80v\xd75\x86\x86\xe0?y\x98\xba\xc0\xd2\xf8\xd9\xbf\x81^\xc0=\xd4\xe1~\xbf\xab\x0fj|\x83\xe4\xe8\xbf\x85\xfeF\xe7\xd5\xde\xce?O\xa4&\xc4\t\xf5\xe8?\x8a,o& \x08\xb0?\xefC\xbcy\xa55\xf1\xbf\xc97t\x1f\xadv\xf1?\xcbi\xf9\x99\xe8\x11\xce\xbf\xc3\xa3\x8c\xa3<\xee\xf5?Mn\xd2!\xe5\x9c\xb5\xbfyn}\x8b\xd4\xb7\xd6?\x80\xdcQY\xe1\xd7\xf4\xbf\xe40\x02\x9eP\xe9\x0b\xc0\x83\xa7\x00}}\xab\x0c\xc0O\xbd\xf4\xcc\x1cw\xf2\xbf\xe1\xe6Snw\xe4\xfc\xbf\xf4\xc3\x8d\xc3\xeb\x12\xf4?h3\x1bb\xc1W\xb0?\xa6\xb6d\xf2Z\x1d\t@\x18\r\xff\xfe\x8a\xa5\xfd?\x84\x15QJ;j\x03@\xc3*\x07\xe2\xd7K\xb7?gc Z%1\xc0?\xbf\x87aR\xc0U\xd8?q1\xc1\xc9\xd4\x9c\xf4\xbf\xcd\xc2\xdap-\xb2\x84?L\x9fRDo\xb0\xc2\xbfLh\xf4\xdc\xc4\x03\xd5?\xd7\x0f\xbd@9\r\x03\xc0@\xf6\xcf\xd0zE\xe0\xbf\x8d!\xcegbv\xdc\xbf\x1b\xcc\xbb\xf27\x89\xea\xbf[(\x8d\xb5{\x1c\xe2\xbf\xc9>\x0e\x8d\x00?\x9e?@\xbf\xbb\x9b@\x01\xe0?Fe!%H\x8b\xf6\xbf\xf8\x19\xe1j]l\xfe?\xf3\x15x<\x11\xa5\xbf?F#\xbc\xce\xcb\r\xef?\x1d\x12\xcc\x92|\x15\x06\xc0\xf7f\xac\xbb\xce9\x05\xc0\xef?\xae\x08\xfd\xcc\x08\xc0\xee\xd0\xc6\x14\xc1b\x05\xc0\xdeA\n\x13\xc9+\xdc?\xcdT\x9e\x93Y\xe6\xef\xbf[\x13\x9d\x11\xa4\xbb\xee\xbf\xa5\xf4\xca\xa1\x81\x1c\xe6?\xd3\xe8\xbc\xcc\x1b\xbd\xe5\xbf1\xd6qBf\x90\xf4\xbfa\xba\xd2\x898T\x07@!\xb4\xb9\xba\x13\xe2\xd9?&2\xc6\xce0\xc8\xa1?/\x15\xd5\xe1\\n\x04\xc0\xbc\xaa\x18\x9c@.\xf1\xbf\x80<\xbf\xca\xa2k\x06\xc0\x03v\x0e=\x87\x81\xf5\xbf<\xc6\xec\x1f\x9bi\xe3\xbfF\xdb\xf0\x1e!>\xc5\xbf\xf1\xc1\xb6nJ\xf4\x01\xc0;\xc7\x8b\xac[\x11\xdc?\xed\x8d\x8d\xbcbe\xf2?\x95pc\xd2s\xe8\x9a\xbf\xb45\xf0\x84\x87k\xd9?\xe0\x14\xa0\x17\x97\n\xe5?\xb0q\xf1I>\xc5\xf3\xbf\xc5&d[\xa1\xde\xdf?\x86Q\x1a\xb9#A\xfb\xbfe\x97`1|\x81\xf0\xbf\xd7\xb1F0\xdf\xb0\xf4\xbf\xff\xf6\x189\xc0*\xf4\xbfTgve\x0fR\xf1\xbf\x1b\xe4m\x84\xb7t\x01\xc0+_,\xbe\xed3\xda\xbfT\x8e\xb1\xd1\xb8#\xdb?m XV9.\x06@\x87\xb6\xc9\x82\x1cv\xc8?f\x8e\x9aXtE\xf4?\xb0\xb9o\x7f\x878\xfd\xbf\xc7\xaa4\xc5\xf0\x8d\xd6?N V\x90\xdc\x05\xfd\xbf\xe2,9\t\xb6f\x0c\xc0\xe1\xdc\xb4\x88\xe6\xf6\x03\xc0N\xc0\xc5\x14\'\'\xba\xbf^\x128z\xe2\xe7\xef\xbf\x1e\xfa\xd6\xdc\x9c\x90\xb9?\xb0\xc1\x01\xf6\xa3d\x08@~;\x14\x0c\x17{\xe4\xbfKge\x02\xcd\xcb\xf5\xbf\x8a\xb6u\x15\x03\x1d\xf0\xbfE\xe7pp\x8c\xe0\xef?g\x1b\xfe\xfa\xffO\xd3\xbf~\xbe\xa09e\xab\xbc\xbf\x02q\x91\xb0Q\x87\xaa\xbf\xc4E\x10=\xc5\xce\x04@GU\xa7\xf1\x94\xfc\x98?\x17\xa7\x0c\x167 \xf4\xbf\x1b\x87<_&\xe0\x03\xc0\xd4\xde\xc1DDi\x06\xc0\xb5\xec\xf29\xae\xae\xfb\xbf\xe6\xcfV\xaa\x8a2\xf8\xbf\x14}\x00\x9f1o\x01\xc0\xdcC\x0f\xb7\x8e\xa9\x0e\xc0\x85\x04\x18L\xcc\xa6\xd9\xbf\xa4\x07\xa9\x90U\xf5\xf0\xbf\x845\xb7I&\x7f\xd6\xbf\xaeD\x93W\x0c\r\xe4\xbf\x19\x1e\x885e\x1b\xef\xbf\xa53\xc5\x8b>\xc1\xff\xbf\xd0F\xb6\x83\x96\xe0\xfc\xbf{Sr/T1\xfc\xbf\xb8\xe3\x89\xa3P\x99\x01\xc0\xfd\xac\xd4,2a\xca\xbf\x8f\xf4S\xd9\x8cQ\xe8\xbf\x88\x97\xc8H\x96\x87\xfb\xbf\xe8\xf8\xfeE@\xb2\xd8\xbf\xea\xc8\x87\xc6\x16\xc7\xeb\xbfOb2\xe5,H\xf7\xbf\x0f\x05z>\x1cT\xe3\xbf\xeb\xf01*X\xa0\xf3\xbfCn#?\xe3\xfa\xf8?^\xa3:\x14\xef\x08\xe7?\x9b\xe2\xcek\xc1\x05\xfc?\x90\xa2\xd1\x9a\xd5\xa9\xe5?\xf6\xc8\xfc\xd2\xd7\x1f\xe8\xbf5D\xcd\xce\xad&\xd0\xbf\x124\x84Bi\x94\xfa\xbf\xe6uY$\xd4m\r\xc0\x0f\xb3\xbf\x1a\xbb?\x11\xc0\x8dZM\x8d=\xd0\x07\xc0\x1c\xedb\xdd\xec!\xfc\xbf\xa2\xd8\xfd\x9aO-\x15\xc0&\x88\x82\xd2\x90\x98\t\xc0\xa0\xcf\xf8*/\xba\xd5\xbfQ\xfe=\\\'\xda\xc8?\x03\xa1V5HM\xe1\xbfA\xe1\xfd\xde\x0f\xdd\xde?\x17p\xadH\x81\x1e\xf2?\xe3V\x0fj\xb3F\xf2?1\xcbW>\x96\xea\xe9\xbf\x86\xa5\xf5s\x14\xaa\xe5?\xb0\x1c\xc5\xb5k\x98\xc1?\x94\xf6\x90\x83\xe9S\xcb\xbf\x99\xa7\x8f2\x9d\xe5\xf5?ex}\xcd\x91*\xf6?\x00t\xb5\xc7o\xe9\xe9?\x9c\xd8\xc2\xdb\xd6\x13\xa1\xbf\xc0\xaf\xd9\x95\xa4\xc5\xd8\xbf\x87^\x84\xcd.V\xe8\xbf\x92\xfc$J-9\xd0?Q\x8at-b\\\xb2?v\x7f\xb03"\x99\xf5?\x92;\xb2w\xbef\xe2?W\xd1>p\xc6\xea\xc7\xbfh\xadm\xd1\x13\xc4\xc0?\xb6\xd4\xedX\xcb\x07\x07@\x02\xf0Ly\x0e\xb0\xee\xbf\xa9\x8dA$\x15\xb8\x05\xc0C\x85W+E\xd3\xeb\xbf/h\xd5#\xbdW\xf6\xbf\xcc\x90\x06\xa9u\xff\x07\xc0\x08\x80\xa1k\x89;\xe3\xbfW`)|B\xbb\xc6\xbf\xbb\xfdX(B>\xe3?o=\x9d\x81\\\x8d\xe3?DfT\x86\xa7r\xd8?x"l\xfa\x83j\xd3?d\xe1\xcf\'\xca \xe8\xbf\xdc\x9f\xafk\xd7\xbf\xb0?\'\x07\x7f{NM\xf7?\x8d{\xa2\x1f\xc0\xe1\xe6?eb\x05\xaa\x1a\xa2\xe2?\xdar\xcc&\xa9\xfb\xd2\xbf\'\xd5,\xe8J?\xe9?.\xc15\x1e\x9e\xdd\xed\xbf\x9c\x15O`\x9c9\xca?_\xb99o\x82\xd2\xa0\xbf\xfb\tA\xb0|\x15\xe9\xbf;\x83\xa8\xf5}\xdc\xb2\xbf\x89\xab\xe7}J\xc9\xfd?&\x0f\xf2\x80C\x82\xfc\xbf\xb9\x1d\xba\xeb\xcd4\xef?\xce\xb60?+\x9a\xd7?$\x85\xa9\xb5\x8cj\xd1\xbf*\xb3x:\x8c\xd9\xb1?T\xa2\x9f\x8e\x00\xb1\xb9?A\x93\xbb\x99\x1e\x9a\xee?\xb12\xce\xe9*V\xd8\xbf\xc8\xa1\x1e\x90\x0fT\xe4\xbf\x02{B%\xe94\xe1?i\xa0x"\xea/\xe0\xbf&\xf7\x83\xd3F\xde\xb7?\x12\xc8?\xb54\n\xa8?H\xd7E\x14\xbdv\xcb\xbfM\xc4\x1d\xfd8\xc0\xe5?2+\xbaR\x0bA\xe0?\x81\xc3\xa7\x93n\xeb\xfb\xbfb\xb7u4\x8be\xee?\x9a\x1e\xde\'bb\xf4\xbf/\xf8\x0b!+\xa5\x02@*J\xb3\xdc\xab\xeb\xe6?k]\xe8D\xa7\xef\xe3?\xe6!E\x99\xa5{\xf3?\xd4\x88)\xd4*U\xe9\xbf-\x0b\xc7a3\x80\xdc?\xc2\xfb\x17\r\x84/\xd3?\xc4\xa3>\xc3\xf3y\xe4?8T\xa5puC\xa3?K\xd1d\xc1\xc7\xe8\xc1\xbf\x01\xbaV\x04%\xb1\xcb\xbf\xc7\n\x96`\xed\xbf\xac\xc1T1\xdc\x05\xe1?\xa9\x99z\xec\xa4\xa0\xeb\xbf\xb7\xbf\xc7\xba\xbc\x11\xf8\xbf\xb5\xb5\xcb\xbd\xda{\xfe?\xceji!\x00\xa8\xe0\xbf<`\x94\xe4\n\xa4\xd3\xbf\x1fu\xac/\x8e>\x02@\x1by\xdc\xe0)z\xd7\xbf![\x14\x85>b\xad?\xe4A\xf6\xcdi\x91\xf0\xbfg\x01\x83\xa2@\x88\xe3\xbf\xb4\xa4\xf0\xc6\xfdx\xe9\xbf\xa7z\x87/\xb3W\xf7\xbfEx\xfciW*\xdb\xbf\xa9y\xdaA\xee\xc3\xf7\xbf\xd0\x00\x0f86p\xa9\xbf~9\x03\x95\xf3\x04\xc2\xbf^z\xbb\tR?\xe3?\xd5\xce#w\xfb\x81\xe1\xbf\xbf\xb9\x888@\xb1\xdd?\tv\x93\x800|\xf0?\xe5\xf3\x1frF\xb6\xf0\xbf%\xa6\xc3\xe2\x9d|\xe8\xbf/\xd6i\x1a\xc9\xed\xc3?\'\xa7\xa9/\xf4\xbc\xf8?\xa2N\x8f\x9a{\xb8\xc6\xbf\x93\x0e\xa8\xca\xcb\xc0\x06@\xdd\x14\x89\x9c7w\xf9?\xf97\x15\xec\\I\x96?\xda\nc\xca\x86\x0f\xf2?\xca\x82c\xcfA\x05\xf9\xbf=#\x0bk6\xf4\xe4\xbf\xa7\xae\x08\x9b\xd6\x1c\xc3\xbf\x89\x9c?_\xb8)\xb9\xbf\x8cw\xe5j\x1aF\x02@P\x82\xd9J\xb7e\x00@\xa9\xed\x84\xbb\x8b\x7f\xd2?\xf7\x96-\xc9A%\xf8\xbfP\xbczFb\xdb\xc8?\x87\xc7#n\x1e\xe5\xd8?\xab\xfc\xe8[\xe3\xd2\xd3\xbfe\xa1\xa9\xe0\xf8#\x05@\x15-\x8e1\xc4\xfd\xe0?2P\x19\xc8\xb1\xd1\xfc\xbf\xe9m\xaa\xf1\xf6\xd0\xe3?\x96\xef\x1b\x9c\x05\x86\xe2?\xdf&\x8c\x11U/\xed\xbf5\x81\xb3\x0e\x809\xdd\xbfA>;\xa8e\xd4\xf1?.(t\x87\xb0\xee\x00\xc0\xbb\x02\x8e\xca\x8f\x16\xe3?\x99\xab\xf3\xf8\xf9\x86\xe5?\x0e\x18v\t!\xed\xe8\xbfi{\xef\xec\xe7\x9a\xf2\xbffE\xee0f\xd3\xd4?Z\xe6\xb4[\\L\xf0\xbf$\xe5\xd25\x13\t\xea?L|\xd0\x9c\xf8\x0c\xb2\xbf\x86\xc0\xa56\xc0<\xf8\xbf\x84\xab\xee\xe4\xa7\x15\xee?0\x86\xb2\xfa\x97>\xf1\xbf$\xd2\x83\x01u\x13\xe0?)\xdeVp\x9b\xb7\x01@\xa9\x96\x87\x10"E\xf5?;7\xe7\xb4\x7fd\xf7\xbf\xbe\xfb\xf1\xf5\xe2G\xfe??9\xd7\xa1T\xad\xe3\xbf\x1e\xea\xf0\x08L\xc9\xc6\xbf\xc2#N\xce\x9f\xdd\xd6\xbf\xc0ABP\x86s\xe6\xbf\xbd\xf4\x8b\x19Om\xe4\xbf\x1b\x1fh22^\xda\xbf\xe4#\xcb\x8e\xd5_\xc9\xbf\xdf\xdd\t\x95\x8a\xcc\xf1?\x108Z\xe3\x02\x05\xb0\xbf\x1a~\x8ag\x9b\xc2\xee\xbf\xef\xf41\x06\x19\x16\xef\xbf\x8e)\xd3\xfc\xba\xaa\xe1?\xc3\x0e\xben\xcc\xa8\xd1?VH\xf4\x05\xb7|\xeb\xbf#\t_\xa7@\xc5\xe1?g\\\xb8\xa9\xe1\x06\xe2?\xd9\x1d\x15\x87]\xc6\xc3\xbf}\x82\x13\x1a\xba\xbc\xdb?=\xd0\x1b\xa38\xe7\xa3?a\xd4E_%7\xef\xbf\xfec\xbf\x10\x08\xc8\xde\xbf\xf5\x0f\xa8y\xfe?\xb0?z\xf3\xa20\nq\xea\xbf\xb0\xa8\x0c\x19~)\xe6?\x98\xbe\xed*\xed\x1d\xe9?o\xd88\xa0\x04\x1f\xa1?\x0749\xc3\xda\xb8\xe4?h2\xed\x0e:\x80\xe6\xbf\xb8/N\xeeT\xb2\xe9\xbf\xd2>\xa7jZh\xec\xbf\xc8\xfb\xf8)\xe7\x1d\xe9?\xd7{l\x95w\xd1\xf8?\xbf\x96\xae[\xf3\xfe\xe6?\xdc+\x1bC\x0e\xd4\xce\xbf\xa7\xa5+\xa9\xac\xe2\xfa\xbf\xee6\xc0\xb5e\xfb\xf7\xbf\xb55(\xbf\xa8\xbd\xf5?\x8ex<*\x13\x96\xd4?\x0ej\xa95\xc0\xc6\xd7\xbfIe\xa9+\xe8\xb6\xe3\xbfD\x82Yc\\\xa4\xea\xbf\x04\x93\x81\x14\xcd\xa8\xf4\xbf\x183\xbe\x9c\xcd8\xd8?s\xf0"\x867R\xe0?Z0\xf3\xc3\xec\x8e\xc0?8Hgj\x9d|\xc3\xbf\xc1 ,\xb3\xf0W\xe2?\xcd\xd8\xf6>\x0c]\xd3??\xe6\x01\xfa\x91/\xf2?\xde\xe7\xc1\xb4hq\xe7?\xf6\x89\x97\x94\x7f\xb0\xf8?\xd80>*\x17\x83\xb2\xbf\xcd\xe9\x16\xa9\xec#\xe7\xbf\x98\xf8\x83\x00\xf6\x95\xc3?m\xee\x07j\xf16\xd9?\x9d}%\x06\xfa\x8d\xe0?C\x81\xa2\xe6\xd2d\xcb\xbf@\xb8\xb9\x87\x01~\xe8?\x97/\xb5\xa0i\x10\xd1\xbf\x0c\xda\xe9g@)\xf6?+\xc9\x90~Y\x91\xf9?\xab\x80\xb6\t\xb6Y\x03@!\x03\xd5\xebi\x93\x01@\xd53C\x97\x8a\xab\xfd?\x9c\xbe\x94\xe6\x8c\xc4\xfa?F\xd8\x9a\xee\xd6\x88\xd3\xbf.H\x08\xd8g\xca\xd5\xbfz\\-\xe1M\x8f\x08\xc0\xddA\xad\x8e(\xbf\xe0\xbf\xdcK\xe0~"\x19\xf5\xbf\xd4\x9d\x94\xb0\x0e\xfd\xea\xbfi\x1a\xef\x98@\xbf\xc3?\r\xd0(\xdaB!\x05\xc0\xbd|\xcesn\x06\xf2\xbfJ\x02[\xf6iA\xd8\xbf\xf4#\x9f$J\xbb\xe1\xbf;j%\xb4\x07\x12\xbd?\xad\x84\x8d\x1e6\xfd\xa8\xbf=K\xca\xce\xd8\xea\xf7\xbf\xdc\xa98iO{\xc7?\x12\xaa\x99\x14\x84n\xe3\xbfP\x13\x8a\xfc\x12\xad\xd5\xbf\xb1l\xd5\xf1\xc1\xca\xc1?\xb8\xe8R\n\xf4\xc6\xf3?#\x07\x87\xad\th\xdd\xbf5+\xa14W\xad\x0c@\x84\xd6\xfa\xfd9\x1e\xf4?)\x1c)\x01}\xd3\x04@\xd9\xa9\xbbJzw\x03@\xd8Yo\xa28\xf0\r@=\xe7\xd0[\xe2\x05\x00@\xb9J\x88w\xce\x11\x02@9\xae!\x0bX\xf3\xfd?%\xc1`\x85t\xe3\xe2\xbf\xc7o\x1c\xc3\xdd\x07\xee\xbfHN%\xcb#\xde\xc6?6\x17q\x13\xa8s\xe5?\xbd/\x0e&\x97\x99\xfd\xbf\xe1&Bx&\xf7\xc1?P\x16\x95\xc6\xd8R\xc1?Xx8d+\xc3\x06\xc0\x96\xf8\x1a\xec\x12\xef\xfe\xbf+\xd3\xd3\xdf\xc8\xf4\xfd?\x9b\xaaPjP\x19\xed\xbf\x8eL\x04\xd4I\x8b\xfb\xbf9?\x0f\xb2s\x9d\xd1?O\xb5\xfb\xe1\xc3f\xf1\xbfax\xef\xc4\xa8h\xe5?/D\xb7K\xd3\xff\xf0\xbf}\x92R\xf9!`\xee?0\xafq\xab\x7f\xe1\xf2?!\x7f\x8e5\xd1\x05\r@^3\xba\xa9\xddC\xfc?\r_`\x0c3\x9f\x00@\x0cG\xd4\x0c\xad\x81\x0e@\x8d4\xcf\xb4\xd1\xc4\x0b@hlHw\xac\xff\x01@(H6\x06\xa0\xd2\x00@\x9f9\x80\xb9U\xc0\xf4?\x04\xa9\xebI\x01\xd5\xfe?|\x9e\xfc\xbac\xd6\x00@A\x12X\x00\x9d\x1f\xfa?B#\xf5\xb5\xf5\x8b\xe7?P\xf9B,\xeeY\xcd\xbf6\x1e\x00v\x93+\xf3?\xa3w:m\x03\x17\xd1\xbf\x84\xcai\xcb(\xeb\xd7?\xdaK\x1d\xb6\xd3\x18\xc2?\x91X\xbca\xeb\xc2\xf4?e7\xca\xf8\x1d\xb1\xe8\xbf\xe8ZG\xe7\xab<\xd7?\x93\xc4\x18N\xe3\\\xda?.\x8a\x96\x7f*\x8c\xe4\xbfg\xc0\xd4\x17d\xed\xf3\xbf\t3R^\xf2\x9e\xf5\xbf\x9a\xf7\xb4\xb8\x9a\x82\xe4\xbf\xffqD\xdf-\x1d\xdc\xbf&\xadb\xb4\xf3P\xdb\xbf9\x9b\xfa\xf9\xbf\xf0\xe4?\xdf\x04\xfa!\xe8e\xe8?\x14k\xda\xdfh\xaa\xd0?\xa9\xfbCD\xf9\xbb\xfe?o"\xaf*\xbd\x89\x0c@\xcb#\x83\x9c$\x80\r@\xd6\x86\xedw\xa4\xf9\x01@\xe1\xa2\xf4t~:\x01@o\xf7\\Ow\xda\xf7?\x85d\x87\x01T\xdf\xe1?\xad\x1e\xfc\x07V\x9b\xe6?@\xfa\xf6\x87\xc6\xbe\xed?b"\xf4\x93x{\xc6?\x917JW\xd4<\xf4?\xfe\xed\xf7F\x9bY\xea\xbfZi\xbcv\'\xfc\xbd\xbf\x00\xfe\xda\xd7\xd1\x91\xf6\xbf\x00\x9cPr!\x15\xf3\xbfo\xa0wkL~\xe5\xbfG\x99=~9;\xf3?\xc7\x19\xc0\xbf\xf6a\xd2\xbf\xe3\xf2\x12\x8eI{\x00\xc0\x13t\xbf0\x9d\x15\xe6\xbfk\xd3\xe6\xe8Qg\xf6\xbf\x98\x0c)\x8b5\x98\xf3\xbf\xd0}\xf0\x02\xa7U\xa9?uk\xe5$\xc9\x83\x11@\xcc\x08\xe8\x88"\xe3\xeb?mx\xec\xd7{\xa7\xc6?\x9b\x0c:\x9f:4\xf2\xbf\xa3\x13\xd5\x1b\xe1\x94\xf4?^\xfa\x11\x87[t\x01@\xbd\xdco\xe4^\t\xf0?\x02i\xefdW\xf5\xfe?\xee\xca\x0b\xc7Vw\xfe?9\x12\xba{\x08[\x08@\x88\xa6m\xd6\x00\xc5\xc5\xbf{\xdcv\x7f\x94<\xd3?\x84\xf8S\xc4D\x12\xed\xbf\xf2\xf9\xea\x8f\xb7\xcd\xed\xbf8\xa4\x1d\x00\xb9\x9c\xe0\xbf\x8ce\x8d\\\xc9\xa6\xea?%\x83\x08[Q\xbd\xc9\xbf=,l\xe6\xfae\xd1?\x19\'?\xb6\xc8Q\xe0\xbf\x87`\xb0\xedF\xfa\xe8\xbf#\xff\xb9s\xb0\x10\x01\xc0!\x03+\x10{\x89\xdf?:n\x9f\xa3\xfb\xb3\xc4?Z\xf5\xb0k\xe7E\xf4\xbf\xfaq\xe4\x11\xdf"\xfa\xbf\xae?8\xfe\xbd]\xec\xbf\x85\xd3\xb5{\xf6\x10\xce?(O\xdd\\\x94f\xec\xbfp\x93\xa3Z\'f\xe5\xbf\xbf\xe7\x05X\x02\x00\xf7?\xda\r\x92\xd87g\xca?\xa7\x0e\x15\x97\xf4\xa3\xde?\r\x1a\xa0\x14Y3\xed\xbf%d4\xd9\x03\xd1\xc5\xbf\xb0\x1c\x8d\xbeb\xf4\t@fk\x88\xf6{\xa0\r@`@\xaa\xc5\xe6\xec\xf6?\xa6\xb9X~\xea\xcb\x02@x\x8e=\x80e\x1b\xe8?33\xcf\xf1iB\x93?\x1e\x9ef\xd8\x93\'\x00\xc0\xbbU\xbf\x9cr\x07\x08\xc0\xc2I0\xda\x8a\xdf\xe8\xbfP\xdb\x1b \xbe\xfd\x00@.\xa8\x04\xad\x06\xee\xd4?\x8e,Q\xbbV/\xaf?\xe2\xedc`NL\xff?\x12\xff\x8a\x1dC\xe5\xd2\xbf\x13yQ\xdc\xc0U\xf0\xbf\xcc\xb0\x17\xe3i\xd0\xe3\xbf\x13\xd9\xd8P\x01\xe5\xe1?m\x8dstx\x86\xde\xbf\x05\xa2Y0\xf8 \t\xc0\x05[\x0c\xf7b_\xff\xbf\xea\x87\xaf8\xa0^\xf0\xbf}\xc8g\x187\xa2\xda?vil\x7f\x049\xd7?4\xc9\xa3\xe5L\xe6\xc8\xbfi\xa0f\xd8t\x9d\xe5\xbf\xef\xc4\x1d\xdbG\xc1\xe5?+`\xee\xbf\\\x8a\xed?\xbbB\x9d\x9e\xc2\xb7\xe1\xbf0}\xf6\x18\xd7n\xe6\xbfh\xfd0\x88L\x15\xd6\xbf6\xff\x82:\x91w\xfd?\x14\xb0(\xcd\x06-\xf2?\xfb\xaf\xf8)\x80\xdd\xda?[\x0f\xf7\x169\xcb\x03\xc0\xc31i\xa6\x87e\xf9\xbf\xefYd\xba\xfd\x88\xec\xbf\x95(z\xf14\xd4\t\xc0\x80\xacfa\xe3\xae\xc1\xbf\x86\x1cB\x93\xf9\xa6\xf0?\xe1\xe8-:(\x92\x00@\xde4\x99\x8f\xe3.\xff?\xe1\x80\xf66\xa12\xde?-Z2h\xf4T\xf2?\t\t\x7f&#\x9e\xf8?\x18\xde\x04+R\xa7\xfa\xbf\xa0\x17k\xa1k}\xf5\xbfvP\xff\xdbg\x9f\t\xc0\xd7\xde\xc1x\x8b\x15\x0e\xc0\xc1%O6\x02\x04\x03\xc08\xd0\x16\xd7k\n\xc2\xbfo\xffl\xdad\x8e\xf5?\x10?\xfba\xb7\xb0\xcb\xbfxN\xc7\x0c\xa0\xeb\xe0\xbf~D0\xad\xd9\xb8\xa2\xbf_o;\xcc\x14\xb8\xe8\xbf\x1e\xf2\x81\xb4\x04\xce\xe9?\xc8\xc2m\x1bn\xa0\xc0\xbfw\x04S\x91\xee\x01\x02@\x05}\xe6\x12\x8en\xd4?\xfa^I\x81\xcbC\xf8?1nE\x7f\xa8\xae\xf6?\x0cU_\x10|I\x05@\xb4\xb0F\n\x90\x85\xfa?\x14\x0f\xab!\x8c\xe2\xff\xbf<\x99\xea\xf0\xd4\x9b\xef\xbfZ\xef\xef(\xe5u\xfc?j\xf7\x02\x1f1\xc1\xf4?\x88\xc2Z\nh|\x11@C\xbb\xfe{\x0bR\x0b@<\xc9\x16\xc64\\\xfb?\xda\xcep\xebc\xe7\r@\xdb\xac\x82zJ\xfd\x07@\xea\xccy\xbf\xbfv\xf3?\x93^D\x85\xa8M\xe0?\xfe\xb8\x13\xfe\xe2\xb6\xed\xbfZ\xdeDF\xc8\x98\xd1\xbf\\\xac6\x19!q\xef\xbf\xf5\xa5\tZ`\x9e\xf5\xbf\xf9\xdf\xd7\x06\xa9\x00\xe2?y6\xf5z_\xf5\xe8?\x08\xe6\x86O\xdb\x13\xbf?\x17c\x9f}--\xec\xbf\xc7S\xd4G\x93\x13\xc6\xbf\xae[\xb1$\xabM\xd4?8Y\x07\x11\xfb|\xf4?\x9c\xa4\xa4\xb9\x08\xc0\xfb\xbfi0\xa9\xcd\xc0\x16\xd8\xbfH\xb8\xf5\xa7p\x12\xe3?\xed\xeb]\xa8;O\xf0\xbf\xa0\xcb\x08n\xc0\x03\xd0?\x84O\xff%\x8b\xa6\xe7\xbf\xa0\xcfhJA=\xf3\xbfF\xd9\xe1\xe8Oy\xeb?\xad\xc6\xaf\x81\xfdx\xfa?\xc0\xbb&\xa7\xab\x8f\x0b@)*mv\xb9\x02\x01@\xe5\xf0[\x8az\xe8\n@/\xe7\xb9\xa2\xc7J\x00@{\x1d\xdb\xa0E\x03\x08@\xc5\xa8^\xff\xa2\xaa\x10@\t\xc9-\xfd4j\x11@\xc8\x02h\x06\x1d]\x0b@\xbc3\xbb\xdfl>\xed?Z\xf7i=\xca\x02\xdf?]\xbd\x89\x89P\x17\xdc?\xe8[\xde\xd0\x9e\xce\xd2\xbfk?\xbe\xfe\xd6\xc9\xe0\xbfO/\x03\xdaX8\xd0\xbfS\x1e\t>\x9a\x11\xff?A?^b\x10\xd0\xf8\xbf!H\x1a\xe6\xca\xbe\xdf?\xa99S\xf6D\xff\xc0?\x03\xf2\x8a\xab\x13\xb4\xe0?\xedR\xb5\x9d\xab2\xda\xbf\xef\\\x8b\xc7=,\xf9\xbfO\xc6y\xeb7>\xdb\xbfQ]\xd9\x1a\xcd\xd7\xd6\xbf\xd9\xd6<]Zm\xa9?6Dz\x9a\x03q\xf3\xbfd\xb9\x81\xacIw\xe3\xbf_\x11\xbe\x90\xd6\x8f\xed?\xfa\x85\xe7y\xa7\xac\x04@\xaf\xd3\x90\x97%\xea\x02@\xc9\xdf\xcf\xec\xceF\xf5?\x9a_\xbf|\xc5\xde\x01@\x9b\xc9v\x11\xee\x8b\x08@w\x92\xdeh\x15|\xd6\xbfS\xd8M\x13\xfb\x81\xe8?\xc6\xf5\xc5\xde~-\x05@U\x83\xab\xc8\x7fr\x0b@s:\x8f\x86\xaf\x9a\x04@\xc2\x03R\xb1}\xd6\xde?\xc8PA\xf5Z*\xf9\xbf\xc6\xfc\xd7\x93\xc10\xad\xbfSJ\x18\x15\x07\xe1\xc5?\xfe\x83\x83\xdd8j\xd6\xbf\xa7\xd1\x9dv\x87G\x00@|f\x05\x1fAn\xf8\xbf\xb9\x95\x8ciJJ\xd6?\xe5\xdf\x1b\xfa\x87\xda\xe1?\x8eg\xd0B\xaf\xd0\x05@\xbb\xd2c\xcfn\x8d\xd6\xbf\nMM\xd4\x11\xfd\xf8\xbfRY\xb2L{\xbe\x02\xc0\x85\x1drl\x90\x1f\xcd\xbfSEA}\x1f\xe3\xfd\xbf\xf5\xfeF\xc6=\xe5\x0e\xc0<"4(R&\xf0?\xb9\xba\xe4\x14a\xeb\x04@^\xcc\'\xac\xff\xf1\xf8?\xab\xe0\x1d\xcdS\x90\xc0?*\xdb\xca\xde8]\xe9?\x95\x9c\xcb\x9e-*\xd2?\xca\x0b\xe9\xc3O\xfe\xd6\xbf\xcd\xfc\x95L\xb3f\x0f\xc0H\xe6@\xed\xbaK\xf4\xbf\x0e\xc6\x0b\x19\xbb\xd2\xf0?\x94\x04\'\xdd\xaa\xd5\xf1?\xe1\xda\xbe\xf2\xb4\xcd\xf9?,\x19~g\xd8\xde\xf7?\xd5\x84\xb1\x89\xfb\x82\xe0?\xdd(\x1e\xae\xd22\xda?\xf6gd\\\x9d\x98\x9b\xbfbNfKC2\x02@\xbf\x8d"%|\xd7\x02\xc0\x11n\x99L\xe6\xe1\xec\xbf\x81\xa4h(\xb4G\xed?\xe0\xcf\xdf\x0bqg\xff?\x84\xbf\x00EZ\x13\xf8\xbf\xe32\xeb7\xcc7\xc5?\\\x1e/\xca\xfdr\xda\xbf\xaf$\tp[\xe4\xf5\xbf\x91J\xd3\xa5\'\xea\xea\xbf\xbf\xd7\xfb\x01\x81\xdb\xf9\xbf*\xcb5\x13\xfc?_&\x8c\x06\x1c\xfd\xf5?%b\x08\x1cP\x8a\xdd?\x82o\xa7\xb73\n\xd2\xbfA*t\xbc\t\xc5\xb4?\xc9.\xcb\xe4\x11\xb8\xda\xbfM\xcd\xee\x1d\xd3C\xed?\x1d#\xd9\xc2\xcd\x0b\xb3?A\x13a \x15\x9c\x04@/\xe8\xe4\x9c\x03~\xad?B\xf0\xf7\xc7\xbe\xcc\xe9?\xdaD\x969\'\x96\xfa\xbf\xd0vs.R\x8a\xec\xbf\xad\x96\x8a\x1cK\xde\x10\xc0C\xc0\xf9cYr\xea?\xbf\xa4e(.9\xc8\xbfv\xedj\x06\xe5\xaf\xf4\xbf\x89\xfa\xd9_\xa1\x8a\xf8\xbf\x167\x88\xfc\xcd\t\xdc?\xd8\xc4\xa2\xe5\x90\xd1\xcd?\xe4\x92\xeb\x97\x19\x9d\xed?\xdeL\x80R\x89\xe3\xf7\xbf\x04\x9d\x1e\xe9\xbb\xe4\xdb?\x0bP\xa5\xafsk\xb1\xbf\x1c\x85\x9a\xb97\x0e\x00\xc0\x82\x03\xaa\x11\xa7N\xe0?~\x9c\xf8\x11\x957\xfc?\x02\xe4S\xd3+\x87\xfe?\x9b\x02\x0b\x7f\xa1\xd1\xfe?\xe9\xeb\xec!\x91\n\x01@\xfd\x84H\x10&=\x0b@\x18\xa52\xc8\xca\x06\xfe?\xa6\x07\xf9I}Z\xac\xbf\xd5\x81\xbc\x9b\xf6\xd5\xf4?Q\x13\xa5\x02\xcb\xd7\xf1\xbf\xa7\x02DW\x9d\xe6\xe3\xbf\x15 \xb6\x8b\x1b\xa0\xfd?\xc9\x05"\x01\xd5\x8d\xf3\xbf\xcb\xfdA\x92\xe8\x8a\xf8\xbf\xe2!6\xa6)f\xe2?\x8a\xac\xabR\x12\xc5\xd7?k\x05\x97\x15\xbb\xcb\xe3?\xcb4:\x91-\xcf\xfe\xbfU\x08\xa5pz\xf1\xfe\xbf|x\xd4\xbdR\x9a\t\xc0KX\xcc\t~?\xaf?,)+\xa9B\x08\xef\xbfx\x08\xb8,ij\xc2\xbfz\xb8\xa2D\xcc\xe7\xc4\xbf\xe4\x96\xbb\xd2\x13\xd7\xea\xbf\xfb\x01\xc3s\x96\xbe\xf2\xbf q\xa0\xb0\x0cu\xf4\xbfi&c\xc8\x8d\xab\xca?\x1d\x98\xd1l\x1e\xdb\xe8?\xfe\x11\xc2:\x87\x1e\xf2?\x04\xdd\x9a\x9f?\x08\xf1?\x90\xc7\xbe|\x03(\xda?0\xe1\x9eA0\xeb\xf3?\xd8\xa6\xe5\xe1\x004\xee?\x9f\x13\xfb\xeeEd\xf1?N\xb4\xe8 \x97U\x00\xc0\xccN?J\x81\xd2\xf1\xbf\x16\xd3\xa5pEm\xd9?\xc3\x9c1d\xde\xbb\xee?a\x1de\x0f\xda}\xde\xbf\x9d&l\x16|V\xd9?\x0bI\x18X\xda\xaa\xe2\xbf(\xa4\xe2(\x12\x83\xd3?\xd3\xc4>\x03\x0e\x8d\xf2?\x0e\x8c#\xe1R\x9b\xe4\xbf|\xc6H"\xa5\xec\xc2?\x97\x16g\xefk\xd4\xf7\xbf\xd9|\xbf\xbc\xa9\x19\x02\xc0\x08\x03\x0eF"2\xd5\xbf\x87\x97X\x18\x01\xcc\xe8?\xa9\xde\x97\x82Z#\xd0\xbf<\x85\xc2\x06N\x01\xd9\xbf\x8cY\xdd\xa8\xa2qf?\x9b\xa1\xc4\xc4\x11\xff\xcc?\x8cy\x8d\x94\x0e\x0e\xe9\xbf>9v\xf5\xa2S\xb2\xfc\xbf~\x06\t\xab\xf0\xaf\xf5\xbf\xa6K\xfba\xb5(\xe9?\x884h{\xaf\xdb\xbd\xbfRrhm\xe3\x03\xe0\xbf\x89:\xc3\xc4\x86\'\xca?\xe9\xf5\xc3\xa4\xce,\xfb?^\xb3}\xa9u#\xe0?8:\xea\xa6<\x84\xc5\xbf\x04\x1b{\xaf\xd5T\xde?\xe2\xac\xa1\xfap|\xdc?\xd2\xf87\x95h\xe1\xdc?FA"\n\xfe8\xe4\xbf\x8b\x0e\x90\xab\xea\xeb\x00@Sf\x8f\xd5\x0c\x9d\xe6?\xf1\xb8\x85{A\xba\xd1\xbf\xf4"Z\x1agi\xf3?\x1bm\xb7\xa7g\xa9\xce?\xa1@D\x0b\xd1\xc6\xd4?\x1a30\x97\xb6\xf1\xf6\xbf\xa0,\xfec\x10\x9c\xf5?5\x1eJ\x97\xbdA\xd8?$q`\x04\xdd\x91\xf3?\xc33\xe0iV(\xe5\xbf\xdd1\xe4\xc1\xcc\xa9\x02\xc0\xdd\xe7\xfc\x0etj\xd6\xbf\x173\x1f"\x9e3\xec\xbf|\x12\x16\x83e!\xe0\xbf\x1b\x88}p\xa8g\xf6\xbf\xf9H\x83\x11\xfaY\xbb\xbf\x82\x1a!\xdb\xf5\x1d\xf5\xbf\x89\xd9\xbb\x98D\x08\xcf?\xec\xa4Z!\xb6\x97\xd7?c\xb4`z\xb2\x9e\xea?nd\x1d\xe4\x8f\xea\xdd\xbf\xcf\xf7\xbdJA\x93\xf2\xbfW\xcf\xc4v\xb2#\xfd?b7\xda\xb5\x88\xb1r\xbf\xd9<\xb6\xbd\xea\xb6\xdd?s\x93\xdb\x82\xc2k\xf0?\xd3\xa5\x1aj\xa4V\xec\xbfl\x84\x16>I\xc7\xd4?\xff\x00\x0c\x08\xfc\xaa\xe5\xbf\x9c,\x0c\x16\xf9\xbc\xec?"8qe\xf2\x1c\xd0\xbf\x08\xca=[\x9f\x80\xd9\xbf@w/BK\xd2\xef?\xb5\xd6K\x1f\xc7\r\xb8\xbf*rv\x0c\xfdk\xe4?\xf4\x98\x8f\xf6!\xe0\xe8?\x9e3\x99\xbau5\xe0?\x19o\xb6\xa0\xa8\xd9\xf3?\xf6\x8d\xbc\x0eZ\xe7\xef\xbf94\xa7\x04\x83\x1c\xe9?\x90\xf9\xea\xa9W\x0c\xfb\xbf\x92\x88\x0b\xba\x8f\xcf\xf2\xbf\xe7\xae\x9e\xd5L\x82\xd7?\x0b"\n\xf6A\xea\xda?\xf2v\xbe\xbd\x9aV\xfa\xbf\x10m\x05\x9a\xd1\xf0\xc1\xbfl1\x13\xe0\xdbR\xfb?\xb7"\x8b\xb0K\xf2\xd0?\xb8\xcb\x15\xad\x87I\xf9\xbf\xdb\x10\x85(Y?\xfb?\xc5O\xccl\x08`\xe0\xbf\xf7H\x19\xc4:\xd3\xd3?q\xbeQ\xad1\xe3\x84?\xe0\x15\x0bW\xf5@\xf6\xbfF\x91Eo\xb1\xa6\xe9?7~\x08p\x1a\xee\xfc\xbf\xfd\xbb4\x8b\xe5\xce\xc5\xbf\x9a\xccn \xf9n\xfb?5\xd1{\xf8\x00\xc6\xe6?&b_u\xcfl\xa9?c\x87\xb63M"\xa7?\xa6\xe4\xf9\xf3\xe3\x1b\x01\xc0\xd6\x04\x90$k\xab\xd7?\xba\xb3&ie\xae\xdf?\xb4\xe4\x88Y\x9d\x17\xf0?\xa8N\r&\xa9\r\xc4\xbf\xac\x02/\xecf\xd1\x04@\x04\x1e^\xb7\x9d\xb0\xdb?\xd5\x1c<5\x07\xbb\xf0?r\xf3\x01"=S\x01\xc0V\xaa\xdc\xabD\xd9\xe5?\x0fKi\xd0j\x92\xe6?%Px\x94\xae\xe2\xee?\x9d@(=\xe6\xda\xec?\x00\xc259\x8f?\xf5?\x80\xae\xf7e\x93L\xc4?\x0ea\xce\t\xba\xe0\xb7\xbf\xe1\xdbr|\x88\xb8\xdc\xbf\x14\xc5&7\xdcI\xe0?U\x83\x96\xd6\x05\xc8\xed\xbfM\xd0X\xf8\xd7\x17\xe7?T\xe9\xa7\x8f\x01\xae\xf3\xbf_\xfe=k:\x8d\xd3?\xedQ\x96Ag\x92\xd7\xbf\x9c\x8d?p\xcf\x10\xee\xbfWf[\xbf\xed \xf3?\xe3:iv\xab\xd3\xb8\xbfI\xb5?\xae\x19\x9e\xc6\xbf\x0c\x82A\xb4\t\xf7\xc0?-\xb8\xce\x99$\x86\xac?l\xcae\xdf\xd2\xec?B\xe5\x87,9\xa4\x00@\xff\x08\x12\xb7\x1dP\xee\xbf\x04CeB\xb6Z\x03@\x0b\x91z\xcf\xcd\xf1\xe5?\xd3\xbf\x96\xe9\x91\xa0\xe9\xbf\xd7r\x9f~`\xc3\xdb\xbfivD\xfavD\x02\xc04\xfci\xc0\xe2j\xcb\xbf\xf9\x1d\xef\xfa\x85\xad\xfa\xbfJ\x8a\x9f\x02\xfc\x03\xbb?7_\xb0\x9fF\xbb\xc0?\x90\xca\xa4\x08\xb6\xca\xf5\xbf2a\x01qK\x1a\xf7\xbf\x92+.\xd3<\xfb\xf6?x\xb4)\xe6R\x19\x9f\xbf\xc6\x97\x85\x0f\xb3#~?VF\xc5\xa7jw\xe1?@\xd6F\xcd\x93\x1a\x82?\'\xc5z\x94N\xbd\xc4?\x8cB\xbeqe\r\xe4?\xdf\\\x89\xc7\x9d\xed\xf1?3\x1f\xfbz#\xdc\xd3\xbf\x03\xe2\x95\xc6\x04!\xbb?Oy\x7fD\xe0\x84\xe4\xbf\x8b\xa6(IwI\xe7\xbf\x00Wk1aN\xe3?\xf9"\xb9UZ\x87\xdd\xbf\xc9\xa8\xd7\xbe\x96\xf2\x00\xc0-\xa8\xc6>#\xe6\xfc?(3\xd5\xc0e\x06\xe5\xbf4\x14\xa5\xbc\x12\x9e\xe4?&\x8e\xf8\xd1t\xdb\xea\xbf8\xfe\xda\x92VW\xe6?\xdc9\xd6\x91u\xd8\xee\xbf\x8f}a~2/\xd1?tSE\xb0\xf8q\xe8\xbf\x89\x1d#7v\x8a\xd2?\x13-\xaez\x11F\xbb\xbf\x07]\x94?$\n\xce\xbf\xd8\xc5m\xb6x*\xf3?V\xda\xd6\x86%\xe5\xf5?*\x1c\xa6\x81\xb4%\xee\xbf\xd3\xc7d\x8c\x8d$\xf3\xbfO\x0c!:R\xdd\xc1\xbf\xd6\x1f<\x01\t\x0e\xc2?\xf5\x86\xc1\xaf\x17\xe3\x06@\xce"H/%\x17\xfe?\xffd}\x83\x9f*\xb4\xbf\xc9d\xca(\xf7\x98\xd9\xbf\x14\xa7g/\xf3\xa7\xd2?YP\xb5:Ge\xe1?\x04\x03AT\xf40\xce?\xcd\x92\x8a`/\xd8\xea\xbf\x96Zt\xc5\x14\xdd\xd7?\xfd\xd8\xa2$^\\\xf4?\xe4\xc9\xa3\x14\x15\x12\xe8\xbf\\R\xb82\x1b\xcc\xec?\xf8\x94\xe6,I\xca\xc6?\xc9\xdeM\xbf\x92\x13\xf9?e\xfc\xa8\xa9\xeb\xbd\xfb\xbf\xe3\xb4\x14\xaf\x0b\xb9\xd4\xbf\xa27#P\x1dt\xd3?${\x8f\xb8\x85\xb7\xf5\xbf\x1d\x16\xff\xda\xe4\xcb\xcb\xbf%b\xc2~\x12\xaa\xef?\x19\x13v\x9by|\xd4\xbf4\x15&o\x7f\x18\xe3\xbfz\x01\xb3q\xfd\xc7\xcb\xbfa\x08;\x1e\xca\x02\xbe\xbf\xd9\xb9\x15\xf5^\t\xdf?\x0e\xd0E\xe6\xd8H\xf1\xbf\x8a\xd5\xb8\xb6\x1e\xcc\xe3?k\xd1h\x0f_7\xdf\xbf\xac\xee.\xad\xddt\xf7\xbf\tr\xcbA\x000\xf7\xbf\n\x99\xcd\x1de\xb4\xe1?\x1f\xab\xfe\x1e\xaf\x97\xf4?\xa6g\xbf\xf1\xd12\xf1\xbf\xbf#-\xce\xed\xdd\xec\xbf(e\x82\xfbMs\xf5?y\xc6\xbeSw\x8c\xf2?\x94\xc5\x86\x84\xa4\x10\xf3\xbf<\x1c1Y\xb3\x88\xd3\xe9?\x99\xba\xc1\x1f5m\xe5\xbf\x86\x0e#\x87\x89D\xfa?\x93\xcdW\xfdu\xe4\xbf\xbf\x08K\xe7_H\xbd\xf0?vS8\x0f.b\xd4?M\xcfg\x10\xe8\xc7\xd0\xbf\x15\x89BZ\x06\xe4\xc1?Z\xd0\xb5\xc6\x91\xdd\xe7?\xba\x90S\xc4&-\xe0?i\xf9P\xec\xfbn\xf4?\xb7\x9cF\xc5\xc9M\xf5?\x1d\xb4:)\x9f\x03\x8e\xbfPo\x9eP\xff\xbe\xea?\xc5\xe4\xb2\xf7oI\xad\xbf&f:\xefH\xaa\xe3?!\xad\xc8\x9fF)\xfe\xbfkmM\xf1wW\xc0\xbfRL>=\xb1m\xe2?L\x9f\r{(t\x99?h\x0e\x92\x05\xc9|\xc0?N\x91\xf1\x8f\\h\xf8\xbf<\xa2\x83\xb8A\xd1\xea\xbf\x90R%\x05Al\xeb\xbfm\xd2Z\xfe\xdb\xbd\xf7\xbf\x01o\xff\xc3Zq\xf5\xbfj\xb1\xde5xg\xe3\xbf\xb8\xbb\xda\x95a~\xd8\xbf\xa2+\xd5<\x15\xc9\xf6\xbf&\x14\x1e\x83\'P\x01\xc0\xf8\x9e\xb9j\x1bA\xe6\xbf\x04\x0fb\xf4\xef\xcb\xd2\xbfo\xae=\xcd$d\xbb\xbf\x95\xc2\xfa\x8f\xf9\xbc\xf5?em\x03\x97\x95\x80\xdf\xbf\xb3\x15\xfb(\xe7:\xd6\xbf8\xdcb\xe9\xa2P\xcf?\xc2\xd5V\x9f\xfb;\xf7?\xfa]\x06R\x99\x90\xf5\xbfA\r}\xa5\x04\xde\xf7?\x00\x0f\xa5\x1c\xfa\xf2\xf7\xbf\x0b\xc5\xbe\x18\xb0?\xee\xbf\xdc\x05`\xd2\x07\x80\xc3\xbf\x12\xfd\n\x80\xb4\xd1\xf9?.\xfb\xdeR_\xe3\xf3\xbf\t\xd7\x1cq\xf1M\x06@\x81\x8e\xc5#V\x04\x00@\x04o\x8f\xc1M\xdc\xd4\xbf\xf8\x86\xd6\xe9r\xbb\xcc\xbf\x88kV|\xa11\x01\xc0\x11M\xfb\xb7\xb0\xb0\x00\xc0\xfe\x80\xd6A\xfbm\xba\xbf\xd1)q\x88\xf5\xfd\xf9\xbf\x1bS\xfbLI\xfc\xa1\xbf\xd6\xb9\xa3\xbd\xd0!\xff\xbf\xd22\xc2\xb9\xfc\xc5\xed\xbf\x1b\xfbP\x1b\xa4N\x00\xc0X\xfc"?a\x11\xf5\xbf\xdc)\x90\x7f\x1d\xac\xaa?\xfd\x12b\xe0\x14j\xf6\xbfYD\x10\x15\xdb\x03\xf0?\xe9\xc59\x07\x99\xe6\xf0\xbf\xa5\xbd\xfa\x03\xdc1\xc4\xbf\x16\x1c6\xc8\x8dC\xe0\xbf=\xbaW7\x17f\x02@I\x0c,yP]\xc0?\x8d\x9f\xf9F_\x06\x00\xc0\x07\x9d\xf8\xfa\xd1i\xe3?\xa7\t\n\xaf\xc50\x03@\xa6>\xa8\x16k\xa7\xe1?\xef*T\xbb\xdf\x16\xf4?\xcc(\r\x89#I\xd4\xbf\xddO\xbd\xa2\x91\xb9\xe5?\x95\xdbHh_R\xf0\xbf\xa4\xd3\xdc?l\x11\xa4?\x99\xf2#\xaf\x03\xc3\xcf\xbfN\xe2\xad\xb9\xa4m\xd3?p\xfb)s\x95\xbd\xd8?\xfd\x9b\xa7m\xe0\xf3\xe6\xbf\xca\xbd\x8d\x1e\x8e\xe9\xe6\xbf\x92p\xd0m\x1b\x07\xb8?x3\xb5\x80\x7fV\xf0\xbf\x19\xe3\x98\xc9\xd7$\xfa\xbf\xf7\x91\x03<\x9a\xb7\xd0?\xa6i\xcf\xa4\t\xff\xf5\xbf\x96\xbbjJ\xbbI\xde\xbf\x07a/6X\xd2\xe6\xbf\x10i\x99\xe9=}\xfe\xbf;\xd5\x94\x80\xa0\x19\xc7?\xee\x1c\xc6\xcd\xcfc\xf2\xbft*\xef4\x0f\x9e\xf4?b\xbd\x90CY\xe8\xd4?\x82\x06\xd0\x90\xd8\xca\xf9\xbf\x19e\\\xa3;\xf4\xde?e\xa5\xce\xf5^\x8e\xd3\xbf\x8d,\xe8\x0b\xeb\x93\x03@\xae\xab\xc3\xe4\xca\xba\xe9?\xe6v/?\xfc\xbd\xcb\xbf\x1b\xe3\xf3\xa5j\x17\xff?8p\x85\x1b\x8d\x9f\xf9?\x11\x8f]6%@\xf6\xbf\xbd\xe9.\x95km\xd9\xbfJ@=9`\xe3\xb5?F\xcbS_\xfa\x1b\xee?\xfdh\x85P\x81q\xf6\xbffX\x0czY\x18\xfa\xbfrAl\x1aq;\xe3\xbf\xa8\xd3A\xf9\xa9\xa6\xee\xbf=\xa0\xa5\xa4?\x12\xef\xbf{\x82\xfd#\xdb\xbf\xfe\xbfWo\xcd\xe1Pt\x05\xc00[\xf40\x9d\xdc\xe3\xbf]E\x81\xb1\x86\x18\x00\xc0\xca|`&)\xf9\xf1?\x14o\t\x95\xd15\xef?\x16O\xf8f\x9c\x99\xc1?\xa3_\xb1 \xaaF\xc2\xbf\xd8\x06Z\xcdY\t\xf0?\xa9kf\x1bs\x0b\xda\xbf\xf4\x80(\xe4\xde\x1a\xe6?/Q\xad\x14\xb8E\xa1\xbff\x7f1\xd8\xdd\xf4\xe1?\xae\xc1d+\xf0Q\xc5?\x90\x9b{\x81\x81+\xe7?\xbcY\xa93\x86\x7f\x08@q\x07J\x0b\xc0\xc6\xfa?\xc5\x96\x1a\x98a\xdb\xf3\xbf\xb1j[\xadW\xfb\xe3?\xd9\x9e\xb4\xabo\x7f\xf6?\xbd\xbe\xdb\x8f\x98}\xed\xbfK\xf7q\xfdZd\xfa\xbf,\x8f0\r\xa5\xff\xf7\xbfS\x99\xc8"c \x00\xc0\xbb\x11\xeeO\xad\x90\t\xc0\x96\xb44\xe3"\xbc\x03\xc0\xe5\xfd\xc0$\xf5E\x06\xc0\x18d\x94\\DT\x04\xc0>>\xf9\xdf\xa3O\xef\xbf\xa2\tS)ej\xc1?z\xabh\xb4p\x1f\x0b\xc0+\x02]\x16)!\xf5\xbf\xdd\x83/d`~\xdd?V?\x94\x8c+\xcd\xed\xbf\xcc\r\x0c\xdc\x14\xc3\xd9?\xee\xc9\x92\x94\xfd\xf8\xe7\xbf\xd5\xd6\xe5J7Y\xf1?\xea\xdd\x97\x93\x07\x87\x9a?3\xa6\xd2?f\x1a\xda\xbf1\xdf\xc3C%x\xf0\xbf\xbe\x9eM\xf1\xfd\xb9\xd4\xbf\xae\x8dkd\x04r\xda\xbfv\x9a\xbb6\xd1\xa5\xfd?\xa9\x95\'\xa7)v\xe4?1\x83\xb5\xd1z:\xf4\xbf\xf9[\xfb2~\xbc\xf9?9\x0c\xbf\xb0\x10m\xec?\xf0\x9a\x8c\xf2\x95\xe3\xe7\xbf^\xc9\x19\x03-|\xc0?:_l\xd0\xb9\xa9\xe6\xbfg\xe5\xa0\xe2\xbe\x98\x05\xc0\x0b\r\xfb\x8c\xf7\x88\xdb\xbf\xed\x15\xd1\xdf\x94\xab\xf1\xbf\x97\xb6\xa1\xad\xe8zUzQ\xef\xbf\xd9\x1a\x9d\xe4\xa9\xda\xae\xbf{\x96\x95\xe6!\xd0\x8c\xbf\xaf2q\xb3\xdc\xa6\xe7\xbf)d\xb1*\x88\xaa\xe7\xbf\x0b3T*nL\xf5\xbf%w\xfc\x8fB>\xe4?Q\xf5g\xbd\xaf\xd4\x04@V\x86\xe5\x14\xf1\xdb\xfa? e\xa5R)q\xef?\xe0\x9d\xc2\xf4\xf9|\xeb?\x12\x12B\x16\xda\xfe\xef?\xa1\xa3\x12\xfb\x9d.\xb1\xbf\x12\xd4\x87\xff\xfe\xa5\xe2\xbf\x0b\xa8\xf6\x9a1\x89\xdc?\xd0\xd8\xf1p\x16`\xec\xbff\xf2\xea\xffH\xeb\xb0?z6c\xdf\x0f*\x06@q\x19r\xc4\xfd\xfd\xfb\xbf\x90\xacA\x04\x17\xd8\x89\xbf\x8d\xd7\xb8\xc0\xc6\xf1\xfc\xbfJ"\xf6\xea\x08~\xf2\xbfC\x8a\x81Or\x92\xe4\xbf\x8c(\xb0"\x9d\xe1\xef\xbf\x9aG\x96\xda,\xcc\xc1?i\xcb:\x8av\x91\xe8\xbf\xd9\x18\xd9\xcc\xb5n\xdf?\x0f\xfa\xc6! +\xfc\xbf\x06\x9bcyi\xf1\xf9?\xa3\x81\xa1\x80\x11\x7f\xf5?\x12\xb5\xeac\x99\x86\xdd?\x81e\xb2&I\x16\xcd\xbf\xbb*\xf9\xe3S\xe8\xe5?\x16"#\xa9`\x1a\xf8?\x9b\x967\n6\xc2\xf7?\xa8\xc0Mf\xf7\xcd\xeb?&F&C\x0f\xe4\xd3\xbf|\x99^#\xaa\xbb\xf5?\x01Q\xd4\\\xab\x89\x00@}\xdb\x15\xcaV\xb7\xe0\xbf\xf3z\x12\xb9\x0f8\xa5?a"96\xf3\xe2\xf6\xbf\x02\x84XC\xa2\xc7\xe0?\xae\xa8.V\xa2\xf8\xa3\xbfL\x85z\xfbN\x9c\xe8?\x0ba\xf0t\xc6-\xfc?\xb5\xc4Up\xa9\xd4\xfa?\xc5\r\xc3"Z\xa3\xfb\xbf\xfd_\x1c\xd9\x06\xa0\xcf?\xb1\x9a\xca%\x86\xf7\xe4\xbf\xb8Juz&\xe5\xe2\xbf\x9d\x00\'V\xf3\x8b\xf8?\xf7\x8b\xc5\xed\xc4\xd4\xd2?D\x91\xe27\x15\xb1\xff?\xe2\xf4}\x01\xc1+\xf0?\xef7\x88L\t\xe3\xe5\xbf\x80I\xe4\x0c\xdc\xc7\xce?\xe0\x9c:\xf7\x1a7\xe9?I\xcf\xc5\x00N)\xf7\xbf\xbe\x1fO `\xc2\xe7?/\x16(\xaf\x99\x01\xea?no\xb8F\xa1}\xe5\xbf\x100_Q\x1c*\xd0\xbf\xeb\xfaV\x9b\xdeA\xe9?>Y3\xf9\xe5\x87\xdf?\xd2{J_2\xa4\xe8\xbf\x96_t\xc5\\\x92\xe8?(\x02\xae\x82\xb8e\xe6\xbfb|G\xa2\xba\x87\xec?\xa3\xb9\x14^S\x96\xf2\xbf\x11\x0e6\xff\x19!\xfe\xbf\xa8/\xa5U\x00\xf5\x00\xc0T\x0b0\x91G*\xf0?|\x87m\xbc\xfa\x05\xe5?@n\xbf\xae1\xf2\xf0?s4\x9bQ<\x1e\xfb?\xd3\xdf]\xab\xe6\xb6\xca\xbf\xf4\xb7`%\xe79\xd8\xbf\x80\xf2\xff\xc7p\xe9\xcb\xbf\r4\xd6\x0cyo\xe4?7\x8d\x94\xd4\xfe\xdb\xf2?\xfaN\\f\xb7J\xfe?Y\x91gQkU\xf3?\xcb\x8f\xa7H\\\x1f\xb2\xbf\xd3?\xc3\x8a1\x7f\xd5?4B\xd5{ Q\xe8?D\xd9o{o\x82\xdf?>\xa6\xee3\x99\xc8\xd0\xbf\xa3~\xa6*y\xe1\xc9\xbf\xc4.\x92\xebn\x06\xf4?Z5\xb4\x0b\x90\xff\xe9?\xa6\xc6\xe9\x90S\xf4\xf8?T\x1d\xa0\xa3\xb7g\xfb\xbf|\xf1\xd3\x18\x86\xfc\xe6?.\xad#\t\x99\\\xe6\xbf\x1e\xce\x16\x174\x94\xf7?\xeb\x04\x86\xd4b\x19\xd6\xbf\xb1J\x1e.\xc7\x9c\xee?9\x1f\xc5\xc0||\xc9\xbfq=\xf2}t\xdc\xb3?\xb6\x1b\xe2l\xfb\xb1\xc4\xbf\xb0t\xb6K\xc1s\xf9\xbfk\xb4IR\xe1\xbc\xf2?F\x05\x99\xd4\xfc2\xe8?\xc3?\x8d\xab\xa5\xf1\xd5b\xdd\xbf\\O(\\\xe6\x11\xf4?\xa2\\\x80~\xf9\xe1\xeb?5\x1b\xd0\xc6J\x9f\xa8\xbf\xcf\xb4C\xb5i\xff\xb4\xbfRx\xb2\xd3\x7fw\xe1?\x1a\x1e\xe60\xd7\t\xe1\xbf\xb2\x15\xe88&\xb2\xd8?\xcb\xb6\x98\xdd\x90h\xe8?1\x7f)4\xd8W\xe9? I\x08\x1c\x92\xa6\xf1?\xd8\xb8|-\xb5\x9b\xd2\xbf\xc8#.\xf6;{\xf0?4C\x00c\x07\x15\xe7?c\xc0\x9f\xd4R5\xd5\xbf\xb8\xf8\xec\x1f\xa3\x08\xed?=\x17v\r\xd1\r\xf7?\xfcH\xc7\x8f\x01\xfb\xf1\xbf\x996`F\x13\xf8\xe6\xbf:J~\xfd\x14%\xe9?\xec\x8f\xcd\x03\xc28\xdc\xbf"\xa3\x8b\x19\xd1\xe1\xe2\xbf\xe3G\xd9\x12$\xdc\xeb\xbfs\\\xac\x1c\x83W\xe6?mP\xa64\rO\x9b\xbf +\x82@\xaf\xd0\xe6\xbf\xb8}\xd5`@o\xf3?A\x9c\xee~\x82\xf9\xf3\xbfL\xfd,\x95I\x91\xfd?\xef;\xcdx\xb09\xe7\xbf\'\xac\xb4\xc3Yr\xe7?\xc9\xa9\x04\x99\x8b\xa5\xe7?\xe6\x92\xd1\x96\xb0\xd0\xd7\xbf\xe94\xfc\xc3\x12\xfe\xf2\xbf\x0b\xc1&\x94[A\x02@U\xaa\x96\xc4\x8c^\xe9?\xd14\xb0T\x86<\xd2\xbf\x85\xfft\xe5\x8b$\xf0?\xa2T\xbf^(3\xdb\xbf:\xfa\x9f\xa3\xa4,\xf8\xbfz\xdbU\xf9\x1a\xd6\xd0\xbf\x08\x92\xb3\x8f&\xdf\xf9?\xff\xf77\xcd\xfc\xda\x01@\xba\x7f^\x064}\x07@\xd2s[=\xa9O\xfb?\x1dv\xf4\x83\x86\xa0\xd8\xbfX\xab\xcdD\xd9)\xfb\xbfr\xf0\xc6\xe3j\x0c\xe2?\xb4;X?Qe\xcd\xbfx\x0f\x01CT\xd3\xda?\x19\xabc\xdduX\xe5\xbfG\xb5\xab\xdbta\xf6\xbfV\xf3\x8c\xa1*"\xcc?\xccw5\xcf\xe5@\xa1\xbf(\x04\xf5N\xde\x99\xe9?\xd3\x1c\xa7s.\x9d\xee\xbf\x8fW#\xc6FG\xf1?\xfd\x17\xf7{\x81\xcb\xf1?\xa2\x1c\xc6\xd0\xfa\x82\xed\xbf\xe0\x96>zLf\xa6\xbf\xe6\xd1\x10c(\xf5\xd0?Q\x1cs\x1f\x83\x0b\xf1\xbf\r]\xbd6\xdc.\xf7\xbf\xe9\x9e\xbe\x94\xfa0\x02\xc0\x91\xc9\xaa\xe4L\x08\xe5?l\x91\x1a\x81{\xbe\xfe?0\x14\xe4\x89{\xda\xf1\xbf\xfc\xb1\xb4\x00B\xe6\xcd\xbfV*\x03iZ\xbc\xa7\xbf\x87\x93,[\xb8H\xc4\xbf\x80\xf0M\x9d\xe0^\xe0\xbfq\x04\xf5\x83\xb3m\x01@\x8b\x1c\xb8\xf7\xbf\x97\x03\xa6\x155\xd7\xf6\xbf\xd5\x9f\xe7n\xe3;\xd5?\xd2U\xa0M\x03\x96\xc4\xbfv\x86zm\xad\x8d\xff?\xc1\xad\xd6\xf9K\xfe\xfe\xbf\xb9\r\x83\xc0\x7f\xcd\xf2?b$\xd3r\x01\x84\xd3\xbf\xe5\xd3\x11J\x80C\xfc\xbf\xbe\x06\x19\xed\xf6?\x88D>P\xd2\xcc\xff?\xfb\x99\xea\x8b.$\xf1\xbf#\x1aev\x07\x1f\x02@\xb2\x028(\x17\x83\xf9?,\xfa3\xf8\xeb*\x02@S\xcf.i\xe1z\xe9?"\xb6\x9e\xbb\xe1\xf0\xd4\xbf\xae\xced\x97\x18Y\xd5?{\x1fj\x8a9\xff\xf1\xbf\x07\xa5K\xb6\xa5\t\xfc?E]\x8d\x88\xc6\x02\xbc?\xb2\xb2\x81\xa52h\xe2?\xe8)\x16\x0e\x02\xdb\xe4\xbfz]k\xc8X\x8a\xe3\xbf>|\x99\x80.\xc5\xea?~\xe0\xf1\xe5\x9f\x19\xf0?@Sm5\xe4\xf2\xd6?A\x15\x82)d1\xd4?\xc5\x97D\xbcj\x8e\xf0\xbf\x17P\x14\x0f\x00\xf7\xfa\xbf6!\xaeFhC\xf2\xbf\x96lQo7\x05\xe5\xbf\x1a\xda\x03\xdb\xd22\xdb?\x91w\xf0\x0c\xafA\xe3?\xf7\xe8\xcbU\xb9-\xf0?\xe4UM\xe5\xbdv\xc3?\xb3!\xba\x97T\xed\xe5?\xb7\xeb\xeda\x04\x13\xef?\x8b\xc1\x9d\x97D\x19\xe9\xbfm~\x88\xb1\x1a\xb6\xe2\xbf\xf3\x00\xeePh\xe2\xea?\x14;\xfd3`\xc2\x94\xbf\xc4\xf7\xecO\xf5d\x00@C\x0c)\x89\n\x10\xda\xbf\xa7\xce\x10|\x90\xaa\xc6\xbf!\xc9\xc0`8\xab\xab\xbf\xc9\xfcR\x84\xe6I\xe8\xbf\x8a_\x1d\xd1\xbe\xf1\xf8\xbfu\xab\x12\x93\xd3\x0b\xce\xbf\x06\x82\x07\x05(D\xa2?$\xfc\xde\xac\xad\xcb\xba?9Gy(\xf1I\xee?\xdfg\'o\x82]\xd0?\'\xd3H\x15J\xd9\x8c\xbf\x9e}%L\xb5\xb9\xe5\xbf_g\xe4TK\x8e\x08\xc0\xcb\xe3\xe0\xae3\xa7\xee\xbfa\x0b\xab\xda\xd2Z\xd9?\x8f\xb4r]\xa1\xa2\x97?\xdf\xbf;\xcd\x13\x9a\xee?\xa9\x968\x94\x9cY\xe3\xbfd+#\xe4v\xc4\xe1?\xf4\x97\xe7k\x01\xab\xd0?\\\xe9\xc1\xcc\xd8\x96\xeb?\xa5\xa8\xf5E\xa2L\xea?\xbc\xb5\xc3\xb3I\xde\xee\xbf4\x1d+\x9f\xb6\xbd\x06\xc0K\xcc\xf9\x8f\x9ep\xf0\xbf\xcf\x0c\x8aN\x952\xe5?\x91\xce\xb1( \x90\xc9?\x94N\xb3\xe0"\xef\xfb?\xe0~\n,\xaa\xa1\xfe?x\xcb%\xe8}>\xaf?\xe9\x19\x8e\xb6\xfb\xe2\x08\xc0\xde\xef\xaf\x0b\xa7;\xfb\xbf \x82\xfe]\x95\x03\xcb?cH\x80\x08\x1c\x88\xd2\xbf\xff7\xbb*\xdbm\xeb\xbf\xd9\x92\x8f\x92\x0b\x8c\xec\xbf\xae`\xd5\xae\x037\xd9\xbfQ\x17\xfd\xc0\xe6\xf3\xcc\xbfl\xd3^\xbcE\xfe\xd8?#\xf0\xc8\xdc{b\xd5?\xef<\xafu\xcd\x17\xe4?\xa6\x1a\xb3\xca\x19\xc5\xb8\xbf\x15\xc2\x8c\x15%K\x02@\xe7\xbaD\x9a\xf4\xd4\xde\xbfco\x17\xe5,\xa9\x8e\xbf\xf8p\xe8m,\xfa\x07\xc0Fs\x81h\xeb\x07\xd8?\xf9\xad\xc8\xcc4c\xd7\xbf\x0c\xfb\x91\x19 \xa7\xf1\xbf\xd7\xe7\xc6\xddX\xd2\xf4\xbf\x1b\xa1\xa2sO>\xda\xbf|\'\x19\xff\xdc\xf6\xfe?\xc9wO\xa6U\x98\xf7\xbf\xd2T\xd1Y\xdb\x9a\x02@\x1b\x188\xc1\x07\xf7\xdd?\xf8i\xc3\xff\xd2\x9b\xba?\xc8\xbc\x02~[\xd5\xd2?t\xaaP\xc4>\xe1\xa5?!\xbcjo\xc78\xf2?\x97f`\xa0\xc6\xbf\xd0\xbf\xb5\x0c\x1e\xa2:\xa5\xf2?\x1ax\x99r\xecp\xca\xbf>\xc3w\x0c\xfd\x06\x00@@\x9e\xa1\xec\xb0Z\xc5\xbf\nc\x1dF\xafH\xbc\xbf\xea\xa26U\xadI\xe7\xbf\x95\xd3\xe1I\x82a\xfd\xbf\xefP\\{\xf5\xd3\xe6?\xc6\x91\xc5\xc8\x80\x11\x05\xc0r\xcb\x0cj:j\xa3?C\x83\xde\xf5:1\xe4\xbf\x80 \x8f\x96\xa7\x19k?t\xc9\xe67\xd2\xe5\xc7\xbfe\xcaMP\xbd\xc4\xd9?\xf1\x1e\xa7f\xeb\xba\xdf\xbf\xe7\n2k\x8a\xee\xdf?w\xed\xd8\xaf\xf11\xac\xbf%16\x98i\xb4\xf2?\xe1\x10\x15B6\xff\xf8\xbf\x1d[\x8e\x0ed\xb8\xf1\xbfZ\xa3W\xa5\xc9\x16\xc7\xbf\xff\xcb\x0c\x06=s\xe9\xbf_IbB\x13n\xdd\xbf\x81OlY?\xb2\xd1?\xcb\xda\xff\xa8\x1e\x91\xb4\xbf\x1cH\x8b\xe0n\x12\x00\xc0_\x07\xd4\x86\xaa\xfb\xef\xbf\x95~\'\xe5\xbd>\xc4?\xc9\x89\xc2\xf6<7\xf2?oU\xf5\xc6\xfd\x8b\xec?\xb6[\xc5\x0eAO\xf5?\xabr\x1e\x1e[\x0c\x00\xc0\xe1P6?\xc9}\xf0?\xc4S\xa6\x9a\xf4q\xe0\xbfC\x17Cp\x85Y\xee\xbf\x8c\xb1\x13\x1a2<\xc8\xbfQb-\x16\xc9\xb7\xf2\xbf\xac\x8c\xbc\xe1|%\xea\xbf<\x19h5Y/\xe8?4\x06\xb0\x99\x18\xb0\xd5?\xb93\xa4\x83\xf9c\xe2?\xdc3\xaex\x81\xe9\xc7\xbf\xa0\xae\x05\xff\x17\xbd\xd9?\xb3[L\x95H<\xd7?zs\x98R\xe5K\xa9?\xa3\xd9W\xd3\xb1\x0c\xd8?\xb1\r\xef\x87T\x90\xb7?DR\n\xa0\xb9i\xe7?\xba\x1f\\w\x02~\xfd?q\\(uM\xd0\xa9?\x88\xdelz\x1a\xcc\xf4?\xee:1D\xf4;\xf3\xbf?l\xf3\xe5\x19U\x01@\xc3\xac\x8ej\xa0/\xf3\xbf\x12x\xbb>\xd7\xf5\xea\xbf\xd7\xf0\xc5\xbc;\x85\xf4?\x82\xdc\xa4\xe8g\xc0\xe9\xbf\x90\x17\xa5\x90\x9c\xd3\xf9?NJ\x112\x1c\xe1\xd4?C\xebm_\xab/\xd8\xbf$\xca\xa8\xee\x9d)\xec?\xe9\xb6u\xd0\x17=\xd0\xbf\x96\xdfq\x17\x03-\x8c\xbfy\xd9\x0bP\xf04\xa3?\xb2\xe2\xfb\xc2dW\xf3\xbf#\x84"\x11\xc9"\xd4?\xee\x8a3p\x92\xd9\xb2\xbf\x9d\x98}\xf6\x99\xca\xd2\xbfs4\xd9\x83\xc2\x9c\xe2?\xa1_m\x07\xd7\xae\xf2\xbf\xd8\x17\x95I\x0b\xf1\x99?\x81)T\x03\xf2\x00\n@\x03L/\xfd\xa2\xd1\xcb?k\xc5;\x1b\xcc\x96\xc8\xbfv\xc4\xd8\x02\x8d\xc3\xf8\xbf&\x01\xe5\x87\x93\x0f\xc3\xbf\xa8\xb9\x9d\x90\xc3\x1a\xe7\xbf\x84\x81J%\'\xa0\xee\xbfK\x82\xd7\x1f\xc1\x1c\xf2\xbf\x9f\xf0\x95\xcc\xb4\x87\xd0\xbfep\xdb\xb1!\n\xfd?\x07\xd7\xa4\xfa5h\xd8\xbf\x9bI\xb51\xa0z\xe7\xbf\xc88\xc6\xe3\x9a\x07@{M\xdd\xbb\xfa0\x97?\xbeR\xe9\xf6\xd6f\x01@\x13\xe8\x98\x8f\x06O\xe9\xbf\xbe \x18b\x82\xaa\xe2\xbfx\xb8\x7f\x84\xdcW\xf9\xbfv\x14\xf2\x02\xa1J\xe7\xbf\x1e\xc9\x83\xb9\xe9s\xf6\xbf-}\xdf\xb2\xad\xab\xef\xbf\x81\xe7\xf1H\xb9\xb4\xfb?\xa0\xdfj\x1c\xd0\xd8\xf0\xbfHj\x82\x8e\x10\x1f\xef\xbf\xdc\xed<\xee/\xf0\xe6?\xf1Q\xe3\xb4\x10\xd7\x08\xc0\xb5\xfb\xe3\xf8R\xb8\xea?\'\xd6\x17\xc07\t\xe2?9\xe4\xd2X\xb6u\xe4\xbf\xf5i\xbb\x8fs\xb8\xf8\xbf\xeenO \x1e\xc3\xeb\xbf\xcfS\xd7\x19\xd9\x91\xe0\xbf\xf5\xa6o\x00\x86\xfb\xed\xbf:\xfe\xa0\x040\xa1\xeb\xbfO\xe4R\x8e%s\xf6\xbf\xe7\xbb\xc9\xe0\xf5x\xc4\xbf\x00\x13\x816.\x82\xfe\xbf\xe6\x89.h\x13<\x00\xc0\xbb\xedY*E\xc3\xe6\xbfF-X\xd7Z.\xfb?y\x1a`g,2\xfc?I^\x92s\x13{\xf0?\xc5H\xef&\xdf\x82\xfd?]\xd1u\x18\xbeo\xb1\xbf\x1c\x123\xb6 \xbd\xdd\xbf\xa3\xa2\xd9\xb8\xd6\xfb\xc6\xbfHd\x18\xde\x07\xfb\xdd?\xe46\x92\x12\xc6\xb5\xd1?\x81\xb8\x89\xc7\x01\xc1\xe0?\x8f\xa22$\xd1\xf7\xf7?Z^?\x9b\x80\xef\xf4\xbfy\xd5`y\xe4\x86\x89\xbfS\xa82\xe9\xce\x9d\xf7?F\x93\xd8\x14\x16\xb0\xde\xbf&\xe3\xe2w\x13\xb2\xc8?8\xaaCI&\xa8\xef?\x1b\xa9w\xad}Y\xc4\xbf\xe2\xdc2\x89\xaf\xe8\xf3\xbf\xc9\xd4\x9f\xd4p\xc2\xe1?J\xcd\xb6\xbf<\xc1\xf0\xbf\xee7\x1e}\x8f\x83\x0c\xc0\xab\xf3\xed\x19\xa9Y\xc3?\xa0\x08\xd6\xd5\xc7\x9b\xf1\xbf\xf7\xac\xc5:E\x05\xec\xbf\xe2\xdb\x11"\xe2\xc7\x0e\xc0H\x18x\xac6\xb2\x00\xc0Rr\x11o\x8f\xde\xad?YF\xee(\x8cu\xe2\xbfY\xb7\x9a\x1b\xd8\xac\xf3?%\x03\x84B>W\xf2\xbf\xc9\x8c\xf7\x92\xc0\x18\xd8?\xb9kbN7V\xe2?\xcf\xe7\xad;\x0e[\xe6?\x18\x94X\x9b\xea\xab\xb7?\xaa\x88\xbdf.\xa4\xe4?.\xc6\xc81\x91(\xe3\xbf\x03\xcb4?UN\xf3\xbf\xf2j\xa2\x007\x01\xe8\xbfM\x89j{\xd8\xc6\xe3\xbf\xcc\xe7\xf5\x9dtE\x03@\xf1\x15a\xdb3\x82\xe3?Ny\xbb\xaeG\xda\xef\xbf\x9cx\xbfVY\x8e\xfe?\xdf\x8d_<\xd9\xc8\xed?\x8d\x98\xb1\xb1-\xf9\xe4?\x08\xe5`\xdd\xd4<\xdd?\xe5_\xacu\xfdq\x01@,\xdf\xb5\x18\xe2"\xf2?\xdb\xea>\xe8\x99W\xe9?}\xf7\x1d\x17t5\xe2\xbfF>\x18\xefP\xb3\xfa\xbf\xfd^\xf9\x83\x80p\x06\xc0_\xcdcK\xb5\x9e\xf6\xbf\x94\x95#\xb5H\\\xfc\xbf\xe8|\xfd\xf2\x86o\xe0??\xa7J\xcby\x8c\xd0?\x1a\x9b\x926\xa0T\xc4?\xd4\x83\xbd\xe4\xb6\x83\xad?\xfb\xc9\xba\x05\x8d\x85\xf5\xbfk\x15\xabvq\xc1\xec?\xbb\xa3\xc5\xb8[D\xa6\xbfT\xe2\xd4\xff\x99\x8e\xe2\xbf\xbe|UPH-\xf6?\x04d\xf88\xcf\x14\xd8\xbfu\xf6\\;\x1a\xac\xd1\xbfU\xe4O\x9f[\xdf\xf1?d\x92\xb9\x96d&\xf2?"l\xa8ci\x19\xc6?e\x04i\xc1\xaa&\xe6\xbf\x06\xdf\xbc]\x1f7\xef?)7T\x9f\xd3k\xff?\xbf\xcc\xb8\x02n\x95\xe0\xbf\xec\xd6\xd3\xb7\x81p\xb6?\xd4\xbd\xbc\x90c\xfd\xf5?n\xf6\t\\\x1c\x97\xf6?kx\xa9\xe4vw\xf3?5rJ\x95#\x87\xc7?\x96\x8f\xee\x8c\xf57\xf2?\xf4\x01\x1b\xf4:F\x01@V\r\xbd\xc8[\xbb\xe9?\xd6\xc9\xaf\xfd\x82\x9d\xd2?EF\xb5\x11 e\xf7?G\xdcy\x0f\xce\xbb\x01@\xcf;D\xe6\x01\x0e\xe9?\x0c\x94${\xf0f\xf8?\xfa\x92\t\xdc8\xc1\xe9?F\x02\x84a[\xcf\xcd\xbf\xc1\x84\xbc\xaftV\xd1\xbf0\xf0F*\xd6P\xe7\xbf&Dm\xb5\xe3O\xd3?\x80yl\xc2*\xe7\xee?\xc1\xdf\xa7\x90\x84\xc0\xc6\xbfa\xf0\x19\x9b\xb5\x9f\xe9?\x1d\xc0Sw\x1am\xf7?S\xb4\x13\xcbCG\xd7\xbf\xcc\x91\xb3\xeb\x04l\x01@>\xf4w\xeb3\x8f\xff?\xe3\xd4\xca0\xa6\x92\xd1?i"\xdd|\x15^\xc2\xbf\xd1\xc9t\xa8u\xc0\x00@\x0c\x05\xc7\x90m\xf2\xfa?\x93\x8d\x97\n:\x1f\xc2\xbf\xa9X\x9a\x178\xb2\xdf\xbf\xff\xaf\xedr\x99s\xe7\xbf\xc9Wa\xf8:]\xf2?\xd8\xa8\xe4\x0e\x84\x98\xdc?}\xf2\x81De\x16\x0e@Ci\xf2\xa4s\x85\x06@\xfb\xd1\xe1i\xdbU\x0f@\x86g[\xe8`\xb7\x14@#Bm\xf1\x04\xf6\xf3?\x05\xa1\x16\xef\x06W\xd4?n\xd7\x91\xf2\x02;\xf5\xbf\x9bG\xae\xc0\xed\xba\xdf?.\x01\xaa\x94\xd1\x05\xe6\xbf\x9eQ\xe5\xe1\xd9"\xd3?b\x8c\x00\x91\xfb\xf5\xb4?"\x80l\xb4\xd0\xfe\xf0?\xa6\xd7\xf7\x94g\xb9\xe3?7\x17\x93\x02@d\x1e\x1c\xe8\xcdM\xf0?\xf9T\xfa\xbfHo\xe0?\xa4\xcb\xbf\x00\xd3\xfe\xd0\xbf\xfd~l\xfa\t\xde\xf4\xbfg=\x9b\xe9\xa0e\xec?\xe96\xe65-\x95\xaf\xbf\xea\x84\n\x9f\xb0{\xb2\xbf\x15\xd9P\x0c\xdd\x84\xf8?\x1e\xec\x0f\x9d\xe7\x9b\xf2?hNf\xcb\xe1)\xee?`\xb2l\xc8\xe2\x95\xd0\xbf\x12\xe8\xb5\x9a~w\xf4\xbf\xc0\x12\xd2\nH\x15\x03@\x0c\xd16\n_\xe2\x10@%\x8dm\xe7aR\x05@>Uq\xf5w\xb2\xf6?\xf8c\xca\xc3\xa3\x99\xf9?\xcd\xe8\x90\xfd(\xb0\xdb\xbf\r\xdd\x04C\xe2\xec\xec\xbfU\xa9\xf0/C\x1a\xe9?G\xd2.y\x15\x80\xf7?\xe8#O\xd6\x95\x89\xdf\xbf\x02\xb6\xd3.`\xde\xfd?\xf9iCF\xa6?\xf2\xbf,n\xccj\xab\xe4\xea\xbf+\xac\xb9\x84y\xd7\xe4?\xd9\x12\xe5`\x11 \xef?sH\xd1\x17\xb6\xf5\xe3?\xf0h\x16\xbb\x96\xce\xf5?\xe1G[$\\\xd6\xda?\xef\xb8Z\xb9\xcaE\xfb\xbf\xed\xf3\x1d|v\xa2\xf3?\x15Ws\xca\xc5\x10\xf6?\xa6\xe8^\xa8\xed\x1b\xf0?Q1\xf2\xb99c\xde?\xab2\x11\x82a\x92\xdf\xbf\xf5\x8fT\x92>"\xfc?\xca]\xa6aL\xcd\x04@\xc8\x83&\xbcB~\xe8? \xdfaFYw\xcc\xbfw<\x8b\xb8\xce\xb3\x05@6\xa6\xb4\xb2\x93\xfe\xeb?k!\xe9\xcb\xc3~\xee?b\x0e\xee^\x1c\xba\xd4\xbf\x81\x97\xf6\xd0\x8b\xb4\xf0\xbf\x90B+q\xd2.\xe2\xbf\xf2\x82\x82h\xdd8\xea\xbf\x19]\xfa\x92I)\xeb\xbf\xf1\x06\x9b\xb4\xd0\xb3\xce\xbf\x9fa@Lw\x02\xd3?\xe4\xe7\xc7\x1b\xea\xc8\x07@se\xe7\x13\xef\xa4\xe3?\x08K\xe4\xb2`!\xd9?\x92\x9c\xffJ\xc4\xae\xe8?\xf7\xf7\x9c\xbbJ\x9c\x04@\xd7D\xb2+w\xd5\xe6?\xea\x07+\x8f\xb2x\xe3?\x8fF\xc3=\x84\xc3\xe0\xbf{\x8a\xba\\L\x14\x01\xc0E\'_\xaa\xff\xc2\x02\xc0+\xf9.\xc9\xf5\xdc\xcf\xbf\xab\xda\xaae\xfbl\xa6?\xc8\xb1Q \x82\x9c\xc6\xbf\xee\xb6\xd7Vh\r\x01@N\x01\xbb\xc8\x8ey\xf9?\xcdQ\xac\x83t\xe8\xd3\xbf\x92t\xabzL\x03\xc9?\xc8\xa2\xc9[\x08r\xe2?r\x12\x1fQ3\xa9\xdc?\x1cM\x86\x198\x1a\xd2\xbf \xd5Q\xc4\xb4*\xf7\xbf\x07\x05\xc0H}\x14\xe3\xbf\xb2,c\xe2EK\xcb\xbf5\xfa\xed\xbb\xb2\xfb\xf0\xbf\xdc.\xd1\x8con\xe4\xbf*\x96{Pi\xea\xe7\xbf1s|F\xe8N\xfd\xbfc\'S\x8d\xa2\xe2\xe5\xbf\x0f%+\xa4(\xa6\xeb?\x12\xe9wZv\xd9\xe2\xbf\xac\xd3\xd3\xbe;\xe3\xdf\xbfS\xbd\xa9\x18\xb4\x02\xf4\xbf\xab\xc1\xc5;\x86\xb2\xe0\xbf@A\x91 Du\xa0?\xce\x17/\x92\xb5\x9f\xdd?\xa5[hB\xf41\x94?\x1fc\xcc\x96n\xe6\xe6\xbf\xc6\xf5\xbb\x92sY\xf4?#\x93\x89\xaa\x9f\xbe\xc2\xbf\xf8\xa3\xdbQ\x84/\xf4?\xc7\xd4\x9f\xbe\xf29\xee??\xcf\x01\xb3\x0f\xf2\xf8\xbf\xe9\xa3\xbdt\xceS\xea\xbf\xbe\xe6\xd1\xf9r\x05\xf6?m\xe9\xae>Je\xe7?"F\xc9\x9aS\x1b\xfa\xbfG\xbcGi\x9a\xf3\xed\xbfi\xd5\xe0\x90\x8a#\x03\xc0\xe9p\xf0 AQ\xe5?\xd2\xe1S\xe8\t~\xd6?A\xa7\x88\xd7\xcfU\xe8?\xe4J\x0c\xa9\x8fo\xec?\xa7Xt\x19\xdb\x11\xff\xbf\xa1NO\x97\xc1G\xf1\xbfD\\\x02\xc8q\x95\x03\xc0\x80dM\xbbO\xdb\xf4\xbf\xf4\xe4\rW\xee\xb6\xf8?3k\xc7\xadT\x08\xf3\xbfF\xd2\xd3\x83_\xb1\xd8?\xfb\xe5\x87?\xe5A\xd9\xbf\xf9\x8e\xe5\x0e\xec|\xf9\xbf\xb9\xc7\xb3\r\xc6\x94\xe7?R@\xe4\xed\xaei\xdc\xbfG\x1f\x82\x80*l\xe7\xbf r\x19X\xe3\xf5\xf4?\xd7\x85C\xa0i\xc9\xd0\xbf\x98\xdbS\x97\xa8\x8b\xb4?\xc4]\xee\xd7&\xb5\xf9?\xa2\xcc\x94\x11;\xc3\xe0?\xb43\xc4\xb2\xcf\xc6\xf4\xbf\x8d\xaf\x1f0\x94\xc0\xee\xbf{\x0e\x19\xff.G\xee\xbf!\xf3\xc3-\xce\xfa\xe4\xbf\x8aD\x1cD\xf1\x0b\xf2\xbf\xc0j\xd38\x01U\xe1\xbf\x039\xcbw`\xcb\xe4\xbf\x88\xc3p|\xbal\xfb?\xcd\xe3\xea\x8b\xd8,\xe3\xbf\xe3\x80\xf9L;~\xf6?\x88mr\xb0I\xd6\x97?\x8d\n\xb4\xbe\xe9\x1d\xf4\xbf+\xda\xe0\xb3\xdf\x9b\xf0\xbf\xd7\x81\x96C/\xcd\t\xc0\xd4s@\x96\x96\xd6\x98?\x99\xab\xd5\xc0\x1b\x9e\xe2?2^\x0e{\xa1\\\xf7\xbf2\xf4_\x7fge\xca\xbf\xf8\x85\x83\x9b\xddg\xec\xbf5\xfd\x1au\xa5\x8c\xfa\xbf\x00O\xc1\x191\x05\xf3\xbf\x1d/\x9bb\x07V\xfc\xbf\x17\xd0F-\xf4y\xfd?\xc2\xc1nj\x88\x08\xfb?\x97\xf0r\xea\xe4\x8b\xd2\xbf\xe5\xcd\xfd\x924\xda\xfe\xbf_dMQN\x86\xf1?\x8dH\x17\x17\xc1\x13\xb5\xbf\xb3|\xb37\x0f]\xdc\xbf}\x1bbN\xe7\t\x04\xc0\xf7k\x1fN\xe2<\xcb\xbf\x16,k\xd4\xbfg\x00@`$\x8bL\xc6\xbb\xe7?[e\x0e\x1cq\xe0\xc4\xbf\x82?\x843\xc2v\xe0?\xee# \xc8ae\xcc?\xbfT\xe5\xec\xcb\x93\xb6\xbfa\xfcf\x07\xff\x0e\xe9\xbff\xd72\xb3\xd9\xdf\xe9\xbf\xab3^\x87zK\x01\xc0\xa6\xd5G\xf2e\xbf\x06\xc0\xea\x17\xc3(\xf6\x11\xfa\xbf\xcd\x8f\x95\x04\xe7\x87\xf5?.\xce\xdd\xe4q\xa1\xf6\xbf6\x95\x08\x08\xcc\xe5\xe2\xbf\x9d\xa0\x84w\xcd\x93\xed\xbf\xc2\x01\xe8Ak[\xd0\xbf\xd6\xb2N:o>\xf2?!\xf3\x08F\xb1t\xe7\xbf_?r\x8a\xb2\x1f\xd7\xbf\x93\x8ao\x80\x83B\xd7?Bw)i`4\xdb?q\xda\xff\xdfG\x17\xe8?\xb5\\\xea\xba\xccV\xd0\xbfWc\x90\xf5\xcb\x85\xdc?\xca\xa4\xeei\xba\xa3\xb4\xbf\xb1\xf9\x0b\xbat\xac\x03\xc0\xe8CfOJ\xd4\xee\xbf\x12\x97!+\xcd\xf4\xd0\xbf\xedo.\xf5\xa7\xf4\xc8\xbf\x03\xd2~\xb0\x0eC\xf2\xbf~\x86\x9a\x8dPm\xe2?m/c\'\xd3\x80\xd2?\xb7\xce\x84\x1c\xd6\xaf\xf5\xbfw\xcdK\x9c"\x94\xd3?\xd5\xc4T=\x8c\x15\xf1\xbf\xf6]{\xa7\'\x85\xf1\xbf*\xcd!?]M\xf0?\x9b\x9d\xee\x867\xab\xec?*z\x17/\x97\x11\xe4?9\xa4\x16\xf7\xc4$\xd3?>t\xe0\x00\xb9V\xfe?g\xa4\xbeO\xf2\x99\xcf?\x86y\xf7>\x0c\xce\xe0?\xad\x81W\xef\x84\x96\xf0?\xc3It\xbd\xee\xb6\xac\xbf\xcd\x83m9\ne\xd0\xbf\n\xdbN+S\xaa\xe9?\xe5[\xb0\x94\xe64\xcf\xbf{\xaf\xc7\x14LT\xad\xbf\x90\x03 \x13\xee+\xdf\xbfS\xfe\x93Nx\xa1\xf0\xbf\xf5\x07A7\x11\xf3\xe6\xbf\x80T\xe7\xc8\xd8$\xd3\xbf\xb2|fBR>\xf7\xbf2\xe95\x14n\xbd\xfb\xbf\x87\xa2X\x18\x19\xd0\xe0?\x81\xff\xef\xdaH\xa0\xe6?\xf1]lY\xad\x8a\xf4?\xdb\xc9\xc1}\xe2\xde\xe7\xbf\xc7\xce\x80\xdfR-\xf4?\xb09\xc8\xb0\xab\xbb\xe4\xbf\x9e\xbc\x9bwj\x9e\xf2\xbf$y^tr\xb6\xbf\xaa\xeaY\x03\x80\x07\xd6?{$\x14\xbc\xa4U\xea?2V\x86z!\x90\xd0\xbfW\x1d!\xc59\xea\xdd?\xc8x\n\xe5([\xe6\xbf7\x85\xda\xa6\x11\x81\xe3?\x84l\xc9\xfa\xe8\x17\x07@\r\xd7r\x0c\x9e\xaa\xe4\xbf/\xf9A\xb8\x08q\x03\xc0o\x91\xcb\xf2\x94\x7f\xf5\xbf\xdeqo\x84g\x94\xdd\xbf\x0f\x14&\xa9\x1f\xfb\xfd\xbf1\xa8\xf9\xac\xf0X\xe3\xbfE\xba\xd0\x06\x0b\xb9\x02\xc0m\x8c3\xc4\xa6\xc5\xed\xbf?\x0c\x92c@q\xd6?:\xb3\xe3\x13P\x05\xc8?\x9d\xd3\x07ly\n\x01\xc0y@@\xa1!\xdf\xb5\xbfVq\xb2\x01\x86\x88\xee?\x9ck\xc7$\x84/\xf4?7\x16\\\tH\x93\xf6?\x9e4\x13h\xc4\xe2\xe0?:\x1cR\xbb\xf4\x9c\xea?t\x81\xb3\xf4$s\xf8?\xb8\x15l\xee\xca\xb7\xe3?(\xf3\x05\x12Aq\xea?\x86+\xbb5\xd9\xf3\xee\xbf\xedc;\xc0\xe3\x0b\xe1\xbf\xf6\xef@v\xb6\xe8\xe5?\x8fT\x8f\xd0\x08\x89\xe7?\x9d\x91\nm\x035\xd2\xbf\xdcM\x15\x94\xd9\xd1\xe2\xbf8\x87@`\x91l\xe8?\xcc\x1f\x9a\xae\x82\x98\xf7?\x10\\\x9f:\x88\xa0\xf3\xbf\\\x01x:\x99\xf6\xf9\xbfG\x01\x0eXU$\xd8?\xec\xee\x8cdN\xd8\xf2\xbf)\x937c\xcf\xd8\x00\xc0?\xf9^\xb1Hn\xf2\xbf\x99\xa5\x14g5\x00\xf4\xbf\xddk\xa7\xd2We\xf0\xbfc\xab\xdd\x89\x96\r\xf4\xbf\xcb\xde\xf5NW\x89\xf4\xbf\x12\xed\xab\xbc\x16\xd1\xee\xbf\xde\x02w\xab\n\xc7\xea?\x08\xe9+$e\x90\xe2\xbf\xceSH\xf3\xc4c\xe1?\x1d\x12\xe0\xc1\xa8\x11\xe9?\xee\x07z\x8f\xfcF\x00@\x8d8\x02\xf3\x9c\r\xe5?\xe9\x1egL\x83\x85\xef\xbf\xe7\xd0\x00t\x90\x07\xec?j\x1d}4\x84\x1e\xf7?\x8df\x0c\x1fm\x18\xcf\xbf\x0b\x02\xed\x84i2\xe8\xbfi\xd0%\x14.\xb0\xe3\xbfi\xed\xed\xf5\xa8\xd2\xf9?\x8f\x8b&\xe3OC\xe4\xbf\x99T\xfc\xc60\xf3\xa7\xbf+\xb2\xe2P_\xce\x02@\x94&\xdd\xf0\x88]\xe8?,\x0f\xe4\xe2\x12m\xea?\xf8\xbc&K\xb3\x83\xeb\xbfn\xec\x8a"\x0f\x8e\x89\xbfa\x92I\xf7\x0b/\xfd?|\xc9B\xb5c\x04\xe4?~57\xcc95\xee\xbf\x15\xb4c\xa4\x07~\xe6?R\xba\x7f\x84b\x80\xed\xbf\xe9\xc6&reZ\xcb\xbf$\x1c(\xfb\xe6\xe4\xf8?\xeb#E\xa6Oz\x08@+\xfe\x0b\x84Z\xec\xcb?4\xbfD\xe7\x99\x98\xd4?\xe6(\x99\x0b)\xa4\xf2?\xca\xf7\x07*\x8f\x11\xfb?^F\xc4\xd5\xa5\xf2\x96?\x08\x02\xbb.@\x0c\xc7?b\x82\xc9\x93`\x85\x02@\n\xbe^\xf6f\x91\xfe?G\xec1N\xeb\xf9\xf1\xbf`\xec`\xac\xbb\xbc\xfd?)\xc9\xf1\x17H\x05\xe3\xbf\xea\x8ax\xdc\xb2\x14\xee?\xd2\xdb\xdb\xe3dq\xf3\xbf\xe6I\x96W^6\xf8?\xe7R\xe2\x82"\xbe\xeb?\x85\xbb[\xb1\xf0+\xeb?@\xfb\x87q$F\xcc?\xc8\xe1a\x06\xac\x80\xdc?\xea\xa1\xef\x00d\x9c\xe1\xbf\x9aI\x88\xea\xa1P\xc3?\xe4\xb3\xfa\x1aZ\x05\xc0?\x8e{\xa5z\x81\x85\xc1?\x1eZ\xe7\xf6p\xb8\xc3?\xe9\x1e\x0e\x12:S\xc4\xbf\x84\xde\x8d\xc8\x00\xe8\xc2?V\xee\xbcr\xbbV\x00@\xf8UJn\xa7\xb8\xf8?-\r\xc4\xe0k<\xeb?5\x96\x158\xc0g\xf5?\xb1\xed\xd2\xa2\xbd\xf8\xe3?\xf6\x8cS\x7f\xbe\xac\xf2?\x93\xbdN\xbb|\x10\xc9\xbf\xd8\xae\x81\xfc\\e\xf8?^\x992\x96\x87\xe5\xbf\xde;\xf8\xed?\xe5\xa4\xbf\x8b\xbe\xce\x03\x1eT\xc6?[\n=\x86\xc3\xfd\xec?\xc7L\xcd=M\xfc\xca?\x1bn\x192\x82\xc4\xe2\xbfD\x82H\xc2?\xa5\xfc\xbf\x9b\x92IO"m\xec?2\xce\x11\xf2\xec\xe1\xe2?\xe8\x89%\x1d\x0cV\xa8\xbf\x7fN\x1a\xc6\xda\r\xca?\xcb!\x1bJ\xba\xf8\xe1\xbf+\x1b}\x82H\x87\xe1?%7\x1d\x1e\xc3\x7f\xd7?V\xf8\x97\xeeN\x16\xc7\xbf\xba\xeb\xd2\xfa}5\xe6\xbf\x1e\xd1\x1a5*E\xf0?\x05\xba\xefj\xe4x\xc0?O\xc9q\xf6\x8e\xd6\xc7\xbfn\xa3(\xcb9W\xe6?\xbe\xcb\x1c\xc2\xaf?\xe6?\xcc\xc8:Ud\xe7\xb8\xbf\x8d\x96\xa3\x10Yj\xe7?\x16\x9c\x11Bd\x19\xed?\t\xbc\xff\xdc=t\xd5?\x87S\xc8\x07U\xdd\xe0\xbfB]\xec\xd9\x19-\xea\xbf\xa1#UJ\xd4\xfa\x02@l\xe0\xd9\xed\x95\xd4\xd0?\xd2\x03\xc6\xbc\xb0\xda\xf0\xbf\xd8\x8f\xee\xf4\x9c\xbe\xdf?\xdf\xfa\xd2W\xb3\xde\xdb\xbfH\xcf#\x8d\x91\xb7\xdf\xbf\xbf\xec\xab\xfdT\xe8\xdb?\xf1bZ\x1a\xecg\x0c\xc0\xb2M\x1a\x8d*!\xea\xbf\xee([CFH\xeb\xbf\xb3O\xe0s\xb2\xdb\xf3\xbf\xbd\xbf\xd9\xd9M\x19\xff?)[\n\xdd\x86;\xe7\xbf\xd9\xc7\xf0\xb3`t\xe0\xbf\x91\x99\xeb\xab\x8e\x03\xb5\xbf[\tL4\xe29\xf0?\xdbLc\xc0z\xce\x06@\xb1R\x1f\x93Q\xa7\xf5\xbf\xdb\xb6\x9a\x86\x16\x14\xea\xbf<\x97C\xf8\x1b\x89\xca?n\x89\x0b0\xe3#\xa2\xbf\xcb\xa2k\xfdHr\xf5?\xea\x93HE\xf7\xc8\xf0?\xb0\x1f]\xda\xf5D\xd6\xbf\x1dW\x01Jr/\xe2\xbf\xcda\xc2\x89\xb1\x94\xb8?\x85"n3N\xd4\xf3?;A\xe7\xbf\x12L\xcc\xec\x02\x90\xdf\xbf\x02\x03\tt\xad\\\xe9?M\x0e\xa8\x12\xfc\x84\xdd\xbf\x86\xba\x07r@\xd1\xfc?\x96\xc8l\xc7h\x10\xd9?lb-\xf9\xbe\xbe\xed?\xa7I\xf1\x0bZ\x9a\xeb?\xd0\x99#\x12\x85\xdf\xda\xbfr\xdb \x1c\xa8;\xe0\xbf\xa7\x85\xd6\xee_\x1e\xf3?\x8a];\xda\x8f\xd9\xe9\xbf\xaa\xea\xa3\xec\xf6@\xe3?k\xd1\xe68\xa0\xc9\xc2?O\xe5\\\xcb\x9e\x1c\xf6?\xe5\xeb|r\x8d\xce\xa8?\xa6\x81\xc1\x96\x88\t\xb8\xbf\x07\xe7\xc4n\x11\xed\xa5?C\xec4\xe1\xed\xf0\xd4?Kwg\xbf\xa5\xbf\x9f?\xa6fV\xf0D\x0f\xdb?\x0f\xe3\xcd\xa6\xc8\x12\xf6\xbf!t|\xf8\\\xfa\xeb\xbf\xcc4Y\xcbS\xed\xf1\xbf\x1a\xd9K\xb7\xea,\xcf?\x94\x07\xa8!F\xfe\xd9?\xa4\xca\xe0\xaaA\xc2\xff?/"\x170=\x0c\xf9?\xd7\x7f=\xa1\x1aP\xe1\xbf\xa8\x01\x07g\x17j\xfa?\x03\x87\xc2\xb0\xa8\xb1\xf8\xbf\x87\x11\x85\xa7EX\xcd?\x8a\x8d\xc0\x86\x89\xd8\xdc\xbf\xcb!\xcf\xd4\x17\x16\xe5?\xac\xde\x17~f\xd6\x00\xc0#sFX\xb1\x12\xee?C\xc6u\xb7\x1fE\xfb?\xb7IUo}\xb8\xf8?\xef\xea,Bhe\xfb?\xb5.mZp\xec\x84\xbf\xb3\x8dM\xbd(\x15\xea?$\x16\xac$\xa11\xa4?$\xeb\t\x157l\xd1\xbf\x16_\xc9\xff\x0b\xb6\xd6\xbf\x92%\r\x9a"\x84\xf7\xbf\xb6\x1a|0-P\xea?\xd6\xfc\x8e\xe0\t\xaa\x9f?\xbc\x80\xf1\x9d\x1e%\xf8?\n\x9d~4\xd6\xc7\xd5?\x8aeV\xc2Ib\xe6?\x97\xda\x08m=\xe7\xf7\xbf\xbf\x0e\x96Q\xdf\x1a\xdf?\x911\xd1\xcdw5\xe2\xbfF\x7f\xadf\xe7%\xfa\xbf\x936\x8e\xb8|{\xd1?ky\x0bX\xf5\xf4\xc0\xbf\xbe\x1fP\x99\xe5r\xd4\xbf\x95\x8a\xf2\xee\xea2\xe4\xbf#\xfd\x86g\xd5\'\xa0\xbff\xb5\x0c\x04\x12\x89\xc9?\xc1\x84\xbd6\x17\x04\xe6?!\xf7\xa5\xa3\xad\xf4\xcb\xbf\xed8\x7f^\xa22\xcf\xbf\xf9\xee)\xc7\x9cG\xf3?_\xef\x90U\xa3\x93\xe0?\xbf`\xea\x84\xf4\xbb\xb2?q\x97F\xe9\x033\xce?\x19\x7f\xbb~\xa6\xee\xdf?4\x12y\xa9k\xa9\xf0\xbf\xd8\\\xbd\x8b\xd1y\xcb?\xea\xfe\xce\xea\xec\x18\xda\xbf \xa4\x88\xe1\xa7l\xdf?x\x81Z\x99)E\xc9\xbf\xb7\xc9\xf3\xf3\xac\xba\xd0\xbf(\xdf\xcb\xfc\xeb\xaa\xfa\xbf\xfa\xa2\x1e\xb0\xea\x9f\xf2?#&3\x87|s\xdc?K\xeaE\xf8>\n\xd0\xbf\xb3\x13\t^=T\xd0?\xc7\x06\xdam*m\x01@nt.S\x0b\xf8\xcb?\x02$\x15\xc2\x87\xf4\xf3?\'F\xbc\xc2\xcb\xc4\xde\xbf\xae\xea\x9d]1!\xff?9H\xf4\xa5\xbcA\xf6?\x91\xb0-\xeb\xa3s\xfe?\xc2\xd7:w\xc9\xc9\xdb?:\x156m.&\xd4\xbf\x16\x81\xec\xbfNK\x89<\x8e4\xf7\xbf7\xfbh_ld\xcb?nvO\xea9\x83\xfc?^\xf0\xc4.\x1cm\xe3\xbfU\xa5\xae\xb3U\xe4\xb4\xbf\x041\x8c\\-\x90\xe7?\x97\xec\xd4\xf1g\xcd\xf4\xbf\x1dz\x18\xd8y\x13\xb9\xbf\xc2\x166\xc7\xec\xf3\xf2?\xb5\xa0\xc3\x04n.\xf4\xbf\xd0\xf01y\x9f\xab\xe0\xbfa\x96\xec\xf4\xb1\xcf\xca?\xad1R\xbb\xd0}\xda\xbf\x8a\x8e\xc5_\x89.\xb7\xbf\xc9\xec\x17h\x01B\xd2?\x87\x14\xa7`\x143\xd6\xbf\xbf\xec\x8c\n\t\x89\xf8\xbf\x8d\r\xc9i\x17B\xe2\xbf|\xf0\xb2I \xff\xd6\xbft\xa8D\x89\x07\xab\xf6\xbf\xf3\x18[\x83\xac<\xc7?\xd3oM\xae\x08\xe7\xb1?g\xb3\xda+\xbc\x04\xe1\xbf\x85\x14\xf7\xa77g\xfd\xbf\xae\xab\xa3\xd9\xea\xe4\xcc\xbfg\xf5\xcd\x1a\xb06\xf8\xbf\xfc\xbd\x820\x81i\xed\xbf\\\xda\x97zE\x04\xef?\xe1\x0b"\xe8%\x81\xd8\xbf\x03\x9c!\xd1^\x7f\xb7\xbf\xe3E\x91\\\x98\xc4\xe3\xbfm\xbf0v;\x12\x05@}\'\x16\x11\x9aa\xf7\xbf\x8b\xe7xHE\x15\xe4?\xbepO~\xe9\xa9\xd9?O\xb1\xd2D\xd2\xcc\xd4\xbf\xc8\'Bz\xa2\xd7\xe1\xbf9\xceN\xb7\xdd{\xf3?/\x8c\xfd\xb6)\x8c\x01\xc0\xe3\xaf!\xcaV\x0c\xfa\xbf\x1a\x8dz\xf9\x91\xb1\xf7\xbf\xec\xe8\xca8u\xb0\xc0?\xa35\xfa\xe1\xbc\xf3\x00@wZV\xb2\xeb\xf2\xe0\xbf\xf6X\x98\xd0\'\x89\x98?\xba\xc9\x10~\x80!\xdd?\x05\x15\x03\xed\xcfN\xf1\xbfd\xcd\x96\xc6R_\xce\xbf\xa6\x9fQe6h\xfb\xbfj\x07\x85\xd51\xb3\xe8\xbf0\xf3.V\x7f$\xf4\xbfF\xc1\x9b\x13T\x90\xf2\xbf\xa33}\nzz\xe9\xbf\x13Y\x831\xbd\x82\x04\xc0\x99e\xb728\xed\xed\xbf\xc5T\xfe\xe6\x86\xc3\xca\xbfx\xfdZ7\xd5\xd7\xe1\xbf\xd6\x98$\x94 U\x04\xc0{b\xf6\\\xa9\xa0\xf0?\xd6\xa2 \xe8\x84\xe0\xfd?\t\xc6o\xfb\x0b\x7f\xe4\xbf2+ 7z(\xd6\xbfB\xeb\x93|ou\xf3\xbf\xb5\x9a2\x8380\xfa?zWs\x9b\xb3G\x06\xc0\x87\x88s\xf66\xb2\xf3\xbfg\xb5\xed\xe6\xe1\x8d\xec\xbf\xf8\x868U\x1e\x95\xdb\xf8\xbfu\xb3\xe4(Q\xaa\x01\xc0w\xba\x19Q\xf9|\xf0\xbf\xef?\x9a\xa2`*\xce?\xe9\x98o)\xbc\x90\xee\xbf\xad\x16\x92T\x8d,\xf2\xbf\x1a~\xab\xce\x9c\xb2\xf0\xbf\xb1#61qU\xea\xbf\xa4LA-\xd8\xc3\xee\xbf\x06U\x06_\x8f\xdb\xfa\xbff\x07\x9f\xebv8\xa3?\xa09\xcc\xe8\xf7\xe6\xfb\xbf\x889\xa5t\x8d\x97\xf4\xbf\x0c\xe1,\xd0o\xf4\x04\xc0\xdc\x08\x10\x81\x9d\xdd\xd3\xbf{p\xa6\xe1\xf1\xe3\xb0\xbfV\x046\x97\xd0\xe3\xd2?\xac\xaah\xef]\xaa\xdf\xbf\xba\x13\x12\xfd\x0f\x16\xf1\xbfr\x1d\xe4z\x96\x9e\xb2\xbf\xbe\x0b\xfa\x12Ld\xf7\xbf\x90\x98Ir\xfa\xa7\xf8\xbf\x81@\xa9\x05U\x1b\xff\xbf\xa1\xc1H\x96\x97\x12\xb8\xbfg?\xe7\xfb\x9e\xb0\xae\xbf{\x18\xc4\xf7\xeb\xbf\x08\xc0\x85\xb6e\xe7\x9dF\t\xc0\xa8Y\xe7\xc5[J\xcf\xbfxu\x1d\xaa\x93i\xce?\x87\xb5\xabD\x85\xe9\xf2?\x97\xd3\x1dC%\xc0\xd1?\xd2\xfbB\xd4\xc2|\x00\xc0\xd9\x86\xf7a\xf4rm\xbf\xd9\xc2\xd8\x9be$\xef\xbfP\xa7:\x10,A\xf5\xbfa\xac5+\x15O\xd6?\x91\xd7Gt\x92\xd0u?\xd1R\xbccq\x97\xeb?$\xcel\xc3\x95\x8d\xda?\xba\xeb/:(6\xf3\xbfY@\x9fGS\x87\xf2\xbfe\xeel\xc2^\x81\xf2?\x1d\xbcx\xae\xcf\xff\xd8\xbf\x84\xeb\xc4*\xe58\xf2\xbfy\xcfu\xeaV\xa2\xf0\xbfX\x05\x81\xb0\xbb{\xf9\xbf35\xc7\x83\nA\xd1\xbf\x03s\xf2\x98\xff\xd0\xf3\xbfA\x8c1\xed\x99.\xf8\xbf\xd7=\xac\xfa\xf4\x1b\xee\xbf&\xe9;\xcc\x86@\xf0?S\x80\xa6?\x92d\xc9?U\xf0\xd7\xd9\xe8w\x03\xc0[,\x99ak\x87\x0b\xc0l#\xb0CO]\xc4?\x16\xc5\x8e\xdf\\\x10\xf8\xbf{}\xce\xe1K\xce\xf1\xbf,\xfd\xa6j\x89\xe1\xee?w\'\xa3|\xb8:\xf8\xbfME\x7f\xd3\xfd\xa8\xfd\xbf\xfbZ\xef\n\x06v\xe5\xbfj\x03g}?\x87\xee\xbf7\x17\xeaa\xc0\t\x01@\x9d\xfd\x1c$S\xdb\xde?\xd7Q\xe2\x10(t\xe0?\xde\x16\x0c\x01=^\xab\xbf\xb5\xbf\xa1\xe4ZD\xa7?\xae\xdeu\xca\xee:\xef\xbf\x90\xfa\xfc~\xf09\xf8\xbfy;\xfe\xae\x92\xe8\xf8\xbf\xc8_r@\x90H\xf5\xbf\x99$h\xb6\xf3\x02\xf2\xbfx!\x18?\x05J\xb0??\x04\x8e\x02\xa6\x02\xe3?Ik:\xf1\x06M\xdc?w\x02\xe7\x96\xaf#\xd4\xbf\xae\x1c\xe1\x8f\x1ew\xf3?@\xb1s$Y4\xca\xbf\x12\x82\x87\x05\xd3\xf0\xcb?\xc8\x99T\xd0\x00h\x84?l\xce\xd3n\xd7]\xd2?\x9d%4H\xc8L\xfe\xbfWJ\xb8\r\xa1U\x07\xc0\x8f\xa0Le\xd6z\xf4\xbf\xc1%Z\r\xc0\x95\xd6?\xea>\x9am \x9b\xe8?\x05\x08\xb2Y%\xa4\xfd?_\xdc\x00a5$\xe9\xbf\x15\x15\x16X-e\xd0?p\xfa\xfd_x1\xc5\xbf\xc4\xe2\x83\x16W\x99\xe5\xbf\x90\xc4\xf7/d\xed\xf3?\xc7ORiab\xe0?]\x87u\xf7\x97\x0e\xec?\xfb=0w\x0fV\xc1?\xa7\xe9\xa6\xc6\xb7\xf6\xd3?\xcc\xe1{\xe3\xc1\x02\xfe\xbf\xc36\xad\xc8\xf7\xbf\xff\xbf\xaf\x07#\xad\xb1s\xe7?\xa9M\x82y\xe6\r\x00@v\x9b\x9b3nm\xf6?\x0f\xec\xde\xd3\xbc\xd1\xde?\x9aa\x97\xf0\x84\x1b\x01@\xc6\xb4\xc8\xef#w\xf5?4<\xe2\xcd\'\xc3\xe5?\x17|\xa0y\xca,\xe3\xbf\xa9L\x82\xc3-\x00\xc9?\xf4\x87\xd1\x92-W\xe9?8e]4\x1bC\xe7?c3\xd6a\xcc\t\xf9\xbfSV\x1eyZ\xc5\xde\xbfH~\x9eK\xab\x05\xf0\xbf\xd9v\xc667\xdb\x01\xc0FUj\xadQ\x07\xec\xbf\x06\xb5A\x9f\x9e\xaa\xef\xbf\x92\xbc\xa3\xfa\x18=\xf1\xbf\xbe+>ec\x8c\xdd?\x13@\xe6\xd6\x1d\xac\xf3\xbf\x175h\xad|\xb6\x07@e\xd7\xfd\x018=\xfd?\x85\xb0\x1a \xc3\r\xf1?\x90\x0c\xfdr\xbb~\x93?\xc4\rU\xdf\xe7\x16\xfa?.!\xb0\x89wY\xe3?\xfbX\x8aS\xee8\xd3?\xa7\xe3LVL\x12\xeb\xbf\xc1H\xfd\x08\xc3\xef\x07@1\xe7\x81tg\xc4\x08@B\xf4wp<\x9a\xf2?QB\xe3,\xa4\x8b\xdb?{\xe3.\xfa2\\\xed?\xb9\xb3\x04V\x93\xbb\xd5\xbf\x92D%\xef\x1f\xa7\x04@\xdd\xcd\xefn\xd0v\xf8?.h\x86N\xb3e\xe8?\x1e"_dkN\xe1?\xd3\xb6\x8b\xd5\x80\x89\xe3\xbf\xadiaX\xed\x8f\xf1?z\xda\xc2\xef\x0b\xd1\xf4?\xb2\xfd\xb0\xb3\x0b\xaf\xe2\xbf\x7f\x0f\x80\t\'\xe7\xd1?\x81\xc2\x93\xfcAD\xfc?\x97*\xf7\xa6pZ\xf2\xbf[\xa7\xf8\xe5\xd0j\xbd?\x1e\xb9T\xb23\xde\xe3?T_g\x80m\xb0\xd7\xbf;>10\xbd\xb4\xeb?\xa3U\xfc\xb8\x13\x8a\xf8?\xf9T\xfc^\xab3\xf9?\'\x07E\x91\xba\xc2\xec\xbfnL\xddX\xb3_\xf8?\x1bRQ\x9b\x1d\x84\xeb\xbf\x04\n"\xbe^H\xc5?\xdcl\xa1\xa1,N\xdf?\xc1\x8c\xc4\xc9\xd3+\xfc?e\xfc\xdaF\xdc\xcd\xfa?\xd2\xf29v\x01\xc9\n@yo^/\xd5\xf2\xe6?/\xa7\xb5\xdc\xdfN\xfe?\xd1\xdf?\x90\xde.\xc8?\xf1\x9fF(:\xaa\xf5?\xc4w\x05\xde\xb9\xe5\x05@\xbe\x9e)\xcfFa\xf2?mWGa\xe2\xa2\xfe?\x01\x93X\x80\xa7\xf5\xca\xbf\xd4\x8d\xa4T{.\xe1\xbf\xa4\x05=A\xfd\x18\xb8\xbfZtM\x141\xe7\xb5\xbf\xf1\xd2\x07\xe8\x9e\xf7\xf1?\x14\xf9\x88\x1c-\x1d\xe6\xbf\x1e\x8c\xaf\xf2\x80\n\xe2\xbf\xba\x9d\x17\xf5\x03C\xe6\xbf\xae\x17\xb3\xb0\x8e$\xf7\xbf\x8b\xd8n\xe0R\xca\xf7\xbfS/\x9c\xe1\x11r\xf6\xbf\xfcC\x92.\x9dd\xd3\xbf\xecem\x84\xe3\xb2\x01\xc0\x88\nv\xfa\xa7\xfa\xd6\xbf\x0f\xa1~8\xb3\xc6\xd1?F\'\xa0l\x90k\x8b?\'\x82\x93q\r~\xd1?\x89\xc7\x92l\x1c-\xfb?\x97W\xf6v\x0e\xd3\xd0\xbfC*\xa1\x9b]\x98\xf2?\x01\xf0\xa2!XB\r@V\x9d\x10\xe2\x1b\xc2\xf8?V\x94\xc7\xec#5\xa9\xbf\x94^\xc3\x03\xeem\xcf\xbf\x15R\x08\xa5r\x85\xd2?e\x7f\xa3n\x18\xf8\x04@\xc9\xe4\x8cg\nI\xf1?\\\x00a\xeb\x03a\xc6\xbf\xa6\xad\xd8J\xe9#\xeb\xbfO\xb2#\xb1\xa8\xe8\xf0?\x8e4\xdd\xbf\x07\xbe\xf4?\xd2\xd9J\xf4\xfb[\xfb?\xd0\x0b\xdfH\xea-\xf1\xbfRP\xcf{B\xfd\x9f?6N\x92\x15\x19\x0c\xd4?4V\xdfa{/\xc7?\xd9\xda\xfc\xb6!K\xdd\xbf\xd9\xf8e\xa5\xa5\xbf\x05\xc0OB\xb9\x91\xedm\x01\xc0\t\xf4^\x08\x14@\xf0\xbfcc\xa3/\xcff\xee\xbfFj\xe4\x02\xa1\xd1\xdf?/\x80\x948h|\xcc\xbf\xf9\x85\x9bU\x04\xd6\xb9\xbf\xba\xf2\xd5*\xd3`\xff?\x13d\x96\x920(\xec?a\xf6\xeb\x15r\xff\xdb?\x05\xbf\xe4\x05\xe0\x13\xc5\xbf\xf3\x18U\x90H\x1f\xf6?U\xd3DA%g\xf7?\x90\xe6\x06%Nh\xf7?\x1b!\x1e=\xb6~\xec?\x96\xbce\xe9\xe6\r\xf7?^.\xc2\x01h\x1d\xd1?\xdc\xeb\xb4 A\x94\xf4\xbf\x12F\x9ch\xf8%\xf2\xbf\xd6#\xe5\xa9\xc3z\xf6\xbf\x0bBv\x80\x7f\x1e\xf2\xbf\xb8\x87\xce\xd4\xf6Y\xe1\xbf\xa4a\xed\xe3\x87\xa6\xf6\xbfV\xbc~B\xdb\xcb\xd9?\x94\x85rfa\x0c\xc7\xbf\xf0\xbb\xba\xa4^>\x84?\x8d6V\xf3T\xed\xd1?\xad7\xcb\x9b\xfa\xd6\xd0\xbf9\x9d\xeda\xa0B\xe1?,\x98\x0c\xa9\x91v\xf8\xbf\xa1\xdb\x80\xfdy\xeb\xf8\xbf\xf7\xc0\xff\x9cQ\xc1\xa1\xbf\xee\xb9\xfc\x0e\x06\x16\xef?9\xa0x\xd1\x8f\xdf\xe0\xbfi;!\xf8\xa0\x1c\xf0\xbf\x99K\xc4\xcdt\xf3\xf2\xbf\x0b\x7f\x13\x96\xbb\xf7\xd7\xbf# \x9c\xd8\x80\xe0\xd9\xbf\t\xdb3k\xec\x1d\xfc?l1\xd3\x12\xc8t\x06@\xf8\xd2#\xcf\x0c\x19\x01@\x05O5o{\x92\n@\xd7\xc3\xa3\xd51\xbe\xe3?\x02\xcc\xec\xfay\xc5\xf2\xbf\xf1}1S\x05\xaa\xe4\xbfp\x13\x1d\xed\xdb6\xe2\xbf\x07\x9e\xf9\x9f:\x83\xf0\xbf\xe6\xae\x97\xa1\xee\xac\x94?3\xd6mXBPY\xbf\xd4\x94\xd3+V\xeb\xbb?\xa4\xd71+\xdf\xfa\xf0\xbfHp\x916\x95[\xf1\xbf\x9d\x1c!5uy\xa0?\x8c\x88 \xa5\x99\xe4\xc3\xbf\xa17\x1c\x97\x1b%\xd9\xbfk\x86\xcf\'\x12\xad\xab?N\x9c\x9c.d\xdf\x02@2`\xb7\x96S\x16\x02\xc0\x1fO\xa6K\xce\\\x02@\xb9\x8aW\xd4\x1cf\xf9?\xa5\xab\x7f<\x01|\xfe?\xbf\xbc\x14d\xcaW\xf2?%\x00\x7fT\r \xf2\xbf\xdd`$\x05\xb9\xe0\xd3\xbf\x16\x97\xa2\xab+\xc2\xe2\xbf\xec\x99\xcd\xee\xd6\xbe\xf1?\x17\xc1\xc6*i\x89\xef?\xa1\n}\x83\x9f\xca\x04@\xech\xdb\x8eD\x9a\xfc?\xd2\xd2\xbfY\xd9\xab\xf2\xbf\x81E\xb4\x86js\xe9\xbf!y\xf34[%\xd2?1\xb8~\xcb\x13\n\xf1\xbf,\xf2\xcd\xe4\x86\xcf\xf5\xbf\xba\x13*\x89\xe4\x81\xe9\xbfa\xbc\xf52!I\xdf?h\xfa\xc46\xd3k\xd9?,\x11y\xd8\x03l\xa4?\x82%z\xc6\xeaV\xe9\xbf] \xca\xf1\xf32\xdd\xbf\x1e\xf6\x1d\x8bp3\xe3?w\xc5#\xd7O\x82\x84\xbf~A\xe6\xd0?\x19\xe8?\x80\x07\xab\xc0=\xba\xd4\xbf\xd8\xb7L\x94M\x92\xe6?f\xb1EC4\xca\xe4\xbf\xdc\xa4\xbf\x18\x011\x0b@\x0c9\xd8\xb3\x15\xc1\x05@\xbf3B\x92_\xf5\x12@\xdfn\x13\xdc\x1c\xde\x13@\x9f\xd5\x94N\x85Y\x08@\xc6>sSq\x90\x03@K\x88%\xd0\xd1\xbe\x01@O\xa6\xe2\x90\xf6\xdc\xf6?\x81<\xed\x1f?\x0e\x12@\xf9\xf1\x1c\x9bTE\x0e@\xf9\xb3\x90\x88\x03\xef\xf7?\xc2\xa3\xcf\n\x9b\xd4\xe3\xbfV\x11m^\xb8\x87\xfd\xbf6\xf8\xfe\xa2\xe2\x87\xf4\xbf\x8e\xc7\x95\xa6\x15\x1f\x04\xc0A\xfa\xee\x1f\xcb\xac\xf4\xbfA\xe9\xc2f\xf6\x1a\xf2\xbf\xbb\x82\x8a&\xf9\xc4~\xbf\xd2er\xdbKv\xf6\xbf\xe3\x94\xc3\xdcI}\xce?\xf7\xfb\xf4\xfc\xb2\x02\xe1?\xc6?\xd7\xae\xee\xbd\xdc\xbfS\x9e\xb0\xae\xff\x8a\xd7\xbf\xd5M\xc6\xa6`^\xdf?;\xff\xac\xc1#\xfe\xf0\xbf\x88\xf7\x1a\xafa\x0b\xf0\xbfF\xc8\x92B\xea\xf2\x02\xc0\x1aX\x01I\xac\x80\xe5\xbfK\x89\x0e\xb69\x94\xea?\x8e\x91\t\x03~[\xee?\xa90\x08\xb3\xa6?\x0c@\xbe\xc7\xd4\x93\xbd\xc9\x15@\xedtXs\rC\x1d@\xb8\xc4\xb1~WH\x1b@\xbe\x9e\x94\x82\x00#\x15@\xf0\x0e\x8el(<\x14@3\x7f\x0b\xbc\xbfj\x19@@\x8f[A5\t\xd4?|G\'\xdby\xc1\xf9\xbf\x7f\x1fTE\xbd\xc7\xfe\xbf8\xedi\x1aj\xc3\xdc\xbfc\xecW=\xc7\xdc\xe8\xbfp_\xcd\xf1\xba\xfb\xd5\xbf\xaf\x97\xb7\xb0\x17C\xf1\xbf_f.W\xf8\xaa\xf9\xbf\x00\x0c\xcb\xfcv\xb3\xf7\xbf\xe2\x9a\xb9\x0f\\\xb6\xd3\xbf\xfd\xec\x97e\x1c\x8e\xe2?\xebZ_D\x06\x91\x01\xc0\x88#\x00\x0c\xb1%\xe5\xbfx\x91M\xd8\xea\xe3\xc8\xbf\xd2\x8bj\xcd\xf9\xd2\xed\xbf\x18%P\'\x1c#\xe2?\xb0f\x0f\xffYS\xd3\xbf\x08\xed\x1a\xc6\xc6\xe6\xe9\xbf\x142\xd5\xc5\x1bq\xf7?\xcbH\xa5\x8a\xc4\xc5\x04@\xf3s\x0c\x85\x1bU\x00@\xee\x8f\x8c\x14w\xf7\xed?\x8e\xd7\x0b\xc3T\xed\x07@\xa3\x0cI\xadJ\x0f\n@]\xdf\x1eY\x95j\x12@(Z\xb3\n\xc5\xa9\x13@\x02|\xe5\xcf\xa0h\x12@\xced\x0e\xbe^\x99\xf6?#\xc7\x0c\xea\xbf4\xf0\xbf\xd8P$\xe7#\xff\x04\xc0\xf8\x83g@>\x89\xfd\xbf\xa2\xe2\x7f\xe3/\xaf\xe2\xbf\xdd\x9eZ\xd0\xf4\xad\xf7\xbf\xffN\xce\x88\xa9\xcf\xd4\xbf9{jw\xc8p\xea\xbf\x8b9\x9cQ\xa1\xbd\xdd\xbfj\x82\xa5\x80\xa5)\xe1?\xac\xb7\xcf#\xe0\xa6\xe1?\xc7\xb4\xba\x0b\xd5\xd1\xe0\xbf\x0b?\xfb\xd9\x82j\xc6\xbfd\xe6\xc1\xc8\x03\xed\xda?dv!\x04\xe8 \xde?@\xa0\x93\xab\x89!\xf2?\x95\x92\xb9\x18\xea\x88\xf0?\xdaa\xc9e\xcfH\xe0\xbf\x15_\x1b)3\x7f\xdb\xbf2\x95\x7f\xa8\x8f\n\xc9\xbf\xae\x14A\xd5\xbb\x19\xda\xbf\x9f6+GT&\xed?@\xa0_S\xa8$\xd6?2\xcbA\x8am\xe8\xd7?\xe0\x9e\xaf\x00\x0e0\xfd?\xe0\xa9^\x1a~\xb3\xf1\xbfT\xd6\xe6\xf0\xc2+\x02@qP\x1f1\xf1{\xe8\xbfa\xff\xc0;8\x1d\x01\xc0\x8c8\xf0z\x073\xf8\xbf\xf3\xa3\x13[\xe5\x18\xe9\xbfw\xf7\xf7A\x9b\xf9\xc8?k\xd8\xbb\xa5#J\xec\xbf\x95\xb9\x19\x8f\x11r\xe2\xbf\'n\x9aeA\xf7\xaa?EB\xdb\x1dc_\xe1?])\xd9\x10c0\xdc?\xc6X\xb2s\xe5\x9b\xd8?\x83]\xc8\x03x\x0f\xff?j\x1f\xfbjIv\xd2\xbf[\x82\xd8\x1d\xa5\x83\xf8\xbf\x00_\xc9\x0e\xb1\x1d\xe6\xbf]\xa6\xbb\xa9\x86\xab\xd7?\xb6\xec\x0e\xbfS;\xf2?\x9d\x13 \xd7\xc9\x8f\xd9\xbf\x835h\xbf\xcc\x9b\xfb?\xb8\xf5\xefQ\x10:\xd1?\x19>\xf6=,\xbe\xf1\xbf\xe7\xca\xa9\x80`\x03\x02@\x17\x92\x12\xe6\x1e\x9d\xf0?\xb3.\xd7J\x99\xa3\xde\xbf\x04=\x0bG\x94\xf6\xfe?\x06\xb6\xe4U*\xf6\xe2?\x96\xdc\xba\t\x9f\x8d\xe7\xbf\xee&R\x07\x10e\xbd\xbf\x98\x00\xf9\xebRy\x02\xc0\xa3\x07\xbbv\xa2z\xf8\xbf\x11\xbf\xc5<\x9d\xeb\xf5\xbf\xa2\xef\x8d\x8fKT\xf2\xbf)\'\x89\xdb\xdeu\xe1\xbf\xca\xa2\xd4\xf4\xbf\xf5\xed\xa9\xd5\xc3\xa7\xf3\xbf\x1d\xd16\xcb\xab]\x03@\xa0K\x0f\x97\x1c-\xd0\xbf\xad\x18\xc5\xa4\xad\x16\xf4\xbf\n{/\x15\x95\x05\xee\xbfr<\x19\xac\xc8{\xf6\xbf\xfb\x07C\xa5Bc\xf0\xbfJ\x91\x87v@\xcc\x07\xc0\xa5ax\x06\xb0+\xf7\xbf\xf0\xe8\x10yO\xf2\xe5\xbf\xedA\x9fQ\xb9\x91\xd3\xbf\xf5\xb5{\xb0q\x8b\xe2\xbf\x9cG\x12\x8d\x17\xc0\xfb\xbf\xf5\xc8\xfc\x0cm.\xdf\xbf{\x1c\xc8U\xe0E\xea\xbf@\xd6\xdf%\xecd\xfc?\xb2V\x82\xf7g\x85\xd7?\x0c\xc1\x8a\xe6?jG\xb3\x96\xad\xf2\xeb\xbf\x8a#\xc9\x11\xa4\x9b\xe8\xbf\xb5B\xd1\x96D\x8e\xb8?\x08MQ\x1dv\xba\xfb\xbf\xfd/\xacO\x85\xd7\xfa\xbf\x84\xb3\xdbTL\xbd\xd6?\x15\x92\xcb\x8c\xaad\xe3\xbf\x8e\x06\x8e^&O\xa9?\xdb"5+\xb9\xe8\xbb\xbf:]\xe0\x15\xd2M\xf9\xbf,\xe9\xf6\xcc)\xe9\xf1\xbf^\xc8:MW\'\xf1\xbf\xf3\xf8a\xfd_H\xc2?\xc9\xaap\x96\x82^\xff?n-}\x9bb\x11\xa5\xbf\xfa6;\xbc&3\xe3\xbf9\x87H0\x8a`\xd5\xbf\xe6\xbf\xa6\xbf\xb4\x1b\xec\xbfz\xb8\x80\xc5\x8fb\xc1\xbfu\x8e\xcb\xfa\xea\xa5\xed\xbf\xe9\x8fc)\\H\xec\xbfc\x84\xfa \xac\x88\x01@\x06-\xec8\xac\xb8\xe8\xbf\xe9\x95\x8b\xad\xf9{\xe6?\xef0\x92sg\xf2\xbc\xbf\x82\xc3\xb2\x85X\x14\xb2\xbf\xae\xdcK\xae\x13;\xf1\xbfB{N\xe6\x1b=\xf9?\xe8;%\x98\xca\xd4\xf1?!\x80\xaa\x17\xcb\xff\xe6\xbfH\xca\x03\x80\xa4\xe4\xf5\xbf<#\xdf:6\xf3\xf6\xbf\x12\n\xd7\x89\xd0F\xd9\xbf6\x8a\xad\x8f\xbd\xcd\xec\xbf\x97\xd3\xc6is,\x03\xc0\x17v\x01\xdd\x1f\xd7\xf7\xbf\xca\x88&\xd2\x84\xdc\xf3\xbf\xc1G\xe1\xfd\x96\x87\xc9?\xdd\xccq\x14K\x15\x00\xc0\xa6\xbb\xe2-zf\xb9?\xb7]\xcd\xcf\xe2\x8a\x03\xc0m\xe9\x13\x9b\x87\xee\xd7?)#q\xb4hW\xba?$\xdc\xfd\xad\xe1\xa1\xcd?jU~\x87\x1f}\xe8?\xf3\xd3\xf8\xdd\xcb]\xe1?U\xcc\x8b}\xa5\xb5\xe1\xbfH5\xc5&\x13\xd0\xf5?\xbf\x9fg\xef\xe4SD\xa2\xd7\xbf\xef\xfe \x1b\xcf\x1d\xe2\xbf{\x03\xba<\xe8\'\xcf\xbf\xc1\xe6$\x8d\xf0\'\xef?:e\xb4D2\xbc\xdc\xbf\xd0F\xf7pBf\xec\xbf/XE\xa2\xa3\xf9\xed?\x00\xd4\xd1\xfa\xd3\xbb\xf0\xbf\xa4\xb9\r\xa7\xe2\xeb\xcc\xbf\x84.\xb1V\x0f\xf0\x01\xc0\xa6\xc2\xfd\x89Hk\xf5\xbf\x9c]U\xe4\xd4\xe7\xd2?\xe3T\x9a\xd1\xa4P\xd5?*\x80r\xc0\x95\x03\x01\xc0E\xae\x88b\xc6(\xf7\xbf2\xeeK\xf4\xd81\xe0\xbf-t\t\xff\xe5\xa2\xf1\xbf\x16\xf2\xd1\xb9r\xa6\xea\xbf)\xef"r\xcfq\xf0?\xeb\xb0\xf6\x8f7\xac\xd3\xbf\xde\x0b\x0e-\rt\xc6\xbfvf\x13\xda\x1b\x86\xfa\xbf\xd8LJ\xb7\x8d\xd9\xe6\xbf>\xbe\x0f\\g\xe6\xf9\xbf\xfc\x12\xaei\t\xa3\xe3?Za0\xaa\x14C\xf3\xbf\x8e\xd1/\xac\xdc\xc6\xee?!\x17\xa5,c\x00\xe8?\xe36\x82\x0c\xe1\x0f\x04@-\x13\xc5=\x8bq\xe6\xbf\x00\x1fM\x0e\xed\x04\xe3\xbf\x99w\x82\xfa\x8dQ\xef?\r*F)X\x99\xf1\xbf\xc4\x93;\x19\xc6\x87\xd7\xbf\x95\xf2\x7f2\x96\xd4\xf2?\x93\xa7\x1bo\xa9\xd1\xec?\xe9{\xbbb\x8a\xca\xeb?\x04\xbaPJ\xba?\xc5?\x0by\xeb\x93\xcez\xb4?\x0bN\xb9:3\xb4\xe5\xbf\xcc\x8e\xfb\xd4\x0f\xc0\xeb\xbf\xd8\xa6B5\xda<\xe6\xbff\xd1\xc5Y\x86\x17\xf6\xbf0\x83\x1c\'Q\x03\xc2?{\xc5?i\x9e?\xd8\xbf\x89]\x98\x7f-\xd0\xeb?]Y[\xbc\x92\xd6\xe2\xbf#!\xb0\xb3\xfcm\xe1\xbf\xcd\xf2\xac\xec|?\xf0\xbf\xa0\xb1@v\xafI\xf7\xbf\xa3V\xe2\x04V(\xe4\xbfy>\x03\x96\xf6\x00\xdd\xbf]\x90\xb2\xc6^\x14\xea?#\xe3\xd7l\x90\xc0\xdb?\x8d\xfe\xef\x98\x06"\x03\xc0Fa3(\xa7\xcb\xe9\xbf{C\xd35\xd0k\xf5?D\xa7\xafL\xc7\x06\x01@\x19\x13\x94\xf6 \xd4\x03@\x86\x7f\xa2\xa4\xb5\xcd\xe9?\xc8\xbf\xa7\xe3\x1aw\xf5\xbf\xf6_2\xfd\x7fH\xb6?;\x8c\xe9\xfc\x03q\xee?\xf3a\xa0\xdd\x85\xd6\xd5?\xd7\xb25{{\x1b\xe4\xbf\x96x\x920\x073\xf4?b\xec\xf6\x15A4\xeb?\xef\xfds\x13\xbdr\x03\xc0u\x10\xe1\xac\xf5\xe8\x08\xc0\x99\xbf)\xa5y\xc3\xfb??\x8c\xe2\xceT\xd7\xf3?\xb8&,\x04HV\xe0?\xc1\x18\xc5f\xeej\xee?\x14\xc8\xc8\x932\xf9\xf2?\xc92\xef\xb2\x07\xd6\x03\xc0\xc0\x0cE\x01\xec\xa1\x03\xc0\x05\xf5\xab\x8e\r\xfe\xef\xbf@\xf3Y\x15?\x07\xd4?Mg\xd1GcC\xf3\xbf\xfc\xa6S\xc6\xa2j\xed?\\\x87\xeap(h\x01@\xb4d\xb0\x7f\x11H\xf1\xbff\xb2\r\x85\xd8&\xf4\xbf\xa7\x0c\xe1\x8c_}\xf8?1/\x00+\xb6\xc8\x00@\xf6\xd2\xa0\xc7mF\xe1?\xa8\xc0\x15\xc7g\x9d\xda?\x14\xec\xea\x851X\xf0\xbfg\x8ew\x9d\xe70\xfa\xbf\x0c\xeaf\x07vo\xed\xbf\xc4\x06\xc9\x07\x0c\xbc\xdf?\xc7b\xbcZ3Q\xeb?y\x19\x00\x8c\x07\xef\x04\xc0V\x8c\xd3%\xb0\x01\xe7\xbf\xc5]\xdd\xbc\xddW\xf5\xbf\x9f\xf1\x89\xf40\xda\xcb\xbf\x86G^Z\\\xa2\xc0?i\xa8\x16\xe8\xea\xe5\xf4\xbf\x82\xbf\x8ai%\xae\x87\xbf\x939\xb8\x08\xd5\xcd\xbe\xbf\xcd\xf0\xfa\xacyX\xce?\x94\xda\xaa;)\xc7\xce\xbf/\x98\x99\xd8\xd0\x02\xc8?\xf1M\xfd\xf67\x02\xd2?\x9a\xb8Xj\xd4&\xe9\xbf\xe5[a\xe6\x99\xcc\xf5\xbf\xdd\x15S\x19,r\xdc?\x91\x1dU\xca\xec\xc0\xf8?\xf9\xc9\x8e\x0f=M\xd2?\xd3k\xc8\x17G\xa0|?\x12\x08W?\x8c,\x94?\x08\xf3t\x90\xc6\xf1\xe3\xbf\xef\xc8T\xf2\'\xca\xf0\xbf\x19\x11\xd8\xedH \xf0\xbf\xea\xa3\xdd\x90E\x12\xf5\xbf\xc7\xba\xdb\xc6k?\xfa?\xdeB[\xb6\x85g\xc8\xbf-gmF\xb6\x13\xde\xbf\xbf\xfa\'\xe9Z>\xd0?\xc858\xa9\xd5|\xf2?\x85\xcf\xdc(\xa1 \xeb?k\x93\x15xf\xf4\xf1?b\xa8\x08|N\xa0\xf0\xbf\x92M\xf3\xb6\x00\xa1\xb9\xbf\xa3H\xe93$\xbd\xde\xbf[\x1b\xfb\xf4\xf8\x0c\xf6?\xa3_>\xd0\x8a?\xfe?\xf4\x90\xf4F"\xa9\x04\xc0\x02\x84G\x07\xc0\xdd\xe9?\x18E\xd3V!\xd2\xdd\xbf\xf3\x98\xbb\x1c=K\xb7\xbfSE\xdf\xf3\xc0\xc7\xc9?R0{\xbb\xc0\xba\xec\xbfH09C_\xc6\xd2?)A\xd4l\x95\xce\xd8?o\x1dKa\xae\xb7\xf4?\xf5\xd3\t\xfex\xc9\xe3\xbf\x8e\x8a\xa7\xe8\xeb\xd0\xa5?\xb5\xb2\xeb\xb0)\x11\xfc?\xef\xe7\xdc\x96I\xad\xec?}\xbe\x14\x9e|\x8c\xfd?\x1a\x10\xc1])\x8d\xe4?\xf4M\x02xrk\xf0\xbf\x1a\xf73\xbd\x02H\xc7\xbf\xc6U\x01\x88P\x82\xc1?\x0f\x90\x8dS&\xed\xf4\xbf-\xcf\xc7=Ju\xf5?\xe3f\xffV ,\xf9\xbf\x8bE6\xd7\x80\x8a\xf1\xbf\x8c\xa3\xbe\xb8fm\xf0\xbf\x14\x01KR\x86 \xf1\xbf\xf4<\xc2\xe7\x7f\xbb\xe5\xbf}\t\xdd\xed\xdd\x00\xfc\xbf\x11|\xf2\x05:^\xb8?\x10zo\x1f/@\xff\xbf\xb9\xd3\xe8$\xf0\xdd\xe8?\t\x91\xf7=8\xcc\xf1?\xe0Z\xf1lY\x87\xf4\xbf\x8d\xbbN?\xba\xcd\xfc\xbf\'D\xb0Hv\x92\xfa?f\xd4<\xefU\x00\xf5\xbf\n\x14.\xf55d\xac?#\xfd\xa2\x12r\x82\xe5?\x0c\xb7\xf4\xc2\x9b]\x00@\xd1\x9e\x1dQ+\xff\xe9?\xa5-z5N^\xf5\xbf\xf6\xb4#\xd8R<\xd9\xbf\x14\xce\xb9\x92&\xe1\xd2?\xa4\x1c\xd5\x9f\xb0\xc3\x00@\xc2\xff\nDM;\xca?\xd1N\xbc\xb9\xe9Z\xee?\x91?Zr\xc5\xcb\xe7?\x15C\xd7:[G\xb8\xbf\r1\xff\xfeY\xcd\xd6\xbf\xa6\xb0Uq5\x99\xee?\xf8\xbc\r(\x08\xfc\xd6?\xb6\nXh-A\xf0?R\xe7\x7f\x9b\x03\xfe\xff\xbf\x12\xef!\x8d\x93\xd1\xff\xbf\xbaG\x05>\x9a\x82\xf1\xbf\xb6`\xa6\xa8>\xaa\x04\xc0#\\\xc0\xa6w\x10\x03\xc0\x96\xf0;\xdab\x16\x0c\xc0p\x07@3\xb6?\x00\xc04M\xcf\xf6F\xfb\x04\xc08s\x96\x10P\xd4\xfd\xbf\x19?\xc1\x0fpM\xea\xbf\xe2>\x99\x8d\xa5-\xeb\xbf\x02M\xa6\\R&\xe7\xbf\xcc\x9f\x0e\xcdJ\xd5\xea?,\xb7\x9cxb3\xbf\xbf\xde\x025[\xb6\xd2\xd8?\xfa\x8d\x0eq1\xfd\xd9\xbf\xeaW\x9cd\xfbf\x04\xc0\x86\xd2\x0f\xef?a\xf8\xbf\x1c9\x84/\x00\xcd\xd1\xbf #g\x96\x0b\xb9\xf1?.\xa8\x8c\xfd\xa79\xb3?\xe1?UY\xba\x12\xd0?@\xd5\x857\xed\x1b\xd4?Gqo\xaf\xedj\xe8\xbf\xd3\xf8\xd8\x8f\xda<\xee?\xba\t\xc0r\xedT>\xd5\xde\xf1\xbf\xeb\x8a\xcf\xfa\xadz\xe1\xbfAxzAU[\xfa\xbf\x96Tm\x90f\xa1\xda\xbf\xe3\xe2\x97\xe8\xe5N\xed\xbf\xcd\x1c\xef@U\xdf\xf0?\xedh\xf1\xb9\xd8_\xa6?U\xb9XV\x9a\x94\xf0\xbf\x81(\x9b\xce\x92\x17\xe4?\xc5B\xc8?\xae\x1d\xc9\xbf\xd56\xadJH\x95\xf9?zr\xa5\xec\xc4p\xdd\xbf\x13I 7\xed\xb8\xd9?\x8f\xf5\xbakB\x1a\xb1?N\xac\xd9\t\xf9\x97\xd5?\x1a\x8b\xfe1\xe5\x0f\xd0?\x0fWv>5\xa8\xfd\xbf\xb5b2\xe8\xe1\n\x0c\xc0\x0eH\xdc+\x0c\x8d\xb1\xbf]\xc3\x1b\x0fp\xa5\xc5\xbf\xc4\xb9\xf4\\\xb2"\xec\xbf\xe2\x89\xe7\xe8\x0c\x8f\xef\xbf\xb7\xe3$\x99\xaf\x10\xb4?8\\\xf5t\x9bJ\xb5\xbf\xf1Y\x9d)\xa0\xb9\xfd\xbfV\xe3\x84,Q\xe8\x0b\xc0z\x82\t\xa8\xf7\xd1\x08\xc0\xd5\x12\x93F&\xb9\x00\xc0\x91\x97\x1f\xe0\xf2J\xf0\xbf\x04\x13\xc8\xff\xffY\xe0\xbf\xd7H\xc6,\xc9\xd0\xea?\x15\x0b\xaa\x85\x08\xfb\xb0?O\x00\xb6\\G!\xf2\xbf$\x95\xcf\xf0\xef\xc5\xd3?\x9acw\xf1\x9dg\xf9?\xfcz;^\xa5M\xf2\xbf\xcbL\xd6\x1a\xf1H\xf0\xbf\x17\x00\xe7h\xb4K\xc1\xbf\xff\xdd\xb0~\xf5c\xfb?\\\x99O\x02\xa00\xd7\xbf\x9d$\x93\xc973\xeb\xbf\xbci\xc2+.\x1e\xd0\xbf\\\x01\xcb3\xbc\xd4\xbc\xbf\\\xf7;`%\x08\x05@\xedW\xb0F\xf7g\xd6??\xe5\xdfqN\xc1\xae\xbf\x98\x92\xa06\x99\xdb\xd2?Wj\x1eB\xbc8\xd6\xbf\xb3\xf4\xec\x06&G\xfd?\x1b\x92C\x17\x13\x03\xfb\xbfJ\x87L\x9a\xecH\xee\xbf\x0c\x83<\xfe\x00\xf5\x04\xc0/.T\xb9\x81\x19\x07\xc0\xdbO\xc6@\xb04\xb1\xbf\xa0\xe2{\xa7r\xb3\x02@\xb0\xfbhP\xd1\xd1\x0e@_\xac\xee(\xc5\x9b\xf3?\xad\xdaf2\xad\x08\xa9\xbf\xa5\x86\x1c\xfe{\x95\x9a\xbf\xd4\xd5\xc6;\x1c\x9a\xe8?\xa2R\xa8+%I\xef?V\x1e{\x93\xe4Q\xe5?\x08j\xa1w\xb0s\xe5?C3SF\xd2D\xf4?\xa9#;\x0c\x16\xb8\xf5?\xeb\xa2\xf1\xf87\xc4\xdc\xbf+v\xfbJ\x9ce\xe3\xbf\xcb\x12 \x97\x96\xe0\xa9?<\xec\xb8\x052\xd8\xf5?o\xe7L\xe9>\xa6\xda\xbf\x9fR\xcd\xf6\xa3\xf3\xdb?D\x95\x0f\xecu\xae\xe5?\xecZ\xcc\xc7>\x01\xc3?\xa2\xd2\xb1\xd37%\xe9\xbf\xd2\xce\x15\xd96%\xf2?\x98E\xb1%\xd8V\xdc?\x91$\x1d\xfe7\xa9\x05\xc0.\xf7\x0f\xe1\xbb\xfe\xd6?)\x0f\xd4\xcaK\xd1\xd5?\x02\xb7\xf8X\xbc\xa8\xff\xbf\xbcS\xadh\x98\x08\xeb\xbf/\x0f[d\xc3\x1e\x03@z@\x04\xa9\x94\xd7\x16@\xb3\x1b\x99\nH\xf8\xf8?\xa4><\x17\x8b\xb4\xfc?\x98\xb1\x87\x01\xd0\xc3\xfe?\xd3_\xe1p\xd6y\xd6?\xabbW\x88Q\xae\xfe?\x8eE\xcb\xf6\x96\x97\x0b@\xfc\xdb}\xcbq\xc9\x07@G7\x13b\xa1\xa9\xf2?h\xe5\xb3\xedO\x11\x02@W\xb7NB\xad9\xe5?F\xb5\xb1\xd7%\x04\xeb\xbf_\xca\xa2J\xe1E\xe7?\x8e\x83\xed\xbe\x13\x97\xf1\xbf\x02\x8d\x86\xfe\x9d\xc3\xd5?|\x9b\xcd\x17\xc0\x10\xfd?G#\xe9O\x1b\x94\xf0\xbf\xde\xdb/\xd2S\xcb\xd6?\xe1\x19\x1dQ\x95\xcc\xb4?\x99\x1b\x18\x8b\xb6\xb5\xf7?\xda_\xa4Z\xf0\x19\xf0\xbf\xc0\xd1GJ\xf6j\xf1?5\'\xea"\x1d\xe5\xdc?v\xbcC\x8e\x9b\xd4\xfe\xbf`\x8f\xaf\x92\xa4S\xf4\xbf\xf0\xd1\xebT\x13\x04\xf6\xbf\x8a\xe5\x83\xc31\xc4\xfa?\x02S+\xf4\x827\x14@\xa1.\x8d\x94\x04\x1d\xee?/|k\xeb\x06\x1e\x10@3\xde\xab\xd6\x05W\x01@D\xc2B~\xfc`\x02@\'\xc4\xc1*\xae\xd6\x00@\xc8\xc7\xfd\x89\x19w\xfa?R\x7f`\x07}\xc1\x12@*B:\xf8\x8f\xf5\x0c@b,\xaa\xbc\xd7\t\xca\xbfM\'\x88]\x9a\x9c\xe5?\x94\xc3\x82r\xb6\x9a\xe2\xbf\xe2\x06l\xcc\xac\xe9\xd7\xbf\xa0\xea\xc5\xb5{%\xfc\xbf\xe5\xa1\xbem\xd8\xd5\xfb?\x9e`-\xd2\xa0\x15\xec\xbf\xb4\xe1!m\x0e\xf2\xf7\xbf#N\x00\xfc\x89J\xcb\xbf\xd92\x8b\x93\xce\xb3\xe0?\x0b\xab\xcc1.p\xd6\xbfj8\x88\x08\xbb\xfd\xa0?MV_\xb8\x83\xb9\xd2\xbf\xce\xcc\xbaa&\xd0\xf7?_\x99\x9e\xf38\xb2\xe5\xbf\xe5\x9ay\xf17\x15\xbb?\xa9\xb09\xbe\xbfn\x02\xc0.\x1c\x0f^\xad\x81\x06@\xb2\xd6\xb6deH\x18@o\xa5\xd7\xad\xb9\xdb\x10@\x1bF\x97c@\x1e\x04@\x93\xbcx|\xe7\xc0\xed??\xdf&\x02\x9a\xfb\xff?\xff\x06{\x11\x99\x00\x01@5\xad\xa25\x0f$\xd9?W\x90\xdfVn\x1f\xe7?\xbc7\xac>\x8ae\xf6?\x10\x06\xf0,\x8e\x88\xf9?\xf3\xa6\xc6[\x92\xe5\xc7?\xa6\xe3?\x8c\xb8\xc8\n\xc0\xf2t\xaaSX\x17\n\xc0\x1c\xc7\xe6\x98j\xbe\xad\xbf\x7f\xf3\xac\xf7(\xe8\xec\xbf\x11C2\x99\x18K\xed?NZ\xe4p\x9d\x1c\xd5?\x9d\xc0\xa0\xb0\xf6\x1f\xa9?\xc2\xb7\xcd\xc1\xf4@\xcd?\xaa\'EoY\x17\xf1?;\xf9&h`\'\xf4?\xc39\xc8\x00\x8a\xe9\xc8\xbf\xfc\xae\xc2\xa6\x1b\x19\xef?\x89\xc1`\xca\xd5\xd6\xe0?k\xdfG\x8a\x9f\x7f\xf7?\x85/\x7f`M\xf1\xf6?\xebq[\xc1w\x96\xb2?\x01\x90\xfds\xdc\xed\x11@\xf9\xcd!5l\x14\x12@\xca\xa8\xf4\x02\x91{\r@\xfc\x11\xf2\xcd\xab\x8d\xe0?\r\x8f\x909b\x8a\xed?\x13/\xb9\xb5\x1ef\x03@\xeaw\xf0\xc1\xf6\x86\n@4\xf2\xebvp0\x01@%m\xf0\x9c_\xcb\xf5\xbf/\xd8\xf6\x00\xa2\x1c\xfd\xbf\x94\xdc\x8ccvj\xb0?T\x02IN\xc3\x08\xdb\xbf\x1bC\xdf\xad^\xab\xf7\xbf\xe3\x19\xf2[^\xad\xf2\xbf\xc9;\xb5\xe6\n\x9a\xb5\xbf@\x95\xf9#\xc7\xb3\xf6\xbf\xb0\xd2y~\x98\xfd\xce\xbf\x8e1\'w_\x0c\xe1\xbf\xb4\xd0,\xcc\xb6e\xc7\xbfw\x83-\x16\xda\xf4\x9a?\xbc\xc7\x1a\xbb\r7\xc1\xbf\xcd\r\x98A\x9e\xf7\xda\xbf\x86jke\xc0\xa7\xd1?]\xe6\x8d\xc1\x1c^\xfb?\xa6\xed\xfet\x00/\xee\xbf@\x01\xa6\xca\r\x06\x04@\xf8\x84\xd7\xb5\x08*\xda\xbf_\xdc\n\x0b\x97\x1c\x01@.P\xe4\x99*\xb1\x0e@\x19*t\xf3~\x8c\x16@\x11\x1aSy\xc4\xd5\x0c@\x96\x1cK\x0b\xf5\xed\xfd?\x0c\x04\xf4\xf4\xfe\xa7\xf0?\x8a\xab\xa8\x85x\xeb\x01@\xe8\x0f\x06\x1a\x9a\xd6\xfb?\xcd\xa30\t\xe5\xdf\xf5?\x8f\xa6\xf5\xee\x8d\x16\x03@\xfbrDg\xce2\xee\xbftI\x82\xec\x90\xf7\x01\xc0\x05\xa5E\'\xdeA\xda\xbf\xb8\xb4\xb5^\tv\xf8\xbfL\xd8\xa9l\xa4\xad\xec\xbf\xd8\xb6\x93(\x1b\x19\xe8\xbf\xa8\xa0"\x1a\xfc\xbe\xd6?\x1b1\xafJ\xff\x17\xa6\xbf\x1e\xad@t\x80z\xe1?A\xcc\xf7\xcf\xa9\xaf\xdb?FSDS[\x15\xa7?9-rG\xe7\n\xfb?\xab\x9b\x063\xd3\xae\xba\xbfLWo&]S\xea\xbf\xb2S\xf7\x90\x13\xbd\xf6\xbf\x82q\x91\xfaV\xd4\xf2?#\xcay\xdb\xda\x91\xe0?\x9e\xb4\x9e \xfdM\xed?+D\x82\xedx\x02\xd0?kCo.\xbdk\xeb?N\xeei~g|\t@\x0e\xbe<1\x02\x05\x07@5\x97\x8e\xf84#\xcf\xbf\xd2\xb4<\xf0?\xd9\xfd?w[!\xeb\x9f?\xfc?\x9a?\x8b\x94\x9f\r\xe6\xbf\x16\xfd\xa4\xa2\x19\xed\xf0\xbf\xb7\xdc\x93\xe1\x07\xe1\xc6\xbf\xf4\xc2\x9b\x8a\xd4\x00\xe8\xbf$\xc2\xd68w \xe8\xbf\xf7\x96\xb8\xb8\x1ar\xcb?p7\x8fb\x01)\xe9\xbf$\xcd\xa8U\xa3\xca\xf3\xbf\x05Z\xc31\x03s\xff\xbf\x10\xe3\x87l\xc1\x8d\x00\xc0<\xb0G\xc3\x92\xf0\xcd?\xac_G\x0eB\x1c\xf4\xbf\xeb"\x9c\xcb\x08\xad\xdd\xbf\xf5\x19\x15f&y\xe3?\x17\xec\x1a\x97]\xf7\xd5?\xfa\xa2\x99\x94\x08\xb1\x97?\x91w+\x85\x1d\x00\xf1?\xc8z\xc8V\x7fO\xdd?C\xab\xb8\xa2\xe4\xe1\xd5?\x0cV\xe1GK\xd0\xee?\xcc\t\x11Nd\x93\x0f@/\x19\xf3\xbeW\xd4\x12@"P3\xc0\xa4h\x01@3Y0\xf2\t\x9e\r@\xe6s\xedp\xea\x98\x06@\x93\xeb\xd4\xd7\xdf\xa9\x00@\xc8;\xfa\x014k\xe5\xbfb\xa1\x9cQ\xb1\xb7\xfe?\xc2\xf4c\x85l\x00\xec\xbf\xf3u\xb7\xa0\xadt\xed\xbf\xc10P\x14\t,\xec\xbf\x92\xd5\x14\xeb&l\xea\xbfH\x8c5w\xb5T\x08\xc0@\xd3:\x14g\xb4\xec?\xc6y0\xc2\ny\xe4\xbf\xb0T(\xd4y\x9c\xdf?\x928\x98Y\x16\xbf\xdd?75\xe3E\xcd\'\xfb\xbf\xab\x8d\xf2\xb16\xc9\xe8\xbf\x81\xc8\x850+\xfe\xec?t\xba\x8a\x1e\xbcC\xf2?\x85.\xd5\xf6\xacN\xba\xbf\xf7\x9dKq\x84L\xf2?\x1a_\xf3\x1cy\xdc\x00\xc0\x8c\xb7\xcc=\xb0w\x08\xc0\xb3.\x88\x87\xaf\x10\t\xc0\xfbT\x1e=\x8bJ\xc7?\x14\x06;\xac \xbd\xc7\xbf\xf7K\x84d\xcc\xc3\xed\xbf\x81$.\xbd\x9a\xa8\xe3?\x9b\xf2\xb3\'p\xec\xed\xbf\xf3\x02\xf1\x06\x7f\xe2\xe1\xbfz\x19][\xdd\xeb\xbf?\xd2tR\xbb\t\xe8\xdf?\xbb\xa9\x9f\x8ddW\xfa\xbfq\x82\tn\xd5t\xf9\xbfq\xba\xf1\xeezG\x05\xc0\x85%\xd1\xa4@\xdc\xdb?\xe0\x0fJ\x92\xe7\xf2\xf5\xbf\xeb:\xcb^\xc2w\x05\xc0\xcd\x9c\xf1#\xad\xb0\xd4\xbf\xd8(\x1f\x9b\xb9\\\x01\xc0\x06\xc1\xd4?v0\xe4?\xbf\x10\x01\xa8n\xd5\xa8?}\x8f\x0fp\xb3\xdb\xda\xbft\x9d\xcd\xd3\xd51\xfd\xbf\x9dy\xfa""_\xf6\xbf\xf7\x8a\xd0G>\xca\x01@\xc1b\xfa1W\xfd\x02\xc0\xd0\x88X\xee\xb5\r\xf2?\xf7\xa2\xb9y~k\xe8?d\xc6\x84Y\xef%\x04\xc0\x18{Z\xcf\xd6\x98\x07\xc0\xeb\xcc\xaa\xfb\x9d\x9d\x07\xc0\xf5\xf0v\xdbF\x95\x07\xc0\x88sDS\n\xc5\x14\xc0\xfe\x84\xfc\x97"v\xff\xbf\xaf\x14\xfe#\x1f\xd4\x12\xc0aVh\x84\x15N\x0f\xc0\xb0\xa3n\x0e;\xe7\x1b\xc0\x0b8\xd7\xaa\x1cA\r\xc0\xc3SH\xf6\xb2\x1a\x0e\xc0~\xd2\xaf\rk{\xf7\xbfVW\xf6\x87\x81\x1a\xfc\xbf\xc5U{\x0e\x96\x99\xde\xbf\x83\xa7\xe0\xf7\x04\xfd\xf8\xbf\xcd\xa9\xbd\x9b\x0fO\xa7?\xba\xd7a+K\xfd\xc9?\xa0\x1f\x94\xe9S\x12\xf4\xbf\x8b\x8845\x81Y\xc8\xbfY\x14\x18\xbc5s\xf2\xbf\x9a\xbc\xf7=j\xd6\xe5\xbf\xda\xd5\xd3\xad6@\xf0?\xf6\xa6\xa2cs\x86\xf4\xbfK\xd9\xef\x1e+\x8a\xcd\xbf\xaf\xf8\xbc\xc0=\x90\xf7\xbfm\xd2\x84vV\x12\xfc\xbf\xc5`\xab-\xe0V\xd0?\xba1\x16tIs\xd4\xbfP\xc2I\xd9\xb2\xf7\x06\xc0\x12C\xa0i\x99b\xf3\xbf\xd3\x1f\xfa\xf7\xa8H\xfe\xbf.\x03\x0bNX\xed\x02\xc0\xadN\xfb*|\xf8\n\xc0\xdb\x99\xc1\xb6\xaeE\x07\xc0\xcc;m\x02\x96\'\x01\xc0\xdc\x97\x82Q\x1ea\x10\xc0\xa6\r*\x82\x9b7\x01\xc0\xb4^\x156?\xb5\x08\xc0\'\x12FR,J\xe1?\xf4-\xd5\xa4~\xbe\xf2\xbf\x07\x8e>=/\xce\xcb\xbfJ\x8f\x88\x08C\x9a\x08\xc0e\xc8\x98\xd4M\xfc\xe3?\x85\xcc\xe5\xa3\\\x94\xf8\xbf\xf8>/\xd8\x0e\xe1\xaa\xbfI\xb0\'8\xc4&\xdd?/\x0fZ\xc8\xeb\'\xc6\xbf\x80\x80V;k\x87\xff\xbf\xb2\x06\x92\xd4\xd43\xc4?\xf1\xf7b"4\xcd\xeb\xbfy\x9fF\x07\xc1V\xef\xbf\xccq\x06\x91|\x0b\xe3?]\x19D\xb7\x00\xb9\x02\xc0\xb5\xb9\xc6\xa6I\xc5\xe9?R\xe2\x80\x93\xfa\xf9\xe5?G\x91\xf0=0K\xd3\xbf1\xf7\\\xf8\x8e\xf6\xd7\xbf\x97\x08\xa3\x0e\xb7}\x02\xc0\xd9\xdc\xbe\xe7\x9f\x86\xf0\xbf,"\xc9C\x8db\xff\xbfU\x86\xd6\xdcf\xab\x00\xc0\x06;\xbe}f\xd4\xe7\xbf\xdc,\xd1\xbd\xbcB\xd9?\t\xe9V\xc0I\x7f\xf4\xbf\x885\xca\xd3\x0ef\xea\xbf\x19\xbf\xb1\xb0g\x87\xeb?P3\xa4o?\xc1\xcc\xbf\xcar\x13v\x8d\xe8\x02\xc0\xb6|\xda\xc6*_\xe0?\x08D;\xaa+\xda\xfd\xbfjO\x87\xa7\xd5.\xf9\xbf\xfcy\xf5\xe2\x12\xe2\xd2?4\xed%,\xdaY\xee?$aF=\xbb\xa7\xe0\xbf\x87\xff\x8c\x90+\xd6\xa9?Z8\x86\xf6P\n\xc1\xbf\xd7z\xc6\xfa=\xa1\xdc?A\xb7\xc9\x8ao\xd8\xf2?\x17,\x19\xed>\x1a\xf4?i\xbbHm\xee\xfe\xc9\xbft\xa1\x9dzM\x1b\xd4?*+Z\x90\x0e,\xf2\xbfs\xa8ZQ\x1d\xe2\xc6?\xd1b\xf60\xfd\xd3\xdf?\xfe\xbc\xc0\xed\xc9\x15\x03\xc0\x8dV\xf8\xa0\x98&\xf2\xbf\x04\x98\xb4\xe4i\x0c\xf2\xbf\xc0/1\x92\\\x81\xea\xbf\xe6\xdd\x96\xd9\xa7D\xfb?\x0cP\xef\xe0\x04\xb9\xe9\xbf\xb5\x04\x925\x91\xd6\xe6?\xd5\xeb82X\xd6\xa1?\xc4\x8b~0G\x1b\xd2?\xd20\x83\xb6\x00\xa4\xf6?\x17\x99\xd8$\xe7\xe4\xc4?}\xb5\x04\x9f\xb0\xde\xe6\xbf/Q\x9ff$K\xd6?\xa9\xcb\xe5\x815T\xfa\xbfC\xaf\xdefaR\xbf\xbf!i:Y\xe4\xc3\xf3\xbfH!\xb3\xad\xbd\xb1\xe5?t\x1b\x87\xfd\n\x94\xcf?&p\xc6b\xab\x8f\xe9\xbfxjq\x94\rM\xf4?=\xbf\n\xc8\x16\x08\xd0\xbf\xec\xc0\x97\x8c\x98\xa2\xd1?e\xee\x96\xe4\x94A\xf7\xbf\xca\xae\xdba\x13\xf5\xfe?Ujd\x1d\x9e\xdd\xde?\xde\xc4\x91\xa6&L\xea\xbf\xbe\xd6\xff\xd9\x9d\x8a\xc3?\x99\xdb\xdf\x1f\xbde\xe7?jF\x1a0W\xfe\xe0?\x83.\xc7;\x8f\x95\xf4\xbf[\xb7\xbc\xa2\xf1\xad\xe0\xbfUG8\x1e\x94\xce\xed\xbf\xbbd\xf51\x14]\xca?\xda1\xbe\t\xafy\xe3\xbf\xfal\x94\x16\xc1\xa9\xa8?\xac[(\x81\x02>\xac?\xa9\xc51\t\xca\xf4\xf0\xbf;\xba\xb6\xf4\x02\xa5\xf2\xbfv\xbe\x91\x06w\xd5\xf7\xbf\xf8>\x13\tT\xd8\xf0\xbfl\xf2\x82\x19s5\xf3?\xa9\x9a\xa4\x9bxk\xcc\xbf\x84fE\xf5\xc3\x8f\xfd?\x12il{!\x8f\xee\xbfo\xb5\x99\x08 F\x02@\x12vNRr\n\xca?\x0c\xb4{D\xfb\xa5\xe4?\x1f\x96\xdd\xc4\xd93\xf6?C\x91\x13\xa6\xff\xc5\xf4\xbf\x07i\x91e\x7f\\\xf5\xbf\xdbW\x1c\x0c\x1c\xfa\xd5\xbfZ\xd0A\x90\x10\xc7\xea\xbf\xec\x04\x90\'\x89\x1e\xe3?!\xe2V\x97\x07\x1f\xf9\xbf\xbf\x8bl\xd0\xa4\xd9\xc0\xbf\x1fS\xc01\x81T\xf0?\xf1\xb5\x03s\x13\x8c\xc5?S\x1a5{=\xe0\x0b\x04\xc0\xb8\xea\x8ao6\xc4\xf8\xbf~z\x99\xffh\x17\xf1\xbfN\xd4\xee\xda\\\xee\xf7\xbf\x1b1I\xb3\xca\xf1\x07\xc0\xf8\x14\r6\xdd\x1c\xf9\xbf\x0b\tB\x96t\xe4\t\xc0\xb5\x89\x98\x87\x96\x12\xf7\xbfHH"\xc8\x0ct\xf3\xbf\r\xcc\x05\xcd\xabp\xfa\xbf\xc7eK&h\x85\x01\xc0\xd1\xcaY\x86\xd7)\xf7\xbf\x0f\xdbt\x00\xca)\xd6\xbfz\x00\',\xba\xd8\x02@\x14\x03e\x87*\x96\xe3\xbf\xf7\xff\x1e\xa2Dr\xf4?9\xbf\xb8\x80j\x16\xd6?*\xedK\xb8.\xcc\xfb?!\xfa\x00AUl\xed\xbf\xceQMy\xca`\x00@x,B$\x08x\xf0?\xd3\x00\xf1$\xc1y\xe6?\xd3\xbb|\xe45\xb7\xed?k\x08y\r\xc9/\xf1?Jh\x9f\xb1\xb8\xfc\xc8?\xf6p\x98lb\xa4\xd4\xbf\xfc\x80w\x82E.y?\xfcO\x84F\x16\xd9\xf1\xbf\x1bR\xcda%\xfb\xd3\xbf+7\x02)l(\xfc\xbf|\xb6\xd5\xd9\xbdV\xf4\xbfa\xca\xc1\x86C\xde\xef?\x8c\x88\x9b\xd5fm\x90?\xb4#\xd6\x95U\x8a\xf0?\xa6\x93\xc4\xbb\xe8\x16\xf2?\xe2\x93\x0b\xb4v3\xc4\xbf\xa6\xee-._X\xe3?>Bh\x85MR\xe8?\xbbY\xaag\xa6\xe4\x03\xc0S\x98\xba\xd1\x9a*\xe3?\xa1 \x14\x8e<\xbf\xc0\xbf\r`\xc6\xf5\xe3u\xe1\xbf\xcd\x9c\xce\x81\\\xe0\xe6\xbf\x9e,x\xa5p\xfc\xe0?\xa6\xc5\xd2\xd4s\xac\xca?O\x8cB\x82\xca\x9b\xce?#\x81V\xcdbk\x91\xbf\xe01\xd33\xf2\xf7\xfa\xbf\xd0\x08\xf4\x9b\\\xba\xd5?Rfk\xe1\x9c\xc8\xbb?-\x87k\x95\xef\x13\xc8\xbf\xabr\xc1\xb9M\xd0\xf1\xbf\xac(\x1e\x94\x89_\xa9\xbf\x1b\x04C\xac\x87=\xa1\xbf\xe5\x82+:\xd9\xf6\xf6\xbf\x1a\xe9\xf1s(T\xd2\xbf\xc0Q\xbaFe\xd0\xe6\xbf\x91\xb2\xe2\xc0w\x0c\xca?\x876\xdd\xa1(\xe9\xae?g\x04\xcd\x15Tj\xc4?QX\x80\xd2p\xa5\xee\xbf\xdc\x155\xc7\x80\x8a\xf4\xbfK[\xe5\x00U\xf2\xcd\xbfb?[-\xc7\xa9\xee?\xf3%\xaf\xe4\xc7[\xf0?4\xf9\x15F\x16\x85\xf9\xbf\x8f\x11=\x80p\xac\xe3?jbNd\xa4\xad\xee\xbf\xb7\x9a\x92\xa3\xec\x0e\xf3\xbf*L\xd1\x11\xf5i\xf4\xbf~&\xc0\xab%\x9d\xc4?!,j"\xe7\x08\xf3\xbf\x12;\xba\xa8#-\xf8\xbf\x8bs%\xc4\xa9]\xe4?\xa3\x9f\x01\x94\xc3&\xf5\xbfA\xd8\xb6G\x98\xef\xfa?\x10\x9cx\tT \xef?\xd6\xf3\xf1\xd7\xe16\xc8?\xfb\xefw?\x84q\xf2\xbf\xc8l\x18\xfes4\xe6?\x04\xcd\x92\xa9C\x7f\xc7?\xd9\xbf\xc6\xafp7\xda?3\x8d\xcc$\x967\xbd?\xdb\xd2\xf8\x96\xc9\xb0\xe6?\x8crWw\x00G\xd6?\x14\x91\xda\xb3\xdb\xb0\xc1\xbf8k\xdaor\xdf\xe7\xbf\x8a\xd3"\xa3\x9b}\xf0?\xa0\x15\xd7\xb7Dj\xeb?*\x7fy\xca\xc6\x9f\xe7?\xd3;ddM\xa2\xd6?\x98\xfe\x8e71$\xeb\xbfM\x86\xf9G\x98\xa6\xdc\xbf\xd3\xab#7\xf8\xd9\xd8\xbf\xd3 \xdc\xd8x"\xf1\xbf\x17a\xac\x9d"\xa8\xe8?\xf1@\xeaR\xb7\xf0\xed?\x0f\xaf\x8f\xc2\x8b?\xe0?\xae\x88r\x07|@\xeb?ktVX\xdd\x8e\xe9?\x03\x1e\x8f\xa7\xa0\x81\x02\xc0#\xc6\x11\xef\xb5\xa6\xf3\xbf\x83\xf5\xa5\xa3NE\xb0\xbff\xd1\xeb\x8b\xb2r\xe4?\x8c\xc5v\xf8\x0e\xa5\x01@Pf\xf3\xbej+\xf6\xbfG|xF\xee<\xd6\xbfA\x93S\xbd\xc9r\xe8?\xa5\x8c\xa1h\xb74\xe8\xbf\xbaH<\x95M\x90\xe9\xbftt\x03\xe9\x077\x96\xbf\xe6\xf0\x97%\xf7\xb0\xff?\xa1\xb2:;\xd3\x86\xef\xbfHg\xabV\xa87\xd5\xbf\x9d}-\x1dI\x1b\xe4\xbf\x14\xc4\xd8\x91D\x18\x02\xc02\xfa\x85\x9fl\xa0\xe9\xbf\xb4@\x1e\x81\xee\xc7\xf7\xbf\x90%\xf7\xae\xd6\x98\xf3?\x93P\xb8\xc1J\xe0\xe6?\x90\xb9\x99*\xcd\x8a\xe8?\x8cz\x9b9a#\xe8\xbfR\xe7\xff\xbaPU\xe9?\xa5[c\x8cO*\xf3?t\xaa\xb5\x04o\x93\xf0\xbf\xb6\xb1\xf77h\xff\xd2\xbf%6\xd5\xa2:\xc2\xe1?\x8b\x90%\xf2g\xe6\xe8?\x1b=\xc8\xd0Y\x84\xf0?\xe1\t\x9d>\x92\xa7\xf6?\xe9\xb52M\xb7\xc1\xf0?\xdc\xd5\xd4\x8e"q\xd6\xbf\xa3j/\x0e\xc9\x15\xd0\xbf\xfa\xb8\x03XJ?\xea?\xa1\x9dq>\xfe*\xf5?Qm\xa0\x89\x05\xc1\xe4\xbf\xa9\n\x08r\x06\xfd\xf4?m\xdc\xa2\xa5:\xe5\xcc\xbfiU\xf0\xf4\x8fY\xfa?\xaaU\xa2\'\x84\xc1\xcc?\xd3O\x1a9\x1a\x8d\xd7?\xa5c\xbc459\xda?\x1aL\x93\xbe\xcbb\xe5?\xfa\xe2~\xdb\x04;\xf3?\xef3\xc5\x16\xec&\xd9\xbfU\x923\xb0\x15_\xe0?\r\x81\xd5b\xc3F\xdd\xbf\xf1\x80\x16\xd7\x11\x13\xfb?\'=\x9b\xbd%\xe0\xf9?ng\x91\xaaU\xda\xca?0\x9e\xd2$\xf6\x84\xf0??"\x03\x98\x8f \xcc?\xf5\xfb\x1d\xc7x\xab\xe4?W\xa2"ut\xdf\xfb?\xfcW\xea\xaf\x0b\xfb\xe2?&\x8f$N\xe8V\x05@5Eb\xb1\x93C\xc8\xbf\x0f\x8d\xafY\xb8\x1f\xf3?\x91\xf4\xf7\xf7\x00\x83\xfe?\x8b\xb0\xc2\'\xc9\x95\xe3?0\x98\xd2>b\xf0\xf1?\n\xf3/\xd1:X\xff?\x04GX\xf1\xac@\xf0?V\x13\x86\xaa\xd5\xaf\xc8?7\x14\xd0\xf8\xe0n\xe4\xbf\xc4\xd3\x9d-`b\x03@1\xbfs\x8c\xf6\xc1\xd0?a1\x97u\x07\xbb\xe3\xbfI\x12t\xac\x9b4\xf0?\x83\xdb\xe1\x1d\xf1\xbf\xdc+\xea\x92q\x8f\xbf?\xd16\x89\x87\t\xa1\xf1?Q\\\xf7\xa1\xb8\xaa\xd2?\xb7e5I\xbd\xbf\xc2\xbfoO\xa6\xee\x92\xad\xf7\xbf+\xf3s\xd0\x07o\xb9?M\xc7\x9eP\xf6\xdc\x00\xc0\xfe$K`X*\x08\xc0F\x14\xd0K>\xba\xf8?\x12\x95\xa4_\x8f\x92\xa7\xbfh\xf8\xa6O\xa7"\xf9?\xa7\x0b`{\xc1q\xf9?\xe7\xd7\xeb\x1b\xa9\x10\xe6?\xf2\xd2\xb5P\xa53\xce?5[\xa5\xfavL\x03@.\xb6\x14-B\x96\xdf?\xaf\xce\xf5\xeb\xd1+\x04\xc0C{\x18\xee<\xd8\xa3\xbf\x1a\xc7}1\x01\x1f\xf9?J\xc6O\xa4S-\xb4\xbf\x04V\x99\xa0\x05+\xf3\xbf^\xbfq\xe2#\x08G?\xde;\xf9\xb0\xb8\xfe\xea?\xaf\xbc\xae\x93\x123\xe4\xbfHL4\xd7U\xc3\n@`)\xc5\x9d}\x05\xdf?""\x87py\xca\x07@&\xf3\x9c,\xdd\xd1\xc8\xbfH\x15z\x90v\xc4\xd5\xbf\xa8\xfc\x1c\xc0V\xb9\xe7\xbf\x800\x9b.\x11\x8e\xde?bs\r\x14\x93\xa5\xe5?>\xaec\xa7\x91\x97\xed\xbf\xb5t\xdd \x15]\x05\xc0,PY\x1e\xbfY\xf1\xbf\t6\x10\'m\x1f\xd2\xbf\xbe\x95C**\xbb\x07\xc0D\xab\x99\x0f\xf3\xdf\xe3\xbf\x03\xf5\x19Nm\xd2\xd6\xbfc\x8ern\x93!\xf1?\xb5\xdf#\xd9\xb8\xfb\xd6?\xdd\xba\x9c\x1d\x12\xf1\xee?\xb1\x8eG\xb6\xba~\xf2?\x9a\xa4c\xa07\xf0\x01@\xf7E}id\xe3\xf5?\x85\x88^E;B\xf4\xbfsp\x98\xcd\xa9Z\xb2?XT\x0b+-\x11\xf5?\xdc\x1f\xea,EC\xe4?f\xb3}\x1e\xf1\xa4\xb9?\xb4\x90\xfd\xe9\xde\xf9\xcb\xbf@L\x05T\xbb{\xe1?1LP\x90\xb5Q\xb8\xbf\x7f\xf3\xc9ls\x93\xfb?[\xb0\xddq\xcb\x97\xed?J\x9e\xf9\xd3\x89\t\x02@\x0f(\xf1\x0e\xe5%\xed?\xf1\r)\xd1\xcd5\xf3\xbf\xac\xaa\\\xab\x93\xf4\xff\xbf\xeb\xf2\xc0-\xbb\xef\xf4\xbf\x99\xa5]\xa1\xa1\xb8\xe5\xbfu\x97\xcc\xb4\xf8\xaf\xe7\xbf\x84^.\x10dM\xd0?"#\x99M\xaeb\x01\xc0F\xd2\x8bm\xeb\xba\xf6\xbf\xc7\xd2\xf9xl?\x04\xc0\x1f\x89\x94\x94tx\x01\xc0g!\xf9x\x0cu\xe3?K\x83<>\x95\x80\xf0?W\xc9\x05\xeb\x8d\xb1\xfa?\xaf\xc8T\x1f\x88\x97\x07@\xe8\xd0\xe2\x94>\x02\xfe?"_\xd0\xeb+C\xfd?\x14\xe5\xf1\xbb\xc3\xda\xe6\xbfA\x17/s:\xae\xe7\xbf\xc4y\xb1\xbb \x1b\xb8?\xe1\xa9\x14\xb4\x91\x84\xe8?\xfb\x9d\xbf\x8d\x06\xb7\xd3\xbfD\xf1e=T\xdb\xf4?\x12\xd7\xdc\x1bo\xf9\xec?\x7f\x13\xc8\x82\x14\xe5\xe4?\xea|\x8c\xae\xa6\x82\xb8\xbf\x8b\x0e\x98\xa0\x0c\xcb\x01@?k\xcd\x9b\x1e\x13\xe7?2\xe1\xfaOl\xa2\xb6\xbf\xa9\xc2\xe2\xb8\x15\xc1\xc9?\x0c\x87\x14r\xde!\x00@s\xe0p\xfe\'\x8e\xd2?\x04\xb0\xb1!\x16\xc5\xec\xbf\xaezNgm\x99\xe8\xbf\xc5\x86|7\x13\x94\x03\xc0\xd5\x98\x96\xab\xd8W\xfd\xbf\xffjx\xd5\x0bp\xf8\xbf>Yn\xbf\x02\xa2\xf9?s\x91\x0cw6\x8d\xfc?\xe8\xf8[\rT\x8a\xea?\xde\x88#\x17\x91\xe3\xee?\xcd\xf7\x08\xd5i#\xea?hxk\x04]<\t@\xc4h\xda=\x91C\xfd?8\xb0I\xd5\\\xf3\x05@\xfcO\x1d\x10\x84\xe1\x9e?4._nia\xe7\xbf@\x8e}\x8d\x15\x01\xf9?=\xc2Z\xa4\'f\xe1\xbf\xed\xbb\xf6\xee\xceo\xd5\xbf\xad\x11\x84Y\xe5?wj\x9f=\x16t\xf9?A\xf8N\xb8\x03}\xc4\xbff\xe9\x7f\xaf\x92\xbb\xd1?#`>\xd7\x82\x0b\xe9?H\x8b\xa5\xa8\xfb\x19\xe1\xbf>\xd1\xa6\x8e\xcd\xfa\xaa?\xf5\xe4OS\xc1x\xb7\xbf\xc4xV,\x1a\x03\xc0A\x9c\xabaYO\xeb?\xb7\xa9\x82\x89 \x90\xfc\xbf[\x0fF\xda\x18P\x0c\xc0\xa4\xa0\xa8J6m\x00\xc0\x94%;J\x80n\x04\xc0\xd8:A\x0cH9\x0e\xc0o\xefJp\x0c\xda\xf3\xbf\x00\xdaZ*\xaf\xd4\x07\xc0\xfd!A\x01{\xa8\xe4\xbf;\xcd\x9d\x9a\x85k\xe2?\x90\xcaq\xfdb\x18\x13@\x13\x05\x07B\x84\x15\x14@9\x17\xe2\x9c.p\x02@\x99,D\xa6\x06\x87\xea\xbf\x12]UR\x86\xcb\t\xc0\xce\xc2\xb7%\x14\xf2\x06\xc0\\S\xe62\xe2\xed\xf9\xbf\xfe\xe1&\rm\xfd\xfa\xbf\xfeE\xaa%X\xe3\x02\xc0\x08\xcc6\xe1b\xe0\x08\xc0\xac\xc8\xca4\xcf\xb3\xed\xbf\x8b\xa2o\x7f\xa7w\xda\xbf3\xf8\xd6\xb35\xd1\xce\xbf\x1ay\xb5d[`\xed\xbf\xf7W2\xfc\xadK\xd8\xbf\x19;_\x9dq\xc6\xe2\xbfC\xeb\xb7\xa8~R\xce\xbf\x81\xb8b\x0c\xd5\xf0\xed\xbf@\x98\xfbX" \xc4?U\xa08\xe6\x03!\xe9\xbf\x07T\x9bp\x11\x15\xf7\xbf\xf8\x06(\x823\xbb\xc6\xbfb!\xcdu\xcc\x12\xff\xbf\x16\xc0"J\xden\x00\xc0W\xce\xbf\x13{\x87\xad\xbf\x93\x85\n\xe5\x84t\x03\xc0\x10\x16\xcb\xf3\x80w\x91\xbf\x95\xd8\xb2\xa4\xbf\xfc\xe9?\xf9\x91\x1e\xe5\xf4u\n@\x1d\x17\xc9\x08\xc0\xa4\x0f@\x18\x99\'\xe2\xbbh\xf6\xbf\x1c\x8e*HW\x00\xf7\xbf\xd6i\xb2<,(\x04\xc0\x1drHQ\x8d\x13\xfe\xbf\xbe\xe0\x15w6\xe7\xd6\xbf\x03\x1c\xc7\x19s\x80\xd4?\x15\xdceC\x1e\xed\xdb\xbf\xcf[(\xab\x1a\xe2\xf6\xbf;\xd8\xf1\x18*\x9e\xe9\xbf\x9a^\x0e\xd5\xff\x02\x01@2b\xa6L\x04\x95\xef?\xfa:z$J\xbe\xef\xbf!\xd5\xf6\x88t\x88\xe5?\xee\xb7"oNe\xcd?\x18&\xbc\xcd\x0c\xd4\xb5\xbfp\xf3:l\xbb\x97\xf6?|}\xb1\xdb\x1d\x9f\xd8\xbf\'n\x9e\xfd\xfdz\xb8\xbf\xb6\xe0w\xb2e&\xfb\xbf\x87\xcfs\r\x9a\xf3\xe8\xbf\x0f\x8c$\xc7K\xe0\xf2\xbf\xae\x01\x8b\xe1\x06\xe6\xff\xbft\xf9m\xabm\x8f\x03\xc0\xba\x1d\xa8\xa4\xa4D\x15\xc0\x8a}\x96dA\xdc\xf2\xbf\x15\xec\xe2Vv\xb5\x04@\x8c\x08\x9d%|\x06\x07@\x15n#\x81\x03H\x03@5\xba@\xc5\x03\xfa\xf4\xbf\xa2\xe2\x00\x8e\x10\x01\xfe\xbf\xff\xd7F\xc7\xb8\xc2\xf3\xbf\xac\xa8\xcb~\xb7\x0e\xbe\xbf\xea=\xa5J\x18\x85\xfa\xbf/\xe0.\xc8pL\xe3\xbfF\xb1[F6\x18\xd8\xbf\x8a\xd2\xbaow\xd2\xe7?\xc0\xca\x18\x1a\xcc\x16W\xbf\x93\xf0\xd6\xe4\xc0?\x08@\xd7\xe7\x1f\xd2\x04M\xfc\xbf\xad_\xa5\xb5"-\xad?\x1aq\x1c\xa0\xe9h\xd0\xbf+)\x8b\xa7H<\xee\xbfc\x91\xf8 \x0c\x92\xce?\x9e\x9e\xfaOm\xdf\xdf\xbf\xac\x8c\xdf\x90\xb1\x9e\xff?\xdd\xc7\x88\x8e\xab\x1b\xc1\xbfl\xfb\xbe\x13~X\xe4?x\x1b\xcd]\x91E\xd5?W\x94\xb4\xa4\x99\xed\xe6\xbf\xb4\xb6\x00_\xa7\xdf\x00\xc0\xee\xab\x13\xceH\x02\xeb\xbf\xf68\x9f\xc6\xbf0\xef\xbf|\xb4j\x9d\xd3\xa8\xee\xbf\xbf\x8bL5O\x91\xf1?v>/\xca\xbbu\x02@{\xaa\xa7kze\xef?\x052)/\x83\xc5\xfa?\xd4\xd3\x1e\xdc1\xd9\x02@\'\xfb1\xc3\xbb\xbb\xdc?\xdeHE\xa2\t\xc8\xe7?\xea2Z\xa5\x8b\xa8\xb4?}\xa8\x86-\xab\x04\xef?\xd9R\x93\xb8;M\xea?\xe3\x99\xe6`W\xf1\xf3?\x06\x1d\xa2\xe9\xeag\x03@\x96\xef\xd4\x97d\xd4\xce?\xeez\xf9\x8e\x9a\xe6\xd9?\t\xda\x0f\x0e\x92\xdc\xda?\xf32\xd3\x06BK\x01\xc0\xab\x0c:\xa6\xd9\xa6\xbd\xbf\xdd\xb8\xf1\xb1E\r\xd0\xbf\xc2(\xfcE\x8b\x08\xef?\x8e\xaaUjCW\xe8?=\xf7Q{2\xa9\xea?z\x1d\x1b\xc0\xe8\x14\xf0\xbf_\xa0U\xed#r\xb7\xbf\x8c\xe4\xe7\x05\xe2n\xf2?_\xeb\xc2\xca[\xb3\xd2\xbf\x90\xe1x\xf5\xdb\x9f\xcf?\xe9\x96\x0c\xd7\xa2\xa3\xd7\xbf\xa8\xfb3\xa6\xac\xb4\xed?\x04\xb9\xce\xae\x88\x05\xc1\xbf\xb8Y\xe2\xf6\xa6>\xe1\xbf\x9b\xa3{La;\x0b@^.\xeeU\xc2\xcb\xd2\xbf\xd2\xf2c\x89\x06\xda\xf4?\x1b\xcc\xe4,\x19W\xf9?+\xa0k\xfc$\x9a\xf2?\xff\xf36\xb6A\x80\x02@aB]\x16\xbd=\x92\xbf\x0e\xed\x1b_E\xfa\xd4?\xada\xdd [m\xe4?\x0c\t\x86\xc2c\xb5\xb8\xbf\x84iz\xe5\xd4\xdf\xd2\xbf\x19\xe4\xf3\xaa{\xc4\xee\xbf9\xe4[|\xbb@{?R\x89\xbbQ.j\xdd\xbf\x1e\xf6\x95\xe1\x9b\xf5\xe9\xbf\x13z\x08\xfeD\x8f\xe3?\xb9\x1b\xa4K\t\xb1\xee\xbfW\xd3i\xeb"\x93\xe1?@\x17\x83\x94G\xbf\x00\xc0\x07\xbe\xe9\xfb\x15\x12\xfe\xbf\x12\xd3\\\x93\xff\x00@!zj!\xb8\xab\xa1?\xb6\xe1\xcd\x95>\x91\x02@\xb23\xa3\xbf+\x14\xcc\xbfc\xab\xa0\x08\xb3\xa9\xb6?\xcfWT\tB\xaf\xf0\xbf\x1dz_\xef\x18\xfc\xd5\xbfU~?\xff\xca"\xe8\xbf\xd6\x87\xd8\xb5\xf0o\xf2\xbf\xcf\x04\xd1J\x99\xd0\xe2?f\x8d\xc2\x9e\xfb\x8d\xef?\x1ce\xd1\xc15\xd6\x03@ap\r"zS\x08@H::5\\\x9c\xf3?\xdfp\xc2\xb5x\x10\xf7?0~\x86\'\xeb]\xf9?1\xd3\x0bv\ro\xf6?f.\xe1Q\xe9\xad\xcd\xbf18\x0b\x98\xdbp\xd9\xbf\xd5\xda\x02B\xc5Q\xe7?\xfe\xd7l\x1f\r\xf5\xc8\xbf\xfe\xfa\xbe\xae\xc3\xc0\xe4?\x04[\xc5I|\x16\xda?\xacP\xfd\ta\xf0\xb4\xbf\x8f\x7f\x98\xba\xf6k\xe3?\xbaw\x8d\x9a\xb7\xab\x01@;9&\xd1\xb6\xfe\xbc\xbf\xfc(\xac\xcd\xd2\xc0\xcb\xbf\xb6\xb3}\xc0\xa0\x18\xe2?23\xde Q\xe4\xe4?\x9cQ\x89\xf9\xff\x88\xd1\xbf\xd4K}HnM\xef?\xc2\xd1\xb1\xd6\xce\xff\xf3\xbf"\x9bX\xcfj\xdb\xbc\xbf\x89zM\xab\xb5\xbe\xe3\xbf\xfa\x18\x9a(\x8c-\xc1?F\x86v1\x95\xae\xf9?\x92\x18DH\xb6}\xe5?\xd1\x1a\xe7\xd8\xba\x8b\xdd\xbfU*\x0bE\x05G\xd6?4\x8f\xac\xb7\xf6\xd4\x04@\x0b4@<\xe2\xc9\x02@&\x9e\x12\xa6b\x9f\xed?\xa3\\\xcd\x0c\x06/\xe4\xbfh\x1f\xe3\xe7\xfaX\xe0?\xa4\x9f\xb6\n\x89\x81\xf5?N\x15\x8e\xf6\xa8d\x03@\xa1\xba\xb8X\x06\x06\xe2\xbf\x88\xf6\xae\x10\xbe\x8f\xe2\xbf\xb2\xe0\xc7w5\xf3\xc6\xbf/\xb2{P\x82\xce\xe5?V\x82\x155\x10\x99\xe1\xbf(\xfc\x18N\xd7\x81\xb2?i\x0e\xb1\xfd\xd1\xd3\xde?\xc1r\x8d\x05+}\xa1?\xc6\xfa\xfc\xef\xa3<\xf4\xbfrQv=X\xf6\x81\xbf\x01-9z\xee\xfa\xf5\xbf\xe6\x19i\xaf\x88\xd7\xd9\xbf\xdf\x18\r0\xcc\xd7\xe7\xbf0\xb5Gp\xc9V\x01\xc0\xb5\x87p\xbb\x03\x1d\xc1\xbf\x9c\x82\xbe\x06\x82\xf7\xba?n\xdbWrVp\xf6?\x104)\xe3d\xda\xe9?\x8d\xe8e#\x0b\xe2\xc4\xbf\xf0\xa9\xac\x87\xc4\xee\xf6?\xe3+\x8ah\xf0\xc7\xf6?\x9dk`^\xc5/\xe8\xbfZ\xdd{U\x90\x05\xf6\xbf\x15\xbaa\x05\xd2\xc1\xd8\xbf\xab\xae)\x06ox\xfb?\xf1\x9f\xf6\x9f\xf2\xa7\xe8\xbf\xab\xc9\x1f;\x9e\x97\xfa\xbfP\xef\xd3|\xa1\xc0\r@\xf7\xc3\x99\\W\xef\xf2?\x02\xf5;S0\xe8\xf1?\xe2Lq\xce\x8eI\xe8\xbfU\xa7\xd1%\xfa\xd9\xe6\xbfQ\xc3\x10HG\xb1\xc5\xbfO\x17\xe7\xfe\xca;\xb1?\x9f\x06yV\xa2\x10\xf5?a\xd5_\xbbp}\xe7?\xb4\xeb?/u\x16\xd5\xbf9\xc0y\x89B\x93\xed\xbf\xf5\xb0[s\xe2;\xfd?\xbf\x1f\xfa\x05\xe9\xae\xfc?\xf8\xd3\xc7\xa1\xe0\x19\xd7?/H\xc5\xd5\xfb\xd2\xf1?:\xf3)\n\xbc\xf3\xaa?\x96\xa2\xd5\x90x\xa8\xaf\xbfu\xfdw\xbd\xe95\x07@\x02 \xf9\x15U\x1f\xd2\xbf\xbe\xb5\xab\x15\xd1\x05\xfe?$\xb1\x03\xb7R\xcc\xe3?\x95\xc5\x0cWf\x81\xe2\xbf\x00\xe6\\\xb7B\xc3\xe8?b\r_q\x19\xbe\xc5\xbf{\xeb\xb35\xd9N\xd3\xbf\xd0\x86\xbaf(\xf2\xf7?T^\xaa\xec\x9b?\xf5\xbf\xfa\xa1\x0b\xc0\x87\xbb\xe1\xbf\x94\xd3}r\x03\xe8\xf9?G\xb8kr\x88\xc8\xcd? \xd2j\x9e]\xda\xf8?qhP\xf3\x00\x1e\xf7\xbf\xaeN\xc0E\xcc\x8b\xc1?\xa8\x1a\x9fp\t\x90\xe1\xbf\xa8\xda9H1\xdc\xec\xbfC1\xd8)9\xf7\xee\xbfQ\xa8\x90D\xfc\x04\xf4\xbf:{\xd8\xd9o\x9c\xe9\xbfK\xdf\xa1\xd1"/\xdf?#p\xf1x\xe3\x9f\xe9\xbf\xd3\xe4\xd7\x93^>\xab?\x14\x81d!\x00\x12\x0b@V\xfd;\xa8U\x0b\xf6?\xac\xf8\xff\xed\xa3\x8a\xe3?2&\xa3}@\xf7\xe2?\xc4/\xbaq$\x8b\x03@\xc9Ne\x7f\x89\xd1\xf4?\xdd\xa0\x07\xf681\xea?\x15\xa6\x88\x80I\xc2\xd2\xbf\xbf^\xa3Y\x89i\xf9?\x1b\xd1M\xe3\xb7\xae\xf1\xbf\xe6bi\xd25X\xc5\xbf\xff\x14\xea\x14\x06\xbd\xf6\xbf\xa1K`\xe9\x8f\xe2\xe9\xbf\x97pY\xd0\xedR\xa1??\xfa5L\xb3\xec\xe4?Y~\xdc\xdd\x1b\x17\xeb\xbf\x0fi\x8fC\xd7#\xec\xbfxN\xe9\xaa\x94\xec\xec\xbf4\x95\xf0\x0e\xd8-\x00\xc0\xa0\xd9\xeb5\xe2\x9e\x00\xc0\xc7a\x12\x80K\xe6\xc5\xbfyA-=\xb5\\\xde\xbf\xdc\x81\xc0\x8f\n\x1a\xe4?\x8e\x9ej\xf8Y\xd8\xdd?\xd23wV\x89\xe7\xfa\xbf,n\x10\xb3\xba\xb9\xc5?\xf0\xd2\xa2T\xd1q\xe1?\xa2\xbck]\x96\xe9\xfd?5\x14\x8e\xfd\xec\x81\xff?H\x19X\xe8\xcbN\xe0\xbf\xad\xf7\xf6!\xde\r\xfe?\x80\xe6\xdd\x17\xb9\xa0\xb0?G\x08\xb0\x93\\\x1d\xb8\xbf\x14P b\xc0t\xfd?@\xd89\x1c\xbb\x18\xae\xbf\x9e&\x05\x93J\x8b\xf4?\xc4S\xec\xd1\xf8\x8d\xe1?\\\x8cI\x18\xedj\xd4?\xc4{\xf0\xd3g\x93\xe5\xbfq\x0c>\x1c\xb53\xc4?\xadzZQ\xe5\xb9\xf1\xbf6\x12\xf3\xa1\x7f\x98\xd5?$p\xfaSb\xd6\xdb\xbf\x16\xfd\x0ez5r\xcf?\xa4U\x10\xb8\xe2\t\xb7?g\x10r\xb8 \xb3\xc2?\xa7Sb\x92b\xb4\xdc?j\xe9R#a\xa4\xd9?Q\x19\x18U}\xd2\xc2\xbf\xaf\xda}W\xfb\x0f\xd5\xbf7?v\r\xf2\xf9\xb8?\xea\xb88\xe1\x9a\xe8\xcc?)\x7fD\xfcj\x86\xc2\xbf\xa2#\xbdL\xc2\xee\xfd\xbf\x8b-\x03\xfcuQ\xfc?\xae\n\xd0\xc1\xc5\xc6\xe7\xbf0q3s\xa0\x91\xff\xbf\xc8\xa7\xd4\x05rf\xe3\xbfx\xb8c=\x7f\xab\xed\xbfPu\xd6\t\n\x83\xc3??\x88j\xcf\xb3;\xdc\xbf\xa2\xda\xb7F\xf8\xd1\xf2?\xa4\xc7\xfd\xbd3\x88\xf9?\xcb\r\xa7\xa8L\\\xd7\xbfC\xad>\xbb\xb2\xf8\xd1\xbf\x0e\xf6/\x100n\xfc?t\xc2\x97\xed\x082\xf2?>\x18b\xc9\xb1;\xcc?2\xe0}3 \x9c\xce\xbf\x9e>\xc31k\xb9\xf8?\xa4!\xbcb\xd7\xbc\xd4?\x88I\x08[\xc8\xc0\xeb?>\xcf\xf6\xa1eE\xd7\xbff\x10\xef7\xe3\x06\xb1\xbf\xc2\x16~,\xb4\xf6\xd7?\xc8\xfc\xf2\x15c\xd0\xdd?G0w\xf1\x17\xee\xd8?\xbe\xfbG\xd9\x10\xac\x01\xc0\xd4\xc5\\\x8a\xdc\x1d\xe6\xbf%\x03\xb8\x18\xcbs\x03\xc0>I[%\xea\xaf\xf0\xbf\xfaV)<\x17\xb1\xb9\xbf+\x84H\xba%F\xdd?\xc9`\x9bN\x95-\xcd\xbf\x10n\xc4\x08\xa9\x10\xaa?u\xc2\x11\xd7\x88\r\x03\xc0\\\xc5\xd8ul\x90\xf7?o\xb9\x14&\x83\xb7\xeb?7e\xeb\x1a\x1f<\xb4\xbf\xf8\xad:aK)\xd4?/p\xb9\x8ew\x82\xe9\xbf\xfd`\xa3\xd1[\x92\xe1\xbf\x8f\xdfz\xb3\x9bV\xea?Z \xc1\x82\x9cv\x00\xc0\xf3\xaf\x98\xca\xeb\xad\x00\xc0\xde&\x80\xe5\xaf\xf0\xf3?\xf7\r\xeb\x7f7e\xc8\xbf\xc3\x862\xec1\x9e\xc7?\x0e\x10O\xe5\xef\xab\xd9?\xeb\xf5\xfa#(\xdd\xf1\xbf7C\tU.\xa0\xe9?/\xc3\x0b?\x99i\xfb\xbf\xd7\n\xee\xab\x9fn\xa5\xbf-\xb8P\xe7\n-\xf8\xbf!j}A\xefv\xb0\xbf\xfa\x88\xdf\xdb\xcf\xb2\xf2?0ifc\x82\xb3\xdf\xbf\x17\x9dy\xeb\x93\x7fy\xbf\x15\xc8\xfds\xd7\x1e\xf6?\x81\xdb\xc6\x90\x11\xa7\xe2?\xdc\x9e\x97,`@\xc5?\xc2\xc8D\xeb\x88h\xc4?\x83\x1dw\x90(+\xe6?B\xb5\xc6~\x00\xa2\xf2\xbf\xdb\xe8\xfc\xe1\xe5\xbb\xf3?\x81\t\xdb\x93vU\xef\xbf\xd5\xd6\xbf^\x83\xe9\xf1\xbf\x8a\xcb\xf7PJ\xb8\xdf\xbf\xf2)L5\xc4\xd9\xe8\xbf\xd5^!\xe8S\x10\xe1\xbf\xbaK\x9a\xac\xbf:\xc1\xbf\x9a\x19\xf15\x8e\x88\xf5\xbf\xb7\xf4\x80\xdf\xccy\xe6?xWD\xd7\x86\x8e\xd6\xbf2\xcep;M\x9e\xf6?\xcfQ}\xcf\xc1\x06\xd0\xbfV%7\xc8]\x84\xfa?\xa0\x96d\x19\xddY\xfb?\x94J\xa88\xb7\x1e\xf1?\xed@\x16\x00D\x81\xad\xbfoo>\xd8\x96\xee\xe7\xbf\x03k\x8dH\xe7_\xfd\xbf\xe2\x84\xa5c\xc6\x07\xd6\xbfh\xbc\xeb\x88\x8f\xfb\x08\xc0O\x98\xa9\x82\xa76\xe6?L\x8d\xf6a\n\xeb\xed\xbfz\xce\x9e\x16\x1a\xbd\xe2?2\x95O\xa0\\O\xe0?SB\x1a<\xf5H\xe3\xbfDo\x1d\xb5\xb1b\xed\xbf\xfe\x98(\xa0\x90j\xb1\xbf6 W@0\x85\xee?\xa2\x98=\xadz+\xee\xbf\xac k\x18\xfb\xd8\xe0\xbfG\x92\xcf\xfc\xd8-\xbe\xbfO\x99\x96\x0b\xbc\xfb\xd6?\x8c\xa5V\x8d\xd7\x82\x94\xbf\x9c\xb6\xa5\xdd\xe9,\xff?.\xb5\x83\x96P!\xfb\xbf}\xec\x8e \xe8\x9b\xde\xbfm\xcb}8\x01\xb6\xfa?9\xa76\xef\xc9\xc5\xee\xbfF\xd6\xa1\x83\xeeJ\xee?\x1bb\xaava\x1f\xb0\xbfh\x92\x10\xf6\xf3\xcb\xf0\xbf~\x90{\x03\xe6\xf2\xe0?\xc2\x86XTi/\xf0\xbf\xa0B\xaeVa\xfc\xfa\xbf\x01\x0e\x993\x8at\xd7?<\xca\x84o.v\xf2?{\xd6{\xb3\xf4\x99\xc9?.I%\x8c\x7fu\x00@\\Q\t\xedS\x15\xef?\xe1@\x9dc\x8c\xfd\xd6?r\\\x1b+\x1dc\xb8\xbf\xbc\x991T\xd1\xc9\x9e\xbfy\xa7\xda1\xb4>\xd5? \xfa\xa6\xa3/\x84\x00\xc0?ir\xd0{E\xf3\xbf\'\xe7_\x8b\xa1\x0e\xb7?\xc7^OKS\x1b\xfc?\x9c\x01\x95!\xa6\xde\xeb?\xc5\xc2R\x8f\xe5/\xf0?e\xb94&Of\xe4?:\x16\x1d(\xa6\xda\xe5?\x02\xfd\x9ak\x191\xb5?h\xdd\xea<\xc3g\xea?\xe8.\xf8\\\xd4E\xf0\xbf\xb9\x12\xbf\xbf\xc8}\xd2\xbf\xa9\x1a\xf9ZR\xe5\xfa\xbf\xe8=\xedm@\xe2\xd3\xbf\x1f\xcd\xeac\xb6h\xe5\xbf\x8e<,*@l\xe8\xbf\xc8\x84\x9a4\xcd*\xef?\xa4w\xd7|\n\xab\xe0?\x94\xe33\x99*{\xdf?\x05\xf1\x93\x810}\xf3\xbf\x89%\xfdD\xd1\x90\xd6?\x81\xda#\x14U-\xbf?6\x1bY\x92\xd4X\xcb?\xc7\xb7C>\xf0\x12\xec\xbf\xf4\xb8\x1a\xd3\x0b\xf5\xed?\x02\xca\x00\xddu\x89\xd8?:\xe3\x88\x13l\xb9\xf1?|\xac\x82\x84hB\x84\xbfB\xd1\xe8\t\x15\xd8\xd5?mM\xb1_\xf6\xee\xed?\x17\xbc\xb7}}\x19\x04@@x;\xcfw1\xda\xbf\xe66\xab\r\xa9\x01\x00@\xa6KH\xba\xc33\xf6?uE_C=0\xf1\xbfT@m\xd5\x94\x12\xe8?\x04M\xaf\xae\xec\x0b\x00\xc0\x1a\xdfg\n\x05\x9c\xe2?\x95\xa0-\x0c{]\xe6?\xa6)\xc1\xf1Y\xb4\xc9\xbf\xe5\xedM\xb6i~\xfb\xbf\xc9M\x82\xa6\xdef\xef\xbf|\x16\xad\xdd\x14\x85\xfd\xbf\x85\x97\xd1k.n\xaa\xbf\x81\xc9\xe2\xdb\x10\x81\xf6?\x18\xb9\xb1^\xbf\xdc\xe4\xbfl\nEm|\xe6~\xbfZ\'\x8d\xa8\xf6Q\xf7?\xe6*\xfdo\x1e\x0b\xee?\xe9\xc80J\xdf\xc8\xf4?3\xbc\xf8\x8bz.\xe1??\xa0u@\x9f\xfa\x00@\xaa\x06\xb1\x1cJo\x01@.\x8f\xd5\xae\x0f\xe5\x00@\xf5BP\x98"\x82\x0c@!\x97\x04\xd8Z\xfb\x02@\xd5\xfc>\xf8\x90R\xe8?\xb1\x9b\x05\xc5h0\xdd?\xbf)$ \x1c?\xeb?v\xa0\xb3{\xa3\xdb\xf3\xbf\xb3\xbb\xfc[\x96`\xe0?>\xcf\xbdU\x18\x08\xe8\xbfoU\xf2\xbeF\xed\xfb?\x14>\xe8V%\xf1\xf2\xbfd\x8c\xb5V\xd0\x83\xf1\xbf4H\x87\x82\x17\x85\xfc\xbff\xb6\x8d\xea\x9eM\xb2\xbfO"n]\x8f\xdb\xe7\xbf\x83\x01\x1aec\xb5\x0f\xc0e\x92/\xc1\xc8S\x07\xc0\xe7\x90Nc\x15M\x03\xc0\x0b\x9a\xe5\xe1\xa0\xcd\xb7\xbf\xa5O)a\x17\xf5\xe7\xbf\x86\xd8+\xd3\x80\xa3\xf4?\\\xd0zl\xc8\xd3\xf6\xbf\xf6\x9c\x84\xc81A\xe5?\x0eG\xc2\xee4\x1e\x00\xc0K\xd7B\xa8_\x9e\xe6\xbf\xd9\xbe-\x81\x06{\x07\xc0\xa7\xc2\xc6\xf5\xb4\x03\x11\xc0\xb9\xea\xa8\xc3N\x90\xf8\xbf\x0e\xee4O\x83\x19\x00\xc0\xbc\xd7)\xc6\x1e^\xfb\xbf\xa0S\x8c\xc1\x15\xfc\xeb\xbf\xe6\xfd\xc3\xf9\x8c\x85\xfb?U\xfa\xd2\xdf\x91\xb3\x04@\\ \xc73\xb4\x0c\x0e@\x84\xc67\xf5\xbdT\xf8?tv\x07\xcf\xad\x97\x03@\xdd"R\x7f\xbfH\xdf?\xf3\xf4XgiT\xb5?:?[\xdd\xfd\xa4\xbc?\x02\x83c\xfd\x18\x04\x0b\xc0e\x02\xff\x92m+\x0b\xc0\xf6\x12/k+\xcc\xfd\xbfm\x10Z\n\x04\xe5\xd9\xbfr\nr\x9f\xf0)\xf0\xbf\xa0\xae\xf6]\xebH\xb2?F\xd2\xab\xfd\x8f\x8e\xe3\xbf\xe2\x94\x86\x1az\x1c\xd2\xbf\xef\xb2w\xe9G\xb1\xf5\xbfz&\xb13$l\xd9?r@\xcf7\xf6\xcb\xe4?\xf9tm\x1e\'\xdd\xf9\xbfJA\x83q\x13\xff\xf1\xbf\x19)0\x94\x94R\xf4\xbf.\xeb\x1c\x11\xe2\xdf\x01\xc0\xaer7\xe9\xd01\xc5\xbf\x0b:\x7f\xfe\xcf\xc9\x04\xc0\xc1<1\x82%\xdf\xe0\xbf\xbb\xd6\xa9\xc7?\xc3\xfd?\xaf4\x1aPQ\xc7\xa1\xbf\xc6\x04\x19\xa9\x89\x1f\xf5?D\xa5V\\5D\xf5?\x8f\xc6=\xa6\xb3v\xef?C&V\x80~\x0f\xd1?\x87\x17Xxa$\xf8?\x1b\xe3f\xba\xa0O\xf5?\x159\xaa\x95OX\xe8\xbf\x8b\xab]\xf7\xe3E\x03\xc0p\x1d.\xd2\xea\xef\x03\xc0o\xc1 \xb5\xb1\xb2\x04\xc0j\xe4jtou\xea?=\x06~\xfa\x85z\xfc?7\tb\xe4\x8c\x19\xf0\xbf0b\xdb\xc0\xc2\xef\xe1?T3\xba$G\x11\xcb?\xaa\xd7TT3F\xf2\xbf7X\xc4\xe0\x86\x9a\xb8?\x89\xa9.\x82{\x99\xeb\xbf\xb2\x9c\x9a\xfb8\x85\xf1?\xd6\xb64}\xa8G\xc2\xbf\xe0\n\xd9\xab\xcc\xe6\xa6?-\x93o\x9a\xe4\x82\xe8\xbfg\xf9\x87BS\xc0\xec\xbfn-\xc2\x95\xe3\x01\xfd?g\xe5*l\xdb\x19\xf8?\xfc\xc7;f\xd2\xa8\xf3\xbfk\xcf]V\x89\xf0\xec?\xdfV/\xb0|\x90\xce?1`7\xfd\x16\xc3e\xfd\xbf\x04G\xc4\xf6r\xec\x02\xc0\xe1q\x0c\xc9Z\x9b\x05\xc0\x08\x19\xc1i\xc5\x01\xfa\xbfo\'\x12YM\xd9\xd6\xbf\x15\x85\x9b\x97L\x1e\xd5\xbf\xeb4\xeb7W)\xf1?"+\'\xa2\xb1#\xf3\xbft\xc5\x0c)l\'\xd3?\x04-\x8c9\xc3\xbe\x05@\x17\xb0\x96%\x89\x1c\xd2?x\xf1\xac\xe4*W\xf6?\xa76\xb8\xe3\xc9w\xef?v\xf1\x891\xba\x9d\xe0\xbf\x9b\x8f\xef\xa8\xf4[\xec\xbf\xa7\\\xadqZc\xe7?3\xc8v\xbd\x88\xb6\xe1?\xcfbi\x92\xaa\n\xe7?\\k\xe5 \x15|\xe7\xbf>\x1es\x14\xdd\xd6\xc3?/IE\xe7\rZ\xbf\xbf\xdb\x8d\xb8\xc8\xcd\xfd\xe3\xbf\xcb?Ms?\xe3\xe3?\xa8\xc3\xce\x85\xc4I\xf6?Ey)X\x7fV\xce\xbfK~\x9b\xbc\x83\t\x06@GO\x9a\xce\x07\x11\xd1?\x1b<\xbf\xd5\'\x01\x02@\x9d\xbe\xdbT\xa2\x10\xcd\xbfX\xcb\xbc\xe2\xc9\xb1\x02\xc0RfG\xb1/o\x02\xc0\xc1\x7f\xf5\x9d\x0b\xf9\x08\xc0\xc5\xdbV\xa4\x1c\xd5\x05\xc0A\x9c\x08\x9f\xf9-\xf1\xbf\x8f\xe2\x96\xe2\x98\xa1\xff\xbf\xc2\x8103c\xcb\xff\xbf\xb3a\xc7\x8f\x8b\xce\xc5?\xeb\xc9\xf2\xb9C\xd8\xff?\xe6\xb7\x87~^r\xf2\xbf\x822\xf3\x0c\xd4I\xd9\xbf~\xb5\xe5\x040k\xcd?\x1b\xb6\x0f\xe4C%\xe2?\xd6\xb5m\xa3,\xd6\xe7?\x8d]\xeb\xea\x00G\xe5?\x804\xb9\xd5i\xe6\xec\xbf=\xfd\xa8\x9c\x97A\xbd?t\x1a\x1dO\xd9\x04\x9b?\xf4\xc52G\xe0\x85\xe2?\xcb\x8d\xa1\xdeEm\xe0??\x98\xd8\x9a\xec\xd9\xb9?\x87\x8a\xdfo\x12\x89\xf4?\xe7\x1a;\xc7c\xad\xfd\xbf\x19d\x99\x834\x92\xf0\xbfM\x94\xf8\x1e\xce\xe9\xe3?\x80\x0b\xec\x9f\x8a\\\x00@\x8dr:\x06}\xa5\xfa?]2\x94\xf2J\x9a\xde\xbf\x1b\x15y\x0e&\xff\xfd\xbf\xccN\xbeuV\x9b\xf3\xbf\x8c\x9cH$O\xf1\x03\xc0yOjt\xcab\x14\xc0E\x7f\x94tr\xf8\x15\xc0\xd5\xa7\x11\xc7\xba\x83\x16\xc0\x01\xb6^\x17\xe1|\x15\xc0\xf25\'\xb7j\x1c\x08\xc0\x82\xb9J\x03^\x80\xeb\xfc?~\xf3\xc1Y\x06\x12\xf3?\xccZ}f/\xbd\xc6?\x15\xdec~\x8d\x85\xc0?u\xfeO\x9b76\xea?\xf5b\x8e\xf88*\xf3?\x19\xb6\x16| \x03\xd8?\xef\xfcI\xd7\xcaM\xe9?\x96\xebpF\x9fM\x02\xc0,\xfbje\xd2\xe7\xc2\xbf\xd95\xd2\xed \xdd\xe7?=\xf6\xf9\xbb\xcc\x9e\xf3?\x80\xdc~\xb8\xaa\x81\xe1\xbf\xae\xa47m\xbf\x9a\xe0\xbf\xcf\x80A%\xa6\xf3\xef?:\x83:\xd3\xa8m\xec\xbf)\xfe\xad\x94H\xeb\x00\xc0\xcdf"\xf78\xdb\x13\xc01\x86v\xef\xa7^\x18\xc0\xaf\xad\xa1\x0fhT\x14\xc0\x06\xa9\x980\x06\x8b\t\xc0\xa0\'2\x89\xe4_\xd7?c\xd5\xade\xf6P\x07@je\x1e\xb8K2\xe3?a\x0c\x999\xb1\x98\xe5?PT\xc2\x17\xb2\xdf\xe0?\xec\x93\\?1\xf8\xf1?]de\t\x96\x97\xe7?\xda\t#A\'L\xe7?\x99\xf07\xbcn?\xe7?\xe8\xd3%\x1fa\x86\xcc?\xdb\x03#\xfb&\x03\xd1\xbfo\x90\xe1_\xc5\x80\xf1?\xa0\xa3~}sh\xf6\xbf\x13\x82\x04\x1c\x9f\x9e\xcf\xbfT;?\xe8\x7f\xc0\xef?\x08j\x98t\x80\x13\xf7?\xf4\x1fX\x9b\xfb\x90\xe1\xbf\xe9\xb8\xb0\xb2\x1e%\t@q\xba\x8e\xa1{g\xf8?rL\xd1\xd6\x87\x85\xd6\xbf\xc3\xf8-n\x05\x18\xf1\xbf@\xe3D\xdd\x0e\xc7\xb7?\xdb7\xe5K8\xa9\xe4\xbfT\x99\xa7[\xac\xa5\xe8??R\x06\x98)\\\xcc?\x83\x81\x81*\x92\x13\xf3\xbf\xc5\xb8\x82\x8a\xb5\x08\xf2\xbf\x17\xc7\xa6+\xa3\xd3\xf5\xbfm/\xf8\xe7A\xea\xd8?\xf6\x12\x9d\x01\xda\r\xd9?\x85E\x02k\xef\xf0\xdb?\x81\xe9{\x82\x98\xd4\xe8\xbf\x11\xc1A[\xc0x\xc5\xbf8\xaf\xc5\xbe\x96z\xf0\xbfCa\x10XD\x1b\xe2?\xc9%\xca\xeal\xb0\xf4?$\x0fB\'\xcb\x01\x9f?9\x1a\x14\xa7\x12B\xc3\xbf,s\x19\x81\x81\x04\xeb\xbf\xbc\xaf\xa3\rt\xeb\xf1?\x9e\xbf\x97)\x00\xec\xe9?\x9a\x15}Q\xf8\xdf\xbb\xbfe\xc46y\xc4i\xd8?\x1f@s\xc5dO\xb0?[\xc7G\xed\x0cz\xdd?\xed\xc6\x81\xfa\x05\x87\xf4??nwY\x11\x80\xfc?\xcd&\xbfx\x8d9\xe7\xbf\xfe\xe4\x8d\xab\xa66\xa2?O\\\xf3\xa6H:\xf1?i_"s\xec\xe7\xeb\xbf\xd4\xf7\xda^8\xe9\xef?\ryW|nB\xec?\x9e;f(\x7f\x80\xb6?\xab]t/P\x9f\xb7?[ \xae\xfclL\xe3\xbf\xb7Y\xf9\x1c\xd7\xe3\xd7?\xd41\xe2\xf7t`\xe2?7\x7f\x00\x9a\xd3-\xed?\x0b\xdev\xe9\xae\xf0\xd7\xbfb\xbe;\xa9\x00{\xe8\xbf7\xeeS\x05\xb4\xd9\xf2\xbf\x95\x99Fq\\\x02\xe4\xbfh\xf9\x7fgv0\x8d?\xbd\x85c|\xee\x15\xaf\xbf\'\x83\xfa\xbe\xe1\xf4\xe5?,\x88\xa5h\xdbt\xf1\xbf\xd9Z\xc7k<}\xe3\xbf\x06\x0cz\nd\x08\xe6?\x94\xc9\x8c\x03\x0e\xa7\xe3?\xa0b\x81#(\xfd\xfb?\xace\xf2e\x0e\xcd\xf0?\xa9\xcc\xadi\xce)\xe4?<\x90\x0b\x11\x10\xa7\xf7?\xc9\x07\x15\xf1\x83\xa9\xe1?\xcf\xdc\xdb\x9e\x18\x8b\xe3\xbf\x00\xb3A\x12R\xb4\xf4\xbf\xef\x0b\xf8\xa3\xd9\x94\xfb?-\xab]T\xbb_\xd9?mR\xdd\xc1=Q\xea\xbf\xce\x0c\x0e\x1dS\x04\xc6\xbf\x19K\xae\xa1\xf5\xae\x05\xc0\xcb8\xaa\xdf\xae\xc9\xf9\xbfh\x03K\xfcG\xc0\xe0\xbf\xb0\xf1\x83>G\x8b\x00\xc0\x8eF\x12\xcaa)\xf4?\x83\x9c\xc72B\xb5\xcc\xbfe\x046@\xb4\xd3\xf4?\t\x02\x88\x19M\x93\xc3?m\xfa]\xcc\x1c,\xdb\xbf;J\xdd\x8f\x052\xe8\xbf\xf5\xe0\xf0\xf5\xc8\x9a\xfe\xbf\x1f\x06\xdaX2\x11\xe0\xbf=m\x87Q\xfa\x07\xf6\xbf\xfa4\x8a\xe7\x120\xe4\xbf\x08%\xb9\xa6\xee\x80\xd9?\xca\xad%\xca\xc6u\xbf?*\xb3\xb9AO\xb8\xc7?\xf3\x89\xe6\xddT\x82\xfe?+n\xd2=\xcb\x8a\xd3?\xf4=\x05!\x99S\x00@GoR\x8bqc\xf0?6H\x18l6^\xf9\xbf\x9f\x8d\xc1\xd7\xacq\xf8?\xcc\xab^\xc4x\t\xd5?W\x98\xb3\x13\x17+\x02@\xe5\xd6\xf7u\xe9\\\xe0?{\t\xea>\xb8\x96\xe2\xbf\x16\x97\xe0\x1d\xae&\xd2\xbf\\\xbe\xba`P\xab\x01\xc0J\xc1\xb3\x9fld\xee\xbf\xfd\xd9\xf7\xc4\xc5?\xf0\xbf_Q_\x80\xfaZ\xab\xbf\xc8r\xd0\xa7/$\xe3\xbf}u\x07I\xa5Y\xe7?\x8c\xb9<\xeb\xf0\x8a\xe5?H\xb2Y\x93\x1b\xda\xe1\xbf\xf3\x96\xec\xcd\x8c\x9e\xe9\xbfZ\xf3\x1eJ5y\xed\xbf\x9e\xab\xb9\xea\xf9\x1b\xeb?\xfb\x1b\xad\x13_W\xf5?c\xad\x0f\xceq4\xdf?\xde\xb7G\xea\'D\xf6?\xca<\x11\xf0\xb8\xe5\xd2?nM>v\xad\xd5\xf5\xbf-F\xee\xfb\x99\xb1\xf2\xbf\x9e\xf3*N\x0b\x9a\xce\xbf\x16\xcb\xab\x92\x07b\xf6\xbf\xf1\xd5\x99\xf6C*\xfb?l(\x12J\\\xc8\xf2?G\xa0\xf5\xdd\xadW\xd7?\xaa\x05\xa4\x1e\n\x0f\xf4?\xcc\x12\x93\xa5g!\xd3\xbf.\xbd\xfd\x9f\xd7<\xed\xbf\xcfX<\xba\xc6U\x00@V\x04!\xa9\xd7,\x05\xc0\xb5h\xb3{AK\xf8\xbf\xee\n5a:\xe4\xed?\x99\xd5.\x96]\'\xb8?\xb2C\xa8\x8a\x8eP\xc4\xbfk\xd7Q\xf00\r\xe5\xbf6\t\xd0`\xe1\xb8\xb0\xbf\xe2\xf0\xb84\xac,\xeb\xbf\x8f\x97\xeddFq\xea\xbf+\xd3\x93\xf2\x94\x16\xf2\xbf\x97-\xea\xe3u\xc8\xd5\xbf#\xed\xe1\x7f\xaf\xe4\xd4?\xc8\xfe\xf4\xedf\x1b\xdc?\xea\t\xdc\xef\xd65\xf4?\xe4l8@\xaa\xa6\xf5\xbf\x14\xbe+\x8b]\x8c\xe0?\xa8\xee\xf5\xd7\xdd\xed\xdb?\x85\x93\\\xd9\xb4_\xe4\xbf\xe8\xa2\xa0\x84,\xd7\x06@=\xec\x80\xb8\x0f\x9a\xed?\xd7\xed\xfcmPm\xcf\xbf\x12\xf4\x1c{\xe5\x9d\xd2?\xae,\xf4L\x08\xdb\xec\xbf\xd3\x0e\xc3\xf1\xbb\x97\xcd?Ih\xce\xcc\xa6\xc1\xd3?\xf8}Lk_\xbb\x10@(\t\xbax\xc3\xd2\xf5?\xfcse>\xb5:\xfd?\xc9\xb9\x7f1\xc3\x07\xd2\xbf\xf5\xe8\x10\xa9\xd3\xa5\xc3?\x8c]\xe4\xe9\xcf\xaf\xc9\xbfH\x06\x04\x96y3\xec?j^2z\x87\x95\xf1\xbf\x8c\x04t\xfcN\xd3\xef?\x01\xc3JF\x87f\xe3?Y\xa4\x04\xc0\x15H\xef\xbf\xd4o\xba\xba\x12\x9e\xe4?\xaeG\xaeT\xe4R\xf6?\x00&\xbf\x8b\xc3\xad\xe2?\x82\xdb\xfa\x0bb\x91\xe7\xbf=\\\xdd_\x81p\xc2?\x13\x82~z\xfc\'\xd8?\x05l|\tY\x03\xee?\x0fJ\x12\x073U\xea?B;V\xd9\xf5v\xcd?\x1b\x8eC\x92V\n\xdb?-Z\x1b\xbes\xd7\xc3?\x97\xaf\x85\x0bM\xf3\xcd?\x14\xefi\x8e\xb6}\xe1?\x97\xdd\xab\n\x1f\x8f\xc9\xbf\x0c\xba\x918<\xdd\xd4?\x83\x9fQ\xd7\xc8\xb4\xe6?\ny\xd1#C|\xf9?M\xa5\xc7~\xe8\xef\xf1?\xc0\t\x06\x973\x17\xe0\xbfY3F\xe2:\xa6\xe0\xbf\x0c\t|yG\'\xf0\xbfn\x89\x0f\xef{\x0c\xe3?\xac\xd0\xc3J\xe8/\xd8?R\\pe\xa0\xa0\xdd\xbf@|\xff+\xe1\xd8\xe6\xbf\xeco\x8d{\xef\x84\xeb?\x1b\x8e\xd9\x90\xd2\xec\xe1?\xd1\x9a+8\xaf\x0f\xa1\xbf\xc7_z\x87\x07\xbd\xfc?3\xf6CC\x88\x95\xae?7\xd5j\xaf;\xdd\xe5\xbf\x16(\xb9\x8a6\x15\xaf?\xe6\xa7\xb4\xe582\xf2\xbf\x12\x9c\xe8L\x84\xe5\xf2\xbfL\xa7*3\x9a"\xd1\xbf\x0c\x14\x89i\x0e\xad\xcf\xbf\x14u\xb6E\xa6\x8a\xca?o\xe1d\xbf\x04\x19\xf0\xbf\xee\xf5:\xecWr\xe5?\xa6\xe69a\xdbB\x03\xc0u\xeaH1\x0cL\xe4?\x8f\xca\xdf\xb7\x08\x1d\xc5?C\r\xde\x05\x0f\xb8\xd9?X\xc8\t\x10\x17\x06\xf7?\xbbd\xd3\xe8\xebs\x9f?]\xe8\xd6+t\x13\xb4?\xcd\n\xa7\x19\xe7\xbe\xee?\x08X\xb6\x1c\x91\x8c\xf0\xbf\xdaF\xd2\xd7\xf4\xb2\xe4\xbf89\x18\xabR\x8b\x00\xc0K\xc5\x15_t\x8b\xf5?\xb9\xde\xd4)\x06o\xf2?):EC\xed\'\xc9\xbf\x9d\xd5|\x95\xc0\xdf\xf5\xbf\xbc\n\x80 V{\xce?B\xef\x8dba[\xf5\xbf\xaa\xbfd\xda\x00\xba\xf8?\x84\xe4=z\xaa^\xd9\xbf\xf6\x07Fu\x91\x02\xed\xbf\xbb<\x13\xc7\x91\x9f\xd0?\xdf\xa2e\xc3G\x15\xe9?1\xee\xc1\x19\x9c\xd3\xf3?\x95\x82\xe8\x04\xa9\xe7\xe7?\x9c/\x99w\xbb\xa3\x00\xc0\\\xf4\xbfaq\xd0\xf6?\xfa\x84 \x8d\xe3\xd1\xe0?\xa7\xd4{\x17M\\\xc0?\xe5\xb9\xaa\x0c\xf3\xe9\xfd?o\xcbC\xd0.3\xfc?4)\x1f\xb4\x92\x97\xe0?\xcb5\xb5\x17\xc4\n\xe2\xbfUaNy\xda\xac\xf9\xbf\x02\xf1\xff\n\xf3\xb0\xe1?l)\x01y\xe6\x9b\xe1\xbfs\xe3\nZ^\xa8\xf2?\xe9\x02\xdd\xa3\x90\x86\xd4\xbf\xaf{\xf9g\rT\x97\xbf\xa9\xfb\x85n\x08|\xf5\xbf\x93\xaa~+\xb6U\xd4?m\xf3,\xe0*z\xfe?E\xdd:\x0b\x99\xa9\xe1?=Y\xd9\xfa\xb6\x81\xd9?D\xb3\xab\x0cI\xf4\xeb\xbf\x9ax> \xf4\xb4\xe9?\x1eA\x07\xd1\xde\xe3\xfc?|\t\x84\x98\x9d4\xf0?\xc8\xc7\xd3\x84*>\xe6\xbf\xce\x08\xcf\xad\x14\xb3\xde?\x1f\x03\x17\x1ed\xc3\xbc?\xe16\x1c\x9ak5\xf8\xbfi\x16\xc0\xcbe\xa8\xf1?\r\x1f\xb6Kj8\xc5?\xcb\xeb\x19\xa8=\xcd\xef?\x7f\xb3\xbe\xae\x9a\xee\xef\xbfeM\\\x19\xd9*\x01@w\xbe\xb1\xae\x98Z\xa1?\x147)\xca\xc2\xe9\xe1\xbf\xeb\xea\x80\xba\x99\xfd\xfa\xbf\xbc\xbb\xb9\xa9\x880\xd7\xbf\xb0\x16@\x14;\xc3\xac\xbf\xc2\x9c-\t\x83g\xf4?/\x07\xa8N\xfc\x96\xd5?sJ=?\xd4\n\xc3?/\x18"\xfe2%\xe3\xbf\xd4G\x07\xe6\x7f\x8f\xf6?\x87+\x85\x9d\xcc\xaf\xf3\xbf\xb4!\xc2\xb1pJ\xf4\xbfI\xaer\xb2\xc5K\xca\xbfS\xe8Ll\xd9\x90\xe1\xbf\x9eq\t`\x91R\xd5\xbf\xb1\x12\x18\xb20\xb4\xde\xbf\\\xc3\xf0<\xb2\x10\xd2\xbfP\xc4\xd2\x85\xb90\xd5?lC\xb0\x10^\x9d\xcc?\x16\xbb\x98S\x81]\x01@2\x8c\x15b\xc8\xf5\xd2?3\x95j\xfe\xee\xec\xd3\xbft\xc2\xd0\xd6E=\xac\xbf[9\x06\xf9\x85\x1d\xff?\x17s\x00\xb7t\x19\xc0\xbfd\xd8$ZW2\xc6\xbf\xae\xbd\xb25\xed\xf6\xf6\xbf\xd6\xf9y\xfa\x90\xdc\xd3\xbfY\xa2J\x17\xb0I\xcb\xbf\n0H<\xd1\xad\xe2?\x1a\xf4\x10\x1f\x89 \xcc?$u\xc26\x13j\xe2?\xc9\xc5\x02Vz\x0e\xe0?\xfa"\xa5\x15\xda\xe3\xd8\xbfOf\x8b\xf4\xe9\x1a\xec?d\xb4\x9a\x88go\xea?\xcb+\x11\x1a\xd29\xe9?i\x94\x11R\xa7D\xf0\xbfi\xa0tz\x18\x9a\xf5?_gP\x8aA@\xd9\xbf\xd7\xaf\xe2\xd8\xdb4\xfa?\xfda\x9f\r\x99\x10\xe9?\xf1\xa1\xd1uK\x06\xe6\xbf\xaa\x83\xb4``\xb3\xf1?\x8cr]8\x0c2\xfa?\xc5\n*\xd7\xc5`\xea?6t\x88\x97c\x02\xf5\xbfaX\xc9\xf9;u\xf4?\xe9\x1a\x8b\xc9\xebj\xc2?\xb6\x89J\x86\x8e\x17\xf5?KF\x89H\x96\xe6\xb9?\xd3\xb5\x86\xee\xb7\xeb\xea\xbf+\xb7\xb2\xa6\xac6\xd4?\x17)\x13\xf6\xb1\x8f\xd9?\xf9ib\x15h-\xb0\xbf_\xdb\xfc$\x95\xa4\xd7?\xe5\xa9\xc2\xef\x053\xa0\xbfYC\xd4:\x95\xff\xea?\x80y\x07Ux\xb0\xdd\xbf\x136\xdb\'\xf8\t\x03@NN\xaf\x87\xc1\x06\xe1\xbf/\xb7\x8c\xcf\xcf\xf3\xf2\xbf\x83 \xbbc \x1f\xb4?\xe8\xc4\xd8\xb5\xce\xe4\xd8\xbf\xc5\x14i\xf4\x1d0\xd8\xbf\xdcn\x9e\xcc\xf1\xca\xf5\xbf\xed\xa3X\xa7\x00P\xf3\xbf8\\\x98\x8aS\xd1\xfb\xbf3\xe0\x84\xe0\xde\x7f\xe7\xbf\xca\xba\xe6C-\x99\x04\xc06\x01\xea\xcd^\x91\xfa\xbf6K\x021=\xe4\xb0\xbfY\xf7\x1c\xc1\n!\xe2\xbf\xb2U\xa2c\x86\xf1\xfb?\n\x8e\\Q\xde9\xfe?\xcf2\xa7\x81\xd7g\xe7\xbf\xd8\xf7\xf17\xd2H\x0b@\xeai\xa3EP\xb5\xf7?~\xf3\xcf\t/\x0c\xf5?G\x05;\xfd\xe5\xf0\xfa?\x83\x07\xb3\xf2\xe1\xe3\xe6\xbf\x9c\x87{=Y\x01\xf2\xbf\x07\x87\xd9\xb7\xde\x96\xfa?\xd6\xe51b\xeb4\xd3\xbf\x89\xfc\x05w\xcc\xc0\xd9?\xd0\x07\xd3\xb9a\x05\xc8?j{O%K\xa9\xf6?\xef{\xf3Z\x92\x07\xb6?\xb5j\xd7sB\xc0\xf0?\x1b~\xf6\xc7\xa5&\xdc\xbf\xcd]\xf0\xa3y\xae\xee\xbfG\xf6\xc0\xbf\x8ep\xd3\xbfV3\x84\xa7\x8d|\xf1\xbfv\xda\xe7\xf9\xcd\x7f\xf5\xbf\x13\x102\x82\x8d\x88\x05\xc0\xd2{\x7fi\x0e\x08\xae?\xe4"\xe5Hv\x0e\x02\xc0\x1c\xbfv3\x01%\xe1\xbfP\xf1\x1e`\x1d(\xfb\xbf\x9d\xc9\'\xee\x93\x05\xe6\xbf\x05\x1d5\x17go\xf2\xbf\xc1\xd6t\x1djj\xf4\xbf\x0cUX\xfb\xb7H\xbb\xbf\x8c}\x93\x18\x89\x16\xf3?\xe5\xef\xc6\xf6\xe1L\xf9?}\xdb>\x01\xf4\xd6\x05@#T\n"0|\xf8?\x85n\x85\x13\x13\xfe\xf0?\xbb\xc6\xa7L\xea\xf0\xd5?3\xe8\xf8y\xc6\xfc\xbd?\xb4\xe44\xd5\xdbO\xf9?\xcb\xb8\x9bL\xa6\xdb\xbb\xbfD\x03\xda$\xb5\x1b\xd6\xbfC\xa7\xabY:\x1d\xf2\xbf\xf0;|1S\x9b\xfe?\x9d\x95D2\xc6\x13\xe5\xbf\x97G\x7f\x94{V\x01@\xc7\xc6t\xcc\x07\xd4\xf1\xbf\xab\x05\xe3s\xab(\xe7\xbf\xac\xea\xdc\xa94\x14\xef\xbf\x81`:\xf3\x82\xa9\xf0\xbf^\x01\xbcT\xf2.\xfa\xbfu,\xa0\xa7\xc8\xc1\xcb?6\xe5\\0\xcfL\xf8\xbf\xca\xb6G\xdb\xb2\x9f\xd6?\xc3\xbd6\xe4\xb1\xa8\xd4\xbfG\xf1\x85C\x02a\xfe\xbf1~\xfa\xaa\xd6\xff\xe1\xbf\\\xe3\x95\xddkZ\xeb\xbfz\x1a\x9c\x88\xa2\xd4\xf1?\xd2\xa6\xbeI\xc8\xf5\xe7?y\xc3\xed>p\x05\xf5?\xb0\x8dR\x924T\x00@V\x07\xba\xb8\x01\x1c\xff?K\xcc1\xbc{d\x02@C,2R\x05c\xfb?\xc1d^\x89\xaa\xbc\xed\xbf\x15\xcd\x854\x06O\xe7\xbf\xbb\xc0\xd4\xd2\x92\x0f\xe2\xbfZF\xa4\x87\xb66\xeb?8\\\xdb\x17\xc7g\xe2\xbf~\x8a\xb0\x99\xca\xf2\xeb?\x02{\xb5f\x198\xe5?I\xb9\x08\xd9\x90+\xfc\xbf\xe8\x1fA}\xebQ\xfa\xbf\xfee7{\xee4\xf2?e\xaeQ\x1c\xb3\x9b\xf1\xbfA\xd6\xa9\x8e\x90\xb4\xd1\xbf\xd0\x88\x12\x7fR\x9d\xe1\xbf\x93\xe16b\xd9\xb8\xf0\xbfd+.\x83\x86u\xe2\xbfu\xdf\xcc\xd8\x1f\x91\xd0\xbf\xe1\xf6w\x01\xb2*\xe6\xbf\xd7\x10\xbb\xb1\x9e\xb9\xf7\xbffl_\x19?\xb9\x07\xc0YHz7\x96\xcd\xf0?0\x02\xe2\xe2c\x81\xeb\xbf=\t\x1f\xfdI\xf6\xdb\xbf_\xdd\x9b\x811-\xcd?v\xbb\xce\xa1vw\xf3\xbf!\xcf\x9b"\xd4\xcf\xda?\xabY#\xcdd2\xde?v7\xb2\xfa\xfb\xbc\xee?m\x15\xa2e\xfd\t\xfc?\xb2\x81\xbc\xc7\xaag\xf5?\x1b\xcdW\xaa\xddv\x97\xbf\x12\xfe\xde\x90\x8d\xd1\xe2??\xa0"\r\xa2\xbd\xf9\xbf\xdb\xb5+\xd8\xb8\x8c\xe2\xbf\x976q\xe2K\xae\xff\xbf\'1\xa2\x05\xfb\xfc\xfc\xbf\x91\x99.\xa3\xfc\x88\xf1\xbf/\x93A\x194L\xd4\xbf\x89\x7f\x1a\x9a\x9f\xfa\xe2?\r\xbd\\\\\xea\xfe\xe6?\xfd\xd7\xe0q\xb1\x90\xfb\xbf\x1b\xfa\xeenT2\xf3\xbfa\xeb:\x00K\xac\xe8?\x01F\x15Q\x91\xa3\x00\xc0;<\xa6\x12P\x18\xc7\xbfY\xa0\xfet|d\x02\xc0]\xcf\xdd5o"\xe8?\x9bb\x03\xb0\xbd\xe3\xe5?8m\xe0\xbb\xe22\xff\xbf\x97\x16\x87\x95X|\xe6?0\x8b\xf7\x89\xd2\x1d\x01\xc0\xec]\xe6\x9b\r\xee\xee?\x07\x9d\xca\xe1\x13\xb2\xf4\xbf\xf3\xb9\x98\x96\xbb@\xf0\xbf\xad\xd8IR{\xbc\xf5?3\xf6\xeb\x0f\x07v\xfa?\xe7`,\x15G\x07\x01@R\xc8\xf9\xcb\xc9\xe0\x05\xc0\xaa\xf1;\xcb;\xf0\xd6\xbf\x90b\'\x87M\xde\xf8?\xf9\x0b6\x8f\xb9\xfc\xd1\xbf%\xc79\x85\xc7d\xe1\xbf\x00%\n\xe0%\xc2\xe1\xbf\xbcB)x\xc4\xe9\xf9\xbfe\xbcy=q\x1e\xf8\xbf\xbe/\xad\xf1\xa1}\xfa\xbf\xc1\xd8>\x84\xb6 \xf1\xbf\xdd\xa5*$\x93\x99\xd3\xbf|e\x07\xc9\xa6\xb3\xd7\xbf\xf8\x07\xcdS\'\xf3\xe3\xbf\xd8\xd3e\xc0\xf9\xa0\x03\xc0@\xd8(\xec\xfb\xaf\x07\xc0\x9d\xf6\xff=?\x7f\xea?\xe6\xb5\x98\r\x06{\x02\xc0\x98V]>V\x89\xea\xbf\xba\x95\xd3>uo\xe1\xbf:\xb3\x7f\x91\x19\x1f\xeb\xbf\xcd\xe5\xd1$k\x84\xe4\xbf\xb5\xfb\xf1\xaf~\x07\xb3?Ir\xfe\x0f\xd8\xf0\xe8\xbf\xac\xed\x13|\xf3\x15\xf3\xbf\x98\xf2\x14d\xcd\x8e\xd0?\xc2\xc1\xf6\x80n)\n@\xd8\xe7\xf0^I\t\x05@\xdbor\xe5\x99T\x98\xbf,\x81\xcbIu\x81\xfb\xbf\x89\xfd\xda\xd76b\xdd\xbf\x80\xef\xf5wt\x99\xda?\xd6\x99\xd2\xdd!w\xcb?\x16gqt6S\x02\xc00\xc9\xbb\x84\xb1i\xf4\xbfl\xe2wg\xe6\xab\xfb\xbf\x8b\xb5\xf2\xb9\\4\x03\xc0\xa0\x16B\xdceT\xef\xbfW\xc7"p\x93W\xfb\xbf\xe9\xa6\xc1\t\xf94\xb4?q\xf1T\x19hM\xf3\xbfU\xf1\xc2\x14\x82\x91\xfe?\xcd\xd9:LE\xd6\xe1?M\xf5\xe9t\xa5\xd9\xc7?\x93\x00\x8e\xb4N\xba\t\xc0\xa3]\x9db&}\x04\xc0x\xcf~4\xf3?\x99\x0c\xac\x87\xad\xca\xfc\xbf~GW\x18k.\xdb? \xb5\xa3\xae\x15\xff\xee\xbf\xf0\xcc\xaff\xcc\x08\x00\xc0J\xb6\x05o\x80\xb9\xd3\xbf4`\x12|\xbb\r\xd7\xbf\x8c\n\xc9\xc0\x7f\r\x06@\x95\xa0\x99g5\xb3\x08@\x83\xe7p\xb4w\xc7\xf3?\xaa1|\xd1\xd8\x03\n@\xca\x1e(\xab\x05\x96\x18@\xba\xf2B\xf2[\x84\x1e@\xdb>\x1c\xce\x0e4!@\xed\xd5\x99\xe4\x827!@\xdb\xbe\x95YWD @\xfb\xdd0d\x89\xd4\x1b@\xa0\xd4S?\x8e\xed\x04@\xef/\x02x\'`\x0b\xc0\xe1!\xc0\xff\x06\xeb\xe6\xbfSPI\xe7h\xba\xf4\xbf\xd8\xa1\xa9[\x0f\xa5\xf3?\xbe\x14[bk\x95\xf8\xbf\xd4\xaa\xd0\xa5=\r\xd6\xbf\xe8\x9ar!!F\xe2\xbf\xd9su\x81\xf3\x9a\xf9?\xcc\xab\x02\x0c\xdf\x11\x07@\x18%*\xc5\xcc\xc0\xee?\xd0\xae\x84\x8d\xf7\xd3\xbf?c)\xb8*\xe8\x0f\x00\xc0?p\xc0\xdb3-\xdc?\xb8\xf8<\x0e\xfa\x1a\xa8\xbf\xc3\x92-\x97\xf9\x0f\xea\xbf\xbb\xa3\xca\x17\xdb&\xed?\x1b\xe4\x03/p\xbc\xdd\xbflNx\x81\xb6\xfb\xff?\xee<\xaf\xc3\x1b\xdc\xf7?\x12\xef\xc0\x89\x107\x18@h\x95\x88\x8cj\xbd\x19@=\xa0\xde\x9e\x15X!@9\xd3%\xcfb\n\x1b@\xcf\x05\xb8\xb5R\xb3\x1b@\xaa)@F\xbe\xc2\x05@\x8b\xc0*\xffm\x13\x04@@\xf0\xb7\x11;g\x0f@h\xc8:\xf1&J\x01@\x85\x9a\x82p\x14\xa7\xee\xbf\xe1\xe5\x84\xc8$E\xec\xbf\xe2\xb9\xc6|Q}\xf5\xbfF\xfeA\xd9\xfe\t\xcf?\xd3\xc0H.\xfa\xb6\xfe\xbf{\xeai\xb4\x10S\xf5?\x14\xb0R&z\r\xf5?\x8b\x84\x0e(\xb6{\r@\xcc@\xceU5\x80\r@Z<\xba\xf7\x808\x0c@w\x82\xa3\xda\xb4\xd0\xb1?\xa2\xabz\xe1\xff\xe5\xf1\xbf\xd5m&\x87\xed\x83\xe4?\xce\xb8\xd3yo\xbb\xe4\xbf\xb6\xb2\xc9sv\x17\xdd\xbf\x85\xe6M\xc4.\xf5\xd4?\xeb\xae\x11=<\xe1\xf2?\x96X\xbf\xfe[\xb0\x00@%J/D#\x9d\xfb?\xc790\xb5-\xa7\x13@r\x0c \xdd\x00\xfa\x1d@9?\xe1\xa0\x9b\xd4\x0e@p\'\xdfA\xdc\x95\x04@!\xedH/\x9a\xf3\xca\xbf\xef\xfau\x10\x17Q\xe2\xbf1\xddy=\xdbq\x05@m96\xa83\xfc\xfc?\xebx%l.\x81\x96\xbf(\x87\xa2\xea^,\xfe\xbf\xec\xbdz\xfb\x05\x01\x06@\xdf/\x86\x16\xbc\x02\xd7\xbf\xdb\x93\x02\x02\xd4\xf7\xe9?\x05+g\xbb\xff\x95\xdb?9\x16q\x90\xf1\x1e\x9a\xbfm\x93-@\xb4\xd8\xae\xbf\x82\x8e\xa3\x15x\r\xfe?~\x8ce}s0\xfe?\x9c\xaa9\x8e\xadS\xdd?\x0e\x90\xd3\xda\xcd\xcf\x9e\xbfJ\xb3\xb6?v\x18\xf5?Nz\xd8S8\xaa\xef\xbf\xe9\xdeXH\xfd\x9e\xe2\xbf\xd6\x85N\xa97\xb1\xe0\xbf\r*@\xcfW\x83\xf7?\xcf\xc1\xdc\xac\x81a\xda\xbf\xb4\x87\xc2\xb4\xbb\xf7\xe7?G\x007M\xc0W\x0b@\xadZ\xae\xc9\xb0\xa6\x15@nN\x1d.\xae\xb2\xf5?\xa3\x18\x10\xcc\xba\xa3\xf0\xbf\x05\xbf\x83\xbc\xf7\x84\xe2?\xcd\r\x9b\xe7G\xfc\xf1\xbfT\xf6\r\xb8+\x0e\xe9?f!\xb6\xef\xabb\x03@\r\x89/tx\xb7\xea?\xfc!\xc8\x9bf\xd5\xb3?e\xf2\x05m\\\xa6\x08\xc0\xc5\x8e\x8d\xe75z\xd8?W<\x8ffiB\x01\xc0\x01\x82\xd0\xaa\x8eY\xd1\xbf{\xc6$Y\xb24\xa3\xbf\xcc\xa4\xdd\xa8\xce\xa4\xeb\xbfkC\x1eP\x82\x19\xf0\xbf\xb8\xba\x1bb\x13\x84\xc3?Gv\x7fO\x11~\xfd?\xe5h\xd89\xc8\x7f\xe0?G\xde\xb7\xbb\x01\x9c\xbb\xbfS\x10\x16\xe8\xc1\xa4\xe4\xbf?\x11\\\x93\xec\'\x07\xc0\xc50\x9a3\x1a\xbe\xc7\xbf\xd96\x9a\xf2+\xa6\xc9?\x05\x92\xb4GZ&\x00\xc0\x8f\xec}\x9f\xef \x04@\xf6)l`_i\xf3\xbf\xe5\xfc\x94\x99\xb0&\xb9\xbf\x8c*\xa5sg\xc2\xb4?\xe9\xb3\x0b\xcc\xe2\x04\xe9\xbf\xbe\xbcX\xf6\xb7|\xe7\xbfH\x1b\xd7\xf1\xd9\xe4\xf1?\xe2\x06\x07=2\x86\xe1?\x07\x89\xa8h\xe6I\xf2?W_oX\x1f/\xc9?b\x89KBM\x9c\xed?9\xa4\xc6\xd9BZ\xfd\xbf\xba\xb2\x16\xd5\xb7b\xe5\xbfF\xa8|\xb57\x0e\xe1\xbf\x02\x03\xbf\xe7:J\xf0?\x9e\xebU\x0e\xd3m\xed?\x91\xf7|\xed{\x83h\xbfP\xba9\x86\x87\xc9\xe6\xbf\xf6S\x00\xf1f3\x00@\x12\x80U\xdf\x08\x83\xf1?`\x1e6\xde\xcf\x18\xf9?t\xdb&\xb0{O\xed?P\x9c\x16J\xf5v\xfc\xbf\xdc\xd9+\x85g>\x03\xc0\xc8\tO\x82\x032\xf5\xbfJ[L\xc9]#\xe4?\xca\x9cd\x90\xaa\xcc\xda?\xfd\x16I\xdd\x18\xb5\x01@\xc7\xfe-\xc4\x14\xda\xf5\xbfx\x9e\x1a\xad\x18\xa4\xe8\xbf\xae\xc2\x93p<\t\xe2\xbfh\x95B\xed\x1f\x19\xd3?71i\x1b\x82>\xf4\xbf\x8c\xcc\xe1\x00\x8d\x1f\xf3?\x82\x8c\xb7\x16\xa6\xa5\xff?B\xccc9\xc2\xf1\xe0?\xe1"&\xea\xb3\xd9\x03@HPCN\xb4\xde\xdb\xbfU\xde\xa2E\x10<\xed\xbf\xe7*W\xa5\xd2\xd6\xe6\xbfW\x0e\x1f\xcf\xfc\xa7\xe1\xbfQ,\x8d\x16\x03\xe7\xdb?\xc3a\xab{p\x81\x98\xbf\xba\xcb"7\xfav\xf2\xbf\x85P\x1c\xee\xe9\x1c\xf1\xbf\x85G\xc7\xads\x9d\xd6?\xf2\xe3Ki\xd7K\xe2\xbf\x90\xa2\xc6\xb5\xff`\xf8\xbfpT\xcbk\x89\xdd\x08\xc0\x07\x9aM\x80\xd4\x83\x01\xc0\xa1\xf4\xcd\xc2\x12t\x07\xc0}\xcc\x89wl\x9c\x00\xc0\xf1\xe7\x8cW\x8d\xec\xed?\x0b\xf0i\x91nM\xf7\xbf\xe7gmd\xbd[\xab\xbf\xab\x1b\x8b=\xc5?\xf1\xbf%\xf7\x8d\x95n%\xd5?\xc9\xe4\x84P\xd3`\xe0?;\x88\x06\x1d\xfe\x1d\xdf\xbfg)\xee\x7f\xcb"\xf8\xbf\xc9\x03\xbb/\xd0\x8f\xd1\xbf \x1a\x0cD\xd7O\xd0?\x19\xc97\x03=\x88\xbc\xbf\xcf\xc4a\x06\t\x15\xf4?\xafo\x14\xbc\xfea\xf6?\xd4\xb3k\xfb\x96\xf5\xd7?\xcfcl\xbe\x93\xbf\xfb\xbf\xa8\x90\xe3\x82\xbf(\xf6\xbf\x02\xe2+g6_\xe4?\x9fx*\x11t\x86\xf6\xbf\xded\xc5\x17%\x02\xe7\xbf\xd8l\xaf\xf1Ey\xd0\xbf\x86//i\xa9\xc5\xfe?\xf7>=\x93\xd2\xf2\xca?b\x9c\xda\n1\xcc\xfd\xbf\xd8\xd0\xfc\n\xf2\x00\xea\xbf\xe2\xa1\xa3A9\xf2\x02\xc0\xa2\xa1\xda\xad6\x9d\x03\xc0\x08\xbe\x9f\xaeRb\xfc?A\xe7\\\xa7\t\xb5\xc8?\xa9\xeb\x94fW\x98\xf3?\x01\x85CW\xbb&\xf2\xbf\x1b\x1d\x86\xd7Y4\xec?\xeb;\xa4\x89\x15\x8a\xec\xbf\x8a[\xf9\xf7\xe3\x91\x05\xc0\x9a.QM\x82b\x01\xc06\x0c\x0c\xff\xeeZ\xec\xbf\xa9\x0eP\x93\xed\xc2\xe3\xbf+\x89\xbaP(\x04\xfe?\xf5\xaa\xfe\xf5|\x9d\xf9?\x82\xa8S\xb8\x13u\xe8\xbf\xd96|U\xb5\xa5\xd2?3\x02\xeb\x87\x83\xa1\xcf\xbf\xd0\xe4\x9a\xea\xe0\xb8\xf5\xbf\xceS\xdaC\x7f\xa8\xe5\xbf\x8f\x97\xe1\x90\x1a\xc2\xee\xbf\xae\xfaG\xbdpa\xd5\xbf]h\xb6l$\x9e\xf3\xbf\x1b\x8c\xd5\x14\xbcn\xf6\xbfE\x0cK\xa6Z\xb8\xfe\xbf\xd4\x9b\x14@]\x80\xc7?\xcc\xd3\x0e\xd6\xa3\xb5\x00\xc0\xc4\xbdy\xb4>6\x95\xbf3\xf9\x85\xa1\xf2\x1c\xfd\xbfd\x86\x1f\xbbz\xf7\x00\xc0\xc6\xc8\xb9\x8f\x9c\x8d\x0b\xc0\x80\x87}l\xee\xeb\xff\xbfo\xf1\\tM\xab\xd4\xbf\xeby\xaep|\x80\xf9?7e\xb7\xc7i+\xf9?\x85\x8cFy\xc3\x04\xf6?\x90\xe4\x1e\xe0d\xf8\xf2\xbf\xe2Y;\x15\x82\xf9\x06\xc0l\xa55\x04x&\xe0\xbf|\xd6\x84u\xc9\x98\xe1\xbf\xaf\xd7G\x06\xd8=\xe6\xbf\xac\x83\x01[L\xc8\x00\xc0\x89\xdd\xbf\xd1\x90\xd0\x07\xc0\xc4D\xb0\xf92T\xe1\xbf\xae\xe9\xee\x85\xd0\xfc\xfa?v2\'\xc0\xd3B\xf3?6\x1a`O8\x01\x02\xc0\xa5\x1d8\xa3\x14/\xe2?\x02\x94\xe3\xb1\x18\xea\xcd?U\xdd\xa9\x16s\x02\xf8\xbf\xc6#\x01o^F\xe1\xbf\xa4\xdb\xbbmD\x07\xcb\xbf5i\xadl\xce\xb8\xf0\xbfgwd\xee\x8a\x15\xce\xbf\xc8P\xa2\xf9)n\xf8\xbf.\x06{9\xf45\xf2\xbf\xa7\xa8\xf4\xf7\xe6\x82\x06\xc0\xe18g\x9ft\x86\x00\xc0\xfeN%\'\x12\xf1\x02\xc0:T5\xff\xd7_\xd0?\x1c\xd1J$\x8b3\xd2?\xe7d\xb0\xba\xfc\x1f\xf3\xbf\xdf\xbeh\xce\xf3\x85\xfd\xbfly\xecsdp\xff\xbf\x04\xd1\xba7~s\x05\xc0 \x16\xa7\xb9\xcf\xb5\x00\xc0\x9f\xf7\xed\xd1\n\x82\xf4\xbf\x92IEZ\xb4\xfb\x02\xc0jG\x17\xe0"/\xfa\xbf\x8f\x8f\x91\xd7Nw\xfe\xbf\x19\xabh\xfc\xe7\x18\xe5\xbf\x08f\xbdZ\x858\xe5\xbf\xb0)\xb9\x1e\xc3$\x04\xc0\x85w?\x8a\xa3\xa7\xf4?]2\xfa?Ti\xfc?\xa5\x069$\xb6\xf5\xdf\xbf*\xc8\x8a\xb2\xcc7\xe3\xbf-\x9e\x7f\x8c\xc3r\xd8\xbf\xdc\xa1A\x19_\xcf\xd5?\xc1MV\x9f\x00\xef\xfc\xbf\x07\xd88\x80\xdb\x9a\xf2\xbfFf\x85\xce\xd6\xf6\xe9?Vt\xdf\xb9T\xbd\xd4?\x1c\xba\xa9X\xc8\xdd\xd4?\xe5\xfc\xcdqLD\xfe\xbf<(|\xa9u\x01\xc0\xda\x91\xc9H\x8d\x12\xea\xbf\x99\x8a\xbfg\xf8\xea\xc1?\xa4\xc7F\xf9\x9a|\xc6?I\xaa~\xaa^\x9d\x03@f\xfe*iD/\x00@\x84\xd6\x86\xce\x02\x89\xf6?7D\xbb\x1d\xe6E\xf9\xbf\x03i\xd0\xc4\xcfo\xb6\xbf\x80\x0bfz\xd0\xa8\xe1?"7\x8fZ\xd6\xe1\xb5\xbf\xa0O\xb2z/9\xd5\xbf\xab\x07\xc4[\xd5\xae\xbb\xbfBK\xef\xfe)-\xe2?rmKrM\xe1\xd6\xbf\x0fH\xf8\xd0\xd4\x96\x05\xc0V\x97\xab\xc4\x1ak\xf5\xbf\x143\xb2\xf1.\xe2\xf0\xbf\x04y\xf1\xf4\x97n\xd2?\x8a\t\xc2\xca\xfd;\xdc\xbf \x02\x8d9\xc0\x17\xda\xbf\xf1\n4\xd14Z\xd8\xbf\x07\xcf\xc4&I\t\xaf?\xe4\x0f\x8ay\xc8u\xbf?\xcb\x8d\t\xfdG:\xcb?t?Y\xef<\x8e\x03\xc0\x13\xa5\x18c=#\xf7\xbfD\x8d\t\xe8\x97\x12\x0e\xc0\xc2@\x00\xa9\xca\x1d\x01\xc0\xa8\xf5\x8f\'\xf5\xfb\xeb\xbft\xee\xc1X\xe6\xa3\xc5\xbf3?\x1e\xfb\xacc\xd0?\xb9u\x07\xe0\x00$\xe1?\xf9\x95\xc1\xa7\xe4 \xee?\xff\x05\x0c\x0eq\xb1\xbc\xbfO70\x07Fk\xe4\xbf\xb2\'\x05a\xad^\xf5??\x16\xe8g2C\xf0?|\xbceex}\xf2?T\x9c\xabI\x82D\xfc\xbfq}\xb1\xef\xdb\x8e\xfe\xbfe\r;i\xae\xac\xd6?\xb6\xf7\xfa~\x10\x1d\xdc\xbf\x18\x18\x83\x89\x02\xdf\xf1\xbf)\xc2\x81t\x97Y\xb1?\xd4\x88\x96\xc2\x88\xf6\xba?i\x15l\x9eJ\xe5\xeb\xbf\xef\xc0ry/\xbe\xb9\xbfW\x87\xf5\x7f\x07F\xef\xbf\xca\x7f7\xb2@\x7f\xf6?X\x16\x95\x9c\x8e\xa3\xf1?_\x1a\xecL\xe2a\xb4\xbf\xf1E/\x15_b\xe8?\x8d\xba\x1f\xb9\xe5\xa0\x01\xc0-5\x17\xc5\xf7\x15\x01\xc0;F\xaa/w\x00\xf0\xbf\x9e\xb8\xc2\x99\x05\xc3\xe9\xbf\x85\xee\xf5\xae*0\xe7?g\xcd\xd2\xe8&j\xc5?@\xc8=\xc2|O\xdf?&\xdewg5G\xe0?\xed\xf7\x93\xc3\xa5\xea\xf6?\'l\xdd\x92\xf3\xed\xda?{\xa5\xaa\xb8\x01/\x06@\\\xa4\xdfU\xc8\xbe\xf0\xbf]=\x10+\xc6\x83\xc1?,\x14\x19wv\xda\xf8?*k\xb0\x93\xe1\xf3\xd9\xbffA1\xee\xc1\xef\xeb?\x1d\xb7tg\x14\x14\xe2\xbf;\x16\x0c\x1b\x95\xcc\xfb\xbf\x1c\xd4\xe7\xa7\xc7\xe6\xe2?q\xe1qb\x91 \xca?\xf8[\xf2\x07Ip\x9a?\xa63\x9e\x13\xad4\xe5\xbf\xe1\x89\xf2e\x0b0\xd3?\xbaR\x8d4\x03=\xfd\xbf\x9d\xc6\x1e\xaf\xf7\x94\xf2\xbf\xc7X1\x87R\x9c\xeb\xbfK-/\x00Tf\xf1?\xb0\x84\x8b(u\x10\xc9\xbf0\xcdV0\xac\x14\xf8\xbf\x1a\x0c|\x8d\xe1\xae\xf0\xbfwJ\\\xcbfd\xfd\xbf`\xed\xd3iF\x08\x06\xc0jd\xd1=\x02\n\xf5\xbf\xc0\xffR\xdc\n\xcf\xe8\xbf\xd4H\x80BHG\xa1\xbf\xed\xc6\xf4\xbdwx\xf3\xbf\x8a\xe3\xbdcT)\xab?\xad\xa3\xc7/\x08\x96\xf2\xbfK]\xe1\xfc\x8c\xfa\xe6\xbf\xbe\xd1\xd4<\xf1d\xdc?\x9e\xd1\xe8\x8f\x91\xe9\xc1?\xd9\x0e\x18\xbeZv\xfc?\x83\x8e\xa4\xae\xfc\x08\x08@\xd5\x99\x92\xc4\xe1\xf5\x07@d\x1d\xe4\x81\xf7\x97\x02@\x05\xab0\xbe#\x1f\x00@\x1d\xa5Q\xe6\xc6\xcd\xde?\xbe\x02k\xfa\xcc\xda\x81?\xedGP\x17\xe6Q\xe5\xbf^\xdeL\x18\xac\xff\xe5?\xe2YC\xc3\xf4\xe0\xf7\xbf\xda>P\xe3q\'\xe7\xbf\x12b\xc0~\x8f\xec\xeb\xbf\x8a\x9fK\xc2BS\xec\xbf\xdc\xc0:h\xad\x03\xcb?\xfdT3\x19\xc1<\xeb?\x18f"\xd1j\xcc\xed\xbf\x04\xdb\xb4\xee\x84\x86\xf9?\x96\x06q\x96|j\x81?C[~D]5\xed\xbf\xef\xae,)\x16\x14\xd0\xbf\x88\x9a\xa4\x11\x8f\xa1\xfa\xbf\x1a\t\'\xa8\xb9r\xf6\xbfC\x16\x1aM\x81l\xc6?\xf5\xd5\xc1\x19}\xf4\xf9\xbf)O\x19\x83\x18\x0c\xd6\xbf\x8a\xcb\x8a\t\x92\x1d\xd6?\xf6d\x04\xa4\xb0B\xd9\xbfQ-\x95\x01\x00Y\xa0?\x99\xb9\x1d\x81v\xca\xac\xbf\x83\x93~\x11\xae;\xf5?z,\x86X\x87m\xf0?\xa2)Miz\xbd\xf2?\x8cU\xffS\xbc&\x08@\xfe\x89eu\xf6\x07\x02@@\x1a\x1d\x05\x82\x11\xde\xbf\x84\x96l\xb2E\x0c\xcd\xbfttlmP\xde\xed?\xf6\xd6\x833\xc8\xe3\xe4?$\xeb\xa6o\xf9\x87\xe4\xbf\x83\xd0\xe8\x11+\x1d\x00\xc0\x06t\xd9\xcd\xcc{\xc3\xbf\xc4\x14\xc8\xfe\xb6\xeb\xf9?\xd8\x7fLD,d\xca?\x01V\x107\xe7/\xdb?\xcb\xf7uP\xb1\xe1\xce\xbfT(\xcf\x15\xae\x1b\xe4\xbfg=\xbb\xb9\x18n\xe5?\x8fj\xf0\xc9\xaa\x9a\xe3\xbf\xc2PE= \xc0\x02\xc0\x97\xa6\xb2\xe1\x8a\xc8\xf6\xbf\x83\x1bvC}R\xdd\xbf0\x940\x04\x8c\x1b\xfb\xbf5\xe5$?\xc4\xf8\xea?\xf9\xdc\xba:\xb5\x10\xe2\xbf\xdbm\xa8S\xac\x0e\xfd\xbf\xfb\xe7v\x9bw\x89\xb7\xbf\x8bL\xd6]\x9f4\xff?\xef8\x16\r\x8fS\xe0?\xcdul\x16\x19\x1d\xfc?=\x1fz\xd4\x85\xb0\xd9\xbfN\x00\x1dLA]\xc5?\x17\x0frhR\xf7\xdc\xbf\x9c\x9a\x8e\xe5\xf5\x01\xd5\xbfN\x0b]\x89\x06\n\x02@\xf4T\x91\xf3\x8dS\xe7?\x83\xd2<\x82<\xbb\xf0\xbfZJ4\x7f-\x7f\xb1\xbf\x93\x92h\xbeq\xd1\xe5\xbf\x86\xe3\xa9H0\x90\xe6?\x01o~\x9a\xe2\x04\xe4?\x9eB\x0fu\x03\x98\xd3\xbf\xd0\xdc+S\xab\xd3\xc2\xbf\xad\x84\xcd\\|\xcf\xd9?\xad\xb9\xef\xcb\xe1`\xef?\'%\x90\xc2\x02\x12\xed\xbf\xa2e\xf8;\x80\xcf\xe0?\xaeA\xfb\x06\r\x19\xe2\xbf\xb5\xc4\x11\x92\x87\x11\xf4\xbf\x89\xef\x8e3\xa1\xc6\xcb?\x9ed\xb5rG|\xd8?\x90\x14K]\xe6\x01\xf9?Nj \x18\xff\xf6\xe8?\xc0\x8d>\xfd[\xbb\xed\xbf,\xeb\xb2\xbd\x99A\xd4?\xb7\xa6\x81\xff\x9f,\xf2?\xf1\x10\xeb\xd0\xf1\xdb\xd5?[\x9b\xad\x0f\x18J\xe1?e\xbd&\xa5\x13a\xfd\xbf\x90\xe1V\x85\x1eT\xd4?\xc6R\xd9\xae\x9c=\xaa\xbfg\xaaK\xd5k\x1c\xed?\x91TJ9\x84\x94\xa7\xbf\x88\xd8\x9a\xb3i\xaaz?\x9d)\x05\xbb\xae_\xe1\xbfO\x8c\xcal\xce;\xd8\xbf\x1a\xfcB\xbc\x94\xd6\xfb?HD\xb6\x16]\xd2\xd1?\xe7W\x03\\\xba\x8d\xeb?\xccQ\xee\xb6\xda\xcf\xef\xbf1\x1f\xb81\xd61\xf8\xbf\xba\xdd\xc1\xf1\x93u\xf1\xbf\xc6\x96+\xfc\xef\xf0\xd6\xbfv\x07\x03\x9b"]\xf2\xbf\xe2}\x9a9GQ\xe2?\xce\xa1\'\xff\xa8>q?x\x81\xbdb\xe4\x91\xf3\xbf\xb20\xc8\xb1\x19\xd6\xb1\xbfb\x02\xe9\xbeA\xe3\xe1\xbf_\x95\xda\xfc\x84Y\xeb?4\xf5\xba\r}\x06\xf1?\x95\x01\x98s(\x1a\xf0\xbfP\x1e\xc6\xf0 ~\xf2\xbf\xd8S|\xcd\x94\x06\xd8?:Y\xc5\xc0\x00\x18\xe4\xbf\x14\x1f\xf1\xda7*\xd8?W\x86U\xd9S\xa5\xd0\xbf\xf8\xfb\x0c\xdc\xa4\xf9\xa3?eAt\xda\xd4*\xf1\xbf\x02Z\xfa\xef\xed&\xfc\xbf\xf6l|\xfc^*\xf0?\xed\xa5\xd0\\\xc8&\xad\xbf\xb3i\xc0\xa4\xca\xf9\xee\xbfR\xf9\x87p\xc2J\xe7\xbf\x14N\xebm,n\xbf\xbf\x1c[\xe5[p@\xe3?j\xbbZj\xaf\xad\x9a?\xd7\xa1\xdaU\x8cf\xe3\xbf\xcd,J%k\xa6\xe2\xbf\xc6L\x1f\xaf}\x82\xe6\xbfx\xcd\xe6\x8fk@\xe0?\xae\xf3\x14\xcd\x9b3\xf8\xbf\xba\xe1\xcen_m\xfd?\x8a\xc5\xe5\x86#0\xde?}kD\x1eq\xde\xf7\xbf2G2s\x17\xe6\xc8?h\xb6\xf8\x15\'\x07\xe5?DX\xe2\xcf\x85+\xf2?V\'cUQ\x10Q?\xf3\xe5\xb1S(\xdc\xe4?-V\xa8\x81\x19\xa9\xa7\xbf\xeb\xe0F\x83\xc3\xce\xe2?5\x13\x01.\x99\'\xdb\xbf\x9c\xef\x11D\xa4\xd0\xdf?\xc8J\xe9f\x8b\xac\xe5?)\xa6\xbc\x96\xd3f\xf5\xbf\xe6o\x14#>\xa2\xe0\xbf\x83k\x12\xd0?g\xaaV\xf9\xb3X\xe9\xbfrS$\xaee\xac\xba\xbf\xbf\xb6x\x9aqo\xf0\xbfx\x0f\xd93\xf1\xc3}?\xa7\x18Q\xa5\xe8\r\xc6\xbf\xdd8\xd3\x96\x98\x93\xe5?\xe8\xb7CR\x13\x9e\x00\xc0\x8a\xcb\n\xaa\x18\x02\xa4?\x15\xb7\\/\x8c\xbb\xf5\xbfji,wo\xb8\xd6\xbfv\x1d\xdc\r\x128\x04\xc0\xef\n\xce\x81\xca\\\xde\xbfp\xbd\xc7&\x8a\x96\xf2\xbf\x1e\xb6\xdbco3\xdc\xbfb;\xc4\xb5\'\xeb\xb1?c\xb0\xab%>\xe4\xd9?\xd5D\x98H\x04J\xce\xbf(\nb\x97`\x15\xc2?\xc3N\xe4\x10F\x16\xf0\xbfH\x1d#Z\xf29\xe0?\xa6\xa4p\x9f\xcf\xb6\xd1\xbf\xbb\xf7Y\x85i\x9f\xd5\xbf\xef\xb8\xcch\x10\xce\xf7\xbfJ\xf4\xb1\xc7Y\xbd\xca\xbf{)f\x95k\xaa\xb8?4\xf6\xe2w\x92w\xe0\xbf\x1a\xf0\x8b\x17~\x06\xf8?\\g\xe4S&\t\x02\xc0%\xd5\x0f\x04\xe1~\xc1\xbf<\nxB\x0b\xc0\xfc\xbf0\x12x\xcbcW\x9b?f\x87zjU\xbf\xd8\xbf\xe28\xc7"B\xe8\xe2\xbf\xd1\xedt\xe0\x86\xf3\xe6\xbf4u\xd1\xf3\'5\xd6\xbf\x19S{\xc7\xa8.\xf8\xbf\x89Y`y\x14\xc8\xa2?\xcd\xd3\xbe\xa3\xb1\xb6\x01@>aO\xa6%W\xeb?\x0e\x98#\xd2\x10\xac\xf4\xbf\xfd+\x7f\xa9\x9dd\xe6?\xda@w\xd2\x08+\xe5?\xd0j\xcf\xbf\xa4\x98\x02@\xe1\x81\xf1\x99/\xc1\t@\xa9:H\x0b\x8a\x87\xe4\xbfw\xa8\xa2`\xf1\n\xe9\xbf\x8d\x95-A\x16R\xf1?\x18\xeb\xddX\xa7\x88\xe0\xbft\x10\xf9u j\xa9?\xa2\x81\xf4\x05x\xfb\xf6\xbfO\x06\x96\xc4\xa4\xe1\x88\xbfR\xbbJ3\x8d\xc9\xde?\xd4\xe2\xc0a[\xc3\xd1?\x86\xf25lQ\xf0\xd5\xbfA_\x97\xffn\xd9\x00\xc0\xc9\x1f`X?\xeb\xf2\xbf\xb0\xc2F\xf7v\x93\xf5?1\x17\xe0\xb9A\xba\xce\xbf\xaa\x0b\x11\xa9\x8d\xd3\x05@\xf8L\xb0\x19\xbe\xa6\xdb?P2\xbb\xb5CU\xec\xbf\x9e\xfd\xbbB-\x98\xee\xbf_\xf2(^\x05\xdd\xee\xbf\x7fz9\xdbo\xd3\x00\xc0\x9e\xff\x0f\xf1\x17F\x01\xc0n\x1b\x9f\xd4\xbe\xd0\xfd\xbfm\xbd\x17[F1\xe0?\x9c\xac\xdd\xd6+\x00\xfd\xbf\x94\xa6\xa2oX\xaa\xfa\xbf\x83\x9f{or\x8e\xf9\xbfV\xe6\xda\xc7c\xdf\xe9\xbf\xf3_\x8a\xe1\xd43\xe2?V\xe7\xf0\x9f\x11\x01\xb5?\x19QF\xdb\x1ay\xfd\xbf!\x80g%#C\xf1\xbf\x81\x04\xce\xc7\xcd\x00\xb3\xbf\xec\x0f\x1a\x13g}\xb1\xbf\x85\x00\x9cJ\xe6\xd5\xf7\xbf<3\xfd\nX&\xee?a\x9c\xb4\x0b)\xbc\xb8\xbfu\xe90\x03Ad\x90\xbfs(\n}\xb1\x19\xd5\xbf\x02\xc8\xcd\x8c\xf0\xf3\xfa?aX&|,\xd4\xbd\xbf\xee\x07I\n\xef\xeb\xea?\xa2\xec\x0c\xbc\x1f\xd9\xf9?\xe6\xa5\xbd\x1cW\x11\xc7\xbf\xdc\x1b\xdf\xae\xe6\xd1\xd6\xbf\xac\xc23\xb4~\x83\xde\xbf\xff!\xaf\x1e}\xd3\xe4\xbf\xdf/3\x872$\x93?\x89\xc1\xb2\xe3\xbcC\xfc\xbf\x81~\xc501)\xf1?\xae\xc6\x8f\x19\x92!\xf7?h\xf7+\x0b\xa6\x82\x03\xc0\x1b>\xcb\x8f\x14!\xf8\xbf\xb4\xbf\xf4y@\xd7\xf2?m\x89u\xbd\xd5\x96\xfa?\xfb\xbd\xa9e\x9b\xb6\xc0??\xfc\n\x02\x9e\xa8\xe1\xbf\x93\xe2%\x90o\xbd\xcd\xbf\xf5\x1f\x80-f\xf8\xe0?x\x94\xa2\x8a:\xd2\x91?\x94\x1a\xd9\xc7\xa0\xe8\xf2?\xdak\x9d\x07:O\xc8?\xab\xe5\x8au\xba\xf9\xe7\xbf\x08q\xf5\x91\x02\xa2\x02@\x95\xf0\xb1`\x07\xf2\xf5?Z7\xe1\x8c\xa6H\xdb?\xda\x08\xcb\x1f\xb3\xef\xe5?\x82\xb3\n\x9fxl\xc8?{\xdd\xdd\xaa\x85\xb4\xfe\xbf\x01\x9e\x914\xbf\xc0\xd7?\xd1\xd6\xc1\x1b\x15\x10\xeb?\xb6i8\xb4@\x9a\xd4\xbf\xe5\xf8\x84\xf3\xf9\r\xf7\xbf\xcf\xc5\xa1\xe6p~\xf7?/\xb4\x92~\xdf\x03\xe9?\x0c\xc4\xd2\x9420\xe5\xbft^\xe8\xb6\x94\xcb\xe5\xbf\xea\x8f=\xf9p\xe8\x01\xc06\xea\x9c\xab\xb4W\x01@\xb4`E2e\xf8\xf9?\xda^b@\x1aA\xff\xbf~\n\xc4\xc0\r\xdb\xd2?\xa6\x96iPE\x19\xf5?9b\x9b[\xa5\xae\xc6?\xd1(y\xb2\x98\x9d\xdf\xbf\xbdC\x94\x12Zn\xea?\x90\x15#^\x00v\x00@\xcec\x06#)\xd6\xf3\xbf,l1:#\\\xf2?B\xe9\xf9\xf6\xe9t\x06@"\xfd\xeav\xdf~\xea?-])\xd6H\xa4\x86\xbfG#9\x8b1\xb9\xb1?\x02\x1e\xd3\x1b\x18\xd6\xfe?\xff`\x84\x81\xae9\xf7?\xbf\x8c\x0b\xcf\xd4\xd2\xbc?\x05\x9b\x1fp8\xca\x97\xbf\xba(\xd5S<\x9f\xd8?\xe5f\x93M\xff\xb1\xea?\xba\xddy=\xdc\xbf\xf3?\xcc\xa6,w\xd2\x13\xf0\xbf\xda\xf0\x99\x0cj\xfd\xe2?{\xb7\x96\x01\xfb\xc0\xcb\xbf\xa8|\x98\xd1\xddk\xb5\xbf\xb5\xe5\xd7\xe6O\xf1\xd8\xbf\x0f\x0f\x8atm\xc5\xc7\xbf\xf1\xed\xde\\\x08\xa9\xed\xbfH\xdf\xe1\xba\x9c[\xe8?\n\x0f\x04i\xb0\xb4\xea\xbf\x13\xbf%\xd0\x11x\xf4?\xb5P4\x18i\x14\xef?\x16\xd9\x0c\xbeG4\xfc?\xed\xed\n\x17\xa7\xf4\xf8?{\x0c>\x8e\x87q\x04@\xba\xb9\x0f\xb4\xc3\xcb\xf1?\xa4R\xa6L\x1a\xdc\xe4\xbf\x85\x83u\xe76\x1c\x01@\x83wZ\xfdv\x16\xd7\xbf\x9e\x8f\r\xd1\xe2\xd3\xb8\xbfO\xe8Zl4\x7f\xf3?!\xb6\xd8I\x18\xa1\xef\xbf98\x80J\xc8\x90\xed\xbf\xcf3\xb2\x18\x81$\xe1\xbf`H\x84\x95\xba\xd4\xc7?^\xcd\xa0)\xdd\xb2\xd0?#7a\x9d\xc4\xaa\xed?\xb6\xa1*;/}\xf5?\xaa\x141\xe9L\xa9\xe0\xbf\xe1-#)58\xc6\xbfX\xfb\xf5\xeb\x92m\xf1?j\xcf\xf8\xa2\x1dQ\xc4\xbfm\x84\x95\xd3N\'\xf4?\x1c\xb6~\xf1\x15K\xdc\xbf\xe8\xf8\xf6\xec@\xd6\xe6\xbf\xe2\xc5\xf7\xb1\xf9\x1c\xed\xbfa\xe8\x959\xb3\x1b\x06@,\n\xcc\xdd\xf8\x84\xb9\xbfT\xbcbh\xe3t\xe1\xbf\x836]\xe1\xd3\xa8\xc9?`\x1c\xf1\xd1\xb3\xc1\xdf?\xdeR\xcd\xb1\x18\xa7\xe7?)\'\xc8w\x19\xe9\xf5?\x81\xc6\xa9\xb2\x95.\xd7?m\xe6%m1\xb3\xe6?\xaf\x8bJ\xe9\xc3\xaa\x03@\xbepH\x1f\x85\x02\xf0?\x95J\xa5\x9cX\n\xf9?B}jCj\xdf\xee?\x99\xce[\xef^\x19\xc4?\\\x9fm\x8cGf\x03@<[\xe4\x0e-\xf1\xf7\xbf\xd6\x1bYSm\xd9\xd1?R\xba\xe7z\n\xc0\xc8?|\xe9\x06\x86`\xd5\xf9?]\x86_&\rC\xf3?\xc5\x1fo\xe5\xaa\\\xc1\xbf\x07\xfb:\x02\xdb`\xee?\x85\xf3\xb1&\x1e\x8a\xd5?\xcfi\xe8\xf0\xea\xf1\xda\xbf\x107\xdev\xde\x87\xfd\xbf\x05\xf2Y\xe0\xa6n\xf4\xbft\xb4\x0b\x0e\xfb\x08\xe2?\xa1=\xfd\xba\x86\xc4\xf1?\x10\xd4\x87\x0e\x9b\xf7\xfe?\x91\xc3\xdaf\xb0\x0e\xfa?\xe6\x8cI`o\xbb\xcf?\x06\xd6\x08CT\x94\xc6\xbf\xc1\x1fV\x9d\x0f\x04\xee\xbf\xee\xbc9=\x97B\xf2\xbf\xcc&*\x99{*\xcc?\xf5l\xe6P^\xc7\xeb?m\xfe\xe5\x18\xb2\xb3\x00@6J\xe8^3\xb0\xfb?\xd6\xdc\xa1:q\xdb\x05@\xc1\xb0\xbb?\xee\xef\xc6\xbf;\x9e\xcd\x07\xa2\xc0\xdf?\xa4\xc9\xdc\xa7\x11^\xd8?s\'j)\x88Y\xd6\xbf\xa1\xa1\xc2\x05|_\xf2?\x84\x98X\x1f0\x8b\xcb?\x1b+\xefF\t\x9e\xf6?\xadB\xcf\xa9\x05\xcb\xe1\xbf\xe7\x19!\x07\x1e\xeb\xdb?\xa9\xa1\x99Ey\xd2\xdf\xbf\x90\x83\xe82\xbc2\xf0\xbff\xc6Nf\xc3\\\xf3\xbf-\xadp\xa2\xb0V\xf1\xbf\x15[R\x11\x99\x0e\xf2\xbf\x03\xae7p\xeb\xef\xf3\xbf1\x93\\\x85v\x01\xcc\xbf\x8d\xf95L%\xd2\xfc?3\x8c\xb6+\x90$\x15@\xed \x87R\x1d\x90\x08@\xf8\xc9}\x1f/\xf0\xfb?;OEL\xa3a\xf9?\x14^v$\xff"\xfc\xbf\n\x84\x03^\x80\x13\xd2?\x90\xc9\xe7q\x88\xb5\xd9\xbf\x04\xe7\x99\x7f\xecn\xeb?\x9b+_\x060\x93\xe1\xbf\xda\xfa,\xdc+\xa5\xe5?MH\xb2C\xe4\xfb\xd4\xbf\xf1\xb7P\xb6o\xc0\xea\xbf\xfdD\x01\x04\xdd\xbd\xef\xbfW)D\x9a\x97\xf4\xde\xbf!\xaf\xb4dY%\xef?\xee\x88\x1ca\x1b`\xf9\xbfW\xa6F\x0c\x0fl\xf4?\xe6^\xfa\x15U)\xe0\xbf\xfd\xb4U\x9e\xf9\xbf\xfb\xbf3{\x7f\xb9`\x14\xde?\x90\x97cm\xcc\x9c\xf7\xbf\x85y\xa5;\xaa\xc9\xe8\xbf\x042G\xc3\xc6z\x02@<3\x945\xb4\x02\xf6?\xa0\x15{!\xa6u\xd7?\x8f\xb9nY\xc7\x1f\xf1\xbf\xce9\x1f\xc8\xbd\x9c\xf9?\x05\x7f\xd6L\xb8\xbd\n@\x86\xa2\x05\xe8\x8c\x05\x13@m\xec\x8aWR\xa4\x18@\x19\x04a\x08\x84I\x00@\x96\xe5.\xbc\xb0\xbb\t@C2f\xac\x04\xdc\x03@\xf0\xf4\xf9\x1f\x95\x7f\x8e\xbf7Pij\x04\x99\xd3? \x01q\x8e\x95W\xe2?\x92}W\x93\x9ew\xf5\xbf\xe6\xac.\xfc2\xbc\xf6\xbfY\xca\x047y,\xe4\xbf\xe4\xd5\xfb\xe1}\xbe\xf2\xbf#\xf6\x14@\xb4\xa2\xf7\xbf\xd9^\x8b \xe00\xe9\xbf\xb4\xbc\xe5\x1d\xfb\x89\xe5?\xbe\x9d\xf1?\r \x03\xc0\x90cq\xf2{n\xcd?\xbf\xa7\xc5\x0c\x11I\xe2?\x998\x03@\xdbL\xce?\xf1\xb8\x1a\x06 \xab\xd6?\xf9\x81`uCA\x03\xc0s\x16]U\xe6\xc6\xe6\xbf\xcd\xd5B\x92{\xb9\xde?\xba\xdfq\xba\xc1\x11\xe6?\x01!\xa7\x89_\xda\xe8\xbf\xe4H\xffE\'\xbe\xe0\xbf[\xf5\x08tS\xe5\xdd?+\x1c\xdd\x86s\n\xfe?\x14\x9c\\w\xbe\x8f\xed?U\x89\xde\xf8\xe0\xb4\xf9?\x1b\xa1\x7f\x0c-\xd7\xfd?\x97d\xa2\xdc\xber\x13@\xd9<[\xb1Df\xf3?&\x0b\x18\xda\x88k\x02@\xb1\x1dd\xfd3{\xc7?\xb4\xdd\xee"\xe4\xb3\xdb\xbf\xbcz\x8cz\x1c=\xe1\xbf\x19\xfb\xf8\x8a\x1f\xd7\xe5?i\xe5\xed\xa7\xb5j\xf6\xbfF\x0f\xa9n)\xd6\xe3\xbfW\xff@W\x19\xc2\xd6?\x15\xee&1\x0e\xbc\xb0\xbf,"\xfc\xb7A\x85\xfa?\x83\xa4\xb8\xd8\xfcv\xd5\xbfa\xacf\xb6\x07i\xde?\x9b\'>6\xe3*\xf6?\xa0#\x1d\xfa\xd1\xda\x93?!!\xa3)\xd7\x91\xe1\xbf\xcb\xe3\xba\x8c\xc9\xa7\xc2\xbfsU\x91\x1a\xa3\x1d\x04\xc0\xe2ni&\xde\xf8\n\xc0\n\x12\xee/.5\xf4\xbf\xbd\x9c\x0eS\x85\x1f\x07\xc0\r\xe4\x1b\n\xe5\xf6\xf6\xbf\x98\t\xc3\xff\xf1\xa2\xbb\xbfZ:$\x92\x88\xcc\xe2\xbf\xc2\xb4\xe2"\x91K\xe3?:\xc0\x07\x1bKe\xe6?\xc84\xb3\x95\xd9\r\xf3?S\x1f\x0e\xc7\rt\xd1?\xabE\xc8\xcd\xfe\xe9\x01@\xca\x8cQU/\xc9\x95\xbf\xac\xb019s\xce\xc1\xbf\xb9\x7f\xfae\xd7\xff\xe6?\xc5|\xe7\xfa\xff\xb4\xf1?U\x89\x8c\x89\xaa\xe1\xfc\xbf\r\xfd\xe3~\xcc\xef\x01\xc0\x05\x18\x1f@\xecW\xdb\xbf\xca}\x94/f\xbc\xce?\xc0\x83\xfa\xa6r \xdc\xbf&\xcb.\xf9\xb3d\xd3\xbf\x18\x88\xad\xbf\xb4\xa6\xf0??\xa8\x1eMV\x1b\xf1?\xeb\xb36\r\xfeS\xf0\xbf\xda\x069\x0fuH\xd1?\\D\xbf\x01\xa5\x8d\xf6\xbf\xf6\xd6\x91\xa2\xbdU\xac?\xc2\x86.-lU\x0c\xc0\xe5-\xbf\xe1E}\xf8\xbf\xdd\x85\xa0\x07N\x93\x15\xc0\xabB*\xe9\x91\xc0\x07\xc0\x8c\x9b@\xd2wa\xf1\xbf;\x18\x8a\xaf\xcd\x85\xf5?.\xe6Bcq\xae\xe7\xbfb4\x1cNH\xd0\x02\xc0Ql\x9b\x1b%.\x07@~\xbe\x0e\xa4u3\xb0?k\xc9o\x02\x1e\x84\xd2\xbf\xe7\x97\xbe\x13\xa2S\xf8?V\xf8\xe37\xf2\x8e\x06@\xaf\xa9\xe4Jr\xb3\xc2?\xe2\x9fmZ\xda)\x01@\x13\xd8\x9c}\x9dj\x05@\x81l\xe6\xbaH7\xf3\xbf \xc5\r\xde\x8bp\xf4\xbfD/\to\xc6\xca\x03\xc0b,d\xb8\x9d\xf1\xa7?j\xa7?\xc2.]\xee?\x95Nde\xbel\xd6?\xae(\\\xf5\x89\x05\xf4\xbf\xf4\x94\x9e\xac,\xad\xeb\xbfwg\t\xb0C\xdb\xe8?6e~\x16\xeb\x83\xd0?\xe0\x17\xf17HR\xa2?T\x1c\x8d\x9b&A\xf0\xbf\x9b\x12\x17\xf4ij\x08\xc0I\x8f\xebdB\xb3\xf7\xbfH3\xafEF\x9b\xfc\xbfh&\xce\x9e\x87\x1b\x07\xc0\xb7q4\x8e&y\x0c\xc0-\xdd>\x192\xe4\xe1?\x12\x08\xff\xb0{v\xf2\xbfy`\xda\xb3\xa5\xae\xf9\xbf\xa6y\xd5\x8b\xfc\r\xe2\xbf\xe6\x8e\xcf{ja\xc9?A\xdf\xdc\r\xe7\x92\xc2?\xa5N\xccj\x19\xac\xe3?L\xc6#\x9f\xf9\x9c\xe1?\x89\x9b\xc8-T\x9a\xc2?\xddu\xc1\xcc]\xdb\xd4\xbf9$\xa5\x96\xe6\x8e\xd0\xbf\x89\x80\x96\x10P\xf8\x00\xc0f\xa9i\x17\xa5\x94\xf2\xbf\xed\xbc\x99\xf5k\x9a\x05\xc0\x01\r\x96UV^\xf7\xbf\x9b\x8co\xfc\xef@\xfd\xbf\xf9\x95\xd7C\xa9\xd7\xd2?( \xcf\xc3?\x98\xd8?Li?\xc6\x1af\xe5?\xb4\x1d5bk6\xdb?\xb5\xc0\x14\xbf\xeb%\xe8?\xa3\xb0\xa7\x1c\x93\xe1\xb2?\xc6\x01N\xc1k\x7f\xda\xbf]\x85{N\x06\xaa\xf1\xbf\n\xe5;.X|\xfe\xbf\xbf\x8b\xc7\xeb\xbe=\xfe\xbf\xc6\x8f\xdc\x8bU\xc4\xef\xbf\x9aV\x11\xf1\xf7D\xf9\xbf\x1e^\x06lK\xd2\x03\xc0K\xe9_(\t\xd6\x01\xc0\x87\x02\xe1A\xf3\x84\x95?h\x81B\x04b\x1d\xec?[\x12\x1a]v\x9f\xff?\xf1\xb4\xc5\xfd\xf1\x06\xd8?iP/\xe3!\\\xf0?>7hN\xb4\xf2\xf0?\xc68T\xb2J\x9d\xd1?z\xc0\xe1\x9b\xd0\\\x00@\x0cAp\xcc\x02S\xeb\xbf\x9b\xc9\x9e\xef\xb1\x02\xdc\xbf7\xd9\xa7\x92\xfe\x96\r\xc0\x02H\x81\x0f\xeba\x06\xc0\xca\xa9\x95\r\xbc\xc1\xfa\xbf\x16\x03o\x16\x12t\xdd?e\xec\xcc\xd2\x03\xd2\xe6\xbf\xca\xbd\x12\x0fCg\xb4?(\xec\xbc&\x91\'\xea?\x82\xea6\xe7\xb9\x80\xed?:\x18p5\x0b\x14\xf5\xbf\xce \xff\xfe@e\xd8?\xc7o\x99y\xef\x92\xe9\xbf\xcc\xbev\xa2\x9d*\xf0?\t\x0f\x12\xa6\x1dv\xf3\xbf\n\xdf\xe3\x0b\x1a\r\xfe\xbf\xe6cq\xef\xf4z\x03\xc0\xf1\xc1-\xbe\x84\xf9\x02\xc0\xd8\x91<6B\\\x04\xc0B\x02\xd8i\xb9A\t\xc0\x86r\xe2\x05\xf2\x81\xfe\xbfn\x0f\xfa\x81SS\xf6\xbf\xa4]\xb9dN\t\xed?1AA8\x96\xe0\xd0?\xf8\xde\xe1w\x1a\xf0\xc2\xbf\x96\xaf\xde\xc3\xcf\xe6\xfd?\x1e\xf9\xe0\xfd\x90o\x03\xc0%\xf2\xf8nC\xbd\x04\xc06\xc3:\x8f\x0fd\xd5?\xc3\x0e\xe4\xcf\x92C\xe9?\x15\x9a\xf8\xf1?J\r@\xbb\x9c1\xfc\xb0\x9f\xdd?\xda\x8cO\xe8\x143\xef?\x03\x96V\xcb\xa5\xff\xd7?\xba\x98\x82+\x97\xe7\xde?\xa3\xfe\x91\xfa)\xf7\xf5\xbf\xe3]\x96j\xea\xf9\xf4?\xff\xbfw\xfeY\xb0\xe3\xd6\xdc\xbf2\x03^\xde\xc3%\x10\xc0\x13\xdc4\xdd\xc1g\xfe\xbf7 \x08\xa9aO\n\xc0\x02\x80\x97\x17\xc8\xcc\xe8\xbf\x9a5\xf6\xe7"\x93\xe7\xbf\xf8.\xd4vV\x0b\xdd\xbf#\xe7c\x80O\x1a\xe1\xbf\xcb7\xf8\xb0\x1f\xb7\xf4?R\x06\xe6\x8e\x19\x15\xfe?A\xf2\x05\x0c\xd3\xf5\xec\xbf\xe1\x0e\xd4\xad\xac\xca\xff\xbf5\xc6Z\x82*f\x14\xc0\xdej\x16\x0e\xba\x95\xf4\xbfiy:\xe4\x9d\xf3\xf6\xbfdT\xab:0\xca\xf4\xbf\xc7g\xcd|g\xd9\xc3?,\x04Wv]\x83\x00\xc0\xd1\xf2:\xfbz\xfe\xe2?\xb0\xa6\xaf\xa9\xcc\x00\xeb\xbf\xbb\x96-<\xbe"\xf2\xbf\x95\xa5\xb1\xd8l\xdf\xe4\xbfk\xeb\x91\x95,\x04\xc09\x033Li\x92\x03\xc0;{\x94h\x8c\xc0\xc4\xbf\x1c\x11\xd5\x14,\xe0\xe2\xbf\xa4\x84h\xf2\xa0\xe2\xfd\xbf\xf0\x87\xd5%\x10\xa0\xe8?\x18\xa2\xb0D\xd6O\xd4\xbf\xae\xec\xb0\xfb\x1aa\x90?\xaaG\x8f\x0b\xe7\xd1\x06\xc0~z|2\xee\xa0\xf5\xbf\x97\x9a\xd2\xb1\x03\xb4\xeb\xbf\xd5\xed\xd6\x88\xab\xaa\x04\xc0\x86\x8d\xbb\x9dz\xef\x0e\xc0V\x151P\xcfT\xfe\xbf\xa3("\x80\xa0\x99\xe4\xbf\xc88\xbcU\x9b\xe6\xee?\xe7\x11\xf2\xca\xe0\x19\xf8?s)C\xe3*B\xdd\xbf\x83R\x98\xb3\xe2X\xda?\xf4R6\xb5\x027\xe4\xbf\xe8\x15\x9fH\x13o\xf8\xbf\x18B\x1do\xffO\xec\xbf\x0e\x1b\x18\x89\xfe\xfe\xf2\xbf\xf4\xef\xe55u\x82\xed\xbfcp^\x02\xda\xc4\xe0\xbf\xf5\x99\x99\xfb<\xef\xe4?\xd5\xf5\xab"\x92,\x02\xc0\xd0\xf2\x1f\x12K\xbb\xba\xbf\xed\x82\x88\x1b\xbd\xe5\xd3\xbf\xad\xf4\xa9?\x10\x8c\xf0\xbf\x00\x1f\xed.\x9eb\xdb\xbfa\xad\xe4\xe8\x1f\xde\xe3\xbf\xaf\xe78 7\xc8\xc7?J\xb5\xed\xee\xde4\xf1?/^\x0f\x96\xd3\x97\xdd\xbf\x1f\xc9\xcd\xe7h+\xe7\xbfG\x89\xa3\xe7\x04=\xed?\xa9\x90\x8a\x8f\x87O\xfb\xbf\xacbu\xd5\x92<\x05\xc02\xa6\xe3\x94\x8c\xc2\x03\xc0td\x0e\xf7\\8\xfd\xbf\xe6\xfe\xf1\x8b\x14\xc9\xff\xbf\x0c:R\x8d\x81\xaf\xf7\xbf.\xf7\x17\x82\x1b\x82\xe0\xbfOPh\xcd*\xec\xe1\xbf\x92\xbc\xb7\xfetF\xf6\xbf\xa3\xea>-\xfa;\xf7\xbf\xc3G\xe7\x82\x0fI\xc4\xbf\xca+\'N\xf0\x85\xe1\xbfJ\xaf=O\xd5\x00\xc5\xbfS\xc2\x08\xe5-\xe6\xe8\xbfW\xa6\xae\xa5}\xda\xf2?\x9e\x06\xa1\xd9\r\x86\xd3\xbf\xe5\x13\xff\x0c\xec\x82\xd8?\x10\x9a:X\xb9\xaf\xae\xbf\xa9\x04u\x01\xffM\xfb\xbf\x1f\x92\xc2W|\xb2\xd3?Oq\x9d\xb34\xe1\xf4\xbf\xddg\x17\xd2z\x12\xed\xbf\x83\x17\xe0=\xd9\t\xfb?\xef\xf4W\xabV\xaf\xb9\xbf\xf2\xfe\x1b\xaeV\xcd\xd9\xbf\x0b\xb0@]\xfc\xc6\xe6\xbf` \x18\x1b)U\xe6?\x8a[\x08\xb6vI\xfe\xbf\x12\xe3\x19\xb7\xb5\x92\x08\xc0a\xd5N\xbb\xfb\x97\x0f\xc0\xf9yY\x99M\xf7\x00\xc0\xba^\x8c\x9f\x06/\xe4\xbf\xea\x0e\xfa\xc6,\x8c\xf2\xbf\xa5\x1d\xc9\x90\x91-\xe5\xbf\xc0aJ\xde\xeaN\x02@\n\x0c\xe9\xc9\xd9m\xd0\xbf\x01bJW\xbc\xf2\xe7\xbfLe\xd2\xfd\x02\xf9\xf3\xbf2\xd6\xd6;\x95;\xe6\xbfb\xec\xfd\x00GR\x01@\xaaS\xa2>p\x0b\xfe?\xd6\x80;\xf78A\xff\xbf0/\xc7\x98qj\x0b@\xe9\xd4\xf3\x9ejm\x05@?\x00\xb9 [\xce\xf9?\x83.{=\x97\xa4\xd0?\xa4\xd3\xea\xa6\xd4\x82\x0b@J7[\xd1O\xa7\xec?\x97\r\xb0\xad\xb3M\xf3?3\x11\x0f>$2\xde\xbf-\x9a8s\xd8!\xcd?\\\xbab\xbb\xbaS\xe2?\xa0\xc5h;\x81\x7f\xde?E#\x7f>\xd7G\xe0?G\x03W&\x08\x06\xfa?\x98\xe1\xd9I(\xa0\xef\xbf\xa7\xae\\.>\xec\xca?)\xae\xbb\xf1\xe4x\xef\xbf\xe6\xdfu\x18\xa7&\xc5?J\'\xe0y\xc09\xf4\xbf\xc81\x1a\xbe\xc8\x7f\x02\xc0\xe4\x91\xe8\xc1ye\xf8\xbf\xf8\x80U\xcb\x11\xb2\xff\xbfh\xd2\xdcKT\x90\xf3?\x89\xe5\x02\xc5\xf5Q\xe3\xbf\x83:c\x964?\xeb?\x19\x8c\xb5\x99\xe0\t\xd0?S\xa6\xfe\xbe\xaf\xa4\xea?\xed\x14k\x936\xa8\xeb\xbfd\x10\xe9\xf6\x8e\x05\xca\xbf\xb4\x83\xf4?\xcd\xea\x07\xcf\x9f\'\x00\xc06L\\i\xaf\x06\xd5?\xf8\xaa\x8f\xf1\x8d\x10\xf1?v\xc7\xbd\x93\x1eW\xf4?\xad0ov\x04\x8f\xc4\xbfy\x99\x0c\xcf\x0b:\xe4?\x18q\xc6#\x11\x7f\xeb\xbf\x8dr\x9ep)^\xbb?\xcc!\x07\xfa\xee\xc6\xa0\xbf%>\x1e\xc2\xee\xb5\xf3?\xed\x8bH\xed\xcf\xee\xeb\xbf\x08\xeb\x01\xea\x8c\xa7\xf9?\xfb\xd7}\x9f\x98\x7f\xe3?N\xc4\xef\xe9\xb1J\xa8\xbf\xe08\xcc[\x8f\'\xe1?\x19\x05E\xb5\xaer\xfa\xbfa\xded:\xfbv\xe3?\x89\x97#\xeb\x90\xb7\xf2\xbf\xeb\x06\xce\xb4\xf4s\xe7?\rK\xe9\x88\x8c;\xf7?Jz\xe3\\\x98\xe0\xc3?v\xe6\x0e\xe3S\x8d\xe7\xbf\x9cV\xc1\xb6a?\xf0?\x99\x07\xba\xf1\x93k\xf1\xbf\xf6\xde\x13\x10\x915\xc0\xbf=">\\~L\xf2\xbfR\x0c\xe3LV\xd2\xd0\xbf\x97(\xd6d\x1a\xd3\xc8? K\x92WH\xfa\xfd\xbf\x8e\x07\xc4f\xbe\xc5\xf3\xbf9]N\xba\x8b\xb9\xc9\xbf#k\xec\xe0\xa2\x91\xcf?R\xe6\x7f\x8e=\xd8\xea\xbf\xb9H\x16\xa1\xfeK\xfd?[\xfe\xd8\xacdN\xeb\xbf\xf9?\xcc\xb5\xf2a\xd3\xbf\xabE\xd0\x1d\x83\xbf\xfa\xbf\x00\x8e\xd2\x8b\'+\xdd?\x1b\xe8\x1e\x1c\x82\xb1\xe5?%^\n\x80\x1e\x1c\xf1?v \xe4B\xcb\xea\xd8?q\xa3\xe8\xd8_\xf2\xc2?\xcdceQ\xfa\xbd\xdb?\x07\xaay\x9f\x00V\x98?\x8b\xc7\x84U\x1a&\xd6\xbfW[\xaat\xb0\xaf\xef?\x1dc {u*\xc2\xbf\x19"a\xd7\xb6\xac\xe0\xbf\xcd\x11\xde\xc1\x1bd\xe1\xbf\xb3\x8eN(!\x8b\xe6?\xe0x\xa1Px\xaf\xdb?\xf7H\xb5 37\xd6\xbf\x86\xf8m\xf7Q\x86\xee\xbf\x81\xe4\x11\x82\x80/\xaf\xbf"\x834\xf6\xa9l\xd1\xbf\x7f\x1e\xe8\xedS\x87\x05\xc0\xd9\xcb\x14r`\x84\xed\xbfQ,(\\\x17\x88\x9e?\xeaW\xa9#\x88l\xe3\xbf\x87$E\x9esA\xf9\xbf,\x83\xb3\x14q\xb0\xdd?A\x0c@\xb9&-\xf2\xbf(N\x9dD\xe9\xa1\xf7?l\x14\x86\x8e\xf8o\xd4?W\\\xa9Se \xb8\xbf\x92\xd7J\xc3fd\xf9?E^\xf7Db\xfa\xe8?\x9eM\x9f\x16\xa7\x00\xe2\xbf\x8c\xb6\x18.\xbe\xe5\xc6?\x1d\xff\x16:\xc6\x82\xd6?\xde8\xdf(Z\xe0\xcf\xbf\xfa\x1b\xc5\xd0\x0f:\xe8\xbf\xed1\xca\x1fK0\x02\xc0\xce\x93\x03\xa2|\xb2\x81\xbf\xd2\x82\xe8\x8c\x8e.\xf6?^\xd2K@\xca\xe8\xe4?\t\x02N\xfe\xc0L\xaf?h\x98\x8f7\xf87\xfa\xbfR9\xff\xf6\x7fe\xe4\xbf\x117\xca2\xfc\xe1\xf1?\xaf\x14\xcdw\xc3\x8a\xf0\xbf\xd7\xb6\xbd\xa2\xe8|\xc1\xbf\xd6\xaa\xc8\x1c\x80\xe5\xdd\xbf\x9d\xb6\r\x16\xbb\xa9\xe1\xbfA\x19e\xc8\x90t\xe9\xbf\x81\x81W\x9f\xc4 \xff\xbfF4\xc7\xf01\xed\xd9?\xe84\xad\xc6\xb7\x86\xfb\xbf\x02\xb2\x95\xc5\\V\xf1\xbf\x8a\xde,)\x84\x0c\xed?\xa1{\xb2^\x88\xc1\xbb?\xf34$@7\x97\xdb?hI>\xcd\x8a \xeb\xbf8\\U=\xb1\xfa\xcd\xbf\x18$A\xf5\xc3C\xd5?|\xad\xc0-b\xfc\xbb?,\xaa.\xd35,\x90?A6\x9b\xaa0\'\xda\xbfh\xd6\xff\n\'C\xd0\xbf\xce>}\xb7CM\xfc\xbf\xa1\nk\xaa\t\xe5\xcf?\xb4j\x04\x96\x11\x0b\xf5?\xa0\x96\x8c\xdb\x01o\x07@\x8a\x01c"\x06\xbd\xb9\xbftP\x8a\xb4\xcfC\xd1\xbf\x9e\x9c\xf4\t>\x1er?a\x1b\x07\x15u\xf8\xee\xbf\xd0M\x87\x9b\x99\x18\xf4?\xa9\xc1\x12\x7f\xe8\x0c\xd0\xbfV\xad[K\x9b\x82\xd3\xbf\xa4w\xfcW\xb7,\xaf\xbfZ\xce\xbec\xc3\x92\xe8?\x90\xad\xc1\x11\x1cE\xfd?\x8b\x84\x8f\xaf6\xfb\xef?)\xc6\xe9\xd6*\x1a\xf2?=,\xad\xfe*\xc5\xe1?f\xfc=\xeb\xb4?\xd9\xbf\xa5|a\x00\xf8\'\xec?\xa7ML5/x\xc7?{\xcciHZ\xf8\xe5\xbf\x83u\x95\x10\xb2\xf0\xd1?\xfcL[I\xac\xf9\xd9?zmlJ\x1en\xf0\xbf\x883\xfd\x1b$,\xdb\xbf\xb8X\x7f\xe7o\xeb\x00\xc0\xa6Zt\xc4\x0f\xd2\xf4?d\xe8\xdd\xb3h\xd0\x96\xbf\x83\xb8D\xd2\xd9\xd8\xe8?\x1d\xf89\x00C\xb3\xc0\xbf\x9a\xb1\x07\xac\x8e\xf3\xcc\xbf\xed\xcfE?u\x07\xd9?=6\xed\x12g&\xd4?S\xb7\x7f!\xc8}\xe5\xbf\x06\xfda\x12b\xc3\xda?\xfc\xfd\xaad\x0bJ\xe2?f\xfe~\xe8\x08\xaf\xbb?\x8a\x97\xc4\xfb\xdbO\xf3\xbf8R\xbb3\xec\xae\xd9\xbf\xe6o6\x8b\x8a\xf8\xeb\xbfE\xc48%\xf1\xaf\xed\xbf\x8a\x00T\x95\xdbS\xf7\xbf\xba\x8eL\xf8\xd5\x89\xe6\xbf\xa9\x90\x8d\xb1\x86\xde\xfc\xbf\xc3\xf1\xec{2\x95\xd4\xbf\xe6\x83\xaf:\xe5\xc2\xcf?\x82\x9e\xa6_\xcdY\xf6\xbfe\x10:O\nS\xd1\xbfv\xe3\xf5&\xd4\x8f\xe0\xbf\xe56!\xd5\xbe\x17\xd5\xbf\xcfLY\xe6H\x9d\xdc?r\x01\x111\x9e\x03\xa6\xbf\xf6[u]JD\xf3\xbf\xfe*l\x136\xa9\xe7?}!\xa2\x92J\xcf\xeb?\x1c!\x82!,\x18\xf4\xbf*\xc0\xe0\x88y\x02{?A\x1a=C\xd1|\xc0\xbf\x9d\x11\x0e\xb4\\S\xfa\xbf\x9f\x99\xa3w9@\xe9\xbf\xea\xab\xa3/n\xad\xa3\xbf\xbe\xe2\xeaI\x1b\xd7\xbc\xbf\xacDR\x81\x93\x99\xe2\xbf_W\xc8V\x92\xd4\xdf\xbf\xb2H\x85/\xec\xf7\xe7\xbf\xbad!W\xa5\x10\x07\xc0\xf21^!=\x86\xee\xbf\r5u]\x9dL\xfa\xbfDi2\x06\xc7\x8f\xf3\xbf7qBY\x81\xc7\xb5?~\xc8\xe0\x17\xfbD\xfa\xbfg\xb6~A\xa0\xac\xfc\xbf\xe4fv\xe6\xeeN\xe9\xbf\xef+O{\xacE\x04\xc0\xd3\xc3V\xbb\xb8\xa5\xda?\xfb\x98\xe7r\x12\\\xea\xbf\x87e\xad\xf4\x0e\xb3\xf8\xbf\xe6\xd2\x16N1\x06\xb2\xbf&\x00\xb1^\xf0\xd9\xb5\xbf\xc6\xdd\x08O\x8aw\xcb\xbf;X\x12N\xd4X\xb0\xbf}\xffh\xa3&\xe6\xe5\xbf\x03Z2\xe5\xc0n\xf2?\xf5\x9e\x15\x86Md\x01\xc0\xbc\xc4\x80#S\x91\xa9\xbf\xad\x91?\xad\x0bs\x05\xc0\xcf\x152\x9a\xcf\x1b\x02@\xbf\xadqF\xff\xd4\xe1?\xef\x8d\xdbE\x80\x1e\xcf\xbf\x01x\x8euLt\xf5?9\xfcO\xab\x83\x82\xea\xbf,\x13\x0bo\x81\x8a\xdc?s\x1f%\xc9\xd5x\xde\xbf@\xd9\xe3\\<\xd0\xe1\xbf%\xac\xa8\x87\x99\n\x07\xc0\x9e*\xa1N\xce\x1f\xf3\xbfk:\x85?c;\x01\xc0\r\xa7H\xb7\x90\xc9\x04\xc0\x101\xceq\xd9U\x03\xc0\x9e\xcd]t\xdeZ\x0e\xc0\xfc8\xd6\xaf\x82x\n\xc0\x83\xc5R\xaaDf\x10\xc0E\xb0P\xe2\xe0$\x03\xc0(\xf6G;\xe2j\x13\xc0\x06\xd1\xd2\xf2\xd8\xa0\xfb\xbf\xd2\x14\xa1\x8d_R\n\xc0\xde\x15\xb6\xf9\xa7\xe1\xfd\xbf\x7fg\xb8\xb2Pz\xfb\xbf\xac\x0e\xd4\x01N*\xf3\xbf;\xe0\xf57\xee\x17\xfc\xbf\x86\'\xc5-\xdf\x18\xdf?#P\x93\xf6\x9d\xcd\xba\xbfpJ$m\xf3w\xba\xbfu\xc3r8\xffw\xea?\xc5[\x9b\xa5C:\xcb?+X\x85\x8cls\xda?\x13\xe7{E~\xe9\xf7\xbf\x86&\xcdN+C\xed?j\x16\x1e\x8c\xe4\xf2\xd7?oy\x88$w\xf7\xe7\xbf\\\x19\x83\xffx6\xd4?O\x91=n4\xc5\xc8\xbf\xf7\xe2\xb6^\xf8\xa5\xc4?;\xb0b\xa3T@\xdb?LH\x82\xd2\x97\x97\xfd?\xac$D\x11\xedo\xc1\xbf*\x1e\r\x19\xaa\xd7\xfd\xbf\xcb\x1a\xa5\x10j\x8c\xc1?\xe1MB\x06\xbe\xe1\xf1?\x89M+\xd2\xee9\xeb?\x11"w3+7\xf1\xbfb\x843\x87b\xe7\x01\xc0\xbe\x83\xf9\x96\xf3\xe2\x05\xc0w\xe2\xba5\xa4\x9a\xfc\xbf\xfaV\xa5\xb7\x8d8\xf3\xbf\xfe\xd9\xaa\xf6\xca)\x02\xc0\x8a\xf0\x1aBf)\xef?\xcb\xa3@1\x166\xde\xbf\x84\x12\xaf\xba\xcbB\xd1\xbf\xee\xe4\xc1\nm\x9b\xde?\x9b\xcd\x1ad\x91G\xf4\xbf\x0c\x81\x1cm\x9f\x87\xe0\xbf\xde^\xe5\x1f\x07\x88\xfc\xbf\x93\xd1K\x99\xb9"\xe9?\xd08K\xfa\xf4\x0c\xec?\x19\x88}\x1bz\xb6\xfd?\xddJ\x00\x1f\x141\x01@M\x01\x9b\x06\xd9c\xfa?\xf7i\x10\r\xea\xab\xd5?\xb8}\xf8z\xb0\x12\xf5?[\x8d\x14!\x1b\xbc\xfd?\x82\x0b\xb0\x89\xa6\x96\x00@\x9f\xe9\x973e\xd7\xd7?\xaa\xf3\x1f\x92$U\x03@\x15\x15\xdf\'\x1f8\xe5\xbfA\xbdN\x92h\xc3\xf1\xbf|\xa6.\xa5|\t\xee?/:\xdfL< \xee?\x07\x9e\x88\xf5\x08Y\xdf?\xe5\x06\xf7\xa1\xef\xbd\xe5?\xde\x04\xc4pr\xb0\x04@\xa3\x96\xc1\xb8jW\xfb?\xd7\xc5\xdej?\xd0\xd4?\xe8\x06N\x8f^_\xea?\x16\xb3\x98\xbe?h\xd6\xbfs\x9ai\x08|\xb6\xdf\xbf&Z\xbe\xd6I\x95\xd6?\xbb\x0e\xcd \x14\xd2\xf8\xbfhl\x84\xae,c\xe8\xbf+\xda\xeb\x97} \xed\xbf\xdbU\x13F\x8d\xc5\xd5\xbfP\xbc\xa6RK\x8a\x04@\x0b2\x80\xea\x9cm\xe5?\x11\xad\xe6\xf5\xccK\xc1\xbf:\x19o\xf1?\xc4>\x00\xf8\xacT\x07@v\xc3f\x067\xe7\xec?I\xc4\x11\xcc\x90\x9f\xfa?d\xc8\xda4v\xa2\x03@\xd0\x9fj\xc7\x9ec\xdd?Y\x95\xde\xba8:\x02@D\x05i\xf8F~\x06@\x8a\xcd_\x80\xde\x87\x01@\xbf\xaas\x17\nw\xf9\xbf*\xccr\xf8*\xc4\xea\xbf\x94\xf1Q%k\t\xe0\xbf\x00\x06\r\x85\x93\xd7\x02\xc0\xb2\x0cvV\xe3m\x9c\xbf6mjy\xf0\xcf\xed?~\xdd\xacO\x9d0\xf5?\x86g\xe7\xc6RB\xf8?\n\x93*\xfbV\xa5\xff?\x88 \xc1\xf3i\xbe\xb5\xbf/<\x9b\xe5{b\xfa?\x055c\xc7\xa4G\xe7?\x05-7]C\x05\xfc?h&*X\x85\x13\xdd\xbf\xe2\xc1\x94\x8d\x89\xbc\x06@\xa9f\rYb\xfe\xfc?dt\xdd\x99\xaa-\xf6?B\xaczq3Z\xec?>\x8b4\x82U}\xe9?M\x99s\xaeEN\xa0\xbfn\xf0[\xdf\x91S\xe6?r\xb9\x91\xac\xbb\x97\x03@`>\x81\xf2U\x9a\xd3\xbf\x90\xe1D\x96\x7f>\xe7?\x02n`\xf4\xab\r\xfa?\x86\xed\x92,x\xfa\x01@\x174Pf\x89/\xf7?\xf1\xd4zZ\xf0z\x0c@a\x1b.p;H\xc3?\x90y\x81R\xbfp\xd3\xbf\xd3\xb7\x94\x1a\xedv\xf2\xbf\x7f\xde\xb9\xa1q\x19\xf0\xbfO\x94\xcc\xee*\xcf\xf1?\xb2\xaaJ\xe3k\x1b\xe9?\x08c\xf4\x89\\B\x01@\x17\xa6\xcc\xc2,\xf6\xd6?\xc4\x97,\x84\xb18\xc1\xbf\x93\xc9\xf3\x82\x7f\x87\xe7?!w\x923\xe1\xbf\xd4\xbf\xb3\x94I$\xd8\x84\xa8\xbfD\xe4\xea\x89L\xc9\x03@\xd4Lw\xe5\xe4\xbd\xa8\xbf\xa4J\xaco\x81\x99\x92\xbf\xe6b9\xee\xbe\x1b\x06@I\x95w\xf0\x02\xb7\t@E\xa9\xde\x13\xdc\x89\r@/\xc7i\x1a\x8a\xd8\x13@\x81\x03n\n\xe2\xb2\x11@u>\xa3p\x87N\n@R\xc5\x8e\xbe\x82c\xfa?\xf9\xd8e\x10a&\x05@\xfdVV\xc0s\xc8\xfa?q\xa5i;GL\xf5?f\x82\xf8\xaa\x11\x83\x04@\xcelz\xd2Q\x10\xff?\xe4Y\xc6F\xd3\xea\xfe?\x9e]:\xc5L\xfb\xbc?\\\xc0\xf8Z\xf8\xab\xe7?\xebB\x90\xbd\xdc\xe3\xfb?~)n`\x8e+\xcb\xbf\x87\x94\x11\x1a\xea\xd7\xd3?\xf2\x99\xe6\xd6[\xb7\xf8?j\xa4$\xde\xe7\xc5\xca\xbf\xde\xc8H_E\xb4\xcc?\xa9\xd7\xb0g\xb6i\x00@d\x8cj\xec\xfc\xb0\xe7?\x97\xd47a\xfbK\xe8\xbf;(\x05\xc4\x14p\xfb?\xba\xd4ZX\x89\x8e\xf0?\xdd\xa7\xea\xed1q\xf5?\xb9P\x8fH\xaa\xbf\x00@\xfa\x05C\xd4h\xda\x10@\xac\x1cL^\xbb~\x07@y\xb9\xfb-i\xa6\r@o\x0c\xfd\x8bJ\x19\x07@z\xeaPrv\xf0\x01@S\x99\xbc\\\x9d\xa8\xf8?vzPP\x11i\xf3?@\\\x02\xc2\xba\xb4\xe2?\xbc\xdf\x8bB\x80\x94\xf4?\x10\xb8\xf92}P\xe1?U\xaa-q\xffe\xe0\xbf\xec\x93^\x14\xd4j\xd6\xbfo\xf2\xc3\n\xda\x94\xb2\xbf1\xd3\x8eS\xde\xa3\xe4?K3/\xb2l\xda\xf5?x)B,m\xb3\xf8?mV\x9f\xb4\n\xa4\xf3?6Hj\x8e-\x8a\xce\xbf\xd2\xb3\xb2\x87\x81.\xf8?fQl\xd8l:\xe4?\xe3\xf7\x8d!\x04\xcc\xe3\xbf0\xd1I\xado\xdd\xce?\xdc\x9b\xcf\xe4\xb3\xd3\xf5?\x8a\xfb\x1a\x1f\x19\xf2\xf0\xbfI\xe3 \xa4\xbc\x10\xf5?G\xd0\x91e\x85\x8c\x00\xc0\xc9X4X\x02k\x03\xc0w\xb5)\x12\xa3<\xf2\xbfL1\xd3\xf9V\x03\xea?\xd6 \xb0U\xcb\xeb\x06\xc05\x9bwx\x1f\\\xe2\xbfFa\x9c8"\xed\x00\xc0\xf5\x93\xd8e\x07U\xe5?E\xda4g\xc0\xef\xf6\xbfhD\x03>\x80\x06\x00\xc0\x1a\xe0\xa4]\xb6v\xef\xbf\x86x\xf2\xd4\x7f\xde\xf0?J\xf6\xbb\x1c\x7f\x1b\xcc\xbf\xf7\xc0\x81\xa0E\xcc\xd5\xbf\xe3\x02\xf0\xe6+\xed\x06\xc0}\x0b{cX\x91\xf8?\x7f\xc28\xb1\x00\x03\xeb?\xc0\xf4=\xb7\x0bw\xd2?f\xc4Z\xa2\x7f3\xb0\xbfv\xe7\xcfH1\x03\xf9?\xdd\xee\x85}(\x9a\x00\xc0\x06\x177\'\xbf6\xe6\xbfW}\x13\x16T\x04\xef?\xd7+\x89\xfd\xcf\xc1\xad?\xfe\x88\x83\xc4j\x14\xd2\xbf\xbeRtHD^\xfd\xbfDb@\x16DC\xd1\xbf\xcetD\x9f\xc3\x8f\x0c\xc0\xad;R\xc9A\xaf\xed\xbf\xfe~^\xaa\xedQ\r\xc0t\x8e\xdd\x96\x92\x94\x0b\xc0a\x85\x95j\x82\x8d\x13\xc0\xf2\xaa3b/o\x18\xc0z\xd6N\xb7\xf3\x8c\x07\xc0\x92niG\xf6\x05\xfb\xbf\xa6\x02+\xcchW\x00\xc0I\xa3\xab\x1e\xaa/\n\xc0y\x8d_S\xbf\xc5\x00\xc0\xc9\x8a\xdf\xab\xc1\x16\xf9\xbfE\x0f\xb7m50\xe6?\xb4\xb0\xee\xb6C\xbc\xfa?\x0c\xfe$\xfa\xa0Q\xe6?\x8cO\xcf\x03.@\xf5\xbf\x04\xd0\x85\xed\x12\xd9\x02\xc0/\xc4\xc9\x1e6/\xf6?\x011\x03r\x80U\xec?\xa2\x13\xd7o\x0b-\xec\xbf\xb7\xc8\x9cP\xd4"\xf3?\xcf7!\xd0vx\xd4?\xb2%\x06\xf7\x99\x93\xeb?\x92!z\xefa\xb8\xe3?S ^\xe7\x03d\xde?\xe8\x8a\xa8\xbb\xe8>\xf4\xbf\xcd\xde\x03\x1c\xec2\xeb\xbfl\x13]:\xe8x\x08\xc0\xb9\xde\x91f\xb54\t\xc0j\x08qO\x92\x8b\xf9\xbf\xc1\x81\xd8\xf1\xfe\xe4\xd1\xbf\xa3e\xdc\xca\x18\x04\xf3\xbf\xa7Xi\xe9\xd0\xc1\x02\xc0>\x11\xd4C\x04Y\x02\xc0\x8f\x8b\x02\xeb\xa9\x1a\xfa\xbf\x06\xa8k\x1ad\x12\x01\xc0\xcc\xe5\xbe(\x8e\xbf\xeb\xbf\x8ce5\xf4x\x0e\xfe?\xe3\x14:y~\x19\xf7?\xeb\x9c\x90T\xfa\xbd\xfd?/\xab[}R\x12\x04\xc0\xb0<\xb1\xbcw2\xf3?\x80,\xc1\\\xc3\x8b\x01@\x06Q\x8b\xdc\xf5\x0e\xd6?\xe0z\xbd,\x9e\xb5\xf3?\xafO\xf5mZX\xf1\xbf\x03SWZ6\xf3\xd1?c\xf8\xfe\x1c\x9e\x9c\xe3\xbf\xe1B\x9a"\xa9\x99\xe8? \xd9Fp\xee\xa2\x00@\xe9\x05\xf9H\xcd;\xd9\xbf\x0e\xf1[\xd2\xb3h\xf8?\xbe\x8d\xa71z\xd2\xfb\xbf\xf1\xacu\x14\x0f\xcc\xf5\xbf;\x8c\xeb\xd05\x88\xe4\xbf\xf6\xa8\xdf\xa9@\xf2\xf3\xbf&\x96V\xbf\xed\xbf\xcf\xbfk\x8c_\xc6Vt\xf9\xbf\xcf\xa0_CH\xee\xfa\xbf@\xcdB!+\xa4\x0c\xc0J\xb3*\xb1\x0f\x02\x05\xc0\xa0\xbd}\xf8\xe5+\xef\xbf4\xf1\xb3^\x1e\x11\xe5?\xcf\x13\x10\xc9\xf2c\xeb\xbf\xf4-K\xd4\r\xa3\xf1?\xccU\xbf\xa1Q\x12\xc8\xbf\xaf\xd7b\xe9\xc6\xca\xd6?\xe9\x13\x84\x86\'\xdb\xe6?<\xe5\x85\x10\x1e4\xf5?SL{\xab\x18\xa9\xf9?\x81\xd5\xd5L\xd8!\xd1\xbf\xa4\xdf\x85\xdcn\xc7\xd9?\xfb)\xb4W\xc4*\xf8? fs\x11A\xa2\xf4?\xb3~6\xf9\x0f"\xf2\xbf\x03kv\r\xfe\x0b\xe0?\'\xb3\x80\xf3y\xe2\xd2?\xf3\xb7\x1d%A\xed\xe4\xbf\xc2\xf9\xa9\xda?/\xe5\xf8\x93\'\xfc\xbe\xbfmK\xe1\x1ac\x0c\xdb?\x19\x92\xaa\x89\x18\xf8\xd9?)\x00\x8d\x96j\xa3\xe2?\x14\xdc4h\xd9\xe7\xd5\xbf;[X;\xe5\x16\xb5?\xb8\xfa>\x84\xaa(\xd6?\xad\xb5+\x17\xaaP\xdc\xbf\x1dUD\x13\xff\xc4\xc8?\x04\x83\xc9\xdc>h\xfc\xbf=\xd4\\U\x9bk\x03\xc0\x00\xd1\xdc\xc0.X\xf9\xbfE\xa3\x0cg\x9d\x0b\xf5\xbfl\xe1\x10\xfc\x9c\x07\xdb?e\xfb\xf2\xa6\x8bg\xf5\xbf\xa7\x138"u\xe0\xd5\xbf%\x1c\xffhY\xf9\xe4?\xaf\xe6y\x8c\x83\x06\xb5?l\xe7\x11\xb9\\r\xd3\xbf%\x13j\xf7\xcd\xe1\xd7\xbf\xd7\xba\x8e~\x8b\x04\xf4\xbf\x84\xc4\x90\xbc\x92\x80\xf4\xbft\xb9\xa0dp\x14\xde?\xd2l\x19mV\'\xf0\xbf\xdc\xd9\x7f\x01\x1c\xdf\xa1\xb9\xeb\xbf\xe9\xbd\x01o\xde\xa5\xf2\xbf6\xa0[5Vw\xf3\xbfG\xdc\xe1\x16\x1dd\r\xc0\x1f\xd6\xb1\x9f7U\xf6\xbf\xde\xf2J$\x9dP\xf0\xbf*\xa0OS\xbc\xfd\xe7?F\xdf\xe7c\x82\x05\xa2?\xea\xf4L\x8a\x836\xd3?\x8a\xabc\xde\x88\xd2\xe4\xbf\xd3\x08\xcf\x08\x0c\x0c\xf9?\xf7\x8b:\x8d\xc6\xea\xe8?\xa1\xf4OqO\x9d\xf2?\xda]\xc5\x7f\xcc\x1f\xd9\xbf\xbb\xa2\xebF\x94\x98\xf2\xbf\x92\xc5\xf9n\x7f\xc4\x00@\x9bNgv-\xca\x06@\x02\x07\xaa;\xa9y\xf6?\x9a\x1eJ\xba\xfas\xce\xbf*\x02\n\x08&\x83\xcb?8\x8fGi\x03X\xfa\xbf\x17Vakt\xfa\xf3\xbf\x98\xbfU\x1a\xc6\xa8\xce\xbfAa\xfe\x0bck\xf8\xbf\x04z\x1e\x1dV\x13\xf0\xbf\x1d\xd6\xa3\xdd\xf2\x0c\xf1\xbf\x9b\x9ez\x1f\x9aR\xdf?\x12h\x84\xeb\x81\xdd\xed?\xeeH\xc4J\xb8\x87\xeb?J\x13\x838F4\xe7?]W0e\xf6\x80\xe2?2\xcf\xa0\x8f\xa0>\xed?r\xdbP\xfa\xcd\xd4\xea\xbf5)\xfa|\x80\x03\xe1\xbfd\xab\xae<\xf0\xae\xf3\xbf4^\xaa\xba\t\xf3\xf2?\xf1k\xdd\x8a|\'\xf0\xbf\x8b\x05C\x83\xb9S\xf3?\xff25\x7f\x06\xd0\xb9?\x04"\x91M\xfd\t\xf3\xbf\xa9\x83\x04\x86^\xad\xe3?\xe7\x97\xff\xe1Qr\xd8?^\xcec\x06\xc2M\xd0?|\xbbq\xcd\x1b\x83\xd3\xbft\xb2\xbf\x03C\xc9\xa1?\x0f\xb7\xef\xb3*X\xf3?\x12\xf8\xf1P\x84\xdd\xdd\xbf\x96\x14\x82aO\x1b\xd2?\xcb\xaa\xe4jV\xeb\xd9\xbfZF\x860\xde5\xe8\xbf7\xfa\x19\xfe\xc6\x9a\xfa\xbfx\xebr\x05\x98<\xfd?\xbb1w\xa2\xf0,\xc9\xbf\xe8\x817|\x7fq\xee\xbf\xff%\xdc\x8f\x13\x80\xc1?\x17\xf3\xae\x85r\xed\xda\xbfa~\xfa\xe0\xab\xdf\xd4?\x7f\xc1\xce\x9eQ\xd8\xeb?\x04V\xc8B,\x0b\xe9?\x0e\xb4\xbc\xbd\xbbG\xe9\xbf\x94\xcb\x9az\xdd\xca\xf7?\xc6iZ\x0f\xbb\xf9\xb0?&V\x93\xa5\x90A\xbb?\xa1\x891\x12\xed\x10\xe6?\x88;\x90)V\xd3\xf4?\x18\xa7c=\xf9\xdd\xf3?\xd9\x85\xc0\xe9`\x1d\xef\xbf\x81\xa9t3\x9d\xf6\xe3?{\x19q\x9f\xbch\xea\xbf\xd6|\x05h\xfb.\xd2\xbfw.\x8b\x04|+\xf7?\xa4\xcd!d\xef[\xd2\xbf"\x0e\xf2\x93\x10c\xe3?\xefw\xf7\xcc\xc25\xfc\xbf\xf0,\xc1\xa1\xa4\x17\xe3\xbf\x15{\xc6\'\xec4\xf1\xbf_\x15$bG<\xae\xbf\xa9\x02\xadZ\xb5]\xf4\xbf\xc0\xe1\x98\x16[@\xf5\xbfA\xbcw,\x06?\xc6\xbf\xbb\xe6\x9d\xd8\x9f\xa2\xf4\xbf\xb3b\xa6\xbd17\xe6\xbf\xdb!:P!\x02\xb9\xbf\x99\xcf\xf46\x1c>\xe6\xbf\x0f\x91M\xe4\x8a\xf3\xd0?\xd96dz\xce\x99\xfe?\x17\xaeRb\xa7\x0c\xda\xbf/\xf0\x8a\xb0^\xbe\xd1\xbf\x8d\x85;\xf8\xc9Y\x07@\xc9-\x0b\x84I\xd5\xff\xbf!%wB\x80\xb8\xe3\xbf\xc7\x13\xec\xae\xa9;\xff\xbf\x0f\xd12\x17!P\xd0\xbf\x8c\'\xa1\xd7\xcf\xea\xaa\xbf\xdd\x80\xda\x7f\xbb\x9d\xaf?>L\xd37j\x87\xa5?\r\xd7f\xd5\xd1\x07\xb4?\xe0q\x0b\x8dHw\xed?\x16\x8e\n\x8e\xf0\x89\xf8?\x19)\x93\xee7B\xfe\xbf\x80*\xf3r9\xa3\xd0\xbf\xcf\xf5\xc17U\x10\xc2?;\xa42\xad\x9c\x0e\xe9?\x05\x1f\x15\xd9\x14\xeb\xff?\x06\x13\xb5g\x029\xe3\xbf\xd5CY)-,\x07@\x9d[\xe6Bp\x12\xf4\xbf\x98\x9c\xc2\xc3#M\xf1\xbf\xf3g\x917_?\xe4?U\x9as\x9d3d\xe4\xbf\x86A%N:}\xda?K\xe7\xe6n;\xed\xbe\xbf\x0c\x84p\x06\xc5\xef\xd9\xbf\xc8W\xeaE\xf1\'\xf4\xbfF|\x98\xd6\x9b\xbc\xce?\x1b\x85\xbb=\xfaj\xe3?D[k^e\xbd\xe3?#\xc1/\x91\\i\xd3\xbfS\xb7LGT\x9d\xe9\xbfa\xd7I\xca]\xae\xf1\xbfR\xcf\x9dpoH\xf1\xbf\xd7\xea\x85\xd3\xc88\xf2?@`t\n\x11\xb7\xde?|\xa8U\xce\xe1\x04\xf1?\xdd>\xabd\x81D\xee\xbf\xf0\x98\x1b\x894V\xeb?\xb7l\x99Y\xf9\x16\xd8?e J|\xc2\xb1\xf4?\xd1\x19\xaa\x97\xc7@\xec\xbf6\x99s\xc7\xbet\xf3\xbf\xda\xa3\xe3h{D\xd8?\xbd\x1dU\x88&\xcc\xe2\xbf\xb6\x9d\x9d\xb9s{\xf4?\xa9\xb9b1P\xc5\xf9?\xd9u\xc3\xde\x152\xd1\xbf2\xf6\xe9\xfa\xe1\x9f\xca\xbfx?\xf8\xe8\xa6\x8b\xb1\xbfDY5\xb7\xbeV\x00\xc0\x14oo \xf8\xd0\xfc\xbf\x072\xf4\xf45W\xdd\xbf1+\xa6\xc0\xfc\xb1\xe2\xbf\x05vnDpg\xfa?\xf4\x8e\xee\x08o{\xd0?\xd6\x1c\x83\x0e\xbc\xda\xf0?\xac\xb5{)\xdd%\xd1\xbf\x90\x86\xeeD\xb7\xe2\xed?\xe3\x98>\xdf)\xb2\xf2?\'\xb0\x9c\xa5u\xd2\xce?J\x03\x13~\xd4\x99\xd4?\x1d\x98\xe7"9\x9b\x96?u\xce\xee\x98\x05\xd5\xd6?\x81\xdc\xa7\xca\xea\xa9\xe2?\xb5G\xfb\x81\x0e<\xec?>\xd4[/1i\xf5?\xf3\x1e\xdd\xc7\x95\x08\xfc?\xd9\x82\xc3\x02\x8f\x08\xf5?\x06\xe4\xac?y\xa7\xdf\xbf\xb5X\xa7QeY\xe9\xbf2\x1b\xafm\x01w\xf1\xbf\xc5V\xdc\xb2\xa0\xde\xe1\xbf\x02^n\x80!`\xd7?\xfbS\x00\xadag\xc8?\xcf\xa5U\xdd\x1a\xfb\xee?\xf1 \xbe\xdc\x00l\xbf?\x85\xee\x0f\x94C\x01\xc2\xbf\xae@@\xed\x08\x1c\xe8\xbf\t\x0e\xcf\xf4rP\xdd?\xfb\xe8\x9a\xe2Z\xe1\xe6\xbf\xb1\xc0\xc9LHX\xed\xbf\x9dL\xa2XN\xca\xe5?R:B$\x05\xaa\xa0\xbf&\':\ty\x9c\x07\xc0\x7f\x93\xca\xb3\xd1A\xd1\xbf\x85\x04Jl\x99\xa1\xf4?\xd5\xf2\x9c\xa8\x145\xcb\xbf\x1f\xd5x\xd9g/\xe5?OU\xb5\xac\xd9I\xde\xbf\xf7\x91\x02)\x1cO\xfd?\xd5\xf5\xca5\xa1\x9b\xb4\xbf\x04\xe6\x88\xfc\x84\x8d\xf5?|m\x81\xf9\xbeL\xfc\xbf[\xa1\x15\x1d\x13\xb2\xe2\xbf\x85\x94\x10\xec\xcaF\xc9?d\x9c\xc5\xab\xa7*\xa7?*\x0c\x83\xd3Vu\xd7?\xc2=\x1c\xcc\xe3\xb0\xe7?\xd1\x17\x88hl\x81\xd7?\xe9\xaf\xecu\x8f`\xea\xbf\x05\xf5]\xe1\x1a\x07\xdb\xbfn\xa8\x19M\xc3L\xcf\xbf|\xee\x90\xcfM*\xdf\xbf\xdfB\xa8\x93\xf8\xe0\xdc\xbf\xf7=;\x17\xe1:\xad\xbf\xc2\x0e&I\xd3\x96\xe1?\xe0`\xab\x9bS\x8f\xfe?\x81\x97\xb6ZP\xbb\xf4?c\xd8\xa4ca_\x05@\x81\xcb\xe8{+\xa5\xff?\x1e\x06\x05K\xf8\xa4\x00@\xed\xc3^\xe8\x19(\xf1?\r\x14\n\xb9\xf7\xb9\xa5\xbf\x054`\x9c\xe9\x13\xdf?*\xbfk\x0ch\xe4\xe6?\xfa\xc4:\x95lu\xe1?\xb8\xfa%\xd5\x90\x82\xf0\xbfZ+\xaa9\t`\x00@\xdc>v\xd0^\x11\xfa?\x1b\x0e\xa2\x03\x93\x13\xfe?e5\xb0\x006\x15\xf3?d-\xa1\xe3\xf0\x0c\xb9\xbf)\xf9\xd9<\x04\x04\x02\xc0d0m\xa0\xaa\x01\xb1?q\xb7\xca\x9b=\x9e\xe6?a1\x19?\xea\xb4\xed?\x1e\xa0\x06\x00C\xea\xf2\xbft\xec\xe0\x82e=\xcc?\x82g\xa1S\xdf\x1f\xd2\xbfS\x8e%8\xc2\\\xf6\xbf\xbb\r\x1a\x04\xde\xd9\xf4\xbf\x1b\t\xe5\xd4\xab\x1d\xe5?*\xfaI\xcabg\xc7\xbf\xf4\xb7\x90\x7f\x18\xfe\xfa?\x91b\xc1\x04\xb8\xe1\x95\xbf\xb1(\x1b\x9c\xa93\x07@\xe7\xad\xe3\t\x17e\xfc?QT)\x040\x8a\xfa?\x89C\xb1\x10Z\xb1\xea?Y\xd5J\xee\xd1\xa4\xe7?h\n\xfdIm\xfb\xe7?\x08\x12\x91i(`\xe9\xbf\xc0\x96\xa1G\xb4)\xc3?\xef\r\xc9\x9f\x8d-\xf2?\xce>\'\xadg\x0b\xfe\xbfvK\xf4\x7f\x04\x13\x05@b\x1d\x80y~b\xc7\xbfP\x99\xee\xf3\x01\x95\xd4\xbf\xba\x82\xda\x16\xc0\x13\xa8?.\x14\x82q\x1b\x0b\xab\xbfQ\x02r\xfayV\xf1\xbf\x7f\x90\x0f\x98\xf0\x91\xe2\xbf:\x1d\xb7\xa1\xd2f\xe5\xbfp\xf9G\x83\xcbO\xed?\xe8\xd7!\x8c\xbc\x9e\xe1?\xc2_o\x16\xbe\xab\xf2\xbf{\xc1\xf2T,h\xe9?d\x01bx\xdc<\xf0?\x9fl_\x90\x89\xdc\xe0\xbf\xc8&\x88\xd2s\x95\xf7?b\xfc\xebubN\xde?b\xd3\x8e\x96\xff\xc7\xe3?h\xe1|\x11{\xeb\x03@\x9en\x94\t\x81\xfd\x03@\x11\xc2\r\x1d\xcf[\x03@\x87\x976\xea\xce\xf6\xeb?d"=\xcfk\x9f\xdc\xbf\xe6\x005\xa2\x02\x9f\xe5\xbf#V\xb6\x9d\x86\x12\xf2?\xe8\xb6\xa8\xc6\xf8\xbc\xe0?\xa1\xe0\xec\xbc\xd4p\xe6\xbfxM\xcd\x9d\x0e\x0f\xd7?@d\xce;\xab]\xf2\xbf\xfa\xae\x04;\x0cX\xf3\xbf\x15\xe9\x9a\x9b\xfa)\xf2\xbf/\x82<\x98\xc3\xe8\xf2\xbf\xf4f\xb0\x92\x99\x9d\xf7?\xdc-\xe17\xc7<\xdd\xbf@\xdbM\xce\xc9\x8a\xfc\xbf\xb9R\nr\xcd\xc9\xe2?C\x0c\x91\\Ef\xf9\xbf\x83\xb9q\xf2\xfb\x18\xf2\xbf\x03^\x87\\\xdcf\xf7\xbf\x1d[\x03\xea\x17\x8b\xfa?O"\x02\xc5\x85w\xd1?2\xab\x1aI\x1bu\xe0\xbfW\x05\xca\xa2\x1d\xb3\xed?\x06Z\xb5\xfb\x9e\xa7\xfd?l\xd8\x9ek\xe15\xf5?a\x93\x8bf\xfbA\x05@\xa9\x92\xd3\x9c\xe8\xa0\xfb?\xc3`YO_\'\xe9?\x05j\xb1\',\x7f\xfe?\xda\x0f\x87XG\x87\xe1?\xb3\x8f\x88p\x13\xbe\xf4?\xfc\x8fB]\x01\xab\xb6?\xe3\x9b<\x0e\xbc\xd7\xf3? A\xf0Y\xe4\xca\xf1\xbf\x93nQaS\xdb\xef?\xf5h\x85\xd4\xe2\xac\xec?[\xf9S\xebH3\xde\xbf\x93.4\xc7\xe4\x00\xff?\xd6i\xe2\xff\xdc,\xf3\xbf\x11\xa0(H\xdau\xec\xbf\xf1\xef\xe8P\x0bC\xfd\xbf[\xb5\xec\xfc\xa0A\t\xc0\xf2\xd7_^q\x02\xfe\xbf\x06\x81\xc03\x93\x1f\xeb\xbfoF\xc4\x99R\xf0\xf2?\xb2o6.\x85\xbe\xd0\xbfX\xd8\xdc`\xfb5\xbb?=\x8ch[j\x02\xa2?\x8dH\xf551\x17\xeb?\x11\x1e\xcdq\x9b\xb5\xfb?\x96\x0cn\xbd/\xe0\xfc?u5><\xdcb\xd4?\x1e]\xfa\xb5]\r\t@/\xbb\xb4\xd3\xba\xaa\xf5?\xb5\xbc\xb4\x1a\\`\xfe?y\xbd\x8b\xb2C\xec\xf9?\xf9\\\xfe\xbf\x917\xd0?\xa1[n\x1f\xbd;\xff?-\x9d\xa9\xfeSJ\x9b?\x84\xa2_\xd58p\xba?S\xe2\xfd\xc2\x88*\xea?\xce\x7f`\xf7\x8d\xe3\xf9?L(wE\x12{\xcf\xbf\xd9\x8a\x03Y\xe8\x9b\xed?\xe8Z<,\xa8\xbb\xdf\xbf\xd8\x89^\x1c\x1e\xaf\xe6\xbf\xf4\xd3\xd5\xce\xedM\xf2?\xd6\xbaO!p\n\xf9\xbf\xb0\xcdG\x8a\xa1\xe3\x00\xc0\xc4\xdc[\x84\xe6\xf6\xed\xbf\x97\xb0?\x91\xa3\x80\xee?E5\x06>[\xa7\xb6?\xf6l\x12\x99\xc4$\xc9\xbf^MY\x19PG\xf2?k\x1f \x83D\x89\xe6\xbfe\xf9\xa6\x10\xf2\t\xe9?\xa3\xa4\xba\xd9\xa8\xb6\xfc\xbf\x18Q\x9d\x9a\xbf\xdc\xfa?\xc6_?\x11./\xf5\xbfN(,ek\xaf\xca?\xc9N\xd3(Ps\x06@\xddAh.$\x94\xfe?\xec{\xcc,q\xb9\xe0?t\x8b\xde\x085\x19\x06@.\xcb]\xef\xdfD\xd2\xbf\x94\xae\xd9do\\\xf0?\x0b\x14~\xa5\xbb\xd2\xe1\xbf\xdfD/3^\'\x96?:\xae\xd2z{\x13\xfa?\x11\x90\xf9\x9a\x9d\xf3\xe0?\xbd\x89\xde:_A\xdf?d\x06\xd1\xc0\xe4\xbf\xc4\xbf\xfd\xb1m)\xcd\x11\xdb\xbf\xa2s\xba9\xd3\x1d\xe3?:;\xa5D\xe2\x15\x06\xc0\x98|\xad\xae\x1f\xfc\xe8\xbf\xa7;V\xb1\xea\x8c\xfe\xbf\xefd\x98\xe3\x87}\xe3?\n)\x01"w\x8d\xa4?8\xac\xfa\x0b\xd8\xa9\xee?0\x9d\xe8\xb8Y\t\xfc\xbf\x1e?g\xc2\xf2\x9f\xa5?#\xc0\xed\x87I\x05\xf8?s]\xaew\x8a\xbe\xc2\xbf\xf7;\x87\xd8l\xd0\xe3?\xfd\xd2\rW\xb4\xf1\x00@\xe5\xd4\xa4\xc1\x15\r\xf0?;#k\x9d\xe2\x03\xfd?\x0f\x96\xfd)\xc9\x87\x00@\xd3K2\xe7\xa2\xac\xfb?\xbfp\xe6\x92\x95\xec\xf1?\x0e\nn\x89;\x87\xee\xbf\xddt\x9e\xf7\xab\x84\xed?\xb7_-_\xae\x18\xe3\xbf%Q\x0b\xa2\xdbC\xdd\xbf\x88g[jj\xb3\xe0\xbfh\x0b\x86Cc_\xda\xbft\xad\xf9\xb1\xf2\x85\xdf?gR\x9d\x04\xf2o\xa3\xbf\x08\xae\xf6]\x81\x0c\xda?$\xc1\xbdfdn\xf0?V`|:^\x92\xc9?\x9c\xc3\xa177h\xb0\xbf0\x847\xf3\x02\xcd\xf4\xbf\x87\xf2%\xa1\xbd\xd0\xf7\xbfo\x16V\xb4\x08z\xf2\xbf\x8f\x0b\x13%\xfdC\xf1\xbf\x13u\xf5|\xe3\xae\xe5\xbfk\xd0Uc\xfc<\xe6?\x84\x06k\xc6f:\x04\xc0a \xcd\xb1\x86\n\xd5?\xc2\n+;\xcd)\xec?\x9b\x97\xa2\xa7P(\xfe?\xedI4\x8f\xd8\xdb\xf5?\xd6\x01\x91ux8\xf4?\xb2\xd3q\xb4no\n@\xfewr`\xc3(\xd4?\xc8\x8f\xa2\'\xd8\xec\xf4?\xa6\xe3\x97\x7f\xf2r\xe8\xbfG-(B\x9d\xc3\xde\xbf\xe4\xfe\xb9v3|\xfa\xbf\xfd\xa5\xf9\xa8\x08E\xfc\xbfB\xae<\xc3\xf7K\xff\xbf\xad0z2\xc7@\xf4?\xaf\x7f2(z\xa4\xcb?X\x85\xa2\x89\xe5\xf0\x02@\xec\xaa\xe75\xdc1\xc2\xbf\x8eZk\xdd\x1d\xce\xd7?h\xf7@/|\x1f\xea?M\xbb1\xf3\xc4[\xf5?fR\nH\xe4 \x01\xc0\x8a\x1e+Z\x9d\x9e\x02\xc0j`\xa2!=\x94\xf8\xbf"0~\xdd\xe4\xc9\xf1\xbfcxt\xb7\xa7"\xd4\xbf\xed\xc1\xf5\x10\x0b\xdb\xb5?\xb5\x85\x9b\xd19?\xf9\xbfq\x17\x96\xeb\x84u\xf6\xbf\xfa3\xf4\xe8\xc3\x8b\xdd\xbf\x1b\xe4\xc5\x07\xc7$\x03@\xc8\x0b\xae\xf5\xa8}\xf8?\xdaqD\x9cVP\xe8?\xc0^\x00\xf0\xb9\xcd\xfd?\xf9\xc8kx\xe2\xd7\xf8?wN\x07\x14\x03X\xf3?\x03\x1dD\xc5\x7f\x0b\xe4\xbf\xfc\xec\xc8\x1cL\xfe\xd2\xbf\x96\x8a\x11g\xb2\xec\xff\xbf\xdf\x12b\x96\x80\x07\n\xc0s\x8f\xb4\xb2\x81\xdc\x0c\xc0\x8b\xc2L\xda\xa8\xc4\x0f\xc0\xb7\xf6\xe28\x02\xfa\x01\xc0\x9c\xf1\r&\t\n\xe3?6\xff\x08\xb7v\xe3\xf0?\xde\xc4d5k\xa5\xf9?\x17\x85\xa8_y\xaa\xee?J\x96U\xb0\xd6X\xf6?\x08\xf4\x1a\xf4\xa8\xa2\xf2\xbf\xdf\xa4\xa5\'\x1b\xcd\xf1\xbf\xea\'1\xb9Gn\xf0\xbf\xd4|eL\x7f.\xf9\xbfPz\xaapM\xdf\xf9\xbfr\xa8J\xf8\x89\x0c\xc1?\xea^\na\xf6Z\xf8\xbf\x8a\x05\xf2\xcd\x0e\x0f\xe0?\x9a,\xb4\xd1o\xe9\xea?\x9b\x96\xachsh\xe7\xbf\r\xfb\xdc\x07\xa0\x80\xf3?\x0c\x9d\x97A\xe0T\xe7?\x1d\xf5\xce4\xe8F\xd2?O.\xba\xbbH\xe0\xfd?\xea7=\x84\xc4\xb1\x03@\xa6\xd4\xfaF\xf1\xc8\n@\x89\x00\x176\xb5\x83\x00@7\xa3 \x94\xb7\xc5\xf1\xbf\x01>\xd2\xa6\x0e]\xf5\xbf\xceQ\xef\x89\xbe\xb8\x01\xc0\x94k1=\xeb8\x0e\xc0\xe9\x84/\xea\xbf\x9f\xf0\xbf@\xf8\x06\xde|\xb5\x0b\xc0\xef\xcc\xc2\xaa\x12\r\xee\xbf\xb3\xc9\xfb\xda\xe2\xd0\xef\xbf\x83\xab\xa2Q&\x9d\xf0\xbf\xe1\x83\xd7K\x8a\xd0\xf5?c\xfa_?\xa3Y\xfe?\xe0n\xd5\xd0\x8cv\xb8\xbf8e\xaer\xeaN\xe4?\'F\x95U\xd9k\xe6\xbf\r\x14!\xfa\xa1\xd0\xf1?\xea\x9b\xce\xa7\xf91\xe3\xbf\xa1]\x96B{a\xfb?\x06)\x15\xda\xe5\xf1\xea?Q\xf5-5iq\xe6?H\x9cI\x8fb\xe2\xe1?\xd6a0\xb9\xfe\x0b\xf6?\xf4\x87\xf8\xef\xde>\xf0?)\x93\xe3"\x11\xf3\xda\xbf\\\xb2\xea\xbb\xdd\xd3\xdd?=0\x05X\xd8S\xcb\xbf|e\xe7\x13\x1a\xff\x03@\xa1\xfe\t\x9a\x9b\x19\x01@3\xe8\xc5\'I2\xfb?\x98\xe3\xd8\xa5\x9cP\xe2\xbf\x80=(\x18+\xf8\xbc?i\x0b\xf4}\xc7\xff\xd5?\xf2\xef\x82\xec\x92n\xf5\xbf\x9a\x023\xc5\xd7G\x06\xc0I\n\xce\x90^\x15\xed\xbf\xc4\xd7\xa6l\xb29\x03\xc0\xd6\x93Sb\xbd\x8a\xfb\xbf>BA\xcf,\xe0\xe8\xbf\xbcM{\xce!\x81\x06@2\x01Um?\xc0\xe4\xbf\xce\x07Y9\xd1\x18\xf4?\x80\xcd8\xb4\x92m\xc7\xbfJ\xf8I)\xb4\x1d\xfd?H\xab?\xcb_\xb5\xc2?\x18\xafz\xca\x9b\xce\xe9\xbf\xd8\xa5x\x1b\x89\x93\xcb?\xffm:\xd6\xa0\xfc\xea\xbf$r\n\xbe\xf3<\xe2?&\xc3\x19\xcf\x8c\x86\xf8?\x8e\xe7\x16,\t>\xeb?0\xb5\xe6\x12\xb2p\xe1\xbf\x18\x16\xe2\xcf\xb0\x0e\xf4?\x81h\x85h@_\xf2?p#\x02\xd6\xb7A\xfd?\xd8\x91\x97\xf0\x8fA\xec?\xf8E\x83\xd6\x81\xe4\x01@\xc1\xd8\xfb\xe4\xf7\x8a\xf4?g\xd9^\x0e\x1c6\xe9?,\xaa\xa7\xfab<\x00@B\x83\xe2\xfc\xfe\x06\xe2?-\x87\xb1\xf6R\x17\xe3\xbf\xa0j\xdc\x12\x06\xf2\xf0\xbf\x8fN\xb8\x0f\xa0\x85\xd6\xbf\x9a5\xd0\xb6\xc9G\xe8\xbfw\xd7\x92\x85n^\xda?\xe7\x8fV5\x1f\xf9\xf5?\x0f\xb8f\xdf\x03\x1a\xfa?\x0f\xdf\x9d\x16bB\x05@*\xcb\x84\x89\x8b\xe6\xee?\xc0\xb8 \xbdr\x8a\xea?\xfd\x1dn\xf4\xd9\x9e\xe9\xbf\xa3\x9foF\xfcS\xee?3)O}\xe5\t\xc6\xbf\xa9\xaa\xf7n\x7f\xd5\xbe\xbfB\x9c\xa0\x8cAZ\xd2\xbf0\xaaS\x06PV\xed\xbf\xe4\xa4I\xa20\x00\xb1\xbfhu[\xb5\x99\x01\xd9\xbfD\xa2\x9b\xd6\x10v\xe2\xbfv\x04-\xe0\xc3\xb5\xf8\xbf\xc0[0F\x80\x04\xc4\xbfu\xe4hA\x1d\xa9\x99\xbf*\x00_\xaa\xdd\xce\xcb?\x07\x8b\xec\x07\xf0\xe8\xf2?\xf5W\xa4\xe2 \xb4\xf7?Z\x8e>\xda\xa4\xea\xfe?\x08Vz\x9bb[\xfc?\xdcJ\x8fo\xd6\xfd\xf4\xbf9\x8e\xa3\xa2\x1b\x88\xfc\xbf\x9a$L\x9e\xee\xe6\xf3\xbf\xe5,3\xc3\xdf\x04\xe9\xbf\\\x16\xbd;\x86/\xbb?\x02\x98Lb\x1b%\xe6?\x97\xb6\xaby\xd8\x83\x0e@\xc8<\xcfD\xbd\xf2\x0c@\x15\x81b\x9d\xdb\x00\xf0?\xc7G\xd0|\xc7\x0c\xe0?M@\x9dU\xa6R\xe7\xbf\xcc\xb3\x0eC\x16F\xe8?\xe04\xf7\rP.\xe1?\x97\x0e\n\xe4\x150\xd3\xbf\x87\xd9[1\n\xbf\xa1\xbf\xe82\x06\xa3P\xad\xfb?\x00\x97H\xe9\xb1#\xc0\xbf\x06zD\x90\xc2a\xe7\xbf\xc5^?\xdc\x07\x9e\xe4\xbf\x17\x94\xf6y\xf1\x1c\x9b?O\x0b\xe7I\xe6\x13\xd5\xbf\x90\x00E\x114\xd9\xba?\xb1\xb7)N\xa5\x1a\x8d\xbf\xc8\x9c)\x13`{\xfe?\xd8y\xf90\x0fX\x03@\xc4*`!#j\xf4?A\x97\xe1\x80\xab\x13\xeb?\xac\xbb\x80\x80<\xf2\xf8?\xf3\x82\xf5\x87\xcb\x0f\xb2?wj\xe3\x8c\xfe\xf6\xd7\xbf\xc1\x08U\xc9(\xe5\xed\xbf\x8b/\x1e<\xd3\x95\xfc\xbfT\x80D\xd2#w\xf1?\xa2\xdcZ`\xe3\x8a\x05@Z"\xe2\xb3sa\x08@\xad\xe3\xfat\x87\xfd\xf9?\xd4\x12\x08\xa5F \x06@/\xa8\xc8\x1a\x15\xa2\xb3\xbf\t/Y\xb00G\xdc\xbf\xc4\xd95u\xba\x0c\x04@-\x9e{\x9b\x14?\xf0?\x05S\x91\t\x7f\xc6\xfe?\xb2\xc7\xf2Q\xa8\xcf\xdc?\xb3<\xa4D#\xb8\xd6\xbf$c\xb1*\x19\x7f\xe8\xbf\x91\xf7\x0f\x16\xbaM\xfe?\x08\x18\x7f\xf7o&\xf4?\xfd\x96;\x8e\x1b8\xec?}\xda\xe1\xe5\x05\xaa\xe7?\r\xa4V0\xe72\xf0?\x95!\xe2k\x8b\xe2\xf1?\xf8\xcb\xf6\x93)3\xf3?\xdd\xc9X=\xe3\xcf\xf6?\x05\xae\x97\xa2\xeet\xf8?WNe\x88\xc8C\xee?\xa9Vy\x91\xba\x84\xe3?&/\r\x83h2\x01@\x08U\xf4\x14H\xfe\xf1?\xdfQ?\xc4\x91\x03\xdd?C\xd4@\xd0)8\xcf?T\x81\xe8\xb5L#\xca\xbf\xa9wB\x9d\x9bi\xfb?\xfe\xe3I\'\xd5[\x06@\xf5\xe66j\xad*\xf1?\x80\xfe\x90!\x9c\xf7\xf1?f=fb\x8b\xba\x00@\xb1\x1d\x94\x7f\x9a\x10\xe8?\x9f\xcf\xff\x9d\xf3\xef\xd6\xbf\xc3\\\xee1\x92\xde\xb6?\xe8Q\xe7\x8d\xbe\x8a\x05\xc0\x80\xacC"\x08\xc7\xfd\xbff\xb2\xfe\x88f\x13\xf3?\x01;\xf9\xb9W\x17\xf8\xbf_\xa4\x99\x97\x95\xff\xd1?<\x88\x0ffm\x01\xf2?_\xf2\xa5\x08\x96\x07\x00@Y\xf3\xd5;x\x0b\xc5?C\x95\xfb\xd21\xaf\xe0\xbf\x15\xd5\x12|\xf4t\xde?\xee"\xc9\x12\x8a\x08\xc1?\xc3\xc9\x80\x9dZ\xcf\xf3\xbff\x08l[r\x9e\xa7?\x0f\x08o\x11\xb5\x1f\xf8?!\x04\x16u\xc3\x1b\x02@\xbe\xb0\xfeS\xa6\xe4\xe9\xbfR\x80]\xd2\xba\xbd\xb2?\xa9\xb1\x89\x14\x9c6\xf3\xbf$\x8b\xcdLO\xac\xe0?\x19\xb0\xdaj\x0f\xcd\xf3?\xdeCd\x958\x82\xdd\xbf\xc7\\\xb4\xb6\xb2!\xe0?2\xe0\x7fy\xf5\xab\xed?U\x82\x03 \xe3:\xbf?\xea\r\xa6*\xf1\xbb\xe2?\x93t\xd2\x85X\xef\xcf?F\x1d\xf5a\xca\xe2\xa9\xbf\xae\xaa\xb6\xc0z\xbe\xf4\xbfd\x1b\xea_1\xac\xf4\xbf\xa7s\xba\x82p\x03\xeb\xbf\xf5\xbc\x0bWv\xf0\xd0?\xaf\xbc8Z\xe3\xe9\xd9\xbf95[r\xa5\x89\xdb?4\xc9m\xdb\xc6\xf5\xf0?V\xf6\x15\xca\xffv\xdc\xbf\x9f\x1fs\x9d\x88\xb6\xd4?\x9d\xdb\xec\x82\xa70\x03\xc0e=\xed\xb8 G\xc4?\xf0\x83\x9aP\xc6#\xc9\xbf\xe68\xda\x86>=\xd2\xbf\xb5F\x7f$1Y\x05\xc0tg\xc2\xe5\xca\xdd\xe5\xbf\xe3\x0f\x16\x01\x9fX\xe0?\x16\x964\x8d\xe8\xd2\xd3?\xea\x85F\xd9\xf8&\x01\xc0\xb2\xf3\x81\xd3i}\xda\xbf\xa9\x9eys\xe8,\x9f?\xed\x96\x06X\x13\xd2\xc2?\xcb\xd0O4(\x9f\xda?u\xe0\xb3J\x13.\xd3?\x90F#@\xc4e\xfc?\r?J-\xaa\x0b\xdc?I|\xd2cNy\xfb?w\xd0\xc7\x0e\x1f,\x02@=u\xb61Z\xef\xf6?\xa9}~?\x859\xf9\xbf\xd2XV\x81\x1f\x8f\x02@\x87zz\xfc\xb3\x02\xd7\xbf2n!+E\xc3\xc2?,<\xcat]"\xe9\xbf\x82?\xfa\xfb\xd3f\xf2\xbf\xdcJ\xf4zh\x8a\xed\xbf/]\xbc.\xe7\x1c\xff?\xf1\x9cI\xee\x1cF\xf4\xbfe=\xcb\x08\xb2s\xf7?\xac&\x96\x06\x1e\x9d\xf8\xbfH\x10\x9fqcD\xe2?pQ\x8f\xd3\xbfV\xfa\xbf\xa3F;\x7f\xcb/\xf0\xbf\xeb\x83\x94\x86l\xe9\xe3\xbf\x0cO\xba\x81\x01e\xe8\xbf\nJb\xea\x07*\xd6?H;\xb8\r\x0f(\xe6?\xb1d\x81z\xc3\x9d\xe4\xbfNU\x84|\xcbR\xd9??&\x0e\xa1W`\xe5?i\xf2\xef\xb5D \xe5?\xf2\x1cg1q\xef\xed\xbf|k.u\xedy\xec\xbf\x88@|4\x03{\xf8?\xda\x16~\xbd\x99\x02\xf6?{\xf0\xd1\xe4\xcc\n\xda?\x1as,\xdb\xe3j\xf1?|#\xb8c\x01R\xe4?\x8c\xd29\x08\xc9%\xed?\xd3T\x89U\xc2\xfb\xfc\xbfJ\xd9u\xf5\xe3e\xc3\xbfE\xbf\x98\x93\xd6F\xd8\xbf\xab~\x8d1\t\xfc\xcc\xbfa\x17\x9e\x97\x9e\x9f\xd9\xbf\x9c\xc8\xcd\x0f\xe5m\xf4?\x89\xfdE\xb2\xe2O\xcf\xbfu\xfe\x87~\x9d\x88\xf1?b\xf4\xa4;*\xd3\xf3?\x16\x88\xff\xe9\xc8\xb1\xe3\xbf\x99\xed\xbb\xf9\xfb\x8f\xde?\x0b\xf1\xcamGa\xe4\xbf\x10U\xc1\xb9\x91e\xf3\xbf\x7f\x10\x03\xcaz\xba\xeb\xbfs\xf6\x10\x087V\x03@\xaffr\xd9\xfa\xc2\xe4\xbf\x11\x04Z\xc3\xddr\xee?\xd5\t\xb9\xad\x93\x01\xc8?:\xe4\x1cd\xba\xcb\x03@\xe0\x035\xfa\x83]\xd3\xbfd@\x07\x9c\xae`\xed?*\x91\x9c\xac\xe4\x04\xdb?\x08\xa8\x988:\x8e\xf2?\xe8\xcf\x9e4\xc5?\xf4?\xdd\xd6\xf1\x98\x1c\xd8\xb6?\xa6\xe5@\xb6f\xa9\x07@ff"\xc9\xe6\x92\xb7\xbfi\x0b\xff8!\xfa\xfc\xbfA\xe6\xcc3T\x88\xe3?\xc0UO\\\xd3\xc6\xc9\xbf\xf4\x89\x9cQ\xf3<\xb8?\xd3\x03\xd4\x0bb\x80\xed\xbf~\xb3\xdf\x81\x89\x00\xd3?\x93To\x18\xc7S\xec\xbf4@\x96\xfca\xa5\xf6?\xc7\xfb\x9a-\x9f\xdc\xe2?b\xa2X\x05!/\xf0\xbf\x08\xa3y]\xaa\x03\xd3?m"]\xdf\xa4\x07\xeb\xbfE"\x90\xb3\x16E\xb6\xbf`\xc1\xfd.t\xd2\xc7\xbf\xaf\xf9\xb0\xffV\x0c\xf7?\xd2\t\nT{\x08\xee\xbf\xbd\x9f\x13\x93H\xba\xd6? \xd2\x88\x82\x14\xaa\xf1?\x88\xab\xc0Z\xdbY\x01@\xbd\xb4.\x82\xa87\xe0?\xb7\xec[\xbd06\xe0\xbf\xbe\xff\x03\xb4\x1fM\x00\xc05\xdf!\xc4G\xe2\xe5?6h\xe3\xc6\xbf;\xb0\xbfY\xd9\xcf\xe4\x82\x98\xec?"\xa2\x03b\xaf\xc1\xe0\xbf\xd7;\x9b\x1auZ\xe7\xbf\x90!\xa7\x8c\x8c\xa3\xe2?\xdc\xb0\x95\xfc\xfb\x1c\xdc?\xfd\xaf\xf9W\x89\x0e\xfb?\xaa\x1d\xdb\xa4vF\x96\xbf-\xd7^\xcdKj\xe7?\xd8\x00U~\xf2\xd3L\xdc\xbf\xe6M\x1b\xd8\x0b\x1a\xec?\xe7*\xee\x81\x91\xf4\xf1?\xcc\xeaZ\xe2\x9e\xda\xbf?\xaav\xbc\x1eJ8\xca\xbf\x97\xe9\xcd\xe3\xaaS[\xbf\xb6\xb5\xa4\x003)\xd8?\xe7\xc4\xc3\xc2\xfd*\xe6?U_\x93_Z\x9b\x01@x\xab\xa2\x94\x93W\xf7?\x8c\x9f\x07\xc8j\xb0\xf9?\x8f\x9b\xd1( \xe4\x02@\xaaJ\x91\xb1`\xba\xc5?u\x84!C\xe4*\xf1?\xce4\x9e\xd0\x91E\xa6?MWv\xc2\xe8\r\xe9\xbf\x8e\xbf\x00\xf4R\xe6\t@\x07\xd7\xbf\xa5\x98\xcb\xd1?\'[\xf6\x15B\x88\xef\xbf\x8a\x7fUi\xa0\x9f\xdd\xbfM\xe2\x1f\x80\x91\x0f\xf7?\xa0\xfa\xcbOy\xc0\xf1\xbf>\x12}\x00\xc9\xa0\xce?~7\x1d\x7f\xc9<\xee\xbf\xdd\x15\xf2\x0f~\xa7\xef\xbft\x91.\xfb\xba\x83\xc8\xbf\xf3\x07/\x02Qd\xdc?\xca4\xe8i\x08[\xbd?S\x8fX\xeb"A\xea?\x8f#G\x19w\xfc\xf0\xbf\x00\xa0J\xa3\x92\xa2\xe4?o\xac\xa9\nx\xd7\xf9?\xe8y\'\xbdz`\x01@\xeb\x039\xd9\x80`\xf0?\x81\xe7\x92\x07\xfc\x86\x06@J\xee\xc0Zc\xbc\x08@\xd97<\xf2\xb9\xb3\xe5?\xf4\xbbQ\x16+\xbd\xf9?\xb8j5\xcf\x07\x10\xf1?\x17\xfb\x8e\x92*\xe6\xd4?\x9c\xb8\xe0\xd3o<\xe9?{\'b\x86\x1b\x1b\x90?\x15\xfck\xe8\xea\xac\xf0\xbfj\x1d\xb2\xceT\x95\x01\xc0\xad\xbcU\x01\xbb\xce\xb3?\xf78$\xf5\xed\x8f\xe9\xbf\x0bA\xf6V\xdc?\xb8\x97\x0ep\x83U\xec\xbf\xf4\xe6\xc0\xbdH\xb9\xcd\xbfO;\x98m\xfb\x89\x93\xbf3\xe2\x84\xe0\x0b}\xf8?\x9e\xfc\xedT\x17\xbc\xb4\xbf}\xb0\xe4_\xa9*\xee?"\x88\xb1\xa7\xf7\x1d\xc5?JiO\xb2\xb0d\xf0\xbfE$`N\\\x95\xe0\xbf\xad\x9d\x01P\x80\xb2\xb9?\xb2Fa\x8b2\x85\xd3\xbf\xcb\xa2\x80<\x86\xc8\xf0?\x84\xbcmF\x7f\x11\xd3?\x87zl&\x18\xb7\xb5\xbf\xfe\xce6\xf0l<\xe2?\xc9&.HV7\xd3?\x8a8\x9b\x0e\xfc\x90\xa9?\xe7\xd6\xab\x8f\x84\xc4\xbc\xbf\xfe\x17\xbf\x13\x15q\xed?\x03J+fWC\xcc?\x1a4\xc1\x07\xb5\xa1\xf6\xbf\x7f:\xb9\x8d\xf2\xd3\xd8?\x17\xd9R\xe3\xf1E\xda\xbfi\xd2\xf4\x8c\xcf\xa5\xf5?\x88\xc8\xb2\xe5\x12\xf8\xf5\xbf\x8ei_W\x17w\xe7\xbfm\xfct\x90I\xf6\xee\xbfJ\xab\xd2Y\x18\xf3\xd8\xbf\xa5q\x03\xc0#R\xce\xbf\x0f\r 0\x9eD\xed\xbf\x98\xfcl\x04oT\xa6?So\xd2\xd4\xa1\xaa\xcc\xbf\xc92\x08A\xe3\x0b\xd3\xbf\xe4\xa9\xc5)\x90t\xd2?\x92\x04zP\x9e:\xd9?\x8b\xe2\xee\xd8\xb7\xc1\x00@N\xa4\xb3{\x89\xb0\xae?/\xa6\xd39],\xe6?\xe3x\xf0\xc4\xdb\xc1\xeb?\xe7\x85\x94\xda]\xd9\xda\xbf\x17\x88=\xc6\xe2\x84\xee?m\x0c\xe8\x8e\x1d\x83\xce?*-\xd0\xd1\x07\x86\xfd\xbf\x06#\x04*\xf6\xe9\xc1?\x8dV\x01\x15+\x07\xf8\xbf\x17\xfd\xa0:#\x82\xe0\xbfd\xf9\x8ev\xde*\xf5?\xa6\xf4\xe0\x8f\xd1\x81\xf0\xbf\x15\xdf\x9e\xf4\x8b\x96\xd9\xbfpJe\xc1\x1fk\xe8?y\x06\xfe\xaa\xd6\xea\xe0\xbf\xfdf\xb8\xde\xd4\x00\xf7\xbf\xd2\xe4\xb9\x08\xb6r\xe7\xbf6?\xe7\x14\xbdT\xd4\xbf\xf3\x8az\xec\xf0\x04\xf5\xbf\'\x9bB\xb3\x19\xdd\xf4?\x19n\xdf|\xfe/\xd4?\xdd_\x83\xde\xb3\xa7\xf6\xbfj*]Z\xe1\xe6\xdb\xbfgy\x0f\xd6[\xf5\xe3\xbf`\x88\xd9\x81\x19\xdd\x02\xc0[R\xb2\x04LG\xe6\xbf:%d\xf0\xcb\x02\xde\xbf\xcf50~\xc5:\xcf?+\xb63\x1cz+\xe7\xbf\xd4\x86]\xf9\xd5\xa6\xf1?\xb1\xf9\x1bp\xb7x\xb8\xbf\xc6\x8d\x82\x0e\xda\x86\xf8?Q\xae\xf7\x10\x17j\x00@N\x05\x99\xa1-\x87\xf2?\x99\xaf\xf8\x15\xae4\xf7\xbf\xfa\x9eW\x05>\xbc\xe7?\xb0\xa3\xac\x8b\rQ\xcd\xbf\xff[Q"\x05\x8e\xd2\xbfK\xf5$\x10\r5\xe2\xbf~\x16\xddI\x00\xf6\xea?\xe1\xc6\xbb:\xaa\xcb\xdd?\x057\x9f\xaex\xd4\xc7?\xdc\x8d/\xd4\\\x82\xe4?\xfc\xa7\xab\x85fm\xfb?m\xe8\xc1\x1c\xc4L\xf2\xbf\x9c\xf42\xa2\\\xa7\xf0\xbfF\xf2\xc3\x07\'\xfap\xbf>\xe0\x11O]`\xc5?@\xe6\x00\xcc\xaa<\xda?\x14of\x84\x11\x8a\xb6?\x99H\x92\xb1W\x8f\xda?\x1d\xa7")\xbe\\\xeb\xbfu\x99h\x08\x81m\xe2?\xf6+%8\x87\x0f\xca?\x82\xd6NA\xb3l\xd8\xbf\xe2A\x8e:.\xfb\xd1\xbf\xc9\xeck\xe5+;\xef?\xfc\xdcL\xc7x\xbf\xf7?\x94\x93.x~\xc6\xd8\xbf\xe0\x84\xee\xcf\xafe\xf7\xbfNl\xa4C\xec\xe9\xd3?/e\xd2\x84\xa4\xfa\xf6?\xd8\t\xb9\x1f\xff\xb8\xf6?\xdb\xde\t(\xb2\xcb\xd8?rA\x01\xd0\xab\x01\xd6?\xb8^\xa4C\x8e\xba\x00\xc0\xbco\xbf\xf8\xd5&\xfe\xbf\xc3\xb3\xe0&\xf3\xb5\xb1\xbf\xce\x17U\xabL\x10\x06@\xfb\xc2\xd6q\xa4-\x00@p\xb2Y\xf7\x88\x99\xf2?\xd6R\xc98\xbbT\xeb\xbf\xe2yH1l\xfa\n\x9c\xf3?\xdc@S\n\xfc\x12\x00@\xb4X?\x82\xbd\x91\xec?S%\xbb\x9bC\xea\xfd?\t\xea\xb0\x97\xd8\xaf\xe5?>[\xdb\x8f4\xa8\x05@\xa65\\/\xb3t\xf1?C\xc3D\xea\x80\xeb\xd6?\x0e1{]\x10\xff\xe1?\x9e\xf5l)\xbe\xe7\xfc\xbf \xdb*\xa2\xd4\xf6\xf6\xbf\xb3\x87\x85\xa4w\x89\xe5?\x1d\xb9\xfc\xa3\xdc\x01\xf0?\xf0\xfa\xd9zYd\xe7?\x0f\xb8de\xe1\x06\xf8\xbf~\xfa9\x89\x16;p?c8-?\xb4{\xea\xbf\xff\xb4ls:\x0c\xd0?|+\x1e\xbdQ\xce\xe5?\xb9\x1ef\xfa\xa1\r\xf2?5M\x93P\xa7e\xec\xbf"\x8efP\xed/\xf5\xbf\xc3\xa8\xfb\xad\x87\x9f\xfa\xbf\xa2T\x8a\xf9\x885\xcd? \xe0,#\xba\x84\xfc?\x18\xbd\x02~\x87\x9f\xa1\xbf5-d\xa1\xef\xb4\xb6\xbf\xf6/\xe5\xb1Bo\xfb?\x81B\xf78\x08;\xec?.\x9c4\xaeXc\xe2\xbf\x95\xd0Jk\xaf-\xe4?\xb0\x9a\x9765\xc4\xa0?\xdaD\xd7\xdf\x91\x12\xd8?\x92\xe3\x81\x10\xeet\xf5?\xac\xf3"\xa9\x9d\xc9\xe8?\xd7\xc2-rs\x8b\xe9?A\xf1\x06\x96O;\xe6\xbff\x06)\x15\x15\xfe\xd5?\x95\'\x96s,|\xa5?\x88\x97\x1ex`\\\xbb\xbf\xeb\xd6\xedp\x9b\xcc\xca\xbf\x89h\x17\x1d"\xa2\xf4\xbf\x81\xf9(\x89_\x19\xd4?_N\xad\xd0vO\xd6?\xbf\x91\x13\x87q\xc5\xf2\xbf\xbd\'epS9\xc8?\x8f\xb6F\xf7\x05t\x00\xc02\x0e)\xd9\xc5\xa2\xf4?\xf9<\xc3\x13n\x9e\xf4?W\xe0a\x81\xff\xf5\xf0\xbfo\xa0\x80\xee\'S\xf9?@\xf1\xc42\xc6o\xf0\xbfg.\x14\xfc\x0fh\xd0\xbfI\x9aR\x12.\x0c\xd7\xbf\xc4B\x1c\xf5\xd6`\xea\xbf#\xac\x0e\x9b\xb0\xbf\xea\xbf\xa10Eq6\xfc\xf8?\x12Y\x0e\x17\\\r\xf0?<\xa1P\x94\xdd\xa7\xe8\xbf\xfd\rl_q\'\xf4\xbf7!1\x1f\x08\x9b\xed?D\xd8\x1e3\x11j\xe7?\x0c\x18\xab;\x8a(\xcc\xbfPA\x9760\xb7\xf5?\x82\x11\xf2P\x081\xdb\xbf\x03\x8d\xea\xe0\x83\xba\xe1\xbf|\xfd\xca\xc1O\x8b\xf7?\xb8\xf6p`\x94x\xda\xbf\xb6\xe5f]\x07\xd3\xfa\xbf\x9e\xd8\x01\xafsJ\xf3\xbf2S\xaa\x95|Ia?i\x15$\x92\xf1\xf7\xe1\xbfdW!\xef\x83\xec\xef?\x9e\xdb3\x8e\x02e\xcf?\x05\x9f|\t\x0f\xf2\xf4\xbf-\xa6qd\x8e\x04\xe2?Z\xcfj\x17p<\xdf?b\xcd\x7f\x9bnY\xe1\xbfQ\x16j\x9eG^\xc4?L\xe2\xcf\x15\xe9P\x9a\xbf\xfay\xb4\xfa\xc2:\xca\xbf?\x04\x1bK\xa4\xba\xd4? \x1cY\x1bS\x92\xfc?TfD\xfde\xcc\xf2?\xf2\xd0\x04p\xb0\x95\xd1?n\x13:?%>\xf9\xbf\x11\xf9\x84\x99\xe3h\xf0?\xa2\x96\xa9\xb7da\xf5?(\xf9\xf1\xbb\x9aw\x00\xc0x/\x1a\xfb\xe0t\xfc?T\xddg\x82\x99\xac\xec?\'\xed94\xf3<\xe4\xbf\xc6\xbf\xf8:\'\xf5\x00@\xa3jy+\x9fw\xd3?W\xf9\xe6\x9c#\xe0\xe3?\x97\n\xa0\x839A\x9d\xbf\x84H\xc0\xab\x05\x1d\xe6\xbf\xf8\xfe\x85\xaa\xae\xa8\xd9\xbf\xca,\xe6\xab\t\x95\xe2\xbf\xfb\x8c\x8f\xa8\xb7\x91\xdd\xbf\x98\x1c#\xe6\xb8\xa3\xf6?S\xfeqVt\x07\x07\xc0+\x0eO\x8b\xaf\xac\x00\xc0x\xfe+\x89\tS\xe0?FD\x10\xa7\xaf&\xde?u\xd9\x17,]U\xde?yB\xcc\x8d\xc35\xde?*e\xc8\xc8\x9b\x84\xfa\xbf}\xef\xe9\x18\x18\xed\xe3\xbf\x8e\xad=\xa4\x17\xda\xc1?\xf5\xbam\xa8\x02\xc0\xe7\xbf\xd1\x15\xf0\xcaQ:\xe1?w~\xba\x97\x85\xfd\xeb\xbf`\x99:;TK\xdb\xbf\xa6\x98\xd7P\x05\x05\xe6?\xd3n/\x92~\x03\xf3\xbf\xebYs\xc0\x812\xd3\xbfo?\xf1\x93I\x08\xc5?\xcf&u8\xd5\xa5\xef\xbf\x05\x89\xa5X\xdez\xf1?\xa2f\xba\xe3k\xae\xed?x\xafAg\x81U\xe8?,\xcbT\xab\xda\xdd\xeb?\\\xcf\x87\xacIE\n@P\xa8\xaf\xf0\xf6t\x00@\x1b\xe0\xf9\xa6(\x0c\xf1?6\xf7R\xc9\tG\xe2\xbfg\xd9\x0b \xda\x0f\xdb?\x19.\xbe\xbf\xd6a\x00\xc0k\xbe\x9e\xa3\xb9\x8a\xf2?\x01\xf3sA_\xb4\xe7\xbf\xa4\x16\xa1\xc0\xb8\xc1\xf0?\xfc\x99f\x01\x13\xf4\xeb?VgQ@L3\xfa?uB\xa8\x85\xe6\x10\xda?i4\xca8\x1d\xab\xe4\xbfh\xbf\xd9\xbb\x86^\xdb\xbf\x05]!v\xb3\x1b\xfc\xbf\xfcZ*\xe9\x12\x8f\xee\xbf\x00\x1cvNI_\xf6\xbf\xc5\xef\xf1\xed\x843\xe4?z\xee\xd4\xa9H\xdf\xe8?\xd6\xc2\x90}\xc4i\xe4\xbf\x14.G\xc5 \x98\xd0\xbf`\x15\xbb\xcc!\x08\xe2\xbft\x04\x85\xa7`\x07\x02\xc0\xf2\xa9pv\xb2\xb6\xf3?\x05\xf4\x95\x19\x87\x9f\xe6?\xc9\x80`\xaf\x17#\xd1\xbf\x1c\x04\x03f\xb0=\x03@\x07\xf0\\\x90\x0e\xfb\r@\xb3k\x972\x96%\xfa?\xb1\xde\x021?\xae\xf2?\x83B2K\xbe$\x96\xbf#\x18\xf1^\xef\xe7\xe6\xbf\xfe\x96\xf9\xf8\xbf\x05\xf0?\x8c\xca\xd9-\xbcs\xe9\xbf\xd9\xfa\x16\xa92]\xd9?\xfc\x8b\xbb\x8bS\x0fz?\x99\xd9e\xc5\xee \xf7\xbfJ\xe6\xeeb9\x1f\xf0\xbfL\x89J\x19[\x00\xe8\xbfHk!(\xc3\x19\xf6\xbf]&9Qh\xa5\xfc\xbf\xd0\xben\x80\xcbm\x00\xc0\xc9`\xa1\xec{\xee\xe8\xbfJz\xa6\x95|\x05\xf5\xbfY;y\x1f$\xc1\xf6?\xb8[\x1bYC\xc4\x06@\xf7\xc2\xda\xb3\x98H\x01\xc0-w\xd1\x87.T\x0f\xc0\xbf\x0b\x8c\x18\xf1F\xf8\xbf\xb6\x00\x9c:\x92V\xf4\xbf\xe3\xec\x86\xa5p#\xe1\xbf0\xb0\xdd|d1\xce\xbfo\xc4\xce]\xe0\xe7\xe8?\xf8\x86\xcb~6\xf2\xd6\xbf !\xff\xe3&l\xf2?\xac\x8c\xcd\xe30\xca\x05@\x00\x97\xbd\xfa#\xb7\xfc?\xc6uR\x90;k\x02@\x01M\xd1\xde\xce3\xdf\xbf\x9fQ=C\xc4&\xd2\xbf\x1c\xf5+\xee\xbd\x8a\xe3\xbf\x17M\xde\xc1t\xee\xc8?Y+\x06\x8d\xde\xf0\xe0\xbf&\xaclRs\xec\xec\xbfQ\x9b\x99k\xe5\xbb\xd9?\xa1\xa3b\xdb\xc6\x99\xea\xbfz\xf5\xf8d\x98\x9e\x01\xc0$\n\xc6\n$\'\xf4\xbf\x07\x94B\x04\xe4\x94\xee\xbf\x9d\xe6\xc7\xd2\xdd\x9e\x91?\xad\xb0:\xa5\x1d\x08\xe6\xbf\xe9|\xdc\x98\xe7\x0e\xed?\x00\r\xbe\x96\xa5q\x05@\xdf\\\xda/\xaeT\xf2?@\xebf=\x03\xb6\xfd\xbfc\x94\xa1\x0f!\x0c\x07\xc0F\xf2%V\xad\xfd\x0b\xc0\xb7\'vi\x05\x8a\x00\xc0\xb0&^:Z\xba\xea\xbf\x99 \x8f\x99h#\xf0\xbf\xceg\x04\xa5\xd4\xb7\xed?\x02\xd4\x96\xe7\xce\x8f\xd2?z%\x03\x88\xd8\xbd\x06@n}\x0b\x81\x83\xac\x10@e$\xcb\xbf\xea\x7f\xf6?\xa8\xfb\xfe\x96\x94L\xe4?\x97\xe3\xe2\xab\'\x84\xe4\xbf\x97\xabF*\xe1=\xdb?\x92\x1d\xb6\xaa4P\xe4?\xad\xba\x04\xd5\xc4\x04\xa7\xbf\x97\x1b~\xf1\xc6\x9f\xf3?:\xaf\xfe{\xe0\x7f\x03\xc0\x84\\\t\x9e=\xc7\xf1\xbfw\xceM\n\x95Y\x00\xc0\x07+\xa7\x9ew\xd8\xf8\xbf\xbc\x90X\xa8\xb2\xda\xe0\xbf\n)\x82\xa3\xd2\xf3\xe2?\x8dF\x8f\xd6^\xb0\xf3\xbf\xe4c,>Z\x01\xde\xbf(\x1b-\x8b\xd4\x04\xf0\xbf\x13n\xc8\xa6\xba\xa2\x0e@&\xbb\xf0\xef\xf6j\xfa\xbf\x9a\xbe\xb6\x99\xb4\xfb\x01\xc0UQX<\xef\x01\xe9\xbf\xfa\x7f\xe4\n\x0c\xe2\x0e\xc0\x0f\xec\xea\xe6I\xb3\xf2\xbf6\xd9\xaaou\x89\x05\xc0X\x80\x9b7WG\x07\xc0yk\r6\xc6g\xea\xbf\x96\x15\x9b\x9e\xe5\xaf\xe2\xbf\xd3\x12\xb0\xb0\xfdm\xfc?\xed\x8e^\x8fs\xd9\x04@\xe82\xcbv\\\x89\xe0\xbfm\xb7(^\xda\x0e\xf6?\x8f\x1dr\xfb)\xbd\x04\xc0\x9bs\x90l,\xc6\xf7?\x93\x02s-\xa1S\xe0?\x97]\x10\x91\xd0\xcd\xd6\xbfd\xae\x92\xe1\xc7b\xe8?N\x9f\xbe\xab\xc4\xc7\xbe?\x80\xe4\xa3(J\x1d\xe6\xbf\xe2\x1b9(/\x08\xdd?\xd0\xbac\x94\xdb\x9b\xc7\xbf\xea\xa7*\xc2\xb0]\xfd\xbf\xc28\\<_\xb7\xe6\xbf:\x90\\9\x96f\xd7\xbfw\'L\xb9/_\xde?\x87\xf76\xca\xca\xb2\xbb?\x1c4\x8c\xc0tm\xf5?\xb5\xd6\x04?\xc9\xbb\xe5?\x9b\xe6\xe7\xba\xdd\xc3\xfa\xbf\xc4\x07\xc9+\xddQ\x0e\xc0\xc8\x00l(\xd7\x8f\xfd\xbfX\xc6\xca4\xfc\xc3\x07\xc0-\xdb9\xe8\xbf\x8a\x08\xc0\x8b\xf3o\xf2\x92\xc9\x08\xc0\xb1\x96`\x11*e\x06\xc0\xd5\xb4~\x915D\xfb\xbf\xec.A,\x99\xbe\xf1?Y/\x87\x8d\x13\xa4\x05@[\xdf]\xce%\x11\xf2\xbf\n\xa7\x84c\x13\xd6\xe3?^\xd8F\x91\x07h\xdb?M\xbaE\xbb\xafo\xf1?|!&=\x9dU\xf7?\x13a\xf8\xe7\x8f\xbf\xf1\xbf\xc1\xd2\xd1o\x14\x10\xf8\xbf\xfc\xe4\x97O\x99\x90\xc2?\xb6u\xc1\xb7\\6\x95?W\x00^\x03\x8d\x86\x02\xc0\xb0VQ36f\xd3\xbf\xd9\xe92\xb1\xd2\xe1\xe3?c7\x90\x13\xa1\xde\xf1?\xae\x19\x9f\x02\xa4\xbb\xfa\xbf\xa2T\x80\xf6\xf8\x02\xdb\xbf\xf8\xdb\xfd\x85\x10\'\x05@\xc0\x8b\x9e\x14\xea\xd2\t@f=\xbd\xe2\x8aG\xee?(M\xa2,5\xbe\xd5\xbf+\xaeG\x87>"\x05\xc0\xf7\x82\x80c\x0f\xc9\x0f\xc0okFf\xdd\xd1\x02\xc0R c\x01Y\x1d\xf6\xbf\x87\xb5j\x9b\x92\xf9\x07\xc0a\xbb7\x17\xdbl\xf2\xbf\x02q%\xdc\xc4/\xf1?\x9aGgRy\n\xe5?\xa0h0\xbc\x08\x1f\xf2?\x19\xd9\xf2\xd4\xa4R\x03@\xd2\xd8\x1cR\xe9g\xd9\xbf\xe91-\xbb\x85\x85\xe2\xbf5\xfa\x9ek\x18T\xf8\xbfK\x875_\xaf\xa2\x00@\x9a\x04\xf7\xdd\xc3\x0c\xe6?*\xfbe?\x85`\xae\xbf\xca\x7fi\xf0\x19\xa2\xd9\xbf\x01\xec\xbf\xe2T\x0f\xf7?\xa3\xa8h\xffL\xec\xc8?T\x95\xe3To\x06\xf6\xbf\x8dq\xf1(Ou\xe0\xbf6\xa1,\xc6\x8b\x7f\xd2\xbf1\xb3\x0c\x12K\xd3\xf2\xbf\xf1\xf8({\xcc)\xce?p\xff\x8e-:\x1c\xf4?\xe1\\\x9a\x10hG\xfc?&\x83J\x18PU\xeb\xbf%%\xe2\xc3\x04\xdc\x02\xc0\xa4\xd8\x16\xd1\x80\x03\xfe\xbf`\xb2e\xb18\xe9\x0b\xc07\x8d{\x0e\xb0\xa3\x04\xc0\x9e5W\xdf~\x8a\xc2\xbfQ\xbb\xdd"\x1dU\xe4?\xb1\xa9~\xe2\xbf2\xe0\xbfRn\x10\xef\x0e\xff\xe3\xbf\xd3\x0f\xf8Sx]\x05@:t5\xa4\xdb\x1c\x08@\xab(Sy\xfa\x0e\x04@Lq\x0c\x8ei\xc6\x06@\xf5\xa2.\x82q\xc3\xde?\x94\xc9\xc8[2?\xd1?\xce\xb5z\xb3\xe5\x9d\xe9\xbf\x8a\x00\xcc\x1a7B\xb1?o\xb0\xfe\xdb\x13E\xf4?\xb4\x02BGH=\x00@o\x87\x04\xdaP%\xef\xbf\x94\xa6\x1879\t\xec\xbfu\x02\x7f\xe6\x8c\x8b\xed\xbf\x16\xc4\xa4\x85Gh\xeb\xbf\x87\xca\xcf\x98mH\xf0\xbf\xe2\xd1\x19\xb1@\xda\xc0\xbf\xeaN\xe5\xf1\x13[\xf4\xbf\x01\x982\xde3\x8b\xef\xbf\xae&z\xc6\xb3\x95\xb6\xbf\xef\x04m\x9f. \xf3\xbf\x8e~|\x16\xf6:\t\xc0m\x0b\x043a\x86\xe4\xbf\x08\x07}\xc9}\xcd\xc4\xbf{\xf6\x07?\xaf\xe5\xdc\xbf\xee\xa7fs&\x9d\x01@\\XCI\xe1\x0c\xe6?\xca\xda\x0e\x197\xfa\xf2?\xd5N\xc3rD[\r@"C\xb1\xa2\xc9\xa9\x08@9z\x80\x14q\xa1\x04@\x080VN\r\xe5\xe4?\x1c\xaeB\xf5\x9d\x93\xfb?eeu\xb6\x8f{\xa1\xbf\x88\xbb\x8f\rE\xd0\xbb?\x8ab\x1ajX\x01\xf0\xbf\x9bV=xF@\xf3\xbfl\xd1Sk\r\x00\xf1?MU\x9e\xab\x8d\xe5\xb0?N=\x15]t$\xff?\x1c\xb6\x81\xe3\xe0 \x00@\xc8\x88\xf4z\xdb\xc9\xf3?\x87\x8d[iY\x1d\xfd\xbfN\xe8\x19Fo\x9f\xc3\xbfr-\xf8\xe1\xb3C\xf1?\x00\xac=G\xa2\xd1\xf6\xbf\xa7\xe1h\x97\x93\x03\xe6?yw\x90\xb6\xadNc\xbf\\Hl\x04L\x8b\xf4\xbf3\x9a\x0f\x82\xeb\xde\xd3\xbf-\xfb\x89\xbe|\xf3\xd1\xbfJ\xd6\x84\xfb\x11O\xdd?\x80\xb4\xcc\x03\xcc\x0f\xf6?\x06ZF\x1b\\\x06\xec?\xfe\x16\xd9\xf4f \xd2?\x9eT\x98\xe2\xf3i\xc8?B\xb98\x88\x10\xa1\x0c@\x1e\xf0\x97:S\xe2\t@\x95\x11\x94\xe3|\xfc\xf3?\xcf\xd5\xda<9\\\xe4\xbf\x03\x15\xaa\x1c*\xe7\x01@0\xf7\x84R\xa5\xc0\xd6?\x17\x0f\x8cb\xe1\xfd\xb2\xbf:\x92\x1b"\xe2\x8a\xe0?\x8c\x13/\x88\x8c=\xf7?\xe85\x7f\\R\xcf\xd2\xbfA\xc0B\xb6T!\x00@\xe8\xd1I\xd5n\xb9\x00@\x1e\xaa3\x8d\xe5V\x01@\x1f\x99\x9b\xcb\xae\x8a\xfc?g\x13D\xf5\xf6\x9f\x13@(\xef\xe0\x89\x89\xa9\x0b@Hm\xbc~\xc7X\x13@\x18\x0c\xa0\x8c|\xa9\x01@\xaf3\xc2\x15\xd0;\x04@\x8a\xc5\x12Z:\xdf\xf1?"\x84\x02`\xfa\xa1\xe4?\xe2\x18\x0bo\xf8\xc4\xf5?\x04\x1d\xb3\xc1\xa2\xb4\x03@(L\x99!\xf9\xa8\xf8?y\x05]\xb8\x15\x0f\xfc?\x9d0a\xc5|\xad\xe7?\xd38\xad\x8c3\x03\x08@C\x9e\xa8\xe9J6\x08@D\x02\x06\x05\xd2\xac\x0e@\xe1A\xf4\xa54~\t@$\x8c\xaa\'\rw\x02@p.\xa7fVx\xdd\xbf1\xe1\rA\x1f\x94\xea?t\xf4l\x02Yq\xfe\xbf\xe3\xacK\xe7\x85?\xcc?tY\xcc_OK\xf5?\x9c\xcc\xed\x19\x94\x98\xca\xbfZ\xc8\xba\\[J\x93?E!\xae-M}\xf0?V\'\x83>nI\x07@\xef\xe9\x10u\xf9\x02\x07@=\xf6\x92&\x1b\x19\x14@\xd8\x05\xcc\xe3\xe0\x16\x16@|\xc5\xe6\xa3\x84\xe2\x11@1\x06\nmh\xc6\x13@\xdf`<\x9aew\x0b@\x97\xdf\\\xcaX\xf7\x06@U\xbdi\xd0\xb8M\xfc?\x9dQ\xa5\xe4.\xad\xf3?Kh\x85`\xea\xe8\x07@\xde@\xb7\xe9\xb9\xed\xf9?X\xde\xa5r\xae\xfd\xfe?p\xbb\x8f\x9b\xd3@\n@Q\x8a*7\x82\x96\x07@\x9co\xf0\x02\xc0*\x01@\xd1/7\xff\xd8\xa1\xfc?\xe7+\x1c\xdb)\xd4\x06@\xdd\x1ah\xf0\xbef\xe5?\xf7u\x94\xa3\xcfD\xf4?\xa6j\x02\xd7\x02]\xba\xbfx\xcd\xe7I\x15i}?\xf8\xc6\xcb\xcc\xa4\xeb\xfd?\xc7_G\x01\x1do\xe4?\xa3\xfd\x85\x8dMF\xe8?\xde\xfc/\x88\xa9%\xee?o\x9a*\xd4\x0b{\xec?[\x9f\xefc\x00=\x03@\x0ej\xc4\x99\xab\x87\xf8?:\xef\x9d\xa3\x84\xe5\x0b@\x9b\x0e\xd0^Z\t\x0b@a\xb1B \x1a\x06\xfd?0\x9e\xd0\x8e\\z\xe4\xbf\xa0\xba\xb2\x0c1\x9e\xfc?\xa4\x1f\x8d<\xaa\xac\xf3?\xadC\x05p0\xd0\xe3?\xe1\x90\x12\x084\x19\x08@\xd4\xb3\xb0\xee{\xae\xfd?2\x06}\xbe\x91\xb4\n@<\x90D\xc3\x0b\x81\xf2?\xa1\xeaua\xf5{\x04@\x82\xa0\x88\xe3Z\xeb\x07@\x13P\x990S,\xf5?\x9a%\xcd!p\xb3\xf1?\xb5\xf6\xb3!\x934\x00@\xc1!\xcd\x83ZK\xf9?p\xbd\xe7\xc8\xb4\x81\x00@\xd7\xdb\'yav\xc0?\x82\xe8`\xc9\x15t\xf5?\xe3\xe2\xb5g\xf6\t\xd4?\x1de\xc9\x87\xb2\x06\xec?B\xfd \xcd\xd0g\xf0?\xc1m_{\xe5\xae\x02@\x8cUi\x1c&1\xf3?"\x85\xbb\xe9\xd6\xe2\xfd?\xa3\xbb\x9d\xfap\xbe\xd4\xbf\xe8]\xe9L\x81\xb3\x03@h\xef\x1d\x04\xc1\xf2\xfc?\x12\x14\x07$\n\x80\xf5?\xaa\xde\x8a{\xac\x8f\xcd\xbf!\xc1\x8af\xc3B\xc9\xbf\x15fd7C\xbc\xe5?Z2\xcdE\xe3\x8c\xc9\xbf\x05\x84\x15N\te\xf3?,\xcb\x80&R-\xf4?\xe8\xe7\x05uox\x02@G\xec\xde\x13M\xe2\xbd?l^\x0e@\xbd\xd8\xe7?\xa9\xef\xa9U&\r\xfb?\xa3\x1c\x84Qgl\x02@\x02\xb8\x04\x1e\x1c"\xf4?\xc6j\x8d\x12\xc1x\xe6?\xb8XC\xdfb\xb6\xdb?\xf77\xeb\xe6\xc5\xb5\xdd?\x86V\xbf\xa0\xa4s\xfb?\x0b\xc1J1\xd7\x8d\xec?\x93\xa0F\xfb.\x1a\xa1?K\xe5\xe3\xba\xc0\xa6\xe1\xbf\x00\x90zh\xf5\xd2\xf4\xbf\n8Ck\x0e\xef\xc7\xbf\xde\xf93B\xe80\xdf\xbfR\xbeN\\\xed\x1a\xf1\xbf\xdf=\xad\x97\x02#\xce\xbf\x19\xe0\x91\x01v\xfc\xe1?\x0c\xd6\xda\xd6\x1e\x05\xf1?\x01\x12\xc6\xc3\xaf\x07\xe2\xbf\xb8\xecv\xdb\xd4\x83\xac\xbfp\x83i\xf3i[\xf3\xbf\xcb\x00\xde\x86\xc0n\x07@\xa6\xf8\xfb\x9d\xaaj\xfd?\xf5\xf7\xc6s\x90!\xf4?\xccc\x03V\xbc\xce\xf1?\xe8\x81\xd7F,\xad\xed\xbf\xb2p\x12\xe2\xd4\x9b\x03@\xd2\x1c\xc9\xab\x94L\xfe?\xf6\xd9\xc7\xd78\x95\xfc?9\xfd[2]\xf9\xec?\xab\xbf\xcct;B\xe2?\xd2\xa2E\xda\x8f\xf9\xfd?\x94\x89\xe9SK\xc4\x86?R\x84\xca!=\xf5\xeb?\n\n1\xb9\x7f\xed\xf0?1\xfc\xce\xe56\xdf\x07@\xd7as\xb4b\x9c\xdd\xbfybP \x98Y\xfa\xbff\xc2_\x18\xf1^\xd8\xbf\xf3\x18\xffm\xca\xfb\xe4?\xae\xfa\xc1V\x07\xec\xdf?\xcd\x1e\xb8O-\xb1\xc8\xbfI\xacv\xe4\x17e\xdf?<\x11\xc5\xa2I\xb3\xf6?\xec\x1b\xb3=\x17s\xc4?\xa1\x14K\xcd\xa9\xa6\xdc\xbf\x82\x8d\xfd\xc22\xd4\xf6\xbf\xab\xd2}4O\x1d\xda\xbf\xc10\xf4\xb40\xf0\xb4?\xed^k\xb8[\x00\xee?6.\x07\xbfsN\xf2\xbf\xe6\xec\xe5\xd8\xd4K\xf2? \xa1\x94-\x06;\x04@\xef\xba\x01\xb4\x126\xfa?\xd1\xb4U\xe4`\xae\r@\xf4dSC\xfc6\n@\x84\x7f\x10\'\xc5?\x07@!\xd0\xbc\xa9\x16\xa0\xe6?\xe6t\xf2s\xb7\xe1\xfb?\xe7\xae\x87\xe2\x9dD\xfb?\xe3\x1b\xd8$\xc4\xab\xc5\xbf#t\x8fF!\x87\xf7\xbf\xd9Z\x9b\x87W(\xfe\xbf\x93\xc3\x8e?\xcd\r\xe1?\xa5Va\xacC#\xed?\xad\x1fJ\x9c\x90\xc8\xf0\xbf\xd5=q_\n^\xe4\xbfRuR\xfb\xce\x1c\xf0?M\xc7%\xde\xef\xde\xaa\xbf)\njy\xfae\xc9?\xfc%\t\xbc\x02\xd9\xf5\xbf\xe3\xbd\xa1<\x02v\xd6?\xac\xaa\xfe\xf2\xc8\xd7\xd8?f\xcd\xf6\xdd\xb6:\xf7\xbf\xb7]\xb5\xc6\xdc\xc2\xfa\xbf\x19H\xb7a\xa8\xd0\x02\xc0HrwI\xd4\xc6\xe0\xbf\xd9A\xf9\x1e\xe3\xb1\xf2?\x9av\x0f\x1f\xbe\x08\xe3?\x90\xcf+\x16P\x7f\xeb\xbfB\x98f\xf0j7\xd5?\xa5k\xa7Z\x94\x90\x90?\xe4|P1\xc8W\xde?g`\xafH\xda\x16\xe1?\xe4{\xe4\xc0\xa6:\xf4\xbf\x06\xd5I \xc9\x03\x00@\xbe6S\xf8}\xd9\x91?\x0bA\xe4O\x89\x02\xfb\xbf\xf8\xfc)?\x9fo\xdf?\x1a<\xf8\xdb=\x0c\xe6?\x9f\xf2\xec*\xc9\x85\xe4\xbf\xa0\xe7\xb2\xa9[\x87\xd8?&\x8e\xdf\xd4\xd3\x18\xe6\xbf6\xe8\x05Jny\xcd\xbfA\xb3\xb0\x82OU\xb7?y\x8a+\xf6\x93{\xfb?Xy\x1c1.s\xe8?[\xe8f\xae.-\xb4?F\xa0\xf4\xa3i\xfd\xf2?\xb7\x10\x8d\xec\x7f2\xb7\xbf\x03fr\x805\x01\xe4?\xa60\xc2\x9e\xc0F\xfe\xbfi\xbe\xa4\xfe\x95\x90\x03\xc0\xdd\x14\xa4\xa2\xf9\x92\xe3?$\xcb\xf9\x9b\x1b\xcc\x06\xc0G\xc4\x06\xa8D\xd7\xbd?\r#\xd9\x82\x7f%\xe1\xbf\xae\xd7\x92\x8d\x83a\xf5\xbf/>\xe8;\xc1\xf6\xca?\xd0B/\x94\xa8\x90\xf0\xbfyr\x12\xa2\xbf\xf0\xfe\xbfb\x82\xbbf\xaaZ\xf5\xbf2\x82&g/\x07\xed?\x85\xf1\xedJ\xdf6\xf5?\x14\xa2\xab\x11Y(\xe5\xbf\xb1\x93\x95-a\xc5\xea\xbfX\xd1P\xe0\x06C\xf3?e\x952\'\xf9\xac\xce\xbfj\x1f\xfd\xe8G\xd6\xf5\xbf\xc0k]U\xcb\xd5\xe5\xbf\xa42rdm\x8e\xf2\xbf\x10W\x1c\xc9\xe8\xee\xf2?\xe9\x14>\xf7\x1a\x80\xf5\xbf\xdc\r\xb9\x01\xf5Q\xde\xbf\xca\xfbs\x84\x0b\x19\xe4?\xbe\x8e\x173\x1aA\xf2?\xba\x924\xe8\xb29\x00@\x13\xe2O`g\xb3\xbc?\xdbL\xd8X\xe3\xea\xd1?\x1d\xb2\x9e\xa6O\x0b\xdd?\xad@\r\xb4\xbbS\xe4\xbf\x12\x05\xe3\x8e3W\xf4\xbf\x87v\x99\xbc\xf0\xbf\xe0\xbf\xe4\xa7\x91\xeeLD\xf5\xbf\xde!\x8aP\xa9\x8e\xe5\xbf\xd2\xa9-r\x1a\xad\xef\xbfA%_H\t\xf5\xcd?\x1f\xb8\xbc\x15\xd30\xc1?\x11~\xf0\xf6V\x86\x00@i`%\x1aMB\xc7?g\xfe;\x08\x166\xc3\xbf\xec)\t4\x1f@\xe2\xbfHcZ\xdb(\x06\xdc?!\x17\x8065\xcc\xd8?\x82\xcab6\xc8\xe9\xf8\xbf\x01\x9f\x0c\xc3\x10\xe8\xec\xbfNO\x92&nm\xf5?\x14\xa8m\xe7\xcf\x81\xf6?\xe4\x82\x81\xf2\x99~\xf1\xbf\x9f\x94\xc6YO\xf4\xcb?!\xd8\r\x88\xa1\x9a\xe9?M\x0f=\'\xe5\xdd\xcf\xbf' -tbag2 -(g3 -(I0 -tS'b' -tRp7 -(I1 -(I16 -I16 -tg6 -I00 -S'\xe5,\xa3Eb@\xe9\xbfR\x9a&\xf8\xf4,\xef\xbf\xbb\xe8zyy\xfa\xd6?\x7f(|\x1e\x01\x9b\xfa?R9\xbc\xb7;\xa4\xd3??\xed\xd9\xc9>\x0c\xf1\xbf\x0bpk"\x9ek\x1d@\xfcR\x0f\xb2u\xb3\x00\xc0\x85\xdb\xef"9\xd5\x16@\xed_\xbc\x89\xe4L\xf8\xbf 4\xeb\xa7}\xfd\xf2?\xf8\t\xfa\xa2\xd1\xe1\x1b@\xf5\x90\x9b\x08\x1f\x11\xf5\xbf]`=\xc7V\x90\x05\xc0\x9ba\xe4\xea\xbb\xa4#\xc0\x046\x1b\xcc\xaa\x13\x0c\xc0H\x01\x84\xb9\xe0+\xed\xbf\x1a,>\xded<\xf4\xbf\xd3\x83T\xb1Hq\xfc\xbf\xe1\x81\xdc\x99\xc8\x80\x0f@\x94@.\xb5Zr\t\xc0\x8b\x8a\x8f\xb2\xe9\xf8\xe2\xbf$\x03"5,\xce\x12\xc0Wf\x19\\r\x06\xf1?iV\x9c`\xa9\xf2\xfa?\xda\xf26\xe6\x9c\x01\xdd?\'`\x03\xae\t\xe4\xdf\xbfr!\xd3\xe4\xe3b\x0f@\xec\x8d\x8b~\x80\xcf\xfd?\x08\x11I\xe7U\x0e \xc0:<\x05\xc3\xb6$\xba?,\x84\xaa\xec\xdc7\x15\xc0\x85\xa55\xf2C6\xf1\xbf\xecbM&\xae\xd1\xf9\xbf\x82\xbb\xda}\xf7\x91\xf5?D\xc2\x12\xdc/\xbf\x03@\xf2`\x90%\x86b\x1b\xc0\xf9}\x8d\xa9\x9f\x9f\x13\xc0\x0c\x1e\xd10e\xf1\x02\xc0\xc3P\x8ff0c\n\xc0\x8cO\x03\x05\x14\x1d\n\xc0\x18@Q\x0c\xde\x8d\xec\xbf\xd7]w}\x97\xe7\x15\xc0<\xf5\x048\xc9&\x04@+\x1b\xb3\xeb\x87\xc9\xe2\xbf\x07JI\x16%0\x0e@\t\x15\x01f\x15$\xb5\xbf\x7f\xc5\xd3\xb8Y\xc4\x07@\x1e\xe8\xf6\xa7+\x03\xfc?\n\x8f\x1e$\r\x19\xed\xbf\xae\xbe\x93>p\xa2\xc3\xbf\x0bd\xd2\xdc9\x7f\x06@\xcb\x8f&\xd8\xb1\xbe\xf2\xbfs,\xf68\xc1\xf5\x05\xc0\xf0\x12\xda\xd0\x97\x83\x11@9\xe1\x83\xf87k\x1b\xc0\xf1,\xaf\xea\xe6-\x03\xc0\x84u~,6\x0c#@q\xc1B\xee\xb86\x01@[\xec\xf3\x18\xb68\xf7\xbf\xb0<\xd2\x0e\xbaR\xdd?%\xa8m\x83y\x81\x15@\xae-\x0f\xad\xc1\x85\x0b\xc0\x90l\x9a\x0ba\xfd\x18\xc0\x17H\x1d4\xd3-\x1f@q\x02P\x1c\x9c\x95\xf0?GU\xc4\xb9I\xa2\x03\xc0+\xd2\x01\r\x11\x97\xc6\xbf\xd3Q\xd1Szg\x00@s\x97fE\x892\x19\xc0(S`\xdb\xe6\x16\xbf?\x86\x95N6\xcd\xe2\xda\xbf\xd8\r\x91\\\r\xfc\x12@\x97\xc3\xb1\xef\xc3|\x02\xc0\xb5\x1e\x8d\xbf>d\xfc?\xa8\xfc\xc0y\xdd\x9c\xd5?\xd4\xc9lO\t"\xf1?PL\xa1\xc8Q\xdf\xf9?3\x85,\xa529\x05\xc0\xfdE\xd9\xfcL\x94\xf1\xbf>\xc3\x1e\xdb1\xcb\xe6?\x98\xa7c\xd0\x19\xad\x00\xc0$\xc1\xfc`i\x9b\xe5\xbf\xcd\xb1\xcb\x03\xab\x9c\xf1\xbf\x1a{\xc4\xe3"q\xf8?\xf5B\xa7Ix\xf0\x11@\xdd\x84K\xb6U\xe4\xf1\xbf\x01n\x01\x82\x00\x8d\x01\xc0\xe3G\x82@\x8e\x9d\xc3\xbf_M\xed~\x92\t\x0e\xc0\x02#\x18\xfc\xe55\xfa\xbf\xe2\x90ED1\x81\x0c@\x1e\x80d\xd8\xf7y\t\xc0\xe6\xd9M\xf4\xd9q\x01\xc0\xa6p\x19" \xf1\x13@\x95\xf4\xc6\x1b\xfa%\x04@F\xb4\xac#\xd7i\xf8?\x9bZ\x1d\x1b\'\xa2\x11@\x7fS\xcd\xd6\xdf\x9c\x13\xc0\xbf\xbd\x98\xb4\xaa\xcf\xd8\xbf\r`P\xc5\x97\xa7\xf0\xbf\x95\x9caM\x03\x1e\xf1?: \xa0\x0fYF\xfc?\x124\x16b\xfa\xd7\xf7?\x8be\x88\x1f\x87\xc2\xf7\xbf#-\x0fh\tq\xae\xbfgn]\xf4{^\xf5\xbf#\x92\xc9^\xa4\xa4\x1b\xc0\x95=\xb0\x8bl\x02\x1d\xc0&\xf4\xccL\xcd\x90\x07@\t\xc5\xffq\x85E\x11@_\xf8\xae3\xb2\x19\xfb\xbf+\xc9wx\x92#\xdb\xbf\xd4\xf0\xe0\x07\xa2N\xfe\xbfI/\x9b\xfb|\x9a\xe9?\xb6%\tp\xa5.\xc2\xbfU\x03y\'q\xac\x00\xc0\x979\x0fur\xab\x10\xc0\xbav{\x8b^R\x10\xc0oi\x16\xff\x9eS\xea?)\x93\x96\x1b\xfc\xd4\x05@\x17mK\xaf\x00Y#@T~\xe6O\xde\x9c\x10\xc0\xb1\xaa\xf0p\xab\xb6\x17@\x85\xe2\x1d\xf2\x00\x99\xd5\xbf\x809\xe9g\xdd\x88\x12@X39(:\xa1\x97?\xef>2}\xc1\xa3\x10\xc0Y\x9d\xee\xe1j\x04\xdb?Ma\xdf\xd3\xa2"\x15@\xabn\xc5gp?\x1a@\xee\x17\x9e:\x96\xef\xfc\xbf\xc4\xe8\xcf yR\x1a\xc0;3\xeb&\xc4\x8e\x14\xc08\x9c\x15\xa6%r\xd6\xbf\xdc\xf7\xf77;\xb4\xc1?\xde\xa2\xfd\xa7\x8f\x0b\xd5?\xa3\x9b\xe4\xa03\x1f\x04@\xbb\x89\xa1\xa6\xf9\xdb\x11\xc08>a\xc4\x85\xae\xff?\x9b\x08\x06\xb3\xcd\xc5\x08\xc0cw\xff\x16v\xd1\xea?\x90\x9ck\r:/\x01\xc0}[\xd67X\xf5\x13@\x14=\xcd\x86\x08\xb8\xc3?\x81\xa8\x90h\xabl\x14\xc0\xd7\xb2\xb8\xc9\x18\x7f%@U\xda*\x8d\x8f\x1b\x10\xc0\x93\xb3J\x99\xb8G\xe0\xbfq\xa1\xc5L9\x02\x0c\xc0\xe0\x96\x00\xaei\xbc\x94?xx\x7f\xd9\xcd\xa2\x18\xc0\x83Gq\xac\xec(\x06\xc0\xf6\xef\xc8:{\xa8\xaf\xbf\x7f\x13G\xe2\xae\x99\x03@\x85\xf8,\xdeo\x9b\t\xc0\x11\xe1\xd4\x06\x03d\xf4?8y\x9ez9\xb3\x0c\xc0H\xee\x9a7\x18\x19\xf5\xbf2\xf57\xd8\x1b\x9f\xf8?\xa57\xfeoY\xbc\x16@\xbe"m\xf0$\x0c\xf5\xbf\xe9=\x94G3e\xfd?I\x805#\x10Z\xe9\xbf\xe0\x8b\x8c\xe8.\x03\xb4\xbfu\x94\xc6\xbcL*\xf0?k\x82Y\xf6\xaa\x93\x03@\x8aN6D\xdf\x8b\xb5\xbf&\t\x07Jm`\xdd\xbfY\x14\x964\xd7\x0c\x1c\xc0\xd1B\x88\x8a\xf5\x8a\xbd\xbf\x1a\x7f\x94\x03\xcd\x0e\xf2?\xd6\xb5\xc4\xc0X\xea\xfc?\xad\xc1g\x9b\xa72\x14@4\x94\xce2\xb2+\xfc?\x8a\x8b\x9a\x8a\xfa\xcb\x15\xc0\x95\x81\xe7K"\x1f\xed\xbf\x15\xbf\n\xd8\xc2\xd9\x13\xc0\xfb\xae\r\x85\x81m\r@\xfc\xda\x84\x82\xefI"\xc0\xdc\xf2`O\xa2\xa2\xf9\xbf\xdfo\x1f\xf3\xeb\x19\x01@Yi\xfc?/x\x00@y\xddc\x1ao\x92\xcd\xbfTn\xc8W3\x8e\x11@Yi\xb9\x9c\xd2m\xfd?k\xeeLm\xf5\x95\xdb\xbf\x13P\xb4v\x1c\x10\xe2?\xe0\x01;Z~\xcb\x16@\xfc\xf1\xbd\x97\x84\xfb\x00@\x86Y\xfe\x0e\xc1-\xfd?\xfc\xd6\xb5\xf9\xfcZ\r\xc07\\\x1ai\xc5A\x15\xc04\x91j\xa2\tZ\xfd?\x90D\x7f\xbb{\x1e\xfa\xbf\n\xd8\xfe\xa3\xcd \xf1?uF\xc6@\xbb\xeb\x0c@\xccrI\x90\xa7\x19\x0f@yq\x89\xcb\x93*\x0e@g\x8f\xb7\xe9\x16\n\xf1\xbf\xdd\x19N\x1b)W\xf4\xbf\xe8P\xe5\xdcK\xd6\xe5\xbf/8\xa4\xf3\xd30\xdc\xbf\xe2P>\x1e\xebs#\xc0\xc3\x84}\x01\xbf\xea\xe5?\xec\xbbm\xb3j}\xe8?\x18\xc1\xc4ZM\x83\x11@\tg\xfc\x15\xd1)\x14\xc0\x85^\xd6\xea\x97U\xbb\xbf\x90U&\x9a1x\xd3\xbf\xbb\x82c+\xf2y\xf9\xbf\x8f\xa8\xda\x9e9x\x03@\x00F8\x03\xb1\xd1\xcb\xbfc\xc6\xc4\xd8\x83@\xfa?\x94gKb\xb0f\xfc?\x84\xef\xd6\xeb\xc8\x14\xe8\xbfy\xf9rz\x1a\xf1\xf5?\xe55\xd7\x9f\xe8\xbc\xf1\xbf\x93\xb7\xafx\x86p\xfc?"\xe7\x7fp\xbe\xb0\xff?\x85\xbb@\x9dq\x99\xd8\xbfQ\xf4\x13\xa6\x9aw\xda?\x05>\x0c\xa0d\x06\xf5?\xb6\x19\xf8t\xf0\xa8\xb7?\xbf[,\x8a\x98\xc0\xdf?2G\x16\x84\xbb\x02\x18@\xa5H>\x87\xdf\x19\xe7?\x82\xc7\x18\x99\x1eo\xfb?\xe6[3_\'4\xf1?\x94<\x04N\xed\x1e\xf9\xbf\xddfp*\x8f\xea\x05\xc0K\x9e7\xce2K\xb7?P\xf0b\xd9\x94R\xc9?<\xbfk\x94\xd3\xd3\x13@\xe4\x17\x8f\xcb!C\xff?\xc3\x80\x0e\xfe6\xcb\x0b\xc0W\xcb\xfec\xe7\xc6\xe5?L\xa3#V\xf3$\xda\xbf\x81\xe0("mp\xc2\xbf\xed\xdf\xaa\xaci\xb1\xf2?\x19\x97\xba\xaa\x1e\xaf\xe1\xbfe\x8c\x9a!4s\xd4\xbf\xf1\x17\xc5\x14\xdc\xd9\x11\xc0\xb2\xe1\x90\x08\x11"\x04\xc0\x9d\x04\x83\xe4SQ\xff?\xc2\xc9\x91\xf2Y\xaa\xe2\xbfd\xbd\x8c \xd6i\xc2\xbf#\xac\x8a\x7f\xb0\x17\x01\xc04\xae\x11\xbd_\x0f\x1e\xc0)\xd3iHY\xfc\xd4\xbf\'o\xdf\x07\xe2\x16\x01\xc0\xcf\xd8/l\x13\x97\xff?\x98\x16\\\x19i\xfd\x0f@\x99kOE\x14\xe8\x19@\xaf\x80OV\x93\xee\xe1\xbf\xe1l\xfa\xeaX\xdc\x1e\xc0' -tbag2 -(g3 -(I0 -tS'b' -tRp8 -(I1 -(I10 -I16 -tg6 -I00 -S'\xb8\xaf\xe9\xd8\x1f\xc9\x10\xc0\x11L\x94S\x03\xcd\x0f\xc04\xb4\xe9\xa0\x06\xce\x01@\xa5E\xf4@\xe1\xfe\x13\xc0\xbd\x8b\x86I\x8f\x18\x05\xc0Dh\xd2\xf2\xd9o\xf5?#\xacv\xd6\xc3J\x01\xc0\xfca\xcf.I\x93\x07\xc07A\x1b}t\xfc\xfb?}\xc4$\xff\x1b\xac\xfe?\xf2\xdf\x90\x184\x8b\x07@I\xd0;\xdb\xd4\xca\xd1\xbf\xd4HK\xec\xb5\xf2\xf7\xbf\xfb\xb0~V\xdc\xdc\xeb\xbf\\\xac\x0e\x9a\xab\xb5\x12\xc0\x1ehK\xba\xae\xe8\xfa\xbf\x0f)3%\xfb~\x15@H\xfb\x0b{\xfc)\xd2\xbf\xd5\x10\x85\xdd\xbe\x9f\x04\xc0\t\xda\xb4\xee \x94\x02@\x8dC(\x19\xba*\xfc?\xa3\xaf\xe4\x1b"\xdd\x12\xc0WH0[\x95\x17\xdd\xbf\x13\xb7\x03\xebr\x15\x13\xc0O;\xa9@R\xd7\xfe?\xcb\n\xbf\xd0Sl\xd9?\xab(\x16\xb3\xc4\xff\x03\xc0\xdc\xa0\xf97\xfe|\x1c\xc0\xd2I\xd9\x19\x90\xe7\xe1?p\x07V\xef \xfa\xed\xbfK\r\xca\xab\xa7r\xf5\xbf+\xe2\x14\x0c\n\xa6\xf6\xbf\xb1N+0\xd61\xff\xbfQ\x1b\x97\xc0J\x0f\x05\xc0\xb5\xa9m[\x95,\n\xc0)\xab\xc9N4\x06\xfd\xbf!\xe5\xde\x9a\x08\xbb\x00\xc0\x85\xbe\x8e\xdaX\xe1\x0b@z\x87\xf3\x94\x0f\xca\x14@\xde\x82\x05\x85\x84b\xf4\xbf:D\xf0WD\xfe\xc5\xbfT\xe9\xa2\xfc\x93\x15\x03\xc0{H\xa8f\x8f\xa8\xf3\xbfQ\x10\x9d\x82\xecN\x08\xc0\xc8\xaf\x05$j\xe1\xef?z\x84\x1d\xaa\xb5d\xff\xbf\xab\x1b\xf0\xdd4\xb0\x02@\xaac|\xee{B\x02\xc0W\x9a\xf0\xab;\xc9\x04\xc0/\xa5\xaaFq\xa8\t\xc0y\xde\xdc\x82s\xbc\x0f\xc0\x96\xd4"\x1f%m\xf2?2\x86e\xcd~\xdd\xa2?\x81\xf7O\x0b#$\xfb?G\xad(\xfe\xaa\xe4\x11\xc0z\x9d*\x06JR\x00\xc0m\x0e\xc7H\n\xed\x07\xc0\x03)\x0fG\x99\x8a\t@\xefp\xfa\x9a\x17\xd6\x02\xc0\x1c\xcfR\t%,\x01@\x83Wa\xef\x0f\xb5\xfa?\x83\xc5\xe5\x12^4\x05\xc0\xd1\x1f\x81\x86}$\x01@\xe0d\xb2\xc7Q\xb7\r\xc0\xc0\xf0Onz\x8e\x10@\xdb\x8f\x1f\xf4\xbb%\t@\xb6KojU\x95\xfc\xbf\xfd\x8e$\r\xe6\x9f\xf7?o\xe5\xb4\xed\x08\xff\x11\xc0Y\xc5\xd7VZ|\xee?\x98#K\x94\x16\xbf\xf8\xbf\x18\xb2\xdf\xa9\x9bc\xf0?\t\x02\x1c\xb7\xb1\xaa\x04\xc0\xcb@0\x11qE\x02\xc0\xa9n\xcf\xbe\x88\x89\x08\xc0\xa4I\t\xd91\\\xec?J\xca\xe9s\x1e\xe7\xfa?%S*V\x0b.\xfa\xbf8F\xeeL\'\x1a\x14\xc0\x82\x10t\xa5\xe1\x90\x01\xc0-\xd9\xf9\x83\xa9\x8b\xd5?\xe6\x15\x8a\xf0\xc6\xff\t\xc0Y\xffk\xaa\xfa\xf1\x0e@\xb0\x0cw\xe4\xfe\xd8\x14@\xe0\x9c\x87\xf9\xdb\x12\x03\xc0\xa7\x15_t\xd3\x94\xfe\xbfi\xfd\xf1\xcb\xd1\x85\xe9?\xcc7\x17 \xf1\n\x13@\'\x02V(\xf5\xae\x05@A\x91\xcb#ax\t@s(\x94ZHe\xf9\xbf[T\xb1\x83\x11\xdd\xf7?\x1a\x9d\xb8L\xe0\x9f\xfa\xbf\xd5\xcb\xe8\xca\xa1\xdd\x16\xc0\x12\xbc\x8d\x9a\x85j\xf9?\x8c\xf8\xe0e*J\r\xc0Q_HA\xdc\xbc\x10\xc0F9\x97\x86\xfe`\x0b@\xef\xd7(\xd9}J\x0c@\xd6tc:$\x84\x13\xc0{\xb9-\x99\x8d\x95\xf9\xbf\x14O\x80\xa0\xad3\xfe\xbf\xb6sc\x0c\x97\xe1\x11\xc0\x8aEi\x7f\xa5%\xfe?\x9f\x0e-\xe9i\xec\xf3\xbf\x8e\x9a\xba\xc7oH\x10\xc0\x9e\r\xc6\x9f\xa8\xa8\x07\xc0\x8c\x17\x1aM}M\x07\xc0\xa8}\xe7\x1d\xb3\xc1\xeb\xbf\r\x1a:^\x1a=\xec?\xa3\x0e\x89\x9f\x9f:\xf6?XU\xaa\xc1\xa4\xb6\xea\xbf\xde\x02\x91\rD\xc9\x11\xc0\xb1\xa5\xe5\xee\x97\xfc\xdf?o-9\xc3c\xeb\xf7\xbf\xd3EAqdk\x06@>\xf4\xb9\xb0\x86\xae\xf1?\xa90\xdf\xd0{/\x05\xc0=6\xcd\x07\x93\x1b\xbd\xbf\xcd\xba9\xb7;p\xee?\xe0\x7f{\xf1\x8c\xd4\xd7?\xb4\xeb{\xef\xa3\xc8\x12\xc0\xb7\xa2\xae\xf25\xb5\x05@\xf7\xf3\nd\xa5\x93\xf5?s,9M\xee\xee\x07@\xe4\xffG}\xfc\xd8\xf2\xbf\x88hb\x91\x8c\xef\x01\xc0\x04\xe9\x89\x00\x86\t\x18@/\xfb\xa9\xe7\x93\xd9\x8c\xbf?\xd3\xc0L\xaa\xe5\x0b\xc0\x86\x12\xc2s\xa8z\x13\xc0\xd3\x13\xd5\xdd\xde\xd6\x13\xc0\x10p\xc2\x92\x1f\xe1\xf2?\xc7\xd4\x0b\x89\xe4,\xd1?\x8e\xec\'\x80e\xc0\x16\xc0iI \xa7\xea\x1a\xf1?\xbb\x00,\xa4\x94\x95\x02@\x1e\\\x15:\xc9T\x0b\xc0\x83\xaa\xc7>r\x11\xef\xbf*\x18\xdbqJ\xbe\x10@\xa6\x11\\\xccw\xaa\x04\xc0\x0c\x11\x95\xe1\x1f\x03\x08\xc0\xdf/\xddf!\xe9\x04@\xdf\xbbi{\xa1q\x03\xc0\xc6\x9e\xdbO\x8e.\x04@r\xd6\'Z\xbc-\xf2?\xfc\xbdB\xf0{\xf5\xf9\xbf\xe94\x83\xac\xeb\xa5\xfc?KZ\x0f\x1b\xb0\xad\x07@H\x0eB{\xbb\xff\xf6?\xb0\x9f0\xa5{g\xee?\xde!\x02\x97\xe4^\xfa?\xf5\x96U}\xaaX\x0f\xc0"\x0c\x91\x8fq\xa2\xaa?\x14\x8d\x00\xa7\xa2\xfc\x04@\x1d\xf3\xa2L\xc5\xb7\xd8?i\'\xc3e\x1cN\x10\xc0\xc8.7\xb3\x82\x13\x0e\xc0\x13\t\xee\x19\xb8\xc1\x04\xc0\x8av\x03{\xc4\x03\x13\xc0' -tba(lp9 -g2 -(g3 -(I0 -tS'b' -tRp10 -(I1 -(I16 -I1 -tg6 -I00 -S'\x02[\xa3\x81f\xc7\x17\xc0/!\x9c\x0bAv\x14\xc0&l\x19vp\xed\xf7\xbf\xec|\xc6\xa0\xde\x81\x10@c"\x17=\x0e\x0e\x07@\xa3\xe8\xb2\xb7\xb0t\xf5?p\xfb\x08?\xe0S\x1d@P4\x9a\x94D\x94\x18\xc0P\x1c\xc4\x0bGB\x0e\xc0V\x7fG\xdf\x96\xdd#@\\B\xf4\xb2\x8d\x05\x03\xc0\x08\xe11JM\xf7\x05@\x9d\x98\xf1|\x1e\xcc\x00\xc0p\xd5gl\xd7\xcc\xfa?\xfbS\x1b\xbdvj\xdf?\x04\xa1\xe6yu\x83\x01\xc0' -tbag2 -(g3 -(I0 -tS'b' -tRp11 -(I1 -(I16 -I1 -tg6 -I00 -S"\x93 \xc6S\x152\n\xc0\x9f\xb1\x8ez\x19`\x03@\x1e\xdb\xdc\x1d\xe2\xbc%@\x8d\x8bTf\x98k\x03@\x04K\xe8b\x879\x01\xc0\x81\x88\xdea\xadI\xd5\xbf\x8f\x98\xee\xaa\xd8\xef\xf9\xbf\xab\xa6\xae\x08\xae\xf7\xf8\xbf\n\xc5'NqQ\xdf?yEk\xd7\xbf\xfe\xf3?<|\x14\x93\xfae\x08\xc0\xe8\xeb\x1f%\x96\xb8\x05\xc0\x11@\x8e\xf0|1\xf0\xbf0\xc0\xae\xd2\x87B\xf7?$\x9e\x17\xc8\xe8M\x04@\x0b\xd70\xf2&d\xe9\xbf" -tbag2 -(g3 -(I0 -tS'b' -tRp12 -(I1 -(I10 -I1 -tg6 -I00 -S'\xe0\xef4\x0c\xd2\xbf\xa0\x97_\x05\xb6p\xef\xbf\x1dN3\xe1P\x08\xc6?\xd8nH\xb9\xd8\x97\xcb\xbf\x00.\xd9\x16\xba\x05\xf4\xbf\xf1\x06s\xfaB-\xea\xbf\x8c=f]\xcb\xa9\xf7\xbf\xd6\xaajfgq\xea\xbf}\x0c![=\x0c\xf6\xbf\xf7JY\xf5Q\x86\xe6?/\x84\xe0F\xf6}\xe1\xbf\x9cX\xd1Cj\xc8\xdd?\xd90\xba\x9c\xce\xfd\xe3?\xfd\xf9\xf8\xe2\x92E\xf7?\x04\x96&\x05S\xf4\xc6?\x82-\xc2\x95\x1e\xca\xf1\xbf\xe4\x8d\xa7\xe8\x15\x1a\xc6?)\xf1\x1ag\x1cL\xea?\xc6\xd1\x99\x87\x01\xf4\x05@\x98v\xbfE\xc49\xfc\xbf\xcb\xe8\xa6r\xed\x88\xba\xbfP\x0eD*\xaeA\xcc?y\x92\xff\xc9\x88\xb5\xcf\xbfw\xd7\x98\x0cP\xe3\xa3\xbfZ\xfa\xc4y\x9c\xb0\xc1?w\xe7\\c\xd2\x94\xec?\xc3\x9b\xdc{\x94\xfa\xf1?U\xe8P\xd2\xc1\xbc\xf6\xbf\xd4\xbf$\x98\xd4\x94\xf0?s\xfb\x95\xdb%S\xd6?\xaf|(\xfc%\xa7\xdc\xbf\x15d\x19EX\n\xdd?\xc1^V5\x1e\xc9\xe8\xbf4\xf9\xbe5\xbd\x15\x82\xbfq6=1\x14\xa2\xf1?\x84(\x89\x1c(\x0f\xc2\xbf\xe4S\x02\xa6\x1c\xc7\xdd?\xf7\xe7=f\xd5\xfa\xd0?f\xa4[\xfe\x87H\xe8?\xd3\x12\xc9?\x8ef\xe4?\xaf[\x9e\xf6\xc5\xb9\xdb?\xa9\xa5\x95lw\xf5\xe7\xbf\xe8\x84A\xe1a\xc0\xf0?\x1cp\x87<\xe9\x9a\xf6?\xfd\xfbS\x99\xc1M\xd4?\xdc\xce\xcc\xb9:\t\xcc?b\x01_\xe2\xd3\xba\xe5?\xfaD%<\xffB\xeb\xbf\xd2\xfffc\x15(\xe9\xbf\xc1\x0b(Dz[\xe1?W\x1c\x9b\x06\x12\x9f\xfe\xbf\xe4V\xa0*\xfb0\xb1?\x1e\x04^\x9e\xc7C\xf2?&\xc1B\xc5\xb0\xdd\xcd\xbf.\xb3e\x8eS\x14\xe8\xbf1\xb5\x89N\xc7\xd4\xe6\xbf\xa6\xb6\x1f\x13\xc5\xdc\xf6\xbf/\x9f\x0eE\x97O\xdb?\xf5\xa0\xedw\xad\x0f\xf6\xbf\xe2 \xf2\x81\xb6\x85\xe5\xbf\xc0\x01WS$\xe2\xdb?\x80&\x91\xb1"\x9e\xf6\xbf\x94^\xa0\x99\x08\xf3\xed?\xe3\xe7\x8aA\xe7\xf4\xe4\xbf\xa1\xc5,y\x1d_\xd8?\t\xea\xd8X\xee\xb9\xd5\xbf\xd7)\xc2\xfc\'\xc5\xc7\xbf\t .[\x9f\xdd\xe3?\x8c\x87}\xb2Mh\xec\xbf*wF\x85\xca\xe3\xb5\xbf5`\x06wt\xefe?Z\xc5\xab\x88@\x07\xb4\xbf\r\xc0\x97\x8fbG\xe0\xbf\xc91P\x03\xfd~\xd4?\x9c@\xbd\xed\xf3\x06\xe9?q"\x89W\x9e\x14\xef\xbf\x84\xcb\xb1E\xe7\xcc\xea\xbf\x8f\xe3\x0f\x864\x84\xf1?\x946\xda\xa47\xb9\xd9?\x17lr\x8b\x08\xd6\xf9?\xdat\x80\xd6}^\xc9\xbfmAT\xfa\x84\x87\xf4?t\xf4\xec\n\xfb\x98\xf3\xbf\x04S\xe1e\x9e\x00\xcf\xbf}\xa8\xc6\x98\xb0\x10\xc4\xbf\x971\xe4wn\xbc\xa6\xbf0\x9f\x9e\xf7\xe9;\xcb?\xa7\x11:\x1c\xfa\xb6\xed?CbH,\x1d\x9e\xeb\xbf\xee\xe5N\x17\xfb\x9b\xf8?\x98s\x19\x98\xd4\x93\xf9\xbf\x0b\x0e\xe8\xc3\xef\x99\xfe?\xc8&\xf2.4\xc2\xc8\xbf*\x8et\xdb\xca\x97\xf0\xbf\xa4\xe1\x91\x90\x9c\x16\xd1?\x9f\x11?\xf4\x12\xd4\xc0?}oS\x82\x93\x1d\xf6?\xb8"\xa6\xbb|\xf1\xe1\xbf\xc8X\xd3,\xec,\xc3\xbf\x81W=g\xc9\xa9\xff?%\x10l\xd0\x01\xc2\xe4?\xe7\xcb|{0r\xf4?>1\xd0\x13|\x0f\x03@\x90)\xf9\xd5 R\xd2?\x97\x89y`\'W\xf2?\x0f[F\xc4[\xe8\xdc?\x1f\xd7[\xf4\xecU\xe2?f=x\x0e\r6\xf5?\xfbO\xa9G\xb4u\xc5?a\x8d\xa3]_\x96\xba?\xed\x108\xca8\xd3\xee?\xda\x81C\xa1\xb4s\xd8\xbf\x06\xec\x89\xd2_\x9f\xea\xbf\xb1\xe7\xd6\x0edx\xb4\xbfrB\xc6\x01\x9c>\xec?ZL\x8b\xb7yv\xe8\xbf\xb4+\xe2\x1f*\xae\xf3?\xbe\xc6BY\xe1i\xf1\xbfl\x12Z\x83lJ\xd9\xbf\xc8\xfc\x84\xb3\xf2\xe7\xea?vz\x1dG\x8b[\xe5\xbfSBY0Y\x0f\xe8\xbf\xc1\n\xf5w\x81%\xed\xbf6\t\x93\xce\x0f\xa5\xdd\xbf\xe9\xe9t\xa9\x1a\xde\xe3?7\x065\xe1!\xaa\xf6?\x18\x8f\x10\xa2\xda9\xfd\xbf\x17\xdf\x9d\x0b\x9c/\xea\xbf\xb7/\xfe\xf0\x93\xde\xb5\xbf\xed\x02\xaa\xf3\xf0>\xd4?\xcb:{\x10\xdb\x85\xdf\xbf\x97Z\xabV8\x1c\xe5\xbf`}I\r\xfb\xd6\xd7\xbf\x99\xe4\x8a\xac\x83i\xda?Y\xca\x1e\x01\x95\xd1\xe1?\xa0z\x91\xccI\xab\xa5?\xa5^\xf3h\xdas\xb7?\xa3\xee\xb15\xb0\t\xec?\xa5=\x13\x8c;<\xdd\xbfB+\x82\xf8\xea?\xc4\xbfC\xdco\xbf\xdb"\xed?\xaf\xf4\xe3\xcb\x1f\xda\xaa?g\x01\xffS,\x01\xeb?\xbe\xcb\xbfQ\xd8\x0b\xd8\xbfGM\xe3{x\xab\xea?\xbc\xda\xe9\x9f\xe4\xb1\xe5\xbf\r\x97ua\xd4\xbf\xe3\xbf\xaf\x1b-\x13\xd7O\x03\xc0\x07{\xc7\x84\x9b\xfa\xf3?\x04\xcf\x164\x1e\x0e\xa7\xbfv\xb3\xeb\x99P\x04\xff?E\xa3\x91\x83u\xae\x03@\xeba\xc0\xc3bJ\xf7?B\xeb\x8a\xf6S\xec\x02\xc0\xb3h\xa3\x07g^\xdb\xbf\x13\xa2(\xb2\x1c\x8c\xfb?g)\xc5\xce#B\xf7\xbfA\x14\xf0\xf0\x01\xe8\xd0\xbf\x82A\x19\x85$O\xd4?E\xb1\xc7H\xaeO\xdc?\x80 \xc5/\x0fY\xf5\xbf\xf1\x02B\x91%\xae\xcc\xbf=\x99\x0c\tt\xbb\xf2?\xb1\x01Ys\x026\xe9?I\xfd\xfbE\x90\x82\xe5?X\xe6{CXJ\xd5?\x90\xedk\x16j\xf1\xe0?|\x17\xaf\x82\xb8U\xdf?K*\x11\xd83~\xe8?\x85\x95\xe9\xf3JW\xf3\xbfM\xcc\x16G)U\xd9?\x93by\xc1\x06\xd9\xf1?\x9d&\x82\xa5X\xba\xa7?\nOf/%/\xcd?/\x12\xcf\x01x\xac\xc5\xbf.\xd7\xdf\xb3\xf6\x9c\xe5?\xc0e\xd0\x8c\xc0^\xd1\xbf\x0f\xeb\xd5\xd7\x16\xdf\xeb?\xa5\x0f\xd2\xba\xa7\x84\xe4?\xf5\xcd\xba\xe1\xe45\xb6?\xcc\x8b\xb8&\xf2A\xfe?&\xa2E\x8a\xd9\x0c\xf3\xbf\t\xda2;\x81\xca\xbe\xbf\x86\xb3\xaf\x9c\x04\x87\xd2?\x06C\x1bq\xe2\xd5\xf0\xbf\x1b@x;\x11\xf3\xe5\xbf=\x86\x0cg\xed\xb4\xc9\xbfl\x0cM\xb6\xcbS\xd2?\x98p+\xcd\x9e\xfc\xd6\xbf\xe7=\x01\xe1\x87\xa7\xed\xbf\xc5\xcf\xb3-\x7f\x1b\x81?_\xd4\xe2\xea\x08F\xf8?\xf6\x91\t\x1e\n\xc5\xf3?\x0c\xf28W\xcd\xcf\xee?\xbb\t\x19\x9cc\xe1\xd5\xbfz\xb3c\x7f\x17\xf4\xe0?\xfc&\xda\xca\xd8$v\xbf?\x99\xdd\x10\x08\xf7\xd1\xbf-B{\x9cr\xf8\xec\xbf\x0e\xba\xa4^\x14@\xdc?t\xc3m\x1d\xce\xd1\xef\xbf\x15Tv\x11~\x07\xe2\xbf\xf1\xf8)O\x99(\x01\xc0\xcdl\x1d\xe4\xc4\x93\xeb?I\xd6\xeaN\xd9\xb6\xdb\xbf\x1cv\x86\xef\xd0,\xe3\xbf-\xb5\x07\xdc\xd6z\x93?z#\xc3\x82\xe8\xb2\xf7?\xba\xbb\r\x89\xe4&\xea?\x89@*\xff\xa6n\xf6\xbf]&"\x08\xaf\xd9\xf4\xbf\xaf\xbc\xdb\x13\xfb\xcc\xf7\xbfSX\x07\xb60\xac\xe8\xbfZ\x0b"9x\xdb\xf5\xbf\xbdy\xbeC`\x05\xe8?\xe1\x9e$\xf8\xf0\xec\xe4\xbfR\x0c\xbe\xe9\xb0\x98\xde\xbf\xa2\xdf\x0b\xfb\xc2\xc5\xfc?\xb8\xc2\xd4\x8d\xbd|\xbf?\x04d\x99\xc0\xf8l\xd6\xbfu\xf9\x9aW\xe4\x04\xda\xbf\xd9\xbc\x81\x89"G\x93?\xee\xe8\xcer\x91"\xe9?}Y\xf2\xbe\x9d\x0c\xd4?-\x16P$\xa6\xac\xf9?\x7f\xa9\xc0o\xf8\x0e\xde?\xdd\x99\xa4*\xf0\x0e\xf1\xbf\x96/\x90\x8c\x17\xeb\xd9\xbf\x87>"k\xa3&\xe4\xbf\xcb5\x87\x9cRH\xd7?\xcd#z\xd4\xf3\xc2\xe4?Q\x99\xda\xc8\x91L\x03\xc0.\xe8y\xbb\xc6\x0e\xe3?\xeb\xe7%\x1c/\xf5\xd7?)\xa7\xf9\xf7B1\xcb\xbf\'\xc2\xe8\xd0\xb7\x17\xff?\x06\xdb\x18\xf0\xf7+\xf3?[\rki\'\xfd\xdb\xbf\x18\xef\xd5m\x1f\x8e\xcb\xbf\xafjU-\xdb5\xe7\xbf\xd8\xd5%\x04\x1b\xef\xc4\xbf\xa8\x156\xea\xa7\xf0\xe0\xbf\x9c\xf5[\xce\xa5\x01\xe1?\xf9\x97\xcb4\x89\xc4\xf5?\x00v\x14o\xe4A\x02\xc0\x19\r\xd4C~6\xed?N{\xd3\x19\xc1j\xc2\xbf\xea*\xceh\xea\xe8\x01\xc0\xc08\xa1,5\xa7\xb8\xbfq\x90+6eZ\xe1?\x0c\xacU\xa5\xe6Q\xa1?\x90|\x19f\x8e\xba\xc9\xbf4q\xffZ;\xbc\xfc?\xaa\x12_\xbf\x9bx\x03@\x11\x7f\xe6E\x06\xb3\xf6?\xe0$\xb2y\xda\\\xf9\xbf\xd5\x1f\xed\x9c\xa7\xa2\xe5\xbfz\xd4\xc3\xcds\x03\xe2\xbfn\xb1l\xe8\xba\xc0\xe1\xbf\x13g\xd4>Z\x85\xe9\xbf\xa9\xebM\x03I\xf4\xe9?\x99\x9f\x8d\xf4\x9f\xb1\xbb?\xb8 \xa9y0\xb5\x03@\x99\xc3\x89\xc3\xe5V\xf9?\x9b\x8f\xd6\x1d\xbf\xf3\xe2\xbf\xf0\xb0J\xad\x96\x08\xd1?\xf8\xf8K\x99C\x1a\xfb?\x97[\x14\xa0N\xf9\xf2?\x11\xbe\xe4:\xf0\xd9\xd8?F\x0f\xe0\xbe\xcb\x08\xfa\xbf\xe3\xb0\xb4y\x0c\x98\xf0?\x16\x01\x15\xae\xca\xf8\xe2?\xbb\xb9\xbf\x02Cj\xb3\xbf\xd9\xd2\xcc!\x0b\xd4\xf4\xbf\x9f_\x18\x81\xb3\x0c\xe9\xbf\xd3\xf4\xd6H\xce\xb2\xf3\xbf\x8f\x03\xad\n\xa2*\xdb?\xb8Y\x9a,\x83\xc0\xf3?\xf3qM\x01%\xfd\xeb?\xa6\xf5:\xf5\x02\xab\xeb?br9\x83\xd3k\xf3?g\'y.}C\xf1?\xa7\x1b\xeb\x93k\xca\xd3?6\x90\xac\xdb\xe0\x97\xf1\xbf{\x8c\x96\xd8x\x0b\xf5\xbf(\xf5\xb3i;\xa2\xed\xbft\xf5l\xe1\xf6\xf3?\x15\x03h\xd7\xb8 \xff?\x8e\xaf\xc5{\x7f\x1d\xc2?\xb8c)ab \xc1?\xdd\xea\x1d<\xa37\xf9?\xe2RV\x988\xeb\xc1\xbf\xea\xd9\xe2*6\xbb\xd4\xbf\x80$b\x97\x92u\xb6\xbf\x0e\x0b%\xa8dL\xe1\xbfS\xc5. C\x1c\xd5\xbf\xdb}\r\xa9\xe4\x96\xdb?\'\xe1\x16[C\x8f\x87\xbfc\x0b(^\xd8\x8d\xfb\xbf\x88\x82\xb6:\xe8=\x01\xc0\xfdl\xa7*J\x01\xf2\xbf\x0fp\xd4\x8c\xd6\xe2\xc0\xbfb\xf3\xd1,\xdf\x9c\xf4?\xa3A\x84\xb2\x8d\x9a\xc5\xbf=\xa9Y\x80i\x08\x07@\x81d\xf8\x0e\x99\xcf\xc8\xbf\xadY\xc4!1\xfa\xe2\xbf\xf2\x94\x18\xcf\x9e\xcb\xed\xbf^X\xa4\xdd\xa26\xf1\xbf\'\xcf\x12\xfe\xcbM\xd0\xbf\xbbCzT)\x02\xef?\xbej\xa5ao\x86\xf7?\r\xd00\xa1\x13D\xdf\xbf\x16LW\x96\x8b\x9d\x05@\x86\xfa\xcdn\x8f\x14\xc8\xbf~s\x8a\xf2\xcf\xd6\xf1\xbf\xfd\xed\x82c\x0e\xb3\xf3?\xeb\xa3\x91\x8a\x14#\xea\xbf2d\xda\x81#\x19\xf7?m\xf2x\x00I\x82\xd6\xbf\xc8\xaa\xf7\xfa1"\xd2?\xf6\xbb\xf1w\xceE\xce\xbf\xe1\x15\xc1\x91w\xdb\x87?\xe0\xf36Z\xf5\xbb\xb6\xbfT\x01\xcb\xbfE\x00\xc7\xbfv\xfa\x99|\x95\x0e\xcf?\x820\x93U4\xfc\xe3?\x15R\xfc\x86\xc2!\xc7?\x8cB\xf7\xf1\x1a@\xf8\xbfQ\xf0\x93G7\x7f\xf1\xbf\xf7U\x814n\xb3\xdf\xbf\xf5\x98\xc5\xe4\xa4\x07\xe5\xbf\xab\xc5\xe8\xad~\xd4\xd1\xbf\'h\xf2\x17\xb1\x19\xe1?\x0c\x8f\xd2\x97$\x9f\xf1?U\xdb\xb0\xd9\xbd\xfe\xdf?Kv=\xde\xbb\x90\xe1?@\x8aDx-m\xf3\xbf\xe7\xdf/\x8d\xd5L\xf0\xbf4d\x14\xb35\xd9\xf2?>\xcc\xc6\x86\xb5\\\x02@\x86\xa1\t\xd1;P\xba\xbf\xf9\xdf\xbb\t\xb6\xa1\xcb?\x968x`\xdc\x8e\xf6?\xc4\xb9A\xae\xe2\xf1\xf0?\x9f0\xaeG2\xef\xe1\xbf\x83\x1c(s\xeeL\x05@\x96{\x87\x0f\x06\x08\xc8?\x87\xf4\x9cO\x15\xfe\xd0?\xe6\xb7?\xa7\xe4\x8e\xc7\xbf\xbe\xf2\x97\x1f\x92\xa2\xf4\xbf\x9b\xd4\xc2\xc7\xe0\xe7\xb7\xbfrt\xbe\x0f\xa2\xbb\xdc?a-wR\xba?\xf5?p\xfb\x00i\xd8\x12\xfe?\x86\xfc\xfb\xef\x06\xe1\xc9\xbf\xdc\xd0\xf5\xcfA\xa0\xf3\xbf\xda\xe2\xc9\x1e\x19\x8b\xb7\xbf\xdcvAqN^\xf8\xbfa\xe5\x87\xbb}\xfa\xd5\xbf\x1bD\x05)\x92\xa7\xdd\xbf\x0c\xa1\n\x94\x12\x9e\x00\xc0(\xdc<\xd0\xa8\x7f\xdb?\xb8\x13dV\x00\x0c\xd6?\xd6]\xea^c\x9b\xe3\xbfx\x1b\xb7\xe7\xd0\x0c\x08\xc0\xc6g\xb4&5,\x06\xc0\xef#\xe0\xee\xf3\x11\xf3\xbf\xb0An@2\xb5\xe5\xbf\xe5\x05\xeb\'V\xff\xfd?8\xd9}7\x01\x9f\xd0?\xb9\x1f\xfe(\xd8\xde\xe5?\x0b\xe1tkK\x00\xf0?x\xa6<\xb9H\xa0\xf0?\xbe/\x1b,\x08\x9c\xf5?W\xb3\xe09\xd73\xce\xbf\x9c\x93\xba\x1e\xec\x05\x00@F\x82\x14\x83U\xcc\xfd\xbf\xe5\xb5\xa5\xf7\xf9L\xe8?\xd4TR\xddT\x16\xeb\xbf f\xca\x88n!\xc9?9\xa9\x86F\xf8\xdd\xe5\xbf]\x18<\xe6\xa0\xd2\xf0\xbf*\x14\xc0\xccNg\xef\xbfG\xb7Lg\xda\x0b\xc4\xbfE\x05\x1c\xc1\xce\x9a\xda\xbfl\xe1.\xcfC{\xc1?o{\xf0\xfa\xbd\t\xe7?;N\xb4\xc7]7\xf3?\xd3\xb2)\xef+Z\xf6\xbf\xae\x841\xf5\xd6s\xd0\xbf\x05{\xae\xa9\x8f\xd3\xf8?\xfa\xfedq\x11\xe2\xec\xbf\xcc\xac\xd1\xae_Q\xdd?\x13\xd5_=\x07\x0c\xe5?\x1c\xe0\xaao\x93\x9a\xce\xbfR\xacw_7\x90\xfb?\xe3\xf2\xeb\x05\xb40\x00\xc0\xf2G\x9e\xe8\x06\xcb\xe2\xbfM\xbes\xfd\'\x84\xf6\xbf\x86\xa10\xd4Q\xfd\xef?Z4\x19\x87\xcd\xaa\xe9\xbf\xb6m+\xcf\x8d&\xfd?a\x8by\xd68\xfb\xf9\xbf\xe3\xf3\xb2\xf8\x14e\xd2?\xe7\xb9&\xbf\xdf\xaf\xe4?\xe4\x81\xe4LU\xd2\x03\xc0\xd7\x0b+I\xbe\x00\xfe\xbfU\xd7]\xdd\x08e\x08\xc0\xfeJ\xd4\x9c*\x9b\x17\xc0\xbb\xb6\xaa\xead\xab\x16\xc0\xb67)t@\xed\x11\xc0\x0e\x95a\x8b\x0b\x04\x14\xc0\x9c\x9e\x03\xfe\x03\x95\x11\xc0\x0e\xee\x19\x9b\xf2\x1c\x02\xc0\xb6\xaf~o]\x7f\xe9\xbf\xf7e\xbfV\x13\'\xe8\xbf\x9f\x82V\x8a9\x1b\xa5\xbf\xab\xf9\x07]\x06\x95\xe7\xbf\xfei+\xf8\x8fL\xf8\xbf\xd0\x96\x0e\xa2\x9d\xaf\xc9\xbf\xc4\xa3\xe8\xa4\x90U\xae\xbf\xd7a\xb4{^r\xbf?O\xf5=\x91\x9d\x02\xe7\xbf-\x14aP\x99\xa4\xdb\xbf\x1c\xe6\xe2[5\x0c\xe9?C\xba\xa7vx\xba\xb9\xbf\xcf\xc96*\x80;\xf6?9q\xa7\xcd@\x9b\xc4\xbfB&;\xfd9\xb6\xd1?6\x9e \xe99b\xf8?IY\xcen\xa6\x95\xe3\xbf46\xad\x9a\xbf\xaa\xf9?\xb4\x9f\xc8\x19\x91\xa6\xe6?S\x0e\xd1V\xb2\x01\xf1\xbf\xea8nN\xdb\xd7\xe3\xbf_"\x17\x1bH\xd7\xe8\xbf\x1e\xa5\xe9\x0c/\xa1\xfb\xbf\x1f\x18\x99g\xdff\x03\xc0\xcd\xdf\xa4=\xf5$\x15\xc0E@zI\x95\xff\xfc\xbf\xc2\x04\xf5\t1w\x07\xc0\xaf\xe2\x06\x12\xf4\xa3\x81?*>\xff\xa0\xfa\xcb\xff\xbf\xf7\x1f\xb3ID\xf0\xf4\xbf\xf6t\xe8\x17\xd62\xcd\xbf\xfe\xc5\xdb\xe7\xf8\xbf\x00\xc0\xb5\xb5\xef\xb2\x08\xf4\xb4\xbf\xfd\x92p\xf3\xf6\xe4\xf0\xbf\xb5\xf4K\xb3\xd1w\xf1?\x9c\x82\xbfw\xdbs\xf2\xbf{\xd9\xea\xf6\xef\xf5\xd5?K\xd37\xecFg\xbe\xbft\xe46n\x8a^\xb4?\xc0w\x97\xb84w\xe9\xbf3\x00}A&%\xf3\xbf\x80\x05*wK[\xeb\xbfQ\xd3\x019\x13\xa1\xe2\xbfe-\xf1\xf9n|\xdb?*7\n\x1cz|\xb4\xbf\xf7-\xf8\xad\xe2h\xe9?\x0e/\xad\x08v3\xe3?n\x0c\x06\xb4T\xb2\xf8?u\x0c`\x1f1\xb7\xcf\xbf\xee\xeb{\xc5\xf02\xe6\xbf\xfa\x999\x02E\xf8\xe1\xbf\xe3\xe8\x0eH\xd8\x85\xd8?\xd6\x88y\xfaXa\xd9?\x0f\x8c\x1bd\xc3\xb3\xcd?\xa2\x8a\xc5Q\x18\xa5\xb4\xbfzK\xb5umD\xd9\xbf\x06j\x1bO4B\xf9?\xdd\xb53\t6\xe6\xf6\xbf\x05\x8e\xb6:\'\xe9\xe5?\xe3\xe20\xc7\xab\xf9\xe0\xbf\xf2\xa7\xfa\xba\x1b\xf8\xda\xbf\x0c\xe62L\x0f{\xb6?\x07\xe1\xa6\xca}\xf4\xeb\xbfREV\'\xaf\r\xf2\xbfc\xe9\xda,\xa5\x83\xda?\x14"qb\xc8\xe4\xd0\xbf\xb5\x17\xf5&\'w\xf9?\x90P\xc2\x86\x1f\x15\xeb\xbf\x05\xc8\x0e\x068;\xe5\xbfL\xe4\n\xaf\xc8\x81\xf6?!\x17\xb0\x85\xd6\x12\xa3\xbf\x0f>\xe0p\x14Q\xf7\xbf\'4\xeb\x12\xaa\xef\xe8?\r\xf6\x05;\x8cD\xe7\xbf*LXA\x85\x0b\xf2?\xa8L\t\xe1Y\x98\xe9\xbfXq\xe0\xcb\xa1\xf1\xd4?\xf0\xd8v0{<\xc0\xbf\x90RQc\xcb\xc6\xf2?\xdd\xa6\xf7\xfc9\x96\xf5?\x8a\xd4\x1d\xcaA\x14\xfc?:/\x1f\x1f\xd4@\xf2?\xf0\x8c6\xbbG\xed\xe5?5\xa7\xed~M\x87\xe9?\xbb(l\xd6:\xb6\xe4\xbfRr\xaa\x8d\xfa \xeb\xbf8\r<>\x93\xb8\xea?\xc5sv\x8e5\xa7\xe9\xbf\xa1\xae\x83\xfcW\xfc\xf4?\xc3\x8f\x8e\xf0=6\xef\xbf\xeaP\xfd\x8a\xffG\xd6?\x8e\\\x17\x9c\x97_\xe6?Z\x04\xb08\xd6\x1a\xd9?5%l\x8b\xf5\xbe\xda?C\xce0\xe3\xf4\x07\xf2?\xf7\x84\xb6\xa2\x89K\xef\xbf\x00U\x024\x06\x81\xb2?F\xb2\xa9W/H\xd3\xbf}\xbbO\xad\xf3\x13\x06@M\x10~l\xb7e\xd0?\xdbc6\x99\xa5\x07\xf1\xbf#\x0f`\x19\xc7\xca\xd4\xbf\xa0\x83\xf3\x8f\xd3\xd1\xa9\xbf\xd8\x90\xb5pF\xe4\xf1\xbf\xf1\xe3\xd5\xfb\x96\xb9\xd6?fQ\xab\xe3y(\xa0?\x14\xae\xd2e\xe9\xfd\xa4\xbfE\xcaa\xe5\x9dA\xec?\x11\x9b\x97\x01%\xb3\xeb?\xda\xd8]\xc7\x16\xdb\xed?\x00S5q\xed\x85\xd5\xbf\xd6P\x02U\xd4\xb1\xdf\xbf\x1f\xa2\xdf\xacc\x9d\x90?\x1cj\xdc\x18\x1eM\xd3?\x92\x881\x96\xc1\x8a\xf8\xbf\xf8\x97\xbc\xe3\x10\xa8\xee\xbfB\xd4\xc5\xd3g\x97\xed\xbf_X|w\xf2V\xd4?\xf5\x8a8\xc6\xdd\xd0\xec?\xeeU\x1e\xee\xa4[\xdd?\x92r:U\xb7\xe0\xf2\xbf\x1c]\xc6\x01K\x96\xd6\xbf\xdabW\x8d\r\xb8\xf1\xbf)\r\xcf\x17\xb2i\xee?\x0e\x88\xa1M\xbe\x13\xfe\xbf\x05\x95\xc5M\xdc\xe0\xbd?~\xb3u\xad\x9aG\xef?\x85\xf30\xac\xa4\x86\xb1?#\xf7\x8d\xa0\x0fV\xf0?\xc0\x1d\xde\r\x0e\xee\xf8?\xa5$\xe5\xfd\x84_\xe3?Q\xd2\xc7\xd2$\xb3\xfc\xbf\xca\xd0\xb0\x9c\xa6<\xc2?\xb2kt\x18B.\xd6\xbf\x9dg\xf0~*N\xe2?\xbf\xae1o\xe1\xfb\x00@;_+\x98\xf1\xfc\xed?\xa1,\xcc\x96\xdcm\xc0?\xbeG_\x99"\xc4\x03@\'PAn7\xc3\x92?(.\xeb\xd4\xa7\xdc\xe8?\x8f\xd6NdJ[\xf8?\xda<g\x90\x95\xa8\xc6\xc8\xbf\x11\xb1\x0f#\x15\xf3\xe9?03\x89\x19\xddf\xf8?\xe5\xed\xf4\xa9\xaa\x15\xbc\xbfU)\xa4\n\x18\x85\xf1?Q\xfb\xa4\xeboV\xde\xbf4\xda\xe25\x91\x89\xf0?\xa0\x06\xe0u\n\xc8\xf3?\x1e\xc1\xb5\x01\xd6\xaf\xf2?\xa3\xdc\'Z\xf4\xe4\xb2?\xe1\x0bd\xd4\xe3\xf3\xe1?YbS.:\xae\xd0\xbf\x84\xd8\x85o\x80\x00\xa9?\x1a\xf2\xc60\x84\x04\xee?\xb6\x97c3\x14g\x00\xc0\xa4\xe1\x08\x19\xdb\x99\xfc\xbf\xd3\xf9h\xdc\xd3\xd0\xab?\xbf:C\xb0;\xc3\xef?\xbc}\xd4=4+\xbe\xbfh\x1b\xdfz0\x90\xe1\xbfuy\xb4\xec@\xe3\xea\xbf\x95\x07M\xad\x06\xd7\x98?\xcad^\x04uY\xb1\xbf\xbd\xb2gcCO\xe8?\xcbp\x03E\xf0$\xd7?\x89\nA1\xf8n\xc7\xbf\xc9\xbbj\xac\xa4\xea\xfd\xbf:u\x8b\xd2/.\xe7?\x18\x0e$\x07Y\xc2\xdd?u\xaf\xb1[0\xb5\xdd\xbf\xda\x95\xb5f\x803\xc5?\xb6\xbe\'\xb9\xd4k\xe3?\x8fB\x01\xe3n\xcb\xdd?\x12DH\xa6?\xd3\x93?\xf0\xb7\xa2\xa8\xc5G\xf0?\xb7\xc13\r\x88c\xd5?\xc6\xce2~ct\xc7?}F\xbf\xbf\xba\n\xeb\xbf\x903\xea\xfc\x16\xb9\xc8?&\xd6\xc4\xfc{\xb9\xe9?\'W\xf4\x8fi\xa2\xe9?\x8e_84\x1a\xfb\xf0\xbfY\xbe\x15\xd0\xa2\xac\xdb\xbf\x15\x83\x9b\xad\xe2\x93\xe3\xbfN\x92\'\t\xf6\xc8\xdc?\x03C:t\'\xd9\xd2?\xdd\xf0VlP\xa9\xf0?\x9fU\xfa\xf0\xa6\x99\xf1?\x87\xe3&\xfc\xd4\xf0\xfa?\x95\x98\xe5~e\xb3\xeb?)<\xb1\x8bd;c?9\xadjBa\xd7\xd5\xbf4\xd3F\xf3x\xf9\xd9\xbf\xa8\xfaM\xf13\x8e\x06\xc0-X\x80\xa9\xc0\x8b\xe2?X\x1fLvm\x08g? \xf1\x9b\x08\xdeJ\xb5\xbf?x\xc9\x9d\xd6U\xf6\xbf\xa2\x81\xcc&\x9a\x18\xc5\xbf/\xa7\xcf\xf0\x1a\xd6\xfe\xbf\xb3L \xa1\xcd\x7f\xe8?\xdb\tiS@]\xd7\xbf0Z\x7f\xb4\xc0-\xc0\xbf\xf1\xa6\xdbtVF\xcd\xbf\x0b\xed\x062\xdcE\xe4?7\xf4a\xcb\xd8\x0f\xda\xbfl\x19\x175\xf9\xe6\xe9\xbfq\xa3z\xf7f\xea\xf6\xbfF\xc5\xe7\x16\x1b2\xc5?\x99\x8a\xb4\xdd\x08\x81\xee?4\x95\xdbw=\x0b\xd0?\xabE(\x88\xbdO\xee\xbf \x16\xe5;!\xc0\xa3\xbfjz\xee\x01\xa2X\xc1?B\x0c;\xe8\xc6\xba\xf1?t\xb7M\x83\xc6\r\xec\xbf\x96h\xb9\xee\xcf\xda\xe8?CC\xa4\x10\xaf,\x8e\xbf\xfb\xe9\xf7\x9b\x9b\xaf\xf2\xbf\xb0Lm\x11\xbap\xd1\xbf\xcd\xa1`\x9a\x18\x86\x01\xc0$E\xd8\x7fB\xb1\xfd?\xf9\xf5\xb3M\x93:\xeb\xbf\xb7Q\xc2NOU\xd8\xbf\xd7\x12m\x89\x8c\xfd\xdd?$hA\xae\xda\x7f\xd5\xbfbihG\xbfr\xc6?n\xb9\x8aB\x12\x0f\xed\xbf`\r\xedj\xb0\x95\xf2?\x00ye\x7fDK\xda?\xd1\n\xda)\xaf\xab\xef?\x96\xcd\x94F\x12"\xef\xbf;\xd9\x0cY\xb2\x83\xd2?]\xf2\xa0\x88,\xc0\xfa\xbfN\x93\x9a\xaf3\xcf\xc3?\x15\x01\xb3\xd6\xf6\x1d\xe1?;"_r\xf4\x1a\xfd\xbf\xb4\x06\xfb\xd8\x7f\x84\xf3?>\x97{\x1f\xbe\x8b\xdb\xbf\x89\x14\xdf=\x18\xef\xeb\xbf\xde\xe2\xceW2\x7f\xba?\x9e\x190c\xec\x0c\xf5\xbf\xf1\xd2\xbf\xf8.\x8c\xe3?\xf8kV\xb3\xa5\xee\xe1\xbfi\xfe\xe5\xf5\x17Z\xdb?\x1b\xf3O\xc7%b\xe5?U\xc0\xc8+\x80#\xc8?\xfd\xe8\xbd\xa4\x0ch\xe5\xbf\xc6\xc2\x92F7.\xd6\xbf\\\xe8\x96-h\xdf\xe4?\xe1q\x0en\xfa\x03\xde\xbf\xd4Cm\xdc\xdd\xa0\xf1?\x0c<\x15f\x8aY\xe3?\x8d\xbb\xfa\x16\xf7\x1e\xde\xbf\xc4\x88a\x16/\xb8\xe8\xbf[\xa7u\x8c\'A\xf9?\x15\xa8\x01\xa5\xde\xb9\xf2?\x80\x85\xbc\x8c,@\xb2\xbf\xf3\xc9\xc5\x12\x91\xb4\xe3?4\xe5\x1fC\xac\xc0\xe2?\xe5\xc4t2\xfc\x98\xf2\xbf\xe1\xdc\xe5\xc6\x03\xc9\x89?u\x95\x94\xb0\x9c`\xee\xbf\xbe\xcdU\xa5E@\xd7?\xf2Hd8\xa7T\xfa\xbf?/\xf4\x11\x82\x8b\xb8\xbf\xbc\xa5tp\xa5\xda\xd5?\x87\xcc7l\n\x88\xd8\xbf\xef\x11Ly\x01N\x00\xc0\xc8\xcb\xa9Q\x06D\xd0?\xd3B/\xb7\xaa\xa3\xc8?\xfb\xbf\xe6\x83C\xbc\x07@\x8a\xb9\xdf\x00\xe3\x17\xf1\xbf\xe6o\xcb\x1e\xf9\xb0\xd1\xbfz\xcf\x92$\xe72\xea\xbf\xc8\xf1\x8a\xa9\x02\xcc\xcb\xbf~\x10/\x14\x91\xa1\xf8?%\xed>\x1d\xb6\xc9\xf0\xbf\xb7m%\x8a\x15\xbd\xe7\xbf\xfb\xdb\x9d\xe3s\xed\xfa\xbf\x9a=\xfe\x02\xbf\x02\xc8?S\x80\xc0}\x05.\xe4?\x8b\xa6Pp\x1a\xb5\xea\xbfl\x04\xc0\xf88\xc6\xee?;\xd0x\x99\xf8\xa7\xf1\xbf\xae\xfb\xa5\x95\xdc\t\xfb?N\xbd\xbd[\x19\xd6\xd7?\x81\xa0t\x02\'\xff\xe6\xbf\xc5R\xff-N\xc7\xfb\xbf[\xfb\x99\xb1\xe9\t\xc7?S\xae\xa7vI\xd5\xf7\xbf\x1d\x9eN\xfe\xc4e\xf1\xbfNU\xb1\x18\x11\xb3\xf2?G\x83\x12\x0bC\x1f\xc5\xbf\xb6[\xd3\xbc\x87\xc5\xe9?\x8aI\x9b\x86\xa3\x00\xf6?\xdbs\xed\xab\x99\xcf\xf1?\xd5\x11V<\xc7\x94\xf2?\xd0\x1fgs\xb1\xe2\xd2\xbfU\x1b\xc2=V\xa8\xc1\xbf>\xa1\xca\xea\xc6\xc5\xf2?\tLo\x9d2\xfa\xd2\xbf\x1f\x01\xcf\x0e\xac\xc7\xd3?\x9bbY}s\xea\xf5?\xc4\xb5\xc7\x8e\xf7\xa7\xaf?\x9c\xc8Q\x99\xecL\xfd?\xf5\xc3iK\x18\xff\xf4\xbf:r+\xd8J^\xf7?\xe8\x8c\t\xbf\xd39\xc6\xbfS\x9a\xbc`\xaf\x06l?\xf66\xc1\xd6\xf5\xbc\xd5?\xf3\xd29m\x11S\xe4?_L\xa3\x16\nl\xe8\xbf\x97$\x1b\xc2?&\xfa\x06\'\xa3\xc6\xe0?\xa5SkR\n0\xdc?\x96x\xa2\x8f\xa5\xca\xee?r\xbed%l\x1c\xd4?\xac\xe8{\x99S\xe7\xbf\x10\t\x88\x8d\xc3!\xe0?\xf1\'\xc6\x7f\x10\xdb\xf2\xbf\x87Woi\x0b\xa6\xc4?\xd3\xc8j\x05\x8b+\xdd?}yx\x81bk\xe1\xbf\x8dF\x92B\xf2=\xd7?{x\xa9\x9a{\x9c\xf2\xbf\xb8\x91\xe7\x86\x95\xc6\xca?"W\xcfXi\xf0\xf2\xbf,\xf6uNY\n\xe6?\x04\x02\xb9\xae\xe7\xf4\xd8\xbfjc\x88 \xa1\xf9\xef\xbf\x9e\x15|\x89~\xcf\xc2?\x02\xb8\xf64Nt\xe8?\x04w\xd4\x97\xa1\n\xdc?4\xa2\xb6S\xe9{\xec\xbf\xaa\xb5\xca`C\xcct?\xa0\x17\xb0\xf0\'\xdc\xc5\xbf\xa8\x88\xb6\x9d@p\x04@\xcfw\xc6\x8c\xed=\xed\xbf\xd4\xc8\xee0\xf9*\xd8?KD\xc5\x17\x18\xcc\xfc\xbf\xf80\xfb\xa9\xff~\xe0\xbf\xb2a\xb1\x928w\xd0?\x19+\xb7.\xa3\xf8\xf9?(z}sw\xac\x00\xc0\x08h\x01\x92\xe0\x18\x00\xc09\xd5md\x1d\xb7\xf7?_\xc3\xf2\xb4H\x16\xf1?\xdf\xfe\x91\xa3Y/\xe0?\x8c\x95\x1e\x00.\xed\xe9\xbf\xf4\x89R\xec\x00\xb5\xd9\xbf\xae\xb9\xae>\xe9\x9e\xe8\xbf\x1a\xe3\xa2\xee"\xb3\xf1?-\x0b\xb4L\xe17\xd6?\x11\xc3\xe6hj\x93\xda?\x1f\xb9f\x96=\x9b\xde\xbf\x85\xa57\xa8B\xe1\xf6\xbf\xc71~\xbc}\xdf\xed?\x85\xed(\xa0\xf3\xae\xf1\xbfx\xb2\xb4G\xb2\xe4\xf0?,}]E>\x02\xf1\xbf\xa5|\xa3q\xce%\xd7?\xec\xe9\x99\xee\xfd(\xe9?\xc3\t\xba\xbe\x8d^\xe9?h\xcd\xe7\xe7\xb2C\xb4\xbf\x9eP\xefZd\x11\xf3\xbf/\xdd\nH&\x03\xe5\xbf\xd3\xf3I\x82\x1aT\xe3?I\xd8\xb9lVo\xe4\xbf\xfa\xc5n\x9d\n\xaa\xe7?\xf3\x95X\xe8s\x91\xf0?\x8b\x02\xa8\x92\xba\xbe\xd8?!\'\xdc\xf0\x17\xf5\xe6\xbf\xd9\xf61\nB\x8f\xf9\xbf}/\xa6\x8f\x8bM\xcc?{,\xde1T\x83\x00\xc0w\xeb\xd8\x06\x0f\x05\x00@\xec\x1c\x117&\r\xcc?\xd4\x00D\xa7(\xc6\x01@\t7\x9dL\x06\xb0\xe1\xbf\xc4O\x17Z\xb9\xbe\xa0\xbfUTJ\xacn\xb2\xe9\xbf\xc7\x02\xe8\x15\xee\xe3\xc2\xbf\xf7\xe4\xbb|\xff\xf3\xe3?\x81\x03+i\x13\xaa\x02\xc0\xae\x88\xebz\xe3}\xec\xbf\x08Y\xd6\xc2l\x1b\xd6\xbf+\x067\x06V\xb5\xfd\xbf\xb4U\xd3k\xea\xb7\xe8?(\x82\xd8Z\x17-\xf5\xbfV\xff"\xb0\x1e\x1f\xe0\xbf]*qy\x82\x0f\xe6\xbf\xa01\x035\xc1\x85\xcd?\xa2\x18\xa8\xc4\nf\xcb?\x1f\xbd\x9b\xbc}y\xf6\xbf\xffo\x16\x83\xa6V\xee?0*\x98\x11!s\xd3\xbfA7\xc6(\x91\xef\xf1?z"\xf4\x99[\x04\xe8\xbf\xf3\x91\xc0\x0e3\x1a\xd6?\x0c\x92\x99\xa6\x00r\xf0\xbf\x1e\xa6VK\x99\x9d\xf0?\x7f~N\x97\x8d.\xf1?\x93uq\xef{\x87\xb3\xbf\xa9!\xa6\xc4#\xeb\xcb?\x81\xde\x0c=?I\xcd?\xac3j\x90\xe3\xe4\xe5?\xdeioM\xb2\x15\xf4?\xcd\xa2\xdc\x987p\xa0\xbfTz+%\xca\xe0\xe2?\xba`\x0c4\x16\x84\xf0?\\\xc5\x84\x03\xdf\x12\xec?k\xca\x14\xac\x85q\xf2\xbf\xdb\x0e0\xce\xa9\xb0\xf5\xbf\xba\xb4P\xf7\xb9\x8a\xf1\xbf:\xc8\x1fD\xa1\xe6\xe8\xbf\xcc\xb6\xa7\x89M\xf3\xf2\xbfz\x1c\xe9\x16F\x82\xf0\xbf\x9d\xa8UO\xf1j\xd4\xbf\xb9\xc9@>\xad\x89\xf1\xbfi\xcf`\x1a\x03\x11\xe8\xbfl\xfeT.\xa3z\xf0\xbfl\xb8\xd3\x06\xfe\x89\xea\xbf\xcb!\xfa\xca\xf8\xec\xe1?\xb2p\xc7\x8a\xad\x83\xe1\xbf\xad,\x13\x02\xce\xe4\xbd\xbf\xf5+\xe7D\xee\x93\xeb\xbf{\x9a\x87\xbcV\xd2\xd7\xbf\xe7\xc2>\xd7xL\x01\xc0\x1f\x9e\x94O\'%\xfa\xbf%\xa9\xd3yB\x1f\x06\xc0\xb0T\xfe\x82\x01\xb0\xb3?&+\xc9mc=\xe9?!m\x87\xd6\x9a\xf7\xf3\xbfD*\x95qj#\xfd\xbf\x19\x93\xe6~@\x93\xe8\xbf\x84\x8f\xf1|\x8bc\xe7\xbf\xb8tu\x04\xd4\x96\x92\xbf\xde\xd52\'\n\x16\xf0\xbf\xdb9\xe1\x02\xfc!\xd2?\xff<\xab\xf9\r\x9a\xf3\xbf\x86\x12u\xb6\x89\x0e\xe5\xbf\x80\xaa\xd0\xfb\xf0\x10\xf6\xbf\xb4\xbd\xc7\x96y!\xf2\xbf\xa1w\\\x1aB\xb1\xa6\xbf\xad|\xa3\x87\xfa3\xee\xbf[\xd39\x0cf\x83\x00\xc0H\x7f\x91\x91\xb9?\xf4\xbf\x8e\x83\xde\xb2\xf5\xe0\xac\xbf\xf8\xb1\x97SB\xdb\x0b\xc0\x06\x98$\xf9\xb7\x15\xc0?\xaa\xbb\xd4\xa8Rn\xf2?\x9c\xe0\xf2\xd5-\x90\xed?I\x81\xd5\xbb\x024\xcc\xbfP\x01\xe0\x1d\x1d\x14\x01\xc0\x01c3\\\x12\x8e\xf2?\x03\xea%YP\xf3\xe1?\xd4v\x03\t\x0bd\xe4\xbf!\x89Q\xa4\xf4\xa7\xf6\xbf\xa8\xec\xb9@\xdc\x99\xf1\xbf\x14\xcd\x96\xb9\x9a\xe9\xa4\xbf\xe2\x14\xa5\x8c\xe0\x97\xf6\xbf/\xe9\x90\x18\xe3\xef\xe4\xbf\x0f\xaf\xa6\\\x18\xec\x01\xc0\xf3Iy\xad9\xa3\x00\xc0Tr\xe9\xf9;,\xe2\xbfR\x90\x83M\x08\xa6\xf2\xbf[wL\\\xe1>\x00\xc0\x0b\xa3\xee&w|\x08\xc0O\x16B\x979\xba\xcb\xbf4 \x11\xe3Yd\x01\xc0\xe7\xa3\xb1\x14;\xbc\xfe?\x9cji\xbe\xda\x10\xf7\xbfg!\x02\x96\x14\xdd\xf4\xbf\x9e\xeed\xa0\x9d\xc0\xc1?\x87#\xdd\t\xdf\xaa\xa0\xbf\xee\xa0{\xf5X\x94\xb6\xbf\x10\xafY\xfc>K\xa8\xbf\xf6\xd8n"D\x8d\xcf\xbf_\xf8-\xe4b\xfa\xd0?\xf9\xe9\xca\xfcg\xdd\xc6\xbfe&\x13^m]\xf7\xbfv\xd4R\xad\x863\xf3\xbf\xf1%\xaf\x07aQ\xd0\xbf\xf0GPA\xe6\xc5\xf2\xbf\xb3\xecN8n\xb2\xe1?\x94E\\\x1e^x\xf0\xbf\x8b\'Pb\x87\xf4\x04\xc0E\xce,\xbd\xed\x0b\xea\xbfUO\xc0&\xa9\xf4\xbc?\xde\xb7\xa0\x12\xf3\xcf\xfc\xbfKvQ\xfa?\rp\xbf\xba\t)\n!\xf0\xd3\xbft\xb4\x99\x07\x86X\xf5\xbf\xf5+\x00\x83\xfd\xad\x02\xc0\x15\x1d\xf2)\xce\x1e\xf3\xbf\xad\xfe\x00\xf4\xa7\x18\xdc\xbfi<\xcc\xd1p\xab\xb9?{B~\x1aM\t\xd0?\xd4\xb8\x8a\x90\xa5\x9d\xf5\xbf\xfe)X\x9f>\xc6\x00@\xff\xaa\xce\xd5\xd6q\xe8\xbf|\x0c\x8a\xb4\x0ba\xe5?\x03\x9d\x84\x1d\xb3\x07\xe1\xbfPZj)`\x02\xd4\xbfG\x00M\xe6|\xa7\xe6?\xf1X\xf7\x9c\x90\xe1\xf0?\xd5\x1f\r\xc5\x83\xd6\xea?\x93\xc4\x05\x10\x80(\xf1\xbff%\x1f\xddx\xd4\xf0\xbf5\x8a\xaa\xcb\xcf \xe7\xbf\xdcT\x15\xf5\xa4h\xd6?\x0c\x17^\xaa\xc1\xb9\xef?(F\'t\xd4\xea\xac\xbf\xe6\x86\xf1?\xd2*\xe2\xbfF\xf2\x17q\xfa\x0f\xe6\xbf\x1b\x7f~\xec\'\x18\xfc?th\xe5\xee\xa3\x96\xf9?d\xfdm5\x85\xb0\xf4?\xe5\x9d\xed(\x08\xef\xd0\xbf>\x1d\x189\xcc\x83\xff\xbf\x01\x87\x90\xe4\xf7\x97\xf2\xbf\xe2\x99\xb0\xe3\x98\xa7\x03@\xff\xf3\xf5\xfea,\x00@\x16\xee\xf4"\x04#\xfd?\xcc\xe5\xae\xff\xff\x82\xe3\xbf\xa9\x8fl\xfb\xe4\xf3\xbb\xbf\xc1\xdb\x1d\x83\xfa\xda\xfe\xbfmW[\xd2\x07\xd5\xdf?$\x84:\xf9\x11\xaa\xb7\xbf^\xa5kY\x155\xae\xbf\xe3\xa3\xe1U3\xeb\xb5\xbf\x04\r\xe5\xa6\x98\xbd\xf0\xbf\x0fe\x19\xff\xe1\xb0\x9f?\xea\x15\x05c\x857\xf0\xbf1:\xcd\xb1!\xf8\xa8?\x81\x8e\xd5\x89\xf4 \xf5\xbf8F*\x8e\x91\xa5\xc5\xbf\x95\xe8\\\x8f\x8cp\xea\xbf\x01\xab9\xbc\xadd\xf9?\x11U\x98\x82\x8e\xbe\xb9\xbf\xda\xbe\xe04\x0fn\xf4\xbf;\xbf\xa6O\x96\xf0\xe9\xbf\x9fH;\x0eJ\xeb\xe6\xbfE\x96M\xe2n\x7f\xcd\xbf\xa9\xc1\x88\xaf\xd1\x04\x04\xc02zT}Wa\xaa\xbfl\xf6\xb4(\x9c\xee\xf2\xbf\xb7\xd2\xe2\x8e\n\xca\xfc\xbf(s\xcc\xab\x97\xde\xc6\xbf\xa5\xbb]6H\x18\xfa?4\x0b\xaa4I\xe1\xb5\xbf\xe2\xedh{\x9cI\xe8\xbf\x17Ai\xd9\xa4\xa1\xb5?\x1f\x1d#lI\x9b\xec\xbf\x88C\xae;\xc8$\x91?\x1c\xa0\x86My\xc2\x81\xbf\x13\xad\xd2<~\x08\xbe?\x8d5\x94\xf3j\x0e\xe9\xbf\x19\n\xdb%OX\xd2?\x16\xd1\x8fDp\xb4\xe5\xbf(2\x07g\xf4\x95\xe1?\xce\xa5Bw\xf97\xe7?\x1d\xcd\x08c\xefF\x04@\xe6zu\xf1\x16\xa3\x93?[q\x19`\xf0\x9c\xf5\xbfU\x14S\xe0\x15\x18\xe0?\xd1\x82\xe5\xd0~>\xe0?\xb23\x0f\xf5[\xa1\xf3\xbf\xe0\xca\xb6w\xa9\xe1\xba?\xf9\xd7[\x05\xfe\x1a\xb1?\xd0\xb3\x17\xbe]?\xe1?\xd8\xc8"\x95\xcc\xb4\xea?_\xac\xd2&"v\xbc?{y\xfe6\x8er\x01\xc0X\xddK/\xab\x8d\xe3\xbfoMW>\x86x\xf3?\x17\xfa\x84"<{\x01@\x92\x1c-\xab\xf8\xb5\xd6?b\x179WJ\xab\xe7?\xc8;5d9\xcc\x03@\xe5\xbd\x1aD\xda\xe6\x0e@\xfa\xa5\xf4\xae\xaa\xd2\xe3\xbf\x9d\xbbp\t\xa8X\x02\xc0\xba\xa3\x82\xef\xcc\x8d\xe8\xbf\x918j.|\x9d\xf5\xbf\x87\xbaB\xd6\x86h\xe7\xbf\xf1\xff\xe8\xeb\xf96\xf1?\t\x0c\xfe\x01\xf5\xe5\xeb\xbf\x85\xdc\xfcm^\xca\xe9\xbf\xddQB\xed=K\xe9\xbf\xe6L\xaeV\x82R\xd9?\x9e\xed/\xbe\xfa\x9c\xea\xbf\xf93\xb9\xc3\x12\x07\xd5?\xb1\xc0\xee\xf9\xce\x0e\xaa?\xf3\x0f^\xa3\xb7\xac\xce?\x17\xb3\x95\x02e\xc5\xd6?\xa9]\xa4="\x01\xf1\xbf\x17\x02>\x15\xe6\x04\xcd?\xec\xfc{\'(\xca\xe5?\x1faI\xad\x83\xadX?\xd1\x18\xfeD\x9a\xe6\xd1\xbf\x1cqMxx\xfb\xf0?(\x823\xb2OF\x02@\xd7\x95\xe8]Xs\xd9?\x03\x04\x91p?"\xfd?\xf2\xcd@Q \x8d\xd6?\xe3\x0f\x19\xb9\x02&\xfc?\xab\x17\x86\xd8{P\x07@\x8e\x11\xf0\xc9k\x1a\xea?\xb4\xb2\xd6x\x15>\xeb?r\xf0\xb8t\xe7\xb9\xf3\xbfGi\x98\x84\x8d\x7f\xf2\xbfLvj\x1d\x03B\xe1\xbf\x1f\xc9?O\xb4\xbb\xf9\xbf\xda\x04E(\x0c\xa8\x00\xc0\x84\xa2\xc8\xd1&8\x00\xc0[\xf3\x91O\xa1\xe2\xe3?A\xea\xd8#4[\xe3?\x03\xbeG\xb9E\x01\xd7?A\x93\xbe\x9a+Q\xf1\xbfc\x99\xf2\xc9\x8c\x16\xe0\xbf!\xed\xa5O\xd7x\xf8?\xaa\xf0u$P\xaa\xf4\xbf\xff\xf1\xdd\xea\xde\x19\xeb\xbf\x7f\x90\xc5\x8d\x04S\xf6\xbf+~\x9b\x0fKX\xcb?s\xb1\x90\x0eW\xa1\xf6?H\xca\x82\x9f8u\xb2\xbfP\xecj\xbf\rD\xf1\xbf|=\x93Bb\x9e\xf0?\xe7Hk\xcc>\xf8\xf8?\x1a\x00.\x0fR\xba\x05@\xf2\xb33\x88m\xc6\x01@\x04I\'\xf5\x80\x8d\xf1?\xa4\x13bp;W\x06@>\xfa\xaf\x92\xcbl\t@\x12C\xef\x89\xe6?Qi\xb7l\xc8\xc6\x00@\xc4<\x12O\x8d\xe5\xf7?@\xaa\x15\x8a\x1f"\x05@\x17Q\xc4\xbaO\xf2\xe8?\xf3~\xc2\xac\xf6J\xcc\xbf\xa2>\xae\xf8\xd1\xd8\xe2?\xa7Xp\xcb\xe1\xb6\x05\xc0\xde\x8d\xd4\x1c\xdb\x18\xf1\xbfQ\xbf7I\xb3K\xd3\xbf\xe6,\xd8\x0f\xd4\xfc\xdd?\xe8S\x02\xb1\x8e@\xaa\xbf\xaf\xcf\xde5\xc2\xaa\xc4?@4\xed\xdf\xef\x08\xf1??\xab\xdd\x05\x9c\x93\xd7\xbf\xa0$\x18\xf4\xe8\xec\xf6\xbf\x00a{\x16\x8d8\xe5?E9\x19K_\xeb\xfb\xbfffm\xbc[\xab\xf2?R\xa6^\x93\x85\xfe\xf2\xbfv\xd7G\x16\xa2\x1d\xc0\xbf~\'u~]o\xed?M\x95\x9f5^\xfc\xf7\xbf\x9f\xa7\xfa\x17_\xb1\xb1\xbf\xbd\x1b\xfc\xa6\x15\xe2\xd2\xbf\xd3\'\x11\xac\xc0\xf0\xf4\xbff16(\xb9q\xb8?\xb8\x9d\xdbn\xbf)\xec?\xf2\xb6\xd7\x1e\xe8L\xdc?kK\x80?;\x87\xe1\xbf\x08\xca\x92\x9a\xde\xa4\xd6\xbf\x99\xda\x8e?\xac?\xfc?\xe7\x87\x89WJ\xd4\xf4?\x985##Yk\xe9?\x1dO}v\xbcI\xdf\xbf\xda\xfa\'\xd9\xa7`\xe4\xbf\xbecs\xcc\n\x97\xe8?\x06\xd51\xcb\xc0\xe8\xc5\xbf\x86%\xc9\xdfT\xb9\xd2\xbf\x8f\xdd|\x90\\\x8b\xd4?_\x9fh5\xbcD\xf1?\xb0\xa9\xb9\x90H\x10\xe4\xbf$\xb9\xa8^\x00\x11\xed?\xaa\xf4\xf0[a\x8a\xe8\xbf\xfd\x96\xd0@N\x0b\xe0\xbf8\xd1\x18\xe7\xb9\t\x01@\xa6LR\x0fD\x0b\xd5?q\x85\xf0\xa0r=\xe7?\xc3\xfa:M\x993\xe2\xbf\xbd\xd2\xb9\xefH\x14\xea?\x0cP\x0fU\x8e7\xeb\xbf\x7f\xd7\xad\x0f\xd7n\xd8?\xbd\x04s\x98\xe9G\xeb?\x96\xebxq\x98\xc0\xcd\xbf5\xfe%T\x89\xd9\xdd?\xea\x9b\x1c\xfd\x17\xb8\xeb?a\xda\x9b\x13i\x96\xea?r\xc6\xe2\xbeO8\xf2?bi\x96Z\xc8/\xe0?\x11\x97\x89\xbe\xd6\xad\xef?5\x87\xe5k\xa7\xc3\xff?\x92\xe2\xeftM\x92\xd4\xbf\x10\x1b\xf3\xa6 \x08\xcb?\x82\n\xbd\x07\x00\xd3\x06\xc0\x8c\xae\x12\n\x15\xfd\xbc\xbf\xc9"\x98#\x16\xa4\xec\xbfZ\xd43\xaa\x01\x02\xd0\xbfo}\x03"\xc4\x97\xdd?\x86\x1f \xfe\xae\x05\x01@?\xc9\x81#<\xc2\xd7?U\xea\\\xae\xe5\xbe\x00@3\xbc\x98l\x855\xf7\xbf\x0c\xf4\xf0S\xc9\xfd\xd4?\xf7c\xb2\xd0W \xa7?t$\xdc\x14\xfb\x12\xff?<\xbd)\xa1K5\xf1?\\\x1f}\xa2Y\x1b\xdd?\x08\xee=\xff\xb7\x88\xd1?\xac\x1f\xafP\x8f\xc6\xec\xbf\xc4\xf3*)\x7f\x92\xf9?\xe3\x18\xf0\xd0I\x14\xe2?\xb5\xb5\xe7:T\x97\xf6\xbf$\xa3$\xf0\xd6\xed\xf5?Qs\nWS\xe7\xf9?\x14\xad;\x17\xc2E\xfd?\xaa\xd8`\xa0\x81Y\xad\xbf\xbd\xc3\x08\xb6W=\x06@\xcfk\xc3Y\xf8\x98\xe0?E\x8b\xfb\xcey\xd1\xe3\xbf{0n#\x84"\xec\xbfn1\xb5\x0c\xcb\x14\xde\xbfJK\xe6y\x07\xe9\xeb?\x85D\xc7\xf6X\x85\xd0?\x03\x04\xc6DL\x0f\xf3\xbf\xb4\xc2A\x80\xab$\xe6?}\xa0$2\xe3\x1f\xef?\x1e\x94Fh\x8d|\xf9?\x85\x8a\x1a\xdb\x00O\x02@\x83\xab\xe1Z\xeaE\xf7?uA6\xe3\xd3\xcd\xf9?\xc0Oq\xa4\x11T\xea?\x0e\xa2\xc39\xf2\xa7\xe6\xbf\x10\xe61aC\x95\xeb\xbf\x8b>\x87\xfa\x98H\xd2?\xad8o\xbc\'7\xc1?\x92-`\xb7rX\xf6?\xd2&\x85"S\xfa\xdb?[1\x8a/\xbf^\xf3?\xf9Ri\xce\xd1U\xd5?\xc4\xf8\xc6\x02\n\xd9\xe8\xbf\xa9\xd1t\x8f\xb4\x0c\xf1\xbf\x13\xac\xad\xc6\xc8\x08\xe2\xbf\xbf\xdcUQ\x8cC\xf4?\xa22S\xf4\xc7g\xd6?k\xf9\xb1\\S{\xee\xbf:O\xd9pk\x9b\xea\xbf\xfc>\x0b\x0c6\xe3\xe9?c\xc9\x9eE\x8c\x06\xc9\xbf\xb4\x8e\x0c\xcd\xfc*\xf0\xbf#\x1d\x9a\xe7a\\\xe2\xbf\x8a\x94\'\x95\x9a\x81\xb3?8\xed?:L\xc8\xc1?R\x9e2\x06\xcc\xc1\xe8?]$\x80\xee\xa0E\xe1?\xc6.Q\xf4\x8f\xa7\xd0\xbf\x882\x87d\xb2l\xd9\xbf\xb3g\xe2}\xbb\xed\xf4?\xeb0\xa6\xb1\xc3k\xd6?\xbd\x1b\xde\xa5\xbe\x9c\xb3\xbfm\xaf\x96\xbco\x8e\x07\xc0\x85\x810\xf8\x8fp\xf8?[]\xdf\xa5\x89\x11\xf6\xbf[{\x07\xbc\x1f\xf1\xee?-`\x18Aa\xfc\x05\xc0\x04\xc5U\xf0\x14n\xe2?~w\x05\xebd\xbe\xdf?\x84\x8a\xb5i\xe4\xf0\xb0\xbf\xa4\x88\xcb\x81\x0bF\xf7?7\xec\x19\xf3"\xa3\xd2?M\xd0\xa4f\x8b\x1e\xf7?\r\xcc\xe5 \xedL\xf6\xbf]\x9e3\x89`\xe4\xe0?\xd0(\xf9\xbf\x0cd\xe0?Tgg\xb1h\xf9b\xbf\xc4\xd5oa\x84\xc5\xc2\xbf\xf7\xf9\x11+\x92\xbc\xe7\xbf\x92\x8a\xe2\xe77M\xf8\xbf\xe3\xf4ue\'\xd5\xed\xbf\xb4E\x0ca\xfb\x0c\xe9\xbf\x0e6 \xef\xc0M\xfc?\xaa{\x88~*\x9a\xeb\xbfk\xaa\xe9\xb5\x0b\xe7\xf3\xbf\xa0#\x8b\xfb\x1f\xb7\xdb\xbf\x9c\x18\xbe\x13\xf5>\xb1?\xf4\xe5\xc4\xfb\xee\xec\xd2?^T>\x1f\xac\xdf\xe4?\x99w/R*\t\xfc?\xd8\xfa\xe8\x89\x96\x91\xe8?\xfdke\xc3\x01z\xf3?,q\xf4(~)\xf4\xbf\x1e\x19\xd1#\xd6\xbf\xf1\xbf\xaf\\\x9eN\x01\x1c\xf1\xbf\x9d\xb1\x0e\x04\x1b\x97\xf0\xbf\xc8\xca\x18\xde\xc0t\xfa\xbf}\r#\xdcD\xef\xf6\xbf\xe8H\xfa\xb0by\xb6?n\xb4;`\x91\xfb\xdb?Y%+{\xcd\x8a\xc1\xbf\xb5\x7f\x8e\xean{\xd2?\xd1\xe9\xc8\xb1\x85r\xe4\xbf\x80!\xff\x0f\xa1q\xe1\xbf\x7f\x06\xd4\xb9\'\x18\xe1\xbf"\xde\x08\xd1\x96\x1f\xe9\xbf\xa3\xd3\xe2\xba\x8e?\xe1\xbf\xfdg\xb0B\xd3{\xd2\xbf\x8a\xd9\xd8)\xae\xe5\xdf?\x83\xb6\r\xb9\xb3Z\xe6?\xdb\xb6%W\xf9\xf4\xb6?\xbb\xa8\xdeGq\x1e\xe6?x4\xae3Fi\xf3?\x88\xb1\xde\x940\xef\x00\xc0\xd9\x84&\x89\x81?\x94?\xab\x9c;\xe8J\xfd\xe7\xbf\x9bM.\xaf5\xe5\xea?\xef\xf9D\x9e/\x05\xe6\xbf*q\x96\xa2\x14\x08\xca?5o\xdb2\xe4\x81\xee\xbfsRCf\x0c\xe4\xa7?ST_\x06\xf8I\xdf?=\xaf\xdd\'\xe3Q\xe4?\nE,BPy\xf3?\x8c{\xee-\xe3\x9b\xc6?\x1fqhY\x80\x04\xe4\xbf*\xd0\x9fX\x87\xa9\xd6?\x08*\xb4\xa8\x1ah\xe5?\x05\xc5r/\xf5\x8a\xc4\xbf\x13\xa8\x1fM\x1e\xe8\xfe\xbf\njS\x81@\xbc\xf0?\x00[\x19\xd6O\x92\xfe\xbfz\x8e\xb4\x9e2J\xf3\xbf\xc5\xcdFz\\K\xe9\xbf`\xee\xd4W\xa2\r\xed?\x9f\x81(b\xe7\xbf\xc3?\x8aO\xe5\x88\xd0\xc4\xef?\xc1\xb5\xd4\t\x93C\xe2?\xc6\x12\xa60{\xc6\xf2\xbf\xb7\xa2\xe6|.\x1f\xd5\xbf\x1a\x94n\xf0T9\xf0\xbf\\\xa8\xfd\x82\x15w\xf0\xbf\xeb\x01\xcd)\x99\xcc\xea?\x08\xf0\xc6\xc4\xa4D\xd2\xbf\xb3\x12\x1eR\xf2T\xf8?9\x01\x0bT\r\x03\x9a?\xeb\x94\x0e\xb8\xce\n\xb2?\xa4-e_\xbd\x80\xeb?\xddYp\xf6\x01\x89q?r\x91\x1e\xc5\n\xd7\xe6?\xf3\xea\xc9t\x91\x81\xe4?\xbf>\x01\xc3\xfe\x0c\xd8\xbf-\xe6\x8b\x81\xd6\x1a\xf3?\xa5\xe2\xf1;\xbc5\xce\xbf#d\xa0\xf3\xecR\xb3\xbf\x8f\xd6\xd2b\xf0N\xe7?\xbd\xaeg\x1a\x81\xae\xf8?\xd5\x0b`\x9d\x1b\xea\xc7\xbfiu\xce\x1e{\xec\x00@\x8f\xe4\x1f\x924\x10\xa4?\xe4\xa3\xff\xeba2\xf3?\x83V\xb3^|\x9c\xe1\xbf\xe9?\xc1\xf1?\xa7\xee\xbf\x9e\x8aR\xf4x\xfa\xc6?\x8c\xbc\x87\'\x07`\xef\xbf\xd9\xb8\x92\x9a?.\xf1\xbf2*j$\xf1\xab\xb7?\x88\xed?,\xf1\xac\xe2\xbf\xf43\xdd\x03\x86E\xef\xbf\xd1\xfcN4\xd3@\xd0\xbf\x0f{r\x93H\x81\xb8? \x1d1\xc0\xdc\x1c\xe1\xbf\xe7\xec\xc6\xadD"\xe6\xbf\x96S?Yt\xf9\xe8?\x16\xa12\x80\x11\xc6\xd9\xbf\xb9\x86\xae?2\x91^\xd3\xc4\x10\xf9?\x9e\x1d5\x9d\x8f\x8f\xfa\xbf\x87k\xa7\x11D{\xf1?\xf6\x1c\xca\x0e\x9f\x94\xe3?YW\xfa]\xe0\xb4\xf1?\xa6\x9b\x1cjL\xe6\xf6?%E$&\xec\xad\xa2?)\x83\x9f+Gi\xf1?\x07\xeb\xc4Ix\xd5\xd2?nDm\x0eI\x04\xc3\xbf\xfcGK;\xb9b\x04\xc0\xbd\x02\xa6\xdd>\xff\xca\xbf,\x80\x07\xcd\xa8\xf4\xf2\xbfw\xaeT\xd7\xc9\x8b\xf8?\xbc\xce6\x93p\xc6\xd5\xbfV\x90\x151\x85v\xbb?\x17\xab}/\xfal\xef?\xd7\xcc\xe3\x14P|\xc7\xbf\xa3\x8a`b\xe5I\xfe\xbf\xf5\xd1\'\xc9g \xe8?\xb4\xd4\x10\xe8q\xa8\xcf\xbf\x02\x8c\xc1\x8c\x8c\xc8\xf5?\x86z_|jU\xa6\xbff\xbe\xb0\x9e\xc9\xa4\xe1\xbf\xac\xc8\xabg?\xdf\xc5?)n\xf9\xeblM\xf0?\xbb\xd9=_\xd0\xf3?\xc7t\xf8\x962\xed\xf5?\x98s\xf5\x92\xf7n\xf5?\xb3\x9f\x87\xea\x92.\xf5?\xc3\xa5/O\xda\xe0\xe0\xbf\xcc\xc9\x07\x7f\x05\xb1\xd9\xbf\x9f\x16\xad\xf0\rY\xf1?jy\xc9\x9b\xe4\x8d\xcb\xbfe\x80\x9auj\xb3\xe4?"\xe4n\xd45\x84\xd2\xbfB.\xa1!\'\xd9\xe7\xbf\x86\x08\xda;\xf5\xdc\xaf?\x95\x18g\xe8k\xcd\xf1?\xf6|k\x9cP\xb0\xfc\xbf\xab\x85*9/\x92\xd5?\xcc\xb29\xfe^\xc2\xd9\xbfb\xae\x7f\x908\xd1\xe1?\xfaV\x93\'b\xe1\xc4\xbfYQ\xa1m\xcf\xd3\xf7\xbf\\\xcf\xc1\x16$\x1fe?\x9a\xf2\xc6\xaf\xfb4\xd0?\xf3&\xd9\x8f(z\xe1?\x17+\x1f+\x19\xac\xf3\xbfV\x9b\xee,\xe8\x18\xc8\xbf\x85e\x11\xaci\xf3\xf7?\xadxj\xa8\xc4\x1b\xdc?\xc8\xd6\x92\xe2H\n\xb0\xbf\x84\xaco\xaaY\xc2\xdc\xbf$8\x8e#\xad\x11\xe7\xbf\x08cn\xb7O*\xf3\xbf\xea\xba<\xffmt\xcf\xbf\xdb\xb8S\xeb-\xd2\xe0\xbf\xb0\xb7\xab\x9b\x93\xfc\xca\xbfKx\x93\xfd\xc6L\xdd?\x05~\xde\xa0\x91\xe9\xca?\xb5\xe6\x16\nb\xae\xf4\xbf+\xcb3d\x17\x0f\xf4?\x8f\x06\x83\xc6s\xf1\xe9\xbf_\xb3\xc7\xd4\x9fP\xe6?\x00\xfb\xb0\xe5r\xa8\xf7?\xef\xe8\xac\xd7$\xaf\xf1?:6\xf4\xce\xe6\xce\xe1?\x88\x0f \x98.\x1e\xcc?\x97\xdc\xc3w\xc5\x01\xea\xbf\xac\x15D\xa1\xff\x15\xf5\xbfXJC:\ts\xd5?\xe2~\xf7=+\xbb\xd3\xbf\x88O\xd1j\xba\xf2\xd6?\x94\x03\x82\x03\xad+\xe0?OFr\x92\xe0\xf9\xd2\xbf3*\x8et\x06\xe3\xd7\xbfJz\x04\xbb\xd5\xe8\xbe?\'R0\x06\x992\xbf?\xc4IY\xf3\xe6\xc3\xf3?\xd9\r\t\x8c\xa4\xdb\xa1?E\x9a\xd0\x081t\x00\xc0\x08\xe0\x05\xec\xc6\xc1\xfb\xbf\xef"\x98\xb8\x0f\\\xe6?:\xdc\xc9j\xad\xfc\xf0?m\x10fm#N\xeb?Yt\x00\x87\xeb\x94\xbc?\x0f\xe88\xa9\x07\x86\xe9\xbfVzy!X\xdc\xe1\xbf+u\x0f \x1e+\xd7?\x90\xaeP\x8dA%\xe0\xbf<&\xc5\x87HL\xf9?Y\xf2\x04z\xe9\xbd\xf1?I&\xd2\x07\xe7\xb2\xf4\xbfR\xa5\xc7\x11z\x0e\xf2?\rU\xd2CZ\x1d\xf6\xbfYFy\xbe&\x10\xeb\xbfe\xc5\x84i\x87\x14\xe0\xbfoz\xfbk\xc3(\xfe\xbf\xdbC\x10\xb2\x8a\x1d\x01\xc0\xacsU\xdf\xdc[\xeb?&l}h|v\xf5?\x0e\x01\xcb}\x1e.\xe3?B\xd2J\xaf\xbfb\xfc?\xce\x02#\x9f#\xf2\xc8?\xdf\xdct\xcb K\x00@\xe3zW\xbe\x89\xcc\xde\xbf\x00\xb2\xe2\xde\x8d)\xd5?\x88\x06\n\xc4\x17\xef\xd5?|;>\x0bC\xec\xed?\xd3\xa3?\xf76\xea\xcf\xbf\xba\x1dTr:\xf7\x92\xbf\xd0\xbdUb\xda\xc0\xc7\xbf\x81\xedS\xbc<%\xfb\xbf\xe48\xc5`\xbf\xba\xee?x\xf5\xa7\xe1\xe8~\xe7\xbf\xad\x1e\xbc!\xaa\x92\xf0?\x8f\xb1\xf2\x1b\xb5\xea\xe1\xbf\xa5\x9a\xb9\x8d\xea\x9d\xf0\xbfz\x07\x99\xf5o%\xe1?\xd3\xf5\xdb\x7f\xca}\xf0?\x85\x9e\t\x18\xc8\xd9\xfa?\x86\x15\x9c\x16S?\xef\xbfu\xa4 \xff\xc8\xa6\xd5\xbf\x1f\xb7\xc8\x9e\xd5"\xbe\xbf:\xf0\xc0rY\xac\xe5\xbf\xec\xa0\xc8\x19?\xb7\xf2\xbf\x88\x89\xa5\xbf\xb1\x92\xf7\xbf\xb5\xa3\x050\xc3\x8d\xd5\xbfM\x82\x80\x8f\xa4\xf6\xe8\xbfX\xd1%!\x19?\xcc\xbf\xc0u"}\xe6\xdb\xb9\xbfYM\x94pS\xa5\xdc?c\x04\\\x9a\x1f7\xeb?\xbd\xa7hX\xd5\xf3\xad?4\x88\x024Pn\xf2\xbf\x98\x16\x134\xf3\xe0\xae?$\x89\x14\x076\xfd\xe5\xbf\x83\xc6\xe9\x87\x17A\xfb\xbf\x9b.(CpH\x9f\xbf\x0c\xc8\xf5\xe7?\xb2\xc7\xe14|\xdf\xf3\xbf\xc9\x0f\xe0\xd0\xdfv\xef?\x11\x9c\xdb\x9d1\xef\x8b\xbf\x88\x04j\xd3\x16\xdc\xd8? [?\xce9n\x02@\xd3Y\xb4\x01\x05?\xb0? k.\x1d~\xda\xc4\xbf|c\xc2y\xee\xf5\xa1?\xacc\xd2\xe2\xca\x16\xdf\xbf\xff\xa9\xc7\xed\xf3M\xea\xbf\xd6\xf8\x7f\xb2O\x89\xda\xbfN\x04\xa1\x10I\x9a\xe7\xbf\xf7\xd1D\x12T\n\xf9\xbf\x1ftBO\xe3\xd6\xe8\xbf\x13\xdf\xfc,fm\xd1?\x8c\xf120_\xc5\xe1?8GD~\xd6\x88\xeb\xbf\\\x9d\xf1J\x9e\xdc\xff\xbf\xe4\x8a\xd9\xd9\x86_\x08@z5\xfc\x8fr\xaf\xfa?\xba\xe11}\xb2\xe2\xb5?\xd2\xfe\x86\xd3\xc8\xfb\xc9?\xdcW\xc0\x11\xa6>\xd9\xbf\xac_9-\xe69\x05\xc0*\x81A\x13\xaf\xfa\xa2?9\xc2f\xce\xb8\xb7\xf0?\x9bzL8\xae\x8a\xd0\xbf\xbc}\x0f\xcd\x86\xab\xcf?\xea\xcbDJ\x01o\xec?A\x9f\xcekdH\xe7\xbfr\x96\xf5\xce\xb6\\\xdb\xbf\x82\xcf\xcd\xdez~\xd8?\r\xf5L\xff\xad=\xe2?\'@\\=pS\xf0?m\xd5\xc9\x02\xa7\xae\xdf\xbf\xdcy\xdc\x83\x8c\r\xf9\xbfe\x11s\x90\xeb%\x83\xbfm\x13\x7f0\xac\x81\xf5\xbfY\xe7\xbdk\xc8\xe8\xe8\xbf\xe2v\x84\t\xb2\x1c\xc5\xbfT\xdd\xff\xa0W\xfc\xf2?\xf6WF\xb2c\x9a\xe1?\xa8\xe8eog\xec\xfb?\x0fG*\xae7P\xf8?\xfd\xbb\xf5:\xb6\xb3\xe6\xbf\xc3f\x9fXEH\x9f?\xf9_z\x1b}\x8d\xef?\x0c\xc0\xc1\xb79\x17\xf0?\xbb\x87\xaa\xf9\x9e\x91\xf3?AH(\xee\xe1\x8f\xd6?\xf0\xcb?\xea\xca\xe2y\xbf\x19\x88\x92\x1b\x98\x02\xa1\xbf\x9d\x829O\x82p\xf3\xbf\xd5\xd8A\x1e\xd8\x80\xf4\xbf.\'\x07\xe2n\xe4\x00\xc0d;\xc8\xca,\xf6\xff\xbfW\xf336\x94"\xf8\xbf\xfb\x00\x1b0X\x94\xf8\xbf\x92|XD\xcb\xb1\xf7?\xf3u\xefB"i\xdb?\xe1\xbcc\x7f\xce\xda\xe8\xbf"\xad\xd2\xc9\x94\x02\xf4?\x12\x1b\x90a\xc3t\xf3\xbf\x84\xd4\x93-\xb1<\xee\xbf\xa4 \xc8b-A\xfb\xbf\xfc\x0f~Kx\xcf\xea\xbfO|}\xfc\x14\\\xb9?\xb7\xf3\x00\x0c\x9a\x11\xf0\xbfgdx\xefA\x91\xd2?pF5"K\xc9\xff?\xa8*/\x96\xa4\x00\xa7\xbf\xbd\xec=}\x1bL\xe5\xbf\x7f\xd4\xc5Y6\x00\xe2?\xf1\x07@\xfc\n\xc8\x02\xc0\\\xce\xc2u\x16\xe6\xd3?\xedQ\xeb\x94u \xfc?x\xc2d\x98:^\xf1?U\xf0KZoM\xe7\xbf\x923\xbd\x87\x0b\xb4\xf8\xbfk\x01\xc5M\xe5Z\xea\xbf\xe23\xad|\xb7A\xe6?lI\xf0\xd7*F\xf6\xbf\xbb\xa0\x88i\xc6\xc5\xe2\xbf\x11,+\x14\x92\x89\xf8\xbf\xbf,\xc8\x0e\x10\xdd\xf0\xbf\xb6X\xad\xbd\x02\xbe\xf6?\x0cb\xe8\xca\x02)\xe3?\xca\xd0\xbe\xe9\xa8-\xe0?\x9f\x08\xea\x1b\xdeA\xdb\xbf\x9f\xa8\x15)!\x86\r\xc0\xe4\xd4\x9b\xd0\x85\xfb\xe6\xbf\xd7p\xc7u\x90R\xf7\xbf\xe0\xf57\xb4\xd0j\xf8\xbf\xa4x\xef!/7\xe9\xbf^\x00,\xba\xfd\x02\xf1\xbfp\xe5f\x13\x82;\xeb\xbf\xb8}\x0b\x88v\'\x05\xc0\xa9\x1d\xba\xed\xfb\xdf\x03\xc0ZQ+L\xfd\x1f\xfa\xbf\xc1\xfb@y\xf3v\xd5\xbf\x88\xa9\x08\xeb*\xc5\xed\xbf\x8b\x84\xa7\xbaU\'\xeb\xbf\xd3\x8cT\xd1\x86\xb9\xed?\xfa\xb5\xa5zt\x1f\xfd?\x96\xb7\xe3e\xbf\t\xf1?\xd8\xc5\x8f\xdeE\xcd\xfb\xbf\xb4\xa2&\xbfF\xa8\xd2\xbf\x02E\x1e\x1b\xf1\xa4\xe7\xbf$\x06\x01\xc8\xeb"\xcb?\x12`<\xb2\xe4)\xf7?\x07\xdb>B\xa9\x0f\xf3?\x8a\xdc\xe3>\xa7g\xe7?\xcd\xacT\x05\xdcu\xd2?\xc9o@\xb1\xad\xfa\xfe?\xa3\xbf\x96z\xc4V\xf0?\xeb\xdc\x808\xaaA\x95?I\xc8\xee\xc4\xad3\xf7\xbf\xb9F"\x11\xb6]\xfe\xbfa-\xe7\x97\xef:\x0c\xc0\xda\xc8S0\xec\xec\xff\xbf\xd1\'\xe3\xd3\xc2\x01\xfa\xbf5:H\xe5\xd4}\xfd\xbfs|\x98|?\xce\xff\xbf\x15\xadrd\x8ex\xd3\xbfz\xe3\xd8=\x99\x0f\x03\xc0D\xa6\x8cW\x1a%\xd8\xbf8\xc6\xe6\xe25v\xfd\xbf\x18\xf8\xc4\xbc\xca\xcc\xdc\xbf\xd1(\xd9\xc5QM\x00@\xad\x95\x1a\x1c\xafY\xe3?\x17P1\x073,\xf0?S\xec\xcac=\xa7\x07\xc0D\xd9\xf93\xad\x85\xf9\xbfk\xabh[L\xd1\xe8?\x19\x8a\xffw\xa0\xb9\xd1?\xeb4\x14X\xb6\xb9\xdf\xbf\xf3\xda\x00\x02s=\xf3\xbf\x9a\xf6\x9bN\x91\x1e\xe0\xbf\xe6\x03\xe4f\x80^\xdc?\x98<\xd3\xb4\x08\x01\xc1\xbf\xd8\xee\x870\xe7Z\x03@\xd3|\xd5\x0b\xbaT\xeb?\xb7z\xa7\x15\xbde\xed\xbfo\xda\xc21\xd2\xf1\xfb\xbf\x0e\x9d\x86\xef\xb5\t\xf3\xbf\xa8^\xe6\xcc\x05\xfe\xcb\xbfu(Wy\xfe \xe8\xbf\xb6\x82~\xa0=u\x00\xc01\xe9\xce\xa5O/\xec\xbfDc_k\x0ch\r\xc0\r\x9d\xa3\x05f\x03\xf0\xbf\xac\n\xe4\xedmU\xbc?\x0bt\xb9\xfa\xf0J\xee\xbf\xf26\xa5\xa0\x83\xbd\xf7\xbf\xdd!\x9e\xa2\xb1\x92\xdd?/"l\\\x94\x8f\xe2\xbf\xef\xda\x05fM\xea\xc0?\xe9_\xae\xbd%c\xed?\xef\xdaFP\xbf\xec\xee?J\xbd\xc1a\x05p\xfa\xbf\xa0\r}tm`\xfb?\rx\x9e!\x14\x05\xee?\n\xe6$\xbd,]\xd1\xbf\xcb\x8a9Uh\xde\xf2\xbf\xccZf,H\xe4\x02\xc0\x8c\xde\xbbb\x93\xe2\xf6?s`\xb6\x98{\xfd\xef?\r\xf8R\x1d\xc1\xf7\xf1?t\r\xb1\x9e\xef\xd0\xa0?|||*\xfa\xe5\xfb\xbf\xef\xd6\x11\xc6\x01d\xfd\xbf\x99\x88\x96o\x1c\xb9\t\xc0\xa8\xe5?d!I\xf2\xbf\xc8\x91\xc2\xcb\\\xac\xd2\xbf\x9c\xc3($JL\xf9\xbf3\x0bj\x1a\xea\x9f\xb1?F\xcd\xd1\xcb\x1b\xa9\xf0\xbf]{\xc9\xa8\x9e\xb5\xe0\xbfX\x9e\xf0\xf6\'\xa0\xe8?\xc1\x87\xc6WVL\xfe?M\x8b5\\\x18\xa4\xe5?$\xde+\x00\x05}\xf5?\xa5Bi\xa9\x19[\xdd?\xbc\xf7L\xc9m\xc2\xd1\xbf\xc3\xb1<\x84\x00S\xe5?@\x03O\x99\x10\xaf\x00@k\x1b\xe0\xe0\x06\xb6\xff\xbf4\xee8\xcf\x7fy\x00@{\x9e\x81\n\xd9)\x00@&\xed\xccb\xb4\x94\xb3\xbf\t\xb9\xc7\xcbo\x07\xe2?\xa1g\x8f\xb7\x7f\x9c\xe3?\xabp\xda\xbd\x86\x00\x01@\xf6?X/1|\xd6?\xe4\x8b\x10\xf9\xb0\x00\xbb?\xfeS\x80M(\x0b\xe7\xbf\xf3\x9aF\xb0gF\xc3?\x10bSHd$\xe5\xbf\x10C\x83\xcd#\x8e\xc9?\x12\xa6cv\x97\xd8\xf5\xbf\xbe\x89\x06,\rc\xa7\xbf\xc8\t0\xd7\xa9\xe8\x00\xc0I\x1c\x12z\xba\x99\x00\xc0\xc5l\xf6\xd8\xe6]\xe6\xbfg\xbdK\x08\x04\xb6\xf8\xbf\n\x87\x9e|\x90\x9d\xc3?\x8c\x99\x8d0\xe1\x18\xe2?c\x07\xb3F\x1c\x85\xf6?n\xe5nY\x0e\xb4\xf8?\xc09,\xfc\xba \xd1\xbf\x1b\x14\xe3,\x8am\xe3?6N\x9f\x84U\xa4\xd1?DD\xd2z\x17\xcf\xdc\xbf\xe8\x88E>-b\xe6?<\xb0\xdd*8\x03\xde?\xef\xcc\x1a,\xa0\xb0\xfa\xbfS\x04\xdc\t\xe3\xdf\xd8\xbf\x81,\x11yx\x7f\xf0?\x9c\xde\xbbHZ\xcb\xe9\xbf\x0b\x1e\xdaLRp\xe6?\xbe\r\x95\xfb\x90\x18\xd2\xbf)\x8c\xc9\xd8a \x02\xc0\xc4\x18\x85T\xd4\xb8\xf3?Y\xc0\xdfq\xcf\x10\xe0?\x8b\xdf[\x01F\xfb\xc1\xbf\x01\xcf\xc0\xaf\xd2\xc5\xe6\xbf1\xd8\xad#;\x15\x00\xc0K\xd5Y\xc8\xc2|\xe0\xbfl\xf1\xf6\x83\x9c\x0f\xd3?\xea\x92 s|\x92\xfe\xbf\xab\xca\xf6\xb7N\x89\xf1\xbf\xf5K\xbfs\xeaH\xe9\xbfc.\\f\xb5\xf2\xed\xbf\x81\x94\xf4\x8cI\x81\xf0\xbf&}m\xb8\xda\xb5\xe6?^\xcb\x0e\x03\xf8\xa4\xb7?Pz\x15O\xdb\xda\xd6\xbf\x01\xd7m\xcb\\\xf5\xdb?\xc4y\xa5\x90\x1f+\xfa?T\xddX\x8f\xc1\xc7\xd6?Z\x1d\x9f\xc2\x83\xf1\xcc\xbf\xed\x98\xfd\xb9=V\xd7\xbf\x131\xfdG+\xf5\xc4\xbf\x089D\x14R\x12\xde?\xb0\x02o\xef\x19T\xcb?\xa8C\xd4\x81k\xbe\xfb?P6Z\x19\xa7\n\xd3?\x1a0\x88\x8dY\xaf\xf6?\xcb \xd6R{\x8a\xf9\xbfo\x97:\xf1B"\xd0\xbfS\x87\x96\xe2<\xca\xe2\xbf]?\xb8\xc2\x0b\x92\xfc\xbf.(:j;\x1a\xcf\xbf\xf7\xf9\x1f\x8bBR\xc2\xbfD<\x11\x00p\x8b\xe6\xbf\x1a2}I\xb0\x9d\n\xc0Z\xe5>\xc8D\xea\xfc\xbf\x9b\xa2\x86\x01\xe2l\xf0\xbf\xeaD\xed\'S\x1a\xec\xbf?\xa2\xef9Q\x18\xbf?\x9f\xa3\xf4\xb2*I\xef?\xd0\xbaC3T_\xfb?\xe0\xf9nx\xf6\xcb\xf0?3QV;\xcf\x9a\xef?\x1ef\xfb\xf0\xcc\x8a\xd5?L\x803\x11\x88T\xf3\xbf\x83C\xc0k\x05\r\xd2?\xb9\xa7G\xadde\xe0?\x16\xef\x9a$\xb8<\xa8?\n\x08\x08:\xb5\xbd\xd0?\n\x89\xb9\x9d\x04\xc0\xfa?\x98p\x81\xee\x00\x87\xd1?:\xe6\x8e\xb1c>\xe4\xbf\x17\xe3WS\xe2\n\xff\xbf\xd5\xed%/q\x90\xf1\xbf\xca5\xb6\x1aZ~\xf3?mX\x12\x97B\xda\xe9?S\xd2\xcar\xe6\xff\xc6?G\xd9\xa4{%(\xe5\xbfpUQ\x118\x80\xd1\xbf\xa2z\xf6\xad\x17\x8f\xf4\xbf\x9d\xa7~\xe5E\xbb\xf3\xbf\xf6\xa6X\'\xd3z\xf5\xbf(\xfb\x82i\xa3\xff\xc2\xbf(\x9b\xb3\xd8\xa4k\xd2?\xc4|W\x9fW|\xc1\xbf\x82\x0e\xc5\xbb\xec\x93\xfa??\xbc\x8b>\'@\xe2?\x03D\xa9\xa9g\xef\xfb?\xaeI"\x12!\xfb\xfc?Ag\xeeh\xb5\xcc\x05@d\xbe\xa48T\xc3\xb9\xbf0\x08\x07\x88h7\xf4?\xdf\x7f\x96~\xd5\xbe\xe4?Wk\xbe;H\x8f\xcb?\xdf\x11\xd5\xfa\x8a\xfb\xd1\xbf\x87[\x96\xac\x10\xfd\xde?F\xe4\x02r9\xa3\xcf\xbf\x17\x12\x8b\x10\xcf\x81\xfd?Q1\x01Sqm\xc6\xbf"\xc3z/gd\xe8\xbf\xb8o\xe3\xe1\xeb\x88\xfd?\xe2\xe1AS]\x08\xfc\xbf\xa8\xc9X\xd5\xa6V\xdf?\xfd\x8ds\xdfB\xa9\xe2?Qc\xf7C\x86\x86\xfb?*\xd9@\xac\x00\x1f\xf4?\x85\xea\xd9U\xd5\x80\xd1\xbfx8\xa4\xe11\xd5\xdb\xbf\xf2b%\xa7\xf7/\xe0?\x0b\xb4\x15\x8dx\xb1\xd1\xbfm\xaa\xe2\x9a\xb4\x9f\xe7?\xe1R%\x808\xd2\xc5?S\x84\\\xc0\xf6\xef\xf8? \xa4\xd1\xdd\xcc\x94\xc8?\xe8\xcc\x14\xad\x94M\xf3?\xbcK\x0e\xe5\xdf\x94\xf4?\xdd\x95g#\xe5\xc9\xa0?}E\xf7x\x0c\x0c\xcc?5\x918$\xb5\xf0\xfe?\x10IA\x7f\xd5\xc4\xf3?\xc9p\x1d\xd2?\xdd\xfa?g\x80&$\xa2\xf0\xec?X@\xfb\xb3F\xef\x02@\x9c\xc3\xfe:\x1d`\xe8\xbfF\x83\x1a\x8c\xe4!\xd0?$m\x87\xff\x83\xfc\xb0\xbfF\xcd\xd0p(m\xfd?\xfd$\xd5\xadkD\xf0\xbf\xa3=b?t\xb6\xf2?\xf5d\x93J.\xb0\xe4?\x0c\xc9\xb9ZwL\x02@+\xaeu\xa5\xe1\xf3\xf3\xbf\xcb]\xd0\x92C]\xca\xbffr\xddD\xd9\x93\x00@\xe5`\x17\x0b\x85?\x00@e\x81\xc6\xf2\xa3\xc6\xf7?\x11\xd2\xcd2\xc4\x88\xff?\xb6\x1b\x9c\xfe\x98\x14\xd9?\xee3<\xea^\x1f\xd5?\xee\x05\xaf\xf1\xc7\xd0\x01@\xa12}\x99\x05\xd6\xfd?\xd8\xa6\xe02ld\xfb?\xf2\xf3\xbd\x1b\xde\x13\xf8?\xb7\x83\xa9\x93\x12\xd3\xff?R\xaaE\xfb_\xb1\x04@\x82\xf9h*\xc7\x11\x02@\xd8\x11\xe9`\xa3\xb0\xdf?s\xa8c\xad\xd0-\x01@\xae_0\xd6\xb6\x0b\xc7?\xe7\xca\xf5\x93\x87}\xe3?H\t\x89\xa8\xcd\xcd\xe2?\xe4H\x12\xb8\x97\x9a\xe1\xbf\xc0@\xbc\xb5,\x1a\xe4\xbf\xb1\x89\xe6\x93\xea\xa8\xb4\xbfO\x15\x17\xf4\x82f\xbc?\x1c\xbb\xf6m6\x0e\x02\xc0I!\x02`\xe45\xe1\xbfp_T\xe6\xafO\xf3?)[\xe4\xf0\x96\xed\xef?\xa0\xac$ l>\xe0\xbf\x97/\xae\xe4\x10M\xd3?k@L\x1f\xca\x88\xfe?\xf8,d\xd3\xd6\x1f\x01@\xdd\x14\xf1\xf1\xbb\x05\xf1?\xea\xae\xa7i1\xbc\t@\xf5\xea\xa7\xb4\x18S\xf1?\xc6oV\x9aLS\xff?0\xce\xaceX\x8b\xfa?\xea\t|D\xa0\xb5\xee\xbfOc\x8e\xfc\xfb~\xaf\xbf\xba\x100\x9d\x8f\xbd\xe3\xbf~\xcc2P\xee\xba\x00@~FL\t~\xf0\xdc?v\\B\xb2\xbc_\xe9\xbfk\x93~*\xd0\xdd\xc1?\xf5\x83\xdb\xdd\x94\xa6\xf1?\x05!7\xa5\xf7|\xf3?\xce\xf6\xc0\x83K^\xfd?3r\xe2\x1d\x810\xe8?\xa9\xbb\x17\x850\xa9\xed?\x7fL\xe5\xb0\x15\xd4\xdd?\xde\xeb*\x84=+\xf4?\xe8\xe48\xe4\x7fy\xf7?\xc2_\xeb#\xe8d\xfd?\xe17\x1d\x0fE\x8e\xd2?#\x162\xab\xf3\xc8\xfd?\xbd\x13-\x94\\\xf6\xf8??\\\xe2\xfe\x8a\xd0\xd2\xbf\xb9\xdf\xf5$\x1e\n\xe8?\x1f\x0eO\x8d5\xe2\xa3?+v^h\xdbz\xec?\x91?0\x17\x898\xd9?\xf3\xa7\xabn{\x1f\xcc?q\x97MD\xce\x82\xff?\xaf\x10I+\xc7\xd4\xfa?\xfc\xb5\x8f\x8d{\x10\xf0\xbf\n\xb0\xbc\x8bu\x17\x00@#\xb0\n\xea\xfd\x98\xf9?\xben"-\x93\'\x99?L%\x8f\n\x84\xdf\xe8?\x04\xee\xbbY\x16#\xf3?\x98\x1f\x88\x86U\x9b\xb9\xbfod\n\x0fVm\xf5?\xd4\xcc8_z~\xf6?\xf1\xd5\x01I\xc6\xed\x7f\xbf\xb4\x16\x86\x93$\xcc\xd5\xbf\xbb\xba\x92\x85\x17\x8b\xd1\xbf\xe17s\x18\xdf=\xd0?ZR\xa0\xf9\x04\x87\xce?C0\xd0\xfd\x86g\xc1\xbf\xf7\x0cP6\x11\xa7\xe3\xbfi\x19r\x86v;\x9e\xbf\x8b\xc6\x0c\xbb\xb6\xde\xe7?\xf3\xd5O\xb3\x0b\xa2\xd5?Ac\'\xa2\xa7{\xf9?\xd7>V\x9d\xfb\x83\xf1?\\\xd2~\x95\xe4\xa6\xf2?\rG\x84\x1f\x82\x84\xf9?k\x96\xbc1\xafY\xf8?k\x87\x8dp\xdc\x0e\xfb?QZ\xb9\x0b\xf2\xba\xf3?b\xa1\n]\xa7\xeb\xc0\xbfT\x9bl\xf6a\xf5\xed\xbf\xebh\xc6\xae\x0c\x8e\xe8\xbf2\xfc\xder\xcdw\xb5\xbf\xdb\x84\x86\xb6\xbf\xa8\xeb\xbfy7;\xdd\x0b\xcf\xc0\xbf\xeeV]\tV\xf7\xf1\xbf\'P\xab6\x1cu\xe9\xbfTO{\xdb)\x12\xf2?Z\xack`F\x14\xfa\xbf^\xffWb\xb4\xa5\xf5?\x12&}U\x110\xf1\xbf\xea\xfe4\xf8\xec\x02\x00@\xe9\x90\x82jD\xe0\xee\xbf\xc5\xc7Iy\x7f\x00\xee?\xb6\x96\xc9\xac\x07\xad\xce?\x8f\xd6"\xa7\x15Y\xd3\xbfy\xcfg\x02w\xc6\xf6\xbfdv|\x90\xcd\xc8\xf1\xbf_W\x8c\x89\x97\xce\xea\xbf\x18\xf5\x99\xe4\x0b\xc0\xe3\xbf\xad,\xd8\xcb\xe0\xfc\xfc?I\x92.\xea\x88\x96\xfc\xbf\xb9\x1f\xd8\xcd1*\xa8\xbf9S-\xba\x8a\xc7\xf4\xbf^\'\xf0B\xf7\x9c\xc6?\xba4\xd5\xe7\x94\xa2\xf4\xbf-\xe2b\xe1-\xdf\xd9?\x9b2t=4\x11\xe1\xbf\xcb\x8a\xfa\xf8Yv\xe8?\xbb?"\x0baD\xdd?;\xb1 f\xb8w\xfc?U\xdb#b\x06\x0e\xf5?\xb2\xd9\x1b\xc8 \x9e\xdb?\x0fk\xee\x96\x06\xbb\xed\xbfs\xb8K\x99\xbf\x01\xf7\xbf\xa7w\x00\xcb)\xe9\xcc?`d\x1d\xc4\xd5p\xea?^\xb5\x87\x18\xb32\xb8?\x82\xdaH\x93V,\xaa?\x9b\xaek\xbe\xffg\xdf\xbf\x0e\x0f\xbcku\x1c\xe3?\x95#\xd7\xcb\xe2g\xfb?\xa5\xe4\x9a\x10\x9f\x07\xad?gv"5\xd5\xb0\x00\xc0Za\xf1\xc1\xa1\xa6\xeb?\xce\x8b51*j\x8e\xbf\x85\xe7s\xdbu\x0b\xf6\xbf\xddy\x11\xae\xd6/\xf2?\xfa\x11\xcf[\xa4\t\x08\xc0\x85\xd9\x04\xc4\xbd3\xfd\xbf\x8aZs\x01\x0e-\xe7?T\x88T\xc2X\x94\xdc\xbf\x13\xcc7P\x9f\x87\xf6\xbf\xbd\x11\x15\x07\x08\xbc\xef\xbf\xb1\xa1\xe8\x13\x8aP\xea?\x19\xfe\'\x7f\xe0W\xc7?\x80\xc9Q\x9du\xe0\xef?g\x81f\x11\xb0\xd9\xeb?\xb3\xe9\x80\xf0c\x9a\xf1?\xc6\xb7\xac\xbd\xba\x89\xf7\xbf\xa9\x94\xe3b[w\xe9?\xaf\xe1\x16\x1fG\x84\xf5\xbf\xf2W\x92\x1fv7\xf7?r\xd9\xb4F\xd4\x82\xd6\xbf\x92il\xfb\xe9\xdf\xf3?\xe0\xd9$yk\x8c\xd7?!\x9eg}\x1bz\xc2\xbf\x8e\xe8\xa3F\xb1N\xf1?\xf7\x04\x89\xc2\x7f\xaa\xda\xbfA\xe8\xd8Z\xecf\x92?\xd9\xa7/U\x8c}\xf9?\x00R\xab(}\xfc\xf1\xbfVB\x17\x04\x83Y\xf1\xbf\xa7_\x14\xdeZ(\x04@\x7f\x07\xff/\xa1\x9e\x8c?\x11n\x8c8\xac\x14\xd4?NU9\x04\\\x1e\xf1\xbf\xb9\xba\xb8\xfdh\xdf\xf0\xbf5i%\x81\x04!\xcc\xbfP\xf1\xe4t\x90!\xda\xbf\xd6\x8ae\xc6Wh\xeb\xbfkH\x9a\xb5@\x0f\xd3\xbf\xbb\x16\x94,4\xe2\xb2?\t\xc2\xdeW\xc2\x8c\xc4?\xfay\xb2\xb7\xb4\xfc\xa7?\x15"\x87l\x02\xf9\xb9\xbf$\xde~\x84\x02\'\xd4\xbfuC\xf3}\n\x8d\xf6\xbfb\x872\xb1x\x92\xb4\xbfT\xa4K\x00L:\xfe\xbf\xf70)\xa0\x07\xb6\xe7\xbf\xa2\xa53\xcf*\xed\xec?+d\xcdi:.\xeb?_\xff\x03\xe3\xbf\xc2\xa4\xbf0\x8c;\x98"@\xf6\xbf\xf2\x8f\xf1\x01\xfb\xdf\xe0?\xfa\r\xdc\x10\xba\xb6\xe6\xbf\x14l\x82%\xee\xb3\xcf?\xc7R\xa0\x9c\xab\xfd\xee?\xd7B\x84/\xc2\xf0\xa9\xbf+J\xeb\xef\xf1n\xf7\xbf\xed\x89\xcf\xefuy\xe6?\xd2\xf6\xd7\xfa\xd4\xbd\xe3?\x8fm\xe3\x8d>i\xe8?\xc0\x07\x1e\xa3\xf6\xe7\x03@\xfa\xb5\x84\xa9g\xf6\xcb?\x8fp\x86e\x18i\xf5\xbfd3[\xd2\xf7\x89\xfa\xbf]\xeb\xe8\x97\xa5\xa9\xe2?\x81O\x1b\xf2\x86\x91\xe0?\x89\x1c\xb0\xf4<\x13\xf0?]\x06\xc2\xe5,\'\xe0\xbf\xfcd\x93"z\'\xe1\xbf\xf6\xbe6\x1e\xcc\x1e\xed\xbf\xcd\x88]\x10\xe5\x0c\xda?Kg\xdc\xa5"\xd3\xfe\xbf\x03\x9e\xbe\xfb{\xf7\xfc?\xd4\xbb\xfa:\xa0\x91\xf9\xbf\x871\xf9g;f\x08\xc0\xd9\xa5`AI\x11\xe1\xbf\xc3z\x8c\xdf\ti\xf3\xbf\xec\x8bIq\xd1D\xd1\xbf\xe5\nx\x89^\xde\xc9?4\n\x18>\x141\xf9\xbf\xadw\xf5\xbe\xceu\x82?\x99\xd2U\x06\x8f\xe8\xd0\xbf\x08v\x0b\xcd\x7f0\xf8\xbf9mv\x80\x10\n\xfb?3\xad\xabM\xe2\xf3\xfc?\x82\xaek\x9cL\xa1\xf8\xbf\xe3\xdb\\\x94\x851\xf4?\x81\xab\x05\x1biK\xea\xbf\xbc\xd4\x89\x81\x00\xf2\xef\xbf<\x1d\xe4\x1b\x1d\xbf\xf1?v\xc5\xcd\xb3z|\xdc\xbft\x9c\xb7\xd1\x04\xc2\xf1\xbf\xbc\x99\xbb3\xb3\xca\xf0?3=\xa0B\x0b1R?\xe08\x0f,N\x18\xf0\xbf}\xc9\xc7\xf6H\x92\xda\xbf\x95~\x07\xbe\xbc2\xf0?\xe1\xd2\xd0\x0b\xe0\xd8\xdb?\xf9\x8d \x1eY\x06\xc4\xbfGj\xd6\x85k&\xd9\xbf\x97\x99\xa3\x0c\xc5M\xf0\xbf\x06\t\xf3m!\xc1\xc4?[\x80`\x8cVY\xfc?c\xad\xa1\xea\xea\xeb\xf4?\x91G\x87\x91\xa4\xd4\xe9\xbf\xa2\xa8\xe0\xa9\xcb\xf6\xb7?\x91\x03\x99o\xbf\xe0\xb4\xbf5\x96\xdef\xac\xb2\xd1?\xb4\x1a\x1c\xb4\xfeB\xd9\xbf\xb0\xa0.\x9b\x05Y\xf2\xbf\nr\xf5\xfe\xaf\xe4\xce\xbf\xbc\t\xf5G;\xd9\x9e?\x06\x08\x8b\t\xb9\x04\xf2?\xca\x0e\xa7\xec;\x9e\xe3?\xb9\xd0G\x00\xc3;\xd3?!VV#\xbf\xa6\xee?K\x19\x0bC\x18M\xcb?H\xf3\x98"\x8a\xd5\xcd?i{Qp\x8f\xd1\xd9?qrII\x96\xa8\xe0\xbf\xd4\xc5\xebd\xedJ\xda\xbf\x03\x16Z\xe9\xb6\xa4\xff\xbfm0\xb0H\xb6\xa0\xd2?\xd8\x8aL\xa9\xbc\xf6\xd6\xbf\xb3-]\x1b,\xff\xec\xbf\x90A\x9b\x1b\x11\xa1\xfa\xbf\xd0\xa1,\xf1\xd3\xea\xf7\xbfs2/\x98]\xea\xfb\xbf:d\xe2r\xc0\x04\xde?\xf8|\xb8\xc8\x9d/\x01@\x87C}\xba\x0eb\xea\xbf\xb2\xd2\xd4\xe1\x04`\xfa\xbf\xdb\x85\x18\x8b\xbe\x1b\x00\xc0\x9d/\xbb\x97\x95{\xe1\xbf\xa6:\nM\xf1A\xb7\xbf\xa8\xaf:\xf8d<\x01\xc0i\xd0\x86u\xf1\xfd\xe0\xbf:\x88\xae+\xe7\x81\xf2?\xd1AdTX\xe1\xf4?R\x16\x0e\x90\xf7\xbc\xe9\xbf\xaa-R67"\xf7\xbf\x89<\xc0\x1c$\x85\xeb\xbff\xc4\xae\xcf\x9a\x0b\xe4\xbf\x06\xbe\x11\xe9B\x06\xf2\xbf\x16\xc7\xf58\x10\xe7\xd4\xbf\xfdH\x9f\x071\xb8\xf1?\xdb\xa4\xf5\x8d2\x81\xea?,*\xd7\x8e#z\xe9?%\x8a>\x81\xa4\xb0\xdf\xbfo\x84\xbav \xfb\xa5?<\xf2\xcakV\xab\xc8?\xcc\xd3\xa8\xbe\xfb\xab\xf0?\xce\x8c\x16\xb8\xfe\xe3\xf7?\xf4\xe3\x88\x14\xd8\xc7\xea?O6\xa3\x12v\xe2\xeb?\xa5\xdbi\xaf\x1c\xe0\x01\xc0\x9a\xc0\x01\xa7\xc1\xb9\xe2??\xfe\x93Sl\xef\xf3?sO`0\x08\t\xa1?\xf7\x1b\xad\xef\xe7\xf4\xae\xbf\xbd\xeb}\x07\x0f\x03\x03\xc0r\x94\xe1c\xe4\xfd\xfc?7\xe4\x92\xb3{\xfe\xd4\xbf\xed\x89\x81^\x19\xe3\xf2\xbf3-\xcb\x8b\x8c\xa5\xf5\xbf\xb4\xf0W0\xa1\x9a\xe4\xbf\ti\xc6\xd2\xbf\x11\x89\xde4an\xf1\xbf\x94\x19\xb4iL\xbf\xd7?\x99\xc9\x12\xd3\xbf9\xf2?\x1emW\xa9\xedE\xe6\xbf\x1b\xa8S:ji\xef\xbf\x8bx\x91\xef\xd8\xab\xf9\xbfo\xe1-S-\x10\xd3\xbf\xab`_L\x90\x19\xdb\xbf&\xaf\xe5\x84\x0f\xb1\xf9?Q\xc8V\xfd\xb3\xee\xfc\xbf\x0b\x84\xb1\xb6\x05X\xd6\xbf\t\x12\x19M\x93\x9d\xf2\xbf\xd0\th\x04\xab!\xe0\xbf\xb7\xd1^\xf9\x82\xb7\xd1?\x98\x96\xe2J\x94\x17\xd1?~\xc3\x8d<\xea\x8a\xfd\xbf\xd6\xf0\x99\xdb\xed\xed\xe0\xbf\xc0J!#W\xde\x05\xc0\xde8T\xfa\xd5\x9a\xf6?\xd1\x1cB\x81:\xd7\xe9?\t\xe6Y\n\x10p\xf1\xbf\x8a\xe5\x87N))\xe0?m\x80D\xef\xfb!\xc5?\x14\xa9\xa0\x8b\xfa\x81\x00\xc0\xf2\xbc->L\xac\xf5\xbf^\x0f!.fx\xe1\xbf\xf2\xd3\xf4\xadw\xf9\xc9?Rw\xf0\xd1\xa9\x94\xf4?i)\xed\xf7\xe7g\xc4?\xeb\xe77\xab\xbb\xe8\xf4\xbf\x08\xb9\xa6m\x1d\x0c\x00\xc0\xc4\xeaY\xe0SH\xd6\xbfR\xb3-R\x81\xb2\xf7\xbf\xa47\x17dj\xbd\xe9\xbf\xe4(\x9c\xe1\xe2\x81\xce?$\xba;8\xed6\x07\xc0\xae^EeX\x8d\x0e\xc0\xe6\x93v\x14\xc7z\x10\xc0\x83p\xc9=\xd0^\x03\xc09z\xb45\xbf\x1b\xfe\xbf\xaf\xb30z\xb3?\xf9\xbf\xcf\xbaACM\xf6\xeb?M\x14\x9ek\x1b3\xca\xbf\xd0!\xf2e\xe2-\xf7\xbfLN\xf3\x1a\x8f\xa7\xf8\xbf\x8b \xc2\xc0l\x9f\xf0\xbf\x03*\x05\xfahF\xe1\xbf\x0e\x81m\xef\xc3\xa0\xf7?OW\xf8x\x89\x98\xf6\xbfU\xb2\xf7 \x15\x94\x06@@\xa4~\x89\xc4\xc2\xd6?\x84`\xea\xba\x14\x95\xc9?Y^\'\x99\xbf}\xba\xbfS\x90B\x16.1\xaf\xbfCw\xd6\xf9\xe4\xf2\xd4\xbfD\xd4\x08hw\xca\xd2?\xfb\x13;\x89.\xe6\xdf\xbfx\xe6X\x1a\xaf\xb7\xfa\xbf\xc2A\x7fPV\xbd\xfb\xbf\x04\x80:hc\x99\xe9\xbfNr\xe5\x9f\xd6\xd7\xf2\xbf\xdc\xc0C*5w\xb4\xbf\x9a&^\\k\xa6\xf3\xbf\xc6\x80\x8c\x87\xd3\xcd\xcc\xbf\x1cz\xc5\xcc\xe9\xf7\xea?\x14\x04\x84\x93\xc8y\xda?\xfa\xf0\x04L\x92\'\xde?r\x11zl\x83J\xf0?\xc8\x84g\xd3\x16\x87\xf9?\x1b\xd6\xed\x8fCf\xe4\xbf\xfc\x88\x1f5\xdf\'\xdb?$zM\xb6\xfbe\xf6\xbf\x81\x08\xf5\x91H\x13\xfb\xbf\xfcb\xf2\xdf\xdc{\xf6\xbf\x13?\xea\x8e\x8d\x0f\xfa\xbf\x16\xca\xb1\xe8\xc6\x15\xed\xbf\x9c\xfe\xdc\x86+\xcc\xe3?\xc4\x08\xa1\xa1=\x05\xdd?\xa2\xde\x0c;[\xf8\xea\xbff*+c:q\xe7\xbf\x1bD\xa8\xf2\x10+\xac?V\xb3\x1d\xc6\x01\x80\xf8?\x1bZeP\xae\x87\xd3\xbf\xc6\'\xc5\xe9^u\xde\xbfI\xa2\xdf\x19O\x96\xe5\xbf\x88\xef\xea\x9ao\x04\xd5?xQ\xfa<\xd8~\xe3\xbf\x0b\xf8\xe52\xd2\x17\xd3?a\x80X\xe6#\xb0\xe0\xbf\xb7\x9a\n\x88\x19\xe0\xe8\xbf\x9f\xc2lr\xfd\xfa\xf7?\xbf+\x12\xe6\xda^\xec?\x00SdCn=\t@\xe3\x12\xfb\x86\xb9\xe4\xfe?\xb5f\xc2\x1d\xf0\xdb\xfa?o\xf7\x19\xd5\x05\xf7\xe2?4\x06\xb0c\xe7\xe1\xf7?\xf3jG\xea\x1d\x17\xfb?\xc9\xd5v\x1d\xbe\xd1\xe4?IK\xbc\x1d\xeb\xdf\xfd?\xd6\x1e\xea3\x83Q\xfe\xbf\xf1\xd0C\x90\xc0\xdd\x04\xc0\x03{N@a\t\x07\xc0\xb1"\xbfr?\x8e\xfa\xbf\xd00\xbbN\x85\xbf\xfc\xbf\xff\xc2\xdbQm\x1e\xf3?M\xfc\x12\x0f\xc8\xb7\xe7?]y\xc8\x96\xfbY\xf3\xbf\x91rB\x99\xbe5\xce?\xbdEn\xad\x939\xc2?A\xbfY\xcd\'\x14\xf0?\xb1\xb6\x9e\x84\xfc\xbf\xf7?\xa0\xc0\xf7\xd8\x9aL\x01@Y\x99\xc8]NN\xed\xbf\xb36\xa8l<2\xf0?\xa9mD\xee\x11\xd1\xe6\xbf\xdb \xa6\xb2\x87\x91\xff\xbf\xf8.\x05\x9c\x9c\x83\xe0?i\xa8l}6\xba\xea?\\M\xe9\xdbxd\xf1\xbf(\xfbv\xb1\xc3!\xfd?S\xca\xfc\xf9\x02\x0e\xc4?^\xb7n33S\xb3\xbf\x89\x11\xe9]\xff\xba\xf3?\xd7V\x97\xc4m\xb9\xf0?8;\x9c\xeb\xee?\xcc?\xe6\xb2\x9eX\xaax\xcb?\xb2\x81\x99;\xdbB\xe7\xbf\xad~b\xe4\x16\x93\xfa\xbfIhS0}&\xf4?\xe2\x13\\\xa5X\xde\x05\xc0+N\x10\x129\xdc\xb3?\xd4\x82\xaf\xbaI\xaf\xf6\xbf\xd6\xf7y\nqn\xe3?[\xf7i\xbe/q\xf3\xbf\x88\xfaHS04\xeb?b\xe1-A\xfb\xda\xe3?\xbc\xbd\xdc\x13@\xbf\xd5?\xc7\xd7\xd9;\xd8\xb3\xe9?P\x8e\x95\xf5\x96\x9b\xca?@\xd5\x8ej\xa4\xbb\xd8\xbf\xfd\x84\xe8\x85\xf1[\xe3?>\xb5\xd2\xeb\xba\x10\xa1\xbf\xec\xa0\xe3\x8e\x0c\x9b\xf4?\xf6G\xf2\r\x18\xef\xf2?\xd2q`\x95\xfe\x83\xfd?]\xaa#`\x01\xdf\xf3?C\xaf\xde\xbfk\x0b\x00@\xd6m\x04\x1c\xfc\xc0\xf5?\x92V\x8dE\x14\xeb\xea?DiT\xf0K\x1c\xda?\x1e5\t\xd1.\x80\xc0\xbf\x15Z\xc2e)\xd2\xde\xbfY\xec\x9c\xccI.\xda?\xf9Fl\xc1\xf7D\xa0\xbfwws\x89W\x9f\xc3?)\xa1\xc3\xf5\x9a\xdb\xdc?\\\x88\x91\xc8J%\xff\xbf\x83\x91\xdc*\x85L\xfd\xbfIU\xaar\xd2\xb7\x01\xc0\xba\xe4p\x0bW\xdc?\xd9\xd7g6{\xed\xe2\xbf\xaaf\x90\xd4\x1a\xdb\xd7\xbf7I_s\xc5P\xf2?\xe7m\\\x83\xfe\xc5\xea?\xbd\xf9t\xcc\xc0\xde\xc8\xbf\xc5B\xe4\x06\x96]s\xbf\xfa6\x93\xa7\xb8:\xf1?hZ\xa3\xdc\xaf\n\xda\xbfa\x1fJ\x04fC\xe0\xbf\xe6\xd4-77P\xdd?\x19\xaca\x96\xd4\xad\xf8?\x84+\x81)\xaf\xcf\x0f@%\xea\x07\xf6kv\xea?\xcf\x9f\xb59\x0f\xa1\xb2\xbf\xf6\xee\xeaG\xa2)\xc3\xbf\xa5\x08bN\xce\xd0\xdc\xbfnuMjc\xa9\xf9\xbf\x8b\x06\xfe\xf1\x95\x06\xc0\xbf\xc4\x89 W\xc0M\xf4?}\xc6@\xb2CL\xd1?\x878\x1b\x9b\xae\xe5\xe5\xbfqB\xaf\xcco\t\t\xc0io\x85\x19YP\xe1?M\xc4XCD\x02\xdd\xbf\xe4\x1a\x94\\cY\xb8?.\xf6T\xcch\xa4\xee\xbf\x8d\xf2\xad\xca\xab\x9f\xdd\xbf\xdfN\x84\xb6\x15[\xdc\xbf\xc3`\x14k\xd8n\xb9\xbf\x84\xbe\x90\xa0\xb5\x86\xe8?\x95`\x96V\x19\xff\xe4?\xcc\xd0\xce5?\x1e\xf7?6e\xa6\xc3\x88\x04\xf4?\xd9\x88\xc7\xb4m\xf1\xff?\xbb\xc8\xb0\x0b;\xc3\xcc\xbf\xd2X\xa5 Aa\xe0?7\x8a\x18\x18\x90c\xe5?\xb8\x0b\x9eH\x0c\xd6\xd6?\x93\x0fUX/\x97\x87?\x1b\xa8\xb11lq\x03@\xf9\xfcP\x95\xac\xe5\xe1?\xae\x01<\x03\x9d\xf8\xee?r{\x07.\x1b\xb3\xe9?\xd3C\x7f\xf9p\xde\xd7\xbf\x9d.E"\x13\x86\xf2?\x99\x9c\xff\xbc\x1fV\xf1?\x95\xde\x8cU\xe0c\xba\xbf\xa8\x81\x98\xba2M\xf2\xbf\xa8\xff\x8f!\x8f1\xf9?\x0c^\xc4\xc6\xa7\x8d\xcc\xbf\xf3:u*\xc6u\xe2\xbf\x07^\xf2o\xa3\xc1\xe9\xbf\xe08?Sc\xb6\xb7\xbfQ\xb9\xb7\x8a\xe0\xce\xdd?\x16\x06\x1e\xb9e\x11\xf4\xbf\x08L\x16EN\x9a\xef?\x9a\xb7\xe3t-F\xf2\xbf:v\xb4\xc5\xb6\xd3\x03@\xbe\x10*Z\x86X\x08@B_pr\x03\xc4\xbc?W\x08_\x0c\x90\xdd\xa5?Y\xbc\xb0 \xb4\x01\xe0?1yf\xb0\x11\x9e\xec?5\\\xa9^\xaf\x80\xf0\xbf\xbb%\xb7\xc2\x01\x1e\xf6\xbf;\xb0\x1d\xb5&\x01\x03\xc0\xb8J\x99i\xe3\xfd\xfa\xbf\xbd\xdd\xcfD\xf1\x08\xf6?\xe5d\xf3\xe8\xeb4\xcd\xbf\xca$j}\x99\xad\xc6\xbfk~\xd8\xdc\xb8\xa3\x0b@\t5\xec\te\xd8\xf0?\xbc\x9a\x9e\xc0\xa7e\xf9?\x93\xb4\xe6\x1aR&\x0c@O\x0e\xb3!P\x82\xeb?\xec\xf4O\x08\xf2\x0e\xee?\x05\x81\xe81`\x05\xf2\xbf^\xa5@\x81\x14\x7f\xf1?\x9f\xa9n,\t\xa8\xd4?`\x18\xb9\xb1/n\xe7\xbf-O\n\xb1\x8f\xd2\xe2\xbf\xdc\xfd\x06(\xb68\xf4?=9R>W\xba\xf1\xbf\x85Jt\x16\x13\xd4\xef\xbf\xd0\xc3m\x8b&\x87\xda?A=w\x9e\x03\x92\xe5?vs\xb0\xae\xfcz\xc7?\xd1\x1fl\xb3\xdd\xd9\xf6\xbf)\xa0D\xf9\xabz\xcf\xbf\x8b\x1bF\xe3\xb1O\x01\xc0\x98\xd1\x85\xab\xccZ\xdf\xbfvh\xb39;,\xe3\xbf\xe2\x95\xc2\xbc\xae\xb4\xf9\xbf\x94\xe3B\xef\xd8!\x03\xc0\x7fx\x8dQ4<\xca?\x85ea>\x1b\xe3\x00@\xc8:\xa7V\xba\x81\x03\xc0dvF\xc6tp\xd5\xbfz\xae\xde\xcd\xae\x08\xf4?Gqy\x9f\x89\x10\xf5?\xd8x\xfa\x06\xa0\xfc\xe0\xbfw\x9f\xd2\x9a\xe75\xfc?B\xb9uWg\xe4\xd3?\xf9\x1bUr#?\xd5\xbf\xe0\xab\x84\xf2\xd4\xfb\xee?Y=\xcfh\xce\xd9\x8a?\xba\x91\xf85\xd7\xed\xca\xbflsFt\xba\xa7\xe5?\xb9\x86~\x14o[\xee?\x1a\xd2u\x7fVe\xf7\xbf\xfa&\xdfz\xf2\xb6\xe3?\xfeb\xd9\x9d\xc5\x1d\xd2\xbf\xfc\x9e\xfbp\x0e\xb8\xf1\xbf5i)|f\x07\xde\xbf\x0e\xa1"\x95\xf5\xc3\xf9\xbf\xe64oFz\xf0\xe3\xbf\x00\xbc\xc1\xdfY\xe5\xd3\xbf\xa5\xf8\xa2\x1b\xddX\xef\xbf\x97\xfd]v\x1f\xa7\xd9\xbfe\xa3\xe6\xf7m)\xf8\xbf\xa4K\x1e\x9e\xa4\xd6\xe9\xbfV\xc1\xb4\x9d\x7f\xda\xfc\xbfMJZ\xf5\xa2W\xfb\xbfNc\x7f\x8f4\xf4\xf6?X:x\xe5>\xe2\xf1?|\x8e\x04\xfaYn\x03@\x1e\xab\xed\xe8\xa9-\xdd\xbf\x9eL1\n.\xd6\xe7?\x96\xbc\x9b\xda\x9ff\x02@\x90\xca\xf4\x85\xb2\x06\xe8?M\xe7qP\x191\xe4\xbf:V\x92\x83\xca\xd1\xfb\xbf\x7f\x07\xd7\xba\xc2\xbe\xf4\xbf\xf5/\x0e\x80W\x9d\xf4\xbfw\xcfZ\xed\xcaQ\xcc\xbf\xf7]\xc2\xcbz\xaf\x02@[\xe1\xab\xfe\xb1\x84\xe4\xbf\xca\xd3\xd4s\x18\xe6\xc3\xbfz\xf7Xd!\x90\x98?\xc7\xf2\xf9}{2\xf3?C\x90\xcb&GY\xf2\xbf\xfc\x84p\xd4\xa7f\xe2?\xfd1~\xf5\xcd\xb8\xfa\xbf\xae\xd6\x1c\xba\x83y\xf6\xbf\xe5U>\x7fv\xd7\xf5\xbf\x95}\xe4q\xc6Q\xf4\xbfu\x85\xa2z\xc1\x11\xc8\xbf\xdbv}5\xf8T\xb6\xbf\x1eSk"\xfd0\xe9\xbfQ\xee\x89\xb8m9\xe4\xbf\xff+\xfe\xe4\x1c\xe1\xf9\xbf\xc6(\xdd\xc26\\\xec\xbf\x17\xf4f\xc6:\x17\xf3?\xaf\x8c\xad\x94m\xd4\xc9\xbf\t\x8f\xf0\xf9\xfd\x80\xd9?\x00!\rX_\xd1\xdf?\xaf?\xe4N!&\xdc?qk\xe2\x9e4\xfb\xe6?i\x85G\x82S8\xd9\xbf\x16\xff%\xd9R\x03\xe7\xbf\x9cr \xb6\x9c$\xd1?\xcd\x95\x1e\\\xd3\n\xe3\xbf\x0fQ&\xbaA\xd3\xf4\xbf|\x9b\xa3\x91\x8d?\xea\xbfi\xb4\xaa\xc8\xd4\x9e\xec\xbfA\x15M\xda\xb3\x02\xf4?\xb5w+\xe3\xf1\xe1\xe2\xbf\xfc\xd7Y\xa8<\xf0\x01@\x0b\xda\xe0W\xcb\x99\xf6?\x99}\xb9=\x80\x13\xca\xbfF\xeb3\x94\x9f\xba\xf5\xbf\xbc\x7f,\x0b\xcb\xd5\xd2\xbf\xaf&1\x9b\r.\xb5?S\xa1C\xa4<\r\xea?W\xcd$\x99o\x1b\xef\xbf\x90\x8e\x80|1}\xe0?\nd\x0f\xfe\x19t\xe3?\xb0\x15a~\x164\xe1\xbf\xc6b\xa2r\r\xef\xe6\xbf0#\n6\xb3\x96\xf9\xbf\xe8`\xe1R$\xcc\xf6\xbf\xff\xa3\xb4\xc55\xe2\xd2\xbf\xa0\xfb"\xe3V2\x91?\xf1/\xdf\xc2\xfd\xa8\xe2\xbf\ro\xacZ\x8d\xc2\xe3\xbf\x8br\xb0\x85Jx\xd0?@\xaf":\x18r\xdd\xbfQ\x8f\xcek\xa5Q\xe0\xbf\x16\x17\xd5\xc1\x97\x01\x00\xc0\ry[\xf7\xdc\xa2\xf0\xbf\\\xf1m\xdfP\xda\xe0?\xfd<\x91\x13\xdd\x12\xdf\xbf\x92\xcb\x1c\xd2O\x1f\xd5?\xa1\x10\xc7F6\x9c\xf8\xbf\xe7\x8b^\x16\xca\xc3\xef?\xef\x15\xf3"\xf6R\xed?,\xe1\xab\xdf~\xc7\xe9?\x1f\r\xbb\x9a\xc4Y\xce?\n\x96\xf5\'\x11\xac\xdd\xbfz\x00=R\xffT\xdd\xbf\xc0&v_\xd9\x0b\xf6?\x03\xf9\x16\x88R\xdd\xf9\xbf\xe2/\xafG59\xf4\xbf~;\xdf\x10"_\xd4?\x96\xf6\xd2]\xcf\x89\xf0\xbf\xdcy\xff{/\xe8\xf5?{\x9d\xa4?\xcdp\xe2?N\xd8#\xb1\x8b\x91\xc7?\xd7\xaf18\x89\xcc\xcd?&\x1b\xda\x9cz\xbc\xea?e\xb4\x95|\x8c_\xdc\xbf\xd5\x17-\x00\xaa\xc6\xf8?i\xf8PO\xc0l\xd4\xbf\x90D\xfc\xe7?s\xe5\xbf\xf8\xf1\x99(d\xbe\x04\xc0\xec\xfd\xe4\xbe\x87-\xe8\xbf\x9d8\xc7&N\x1c\xd1\xbf\x9a\xfe}\xd7\xc2\xd2\xe4\xbf\x97\xd7k\xb6\x8bY\xd3?\xcd\xbb\xa8g-w\xb5\xbf\\\xf7S\xce^_\x00@^\x05c\x9co?\xed\xbf\xea\x95\x05\xa5\x1c\xff\xda\xbf\xaa\xc7\xeb\x7f\xbd\xf3\xe0?\xfde\x1a\x9b\x82\xb8\xea\xbf\xd1\xccw\xe3\x9a\t\xe2?$\xc3\xa2\x02h\x88\xda\xbfX\xb2\x9b\xce\xdfI\xe0\xbfuhfo/\xf8\xef\xbf?!\xfe\xaf\x89\x96\xe1\xbf\x903\xeeqF\xcf\xe9\xbf\xe0a\x12\xb0&\x7f\xe8\xbf\xb0\xf7\x89(\x1e\x9c\xe2\xbf\xa6\xa3\xc8yt\xb8\xed\xbf\x91\x9c\xdf<\xcc\xe0\xe5?B\x92\xe7\xa6\xb8\xa1\xd8?\xbb\xa9\xb4\x9f\x86Q\xbc\xbf\xb7\xcdrD\xa9\x01\xfb\xbf\xd5\xba)\xe3\xed\x1f\xf0\xbf.\xf88V\x1a\x1a\xe3?\x00_\xcc\x16\x11X\xe5\xbf\xf7{W8qy\x03\xc0\xfb\xd2\x0b\xd49\xb7\xc3\xbf\x0b\xcc\xbeVI\x04\xfb\xbf\x81u<\xd6*\xc3\xe5\xbfF[\xd5i>P\xb7\xbf\xe1U\x8b\xec$\xd7\xe2?\xa5\xb2\x878\xad\xad\xf6?\xa5\xe4y\x12\xbe\xd6\xee\xbf\x8b\x83O\xa3vx\xfa\xbf@\x87-\x8f\xce\x1d\xeb\xbf\xc8\x0e\x97Q\x8f\x0f\xef\xbfX\xc97\xb7>=\xf8\xbf\x17\x97u\xae@H\x96\xbfy\x00\xbb5\xd6\xb3\xfb\xbf\xfe\r7?\xfdq\n\xc0N2\xd5\x19\x8a}\xea\xbfg\xb4?K\xb6\xd3\xd6\xbf\x8c\x9d\xa62\xe3\x8f\x08\xc0\xed\x8b\xc4\xdd\xecg\x00\xc0\xbe\xee\'\x16\x18\xb1\xd8\xbf\xb3\x08\xc8\x8fZ\xc1\xe6?V\x94\xf9\xb3_\xef\xf0?\x00\x15\xd1\x87\xa1Q\xe2\xbf\xc1\x19\xf6\x9d$\x89\xeb\xbfrO\x0eb\xf6\xa9\xd7\xbf\xa3"\xed\xec\x13`\xce?\xc4\xd5\xd3^\xe3\xf2\xf1\xbf0\x88:\x87F\x00\n\xc0\xf0\xf9\x1bqN\x9d\xfc\xbf\xa7V\xf2\x80\x10\x16\xe8\xbf\xe3\xc1\x81\xf1\xedE\xec\xbf\xbc\xdb\x8a6E\x0c\xa8\xbf\x8e>\xafgD\x07\xcd?9\x03\x84\xcd\xf37\xd5?\xe5\x91\xc0~\xf8F\xd4?w\x1e\xa4GPb\xb1?KG\x02\x15%f\xc9\xbf \xeb\xc7i}\xc8\xf2\xbf|\x8a\x16\xcb\xc6\xaaz\xbf\xceg\xda\x80\xce>\xd7?\xf9\x82\xc6\xb1-\xfa\xe2\xbfY\x95\xe3\xa9\x13$\x04\xc0.=x\xdcP\xf6\xec\xbf\xc8\x16\xd4\x1c\xd7[\xfb\xbf+\xe6O6\x95g\xfe\xbf5`\x82Vj7\xfb\xbfy`\xd3Q\xe84\xf6\xbf\xe56\x06I\x04j\xf4\xbfIB\xf6G\xcf\xe7\xca?\xa6&\x93\xf9\xf5\xf5\xeb?=\xe7 7\x89\xe8\xfa?\xae6\x86\xd6\xd8\xf3\xfe\xbf\x03\xb8\x84\xb0d\x88\xe3?}T\xac\xa1\xa9,\xe8\xbf\x9b\xb4\x00,2\x1b\x04\xc0=\xa7}\t/\x8f\xf6?\x9f\x06\x97h\x97\xbb\xe6\xbf\x99\xd5p\x9d\x8c\xdd\xff\xbf\x0e\xd3\x8b\x95\x1a{\xf4\xbf\n\x1cBGL\xd7\xdc\xbfN\xdd\xfb\x16\xea\x1e\xfe\xbf\xd0\xda\xffFm/\xe9\xbf\x84\xf4\xf1\xf8k~\x02\xc0\xb8m\t\xdc\xf4\xef\xe5?\xc4"\xca9\xdd\xd5\xd5?\x19It\xddwP\xd3\xbf\x15\xfdI\xa4\x99W\xf9?)\xce\x05\xbf~Q\xe3\xbf\x9a*\x96\xec%\xd5\xf3\xbf\xce[\r\x03x\xea\xbd\xbfW\xa1\x1b\xab\x18~\xe1\xbf\x0e\x8c\xad\xef\x99_\xe3\xbf\xb3\x98*\x1fJ\x8d\x03\xc0 \x8f\xc8]L\x89\xd2\xbf\xaez1(\xaa\xba\xe2\xbf\xa2vwN\xaan\xf9?%\x16\xf6\xa7}\x04\xc9\xbf9\xa38.\x1b\xf2\xcc\xbf\x93\xb3$(\xe2\x1e\xd1\xbf\x1f:\x8d\n\x9d\xd9\xc7\xbf{V\xc0)\x82\x19\xe7\xbf\x1f\xfe\xe47l\xc9\x9a?\xbe\x84\xdf\xb6JZ\xf0?\xea\xfa\xef\xeb\x07M\xda?\x83\xef\xdb\x926&\x04\xc0\xbc\xf0\x0e\x82\x988\x02\xc0\xce5c\xf9@X\xa9\xbf\xd6x\x87ah\xbe\xce?\xb4\xbd\xd9y\xaf\x99\xe4\xbf\xde\x9b\x15\x0f\x0c\xbe\xf0\xbf\x19\xb9\xc7)\xd4\xa9\xfa\xbf\x8a\xd0&\nL\x8b\xe8\xbfK\xe0I\x16\x84\x13\xf9\xbf\x8c\xa9\xbe\xab\xbcq\xf7\xbf{GG\xca\x1f\x1a\xdf\xbf\xb7\xc5\xb2\xae\x08\x8f\xe4\xbf$\xd9?\xed\x1d\x15\xf2\xbf;\x0f6Z\xfe\x16\x04\xc0\xd4+@\xb7\xbe\xae\xfb\xbf\xce\xe9\xd0\xa5\xdf\x84\xdc?\x02\xe9\xb8\xff\x0f\xa7\xf0\xbf U\xff\xe2o\xb6\xf6\xbf\xea\x0fb/\xaf\xad\xf4\xbf\xb6U\xef\x96\xa9\xb8\xf5?T\x08&\x8d\x1f\xe1\xef\xbf\x8b\x9b:L\x91\r\x83\xbf1r\x11\xfc\xee[\xf4\xbf\x81\x90\xc6\x9f:\xcf\xd6?\xa8n\xd8\x95\xe5h\xef\xbf\xb2>a-2E\xf5\xbf\xbd\xdfF\xee\r\x14\x01@\x1f\xd7\xfa\xf7{\x8d\xf7\xbf\x95\x86"3\xe0}\xe0?\x9c1v\xdf\xc1\xea\xfb\xbfq\x18\xb5;\xb2\xc5\xc1?$\xe6\x05\xff\xe7e\xf2\xbf\x99\xde\xa0\xeb\x9aa\xfc\xbf\xe9\x18Q\xecJ\xdb\xe1\xbf\x93\xc25>\x16\xca\x01\xc0\x91\xe8B\x90T\xb3\xc2?\xb8\xaf!"\x1f\xb2\xd6?\x86%h\xa5\xb1J\xeb\xbf\x99\xefzv\x18<\xf5\xbfc\xa6\xc0z\x94\xc4\xf7\xbfV\xe4P\xafyg\xb6\xbf\xf3\xf7\xd6`\x85\xc8\xfc\xbfk\xc5a\x9b\x04\x03\xc4?\x03^\xcbW\xd1\xcb\xf1\xbf\x10\xff }5}\xc7\xbf\xe0,N\xf9r\xbe\xc3\xbf\xbb\x10Q"\xe5\xad\xf1?:\x8aA\x99\x92\xf4\xdb\xbfd-QY^}\x8b?7\x1e&U\xe5\xf9\xeb?\x16\xd2<\xd6\xca\x94\xdf?UC\xb9\x89\x06\x86\xe5?n\x00~\xec\xb6X\xe2\xbfl\x08\x04Xv\xf1\xc9?\x9d\xed\xc41d\x15\xf3\xbf\xf8\xe8\xe6\xe4V`\x00@\x0e\xafLV\xa7\xbd\xf1?C\xba?\xa1\xaa(\xf8?\xc1m\xad\xb8\xfd\xdf\xd2\xbf\x16\x1b\xabvu\xbb\xf4?5\x06\xd85P\x10\xf5\xbf\xde\xfe\xc9\xfe\xfa\xc8\xfd\xbf!+A!\x1e\xa8\xf7\xbf\x9e-\xf6\xde\xcf\xa5\xfd\xbf\xb0K\xb2V^\xe6\xfb\xbf\\\xc0?L\xc6/\x05\xc0\xbeb0\x9d\xb3X\xdf\xbf\x0c\xbe,k\xba$\xe7\xbf\xb4\xec\xcc\xc5\xbf\xc5\xe2\xbf\x0cv5HAY\xef\xbf\x92\xdd\'\x86\x98\x04\xf1\xbfe\x81\x96V\x80p\xd2?\x95Y\xe2v\xab\x1c\xfd?AzS\xf1\xe9\xbb\xf6?\x1b\xbe\x15p\x14\xc4\xde\xbf\xe1\x02\n\xa9\x81\x1e\xc9?\x93\xc8Db\xe5\x02\x01\xc0\xeb\x8e1\x17\x86\xaf\xdd\xbf\xb18\x9f=\xce\xb7\xed?~C\xb8\x89\xe1L\xfc?\xb9\x94$\xb4\x84\xf0\xc8?P\xe7X\xb3\x0eG\xe3?\xb0\x1c\xaeK\xd3\n\xea\xbf\x17HGx\x93\xdd\xe7?\xe1L\x82\xbe\x94\xd9\xeb?\xc8\x8b"\xac\x91x\xf3?\xa6\xce\xc5\xd9D\xb2\xc9\xbfO]4\\\xe6\xc6\xf2?\xd3\xb8$\x0b\xbcM\xda\xbf\x84\xa3\xa5He1\xc7\xbf7&\xca\xa5\xe6\x85\xe0?\xba\xbeW#\x1d\xed\xce?\xfdo/\xe2e\xe6\xd3\xbf\xee\x88\x7f\xe0\xb8\x1c\x00@h\n\x02&\xb8\xff\xf9?\xf1"\x7f\x11y\xb5\xf2?\xd6\x15\xd4\x03\x97N\x02@\xbb\xeb\xd3W\xc7\xda\x02@\xe9$3GB\xb3\x04@\x85y?\x00\x05\x08\xe2?\x12\xbf\x9b\xbcR\xcb\x00@\x9a\xda\x0c\\/\xf9\xff?\xfbdK\xb2z\x1f\xf4?\x9a\xba/\xa1\x81\xc0\xfd?\xda~_\'x{\x00\xc0\x18\x80\xf6\xea\xc7~\xf0?2 \x88\xe8\t\x82\xf3\xbf\x04\xcd\xdaj\x9f\x06\x01\xc0M\x8e\x11\xeb\x9e\xda\xf8?\xbf\xc5\xdc]S\x01\xfc?\xe8\xfb\xcf%\xfc\x87\xe6?m\x8e\x01\x82!T\xc3?(\':w\xb6O\xc9??\xadv\xa9\ni\xe8?0\xd3a\xc97t\xe7?\xf4M\x19\xbd4\x14\xf5?\x8eK\x9c\x95\xa8\xde\xf3?\xa6\xe8\x15\xf9E \xbb?.\x92\x01C\xcbT\xf5?\x82\x8aj\x90\'\xa7\xe0?:\xca\xee\x9dh{\xfb?\xf9uO\'\x99\xae\x03@\r\x04}\xd9\x0cL\x00@\xe1"\xc8-]k\xe2\xbf83Xil\x94\xfe?\xd2\xae\xe6\xd9\xef\xef\xd3\xbf\xcf\x02\x9bS\x1e\xbd\xf9?\xd7qqsq>\x82?c\xe0\xf7\xf4\xfdQ\xcc?\xfa\xd2\xc9\x98\xd5W\xeb?K\xb9\xb37\x90\xc7\x01\xc023#(C\x9c\xea?\xcd\xee\xb7\x98\xb4S\xe4?\x8c]\x8a\xd8\xca\x13\xeb?\xbb\x04\xb5\xea\xa6z\xe9?\xdc\t\xa6\xeb\x91\x93\xe6?\x9e\xdb\xd8\xd4\xa9\xba\xd0\xbf\x96\xba8\x9a)\xc9\xc2?\xc1\xbb\xf5E\xd3\x06\xf1\xbf\xf1R\xc5\x82\xff\xb6\xf4?L\x0e\xd3\x1bY:\xe4?\xd4Zp\x8a\xfe\x83\xe4?x5\xab:\xea\xd8\xfc\xbf\xdeH\xf71\xc2\x0c\xef?\x97\xf6\xb9\xfa\x9b(\xe9?\xda\xac\xdc~\xa8y\xdb\xbf\x17rL^\x88\n\xe6?9y\xde\x8a\t;\xe7?D\n\xb6\xcb\n_\xe9\xbf\x05\xd9\x90\xac\xd8\xfa\xf7?\xfd\xc5=\xc8\xb6\xed\xb7?\x16\x8985 \xe2\xfa\xbfZXFv\r\xc5\xa7?\xe6G\xb34\xa7\xe4\xe0?\xd0\xc9\x05`<\xc2\xd2\xbf\x93\xa5\xeb\xed:t\xd8\xbfD\x02C\xe0\x8eZ\xd1?\xfe\x8aG5O\x86\xc0?\xfdG4\xbd\xc5\x8b\xc7?\xa4\x0f;\xf0\x07\xd7\xd8\xbf\xe9[\xc05\x02C\xf4?\xc3\xdb\x91O>\xd4\xcc\xbf\xb1\xee\x94b\x1e\xe5\xe4\xbf\x01mC\xd2:\x95\xf8?\x9f2\xda\t\x0c0\x84\xbf\xd9\xf7\xc9i\x15\x91\xf7\xbf\x16\x8d\xeaQ\xb1\xaa\xad\xbf7P\xd2Ad\n\xe0?\x07\xf3\xbe\xd1\x97!\xe6\xbf\x81\xa4\xbc\xca\x10\x8c\xd4\xbf\xb5\x07VV|\xea\xce\xbf\xe6(\xc7\x86\xabH\xd1?=s\xfc\xf9\xcag\xe8?5\x8e\x84\xb9\xa2?\x00\xc0\xe9x\xcb\xe2\x0cs\xdf\xbf\x8e.\xe43\x12\xe1\xdf\xbf\x04\t\xb7\x97a\xb4\xd4?\xeaE+\xd9\xdb\x93\xe3?eIz\xf4\x16M\xea?\xe0z\xb4\xe1\r\x0c\xc1?\xac\x10\xfb\x919}\xeb\xbf\x1c,\xd2\xb0\x02b\xc9\xbfT\xd4vS\\\x0e\xd1?\xb9\x8a\xce\x8e\x89=\x07\xc06\xdb\x0e\x9f\xfcg\xfb?z\xab\xd9\x8b\x9b\x15\xe4\xbf\xb9\xect\xaah\xb9\xf6\xbf\x9a5\xb5\xee\x1b\xc3\x08@\'\xf3\x1bc\xb3\xdc\xbf\xc2T=\x14-\xa5\xfb\xbfx\xb0\x0b\xcb\xe4G\xb2\xbfA\r\xb9\xe5iU\xee?\xedi\xbaw2\x12\xf9\xbf\xedl\xe6J\xb1f\xe1\xbf\xd6\xc5\x97\xc4 \xfc\xd9\xbfg\xc1\xa8\x03.\xc4\xe3\xbf\xf4!\x9a\x079o\xef\xbf\xec\xb3a1\x91\x83\xc0\xbf\xb84\xfa\xfd\x06\x9d\xe4\xbf\xb9\x91\xa4\xee>\x94\xd3?\x8fu\x13\xc1\x0f\x13\xea\xbf\xecZ\xc7\xbd\xa0\xbb\xf9?\x9b\xf1Bi\r\x7f\xe6\xbf\xb2\xefF\xf367\xd1\xbf=\xda\xbc\x86k6\xd9?\xb0\xda\xea\xab;\xf9\xc1\xbf\x02pC\xf3\x0b\x83\xe6?j\xca\xb1\x1f\x81E\xfa?\xd6\xb2\xe2\x16\xde\xad\xd0\xbf\xb4\xba\xc0\xa3\xd3l\xc4?\x02\xe2\xc8\x12\xef<\xf3?/\xd09\x03\xc0i\xfd?\xb2\x19\xbd\xf7\xa8\xac\xea?0\xa5\r\xc1kF\x96\xbfx8\xa7\x82\xed\x9e\xf2\xbf\xb8S8rZ\xd9\xd9\xbf\xfe\xc8S\xfc\x16\xbe\xe1?5\x8e<[\x08\xd6\xe6?N\x9b\x17\x8f?j\xe8?nbq\xd5\xc2\xce\xf9? \x82\x94\xe9\x1c\xeb\xe6\xbf\xc8&\xfb\xdb\xb2\\\xd7?\x1e\xe6\xb90Ns\xdc?\x9d\x88\xce4\x00\xe8\xb1?\x0b|P\x07@J\xd8\xbf\xf9\xed9\xb9H\x04\xfb\xbf\xac\xd6e\xc1v\xb2\xf5\xbf\x07\xbe\x048\xd5\xbe\xf0\xbf7\x03\x85O8$\x06\xc0\x7f\x16\x1c\x8c\xceh\xd3?\xd9\xea\x0cb\xed\xb6\xe3?\x16\x01\x04\t\x06 \x01\xc0sjH\xad\xb3K\xdd?\xcb\xbb\xcf#\t\t\xf6\xbf\xf800\x90\xac5\xf6?\x8dM\x1f?MG\xc7\xbf\xc68\xe0\xb3\xfe\xd1\xc7\xbfl+\xb9\xd2t\x91\xfc\xbf\xc1\xe6\x00\xcc:\xd6\xb6\xbfc\xa9\xff^Bm\xef?\x12Zj\xe4.+\xf9\xbfsf\x95z\xd2j\x91?c\x91\xa8\x863\xa5\xdf?\x00\x08\xb9\xcf\x85i\xf3?=D\xe1G\x91P\x08@|\x106.\xbe\xbe\xe6?-\x97#\x95\x0b\xca\xed?\x17\x91\xb9\xff.\x89\xe0\xbf\xe3\xdb\x1f)>\xd2\xf4\xbfG\xc3\xef\xf5\x1a\x9a\xed\xbf\x85\x99\xa3-\xda\xa3\xe7\xbf\xaf\x9a\xd7\xa2\x01z\xcd?L\x87\x0c\xff\x83Y\xd3?z\x8c\x90\x08\x96\x7f\xf1?\xf8\x1a\x83_\x0b\x1b\xc3\xbf\xa7\x08\xd0\x04n\xba\xd4\xbf\xb9\n\xd71\x1d\x12\xb4?\xe1\x16\xb7\x8f\xbf(\xf2?\xc2\x1c\xfc\xb8\x8f?\xff\xbf\'\xfeg"\xa7\xf8\xf8\xbf\x05\xcd\xb3\xbdl1\xe0\xbf\xf4u,US\xae\xc4\xbf~\xc3\xcf\x90\xe7t\xf5?\x12\xab\xf5\xf5y*\xf1\xbf\xbf\xe1\x1e\xe8\x8c\x80\xf1\xbf\xcf\xe1\xb2&\x1a\xc5\xef?\x9bh`\xf9/\x9b\xeb\xbf\xd5?\xa4\xf3\xe5:\xb9?\xed|\x11[\x90`\xe7?\x0c\xc3Z\x9f\x12\xbc\xa6\xbf\xbfhx\xf5d\x95\xfc?\t\x02I;\x0e\xc6\x00@5\xad\xb3`\xb7\xa7\xe3?%\x85\x1c&Qu\xf8?\x8d\x03\xca{\x93\xd6\xd2?\xa3b\x9aU\x96\xd4\xc3\xbf\xab\xe3WK\xfd\x85\xf3\xbf\xf7O|\xdf\xdaS\xeb\xbfcR\xd2\xec\xf3\x9a\xef\xbf\xaa\x80\x1el\x12\xdc\xf5\xbf_Bi\xf8V\xe5\xfe\xbf\xd2\xe2\x97jYZ\x01\xc0&\xac\x7fw\xddA\xba\xbf\xe6\xf7\xe0\xd0\xebI\xf3\xbf0q\xbdY\xa0\xfd\xe3\xbf\x86+\x0e\xb1\xa46\xfa\xbfB\xa5\xedlS1\x99\xbfMD\xa7\x10\x9b(\xdd?\x1c\xef\r\xcb\xa3\xe9\xf0\xbf\x18Z4\xd4\xb1\xaa\xe0?\x0bi?\xc6d\xfc\xe7?\x14U\xfa\xb1,\xe7\xd8\xbf\x87\xa0F\x81~\xe2\xe9\xbf\'\xb2\x1b\x13f\xd4\xb5?G\x87\x1f\x192\xcc\xde\xbfg\x15"\xfb\x1e\xc1\xf1?%\xab\xdf\xaa)\xa4\xdb?"G\xaf\xd5\xbf\xc6\xff?q(\x92\x83Z\xd5\x02@\xed/\x9b\x82\xfe\xc2\xec?`o]\xedK\xe7\xc1?\xdf\x8dq\xa7M\xfc\xfb\xbf\xe5\xec\x9a\xb1\xf1\xb8\xd3\xbf\xfe\xf9*\n\xac\x0f\xf3\xbf%}\'/d\xbd\xe0?2\xc1Y\xe6\xa9\xb0\xe7?\x97\x8cm\xa8P\x14\xea\xbfc\x9b{\xa9.\x86\x9a\xbfC\xb1)_\xff\'\x03\xc0\x8aG \xfe\xd6\x82\xe4?\x86\xa4\xc2~\xba\xcc\xe7\xbf5\xbe\x07\x8d%\x0b\xf8\xbf\xa3\\\x9e\xf6D\xdd\xe9?u\tf\'F\x00\xf5\xbf\x07< \x95\xa4\x8c\xca?\xb0,\xb2\x16\xd3\x07\xe7\xbf\xaf`\x8b\xabG\x12\xe9?\xf7\x8d\x9d\xd5\x90^\xf6\xbf\xeb\x192-\xb0\xea\xf9?;\\"\xf8\xa3o\xdc\xbf\xbf\xean\x91\x90V\xf9\xbfm\x97\xa4b\x0b\xd9\xec?mI\x07\x0e\x94\x06\xd3\xbf\n\xffa\xb3>\xe5\xe0?\xf8\x0f\xfa\x9eh\x06\xd9?\x15N3\xdd\xeaG\xec?\x7fZ\xd8\x92H:\xea\xbf\xe7\x1e\\\x03\x02\x88\x96?\x08\x99\x1fx\x91*\xe4\xbf\xd8\xc2\xd9?\x0c?\x1b\xd0\xc98\x01@$< \xaa\x8e^\xe2?n\x80e\xa6m\xbf\xe8?\xab\x84L\xfd\xb9\x1b\xfa\xbf\xdf\x0c\x17hG\x0e\xb6\xbf\xcc\x1a\x1bo\xca\xc0\xf0?\xe9\x00\xfb=\x93H\xeb\xbf\xb9T\xc9\x1f\xd1%\xc0\xbfR\xf3\xd2\x9e\xd8\x85j\xbf\x81\xa0R>k\xd5\xee\xbf\xdc[U\xd6am\xec?\x9b\xd1Mz]\x98\xf1\xbf\xd9\x8d\x06\x0f}\xcb\xff\xbf{rn0PQ\xa6?\x0f2=\xecl\xd8\x07\xc0w\xadYf\x85\xd6\xd8\xbf\xceoRw\xe7u\xfa\xbf\xe5\'\x1e\xe7_f\xe5?8L1\xb2\xbd\x10\xd8?w\x06\x8ac\x0cW\xe6\xbf\xbc\xbd\xa1\xc5\x1cw\xec\xbfl\x9b\x80_\x80E\xea\xbf\xc5)\xd3(p\x9a\xf7\xbf#X\xd0\xce^\'\xca\xbf\x0e\xa1Z\xd15\x91\xc7\xbf26\xdb\x92\x1eA\xb5?\xb4\xfeO\x02o{\x01@Z>w\x97\x8c\xa8\xe3?vwuFOc\xe3\xbfQ\xf7\x00k\x0f9\xea?\x0c\xd8\xd4\x04\xc2\x18\xe5\xbfD\x80\x94\x8fP\xd0\xe6\xbf\xd8(t^\xa9x\xfb\xbf$2/"\xa9\xe9\x02\xc0\x8f/1\x1b\xf23\xbe?m\xd7\x94\xa4\x02\xbc\xea?\x89o\xd20\xb0\xce\xe5\xbf,\xe6\x893\x9d\x97\xdd\xbf\xa0X\xecN\xb7p\xe8\xbf\x05\x8a\x84\x04\xa7:\xa0?\n\xfa\x04ibU]\xbfS]\x94A\xa6g\xe7?\xd7\xfd\xdb+\xcd"\xef\xbfa<\xc1\xc84\xcf\xe2\xbf\x02\x8f\xb0\xb1*\x11\xe4?\xd0\x0c\xf7J\xc6\x8d\xda?1-\xa0\xd6\xcc)\xcf?^\x14\xdaGA\x07\xd5\xbf\x1a\x85\xb5w,z\xeb?/\xae*\x1c\x02\x97\xe7\xbf\xb7\xef\xc79\xed{\x04\xc0\xb5\xefm\xe3\xfav\xd2?w\x96.#\xaa\x0e\xfa\xbf\xe4\xd8\xc2\xeba\x9b\xc6?\x00:\xaf\xce\x00Z\xf7?=Z\x9b\xcb.>\xcf?\x95\x04l\xb4\x15\x02\xe4?\xac\xe8\xbd\xd6\x0c\x9a\xb8\xbf\xb5W\xd6V9f\xfe\xbf\xc6\xc4\x95\xc5\xcec\xec?\xc9\xb7\x03\xf9w\xe4\xc6\xbfRn\xf4J#\xe8\x05\xc0\r9\xf1\xc1\x82\xaa\xf2?\xcc\xc5\xda\x0c\r\xc3\xe6\xbf;\xca\xbd\x8c\xd0\xde\xe7\xbf\xaaB\xfc\x894\n\xff?\xddGE_?\x0e\xf6\xbf\x1b,\xa4\xe1\xa2\x0f\xf4\xbft\x83\\\xc6MD\xe2\xbfaA\x17\xd6J\xf9\xf1?=\x12\x13Jh\xc6\xc6\xbf\x1e\x8ed \xc4\x9d\xed?Xi8,\xc20\xb9\xbf6\x0bz \n\x8c\xfd?\xb1%4JM\xf8\xde?\x92\xae2\xd5\x80a\xce\xbf \xeag\x10\x19\xf4\xf1\xbf<1\x07d\xb9\x83\x00@\xfc\x10\xb8_Pz\xf8\xbfd\x1a\x02?\xeeI\xeb\xbf\xac<\xd9?_]\xcd?t\xf4\xc2\x07\x9e\x12\xf1?V\x08o\xfd\x13?\xb1?\xbbg\xca\xf6\xb2\x8a\xc2?z\x82\xe0\xcf\x7f\x13\xe4\xbf\xb9\\`c^\xe6\xd6\xbf\xa7\xdf\x9d\x07\xa7_\xf1\xbf\x92\xd0c2\x1c\xf0\xd5\xbf\xd5\x0f|\x07\x1b\x0f\x01@\xe7\xfa\x1c\x0f~\xfc\xfd\xbf\x00\x8c\xb3y\xacP\xd3?\xf16s/\xb4\xb3\xf1\xbf\xbe\xc0\x7f4\xf2\xce\x02\xc0\xcb\x83\xa8\x1bnB\xda?!s\xa2\x03b\xc3\xed?%\xe4\xb7\x80\xb0\xfa\x03@\x8e\xba\x9b\x92F\xb2\xd6?\xbe\xfc\xc1S\xdf\x98\xa0\xbf\xc6\xeeS\\\xc52\xd6?E\xcfNG\'\xf5\xcc?\xdf"K3\x99\xa0\xea?\xb3\xfe\t\x93\xb5\xaa\xb7\xbf\xc2\xcb\xb4&b \xd7\xbf\xaf\x1e\x0f\n\xc1\x9f\xf6?\x0b\xbe\xd4yc\x82\xf6?8\xe6\x85O]\xb2\xf8?a\x84\x03\xd2\xc6\xa3\xe5?\xc3(\xde\x8f\x82S\xe1\xbf\xd5\\\x12\xed,\x05\xb9?ma\xd0odY\xd8?\x8av\xed\xcc\xdc\x9d\xc5\xbfw\xc8\xe9/\\\xe6\xf1\xbf\xfb\x8e*3.\x06\xed?\x7f\x05\x1e\xd5Lb\xf7?9\xfe\xdf\xb3\xe7\xa3\xce\xbfXo\x1a\xd7\x80\xd2\xf9\xbf\xfd\x89\xd6o\x1d;\xb0?\t\xa9*\xa8\x9a]\xb7?\x85l\xcd\x13\xb0(\xef\xbf\xa2\x1b\x9a\xbfi\x14\xeb\xbf\x8a\xa1\x10mc.\xdf?^y0\xcb\xc4\xb6\xee\xbf\xf0L\xfa\xa8\xc8\xeb\xf1\xbfbga\xd3\xf9^\xc7\xbf\x80\x85=\xdenj\xed\xbfC\x89y\xc0\xbew\xc2?\xd4\x1dO1\x98{\xd3?\x9aG\xbc9\x11\xd5\xe6\xbf\x15\xde\xc9\xca7\x07\xad?u\x1eAS\xf5\xe0\xbe\xbffU\x12(\xa3\xe4\xd7?\x08\x8b\xa2dr\x18\xc8?\x18\x91\x85e\xb3o\x01\xc0\x13\xcf\x862\xa8\x15\xe3\xbf7\xf8\xe8,}\xfb\xf1?^8\x86\xa2\x06\xda\xe4\xbf\x92\x00b\xe2\xef\xb1\xbe?f\x8f\xc6\xab\tw\xf8?r\x88\xc82\xec\xef\xde\xbf\xb5\xe8E\x81\x12\xa0\xce\xbf\xac9W\x91;|\xf8\xbfbdX\'}\xca\xf5?\xb1s\x9b[\xfd8\xe5?\x1e\xa3\x98C\x187\xf2\xbf\xd7\x9d\x81\xa6\xc1\x9b\xeb\xbfE\x03\x16\xcdg\x18\xf2\xbf\xdd\xbc!\x0e\x99j\xc5\xbf\x80\x02Qov\xff\xec\xbf\x95.\x9dKlC\xf3?T\xbd\xc5B\xaew\xdc\xbf\x84\x08\x98\x93\xeeG\x05\xc0\xd6\xd9\x88(\x14\xfc\xf8?\x17\xa8\x9ea\xc6\xb1\xfd\xbf\x1b\xbfo\x9fU\x84\xa7?G\x1b\x8ex\xd3\xec\xa7\xbf\xe8\xba\x88\xbed{\xf0\xbf\x88\xd0%_\xcc\x91\xc0?\x8a^p*\x1d\xb1\x00\xc0?[\xf2\xa2\xe7\x88\xff?\x85\x12\x06b\x87N\xf5\xbf\t\xb5\x8ft\xd22\xfc?H\xf0\xca\x18\x03z\xf5\xbf\xb8h\xb0\x84-\xf2\xd8\xbf\x7fU\t\xf9\x81\'\xe8?8\x7f=\n\x98\xcb\xf8?8\r\x9c,\x1b\xb9\xf8?\xf1\xf9\xab\xce\xcf\x96\xd3?\x9e\xa0\xbcy\xed\xc3\xec?9H\xc9SI\xa8\xf1\xbf\x89$\xf41\xe2\xfd\xf2\xbf\xc9\x19o,2\xff\xf1\xbf\xddc\x1f\x93\x17\x0b\xea?\xb6\x02\x84\x85\x15}\xe3\xbf\xb9\x06\xd8KQY\xdd?\xe7\xaay\xc4\x90C\xcf?\xe57{\xec\x04\\\xb4\xbf\x81\xd0\xe2\x01\xf7s\xf9?B\x1e\xc3\x954\x87\xe1?\xe8\x00!\xcak(\xe3\xbf\x19\xa1ABw?\xdd?\xb2\x12i\xabB\x08\xcb?\xa7Y\xbcg\x9f\xe7\xe7\xbf\x07\xfb\xff\xc4}\x97\xe7\xbfn \x0cjm.\xf1\xbf\'\xc2\xf6b~\x10\xe3\xbf\xaf#7\x16\x0f\xb0\xf2?\xb5\x11AJ\x0c\xae\x00@d\xc6LGuv\xed?D\xe3\xa2\xf7v\x0f\xbb\xbf\xac\xba\xc4\xe6s\xfc\xfc\xbf\x8a=\x82\xe9\x1e_\xab?\x15\x9b\xfe\xfb7\xc8\xd9\xbf\x82\x92\xd7\xf6Q3\xea?89\xf3\xbfv\x14\xd9\xbf\x17\xf1\xa8l\x13/\xf3?w`6\xa1\xb1\x17\xe2?\xa2\xd2.\xb3\xa4k\xd4\xbf\x95\x04b\xb9p\x19\xbe?\xfc\x1b\xd0\xc5\xfd\xcd\xf7?~,%T\x8e;\xd9?\x03\r\xd5\r\xd1\xa1\xa0?W\xf1\xd4W,B\xbd\xbf\r\x82\x12/\x9d\xe0\xe6?\t\xf0\x12\x94\xea\x11\xea\xbf\x19:\xd7\x1a\xfdn\xd2\xbf\xc7\xeb\xe9\xb9\xe6z\xb2?-\xb1\x08\x9a\xac\xb4\xf9?\xb0\x13\x93J\xd4\xd3\xfd?\x0b\x91Ez!2\xf0\xbf,\x0e.\r\xbb\x98\xb5?\xddv\xf2o\x90r\xfd\xbf9\x85Y\xaa\xf5 \xc2?\xd6\xe7~\xe9\x04B\xd4?\xbc\xa8HR\xf4\xb7\xdb?\xd2\x9fh>\xa5\xaa\xf5?\xeeF]\x97\x088\xdf\xbf\xd5\xb8\xc8\x0c6\x14\x92?\xe3\xf3+\xa2\xc7\t\xeb?\xc0\x81\xde\x01\xbbN\xe3\xbfp\xb7u\x8d\xefZ\xde\xbf\xb1E\xaf\x1en\xbf\xd5?3\x800\xc2\x1c\xd9\x00\xc0\x83-\x83\xe5\xcaV\xf3?\x8d\x82\xf6{\x9b*\xdd\xbf\xa6?\x92\xdc\xa4<\xd6\xbf\x08_1\xadSY\xe1\xbf\xed\r\xc4 \x9a\xfe\xed\xbf\t{\x0e(\xde\'\xe4\xbff\xc1pq8\xa4\xf2?\xed\xa1!R\xa6W\xe3\xbf\xaf\x1b\xa7\xa8\xe0\xfd\xf6\xbf\xf2\\u\x17\x9a\x01\xa8\xbf\xe4\x91i0\xf93\xca?\xf4\x0be\xf7T\xfb\x01\xc0\xcc[\x13\x92\x8aI\xc6\xbf\xda\x82r\xbd\x08\xb9\xfd\xbfh\xac\x97_\x81\xa1\xe2?S\x1c\x91\xf7\xd7\x05\xeb?\xc8\xd8B^\x18\xe5\x88\xbf\xe2\x06\xd4\x13\xacW\xf4?\x01\x9f\x8c\xf9\x10\x88\xed?Y\xd9\xbeX\xaa\x8b\xfa?\xa6\xf0w\x81\xb4/\xf5?\x99\x14S\x96$\xb1\xf0\xbf\xa1\x0f\x7fUn\x8f\xf4\xbf)\x01\xc6O\xe7(\xcb\xbf\xbe\xe8\x02A?k\xd1?Wl\xadQ}g\xef\xbf\xa4s9\xc2\x15\x0c\xd9?";\xd2\xd1\xc2\xb4\xdb\xbf\x00O6\xf4\xa4\xbb\xfb\xbf\x03kj\x0b*\xf3\xcb\xbf\xb9\xb0\xb9\xaa\xc5\xff\xc6?\xbf\xae\xb0W\xf8u\xe5?\xf0D\xdap\x7f\xe4\xe4?\x9e\xbaO[\xe8\xee\xec\xbfI\xdd\xe92\xc2\r\xc9\xbfI\x90A\xb2\xce\t\xcc\xbfuP\xcb\x9d\xe5\x1b\xee\xbfc >H[\x10\xf6\xbf\x86X^gIZ\xd6\xbfX\x95\x98\xdf\x03\x9e\xd6\xbf\xa4\x83\x94B\xb9\xbf\x04\xc0\'>a\xc1T\xea\x03\xc0\xfb\x8a\xfa\xaf5}\xf7\xbf\xdc\xbeo\xa9\xc3\xcc\xf0\xbf%\xfe\xa9m\xfb\xc0\xcb\xbf$\xe5\xf09\xda\x14\xe5\xbf@9"O\x8b\x04\xf3?\xac\xe9\xaa\xe7a#\x07@\xc7\x87@\xea\xa3\xdf\xf8\xbf\x98\x8ed]\x12\xd5\xc8\xbf\x1f\x90\x9e\x8e\x0e\xd1\xfe?y\n3\xb0j\xb6\xcd?\x1a\xde\x16YJ\xf8\xb0\xbf\xb6M\xf9Ti\xc2\xcb\xbf\xc8\x93F\x87\xe8d\xac?\xf7\x9b\x1b{\xb8\xf9\xe8\xbfX\xda\'\xea\x98\x00\xf1\xbf\x844\xc4\xa1\xfc\xd9\xf7?-\xcd\xdfX\xac\xe8\xc7\xbf\xd2\xc5/\xfcl\xe1\xe9?\xdfC\x82\xeb!-\xf7\xbf\xfc\xca\x1e\x06!P\xf1?\xccr\xc7\x9f\x12\xad\xe2?\xde\xe6\x83\xbb\xe4\x88\xee?w\x8b\xe3/\xd4\x07\x0b\xbf\x83Ox\xde\x08\x8a\xe5\xbfuW\x91\x85\ta\xf3\xbf\xad\x8c\xae}g\xf3\xe6\xbf\xc6\x8b?8S\x8a\xde?\x0bsn\x18\x8d\x07\xd0?\xe0\xe5\x1ex\x80\xae\xfd?\xd0a\xce\xecO\x00\xec?>\x15\xc1*\xc0;\xe4\xbf\xc8\x03+uw\x96\x03@\xee\xde\x1dS\xed\x95\xf7\xbf\x02\x83\xc1l\xc06\xca?\x13\xd9\xf5B@G\xc2\xbf\x03\x1fV\xe5\xe4\xe2\xd5\xbf\x7f\x0f\xcd\xdb(\xa0\xd4?\x8f\xc2Q\xa7\xd9\xea\xf2?\xbe\xfa\xcfw~\xee\xc0?z\xfd\x10\x813\x12\xf4\xbfA\x03V\xf4\xee,\xde\xbf=\xe3t|\xc26\xd9?\x1cP\xdaz\x9az\xd0?N\x8a G\x82%\xf2\xbfV\x9e\xdan[^\xbc\xbf\xc4\xe2Y\xb9\x9b\xa1\xc0?\xd1\xbc\x93\x10\x1aN\xea\xbfW\x82\x8e\x9eX~\xba?\r\x80~\x9eN\x98\xe9?E}\xbe\xa3\xe3\xf1\xe3\xbf\x9d\xd0/0\xd6)\xeb\xbf(\xa6g\\`\x7f\xe8\xbf\x83\xdav4\xa8L\xe3\xbf\x81\xdb\xdc\x08\xbbx\xf1\xbf\xab8x\xdby7\xe9\xbf\xdc\x17\xb5\x0f@\xb8\xf1?\xfb\x07a\xdf\x02\xcb\xe1\xbf\xcb\xde\xdf2U\x97\xda?\xb5oOQu\x00\xee?\\\xe2<0Y`\xfb?\xe0\x00v\x0b+\xb2\xc6?\xc6\x12we\x9c\xa1\xf0?\x90<\xefR`$\xd9?\xd8\x10B\xe6x:\x03@e\xa63\x0e\x15g\xf5?\x00\x16\x16^\x84\x19\xe0\xbf\x91\'\x99M\xd1\xbc\xee\xbf*\xbf\xc2\xcd\xa3\x83\xe4\xbfxbc\xa6\xc2\x9d\xfa?\xc7\xc7E\tGA\xf2\xbf\x893\xffYzw\xee?\x96T\x00B\xfa\xbf\xeb?\xbf\x11\xda\x91\xc3\x02\x98?T\xa3\x12m?\x14\xec\xbf\x02\xe5/\xd4\xe0\x0f\xed\xbf\x93\xa9n\x1ck\x81\xfb\xbf\xa9\x03\xeb\x98L_\xeb\xbf\xa4\xd4\xbd\xc5\xc8\xc6\xd6\xbf\xf6c^}ND\xd0\xbf.\x06\x81\x80\xde\x15\xf8\xbf\xc5;\x90\xe1\xb4\xbe\xf3\xbf\x88\xf8\xef\xaa\x11l\x91?|~js\x9b\xe3\xf6?\xfc\x93\xc2\xfcfG\xe1?\xfa\xd0l\x93\xd6\x03\xfa?\xe8_\xee\xff\x92\x9e\xf1\xbf\xd7\xa9\r^%\xfa\x00@\x9e\xad\x87q\xc6w\xc7\xbf^\xaa\xfc\xbe\xc4[\xe5?\x9d\xb4\xa1\x04DI\xf3\xbf.\x1e\x9b/\x9b\x8c\xd5\xbf\x81\xe0\xba\x90M\x1b\xe9\xbf\xd9\x98(\x03\xc4\xa5\xd2\xbf\xed;|\n\xd3\xcd|\xbf\x19\x12\xc0\x95\xbb\xc6\xd3\xbf]k\xe3\xfc\xa9H\xee?\xdb?MbSD\xe4?f[\x15\xbb\xc1\xf3\xd3?\xe2i\xad\x94\xa0/\xe8\xbf\xfe\xd8\xd1\xe8\xa3\x8e\xdd\xbf#\x89\xb9l\x9e\x9c\xe9?\x13%\xd2\xdf\xa0[\xf4\xbf\xd8\xd5\xbdV\x1c\xf8\xf0\xbf\x978\xf7\xf8\x98\\\xec\xbf\x03\x1f\xbfb*\t\xd8?\xeeH]0\xe7q\xf6\xbfK\x13\x02\xdb|R\x06\xc0\x95\x1a\x1c\xc5\xda\xe2\xe6\xbf\x84\x9fi\xac\x0f,\xe9?\xef\xbd\xa7\xe6\xec\x8b\xf6\xbfZ\xbc\x94\x06\xf3X\xdc?\x94\xbd\x8b\x99\xa8\xa1\xe9\xbfR.rPg\xbd\xfb?\xfd\x18G.{1\xe1\xbf\xfd`\x1b\xcaiT\xaa?\x8e\x9d\t\xc0\xff\xfa\xd7?\x92\xab\xbbF\xfc\x87\xdb?\xeb\x94\x8f\x91\xb2\xad\xf5\xbf\x97\x9d\x0b\xdd\xa2j\xea?\xf5\xddC\x7f\x1c\xb2\xf3\xbf\x85\x02h\xa2\x8f\xf4\xd7?\xd1Y\xb85o2\xff?>\x8c\xab\x1f\xdc\xa1\xe3?I\x93\xa9\xd2\xfc\xc0\xa9?%\x9f\x03\x05\x1f\xdf\xcb\xbf\x1f\xfc\xf9\xa4\xd2t\x96\xbfS\xedM+\x1f|\xe4?\x1bw\xbc\xfb\x03m\xf3?\xd5\xb0.\xf2k\xcf\xf5?\xd0\xec\xa9R[\x95\xdb?\xbd\xb48\xa7\xa3\x1f\xf1\xbf\xfa\xc1(\x10\x90\t\xbc?\xe5\xad\xfe\xe1\xf6\x0c\xe2\xbf\xc9r"\x99\tV\xd0?\xb3q{\r\xa3\x96\xb0\xbf=vc\xfa\x06\xbb\xe2\xbf{!V\xa1\n\xb5\xe4\xbfMZ\xecR[>\xb2?o\xebd\xbf\x91\xbc\xf2\xbf\x1c\x15\xb5\xc7\x9a<\xef?\xe0\x8d\xa1\xdb\xbca\xf2?\x9a\xc19\xa95-\xd7?:\x13\x9f\xd7\xd9\n\xc2?\x16\xa9#\xda\x92\xb0\xe5\xbfK\xcc\xe1\x8b\xa8G\xe0?\xf1\xc5\x9b\xbc\xa7D\xfd?QG\x9bV\x9a\xcc\xc4?]\x12\xc2\xd6g\x99\xcd\xbf W\xbd\xb2\x0bG\x05\xc0W\xfe\xbc\x11\xcf\xc3\xf6\xbfG\xde\x87bf3\x90\xbfzW@\x91\xcd\xfc\x94?\xc5\xf3\xde\xc8\xfen\xe1\xbf\xf0\xd2\xaa\x9a\xd3\xc9\xc8\xbf2\x9eb\x93m\x16\xf2\xbf(_&|T\xc4\xe5?\x84\xb1\xf7Ghf\xda\xbf\xe5\x04\x80\x02\x8d\x1a\xec\xbfL\xeaX\xceA\xea\xb8?\x0cT\xa4\x95X\x96\xf4\xbfw#\xc7\xa8\xa7\x1d\xf5\xbf\x87xeL\xd1\xe7\xd1?\x8f\xcf\xa4\x04\x7fz\xdb\xbfae\xcf\xca\x02\xb4\x0c@v;\x1anAt\xf0\xbf\xc8\x1bi\xd7\x82\xc9\xf0?03V\xf8\xa0\x96\xde\xbf@\xcc>5\xa5\xeb\xe5\xbf@\x18\xccVm~\xbb\xbf\x8b\xf0W\xf9\xc3\x91\xd4?\n\x88\xf1\x90\x1be\xf3?\xb1\xb9\x86G;\xcc\xd9?\xa2\xb7\xec\xc8\xb3\x81\xdb?\xf0\x12v\xea\xfe\xd8\xee?\x94\x10\x11\x06q\x0f\xd6?\xb5DV\xf5\xa1\r\xd5\xbf2\xe2\xf3\xe9s\x86\xc2?\xc0X\xc2\xeb)\xee\xd2\xbfqk(Zr\xe5\xf7\xbf\x86g.\x06\xae\xd2\xb7\xbfUC"\xbf\x87_\xf3?\x7f\x197\x17^\x11\xac\xbf\xc8:\xf0\xed\x98!\xef?\x1f\x8f\xfa\x84F\x04\xe9\xbf\xf2\x86^\xa0\xff\xf7\xf6?\x8f\xd0_0\xa7Z\xd5?\x8cg6\x9d\xc1\x13\xe8\xbf\x7f\xc1\xdd\x9d\xbe\xad\xf0?\x16i&\xeb\xf8\xa5\xff?\xba\xe0\xe1/\x1d\xfb\xf2\xbf\xb4\xfc\xf6\x14(\x0c\xf0?\xb2\x9e\x91\x0fP\x03\xf0?\xac\xca*\x8d\xd9\xb4\xe1\xbfM\x8b,]VH\xc6?\xe5\x93\xab\xbb\xec\x0c\xe1?2\xcc\xd0\xb4U1\xe6\xbf\xcf&\xbb\xcf\x9e$\xab?3\x1cM\xc5\x9d\xe9\xea\xbfj\x9e\x10\x1a\x16|\xcf\xbfq,b]\xb8\xa2\xe5?\xb0Vp\xd1\x98p\xda?\xdf\x82\xdfu\xdb!\xf0\xbf\xd8.\xf6\xda$J\xdc?\xd8\x07 \x97d(\xf1?#\xc9\x9fp\\\xe2\xd5\xbf\xcb\xb7$\xa7\xbc\x08\xe9?0!\x817\x86\x08\xe8\xbf\xe3E\xdekU\x82\xd5\xbf\x8fEEyi\xe2\xe6\xbf\xf8c2\xfdy\x0b\xd0\xbffv\xd6\xfeU\x87\xea\xbf\xaf+\x81\xc7o/\xf9\xbf\xabh\x95"v\x13\xeb?5!\xf2%\'^\xfa?^\xef;\xa7\xde\xf2\xe7?\xb9\x14\xeb\x86\x8d\x1e\xd9\xbf\xe5\xcf\x95+\xdb>\xee\xbf\xe0v\x13\x8e_\x07\xc5?\xb2F+%\x9d\xa2\xf1\xbf\x0e\x01\xe3G\xf3\r\xfd?\xafT\xc5\xa9U\xa5\xe3\xbf\xce\xb86\xcd\xbe@\xdc\xbf\x015{\x948\xb9\xad?!\xce\x1ar\x9d\xf5\x92\xbf\x93\x93\x85v^t\xe4\xbf\x10\x81\x92Y\xb6A\xf5?\xe2eG\n\x7f\x8d?\xb1\x9995\xa9\xa1\x00\xc0\x91\xf6%3k\x07\xc2?\'F\xa4}K)\xf5\xbf\x04\x91\xfe\x92\xeb\x8e\xd9?\x8d\x10\xe1\xe8f2\xf2\xbf\x8a\xdcC\xcfg\x18\xe0?\r\x8ey\xfe\xef\x05\xd7?\xd9\x98xs\x96|\xd2\xbfeE\x0b\xb1\xcfH\xd7?\xedV\x90\x80\xceI\xf4\xbf$\xd6\xe4\xe7\x1d\xed\xd1\xbf\xecp\xdcai\x9e\xe6?\\\xdd\x0bR\xad%\xed?5:\xb6no\xb5\xf2?\'Ch\xed\x1a\xa3\xcc\xbf\xc4O\x83\x0c\x00\xa0\xe7\xbf\xdf/\xafR\xb7K\xdc?\xe5y\x10\xf5\xe25\xe7?\\4\xe3\xc8eX\xf3\xbfL\x10\x9f\xf7\xa6\x00\xec\xbf=\xe8\x07\xbb\xbb\x97\xc6\xbf{`s\xaaU\xe5\xe7\xbf\x0b\xee\xe1\xe4C#\xf7\xbf\x8f\xb0\xd4\xa6)\x98\xf5?\x85\xc5\xbf\x02\x1d\x94\xe9\xbf\x1dD&u\x92\x0e\xce?\x15\xc8D7?\xf2\x01\xc0\x8d\xf0\xb4\xdc\xdf\x94\x02\xc0\x1f\xfc\x87\xff\x03\x07\xea\xbf\xdb6\x89X\xf6\xbd\xeb?WMx\xcdL\xf7\xc3\xbf\xe1\xb6\x13\xe6\x14A\x03\xc0\xb3m\x0e\xa9\xddd\xf6\xbf\x8f\xb7D\xa0.\x82\xf2\xbf@*\xf7if}\xf8\xbf\x04\xd6\xdaF\xcfv\xf9\xbf\xbd\xd1\xab\xdbK!\xb9\xbf\x96\xed\xa0\xd8U\xa8\xee\xbf\x12\x13\x9d!)~\xe1\xbf3\x07\x86\x11\xabA\xfa?\x03`2;\xd6\xa4\x81?P\xa3\xba\x1a!\xef\x9c\xbf\xc1\xfd\x14\x067\t\x01\xc0\xdd\xba\xa7\x15=g\xf0?s\xd1\x12\xa4\x9e\x84\xfb?D@,\x00\x88=\xc6\xbfb\x91\xdaC:\xf2\xc6\xbf\x82\xb2#\xd8\xf0\x9c\xc1\xbf\x97\x9e"\xda/\xd2\xc7\xbfC88\xe9\x86b\xd5\xbf)\xaf"14\x12\xef\xbf+\x1b\xf9\x8cY\xa9\xf0\xbf\xccJ\xfe\n\x04a\xd1\xbf\xbeY\xfe\xf9\x80\x00\x00\xc0\x129\xc9\xf48\xac\xe2?\xe1a?\xf8\xc2\xd6\x04\xc0\xb2\x18p\xb5<\x0b\xf5\xbf\xc3~g\x0b\x8a\xf4\x01\xc0\x8f\xfd\xc6/\x16\xd6\x01@\xb0\xe6\x85s^\xbd\xed\xbfe\xd0U\xc8E\xa2\xf0\xbf\xf0Y\xe4\xc7~\xb3\xf0?\xc7\x88s\xc6\x01\xf2\xfa?\\\xffW \x13?\xf1?o\xcc\xe9\xe5\xedl\xc5\xbfP\xff\xe6y\xd8\x85\xd4?$aD\xea\n\x06\xc7?T\xeb\xa6\x18\xf7\xfb\xdf\xbf\xbd\xb6\x17\x16\xdb\xf1\xec?\x829H\xb3;\xda\xf9?5\xfb\xa1\xf9\xe6[\xfa?\xeab\x90\xd0\x85_\xf5\xbfo\x93\xa2\xfa-\xfa\xed?\xff\xf6$&\x9dg\xe3\xbfm93\xb0\x059\xd8\xbf>\xa1kZ\xf3\x8f\xd5\xbfp\xc3IZI\xea\xf0\xbfj\xec\xec0\x06\xe6\xf4\xbfw|\xac\xa5\xab\xf0\xd7\xbf\xa2\x87$\x01\xef\xf2\xf0\xbf\xc2c\xe4\xab\xee\xa2\xf5\xbf (\x15\xf3\x0b\x96\xf5?z\xd9\xf0\x03\xef\xe2\xb1?\xa8\x8c}z\xb8 \xf4\xbf\xaep\xe05\tJ\xe3\xbfw\'fXzY\xed\xbfr=v\xc1\tf\xea\xbf\xdb7\xfc\xe0\x11H\xdd\xbf\xc2\x97\x1f\n\x19\xf2\x03\xc0\xdaS\x95x\x1f\x03\x82?bN\x1dW3~\xc6?\xa2\x85\x0e\xf6\xc3d\xed?\xfeW\x0e\xf7:R\xe9\xbfH\xf8\x98\x0e\x88\xb3\xfc\xbf\x8b\xff\x0e\x1ea\xd4\xf5\xbf\xab\xb2\x9c\xeb\x12\x8b\xfd?\xdf%>\xb7\xd9A\xd9\xbf\xd7\xc1\x01w\xb9\x0c\xe4\xbf\xa5\x02\x90\xa7\xd8U\xda\xbfZ\xfc8\xcb\xad\x9f\xf3?\xcd\xcc\xac\xfdf\x94\xf7?\x04H\xfe#\xd0\xbb\xe0?g\x9aQ\x97V^\xfd?\x1erHq\x16a\xd9?\x00\xfc\xd7\xb5\xa1\xce\xe7?7\xe2\xa9\x11\xef\xfe\xf3\xbf\x87\x17)t\xa8\x9b\xf2\xbf\x19\xd59\'\xc5\x15\xdc\xbf\x9e=Y}\x1e\n\xe9\xbf\xb7\xc2\xb1.J\xe0\xec\xbf$^\\3\'o\xad\xbf\xb4Vq\\\\d\xe9?@\xf7\x0f\xc9w<\xf5\xbf`Css,\x9a\xf4?t\xde3\xaf\xaa3\xf5\xbf\xc9\xbd\xe0\xc9P\x17\xc4\xbf\'\x01\x81\xba\xb1\x89\xe5\xbfF\x08\xdd:\x82Z\x03\xc0\x1c5"e\x0cL\xe3?\x06\xa0\x18;\xa6G\xd7?\xd8\xa7\xd8\x1b\x01\x04\xc1?\x87\xe5<\xd5L\xe0\xe4\xbf,\xe1\x12\xdf\x98\xf4\xd5\xbf\xd8\xe2\xfa\x93-\x07\xf0?\x1c{\x9c\xb8\x89S\xd3\xbf2U\x1f\xec\x91\x04\xd5\xbfG\xc9G\x06\xb2\x19\x161[\xf3?[\xfa\xaf-A\xb0\xf1?\x13\x12\x13W\xcc\x80\xef\xbf\x90%BY\x1e\x1e\xe4\xbfs\x07\xf5\xc5}\x05\xda?\x1d\x0e\xfa+\xd9}\xef?\x871\xcb\xc3\xcae\xd9?3\x1f\xdd\xf8\xec\x9c\xc5\xbf\x1b\x01\xc8\xe1 \xcb\xff?\x050\x84\x99\x8b&\xf6?7\xe3\x876,\x1d\xf0?\x94\x97F\x99\x95\x06\xd3\xbf:\x94{\xa5\xdfP\xdc?\x1c\x882\xb4\xc5=\xf9?=\x9fx4D\xb2\xf3\xbf\xae\xb1\x08\xd3\x86h\xd0\xbf\xb4\x8d\xff4?\x15\xb0\xbf\x1etE\xe5\x1f\xb0\xe4\xbf\xbc\xa5\xe0\xa18\x10\xf6?\x8c\xf9e^m\xca\xe1\xbf\xb1\xc7\xc6\xa7\xd9-\xef\xbf\xd5\xb8\xf00\x8d\xa6\xe4?\xe6\x8e\xc0\x8e^\x97\xc4?\x03\xfb\x98\xf5+0\xed\xbf\xcf\x83\n=mH\xda\xbf\xe17-\x96\x976\xdf\xbf\xc2wPg\xa3G\xe3?\xb7\x17ZZ\x95\xea\xdc?\x11\xabF\xf4\xcbg\xe3?\x1a\x1d\xa7X\xf2X\xe7?5\xb2&5\x8c\x19\xb9?E\xb7\x8fMLt\xd0?;\x8fO\xb6\x0c\x12\xc7\xbf\xbac\x97\xe4i\x02\xf9?\xc3\x15\xe0\xbe}\xe1\x00@Oo\xe5\x8f~\x19\xf0?\x9a,\xc3\x81CF\xd2?\r\x9f\x87\x98\x8c\x96\xf0?"J\x1bq\x05\xec\xe1?\xff\xfe\x17\xd3MQ\xd7\xbf\xde\x92F{\xe9K\xc5?8\x08\x03\xc3-\xea\xe8\xbfOc\xf1\xe7EB\xdb\xbf}\xb8\x1f\x97\xbf\xb6\xb9?\xd5k\xa7S\x1a\xe8\xed?#\x140\x9c\xf0C\xe7?\x1ez\x90\xe4\x82+\xf2\xbf\x81\xed\x0b\xfa\x04\xab\xe3?\xfd\xa6\xcfI\xa5T\xaf\xbf\x88\xech.\xeb\x08\xca\xbf\xe7\xc0M\xe2"x\xe2\xbfLT\x80\x1f\x87\x83\xe9?#{4\x9dW$\xac?o\x1cs\xdd\xd9\xb2\xef\xbf\xb4\xa6\x8a \xa8\xea\xbc\xbfY\x8f\xa4\x8af\x99\xd0?{\xdbX\xc1}d\xd0?\x87Z\xa1\xf4e\x02\xe6?9\x8c~\xe2\x04>\x02@5\xb2\xfd\xddF\xa3\xc5?\xbcz\xd6\xcd_R\xf2\xbf]\x184\xa2\xf7\xf5\xec?\x8d\xbc\xf7\xe9f2\x0b@+x\x17\x17\xbea\xe6?[MKQU\x01\xf7?.\xbb\xddXXb\xf1?\xb3\xe5\xcf\xa8]\x16\xb9\xbf?\x0ch\x17\x16\xee\xe6?^\xaa\x86\xc3\x14\xf2\xfa\xbfU\xd7\xde\x1f\xfa\xec\xe8?|#u\xbd\x92\x06\xea?\xca\xe6\x9c\xba\x07\x83\xea?\x0cR\x13\x0c\x80\xc0\xe7\xbfe\x88B\xfa\xadG\xe4\xbfRM\xb0\xc8\x0b\x8b\xe2?\x803\xb6$\xf0\x87\xdf?*\xaf1\xfb\xd8\x93\xe7?\xac\xdc;\xaf\xf8\xef\x87?\xb7\xc1\x01h!\xaa\xda\xbf\xc6P\xd1\xd2\x14G\xe5\xbf\x87D\x9b\xb2\x03\x17\xd9?\xfd\x08u\xe2\xe5\x82\xee?{r\xbb\x92\xc6:\xf5?\xa5\xa22\xe1B \xfe?\xf4tw\x93(\xc2\xf0?\x91\x11)\x19\xa4\xa6\x01@\x1a\xdb\xb7mLy\xe1?\xb5\xdeD\x17\x86\xed\xd6\xbf\\\xbd\x9d\x04\x86l\xf0\xbf\xf7\xefT^\xb6\xc9\xf3?\xfc\xca\xb8\xe6cb\x02@\x7f\xf5*\x9a\x1d\x9e\x02@%o\xd97\x91g\xf2?a\x15\x12qn\xc8\xe3?\x0c\x13\xd0\x91\x15\xac\xd7?\xc6\xc6\xb5U\xe4o\xd3?+\x85\xb9#Pb\xfa\xbf\xdf\xf2\xc1\xa9;\x86\xf1\xbf\xf9\x9f\x0b\xa3\xa45\xd1\xbf\x83\'\xdd\x8e\xe3\xc6\xfa\xbf\x83\xb5$\x9d!\xb0\xcf?\xd6\xce\x01\x16#\x1c\xf4\xbf\x0c\xb8v\xa9"\x98\xe8\xbfo=W\xdfI\xac\xc8\xbf\x08\x84\xed:\xe2\xc2\xf1\xbf\xb0\x0b\\T\xbd#\xe8\xbfW\xdf\x81?\xd5\xff\x97\xbf\x02\xa0\xd8\xac\xdd~\xaa\xbf\x00th\x1e/\x9f\xf1?\xae\xe6|PV{\xfb?\xc2\xd1\xffi\xf8\x88\xd2?\xbc\x9dYF\x85\xdf\xf8?wB\x80>\xaa\xa0\xf7?\xbfEzlr\xfa\xc2\xbf\xbe\x93\x9e\x1c\xdd\xc0\xed?\xdd\x87\xe0\rP\xe6\xf6\xbfe\x8dL\xeb\x1f&\x02\xc0\xf0\'\xc7\xeaF\x8f\xe8\xbf\xc6Y"\x86\xf0w\xe8\xbf\x19\x90?S\x05[\xd9?\xe8j\xf1\xa8\x04*\xb3\xbfq`c2C\xec\xe3?\xb1gZ\xb5Z\xa2\xf9?M<\x13\x82\x9e\x07\xb1\xbf\xf4m\x87F\r\xd7\xe0\xbf\xeeo\xea\x97\x81\x07\xe8\xbf[\xbcCB\x8c`\xfd\xbf9\xadd>0+\xe0?C\x9a\xe0f\xa2v\x9f?\xc8\xbf;\xe0\xc9\xe9\xc7\xbf\x0e\xed\xf9V\xac(\xf4?E\xac\xc9\xc3\xec\xf1\xf0\xbf\xdc|hFl0\xe0?\x0bWId\x81\xf8\x96?O\xd60gS\'\xfc\xbfU\xc0\xfeVp\xce\xd5?\xaa\x10\x81\xe4\xac\xc5\xb3?\x8f\xca\xc5vN\xe8\xfa?\xe0}=\xa2z\xf3\xf0?\x00\x99\xf1(\x7f\x92\xde?\x18\x8d\xe0\x03\t\x89\x05@B\xd4W\xe0\xc6\xac\xf1?\xa6\xfbj\x90\xba\x96\xf3?UN\xe7.\xf7\xce\x08\xc0\xfa_\x9cI\xb3|\x02\xc0.\x9a,\x96\x98X\xdf\xbf_\xae\xf4M\xb3\xdc\xf6?\x98\xb2\x85\xca\xcb\xd1\xfa?\xc4:\xf6[\x04\xdf\xdd?]\xaeG=\x0c[\xf4?\xce\xf3\\\xa1\xa0\x94\xef?e\xb4\xcb]u{\xdd?1St"\x8b\xa3\xf4\xbfz\x0b\x97P\xd4@\xf5\xbfI\xf0\xae\xfbQG\xf3?\xa5\xe4\x8f\xb7\xd2\xd9\x04\xc0\xaa`\xa0\xaep\x9e\xaa?\x90fu[\x0ba\xe5\xbf\x92C\xf1]\x88\x05\xe0?\xd3n\xad\xd0h{\xd5?}eR\'|\x17\xf6?\x97\xd1\xc9K\t\x9d\xce\xbf\xb2M\xeb\x01\xabb\xd4?X\x18\xca\xdb\xca\xcc\xc1\xbf\x07\x9e\xd3\x90\xe4\x0e\x05@:G\xd8\xfb\x87\xd4\xe6?\xc0\xce\x12v\xb9j\xc3?\x85`\xe3`e\xc9\xeb?S\x90\xc7<`@\xde\xbfB\x0f\xb3-2\x99\xfa\xbf\x93\xd5\xc6\xc0\xc8y\xf3?\xad\x1bQ\xf0\x8c>\xf0\xbf\xda\xbe\xed\xb9\x10\x9e\xfa\xbf\x1a\xf0-\xf91\xbc\xf0\xbf\x0b\xeaH\rq\xd0\xd2\xbf\xf4\x18}\xbd2\xfb\xc1\xbf.\x18D\x86\xb5\xde\x01@\xe9\xdf\x1e<\xf3\x8d\xba?\xaaE\xf05\x82I\xf0?\x0f\xb5\x99N\xb57\xce\xbf8\xe8\xf0\xd3\xbb\x9c\xf0\xbf\xc9\xf7\xa7\xcep\xa9\xf1\xbf\x14\x83\xcb4\x13\xf4\xd3?\x8co\nV\x1d\x19\xe9?\x95\xac\x8c,\x19:\xfd\xbfD\xba\xc5u\xa8\xb9\xc7?\xca\xbf \x0e\xae\xa1\x00\xc0H\x13R\xef\xff4\xed?\xc0\xaa.!\xf3\x8d\xff\xbf\xc4\xb6\xd2\xc7wU\xdd?\x18\xfb\xaaM\x82\xc5\xc2?\xeaf\xb5\\NH\x05@\xba\xa8\xba\xc0\xe7\xeb\xf0?\xbd\xe8"\xaf\xdew\xfa?\xae\xc1G|<\x89\xd7?\x0e\xa3\x13\xa9\x9a\xa3\xc7\xbf\x97JU3\xad\xec\xfc?\x044\x05\xbf\xf3\x93_\xbf\xf1\x0b0,+\x0e\xe4\xbf\x11\xa8H,8\xdf\x0b\xc0\xf4\xf4H\xe2\xc2\xfd\xfb\xbf%\x88x\x87\x07|\xf9?\xfd\x91\x8a:\xc0\xa5\xee?\xe5#\x05\xa6\x9a\xa4\x02@C\xfd\xb0\xd4J\xb2\xe5\xbf\x80\x88u\xc6\x13\xae\xf1?+*HP\xf1\xfa\x00@ \xdd\x0fd?\x17\xd2?\xfc\xf3\xc4\xe1\xf1\xc9\xe0?\x8fc\xde\x06A\x93\xd5?b+\xb4\x8eK\xe5\xdf?\xce\xed\xb5v\xd4\xdb\xe1\xbf\x9b\x19\xf5\xd2\xc4\xdc\xf9\xbf\x81\x0c\xf2\xf2]\x83\xfa?\xaf\xb2D\x97\x19H\xe5?N\x0bq-\xfb\x8a\xe9?\x97\ni\xf33\xf1\xda\xbfW\xfe\x93\x19I \xf1\xbf\xd5\xb3\x7f\x17\x7fc\xf6\xbf\xdb\xcd\x7fT\xe41\xca\xbf"r\xc8\xc60]\xf8\xbf\x90\'\xb8"\x82\xcc\xfa?^\xf0~\xe4\x9bm\xf6??\x86\xc7\x9b\x8e\x9b\xee?1\x03\x18\x13V\xac\xe1\xbf\xc4\xf4\x15B\xae\x95\xc9?\xdc,\x87H\xec\xdc\x01\xc01\xd3+m\xe8\xbc\x11\xc0\x9d\x1f\x0f\xbf\xdf\xe8\x01\xc0\x0f\xe9\x18\xb6We\xe7?LOq\xd7D\xf3\xcc\xbf\\\xe7]\x07\xb7o\xfa?U%\xa7\xab\xb5\xa0\xd5?\xbaX\x93+\x16\n\xd8?:\xce\xc86\xb0o\xe3?w\x0e\x13\x1d\xff\xc7\x03\xc0\x99.\xe4\xd1\xc0:\xf2\xbf\xb1\x8f\xe8\xf5(3\xf8\xbf \x93\xe1\xb3?\xde\xc8?\xcb\xe2R-L\x02\xf1\xbf\'\xb1\xcbQ\xa7n\xd5\xbf\xaaP\xb3\x90\xa7\xca\xf2\xbf\xae\x99\xce\x1b\x8c\xf3\xf0\xbf\x12\xe6\xc5km\xa7\xe9\xbfV\xdcu\xa4\xe5\xab\xce?dnO\x9b#\xcb\xf5\xbf\xd9\xf7\xc1\xf2\x83X\x00\xc0\xa0\x08\xbc\xdfS\xd7\xdf?\xc5\t\xf1\xdc\xdf\xb0\xfb\xbf\x1a9\x15\x005M\xea?T\xb0&\xc5h\x19\xf7?O\x85E~\x98\xcd\xe5\xbfD\xe9\xd7g\x8a\x0f\xad\xbf\xd1g@\x86\x19\xc6\xe5\xbf\x06a\xd3\xcb&\n\xed\xbf\xf9\x82\xac\x1f\xf9\x03\xf9\xbf\x835\xf2\x83\x97\r\xf8\xbf\x02\xb0\xb7\xdc\x9b\xe8\xf2?\xf7\x0bG\x16\xb0!\xd5?\xce&\xd5\x8b\x89\xe1\xe1\xbf\x00\xb8\x9b\x0fl\xdf\xf6?\xca\xed\xae\xf9\x1c\xd3\xf5\xbf]\xec\x16\x02:W\xf9\xbf\xec\xa8,\x87\xdb\xe6\xbb\xbfi\x01c\x84K~\xf7\xbf?+\x19\rAv\xe4?\x05x\xa2\xc6SJ\xf2\xbf\xf6\x8fF\xadsx\xe5?\x02\xc3\xc9\xd6b\xa1\xc5?T\xa3R\xf3\x1fv\xc5?\xad\xd9\xe7\xc6\xe6n\xf7\xbf\xe9\'\x82U\xa4*\xec?E6\x04S\xab(\xea?0\x1bv6\x13\x14\xea\xbf\xf2 \xbb\xc8\x96\x9e\x01\xc0t\xf1+d0Y\xf4?(\x82{^\x0b\x9d\xc1\xbf"\x97\x83\x84\xff\xce\xfb\xbfMV\xf0W\xbd\x8c\xca\xbfT\xd0I\xfa\t8\xb1\xbf\xa0\x8e#\x1eZ\x9b\xef?\xf9$J\x11\x1bU\xf1?\xc7Sv\xf6\xa6\xb2\x9f?\xee\xc2=X\x1d\xa6\x02\xc0oyTR\xe7\xda\xf2\xbflt\x8c\x9a\x8d\xda\xe1\xbfd\xeb\xb8&\x94\x10\xe1?\xde\x06 \xabf\xa8\x98?\x04\xc0\\z^\xbc\xff\xbf\xbb\x1c\x15\xf7\xc1\x02\xff\xbf\xd8M\x07-`\xf4\xf5\xbf\xd1\xb8\xf9\xe8=\x1e\xe9\xbfp!\xa2\xef\xaa\'\xea\xbfk\xa9\x99i\x02u\xf6\xbf\xe3\x08G\xcd\xe5\xfc\xf7\xbf8\x10\x8fo\x7f\xfd\xf3\xbf\x82!\x1a\xe3\xcd\x14\xeb\xbf\xf1o\x81\x99w6\xf4\xbf\xfaGs\xdb\x96W\xe4\xbf\x0e\xe5\x00\xce\x89\xc8\x02\xc0\xf5\x19\x15\x1e\x88\x05\xc9\xbf\xdf\xc1\x86c\x97`\xfc\xbf\x02\x88$\xb9\xf9\xf6\xf0\xbf\r\xa2\xb3\xc1\xc6\x0e\xf3?E\xaa\xea\x88\xc2\x0e\xd1??\x8c\xf4\x8dh)\x00\xc0\x08\xebf1\n\x96\xff\xbf)\xf3\x1ce:a\xe8\xbf\xa6~t\xde`\xbd\xe5\xbf\x9d\xf4Q\x16\xa3\xdb\xf8\xbfJ5\x10_\xfe\'\xf9\xbf8\x996\x05\x004\xe6\xbf"\xc7\xd6\x93D\xb7\xeb?Sm>yi\x16\xdb\xbfeC}\xa7\xc7M\xe9\xbfJ9\xe2\xa9|V\xcd\xbf]3\xaeQ!\\\xe2?%<\xdf{\x92\xbf\xd2?\xa5\x1b\x94!\x82"\xf1\xbfI\xf4.\x00#,\xda\xbf\xe5=\x94:\xdf\xcb\x07\xc0\xba[\xa8\x99\xdcV\x03\xc0^3\xcc\xc7\x08\xe7\xd7\xbf$X\xd1\xac/\x88\xd8\xbf\xfb\xd8x`i\xdc\xdc?\xd4\xe04\x01\xef\xb3\xf5\xbfY\xc4a=\xfb\xbe\xf5\xbf\x9eCb.\xdf\x19\xdb\xbfAy};\x08-\xde\xbf\xf8s|jV\x8d\xd1\xbf\xf9\xdbm\xf1Uo\xba?L\x8a\xb2\x08u\xf0\xe5?\t\xc1i=\t\x88\xef?5\xcf\xb8\x12\x94\xb9\x01\xc0+ud\x96\xe1\xdb\xf6\xbf\n\x8f\xe7\xd9\x1e\xb4\xf0\xbf\x80\xeas\xaa\t\xca\x98?~\x98\x9b\xc7\xd5 \xec\xbf\xffv\xbdTD\xcc\xd1?&\xa9*\xdd_\x03\xea\xbf\x9a\t\x88\xf5\xe2\xe9\xe2?)\x9c\xb9\x0ct\x89\xfd\xbfP\xeek\x82\x88\xc1\xf6\xbf\x19u3\x9b\xc5\xd9\xf5\xbfmx;\xe1\xb2T\xe7\xbfr\xd6\x99\x1d\xc0O\xe6\xbf\xcd\xc3\x8e<\x95\xb4\xbb?\x0bo\xa2\x96}a\x0c\xc0A\xa9\xa8M\x82x\xd9\xbf\x0e\xa78\xfe<{\xf8\xbf\x9d{}\xaf*\x98\xe4\xbf\xca0\xb2\xba\xc9\xf1\xea?\xba\xf4%8A\xe7\xa1?\xedtB\x0c-\xd3\xd3?\xa1H\x14\xab\xac\xae\xbe\xbf\xf5?\xdb\'\x98\xef\xc2?\xe9\x1a\xe7\x9fx\xde\xe3\xbf\x9a\x04~\xa56\xa7\xf4\xbf\x8aD+A\xa4\xca\xf1?s\x9cC\xd45\x85\xf5\xbfz\x97\nA\xccZ\x00\xc0\nV\x94X\xbd<\x9b?\xa2f\xb2J\x99\x1e\xe8\xbf\x05\x813IJ\xe8\xf6\xbf3\xe8ix\xf4d\x01\xc0\xd0\xbf\xc8\xdbZ\xf7x?\xa3\xde\xcf\xaa\x9d8\xd8\xbf\x9e\xfa\xad\xef\x9b\xb7\xfc\xbf\xa9o\xa1\xbc\xa9\xfe\xf7\xbf*b\x9a(^S\xdc\xbf>\xfa\x15\xf2&9\xf6?\x87[\x80\x16\xe7\x9a\xfd\xbfcakD3\xfc\xfd\xbf\xcc\xca\x9c\x0b\xb6k\xe4?\xca\xf8\xb9c\xa5\x11\xe5\xbf\x81\xedT#\xca{\xfa\xbf\x8c\xbbs\xf5\xc9\xf2\xf1\xbf[&~\xa9\xdc\xf1\xef\xbf\xff\x0f%\xc7\x7f\xe5\xbe?\x9d\xfa)\xe1\x7f\xef\xb5?\xf7:\xac\xbc\xa0\t\xed\xbf\xf7\xadB\xc7\xa8#\x01\xc0\x11\xe6\xbb\xb0\x83\x1d\xf2\xbf\x12\x0b\x88\x19h\x97\xec\xbf\x030\x8e\x17\x88,\xee?\x9a&t\xb8E\x8d\xfc\xbfs\xcd\x1e\xea\x9e\x0e\xf5?\x84\xd7\x1f\xd3z\xec\xee\xbfL\x0cH\xda(h\xd4\xbfj\xb8g\x19^\xad\xf7\xbf\xca\x18\x84\xfb"\xbe\xcc?\x0fT*\x98R\x1f\xee\xbf\x1f\xc7\xe25\xb0"\xf2\xbf\xdd\xf2\'\x14\xae6\xf3\xbf]#0\xf8\xcc\x80\xdf?\xee\xf7\xd0\xb3\xbe#\xad?\x8b\xd2\x98\xcd\xe4\x01\xc1\xbf\x98>\xf3\x91\xc3\x1e\xe5\xbf\x1b\x12\x84g]\x99\xf9\xbf7\x1c\x17&\xab\xbf\xf1?A:)\x04\xad\x1b\xeb\xbf\xc1Y\x17\xa8\xde>\xeb\xbf\xd0oy\x982@\xdb?D3\xd9\x9b\x0f\xf0\xec\xbf\xa9Mo\x89\x13\xfb\xd4\xbf\xe5\x86%2\x1e+\xd0?*\x01\x13\xc9\xda\x9f\xf0?\xf7k\xa5\xef\xfb\x05\xe0\xbf\xfc\x19e\x1b\xf47\xe9\xbf\xb5P\xe42\x94E\xf4?\xd1\xad5\xea\x9b\x0f\xcb\xbf\xd1=\xcc$\x85r\xe1?"/\xc7z\xd0|\xf1\xbf\x9e\xadZs\x9e\xea\xe3?\xc8l\xb6w\x9c[\x03\xc0\xdc\xf0\x8a\xb3\xb9\xd1\xd6\xbfN\xea^GK\x98\xd4\xbf\x94\x14\x82\xcb6\xe4\xf5?h\xd67*U\xa4\xf1?+t*Ff\x19\xf4\xbf*\xeb\x05j"\x98\xcd?\xe4rbh\x9c\xa0\xdf\xbf\xa2\xe5&/TT\xc8?\x90\x0b\xdf\xcbi\xb1\x85\xbf\xd2Lp\xdc\x06\xdd\xcd\xbf\xa2\x7f\xdb\xde\xeb\xf7\xfc?:gQa\x08\xb9\xf1\xbf\xbdABd\x9f\xfb\xf0\xbf\xa9Or\xe4?\xfe\xdb?-\xb0\x80N\x9d\xc9\xf1\xbf\x16\x82)\x89Q\xba\x04\xc0\x9a:,\xb6\xd0i\xfb?^F\x92\xf0\xcc\x9e\xe5?\xcd\xc9\xdd,@\x19\xf1\xbf\xea3\xe4_\xc7 \xe0\xbf"\x96\xa9\x00\xfew\xe0\xbfQjS\xb0\x0f\xe9\xd6?6zI\xf5\xc6\x82\xd9\xbfY\xbd\x01\xc1H\xe8\xfa\xbf\x93\x1cd\x8b\x10J\xb1?h\xce\x91\x9d.\x1d\xbd\xbfi\xfbZ\x92\xfbx\xb4\xbfy\x93\xb0\x8ev\x8b\xf3\xbf\x1a}1\xe7cY\xd4\xbf/J\xf9\x8fa\xb9\xea?j4E&\xf1]\xf4?\x91\xe0\\\x94vV\xdd?\x8e3\xef\x1c4A\x04\xc08\x90\x96l\x99d\xa5?`\xb5\xa3\xb1~5\x04\xc0Lw\xba\x91\xb8\x92\xf9\xbf@\xf8+\xea2\xc0\xfb\xbf\x0f\xafF\xf9\xe4\xd7\xf9\xbfLY\x0c\x10\x15\x0f\xe8\xbf}}4p\xa3\xa5\xfd\xbf\xe8d1\xc1<\xe7\xf6\xbf\x9bg\xaf\xb7C\x89\xca?\xe7&\x90\xa3ce\xe8\xbf\xa8\xda\xde\xc9\x88\xeeT\xbfw\x90:g\xe1\xef\xd3\xbf\xaeU\x16\\\xa4\xb3\xfe?\xbe\xdc\x01\xab\x80\xa8\xc4?\xbaD\xadD>\xae\xf7\xbf\xd4Gt\x1b\xcbY\xf0\xbf\x19\x8e\'\xc7g\xd1\xef?\xffJf\xa4\r\xa7\xf9?nY\x01\x85\xbf\xeb\xe2?8\x9b\xacB*\x9a\xd6\xbf\xf5VA\xb45\x19\xe4\xbfl+j\x8eJ\xc8\xcf\xbfO\xbe"i&7\xa3?W/\x0b\xea\xd83\xf2?H\x9c\xd7w(\xdd\xd6?_\xb92\x84{\x96\xe0?,V/9@\xaf\xe1\xbf\xe1\xa3\xacW\x00\x8d\xc2?.\x08\xeb\x11\xd4\x82\xf1\xbfS\xd6\xe5\xf6\xd5\xa0\xf6\xbf\x07\xfa\x945PN\xe2?\x92\xa9\x1b7"\xfe\xcd\xbf(@:\xcb\'\xaf\xf2\xbf\xef\xd1\xbe\xbdks\xf8\xbf\xa1\xa9\xe7\x13\xa67\xd6?>V%_\x08\xcf\xeb\xbf\x1c\xefn\xcdv@\xc1\xbf\xe4!\xe88/^\xf7?L\xe1\xaa\xed\x11\x16\xb8\xbf\x8f\xcf&\xdd\xdf\x83\xd7\xbf\x15\t\xc0\xff\xe5\x12\xd5\xbf1\x83\xf7\xa3\x9fJ\xd6?\x93\xc7\xc3\x0c\xc9\x1d\xbc\xbf\x0b*\x06\xad\xa2\xdf\xf3?,\xc1!p\x83\xaf\xc7\xbf\x158\x92\x7f\x0b\x9c\xfd?\x91\x1co\xc6\xec\x05\xd3?\'\'\xe5\x97\x90\xc3\xe8\xbf\xee\x94F\xec\xc8>\xb5?\xd2\xa5Y\x93\n\x0c\xb6?}\x86,e\xba\x84\xe2? \x98\x8b\x0f2\xba\xdb?\n\x9d\xcegdA\xd6?\xf2\xfa;n\x18\xa9\xb7\xbf\xb3}>\xe9\xe2k\xf1\xbf\xcf\xa3\xa49Qh\xe5?\xa6\xf2\xc4\xd1\x8do\xe5?/\xf3\xe9/\x9c\x89\xda\xbf\xc4\xdd\xbb/\x1c\xa3\xb9\xbfd\x9aJ\x11\x94a\xd4?3\xf9]\xa6\xa6*\xd3?U\xd1}+\x08\xcd\xbd\xbfbt\xed\xde\xad!\xe9\xbf\xad\x9b.u\t\xad\xf2?i\xd0\xec,\x96b\xee\xbf\x8aH\xd9\x81f\x93\xc8?\xd9_\xc4\x04\xd9\x91\xd9\xbfO\x8a7\x98"V\xe1\xbfO\xdfR\xfb\x8a\x97\xdf\xbfF\xc0\x8a\x02\xfb\x94\xeb?2/\x10\x9c*\x97\x95?\x91(\r\x07\x9dx\xf2\xbf\x06\xa0\xf8t\x9ds\xed?\x86\x1e\xe9\xb5$T\xde\xbfdh&\xf6\xc9I\xe9?\xc1\xc4]/d\x1f\xf0\xbf\xcf\x85\xecv/\x9b\xfd\xbf\xb0\xac:\x85\r#\xf5??5\xf4\xb6dS\xa8?\xc8\xf7\x0c\xac\xb5Q\xed?b\xec\x91A\x9f\xea\xe1?x\xde\xf2\x99\xe1\x01\xf2?\xec\xa6P|V\x00\xfb\xbf>j\xddFF\xb7\xc0\xbf\xf1bQ\xa2\x84\xa4\xb6\xbf\\\\\xaf\x14\xd2\xe6\xd9\xbf\'5&\xc5\x8c\x17\xfa\xbfg\x8ep\xe9\x84\x11\xf2\xbf\xa0?\xe9\x02\xf4\x80\x03@\x8f$\x83\xf6\xe9\xf4\xd5?\xd6*\x08\x14\xeeE\xd7\xbf\x01x\xa7Z\xa2\xbd\xe8?nR\xde=/2\xda\xbf%\xe6%^cL\xf5\xbf\xfe\x00\x11\x80\xc4A\xd0?<\x8d{\x9c\xd2\xc7\xe9\xbfl%_\xdb\x7f:\xe2?\x10\xcb\x13\x8a\xbd\x02\xb2\xbf\xe6s\xb1\xec\xd6\xca\xc8\xbf\x81\r\xdb\x8dt\x9e\xe8?\x88_\xbb\xe2g\xad\xe5\xbf\xb5\x0e\x8c\x84\xa8a\xc6?\xaedr\xea1\xbb\xd1?\xfdj\xc0\x11 ^\xf2?\xe4h\xbf3\x8e\xfa\xcc?\x96\xb5w\xa4\x91\t\x97?OH\x80\x03~\x8e\xf2\xbf\xb7Dp\xc1\x1e\xddq?k}\x16\x90\xc4.\xdc\xbfl6?\xc4\x86\xb4v?\xad\x05\xd9.e_\xf5?E\x0c\xd0\xbe\xd3\xa2\xcb\xbf\x18\xb5\x8cQYo\xe2?\xa2\x08\xb2\x85#\x13\x85?\xf17\xe75!b\xd8?8\x9eXv\xf9\xe1\xd7\xbf\x82)\xd7\x167V\x9a\xbf\xe6$\\\x0fj\xdc\xe7\xbf\xf7_vvpt\xbd\xbf\xbf\xa47\xfc\xbd)\xf1?f\xbdH7\xa1\x82\xe8\xbfDl\xbeD\x00\xba\xff\xbf&\x18Wfp8\xf3\xbf\xca\xdf5\n\xf5\xd0\xf2\xbfL\x93h$n\x1a\xda\xbf.\xc3\x1e\xa9\x04z\xf3?\xd7DT\xdb \x91\x10\xc0\x969\xec\xa7\xd3\xab\xf1\xbfKG}\xb1\x85\xe2\xb4?\xc7\xd56\x9d\x19\x85\xf5?\xe5]\x0c\x049-\xe4?\xd0\x80$\xfb\xf6S\xc2?\t\x85T\xceX\xf4\xd0?\xae\x9b\xd8\x1f\x84v\xe0?\xbc\x89\x1cw|c\xf1\xbf\x8c\xba\x18\x9c\x12Q\xe6\xbfD\x80\xed\xb3\n\xf8\xf5\xbf\xa5\xaao{D\xcd\xe7?\xab\x8c\xde2\xb9J\xb1\xbfRt\xf5\xd7\x08\xcf\xf5?\xe5k\xb5\x1ai\xd2\xe1\xbf\x9a\x9d\x8a\x1e\x02n\x94\xbf\x9f\x06\xfek\xe6<\x05\xc0\x88\nc\x94B\x16\xe4?\x82O#`\xd0A\xcf\xbf\xe7\xcd\x88\x07\x83\xd0\x06\xc0-u\tYTz\xc4\xbfD_\x9en\xe3\xd6\xf3?=\x1dH\xa0Z\x19\xd4\xbfm\x8f\xeb\x7f\xd5+\xfb\xbf\x1eT\x8a\xeb6\xbf\xdf?\xa91\xab\x8e|\x06\xc5?z\xb8\xa6w\xd2}\xe3\xbfm^\x92\x0f<\xcc\xe1\xbfF9\x98\x9e\xd6\x84\xbf\xbf\x0c9G\xc05\\\xc7\xbf\'\xd6\x13AqI\xfc\xbf4F\x986\x00\xc4\xe4\xbf\x13\x04\xf0\x03\xd4/\xfe\xbf-\xad\x8d\x8cX\x9b\xee\xbf\xe7\xf6\x96\x16?\xa7\xa9\xbffJ"\xabe\xa2\xd6?b\xf0\xa2u \xd0\xf1?\xb8\xa1\x86\x96!\x80\xef\xbf\n\'\xfd\x85X;\xe5\xbf\xf0\xcc2*(*\xf1\xbfw#\xe9\xbc\x13Q\xf1\xbf\r\x17\x98\xc4/\xaf\xb6?m\xd2&*6\x1f\xdc\xbf\xa5\x00\xf4nP\xa4\xd9\xbf\xf9\x88\xcd,\x89\x9b\xcc\xbf*[\xef\xa6\xc8\xe3\xdc\xbf| \xa4>\xcb\xdb\xaa?\xe0\x86\xde\x8cy\xbf\xdf\xbf\xbdy\xa5\xa11\x8c\xf1\xbfW\xf6y\xf6-\xc7\xf4\xbf\xe3\xd4\x0cl\xe1\xca\xf5\xbfB\xfe9\xc4\x85h\xc7?\x06d\xb3\xdc\xac\xe0\xe3\xbfW\xec\x9f\xf5\x0e\xc3\xed\xbfN\x80*\x82\xc5Y\xf2\xbf\xe0w-R\xfau\xeb?\xe3Ot)v\x96\xd4\xbf\xedu\xa9\xd1r/\xeb\xbf\xd0G\xecPZ:\xc8?\x9b\xb9`\xd2\xd94\xf6?\xbb\xe2\xa1t|\xaf\xe2\xbf\xcb\xdd\'s~\xe3\xf3?`\xcc\xab\xdbs\x99\xe5?\xe4M7\x17G\x8a\xf1\xbf#\x1b\xec\xd6\xe2\xf5\xcb?\x03\x04\xa1\xa4;\x1f\x01\xc0\xdc\tj\x87+_\xf9?4%\xdd\t\x14y\xf2\xbfJg\xae]\x90\xd1\xf7?\x11h@\xfe\x8e2\xe5\xbf\x9f(\x87\xa2\x84\xba\xd7?\xeb\x97O\xc2\x10R\xfc?\xd7\x00l\xf0\xe8}\xd5\xbf\x06\x89M\xb8\xc3\xf4\xf7?<\xbe\xd5M\x00\xd8\xbe\xbfh\x94\x88u"\xa8\xfb?\n4ru\xe7v\xe0\xbfI\xd3#\x14LS\x9f\xbfs\xf5\xa1\xa43\xbc\xc5\xbf\xb0q\x11\xf4\x98O\x05\xc09l\xb3l\xe1\xb7\xf6\xbf\xb5-8\xf1\xd9\x19\x00\xc0\x1cI\xca\xa8\xde2\xf2\xbf\xb0(\xff\xc7\xd7\x10\x00\xc0w\xebbve\x95\xfa\xbf\xa5\x99\xce\xda\x92\xd0\xd0?0\x156+\xd7\xc2\xe5?\x99\xb8\xdc\x8e\x9e\x86\xe5?\x8bf\x05\xe8\x7f\xf4\xec?\xf9V\xe9\xe3c\xbd\xfe\xbf\x9e\x15\x10H\xf7\xb5\xf8?\x1c\xbb\x03b\xfa{\xf1\xbf\x90U\x1bc\xb2\x93i?\xdb\xc88(\x1cKx?\x95\x85Z\xfb\xc17\xdc\xbf\xe6\x85\xab;\xc6\xe8\xe7?\x9a\xda\xf1)\x9d\xfc\xfe?$Ah\xe6\xf2j\xd2?h\xc3\xa5|\xf2\xee\xa4?\xa5\xb8%f\xd0N\xe3?\xa5\x0e\xbd[\xa8W\xdb?@UO\xa71\x8b\xf0\xbfIB7\xa5\x87s\xeb?\x84\xdf}\x03#\xf0\xd2?Z\x97\x86\x10\x08\xff\xec?\xba \x13\t\xe9\x95\xe7?S\x99\xfd7\xb5\xa0\xe7\xbf\x10\xe0\x8f/\xf5\x9f\xe3?\xf5S\xe5YrR\xda\xbf\x8b\'?8\x0e\xb3\r\xc0\xa1\xe4\xe4\xfd\xcet\x0b\xc0=I\xa6\xe2\x08\xf1\xd5?\xf1\xdbt\xaaa\xa3\xe9?\x9a\x90\x1bu\xe9\x07\xda?\xf5v.]X\x1a\xc9?\xa3R\xa3[\x01\x1d\xc7?&\xefy\xc9\x82\xa1\xea\xbf\xd5\xb7\xea8%3\xf1?v\x1f{\xefY\xe1\xdf?\x0fB\x9ekb\xcb\xed?\'C/\xbd\xba\xf0\xf2?\xb2\x87\xe9\xd0o\xb1\xdb?\x92\n\xce\xd5\xf6m\xf0?\x94\xbe\xa7\xb9YH\xff?Q\x0eb\xddT\x19\xdf\xbf\x0e\x06:\xb3\x10c\x01@\xaf\r\x97\xef\xb5$\xef?\xeb\x83\xfd\x1b\xaeM\x05@^\x16L\x83\xe7\xb0\xcd?\xdf\xe8\xec\xc2\xed\x9e\xc2\xbf\x89\xee\xcd(x\x80\x92?\x9a\xad\xdc3\xef`\xf6\xbf\x1dZj\x96\xbc\xce\xe3\xbf\xe6\xdb\xf6\xebyW\xf4\xbf1_\xfb\x15\xeb\xe5\xf9\xbfv"\x18\xdd\xb3\x81\xec\xbfNs\x18\xc4u\xba\x02\xc0f\xf4\xc6\xfa-\xb7\x02\xc0S\x12\xfc\xe3\xe3\x83\x01\xc00\xbaA\xd8\x13\xad\xc5?\xf6N\x1bE\xaa\xa0\xa3?\xa6%eS\xa7\xb3\xd3\xbfX!A\xa1\xa5\x90\xbf?\xef\xce\x85\x8b\xfa\n\xd1?/\xb0\x0c\xf3\xd5\xb7\xbf\xbf\x8d\xad\x8dY\x9b\xfb\xd2\xbf&>\xed\xa5\x8e\x88\xce\xbf\x95\x11\xc4\x8b\xb5\xa5\xe9\xbf\xed\x88\xeb\xcb\x8cK\xd6?\x82m\x8aT\xbb\xbd\xfa?\xd0\xe4c\x93\x04P\xee?\xd8\x032\xcb\x00\xfa\xb9\xbf|\xc03qw\x18\xe9\xbf{a?\xacd@\xf9?4\xcb"U\xa39\xb7\xbf;\xb5\xa3\x7f\xeb\xc2\xf4?h\x19\x13$7>\xc3?\xef2\xa4\xbd\xca-\xe5?wV\x99+\x1c\xe0\xe1?\xb1\xfco\x0e\xa0\xc4\xf6\xbf\xb1\x1eDX\xa4+\xdf?N\x8e\xd0\xa6\xcd\xdbZ?\x1fZ\xac\x95\xd1\x0c\xf8\xbf\xbc\xce25\xc8\x06\xe5\xbfLrl\xfd\xb2\xd3\xee\xbf\x18BC:\xc6\xfb\xea?w\x18\xeaJw\xf1\xc8?*\xaf\xdd\xd9\xb6}\xb8\xbf\r\r\x8d\x9c\x85H\xe5\xbf-\xfc\x8aSxo\xce?\xd2r\xf0\x8f\x81\xb3\xe9?\xd3BUMj\xe8\xff?`D\xb7\xbb~\x82\xf5\xbf\x96\x08m\xe1\n;\xd8\xbf\xbdb\x82\xce\xeeU\x00\xc0\x9c&\x04\'\xe7H\x00@s\x9c\xd9d\x12"\x01@\xdd\xba\x10^\x94\x06\xfb?o\xc9 \x9c\x8eo\xf3?\x92}n\xa1!\xa3\xe9?9\xdcN\xaf0\x9c\x03@\x13\x10\'P\x1d\xa5\xe7\xbf\x154\xd8\xb4L\x1d\xca?\xb2\xa4\xd36\x9c\xcf\xb2\xbfc\x14]u\xc3\x1c\xf3\xbf\x9e\x86\xfc3\xab\xd7\xf4?!\x89\x18Yo\xcf\xf7\xbf\x14\xc0\x11+}q\xcf? W-\xf4\xae\xa0\xe9?\xb7\x9d\xfeC\x96\xef\xea\xbf\xaf\x9d\x7f&a%\xe6?\xa8(\xbf0E[\xb8?"\x93\x03\\X\xa1\xe8?\x8d,0{\xafo\xee?\x92\xb2+z\x94s\xf5?C\xd1ap\xf9h\xe2\xbf4\xbc\x82\xb2~\xa9\xbe?\xde>\x1fH"\x9d\xd7\xbf\t[\x1a\xe0\xcd\xcf\xf0\xbf\xb6\x9e\x7f\x1d\xc3\xd5\xe8?O!\xbc\xcb\x9b\x86\xd3?\xe4\xb9/\x86\xe1K\xf0?\x9c\x14-\xac\x8aF\xf9?R\x16\x9e\xcd\x18S\xa9?\x0c\r\x1d\xf2\xb1s\n@p!D\xa7)_\xfe?\xe4\t\xf6\xf1\xbd\xec\xfb?s%\xe3\xf1\xc8v\xf8?\xd0\xc2\xee\x17\xad \xc7\xbf\xc4\x15\xd9\xad\xa9g\xd0\xbf\xce{m,nu\xf2?\x19\xeaoia5\xdb?h\xc8\x04L\xc4\xc9\xe2?\x0by\xc9sU,\xc8\xbfc\xa1\x8f\x02JE\xbb?\x1b\x0f\xb4\\\x89\x12\xe9?,\xbax\x91\xd0\x7f\x06@\xf2\xaf$\x0c\xa8\x8e\xee?G+o&\x90\x80\x01@F\xe1h\x8e\x84\xb7\x10@\x8886$\xb4\xa5\x06@\x98;\xc6\xb0\x8b\xe5\xfd?\x00\xff!C\x7f\xe3\xf3?\xfd\xe3\xeb\xfc\xe3N\xf6?YH\xc3N_X\xa6\xbf\x8d0E\x95\x8b\x87\xc9\xbf\xf6s\x08\x10\xe0\x90\xf2?\xfdw6\xea\xb6\x9b\xfe?\x10\xbc\xdf\xa74\xd8\xc5?\x87L\x86\xf9}y\xe6\xbf\xbe\xe9\xbf\xf2\xe0\n\xd1?\xe2\xdaG\xe7Y\x8d\xef\xbf\r=g\xa4\x95\xa0\xf5?\xd2\xfa\xbd \xb6\xe4\xdf?1\tc\xe6lK\xee? bRS\xdex\xb6?/\xf1&R7v\xdc\xbf\xa7bVi\x13u\xe4\xbfp\xbb$\xaey\xa7\xdb?\x8ee\xed\xe7\x08\x97\xb5?\xd4\xfa?\xc0\x10\x8c\xd1\xbf\x8e\x17\xfb\xdb\xb0|\xe6?\xca\x91\xbf4P\xa8\xe1?\x03\xd4b\xf6D\xc7\x02@\x9a\xbb\x7f\xd7_\xa8\xeb?F\x7f\xf2v\xe5\x8f\x0e@z\xf2\xdf\x17\x05\x1f\x12@\x91\xbd\xc7\xc3S\xe2\x13@Yv\x0e\x8aw\xaa\x14@\xef\xbd+H\'\xcf\x0b@\x91\x11cEuo\x12@:\xdd7{\x1b\x17\x07@\xc3!yo\x92-\xf4?5\x15\x91\x9b\xdc\xf3\xf4\xbf\xb6\x003\xd4%1\xe6\xbf\x836\x82a9.\xbb\xbfxQD\x84s\x86\xfb\xbf_\r.\xdd\x8d)\xe1?,\xf7F\x0c&\x89\xe1\xbf\xfc\xfc=\x9c\x89j\xdc?\x04\x10J*\xe7\x1c\xdc\xbf}\xefrp\x11\xe7>?\x98\xa2\xfa\xa7\x0f\x0b\xa2\xbfpxJ\x1ay\xff\xe6?e\xa92\'2\x85\xef\xbfb \x8c\xa3\xc6\xc3\xda?\x90\xbfI\xe5a\x06\xca?\x8dVxu|I\xd1\xbfb\x99\xd9\x1bVZ\xfe?\xdd\x7f\xe3\xe9\xdf{\xf3\xbf*k\xba\x96`\xd9\xe1?\xde\xa7\xa1\xd3\xa6\xe7\xf5\xbf\xf4x\xc0d\xc9\x80\xf5?g\xff\xb4\x1d\x89\x05\x0b@\xcd\x16\x1d\xbe=F\x02@\xa5\xe3\xd0.YQ\xfd?\x08w\xa6\xc7~\x00\xfa?\xda%\xf1\x8dXp\x0f@D\x824h\x12q\xf1?Q.^\x9a\xdaG\xf1?\xfa3\xea\xb0V\xfe\x01@\xd7~w,\x9a\xcb\xc3?\x01\xed<\xbd\xce)\xe3\xbf_\x90\xe5\x00\xe2;\xc4?DLmOd\x90\x02\xc0\xfc\x14\xac\x8c\xeb\xe1\xfb\xbf\x0c\x97\x9d2w\x7f\xf6\xbf\xb3,\x062\xcff\x01\xc0\xb3\x92\xc7\x11`\x0f\x04@\x1d\x1e&\xbd,#\x01@`\xa8\xc6\xdeny\xf6?\x80W\x19S\x9d(\xda?.\xeb$r\xc7\x01\xe3?\xc5\x90"u\x1e\xaf\xdb\xbf\xa3\xe1{\xac\x9a\xef\xdd\xbf\xb6g\xc0z: \xe3?\xf5\xa1\xc8\x88\xc3\xee\xde\xbfT9\xdaW\xac\x91\xb3\xbf6<\x89\xeaNE\xd7\xbfd\x04\x02P\xfd\xca\xe8?7Cg!\xe9\xd0\xd1\xbf\xd9\x17\x17\xe00Z\xd5?z)\xa4n\xcc\xa3\xbb?\x85\x02\xb2d?\xf5\xd4?\xc4\xdcHK\xd1\x91\xd2?\xa4\x12,\t\xe5\x91\xcd?\xa8\'\x7fu{\xc9\x0b@\xff\xe84\xf0\x13\xa8\xfe?(y$\xccd\xc3\n@\r\xae\xb1R7\xb1\xcc\xbf!\xf4u\xaf}}\xee?\t>\xc5`E\xb3\xcb\xbf\xa6\xc8\xe2/\xfe\xe2\xda?\x8f\x91\xdf\xbc%o\xec?\xeb\n\xdc\xfae*\xee?]\'\x97c\xcbq\x01@\x16V\xa7v~\r\xc1?\xc2i(\xfe\xf2\x85\xf1?k\xd1\xa3\x9c\x7f)\xde\xbf\xdb\xba\xe4\xfdgF\xd9?\xf5\xe4\x8d\x8b\x97\xe0\xe1\xbf\xfd\xfa\xad\xba\xa5L\xf3?\xc7\xff\x88\xca*\xe1\xdb\xbfh\x84\xbb\x82\t\x8a\xd3\xbf\xef\x14\x06[{2\xe5?\xb5\xbe\x8e\x8a\xacW\xd1?\x9dB\xecb\xbc&\xf2?\x9b\xd4}k\xe2E\xdd\xbf\xb4\x11\x02\xbf*J\xf8\xbf\xf7\xb6q\xb7\x13f\xd3?\x9dJ\xf1\x1b]\xe0\xeb?vGEY\x8f\xc0\xdc\xbf\x7f\x9e\xac?G\xbc\xe4\xbf6\xd8\x98l\x17"u?\\\x19%\xe0\x94\xf3\xf2?\x90\xa2\xab\xa8d\xd6\xf1\xbf\xe5\x9e\xd3\xce\x1f\xdf\xee?/\xf1\xa0\x13\xe3k\xe5\xbf\x86#\x8b\xcf\x82\xa7\xe2?\x9a\xba\x91\xa8\x02\xd7\xec?9\xad\x06i\xf5\x0b\xf4\xbf\xb1\xb6\xdd\xdb\x0f\x06\xe4\xbf\xb4e\xbf\x08\xb9\x02\xf2?\x17M\xe4\xaaT?\xf2?\xff\x7f\xc1\xb0\xba\xc4\xd1\xbf\xd6\x08R\xebu\x8d\xe6?!\xa5V5L=\xd2?\xfd\x84\x83&\x94S{?\x89\x13\x14r\xd9\xe2\xdc?\xb4\xc8\xa4\x8e\x02B\xdc?\xcd7y\x03\xc4\xd4\xb3?\x0f\x8b+ \x05\x17\xd7\xbf@\xaaV.`J\xde?\x02\xd9\xe0\xe1\x05\xdf\xe1\xbf*\xc7B\x01/\x92\xc7?\xe9\x87h\xf9\x138\xcc?I\xa0\x00\x8a\xefG\xd6?,\x9e\x9aA\\J\xe7\xbf\xb0\x7f.\x03\xa8\xea\xc8\xbf\x15]\xdd\xec\xd4)\x86\xbf\xa8\x85\x19\xc4Pj\xf2?\x1ez\x0bt\x83\xf4\xe7?5\xb8sO\xcd\xf7\xf8\xbf3\xc5\xa99+\x9b\x05@?\xbb\x95\x94qZ\xf1?X\x93\xacDO\xb8\xfa?[\xec\x138\x07.\xfd?J\x08\xd6|z\xd3\xca\xbfx2\x1f4\xe7r\xdf?),\xb8\xcb\x13\x9b\xcb?\x0c\x8d\xc9\xb8\x14\xb5\xfb?\xc6P\x8f,\xe1\xb7\xe1\xbf\x19\xfa\xca2T\xfc\xf3?VJ\xfa\x17\xd3R\xf1?0{R(\x15s\xd8\xbf\xe0\x96\x96\x8f\xc4\n\xe1\xbfV&\xe4q\xb0\xcd\xe6\xbf\xa8\xc3Dj\xa7S\xd9?a-\xdb\x81\xe4\x86\xf1?\xd5\xeb\xd0S\x7fa\xe4\xbfx\xcbp\x10\n\x8a\xf3?n\x15Y\xc9)I\xd4?\xd4\xe1\x81\x18K\xc9\xd4\xbf\xa6?\x1b5\x01N\xe8\xbf\xbc\xd3\x0f\xb6:\x0f\xdb\xbf\x92\xdeF\x88\xf8\xb1\xc4?\xbc\x01\xec\xf8\x8f\xd5\xca\xbf\xca\xd9\xb2*}\xba\xf3?t\x92\xbeRd\xb7\x05\xc0\x15#\x8d\xfc-*\xe0\xbf\x97\xce\xac\xf8\xa6\xdc\xf7\xbf\t\xf5\xd7\x1f\x01\x0c\xc0?\x96\xff\xd8\x9b\x0e*\xeb\xbf\xd2\x87\x0c_\xc4\x8a\xf1\xbf\xeb%\xf9\xd7kZ\xc4?al\xec\x7f6p\xde\xbf\xa2\xa0_}\xf7k\xe5\xbf\x06\r\x91^\xa0\xec\xd7?\xb3\x7f\x1f\xd7E\xfd\xd5?M\xc2\xbeW\x84\x03\xc9\xbfo\x82\x81<\xcf\xb2\xc8?O\xfd>\x03C\x8b\xeb?\xc3<\x95\x07\xc2\x14\xc3\xbf\xf4\xce`\xb4_\x0b\x7f?\xd9\xb5\x9dG\xa4\x92\xf1\xbfQ\xeb\xfa3\x80\x92\xfc\xbf"j\x80\xad\xbdJ\x84?\xc0I3\xbaP\x8f\xf4\xbf\x07I\xb6\xde\xac~\xf0\xbf3d\xcd0\x99\x1e\xdd?\xb2\x95\xa8\xe0\xaa\xa0\xf1\xbf\x17\xbe\xcc\xd3\x15\xb0\xd9\xbf+c2\x8a\xbf\x8d\xe6?\xb7\xff\x99\xcd\x01\xd1\xdf?Oh\xcc\x155\x15\xfa\xbf\xa3\xca\xb6fQ4\xe6\xbf\xdd\x11J\xd5\x08!\xfe\xbf\xf0\xae\x15\x96\x9e\x99\xe5\xbf\xea\x7f\xa2~?|\xf6\xbf\xa7\xfeo\xbd\xb8f\xbb?\x90\xee\xcfy\xbc\xaa\xfa?\x92B\x94\xe9\xfe\x1e\xea\xbf\x93\xb8i\xcd\x17\xff\xef\xbf\xd3X\x04W\x0e>\xb2?\x9fBt\x92\x11\xbe\xf4\xbf\xd8\xca$\xf6b\xcc\xef\xbf\x7f\xd1I\x05\xd6\xfa\x01@$d\xc9j\xb9F\xcd\xbf\xfc\x92I\xbe\xf7\x91\xfa?|\n\xc6S\xcdH\xee?GRL+\x07\t\xe0?\xf1\xaa\x89\\J\x06\xaa\xbf\\{;\xa5\xfa\x88\xf3\xbf|".\x8d\x12v\xd5?]\x9f\xc5^\x83Z\xec\xbf\x0c_1\xb8\x01\x04\xcd\xbfe\xd1,\'\xcc<\xd2?\xdf\xc5\xb6\xb8\xe04\xea\xbf\xda\x0f\xaf(\xc5l\xd1\xbf\xfb\xde\xdb\x0f\xa1\x90\xa1?1F\x9d`^\xe8\xd4\xbf\xf3\xfe\xed\xb9B6\x82?t\xc4\x89a\x1c\x10\xdb\xbf\xc5\xcc\x91}\x7fq\xd6?h|\xd4\xd9Bq\xdd\xbf\xadLy\\.\xa6\xec?\xb6\xf6%|\x07(\xf1?\x0f\xb3U\x895\xe6\xd8?\x9a)J"\xa0S\xe7\xbf;\xe6\xcc\xadI4\xe1\xbf\x82_N\xf0j\xd6\xe7?\xd9\x95=_\xe1\x8e\xa3?S}2\xb5\xd4\x19\xf0?\xcf\x13\x9e\xbcu\xbd\xac?*\xb5\xd7M\x13\xe7\xb3?\xc0\x8c2\tR\x8d\xeb?*\xeb\xacHg@\xec?R\xb4\xd9\xa4\xe0\xdd\xac\xbfe\xbe\xea\xee\xc0\xea\xe3?\xe6\xe7\xee>\x04k\x04@\x0bm\xfd\xdd\x16\x11\xa4\xbft\xa4\xce{\xb2\xbf\xcc?\xac%\xbeM\xa5\xa8\xaa?i\x14\xcc=\x0fU\xe7\xbfE\x8bm\x13\x0c\\\xe0?\x00\xb0\r\x91\xc5\xff\xd8\xbf|\xd8\xe7\xbc\xf9)\xf2?\ng\xd4x\xc4(\xc6\xbf\x19\xa9b(\xd3k\xe5?L|\x97\x11\xdeb\xa7?\x9bF0\x9d;\x87\xab?\x19\xae1[\xdd2\xf8\xbf\x15*I\xa67;\xf3?\x89\xd6\x1aY\xc2R\x8cu\xc9\xbf\x16l\xe5\xeb\xe1\xdf\xef?\xb1\xda\xc8W\x13\xb8\xda\xbf\xa4\x88\x1fTH\xb3\x84?\x1fym\xc6C\xea\xe9\xbf\xb7\xd3\xa0\xf9\xa3\xd0\xf2?\xa0b$\xe7\x18\x8e\xe0\xbf\xf2\x80\xa6\xedMM\xc8\xbf#\xa4\x1d(\xef\xeb\xe6\xbfBS\xba\x8eDN\xf2\xbfR\x89\x90\x85k\xd6\xc7?S\xa0\x8ch\xbc\xd6\x03\xc0\xb6\x97>\x80\xea\xea\xfa\xbf\xf8\n\x9d\x99(\x0c\xfd\xbf\x87\xc9\x89\xe9]b\xf9?\x18\xa6\x14\xb2\xe9\x14\xee?\x8e\x1f\xfa/\xfaI\xe6\xbf\x06s^l\x1a\xac\xe4?\xfe\x9aH\x07?\xf6\xf1\xbf+\xb1\xdd\xe5*\xc1\xee\xbf\xe1\xff\xe2\x88\xfa\xbe\xe3?\x05\xb7\x82\x96\xdd\x12\xcf?\x8f\x1fYT\xca\xcc\xd4\xbf&1\xf3)U\xab\xd7?\xf1\x0bC\x9dV\\\xfa? nMk.\xf9\xe8\xbf.\xbaKM\x00\xdb\xe9\xbf&\xe3?\xc7\x8e8\xdd\xbf@\xce^/P\xb6\xde?\xfd;\x0c\xa1\x8eN\xd1?\xcd0\x8a,\x82\xf5\xbe\xbf\xd7;\x8b+\x0ep\xf2?"\x1b\xc4n:R\xd2?|o9\x9e\xd6C\xf9?\x0e\x11\x01I\x1aj\xc0\xbf\x9d_zB\xfe\xa0\xe8?\xc4*x\xef\xac!\xb7\xbf^\x05\xbf\xc5\x19X\xf9\xbf\xbf2\xbb*Z0\xc2?X\xf5\x18\x14Q\xca\xf3\xbf\x8bp\x96A\x8bu\xf8\xbf\x82\x7f\x1f39a\xc8?\x08qe\r\x14\'\xe7\xbfZ4\xba\xbb\xdeg\xeb\xbf|\xc9\xec\xcdC-\xf1\xbf\x95=+\xdb\x03\xb0\xd3\xbf\xf7\x0e\xd3\xb6\x93B\xee?X_\xb8\x18\xb2\xe0\xe1\xbf\x91\x05\x10\xfbM+\xd8?\x13\x0b$\xa9\x1b\xaf\xfe\xbfQ\xe9+=\x84U\xd3\xbf~Ii\xb05\xfe\xb5\xbfX\xbd\xed\x8d\xc5\xcc\xdf?\xd4\x84\xd0\x80\x8cw\xe1\xbf$\xd6\x9c|\x02\x08\xfc?\x97\xb5i\x87\x9b\xa0\xfb?9\x0e\xba:$\xe8\x8b\xbf0\xf90\x03\x8c.\xcb?\x9e^\xa1\x16\x96h\xd6?R\x14\xa3\xec.\x89\xdc\xbf#H\xb4\xac,D\xfe\xbfj\x87\xf7y\ng\xe9\xbf\x03S\xf6\'k\x13\xf1\xbf\xcc\xc6\xf3\xcb\x03s\xde\xbf\xdd\xfa\xf001\xdc\xe8?\xa0\xcb\xa262\xc2\xc2\xbfL\t\xaaW%\x8e\xd6\xbfA\x85;\xfa\xfdq\xd1\xbf\x84}\x1f\xce\x7fD\xf8\xbf\xbd\xe0\xddR\x95\x0c\xe0?\xb4KS,!$\xf3\xbfg\x9ep\xe7\xaa\x99\xde?wT\x94\xfc#\xae\xeb\xbf\xf8\x0c\x80\xf7z\xab\xe7\xbfe\x84~\xe3\xcd\x9c\xe6\xbf\x81\xe0 \t\xf7\xc5\xf5?E\xff\xf1\x17y\xfa\xe4\xbfp\xc9\xcd{\x87\x81\xdf\xbfW\x85\x1d\x97\x86\xec\xa2? (5\xe7]\x80\xd4\xbf\xe6J\x1f\x14\xfdK\xe0\xbf5h\xd9\x9c\x89\xd1\xda\xbfT:m\xae}\xe9\xdb?\\G\'\xa5\x0bd\xc3\xbf\x01\xb5}\x80\x87^\xd0?a\xd8\xbcK\xf6\x89\x01@\x8a\xc0\r\xba\xf3\xcf\xe3?\xc5\xca|?\xb4j\xf5\xbf\xfc*6\xe2)\xa8\x01@\x15\x86\x06\x80om\xd5\xbf\t\xa7\xc1\xd6\x88\xec\xea?l\xc7\xf4\x88\x9e\x95\xe3?p\xd4\xe3\xe5yY\xf6\xbf& \xf9\xc6\xa8\x06\xdf\xbf\xa8>-\x95\xa7[\xb4\xbfp\x03\x1c\xd5\xcf.\xe8\xbf\xad\xe6\xa4\x882\xc1\xf0\xbf\xf3&\xcaq|S\xf6\xbf\xc6B\x7f\x90{J\xf3\xbf\xa5\xed\xd4\xbdkE\xa1\xbf\xe0\x0f\xa8zy\xf3\xe3?\x92\xce\xf5c\xd6\x99\xf7\xbfF2G\xf9rl\xe2\xbft\x8as\x12K\xc0\xee?$\x0e\xc2\xe2\xbe)\xf9\xbf\x8a\xb2"\x97\x99R\xc9\xbf~\xd0vK\n\xf3\x02\xc0\r*a\xb4\x869\xd3\xbf\xab\x8b\xe6\x84\x06\xa3\xd3?C{\x05\xbb\xa6\x95\xe6?\x84Ai%{}\xf9\xbf^m\x15\x7fku\xd4\xbf\xd4\xd2\xf2\xfe\x14V\xf8?\x1bA\xc5\x1d\xed\xaa\xe1\xbfL\xb4p\xdbv\x81\xc8\xbf\x1do\t\x82\xb1\x9a\xe6\xbf\xd5\xdcF\x1b\x84\x8c\xe6?UM\xdd1\xfd-\xe5?h\xea\xc5\xd7\xd4\x92\xfd?\xf0bZ\x94,s\xd5?\xb0#\x16\x82\xb1y\xf6?\x88`\xe5\xc4<\x97\xdd?\xe9$\x17\x12\x8e\x1a\xdd\xbf\xf0\xa3\xe5\x13vF\xc5\xbf\xf8\xe2\x87x(\x8c\xdb\xbf\x8e\xc79\xb9d\xc5\xe0\xbf\xea\xcf\x9f\xa3\x8b\xb1\xc6?\xe5\xe9X\x1c?/\xd5\xbfu\x97((D\xa3\xed?\n\xc1\xd6z\xbd\x05\x06@\xa3\x11\x98I\x99\xfc\xc0\xbf\xcdt\xa8o\xcdp\xb1\xbfo\xf9\xfeL\x82\x0c\xe9?\xf8\xf1\xdf\xdd\x00\x04\xe4?\xe0)\xc4@\xc7j\xfc?\xc6\xea\xbfl8\x1a\xe2?\xa0U=J\xd3m\xd2\xbf48\x7f(*\xe7\xe5?\x9d\xb8\xe6\xaco"\xd5\xbf\xea\x1f\xc1\x13\xc0\x98\xe5?\xedJ\x14\xd7D\x89\xd6\xbf\x16\x15\xb2y\xdf\x02\x96\xbf6\xb8\xdc\xc4\x14\xdc\xe4\xbf\x892\xad\x93\xee-\xf1\xbfy\xff]T\xa4\xfd\xc4\xbfWTd\x1b\xa0\xaa\xc2?\x89\t\x1f.MP\xb9\xbf\xd1\x8d\x9e\xec\x99\xc5\xeb?}\xf7U\'\xe2%\xff?\xe6JA\xcc\xdc0\xea?8~|\xd1\xb7\'\xd3?\xf5\xf7V\xe1\xeb!\xf7?\x03\xb8\xe7=\x9bt\xf1\xbf\x8a\x1bR-\xd9\xeb\xd7?\xb1C7Pk\x98\xeb\xbf\xd5{\xdc\x00Tm\xe8?@B\x08\xd0z\xc8\xee?\xca\x01\xa5\xbemK\xac?\x8eD({i\x01\xd2\xbf\x01T\x9d\xe2\xd7u\xe2\xbfq\x8e\x8244\x8d\xd7?|\'C\x9e\nc\xd6?\xd5\x85\xc2\xe12\x0ct\xbf\x00\xd7d\xe2\x17J\xf2\xbf\x00\xbfH\xda\xb4G\xed\xbfn\xcf\nd\x00 \x02@)\xa5w>\xc0%\xc7?\xf0I\xce\xf1\xae\x16\x00@}\xfe\xb20\xf9\xaf\xf7?#&\x9eCl\xf1\x04@c\xd8@\x81\x9ao\xc7\xbf+\xd14\x9d\xcd\x92\xe8?\x06\x126\xf7\x03\xd0\xee\xbf\x057M\t\xdcM\xc7?\xca\xfb!\x14\x94\xc9\xf4?q\xa7:\x1ez\xca\xe3\xbf\xbf\xd6\xa2\xa4*\xd2\xb6\xbf\x82*t.\xf5\xb5\xb6?v/\x0c\xc4\xc1\xef\x06@\xe1\xf7\xdc\x8f\x9c\xca\xdc\xbfs\t\xc2\xc8\xa3\x99\xc0?s\x1ae\x1f]\xdb\x00@\xb9TF\n\x15\xa4\xd1\xbf\xe9\xb6\x1b$\xba\xc9\xd2\xbf\xfa\x96\x1eh\xac\xdf\xd2?\x83\x02\xd1\xe1u\xd8\xe1?\x98+\x9a0=\xdd\xed?\xf8\x95Yb\xd1\xd1\xff?\x98Z#\x80\x1a\x1d\xf2\xbf2DrNk\x9f\xdc?\x82\xbe+k\xf5G\xec\xbf\xba\xe7U?Fo\xdf?\x17\x80Jk\xbc\x06\xfe\xbf\x94j\xb7\xdc\x92\xa8\xf7?\xb2H\x91\r\xdf\x0f\xd6\xbf\xf0$\x97qWW\xe3\xbf\x03@\xb9`\xddR\xd7\xbf\xc6\x04\xaf\xa6q\xfb\xe4?\x92*[nt7\xe1\xbf\x83\x9b\xbc;\xc4\x18\xf0?q`\x1b\x14N"\xd2?4\xfd<\x9f0"\xe9\xbf\xbb\xa0\xa9\x82\xc2\x17\xf2?\xedK){p\x8c\xfa\xbf%_\n\xfd\x93H\xec\xbf\n\xfa\xa6\xc2\xc8a\xd0?j\xa6\x85\xc1\xba.\xf2?\xe8k\xb8"\xe7F\xf1?\x96\xf2U*\x92\x0f\xea?s\x80\xd5~H\t\xe1?\xeb\x97\x8cb\xf6\xda\xf6?\xc1c\xba\x89\x0f\xac\xe5?\xaf6\xfe\xd7z\xe8\xe3?\xd1\x8e\xe4 0x\xe0?$\xeaY\xb0\x9fW\xcb?\x90\xb8 h\x16\x96\xf1?\xb4\xb1q7\xe9X\xed\xbf\x98Gm\xea\xee\xb4\xf4\xbf~\xf7N\xb3\x1a\x91\xf9\xbf\xfb-\x0b(\x858\xe4?\xeaCc[\x8cy\xdf\xbf\x19\x9ek\xc96@\xd8\xbfr]\x91\xa0\x15\xe1\xeb?A]\x16Vi+\xcb\xbfG\xe6\xfcyT\x01\xe5\xbf\x90\xd3e\xd4u\x80\xd0\xbf\xc3a-H\xe4\x83\xef?\x12K\xb5^\x8fV\xf2\xbfy\xc1p\x13\xd6\xc2\xf7?i\xf0\xe1;\xa98\xed?)\xa3\x8d\x0bw\x88\xdd?\xb4\t\x1fj \x9d\xc6?m\xf9\xfb\x07\xf6B\xe5\xbfH\xaf\xb7.\x9f\x01\xf2\xbf\xad~$\x95\xbc}\xb5\xbf\x8dF\xd8@99\xe9?\xa2lD\xe7\xb3\x80\xe3\xbf\xd2o\xe0*\t\xfc\xd4?\xca\xed\xb7\n\x05\xd2\xbb\xbf\xbc\xac\xe3\x05\xe5\xe2\xb2?7\x98\x16\xeeK\x8d\xe0\xbfD\xab\xcb\xc33\xd8\xe0?\x93d\x0e\xd9\x9c\x06\xf0?\x9ce?f\x88A\xb6\xbf\xa7\t\xffOS\xc0\xdc\xbf\xd8\xf9\xb2\xe8G\x7f\xeb\xbf[%\x99Y\'\xc6\xf2\xbf\xf5\x0c\x92\x97\x81!\xe1\xbf\xddY\xa6\x91\xa7\x95\xf7\xbfk\x13:5R\xe9\xe0\xbf\x98.5?\x9be\xe1\xbf\x808<\x1bW\xb1\xdb?]\xf2\x17\x89j\x1f\xa1?\'\xbf\x98\xf8}.\x00\xc0\xa1"7\xcf\x98K\xe3?\x87\x9f%\x98\xef\x92\xd6\xbf\x81Ra\x9d}0\xf8\xbf`\xebXK\x12\xa5\xe0?\x87V\xc6\xbf\x81\xfb\x19Qw\x98\xf6?\xf6\x95\x8ef\xf7\x9f\xde\xbf\xb4S\n\x08d\x82\xf9\xbfs6\x0c\xd8\x06\xbf\xe3\xbf\xbdi\xef\xab\xaf\xb2\xdc?b@\x01\x04\x8a\xd5\xe1\xbf\xc0\xe8\r4\x92\xcc\xf1?\x910[\x8e_\xa6\xed\xbf\x1c^\xff\xa4~\xf7\xe8\xbf$\xef]\xa9+\xb7\xe7\xbf\xc6\x1f`7=V\x05\xc0,\xb8\x16-\xa5b\xd5\xbf\x17zY/A\x84\xcb\xbf\xfb\x05\x18\xe1\xac.\xc5\xbf\xc2\xd9z\x05DR\xd7?\xb9-\x93\x86C\x1f\xea\xbf\xaa\xff\x1b\x99Jt\xe3?;\xd4\xad\xf7R/\xd8?\x1ca\x1b\xb5X\xab\xe3?\xaft\xc2_a\xaa\xf7?\x0e\xd5\x0b\xbcT\x1f\xea?8I0.<\xa3\xf9\xbf\x04\xc7\x08\xf9\xac\\\xfe\xbfT\xbb\xc4\xec\xb9"\xf4?\xfdQ\x01\x88\xc0\x98\xfb\xbf\xde\xf7\x17\x9a$O\xf8\xbfb\xb2\xbd\x8f=o\xf0\xbf\xc7\x85\xe6A\xfe3\xad?P!\xb3F\xea\xcd\xdb\xbf\xe4\xe0\x1e\xc7\x19/\xd7\xbf\xfb\x83\x86)\xa4\xdc\xe8?\x11\xb28\xe9\xdd(\x00@\xa6\x13\xf3(\xe1\x13\x02\xc0;)\xb9v\x02\xda\xf7?\xf1\xed\xe1moq\xef?\xeb\xd9\xdf1+\xa4\xe3?\xf3\xdd\x14=\x99\xeb\x9d?\x08]\xbe\xa0\xd4\xb1\xa1\xbf\x96\xab\xf0\xbfpO\xb6\xbf\x03B\xc0\x94\xd7R\xd9?>\x07\x8e\x9d#\x9c\x01@\x84\x97\xbaX\xf5*\xd7?Q\xcc(\x00)h\xe2?\xc5Pw\x15\xd9\xe6\xe9?\x05\x0f\xa8dh(\xec\xbfE\x89\xf0\x9e\x9f\x10\xe2\xbfa\x0b\x11\xca\xb5d\xbd\xbfL\xc5Z\xd3\\\x89\x82\xbf\xd5\xe0\x94\x80\xc9\xc8\xf6?\x87C\x82C\xf3\xab\xef?L8\xe9_9m\xf0\xbfx(\xc5\xf8\xd0\xab\xd0\xbfH\xb8gc\x93\x04\xd3?\x81\xb9\xc4\xb2\xcd\xa8\xcd\xbf\xf5\x8aGNF\xd2\xed\xbf\x03\x18;\xec\xd3\x88\xf9?j\x1a\xcc\x04\xc3\xb6\xb4\xbfd\xb6\xd1\x06\xf5\x1f\xf1\xbf\xabD\x83\xae\xab>\x9d\xbfW\xa9\xd6\xd4\x7f\xe7\xf7?\xcd\xb4\x8c\xd4g\x12\xe7\xbf\xf2\x07\xd5\xd6\x7f\xc7\x99?\xea\xb73\xceH\x96\xf8?\x81M4\xc8\x91g\xf3?rxcR\xdcb\xfc?\xb3\x11D\xbc\x99\x07\x02@"\x90\xee[t;\xf2?\xc8Y\xf7\\\xef\r\xfc?m\xa3X\x85\x16\x80\xee?\xf7\xe8\x03\xd5\x84\xa2\xe0?Nv:\x13\xb5\xaf\xee?\x81\x06\xe3{\x88\x82\xf4?\x1d\xba\xd8s\xc6\x1d\xbd?\x85j\xbb(u9\xd9?e\xd7\x87\x9b\x82\xa9\xf4?\x1f+\xae\xe5\xb0\xaf\xdb\xbf\xf6\xf0\x9f\r\xd0\x90\xea\xbf\xc9\x1d\x05k~Y\xef?i|k\xe6y\x0f\xfc\xbf{\x83\x10\xe5Q\x80\xe5\xbf\xc2\x1c\xd0\x15D\t\xbd?\x86,C\x811\xff\x9c?]\x0cH\'\xad\xd0\xff\xbf\xda\x9f(\xa9\xc2\x13\xf9\xbfI\x95p\x02\xbd2\xb7?\x95\xe5L\x92&U\xe3?\xb14\x0f\xe25*\xf1\xbf.>\x85\xbd6\x89\xec?\xa7)\x9ep\xb3\xa2\xd6\xbf\xff#\x90\xe6a!\xf8?\xba\x98P^\x16\xc5\xe0\xbf\x16\x8a\x1ak\x82\x15\xf2?3\x93O\x94c|\xf5?\x13\xa5\x8b?7Q\x01@\xb0\xbcJ\t\x97i\x00@\x1f\tD\x81#\xf0\xe9?\xc0\xe6\xe7l\x03M\xf2?\x1fr\xcad\xd8\xdc\xe4?>\xb7\xcf\xc0\x947\xe6?\x0e\xae\x0eBC\x06\x06@\xb0\xbe\xddPj\x83\xe5\xbfe\xb6\xae\x1bk\xdf\xbd\xbf5\x8e\xde&c]\xfb?AS\x9a]\xf5\xaf\xe1\xbfV\xe8\xcf\x03\x07\xbf\xf9?3IE[\x16\xa4\x03\xc0\x9c\xcd:&\x8e\x10\xea?\x91G\x03\xf2\xc98\xf1?\x9cw6\xc4\xc2\x1b\xe6\xbfri4\x94m\xc0\xe4\xbf\x14W\xf2I \r\xe5\xbf\xac\n\xb8\xba\xa7N\xe9?!\x93\xea\x98\xe4\xe3\xe4\xbf\xd7\x9b\xa6N\xf1\x1c\xf4\xbf\x18.B{\xf3;\xde\xbf^D\xefA\xb2\xb4\xc1\xbfm2N>bK\xff?U\xf4 \xe3\xf9e\xa0?\xce\xeb\xe7!\xdc\xe9\xca?Dv\x0f\xc5W\x15\xc8?c|\xf5\x11D4\xe6?O37j\xd9\x9c\xf0\xbf\xa1\x12\x95UmH\xe3?\xe7\xaf<\xedu\xac\x01@\x0f\x02\xe2\xcd^\xa3\xc0\xbfI=Qh\xbf\xaf\xfa?\xd8E\x17\t\xeb[\xf0?\x87r[\x9e\xf4\t\xf4?nY.8B\xc8\xdd?Me\x91\xeb\x9f\x84\xcb\xbf,z\xaf\x19\x84\x14\xa0?\xe3\xc8\x7f.\x9d\x97\xf8\xbf\x1fM\x08\x81\xa3\x0e\x90?/\xa3\x15\xd5\x00h\xe3\xbf\x98\x04V,~`\xee?\x8e\xf3]\xb9\x1bA\x05\xc0"\xe2\xd9\x9d\xacW\xf0?6\xb3l_\x86\xb6\xef?\x81\x92\x9b\x95#\xbcw?\x14{\xa1\xc4`\x19\xea?\x0c\xc5\x05\xc7\xf0\xc3\xfa\xbf\xce$\xd9\x83#\x14\xef\xbf\xb9`\xb2bS\\\xa8?\xc1S\x0bt5\xb8\xd1\xbf$\xce\xe8uq;\xdf?I\x9c\xe9\x90\x0e\x8a\xfa?\xe9p`\xda\xbf\xd8\xf3?\x0f\xeb\xee]y \xf0?i!\x04f,\x18\xd1\xbfYo-\x1d"\xea\xb0?\xbeh!\xe4p9\xf1?E\xb3\x196\xf6\xf3\xf6\xbf\xd2\x9bp\xf3Y\xc9\xf1?3\xce}\xb75f\xee\xbf;/\x8d\x15! \xeb?B:\xc0\xda\xbc\xb0\x92\xbfZ\xedSw1\xfa\xf7?c\x02\x8e\x8a\x17\xde\xe7\xbf\x06\xea\xb1\xf0\xbf\xce\xb4\x80\xbc\'z\xd7?\xea\xe0\xbd\xc5MP\xd7?q\x02`\x0b{\xa5\xf1?\x84"\xff-\x97\x02\xf5\xbf(XC\xa1\xc2\xaa\xe9\xbf^\x05\xc2\xee\xd3\x81\xe4?\xfemO\xdb\xb9\x81\xf9?\xa9~\x02\xa6\xe5\xdb}?#\x0e#)\x85\xe3\xe6?i\xa9\x9b\xe0\xcc\xf2\xf7\xbfX\xe9\xe7U\x93]\xea\xbfg!\xdf\xd8Lj\xfa\xbf\xf0\xa6\x83\xb6C]\xd9\xbf\xbc\x9f\xb76\xddi\xf5\xbf\x95\xe0\x99\xa1\xe1~\xe6?\xd0j \xec\xd5\x00\xf6\xbf\xfe\x7f"S\x0cT\xe8?G&w1\x0f\x91\xf2\xbf\xea\x10t\xda2\xf3\xcf\xbf\x9c\x1cR/\t\xe8\xfa\xbf\x1e\xcc\x1b\x9d\xfeg\xf3?J\xd1\x0e\x93\x06\xf4\xe9\xbf\x08-\x88\x07\xe4/\xbe?\x03\x8b?\xdb\xf1?\xe2\xc3J\xbc\xa1\xf5\xfa\xbfQO\xb7\xd19\xf6\xe3?V\x8cM\xbc\t\xb3\xdd?E\x06n0\xec\x18\x03\xc0\xee-Z\xee\xf85\xed?n\xa5\x95\xc2\xa2\x8f\xfb\xbf\x19\x1b\xbe\xce>~\xe0\xbf\x81\xcaV\xa5\xbd\x00\xe3\xbf\xd1XF\xd5\xbc1\xdd\xbf\xbc\t\xda\xda\xed\xd7\xeb\xbf\x10>\xb5\xc0\xc7\xa7\xff\xbf\xd3\xd4\x0fAS1\xf3\xbfQ\xfa\xe5\xfd)\xf3\xc4?-1\xeep\xefQ\xd5?\xd2\xa4\x8d\x9c\xec\x9c\xfb?\xf20htC.\xc3\xbfp\x8b\x04\xd3\xf7\xe3\xef\xbf\x9e\x9a\xf4S^,\x04\xc0F\xccs\xe3\xea\x14\xf1\xbf\x81\x91\x02\xdbAU\xe9?\x94;\xdf\xf7\xb0\xc4\xf8?\xd3\xbb\xa7\x18\x1d\xf0\xda?\xa7.\xb7\x83\xdc\x80\xe1?\x13R\xcb\x0cL\x1e\xd0?9K\x98\x06"z\x00@X\xc24A\xcdK\xfb?\xc5\xebU\x9dWR\xf5\xbf\x81\xb2R\xe3\x841\xe8?\xf7:\xc8\x84\xd2\x01\xe7?]\x1aw\x84\xad\x03\xfe?\xa4f\x0bO\xc7}\xe3\xbf\x85\x96\xb2\x8fK\r\xf3\xbfV\xcc\x07\xec\xc6\xf6\xd1?\x91\xaa+\xf6k\xc2\xad?E9\xb1\x02\xdcr\xe1?W\x1fHd\xfc\x7f\xb2?\xc7\xa3\xce\x9coj\xf6\xbf\x82Y\x04\x08\xfdI\xec\xbf\xfb"\xd6Xo\xd5\xe6?\xde\xfa\xf2|l\xb6\xc0?\'v@\xc89\x8f\xe8\xbf9Z\xea\xeb\xea#\xf2?\x818\x12r*f\xa1?\xcb\xad=G\x04v\xda?l\xca\x90\x93/\xf9\xa2?\xf7\x1e\xf8\xaeDz\xec\xbf-\xe8\x104\xd4~\xf4\xbf\x05\xa9kX\x01D\xec\xbfG1\x88\xa9\xc8&\xd7?\xda!qk>\xa7\xfe?\xed\xbaB\x82A[\xe8?\x8d\x0f9\xc9\xde\xf9\xf1?_(\x9c\xc4\xaaw\xfc?+?j\xe0\x00\xe6\xeb\xbf\xe5P"9\x9d\x1b\xe2?B\xc2\xa9\xd8\xa5\x1b\xff?\xaao\x9c/\xeaw\xfa?\x9b]E\x0c\xe30\xca\xbfi\x9c*\xaa,\xf0\xd8?Z\xd1V`\xd4\xbd\xd0?\xf4e\xf9C\xc4\x12\xf0\xbf\r\xb5\xdf3/2\xf6?\x1f\xd7\xfa\xacA1\xf1\xbf\x8a\x9a-`\xb0\xde\xe2\xbfG\xbd\x98\x04\xd66\xf1\xbf\x9c\xf0h\xa2\xfe\x98\xe5\xbf\x95S\x85gQ\x89\x01@s\x12\xc4\x91\xd7r\xe2\xbf\x8b\x83\xb0\xa5Ob\xef?\x04\x81i\xb3W\xf5\xe8\xbff\xde\xe8\x81Q\xc4\xd1?[E\xaf\xe0)\xbc\xe1\xbf\r\x88\xb57\xce\x0c\xfb?\x1c \x12\xd8d\xe9\xd9\xbf\x98"\x95\xa1]\xec\xb5?\x0e\x94E\x02\x83\xa4\xfa?\x81>\xff\xb5\x06d\xe5\xbf\xddb\x14\xed\xca\x83\xe3\xbf\xa4s\x02\xf2\x03\xd7\xf1?\x85\xa2T\x14\xd3\xe8\x01@>0\xd0\x9b\xeb\x0e\x00@<\x0c\xdc\xb9q\xdd\xbe\xbf\xf8e\xe7B\x03\x9b\xeb\xbfkq\xbaEa\x9b\xf8\xbf\xcf]\x94\x9dz*\xe4\xbf\xdd\x0b\xdaI\x83\xf0\xd3?\xd90\x1a \x00}\xeb?B}\xfatM6\xf1\xbf\x9e\xa1<\x96\xb0\x82\x01\xc0a\x85\xb6\x17<\xcf\xf6\xbfKEhew\xc4\xf0\xbf\x0f(\xaf\xa1\x10w\xfa\xbf^\x90\x13\xe0\x12\x1f\xf3\xbf)/\xcd\rI\xad\xdb\xbf\xf1\xd0f\x8fzl\xe2\xbf\x08\xa2\x06\xa3\xaa\x80\x84?\x04\xdb\xce\n\xa5\x94\xea\xbfir4\x1b\xc6\x1b\xa0?\xdd\x15\x03 Ta\xfd?\x10\x8bT\x04\x94\xbe\xdb\xbf\x0b\x08\xff\xae\xd0\xa1\xfa\xbf\xd1\r\xe3\xbf\x88\xd6\xf6\xbfVVl\xcc6/\xe6\xbf\xafV\xd4\x9aR=\xd1\xbf\xb6`\x0b\xe3\x8e\xcc\xed\xbf\x92`\xe5\xba,\xaf\xa8\xbf\xb7\xd2\xed\x80b\xab\xed?\xd6\x8f\xdf\xa6b\x0f\x05@\xde\x8c\x94$"G\xf1?\xf0T\xabi\x15u\xf8?\xeb!\x817J\xf9\xe6\xbf\xa4\xce\xe9\xb7b\\\xe5?\xfax\x82b\x92\xe3\xed\xbf\x82\x1c5m\xeaB\xed?ZA\xa7\x1d/\x95\xe9?\x7f\xa1\xb8q\xce\xc9\xc1\xbfi\x92C\t\x02\x94\xdf\xbfy9w2\x89\x80\xe4\xbf-\xf3h\xae[D\xf3\xbf_\x1d9s\xea\xe9\xe6\xbf\x92\xb3V\xc9A\x99\xf0\xbf\xce\xbb\x92\xe9\xaf\xc2\xe8\xbf\x04\x07\x88F\x87\x9e\xeb\xbf\xb5L\x83\x9b\xb5\x17\x9a\xbfX\xde\x99\x1cS\xe1\xe9\xbf/\xf9\x94\xfb\x04\xf4\xd9?\xd4u\x94o\xce\xe7\xfb?\x1b\x87?\xe4xa\xe5?\xa7z\xdb/\x94\xef\xe3\xbff\xd0\xe3?^\xc1\xdc\xbfJm\xcf\xda\xb03\xd8\xbf\x11L\x92\xe6\xea:\xdd\xbf=\xe2B\xf2\x9fv\xfa?\xfc\x9fs\x85\x91\xf7\xc3\xbf\xbd\xcb\xaf\xb7Y\x0c\xc2?\xcd\xc7@O\x8dI\xf7?hj\x18\x15j\xe9\xd0?%\xb14\xbf\xd6\xbe\xed?\x94p\x1dg\xda\xd7\xe8\xbfH\xa7b\xc8WE\x82?Ri\xbc\xed\x94\xa1\xea\xbf\xae\x1dM\x0e\x1f\xaa\xf8?%[b\xb0\xeb\x12\xd8\xbf\x9b\x1f\xd4\xc6\xe9\x1f\xf9?C\xcc/-\x99N\xe4?\xc2\x1c\xebb2\x8c\xc6?\xcd\xe3\xdd#>\xe1\xd6\xbf\xf4 \xa6\xa6\xb7\x99\xb6\xbfE\xed4\xaa\x87\xf8\xfc\xbf\xbfTB\xb9\xd2E\xe3?\xb8;\xa3>N\x99\xda?Gf\xbb\xd7\x0e\x13\xf0\xbf\x1b\x7f\xd0\\\x1d\x90\xf3\xbf\xa0\xd4c\xc8\xcao\xbd?\xd9\xf7\xad\xc9\x1cI\xec?6\xc8\x1d\xd0\xbe\xe0\xec\xbf(\xce\\z\x9f\xa6\xe2\xbfe7\xef\xba\x9c\x95\x00\xc0\xf9Y\xa7\x1bd^\xd4\xbfzIhC\xe3\xed\xef?\x02D\x95AA\xc6\xdc?g,\xa5LN\xbb\xa5\xbf\xe3S\xeb\x8a\xa96\xf7?X\xd4\x9a<:!\xf0?\x8f.+D\xc6*\xd2?\x82o0\x8fW\xe9\xf3?\xf5\xf1\x12bi\xdc\xe5\xbf\xea\xda\xce\x128^\xe4?b\'\x8e\xaf\xea\x9d\xf3\xbf\x1f\xf7\xd2\xbf\xe1|\xb7\xbf\xd6!\t\xad\xc0\x90\xdd?\xf2h:)\x03V\xf3\xbf\x8bE\xa4\xd61:\xfd?\x04\x02\xfdh\xfc\xbf\xf9\xbfL%\x8b_\x04\xb2\xf9\xbf\xe1\xa1\xc3\xda\xc1\x02\xc1?K\x98)\xf5\x0b\xc2\xee\xbf\xcf&\xba\n/\x91\xff\xbf\xff\xdd\xbb\xc6\x0cp\xca?\x9b&\xbc\x0f\x90|\xf1?\xe7\x05\xc1o\xaf|\xe2?\xff\xe2N\xf2\xea\x1a\x06\xc0\x90iL\xed\x12"~\xbf\xfe?\xd1\x1c\xee@\xfc\xbf\x0c\xeb\xec\xdc}\xdc\xbf\nh\xba_\x9e\x98\xec?\xfc\x94\xf2\x1b\x7fu\xe5?\x1az?\xc4!\x1c\xcb?\xbc\xc3\xdd\xcc\rs\xe8?\x13\x1bB\x03j\x12\x8e\xbf\x14\xf4\xf51\x91\x16\xef?\xcf\xc2\x04\xf3lp\xd1?\xc6\xb1\x001\xf2\x02\xff?e\xca\x8d\x1f[V\xd0?@\xdc\x89\xe4\x04\xbe\xf3\xbf\xd82V\xd0\x15C\xda\xbf\xcay\x9cu\t\x81\xe7?\xf3\x03\x8a\x17\xb3\x1e\xf8?\xd3\x9f?\xd2s\xfc\xb4\xbf\x8c\x82\xb5m(_\xf3?\n\x1b\x94]4\x17\xe6\xbf\xeb\xda\xfa\xdd\xc36\xaf\xbf\xdeXU\xc6|\xee\xf4?\x00\xaeH\xafh\t\x00\xc0~\xdf3\xdc\xecD\xf0\xbf\x1c\xe4\xf1\xd0\xaf\x86\x01@0+\xcaD}\xe0\xfe?is\xc7\x1c!\x98\xec?\x1d\xa5+\x1e\xef\x88\xf6?\xf9\x05\x03\x95\x02!\n@\xee7\xca\xc1`\xc5\x05@reH\xae+\x9a\x04@Q,\x94l*i\x03@\xdas\xf91\tm\xff?\x97Kwc\xf2$\xfa?\x8aT\xbcE\x9b\xdf\xc5\xbf\xaa\xb1\x95\xb8b|\xed?\xc95\x00\xaaLd\xf5?a%\xec\xa6zs\xe9?E|\xbd\xady\x8a\xe5?\xfbz6\xdc\xb3\x1d\xf8?o\x95\xec\x98\xb7\xc3\xe7\xbf\xd3x\xa3\xbe\x0c!\xf7?\x1ck\x0c\xf3&\xd9\xf0?\xf0C\xbaG\xd2p\xfd?j\xcf\x84zD?\xf7?z\x14\x80\xc6!\x13\xf7?L\x95\x1b\xccW[\xe2\xbf\xdes\xa2{\xfbS\xd7\xbfO\xc4k\x17\xbf\'\xf1?Q\x9au\xa22-\xfb?\xd6\x16\x9aU\xcah\xe6?\xacd\xa8\xc9\x1a\x15\xb3\xbfO\xaf\xa5\xd4HK\xa9\xbf\xcaB\xc3\xf2\x91c\xf9?\x85\x88\x8325\xa7\xf3?\xb0B\xb7\xcee\xb7\x07@\xf4\xe6\xe5"\xf3v\x08@@\xfdE\xa0Gp\x10@F\xbas\xad\x93\x8e\x06@\xc0"\n\xd8\x8bS\xfd?\xf9\xee\xb7\xa4\x99\xa6\xf1?\xf8\xd3\x9b`3\x89\x0e@%X+\xfcK\x96\x05@!Y\xf5q\x7fI\x03@\xb4\x83\x95\x82\xf7^\xde?\xbf\x9d9aU\xc5\xa5\xbf\xf0\xc5#\xca0\x8c\xe1?\x9a4V\xf8\xf2\xec\x07@\x9bs<\xd75S\xeb?\xab^\xde\x8d\xe0\xec\xfe?a\x1a\n\xc6\x1c\x9e\xe9\xbf\n0o\xa4\xee\x89\xd9\xbf\xdbF\xe8\xcck\xb5\xa3\xbf\xb6=^\xc0\x17\xb7\xea?\xa6\xea;\xcd\xf1\x0f\xd9\xbfn\x93o\xc5\\N\xe4?R\x98?5\xa5c\xdc\xbf\x150\xb91\xfcT\xe6\xbf\x866\xd5\xb6J\xb9\xe7?\xd9\xba\xf3\x13*D\x02\xc0V\xf2\xad\xa8\xb1+\xe8\xbf\xb7\xfa\xe8\x04PT\xe3?Jt4\x8e\xeb\x97\xfe?\x94\xe0\xe6Thh\x03@\xab\x82\x0e\xbang\x01@w\xa0(\xa5\xce\xfc\x01@\xae\xebK>D\xed\xe3?w\xd2\x01\xc0\x14\x84\x08@.a7>\x90\x1c\x06@*\xc7\xf4l\x00-\xef?E\x01\x84\xbc;\xc3\xd9\xbf9\xb2pv9V\x00@l\xa4\x93\xef{3\xe1?\xffa\xf7\x83\x87o\x04\xc0|\xc5<\x00\xab\xd0\xfc?\xb5\xde\x1c\x97\xdf@\xf0\xbf\xe2\xb1c\x11*\x86\xe2\xbf\x82\xfaO\x98\xafz\xd5?L\xbd;\xec,\xae\xcb\xbf\xaf\x1e`\xa3r4\xe5?\x03\xee\x02\x0f\x07\xed\xb8?\xef\xf6\x9d\x0b\x05\x80\x02\xc0\xd5~\xb4\xb8\x85\xfe\xdc?\x11\xf74\x1d\xde\xa7\xda?\xbb\x98\x0b6T\x93\xf2\xbf)\xc6\xc0IT\x83\xcb\xbf\xd0\xe8tVg^\xfb\xbf\xe11\x8eo\xd2\xf2\xf2\xbf\x98\x9cC\x80/\xc8\xf2?C\xa8.q&*\xf2?G\x97\x8a\xcb\xc7}\xea?\xd19s\x83gr\xea?\xed\xa7m\xberl\x07@\xb7\xfc\xd8[Q:\xf0?\xfc6\xd2\xbb\xf5\xc9\xf2?\x01\x02\xee\xc0\xe40\xef?\xc3rX\x98\xaaS\xfb\xbf\xef\xbc\xc6\x87\r\x1a\xe3?~\xb6{\xc6H\x95\xbf?\xad\xcc\xb3P\xd2\xf1\xfa?\xa8\xa0KR\x1d\xbf\x07@\xb9\x89\xab\xa6n\xae\xa3?m\xad_"\xf7\xa9\xf8?\xe3\x0e\xb4@a3\xee?\xf2\xd03L\x9ew\xd4\xbf%\xff\x91\xe1\xd6S\xe7\xbfE\xbf\xf7:K:\xb9\xbf\x8f\x1aZP\xe2\xf7\xd1?\xc0f{SZ\x82\xfe\xbf\xfe\xeb\xef\xc0\xe3\xd9\xc3\xbfWy\xa9\x98\x9b\xc5\xe0?lb\xc2\xd4!\xe7\xe7?\x94/\x91q\xba\x96\xf3\xbfU\x9a\x169\xdev\xc5\xbf0\x13/X\xd4"\x03\xc0\xf1\xc3\xcd\xf7\x8a\x06\xfb?\x08xK\xeb/N\xb5\xbf\x9e\xaa\x85\xeee\xc2\x9f\xbfN\x8f\xfa$\xc8-\xe2\xbf]\x17Z\xc8\xa9\x81\x02\xc0\xc8\xb7\x81\x837\x15\xb2\xbf\xd4f\xc4N\xfc>\xf2\xbf\x8d\xb0\xc5\xcb\x1b8\xad\xbf\xa7\xd2\xd3V\x8a\xe1\xca?\x0ei\xc1\x9f\xde\x11\xc8?"o\xce\x8c\xfco\xf2?|\xcf\x99\\\x05]\xf6?0\xd9\x82\xb729\xf1\xbf\xb0\xf3\x8b\xe7s\xca\xf2?\x86\xc1(\xda\xf2~\x05@TR\xe3\xd3\xe8\xe7\xdb?\xb7\x0c\xda\x17\xfco\xd2?\xfd\xc1xk\x0f\x1d\x96\xbf\xc2\xfb\x9b\x87\xc0i\xf5?\xdda\xfa\rm\xa2\xe1\xbf\xce\xc0b\xe3a=\x90?\xbd)\x9a\xed\xf8\xa1\xc1?\x83b\xad5\xf5\xa9\xe7?\xe7{PZH\x18\xd0?t\xa1\x1e\x82H\xe7\t\xc0\xa3\x0e\xb49?8\xc2?\x81\xfa\xbf%\x94}\xa5?\x0e\xcb\x00\xd0^"\xf5?G\x02\xd9:\xf5\x91\xe8\xbf3w\x97\n\xa6\xa1\xe2\xbf\x10\x07\x8f|n\xbb\xf2\xbf\xe4\xd3\xfe?-\xc1j4\xdc@\xe6?x\xcf\\\xd5\x01\xf3\x03@\x97\xfcX\x9e\x10\xe7\xe3\xbf\xd7\xb9-k\xbd\xa1\xd2\xbf\x14\xab\x1e\x9eM`\xf1\xbfD\xb8\xf3\xad\x7f\x1a\xfa?\xb2G\x17\xe1/B\xcc\xbf\x1b\xee\xc9\xe8!\xa9\xbe?\x1d\x98\xa9\xf942\xca?\x12\xd6\x18\xf1\xda\xf0\xeb\xbf5\xfe\x98O\xd8:\x01\xc0\x90\xe8\xe4\x06\t\x99\xb9\xbf\xe0\xf6z\xf8\xd6\x93\xd1\xbf\xfb\xeb\x83\xb13\xb6\xed?s\xd6\xc8\xd7K\x17\xff?X\xdf \xe5\x9f\xd6\xf2?^/\xb0\xd2\xec\x9f\xe1?\xa2|\xec\x7f\x1f\x14\xed\xbf\x15\xd9\x13\xde\xa2C\xf3?\xfe\x96-\xeb\xf8\x95\xe9\xbf\xe1,\xceA6l\xf5\xbf\xc2\xe4\xcf\xc9\x80\xfc\xed\xbf\x18\xdd\xc21\x81#\xd7?\x8d\xd71\xc0\x90)\xf1\xbf0\xb4\xea\xfb\xa7\xb9\xb6?\xbbg7\xcf\\\x1d\xb2\xbf\xc2\x13\xdd\x138\xc2\xee\xbf\xf7\x92\x80\xfbH\xfd\xda\xbf\xc3G[\xbf\x10\xef\xe4?\x87\x8c\xf3\'\xb0\\\xb5\xbf\x1c[\xa0\xc6%\xf1\xe8?\xe6])PSh\xf4?\x1dPA\xbcdY\xd2\xbfn\xd5f\x06\xf2\'\xea?\xb1\xac\xa5\x8d;*\xab?0\x98\xf7\xee\xaes\xf1?\xd6^|\x07%h\xdd\xbfi\x00\xa5\x1e_\xda\xe8\xbf\xa3l\xdd\x17\n\xc1\xd5\xbf\xdd\x01\xc3T/s\x01@\xf9"\xc8\x1e\ns\xd8\xbfbD\xcaa"W\xee?L\xbd\x8e\xeb]\x85\xcb\xbf\t\xba26\x91\x8a\xe1?\x8d/\xe3r&\x08\xf2\xbf$\xdb\xddA\xc9\xa3\xa8\xbf\x95\x05\xda\xe6\xb7`\xbe\xbf\x83!$\x999\xfb\xf0?\x19\x95\';\x82+\xf3\xbf\xcf]\xac\xdeO\xa2\xf2?\x9f\xc0\x9b\x1a\x06\x94\xd8?\xde\xc9\xde\x16Qp\xea?\xbd\x80\xcb\xa9\x01e\xa0?\x1e*\xd8\xa4\xaf\xe5\xc6?m}\xc4<\x1fX\x0b@\x1b\xb9\xa8\xed\xec\xcf\xcb?\xbe\xdb\xe8\xee\'\x06\xc9\xbf\xfe\xaf\x88\'\x9e\xa5\x07\xc0B\x94\x19>d\x8e\xd5\xbf\x8b\x19\xdb\x1aV\x8e\xf2?\xaab\xb9\xdc\xcf\xcb\xf9?\x05nu\xd7\x11\xf8\xdb\xbf\x7f\x80\xbd\x04\xf3\xa7\xdc?|\x8f\xb5\xc2%\xd9\xc8?\x04K0{9\xaa\xe5\xbf".H\x13\xe5\xc2\xf2\xbf^\x1e(\x9e\t\xa6\xdb?O\x0e\xa5s\xb5\xbd\xe3\xbf\xd0\xf4\xf0\x05\xb7\x91\xf3?\x11m\x95L\xe1\xcd\xf3?\xbc\xf5\xb2\xd5\xac\x06\xe5\xbf3u\xa0B5\x08\xe2\xbf\xb1\xe4V\xb5\x1c\n\xe8?\x18\xc0\xdf;\x93\x99\xec\xbf7\n\x10\xcc..\xd6?\xf2\xb3"\x02m\xec\xe4?\xa5\x08\xac\xb7\x14\xe0\xd0\xbf\xc34`!\xd9\xa6~?\xb1\xab\x08\xb8\xc8J\xfd?\xd1/\xaf\xc4\x81w\xf0?m\r\x07q\x92\xb7\xd9?\xec\xb1iA\xa7n\x03\xc0\xe1\x12\xa1t\x94F\xcc?\x83\xe4\x8e\xfe h\x0b@\xc0v\xe0\x8e0\x8b\xdb?,\xb1\x11\xcel \xef\xbf\xf9\x99\x0c\xa3$f\xe7?\xf6\xacx\xfc\xed\xf0\xde\xbf_\x1c>\xcfe6\xd7?\xef\xb1$T\xa1\xb2\xba\xbf\xeb\xe6@t\xfa\xf5\xed?\xbaY\xbdur\x1b\xfe?Z\x9f\xc6\xa9B]\xe3\xbf\xc4\xc9Evr\x1ev\xbf_*\x02\xf1r\x13\xf2\xbf\xe3\xe7k\xdaK\x81\xd5\xbfG\x8c\xebqV\xe0\xe3?\xf9\x0f\xd1;\xd1\x83\x00\xc0Eo\xef(W\xcb\xf4?[#z<\x00s\xde?\x0fE0\xc2\xa6\xd7\xf7?\xecQ\xb1\xe5T(\xca?u\xe1\xf3\xd1GM\xfb\xbfzqy\x88\xceG\xd8?\x01T6\x81\xed\x19\xeb?\xec\xcdoJP\xeb\xc2?e\x9dz~\x18\xa1\xe1\xbfNx7\x9a\xb6W\xc8\xbf\xe3}4\xdee\x1a\xaf\xbf\xd6\xe6Z\t\xb9H\xd3?\xcc\x17eL\xf3\xf2\xf0?\xe6\xe0nP\xe1\x90\x03@\x97\xdf\x9dE\x85\x8d\xe7?\xefdpd6\xff\xd0?!_\xa8\xa3mU\xe8?}\xf4W##o\xce\xbf\xdc\xc6m\x9dK\xa1\xe0?\x1a\xdfI\x83\x87\xfd\x06@=k\xa5Y\xed\x1b\xee?\x0c\xad\xf2\xfe\xcd\xa6\xfe\xbf\x05\xa3\x14\xca\xcdh\xcd?f)\x9dB\xa0\r\xe4\xbf~.{\x16\x0f\x08y?\x08)\xb7\x8dOa\xe5?\xd2-\xe3f\xb6\x84\xf4?\xb8\xaa#\x103\xe0\xbf\xbf\xccTh\xf1]4\xc9\xbf;\x19XIK\xad\xb1?\x8b\x9dx`-9\x02\xc0r\x1f\x07\xa0\xd9T\xe9?a\x8b\xa7\xe1\x13\x15\xe8\xbf\xa2\xcd\xad-\xa2\xd9\xed?\xb2r)\xe0\x7f\x89\xde?\xe0-*\xd8\x80J\xc2?\x11\xf6\x13\xe6\xa2\x97\xc3\xbfh\xe5\x13\x8c\xe7k\xa0\xbf\xc5\x84#m\xe6)\xdc?\xb0W\x84\x9eM1\xf8\xbf\x99+\x87!\xd4\xdb\xe4\xbf\x0c\x91\xd4h\'G\xbd?\xf7]\xf5L\xf4\x9d\xe3\xbf\x94b\xcf\xceO\xd5\xe8\xbf\x025K;\xb1\xce\xde?\xdab\xd1N\xd7\x8f\xd4?\r\xed<\xd5\xb7\xfb\x02@\x8a\xc3b\xfegX\x00@%Hpp\x18\x12\xf8?+E\xb5\xd4\xdd;\xe2?i\x96\xd0\x7f\x9b\'\x81?`tx\x14\x08\xc4\xf5\xbf\xb1\xa9\x8c\xa2m\xff\xd2\xbf\x80\xba\xdb\xf3\r\xf1\xf5\xbfp\xd4\x87\xe1\x03\xc8\xe0\xbfR\xb8\xb4\x95\xc2\x0f\x01@\xe54H\x05\xbc)\xf3?2\x89\x80*\xe4\xbd\xe1\xbf\x9b5\x9ae\xf8\x90\xec\xbf\x97q\\\x1fd\xa2\xdf\xbf\xef\xbdM\x07V\xaa\xd0?\x8f\xc5;\xdb\x08\x9f\xe4\xbfiDq\\\x00\x08\xf5\xbf\xb0\x9c\x07+hA\xf0?\xeb4\x17\xf9\x91b\xea?\xc6\xf0\x0b\xfb?\xb1\xe1\xbflB!7\xc4}\xd5?\xb5\xcb\xaeM\x82\x1c\xbc\xbf1\xb7v,v\x03\xf9\xbf\xab\x14\xf2\xf9f\xf5\xdf?&\xa7\xe5L\xbfX\xe4?\xc0\xc0J!\xd2\xc2\xe9\xbf\xce\xee\xe7|\x82y\xf9\xbf\xec\x97\xc0g\x1e\x1a\xe9\xbf\xa9\xd0\xf0U\xff2\x01@\x17\x9e\x04\xd7\x80\x81\x05@\x00D\xfcT\xb64\x14@\xc5\n\xd1\x85\xdaj\xc4\xbf\xa4\x98\x93\xf9\xed\x1b\xe6\xbf\xe2?\xff\x1f\xd0\xd1\xf4?3^\x94\xa2\x052\x8c?\xd6\xd3\x87U&\xa0\xd8\xbf\x97>\x94\x07\x90\xcd\x8a\xbfv\x13\x11K\xc6\x14\xec\xbf\xb1\xb0\xd9\xde\xf7\xa2\xfa\xbf\x03\xaf\xcdx)J\xfc?\x95\xdd\xd7\xf1\x10\x01\xdb?\x13\x14\xea.\xcc\xcc\xe5\xbf}v\xd53+\xf0\xed?TR\xdcY\xab\xba\xda\xbf\x87\xe1\x1ba\x01\xa1\xd8\xbf\xa8\xa1M\xf0: \xd5?R\xf2\xc9\xe6\xed*\x00\xc0\xa1:\x99\xf9\xfe\r\xd8?\x1dY\x04\xe1\n\x9c\xdd?GJE\x8a#5\xa4?&\xa6\xdan\x02\x03\xe7?J+\xe5\x162\xd9\x93\xbf\x84tJ\xc2j\xdc\x9e\xbf\xc61,\xee\xb9\r\xed\xbf6\xb4\xe4\x06O\xd1\xf6\xbf\x08\xe6\xfb\xd6A\xcf\xb0?\xef\xac\x05$\xeb\xee\xe6?vhX\xb6B\x10\x14@X\xe2\x0b\xe5\\\xf0\x08@\xe6>\x0eH)]\xa9?\x90=N4\xeaw\xc4?]aJM\x8d7\xcb\xbf\x90\xdb\x11p\xffK\xdb\xbf\xd3c(t\xd5\x1a\xd5?\xd0w,@p\xd7\xc3?\x0e\x1c%\x96\xf7K\xe0\xbf\xd2\xfb\xad\x8e"q\xed?\xabV\x918\xf1\xbf\xe9\xbfH\xadK\xb3\r1\xb6?\x82A\xa4\xd0\x8a\x80\xdc\xbf\x84\x89zWT\x19\xf0\xbfs\xc7X\x19\xa1\xf0\xf6?a\xa4\xeb\x84\x19A\xbd?Qz0\xe3\xbe\x06\xdc?\xf2z\xd7\x00c\x1a\xe1\xbf\x8a\x19\x0b\xb0$=\xf1\xbf\xb0\xc4\xa4\xd0I\xac\xd9?$\x88\xdd\x91\xc6t\xe0?0\x8b\xa2\xda\x8a,\xe4\xbf\'w\xab\xc7Hi\xb2?\x18\xdf\x8f\x0bC\x84\xe0?U\x90\x9e\xab L\xf0\xbf%2\xc1\x10\x87\xce\xf5\xbf\xaaF&VJ\x9e\xe9\xbf\n\xd6\xadX\x9fc\xb6\xbf>\x08\xbcU\xec\xe9\x0e@Li\n\xd5O\xb8\xf4?W#4}\x90\x9a\xf0?<\x02\x1dD\x9b\xe1\xed\xbf\x89\xf2>\xd2\t\xdd\xe5\xbf[\xee\x0c\xe9\x06\x08\xf0?\x8e\xd5\x05\n1\x97\xf4?\xcf\xce\xf9\xed"\x8c\xea?\x05*\xa7.\xef5\xf4?p;\x96\x8a/\x9d\xb6?-\x0eMJL\t\x01@\xf7\xd0\x12\x1d\t\xa4\xc1\xbf\x04\xea\x0fc% \xde?>\xbb\xed\xf7\x9f\x95\xf0?Q#n\xb3}\xc4\x01@\x8d\x97\xfa\x872*\xe1?%\xf6\xbf\xefw\xf3\xc3?_\xc0\xc2\xef\xd9\x92\xea\xbfw\xe9{L\xc7\xf1\xfc\xbf\x8d\xfb-=9\x8e\xe2\xbf\xd1,y\xdc~\xdb\xfb\xbfb>\xf3Th\xec\x95?\x99\xce\xd3\xac\xe3\xad\xd2?iu\x81P\x18\x02\xee\xbf/\xea\xe5\xc2\xe7^\xde\xbf\xb5\x8b\xdf\x91\xad#\xd6\xbfe\x04NZq\xf9\xe8\xbf\xd6\xb9\x9e\x97G\x0c\t@\x9bX\xcb#\x94l\xfd?\xc6\xc5\x10\xa65\xb8\xfd?g\x06z\xdc\xe8\xba\xce\xbfav\x90/V\xdf\xdf\xbf\xab~\xf0\x1d\xa3!\xe6?\xb0f)\x92\xf6\xa8\xe8\xbf\xe1}(\xfa.\xc6\xf3\xbf\xfa\xcb\xcaf\xa1\xad\xd6?\xd2\xcc\xcbv\x9b\xc5\xcb\xbf\xa0\xc5\x19_^\xcf\xf7\xbf\xeb\x00K\xba\xad%\xd4\xbf1^\xd9D\x9a\xea\xe5?\xdf\xc7\xbfe\xfbb\xfd?\xb4\xf3>aQv\xa5\xbf\xd5\x8bI!l\xa9c?\'\xdc\r\xfa=,\xe5\xbf\x89\x13\xc8t\xad\x02\xd6?g\x1e\x1c\x8e\xddA\xd7?5\xb6m\x12#K\xf8\xbf\xc1W\xcf\xa1W\x8b\xcb\xbf\xdcY$\x0c\xf6\xc6\xf4\xbfI\x9e\xbc^Yo\xea?\x95\x93\xb5\xdb8\xd8\xd1?\xa0\x17q?\xfeY\xea\xbf\xf9Q\xa0\x8d\xab\x94\xee\xbf\x05V:\xf500\x06\xc0h\x10\x12\xde\xeaT\x00\xc0\xc2\x1c\xa2\x19\x8e\xf7\xe4?b\x1e\xf9\x997\x14\x01@\x9bO\xca\x01\xf8\xfa\xf3?\t\xd6L^\xb8\x8d\xe1\xbf\x05\xa7.6\xaa\x9c\xf0\xbf\x80\xbbBP\xa9\xf4\xf4?\xc1\x19mQs\xb8\xe2?\xa8\x19\xfe\x01\x0ct\xc0?\xd6\x19\x03\x7f\xd1\xfd\xd4\xbf\xea\x8f\xc9\x9e\xe10\xef?\xdb\x9c\xfc\xa5\x04\xd9\xf0\xbf\x06\xbb\x9d\x91\xbf\xcb\xf6?\xcb0g$Uj\xeb?\xc78\x7f\x99.4\xf6\xbf\xee\xab\x86\x18\xa9;\x01@\xc4n\xf7W\xa0\xd3\xdc\xbf\x0f\xfesx*\x91\xef?l\xc3^b\xe0`\xd0?p\xfa;\x12\xe4\xaf\xf1?<\xf3`\xe0Sg\xee\xbf\xb0\xdd\xb3*E\x82\xa3\xbf|\x03\x83\xba\xfb\xeb\xe7\xbf\x04\x8f\xa1\xc4%\x9c\xf4\xbf\x04\x06d\xb8{R\xe0?\x89\xcd&\x8b\x1e\xda\xf9\xbf\xc8\x83\x85\xed<\x80\xb9\xbf\xc5\x02+D_\x11\xfc\xbf\xce\x18\x06\xfaO:\xb1\xbfy\x829\x02\x0cj\xeb?\xdb=GP\xcd[\x0f@\x19\x0e\x10T{_\xf2?_\xb6i\x9c\x8e{\xe7\xbf\xf5\x9a\x87\xb9{*\xdf\xbf \xc0\xe3nP\x8a\xe0\xbfq\xb9\xb6C\xb0\xdf\xe5\xbf\x92T\x8a\xbe\x0e z\xbf\xc9:\xaf^\xee)\xfa?\x03\r\xa6\xb6|~\xd0\xbf\xb1\xfep>h\x87\xf5?\xae\xc0\x17T\x13\x83\xfa\xbfT\xfe\x8f\xf9\x0f\xb2\xba\xbf6\xb2F \x1bH\xed?\t\xb8\x00\xe9\x18\x19\xfd?r\x98\xfee\xbb\xd8\xd3\xbfLo\xc8,e<\xfd\xbf\x9d\xfc\xf8P:\x99\xee\xbf\xfb\x14)\x8a\xa0\xc1\xe3\xbfa\x86\x06\xb2d\xed\xd8\xbf/\xffqg8\xb0\xe4\xbfO\xda\xce\xf4\x88]\xd6\xbf\x1f\xc2?N\x8bY\xf9\xbfg?\xeb,+\x17\xe1\xbf\x992\xbe=\xeb\xaf\xe1\xbfz\x91\x93\xbd&\x06\xde\xbfR\x1dR\x9ds\xfc\x06\xc0\xf3\xce$\xc8\xda>\xe3\xbf\xb1\xca\x91\xca\x0eL\xff?U?\x15\xbe\xc80\x08@\x98\xfc\xd9a\xa4\x1d\xd4?\xb2>\x0bF\xecT\xfb\xbf\xe819p\xb2x\xdb?\xdd\x80\xb1e`\x02\xd3\xbf\xd3\x12c\x8f9\x85\xf1?M\xa9\x16we\xe03?\xfcvI:kh\xff\xbf]\x13\x9bz\xbe\x17\xe2?L\xf2\xff\xa9\x8a\xbc\xc8?\xdd\x14@"\xc4\xb6\xe4?\xdd\\\x13B\xe4J\xf1?$\x89To\xee\xbd\xbf?\x0c\x80\x91\xf0\xb7\xae\xd0\xbf\xdf\x9b\x99_\xc9M\xe3\xbf\xe0\xedJ\x86\x19u\xf3\xbf\x08\xfc__?\x0e\xe5\xbfS\x00\x10\x12*0\xeb?+\xe3\xc0\xf5l\xc8\xea?\xe1\xf0 \xfb\xff\xc5\xfa?\x92\xa7\x8b\x87\x9d\x08\x02\xc0\xc0\xec\xa7\xa4\x1c\xda\xf3\xbfu\x1d^\xf6Tq\xf3\xbf<\x8a\xc8\xa7\xcd\x90\xce?\x1e\xfc\xa6zf5\x01\xc0\x98\x8e\xdc\xf5\x8a\xd5\xd8\xbf3I\xa7.\xec\xd7\n@1\x93B\xc2{\xf8\x13@\xd8|T]\x04\xa2\xf9?f\xf5>4$\x98\xe9?\x9ei\x18^Y\x03\xef?w\x9a\xbb(v\xe2\xeb\xbf\xcbD,\x15\xc3\xc6\xd0\xbf/\xc6\x07V._\xe0?\xc0s\xe5\x81\xb2\xf2\xb0?\x1d\xbf\x1cF&f\xf2?6\xb2\x04P\xe6\xfc\xe8\xbf[\x9f\x93\xdf\xbb\x88\xf5\xbf\xf2\xafX7_S\xec\xbf\x14\x15@\x85\x04?\xef\xbfu\x8eC6P\xbf\xe3\xbf\x00U%T\xbb\xfcs?EY\xd6\xc5-\xff\xf1\xbf\xe6\x0b\xd4#\x0f\xcc\xd3\xbf\xd4c\xa4\x18"\x93\xea\xbf\x84\x1c\x83\xd6q\x0e\xdf?\x93\xff\xa5\xf5\x11\x0f\xc5?\x0f\xe7aH]F\x05\xc0Gn%\xfd0\xb6\xff?[\xa3\xec5\xaf\x99\xe4?x\xa6\xe3\x1ej\xd4\xdd?\xe6\x18\xc0L\xc2\x13\x06\xc0E\xd2D\x15m\xf0\xe4\xbf\n\xfe\x05\x00\xd4\xa9\xe6?\xb9\xe7\x11\xfd\x99\x05\x01@\xb6\xdb\xab\x82\xf8Z\x07@\x96|\xb9_!\xff\xe5?\xfb\x1bYPG\x83\xa0?\xf8t\xe4Sc\xfb\xd3?\xd6_\x07\x1f\x8f\xe7\xe2\xbf\x0f\x81\xb9\xec\xb5\xd5\xf2\xbf\x1fj\xd9*\x0b\x15\x01\xc0\xf4G\x91B\xf7\xb5\xea?\n\xeb]\x1d\xb0\x05\xd6\xbf\xb5o\xf2\x83&\x97\xf9?\x05\x84n\xac\xc1>\xda\xbf\x903\xde^t2\t\xc0\xbf\xa4\x81,\x85\x94\xd4\xbf\x8b+h\t_f\xe2?08\xa2L\x02\xd3\xf1?\xc3\xeb\xa2PL\x86\x96?R\x9b\xabY{\x07\xd7\xbf^\x98\x12\x89\x1f\x88\xb4?\xc3\xd9-\xad\xfd!\xd0\xbfX\x07l&\xcd\xa8\xee?\xae\xce\\\xcc\x03/\xe4\xbfp\x93\xd0\xaech\xe1?\xe1\xbaz\xbf\xa6\x87\x04\xc0\xfbH\xbf\x88\x16\xf6\xe1\xbf\xae\x06\x7f\x00M\xc2\xe6\xbf\x1b\x94\xa9v\x16\x8a\xd3\xbf\xc7\x7f\xd4\xcf\x8a\x98\x07@\xd9\xfd0\t\x1a\x16\xfa?\xb2m\xbb\xee\xea?\xfa?xK\xa3\x99\x94\x84\xaf?\xed\x96q\x0f\x061\xdf\xbf\xe3\x12\x81*\x8e\x9c\xf8?\x1a\xac\x9b,\xe2\x14\xd0\xbf\xec\x19\xa7(\x9d\xad\xcc?\xe2R0A\xfa\x9f\x02@\xef\xd9x_\xbb\x10\xd4?\xa5V\x8f\x04G&\xf3\xbf\xeax\xacT]\x9c\xff\xbfkh>\xe5\xf6\xef\xe3\xbf-F#\xcd\xb2\xc9\xc1?\xf9&#*\x13\x95\xf0?F\xce\xafH\xec\xff\xe5?a~\x98k+)\xab?\x9d{\xc3~5T\xeb\xbf\xe8\xc5I;\xb0\x87\xf4?=\xc0\xef.\xcb\xd3\xfe?h\xb9\x0e\xd6`%\xe2?\x929#o\xac\xcf\xb1?X\xc7\xd6-d\x0b\xf8?\x1e\xd4l3# \x03\xc0{\xc9\x02\xf7\xf3\x91\xd1\xbfQ\xd5\xa3\xb5\x17v\xeb\xbf\xcd\x12\xa3\xd4\xa8\xa5\xc4?\xe4,\x1e^\xcdE\xbc\xbf\xf6\xafb#WM\x07@\xdc\xc6\x1a\x06\xda\'\xff?\xfb\x0f\xa7\x81,|\x1c\x93\xdc\xbf\xe3K\xca\xabm\xbf\xf6\xbf\xe0\xab\x0b.\xf61\xb4?\xce\xd6\x1a\x9d\xee\x85\xc8\xbf(\x03!~\x9fR\xea?\x9f\x93\x04\x08\xa0\xc5\xd9?\x84\x11\xf3\x9dt\xc7\xeb\xbfQ\x1a~,\xc6\'\xd1?\xe1\x8bw5\xbd=\xf2\xbf\xc9\xbc\xc8(\x88w\xe7\xbfN\xf2\xc4\r\x17\xa3\xdd\xbfW[\xca\xc8\x90\xd2\xf0?+e\x8a$8\x0c\xd9\xbfP\x9e\x08\x133\xea\xe5?\x9e\xbe\xd1\x85\x8fM\xeb\xbfS_\x16\xb7\x81\xac\xdb\xbf\xe6\xb0\xbc\xa5\x06\x87\xea?D\xd0/\xb0\xf1\xf9\xb6\xbf\xcb\xcf\x0e\xb5\x91\xd2\xd6?\xab7\x1f\xcc\x8a\x86\x0e@>`\xd0\xc9\xa6\xfd\xfc?B\xa4\x9d\x00\x18\x93\xfd?\x06\xe3v\xb5\xca\xc3\xf4\xbfOqR\xf7\xf5\xe2\xe6\xbf\xac\x96\xdbgc\x8b\xea\xbf\xb6\x1a^\x17\xfdH\xd2\xbf\xa0\xc7\xa7%\xc5\xbe\xff\xbf\xe1\xabtY\x0f\x05\xcc?\xf2\x11\xe3m\xd8\x0b\xdd\xbfE\xcb\xec4\xa4\x05\xed?\xea\xf6\x19\xdb\xcc\x89\xd7?\x08\x1f\xa7_\x82\xa2\x01@FO\xb4@\xffz\xe6?\xbd\xef\xba\xb33\xda\xd5?\' Q&\x0f\xc3\xf8?\xdd>\xecA\xe9\xe4\xf1\xbfD@\xc0X\x15v\xd8\xbf\xa3\x01\xa0\xca\x8cY\xe8?\x93\xc2\x84\xef\xc9\xfd\xce\xbf\xce\x86\x97\x10\\\xd5\xdd?\xaf*\x00x\xbc\xe7\xf9?\xa5j\x04\xf1\\\xb7\xf0?\t&\xc9S\xb8\xe6\xd1?\x00\xb0\xca\xba\xb81\xda\xbf\x96vyv\xa0\xd1\xb4?\'\xea\xb3\xab\x87s\xed?\xe1\x89\x12\xe2o\xbb\x02@\'\x1b\x1a*\xe3>\x07@\x8c\xad\xed[\xd1\xe5\xae?\xd2\x8dI\r\x8a\xfa\xf3\xbf\x84a\xc4>\xec\x92\xcb\xbf\x13\xcc#\r\x14b\xe5\xbf\x84\xc3Z\x9b=\xc6\xe1?l\x9bk)N\x0e\x8f\xbf\xae\xd8\x11AY\xae\xf6?\xa0`\xa2\xa0\xea\xdf\xa7?\xacm \x97"\xc3\xd9?\xc5\xd8)\xa36)\xb1?\xaf\x12\x134\xb3\x92\xd1\xbf\x96v\xd5\xba\xdd\xea\xd9?\x1e\x83o\xa9\xc7\x99\xe5?h\x9c\xbc\xb0\xab9\xf4\xbfL.\x16j\xe2\x1b\xf9?\xa5\xdb\xe5\xc8\x869\xec?w\na[ .\xcd\xbf\xd9\xf0\x87\x0c|\\\xb8\xbfhA\\m[\xb9\xf9\xbf\x97\xe4~\x9fM\xa9\xf5?NA\xd9gq\xf6\xfc\xbf\x8d\x18n\\_\xeb\xf1\xbf>a\xc6\x00\xde\x0e\xc0?\xc8\x8c\x82\x9eW\xc9\xf5?\xbe\x0b\xd2\xb0\x89\x14\xfb?\xc2\x84k\xddJQ\xd5\xbf\xec\xa7\xe2?\xb8\xeb\xfc?+\x04\x1e\xfa\xbf\xfd\xdb?eK\x0f\xc01\xbd\xce\xbf\xd2\xd5n\x1b\x8f\xd6\xee\xbf\xdcn\xe8\xa2A\xd1\xe6\xbfV\xa0\x9c+\xb4\xed\xe8\xbf=\xd5\x96/\xa9\x1a\xd7\xbf\xa2\x84R`\x84i\xf8?\xf06\xb0g\r\xda\xe2\xbf#\x1b\xb0\x80\xf3\xe4\xc6?\xcd\xe5R\x8e\xdb\xcb\xd1\xbfW\xef5U\xe2\x00\xc8\xbf\x88fb\xf5\xb4\xf0\xc9\xbf\xbf\xd6E\x0eh9\xd6?\xde\xac\x90\xf1M\xbf\xe4\xbfV\xd4\xdb\xd0\xd0`\xe2\xbf\x14\xff\xd9\xf0\x8a\xa1\xe8?\x88\xeb\xfd\xb5@\xdd\xf7\xbf\xdd\xd3<\xc9\xe5\x92\xec?\xeb\xdcu\xf8n\xa0\xb5?\xb7\xdfl\xd1\x19\xa2\xcd?\x80\x10\xb6\xea\x16\xc3\xe8?F\x8c\x8bb\xdc\xcb\xcf?\xf1\xcb[\xaf\xdc}\xd3?\xc2\xbb^\xec\x90\xa2\xfb?\xf71C\x0eV\x81\xf0?)\xba?t\xbdh\xe5?\x9b\x13\x7f1\xbfi\xf1?\x1d-_+4O\xf2?\xb7g\xcf\xe8\x96\xcb\xef?\x8f\xd3\xbc\xa01\x83\xe9\xbf\xc3;\x19\x9b\x0e\xf6\xa0?\xdb\xab\x95\xf1a\xba\xde\xbf\xa4\xbd\xe0\xe3-\xb0\xe0?<\xc2\x1e-\xcf\t\xe0\xbf\xc7\xbe\xa4\x9f\x17\xf1\xbd?t\xad\xba\xeb+\xfc\xb3\xbf\xfc\xa3\xa0\'\xfes\xe3\xbf_\xbc\x11\r\x0c\x85\xd7?C\x83\x9e\xfb\xf9U\x05\xc0C\x7f\xa1u\xf0H\xef?[:\xa3Q\x94\x89\xed?\xf1,\xc7\x1b<\xe1\xea?\xd8\xcd\xc4\xa5d\xbb\xec?\x10\xc0&\xd1\x89\x99\xe0?]\x80\x8c\x00\x809\xf1?\xf8&\xf1q\xac\x0e\xf1?\xc0!\x9c\x87\xdf\xe0\xd5?\x11[\xa8\x7f\xfa\xc6\xee\xbf\x95\x86\t\x9b\x96\x1b\xe7?\x07\xe6\xc3GG\xef\xf0\xbfi\xc8\x86\xbf\x12O\xd2?\xda\x8d\x96,\xdd1\xd8?$m\x9c\x12\x90\xc0\xf8?\x9ay\x7f\x0bh\xc6\xf6?%r\x14\xf8\xc0\xb1\x02@;j\xa7\x15\xaf\x82\xf5?\x05I\x0fP\xb7\xde\xc6?P\xe2Xh\x8f\x1e\x03@\x9d\x80T\xcb:\xb6\xee?\xb7\xca5Wcd\xa3?\xa9\xf9\xe2s]D\xea\xbf\xf4\xcdv\xab\xf1\xdc\xc6?\x9d\x0c\xa1\x81\x9bm\xe6?3\xfb9\x8c\x88)\xc1?\xf6\x9e\xa1\x0b6D\xdc\xbf9F\x17\x83\xc2\x10\xf3?b`1\xd8z-\xef\xbf\xef\xe1w\xd1\xee\xbe\xe1\xbf\x8e\x95"\x15e[\xf9?\xc3L\x98\x033\xd6\xea?$\xf3\xb1\x8d\x8a}\xb6\xbf\xa8,y=\x1b0\xf8??\xce\xcc\xea\x96_\xd6?\xbb/\x00\x9c\xbb\x1b\xf1?\xe4r\x13\x0fy\xcd\xe9?\xd0\xfa\x1f\xbb\xfc\xa6\xf2\xbfPG\xed\xb3\xc1\xbf\x12\x8a6\x80\t\xb1\xf9?\xf7\x90@\xec\xb1t\xe6?\xadF\x84\x84\x11w\xcd?\xec\xe2\xd3\xc8X\x81\xc1\xbf@\xe8\xa6c#\x84\xf6\xbf\x1a\x02b\x9f\x7f\xe5\xf8?\xad\x9bDa\x00\xfc\xde\xbf\xabZ\xc8:\x18\xc6\xe6\xbfQl\xec\xc2\xf9\x92\xe4\xbf\xd1\xc3|0*\xdb\xef\xbf\xaa\xa9\x02Om\xf5\xed?\x1d\xd8\x87"f\xd9\xd9?\x0c1\xa4\x1a|2\xc1?@\xe0\xcbR\xc7\xd9\xfd\xbf\x9ev\xaa\x0eh\x90\xe4\xbf\xfc\x00\xc7\xe1_\x13\xea?\x83\xcd \xde7}\xa3?\xbdBg\xbf,\x11\xe8?f}\xb7M\x08\xc6\xb8\xbf\xa0\xca\xd0=\xb9<\xc3\xbf\xf9\xa9\x7fb\x18|\xdb?O\xbd_\xaf<\xd5\xf7\xbfA\\\xc3Z\xee}\xe5\xbf{\\\xe5\x0b\x0c\xfe\xc0\xbfvzs\xf0c\x04\xf0?\x8d\x950yZ\x1d\xcf?\x99\xb9^m+\xe5\xff?\xbbB7\xc5%\xff\xec?\xc0g\xc3\xc9\xcal\x03@\r\x07\x96A\xa8q\xf5?>w\xed\xd8\xa1c\xef?\x87\xa48(\xb1\x1c\xd9?"\xb4\x14\xa6\x9e\xd7\xda\xbf*Q\x9aB!\xa6\xfb\xbf\nB\x0f\x81\xf0\x0e\xf4\xbf\xf05\x9e\x17\xa4\x82\xeb?\xfe\xa6\xb5K\xad\xfe\xe6\xbfzy\n\xec\xc9y\xeb\xbf\x1fd`\xc2\x81V\xeb?\xd7\xbbn\xbb(\xc7\xf8??x"})L\xe0\xbf\xb5\t\x12\xd8\xd3$\xdf\xbfK\xca\x9e\x14\xa6\xcd\xed?\xc1\xeas\x8f\xee\x8f\xe0\xbfU\x90\xbe\xc5\xa7\xe7\xf4\xbf\r\x83\xe3\xce(\xf9\xf3?>)\xebNgm\xf4?\xb1\xe8\x81\x87\xe6\xbc\xd5\xbf\t\xb4u\xa6\xbd\x18\xf7?\x9d\xd72\xff\x92\xfc\x02@\x9b\xa2\xc3l\\\x1b\xe0\xbf\x89\x7f\xdb3\xdex\xef?\xe9a\xca\xf4\x84\x16\xe7?\xf4s\x1e\x90\xe7\xbb\xe2?G\x82\x91\xaf\x87\xc6\xb6?!o"\x8bD\x14\xf1?\xb5\rUem\x1f\xe5?\x0f\xe8\xd8\xc5M\xa2\xfb\xbfVoc\xc0\x8b\xff\xf1\xbf0\xb1\x1d\xc8\xf6x\xdc?b\x04l\x99\xef+\xe3?\xba\xb9\x81\x1a\x15\x08\xed?8d\x87\x93\xfb\x84\xeb\xbfU\x00\x93\x1c\xb2\xb3\xf0\xbf"\xcb\x15\x8fn%\xd6?7\xd5\xc3\x9d\xd1\xdb\xf2?k\xe2\xe1\xb6\x8ca\xfc\xbf\xe6\x88\x08\xae\x19\xe3\xe0\xbf\xfdo\xe2Ps\xd2\xde?W\xe5;/z\x83\xf2\xbfK\xe9`+gN\xeb?+Q\x1aV\x89x\xdb\xbfOI\xf9\xa6R7\xcd\xbfx9\xf7\xd1\xce \xc1\xbfp\x92\xc5\xa7z\xfe\xee\xbf)n\x86\xef\x0f\xa6\xe0?\xdbb\xf9<\xa3\x82\xfa\xbf\xdb\x8d\xa0k\x9c|\xfe?\xec\x99K\xf4\xc4\x85\x06\xc0\x92\xb3\xd5\xe2\x07z\xed?uq\x88\x1f\xa1\x08\xae\xbf\xcfe_\x80\xabJ\xdd\xbf\xc91\xe2\x95\xbaX\xdb?\x86Jl\x04\x88\x04\xa7\xbf\xe4\x82\x93YN]\x03@\xb3\xd8\x19\x1e}\x81\xfc?LB\x1d\x160{\xe0?\xb5cA\xa6dG\x85\xbf\xda\x99\xd9\xf2.X\xdd\xbf\x1a\xd7\xbc\xf2\x81\x85\xcc?\xce:#\xde\x91\x0c\xfc\xbf\xc1\xa9\xaa,\xbdu\xdb?\xad\xf4\xc6\x87\xacG\xdb?\xb1\x0f\xce\x12\xab\xb1\xfc?c\xdf\x99\xc1\x8a!\xf3?\x8d\xc5M* \xcf\xa4?\x18\xa2\x7f\xdb%}\xad?\x1bqs\xd45\xf6\xf9?\xc0\x0e6|6\xba\xf3\xbf\xfe\xdf\x18\xe2\x04\xa9\xb9\xbf\xc6^`a\xc2Y\xf0?\x9df9\xcf\xe5s\xd4\xbf\x1a\x1c\x9dq\xf0\xd7\xe0\xbfmO\xfa\x06\x81s\xea\xbf \x02\x0c\xab\xf6\x81\xed\xbf\x0b\xb7\\\xfdy\xcf\xee?S\x08^|D1\xe5\xbfAqJ_q\xcc\xf2\xbf\x9e\xb8\x1f\xa2\xe2j\xe1\xbf\xa5\x1a\xab\x1e\xf2&\xf5?b\x14V\xdc\xaeN\xef\xbf\x08h\xc9\xc0\x8cQ\xec\xbfH\x13\xec3g\xe2\xfd\xbf\xc6\x1e\x08\xc2\x01\xe7\xf5\xbf\xef\x9ab+\xba_\xb2\xbfL?xe\xd8\x85\xc3\xbf\x8dM\xe2\xb5Z\xe7\xf1\xbf\xa8\xd5Wk\x92\xf1\xcc?$\x11g\xe3.\x18\xd6\xbf\x13A\\\xc0\xf8\x97\xeb\xbfo\x94#\\\xa8\x80\xda?\xc16T\xab\x14\xb2\xdc\xbf\r^\x19Bq\xb1\xf5\xbf\xaf\x1e\x88\xde2@\xe7?\x8f\xb7\xa8.9\x83\xe1\xbf\xa7\xab\x8f\x8c*0\xf3?ckw\xe9\xc0$\xbb\xbf\xc91\x9b<\x9c\xb6\xf6?Xu\x94\xdd\xc1\xfe\xe0\xbf5\ng[\x907\xf7?DR\xb3\xfd\xb0&\xd8?\xbf\x8c!\xbb\x1a\x01\xd4?v!\xe6\xc9\x1d\xa2\xe2\xbf\xdd\x9c\x90\xbd>\xa3\xf4\xbf\xdd\xca\xeda\xd9\x9c\xfd?\xf4;8<\x1c\x8c\xfe?\x90\xb2\x93\x7fh\xfa\xd1\xbf!\x1d\x0fL\xc8Q\xe5?\x0e\x96\x85\x8fB\xa7\xe1\xbf\x80)\xbd\xb1\x98?\xfd\xbf\xae\x8c:\xf6r\x1d\xe7\xbf\x8f\xdf5rH\x81\x00@\xa9g\x1eH\x9f\xf5\xdd\xbf\x0cff-l\xda\xf1\xbf\x83^#L\x8cj\xe3?O\x87~\xb8\xd4\xbc\xe1\xbf\r\'\xca`\x8e\x1a\xd6\xbfM\xef\xc5\xcf\x80\x96\x00@!\xda\xc8zoa\xe3\xbfPq\xfe\x94\x17\x82\xa1\xbf\x9f\xa1\xbf\xdb\\o\xf0?\xe9S7\xfb\xc6C\xfe?\xcaC\xd3\x93R\x8b\xd8?\x87\xf3}\x00\xa2(\x02@p\xc2 \x9a\x97=\xce\xbf\xfe\xbb\x9f\xe9\xdd\x83\xc5?RVP\x91\x81\x82\xfd\xbf\x96Of\xd7\xa4\xff\xac?\x93S\xe6\x07\x8cN\xff\xbf#\xf7s\x9c~\xef\xfa?\xf9\xd1\x90\xb3\xa5\xd8\xee?\xda\xf0#\xf7\x16\x1d\xf7\xbf\xef\xa8[\x9c\xddq\xf3?\xcf\xeb>%+\xf1\x9c?\x82w1\n\x0c\xfb\xfc?9jI^a\xed\xf4?\xd5\r\x9a\x9a.\x83\xdc?Yu\xbf\x1e\xc8X\xd4\xbf\x8d\x06\x1c\xeaI\xea\xe9? \xec\x05G\xcfH\x86?\xa8\x18\x1f\x1c\xa5]\xc9?s\x0b\x8d\x0c\xb5\x0c\xed?\xf6*\x97\xa6$5\xd3\xbf\x96\x85j\xa1\xa9\x80\xda\xbfp\xdd3>\x08V\xe7?8\xeb\x16\x93,q\xf6?\x01\xfcO\x11"u\xcb?\xc831\xeb\xf0/\xf8\xbf8/\xb6\x16Q\xe4\xec?\x98\x8d\xc8\\\xd7\x9d\x06@\x9a\xe4O\xc8\xea\x01\xf3?U\xba[\xdf\xd7\xbb\xe5?\x81\x00.\x9c\x92\xab\xe7\xbf\xe1\xd2m\x12\x15\x0e\xf1\xbf\x0f\x9ewk\x16>\xe8?\x19e\xad\x18w|\xe9?\x93\xc5\xa7Be\xe1\xfe?\x84AN\x14\xa1\xef\xf7?\x89\xcc\x0b\x03\x87\xcf\xd5?\xc6\x9e7\x0f\xac\xa1\x05@|\xce\xbey \xcb\x08@\xe1rG\x06\x19\xe9\xd2?K\xf74\xa7\xb4\x04\xf9?\xa8\x1f\xa47]\x1e\xe8?\xa7\xe3\xdd1S\x7f\xd9\xbf\xc5\xd8r\xaf<\xc0\xb7\xbf\x10wi\xc07H\xb5\xbf\xe1\x17\xe3\xf6\xe8\xec\x97\xbf\x8an\xb9\x1b\xb2\xeb\xe4?\x95\xbe\x1a\x80\xf3\xd3\xee\xbf7\xb5\x0b\x87N8\xee?\x9d\xf2\n\xd7\xdcQ\xda\xbf\x96\x87}K\xda\xd0\xf0?\x1d\x06\xdcUg\x9a\xf5?\xe65\xa7Q\x0cL\xfc?@\xc8\x8crr\x81\x00@M\x8c\xbd\xaeP\xbc\xdc?3fAi\x0eg\xe8?\xcfqP\x9d\xb6H\xf1\xbf\xdd\xce H\x1c\xb9\xec\xbfw\x0f\x02H\x87b\xc8\xbf\x98\xf5\xd4\xa1\x86%\xdd?\xa1\xddO\xc5\xe8G\xf8?z\xc4\xdf\xf1E\xf3\xf5?\x08=\x10\xd3V\xec\xe4?\x84\xb0\xfa/3\t\xe4?%\xac*\xcd\xfdP\x0e@T\r\x19l\xcb\x9c\xc7?!\x07\x16\xd5\xf2K\xfd?\x82n\x00#\x94\x11\t@57\xdc\x9d\x8b\r\xeb\xbf-N)\xc82\xe0\xe5?\xd4e\xeeq\xb6\xd5\xe1?\x0e\xd6\xac0:s\xf1?\xf8\x9dE%\xe2\xda\xe3?\xf2B\x15\xcd\n_\xf5\xbf\x84:\x12:\xfb\xb2\x03\xc0\xf3\xb7!\xb4$\xe7\xbd\xbf\xca\x07:F\x04\x0b\xf1?\x97\x91\xe4\xc2\x04\x88\xdd\xbf\xa4\xec(\xb7F\xef\xd9\xbf[#\xc2i&\xf9\xfa\xbf\x9aQ&\xdb\t\xb0\xa7\xbf\xca\xc2\x91S9Z\xec\xbf\x1c\xab\xe8\xbd\x8f\x14\xb3?<\xf9\xebw\xad\x0e\xf0?"N\x8aJ\x1f\xaf\xf7?W\xdb\x8b$\xcan\xde\xbf\x173b\xcbF\xeb\x00@\xce`~\x92e\xd9\xa2\xbf\xddl\xcc99\xb9\xe5?\xc4\x90!w[\x8f\xec?\x9b+\xfbsm\xa1\xd1?\n\x16\xc4\xe1\x1a\xc9\xe5?F\xc1T\x0e\x9d\x15\x04@F\xc7\xd3\xf3\x08q\x01\xc0eZq\x1f\x95*\xf2?p\xe2e\xabo\x88\xd5\xbf|\x18\xe9Y\xber\xce\xbf6)\x1b6\x10u\xfa?[\xf3\xa5\x160{\xf8\xbfJj\x10T\x07\xde\xd9\xbf\xc3\xa3D\x1ea\xe1\xbe\xbfR\\\x96>\xb6s\xff\xbf\xe7\xc3NU0\x12\xcc?I\xda\x94\xc9\xe9\xfb\xf1?\xad\x18\xfa\xa1t\xf0\xf1\xbf\xd81\xdf\xcf\xca\xd2\xdc?\xa5id\x9a\xf3\xb9\xd5\xbf\x1f\r\x0b\xc1X\xe6\xeb?\xfb\x0b\xc2|\xc5\x9c\xfd\xbfDc\x1d(\x9c\x1d\xda\xbf\xaaW\x98@T\x90\xd9?\xe45J14\xc4\xf0?\xcf\x18:\xe5\xf8\xc7\xef?\xc4\x80R\xb5\x87l\xf8?\xb9\xe8BC\x87&\xeb?(\x99\x07\x8d\x14\xd3\xfa?Tw\x9d9\t\xf6\xfe?\x00o<\x03\x11\x04\xc9\xbf[kh\xc6\xa9W\xe7?i\xb8@\x85\xaa\xeb\xd4\xbf\xe9/\xc8\xcc\xc6h\xfd?\xac\xc4\\\x87\xc1\xc7\xd9\xbf\xe2H\x0f\x9f\x85U\xd3?\x97\xfa\xdf\x90\xeds\xf7?E\x85\x02$fo\xf1\xbf\xad\xe0\x88\xbf$-\x8d?\xb6\xbe\x13*T\xab\x02\xc0G\x89sS\xd1\x87\xda\xbfC\x0b\xbd\x85\x07G\xc1?\x98a\xba\xf1\x16>\xf3\xbf\xbf\x1c/\t\xcc\xa2\xf6\xbfL\\\xe1)\x95\xc5\xcd\xbf\xd23\xbft\xa8\x87\xd0?\xc1\xfd1\xe4\xc6W\xd8\xbf\x12{\xaba8a\xcd?Z&\xf4\xa4\xb2\xe3\xf6\xbfO\xbe\xb0\x86\xb3\xa5\xcf?\xc7\r\xb2\x84\xbe \x94?c\xfa\x05d\xda#\xdd\xbf/\xedg\xcb\xdc\xa8\xf7?\xa6\xc2\xfb\xf0\\\x05\x01@L]-\xf2\x81\xe3\xf5?\xd9I=-\xddg\xd4?o ;\xe3\xe5\x8d\xc1\xbf`S\x0fZ\xd9\x87\xd2?\x11\xbfv\x8d\xa9\x9e\xe9?\xfe\x8e\x89+\xf4:\xe0?\x9aQQLo*\xe9?\x1c}\xde\x10-\x01\xf2?\xf8\xc7D\xc3\xe6\xb0\xc2\xbf}.\xa2\x13)\xbf\xf7\xbf\xaa\xe7\xe4\x92\xbdC\xe1\xbf"N\xca\x0f|6\xc4?\xa1\xa0\xe77\x7f`\xc7?v\xdb\x9f\x86\x12\xe6\xc4?L\x1cN\xcb\xfc\xf2\xee?,\x03U\x88\xb8\xda\xe5\xbf\xec~\x85\xb1\xaa\xdd\xf0\xbfK\xe2\x14m\xee\xff\xdb\xbf\xc0`\x89\xd9\x15\xdb\xf2\xbf\xa5\x02f9\x15e\x07\xc0\xa8\x9a\xc3\x9c\x93Q\xcd?\x853\x1bgp%\xdf\xbf\xa5\x06sG\xeb.\xc3?-1w\xcb*m\xd6\xbf]\xeb\xb2`\x89x\xf4\xbf\x12\x12\xb7J\x12\xb6\xde?9\x92\xdf\xd6\x95\x99\x07@\xb5#a\xa5\x1f_\xdd?\xf0\xcc\x8c\xbePJ\xf1?\xbc%{\x15\xe5\xb8\xeb?U\xf1\x8bT\x9f&\xd2?\xa3\x93\xeb\x87dX\xfc?\xc2\x11I\xd8\xf3\x9e\xc2?\x94b6\x91\x91\x11\xeb?\x0cf\x98\xac\x1bj\xe0?\xaa\x05U\xe0\xefr\xe1?\xb2\x0f\xac1\xaeM\xeb\xbf/\xceH\x9d\x0fJ\xca\xbf\x02\xd7\xa2H,\xb4\xb0?Tm7\xc99<\xeb\xbf\xc0Ow\xcc\xafy\xe7?p\x84t\xee\x1f\xa6\xe4?\xc8\xe8\xcc\x85|\xcd\xe0\xbff\x86\x04g\xca9\xb8\xbf\x12\xf9+\x1c\xd3e\xc1\xbf\xfe\x88\x86\xcd5\xd6\t\xc0\x03{?\xd9|\x99\xf4\xbf\xdf-c\xe6\x13\xa3\xd9\xbf\xc0q\xe4\xc7D&\xb2\xbfOE\x9e\xd59\x08\xbc\xbf\xcac\xc6\xb7\xb8L\xf3\xbfu\x8f@\x07,\x8a\xcd\xbf\x90g\xd9R\x9b\xd7\xf1?\x9d\xe9V\x1d\xac\x0f\xf2?I\x05\xfb\xa6\x91\xab\x03@\xe9\xf9\x8aK}\xc9\xf1?\x14Z\xcc\xac\x8a[\xe6?\x04!\xd3/\xca\xe3\xe7?A\x82\x13\xc5e\xd2\xf0\xbfh\xfc\xefE\x1f\xc6\xe6?p\x8f\xdd\xa3\x18p\xe4?]&\x88\xa6\xb2\xc8\xe7?\x8d\xbf\x1e\xed7?\xd7?\xa9r\xb4\x06\xf2D\xf6?\xac\xc2\x08\x9e\xec\xb5\x05@\xdf2<\xfe\xfa\xd2\xf2?bSt\xe8CD\xeb\xbf\xc6\x81\xadZ\x1d\x80\xb5\xbf\xf0\xa2c\x00\xd7\x1c\xeb?\x06\xe4P\xbe\x7fO\xe5?\x04\xfb \xc1\xfb/\xf1?\xc5\xad\x96\xa6\xdf\x9d\xc7\xbf\xa7+\t*W\xf5\xef\xbf\xcb\xf5j\xe6.\xad\xec\xbf\x8a\xaf\xe3\xfe\xb6\xe5\xf2\xbf\x974D\xbb\xe5\xed\x00\xc0\x85$\xf3C\xeem\xe5\xbf|)\xf9\xf8\xf5\xbf\xf9?\xbe\x04@\x19\xbb|\xee\xbf6\xa9\xcf\xe2\xb9\x8a\xd8\xbf\xd0\x0c\x8bw\xc3\x9f\xe3?e\xb7\xa3,\x04\xfe\xda?\xac<\xc6"\xd3m\xea?\xad<\x13\xb9t8\xc3\xbf\xe6\x99\xd4\x9e\xbe\x0b\xea\xbfYcH\xb9\xdc\xf1\xdf\xbf\xb8w\x1e\x97\xa4\x87\xee\xbf\xb6\x05\x08\x81\xb8\xc2\x01\xc0\xf7\xe8`DD\x97\xef\xbf/m\xe9i\x18\xe4\xff\xbf8\xac^\xfa\xbdq\xff\xbf\xea\x00\xcd\xd88{\xf2?\x14\xe5\xba>\xaeV\xf5?XX\xd6\xcb\r\xbc\xf8?Z\x93u1\xe0\xc7\x04@\xc4\xd2m\xd9I\xce\xfe?8\x0c\x9e\xafP\x18\xd6?\xfa\xf4\x06\r>\xae\xe6\xbf!\xff\xb3h$L\xe3?\xaf,\xa0\xc29\xc3\xba?\x06{O\x8an\x9f\xed\xbf\xebh\xec{,\xba\xc1\xbf>\xe2\x19\x90\xe4w\x01\xc0\xfc\xaf\x85*[\xa6\n\xc0Y\x85a"M@\xdd?\x97\x81w\x8a\xaf\xd0\xf6?FgP\xc5\xf9\x83\xeb\xbf\x15\xdb\xea\x18\x9br\xfe?T\xc9\xf9\xd9o\xc8\xfe\xbf\x15\xb4\xd6\xe7.2\xed?\xef\xa2\xcf\xed\x8e\xa9\xc8\xbf\xe4\xb4\xbf\xd2\x96\xae\xe1\xbf\x05a\xe56\xe6\xfd\x08\xc0\xf4\x82\xaa\x17\xe6\x95\t\xc0\x80e\xb4\x05\xfc\xb2\x07\xc0\x9bx\xd1Sc\xd5\x03\xc0A@\xc2Ct\x1a\xf9\xbf\x93\x19\x15\x8a\x95\x02\t\xc0\xcawGW\xc8\xc9\xf2\xbf\xf9\xcc\tC\xfem\xf9\xbf\xd4)l<\xc7\x9a\xb8?\xb9c\xbd\x8d\xab\xa0\xee?Rtu$\xeb\x83\xd9\xbf^\x9a.b\xaax\x05@\xe1\x8a\x9fM\x0f\xde\x00@8\r\x85\x98\xfa\x83\xfd?\xcf\xed\xdf>\xf4\xcb\xfd?|DT"\xdeZ\xe1?\xb9\xfdx3\x18y\xb0\xbfH,\xdd\xed.G\xfb\xbf\x0f~\\\xe6\xc2\xdd\xfd\xbf\xf7\x19 w9\xf0\x06\xc0\xd4\x87C\x9d\xdc\x7f\xf5\xbfo*\x1e\xd8\xc4e\xdf\xbf\xfe\xf0\x90\xa6\xac;\xf5\xbf\xcfS\x0b\xc7\x11\xe9\xf0?y\x16fX\x17O\xe1?z\x8d\x00\xfd\x85`\xe9?0i\xa1_\x177\xf3\xbf\xdcB\x06\x89\x1b\xbc\xe9\xbf\x15\n\x19@\xa2\x19\xfa\xbf\xfe\xa5CM\x8c\xcd\xfa\xbf0O`\xea\xe6.\xfc\xbf\xae?Y\xa7\t4\xed\xbf\x0cy\xe4{\xee=\xf4\xbf\xd7z\xb0,\xe5f\xf4?\xe2s\xe8\x8b\x89\xd9\x02\xc0\x1f\xa38\xa1\xf97\xf8\xbf\xf4Bf,\xfe\xa8\xe1\xbf\x15\xc8E\x94K\x95\xf7\xbf\xa8\x82\xa4\xf8\xf7\x8c\xf1?\x1a\xa5\x0b\x19\xaf1\xf3\xbfR\xdf\xd1^\xb4\x96\xd4\xbf8\xb5\xeb\x90\xb2>\xf4?\\\x9e\xd9\x14\xf8\xc6\xdd?\xc6\x8fi\xa2\x9c\xa9\xbb\xbf\xf5\x19\xc5U$\x17\xf1?\xcb\xfbD\xa0\x9f\xd7\xc1\xbf/Aq\x9a\x84\xb8\xd2?\x98o\xedx\xd9Y\xc4?\x1a\x84\xce\xb9`c\xdc?\x9e?\xcd\x801F\x02@\xb0\x05\xddp\x8a:\xec\xbf\xef\xe2\xe8\xd88"\xdd?\x93\x8b iK\xc8\xfa\xbf*2, \xda#\xcb\xbf/X\xe6\x1e\x8d\'\xec\xbf\x94\xbd\x99P\x8d\x9b\xeb\xbf\x1fH8\x1f\x9c.\x00\xc0W\'Z\x81q7\x07\xc0\xa98\xf0\x8dl\xcb\xf4\xbf\xf9y\\\x1b%;\xbf\xbf\x92\xe9\x98\xf5\xca\x8c\xe9?\x8fR\xf3\xec\xd0m\x01\xc0>\x0c\xf4\x8bhx\xed\xbf\x064\x94\xe2)\xec\xe7\xbf(\xe9\x05,\x9fn\xf6\xbf\x8c@\x9b\xd3\xbf\x0f\x00\xc0\xefE\xaa\x1bb\xc0\xf4\xbfN\'\xa0a0~\xd7?\xb9\xe0\x0e\xb32\x06\xdf\xbf\xd6\x87?\xab\x8b4\t\xc0u\x9a\xe0\xcd@\xba\xf8\xbf!\x91\x0e\xd56\xb1\xc4\xbf\xb8G\xc0\x87H\xf8\xee?c\xf9\xeb\xca\xecp\xd1?\x14\xe0\x11\xff\xef\xfd\t@Q\xeb\x1cj\x9f1\xd4?\x1d\xf49 \xbb\\\xd9\xbf{\xf4\xf9\x89\x07%\xed\xbf\x1e\x9d#0%\xcd\xe0\xbfI7\tw\x8c\xd0\xec\xbf\x86\x11\x93\x933%\xb8?\xf4f0\xb5\xc0\xa3\xfd?\xb8\x12\xb5\x9b\\\xa2\xd1\xbf#\x0b;<\xe6\x9e\xdf\xbfG\\\xeb}\xae\x87\xd3\xbf\x8d\x1e\xf1\xbe\xf4\xd7\xce?\x03ccha\x14\xcb\xbfa\x9a*\xec\x8d\xa5\xe8? \xff\x04\xeb\xe2\xb9\xd5\xbf\x97\t\x17\xa8\xaa\xe8\xe5\xbf\xd9D\xfc\x98.\x9d\x02\xc0\xff\x9c1\x18\xdc\x1a\xf2\xbf7\xd2\xff\x19\xde\x88\xfc\xbf\xdf\xd6\x1b`\xcb\x15\xeb?"\x04\xe5\\\xb92\xe7\xbf\xa7\xb8\xd2MA\xfa\xf3\xbfo\xe3\xa5\x08\xde\x7f\xc1\xbfU\xe7c\x83\xe0\x98\xf5?\xce\x1es\xa9\x90)\xf0?\x1fO+\x90\\y\xe9\xbf\xf3d\x1d\x13|U\xe8?\x86?/e\xddJ\xf6?~\x9c\r\xbck\xdf\xfe?h7?\xa3K\xff\xf9?\xb0\xba\xa6K\xfb\x05\xdc\xbf,\x87\xf8\xcd\xcb\xe4\xf1\xbf\xc2\xc9Eg-\xff\xa9\xbf\x9b*\xab.\x16\xc9\xe6\xbf^\x95\x98L\x15l\xde\xbf\xbd\xd9Y\x17\x10o\xf4?\xab\xd9Z\'\xf1)\xe2?\x1fD\xab\xa0\xba\xc8\xd8\xbf\x0ch\xf6i\x80\xca\xf0?\x86\x8d\xbc[O\xcf\xe9?)?.\xe8\x93\xd3\xda?\xa6\x00\xffq\xe9\x0b\xe0\xbf\xc2\x87\xdf\xc4\x80\x08\x04\xc04a\x1f 9\xcc\xe2\xbf\xfbd\xfa\x82\xb1\xe2\xe1?t\xac\xbb\\\xc2\x9b\xf3\xbfg`0\x89\x83\xbd\xcd\xbf\xf9n\x16\xa4\x82L\xeb\xbfGc%\x15\xa2\xb8\xf3?\xc6\xe5\xb0\xc0\xe9\n\xfa\xbf\xab\xb5\xea\x1f\xc7\x05\xe0?\x0bP\xa6F\xba\xb8\xe8\xbfs\x8e\x83\xab~?\xf0?\x03\x81\xba\x1a8\xed\xf3?\xeb6e0\xc7\x9a\xf4?\x9a\xa2\xaeB\x81\xb5\xe9\xbf\xf4\xef_\x85\x8a\xec\x04@\x0e\xc5\x84\x1e\x9d\x93\xe9?\xfb3KWH\xad\xf1\xbf\xf85\x8e\x96\xaf\x96\xe1?\xc5\x8e\x07|\xf1\x12\x9f?\xc3Y\x94hT\xf3\xd2?\x9bG\xa4\\\xb3r\xf0?b1\xb2i\xac\xf4\xd6\xbf\xe9"\xaa\x89X\x07\xc2?\xcd\xd8Y\xd5Vc\xe5\xbf\xfd\xc6\x01\xca\xf1\xb4\xd0\xbf\xbfsV\x94,q\xf9?\x8f\x9bx\x8a+Z\xd6?\x15\xd4\x01\xc0\x18\xfe\xc0?\x11-\xfah\xcf\xcf\xc2?Z2*N\xa6%\xe4?9\xd7\x80\xa2y\xfc\xc1?\xfe\xe8#d\x1b\x90\xd6?\xb1 \xed>\x11/\xf1\xbf\xba\xcac}\x9du\xda\xbf\x98\xce!1\x18\xb3\xe0\xbfur@r\xf38\xe6?\xa2\xb5G\xe6\x01\xac\xde?\xda\xfdX\x10[P\xfa?\xb6\xbbi\xf6\xc5\x91\xed\xbf\xdc\xc9\xb4 \xa6\x1f\xa4?\x89t\xd4\x90\xce\xf7\xf4?\x84\x15\xa2\xadr\x12\xec?|\xc9\xfe\xab\x7f\xd0\xa8?\xa4\x8dU<\xa0\xb4\xbf?\xce\x1e\xa2\xab\x85\x0e\xf7\xbf\xca\xa1/\xc9!`\x02\xc0!\x06\xf5\x1d4\x14\xef?k\xc4\x89\xee\xc4\xc9\xab\xbf\x1a\x8bA\x9bM\xa0\xdd\xbf\xe9\xf4\xf4#9\xd8\x02@\x88\xf3\xdb\xbf\xc4\xd4\x02@\xa8B\xb1\x1c.\x94\xe0?\x8b\x1a\xed\xf0\xe2\x16\xfc\xbfx\x07\xf7\xa0\xc4-\xf6\xbf\xd2\xcd\x01h\xf3\r\xeb\xbf\xa0\xeaQq\xb9\xbf\xe8\xbfW\x89\xe1C%\xf1\xca\xbf\xb1\xfb\x08\xe1\ne\xf5\xbfR\x10\x10\x1b\xde\xd7\xee\xbf\xba6\xbd\xd3\xe9\xf7\xe9\xbf\xfe\xbb\xf5\n=\xd4\xe8\xbfUt\xb5\xa3u\xcb\xc1?\xb1\x07\x836}=\xf0?\xc7P\xb6\xed\x89B\xd4?\xe0W\xe5\x1c&\x9c\xd2\xbf\xa2\x1a.\x07;\xd8\xf4?\x7f\x82\xad\xa1\xb3\x7f\x01@;3\x0f\xe1:\x19\x03@J\x97\xdb\x8f+`\xbe\xbf\xe7\xac\x03L\x9du\x01@,\xf9\x85\xc1\xa8]\xdb?\xa0\xcdY\xfe\n\x19\xf3?gIq\xa2n\x99\xcf\xbf\x01Z17P\xad\xd4\xbf8\xafj\x9fo\x9f\xf9?\xfd]-\xb51z\xe5\xbf\xc6\x18\xf7\xac\x84\xdd\xcd\xbfv_}\x91t\x1a\xeb?MP\x86tG(\xde?XG\xb2\xb2\xb3L\xd2?\xef\xf0\'\x95\xcf`\xec?)m\xcdwo\xbb\xfc?\xab\xe7\xb9\xe3y_\x96?]\xe4?O\x85@\xb4\xbf\xc2\x89;&7L\xd8?r;0\xdf\xd3F\xe0?Z\r\x88f\x8e*\xeb\xbfS\x9ed\xa7\x0b\x0e\xd0\xbf?\x9f\xff\xa8\x86b\xdf?\x01\xf4Hq\xaf\x9c\xe1?\xd2\xfb\xb3\xeeQ\x8c\xe6?\x9c\xed\xcd\x1f\xf0\xec\xe9?\x1cfw\xc3\x99&\xf2?\xec%\xda\x05Z\xf0\xff?{\xd2c;\xcbo\xf8\xbf1\xfd\xf1\xe2\xcd8\xf8?F%U\xb3\x06%\xf6?#\xe5\x01\x1at)\xee\xbf\xf6\x08\xa3\xcb"\x83\xbf\xbf\xfa\x11\xcd\\5\x85\xe2?\xb9\xeb\x14S4\xf6\xf2\xbf\xf033-~\xbf\xec?\x9a \x1d\x99\x05l\xfc?R\xc6Y.\r\x9b\xf2?^\x9e\x8f\tb\x7f\xf8?.\xc8\xb6\xf5\xb56\xe1\xbf4(f\xfc\xd5P\xf6?\xff\xf2l7X\x93\xf2\xbfa\xb8c\xd8\xe9\xd4\xd5?_-92\xa5B\xed?\x93\x0ch\n\xb4\xb2\x02\xc0\xfc\xac\xdcEQ\xb1\xb5\xbf\xb2\x9cl\xd9\xe5o\xe7\xbf\x99]\xf4\x8b\xfbp\xdd?\xaf\xb6\xda\x9bN\x1a\xb5?\x9b\x14E \xd2/\xc0?b\r\x19|N\xd6f?T\xb8\x01o\xeb\x9a\xe7\xbf\xc6$\x1d\x13Z\xe0\xd3? \xa1\xb3r\x9e\x18\xf1?-G)%t\xa6\xed?\xc0\xf1{\xab\x1d\xfb\xcc?\xf2\xdd\xa8\xfbWK\x04@\xbeq\xad|}>\xfa?\x80\xaeU\xb6gQ\x01@\x1cy\xb3\x97\x96\xf2\xe7?\xe4\xc8\x8f"\x89\xbe\xf8?\n\xa6\xb7\xf12T\xdc?\xc6\xf4f}\x84\xbf\xf0\xbf\xd1(Pbw\xe9\xe9?E\xb63X\xf2y\xe3?\x84\xfd\xb0S\xb6h\xe0\xbfu\x98\xe3\xfa$Q\xf1\xbf\xee"[\xe3\xea~\xe7?Q\xa6\xff(\xa2\xd7\xf1?\xc9#\xb2CuD\xf8?\x83b \xe0\xa3O\x82\xbf\x7f,q\x94G\x80\xf6?U \xdb\xfbK3\xf8\xbf\xb6\x18O\xec\x16>\xb9\xbf\xe4y\xfb\xb3\xfd\x9a\xd8?\x9b\xdaH\xac\xe7\xcd\xe9?\xe4\x95G\xcd\xc7\x8e\xd2?\x18\xf3\x87\x8b\x90\x81\xef?ya\xe6\x95\x00\x86\xbf?\xe6)e\x9ez\xb2\xf5?\xb9Q9\x9b-\'\xe1\xbf\xdb\x85\x06\xc2\x7f^\xb8?\x12N\x84D8\xef\xf0\xbf2\xc6<\xaa\xbe+\x04@Y\xee\x01\xcf\x1b\xf9\xeb\xbfM\xf8\xb7f\xf6A\xf1?X\x1e\xa0I\x1b\x00\xee\xbfp\xef\xa5 \x1b\xde\xf7\xbf\x03\x0e\x96\xe7\xcf6\xf4\xbf\xc5\xe3BW\x9fF\xfd\xbf\xff\x8b\xbc\xd4+\x10\xd5\xbf\xe7Y,_\xf3\xaa\xeb?\x90\xa3\xe2\x8e\xc6v\xf8\xbf<9\x86B8\xfe\xf5?1z\x0c\x9f\x8a\xd8\xd2?hA\xeb\xbf\xf2\xbe\xe3?S\xda\xae\x11\x1d2\xea?\x88\xb8]\xf43c\xee?\xb2\xe5\x88y\x8c\xd6\xef?/\xfd\xedL\xf7T\xf6?\x9f\xdc\n\xaf\xf0\xba\xe7?\x7fe\xf5\x80\xaam\x9f\xbf`T\xd6Z\xc7\x8a\xf1?\xb8\x93\x86`q[\xc1\xbf|\x10\xb3\xc4c\xbd\xd2?\xbc\x8a\x81\xfe\x97\xb5\xb6?\xebw\xca\\\x95U\xc2\xbf\xfaw|\x90\x03\xd7\xf0\xbf\xcd\'\xd1\x9f\xcc\x87\xa9?\x00\x15\xd5\x82\xbb.\xc8\xbfFK_0\x88w\xf3\xbf\x07~\xee\xd0\x97\x80\xd8?\x99u\xfb\x0e\xc3b\xdb\xbf\xdcG\xf4\xd5\xa7\x7f\x00\xc0[P\x81\xf3\xa0\x9c\xe1\xbf&\xc0\x84\xf0\xdf\x93\xd2\xbf"\'\x97\xf7\xffU\xe4?\xf9L)\x0e\xd5c\xf9?xIE\xa9\xb5\x0c\xf5\xbf7q\xcc\xf4\xd7\x9a\xe0?\xf8\xb5\xcd0=j\xc1\xbf:N(9\xc5\x06\xe8?\xbd\xca\xb2\xe7\t\xf4\xc1\xbfs\x8d+Y\xf9\xe2\xed\xbf\xff\x04K\r_+\xd5?\x10\xd9\xe9\xfe\xdd\x9b\xfb?\xd7\xcc\xb5w\xb9\xf4\xeb\xbf\xf1T56A\x01\xf3\xbf\x88]\x06vB\xd1\xd5\xbf6\\\xd5|\x171\xe1?\xfd]\x04\x9af\xad\xf7\xbf\x9bW\xe8\xbeO)\xe1?\x1fd\xc9\xe8\xed\xd2\xc3\xbf\xe6\xad{\xe7X\x87\xd9\xbf[B\xcc\x8e\xdf\xbb\xde?\xb3\x9d\xeeZ\x10\xcb\xf3?\xaf,\xec1\xc2&\xd9?\xc0\xbd\xf7\xed\xbf\x85\xe7\xbf\x1e\x9f\x0c\x08G\xf2\xca?\xd1\x8a\x98^\xe9s\xf2\xbf\x83\xff\x8d#k`\xed\xbf\xa5D\xdf\xd4$\xc9\xcd?\xfd\xae\x9c\x94_/\x86\xbfb\xe4\xf8\tA\xdf\xf3\xbf\x85>P\x00S\xb3\x91\xbf\x98.\n$1\x8e\xe2?\xb8\xb3c\xca\x97\xd2\xe8?\x8b\x18\xb4%.\xc1\xae?S\xe8\x8c-\xd4\xd5\xe9\xbf\x1c\xbd\x0e\\T\x1d\xdd?c\x10R\xbe\xa9\xa6\xef?\x06T@\xf5\xf2\xd8\xd2?\xe3w\x10G\x82\x92\xd3?H5\x04\xdbR-\xc9?\xe5i\x8eG4\xf4\xfc?\\\x1d2\xca\xa6o\x95?\xdb!\x8c\xad\xdcU\x02@\x12\xca\xe4\x17\x87\xfb\xcc?2\xfb.\x04&M\xb3?j1\x82\xbbc\x92\xf2?A\xe7R=bH\xf8\xbf\x9f\x1bT\xd2P\x96\xdf\xbf\xbf\xc1\xe7j\xe7\xab\xf7?%\xc8"\xfe\x1cp\xf2?\x80\x15h\xa6\xd9\x98\xea?\x94\xf42\xf7\xda\t\xca?KF\xc26\xeb\xdb\xdb?@\xbd\x80\xde\xbe\xcd\xe8\xbfc1\xc6>\x18[\xdc?\xac\xef\xafsS6\xe1\xbfg\xe0\xc2\x9a&\xd4e\xbf\xf5\x13\x03\xbcKD\xae\xbfp\xe6\x92o\x11\xbf\xdf\xbf\xb3\x81\x93\x04\xf7\xd5\xd6?*\x8bmf\x88z\xe5\xbf\xa1\xca\x99\xbf\x1e\xf2\x81?\xf7\xc0\xde \x98\xcd\xdd?\xb3\xcb\xef\xb2f\x81\xdd?E-\xd1)\xe0\x97\xa2?e\x9d\xfd\xa5\x0b#\xf7\xbf\xfa\xd3\xed\x10i.\xb4\xbf\xa6e>\x1e\xa8/\xf2?+\x9a\xc5\xc7\xe1\xbfm\xba\xfa\xfb3J\xc8\xbf\xa4V-\xa6\xe5\x95\xf7?ar\xb7u\xae]\xf7?\xa7\x1c\xb1u5\xe0\xe1\xbfv\xa7\xde\xe7\xa8\xb7\xbd\xbf\x1c\x1e\xab\xccQ\x08\x02@\xea\x7f\x9b\xfd\xb2\xa6\xdf?\xa9\x8er\xaa\xeb2\xf1?\x1c\xefo*\xcfH\xb8\xbf2\x1f\xd0\xa5\xa3!\xcc\xbfYC\xf2\x0f;F\xe0?\x16\xba7\x9aU\x8e\xf4\xbf\xb1xn\xdaW\x84\xed\xbf0t\xfcK\xd4\x0e\xf1\xbf\xf6#\x05\x07V\xc8\xe6?W\x84\x06\xf8\xac\x98\xbe?\xf0=\xc4\xd5\x1a\n\xae?H\xc3\r\xdeI\xd7\xe1\xbf\xf7\xc2B\xb8\xd9j\xde?\xb33bF\x81\xb1\xf5\xbf5\xd0\xa2e\x9e\xfb\xd5?\xca3\x81t\xcf\xc8\xd6??t\x9e\xde\xb4\x08\xb9\xbf\xbaSm\xd3o<\xdb?U\xbb\x02C\xecK\xeb\xbf\x10\xe1\x0e\xf2\xf5\xea\xe6\xbf\xa9\x9f-\x99N4\xf4\xbf\x1b\nD\xe0\x0c\xa9\xec?\xacw\x89/:o\xf9\xbf\x13mU\x89\xd2\xa2\xd9?\nL(\xef\x8b\xb8\xed?\n\xc5\t\xb5\xd9p\xe2?\xb3I\xd7L8\xff\xff??\x11?\x15\x02\xa6\x04@>\x07\x84,G1\xe0?\xe5\xdb\x0e\xdd\xe9\xd0\xf3\xbf\x1b\xaa\x00\x8b#g\xf5\xbf/ \xfc.\xa4\xda\xfd?\x8c\xb1\xe2D!E\xf3\xbf\xbf\xe5\xb4\xac8|\xe6?\xef\xf2s\x0b\xa6J\xe7\xbf\x1d\xf11\xcd\x02\xa0\xd7?\xd2":\x03\xa7\xd6\xf9?O\x05\xf1\x1d\x00<\xbc\xbf\xbd\xc3\xc4\xf0\xc7?\xe0?\xf2\xf66\xe5\xd1\xd6\xf8?\x03\xc6\xab\xee\xd2\x1a\xf2?q\xffm\x0c\x12\x8b\xfb?\x9a}\xd0g/\xd4\xe3\xbfo\xbeRy\xfeO\xe7\xbf\xbf\x06\xffo\xcb\xa5\xe9?\x05_$\\I\xeb\x8f?\t\xb9\x7f\xd8I\n\xf9\xbf\x80\xf6=Up\xbb\xb9?\x12\xdb\xd6\xa0\xcd\xf8\x00@gL\xb9\xe2=\xe3\xd4?\xe0\x84d[A\xa2\xe8?1\x0c\xba\n\xea\x9c\xf6?\xc9\x03\x16\xf5\xd6%\xf1\xbf\x94\xd3Y\x0b\x13N\xb8?6\xb8\x94\xc4\xfc\xea\x00\xc0\xa6d\x9cO\x85p\xd2?\x9ao\xd2\xb0z\x0b\xe3\xbfrV"\x9e_\xd9\xf1?R\x12\xf8\xbbB\xc3\xe2\xbf-\xe1\xef\xd7\x97\xd6\xea\xbf\xc1a\xd97\x96\xd6\xe3?\xdd\xb1+Inb\x06\xc0\x86\x7f\xe3U\x9a\xad\xe1\xbf\xe4\xa0\xfb\xee64\xe4?Z\x14lx\x1c?\xe3\xbf\x1ag\xb3\xf2\x81\xc9\xf3\xbf#\xec\xe4q1\x04\xe6\xbf\xc9\xfd\x14\xb6\xf9\xbf\x02\xc0\x197\x91\x91\x9dU\xd6?B\xbcB`\xde\x92\xf2?\xb1\x00\xa2\xf7\xe8L\xb6?\xa9N\xc7\xa1d\x9a\x84?\xb8\x9b\x92Z\xf2\'\xe7?D\x164\xb9\xb5(\xbc\xbff1\xd8Q\xcb\xd5\xde?lj\x84e\x1d\xd9\xdf\xbf\xdbrA\xeeT\xf9\xa7\xbf\xf4Y\xb38\xe1L\xcc?\xca\x0b\xef!=\xd9\xc5?\xf34\x1d]\x84O\xf1\xbf8\x8b\r\x8d6l\xe5\xbf\x87\xa4\x08\xc3*\xda\xe4?\xe5\x002\xd4\xc0\x08\xf7?d\xb1\x9c\x83$\xf8\xf7?)+-\x07\x80N\xdd\xbf(\xa0:\xf3\x1c\x87\xea\xbfv7\xe7\n\xff\x01\xf1?\x06h\x8d\xb2\xa0\xfa\xe6\xbf\x91\xb4\xdf\x90\xb0\xad\xe1?\xc8\xab\x83k\x125\xfa?\x84\xff\xb0\xf7\xebB\xf1?\xc1\xf0\xa1\xb1t\x11\xc7\xbf#}\x1e"\xca\xf9\xe2??!\x18\xbb\x0b\xa3\xe1?4\n\x13\x9aN\xaa\xf5\xbfR\xfa^\xef@p\xa7?\x93\x0e\x83\xa9\x9e\xa4\xde?\x12*\xc1\xee\xaf\xbb\xe5?g\x8f\x88\xf4\xbb\xd4\xe6?\xfa\x8a|\xa9ia\xec?\x15\xfc{I\xa4\xfa\x80\xbfp\xce\xb6\x87&i\xc7?\x04\x1b4\x8fK\xd9\xeb\xbf\x01\x0eR\xd7\x8e~\xf9?\xd4\xa1\xf5\xe1\xdbM\xf9?\xb8\x1c\r\x11\x9e\xe1\xce?\x9e\xe7B"8\xba\xe2\xbf\xf7\xa0\xe2O\xa8M\xb2?C\xa4I\x94\x18m\xef\xbf\x06c\xdd\xae\x92}\xea?t\x16\xd9\xfb\x99\xb5\x00@\xae\x06\x9f\x11\xfae\xee?\xba\x82\x8e\xfdK\x1a\xd4?\x93\x82\xa0nLi\xb2\xbf\xc68\xe8S\x80\xca\xe5\xbf\x9a\x15\xa8\x19\xed\x8b\xe7\xbf\xed\xd4\x03\xcb>\xa1\xf2\xbf\xa3\xcc\xe7G&n\xfc?\t\x8aYV\xe0\xa9\xdb?\xff}_u\x8cp\xa6\xbf\x86Zv\x07Q)\xcd\xbfvo\xfah\xbd\xd5\x00@\xa5F\x8e\xd6\xe2\xf7\xe5\xbfxkh[\xa2w\xc1\xbfv=\xf8\x8dX>\xe6?\xf1\x90\xb2a\xec\xc5\xee?H\x95$\xe1\x89\x10\xd5\xbf)\x96\xdccm\xe3\xe3?\x19\x05pIi+\xa5\xbfp\xa4\xec\xe0S\xc9\xe6?D6$\x14,a\xf8\xbf\xdd[Ix\x9dp\xf1\xbf\x8d\xc3\xea\x1f\xd0\x97\xfe?w\xa6\xeb\xa8\'\xdb\xe5\xbf\xbcr7\x10\'\xdb\xf3?\x15\xd9\xfe3\x95\x05\xf0\xbf\xb1"\xb9#\xc1\xaf\xd1?h\xe7=>\xec\xa8\xdd?`\xb7\tA\xc8\x15\xee?\xc3s\x1e&m\xbf\xc9?\x96&&\x80{@\xe5?\xeb\x80\x06E\x8e{\xda\xbf"\x01\xdfU\xa5/\x04@\xe6B\x88\xb0q9\xfa?\x08\xcdU\x037\xd5\xe8?[W\x1e:E\r\xfe?+\xe8\xc8\xf4\xa3v\xe4\xbff\xb4Es\x0b\xe0\xfe?e\x96\x10\xa2\xb3\x17\xf1?\xb4J\xdfKF\x03\xf7\xbf\xdb\xcb\xc4\xff<)\xe0\xbf\xa9\\}qe)\xee?\x88\x0bw3\x07\x9d\xf5\xbfu\xb8\xc9\x12+\xfd\xe3?\xed\xbf\xcd\x1b\xb5D\xe2\xbf\xc9)\xf5\xc4rV\xe6?\xfe\x92\xd2<\xdc\x1a\xbe\xbf\xa3\xe0:9\x99\x8e\xc5?\xbc\x94=\xb9N\xb2\xec?\x19i\xfc\xab,\xe2\xd3?I\xb0\x80\x9bs%\xf2?\xd3\x161\x8b\x9e\xde\x05@\xdd\x8e18hj\xf6?EE\x87f?\xb8\xf5?]\xe7\x07Q\xc2\xb3\xe8?EN\xbfI~J\xf0\xbf\x1bfZ\x80\x06 \xc9\xbf\xf9\xbaBx\xdev\xe5\xbf\xf2W\x8d2\xc3\xa4\xc8\xbf\xc2\x07\xeb\xf6\x86\xbe\xde?\xcf_\x0b\x1bbW\x9f?\'\xda\x84w%W\xf3\xbf\xb7\xab8\x83w\x12\xf1\xbf\xb0\t\x7f\xbf@\x99\xf2?\x0cM\x11XZ}\x04@\xdd\xe2\xd5\x10\xa9c\xe4?\xf8\xdb\xde\xfc\xd5\x92\xab?\xc1\xa7D}x%\xf4\xbf\xfb\xdc\xd1BJ\x01\xd4\xbf\xcf\xd8\x139\x97\xcf\x06\xc0P\xac\x1f\xec\xe2H\xe1?Sy0\xdf\xdc\xdc\xeb?3\x0b\xda\x1a\x15\x0c\xef?\rVFA>M\xe1?\x92\xc7\x8f/\xa2h\xe8?\xe7\xc41\xbb\x06s\xe7?f5\x0e\xc7\x99\xb3\xfa?\xec\xd8\xf9\x95\xb3\x1c\x01@\xa0\xc5\x8c\xb34\x08\x04@\xb6\xf0\xf6\xc6\xef\xc6\xf5?E{\xe9\xb2\x96\xb8\xef?\xa3\xa0\x8b\x07G\xe5\xfb?D\xbe\xc40\xd9\xf4\xbe\xbfu/0ye\xc9\xe7?+n\xb4y\xb4n\xf5\xbf\xe0\x87\xebY8\x9f\xf5\xbfg\n\x82\xfe\xf9\x86\xf9\xbf\x08\x06\xa5g\x16\x04\xff?\xe5Cz\xccd@\xba\xbf\xb7\x15\xf9a\xabg\xc2\xbf{\xc0\xc4[{c\x00@h?\x86\xa9\xa2y\xfc?MZ\x02\x08\xfbl\x04@\xdcs\xc8s\xc9\x10\xd8\xbf \x0ff\xec\xa7\xd2\xe7?\x87e\xd1\x8ef\x8c\xf3\xbf\x84.\xa72<\x15\xf0\xbf\x81\x86\x17\x7f\xbc\xfe\x00@z\x01\xfc?\xf55\xf3?3\xe2#\x99\x93a\xd8?\x04\xe5\xb7\xbb\x94\x8a\xea\xbfn\x80]G\x86\xe7\xdb\xbf\xecZ\x10$\xbb9\xf3?\xd6\xf6pF\xef \xf2?\xe2\xb4t\xe2j\xe5\x02@(Y-\x8d\x9e\x08\xf2?Q^\xac[\xb2\x11\x02@CA\xdd\xed\x00\x05\xf8?\x8c\xe6q\xd1\x14w\xe4\xbf\xa5\xb0\xdb;z\xb4\xb4\xbf-C\x93F\xc5\xd2\xea?\xc7\x84\xbb\x02\xa3\xfd\xf8?\xbc\x91\xbe!\x81\x83\xcb\xbf\xa7\xf4V\x869Y\xf0\xbf\xa3\xddt\x87^P\xf3?\x80\x97;\x0b\xd2\x9f\xf2\xbf\xac+\xde\x1f\x1eH\xdf\xbfl\xde\xf8O\x8f\xd8\xec?\x9f\x0b3+I#\xb7\xbfXBJ\xae>\x8a\xf1\xbf\xdf\x16\xdc\x9f\x00\xe9\xcb\xbf\x08\x89\x97WU>\x05@\xb1\x8ed\xffOv\xfb?=\x88C\xf5T\x17\xfd\xbf1#\xba\xc4\xe19\xf1\xbfJ\x94\x0f\xa0\xa2\x95\xf3\xbf\xb7Vs\xda[\xf7\xb0?\x08\xfc\x9dX\xd0\xad\xd3\xbfF\x9ep\xfeL6\xb1\xbf\xd9D\x02\x0e\x8b\x15\xed?\xef\xbb\xf8\x91\xbe\xfa\xd4?\xddP\xff\xb0\xe00\xf6?||\xd5\x9d\x14F\xcb\xbf\xbe\xdc\xc6\x178\xf3\xfe?lr\xdbD\x8d\x1c\xd2\xbf\x0f\xc9\xf73)\xe2\xf1?\x12Vw=?.\xcd\xbfmGx\x9dW\xa6\xd1?>P\x1b\x1b\xd34\xdc\xbf)*\xc2\xd0\xbc:\xf0\xbf~\xcc&\x99/\x9d\xfd\xbf\xcf\xc2.\xd3\xfd\x97\xd1?\x96\x03k\x1bp\xb2\xf0\xbfc\x1ao\xd5W\xb2\xec?\x932\x85\xfe52\xfb?\xec\x81\x11\x1e\xd2R\xee\xbffv^*i\xf5\xd2\xbf\xb3\x9c\xe3\xf7\xcb\xbf\xf0?\xc6\xcb&\x06x\x01\xfd?\xd8pdO\xc89\xd4\xbf\x93+S\x95a\xaa\xe1?*e\xc4\x04K\xcb\xe4\xbf\xbd-\xa9\xf80\x7f\xc1?$\xeeTH\xab\xd9\xe2?\xb0\x99\xfe\xd3\xdaF\xc6\xbf\x9e\xba\n\xf2^F\xfb?vQDAlY\xe1?\xed\xd0\xb2\x06\n\xa9\xed?b?=\x94\x89\x14\t@\x0f\x95k\x03(]\xf6?\xea\xa5P4\xba\x99\xd7?\xde\x88\xc5\x1c.\xbe\xd2\xbf\xff6\x9b\x99\xfc\xf7\xf1\xbf\xecf\x1a\xd2>\xa6\xdb\xbf\xa0\xfd\xd9\x81E\x9d\xec?\x1f\xdb3\x9e\xbf\x9c\xd5?\x8f\xb9\x96K\x8e\xe8\xfa?\x8f>\xce\xcdp\x08\xd2?\x13\xec5E9\xdf\xea\xbf\xb4\x91\xea\xf8\x13v\xd2\xbf\xaf^\x83\xfd\x8c.\xe2\xbfK\xfc:y\xc4\x16\xe3?\x0f\x15\x0fQ\x9a\xc3\xca\xbf\x89[\n\xff3\xbe\xd7?\xaf\xcc\xb7\xeaw\xab\x05@\x9a\\:\x10\x9aj\x02@=n\xc3\xdc\x1f\xeb\xe5?R\x12N\t\xae\x86\xf5?:Cu\x1dxi\xfb\xbf\x1c\tJl\xaf>\xe8?\x0b-\x9a}\xa0)\xff?\x1c\x9ff\xba\xb8\x9d\xd7\xbf\xc3\xda\xa7\x19\x05\x95\xb2?\x97\x15\xd3D\xb00\xed\xbf\x1d\xcc\xbf\xccG\xe5\xf6?4\xc6\xe2Zc\xb4\xd5?\xbeq5\x16Pr\xfc\xbf\x9f\x97\xb1*\xf3\x0el?\xb8\xb1\xa0\xdd$\x14\xcd?\x85\xe0_k\xe8\xbd\xf9\xbf]K,c&\xdd\xd5\xbf\x00\xe4\x8cC.\xe8\xf5?\xaeW\x06k(\xc1\xf1\xbf1pK\x1c\x88\x86\xb8\xbf\xb8\x0fBu_\xea\xe1\xbf\xcas-\x02\xd8\x8e\xa1?M\x80\xa6\x8b\x1f\x19\xe4\xbf\xe5\x95&n\xce\t\xc7\xbf\x19\xc4\x89\x9bK%\xfd\xbf\x88\xc4\xca7\x9e\x03\xd9?jf\xba\xc1\xbe(\xd0\xbf\x81\xfe\x85\x91\xf6\x8c\xa6?\xd5\xb4\xe8Xj\x81\xe9?UPsckm\xf4?\x9f\xdd\x1fA\x80\x0c\xf8\xbfS\xd4.#\n\x92\xda\xbfY\xdaN\x9b5\xf5\xcd\xbfUJ\xf68\xcb\r\xb4?c\x90\xdd\xa4x\xb4\xb6?\xcd\x98\xe2\x1bN\x08\xec?\xeb\xf9o\xaa\xc8\xe4\xbf\xbfP\x173\xea\xf6c\xf2?\x95\x8b4\xbd\t\xbe\xe3\xbf\x9aH\xfc5\xbc\x1f\xc3\xbf\x04\x8f1e\xc80\xcb?\x05\xc6\xd8C\x0c\xe6\xf9?S\x84\xc4\x1c\x9b;\xc9\xbf\xee\xa0\xb27Kj\xa5?6O\xe1\x12\xc74\xd6\xbf\x9eG\xe8>\xa2R\x00@\xe8\xa5\x03x\x99\x8f\xf2?\xf4\xd3\xea\x17\x8d.\xe3\xbf\xebYp<\xda|\xe0\xbf\xde\x1c\x95\xf3j\xbe\xf3\xbfK\xde\xcc\xc5\x90x\xeb\xbf\xd9\xb4 =,\xb2\xdc?\xf4\xbe\xb8\xafW\x82\xdb\xbf\x81\x06\xa4\x04\xfeh\xc9\xbf:sMo1+\xf7\xbf*\xa6\xb1\x9a.\x85\xe7\xbf]\x07B\xec\xa0\xde\xc7?\x93S\x94\x9b*\x83\xe4\xbf\x1c\xfef\xc5\x96E\xf1\xbf\\\x80\xc3\x10\x1e#\xe2\xbfC\x7f\xa0~mM\xd3\xbfh\xa6\x0f\x90z\x03\xe4?\x99Z\xd0$aF\xf4?\x12O\xbb}OM\xde\xbfQ\xb6<\x8f\xa0\xa4\xed?6l\xc0?ja\xe4?\x05\xec_\xcfk\xe4\xab?\x98\x0e\xdd\xdb\x8f\xdc\xf3?PE\xd1\xfdk\x0b\xff?\x15[xA\xfc\x1b\xf0\xbf\xefy\xce\x0e\xa6t\xcc\xbf\xb0{WdF\x9c\xed?\r)\xa3o\xac\xc4\xf5?~\xc7\xb9_\xd1b\xd7?\xd5\x10\x97\xc3\x13\xf4\xf0?\x1e\xc0\xbdW\xc1?\xf0?}\xe5\x87J\xb4>\xe0?\xaa"DRC\xa1\xdd\xbfl\x1d\xb7l\xae\x90\xd2?\x91\xbakW\x95\x1c\xea\xbf\n\xe0^\xa3y]\xf7\xbfm\xd0s:\xab~\xf8\xbf\xb3\xb9I\xc0\xb6Z\xf1\xbf\xbd%\xf5\x9e\x01\x04\xe5?\x06\x1c\n\xb6\xc4\x17\x00\xc0\x02\x96z\xa3#\x91\x0c\xc03\xf8\xc6\xe6*\xaf\xe8\xbf\xd4HW\x0b\x12-\xe0\xbf\x84%}R\xb4F\xfb?\x07\x1a\x80z~5\xf0?LlO\xcc\xdd%\xd9\xbf[N.\xec\x9c+\xf8?\xa3\xf3\x1e\x94@\xa1\xf0?\xcc\xaf\x7fT\x0e7\xf7?B\x04@s\xd2\x99\xde?\xbb\xd5\x88vG/\xfe?2\xba\xe3\xa8\xfa7\xea\xbfRq6|4L\xf2\xbf\x13l*3\xea\xa2\xcd?/=\x9a\xff\x16\x84\xf3?\xa4\x9d\xa8^|\x18\xbe\xbf8\x848\xe4\x07\\\xf9\xbfD\x9fn\xc3\xb1\'\xe7?7\xbd!R}\x0e\xf0?\x84\xb5\x82\x07R\xd1\xe8\xbf:\xc4\x06\xd4V:\xf3?Vg\xad\x15\xd4\xaf\xde\xbf\xb5\xa0/\x8b\x04\x9b\xe1?\x99\x0b\xbc\x80\xf1k\xf8?k6G\x1f\xef)\xf9?\xe3\xdc\xab\xda\x03\xbe\xf6\xbf\xf7\xeaXh\xfb\r\xf9\xbf\x12P\xe9\xbb<\xe1\xf1\xbf\x18\xaep\xd8y]\xe8?\n\xc1\xa1\x8cb$\x9d?`\xa3\xf9\xd6\x0fT\xf7?o\xc57k\xe8X\xf6\xbf\xa4~%\x034\xe4\xdc?\x05\xa8F<\x1fG\xd5?\x1e\xf2\xf5!\\O\xf8?o]z.g"\xc1?;\xa2\xb1\xc2H>\xf1?\'v\xd9\xd2\x83\xe7\xe8?\xfe\xcfVm\xedp\xf0?\xc8)\x81w\x02\x1e\xe0?m\xa4\xe6\xbd0\x05\xbe?Teq:":\xca?xEF{\x13\xb2\xc9?\xf4\xd9\xef\xdeP\x85\xe9\xbf\x9d\xa6s\x8f\xce~\xd9\xbfmI0\x03u\xa6\xf6\xbf\x02\xeb\x8b\x8ff\x06\x01\xc0\xaf\xfb\xcf0\xd8\xea\xf2?\xaa9d\xef\xd9\xf3\xd3\xbf\x8eJt\xac\xae\xe2\xe4?>$k|\x9d\xf7\xec?\xdb\xfc0\r\x14c\xc4\xbf\xe3\x04L*\\L\xe5\xbf\xef\xdbb\x07vy\xe6?\xe2,\xa12\x043\xd0?sW\xd33\xffV\x06@\xa6K)n\xd7\x8e\xe0\xbf\xe53=\xc3\x7fR\xd6\xbfy\xbe\x9d"b\xe5\xe9?*\xfc\xbd\xef\x17\xed\xe4?\x13\xd7b\xf4\xb1\x1a\x02\xc0v\x98\xf0\x93\xa8\x12\xdf\xbf\xdb\x9ft\xbd;!\xf0\xbf,\xc8_:u\xfb\x04\xc0\x9d7l\x12w,\xf1?E78\x82\x97\xac\xc9\xbf\x82\xa7*M\x08\xb4\xf9?Y\t\x8bs\xe5\x13\xc0\xbf+\xb4\xd1U\x9b\x00\x00@\x97\x02\xe8\tm\x87\xd8\xbf-~\xe7\xa5Q\xd0\xbd\xbf`aG\xdf\xb2\xbe\xe2\xbf8F\x1c>\xc9t\xd5\xbfD\x87\\1\x0b#\xf2\xbf6\'j}\x814\xff\xbf\x1dw\xe9\x9f^l\xdc?\xfb\xcf9\xe3Jv\xff\xbfr&\xd6X\x02\'\x98\xbf\x10\x9fM\xb2h\x83\xe5?\x13\x1822Y)\xf5?\xbd\xc5\xebq\x9d\xcb\xfa?\xa0\x8a\xf0\xe4\xab\xec\xf2?\xe4\xa5\x99\xe1\xde\xda\xe0\xbf\xc3s6\x98b\xdf\xbe?\xe4~\xab\x83O\x01\xc7\xbf\xde\xfe\x0f\xb7\xb4\x94\xcd?\'\xdb`F\x88\x85\xe2?W\x0b\x9d\xc0\xc8&\xa5?\xd4\x15\x9b\xb1y\xa9\xe5\xbfql\xd2/\xe9\xf6\xdc?\x91\xdc0]\x83\xe2\x05@c\x83M\x9e\xab\xf8\xde?\x94h\xff\x9d\\\xea\x9a?\x02\x80\x81;\xb9\xa5\xd0\xbfA\xa6\x0b{~\x8c\xf1\xbfK\x0br\xaa1\x9f\xfc\xbfB\xb4\xdb-M\xea\xa6\xbf\x11\xbf\xa55\xf5z\xfc\xbf&{\xe8\xc7\xd6\x9f\xf2\xbf\x17\xa6\x19\x14\xc7W\xf8?,\xde\x04\x03H\x86\xd7\xbf\x81\x01C\xb2\x15\xa5\xe6?^\x82@\x88\x17\x05\xf9\xbf\x0eg\xd1\xf4u\x14\xee\xbf\xc65@\xe3\xad,\xd4?\xfc\xdeO\x08\xa6o\xf0\xbf\xe1\x1eUn\xf4\x97\xf3\xbf\xb2\x98\xa4\x82\xbe\xd0\xf7?S\xa8\xad\xe9\x83\xad\xe7?/\xd3\xd0*h\xc5\xf2?\x8a\x15\xc6;|\xc9\xd2\xbfubM\xc2\x08\x17\xdc?y\xe1\x12\xc3\xea\x81\xef\xbf\x9d\xfa~jW\xf1\xfc?DY\xafR2c\xfb\xbf\xc1\xfd\x94\x7f\xdcO\xf7?\x97\xdd\xb5j\xd4D\xe4\xbfGvMO\x00\x98\xd9\xbf\xc8\x99\xe7\x7fJ\xb6\xf5?9\xdcL\x8f\xcc\xa1\xb4\xbfF\xa8\x83\xb9\xf6\xe3\xdc?\x99\xf8\x01Evx\xea\xbf\x923\xf0R\x8fg\x00\xc0\x12d\x8a\x8c\x9b\xb0\xe2\xbf\xe0\\\x8eD\xe4\xe7\xf3\xbf\xb5\xd3\x8f\xabm\xee\xf2?\r\x8d\xbdIY\x14\xe7?\x95Q\x93\x14\x0b\x15\xf5\xbfe\x98?\x97\x81\xc3\xe0?\xec\xba\xe1U\xa8\xb2\xeb?k\x8ba\x9b\xfd\xc9\xd9?\x05\xb7\xcf\xab>9\xf3\xbf;\xe4\xbb\xcc,(\xdb?\xe6t\xab:\xcb0\xe5\xbf\xd9\xd0P\xb8\xf7\xef\xe9\xbf=-+GK\xd9\xf2\xbf{\x9d\xca<\x13\x00\xfa?\x01\xe4\xdd\xf8\x86\x06\xfb\xbf^\xbd+\xf3\xf8v\xf9?\xde\x9b\xdc\xc3\xf9\x85\xbc?\x1a\xd9d\x81\x95 \xba?\xd05\xcd\xa9\xa5\x94\xea\xbf\x86a\xd7D\xc8\x87\xec\xbf~\xd7\xc1$\x89g\xb1?\x9a\xfc\x8a\x12\xd9i\xef?!s\x97\xa0\x00\x93\xf2\xbf\x85q\x1c\x91\x02\xb4\xb6?\x07\x81\xf9{Nz\xe5\xbf\xe5"\x97\x158&\xe7\xbfP\x9dl\xd0e\x93\xd8\xbf6\x06\xfd\xd2\xef\x9d\xcf\xbf\x8a\xa0\xb1|\xefy\xf7?:\xff\x8dc\x862\xf8\xbf\x85\x7f;\xc7\xdc\xc6\xb3\xbf<\xf0\xb2u\xc8\xfd\xff?\x7f,\x01~\x92s\xee\xbf\xf9\xd7\xd4\xaa\xaf\x87\xb5?\xfa\xaf\xcf(\xc1\x19\xf1\xbfzt\xd0\xccP\xc7\xf3\xbf\xd7:\xb5\x0e3\xb7\xf5\xbf"\x0e]\xfa\xac\xc2\xf1\xbf5\xda\x0ew\x7f\x1a\xf9\xbfE\xb0\xa1A\xfa1\xf8\xbfa\xad\xc2Qz.\x01\xc0\xbe\xb8\xca\x83\x9c\x97\xe3\xbf\xe3E\x0b\xc1\xe9\xc0\xeb\xbf\x07\x01\x93\x1aPN\xfd?\xb5\xbbh\xedBa\xc7\xbf\xa7u\xc8}\xd7t\xa0\xbf\xd15\xca\xa4\xec\xfc\xda?n\xaa\x9d\xca\xe4\x9e\xf7?\xa6\xb3\x98\x87\t\x81\xe6?\xed\x88\xa3\xd5\xa3\x1b\x00\xc0\x8b\xa3\xf8\x83\xee\x17\xea?\\\xd1\xe6\xbd2\xed\xe1\xbfX%\x89\xf2UQ\xca?\rmOE\xd1\xe6\xe8?w\x0f\x07Y\x11\xec\xeb\xbfZ}\xff\x15F#\x8d?w\xab|\xed_\x1a\xdb?s\xa9\x98[\xdeD\x00@\x86\x8e\x90\xc3n\x82\xfd?\x9bm\xca\xcdeW\x8e\xbfF\xeb\x03?S+\xf0?)u\xd0\xd3\x90N\xf4\xbf\x96\xf3a\x9d\x93\xc0\xf1\xbf\xb9t\xd7R=\x0e\xe8\xbf\xac\xc5\xcfO\x19\'\xe3\xbfB\xb5\xa2)\xdc#\xf0?\xb4\x1e\xe9\xf8\x10\xa0\xe9?\xa9\xa7\xd5W\xd2\xb0\xe3\xbf\x1dD\x1d\xa3t}\xf1\xbf\t\xec\x18\xcfg\xf8\xf6\xbf\x9c\xfd\x04\x84\xf5e\x0c\xc0\xca[\xd3q\x93\xb6\xf5\xbf\xc3\xf5!\xddAp\xd0\xbf\x15\x8b4rX\x04\xd0\xbf\x05\xfdj\xe0~r\xe4?\x1f\xa1%|*,\x01@\xb3\xdaG\xed\x9d[\xcb\xbf\x84\xc3XQ\xa9\x91\x84\xbf\x95\xfb\xf6\xcd\xe9\x89\xe4\xbf84\xa4m\xfbl\xeb\xbf\xf2\tM\xd9.}\x90?\x8c\x025\x02\x81\xbb\xdc\xbf\x9f\x8a\x97\xedw\xd0\xe7\xbf\x80\xea\xd22C>\xf4?\x99\x12\xf8\xaf\x14\x05\xf3\xbflyZ4s\x7f\xf7?sn\xbf\xed\xa3b\xde\xbf_[d\x87\xf7]}\xbf~B2\xb6\xc0`\xf5?\xf8@\x9c\xf6\x00C\xeb\xbfMr3"\xd8\x80\xe6\xbf\x8a\xe3\xe1o\xbfo\xd7\xbf\x8d\xa7h>=\xfa\xec?yb\x94\x807g\xf7?\x95\xb4\r\x86\x84(\xd3\xbfA\x15\xd8\xa3=|\xfc\xbf\xb1\x99amqh\xfd\xbfY,#3\xe1\x18\xe4\xbf,8\x04P)\xa7\xf0?\x12\xe8\xa3\x9b1\xf9\xe2?<@$\t\x7f\xec\xfe?Z\x9b\x1cuT\x8d\xf4?\x9eN\x81 \xd7\xc2\xec\xbf\x9c\xdd#\xbf\xe8\x03\xde?\xde\xf2vG\x0bE\xc7?\xb2\x81n\x06\xca\xd0\xed\xbf\xcen\xe9\xa1\x12,\xcd\xbf\xb8A\xe0p-[\xfd?\xa3)\xfcjR\xce\xf3?\'\xb6R\x04P\x0c\xe1?=Xm\xc9Hs\x00\xc0\xd2\xa72\t\xfa1\xe4\xbf]\x0b\xbeA\xafG\xc5?\xa6\xe3\xf0(H\xaa\xf2?\xe1\xac|Y]\x91\xe3?\xdd[\xbf{)Y\xf0\xbf\xd7\x8d\x1d\xf8\x1f\xf7\xf8\xbf%\xa0c\x01\x8b\x8a\xfb\xbf\x0c\xa1u#\x1b!\xde?\xa0\xcd[\xf2\xb6\xcf\x05\xc0K?\xa4\x0f\x82\xa7\xd8\xbf\xfd\xea\xb6^\x1c\xdb\xe5\xbf\xad\x8c\x14\x8a\x8c<\xf4?\x1a\xaed\xa7\x8e\x01\xf8?\x8c\xc09\xd0\x85\xf9\xf2?a\x83)\nP\xa5\xce\xbfRA\xe7F\xf7\x84\x00@P\xc4\x93\xda\xa6\xf1\xfa?\x93\xa50\x12\x92\xb4\xf2?]\xa5\x1c\xd5\x88R\xf2?\xfcv*\x80\xd8\xb6\xe3?\xf8\x94Q\x9e\xd4\xf9\xf0?\xae\x9f\r&\x9f\x03\xee?\xce\xf4\x17\x89\xeeT\xc7?\xbf\x87\xc3\x85\x89\xf2\xf1?U\x9f\t\x87$N\xb2?\x1b\xa0i\xea\xa4\x91\x91\xbf\xe8F\xc8@\n\xc8\xdf?\xb4\xfa\t_\x9cY\xf0\xbf\xea;bP`\x83\xea?\x16\xe1\xbf=0v\xba?@|\xc1\x15\xf1\x1e\xf2?TS3\x14\xcbr\x02@\x90\x19\xf0\x94\xe7\xeb\xf4?Y\xd0\xad\xeb"\x82\xf2?\x04<\xca+)\x18\xe2?\x83F\xfd\xb3\xa3l\xc7\xbf\xbeb\x9c+\xa6{\x01@)j\x17\xe9;\xc5\xe7?\xd7f\x04vP\x84\xb2?\xefowN\xd5\xaf\xf2?\xe0\xa8X\x15h\x02\xc8?\xe1\x9e\xec\xf1\x14\xbf\xfe?\xab\xc7\xd6\x14\xd3-\x05@\xabj\x8d[\xa2\x9d\xf4?\x11$Z0F\xe2\xe3?Hma\xd3g\x85\xde?\'\xb9\xf80w\x0c\xd9?`\x11\xf5\x17!W\xfc?\xbb.\x95\x92\x07\xce\xc0?\xf6\x03^F\xf0*\xb8\xbf;\x1a\xee(WC\xb6?\xf0F\x929\x8a_\x8f\xbf\x0f\x1b6@?#\xb0?\xc4\x9f\x92e=f\xf5?\xcc\xf1\xdd\xb6\x0fd\xd2\xbf\n\xffyEC\x05\xe6\xbf\x88p*\xb0\x13\xae\xe2?\x07\x01wTW\x8c\xc6\xbfD\xd1\r\x84I>\xc4?\x074%\x10\x06\x0f\xcf?ei\x1e\x0c\x19\x9b\xd0?b\x00@4\xd9I\x03@\xf3\x19\x9a7\xdeU\x04\xc0\x04\x1c\xa5\xa23\xce\xd6\xbf0R\x18Gvr\xf0?-\x85\xe8\xae\xf7\xb9\xe9\xbf6\xfe\xda<\xe7\x94\xc6\xbf*?\x87\xc5F\x1a\xea\xbf\x0b:\x99\xfb\x9c\xee\xee?\xe6A\xae\x18Y1\xfe?\x91\xa6\xfc\xb3\'\x9c\xdd\xbf\x88\x84;\x1b\xfd\xe7\xe6?*A$\xa8?\xeb\x04@\xbe\xb9,\xe6(B\xf8?\xf3!\x1c\xa2t\xa9\xdd?\xf7\xaa\xe4\xa8v2\xc7?*RM\xab\x9a\x07\xe5\xbf\xf5\xdd\x1c)\x0b\xce\xdc\xbf\xed\xf0\x1d)\x19\x7f\xe2?\xd3O@?%\xe2\xe1?\x85\x89\xb8\xfa\n\x9c\xf1?\xcb|\xe1\x01\x7f\n\xe4\xbf\x05\x8f\xb2\xd0|\x1c\xe6?"\xcd\x8e\xdfd}\xd0\xbf\xc9\xff\x1f\x12\xa2\x95\xbe?\xbe?\xcf\xb6\xb4\xca\x98?\xd5Z\xa0R\xa70\xab?\xa0\xc6\xbe|\xe0\x93\xe4\xbf\xd9\n\xb6#\xb0?8\xbf\xa3f.-\xb7\xd5\xe2?\xe7\x05@0\xe7\t\xe2\xbf\x83\xc7\x87\x1a`L\xd7?\xbf\xf1\xcd\x12k\xd7\xd7?\x11\xddU\x96)\x94\xf3?\x1e\x84I\xe06\xb6\xf5?5 \xa5\xb0"\xee\xd3?\xf9R\xb5"\xb7Y\xec?\x94n\xac\xcb\x84\xf8\xde?J\x1a\x8e\xcb8K\xc3?"G\x06\xd30\xf4\xd6?\x89,"b\x08\xfe\xff?\xe4\x1aX\xfd\x89\x90\xa3?\xf7G\x85\xd9\xa3\xb8\xc7\xbfn\x03\xf8Dy\xb1\xf0?\x0ei\xd5M\xf9\x14\xa7\xbf\xd4lh9]J\xd4?U\xe7any\xf0\xec?\xbb\xb8$b\xbb\xc3\xdf\xbf\xa1\x85\xacw\x11\x02\xcf\xbf\xbe\x1e\xd6B\xdaJ\xf5\xbf\x81]\xc6\xad\x13\xb1\xcc\xbf\xf7\x927\xc1,\xac\xed?\xc0\x01.\x917"\xe6\xbf\x9f\xd2\xf4\xaa\x97\x95\xe1\xbf\xa5\x93\x01B*\xf7\xd0?\x114d\xcc\xf3=\x01@Y \xd9\xd3\xdbI\xfa?U\xc5\xe3p\xf1\x1d\xea?3\xbc\xdb`\xc9\x0c\xdf\xbf\xef\x1bl[\xba\x07\xc2?\x1a\x91E;\xd1(\xe1\xbf\x97!C\xb3\xc7e\xa4?\x14\xcc\xe5(P\x0f\xe7?\x8ag\x8a\x8b\xd1\xa3\xf3\xbf\xcb\xea^;\xef\x90\xf4?\xbcm!\xcd\xff\xfc\xf3?\xdb\xac\x99d\x90\xd4\xcb\xbf\xd5E\x7f\x1df\x8d\xf8\xbf\xe6\xb0\x17~\xd8\xa1\xd5?\xea\x88\x01\xa8\x15\xe7\xde\xbfR\xc4\x0f\xfe\xcc@\xfb?\'39\xa3\x18\xfa\xe4\xbf\xd6g\x81-\r\xd9\xef\xbf\xbf\xd9w/.\xd4\xe8\xbf\xafxc\xde\xdd\x86\xe7?\r\xca\tF\xb3\xcf\xf2\xbfA\xa3\xbf\xfa\x1c\xd6\xa2\xbf\xd5\xf4d;v\x85\xe7?J\xd8mv\xcd\x87\xf9\xbf8"EE\xfa\xae\xee?NT\x7f\xde6\x9b\xe2?hO\x1bE06J?\x02N\xe2/o\xb7\xf9\xbf\x13\x86o\x0b\xc3\x18\xd1?\xd0\x9f\xb5\xd6\xb6\xb4\x05@\xc8\x89NK\xb1\xf7\xdd?\x97\x05"\x07\xf9!\xeb\xbf\xad-\x01\xef\x1d\xd1\xb3\xbf\xf2\xf3\xd5\x04\x02\x8f\xc2?\xf1\xe5\xbd]L\x19\xd9?C>\x97\x02z\xa1\xd5?\xe0$\x96\x05.\xd1\xee\xbfz/.\xd0\xd3\xb1\xe8?\xfa&b\xf2jE\xbe?\xb4nzpU\xb0\xbe?4\x01\xf5\x93\xb1{\xd7?\xf6*0B\xb5\xb3\xf4\xbfX\xcb\xa1\x80\x87\'\x9c?5\xcf\xae\xb6\x88\x0e\xc2?O\x83"\xf7\xff\n\xfa\xbf\xeeItj\x80:\xf2\xbf;g\x1d\xe1\x15\xf2\xed?\xf8B[6\xd1\x18\x03@\x05\x98\x90\xfd\xaf\x9b\x00@|F$\xfd\xd2\xdd\xe6?\x1e\xc0\x86\x8bCy\xec?HiUW\x93\xd1\xd4?u\xab/<\xe8O\xa8?!\x14\x93\xb4\xf3\x8c\xf6\xbf_\xa0\xa9\xc7\xf7\x8c\xdc?\xf7\xe9D$\xa3\\\xca\xbf\x1f0V\xa3\xb3_\xe0\xbf\xf2\xb8\xa4?z\x08\x04\xc0._+\x10rX\xf1\xbf\x04H\xfe\xde\xf2\xdb\xe5\xbf\xd7;B\xf8\xd9o\xc6\xbf\xafL\x84,X\x9d\xe9?\xe0\xeb\xbe\xe1i\x9e\xe6?\xa8a\x90\xad\x1c\xc0\xb3??.(b\x86\xa0\xf4\xbf\x06\xa4m\x1c)\xb1\xb4?\xa6\xd4\xab\xf4O\'\xe1?:\xd2\xdb7\xebq\xef\xbf\x1c\x0b\xb1\xed\x95\xef\xf2?b\x102\x01\xe0d\xe2\xbf.\xce!\xb7\xcei\xd3\xbfo\xe2\xb9\xc1&\xd3\xc2\xbf\xdbk\xb7}\xd0D\xf1?\xaf\x1c\x9c\x98!$\xec\xbf\x85@D\x95\x03\xdd\xf5?\x1e}\x13\x0f\xf0\xbf\xfb\\\xab\xef\x85\xc8\xa0?J\xfd+,\x86\x1f\xeb\xbf\x13;&\x82\xa4\xe8\xe1\xbf=\x86h\x82\xd3<\xfc?\xac\xf9\xa4\xff\x9b\xa8\xe1?\xc9\x85\x1a\x7fsP\xd3?\xd4\xbd\xfd-\xf8\x17\xee?M\xbcC\xe9\xd4\x17\xfc\xbfrSCTU\x85\xed\xbf\xfaaO\xc3\xd8 \xf6\xbf\xbf4\xa6\xfe\x98\x93\xc6\xbf\xfaf\x9b\xac\x8f\\\xcc\xbf\xd7\x7f\xe5\x0c\x1e\xdb\xf7\xbf{\r\xd0SZ\xc6\xda?\xc4\xce\xe2$\x90\x15\xea\xbf\x89\xce\x14\xa1\xaa!\xf3\xbfZ|@r6>\xd1?\xc37\xfaY\xcf\x98\xfc?\x8c;\xbb\x02\x94\x8d\xf2?4\xaf\xc7\xdb\x05\xf0\xd4\xbf\'\x8b\xea\xca5\xc8\xf2?\xe6\xfb_\xc7\xfe\xbd\xe6\xbf\xd37z\xc3e\x9f\xe4\xbf\xbf\xcb\x80\xcbp\xa1\xea?\xcc\xc6\x88\xab\x10"\xfd\xbf!\xcc\xaa\xf6\xdc9\xf1?0_g\x01\xc7\xb0\xe3\xbf\x8b\xc8\x84\x02}\xe2\xff?\x1c\\\xa9\x02\xff\x84\xe9?e"\x8e\xbcq\xa3\xeb?\x19\xbc\xcb\n\xd2\xc9\xd1\xbfMkB\xf9i\xb7\xe6\xbf\x9e\t=\x9fD\xac\xe3\xbfC\xf7\xadY\x0c\xba\xf4\xbf\x074r\x83N\x89\xc3\xbf_g\xb8ie\x97\xdc?\x06\xd4\x0e\xae\xecV\xf2\xbf|-\xba\xc6\x90\xd8\xec\xbf&>\xf89\xf4\xf7\xef\xbf\'w\xb1|KJ\xf8\xbf8KK\xe0\xe8\x1d\x05\xc0?\x9d\x17\xf6\xb2=\xe6\xbf&\x06\xcc+y(\xdb\xbf\xfc\x86\x97\xd4/\xc9\xc6\xbf\x1fQN_\xe4\x9f\xeb\xbf!\xb5\xc7\x94\xdf\xad\xf3\xbf\xf4\xb3\xfb\xbd\xe2v\xe5\xbf{\xd0\x9a!\xb8\xf9\xf8\xbfv#\xfb?C\x06U\xce\x0cm\xec?+-w\xb7\rE\xf6\xbfl QFL\x0f\xf6?\xf1l\xba\xa4\xaa\xbc\xd4\xbf\n\x93\xa1`n\x1a\xe3?&HgA\xf9\xc9\xdc?\x04\xc8\x1fF\x85\xa4\xe9??\xa4\x1c\xdc\xcd\xc9\xa1?\x7f\xee\xe5gnM\xf4?\xa7\x8a\x99\x8a\xcdP\xb9\xbfd@\xb0\xf8K{\x08@g\n\xcc\x0fy-\xff?\x93\x13x~:\x90\xf1?\xd4l\xc9:W\x1b\xe8?{w\x9c\x07X\x87\xaf?\x92\xb4ys\tn\x9d\xbf\xdd\t\xad\xb3\xc6\xb2\xf6?\xc1G\x82\xc0\xb4H\xe2\xbf\r\xee\xccXRK\xb2?YTZ\x7f\x89\xe6\xec\xbf\x129\xd3\xf3\xca\xca\xfe?\xc3\x9d5\x82dS\xc0?5%\xd1\xa8\xe3\xee\x05\xc0\x15D\xad\xe9\xf9)\xdc?\x8cv#\xd8\xc4d\xd7\xbfV\xda\xd2\xe7v\x97\x02@\x96\xe4\xec\xb1\xbf\x0b\xd9\xbf\xf3\xd0\xd8\xd9\xdb\xf4\xf3\xbf\xb9d!\x98<\x13\x9d\xbf}0\x818\x8dT\xc1\xbf\xc2i\xaa\xec\x99^\xda\xbfr\xc3\xf7\xa6\xc1\xb8\xe4\xbf\xfc\xd3\x99w\x01\x96\xf9\xbf\x15\xe2$\xab\xbek\xab\xbf\xfc\xc5\xf0\xe9\x99\x94\xeb\xbfF\xae\x15\x1a|{\xff?z\x815\x1dO\x99\x10@\xf693\xc2\x08W\xfe?y\xfb\x1bG~z\x01@MU\xf7),|\xf7?\xa8\xed"E\t\x1b\xf4?\xa0k\xd2\xe2\x88\x1d\xf2?\xf7\xec\xc7\xfd\x0b\xae\x01@\xf6\x11\xddo\xf1L\x04@\x8eJ}\x0e\x95\xe0\n@h,\x9f\x835c\xf3?\xf9\xc8R\x8e\x103\r@\x08\x95\xc3\xe3d\xc1\xc6?J\xc8\xf5\xe8\xf5D\xf5\xbfqH<\x87Z\xa7\xdb?\xc0v7\xaf&\x8c\xf6?\x87\xfd\xb9@w\xc7\xb4\xbf\x91\xb7}\xca=\x84\xd7?!q\xe8\\6\xf3\xc8\xbfl9\xcf\xd3q\\\xbb\xbf$\xad\x8d\x8c\x8c\xb4\xd5\xbfg\xce\xaeTUY\xde?-\x85@\xdc_\xfc\xcd\xbf\x90\t\xb9\xc1@\x89\xf1\xbf\xbbU \x06\x041\xfe\xbf#c\x98\x03]\xba\xf2\xbfEd\xe9\x90\xcc{\xd6?}\xb6\xe3\x1d \xd7\xf8?\xe8\x91v\xafm|\x03@\\^\x03\xca\x17\x02\x02@\x9e%\xf8\\\xa9\xcd\x11@\x84\x1f\xb9\xbcU\xbd\xf9?\xffX\x11\xf2\xf2\xbe\x0c@U\x7f\x14\xf7\x81\x81\xfc?\xd4Cv4\x06\x90\x11@M\x98[a\xddy\x03@\xc3\xd3\x9eP\xbe\xbe\x06@65t\x82]\xfb\xf8?\x10\x05\x05X^O\x01@\x8d\x9boe;?\x81?\xe4a:\xa2\xd5N\xe2\xbf\xef\xd5-w\xaf2\xf1\xbf\xd0\xdbw\x9d\xed\xd4\xfa\xbf\xee\x90\xe96G\xa2\x92\xbf\x02K\xeb\xcbxc\x02\xc0y\x16\xd6!bV\xd7?\xa4S\x91*\xff\xa2\xec\xbfkTRfi=\xac?z\xb5\x10\x1a\xb7\x1d\xf0?6#V\x9c\xc2I\xdb\xbfC\x87\xc5\xfe\x8c\x9e\xeb\xbfE\x95f\xdbNG\xfa\xbf5\xf0\xa4\x1c\x19\x15\x00\xc0\xec\x93Z\x9a\xeen\xf3\xbfe\x99\x18u:\t\xbb\xbf^n\xbb\xef\xd5\x15\xd9?\xc2Y\xado\xea\x0b\xe3?\xbdN\xae\x7fv\x06\xfa?"/\xedh,\x8f\x03@\xf9\x8fx|?&\x11~\x80\x0bq\xd9\xbf\x05O\xf5x\xb0\x91\xf8?[L}l8\xa9\xfd?\xd3\xf0\xa5R\xf4J\x04@\xfe\xb7Hjf\xfa\xe8?\xa7\xb7\xb4/\x05\xec\x0f@\xf9\x00\x07\x18"\xa5\t@\x96d\xa5+mC\xfe?o\xe45\xdbLW\xe5?|\x19\xec<\xf1\x96\xf0?/>c\x80\x83\x8b\xce?\xf8\x96\x198\x81\xd9\xe3\xbfL\xc6.f\xff\x18\xf4\xbf\r\xe4j\x8c\x1b\x03\xd9?b\xced\xf1\xc5\x8f\xf3?\x14\xd7\xaf\x0f\x90\x00\xc8\xbf4\xe30+\xc5\xf1\xe0\xbf\x93w\x0cHkF\xec?\x07\x08\x90\xfe\x06\xac\xf4?\xf9\xf4\xc6\x0f\x99{\xf5\xbf?7+\xc4\x0c$\xf7\xbf\xdc\xe4\x04\xd2}!\xb3?\x83\n\x9c\xca\\\x0f\xea\xbf\x97O\xdf&e\xb1f?\x04,}\xcbOl\xe6?9h\xdaW?\n\xf7?\xd4\xdct7\xb7\x86\xf7?\xc6\n\x1d\x0c\xd6\x1b\xe0?\x1a(\x86_Y\x91\xdd?!8\x18>\x12\x90\xce\xbf\xa8-\xf2\x8b\xde\x92\xc1?\xb4\xbcb\t\xeco\xdc\xbf\xc8=:\x84\xa5\x14\xa0?\n\xc5U\x02\\\x86\x04@l\xe4\xf4\x82_\x18\xd7?tB\x0e\x9cC\xb5\xe1\xbf\x86\xa3\xda*+\x95\xc3?\x82\xd7`\x90t\x04\xf3\xbfcZ\xbf\xc2Rg\xc3\xbfe\xb3+=u:\xd8\xbf\xbb\xbb\x9b\x8e\x03l\xf3\xbf(\xce\x92eRE\xe9?\xfb\xd7\xab\xe2_|\xf7?B9\xf6\x054#\x01@fo\xe0\x13\x1d\xb1\xe3?7l\xe4\xf7\x93\xe1\xf9\xbf6w\xa3\'\xc6\xbf\xde?\x90U;;v\xa6\x05\xc0%/\xaeZ^\xe4\xd7\xbf\xa5\x0b\x83\xa3]\x9d\xe3?\xa2hVD\xf2>\xd4\xbf\x08\x8d]&/\xff\xe3\xbf"\x9d\x00\xbe\x0b6\xf6?\xe9\xe7\x0b\x01G\x05\xf3?#\xea\xf4`\xf5\xc5\xa2?\x99|\xa5\xfd\xbdc\xeb?\x8a\xad\x85\x18a\x9d\xe0\xbf\x8cD\x8ck\r\xea\xe1\xbf4XZ\xb3\xd3\xef\xef\xbf\xf36\xf8w\xb2`\xef?\x0b\xc8w\xdbR\xfd\xd9?\xe9\xe0\xa2\xe44\xfb\xcd\xbf\xbd5Al\xe7\x94\xfd\xbf\xb2\x91\xb3q\x13\xda\x01\xc0\x19iD#\x9d\x92\xeb?\x13\x1b\xdc\x9eAp\xf4?)N\x8f:\x97\xa6\xe1\xbf\xab\x04\x17\x90\xect\xd8?\xa3\xa5\x82\xecRA\xfb?\x92\x03\xe0\xbb~\xa4\xe1\xbf\xfevo\x11>\x87\xf9?\xfc\xb3\x1d\x00\xbc\xd0\x01@\xa2\x92\x9f\x97\xd7\x07\xe9?\xc5\xaa\xcdaVi\x02@\xcf\x90\x00\x17I]x\xbf\x8f\xd2\x0bP\xff@\xf0?Xe\x0f\'w\xbb\xe3?L\xc6\xa5>\xe2^\xf5?@G1\x13\t\x02\xc9?<\xa0v\xdey)\xbf\xbfZ\xcf\x8c\xae\'\xf3\xbf\xd4B)\xb6\xd8|\xf1?u\xa7\xcc\xac\x8e\xba\xe4\xbfL4\x03/\xf1\x82\xcb\xbfU]\xd5\xba\x9e\xd4\xf6?\xc4\x7f\x01\xfbgR\xf2\xbf\xdf\xa9MR}\x96\x14?\n\x8e\x7f\xe6\xb0o\xbb?\x07O\xe9S\x17\x1c\xf2?$j;\xbd\x11\xa6\xbd\xbfbb\xbfl\xa6I\xd3\xbf>\xc2\xde\xa5\x89\x95\xe7\xbfUOO\xbbv\x80\x90\xbf\x85?\xe8\xd8\x9d\xe9\xe8\xbf\x00\xd1TKy\x96\xda\xbf\xc7I2\xf9\xba6\xef\xbf\xfa\xbc\x97\x8a\x0e\xb0\x05\xc0\x9f\xect|sD\xf4\xbfg\x85M\xe2\x8ek\x0c\xc0\xf7Pu\xff\x97\xca\xd6?^%\t\x1d\x90\xf1\xfa?\xf9/\x03\xbd\r\xb8\xf1\xbf\xbe\xc4\xdf,\x08\xa5\xd0\xbf\xc2\x0cA\x91b\x0f\xe0\xbf\xb3\xa3\xcb\x93\x11a\xcb\xbfh\x9d\x0c\x89\x02\xbb\xc0?M\xab\xd1\x13\'\xea\xb8?Q\x08\x06\x92\x10<\xd5?\xbd}<[\xf9Z\xe7\xbfz\xeb\xb8>\xb3\xa2\xc8?\xd1\xcb\xb8\xb4\x94\x89\xe7\xbf/%\xdb\x89p\x0f\xf4\xbf\xd4\xfb\xbdsnU\xe6\xbf\xd3L?\x1bi\x99\xdd?\x0c\x81\xe4<\x12\x17\xfc\xbf\x1dC2\xae\t\x97\xfd\xbf\x18\x8e\xc9\x96\x14\xe7\xcc?\x8f\xd1{\x9f\x10{\x02@\x072\x83[\xa0\x80\xc3?9.\x8c\x12\x85\xf5\xfa\xbf\xa8\x1c\x8e\xb7\xf1n\xe9\xbf,6\xeev\x9b\xda\xfa\xbf\x82m\x8dh\xe7i\xf1\xbf\xed\x95\x0cJ\x8b\xe5\r\xc0\r\xdc#\xf3\x98\x82`\xbf\xbe\x07\xb9\x16\xb7\xb1\x0b\xc0\x11\x06\x13\xc6\x10\x8b\n\xc0o\x7fh;\xa6\xf4\xc9\xbf\x1fo\x82\x0fN:\xe2\xbf\x98\x93\r\xe6[\xdf\xc6\xbf\x8e\x15=\x8aF\xaa\xb6?\x17\xf4\x85\x1c\n\x7f\xec\xbf\x9d\x11\xb7n\xa0I\xd9?\x16\x86m\x03\xd1\xa8\xf0\xbf\x8e\xd8\xc1\xa2 w\xf0\xbfg\x11\xddo\xe5\x8f\xe5\xbfh\x08\xca\xa3\x12I\t\xc0h\xda\xd22\xf3\x8e\xf3\xbfe\xc3\xf9\xbe\xd0=\x08\xc0\xc2\xae\x82\x86\xe2R\xed?\xc3\xbe\xe1\xa0h\xfd\x08@6\xa2\x10\xf9J\x06\xd2?\xf4~\xdb\x95\xc8j\xd0\xbf\xe9%\x05\xb4"b\xd6?r\x89+G \xba\xf3?7?\xa1y\x88M\xe1?\x02\xbf\x8bf8F\xda?%5\x0c \xaa\xf9\xc1\xbf\x9f\xe4c\x18Zn\xf7\xbf\x95u\xfe\xeb_5\xcc\xbf\x06\x99\xb0\x9c\xbb\x97\xd8\xbf\xac\x07\x0b\x92\xf0M\x02\xc0\xf8\x9cZ\x16\x93\xa9\x01\xc0\x91$\xcd\xbc\xaad\xe1?\x02\xb8\x9c\xea\xa7W\xd8\xbf\rd\x8e\x97\xa0y\xea\xbf)\xef\xd6\xab\x00\xb5\xef\xbf\xb8\xe0\x87@\xf1\xe5\xd5?\xd4\xf0\x94O9=\x01\xc0-\xeft\x06\'\xa4\xe0\xbf\xb2\xd5\x94\xe9\xa0\xff\xfe\xbf3\xc3\x1b\x1f5?\xf4\xbf\xf4\x19\xc6\x9aE\x0c\xef?\x1e\xebuAL\x95\xf1\xbf\xab(\x88\xc3\xc2\xa8\xf5\xbfQ4A1}\xff\xfd\xbf\x95\x7f)\xd4\x8e\xf4\xbb?1_>\xb7\t\xff\xca\xbf^\x83\x17\x84\xd2\xd9\xec\xbfJE)%u.\xf1?\xda!BY\xbaB\x94?B\xce\xac\xaa\x05\xca\xb3?\xd6\xf5\x15\x17\xf8\xac\xf7?\xe6\x1e\xb39\x14W\xf1\xbfw\xfe\x0f\xaav\r\x02\xc0\x12T\xf4-\xf1e\xc4?\x7f\x0c\x8c\x00\xb3\xe0\xfa\xbf\x91{8\xf7\x84|\x05\xc00M\x10\x98\xdb\xe0\xfa\xbf=X\x80\x1b\x18\xc5\xdf?\xa5\xb5\x8c\xba0\xe9\xe4\xbf\xa9\x1cub\x8a\xb9\xf5\xbf\x81\x8e\x0c\x1f8\xdc\x00@\xc8\'000\x89\x02\xc0\xf8Ux!W\r\x05\xc0\x82S?\xeb\xf5\x98\x06\xc0\xa0\xb5\xdf\x17\xf3[\xf4\xbf\xefU\t(\xe1s\xfc\xbf\xe9\x93\xc0\xff\x850\x07\xc0\xebny\xb8h\x11\x08\xc0\xff\xcd\x93G\x1at\x05\xc0\x16\xd3\xce\xab\xc2\xa8\x05\xc0*\xca\x8e\x807\xbc\xf9\xbfa\xfd\xdb\x00`\xfa\xee\xbf\x1c\x18\x13m\xcf<\xff\xbf\xa6\n\xcaJ\x7f\xe2\xe4\xbf\xe2\x03\xb2\x04\x99\x87\x9b?F\xc2\x18{I\x9a\xf9\xbfZ\xae\xd6\xe0 \x86\xf0?\xf7\x18R\x80\xf7Z\xd2\xbf\xa0\x95\x80ow5\xc3\xbfgL\xb1}\xc2\x99\xd0?\xca\x82\xfb\xd6\xfd\xca\x00\xc0\xa8JJ\xc6\x7f\xf1\xfe\xbfB\x16\\\xbc\x1b\xeb\xe4\xbf1\xe5\xb4\xa7\x1b7\xe8\xbf\xd6\xcd\xad\xf6\x08\x8d\xc9?6\x8ar\x83\xcc\x8e\xf0\xbf\xc0\x82\xe9D\xf8\xd3\x94?h\x1a\xbb?\xb84\xe7?\x1a:\x9cQt\x15\xb8\xbf)\xd4\x90_\xc6t\xe3\xbfC\xc4\xe8^\xa2U\xf8\xbf{\xdc\xf8SM \x06\xc0\xcc[\xccw\xde3\xd1\xbfnA\x14\x04[\xcc\xcc?\'q\x19\xd3[\xc8\x02\xc0\xebZe\xffN\xa5\xd1\xbfs\xac\xbd}\xc5\xa0\xed\xbfm_\xea\xd8\xbd\x1e\xf1\xbfu\xeb\x18:\xb4\xf6\xe0?\xd1\x8b>wC\xfc\xf2\xbfIF\x8bU\x8a\xc1\xec\xbf3G\xb5a\x18!\x05@j\xa9Q%\xa4\x93\xe1\xbf\xd3\xdd\xa4%\xd2\xcf\xf3?t\x97\xed\xe0sv\xe4\xbf\x03\x1f\x0c6\x86\x18\xfe\xbf\x98\xc6_M\xe5\xa7\xd8?\xfa\xca\xeb\x82\xee\xc4\xf3?nxa=o(\x02\xc0`\x01/\x14F\x86\xea\xbf\xb2^\x8c8T\x07\xfa\xbf\xa0\xfe\xca\xe5\x16@\xd8\xbf\xaa\xc8\x17\xb7\xe8X\xe9?\xc0\xa1\x17\x92\'\x05\xe5\xbf]\xb5c\x83\xd2\xed\xd1\xbf\x0be\xf5\x8b\xd6\x07\xe5\xbf\\\xaa\x01\xda\x17\xc1\x02\xc0\xa0kCj\xd0\x95\xd8\xbf\xe1\xfd\x84\x1d\x92\xdb\xf0\xbf%\xab\xc1\x13\x8aq\xf3\xbf\x80\x8b\xd2@\xcc6\xf0\xbfV\xf7\x1d\xfd,6\xb8\xbf\x9f;\xf9\x9c7\x8a\xe5?\xdd\xc5\xdaS\xe9\x86\xe0?>\xfd?\xc8+L\xe1\xbf\xb1\x99\xeb\x97\xcd\xd0\xad?}h\xa6\xa0k9\xe1?\xe7\x82\xd7\x8e\x1c\xd8\xe5\xbf\xb5\xbd\x0fCE\xc6\xda\xbf\xa9\x92\x91\x14\x9c\xd1\x8d?y*XP\'\xca\x04\xc0\xa6\x15\xb1\xbf\xd6\xf0\xf0\xbf\xaa\x90;\x9b0|\xfd\xbfD\xf2\xe4\x06\xa2\x1e\xc1?~\xb6\xbb\x1a\xe9#\xb9\xbf\xe4G&\x03G.\xf5\xbf7\x06\xca\x83\xe1x\xf0\xbf18g?b\xa7\xf8\xbfg\x12RV\xbb!\xc2?J\xf8u_\xd0\xc7\x8e\xbfj\xa9\xb9\xa8#M\xc6\xbf\x8c\xeci\'aW\xe0?<\xbf\xee}\x80\xcf\xa6\xbfk\x81\xf6\xb1\x82\x8a\xcc\xbfu\xb6q\xc4j\xd7\xf7\xbf\xd6\x11\xf9\x0ey\t\xfa\xbf\xf5e\x84W\xe1\xde\xcb\xbf$\x1b\x10e\xf8\x85\xfa\xbf\xe2\xe6\xba\xf8Y\xdf\xe6\xbf\xd7\x17cS\xb05\xcb\xbfu\xc8p\x91c\xa5\xfa\xbf\xdf\xa5\xec\xb5\xa0\xbb\x01@\xbe\x99\xd1\xa8y!\xb4?z?\xc3nq\xc9\xd8\xbfO!/Z\xdd\x8b\xd0?\xe7_M\x17.k\xe5\xbf.\xe1U\']h\xc1?{\x18\xb7\x85[\x9c\xc3\xbff\x8e\x02\xc2\xf5g\xcd\xbfG\xfc\xbe!\xbb\x06\xb3?l\xdd%\x92\xce\x8a\xa3\xbf\xc9\x0c\xc6\xc9\x0b{\x01\xc0\x89\xc0!\x8a\x98|\xf0?\xa8\xd9\xc4L\x14\x12\x05\xc0\xe6\xe8\\f\xba\'\xe2\xbf\xbf\xc6\xad\x11\xa6\xa0\xd8?3\xd0\xcf\xdd+\xfd\xf4?\xde\xf0\xff\x15\x19\xcc\xf8?\xec\xe3\ng\xacj\xe5?\x1a\'\x14f%\x81\xee\xbf\x9f\x04\xf5y\xefI\xd4\xbf\x0c\xebv\ra\x17\xf9\xbfJ\x89\xe5 `m\xf0?\x90\x04I\xa4@\x8b\xc3\xbf\xa2u)SL\x7f\xf2\xbf\x80a\xd5\xa8\x83\xd0\xe3?\x94\xc8\xa9\xda\xd79\xf6\xbf\xd5\xf5C\xe01@\xbc?hg\xd5u\x03\xea\xf2\xbf\xeap\x83\xad-\xcc\x00@9M\xca\xe4\xe3\xa4\xf5?\x9ch\xa3\xe8\x88\x8f\xdf?\x0fM\xf1\x86\xc5#\xe7?\xef\xcbV$\xa67\xa1\xbf\x8f\x9c0\xab&[\xd0\xbf\xe1\x1bo\x96\xcb\xa9\x00@\x14\xbe\xfe B\xe4\xe3?+t\x7f\xdb\x1f\xb1\x90?\xb2Z\xbe\x94\n\xe3\xed?\xfa|\xb8L\xdb(\xfc\xbf\x19u6\xac\x87\xf4\xb5?\x81\x82\x9e\xb5j\x81\xa3?T\xe7d\x0fT\xff\xef\xbf\x18\xc5\x94\xd2\xd9>\xee?\x98\x10\xb1W2\xcf\x05@"9\x13\xd1\x98\'\xda\xbf\xda\xcd\t\x7f\x18\xc5\xe4\xbfs\x84\xce}\x1d>\xfc?\xa9\x99\xa1\xc4\xe8\xeb\xf0?v\xa9\x8c\xd0\xca\xe4\xf1\xbfvH\xc9\xc3[\xa1\xf0\xbf\xf8QCX\x81\x88\xc6?\xd7\xb9$u\xc5[\xed?\xe9\xbcZ\xbe\xa1&\xf0?d\xb4\xf5X\x82t\xe1?\x9d\xdc^pnO\xd4\xbf\x98\xfc\x12\x82)\x8d\xf0?-\xf5\xb0\x91\xb4h\xd8?\xaex?\x11\x86\x8a\xc1?\xce\xe2~<0\xed\xde\xbf\xf6v[\x8do\xc9\xfb?\xf0w\xc92\xd3`\xde?]\xbf\x80\r!\x87\xe2?8\x8b\xcc\xc7\x12\xa0\xef\xbf{D\x9cS*C\xc5\xbf\x06\x87[\xb1Be\xea\xbfI\xed\x92K\x83B\xdb?\x1bw\x1e\xe6\x11\x13\xd9?B\xcb\xb7g\x08\x0f\xb5?\x02u\x89\x9a\xa2`\xf5\xbfdd"-\xc5\xe6\xc4?@W\x93g(D\xe3?\xb9D\xa7\x19\xf8\'\xfa?\x9f\x14S/\xee\x92\xf1?\r*\xc5\xb9\xee\x06\x04@n\x04Z\xa5k\xff\xc9?2\xb4S>\xbe\xc6\xe7?\x8c\x9c\x123\xde\xee\xe4?\xadL\x05H\x1d\x15\xd4\xbf\xdd\xa1\r\xe7\xefx\xee?\x16kl\x9f~\xe3\xfc?\x98 \x9c\x10\xcc\x9a\xaf\xbf$TM\xc55]\xf3?\xab\x91\xb9\xea\xba\x94\xfb?|e\xd6\x9f\x01>\xfa?\xd16\xff\xa5\xcf<\x02@\x85\xe36#2\x17\xe6?\xf2d\xcf\xe2TF\xe1\xbfA\xaa\xca.<\xd2\x00@\xa8\x87\xf3\\\xd1\xad\xee?\x8aA\xc3\x12\x04\x90\xe0\xbf+3\x0e\x97A\xbe\xd6\xbf\xee\x008\x0e@\x14\xd8?\xe7\xc1\xf1\xee\x04\x8a\xf0\xbf,\x04Z\xa9\x95\xd6\xc4\xbf\r\x06\xee\x9fg\x02\xe4\xbf$&\xc40*\xfc\xeb\xbf\xb3\x02\x83X\n\xcf\xf7?4\x93\x99\xa2\xb7$\xef\xbf$\xa5\x19\x9a\xaa\x8c\xf1?\xe6\xc2Uk"q\xb3?&f\xb9W\xbf\x99\xdc?\x02\x0c:\x88\\\xad\xfb?O|\xaf$,\xd0\xf4?\xac.\r\xa3Sk\xe8?\xd9\xf5\xe11]\x9a\x99?.\xa8\xd3?\r\x96\x02@\xa8\x89J\x84\xf4\x00\xd5?\xd4\xd3\x91\xef$\x08\xfd?\x93i\x06V\xec\x87\x01@\xa1\x19.M\xc9\xdf\xe7?\x8a\x0cB\xa8@|\x91?\xc3b\xb4\xe2^\xb7\xdc?_\x84\xffy\xca\xcc\xeb\xbf7\xb3H\x04)\xc7\xf1\xbf\x1ef9G\x10\xb8\xf3\xbf\xe6[&\xd4\xbb\'\xf5?m\xef&/A\x1b\xfb?THL=Q>\xf3?k\x86\x8bt\'\x0f\x04\xc0\xd9\x87\xad\xab\xdf^\xd3\xbfx\x18a\xb7X\xb6\xdd?t\xe7\xb4\xd5\x96\xd3\xf5\xbf|\xa1\x16b\xec\xc1\xea?\xad\xeb\xfa]\xab\xf3\xf0\xbf\xea\xa4$\xdd\x1b\x17\xd3?\x07\xc8:\x9b\xe3j\xef\xbfi\xd6\x1d\xc5L\xd8\xda?\x00\x05\x021\xeb\xcd\xdd\xbf\x1e\x80\xe1+\xdb\r\xf4?\xd8\x10\x913\x11X\x00@\x85ef\xfb\x0b\xde\xfa?|\'>\xc7\xbe7\xf3?\x80N\x1c\x9b\xfak\xe8?&\x1e\xc8\x90i\x04\xd2?\xfa8f\xf8^a\xf1?(9\x8c\xd4\xba\t\xdb?\xfc{\x15\xb0Wm\xd2\xbf\xb1\xcd\x10\xe5/\xd7\xea?\'\xa9\xda{\xdd6\xe1?c\xcc\xbf`i@\xd0?\xc9\x85\xb5\x90*\xc8\xf1?\x84\xa2\x04\xef;\xed\xfa\xbf-dvU\xbf\x08\xd5?Xc\xf4NdJ\xd2\xbf\xd4\xcd\x9b5z\x86\x02@\xdfe\'\xf9sJ\xfc?g\x97\xa7\x0f\x84\x08\xd8\xbf\xd4\xc3d\x808\x96\xd7?\xc3\x89\x99\xfd\xe6\x06\xd3\xbf\x8a\xe8\x94\x9c6\xfd\xfa\xbf\x04\xacJ\x90\x82x\xf9\xbfe\x8a\xd7e\xc6\x8a\xe6?\xe7\xd0\xfa\xd2;\x83\xcf?+\x1dqV\xc3\t\xec?F\xba\xe4\x85O\xbf\xf5?\x0eb\xe1\x82\x9at\xd6?l\x02\xebh\x8a\xb9\xad\xbf\xe1-k\x0bN9\x08@Wk\x9e\xca8^\xfd?|\xae\xc4[.\x0b\xfa?C\x1b4\x1b\x85\xe6\x00@q\xbaK\xa6\xb9g\xf6?#\xa9\xcdX\xcf\x97\xff?\xd5$\xb8Y\xae\x87\xdc?5\t9\xbd\xd1\xd5\xe6?&\xb3\x86\x10\x1c\x06\xea?O\xb1\xe5]\xdd\x0e\xfb?\x1dz\xcd}\xa9\xc8\xe5?\xd7P\x02\xfe\xde3\x01@\x8a\x88\xad\xb2fP\xf5?I{\xbb\xd4ih\xd2\xbfY\xf6\xda\xda?\x0e\xf2\xbf\x11>\xc5\x99;r\xf7\xbf\xaa4\xc4x}\xb9\xe8\xbfy\x93\xc3N\x04B\xe4?W1UDs\xb5\xcb\xbf\xf9\xf1\x15k\xaf\xc8\xe7\xbf\xd4\x9a~\xb0\x80v\xed\xbf\x15\xd0\x88\xdf\x99\xbd\xee\xbfJ\xe0(\xe2\x9e\xe6\x04@\xd8\x05\x83\xbe\x8a\xd1\xf1?\xf6\x82\xdc\x11h0\xed\xbfK.ly7\xf4\xda\xbf\xcb\x0c\x98\xe3\x86\x82\xf0?jP 2\\\xb9\xd2\xbfZ\x97\xfcI3\x1a\xff?\xc9m\x04\xfd|\xc1\x90?\x98\x91\xdb]\x97\x14\x00@\xd2\x8cE\xf0a=\xea?\x1d\xfd\x85\xbfx\xc9\x83.\xcb\x13\xe7?\xf4To\xd1\xb3\xd0\xf0\xbfoJ\x8c\x89\xc2\xca\xee?*7J\xe1\xf9\xf6\xe6?\xdb\x17\xa7\x81*\x80\xe0?"\x81v \xfbq\xf6\xbf`}\xd5]\x87\x84\xbe\xbf\xef\x91\xb8}\x9c.\xe0?\xe6\xb5\xcag\x88&\xeb?\x15\x7f@\xa7F\xfd\xfc?E\xf7N`\xd4s\xda\xbf\x80\x18\xee\xd7G\x87\xdc\xbf\xf2\x87\x1b\xecDL\xf6?\x99&>\x0ch\xc8\xe7\xbf`\xf5\x7f\xc1,\x8b\xb6\xbf\x18Nucj\x1c\xf5?{\x04Q\x11\xeb\xdc\xfa\xbf%MP\x87\x01X\xc4?}>\x8c\xa8k\x8a\xf3\xbfaF\xf43q\xa1\x04@\xd5\xb4v?\x11\x9c\xd6\xbfEM\xbe\x96\xbcY\xdd?G\x85\xd8\x01\xe9*\xcc\xbf\xe3?\xe6\x12\xaa{\xd6?\xcd\xe8_6\xbe\x1c\xec?\x1f\xad7\xaa\xf7S\xed\xbf}\x89\xfas\xecn\x01\xc0\xd9\xe6\xc5\x17-^\xf1\xbf\xe4\xe9\xdb]J|\xda\xbf6\xc2\x8bo\xd2\x0b\xe7?&\x93\xbesp{\xf2?\xe0B\xf0\xf9\xec\xaa\xd6\xbf0?\xc3\xe3B\xb7\xe7?\xd4\xa4lV\x0ez\xec?2\x85\xbe\t\x81\xad\xd7\xbfj\xf1\n\xdb\x06C\xe2\xbf\xc6r_J\x01\xad\xe8?\xaaA|O\xab\xfb\xcc\xbf\xb8\x1e\xa4\xb4\xdf(\xe2?\x98H\x14\xb4Z\x81\xeb?\xd0\nu\xcf\xab\xbe\x95\xbf\xbfJ\\y\xee\x95\xf9?\xe9\x95\xa7\x1eMA\xbb?\x82\x96\x10\xba\x16#\xbd\xbf\x08\x1f\xfa$;\x11\xba\xbf\x82$\xeb\x13\xecv\xf3?\x89nb\xaa\xb5\xea\xd7?\xc104P\xcd\xda\xc4\xbfffCp\x13\xde\xe1\xbf\xc14\xb6\xca\xc9\x95\xd7?\x8a}34%~\xec\xbf""^\xaf\x16\xcd\xf5\xbf\xf3+*,\xfc)\xee\xbf\x86\'\x95\x81\x81\xe2\xf2?\xf6\xf63\x83\xddc\xca?S\xbe\x03z\x1c\n\xde?q\x95\xf8\x8d6u\xf0\xbf\x7f\xb9\xe7\x0c\x95\xc5\xeb\xbfC\x84\xd4\xbc\x83\xac\xf0\xbf[\xb3~7\x96\xc6\xf4?$3\xb3C\x13~\xc0?4\xe4\x88\xf0\xe9\xa2\xbf\xbf\x9c\x80\xc5n\xe2\x1c\xe5?\xd38e\xe5\xfcH\xf4?\x06F\x053\xe8\xd6\xdd?\xabv\xb4\xd9\xcfI\xd1?\x81\xc2E2\xf9\x17\x9d\xbf\xa7\x08\xdc\xf1\x98\x8b\xe9?gI\xcc?\xcb\xc7\x00\xc0h\x03L\x06\x1eD\xec\xbf\x01\xb0\x95p\x04I\xe7?\x86\x19\xbe\x9d?x\xec\xbf4\xca\xdd\t\xeao\xd3\xbf>`\x12m\x98\xd0\xf3\xbfS\xb0\xb0Ey\xbd\xe6\xbfF{qRt\\\xbd?\xca\xf3ZZ\xe1d\xec?\xfe`\xf6\x8f\xbc\xd0\xd0\xbf>5[W\x01\xf2\xcf\xbf\xd3\xa8M\xfc\x1e\xe9\xfc\xbfv\x9c\x99G@|\xd7\xbf\xeb\x18\x9eB\x7f\xa7\xce\xbf5\xe5\xbb,\xefr\xe5?\xc0\x9a\x16\xc7\x03\xdf\xd4\xbf\xc3,\xd7FkB\xe9?\xc9\xcd\xb4\r\xd9I\xc5\xbf\xd6\xcd\xdc3\xc2\xca\xc4?\xe6\x87Z7\xd9E\xca?oY\xfeO\xc9)\xdb\xbf>}V,\x82\xe9\xe7\xbf\xc0D\xe7\xde\xfcN\xd5?\xe4\xcc-=\xda\xac\xfa\xbfk\x8d\xa5\xeal \xe0?\x03\xdco\xc0E\x11\xca\xbf\x07\xc1\xbf.\xf5w\xff?\xfb\xd1\x894\x12G\xc2?DW\x83\xef\xfd\r\xe6?\x0c\xbeM\xe4\xd4-\x83\xbfVFh\xc6\xb6\xe8\xd8?\xc6\x8b\x11\x9bF\xea\xb6\xbfCOL?JO\x02\xc0\xe4\x04h`\x98\xdb\xf2\xbf\xda\x14\xff\x01\x06\xb1\xf1?\xe6\x1c]=\x1c^\xd4\xbf\xf4\x0f\t,\xb4v\xd3?\x7f\x1a\x11\x14dP\xac\xbf\xd1\xb5h\x82\xbd\x10\xe3?\xc7\xc9c\x05YR\xf0\xbf\x19iC\x11\x87\x0b\xf2?a\x7f\x9e\x03\xd6\xfd\xc2?/\xd1\xbd4"\x8f\xdd?\xdf\xc8\x8f\x15{\xab\xe1?\x04\x85\x16\x84b\xc8\xe8?m\x85@\x8d\xce\xaf\xea?\x02L\x92\xc2$\xe7\xf8\xbf\x7f3r8\xd4\xb4\xea\xbf\xb8\x03gi\xe6\x14\xd5\xbfZ\x9a\xbd\x16\x10G\xe0?\x96\xb7x\x8fD(\xd1?i\xb9\xee\xa6\x17\xac\xdd\xbfu\xb9\xa6\xcbS\xb2\xb0\xbf\xc2\x83"ou\x85\xdb\xbf\xbf\xbfK\xdfe,\xcf\xbf\xb8\x94\xd1N\x97\xf5\xf5?%h\x84\xc9\x0et\xdb\xbf\xb0m\x16\x9f\xd2\xb5\xaf\xbf,\xb8n-\x07m\xee\xbfw\x0c|\xa9\xa3\x01\xbc\xbf\xed\xf9w)y\x1b\xfd\xbf\x94\xbf\xf2\x91\xc8$\xdc\xbfPN\xfe\xc9\xe1\x9e\xe3?S\xfe\xad\x1fR\x1a\xec\xbf\xd3\xb1I\x0e\x95\xbd\xf4\xbf\x9a\x01\x18\x97d\xe6\xdb\xbf\xb5!W\xdbSO\xe7?`;d\x8fw\xcc\xdb\xbf\x1c\x92l\x8c!\xfd\xe3?|OL\xb2g\xedx\xbf\xe6\x02T\xba\xd7\xc0\xee?\xea\xc5\x95\xee\xeb\xc2\xde\xbfN\xe8\xb812\x94\xd2\xbf/\x13\xdd\xb65r\xe1?\x88\x9cNJC\xc6\xf1?7\x1b\xb9\x8a\xd3\x16\xd0?\xfc\\\xebd\t\xb0\xe1?^9h/0\\\xeb\xbf\x11\xb5\xf9\x11mS\xf4\xbf\xd4\x06\x8ey\xf4\x1f\xec\xbf\x91\xed\xbeA\x83k\xca\xbf\xc0\x8c%\x82\xbb\x8d\xe6?\xdd\xfc\x1c\xa8\xf6J\xe8?\xf6Ed\xc5v+\xfd?b\xcc.\x8f\xfe\xec\xd9\xbf\xdb\xd4\x89\rI\xb2\xef?\x07\xd6*\xe0=\xef\xf7?\x9bf\x9f\xf0\x98\x1e\x95?\xd8"\xd4\xdd)3\xf6\xbf\x9d\x0c%dN\xaa\xfd?\xd2]\x12\xd7D+\xd2\xbf\xa1p9\x06F@\xfb?\xa06\xe7oT\xc6\xe6\xbf\x83\x1d\xfd\x83\x9do\xc6\xbft7\xe6U\xd3\xf5\xde?\xb7\xa6\xc9,\x82\xf3\xe5\xbf\xa1%\x90R\x9e\x80\xfa?\x81\x80\xd3\'\xb4M\xf2?6\xc0\x8c\x7f\xbc\x9f\xe9\xbf|\x93[\x90\t*\x00\xc0*O\x05\x11\xceZ\xfa\xbf\x15\xc2\x9f\x101\xbd\xf5\xbf\xeejV\xcf\xfd\x11\xf9?}\x04\x14\xd3\xecY\xa4\xbf\xc4\xf35\xc4q\xd0\xe1?T\tu\xee\x13\xa0\xf3\xbf\x17\xa7\xee(\x16\xdb\xdc?\x8fY\xfb^\xbeT\xf5?\xf0\x91\x0c\xd6\xcbR\xd8?\x05\x08\x0e\x1b\x84\x84\xf0?F\xe9\xe6\x82~\x81\xfe\xbfe_C[_\x07\xeb\xbf\xcc\x18\xf1U\x91\x83\xe6?>\xb7\x03\xd6\xbc%\xcc?\xc2\xa2|-]\xc3\xee\xbf\x85\xacM\x9c>\xfa\xf0?\n\x06\xb5\x84A\xfa\xbd\xbf\x19J \xa9\x1a\x1a\xd3?\x97dic\xd0M\xd4?c\x1c8\xc5Da\xaa?\xcf\xa6\xfbB\x14\xb1\xe7?\x95\xc3C\x04\x8d\xa4\xc4\xbfJ\xf5J\x1ap$\xdc?\\\xb4i\xff\xf1\xd3\xbc\xbf\x9f\x8a\xfb7xG\x08\xc0\xf7\xc7\x15.\xa1\xfb\xf6\xbfF\xd7C\x1d\x98\x97\xd4?7H\xa7\xab/\xbf\xf7?\x96\x81w\x91\xc0\xb5\xee?\x96J\xbe\x06\xee&\xec?AO=3\x12\xc5\xe0?\xd0\xcb\xe0;e\x12\xde\xbf\xd9Y\xa0\x8b\x93\x16\xd2\xbf\xbc\xe1\xf1\xc4(\xb6\xf2\xbf=\x87\xbf\xb7W+\x01@@\xc35\xc1\xb7.\xdb?\xd6)\x1e\xdb1\t\xd7?\x913\xfd\xde\x87r\xdc\xbf\x90\xbd\x0e\xd5\xeb,\x00@\x7fp\x8b\xf5ek\xe7?\x1dg\xbd\x90\xfd!\xe8?2\x90\xc1\x98\x1b\x8e\xb6?\x00\xd0`\xb4\x15\xc0\xd3\xbf]\x04\xa9,tC\xcd\xbfA\xa6"\x12M\xfc\xf6\xbf\xe3?\x01\x86\xcbG\xcc\xbf\x91\x9f{\xf29\x9a\xe1\xbfb\xe1js\xde8\xe5\xbf\x10p\xcex\xc4h\xf1?#\xf54\xcf\xd5\xb4\xf1\xbfwG\x05\xaf\xa2P\xe9\xbf\xc0\xed\xdbMx8\xe4?\xd9N\xe9\x8a\xfb\xb3\xcd\xbf\xda\x18+e!\xdd\x07@W\xee\xba!\x93\xa6\xce?Z\xa6N\xbc:\x89\xc7?X\xe4\x80wj\xa1\xd0\xbf\xc6\x9dv\xa8\xca\xb0\xc9\xbf\xef\x19L\xd8}\x8e\xee\xbfR\xbd\x03\xfe\x03\x9b\xeb\xbfz\xae\x8d\xa4\xf5\x02\x02\xc0\xec\x91XF\x90\xf9\xda\xbfm\xac>G\t\x94\xd4?\x10\x19\x8cH\x80i\x01@\x8a2\xcf.\xee\x0c\xcc?\xe9\x87\x90\ne\xf9\xdd?\x8b\xef\xdb\xd4\xd4b\xc7?Y\x0e\xb8\xf7\x05p\xd5\xbf\xc7\xec\x12S\xab\x85\xc2?\x88\x83\xff4\xa0\xaf\xd3\xbf\xb7&\xd2\x84\x90^\xd0\xbf\xa9\xdcC(\x13\x1f\xe3?\xccG\x1f\x19\x87\xfc\x03\xc0\x04\x1f\x88wp\xcb\xe9\xbfD^-b<\xc1\xb6?m\x99\xc8VA\xef\xec\xbf\xb6\x8e\x0c}<\x9d\xc7\xbfj\xe4\x9a\xf7\x1c}\xe3\xbf#\xfb\x05|\xd9L\xe2?(xR=\x9f[\xe0\xbf\xe2\xeb\xefU\x9eU\xf1?\xb5\xa7%\xe0\x823\xc9?x\x17\x91\xbc\xc2\xc5\xf3?d\xbd\xf0\xa4e\n\xd7\xbf\xabd\xfb\xb1/B\xf9\xbf\xc6h\x94\xf9\xf1\xa5\xf0?\xd1\xbek\xd5\xbc\x17\xf7?\x98\x13\x99\x98L\xe6\xda\xbf\x9e\xce\x13G\x8aN\xe7\xbfR!#\xb0,I\xd2?Pw\x0b\x0c\xe5\xf1\xda\xbf\xd5\x95\xb7\xc118\xf5?\xa7\xc6\xb8\xa6\t\xa9\xda\xbf\x00\xbd\xa1L5\xc5\xf0?7\xaa7\xa1+\x83\xbd\xbfj\xf0\x89\xf5\xc5\xb1\xe1\xbfZ\xdb\x98\xf4U\x1a\xda\xbf1\xa2\xb6]!\x81\xfd\xbf(\xd8RC\xc7&\xc1?w\xa8\xb7\xdc\x14_\xe0?\xa7\\t\x16\x851\xe9?\x90\x91\x86\x8c\x96\x85\xc2\xbf\xe5\x16\x905\xddp\xd2\xbf#\x97\xd8ZA\xfa\xe1\xbf=\xca\xb3I\x0e\xe5\xf3\xbf\x83\xe7\x11\xf0rD\xc4\xbfg\xea\x11\xec\x8d\x98\n\xc0\xfaR\xdc\xd6\xd5\x7f\x05\xc0\x1bn\x00B\xe4\x02\xe3?NEK,q\x9a\xd1\xbfO\x9a7\x82\x95\xfb\xd0?\xbe\xafQ3\xde\xd8\xf8?\xdf\x03\x1dA\xb8\x85\xed\xbf$<\xbb\x89St\xe3?\xeep\xdfhf\xdc\xf3?\xb0~Y\x89\xe7\xc3\xd4?\xc1\x9f-\x98\x93I\xe0?\xe5\xd6\x82\xd17\x8f\xf9\xbfm\xf1\xc2:\xbb\xb4\xef?p\xc7\xb5K\x1d\x83\xd1?\xd4\xa5\xe7E\xb7\xc6\xf9?-O\x0e\xa9on\xca\xbf\x89A\xfdmzu\xf4?\x8a\x0e\xf6\x08\x97\x1b\x03@\x10\r\xbb\x08\x13\xee\x91\xbf\xfeg\xbe\xe7\'\xc8\xd2?\xb4\rK\x15F\xfd\xeb\xbfN\xf2M\x9d\x83"\x00@x\xb2-\xfd\xab\x9b\xbd\xbf\xbcx\xee!\xea\x1f\xf8\xbf0\x1a\x11V\x1cf\xf5\xbf\x81\xd3C\x0bM\x0c\xee\xbfX\x12[\x06\xf8\x7f\xe1\xbf\xae\x7f\xb7\xc3\xa2u\xf5\xbf\xf2\xd0{\xf1\xa0\xf8\xee\xbfi6\x13\xa4\xc7%\xde\xbft\xa7\x19\xfa\x9b\x94\xd0?\xa8\xcf)R\xe7\xd3\xf3?\xbd\x9c\x13\xab\xb9\t\xe1?\xaf\'\xba\xe0\x8f\x82\xdc?oD\x9a\x82"f\xe4\xbf\'d?\xc9\x98\xbc\xf0\xbf\xd3\xdaq\x07\xf5\x8d\xe9?\x7f,BMA\xc9\xd6\xbf\x9c\x17\xe5D\xc8N\xc3\xbf\xce\xc9\xee\x96\xf7\xd7\xe0?K\xd7\x9c\x96\x14\x96\xf3?\xd5\xcdA\xee\xd0\xbd\xcc?\xbfRO\x95\xd1 \xf2?\x0bBy\xb9\\%\xf4\xbfN\xac;\x17\xe06\xdb?O\x9a\x8f\xe5\x9c\xda\xb6?\x9eU\n\xcf\xd6s\xe1\xbf\xf6\xc8\xc7\x1dX\xa1\xa0?\xea\x8co\xe6\xffC\xf4\xbfs\x92o\xa8\xb5r\x07\xc0\x03\\\xf4D*%\x0e\xc0,rA\xf2\x10\x17\xfc\xbf.EF\xcb\xc2\xb5\x0b\xc0D\xa8\x152p\x0e\xf9\xbf!lv-\x08&\xf8\xbf\x17\x85\x1bxl\x1e\xe4\xbf!\xac\\\x0c)<\xd3?\xef\xf0\xc4\xfa\x1d\x85\xfa\xbf\x1f\xf0\x1b\x96\xef\xcf\xcc\xbfL<\xb3\xd6\x8f\xae\xd2?\xf0R\xeb\xb9u$\xeb?\x0eap\xec\xde\xc3\x06@U\x83\xd0w\xe5\xd5\xab\xbf\x04 @\x98"\xc9\xee\xbf\xe6\x81_D5\xb6\xf8\xbflj\x8b\xc5\xc9\xe9\xf1\xbf\xad\x9c\x14E{\r\xeb?K\x9dN\xd6W\x8b\xc4?\n\x10\xda\x1a\xab6\xec\xbf\x0f8Hdc\x10\xec?Y\x91\x86oR\xa4\xfc?:\xa0\x08%\xe3\xd5\xf2?\x98\xfbN\x82\xe6\xb7\xed?\x1f\xaa\x05\xeb\xec\x93\xfb?\x95y\xfb\xd0\xb2\x08\xe4?\x8f1eI&\xba\xf9\xbf>\x8cX\xbc\x98\xef\x00\xc0\x02_\xc9\x1b\xb6\xc1\t\xc0\xa9F\tI\x14\xfa\x00\xc0\x0e\xd8Y\xdc\x9f:\xfe\xbf\x18\xd3uq\xef\x88\xfb\xbf\xb4M\x16S\x83b\xdf\xbf.\xf3f/\xb7\x91\xf1\xbfd\xf0\xbd\x11m\x18\xd3?\xb1V4\x84\xa0\'\x9e?\xc2\xe7\xc1\x89\xb98\xe6\xbfaJ\xf5\x17\x9cd\xf2\xbf2r1\x8f\x14\xb7\xf2?j|\x13Wf)\xc6\xbfPy\xe2\xe41\xa0\xbf\xbf\x93}\x0e\xee\x97\x01\xcc\xbf\xdd\x064x\x83\xa8\xda\xbf\x83\xc6\xf1&\xd8\x95\xf5\xbf\xa8\xec\xb7>\xcd\x00\x03\xc0d\\\x03\xf1X\x87\xe4\xbff\x81\xed\xba\xa6(\xfb\xbfX\xed;\x80\xc9\x8c\xe7?{\x07x#b\x96\xfc?\x9e.\x1fa\xc0\x8fx\xbf\xc7M3(\xd6\xd6\xda?\x81\xf2\xa4$\x18\xbd\xdd?\x07m\xda@\x96\x17\x05@\x9f\xb6\'\x86\xa7\xb3\r@$]{:A\x97\xf3?\x8f\x9a\x13\x91F\x00\x8f?\xd5i\xd5\xbf~\xdd\xfc\xbf\xc0\xd1\xb0\x0b\x12\xf7\xdd\xbf\xb0\xb3\x18\xeb\x8f)\xb7?D\xfe\x08V\x91\x90\xb9?M\xa8U4\x9f1\xf0\xbf\r*N\x97\x98R\xf5\xbf\x12\xa69\x14Dy\x8f\xbfsp\xee\x1e\xc1\xf6\xde?0 \xbcd\xf3\x1c\xe3?m\xdb\xcfiu\xd3\xff?W\x0e\xb7\x14\xa88\xf1?\xbc?8<\xb8\xce\x9a\xe5?\xac-\xfd\xa1,q\xe7?\x1a\xc5\xd0\x95kU\xed\xbf\xaa\x0ecp%\x93\xdc\xbf\xbdS\xac\xb5,\xe4\xe9?\x82R\x81\xb2_\xe2\xf0\xbf\x87\xad\xaa\xbeb\xe0\xd3?$\xfe\x11\xd2\x02\xf2\xd8\xbfx1\xec\xbc\xa3\x9b\xf3\xbf\xe9\xe1\xb7U\x1f \xfa\xbf\x95o\xe4-\xfe\xfc\xa6?\x10g\xad\xc5A\xd4\xe7?I"\xe4\x16z\x8f\xaf\xbf\xff\x045L\x03\xaf\xe0?O\t\x14\xc2+\xb3\xda?\xd0v?\x99D]B\xbf\xdc\xb6O\xf9=\x8a\xfa?L6\xbe%\x152\xa0?\xa0K\xdc\xf9\xc6X\xc1\xbf~\xee\x9e\x84\xa0\x1f\xf4\xbf\xd50\x06z\xcc\xc9\xd3\xbf\xfa\t\xef\x03v\x8a\xb2\xbf2QK\x13\xe4\xa6\xf4\xbf\x8f\xe3\xb2\xb2\xae\x94\xf3?Y`\xa2\xbd9x\xe1\xbfZ\xc5\xf1\xc5\xb1S\xb6?}\x1cm=\x02\x84\xe5\xbfZ\xc1\x7f\xca\xff\x89\xd9?\xb45\xb3\x91\x1e}\x03@\x04\x13J6Y\xd0\xf2?\x0b9&\x7fP\xc3\xf0?\xd9YP\x18_\x05\x00@\x85H\xca\xe2\xf9D\xed?\x13\xe4\x18\x07\xea\x17\xde?;t\xf1C&%\xd0\xbf\x03v?\xd9\xbf%\xf6\xbf\x06\x88\xac!\xe9!\xc8\xbf*n\xd4\xa2L\xab\xce?0\xeb\x9d\xa2\x0c\xdf\xba\xbf\x99w\xc1\xa1\x8c\x99\xf0?\n\x82y\x94I\'\xcb\xbf~6`\x88V\xe7\xee?\xa2\x83\xc4\xdf\x7f\x92\xe6?\x19(t\xf5\x8e;\x00@rQ\xe7\xc4\xf2\xe1\x05@`\xb5zE\xda\x03J?<\xb2I\xd1\x90W\xfb\xbf\xf4\xfc\xd1\xcc\xcc\xf4\xf1?\xa26\x9e\x9b\xc4\x7f\xa6?&!\xa2\x94\xb93\xdb?\x80Q]\xaa\xfa \xe1\xbf\x8b9\xcdt\x1f1\xe0\xbft\x8a\xfa\x9a\xa8\xba\xe0?\xa1\xcf\x10B\xa6\xf7\xfc?M\xb4\x84U_[\xeb?\\\xd2\x06\xb3\xa5\x83\xdf?\xb4k\x01\xcf\xfd9\xfb?\xafn\x0e2\xba\xcd\xd1\xbf\xfc\xef\xd5}\xe8\xe7\xf2?\xb1"\xd9\xf8@\xac\xe9?q\xc2\xe6g.\x9a\xea\xbfc\xc1B\xa3\xd9?\xbe\xbf\xd1\x91s\x84s,\xeb?\xdbO\xc4B\x84N\x04@\xb2\r\xe9\xe54 \xe3?\x82\xae?=;;\xb6\xbf\x9a\xfeP\x99v\x03\xd5?\x86\xfa}\x11q\xef\xde?R\x9d\t\xc5*\xa9\x00@\xdad\x18\x87\xbc$\xe5?oZ\x95J\x9d\x9c\xf6?E\xbcGw\xf8\x9f\xe6\xbf\xd5r\xec\xef\xabX\x05@\x809COX\xb4\xe8\xbf\xeai\xf5K\xc6\\\xd7?0\xfd\x83\x00\x1e\xff\xe6\xbfN\xe4.\x11-O\xee?\x1a\xcb\x84\xf9\x15\x80\x01\xc0\xb6$\x02@\xe4\xd1\xc7\xbfj6q\xfe\x8f@\xe3?%3J~\x99\xb0\xaa\xbf\xe9P5\x15+\x83\xf7?$@v\xfaP\xcc\xfa?\x07\x1d\xc5\x05\xbe\xe7\xe0?\xf7U\xe7)\xa4\xca\xec?\xf5?J|0\x19\xd1\xbf\xb4\xdf\x9e\x03\xb5\xfb\xd0\xbfX\x9aB(\x05\x87\xf4\xbf.\xacYN)\xe0\xec\xbf\x1e\xeda\xa1\x8b\x18\xf6?|_8\xfb\xfe<\xec\xbf\x97\xbb\x0c2\x15:\xb1?\xd8\xfe$,\xce\xca\xbc\xbf\x06)\x17\xf0\xb2\xf8\xe9?r\x14*\xe1\xe8\x15\xeb?\x8d\x15\xdfgp\xf1\xec?;\x1b+\xb3\x9b\x90\xf3?Ww(\xaf\xb88\xff?OR\xdb\xc4\x85\xcf\xcd?\xcb\x15n\x9b\xcb\xba\xfd?\xec.\xb9o\x82:\x00@\x93\xd2\xb4}\xc3\xc1\xf6\xbf1\xa7M\xa1Ue\xda\xbf\xdc\xfb\x8c;\xb1I\xf5?}\x17\xb3,{\xd6\xff\xbfY\x147\xf9\xb0:\xff\xbf\x01\xf8j\xae\xf27\xf6\xbfu\xed:\x05O\\\xf8?>\xb8\x11\xa6\xd5b\xfb?\xdf\xf1\x13\x06\xa1l\x05@\xc2\xd7\xc6\x9c3\x04\xf7?\x8a\x81\xf5,\x91\xe8q\xbf\xd2\xe6\xfdzg\xb7\xfb?u\xe1i\xf9\x06\x93\xf4?\xcf\xa4\x90\xbc\xdb\xbe\xea\xbff\x97\x7f\xf5\xbb\x15\xfc\xbf\xb5\xe1\xe0\x83st\xed\xbf[\xc16m\x92\x9d\xd2?>\xfe\x96\xc7\xd5\xac\x00@\xd5\xc1\x8c\x1cpQ\xe3\xbf\xfc9\x0c\xe7\xd96\xf2?\x94\x19\xa5\xe8B\xb1\xf1?M\xbd\xe0#\x06\x1e\xf5?\x80>\x82\xb4\x9aE\xe7?\xa34\xa7W0\x83\xe5\xbf\xb3\xe1\x8a9\xaa2\xf0?!\xc1\x97\xbb\xab\xd3\xec?\xb0\xc1\x9a\xc2\xc5\xb2\t@\xa5\xf3\x10M\xc2e\x01@i6\xc3\xc2\x07\xd2\xef?QR\xd3\xbc9M\xd4\xbf\xf2\x1a}\x05\xba\x04\xf9\xbfGHq>\xe0h\xa8?e\xd4\xf0\xc0\xd1\xe1\xe1\xbf<\x01\n|\xf3\x82\xee?Z\xd4[\xaan\xf8\xda?\xc9M\xc4\xd5\xf9O\xe5?\x00\x0b\x84zh\xc9\xd8?\x96\x87\x117\xc2\xaa\xfc?\xab \xd2p\x18,\xe2?Q\xc1\xf7\x91\x17\xbc\xe2\xbf\xc3\xb8\x88\xaf\xc5\x15\xf5\xbf\x9c\t\x86\x9c\xb6\xed\xf7\xbf<\xfd\x1b\xa1@\xab\xf8\xbf\xb7\xbc\x83\xc3c#\xf1\xbf\x05\xd3\xc7\x91f\xa7\xb2\xbf<\x15\xc7\x9dh)\xd3?l\x18\xad\xbd\xb8\x9a\xef?\xd2\xfb\xb4]\x15\xd7z\xbfmQ\x1aS\xf7(\xe0\xbf\x11.\xee\xebn\xba\xe9\xbf\xe0\xcb \xbb\\U\xf2\xbf|\t\xe8Y\x88\xb3\xd5?\xc1\x16\xf7\xef\xc6\xb3\xe6?\xbd\xd1\x85\xeeP\x06\xf4\xbf\xc9\xf5L\xaeOt\xd0\xbf\x81\xa1G\xf1F\x11\xea?\xff]\xe8\x08\x11T\xf5?x)t\xde\xcc\x1b\x81\xbf\xd1\xab-\x15\xe5\xbd\xea?+\x90\xa2~\r\xe0\xdc?\xfc\xad\xde\xb0A\xa2\xe3\xbf\xea\x9f\x8c\xb2\xc7;\xed?\xe5]\xa4y\xc1\xa8\x0c@Tl\xe6\x00\x80\x0c\xfa?\x99\x17\x19\x08kg\xee\xbf\x87\x9f\x0e\x11\xd7%\xf8?\xe8\x87\x14d\xaf\xf5\xf8?\xc9\xadm]\t\xf8\xf2\xbf\x1c\x8ej\x03\x909\xdf?\x8a\xc4\xaaZEm\xd6?\x90z\nxl\x08\xec?3\x1eD\x89p\xd6\xf4?\x1d\xfd\x85\x1f\xe3\x9e\xc2\xbf\x1d\x8d.\x8c\x83|\xc9\xbfD=\xd5*\xc0\xda\xfd?\x11\xfd\xcc\x00Gv\xd3\xbf\xa0\x11*\x86\x9a5\xe7?\xca\xed\x83s0\x15\xdb?G\x1bp\xb0\x91\x00\xb7\xbf\xbe\x7f\x12\xcd\x96I\xcc\xbfy\xa8\xc2\x9f\xbd\x17\xe6?\x962R\x814\x8b\xed?OEX\x9e\x11\xef\xff?\r"\xc1|\x136\xf2?Kd\xad\x13\xdd(\xe0?/@\n\xb80\xb2\xf0\xbf$\xe9^\x08R\xb3\xd0?\x19\x03\x00\x8c\xc9\xd3\xfc?\x9a\xff\x89\xeb\xebM\xdc\xbf\no\x99t\x14\xaa\xf8?\x9f4\x83\xe3\xb09\xf2?D_\r#\x9d\x8e\xde\xbf\xa3h8w\x03\xc9\xf0?L\x1cM\xd5,\x16\xd9?Q\xe6\xe6DW\xea\xf8?~{\xd7u\x9a\xf6\xef??7z\x08s\xfc\xdf\xbfZh\x1c\x1bI0\xd1\xbf\xa5F\xc9\n\x0b\xf2\xe9\xbf\x04\x82\xe0\xda\xc3l\xcd\xbfH\x85~\xf7\xbdm\xf3?E\x19\xe1T\x97\xf3\xe3?j\x80\xa6\x16\x0e\x12\xf9\xbf \x88?\xd8\x08\xe8\xf5\xbfBl%\xd6\xcb\xde\xe2\xbf\x94\xabH\xdc\x84N\xb6\xbf\xaf\x96\x0f\xf2\xe7\x9f\xf4\xbf\xcb\xb0\xe3nsK\xd0\xbfv\x81\x1b\x05\xb3\xf2\xf2?=\xd2c\x04(\x83\xc3\xbf\xd6\xe2p\xa0\xa8\xde\xf6?X\xdf\xa6Q@\x88\xc8\xbfFpH\xe2x\x9c\xda?\xb3\x0e\xcb\x82\xdb@\xc3?vf\x05\xeb\xff\xc7\x9a\xbf\x86\x81\xcd\xb7\xb6"\xfc??\x1e\xe7\xf6o\xac\x05@\xc4\\\xba4u\xbe\x05@\x04y\t\\\x16\x81\xf5?ef\x12\x1e\x08\x83\xf5?\x07~\\\xe9q\x80\x06@\x12\x91\xa0\x9e\xd5\x1a\xdc?\xfdc\x8e\x91}\x13\xf7\xbf\xb3n\x1b\xc9\xb8\x88\xb7?"\x8eH\xc0\x05\xe5\xf6?=\x04\xab\xa0\x13\xbb\xfb?zv)R\xcd}\xc5?Q\xf36\x1f\xdd\xb9\xec\xbf\xca\x07K \x16s\xd6\xbf7\xd5\x88}\xbe\x93\x9d?\xf0\xca`^\xf0T\xf2?\x94\xaa\xd4\xd5t8\xdf\xbfI\xfaU\x0fn\x0b\xd8\xbf\xa1\x84{A^!\x01\xc0M\x91\xfcE\xa8q\xc6?\x15f\xd8\x0f >\xe6\xbf\xaf -\x91M\xa6\xee?\xd2\x8c\xba\xc5\x95\x01\xf9\xbf:\x9c\xdc \xca\xb9\xc7?\xd3\x02\x11\xf3\xa0\x93\xd6\xbf\xf6k\xd9\x15\x8du\xed?\x8f\xf6.\xd9\x10\x98\xeb?\xdc=y\xa9N\x85\xeb?\xe9!\xffc^\xc6\xf5?\xbd\x10-O`\xb5\xf3\xbf\x15\xf1\x84\x1cV6\xee?{a^\x03\xc7\xbe\xf3?\xe0\x94-\xd7\x86\r\x01@\xce\x17\xa0F\xb4\xb9\x02@\xb7\x89\xbd\xad\xf9r\xde?\'R\xe1\x84\xd4\n\xe6?\xd7w\xac\xeb\x94\x01\xf4?]\x0fv\x0f:f\xc1\xbf\x17U%\x1cb\xf1\xe6?<}(\x1c8\x87\xad\xbf\xeeC\x7f\xfa\x9d\xfd\xdb\xbfm\'\x13\xfc\xf3\xec\xf1\xbfR`\x1d\xdcKm\xc4\xbfTT\xac\xaa\xa8\x89\xc3\xbf\x1a\xe0\xa9\xf6v.\xf2\xbf^\xac|%\xe8G\xbe?\xe0\xa4\xe3;\xbc\x90\xcc\xbf\xe2}\xa32Q\xea\x00\xc0.\x1d\x84\x9e\xde\xed\xf0\xbfa\x9b\x9f\x1d\xe8\x1a\xfc\xbf\xad\xd8\xf1\x920\x14\x0f\xc0k?\xfe1\xc2\x8a\x06\xc0\x8e\xf3\xc2\xdf\xeb\x00\xf7\xbfk\tVMu\xba\xf8\xbfH\xdd\xee\xeb^y\xf0\xbfP\xb77\xdc\x08\xca\xf1?\xb2\xd5\xcd\x80\t\x1b\xeb\xbf\xb8\xfb\xa2\x86\x19-\xe9\xbf\xf5\xb0\x1b_;\x85\xfe?yo\xf2\xe0\xf6\x94\xef\xbf\xf0\xae\xf5N\x1c_\xe7?J\xa4,\xf4q\x8f\xd7\xbf\xb21T*\tl\xdc\xbfD\xd3Kg\xf2\x9b\xec?\xb5>i\x95\xd7v\xbf?S.\x97\x14\xb5\xb3\xd5\xbfi\xfb%\x8d\xe6\xdd\xef\xbf\x07\x87\xf4Kx\xe9\xf9\xbf\xc0\xd2\x06\x9c\x80\xa5\xc9?\xbd\xda\xe9]\x06)\xf0\xbf\x08\x12\xbaD\x9a\xf4\xe7\xbf\xf8I\xc4eZ\xce\x01\xc0R\xf0\x06\x86\xa8\x9f\xe8\xbf\xa9\x14\x06\xa0\x07\x99\xf2\xbf\xf6\x05\xcfE\xa7u\xc7\xbf\xc2\xfbsa\x89\x94\x02@[\x90u\xf8b\xc3\x02\xc0^~\x8bhy\xea\xf2\xbf\xcf/\x9eh\xee\'\xfe\xbf:\x1aEYH\x81\xf8\xbf<\xc7H}\xebl\xea\xbf\x91\xd2Ow\x9d\x17\xe1\xbf\xd9)\xb9\xc4Y\xd7\xe8\xbf\x9d\xea\xc4\x9b\xe4\x13\xfc\xbf\r\x89\xb2\x9a\x1d\x97\xee\xbfH\xe6~\xdd\xf8\xf1\xca?|A3\x8e\xa5\x0b\xe3\xbfL\xc4\xfd\xba\x19{\xdc\xbf\xbb\xd9\xebPC,\xa4?t\xaf\x12\xd3\xc7\xe5\xc6?\x08\x039\x08Lr\xf0\xbf\xa3\xaa\x0f\xcer\xac\xf0?3\xe9\x07s\xce\x1d\xf4\xbfd\xb8\xfcz\xae\xf8\xd9\xbf,\xd6\xf5A\xdd\xd3\xd4?n\xfa\x80l\xbcr\xe3\xbf\x90v\xf8\xbd+o\xf3\xbf*3j\x8fV\x17\xf5?\x92\xb8\x16\xdb\xf2\t\xa2\xbf\x9b\xe9\xd8\xbb\x8a\x89\xee\xbf\xc2\xef\xb1#\xd2\x08\xe3?\xa7\xa4u\xfc\xb3v\xf7?\x81\x1c\x88\xf37\xf3\xcc\xbf\xcal\xc9\xd7\xd2.\xd2\xbf\x95bZ\xfc3S\xff\xbf\xeb\xf6\xb3\xe4\x9e$\xdb\xbfl\xb8\x13\xb3\x0f\xc1\xe3\xbf\x9e\x93z&\xa9\xcb\xbd\xbfPi\xb9\xde\x84\x92\xc7?} \xfde\x9a!\xee?\xf7)\xae\t\xa2\xb7\xd5?\xa7.\x86\xeb0\xe4\xd7?\x8d\x9d\xcfk\x95\xe9\xf7\xbf\x06R\xca>\xf8i\xf5?;\xd4l\x0eC\xcd\xf7?\xd4\xb1\xbb\xd6Z\xcc\xfe?\xcf\x02\xc9\xdeg\x16\xd9?\x16\\\xa4lA\\\xf6?\x87/\xb4\xc0\xcb\xd6\xf5\xbfa\xe6\x05\xae\x1e\xb4\xe0\xbf\x05F\x1d\xf6\x8d\xf1\xa4\xbf(\xf8K\xd7\x07\xb2\xf6?\x0f(3\xea\xa02\xd5\xbfd\xb6S\xd9\xe6\xe4\xec?f\xfa\xdf/[}\xf1?\x17\x81\x0b \xbf\xa5\xed?!=\xc4,f\x90\xad?\x88l\xcb7\xe1\x0e\xdd\xbf\x97\xe8\x1dV\xbbm\xf5\xbf\x92]\x11\xb6\xf9\xe1\xf0\xbfI\xc7o/]\x9e\xdc?\xd0L\xee\xba\x95\xb3~\xbf\x12@0e\xe1\x00\xe3?%\xb6\xf5[S\x9f\xd6\xbf\x86o\xec71k\xef?\x04d7\xc0M\xc6\xcd\xbf1\x1b\xa7c8\xc3\xea\xbf\xed>h\x05\xf6\xb4\xca?T\x02\x19\x15\r-\xcc\xbfg\x8b_\xd1M\xae\xc7?\xcc\xc8\x12\x8e\xadI\xdd\xbf\xa4\xbcj\x99gp\xf5?\xf3\xbd\xa8\xfaI@\xdc\xbf\xa5\xf3\xe9\xe2[]\xfa\xbf\xbb\xcf\xc0\xef\xbd\t\xee\xbf\xc5\x05\xdb\xad\x9a\xfc\xd4?\xe5\x1a\xfc!\x02\x8f\xdb\xbfJ\x97yN,\x12\xf0?\x00W\x97\x80\xf9\xcb\xda\xbf\x03\x11{\xfc\x95z\xd8?\xe2;M\xe3&:\x91\xbf\xf3]D\x1b\x1e\xec\xf8\xbf\xdb5\x90\xb9`*\xe1?\xc9!T\xb5\xfa\xbf\xfa?\xdd+\x19\xc4\xd6(\xef?\xd7T\x1d\xed\xea\xbd\xb2\xbf\xf2\xab:\x10\xaaz\xbe\xbf\xa5\xe5\x97\x0f\xc4\xc5\x9e?\xe0-\xe4\x88I\xa5\xf1\xbf\x12\x94~C?d\xd4\xbf\xb6\xf0=v\xcc\x81\xee?\xcbX\x86\xb8\xa7\x04\xf3?\xfc\xa1,\xb9=\xcd\x00@H!_\xe4\xff\xb8\xe9\xbf\x0f\xb1\x96\x16\x16\xc0\xd0?\xef\xc4K\xda\xdfN\xd1?\x8d\xb3]u\x95\xc5\xfd?u\x88\xff~(\xf5\xfa\xbf\x1e\xdb\x94i\xa9\xa3\xea?\xdc\x12\x04I\x8d\xc1\xfd\xbf\xa2\x87\xa5?d\xa5\xda\xbf\xf4\x9eml\xdf\xb7\x01@\xf3e\x1b\xa6\xe1\xd7\xe4?\xf1\xce\xb4\xbcR#\xe2\xbf\x9co\x05-\x8a\xf3\xb2?\xf3>\xd5\xa5\xa9\xfc\xf3\xbft\xaa\x1e\xc4G\x1a\xe5\xbfX\x97\xa9}p$\xef\xbf.\xb3\xaaV\xbd\x1d\x02@8\x8bjvh\xe5\x89?\xa2B\x86M\xb7 \xf2?\x99]\xbd\xd4\xf1d\xec\xbfK\x84\xd0%^\xf3\xe2\xbf\xf7w]Q\n\x8a\xae?F\x1aSJ\xe7\x91\xcf?\x03\x7f\xe1\xbfI\xb6p\xc7\xbcH\xba?\xe1\xc5\n\xed\xf7\x8d\xc5?\xeb\x12\xee\xb9\xce\xe0\xf1\xbf\xfbB\x1ff\x8a\xc7\x04\xc0m\x96\xdd\x84D\xb8\xd4?\xe5\xee\xad\xd4\x11)\x96?$\xba\x01W\xa1\xe4\xe6\xbf\xf8b\x80\xda\n\'\xe4\xbf^S\x11\xac\x85G\xfc\xbf\xfda\xeb\xf5%\xd2\xfe?\xdd\x91\xa4\x1e_\xea\xb9?\xb5\x00\xdb\x95\xb5\x0c\xd6?\x8e\xb9\xabz\xf6l\xe5?Ei\xed\x98\x07(\xc3?\x81f\xd8\x99\xa4/\xe2?\xbf\xd9\xfd\xaf\xb7\xbc\xc4\xbf\x12\xfe\xf1\xfd\xcb;\xeb?\xdf"\xb7\xce\xe1\xd6\xf0\xbfj.\xb6\x107c\xec\xbff\xa1I#\xb9/\xe0?\xdf"\x83=\xbe\xee\xd0?\xf8\xa6\x1c\xfcEy\xee\xbf\x0f\xe3h\xc5\xa7b\xd5\xbfF\x06\xad\x96\xd6\x9d\xc7?\xc1\n\xc7)\x0c\xa3\xe8?\xc5\xc7rw\xe2T\xf5\xbfu>~<\xa1c\xd8?\xf1y\xc0\xa4o&\x02@\xd27n\x11\x139\xec?^\xfa\xd8\xd5\xba-\xef?\xf9Z\xafj\xba\t\xbb\xbf=lj|"f\xe9\xbf\xcd\xa8\xc2?\xf9D\xda?j\x02\x80\xd2!Q\xe3?\x0c\x9d\x08\x19\xe0`\xa7?\xdc\xfe\x15>\xeab\xd7\xbf/\x97@\xbd/\xf5\xd2\xbf\xb9\x0bo=9\x82\xd1?\x85A\xda;(I\xd3?\x12\xe5X2\x05x\xce\xbf\x9f\x96\x95_\xe7\x1f%\xf2\xbfW\x1b\xd96\xf4\x08\x06\xc0\x0c5\xa4\xa0\x07\x96\xf4?%c@\x13\x0f\xfe\xe2?\x0c\x8cC*\xa2,\xf9\xbf\x93\x8d\x07\x9b\xe3?\xec?\x1c\xb9\xe9\x87\x90\xd9\xf9\xbf\x89\xc84\x08\x1d\xf4\xd1\xbf\xdc\xa8,+\x8c\xba\xdc\xbf\xa3\x0f\xd3r\xa2\xf7\x03\xc0\x9e\xba\xa3\x15\x06\x1a\xec?\xbapS\xf4i\x13\xf5?3\xcd\xbe\xf8o\x1d\xa4\xbf\xdan<\x0f3\xff\xd6\xbfe\x97u\xa3\x8fd\xf5\xbf\xb5\x0f\xbd}r\xde\xd3\xbf\x0b\x90\xe1\xcd\x8fR\xf5? \xb0#\xc2H\xcf\xf6\xbf\x9f\x7fs\xbd\x9e\xbb\xe8\xbfS\x80EQ2P\xee\xbfBb\xb0\x1d=M\xca?H\x1e/\xaaT \xfd?\xbc\xec\xda\xb3_S\xdb?f\xdb\x86\xc2\x1e:\xf2?\x01\xfe\xbe\x1f\xc7\xb9\xf0\xbf\xb8\xdd#\xa00\xea\xf0\xbf\x96K\x9c.\xae\xc9\xdf\xbf\xcc\xa1\xf0\xb7K\n\xd3\xbf<\xa1|\xe09q\xea\xbf\x9a\x19x=\x9f\xb7\xfa?\xed+\xcdv\x8e\x89\xf5\xbfJ\xfb1$\x06\t\xeb\xbf\xdd\x83\x80\x8bs\xd7\xd1\xbf*\x08\xceFl%\xeb\xbf%|v\xdc\x8c1\xe2\xbf\xad\xc4\xb1\xcd1\xf8\xe8?i\xc5\x17\xb3y\xb6\xf8?\x81\xa9\x98\x9b\xd4\x8b\xe0\xbf\x91\x06W\xc3|\x87\xd8?\xfd\xc5\x91g\x16b\xe0\xbf\xc46\xc1m$\xfe\xf4\xbf\xa9\xfdy\xa6We\xf8\xbf\x07\x0b\xc7\xc1I}\xe0?s\xde\x992\xae\x81\xe6?}\xcf\x8c:\xe9\x8d\xbf?s\xda\x82\xb5\x8b\x06\xf9\xbf\xa0\xd1\xb8\x8e\x1c4\xca\xbf\\-\x8f#h\x80\xe0\xbf\x1a^X\xcb\xd9:\xf2\xbf@0;\xbf\xbc\xe3\xef?S\xd5\x93\xae\xe3\xa9\xf8?U\xc1]\xe4\xf9\xf4\xe7\xbf\xab\x05\x91!\xca\xbd\xfc\xbfdQ\xa1\x97\xe7\x86\xa3\xbf\xa7\xf3s@0\x02\xe8\xbf\xba\n\xd8\x17>t\xd5\xbf\xd4\x171\x15\xce\x93\xfd\xbf\x9b\xca\xae\x17Q\xd0\xd9?\x94l\xde\xc9\xcb\xd3\xe4?V\x03\x8c\xc5k\x92\xe5?$$\x1d\x8e@\x88\xd2?,eM\xbb\xda\x0f\xa7\xbf\x94\xc1\xbcn\xb3\xbc\x02\xc0\xce\xf1\xde_$\x1d\xe8?\x87\x85\x14\x88\x8b<\xf5?o\xc5\xbb\xc4\x05\xf1\xef\xbf33\xf3W\xc9\xe5\xe0?*L\xd8\x01\r\x8f\xd7?\xd9\xeeZ\xab-s\xe8\xbf\xe2\xf1~\xfaE\xa5\xed\xbfT\xcd0E\xb9\xba\xfc\xbf{\x0e.\xe3kS\xe6?\xfce\x9b\x859\xfb\xe7\xbf\x86wu=\xec2\xf0\xbf\x8f#8\xc0\x1fJ\xf1\xbfu!F\xecz\x90\x01\xc0\xec.\x90{\xb6\xe9\xf1\xbf\x82X;\xc5s\x88\xb8?,\xfb\x1f/Z\xac\xfd?\x87:\xc7\x86\xd0\xfb\xe8\xbf\xcf\xce\xf0\x0e\xd2\xc0\xea?d\xff>\xf1\xc3=\x05\xc0c\xce\x0f\x90R\xe9\xe1\xbf{\xecp\x98\x18\xfa\xf2?\xf0\xe4>\xc9R\xea\xc5\xbf\xf6\xd1\xcaw\xaa\x05\xd7?\xd6\x9b\xae\x1a\xcf\x10\xe2?B\\f\xb6\x8fJ\xd3?#\xa8t\x97/4\xde?\xc5\xff6\xd8\xab\x14\xb9\xbf\x90W\xed\x8fN\xfc\xe5?\t\xdb\x9a~Um\xd7?\xe3y\xb6\xf7\x15L\xf3\xbf6\xa4\xa4\xb0:\x89\xeb?\x96\x92\xbf\xd1\xbc\xbc\xe0\xbfUcI\xa5\x07G\xe1\xbf\xf6-!\x9b\x02\xf3\xd9\xbf\x88\xae\x9f\xa8\xd9\x01\xf6\xbf;o\x9e\x8ap,\xd6?\xd3\xdb\x8e\x10k"\xfb\xbf\x9d\xd7,\xf3\xe8\x0f\xf6\xbf\xb9\xc1go\xb6\x88\x03\xc0$9\x04TH\x1c\xe7\xbf\xab\x04H\xed\xf0\xda\xd4\xbf|\xf5\x8c\xd4\xc8\xf2\xf3\xbf\xf3\xd5`K\xc8\xbe\xc0\xbfg\xeb\x84\xeeC!\xef?\xf1\xcd\x8bE#\xee\xe1?[\x8b\xcf\xe5\xcb1\xf6\xbfN\\\x86_\x865\xd0?\x98a#\xffp\x7f\x01\xc0M\n1\x0f|/\xb3\xbf\xf5\xe1\xb6$\x01\x05\xec\xbf\x0b\x00\x18\x8a\x9d\x1d\xdc?#\xbb\xb6\x94\xf7,\xaa?2\x03\x17\xf1\xad\x07\xb6?*\xef\xc6L\xac\x7f\xea?\xd0v\x1d\xad\x15\x03\xe8\xbf\xb4t\x99\xe4>A\xc1?p\xc2A\xdf\x99\n\xd1?\xeeE/aD\xa1\xd9?\xd2\x0f\xf5\xca\x01\x88j?=-\xc6\xad\xac\xe5\xff?\xa7\x815\xb6P8\xb7\xbf\xc9\xe3o27=\xf0?\x1b\xad\x9fU\x84\x15\x02\xc0\x94\x9e\xc6\xcfD\x06\xe7\xbf\t\xee2N\xb5\xc5\x02\xc0\x9a\xa4\xa8\rM\x19\xea\xbf\xd9\t\xbc\x17$e\xa5\xbf\xc5c\x06\xd1\xfaf\xbc?\xed\x02A\x9e\x0cD\xf3\xbf\xd3\x97\x8d+\xfe\xbf\xde?\xdf\xe8<\xc4I\xac\xb9?\xb9\xb9\x8d\x03\xb8\x88\xdf\xbf\x91\xe2Y>9\x87\xe6?VP\xc5\x16*P\xfe\xbf \xcc\x88\x9b\xe7\x84\x02\xc0(\xd7F\xeb\xac\x8e\xf6\xbf\xbd\x1f+\x0b\xcfj\xe0?\xf3\xd9if\xb6\xc8\xe0\xbf\x00\x1d\xce\xd7\xe9\xda\xcb\xbf\xa2\xb08\xb4\\L\xb8?\x12\xac\xccs\x15{\xcb?9\x9f\xc2\xfe\xf2\x92\x86?1\xea\xbb\x0f\xdd\xf1\xee?\xbeJ)g\x8a\xe8\xd3\xbf\xc3\xdd\x86(L\xed\x08\xc0\xee\xa1 /\xe7\xc3\xe1\xbf\xb1dz\xfbO\xee\xeb\xbf?s\x08\xb3\xe8\x0b\xf0?\xb0cH\xc2Q\x87\xc2\xbf\xd4\xff\x97\xea\xfb5\xf5\xbf\x951\xefR\x07/\xf5\xbf\x18\x97\xd62\xd4@\xec\xbf\x9e\nc\xb9\xd39\xf0\xbfAr@\xefP+\xcd?\xa47}\xedih\xe3?uj\x91\x02\xb8\x8a\xd2\xbf\x11\x8dK\x95\x84\xca\xf5?\x975`.\x8b0\xdb\xbf\x1a\xb4\x9e\x9a\xff\xcd\xc5?\x1f\xae\x90\xc6=\x84\xf4?\xba\xc03\x8bFj\xe2\xbf\xc82C=\x80E\x06\xc00\x17\xcb\xce\xff\x19\x01\xc0/\x0b\xb9\xb6)\xcd\x00\xc0K\x88\x1f\x8f;B\xd8?\xaf\xb2\x91\x06\xb6\x8b\xf8\xbfUn\xd7\x87\x1f\xf2\xb6\xbf[kt\xbe\xc0A\x03\xc0V\x00]&:7\xe0?\xfb\x9d\x12\xcb:\xdb\xe2?\xd6\xd6\xb0\\EP\xc3?(\x8a:g\x9c\xa2\xe3?ww\x8a\xb4O\x8f\xd0?\xe8\xbe\x94\x90\xab_\x99?\x81\x1cC=\x05B\x01\xc0\xe77K\x10\x1a\xa6\xf5\xbfg\xb8\x17\xfe\xdd8\xf4\xbf\xdd.\xf8$\xf4t\xf0\xbf-\x1d\xca\xb4\x90=\xf2\xbf\xd3\xc5CHf)\xcd?\r\x8e\xe5\xf5\xc7\xb2\xf3?\xa9\xbd\x85\x13\r\x80\xe1\xbf\xd1\xbd-{\x1c\xce\xf1\xbf]?\xf6\xea\xe9U\xf0\xbf"$\xe3\xdc\x84\xb7\xea?D\xfd\x93\x072\xfc\xe0?\xf0Y]\xd3\t\xfb\xcd\xbf\xbbr\x85\x83nT\xcf\xbf\x9d\xca\x7fI^E\xff\xbfP\xb8\xf3\xe3\xa8\x0c\x11\xc0{\\\x8b\x93\xbf\xb4\xf7\xbf\x82\xbd\xef\x95X\xf5\xf8\xbf\xa1J\xd4\x14^\xee\xe4\xbf\xe3\x8c\x1f\xac\x1bY\xb6?(\xff\x1fr\x07\x82\xf6\xbf\xb8\x1c\x9d\xe7<4\t@\x1cS\xb8\x87\xb5\x1a\xa8?\x88\x18ri\xaf\x15\xd1?\xed\xcbW\xcd\x90\xf3\xf0?Z\xd0*\xd0\xeds\xf0?\'c\x87\xe0\x15\x98\xa8\xbf\x0cF\x82\xfe\xe8\x0b\xe7\xbf\x9a\xb0\tv\xdb\xfc\xe4?\xf3\xd7\xa6\x1d\xc6\x1a\xd1\xbf7\x9f\\i\x99\xc2\xe9?\xd2\xd13$\xea\xde\xec?\xbdF\xa6\xfe\x9cm\xe3?d\xb8\xafZn\x8c\xe0?\xb9\xa7\xe1w\xf3o\x04@\x91\xe7\x0c\xbd7\xcd\xf2?\xf8\xe6U(\x99;\xe0?\xd9t\x90\xc4\xbc\x91\xcb?n\x0c\xe5\xa8\x08;\xfe?\xe6\x7fx\xfbE\xec\x8f\xbf\x0c\x91?p\x16\xea\xfd\xbf\xc9X\xc4\xbf\xd9\xf9\xf9\xbf\x04}pV\xf9\xcc\x02\xc036t\xcf\xb1w\x06\xc0<\x9fq\x1a\xfb\x98\xff\xbf\x0c\xf5\xbd-\t\x1e\x95\xbf\xa4[\xbfk\xfc\x1f\xf4\xbff\xcfg\x9f\xc4)\xf5\xbf\x9a\x92\xda\xc2Ao\xc7\xbf\xe6Dd\xda\xb7\xe2\xb5?dT\x83\x12I\x84\x04@7\n\x99\xe3"\x92\xda?-\x14\xf3\xaeO\xe5\xd7\xbfm\xd0%^\xddl\xf6\xbf\xd9\xc9\xb1\xd3(b\xed\xbf\x87S\xb2fB\x83\xdc?\xd5\x97\x083\xb5L\xdf\xbf\x13YW\xc0\xc8\x9e\xd0?1\x11/\xf5\xdb9\xd8\xbf\x99\xd8\x9d\xe6s\xe2\x06\xc0\x15z\xb9\riv\xb0?\x94N\x11\xcc^D\xe6\xbf\xc7\n_\x86T\xff\x00@\xfc\xc2\x81\xdf\xa1\x04\xfb?\x0f;P+R\\\xe0\xbf\xca\xf6_\x01P\xf0\x02@\xa5V\xda\xc3\x90Z\xf2?\xad\xa2\xde\xda\xb8@\xc7\xbf(a\x1d\'te\x06\xc0\x1f\xa5\xc3\xefjC\x07\xc0\xda$\xf7\x97q\xa9\xfd\xbf\x9f\xa6\xae\x84\xa5e\xdc\xbf\n\xe7|W\t1\xfa?\x00\xc2\xa2\xaf\xc8\xc3\xf8\xbf}A\xd0\x18\xdb\xe6\xee??\xe1\xb9\x0f\x94\xda\xa8?d\x82\xf23\x8d\n\xf3\xbf\xbb\x87\x04\x8f\xd5"\xf3?\r\xb0e\x98\xcd\xf3\xb1\xbf\xc4W\xefKy\xa0\xd9?\x81\x8a\x15\'\x9a\r\xf1\xbf\xfe?\x9b\xae\xdbY\xd9?[\xc1\xe8\xaf\xfe\xf9\xf2\xbf\xf04\xd4\xad\x88\x8d\xf1\xbf\xe9\x86\x87r\x11\xd8\xc3\xbf\xb4\xff\xdaj\xfc\xd6\xf0\xbf"4\xd9W\xd2\x0f\xdb?\\\x99 \xae\xa6\xff\xe2\xbf\xdbDT\xec&\x15\x00\xc0\xb0\x86\x1c\xa6\x15C\xe0\xbf\xdf\t\xa8cYH\x00\xc0\x9c\x0bSl\x1c\xc4\xc8\xbf\x08\xeb\xdc\xec\xd2\xe2\xaa?\xe2\xc9-?\xaap\xb5?n\x8b\x92O|\xd1\xf8?\xc4\x85w\xd1\x9e\xf7\x9b\xbf\x1b\xed\xfdP\xc2\xdc\xee\xbf\x83q\x1c\xf1\xe9\xb6\xe3\xbfZS\xd6\xc0.\x1c\xf9\xbf6\xb0f\x82]\x07\xe1\xbfX\xab\x86.}\x87\x00@\x04\xe24A\x8e\xf3\xec?\xe0\xf8\x1a5\xd6L\xc1?\xeb\x9b\xf5\x06\xb8F\xcd?\xdc$P\x8bM\xb5\xbd\xbf\xd2\xe5\xa4M\xdfx\x01@\x8dn\xf7\x0c\x04\xa4\xeb\xbf\xf97\x80ub#\xe1?\xe6\xc3\xc7\xa8\x94\xf3\xd4\xbf\xf5\t9\xa5\x15\xf9\xec?\xae\x18w\xed\xf9\xba\xf3\xbfbY\xdb[\xc9{\xed\xbf\x89\xfd\xb2V\xae\x0c\xf7?w\xe4\x0c`U-\xd3?j\xa5\xdc\xef8 \xb1?\xd7h]\xc8\xdbD\xe7\xbf@\x1cgg\xeb\xe9\xe2?\x96\x0c}\xa7\xdcE\xf4\xbfikE\xe6I\x9a\xe4?^\x13\xc2:\x9b\xd2\xff\xbf\x04\xf2\x84\xcb}A\xea?jT\x87\xde\xa9\x89\xd5?dn\xc8\xf1\x83\x08\xe0?[\xd6\xbf\xeb\xf5Y\xd8?=\x9fO\xb9\xc8\xbe\xf5?\xd6g\xa3\xb8\xc6\xfd\xf0?\x82\x16\xa5\xe2\x84\x9f\xfb?m\'!Wt\x00\xe5?v\xf8d\xd9l\xf8\xd8?\x96\x98\x1fX1\x03\xea?\x1a\xcck4U\xb9\xe3?\xdf2Y\x9f\xd6`\xd7?4\xee\x8a|\x90\xc9\xc6?\x8b\xce,\xfb\xdc#\xd3?T\x0b\xcc/^\xd1\xf3?\x92t\xce>/\xf1\x9a\xbf\xc4b\x93\xa9!\xfb\xfa\xbf\x15\x14\xec\xb8\xdfm\xd0?\x8e\xbc\x91\x96\x19\xa4\xfd\xbf\xa2\x92\x84\xff)\x17\xe2?dx\xc6\xaa5\xaf\xdc\xbf1\xc8s\x9e\x82\x03\xf4?\x04\x9e\xba\xc1\t\x92\xc3?;nW\xf5\xa5\x0c\xc2?\x01l0\xc5=\xa4\xfc?\xabt@\xc2\xb0n\xfe?\xcbE]\xf7\x83\x8e\xe8?\xeak\xb8o\xf6y\xf8?.\x8e\x8d\xc2\xeb3\xf4?\x80\xf0\xa8Q,\xc3\xc6\xbf&F\xf2A)\xf5\xf9\xbfCENQ\xa8\xb4\xd3?E#\x12u\xdf\x03\xba?*\x9f\xf4{Hn\xc9\xbfd\xec~\x8c\xd8\xce\xd2\xbf\xc4J\x84A\x87%\xf9?\x81\x93umm\xb6\xd7?\x81\xa8\xb7=%\x08\x04@p\xab\xfe\x81\x11\x88\xf2?\xfcA\x9c\x96\xf5\xd0\xf4?;Kr\'\x85\x97\xd4?A\x02\xa8\xc4\xe8\xda\xdb?\xcc\x0e\x14\xc5\x11\x94\xcf?\xc1d\xd7\x0c\x1dy\xea\xbf\x82\xe0\xb6H\x03\xe9\x00@[w\xabH\x93\xb2\xd4\xbf\x89\xba5\x89\x1d2\xc6\xbf\xaf\x88k\xb93J\xec?\xef|\xdc\xf2\x82\xe7\xed?\xff\x15\x89\xfa\xc0u\xe3?c,C\x9bT\xf7\xd7\xbf\xfb\xa3\x06\x0b\x9f\xc5\xf0\xbf\xf7\x9c\xdb\x1a\x86=\xf3\xbf\xd0\xa5[\x1cKU\xe8\xbfCV\x92\xea5P\xe4?0\xe6\xe427\x02\xe8\xbf\xb3"\xe0\xf7\xa6\x10\xc3?\x14\x17\xf8\xae6\xf9\xdb\xbfv\xff\xfe2F\xa3\xe1?8\xbfk\xd2\xa1,\xf7?\xe1\x15 \x83\x99\xa7\xbd?%\xd0eV\x16d\xad\xbfR&I3km\xdd?\xdeU\xd5\x97\x96x\xf8?\xfc\xeb\xac\xd1\xd4f\x82?]\xa5a\xb2kA\xda?<\xe8\xeb\x0fZ\xc7\xd1\xbf\x97\t\x17\xff\x12K\x01@\x90\xf1aq\xe9\x8e\xff?\x85cm?\x0b\xc0\xe7\xbfA\xd0\rT\x04\x10\xd3\xbfTf\n\xe5h\xb6\xf3?\xd5\xf3\x91\xa2\xc1\xe0\xe8?+\x8f\xb9\x86\xc2Z\xeb\xbf\xd9(\x01v\xdcu\xaa?W\xf2r\xd0K\xdf\xd7?Y\xcf"\xe2\xaa\x85\xe6\xbf\x99\xcci\xb8\x19w\xea?\xee7\xcf\xf7U\x85\xf1?\x0e\x85\x07\xd2f\xa6\xea\xbfA\x96(\x9b-`\xd6\xbf\x9fNt\xbc+w\xf4\xbf\xe6Rk\xd7\xf5\x1e\xf2?\xa0\x1aS\x023\x19\xef?\xbb\x1aC\x1a\x87w\xe3\xbf\xdbk6\xf1\xa8B\xc9\xbf\x92\xaaB\xb1\xe4\x19\xda?\xc0\x9d\xea\x94\xd5\x8b\xc5?v$iu\x151\xd6\xbf\xd1#\x8d\xfb\xf5t\xb9?\x1e\x969\x0e\xc9\xc9\xf9?\x15\xe7\xda\x91=y\xf2\xbfkQ\xcf\x14\x151\xe6?\x01\xa2q\x03\xa0%\xe5?\xdcX\xb9\xea\xe84\xf0?~\xc5B\xcc\xbf]\xdb\xbf\xca\x06\xd0\x8ciW\xcc?\t e\xd5a\xd0\xdf?Jt\xdf*y\xc1\xf1?\xdbg\x7f*\xb0,\xf4\xbf\x05]-\x96I\xd8\xf0\xbf\x8f\xf9\xea\x9c,\xfd\xe6\xbf\xd3\xc7h\xff\xd7\x1b\x01@\x8bh\xca2\xfa\xba\xfc?\x83\xb3\x0b;)3\xde\xbf\x93\x1d\xa5\xd7+|\xe4\xbfg\x99\xe7\xda\xb6\xdb\xd2\xbf\xcf\xe4\xb1\x8cB\xd8\xcd\xbfsz\xa4\xda\xe6\x88\xdd?)L\xe73\xaa\xbc\xd7\xbf\xaf\xecL\xb0\xfd:\xfa\xbfnHzCIh\xde\xbfbH4B\xe1\xef\xba?\xdeO\xd0\xf4\xc1\xf2\xdd\xbf\xee\xc1Fq\xea\xf8\x00@\xd1k;S\xdd\xd4\xd9\xbf\x96\x18\xd0\xcd0\x87\xf0?\x8b\xdb\x96\xa5d\xb1\xf0\xbf\xe6\x10|Wc\xb5\xdd\xbf\x1d\x0b\x9e\xd7\x06w\xc1\xbf\xdbbS]\x98)\xbb\xbf\xc3[\xe3\xea\xac\x90\xea\xbf\x06\x1c\x95\xad=\x1e\xf8?\xea[\tAA#\xe3\xbf\xaa\xe7*\x13\x01\xfd\xd3\xbf\xe7\x9b\xe6j\x1b^\xe8\xbf\xf6X`\xd9E\xc6\xd4\xbf\x00\x89\x97\xe3\xda@\xe3?\xe7\x06\xbf\x04\x19\x86\xdf\xbf\x06\x10\xaf38u\xf6?\xff}\x8c\xb9\xa6u\x8e?\xcc\x06t\x939\xa3\xe4?\x07\x0fC\x8a\xb8r\xed?~j\xdb\xe7\xdc\xc6\xe6?z\xed}a\xb1\xd9\xd0?\x1d\x81\x066\xf2\x7f\xd4\xbf\x86|8+a\x14\xd1?\xa8Tm5=\x1a\xeb\xbf\xdcD\x8d\xa3)b\xf0\xbf\xbe\xc16\xc5F\xab\xdd\xbf\xfc+\x9b\x1d\xaf\xa70?\x8f("\xb2\xb0a\xfd\xbf\xcb\x07\x9f:u\x1a\xdd\xbf:\xc1\x8f?=\xe4\xee?\xd3=\xe8\xe4r\xc9\xf2\xbfY\x10c\xa8\xc9y\xf1\xbf\x18\xf8Q\xeaD\xde\xf5?\xd2;\xfb\x19C\xb2l\xbf\x8aF\x03\x19\xa7`\xe4\xbf$\xc3\x15X\xa6\xd2\xf8\xbfuq5\x04\xc9\x87\xf0\xbf\xdba\xf9K\x95\xe8\xf1\xbfe\xfe\xeb\xdb\xba\x04\xed?\xdd!\r\x05T\xb4\xf6\xbf6\x7f(\x80z\x10\xec\xbfO\x95m\x99\x9e\x88\xf0\xbf\x07\x9d\xdcpgY\xe7?\xd0\xa0\x18L\x18+\xf1?\xee\xc6N\xe8F\x01\xdc\xbf?B\x96v\xdeF\xce?w\xe7\x08\x82\x16\x86\xe9?tmn\t\xec\xd7\xd9?\xd1\xefrw\xd0\x83\xca\xbf\xa6\xa9\x9d\x1bk\xbb\xd3?\xddC`\xbc\x9f\x7f\xd7\xbfh\xf5\x10\xbd\x9a(\xfe?Qz\xde\xe6C\xa7\xbd\xbf6\xa4\xfd\x9a\x1fM\xcc?\x99x\xc9\x12\xde\xe9\xde\xbfb\xbbKU\xe3\x0f\xee\xbf\xd6\x18\xda\xc4\xe28\t\xc0\x15\xbf\x97\xef~(\xed\xbf\xaa\xc3\x05@\xf5\xc2\xd1\xbf\xe6\xde\x1dx\x19\xe3\xfe\xbf\x84\xabQ\x0f\xfe\xa3\xf3?q`\xc8r\xe3J\xfd\xbfy\x1d$G\x9aE\xf6?;\xc7\xef\x8e3\xa3\xd1?\xde\x1c0\xdc\xa3\xee\xcc?\x10C\xb5p6\xd6\xea\xbf\xf8\xca0\x8d\xc3\xba\x92\xbfR\xf9\x82\x03r\xf0\xbd\xbf\x16;\x16PC\x87\xda\xbf\xfd\x05_\x0f\x02\xc0\xd6\xbf\xd4\xf32\xb5\x0f\xc3\xe4?\x82\xf5\xaed\xc6p\xe9\xbf\xc7\xae\xaaZJR\xd7\xbfg\xc3.\xa8\x0b\xcf\xeb\xbfw<\xd0\x1cR6\xef\xbf\xe6\x05 NQ\xf5\xf3?\xc8\x89\xacI3\xbf\xab?\xf8P\x16\x87S\x19\xec?{j\x11w\xfd\xdc\xe2\xbf\xf4\xb2\x94\x1e\xd3\xda\xd3?\x80\xb4\xfd\xef\xca\x92\xcc?\xa7\xfb\xba\xed\x1f>\xef\xbf\xbf\x1d\xa0l\xa34\xdc\xbf\xa5\xad\xa4s\xce`\xe1\xbf\x95dp9\x99P\xe2?\xfd\xe0\xc3\x1d\xe7\x12\xec?\x94^\x86\xd2\xf3x\xe1\xbf\x8a;\xbb\xa7\xa1\xf2\xf8?\xd2\xe4\x91\x85\xbb\x85\xf1\xbf\xd1sN \xb5*\xd9\xbf/\x1d\xd1H\xbe\x15\xc8?\xb0_e\xa3\xb2\x03\xe9?\xe3?\x1c\xd5\xc3\xa1\xf5?4\xebE.\x8f \xfd\xbfaO~\xe4\x80\xc8\xa1?\xb3D\x8a4\xa6\xad\x94?\x8f\xf1\x9d\xd8cw\xd1?\xb9\x01\xb3\\\xe2\xba\xf4\xbf\xfaF@s\xc8\xa4\xf4?\xe3 .)\xc2Y\xed?#C&\xa7\xe5\x1c\xc7?\xd5\xcb)\xa2\'\xe1\xf2?\x80*9j\xce\xe0\xf3\xbf\x98\'<4<%\xd2\xbf\xd0\xfd\xb91\xe5\xda\xf6\xbf\xe3\x86&$V\xf3\xbf\xbfT\\\xcb\xac\xd36\x86?#{Qo\xf4L\xdf?I\xfa\x96\xdf\x8c\xa1\xe5?\\\xaePq\x05\x1b\xf7\xbf-c&\x9e\xa0\x1b\xee\xbfp\x04\xff#\x99\x18\xe4?}\\\x88w\x88\xfb\xdb?\xdc\xf7*O\xe6\xe6\xea\xbf>\x9e\x04\xfa\xc5O\xe1\xbf\xbf\xd4\x98L\x8e!\xea\xbf\xa7\xdb\xbd\xf0,\x13\xf1?\x07\xf2g\xd6GY\xcc?\x15S\xb9|\x00|\xd5?i\x94#\x04\x03X\xe4?M\x07<\xef\xdf\xbf\xe3?\xec\x83X\x0b\xc8\xba\xf3\xbf\xc5\xd3\x05NA%\xfb\xbf\x1f\x1cc_\x15\xba\xa5?\x10/qHQ\xb6\xcc\xbfQ\\l\x12q\xd5\xeb?V|L\xb2\xfb\xf4\xc0?\xa0j\xe1Y\xdc\x9d\xf0\xbf\x8c#`\xfa\x99"\xf7\xbf\xb5z\xfd$\x11\n\xff\xbf\x9d\x98J\x18\x8f}\xe7\xbf;N\x9e\x19\x9e\x08\xdf\xbf\xf8\xc5Y\xf1\xeb\x0b\xf0?\x9c\xaf\x860\x84\xf0\xd7?\xc6R\xc3\x87\xb0\x8f\xdd?\xc5\x98dQ-\xc7\xd8\xbf.\xfc\xf7"\xa8\xd0\xf2\xbf\xef,\xb4\x01\xda\xae\xd9?\x01\xe5<\xa3J0\x01@\xb4\xd2\x0b\xf3\xf4&\xee?c\x19 \xca7\n\xe0\xbf\x07r\x98{\xd0\xf1\xf2\xbf\xf7m\xfc\xb3\xea\x98\xb9?\xb0\x01R\xae\xe61\xe9\xbfNce\xe9\xae\x8d\xc3?\xc6a\x07\xe9p\xeb\xfa?\x9d"\x93\xf3Ei\xf9?\x8a\xee\xfc\xc5\xe5\xea\x0c\xc0\x11=\x8b\x95\x00\xbc\xe2?\xd9T1\xcc\xfel\xf3?\x8a\xa8\xcf}\xa1 \xd8\xbf\xe0\xda\x16\xb5>p\xbd\xbf\xe7\xeb\x96Q\x8b\xa2\xff\xbf\xf5\xbc\xee\xd9\xde\x9a\xe0?b\xb6\x9b}a\xbc\xe9\xbf\x7fz\xa7\x04g\xf5\xf0?)\'\xe4\xadN\xe1\xfb\xbfI\xbb\xf0\x94r\xcd\xbb?\x7fU\xe6\x01\x8b\x9e\xf3\xbf\x8dX\xa5\x97e\xb0\xf5?\x9b\xb9\x9e\xc5\x18)\xea\xbf\x14\x07\xd2\xbc\x91B\xfb?\xce\xeeU\x91<\xde\xf6\xbfk\xe9\xb7\x18\xda\xce~?\xf0\x9aRnH\xbe\xdd?\x1d\xee\x8d\xe2&\x17\xb7\xbf\x15\x9a\x02\xbd?\xf2\xe1\xbf\x0c\xf3g\xb9C\xac\xee\xbf\x85\xdf\x93\xe8\xb6\xab\xec?\x87\xd7\xf9t%\xa0\xdd?\xed\x9b\xf2\xce\x91\x80\xd9\xbf\xa9\x96\xf8\xefA\xcd\xf8\xbf+\x18\xb3\xb2\x95\xb6\xf1?Ca\x87I\x86\x7f\xe2\xbf\xa5\xcf\xe8\xc9Q\x94\xef?B\x98ezl\'\xc6\xbf\x9e\xd4\\\x837\xcb\xc5\xbf\xb3\xf2\x0b:\x97{\xda?\x823\xd4\xdfI\xeb\xf6?m\x85\x02,e\x1c\xf2?A\xee3\xaa\xbe\xcd\xe0\xbf\xfa#\x88\xc9v\x0b\xf3?\x06c]2\t(\xf3\xbf\t\x8a\x8cZ\xcdb\xe3?+B\xc7\xfe\xc8\xe5\xf0\xbf\xa7\xd2\xe8\x11\x01F\xc5?\xaf\x10c\xe5\xb7~\xe0?\xb9\x9d\xfeF91\xef?yA\xba\x86:O\xe7?\xa8lnh\xc0\x11\xca?P\x83\xf6\xe2\xd0\xbd\xe8\xbf\x86a*\xa6=\xd4\xaf?\xbaXg@\x8c\x81\xf8?\xd2\xaa+\x9fDr\xf2?$w\x01\x89@\x16\xfd\xbf\x8ci\xfc@\xf3\x1e\xf0\xbf\x9f\x90\xbc\xe4V<\xe8\xbf\xf1\xc1)V\xa1B\xce\xbfk\x9a \x97\xc9\xc8\xc1\xbf\x8f\x97\xe9xV4\xe7\xbf]Q\x05x;Z\xde\xbf)\xd9~U\xca\xaa\xef?\x0eG\xfb\x06=\xc2\xc8?OHA\xde\x0eH\xa8\xbf\xea\x8e\xbe\xa7\xba\xac\xdd\xbfu\x9c\x1d\xb8\x93\xb1\xc1?\xa0\xe7b\xda\x82Y\xf2\xbf\x13E\xb6T{\xee\xe3?7\x04\xdcU\xf0\xd5\xd8?v\xb3\xfa\x96\xfc0\xf6\xbf\x99\xd3a\xe6\x0c\xa0\xe4?\xca\x9d\x84\x0bF2\xb0?\x83\xab\x07\x02K\xce\xb7?\x1e\xcd?\t\x8e\xbd\xf9\xbf;\xf4\x93)\xb7\xf1\xcd\xbf\xe4\x0f\xc1n\xf0S\xea\xbfMx\xaaF\x05\x08\xf2\xbf\x05\x9e\xffS\xfd\xf9\xf4?\xf0\x0b\x0c\xf8\xa0\xb1\xc9\xbf<\\V\x15\xd6O\xfa\xbfZ\x01W\t!$\xf8\xbf\xdai\xd1\xfa/e\xdd\xbf\xa2\x07\xf9l\x95\xb9\xf5?yK\xdc\x9b\x12\xc6\x05\xc0\x8d\xfe\xe0-\xd1\xa6\xfa\xbf\xcb\xcd\xf7D\x9b\x93\xfd\xbf\xfbf)\x10|I\xc9?R\xbej]\xc60\xf6?\xe0]XY\xfc\xd7\xd7?\xdbio(R\x1c\xfd?\x9b\x9f\xad\'\xc0O\xe4\xbf/\x02-\x87\xb4\xea\xea\xbft\xa3\xc4\x07\x7f\x96\xd6\xbf1)a.Uq\x02@\x0e\x9c\x16&h~\xea?\x9f\xec\x9e,pX\xf3?6\xbeA\xf2\xb2\xc8\xf9\xbf\x16\xc2`\x03$\x14\xbe?\xa8\xde\x88\x96\xc4$\xdf?\x93\x8f\xd8\\\xe7\xf2\xe5\xbf\x8ex\t?\x0c\xa7\xb7?\x88GMY\x06\xd4\xe9?e\xb0\x81\x087\xf0\xd5\xbf\x12\xc3m\xaa\'_\xf7\xbf{\x80u\x0c\xae\x04\xe5\xbfx*Q\x16\xe6\xde\xed\xbf\xe09\xc3\xf7)\xab\x01\xc0\xcfmYL\xec\xc1\xf2\xbf`\x1f1\x8e0\x13\xc8\xbf\xe4\xb9F\x10\xb4\xa6\xb3\xbf[\xd5N\xfa\xe2$\xf3?\x1b<|\xed\x01X\xe4\xbfU\xdb\xc3J\xfd]\xdb?q\x82\xe6\xf9\xd6\x07\xe7\xbf\xd15\xa5\x84\xc5\x1a\xf9\xbf\x80\x98\xed\x13\xbd\x00\x00@P\xd6\x1c0b\x03\xdd\xbf%MLA=6\x04@\xfe\xd9\x82\xb8eJ\xe3?\x87\x97\xe4\x0c {\xf1?S.]\xaeaI\xdf\xbf\xc6\xadu`(\xfd\x03@<\xfb\xb9\xbb\x07\x97\xff?\xf3\x86\xd8%\xa6\xee\xc4\xbf\x07\xd3\xc9\xbb\x93\xb7\x9d\xbf~\xdbt3&\x0f\xe5?\xd7 \xa4\x0f\xc1N\xf4\xbf\xea|\xbc\x82\xed6\xef\xbf\xe1v\xd8O\x96`\xf3\xbf\xb6d\xdb\xbf\xc5\x1c\xb6?s\xc7l\x97\xb7\xba\xde\xbf\xeb\x1b\xbb\x93f\xf7\xe7\xbf\xc4\xabx}CB\x0f\xc0s\xacy\xae\x80\xbc\xdd\xbf\x7f*\x9a&\xe0\xd5\xc5\xbf\xf51\xa7\'z>\x99\xbf\x9d9\xea\x93\x98\x83\xe2?A?\xc6\x86&.}?\xb4f\xcf4v\xfe\xec?\x05\xebu_aO\xf4\xbf\xad \xfd\xe9K\xe2\xf3\xbf2\xae\x94\xa2I\xe0\xfb?o\xe5;\x836]\xf5\xbf\x91\x12N"U\xb1\xe1?\xfa9H\x02R\x01\xcf?\x8b\n\xa3\xac\xd7\x82\xd2\xbf4\xbe\xc2\xab\xfe\xb3\xd6?D9]\x87R7\x00@\xcf\xbaP8X\x9b\x00@\xe9\\\x04\xe5\ts\x00@m\t@\xa6\x84\x90\x07@\x04\xa8\xe9ZB\xd3\x0c@\x0f\xb8\xb4\x1a:9\xf2?\xb0\x0c\xa9(\x8b4\xf8\xbf\x16\x0c\xe5\xef\n\xd4\xf9?F\xff\x1c\x05\xcfu\xe2?\xd4\xd6A\xe3,z\xe7\xbfW\x0fl\x12p\x96\xfa?_\xed\x0e@96\xee?#b\x11\xb6\x9d\xa9\x08\xc0.A\x8c\xeb\xafk\xeb\xbf\xff\x97f\x99\x1b\x82\xfc\xbf\xd0\xddsnB\x95\xf4\xbfNB\x86`\x1f\xfb\xf2?XQ\xca\xe4\xfd\xdf\xc7?\x92\x81\xf5\xb5G\xe7\xf2?\x8f\x08\xd4\x11\xed*\xd7?\xf55\x9d\x8c\x8b\xb2\xe8?\xef\xc9\xf9\xe0\x8f\x82\xc0\xbfYm\xa0\x00\xba\xd1\xf2\xbf\xcf\xeb\x1a\x97\x99&\xe4?\xdb\xfeR$\xbf\xba\xf7?\xda\x1c~_\x07\xed\xf0?\xaf\xc6\xafg^\xa8\x01@-\xe2\xeb\xb7\x1b\xfc\xfd?\xe4~T\xdd\xa2$\x02@X\xd0\xdb74\x89\r@\xd2\x81e\x19bM\x07@\xe8\xce\x17VC\xe9\xf2?a\xa7$Q\x8d\x06\xe4?b\xb6\xf3}1\xda\xf4?\xb4\xa15`x\x17\xff\xbf2\xe5E\x9f\xd7\x08\xec?\xf4\x90?\xdb\x88=\xdb?\xf5\xb32\x7f\xe0\xd5\xfd\xbff\x08\xa2\x1b\xd6\xa0\xf5\xbf&\x93\x064=\x81\xf9\xbfT\xdd"\xd4\x02\xb3\x01\xc0\xed\'\xd3\xec)\xe6\xe4?\xd3\xa0w\x1a\xdb\xed\xca\xbf\'\xb6\x96\x11X\x1a\xd8\xbfX\\\x19\x92(c\xdb\xbf*\xf3o\xc3\xba/\xe7\xbf\x0cT\xaa(6d\xf5?\xf1\xb3\x9b\xd3\xd7G\x01@\xda\x99\x14<\xfa\x89\xfa?5\xbc\xff\xa42\x83\xfd\xbf@\xaf\xddJ\x07\xa0\xf1\xbf;\xbbj\xe3\x8e\xf7?\x10\x80\xfb\xa1\xc7\xd0\xbc?(\xbc\x18\xa9\xd8n\xa3\xbf\x14=\xdb\x8d\xf0\xcc\xd6\xbf]\xb7 \n\x9e\xdb\xe8\xbf\x96~\xab\x14\x84:\xdf?\xbf\xda\x81)Z\xb1l?\xed\xa9\x0cW1_\x07\xc0\xa9\xa0\xb6\x06\x94p\xf5?\xe0[Uo2O\xf9\xbf\xcb2]T\x93\xec\xd3\xbf\xc4\x9bw\xf8\x91|\x89?\x89{\xab\xcdU\\\xf8?\x04\xba\xa6\x87+\xf5\xbf\x04\xea\xc8\x8dq\x86\xf4?\x8e\x00,\x85\xe7!\xf0?V\x8c|\xb5\xdex\xff?pj\x15\xae\xe11\xf3?\x96\xfe\x90\x15]\xb5\xed?\xae\xeb\xdd\xc7\xe2\x86\xf3?\xec\x8b\xdb\xda3k\xef?\xd5D\t\xe7/\x86\xdf\xbf0C\xa9\xd8B\xe9\x04\xc0\xfb\x95wO\xd7\xe5\x0e\xc0\xf6\xbf!\xce\x19\xe7\x11\xc0\xbd\xc3u\x96\xfb\xd6\x0c\xc0ht\x8b\x15\x0cC\x15\xc0:\xd8\x91\xe1\xe6\x9e\x1a\xc0vTL\xf3y\xf9\x16\xc0\xd9\x0c\x9c\xfcq\xa3\x10\xc0\xa5\xf5\xbd\xa3\xec~\x07\xc0\xb6x\x85\xd5\xfa\x9c\x06\xc0\x01i\xd9\xcca\xae\xe3\xbf\x19\x04\xb0d)\x7f\xbb?k\xcb\x87:\x00\xc8\xfe\xbf?\x8c\'Muf\xfe\xbf\xf7\x85\xbe%\\K\xe4\xbf\xbfD4`\xa7\xd9\xeb?\x15_\xc9\x11\xa3\x14\xd7?e? Y8\x92\xe0\xbf\xf0\x00\xa2\x88\x86\x91\xb6\xbfH\xc3\xe5"\xde"\xf7?q\x7f1K\xc5X\xc8?\xd9\rX7\t\xf5\xde?e\xed\x95\xb2\x13\x97\xef?\xfd\xf3\xa7$v\xab\xec\xbf\x82\x18\xeb9A\x08\xd1?\xed\x1aK\x19\xb1o\xf9\xbf\x8d\x9f\xa7\xd7\x8dz\xe4?\x7f\x9cbB\xbdc\xe8\xbf[\x8a\xff\xb5\xe8\xf3\xef\xbf\xbb\x8dy\x80\xe4B\xf4?W\x82\xb2\x00\x7f]\xd6?\x17\xb68\xfa\xa0u\xf0?\x10S\xdf\xe8\x89\x01\xea?\x13\x16\x91\x83\xf7V\xf5?\xc1B\xf3\xed\xf2\xaf\xef\xbf\xef\xc0\xe4\xf7\x02\x87\x14\xc0\x8e\xb7\xcd\xb4I\xc2\t\xc0,\xfe\xcfu\xc07\xf1\xbf\xf4)\x0e\xc2\x15\xf3\xd1\xbf\xe5\xb1\xaf{\xf0\xb7\xdc?\xf1Q\x9f\xa3\xbdY\xa1\xbf?,i\xd7\xed\x1c\xc4?\xf4`\x8b\xb0\xdaD\xd2?M\xee\xc6\x08\x05m\xc4?\x06c7p[\xd7\xd5?=\xa97\x19\xb4[\xb2\xbfQ\x82[\xf5\xde\xfb\x82\xbf\xc8\xca!\xf2\xdd\xa6\xe3\xbf\'\x12\x94\xc3\xc9\xb2\x97?\x018\xd1\x0b%\xe8\xf6\xbf\x90\xe6y\xbeK{\xd5?\x00\xb1\xe2\xd4e\xed\xe1\xbf\xe4\xd3\x8c\xa5\xd7\x81\xfc?\xcaO\xed\xe9\x04"\xc5?D\xef\x87a\xef\xb8\xdf?4\xc1t\xf3\xfc\x99\xee\xbfw,n1i\xc5\xfe\xbf\n\x1bU#\x8b\xcd\xfc\xbf\xd6\xfc\x81GAm\xfb\xbf\xabN\xa3H;k\xf7\xbf>Q\xa9G\xba\x19\xf4\xbf\xd3~\xb2\xc9H\xafs\xbf_\xc5\x01\n\x94\x1e\xfb?\x10d\xe5\xe5\x8dW\x9f\xbf\xfc\x8a\x079^\xfb\xe7\xbf\x95\x05\xab^\xbcN\xed\xbfW\xa4ib\x13|\xf1\xbf\x8d\xd6\xbd\xf9`\x96\xf4\xbfE\xe8\x8c\xdfT\x96\xda\xbf\'\xa7\xaf\xbc\xf6\xa5\xe8\xbf\x85 \x06\xc2\x08\x1a\xe3?\xaf\x9cu\x88\xec\x00\xef\xbf\xee\xcf\x9f\\\x91\x7f\xd5?\xa2_S\\b\xe8\x93?\x04\xc0\xf5\x11\x93\x91\xf3?\xabc8\xa2\x14\'\xe7?\x97\x154\xec\xd7^\x02@6k\x97\\\xdd\x90\xe4?\xdbF\xabGO\x01\xe4\xbf-\x8f4\xf2v\xf8\xd4?\x07\xb1\x15a%\x87\xda\xbf\x11\x8d\xe1o\xa2Z\xd1\xbf\xe3\xab\xb6\xe5(^\xce\xbf\xd4\x84H\x87\x1di\xd2?y\xd4)\x0f)*\xf4\xbfB:\xe6\xf9\x9bf\x00\xc0\x80\x82\xc8Rwm\xfa\xbf\\\xf6\x94\xbf\x19\xa6\xf5\xbf(\x129\xb6O}\x01\xc0\x8f\x05\x95\xc7Md\xe1?\x9d\x07\xadMd\xe8\xee\xbfw\xe7\xef\x07\xef\xde\xf2\xbf\xa6\xe1\xd7\xf3a\xcd\xf7?c\xa5\xd5\x16\xe1\xe7\xeb\xbf=\x98c\xed\xc6\xa6\xf6?\xe0\xc4\x1f\x04/>\xe0\xbf\xaeG\xb8\x88CU\xf4?\xb8[vQK\x7f\xcb?\xb4\t\xf7?8\x15\xf1?}\x96 \x8b\xdd\x87\xee?i\xe3\xf6\xf0\xd5\xcc\xc6?\xacb}\xed\xba\x11\xf5\xbf\xc1\xc2e\tn\x1a\xe2?\xac\xda.\xc2\xae\t\xf8?\xd0\xf5\xa6IPf\xe7\xbfz\xb3\x8dJ\xa9G\xe0?\xa7\xf4\xec/\xa0\x1d\xfd?\xaf\x84\x04ae\x9d\xe3?\xfa\xde\xf4 nX\xf1?\xbeU\xe2\xb6\x10I\xda?\x01\x1a\x83\xd7pL\xe7?"9I\xc0\xb6\xbc\xea\xbfu\xbc\xe2\x92\xc7\xc5\xf3\xbf|\xe34\xa9\x8c\x99\xdc\xbf\xd7\xa10Nw;\xea\xbf;F88Y\x85\xfb\xbfW\x91,\x9c\xab\xf3\xf4\xbf\x1c\x0c\xa8\xe3\xf6\xa7\xb7?jcG5\xf5\n\xf0\xbf\xca\xf4%\xbf\xd6:\x03\xc04=\xad\xc6\x9f\x8c\xe7\xbf\xf7\x82\x05YI\xce\xf2\xbf\xbd\x0c\xdb\xeb\x7f\x82\xe1\xbf\xf3\x80\xd6\xf0\xf7c\x8d\xbf\x92};W\x9f\x03\xec?\x99\xc0\xda;\x01P\xbe\xbf\xd0\xf8h\xf7\x06\xdf\xdf\xbf\x01y\x85\x9f\xa1\x18\xcc\xbf\xc6u;\xdc\xe3\xcc\xf2\xbf\r\x8f\xf1\xd8\x93$\xe5\xbf$\x1fQ\xc9\xb9\xc3\xd4?\x16\xd9w\xf64v\xde?\x1an\xbc\xa4r\xcb\xf1?\x90o\x84\x10\x01S\xc0?\xedni\x1a\xb6y\xc9?\n]Z\xc4_h\xf8\xbf\xd2\x92\xb4\xad\xd3o\xb6\xbfv\xbf\x9e\x0c\xaf\xca\xd7\xbf\x9f\xb7B\xe7\xe1\xb2\xd2?\x97\x0cY\xa0\xa8\xe3\xc9?\xf3l2q\xd8N\x06\xc0\xf1\xccx\xbb\x17\xd4\x03\xc0\xcb\x81jC%>\xf6\xbfd\xbc\xf0\xc1Jl\xfb\xbf\x88\xda\xd0\x1b\x9b\x8b\xe8?W\xf4\x7f8\x0c\x89\xe5\xbf\xb2\xc7\x98\x1aOU\xd8?\xa7\x01\x15\xa3Z\xb2\xf0?\xe4\x91\x95\x88\xf3i\xf7?\xc8\x9fE6f\xc6\x02@$\xbb\'\xad\xaaP\xca?\x93\x0c\xe7m]\x1b\xf4\xbf6[:\x7f\xe8\xc7\xf5\xbfst\x17\xed4g\xf8\xbf\x1b\xef\xde*L\xc4\xf9\xbf/\x16\xe0\xce"\x99\xf9?\xcc\xbf\xcb\x05\x8f\x96\xec\xbfx7\x99\xc2\xe3t\xfa?CSq%\x92m\xc5\xbf\xc9S\xa8\x01a1\xe1\xbf\x03\xafR\x80\\\xde\xf5\xbf\x18\xb4(\xbdK\xfd\xd7?R\xa4/\xb57\xa2\xe4\xbf\x0e;\x94J\x8d\xa1\x00\xc0\x9e\xcb\xb0\xe8\xd6"\xf1\xbfY\x9c4\x8b\x89\xce\x87\xbfkF\xb8\xfa3\x8d\xc6\xbf\xd1=`Wz\x94\xff\xbf\x14\x03\xe7\x88\x1c\x19\xf7\xbf\xf1\xbcL\x93\xdb\x19\xd1\xbf\x82\xf7y\x8e%7\xf8\xbf}\xe3\xe1\xb2\xc21\xcc\xbf\xa6\\\xfbBA\x1f\xf8\xbf\x87\xb9\xd1\xb9\xc6\xb6\xf6?\xffA:\x99\xe2\xf0\xaa?Q\xee\x9bG\xc3\x17\xf8?\xce \xb7\nI\xf8\xa5\xbf6U\x9f\xb9\x8cx\xf4?q\xf6M-\xfe&\xb3?\xdd\x05\xe9\x01q\x18\xfd?\xba\xc8\'_-:\xb4?\xba&%b\xc8E\xe7?\xe3\x1bey\x8ee\xe1?\xca\x96\xb1?U\r\xf5?:a\xeb\\\x83\xfa\xe6\xbf\x8b-.?\x86\x05\xf8\xbf\xc3\xd8%\x81\xbe\xe2\xb5\xbf\xe9\xdc\xe0\x99B\x17\xef\xbfqm\xb3\x14\x9f\xf4\xd3\xbf\xdbLd=\xa0K\xf2\xbfMc\x00t8[\xf5\xbf\x04>\x1ao\xdc\xa8\xe9\xbf\x99\x1c<\xee\x9b\xc7\xd4\xbf\\h\xcc\xde\xe1E\xab\xbf\x03\xb0\xce\xde\xf1"\xfb\xbf.B\x08\xba\xcc\x1b\x01\xc0\xb8\xbcm\x88\x01\xd6\xd3?V\x00\x04R>\xdb\xe9\xbf\xb5 5\xb8\x133\xe4\xbf\xbe\x81kyJ<\xf8?\xee=\x91&\xed\xef\xf2?\x18\x9f\x92AFI\x05@\xee,\x94\x0e\x08\xe7\xf7?o\xa9\x81V\x9f\xc9\xf2?\xba}{\x8evJ\xf8\xbf?lB<\x80\xce\xf2\xbf\xc7\x1b\xd1$^\xf0\xe1?X\xf5\x8aS\xfeN\xd8?\xd6N\xcfF\xd0\xfc\xd6\xbf\xba9\xe3lI\x9f\xed?s\xc3\xc7\xc7\x99\xf3\xec\xbf\x0fS\x90\xdc\xb2 \x02@L0\xfe\xae\xdcz\xdf?\x17\x1f<\x80\x02\x8a\xc5?\xacm\x8e\xfe\xe2\xc7\xf1?\xffM\xbd6\xfe\x07\xc9?j4.\xf3\x10\x1f\xea\xbf\xfa%5$;\x1d\xf5?\xa7.\xa4\x08\xc5\xd2\xf0?\xffq\xeb\xa1&\xd2\x07\xc0\x04\xb9\xaa\xb4x\xa1\x04\xc0\x90\x83k\x03i\xdc\xf8\xbf\x9b\xc1zF$\x01\xea\xbfF\x0c\x91\xc1\xe7\xa0\xfc\xbfZ\xbb\x87[\xe8\x84\x0f\xc0\xd9\x91A\xc9\xfca\xe7\xbf\xed[\xf1\x05\xee\xf3\xfc?\xdf\xfb\xe2\xe2?A\xaf\xe7\xb7\x8f\x83\xe5?\xf1\xf3e.\xa4\xae\xfe\xbf\x8f\x8c\n\x96\xfe0\xdf\xbf8:,\x80\xf6\xd1\xc3\xbf\x9e!|\r]\xac\xed?1\xf1/M\xb3\xa7\xed\xbfmu\xb5\xc8\xb2\xc3\xe8?\x13Wx\x8f\x08\x15\xfd?9\xac\xd5\xd4\xe6\xdc\xf0\xbfv*\xd0\x0b\xe0h\xfb?]\xb2\x91f\xc3\xc6\xdc?*\xe9Dz\xffB\xed?\x10\xd9\xb7\xbb\xee\xdd\xb3\xbfk\xc61\x1d\x94:\xd1?\xda\x9e\xba\'\xc1f\xe2\xbf,\xaf\xba\x8f\x7f-\xf0?6\x8d\xb8\x06\xb0\x99\x02@7q\xe8\xca\xb6\xa5\xc1\xbf\xbdh3\xaa\x14\xde\xf1?\xb8\\\x07\x12a\xf3\xf0\xbf\xf9[`g\xc4\x9f\xe7\xbf<[P@\xcd\x8f\xe4\xbf^x[b\x89\xcc\xe4\xbf\xf6\x18\xeb\xbfL\xa0\x00@U\x82\xa0dK\x18\xd5?6\x11\x1aq\xee\xce\xf1\xbf_\x90\x91(\x0c\xc3\xc9\xbf\x85\xb8*\x89\x9c%\xea\xbf\xd8\x0f\x9c\x866\xd7\xf7\xbf\xdc\x82\xcc+lI\xf1\xbf\xa1)\xa2\x0c\xe9V\xd1\xbf{\x83\xc1\x80N \xe3\xbf\xa7f)\xef\xe9\xef\xe6?\xfb\xb6\xa3\x0c+\xc8\xf1\xbfX{9\xd2~\xb1\xf0?\x0c\xd4\x08\xbe(c\xe5?\xb1\x9c\xc4W4V\xd4\xbfT\x0e\xce\x8fqT\xef?FO\xa1\xd2\xc9O\xb5\xbf}S*\n\xa0h\xf2?\xb1]C\xfd\xb0\xad\xee?7x\xfe\xe9!B\xfe\xbf\xb7\xc3x\xfa\xa3m\xe2\xbf\x86\x82,=?0\xf0?Pa\xb28\xf4\xcc\xe3\xbf\xcf\x12L3\xd1C\x00@1K4/\x9a\xa7\xf1?(.%\xfel\xc4\xec\xbf\x91\x84P>:\x99\xc2\xbf\x90\x8b\xc7\xd1\xea{\xc0\xbf\xb1\x17\x0f\x94O\x13\xdc?6@\xba\xfcp1\xe5\xbf?b]K`\xe2\xd9?`Y9\x02\xd7_\xe2?\'\xd4\xfc6\xe4\xce\xe4?\xa9\xc6\xaf\xc1\x82=\xd6\xbfl\x17=~\xb7\xda\xd6\xbfqI\x0f\xa6\xd6\xfa\xee\xbf\xe2\x91\x99Yb\x8e\xdb\xbf\x99\xce\xa4\xcc\x80\xe3\xe4\xbf\x9a\xceay\xa0n\xea?\xb6\x1f\xff\x98}\xcc\xe6?\xecu\x84:\x88\x0b\xd8\xbf\xd5.R`\x88\xc7\xf5?\xfb\xb5D\x0f<\x0e\xe5?\x81m\xf7\xbc\xa2\x15\xda?\xe27\xb3\x85\r\xb3\xee?\x92\xd8\xab\x0c(b\xe8\xbf\x94d!\x93\xe8v\xfb?\x1a9\x92\xb6K\xbb\xba\xbf\xc79lG`\x05\xf4?\xcb\xc9\xeb\x13\x96\xdc\xfa?v\xb8=\xd3\xd0\x96\xe9?\x95\xd8\xab\xc5\xea2\xf3?\xaf\x17\xaa\xfd\x11\x90\xf7\xbf:$\x0b}\xec\xa7\xb2?\x98i\n\x05\x1c{\xc8\xbfo\xbe;\xfb|v\xd6\xbf\'\xa0\xaa\x02\x99P\xfe?\xf6\xb6\x81\x16/\xa9\xd2?)?\x97\xf2\x91\x08\xff?w-&\x85\x02\xb9\xbc?\x8b\xd1\x13\x17\xedb\xe8?\xb4b\xbf\xe1\xc8\x10\xf4\xbf\xe2o\xeb\xaa~h\xe4\xbf\x14(\xcb\x82\xce\x85\xe2?\xeb\xe2\x14@\xb7\xdb\x9a\xbf\x0f\xe5c\xd0Wq\xde?\xf5\xb3^\xd9\x1eb\xc7?6$9F\xbd\n\xd9?\\K\xf9\x8aQ\xcc\xc8?NA*\\79\xeb?\xff\xd5\x86\x10Kf\xf5?\xf9|\xd6/\x15A\xb2?\x18\xff\xeb\xca#\r\xe1?.\x0f+0\xe2\x16\xf8\xbf\x19\xfaYir{\xd1?*\x80UT\x91\x17\xef\xbf\x06\xaf\x18\xbao\x83\xd4?y\xdd\xdaT\x117\xe7?\x03\xa5t:\x95\x14\xf3?\xa5\xbd\x91\xdc\xd7\x90\xbe?\'\xf4\xe5\xa1Ax\xf3?\xa6<\xf8\xfa)\xcd\xd1?\xd7\xa08D.\t\xe4\xbf\xf3\xb56\x13$\xec\xe8?\xf7\xc7\xa8Hl\xac\xd4?\x96\x869\xc9\xec\x01\xd9\xbf[\x01\xa1g\xc4\xf1\xef\xbf\x1a"gN\\\x83\xd4?\xc1\xe3Q\x1f\xaa\x16\xc9\xbf"\x01\xfb\xcd\xa8\xb3\xd1?J\xe67\x88\xedO\xf5\xbf\xccL\xe3\xc0!G\xd2?\xe6\xcb\xbfU$\xc1\xf2?\x8fx\xfdn\r \xc7\xbf\xc6u\xc1}\x04o\xdf?\x1e\x86\xad\x18k\r\xd2?\xdf\xc6\x9dM"t\xc9?\x9f0V\x85\xf4J\xfd\xbf\xc9\x8e\xf0Nz\xd5\xee?\xd9\xb7g\xbc\xacp\xdf?St\xc1\x1a\xaa\x8c\x00\xc0\xdcu\x85l|1\x00\xc0\x98\xca\xe0\xb0,\x86\xe2\xbf$\x01\xe5r6\xf7\xd7\xbfkU\x9e\x8d\xc4\xc0\xc4\xbf\xa0\xf0\x8cb\xf9{\xec?\xcd\x07\xa8h\xd5\xde\xf4\xbf\x82}k+\xfa\xc8\xde\xbf\xc3\xfb\xbf\xb9\xba\x98\xe6?Ww\x9d_\x90E\x06@\xcb\x03\xc3\xc3\xdd@\xc1?\x99\xb7\xbeW\xcd\xd7\xe4\xbf\x01\x16&\x9a\xc5\xc5\t\xc0\xbf]O\xf9\xb9l\xd1\xbf\x1e\x8e\xe3\xa0j\x8f\x08@\xb0\xaf%\x86\xd4\x1a\x00@i\x91b\x05\x99\xaa\xb1?|v>`\x0f\xbe\xe6\xbf\x85\x82\x9e\xc8\xe4\xb4\xf5\xbf\xa9\xe6\x07\xc1^\xf0\xf7\xbf\x12\xb8\x91\x9e\xa9\xb6\xe5\xbf \xe6\x94>:2\xc5\xbfHJ8\xef1>\xc1?\xd9%y"\xfd\xbb\xef\xbf\xc2\xbaH\x90\xfeK\xe3?\xdb\xf7\xdc4\x94\x97\x9d?\xf5i\xe4.\xa8\xee\xcc\xbf\xa6Sc\x9c\xee\x07\xee?\xd7\x1av# \x81\xe8?\xd6\xd4P\xb7\x1a\xb7\xea\xbf\xae\x17\xb4\xe7\x04\x92\xf3?1]\xe2\xc1S\x9a\xf4\xbf+Z-\xa2 P\xfe\xbf\xfe2N\x87]\xb8\xeb?\xfeu\x08\xb0\xef\xd8\xd4?=V\xd0i\x82a\xf8\xbf\x16^\x8f\x0f\xf4\xf6\xf7\xbf\xd3\x0c\xb7E\x81\x8b\xd8?Y~\x800pm\xe8?\x83\x07\xe4D\xd5\xed\xfe\xbf4@L\xdf\x07\xe8\xe0?\x91\x08\xfaQ\x1e\xc5\xe6\xbf\xbf\xe8\x99@50\xea\xbfy\xd1\xd8\xcbJ\x9f\xe4?G\xddp\xe8\xd8\x18\xee\xbf\x99\xc1\xdc\xaa\xa7\x96\xe8?\xc0\x9b"Gz\xef\xf0\xbf~\xa94\x9f\xeeu\xe1\xbf\x9b*\x7f\xae\\R\xc8?\xec\xf4\xd4Af\x7f\xe9\xbf\x99\xd0\xf3\xbeI\xa3\xf1\xbf\x94\x8d\xf8|\xca\xc5\xf7\xbf\x98H8l\xe4?\x16V\x08\xa8`\xac\xef?W\xb5\xf2Ll\xbc\x04@9\xc4\xf6JJ\xb2\xd1?h$\x7f6B\x9c\xf6\xbf\xf53(K\t\x8f\xa8\xbf\xb0\xf8\x15\xb8\x96v\x9b\xbf\xf0\x8e\x8bz\x85\xde\xf2?9\xf0\xe1\x1c\xe3*\xee?\xde\x8c\x0c~\x9b\xf5\xee\xbf\xaeO\x0e\xce-f\xf6\xbf\xf8\x0c\x10\x8f\x9d\xe8\xf8\xbf\xe5e\xc51\xdd.\xea?\x8e\xa0\x05\x8f\xd9L\xee\xbf\xd0p5Z\x9cJ\xc0?\xbc\xcaY\xd4\xfc;\xcc?3Vlh\xa5q\xb1?\x91\x04\x14Ui\x86\xe6\xbf\x18#?\x04j\xcb\xa5?\xd0\xac\xa1\x0b\x10\xf0\xd5?\x83h\xe8a\xbf\xd6\xe3?\xfa\x08\xb7d\x8dL\xed?\x8cM\xe7\xa2/v\xf3\xbf\x1cf9\xc2\xd01\xfb?\xdfNt\n\xb7\xb3\xef\xbf\xfe\xe3\x8eBr\xc5\xff\xbf\xe8m1\xc8P\x86\xe1?@E\x01X9F\xf4\xbf\xe8\xcbw\xcf\x16\xf8\xff\xbf\x90c I\x0c\x9c\x00@\xf6w\x17x\xa4\x91\xe5\xbf\xbd\xb2M\x9c\xe6M\xf6\xbf\xa7q\x03\x90\xdf\xf6\xf6?\\J\x00\x1d\x01\x83\xee\xbf\xef\x89\xa9h\xfe\x9f\xd8?O\r]\x17\x1c\xf6\xe6\xbf\xc2A\xd3\xab\xffG\xe6\xbf\xaa9yczF\xec\xbf\xe5\xa5\xa0\x931\xa2\x9e\xbf\xa2\x9c\xf0\x9d\x87R\xf3?\x8d*\xad\xa0\xf8\xc1\xe4\xbf+\x83\xc5\x1eI\x1a\xd2\xbf\xcd;i\x11\xeb\x8f\xba?\x06\x11?\xbd*\xe7\xbc\xbf\x12\xe7\x8f`\x017\xb1?J\x8c\x9b\x05\xce\xd7\xca?G\x8b\xd1$\xbc\xbd\x03@\xd8B!\x8c\xac\x1c\xd5?\xc8\x8d\x8d\x1e\xba\xf6\xef\xbf&\xca\xd5F6d\xf0\xbfZGE>\x11\x18\xd0?]\xd5\x1e:^>\x08@\xf4s\xe1Dp\xc5\xec\xbf\x0c\xd3\xe3\xaat\x92\xed\xbf\xc2\x9d\x93\xa4P\xb8\xec\xbf|e\xf1\xc8\xcd$\xe9\xbf\xd34h9\x93{\xf2?\x83\x91Y\x99\xfa\x11\xd5\xbfH\xb0V\x9c\xec\xc9\xe5\xbf\xd3\x0b\x046Bt\xe0\xbf\x08\x18Sv\xa1,\xd6?wG\xc9\xb5\xaf&\xfa?\x82\x03\xf66`M\xee?\xcf\xe0\xa9\xf0\x1c,\xe1?\x95\xcc\x12\x18q\xac\xf6?\x0e\xb2{\xd4\xaf\xa8\xf5?KC\x00\xa8t\xc9\xf3?\xb3\xd9\xf4\x9d\xddV\xea?\x7fq\x7f\xa1\xd7J\xe1\xbf\x9aWU\x18\xf46\xef?)\xa1__\xb5\xea\xe3\xbf\x16\xf9\xce\xcb\x07\r\xf7\xbf\x15\xe6@p"%\xe1\xbf[\xd6\x9b\x1fD\x9c\xf0\xbfL\xbb\x81\xbd\xcf\xd7\xe1\xbfu\xe9\xc8M\x12J\xf1?V\xd5K65-\xeb\xbf\xbf\xbb\x91\xa5\x97[\xf3\xbf\t\xeb^e|\xee\xf1\xbf =U\xb0\x1e\xb8@\xa1\x93\xf3?^\xba\xa5\x9c\x9f\x92\x06@\xad\xad\x18\x1c,{\xd4?\x14S\xb0\x1b\x10\xbf\xf3?\x136V,\xac&\xe5?\x12N\xb1\xedfH\x01@\xdaW\xe9\xb5\xe1\xdc\xff?\xe2\x00k\xd7\xed\xd5\xa8?\n\x84+#\x90\xaa\xf3?v\xa4\x94\x862\x0f\x00@M\x9b\xcd0\xae\xfd\xed?\xdb\x8c\xce4\x1ez\xf2?*$M\x12j\x93\xc3?p\x11\x1bh\x83\xc8\xf5?\xb0V\xbe\x91\x96[\xf2?2\xdf&\x1c\xc7\x96\xd7\xbf \xfcw\xb5>\xde\xf8\xbf1\xe6\x05\x92\x7f\x9f\xf9?\x8d\xe4\xdb\xe4B3\x00@\xe50\x97\xb3\x06<\xf9?\xdd\x04W\x8e\xe0\n\xe0\xbf\n\xfd\xdd\x93:B\xe0\xbf5\xb1P\'\xc9d\xc9?\x80\x10\xc4s\x83?\xf4?\x81Ve\x9d\x1a\x16\xec?\xf6\n\xfa!\xb3t\xe5\xbf5@\x90e+g\xf2?B\xcb\xfb\xb0\xd1\xcd\xb3\xbf\xc4\xa1x3\x0cU\xf3\xbf\xd4\xc49`82\x03@\xda\xb5\xa8\xd5\x06\xad\x03@6\x1a\x029d\x9f\xe6?\x9e\x92\xc3]\x86\xd6\xaa?\x98\x12\xcf~\xf3\xbb\xc5\xbf\x91\xff\xc4\xc8{\xef\xe4?\xa7h\x979\xe8-\xc0\xbf\xf7XR\xc4@\x1d\xf5?\xec\xa4\x8a\xb7\x1aX\xe0\xbfV]1v+\x91\x9f\xbf\x9f$w\xef~E\xfd?\x91\xf0U%\x1a\xe3\xe7\xbf\xc2\xfc\xec\x91\xa2S\xfc?\x89\x10\xe2A\xe8\xb1\xd0\xbf\xed\x9e\xd0\x00#W\xea?b\xc58km\x9c\xd7?~z\nE&\xb7\xe8?\xca\xd2\xac\xd8-\xed\xfc\xbf\xcd\xbd`\xcc7\xf8\xdb?\xc7$\\e\xf4O\xcb?\xf3s\xd0\xb8(\x17\xdf\xbf\xd11%\x1b3\xf5\xd2?\x98",i\x19H\xdb\xbf<>\x0c~\xf6S\xd9\xbf\x06\'f\xd7\x96u\xf7?Xh\xc5\x936\xb7\xeb\xbf\x9c\xa7|\xb4\xb8\x9e\xde?\xfa\x8f\x14!\x152\x00@>)\xed\xea\xc9\xbf\xe2?\x98\xca\\q\xe0\xa2\xee?5\xac\xda\xf3g\x81\xf2?\x1d\xab(/B\xd7\xf6?\xdd\xbd\x1c\x13w\x80\xda?\xceN\t\x9c\x04"\xd3?\xdaRI\xe0?\xf1\xea\xbf>\xbf\xa9\x8b\xf54\xf3?\x11\x11\xb0|\x06\x11\xf1?\'e\xe5\xf2\xc9>\xeb\xbf\x04\x0f,I\xe8#\x97?a\xbf \x9eg\x12\xd6\xbf\x82\x0e\x7fx\x00A\x00@\xac\xeao\x9a\x8c^\xec\xbf\xfeWG\xe6\x0f4\x07@\xd9\xdc\xc8\xcc#\xbe\xde?D\xb1\xc3hi\xaa\xa3?\xd9d\x7f\x108\x8b\xd5\xbf%l\x90)\x14\'\xf3?\xbe\x17\xe3+0\xc8\xe1\xbf\xfc\x14^T\x85\x1d\xda?\xad\xda\x99G\xa2\x93\xdc\xbf\xa8S\xa8\xfc\n\x98\xe9\xbfoV\xdd\x85\x18\t\xf8?L[\xe8d\xf5Q\xe7?\xf0H\xcfej\xb0\xde\xbf\x07u\x8d_\xf0\x8e\xc6\xbf\x04\xb8\xa6^D\t\x01@\x1d\xc7\x08\xaf\x87\xfc\xf4?\x9d\xab\xb25\xc6l\xed\xbf\xb2 \xe0\xdf\x02e\xc5\xbf-\xfaXw\xdb\xb3\x82?\x19\xef\x94\xde\x8c)\xe2\xbf^\xbd\xad\xff&\x92\xe1?8r\xe8?V^\xf8?\xe5\xa0"\xf3\xad8\xef?`\x1e\xcbS\x11s\xe8?\xf0E\xd6^\xf2\xcd\xf8?\x19\x0b\xacI\xd2\xbe\xe3?\xe7\x9bA\x8c[r\xb1?\xa2B\x81p\x0f\n\xeb?\xbf\xdb\xeaR\xa9\xb9\x03@G\x929\xb0\x95\x98\xee?\x88\xc0\x99\xe2\xc3\x86\xe1?\xc1Lo\xc6\xfeJ\xa0?\xa8x+\x03\xfc\x96\xe5?\xb6\x05\x13\xea\xa1\xa4\xc3?\x8e\x17\x9c?\x81\x0e\xe2\xbf4E [<\xd7\xd7?\x8d\x12H\xb5\x15\xdc\xf3\xbf\xafqd)\xb7h\xc8\xbfQF\xdeT\x12z\xe8?V\x91V\x1c\x15\xce\xd9?\xfa\xf1\x9b\x92\xf7\x98\xe7?\x85\x8f\xec\x00]d\xf5?\xbc\xdc\xb0\x96\xa1\xcd\xfd?\xe1\xbb>\xc3\xea\x9f\xd0?\xc2\xd3\xb9.`\x7f\xb8?s\x06\xeb\xf7\x8fa\xdc?\xe8\t\x08\xc2`\xd8\xf1\xbf\xb1\xfe\xce\x12y\x96\xfd?\x19eA\x01\x0b8\xde\xbf\x88-\x81\xae\'\'\xc1\xbf\xf9g\xecH\xc8\xbe\xcb\xbf\x0e\x1b\xa0\x85E}\xd8?5\x96\x13(\xbb\xa8\xd8?\t\xc1\x91[\xa3}\xf0?;\xf2QM4*\xe0?\xd2\x01N>\xac\x82\xb8\xbf\xc2\x9e\x8c\xa9"\x08\xc8?\x00\xf7\xc5\xb1d\xdb\xf1?8\xdb\xec[\xaf\x97\xcc\xbf\xe5\x1b\x19\x10$f\xbc\xbf\x13\n\x8d\x137h\xf1\xbfW._\xc5\xb8#\xc1?\xd8m0\x00\xae\x17\xfb?s{\x01\x87\xcc,\xea?x\x0c5\xdd\xe6\x00\xe4\xbf\x03&\x04(\xdbP\xc9?1A4\xb5ru\xea\xbf\xe06\xac\x83\xc0v^?\x87\xaetB5\xac\xf8?\xb9\x97\xbe0<(\xf2?\xa0\xa3\xd0\x82\xfai\xf0?C\x8a\x8c\x9aZ\xf1\xf6\xbfg\x08\xcd6\xe2*\xe1?\xfb\x1e\xba\x99M\x04\xf0?\x91\x903\xb8hS\xfa?\xa7F\xf0\xc9g\x86\xf2\xbf0\xc9Zz\x15\xa2\xfb\xbfz\x0e\xf6s\xd6\xbb\xfa\xbfDCW\xf8\xb4x\xef\xbf\x16\x98>\xf9\x05E\xa6?8\x96\xb7S\xca*\xd0?$\x8b\x1bE\xa6\xfa\xe4\xbf\xa8\xe9\xa3Y+\x1f\xdd?\xcbX\x05\x0c~\x8d\xf6\xbfRG;Z\x96U\xce?\x94\x1f\x94\xca\xef\x8a\x06\xc0\x7f\xba\x06Y0W\xed\xbf\xa3\xe2\xab\xdc\xfd\r\xf8\xbf\xe4\xf2\x13\x9f[\x18\xf0\xbfB\x1e\x98{\x9f\xdb\xdd\xbfx\x1dKN\x19>\xe6?\xe3\x9e\xa4\xf0\xac\xda\xc4?\xca\x08q\x03}c\xa7\xbf\xe4\x18\x0e\x93\x00\xf9{\xbf\xcc\xf8\x11\xcdB\xac\xf0\xbf\xc7Y\xcbw\n\xc2\xe4?\xa8]~^\xbb\x8f\xcf?\x85\x0fw:\x1f\xb4\xdc?\xa2\xe2{\xcb)\x1a\xd4\xbf<\x99\xe0~\xb5\xf9\xc6?\x0c\x08L \xa2\xff\xe9\xbf#\xaf&;j\xa4\x01\xc0\xdf$\x0bt\'H\n\xc0\xf1\x1cz\xb3!\x9a\xf8\xbf\x93\xcd+\xf3\xdf#\x02\xc0~\r\x86r\xc6\xdf\xf6\xbf ^\x90\x02\x9bB\xfb\xbf\xdb\x00\xcb\xc4B2\xef\xbfFJ\x14\x8e\xdc\xaa\xd3\xbfa\x8e\xeaM\x8b\xa5\xf0\xbf\x80`i\x1e\xf0\xe6\xfb\xbfY9]<\x9b\x8e\xe8\xbf\x10[j\x0ch8\xf6\xbf\x97\xf6>\xe5\x13%\xf6\xbf\xc9\n<\xba\x11U\xeb?\x96\xa0\xabm\xf4\xda\xeb\xbf\x15E\x08\x9c~J\xe2\xbf\x90\xbf?d\x03\x03\xd4?>\xa9d\xc0m=\xbb?O\x92\xedAX\x83\xc0\xbf\x87\x05T\x0bM\x9e\xe9\xbf\x14\xec\x822\xack\xa8?P\x93&\xf3\xf2\xed\xcb?X\x97Tji\xab\x9f\xbf\x90\x93M*u\xd8\xef\xbf\x91\x18\xb1\xed\xfe\xb2\xca\xbf\x96x\t\xb6\xb6t\xfa\xbf\xe6.FE\xa7\xbb\xd8?\xdfK\xec\\\xa8\xa1\n\xc0\xa4\xf9b\xeb\xf6\xe4\x04\xc0\x01=\x81:O\xbd\xfd\xbf\x00\x0f\xc8\xb6\x98d\x02\xc0\xc8\xc7\\|#R\xf8\xbf\x19\xb0\xb3\x88\x918\xef\xbf\xb2\xac\t\x9b\xa7x\xe9\xbfW\x03[\xd0\xf5k\xf1\xbf{*\xb48\x9ad\xec\xbf1\xf3\xd4\xe3\x1f\xfa\xad\xbf\xcfH\x82\x08NT\xda\xbf\x0f\xe7nF\xc30\xd5?\x8fMA\x06\x90\xa2\xd7\xbfV\x07#z\xbfZ\xf2\xbf\xfb\xe5N\x12\x9d\xe2\x00\xc0\xf31\xf3\x82\xad\xbc\x03\xc0\x95\xf4\x0f{^\xe2\xf0\xbf``\xde/w\xf1\xc0?\x08 \x82\x92W\xac\xf5\xbf\xc1q\xc5\x8d\xca\xd3\xe4\xbf\xbb\xc0`&]s\xed\xbf\x17.\x95\t\x8az\xee?\x05\xad\xa7\x97\x00\xdc\xed?\xa8\xe4J\xd1\xc1\x7f\xda?[\xa5E\xea\x83%\xd8?_\x86\xf5\xd6z<\xf5\xbf\x14\xe3\xd2\x91\x83\xe2\x8c\xbf\x03\xf13 m:\xf8\xbf\x1bM\x8cK\xa9\x92\x03\xc0\xbc2\xb0\xea\xce\x9a\x04\xc0\xdf\xb8\x13J\xd5q\x05\xc0\x040\xa49\x8c\xa8\xfa?\xc5r\xe9\x0f\xe1\x9b\xc2?\xf6_\xe6\xdf\xc6\xf9\x9d\xbf\xdd$\x12@\xafB\xd4\xbf=j\xbc9\x13\x05\xf3\xbf\xc1\xe6\xfa\xaf\xc8\xb5\xb3\xbf\xc6\x1bM]^6\xc5?\xefx\xec\x9d\xf5|\xc4\xbfA\xc9\xa5-\x9d\xcf\xdc?{\xac\x99#L\x08\xe2\xbfJR\xfa\x02b\r\x01\xc0\x8a\xe4\x1b\xe1\xe4e\xfe\xbfg\xaf[\x15u\x06\xff\xbf\x7fS\xab\xd4ze\xdf\xbf\xbb\xe2\x17\x0c9\x92\xe4\xbfm\x8f\xa3\xb4\xb2\x0c\xbc\xbf\x18M\x82\x02\xa0-\xd6\xbf\xec\x1c\xd0f\xc7\x1e\xf4?\xd1\xf1{\xb1\x87\r\xec\xbf\xd0\x06>\xf4\xe8\xe8\xd5?\xae\xf3z\xc2D\x07\x00\xc0\xa9\x99\xbfvc\x9b\xd9?\x84\x17O\x92\xe2\xad\xdb?\'$\x15Fsx\xcd\xbf\x91\xfa\xd7\xe4\xe4\x8a\xe6?)\xc1N.X\x16\xea\xbfu\x91\n\xad\xccL\xe8?(F\xf3Q\x16\xbe\xea?\xaf}e(,/\xea? \xe2\xd0\x7f\xf6\xd0\xd1\xbf\xa3s\x80\x16D\xbd\xe3\xbf\xe2\xde\x12,\xac\xf0\xee?\x89\xaa\xb5\x1f*\x11\xec\xbf\xad\xc2\xde\xb6$$\xdb?\x98\x9d\x91P\xa1\xdc\x02\xc0\xcd1\xb3\nT|\xd9\xbf\x97GHO\x15\x92\xf3?< *J2N\xb4?\xdc7|\xea\\\x1c\xea?E\x13\x00\xee`.\xe0\xbfZo)\xe1\xa8Y\xde\xbf\xd8\xf6\xfb`\xf2\x81\xf5\xbf\xa0\xe8\x9c\xdd.\xd5\xf9\xbf\x88\x9a\xe7\xfe\x8fB\xec?\xecEw\xb9H\x1b\xf7?y 8:\xd3b\xf9\xbf\x040N\x97\x97"\xe5?h\x8f\x0e\xb1M\xc6\xf1?\xc7\xd1\x94\xc0\x1aL\xe1?{\x8bA\xbe\x17\x89\xef\xbf3\xb6\xf1\xef\x10-\xef?\xf7`mA\xf8/\x01@\xb4\xbd\x04\x18{+\x9d?\xa4W\x9c\xaf\xd3X\xf2\xbf\x13\xc5\xb1\xfbo\xba\xf6\xbf\x88\xa4\x96\xcb\x9c_\xe2\xbf\x9cu\rK\xa9$\xd1\xbf3p\x94\xe8\xdb\xa9\xf4\xbfP\xea\xa0\xfb\x86O\xc1\xbf\x9c_\xac\xe4+\xd9\xdb?9;\xe6\x0c\x89\x0f\xf1?A\x07\xd6*\xaf\xb6\xde\xbf;/\xd7\xb9IH\xf7\xbf\xfc\x04m\x91\x16\xa8\xfc\xbf\x16\xfae1K\xb6\xb5?\x1c\xe7^\x14aY\xef\xbf\xff\xebx\n\x9b\\\x00\xc0X\xe8"\xbf7K\xd0\xbfG&\xd7Oq\xb0\xf2\xbfM[\xd8\x9d\x03\x8c\xf2\xbfo%N:R2\xf3\xbf+\\\xec\xe4\x10\x87\xed\xbfau\rW4Z\xd1?\xbd<\r\x10\x92_\xc5?\xc1NQ\xb1\xda\xe2\xe7\xbf\xe4\xaa\x1bE\x95\xfe\xf5?7\x07F\x89\xa6\xd4\xdc\xbf\xb3\x10\xbf{E3\xee\xbf\xd4\x0c\x14\xea\xf5z\xf4\xbf\x10Z\x04^\x12\xbc\xeb\xbf\xb9\xa4{\x82\xf40\xf8\xbf\xfe\xd2\xe7dl\x92\xe7\xbfX\xd5\xed\xff\x16\xbc\x04\xc0\xe5\xe2\x00$}8\x05\xc0Y\xeb\xfe\x90\x1d\xca\xf4?\xd3\xb1o\xe9\xab\xee\xf4?n\xfa\x17\xec\xc5T\xdd\xbf$\xe5\n\x9c\xfe\xfd\xe1\xbfy\xaa~8\xe7R\xf9\xbfQ\xfe\x15\xe1\xe41\xd4\xbf\x82\x8d\x9e\xe6\xdb`\xf2\xbfB2\xaa\x91\xa5\xca\x01\xc0Y\xcb\x84\x9aif\xcd?{\x15l\xcb\xa4\xa6\xd7?\x8c\x90U!\xb9\xc6\xf3?\xde\x91{\xa9\xef\x1f\xb8\xbf\xcf{/\xb6\x11\xac\x06\xc0\xf3\xf5T\xaf\x06\x88\xd1?\xea."XW\xdf\xe5?\xf2\x1b\x81y;\xab\xe3?\x7f\x1c\xed,\x84\\\x02\xc0\x14\xd60x/\x92\xac?!\xa5\x9f\xa2\x9b\xcb\xb1\xbf\xb0\xeesd\xab\xd6\xf1?\x1f\xf5\xad\x9d\xfc\xc1\xe0\xbf\x12\x8e\x03\xbe\x17\x07\xdd\xbf\x9a>\x10\x92\xac\xc2\xd2\xbf\xbe\xf4d\xf0\xb9\x05\xe9\xbf\x1d\xfe\xa3\xf7\xfd\xdc\xe9\xbf\xc1\x0e/;\x9cl\n\xc0\x98:?\x82\xe8G\x15\xc0\xf7\x87\x16/j`\xfd\xbf\x87\\\xac\xb9\xde\xbd\xf6\xbf\x1bP:\xbd\x97=\x02\xc0w\xe5mB\xb6\xa1\xf2?m_\x03\x03\x05$\x94\xe5/\xd6?\xa0\xb4<\x9a\x10\x01\xd3?\xee:\xdf\xd3\x98\xb5\xf5\xbf\xa6W\xa6\xf9xn\xd0\xbf\xb2\x0f\xb6\x12<\xce\xf2\xbf\xd8\xd5\xb0\x9d<"\xf2\xbfq\x82Tc\x87\x1e\xff\xbfWn\xb13\x83\xf7\x06\xc0 \x1e\x14NS\xe5\x0c\xc0[\xc3\x8c\xda\x17\xe0\x05\xc0\x81g\xcc?\xa9\xba\x07\xc0F\x0cI\xf5\xe0\x99\xf1?\x1aQz8?!\xba?J\x1fyL\x0bA\xda\xbf\xe2\xbd\xa6|\x96\xfd\xfe?\x88\x94\xeb+\xbe\xea\xe1\xbfF\xef\x9em\xd5\xa0\xea?\x85\x06\x8eb.\x17\xe1?\x03\x11{Ql5\xff?\xce\x8e\xbeo\x80Z\xf5?\xb4\xba\xd4Z\x11\x97\xf1?\x8a\xbcB\x93m\xdc\xb0?/\xf5\x87\x13(\xca\xdc\xbfw\x1aW\xe9\x01\xdb\xdc?\xcb\x85\x9f\xc6\x97A\xdc?;&%\xe7\xa4\x7f\xd1\xbf\xfa\xf0Tf=\xec\xe0\xbf\xcd\xe6\x8b\xc8$T\xdf?k8\xef\x95\xc0\x87\xf2?\x18\xed\xbfc\xeaA\xd3?@1\xaa\xce\x11n\xfc\xbf\x12* f\xc3\x87\xf3\xbfP\x89wM\xda\xbd\xeb?Q\x07\xdc`\xe6\xf1\xac?(\x05\x7f&u\x9e\x94\xbf\xf9\xc5?\xa3\xd3\xcb\xc3`V\xf0\xbfww<\xbb\xaeO\xe4\xbfEi0\xa0\xcaz\xd0\xbfS(\xc9\xa6Q\xb7\x0c@w\xac\x17\x0c\xd0#\xf1?\xf7QEK\x9d\xf6\x01@\'O\x1f\x01\x11\xb2\xdc?\x1a\x82\x97\x13\xc1b\xfa?5\xff\xc7\x18\x9b\xe4\x04@\x01(\xaa<^\xa1\xe0\xbf<\x1e`\xea\xe1\xe2\xfc?Se\x1cA\x02\x1a\xf5?c\x10\x8f\x1f\x07o\xf0?\x99\x8c\x9d:\x0e6\xe4?\x7f\xdd\x86\x1c\xa1w\xe0\xbf\xbc\xd9\x84*k+\x03@\xca4"\xf2\xb6\x1c\xee?r\xd4xvH\xbf\xc6?\xa5\xda\xde\x91$\xed\xfc?\x12\x07;\xeaOa\xbd\xbf\xa36(\x8c\xe2V\xf1?\xc2\xd0`\xdd\xb1=\xf4\xbf\xf1XR%\xc7\xfb\xfc?\xadkq\x9a\xb8\xfb\xf6?\x85\xdd|Fx\xf0\xe7\xbfnI\x18\xc0Gq\xe6?\x86c&\xf6vo\xf0\xbf7\xa6\xb3\xbf\xd2\x86\xd3?L}\x9d\x8a\xd1\xba\xf0?\x9f\xde\xc3\xec\xaa\x95\xdc\xbf\x04\xf1\xe0Z\xa85\xca\xbf\x16\x89/\xdd\x82Q\xe4?q\xc0(\x8f\xc3x\xfe?\xa4\xe0N-\xd7\x9e\xf5?\xad\xebS\xefqk\xf1?~\xb4\xfc\x80v\xa5\xe7?\xffsNP\xcfi\x00@\xa0\x95\xc7\xd3\xc5\x1c\xfe?iI\x17\x8au\x9d\xc1?F\xd9\xe5 9\x9d\xf8?wrRf[\xfa\xc2\xbf\x1a\x9e\x84|\xfa\x7f\xe3?[uP\xe5\x90&\xdc?\x0e\xb4\xe9\xf0\x8d\xde\xce?W7\xa2Y$\xeb\xea?\xb4@\xee\x80\xceu\xe3?<\xe5}\x95\x07\xf3\xd7?p!\xfc\xb0\xe0\x91\x00@1M[\xf0\xa9\xbc\xf7?0\xe4\xedJY;\xfa\xbfT\x16w\xbf\xe2a\xe2?\x03\x14\xd2\xda\xbfA\x00@!Qn\x9e?$\xf3\xbf\x8e\x95}R;\xbc\xf4\xbf\xe3\xce\xa3\x8d03\xf9?\x8d"Z\xc99\xe6\xaf\xbfW\xbbrq`\xbb\xd8?j|\xaa\xd9uG\xfd?\x19\xe3\x11\x883u\xfb\xbf\xe2.Oz\xe5\xc3\xe4?\xde\xaa\x96\x80\xf2g\n@_\xdb\xe9|\xac\x00\xea?\x8a\xec\x83\xfba\xaa\x03@\x95\x191\x1cos\xed?\x8d6\xb1\xc3f\xc4\xf8?\xf3\x8d\xc0p\x94\n\xcc\xbf\xd4\xe2\x1a\xda\x7fd\xe6?\xc1d\xf5\x86\xfd\xdd\xe7?l\x04jbV\x1e\xc9?\xe1\xdd\x17\xba\x04\xf9\xa0?\xbd\xf8\xa4NN-\xfa?\xef\xa9\xf3\xf9M\'\x96?+\xdf\x86\x0f\x05\x0b\xca?a\xf6\xc9\xfa\xef\xfc\xd4?\xb9XG\xf1\x04\xbc\xe1?H\x949\x02N\xf0\xec\xbf,\n\xf6\xe0$\xa0\xbe\xbf\xc9\x0e\xf2\xae\x13q\xe7?\xdf\xf7Z\x96\xfa\xf5\xf4?\xb0\xfe{\xea\xe3\xa1\xed?\\[X*{\xca\xf3?\xcf\x04?|\x1eG\xf4?m\xf6\xb69\xff\xe0\xfa\xbf\x99MW\xd9\xc3\x89\xf9?\xf4\xd6p\xea\xa2\xf0\xdc?Cw\xee\xa4w\x82\x95\xbf\xcd\x92bZ\xfb\xf6\xbe?i\xdd\xca\xf7\xa5\xe1\xc5\xbf\x95}K\xf6P\x90\xe2\xbf\xad\xb6g\x94\xe9R\xf6?\xd1Z\x003&|\x04@x*qD=\xdf\xf1?\x12\xe2\xc7\xf6;\xf4\x06@\xc4)n\xe3x\xdb\x02@7,\xd5\xd5-\xe8\xfc??\x1e$\x9e\xc3\xf6\xfd?E4\xfc\xe1)\xbf\xf2\xbf\xcf\x06\xea\x86\x0f\xe0\xfd?}BI\xf9A\xc4\xf4?J\x12L\xaeh\xf5\xd4?0\xa7\xb8\xe6\xc3\x9b\xc7?\xa8\xdd<\x13^f\xfa?\xb6u\x02\x85\x94\xf6\xdf?\x15)2\x84}\xc6\x05\xc04\xba\xc6{\xb7\x8d\xfa?\xa0\xfaJ\xc7\x92\x9c\xe8?N\x7fN=\xa9\t\xf8?\xe0Y\x02\x01\x96k\xe9?\xb9\xfdIM]\x8d\xcc?\x18\xa1=c\x9f\x1a\t@a\xc1\x89Z\xd4\xfd\xebv\xf3\xbf{)\xc1\x86kA\xf4?\\\x99\xd6\'\xe7)\xf0?\xfd\xa5\x96\x1dUO\xe0?\x84\x12\x03\xd6x\xca\xe3?:\xe0\x11E\x15\xe1\xf8\xbf0.l\xc1R\xbd\xfc\xbf0\xeb\xca\x90\xd5\x1a\x01\xc0\xa19\x8d-\xab|\xed?\xe0"N\xb0\xb2\x0f\xdf\xbfE\xdfl\xf6\xeb\x83\xed\xbf\xac7\xd2u\t\x1c\xee\xbfVKR\x87uI\x87\xbf\x8f\x8bxju\xdf\xcb\xbf\xa5\xde\x13\xe8{7\xfb\xbf\x1aV\xd9c\xe7\xb5\xf4\xbf\xa2\x81\x98\xe6\xc6\xa2\xcf\xbf]S\xc9-\xa0\xef\xe6\xbf\xc0\xf1\xcay>0\x02\xc0B\x0fK\xbbMm\xea?\xd7\x87\xf8\xc3\xbf\xda\xf1?\xea\x18\x9b+\x8cD\xd2?[\xea%\xa7L\xba\xe6?\x97S\xf3FN\xc0\xef\xbf\xd2\x17\xa9\x7f\x1e\x88\xec?0U\xd1\x00\xde)\xe2\xbfO{\xbc\x8d\xbc\xc1\xc4?H\xe1}\x11\xdd\xe1\xef?<\xc2L8\x1f\x07\xf1\xbfGA\n#}\x8a\xf0\xbf\xaf\x87\xe7\xbefW\xd8\xbf\xb1\x0fnt\xac\x0b\xc1\xbfl\x88\x00Jb\xae\xea?\xe6u\xfb\x8eF\x0c\xd6?=z\xc1\xdb\xb2t\xfc\xbf\x1a\xf6G,1\x8b\xdd\xbf\x90\xf0k@\x83\x81\xe8?\xd7\x803A\xe0E\xe6\xbf\x91\xce\xba\x82\xf7A\xf2?\x94\xac\xa9\x10k\xcc\xe4\xbf\x9e\xd1\xee\x12WQ\xe9\xbfp{\xd3_N8\x02\xc0\x13\xcar\xf0\xc4\x99\xed\xbf\x96\xb5\xa4U\xd1\x1f\xd7?\xcb\x14z\xbb\xd5\x7f\xc4\xbf\xdc\xe4\xeb\xf6\x15\x82\xea\xbf\x813\xea\x02\xac\xfd\xd8\xbf\xf1(\xa3|\xc2\xd6\xea\xbf\x1c*y\x1a\xbaa\xf6\xbf\xdc\xa8\r\x92+\x15\xcb?\xc5\xb3\xea./l\xde\xbf\xf7\xda5\xa4Cq\x02\xc0\x16\xeb!\xc3\x1c\xbf\xf4\xbf\x0fc\xf6\xa9e\xf4\xe8\xbfe]\'\xe0uA\x02@\xee\x86$\x08O\xae\xf0\xbf\x9a31\xc1\x92d\xcd?\xb2r\x9c\xb9 \x01\xd8\xbfyWA\xdf[\x06\xf2?\xeb.\\y\xedf\xee\xbfp\x95\xbf\x04W\xa1\xd4?\x1d\x95}N\x0fO\xe8\xbf\x9ax\x89n\x10\x18\xc2?\t\xdb\xaf&1\xa6\xf8?\x8c\xb4\x9d\xf5,N\xf2\xbf\xc1L\xcd\xdb\x8fr\xde?\x1b\xa6\xa9\xfa\xdbn\xef\xbf\x07\xb9\xd6\x82v\xf1\xe3\xbf\xdeDp\xac(N\xc6\xbfY\xf8\xcf\xfeF\x85\x00\xc0\xc3\xf5C\x93\xd3J\xfa?\x91*\xe7\xe3\r\xf0\xa9\xbf/\xa3\x90\x02C\x97\xe6\xbfik\xe7\xe4\x8c\xdd\xf5\xbf\x9e\xc6s\x9b\xf5s\xfb?X\x1a\t\xc3\x9c.\xf3?\xbd\xb0E\xe3\xf2\xbb\xb0\xbf\x82|\xbe?W\xe6\xdb?\x1e\xaf\x88\x05\xad\xf2\xd9?\x8cn\xf3a9\x85\xa0\xbf:st\x18\xbbc\xd5\xbfo\x89\xd5y\xf6A\xf8\xbf\xaeqQ\x8ee3\xca\xbf\x9a\xbcM\xf3W\x90\xba?\xa7}B\xebm\x85\xd8\xbf\x0bW\xef\x9e\x94\x89\x02\xc04\x1f\x01H\xebj\xf9?\xa3\xb0\r1\x18i\xe9?\xdd\xf1\xa7\xae\xc5\xb2\xee?6T\x8b\xac@\x08\xd6\xbf\xef\xa0\xe4n\x0eR\xe6?V\x9dU\xc3\xa2\x94\xe6?F\x91\xc4`|x\xf3?\x0f$\x10\xac[\x8e\xe6\xbf\r\x8bVH\x93&\xf0?\xa5\x82orw\x97\xf8?\x15\xc4\xc3J,\x03\xf9?m\xd2\xb6\xdf\x1a:\xf5?\x8fONk\xbb \xfd\xbf\xa3GI\x18 \t\xd3?\xa9\xc3\x1c\xc0\x8a\x0c\xda?S\xaf\x8a!\xeeH\xf8?7q\x87=\xa3\xd8\xd9?NH\xb5n/N\xec?\x048z\x9e\x11g\xef\xbf$<\xb8\x96\xf8D\xf6?!\x9a\xa3\xc7K\x0c\xdd\xbf\xb9f\x88\x8f\x0e5\xb4\xbf\xccs\xc3\x8a4\xa8\x00@8\'\x92\xe4\x9d\xdc\xf4?dc\xe1\xab\xf0m\xf1?t&\x84\xb2 \x9f\xdc?N\x82\xb1l\xa2\xa9\xe7?\x9b\xa8#wC0\xd2\xbf\xa6\xf4]\x92\xaf\xc9\xb8?i\x9a\x8a\\\xe8\n\xbb\xbf\xec[\xc1*6\x0e\xa5?1h\x9a\x06\xa9\xbc\xbc?\xc2\xdb\x94\xe0KS\xb8\xbf\x7f\x94\xfc\xa5\xa7\x11\xe8\xbf\x8d\xb40M\x93\x9e\xe8\xbf\x05\xb1\xdd\xd18\xc3\xee?\x80\xfd\x9a\xe9\xbaf\xef\xbf*\xc6k\xf2\xc9\xca\xda\xbf\xaf\xf63"\xa5?\xe5\xbf1\xfb>\xe4\x16\xc4\xc6\xbf\x84wszb\\\xe5\xbf\xad\xaf\x9b\xa1\xacn\xf1?\xc08\xbfF\x12\xd7\xf3\xbf8jl\x8e:*\xe8?\x0e\xb6\x99!\xea\xad\xe2\xbf\xcd\x8f7B\xfdl\xff?\\\xab\xee\x10\xf9\n\xcb\xbfW^\x9b\xb4\x9e\xa7\xf7\xbf\x85\x90d\xcb\x03\xc5\xfc\xbf\xe1\x1d\x04\x1b+\xb4\xe9?\xd8V\xfazZ\x00\xf5\xbf\x91\x11N\x89?\x0c\xec\xbfH\x8cM/Q@\xfd\xbf\xc2\x1cF\xac\x17\xfe\xa0?\xe2\xb0;*S\xfa\xe3\xbf\x08\x84?\x9e\xf3v\xd6\xbf,P\xcedy\xf1\x03@\x18\x10\x81\xce\x14\x8b\xe5\xbf\xfa\xad4\x96\\\x1a\x01\xc0{\x1dh\x15\x12,\xe8?\x0e\xf0i7\x83\xe2\xd0\xbf\x98\xe9a}\xcf\xdc\xe2\xbf\xec\xfbj\xe7\x93/\xca\xbf\xf0r\xa0\x8a\xe1\xb5\xf1?\xd7\xa4\xdb\x85h\x8e\xf1\xbf\xd6\xaf\x0e\\+9\xff?JD\x81B\xf9\xd1\xfb?.\xe4\x91\x17\xbau\xd3?\xc1\x0c\x99\x0e\x8d:\xe7?&\xa4\x15\x05\x87f\xa1\xbf@\xb1[NG_\xc1\xbfc4\x14\x13\xb8%\xde\xbfJMc\xff\x9e\xdc\xf7?\xda1\x9ag\xdd8\xe5\xbf\x89\xd6O\xfb*\x95\xf2?\xfa\x8f\x83\'a\xae\xdf\xbf^\x9f\nu\x84`\xee\xbf,\xf9\x83\x15\x03d\xe7?l\x02o\x89\xc2y\xd9?\xb3\t\xe7\xa7\x8e\x95\xd3\xbf\x9d\x13\xb0\xe0\xa0]\xed?\x7fou1\x0c\x03\xf7?8\xc5\x88T\xc2Z\xf2\xbfqGr\xb1\xa4\xe1\xa0\xbfm\xa9\xfd\x04\x85&\xd4\xbf^\x08\x94\\\x92,\xe1?)g\xc5\xc5\x13\x99\xd2\xbf8\x98K\x88\xd3\xa9\xe4\xbfg\xd3\xa6FV\xc5\xe0?\x9cA\x1b\xec\xc0\xa7\xd2\xbf4l:\xbdk\x97\xd2\xbf\x1c\x84<\x05W\x0b\xd9\xbf\x1f\xe8\xcee\xdcq\xf7\xbf\xb7\xb9h\x85\x9c\x87\xc7\xbf \x92\xa2\xd9\xff&\x9c?\xee\x04TL\xc6\'\xd1\xbf%\x88\xc0\x1a\xc3\x86\xf1?D\xfa\np\x95G\xe9?\xc0\x82\xa4\xb4\xec\xf8\xec\xbf%\x08\xce\xd3\x1e \xe1?;W\x004\xd9\xf2\x00\xc0\xd2<\x86\xbf\xf01\xdf?\xbc\xa80O1J\xea?b,\xd6\xc1,,\xe7\xbf\x94\x0fQJ\x8b\x9d\xee\xbfA\xf1\xe6\xbb4_\xe9?\xce\xf1\xd9*h\xa2\xd1\xbf4\xf2\x8bP\xc7T\xff\xbf\x96\xa2T\xca\r\x86\xe1?<\xb49$@\xee\xf0\xbf\xbe\xdc\x96\xbd\xban\xf4?\x87\xda\xed\xdf\x9d\xd1\xed\xbf~\x17\xbf\xae\xb4\x82\xf4\xbf\xc4*K\xd5Y\'\xc0\xbf\x85A/\xd1\x9f\x89\xd5\xbf\xcb^\xd3\xa3u2\xe3\xbf\xa3=`\xe4\xef#\xe7\xbf\x85i\xd0\xda`\xe4\xc1?\x1d\xed\xf8I\xff\xe9\xe1?\xcc\xeb>\xe9\xfaO\xf0\xbfh!T8~\xdd\xf2\xbf*\xfcL\xe6\xf6\xc7\xf7\xbf\xbe\x01\x7fc\xee\xf8\xe0?\xa3\xa4\x0cA\xd7q\xeb?a)\xeb\xf5\xab\x91\xe0\xbf\x89V\xcdi\x05]\xdd\xbf\xb9v\xa3V\x16\x1b\xf2\xbf\xe4\x9b\xcco\x80\x9b\xd7?\x17_\xe6\x06_\xa4\xe3\xbf&R\xbf\xfes\x9f\xaf\xbf\xe0$\xbf\x9b\xb6\x7f\xf0\xbf\x05\x04\xbe>=\x8a\xf9?\xeb^S\xe2L\xfc\xbb\xbf\xcb\xe9\x19f$*\xeb?\xb2\x10\x10L8k\xe2?\x17\xb7;\xa4\xa7c\xe4\xbf,\x0e\x99\x13\xa5/\xfa\xbf\xef7\xdc\xd7\xa4\x8a\xda?\xfd|\xba\xc5:\x17\x01\xc0`C\xf5\x03\xea\xb5\xed\xbf\x1b\xeah\xad\xea\xff\xd5\xbf.\x9a7\xd3\x86\xb0\xe9?)\xad8D\x11\x8c\xcb\xbf\xeb_\xe8G\xf1\xcd\xfd?2j\x89)\xb3+\xdf?\x8b\xc2o\xbe\xdf\xaa\xe9?\x14\xe4y1\x1a/\xf5?\x9b\x9e\xa4\x9b\xad \x01\xc0\xb2\x13\xbf\x88nG\xdc\xbf\xa1{\xf07\xb7\xf2\xe9\xbf\xed\x95X\x1c \xc5\xcd?\xbc\x81\xe3\x88h\xdb\xe0?\xcd\xad`\xe7\x7f\x0f\xcd?/\x14\xd3\xfe\xbf;\xca?\xc5\xb3\xb4.\x11\xff\xdf?\x10\xf8\xbd\x13\x9f\x8b\xfe\xbf\x13\x12}\xf5\x16\xb6\xec\xbfz\xdc>J?\xd9\xc1?\xc0\xa0\rt\xe7I\xb2?2Y\xe5\x00\x0f\x05\xbc\xbf\xc3@\xb6\xb2\xe9\xc3\xf2\xbf_\x0e_>r\xeb\xf6\xbfJ\xb6GD\xdb+\xda\xbf\xec\xcc\x8f\xce\xda\xe7\xd0?\x17\x06!d\xc6Y\xf2?\x81\xbf\xa0P\xb1\x8e\xf4\xbf\xc7\xec\xbe\xc4zZ\xe1?Z\xd4\x86\xb94\xaf\xc7?\xdfrZ>i\xec\xd0\xbf\x03\xb0\xcb+\x8d\xee\xc7?\xad\x87\xcfp\xe9?-9\x82\x9f4\xbb\xda\xbf\x17(\xd6+\x9eE\xcd\xbf\xcb\xa6\xbep\xd6\xbb\xd6?\xb7\x86\x9d\x05\xb7\x85\xc9?\x1cT\x17\xed\xab\xac\xc7\xbf\x83\xa8\x91\xf9\xe2\x1b\xd3?l\x8a\x18\xcb\xc3\xea\xf3?1\x025$\xf3w\xf9?\x04\x94x~\xce\xf2\xe0?\x17\xfa^\xad\xbf\xfb\xd8?\xc4\xe3)\xb1\x87\xc7\xf0\xbf\xd5)\x0b\xe3U7\xe5?\x99/\xe2\xf0\xa4\x8b\xd6?zA\xd4\xd8\xbd=\xf8?\x14\xfd-J}\xcd\xf5\xbfvE\x06\xdeF\xd4\xd7\xbft\xe5\xda\xb7\x04\x89\xf0\xbf\xed\x08\xbc"\xcd\xee\xf1\xbf\xed\x0c\xf8\x836\xb5\xf1\xbf+35\xae\xbb\xd6\xc2\xbf\xcd\xc72\xfa\x1f\xd5\xf7\xbf\xc0\x00\x0f\xddA\xe8\xfa?,\x9c};<\xe8\xf3\xbf\x92\xcb\x02f><\xd0\xbf\xb6P\xfd}Q*\xd9\xbf\x01\xa6\x86c\xc3 \xea\xbfv\xaa\x13\xf5\xb2\xab\xe8?\x9f\xd0\xb5\r\x82\x99\xd1?\x1cTkV\x96j\xe7\xbf\xf47\xc0\xb1\xbb \x05@\xbfN\xd8\x8a\x9fZ\xee?\xe4\xbb\x14\x1b\'\'\xe7\xbf\x8fT\x85>\x97u\xc2?!\xd6\xf01|V\xed?\xd2\xfa\xb5\xfb\xcax\xe7\xbf\x1c\x08VB\xf7\xab\xf1\xbf\xdf\xd9?}\xf9C\xd8\xbf\xb2\x03\xe5*\xdeP\xf2?\xa8\xcd\xae\xb3\x87\xa5\xee\xbf;\xca0\x0e\x07\xf1\xf4\xbf\xa9\xafn,$\xce\xf2\xbfp\xfa\x1e\x14N\x1e\xe2?\x98\xd4\xd9\xb0\xbdd\xd1\xbfg\xe5\xf64\x91A\xce\xbf\xc7kLsZ\xc1\xf5\xbf\xf5\x8d\xfc~\xf9:\xab\xbfx\xeb\x13\x15\xc6\xda\xb8\xbf8#\x01)H\xe8\xdb\xbf\n\xa4B\xd6\x19\x10\xf4?C\xee\xe0v\x93\x83\xea\xbf\xe4\xb9\xf1\x9c4\x10\xf4?\xf1W\xa0\xf7Q\x8e\xf8?\xeb\xb8\x03-\x88]\xf6?,\xf3\x13\xea\xdc\xd2\xe0?\x8a\xc3z\xd6\xcby\xff\xbf\x8a\x95\x81\xd0\xf8v\xca??\xfa\x05\x10\xb5/\xe0?\x7f\xe8E\x17\x1a\xd2\xb0?\xd3\xa4 \x0b\xbaa\xed?\xea\xdc\xfeEXW\xfa?\xc8\x1c\xf1\xbf\xb3\xeb\xd6\xbf\x87\x90cwg\xf3\xf6\xbf\x18wZ\xeb\xde\xdb\xf8\xbf7J\xf0\x13\x9c}\xdc\xbf3\x97\xab\xb6\x1eG\x93\xbf\xac\xebm\xfd0q\xe7?-\xb8\x0bv\xb2\x87\xf1?\x07\xab!\xe1"\xdb\xec\xbf\xe7\xa5-f\x9d\x12\xcd\xbf/a\x87B\xfa\xe4\xef?\xf2\xbf\x95\x8f\x08\xf5\xf1?\xa0Of\x07p\x92\x90?\x06!\xfdf\xef\x91\xff\xbf\x86\xa5\x14\x96\tV\xef\xbf F\x1b/\x17\x1e\xe8\xbf\x14\xaa)G4>\xc9?\x14\x8e\x15\xde \xa6\xc4?{\xec3M\xaf\xf7\xe7\xbf\xeb\xcf4\xb1\xf5?\xe3?O\xf7\x13\xae\xd4\xe8\xcc\xbf\x89\xd3~\xe7\xfa\t\xbe?\xaa\xa0\xcb0\n\xcb\x00\xc0N\x11\xaf\xb1G\x88\xfa\xbfyZy;\x0c\xee\xbb\xbf\xcc.\xd3{\xf2\xcc\xfa\xbf\x7f\x83*\xc8\xde\xa8\xdf\xbfUZ\x0cG\xca \n\xc0\x92T0Xz\xdc\xef\xbf(\xed\x1d\xcd3\xdc\x04\xc0\xb9\xda\xd7\x89\x98\xf4\xfa\xbf\xa5\xb19?\xe1\xb6\x84\xbf\x18M\xa1\x1a\xd3\xc4\xfa\xbf\xfb\xe0\xaf\x89u\xf9\xf2?F\x03Z\xc2w\x82\xdf\xbfg\xecb\x1dw}\xe5\xbfS\xa6\xc0m\xda\xbf\xe2\xbf"N\x17d\xaa\x97\xc6?M1\xfe\xca6j\xe7?jNT,\x962\xe1\xbfN-\xefQ\x9d\xf0\xdc\xbf\xea\x81\x9d`a\xac\xf6\xbf\x04S\xb9m\x12\x10\xfb\xbfn4\xa6\x82D\xd8\xfd\xbf\xd2z\xa19\x1e\xce\xee?\xf2,\xe1X\xca\xf8\xe1\xbf\xa4\x07\x9f\x8f\xd3t\xe0?+\x82\xc4\x8c\x96=\xf2?\x90^\xe8\xefS\xcb\xf6?\xde?t\xff\x1f\xbb\xca\xbf\x06X\xfe\xe8\x12\t\xf6\xbfG\x99\x95Z\xa7\xa3\xb8\xbf\xad\x82\xd4\x84\xa96\xf2?\x9b\xec\xf1\xa5\x1aj\xf4\xbf\xc8\x9bL\x00\xce\x82\xf0\xbf\xf6\xackW\'/\x05\xc0\x10\xe6\xc7g\x15\x92\x01\xc0\x9e\xfb\xfd \xbd\x0b\xea\xbf\xb7\x1aT\xbaM}\xf1\xbfy@\xd5\xe9\x11\xf2\xd9\xbfAW\xc6\xf4[\x06\xc9\xbf*\xe3\x12#\x1a^\xe8?\xc5fp\x10\xc7\x1f\xea\xbf\x19wIdKy\xe6\xbf]\x0c#-oM\xf1\xbf\x18\xc9M\x85\xfb|\xf8?\xc4\x8b\xa7\x98d\xf5\xec?+\xf1\x1f\xec\x1bm\xf6\xbfv\xe2\xd9<\tc\xef\xbfR\x10E\xb4\xa7\x07\xff\xbf@\xee\xf5\x14\xadW\xc0\xbf\xcf\n\xf72\x1b\xa1\xfe\xbf\xdf-~\xf8\xe7\xcb\xe9?\x02_\r\x85\xa9J\xa9\xbf*\xcf\x04\x17\xc0]\xf6?\x17\xa7\xb8\xd5\x82\xd6\xb0\xbf\xf1\x10i\xdf\xf8\xf2\xe2?\xb7\xf2@\xca\xf7\xc3\xff\xbf\xdbj\xd4`\x15\x1a\xa0?qX\xf0I\xfe\xdd\xfd?\xcf\x81\xc2.d?\xe6?\xd6R\x85\xe8\x9f\xcf\xbd\xbf\xd2\nA\xcf\xb1z\xfd\xbf\xeb\x1c\xa3\x86\xdd\xb8\xf5\xbf\x1a\xf0X5\x05A\xf1\xbfb |\xe7nl\xe6?\xe6f+q\xb3h\xe6?\'\x93\xa97^\x12\xd3\xbffY`xK\x18\xf0?!C\x12\x84k\x97\xc5?Tr|\xb3\x97\x8b\xd4\xbf\x0f7\r2\xba\x00\xd9\xbf\x18\xe9R\x01r\x1c\xe3?\xd1\x04\x17\xef\xe3\n\x03\xc0(5\x1b1\xba\xa2\xdb\xbf@tm=%\x84\x03\xc0\xff\x97t0E \x08\xc09\'&7\x8f\xea\x04\xc0$\xd2{fz\x1a\xeb?\xca4\xc6W\t\xa3\xe7?\xac\xd3!3t\xfc\xff?\xf3\xb30\x038\xf7\xe0?\xc5s\xa7KC<\xe8\xbf\x13\x90\x83\xe9\xe3\xe1\xe9\xbf\x92\xf1\x19\x80\xe5W\xf4?$\x1a\x18\xcc\xef\x9d\xdd?\xaaG\x86{]\x90\x0b@\xb1\x89\x95-k6\xe2\xbf\xa0\xc7\xeca\x95O\xdf?&\xd4\xd9T\xb5\xb2\x00\xc0\xe7\xfcKv\xd5\x94\xf2\xbf2\xb0a?8\xef\xc6\xbf\xd4+\x9f\x08\xd8\xb7\xd2?\x0bf$b\xb5;\xd9?+\xafK\x9a\xbek\xda\xbf\xd4$R\x7fb\xcb\xf0\xbf\xf7,\x88\x1d\xc3\x12\x03\xc0k\x80*j\xc0\x97\xf6?\xe0%%\xb8\xaf\x14\xf0?\xd8#\x02/\x96\xd2\xe6?\xd0\x96\xfca\xeef\xc8\xbf\xc60\xc4\xc3lw\xe2\xbf\xdd\xe4\xeb^ &\xf0\xbfv\xa4".\xca4\xf1?=\x877D\x883\xf2?bS\x1b|dn\xf7?a\xa0\xf3\xf0\x114\x02@\xfds\xd6\xe0\x81\xd5\x01@Et\xa6U\xeex\xe8?+\x0c\xabO\\@\x04@\x9c\x9d\xe9\xa5\xab5\xe6?F\x95\xf6?\x1bg\xee\xbf\xe8\x07\x19\x92\xe3H\xf3\xbf\xda\xa4\x9d$\xa6\xb7\x00@\x83\xc2\x04\xf81i\xc6\xbf\xbd\xd5\x06\xd0d\xd0\xc5?\xae\x85\x90E\xb8v\xda?\xb1\x85\xd1\xaa^U\xe3?2\'f{\x12\x1e\xf1\xbf\xcfX\xffu\x10+\xdf?YS\x96*c\xaa\xe1?\xbe\xd3\x1e\x08~\x1f\xe1?\xec`Y\xecj\x1b\xd7?\xbb\xa3\x9fM\xdc\x9e\xec?`\xf9\x06\xf5\xeeT\xf3?\xf3\xdf\xbf\x01\xc6\xf4\xbb?\x7f$ \xcb\xbb<\xe5?\x97\xf9\x93\x8f\xa7w\xe8\xbf\xf0n\x9a9\xb5H\xf9?\x9b\xad@\xd3\xc0\x10\x00\xc0\x15U\xaf\x8c\x99\xf8\xf3\xbf\xa2\\\xff\x9e\x8a\x97\xec\xbf\xfd?l\xf44\x1a\xa3\x9d\xe2?\xd8\x84t\xfb\xdf\xd0\xeb\xbf\xe6\x9fY\x8d<\'\x00@\x89dj\xb6%\x16\xe0?\xf3]<\xa6\x85\x08\xe2\xbf\x03SL\xa0_\xc1\xd6\xbf!l DG\\\xb1?iH>f1\x00\xb9?M\xf9\xc5\x93>\xbb\xd1\xbfL2\'\xc0\x89\xd8\xd5\xbf\xe0\xad\xdfd\x9c\x13\xe5\xbf\xad\xdfE\x85\xa4\x08\xed\xbfX\x8b\xf1w\xdbd\xf4?l$\x13\xf4\x15\xe3\xc1?\x86\x7f\xa0:\xb7\x8d\xed\xbf\x11\xd2\x88\xa1\xe4C\xe4\xbf\x05\xb8\x04t\x8f\x81\xf1?\x88x4\x0cA\xec\xe0\xbf,7\xf7\xd2\x11\xc4\xe7\xbf_0eV\xd1K\xdd\xbfY9\xd1[\xa4\xf9\xd0?OXj\xfe\xff\x1e\xd5\xbf3\xbce\xa6\x89\xc8\xea\xbf\xe1\xe5\xb7\xa9b\x14\xc2\xbf\xc4\x01\xb2\xb1\x86\xef\xf0?ew:\x7f>w\xea?\xe3\xa2\xe8-\x06\xe1\xc8?\xc00\x06\xdcft\x85?\xc0\x1c,\xb0\xba\x19\xfc?\'\xe2\x17G~\x89\xfc?\xdcX9\x0f\x8a\t\xe0?J\x97\x8a=\x04\xfc\x9a?\x1d\x91\xa1\x0cq\xa9\x97\xbf_\xe5\x7f\x83G+\xc8\xbf\xed*\x07\xab\t\xd6\xd3?\xc4\x96%J\xfd\x8a\xdb?\x88\x91\x122\xba*\xe6?\xe4\xe6\xe9\x06&D\xf3\xbf\xb1\xf6 \xc9\xde\xab\xdd\xbf\x8awt>\xff\xdc\xd5\xbf\xdf<\xa7\x0c\x8b\xe0\xf3\xbf$\xcd\xf3/\xbe\xc8\xf5?\xba\x8d\x1b\xbc\xff{\xf9?)\xcb\\\xf8\xeb%\xfa?\xdbh\xa3\xe7\x1b\x85\xe5?a+Y/\xe7\x93\x01\xc0e\xf4+kR\xcc\xf0?\xb8\x86s\xda\xfe\x16\xf4\xbf\xbd\xd5;;\xe2\x8a\xee\xbf!\xb5k\xa3F`\xb5\xbf\x12\xf1|\x9c&\x18\xe5?*!\xb6\xfa\xed\x9d\xdd\xbf\xac\xed\xf0\xc8NH\xe5\xbf\xd6F0\x8a\x08C\xef\xbfp\xce\xb1c\x04\x17\xf4\xbf\xfb\xfc\xfd\xe6E\x99\xf5?\xd8\x82\xecV\x16I\xed\xbf1>\xacH\x96\xa0\xf0?\xdf[\xac\xaf\xba\xb5\xd8?\x05\x1c\xbd\xa4\xd0\xba\xe4?\xfad8;\x04\xc2\xf2?\xa8\x97\xef\xf3\xd6\x07\x90?\x00R\xa9r=\xc5\xe8\xbf \x84\xa2\xfd\xe8\xf2\xcc\xbf\xaaRR\xd0\xc1\xfc\xcd?\xcd\x1a\xbf\xd4$\xa7\xd3?*\xfdq\x15\xe0\x10\xfd\xbf\xce\xbc\xca(7;\xde?KQF\xfcB\xb0\xf7?z\xeb\xd3\xcd\x7fm\xe7?zL\xf89\xfb\x82\xa6?\n\xe7\xddW\x93n\xc4\xbfl<\x82\xd2l\x97\xda?@&\x00\xd2S\xf5\xd4?+\x01\x11\x13\xa8\xe2\xe2\xbf\xbf\xc2\x80\xe9(\xee\xdb\xbf \xd1??G!\xe11\x8e\xf2\xbf\xacZl\x82\\\n\xf6?\xac/\xfc\x99\xe0\xe5\x01@\x1bt\xe6\n\xf3\x90\xf2?Q\xf0KL\xe6\xe3\xd0\xbf\x85\x00\xe9\x1e(A\xfc\xbf\xfd@d\xa7R6\xe2?\x81i\xecuA\xc5\xfb\xbf"\x86+9q\xbc\xe6\xbf*\xf8\x8eq4\xea\xe6\xbf\xc0;\xfa\x8a\x91\xc2\xe9?\xb2\x91\xf9\xf2\xf9W\xf1\xbf\xddR\x12\xa2\xa9E\xd9\xbf=\xb3\x8c\x168\x05\xd1?\x933!$\xba\x08\xf7?]\x84\xe3\xa64\x17\x92?i6\xb2\x94\xf9\x1e\xd2\xbf\x0b\xbb9-\xa6\xbd\xf3?\xafB\xec\t\x92\xe2\xd5\xbf\xa3\xde\x13M\xf6W\xc0\xbf1\xfan\xb9\xe9D\xf0\xbf\xff\\x\xab\x14\xc0\xc4\xbf\x1c\x07p\x07\x8d1\xeb\xbfL\x14\xd5~\xe1\x88\xd0\xbf\xb7"\x13\xef:\xa4\xdb?\xf8\xef\xfd\xef\xf5s\xf9?\x87\xd7\xae\xe2\xedG\xb3\xbf6\xc4\'9\x14\xac\xe1\xbf\x14\ttDv\xcc\xf1\xbf\x0b.\xe4,\xbd@\xf5?K\x97\xeacO\xcb\xaf?o\xda\x8f\xee\xe1\xb0\xea?\xae\xd4\x08\x00\xab\xe0\xf0?\x97f\'T}\xc0\xe9?\x10m)!\xdd\xcf\xed\xbf\x7fZs\x1f\xf1\x06\xc8?\xee\x15K\x99IC\xd0?\xa3\xcb4\xcb\'\xfb\xf2\xbfO\xae\xba\x1f\xcf0\xf1\xbf\x13y\xbb.p\xb2\xd6?\x891\xd0U\xc0\xc8\x00@\xd9B\x98\x90\xb2\x1b\xf1?\xe5\xc6\xc5z\xdd\t\x01@\xab\xe9\x01b\xac\x16\xf5?yO\'\x96C#\xb6?(0\xb9\x89`\xde\xed?\xdc\x90\xea\xcc\xf9\x8c\xc4\xbf2\xc2\r9\xbbI\xda?~\x15\x00\xa2,\x14\xd9?\xd0\xcb\xbd\x1c\xf3p\xd0?\xc5\xa1z\n\xe7F\xdc?\xe6\x1d\x1a!A\x0b\xaa\xbf\xce\xed\xdf\x8e(\xb9\xe9?\x02\xb2q5\x02l\xf7\xbf>\xa3\x0e\xb2ue\xa4\xbf\x1e\xea/\xd5\xf2\xa7\xe6?\xb4\x07n\xea\xf9\xb6\xf5\xbfhd5{\xa3%\xe4\xbfh|Q\xe81\xc3\xcc?W\x93\xef!\xc2\xdc\xee?n\x86S/\xfa\x05\xe3\xbf\x15G$\x07s9\x00\xc0\x990\xdc\x10\x1e\'\xe9\xbf\x9a..\xd6h\xac\xe6\xbf\xaf\x87\xb2\xec\xe3\x1b\xeb\xbf\xb6OQ\nU\xd7\xe4\xbfO\xc4\xd7~\xa2\x98\xeb\xbf\xd4\xe9\x16!\x93\xe1\xcd\xbf\x0c\x1f\xccU\rx\xfa\xbf\xca\xc4\xb52\xab\x1e\xf0?\xa9\xe8\x9aUl\xa3\xe8\xbf\xa9\x85\xa8\x0c\x07O\x00@!\xa8\xdc\xe9_w\x00@}\xdep\xd1\x8e\xb5\xd9\xbf\xa5\x80,\xe2\xb9\xd1\xed?kE\xf7\xe4/\xe8\xe3?~b\xe2\x86[\xe2\xc4?\xb7(p<${\xf2?\xfcg\xfaS\x19\x87\xd9\xbf\xd7^\xd5\x83\x00\x82\xd8?\xe9Y@\xdf\xea\x93\xe1?*\xa9\xee\xdd\x02\x85\xf8\xbf\xda\x9eD,\xf4\x1b\xf2?\x0b)\xc3\xfc?\xfa\xe7\xbf\x85\xed\x84M\x03\xc5\xda\xbf$\x9a\xcd\xc7\xd5\xd9\x92\xbfb\xde\xfe\x180G\xaf\xbf2\xb7r\xf8j#\xc4\xbf\t\x9e\xea\xf7\xb1\xfe\xf2?\x0c\x11j[\xe0V\xb6?\xfb\xd5\xcb\x12\xe2h\xf9\xbfW1\xef\x92\x86a\xfa\xbf\xa3&y&\xf9\xb0\xf3\xbf\xef\xe7\xe9\xf4E\xee\xdb\xbf\xfc\xa8R2\xcd\xcc\xb3?\x9a\x97Xo\xb7\x96\xe0\xbf\xfd\xe5\xb4\xda\xf9\x94o\xbf\xf6\xbf\xe41\xd9\x85\xe2\xbf\xd8V\xdd8\xdf\x83\x9f\xbfj,c\xc6\xa0\xc6\xe5\xbf\x8f\xde\xeab\xdc\xef\xc9\xbf\x8c~\x1a-\xee\x08\xc3\xbfU\xdc\\[b\x91\xdb?~\x186\xb3\xab1\xed?!\xd9o\x14@\xa7\xf4\xbf\xf1Gk\tF\xb8\xd4?\xc3\xae\xf7_\x0c\n\xed?\x04M\xf3\xdd\x8f \xb8\xbff\xd1\n{v\x1c\xf9\xbf{*\xe8x!\xac\xf7\xbfM\xa4\xa5tI\xd4\xf4\xbf\x08\x16\x1c!\xd5\xd1\xf1?\x84K\x85Ml\xfd\x9f\xbf\xadi\xa4\xc9$A\xd7?\xd6\xebL\xd1\xb4\xba\xe9?yr=\n\x14\x91\xfa\xbf9\xa6\x03\x99\x8b9\xed?\x87p\x0f\xa8G\xee\xd7\xbf\xee \xaeU\xefG\xe9?\xe6\xa0!\xe5\x12\x9e\xcd\xbfX\xc2\xc6\xee\xfb \xd7?\x97\xd5y_i\x08\xcc\xbf\xa7OJl\xce\xf8\xf6\xbf\xa1\x02^\x0e\xed\x8e\xf6\xbf\x0f\xc3\xf3B\xe2\x04\xf3\xbf\x861\x13\xf9/c\xf1\xbf?_\xa3\xdc7.\xf9\xbf\xd8\xc4e-\xe4\x8d\xe1\xbf\x80e\xc4\x05\x0f\x8d\xe2\xbf\xe0\x8e\x9d\xe7\xffx\xe6\xbf\x9d\x82\xc5\xfe\xd8"\xcf?\x8dG\x06\x82\xea\xeb\xc9?A\xf7\x0e\xe8-\xa5\xf3?\x87Y\x94\x0b\xc0K\xa3\xbf\xa9\x19h2\t[\xdf?_\xe4<~\xc2I\xa8?\xcf\n\x13%\xeef\xe6?Z~\xe0\xd6\xcfB\xe9?\xab-\x8f\x91\x94\x19\x03@\xb7\x9f\xd1K\x90\xd6\xd5\xbfW\x00y\x8a\x01\x91\xc3?\xc7\'-N\x01\x82\xce\xbf\xdf3\xde\x1b|\xf9\xcf?\xb9\xa6\xe4h\x0b$\xf2\xbf\xa1ut\xc6\x0f!\xf1\xbf=\xbf\x8aE\x00z\xf9?\xbf\xcf\x95\xa1\xf7\xb3\xf3\xbfb\x0c\x1aw\x98g\xe2?\x87\xf2%\x88\x86\x0e\xed\xbf\xde\x01\x10r\x04\x1d\xee?\xce\\\x97t\xab[\xd8\xbf.)\x93\xaf>\x9e\xb6\xbf\xb32\x15\xac\x02y\xfc\r\xe5\xe9?\xa0&\x95\xfd\xcb\x93\xa6? \x8e\x97\xd7/B\xa6\xbf\x8cAr\xbfb\x13\xeb\xbf\xb2\xb4\xca\x85\xdc\x13\xd0\xbfJ\x02~)~\xfa\xf1\xbf\xa62\x8c\xb7\x9c\xdf\xda\xbf]\xb1\x13n\xd9;\xc9?NT\x81o\xa2\xcc\xfa\xbf\xb7\xb6|\x87\xf0\x1c\xe3?\x1d\x19\xf9\x8cQ\x07\xd3\xbf:\xc8\xfb\x96W\xe6\xf0?\xb0\xaf\x0c\x9f\xba\xc5\xca\xbf\xae\xef\xc6G\x02\x83\xe2\xbf\x92h(\xc2\xffB\xb2?\x1bo\xc4\x8ed9\xfb\xbf\x8eg\xa7C0k\xc0\xbfo\x16^\x10\xa8u\xf4?\xe2\xe1[}\xb8k\xf8\xbf\x14\xd0\x9c\xa4\x13k\xc8?\xa3piM&\xd2\xf2\xbf\x1d\xe5\xc8\xf9:\x1f^\xbf\xa5\xd5\xe8;bx\xf5\xbf4\xa1X\x99@\xe4\xe8?\xc4\xe83)\x84\r\x99?\x17\x86\xf7i\xb2p\xd4\xbf.\x0b\xe2\r\x9c\xf1\xf7\xbf\xf7\xdf\xe1\xac\xf0~\xfe\xbf\x10\xd7\xb6\x9e\xe8\xda\xeb\xbf\xc8\x85\xeb]\xbc\x87\xca\xbfe\xe9C\xb9T\xcb\xe6\xbf\x83\xfc\xafR!-\xe2?\x16T\xd9\xcc\x9a\xcd\xef\xbf\xf4\xcd\x86\x18\x03\x9a\xe0\xbf\xad\xeck}\xa8\xd8\xd5\xbf\x95T\x94K\nc\xf0?0\xd2Tw\x02\xdd?(zun\x95Y\xdb?\xb0\xf2T5-,\xe6\xbf\xd1\xf0\xb1\x15\xef\xde\xe2?U\x13~Zo\x05\xa4?\x12\xc3\x0f\xb5\xfc4\xf1?Y\xcd\x07rr\x86\xc3?\xee\x00C\x8a\x04\xb2\xef\xbf\xde9\x7f(\xed\x12\xce\xbf\xfeZ\xb8\xd7\x0cO\xe8\xbf\xaf#\x0f\n\x0c\x7f\xef?X\xd0\xdf,WQ\xe7\xbfgf\xc5t\xc0c\xe9?\x9c\x9d\xe2L\x1a/\xf8?\x1f\xf0\x00;Q\x1e\x00\xc0\xa8\x1e\xda\xfa\x11\xe4\xe9\xbf\x05\x90\x8a\xceU\xb3\xde\xbfY\xbdF\xc6\xc1\x82\xb3?+q_1\x02B\xca?y\xa0d\xde\x82\xda\xf0\xbfxsx\x94\x94\xeb\xe9\xbf\x07\xe3\x82=\xcdA\xe5\xbfC\xbaU\xa2\x03a\xf4\xbf U\x8b\x92\xaf\xb6\xec\xbf\xa7\x1e\x12<\x94\x8a\xf2\xbf\x0fP!\r\x0f5\xd5\xbf!_\xe4B\xdcH\xd6\xbf\xbc\xaa\xe5A\xed\xbe\xeb\xbf\\;\xf6Y\xbd\x92\xf0\xbfS\xc8\xad]\xac\xfa\xf1\xbf\x9d\xf6g\xc63\xca\xdf\xbf\xab0\x08tn\x8b\xd8\xbfEx\x0e]\x04\x88\xf4?\xee5v\xe97\x13\xb4?\x01:\xd8\xfaf[\xd3?\xe2\xb0\x1b\x18\x96\x14\xf5?z\xae\x0b/o\xcf\xe0\xbf\x1b\xd7\x10\x81\t\xbd\xf1?k\x9a0\x8d\xd0\xbf\xe6\xbf\xa9\xa4\')\xa6\xfa\xfd?h5\x92M-H\x00@%\x98T\xc7b\x80\xf8?\xd6\xa7dz\x9bP\xdc\xbf|/\xde\'\xe9\x04\xa5\xbf\xfb\x93\xcf\x13K\xd1\xf4?r\xbc\xe2\x90U\x97\xdf\xbf\x8f\x85\x1e\x9b\x14\xc4\xfd\xbf\x9e\xbcK\xf8\xa5\xbd\xc8?\xe9\x98\x81\x10y"\xc3\xbfu0\xfa\x062\x83\xb0\xbf\x0c2Ch\xc9\r\xd4?\xbc3l\xc6\ra\xd2\xbf\\\x11\xbf\xefR&\xe7\xbf#\x07\xcbJ\x18\x91\xd0?G\xba\x83\xbc\xe7\xbc\xdd\xbfH\x0b\x04\xf3\xf5P\xd5\xbf\x7f\xff>\xbb\xd4)\xe4?J\\\xd3\x16r\xcd\xbe\xbf\xd7\x95A4F\x1d\xe7\xbf\x9b5\xd3f\x1cO\xe7\xbf\x08\xd37A\x8d\x7f\xfc?bA(\xf2\xdeU\xf2?\xd0x[\x1d\x8a\x9f\xfe\xbf\xae[\xa7\x98Y\x8a\xcd\xbf\x83_\x85*\xc7\xe5\xe7?QL\x0fL(\x13\xdb?\xae\xee\xe0f\xf3y\xf0?\x9aT\xc2\xd0\xf2\xe3\xe3\xbf\xabv\x8f5\x06>\xe6\xbfWe\xf0\xa7Q\xa2\xa6?\x83\x9c\x8bd{t\xf3?)\x7f\x84\x86\x8e\x99\xd6\xbf\xde_y$@\x9b\xd7?\x81\xe3\xd58\xfb\xf2\xcb?4l\x8b\x11{\xba\xf0\xbf\xae\x0b\xc0\x1ff\xcf\xba?W\xd0\xd1\xb3\xbe\xd5\xeb?Co\x90\x81Fb\xbe\xbf\xa0\x9e\x0b/k\xd2\xed\xbf\xe5\xa1(\xf1\x86\xbc\xcf\xbf\xbe+\x9e\xeb|=\xf3?\x00\x06\xb2\x97\xa2y\xdd?\xe2G\xeb\x0e\xe9\x84\xe3\xbf\xc1\x0c\x99\x98\x95:\xe9\xbf\x0e\x8d\x90\x87Q\xa7\xf7\xbf\xf6@\xa7)\x88.\xf3? \x03\xdaJ\x89\xcd\xdd\xbfC7\x11\xc5\xbd\xc6\xe1?6\x1f\x0b\xde\x99\x18\xcc\xbfF\x94*\x1b\x9a\xfa\xdc?\xf5\x89g\x80\xeaa\xf3?U\xa3\xd5\xa6\xde\xf7\xd5?t\xa9\x19{\x00\xd9\xf4?K\xbf2\xef\x99\x92\xfd\xbf\xe6\xe0\xbf\xb5\x08\x1d\xe7?\xd9`\xef\x86b\xc5\xed?\xf9\x8b\xe3\xac\xcek\xfd\xbfr\xaeF\xb8\xa7\xbc\xf9\xbf\x17w\x9139\xd0\xf1?\x8a|\xe8kFA\xf5?f \xb7\xf2\xcb9\xed?\x85\n\xc3I\x86\xf6\xfb?N\xcb]\xdc\xa6\xec\x02\xc0\xdc\x9f\xb9x(\xd1\xed?\x89\xe8\xc2,3l\xdf\xbf\x8c>\x82W\xd5\xca\xe0\xbf\x01\xc9\xfc\x8a\x81\xf9\xeb\xbf@zvkp&\xf3\xbf\xbc$\xee\xe1J\x8c\x0b\xc0\xa1\xe1\xb4\xd3\xde\xf5\xe4?4\xb4\xd9d\x86C\xc1\xbf\xf9\xd1F\x9c@{\xe3?0\x00\x07\xa6v\x0f\xf8?\x16k\xdfI\x81B\xf0?\xb5F$\x801Y\xd8\xbf\xf8z\xfe-\xf0\x8d\xa7?K\xd6\xd7\x102\xc3\xd7?\xe3\xe6p\xe7\xbbT\xe3\xbfaT\xc1td\x9e\xeb\xbf\xfe\x9d\x9d\xf4\xf75\xe3?\xf5\xaex\x00\x9c\xe7\xd4\xbf\xda\x8a\xa7\xd6v5\xe5?\xcem}J\\e\xb0?\xad\x8f\xd2\xe6\xf3\xa2\x8a\xbfr\xf0\xfc:\xfeI\xd7\xbf\xf4>\xf9\xe9\x857\xa5?)\x04\xba\x9d3)\xf5\xbfEp\xd2\xc8F\x05\xa1?\xbdG\xd7\x87\x98R\xd6\xbf\xe7N\x82\xe5\x8fs\xf2\xbfW\tX\xf4\xf4P\xfa\xbfv\x16\x17\xff$\xe9\xec\xbf|UT>\x13i\xea?\x8b\xf3\x1bW\x90\xb3\xbc\xbf\x1cn\x9aosi\xf8\xbf<\x14kiwA\xcd?\xdb\x1bo\xa4\xc7\x92\xf5?\xe6\x17@\xbc\xe7:\xe6?\xdf\xdb#\xeaR\xa0\xd3\xbf>\xf0\x8a-\xed\x92\xeb\xbf!\x11\x1fG\xb9\xad\xf9?!jt\x82_3\xd8\xbf\x1b\x8d\xf6 n/\xe3\xbf\xa2\x87-\xc4\xf7\xff\xd5?=\r\xbd\xae\xe4V\xef?\xc74\xa2\xa7[\x0e\xe5\xbf\xcbzM\xa3;V\xeb\xbfOc<\x8cm\xa3\xf3\xbfw\x9f\xee\x1f\r\n\xb9?\x96\x1c\xf5\xd2\x04s\xe1\xbf\xfeuM}\x8d\x1a\xcd?\x8a\x0e\x1a7\xb2H\xc5\xbfBaF\xbf\x06G\xe5\xbf\x91\x8f\x05\xbc\x8eT\xd1\xbf\xef\xf4\x0cC\xb9c\xa3?b\x05\xa8\x98)\x99\xeb?b=2\xe4\x191\xf0\xbf\xfb\xd9\x8b[\x15\x06\xde?\xdeU\xc3$\xd6\x0b\xda?j\xbd9\xc2\x8dB\xf8\xbf\xac\x13W{x\xd0\xff\xbf\xf0\x10+\x9a\xebt\xe8?3?\xa0\xf0VF\xea\xbf\xfa\x05\xfa#\xdfw\xd3?\x069\xae-0\x9f\xac?Y\xff98~C\xca\xbf@m\x0b\xc4\x1c\xc4\xff?\xb3\xd6F\xc8\x11L\xd4\xbf]d~\xe1\x90\x92\xed\xbfnL\x7f\xd9%C\xfb?\xf9e\x1c\x97\x86\x89\xd1\xbf\xa0\x8e\xfd\x921\xde\xf2\xbf^\xfe\xd7\xa50\xbd\xf0\xbf}d\x0f\xe7\x17\xfc\xda?\xde\xcd5\xb0\x14\xe8\x08\xc0U\x01T\x9fg>\xea\xbfx$\xb3Wd\x03\x01\xc0\xa3G\x1b>\xd9\xf2\xe4?\x06\t@\xd1fC\xfc\xbf\xa0\x1b@4\xa0\xf3\xe0\xbf\x96\xc0\xf3\xf0.\xf2\xbe?\xbc\xe7\xb2\xe9\x90\xb4\xef\xbf%\xfa\xba\xf6U(\xa2\xbft\xa4\x0f3\x03\xfd\x94?\x8fu\x82q=t\xe3\xbfn\xb7\xa8\x0c\x1f7\xe9?m\xb4\xe4m\xe4\xf7\xdd\xbf\x90I\xa0\x03\xfd|\xd7\xbfO\xb4\xf24\xa9}\xf3\xbf\xa2\x80\xbb\xa5A\xb4\x01@\x8e\xd5\xfe9\xf4%\xe7\xbf\xdd\xfcSm\xec\x8a\xd7?\xe9U\x15\xbbBw\xe1?\xf5&\x11\xbb\xe8\xba\xd7\xbf~\n\xd0vi\xa7\x02\xc0\x84{\xbe\xb9\xec\x11\xd6?\xadM\xa0\x12@\x94\xe8\xbf\x90\xac\xb9\xf9XS\xee\xbf\xa3\xd5\xbe\x9b\xeci\xcf\xbfg\x89Y\xc8C\x13\xf0?j\xf6gI\x1f\x8b\x05@A)H)\xbd$\xaf?\x9c\x90\x07s\xc4T\xd8\xbf\x91\x19\xf8Y\xf6\xfe\x06\xc0\x99\xe0.\x17\xcc\r\x07\xc0\xdcm\x1e,\xc7\xe9\xe9\xbfp\xe5i\xb6\xfc\xd4\xe8\xbf3p\xc3\xc0&\x89\xa1\xbf\xb1T|)\xec\xd6\xe8\xbf$\xd7\xf8\x16}*\xd4\xbf\xd4\xb9\xbct\x9c\xa4\xf4?\xb2\x16xx\xda\xef\xc7\xbf\x99\x0f\xe5\xafH\xa1\xa5?.\xf0P\xbd\x89_\xe3\xbf\xd7\x7f&1\xb3\x84\xde\xbf4\x9e`\x17\x8d{\xe6\xbfY\xec\x19Q\x12\xe2\xd4\xbf\x101\x94\xa4>\x0b\xec?\xf3C\x06\xac\x10\xd2\xe7?\xb03\xe9\xbb\xaa4\xfa?,\xaf!G\xadk\xf0?\xc7\x8eNy\x9d\xde\xd5?\xb8\x9f\x8b\r\x86\x10\xe1\xbf+ZO\x10\xd0\xc8\xe3?j\x18@\x11=l\xd2?H>\xda_\x12\xc1\xe3\xbf\x8d\xde\xf9\xa9\xfc\xc7\xee?uW\xf2?\x84(\xc2?\xc2\xfb\x91\x00\x15\x1d\xdc\xbfG\xae\xd5\x98\x98\xd2\xb9\xbf=\xb1(o\xaf{\xff\xbf\xa3\xbe{0\xb1e\xbb?\xb6\xda\xf7\xb47\xd6\xe1\xbf\xee\xf9\x18O\x9cO\xf1?^\xda\xe84\x85\t\xe5?\x07!E}\xe8\x81\xf5?\x90[d\x87.`\xa1\xbf\xe5JG%\x9f7\xfb?\'\tt\x1e\xabV\xe8?\xe8e\xfcw5P\xaa\xbf\x8f8{\xc4c\x85\xbc?:C\xa1v(0\xe9?\x9d\xa8t\x1a\x8d\xa5\xef\xbfJ|\x96\x00[X\xec\xbf\xf8\xab\t\xe8l\x8a\xe2\xbfDi\x15!\xb0\xa6\xf3?\xbc\xfa\xa1-\xac\x1f\x06@\x8d\xb5\x14\x81#\xe7\xf5?\xf8ch\x99\xee\xdc\xf8\xbf\xe5\xdc\x7f\xe4\x1f\x1e\xfa\xbf\xf3_\xa7\xe5\x1df\xdc?\x86\xf8.\xf28\xce\xf2\xbf\x94\xfc\x8fA\xab\xa2\xe4?\xe54\xc3\xe79j\xfd\xbf\n\xae\xb2\xf51\xb0\xc8\xbf\x15;\x93\x86\x9d\x05\xfa?\xec!\xee1+Z\xe3\xbf\x02\xefHA!e\xf1\xbfh\xa3t,S\x07\xf2\xbf\xf8\x9ae\xdd\x1fF\x04\xc0\xb2\xf1\xf35\xb7\xd1\xf2?\xa9\xdd\xc8\xdc\x87\xb7\xf6\xbf6\xd1O\xbah\xa2\xce\xbf \xff\xe1T\xea(\xe1\xbfH\xf6\xb0\xfcYy\xd5\xbfB\xf7;\x0f")\xd3?\x18\xaf\xc8\xb4\xd8Y\x00\xc0\x07v\xba\xe4\x00\x0f\xf3\xbf\xa8N\x1db\x8a\x95\xf6\xbff\x95\x83\x13\x89b\xf4\xbf\nn\xcfY<\xdb\xab\xbf\x8a_\n\xdf\xd1\x8b\xd5\xbf\x80A\xd5\xe9\xca\x00\xf5?\xd6X\x059\x83]\xfb?)\xcb{Eb\x97\xef?\xce\'E\xfc\xcbG\xf4\xbf*\xd9\xb9\xe8;\x10\xdf\xbf7n\xf233d\xcc\xbf\x03\xd6\xf5\xa2\x95\x9c\xd7?\x85\x0b\xaf\x8ax[\xf2\xbfCE\xd6\xd8\x02v\xc3?j\x86Hg\xe3\x8f\xf6\xbfp\x8brBV\xeb\xf1?\xc5\xbd\xc1x6\x0c\x08@hz\x17\xf6z\xc3\x0b@\xd4;[g\xe5V\x07\xc0\xacJ\xe8\x04N\x96\x0b\xc0\xf4\xe9\x1f\t\x99]\xc3\xbf\x1d\xbb\xef\xdd}t\xc9\xbfE\xcc\xdcF\xe8\x1a\xd0\xbf\xa5\xcb\x08sOI\xe2?\xce_!d\xbdu\xea\xbfdt#\xbe\xfe\x9c\xe8\xbf\xfe\xd9U\x01\xa9\x82\xd0\xbfu[\xb4sF\xbf\xd3\xbf_M-\x03\x0f\x7f\xef\xbf{jZ\xa2\xbd\xde\xf0\xbfy*\xf9\x1eca\xd3\xbf\xa8\x90\xbcg\x98v\xe6\xbfiH^\xdf\xe75\xf4\xbfiB\xcbg>\x8d\xec?\\\x15q\x80\t\xdb\xee?\xcf\xaf7w\x8f*\xd1\xbf\xbc\xd19\xc0\xac\xdb\xed?\xc86\xa3vU\xec\xf7\xbf\xf6F\x06\x81\xbf\xf5\xf5\xbf\x8aq\xfc\xcf{7\xe0?\xf3%W\xf7\xb0\xa4\xdc?|vP\xebi\x98\xce\xbf?D\xc1li\x95\xec?\xe1C\x97\x95\xe6\xb7\xf9?+\x00\xef\xfe\x91m\xb5\xbf\xc3&!]\x1e\xc5\xfb\xbf\xce\xe1J\x86\xae\xf8r?\x07\xdf\xba\xbb^\xc1\x03\xc0\xa2\xb0Y\xbf\x87\x1e\x0f\xc0/-\xac\xd3k\x16\xe9\xbfk\xb6\xff*\xf3\xf4\xa4\xbf\xe4\x84\xc0\x97\xc9T\xc4?\x10d\x1b\x9b\x97:\xdf\xbfn\x17\xccO;\x9c\xde?u\xdb\xc0Y\xa5\x8e\xf0\xbf j\xeb*x\x1e\xe4?b\xb7\x9em\xa2{\xf3\xbf\xdfeM\x0b\xaa\x1eU?l`s\x1a\x0fe\xd4?b\xb0\xa2\x94_\xce\xf1\xbf#\xd6\x9e\x05\xcc\xc6\xf7?\xc4\x08\x18$\xb3\xc5\xf5?s\xb39\x92\x08\xd4\x03@\xa5\xfa0\x1d\xd7v\xf2\xbfx\xd0\xbcv\x9f\xa0\xfa\xbf\xbb\xd0U\x95b\x88\xf6\xbf\xf6!\x03^w3\xf9?\xac\xf8V\xae\xc1Q\xd2\xbf*\x8f\xd0x\x1dU\xfa?o7\xa8\x8eEn\xd4?\xd3\x91z>\xe9\xf4\xce\xbf\x1f\xaeF\xf3/\xf1\xf1\xbf9=\xaak\xda\xfb\xda\xbf\xa7\x8c/\x02\t>\xe4\xbf\x92g\xb2\x90U\x10\x06\xc0g\xb0\x8d-\xd2(\xc7\xbf[\xb2\nNk\xb5\xf7?\xd3\x1d\x13\xe1\x7fQ\xcf?V`\xd0\xe2]\xd8\xee?c\x8c\xcb\xde;N\xd0?\x97)\xbeHGx\xd8\xbfAw\xe9\xd0\xd8\xac\xdd\xbf\xbd\xe7?\x88OS\xd1?\xf9\xeb\x8e,\xf6\x84\xdb\xbf\xfbV\xefEt4\xdc\xbf\xe1\xe4\xb4\x04\xc1}\xf7\xbf\xc8]e\xfa\xf3\xe1\x04\xc0\xd0\x7f+-\'\xff\xe7?%a\x9c\x13\xec-\xf5?\xb5\t\xf3\x99\xcb\xe0\xeb?\x07\xc4)\x84\x88r\xf6?\x80R+;u\xa2\x03@\x0e\xf5\xe8\xc3\xb0M\xfc\xbf\x9c\xbe\xd8H\xeb4\xeb\xbfW\x17;\xb0!e\xcc\xbfd\x04\x82\x07\xe1\x9e\xa1\xbf\x8a\xa6\xe6\x8a\xf7>\xe1?M\xd5Ie\xdbf\xdc\xbf\x17\x13\xa7&8?\xfd\xbf\x93-4\xd3\xdcU\xed\xbf(z[4N\xd8\xeb\xbf4\xf8X\x1a\xcfj\xf2\xbf\x99&s\xfd\xb4\xd5\xd0\xbfg\x1c\xbe1{p\x99?\xeb\xdep,d\xea\x9e?\x86\x056\xf5\x94\xb5\xbf?\x1a\xeb-\x9aOk\xef?\xc0x}\xae9\xa6\xe1?v/\x9c\xf0\x85\xe9\xc6\xbfl\x00\xe4vy\xc2\xfb\xbf\x03\x04%i5\xce\xf6\xbf\xec\xad\xb2.\xd4{\xeb\xbf\xddk\x05\x9b0\x0e\r\xc0\x81\xb8\xa7M\x8c"\xe2\xbf\x1fDb\xe7\x83\xd3\xe0?\x9e\x1a\x17xsx\xc2?T\xadfN(\xc6\xe4\xbf\xba\xd4WU\xc0\xf5\xfb?n\xc8\xc9\xa3\x14c\r@\x97\x99\xb8\xa3%v\xeb?j\x8d\x82\xf9Z1\xfe\xbf\x8c\xc4-\xc7\xa4\x94\xfc\xbf\xee\x92\xee\xc8b\x13\x00\xc0\xf3=#\xdd\xa7\x8e\xd7\xbf\x8d\x9e\xf6\xc8\xe0\x96\xfb\xbf\x13b\xd5\xdc\x80\xb6\xde\xbf|V` \xf5f\xe9\xbf|s\x07R8~\xea\xbf\x9d\x96<|\xe8\xd9\xf3\xbf\x1d\xf6\x9eh\xbd\x07\xb1\xbf\x99\x90<\xe8^\xb2\xe7\xbfP\x97\x9c\xab3\x02\xce\xbf<\xb0\xd2\xda\xf4\xe8\xd3?k88\xd4\x95\x93\xe9?L\x9aq\xee/u\xe0?\xde\x92\xa8\x88-\xb1\xdb?K\xad\xec\x81e?\xed\xbf=n\xf1\x04\x86\xec\xe7?\x95\xf1\x14\xa6z\x1f\xed\xbf\xdf5\x87N\xf5\x1e\x02\xc0\x15\xdf\xa1\x88:\x93\xf7\xbf=\xf4\xa2\x81\x17l\xef\xbfEv\x93\'QV\xe4\xbf\xc0\x18\xda\xae\xf7\xca\xf0\xbf\xe0\xc8\xb6\xa2\x07\x9b\xf0?-\x83\x9e\x95\xc2\xb9\xf2?gY\x98\xb3g>\xf8?\x8c\x81\x98\xc5\xb9f\xf9?\xa0\xfde!q\x10t?\xe5q\xda\x8cB\xfa\xe9\xbf\xc3NG\n\x07\x99\xf5\xbf\x9b3\xcc\xec\x06R\xf6\xbf\x9c\xe1\xb2\x08T\x9e\xb2\xbf64\xa2\xd5\x91\x86\xbd\xbfvr+\xf5@\xfb\xf7\xbf\t\xc91\x0e+\xbc\xe9?)\xb7\x0e\x16\rK\xf6\xbfMM\x06\xaab\xfe\xe6\xbf A\xf0\xc4\xb2I\xd0\xbfy\x97m\x8d\x9b\xf8\xa3?\xc4F\xe7\xd3\xbe%\xf3\xbf\x1bQ\x94\xac\xe3\x12\xf2?\x04\xc0\x9e\xcb\xfc\xe0\xd3\xbfAb\xef\xafz!\xeb\xbf?\xaa\x93\x1fr{\xed\xbf\x9a\x1f\xff\xc3\x1fc\xd6\xbfM\\L\x98\x9e^\x03\xc0\x8fs\xdd\xd1\x8fo\xe6?\xb7\xeb\xd4\xfe\x8a\x84\xff\xbffJ9\xb1\xda\xcd\xbc?\xa1\xb4\xe9\x84\xf0\xc4\xe7\xbf\xf3\x9a4V;]\xd7?\x03\xa6\x99\x9e\xb9D\xce?X\x96\xb8dg\xf9\xd3\xbf\xe1K\x87\xed\xa0\x85\xe3?\x05\xa8\xd4\xb7@\xe0\xf9?0\t\xe1#\x83\x1d\xd5\xbf\xd26H+&L\xe7\xbf-?\x1b\xe8}U\xd7\xbf\xff\xd0+\xe5Q\x17\xf4\xbfg\x7f\xb55y\xa2\xe4\xbfZZ0\x07\xac\xab\xeb\xbf+\x99\xe7sp\xfc\xf3\xbf\xd2\xb4RI4\xbd\xea?n&0S\xdeg\xe8\xbf\xcf\xd3\x1f\xc3\xc5\x95\xe7\xbf\x1a\xf4h\xf5\xee\xf1\x93\xbf*\x1bZ\xdd\xaa\x0f\xe5?j\xb9I\xdf-N\xeb\xbfG\xbf\xd4\xec\xb9\x9b\xec\xbf1\xcc\xb9\x98\xc4f\xec?6\xdb\xf9\x0f\x84A\xff\xbf\xbes[\xbd1q\xea?\x07\xebP0\x8f\xc9\xe3\xbf\xcd\x115\x18\x0c\xd0\xec\xbf!\xe4<\xeb\xe6l\xcf\xbf?a<\xfa\x03\xbd\xd4?\x0f\\b8f[\xd3?\x17b\xd8IGw\xe1?\xa9|(\x0f\xee/\xf7\xbf\xe5\x10\xc4\x0eQD\xe9?\t\x93\x91\x0c\x1dK\xf5\xbf\x81\xea\x1c\x8d\x8c\xc7\xe7?\x08_\xc8\xc3\xcbC\xfa?0R%\x85\xf3E\xb4?}B\xaf\x1bl\x9f\xf9\xbf\xc2\xf8Q\xd8\xf5y\xf2\xbf\xb8`\xf7\xf82\xd0\xea\xbfs\xb3\xc2\xf4M\xe6\x8e\xbf\xe9i\x1e\xf9\xf8\x19\xf7\xbf\x14\xa5k,\x94=\xcf\xbfYfz_m\x8a\xe4\xbf\xd6\xcaSE|\x88\xe1?V\xb6\x13\xf6\xce\t\xf9?\xc4\x1ck\xafe\x90\xe0\xbf\xb4\xb1:*\xd5\xe7\xfd\xbf#J\xc5A\xea\x00\xd9\xbfHj\xfa\xbb\xd5l\xe9\xbf\xa5\xca@m\xd2L\xc3?\xfc\x912\xadx\xad\xf3\xbf\xafKu\xa7\xdd\xe3\xee\xbf\x91y\xafoW\x88\xd7\xbf\xa2\x9b\xa3nw\xb0\xd8\xbf\xe007#\xabv\xe0?\xe7\x19\xec\x84\x0e\x10\xef?\xf2n\x08\x8bV\xfc\xfa?\xd7{\x171\xd2u\xdb?\x01\x9f\xc7\x08\x9a\x13\xdd?\xf3{\xeai\xb4X\xe4\xbf\xe0F\x12\xc2\x12\xa0\xf5?v\x1e\xc1\t^\xfb\xf7\xbf\xc2\xc8V^\xcab\xe1?0;\xf2x;\x08\xf0?\x9f\x05<\xce9\xbe\xf3\xbfQ=\xe7\x90\xdb\xd5\xfd\xbf\xca\x01\xb0i\x06\xb1\xf6\xbfD\x1a\x11\x02\xe9%\xcd\xbf;\xa7$\x1e\xf7Y\xdd\xbf{I\xc4\xfck\xa3\xf5?q\xaci\xe0\x9dk\xe8\xbf`8\xf5\x13FG\xb9?\xfd\xb6\xef\xf2\xc1\xfd\xd2\xbf\xb0&\xb9\x0b\x13\xab\xdd?G?\'M{\x0f\xf9?Y\xd6Dm\x13\x0b\xf2?s\x1bt\xd2\\\xfd\xb1\xbf\xe5\r\xee\x11!\x8d\xfe\xbf\xde\x8a\xe3m\xcdf\xe2\xbf\x13\x9aW\xac\x8f\x14\xc2?\x90ANQ\x84]\xf4?\x9a$\xb6p2\xf4\xf7\xbf\xfd\xe5\x9f\xafKK\x04@\x91\xdb\xb2&\xcb\x95\xf6?\x06\x18\x99\x0b\xea2\xdc\xbf\xc1w\xe0\xf3\x8aG\xa4\xbfl@p5\x9e\x19\xf8?\x84\xac\x00\xb0Hm\xff?\xec\x9a\xc6\x80O6\xf2\xbf\x11$\x18r\x86Y\xea\xbf\x15\x8c\x06\xabL\x13\xd6?\x1f^QFi\xab\xf1\xbf\xd5\x04\x8a\xb9\x11\xc9\xfc\xbff\xa5\x98\x18\xeb=\xf6\xbf.\xc5\x01+\x16\xa5\xeb\xbf\xf8&\xaa\x8b\xb1\t\xe4\xbf\n\x06Jz\xab!\xf2\xbf\'\xa1ZF\xee\xe2\xc3\xbfm\x8c\x89c\xf8$\xce\xbf\xdc\xda\xe1D\x1b\x19\xbb\xbf\xc4\x92\x90,\xaf\xe9\xd9?\r\xf0J\xe4?\xda\xd5?\xa3\x00\x0f\xb4@\xc4\xe5\xbf\xfb.F\x929\xaf\xd2\xbf\xeb\xb9\xf0\x9a\x85\xf4\xd0\xbfm%\xe3\xc4\x87\xaf\xe6\xbf\x87\x17-{as\xf3\xbf^\xf8?"J\xe3\xa6\xbf\xb7\\Z\xf5\xce\xcc\xf0?\xa6\x0c\xc4:\x12\x8f\xd2?\x19F\xbdmb\x85\xd5\xbf6:-%\x7f\x87\xb8?\xe9\x0b\xb4dM&\xf5?\x85F\xdf\xa4%\x0c\xf6?ii\x98MnE\xf1?M\xf1\xc2Z\x84X\xe0?g\x19\x9b}\xc0\x87\xef?6\xecZ\xd2@\x8c\xe0?\xf5\xa7\x87ho\x07\xd4\xbf\x83\xacwl\x88G\xe6\xbf\xe6\xfd\x11\x0c\xe6\xc1\xc0?rn\x15\xa75\xf6\xed\xbfA\xc7\xc8ya\x1a\xe1\xbf\xb7\xf9-\x1b\xf6$\xe6?\xc6\xb7K\xb1\x8c\xe4\xe8?\xb5\xdf\xa8\xf3\x02\x97\xed?:\xb4=\x87\xdc:\xf5?v\x1e\x9a\x87\xab\x03\xff?\xb0$\x10\xa0\xdc\x91\xd2?{\x82=~\xf98\xdb?3\x86\x81\xdfW7\xed\xbf6\xe8kw\xec;\xfc\xbf\x9b\r\xf0\xc9$\x16\xb2\xbf1\xfe\xce\xbaz`\xc7?\x13sq\x80A\x13\xd2?6\x83q@{\x0c\xf1?\xa6\xd4\xa1\xff`\xe2\xf7?\xee5\xe9\xff\xab[\xbb?\xdb\xcfYP\xc0x\x00@\x16[TU\xb9\xd1\xe0\xbf\x8fT\x19Rw\n\xab\xbfh\x9d"\xc8\xfa\x0e\xb6\xbf\x9c\x9a\\\x16\xfb\xb1\xc9?\xc2\x9a\xa7-G\xcf\xec\xbf\x16/s]\x8e\xeb\xd6\xbf\xde\xe5\x0c\r_\xa9\xf3\xbf6\x94\xb8\x9d@a\xb0\xbf\x89\xe5\x18\xff\xc6/\xc7\xbf\x1d\x98u^\xb2\xf5\xe6?\xf7\xdb\x88\'\xcc\xe3\xc8?"\xd7z+J[\xe9\xbf\x9c\x95\\N\x11\'\xf4?d\xc1u\xa4;\xa0\xfb?\xcdzzl\xe0$\xc6\xbf]\xf2\x16\x86\x85\x9f\xf9?\xcf9\xb1\xe9\xd4\xef\xaa\xbf\x87\xfc,\x8d\xf8H\xe8\xbf.By 3\xfd\xed\xbf\x85\xfa\xf5r\x94X\xe9?\xb0\xd0\x86\xa0/,\xad\xbfik\xfb\x01\x97]\xc4?\xc0F\xda`\xd0\xaa\xd3\xbf\xb2\x80\xb7`\xfd\x86\xdf\xbf\x98"\x19\x1f\x0bt\xd3?LX7\x1f%\x10\xfe?;yI\xb9\xab\x91\xef\xbf\x15.\x85\x1d\xfb\xcf\xe3\xbf\xe8\xdb\xb2\x96F\xa0\xc7\xbf\xab\x1eb\xdf}D\xcd?6|\xea\xb8\x05k\xf5\xbf\x89\x82@\xd1\x93\x82\xb7?\x9e\xad,__\x15\xfd?\xd0\xea\x12_\x1d\xb7\xa2?Z\x99\xa2\x08\x8d2\xb7\xbfu:\xae4T\x9c\xd8?\xf3?\x8a\x9b\x1a\x9b\xe0?\xea\x8d\xcfl\x9b\x11\xda?\xe9\xe5L\xf0\xb0\x05\xc7?Ej\x81\xc9\xca\xdc\xe8?\x9e\xc5\x96\xb7\xca(\xd1?\xbd\x85\xa1l\xed\x12\xe9?y6#~=\t\xc3\xbff\x1c}d\xeed\xe1\xbf-\xcc\xca\x80W\x12\xc4?t$\xb66\xff-\xe5?\xa6\xb8\n \xbf\xb9\xe6?\xfdk}C\xeb\x1b\xb9?Y\xb9\n\x9a\x9d7\xd9?A<\xaaca!\xf3\xbf\xa0\xe6\x15\xc8/<\xf2\xbf\x86\x82\x94\xbf5X\xf2?\xd0\x15\xc1\x82%\x87\xd1?Q=\xdd\n\r\xf9\xc0\xbf$\x88\xb3x\xa4\xa6\xf1\xbf\xf8\xb6I\x9d\xa0\x85\xde\xbf\x1e%8O\xa9\x14\x91?\xed\xcb\xb7d\x94\x80\xda?\xec\x81\x04\x0e\x83F\xe0?s\xecc\x93Z\x9c\xd5?\xb0\xe1U\xd27\x9b\xe1?I\xfe\xcc\xca6J\xf1\xbf0)\x1a\x00r\xea\xe3?\xfe)M:\xfc\n\xf2?T\xac*\x944\x02\xd4\xbf\x9a\xb4qp6,\xac\xbf#\x7f\xf4\xa4\xa8\x7f\xf5?\x9d\x97\x93,\x93f\xdd\xbf{\t1\xc4F\xfd\xfe?he\xf2\xb4\x81@\xd3?\xdf2\x89z\xfb\x06\xf4?\x11:ln\xbee\xe4\xbf\x01g\xc9\x9c\x86\xae\xce\xbf\x1f~ui\x00\xfa\xe0?\xf0:\xe6\xb7\xc2[\xf9?\xb0\x108>\x85&\xb3?\xbb\x90\xbbN\x96\xce\xf7\xbfN\xc3\xd1\xcak>\xe4\xbf\xa9t\xe0l;7\xd6?wi\x1d\x80\x0b\xb6\xa2?\xaeh\x16A\xbbe\xe6?t\x1f\x98T[\xce\xfb?;R\x7f\xf9\xf5P\xcc\xbf\xcc\t\x8c\xc2\xb2\x02\xf0\xbf\x93\x8c\xe89\xa59\xe5?\xb0\x0e\xf1fe\x93\xc9?\x8b3V\xbf\xecu\xee?$\x87\xb2\xf2O\xc6\xf2\xbfW\xef-q\x18/\xee?5\xdb\n\x06\xa8\xb3\x00\xc0^\xae\x87\xf5&\xd3\xd9\xbf\x88\x9c\xddk\xd1\xa8\xe0\xbf\xfd\xa7\xf5a]\x04\xdb\xbf\xa4"\xbd\xa8\xb1\x8c\xf8?\xe8\xe1\xda\x96\xb77\xf2\xbf\xb8\x94\xe1\xcc\x1ac\xd5\xbf\xac\xfe\xe2\x16<\xcb\xe5?\xf1\xc0#\xe3\x809\xd2\xbf\xa6\x82\xebw\xab!m?\x9f\xa6\x0fn\x91\xa4\xed?\xfc\xee\x86\x1b\x12\x99\xd1?\x93_\x0eu\xcc\xfb\xba?\\\xa3\xa8+\x96e\xf1?SkZ\x1a\xfa\x02\xf9\xbf\xd4\x9c/t\xda\t\xcd?U\xd8\x0b\x83&\xb6\xf9?\xdfH\xc5\n\x8c&\xe2\xbf\xc9\xa5R,\x94o\xb8\xbf\xbd\xe3\xe7\\\x85j\xfe?r\xde~\xda\x8d\x92\xd3\xbf\\D\xee\r\xd7#\xe4?\xe7\x19]Bv\xbf\xea\xbf\r\xd0\xc7E\xc9@\xec?\xf3\xa8\x0e\xa1\x0b\xfe\xc3\xbfx\x8b\x02\x14v\xa7\xf1\xbf\xc2\x1e,<_\xc4\xe8?\x03\xc7\rx\x08\xc4\xc0\xbf\xff\x8br8\x9c\xf6\xe0\xbf\x926h\x8bN\x89\xb9?\x19E\xee\x17\xb9\x10\xe2?\xd3E\xc4\xee\x95<\xfb\xbf\xcb\xe2\xfe\xe4\x07\x8e\xf6\xbf\xcf\x94\xba\'uk\xeb\xbf\xe8\xbe\xcc\xd3D_\xe8\xbfv\x9d\xdf\xe5\x02\xed\xe1\xbfk\xdd\x85\xc7\x8b\xae\xcc\xbfA\x8a\xa1\r\xec\xa8\xc0?m\xf7)\xf0\x1b\xe4\xeb?[\xe5+2q\xcc\xdc\xbf\xa4\xad\x85\xd4\x1c\x03\xf7?2\x1d\x15\xf0\xc9\x0bs?qQ3)o{\xe0?\'-]\x07@\x91\xf3?\x83e\x13\x8ew\x1d\x87\xbf.\x0f\x0cn\xd1\xfb\xf5?\x93{\x1e\xb0\xd2\xec\xfd\xbfn\xed\xf4\xa1\xc9\xf6\xed?\x07\x96\xbf\x19\t9\xc8?2\xe0\xc5f{>\x06@\xbc\xc3D]\xbaG\xe4\xbf\x19;=\xfd\x1a\x04\xec?\xc3\xeem\x98\x8e_\xd0?\x0f\x0b\xda=\xab\xdd\xff?\x8b6R\xf3\x99\xcb\xd5?\xa9?@\xaf\x11\x17\xf1\xbf\xf1~\x86\xaa\xd5\x0e\xe4\xbf\xb9\xb6\xd5\xc5\xa2V\x91?\xe7\xa5\x04^\xc6\xe0\xea?\x9a\xe4\x1fX]\xff\xe4?\xc1\xd2\x8cy\xe78\xb8?\xc9z\x1d%\xa2\xd2\x00\xc0\xe8\xffoc}\xe7\xd0\xbf"\x8c\xe6\x95\xcd\xe4\xf4\xbf,\xb8\xd6\xa9\xef\xe3\xf1\xbfB\xf0\xceA\xc0\x11\xdf?\x15*\xff~D\xf3\xf1?\xb7\xd8\xc8\xcc\x8a\xe7\xe8?\xf2k^\xfa\xeeL\xe2?\xa1s\x9a\x01_\xde\xf4?f\x15\xc0\xd0y\xf1\xe1?2\x9e\x864\xc3\x1c\xe2?+[\xf3z\xe5d\xf1\xbf9\xf2{\xd2\xad\x1e\xe1\xbf\xd8s"??\xf6\xd4\xbf\x00\x8f~8\xce\xf2\xf9\xbf\xab\xab\x16X y\x90\xbf\xda\x1b\xe0\xf1\xb6\xe5\xed?\xa9\xcaK\xf9E\xcd\xea?\xeaK-\x15\x87\x17\xe7\xbf\xf3\x00j@\xa0!\xf8\xbf}\x03\xd26\n\x07\xf7\xbf\xdf\x98\xe9\xe9\xa8\xe9\xa0\xbf^>\xb95\x14\t\xf8?\xae\xa6\x8c\x0e\xebJ\xdc\xbf\xb1[2\xb5\x81\x01\xf1?\xc6\xd7\x01\x86\x02w\xf6\xbfC\xe2U\x86\x0e\x07\x03\xc0\xc9\x1cHb\x1f\x10\xe5\xbfM\xe7>\xa5\xd40\xc2\xbf[g\xd9\xeeu7\xf4\xbf\x95\xaa\xd3\xb8\xd0^\xf2\xbf\xa7H4\xb3g)\xdb?y\xad`\xc7\xb1\x10\xe3?\x12\xe9\x15\xa7\x837\xe9?\xba;U\xb6FS\xbc\xbf\xde\xfd\x1aFno\xe0?\x8da\xa4o\xe3\xf8\xfe?9\x9b\x8a<\xa1>\xea\xbf"\xc8\xbdaz\x0b\xee\xbf\x9b\xda\xa5w\xbb\xfe\x01@\xab\xc4?\x82\x19\xe6\xef?BL\xa80q?\xeb?X}E\xa1\xc9\r\xe8\xbf\x04+Xi\x1a\xb8\xd5?kG\x0f\x91\xc5\xc5\xe9?\xef\xb3\x99\x8e\x8f\xc5\xe7\xbf\xedq~s`N\xf0?\x93\x83p\xeb\x8d/\xed\xbf\n\x15\x14P\x89\x03\xec\xbfn\x9e\xee\x87\xc9\xb7\xc4\xbfI\xa1(\x05\x82j\xdf?\xf0%\xea\xac\xafv\xf0?\xa0p\x08\xd6\xe18\xd7\xbf\xc0WE8\x9d\xb1\x02\xc0T\xbd?z\x13\x95\x9f?\x0e%(\x00\xf9\xc1\x8d?)\x91"\xf1Z\xa1\xec?H\x13D\xcd3\x1d\xf1?T\xaf\xe9\x9a\nx\xf9?w\x17,I1\xcd\xf4\xbf\xd4\xda\x89\x1c\xd5:\xd3\xbf\xcfi\xa5>\xf9\x84\xe1?i\xa5cKR\xbd\xf1?\xc0q%\xabx\x9a\xe0?\xca\xd2\x97\xd9\r\x10\x00\xc0\nb\x86\x9e#\xb6\xd1\xbf\xab\xb6(_Q\x9d\xf0\xbfVt\xa0\x9b\xc9\x90\xeb\xbf\xb9\xe3<\xfbv\xc6\xf1\xbf/\xed\xc3\xe6,\x8b\x03\xc0\x9e\x81Q\n\x82\n\xda?\xa6_\xeaS\xc4H\xc3?\xf6\xfa\x05,"\x91\xf0\xbf,\xfb\x91\x8e\xece\xf4?\xc3\x93\xfdK\xfd\x8f\xcb\xbf \x8fh\xb0\xa2y\xe4?\x03\x1b\xa6sL\x8e\x03\xc0_\xac1tsQ\xf1\xbf\x0b(\x16`\x90\xe6\xf8\xbf\x84T[)\x8a\x8e\xfe\xbf\xe6q\xa2,\xe5e\xf3?)\xec7\x9bA\xe8\xd9?f\x91\xef\xe4\x155\xe7?\xa3\x8b\xf2\xad\x8d\xb3\xe1?\xad\xa6\xd9\xde\xed\x8a\xb3?\x17\xf8\\s9i\xe1\xbf\x13\xbe]\x04\xa9\xb2\xe3?<\xed\xd1\x85\x89(\x02@G\xea\xa5K\x1f\xd0\xeb?\xadrM\xdfX`\xd8?\xa6\x8cc\xacm!\x9e?!\x1eR\x9e`p\xf7?[\xcd\x96\x00\xe3\x83\xfc?\x96\xde.%>\x8c\xe5\xbf)\xa0R\xc6\xef\xaf\xde\xbf\x94;\xefB\xee\xf6\xd7\xbf+?K\nB\x16\xf9\xbf\xa6w\xef\x8a\x933\xe4\xbfy\x19\xc2e\xac\xf2\x9c?;-D\xee\xf2\xa8\xea?\xb7?G#k\xea\xfb\xbfg\xaa\x87\x13\xd7\xa9\xdd\xbfH]2\xa2\xe7\xfe\xf2?\x92~5\xf9C\xa7\xea\xbf\xadc\xba\xef\xfb\xda\xdb?\xb4i\t\xef\xd1x\x9f?\x1c\x7f\xf1\xd4v\x83\xea?`w\xbc\xf0\xceT\xf4?\x0b\x9ey\x1dSN\xf2?\'\x9c\xe8\xa3\r\xc0\xfe?\xe2b\xfb\x10\x03\x19\xe1\xbfg\xef2\x8d\xb0\xc8\xe1?\x98\xea\x02\xa1M\xbd\xdf? \xd7J"{\x19\xee?\x15\xe4\x9a\x14}\xee\xec?I\x00\xf5\xca:8\xf0\xbfM\x07\xc3G\xa5\x85\xef\xbf\xfc\x9f!k\xb1`\xc4\xbf\n\xa6\xe2\xfe\xb9\xd8\xe4\xbf0\x99\xb3\xb2|\xe8\xe3\xbf\xfb\xb3E\x9cL9\xfc?y\xe6\xe3\xb5#\x0f\x08\xc0\x01\x84VA\x86\x81\xe5\xbf\xd4\xd0:\x1c\x1e\x04\xbe\xbfl\xc5\xe1b\xe3\x02\xe7\xbf\xd3\xbek{\x88G\xf8?\xf6\xf0\xb5\xea]$\xc7?\x18\x07\n\xa8n\x8a\xcd?\x9e\x16it\xd4D\xd3\xbf\xe3d\x0f\x9b\xe3!\xec?\xb9\x0e\x94\x93\x0e\xc7\xc5\xbftO=[q\x08\xe5?\x1f\xc1\xe8\xcb\xa7X\xd9\xbf\x97+t\x04\xac\x80\xf9?y!\x92\xbb\xd4S\xdd?\xeb\xc1\xcb\xff\xc8\x99\xf4?\xf6\xfa\xebb\x827\xae\xbf\xcc\xe7B\x8c\x85]\x03@"\xe5m&\xdcI\x00@`\xd3\xe1\x14\xf6:\xfd\xbf_\xd8\xb5\x17^\x84\xe4\xbfl\xa7\xabuT\xb9\x01\xc0eA\x07\xc8\xf0\x7f\xfc\xbf:\xf9\xca\xbb\xc7\xae\xd1?\xb3%\x83QT{\xe0\xbf\x1dc\xdf\xbe\xdc\x1d\xf0?\x9djo\xc6\xf4]\xf6?l\xe0\x98i\xe7\xc4\xe9\xbf\x0b&\xd6Y9\x8a\xd8\xbf\x05Bw\xf1N\xef\xd1?2\xbak\x9e\xdcG\xc4?\xe4[3\xbc0\xfc\xc2\xbfP\x9dzC\xfc\'\xe0\xbfI\x16;\xdaJ\xc9\xb0\xbfc\x13f\xa2ei\xd3\xbf\xf7\x9ev\xeb\xd7\xec\xfb?\xf9\x97u\xe7mA\xf9\xbf\xe3\x1aF?#\x1b\xf5\xbfn\xcb ,\xa6\x85\xf1?\xbd\xfcal\x0c\x06\xff?E\xe9\xb1\x19!S\xe7?m\xb5Q\x95\xd8\xbe\xe3\xbf\xe3\xc9\x97\xd3\xab\xec\xe5?\x04K%\xaa*\xf5\xfa\xbf\x1a\xfb\x83d\x1bc\x95\xbf\xd6/\xd2\xdc5)\xd5\xbfW\xb5\xff\x13\x88\x81\xde?K\xe9\xe4\x00,Q\x00\xc0[\xf5\xbc=F\xcd\x00\xc0\xdd/"Ed\xba\xeb\xbf\x8a\x8bqnD\xe2\xe7\xbf \xf70r\x97\xa4\xd3?~\x8b\xfa\xbc\xe1\xc4\xf6\xbf\xc6\x8cN\xb8\x06R\xfb\xbf\xaa"\xb5\x9b\x0f/\xb0\xbf)\xed\x01>\x16\x1f\xef\xbf\x90\xbe\xdb\t\x02\xae\xd9?\x08Y\xf1O!U\xcd?\xefExJ\xd4\xa9\xa5\xbf(B\xe6\xe79\x1e\xb6?0zx\xe1#\x15\xdd?\x873\xf7\x97\xdf#\xf3?\x94\xf3\xc5\x1f\xf9\xdc\xfa?<\xd03\xfa\xack\xf8\xbf[\xfe\xf9\xfc\xc2\xbf\xd6?S\x1cL\x9br#\xe7?\x12\xc5r\tS\xdc\xe8\xbfd\x08\xcah\xd1t\xf0\xbfO\xf2E2s7\xce?w\x05\xebT\xf4w\xc0\xbf\xe2V&\xc2X\xb9\xf6\xbf\xdd|\xa0\xf7*\xf0\xf9\xbf>\xca\x9f|\xd7>\t\xc0Tb\xf2N\x88^\x04\xc0\'\x84\xb4\xa47\xf4\x01\xc0\x14l\xc3\x02\xfak\xf5\xbf\x92\xff\xde\x1c\xedP\xf1\xbfCi\x06\xc8\xe9A\xc1?\xd2\r\xd8\x9dP\xcd\xf0?\x9c;][\xc8\xed\xe6\xbf1\xf9\xd6;~P\xda?m\x89\xe3\x9e\xbbA\xed\xbf\xd46\xdcc\xb0\xf6\xe6\xbf\x94\xa6f\x83c\xb7\xee\xbf9\x8b\x15<\xd6\xf0\xf2\xbfl\x0f\xb0\x99\xef\xed\xd4?g\x1d\x10\x99`H\xd8?\x8c\xc5d\xd5\x11\x1b\xf8?\x0c\\\xd5]\xb93\x00@\xbej\xee\xc5\xfd\xab\xf0?\xa3\xd1\xb8\xaf\x8c\x93\x8d\xbfL\x8ba\xda\x01c\x02\xc0HK\x88*M\xac\xf1\xbfg\x9a\x91\x1a\xda\x9c\xe5?\x02\x9d\xf5\xf7\xd3\xdb\xd0?\x99~+\x17%\x8e\xf2\xbf\xa5-\x1f/\xbc"\xe3\xbf\xff\x8b\xca\x92\xae\x1b\xf0?\xe4\xf5IgB\xd4\xf0\xbf\xa98\xdd9\xa0;\xf5\xbf\x8f!\x9a\xe6-l\t\xc0\x87-\xd1o\xaa\xe2\x0c\xc0\xef\x8f\xd4H#\xd5\x00\xc0)\x18\xfd\xa7\x10,\xdb\xbfiA)Dkh\xea?\x8d\x7fXS\xce\xa7\xee?\x10M\x8b\x0f\xa7\xae\xe7\xbf\xd5\x86\xa5\xfd\xd7\x80\xd4\xbf\x1d\x93\xeaaS\xb0\xd0?fo\xb8\\;\xf6\xd5\xbf\xfd3\x11\xc4\x05|\xe9\xbf?Ati\xa1\xfe\xe9\xbfi(\xb5{9\x1b\xf5\xbf\x8b\xfb\xa5\x8b+T\xd9\xbf\x0f\xab\xbd\xf8.\xb6\xa4?\x88|,\x82GL\xfe\xbf1\xb3\x90\xe4!:\xf2\xbf\xd7%\x80\xfeP\xc1\xe6?\xb0_>\xfb\x9e\x08\xcb\xbfbC\x9b\x0c\xb7\xc5\xc1?\xc9\x02\x9dJxd\xce?\xc1\r\xa4\t\xd2i\xe7\xbf]\xe2\x0e\xaalr\xf0\xbf\x8a\x96\x83\xc39\x05\xed\xbf\x86\x8b\x92(\xb9\xd5x\xbf\'\x93L)~\x18\xcb\xbf\xe8M\xf0\x81hz\x00@h\x7f\x91\x08%\xe5\xac?2\xa0x\xa0\x86z\xeb\xbf\\\xd4\t1\x99\x87\xfb\xbf\x8bu\xba\x80X:\xe5\xbf\x9a\x8b3ce2\xa9\xbf#\xa5\xf5\xa3\x12\x10\xd2\xbf\xdc?\xc9\x13\x05\x0b\xd3?\xf7\xea\xb8\x85\xc9\x1c\xd6?\xa5\xe82\xf7v\xb4\xf1?\xf4\xd3\x11\xaf\x9b\x03\xe4?\x06V\xcb\xd2\xf0\x03\xf4\xbff+C\x00\xf6\xe9\x01\xc0A\xe3\xd3\xa6\xba\x12\xf7\xbf\x966\xd3\x10\x87\xa6\xc3\xbf\xef\xab\xee{;%\x9e\xbfV\xc9\xdd\x12\x86\x1d\xc9?\x99\x05A-\x0c\xf2\xc2\xbfG\x06\x10n\x93\xcf\xd2?e\xb3\xaet\xd6;\xe0?=\xa4a\xa6\xe4\xfa\xe9?\x0c3\xaaW\x91K\xb6\xbf\x89\x85\xdd\x05$\xbb\xf5?\xd2\xack\xde\x7fK\xf3\xbfg\x13\xe7\xc7\xbe\x00\xf8\xbf\x8c\x98\x9cr\xc2\xb3\xe5?y\x1d\x86U\x8f+\xf3\xbf\x91_\x99 9%\xf1?3\xd7\xe2\xe8\xb8x\xde\xbf\xb0\xa2\x0e\xbc\x06\x10\xfc?\xb9^@\x0f\xd5K\xe9?\xc7FV\x18y\xad\xc3\xbf\xae\xf4L~TJ\xe5?c+#\xcaU\x12\xcc?vU\n\xc8<\x1b\xbd?\x97gw\xfc;Y\xea?\xc7\xfdf\x18\x14\xe0\xd1\xbf\x8f\xdec\xb0\xd8\xe1\xe9?\xe8\x8b\xcf\x10`\xa1\x8c?\xb4\xa2\xf2\xde\x98\xb9\xed?\x97\xd6F\xda\xac\xa2\xdd\xbf\xba\xb4\xb8m\x97\x05\xfd\xbf:\x8f\x88\xae\x84\x83\xed\xbf$\x87I\x990\xe2\xe1\xbf\x90\x9b1\x9e\xf4\xef\xaa?K\xac\xf3\x1f\xf3\xb6\xef\xbf\xe3\x14TP\x9c\'\xd8\xbfv\xbbt\x1f\x0c}\xe7?\xb2&\xa1\xb5\xce\xad\xe0?\x8b\xfb\x9fV\x87*\xef?\xdbP\x0c,5D\xf0?\xbb5c\x8e\xf1\x08\xf6\xbf}\x7f\xd9A\xe4r\x85\xbf\x90\x124\'\xdc\xd5\xdb?\xa5U\xfb\n<\xd8\xd9?\x1fC\x82\xfb\x1c\x02\xd4\xbfU=\xee\xfa\x91\x17\xeb\xbf}\xfa\xc2\xe09\xa5\xfd?t\xb9[W\xfa\xd1\xd5\xbf\xf7\x01\x89YH\x9e\xe4?\x14\x12}\xb78\xc4\xe4?\xa5\x9fP\xa0\x05t\xfb?\xa6=\x98\xba\xbd\xd6\xcb\xbf_\xbc\xd7\xb7\x15\xed\xfc?\x8f%x]\x18\xdc\xec?b\xe4\xa6\xcda\\\xea?\x85\x03mU \x93\xce?r\x1b\x0b\xda\x00\x9c\xf2\xbf\xeb\x84f\x11\x95\x11\xe0\xbf\x05eu\xf9\xe5\xff\xe4\xbf\x06\r\x90\xfcO\xcb\xd6\xbf!!{\xa0\xae\xcd\xbe\xbf\xc9\x94r/\x1a\t\xfe\xbf\xf0o\xc9\xe2m\xd4\xe1?\xa1\x8f0\xea\n\xdb\xde\xbf\xb1\x7f\xa8\xe9\x9f\xcd\xfc\xbf\xbd\x02\x89\xc6\x18\x12\xc3?K+Q\xde2Q\xfa?V\xac\xda\xdd\x05\xaa\xd3\xbf\x8f\xceM\x95F\x19\xe0?\x01\xd5(\xec\xcaL\xea?\x95d\x97\xe6\xde\xf1\xd2?\x89*R\xa8\xdc.\xea?\xb7\xcfO\x86\x9b\x17\xbb\xbf\xfc\xeaHMy\xe2\xba\xbf\x8fuJ\xbf\xb6\xa6\xe6?\x83@\xb6\xf3>\xd7\xb3?#~\xcc\xd2\xf1\xdc\xe7\xbf`\x9dX\xb7\xc0\xe2\xf8?\x0c+\x89\xbe\xc8\x95\xf6?\xc9\x1c\x0c\xb7\x85\xb8\xfd?xb\x17p8\x02\x00@\x15\x15\xb1#\xc2\x1f\xd3?j\xf6\xeaJ\x14\xa7\xf9?\x08\x17\xed\xd0\xef\xfa\xaa\xbf7\xf1\x80\x8d\xd0\xfe\xfc\xbfB\xe0).\xb5\x91{\xbf\xe1H\x05x\x98w\xaa?\xd9N\xcb~\xb1\'\xf7?\xd8@\x9fz\xda\x8c\xe5?\x1c\xc3\x1a4\xf6\x98\xd0\xbf\xf0\xcd\xab\xd1+W\xed?)\x1d"\xc3\xd1\x95\x81?\x95\x07]\xeasN\xbc\xbfZ\xe8\xbb\xc4\x96\xd8\x99\xbf\xc0\x99\x8fw\xcb\x14\xc3\xbf\xaa\xcd\x9a\x9d\x15^\xef?\xc1\xc7\x18\xd6L\xcd\xe2\xbfm\x99u\x80;a\xe4?\xda\x19V\x1b\xc5\xc9\xe3?d0\xca\x1bm\xb2\xd2\xbf?\xab\xcc\xc5\xab\xff\xc3?4\xa4j\xb3\x91\x9d\xe7?&\x8d\x14\x7f\xbe\xb5\xd7\xbf7[\xf3oP\xe4\x01@\xe96`G8u\xea?\'\xde$\xd0\x07Y\xdc?\x9b\xb5\xb8S]\xd3\x00@\xad\x8b\x1a\x8f\x92_\xfb?\x111\xd7\xc2g\xcd\xf7?KF\xc6\x01\x06\x86\xdf\xbflIE\xba\xe5\xd3\xf3?=\x96:\xc64\xe1\xf1\xbfnPGA~;\xfa\xbfG\xdd\xfcE\xd3\x87\xcd\xbf0\x91\x95E\xe8\x02\xf6\xbfj>"\x14\xf1\xee\x95\xbf\xfbB}\x1d9]\xd5\xbf\xa5\xd8w\xd6\xc2W\xac?]\xc0.q\xbd\x95\xc7\xbf\xd7\xde\x98\x92\xab\x8f\xf4\xbft\xae\x02\x1fk9\xe1?\xa64\xce.\x92m\xfa?\x1e\xa8\xedTT\x1e\xde\xbf\xa9\x7fa\x8c-A\xd5?<>\xd3\n\xf8\x0c\xdf\xbf\x08\xfbq\xe4\xe7\x96\xfc?\xc4\xc4\xa1Bo\x8f\xe6\xbf\xd6\xe2\xef\x844I\xd3?.^d\x8e\x89\xbd\xc5\xbf\x13+\x83\xbf\xde\xba\xfb\xbfv\xc1\xdfw~5\xfe?\t!\xf52\xb5\xf2\xfe?uw9#=|\xe2?\xfd\xaa\x963\x0cj\xff?\xcd+\x8c\xb2)\xce\xd8?\xfc\xa4\xb6K\x18\xcc\xf2?\x19\x87\xff"\x8f\xd1\xd6?\xb9,GCi\xca\xe9?l\x1c\xd2\xe6\xbc8\xfe\xbf\x19\xa8o\x88)\x0e\x00@\x04!\xee\xd5\xa9>\xed?\x0e\x8f\xab#\x85k\xea\xbfyP\xff\xc2\xc6\x98\xee\xbfQy\xee\xda\xe6,\xfe\xbf}D_fJ\x0f\xf3\xbf\xb7e\x1co\xf8~\xe2\xbf:\x94\xf5#\xa1r\xdb\xbf\x18\x99\x1cx\xa6\x8a\xed?G\r7\xe4Z\x11\xd0\xbf\xc1&b\xf2\x0e\xd2\xd7\xbfEa\xd3.\x1d[\xb9?\xb0D\x90\xcc\x86~\xf4?\x14s\xac\x13\x07\xcb\xdd\xbf\xa2Wv/\xb49\xd3?\xf2HT\xe4v\x14\xf5\xbf\xab\x0f\xfd\xcf\xec\xdd\xb4?0\x02\x95!`O\xb3\xbf\xd3:j\xff\xd9\xe3\xf9?\x8c\x95\xee\xa7\xe4&\xde?#\xd2%\x9fB*\xec?\xc4\x14[\xf1\x1c\xad\xba?\x9e\xa0\x86\xe7\xc9\xb4\xe6?\xa6\xed}\x1av\xb3\xe2?\xc5\xfa>\xc3u\xb5\xd0\xbf\x06\xd4\xc0W\xb9\xbf\xc4\xbf\xdc\xa1\x0c\x8d\x1bq\xe9\xbfT\x85H\xdc\xbbR\xf3?\xce\xd9QRQ\xc1\xfe?t\xc3su\xe1#\xec?\xca\x1f\xff\xb5\x0ez\xab?\x86\t{\x1e\xba\r\x03\xc0y\xd0\xb1\xc0\xb2]\xca?~\xaf\x12@P\xed\xe6\xbf|Y\xf6\xfcR\xfa\xce?\x8a\xee\xde\x8e\x9fu\xf2?H\xf8\x8a\xc6\xd5?\xf3?\x121\xc6\xa6\x8db\xc0?\xfbVi\x96\x18\xd0\xfb\xbf\xd5Pv_\xdd/\xf3?\xb7\xb2S\xd3\x0c\x8b\xe5?\x9c]\x08B\x1eC\xf8?[\xfct*\xb2\n\xef?\xfe.\xf2\xc2\x18\x1c\xe0?FQ\x19\xf8\xfc\x8c\xe2?\xb7\n\xf2\xd2\x12\x81\x00\xc0\x9c\xd6J#\x93H\xe4\xbf\xebgo\x99+\x83\xd2\xbf0`\xac\xff\xdc\x08\xd6?\xf6\xe2\xf8\xf7\x97\xdf\xd3?\x03<\x18\xfa7K\xcf?\xd1\xa1\xb5\xb2M\xec\xe5?\x11\x9a\xc1g\x0f\xa8\xc5\xbf\xc6\\&\xd2Q_\xa3?Hz\x9b\x9e\xdf{\xc3\xbf\xc8\xa0\x82n\xfa"\xda\xbf\x00\xc3y\x9d\x97 \xf1?<,\xa4\xb2j\xcb\xe4\xbf\x1c\x85\xcb\xfe\xd0\xf2\xf6?\x92M\xbb\xbf\x89\x0b\xf4?xDO4:\x1a\xd9?\x7f\xff\xc5UaK\xfa?e\xa8\xe7\xbb\xe8\xe4\xbb?\xb2+DP\xabl\xcc\xbf\x9b\x0e\xa5\x12\x88\x97\xe3\xbf\x10\x15{hW\xeb\xea?\xa8\'V\xdf\xbc\xe6\xdc?6h\xce\x16\xa5|\xe0?\xbe9\xb8\x13\xbe\x83\xda?\xe3r\x92E\xc8\xe2\xe2?FW\xf4h\xda`\xef?\xf1!\xf4\x92\xf7\x18\xd2\xbf\xe0\x1b\xb1\x9a\x03\xd7\xf0\xbflzh`\xcd\x83\xf8\xbf\xfcE\xd8\x87W\xd8\x03\xc0\xf2\'\x05\xcf\x99(\xe2\xbf\xe9\xa0\xbf\x9e\xcd\x0c\xd4\xbf\x05r\x85xAk\xe0?\xaf\x1d\xab\x05\xcc]\xc1?5wC\xb7\x91h\xdb\xbf\x00\x89\xe8j$\x91\xdf\xbf\xf8cA8\x9d\xc0\xf6\xbf*\xd3\xc4\xaa\xe7\x95\xe7?\x01"V\x0f\x8eM\xed\xbfR\x12\xfe\xd2}\x1e\xe0?\xd9D6W$\xf3\xe6?a\xde2\xe4%H\x03@\x04\xc41W`\xa9\xdd\xbf\xdb\x99\xdf0c\xc0\xbe\xbf\xd5\x1e\xc15\xfa\xc6\xcb\xbfBjC\xe3\xd0\xe4\xf3\xbf\x16\x15p\x02S\x1e\xe5?\xa4\x0b\x84\x84xu\x89\xbfXD\xe9\xc3\x0b]\xc0?\x05\r\xe6Mby\xbc?o\x06\xa8\xc9\xd4\xa2\xef?j_\xc5\x9d\xe4\x85\xa7\xbf\xa2\xf9>gQ\xee\xc8\xbf\xacK@X\xfc>\xe3?\xfd{\x0cm\xbd\xfe\xa8\xbf\x8d\x06\x99\xf7\x84M\xd2?\'\xce\x04\xfb\x14\xd1\xc1?\xa5\x8d\x97\'\xc1g\xe9\xbfz\x14\xd5!\x9cU\xf6\xbfB@1!b\x7f\xf4\xbf\x15@\t\x11|\xf4\xfa\xbfP\xa9\xe1\xdb\xfc\xc7V\xeb?~\xdb\xea\x0bd\\\xd6\xbf\x8b\xc0\xd2\x0b|\x95\xf5\xbf\xa5\x88\xfa\xd6\x84Q\xd7?\xf1g(\xda5G\xd5?\x10\xea\xf9\xa8\xe0\xee\xc1\xbf\xd9f+\xfac\x19\x7f?\xe9\xdb\xdb\xf6\xdb\xbd\xe5\xbf\xdc\x9e\x99;k\x19\xe3?<\x98\xdb6&\x08\xda\xbf\x82\x85J\x17\x00\x00\xa7?\xc1\\\xfb-\xbb\x91\xf0?\xf0\xda\x87\t\xe6l\x01@2^\xfc4j\x85\xf3?\x0f\x95\xdb\x1c\x9b\xf5\xe2?K+C\xaf\xeb,\xd2\xbf\xc9\xd4\xbf\x82\x85\x11\xf6?\x86\xc6.r=\xa0\x00\xc0\n,\xd8\xf1A3\xee?|\x009t\x9cy\xe4?\xf4\x95\xf5\xb8)K\xf5?1\xdbw\xf7\xe3\xfc\x00\xc0\x96\xd2\x82\xcd5\xcc\xee\xbfN\tx\xd3]o\xe7\xbf\x98(\xe5o\xb4\xc2\xde\xbf\xcbL\xfb\x94\x14\x7f\xe6\xbf\xaf\xc2\xd5c\x7f\xf9\xd7\xbf\x03c\xd5\x9d-\x1e\xe7\xbf\x8c\x06\x14\x8d\xaa\x08\xc6?\x13\x81\x1fX\xfe\x0e\xe3\xbf-B\xc5m\xef\x82\xb8\xbf\x91\xa9\xf0F\xe6\xd8\xd4\xbfH\xa8\xf2`\xe1\xbd\xff?\xaa%m\x1c\xd7\xd1\xe4\xbfP\xf7\xe1\xc2xS\xf8\xbf2\xd1\xf2O|\xf3\xd4\xbf\xdcI=\xa0qi\xe5\xbf\x9e\xee\xc5\xad\xa9(\xe1?p\xc0\xf7\xf4L`\xf1?\x8d\x8f\xbdQ\xaf\x05\xf4\xbf\x7fnT\x90n:\xf5\xbft3\x11ry\x8b\xda\xbf\xe5oqb\x98\x99\xf6?\x93W\xec\xc9\xf5\xa0\xf9?\xafq\xf6\xc8$\xc1\xf0?\xd7\x01)sC\r\xff\xbfH\xa3\xcf\x91\xea\xa6\xe5?\x81J\x8a\x1b-u\xde\xbf5\x83\xbb3\x06i\xf4\xbf\xac\xe1a\x13_,\xfe?\x82@\x93\xeftu\xf1?\x16\xd7\x17Z\xe0l\xc3?\x0fd;\xa3\x0e.\xe8?B\x83\xb2DN\x1e\xf7?\xbeK\x12Tn\x98\xcf\xbf\xfe\xf3\x9aTz\xd2\xc4?k\xa8\xab\xb1og\xf9\xbf\x8aq\xfc\x17\x96b\xf9\xbf\x1d\xce\x15=,\t\xc6\xbfp[\xc3\'\x9c\'\xf0?E+\xeb\xd3 \x19\xc3?\xb9\xddB\xd8vT\xfe?*\x8f\x7fd \xa0\xfc\xbf\xf4N[\x8b\xd3l\xd3?\xbe\xd7\x86E\x9f\n\xf6?O6q\xc4\x1f\xd7\xe9\xbf\x8e9\x11`\x90\x13\xde\xbf\xdd?\xcb\xae\xfa\xc6\xa7\xbf\xe6\xc4j\x91Ko\xeb?\x8c\\2\xca\xddY\xb1?>\xc2s\x92\x14?\xe1\xbfZ\xdb\x8f\x95\xaa4\xe8\xbfQ\xa1J\xc3\xa4\xfd\xf2?\xe4\r\t\x93\xa5\xab\xd2?ZV/\x18N\x07\xc8\xbf\xa8`\xd8\xec\x16\xc0\xf6?\x86\xdd[R\xf6\x85\x81\xbf#\xa1\xd5,j\xf6\x01@\xa7\x98\x01\xbe[\xb6\xf1?\x15\x15\xb2\xf0\xbb\xaa\xfe\xbfP\x8c\xa7n_\x9c\xb5?L\xf16\xada@\xe7\xbf\xd5\xf3jd\x02v\xe0?X\xcb0{\x04.\xf3?)\x8f\x87\xf2\x1et\xc9\xbf\tw\'\x08}\xdf\xec\xbff\xa9\xa8\x107\x95\xd5\xbf8\xec^\x19)\xb5\xd1\xbf\xa1\x00HH\xac\xfd\xe3\xbf\xb5\x07}\x1a\x8c\xb0\xec\xbf&\x1e\xfc/\xd5\x16\xda\xbf|H~\x9a8\x9c\xfb\xbf\x18dn\x9fM\xe5\xf3?\xd5vj\xe6\x0b?\xf6\xbf\xcd\xc6/\xc7f^\xfd?\xff\x91g\xc1\x8f\xcc\xd1?\xf8^\xf0\xc0\xb1`\xda?\xfc\xd2Y\xcarU\xcb\xbfM\xa1\xf5L\xc9\xe9\xef\xbf\x8d8\x99\xc1\xeau\xfd?\x05lc\xbd\x05\xf6\x99?E\xfc\x80Ze#\xef?%\xcd\x12\xc3+\xe1\xe9?+xo_(\r\xf7\xbfE\xbaR\x19\xf3\xae\xcb\xbf\xeaD\xef!n\xb8\xe6?\x85v\x02j}}\xed\xbf:\xc2:\xa5`\x10\xf1?3Y\\A\x84o\xf7?iH\xfa4\x0b}\x00\xc0^\xee\xd7;[\r\xe6?\xb4\x8eU\x17\x80/\xd4?4\xb4\xb4\x97\x0c\xd3\xcc?@\xf8S9\x1b\x1f\xc8?In-\x86\x88\xcf\xd6\xbf\xf0\x7f7\x101\xcd\xf0\xbf\x9d\xbd\xde\xe9\xfb\xd8\xf7?t\x03\xe7\x93?0\xf6\xbf\xe1\x92cV\xfc\xe0\xe0\xbf\xa2\xb9D\x10t,\xe9\xbfq\xbc\x83x/]\xe0\xbf\xedl\xb0\xfc\xacS\xe6?xB\x9d\xc1\x1c[\xd3\xbf"\xb3\xcf\xd1\xe6\x1d\xf0\xbf\xa0?d9V\x94\xf0?\xc2\xe0\xd5?\x86|\xca?cx\x8c?D7\xee\xbfT\x17\xa0\x98\xea#\xf0?J\x96b\xc6XB\xfd\xbf\r\x0c~\xac\xa4\x97\xeb?\xec\xe6\xbeh\xb0\x96\xf4?\x96^C-n\xeb\xf1?!~v\x00\xc7\x1e\xf0\xbf\xd9\xe9\xdb\x0f\xdcP\xce\xbf\xc6\x97\x1f\x8ecm\xee?\xd9\xf0&\x99\'\x94\x03@\x100k\xcd\x93\x87\xec\xbf\r\xe7\xd5\xc5\xf7\xa1\xe9?5\x12p\x8b@\n\xf2\xbf\xb6\xe2\x80\xa2r\x88\xd8?~\x8cw\x8a\x13\x8a\xfa?\x8d\xfd\t\x84;\xed\xe3\xbf\x94\x05\x10\r\x9a\xe8\xf3\xbf\x18\xdf\x98$\xf6{\xd6?\xdf<\x94\xbe\x15[\xf5?f\x93\x1aT\'~\xf6?J\xb7\xd8\xca\xd3h\xf1\xbf\x8e\xd8zP\xf9\xe1\xf1?\xc2\x846\xf3{\x8d\xcf\xbfn\x91:r8\x9a\xd3?\x07\xe2\xe9kJ^\xfc?\xea3dO&|\xce?r\x9a\x15\xa1\x9d\xc7\xbd?F\xa5\x84Pz\xa3\xe7\xbf\x80\xd9\x9c\x80_\x18\xc2?8\x08\xb9\xebq@\xe4?F\r3\xd5\x0e\xf6\xf0?M8\xd7[\xc5\xa9\xd5\xbf\x91E\x9e>\x90\xc9\xe4\xbf\xff\xc7\xe8q\xceC\xc6\xbf\xc40\x17e\xf0C\xe8\xbf:\xa7/f/\xbc\xa4\xbf\xd3YC\x8d\xea\xa7\xe1\xbf\xfa\x8eA\x8dv\t\xf3\xbf\xf0\x0bj,\xd5j\xa0?a}{\xc2\x81\x8c\xf4?\x8a\xbf\xd7\x8c\xa6\'\xd2\xbf\x83>\x05\xe0\x08Q\xe3\xbf\x9c\xe8\xbe\x03\xf3X\xee\xbf\x1aUo\t&\xcd\xe6\xbfw[tP4\x8d\x8e?\x8d\x1cyD\x0e\x02\xe7\xbf\x93\xf6\x8b\xe0#B\xda\xbf\xba\xa9{\xdd/\xdd\xe1\xbf\x08\xa5\r\x93\xc80\xb8\xbf\xb1\x7f+zG\xf2\xf6\xbf\xff\x0f1\x8b\xe5Q\x05\xc0\xdbCRe#\x89\xfb?\xf5\xe0\xe9\x1eD\x8e\xf1\xbf\xf1|\x81>\xfb\xf7\xd1\xbf\x01\x9f,\xc4\xe6n\xe8?j\xc3\x1f\xbdP\x0e\xe5?K1\xc0\xfd\rL\xb0\xbf\x95\xf4\x80[]\x8c\xe6?})\xe4\xbcZ\xe5\xf9?\xdbQ\x95\x9fs\xd2\xe9?5\\\xc1\x1c\xacb\xe4\xbfN6\xe5\xbd\xe7X\xe6?t\x0e\x1a\x11\xebl\xd0?\xdc(J\x97\xa4\xa1\xfc\xbf\x7f\x05\xe6\xa2\xb2\xfa\xd6?\x81\xaa@\x92\x8cO\xa4\xbf\xab\x1f\x86\xeaXl\xda\xbf|\x00`\x99\x98\xdb\xd6?\xa5ad<\xa9R\xb5\xbf\x0b\x08\xe9~\xd9\xe3\xe7?\x17].\xe1\xab\x11\xd7?cP0\x03\x07\xe9\xf3\xbf\x0f}\xfb\xb4\x91\xf0\xff?\x03:V\xe4L2\xef?)\n(P>U\xf2?xt\x83\x8c\x08\xe3\xf2?\x0fr\xefP\xe7\x15\xf1?\xb1\x0f\xa8\xa7\xc2\r\xd4\xbf\x86\xe5\xc5\xf5\xf9V\xcc?\x89uXF\x1cR\xe4\xbf\xecsq\xc0\xf0\xf6\xf1?\xf5(\x890\xff\xe0\xec\xbfV\xd8\xb7_r\x14\xd3?\xf7Bt?w\xae\x04\xc0\xe9\xfd\xeb\xf1\x8e|\xc5?6\xdf`2\xd9 \xf5?\xa4o\xd2\x8d\xb6\r\xf0\xbfd\x95i\xfa\xe7\xe9\xe7\xbf\xcb}\x0f\xfd\xc2\xe1\xdd\xbf\x8a?\xd3`\x12\x1c\xef\xbf\x11!\x86\x84\x81;\xd3\xbf\xfe\xfc\x961\xf6\xba\xeb?e\x94c\x81\xaaF\xc8?\x16\x8e\x9ejN\x9d\xf8\xbf\xc4A\xc4\xf8\x90r\xd4?\xec\x9a\xb4\xfa\x81\xde\xe5\xbf4\x9cb\x96\x0fQr\xbf\xbdO#\x00\xa8H\xe7?\xa4\xe2:\x86j\xe1\xef?\x84\xe2\x9f\xfd\xd3\xe4\xf4?\xbd\x013\xc7\xc5a\x08@\t\xed\xa0\x1c#\x8e\xea?:\xcb\xa0\xba2Z\xc5?\x10\xf1\x95)T\x82\xe4\xbf9\n\x81q\x07"\xed\xbf\xd5s\xb1\xaa\xc7\x1e\xe6\xbfk\xcaE\xa0b\x80\xe3?\xc2nw\xca\x9fY\xdf\xbf&\x122\x99G\x1d\xed?\xdb~\x04pL\xb6\xdc\xbf\xb7\xc5\xa0\x0e\x0c\x05\xd3\xbf\x19\xe8\xdb\xbdA\x08\xe0\xbfD\x96\x81\xc9|\xda\xe0?\x1f\xfb\xf4\xb6\xdb\x14\xe5\xbf^\xa4\xf4\xb5\xe1g\xf7?\x97\x18[\xadt\xe0\xe5\xbf\xd8\x93r\xb5\x92\xcb\xe5\xbfg\x8f\n\x0c\xa8\xac\xe8\xbf\x05L\\\x84\rD\xe3?62\xf7\xff\xac\xf3\xc3\xbf\x83\x0fNu\xd8\x16\xdf?\xc6\x1c\x1c\x03\xc6\x06\xd7\xbf\xae.\xa0\x8dw\x1e\xe3?\x87\xd2\xce\x95\xdbS\xee?\x16z\x1b~\x9d~\xf7?\xcf\xe8\xed\xc93\xe5\x93?\xa2\xde\xc5\xb3\x08/\xd1?\x15R\xa2}f\xcd\x9f\xbf\x8d\xa4\xc6E\xb2\xd2\xd5?\x1cB\xb7\x8e%`\xc9?\xa8\x17G\xf0\x12\x9b\xd9?\xdbT\x98\xf6\xc2\xe8\xe1\xbf5`GC\xac\xf6\xb3\xbf\x1a\xff\x0e\xa4\x1d\xad\xfe\xbf\x12)\xdd\x8c\xcc\x82\x96\xbf\xb1\xc62\'\x07\xbc\xf2\xbf\x8f\nIen6\xfe?p\x11-f%1\xfa?\x92\xf6\xa7\xdaT4\xe6\xbf\xbd^\x89\x9a\x1f\xc8\xd6?7\xd3\xca\xc6\xe1-\xec?\xd2\x9f\x19\x05\xc3\\\x00@\xee\xa5\xd9`\xa7\x16\xbc\xbfL\x01\x84\xbeN\xec\xd8?\\\xc6\x92\xbc\x8d\x0f\xf2?\xf4K\x08\x115\xea\xd0\xbf\xe4&\xddc.\xd5\x98?R\x9e<\xb8A\xb4\xb4?\x8b\x0bHE\x89\xa2\xe6\xbf\xa5\xf7\x9e"\xec\x86V?\xa2\x02\xc0j\xee\xc3\xd6?\xd9\x08\x19\x9fj\x03\xd7\xbf&\x86O\xd9\xab\xce\x00\xc0\xed\xca\xe5\xe2\x98\x94\xf4\xbf\xeb\xcc\x16\x0bFk\xd2\xbf\x98\x06\x18\xe5/\xa6\xd7\xbf\xcf\x0c\xb2A%R\xe7?\xbe\xaf{q\xb0v\xf3\xbf\x8dJ\x11C[3\xf4?\xebZ\x0e\xed\xd7$\xd2?}\x82\xf7ye\x92\xfa\xbfJ\xed\x8d\x7fg\xfe\xca?[O:V\xe1Z\xf7?\x91\xd4n\x1c\x95*\xb4\xbf\xa8\xcf"ph\x89\xdb?\xd9\x12\xd2\xdb\x8c\x0f\xd2\xbf\x9f\xff\xa5/\x98 \xf3? \xe5b\xe3\xef\xc6\xf8?\xeb\xb0e\x89h\x14\xfb?\x8c\xe4"f\xb9j\xd5?iP\xcbX\xf8\xcd\x06@C^\x1b\xcd\xb8U\xea\xbf\x8d\xe6\xe2V\xad\xe3\xb8?s\xba\xe1?\x99g\xaf\xbf\x8e\xa8_%\x85#\xf7?/\x9fyh\xfd\xc7\xd3?~\xb0^\xaa\x9a\x0b\xf2?\xb4E\xf3o\xfe\x13\xf1?\x99`\xd1\x84s\xac\xf1?\xdc<\x03S\xf7e\xe8\xbf\xde)Rc\x13\xf7\xd3?\x0e\xca\xf5\x04\xa5\x8a\xd7\xbf;<($)\x0b\xf5?}\xb1\'\xde@C\xd4\xbf\x9c\xf4r<\x80\xe1\xdb\xbf\x88b%\x01\xb6\x8b\xfd\xbfmg\x9b\xd3c\x90\xfb\xbf\xef\xde\xe5\x0b\x8e\xd3\xf8?}\xfbx\x9a1Q\xf0\xbf\xc1\xd5\xca\xe3<\x1e\xd9\xbf\xc1\xd5\x13\x82\x1f6\xdb\xbf\xc3v\x95<\xe3\xcc\xd0\xbf\xd6\xe5\xc8!\xa78\xe8?\x10\xf7\xf7\x8a\xb9\x1f\xb5\xbfW\xc8\xcd\xd0\xcf)\xf0\xbf\xa4\xe4(\xd6=\xfd\xeb?\x17\xce\x85"a\x18\xb6?h\xa0\t\xf0\x83f\xb5?\xaa\xea_I\x8bU\xa1\xbfH6z9=v\xd5? \x8eI[\x8d\xa1\xe0?\xf1\xcf4\x92\xc6\x95\xea??\x11\xe9A\x18T\xc2?\x0c\x12\xbd\xa7\x80<\xff?3!k9!\xe0\xdf\xbf\xda\x94\xfdv\xc1\x81\xb3\xbfi\x80\x1fF\xd9\x87\xf5\xbf\n\x95u\x8b\x08,\xd3?\xfd8\xbc\x89\x91z\xdc?h\xa6v\x0b\x83\xaf\xe7\xbfse_\xe4\x7f\xd4\xce?/j\xd0U\xdb\xac\x9d\xbf\xa3T)\xa9\xee6\xf5?ETi`\xeaW\xfc\xbf\x12\xcd7w\x84U\xdc\xbf\xcb\xe0\xd8/.\x82\xe4\xbf\x99\x98\xfcL\x9b4\xeb?]\xf1\x9bT\x07\xe8\xe0\xbfQ\xab\x81\xb9\x8c\x1c\xfa?K{=\x85\xf5E\xa7?\x16\x86\x15\x8bU\xef\xc3?)@\x12\x0c-\xf6\xf9\xbf\xaf\x13\xa4\xb5\xaa\xe0\xde?\xa7\x1b"[=\x96\xe1\xbf6\x82:\x9bg\xd8\xf6?B\x93\x95\x00\xb64\xdc?\x06mm\xe5\xeb]\xeb\xbf\x9d\xa9\x94F\x86\x05\xe2??d:\xc3\n\xb3\xcf\xbf\xdd\x8f\x97\xf7\x87\x0f\x08@\xacQ\x1el\xd4\xd3\xed?\x1f\xc3gv\x0c\xe2\xe6?\xc8\x00\x01\xb3\xb3T\xf1\xbfu\xe5\x0b\xa8 \x16\xfb?B\xaeE\xc8\xeb\x96\xdc?\xcfJ\x97\xaeC\xe9\xbe\xbf\x95\x00,\x13\x8dJ\xdd\xbf=\xee\x8b\xc3\x87\xcf\xe0?\xbf\xd9\xe3<\x87b\xe9\xbf\xb29~\xbd\x86a\xf1?\x98\xf8\'\xca\xb5\x0b\xf1\xbfh6\x98QX\xec\xe9\xbf\x07\x0f4\xde\xea\x95\xe8?\xb8\xd2\x86Kc\x07\xbc?Hw[\x83\xa7"\xea\xbf\xdd\x11\xf60\x10 \x9e\xbf\x8a\xe1\rg5.\xcd?Hp\x98\xb4"\x0b\xd1\xbf\xe7\xb9|Z\xc2\x1d\xd3?\x83:=\xef\xb1\xb4j?$\xf5\x9dAL\x8c\xfb?\xb5G\xfd\xec_w\xa9?\xe9\xc8\xa1\xa7\x11\x95\x00@\xb5\xeb\xf5\x1e\x16X\xb1\xbf\xd9\xee&\xd3\xb7\xad\xd9\xbf\x0f7\x95]\xd8\x81\xee?twT\xdc)\x86\xe6\xbf3\xd8\xa45\xdc\xe3\xdf\xbf#\xa4\xddN\xa2\\\xd4?>$\xbc!(2\xe2\xbf\xab\xf5\x00\x16\xe5g\xf1?\xb7i\xc4b\x8b\xa2\xdb\xbfh*g\xbd;\xe1\xf5?\xa5\xd4\xe6\xf3\x83"\xef\xbf\xa3\xeb>\x07\xca$\xd6\xbfxb\x06\xd6\xf9:\xd2\xbf\xb339d\x84\x97\xe1\xbf^\xcf!"\x0f\x02\xeb?\xa6\xa1\xd4}\xd9.\xd4\xbf\xca\xd3\x941\xd2\xae\xd6\xbf\xa8\x99W\x8c7\xd4\xff\xbf\x0c\x89a>a\xee\xf7\xbf\x08B,\x99\x0e(\xa1\xbf0\xeb\x83\x1f\xf7\xc5\x02\xc0|\xb3\xc2\x93\xd8M\xe4?\xe6a\x1a_\x8aR\x01@\xc9c\x0eeB\xf3\xd6\xbfV\xd5F\x8e\xfcg\xd5?\xd6z^t\xf8\xef\xee\xbf=\x14K=\xb3}\xeb?>\x02N\xd6\xd2\x8e\xe4\xbfw\xe0\xe2Q\xd8\xd7\xda?;\xf3|\xa2\x96\xcf\xcf\xbf\xeeb}\xb6\xb1=\xe6?1z\x94\xa9\xb8L\xb1\xbf\xf0\x1e\xa0K1\xb8h?f\xd4\xf3Z\x14{\xf8?\xc3\xcf\x14\xa0\xa7-\xfa?N\x15\xcfiW\xbe\xd0\xbf\xc1/\x7f\xc6\xbf\xce\'\xee\xb3\xe6\xf6\xdf?p\xbc\xa8\xd5\x03\x12\xe3?jQ`\xc2n^\xf1?\x13\xb5\x98\xfb+\xb9\x04@bl\x85\xe8s.\xfe?\xc5_\'\x84)\xa4\xf5?w\xd4JVf\x12\xe3?\t\x9b\xb5E$\x1a\xda\xbf\x8c7\xddxt\xde\xed\xbf\xce\xdb\t3*:\xb1\xbf\x92\x7f\xfb\xd9q\x07\xa8\xbf\xae\x8a]&\r\xd6\xbe?X\xb0\xea\x0b\xa7\xc8\xe7\xbf2\x87\x92\x9b\xf1\x97\xf1?\\\xf5\x03\x93\xf3\r\xef\xbf3@\xc6\x06\x13\x9b\xd6?\xd3&\x01\xc4!J\xe9?\xb1P\x93\xa9\rt\xef?\x87~\x8f\xaf\x07s\xf4\xbf\x0f\xe5W\x0c\x06\x1a\xf1\xbf\xc2\xd2\x8b\x18\x90o\xda\xbfO.rG\x176\xe6\xbf\x85@\r\x1f\xda\x0f\x05\xc0\xb2/{\xb3\xc1\xac\xf1\xbf\xd4Bu\xfasR\xc9?\xfd\x8a\x14\x93\xb2#\xf6\xbf\xec\xa8p\x87\xbaP\xe4?\xacw\x93\xf3a\x96\xe9?u\xbf3\xcc\x05\xb5\x02@\xdc\xd2\xdb\'\xe1\xf1\xc4\xbf]\xca\xf9\xc3\x9e-\x06@T\xe8U{\x04\x99\x0b@o\xbc\n\x8f\x10\xc1\t@Ej9~\x83F\xdc?q8\x07\'6\xd1\xdd\xbf$\xb3rZ\x15{\xe0?\x1b\xa13\x99\xfc\xa6\xf7\xbf\xad\x04\xeb&i\xe7\xed\xbf\xae\xa8\xaa\xe3\xecm\xd7\xbf\xaf\x9a\x19\x0b\x14\xde\xc3\xbfqq\x11z=\xfd\xe4?f\xd2"\xb9\xd3\x1a\x05@\xd2\xf5\xa0\xf7>?\xe5\xbf\x0f\xdbl<\xb1\xbe\xe7?4e\xedM\xa6?\xd3?\xbd\xbcKMM@\xe8?>\x11\xfc#W\xe9\xdf\xbfZ\xd1\x82\xdbs~\xed?\xdb\xa2F\xf5\xa3\xf1\xea\xbf\xb3?cGA\xe3\xed\xbfd\xcc\x91\xa7\x8fq\xd0?\x8dSlO\\E\xf1\xbf\xfd\xffZ\x01\xce\x05\xe4\xbfSYr.8{\xda?\xa7n\xb6\xae\x0f\xb6\xe0?\x8f\x9a\xd1\x89\t8\xf7?\x8d\xa8Y\xefy\x8a\xfe?\x87\xe1\x13\xa5\xfb/\x03@\x0e?\x98YQR\xe7\xbf#\xa3\x1d\xd0/\xfc\xf9?4\x9ac\xcb\xab!\xc9?j\xaa\xa8,q.\xf1?\x01A\xae\x1e\x06\xa5\xf6\xbf\xf8\xfd\xa2?\xd6\xc6\xd8\xbfn\xd3\xe4\x80\x1cI\xf5\xbfC\xa0\x9c\xea\xa9\xd5\xd6\xbf7\xb2!\x81\x00?\xca?\xf0B\xb8\xcer\xd8\xd1?\xc01\x1a\xd7\xe7\x97\xf4?4\x19\xb9\x03\x0e\x0b\xc5\xbf\xa9\n\xd6\xc7\xf7\xad\xf6\xbfg\xf4\x83\x8dz\xa8\xe2?\xa0\xfb\xb1z\x0b|\xd3?f\xe8X\x1d!\xa2\xfa\xbf\xdb\xe9\x90\xb3\xf5-\xe6\xbf\x89?\xad\x13\x83\xc3\xfe\xbf\x99b\x97\xfaS\x00\xd1\xbf\x0e\xa1c\xeakk\xfc\xbfm\xa3\x18\x1dG-\xdf\xbf\x8es\xb5\xe5>\x1f\xfc\xbf\xe3\x91\xb3B\xd4d\xef\xbf\x92\x1fV\x81\r\xfb\xe5\xbf\x9d\\\x80\xbaA\xbe\xa1\xbf\x9d0o\x83\x98V\xf1?\x8f\xc6\xb5\xf1\x88\xf4\x02@N\xd8<\xe8u\x0c\xe2?j\x02Hq\xadB\xd2?\x8f\x99\x19\x8fZ \xf4?m\x11\x99\x13\xd3|\xa6?\xe1\x8dj\x9b\x87<\xe4\xbfi<\xdb\x1d\xbe`\xf3\xbf\x14\xdb\xdf\xfdg\\\xe1?\x81\x1e\x8c\x87\xa9\x1d\xda?\xd2]hc\xec\xbf\xf6\xbf\xe7\xa6<\x1fJ}\xf0\xbf\xe7)\x01\x04l\xb9\xe3\xbfaf\x9f\x86\xbd\xc0\xe3\xbfD\xc8\x94\x90 \'\xd8?\xb2\xd5\x99\x1f.\xc7\xeb?/\xe9i1H\xe9\xd5\xbf\xec\xee]\xe4\xda2\xf4?\xf27z\x06\xdf\x0b\xcb?p\x8b\x15\x8d\xd7u\xf2?\x1d\xe3\x9b^\xe2\x03\xf3?@\xadA\x86s\xd2\xf6?\xd5\x85\x82\x9d\xdb\x88\xf2\xbf\'f\xc9\x81\xc8\xb4\x02\xc0\x08\xa7\xd7\xa94!\xf6\xbf\x92qI[:H\xff\xbf\xd6~\xe8\xc5\xc6`\xe8?}\xeb\x0ebP\xa2\xe2?\xaeZ)\x12\x05\xd5\xf8?y\x18u\xceE\xbf\xf0?\xfdD\x87v\xbe\xf9\xe0?v\x1d\xbfW\xd5s\xe2?*\xfc\x82v6\x15\xf7?\xf3\xb8\xb7\x0f7\xab\xb8\xbf\x86\xe8%\x97o\xc7\xf1?\xf4\x14Eht\xd3\xec\xbf\xfe\xa8\x86\xbb\xd0s\xe6\xbf\x0b\nG\x14\x01\x05\xe1\xbf^\x86\x13\xc1|C\xfd?\xf8B5\xb2\x15\xd5\x00@\xaa\xb4\xacl\x008\xcf\xbf#\t\x8b`_\x14\xdc?5\xdfK\xcdw\xb4\xf4\xbf\x02\xaf\xb5\x05@\xae\xfa?\xediy\x1do6\xf7?T8QW}\x11\xca?\x086%\xc5-o\xea?vd\xc9\x88\x06\xe7\xdb?H\x0e\xb54S\xb9\xc5?\x1a\xc0\xa4f\xec\xd6\xf6\xbf\t\x0cp_\xc1\x89\x92?%Y\xe0\x8d\xc0\xd6\xb6?{\x99*\x91d(\xa8?_N\x07\xa4\xb1\xce\xe3\xbf\xb4?6\x10\xe8|\xe0?<\xcaT\tM\xed\xd6?\'I4\r\xd6\x02\xac\xbf\x1a\x96(GII\xda\xbf\x18\\1\xae9\xb1\xfc?\xceu\xe6\xec\x88a\xeb\xbf\x082p\x8a\x02K\xe5\xbfr2%k\x9e\x87\xdd?\x1d\x1b\xc8\x87\x99\x90\xdc?\x9ahj?\x9ay\xe3\xbf\xbcH\xb4I\xc96\xdf\xbf\xd0&9U/\x1a\xeb\xbf\xebM\xb3_\xe77\x00\xc0\xcf\xa2\xc9\xe6\x91/\xef\xbf1\nFk\xe7&\xe8?\x9d\x07\x8d|\x17R\xce?\xb6\x1e\xaa\xecD\x98\xa7?\x07\xb2jQ\\R\xfb?wSE\xdc\x15\x16\xbc?\xbaz\xdb\x9a\x04\xeb\xeb\xbf\x88q\xfft\xb8q\xd7?\xb3"I\xd3_X\xe6\xbf\xa0d\xf9\xa2\xbd\xde\xde?\xda\x13\x82\xa0\xf4\x0f\xf8? \x1f#T:\x08\xe9\xbf\xae\x80\xa8\x9e\x8e\x1b\xc6\xbf|\xe4\xf5\x99\xf9:\xe4\xbf\x0f\xf5\xbd\xab\xbd\x8e\xa2\xbfB]N\xba\x99\xfc\xf1\xbf\xb8\\\x16=\x93\xa3\xe0?\xdb\x87\xaa\xe4?\xe0\xe9\xbf\x18\xe7\r\xf6@j\xe9\xbf4\x1e\x8aoW\x05\xd3\xbf\x9e%m\x809\xff\xd7\xbf\xfe\x86\xc8\x13\x9fM\xf6?\x94\x81!VX\x11\xf9?^\x7f4\x17=,\xda\xbf$\x03S\xee~}\xd1?E\xd1+jG\xe9\xf0?\xcc\x18\xc2\xd6\x11\x97\xc2\xbf6\xf5\xb3\x88\xe9\x89\xec\xbf\xd0\x94\xed=\x14\xf8\xd8?jn\xd6\xf4\xf2\x03\xd7?:\x91\x7f\x90t\x85\xfa?\x98\x9d\xc8\xd7\x1b\x1e\xe3?o\xd3\xb9Gu1\xe5?Tg\n\xc8\xd7P\xfd?\xf8N\x12*\xc18\xf1?L.C_\x06\xfb\xd3\xbf@\xad\x06\xb7\xd9\xb7\xce?\t\xd9\xcdh\xed\x19\xed\xbf\xb7\xd6!\x99\x1el\xf9?\xe6\x02n\x97\xfeq\xf0\xbf\xb6`\x18\xd4\x8d\\\xeb\xbf\xaf9]R\xdd\xd4\xc4?\x02\xb3\xe6\xa4\xbcQ\xe1?Z\xc3\x86K\xadU\xf6?\xc5\xe1\x9a\xb7\x84\xa9\xfe\xbf\xde\x85\xe9\xe8aZ\xd4\xbfiNO\xbd\xb0\x84\xe1\xbf\xb8a\xc3\xderK\xf8?\xea\xb8\xb9G\xfa\x87\xea?\x0e\x81\xfdY\x06\xc5\xf9?\xf8SC\x8e\xd1"\xff\xbf\x0c\xc9j\\\xac\xbc\x03@\xb2bg\xa1jA\xe2?\xfa\xf4&\x1c\xc7\x14\xf1\xbf\x8e\x8f\xdb\xab\x89\xb4\xc9\xbf\x853\x07/\xab\xaf\xd3\xbf\x87\xb6\xd9\xfa\xc2\xe7\xed\xbf\xe0\xe7A\xb4\x8e\x93\xd8?AKO5\xb2!\x98?\xdda"uZ\xa3\xcb?\xdcl=\xce\x1b\xf7\xc1\xbf\x14\x148\x07\xaa\xf4\xc6\xbf(\x1f\xe3\x8e1\x0b\xfc?\xcf\x7f\xae\x9f\x80\x12\xd2?\xdd]\xe9-\xe4xx\xbf\xdcH\xdc\x95\xacX\xd7\xbfO\xfaF\x91q/\xfa\xbf\x8f\x01\x19\xd4\xce\xf5\xfc\xbf2\xf0R^\xe5#\xe3\xbf\x1b`\x12\xab\t\xd7\xe0\xbf\xe5\xd3zt\xec\x06\xd0\xbfG\x8b,\xadT\xaa\xf1?\xc1\xbd\x07\xb0\x8ag\xdb?6\x01\x05\x07\x1e\xb2\xee?\xd6\x8b\x17x]\x7f\xf4?\xd5\x96\x9cE\x13\xa4\xea\xbf\x04\x95\x1e\xf0\xe4z\xdb?\xe5 l\xa9\x82-\xd9\xbf\xbeq\xca\xf6|a\xd7\xbf\x8bB`\xb5R\t\xf6\xbf\xc1\x12x\xdc\xe7\xcc\xf6?\x95\xf3hzDh\xd9\xbfx)\xfa\x10\x07\xcb\xf0\xbf \x08\xb9\x1bs\x03\xf1?\xfe\xb2\x82\r=o\xd2\xbf[H\xe3\xa1\x94?\xf5\xbf\x14\x9f\xf7\x13I\xd1\xf7?B\x19Lt\xb7\xbc\xe1\xbf\x83\xb5cg\x87\x19\x85\xbf\x84?\xa7L\x0b\x98\xf2?\x86Y\x84\xfe\xc1\x9d\xc1?\xabV\x01qGw\xb7\xbf\xfeg\x1f\x14\xeb\xaf\xd7?\x8b\xbc]-\xa8\xc8\xf6?\xed\x03r\x94;\x93\x02@s0D\x06\xa9\xc1\xe1?\xec\x9f\x8dg\x9ca\xa0\xbfH\xad\xebF\x1e\xbb\xc9\xbf\x93B3\r\xa6%\xe3\xbfy;J:\n8\x00\xc08\x92\x0c\x15y\xaa\xfb\xbf$\x13{E\xb6U\xe5\xbf\xe5\x05\xdc\xd8\x98\x9b\xe0\xbf-VY\xf7;x\xe7\xbf\x07\xf6P2\xf2\x07\xf5?\xa7\xd0\xfb\x9ca\xd9\xd1\xbf\xfa#\xe1Y\x90\xb6\xe3\xbf\x85\xd0c9\xc0\xc8\xdc\xbf\x04|\xa3\x0f"x\xee?\xc0{\xdb\x02>R\xd6?\xc9\xab\xe6\xael\x8c\xa9?\xf3_0\xf9\xaa\x95\xd1\xbfh\xe0\xd4Ra=\xb2?\xcc\x90|\xa0\xc1m\xbf?:&\x9f\xbdH\x1a\xfd?\x93F\xa3+\x94G\x06@\xfdg\xd9vMV\xdf?\x00mn\xf6\xe5\x94\xe5\xbf\xd4T\xb9?\xf0S\xe2?\x86\xa7\x87\xe9\'h\xf0?\xf1e:\xd5]\xf9\xe4?\x8bb\xcdD\xef\xd6\xf2?\x97\xe6\xab\xe3a\xa9\xeb\xbf\x865\xe4\xf9\x89\x81\x81\xbf\xa0Db\x0b\xa2\x13\xbe\xbf\x80\xc3;\xbeo\x97\xe6?*`\xd1\x9a\xab\xfa\xf5\xbf\xd7\xdf\x0b\xf8\x18\xea\xca\xbfO:vMa\x86\xc9\xbf\x0b\xdc\x9454\x1d\xf3?g\xdeO\xc0pO\x00@[D\x98E\xf4\xf3\xed\xbf\x9d\xe1\x1f\xe6\\h\xe4?:\r\xd6\x98\xa42\xea\xbf\xc5N\x84(qc\xfe?\x93c0\x81oZ\xdd\xbf2^\xbc\xe8\xb5c\xe7\xbf\x1b\xd8\xebG\x18\x10\xee?\xca}\x7f\xa7\xc6"\xf7\xbfM\xc4\x08~\x82t\xef?\xbc\x0c\xcd\xc4\xbf\xe2\xea\xbf\xd8\xe57\x81\xf9\xfb\xe4?\x85\ni.\xe1\x81\xf4\xbf.0Ss\x9b\xbd\xdf?Ot\t3s\xc3\xf2\xbf\xc6[2\xd8\xbb\xac\xf7\xbf\x03\xa0\xeb\x8a\x8e\xcb\xf1?\xb3\xb7\x1a_\x8a-\xe9?\xd8!m\xd4f\x86\xf2?\x90\xdax\x96{\xaf\xaf?r\xf2\x91FW\xb0\xa8\xbf\x1d\xa3\xa1\xc3\x96\xe3\xef?uS\xe17\xd9\xaa\xe2\xbf(M\x05\xab=\x1e\xca?\xad\xe4\xf6\x03\xc2*\xf6?`F\x17x09\x00@\xed\x9f\xc4\x89\x9e\xa1\xc2?\xca6\xe8\xedl5\xe4\xbf\x16\xef\xed\xb4\xd3\x08\xef?y\xeb\xe5tj\xdb\xf0?\x88\xfd)n+\xe7\xf6?{\x86\x8dC\x93+\xf5?jv9\xeb\xd3\xaf\xfb?\x97\xedm\x13\x93\x9f\xc2?c,\xe5\xf4\xb54\xef?d\x99\x1b\xb7G\x98\xda\xbfx\xda\xc3\xbb\nP\xd8\xbfn\xc9d\xdd\xb7f\xdf\xbfj`@iO\x96\xe9\xbf+\xca\xa5pRp\x01\xc0\x81\xa4p\xaf\xfc\x82\xde\xbf\xd0\xd9\x07 w\xb1\xf0?~\x93;\\tY\xf3\xbfh\xed*R\xbc"\xa9?\x1c\x00\xe8\xdd\xdd\x1d\xeb\xbf\x8f\x00\xbe\xdb\x99Y\xd3?\x7fIP\xbf>,\xf0\xbf(\xc7\xf1CJ\xe6\xf8?\x9b\x1d\xb6\xb2\xdc\xa0\xf9?\xe2e\xfb<\xec\x98\xdd?\xd5\xd4\x96\xfb^\x99\xf1\xbf\x19C}\xd3W\xcc\xd3?q\xc5\xbed5\xc9\xe7?\\X\xd7\xaa\x0f\xc4\x00\xc0\xfe-\xccH_5\xc6?U\xb4$vG:\xc5\xbf\x02\xa7,\xc4\xf4\xbd\xd6\xbf]#\xed\x8a\xa4\x82\xb1?\xdf\xe5\xc7a\x85m\xe0?\xd8{\xd0"\xa4\x1e\xf7?\x9a.(\x16\x1e\x1c\xc0?\xad\xb9@\x7f\xb6\xfa\xd4\xbf:\x10Y\xdcfi\xf2\xbfk\x00b\x04,\xf0\xf4?\x1b\xb1.\x06[h\xe5?]\xbbu\x1c\xd0_\xf9\xbf+6\xfd5I\xe9\xf7\xbfH\x06\x9b`o\xc9\xfb\xbf\xc551\x16\xbb2\xed\xbf\xd3\xd7\x82U\xbdd\xf7\xbf\x7f)D\xaf\x13\xb8\xc9?vIuu,\xf4\xb5??\x80%\xbf\xc6\xe7\xd3\xbf\xae\xc3\xf1\xf7q\x8d\xfa\xbf\xdb\x98{w\xec`\xd0?y\x90\xb0\xf3\'^\xe5?\xa1(\x807\x04\xbc\xf6?\xbc\x83P\'\x98\x93\xe2?\xda\x1d\x89<\x12g\xc7?\x9a)\x9bH\xaa\x04\xd1?\x18\xbd%\xab8\xe0\xb4\xbf-Nu\xb5\xc2\x17\xf9?\xf2T\xf8x_2\xd5\xbf\x11\x8e\x1c\x94U\x85\xe3?\xd6M\xce~\'$\xf6?1C\xfb\x9b\x13I\xe5\xbf\xe7\xf4b\xb0\xbcC\xf0?\xe6\xb3\xb7\xdb\xa7\x9e\xf8\xbf\xc4"H\x8ck{\xec?\xa9\x8f\x16\xf4\xaeI\xdf\xbf.b;\x18,\xaa\xf1?\xe4\x05s;\xa8\x89\xe6\xbf\xfbb\xb0\xd3\x1e[\xf4?s\xa4\\\x9f}\xe1\xd4?\xda6\nCI\x1f\xe6?l\x115\xdct\xcd\xf6?uV\xe2\x8c\xbe\xe3\xdd\xbfX\xf5\xa0\x1ca\xb8|\xbf\xd7I\x01S\xd8\x91\xfc\xbf\xc6\xb8\xd1\xac\xa6>\xd2?0\x87\x03\xa3Y\x93\xfb?\xae\x8e\xd8$\x995\xe5\xbf\xae,\xb4\xe3O=\xf2\xbf\x13\xdf\xba\x1b}3\xda\xbf\x96N\xf9\x84W\x83\xfa?\x98\x14\x03\xc3\xe6&\x08@\x81\xcf\xe4\x99B\xa1\xe9?\xe4\x07\x08\xa9\xa0\x0b\xf3\xbf\xe4\xf1\x0f\x1eT\xc4\xf7\xbf#\x1f\xc0o|6\xd5\xbfa\xa4\tZ"e\xdc?\x96\xdag\xa6@o\xef?2\x07i\x05\xb5\xed\xfc\xbfH\xba\xc3l\xad\xbe\xe5\xbf\x8cX\xfa\xf6\xa6\xeb\xee?-{\xef\x8dY\x86\xd9?\x8e\xf1!#\xd8\x93\xdd\xbf\x8b\xd3x?2\xe2\xf3\xbf\x88#\xe5\xb4\xff+\xf2\xbf\x9c0\xec\x17\x1b\x0f\xd7\xbfe\x86\xd2\xe1*V\xf4\xbf\x07\x80\xdc\xd5\xb6\x00\xc2?U\x89`\x8b\xb7\xe7\xf1?\xc1\xe1\xf3nP\x89\xe1\xbf\xc7d+\xbd\xa7\xa8\xdf?\xe7\xca\x04I\n\x1e\xef\xbfg\xac\xbe\x1e>5\xcc\xbf\x1e\'\n\x8c\xb4\xf9\x06@*\x14b\xe2C\xe8\xfc?\xff\xc26\x96\xecE\xe5?\xc5\x9f&5\xef\r\xd1?\xf1\xfbm\xb2\xd8)\xe0?\x19b\x11\x94)\xa2\xf2?\xd1~\x90\xf0{\xe0\xef?f\x99\x03\x7f\x8eI\xfc?\x84\xe4<\xd7\xe3\x8e\x01\xc0\xa9\x1fy\x94`q\xdc\xbfb\x06\xbdc\xd8\x07\xe7\xbf\xca\xafy\xeb\xceI\xe3\xbfg\x9b\xa9\xf8s\x9a\xf5?_\xe2\x8d\xd9\xbe\xa7\xe7\xbf!Zp\xbc=@\x00@\xcb\x06\xe1\x91\x0c\x92\xf4?\xd4q\xc3\x1eu[\xce\xbf\x0eyH\x96\x04:\xf3?\x16P\x01\xd4\xbb\x99\xe1?\xc5*\xe1\r9{\xfb\xbf\x89;Q\x18\xde\x15\xf7?\\\xa8^!\x99J\xc8\xbf\xb8\xda3\xa1*\x91\xe3?t=\x84\xeeC\xf2\xd8?9j\x8b\x81\xf3\x8f\xe3\xbf\xd0\x04+\xcfcv\xf5?\x12\xd2\xd6\xb8\xfb\x12\xf4\xbf@\x97\xfe\x10\xf4\xf1\xd1?\xd5\xdc\xe9h\x86i\xeb\xbf:\xff\xd0\xec\xcd\xb8\xdc?\x04\xbcBivl\xb2\xbf\xe1\x15\xf5\xdf\xc7\xe1\xe4\xbf\x02\x90g\xf9\xab=\xf1?\xb5\x8cj\xe9\x10\xb1\xe1?\xbe\xdd\x8a\x8eJ\xfd\xfd\xbf\x90*\xa3\x13\xd4\xe0\xd8?\x84AE\x7f\xf3G\x96\xbf\x8b\xa9d\x86Z3\xe7?X\r\x01\xae\x01\xbf\xf1?\xb0\xefD\x8a\xe9.\xb2\xbf\x836!M\x7f\x8d\xdf?\xd0\x8b\xad\xb6\x91\x1a\xde\xbf\x94xy\xc7=<\xe3\xbf\xa5\r\xec\xea\x13\xb1\xf4?|\xec2\x1all\xc7\xbfo!\xa2C?c\xf1\xbf9\x82`\xc0vW\xd1?:\\\x99j5\xb9\xf2?\tScmL\x06\xf8?\x05\xc5\x1f\x0e\xed\xdf\xce\xbf\xcd\x17\xb0\x9c\xa4k\xe4\xbf\x08\xd3\xe4q\x16c\xee?\xbb\xd9;\x02\xae.\x90?\x8b\xa9^\xb9\x8c\xd4\xf3\xbf\xe0\xe5\xc4\xeeY\xbb\xe3\xbf/\x84N\xba\xdb\xa2\x00\xc0Q\xa8\xf6\xc5&\x80\x8c\xbfc\xd5:&LP\xee\xbf`\xf2\x88\x18B8\xdc?\xfc\xc1P\xae5\xbf\xe0\xbf\xffR\xc5M\xd9i\x94\xbf\x92\x1bs\x9bb,\xe9?\xa1\xc0\x11\xf7\xd4\x1c\xe7?M\xfe\x98\xdd3\xb3\xee\xbf5.\x91|!\xc4\xc8?,<]\x9e\x04K\xe6\xbf\xacI\x997\n\x8e\xf2\xbf\x17\xcd\xb33\x9e\x9b\xd9\xbfo\x1e\xfd\xa9r\xf1\xf7\xbf\xe5\x9a\x17{\x87\x1b\xf0\xbf9:\xf7u\x92\x87\x00@\xd7\x87=\x0b\x87\x08\xef?\xe0\r^O\r\x8f\xf0\xbf\xb6\xb2\xa6$G`\xd4?\xbd\x85\x11^x2\xcc?\xb1$\x1c\xfd\x14\x8f\xf0\xbfPq\xe9\xb8\x84t\xf7?x\xb4\x10\xde\x82\r\xe6\xbf\xc6\\I\xe0,\x01\xf3?\x0bJ\x0c\xb3Ln\xdf\xbff\xd0\xdb\x0c\xceo\xf6\xbf\x06;\xdfO%\xcd\xf6\xbfE4\x1d*\xce\x9d\xeb?\xe8\xff\x97\xb6\xe2\x18\xd6\xbf\xd9\xdd\xa7Z~z\xd5\xbf\x01_\xa7)\x1a!\xcf\xbf\xa2]X6\xb7\x8d\xdb\xbf;\x97\xdf\x9e,#\x02@\x89\x9cF9\xed5\xed\xbfkO\xcf\x88\xbee\xef?\x06(\xba\x17\x90*\xef\xbfLI%\xa5\xbaj\xd4?\xc3\xea\xa0\xb6\x8b\xa8\xf2\xbf\xe9\xaf+A\xbel\x87?\xb2\xdb\x8f\r\x9b\xb1\xf9\xbf\x97xH\x88\x8eW\xc5?l&\xae\xbfL\xa6\x03\xc0"}\x9e\xc4\xa0\xd2\xf4\xbf\xcc\x193,\x08^\xe7?\xea\xa8\xcc\xdb\xd3\xd9\xe8\xbfG.6\xcei\x01\xd1?p\xf8E\x02\x07c\xea?\x16\xf2\x1dr\xbaE\xf6?\xba\xfc\x03$$\xf8\xe1?7\xcf\x93\xcc\xe8\xa4\xe7?\xb4\xcd\x98l\xb9 \xd4?\x10\x07Q[>\x98\xf5?\xa2\xed\x93\x05\xed\xad\xf3?Z\x91a\xba\x80\x80\xed?\xa6K\xac#50\xea?\xc7nMB\xc6\xe0\xe9\xbf\x9dL\xe1\xa8\xeb\xd3\xe6\xbf\x05\x8dx\r\xe8<\xf3?u\x84@\x1c\xa1\xdc\xf2?KW\xa0\xe1e@\xf9\xbf\xb1Rp\xf0\xef\x0f\xe9\xbf\rP\x8bq\x14\x81\xdb?\xfb<\x1e\xe3\tG\xb0\xbf\x1cIN\x19\xf5\x9f\xf9\xbfRI\x8aO\x91\\\xf7?\x87z\xc8\x12\x1f\xe8\xea?\xee|\x03\xf6\xf9\xa6\xf5\xbfR\xa3\xc6u\x8a\xd0\xd3?\xd3ma\xd4K\x90\xf1\xbfbK"\xd4\xa6\xeb\xd6\xbf\xab\xf4\x7f\xb1@\xd1\xd8\xbf\x84\xa5O!\xd8\x9b\xe2?h \x8c\xd7B\n\xd8?&\xdfp\xcb\xda)\xe6\xbf\xc6\x00\xb6\xf3\xde\xce\xc7?\x11\xd1\xbf\xee\x1au\xed?} \xb7\xdc\xf3h\xf0?\xd4$\x0c8\x0b\xf7\xf1?v\xe8\t\x15w\xef\xe4\xbf\xc7\x10bX*\xfc\xf4\xbfE\xf9\xae\xcd\xae\x9c\x93?\x15\xf7\x1b\xed\xd5\x05\xc8?\xf1\xc3\x00\xbf\xf3\xed\xf2?ddc@!s\xf4?\xef\xac\xa9T\xe1\x9f\xe5\xbf\xcf\xe9.\xf8\x81F\xe6\xbf\x06\x11N\x03l\x06\xf0?.p\xcb\xf7\xbc\xaf\xfe\xbf\xb2\xfaB\x8f\x8e\x8f\xce?az\xce\xfb\x80\xe5\xef\xbf\x11l\xf8\x1b\xf8&\xc2\xbf\xfa\xa5V\xe9\xee\x17\xc5?\xece\x163\x07\xbf\xf8?\xd0\x1c-\xadP\xaf\xec?Q\x85\xe5\xc4\xd0\x97\xf4?\xf2)H\x8e[\xf9\xf1\xbf\xe8j\xf3\xa3O\x07\xd2\xbf\x87\x05\xa8\xcfD\xfc\xf9?\xe6\xa8:\x90/\xbb\xf8\xbf]6\xb7;\xef\xc7\xe7?\x04\xd8P:\xaas\xed?\x8e\x0f\x99<\x94\xc7\xda?W\xcf\xca\x81\x97\x0c\xe4?\x1fq\xea\x17\xba6\xef\xbf/\xfa\xa9\xe5\xe5+\x00@T\x9a*\xf5\xee\x7f\xcf?\x10\xc7(\x17\x8b\xdf\xb5?\xc4\x9e\x84\x83Q\x98\x98\xbf8\x15%\x84\xf9\x1a\xb6\xbfI\xc7\taj\xe0\xf2\xbfGA\x92\xa9\x8eu\xf1\xbf\xd5\x00\xad=!W\xd9\xbf`\x8d\x99\x0c\x00>\xe3\xbfJ\x9a#\x83\xf2[\xf5\xbf\xa3\x14\x13\xfd\x05\xc0\xfa?\xbc\xb1$4\x81\x13\xf8?\xa53\x8d\x91\x81\x8e\xbb?\xbc\x03\xdaq%m\xf8\xbf!=\xa2\xfe\xf2/\xe1\xbf\xc2\x0b\xbc\xd3\r\x13\xf9\xbf!2\x03\xe4\xea\xd7\xf1?2\xc2k\xc3Y\x86\xfc?\xf5\x08;\xe1ZL\xea?\xb0\x8f\'h\x92\x8a\xf9?\xe2\xec\xd0\xba\xb7\xc7\xf0\xbf\xbb\xe6Z\t\xe3\'\x03@n\xe7BWn\xf0\xfd?\x13\x0b\xab?\xe8\x8a\xe4?\xe4\xe0\x14\xcd~\xc3\xf5?\x84\xe9\x9e\x1a\xc7\xe1\xfb?%H\xb9j\x0e?\xc8?ac7\xf1\x89\x93\xf6?P\xdc3u\x15\xd5\xe5?\xbe\xa4\xe6w\x90\xdc\xf4\xbf\x8e\xe1\x9dQ\x18d\xd8\xbf\xfa\x8c/\x97~1\xf5\xbf^\x9c\xf9\xee\xc5\x07\xe3\xbf\xc8\xbbRMZY\xdd\xbf\xd5\xeax\xdb\xaf\x0b\xeb\xbfrp0\x07[\x0e\xd3\xbfw\xf67\xf9\x18\x05\xf9\xbfN\xaeQN\x1f:\xf2\xbf\xe8\xcay)x\x91\xf9\xbf\xc5b\xb1im\x1d\xf6?\xf4\x05B\x9f,\x08\xd6?\x07F?\xf3\x1e=\xe5\xbf\xba\xfa 3\xbf\xb4\xf7\xbf7\xa75\x17\xb1\x9b\xfa?Z\xa0\xd6\xe5\x00\xeb\xf0?q\x84\xf8u\x03\x8d\x03@|\x1b\xa4\xc0\'\xba\xf6?\xdc\xc4\xf0\xf7"\xd3\x04@I\x87\r\xa5\x8cp\xe3\xbf\xf6\x99\xe3\r\xe0{\xe6?\x87M\x8ci\x13Z\xdd?\x8f4\xea\x1c7\xcb\xf0?\x91\x1b\xeb\xcd\t\xd7\xeb?BZ\x1cxV\x96\xf7?\x85\xdc\xfd\xd1r\x9a\xf4\xbf\x9e\xed\xcc5sA\xf5?\x88\x97\\\xee\x0f\xe6\x00@\xb4\xd9\xbe\xd6\r\xd6\xb0?0\x81\xfc\x8a\xf7\xbc\xf7?r\xa2\n|\xe1\xde\x00@L\xf3\xb5y\t\x84\xf1?h\xd2\xed3\xfc9\xea\xbf\xee\xff\xce\x0e\x93\x11\xd1?\xa7*\xb7\xc2~a\xe1\xbfH(\t\xa5!\xcb\xd9\xbfh\x1dlF8\xd1\xe1\xbf\xd4_g\xdaw\xa0\xe2?\x1az}M\xe1*\xe6\xbf\xd9yx\xe7\xe6\x9b\xd6\xbf)\xa1\xcc\xbc\x9f2\xb9?B\xa7\x17\xe2\'?\xb8\xbf9\xb6\xab\xe4\\;\xe6?/\x84\x1e8\x07\xbc\xe5\xbf\x9d\xfc}\x9d\xb5\x8f\xf7?x\xa4E\xef"\x12\xea\xbf\xc3\xc6\xe3\xf3\xe3\xad\xe8?~5<\x9b \xa4\xe3?\x10,\xa3\xcf\xfa\x1d\x00@\xf2\x1eNq U\xe3?"v\x99\xa3\xd1\xc9\x9d?\x1b\x94^\xb4\xda\\\xa5\xbf\x15\x0b\x9fNE7\x97?\xe1K\x99\'\xc6@\xea?\x08n\xf32\xd8\x0c\xef\xbfV\xab\xde)\x80Y\xf2?s\r\xc4\x1e\xf3\xbc\xde\xbf\x9d\xb3\xd5d\xd7\xf8\xe0?\x1eQ8V\xa3\x1d\xef?S8\xe7\x83\xfc\xc6\xf1?\xd0\xf0\xc8\xb6\xf6N\xef?\xef\x90\xf2b\xd7~\xfa\xbf\x890\x17\xe9\x9fx\xed?~Pu\xa6)\\\x07\xc0\xccF\xbe\x9e\xfc)\xda\xbf\xc1\xd0\xd5s{\xf7\xe5?\xb4\x83\xf1h\xd8+\xf0\xbf\xdf\xd9I\tM\x0f\xcb?.L\xacq\x87\xa9\xcc?\x0b\x08\x0b\x8c\xf3|\xda\xbf\xf7\xfa\xa89c\x99\xf7?\xfc\xb9?\xd8\xd3\x11\xf8\xbf\xe6\x0f`\xf0\xfbh\xe3?7h\xe6\xdd\xf0H\xe6?\xc2\xc2\xc1\xf3\xa5\xe5\xf7?\xa8\x9b\xa1\xb5\xe7B\xc9\xbf\xcem\n\xb8\xa1)\xd3\xbf\x85Y\xdce\x81P\xcf\xbf\x91\xc7\n7\x16W\xd9\xbf\xd9\xdd\xc23\xb4\xde\xfc\xbf\xca\xc6\xcc\xf3 L\xf5\xbf\xb5\xaf\xbf\xef\x8b\xef\x96\xbf\xd1\xa9\xef\xb6\xb6\x05\xc6?61\x7fF>\xac\xe3?r(\x9a\xbeC\xc2\xf0\xbf\x9f5\xfe\x93\x04\xb1\xe5\xbf\x0e\xce\xd9\x9c\x1c\x85\x04@.\xa5y\xb3W\x88\xf7\xbfz\xdf\xa5\xef\x1e%\xea\xbf\xdf\x9d\x9d\x97!a\xc3\xbf\xc7C\x98\xfb\xbd\xa3\xc3\xbf2\xc7\xf5X\x9a\xd8\xe1\xbf\x07\x04\x94#\n\x06\xd8\xbf\x13V_\xb6\xe0\xc4\xef?f\x17N\x1e\xc9x\xd9\xbf\x87\x18\xc8\xe3W\x03\xde\xbf\xbbm\xc2\xae\x9a>\xea\xbfO\x97|\xfa\xa7\x06\xd5?\x03[\xbc\xe3\xa1]\xad\xbf\xe3\x16\x90\xa6\xe3\xa2\xfd?\xe9\xb90n\xcb\r\xb1\xbf\xf2\xe1\xfdA\xd4B\xf9?4X\xb9.\xef\xc2\xe3?\xab\xaa}S\xb9\xde\xf7?\xfd\xef9\xb7ip\xe7?F\xb6W\xf3\x847\xa3\xbfM\xd5o\x81\r\xdd\xff\xbf\x91\x1c\x95\xdbe\xa8\xe2\xbf\xd4\'(\x1ab\xbd\xfb\xbfk\x8bN$#0\xee?*\xc1\x7f\x94\x04\x01\xfb?\xc2\xb8\xf4\x01L\xf0\x06@1\x89\xe6#\xc7\xb1\xf7?x\x12`\x15\n\x19\xca?\xcbn\x9e\xf0 \x98\xc4?r\xab\xfc6\xaf\xa8\xf4\xbfBD/\xe1&K\x9f?\x84\x87"\xacA\x9e\xdf?\xfe\xe4\x97\x88\x0fp\xea?\xb2+<\x0f\xb3\xa4\xf4\xbf\xb1\xba*\x8c\xba\x1b\xf2?\n\xe5\xb9,\x87k\xea?\x8e\xfd\x9d\x87\xca:\xf2?C\xce\x86\xab\xcc\xa0\xf5?:\x967\xb0\x02\x1a\xe6\xbf(\x89p\x12\xfc\x1e\xb3\xbf\x04\xe0\xfe\xa4#\xdc\xcd\xbf\xbf\xaa\x10\xf5\xcd\xc4\xe4?*$\x89\t:R\xfa?\x02\xe2\x9c\xbd\x96,\x00@\x07\x7f\xc6)i\xd9\xac\xbf\x0c\x01\xac\x0f\xdf\x8b\x00@ZJ(\x90\xa0\xd5\xd1\xbf\xfe$\xfela[\xcc?\xcc\xeb\x93\x14\xdau\xea\xbf\x19\xd6F\x06\xd2\x00\xdd?\xf0B\xbdgX)\xc5\xbfw\x82c\xe5.`\xd0\xbfU\xfb$cy\xea\xdc?,\x8a(LX[\xb1?6\xea<-\x1d\xf3\x00@\xe9\xcb`R+\xa1\xe0?\xaa\xa0jE\x85o\xf3?z\x18`\xa7L\x84\xf8?\x7fb\xe8\x1d\xe4\x97\xf1\xbf\xb3\x8f\xb2\x04\xef\x96\xc6?>o\x1a\xfey|\xec\xbf\xae\x84m2\xfd\xa2\xc6\xbfg\xdc\xe7\x11\x7f"\xdc?\xf9\x1a\xf3\xafmw\xf2?\n!c\x03}\xe7\xf5? ]\xde\x8a\x98H\xf0\xbf\xcf\xffi\x88\x95\xa1\xef?Y#\xd0i\x856\xe0\xbf\x01\xa4\x914\xda\xe2\xe8?\xa4|\xd9&J~\xfd?\xcc\xc9\xf0d>\xce\xe9?\xc3l\xc1g\xb40\xee?\xb4\xfb\xbdI\xfe>\xf3?\xea?=\xf9\xa3\x11\xc0?N`v\xb4\x93\x9c\xd8\xbfL5\xd0\xce\x88\xdd\x10\xc0\xac\x98A\xe4S\x90\xe6?\xa1io;uM\xe7?\x8b\xd8\x95X\x9bu\xfa?`\x9a^\\\xc3\xf7\xc6\xbfa[\x9a\xad\x12.\xd3?,G\x8de\xa9\xf8\xfa?VawM0\x04\xe5?`\x08a\xbeM\xd8\xfe?\x0f\x9fI\x07y\x08\xe8?5r\xd6;66\xf9?\xa1\r\xa5\x1a-B\xdd\xbf\x10N\x98\xaf\x80\xd7\xe5\xbf\xf0\xa6\xee\xd2I\xde\xcc\xbf9\x054\x18\xe6\xbe\xf4?\x8d\xd4\xec\xc77h\xc9\xbf\xf5\x1f\x1e_\xef$\xa8?\xef\xa4\xf0;\x04\xf3\xf1\xbfu\xec\xb3^R\x90\xf0\xbf\xfdK\x88\xd0\xfa\x14\x08@\x19\xddq\xc1P\xb5\xed?\x93\xa1\\\xd2\x17g\xb3?\xa6\xc2\x16\xeasp\xee?\x95Tn\xd2\xd0|\xef\xbf\xe8\xeb\xc9u\xf2\xb0\xe4?\xa8\xeb\xd0\xc0\xbd"\xfa?\x17b,bgR\xed\xbfE\xcfPz\xf8\xc1\xe1\xbf?\xffK\xf2\xba\x95\xd7?<\x89\xda|\x0f\xa5\xe7\xbf\x94\x87\x8e\xb8.L\xf2?\xdc\xdd,\xf7\xd4\xdd\xbe\xbf\xed\x9a\xc5M{F\xe0\xbfj\x98\xde\xe9V\xf7\xc2?\x17}\x19\xdf\xb0t\xc3?N\x9fV:\xf8\xf0\xed?\x03\x90\xae\x97\x12M\xef\xbf\xcd\x8e\x80\xb9\xa3\x1c\xf2?}\x19&8\xc6\xf8\x00@9|#\xf9\xa7\xc9\xf0\xbf\t\xdd<\xe1\xb5\x86\xf9\xbf\xb1\xdfv+\x08\xfe\xfe\xbf\xb6\\\xec\x92\x07h\xd4\xbfF%\x95\xe5K\xf3\xd5\xbf\x18L\x05\xb1\xf3\x8d\xf4\xbfwA\xfb\xb9\xb4\x82\xa4?\x81Bx\x9bYx\xf2?n\x80\x92lV\xa3\xe1\xbfQ\x97\xac\xc4\x98&\xe7\xbf\xea}rg\xd3\xee\xe4\xbf\xb2\x91\xd84\xa2,\xfa\xbf\xb8\xf0X\x03Oi\xbf?\x8e/\x8f\xe7\x00\xf8\xe6?rt\xf6^\xe4\x02\xfd\xbfk\x9d\x9b\x7f?\x95\xd4\xbf\x1aw=\xa9ze\xe5\xbf\x92\xf5\xce\\C\xbe\xd5\xbfW\xafA\x9d\x8d_\xf9?\x028\xc8\xe6\xaa\x08\xc5?b/,M\xdc\xc7\x86\xbf\x1at?j\xa0\xaa\xf4\xbf)\xa2\xb3,r\xaa\xf1\xbf\xd9\xd3#\xae\xdd\xb7\xe1?\xcd"eF7x\xf0\xbfY\x84\xd2\x04\x8e\xb2\xea\xbf\x08\x16\x16\xe3\x0e\x04\x01@3\xd1\xeaA~\xf5\xf3?I\x81\x01\x84\xc3\xf2\xf0?\xfc|\xc2\x90:\xa4\xb8?\xa6B\x1d\xfa\xbf\xeb\xd4\xbf\xeazH\xa4W\xbc\xe6\xbf\xd0\x16DZ\x89S\xa9?\xe4\x8e\xcb\xea\x13\x10\x99?k\x9c\xdc\x10"T\xea\xbfV3D\xc2\xc6\xdb\xf2\xbf\x11\x17\x0cL\xb3\xff\xf2\xbf\x19\x94\xc0b\x0c\x87\xe8\xbfY\xa0%lm\xfc\xd8\xbfp\xb9(\xd6\x17.\x02@b\xaa\xa3\xd4\x1c5\x00@lC\xd4\x04QA\xef?\xca\x85\x00f\xf51\xef?T\x0b^!Gc\xf8?G\xaa\x08/G\x88\xe7\xbf\x0f\x7fK\xff\x93\xfb\xc9?2\xd3\x02\xf4[\xd4\xd3\xbf>\xed\xa07\x8c\xcd\xfa?\x12}\xc8@\xfa\xd5\xfa?\xcc\'\xa1C\x90WX?$\x03\x8c\xab\xdf\xe0\xe6\xbf\x83\xe7N\xe4\x93\x14\xfd\xbf<~;\xf3\xb0K\x01@\x93\xed\xbe\xdb\x16\x90\xe8\xbf\x1e\xed\x0c;>\'\xed\xbf\x04\x1dr\x03\xcc\xc4\xe9?\x18)\xa8I\xa74\xef?w\x83\x84\x1dgP\xc5\xbfc;v\xfd\x9e\xd4\xd2?g\xe4mq[\x8e\xf0?\xf1Di\xa0\xd6J\xe6?\xbd\x9c\xb2\x81\x8b\xeb\xa0?\xb2\xc5^\xc2Dw\xce\xbf\x0b\xdb\xc8\xb6\xeb\r\x03\xc0\xd9Kr\xc19\x8a\xf9\xbf|\x95\x1c{\x1d\xc9\xfa\xbfG3\xff$\xc5\xbc\xf8\xbf\x8b\xaf\xba\xf7\xf5\xe8\xff?4\x93\xe4\x10?+\xd8\xbf\xa5e:W\x93\xa5\xc5?\xd0D\xa8BJ_\x98?x\xe7\xeb\x86\x18R\xf0\xbfk\xd7\x0e\x9f\xcf\xcd\xdb?\x9a\x83\xceM\xc7-\xf3\xbfl\x7f\xd3$)\x83\x02\xc0Q\x1a\xde]q\x07\xad\xbfw\x00=\xc4\x838\xf8\xbfkpo/\n\xbc\xd3\xbf&\xc5\x85\x128\x18\xd3?u\x19[\t\xa13\xf7?E\x80Oi\xd3[\xf2\xbf\xa7\xbb\x8d\xcc\x1c\x1d\xd9??B71T\x9d\xdd\xbf(\x84\x9b\xef:\xa1\xce\xbf\'?\xb9\xaf\xf7\x8e\xd4\xbf)\xb6\xf6zgb\xfd\xbf\xbe\xbfk\x13+\xd9\xdb\xbfe\xb6]\xab\xe6\x92\xd9?\x07\xc4\xbe6Y)\xd8?\x1c>9\x80\xaf\'\xd4?b2J\xec\xe9&\xed\xbf\xe4\x99\xb0$~\x9d\xd5?M\xee\x84\x9dC\x80\xda\xbf\xed=\x1d\x01\xd9\xe5\xce\xbf\x0e:\x1a\x82\x1b\x86\xd4\xbf\xd7=\xde\tz\xff\xc7?U]\x8c\x1e \xc1\xe5\xbf\xdb1\xcc\xf3\xdfu\xd5?\x86\x84\xa8\x1a/d\xbc\xbf\xa33-\xb0\x90\xaa\xe3\xbf7\xcfb\x85e1\xf2\xbf\xf0\xca\xbd$?\xaa\xf6\xbf8\x03\x0eG\x05\x13\xe8\xbf\xb6\xf0\xe7\x98m\xde\xfd\xbf\xcf\x10WY\xf8\xec\xf7\xbf\xb4\x97e\xa8Kb\xf5\xbf?I=\xb4~\xc4\x00@\xb7\x1e\xa5\x12\xd1\x02\xdc\xbfff\xbcQ\xccO\xe2\xbf0\x8e\xa9\x04D\xe7\xdd\xbf\\\x8b\xd7p\x856\xf3?f\x80u\x00\n~\xfb\xbf\x83\xe2\x1c\xd3S\xfe\xf0\xbf\x94"\x97\xa4\x8e;\xff\xbf8K\xc4\xeb\xd3S\xde?\x91\xe1\xe3\xff\xc9R\xf7?\x9c\xba]\x13"\xe7\xd1?\x9b\x16`\xe3\xb4c\x01@\x98\xc1rK\xc2|\x9a?\x9f\xb6\x99I\xbd\xc8\xdc\xbfA\xc0\x98\x92.|\xe2\xbf),\x8b\xca$\xd7\xe5\xbf8\xe2\xba\xab\x13\xc9\xea\xbf\xd7\x1eM\xc8 \n\xf1\xbf\xad\xfd!w I\xea\xbf\x91rH\x99\x12\xa2\xe5\xbfg\x820\x8c\xeb\xc8\xec?\xb0f S\xe8\x98\xf3\xbf\xf4\x93\x15Y\xa3\xf7\xc5\xbf\xe2\xa3\xa4\x9f\xbb$\x01\xc03\x04&\x0f7\x1e\xf6\xbf\xe8\xddN\xba\xe3\xb4\xf1\xbf\x00`\x01-5\xe0\xd2\xbf\x1a\xa9\x16\x04b\x94\xd9\xbf\xa5\xf7\xbf\xee`e\xf6\xbf\r\xd3\xbb2~\x8a\xef?\x0f\xd7\xd9\x9c\xe4\xd3\xec?\xe3\x7f\x0e\xc1\x0bD\xf5\xbfg^\x15-\x81w\xf4\xbfl\xea$\x9e\xf7\xc8\xf7\xbf\xc5p\xc2\xe8\x85\xde\xf3\xbf\xf4!\xeaN\xc0\xeb\xdd\xbf\xfcGO\x96.*\xd4?A\x8f\xf6\x12\x1d\x1e\xe3\xbf\xb0\xd7\x1e\xc0os\xd6?\xef\ntp\xe5\xee\xc9\xbf\xe7E\xb84\x98\x10\xf6?\x0f\x86\x0f\x8f\xb2\x91\xe3?\xc1\x07\xc2\xc2H\xbf\xde?\xf55\x01\x92\x17K\xe8?\x8b[\x02\x1c\xb6[\xdb\xbf\xb1T\xdb\x009y\xca?\xe9\xc9\x97\x0e\x01\xf5\xfc\xbf<\xc2\xe7\t\x0e\xd7\xea?\x86.Ed\x10Q\xec\xbf?\x15\x86f\x18\x9a\x07\xc0\xde4\x9c\x1b$\xeb\xf8\xbfLX\x0c\xdd\x8b\x07\xa9\xbf\xae\xd9H\xf3\x8d\x0c\x05\xc05`\xc1\x00\x0c\xc0\x02\xc0\xd4&\xc0\x8a.\xcc\xc1\xbf\xc4S\xb9\xfcN\xea\xd8?<\xd4\xf9r\x07\xf0\xeb?bF\xc7u{\xf4\xd1?W\xabU\xf8\xf7E\xe2?:#\n<\xb6\x1b\xeb\xbfhK\xc7\x00\x8c\xc0\xe0?\x13\xe6\xa6\x13$\x8c\xf1\xbf\xff\x15NDA~\xec\xbf\xe2\x82,\x9b\xaf\x01\xd0\xbf\x90\xe1\x99\x87\x0f\xcb\xf7\xbf\xed\xfa\xa1\xfa\x13\xae\x01\xc0]5\rJ\x04D\xa5?\x8b[eR\xa8\xb5\x84\xbf#{\x033\xd4\xa0\xb6\xbf4\x88\xa2\x0bg\xbd\xf9?U\xeb\x01c\xf9\xe0\xd4\xbf@7tmXK\x04@"6\xc9\xf1I\xfe\xdc\xbf\xb4\x04\xb8\xf3\x9d\x92\xed\xbf\r\x88v>\xb3/\xba?\x86\xf0\xca\xc8q8\xae\xbf%\x80\xa6\xc0\t^\x02\xc0!\x05\x0fm\xc6\x8a\xf6\xbf\x05\xd4\x9a`R!\xc4?\xdf5\x9d\x85\x84l\xf4\xbf\xbf\xbd\xe5L\x15\xc1\xdf?q\xc4o\x1cpk\xed\xbf>\xebP\x8e\x92\xd2\xa9?e1\xa5\xe0\x19\xad\xca?\xe1l\xe6}\xb8\x0c\xd1?\xd1e/tK\xc2\xf0\xbf\x8e}$\xac6\xa1\xda?d\x18\x84\xa6\xd4\\\xc0\xbf\x1f\x9bz\xf3\xe4\x96\xac?\xeb9\xbe\x1c\xc8\x82\xee\xbf\x8b\x18\xbau<\xbc\xd7?\x95_v\x1d\x83\xd3\xbc\xbf\xf6\xe6%+\x88\x07\xb0?\xb0\x92)\xa6S\xea\xbb?\xa3i\xa7\r\xb0\xa0\xeb?!\xbd\xb7\x00\xe8\x85\xd4?1\xed\x9e\xb7p\xb0\xb0\xbf)W"\xa7NR\xf1?\xd6\xbb~m\xc8\xa4\xe2\xbf\xb7h\xe9\xe9\x88\x9e\xc6\xbf]\xca\xfbp4w\xb6?\x179\xe4\x00%r\xd6\xbf\x84\xa9\x9f\x8d\xde\x03\xf1?\xd2\xd9\xf0\xb2#\xd1\xb9? \x0eu*\xd2\xba\x02@j!q(\x84\xc7\xf1?\x8f.]\xd1({\xba?\x1a\';{W\xe4\xd0?\xbb\xe4\xaf*\xa7\xd8\xec?y\x92\xb6\xdf\t\xd4\xf5??\xa6\xa9\xa0 F\xc6?o\xe1\xfe\xd7\x95%\xf7?\xc4\xc5\xbe\xd6\xa4\xd1\xba\xbf\x89\xe2\x7f\xd3\xaa\xcc\xf7\xbf9\x06]\xe5\xd4\x9d\xd0\xbf\x12\xcdM\xf9\x9a5\xf2\xbf\xbcL\x94"\xca\x89\xb1?\x9b\x95\xdaV\xbc\xea\xb4?W\x9e\xdf\xc6\xa1\xf6\xbb\xbfV_i\x1c\xd4\x8d\xc7?\xb3UtlTM\xdc\xbf:9\x84\xb2Cb\xf1?\xee\xd2\x01I1\x8e\xe2?f\x95|A\'\xe0\xcd\xbf\x0b \x02\x03"\xc3\xfa\xbf\x83\xa3\xefc\x80P\xe8?D\xc7\xb3\x7f\xcc\x15\xec?\xc4\xfewr\x05T\xe0\xbf\xc6Y\x94\xab \xe2\xe7?\x91\xaa\xaa\xa5\xa7\xd2\xe6?\x99\x84\xfe\x92X\xf8\xb2?\xefn\x92\x1b\x835\xe0\xbfhP\xe0/\xc57\xf3?U\xa2\xa0V0:\xf3?\xe1A \xb1.\xa1\x03@^\x1d\xd0P\xf0\x1e\xe3?f\xa2\xeck\xbf\xe6\x04@\x1f\xce\x12t\xcb\xfb\xf8?R\xcb\x9c)\xa1>\xec?\x87\xab\x10\x8d*\xe0\xe4\xbf\x1c\xc6\xc3\xcf\xa1\x87\xc0\xbf\xe1\xeay\x15\xa4\xd9\xe8\xbf\x0fe:\xf2\xd7\xff\xf2?\x1e5\x1fu\x00\xdb\xd3?\x1edn\x8f\xb6]\xe7\xbf\xff\x91T\xef{\x12\xe9?\xdb@\x84\x7f\x90}\xf1\xbf\x0f)\xe5\x10\xe8\xb2\xff?\xee*F\x84\xdc\x1a\xd8\xbf\x9c\xb7l>\xcb,\xcc?\x80\x9bX6\xe7\x89\xd1?\x0b\x8f\xb0k\x9d\x0f\xef\xbf\x04de\x0f^\x05\xc7?\x1e\xbeyVc\xdd\xea\xbf\xbf\xc1\x9f\xb9W\xe5\xe4\xbf\x1e\xdfy\xb2\xd0\x08\xd9?\xcf\xfb\xe0\x04\xa5\xfc\xd1?\xf9\xbf\xe0\xdae&\xaf?#\x90\xdb\xc1\xad\x02\xe8?x:<\xe6JV\xd9\xbf\xbc\x1a0\x1bi\xcf\xd6\xbf\xc1}\t\xdd\x9e\xd6\xf1?"\x07\x00m\xb5\xa4\xa0?\x86J[\xc2\xf9\x98\xf4?F|\xa0\x83J#\x8c?\x82M&\xfdc\xb8\xe8\xbf7l\xe3\x1eH(\xf2?~\'V\xa3\x97\xac\xc0\xbfi;\x97*\xb8\xca\xc8?!\xf6E\xef\t\xb1\xed?(\xcdz=`\x9e\xe4\xbf\xfa\x05\'H2\x1a\xf0\xbf\xfd\x98H\x87\xa9D\x03@\xc2\xf9i\x1f/\xa5\xe1?n\xcf*\xf321\xf3\xbf\xe4\xb2\xdd\xb5h\xb5\xf4\xbf\x15\xc0\xe9$\xe6\xd7\xd2\xbf\xe1\xf3\xae\xd32\xad\xf9?\xeb\x1a\x13\xef\xcf5\xc0\xbfD\x0b\x00\x15\x07\x8e\xe0?\xac+\xab6m\x89\xdf\xbf]|\xdcg\xceC\xac\xbf\x08O\x18\x85\xa2\xb0\xf9?\xde\xcc\x18&\xf7\xe4\xe6\xbf\xaao$\xe9\x1bt\xc5\xbf\x1aou\xacE\x0c\xf0\xbfX\xe7\xf3\xda\x1d\xca\xe6?\xab\xad!^\xfc\\\xc6\xbf\x88%\x84\xec\xe9\x08\xfb?\xff\n5\xd8\xbf\xca\xcf\xbf\x89G\x18\xd4\x9bg\xb7\xbf\xc4\x1bz\xbe\xadD\xe5?rWK,Vl\xf9?1\xe3~9~\x91\xed?\xed\xc0f\x03\xf3\xa9\xf4?\x03\x14g\x9bU/\xde?{\x93\xbbA>\xd8\xde?\xea\x14\xf9\x96\xafd\x00\xc0\x83\xf5\x07\xbc9(\xf4?\x83=\xf4\xc1P\x01\xd3?\xa4\x17\x12\xa0y>\xf3\xbf\x7f\xdf|\xde\x83h\xf2\xbf\xf9:e\xe4\xe3r\xeb?\xec\'\xf5m\x85;\xe3\xbf\x1c2h\x8b\xdfR\xe4?8\xddd\xd7\x1e\x80\xd6\xbf$3\x17\xa4\xf7\x0f\xd2?:\x85\xc4\x07\x98a\xd1?\'\xa4V\x8a#\xff\xdb\xbf\xc1)!\xf7\x02\xaf\xea?\x0b\xcf\x04\x88;\xa9\xf2?U\x12\x97\xe8\xfa\xbc\xd5?f.@h\x15\x13\xee?\x8eZ\xdc"\x880\xa9?[\xfb\x11\xb4\xa8\x84\xf7\xbf\x9a\t\xe4*\xe8v\xcc?t\xd1TN\xd9a\xd8?\xacj\xff\xd7\x98M\xf4?\xc5r-"M\xf4\x07@;&&\xdb\xec\x1e\xf4?x\xffx$\x11\xb6\xd8?\xff\x88\xeb+\xea\xbf\xe1\xbf3\xcd\xaa?\xd3\xe4\x84?PV\x97\t@\xd6\xfe\xbf\xa2\xe4 &\xa7\xc4\xe6\xbf#\xab\x1c\xf5\x1e\xd2\x01\xc0\x95\x9b\x9e\xfa\xbc\x0e\xfe\xbf\xa2\xee\xaa\xe7\x9b\xf0\xd2?\x02,z\xe9\x9a\x8b\xf9?W\x96\xc8|\x87h\xd0\xbf\x04I\xed\xf4\xe7\xc0\xd3?\x10|\x12\xadk\xb8\xcb?\x0f\x14&c\xe3\xde\xf2\xbf\xd6\xd0\xe1V\xedP\xcc\xbfO\xde\xe5\x7f\x10\x87\xe6?^\x8f\r\xb5\x8d\xba\xd9?\x17*\xfd\xedzn\xfe?\x96\r|\xe2\xe3\xc6\xee\xbf\xde\x9a\xb2\x03X\xb0\xfb\xbf\x1b\xb0\x04\x84\xaa\x8f\xe4\xbf\xb9\xe5\xbe1\xf7\xca\xfa\xbf\xdb\x02c\x81\xe3\x99\xcb?\xf4u\xb6!U[\xce?\xa4x\x88#\xea,\xdd?5W\x07\xb8S\xf4\xec\xbf\x8d\xc9Q+\x9b\t\xe9?\x8ac0\x01G\xc2\xd5\xbf\xcd\xa2\x91\xa6\x97\xd5\xf7?[y)\xd8A%\xfa?,*T\x0b\x1b\n\xf1?\x88\xe8l\x81b9\xf9\xbf\rOi\x14\x8er\xf2\xbf\xe3\xb5\xfc\x17\xbb\x9c\xf1?\xe6\xd6b\xc2P\xb0\xdb\xbf\xca\x00\xd0\xb3|\xf2\xf6?\xa3\xca}w\xb8r\xc0\xbff\xa3\xca\xc76\x9a\xe9\xbf\xbc\xaa6\xfe#X\xd4?\xa9\x07\xb4\xb7\xa8\t\xda?*\x98\x18\x92\x9b\n\xe8?\xbf%\x0b\x87\xb4z\xd4\xbf3\x89\x8e\x1fk\x18\xfc?\xce\xaf\xac!\x9ae\xd8\xbf\xe9\xfc\x97\x85\xbeM\xb1?\xa5\xaf\xa1\r\xb00\xd6?\x1e\xdb\x9b\x90\xfb\xe5\xc3\xbf\xd0\x98\xb9\xae&\xbc\xe1\xbf\x1c\xd6;\xf8\xd3l\xa7\xbfk!\x90vNb\xf4?\x0f\x12\xc4\x14\xcd\xe1\xfa\xbf!\xe2\xa0b2~\x05\xc0\xb2i\xac\x9e-\xac\xe0?s\xe2g\xed\x9a\x9e\xf7?\xdd&\xf4\xfcs^\xf4?\xdbY(>\x13\xa0\xf6?\x0c\xa4\xfe \x00\xd2\xa8\xbf4ng\x03\xa2\t\xd8?\xab\x1dGr\xb1\x04\xd8?<\x07\xab\x90\x8dr\xed?\xa9\xba\xf6\xd2\xb5f\xf1\xbf\xf3\x93xQ\x8d\x00\xd7?-kg\xe2\xae\x99\xc2\xbf\xe2\xc1\xe5X\xa8u\x00@\xfa\x9e\xa8\x10\xafy\xcf\xbf\x97\xee}!n\x8b\xe9\xbf\xd9\x15\x13D\x9ad\xd7?\xeb\xfd\x91\xf4\x9a\xdb\xc0?f\xaezH\xf04\xe8\xbf\xb5\xf8\x1d\x0f\xf8\xa1\x90?y\xd7\xc0\x99N\xb3\xf2?y\xcf\x977\\\x84\xb6\xbfJ\xb6"\xf2\xde\x89\x04@\xce\xedX\xff9o\xe3?be0\xf6%f\xfc\xbf\xd8\xe7\x93\xd9\xd7\xcd\xe3\xbfU\x16\xe0\xc7^!\xf5?\xc7F\xe1%\x88\xbc\xd1? (\xb0\xe7"h\xba\xbf\x84"P\r\x93\x89\xf3?\xcdae\xff\xb5\xfe\xe4\xbf\xc8\xe6\xeb"\x9d\xa1\xb3\xbf\x1a\xe1\r\xdc\x96^\xd5?\xee/\xb0\xa5=\x0e\xee?F\xceM\x98\xb8\x10\xf8?\xaat\x0c\x97\xc4e\xcc?\x82O\xe58\xda\x83\xe9\xbf\xc5\xc5\xdfs\xb3\xab\xd2?/\x88\x8e/V\x0b\xe4??\xc4\xd4i\xab\xef\xf8?I_\xf2\x10{\x04\xe3?L:&\x15\xf3\x17\xf1?\x9f%f\x07\xb5\xb0\xe5\xbf\x8e\xfd&\x80]M\xf0?o]\x11\xb8"\x07\xf5?)D\xa2\xbf\xbd\xd2\xf0\xbfw\xb0\x8f\xf0\xe0\x18\xef\xbf\xce\xd0\x81\x7f\xf7\x91\xd0?2\xa0q\x175\xc8\xfb\xbf\x00\xb5^z\xcbb\x07@:\x02\x7f}h\x9e\xe2\xbf\xfc\xb7\x8a\xd8\xf8\xe6\xde?~\xc4\x05kB$\xf8?\x12\xd6\xf5\xb4/\xfb\xe1\xbf\xab\xca\x0e\xea\xeb&\xd5?\x04P=\x10\x1b\xdc\xc4\xbf\xa6\x90j\x86L\x16\xd0\xbf\x1az\xb8\xcbSH\xc6?\xd8X\xde3\n\x18\xf7\xbfq\x80\xcd\x96\x1a\xa3\xc0\xbfcPI\xab\x93\xc7\xe2\xbfF\xed.AG;\xea\xbf\xb3\xfc\x8e\xdc{\xd2\xd7?c\xa6\xe0g\xa4\x02\xe4\xbf7\xfd\x8b\xc0\xe6\xd3\xc4\xbfi\xf4\xe2\x1c\x8f\xf0\xe7\xbf\xbcOL{t\xf3\xe1\xbf9\x9dp\xe9H\xd0\xf4?\x15w\xb6D\xef\xd9\xee\xbf\xd6K\x96\x10\x19\x9c\xc6\x96\xec?W\xd3\xc9\xb0C~\xe9?W\x81\xfe\x05\xac:\xe6?\xc9\xf6\x8a\xe7Y\xa0\xdc\xbf\x00L\x93\x17a\xda\xf7\xbf\xbb\xd0\x1e\xe1\xa8\xb3\xed\xbf\xc30\x1al\x8b\xe0\xf0\xbfox\xfb,Z\xf3\xf1\xbf\xa9\x07o\xad\xebY\xee\xbf\xb7@\xcb\xeb\xa5T\xb0?\xff\xd3i\xc1\x1f\xea\xe9?_c\xd2\xeb\xc7\x8d\xc0?\x84\xb4"X0\x16\xf7?\'?\xb8D\x06P\x01\xc0\x0f\xa9\xc6\xb8\x95x\xf9\xbfa`l\x01\xa1c\xd9\xbf\xd3\x0b\xf6\xfb\x90t\xd1?\x07\x05\xfdj\x84\xd2\xd1?\x82\xfb\x16h\xd9\x07\xd4\xbf\xb7\x06\x06\xc5\xb3\x8c\xee?\xc0\xb55\xa1\x19\xe8\xf1?\xc2\x88\x96\xaait\xd5?\xe7\xaf@\x9dz\x11\xf6?D \x888m\x9c\xf3\xbf\x04*\xce\x16Z\xd7\xce?\xcf\x16\xacE\x0c\x83\xf8\xbf\xb0LP\x12\xa2\x12\xf5?\xc8P^\x8d\x9e\x16\xcb\xbf\x9bI,`\xff{\xf5?a\x11!\xdc1(\xe4\xbf\xf2\xee\xaf\x12\xe0\x17\xdf?\xa1\x18\x82\xc1\xef\x90\xdb?\xe0\xd0\xa0\xb5g\xb2\xd6\xbf_\x11\xe6q\x1e:\xf0\xbf\'\xb4\xc3\x97\xab-\x01\xc09\xcc\xa4\xb8I\xc7\xf7?Vy\xe5\x86A\xfc\xe3\xbf\xfe\xe6\xc5\xc8\xff\x06q\xbf,\xf4*\xb5\x12\x11\x01@\x8d:\x8ai\xc9\x8b\xbe\xbfSIn\xb4\xef\xc1\xf5\xbfxH`\t\x01R\xea\xbfd\x89\xb5@\x91h\xfb\xbf\xa5\x04j\xae\xd0\xe7\x08\xc0S2\xbeG\xac"\xf2?\xf5\xda\x1c\xea\'\n\xcd\xbf\x83\xc3\xd3v\xf8l\xef?{\xa4\xd9|L\x93\xf0?\x18\xe7\x9bM\xa6c\xf4?R\x10\xc7k\x94L\xfd?\xb3\xc8o\xc2\xb4\xac\xc6?\x81\xe5\xf5et\x90\xea\xbf}\x19\x1e\x1cn\xae\xd9?4+\xbc\xe1C\xc8\xa1\xbf\xb4(\xd0o\xd0\x98\xdc?\xf30Ts\x058\xfd?\'\xf5^\x9crU\xf1?v\xa8\xe9\xb0\xda\xff?$c\x9dF\xef\x05\xdd\xbfI\xab\x93\x0f\x02l\xcf?\xe7k\x83?\x9d\xc9\xce?\xb4\xab\xa81\xea\xbf\xee\xbf\xcdDu\x01\xef\xf7\xce?>B\x06\xe9\xb5\xeb\xfa\xbf\xd4\xcbI\x94\x17a\xef\xbf\xf6}k\x9d\xbaq\xe2\xbf\xb3Z\x7f\x07^\x03\xe0?gW\x05\x02y\x8f\xda?\x14va\xb4I\xa9\xd4\xbf\xe2\xc2^\x90\x84\xd9\x10\xc0\x15\xf4n\xfe\x86\xb7\r\xc0\xf4\xca\xb9U\xb1\x1d\xfa\xbf\xd9\x8e\xa4\xd4D\x1a\x06@8\xd8\x1cd\x16\x8d\xef?\xa5S\xa9\xce:*\xc8\xbf3\x9eW\xe5\xd7\x06\xed?\xce0:\x82\x86\x92\xdd\xbf\xa8\x1e\xe2\x8d>w\xed\xbf\x88\x1f\xa6\xdc\xb1\x15\xc4?D\xe0\x1c\xef\xb6\x00\xf3\xbf\xd9i5\x96\xfb \xf3?\xdd\x90Z`\xf35\xc4?\x84{C\xc2\xec\xa4\xed?\xe0\xcd\xf2\x01\xfb5\xda?\xc1\x7f\xc3\xb1\x0b\x99\xe1?\xa4\xfc}\xe0\x98E\xc4\xbfkje!#H\xf7?\xf0\x1b\x8f\x80k\xd0\xc2?D4\xa4\x14$\xe5\xf4\xbfiT\xc8\xef\xd4)\xfd\xbf\x81\x033\xda.z\xf3?\x1b\xb5\xda\xfd\x8aN\xeb\xbfM\xcd\xd8~zw\xd5\xbf\x7f\x84e\xd8\x80\x11\xe9\xbf\xfc\n\xda\xcc\x13+\xf1?\x8d\x9c\x913\xee\xd9\xf1?\xbe\x00d\xd2Y\xf7\xef\xbf(\xb2X"1\x17\n\xc0\xa8u)\x14\x98\x80p\xfc\x01\xc0\x91{\xf3\x19\x84\xa3\x07\xc0\xda\x82)\xf3\xe9\xe0\xf9?\x02&t\x8f$\xc4\xe0?\xb6\xadwD0\xb4\xd2\xbfD\xac\xf6R(\xea\xea?\xf8\xd7\x86\xade\xca\xe1?\x03;\xadt\x8f\xae\xf2\xbf\xa3r\x98\x98\x90\x9c\xc5?5\xbc\xf2\x1f\x1a\x99\xf0?\xba\xbf\x99x\xdb\'\xe0\xbf\xbb\x95\x89\xe8\t\xdb\xe3\xbfW\xee\x144\xa2\r\xdb?ZT4M\xa0t\xf3\xbf\xcb\xb6\x07-\x95O\xfa\xbfj\x00,k)`\xf4\xbf\xb6\xe8&\x11\xc7\xca\xe2?\xaboe\xbd\xb5\xbf\xec\xbf\xaaS\x12R\xf7\x0b\xe6?\x1b)%\xd3\x94Z\n\xc09\x95>\t\xa8m\xf3\xbf#\xfa\xbe+\xbb=\xef\xbf\x89\xe9<\xb8\x14.\x98?\xbcA\x0e\x17\xee\xb9\x01@e\xeb\x85\xcc\xdf@\xdd\xbf\xac\xb0\x07w\\\x18\xf0\xbf\x1d\x93\x9a\xe3\x7fl\xe4\xbf\xbd\x10uU\x0c\xf4\xf2\xbf}x(\xa9pi\xf4\xbfi\xbaNc\x02l\xf1\xbf\x0b\xfc5\x11\x05\xf2\xd6\xbf\x0b2\xd1X\xae#\xea?|\xc8\x00\x1a\xce\x82\xe9?[\x85\x99&\x0b\xcb\xf1?l\xb1\x1f\xc8\xe3P\x7f\xbf\x01dK1\x99\xdb\xe8\xbflH\xb1|\x9d\'\xc4?\xd6Y\x13f\x06\xd9\xfc\xbf\x1a\xc8=e=\xa0\xea\xbfO\x16z\xdb\x91W\x06\xc0Q\x94H\x14\x83C)\xf6?\x83i\x02S=\xa8\xc3?\x84\r:.\xcez\xeb?(\xe3\xf0uj{\xee\xbf%\x04\x07\x94\xfbf\xe5\xbf\xc2\x08-\x0e\xb9\xbe\x03\xc0\x92\xe2\xb4P\xb2\xbc\xe8\xbf\xcc\x17l\xdc\x7f\xdc\xf3\xbf\xc17\xca\xcf\xb68\xe3?M~\x14>\xbb\xdd\xea?5\'\xc5r\x19\x99\xf4\xbfi?M\xb7\x9c.\xd1\xbf;\xe8j^$\xb8\xe1\xbfn\xd6\xbc\xd2o8\xee\xbf@\xc1\xb2\x9c\x93\x9c\xb2?\xf5\x87\x9d\x1e\x1a5\xe7\xbf\xda\xde\x9c\xb4g,\xe0\xbfZ\xd4>\x00+>\xd1?\x91K[\x05\xba\xf7\xf0\xbf\x10\x04\x88\xa0E\x02\xf7?\xc5\xb5@y4\x00\xd1\xbf\xae\x06\xe56E\xb1\xec?\xed"\xfe\x05M\x10\xe0\xbfK\x9b\xa5\x10*\xd0\xeb?\xc4\xd76\xa7\xa3\xea\xe6\xbf\xe6\x97,"\xfb\x9b\xf2\xbf\xc2F\xac\x81\xb6\x9d\xd0\xbfa>\x03wg\x1e\xf3?\xba}t\x17k(\xe5\xbf\xddf\xb5\xe4\'*\x9c?p\x8b]e\x9f\x16\xd8\xbf\xaf\x9b\xda\x12\xa9t\x08\xc0\xf7\xdd\xfdmF)\xe0\xbf\x1b\xe7k\x0fm6\x01\xc0\xe2\xeaf\xdb\xf2\xf6\xc2?o\xd3\x91m \xf8\xfb?\xd5\xd2\x0e\x8f\xce\xf5\xfa\xbf\xcd+\xab]w\xd7\xf3\xbf\xcf\xe3D\xca9-\xfc\xbf\xff1S\xe3;c\xe0\xbfj\xc5\xdc\xfef\x86\xf7\xbf\x8bbBB\x9al\xf0\xbf\x7f\x1exb\x9db\x02\xc0;y\xde#\x10\xbe\xdb\xbf\x1c\xfa:\x9b\xdd\x83\xed\xbf\xa1\xe1L\xdc\xa9#\xd6\xbf\xa6\xcbDj\xcaM\xef?A\x00\\\x12\x1a\x91\xf6\xbf-S\xbc\xb6\x9cR\xe6\xbf\xb5\x08X\xc7U_\xd2\xbf\xab\xfb\xc6\x19\x9b\xe4\xee\xbf@\xd5\xe1!\x7fZ\x03@~\n\x0e\xd5\x9e\xad\xb9\xbf\xc8\xe6\x04\xa7R\xff\xfd\xbfHu\x84\x18\x93\x01\x82\xbf\xcb\xe2\xa1\x1c\xea\xb1\xd0?\x8ft,\x82\xfce\xf0?\xb35&\xd6\t\x16\xd8?\x89\x8ct/9[\xf1\xbf\xf1\n&*\xe4\xb9\xd4\xbf\'CQm\xab\xc3\xc6?\x8e\x82\t>C\xef\xdb\xbf\x92|jf\x98\xa8\xd4\xbf\x06\xbeiJ\\\x98\xfe\xbf^\x9e\x8aYJ\xa4\xf3\xbf\x91\xcb\x12$\x95G\xc3\xbf\xceB\xcb\xe3Sw\xc3\xbf\x9c\x82\xcb<\x9d.\xe0?r\x18cM\x84U\xd6\xbf\xd1N\x92z\x08N\xda\xbf\xc6,B$og\xe3?\xf2>\xb7\xb9\xce\x8c\xb3\xbf\x05C\x9bO\xb6\x9a\xdf?\x94\x8d\xf2d3\xa0\xf0\xbf\xbfZ,\xce\xf4\x15\xfc\xbf\x01\x1c\xc0Q5T\xd6?\xc2\xba\xde1.\xb6\xce?\x91\x18\xc3\xd4\xf3\xe6\xe0\xbf\r(M\x93\xffP\xe5?\x00\xc6\x1c \x975\xf1?\xf0\x0co\xcb^6\xb6?\xd8\xb3\tKK\xc8\xec\xbf\xc6\xc1F\x81U\xdf\xfd\xbf\xc0\x92\xaf\xc0\x8f/\xe6\xbf_\x93\x93\x84]\x10\xdb?\x1d\xc5i\xa7<\xbf\xf2?/\xbf\x00W\x82V\xe8\xbfOk"\x98V\n\xe5\xbfl\xf9c,Y\x08\xf2?\x10\x9e\xd5\xf9\xf9\x93\xf4\xbf+\x93\x97\x98]\xce\x9d?\xa0\xa8\xa8v\x12x\xe8\xbfT[A&\xdd\x06\xdf?\xe0\xea\x93|R\xd3\xfe\xbf \xf8A\xf4\x97]\xcb?\x07zmM\xc0\x1d\xd6\xbf\xdc<$\x0f|?\xb8\xbf\xa7\x80\xcd\x1c\xbd\xc9\xf2\xbfJ\xec\t\xe55\x9a\xe1?\xb3\xcb\xc3\xf7ge\xde?\xb9\x92uL\xd8\xeb\xd0\xbf\x0bh!\xde\x97\x92\xe5\xbfYE\xf0\xb1Rk\xf7\xbfc6\xebJ\x19x\xd3?\xe4\xc5\xd2\xf3\xaa\x8b\xf3\xbfr\xc9<\x11=\xc5\xeb?a\xa4\xfe\xb4\x1e\xe8\xeb\xbf\xa0\xff\xa4\x8e5\xcb\xf7?\x05JP`\\\xeb\xf9\xbfk)\x868\x98\xd0\x97?Jh\x17Ze\xab\xf7\xbf\xa5\x988\x97\xd7e\xf5?\x0cn\x15\x11}<\xff?7\xacx\x80\x80\x01\xe5?\x15\xe7\xd6\xc01\xd1\xd9?\xfeSQ\xc8So\xeb\xbf\x8f4\xa5\x918\x93\xe4\xbf!\xf1\xc1\x1e\\\xec\xe0\xbf\xa8\xb2]N\xf6t\xd2?&\xa4q\xb8\xd1\x92\xd2\xbfX\xff\x03}p\x91\xe1\xbfPu\xc8\x9a\xee.\xd0?\xbdc_\xc0\x89\x08\xe9\xbf\xd1mdH\xcc\x89\xf9\xbfc\xe6\xd3\xfdw(\xe7?vWVv\xf5\x04\xe4\xbf,Gs_ n\xf7\xbf)\xf1e\xf1Y\x82\xf3\xbf?hfLvq\xc8\xbf\xf1\x8f\xa2\xd0-\xf4\xd8?\x02\xe0C\xdc\x85\xa3\xa4?\xfb`\x9b\xed\xf6\x8e\xe1\xbf@)o\xba~\x9a\xd9\xbf\xb0q\x0e{Ak\xf8?\x93\xce\x85cr\x9a\xf9?\xef\xc9\xda\xad^\t\xf2\xbf\xc8^\xa1\xb3^[\xe2?W\xad\xfc\xb1\x8e3\xd7?\x04,\xb3\x1f\x91\xe7\xe7\xbf\x93\x19w\xf4\xb4R\xbf?2\r\xb2\x95\xefX\xc3\xbfb\xa7\x87\x01w]\xd5\xbf\xca&\xd3o\xdfo\xe5?\xeb\xc5\xd7\xf1\x17\xbf\xc7?(\xc5wn\x15}\xc1\xbf?p\xf4`?\xec\xe6?\x94u\xdf\x16m\x8c\x00\xc0s\xe6\xa6\x9bZ\r\xe6\xbf{\x98/\xfa\xa1\xda\xda?\xfd\xce\xb0\xe8\x96\xdd\xf3\xbfLF\x80W\xa4\xc1\xba?\xf0!\x1f/@\xa6\xea?\xe8\xf9V\x81?a\xe9\xbf\xe3\xc0O5\x87\t\x00@\xfb\xa0Q\x04BE\xd8?!]\xf5Q\x1e\xf6\xf6\xbf\x1a4V\xaf\x8a\x8e\xca?\xc9\x86\x8e\r\xf4\x16\xc8?\xbf\xce\x80\x1f\xf6N\xf5?\xd7"\xb5\x80\x83X\xdc?\xea+\x87\x9a\xb0e\xf1?\xa5\x0e\xec\xb9@\xf9\xe9?\x13\x84U\x92\x96\xd3\xee\xbf\xc6\x17y8p\x98\xf3?w\x01\xbe\t\xb4\x17\xeb?\xbf\x06\xa1~#Q\xae\xbf~\xa8a\x93\xa65\xd6\xbf\xdf\xbe\x01\xc4\xc4\x8e\xe8\xbf0\x9d\x12\x8e\xa5\xf5\xfe?\x8d\xfe+{\xe97\xc2?z\xf9\x9d\xb3\xdb\x8b\xe2?\xfd:\xfb\xf3i\x9c\xfa?^\\p$R)\xd5?\x9c=&]\x9cg\xe2\xbfX\x90\x9f\xceb"\xf5\xbfkY\xae\xff\xcb,\xf3\xbf\xf7g\xcb|\xb8W\xf8\xbf\xc0\x0e\xd9\x13+\xce\xe4? R\x8eaD\xc8\xee\xbf9$\x18\x02!\x9e\xec\xbfN\x81\xf9\xe0{^\xdf\xbf&\x8f\xce7\xba\xfb\xee?\xa8*\xb5\xef\xfad\xde?!\xf6\xc0\x95\xcav\xc4\xbf\x0b\xdc\xb4\xc5\xd9\xf8\xf3?\x04\x18\xaf\xb8\x14\x88\xe6?#\x1bj\xd2\x82E\xe3\xbf\xd5B\xf6\x94\x0c\xa3\xef\xbf\x99\x9bD\xc4\xee^\xf1?=&\x96S\x91\xd3\xe4\xbfGJ\xce\xab]\xba\xd5?\xae\xeb\x7f\xd0\x1e\x16\xea?\x160\xb0\x97y\x05\xe5\xbf5\xf1y\x8e\xe9\x7f\xe5?J\rk\x89\xb8:\xb3\xbf\x125\xd3c\x01\xf0\xe6\xbf\x1f\x0f2yG\xf8\xdf\xbf\xe1\x8c\xc7\xef+\x01\xe5?\xe1\x1d\r\xda\x8b\x02\xee?\xe3\x9e\x87\x98D\x82\xa5?\x80V\xc8\x91\xa2\x84\x95\xbf\xf0\xe1]J;@z\xbf\xbe\xa8\xe7)\xd8\xeb\xe6\xbf\xc5\xe8Ob\xe3\xf7\xd6\xbf\xdb\xa0\x93\x03v\xd5\xc4?\xb8\x1f}|\x80\x89\xce\xbf\xb9\x89\xabmm\xc5\xe4\xbfu\xdf\x06\xccHz\xe1\xbfZ\xb7W\xe21\xb2\xb9?\x8c\xe4\x04\r\x9b\xb2\xea\xbf\xbaJX\xads\t\xe2?a\x19k!\x1d\x10\xf6?\x10\xac\x98\x95\x87\xbd\x96\xbf\xc8Jh+Yh\xee\xbf\xd6I\x9b\x9c_o\xde\xbf\x166u\xef\xa4z\xd0?\xbf\x93&\\-\xc4\x00\xc0\xd7\xb8\xb4\x07jo\xe5\xbf\x8d\x89\xed\x04\xa2s\xbd?\x8c\x8a\x81\xfbU\xbf\xe1?"\xca\xeea\x02!\xb6?\x05\xbb;H\xc2\n\xcf?\xc6\xf1#,\xdf\xd1\xf3\xbf\x0e\xf7\x9c\xb1\x9e2\xfa?\xb7L\x0e\xa4\xc5S\xce\xbf\xde\x07J"\x8aT\xd2?[\xec\x03U\xe9\xf6\xe2\xbf~y\xf2\xbf\x14\xc4\xf1?\x07\x1f\xc6\xf9a\'\xea?\x1dW\x88~{\xe2\xe9?\xcf\x9b0j/\xe4\xf8\xbf\xed\xa2#\xdf\x99\x95\xe6?\x9a\xde)\xf6\x8d\r\xf7?\xe3=\x85X"\x01\xc0?J\xd6x\x0c\x8b\xea\xd6?#t$+\xf7\t\xd9?\'\x92\xdf\x83\xce\xc0\xe6\xbf\xefM\x97\xda\xcfc\xed?\xc2\xf4\x1f\x8e0\x83\xf6?\xa5\xbf\xda\x1b\xe1j\xd3\xbf\x1c\xbe\x99*\x9e2\xcb\xbf\xa9{\x19^\xd5\xd2\xec?\xd0\x96R\xd1\xcei\xd7?\xefA\x85\xed\x96b\xcf\xbf\xb2\xd2\xe1B\xf7\x9d\xb7\xbf\x13\x80o\x00\xa7:\xf2?"\xff\x0b\x06\x95T\xe2\xbf/\x88\xb5\x02\x0c4\xef\xbf\rI\xf2\x9eb\xd9\xd7\xbfHz`\xcc\x00\xa2\xe2?|\x7fn\xe0\x13\xbf\xf9?\x90\xc9\xc7\xea\x03\xbb\xff\xbfm<\xe5dL\xbb\xc6\xbf}E\xb5@\xab\xfc\xf4\xbf\x17]\x01/\x06\x05\xf9?\x80\xc0\xa8;x\xd5\xf1\xbf\x1al*\xed\xbfe\xf0\xbf\xcd\x1a\x0e\xa9\xd7\xb8\xf4\xbfR\xd8E\xb4\xdc\x90\xe9?\x93\x19\x8a\xef\x9bl\xe7\xbf\xc7\xdf\xee\x0f\x9e\xb3\xf4\xbf\xe0~s\xa8\xb6\xf6\xe0?\xa2\xac\x8f\x82\n\xaa\x84\xbf\x99^\xf7\xae\xfe\x02\x92?\xa0\x17\x80\'\xaai\xed?N\x97C\x1c\x7f\xa1\xcb\xbf\xd2*\xd4\xb0r\xf9\x91\xbf\xda/\xf9\x17\x8d=\xda?\xf1\xd0\x0fY+h\xf3?\x82\xbeQ)\x9d\x12\xb8\xbf\xe2\xf8\xdc"\xaa[\xff\xbf:\x87$\xceb\xaa\xfa?\x03p~VR\x98\xd6?\xb3\xbb|\xde\xaa\xe1\xe6\xbf\x95\x08\xf9#j\xe5\xe6?\xdf~\xd3#\x1c\x87\xf6?\x95\xa3\xf3\xa3\xd7_\xa7\xbf\x90/iHY\xef\xf2\xbf\xdb\xc9a\x18>\x13\xf7\xbf\x10Y\x99\x19s\xd0\xd6?\x8f\xc6\xec\xd0\x80\xd5\xff\xbfF\xeat\xcc\x8a\xa0\xe3\xbf?A\xedm\x16\xeb\xe2\xbf`\x89\xac\xe6]\xa2\xf5?U~$\x89`\xf6\xdd\xbf\xe8?+<\xcb\xbc\xc9\xbf\xae\xa2\xe1\x06)\x89\xd0\xbfZ\xd3C\xc6(\xdd\x01\xc0\x9fr\x99X\xe9\x1f\xf4\xbf\x0b\xa9s \xe1D\x01\xc0\xa5\xaf\xda\xa1f\x07\xfd?\xaa\x08\x8eB@\xee\xfd\xbf\xdf\xba\x9bIY,\xf5?\t\xb5\x1b\x1f\xfd\x03\xd0\xbf2\xe5\xe4\xad\x1fm\xe2?R\xa3\x8c2\t\x03\xdf?\xef\x9a\x02\x02\xca\xe8\xb1\xbf|@\xe1\xf2\x9b\xd3\xd5?\xc8\xc7\xb7\xf1\xbf\x12\x83W\xa5R\xb8\xea?\x97\xdb\xb6\xd21\xfe\xed\xbf\xf4\x94\x07\x1a\x9f\xe8\xe5?!S\xc9\x07\xcd\x9b\xfc\xbf\xfc\xf5\xd6\xeb\xe2\xbe\xf3?\xe5[1NG\x89\xe0?\xaf\xe1\'\xd3\xd0\x1f\xae\xbf\xdb\xce~\xf1l/\xe5\xbftMbE\xc6r\xd0?\x9dU/\x8dP\x83\xf0?\x9e\xb3\x85\xc8S\x9a\xed\xbf\x8d\xafA\x8c\xfa\x8f\xde?\xab-\x05\xcb\x00\xb7\xe8?M\xa9YkkX\xdd\xbf\x93\xb4\xc5\x03\x15g\x06\xc0\x1c\x95\xa1\x1c\xd0\x8f\xf4?\x9a\x901nx\x1b\xe7\xbf\xe2\xf4\x04\xcdl\x81\x1e\x8a?Q\xbb\x96FE\xa8\xe9\xbf\xed\x9f\xec\xdc\xac\x02\xe7\xbf\xae\r\x82\xc8.N\xf4?\xc1\xdd\x0b\xa3}\xe9\xc1?\x13)\xf9t`\xec\xdb?\x80\xf9\x8f\xb32\xe7\xf4?\xea\n:\x04)\x01\xeb?\x91\x7f\xf8\x16\x17\xd4\xb9?\xd5y\xd5[\xedb\x95\xbf\x01O%<\xc7~\xd2?K\xd1\x11\xaf\xe9\xdf\xf3?\x02\x9emq\x89l\x05@p\xe3qt\x9e\xa4\xe2\xbfl\n\xe3:\xf9\x94\xe3\xbfm\x9e\x8d_wg\x01@\x03\xa7Lp\xb7\x8d\xd8\xbf\xd6\xee=\xee\xd6\xef\x9e?\xd0\xf8\xe3\xf7\xda4\xf1?O\x7f\x90\xff\xc9i\xf0\xbf\x0b\xfc(YF\xa7\xed\xbf\xfc\xcd\x89\n\x97(\xe7\xbfz\xaa\x8a\x08\xe0J\xdc\xbf\x03.\xbc\xdf4a\xfe?.35 \x90\x85\xf1\xbf\x16\n\xd6\x95\xb8\xcf\xd7?.\xb5\xfdN\x82R\xeb\xbf\xd0\x1bH\x1f\xee:\xeb\xbf\xdc\xech\xb8\x0eG\xe2\xbf\x08e\x7f\n\xf7\x02\xf1?\x8b;\x19\xaf#\xd9\n@\rh\xfb\xb8*U\xdc?b[\xb4\x05?\xd6\xd1?kj\xd2\xe2M\x80}\xbf\x9d?\xc1f\x8c"\xe6?sY\x16[\x97<\xfa\xbf_\xfb\xc2NI\xcf\xc5\xbf\xfc\xe5A}e\xd8\xe7?\xa7kv\xf0\x8c\xe0\xdc?>\x8a{\x8d%\xac\xe8?\x17S8W\x9a\xf6\xbc?N!\x7f\x84\xa4\x04\xe8\xbf\xbb\xd1\xe3\xd2\xf0\xa5\xf4?\x85\x07Ty\xa6\x87\xf5?qb#\xb1M\n\xeb?#\x0fA\x07\x8fd\xe0\xbf\xee\xe1{\xed\x8f\xa4\xe8\xbf?\x9aYG{\xb2\xe1?\x00\xb3=\x03o\xd8\xe6?\xfe\x16\xd2\xe1\xaef\xe6\xbf\x84a\x0b\xb0#\x86\xe0?P\xe0\xbd,)\xf7\xe5?\x95\x08\xdc?\x03\x96\xfc?\xc0\xc9\xb8\xf3+\xf7\xd0\xbf\x99x\xd4\x01\x17\x88\xf1?\xc9\xd2\xe5r8\xef\xf6\xbf\xd1\xeb\xf5,\x05t\xee\xbf\x90\xf2\x8a\xe7\xabe\xf2?jI\xe7\x125\xc7\xe2?\xa0.F\xe1\xe3_\x02@\xfeRHr\x7fi\xea\xbft\x9e\x7fJ\xaf\xfe\xb5?\xda\xf3\xa0q\xce\xa3\xc0?\xdb\xfb\xf3\xb5:\xe4\xce?\xf7\xb9\x81cv|\xec?\xd9\xe9\xf9\x1d]\xd9\xe7?\x9c/,\xcaF\xef\xf0?\xb6\xb4\x9e\x1b`\xec\xc6\xbf\xe6\xed\x1bx\xef\x86\xc1?\x19\xca\xace\x7f\xf6\xed?\xfb}v\x95\xff\xcd\xdb?\x19B\x05\xd8\xad/\xd2?\x18\xba\r`T\x05\xd1\xbf\xc0\x119+/\xfe\xfc?#,w\xcc\xb4\xcf\xe5?S\xf9\x06\xbd\xef_\xb5?Z\xd9\x14=\x117\xdb\xbf\x14\x858\xa2\x8c_\xf9\xbf\x14\x0e\xbc\xb8\x17\x05\xf2\xbfL2\xfeCt\x10\xe7?\xbal<9\xfcj\xc9\xbf\xbc\xe3\x08\xa0Z\x95z?\xc8\x03JN\x8a\x0c\xdd\xbfiK\x91\xa2@v\xe7?k\x82Ab\xbdd\xe1?\x94yQ\x9f\x1cZ\xc2\xbf\x14y\xc1d3\xbf\xd5\xbf\x03v1\xd2\x99\xbf\xf7\xbfa_\r\x9f\xd8k\xd5?,\xd0\x92x\x01g\xe9\xbf\xa2\xc8m\xbd\xc9\xce\xb8\xbf\x13\xd1\' \x94z\x04\xc0\xbaC\x01NH\xb8\xbb?\xab\r\x05\r\xf1R\xe6?\xa5\xd8\xd7\x9e^c\xe9?\xcc;\x8c\x19_\xb0\xde?\xb9q\x19)u\xfd\xf0?\x7f\xb6\xbf\xb6\xda\xdb\xd2\xbf~\xa4xux\xbf\xf2\xbf\xa8%\xa5(\x16\x89\xf2\xbf\x82\x8dE\x96%\xf0\xed?\xf7\x14\x8c9\x02t\xeb?{4\xd9\x11H\xd4\xf0?\xc6\xb6\xfdR\xa1\x87\xdb\xbf\xab-VD7A\xd1\xbf\x05\x1f\xdfmw\xf7\xe8?\xcc&\xcf\xc6\x17\x95\xe8?ge>\xc9\x8e\x1b\xe9\xbf\x85\xfcg\n\xa5\xff\xef?\x7f@*J\xfd\x9a\xd3?=\x1f\xa5.\x94|\xee\xbf\x82^\x81\x0f\xa8\xdf\xf2?\xe5\x86\x90\xf0\xb9\xb5\xf1?w\xa2\xa1\xb7\xb0\x8e\xcf\xbfx\xb3D\x08\xae\xef\xe2?\xad\xf3z\xd0{-\x00@7>\xed\x84\xbd\xd3\xf3?\x8a\xc22\n(C\xfa?\xcc\xb5.\x97\xa7\xcc\xe9\xbf\xe3\xf6\x92U X\xe0\xbf\xca\x88\x1a\x123>\x08\xc0\xc9q\xe0\x13H\xa6\xf3?\xb0\x19\xb1\xa2\'4\xfe?\xae\x94*\xdeC\x98\xef?V\xff\xd1\x9dIb\xd1?\x904C\x819\xf8\xc7\xbf\xe2\xc4\x8f\xb7\x94z\xd9?\xe6o\xe0\xe9\x82\x16\xc5?!<\xb9%\xeb\xb5\xf8?\x07\xa2I\xf8\x1c\x83\xa6?c\xc5\x06p|\xf8\xd0\xbf\x1c\x16ur\xe1\xbc\xfa?\xfc\xdd\xbet\x8c\xa0\xb1?\xcb\xdc\x94@[\xb8\xee?H\xd4}\x99\xec\xa1\xf1?\x81h0c\x95`\x01@\xadzFK\xb4\xac\xf1?*%\xfc\xb4bI\x02@4\x13\xc8\xe88+\xdd?\xef\xde\xc5\xa9\x974\xf8?uZ\xf4g\xb8J\xd1?\xa0\xb4^s\xd5I\xbb?\xfe\x10\xfd\xf1\xa8S\xe6\xbfz\x91\x98vhp\xeb\xbf+MFa}\xf4\xf8\xbfj\xdf\xb0\x99oy\xd5?\xed\x98\xa1\xaf\xdf\x0f\xe4\xbfI\xbc\xa5w\xe6\xc9\xad?\x10Z\x8d\xca\x96\x0c\xf1\xbf\xaf\xa8\xa8\x7fC\x1c\xdc\xbf\x93Za\xab3`\xf5?\xaa\x92ri]1\xf6\xbf\xeckV\xf36\x0b\xe5?\xe0u\xab\xbbE\x18\xd2?B\xd5\x18\xc6\xdct\xb8?\x8dLh\x06\xb6?\xfb?V\xe4\xc4\xf2m\xb6\xdc\xbf(\xaf\x8e\x1b\x8d\xb5\x02@\x89\xec\x87%j\xee\x02@c\x87\xc0E\xff\xf1\x04@b\x14\xdd\xcb\xfd\xb7\xc9\xbf\xab\xda\x00\xb8\x8a-\xd3?\xf5\xc71tC\x9a\x08@\xf2\x0b\xbaS\xab\x85\xe1?3\x84\xd0\xbd\xa3\xbf\x02@8j[\xb5x\x84\xf4?\x00Q\xbcq\xbe\xa6\xe3?c\x80\xb3W\xa5\x9c\xf9\xbf\xbb0\xb4\xaa\x14\xc9\xf6?\xecC\xb8f\x9ez\xf1\xbf\x03\xf8\xa9hm\x93\x95?\x87\x03\x1e\x1a\x013\xc8\xbfT3\xe8\xe7\xca\xd6\xc8\xbf\x94\xd5q\xda\xa6\xf1\xec?\x9aT\x1c\xfdjb\xe6?af\xd5\x821\xa3\xd7\xbfG<\x0f\x18\x11\xe2\xee?\xae_(\x85|L\xe5?\x11\n\xdb\nYY\xc3\xbfo\xe8\xea\xaf\xa5\xa8\xd3?v\x87\x11Ej\xd7\xec?\xf6\xb5\xcc\x8d[<\xda?xZ\xff\x0c\x9d\x7f\xf5?Xy\x95\x10\xf1Q\xd1?\xf2\xd24\xc6 \xcc\t@\xe9\xb3u\x96\xb3\x06\xe6\xbf\x92QN\xac\x94\xc0\xe6?\x9af\xd6A\xc9\xa2\xef\xbf\xbc\xef\x8d\xc8\x1b)\xf3?9\x96i\xf8\x9c\xcf\xf0?\x9a\xe3\x19\xfa\xa7D\xf0?\xb5\xde\xe3\x9c\x98\xc2\xe0?w\x19\x00B\xb6\x12\xe0?O\x17\xa0\xd0?kov\xbd\x08?\xd2\xbf\xa0Z\x02d?\x10\x00\xc0lL\x19\x9cxC\x00@[5\x8fn=;\xfe\xbf\xe8|\xe0\xa8 \x1d\xf1?\x18\x87e\x0b\xa7v\xc2\xbf\x17\xe4_\x9a\xff\x0e\xf5?\x8d\xd2\xef/!y\xe4\xbf\\t\x84\xa8V#\xd6?\xe9\x9e\xf7\xff\xfc\xb8\xf7?;\x1fR\xe1\x9e\x15\xe4?R\xa9t\xf59\xbd\xf1?U\x8b\x124\xc8f\xd1?\xa6\xcf\xf8eq\xa0\xec?\xd3\x95\xf5D\x9c\n\xc3\xbf\x08\xa9\x9b\x0f}4\xd3?\x87\x0b\xcclEN\xbe?i\x92\x7f\xe1\x97R\x05@\x9e\x83\xea+\xdeZ\xc0?\x10\xeb\xfcB\xdb\x91\xcf?\x04k\xe4C\xe7\x18\xeb?\xc1\xec\xb4\x9e\xd7\xa6\xe6?\xaa\x18\x01$I\r\xe3\xbfC\x81\xc0q\xd7\xab\xe2\xbf\x08\xbdeD\xca\x82\xf4?\xf95\x8ej\xb6\'\xe3\xbfn%_\x13\x83\x1f\xec\xbfr;\x0c\xf8R\xc6\xe5\xbfp\x9c\xadT\xd4\xed\xec\xbfx\x149\xd2\xa2\xfc\xde?\xa8\xbb\xeb\xbf\xe1\xaa\xb9\xbf\xe9\x08\xc1\xa0\xd28\xd8?7\x91\x93P\xb4\xd0\x00@\xdf\x7f\xbfE\xb4\xef\xe8?lfc\xc5}\x83\xd2\xbf\x1d\xd2b\xc6\x81\xde\xf1\xbf\x9f\xd6\x7f<\x02\x01\xf6\xbfq\'\xef\x97\xeaC\xff\xbfrYj\xa7p\xa1\xcf?\x0f@d#|\xe8\xe6\xbf}-\xbf\x1cT\xf9\xf4?\xe5"\x1a\xdd\xd7v\xf2?\xc4\x91\xff@\xac\x92\xe5\xbf\xe5\x8f\x1bu\r\xa3\xfb?ZT\xdb\x87!<\xf0?\x92\x15-\xf6\xbb\xcb\xe6\xbf\x83\xf2X\xbf\xf4?\x07\x87Co\xd9\x93\xd6\xbf~\xcd\xfd\xc4\x0ek\xf4\xbf<\x19\xcaJc\xc6\xd4?\x8b\xb4/#\xc4R\xd4?\xcc\x91\xb5P\x9f9\xdb\xbf\xe1\xfe\xa9k#\x03\x05@2$_\x05\xb3\x88\xdf?\xfao\x81\xca\x03\x11\xf0?\x92=\x9dC\x1d\xf5\xd4?\xab\xd5L\xfe\xcf\x8c\xf5\xbfR>\xd7\xc7\x82\x87\xf5\xbf\xbev+\x7f;\xf1\xe0?V4\x7f\x87\xc6\x16\xf1\xbfB$\x065\xcc\xe4\xab\xbfMu\x13\x8fA\x85\xb4\xbfl.\xce\x0ed1\xd9?k.\xaap\x9d?\xe6?`)s\x9a\xaf+\xd9?\x0e\xa2\x05\xa6\xcb<\xce\xbf\xf86\xbe\xe5&\x8e\xe5?\xc2\x8f\xaf\x16z6\xcf?\xb1\x9fC\x99\xf6\xbd\xd8\xbf\xa0\x90\xaf\\\x88s\xd5\xbf\xfc\xc3\xea\x15\xc2\xd9\n\xc0\xb5\x82\x91+jM\xf2\xbfD\xb0yt\xa6\xb5\xf3\xbf\xf9\xcb\x16_t\xd5\xc4\xbfN\xef!T\xdd3\xde?\x12n\xe7\x96?\x85\xcf\xbf\xa8\x99!\xe6\xe1\xf9\xd3\xbfS\xb3e3uF\xe6?/?u\x8c\xbdy\xed?\xe6\xd7\xb2\x86\xba\xdb\x00@F$U\x87R\xc7\xc6\xbf\xc0i\xb2\xf4JQ\xec?\xf4\xe5b\xb27T\xb3?.\xb6\xf1\xd0\x0f\x85\xf6?\x8c\xf7\x06-\x07\xc9\xca\xbf6\xc9sM\x83V\xf4?\xae"\x81\xd23\xa1\xa3?%~!8\x86M\xcc?:I\xa9\x16\x10\xf7\xce?U\xc3\x0f\xb7\xe3\xc3\xf3\xbfb\xfeN\xb1\x03\x03\xb4?\x016<\x87\x85\xf7\xe2\xbf\xcd\x9d\x066\x19D\xf3?t"`\x15`P\xc0?\nB\xac\x00\xe5\x82\xea?\x99/8x\x84$\xfe\xbf+\xc3e\xc4\x98b\xfd\xbf\x8c\x02\xa3\x04\xdd,\x00\xc0\xaa\x97\x86\xe0\x00\xe9\x03\xc0\xc8I\x892\x88\xa7\xf4\xbf\xe8`S\x03\xe1+\xf0\xbfk\xd9>\xadz\xa8\xc4?\xe6\xc5\xfdW\x11\x99\x03\xc0\xd3\x85\xebda_\xf6\xbf\xe0\n\xbc[d\x16\xfd?I\xd5\x8e\x98g7\xf4\xbf\xeb`}\xb9\x10\xfc\xda\xbf9\xa1\x1c*\xd4\xd6\xf5?\xd55\x1b\x05L\xb4\xfb?\xaf\xf8\xd7\xf0\xca\xed\xc3\xbf\xe93\xc1Y\xa3~\xba?\xa12{\xc4L\xcf\xf6\xbf\xac\xbe~\x11F[\xac?K\xd0\x9d\x15\xf5\r\xd8?\xa5\xa3,\xc3\xcb\xc9\xd4\xbf\xa2\xc3\xbfw!\x05\xf6\xbfq\x96\xd9\x9fJ\xa8\xf3\xbfI\x1cr\xa7R\x85\xe1?\x9f>\x8d\xda#=\xf4?a[\xfd\xa8\x15\xca\x08@\xd4\xec]m\x11\x86\xf4?\x8b\xf7GQ=\x02\t@\xec\x7fB\xff\xb7s\xe1?\x9d9\xe3\x99\x87\x88\xe6?\x1b\xc4\x1e\xe6\x92\x8e\xf8?k\xbb\x8b\xed\xaeS\xf5?\xbb\xb2\xce\x9a\xc6\x1b\x00\xc0\x18\xc6\x04\xd5\x9e\xc6\xf2\xbf\xbf\xd2\xfa\xaf\xb4\xbf\x8e\xbf\xb73\xf4`\x13H\xda?,\xfa\x13km\x83\xbd?\xefj\xf6g\xe2\xa0\xea?@j\x0f\x9c\x82_\xee\xbf\x03t\x06\xee]\xbe\xc5\xbf\xb2q\xf6\xb0g\xb1\xcd\xbf\xbff\xe8\xfd\x81B\xf3?/)\xbb\xa2g\xca\xe4?\x1d\x879\x86e\xc7\xbe?\x93\x0fN\x0f\x83\xb2\xe2?\xe2\xfd\xb0\xb8L\x85\xd5?\x8a\x1fI\x19k\xf2\xf8\xbf\xef{\x80\x0f\xdf}\xe1\xbf\x08\x9e\x8e\x06\x90\x85\xeb\xbf\\4un|\xde\xe2?\x91I\xbaL(\xd9\xf9?\xa48\x10\x05$\n\x01@\xb7\xc60&u\'\xe3\xbf|\xeb\x14\rv\xf0\xf7?\xbd=\xba\x13\xe0\x1b\x00@M\xc2\xec\x1b\xd3i\x05@\xbd\x16\xc9\xb1\x9a>\x01@\xcaytA\x86M\xfd?4\x12\xf4\x89g\x8e\x02@\'!\xf8C\xa5}\xfb?\xec\xc3\xebL\xde\xd5\xec?\x85\x01\x9f\xf6z0\x01\xc0\xb9Yr\xb6s\xa2\xfa?B\xcb\xc6#\xc3\xc0\xde?\xeb+q\x04?\xde\xd2?\x91\x16\x8c\xe2PD\x02\xc0\xa7\x94#\xc9\xc1\x87\x00\xc0\xf19\x90Jg]\xf0\xbf\xf1\xc8\xe8OTn\xe7?oi\x03\xd3\x9c\xbc\xf3?\xdao\x91\t|\x0f\xd8?\xde \x84\xb5\x96_\xbd\xbf6\x99\x81o:\xa1\xd2\xbfO#\xdb\xcc\xde\xdf\xf1\xbfT\xf5\xd1\xa66\x0f\xd3\xbf\xd3\xa4\x90\xe6\x12\x94\xf9\xbf6\x16\x89k\xc7E\xef\xbf\xc6\x1b\x9e\xa0S\xaf\xe2\xbfY0o{\xa1\xe6\xd9?\xf7\x87\xfap\x9d>\xf8?\xa2H\xaf\xe2X\x00\xd6?\xd6W\x8b\xe7\xca\x90\xf9?\'\x8d\x82\x8c3\xf6\xeb?~|b\xd0i\xa8\x05@H\xde\x1a\x15\xd0\x10\xce?\x1e\xce>\x03y"\xf9?q\x0cc\xf5w\x1c\xe1?U\x1aIJ\x1cj\xc3?k\xc1\xda\x0bn\xc4\xd2?`p<_\x9c\x15\xae?\xa5\x1e\xf6n.\x0e\xe2\xbf\xe1\xe9\xe1<\xee_\xfa\xbf\x7f\x14|\x886\xd9\xe9\xbf.\xce|\xfeA1\xe8?r\x9d\xe9n\xf1\xa2\xe4\xbfw\xd9\xf4\xd6\xb1+\xe1\xbff\xdd\t\xea\x8bW\xe4\xbfaD\x83\x0e\x19(\x02@M\xce\xa3\xe5\xfc)\xd2\xbfL-+\xc9Y\x01\xf3?\x96/\xd9(=\x8b\xd8\xbf\x96\n.\xe3&\xcf\xe1\xbfM\xad\xd0\xd2N\xcd\xb6?\xb5\xc6\x7f\xf8\x95\x97\xfb?\xd2\xe9b\x08e:\xf7\xbfp\xa5\x0eH"\xcf\xf1?q.\x07b\xb9\xd0\xe2?\xd8\x80\xfaZ\xa7\x1b\x02@\x1e\x98\xea\xd2\xda\xed\xd4?y\xfa\xd8\x11\x9a\xab\xf2\xbf\x0b\xcf\xb7\xe6+\xf5\xf4?:\xaf\x8d?$n\xfd?\xf78\x10\xd0@(\xb3\xbf\xad\xeb^T\xce\x05\xc8?k\rnw\x87\xf1\x04@\x99\xa8}\x0e\x14p\xef\xbf S\xbbs\xf0\x10\xd9?J\x8a\xe2ri\xeb\xea\xbf\xf1\xc7\n\xf6\x07\x01\xe0?b\xb0\xb8\xbf\x1e>\xe8?f\xb9\xbf^\x840\xfa?\xaf\x91\xef|\xfa\xea\xdd?+\x91\xed5\xed\x0b\xf2?\xec\x18\x88\xcd\xef?\x00\xc0\xb7\x07\xa5Od\xec\xd3\xbfS>\x8a\xc0\x0f\xd2\xe6?\xee!\x91\xc5 $\xca?\xe0\xd4\xd08F\x12\xb3?\xb9\x88\x05\xd6Q\xbb\xdb?\x1a\x17\\\xf3\xb7r\xf0?5\xfd\xef\x81\x85\xbb\xfd?\x16\x80,\x1dtL\xd9\xbf\x1f\xb5db\xd7|\xce?\xe6\xbf\x13\x08TP\xf2?\xa8\xad\xd4R\xcb\xdb\xc9?\xfe9\xf37\xfd\xb0\xe5?\x93\x00\xb3\xf9\x9e\n\xe7?2h\xe1\xc8\\\x7f\xec?\xe9\'\xea\x1d\xbe9\xff\xbf\x9c\xe1\xfb\x06\xe4\xb0\xdd\xbf2k\xf6\xedK\xcf\xd8?\x99jd\x11\x07\x9e\xe2?\xe9w?\xf1\xd8\x8e\xab\xbf\x95\xb0\x8f)\xdd\x14\xf4?+y0\xb7o\xdc\xd9?\xcc\x06\xef\xf1\x91]\xf3?\xe0\xa3T\xe7.\xc3\xe4\xbf|i=\x18\x96\x8a\xe6?!P|\xf4\x14\x02\x9e\xbfh\xdbG\x04_\xb3\xf9?\xde^\xbe\x92\xe8\x83\xf1?\xe7\xbd:^\xac/\xd8?(\xfd+Cca\xde?\xcc\xb4\xc7h\xfa\xa7\xf0\xbf\'_K\xe8Yc\xea?\x0b6\n\x8b\xd2\x83\xeb\xbfP|HJ\x1em\xf0?a\xddc\t\xe94\xca\xbf\xf2b\xd0\x08>\xcd\xcd\xbf?\xfe\xbc\xd6&J\xd6\xbfa\x83\xb9\x8aiu\xdf?\xc8nYgP\xb4\xf2?\x9f\xfdq\x11\x96M\xa1?.\xce\x0eh\n\xf8\xe2\xbfh\xac\xf2!\x84+\xd3?E\x80V`\x8d\x92\xbb\xbf\xea/\x8a\\X\x16\xe7\xbf\xaa\xf7\xcf\xb7g\x89\xf7?\xfa\x1ft\xf9V6\xdc\xbf\xe6Q\xc6\x0e\r\xad\xe0?\xff\x8b\xad\x06S\xde\xcf?s\x02\xfc\xda\xc2\x0b\xeb\xbf\xbb%s\'\x7f\xe5\xc3\xbf\x17\x9a\xf0\xda\xe9j\xfe?\xfe\x13\xd4\x9b<\xb0\xfc?\x98\xe4;\xcb<%\xd3\xbf\x82\xb68%\xc2G\xb9\xbf\xc6\xcb\xd4u6\xe5\xcc?\x82"\x01\xbb\xd4;\xcd\xbf\xa1\x99]\x9d(\x1a\xe5\xbfA\x0eC\xd2\x8c=\xdd\xbf\x88\xfa7BUD\xfe\xbf\x96W\x8b=m1\xe7\xbf+\xd1"\xa3\x0f\xf2\xf2\xbfb\'6%\xb5;\xea?\xd4}\xae\x8b\xbb3\xd6?\x1f\xb6\x00\xb4\x15\x03\xda\xbf\xb2p\xb3oU\xe4\xe4?\xd64\x1e\x9b\xf8H\xe7\xbf\xd9m\xafh\xe5\xd3\xd7?\x92\xd3E"\xc0\x9b\xb1?\x9c\xfa\x1c\x0e\xde\xdc\xd0?-7\xace\xdf\x8e\xeb\xbf\x9e\xb8"\nWu\xbb?\xe0o\xf2\x02\xf4\xfb\xfc\xbfV\xf55=\x1a\x9e\xd3?\xb0\x15\xe3\xf54\'\xfb?\xb2\xdeC\x8d\x01g\xd3?\x11\xf2\x02\x9bG\x86\xe0\xbf\xb4\xdb\x9c>_\xfb\xdf?P\xf87\xba\x8d\xff\xdf?H\xc12\x00~\xe5\xcd?\x17\xb4\x0c\x1c\xab\xee\xf2?}\x08\x9d\x05\xa0\x90\xf0\xbf\xa7\xc7\xd39\xa30\xe8\xbf"\xe83\xd6D%\xf6\xbf\xc6\xa1N\x1c\xd0W\xc2?f0~\xeb\x04\xae\xf1?\xe1\xfe\xa6\x06z\xa8\xd3\xbfe}\x00I\xc5R\xe5?\xf7\xb8\xd1H\x0f\xfc\xe2\xbf\x82I\x9f\x9c=\x90\xb6?\xb7\xd0\xe1@\x86\x80\xd4?\xa7\x135\xcf&\x10\xf5\xbf\xe8\xb6C\xc9<\xf7\xd2?\xaf\xe4\xfe+Vr\xed?\xb7\xb3\x9c\xd3s\x01\xb5?\xf2\xdce\r\xf4\xba\xef?\x90#(\xd2\xed\x10\xd4\xbf\xb6A\x80\xa7\xf5\xcb\xc7\xbf\x049\x02\x8f\x9fd\xfd\xbf\x86\xf7\xd9\xd0\x1aJ\xa0?F\\\x99K\x8a\\\xf7\xbf+\xe3\x9e@\xec|\xed\xbfZf\x95t\xb6f\xe3\xbf\xcb\x15[\xdat!\xe5\xbf\xbd\xcf\xccA\x05o\xfe?\x81\xde\xf9*\xe5`\xb0?D\xe0g\xa1\xe1\xbe\xd6\xbf<\x8a\xf1R\x19\x1c\xec?p\xad\xf7\xec\xe2\xdc\xe2\xbf/\xc2A\xd3Bl\xf3?\xb6[\x8d?\xcaU\xfb\xbf=\x83\xe1\x17\x8d2\xd9?\xb1\xb4\xbaEsM\xf2?\xdc\x8eM\x13\xfdo\xe4\xbf\xd9\xa8\xe9k\x00S\xfe?{\xa8vY\xae@\xed?\xc5\x00x\x81-;\xd3?1s\x8a\xef\xa4\xc6\xdd?d\xbeT\x85\x068\xbb?\x90\xbb\xa2\xf1\x0e3\xd0\xbf\xb9\xa7\x06\xbf``\xe6\xbf\xb6\xa1\xba\x17\x15c\xf6\xbf\xa5O\x98\xd9\xc5@\xdd?\xf7\x85\x8d\x0e\x83\xd6\xe4\xbfA\x08t\t\xef\x19\xe3?\xe2=g\xe0[\xbe\xf2?\xe0z\xc6\xcc\xe4\xb8\xce?\xec\xb6\x81\x0b\xac\xba\xe4?o\xea$\xf0\x10\x87\xee\xbf\x94>\xa8\x16\xc2\xff\xe3\xbfV*R\xdb67\xe5?\xf3\xc5^\xcb\xa2\xc2\xbd?\x96f\xbd\xd2S\x18\xf1?\xb7n3\xa8Bz\xde?\x06\xbb.\x17\x99\xb8\xff?z\x81\xbf\xce\xf7\x84\x81\xbf\xb5t\xd6\xd2,2\xbd?\xe4\t\x91\x93\xa9\x9e\xe6\xbf\x89\xd7\n~\xbe\xaa\xf0?\xf0\x87\x18\xb2\x98\xb5\xd0\xbfJ\xa7\x05\x88\xe2\xf2\x00\xc0\xbe\x86\xf4Loe\xec?N\xae\xff\x1ci\x91\xc4\xbf\x07\xe7p\x93\x10\x0e\x02@\xe4\xe6\x1ay\x1b\xc3\x02@\x0c;nH59\xc3\xbf|k\xa9\xa0\xdb\x8b\xdb\xbf\xd3\xa8ji#\xf6\xdb\xbf\x0e\xa4\x89%u\xd5\xea?\xb6\xc0\xe7\x0c?\xc8\x00@\x88\xfd\n\xb75K\xc2\xbfK.\xf5\x0e\xdb\x8c\xe5?\xf8\x1b!\x8dHb\xef\xbfo\xb5\t\xe6\xa3\x93\xe9\xbf\x1e5\xefxh\xa5\xda\xbfg\xae\xa2\'\x11\xde\xd3?w>\xff\x98\x89\r\xde?\x88\x0c&9\xfd\x04\xee\xbf\xc7o\x1a\xbb\x0c\xed\xe9\xbf\x1b\xe5\x7f\xc6\xd7\xe4\xf6\xbf\xa3\xf3\xf1z\xae\xc3\xe4\xbf\x1c\xa5m\xd7\xb1>\xf0?q\xc9eu\xe0\xae\xde?\x82\x81^\xe3\x7f\x7f\xf8\xbfm\xdf-z\x8c\xa6\xd6\xbfC0b\xbf\r0\xeb\xbfW|\xa6q*3\xea?\xe6`b\xd5\xefB\xdf?\x9d\xc5N\x89\x9b\x0e\xf4\xbf\x8f\xd0!\xa4\xad\xb5\xd8\xbfq\x03\xd7m{\x82\xf3?\xb5U\xa9\xe3|x\xec?\tM\xd5\xa5"$\xe0?\xd6\xfb\xe6\x00\xd1?O\x9e\xcb\x1d\x1c\xf4\xf2?5\x1f2#_\xb2\xc4\xbf\xc8H/T\x9c\x05\xff\xbfm\xeeJe\x15\x94\xe6?2g\x00\x89\xde\xfb\xc5?\x8a\x1aZlDU\xda?\xb6\xb4V\xc2!\xad\xf8?\r4I\x1f\xd0K\xfd?\xab_-Xcm\xc7?\xa7\x96_f04\xf9\xbf(/\xb1\xf0\xaf\x1a\xec\xbf\x0b^\xcf\xea"\xc0\xe5?\xc6\x90\x96{\xbfu\xf9?\xab\xd7cG\x1b\xe6\xd5?\x8b\x7f\t\x18-c\xdd\xbf\x95\xd7\xc3\'f#\xff\xbf\xd5F\xae\xe0\xde\x19\xf2?\x17\xb1\xf9\x92\xc1\xe4\xf6?\xa6\xa1\xad\xc2U\x9b\xd0?[,\x8en\x0f\xb4\xe6?\x05&UY\xf4Wd\xbf:+e[\x0c\x8b\xe9?\xe5@he\x03\xcd\xdc?#><\xea\xb9\x81\xf3\xbf=\xcd\xc8h\x13\xdb\xc6\xbf\xe2r\x13\x89\x0c\xbd\xf9\xbfq\xb7S)\xb7\x17\xfb?\x13\x02\xd0{\xc80\xb1?Z\xc4\xfbj$\xd1\xe4?\xfc\xa2Oe\xef\xf1\xc1\xbf\xd3\x1e\xda^F\x8c\xf0?Uk!ol\xb2\xcf?\xcb\rF\'\xe0I\xe9?\xda\x13|\xef\xcd\xca\xc8?\xfcn\x07Z\xdfp\xd0?\xa995\xe6\xce\x06\xe9\xbf\x11;\x15\xbc.M\xd0\xbf\xdc\xb3Z\xb9\xc5\x89\xd0\xbflIC\xa3\x0f\xe3\xf7\xbf\xdb\xbe\xdd\xd3_\xce\xbb?\xe9\xbbP\xf3No\xd6?\xa3\xa2\'\xde\xc4\xff\xb8\xbf\r\x9c\xd9i`E\xe9\xbf#\xdd\xd8\n\xa5\x1f\xfb\xbf>\x99\xdf\x9eN\x96\xd5\xbfo?\x05fGV\xea?\xb1\x1a\xba\xd0\xc6\xdc\xd0\xbf\xc6\xe1\x07\xac\x04#\xf3?\xef#h\r*_\xdf?z\xd1\xa3\\\x9c\xe0\xe3?+\x0b\xd2S?\xf1\xef?\xd0e\x89\x18\xbd\x8e\xa9\xbf\xfc\xa3\x19\xb5\xa2:\xff?\xbf\xfdp\xbb\xaa\xaa\xdf\xbf\x16\xc1`\x8f\xe7S\xfe\xbf\xa7\xd3\xc6i5\xca\xc8?v\xf0V\xe7\xa5M\xfe?\xba\x97K\x81\xb2\xed\xd0?\xc8\xf4\x90\xdf\xcc\xeb\xd2?\xe7A\x84\xc6\xeb\xb6\xe3\xbf\x15 \x1b\xe9\x83\xe1\xdc?8\xe7\xf2\xfc%\xcf\xf6?\xfd2\x0e\x90\xb0\xa8\xd8\xbf\x13Q=\xe5]\xff\xdf\xbf\x08;\x8a\xba#\xec\x86?+(^\x1d[\x85\xc4?\xb4\xd6\xea\x90%\xf8\xf1?\x18\xd4\xcdYm\x04\xe7?\xf7U\xcf\xbe\xe9\xcb\xd5\xbf\x9d\x1b\xfeM\x84\x88\xfe\xbf\xc2I\xf81\xb6\xc8\xea\xbf5\xf3\xd1\xd8#\x87\xfe?\x80\xd0\xa8\x00\x80\xc2\xcc?y\xfeO\xcb\xdf\x96\x9a\xbf\x86\xc7LN\xc4\xaf\xce\xbf\x94\r\x1b\x7f\xb4\x13\xe6\xbf\xa9&\xd5\x15MP\xcc?\xa4\xe5\x9c\xc2B\xcd\xf4?~Dn\x84tC\xf1?\x97\xaf\x03y\x07\x0c\xf3\xbf\x85\xbb:\xfb\xe2\xd0\xd0?eK\xb5_\xc1%\xda\xbf\x7f\x0fXB\xf7\x8c\xd9\xbf|,\xdc\xb7uJ\xe4?\xb6\xbe\xfd\x1b\xa7\xea\xd9\xbfD\xedUNp\xca\xd3\xbf~\x99\x00\xbcO\x0b\xe7?O]\x85\xf0e8\xf1\xbf\xaaL\xb5\xae\x84\x88\xf5\xbf\xacL\xe2\xf1\xf97\xf0?`I\nq\x08R\xed?\xd2\n\x11G|\xbc\xe6\xbf\x8f\x12\xd5\xfb\x0e\x8b\xe0?!\xa4\xf9\x9f\x99\x90\xe7?\xbdC\x81\xf5\x9f\x98\xe2?\xab\x86\xe37\xff\xe8\xe6?(\x03\xac\x0bH\x1b\xd4\xbfY\x18\x17\xa8*w\xc8?\x83\x11\x83\xb2\x9c\xe9\xeb\xbf\xa2\xe1\xbd&7\x96\xf8\xbf\x01g\x81\x84\x1a\x86\xec?\xbbl\xe9\xae\xe9\xc7\xfc\xbf{\xa2\xa4G\x7f\xe3\xa0?o\\\x9bg\xc2\x94\xd2\xbf\xca\x141i\x92\x98\x01@y\xd1\xf9\xd9Y\x00\x06@$pC\xeb\xcc\x80\xf1?\xf7\xb1\xce\x08K\xc4\xf4\xbf\x06\x16\x9b\xd2\x97\xd6\xe4?v\x11d\xdc\xb6\xa0\xec\xbfj\xbc\xb1\x81L\xd3\xd9\xbf\xb6\x97\x8e\x84M\xe6\xcb?\x89\xf1l\x82\x8f\x90\xe2?\x85\xa0\xee\xad\x18n\xdc\xbf\x0b)I\xb2\x17C\xca\xbf\xcf\xc1\xc9}\xfd\xb8\xde\xbf\xd4\xb5+4?l\xae?n\xdf\xfe\xc1Q\xee\xf2?\xaf\xbeY\xde6a\xf2?\xc2\x17\x1e\xcb\xe5\xe2\xde?\xad\xf0j}\xb3\x95\xe8?\x8f\xba\xfe1\xcbR\xfc?\xa7\xf5\xba\xb2\x84\xd8\xee?\x13\xa3\x8b\xa4\xc6\xf5\xda\xbf\xb6\x89\xd6\x90\x16\xd2\xec\xbf\xd1%w!\n\xdf\xdf\xbf\xf3<\xca\x1bB\xfe\xe5?\xcb\xe2\x8e\x8e\x8eA\xef?V\x19S\x04[\x17\xdc?&\x19]\xf9\xe5.\xd8?\x16\x98b\x82i\xb2\xd9?~\xb6\n\x9b\xe2\xc5\xc5\xbf\xda\x96\xdb\xcb2\xbb\xe7?\x11`\xd6\xae\x1f\x85\xf1?\xd9-/\xea\x03`\xe3?tr+\xed\x95\xa7\xda?#\\\xdf\xb3\x1f?\xe7?\x80\x97v\x91%\x19\xe2?\xb5\xfa\x14\xb4\xaaP\xd9?\x90Se\xf6\xf3\x19\xee?y#L\xc7\xe8\xb0\xde?\x1f!\xa9\xfc\x92B\xf8\xbfw\x00\x1b\xf7\x03d\xe8\xbfcB\xf6!\xfd\xe1\xe8?L0\xfbT\x8f\r\xd9\xbf\x92\xe2;\xdf\xdcm\xf1\xbf\xf9\xba\xd9\x0bAZ\xf1?-\xd2\xdb,\x0e\xbe\xb5?m\xf2\xa71\xe3\xca\xd3\xbf\xe3\xc00\xe7I\x95\xdd\xbf\x83S\x9c4\xf2u\xf3\xbf\x96f\x98\xff4\xa0\xe2\xbf\xe2\xfe\xa0oa\xfd\xeb\xbf\x8a]\xb8\r\xcc\x19\xeb?\xb5b\x8e>\x06=\xda\xbfo\xc2\x8dK\xde\x11\xf5\xbf\x85\x8b\x04k\xc1\x03\xc0\xbf\x95\xa7\x14}D\x92\xf3\xbfd\xc5u\x00/\xb7\xe5\xbf\xbb\xba\x92\xd5\xe3K\xf3\xbf\x7f\xdc\xc3J\x08\xbe\xe2?\x9b\xfd\x1a\xab\xe9&\xe3?\xbf\x06)\xb5\x84V]?\x93\xe2\x857\\\x9e\xc0?\x19EUci\xcb\xc3\xbf\xdbXX3\xe3$\xea?\xe1\x90\xed\xc9\xc6\xc3\x97? \xedE\xbb\xefO\xf5\xbf\xe5P\xadD\x16%\xc7\xbf\xe9\xd6EY\xf6\xab\xf2\xbfA\xb0\x93-Mx\xf5\xbf\xa6\xb92\x84E\x84\x08@\xea5\x93\x87\xd5g\xbe\xbf\rl\x9b\xa5YT\xf1\xbf }\xad\x1f\xa84\xc7\xbf\xc34CmL\xf1\x82?\xe7Tv\x1e\x8c\x8a\xc0?\xbd\xe32\xa1j6\xe1\xbft\xc3\xc5\x01\x13\xbf\xe8?L6\x19Y\xf3\x03\xed?\xdc\xa9(\xd4\xabH\xc0?"\x11\x812\x8c\xba\xed\xbf\xc2O\xec)M\xcf\xe9?\x82\xb5#\x1b8(\xa3?\xb8&\xa5\xa1O\xbf\xf6\xbf6\xfc-f?\xc6\xe3\xbf\x05\xb3t\xb4\xbd\xba\xd7\xbf-\xd2\x12z\r\xa9\xf5\xbf1\x122\xad\x00\xbe\xd2?5\xb7\x82\\\x1d\xbe\x9f?\x08\xa3,.\xf5B\xc7\xbf\xd1,\xf9^\xc9\xa5\xe7?\xee\xa5\x82\xb1.\x1b\xf3\xbfZE\xe4\x055t\xf7\xbf\xcf\xbb\xb6\x1c]j\xc3?,\xaf\xf8\x97\xa7\xbe\xea\xbf~\xdb\x1b\xbeq;\x00@1\x01\'\xc5\xe4\x8f\xb0?\xa5?\xb7\x15\xa5\xee\xd2\xbf\x9ar\xda"cx\xf0?\xfa\xd3\xd1%r\xd3\xfc\xbf\x86\x9209\x0f\xf4\xed\xbfB3\xc6\x16\x9bx\x93?T\x9e\x9f\xeap\xe8\xe0\xbf\xca\xbfG\xcb\x81\xbf\xf0\xbf\x83\x96\xfb\xeb\xd7f\xe8\xbf\xa0\x8e\x98\xf0I\x10\xf8?\xca\xa7(eMn\xac?\xdd|\xec\xedp\x8b\xb2\xbf\x93\xc4c\xbe\xe7\xc1\xbc?\xf4\x0b]\x16\xba@\xb5?R\xf8\xde5\xc7m\xcf\xbf=b\xed\xaf\x99l\xb8\xbfe8\xd2\x0fF\x83\xe9\xbfX\x07\xda\x0e\x91\xd8\xf4?]\xa7\xd6\x9a.\xf5\xf9?\xbf\x9bP\xb6bF\xf7\xbfVJ\x9f\xd3\x00\x05\xf4\xbf\xbd\x03\xd5U\xaa\xd0\xb5?\x17\xc6\x07\xf9(1\xe4?\xa1\x9c.\x11"/\xf0?N\x1c\xf5\x92\x8a\x8b\xd3?\x15\xfc(>\x08=\xdb?V\x19O\x84[\xdd\xe5\xbfM\xf10\xfd9!\x06@A\xe6\r\x1f#J\x0b@\x9cZ.\xbd|J\xdd?\xe4j\xec\x9c}\xdb\xbf?\xcf\x8c:\xd1\x1a\xb7\xe1?\x03\x8e_\x91=+\xcd?x\x101\x83\xf9"\xf4\xbf\x82X\x83\x12!\x95\xec?\xcd\x8b\xc82\x04F\x03@\xc0\x90F\xd6\xaf\xdd\xd0?e@=x\x9dm\xd0?\xdb\xebp\xff\xe4\x8a\xd0\xbf%\x12\x10\x071\xa2\xbe\xbfQ\xf3\xf8\x93n\x1f\xed\xbf\x8a\xee\xe2\x80\x95L\xf0?ds\x84\xea@}\xf8\xbfB\x9d\x1d\xd8\xfbQ\xf8?\xa4\xf1v\xc8\x83\xee\xcb\xbf\xae\x82\xd3\xecQ\xc1\xdb?\x0c\x9eLz\x1b\x94\xa9?1\x14G\xe0\x1b\x15\xe7\xbf>\xaf\xc5\x88\xb6\xd2\xa8\xbf\xdb>\x11\xd3%]\xd9?\x03\xee\xa6M\xd5\xff\xcb?\x7f\x08p\x93\xfb\xe0\xe1\xbf|9\x0e6\xb0I\xee\xbf"EhJ\xf8\x8a\xe3?\xe3\x9b\x14>\xa5\xea\xc6\xbf=\xe2E\x7f%\xc3\xbc?\xc4cW\xaf4\x85\x05@P\xad\xf9\x8a\xfc\xf8\xca\xbf{\x91\x1b\n\x07n\xf0?\xb0\xbe\x0b\xd6\x16\xf5\xcd?\xc4b\x86F\xe1\r\xfb?\xbb<4\xa1\xa7%\xeb?\xd2\xb6\xa8\xb5CU\xcb?T\x07$Hi\xba\xe5?\x86\xd3\xb3\x14s\x88\xf4?!\x82AT\x12\xff\xbf\xbf\xe0\x02\x0b\xcbEn\xd5\xbf\xe0u\x16 w\x86\xb3?\x807\xc3O\xa7R\xf5?\x19\xd2\xa7`v\x08\xd7\xbfw\x10U\x0b\xa9\xa3\xf5?O\xfac\x96|I\xe5\xbf\xa4\x98\xe0c\xe7\x90\xd5?\xc6\xce\xba\x19kh\xe4?\xb0BZ\xee\xdfN\xf1\xbf\x12t\\\xc2\xed\x1e\xfb?\xa3\xbf\xe8Drx\xfe?Q%\xfc\x87U\x1a\x06\xc0\xc4{\xef\xd2\x98\x91\xdc\xbf\xf0K\xd9\x8fxz\x00\xc0k\x93P-O]\xf3\xbfl#\x92\xdb\x86\xb1\xd2\xbf\xc4*m\xdeR\xed\xf5\xbf[9\xa8\xa5\xbet\xe5\xbf\xb0)9L\xeb\xbc\xf6\xbfy\x17\x92\x92+>\xc7?\x7f\xa8\xcc\xf8\xcd\x8c\x04@}L\xf0\x9286\xf4?\xb0\n\xb4\x94\x1a\xea\xfe?{\xab\x19>Sa\xd1\xbf\x1c\xc5N\xbe\xafn\xe3\xbf\xed\x9ai\x9d\xf2\xdb\xda?\x84IaMp\xb4\x00\xc0\xef\x1c\r\xd5h\xbc\xf5\xbf\xb8\x01k\xc1\x84\x02\xf2\xbf\xf9\xed\xc7%T\x8d\xc8?\xf8N\x0e-\x97\xea\xef?\xf6\x00:\xe3\xca:\xfe\xbf\x8c\xbb\xa7\xb6|\xbf\xd8\xbf\xb0\x11K\xef{\xb5\xfb\xbf#\xbcma\n^\xf9\xbf\x9ez\x7f\xeb\x08\xc5\xf1?H\x9f\x97\xb3\xeep\xef\xbf\xbe$pT\xa2"\xd8?\xc6\x8e\x9fy\xefI\xda?I\xcb\x80\xf3H\x7f\xf3\xbf_Q\xe1\x8b\xa5"\xcb?\'\xd34\x11H\xfb\xe5?7\xc7\x17\x93\xf0\x1e\xfd\xbf\x06X\xe9\xd4\x98.\xe1\xbf47\xea_ )\xf1\xbf\xcc\x8c\x81\xf2#\xe6\xde\xbf:\xd1\xb4\x16>\xb0\xc6\xbf\xcbi\xd6@\x1c6\x05\xc0 \xec\x8bW|\xb3\xec?-7\xa7y\xf6|\xca\xbf\x8d\xfa\xa1\xa7\x8c\x94\xe0\xbf&Cc\xdf\x01z\xfc?\x10\xd9\xb2R\x1f<\x03@g\x89\xcan\xfd\xe3\xeb?\xe31\xc4\xf4\xa2`\xd9\xbf\x94uu_\xe7#\xea?\x94\xff\xd7\x15\xeeY\xed?\xf1\x88\xa7\n\xc2i\xc9?\xe4\xa7\xaa\xf7\xc8\x06\xee\xbf\x00\xdd\xbad\x7f\xbf\xc7?Y\xc1\\\xa1\xf3\xc0\xf0\xbf\xf5\xbb\x80V\xe3\xdd\xfa?\xe4\xd1\xd0\xa2X\xfd\xe2\xbf\xf1\xdd\xa4\x0b\x92\xf2\xc8?\x93\x90@C\xd4t\n@\x11\xde9\x97o\xba\xf7?Y\x1f\x84\xd6Hk\xe1?\xce\xa8\x8a\xa6\xf4m\xeb\xbf\xf4\x90=\xcb\x91\x80\xb8\xbf.e\x96m\xf2|\xf0\xbf:\x1a\xa6<\x12\xe4\xd2\xbfc\x06CE\xb0h\xc4?\xe0\xb4\xdfh\xbb.\xed\xbftt\x12\x80}\xd9\xd6\xbf\xda\x93p\xb2?\xba\xf8?$\xe5h\xfc%\xe3\xe2?\xe6\x89X#/\xed\xe2?t\x1d,$m\x9c\xd3\xbf\x18\x8b\xeaY\x9a\xc4\xd1\xbf\xa8V\xf5\x85\xc2\x08\xf8\xbf\x90\xbeZT\xb3\x81\xa6\xbf"\xcc~_f\xc8\xf0\xbfv)\xd6\xcb\xfd\xd5\xd8\xbf\x1c\x07\x1e;i\xba\xcf\xbfF=\x16\x00x\xee\xe8\xbf\xe1\xbd\xc7\xd6O\xa7\xfa?\xe3\x89\xe8\xf0\xef\x0b\xf0?\x83+3\x10\x87 \xe4\xbf>\xb3\x08\x1b\x03o\xb5\xbf\x8e)\xb4\xf3\x822\xf9?M\x8e\x040\x1a`\xfa?\xc6\x1b\xb7si(\xbf\xbf\x1dh\xe4\xd1\xb2\x85\xca?n\xa5\xc2\xc7\xc1\xa5\xed?H_\xc7\r\x96\xf7\x03\xc0P\x07\x16\x0e\x00\\\xfb\xbfiDt\xda\xfc\xf1\xd7\xbf7\x83M\x86\xa5\x00\xf1\xbf\x06H\x9a\xa3uD\xbd\xbfA\xb96g\xd7s\xcc?s\xa1\xaf\xf3\xd2=\xf5?\xa4\xdeqg\xfd\x0e\xed?k\xdb\x19w\xc3n\xe5\xbf\xb0\x04\xb2\x8d\x9b\xc8\xfd\xbf6xjpQ\xd7\xf5\xbf\x89\x91P\x865\xfa\xa4\xbf\x0b\xb6\xa6\x9c\xaf\x12\x94?\x19z\xf7X\x8e0\xe0\xbf]\xe3\xce\x8f\x1aR\xb9\xbf\xe2\x18B/\x80V\xf0\xbf[\x10\x90\x12\x06/\xdb?\x7f\x13\xa49+\xab\xb3\xbf\xae\xc6\xca2\x0f\x82\xf8?\xe7\x1e\xf6\xc1v\x8b\xea\xbf\xc3\xd3T\x94\x06\x00\xe9\xbf\x99\x03L]\xaf\xcf\xcb\xbf\xcbY\xc6iQ\xc1\xc8?\x9ab\x08\xcb~\xe7\xe1?\xed\xf9l\xcd\xc5\xdb\xdc?*6\xa5\xaeV#\xc7?\x92\x04\x0c\x80\x1f\xa1\xd8\xbf\xa7l<\x81[\x0c\xd3\xbf\xde\xb7A\x0b\xc7}\x0f\xc0\rU:\x1f\x97\x97\x03\xc0C!2\x8e\xf1\x15\xd5\xbff\x17\xc8I\xd6\x85\xce\xbfjZc\x08\x99\x1b\x00\xc0E\xfd\xb2}V\xd9\x00\xc0\x80\xd7\x1ae\x8c\x16\xe5\xbf\xf0\x1f\xad$c`\xcc\xbf\x03}s\x8bB\x0b\xcc\xbfsr\x02\x82\xb3\x00\xf2\xbf\xa2\x85s\xb7\x05\x03\xf8\xbf\xeaC\x01c\x7f\x9e\xea\xbf\x16\x95\x84y\x1a\x80\xc0\xbf\x95o\xa0\xce\x15:\xec\xbf\x07\x88RE[\x82\xcc\xbf.\x9f\xd0\xfb\xa7\xf3\x05\xc0\x82vM\x1c?\x10\xe5\xbf\xb3G+w\xc7\xbd\xf2\xbf\xeaOp\xfd\xf6B\xf6\xbfG\xf0\x01B\xb9n\xf1?_\xa9\xbe\\|\xa6\xd7\xbf<\xef\x89\xf6\x15\xa7\xdc?\x9c\x8a\x02f\x7f\xad\xf1\xbf\xa4u\x17n\x08\x93\x05\xc0PB+`~!\xf5?AJc\x90>\x10\xc0\xbf@\xf2\xa5*=+\xe0?\x83;i\x17:,\xe4\xbf\xf9\xdadJ\xda\xd2\x90\xbf\x8d\xe5\xaa\x1a\x96\x04\xfe\xbf\xcd\x07\x9ce\x1c\xf5\xf6\xbf&\xf8Z%\x87`\xf1\xbf\xfa\t\xfa`\xafS\xe3?\xbeL\x12\n\x05a\xe8\xbf\xbcK\x85\xabQ\xbe\xef\xbfw|\xf9,;I\x04\xc0\n\xeeQ\'\xd1Z\xf4\xbfa\xc3Y|j\xaa\xc2\xbfn7kv\xa3L\xd6\xbf\x8d3\\\x1a\xe2\xbf\xdb?w\xc3\x1cs\xbaw\xb9?R\xcd\x8c\xc8Y\xe1\xfc\xbf\xdah\x14\xd4/\xc3\xd2\xbf\xee\x08\x00\xc5\x0f\x16\t\xc0x\x91n\x1d\n\x8a\xf4\xbf\x9d\x08\xf8X\xb00\xd2\xbf\xec4\x9c\xf5\xa2\'\xe1\xbfFs%\xbf3\xd0\xb7\xbf\xed\x96\x93\xbe\x97\xe2\xf8\xbf\xff\x8d\xf1\xa6\xc3M\xba?:N\x8c1S\xdf\xdf?\xafN\xce55\xc3\xe4\xbfr C\xba"\xe7\xeb?\xdf\xd8\'\xfe\x8f\xcf\xff\xbf8\x10#V\n\x8c\xc3\xbf;\r\xa4\xdb\x07\xd9\xd1\xbf\x13>\xd9\x83@\xa3\xfb?W\xc8m\xa3\xbb\xe8\xcf\xbf\x07c-ct\xb8\xd8\xbf\xba\xf6a$0\xde\x01\xc0\xeb\xd6>\xb0\x91\xb1\x04\xc0\xd5T\nE\xab\x90\x06\xc0K\x19\xd2L3\x11\xfa\xbf\x97,t\x90M\x80\xf0\xbf\x9c\x99\xe6\xb32M\xa2\xbf\x84\x1b>\x84\x06\xfe\xf6\xbf\x1b\x1d\\\xaf"\xec\xf4\xbf\xc8\xb2\xf1\x17\x95\xf2\xfd\xbfC\xa2\x02\x8f\xe71\xf3\xbf\xb4+\x85\xb7?\x9b\xff\xbf\x9dm\xa2\xcb\x13\xb8\xe9\xbfR\xe5\x02#2F\xc0?\xcdQW\x88\xca\xb4\xe1?G\xd8\xb7\x91v\x9f\xff?+\x1f0\xa9/\xbb\xe9\xbf\xcc\x15m\x82\xc7\r\xf2\xbf\xb1\xc71\xeb\x88\xc9\xe3?/\x91,+\x12\xea\xeb\xbf\xcd\x87D\xbf\xd4\xca\xd1?#\xcb\x9c\xb1>\xfd\xc5?2v\xc2\x103\x8b\xfe?\xd9z\xb7W\xa9\xfc\xfc?\x03\xe3\x8d\xcf\xf8\x83\xe0\xbfv0\xf2\x8a\xdd_\x82\xbf\xe6\x1dy\x12\x92\xd0\xd4?\xf4\x87Z-\xf6\xa2\xf1\xbfD\xfa\xdf\xe2H\x87\xef\xbf\xfa\x1bY\xb9\xbf\xc9\x9f\xbf0x\x17\x97\xa3f\xc3?\xfa\xd2\xc43\xe0\xf1\xfb\xbf\xb0\x05r\xb9*\'\xee?\x13b\xf7\x01\xb5\xdd\xf6?\xea\x866\xca\x9b?\xd7?\x8b\xd1r\xbb\x17\xf7\xdd?q\xc3d\xdd$5\xc7\xbf\x03\x94\xc9\xb8\x99$\xd4?`R\xf7\xaa\x92\x1c\x00@\x8f\x97r^}M\xe6?\xa9\x83|\xffi^\x02@\xaa\x7f\x10\x86\xa5\x90\xe5?\xba\xbc5\xcd\xd7H\xc6\xbf\xc7\xeb\xdf\xbe\x90&\xcb?\xf2x\x1e\xb7\x04\xd6\xe7\xbfH\xa4\xc4\xa1\xd0\x94\xf3?\xbd\x07\xe4\xb8k\xd7\xc2\xbf\xc8\xb1m0\xda8\xdf\xbf\xc4\x1cOX\x8f\xb8\xe5\xbf\xe9\xb9\xdd\xb0y\xb5\xc4?\x05\xc3\x0e\x1e\x01m\xec?4r\xb4C\xb7b\x03@\x14\xcc\xe06\x02\x97\xf8?\x9b\xb4\x94\xef.\x89\xd8\xbf\\\xa7\xec=\xd0\xa0\x84?\x8d.\xb2W\xe6\t\x04@\x1f\x18\x86??\xad\xe7?\xe3\xaf\r\x83\xff\xd0\xf6?\'\xdb\x00I\x83b\xc7\xbf\x17R\xaeJ"2\xf4?\xa9\xbcn\xf9"\xa5\x02@\xcc\x86F\x06\xb4\xb6\xdc\xbf\xd0!\x02GE\xf3\xe6?@\xd9\x8d\xbf5\xba\xd6?\xa9p(@I\x1d\xea?S\x8aU\\\xaeJ\xb5\xbf\x03\x93R\xf3\x89\x9f\xe1\xbf\xd1\xb9\x82\xd2\xf4p\xf2?q\xa8\xc0}\xeec\xc4?\x10\xe6tP\xd6O\xf3?3\xb4\xb6\xb6\xc4\x9a\xe8?\xbd,\x1bg7\xb4\xdf\xbf\x83\x80\x04\xe3\xdd\x00\x00\xc0\x92\x05gV\xd8\x98\xf5\xbfh\x17\xcf\x07\r\xd1\xf9\xbf\xc8\xcd\xe6!B\xed\xcc\xbf0"\xb3b!\xe2\xa6?\xe4E\x8c.\xfa\xb0\x04@\xad\xd6\xd2\x90\xa6&\xff?K\xef[Qs\xcb\xde?\xe2\xa7\xeb\xcf\xceg\xf5?e\x9a\xff\x86\x8dF\xfd?\x14G\x8f\xf7~\xe4\xd5?\xc4\xa5\xc8\\\x03%\xf1?\xa9<\x9f\x1a\x05=\xff?\xb9\xa5\xf5\x11\xf2\xa9\xb5\xbf\x84\xdd\x90\x10\xd79\xf2?\xb90\x9aS\\\x10\xca?$\xc1\xb1\x05-\xba\x02@\xf1\xea5\xd29\xf6\x01@\xb1\x82|\xddp\xf2\xc0\xbf\xd2\xb2\xca\xf1\x80\xe8\xc9\xbf\x8f\xf5\xdcH\xf7\x87\xe1?\xb3\x04j\xe9=Z\xfc?\x1a\xc0\xce\x02\xf2\x00\xff?\x94M\x01\x1eE.\xe8?m\x17\x9f\xb9\x80\xcf\x02@\x07b\xa8\xca\x19\x85\xd7?\x8f\xa0\xec7uj\xfc\xbfY\xeaz-\xd3\xc9\xe6\xbfk#\xba\x04\x9c@\xd3\xbfS\x9d\\\xbb\x88\x0b\xf7\xbf\x04\x96$u\xc9_\xcb?\xb7\xddg\x18\xa5\x96\xce\xbf\x9cu\xfa\t\x1f \xf2\xbfXp\x186l\x08\xe5\xbf\xe5lU\x19\xcd\xfa\xf1?&\x06V\x90Q\x16\xda?N\xf3\xda\x10\x92\x98\x01@\x17)\xd4\xc2\xbb6\xc1?\xefU+\x1bLB\xfb?phFa\xab\xfa\x00@08{\x0b\xfeC\xf5?yD\x15\xe2\x1e~\xf9?J\x1eY\x96Hr\x01@\xacsU\xe6\x01\xbe\xfc?\xe27\x1ffzM\xf0?9>P#m\xcb\xfe?U\x97\xca\xcd\x9e\xbb\xf6?B\x17@$\xe1\x93\xe8?~\'\x86mq\x05\xcd?i\xc2\x99\x0beW\xe4?Y\xfc\x8b\x8c\xd7\xef\xf3?\x81\xfc\x01\x8b>7\xed?\x0fXu6z\x80\xdf?\xb6\xb0\x1aMu\x04\xe6\xbf\xb9\x14 n~\x02\x02\xc0\xb3\xccj\x81\x93\xe3\xef\xbf{\x8aZ\x15\xb4N\xe0?\\ \xe3\x96D\xd1\xb7\xbf\x85\xf6\xabD\x8c4\xf4?D+S\xd3\xa0\xe3\x85?\xe8\xde\xeavwS\xdc\xbf\xf4\x06\x02P\x98N\xd6?\xd5\x03\x9c_\x86\x84\xd8\xbf\xb4\xd5\xbf\xcb\x9e\xc2\xd4?C\x9f\x0b\xd2+N\xdb?\x11b\xdb#\\1\xf1?\xab\x9b\xc2Q\x02H\xeb?h1\xe4\xab\x1d\xce\xa3\xbf\xa1\xb2D\xeb1\xa4\xa5\xbff~y\xc8/\xb1\xf3?|\xec1\xa0\xafG\x03@\x11K\xd4"\x7fk\xe7?\x12\xbe\x99\xc8H\xb6\xf7?\xdc4\x91\xf6\xe9\x89\x00@\xc4}d\x8c\x12v\x03@;\xeck\xb2\xe2\xce\x01@1O\xff\xad\xdc#\xe8?`4Y\xd8\'\x91\xe2?2\xe3\x8c\xdf+1\xf5\xbf\xd7\r\xd7~\nN\xf8?\xd9\x8d\xe6\x132F\xfa\xbf\t,\xcf\x97\xc0]\xfe\xbf\x8f\x82\x06k\xcdj\xdf\xbf\xde\x8b\xe13a\x10\x02\xc0:d\x0e\xd1\xa2\x89\xfe?\xeevv\x81\x9a\xf7\xa2?\x1e\xa1\xe7\xa1RW\xd3?>W\xd9\x84;3\xbd?y\xc7\xda\x0f\xf0\xc3\xca\xbf\xf8\xb6M`\xc4I\xe9?R\x94\xdbP\xcc\xe5\xf8?\xce\xe4\xad\xe2\x14\xec\xdf\xbfp#mZ\xf71\xc5\xbf\x17^+\xa56l\x05@e\x10\xb8\xe7_\xa9\xd6\xbf4\xa0y-\x8e(\xd4?*\xce\xa5Y\x98\xc1\xd9?\xbb\xff6M\x87,\xe2?m\x9c\x13\xea\xd9~\xe6?\xd0\xc3`\x93\xf5\xc4\xe8?\xfc\xadXN/\x94\xd3?\xcf\xf2\x99\xfa[ \xd7?|\xc3\xb5z\x92\xd2\xe9?Z\xaa\xc3\xecq\xfd\xd6\xbf\xf3g\xdd\xd7Lq\xeb\xbfb\xa4m\x1b\x19\xcd\xde\xbf\x83\x955 \xb5\xfb\xf9\xbf\xe2\xf8sO\xf0\x7f\xdc?\x91\xb7\xad\xf2?\xb3z\xbb"WE\xe8\xbf\xe4\x8d^{zY\xee\xbf\xeb\x1e\xe6\xff\'\xf9\xdd\xbf\x1b\xb5\xa2\xf04\x85\xe7\xbf\xe3\x89V\x89\x14\xbd\xe7\xbf\xac\xb5\xe1HI\x9c\xf4\xbfN\x13\xdeI\x1a\xc5\xa1\xbf&Ig\xd9\xc3f\x03@\x99\x9f~\xa7R\x0f\xf3?\x99I:\xb21\x80\xe1?\xd3(>\x84\x90z\xde\xbf{3\x1b\x1e\x98V\xb0\xbf\x13p\x07\xa1\xe4\t\xfa?\x9b]\x82\xc5\x01\x13\xf5?\xd9C \xa3p\xe3\xf8?\xfa\xb4\x12\xd5\xbf\xa7\xe4\xbf\x16/\xcb\r\n\xad\xf4?\xb3H\xef\xb8\xee\xdb\xe4\xbfn_\xe3\xab\x88|\xe4\xbf8\x1e\xc1\xca\xaa\xf4\xe3\xbf[\xb0\xb6\xd2q\x16\xb0\xbfG`\xed\xa5\x02\xd9\xd8\xbf,\x00\x86\xb5\t\xce\xcb\xbf\xbb\\\xe5\xff\xa1\xad\xf6\xbf\x9c\xbe\xe6\xda\xa3\n\xf5\xbff\x13\x05\x8due\xe2?\x06\xed\xe5\x07\xa2\xa3\xb6\xbf\xd0\x03c%w$\xc1?\x1b\x7f\xfb^\x03\xb9\x03@\x01\xd1D\x8e\x147\xc7\xbf\x1d\x15\xd0\xe2\xa9\xbd\xb8?\x8aCj\x92\x8c7\xdd\xbf\xf5\xccod\x18}\xe6?8\xf1\xc5\t\xbb`\x02\xc0\x8c\xfe\xe1\xb6~\xf5\x06\xc0\x127\xcb\x19\x1f\x8d\xf2?\xb5\xc4\x87\x91D\xac\xc8\xbf\xbe\x1b\xee\xc5AB\xf1?\xec\x03\xfbE\xc4\x7f\xfc?r[d\x03\xc7\xec\xed\xbf\\\xbd7\x7f\xad\x17\xb7?\xa2\xd6\xab\x03~\xb8\xcf\xbf\xc2\xf62b"\xc9\xfa?!\x8ci\xccw\x1e\xc4\xbfAi\x96\xee\x01\x81\xfb\xbf)\x05\x92}\xad\xd5\xe8?I\xb9\x9bs\xe5J\xf9\xbf\xfc\xf3v\xa9\xba\xb1\xb9?\xa9\x90v\x90\x13\xc6\xf2?%d\xed\xfd&\x8b\xf8\xbf\x05:y\xfe#&\xda?\xbduH\xe1\'\xc6\xe7\xbf\x10\xe0b\xa0\x12o\x02\xc0\xcd\x93\x94\'*{\xdc\xbfK;\xa3\x05\xbd\xa7\xbc\xbf/\xdd\x9d\xed=\xda\xbb\xbf\x8e\xcfa\x1d\x14\xd1\xef\xbf\xa3(:\xb1\xc7\x13\xf0?\x96\xc5|\x9b\xe51\xd9?C\x92\x84\xef.m\xf6?\x15%\x93L\xa3\xf9j\xbf\x16\xed\xfc\xf9\x03\x80\xfb\xbf]3\x94D(D\x06\xc0\x9b\x87\xc8\x9d\xcf\x88\xd7?~\xa6\x0b\xaa!D\xe1?V\xaa\x05+\xd6\xd2\xcf?\xb8q\x8c\x8f\xf2\xb9\xee?!\xd9-\xc5\xabV\xe7?\x96"\xd1\xe7\xf6\xae\xd1\xbfB\x1c\x8c\x9b\x16>\xe7\xbf\xc4\x8aX\x0b#\xb4\xe9\xbf\xc4\xa0\x80|s\x97\xc6?\xae\x9a\xbb\xd8\xf1M\xe6?\x00\r3\xef\xe2\xce\xe7?\xcb\xa3\x8cz|\xa4\xda?\x00\x8f\x7f\'g[\xf3?X\xee\x9d/\x1eX\xe7\xbfG\xf8+\r\x86\xad\xd3?\x81H\x9a[A#\xb0\xbfz\x97+\xach\xf1\x01\xc0\xa4\x91\xc5\x17\xe1[\xce\xbf\x1dS`#a\xe9\xdd\xbf\x1dZ\xcf\xc0\xe6\x88\xe8?\xd5\x80\x8dn\xe1\\\xe5\xbf~&\xb3Q?H\xe5\xbf\xd5T\xd83@\x83\xc7?\x8e\xf1\xbd"t\x15\xf2\xbf\x07\x84n\xe1L\xdd\xe0?\rB\xaaM)\xab\xc7\xbf\x95{\x9c\x7f\xe3\xed\xe6\xbf\xe4\x1cB?\xb1?\xf5\xbf\x92j\xb4U\x86\xe4\x04\xc0\x89\x8be\xf4\\k\xf9\xbf\x1e\x1c\xb4D\xd1\xe6\xf7\xbf\x05\xa4\xef4\x12\x88\xf4\xbf\\\xcb\xa9.\xd7m\xf0\xbf\xa8d,\xde\xf5\x0c\xa9?\x11\x1di\x1d\x05=\x04\xc0\xd1?\xfe?\x19L\xd6?\x9e\x14\xc2\xed\x10\x0c\xd8?.\x96V\xfc{^\xf4?\x1fz#\xb12/\xb0\xbf3UY\xa7\x17:\xf9\xbf\xb6\x8e\xaf\x97.\xbc\xfb?A\xe9\x00H\xd7o\xaf\xbf3\xf00\t\xcb:\xe1\xbf\x8d\x04=gM-\xe7\xbf$^^\x0e4U\xd6?\xb5\x16\x12\xa7\xbd\x1c\xe8?\xbb\xa4\xfd\xac\xa8s\x8f?\xd0s\xa3BO\xa4\xf3\xbf]z3\xc6\xe7\xa6\xe6?\x88\xb5\xf4X\x0bj\xdc?\xc0\xf2\xdf\t[\xda\xcb?\xe1\x91M\x16\xb2\xd4\xd2?\xf14\xb32\xaa"\xec\xbf\xa0AOd\x98\xc4\x82?\xec\x9f6d?i\x02@\x12$\xf3\x9f\x8bg\xe8\xbf\xb4\x0c\xfa\xe3\x02W\r\xc0\xcaZ\xc1}\x9cm\xd0\xbf\xf6n\x1b\xd3,1\xf1\xbf\xa2,\xb4\x16`\xa9\xe6\xbf\xb1\x02\x10}|*\xc0\xbf\x9c\xa5\x8576\xf2\xf4?\xe8\x82k\xf8\xe8\x0e\xdd\xbf\xd91r\xe2\xf4\xcf\xd8?\x9a\x07\xf6~A\x1d\xe7?\x07\xdd\xd6L\x00X\xfd?\xbfw\x05.\x1eA\xed\xbf\xc0Z\x0b\x14k.\xf3?TA\xba\xc0\xfc\x08\x04@3\xcf\xf8\xf7\xfc\xea\xa6?\xfc\xffw!\xde+\xf5\xbf\xddzB*\x9b\xbf\xd9\xbf9\xbb\x02p0\x97\x04\xc0PB\xd0lZ\xc6\xd4?wv\xd9\xfe\x1f\xe3\xed\xbf\xafo\x95\xd6\xee@\xcb\xbf\xda\x04h\xf3\x1fn\xed\xbfAk\xdd\xc4\x81\x87\xfb?\xbdc\x14"\xf28\xe1?\xd5\xb8s\x84\xe1A\xf7?_\xb9\x1e\x94o\x8b\xe3\xbf|]?\x17<\xac\xdc?\xcf\xfa\x10a\xb0;\xc6\xbf\x84\xf5\xbco1V\xfc\xbfS\xa4\xd1\xe7\xbd\xff\xd2\xbf\xd0\xa2\x9bT*q\x08\xc0\x17\x8b\xaa\x9d\xe0\x07\xe0\xbf\x10\x03\xd8\ns\xbe\x06\xc0\x16\x85\x17\xe2\xbfZ\xaf3\\\xa9\x90\xf0?\xfb\xa3g\x96\xde\xe5\xed\xbf`\x8a\x82i\x9eG\xe8?V\x89\x7f\x06\xf1\xb1\xc2\xbf\xd7\x8aD\xd3\x03\x88\xf7?\xaa\xf32\x8c5^\xe7\xbf\x97\x85\xa4\xf9\xb7\x97\xf7?\xce\xfd_Z\x896\xfb\xbf\xa7\xf1TGpZ\x94?\xf7\xec\xd2x\x13h\xda\xbf5\x83\\\xc0\x03\xa2\xb6\xbf$BD]\xbbT\xee\xbf\xf3_R\x8d\xdf\xf0\xe8?\xb8\xb0\x15\xc0\xe0\x13\xdc\xbf\x8f\xa2\xfa\xdc\x11\xe7\xc7\xbfPi\x91\x01y\xb4\xe6?B\xaa\xd7\xcc\xb0\xab\xf5\xbft\xf5\xa5\\\xef\xba\xb5?\xa324\xe1\x97\xe7\xea\xbfx4\xf51\x81\xd6\xcb?C\xa4oE>\x9f\xf0\xbf\xe9UfN\xc1\x06\xf3\xbf\r\x8c7\xce\xc0f\xf4?\x97\x9c4(\x81\r\xdb\xbf\xacy\x92\x9208\xf4?\xf7\xd7\x97\xa4\x94\xce\xda?z\xe8U\xff\xf3\x0c\xd4?\x9d\n\x1c\xe34\'\xef?\x9f&\xdam\xbel\xf7?z\xe1\x8c\x8bb\x11\xc8?D\xba\x1bV(\x13\xd2\xbf\x1f\xfakV\xa2\x15\xa9?yap\x9f\x19l\xc2?g[y\x8fC\x9e\xe3\xbf\xd2\xf7\xe5\xc4\xd4u\xc0\xbff\x01\x102t\xdd\x03\xc0\x06A\xa2\x98\xb04\xf0\xbf\xa6\x14+\x9dj{\xf3\xbfo\x94\xb2\x8e5p\xfd\xbf\x8aSE\x1a\ng\x0c\xc0\xe7\xd9\xe3\xc9\x1d\xee\xf6\xbf\xa9\x15z\xba\x12\x86\xda?\xea}\x8e\xef2\xa9\xff?%\xa9\x02s\x1e\x11\x04@_\xdcNGD\xbe\x00@\xa3\x81\xda\x836\xe7\xeb?M\xdb\xd7\x81\xc0\xa7\xec\xbf7*dV\xc2\xee\xf6?\xe2\x97,\x05B\xd8\xe0?5\x0c0l\xb2\x01\x04@\xe3qi\xa3\xc2\xa4\xf3\xbf\x83\x88\x07\xc1\xc5\x1d\xe2?b\xcf\x0f4\x0bl\xda?\xa4\xc4\xbf\x12\x13_\xfa?\xd3\x1d\xae\x90\xce7\xd8?~ZC\xd8\xff4\xea?3=M|\xd2\x8c\xe8?9\xd4\xed\x8f\n\x93\x01@\xbf\xcc\xc8\x1f~\xce\xed?\xf8C\xb8\xc3F4\xca?\xadu\xd8Y\x10:\xe1\xbf\x02\x95\xc9\xd5\xb7\xe6\xd0\xbf\x00\xfe\xf7l\\\xa2\xe3?\x99\xfa\xbf\xc4\xec.\xd8\xbf\x9fy\xd7vv{\xec?D\xb3\x18\x04*~\xe7\xbfV|\xf1\xef\x17X\xe2\xbf\xb536M\x8f\xce\xd3?\x89"+e1t\xf0\xbf\xb7\xe9\xb0\xe6\xed\xae\xf1?\x18\xf6\x9a\x007\x16\xf2\xbfw\xcc\xd3}\x8cE\xe9?\xcb\xa9E:\xf7\xef\xe6?\xa4\xef\xa8rU\xd0\xfc?\x905\x11b@\xc5\x04@\x1fk\xb0\xc2i\x08\xfe\xbf\xdf\xe7\x89\x0b\x87~\x01\xc0\xe5\xcd^\xecv\x13\xa5?Z\x8de\x812`\xcc\xbfuH\xc5\x11>\x86\xff?<\x11\xfc\xa6\xbf}\xe7\xbfbg\xf0\xadvH\xdf\xbf\x96*\xf7\xda=\xd1\xff?\xd4ST\xd5\xe4{\xc3\xbf\x85\xd3?\x06\x84\xdf\xc1\xbf\xc5\xd3\xc7?pM\xde\xbfPSyt\xcdH\xf9?\xfc\xdai\x98\n\x15\xde?\xc5\xcf\xd4fM=\xf9?\xe0\xe0\xebG\xe2\xf4\xc0?\xc6{\xc9e5C\x00@\x89;\x82\x19\x15\xb8\xcf?\xbe8\x98\xef\xb2J\xe5?\x8fE*\x93$]\xd6?T\x10p\x1c\tN\xb5\xbf#u\xd6)/4\xe5\xbfn^J\xc99\xd8\xf5\xbf\x13\xd0j\xab\t\x90\xc0\xbf\xea\x17;%\x06\xfb\xec\xbf\x91s:"u0\xc4?\xa2\xfbw\x8f\x81\x93\xe4?\xb9\xe6\xa1\x06\xd1\xbd\xe6?"&\xf9%\xba\xe3\xbb\xbfF\x81q\xc3mT\xd2\xbf\\]\x08\xa4\x85@\xee\xbf\xbe\xadl\x1a~\xa1\xf2?v\xc6WA\x97\xd5\xe1\xbf\x0b\x9c\x89\xf7\x06]\xff\xbfj\xe9K\xf2o\xd8\xe5?*\xb9X\xe1\x1dc\xf6\xbf\x93$b\xeaQ\xe2\xf5\xbfq\t\x1c0\x01\x7f\xf2\xbf\xc2\x90\xf5\xc5z\xf1\xfb?\x12\t\x00\xf9\x90\xa0\xd8\xbf\xfc\xb0Ez\xac\xfc\xcc?:\xfc8\xbf\xeao\xef?\xce\x0f\x96\xa7\xbe`\xff\xbf\xc4\xf9\xfc\xd7\xf6\xa3\xc3\xbf\xd3\xf0*\xbdU\xbb\xd1?\xac\xd9l\xff\x9a\xed\xe4\xbf\xcdE\x8fK \x9b\xef\xbf(\x96Z\x16\xebD\xf2?\xba 1\x9c!\x17\xc6?\xfb}\x1b\x14`9\xe6\xbf\xaf}M.\xa2\xdd\xd6\xbf\xd7\x82\x899\xbbP\xe4\xbf\x1f\xe6\xa3\xa9\xd9L\xca\xbfP\x8dFBEr\xf2?\xd544\xb0u\x07\xd2\xbf\x1fx;R\xb7}\xd9\xbf3q\xdf\xfbk\xba\xe4?z\xf8\x9a.\xf8\xb2\xdd\xbf\xfc\x05\x1bP\xfe\xc2\xe3?\xf4D@\x91\xc4T\xf4?\t\xcbYM\n\xd6\xec\xbf\xf6X_\x9b-\xc8\xf1?9\xb1N\xeb\x8cw\xc0\xbf\xc2\xf4\x86\x18\t\x89\x08\xc01\xc8\xfbC_\xe1\xf3\xbf\xbe\xcf\xd4l\x92\x98\xef\xbf\x17"\xf6B\x08\xce\xf3?\x11\x1dF#\xf3\xcf\xe5\xbf\xc5\x01\xc9[=\xa1\xd1?w\xd2\x91\xbag\xfa\xe6?\xfa\x0e&$\x01b\xec\xbf\xc0a\xc8\x87M\xf6\xdf?\xa4; \x1fB\t\xc0\xbdn\xe8J\xce\x92\xc2\xbf\x0cS;\xe4\xb8y\x02@\x93\xd9v\xd7\xa7\x1a\xf1?\xbc\xaa\xe7W\xb0\x93\xf4\xbf\xa4P&\xa1r\xf9\xe5\xbf\xf6\xdb\x05r(\xe8\xd9\xbfd\xba)#I\x10\xf4?\xa6\x0b!\xa2g\x85\xec\xbfk~\x18ug(\xe2?n\xb7O\xc5\xfe\x0c\xf6\xbf\x9bc\x97\xb0\xbc\x14\x02\xc0*D\x9e\xb5/\xeb\xc1\xbf\x80F\xc70\x9d\xbc\xe0?\xf5\xb2gdOc\x04@^\xa0l\n\xebY\xc2?\xa7\xa9\xe4\x08{\xae\xe2\xbf"R\xab\xc6\x9c\x98\xf0\xbf\xab\x96\xae\x91\xf0\xaf\xf3\xbf\x1f\x11\xb7\xd2\xf5\xac\xf9?\xb8\x03)m\xe1y\xe4\xbf\xa7\xf7\x91\x7f@\x80\xee\xbf\x96Z@\x85\x1e\t\xe5?%0K\xd5\xd0\x87\xda?H\xd5j\xb2\x95\x9c\x05@?\xa10\\\x1fs\xc3\xbf\xf5\xf6tcv\x11\xed\xbf\x84"o\xe5\n\x9a\xbd\xbf\x1c\xe8\x86^\xfb\xa7\xf0?Vm\x9acA\xa5\xbc\xbf\xe4&\xd8R\xbe\xd2\xf2?g-+\xe1\xcb\x17\xf2?b\xd4\xb9G/@\xf7?k!\xae\x04\xe3\x95\xf0?R9G\xfa\xff\xf4\xbb?\xb3\xd6\xfa\x97P\xc0\xd4?\xea\x85%S\xfb\xff\xd7?n\x94J\xca\xe8\xbb\xe5\xbfn\x93\x7fa<6\xd9?t\xc2Q\xf2\xb2\x97\xeb\xbf\r\x9e^\x92\xa6\xc7\xda\xbf\xf0\xa1\x01Z^\x87\xe6?\x1f\xd6y\x13\x1cv\xc5?\xff\x12\x17G\xe3\x9f\xf6?\tt?\xb9V\n\xeb\xbf\xf9\xd0.{\xbb\xfd\xc1?\xe7\x88\xe921\x8b\xe6\xbfF\xd1\xc4\x88\xce \xf6\xbf\xe3\x81\xf1UH\xe7\xf8?\x9e\x98\xc9+E\xe2\xd4?\xc8\x9d\xed\x9c\xc5\xb3\x01@qv\xfbz\xac\x1b\xec\xbf\x83\xcf\xb1\xf4R\xdd\xf9\xbf\x88cz\xd1j1\xbe?\x0e{\xed\xc8\x04<\xef?\xb3e\xd5*\xf4\xe8\xd1?S\xd0U3\xd6K\xe6?\xe7\xa3\xc2B&[\xdc?\x901[\xa75)\xf1?\xd10Z\xba\xcc\'\xda?\xcbx\xf9\xa4\xc8\x08\xe0\xbf\x8d\xd8:\xc6\xfa\x90\xd7\xbf\xfd\xca`\xbbf\xb4\xea?\x02\x89D~j\xc2\xc5\xbf\x0f\x8b)\xbeg\xdc\xdc\xbf\xe0\xa00uPE\xce\xbf\xbbR!\x8b\x1e\xd8\xe3?\xf2\xeb\xdaC\xbc)\xfc\xbf\xda?W\xfa\x11m\xd6\xbf\x88a\x15D\x08V\xfe?\xfb\x8c\x8f\xae\x98O\xe7\xbf\xa9\x17\xd4\xd83\x8e\xe0\xbf\x1c Bp\x82\xf2\xcb\xbf|\xa3U\x81\xd4\xf2\xf0?X[\x15\x8c\x05\x80\xec?\xfa\xc0\x95\xf8\xe0U\xe3\xbfQ\xa92|\xfd\x1f\xf5?\x9e\xe1\xf1Z\x85\x86\xf0?\x8a\x1c\x84\xd5\xb3\xde\xdc\xbf\x0ff\xf9!M\xa3\xcc\xbfX\xe7o\x1e\xd4/\xcd?A\xb8\xb7\x10\x10\xeb\xf8?\x12\x99@\xda7\x8e\xee?un\xc4i#\xf3\xeb\xbf5\x8b\xa7\xa3\x99\xa3\xe0?o-q\xa8\xef\xc7\xf7\xbf\xdf\x01\xe2\xc6\x88\x98\xc6?8+@\xa0\x0ba\xdc\xbf\x1e\xb3\x9b\xedV\r\xdb?\x03\nT\xf2L\xa9\xe3?\xb0\xa7j\x85\xfdC\xd6?\xaf-\xc4\x9c\xe3\x91\xc8?\x8d\xab\x11&\xf1\xbb\xeb?\xd7\xd3D\xf2\xf5)\xfa?\x02U\x99\xf1\xdb\x11\xdc\xbf\xda)\xc7r\xde\xe3\xe8\xbfo\x1d\xaa\xa7\xf2X\x01@I\xab\xbcW\xbb\xd8\xee\xbf2\xdc\xd5\x0b\x80\xe8\xcb\xbf\xc2{\xe7\xfc\xe3\xfb\xfd\xbfcw\x18\x80\xcd\x86\xe6?\xd5\x90S\x1e\xe0[\xf5?\xe0r\xc3\x8d\xa01\xb2\xbfg\xfeE\x1b\\\x04\xea?\xbc\xc1\t\x84J\xc2\xc3\xbf\xbf\x91o\xa1\xe8\xf6\xec\xbf\xee\xecJ+Z\x07\xbf\xbf\x9b\x92\xdb\xb6\xd3Z\xf4?J\x8cv\xb2\xd1\xc2\xf3\xbfF7\xd0\x08@\xe7\xde\xbfk\x13\x08\xf3\xf6\x8d\xb5?\xf7S\x9b\xacV\xcd\xb1\xbf\x17p\x8e\x9d\xde(\xeb?|\r\xc9\xd6vU\xf3\xbf\x04M\xbb\xcd@\x04\xf1\xbf\x16/\xa2I\r\x98\xd1\xbf\xc5\xd5k\xaf\xc4\x10\xe2?1um\xc3\xe3\xdb\xe8\xbf{#\x03F\xe2\xa7\xe8?\xf4\x83\xecM3$\xe9?\x8c\xe6F\xdfi\xbf\xeb\xbf?\x99G\xc4E\xa1\xc6\xbf\x0c\xb7\n\xeb\xfc\x1a\xaf\xbf\xd0(\x18\xd2\xabx\xfa\xbf\x92\xb9j`cd\xd6\xbfk|\xf3\xacR\x81\xfa\xbf`\xb3\x94\x8cAN\xe1?C\xc8q\t\xc3#\xc7\xbf\xc4\xde\xd3\x8b\xe6j\xde\xbf\xc4P\x1a\xb1\xe0\xa7\xfd\xbf\xe1V\xda\xfe\xd5A\xe7\xbfgN\x05\x8f\xa1\xaf\xf9??`\xa3\x82\x15I\xe0\xbf\x1e-l\xa0\x90\x13\xd3?\x04Q\xa5\xf1\xca\x8a\xe2?\xe6\xd0(k\x1c\xb8\xf9\xbf\x17\xd9\xf6\x0e\xeb\x02\x00@\x17\xa5\xab\x9c\x88\xf4\xd0\xbf\xfc\x9d_f\xc4\x8a\xf5?\xe9\xd6Z\xf0\x14&\xe4\xbf)\xe4&\x9a\x91]\x04\xc0\x97j\x07n\xca\xeb\xe8?~t\xcaX\'\x06\x00\xc0gO6\xc4\x9d\xe4\xeb\xbf\xd6\xd8\x9fP\xb7\xc7\xb0?0\x8a\x03JAG\xea\xbf\n\x03e\xb3\xbdn\xf5\xbf^h}(\xed\xa0\xe1\xbf\x89D\x87\x1aR\xfe\xdb?f\x83\x8e\x93\x886\xe1\xbf\xff\xd7^\x9e\t\x83\xe3?\x8f\xd9\xdc\xc8\xbe\x9c\xf1?\x94\xb24b~\xb5\xd9?\x91\xed\x80C\xd0\x99\xf4\xbf/Pb|\x93\xfa\xd2?O\x03\xd7\xb3>\x13\xe3\xbf\x11\xc6D\xbd:\x07\xe7?\xb0!LY\t\t\xfa?\xa3A%V\x04~\x06\xc0{\xf3\xaa\xdd|#\x04@\xc4\xce\xffm\xab\xe4\xe0\xbf\x86\x1d\xdf5:\xbc\xe4?.\xf7\xcc\x90\xa8\xaa\xcd\xbf\x87\x97\x96\xb1\x1b\xb5\xb6\xbf\xc38\xdc!y\'\xeb?\xacS\xdd\x93\x02\x89\xbf\xbf\x8d,\x85\xbe\x91\xe3\xcf\xbf9\x84\xd3\xca\x8fi\xd3?\xc9\xf4-\xcaX(\x00\xc0\x8f\xfdD\xf9\x9cM\x95?\x7f1nt\x91z\xe7?Y\xa6\xa3\xcb\xeb\xa0\xd6?\xfb[S]\x87\xc8\xeb\xbf0p\xf5\x1cPt\xd3\xbf\x922 \xf4\x17c\x97?\x01]U\xe5\xe9}\xe4\xbf\x15\xa3\x91\x8b\r\t\xe9\xbf\xed\xbb\xa5+\r\x9d\xef?\x02\xbdT\xbd\xd9m\xf2\xbf\x8cv\xf0Q\xe6-\xf4\xbf\r\x9f\xa5\xa6\x92R\xea?u[U\xf5\x80\xbf\xfd\xbf3\xca\xe1\xb9\xaa\xe2\xed?\xa5m}\xe1\xa5z\xe3?\xfe\xcb\x82\xe1\xb5@\xbf\xbf\x894\x08\x05\xcb\xb1\xd2?\x08\xebd\xca\x90u\x02@\x05u\x15|hW\xb4\xbf\xa8T\xef\xdd\xf0\x07\xcc\xbfx\x9d\x9f\x8fI\x8d\xf9?Zn\xa6\x17i[\xde?\xe7\x9d\xecN\xf8-\xd1?L&\x96\xb4\xe6\x1a\xea?\xcb\x10\xd97"\x95\x02@\xf0W&`\xe3\xa6\xc4\xbf\x11\x03m&\xde\x16\xfb?\xfc\xb1\xf3\x05\x08\x97\xef\xbf\x08\xda\xd7M\xc2\xfd\xed?N\xe9%O\xe8\xb9\xd3\xbfz\x08+\xbbk\xe9\xe0? \x0e\x1c\x06[,\xf2?\xee\xad\x12\xe4x{\x9f?\x83\xbe\x0bQ\xcf\x81\xe5?\xf1\x7fv&\x11P\x82?\xcaJ\xeex}s\xf4\xbf\x1e\xcc\xc7Q\xbe\x92\xa9\xbf\x97\x05\x7f\xfeX"\xc7?\xfa\xf57\xca\xc9W\xf2?\xf4\xf6|\xb5\xb3\xa3\xe0\xbf\x94\xdd\xa3T\xef\xf9\xf6?\xd9Ac\x170\xc9\xf5?\xc1p\x96\xb7V[\xda\xbf\x970\xdb]\xd4\xef\xe8\xbf\xa4\xc9\xc4:O|\xf3?\xbb\xf4k*\xde0\xc7?\x92\x97\xb0\xfb\x0f\xb7\xb5\xbf\xad\xdc\xaeJz\xbb\xa6?\x8b-\x9b8n%\xe1\xbf(K\xfd\xf4\x9c\xde\xd0?\\\xec5\xad\xc1\'\xca?#\xba\x0f\xd9\xebX\x00\xc0\x19$\x00\x1d\xe5Q\xe1\xbf,\xf2\t\n\xd9\x16\xec\xbf\x0b\xe8\xf4\xa9\x86/\xfa\xbf8\x0c%\x90\xc0\xd5\xc3\xbf\xb1>\xfc\xe4\x8br\xd6?\xd8Y\x974!\x90\xe7\xbfcL\x87M\x0f\x14\xf5?\xd3{\xe0@q\x8b\xe6\xbf"\x1f\xe0\x86\x9a_\xf8?\t\x01\xcf~\x15\xc7\xf8\xbfh\xf6or\xedm\xf1?\xfe\x9a\xaa\x19\xac\x12\xe4?#\x1a\x15\x89\xfc\xc9\xd7?\x8e\xdb\xc8\xd4.\xe1\xda?1\x90a\x8b\x0c\x88\xfc?_\x14U\xc5\x0ee\xd0\xbfII\xeeS-\x8b\xf2\xbf+}\xec\xca**\xca?\xfcUi\xdcn0\xb5\xbf\x810\x01@\xc3\x13\xfc\xbf\xbe\x9e\x84"\x91+\x00\xc0\x0b/\xf4|\xb8f\xe1?\x93\xac\xae`\xf7.\xe2\xbf0v\xc1\xda\xaf\xc4\xd3?\x0f9\x94\xc2\x02\xdb\xed?\xc1\x92\xf0\x01\x85a\xc2?\xe9?\'#y<\xef\xbf\x14\x00F\xc9L<\xef\xbf\xc5\t\xf4\x97K\xf7\xee\xbfo\x98{\x0e\xd8\xa3\xae\xbf\xb6u\xd3\x07\xfek\xf5\xbf\xddE"\x86\xb7\xcb\xd7\xbf?\xdb\xf7r\xcb\xd1\xed\xbf\x96p \xc2\x161\xf4\xbf\xb3aF\xb7\xc6\xa2\xf1\xbf\xeew%\xf6\xb6\xf4\xfb\xbf\xf3\xd1\xf6\x94<"\xa3?\xe2\xb5\x11IX\xae\xc4\xbf~\x81\xbb\xea\x8b\xdc\xfb?j\x95\xa8\xceS\xe1\xe4\xbf\xab1\xb2?\xe7\x99\xda?\xfc]\xee\x9cb\x19\xdf?\xadC\xf9\x8f\xc7\xd2\xed\xbf{\xc0H\t7k\x02\xc0)\x08\n\xc2\xae\x88\xf2\xbf\x94\x977:\xea\x0c\xf0\xbf14\xe1\xf1)t\xf3\xbf\xbbj,\xfb<\xf7\xf6\xbfz6i\xdc\x1a\xf0\xe8\xbf\x9f\x8c\xd2+\xd7\x9f\xe1\xbf%\xe6?z\x7f\r\xe6\xbf_\x83F*F6\xe6?\xc3\x82r&\xeei\xea?&\xfe\x03}\xecI\xf2?\xed\xf0\xc4SmK\xd4?#H\xe4\xfd\xb42\xd3\xbf\xed\xab\x16\xca\x10\xbb\xd6\xbf25\x98AZ\xe4\xe0\xbf8\n3\t{{\xf3\xbf/\xc6\x1dwQ\xec\xf8\xbf\x8d\xe3\xd5\xad\xc7w\xf3\xbf\xa4\x80\xadj\xf3X\xf6\xbfhB\x13\x98\xfb\x95\xfb\xbf\xd9\xdf-\xc3\xe2]\xeb?\xeeM\'\xa3\xee\x08\xf1?\xd3V\x8f\xc9,\xb0\xb9\xbfYL5\x05P6\xe4\xbf\xd5`\xddk\x08\x1a\xd3?\xb3<\x87\xb3(9\xaf?\xc5\x1c2=\x96\xde\xd6?\xaf\x95P\xd2F<\xe8\xbf\xe8\x90\xc2i\xff\xf6\xc8\xbf\x96\xc3\x11\xd0\xf0&\xfc\xbf;\xdb\x19"\xe74\xe8\xbf\xda\xa8\xf4,\xf1\xa6\xd4\xbf\x1eV\xf5\x8a\xb3\x16\xe2\xbf\xed\xf8\xaf\x04"\xe7\xcc?;\xef\xee\x10\t\x14\xd7?\xc9\xbc^$Yu\xe5\xbf\xdb8\xc4\x8bL`\xc3\xbf\xac\xd1*\x07\xbd\xc8\x00@b\xe2"\xe2\x9a\xb3\xe6?L\x8f\xadQ\x15\x00\xe5?\xe4z\xd8\x1e$L\t@<\x87\t\x94rZ\xcc?s\xaa9\x8f\xba\xf2\xdd\xbf\xf2\x9cL\xd1\xc7\xa8\xf8\xbfA\xc7\x87\x8f\xd0\xc8\xf0\xbf\xddO[H\xc4^\xaf\xbfl\x90\x9bE28\xf4\xbfs\xf5\xfcd\xa6>\x01\xc0\'G\xe6$\xfb\xc0\xd0\xbfE\xf0\x89\x01p\xa9\xee\xbfX?\xdf\x1d\xe0\x0e\xf8?8\xab\xfd.\x1e\x8b\xf8?\xbd\xf3u\xb9\xa7\xff\xe6\xbfg\x84{A\\{\xd4\xbf\x0b7^$\x19\xd6\xc6\xbf\xae\x8c\x8e\x9f\xbdX\xdc\xbf$\xe2\x1a\x0f9\x8f\xea\xbf\xe1\xe2\x9f\xd3\x93w\xea?\xed\xb0#\x9f@\xe7\xd7?v;\xb2\xeeZ\xda\xf0\xbf\x82\x96:!uS\xe6?\xe9s\xb4\x17|\xb7\xc8\xbf\xf2\xbd\xcd\x0ct~\xfb?\x18\x1e\x1b\xd4\x0c\xb9\xe8?\xc5L\x05\x9f\x93k1\xd1?\xd1\x989\x99\xb4H\x02@\x1d_\xf6\xb9\xf8\xe7\xe5\xbfC\x96\x14\xd8Y\xa9\xe1\xbf[\xba\xd4\xf0P\xf7\xfe?\x7f\xf5\xc5Y\xb9\xd3\xf3\xbf(\xc8\xf3D\xc45\xb4\xbf\x04\x19\xef\x9c\xf0\x8f\xd6\xbf\xe9\x84\xc0\x0fM\xf6\xe8?\x93bb\xda_O\xe9\xbf\x1a\xbbk\xd5w\x0b\xf5\xbfjM\xd8\x1b7\xd1\xe3\xbf\xb7s\x9a\xc9\xb0\xce\xc8?\xa5\x9dw\x8cT\x03\x93?\xee\xd9\x9b7\xf7\x16\xf1\xbf\xd2t\x04G\xdd#\xef?\xd8\x13\x1av\xa7\xdf\xef?\xcb\x9d\x9c\xe0\x11\xb2\xa1?\x94z\x7fF\x19\xd3\xfd?kf\x81x\xc4,\xf1?\xcc\xc9A\x93\x8c\x1d\x00@\xda*\x9b\xc1\xe0\xfa\xe2?\xa5-)\x8aI\xe7\xf5?\t\xa1x\xd2\x02<\xdd?;\x0b\r\xb3\x853\xe6\xbf\xdeGjst\x8b\xfb\xbfG\x86%\'\xed\xc6\xfb\xbft!\xd3\x0c\xb8\xd8\x02@\xccZh\xc5\xde\x00\xf7?\x90\xdf\xa3\x8c\xa7\xf4\xfc?\x18\x05\x99\xdf\xda\n\xc6?7\xaf\x9f\x91"l\xc3?\xe9p\xad\x1bD\xa5\xe3\xbf!8J\xc8\xb3\x98\xff?\xc6K\x9a\x1d\xbcc\xe0\xbf\xce\xdd\xa4\xde\x06s\xa8?\xba\xfe&\xe3\xc5\xf8\xdb\xbf\xf8\xaeu\xb9\x01<\xe8\xbf\x03\x9a\xe9\x02\xb4W\x00@Y\xb7j2\xf8R\xfe?Lf\xdb\x9b\x90\xd6\xc0\xbf2\xe9\xa4\xd7B\x03\x04\xc0\x1f\xf8\x84\xf4\xdb\x19\xd2\xbf\x17\x1b\x8b\t\xe9\xfd\xb4\xbfo\x87\xa0\x08H\x98\xe1?\x17T\xde\x17\x7f\xe0\xe1?\x98\xc1HH\xf7\x8b\xeb?\xdcX\x9c\xfe\xb9!\xf4?\x13\xea?\xbb\xac\xe4\x00@v\xf8-{H\x88\xec?EM|\x9d\n\xb2\xfa?v\xaaN\x8e\xad1\xee\xbfVJ`\xf9\xa2\x8d\xed\xbfqk\x89\xc8]W\xfa\xbf&\\\xc2u"\x90\xe7?\x14\xd1\r\xac\x8b}\xd3\xbfL\xda\x04a_\x08\xe7\xbf\xb8=\x0e6H\x8a\x00@#\xdb\xd9I\xa1\x8f\x02@X-\xd1bS\xef\xd8?\xa4QBK\xe4o\xf1?*q\x1c\xb2s\xaf\x07@-\x00\xc3s\xf0\xd4\xbe?\xc7UG\xa7\xef6\xc5\xbf\xd6\x96\xdf\x0f\r-\xa4?P\xb6sdu\xfe\xf4?\x9f\x1f\x07H`\x96\xe3\xbf\x88\x95EJ\x9f#\xeb?\xb1\\\xa5\xadX\x0e\xd5?\x0eo\xe2\x13\x07\xb8\xea?}\xe1\xa8\xa0\xd5\n\x01\xc0\x14L\xc0DYM\xd8\xbf\xf80\xd8\x82\xe7C\xd1?\xdfl\x99U\x1a\x8b\xfc\xbf\x8b6\xbf\x9a\xef\xae\xed\xbfL4r\xa8[\x9c\xeb?\x17\xccR\x9f\xa0\x8a\xd1\xbf\x93\xf5M\xf1\x1b\x9a\xe4\xbfp\xe6\xd0n\x97\xaa\xd2\xbf\xda\xcfY\xfe\xef\xd6\xf7\xbff\xdeE\xb8\xb2\xde\xec\xbf?\xcb.t}\x01\xf5\xbfzu\x0b\x1bCn\xc8?\xc6\xb7o,\x17\xcf\xc5?Z\x84\x0e7`\x99\xf0?h\n4"\x8a;\x08@\x82/5i\xfb`\xff?I\xfb\x87W\xc4A\t@\xef(\xb5\xd5\xa1\x8b\x02@\xd9\xff-\xc9b&\x00@d\x94\x91u\x90@\xf9\xbf\xd4\x97\x8c\x8e\xee\x1d\xd3?%\x18\x07\x9aw\x9b\xcb?\x85Y\xa2\xec\xc0\xbd\xd0\xbf%\xf4o\xbd\x921\xb0?\xba\xf7\x11\xa3b]\xfa?\xff\xb7\x13\x838\xad\xcc?\x98@B\x1d\\a\xe5?\xbf\xff.<`w\xfa?\xfd3\x84v\x87i\xe6?\x8e\xa9\x1e\tb\'\xf6\xbf\xd0V\x19Y[\x0b\xfa\xbf\x8aM\x17\xa2\xe8z\xe1\xbf\xc9\xdbTt\x9d*\xc4?`\x8dx\x10\xdb\xf6\xf2\xbf\x8dc\x9e$ W\xfb\xbf0b\xbdmW\x93\xe9?\xae\x15%\xde\xc3\xc3\xc8?\x98\xd6\xe7\xc4\xf8m\xf5\xbf\xdc\x85\xc5\x188\x86\xe6\xbf\xc9\xd3\xd4\xa2\xc2\x1e\xc3?\xc6\xb2\xfa\xfe\x00p\xee?C\xbf\xf7\x83Zh\xdb?%K\x9e\x92\x1do\xf2?\x8f\xdc\xe7\x03\xb7\xb9\x02@Od\x08\x90 Z\xd7\xbfwY\xc2\x8b\x066\xf8?\xa8\xac[Pi\xb5\xec?\xbcw\r\x9bq\xb2\x02\xc0s\xfe\xa7.*\x08\xe0\xbf\xbd\xa3\x1b\x84\x85c\xef\xbf\x13\xfdf\x7fn\x96\xe9\xbf\x16oV\xbf\xa24\xe7\xbf\xa6\'\x04\xea6-\xf0\xbfC\xac\xa6\x1b\xdc\xea\xc6?\x0b\xd7+\xcd9G\xf2?\x1dg\xa8k$\xb6\xf0?Upx\xb0\xd3\xab\x04@d\x06(F\x8a_\xe8?#\xa7\x0c`e\x8a\xfc\xbf\xa39}\xa7\x94\xa0\xe9\xbf\x82\xb2\x1f)\t\xe7\xf0\xbf\xcc:\x84\xeb\xce\xa1\xf7\xbf\x91=g\xd9\x98I\x05\xc0\xe6\xf0\x85\xa2\xed\xea\xf4\xbfW\x87\x88B\xf0\x0f\xca\xbf\xeb\x92\xf6\xeb\x15\x11\xe9\xbf\xd7\x1b)\xc4ed\xb5?\xb8\xcb\xa3Ho\xf1\xf3?X>\x16T\xa0e\x06@5?\xe8\xb2s\x9a\x00@\x87\xf99\xe2\x03\xe7\xff?\x9d\xb8\xeb\x17\x19\x86\xe6?\xad\xf2D1\xf24\x05\xc0\x8a\x87=\xf2M\x86\xe1\xbfS\xa2\xed\xc4\x1f\xe6\xf0\xbf\x99\xdc\xf7\x10\xd5\xe0\x01\xc0\xfa\xf0(\xd8\xdc\xbd\xdf\xbfq(Kb0\x13\xce\xbfbU\x99 \xcah\xd8?@\xf5\xce\x9f\xb8\xb5\xdd?*\x12#\xec\xff{\xe0?\x83\xfe9D\x8a\x00\xd5?t<\x96\xb9\xc0\x83\xe6\xbf\x0f\xb6\x9b\x11\xb7\xc3\xf6?\x99\xcb\x02\x84y[\xfa?\x86]\x8a9\x05-\t@\xf3\xa7\xa1p\xc0\xf9\xdc?\'\x9eD\xa0\x07\xe0\xe3?^\xdfA\xd4\xa2\x9a\x07\xc0\xbd\xe7\xa5M3k\xf6\xbf\xd8\x17A\xc1\x13u\xe1\xbft\x8d\x04\x8d\xcc\x1b\xdc?\xc1y\xd4Z\x14\x8b\xe6\xbf{\xd5}\xe0R\xc1\xf9\xbf\xcb\x8b}\xd9a\xde\xef?Q\x04\xbc\xf0\xb1\x9e\xbc?\xec\xe2*6\xbf\xae\xf4?6\x81\t\xc0\x86\x12\xf9\xbfqU\xbd\x0c}\r\xf1?G\xdb\x15!\xc3\xc2\xfd\xbf\nEd\xad\xaf=\xfc?I\xf7\x82vT[\xd6?\xf6\x84\xb4J~}\xe0?OT\xa1v[\xe3\x03\xc09\xf6\x9e\xc5\x10\xc7\x05\xc0\xe9\x0cJ\xe8\x9dxQ\xbf\xaa\xbc\x1f\xb5\xb4\xc8\xf1\xbf=\xf5\x1dC+;\xe1\xbf\x83\xb8\xd2;[\r\xc3?k-c\x1f\xe1z\xe1\xbf\x05\x04\xaf\x90\x16\xb0\xe8\xbf\xae5\xf5)b\xf8\xf5?\x15\x9e\xd1\xb211\xea?\xecv\xb6cS\xb1\xf1?\xc6\xdc\x15U\xc8\xf0\xc0\xbf\xb9Bkr)\x9d\xe7\xbf\xd5j\xf1\x02\xc1>\xe3\xbf\xa1?^\xddrnS?\x9f\xa9P\xa9\r^\xf0\xbf\x84MH\x9aL"\xd8\xbf\x9b(\xbf\xcb\t5\xf5\xbf.6\xa9\x02.\x93\xe7?\xfe\xf1\x83j\x10\xa6\xe7?\xcaLm\x91i\xec\xc2\xbfe\x0b\x9b\x84`\x7f\x02@\x03\xd4\x9c\x98\xff\x99\xe9\xbf\xe1r\x9f\xa7\xdf\xaa\xe4?\xd3^wj\x8d\xb9\xfc\xbft\x92^o0\x08\xc2\xbf\xba\xc2\xa0\x89B\xa0\xf4\xbf\xd2\xd9\xe1\xcceA\xfd\xbf"\xe0\xb9F\x18\x1c\xe6?&b\xe8`D:\xaf?_o\xf0x\xc6?\xf1?\x90\x87\xe4\xf5\xc7,\xa0?/\xa4\x997P\xd4\xfc?\x17\r\xb5!\x9e\x8c\xf4?8\xfd\x80$\x0en\xe4?H\x0cbd\xe1\xaa\xeb\xbf@\xcc\x0bJB\x1c\xc5?\xabiF\xe5\x9f\x8f\xe0?%\xabR\x93Ph\xfd\xbf\x06O\xca\xfc\xfcN\xcf\xbf\xd97\xf3\xa31>\xf3\xbfu\x13^\xde\x10n\xe0?\xa9o\xe4\x9a\xd0\x86\xee\xbf\xb61p\xe7\x07\xce\xc8\xbf\xc3_?4\x9b\xe8?\xd0\xd4)\xf0\xac\xfe\xeb?a\x8f\xab\xfeK0\xbb?\xd0\xc3a\x01\xb8R\xda\xbf\x12\x07\xf0\x07\x99\xa3\xf4?\x88=\xeab\x1fo\xcc\xbf\xd0\x1e\xa7\xac\'\xcb\xf5\xbf\x91\xeec\xc8}\xe1\xf4\xbf\xcei\xca\x93\x83\x93\xb6\xbf\xe7^\xc7\x83\t\xfd\xe5\xbf\xda\xe4V\x18\xdf\x83\xf5\xbf?\x91\x98\xc5\xa8\xc4\xfb\xbf.ZF<\x92N\xf5\xbfS\xd6\x9c\xb3\xc9>\xf6\xbf>\xa9\x04~\xda\xe1\xef\xbfK\xf7\x04\xe1\xc2\xd1\xe3\xbf\x04U+\xa7\x95\x8a\xe7\xbf\xfemb\x86\x80\x99\xff\xbf\xe9j\xb4\x18\x9d\xbf\x92\xbf\x1a\xb5\xc6\x13\xa9\t\xd8?sE\x11\xc1\xe2<\xd1\xbf<\xc1\x803o\x06\x83?`\x16\x9b \x19\xa8\xb6?"\xbc\xbc\x9c\x98\x0b\xb3\xbf\xa6\x9al\x8d-\xe5\xe8\xbfNY\x87{p!\xf7?&N\xef\xee\x9c\xb6\xef\xbf\x9awX.\x9e=\xf8?\xda6\x94\xd6\x1f\x18\xdf?,\xe0\xf5\x98\xe4\x86\xfb?\x01\x1d\x10\x98\x813\xef?\x9b5\xb2\x0e!/\xfe?u\x1f\x0e\x96\x1f\xeb\x06@\x8b\xf7\xf4\x0e\x1f\xbc\xc6\xbfx{\xbaf\x01\x80\xa6\xbf9\xa2\xbe\xc8P*\xe5?\xe0C\x1a\x1b\xcb\xd0\xe7?\xfd\xc5\x0e\xb7\xb6A\x01\xc0\xf9\x14\x85^\x1cu\xf8\xbfp$\x08S$(\xfc?>a!>u\xcc\xd6?\t\xeeiHz\xb0\xf3?\xcb\xc4\xc0\x84\x98`\xe1\xbf\x7f\r\x1b\xa4\x05\x18\xd1\xbfaqX\x9c\xd8\xfe\xe6\xbf\x02\xa64\xe5\xf3\xcb\xe8?\xbb\xd6\xf0\xc3\xdbJ\xc9\xbf\x15@\xceO\x96\xdd\xf0\xbf=\xf2\xb6\x18\xc7\x81\xdb?\xb9\xa1m\xab\x08\x1b\xf8\xbfi\xf2\r\xca\xaae\xc9?W\xe4\x99\x9a\xb8\x88\xd4\xbf$\x0f\xff\xd6\xae3\xed\xbf\xc9E\'\x89Y\x85\xe9?\x11\x1d\xb6G+R\xb9?\xe6\xc5\xa9J\x84\x96\xe6?P\xf4\x98v\x17i\xd9?*2\xd4{\x02\x18\xc6\xbf\xab\xa6\xd3\xc7\xa3\xff\xf3?\x05\xcd\x81?\x0c~\xf5\xbfeoR\xd0\xb2 \xe8?\xffi\xb3\x98\x04C\xf1?e\x1f^3,\xd4\xf3\xbf\x8d\xc5\x92b\x01\xc0)\x14bNdk\xdc\xbf)\xe7\xfdD\xe8k\xe7?\xe5l)\xcd\xa0\x17\xe6\xbf3\x05;\x8e\xda\x8e\xf4?b\xd1\xf9\x929#\xe1?\x84W\xadmc\xeb\xe8?\xc2\xa0v\x7fkp\xf9?n\xd3\x9b/\xcc\x80\xfb\xbf\xf4\x1a\t\x0c\x81\xfb\xf4\xbfH\xc6\x0c2\xc8\x19\xe7\xbf\xa4#\xbf\x8c\x83\x99\xff\xbf\xa68]\xbc;>\xda?\n\xe0V\xad\xc7\x8d\xc6?6\x8e\x12\xc0M\x1b\xfb\xbf\xfc\xe8\xcaD\xfb\xbd\xda\xbf;\xa2\x03SD\xef\xc8\xbf\xf6\xf0\xfe\xd0\x83*\x01@\xacn\xe9iy=\xe0?\x13\x06?The\xd0?\xa5\x9bTErx\xf0?_\r\xb5\xb7e\x15\xcc\xbf\x84\x84\x13w\xf9O\xfa?\n\xd7\xc7bL(\xf3\xbfL\xca\xb3{\xaaw\xe8\xbf\x0c7\x167\xc6\xf0\xd5\xbf!jh\xb0\xf93\xfd?\x0c\x92\xb4\x85*\xd6\xda\xbf\x13\xc4>W\xa2\xd6\xd1\xbf\xb2\xacT\x86\xc8\x89\xf3?\x04\xbf\x96\xf9\x94V\xf9\xbf\xdf\xab\x00\xa8\x97\xb8\xb9\xbf\x85[\x87\x90\xe9\x0f\xe6?rn\x80\xd9c\xd6\xf7\xbf\xc9\x0c\xe6\x83\xb3\xe6\xe0?I\x13C\xc2$>\xf1?4b\xa7:\xc1/\xd2\xbf\x15z\xad\xac!F\xf4?\x8a_\xd4{\xd0\xe0\xcd?\xeeF\xc2\xa9#\xa1\xee\xbf\xb7G\xa7\xb9o`\xb2\xbf5\xbdu\xa1\xe8\xe5\xe6?.\xfa\xb9\xd5\x96\x99\x08@\x86\xf6\x0c\xae\xb6;\xf0?\xc1\xe7~p\xe3\x9c\x01\xc0\x01\xe2\xa2\x8e\x9b\xe5\xf5?\x1c\'\xa7\xd5\xa6\xf3\x83\xbf\x84\x04.\xef\x87D\xe1?\xe5>\xa9\xdfk\xae\xe9?H\xff*\xf0\xdca\xb8\xbf\x90Q\x96Xu\xfa\xf6?\xc8L\xe6\xea\xf4s\xdf?k=d`\x1e\x1c\xde?\xc1\x98\xe4\xdc\x1aa\xcd?\xf9\xee\xa8\x96y\x07\x91\xbf\xbb\x84z\xee\t$\x02@\x95\x8a\x88\xaa\xbb"\x02\xc0\xe2\xca\xb9\xfc\xcc\xf9\xf6\xbf\xba\xdb\x9c*\x84\xc5\xec?q5\x95\xc2/?\x03@\x16t\xe5\x93\xf6\xbd\xe2\xbf\xdf}\xe0\xc4`7\xe7\xbfU\x16{\xf6\xb3#\xf4\xbf\x7f\xfb\xa3\x99\x1b2\xf1?\xb2_P7o?\xe7\xbf\x0fK\xd8\xcd\xa5\xd4\xe7?\xeb\x10Z*\xe2\xe0\xec\xbf\xda\xa2#\r\x00E\xf9?q\xea\xad\x84!j\xd9\xbf\xe1.l\x15\xa7\x8e\xd5?\xbc\x15u~\xc2\x19\xfa?\xact\xc9\x95\xeb\x92\xe7?\xf3lS-+\x95\xea?OsU\x99\xa0\xd1\xe7\xbfJ\xb6\xee\x85!z\xee\xbf\tW\xde\xc2\x8c9\xf8\xbf\x90\x01\xe7\xfd\xc8(\xf0\xbfY\x8d\x88\xba\xe2\xaf\xee\xbf\x82tQ\xf6\\(\xf3\xbf\xc2\xbf\xe4\xc5l3\xf1?\xf8 \xe7,n>\xfc\xbf\x86\xd7f\x95\x94X\xd5\xbfx\xf7\xce"\xf5+\xe8?\xf2\xc3\xa4\'\x1d\x1e\xcc?\xc0\x1e\xb7s\x0cE\xe0\xbf\x9c)\x92\xb7\x9ef\xcf\xbf\x91WQ\x0e1j\xbf?\xaalN\x90\n\x85\xd7\xbfz2\xf7\xc4B\x87\xf0?m9\x95w\x12\x90\xad?\x9bn\xdf@\xdd\x06\xa1?V\xff\xb7\xd1\t\x00\xd9\xbfE\xf7\x9f\x15qH\xe0?y\xe1\xfb\x02\x08\xd9\xe0\xbf\xb4I\xa4}T\xd1\x8e?\xb6\x96\xf0n\xd8\xf2\xfc\xbf\x11r\x92\x87\x00\xd3\xea?\xef\xa4.,\xf5\xb1\xb2?|\xda%\xd6\xb2m\x00\xc0\x8a\x848\xadCI\xd9\xbf$\xb1y\x82\xdd\xb4\xfa?\xf9HL\xc9\xea\xf3\xee?\x81o\xb8v\x0b\x16\xcb\xbfp\xc5\xcfi\xa6q\xda\xbf\x97\xd0\xb3$\xcb\x8c\xc6?\x16\x11\xfe\xab\xeb\x0b\xe5\xbf\xbc\xa6gG\xc0\x87\xd6?|\x99\xb7B\x1c\xc7\xf6\xbfD\xbf\xf9m:>\xeb\xbf\x9f\x98\xf9\xe0R\x19\xee?\xb5i\xb6L>\x06\xc6\xbf\x0b\x1cT{>/\xea\xbf\xb2[\xa5\x12&I\xfa\xbf\xe6\x1c.\x8ex\xc3\xc9?\xcbn\xcd\xccj\x0f\xe4?\xd1\xb9d$W`\xce\xbf\xfa\x1b\xac}hA\xf4\xbf6\'\xd6\x11\x99\xdf\xe2\xbf\xec\x896^M1\x96?\xde\x8b9\x11S\x17\xc0\xbf\xfc\x17\xce\x0eC\x87\xb9\xbfq\x1f"VZK\xef\xbfut>\x97\x14$\xed?\x16\x7f4\x8f\xf2\xe6\xf1?\xfbX\x0b\x0e\nW\xc7\xbf{,\x0e\xef]\x9a\x02@\xaa\xaa\x83ZwH\xf0\xbf\xd7\xd1\xf6T{\xbf\xda\xbf\xe8g\xc4,z\x94\xe3?\xaa?.\xe3F{\xc9\x11\xe0\xbf\x86wH\xbeT\x10\xf5\xbf[\xb2\x8dEd,\x9d\xbf\x8fO\x93\xa3\xf4\xc6\xf6?\xf9\xfa\xe1Glt\xad?\xcb\x97^\xab\xec@\xd6?\xc9\x8c4W6\xb2\xde\xbfs\xcc\xa7<\xaa\xd8\xe3?\xb3\xd6!\x8d\x04\x0b\xf3\xbf\xfe\xa2`\x01\x9fd\xe2?\x9c\xd0\x8b2\xf5l\xf0\xbf>\xc7x\xaeA+\xe8?,\x8bQT\xc6\xed\xf4?\x84a\xcckK[\xec\xbf*f\xb1\xde\x18\xf4\xba?u\x12\rw\x8f\x1e\xeb?o\xa2XY\xc1\x06\x06@Awj \x9f\xf5\xd6\xbf\xf2\xa6)\x95\xc5|\xcc?\x9a\xee{\x06\x1b\xa7\xed?\xc9\xa1\xc5\xff\xcf\xaf\xe3\xbf*u\x14m.4\xca\xbf3\xab@\xf5J&\xf8\xbfO\xf9\xea\xff\\\x95\xe6\xbf\xc7\xe8\xf5&W0\xe0?\xcc\xf5\xfbH^\xcf\xa8\xbf\xf0\x9e\xc7OK\xca\xf7\xbfWl\xae\xae#{\xe2\xbf+f\x96\xa9\x950\xe7\xbfG`\xfe\xdf\x16\x0b\xe0\xbf\xce\xae,.\x98\x01\x02\xc0\xb2TDDKe\xcf\xbf\xe0@\x96\xaft\x08\xbc?\x99\x85\x14\xd1\xe1\xfa\xf0?\xd5\xa3=GQ\x00\xf9\xbfR\xa1RD\xce\xb4\xf6?|\\\xad\xea(\x86\xe4?\xde\xac\x81dnA\x02@\xc4n[\xd8\xed=\xe2\xbf\xe7\x1dLh\x92\x1e\xfb?\xa8\x9e\xb9Mo9\xf2\xbf\x11\xb0q\xa6z\x1f\xe2?\xbfcHY\x83\x90\xe3?\xa7i\x84.U\xf1\xef?\xe6\x00\xc3\xcau7\xc7\xbf\xd0\xc7%\xa2\xe7z\xe1?\x89\x14A\xe4\xf9h\xe3\xbf\xadF\x93H\x94\xb1\xea\xbf\xe8\xda\x8bo\x87\x96\xf4?\xb5Q\x01jj*\xd7?j\x86\xfb\xdc%G\xef\xbf\x0e\xe22\x8c\x83s\xe7\xbf\x01\x11\xed\xc1\xee{\xf8?DF\x11\xaa\xc0\xd5\xf4\xbf!\xbd3\xc43\xbe\xe0?H\xec\x0cc\xf2T\xf8\xbf\x9c\xcc\x0c\xcf\x02\xbd\xcd\xbf\xc5\xe8\xcc\x11\xeat\xfc\xbf\xaf\xb6\xb8\x15~K\xa2\xbf2n\xcf\'\xa3\x7f\xfd\xbf\xc5\x1a\xe1\x92a\x01\xec\xbf|\'\\U\x9ad\xda\xbf\xafeF*o\xb2\x02\xc0X\xf8\\\xcfQ)\xfb\xbf\xa0H\xba\xcc\xad\xe8\xc8?K5\x9dC\xccF\xe0?g\t5\x90>3\xec\xbf\xe4\xc2X8\xa9\xfc\xfd?.*zB>\x07\xf6\xbfD\xb6\xf9\x91\xd0\x1f\xe7?\x14\xaed\xbfd`\xe7?\x1a\xbbg\x1e\x1cJ\xdc\xbfL\x19j\x1e\x7fj\xeb?\x88\xed\x1d\xd5\xf3\x85\xe7?`\n\xee5u\x19\x9d\xbf\x8an3"\x8d\x12\xf5\xbfCF\xc8\xf6\xbc\xa8\xd3\xbf\xad\x11\xa7\xa0A\xbc\xf0?\x18\x1at\x11\xcb$\xe8\xbf]\xac\xca\x14\xdfu\xe9?\xa9\x07\x18s\xc8\xb4\xf4\xbf=?=\x03k{\xd4\xbfM\xca\xc1\r\n|\x08@\x12=r.\xcfl\xf1\xbf\xb2\x89d\xe5h\xc9\xeb?\x04\xab\x06\x99)\xa9\xfd\xbf\x12/\xee\x1dcP\xe9\xbfM\xda\xfa#"\xdb\xda?do\xaaN\xfbp\xcf\xbf6\xdf\xaa\x1b\xe2\xa4\xea?\xc1\x97\x06z\xedz\x03\xc0\xab\xaf\x86\xcd%\xd9\xf0\xbfc\x1ad\xbb\xb5\x8e\xf0\xbfT\xe1\xfb\xf0\xed?\xff\xb6\x7f\xb9\xb3\xaf\xdd\xbf!\xeb6\xfd\xa5\xee\xef?\xa1\xa2T.\'\xe0\xf2?l\xf0\xe5Y\xe68\xec?\xbb\xa4\x13>\xd5\xcb\x02\xc0\xf3\xc9\xedi\xe5+\xf4?\x7f7+\xb5\x81:\xe4?\xeaE~x*2\xc2?cIx\xce\x15\xdf\x85\xbf\xa5\x9e\'\x1c\x12M\x05@j\xfb\xbf\x92\xdf\xb6\xe8?\x9aJO+\xe9\xdc\xd4\xbfO"\xd4\x10\xee\x8a\xd2\xbf\x99\xcd\x0b]\x8a\xfd\xf6?\xd9\xb8\x84gF\\\xd2\xbf\xf0\xa9w\xc2RP\x03@8\xb2\xa0\x145\xee\xf1?/C\xf8Bu\x9c\xeb?\xb9\x16rg\xe4G\xe1?\x93\'\xec\xf7\xe6\x9b\xf6?\xcb\xd5\xca\x17\x93\xaf\xf1?\xa4-\x94\xaf\xb4\xca\xdb?\xbf\xf9=\xf6\xa2"\xe1?UF:\xb2[\x90\xa8?/nE\xdb\xe7\xe0\xea?\xa6\xe0\x007\xe7\xbb\xf0\xbf\xeeRA\'\xdd2\xf2\xbf\xd36\x16\xf0\xbf\xd4\xf1?\x06{\x03?\x1cz\xfb?\x16\xfd\x10P\x1e]\xf9?\x13&j\x9c\x9b\xb4\xe9\xbf\x94\xb2\x06\xe1\x08\xe0\xc8\xbf\x96\x15i:\x13`\xf9?\xdf\x12\x84\xbe\xa1b\xe1\xbf\xd8\xa1!I%\x98\xef?\xf0\xe5\xc7m\xaa\x9b\x88\xbfg\xcdk\xa3\xd5+\xf7?\xf5\xffx\xd3\xbd\xa9\xfd?H\x0f\xf1\'\x02\xf1\xe2\xbf\xb2\xe5\xb2\xad{\x93\xec?\x08\x9f\x99r$A\xf4\xbfm\x8e\x8f\xd6wo\xe3?\xf6\x1b\xc1\x8e\xa1\xd9\xd4\xbfR\xa6<5L\\\xd6\xbf\x8d\xcb\xcde\xc4Y\xfd?\x10QE)\xdf\xb9\xe8\xbf\xce\x12\x12\xcb\xeaf\xf1?\xf7\xb9t\xb0\x17+\t@\xc9\x04\xd2\xbf\x11\xc7\xef?\rr\x86@\xa3\xbc\xf1\xbfj\xe9o\xc8e5\xf2?\x18$\x1e\x11\x99,\xee\xbfr\n\x84GQ%\xf5?*\x147Y\xf8\xb0\xd2\xbfW\x9es,\x18c\xf7?m@\xd4\x17\x9ci\xd3\xbfJ\x04\xa7\x04\xbe\xfa\xf4?\xd8Ac\xc4\xe3n\xbd\xbf{\x84\x14a\xd9\xea\xce\xbf\x1b\x82\xad\xffD\xf9\xec?*\xef\xa1\x9ch\xc4\xd0?\xf9\xf9\x00\x85[\x8f\xb8?\x1b\xa9_\xbe\x9b\x05\xf0\xbf\x8b\xcf\x14\xcc^\xf3\xd0\xbf%5M\xcd\xb2q\xfd?@\x15\xd8\x83=\xfa\xef?\xf9M\x8e~\x0b\xbb\xfe?B]\xd7\xe0\xcfh\xe2?o\xc3A\xb4\x81F\xf8?a\x80\xea~\xbb/\xf0?,bzB\x8b\xb8\x9a\xbf\xb4\x83\xf9\x1f\xfb\xd3\x07@\r\x81+nX\xf3\x04@g\x10\x0f\x16\xfd\xf7\xea?B\x8a\xc7\xbb\x94\xea\xfb?\xd0\x0b\xff$\xc3\x08\xfd?\x02\x92\x8c\x16v\xd6\xfb?\xd4\xe5\xf0\xd5\xe9v\xd5?\xc2\xbd\x99\x97(h\x9b?}\xb2\ty\xaa%\xba\xbf\x97\xfb?\xaa\xbc\xee\xf5?\xe2\x15\x8e\x8c\xe3\xd6\xec?\xc4\x03\x99|\xef[\xd0\xbf&\xeeK\x86\'\xa0\xf1?\x12\xa8\xb8ZAP\xf5\xbf\x05\x0e\xa8\x9e\x8a\xa9\xf8\xbf\x972\x1ck\x8b\xab\xff?\x19V\xe5\x91\xc1k\xe4\xbf$J\xefi0\x1d\x96?^;I\x9a\xaa\xbb\xd4\xbf\xef\xbaL\xa96=\xdc\xbf\xd3.\xec\xe4\x1e\x0e\xd0?\xb2\x8b\x81\r\xea\x1a\xe3?V\xf0\xb1@k9\xf5?M\xa4\xac\x9a:\xc3\xd5\xbf\x8dS=\r-t\xcd?\xd3\xd1$\x80>\xf5\xce\xbf]\xba\xb93$9\xe9\xbf\xbb\x8d\x89j\xa3\xd7\xf7?\xe4\xc4^\xf1"\x08\xe0\xbf\xf0\xcf\xba\xa1\xee\xc6\xea?(M\xfb\x0ck\xe3\xc6?\xc8\xb6\xc2\xcb\xd2\x90\xe7?M\xef\x1b\xfdj<\xeb\xbf\x97\xb8\xf4tX\xd0\xfa\xbfb\x90s\x1aG\x03\xfd\xbf\xcb!\x05\x0cE\xfe\xf6\xbf\x96\x04\xb9\x9b\xe0\xb9\xd2\xbf\x8b\\\x93O\x03\r\xe2\xbf[\xd4\x11\xd0\xbf4\xf9\xbf=df\xe1\xc6s\x01\xc0\xfc\xac\xd0\xfev\xee\xe7\xbf\xe7/\x8bK\xcbO\xfb?\xd3qqS\x1d\xb3\xd8\xbfo%\x1a}\x11\n\xf6\xbf\n:\t\xbfc2\x93\xbf\xae\' \x13\x07\xd1\xb1?\x03s1\xf4\xdfv\xf8\xbf\xb3F\xab(Hm\xbc?\xd3\xd8\x93\xb4\x8e"\xfb?e\x01\x84%\xc2\xbb\xed\xbfO0\x85o"\xb8\xf3?\xc9Uu-\xeb\xe6\xd5?\xcae\xad\x82eP\xce\xbf\xd5\xf4\xefn\xc0\xd0\xfe?y\x94\x9d\x9bf\xc8\xc8\xbf\xfa\x83\x1f\x19G$\xfb\xbf\xce\xf2=\xec\xb9\xc7\xd8?\x80Gs\xe5\xc4\x17\xeb\xbf\xdf\xa5\xa6\xba\xe8\x02\xca?O\x88\xa0\xb3aR\xfd\xbf\x9a\x13\xf9!\x9f\xb0\x0f\xc0,\xc5\xa7\x80\xff\x8a\x04\xc0\xee\x04\xb0AS\x83\x02\xc0$\xd6\xe6\x10\x02\x13\xf9\xbf\x9aD\x0e*kV\xf9\xbf\x07\x17-\x1a}H\xca?\x8c\x82\x96J\x05\xe0\xd0\xbf5\xa5\r\xa0D`\xec\xbf\xd3\xd4\x9e]C\x96\xf4\xbf,\xf7ZMn\xf0\xf2?\xfc\xf3PJ\x97r\xe4?\xf4\xc3\xa2\xa4\x88\xe0\xf5?\x93>+\x8b\xdd\xd7\xd3\xbf/I\xb7q\xd0\xd9\xe8?\xf3DE\xa6\xe6.\xd7\xbf@\xd8\x88a\x04\xe0\xd2?\x17\x8b\x06\xadB\xf0\xf9\xbf\xd6\xaa\xde\r j\xbd?\xa7\xfd\x8a\xba\xe4T\xf0\xbf\x125\xc9|\x18\xc2\xe9\xbf\x06C\x1a\xdc\x9a)\xe0?\x05\xaf\xa2f\xe1\xa4\xf1\xbf\xf39;\xde\xaa/\xd2\xbfk)N;w\x82\xe6?A\x1d6R\xb5\xbe\xff\xbf\xe50|\x97\xbe\x0b\xc4\xbf\xdd\x9a[\x93\x1dz\xf5\xbf\xd3\xe6\x13\x92\xcd{\xff\xbf\xc5A\x8b\xe1\xf9\x1d\x0c\xc0\xa03\xafy\xa0\x86\xe0?\x1f\x04\xf5\xba)\x07\x00\xc0\x9d\x94\xb1\r\xea\x1c\xe1\xbfO\x04\xb1\n\xb1@\x06\xc0K#\x06\xac\x18]\xeb\xbfT\xe1?a/\x88\xe4+5\xce?}\x07<\xd1\x92\n\xed?\x0f\x85l\xb11j\xd0?\xef\xe8\xc5\xf1\xdd\x92\xe4\xbfa\x93\x1d\x1b\xab/\xf1?\xf2\xc7\xf5m\x81y\xd2?\xbf\xfcp\xa8\x06W\xde\xbf\x80\xd6whR\x06\xc5?F}\xacl\xe0;\xe0\xbf{\x17\x9a#\xe1R\xd0?\x80\x81\xc79\xa8\xc1\xf2\xbfK1\xba\xa0`\xb1\xdd\xbf\x98O=\xc1\xe1\xdd\xd0\xbf"\xda\x86\x96\xdd\x91\x00@x\xcf\x1b\x98\xe9+\xf8\xbf\xa4\x9c\xbb\x89\xa9\xdc\xd6\xbf\xd6y`\xb3\x8b\xad\xfa\xbf|O\x05\x9b7\xdd\t\xc0\x87M\xec\xb8\x0f\xb8\xf7\xbfZ_c\xb1\xban\x01\xc0m\xd8\xe5\r\xc7!\xef?U\xba\x81\xe5uT\x03@\x81\xa7\xfb\xb7[\x92\xf2?\xa5"4\x84\xa9#\xf3?h\x17c\x92\xc9H\xf2?c\xadr\xac\x93\x1e\xd1?\xf6ZU\xfat\x7f\xd9\xbf\x90\x89#\xf9^\xaf\xe3?\x0c\x1ckg\xde\x8f\xeb\xbf\xce.\x9a5~5\xf0?}\x0f\xdf\x11\xa3G\xd2?\xf4=\xb6a\x8bs\xbc\xbf\xba\x16\xc1\x8e1L\xc8?\xbeAi\x88`\xee\xba?a]\x84z\xa1\xb6\xa4?\x99\x12\x02<@N\xf3\xbf\xd6\xce\x10\x7f\xa2\xfa\xdb\xbf\xb2AO\xa6\xf3\xf1\xfa?\x87\x17v\x8e\x8b\xab\xed\xbf\xca\x8d\xa6c\x10\xce\xb4\xbf\xbb\xfe\xf6\x95#M\xfa?2\x0cT\xcc\xfbX\x06@.\xe8\'`,^\xeb?Zh\xd4\x14\x9e\xfb\xf9\xbfN\xecGx%\xd0\xf1\xbf\xaa4\xe8^\xb8\xae\xed\xbf\x16\xf2\xd4\xcb\xfe\xb0\xd9\xbfts\xa2\xa6\x11y\xdf?/^e\x99\xe84\xda?Ir\xc0"\xe6\xc4\xd5?\x95\x04\xd9R;*\xb9\xbf\x86gK\xf9\x135\xd4\xbf9\xbf\xd2\xba\xec\xd0\x03@6\xcf\xdcp\xce\xc0\xe9?\x01\xf3\xdc\x8d\xe2`\xe7\xbf\xda\xac\xd2\xd3z2\xd7\xbf\x94A\x0c*\xd3\xe6\xec?\x8crKR"\xbc\xea?\xb3\xe2\x8776\x8b\xf2?\xb1\xa3Y\xf6v\xbf\xe1\xbfO\xba\xa8,`\xe7\xd3?\r\x8d\xa9\xc1,\xb5\xbe?W\xa3\x1e\xc3\xa8\xdd\xf9\xbf\\\x1c\xc7\x01\x0b;\xe6\xbf\xe3K<\xc3\xa8\x8e\xd3\xbf"\xa3\xc3(\xb3*\x05\xc0H\x03\x86\xc3q\xd4\xcc?\x99\xa1Dk\r\x14\xea?\xf1\xb0\xa9\x0bQ\x02\xf1?k\xce\x81\xf2\x00\x8f\xf8?>5H`\xf6\x01\xf4? \xefk\xe1bo\xfd?\xa9RO\xab\xbe\xc3\x00@[\xad\x8b\xec\x1b\x7f\xd7?\xc0V\xf1s\xb46\xd6\xbf)S`\xf2\xb4&\xf0?\xac\xe4\x18\xbe\xd1\xb8\xcb?\r\x18OyF\xe4\xd9?\x14\xfa\x10\'\x069\xf4?zKEw\x1a\xdb\xc5?\xb3\x10\x88\xeb?\xd7\xe3\xbf\xf9\x91Bh\xdb]\xf2?\x8e\xe6\x0ca\xaa\xbe\xf8\xbf|\x96X\x1d\xc7\xca\xd4?\xa6]\xcf\xa1\xb4\xce\xea\xbf9\xe6\x15\xb9\xff\xea\xd7\xbf\xda\xb7y\xf0\xdaX\xa6\xbf\xe1D\x1c\xda\xf5\xaf\xe6\xbf\x9a\\\xdd\xd3~\x8f\xbf\xbf\x88\x80\x9e\x88@\xe3\xe5?M\xe4g>v\xbe\xd1?*\xfe\x1a\xd3\xa1/\xf2\xbf\xe5\xd4\xd6\xfd\xf5\x18\xe2?s&x\x08\x9d5\xf7\xbf\x81^\x01\xa0Yp\xe6?\x0e\x10\xa3\x15\'\xf6\xef\xbfg+@\xaf.~\xcb?Z\xdeP\xfa\xe5\xa2\xbe?\x94\xb7\x0c\xc2\xdf)\x00@!Uy\xb6\xdf\xd5\xa4?\x99`\x9cyx\x07\xe0?\x08J.\xc1+\xf8\xbf?dD\x18\xef\xcd*\xdc\xbfks\'\xd8`,\xc1\xbf\x7fMX\nZ\xc6\xff\xbf\x99:lQ\xd2\x86\xed?>B=\xe2\x838\xe6?j\xb1\xb5\xa7R\x96\xd4?\xe8\xe1\xbd\x91P\xe5\xe2\xbf_\xefrd\x9b.\xf0\xbf\xbb_\xe1}\r\x07\xe6\xbf\xc6\xc1ZU\xbb\x18\xea\xbf\x05\x8b\x0e\xd5\xf4S\xea\xbf\xebh\xe2\x9f\xc5T\xc0?\xe8w\x8bq\xbb\xec\xc1\xbf\x16#\x0b\xfd[ \xdf\xbf#^\x0c\x138s\x8a\xbf\xe3\x98\xcd\xc1\xf5\xc8\xf2\xbf\xcc\xaeG\xb1-\xd9\xe0\xbf\xd2L\x9bL\xe0\x86\xde\xbf\xe1\xcc<6\x9f@\xd6\xbf&B\xd0%\x8c\x1f\xf1?6\x81=\xaa[-\xe9?R56\x1cN\x9e\x99?N\xf6\xfd \xbfU\xef\xbf\xa8\x1ca\x1b\x19\xcf\xd1?\xab\xbb\xd3^"\xad\xee?\x03\x98\xab+\xe0\xfa\xf5?\xe2\xe9\xdf\xdc\x95\xd6\xcb?\xd2\xaa[\xed\xb0\x18\xf5?{\x1a|\x1c+x\xcb?\xd8\xd6\xeb\x98h\x93\xf1?J\xb0T\x01\x97\x9b\xeb?\xf3[\xd9\xaf\xb3\x1e\xb0\xbf\xfa+<\xd7 \x9b\xc1?u\x92\xc8\x02<\x11\xf0?\xb6o\x9a\xa4\xc0y\xa1?F\xfa\x0c\x1f>\xc8\xf0\xbf\xcb\x1a\x1f~\x10\xb8\x03\xc0\xf9\x9b\x01\x16\xfca\xe6?\x96\xc0\xba\x10\xc0\xd4\xdb\xbfz\x1f\xd2{\r\xab\xea\xbfE\xff|\x17\xbcK\xdf\xbfcZ\xa2\x93\xb4\xc2\x8a\xbf\x9f9\x98 \x84\xc7\xdb\xbfrj\xba.2_\xe3\xbf\xc8\'W\x13\x17|\xe4?\x15\x83\xf8\xf4k\xde\xd2\xbfU\xebv\xd3\xda\xf7\xbc?\xc6\xde\x8e0=\x9e\xd3\xbf\xde|\x8b\xc5\x95\\\xda\xbfq\xc1 \xd3\xa5\x8e\xea?\x1d\xd1{M\xa1\x17\xd4?%\xb1\xd5\xad|\t\xb0\xbf$s\x8f4_\x07\xf8\xbf\x1c\xe5\xd3\xc0\x1a\xd2\xed?\xa8\xf4\xeb\x8a\xeb\x89\xe0?B\xaf\xfd\xae\xc0~\xee\xbf\xd4\x8b\x1e\x19#]\xcd?\xd6\xe9\x8a\x18^\xc0\xfa\xbf\x83\xed\x1dt\xb1W\xd6?\x9aB\xfe(~\xb7\xa0\xbf^&Yr\x82\xa4\x90?v\x0c&\x89K\\\xe7?)\x1e\xf9B\xd54\xd3?\x1e\x81\xa1\x164:\xd3\xbf\xe2\xe8\xfb3g\x93\xbd?\x9e\x9e\xa9\xd3\xc2{\xf0\xbft\x9fkc\xf2S\xe5?\xa2\x17\xde\x13\xaa=\xf7?\xbb\x08\xad\xde\x80\xc9\xcd?\x8c\xc9H\xb5\xd7\x16\xfd\xbf\xc2hTh\x93\x8b\xef?e\xe3\xdc\x9a\xfb\xcd\xbd?)\x93\x1c\xa1G\r\x01@\x9a\xf8\x08x\xbd>\xf7\xbf\xd0\\X\x9dJ)\xe9?=\xa6]\xf3pr\xf8\xbf\x8d\x8b\n\xac\xc9\x93\xcc\xbf\xa4\x8c\xa5TN\xc3\xe6?4\xf0\xcf+F!\xf0?d\xb2$\x9a\xd7f\xd8\xbfB\x8d$\xac\x9e\x1d\xb0?^0EK\x04\x87\xe9\xbf\xe9)\xfesx/\xcb\xbf\x18\xe6TV=9\xf1?(\xc8\xe9\x8d\x1a\xcc\xe5?\xe6\x97l\x9b\xbf^\xf2\xbf_\xf7;\xe2\xb4\\\xea?\xbd\'[\xe80$\xf7?m\x06\x1f\xb0-\xf3\xa5\xbf\x95\xef\xa7\xbf3\x7f\xd5?\xdca\x1cz\x81\xd0\xee?\x95\xc7\x0b\xdf\xd8\xa7\xbc?\x19\x8d\x87F\xe1|\xde\xbf\xbf\xb8\'\x95(\xe7\xf0?_i\xc0`\xa8K\xb5\xbfE\x06\xd8\xf6\xf3\x82\x00@\x16gIzs\xf2\xea\xbfk\xd9\xbb\x0e\x94\xe8\xf4\xbfU\x96\xf6\x0bW1\xd0\xbf\t\x14\xe9\x06yi\xd2?\n2\x1c\x03\x02|\xd9?\x8b\x1dg\x06\xe56\xd2?N\xa1\xb0\xa1\x1aO\xd0\xbf\xe3#V\x00\xaf\x90\xa4?ng8~\x15\x9e\xee?,\xceC\xb9\x009\xe5\xbfb\xe1\x97)r\t\xfd?R\xf0\xd0\xfb\x818\xf9?\x96\n#-Tq\xf2?\x04nc\x00\xc7\x82\xfa?{%\xce\x95\xe9\xd1\xf0?\x93\x12\xe1\xc0\xe2\x8b\xf4?4\xac@xL+\x01@\xa0\x86\xc2\xf6\xcb\x06\xf3?\xdacqZzw\xe8?\x98\x8d\x9fj\x97d\xfe?\xb4\x1e\xfc\xa5lU\x00@.= $&j\xd5?\xc1_\x19\x92\x04\x8c\x9f\xbf9\xf2\x96\r0\xf0\xf3?oys\x8b\xc2\xdc\xe0\xbfao\xfb\xcb\xc7y\xdd?\xee\x97n\xfb\x02\x8e\xf2?\xf5\xe7\xd6U\xfcD\xea?\xea\x15\xd3@\xda\xac\xc9\xbf\xa2\x80xG\xd6\x0f\x07\xc0+\xde\xee6\xe5n\xfe\xbfd\xfc\x94\xd0\x06o\xd9\xbf\xdaB6,\xfak\x05\xc0|B\xcbrId\xeb\xbfC\xf2j\xbbZ\xbb\xbb?0\xd4\xc4\x1c\x1b\x15\xe3?\x98L\xf06;\xc0\xfb?#\x11\xe7\xe0-\xf4\xf7\xbfkg}\xa7q\x94\xf6\xbf\xa8\xf9F\x1a\x11\xae\xcb\xbf\xd8\x01\xf3,\x8f\x1b\xeb\xbf\xf8\x80.6\xd5\x8c\xf7\xbf\n\xf15\xac%\r\xc7?h%\xeb\xef\xe65\xca?\xaemw<\x835\xaa\xbf\x03\x04,\x8e,\xfe\xe9?0f{\xc1A\xd7\xe0?\x12\xb7\xca\x11/\x8d\xc2\xbf\x8373\xa0\x18<\xe4\xbf\x8b\r\xa0\x1a\x0f\xf6\xf0?\x9bS\xd9L\xac|\xe3\xbf\xb5\xbc\x81\x97\xd6^\xf5?#\xb7B\xf0\xed\xf0\xb2\xbf{\xf6R\x98c\xb3\xda?g/\x10Z\xf2y\xe2?B\xd4u\xf2ZW\xe9?g\xc8\x82\xa5X\x94\xcb\xbf\x17\x8d\x82\x9c\xf2\x89\xf7\xbfsM%\xc2Z\xda\xf3\xbf\xa8(\x82R\xd3\xfc\xef\xbf}\x92%\x10\x1eW\xce?\xb9+&X\xc6\x82\xe6\xbf\x8e\x81/\x93@\xc0\xe9?\xa5\xf5w\x80\xa2L\xf9\xbf|\xb9\x9cL\xaeM\xb7\xbf\xe8O\xa3\x1c;\xdd\xe4\xbf\x1c\xdd+;\xd6\x19\xdf\xbf\x82w\xaa}@\xeb\xd3\xbf(W\xe8\xbd>\x18\xea?\xd9\x83\xdb\xf6.L\xd4\xbf\x89\xe6\x83\xbf\x99\xba\xea\xbfp6s\x9d6Q\xe4?\xafn\xc5\x9a\x87\xef\xba\xbf\xf9\xd1\xee\xf7\x8dP\xe1\xbf\xe4f!\xb0\xf8\x10\xf9?d\xe3\xfe\xa4\x0e\xce\xc1?\xe6\xb0\xf5\x0b\x9e\xfe\xea\xbf\xc1\xa2\x83ki\xea\xec\xbf*\xcb\xdf(\xfa\x12\xfc\xbff\xde\x9f\xdc\x1e\xe2\xf2\xbf\x81\xf2\xf4\xcaQ\xb8\xde\xbf\xc8\x1d\xf4\x19E\x1a\xdd\xbf\xaa[\xfa\xf8U\xfd\xd8?j\xd8\xe9z\x19\xdd\xde\xbf\x9fj\xaa\x89\xb9\x92\xfa\xbf\x0b\xfaY_\xf4\x81\xf7\xbfQ|hM\xc1\xc4\xea?\x96\x9e\xf5\xad\t\x80\xc7?\xb6I\xf7\x183\xf2\xa8?\xba%\x15\xebP\x07\xc0?\x17\x93\xdc\xd1\xab-\xb6?\xe7%-#\xfd\xbc\xdd\xbfV\x9b\x02\xe7\xdc2\xbb\xbf\x9f.\xfe\x91r\xca\xe2?\x87.(\xa0\xb3\x15\xd8\xbf\x08\xa6_\x8a5E\xf6?\x9c\xc2\xf9\tEB\xf4\xbfY\x0f&\xdd\xbd\xdd\xd3\xbf&w\xbdG6\xe3\xf0\xbf\xae\xc2\x12K\xd4\xd3\x00\xc0n\x12\x86\xdf7;\xd0?\xe4\x86\xaf\xd6\x0f\x8b\xdb\xbf\xfa\x9ag\xc6\xd85\x99\xbf<\'\x1dJ\x95\xe2\x05\xc0\x9bP\x92o \xee\xde\xbf\xa1\x04\xcf\x076f\xf5\xbfx\x86\xfdq\xda\xe0\xf2?\x1b\xc3V\xd3fE\xf1\xbf\x90l\x18\x88\x8e\xaa\xd1?\xc6\xb34[\xef\xbf\xf9\xbf\x826\xb2f\xf8\x7f\xb2?\x95Uw\xae\xeb\xc1\xf1?\x08\xa7\xdf\x16c\xc5\xf5\xbf7\xda\xd8\x006u\xf0?5\xee-\x93\xb8\r\xd8?\xf7\xa7\xd3\xf7\x97\xa1\xfb\xbf\xfa\x8f\xcc\xcd\x06g\xfc\xbf.)\xfd\xf4\x07$\xdc\xbf\x8d\x02\xce\xdd`-\xf8?\x10n\xaa\xea\xb5\xc2\xf3\xbf\xef\xab\x8a\xc2,\xd9\xf5\xbf`\x93?\xc3%\xe9\xe8\xbf\xe2d\xbf\x0c\r\xb1\xf3?\x9bE\xfe9\x8a\xf3\xf3\xbf^\xb8\x19\x18\xc3\xc8\xd9\xbf\xa6\xd1\x06\xfa\x03\x06\xe5?}K\'\xea\xe6L\xf7\xbf~\xc5\xef#\r\x05\xd1\xbf&t\x08}S\xc2\xd0?AUA\xbbj4\xf1\xbf\xab3\x90\xf6}F\xe4?>\x0c\xb4\xcb\xf0I\xe1?\x9b\xd7QQ\x0ce\xfb?\x8ev\x04\xaa\'i\xbc\xbf^\x83\x13F\xd0z\xca?p\xfd\x8c\x04\xa6B\xfc\xbf\xa4\x11k\x1eI/\xfe\xbf\x15IX\xf6{\x14\xf7?\xec\xd5\n\xf3\x917\xe1\xbf*ac\xc92\t\xfb?\xe9\xbb\x18\xb3\xdd\xcc\xf8\xbf\x08hG\xcf\x9aB\xe9\xbf\x1c\x06\x8d\xc0\x0ej\x01@\x90\x80\x05\xe1\xaa@\xfa?\xf1\xf1\x8b\xe9\x1c9\xd8\xbf\x87\xd9G\xc7n+\xf0?\xefI\x98\x95\xf2\xeb\xe5?8e\xaf \xc3!\xcc\xbf\x83 \x9e\x99\xd3\x91\xff\xbf\x1f\xb5\xd7}OP\xee?\x19\x13\x87<\xbdB\xe2\xbfG\x89\xb5\xa4\xb8;\xc2\xbf\x9a\xea\xc9\x80?\x99\xd8\xbf\xf68/ }\x1e\xce\xbf\x93o\x05\x9a\xb24\xc0?\xee\x02\xb64.\x0f\xf6?\x04\xd7~\xf6\x8c\xfb\xdb\xbf\x8ck\xfa\xb1\xb1y\xe1\xbf\xd6S\xa8\xab\n,\xc1\xbf\xd4\xcb\x9e\xa6\x7f\xf9\xd3\xbf;\xc8\xaa\xac\x89\xf8\xf3?\r\xe0>\x8dMl\xfa\xbfw\xb5\x91\x93A\x9e\xdf?\xf8\x1e\x7fWE\xd3\x01@+\xa8;\x82\xcc\n\xfc\xbf\xf3N\xe6\t\xcdq\xe1?/\xbc\xdf\x81*@\xfd?\xda\xa4\xa6\xdf\x1b\x88\x03@\xb9\x9c\xff\xe1\xf5m{?sr\xc6L\xd9!\xb7\xbf\x1c\xabj)e\x1e\xc0?\'\x98\xc5\xc9\x1bY\xe1\xbf\xe5\xf1U>4\t\xce?\x1d\xeb\x9e\xeal\xf6\xe6\xbf\x03\x9b(*\xda\xea\xf1\xbf4W{#\xb2\xca\xfe\xbf\x84\xe7c\x81\x9b\xc0\xf3\xbf_z\x07q\x1et\xe3?\xbc\x9c\xc7\xab\xea\x82\xba\xbf\x7f\x8d\xc5\xd8\xdf&\x02\xc0\x85\xb7.d\xfc\x91\xb6?\xe4\x0e\xc2GH\xf9\xd0?\x90\xaf\x90\x0e\xc6R\xee\xbfYyi\x1d\xf6\xd4\xfd\xbfa\xe3t\xb3\x92\xa6\xe6\xbf\xc7K+9\xda1\xc3\xbf\x8d\xd10\xac_\xb8\xf5?yT\xf8\xe32\x01\xf7\xbfx\xaf\xe1\xf8\xf22\xe5\xbf a&y\x1e\xad\xbd?\xef\x82^u\xbc\xaf\xed?\xaf)\x93_\xda\xc6\xe0\xbf\xf5\x8fV\xe8J\xb7\xfb?\x0b\x83\xdb\xda\x13\xc8\xf8?q}\x86\x84\xcc\x93\xdd\xbf\xf5{%5\xfb\'\x05@oF\xc4\x93\xde\xda\x03@\xdaV\xac\x0e\xf8\xf9\xf1\xbf\x05\x04l\x91\xaf\xaf\xe8\xbf:x7x\x96\x92\xbd\xbf\x84\x1b{\x8a\xe4U\xf6?\xa8\xfd\xe0G\xafN\xc6?\x8b5\n\x16\xb2\x97\xd1\xbf\x84X\x13\x803\xbc\xf2\xbf\x1b\x02\x10\xc4\x9d\xac\xf5?}{\xb8Y\xce\xfb\xa8\xbf\x0ek\xa9\x02\xe8\xfc\xc1?\xa2\xd3\xd9Ri\x1b\xd9?@M\xe6\x1c1L\x87\xbfd\xde\xd7\x03\x94q\xd7?\xcd\xc8\xbe\x83O\xda\x03\xc0\x84$\xd8\xf3\x0f\xf8\xd8?\xa8\xf5\xae\xb7:\xd1\x04@\xa5\x15\\\\\x0b\'\xf5\xbfxtt\x81\xdd.\xd4?8iQKn\xbb\xee\xbf\xc63\x1dt\xb2\x8e\x00@<6\x95Y\x01T\xf0\xbf4\xa7\x8f\x98\xd9\xe4\xd4?\xcd\xb4#\x1f\xa26\xea?]3\xf8\x86]p\xe6\xbf\x16m\x95\xb2\x9c\x1e\xa0\xbf\x9c=!\xa5\xb7>\xd9?)\xda\x00\x86\xbb\xa3\xe9?\xbb\x8cF\xd8\xf5\x87\xe6?\x06\xd7\xa0F\x94m\xb7?\x08\xd5\x87\x19\xdcX\xf3\xbf\xe0\xf9q\'\xb4\xfa\xed\xbf\x81\xdd\xb5\x1189\xf3?\xe8\xd6\x8fHy\xb4\xd0?\t\x9c\xebn\xb1?\xe8\xbf\xbb\xa2J\x83\x1c\xed\xea?\x1f\xf2\xeeO{@\xb4\xbf\xd5:\x03\xb3\x15\x0b\xe8?S*\xfcW\xe25\xfa?\xact\xa9_\x06:\x02\xc0o\xc9ft\xc4\xf7\xdb?\x87Pl\xb4_+\xd2?#\xe1\xad\xcd\xf6\xf5\xe1\xbf-\xcf\xa10<&\xed?`e\xdc\xff\x9d\xa8\xef\xbf"D\xa1g2q\xe8?\x81\x9b;c$\xa8\xe5?\xa2u\x95\x02v\xab\xe6?t\x14H\xeb[\'\xe4?\nW\x02W\x91\xdc\xd4\xbfL\xfa\xd3\x9exD\xe5\xbf\x9b`5\xe0\xb9\x80\xed?3FK\x07\x17\x85\xd2\xbf|\xe0\x81L\xb3\xdb\xfa\xbf\x95\xe1\xc2F}L\xbe?\x7f\x1eI\xdf{\xf9\xe0\xbf\xe0\xeb\xa8\x89\x96\x8b\xda?\x82\x11\x8f\xff#/\x9e?\xef\xc2\xf6\x84\x9b\x9a\xf6\xbf\x17\xfcE\x03\xb8\xb1\xeb?h\xac\x9av\xcbW\xdb\xbf/\xa2\x9ew\xf3\x98\xe8?xD\xa1%\x86>\xed?[]z\x86\x05\xa4\xb2?\xa2"c\xce-\x9e\xe4\xbfi\xe27\xec\x96\x80\xd0?8\xb1\xb7\xdf\x00\xf3\xd7\xbf\x83&\xa7\xf5\xe7\xa4\xf2\xbf5RC\x97\xd4\xdb\xf3\xbf4@yD\xda\t\xc2?\xe1\xfa\xe7$\xdeW\xdb\xbf\xa0\x1fo|\xd8\x80\xfc?t6\x91\n\xc0>\xbe?\xfb6\x18.\x80\x0c\xc1\xbf\xd1\x08:\xc5\x01\x03\xf3\xbf\xf4\x1dBHM\xe5\xe0\xbf\xad\xb2\x16b\xc3\xdc\xe7\xbf\xe7^n0?\x99\xea\xbf\xc2G5\x94[\x86\xfa\xbf+.\xba\x10rC\xfb\xbf)d&PU$\xc8\xbf2H\x04\xcb-\xf9\xf0?\xdf\x82\x916\xef\xf4\xe6?\xbd\x9d\xc5\x15\xa6\x8b\xf6\xbf\xb0\x92\xf33(\xbb\xf5\xbf\xd6\xc2.\x04q\xd7\xf5\xbf\x99a0\x04\xc2\xab\x00\xc0\xe5-=j \xca\xea\xbf\x13\xbf\xbc\xeb[\xaa\xc8\xbf\xd935=)U\xa7?\xd7\x97\x1f\xf0S\xf7\xea\xbf\xd5\x99<\x12[b\xf2\xbf0\x03\xfc\x05!\xa9\xd4?\x8a\xf3-\xfa\xfc\x91\xe0\xbfc\x83\xe7w %\xcd\xbff\xf9L\xf4E\xda\xd7\xbf\x83\xdf\x94\x82?\xd8\xc4?aq\xbf\xef\xb5\xdb\xc3\xbf\xf5X\xfc\xbd[\x9f\xee\xbf\xa2\xf6u\xd1K\x0f\xf6\xbf\xfb\xcd\x8f\xc2\xb4\x86\xe6?dh3\x8d\xe2\xd5\xf3?\x1a\xf31\xbd\x90\'\xc8?\x89\xff!\x84\xd2\xf4\xee?!\xf8\xf8\xf2\x9dL\xd9\xbft\x99\x8d\xdb19\xf2\xbf\xb3\xf3\x8eS\xe7\x07\xf5\xbf\xfb{\xdb\'\xd7\xee\xe8\xbfE\xc4\xf4\xbc\x14Y\x04\xc0\xc1\x03\xed\xe0b\xe8\xf4\xbf\x08\x196O\x7f\xe5Y?\xdc\xcfyd\x12\x08\xe6\xbf\xbao\xb25c\xf7w\xbf;Z7f\xa0z\x00\xc0\xa4\xfe\x97UJV\x00\xc0\xd4\xa4T"\xa6\xef\xcb\xbf\xbd\xa2p\\\xe8\x88\xa0?3\xde\x92NB\x8f\xe5\xbf\xc0=\x81\xedi\x15\xda?\xdd\xf1o\xa7*\xd3\xd0?\x03\x08\xad\xeb%\x7f\xe3\xbf[\xdf\x92\x96\xd8\x82\xb2\xbfM\x1a\xca"\xb3\xb0\xec\xbf\xf5\xbf;W\xd1\xc1\xed?\xbb\xe1\xc4P\xe3k\xed?i\xa7\xfa\xadI\xe8\xe9?\xf6\x10\xa9:\xfa\x90\xec\xbfg\xcb\xb2\x0c\x9d{\xda?\x88\xadA\xd7\x8b\xc4\xee\xbf\x04Q\xcf\x88\xda\x15\xfa?\x92\xce\x88\xd0\xa8/\xdc\xbf6L\x9b\xe5\xfc\xaa\xe6\xbf\xe1\xda\xcd\x9a\xe4;\xf0\xbf5\x84\xa9e4\xa8\xde\xbf\x9b.\x02\xce\xf5\x99\xfa\xbft\xd2\xd1+8\xb6\xd9\xbf\xfey\xc8\x0b\x8e\xec\x02\xc0\xea\xd6)n\x83\'\xbe\xbf\x08j\x10>$\x91\xd1\xbf\x16\x1b\r9\x10$\x8d\xbf\xb6x\'\x14+;\xf2?\xe2\x04\x07\x1aq\x0e\xf4\xbf\xc6\xcf\xa4x\xea%\xac?\x1a\xcdAE\x16\x1d\xea\xbfq\xa1\xd5\x86\xe3=\xdd\xbf\xa3.}\x1cbt\xf3\xbf\xd2Y=\x84\xfba\xf3\xbf\xae \xb4\xf4\xe9\xe9\xe6\xbfkcU\xb2\x0c\xce\xd5\xbf\xda\x05\x9f\x17z5\xf4\xbfz-d\x89C}\xf1\xbf&u1](L\x03\xc0].\xd1\xf9^\xc9\xdd\xbft\x17\xf9\x87\x8e\xd1\xe5\xbf\xa7&\xc5M\x9d\xed\xb4\xbf\xdc\xd0\x9c\xcfN\x10\xeb\xbf\x88O\xf7\x9fa\xad\xf2?dk\xb1\xa5i\x13\xd6\xbf\xe4\xde\x1d\xc0\xa8\xd8\xd2?\x9e\x8bZ\xbcd\xb2\xd7?d\x1a\xad"\x11\xdb\xe8?@\x9az\x0c,\xc5\x10\xc0E\xc4\xf44\xe6?\xd9\t\xcb\xeb@\x05\xf3\xbf\x81sTEPU\xd4\xbf\xcf\x0f\xb7\x0c\xbdp\xbc?\xa04\x02\xb0\xd3\x8d\xe7?\x8a\x11\xde\x1dZc\xea\xbf\xaa\xe2\xc4\x8dP\xd8\xf5\xbf\xd6\x120V\xc8@\x01\xc0\x8f\xb2\x96<\x17\x0f\xe4\xbf\x18\x9c\x84]\x9f\xf4\xf0\xbf\xcfg\xa7\xc6\t\xc8\xd7\xbf\x88()V]i9?\xe3o\xf7\x86\xe6\xdb\xf2\xbfT\xc5\x1a\x03;\x15\xf1\xbf\xd3S\xb9/\xc4Z\xe8\xbf\xfa\x17\xee\x8ab\xfa\xf2\xbf/?\x17\x9d\xbc\x87\xc1\xbfd,\xfd\xc6\x8b\xbb\xd5?}^\n\xd1n\xdc\x03\xc0=\x046|*\x1d\xd9\xbfPxi\xd6\xaf\xb1\xe8\xbf\x83\x92\xd5G\xcc\x8b\xd1\xbf\xf3\xda\x8d?g#\xb7?f\x9f\xc0N\x8d\x91\xf2?\xba\x1d\x1e\x98\xdd\x99\xe1\xbf\xdbI\xb5q\x0e\xdb\xbf?\xa8\x81\xd1\xfe\x91\xd6\xea?\x80\xaer\xf9+o\xd5?\xd4d_\xfc\xe3j\xab?\x1a\xcc\x82`\xdd\x88\xe7?#\x92\xcf,\xbbD\xe3?-\x11[\x00%\xbbs\xbf, 0\x13\xc5\xe8\xbe\xbf\xaeK\xce\xe3l\xfc\xfd\xbfU\xe7\xdb9M\x83\xd8\xbfr\x1c\xb6<+y\xf3\xbf\xb7\x12\x16\xfe\xf4\xa1\xe7?\xb0\xb3\xf3\x86<\x7f\xd3?z\x8c\xberJW\xf5\xbf\xe8\xed\xbb0\xa0\xce\xf0\xbf\xc1\xfc\x14\xdf/T\xf7?\x9d=C\xffo\xd6\xda\xbf`\xa2\x05\xf6\xc1\xf5\x06\xc0\x03~l7\xa6d\x04\xc0\x93D\xda\xdf\xd2\xeb\xf7\xbf\xe9\rM4\x16\x14\xed\xbf6\xf6\xe4\xd7\xfaI\xfd\xbfeP\x99\xd3\xc7<\xe7\xbfd\x15T/\x8c\x1b\xb8?\x10\x82\xda\x87G\xdb\xe0\xbf-\xf1\x89\x17\xae\x8d\xcf?\xb2Fo\xc6\xc8?\xf0?\x93\xb7$\xdd\xb0\xcb\xd3\xbf\x0c\xb3\xf2\xa1\xff\xb3\xcb\xbfI\xd5\x015M\xd9\xeb?e\x955\xae\xb0\x01\xcd?\x05\xcf\x16\xc9\xe3\xc8\xf1?\xa6\xd5\xc3\xffX\xf7\xf2\xbf\xc6\x18r\xa4\x1a\x92\x01@\x0b\xe4\x913j-\xe4\xbf\xe7YF\xb6\x1a\xd0\xfd\xbfg\xce\x17[\xcd\xe7\xcf?PX\x7f\x06n\xb9\n\xc0\x93\xbb.x\x9b\xf4\xf9\xbf\xaazEzB\xfa\xf2\xbf\x9a\xf3K\x07!\xb4\xda\xbf\xe1\xbc\x90(\xe6d\xff\xbfU\x02\xab9a\xe5\xe3?,@\xdb\xf9\xd6\x13\xe1?\x95\x89\xe9\x94z\x8f\xbf?\xb18\xe5\xc8\x03\x9f\xf6\xbfL\xb0\xfc\xc7\xe2\xd8\x06\xc0\x85_mm\xd1\x93\xf8\xbf\x17\xb0\xaa\x02\x85\xd5\xa1?\xab\x9c\xc3C\x86\n\xd0\xbf\xef\xee\x06\x1b\xff.\xd5\xbf\x8b\x97\x150%.\xef?\xaa\x1a\x85\xd6\xb0\xaf\xf0?\xadE\xa7\xadD\x90\xe1\xbf\xbb\xed\xf0\xc1\x18\xd8\xe8\xbf<\x11\x98J^\xa1\xfc\xbf\xc4\xbc9y\x05\xfc\xe3\xbfwF2jf\x9e\xe8?ic\xa7\x18\xca\xb3\xf1?\xfd\x90\xbf\xc3T9\xd5?\\\xce\xfd\\\xd7\xe0\xf3?\x11f\xa7/g\xa7\xc9\xbf\x9f\xefz,\xfc\x97\xde\xbfw\xb4X\xa1O\xbe\xc4\xbf\xf2g\x10$l\xb6\xf1\xbf\x03gb\x90\xde\x84\xd7?2\xe8m\x02\xe7\x05\xf3?\xe5\xab\xa9q\xa9l\xed\xbf\xea\x10\xaa\xbb\xf0\x8a\x00@\xf0\xd4\x8f\'\x03\xf1\xf6?\xf8\xb2\x81eA\xa7\xcb?\x90Hz\x96r\x8c\x02@\xc4\xe4\'\x10*\x92\xdb?\x1c\xd4\x18M\x91\xad\xec\xbf\n\xea\xab\x9fG\xbd\xf4?\xbb+{\x8c\xea\x93\xd9?\xc3\xc1|\x14y@\xe7?\xb4\xe6\xfb\xe5\xc1K\xf4\xbf5\xa0\x9a\n\x19\x82\xf4\xbf\xbef\xe7oH\x18\xf2?\n\xe0\xd6\xc0\x90\x1c\xe7?\xcb\x1f\xb2Q\xb4\xc5\xf1?\xea\xe1\xca\x84\xcf\xae\xf0?N\x93\xb7\xf6q\x88\xde?\x93\xa5(\xb8\xcf\t\xf7?\x8e\xfciq\xc1\xcd\xb5?\xe3T\x9b\xc7\xd7\xd5\xb3?\xfc\x8e\x85\xa35\xc0\xe9?\x88\x88\xbdUEU\x00@cda\xcc\xce\x87\xcc\xbf_\xa6/\x08\xdb\x9a\xfe?b\xa9\xfd\x17\xbd\xea\xe5\xbf\xa0b\xa4\x87\xd1S\xdc\xbf\xe4Y3\x97\xd6R\xe6?0\x930\xe5-\xb1\xf2?\x00\x9fC\xfc\xfc\xe0\xfe?1\xe9*\xe6\xf4\xae\xe6?\xbe\xdf!)\xf5B\xe7?\x02\xd2\x87\xe6eM\xed?\x17\xa6\\PA\x0f\xf0\xbf\xe8d&^\x1f\xd5\xe8?\x8f\xcf\x0cP\x95\x8c\xf8\xbf9d(\xdav=\xe2\xbf6R\x07\xbd\xea&\xe5\xbf\x98G\xb5\xd3\xb4\x1a\xfb?\x825\x9eC\xc4\xf0\xc1\xbf\xcc\x95\xf3uK\x88\xe4\xbf\xe6\x90\x08l]\xed\xe3\xbf\x14\x99\x18]\xc6\xd9\x85?\x1c#i\xe1\x86\x7f\xe7\xbf\x82\x97\xdb+\x16\x04\xd8\xbf\x7f\x14\xa5\xf3\xc8\x94\xf5?y\t\xcd\xb4Cp\xcf?\xf9\x05y\xe9\t\xb6\xee?\x1e\x9ah\xbf\x99\xfc\xdb?t\xb7\xb5]\xd2\xfd\xf4?\x119\x8b\xd935\xfa?*\xdd\x0b\xc7u\x03\xef?u\x8b\x7f\x17\x7f0\xec?\xda\x13\x12k\xdd\x08\xf9?\xff\xed\x82\xe8YE\xed?m\x90\x84<\x8a\x88\xf2?#\xde\x9d\x84\x86\xfe\xea?\xd5\xed\x8a2}\t\x02@\t\xab3\x9d.I\xe8?>\x92\xa2\xbb\x19^\xe3\xbf.\xa9\x7f\x84:\x02\xfb\xbf\x89\xadH\rr\x89\xe0\xbf\xb7\xe9\xcb\xa5IO\xf9\xbf\xa9\xb6\xac$_\'\xed\xbf]\xdcY7\xe4\x9c\xeb\xbf\xdc\x8a\xa8njR\xad?\t\xf3\x18>&\x0c\xf4?\xc2\xd4[l\xed\xa3\xf3?\x8fw\x04A\x95-\xf2\xbf\xc2Gm\x83\x9a]\xe5?\x85YR\xd6R\x96\xd3?z\x8fG\xc8]\xbd\xeb?\x8f\xb0c\x17D\xd3\xbc?\x06\xa1\xb0\n^u\xd0?\x17\xae\x12\xff\xcd\xf1\xb0\xbf\xcap\xe3\x1c\xa2\x99\xf2?\xe21\x95I:\xe0\xb8?R>\xb1.\x8b2\xf2?\x04\x8b,\xcbU\xbd\xb3?\x84!^.8\xa8\xf4?\r\x98g\xa7\xa5\xde\xf8?\xf2\xf4\xbb\x87[\xf7\xea?\xb3\xe9\x01k\x1eB\xff?\xf6\xd3\xabW\xa0\xbc\xda?\x1d=\xd03\xb4\x80\xf6?if\xda\xe1\x1bd\xd2?\x1f\xbdQox\xf4\xfc?\xd4z`\xe6\x94\x9f\xdd?0\xb7\x8a\xac\t\xf1\xca?\xa7\xe0\xb3\xe6Qp\xb3\xbfg\x95<\x96\xfe=\xf8?\xa0\xb08\x05\xd9\'\xdb\xbf&\xfe\xaa\xea\xdb\x83\xdc?\xff\x8f\xa4\xd6\xab\x82\xf5\xbf\xe0CD\xaa\x04\x1d\xf6?vjc-\xcf\x1a\xf4?SF \x0f3\xa5\xd2\xbf\x83\xffs\x8bh\xf1\xd2\xbf\xae\x81J&P\x7f\xda?9&\xd4\x8f\xe8f\xc4\xbf\x82\x1e}\xf0\xf0y\xdf?\x0e\x94\x07\xd7\xfaU\xde?>\xca\x18u\x8f\xf1\xcb?\x06\xee\xfd\xb7\xbbi\xf0?o\xd6\x93\xe8\xeb=\xd5?\xd8\xff\xecD\xde\xcf\xf7?\xd5{\xd2\x13\xbd\xd7\xf8?\xac\xdcY\xd8\xf7\xe3\xb6\xbf\x8f[\xe3&OO\xde?\xf5\xc4\xed\n\\\xa5\xd0\xbf\xa7\xd4=\xb3p2\xf6\xbf\x1d\xe6\xa1\xe2\x11\x9f\xf8?\x0f\r\xc5\x9a\xff=\xfb?3\x89(W\xec\x91\xd1?1?7.\x17\x84\xce?\x8d\xd5\r\xb6\t\x91\xd3?+\xd9\x06\xe5n\x8f\xd4?\xe4\xa23\n\xad\xc6\xc1?L\xdc\xdbs\xa5\x8e\xd3?\xafp\xaa\x91\x11q\xb6?\x17r\x91\xb9b\xe8\x00@\xa2c\xbf\x81\x8c\xfc\xf0\xbf^\xba\x0f\xef\t\xbc\xd5?\x1c\x96Cg;T\xf4?\xe5i\x13\xee\x944\xe8?\x7f8\xb5\xb7\xe4y\xfb?<\xd9V\xaf\xbe\xf0\xf3\xbf\x90\xdfy\xfd\x16\xb5\xcb?)\xc7\xa8`_\xfd\xea?RXjW\xd1S\xc9\xbf\r\x07b&\x0et\xe3?3\xc2\xef\xc3\xdb\x85\xf9?\xa8u,E\x93B\xea\xbfg<\x80\xb5D\xfd\xeb\xbf\x97\xf8\x90\xf8\xcf\xce\xe6?\xe9J]<\xdf\x98\xf9?\xe3D\r\xf2\xa2%\x8a\xbf{\xa2\xd9|\xc8\x03\xea?\xf1\x08b\xcf\x1d\xac\xc6?\xaaO\xa8%\x9b\xcf\xee\xbf\xc2\x89\x85c\xfb\xc9\xff?\xc4\x10\x8a\xfb\x1e \xe8?O\x1f\x95U\xc9{\xf5?\x85&O\x82\xaa\x0e\xf7?\xa0p\xe1nX\xf2\xff?f\xec\x82\x07\xb5\x80\xf4?\x9e\x8c\x9c\xa4-\xd9\xff?\xf9\xa3\x00\x10\xe5\xe3\xb4?\xbe\xaf\x92\x89\x80\xe3\xd4?^\x01e\x87\x8b\r\xf4?Q\x01\x1f\xbc\x84Z\xd7?\x13\x93c\xe1Tw\xd1\xbf\xb7a\x00\xc4\xc2I\xe5?\xe4\x1ef\xd9\x1et\xd9?d\xa5LM\x8b\xdf\xd4\xbf\r!\x01)%z\xb5\xbf\xe1P\x15\xd6u\xbd\xfa\xbf\x96\xf4\xc9\xd40\x1e\xf6\xbf3\xa5\xf4q(\xda\xde\xbf\x87N\xeb\xbd\x1a\x12\xe3\xbfv\xfa\xa2\x9e\x9fb\xf4?\xe2\x08\x17\xccTw\xd0?\x06\xcaee\x99\xa4\xe2\xbfKR\xbe\xab\x81\xbc\xe6\xbf\x15w\xfd&\xcc@\xe9?\xe8\x1f\xb2\x14N\xd8\xe4\xbf\xa8\xf9\x1a\xd6\x04\xf5\x00@O\xcb\xa2|\xca\x8c\xb4?\xe9%\xb3\x98\x96\xd4\xf9?Z\x19\xd1\x9a\xa6\x0f\xcd?2f\xa9G\x17\x04\xdc?\xfc\xc9\xb5\x8bhr\xda?\xb9jf\r\x1c9\x04@\\\xe6h\xa5\x14$\xf1?\x06\xaf\xfb\xb9c#\xdf\xbf;?\xeac\xac\xb4\xf2?\xfb\xf3Ph$\x16\xfc?\x1c\xf6\xfcK\xf3N\xc9?\xc3D\x05cZs\xe1\xbf\x07w\xab\xc1\n\x16\xbd?\xce\xd9\xb7\x87J\x86\xd3?\xdd\xcbt>iX\xeb\xbf\x92\xb1\x18\x9f\x7f\x0b\xe2\xbf\xf0M\x08\xc6\x87\xc5\xeb?\x86\xf4\xb0b\x8d4\xe2?\xdb\xf0_\xd6\xaad\xc4\xbf\xf3\x9c\x0f\x13\x8f\xd1\xe3?y\x1a\x1e\x98\x00N\xe8?\x93"\xcbD\x84\r\xda?\x1e\xbd\xdbH\x84\xd0\xf2\xbf\xfb\xe7\xc4\'\xf4\xc5\xf9\xbfE\xe1a,F\xd0\xf7\xbf!As)\xeb\xcd\xb1?\x04\x9e\xc1/]a\xf0\xbf\xe8t\x93\x94d*!\x8d\xbf31^\x17|\xb5\xee?\x94\xcc\xbarxH\xd9\xbf\xb9d\xb1_\xb4\xbb\x01\xc0\\\xdf\x0f\xa4Z\x1e\xe7\xbf\x13\xefr\xc7;\xc6\xd4\xbfz\xd2*\xf8\xc9+\xf9\xbf8\xea\x9e7\x7fT\xf3\xbf\x8dz\xf3\x10\x1c9\x06\xc0,\x9d\x818\n\xbe\xd9\xbf{\xac\xdc\xffT\xde\xe8\xbf\xa5d%7\xbex\xfc\xbf\xdd\xd3}\x0c\xe9y\xf4\xbf\xf5\xb1\xb9\x0c\xbaZ\xee\xbf\xec\xbc$qQ\xa7\xd4\xbf1<\xf2_U\xd6\xfd\xbf_\x08d|\xda\xa8\xe3?P\xc7\xcf\xdcb7\xe8\xbf\x8c\x8e\xa4\x80\xd3\n\xdb\xbf\x15\xdf\x8e\x82B:\xd4?\xfc\xf5\x17\x0c|\x98\xd1?\xcc\x9a\xed\r\xe3\x92\xe2?mr=X\xc5\xe3\xf3\xbf\x1e\xc3\x99\xadT,\x02@\x93\x96\x8c\x00 \x85\xeb\xbf\xe5G4\xef\xf8\x02\xf8\xbf\'\xa5\x95\xd3\xd1,\xf9?/\xf0\xfbRJ\t\xf2?\x00^\xb6\x94\xc1k\xfb?}\x81\xdf\xc7\xc8\xbf\xe6\xbfS\xc1\xdf^u\xc9\xe0\xbf\x1a=?\xf3\xb2\x92\xea\xbf\x97\xba6:\x15\x14\xd1?\x94\xfa\xb1\x05\x1db\xf8\xbf\x80\xa7\x89.\xdb\xce\xf5\xbf\xa5\xcaI\xf6Lg\xd4\xbf\xfe8=j/2\xf4\xbf\xb7J3\x8e\xaa\x00\xfb\xbf\x0f\xaf\xa9\xb7\x11\x88\x06\xc0\x19\xab\xd2\x9d\xb2/\xff\xbf\xd70E\xebw\xd6\x06\xc0\xe4\xdb\xfe{\x01\xe3\xf9\xbf\xd0k\x02C\xef\xc5\xe0\xbfa\xcc\xdb\x14\x1fT\xe0?t\xec\xb7\x06\x00\xac\x01@\x1eA\xb8\xd8Y;\xcf?\xebi\x9b\x9d\x81\x8a\xbc?\xdc\xdbxq\xc5\xa6\xe6?\x91\x94\xfc\x7f\xb0\xf5\xe3\xbf\x1cm[\xd7>;\xc8?+\xb5P1\xb6\xef\xa0?tOK\xba\xeah\xc7?2u2\x7fhc\xc1?\x8b\x9d\x9c\x9c\xa7&\xd3?\xe1\xcc\xb4C\xe8\xc0\xf2?H@\x1d\x9d\xb9\x14\xf4?e\xff\x0b\xeeb\xaf\xe5\xbf\xbfx\x8cT\xf5\xf5\xf1\xbf\xb2\xcdRre\t\x03\xc0\x86\xac\xda\xc0\x14:\xe5\xbf5\xbb\xb4)U(\xe7\xbf\xab\xf8\xf8\xc2P-\xd9?\xbd\xb8a\x08\x17q\xde\xbf\x99\x9b\xf8\x11_\x1d\xf8\xbf\xcc\x10K\x88\xec\xdb\xf1\xbf\xe6t\x1b\x0bz\\\xd7\xbft\x02G\xc8\x9a\x8c\xb4\xbf\x81\xbdx\xf8\xc9"\xff\xbf\xa8\xda;\t*h\xd1\xbf\xa4\x91\x96\xf42\x19\x07\xc0rP\x0f\x1b\xcf\x80\xf8\xbf\xc5B\xce\x90\xe5E\xe5\xbf>\x02\xbf\x81VB\xe1?\xf0\xba\xcf\x99\x8ev\xe4?\xc9\x01p`\xd82r\xbf\xe1\x98l\xab\x8c\x05\xe4\xbf\x93\xe38\xf4\x978\xe2?w\xe5\xdcTU\xc2\xb4?\xf7\x8a\x1b\x8b\xbf\xc1\xd4?\xc1u\x13\xdb\x8c0\xa2\xbf\xab\x01\xf4\x99;\xa7\xe9\xbf\xe6\xc0\xc1k\xd4\xaa\xd2\xbf\xda\xbe\xf8\xf9B\x9f\xe6?Z\x9a\x04\xa0\x98\x0c\xb4\xbf\x96\xdaM\x8c\xc6\x83\xf9\xbf>*-\x8a\x84\xe2\xc5\xbfhD\xf6[\xd5\x8d\xf0\xbf\x0f\x1a\xbe\xa7UI\xe8\xbf\x8d5\xe9\xf4S\xf3\xdf\xbf\x88\x07\xac;\xc0\\\xf0\xbf\xd2$;\xc2\x90\xc6\xea\xbfwG\r\x8c\x1b\x85\xc2?D\xc1\xda\xecr;\xe5\xbf\xd0\x84\xde,\xe5\x82\x03\xc0\x12\xd3\xa69\x8c\n\xcd\xbf\xca\xfb\x8a^\x06\xe8\xf5\xbf\x1a \xa6K,\x91\xa6\xbfK\xe4l\xec6)\xf2?\xf1\xe5\xbb\xf6r\xd3\xfa\xbf\xa6\xb2\x1b\xd15T\xea\xbfTO\x95]\xfe\x96\xf6?\x93H\xac^\xf1\xee\xe8\xbfl\x84\xaey|\xb8\xd1?\x9di\xeezr\xaf\xeb?"\x10o\xa8\xcf\x12\xa4?\x16\x86\xe4\xa75\xf3\xe8\xbfO\x0f\x9d\xacvv\xd2\xbf\xaf|m\x99z\x8e\x01@\xe3\x02l\x980\x1f\xd3\xbfm\xf06\xeb\xef\xe2\xf5\xbf\x85\xa2<\x16n\xb9\xd2?\x8f\x8d\xd5:\x14V\xfb\xbf5S\x12\x8eV\xa6\xb3?\xd6q\xd7E\x80+\xf4?w\xfaI4V\xea\xd3\xbfq\xacNyZ\xae\xf9?\xe4\xc0\xd8!\x84\xcb\xf8?\x802!J\x95\xa5\xb1?A\x12\xe8\xaa\xf1\x15\xfb?\x1a\xb1\xf9\x17\xbf\x05\xed?\x19\xb1\xa6@\x07\x8d\xf8?\xf8\x01m\xb9\xdet\xde?bZ%\xb3\x96\xdd\xf9?\x0c\xa9\xcd\x14\xa8H\xea?\x96^\xb3\xe5\x19\xa3\xe8\xbf\xe1\xc9\x10\xe0Nc\xde\xbfV(\xf0U\xae+\xf6?\x10O\'\xbd\x04\x0b\xec?\xa8\xc9\x10\x1a\xb9\xa6\xed?\x13en)%\x11\xcb?q\xc3QOu\xb4\xf9\xbf\x024."\xb3$\xf3\xbf\x92tl;\x9c\x08\xf1?Q\xf2k%\xb4J\xe8\xbf\xf6\x89W\xe1\xc51\xf2?\x93o\xa0\xd2"\x0f\xf2\xbfO\x1e\x89p\xff\x92\xf8?\x1bgV\xa5%9\xe4\xbf\xaf\xdb\xae\x97[{\xc3\xbfy\xccCd\x0e\xe0\xe0\xbf\x8f\xf0h|\xb7\x0b\xcb\xbfd\xfa\x85\xfa\xff\x9c\xb1\xbfz\xce\xe2\x9b\xe1w\xf6?\xc5)\xda\n\xa0\xe3\xf7?^\x1a\xbe\xba\x85\x8c\xd3?\x9d\x85\x91\xc6\x03-\xec?N\xbb[\x06e\x86\xe4?\xf4\xac\xae\xcd\x18\xa2\xdf\xbfL\xb50\x8e\xcfX\xe0\xbfj\x0e\xb79\xf5\x03\xfe?\xd5\x0b\x05\x85\xdc\xd1\xb1?\x92J\xdf\x89>[\xef?E\xc4\xfd\x8c\x8c\xd3\xde\xbf!\xfa*f\xaac\xf3?\x97\xe2R\x0b\x0ck\xe8\xbf\xfb+a\xb0\xd2\xbe\xf1?~<\x1a_\x96\xe2\xe1\xbf\n\xf7w\x08\x92\xbc\xe2\xbf\x7f!\x1a\xc2\t\x9f\xf1?\x86\xec\xda\x1fd^\xe4\xbfsD\x86\\)\xb2\xe0\xbfB:\xaaRRF\xf2?\xbb\x14\x93\x9f\xd0x\xe6?Y\t\xc8\xe5\xc0\xa5\xf4\xbf\xae\xa2\xcf\x86\xd4\x16\xff?\xa5f\xd7\xaaO\x07\xc6\xbff \xb0u\x06\xb3\xf8?\xd3}=\xd9\x18\xbe\xed?\x19(\x031\xc43\xd0\xbf"\xc4\xcd\xb5\x84/\x00@\x1d\x92`@\xf1\xf8\xe3\xbf\xf7%\x1b\xf7\x1e\xe6\xba?\xd3\x9aAe8\xbc\xe3?\x0b\xcbK\x9f@I\xeb\xbfZ\xd8\x14\xd3\\pq\xbfkh<\x0e\n\x00\xef?\x92@\x1e\x84\xfc\xd6\xe7\xbf\xf1h=\xc5\x86\x12\xd0\xbfC\x82"\x0e\xe3\xab\xc1\xbfPD\xa1ggt\xe8?\xa5\xc9#~\x8a>\x00@\x18\x1f]\x89sb\xfe?\x89\x8e\xf2\x0e\xd9#\xea\xbf\x1b\xb5\xf47+\xce\xf1?mi\xad\xbd\xed\xdb\x01@i\xbe\xad\xc7\x8a\x81\xca\xbf\xc7\x01<\x10\x1d\x16\xec?\xe6@j*\xa5d\xe1?\xd6E\xeb\x96\xc1\xab\xe2\xbfN\xe7\x18~\x08\x83\xd0?\xc3\xe0_\xa7,\x1c\xfb?:\xa4\x19:\xfa\xe4\xf4?\x08]b<\x0b\x7f\xee?\x1b\x92\x10]\xcf.\xb1?\x19\xb28\x91<\x87\xdd?\x14\xa5i\xa2\xb30\xce\xbfB\xbe\xd5\x84g\xf1\xe4?A\xce\xcb\xb8\x1bO\xe3?!\xb1\xc7/\x18o\xc3\xbf\xcb\xbd)\xd7\xcf\'\xf1\xbf\xf00E\xe0<\xe7\xe9?\x1btu\x81X\x10\x00@\xf6)\xf8\xea\xba\xdc\xfc\xbf\xeb\xfe(P\xc3\r\xd1\xd5R\xc4\xbf +"\xb2w\xb9\xd7\xbf\x99\xda\xe5\xdbm\x04\xdd\xbf|@\x1d\xcb5\xcd\xfa\xbf\x8a\x81\x92W\x18\x95\xe4\xbf1v\xcb\xa4F\x9c\xed?\xd8\x99\xf7\xbb$\x7f\xf2\xbf\xbe\x97\xee\xc8z\x00\xd2?\xfdY\xd2\x1d\xf3m\xe8\xbf:Z2\xa1\xe2\x89\xda?\xa75\x91}%\xc8\xe5?P\xcb+.Gg\xd1?\x7f\x80*\xfe /\xd5?\x969-\xca*\xf6\xec\xbf\xa6y~\x1f)\xa3\xeb\xbf\xafK\xd6)Y5\xf5?\xb2\xaf+\x10\xa1\x14\xd4?\xf9\xa4\xfb6\x96\x97\xda\xbf\x0c\xbc\xcc\\oS\xe5?\x91V<3\xcd\xfd\xd2?`\xf5W-V\xec\xd7\xbfH6S{Y\xaf\xff\xbf\xb6K\xa2\x07\xc2;\xc2?\xce4\x8a0\xa1\n\xd6\xbfy\x0c\xd8V\xa2"\xd4\xbf\xb7-\xcdP\xbf\x88\xe6?\x83\x1e\xad\xb8E\xa4\xef?\xf9\xff\x97\xf9\xfaT\xeb\xbf>P\xea\x18T\x03\xcb\xbf\xe6\xa7\xa5\xe6\xe0\xba\xff\xbf\x98\xa7X\x9a\xd2\x99\xf8\xbf\xf8\xc5\x1a\xad\x91\x1a\xc4\xbf\x07\xa15\xb3[\xcb\xd8\xbf\x08\x9b+@\xcf~\xf1? \x8d9\x84\xc7w\xe1\xbf\xb8\x1b\xfdo\xc7\xcf\xca?\xe1\x0f/}:\r\xe5\xbf\x10 A\xa4\xca~\xe3?\xe2\xf1z\x87\xb8\x8b\xc0?5U\x0c\xdc\xd8\x91\xcd?Ou\xb8\x83}N\x06@+\x9c6h-\xd4\xf9\xbf\x0f\x1e\xbd\x0e\x9b\x91\xc5\xbf\xcb@\x90\x85R\xbf\xe0\xbf\x19N\xc0\xa4\x03\x14\x95?VU\xd9\x96g\x1f\xd8?\xc63F\xdb\x06\xb1\xf5?\x03\r\xc2\x18\xccs\xf1\xbfr\xf5\x80j\x9c\x1e\xf3\xbf\xdd\xa7f\xd8\xbc\xe8\xe7\xbfBt@\xb6 <\xf2?\x96\xc1\xc1s\xf8u\xf5\xbf\x98\xb3`\xdbh\xa05?;p\xa9\xec\xfe\x98\xe0?\xc4/\x00\x0b\xc7@\xd1\xbf0\'\xc6\x82G[\xfa?\xdd\x08Q\x92\xe1\xe9\xc8\xbf\xa4@\xb3\xca\x11\xd6\xcc?\xd9\xb8\xd7\xb5\xb0\x9e~?\x9d\x97e \xaf\xdf\xdc?Uw\x9c\x95T3\x05\xc0\xb4<\xec\x1f\xa3H\xe3?\xeb\xbf]\xa2\xd6B\xe2\xbf\xf3\x03:f\xe7|\xb6\xbf\xc7u\xd5o\xb3H\xf3?\x17q\xef\xc7\xae\xf7\xda\xbfs\n\x90Be\xbd\xeb\xbf\xc04\xab\x02o\xe2\xdc?&\x8f6\xe4\xe1\x1e\xdc?2\x14\x9f5\xebJ\xfa\xbfr4\xb9_\xa2>\xec\xbf\x0c\xea\xbck\x06\xd6\x02@w1&ry0\xbf?U\x83\x0cr\x7f\xc6\xd8?\xe6\xd7 \xc2\xdd\xf9\xe6\xbf\xa5\xc5\xf5Nqc\xe9\xbfc4\xfc&\x7f\x06\xb4\xbf\xf7\n\xf5\xec\xa3\xb5\xe3\xbf\xea\xf9e_\xd4\xb3\xcb?Rb\xbf\xba\xc7\xcb\xfe?\x16\xd8\xa8J5\xca\xe0\xbfZ\x95\xa4\x00\xca\x9d\xeb\xbf\t\xef\xaa\xc3\xed\x83\xfb?\xb6[/<4/\xe7?9\x1c\xe1\xd50\xe5\xf2?\xe1y\xd14\x11\xe6\xec\xbf\x12\xb2\nN\xc5\xcc\xe5?\xf0\xe3\x85\xa3`\x9d\xe2\xbf\x10w\x0e\xa4\x7f(\xf7\xbf\xe4k\xd0\xab\x00\xee\xc1?9\xae$\xf9\xe50\xf6\xbf\xcc\xc6\x10\x95\x12\xa1\xaa?\xe1\xb1\xc7\xff\xd7\x8f\xef?\xed\xd5\xf5CN\xb8\xc3\xbf\t\xb3\xae\xf3\xc1f\xd8\xbf30mc\x14w\xf9?\x98a\x9fs\xf3\t\xf4\xbf\x8c\xc7\x9b\x12\xfce\xc0\xbf\x90Olq\xde0\xd6?\xf7\xf2\x9c\x16\xc4\x96\xec?\x05/\xaa\xb2t1\xe8?IY\x9cQ\x1d\xb8\xd1\xbf\x8a\x89\xf9\t\xa9\x8d\xd9\xbf#]\xe2\x01\x12\xc8\xde?5\x0f\x8d#\x90\xe3\xb6\xbf\xf8\xa0\xcd@\xaf\x06\xed?[\xf2\x13\xf9\x0ep\xf7?\xe7\x02\xcfQB\x83\x02@?C\t\xaaQ\x93\xfb?x\x91LB\x1a\x83\xd5?\xfb\x99!w\x87F\xda?\xb2\x10i\xdbm\xf9\xf4?\x81\x12\xea\xb7r\x8b\xed\xbf(\xcc\x89\xd6<\x9f\xe2\xbf\xba\xa5l2\xa1\x19\xe5?p\x9bH \xca\x9e\xfb\xbf\xacb\xcb\x11J5\xe2?\xe6\xbe\xd9\x86\x90\xd7\xd8?dX\xc3j\x83\x8f\xe8?\x1dj~`\x9a@\xe2?,\x07"A\x90\xfb\xe3\xbf%\xfeJ\n\x0b\x05\xe3?\x99\xd59\x1f=\xec\xfd?\xb4\x0b\xdf0(\xac\xf8?\xc4\xfa\xd9\xeb\xd0G\xf7?p\xa4\x12U\x80\x7f\xfd?\x82\x8b\x9b\x89)\xf3\xe5?\xa3@\xb1\xa7\xad\xfa\x02@\xd7\xeeO\x85H\x05\xe5\xbf\x8a\xbf\xbb\x19\x13\x99\xda\xbfs\xc48\x8910\xf3\xbf\xf7\xbf\xb8\xe7\x12\x1f\xf2?d\\\xca\x84&\x1c\xe1?n\xae\x19\x02\xa6 \xef?t[\x86b\xe6\x92\x8b?\x8c:{\xc8\x03\x94\xb0?N\xa4\xa7\x8b\xd5\xe7\xed?}UY\xc5S\xe2\xf0?\xdaK\xed\x81\x92n\xf9?\xd7\x8c\xb0\x90\xec\xd2\xcb?W\xfe\xd7UK\xf9\xe1?2\xf6\x1c\xfd\r\xee\xe3\xbf\x9b\xf9\xc7\xe7\xde\x1e\xf6\xbf\x96!\xcc\xad\xf9=\xef?\x03\xfc\xfa4\x89\'\xf3\xbf\xec\xf3\xe9\x93\xb1\xb1\xd9\xbf\xe7\x81r\x9e\x94J\xe4\xbf\xb3,\x9b\xbdT\xfb\xcf?\xcc\x833u,e\xd8?\xd45k\xd5\xa6\xfe\xd6\xbf\x93~\xa2\xadV\xbf\x03@^_t\x18n0\x13\xe4?\xa2j\x06\x92t`\xeb?1\x1b\xe6\x05da\xf7?6+\xb5\x80\xb5\xec\xfe?\x8aw\xd6T\x9e\x8d\xc1?m\xda\xe8\xf4\x9c\x84\xec?\xad_\x19\x18z\x86\xed\xbf\xd8}b\x16\\<\xe0?wl\xa5dv!\xd3\xbf\xc7\x0c\xc9n\xde\xb4\xfc\xbf\xf0\xe0\xb0\xf8\'3\xf9?\x1f\xe7\xaf*\xc7c\xde\xbf\x8cG\x9d\x82 \xf8\xf7?=\xf9\xe3L\x02q\xd3\xbfL\xc9\'~I~\xc9?\x08?\xe4\xae&\x08\xf3?\xdeB\xf5\xf3D \xf0?\x08i.\xf0\x91{\xc7\xbf\xa7\xdbQy\xfe\x0b\xe8?\xb5\x1eZO\x05\xcc\xf6\xbf$J_\xad\xaf1\xe6?\x07\xcf\xc64\xf3)\xf1?\xe8W\xd6\x84H3\xf5?\x89\xe7i\xcf \xd6\xda\xbf4\x16C\xfe\xee\xf2\xe2\xbf\x859$Y\xc7\x9c\xeb?\x0f\xa6\xcc}\x05P\xfc?\xe0\x8d\xa5h\xd1|\x04@b\x84\xac\xb6\x9a\xba\x01@x\x1c\xfc\xc7\x89\xe6\xc9\xbf\xc7)\xa9\x07\xef\x9d\xf5\xbf\xf5NB<\xd8\xf5\xe3\xbf\xf4\xb7\xe3\x8d^\xc8\xf7?f\x1e\xfd\x87\xc3U\xf2\xbf\xed~\xb5\xb6]\x9f\xf6\xbfh\xc0\xfb\x80J,\xe0?\xf1P\x072\xd5\x16\xe1\xbf;\x89\x07\xee\xe9\xff\xb2\xbfoc\xed(\xd6{\x08@80\x94\xdeN\xf0\x01@\xac\x17W\x17\xe3,\xef?\xea\xa6\x90_x:\xea\xbfa\xcc\xaf\xc0\x0bF\x02@\xd7>x\xf7\xa8\x88\xb5\xbf\xef\x01\xff\xd5l\xce\xf7?\xf25\xec< \x0f\x83\xbf\xdb\x99\x1b:\x85\xc3\xc7\xbfu\xdf*{\xbd5\xc5\xbfM\xa4\xed\x8e\xc8\xf0\xee\xbf\x114X\xf1\xc6\xef\xfa\xbf\x1aA\xdfm\xe1F\xec\xbf\xe6)\x97\xa4\x85\x82\xff?\x9c,yw]Y\xbb\xbf\xb5q\xa81\xe2\x89\xe7?\x02\x90\x03ns\xce\xfe?\xa0\xec\xba\xbc\x97\x8c\xf0?n\x9f\xc4s\xcd\x83\x02@-\xa07k\xe2\x89\xfd\xbf\xbf\x89\xeb\x8cfw\xfe\xbf\x1a[G\xadq2\xf2\xbf\xc2\x9b\xad\xdc\xa7\xc2\xe6\xbf\\i\xe4\xec\x85\x11\xe3?\xb6\xc9\xd6\xcd\x836\xfd?\xbfL\xd5\xc5\x1c\xd3\xf3?g\x80\xb3\x00{~\xfb\xbf\xd8^\xab\xa4\x87\xe3\xf8?\x93\x8dh\xdbC \xe3\xbf\xee7\xd1;\xd6%\xd5?.\'\x81\xb0yZ\xed\xbf<\xe9oG\xe2@\xfc?Qw\x1f\xfaw\xb1\xd6?\x9d\'\xb0\xf2\xac\x11\xd8\xbf+\x11 \xa6\xca\xf3\xcf?\xb8\xff\x83~Bd\xf4?k\x80\x03\xab\xb84\xfd?\x04\x9f\xc9}A\x8a\xde?\x1a\x9c\x16\xe3F\xbd\x01\xc0\xb9&\xabm\x12\xb6\x01\xc0\x08\x84*\x9e\xb1\x8b\xf3\xbf`\xfa\x18\xce\xd6V\xe5?3\xa6&\x02p\xe2\xd8\xbf\xbfa\x95!\xb7~\xfb?\x08a\xed-!\x17\xd0\xbf\xc2Mp\x01c\xe0\x03@%,\xd6@hO\x07@C\xdag=\x01\x93\xec?\x84cS\xd7\x1eF\xcf\xbf\x13\xfc\x99\xe1\xb8b\xd4?o\x7f\x08\xd2~~\xed?!\x13\xd1\x14k<\xe0\xbft>(\x98\xb6{\xe4?\xf5\xc3*\x03\xdb\xa3\xe0\xbf\xbba\x14\x19j\x0b\xf8\xbf\xd8O\xfcG2\x8c\xed\xbf9!\xf1\xff\xfc]\x03@\rN\xa6\xd3:\x97\xdb\xbf\x0b\x81\xfc8\xe2|\xc6?\x02\xfcI\xf2\x8a\x15\xe0\xbf\xb8\x1e\x1c\x7f\xc9B\xd2?"\x81o\xa5\x0f\x8b\x06@\xec\xa2\x051\xe1\xc0\xee?\xdd84\xed/\x99\x03@\x1aGv\xee]L\x0b@\xa0\xf0\xdc/\xfc\xab\n@/\x9b\x889M}\xff?A\xeb$\xed]\x15\xd2?\x0eP\x8a?\xd5\x83\xe7?\xb4}$\xe1\xb7f\x92\xbf\x8e\xef\x8c\xc13\xe6\xfc\xbf\x0b\xe4\xea/\x0e\x9d\xf4\xbfV\xfd\xf0\xab\xcfy\xe6\xbf\x89G\x86\xc6\xcc\x85\xe9?1)Y\x93\x1c\x84\x0c@jjQol"\xd7\xbf:\xcfQ}\xcdf\xf6\xbfN=\xdfA\tU\xc5\xbf+\x00\xdc\xec\x1e\x1b\xd3?\xb5\xd7\xe7^\xe5\xba\xc2\xbfn\xe2\xe0\x1cZh\xd6\xbf3\xd8\\\x12\x13\xe6\xf6\xbf;h^_su\xdc?\xe8\xbb\x8co\x06\xba\xd0\xbf\xcd\x93\'\xbc\xbb\xc5\xdf?g\xbe,[\x10\r\xfd?\x18\x13\xb2aqK\xf0?,\xe0\x1d\xd6\xdc\xb0\xf8?\xef\xd3\xf2\x82\x80\xb7\xe5?E\xb4\x1fJU2\xea?\x8e\xd7=\t\xde"\xd4\xbf2-\xf5\xb6\xb8\xf3\xf5?\xa2\x89Xw#\xc4\xed?\xb2\x1a\xb5\x1dF\xf7\x07@\xd1\xe7\xa8\xa46\x07\xed?\x87\x18i\xd3\xa6\xc8\xf8\xbf\xa8\xb1O\xc7wF\xe4\xbf2%>j!\x01\xd0\xbf\x8f\xa0\x98\xd4z\x1a\xd3\xbfsb?\x0e\xb2\x0c\x01\xc0\xb2Y\xde&\xbf\xf0\x04\xc0A\xcd\x0b\x82i\xcf\xf0\xbf\x1b~\x82t\x82G\xb1\xbf\x03\x10\xad\x03]W\xe1\xbf(\xe1L@\xa3\xd8\xf6\xbf)\x9b\x9f\xe3\x1e\x98\xd2\xbf\x83\n\x98\x94\xd1\r\x05@\xe7\x92o\xe9<\xd5\xe3?$=2\xcep>\xbc?\x15\x8b\xda\xa5\x15P\xe7?\x00\x93\xfb>^\xb9\xf9\xbf\xbdh\xe3u\x9e\x83\xea?\x86\xc59\x151\xb4r\xbf\xf2s\x17E\x9d\xad\xf6\xbf\xd7\xf8\x80\xd4\xd8\xcb\xe0?K\xd9OW\xf7\x1f\xfb?v\x8a\xad}\xd5\xfc\x01@\x00\xc6\xd8\r\xe6\x15\xd9?;,\xc8S,\xbc\xe5?\xa5\x06\x96\x039a\xec?edy@C\x9a\x04@q\xcfIL\x9a9\x00@\xcf#\xb8%\xc4\x8b\x00@\xf9\x96\x00vVY\xe4?\x8f^\x8aL\x05>\xd2?3\xc4\x83\x84\xc4\x0b\xee?\x92\xdf-\x92U\xa6\xac\xbf4i\x91\xe7\xd1\xb6\xb4\xbf\xeaq\xa2X\x7f\x8a\x0c\xc0\x89\xe1$D\x97\xaa\xf0\xbf\xdao:\x01\xcc\x82\xdd\xbf\xc82\xeaW\x13\xb7\xf4\xbf\xd7!\x05O|I\xe6?cN\xc9E>\x0e\xfd?1O\xb9\xa9I\xb8\xc4?\x19\xa5W\xf8%\x91\xd9?8L\xf7\x0f\xcaA\xe0?\xcf\x06\x9a\xf7jh\xf4\xbfE\x01\xc2\x00\x8c\x10\x01\xc0\x9cPrzw2\x0b\xc0*\xce\xa4~\xc4\\\x01\xc0wA=\x0c\x0f?\x07\xc0\'\xb1\xd6f6}\xe7?\x05\xa0~\xf2\xf3Q\xf5?\xfe\xd8p\xb2\xd7\x82\xf0\xbf\xd4\n\xd0\xf9d\x19\xc4\xbf\xe1\x02\xb64t\xf8\xc2?\xac\tZAV\xbb\x03@/\xf7\xd0\x7f\xc0\xcc\xfc?e\xc7<\xb9{~\xe8?\xd9(D8ry\x06@\x83\xe6Xb2\xa1\xd0\xbf\xb0\xad\xefF\x90\xa0\xfb?2\x83&3\xe9-\xee\xbfJ\xb0\xd3\xe9\xae\xb0\xcd\xbf\x88\xfb\x9bz\x00\xf9\xe1\xbf\xbc\x00\xfcY~w\xd1?\x93\'E\xf1\xeb\xb2\xdc?R`R|\xcb\x04\xf5\xbf\xe7P3&\xef\xe6\xf2?w l\x19\x8f\xb9\xf2\xbf\x051d\x88\xf0\xa9\x9a?\x83nx\xe0s\x96\xff?ua*\x00\x97T\xcf?\xca\'\xc1\xdf\x8e\x84\xe9\xbf\x7f\xd1_H\x13a\xbb?\x19\xc5Z\xc5\x1a\xc6\xfd\xbfO\x93](p{\x04\xc0\xee\xec\x98\xbd\x013\xf4\xbf\x17\xf1\xd9\xc5\xd46\x05\xc0\x83g\xb8\xbee\x8e\xfb\xbf\x88o\xb6P~\x16\xef?\x98\xbd\xeb\x7f\xf0\xf6\xe1\xbf\xd2?C\xeb3\x97\xf8\xbf\xe5\x06\r\xd8\xf7Z\xf2?\r\x19\xe5$SZ\xef?.\x82\xdf\xbb\x18v\x02@$B\x8a L\xd8\xe3?.\xe9=\xed1\xf4\xfe\xbfO\x9f\xc2n/\x0e\xb4\xbfd\xc0\xee]\xf3;\xd8?UE\xcd\xf3\xaag\xfe\xbf\xb8l\x90\x87\x93h\xe9\xbf\x07<\xd3\xd7\'\x01\xc1?\xe2\x85\xd5\xd2\x16\xbd\xec\xbf\x99Uq>\xeb\xd0\xe4?\xf4\x12<\x7fb\xb3\xc0?\x89\r\x1e.\x8d|\xe3\xbf\xf1\xbc\x92+\xc4\xbe\xe4?\'\x90\xd6]\x88\xef\xa4\xbfn\xad\t\x05Pm\xed\xbf\xe4\x83\x94\xcf\xedq\xcc\xbf\xc7\xfe\x1b\xc3\xa6Y\xdb?\xa79:\xa7\xf0\xa7\xd9?\xb5P%\xcd\xe6G\xf4\xbf@\x1e ~\xacY\xe3\xbf\xf69\xb0&\x8c\xd3\x02\xc0\x11\x89\xa3\xb1\xe4\xf6\xff\xbfe\xec\x1d\xb01\xe2\x07\xc0\x8bj\xd6|@\x13\xd6\xbf\x11\xce\xbeJ\xbe\x15\xb1\xbf\xaf\xdcaW\xed\x84\xfa\xbf\t\xd4\xe9\x88\xd3\xb7\xf1?\xf1\xc6H\xc0\x97\xab\xea?|\x02\xe9g\x10\xb0\xfa?\xc8\xb6hIz\xbd\x01@\xc2\xc8}\xa7\xc6\xe3\x02\xc0\xa2_f$\xd4\x7f\xee\xbf,\x8d\x8c\xe3q\xac\xa2\xbf\x0b\x9e\xb31\xf4I\xd7\xbf\x155\xf8\xe1\xdaj\xf7\xbf\xa6\xc8^\xa2\xe59\xdc?!@[m\x0e\x7f\xf3\xbf\x05\x97\xe5\x1fc\xb5\xe4\xbf\xa0\\\xf9\xd9\x92\xf2\xf3\xbf\x82\x94\xe01\xe3\xc1\xc0\xbf[\x08\xe3\xde\xa3w\xe2?\xafk\xc5\xe7\xbb`\xc9\xbf8\xef\xc7\x86\xb5 \xf9?\x8b\x87bd\xee\x13\xf7?\xec\xba\xfe\xa4\xfe\xac\xc0?\x1e,F\x06r\xcb\xe1?9\x00B\xd1\x0c&\xae\xbf\xb2H\xe7^#\x0c\xaf\xbf\xab;\x06\xa8\x82\xe4\xf1\xbf\x1c3\x014h\xb3\x00\xc0\xb0\x98H?\x89;\xdf\xbf:\xeb\x17\x8b%_\n\xc0\xde$\x8a?3\x87\xfa\xbf\xfb\xda\x12\x8b\xa4\xea\xd2\xbf\xca\xc0\xb0\x12\xe5\xd9\xdd?\x90\xe1\xf4\xef\x17\xaa\xe9?\xfd\xbaYj\x08}\x01\xc0\x99\xb8\xb7\t\xa0\xc8\xf1\xbf\xff4\x02\xc5\xe8)\xea?\xfa80N\x03\x13\xb5\xbf7t\x93\xea)\x93\xea\xbfq\xbfll,\xb1\xf5\xbf)\x18\xce\\b6\x02\xc0\x10|\xc7\xaf\xf1\x7f\xcb\xbfx*\xa1:8\xcc\xe9\xbf2\xc1<\x0cM\xb9\xe3?\xa3\xc8H\xa4\tA\xf9\xbf\x89\x8c\xdf\xb5\xf5\x82\xd7\xbf\x8c\t\xdf\xdd\x844\xc3?\xd8\xbb\xeez\x15\x01\xf1?A\x0c\xdb\x9e\xf7\x81\xea?.\xf0;\xf3\x1f\xf3\xe7?\x82R\x9b\xf7T\xde\x9d\xbf\xfe\x05 \xa9\x80}\xeb?\xf2?\x11\x03\xd8\xa9\xce\x1e\xd8?\x11:\xf8Y\xdbU\xe5\xbf\x8b\xb4\x89t[{\xde?L\x07U\xfb\x01[\xe6\xbf\r\\\x93\xb6*\xa4\xf1\xbf\x0fu\x9c\xa2\xafq\xce\xbf\xc3\x84\xd3^\xf8\x87\xc9\xbf\xbfd\x8b(\xb1>\x03\xc0\x8cN\rVzI\x07\xc0y\x12$\xaa\x981\xbf\xbf\xb5\x86\xce\x17\xaf\xf2\xe9?\x91\xe1\x87e|\x14\xcc\xbf\xb0\xa2\xd5\xa3\xe6z\xba\xbfxb\xdf9:h\xbc?#e\xde\xc4\x84\xc0\xd9\xbf\xb6\xf6\x04\xb9\xb0\x9c\xf9\xbf\xa9s\xe2B\x0c\x96\xef\xbf\x06\t\x0c\x01\x95T\xe0\xbf\xe1\xb5\x93\x83\x96z\xf7\xbf*\xd2X\x87\x03\xc0s?\xef4\xc4\xaf\x15\xc5\xec?f\x0c\xa8\xc2\x11\x03\x96\xbf\xf0\x8c\x96\x16\xf8\xe1\x01\xc0\xb5=\xfa\x08\xa4\xcd\xe5\xbf\xe6\\B<\x0e\x0c\xfd?\x90\xf1@\xa2\x8c`\xf6?V\xc6m\xe2}\xfd\xc7?k\x0c\r\xdbT\xa5\xf9\xbfP\xff"\xe8\x1e\x80\xeb\xbf]n\xc2\x99\xf1\xa2\xc7?\x03\t0i\\\x8a\xac\xbfz\xea\xf1\xa6\x96=\xd4?P \xb8\xfc\x04(J?\x9c\x11\xfb\xf1\xef\xc9\xd3?\r\x97\x86\xa3_\xfa\xee\xbf=E6\xe2\n\x7f\xf7\xbf\x17\xec#\xab\x19\x97\xe5\xbfDK*\xcc\xdfu\xfb\xbf\x96\x99\xfd\xef\xdb\xd4\xf3?\xa6]\x9e:\xe9{\xf9?m\xa8\x95\xd2\x8b\xef\xf0\xbf\xd7\x99\xfa\xa1U\xc2\xde\xbf\x1e\xf9\xd2\x9f\xa5g\xa2?\xc2\xb8.\x8aT\xc6\xec\xbf0\xe1\xd1\xaeB\xcd\xd3?\xb6\x06\xab\x1b"o\xe7\xbf/\x9a\xe2\xcf\x05>\xdc\xbf\x9a\x14\xd7\xa1\xe5+\xfc\xbf\xa2\xc9\xa8\xfc\xdc\xa5\xec\xbf\xea\x0f\x12x^\xb6\xf2\xbf\x82\xf2\xa5\x92V\xe0\xf6?\x97;\x1b\x7f\x01\xaa\xe9?\xb5.X\x83\x1b\x05\xfa?J\xa3\xb2\xce\xc8Y\xe3?\x8a\x8e\xc6\x86ln\xc0\xbf\xcd\xe8\xe6:\x93\x8e\xf1?\x8e\xee\x94\xdf/2\xe1?=0t\xc3#\x00\xd8\xbf\xfe|\x87\x08)J\xde\xbf\x93d\xcb\xdd\xd8\x97\xf9\xbfZ\xe0|\xb5T\x01\xe1\xbf\x9b\xe8\x8a\xe5\xeb*\xe6?`\xba\xef\xa7\x90\xf2\xf0\xbf:\xac/\x05r\x04\xf6\xbfd\x05\xc6Bt\x0c\xd0?\xdbcFXV\x91\xf0\xbf\xff\xed\xac\xe5\xf9n\x89?\x13T\x18V\xb5\x87\xdb\xbf@\x07\xde\x9f\xbb\r_?\x97A\xfb\xc4\x97[\xde\xbfz6\xab\xd5J\r\xe8?\x19\xe7\xef\xd7^.\xd0?\x12\x1b\xe2>\xf5\xc1\xe8?V^u\xca\xa9A\xfd\xbf\x1c=\xc1\x16\x0f\xcc\xcf\xbf\x0c!J\xdd.?\xcd\xbf\xc2]zq`8\x04\xc0S\xc0\x9a\xc8\xb8<\xf5\xbf\xad*f\xde[\x80\xef\xbf\xb4.PG<\x1c\xd0?\xd7\xb1\xf5\xbd\nC\xdf?\x0e\x8e\xf2\x0fK_\xf4\xbf\xe5\x92\x8b\'>s\xf0?\xa5\xca\xd1/Q\x0f\xf0\xbfBq\xe4\x97\xdc\xab\xeb\xbf\xdeS\xc3C\xc0]\xd2\xbf\xa9\x05F\x1c;\x96\xe6\xbf\xdbp\x0f\xf2{1\xb6\xbfT7Uno\xc5\xd8?\xdc\x87\xf3\x80~D\xe0?\xffg\x06\r\x0e\x98\xfa\xbf4\x92\xb6\xe4\xfa\x83\xf0?>M\xdf"ge\xda\xbf\x9d]\x93\x8e\x8d\xd6\xf0\xbf\xb5\x08\x1f\xd4\xfc\x9c\xd8?\xe1\xf2\xc5\xfc\xb9\x91\xec?\x8e\xd8R\x82Z(\xf8?/\x89v\xd4\xf5\xf3\xe1\xbf\x02ch\xc2\xef!\xee?\x04\xb6\x90\x9b\xf4}\xdd\xbfm\xb2\xd2s6\x03\xde?\xb4B\xf3n\xcd\t\x8e\xbf\x80J\xad\x95]\xcf\x01\xc0v\xb9^;\xa9\x9e\xf1\xbf<\xd3\xc48f~\xf4\xbfKf\x04\x1d\xbb\x1b\xd2\xbf4 g\xc1\xb7\xd4\xfd\xbfn\xa3\xb0\xe0/\xf7\xca\xbf\x0f\xc8\xf3\xecU\xd9\xf4\xbf\x1d\xcd\xec/\xf0\xd3\xdd?\xc4B\x17\x03\x12\x1d\xca\xbf\x08,\x12\xf4}\xea\x01\xc0\xc6\xe0\x9c\xcc@\xff\xf4?\xc6p\xe5_\x0c\x17\xe4\xbf\x91\x15\xff\xef\xc5\xf5\xd3?\xe2\x94kP\xc4\xde\xd0\xbf\xde11`\xa3\xbb\xe2?\xbc\xb2Kec\x12\xd2\xbf\x81\x88\xc1\xce\xf1\x02\xeb\xbf\xb9\xa9\x1f\xac9>\xdf\xbf\xe6v@\xab\xf8\x85\xf5?\xbd\xb9\x11\xda\xd5;\xe4\xbf\x010\xf0\x08\xee\xb4\xee\xbfq\xd4VC\x977\xb2\xbf\x8c"\x12\x8evW\xe8?\xaf\x15\x16\xc6\x87\x00\xb5\xbf\xd4E\xa5E\xd3;\xde\xbf\\$\xf3\x01\x1f\xaa\xf4?2m\xb4\xee\xd7\x01\xe5?\xfe:\xe8\xa4]s\xfc?\xe3K\x10\x883e\xd3\xbfD\xe3z\xc1T\xc4\xeb?"\xd4\xed\x8e\x95\xe0\xc3?\x14:\x80\xe7~y\xf4\xbf\xd2\xb1S\x88\xf0\x80\xdb\xbf8\x1c\xc7f\xed\x90\xe7?1\x96r\xd0\xf8*\xeb?\xa3\x8b\xb5\xaf\xb25\xeb\xbf\x9d\x1es5\x15\x0f\xd4\xbf\x0bJ\xc3r\x7fc\xf4\xbf\xdb\xf5\xf7\xe3M\xea\xf5\xbf\xa9k\x08\x120{\xf2?&\xa3\xb3\x17\xbf\xbc\xc8\xbfH&GS\xd7\xf2\x9e\xbf\xe5\xe1\x98\x85\xb2\xf3\xe7?\rX\xd7\xa6\x14\xe9\xe4?\xf5\xa4\x1d\xb10(\xe4?\xf8x\xd5Z{|\xf4?\x8e.(\xf2\xd3@\xe1\xbf\n\x08\x8e\xfb~\xcd\xf2\xbf\x91>\xbf\x92\xaa\xfa\xe8?\x85E\xb8\xe2~\x04\xf2? i|\x0b\xaf\xa0\xd9\xbf\x8c\x8a\xcc\xf7\xf14\xc3\xbfy\xd2\xabR\x8c\xe3\xfa?/\xee\xac\x99\xab\xc1\xe5?\xcf\xcd\x860Y\xc8\xe3\xbf\x9bd\x01k4\xe7\xca\xbf\x8c\x04\x06\x19\xa6P\xd3?\xfc\xb7\xfd\x1e\xe2\xad\xcc\xbfr)\x15\xcf\xa0\xcc\xd9?\xf0"\xf5\xb6|\xa8\xe1?\xf47\xb4\x9eza\xe9\xbfc\xf7\xde\xb2c/\xd6?\xd25!x\xf2\x83\xf7?\x1c\xd8l\xd0j\x07\xf2\xbf\xd37c\x9a\x06\xda\xce\xbf4]#b\xc5\xa4\xf6?<\xd4\xac8\xd1s\xfe\xbfO\xc0\xd7\x06\x95\x9a\xda\xbf\x07\xe0\x9a\r\xe7?\xf8?/\x02O\x87\xd7W\xe9\xbf\xeci\xda]\xcd\xe6\xd1\xbf\xa6Q\x18\x18\x871\x04@\xb5sy,4\xac\xd7?\x07\'d9\x8d\xa5\x02@o1\xeax\rD\x04@\xde\xe7\xbaj]\x19\xf7?\x98\x87\xae\x94w.\x05@\xeb\x8e\xab\xec\xf4V\xec\xbf\x85\xf9\xf1C`<\xf7?\x9e>2\xb8\xfb!\xf5?iw\xe1p\xc4\xc0\xf9?@\xf0jk\xb2\xbb\xe7?\x0cE\xc1\xca&\xf9\xe8\xbf\x1e\x93\xcc\x83H\xa6\xb7\xbf;\x8aS&a\xba\xf6\xbf\xe0\x10\x7f\xb3\x06\xfb\xeb\xbf\xfa9\x02\xef\xed\xba\xea\xbfRLyM\x19\x08\xe0?2\xd5\x8d\x80\xaaE\xdd?\xff\n\x92\xff]5\xef?mH"e\x96\xa7\xc8?\xe1\x1b\xcfY\xc6\xb9\x04\xc0\xfd\xa3&&0\x1a\xcc\xbf\xab\xf3H\xce[\x03\x02@\xd2-Z\xb1\x08\xa2\xf4?\x078\xc3;\xb5\xba\xf8\xbf\xe5u=\xf4W\x85\xf4\xbfO.\x12\x80U&\xeb?C\x022\xb4\xa0\xa2\xe2?\xc3\xa6\xb2/\\\x14\xe8\xbf\td\xac\xc7\xd9=\xcf?%T,\xe8\x8d\xda\xfc?-\xab\xbe21\xbf\xdb\xbfa\x86\xe2N\x0b\xc1\x02\xc0kX&\xee\x10\x1c\xf0?\xca\xf3z/2\xe8\xeb\xbf\xf9\x8f\xe4X\xea@\xe9?\xc5\xe5\x07\xa2aH\xe3\xbfl\x12\t\x897\x92\xe0?\xeaM\xe8\x9a\xdb\x92\x02@L!\x9cD\xa2h\xe6?,0g\x83\xcf]\xf9\xbf\xb7\x82^\xc1\xce\xca\xce?{\x8c/(\\\xc2\xe0\xbf\xef\'\x14\\r\x8d\xf5?\xcfav\xa9.\x15\xe4?\x85\xbax\xb5\x1a\t\xfb?\xb6\x18\xd6\xa5\x9a\xb9\xbf\xe4\xe9\'I\xa9\r\x89\xbfF&\x9f\x8d\xdb2\xea\xbf\x8a\x85\xf8\xbb\xbb)\xd1?F:\xc2\xd9\x86_\xf0?\xc0y\xd6C\x89\xea\xf8?\xdf\xbeU\x91hV\xce\xbf\x1cb\x93\xd2\x92\xc4\xe8?\x9c\xac\xdc\xef\xb3$\x97?\xaa38\x83\xc0\x12\xf4\xbf\x9e3@\x8at\x13\xe3?\xcd\xdf3f\xb5\xe9\xf8\xbf\xf4\x0e\x9a\xfa\xadO\xd8\xbf\xf7\xc5\xcb\xf2\xa5\xf6\xed?\x91$\xf3\\\xf4\xcd\xf1\xbf\n\xda\x00<\x8f\r\xfa?4\xd0H\x1a\x8d\x12\xf3?\xf2Sz\xe7\xfe\x85\xf4\xbf\xe4\xb3\nq\xb0"\xd6\xbf\xe7m\xdb7\x1e\xe6\x00@c\x8e\x08\xc2:8\xca?\xa1k\x03]\xebY\xef\xbf5\xbeMD\x1c\x9f\xfc\xbf\xf0+\xb4\xda^\xf8\xe0\xbf\x85q\x8d\xe0\x0e\x81\xf4?\xb6:\x19dNm\xf6?&\x13\xf8\xfb\xaf\xa8\xf5?\x92\x81W1\xf2\xb5\xd5\xbf\x86B\xfb\x99=n\xf3\xbf\x8bK\t\xbd5\xf2\xb8?J*\xa7\xd1\xc6\x1f\xdb?\xc8^e\x9f\xbd7\xf0?G\xb91\xb4\xb3\xe7\xdc\xbfc\xcc\xee>\x14I\xf1\xbf\x99\x13\xb77\x83T\xe0\xbf\xa6M\xab\x96b\xf3\x04@v\xb4\x93\x92p~\xf1\xbfXY\x96\xde$H\xf3?\x01\xdfl\x9a\x0f$\xf6\xbfm\x00\x04\xe9O4\xff\xbf\x1c\xc3\x9c\xfb\x84\xc5\xf9?\xd0|\x9c\xf2^A\xe8?E^\xe3\xe8i\xfd\x93?g\x9ez\x13\xe1h\xbb?2\xa5x}\x8e\xa4\xdb?\xd7$\xff\x06\xbal\xec\xbfU\xff_\x95\x12\xf4\xe6?\x14\x03\xa2\xc5\x19\x95\xe8?\x0f\x92\xb5\xf8\xe6\xdb\xa9?\x14`\xf4\x02k\x13\xe1\xbf\xc5\xa0\\,\x1a\xe3\xb2?\x17z\xec\xdb\xc4\xfe\xe4\xbf\xe8Z\xed\x8bl3\xe0\xbfB\x7f\xd3l\xad9\xfa?\xa4\xe5\x00,\x1f\x10\xf1?2\xae\r\xf5}\x9d\xe5?\xcaJ\x96\x01\xbfz\xbf?\xbe\xc4\xc57\xd5\xc6\xe8\xbf|z\x81\xa0\xd4\x92\xe8\xbf\xd2\xdd\x91\xeb\xc2\xe3\xd5?\xaa0\x04\x1a\x0b\xe6\xfb?A`\xec<\xc7O\xf5?\x11\xa0\xb2x\x89\x80\xdb?}(\x96y\xfc\xd3\xe1?0\xad\'@\xed\xe4\xe1?\x05\xb5\x11\xa2K\xb5\xb4?A\x1f\x1b\x84\xb1w\xf2\xbf\xe0\xfdUE\xc0\xdd\xdb?-\x15\x86u\xd5\xe1\xc8\xbf\xd1\xffe\xa3\x97\x7f\xe0?x\xe1 \xec\xd8\xec\xd4?\xe9\xb4\x01M\x83\xc6\xda\xbf\xb9hB\xd6\xcb\x94\xc3?\x84\xe7\xcc|\xc9\xaa\xda\xbf\xf3\xf7\xa1;\x8d2\xf7\xbf\xd4z\xd8\xdc\xf6U\xd5?\x00\xa6z?\xeb\x0b\xf0\xbf\xd0&\xbd\x8c\xad\x13\xe3?\x9d\x18\xe9B%-\xf6?\xc6\xa5S\x13\xf9\xa3\xf1\xbf\xfc\x16\x99K\xeal\xe1?\xd6\x95 \xa8\xa8\x8c\xc6\xbf\xb9\xcf\xff0SA\xe5\xbfBKh\x86:\xbf\xcc\xbf&V\xe9Y\xb7q\xf3?\x10SNf\xfd\x14\xf1?\xdd\xe6k\xc7\xc0\x1c\x01@cji3\x86s\xe3?\x19":B\xc3\x1d\xab?\xfeM\xf4\x00f\t\xe5?\xb4H\x81j=\xcc\xfe?\xd2\xcb\xa7\xae\x04\x1e\xf8\xbf\x92\xca\x000\x92D\xd4\xbf\xdbQD\x80[\xb9\xd1\xbfT\xae\xcci\x1e,\x94?z1\x812\xb5I\xf0\xbf\xa0O\xb6\'V\x9b\xe1\xbf%A\xd1\x9f\x9f\xcc\xe3?\xa8R\t\xb6g\xec\'\xbfiJ \x87}\x90\xe7\xbf\xc4\x95\x1c\x15pc\xb4\xbfr\xcb\xefo\x85\x92\xfb?4\xaa\xcf]78\xf8\xbf\xb3l\xf1\r\xb1@\xf0?B\xfd\x04\xc3K\xc4\xf0\xbfIq\xff\x905=\xec?\x11\xd26\xd3\xbb\xf6\xd2?\x9f\xb3\xaf]\xddJ\xf9?\xa1\'4\xc99F\xe6\xbf\x10"\xb2.\xa3\xc5\xe5?y\x98\x87\xa2jC\xdb\xbf\xc05s\x1f\t\xea\xf4?\xec\xa0\xd3#\xd8\xcc\xe0?\xec$c\xe440\xe2\xbf\xeeO\xe6xf\xa6\xfa?E\n\x1d\x83\xbb\xeb\xb8?O\x92j\xa6i4\xfd?\x14\x8b\x01\xca_N\xb5?\xec\x11xx\xe3<\xe0\xbf\x9e\x12J\xca\x1e\xbc\xe4\xbf\xa1\x9cwO\xb7\xe1\xec\xbf\xb7%\xd0\xb5\x9c#\xe7?\xeb\x87\xcfro(\xa0?\x1f9\xb5\xc8\x11k\xe6?1\xd6\xf7\x00\x18\x9a\xe1?\xb75\xf2\x93=\xbb\xe9?\x9fz\xe9\xa1\x17\xc3\xa3?\x1f~\xa1RK\x1d\xe3?\xc4\x07\x92d\xbe\x07\xb0\xbf\x18k\x82\xe5\xbd*\xf6\xbfy*N\xd6`i\x01\xc0\xf0_\x97\xc3\x97\xab\xc0\xbf5\xd13V\x98K\xe9?s\xa0\x031\xdf\xa0\xf2?!%\xe4\xefG;\xdd\xbf\xc2\xefz\x9fJ\x1d\xc8?_m\x92s\xb5\x0f\xe4?\xc1(\xd3\xe1\t\x01\xf0?\xf4\x898\x88nj\xe4?0Yx}\xd7\x87\xcf\xbf\xa2\x9f\xdfM\xca[\xe7?\xe2\t\'\x02\x07\xf9\xe1?\x83\xc9\xe1\xc4\x84\xf9\xee\xbfx# \xddMH\xd7?B\x02\xcf\x1b3q\xf3\xbf\xb01\x9a\x01$\xa2\xe1?\xe3\xb2\x8d3\xe2\xa1\xd4\xbfx\xae\xcf8^#\xf8\xbf\xd6\x8b\x1e\x18wt\xd9?\xab\x16I\\\xfd\x1d\xe2?\xe2\xd5\x1a\xdf;{\xeb\xbfzC\x0e5V\x84\xf6\xbf\xd2\x91D\xabz=\xb3?\x03\xbd\x7f\xd8\x11Z\xb8?\xdc\xceKt?\xe6\xf8?\x0b\xbe-h-|\xd6?\x05\x8c\xa6\x92\xed\xa0\xb0?\xc1l\x05\x07\x04)\xf8\xbf\x00\xcc\xd33~\x82\xf6?mb\xd6 \x95I\xe5\xbf\x89\xda\xbb\xf4\x9a\xff\xe1\xbf\xe4\x81\xdal\xa8\x15\x99?\xde\xe6\xada\x12\'\xeb\xbfn\xe2\xa7\xec\xe6\x0f\xe5?\xaa\xcf\x90\x06*\x02\xec?\x96\xe1\xc8\x0b\'\xed\xd1\xbf\xfd|\xd8$\x9a\x18\xc5\xbfV\xe8\x1e~!\x04\xeb\xbf\t\x11\xe0\xf1+\x02\xe4?^\xfe\xbc\xb38\x7f\xf4\xbfZ4\xf7<8\x19\xe7\xbf\xa0\xc6\x89\x87\xfa\xea\xfd\xbf"B\xf55/\xc7\xe6\xbf\xcc\xe3LB\x07\x86\xf4?R;\x88\x07\xae^\xe6\xbf\xbfH\xb6\x11^\xb2\xe8?\xe2\xafDL\x8f8\xf6\xbf\xa6\xbd\xeb\x9c\x19\x18\xbc\xbf)\x97)y\x9d\xed\xcd\xbf\xacz\xb8\xc9\x9c\xc8\xf7?to8/\xb1\x08\xed\xbf02\x96\x9e\x93\xee\xcc\xbf\x04\x06\xa5\x9b\xf6\xcb\xe4?\xca\x9a\x8cRC%\xc4\xbf\x90\xf2\x02\x97\xab\x8e\xe5\xbf0\\k\x8b.r\x03@;H\x89\x15\x04\xc6\xce?\x8a\xb7\xe8\\\xce\x93\xec\xbf\xc3_\xdenj\x9d\xd0\xbfa\xf2\xafTP\x05\xc5?\xe1\x1f\x8c\xae\xbf\x92\xf2?\xecL\xb8e\x84]\xf6?\x90\x19\xf6Ag/\xf4?\xd32\xb5\xb5\\\xcb\xdd?ET\xd0\x9c\xe0\xd1\xd6?\x1d\xa8\xda\xcbj\xfc\xf0?\xaf\x9d\x9b\x852\xe3\xee\xbf\xa9\xae\xdf\x99\x02\x0b\xd1\xbf\x91\xf5\xd0[_\x19\xf1\xbf\r\x87Kk\xb3A\xdb?\xde\xe1\x85\xb9O[\xf0?Ie\x9f\'\xeb\xdb\xea\xbf\xaa\xab\xc9\xadLe\xfb\xbf\xd1>\xa8\x06Ak\xec\xbf\x11\xa9B\xeb%\x81\xd7\xbf\xa2\xc5\xe7\x133a\xe8\xbf\x93\xff\xf2\x19\xb2n\xb4?V\xac\xdf\xc1\xf4\xea\xe9\xbft\x83\x88\x83\xac\x19\xf7\xbfa\xfa[}\xbe\x91\xfc\xbf\t\xcb\x02\x03E;\xd8?q\x10\xd4&\x16V\xab\xbf\x1b\xd4\xf3\xd5\xa8Q\xff\xbf\x97\x05\xf3\x80\xf6b\xe1\xbf*]\x8f\\$\xb0\xf3?\x82VJ\x7fC2\xe6\xbf\x97D[[\xd2u\x03\xc0I\xe7j\x8c\xc3\xe5\xa9?h)Q?\'Q\xe6\xbf\x86A\xb1\x12\x04\x82\xa0\xbf\xd1E$\x96\x8eF\xf2?\x8f\xc7\xa4Z\xfc\xed\xea?\x8b\x04S\xcd\xc4I\xd7?\xda\xb5\x0b\x97 \x00\xc2\xbfd\x8biJ\xb4C\xc4?\xafl\xb4r\xfeM\xfb?G\xe1\x1fo[[\xd7\xbf^is\xe4\x9c\xc3\xdd\xbf\xc2\\+\xeb\x12W\xf9\xbf\xa0B71\xd2\xd5\xe4\xbf \xbb\x1a\xaa\x80\x93\xea\xbf\xfa\xe0\xcb\x00\xad\x8b\xf0?\x12\x91\xec\xc3\x8e\xad\xe0\xbf\xceo\xd4\xf0\x92\xd2\xfa\xbf\xe7\xb6w\x90\xde\xe3\xf0?\xa6\x84p\xa6\xf62\xdc?\xb7\x8a}l\xf0[\xb1\xbf@\x15\xda\xf0V\xdc\xd4\xbf\xff\xd8\xba\xc1\xea\x11\xf8\xbf\x1eR\x90[\xe8\xa3\xd4\xbf3\x10o\xfdv\x83\xab\xbf\x02\xa5\xae\x0c\xf6\xbd\xbd?\x03f\xfex\xd4\xf9\xed\xbf\x0b\xa4\xed\xc2O\xf8\xcf?\x10\xd4\x99\x8e\xdd\xcb\xec?\x9eT*\x10\\)\xef?m\n\xa3\x91V\x9b\xa9?d\xdf/{*;\x06@0\xc5\x8f\xc4\xf6T\xee?\t\x93\x01\xfam\x88\xe8?]\xf7\x0ch\xbf\xb7\xe0?\xe1\x0c\x90Ts\xb2\xf4?_PD3$\xf6\x01\xc0\x01\xca\xf9\xa2\x94\x82\xf6\xbf\xf6\xca\x98`\x1a\xf7\xf2\xbfQ\xcd\xa3|*\x81\xfb\xbfX,G\xc0e\xbe\x01\xc0?\xfan\x03\xca\xe1\xcb?\xea\x82\x97\x1eL3\xf1\xbf\x9f]\x98C]\xb0\xe1\xbfm\xf7O\x97\x93\xee\xef?\ty\x93\xbbP\xd2\xfe?\xbd.;\xec2P\xfd\xbfr\x84|\xfc4\x13\xd8?\x07\x8d\x8d\x10\x7f\xfa\xe3?\'\xf8\xf8\x97\xa5\x8c\xd8\xbf\x02N:\xdb\xd2?\x08@\xc8\xd3a\x9b\xa6\xfd\xf6?\x04n\xf4g\x8d\xa6\xfe?_a\x9d{\xd7\x9d\xd7\xbfK\xa5\x83=G\xf0\xe0\xbf\xe6\xe2Er\x98\xcd\xeb?>TS\'#\x9a\xbd?\x84\xb9@J\x8cn\xa0?\xdf\xfc\xad\xd1\x90\x8e\xe0?!iW\xaaA\x11\xc5\xbf\xf9\xb3ET\x02t\x00@\xb8\x1b\xdbI\xc2}\xff?_\xcaK\xe4P\x18\x02@\x0f\xe6\xf5\x05:G\x02@\xce\x1c\x91\xf2\xff\xe7?Kma~\xc8v\xf7?\x82\x07\xb6\xe0\x81P\xe8\xbf\x16\xc6\\\x1cC\r\xf5\xbf\x03\xea\xb3\x11$\xb6\xf0?\x01hM\x8e\x00\'\xfd\xbfo+\x05$m\xb9\xcd?\x9a\xdb<*\x9e)\xea\xbf.\xa5\xf2\xb9qw\xf1\xbf\x0b\x1a)P\t\x1a\xe0\xbf*\x98\x024\xcc\x83\xde\xbfP\x00M\x8fV\x9d\xc9?8#\xe1}(7\xd6?,\x81ql\xfb\t\xf1\xbfd\\_nj\x0b\xd3?\x8b7N\x81\x07\xe0\xc3\xbf\x03\x9d\xa4\x99l\xec\xee\xbfa\x9d\xdfS\xee\xf4\xf2?\x8f?\t\xa0\x80%\xf9?\xf3\x14\x99}\xe7\x8d\xf4\xbf\xde\x07\xde<\xdf*\xfb\xbf\x12\xfb\xdd\xb4\xa7\x05\xe7\xbfUPb\xd6\xc2\xea\xcd\xbf\x99\xa8HphM\xec?r48\x05\xe5\x1c\xf4?\xecb\xa4\xc5\x10\xf9\xe0?\xb6RW\xe0\xb8\xd5\xf0?`\xe8\x16\xae)A\x9e\xbfqj\xdco:\xbd\xea?\xf9\x0e.H\xce#\xf5?\x14\xeby\x16U\xea\xf9?\xa2\xd4\x97.UY\xd2\xbf\xf1\xe3\xb9\xaf\xf7B\xbe?\xcdQ\x8b\x08\x00Q\xe4\xbf\xed\xc2&\xa4:\xa6\xed?\xb3\xcbA\x141\x14\xef\xbf\xd5\x8b\xfc\xa8\x83\x03\xe4\xbf\xdee\xf1\xba\x90\r\x03@\x04og\xbam\t\xf2?\xa9\xf6?a\xbc\x1b\xf6?s\xd3\x9b\x0e\xcf*\xf2?\xf8^x\xa9n\x02\xf5?$\xb6b\xc80M\xfb?~ocM\xc9\x08\xf3?\xdfa\x1f\x0fqf\xe8?\xe3\xb1\x81\xf9\xf3:\x08@o\xe7\xc9*\xc86\xe5?\x8b>K\xf6O\x87\xf5\xbf\x82\x89v\x03E\xe7\xff\xbf\xdf\x92\x92K\x8e9\x01\xc0&\x03\xdf9hI\xe7\xbf\xd6\xa0O\x96U\xc1\xd2?\xd8\x90\xa0\xbc\x13=\xf5?\xf6\x0eG\xdf*\x02\xf6\xbf\xf5\xf7\xef\xc0\xfat\xe3?\xb4O\x08L_L\xd9\xbf>)\xb9\x8b\xcf\xa5\xb9?\x05\xc3/\x08?3\xf6\xbfp\x99\x90\x8c\x9a\x95\x05@\x8b\xc7\x95j\xc0A\xfa?h\xf2hpT\xb8\xb7\xbf\x9aI\x1d\xdbF\x18\xe7\xbf\xaaWo\xc5#M\xdd?\xca\xf5\x91\x1b\x00\x08\xd3\xbfL+^z\xf6V\xec?_f\x17\xd7x\x1d\xe7?\xe7\xde\xd1\x8b9{\xee\xbf\xb4\x01\xbdP\x0e\x16\xef\xbf~\x18LV\'\xf1\xe7\xbfx\xf1\xe0J\xa8m\xe7?<\xfcw\xffk\xf9\xc9?KW\x9d#\x88l\x01@6\xbd\n5\xd37\xfb?9\xbf\xc4YB\xa8\xfa?S\x7f\xee\x85\xfb-\xe4?\x9cY\x95\xbb\xfd\xc1\xee\xbf2\x7f\xb9\xbaj\xd9\x06\xc0\nKl\x8e=\xb0\xf8\xbf_w#\xc9\xb0\r\xf4\xbf\xd0\xdc\xc4\x0c\xd6\x1a\xe2\xbf\xb1\x13rp\xe1\xcd\xe9\xbf\xc1dS\xd5\xf0\xdc\xbe\xbf\xeb\x10\xf7\x0f#\xba\xe0?\x9av\x03\x1e!\x82\x8c\xbf\xd1\xb2\x1cJ\xff\x07\xb4\xbf|\xb4\x8f\xdf-\x9c\x83\xe9\xbf\x00\x1b\xa7t\x17\xd2\xff\xbf\x80&E\x9e\xc8]\x06@\xe0\x1dhu?w\xb2?vET\xd4\xc7\xc8\xf9\xbf\xf3Sb\x89\x0c0\xe7\xbf\xff\xabEC\x87\x89\xa7?\xdbK\xc1\xcdE\xda\xf9?\xefw?\x1d\xcc\xe2\xf2?\xf9\xfc\x1f\x80m\xdb\xd4\xbf\xdb"hH<\x1f\xe1\xbf\xc0\xd0\xfd\xe5\xfca\xf6?\xc4\xfb\xa5S\xfe\xa2\x02\xc0r\xa6J\xa7qd\xe5\xbf}^l\x00\x08X\xeb?\x84B\xd7;\x1f\x95\xb7?\t\xa5\r\x80\x83\x0c\xc8\xbf\xbc\x99\xdc\x9b\xad\xff\xbb?\x1f,\x06\xefbf\xcd?\xce\xf2\x90\x7f\xdd\xa0\xe1?\x08 \x9b.\xe6\x08\xfa?8d\x84\xc3\x15\xbb\xf1?\xf3\x9f\x8dE\x84\xe7\xa8?`A[\xb2j\xa8\xe1\xbf2\x81b\xd7\xbe\xc6\xff\xbfZ\x8c\xf2>g\n\xdb?\xbe\xda\xbfs\x94\xec\x01\xc0>\xf2\xa9\xbf\xe9A\xd3\xbf:\xc5j3\xd7e\xf9\xbf\xad\xbck\xd9\xf5&\xf4\xbf4\x00L\\\x8c\xd6\xe6?\xc9\xd3|\x1c\xa7\x19\xe1\xbf\x8a\xd1pUY\\\xc3?l~\x98\x16UD\xdc? mN\xfd\x99\xa4\xf2?\xd8\xd5\xd1.dN\xfc?%\xa7ij\x8aR\xdf?\xc3\x91\x16\xde\xdc\\f\xbf\xbb\\\x02\tx\xfc\xf5\xbf:T\xd8t\xb1\xb6\xe6\xbf{H~\xefF\x0f\xe1\xbf\x14Q\x99\xa9k}\xea\xbf\x82=u<\xc1\'\xa7?\x18\x83EF\xcej\xea\xbf\x8f_7N0\x12\xd8\xbf\x903Z*\xbd\xec\xd1?K\xc4\xf7\x92\xe3\xab\xbd\xbf\xbbU\x00\x1dK\x11\x04@\xd4AQ\x8e\x0f]\xf7?O\x06\xa2\xbb\xad\xb6\xed?T-\xec\x8a\xb4\x17\x00@\x07\x99\xaaj\xddI\xe0?|\xb4j:9\x8c\x00\xc0}\x81\xca\t\xd3m\xea\xbf\x04\tp\x1c\xd1\xc2\xfd\xbf\xa6\xcb\xd1\xea\xd2\xb9\xd6\xbf*h\xba\x11\xd3q\xdb?\xb6\xaf@\x08\n\x89\xfa\xbfK\x0e\x15\x04\xb4\x85\xea\xbf\xa0#\xbe\x16\xc5\xf8\xf2\xbfO\xa2g\x8bq\x1e\xd3\xbfW\x9cK\xfe\x87P\xc0?\x9dR\xf3\xc4\x19k\xfa?\xb4X\x84\xe7\x1d\xb0\xb6?\xba\x0cv1=\xd8\xee?\xbbA\xd5b\x94\xcf\xf7\xbfn)\x08R\xf7\xf7\xc8\xbf%\xf3\xd0\x0e\x82^\xe6\xbf\xcb\x8dz2?d\xf1\xbf\x91\xa7\x16\xe2\xb0\xe9\xb0?`\x88\xd5\xae\xf2_\xfa\xbf\x8chl\x0b\xf4T\xda\xbfB\xd3W;\x18\xbb\xf5\xbf\xa3\\%\x10\xb1?\xe2\xbfD\x8b9\xd2I\xcf\xc4\xbfM \xf6S\xb0T\xd8\xbf}\xd6_\xc1\\\x1d\xe6\xbf\x8b\xfdK\xc2\x12/\xed?\xf4\xa8h\x8a\xe9\xbd\xe7?{\xa0\xe4\xf2\x01\xfd\xb1\xbfn\xa6\x84\r]\xae\xca\xbf\xd4A\x96\xe5\x0e\xc2\xe0?\x7f\xc3"\xce]\xc7\x00\xc0\xb8U\xf2\xe5W!\xec\xbf\xcb9J\x18\xc5\x1b\xfc\xbf=,\x9ex*\xaa\xdc?\x87\xba\x8c\xfa\xab\xec\xbc?+\x92\xacEG\x9f\xaa\xbf\xb0\xf5e/\xd7\x16\xee? \xa5e,\xa0\xfe\xdc\xbf\x82*\xf9\xa38\x9e\xc8?\xe5\xe3)\xa3j\xc8\xf4?\xb3\x8fu\xf1s\xc9\xd7?\xbaM\xa1\xb1+\xd2\xe7\xbf\x08\xf9\x06\xdbw<\xe7\xbf\xf2j\xa7 \xdcM\xeb?\xd6r\x9f\xc9\xf27\x91\xbf\xe1\x85\xaa\xdb\x9fD\xd3\xbf\xd9\xf7\x05\xcf\x02\xb6\xf8?\n}\x04B=t\xf6?S\xff\xea\xd8\xbb\x99\xd3?ao\xf7\xd4\xf4G\xef\xbf\xfd\xd3e\xe7my\xc2?\xf0~K3\xdd\xc7\xec?u#X\xb1\x93\x02\xfa\xbf\x8d\xb7?|\xd4<\xe6?,\xb4y\x08\xfd\x8c\xf4?\x9dTY\xaf\xfd\x01\xff?j\'\x9d#\x00m\xf8?V\x9b\x0e\x13V\xa7\xd9?\xf7=\x1aS\xd5\x1f\xf0\xbf>\x9a\x15\xd4\x1es\xc4?\xd0\xf3:\xd4\x903\xf1\xbf4\xbd\x06\x04\x0c{\xf9?\x9b\xa2&:\xbb\x1a\xf7\xbf\x89\x07AT\xf5\x85\xe1?\xef\xf4@\xadl#\xf3?\xb5\xe3\x11\x1c\xe2#\x06@6\xc9\xb2\x1eD\x13\xda?\xf7\x8f\xbd\xbf\xb0\x84\xf8\xbfu\x10=\x89\x97\xf6\xf0\xbf\x94\xdb\xb9\xd7h\xdd\xce?\x9fP!\xd7@\xbe\xae\xbfE\xdc\xcbu\x05d\xd0?\x9a\xfd\xd5\xa5\xbf\xa9\xef\xbfh\x7fSC\x9e\xa5\xef\xbf0Nk\xdb&B\xf3\xbf\x10\x83\xbc\x8b\xf7\x8c\xf3?C\xa2\x15ly\xc4\xea\xbf\xf4\x82G\xb8\x96l\xd3?\xb4\x0c\x8d\x9f\xb0\xa9\xfa\xbflf\x8f\xd3\x96S\xf9\xbf\xa5\x07\xed\x83Up\xd4?I\xb8\n\xa8\r\x88\xf2\xbfg\xf2\xf7\x97\xbe\xdb\xe6?}\x13\xe5G\x03\xbf\xf9\xbfI\xdf2Va\x99\xf7?~\xd7]\xc4\x00 \xdd\xbf\xb8\xb5l\xce\x87W\x06\xc0\xad\x8b\x9c\xc9*\x90\xd5\xbfGRbZYi\xed\xbfO@\xb8\xf4\r\xa4\xb5\xbf\xcc\xe2\xeb\xec\xb6\x8e\xd9\xbf\x98\x16\x82\x80C\x13\xeb\xbf\xd0/\xd5\xb4O\xe4\xe1\xbf\x10\x9e\x94C%.\xee?\xae@|\xcd\xc1\xa5\xf3\xbf\xa6\x0f\xa3@\xb4H\xcf\xbf\xca\x8cm4\xf1E\xcf?\x9fv\x0f\x03EF\xd8\xbfZ\xab\xf4`\x00\xc9\xe7\xbf\xd75\xb3\xd0\xd2\xb2\xef?\xbb\x19\x93\x8f\x84\x06\xe8\xbfF\xe0\xaa\xd7~<\xe2\xbf\xb2\x82z\xc4\xe4 \xce?\xd7\x8b\x8cOp\x10\xf1?Z\x9b\x90T\xa4\x05\xcb?;j\xf5\x97\xa5\x06\xda\xbf\x15\xe4\x8a\x81g\x08\xe7?^\xaf\x95:T\xda\xd4?g_"C\xdf\xf6\xc2?\x0fB;W\xdb\x8a\xf4\xbf\x18A \xbcC\n\xd8?\x84\x9d\xee\xf0\'\x06\xf2\xbfn\xe9)\xbb\x13\x1d\xdd\xbf\xbbj\xf9\x919\xec\xe5\xbfh\xba\x15\x90\xabz\x02\xc0R\x9f\xcb2\r\x0c\xf0?\x00\x93\xff\xfb5\xb1\xff?\xb2\x00 \xef\x04\xa5\xf9?\x86\xfb\xd3 7\x0b\xe5\xbf\xb5RJ^\x0b\xa9\xfc\xbf\xb0I-\x13\x93g\xc1\xbf\xf3\xad/\tb\xb3\xb1\xbf\x7f\xca\x9b\x80B\xf8\xee\xbfpf\xf0j2\x84\xe3?Z\xf5o>9u\xc9\xbft\xe0\xba\x18\xe9M\xee\xbf{0\x17\x0cj\xde\xfb\xbf\xfcQ\xea&!\x14\xdb?\x85iby\xdfQ\xca?\x84cK\xdd`O\xed\xbfn\\o\x1c\xf4\x1d\xf7?\x9d$k\x12 \xf9\xe0\xbf\x14r\xb9\x15\xd0\x87\xcc?g8\xf7wP\xa8\xe4\xbft\xe0\x1cB*\r\x01\xc0\xd2S\xbb\x9d\xdc1\xe4\xbfo\xd0\xd4\xd9g\xe8\xd0?i\x8d\xa89(\xa6\xff\xbfJ\x97\x0e\xda\xa9\\\xee\xbfNd\xb0=\xed3\xbe\xbf\xa6\xb8\x04g\xfc\xc6\xf2\xbf\x8c\xfcB&x\x14\xe8\xbf\x82Q>\x17\xa0\xe1\xee\xbf\x1c+M\xd6Y\x88\xfd\xbf\xfa\xbf\xee@?\xa3\x0b@\xceW\xbf\x1d\xcbO\xeb?\xc4\xbe\xff\xe5z\xc7\xed\xbf4\x08\x82\xca\x0f4\xf5\xbf\xb2\x8f\x1d|\xb1\xd2\xb8??\xbbS\xf3\xb5\xec\xe3?R\xcd\x82\x94\xbda\xe4\xbf\xf6\\\xe1K\x14;\xdc?\xab8\xb7+|\xef\xe2\xbf\xaf\xe1\xe78\x17\xe6\xf5\xbf\xb1\xd7\xbeH\xdc\xc7\xee\xbf\xc0lN\xe3]c\xd3\xbf\x10\x08\x86\xc3\tL\x8e\xbfh\xc2E\xaa\xda\xcc\xf5\xbf-\xef\xd7n\xebm\xe4?\x8d\xc38]\xffC\xda\xbfn\xc7y\xd1\x19L\xdc?\x90\xbe\xde\x89;\xd8\xb6?"\x80\xf7V!\x1d\xff\xbfG\xd8\xc6\x95\x04]\xa9?\xd8\xd6\xfeS\xa9\xac\xd0?(\x0e\xe2YK&\xf2\xbf+\xfcB\xd2\xa0\xa4\xe5\xbf\xa9)\xa1v\x1c\x07\x00\xc0\x9a\xf4\x84\xa2\xadw\xf1\xbf\x9c\xefw\x922V\xf4\xbf\xc2,\x97\x96\xaf\xe7\xf9\xbfs\xc74\xfe\x94_\xe7?\x0b\xe3[6\x1c:\xec?\xdbza\xf5\xd5.\xf3?\x86o\xfb\x84\xec\xc5\xbd?\x92Z\xec{\xf9\x17\xf0?\xdc\r\x93\x15\xda\x85\xdc\xbf\xdf\x1f7\xf1{\x91\xf4\xbf9/(`\x10-\xf9\xbf\xb7,r\x15\xd8\xe8\xf3?\x03\x8ap\xd9]\x9f\xf0\xbfc\x95\x1d\xde\xc9\x16\xe2\xbf\xfd.\xa0\t&\xef\xdd?g850\x88F\xf8\xbf\x8b/\x02Z\xbbp\xee?\x9bbk\x14\xefX\xdc?=g(\xc4\xd6*\xee?\xd7\xb3\xc0\x7f\xbb\'\xd4?\xd2\xbd\x06\x05\x1es\xf8\xbf\xbd\xb5j\x97\xd1!\xd8\xbf\xe8\xf5nW\xe6\xc2\xf6\xbfI\xc6y\xb4\x1e\x90\xf3\xbf\xc7\xe6\x9fMS\xa0\xf4\xbf\x81\xf3\x7f\xde\xb1?\xdb?\x17\x8a\x9f\x88\xbd\x01\xb5\xbf1L\xc3\xef\x87\xf8\xf6\xbfvE\x8c\x1f\xfb2\xe3\xbf\xce\xc5\xb2\xb7\xfe\xf1\xd3\xbf\xbcon\xd1^\xa4\xfd\xbf\xd6v\x9d\xc7\x89\x84\xf5\xbf\xae\x1c\xb4!\x9aN\xf3?\x02\xecfjRH\x00@\xa8\xda\xc5{9\xdb\xe1?\xca\xfd!\x99:\x12\xf9\xbf.\xbb\xa5\xdc"\x89\xf4?\xbf\x03\x05>h\x1b\xed?\xcd|\x8d\x82\x19\x11\xe2?\x9a\x82\xbe\xd8\x9d\x8a\xe4\xbf\x07\xe5\xb8S\xf0\x1c\xf3?k\xeb\xe0P\xcf\xb7\xcc?\x18\x1bglG\x06\xf5?\x12JN=\x94\xc2\xf1?i*I\xf1g\x1b\xe5\xbf\xbal\x8f\x1af`\xfd?\x1f_Lq%\xb6\xd9\xbf\xcb\xff\x17)\xfa\xe6\x01\xc0j\x90i\xa6\xbdm\xf1?:c\x98\x04s\xe4\xc3?A?\x1bC\x9d\r\xf0\xbfR\xb7\xd5\xea\x96,\xe1\xbfz.r\\\xd5\x01\xe7?\xa7Z\xb5F6t\xe9?\x82h\x157\xd0)\xd2?i\x9e\xb9:\xc8\xf4\xca\xbf*\x89\xa1n\xb2\x15\xdc??\x11a\xcfk\x99\xdc\xbfs\xfc3\x8d\xf5\x1b\xf2?\x06\x9d\xaa\xad|\xc2\xf1?<\x00\xbe.D\xdc\t@5>Xy\xa4\xe2\xcb?5\xb4o\x04e\xc5\xc2\xbfpf\xe8.\xb3\x84\xe1\xbf\xb2NT*\x94n\xe1\xbfe\xd32`\x0fS\xf0?\xb1l{!\xaf\x83n\xbf\xf9\xb6\x08*\x8e\xab\xec?,\xc08?\xe0{\x03\xc0f\x8d\x8e\xc8Y%\xde?\xfe|TXp"\xd9\xbf9\xf9\xed\x00\xc3B\xd4\xbf\x94\xba\xa1\x1a\x84\xb5\xee?u\x9b\xd0X\x03O\xc4?\xdd\xe7e\x15\xb9\xb7\xd4?\x92\x95\xa6"r5\xc3?W\xfa\xe2\xc4\x93O\xbb\xbf\xb0\x84\xee\xf25\xe8\xf2\xbf\xf5\xb9i#\x0es\xe0?!=\xfbK%\xde\xd9\xbfq\xb3\x1e\x81\x81\x0c\xed?;\xd2o_Yr\xeb\xbf\xa5a\x94>#\xcd\x8a?\x0c1\x07?-\x97\xd5\xbf\xa7\x99d@\x8c\x1b\xf1\xbf\x96{&sa/\xf0?\x97\xbaS=\x12k\xfb\xbf\xd2n\xbb\xe9\x9e\x91\xe1?\x13*%\xd7\xd0\x88\xd8?\x90\xf8\xd8BOY\x02@\x83\xe8F\xd5WC\x04@\xca0\xb01FP\xd3\xbf\x82QM\xff\x88\x17\x01@e\x19?U\x85\xe4\xc4\xbf\xd2\xe9\xc1\xeb*\xa8\xfe?\n\xc9\x91\xff\x14\xd9\xe0\xbf\xab\x13Z\xd9-?\xe5\xbf\xc9\xa3o[Je\xe6\xbf\xfcTB\xe0\xdf\xe6\xf1?\xd7\xb6\xfd\xba\xe1\xe2\xf3?\xc9\x191\xc4\x91\xaa\xc6\xbfB*\xba\r\xa6\xf7\xdb?;\x91\xbctV\x98\xfe\xbf6\xa3\x16\xbbK\xdf\x93\xbf\xdb\x9f\xb0\xb5*x\xc9\xbfL\'\xecW\x17*\xfc?\xd5\x81\xfe\xb2\x9e\x8f\xce\xbf\xa2\xf0\x8d\x14\xa5\x94\xd7\xbf\xd5\xf2\x8f\xedJ\'\xec?\xb4\xf9\xb9v\xb2\x00\xf8\xbfC9\x167} \xf6\xbf\xb8%\xcdG[P\xc7?\x85!\xd04E\x1c\xc1\xbfH\x14q\xfa\xdd\xec\x01@\x91\x88\xdc\xb31\xba\xf2\xbf\xa7\xa6\xf8%\xb4\xa9\xdb?Ri\xbd\xc3\xe4q\xc0\xbf`\xcb\xe6\xdd8\x7f\xb6?\x17?\x96_\x8ap\x03@\x06\x13\xc7A^\x04\xd9\xbf\x1a\xf4\xf9\xb6\x8a\xc5\xef\xbf{5^\xa6\xfa\xb6\xee\xbf\xf2u,X\x94X\xdc?\x08\x88\x99\xa5\x11\x98\xf0?\xd7(y\x02\x80\xd6\xfa?\xbc\x92%JV5\xf5\xbf\xa9\xdb\xf7dC3\xfc?\xc6\xe7\x17\x1d\x15\xab\xd3?\xc0\xda\xb4\\>\xc9\xf8?\xfb\xebf ^\xc9\xe0?@@\x03of\xbb\xcd?\x04l\\\xab\xdb\x08\xea?\xafS-N\xbaP\xe8\xbf\x1aNY\xdd\xe9\x05\xd1?\xf5sL\xcbi\x12\xec?\xd4w@"\x89\xd6\xf4?m\xbad\x1d/\xa5\xa8\xbf\xe7(\x146r\xd4\xf5?\xdd\x8c?\x9b\x8f\xa4\xf2\xbf\x8f\x170\x05\\U\xf2\xbfq03;\x82\xac\xe3\xbf\xec\xe2\xe0\x8aUa\xec\xbf\x02\xb0>fu\x8d\xd8?Pf\x0c\xfe\x11r\xb3?\xb1\xec\xdd\xb0\xdc\x8d\xfa?lg\xf0c\x06\xbd\xd9\xbf\xcc%m\xea\xd1\xa4\xec\xbfCo\xee]\xfc\x8e\xe1?cg\xd2J\xef(\xc5?Q\xfepQ\xd7\x11\x00\xc0J}\xab\xe3\x0e\xdf\xf7?\x8d\xaa\x1dG\x81e\xd5\xbf\x14\xf5\x00\xb6lB\xa1\xbf3p\x08\xcf\xedG\xe8?A\xfa\x7f\xc7\xda\xe2\xef?\xc6z\x19+\x91Q\xf3\xbf\xcd"\xbcZ#\xaa\xdb?\xc6k\x9b\x84\x01\xd6\xc3?\x88\x055\x83\xa7\xf1\xe7?\x0b\xb6d\xd2\xf95\xfa\xbf\x9c\xa9<\x81\x9c\x89\x03@\x94\x19k\x94\xc4\xc9\xef\xbf\x06\xf8\xda\xa4F\xd9\xd6\xbf\xb4\x80\x17\xd7\xac\xc5\xf4\xbf\x94\xb8x\xeb\xc8`\xcc\xbf\x13?5\xd3\xb4\xbb\xe3?x\xc9\x84\xfa\xb4X\xe9\xbf\xda\xdf\x01\x9f\x13\xad\xe8?\xf0\x8f\xa8\xdc\x94f\xe1?\x15\xc5R\x14\xb1O\xad?)\xf0\x02\\\x99\xbc\xd2\xbfMt\x15|\x1e\x15\xc0?v{\x1c+\xa0-\xff\xbf\x86\x16\x83\xf7\xd0\x05\xed?\xe4\x1f\x15\x14M\x8a\xe1?\x16\xc82\x0f\xbb\x0f\x01\xc0\xa9\x02.\x10A\x15\xe6?\xbbk8\xe1"D\xe4?\xf1$3\'\xac\xcf\xe9\xbf\x8bw\xb9yn9\xd0\xbfZ\x0e\xaf3\xc6\xe7\xe1\xbf\x11\xcb\xc0U\x82N\xe0?,\xfd\x1d`\xd4:\xf4?\x92~L\xa5\x9a\xaa\xea\xbf&\xc6\xdf7\x84\xac\xb6?\xd2\xbc.\x0e\x1e\xf6\xdf?z\x93|\x03!:\xdc?\xe4\x8a-f!o\x06@hQ\t\xd3\x94\xcf\xe9\xbf\x0c\x81\xe8L{\x1a\xed??\x02\xa1\xc5)\x87\xe2\xbf\x04i\xf2!\xa6\x02\xe5\xbf\xc4\xb2r8)X\xf0?[\x17\xba\xcaPm\xca\xbfV\x0e\x0c\xe6@\x0e\xed?)\x16\xb06b&\xf6?\xc34\x1d\xaf\xa9b\xf9?\xca\xce\xdf\xe1\xe6\xc7\xf3?\x84\xc2\x1eV]\x04\xc4?\x1e\xafX(*j\xf7\xbf\xaaM\xf8\x1eb\xba\x03@c&\x9fl\xe8b\xf1\xbf\xf1\xcd\xbbg\x1b!\xbf?\xc6\xbd\xa5\x7f\xa7\x9a\xf9?/\xa2\xa3\xee0E\xe9\xbf\xad&\xd9Jh\xe4\xf6?W\xe5`\xfaW\xa1\xb3?\xb4\x00#\xac\x1fK\xfa?X^\xf3\x88m<\xcf\xbf\x16\xf0\xa4\xe0\x12c\xd4\xbfG-\x0ba4\x8d\xe8?\xcd\xe1\xf1XG\xfb\xa9\xbf\xff\x1a\x94\x0cd\x88\xdd?\xee?:\xf6\x80\x13\xd8?\xce\xf2\x8d\xd7\x1a\xc1\xe4?%\x05\xac\xdc\x85s\xf6?\xc7B\x18\xf4}\x18\xe3\xbfc\x1f9\x878\xf2\xed\xbf\xb3y\x8b~\xfcl\xb7\xbf\xfa\xfc\xf8\x19\xf1\x17\xff\xbf\x9d\xa4\xbf\xe5\x17q\xfb\xbf\xe5\xf1\xa0\x9d\xf1\xd0\xca\xbf\x9f\x8dT\xc8\x94\x9e\xf6\xbf\xaa\xe7fI\xb8]\xef\xbf"\xb1\x95\xce1}\x05\xc0\x90\x03\xa8\xab\x93p\xf7?_a\xa2\x82\xba\xfa\xc6\xbf\x0e\xc9\xb9\xd7\xd4\xf4\xf1?\x0eG\x10mIe\xec\xbf\x7fp\xeb\xcc\xcbF\xe1?\x02m\xb5\x06\xe6\xab\xc7?\xec\xf0\xdd\xc3;\x99\xd2?\xff\x18\xcb\x92J7\xe1\xbf\xf7\x970\x06t\x95\xcc\xbf\xc2\x10|\xb0N\x16\xd6\xbfC\x81&\xd0"G\xd0\xbf\xd7\x01\x01(\r\xa2\xe0?\xda\x17\xfeU\xf9\x04\xe8\xbfW\x1d%\x06\xb5\xfc\xd3\xbf\xb0\xe0\x0c\xdftg\xee?\x8f\x08\xe1\xa4\x06\x8e\xeb\xbf)\xf9(pf\xdd\xd5?{9\\X\x1f\x8br\xbf\xe3\xf09\xd6z\xd4\xc7\xbf{\x00\xec\xf5U\xee\xed?\xa1\xeb\xbb\x872U\xfc?Z\xbe\x8cb\xb3\xf7\xfd\xbfS/\xe6?f\xdd\xd4\xbf\xe3\xc9\x12\x86IB\xe0\xbfk[ws\x0c5\xd7\xbf\xb2\xe2I\xb3\x8e!\x07\xc0\xaa\x9b\xc1\x8c\x95\xb4\xf3\xbf\xa4\xbe5n\x95\x07\xf1\xbf\xde\x8e\x05\xd7\xda\xa9\xbe\xbf/QLN\xc9p\xf8\xbf\x04\xc1O\x00F2\xe0?\t\x8c\xe3\xe0\xe2\xe2\xbb?\xc0\x7f\x17\xcf\x88\xb2\xe7\xbf\x9f\xf7\xbd\xdd\xa5\x10\xf7\xbf\x8e\xc2\xf3b\xee|\xc6?\x16\x01Y\xd5\x86\x98\xda\xbffSG\xf8\xd0\xbd\xc2\xbf\x0c\x853@f\xcc\xe9?\x82=K\xbb\xc3\x9d\xe7?\xa3\x8b\xdc*\x88\xec\xbe?9\x13\x11\x9c\xacq\xe6\xbf$\xbc\xed\xd7WW\x95?0\xbc\xa6\xf8\t\xf4\xf4?\x83W\x8c\x1e\xe0\xf7\xf6?\x8e""PV\x8b\xdb\xbf\x95\xba\x9f\x12\xd9\x9e\xda\xbf\xab-\xe6\xc3\xf9\xda\xf1?\xa7/S\x11M]\xcc?\xdb\xe1\x8b"\x99\xaa\xd1?1|\x9d\xf9\x93\xc3\xf3?\xd7q\xf1\x9et}\xe1\xbf\xd3\x1aF\xc9LY\xe7?Z\xc7\x8f\xc6\xdf\x02\xfd\xbf\r\xd7\x13lhv\xf0?TP\xde\xd2M4\xea\xbfK\x11i&\t=\xb0?wn\xa4\xa3D\xad\xe2?`\xa9U;\xc9\x8c\xc6\xbf\x12C\xb6\xdf\x9d\xa9\xf0?8*\x84N\xa7w\xeb?"\x1e[9\xb9\x89\xed?x!\xab\x80\x08\xbc\x95\xbf\xce4\xaaK\xd2\xe7\xc1?\x9e\xcd\x01\xe9\x01+\xff?(\xf0c\xf2;p\xef\xbf\x90\x93\xb8\xfd\x9b\xb6\xfd\xbf\x97\x19\xbd\x16\x94\x8c\x00@\xf4x\xae\xf8\xff\x93\xf3?m]\xa4m\xa1\xc7\xeb\xbf\x86\x99\xcc\xe8\xbd!\xf1\xbf\x08.\x1e\xe0\x1f\xf4\x93\xbf\xeb\x01{m\x11\xa5\xcc\xbf9\x80\xe9\xa3\xcf>\xe9?7D\xae+$\xf4\xde?\x0e\xf6\xfa\xb8\x85C\xf9\xbf\x80\x06my\xec\x7f\xd1?@r\x9eJer\xfc?\xf63D\xd0D\x03\xef?S\x8b\t\x96\xac\xfa\xf3?\x13\xc6m\x8a\xef\x86\xf7?OE\x08~8\xcf\xd8?\xce\xae\xef\xdbv^\xe0\xbf\xa5\x90?v\xfe\xc3\xf1\xbf(%\nt)\xcb\xeb\xbf\'\x11\xd4\xc4s\x86\xb5\xbfC\xb1\xb9\xe2\xaf\xf0\xe1?.\xf7\xc8\x90$6\xe4\xbf\x0f\x0f.}\xb1\xc0\xfb?\x18\x11\x0e\x92\x85\x16\xc7\xbf\x1f\x9b\x0b \x94\xae\xef?d(\x02\x0fW\x95\xca\xbf\xa1\xee\x80$I\xe2\xf8?j\x0e\x93U\x9f\x03\xe6?\x82 J?\xc0\xc7\xfc\xbf\xc2\xb11\x11B\xd4\xe7?gF:#9y\x06\xc0m*\xca\xd3\x88\xeb\x04\xc0*\x88m\xfb\xc6\x92\xea\xbf\x88\xfd\x9a\x1f\xb6\x10\xf3?C\x1e\xb6\xa6q9\xe0?\xd4\x9b\xcf\xb9\x9e0\xe6\xbf^\xa9V\xc9\xb1\xab\xa6?\x11\xa3"\n\nK\xe1?\x97\x00\x9ca\xb3\xc4\xdc?\x05\xc9\x07\xed+:\xea?\'\xaa\xf4\xd4G\xed\xf0?t\xc6WaI\'\xce?D4\x81\x1e\x03o\xd0?O+O$\xeea\xe4\xbf\x9f\x16\x8a\xb2\x81\x84\xf2\xbfD\xb9\xec7\x92\xca\xe1\xbf\xc3\x90hz6\xb1\xe0\xbf\x19\x17\x00\xfaL\xb4\xd0\xbf\xa1\xa9^\x15\t\xb0\xd6?C\x83\xac\xf9\x00\'\xe8\xbf@\xe5\xcfc\\\x0c\x00@\xc9\xd8t\x9d\xf2\xbc\xd6\xbfE\x860G\rl\xbc\xbf\x16\xe5xK\x92\xa9\xbe?\x07Q\xd1!y\xb6\xd2\xbfUH\xb4$:E\xe5?\xa9\x00\xb5\t2\xd9\xf2\xbf\xbd\xc3\x87\xfb9\x02\xba?\x879\x9e\x0e\xf2i\x84?\xac\xd2C\x91\x1bJ\xda\xbfxT\x01\x15/\t\xe2\xbf\x03J\x06\xe6&H\xeb?\xde+\x11%\x1fE\xde\xbf\x83\xde\x98\x7f\xe5\xdc\xed\xbf\x12\x80\'\x10)|\xd7?\xcb\x17\xd4j\x8a\xfc\xdb?2\xa0#\x18\xc5R\xa7?\x14\xed\x9a\x9exs\xe2?\x1d\xc0\x0fl\x87R\xb1?l\xe3\x82,\x1cV\xfe\xbf-"\x13\xc1\xcb\xc7\xe5\xbff\xf6\xebn\x8c\xdf\xbf\xbf;\xc9*\xfd\xed\xaf\xfe\xbfZ\xb5\x9c\x8d\x0f\x1e\xb9\xbf\x05\xf2\x16\x01qD\x02\xc0MI\xa1U\x92\xcc\xe7?\xb7\x0cK\xf0\x96v\xb4\xbf2\x0c\xc3\xd7\xee\xf2\xdb\xbf\x91\r0\xd0q\x03\xd0\xbf\xb4\x076\xaa\xee\\\x97?F\x0c\xe5\xa4\xe3\xa9\xe1\xbfY\xe0\xdc\x08\x86\xec\x03\xc0\x8a\x1f)\xb8\x962\xfc?`\xe2\xad\xc6e\xe2\xfe\xbf\xf8\xc9\x10\xb3c4\xd4?\xa0\x16\x9a\x98\xfcS\xeb?\xb7\x19\x9a\xb6zC\xd1?\xd8\xd5]\xec\x85\xe4\xdb\xbf\t\xc6u\x07\x90\xb1\x01@\xa6\xd7\xd0&\x07\x9b\xfb?\xb3\xc2\xbf1y\xf4\xf9\xbf\x03\x1bo\xa1O#\xd1\xbf\xd8\xeeU\xb6\x80\xd8\xd3\xbf\x9e\xfb\x89=\x17y\xd1\xbf\xa7\x8aqv1 \xa6\xbf\xe1@)\xb4\xdd\xf5\xf2?N\x99p\xb2\x03\x8a\xf4\xbf\xbe\x8d\xc9)\xd0*\xd2?\xc7\xfd\x12VS\x0f\xee\xbf\x02\xe7\x164\xe0\xd5\xe3\xbf\x04\x15M\x00`\xaf\xf0?vQE\xe0a\xa8\xee\xbfRV])y\xf0\xc9\xbf9\t\xf3\x0c\xef`\xe8\xbf.$\x1a)\xcdg\xf0?%W9f\xa3\x98\xee\xbf\x1a\x85\xf9\\\'\x00\xeb\xbffO\x81MC\xe8\xc6\xbf\xfdX\x9f\xbac#\xf7?\xa2\xb3\xa8\xd3\x02"\xca\xbf3\x9eI\xb3\xab\xa2\xf6?N#&vt\x1e\xf3?\x8a\xa70\xd2\xae\x9f\x0c@\xed\xf2\n\xa9\x8bK\xe6?\xa1bXNzy\xef\xbf\x8a)\xd7$\xadg\xef?c2b/gt\xe5?\xc2c\xb4\xde\x8c\xc4\xf1?\x15\xd66\xcd\x9a\x1f\xee?\xb1c\xe3\xc1\x14\x8d\xda\xbf\x9b\xc3\xa1\x8ea[\x01\xc0p\x84\x9e$\x08\xcf\xfd\xbf\x93$\x10\xd8\xc33\xe6?i\x91\xe25\xb8\xee\xda?+u\xc4\x94)v\xeb?\x1bP\xccr\xf4W\xd8?->u\xbb\x06X\xff?y\xba\x8c*;.\xef?\xcb\xb4vi\x8f\xc3\xe6?d\x84\x90\xc9\x9d\xdf\xc7?\xf1\x88\x9b\xdc*\x00\xd7?DX\x86K\xa9\x9a\xf0\xbf`\xc03\xf8\x97\xec\xec\xbf\xa3\xbe\xa0\x04d\x11\xe1\xbfN!\x01\x93\xa9\xc4\xd8\xbf\xbc\x1a\x08,T&\xf9\xbf\\j\x05:\xdf\xa3\xf0\xbf\x91\x98D\x8d\x04q\xeb\xbf\r\x90\x04\xf9\'\xdd\xeb\xbf\xba6\x7f[\x04\x06\xf5\xbf\x7f\x12\xed\xde\x8bs\xeb\xbf\xac\x7f\xa1p\xa0$\xb1?\xcd\xcb`7ou\x00\xc0\x8b\xe6)\x81\x0fm\xad?R\xe2\xd8O\xd7H\xfc?\xf5\x12\xab\xb3\xe9\xa4\x8a?\x85h\x15\x84\xb5I\xf7\xbfc\xbf\x9c\xa8\xfaZ\xf4\xbft\x1bcN\x7f!\xd0?\xfd\xde\xce3\x93n\xfe\xbf1\xbf\xdc\x1a\x11#\xe8\xbf\x05\xcc\x19\xd1&\xae\xf9\xbf\xd8V\xae]\xa4]\xd9?f\xe0O\x01\xc4i\xe8\xbfB\x192\x02\xf7E\xea\xbf\xe4\xc7\xda\x87b\xf7\xf2\xbf\x1d\xc4\x1d@\xf4\x0f\xde?\xe1K\xe2g"F\xdd\xbf5\x9b\x9fF\x81F\xbb\xbf\x82\xed\xb7V\xe8\x85\xb4?\x9d\x9e\xd9\xb5\x96{\xf7\xbf.\x8dH!\x8a\xb3\xee?\xa0iG\x04\xed\x15\xb7?\xd1\x91y&\x0f\n\x01@z\xacX\xc6\xf0I\xd8?\xc5q4\xf8\xe0P\xf4\xbf\xde\xc37\xdb1\x8f\xf5?\x83hHr\x158\xf6?\xd0\xdeI\xda\x82\\\xdc?\x9f(\x06\xc0e\xa0\xcf?Q\xf0(}\xc4u\xc0\xbf\xc6\xf9\xd5/e,\xc4\xbf\xfe\xbc\xae\x95\xfe\xaa\xf3\xbfw\x05\xe8\x7fKz\xed\xbf\x1e\x95\xcf!vB\xe6?@\xcf\x97\xde\xdbT\xe3\xbf&\xb6\xc3aD\x1c\xbd\xbf\x1d\x14\xb9ur-\xe9?\x1b\xed)\xd0a9\xdd\xbf\x8dM\xda_T\xd3\x00\xc0\xc4\xf7}\x96\xb48\xf6\xbf>\xdb\xd8Y6x\x07\xc0\x13+\xc3\x9b.\xff\xad\xbf|*\xb0\xfc\xcd\xa1\xca\xbfz\x85\xe3\xbbV\xf5\x10\xc0\xdeE\xfb\x04\xf7\xe1\xfa\xbfZ9(\x0b\xa4\xab\xf1\xbf[\xe8\x86P\xb7]\x02@\xa8\xf0\x1a@\xebG\xee?\xc6%\xaa\x9c\x1e\xa2\x03@\x9b\x9fzp\xfb\xa2\xe3?\x16\x1f\x8a\xab\x16e\xe3?O\x86_\xa1\xb1\xec\xe0?\x14\x00\x87\xe5\xe5\x94\x07@8\x997\xa4\xa4\x02\xac\xbf7g\x07\xec\xfd\xc1\xf2?`\xccWp\xe8[\x08\xc0\xaf3\x11\xbc\x9c1\xce\xbf\xf9\xcb\xd9\xb4\xc6N\xc9\xbfoyQ\xfe.F\xd2\xbf\x03\x14>u\xdd\x91\x07\xc0\x8c\x0b\xab\xbb"f\xe4\xbf\x1e\xc3&\x0c@>\xf5?\xc6\xa0\xccb\xde/\x8e\xbf\n\xf6::\t\xcc\xbc\xbfcWf\x92\x9b\xba\xf2\xbf\xfdPo\xff\xfdK\xd9?\xee\x98|\x16\xdcA\xf4\xbfn4\x88\nCq\xf4\xbf\x81nbn\xca{\xf7\xbf\xda^\xe4/\xf2l\xf4\xbfB\x91\xd5\'\xfeC\x01\xc07.\x07\xfb\x86\x9f\xe2\xbf\xe9M,\x0e\x06V\xe9?X\xed"\xf3\x92\xf1\xe5?\xc0\xb1\x10\xda0}\x00@H\xa7\xd3u\x8e\x8c\xf7?S}r\xeb\t\x1d\x00@*\xb2\x00\xe0.d\xf4?\x81\xdd\x94=\x87\xa4\xf6?\x9b"57\xd2\xb7\xe6?Q\r\x99\xa5\xa3N\x06@\xbe/b/\x11e\xe1?RK\xcf\x17S\xd0\xec?\xa7u\xd4\x19\xe9\x8b\x90?\x8b\xb2\x80.\x10\x83\xe5\xbf\x10\xc7N\x05\xdf`\xe4\xbf\xd2\x9eY\x14o\xff\xe3\xbf\xfb\xd2\x14J8\x07\xf1\xbfC\x14\x94\t\x87#\xfa?\xd4\x95/\x82}\xb4w\xbf\x87\xb1%s\xd3\xac\xea\xbf\x07\x85\xe9-2-\xcb\xbfg$swU0\xd3?\xe7\x1ar-2)\xd7?\x1e\x8d\xac\xc8\xf9U\xeb\xbf\x80E\xaa\x1c\x051\xc9\xbf\xa1o7\xa6.x\xfe\xbf\x9f\xc6m\xbb06\xd8\xbf\xeb\x15\x1d\x00i\x1c\xfc?\t\xd21_8/\x83?\xe27\xd5\xcb\xcfK\xd0\xbf!*\xd9\xb3Q\xf2\xdf?\x9c\x83\xf7\xdf\xd5\xc1\xc0?\x7fI6h\xb7\xfb\x05@\xbd\x0b-\xea\xa2*\x07@\xb8\xcel\xed\xadB\x02@\xef\xda\xb1\xd1\x1dg\xf4?\x8a\xdd\xadsG\x19\xe9\xbf\xffl\x92\xa3s\x92\xb9\xbf\xe5\x11\xfd\x14\x0b\xdb\xf1\xbf\xd5\x9c\x0f\x9f\\\x11\xe6\xbf{8\xd1\xbf\xe1\xa2\x05\xc0\x9f\n\x1b\'b#\xf2\xbfL\x9b\x0b\xa0\x01\xb7\xf7\xbf\xbe\xa8.\xd8\xe8\x83\xc7\xbf\xb37\n\x19\xfb\xdb\xf2?\x16p\xeb\xa1P\xf7\xfe?\x82\xd5\xde\xadx^\xf7?\x97\x92\x15\xd3\xb4\x07\x04\xc0o}\xd5&-\xc5\xf9?\x1ac\xef1\x80\x90\xe8?(\x99`h\xffC\xe8\xbf\xa4\x96\xf0\xdbR\xcb\xef\xbf,j5>\xf7\x8b\xfa\xbf\xf4\xed\x13\xa0^4\x00\xc0\xa6\xabD\x14\x93\x15\xe3\xbf\x8e\xe8\x93\xbf\x17\'\xe6\xbf\xa9yy\xaa\xc8\xbe\xda\xbf\x1a:3\x05X\xcf\xb9\xbfD\xbfb\xa1Y;\xf2\xbf\xde\x97\x89u\x00p\xf2?\xfe\xf0\xd6\x84\xec4\x01@\x8d\xf9\xcf\xa1X7\xc7?`6\x8c\xc1\n\xf1\xf5?\xca\x18\x9aJ\x11\xa5\xc7\xbf@\x89J\xc7\xcf\xc3\xdb\xbf\xf4W\xc8\xdb\xee\xce\xfd\xbf\xf2\xee\xb7\xa0\xa5\xaf\xeb\xbfygdu\xa4\x05\xd4\xbf\xfa\xb7\xa5\xb0s\xa3\xc0\xbfd\xc0\xf3&\x15\x1a\xfb\xbf\nJVlF\x92\x01\xc0J\xfa\xdf=\xa05\xef?\xc7\xa3pP\x7f\x85\xe0?\x01\x078\xa6\xebq\xe7?g\x87\xf5\x1f\xe1$\xf5?\x93\x9eL\xe4O\x91o?G\x7f\xdf\xa7\xdbM\xb5?\xcb\xd4\xd6\x19\xd0\x9a\xf0?\xe0\xd0\x1f]\xa4\x9d\xdd\xbf\xd6\xc3C\x1e\r2\x05\xc05\xb5L\xb3\x84z\xee\xbf\xe9J\xea\xc5_E\xeb\xbf\xe3[\x05\xb63\xe5\xfd\xbf\xad\x85\xd0\xf6\xe3\xf0\xf5\xbf\x8a\xce\x9b\xbf\x1f\x93\xf1\xbf\xfe\xce+\xab\xa7\xec\xfd\xbf(\x02\x9d\xe9\x94g\xf1\xbf\xbf.\xff\xbdw\xb8\xfa?6\x0e\x85\x9c\xa8\xcf\xeb?\xdf\x90\x86h\xcd\x13\xdc\xbf\x04\x7f\x1a\x0eb\x17\xd9\xbf\xdfR.lb\xb7\xb2\xbf\xc6\xbd\x95\xad\x17\x83\x00\xc0\x87f\x98\xd4\xce\x1d\xf0\xbf\x8d\rT\xee\xfd\xf5\xed\xbf\xae\xd6\xd3\xa9{\x00\x03\xc0<~\x0eS\xff\xae\xf9?\x8b\xc8\x16\xc1\x1b\x06\xee\xbf\xa6\xe1\xf6Q\xf6a\xe4?\x8f\xe8Z\xbcj=\xea?\xbd\xbd_\x0b\xc7\xc2\xeb?;\xd83^\xca\xa6\xf2?b\x02\x98\xc0\x8d\x17\xf2\xbf1\xdc\x10\xa5\x1d\x0e\xf6?Z\xcf\xe2\xe2\xb4J\xbd?`F\x1fk\xd8k\xde?\xc6\xd86\xd3\xcc\'\xf4\xbf\xdf\xbbM\xc3\x81\x8d\xd4?\xf2e\xe4\xdb\x88\x9f\xe7\xbf\xfd mu\xd3\x9e\xe8?\xfbc\x86=\x04\xb7\xea\xbf\x80Y\xb7}\xf8\xcc\xf5\xbfsGVdT\xcf\xd9?(\xb4[\x1ez\x84\xdf\xbf^\xd9\xeaU\xfa7\xde\xbf\xa4F\xdb7\xb9\xb2\xf4?\xa8/u\xdare\xd5\xbf\xe1\x87tB\x03B\xf4\xbf\x12\xe96\xab\xa1K\xf0\xbf\xe8\xfc\x9f\xa1"\t\xed\xbf\xc2r\xd8V\xe8\x92\xf3\xbf<\xf3%\xfa\r!|?\xd7\x89\xd8\xec\xab\x12\xeb?\x18kl&Pu\x93?\x81g\xf3\x92\xb6\x08\xfc?i\x0e@L\x8c\xcc\r\xc0V\xf0fqqP\xc9?\xbd\xc3e\xcc\xa2\xf0\xc1\xbf\xe7\xe9\x1cw8\xec\xff\xbf\xc23@3\x83\x98\xe4\xbf\xeb\xb4ioO\xec\xd4?\xbb_T\xe0\x8f\xab\xfd?\xe7\x81\xb7\xd0)\x96\xde\xbfo\xb4\x8d\t\x80@\xdf\xbf\x1fa\xe8\x8e\xaeS\xee\xbfws\xc6\xd0\'\x0b\xd2?\xf4R\xce\xaeW\xf4\xea?R\xc1c\xa5\xb7\xa2\xf2\xbf\xda\rH\x0c\xb8\xfe\xf8\xbf\xa0\x8b\xe3\xbd\x1e\x91\xf0\xbfV\xcd\x03\x07m\xbe\x02\xc0-\x9c\x8c\x9e\x14{\x00\xc0\xcck\x95\x8fo\xa8\xd3?8\x1a \xc9i\xdc\x02\xc0\xd5\x9f\xec\r8\xbb\xb3\xbf2LW^\xa5\xf8\xc9?\xfej\x9fU\'\xb3\xfa?4\xa3\xb6\xd5\x1a\xcf\xef\xbf8V\xdd\x9e\xa9\xd6\xe1\xbf\x8c\x92qey\x1d\xf2\xbf\xad\xddg\x013\x1b\xdf\xbf\x0fnd\xa0\x01p\xf9\xbf\x9dD\xb3^\xe5\xc7\xd1\xbf\xc3f\x90\xe8VL\x97?Ds#\x92\xde\x10\xcc?\xe5>dn\x12n\xf1\xbf\xb7\xb8\xa9\xd5A\xd9\x02\xc0w\x02\x85\xa9\xfa\x1f\xd9?\x9b\xd2\xa4\x19\xf2\xf8\xf2?\xf9\xb3l\xcb}5\x9b\xbf\xaa\x00-\x7f\xe2>\xca?\x114&\xcc.\x19\xe8?\x0b\xe1\x89\xb3\xdc\xa2\xfe?\xed\xe2\x14ue>\xf1?\x1d\x17\xa9\x96\xd9\x9f\xc0\xbf\n\xe0\xc0L\xd9\xec\xf3?\xb4\x19\xad\x85\x8c]\x04\xc0:L.V#F\xd3\xbf\xf3\xb3\x8b\x9b\x04a\xc7?\xae\x93\xf5M\xe2l\t\xc0\xa76\xa4W\x0ek\x06\xc03)\x8d\x1fGO\xff\xbf>i\xe9\x07\x1e\xe3\xf0\xbf\x7f\x0eL\xb5\x89\xc4\xf1\xbf6\xeb\n\xcc\x88\x85\xd1?\xa3\xd0\xdf\x9f\x9a\xf3\xe1?\xcc\xa83\x85\xa6w\xc8?\x89\x82\xc0\xe8\x95\xd0\xf7?\x90\x1e)=%\x92\xea?y\x8f\x96\xb6e\x9b\xf2?\x1aNq\xd5\x82\xa8\xf2\xbfBi\xacM\xb4\x1a\xe0\xbf\x83\xf4\xce\xfe\xfb\xee\xe4\xbf3A\xafS9\x80\xf3\xbff\xfb\xdc$!\xd4\xc4?L\x8c\xd8\xf1\xbbe\xbc?)sC\x0b\xf3\xde\xa7\xbf\xa8\x02/\xdf\x86\xea\xe6\xbf\xc9G\x16szE\xfa\xbf!\xec\xb6\x9f\xd9\x9c\xf0\xbfkHVc\xbc\xa1\xf1\xbfH\xfa\x9a:\x89\x05\xf0?\xcb\x0e\xc3\x81\xbe<\xe7\xbfE\x1a<\x96(\xed\xeb?S\xaa\x15\x88\x99\x16\xe7?R\xff6E\x8b\x97\xe5\xbf\x9ea\x1e\xe8]$\xda?\x84\x8eC8\x8d\x00\xd8\xbf\xa4]\x8a\x9bB,\xf5\xbf;\x90Rn\xbal\x0e\xc0\x1dd\xbc\xa3\xc7\xca\x0b\xc0\t\xb2\x12\r\x81\xe5\x08\xc05SY7\x13\x95\xe0\xbf\xc1g\xa2\x9c\xb3}\xfc?\x81\x16\xaa\x92\xff\xf9\xf2?c\x89\xae\x85\x86\x92\xdd\xbfj\x9a\xdfT\x94\xb9\xaa?\x1e\x0b\xb1\x19\x19\xc2\xe4\xbf\xa4\x921\x82d\x1e\xf4?\x10X\xd2\t\x8e\xdf\xe4?aVb!\xa9\xa6\xd8\xbf7\x96\x07^N\xb7\xcb?E\xb8\x0e\x9c\xf3\xbf\x00\xad\xc8J\x15\x9e\xa4?\xbd\x04\xff\x89zc\xe7\xbf\xb2\x92\xca\x85\xd4\xba\xfa\xbf\xa5\xf3ch|\x13\xf5?\xff\xec\x12\x9cn\xb0\xfb?\xf83\xcd\xb8\xa2r\xd8?\xa7\xfc=^\xe5\xdd\xef?\x9c\xef\xa0I\x82\x97\xe8\xbf\x0cL\xd9\x86\xad\x07\xfe\xbf\xc9a\xb4C\x97\x11\x9e\xbf\x8a\xe0Y>\x1b\xea\xf1?\\\x0b\xb8\xb6Tg\xd1?\xbb\xf0\xf2!\x9a\xf9\xe7\xbf\xb6\xa8\x97AB.\xe2\xbf\xd4\xdb\x01\x7f\xe10\xf2\xbf\xd4\xbe\x0b\xed\x11\xa5\xe7?\x01\xe4\x97+~d\xd5\xbf\xf4\x9cyR\xda%\xe4\xbf\xda\x8b\xe8\x85\xb8\x8c\xfa?\x97\xfb\x89I\x02\xe9\xdc\xbf\xc9\x93\x89:/\x12\xf7\xbff\xe5\xe0\x85C~\xe5\xbf\\13\xbb\x86q\xc9\xbf|\x17\xb8\xa8~\xfe\xed?\xd1J\xc2\xc1\xfdT\xf7?[\xdd\xff\xa3(>\xe3\xbf\xf4"m\xce\xfc\xca\xee?\x10\x88q3 o\xa6?\xdc\xa4\xe7\xf5\x13\x0f\x81?M\xa4V\xdb\x8f\xb5\xe1\xbf\x9eM\x97\x11\xa5\xad\xe0\xbfU\xfc\xcd\x0e\x19x\xd5\xbfI\xd0\x17\xf0G\x14\xc0\xbf\xc2\x89\x8c\x92\xf9k\xc5?\x7f3V\x0b\x84\xdf\x01\xc0x\xa7\x82\xfc\x9f\xe9\xf7\xbf.Oz\xf1\xf8\xb6\x9e\xbf\xbe\xb5u\xf4\x01$\xfe\xbfU\xe6\xa5\x9b\xf28\xfe\xbf\xae\xb6\x11a\xecB\xd0?\x96}F6~\xa5\xec\xbfP\xe3A\xf2\x98\xaa\xd2\xbf\x9d\xcfn\xc6\xa3W\xf5?\x91\x91\xcb\x02\x955\xd4?\xa5\xe00A\x80^\xe6?\x94\x86gG\x8c\x81\xf3?\xb6_W5)A\xd2\xbf\xd7\xb0\xad\x07\xc4\xcf\xe3\xbfxrf^#\'\xf1\xbf\xa3+\xb7\xfd\xbb\xd9\x00@pR\x10~H \xf7\xbf]O\\\x0e\x14\xbe\xe6?\xe0\xe4\x86\xdf\xd5V\xf1?\r\xee\xcb\x80\xb5\x0e\xf0?\x01\xeb4\x0e \xbe\xe4?\xc0\xe7\x95\xd6f\xd6\xf4\xbf\xac2og\x97Y\xf1?\x1e\xd0I:\xa0h\xf1?\x1c\xa3\xf3\xcaP\x0c\xd3?V\xb5\xdd\xbfBm\x03@\xae\xa3*\x12\x9eY\xed\xbf\xadT\xdc)\xdf\xcb\xd2\xbf\x85\xe33[\x07\xd1\xfa\xbfm\xb3\xb6\x10#\x96\xbe?\'\xf7i\xde \xc9\xc8\xbf\xb7\xc5\x8a\xd0^4\xf0?\xe7b\x1f9:n\xdd\xbf\x03CP\x8e0\x89\xa1?\xe6\xfas)R\xb3\xda?\xf5(\xbd\x99\x14P\x07@\x94\xe1\x1f\xa6\x00\xc9\xe1\xbf\xe3m\r\x1f\xa4\x0c\xbd\xbf\x0b!\t\xb5(\x9a\x06@\xber\xa1{P\xd3\xcc?\xf8\x14\xa7$\xcc\x14\xc7\xbfB\xce\xc2\x08\xcf6\xa9?|\x81K\xc0\xfb\xa8\xff?\x1b\x9cq`\xdd\xc7\xf1?\xb9"\x1d\xd0\x11\x1f\x01@\xc1n\xe7\xfe\x0b\xdf\xf8?\xbbM\xeea\x9dg\xd3\xbf\xba\x00P\xd7\x8fH\xd7?\x1ay\xf8\xba\xcc\xca\xf1?\xd8sA\xe4I\xc0\xc3?\x1c\x08Z\xf1\xbd\x82\xe1?f\xb89\x80m2\xfb?c\x1f\xe9\x07&\x91\x04\xc0:\r\x97\xe8\xb37\xee?G@\xa9\x02\x08<\xd6?1\x15V\x9a\xb4\xa4\xe3\xbf\x81\xff\x8cp\xd3V\xf1?UF)\r\xd0l\xd1?[B\'\x82\xb0\x14\xe1\xbfr4\xe93!d\xc5?^\xa1+\xb4K~\xf3?\xfb\xb2\xb7i\xb8\xc8\xf5?\x86\xdf.bra\xdd\xbf\xc0C\xf9\x7fc\xc4\x97\xbf\x04\x03\r\x18c\xb5\xe4?\xa3\x95\x9f\xc7\xeeu\xf9?j\x97\xd9\xc9?\xf6\xef\xbf\xc2\x97+\xe7\xf2\xb1\xe3?3>U\xc8F\xe8\xf5?\xb3is\xd7\x17\x05\xf0\xbf2\xe1\x0c\x1c\xc4\x0e\xdf?l\x9es\xd4\xfe\xd4\xd8?a\x8dt\xb9o\xdc\xef?\xd0;\xc4f\x94!\xea?}H\x1b\x82\x9f^\xfd?h|\xcc\x1a\xb6\xfc\xbf\xbf*\xa5F\xc8~\xe5\xd6?\xe8d\x86\x14\xb0\xa9\xef?Q\xf4\xe1Rx\xd7\xc1\xbf_z\x95\x91h=\xf0\xbfr\xe1\xe7\xcaV\xf8\xb8\xbf40Q\x15\xd1u\xdc\xbf\xcb\x96\xe7\xb3\x10\xfb\xdf?\xfc\xfcJ\x87\xce\xe0\xf9?\xc2\x8e\xa4\x05\x1b\x1a\x94?\xb0?2\xd4\xf1\x8b\xf5\xbf\xef\xb7\xe3[\x7fR\xd2\xbf\xb2\x84B#8\xf3\xf9\xbf\xff\xbc\xbam;V\xf2?\x84\xb6\xfb\xaa\xb9\xc0\xf0\xbf\xb2\xb0\xec\x95OD\xf1?\'\xd8\x17Y\xb9\xb8\xf5\xbf\x89\x14\x04\x8d\xc0\x97\xd1\xbf\x82\x12\xa1\xfa7\xe9\xca?\xf5\x97\t\xf6\x0b[\xe2?\xaaW\xea\x1f\xe9\x80\xd7\xbf\xa6\xf0\x82I&\xfe\xe9\xbf\r\x1d\x90=\xb5B\xd8?\xcb\xb5\x85\xaa\xdeC\xdc?F\xb6\xc88\xfco\xee?\xde\xaa\xa2k\xfa2\xc3?\x80jj2|\xb8\xd0\xbf\x08\x1b\xfc\xe4<\x17\xe0\xbf\x0f}\xa4V\x10\xd1\xd7\xbf\xdf\xec\x19\xa71\xbd\xf6\xbfq%!w\xf0v\xea?\xb0z\\\xe4I\x93\xf4?H\x1a\\\xf7\xc4;\xdf?_TRw\xbaP\xf1?\x85\xa4%!\xe2t\xe7?\xb2\xbe\x11bgx\xe4\xbf\x8b\'q\xd2Nc\xef\xbfh"`Ch%\xda\xbf\xa7p\xd4\xff\x0bk\xea\xbf\xd8\xce\xf5g\xc6\xa9\xc1\xbf\x18\x0e\xeb\x13T\x92\xd8?\x98\xde\x0b\xa3$\xb5\xfb?\xfe\x11t\xa3\xdf`\xe6?\x13 \xfa\x05+\xe9\xf3?\x12\xe1F\x80\xe5\xde\xf0?\xe4\xd6\xcf}\xeb\xe2\xf4?\xce\xbe\xafB\xaey\xe5?\x8b\xd8&\x8a\x02\xaa\xed\xbfE\x8eK\xc8+\xe5\xb6?O.\xa1ltt\xf7?\x0e\x9arA>L\xc6\xbf\\\xf9\xc8!)\xa1\xf9\xbf\xab\xe5\x1c\x85p\xdf\xe6\xbf\x86\xcdr\\:\xc6\xe1\xbf\x8c\x98/g\xc7 \xc2\xbf(\xd1\xfa%\xfdG\xdd\xbfV7z\x8d\x0e\x8b\xc2\xbfE\x08{\xb5n\n\xd8\xbf\x86$\xb2\xd1u\xc9\xca?\xf1\xb4\xcf\xd9\x91\xa4\xd3?\x00\xd2\x9a\xce\x04\xc8\x00\xc0s_\xf1\xf5\x16X\xe0?G\xa3\x10q\x8c\x92\xf0\xbf\x83(4\xae\xccB\x0c\xc0\\\x86)\x15\xe0\xae\xe1?M\x0f\xd0\x94\x19\x9f\xea?k\x9b\x8c\x1fq\xd7\xd3\xbf~\x87\xc2\\(\xfe\xf3?\x05\xd8\xech.<\xb5\xbf\xa2\xa5\xc0hi\xc8\xec?P|\x1f\x85a8\xe7?\xe0\xc67\xf5\xab-\xe5?ou\xc7\x8e\x16I\xf0?:v7@\xdf\x81\xec\xbf4G6#\xd5\xf5\xd0?\xd0\xbe\rT\x12\xd6\xe3\xbf\x0f\xb7\x12\x98U\xdb\x0c\xc01?\xa8\x84x\xb3\xff?\x05w\x11\x17pq\xb0\xbf\x12\xf8 \xcb~\xaa\xf3?l9\x90!RZ\xd5?\x97]\x00E\xd4\xbd\xb9?\x86\xe9\x00\x13\xa1\xf8\xd0\xbf\x9e\xec@X\xbe\x9b\xf1?x\xce\xf9\x07\x10\x1c\xce?m\x03\x9c#,\xb7\xec\xbf\xa8\xb0C\xa1\xac{\x00@\xd1\xb3\xe7\xe8@\xcb\xa9?\xc2~y"\xb2"\xdd\xbf\xf4\xa8<%\x0fR\xeb\xbfPD2A\xe2\xd5\xd6?"JZ\xc5\x14\xa8\xd9\xbf\x8c^\xd5\x06\xdd\xdd\x05\xc00\xebT*/\xe6\xed?\xfc8\xccb\x17X\xa9\xbf\xc2\xebC\x15\xf2\xa2\xe1\xbfX\xf6\x9f\xfd`\x1e\x00@\x9a\x96\xe9\xef\xc8H\xba?\xf9\x06\xe9\xec\xa4:\xe0?]\xf1\xe0\xbcQ\xe9\xfb\xbf\xeb\xc3\xdf\xd8n\x80\xe6?1\x97\x12\xda\x83x\xf5\xbf#(bWK\x7f\xf3?\x96\xa4\'\xe4\xc3`\xe6?L>U3\x1dn\xe4?\x1d\x82\xbe\xed\x0b\x1e\xb0?\xca\x95Y+\x18\xe0\xea\xbf\x04\xdc\xe7\x8en5\xd7?\xc8?\x93\xe5e\xec\xe4\xbf\xb8^\x16\xcb\x83\xd9\xe8?\x8a\xb5\x16\x07\xa5\xb5\xbd?\x04\xfd]@\xd0V\xdb\xbf\xac\x88*\xf5Z\x86\xc5\xbf\xb3\x1b\xe8g\x03\xd3\xc0\xbf\x10\xafZ\xf5`f\xf9?(+i\xf7\xdc\x86\xe0?Mx<\xa9\x05x\xc9\xbfY\xd8\x05\x9cTd\xe8?&\x95\xf7\x04^4\xfd?\xb1\xc7,O$\x94\xc7\xbf\x19\x1a\xc5\x1eg*\xe8\xbf\x05w\xd4\xe2\xe1\xb1\xda\xbf\x9b\r\x8f,\xab\x7f\xf6?Q\xcd\x13y!%\xe7?\x8f/\x1bQ\xce\x16\x03@T\x08\x93\xfc\x9b\'\xe1?\x92\xaa0\x81\x8e,\xe3?yb\xe2\xb2\xdbU\xf0?\xc5bY\xc6\x1bN\xee\xbfm\xd7\xb18Y\xec\xe8\xbfY\xa9?q(\x89\xe8?\xe0~\xe2\xa0\xcf\xd5\xe7\xbfX\xd7\x98eU\x92\xdd?\xf6\'\x0f0\xeeM\xd2\xbf!`F\xe5j\x81\xe5?R\x96\xc4\xb5\x94T\xdb?\x02\x0e\xe5C\xc7\xa0\xf3\xbf@*&a*$\xb0?_h\x9cq,\xe2\xf1?a\x8d\x8c\xcb\xb7\xab\xd2?\xb9=\xe78\xf0\x14\xea\xbf\x11\xfe\xbd>g\xf8\xf0?\r\x0e\xf7%T&\xc7?\x1euD\x9b\xaf\xab\xd1?\x01\x7f\xb6W\x06\xaf\xd8\xbf\xe3c\xc0\xe9\x049\xf8\xbft\x16\xe5a\x14\xae\xd7?\xc1\x14c\xda\x8c\xaf\xdd\xbf\xe7\x9d\x17\xab\x07\xa4\xec?X7c\x7f%\xb5\xee\xbfSV\xee\xcc\xf1\x04\xdf?\xaa\xd3\xaa\x18\xa3\x96\xd7\xbfN\x82\xda\xbd\xcc\xbe\xe9\xbf`9\xc7\xecO\x95\xb1?\xecf{*\x14\xb6\xdc?\xd2\x9b\xfc \x97\xb2\xdf?J\t"\xb0\x93\xcd\xf5\xbf|\xc8K)w\x80\xf1\xbf\x1f\xb7\x00i\xf0p\x05@`(\x14=VX\xd3\xbf\xaf\xaa\xfdP\xa2\x8a\xc2\xbf\x9e\xe4\x8a\xf6\xbbK\xe4\xbf\r\xd8R&-\xbf\xf7\xbf\x84A\'a\x94\xe9\xd0\xbf\xa6A\xe9_t\xdb\xf4?`\x07\x1aj\x0e\xd3\xea?\xdeU\xaeI\x96\xc8\xe0\xbf\x8ff2\x99\xfd\xd8\xc2?\x92\xa7\x16\x03P\xd8\xdf\xbf\x87\xe3\x14\xbf\xbc\xf2\x02@\x9c\xe5\xc32\xa6X\xea\xbf\xd6\xef\xc6S\x1b\xb3\xd4\xbf,\xc7O~\x8a\xff\xff?\xc4\xe9\xbd\xc7CB\xd3?\xbd\x19\x1b\x93\xa3\x16\xe7?\xe7#-\xe96P\xe6\xbfs\xd4IZ\x8b\xf6\xd3?\x85\xf3isk\x8f\xe7\xbf\x06b\xbd5T\x07\xe7\xbf\xceL\xa7\xd9x\xa5\xde?`\xba\xc7\r\xe5\x87\xd9?\x96%P\xdd\xaaD\xe4?@\xbc\xd6H\xb3l\xe6\xbf^>K\xd9\t\x0c\xe7\xbf\xc4\x8bz9\x9c\x1c\xf4?K]K\xc4\x84\xe3\xf5?\xbf\x85\xc6K\xfe9\xe3?SDM\x1a)\xe9\xce?XV=L-\xc6\xf9\xbf\x99\x84\x8cc\xbbz\xf0?6\xa2\x1c\x98\xd8\x9e\xec?\xdf\x9c\xc2\xcbZ;\xe3?\xa3;\x9f\x987\xc0\xf7\xbf\xac\x01\x1b\xeb9\x9a\xe4?\t\xdce\x9f\xe1\xff\xc5?D\xdb\xe2\xfc\xe3<\xe5?\xd7\xbb_i\x9b+\xb4?a\xee\xf6\xb9pr\xe0?\x83\xfa\x9d9\xd1\x17\xd4\xbf,\x0b\xe57\x8d3\xd8\xbf\xa3Tc\x921\x0e\xf6\xbfN\'\xe1.\xd0\xdc\xdf?)\x98\x85\x0e\x1b\'\xe3\xbf+ \x873\x8d\xee\xe1\xbf\xb1f\x8a\xb8f\x06\xe8?\x9c\x0b\xce\x83\xd6\xa8\x9e?\xbd\xb6a\x90I1\xff\xbf\xb7h\x9b~\xf4\xe1\xcb?oQ\xae\xac\x17\x85\xda\xbfe_\xe3b\xd9\x8f\xf2?\xdd\x14F-M\xf3\xd0?\xcb\x80\xb2\xcb\x9f*\x02\xc0 \xc3\xabx\xdc\xf4\xc2\xbf\xc9\xb7\xb6\xc0\xcc\xc2\xe1\xbf\xfax\xac a\x8b\xf2?\xd4\x9a\xeb\xec\x02p\xb6?\x8cz\xc4\xf6\xdf+\xef\xbf\x9f\x8c\xecE\x0b\n\xfd?+"\xc8c(s\xf9?\x88\xae\t\x925\x0b\xe1\xbf\xce"\xbdZ\xe2\xc9\xf2?\xdeg5\xb2+\xd3\xfa\xbf\x9e\xf5\xf5\xba\xb6\xed\xbe?\xedg\xb2\xf3\xad\xad\xd6\xbf\x8d\xa6\x87\xc3Wy\xcf?b\xfaCZj\xca\xb7?\r\xd0\x81\xae\xb3\x9c\xe1?-\xf4\xd2\xda\xcb\x1b\xe4\xbf\xbbU\xdf\xe5\xd9\x12\xfd?8\xc4\x8e\x819\xe5\xde\xbfka\x94\xddO\xf4\xe1\xbf`\x02N\xb2E \xe4?\x7f/Nt\x96\x9e\xa0?\'\xb9\xe3w\xe7\x1f\xc3\xbf\xa4F\xd6*\x0e\xba\xe1\xbf\x9f\xbc\xc4\x86/\xcd\xed\xbf(17\xf3\x04t\xf1\xbfm.\xbaL\x95\xff\xf9\xbf]\xe6_\x04\x82\x84\xe0\xbf\xec\r\xb2\xf5\xc7\xaa\xfc?\x0c\xe8\xc9p\x16m\xbf?\xc4\xcf|h:\x04\xfb?<\xa1\x98\xac\xc7d\xe5\xbf\xf2\xd6\x9ep\x92V\xeb\xbf\x18!\xce8\xcby\xd7?\xdfL\xe3\x02D\x8f\xe0\xbfd\xb6\x87\x9c(\x98\xc8\xbf\xabf\xc4\xbd\xe3\x89\xdb?E\x1b\x10_4\xb1\xfb?b\x95\xcc\xb1)d\xfb?\xc0\x12G\xb3^0\x04@\x8f\xd0U`\x04\x9d\xb1?I\xc5\xc7zt\x17`?A\xe8\x1d~.\x97\xf8?\x83\xfb\xdc\xecRX\xb7\xbf\x07\x1c,\r\x17\xb4\xe2\xbf\x9f\xee\xe7\xbf\t\xb2\t\x9c\xb9\xa0\xc9\xbf\xbd\xc0:\xffk^\xeb?:\x07\xe6\x0b\xe4\x05\xb1\xbf`\x13\xd4x]\xc0\xf8\xbf)\xa6\xfa\x89\xa8e\xf8?z]\\\xa5\xec\xb6\xf1?\xc0\x9d\xd0z"j\xdd\xbf\xe6\xe9:f\xdbw\xc0?\xdf\xa8\x8eh\x89\x9f\xed?\xd6C\xe7aO\x07\xea\xbf\xb0\x99Ju\x1f\x96\xe7\xbf/\xae\x9fc\xd5\x86\xef?\x96p\xe0\xe9\xa1\x0e\xfc?\xa6\x96\xac\xb7=\xa1\xe7\xbf\x0es\xa2^rs\xfd?\xf5\x8e\xc3\xbcB\xbd\xef?~h\xc2i\xaew\xc4?3\xb8:vF+\xe0?\x96\xc8\xb2\x0e\x90\x99\xe8\xbf\xa7\xac9\x0f\xb4>\xd4?t\xa7\xedU\x93t\xb5?\xed\xd5rlkN\xe3?\xbe\x13U1\x9d8\xdb?\xde\xb9h\x8e\xc2\t\xe7?\x14fzXs{\xb7\xbfA\xb4\x16\xfc\xd2\x98\xd1\xbf\x8c\xdc \x03R\x0e\xb9?\xda\xee?\x86\xc7\x9b\xe5?\x11m\xfd\xe1\xda\x8c\xf0?\xdc\n\xd1\x841,\xe2\xbf[2\xa7Psh\xdb\xbf\x04?\xfb;!\xc8\xd5\xbf\x91\x99\x83\xa5\xe9i\xc0\xbf\xec\x048\xd5a\x17\xd8?\xbes\xd0X\xce\x91\xe7\xbf\xa4\xc6L\x01\x99~\xfa\xbflb\x16\xe3b\x1d\xd9\xbf\xd4\xf9\x84\x8f]\x9d\xf6?\x1d\xf2\n\xd3K\x91\xf1\xbf\xe7\xe1Z\xe9\xd6m\x00@/\x95\x84\x1a\x93M\xe9?\xa9\xb1\xc1=\xa5\x84\x05\xc0\xe7.\xc6Z\xb9\xac\xc4?=\x9c\x00U\x80\x19\xe4?q\xedx\x9e\x06P\xda\xbf\xb7Z\x07\xf0\xe5+\xc4\xbf\xd2\xc7\xef\x1a\xech\xf4?\x97\x82\xd5\n\x143\xf8?pb\xa4\xc8\xa3\xa9\xe7?\xf8\x7f\xbc|\x88\xd9\xf4?\xa0h\xa7\xca\xf0\t\x03@\x84\xf23\xa4\x0e\xae\xd0?\xbbO-W\xffG\xdf\xbfw!l\x17\x95\x16\xe3?\xac\xad\x82+z4\xe8?n8\xd7\xf8u\x08\xe3\xbf\x05\x0c\xa6kq\n\xf9?\xe6\xfb\xde\xdc^\x93\xc1?,N\x85\xc8\x8c\xd9\xe2?R\x94sJ\xcaF\xe1?\x86\r\xa3\xee\x97\x9d\xfa\xbf\xb9\x9d\xf9\xdfX\x02\x00\xc0@\xff\x9f\xa7;\xa5\xfc?\xe7d\xc1\xb3y\xf3\xf1?\xd1\xfc6A\xcc\'\xec?\x08\x12\xbfK\x80.\xdd?\x87y\xddOJ\xe8\x01@L\n\xa0V1a\xcf?\r\x9f\xa1\xb5Qw\xd4?\x9c4\xeb\xe7G\xe1\xf0?\x0c\x92\xf3\t\x922\xe2?]j\xb0*D\x98\xf6?\xb8\xdd\xce\x92:\x82\xfb?\x92\xf3K\x84\r\x9c\x01@\xf7L\x0b\x85\xff\x88\xcf?\n\xc4\xfb\xda>(\x00@XD\xd2\xf7\xd9\xe4\xc0?\xa8S\xdc{w\xaa\xbd?\x8c\xe9\xbf\xf6\xa1\xc0\xa3\xbf>\xd7\xde\x95\xb8\x1c\xd3?\x11es\x02&\xbe\xf0?\xa2\xa0\x97\xe2\xa0\xd3\xd0?\x04\x11\xd1\xdd\x9c \xec?\xb3\xc5\xd4\xc9~L\xd6\xbf\x19Ex\xfcT\xa3\xf1\xbf\x96\x81\xb0{\xf20\xd3\xbf0\x9a\xc3Q\x85F\xef?\x80ev[\xf6\x9f\xe4?\rU\x98[\xfa\x08\xdd\xbfoN&x\xe0\xca\x0e\xc0.\xc4P*H[\xda\xbf\xdd\xb9?\xbc\x7f4\xaa\xbf\xae\x07\xb2<\xa0\xdb\xa4\xbf\x136\xf4\x07\xea2\xb7\xbf)\xb7\xb7\xd5\xe0C\xee\xbf\xa8\x9d\xa5Up\x04\xf8?\xac\xdf?k\xd6\xab\xe8\xbf\x033l\xc1>\x05\xc4?\xb7w\xabW\xc3}\xe6\xbfb\xfc\xee\x02\xa6\x85\xf7?\xb3\xf1A\x0c\xcd\xc5\xe7?^\xb8\x9f\xf5\xae-\xbf\xbf\rG\x8d\x91\xe9\xfa\xe2?\x0e\xf9\r\xef\xa6\xb1\x04\xc0~\xa6\x12XO\xcb\x0c\xc0\x8brb\xee\xc7V\x07\xc0\xfcbhE\xc4~\x01\xc0\xc7uO\xec\xc8V\xf8\xbf\x06\xe3\xaa\\\xce\t\xee?_Q\x8fm\xbb.\xf1\xbf\xb7\x7f\x07w\xdf\x80\xeb?c)\xb8\xbe\xf8\x16\xb2\xbf&|\xb3\x81\xfa[\xdb?\xf5\xbe<\x86\xfcb\xf4?"\x18\x93W\x01\xdc\xfb\xbf\xb0\x98\xb6\xf9\xc8\xc8\xf3\xbf\xf5$y\x90D(\xfe\xbf\xa0GU_\xdd\xba\xed?\xc7\xf2\xd2)\x04Z\xd8?1\x16\xe7\xa0\xccE\xf5\xbf\xe7K8\xff\xdc^\xe7?6P\xe4g\x07E\xdb\xbf\xe3\x99\x9c\x87\xd4[\xfd?\n\xf0\xe1\x9ea\xc3\xe3\xbf\xc9\x90\xfa\xa9\xa9\xd9\xf4\xbf\xc1\x8c\n\xbf\xd63\x03\xc0\x9e\x06 `\xb4\xd4\x06\xc0\x87.K\xe8C\x1a\x03\xc0\xb4\x7f_\xf0\x14\x8c\x06\xc0+hP\xc2r\t\x15\xc0\x05\x0eJ\x82n\x80\x13\xc0\xee\xce/)\xb5\xf5\x0c\xc0\xf4\x8f\xec\xb7\xbfA\x0c\xc0.\xd2B\x8a\xf0g\x13\xc0\xe1\xf6\xec\x8f\x8d=\xfe\xbf\xbb\xc0>A\xef\xbc\xf2\xbf\xe3\x08/\x06\xe9n\xeb?I\xa9\x8eh\xe3\xc3\xe8?\xe7tyS\x89\x0b\xe7?c\x03\xb2\xbc\xff\x84\xe1?z\xd4\xae\xf6N\x8f\xf1?i\xddm9.T\xfc\xbf\xf5\xb8\xf84\xf0*\xf3\xbf\xe0\xc7\xc0\x03\xe6z\xc5\xbf!\xd1\x85\xa0~\x10\xee\xbf\xd4\xe5\x17\xb3\xce\x14\xf3\xbfs\x0f\x9d\x8f\x17H\xc8?\x9fMrx\xe1Z\xf7\xbf+\xa69\x1fS\xe4\xed\xbfA\xdeg\xb5\xcd\x8d\xd7\xbf\x95u\n\xa9\x93\xdb\xdb\xbf\x81 \x04\xb3\xb7\xd4\xbfE7J\x95\t,\xfb\xbfn\x91\xdd\xfa5\x87\r\xc0(\x1b\xa8M\xca\x92\x06\xc0\x11e\xc3D\x0cP\x17\xc0\xb8\xf5~b\xefj\x15\xc0\x86\xfd\xcd\x9e\x1f\xb7\x1e\xc0\x7f\x02\x889=\xfa\x14\xc0O\xb0\xafM\xc7\x1b\x15\xc0\x8c1\xd5\xd2*\xbb\x02\xc0\x01\x0c\rWO&\xdd\xbf\xd5\xf6!\xa1P\x15\xe9\xbf\x93_\xbcx&\x18\xc0\xbf\xab3\x18@\xdcg\xe6\xbfu\xe6\xc3"\xf2\x8e\xe1\xbf\xbb\x12\xb6.\xc5\xcc\xd7?@=O\xd2\xb2\xac\xf4\xbf\x8bOq\xd8\x1e1\xf1\xbf\x19\xef]\xf1\x18Y\xd1\xbfc\\\xb4*\xa2\xaa\xe5?\x15\xf7r\x8e\x93\xfd\xbf?\xd4%\xcf\x05\x8c\x90\xc6\xbf\xe1Z\x8f\xd5N\x95\xf5?j\xf1\x8b\x18k0\xc9\xbf\x81\xf4\xdf\xbf\xe6\xa5\xe0\xbf9\xa28{\x0c\x05\xf9?\xd0\x8e\xbf\x8a\x17\x14\xde?\t\xd9\x01\x8e*\x82\xee?n\xe2\x80\xc1\x9fQ\xe0\xbf1\xae\xb0\x14n%\t\xc0\x86\x86\'->Z\x16\xc0\xbf\xa4\xa4^w\xe7\x13\xc0s\xa7YJ\x8ew\x16\xc0\n\xa8\x99\xc99\x0c\t\xc0\xba\xa7\x97\xb2O\xdf\xea\xbf.LP\x8b\xc8"\xd1\xbfan/\x99&\x0b\xf4?\x88\xc6\xc0_\xc2l\xf4?5X\xaf\x14\xcfB\xf6?\xb69\xf5\xbc6\xaa\xad?~\xb9\xcfL\xc3g\xae\xbf\x1d\x9d(s\xfe\x96\xcf?n\xbc\xfa\x97n\x12\xe2\xbf\xce\xe6\x9c\x07\x89\xaa\xf1?\x84\xf8\x14\xe1\xc2\x0e\xc5?\xde\xaay`\xa93\xed?\xc1\xd3>g\xaf`\xbe?\xf0\x8dF\xc6+\xcb\xee\xbf7a\xb1\x99\x04|\x04\xc0B\xbf\xafU\xbf\'\xe7?\xdf\x85\x05\x9c\n\xd3\xd1?\t\n\xf3E\x94\xa6\xe4?\x8b\xdch\xfb\xa9\x07\xf8?\x9f\xcbh\x92*#\xf0\xbfN\x95H$\x85\xda\xe4\xbf\xff\x11^\xb4E0\xcf\xbf\x0f\x81\xdb\x99O\xd0\xc0\xbfL\x17\xbd\x18o\x91\x0f\xc0\x99L\x03*;\x8e\xfc\xbf\x84\xea\xf2\x95\xf0\x91\xff\xbf\x1a\\\x04\'\xf8\xc7\xe2?}\x01\xb9\xa1\x90\xa2\x01\xc0a\xce\xf3b\xd9G\xfe?)\xd1\xbf\x18\x14\x1f\xdf?b\xb3S\x04^0\xc6?\xe3\x84[\n\xf0\xbb\xdc\xbf1\x15\x82\xc1\xc4Q\xf7?:\x00\xba\x1d\x11\x0b\xed\xbf\x04\xd7\xa8\xb7fY\xc3?\xd8Ihm\xdd\xe5\xa5?\xae6\xeb\xac\xc7W\xa6?\x1bJ\x05\xf0\xea\x8c\x9d?\xf3\x80H\xc6\x00\x8d\xf0\xbf\xf4\x87\x85\x92\x0c\xd1\xe4?$\xddW_\x15\xf7\xdd?\x990\x93y\x89\xce\xd2?w= m\x89c\xca\xbf4\xc9N\x8b\xeel\x03@#<\xff\x04N\'\xf8?\xba\x18\xbe\x19\x95\xf3\xf6?[V\xcc|@M\xeb?[\x9ct\x93N\xde\xb2\xbf\x01\xdd\xf3\xdc\xd8I\xd5\xbf\x9f\xfbe\xe2z\x8e\xe3\xbf\xddRw\xf2\x16\x81\xcb\xbf\xfd\xdc\xf1\xfb\x94\x1e\xe0\xbf{9\x8d\xf6@{\xe8\xbf\x93\x07\xad\xf7v\x10\xf4?a\x1a\xe2\xbfN\x8d\xe2|\x16\x04\xf4?kt\xc6\xac\xf7\xf0\xf2\xbf\xb4\xf8\x8e\x04\xef\xed\xe3\xbfc\x8cg\xc2\x04;\xee\xbf\xa8\xda\x9c\x1d\xca\xe5\xe0?\x0b^w\xc6&\x11\xd9?v\x01\xdf\xac(v\xc9?*\x94\x05M\x95\x9c\xd0?\x16\xd13P\x80,\xb4?Gjq\xb7S\x7f\xd0?\xee7\x90\xdc\xce)\xe8?>\xd4\x0eO\x02]\xf2?l\xcbV\x03\xf9\xe0\xd9?\x99)Y\x9a4\xf1\xf3\xbflKt.\xebF\xf7\xbfb\\\xde\xba2\xbc\xef?L6\x0b\xf9\xaao\xe5\xbfx\x0e\xda\x92\xef\x9f\xf9?\x8e\x00\xc6\xe0~\xbc\xf4?*\xcflRL\n\xe6\xbf\x93\xf5&\x97#\xa7\xf6?\xa2k\\\xe8\x1a\x0f\xe6?\t\xa1\xe7\x86j\xbf\xe6?\x08\xe9\x89\xf73\xe2\xe0?\xd6\x8f\x15\xee\xb5X\xf0\xbfy\xf1\x97:\xfbT\xe5\xbfy\xc3\x97\x8e|\xd1\xe2?U\xe4-v\xf5=\xf4?-\xff\xe05\xeb\xb2\xf4?H\xf1\xf7\x10T\xd6\xe1\xbfW\x1a\xf0\xbf\xf5\xcb\xcf?T\xc7d\xbf\x02y\xdc?\x1f,\xa8%\x08\x19\xda?\x1cF3F\xbbJ\xec\xbf>\x17\x18p\x85\x8a\xe4?\x10,"*s\x91\xcc\xbf\x99.~tQ5\xe4?\xa2\x9b\xea7\xca\xb9\xf1?\xddz-\xac\xafi\xe4\xbf\xe98\xf7r7\xc9\xe2\xbf\xb4\x1b\x1a\xf5\xea\xce\xae\xbf\x0f7\xc2\x9d\xd4y\xf1?\x88\xa9{\xbc\x05\x9f\xb3\xbf\xdc\xad5\xd2\x01\x15\xd4?\xab\xa9K\xb6\xb3\xe4\x8e\xbf$)f\xeb\x89\xd8\xf6\xbf1>@\'\xf0\x84\x02@\xdf<"4\xf5\xd2\x06@)\xbb\xbe\x18\x93M\xf4?WR\x15\xc5\xa9(\xc2?x\xbf\r\xb3\x16\xc1\xbc\xbf\x9b\x05a\x83k\xf7\xd0?\xfb\xc4,\xac\x8d\xd7\xe5?\xd6f\x954\xb8\x19\xe4\xbf\x16@\x87o\x8b\xf6\xd8?\xae\xe8dP\x82\xc5\xf3?\xd1\x9b\xa2\xa6B)\xdb\xbfN\x1bM\xac\x1f\x03\xf6\xbf\x13r\xcba`z\xe2?\xfb\x1c\x03\x7f8\xc9\xcc\xbf\xe1/\xe7\'3\xc05?"\r\x9a\x01a\xc6\xee?\xb4\xd1\xb3\xf8r#\xc5\xbf\xb0n\xce\xbe\xd2\x97\xd0?d\x10\xaa\xcc\xc7\xec\xe3\xbf\xda*\xa7\xe1Q\x9c\xd0?\xc1\xa9m}\xab\xc6\xe9\xbf\xaeo^\xf7\xc0\x8c\xe8?\xda\xa0\xc2\x1f\xb4\xc4r\xbfJ\xca=YS\xa3\xc7?\xe2\x94\xe4\xc1\xcf\x1c\xc9\xbf\xfb\xf4\xa1\xcdL\x90\xc3\xbfv\xc1\xcf\x08\xb4\xe2\xea\xbf\x1cu5L\xba0\xe9\xbf\xa75\xaf\xf9\x08\x1e\xd2\xbf\xd2\xe1%\xb1\xcd4\xf1?Q\x8cF\x02B\xb8\xfb\xbf\x02\x19|9z\x8a\xd9\xbf\x80\x92\xaaEq\xaa\xc7?.\x9c\xd8\xbd\x8f\x02\xd3\xbf\xf7\x82\x8fg\x82V\xe5\xbf\x9fU\x0eU9\x83\xe9\xbf-\x08$\x8b\x89\x1e\xfb?\x9a\x01-\x03/\x84\x07@\x87\xcd\xcfm\x19\xc4\xf2\xbf\x19\x01N&\x15\x84\xe4?=R\xfb1p\xef\xe1?;2\xccgU>\xe3?\xacu\x89\xdcA7\xeb\xbf\x99P,k\xbe\x86\xd7?\xf4\xc8R\tr0\xe6\xbf\xb8\xb3\xdc\xd9f\xb9\xe4\xbf\r\xbc\xa68\xfd\xc0\xde\xbf\xec2i\x0bHX\xe1?%\xde\xb2A\x98\x9a\xe1\xbf\x0f\xc80\xa0\x00\xaa\xc0?\xbd\xbaZYF}\xbd\xbf\xe5TK\x04\x89`\xb1\xbf\x97\x89\xc5!&[\xf0?\xca\xd4\x89\x1a\xe0\xff\xd7\xbf\xc7\'\x9f\xc6z\x7f\xe0\xbf\x0b\xeb\x8a\x0c\x0f\xbb\xf4?ll\x03\xdc\x02\x8c\xf5?\xa7\xa8\xcd\x8d\x93\xd7\xef?\x9d\'Y\xbc\xcd\xce\xd7\xbf\n\xd0!\x02\xad\xfd\xe5\xbfK\xab\r\xc0D\xd9\xbe?\xf8,\x0e\xe5\x89e\xe1?]"<\x8c\n\xfe\xe1\xbfnz\xd4\xab\rJ\xf4?\xd0\xdb\xc0\x10\xd8\x1d\xce\xbfY\xfe*\xcd\xa2\xb5\xe9?\xbc2\xa7\x022j\xfa?\xc0{e\xe8YX\xa5?AF>h]\x85\xf6?\x81y\xce\x89av\xee\xbf\x7f\xaa\x0e\xbd\x0b\x8a\xf2\xbf^\xce\r\x82X2\xdd\xbfN\xf2\xda\\[3\xe1?\xf7tf\xa9\xb4\x1e\xcd\xbfm\x96\x07\x04\xf0\x80\xab\xbf&>\xff2\x1b\xc0\xdc\xbfR*\xaf\x8dH\xcc\xce?\xa5\t\x13\t@\xaa\xb2?2v\xec\x16?\xb7\xa7?\x17\x8c\x87\xfa)/\xf0?V\x1b\xcbr|\xa5\x87?\xf3\xbc\xd0?r\xbc\xb0\xbf\xab\xc6\x0ca\x7f\xa3\xe1?\xf3\xafj\'\x9e\x13\xc9\xbf\xff\xd5\xfc\xe8\x03u\xeb?U\x121\xdd\x95#\xec?\xb9\xb0C\xf1\x08(\xeb?.\x90\xc4r\xd1o\xe8\xbf\xe6\x07\xd5^\x91A\xc7\xbf\xa5\x7f\xe0\x86\xb0\x7f\xd2\xbf\xee\xe9\xfb1s\x84\xf4?Jk\x1ak#\x16\xbe\xbf\xc7\xd1F_\xde\xa6\xc9?\xcaRs;\x1f \x84\xbf\xfcS9=\xd8\xa2\xf7?\xab5`\x86\xe6\xee\xeb\xbf`\xd4nfw\x87\xf1?\xca\x9a\xdc\xe9\xd6\xe7\xe3?O\x8d\xa8;\xf4)\xf2\xbf\x9a\x10\xea\xe0R\xe7\xf0\xbf7EpG8\xe1\xde?\xc3\xfdX\xd1\xdem\xe5\xbf\xa0\xdcwBc\x98\xea\xbf\xe7\xc3\x02\x1b\xdd\xf7\xcf?xO2\xcc\xee#\xb4?]Z\x12\xb0Q_\xea\xbf\xa5\x9e\xf5D1\xff\xf7\xbfLU\xb0\xe9=X\xe1\xbf&\xda\x95\x81*6\xf5\xbf\xfd\x0f\x14}\xf0\xca\xf6?\xd7\\\xe7>\x95\xca\xcd\xbf9d\xff\xe3c\xc8\xed\xbf\x02#\x9d\x17\xa2\xec\xe3\xbf\xb0h<\xf5\xde\xc5\xe0?\xcbc\x12\xa4\xceH\xeb\xbf\x11P\x12A\x0c2\xec?\x07\xc3\xd9+\x809\x02@s\x02E5\xf5C\xc9\xbf\xd8S\x16f\x92{\xf1?\xb69\x99\xbb\xe5V\xd8\xbf\xaf\xce\x92\xe2\r\xdb\xe3\xbf&V;\xc4\x04\x0b\xd6\xbf\xf8*\x83\x82l\xd6\xf8?\xdfcp\x82\xad\x18\xe1\xbf\xba\xe6%\x0fH\xd2\xd4?\x00\xa0\xadi\xed\x13\xb9?z\xc7a\x9eWT\xf4?\x8fzp\xf01t\xde?B}\xae\xd4\xbd=\xdd\xbfVZ\xaaU\xf1B\xda?\xc7V\xb8mk\xee\xc7?\xff\xf6\t\x17\x91\xa9\x82?\xc0\xc9\xfc\xda\xe9;\xdf?H\xb0\xeeimM\xf4\xbf[\xfcw\xa8\x90\xf9\xd6?\x88\x90\xb3\x10\x9b\x81\xe1\xbf\xa8[\xd1\xeb\x94l\xf4\xbf[\xac\xc3P\xf9\xe6\xe3?\xd8\xbb\xdc[\x82\x9b\xcd\xbf\x0e=.\xf8\xaf\x85\xf1?\xc3\x89\xf6\xea1#\xb2\xbf\x82\xedD\xb1\xac\xe9\xc2?\xd6\x9e\x88\x9f\x16\xf6\xe1?.\x99\nG\xc45\xff?\x16uGMWu\xd6?\x86\xbe\x01U\x8c\xcf\xb5?e!k\'\x1c\x00\xe5?t%p\x92\x06\xbe\xe1?1\x121\xbd\xcf3\xea?\xad\xe0\xfb\xcd\xc9U\xea?\x99\xffF9\xd1p\xe3\xbfv\x96\x1b\xb8\xaas\xcf?<\xe4-\xee\x8f:\xf1\xbf\xb9q\x8fFN\x8e\x01@n\xdeP\x8a\x9b\xae\xce\xbfoG\xb4\xf8}/\xf1\xbf\x8a;\x8b+\x9e\xe2\xac\xbf\x86\x1cB\x90\xa8\xc3\xcc?\x96\x1c\xe5\xa9\xe0\xd5\xd9\xbf\x9d\xfb\x9eV\x0e\t\xf1\xbf\x8e\x1a\xe4x\xc23\xc5\xbf\xd0F\x14\x87\xcb/\xfd?Mu-\xd7\xcb\x85\x01\xc01Nh\x8be\xe4\x04@O\xf6&\x18\xa3\x1e\xf8?\x1eu3\xc5k|\xe0\xbf>S%\xc3[\x1e\xb1?\x01{C\x0cM\x04\xe6\xbf\xe7\x1e\x1c\x1c{0\xe3?s\xff%\xc9\xb3\r\xef\xbf\x9c\x13(\xeb\xedD\xde?CX\xbc\xeb\xf7?\xed\xbf\xbd_\x0b\xba\xa5{\xbb?\xd9\xca\x97\xac6\xf4\xed\xbfqZ\xc7\xfe,\xe2\xf7\xbf\xb9\xb8\x9c\x1b0\xe4\xe4\xbf\r \x12\xb2=\xa5\xbf?\x08N\x93~\xa5<\xfa?\xb8\xd0!\x1dU\x05\xf8\xbf\t\x08O\x95\xa6\x17\xdb\xbf\xa7\xb8\xd2\xed\xa9\xbe\xe2?\x93\xa2\xe9\xfa\xa8w\xd9\xbf\x93\x1bt\xab\xe3F\xf8\xbfOQ^\xfb\x18|\xe2\xbf8\x85\x7f\xef\xb8\x0e\xe5\xbfg\x86a\xa8n\x08\xf1?\x0f\xc4\x9b\x900K\xf7\xbf\xb3\x03\x7f\xd1\x95;\xfa?\xac\xa5\xa68\x0f\x95\xf1?u\x951\xd6\xbfX\xe5?\xb6\x13\xeel9\xae\xf0?Ra\xc2h\xb7\x16\xe8?)]\x10\x87\xe8\xbe\xfc?\x87\xcd\t\xc9m\x0e\xf5?)\x9c\x04\xc5\xd1\x96\xf6?\x84$HVD\xc5\xc3?M\xcc3\xb9\xdc\x0c\xd5\xbf\xe1\xb8\x12\xfa0\xe4\xda?\x1a\x17\x81}\x96-\xec\xbfs\xd2~\x93\x8cF\xfa\xbf\x03\xb3w\x07\x14N\xef?;\xe2|#+\x86\xe2?\xdb\xde;\xa6\xf8Y\xd7?\x93h\xd4L[\xd0\xf2?\xbbT\n\xd1\xdb\xc8\xcc?\x18\xec\xe6\xaa\xd5l\xf3?m5]VC\x8b\xdb\xbf\x16\xec\xf9\xba\xbf\xd3\xf2?\xbd\xfe\x8c\x03\x85_\xf1\xbf\xa2\x82\xf5-\xf0\xd1\xea\xbf\ri\xbd\x03\x90C\xe3?\xcd\x01\x9d\x80`e\xd2\xbf\x88G\x0c\xfa\xd9g\xde?\xa2O\xa3a]\xdb\xba\xbfDD\xa3\xd4\x17)\xc3?\x08\r\xfb\x15\xe7\xe3\xdb\xbf\xf0\xa3\x1e\xbc\xc7\xbb_\xbf\xf0\xe8\xe3\x1d\xa3\x97\xb8?\xe2y\xd8\xe7\xdf=\xe4\xbfS\xca\x91\xc9\xa0W\xd5?\xc6"RV\x8bu\xed?\xc5\xbch-\x10*\xc9\xbf\xd9\x92\xb8C!\xb6\xe8\xbf\x13\x92sl\xda\xab\xf1\xbf\xa0\x1e\t\xf5\xeb\xbe\xe8\xbf\x83\x87\x15iR\x7f\xcb?x\xec\xd0\xd2\xc3\xe8\xc3?\xa2K{\x94\x81;\xe7?\x1f\x15}6\xf2O\xec?\x12\xa2F\xd6\x89k\xf2\xbfAmB\xa3\x14\xed\xdd\xbf\xcfV\x87\xb7Q|\xe6?\x9e\x1d\xbfv\xa0\x04\xe2?\xe2 \xe4\xff\xea3\xf4?\xf7M\xd4X8\xca\xdc\xbf\x8d\xf9\x84\xa1z\xe4\xe6?\x0f\x04\xaaO\xb1I\xe6\xbf\xc2\xd2\xc8\xc9p\xfd\xe5?\x896\x8c`\xe0z\x02@\x8a\x03p!0\xaa\xee\xbfl\xb5\x17*2\'\xd8?o\xfd\x9fBD_p\xbf\x8au\xe2\xc1\xc7M\x99\xbf\xa4\xab\x19\xbd\x03\xe2\xd4\xbf\x03\xb4H\xf2i\x7f\xcb\xbfJ#0z\x0ci\xf3?\x81a6\xf4\xb3%\xe9\xbfK\x0f\x82\x17p\x8e\xe1?\t\x1f3\x14s\xc4\x00\xc0\xe9V\x00j\x9f\xf3\x97\xbf]Gd\x10h\xeb\xe8?\xc1)k\xf2?\xd1\xea?8\xb6\xe7\xc9\xe2\xf7\xe1\xbf\xc8,5\xd4\xa0?\xf3?\xbahc~\xe0\x9e\xd3?TP; \xf4\x92\xd8\xbf\x84`\xc7\xb5\x1b6\xa1?f\xc4\xf5%\x91\xd8\xf3?X\xeeW&\xbb\xe9\xdb?CYh\xdb\xe5(\xe0\xbf\xfd1\x1f7\xdd@\xeb\xbf\xfd\x9d\x15=\xa6\xd2\xd9?&\x99\xc4Rm`\xe2\xbf\xa6\xc87@\xb5\x92\xf0?\x14\xa1\xe1n~\x1d\xfa\xbf\xd9".\xab\xa3\xea\xb5?%\x14K\'T{\xd9\xbf\xe6Pz\xd4\x8b\x14\x8e?\x12\xae\x81=M\xe9\xc0\xbf\xa8\xab-\xb7\xdbE\xf5\xbf\xa7C=\x8a\xaaU\xc5\xbf\x19Y\x93\x9a\xc4B\xc7\xbf\x97\xb5\xd85G\x18\x04@|\xa9N\xa5\xaa\x83\xdd\xbf\xaas\xfb\x88=\xaa\x86?\xfeP\xd7z\xa2\xc8\x02@\xa0\xbd7&\x7f\xef\xde?\xdapRO<\xaf\xf2?\xfc,\xd9\xd8\xa9r\xaa\xbfq\x85f6\x89\xb0\xa0?$\xe1\xe2\xac\xc5:\xe8??\x83\x02\xbbt@\xe9?\x8e\xa4\x1e\xd7\x0f\xe3\xdd\xbf-_\xa6MfU\xf0\xbf\x85_X\x8e?\x1e\xd9?\x15\xa6t\xc5\xa5\xf7\xe5\xbfCT\x8fge\x00\xe2?\x14\x14\x89\x94va\xcd?H^eS8\xab\xd3\xbf\x98~_\xe9>8\xdb?q\xf0\xe4\x9f\xb8\xb3\xce\xbf\x082\x87\xb1\xe6\x1f\xed?\x18\t\x8a?\x8f\t\xe4?\xde]i\xf6\xd3\xcf\xd7?\xa1\xb9m\r\x91\xb1\xec\xbf\xd4\\\xb9#m\xac\xf7?\x15\tg$_<\xf6\xbfus+f\x00\xa6\xe8?\x9e{2\xd3P\x0f\x81?\x0c\x12z\x0b\x11\x02\xd5\xbf\xa5\xa0\x06]\x0c\x81\xf3\xbf}\xc11\xcc\x88\xea\xef\xbfm\xc1\xcdQ\xfb\x04\xf2\xbf6\xedu\x1f\xed\xa3\xf3?}\x90\xc5\xccK\x11\xb9?\x1a\xe2_}\xa13\xdf\xbf\x932\xb1X\r\xa7\xe5\xbf\xbf\x01\xe1\xcde\xce\xe1?\xdejfk\xba\t\xfc\xbf\x18P\x97\xf5U5\xe5\xbf2Bz\x04Ba\xc3\xbf\xc5\xbcS\xac\xab\x87\xf6\xbf\xb2\xa3\x96\x01\xfe\xb3\xfe\xbf{\xf0\x89\x0fo\xe0\xdd?\x1b\xc9\xb3\xed}\xca\xf1\xbf\xc7X7=e\x11\x00@\x15\xb5RcX~\xfc\xbf\xa1\x10Zo\x9a4\xc4?\xaa+\xe7\x81\x9e\x94\xe4\xbf\x12\x83\x91+`@\xe2?\x93!\x9c\x8f\x04\xa4\xdb?\xda\xc0\xaaP\x1c\xfa\xe6?\x07\xd5\xfe\x06\xad \xd0?\xbfqW\xbd\x83\x02\xea?n\xd4tt\x8d~\xde?\x80\x0e\xa3U.\xaa\xf5\xbfF\x04t\x86\xd78\xd7\xbf\x16\xa5\r\xba\x0f\xdd\xfe\xbf&\x9au\x18\x8fv\xea\xbfd\x1f\xef2=\x9a\xea\xbf\t\x89\xa5{3\x8d\xfd\xbf\x009m\x17hi\xea\xbf\x15\xd0\x0f\xba\xa3@\xdb?Y\xa4\x0c\xfd\xd5\xc1\xed\xbf\xf2\xb0\x99\xb2M\xf1\xdd?Zl\xaeg\xb6(\xe0\xbf\xc6t\xbb:\xe1\xdc\xfb?\xadV\x9ekb\xe6\xe5?\x89\xc9\xc4\x14\xd8\xff\xf5\xbf\x93mn\x0b\x0c\xf9\xf7\xbf\xf5\xcb0\xac?\xb6\xdb\xbf\xd9h\n\xe0@\xad\xd5\xbf\xe15t\xc0]\x12\xeb\xbf\xa2}hK}\xfb\xd1\xbf\xd4\x9c\x07\xa8\xbf\x9b\xd7\xbf\x99@\n\xcbH\xd0\xcf\xbf\xc7V\x13\xeelN\xf5\xbfm[q\x15\xf5n\xcc?Id\xa00\x99\xa5\xd2?\x98\x1f\xd1\xd3\xf8>\xf8?\xab_\xca\x02\xa3\x0b\xdc\xbf\x10W\xcb2\xea\x93\xce?\xab9\xdcF\xef\xa9\xd3?\x1fY\xaf\xfd\xf4\xf2\xfb\xbf\xb1\xd2\x84\xa6DL\xfe\xbfD\x1c\xff"\xe8\xad\xfe\xbf\xac!\xd2g~\xe7\xf8?\xf0\x85\xfb\xa8\xaer\xea\xbf=\xda\x01\xd7\xa0}\xfe\xbf1:\xbe{\xf5+\xea\xbf\xc6\x9c\xc5\x81\xf2\xbe\x08\xc0\xf4\xda-wi\x10\x01\xc0\xf5\xd9`.\xb0^\xc0\xbf\x18\x8b\xbe\x82\x0c\xcb\xf7?\xddY\x01\xe4\xc5\xa6\x91\xbf\xdc\xef\xdc\x89\xef\x81\xec\xbf\x13\x1e a\xe7\x8e\xd9\xbf\x16\x8c\xcf\xf8S\xb3\xd6\xbf\xb1\xe2\xa83\xa0n\xe4?\xbfw\x03\x8d\xa1\x0e\xf9?\x93\xf0\xad\x14I\xf0\xe4?\x01:\xffL\xb3\xf5\x8f\xbf .\x00\xa2\xf6f\xcf?\x10\xa6i\xe9\xa2\x0c\xdb?N\xe8\x93=:\xfa\xf0?\xc3J\x06\x91\x8fF\xd5\xbfE\xc3J\x14m\xd3\xe6?e[b\xb8\xe6\xe4\xf1\xbf5\xa0&,\xb36\xe0?\x04\xc1V\xc01\x1c\xe8\xbf\x1c5\xac/q\x85\xf2??\xd0~\xf5\x82\x7f\xd8?\xfa\xee;/w\x8a\xee?\x96\x89\xe5\x87ns\xf0?D\x8b\xae\xe0D\xf0\xe4?\x82S\x10\x02p\xcd\xe0\xbf\xadz\xe05\x0f(\xd9?!;\xb3\xcb\xe8\x9e\xba\xbfV\x12\xbd\xdf\xaaW\x94\xbf\x10R(\x17)(\xf6\xbf\xf2\xff\xbd\x18\\\xe0\xa6?\x18\x0e\x88\xef\xbe\xc4\xd2?\xa8/?V8\xf5\xec\xbf\xb9\x12\x1et\xaf\x82\xf5?\tNj\xc6Z^\x03@5\x80\xd5\x9c\xd2k\xc1\xbf\x8a\xb93\xa1\xcb\x88\xeb??@\x8b\nNO\xd1\xbf\xee\xef?\x0cL\x9b\xf1\xbf<\x9e\x94\x0f\x96\xb4\xf2\xbf:wlL`\xe4\xfb\xbf\xf4\xd0\x00\x1d\x8d\x12\xf2\xbfV\x94v\xa7\x8e\xec\xe8?Rlc\xc6M\x86\xe5?r\x87B\xed\x1e\x1a\x9f\xbf\xc1\xc2\nU2\xef\xf0?|*\xd9S#i\xd2\xbf\xc3\x86\x8cM0\x0c\xd4?8ZF\x0f]\x80\xf4?\x94 I[\x89\x8b\x00@\xa3\xf4\x04\x95P\x82\xd3\xbfl\x9e\x86\x8fkQ\xd3\xbf\xe6\xc2\x13\xc9.#\xe1?\xd7\'\x90\xe2\xff\xd6\xbe?\xbe\x0f\xde\xd3\x18\xc6\xfb?\x03m;g\x16\xc2\xd9\xbf\xe4&\xb7\xc0|\x91\xdb\xbfX\xca\xbe\x8da\x8d\xe4\xbf\x1e\xfdT\x88\xc7\x93\xf4?9\x16\x05\x83\x8fE\xc6?\xdd\x92\x15\xbbX%\xf2\xbf\xf0\x17\xc2\x06\xce\xa0\xa4?\xb4\xa0\xe9\x85\xf3\xd0\xfe\xbfK3\xcb\xa3\xbb]\xf9?"\xfe\xa7\x16|\x04\xf0?R\xfe\x1b|\'u\xd1\xbf\'\x8e?u\xde\xfa\xdc?q:]\xc1\x14\xf0\xeb\xbf8\x9e\x04\x1e\xfc\xe6\xf0?Y\xe7\x86\xd3\xa0\xa1\xed\xbf\xc2Z\xb0\x1c\xde\xb8\xe7?\xb3J\xb2\xd8\xe6w\xee?\x93\xe1\xd2#\x83\xc0\x04@_\x81K\x11\xab\xd3\xd7?\xa2\xf9G%\x14\x9d\xd9\xbfw\xc87_D\xe0\xe4?\xad\xaa\x93(ZT\xe0\xbf\xdb\xe5\xf9\xeb@\xb1\xe5\xbf\xca\xed\xdf \x80e\xde?pj\xba\xda@Y\xe4?F\x9d\xe2\x86\xad\xca\xcc?\x1e\xda\x99\xee\x15*\xf6\xbf\x92Z\x03axu\xed?\xb7EX\xef\xa7|\xe2?8\xaa\xb4\x8a\xc6V\xe8\xbf\xf2S>z\x80J\xea?"t\x8a\xd3\'B\xe4?\xd1T[\x8d\xa2\x8c\xda\xbf\x9f\xbfr\x0c=\x96\xea\xbf9\x0e\x87\xd4\xc3\xde\xf7\xbf\x17\x1brwoM\xc9?OnV\xde\xcbR\xd0\xbf\xaf\xb8\xa57>\x1e\xe7\xbfO\xb9-\x95.Z\x83\xbf60\xdc\x87\xd4\xff\x03@v\xb5\xf62\xdfw\xe2\xbf\xb4CL\x14f\x05\xcf?\xbc,\x9f\xf1\xb6\x95\xd7\xbfo\x8f\xfeyE\xe1\xf7?t\x19P\xf2\xaf\xf3\xe3\xbf\xd2\xe4\x1c3?/\xf5\xbft\x0f\x18\xe0\xf9\t\xf0?\xa0\xff\xedk\xe4\x82\xf1?\xc8\xe7\x83U\xd2\xa8\xbb?\xc9\xc3;W\xd3e\xe4\xbf\x82\xf2(\x87F\x88\xd0\xbfH\xfc\x92%Z=\xd6\xbf\x8a.p]\x93\x92\xef?\xce\xcc\xd9\x01\x83\x18\xf6?\x17\xd6\xaf\x9f"\xb2\xf0?\x7f\xc0\xe05R\xb1\xd2?\xd3Vl\xc0\xa7\xa0\xf3?D\xa3a\xe0\x84\xe7\xeb?4\xdb&\x1e\xe5%\xeb\xbfw\x1d\xf7Qw\x11\xe6\xbf\xbb\x85\xb9\xbc\xdcO\xe9\xbf\x1akX\xb56\x05\xf9?\x95\x1a3\xca\x93=\xec\xbfLg\xd2\x13\xd9M\xe5\xbf\x16\xe5P\xdeT4\xb5\xbfE\x83T\xe4\xab1\xe8\xbf\xc1\x94\xba\x89\x91\xf5\xbd?\x87\x1f\xe5\x1a{\'\xe3\xbf\xd9\xd2\x0cp\xcdD\xfe\xbf\x8d\x82\xe8H;\xac\xfa?)\xf3\x8b\xc9\xfe\xf6\xe2?\xac^\xee\xfew\xd0\xe2?u[\xd2\xd3\xfbJ\xec\xbf\xe5\xf3\x04\xc3\r%\xb6?1.\x9fe\x04\x83\xdb? k\xa3N\x87\xa0\xf4\xbf\xcb\x15_T\x86\xd3\xdb\xbf\x18\xd3\xb4\x97\x931\xf8?\xf1rb\xb7\rW\xc0\xbfse\xdf\xf84/\xeb?\x02\xc95\xc0S\xe9\xc2\xbf\xb8W\xc9\xd2\xa8\xcd\xe2?_h\xb3\xf5\xc0U\xbf\xbf*M\xf7R5\x93\xde?\x99\x0f\xda\xaeX\xd9\xdf\xbf-`k1\xbd4\xef?\xa1\xc3\xa4\xe3\x1cw\xe4?`\x86\x11l;~\xf2?\xd1\xa0\x17y\x80\x0c\xeb?\xac\xbf+\xa4\'T\xcf?\x95\xa7\x90cW\xa5\xc0\xbf\x00*\xf0\xceT\xb5\xb4?\x02\xb8$a\xde/\xff?ahI\xd7\xb1\xf4\xf1?\xe7b=\xe2\xfa!\xfd?7\xdd\x16\xba\xf36\xf5\xbf\xa4\x1e\x0b\xb9\xd97\xdd?\xaa\xda\x8b(#\x8d\xd9\xbf8\xf3Y4F7\xc7?\xdbGI~\xe8\xf4\xf1?\x97\xca\x90R\x14z\xc0\xbf\xdc\xb2\xe8\xab\xe2?\x86\xc5N"\xd2\x1c\xf0?\xe7\x98\xc3\xd5t\xe0\xde\xbf\x18\r\xb2i\xbdJ\x01@\xad\xbf\xb7\xb9l\xca\xe8\xbf\x17\x19\xa1\xbc\xdb\x1a\xf6?v\xe0@4d\xd6\xec?$\x11\xce93\xa0\xde\xbf\xb5=\xfcu\xfbS\xe7\xbf"\x14\x18\x8d\x1f-\xd9\xbf \xd6\x0fm\xa2(\xe4\xbf\t\\\x10\xdbPt\xc5\xbfiN\x05\x14\xfa\xbd\xed?\xe9\x8fY\xa3rE\xd4\xbf\xb5\xddc\x96\x87[\x04@L{\xd5?x\x8a\xf7\xbf\x84\r\x89#}U\xe5?\xe1\x93\xbd\xe8}\xea\xdd\xbf\xa5\xaf^\x0e\x90%\xe0\xbf\xc6\xf7\xef\x8e\x87I\xe3?XE\x1f\xe2\xd7"\xc4?\xa7\x04\x15\x96\x8fz\x9f?\xbeh\xaf\xdf\xdb\xa7\xf3\xbf\xcbN\x91\x9c6<\xe4?\x8a\xca\x0fv\xda\x92\xe0\xbf\xcf\xd4_\xb0\x9e\xcb\xdc?\x02E\x12\x16\x97i\xf0\xbf\xfa\x89qt\xc4\x06\xf4?pDaW\x8ac\xd5?Gp\xcc\x18\xa7\x05\xf0?\xaaR\xa9\x06[\xd9\xdd??a\x1f\xbbS\x92\xf9\xbf\xc0pr\x046.\xff?\xdd\n\xde\xc7u \xc8?Wb\xa5\xbbr\xed\xee\xbf\xc6RI\x1d\x8f\xea\xf3?{s\xc5\x1aM\n\xea\xbf\xb2\xe7n\x17\x05\xd3\xf1\xbf_\xe5~\xa8 X\xea?\xd7\xe4\x19\xf1\xc1\xd8\xe4\xbf\xa0\x97\x97\xa7_\xdf\xca?@\xaf\xa8p!\xd7\xf0\xbf\xf8\x81#\xa9\x98\x1b\xd6?\xa1\xc5\xaa\xc7\x9b~\xe4\xbf\r56C\xe6\xba\xe5?P\xc1\xe9\x96Z\x9d\xe7\xbf\xd9\x12\xee\x172\xf7\xe1\xbf>\xe2\xb0L\xea\x9d\xe3?\\\xbe\x88(L&\x07\xc0\x11OE\x92\x81?o\xa1\x85x\xde\xa0\xd0\xbfp\t\xf9\x1at\xa2a\xbf\xd52\xd1Cp\xc6\xe4?c\x87D,\x06\xb1\xed?\xde`\x8f\x8a\xbc\xbd\xfe\xbfkR\xb0\x1e]\xd4\xdf?Q*\x98\xc2\xf2^\xdb\xbf8\x0e #\xd9D\xf2?\xb1\x05H\xe3\xb9\x8e\xd7\xbfH7\xc2\xf7\x8f\x8a\xce\xbf\x10\xdf\xaf2\xa2\xb7\xe1?\x18\xeb2\xd25\xb6\xda?\xfblwn\x98\xb3\xe9?\x91\xc0b\xd6\xc9\xa0\xe0\xbf\x8b\x7fh6=\xcf\xb1?/\x8b\xec\xdf\xbf\xe5\x04\xc0\xc0\xce{\x8e\xaa\xd1\xca?\x96r\xc9\xae\xbc\xd1\xc3?\x86b?\x99/\xc1\x11\xc0\x06J\xf9\xba<\xe5\xec\xbf\xd8O\x05D\xde\t\xea\xbf\x0b\xd2q\xde=\xb7\xe3?\xa5\x7f\x05_\x98<\x07\xc0i\x07\x1b]qZ\xee?\xfbw\xdd\xd4\xc0\xe0\xf7?\x10G\xed\x84\xd9\xf9\xbc?.\xc3\xc4\xaa\xd7P\x02@\xd0\xee\xfafDy\xe6??\xe4R\xf1L\xe8\xe0\xbfl\xbb\xe6K\xb3\xe4\xc3\xbf\xf2\xfc-E\xd1~\xe4?\x07\xb9\x9a\xe0\xa2\xe2\xd7\xbf/c\xf2)`a\xe0?\xc2\xbctK9O\xd4\xbfezI\x85\xf4/\xef?3\x87\xa0\xc3\x16\xa7\xe6?\x83\xc22b\xc1\xb5\xe4\xbf#\x87\xcd\x10aw\xec?\x1a\t\xe1T0\xfa\x04\xc06p\x11\xdc\n\xa1\xe2?,#\xb8\xde\xa9J\xd2?\x97\x01\xdf\xb3F\xdd\xf6?,tk\xb5\x83V\x00\xc0+\xda\x92\xfb`\xf8\xdb\xbfk(\xe6\xcc\x0e*\xf1\xbf$\xbfE7\xe7\xab\xef\xbf1x~D|\xec\xfc\xbf\xd7\xdaU\x11\xf2\x8b\xf1\xbf\x0c6P7H\x81\xcc\xbfg\xa0M\xb2\xa4\x1f\xf1\xbf\x1b\xc8\x9e\xb29\x00\xf2\xbf?\x95\xbd\xbd\xf7\xd2\xde\xbf\xc8\xde\x03\xa7m*\xf1\xbf7\xc6V\x14\xeax\xe9\xbf\xd8)y\xd54\xae\xf2\xbf\xad\r\x19\r\xfa\xee\xc5\xbfk\xa7\x0c(\x8f\x89\xfc?\xfd\xaf\xc8\x0f\xaa\xbd\xfa?z]\x7fo8\xeb\xb7?\xab\x95\xc5\x82\xae.\xf3\xbf\xd1\xbf\xf4\xb4\xb8\xf8\xec\xbf\x0ed\x86\x9b\x85\x87\xe0?\xbf\x0f %\x94\x0e\xee?\xe9~\x05\x98l\n\xd9?\xb7\xd6\x15\x868.\xca?\tT:\x97\rQ\xc9?\x8d\x02\x03l\x12\xa1\xf1\xbf\t\x0bL\xb8w\xcd\xea\xbf\xd0#N"\xdcZ\xe3?\xc0\xd4\x07\x08\xc4g\xb1?\xac\xaa\xa4\x9b\xfd\x99\xc6\xbf\x8f\xac\x15\xc1d4\xdb?\xea\xcaX\x8fs\xcd\xd5\xbfL\xc8\xd0\x0bxv\xff?\x972\xa9l$\xfa\xe4\xbfC\xf7\xd0e\x11\x92\xe0\xbf\x04\x0cN\xa7\xd8(\xfb\xbf\xaf\x9f}&?\xb6\xdf\xbf\xb9J\xa7\x13\xd8A\xf8\xbf\x1e\x88\x81_\xa0\xfd\xe2\xbfd\xc2\xb1Z\xefQ\x01\xc0\xeb\xc1\xd6m\xfe:\xe7\xbf]\xd2\xf7\xe9\xf5.\xf5\xbf\x887o\x01z\xee\xf6\xbf"\xd7+\x95\x11\xde\xcf\xbf\xc3\xaf\xb3\'\xc4\x04\xf3\xbf\xa3bb\x9e\xc3_\xe2\xbf(\x1d3\xe7\x19\x8c\xda\xbf\xbag|\x91\xf5\xae\xe4?s\x1e\xb2\x86\x9d&\xf4\xbfo\x1d\xa4\xf0\xb1\xf4\xba\xbf\xae\x00\x06f\t\x88\xff\xbf\x0b;\xf6X\x10"r\xbf\x89b\xc1\x96l\xc6\xe1?\x17\x8e\xc4\xc5\xd6\xf7\xe7\xbf\x17\x1f\x7f\x95\nb\xe0\xbf"\xa5\x9f\xcc\xd7\xbb\xe4\xbf\xa1\xfc\xdb\x1a;\xc0\xa7\xbf=\xe5\x97\xb1\xe8\xc6\xec\xbf\xda#\x84$\xdc\xb1\x03\xc0]\xf9\x9c\xbb\t\xf4\x00\xc0\xb2\xd17=\xdb\xa1\xcb?w\xfae\xd6\x94\xfc\xd5?L\x1a\xb4\xd9\x8eE\xf7\xbf+\xe5\x01\x9b\x87y\xe9?\x8f\xa2\xab\x9e\xae!\xf5?\x86\xc5uf\x0fC\xfe\xbf\x97\xe165B\x8d\xaa\xbf\xee\xa7f\x1c;\xa5\xe4\xbfAHF\x9a\xb7\x1b\xf3\xbf\xe4i\x80xf\xba\xf5\xbf\x97j\xb6\x83\xb6U\xe4?f\xb1_Y\xc38\xe6\xbf/J\xcc\xd8\x9d\xfc\x01\xc0\x15\xee.\xc5,\x9a\x00\xc0\xcc\x91\xf8\xabR\xd8\xf5\xbf\xffR\x17\xe8\x99 \xe4?\xec\xe8\xa1\x15+\r\xa3?k\xe6\xb9\x14sc\xf4?\x86\xa1F\xe2\xc9\x19\xba\xbf\xd9\xdaq?6\xf7\x00@\x04\x12-D/0\xa7\xbf~p\xa0\r\xb6n\xcb\xbf\xb4d\xc0\xa3\xa3v\xe0?\rJ\x89\n;\xc6\xc7\xbf\x94\'?E\x00\xb4\xc8?B\x0f\x9e\xbd\xcd\xda\xeb\xbf\x18Am}\t\x19\xf8?\x9f\xaa\x84\x17qw\xe8?\xdbk@\xcf\x81\x9c\xef\xbf\xbd\xb6\x14>De\xeb?\xea\xb2\xfc\xdbl-\xe6?\n\xcc&\xb2\xf9\x15\xfd\xbfB\x94\x05(\xed\xcd\xc7?\x07_\xf7\x9e\xb4_\xf8\xbf\xe3\x99\xb2Al\xa5\xfa\xbf\xe2\x8ds\r\xd8e\xf9\xbf\\O\x89\xfc\x88\xa5\t\xc0gB\xa5U9\xa9\xff?\xb2OY\xf8\x01H\xf7?\x91\x9c\xea\xa6\xefW\xec?\xe5\xe6\xae\xea\xfcz\xd4\xbf*\x88\xfd\x1d\x1fJ\xfe\xbf~\xe9E\x88\x18\x01\xe9\xbf\x9d\xdf\x1c\x85\xbc\xec\xc9\xbf^RI\t!f\xf4?\xc5\x8eg\x90\x94~\xd6\xbf?>aU\xcf\x9c\xde?\xe2ju\\\xae\xd5\xd0?\x01\xbd\xc1\x05\xff\xe0\xe9?\x07\xec\x9619\x9d\xf6\xbfbx\x9e@Z5\xe4?\x8b\x0cW=\x13\xbe\xec\xbfR,H\xcf\x9f\x89\xfe?)j\xda\xaaR`\xf9\xbff\xc9\x84\x81\xa6\x99\xe8?\n\xfb\xd7\x97\xa1.\xc8\xbf\xad\xeas!L\x10\xe2?\x8b\x80\xdb\'\xfaJ\xec\xbf\xa3]\xc11\xd3\xbd\xf7\xbf\xee\xc6\x82\xaa4\xa0\xeb\xbf\x17[\xa4\x7f\xfbu\xf2\xbf\\\x18!vPo\xe2?\x17\xf63Q\xedr\xf0\xbfQ!NE\rK\xde\xbf\x02~dF~\xa1\xc0?\x12Gn\xc3\xac\xd0\x03\xc0\xc6\xcd\x95< [\xec\xbf\'\xb6D\x9b\x08p\xff\xbf\xa4(\x903k\xba\xc0\xbfgvo\xa5\xa9\x07\xfa\xbf\xf2^\xb6=\x92\x1b\xc0?\x15\xdd\xe5\x9d\x83\xbe\xf5\xbf\xe2\x96\x12\xb4\'Y\xd8?\xe3w\xec\xcd\xd5\x92\xe7?\x19VZ7e\xaf\xb9\xbf\x88\xf0\x9c\x91\xe2\xd8\xf9\xbf\xdc\xcc\xb9V\x19\xf0\xd7?Z\x04rR\xd4\x83\xf1?,\x93\'I\xe7\x91\xdc?\x8b\'k\x81\xd5\xb4\xe1?\xf1\xd8\x03\xe6<\x1e\xcc?\xa3m\x01\xcc\xe4d\xfa?\x0b_\x96\xf8\x0b5\xf1?\xd5H\xb7\x14+\xc6\xf2?B\\\xa95\xd3\x9a\x90\xbf\x9b\x90\xdc~fV\xf6\xbf\x8dKP\xb2,t\xf9?J\xecT\x82\xfa\x93\xe3\xbfi\x7f\x92\xdcX\xae\xf3\xbf>\x0b\xd8\xa7\x19\xb2\xe2?\x02@i\x0f\xe5\x03\xdf?\x1ev\xdb\x0f\x0b\xa4\xe3\xbf\x7f\x9b\xb8\xb4\xc5\xb4\x06@o\x03o$\x02\xa3\xfe\xbfm\xf5\xe1\x00\xdfQ\xf1\xbffi\x07@\xee8\xf9\xbf\x03\\\xe4\x17\xef\x1d\xf9?\x83\xc6\xe7~\xfb\xba\xfd?\x0e\xa3X\xc8\nx\xec?=\x0e\xec\x05\xe9\xf5\xdd\xbf\x8a\xb6\xc9\xca\xfd)\xfe\xbf\x9d\x1fq\xde\xa1h\xf4?\xafF"\xf7\x81\x01\xe8?\xe4\x84\x96T>E\xd4?Ps\xc8\x0c0\xe6\xf0?\xda[6\\\xef\xe4\xe3?r=;\x90hh\xef?\x06h(V%Y\xe0?hi#D\x9eE\xe6\xbf2>F\x82;\xcf\xcd\xbf\xd8\xbe\x84\xb0\x91<\xbb\xbf\x05\x1dtV*\x03\xee?\xf8\xe4\x86h \x05\xe9\xbf\xaa3\xa1.\xde9\xfb?\x0e\xbb\xb0MxI\xee\xbfg\xaeT\xfd\xcb\xb5\xd9\xbf~y?5\xa8\xf6\xe5?\xf2\xb0\x92\x8eN\xb4\xf2?\x01y\x8c\xb4\x94\x90\xf7?\n\x91\xd1\x9a\xa7\xa3\xb9?5?\x14\x06)\x97\xf0\xbfp\xbc\xaf\x95\xf8\x7f\xf2\xbf\x18\x13\t\xbceB\xd0\xbfi\xda\xfeO\xca \xf7?d{\xba\xdb\x8ej\xec?\x00+\xb7\xb4\x9b\xf5\xf2?\xc9d\xa9?v\x87\xd3?\xd0f\x81\xdd\xee\xa4\xf3\xbf\x13\x89po>\xc5\x0e\xc0\xe6\xb1*\x82\xa1\xf6\xe0?\x14\x17\xd0\xbdN\xcb\x00@\xa2\xa0\xd4\xe5i\x81\xe5\xbf\xac\x9bx\x80\xaf\xcf\xf4?\xd7\x8b\xacV\xfc\x82\xf2?\xb8\xe4m\n^\xa4\xf1?\xbb\xca\x02A\xf1\xe6\xd2\xbfe\xa4\x80\xbdf4\xe6?\t\xd7C\xacK\xdb\xdd\xbfr\xd2\xa5p\xd0\xe0\xa0\xbf\xc6/\xdf\xac\x84/\xe9\xbf\xcd\x18$Q\x87\xd4\xe0?\xc3\xa1aPM\xf6\xd7\xbf\x80\x9c\x1a\xaa])\xf8?\xce\x1b^vB4\xb7\xbf\xcc\xd94}SL\xdf\xbf\x19\xa1\xd3^;\x85\xdb\xbf\xa5g\xee\x88\x95\xfa\xe5?\xb0y\xcd\xe5?n\xe5?~\x91b\xa7\x94[\xe9?\xa2\xdf\x9e\xba%\xc6\xfe\xbfU\xd6\xe6\rED\xf4?\xea\xa4\x18\x03\x07\xc1\xf5\xbf\x8c\xb2\x11\x98\xcam\xf0\xbf\xa2\x87\xb2dpK\xf0?@k\x11K\xbf\x1b\xee\xbf\xde\xe5g$m9\xf0?\xd1\xb43\xcfA"\xcf?\xae\x06.\xaf\x98\xe7\xe0?~S\xec\xb9\xab\x9d\xf2?\x96%\xcf\t\xfc\x99\xee\xbf!\x14\x80\xca4\xfd\xd4\xbf\xb0"&\xbe|\xd8\xd3\xbf\xe7r_K\xe63\x01@\x06\xa5\xff+\x14\x03v\xbf\x05\xe1\xed2jf\xed\xbf\xeb\xdd\x1e$\xe1\x06\xe2?\x88\x84\xd4G\x9a\xe5\xc5?\x08\xb9H\x13ZW\xe2?2\xc6I\x9du\xc8\xd6?3\xa1\xe6\xb5\x95\x82\xf5?\x0c\xce\x81+\xf0\x80\xa7?\xfd\xd0\x93\xc0\xb6]\xeb?\xb6,\xc7\xac>\xee\xe8\xbf\xa3*\xd7O\xda\xc8\xdb\xbf\xb9\xbeJ\xee\xa4Z\x00\xc0k\xbc"\xbdb)\xf0?l \xfd/\xe2\x8b\xc6\xbf\x85x\xe1\x96g\xf3t?>\xbdFu\x96\xd2\xe7\xbf\x97\xf7\xa9\xc6\xfe\x16\xf8?t\xb7\xe3\x8a\x07*\xf7?m\x88a34-\xe7?\xe1\xb0l)%\xa5\xe3?\x07\xbf\x97\xf1\x88p\xe5?G\x1fq\xe3>\x01\xf5\xbf\x03\xfc\x8eJ2Z\xd8\xbf\x06\xe8\xc9\x04\xe2@\xf4?\t\x1e\xda\xbc\xf6\x86\xea\xbf\x813\xdf3E\x01\xfd?=\x87\x84\t\x977\xc1\xbfh_\xa2\xbd\xd8N\xf2\xbf\xfd\xb4g/\xc7a\x05@\x07pN\x80&\xbc\x00@\xe3M%\x1c\x81|\xfe\xbfB\xd8\x16\x0f_\xa7\xdf\xbf\x04\r+\xf0/T\xe0\xbf\xff\xfb6\xae\x1b\x11\xc2?\xfd\xea=\x93%\x94\xdc\xbf\x08\x9a\x96n\x93\xaf\xe8\xbf\x1f\xb4Y\xa5\x08I\xd9?\xbe\x8dp"\x94 \xba\xbf%LB\x18\xae\x0b\xd5?V\xbc\x1f2+h\xd2?\xfbb\xdcb|@\xd6\xbfU\x1e\x0fq\'\xea\xf5?\xd2\xc5m\xc0\xb2 \xe9?C\x017\x9f\xc8e\xea\xbfMQ+\x14B\xa4\xf2?\x92@\xde"\xe0 \x00@\xaae\xa2}\x1a\xb0\xe4?ch\x90FN\xa9\xbd\xbf\x0b\x98\xd0\xa1\x96\'\xb8?\xe0\x1b\x07H\x990\xd0?\xf2\xbaT\xc3\xe3\xe8\xdf?e\xd2\xd9xZ\x8f\xf2\xbfz\xf7\x0f\x00\x8d\xf6\xdd?\xa0\xf3\x12\x07\xe6\x90\xf4\xbfW\xdclm\xd4m\xc3\xbf\xcc\r\x98\xdaN[\xd7?\x94\xd6\x85B\x0b\xc9\xd5?\x11\xdc|\xc87\xb0\xe9\xbf\x81W\x90\x8d7\xe9\xf3?\xab\x1b?/>\x9d\xed\xbf\x10\x8d\r\xaf\x7f\xb9\xd3\xbf-\xed\x05F*\x14\xcb?\x9f\x04x\xb7\xfb\xe1\xe7?,\r\x1f\xda\x90m\xe3?e\x1f\xf0&*4\xe1\xbf\xa6\xaf\xd5:\xe53\xc8\xbf\xf3\xda\nK#\x9c\xf6\xbf\x91]c\xca\x00\x94\xd0\xbf\xfb\xe6\xd1lm2\xfe\xbf$R\x95~\xf8\xbc\x92?\xed\\\xeb\xddD\x95\xd0?\xdc\x97\x18\xf7`\xfc\xf5\xbfk\xe8s\x07V"\xbc\xbf_<\xef\x0e.#\xe9?\xbb\xf0\xe4k\x06+\xe1\xbfk\x0fV\xb3\x93&\xc5\xbfu\x80\xdf9 \x9b\xe0\xbf-[\xe1\xb3\x81\xeb\x05@Kuu\x87j\xb4\xf0?O\xc5)eO\x9e\x01\xc0L\xdcM\x12\xf3\x1e\xff\xbf\xecW\x87^%\xf5\xfb?CN\x1aV\xcd\xd8\xec\xbfJ\x1a\x97\xd5C/\xf5?S\x95\x82\x99\xbf\x04\xee?\x8c,\xec\x17\xf1o\xdf?\x94\xcb\x02Ady\xf5?\xcd\x9a,\xfabk\xd1\xbf\xa0"\\\xf4\x80\x96\xea\xbfn/\x96P;A\xc9\xbff\x99\xe0\x90\x01E\x01\xc0p\xce}\xb2@\x02\xf3\xbf\xec\xff\\\r\xa5<\xe3\xbf\xb9\x93\x82dJ\x8f\xcd?\x18\x8e$\xa5\x99\xec\xef\xbf\xa3\xa2BA\xa3Z\xd4\xbf\xfc\xee5\x06L\xcc\xe6?cZ\x07\xfcz\x90\xed\xbf\xf0^\xd6\x83\xe6\x06\xf9?&\x0f\xd6\xbd\xae\xa0\xf7\xbfL5\xaa \xe9w\xdd?\x18S\xb1#\x9cJ\xf3\xbf\xa1\xa7;3\x95\xf3\xef?\x0eq\xb0\xc1(\xbc\xe1??\xde\xe8\xc89\xec\xb4?N\xe4v?\xd3\xda\xda\xbf\x90p\xd3\xad\xcc\x8d\xeb?\x8e\x1eL`\x0e\xad\xe5\xbfh|=\x15\xa2\x88\xd6\xbf\x96\xa5(\x03hW\xc3?r\xe1\xe0(\x01{\xdd\xbf\x022\x01X\x0e\xa3\xdf?C\x06\xaf\xf4>r\xe7?\x00\xce\x8f\x80\xa8\xfb\xe4?\x95\n\x01{}~\xef\xbfN\xc7\xfa\x89|N\xf6?\xe8\x91\x04\xcd\x191\xc5\xbf4\x8a\x0bV\xa9\xe9\xf7\xbf)\xfa\xdd\xd3[\xf9\xe3\xbf\xba\x86\xeeV\xdbJ\xf6\xbf\n\xba\xf59\x1c\xc1\xfd\xbfq>\x01cr\xdf\x00\xc0\xda\x1c==\xcei\xfc\xbf}\xd0G\xb6Z\x1c\xf6\xbf\xcb\x81>\xcf\xe01\x05\xc0\x0c~\xc6\xb08\xb6\xe7\xbfg\xdd\xf9\x85\xaa\xe0\xe2?4`\xf6\x82\x03y\xd2\xbf\x0b\x9e\x83~\xc6\x9a\xfb?7\xe401\x9e8\xa4?s\x05\x14j\x10\xc4\xda\xbfo?\xe9\xd3\x08\xda\xf2?\x18\xcb\xd4\xc2\xef`\xf2\xbf.t\xeaFT\xcd\xe9\xbf\xecC\x9c+\xfdP\xac?\xc1\x1fq\x9a\xe6V\xfb\xbf"\xde\x82<\xe3\xbe\xfa?\xd3RI\x050s\xdb?W\x0b\x8a\xdd]\xf0\xf8? g\xec\x86!T\xc1?\x8d\xbe\xb0\x7f\xe0\xf5\xca\xbf\xe0G4t\x7fg\xdb\xbf[.\x0b\xd5Uw\xf1?6\xe36\x9c\\\x16\x89\xbf5\x9a\xec\xd3\x17\x81\xbb\xbf\xd3F\xd5\x8de4\xdd\xbfo\xa4\xea\x93\xba\xd3\xca?\xe91\xee\xc5\xaa\x9b\xe2\xbf\xbd\x91\xa9\xe3\xa4\xd3\xf4?!\xcb\xea.\xf2a\xea\xbf:A\x9f\x0f\x05\xb4\xd2\xbf\x12\x03"X\xc2k\xf0\xbfK\xd5\x0e\xc7M\xef\xe4\xbfy\xe8#\xbb\xbb"\xd0?\x01\xab\xee\xe5\xc6?\xe2\xbf\x9d\xd1$\x07L,\xf1\xbfE\xb5\x11p\xa8u\xee?L\x95\x99\x9b\x10}\xfe?x\xef\xc0>\x99&\xa2\xbf\x9aF\xad\xfa!\xe6\x07@G\xc3\xe4\xf8\x1d\x05\xfe?Li\xfc\xb6)\xe5\xe0\xbf\xbd\xedg3w\xa6\xac?\xd9\x1f\xb2\xc4\xaf}\x01\xc0\xca6\xed\xfc=\xf9\xba?\xe8\xfb!4\xc6\xf6\xea\xbf\x7f \x0b\xe0\xbaE\xe4\xbf\xc5\x17\x07Y\xdb\x84\xd2\xbf\xd2\xc1\xbd\xedgE\xe4?\xef\xc5\xcb\xab\xb5c\xd8\xbf\xff\xc2\xaa\x14#\xd9\xcd\xbf~\xf6\xa4\xda\x8f\x8d\xf9\xbf+l\x95\x80i\xed\xe6\xbf\xdc\xa1\xe3\xf7\xceH\xe0\xbf\xfdH\xf46\xf6o\xed?%\x84\xc1\xa3\x1b\x0f\xe2?\xae~\xfe\x9e-A\xe6\xbf?(\xf0\xbf)\xb9N\xf7a\xfb\xe7?\xf2\xf3\x08\x85G\xcd\xf4?=#:P\x93\'\x06@_\x99|H>X\x08@\x85\x11\xddzh\x04\xf4?_b\xa7\xc2>\xb5\xf6?O;[b]h\xf5?\xbf\xdb\xe5v\x9a\xeb\xf3?\x9c\xb0\x926\xa5\x15\xe1\xbf^6\xe5i\xa9\x83\xf1\xbf\xdc\x14\xd3C\xe3$\xf5?\x9d\xe8t2\xbc\xc3\xd0?\x16:L \xa9G\xbe\xbf>\x06og\xd7*\xe4?\xb1\x84\xc67|D\xc0\xbfj\x98y>v\xe7\xdd?Tp\x1a~\xaa\xec\xec?!\xd5>\xd6]\x15\xf1?A\xcf:\x8bR\xa6\xb7\xbf\xc8\xf8\xa2d\xdf\xc6\xe3\xbf\xca\x94\xb26r|\x8e?\xb7\xbb\xb7\xbd\x0b\x9a\xed\xbf\xa5l2;\x8b\t\xe3?\xd9"Pz\x9dF\x05\xc0\xcd\x15?\xa2\x96)\xea\xbf\x13\x04\xa4R\xefk\x01\xc0\xcb\x12cP\x9f\x05\xa9\xbfB\xa2\xaa\xefl\xfc\xad\xbf\x81i\xa7o\xeb\xa0\xff?\x05+1\x03A\xc1\x0b@\xa0\xda\x1e~\x1b\xa8\x00@\xc8\x17\x9b{,`\x12@\xcb\xa7\x99\xca\x03^\xff?\xee\xf3\xc5D\x0e\x82\xff?4\xad7\xfa\xdb\xb2\x08@\xfb\xe6\xe8\xbf.<\xeeM\xce)\xe8?Q\x9d\xa9}\x8b\xe0\xfa\xbf\x12T\xea0\xdd\x97\xce?=\xbc]\xf5\xfaW\xe3?\x04>g\x97\n\x08\xf1\xbfT\xe8PK\x0f\xda\xe4?\xce\xc6\xddu\x8e\'\xe1?\x0e\xb8f\xebY\x88\xe0?9\xe7o\xf7\xef_\xeb?\x8f\x9d\t\xcbzI\xf8?P\xd1\x8e\x07\xb08\xc1?\x80\xe6}\x95*\xcc\xdc\xbf\xce \xed\xaa\r\x0f\xf5?Hl9\xc2\xee\xbf\xf0?\xab\xc1\xba_y~\xec?tP\xddj\xd4\xbf\xec\xbf*M\xc5\x19<~\xd6\xbf\xcb\x17\xc4\x16q\x8d\xe7?\x8f\xf9\x9b\x0b\x143\xc7?\xdeg\xe5\xfa\r)\xe8\xbf\x11\x91$\xe5\xceR\xe8?\x93a\x9b\x18\xc2\x8d\xf0\xbfJT\x86HN \xec\xbf!B-\x02\xdd9\xf8?\x04QYQ0\xf6\xc0?\x9f\xab\xd52x^\xda?\xc4\xb61Iy\x87\xfc?\xf7\x82\x87\x1d";\xfe?\xd7@p\x9f\x85@\xd3\xbfw:\x98e\x84\r\xf6?\xbb\xc8k\x89j]\xd3?\xde\x01\xe4\x9c\xa9\x9e\x90?\x85\xfa\x8e\xd8\x1d4\xe6\xbf\t\xbc\x8c\xbf\xb9\xa5\xbd\xbf\xad\xa7Pq\x04\x0f\xf0?ql\x89G\xc43\xf1\xbf\x03\x1a\xb7\x938\x15\xf7\xbfY\xc1D^\x1brq?\xac\x1f\x118\x0b\x1e\xe5?\xc0#\xe6\xf6&\xe3\x00@L\xca\x9c-5;\xf2\xbfq\x1b\x19\x99\xfb\xdb\xfb?\x85`&\x82M\xad\xf2\xbf\x8b\x06\xe9q\x7f\x89\xcd?f\x8c\x19\xa7\xd7\xf2\x87?\x1a\'\xff*h\xc2\xde\xbfi\xe6\x9a\xee\xe9\x92\xd4?\x073>+!\x94\xc0\xbfR\x85\x13\xe0\r\x04\xdf?F\x07.\x9a\x1c\xef\xc3\xbfm"\'=\xe1F\xe4\xbf\x878\xa4R\x1f\xc7\xe3\xbf\xf8\x82\xdd\x9d\xb0\xaa\x9d\xbfg%\xaa\xfeu\x1b\xef\xbf\xa7p\xc3\xa2\x19\xad\xfc\xbfQEU,\xe4\x8f\xdd\xbfHQ\x95\xa8\x99\'\xde\xbf\xc9\xe9o\xee\x0fx\xd9?e\x14ec\x9aP\xe6?e.\x18.\xf8\xc0\xf3\xbf\x84sr}\x1e\xc1\xee\xbf%\xe4\x9c:\xe4\xbd\xb2?\nC}\xfb\xef\xef\xee\xbf\xe2\xa4u\xbd.L\xf4\xbf.\xdc\x0bvZ\x89\xd2\xbf\xde\x05\xb4\xb5L\xfa\xd7\xbf\xae\xd5I^\xfa\x07\xe4?\xbdB\x98\xd8\x0b\xa9\xd4\xbf\x1d\xc7/ \x81\x14\xc2?[\xf9\xf0\xa4\xcc\x04\xc9?\xa2\xd42\xb1A\xf1\xfe?\x1b)\xac\xab\x07\xb2\x85?\xfd\xcc\x84\x11+\xd7\xf9\xbf\x8d\xf1\xdfT\xfe\xea\xfd?\xed\xda!uz\xad\xf1?"\x89z\x9e\xb4\xec\xe2?\x1f\x1a\xeao\x144\xe8\xbfr\xf6\x88\x04\xc40\xea\xbfoQlB\xa0\xd3\xd6?7\xf6Z\x93\x1a5\xf4\xbf\xbe\r\x7fV,%\xed\xbf\xc5a\x0f5\xec\x81\xd8\xbf\xe6\xf1\xa8|\xbd\xca\xd8?\x11\xc8L}\x07s\xf4?O\x17\xa6T\xb2\x0f\xf8?\x18W\x89&\xa6^\xe7?\xf7\t\xd6s&\x8e\xd9?;z]\xdeu/\xf0?^ _$\x86\x19\xe1?\x8d\xe7dC\x8c\\\xda\xbf\x13\xeb\xc1\x1e\xa8\xe1\x81\xbf\x00"?\xfa\xd1e\xc6?_\xb1\x19\xe6\x81\xbc\xd6\xbfb\xe3r\xef2\x9a\xe5?C\x9c\x07Q\x07\xbd\x0c@\x15\xa2m\xec\xb4x\xf8?=^\x0b\xf8:q\xe1?\x8e\xc3G\x1e\x90\x8f\xee\xbf[\xd9\x13\xd2\xb8\xb4\xf2\xbfDx\x1fj\xd6R\xd8?\xd3\t+\x12y\xd7\xdc?\xb9~a\x91\xc1\xe6\xe3\xbfQ?\xab\x14\xfc\xe8\xea?\x0c\\f\xac\x97L\xe3\xbf8\x9e\x9b\x10\x08\xa9\xba\xbf.\xfa\xce\xa2V\xca\xe4?4\x90R17\n\xde\xbfb\x83\xa2\x8a\xcfU\xdd?\xb5\xcck\x94\x04O\xd5\xbfz\xc2\xee\t\xa8b\xeb?\xdf\xd7\xee\xfe\xa9l\xe0?\xa5\xffw\x10[i\xc8\xbf\xc8Y\x11\x94\xaes\xf3?l\x16T\x11@:\xfc\xbf\xa2C\x04\xd1\xb91\xfe?\x12Nix\xb0\x8e\xd5\xbf\xe3\r\x06\x0e%\xb7\xfa\xbf+\xa2\tJi\x0b\xf5\xbf\xaci}\xd4\xbew\xff?\xb0\x9b\xe0\xc2\xc0\x90\xd0\xbf\xea\xa85\x1a\xed+\xcf?\x19\xb7\xb3-)\xe9\xe0\xbf\xf4|\x18\xa6\xeeP\x07@\x1d\xbc\xc8Z\x97[\xe6\xbf\xa8\xab\x82\x92YM\xeb?\x16\xaf;2\x8c\x91\xc3?\xd8\xef\'\x9fz$\xf3?\xbb\xe2\x82\xeb\x96\xd9\xf1?\xaej\xd1"\x08\xa1\xd1?M\x16XJ\xcc\x96\xe3?C\xbb\xf8NWQ\xf5\xbf\x08\xe1{\xeaN\x03\xf1?\xe9{B\xf9\xe6\x07\xbd\xbf\xc1d\xac\xce&\x99\xdf\xbf13a"t\xc5\xf9?k\xb4\xe6\x7ft\xb4\xe5\xbfl\xf5x\xa0\xb7\xbb\xfa\xbf@8Cm\x87\xc1m\xbfj#\xbd\xe1\xef\xcb\xe3?h<\xbb:\x03@\xd6?\xaf\xb4m\x8f\xabP\xd9\xbf\xd1<7\x19x\xac\xf8\xbf\x0eS\xaa\xfe\xd5\xc1\xc8\xbf\xad\x86U>\x86+\xf9?\xe9K,\xf1\xba\x04\xc1?\x1dM\xc1\xb58\xf2\xf9\xbf\xc8a\xfc.\xa8\xea\xfc?\x83\x8b\x9a\xf0\xc2\xb1\xef\xbf\xd9&\xa8@\x03\x8c\xd2\xbf[N"\tE\xc1\xd2\xbf3n\x8eo\xd4\xda\xd5?@\xbd\xd8\x04\x0e\xeb\xec?f\xdc\x08\xfeh\xb9\xf4?V\xd2\xde\xe6\xcf>\xcf\xbfH}Hb6:\xd3\xbf.Z\'\xa9\xc1\xa6\xf6?(!1\xf5\x08i\xe0?t\xb57\xaf\xb8\xf0\xd8?\xcd@\xfc\xc1O\xc9\xf5?\x13\x0e]\x80\xdf(\xe7?\x96\x1dd$\xb6\xb9\xeb?n\xbc\tE\xb70\xf6\xbf\x89\x08\x95\xe3__\xe1\xbf\x80\x13\xdb\x1e\x8eu\xf5\xbf.XN\nt/\xeb?\x04\xd7J\xb7\xfdT\xd1\xbf\xfeK\xf7\xe3\xed\n\xf4\xbf\xa1b\x11n\x07\xe8\xed?E3\x14\xdcR\xe6\xed?o\xd0\xa2\xc6\xa5\xad\xe1?\xef3\xb6\x863\x05\xee?W\xac\x96\x83\x8fk\xfa?2QzS>Z\xda\xbfa^\xf9\x94\xdd \x01@\xd99\xff\x91[}\xf5?\x84\xf1\xd5\x00\x91\'\xee?\x14/g\xf8\x85\xc7\xe4\xbf\x1d\xc2b\xf1vC\xf0\xbfp=\xd7\x13d\xc8\xf0\xbfD\xb95\x94Oc\xed?\xa7\xbb4\xdfZ\xe5\xe6\xbfV\x80\x87\xbdW)\xed\xbf\x85\x0eAg\x1b\xae\xe7\xbf\xe9\xd4\xa0\xd4\xa1\xda\xb2\xbfXP\xba\x91T\x95\xfc\xbfi\x94\xbbr\xe7\xf5\xc5\xbf\xed\xc8\xbe\x1f{2\xe8?\xc9\xaaw\x01\xf2\xe6\xed?g\xfa\x98\xdb\x93\x8f\xdc?z%>)g\x03\xfe\xbf\xfa\x878\xa8\xf4\x80\xcb?\x128\x8f9H0\xf3\xbf\xc8\x8f9f\xa6E\xe2?\x0fq\xc6@\xaaW\xd8\xbf\xb8\xd0voyJ\xfc\xbfL\xaf\xe5\x80\x88\x1c\x96?e\xd3<\xaa\xc0\xde\xeb\xbf\x92u\xcf&\xe2\x02\xb3\xbfW\x1f(\x8c\xef\xf7\xdd?E\x1e\xcd\xbb\xbf\n\xad?\xc7\x13!\xe6\x06~~?m\xb7f\x8duN\xd2?\xee\xc8~-\xa4m\xe1?\x10\x02@\x9e\xc8\xda\xd9?\x12\x9b\xe8j\xa1T\xe8\xbf\r\x03d:\x87\xe9\xf1\xbf\xf3\t\xfb\x80\xeer\xd0?J\xd8:\xffD\xb3\xf7\xbf\xd4G\xfb\xf6\x04\x15\xf4\xbf\x95N\xc4\xb3|\x9d\xea\xbf\x96\x9e\xd9\xa8\xdd\xbf\xf3\xbf\x8a5\xecjc.k?g\xc4\xd9\x14%\xd3\xe3\xbfuqAe"\xa2\xc9?\x0bK\x83\xd1 6\xc6?\xd2\xd6\r\xbeH\xde\xe7?=\x9d\xe7\xccxW\xcf?n\xd3\x87L\x8b\x1c\xd7\xbfEhv4\xd5q\xf1\xbfX\xe3b$\x1c\xcd\xeb?\xb4x*h\xb2\xbb\xf1?\x138\r\x98o\xd8\xef?;\xe0\xee\x9dG\x0c\xf4\xbf\xe1\x1b \xbe\x8f\xd2\xf1\xbf\xca\x1fbgQ@\xd7\xbf\xa9\x9f-\xe0\xfe\xb9\xf6?}\xf7C\xd8\xd8\xbd\xe7?\xf5\xad\xe7\x00\xbb\x98\xf5\xbf\x11\x80\x81sG\xe4\xf5?\x89\xe3\x89\x8e\xfb4\xf8?\x0cS{\xf6g\x90\xc3?l\x91\xd3$\xfdq\x02@\xf8\xcb\xb1=o?\xf4?c>\x10\x18\x08\x01\xa1?\r|y\x1c\x18$\xec?\xa0\xa0\xe6\xf3\xea\xc9\xfa\xbf\x9b\xddv\x14 >\xe1?\x8d\x8b\xc6\xa8Y\r\xe1\xbf\xec\xd9\xc0\x08\x10o\xec\xbfO}\x1f=\x8a&\xe2\xbf\xed\xe3\xa7L%\x97\xbf\xbf\x94\x8d\x08I\xea\x98\xf4\xbfn\xad\xa1\x9d>\x1c\xf7?\xefT\xc5\xac\x11\x96\xf1\xbf\xfa\x0eF#@\x0b\xf4?\x1aV\x0f\xecg\x99\xee?_A[\x91\xa6\xbd\xe1?U\xb7\xfb\nR\xad\xf7\xbf\x06\xf8~\xc6\x9e\xae\xc3\xbf\x1d\x81\x90\xa65\xf0\xb3?\xe8\x13\x96\x8a\xaet\xe9\xbf\xc5\xeb\xcd\xc5\xd1&\xf9?\x91\x120\xbb\x94\x8a\xbc?G7\xf3*\x9b\x95\xfa\xbf?\xb9\x137\xd2.\xf1?\x9d\x1e\xec\x19\xa9\xb7\xc2?\xadwo}\xe8\x12\xd4\xbf\xe5T\xa4\xa9\x99\x17\xf8\xbfi\xeb"\x15*\x02\xfd\xbf&\xb7\xc8=es\xbb\xbf\xfb\xc7\xe7\xa9\x95\xc0\xdf\xbf\xd2\xb2\xc5\x1d\xb0l\xcc\xbf-\x02yud\x81\xa2\xbfP&\xb1T\xc2\xbc\xce\xbf\xe1\x93\xfe\x1cb\x1f\xc5\xbf`\x0e\xe2\x90\xf6)\xed?\xf4;\xa0\xbex\xd3\xf1?+ \xbf\x03\xd1\x00\xda\xbfB\x03\xc2V\xfb\x1d\x9b\xbfR\xd6>\xde\xda\xcf\xd9\xbf\x93\xc8\xb9\xab .\xd8\xbf\x87\xd9\x83A\x13\xbe\xc2?\xccT\xce\x0e7\xca\x04\xc0\xe9\n\x80\xd1\x9cl\xce\xbf\xb4\xf2\t\xd7\x16\xd3\xb0\xbfa\xa6]4l\xa2\xf0\xbf\x9f\xbe\x9b\x06"\xb0\xe5\xbf\x8cP{\xeax\xa5\xef\xbf\xecE^W\x80\xe3\xf3\xbfuy\xb0(\x0fF\xd9\xbf\xa3\x19\xec\xc1IL\xe3\xbf\xe9b\xe3\x98\x16\xc3\xdc\xbf-m\xe7o\x16\x0c\xcd??03_\x92\xd6\xe4?\x94\x1d\x7fQLI\xbb?\x9du\xde"U\xb6\xaf\xbfm\x80\x8a\xc8+;\xe4\xbf\x93D\xc2V\xd7\x1c\xda?\x8c\xa9?\xe3\x8e\xb5\xec\xbf\xc5\xff\xaa\x13\x1aW\xe3\xbf\xae\xfe\xa2>p\xa5\xf0?c\xb2\xc6(7/\xf3?d\xf4R\x8dO\x1e\xb6?\xd4\xc4\x10u\xa6\x9f\xdd?\x8a\xdb+\xf2J\xd5\xd4\xbf\xa6\x0e\xcd9\xe8\x03\xd4\xbf\x8f\xebL\x97\xc2\x03\xd3?\xe0\x15\x0b$\x05\xca\xf9?_\x1d\xf4=\xac\xd9\xf7?\xd7\x80\xf0\xd3\xcc\xd8\xef?\xb3\x99\xd4\x8cD\x1a\xe4\xbf\xa6N\x804y\xe4\xf9\xbf\x03PG\x1d6\xae\xa0?\x82&\xc1\xdf\xb4\x15\xc8?H\xd3\x82\x1f\x9c]\xc0?\xd3=\xbf\xf0\xe6\xb9\xe0?\xee\xca\xd8\x8fM\xb2\xe9\xbf"\xfaV\xd8\xbc\xec\xd9? \x9e\xb5l\xca7\xe8?7\xce\x0b\x0c/\xa6\xc0\xbf\xfe"\xcbm\xe85\x01@\x9e\x81zWx\x16\xf9\xbf(\xeb\xa04\xfc\xe8\xf7?5\xc8\x89451\xa7?\xd4\xe5\xfc\x93\x91^\xd9?\x1aj\xbex^\x7f\xf2\xbf@*\',\xcd\xe8\xf0?/S\x18\xc9\xb1[\xec?\xaa\x18\x99\xe6\x01:\xe4?\x02\xf0\x18U\xde#\xf2?2\x83\xdc\x08\xd6\xb3\xa4\xbf\xba+\x01\x93U\xf4\xd3?\x89\xb1\xc8\xd4{\xc3\xd1\xbf\xd9s\xd1x\xf5\x0b\xe7?RR\xe8L.u\x8c\xbfAth\xe9\xd6\xc7\xea?\xea\xa8i\xf1\xb4\x8a\xfa\xbf\xb7I,\x15\xbea\xee\xbfI]\n\xbf;\xc3\xf2?Fv\xa6\xfc[H\xd0\xbf{\xcfj\xdd`\x86\x03@eO\x0b~\x98\xc4\xfb\xbf\x95\x14\xdd\xae\xc2u\x01\xc0\x14\x04\xff\r\xea\xe2\xf5?-\x16\xa3\x94\xde\x04\xf8\xbf{p\x9d\x94\xa1m\xdc\xbf\x06\x1f\x04\\\xf1\xa4\xd1\xbf\x81\xe3\xddG<\xa7\xe3\xbf\xfcT\x1fp!\xd4\xd9\xbfzZ\xecV\x84\xe5\xbb\xbf\xd4\'\x1c\x10\x17\xa3\xf2\xbf\xa0#\xc0\x04\xfdP\xe2?P-\xc5\xcd\xe1\x1a\xc4\xbf;\x0c&\x8b2\xf6\xe6?`\xd9\xf4\xc6\xa8\x91\xb1\xbf\x19\xff\xb4\x96\xdbC\xd8?\x8b}\xbc\xd5O\x91\xae?Ho\xb98=\xf1\xe4?\xd8\x9fQ\xfa\x01\xd3\xbf\x80TR\x05\x90\xc2\xfe\xbf\xc1\x8a\xc9\xc1\x9dO\xb4\xbf\xce\x15v\xcf\t\x1f\xf0?!)\xda\x04\xc0>\xd3\xbf\x96\'\xab\xf3\x8a\x13\xfa?\xfdx\x173\xc0\xf7\xd9? \x98\xc6\xfd\x8c\x84\xdd?\xeb;\x19s\x18O\xef\xbf\x1cA\x0e\x99\xe6\xcc\xe0?\x13L\xc0C\x03\x8e\xe7?5#\xe1\x0e$\xf2\xf1\xbf\xc0+2\xb7\x00\x13\xc4?\xcf:\xa180T\xf6?\x98\n&\xb1)_\xc0\xbf\xcb[#<\xd5\xbf\x00\xc0\x9b\xb9R\xe4\xbar\xe2\xbf.\xfb\x81\x04Tx\xd8\xbf\xff\x19r\xcfa\xde\xff\xbf\x03\x8a\xe1~b\x8a\xf5?\xcen\x9c\x94\xb4|\xf2\xbf\xb4\xca\x18j\x96i\xdc?\xac\x80\xd6n\xd4`\xe6\xbf\xe7H\xd4q\x0c\x99\xea?\xc6\x85\x94J\xc3\xe0\xd2?\xd3\x8a\xd2J\xa0\x92\xf1?\xc1kP\x8ap\x03\x02\xc0t\xef\xe8\xbf\x8e\xe8\xb2\xc6F\xac\xf0?\x90\x1a\xcbc\xd5"\xd4?&W\x90\xc0\xcb\xfd\xf8\xbf\xcf\xbeU\x8d\xdb.\xe9\xbf\xb1\xa9p\x84w\xe2\xe0?.\x93\xc81\xe2X\xf1\xbfY\x19/\r\xdeu\xe8?\xad\xed6D\x93\x1d\xe2?\xd1\xcf\rR\x08\xdf\xe9?\xe5\x15\x8bl%\xb3\x00@*\xe2\xd0\xc0V#\xb2?\xfb4u\x08*\xba\xe7?a\x8b\xe01\xfd\x90\xb9?\x12<\xeaj;\xaa\xf2\xbfu\x05a\x84\xcd\xa1\xe9\xbf\xedJtJb\x15\xd1\xbfVIm\x91\xcd\xbc\xd8\xbf\x9e\xdd\xba&\x88;\xf3\xbf~\xcd\xa8\x92Yd\xd8\xbf\x06\x19\xf9\nlX\xcb?\x9ad\x024\x13\x04\xe4?\xc5sJ\x87^<\x00@\xcbA\x87\x13\xce\xd5\xc7\xbf\xaf\xc1\x97\x82\xf9C\xe2\xbf\xae\x01\x8a1DG\xd5\xbf\xb3,\xf1\xd8\xbcA\xe2\xbf\x9cb\x05@l\xa2\xbe?\xa6\x95=\xb6\xd2\xc2\xf0\xbf|\x1c\xa5\x91b\xac\xe0\xbf\xa2\xc7+\x8f\xdf]\xe5?j\xc3\xadJ\xd7t\xc5\xbf\xd2\x0b&\xac-V\xe5\xbfL\x1e\xef\x12\xde\xfd\xf3\xbf\x06\x9f\x0c\xa8\xdd<\xe1\xbf\x8d\xa2\x9c6\xc9f\xe1?\x9c\xd8t\x88\xa4\xbd\xe3\xbf.c\xed\x14\x94w\x04\xc0\n\x16\x16\xfc\xddo\xf4\xbfGs\xc4\xe5\x91y\xf7\xbf?$u\x18}\xe3\xdb?\x02\xb1\xd1\xf0\x8d\xb9\xed?\x88n\xcc\x9f4}\xf7?\xb5U\xf0\xb3\xae\xe2\xf4\xbf_\x16\xf6\xf7\xce\x8a\xe5?x\xb8\x9c\x94\xc86\xa2\xbf9&\x08\xc7z\xed\xd6\xbf\xa0\xb8j|\xdf9\xf0?\xa2Ft;Q3\xe8\xbf8\xd2|+$#\x00\xc0\xae=M\x10\xfc\x8e\xb1?\x8c}m\x82\xe9\xa5\xf3?\xaf;M\x95T\x8a\xf6\xbf\x7f\xfb\xf324&\xf3\xbf:J\xa3\x14\xb1,\xe4?\xe1l#VV*\xee?z%\x9f\x86\xc2\xfa\xeb?\xe0\xb7&\xb5^+\xd9\xbf\xbd\x13r\x97\xa3\x9a\xbf?\xac\xa4\x8e;G\xb9\xc6\xbf\xb8\xc4}Y\xa6K\xdc\xbf:\x0c\xed!\xd9\xd7\xe3?k\x10m6\xc8^\xd2\xbf\x1a1\xce\xc3\x1c\x87\xcc?\xd8\xa8Eg\xadN\x05@\xa4\xe7\x99{\xd9F\xf0\xbf\xfd\xe2\x13e\x07\xfd\xd5\xbfg(\xab\n\xc1\x90\xf5\xbf\xa7v\xa5\xeb\x81\x95\xfc\xbf\x9a\x00\xea\x18\xbc\xd9\xc7?\xd5\x94\x1f$\x9aw\xef\xbf\xd6\xf8;\xda\xf2\x88z?\xa6\xd6A\xe3\x8e\xda\xf4\xbf\x83\x9f\xc7\xdf/\xe7\xd9\xbf\xeb\xc8\xd0\x80m(\xdd\xbf8\xbd\xf0\xf1\xc2\xc8\xe3\xbfx=\xefD\xfeU\xd6?\n\xdfC\xf5\xce\x81\xdb?\xa0\x9c\x13\x94\xf2Y\xb9\xbf\x87~\x14|\xda\xc6\xd7\xbf\xf9\xe5;\xa3*6\xf6?6Z\x96[\xf62\xfa?\xbcjb;\x06\xb3\x99?\xb4.\xab\xc6\x14\x8d\x8e\xbf.\xaf`njF\xcf\xbf\xb31$=:\xb5\xd1?l7\xf8j\xc0\xd7\xf1\xbf,lc\xbabg\xf1?]T\x1dw\xe6:\xe5\xbf\xa9@\x9d\xc6\x14%\xe7?6\x98 R\x1cw\xd4\xbf\x9e\xc6\xa0\xa2\xfe\xb0\xc3\xbfJ\xfa\xee\x17\x06M\xf2?\x91-\xca\xe1\x80\xb5\xd0\xbf\xf5v\xa7V\xec\x08\xf4\xbfV\xcf;\xd8\xab\x8f\xf5?\x0b-\x90\xc2\x04\xc2\xf1\xbf\xde\xc0\xc2\xc28r\xfa?\x84*\x9bO\xd2\xf8\x95\xbf\xa8\xa7\x97\xba\xa6s\xed\xbfi1\xd8C\x18\x1c\xf1?\x90\x94Hb\x8f\xe8\xed?\xb4\x08r\xe5\xd3\x0b\x01\xc0\xff\xbf\x89\xec\x97(\xe8?\xd0\xc8\xd4\xd1\xc3\x9f\xf1?4\x16x\x90\xf9A\x00\xc07"\x9fl\xd7_\xde\xbf\xcclW\xacN\xdd\xfe?\x88;\x1a\xb8k\t\xe4?\xc7\x8d\x98\x13}\xc6\xe2\xbfN}*\xf2W[\xd2?S\xc9\xf1\xb0]8\xfd\xbf`\x8c\xdcS\xd5d\xe0\xbf\x01\xf3\xc3?\\\xaa\xf1\xbf\x8d6Fr\x9aN\xb0?O\xa5\\\x99}6\x00\xc0q\xca\xbf\x96E\x0f\xf9?h\x13%\xb4H}\xdf\xbfJ\xb0\x10\xee\xbb\xa7\xeb\xbfL2\xed\xb8\xbdv\xf6\xbf\xe3\x9fW\x01\x12\x17\xce?\xd8\xadR\x80:\x85\xeb?y\xe5\x9a^\xc4\x03\x01@\xbc\xf6\x1c\xf7Kv\xeb\xbfq>\xd2*\xba\xcc\xc5?\x98<>\x11\x04P\xe8?\x17\x80\xb91D@\xd8\xbf-q\xe8\x91\xb9\x88\xf8\xbf\xa6\xc83\x9f\x9d\xe7\xe8\xbf\x8b\xb63=L\xe9\xc4\xbf2+.\xde\xb1\xc7\xdc\xbf\xe3\xd82\'S\xa1\xf0?\xd38\xa8\xdc\xefU\xcd?:\xe1\xcc\x0e$\x1d\xdd?\xc51\xe1\xd2\xd3\x1d\xf0?\x07V\xe6\xb48\xf8\xd3\xbf\x8e\x16\x96^\xacd\xef\xbf-5\xc4\xb6>W\xdd?\x05h\xbf\ry\x1f\xdf?Dq\xad3\xf5\x14\xcf?J\xb7\xab\xa7\xc1\x11\xda?\x16v)O\x96\x88\xd7?\x00\xef~\xc0\x8f\xf3\xf1\xbfk\x866\xdf\x04)\xd0?$\xaf\xfdkK\x86\xde?\xc0B\xe5)v\xf4\xfb\xbf\xe9s\xcd\xe9\xc4\x81\xd7\xbf\xfc[C\xd1\xbc\xc2\xe6?\x05>od\xb3,\xb1?\xd9\xa7t\x17\na\xf0\xbf\x97F\xe1\xb0D4\xe3?\xa5\xe7\xcb\xf3\x1b\x8a\xde?s\xd0+\xb8\xfbd\xc7?C\xb5\r\x1e\'\xd4\xe4?=\xeb 4l\x9b\xc1?\xea\xbcH\xa9\xa3\x9d\xf3\xbf\xe8-\xc5\xeb\x90/\xe3?7Hk\xf3\xef\xdd\xea?K\x807\xc1/\x94\xf9?J[\x06p`\xdb\xde?\xd1J1\xf3\xe5\xa5\xe4?1?\xd6W%,\xec\xbft\x13!WI\xfa\x04\xc0\x97L\x15\xd0\xc2\x00\xf3?\xdd\x14%\xca\x97[\xd9\xbfH\xd7!\xf0F\x15\xf4?\xbb\x96\xe8\xa7\xbe\xd2\xf5?\x8a\xe8\x8f\x9a\xde0\xea?gf#\x83J\xc6\xf7?\n?\\\x88\xd5\xb9\xe1?\x8cF\xd4\xd6"\xc8?sF\xd1\xa1a\xf1\xe5\xbf\x8c}L\x96p\xf3\xf8?1?\x12\x17\xe7\xf3\xdc?~*((\xb4\xeb\xe2\xbfnUW\x9c\xab\xca\xe5\xbfZ\xbf\xbb\xcc\xa07\xe0?@\xde\xd3\x94\x80Z\xf6?\x01?(\xb6\xd4\xfa\xe1?2\x14\x0e(\x16\x18\xc9?\xae\xb9\xd1\xc6\xf1a\xe3\xbf\xbb!\xfa\xa3\x19\xc0+\xbf\x10J\x00n\xefB\x08@G7\xae\xc7\xd0k\xef\xbf\x99\xbc\x7f\x07J]\xee?Qw\xc8\xca\xd9\x06\xbb\xbf\xc2U\xaf\x98\x87\x15\xe9\xbf\xfd.\xda\xa0\x0c\xd9\xee\xbf\xd5\xeays\xf7G\xbf\xbf&\x95\xee8\xd9\xd0\xf0?#9\xaeR%H\xed\xbf\x91\xa2"\x07\x04Y\xe8\xbf}\xf1\xe4Z\'/\xfc\xbf\xadm\x12\x06\xd6\xc2\xe5?\xae\x9cN\xa9l\x9f\xe5?a\xc8"T\xb5\x1c\xcc\xbf\xa6\xfc\x0cZB\xbb\xd9\xbfm\xa9\xe0G`\xb9\xf8?\xa1\x0f\xcf0a8\xde\xbfX\x86\xbdy\x9e\x15\xea\xbfY\xa9\xc9\x07@\xa1\xf0\xbf\x1b\x02/\x84O\x95\xe7?Bi\x90\xa9\x1c\xa3\xb0\xbf3\xear|\xaf\xe9\xd1\xbfFjd\xdb\x9fN\xa4?~G\x15\xb2\x86\xda\xf6?\xf7pq\x87\x8dk\xf8\xbf0\x19\x04\xd0\x9a\x15\xfd?.y\xd5\xf9\x0c#\xd9?\x94\xc0\xb1\xcf\xba\x8a\xe0?f7\xa7\r\xbb\xe0\xf1?H\xc3\xe3/\xe7\xb8\xf4?\xb0]\xdeh\xca\xee\xca?V\xb9\x8eS\xdb\x1e\xf3?\xdcd\xac\x96\xe4\xe7\xd3?\xf4w\x80/\x07R\xd1?x\xed\xaf\xea6\xb5\xb7\xbf?\xba}\xb0\\\xa1\xe0?UHv\xba\xc7\xba\xff\xbf\xd1|\x9eW\xb7v\xc9\xbfE\xab\x0f\t5\xac\xd4?\xb9Z\xe2\x0e\x9e\xb8\xea?U&\x14\r\x17f\xbe?\xb9\xffgs\x96\x87\xf4?\xbd\x9b$\xdd\x16\xc6\xed\xbf\x92\x83\xc8\n\xf9B\xe8?ZK?-^\x00\xf9?\x9f\x9a\xabo\xd7`\xed?\xa4LS9\x8e_\xcf?\x1a\x0b\xbc\xec5\x04\xcc\xbf\xe1\x84\xc8\xb8H\x9a\xc7?z\xd3\xe9@\x80\x8b\xf4?\xdc\xf4r\xac\xe5x\xfb?\xe4Y|\xf7\x03|\xdc?\xa9\xd4x\xf7\xd0s\xca??`\xec\xf7\xd8\xbc\xda?\x00\x1c]PP\xfc\xf4?\xd4\x8a\xd0\xa7\xdd2\xf4?\xc1\xba\xac\xc7\x8a}\xda\xbfe\x03\xbe\xcdw\xb3\xf7\xbf*\xa3\xff\xaa\x8b\x03\xde\xbfHYM\xcef\xe7\xf0?\x0e\xed\x7fM\x8b\xd3\xe0\xbf\n\x03Q\\\xba\\\xe5?Lb\n\xc7\x11\x82\x07\xc0.|P\xad\xb4\x82\xda\xbf\xd7\xbb(}\xd2\xda\xe5\xbf\xbcU 31\xa3\xf1\xbf}\xdb\xb0\x9b\xa7\xed\xbc\xbf\x0b\x86\x81R\x80G\xf0\xbf\x9c|)Z\x0b,\xc5?\xca\xfa\x92\xb9\xe4\xf4\xf3?^\x9b\x85\xf7\xd6\n\xeb?\xbc\x0bl\n\xabd\xf2\xbf\x9b\x9d\xf0\xe1\x92S\xc7?9M\x0c\xb4[i\xe6\xbfu\xa8\xddQA \xef?\xefhc)4u\xfa?\xcb\xcfT4AU\xc5\xbf>\x97\xaaZLq\xfd\xbf\xd4\x06:\xc6\xb8\xb8\xf4?\x88\x03\x0bIT\xc2\xe1?\xe1UJ^\x01]\xfa\xbfLMSwG\x03\xea\xbf\x82H\xbc\xd4\x9d\'\xdf?\xd6(O\xa0~\x8a\xf3?\xf1$\xa1\x9d2\x07\xe6?K\xc6\x9d\x0cb\xd1\xe7\xbft\xbd\x05\x8b\x08v\xde?_1_\xdb\x01\x1a\xf0\xbf\x19\xdde\x82\xcbm\xe7?.(z\x98]g\xdc\xbf\x9a\'\xbc\x7fks\xe6?\x06\x11L\xbc\x80\x90\xf0\xbf\xb4\xd0\xcd?\x92Q\xf0\xbfR\x80&\x06\xdeU\xf7?\xd4\xbfq\xf7\xe7\xb5\xfe\xbf\xef\x1a\xf5VE\xdd\x03@$p\xdf\x9c\x83\x9e\xe8?8\xfd\xa2y\x95\x8c\xe7\xbf\x0c<\x1bwkA\xe6?\x9d\xdfH\xa7\xffA\xfc?\xc8\xba\xa7t\x7f\xac\xe3\xbf\x98\x85\x8d\xb0\x80\x0b\xe8\xbf\nJ\xf3\xe4\xd4\xe0\xca\xbf\xb7\xa3\xd2\xb1\x0fA\xe2\xbf~r\x85\xd5\xac\x95\xf3\xbf\xb2L\x18\x9f\x87\t\xa2\xbf\x00\x87z*d\x9b\xe6?\xfeE\xab\xe2aO\xe9\xbf\xd9XTW\xd4\xe0\xeb?\xaat:\x80\x8b\xf9\x04@\x0f\xc0\xfb^h~\xc5\xbfw\xcf*\xa5"\x0e\xbe\xbf\t\xa7\x06\xce\xa7\x8d\xe8\xbf\xd2mF\x1a?\xa8\xe7?\x96\x9c;\x9e\xb4\\\x9f\xbf\x87\xd68\x86\x03\xfc\xf9\xbf\xcb*k<\xb3~\xd7?RbH\x947\x1b\xe5\xbf\x84J\x9ep\t\x11\xcc?\xf4e\xa6\xbb\xae\xe2\xf0\xbfF5\xa4C\x0e/\xdf?\xe8+\xd5\xabm\x0f\xdb\xbf\x99`6\xb7\xd1\x98\xb7?\x0b\xaa\x80\xbe$\x0b\xce?\xaf\x0c\xe6l\x80\x81\xa1?\x93\x08~-\xe7r\xd2?\x100\x03L\xfe\xf0\xf5\xbf\xd2\xdc\xe0L\xf3\xe9\xe5\xbfUZBq"L\xe2?a\x0f\x05z\xa0\xb0\xf7?\xab\xf0\x89\x81>z\xf9\xbf\xfb\xc9*L6\x80\xf9?.\xb2yMuD\xc4?\x84\xfd\xc8\xcfhU\x00@\xf3<\xdd\x91\xd9F\xe8\xbf"\x14\xfd\xb0\xf5\xac\xf2?\xf4\xe1\xc3\x80\x83\x1e\xeb\xbf\xa9\xd7\t+\x8f\xb5\xf1\xbf;=\'f\x1b\x0b\xf2?f\x0c8\xf2\xd6\xd1\xf1?~\xe6\x07\x84\x99I\xe5?q\x80(\xefi\x1c\xfa\xbf\x1d\xa2\x1cf\x9a[\xf1\xbf\xa0x\xb6\x14f\xe6\xf6\xbf\xf7\x86\xa8\xda\x83x\xe3?R\x89\x1a\xd0\xef8\xeb?\xe0J\xc1\xe1\xf9\x86\xf4?\xf9\xe1s\xb1\x15`\xd8\xbf\xae?L{\xb8N\xd9\xbf\xa9\xd0-\x93\x11Y\xe4?H^\xe5\xfa\xccE\xfb?\xe3\xe1^y\xf9\xd7\xea?\xbd\x87&\xbdT|\xe0\xbfdc\xa6f\xe8B\xe6?\xf5\xfb?\xefh\xba\xf4?\xfd9$\x07\xc9s\xe5?\x88\xc2\xbbn\x87\xdc\xe2\xbfW\xaa\x99\xc5h\x08\xe1?)?\x8a\xa2\xe8\xf2\xb5?\x8bqn$U\x83\xf5?\x91\xe6\xd0Y\x0e\xfb\xe2?\xce\xb8\xb8\'\xc4A\xe5\xbf\x9aZ\xee\xca\x0f\x06\xde?\xad\xe9\x98C\xf2\xf3\xe9?x\nM\x9d\x9dK\xed?\xe4\xb6\xf5\xb6Y\xee\xe2?\xc5O{+Tp\xdc?\x89\xc0\x91o&\xd2\xf4?\x03\xc8o\xb7\xa6\xe6\xe8\xbf!\xf2\xc3\xf9Q\x9b\xfc?\xa5g\x9e]\x0b\xb8\xdf\xbf\xadGe\x12k\xdb\xf7?\xf17\x9fd<\x0e\xff?a9|G\x1d\x1d\xf2?\xbb\x11\xf9\xb1&\xfa\xf1?&\x80\xa8\xae\xa6\xfa\xf7?\xab\xb1,\x18>\x80\xe8?\xd7\xfa\xfeE=\x1f\xd1\xbf\x8c>\x91\xa2=x\xee\xbf+nJfA\x01\xe1?\xe9\xf9\xc5\x9fx\xcc\xc8?\xb0|\xf863\x9e\xdc?\\\xcb\xaeR\x0c\x92\xe2?qh\xb5.+\x91\xe6?\xaf\x07\x17\xa5\x1e\xfe\xd7\xbf,\x98\xa5\xf8?\xb3\xeb\x06\x87\xf0\x8e\xdf\xbf\xe3\x02\xf3i\xbaP\xdd?\x8f\xc4\xcd\x04\\<\xf1\xbf\x03\x02t\xfa\xbe$\x08@\xd1w?\xf6BU\xe7\xbfE\xf7sU\xd5s\xeb\xbf\xaa\xd3\xce\xff^\xb6\xeb\xbf\x0f\xf9\x9a<\xdf0\xfe?\xce\x8cE\xbd\xfc\x0c\xf8\xbf\x0f\x0c#/\x9b]\xe4\xbf{\xa5\x81\x14ND\xf0\xbf6\xeaghg\xce\xf5?m\x88\xf4\xa06\xcf\xf1?\x10 JO\x17\\\xd6?\x89O\'cV{\xd1?\x8b\x98\xeb)n\xf2\xdd?]\x90\xbf\xc8\xeey\xef\xbf\xf1o\x9a\x19\xb8q\xfc\xbfg\x1c\xfe\xed\x14s\xec\xbfs%z\xcf\xf5\x97\xd4?\x88\xcaJ\xe5C\xfc\xe4\xbf\xb0\xd0.\xb1\xb3 \xd4\xbf\x89\xad\x02\xaa\x8d\x86\x03@\xfeo\x8biB\xfd\xc0\xbfF\xda\x0e\x9e\xce\xa7\xf5\xbf\xe3\xf9\x1f\xe6\xab\xc4\xe6?\x81M\xa3\xf8*\x1b\xd0?\t0~\xb3G\t\xbe?.<+S=\xe9\xf8?N\x1f\x10\xd7\x9e\xdd\xe1?B\xf2\xaf{4(\xf2?UEq\nO(\xef\xbf\x16\x07\xb3\xf2\x10\x90\xaa\xbf\x1c\xdb\xa5V\xe1\xd7\xf4\xbff\x17\x13\x9a\x96O\xf6?\x88\x02\xc2\x13V\x05\x02\xc0<7\xc5\xd3\xe2\xb2\xf2\xbfg\xe2\x8cLBT\xf8?*\xa2NW\x12\xda\x03@\x80W\xd7\r\x90\xed\xc8?\x9a<\x97\x02\x14p\xe1\xbf\x94\x02\x9c;\r\x18\xe8\xbf\x8f\xb6\x11ST<\xd0\xbf%\x04L\xf0\x81\x15\xbd\xbf}\\\xad\xd8\xe3b\xe8\xbf0\xdd\xfe{\xde\x88\xc8\xbf\xa9\xae>\x90\xe1\xc6\xec\xbfh*\x94\xa3\xe6f\xf1?\xd0F\xb6Mn\t\x01\xc0\x19\x06\xd0Y\xcf\x99\xfc?\xe7e\xbe\x83\x88#\xed?\xfd\xc6^)y\x17\x05@\xd2\x1a\x9e{\xd7\x93\xe8\xbf^\xd1\x99\xdb\xb4\xce\xf0\xbf\xf5\xcetq,\xad\xe4?$>\xc1\x80\xc7N\xf2?r*`\x0e\x89\xf3\xc8?\x06w\xd1E\xd4\xe9\xc7\xbf\xff[\xd3I\xb9R\xd6\xbf\xdd:\xadq\xd7s\xf4\xbf\xacTw\x18&y\xb3?$\xf9\xf4\xf1\'\xba\xe4?\xae\x81\xdd\xfb\xae\xef\xf0?|\xa2\x05\xd0hE\xf3?\xcc\x88Y\xfb\x9f\xd0\xd4\xbf;\xe3&\xd7!\xd2\xbe?\xce\x1c\xf3&\xe8v\xe3\xbfK\xf2\xd7\xe6\xca)\xf0?0\xa1\xb1\x8c\x15!\xec\xbf\xd0\xcf{\xd4\x01\x9d\xe1\xbf!2\xc9\xd9\x1f\x85\xf1?g\x12\x16\t\xacn\xfa?\xd9\x90\xb0\x1b/\x00\xf6?e,Gt\xc7m\xcf\xbf\x19\x03@^\x196\xf6?\xb8\xdfjI\xc8\xa7\xf4?\x8a\xbcP2l\x87\xfb\xbff\x12\x8e\x1d{r\xe6\xbf\xe5x"\xac3\xc7\xac\xbf\xeb\xab\x7f\xb3\xdc\x86\x97\xbfv\xf2<\xecu3\xf7?\xe4\xd8\xbb\x03\x90~\xdb\xbfT\xe7\xcf\xd6\x04\xea\xf0?H\x8c\x90&Z\x87\xf0\xbf\x9b\xb3\r\xc3\xa0\x9d\xa5?\xe8;=(\x05\xb4\xb1?\xb9\x0c\xaf\x02\x13\x96\xd6?r\xbav\xdd\xeb\x9e\xf7?)x)we\x16\xc0?\x91_\x01\xb4\x987\xd8?\xd1S\xc2@\x85T\xd8\xbf\xa3\xf1\xbf\xd5R\xde\x04"\x88\xdd\xbf\x04ec\xf07\xed\xea\xbf\x80\x93B\xb2hx\xdb\xbf\xc0\xe9{\xbd1\xae\xc2\xbf\x00\xdb;\xe8%\xe1\xdc?G=\x1b8\xed\xbe\xf1\xbf\xeb\xe4q\xdf\xfa$\xdd\xbf\x19\x0e6\x93\xe0\x8b\xe4\xbf@|\xf4\xe1\xcd\xfc\xf8?\xaf\xe2\xba{\xb1\xd9\xe2?\xac\n\x90\x88\x12X\xac?\xb98\x03 \x88\x1d\xf6\xbf\x95\t\x95\xa7\xc98\xe7?\xd9\xb4\x10~oO\xcd\xbf\xb0_\xf4\x02\x1b\x85\xed?\xfdvn\x7fm\x06\xe0?\xce\x1a+\x8b\xcb\xc6\xf1?\xce\xcbi\xd5W^\xea\xbf`\x11\xfa\x0bl\xbb\xcf\xbf\x89\xdd\xf3\xa4\xb6,\xea?o\xbe\x1d\x94{\xcc\xdc?\xc40\xe7ty&\xc2?u;\xa1O\xe7<\xcd?XX\xf8\x00Z\xd0\xce?\x8f\x1en,E\xcf\xd9?\xd7s27\xf8Ru?Z\xbe\xed\x0f\xbec\xc8?\x8e\xd0\x0e\xd9|m\x9b\xbf\x1c\xd6m\xde0\x0b\xe3?\x13Nv\xd4Pa\xb8?)g9}#\xfd\xf4\xbf\xbf\r-\xe6\x84\x8f\xd0?du\xbd\xe0;@\xf4?^\xe0uK\xba\x00\xf5\xbf\xac~N\r\r`\xd1\xbf\xa2\x9e\xe6m\xdbR\xa5?Q\xc1E\xcc\x8c0\xec?\xc4\x84M\xcf\x98Z\xf2\xbf\xf9\xe6\xd3o\xbbw\xc5?8\x0fjy\xd7\xbd\x87?hMM\xb1I\xc9\xf8\xbf\xa7%8\xdc\xa6\xb0\xd7\xbf\x05\xc4\xc6\xf3\xb3\xe7\xef\xbf\xb2\xd8\xd56\xf7\xe2\xf4?\xbd\xb5Y\x02\r\xae\xd1?R,\x0fz\xfd\x07\xd8?\xb4\xa7;\x0f\x02\xee\xdd?\xc6\xe3"W\xddX\xdf?\xd2\xbc\x07\x03(\xe1\xd2?{\x7f\x02\xcf\xec\x8c\xcf\xbf6U\xaa\x03\x13\xde\xf8\xbf6\x87\xcf\x17\xfdf\xe6?<\x91\xf8\x8c\xd7\xb8\xfb?1\x8d\xcb&Q5\xf5?/\xcd\xf8\x1d[L\xd0\xbf\x11\x92\xbb\xb2\x8c\xdf\xf8\xbfUp\x06\xf7G\x9c\xc2?R\xabX\xd4\x82\x15\xe3\xbf\x7f\xa6\x1d\x06\x9e\x9b\xf4?\x89\x8b\xbcq{4\xdd\xbf\xf5\xdd8a]F\xf5\xbf\xa1\xb0t6\xea\xeb\xf1\xbf8/\x12;\xb2\xf1\xf9?\xc2\xee\x8b\r7\x12\xe3\xbf,>\xf5\xf6\x9c\xe1\xd8\xbf\xae\xe2\xf2W\xc1\xab\xe6\xbf\xdb\x8c\xdb\x9f\xe6\x94z\xbf04\x88\xabJ\x01\xc5?\xb0\xb4\xf7\xc0\xdbQ\xd5\xbf\xa8\xdd\x07\xca\x902\x99\xbf\xad\xed\xad-\xbc\xa6\xf4\xbf\x8d\xc0\xbb\x89\x830\xdb?C\x17s\x88\xcbD\xe1?\xaay"`JD\xd8?\xc1\xe7\xf0z\t\x02\xeb\xbf\xc6\x7f\xb5\x93=\xd5\x00@\x8c\x08~\xbaR%\xf1\xbf\xee\x8cs)\xbb\xfb\xd6?\x9b0\x12l\xd7\x06\xe1\xbfT\xcem)\xc0a\xf9\xbfA\x0b#\xa4\xe8e\xaf?\x97\xbe\xfc\x97\xaf\xa8\xf2?\x9b\xad\x8e-\x9c)\n\xc0\xdew\r\x9e\x83%\xf6\xbf\x90\x91\xfb\x8cm\xf5\xd9?\x9f\xee;\xe6\x9c2\xf6\xbfx\xdd\x91\xf1\xa3N\xbe\xbf\x91\xeeV\x16\x01\xdd\xed\xbf@\x88\xd1\x11\x9c\xc8\xe7\xbf?o\xc6>\x04=\x01@\x08\xe1\x97\x90\xc3Y\xe7?\xa1\xee\x85\xfd\xd2\x88\xe3\xbf\xe1bt\x1fj\xa0\xf5\xbf\x90Y\\\xaf\xb9\x16\xe5?gjq\xaf&\'\xc2?\x1fa\xc5\x1e\xaa\xc2\xef\xbf\x01\xa7\xdd\xc235\xf7?\x1f;\'\x10\x12(\xf0?\xe8\x94V\x92\xf9\xef\xdd?X\rP\x91\xac.\x00@\x0ffE\xf3\xba}\xc3\xbf\xb4\xfd|\xb9g\x86\xd0?\xcc\xe3\xa3\xe6c;\xf5\xbf\xff\xe4\x81\x95\x17\x13\xfc\xbf\xfcm\xb2\xeb\xfd\x0b\xc0\xbf\x99\x96\x10D\xa2\xe3\xdd\xbf;\x90IGB\x03\xdf?\\\xed\xde]\xf9\x8b\xfc\xbf\xc7/1u\xf0\x83\xc3?\xe4N\xe1R\xba\xdf\xc3\xbf\xf3B\\#\x18\x1e\xeb?m\xe85\x1d\xf5\x9b\xc5\xbf_\xc9\xf4\xa2@r\xdf?\xfcj\xc8\xe7\xf8U\xf2\xbf\xa8\xf3\x8d\x10\xca\xc6\xf4\xbfD\x0c0\x8e\xad?\xa8?;-,\x9fg\xff\xea\xbf\xbeG\xb3\xd8\xbf\xa1\xe5?\x8f]\xb7\x88\x83"\xdb?;\xe1?C\'}\xed\xbf\x1a\xcd\xcc\xfa.c\x02@\x97\xcd\x05\xa3s\x05\xd8??\x06\xf7\xd4\xbe\n\xa3?\xcd\xb9\xdf\x05\x83\xf6\xc8\xbf\x95\x112%\xd9d\xff\xbf\x92\xc3O7\xdd\xf6\x98\xbf\x06&rb\xa3\xb1\xda\xbf\xfa\xdb5\xf4\x9e\x86\xdf?\xe0\xe5Z\x9d\xde\xf4\xd0?r+\xdao`Z\xe2?\xdbe\x042\x11\n\xd6?\x7f\xc1M\xa9\xb6\x8f\xda?P\xed\x08m\x07\x9d\xf7?\xdf\xb6@M&\xbf\xef\xbf\x07\x8f2\xff\x8c\x9a\xb7\xbf(\xd4T\xb3\xc9Q\xf7?\x9c\x89\xa3RR\x11\xe9\xbf7Ll2\xa1\x96\xf4?,\xa6g\'A\xe2\xd8?\xe8\xa3K\xefu\xe2\xdc?\x88f\xa4;\x8a\xb9\xf4\xbf\xc8\xd9\xcc\xdd\xf5\xd1\xf0\xbf\xa5\xcaY\x87\xcd\x0c\xc8\xbf\xec,\xa9\x97%\x8d\xf1\xbf\x03\x86(\x8f\x9br\xe6\xbfa\xfd\xf1=$\xa3\xd4\xbfu\xff\xc2\x1b\x7f5\xd9?\xbc!\x98B\n\xe0\xe7?\x9de,\x93\x08\x00\xd5\xbf\xa1S\xea\xe4\x00X\xfb\xbf\n1\xe6);C\xd6\xbf\xcd\x92.\xb0t\xf4\x9b?\xf0\xbf\xa2Q,\x7f\xf1?\xfch\xea\xfep\x99\xe5\xbf,\xd0\x0b.\x03\xe8\xe2?`\x87\xe6=n\xee\xd6?6\x9b\xfd`\xa2\n\xf1?5\xeeq\xebW\xbe\xd6\xbfu\xa9\xef\xa9\xd7\x0e\xc7?\xc9\xa3\xd3I\x9de\xe4?\xf5\x8d\x88I%\xd3\xd5\xbf3\x10eI\xd4\x06\xf4\xbfZ?\x9evc_\xd8?\r\xde\xf0\xdd\xe2\xa0\xf3?\xabe\xa0o\xe4\x9b\xf0\xbf\xfbb\xef\x9d\xef\xd7\xe9?$\xd7\x01w\xeaJ\x00@(\x13\x8b4lB\xd7?\xdd\xc8]\xf8\xa0\x15\xc3\xbf\x95\xa9\x06g\x1d\x12\xdb?a\xb4xI\xe2\xea\x00@\x1a\xa3\x03\xa2W\xbb\xff?\xc7\xf2\xebv\xb9\xf1\xb1?\x8f&O\x02\xc6\x85\xdf\xbf\x1e\x8d\xb2\xf6\x0fl\xf6?\x82#IZDH\xfb?\xbbH\x90xt\xfd\xa0?D7\xe4\xb1\xe4c\xd7\xbf.\xea\x18b\xd9 \xe9?\x1d|6\x87Ny\x00@\xc3\xe5(\xd5\xe8\xa4\xd5?\x83\xbb\xe4\x01\x95 \xe1\xbfaX\x95\xe2\x11\xab\xe1\xbf\x9a-h\xa8?K\xd6?\x05\x9b\xbe\x9f\xa4_\xd2\xbf\xe9\xc2\xda\x7f\xcb\t\xe0?\xc1\x91m\x048\xd3\xee?\xdb\xda\xf22\xad\xc6\xf7?\xd5U\xd1\xcb\xa8\xd6\xe3\xbfJ\x8dF\x9b\xd2\xe8\xcf?\xe8\xea\xb2Jy\xff\xe2\xbf\xe0\x0e\xbf\xd8\xbc\t\xef?\xfc]\xe5(\xb3\xb0\xe6?\xa6M\xf5\xb2\xfe\xee\xd3?\xfc\x10.\xcc\x12\xbd\xf7\xbfp\xeb\xd1\xb9\xdb\xfb\xfa\xbf\x04\x8c\x1eH\xaf\x0c\xda\xbfa\xe6\x80\x95\x06\xc2\x01@7\x8e\x86\x03\x90/\xf0?\\mM\xa8a\x04\xc3\xbf[I0\x11)\xc8\xc4?\xe7\t-\xa9k\x84\xeb\xbf\xcdl\x9bS}\x88\xc4\xbf\xc3\xc3A<\xf1|\x01@\xc9gO\xa1\xa2\x0b\xa4\xbf\xde\x8f\x1d/\x8d\xc0\xb6?P\x16\xe3zG\xce\xd9?I5\xe7w[b\x00\xc0\x98\x9f\x08x\xa5\xc9\xcc\xbf\xc0\x86\xde\xc4\x97k\xdd?\x17\xc0\xb05\xc5\x18\xd7\xbf\x8b\xa0\xb4\xef0\xd2\xe1\xbf\xa7\x80\xab\rV\x9d\xf0\xbf\xd3!\xb5\xdc\xf1\xca\x03@\xd5 \xce\xbdK\xf2\xef?\xe0\xa8\xe2\xca\x94\x03\xe9?\x86n\x07r\xc8\xbf\xeb\xbf\x1fyzh&k\xe3?\xe9p\xdcT5\x12\x00@K\x9ds\x12eU\xe8?P\xd1U[\xec\x9e\xf2?\xb6\x12%$\xf2B\xf6?m}\x01\xcd\xe4\xd9\xf8\xbf\x9c\xeb\xb9\x8c:5\xdd\xbfcS#\x0f#\xf1\x01@:\xfaR\xfa\xb7)\x07@\'m\xe3wS\xd7\x05@\xf5\x15B\xedU&\xf1?\xd2P\\o\xd5\xa8\xe5\xbf\xfb\xd8u\xa3\xc6\x92\xe4?\xb3\x06,T-\x9c\xe3\xbf\xc0c=\xcb\x8d\xb2\xcd\xbf\x13b\r\xf5@\xd9\xf2?\x11\xb8\x1b\x12\xa9\xda\xd3?\xbb\xfa\xe0I\xce\xc5\xf4\xbf\xc3\x85\xc1\x1d\x18>\xec\xbf\x05nL`;\xf0\xf8?\r\x88\x80V\xdb\x94\xc2?\x9f\x89\x1c$\xb0\xae\xd4?~\xd1\x8c_\x82\x98\xc7?3t\x84\xd9z\xd4\xfa\xbf)%\x1a\n`\xff\xf1?\xb8\x157\x03\x99\x9f\xff?\xb6\xe6^F5\t\xe1\xbf\xf38\xe3G\xfc"\xd0\xbf\xca\xba\xdc\xad\xd0 \xb2\xbf\xf3\xd8\n\xf9\xa4\x99\xf2?\xec\x81v\xb0\xce\x95\xd9?\x93\r\x97\x9b \t\xf0\xbf\x183A?\xa7n\xe0?\xb0o\x07\x90\x83\xf9\xee?\xd5pR^`\x15\xef?\xf8i"\xccn\xfa\xfa?\x93\x07\x9a\x1f\x1f\xc3\xca?.\x80\xf5\x86l\xba\xaf\xbf\xd41\xc3Q\xc6S\xee?\x1e\xe6\xd53\xb4\xb6\xb9?\xcao\x05+\xb1\'\xe7\xbf\x84F\x0b\x80\xcc\xe9\xf0\xbfN70\x9e=1\xf6\xbf7\xb9;\x0b\x00\x97\xa7\xbf_^k\xf5\xc8\xb6\xcd?\x0f\x1f\xe4\xc2\n\x8c\xda\xbfL0\xd0\x89v\x0b\xd4\xbfq#\x91\x19\xe5\xe1\xe3?d\x1c\xcfw\x87\xe2\xcb?\xbbT\x12\xd4D\xc9\xe6?b\xd3#\xc9U~\xc2?\xbd \x89\x86\xe2\x1a\xd0\xbf\x1d2=\xddk\xf3\xf4\xbfv\xd7!g\xac\xb1\xe8?\xc1\x14!\x8b\x06!\xec\xbf\xeb\x9e\\b5\xa6\xde\xbfwv\xca\xbf\xe1T\xc6\xbf\xabb\xcehW\x16\xdf\xbf\x02\x8e\xea\xae\xd6|\xc4\xbf\x05\x8d\x10\xacL\x07s?+\xa7\xdf\x16\xab\xf3\xad??\x12k\xf5\x05\xca\xed?\x0b\xcf\x9a\x98\xda\xa9\x01@\xf0\xb4?\x12\x1f\xe1\xea\xbf\xdas\x0fe\xe3\x1c\xc8?\xf7\xb4q\xa9\xed\xcb\xbd?\x9a>\x9c\xa5\xc8\x00\xe5?O:\xe1\xd6\xcc\x15\xb9\xbf\x10\xc2\x11\x0e8\xd6\xdc\xbf\x0e\xe7\xfc|\x85\x01\xe3\xbf\x13\x92\xa7\xbc\x14l\xfb\xbfs\x03\n\x1e\x82p\xf9?\xe3\x05\xdf|\xe3\xfe\xde\xbf\xc1\xd4\xa0"\x07s\xf1\xbf\x02^T\xf8\xb57\xe5?\xc5g$\xc5 \xfe\xbf\x87\x12\xcf \x0c\x98\xf4?\x97\xfb\xbbK\xc7\xa6\xc9\xbf\xa0q=\x08\xbcK\xeb\xbf\xc785\xbd\xd7\x8e\xdf?`\xfe\xa9m\t\x06\x05@\xddHA\xc8\x08\xdc\xd1?\x0b7\xb3e\xf9a\xd8\xbf[\x92\x08\x83\xa1\xde\xed?)+~\xb6\x99N\xf7?&\xf6#Y"\x98\xfc\xbf\xbb\x89\x0f!L\x92\xe0\xbf\\\xf8\xda3\x0f\xdb\xe7?\xf2u\xb9fSH\xdb\xbf\xc2Ik\xf1\x0cO\xf9?N\xb1\x8b\x93^\x86\xf0\xbf\xa2\x1c \xb6\xbd\xd9\xcd?W\xb1\x85>*\xf2\xdd?x\x90\x03\x02|\xaf\x89?PW\xf2"-8\x00\xc0~\x9b_n\x06\x1a\xf8\xbf\xe0\xe3q\xe0l\xd3\xf3\xbf\xebY\x04\x82\x11B\x00\xc0\xc6\x00\xc4\x8d\x85]\xd8?v{\xcd\x12\xb1!\xe1\xbfK\x9c\xffzR\x01\xfe\xbfT\n\xbd\xf0i\xe9\xe8\xbf\x1f\xf8^\xf8v\x8a\xf8\xbf\xf6I\xe6\xdd\xd4\xda\xe1?~-\xfav\xda\xa4\xdf\xbf\x16\x8d\x1f\xea\xc5\n\xcc\xbf\x0b>\xf27\xf8n\xa8?n\xee\xa9!q\xdf\xf5?t\'\x99\x8b\x05\x1c\xfa\xbf\xc7O6\xd0\xf9\xa7\xfe\xbf9\xc7\xd5\xd4\x15\xb4\xc2?~\x00:\xfa\xb4\xed\x00\xc0T\x04\xbcCv\xa3\xce?-K\x91@)\xa0\xf1\xbfh\xf7%_\xdb\xa2\xb6\xbf5B\x0b\xd9)\xd9\xdb\xbf2|\xff\xbb1,\xe6\xbf\xeb\xb1\x80\xe3\x84)\xd4\xbf\x10\x9514\xb3\xf8\xf9?Ux\x1d\xef<\x9d\xcd\xbf\xbc^\x80\xa3\xac\'\xbb?)RG\xc7\xd1W\xf2\xbf\x13d\x97\xdc\x99\xb0\xe5\xbf\xa2\x12\x07`C\xb3\xe5?\xe5\xf5\xf9O\xc8\xd9\xf2\xbf\xdf\xfd\xbbZ\x01\x83\xea?\xcaay\xdf- \xf0?\x00\xbaQ\\nf\xdf?\t\x9e\x1c\x90\xe3\x18\xde\xbf|E\xc9\xe3x\xb9\xc6\xbf\xde\xb8\x90l\xbc\xe9\xdc??\xb2\r\xc5l\x91\xe8\xbfF\x1a\xa70\xf5\xac\xdb\xbf\r\xd6L\x88\xaf\xb6\xb4\xbf\xa9\xbe\r)\x92\x9a\xe9\xbf\x05u\x15\x8eeU\xf2?*w\xb5c-\x10\xf2?\x1f\xd6W\x95\x13%\xe9\xbf\xf3\xdb\xae\xb9\xe1l\xd8\xbf\xf7\xe4\xa2\xc7\x80\x84\xeb?\xa9\xe8\xf2\xde9/\xa0?\xbc:\x10\x90\x82\xaf\xf2\xbf\xa2\xdb\x92\xd1\x10\xc2\xf8\xbf\x18\xcd\'\xcf\xed\xc8\xf2\xbfV\xa6)h,\xd8\xfa?\xdeOpn\x11\x1c\xf1?\xed\xfag\xdf\xd3n\xee?\x0b6\x16\xfff\x88\xd2\xbfN\x9c\xad\xdd\xc6\x01\xe4\xbf[c\x8e\xff\x02\xde\xcc?H0\x7f\xaer$\xdc?S\xea\xa63\xa8\xd0\xf0\xbf\xbb\x84\xa0\x15\xd9\x94\xe9\xbfn9\xa0\x1f\xc8\x89\xf5?Car\xa5>d\xdf?rt\xea\xf2\r\xac\xaf\xbf\xf1\x8c\xd4+q\x8e\xe3\xbfvR\xf3\xb5j#\xe8\xbf\xbf\xcfB\xeb-3\xed?2\'\xa9\xd4\xbd\x04\xe7?\xcb=\xb6\xfa\x05K\xf1\xbf\xc57\xe2\xeb\xecI\xf2?\xaa\xaf\xfc\x11\x8d\x87\xb1\xbf\xde!\x9a\x83\x15}\xe3\xbf\x1e,\x7f-\x19\xd5\xee\xbf*p\xfc\xecbG\xdf\xbf?\xe7#\xb2\x06\xd7\xff?\xd1\xf52]\x0f\xb9\xc6\xbf\xc4BZxg\xa9\xe9?\xc8\x97\x1a\xea\xbb^\xe8?e\x13\xd7\\J\x9d\xd3\xbf\xf9\x83l\xbc\xe9\x90\xf9?\x17\x12\xb5A\xafF\xce?\x16.a\xd9i.\xed?\xcf\xe0I/\x13&\xea?\xe0Zu\xfc\xe3\x1b\xfa?#\xf1Y\xe4\xaf\xd5\xeb\xbf\xaf\xe01\'y\x17\xca\xbf\x00{\xa3nq\x1e\xc8?\xf3\xdd<\x18\x93\x96\xec\xbf\x85\xce\xfb\x9b\xc7n\xf6?\xfb{\xea\xba\xe1\xad\xfd?\x81diU$\xa0\xb4?=\xb2\x92\xaf\xf8\x87\xe0\xbfme\xa6\xcbc\x04\xe5?\x03*0\xaa\x9a\xfc\xc4?\xc6^r\x03\x12\xf9\xe7?\x16\xae\x9e\t\xe2\x02\xed?\xd3\xb2tX\xeaF\xdd?<\xf1\xad\xdf\x8e\x13\xe0\xbf\x00\x9at\xe8\xc6\xcb\x00@ \x02\xa2\xee\x8aZ\xdf?\xf8\x04\x97q\xed\xf2\xf2\xbf\x1e\r\x08h\xcf\x03\xdc?#2\xcaV]\x1b\xe4\xbf\xbcsblX\x88\xbb?\x84\xc8\xe6\x87\xbdF\xb5?/\x1a\x82w\x95b\xf2?G\xba\xe3\xc4\xebK\xea\xbf\xe4\x88\x94\x16\x9b\x17\xfa?\xceK\xc2\xee\x84&\xfd?=\xaa{\x93\xe8O\xf8?\xc1\x89"\x1c.E\xc1\xbf\xd6\\\x07\x03\x7f\xc5\xf2?\x11HA\xd9\xbbL\xe8?\xc2\xd1h*\xe8M\xf3\xbf9\xbd:\xe9\xe7q\xc8\xbf\t=\xea\xbao\x82\xd6?\xbe\x0e\x1byT\xc5\xe9\xbf\x97C\x12^L^\xf1?A9\x8e;\xc8\x87\xf9\xbf\x00d\x84\xa4&\xf8\xe0?e\t\x03fe\x82\x8e\xbf\xeeY\x80\x8f\xb2\x94\xf0?\\\xac(\xe4\xb0\xdb\xd3\xbfk,6\x8e\x99\xdb\xe7?6\r"\xaf{\x0b\xc4?\x00s-\x13/)\xf2\xbf\xef\xaa\x94\x8d\xb0[\xb3\xbfA?\xa8\x04\xa5\xdd\xf2?Ife\x8d\x86s\xf6\xbf\xcb\x14\xc9\x9ai\x18\xdb?\xb9\xc9\xbe\xf0\xe0:\xe0?R\x9a\x87\xd7$?\xa1?-\xd5XE\x7f\xf5\xd7?\x94l\xf6S\xb1\xbd\xf4?w\xe1-"\xc0R\xd8?L\x8aF\xab\xec\x8e\xc6?\xde\xa14 !7\xd3?\x97\x07\xa8\xa0\xffO\xeb?\x03JK\xfca?\xc9?\xcf\xe0\xfc\xc0\xca\x0b\x12@|\x00\x99k\x96\x1a\xeb\xbf\xa91\xde\x8b5{\xd9\xbfw`6p\xb8\xa7\xf1\xbf\xf2\x1f\x0fM/\x8f\xe2?\xa0S\x1d\x0e\xd3\x88\x00@%/3\x08\xc8Q\xf5?6Y4\x86x\xcd\xf6?S\x05\x91\xaa\xc6(\xf2?7\x81\x13\xcbL\xfd\xd5\xbf\xfeM9\x88\x95\xb1\xe2?\xe7q\xc1\x8cm\x90\xec?0\xc29\xb1w\x97\xe5\xbfs\xfa\xc7N\xac\t\xf4\xbf\xc6W\x03{\x10\t\xf1\xbf\x01Q\xf4\x8f\x1c\x82\xe8\xbfa/E\x19\xbag\xf5\xbf\xb8\xfdG\xdb\x14\x9e\xd6?zrWRW\\\t@b\\\x938\xf7\xe7\xc9?\xae\xc0\xcdW3\x95\xfa?\x1f\xaf"\xf3\xa4\x87\xd0\xbf\xc1E\xb3y2\xf2\xf1?j&|\xd1p\xc9\xd5\xbf\xb7\x9b\xbfX\x0fc\xe0?\xf8\x03$\xbe\xb6\xea\xe5?\x1e\x15O\x0bJ\x14\xc8?\xcf\xf3\xbdqOx\xe4?\xa4~\x129\xffx\xc2?\x8fu\xc0\xae\xbb\xf7\xdd\xbfp\xa4p\xc8\x8b\\\xe7\xbfZ+\x1e,\xc6\xc6\xfb\xbf\xcd\xa1\xda\x1f\xa7i\xe4?k\xaa.iI\xe1\xff?\xa01\xdd\xca\xe2\xa4\x00@\x17\x03\xc6\x10q;\xfc?\x93[\xc8Xk\x97\xd7?\x98\x91(<\x8b&\xd0\xbf1][[\xb0\x01\xfe\xbfb\x93\x9f@\x04\x9e\xf7?A\xd2?`f\xba\xf5\xbf(z\xf9MI\n\xfc\xbf\x95\xe8\xb61\xae\x08\xe3\xbf\x8b3\x1d\xf7X\x04\xea?\\4\x05]p+\x02\xc0\x00\x13\xbb1\xe2\x8e\xda\xbff;\x9f\xa3#\xfd\xeb?\x14e\x1dW\x9e\xcd\xf5?\xda\xd2&\xfd\ro\xd4?O\xa7c\xed\xaf\xc1\xc4?\xa3\xad\xf8C\x1e\xf5\xbb?\x14i>!M\\\xe0?\x7f\xee\x0e\x153\xfa\xb8\xbfj\xac\x8b\xc7\xb3H\xec?\xc9t\xc5\x01\x15\xb0\xfc?/>\x148\xcc\xcf\xd9?\x12\xbd\x1c\xea\xbc\xdb\xf0?r\x141\xe71\xc7\xe8\xbf\x80\x81\xa4Cw\xba\xc6\xbfh6T\xb7\x8c\x0c\xfe\xbf\xdf\x03n\x19\xe9\x1e\xd4?7\x86\x03\xb0#\xd3\xfd?vA\xae\xfdQ\xad\xfe?\xe1\xcf\xc9Or\x1c\xf9?\xa6\xab\xc0\xd0\xf6\xca\xfe?Q\xde\x91\x92\xfd\x95\xd8?\xc8\xf6\xc2J\xb7\xdd\xe0?\xe7=\x81\x1ce\x15\xe1?x\xdb\x86\x86\x9c/\xdf\xbf\x00\xd0\xd2\x15\xc8\xaf\x04\xc0,\xb4\xe7\xce*u\xd0\xbf\x0c\xfe\xaf\x01\xcf\x11\xf2?\x1e \x95\x0bt\'\x01\xc0\xc9UN\x02\xa7\xf2\x02@i\xb7\xf3\x8b\xc3p\x9b\xbf.*\x91ck\xa3\xd6\xbf\x18\x13\x86\x92)\xbb\xd6?\xd1\x0b7\xfb\x92,\xc2?%Q\xf0:ht\x04\xc0"Do\xe1\x19\xbf\xed?\xf9a\x93\xa7\xed\x88\xd0?\x86\xe4\xa7(\x98:\xea\xbf\xafj\x1c\xdeD\x88\xbb\xbf\xce\x07\x01\xc9\xfc\xd5\xe4?\x00?\x02\xba2\xf2\xed\xbf\n\xf6\x17q\x07\xbd\xf1?\x0bi\x91n\xbb]\xf5\xbf3\xda\x95\xbd\x9b\xd0\xfe\xbf\xb90\xae\xfdf\\\xdf\xbf\xd9(\xb7Z\xf60\xb6\xbf\xfd\xa2\x1e>\xeeE\xf1\xbf\xc1\xc4A\x8d$~\xce?\xce\x83\xf0\x99\xf3\x8e\xe9\xbf8j\x99>K\xb3\xd2\xbf\xc1\xf2\xbb\x8c\x81\xf6\xe8?\xc1+\xd27\xc0n\xf8? \x04\xc5\xfbE\xc8\xd4?\xf4\x95g\xc7\x1e\x01\xdb?\xd8\x7fNL\x82\xa5\xdf\xbf\x00\xb1h\x07\xbbt\xfd\xbf\xaa\xab\xba\xa4\x8e~\xd1\xbfu\x83\xdbd\tV\xf3\xbffHW\xe5(o\xd1?\x88\x9f\xcd\xfdv7\xd9?\xf04D\xb6\x88e\xf2\xbf1-d\x91O\xad\xd2\xbf\xd1(\xbc\xac\xa1y\xd2\xbf8[_\xf2\xfc\xf7\xd2?\x9c\x8dT:!\x8b\xdf?\xc2.q\xf5N\x82\xd9\xbf\xe02\xc1\xa2\x83\xd3\xaeO!\xe8\xbf|\xa0\x02\x05!G\xf1?`v\xd9\xbfQU2\x07\xf1\xab\x03\xc0\xd8\xa3\xb5\x8c\xbc\x8c\x0b\xc0\xd0lgZ\x0cL\t\xc0v\xdd\xea\x1a\xf8N\x06\xc0\x85\xc6:Nk\xf8\n\xc0\xa6\xfa=o\xf0\xc1\xe9\xbfP\xea\xde\xf6M-\x01\xc0\xda\xfc`\x9c\xb4\xaa\xe1?\xeb6\x13k\x06,\xf5\xbf\xb5Wj\xb3\x0e\xf9\xce?\xe8\x85\x03S\xeb\xed\xf1\xbf_\xa2\xf9b\x02\xd0\xb4?\xe0!t\x1e\t\xb2\xe2?ag\xcdR\xf1G\xf8?\xa8\x91\xf7\xa8Q\xcd\xce\xbf\xa9\xfa\xf0\x04\xae?\xc0\xd9\xd9~U/~\xbfP3e\x04\x01J\xf2?\xa54\x06\x92\xb3W\xf2\xbf\x1c\xb6\x9f,&:\xbd?wI\x87qY\x9b\x00@<\xa2\xef\x89_\xf7\xe9?\xa1\xe6\x13\xd4~"\xe5?\x88\x1bZ+\xa9\xf8\xc4?\x0b\xe1\xb0\x9e\xb6\xdd\xfe?\xc2\x10\x9e\x0c\x12\xd4\xe6?A\xb4-Ay\xe9\xf6?\xd4\xbbP\x81\xb5\x17\xb9?\xbfUg\x86\xfa$\xf5?\xf4\xd5a\'\x1c?\xec?\xf2\x11Y\x94\x8b\xb0\xc7?\x9ds\xbd8z=\x04\xc0\x086\xf0g \xd8\xbc?\xa9\xf4\xf2@\xcbs\x00@FT\xa00\xaa\xc3\xfd?\xc67\xcf9/\xd0\xb1?\xad_\xaak\xb6\xa7\xfa?R9\x91y\xffv\xd3\xbf\x87\x9a\x1e="~\xe2\xbf\x1d\x96\xdc^\xd3Z\xf5\xbf\xb8\xd5$t\x17B\xde?5\xb0\xeb\xcf\xd1\xa3\xf3?N\x9b\x0b\xf3[o\xbe\xbf\xf0Kw\xfd_\xd7\xaa?\xc0\xa3\xc8{\x01\xb0\xce\xbfl\x8f\xe4H\xb0\x9d\xe9?3\xc1n\xe3W\xae\xfa\xbfy\x98\x9ah\xa3\xf2\xf1?Z/\xf1\x0b\x81z\xed\xbf\x9d\x8fyg\xe1\xfe\xf3\xbf}p\x8a\xef\xc5\x86\xd0?\xd4I\xb7\xbdx\x16\xe7?/\rS\x98l\xd1\x00@\xfeM\xb5\xce\xe4\xeb\xea?(\xad\xa2\tW\x8e\xde\xbf\x9dQQ\xb1 \xd3\xd4?V\x06\xf2\x8b\x81\xe4\xec?\xd8\xbc=5\xa7\x81\xc7?\xfa\nf\xa9$W\xe6\xbf)\x82\xf2k\xc0:\xd1\xbf\x07\xe6ea\xa7\xd1\xf6\xbf\xf2\xcc\x84\xa28\xf7\xd0\xbfuy\x80\xc8\xb6\x8f\xf1?\xd6$,\x9b\x1b\x0f\x00@\x81\x0c\x1e\x14\xf88\xd6?\x93a\x10\xea\xf4\xf2\x05@r\x12c\x0c\x9c5\xf5?\x0c\xa1\xb4\xeb\x02+\xe6?\x01\xd4^\xab\xf9m\xe7??\xb0\x1aHH\x1d\xdd?\xec\x9aJ\t\x87\x18\xe1?Fp\xad1\xc7\xf6\xe5\xbf\r\xdb\x14\xeb\x97\xac\xe4?\x03\xa3a\xe7\xf2c\xe4?g\xf7\x92o#\x13\xd4?}\xd0v:\xbe^\xf8\xbf\xa2\xbaN\xe8\xaa\x80\xf8?\x98=^\x8b\xeb\x96\xf6\xbf\xa9\xd4\n\rL\x80\xf2?Xt\t\x82\xea\x05\xde?\x0c\xdf\x86W\x0c\xa4\xfa?\xd1+l\xb4\x90\xd4P\xbf\xb9\xf6$}\xe1\x1e\xef?\x83\xeb\\\x84\xa5\xee\xcb\xbf\xff&\x10\xcb\xb4!\xfd\xbf\x1f\xf4\x97\xe2\x97\x13\xbf?u[E58\xc4\xec\xbfWu\xfcC\xaf\xac\xfa\xbf\xc9C\xdcx\xbfO\xf3\xbfMz\x1e]\xc7w\xdb\xbfx\xfapH\xa5\x9d\xc3\xbf\x97\xf7\x95\x91K\xf3\xd7\xbf\xb3"\xbc\x17h\x1b\x01@\x85\xec\xd2dG\x9d\xea??\xa2\xc2\xc5\x1c\xe8\r@\xeegc\nA\xd5\xf4?\xf9\x00>ny\'\xea?\x8a\xa7\xb9/s\xba\xec?k^\xcd@-\x9e\xb8\xbf\x12x\x14\x1eu\x85\xf5\xbf9,lQG\x8a\xbb\xbf\x8d\xf8\x8a\x11\xeb\x99\xee\xbf\xc9\x05;\xf6Nq\xf4\xbf?K\x1f\xf3ii\xe9\xbf\xaa|z\xf2\xfd{\x01\xc0\x86\x10W\x02\xdcF\xe7?\xbc3\xaf\x92\xca\xc2\x02@\xe3./\x01s\x04\x07@%\x0f5S\x1bg\xf8?mH\x0e\xa3Ix\xb5\xbf\x13%m\'\xe6\xcf\xf1?\xee\xe5D\x0e\xd0v\xe6\xbf\xcf\x91\xa8>z\xc7\xba?\x1e` \x96\x8d\xe3\xef\xbf\xed|\x9ce\x0f\xae\xe9\xbf\xa5\x81\x1en\xf2\xff\xf4?\x01]\xf6\xb2(O\xd4?\xbc\x02h\x16\x0b\xe6\xf2\xbfG\x9d\x8bZ \\\xec\xbf\x97\x965\xcb\x1c\xba\xf5\xbfy\xb8\xba\x05\x12\xae\x81?\x9e\xba^\x05\xdb\xd1\xd6\xbf\x16D\xe5\xa7-Q\xd4?\n:\x1b\x88\xe0\x0f\xc8?\xe5\xe6I\xb8\xa7w\xd1\xbfO![\xfa\x10\x16\xd6?\xd9\x1a\x9e\x1393\xe5\xbf_yK"W\xab\xf2?]\xcbV\x8e\x84\x17\xea\xbf\x9do[\x9b,\xc9\xfa\xbf\xee\xf7]\x85\xdab\xd0?/&\xd1\x84\x7f\xfc\xf5\xbf"\x95<\xaeO\x0b\xf5?\xe7P\x18\x9b\x83\r\xd6\xbfE\xb9\xb5\x1d\xf20\xff?i\xecl/g1\xef\xbf\x13\x9c\xd0?\xae\xb7\xe1\xbf\xdc\x15\xfbO=\xe6\xe4\xbf:\x8fv\x04\x9d\xe9\xd6?n\x03\x8f\xc9\xd9\xb5\xf0\xbff\x9dG\xf5\x9cA\xdf?\xc4F\xe88\xf9\xb2\xab?\x8d\x10\x1d\x86\xe3\x9f\xb2?\xdc\xd4\x03c\x1d\xd5\xf1\xbf\x0bQ\xa2Pj\xc5\xd1\xbf\x91\x89\xe1\xbb\x16\xf2\xf1\xbf]\x1f\xe4w\x81\xe7\xfc\xbf\xd3c\x82s\xd2^\xf0\xbf\x9e\x9f\x174\xb4\r\xb4\xbf\xef\xc3*9~\x87\xf1?y.\xd2\x85Wp\xe2?\x9f\x84\x80j\xa1\xb3\xf3?\x1eXyJ\xb3\x13\xb0\xbf\xc8\xd8\xf8\xa0\xa8\xc9\xda\xbf2\x91\x02\x15\x81\xdf\xf4\xbf"C9\xed?}\x03\xc0\xfd\xc7G\xb5\x0e\xbc\xea\xbf\xaf\x0c\x82\x18\xebr\xcf?pS02\xa0\x99\xe7\xbf\xcb\x8f\xf1\x8d\xb8\xcf\xf9?\x1f3\x0f0\x16\n\xed\xbfy\xc5\x1d\x1a\x1d\xe5\xd0?\xdf\xc9b\xf4\xe6\x19\xd7?\xb1\\\xb9\xfd2\xc3\xb7?\xd3.\xe0\xd8$\xc6\xec\xbf\xf5]\x8f(X\x05\xe4\xbf,\xa9\xdf\xf8\x9cW\xe1?\xaa:#\x18t\x0b\xfc?\xe9\x80\xff\x9d\x83\xe9\xe4?\x8b\xa2\xf9~\xbb3\xde\xbf)\xe1\xbc\xd9K\x10\xc7\xbf\xcf\x81\n$\xbb\xc0\xd9\xbf\xcc\x96\xe5\x98o@\xd5\xbf}\x0cI\xb1\x7fS\xd3\xbf\xad\t82\r\n\xef\xbf\xff\xdc\xcb\x9d\x05\\y?\x01$\x07\t\x106\xc4\xbf\xd9\x7f]C\xc0\x94\xb6? e\xba\x9a\x83\xb9\xe2\xbf\x04\xe2\xd6\x95\xcd\xce\xe4?y7\x02\x87\xa2O\xee\xbf\x90w\xa7k\xf1\x96\xc0?p\x90\x03G\xcc\x0e\xb2?\xc7\x85#\xaa\x01t\x93?`\xd6\x1eI\xec\xb5\xd0?\x05(2B\x8f\x96\xd7\xbf\x1e2c\t\xf6\x8c\xa3\xbfAd\t\x15\xcfz\xc0?2\xe4@\xc7_i\xd1\xbf@\xb5\xc3\xf5\x9a\xc5\xe8?T\xaeh\x9a=/\xf0?\xa2\xcf\x1d\x05\xf5-\xf5?\xa8.\xcdi\xa9\xab\x84\xbf$\x94t\x88n\x1e\xfa\xbf:\rL_\xe3\xbe\xf1?\x06\x0boo-\xd6\xff?\xee\xfd\t\xc3Q\xc5\xdb?\xbd\xc5\xc2B\xa7k\xf5\xbf3G7\xf5\x0f\xd8\xf1\xbf\x02\xa1S\xbb\xdb\x11\xd3\xbf\xb1\x0b\xfb\xdcQ\xe1\xed?\x8d\xb5^\xcdUT\xe8\xbf}\x15\xa7\xec\x0f\xeb\xc4?\xc1\xb7\x8f\xf0\x1e\x8f\xe3\xbf\xf5\xca5n\x80\xdd\xee\xbf\xf0\xb9\x01\xb3\xdbQ\xd3\xbfsA\x0fS\xff\xcb\xe6\xbf\xb0\xa4\xba\xaa\xees\xe7\xbf\x01\x0c\x07\xd2\x07P\xf7\xbfU\xd8\xab\xeb\xf9Z\xc9?:e\xea\xc6\xdb?\xd6\xbf|\xb6\x8a46\xae\xf6\xbf\xdd\x9d8\xc9\\\xc1\x02\xc0\\o\x02\x82\x99\xb6\xc1\xbf\xd6\x0b(\xaa\xc6\xf7\xe5?0j\xcd6\xe0\xf8\xea?\x82p.\xf2_w\xf1\xbf\xe7X^\xbfW\xa8\xe7?\xac\x15\x13~\x0cs\xe3?*^\x1ce\x02\xc6\xb1?\xa3\x0e $5\xfa\xf1?\x0c\x19wJ\xd5\x88\xe0\xbf\x86\xf9\xe7\x1f\x82\xdf\xf5\xbfW\xd7b^\xc1;\xb2\xbf\x81\xa3\xb8\x80\xe5\x82\xe2?\x8f4\xe97\xe5\x00\x00@\xc6\xf4|/}n\xd4?\x96\xb1\x88D\x8be\x86\xbf\x83\x01OI\x01v\xc2?_3\xdc\x0bU\x88\xcc?I}\xb8\xbc\xb0>\xe1??\xd7]y\x8f\x0f\xe7\xbfc\x1c=\x1cF\xb1\xc3?\x95a\xfd$U\t\xed?\xb8.\xde\xbf.\xf8\xe1?d\xfc\xfa.\xa3\xb5\xf0\xbf\xcf\xba\xa5\x1a\xcew\xf2\xbf\x00\x12<#\x90_\x89\xbf\xcb3\xe4\x97A\xc4\xf4\xbf\xacw\xc3\xd1/\xa8\xcf\xbf\x10\x8a\xd5\x197\xa2\x00\xc0\x95\xcclC\xb5E\xd8?o\x9b\xb0]\xf0t\xc9?E\xfaBfzC\xde?+\xf3\xbd&K\xa3\x08@\x9c\x1bR\xd4\x07\xe9\xf6?\xd4\x95\x0b\xb2\x92\xac\xe2\xbf\x9drq\xea\xa35\xe6\xbf\xc7\x909\x97\x17\xbd\xf1?\xa2|)Q5\x97\xee\xbf\x1esK\xa0\x1a<\xf6?\xa2\xec\xba\xe8QO\xe8?\xadU\xf1U\xf5\xdf\xe5\xbf\x07\xd7!\xa0\xa4\xc8\xed\xbf\xe7gx\xa2c\x7f\xf3\xbf\xe3\x95\xff\xa8\xe5\xb7\xfd\xbf\x90X\xe8" \xa0\xdb?\xdd\x03\xa2\xdc\x916\xe0\xbf\x8fcr\xc4\x13\x9d\xdb\xbf\xee\x17\xd1\xe7\xb3\xcc\x01\xc0\xeb\x07\xc5B\x16\xea\x06\xc0U\xd6\xe6-\xcc\xd4\x00\xc0z\xba!e\xd9\x91\xec\xbf5\x1bh\xa7\x1b\xf3\xec\xbfAW\xe0\xb2\x04\xda\x02\xc0\xfc\xa7\ni\x0bx\xf8\xbfm\xe8\x84\x88\x96\xa4\xe0\xbf\xac\xaa\xc3\xee&\x9e\xf8\xbfm\x807\xcc_s\xd6\xbfRl\xb7\xea\x88O\x00@\xca\xea\xaaBt\xb7\xef?6|\xa8\xed\x9e{\xef?!\xdd\xd5B\xe7\xc9\xe5?\xddN\xe0\x03\xc3\xbb\xb0\xbf\x1c\xa6~\xe7\xbb\xc9\x84\xbf\xb3|\xa5\xff\x84g\xd0?!K[\x08\xd5b\xeb?\xb6\x90\xdf4\xacf\xe8?\xc64\xca\xff\xe2I\xfb?\x18\x1c2\r\xcd\x1d\xdc\xbf\x07sM\xfb\r\x14\xa7\xbf\xc5\x07\x8c\x14f-\xed?\xbd\x87KU\x94\xa5\xf8?\xab\x11IU\x89\xa9\xea\xbfi4\x10V7M\xeb??\xa8\x81\xcd\xb5 \xec?\x0f8\x0f\xbf\xa1\xfb\xe5\xbf\x84\n\xd9*\x1aJU"\x05@\xe5\xbf\x96:\xfal\xd1?\x06Y!\xe3\x9b\xf4\xe2?\xe4\xe3,\xbc\xc9f\xd4?\x840K0|\x12\x98\xbf_\xf4\x8b\x8d|\xaf\xdf\xbfY=R:\x92I\xa2\xbf\xffM4\x97}\x0b\xf5?\x88|mmF\x1a\xdd\xbf\xb9\x05\xbbtWM\xff?G\xfc\xa8\xa6\xfb\x9a\x00@\xfb\xecM\xe4\xaf\xf2\xbb?\xe2\xb0\x96\x0f^\x94\xdc\xbfFC\x06\xc4.\x8c\xdd?8q\x1d^\x96\xb7\xf7\xbf\xbcEtm8\x9e\xe8?[Z2\x01#-\xb5?x\x07\xees\xeb\xe8\xd3?\xc8\xab\x98\x02\xc2\xf0\xf2\xbf\xe7\x82\x0c\xd6\x8af\xe3?\xd7\x07\xd5\n\x9d\xe0\xc7\xbfl\xdf\x0e_\xeba\xaf?\xef\xd5\xf8&d\xbd\xe5?\x9a8\xc8\xba\xdc\xa4\xd3?\xfc\xe0\x89\x80\xd7\t\xf1?\xa3a\xbd\xf1\xeaq\xcf?\xb9\xdf!\xb0@\xdd\xb6\xbf{\xae\x05\xef\x87\xc5\xb8?m\x96>Wf\xb3\xd6?\t\x0cx\xbew{\xeb?\xc2]\xebw\x83(\xf0?EfS\x14\xc0\xdf\xd1?r\xcf`\xe9\'o\xf2?\xbc#\xbe\x0c\x9a\xa3\xda?\x87\xb5a\x92\xf1\xfa\xf8?\t\xf1\x1e@n\x18\xe5?\x15\xc1\xd1\x0c\xef\xaa\x02\xc0\xaaZ\xe4\xad$y\xfe?\xbe~\x19\x10\xa3\x0e\xfd?\xe8\x15\xcd\x8f\xce\xa0\xea\xbf\x0f\xd6\x8d\x0b\x92c\xdf\xbf\xf2\xd72|o\xa8\xee\xbf{\x91\x99n8\xba\xf8?\xb6&\xa5\xe0,O\xec\xbf\xefw\x9e\xef\xc3\xa9\xe7\xbf\xd2\xae\xfawe\x7f\xb8\xbf\x13\xb2\x8a\xe9\xe9\x01\x00@x\xd3\ra\xe1\xd0\xd0?\xf9+v\xd5?\x91\xd3?\xea.\x83\xa4\xdf(\x02\xc0\x1cAw\xc7`\x83\xe1\xbf\xb2\xf79rx\xbf\xf0\xbf\xb4\\\x98\\{/\xd7?\x80y\x16d\x0e\xbe\xca?\x1b\x14\x96\xa4\xfdj\xcb?$Onp\r\xd7\xc4\xbfAz\x82d[\n\xd4\xbf\x81\xbce\x96\xbat\xef\xbfm\xd5\xd8\xed\xa4e\xdd\xbf2\xcc\r\xea8J\xf1\xbf\xef\xa3.4\xa0\xe9\xd2\xbf\xd4mv\xd9\x89&\xf5?]\x87\x99\xce\x17\xf3\xed\xbfSp\xe6-\xb0\xd9\xe1\xbf?\xc5\xf1j>\x0b\xcc\xbf\xa4?\xba\x12\xb6\xdf\xf7\xbf\x94\xf4hL\x17\xfa\xe6?\x01dcD\x95\xeb\xd4\xbf\xf8\xf8\xdfe\xccF\xee\xbfB\xdd\x8b\xbe\xa9.\xe7?pK\xa2\xde\x15\x9f\xee\xbfx\x85\xe3h\x9c\x0b\xd3\xbfe~\xff\xbbL;\xf4\xbf\xc9\x83JX\x0e\xe8\xd2?\xc3\xd9\x12\n;\x14\xe6\xbf@\xc5\xc3\x8d\xe0)\xca\xbf2\x9dF\xfd\xb0\x1a\xf5?g]\x8a\xc7p\xdd\xdc?y\xc0]\xb9\x8b\xca\xfa\xbf\xc0\xa5\xbbx\xc2\x19\xe7?]\x9e*\x1c"\xb3\x00\xc0\xb4\xa2\x8f\xac\xf2\x08\xeb?\xf3\xd5\xda\xdb\xca\\\xe0?\x19\xaf\xb0\xd5\xe2\xbe\xe6\xbfL\xa2#lS\x07\xf0\xbf\xadz\xaf\x03O\x13\xf4?B\x05\xc6j\xd3\x81\xe6?yq\xe0:\xdc\x9a\xcc?\xc5|g\xe3\xdbN\xf5\xbf\xbd\x0c\xfa\xbf<\xc5\xea?\xf5\xfe\x02\xde8a\xd7\xbf\xbd\x14o\x9eu\x9a\xe4?\xcc\xe5;\x8bGQ\xb8?\xfeV\x8a\xee#\xcb\xea\xbf5\x96U/&\xa0\xf5?H\xc6\xcf\xb1s\x85\xe5?\xf0\x10\xdb\x1f\xc8\x03\xf7\xbf\xda_\xd6\xce"\xa9\xe6\xbf\x1f+\x95\'\x03Y\xfa\xbf\x8d\xa2u26\x86\xc5\xbfv\xbe3\xa8\xd1\x16\xf7?\x8a\t\\\xa2\x14-\xf3?%\xf3\xf6\xc8WX\xa9?\x0f\x19P\xe1\xd3\xf5\xe5?\xba\xde\x11\xb9&`\xf7\xbf\xc9Kz\x12\x05i\xd2\xbf\x11\xd3\xb8\x92\xb4\xbc\xd9\xbf\n\xee\xe0\x08\x98%\x06\xc0Z\xec\xaa\xd4\x1fg\xdd\xbf/\xdbGl8\xf5\xf4\xbf4\xcc\xf5_\x92i\xc8\xbf\x19\xfb\x06p\xf6\x84\xb7\xbf\x8a\x16\x98\xc0\xeb\x05\xcc?\xa4\xa5\xc1J\xdcR\xe5?\xed\x9a\x15e;\xc4\xdd?\x12\xe7]\x81\xe9\xba\xf3?\t~\xca\x87\x1f\x94\xdd\xbf\xe63\x8aU\xea$\xf8?w\xd4F\x83\x07\xe4\xc7?\x08q\xc6\x1e\xb7&\x05@\xb4\xcb\xc4\xc7\x1fu\xea?mu\xbea\xc9\xae\x9c\xbf\xdb\x99\x9d\xbb\xe38\xe4?\x99(\xe8\xa7\x12\x97\xce?\xc10)\xfa3\xab\xfe\xbf(\x7f0\xc4\xba\xf4\xc2\xbf+\xcb\xce\xcb\xd4"\xf4\xbf\xb8?\xb2\x00\xd3V\xe1?\x0f\\\xf4\xb5\xce\t\x9a?p\x81!\x13u\xa7\xd8\xbfS\xcb*[An\xf6?\x07\xfb\xcf \x91\xf8\xee?1\x1d}\x18\x08\xee\x01@J;\xf5\x122I\xc1?K\xa2\xd5[\xda\x14\xfe?\xe0~\x89$m\x8a\xf5\xbfY]\x1e\x86\xc6E\xd8?\xda\xc4^oa\xa8\xe3?f7\xac\x92?%\xba\xbf\xea\x80\x94\xa9\xfa\xad\xb1?\xc3\xe9\xfd3\x0bF\xf1?*\x93Mw<\x83\xe8\xbfu\xe9\xcasim\xfd\xbf\xe0\xe3#\x07n\xa8\xc3\xbf\xed\x9e\xaa\x05uq\xeb\xbf@\n\x0e\x06\xd7F\xfe\xbf\x11\xb9\x07B\x83e\xf1?\xc9\x0b#\xa0\xc5\xc2\xe7\xbf\'!\xc5s-\x87\xc9?\x11 \x92\x9d\xe5\x1e\xf6\xbf\xa3\xc0\x8a\xe1z9\xf9\xbf&\xac4\xa2N\x0c\xf5\xbf\x96\xb8\xdb\x19\x07\x98\xb1?>\xca\x90\xae\xfeF\x01\xc0$\xbf\xb3)s\x0e\xe4\xbfg\xd2\x89\x97Q\xf0\xa5?\xda\xc1\x97\xd9*\xf4\xcc\xbfM\x96\xb7\x08\xa5E\xda\xbfx\x1f1\xa4\xa8-\xe8?@6b\xe2eH\xf6?\xe9s\xae\xe1ii\x03@\x92}\xe1\x01h\xed\xf7?\x98\x9d\t/Q\x8f\xf6?\x7f\\z\xdcA\x11\xe7?\x80\x98e\x80\xef\xb7\xeb?\xe7\xb8\x0e\x04%`\xe2?\x0f4r\xcb\x90\xed\xe1?zc\x8bpBe\xd3? \xa2\x82m\xcec\xeb?GR`\x8b\x8c<\xfd?m \xb8\xa6\xb1\x94\xe3?\xb7\x03\x0f\xdd\xa3\xf5\xd5\xbf~.P\x87\x92L\xe2\xbf\xdb\x84\xbb\x84\nG\xf4?/\xf5\xb8\\k\x07\xf6?\x9c\xc4N\xa3lg\xfe\xbfH\x05\x95-\xbdv\x00\xc0\xb4\x904\x02W\xd5\xe3\xbf\xa2\xbe\xae\x1c^\x0c\xfb\xbf\xa0\x9d\xf5\x1e+y\xc3?\xb4\xf1\xd9|\xd6y\xba?\xfc\x98%\xbc*N\xd7?X0\x00e\xb2"\xf9\xbf[Tp\x1c\xa7\xc3\xf6\xbf\xf3\xed\x99\x148\xb2\xd8?\xbf\xcbI\xe4\x96\x91\xfc\xbf\x7f\x15\xcd\x93L\x87\xfc\xbf+\x03\xd8\xaf}n\xe8\xbf\x0fD\x90\x07\x19&\xfa?\xd1\xf3\xc1\x97-\'\n@\xa2\x88Z\r\x8d\xa5\x01@5\t\x00&\x809\x00@\x8e\xee\xc5\xbat\x1f\xbc?K\xd1S\x80\x92\xee\xfc\xbf\xe7S\x94\xca\xf2\xe1\xfe?\x0bh\x16a3\xad\xc1?\xb6m\xc5=\x1d5\xeb?\xcb\xe8\xbelX\x00\xef\xbfJ8\xed(\xe4\x89\xa0?\x10H\xac\xaa\xa1\xc0\xfe?\xfd\x0c\x06\x93\x88\xc3\xec\xbf\xd6!\x82\x9e\xc2)\xe5?\xc4+\xd9!\xf1\x82\xe6\xbf&\xa4\xc4\xf0\x06\xf5\xa3\xbf\xack\xca\xd0\xd1\xa1\xe5?\xfe~\xad4~P\xe1?CB\x81\xa4j\xa4\xec?\t\x1f\r\x9c6\xb1\xe0\xbf\xe5\x18VZ<%\x01\xc0\xec\x03\xfbx\xfa\x19\xed\xbf\xc9\x831c\xec\x19\xe5?zny\xc23\xa4\xd6\xbf\xea\xcbh\x96\xe1\x84\xf2\xbf\x9d\x06\xad(|\xe3\x9e?\x0cX\xcd\xaav\x01\xc4\xbfU\xaf$\x17\xe8\x98\xd7??\xd4\x07.\xe2\x91\xe3?\x80\xbc@f\x8d\x9e\xf4?I\x10\x17o\xef\xe6\xf9?\x8cX(,j|\xfc?S\x82L N\xc5\xf3?\xf3N\x82T\x81f\xed?"\xa5\xaa\x1b|k\xdf?\xea{\xb3\xb3G\xdd\x05\xc0\xaeX\n\xdbY\xdf\xeb?\x16\x8a\x15\xfa\xacU\xe6?\x08\xb7\xa1e\x97L\xec\xbf\x1b\xe1\x94\x81p\xae\xf2?!\xc4s\x95\x0fz\xb2\xbfN}\x7f\xe7M\x1b\xbf?\xc6A\x03\x96\xb4\x89\xfb\xbf\x87\x123\r\xdf\x03\xde\xbf\xa9pU\x01\xe0\x14\xb4?J\xed\x98\xa1\x16O\xe7?\xc1\'3v\x0cU\x88?\x05\x8cW\xfaT\x19\x00@\xeb\xbdwL\xbc<\xca\xbfI\xa5o\x82\xb6\x90\xf6\xbf;a\x8fkh\x0c\xbc\xbf?(\xba\x88\x1a\x11\xec\xbf\xd3\xb8\xa8\\\xb96\xe0\xbf\'r\xd1S3\xa8\xee\xbf\xe8\x91\xb3\x1c\xd9F\xba\xbf\x01\x97\x99\xee\xde\x00\xd6\xbf\xab\xf9\x0c\xf9F`\xf8?\t\xfc\xf9\x86\xc0h\x0e@\x83\xda\x1d\x980\xa7\x01@\x8f\xd8"\xa6\x84-\xe0\xbf\xca\xb3\x1c\x87\xc5W\xde?\xee\x08d\xbf\x06J\xf7?w\x92\xabxw\x80\xeb\xbf\xf7\xb9W\xfa\x91k\xf1\xbf\xd1\xb2\x82z\xf9\xe5\xe2\xbf.\x92-\x7f\x85\xa3\xdd\xbf\x9d\xa7\xf2\x91\xf8\x12\xe9?\xa9\xf9\x98\xfc\'\xf8\xf0?\xfa\xc5\xfb\x86\xb6\xa0\xd0?\x8etd\x18t\x83\xf0?F\x9e\xb2\xe4\x03r\xec\xbf\xecJ\x0f\xa1\xdbE\xf2?^G\t\x9b\x87\xa3\xed?G\x17\xb0d\xa3$\x05@\\>5\x13vh\xcf?\r\xd8o\xe0\xf2\xce\xc8?\xf2A\x90\xb7\xb4+\x00@\xb3\xb0\xfb\xc3\x94p\x04@\xae!\xad\xdc\x1d:\xd3?\xe9\xc6\x14\x1fe\x97\xeb\xbfBz\xec\xce\xf6\xb7\xe2\xbf\xb1\xdd\xf98\xa4\xb7\xec\xbf\x9c\x86\xb1\'\x02\xaa\xfd\xbf\x02{\xfa\xe6.\xcb\xfd\xbfS\x02\xb3\xdf\x84\x9a\xf7?\x98K\xaa\xad\xb5\xd0\x03@\x85\x8f\x02 r%\xf8?\x8c\xbe\xee\xbd^I\x01@\xcb\xb5\x1d\xe1\xcd9\xd1\xbf\xca\xb8.\xc8\xf8\xd6\xe8?Pz\x92\xf3\x8f\x81\xf7\xbf\xb5\x80@\xe0D:\xe9?\xd0\x81\xb2U9|\xec?\xe7\xbb\x17\x12+$\xf0?\xec\x81\x9bbXI\xf3\xbf1\x1dC\xb8\xbd\xd0\xdf?Ue\x81\xa1~\x84\x00\xc0e4q\t)\x96\xf5?\xfa\x81o\xf2\xde(\xe8?\x11\xf3\xb9\x87`g\xf0?x\xb0n\xf1X\xdf\xee?\xe3\xab\xf2b1:\xee\xbf\x84\x15QEC\xe5\xef?^\xd2\xd1\x13\xad\xd4\xf1?!\xd9q\x97#\xd0\xfa?\xf7\xf0\xda?d\xac\xf9?\xc2T\x01\x00za\xeb?\x0f\x16r\x80\xe3f\xfc\xbf~\xf7\x07\x1c\x1aj\x07\xc08\x9eP_\xad;\x05\xc0W+\xce1\xcau\xf7\xbf\x1d\x8b\x82~\x7fC\xee?-\xc8\xcfM\xcd%\x00@\xec\x04\xf5\xa9J\xf1\xfb?v\xac\x84K\xd9\x8f\x02@\x9eh\xa7\x93\xf1\xb2\xe2?\'\xf2_\xfe`D\xd3?]\'D\x88\x10\xca\xf3?\xf1f\xf3\xf2\x94 \xe2?\xdc\xd1\x12}Q\xdc\xdf\xbf)\xe3\xa5\xba\xc8\x83\xf2?\xf5\x18b\xeb\xc3l\xc7?\\q1S1\xb2\xf1?*)Y\xc8\x1d:\xd7?S\x19\x17\xf6\x19\xdc\xfd?\xacR\x8b\x12\x1e\x8e\xe1\xbfa\x02\xe4\xa6\xde!\xe7\xbf\x8c\x11\xca\x13\xb2I\xbf\xbf\xbd\x84\xf3\x90\xd0\xe5\xda?\x81\x9bL,\x92S\xfa?\xdf\x1b\xdd!m\t\xfe?\xb3BS\x06\xef\x81\xe8?i\xc7\xca\xd2\xbdV\xdc?z\x03by_\xdd\xde?n_\xf1\xdc\x17\x07\xca\xbf\xb8\x1b\xbc/f\xd4\xd8\xbf_\xa51m\x0c\xaa\x05\xc0\xe5\xdf\xa8\xf7\x0e\x8e\xf2\xbfx\x91u\xb8\x00\xd7\x9d\xbf\tr|\xd7jv\xef\xbf\x1b\xa55\xc5\xbb.\xed?\\\x18v\x15\x91\xff\x10@i\xb4V$\x0fr\x02@}\xc5\xd1PnO\xfe?\xd2\xd1\xe6\x98(\xae\xbf?s\x01\x843\xf6y\xf2\xbf\x18wa*\xf5\xf8\xc5?\xf1Y\xb8\xf4\xaf]\xef?&1\xb8\xc5}\'\xfb\xbff\xe0VN,\x91\xc5\xbfV\xcdv~-D\xae?M\r`\x91\x010\xce\xbfH\x9dP\x0c\xc0~\xf7?\xe8\xc03:v<\xa5?\xb0\xa1\x8b4\xa1>\xd5?\x86p\xd8J\x9b\x1e\xe6?\x12\xf0\x942%\x97\xfb?\x0bH\xd0\xd8\x83J\xbe?\x89\xfd\xd3\xa6\xef\x1b\xe2?\x0e@N\xfa%\xd8\x84?\x0b\xef\xf6a\xebf\xd5?\x02\xbf"7;\xe9\xf3\xbf\x05\x10?\x1c\x90\x91\xf2\xbfC\x8eJ\xe2\'S\xf6\xbfUek\xf6\xa4\xc9\xe6\xbfn\xb6\xc2\xecD*\xbe?\x95J\xcdp\xd4\xf8\xfa?\x18\xccX1\x8e\xfa\xf4?\xa99v\xe2J\xb1\x03@!\xb6\xf3\xb7\x8b\x9b\xf3?"\xab\xc0\x11\xf7\xf8\xe2\xbf1\xd6p\xfa/\x19\x01@\x8b\xfa`\r\x07]\xd1?\x00~\xdbw\xff \xda?\x9b\r\x10\xec\xa5\x08\xc3\xbf%\xa8\xd1\xb3\xc9\xd3\xe5\xbf=\xa6M\xabP\xb9\xe4?pE\xff^O\xcf\xb9\xbf\xcaO\xc77\xcf\xf5\xf1\xbfZ\x8a\xe8o\x1a\xe3\xe9\xbf4|\x9d\x955\xba\xdd?\x0c\xe9\xf8\xcd!N\xf3?\xe2B\xc3\x03\x0e\xc7\xe1\xbf\x00\x9f\x1eX\xb6W\xd4?\x82\x19\x1ez\xe1\x9d\xd8?#\xa1-\xaf\xb7\x02\xd6?\x97@\x14\xfa\xaf\xe8\xd5\xbff\xa1X\xda\x0e\xed\xec\xbf\x99i}Z!\xf8\xc1\xbf\xf7|\xe1ne\xdd\xaa?Xh\xfcb?\xa0\xb7?\xd9\x9dr\x92\xd8\x90\x01\xc0\x9d\xf8@6\xec\xb4\x95\xbfU\rhu\x1d\x14\xe3?e\x7f\x025E\x9f\xed?\x99\xaa\x14\xaa\xcc\x1e\xe1?\rt\xfc\x9d\x122\xfd?\xe8k\xcf\x11\xd7\xae\xdb\xbf\xdf\xc0h\x98\xe4o\xd6?i\xc2\x14IW#\xf0?;\xb6&F."\xee?=I\x13M\x9e\x0e\xd8?Rq\x8d\xefLB\xf0?\x08\xea\xc2\x1f6\x88\xf3?\x06\x9f\xdf\x82\xe3N\xb0?\xaaI\xe2w\xdb\xbe\xe2\xbf\x137\xb4KP\x7f\xd9?\xe6\x8bp$2K\xe1?\xa2\xc8G~b\xda\xf3?0`1\x1f\xa5X\xdd\xbf\x02M\xa4\xffne\xe9?\xff\xba\x1a\x1a\x04\xb6\xd1?)\xfbt\xaa\xccP\xf0\xbf\x16M\xd7\xed1\xcd\xff?\xe1\x12\xa2\xc2\xa7\xba\xf7?\xac\xc8#\x10\xb8\xf1\xf2?\xd0rs2\x95\\\xf4?\xc7\xd4\xadn\x96\xe5\xe6?\xed\x8fs}dz\xf6?hO\xae\x10\xc5\xe5\xf0\xbf\x08\r\xf9V\x00w\xb8?-5\x00"\xd2\xdc\xda?\xdc@\x1d\x03\xcc\x1b\xf5?\xce\xc86\xac\xa6x\xfd\xbf\xd6B\x82F\x85\x97\xf3?\xc6;}\xfc^\x18\xe7\xbfU\x1d\x9a7\x06\x07\xde\xbf\xf5\x1d\x148\xd7\x13\x04\xc0/\x0f\x00f\xc7\x80\xc8\xbf\xc61.\x9cea\xd5?5\xcb\t\xd5=/\xc2\xbf\x94\xea\x12 \xdc\x95\xf2?o\x9b\x1cs\x9e;\xd6?h\x88\xe6\xb6\x86^\x00@\x19\xfb\x0es\xe7x\xd0\xbf\x0b\xad]\xe3a.\xe8?\xfb\x95\xb1$\xdc\xda\xf5?M\xe6\xf3>\xa5\x9b4?\xd1\xfbWS\xa0 \xcb?@\xffb\xfaV\xc1\xcf?J\x13\x9bJ\xc2\x98\xd7\xbfW\xc0s\x9d\x8e\xfa\xd1\xbft;|\xd7\xab\x99\xe9\xbf\xf8pQe\x10\x9a\xfb?*\xe9bx\x9ap\xec?\xef?\t\xcc\xf7\xd5\x91\xb3\xd0?\x08x\x7f\xb5\xd2$L\xf6\x8b\xd4?\x01\xf9\x08\xb6\xb1v\xd3?\x00\xdb$\xc8&\xbe\xe9\xbf\x9e\x8a\xa7{\x16\xd9\xf3\xbfv[9\xdc%\x18\xf0\xbf\x9a\xf7\x83&\xd6o\xb7\xbf\xce\xdd\x17s\x06\x0f\xf8\xbfy\x8f\xb9\x18\x0f\'\xa0?\x0b\x0cD\xc0\x81!\xc3? \r\xf5\x00\x06\xa6\xc7\xbf\xb8v\x9c7\xf7\x7f\xe8\xbf\xb7?\xa4\xdf\xc4!\xf8?\xfb\xe8\xf7\xb2\xbav\xd2\xbf\xe3\x06\xf4 \x1e\xac\xf7\xbf\x95\x9c|\xfd$\x0c\xf3?1Z\x83\xe4&\x08\xea?\x1cG\x0e\xc8<\x14\xd3\xbf\xd1\x8b\xdc\xd3\xd5\x8a\xda?\xb7\xf3\x0c\xac\xc3:\xff?\x11,kurJ\xb9\xbfT\x16\xdb\xd5,t\xbb\xbf\xae\xc6\t7\x19`\x00@\xc2\xe2\xe7\xb0\x0c\xb9\x02@\xd4-`\xf6&-\xe6\xbf>\xa7\x1eY\xbc\x9d\xdd\xbf\x05\xbb\xf0\xf4!\x00\xce\xbf\xa4\xce4\x1c\xcd3\xcc\xbf/\x15\x8b\x11B\xdc\xdc\xbf\x0b\xe7~\xfbY8\xe7?\x08\xd9\xc0\xb4\xd5a\xdd\xbf-\xdb\x1a\xd55r\xde\xbf\xe8 \x078\xb5\xdb\xa1\xbf\xb70uG\xccA\xe5\xbf\xed\x8b\x8dl\x8e[\xf7?%\xa3N\xc3?G\xdd?\x8cY\x03\xebz\x98\xf1\xbf\xf9\xa7v\xf4\xdd]\xfe\xbf\xfe\xb7\x81\xaa\xfa \x00@\x9dJ\x89V\xec\xeb\xc6\xbf\xdc\x04\xf8\xe3pJ\xf5\xbf\x9c\x1fK\x9f\xb7\x07\xe2\xbf\x8c\xa7qJs\xb3\xe4?\xc8\xd4\xf4/\xdf\xc7\xfa?g\xfa\xbc\x1f\\\xa6\xe8\xbfo\x01&\xf8\xdb\xd6\xe9\xbf?\xee\x8a\xd2\x85\xc2\xf5?b\xb2\xc3\xff\x02y\xe3?\x85l\x1e \x043\xfc?\\\x8aa\x99\xc4\x00\xf6?9\x7f\xff\xc2\xbbx\xbd?"\xed\xca\x89\xc6\xa2\xe4?\x11S\x8f\xdf\xd4\xe0\xf4?\xa0p\xad\xa4\xf7D\xf1\xbf\xa6l\xb4Kd\x11\xbf\xbf\x9c\x12\n\xcd\x83\x1c\xaa?5\xdaxPM\xdb\xc4?S\xd5e\xf4\xb3\xc0\xe7?\x9c~\xc7$\x16\xf7\xcf\xbf/\xd3\x1dbm\xdc\xf2?\x86\xe7no\xd3\xe6\xe3\xbf\x11\xfaN\x89!\xeb\xd2?k\x05P\xbb^w\xcb?A]M\x85\xe8b\xe3\xbf\xaf\xe7\x1d\xc4\x80\xe1\xf2?S\xb7\xf3\xe1\xce\xfe\xf5?`\x9d\x7f\xc2*>\x9f?\xfa%\x9d>\xef\x1b\xbd\xbf\x12@\xa6?g\x08C\xbf5t\xbc\xf8n9\xf6\xbfB<\x15\x1d\x13\xff\xce?\xacB\x1c4\xd0g\xc1?x\x92e\xbe\xa9\x04\x02@\xac\xc7\xed\x85&\x1a\xd0\xbfLi\xfb\x918\xc5\xdf?}\x8dl\x9c\xfbq\xd4?\xdd\xbe\x86\x1b\xf6\x86\xe3?\x80\xe5\xe4$\xba\x1f\xd8?\x1dy+!\xc7\x19\xed?n4HL\xbd\x1a\xfc?\xa5n}i\xd4\xe3\xf0?\xb0\xa4!\xd0\xd1r\xee\xbf\x1a\xb6\xf5\xb1\r~\xe6?#\xe6U\x0e\x9d\xac\x00\xc0\xa9"\xd1\x8cm\x0c\xb1?\xe5\xb0P9\xe8\x08\xc7?\xd1oDi\xc0y\xf4\xbf\xca9]\xd53@\xed\xbf!l\x06\x19\x81x\xd5\xbf]VG\xf9\xc0\x88\xe2?\xed\xb9oj\x8aB\xda?\xb2i\xba\x86\xf0K\xfa?4\xb9x\xba\x00Q\xf9?\x12\x82\xe5\xaawu\x00@\x9d\xfc$\xc5U\xa6\xe3?\xfa#\x02!\xb0h\xd8?\x03^\xbd\x80\xa1\x06\xe4\xbf\xa8\x8e\xac\xa9\x81\x98\xe8?\xfa\x80\x1e\xa0c\xe3\xf0?D\x90S]\xebY\xe1\xbf_T\xc2@\x84\xc7\xff?\x1f|(u\x1e/\xf0?\xbbF\xed\x07.\xfe\xe1\xbf\xc5?\x9d\xdbg\xc7\xf3\xbf\x04\xff\xafg$P\xfa?\xfak};\xceY\xf0\xbf7\xe3\x15\xc8o\x95\xfb?\xdd\xd6pAG4\xf1?\t\xef$d\x83f\xe3\xbf\xfa\xaa\xacY\x10\xfd\xe7\xbf\xf9\xb2\xb8Q\x8c\xe8\xdc\xbf\x90-\xa1\xe0\xf87\x9c?\x00\xcaR\\D}\xf6\xbf\x84x\t\x88\xfdm\xb2\xbfB\xc4\x94\x13ZQ\xf3\xbf\xff\xed\xab\x8b\xc1\xc7\x9f?H\x16\x05\xc9\xa1\xc7\xda\xbf|;%\xf3\xa7\xae\xf7\xbf\x1c\xf4\x87o\x84\xe4\xe2\xbf\x93\xf2_\xd6F \xe2?\xf3\xbd\xe8h\x9d\xdf\xf9\xbf\xf2}\xbe\xe9\x8c>\xea?t\xaa\xa4F\xb8\xdb\xf4\xbf%\x8eW\xad\xb3\x9c\xf3?/\xe7\xd5\xf3\xbd`\xd1\xbf\x84\x16?#e\x9d\xf2?\xbb\xf1\xda\xb6\xe7\xcb\xf1?I\x94S\xd1\xd6\x94\xd0\xbf\x93\x8a\x12\'\xb9\xc0\x9d?\xbf\x10)\x98\xabA\xe6\xbfaZ\x16\xb0\x02Y\xea\xbf\x11\xf9\x88Q\x7f(\xf7?\x80\xabEgn\xd1\xf5?S\xa7\x9dl\x90|\xeb\xbf\x11:\x1e\xdf\xd6\x06\xe2\xbf\x82\x8f\x11\xbd\xf3Z\xf4?\x10\x9be\xe84\xfc\xf2?4#\x02d\x0eX\xf1?CY\xcd6\x8e\x86\xec\xbf;\x08\xf8H9K\xea?~\xa9Q\x06L\xc7\xe8\xbf\x0f\xf7\xb8\xe8q>\xe3\xbf\n8\xe85\xc1;\xf1\xbf*\xa8l\xa1\xb5\xb2\xea\xbf& \xbf\x9az>\xfa?\x82\xe1\xab*\xbd\xcb\xc9?\xb48\r\xccK\x95\xe2?]t\x81o\xd3\xa9\xe9\xbf\xc4\x92\'\xe2~\xf9\xc2?L\x93\xcf\x07K2\xf1\xbf\xf2\xcb;K-n\xf6?\xbb\x9a\x91\x864\xe3\xfa\xbf\xf2>e\xfbXK\xf8\xbfo\xc4A\xda-,\xe1\xbf\xf2SA\x92\xa6\x0e\xe4\xbf\x0c\x04\x87\xd0\xa2\r\xfe\xbf\x99\x88\xe7\xceI\x1e\xe8\xbf\xe8o\xf6.\xb2\xa9\xf2?\xc0K\x92\x92\x17o\xec?\xf5\x81\xca\xff\xccd\xbd\xbfP\x8e\x81\x86\xcf\x1e\xe1?\xc6|\xc3\x7f0\x0e\xe1\xbf0\xc0N\xde$\xea\xe8\xbf\xcb\xdbC\x01Q)\xf0?\'&\xdc\xcf\xa8\xf1\xf8?\xb3e.\xac\xe5\x02\xf9?\x182\x8b\x8e&\xc5\xc0?\x82\xc9p\xdb!v\xe3\xbf,\xa8\x06\x81s\x8d\xf0?i\x1c\x10\xb1\x1a\'\xed\xbf\xec\x9d\xf6\x1b\xa3\xf9\xaa\xbf\x9bn\x88\x7f~\xca\xe1\xbfK\xe5\\;\x9e\xb1\xf1\xbf\xb6z\xd6z\x17\xd9\xf8\xbf\xb3\xcfp\xc7\x9f\x1d\xc4\xbf[\x89\x86\xcc\xbb\xd0\xe5\xbf^\x97\x91w\xd5\x1a\xdb\xbf\xe0\xde\x15\x03mS\xb9?\x9f\x1f=\xa6\x96\xa9\xd7?\xa9n\xa3\xb4\xfcx\xe0\xbf\xb2u0\x9b;\xee\xe2\xbf\xadm\x9e\xb4\x03\xd0\xe6?2\x02J\x9a\x946\xf1\xbff\xf0\x9b\x86\xac\x92\xe5?,0\x99m\xf2\x87\xcc?\xaa|*\xf7\xfa\xde\xf0?\xb4\xd3Z(_\xfa\xe4\xbf\xdd\x9c\xda\x9eX\x0c\xb8?.\xb2\xe4\xdbdI\xef\xbfU \x1b\xcf])\xe8\xbfZ\xcc\xd0} \x03\xf0\xbf\x1e\xaa\xb3a]6\xf1?E\x8c\xa7rs\xdb\xc1\xbf\xcf\xed\xc1\x0b\xb1\xa8\xfb?\x9a\x94\x16\xacP\x00\xe7?8\x13M\x17\xe39\xe2?7\xa0\xf9\xa9\xdc\xaf\xeb?3\x87\xe7\xfb\xef\x04\xf3\xbf\x9fY\xe6\xafh\x8c\xf4\xbf9\xc2\x18\xaak\xf4\xe1\xbf\xe0\xeeF\x1d\xf2\x86\xdb\xbf\xa1\xf6\xe7Z\x98s\xb3?=\xcb\x89 ",\xbb?\x8a@:\x17^u\xa2\xbf\xf00\x15zZ\x1b\x03\xc01\x89d\xbcw&\xf7\xbf\x01\xfb\x92D\x89\x08\xcd?\x94Np1\xff%\xc4?\x84e)\xb6\xcd#\xd2?y\xf4\x82x\xe8L\xf3\xbf\x17\xa1\xda\xca\x19\xb6\xe2?z\xf0\xed\x9d\x0c\xab\xcc?\xa5\x91\xa5y\xc43\x06@\x81\xac\xe6h\x99W\xc1\xbf\x0e>\x7f\xa4y\x99\xd9?a\xf6\x90\xc0\x80\x1f\xe2?\xa1\x1e\x94\x92\xfc\x1b\xd0\xbf0\x86\xdf\x95+v\xf8?j\xe0X/D5\xdb\xbfr#7\x8d\xc8\x8c\xd1?\x88\x97\xb5RY\x82\xfd?P\x8e[\xcfL\xd1\xfb?\xb7\x15\xad\x89c\xce\xd3?\xc1v\x02\x93U\x01\xcf?W \xc3\xd4\x1f6\xce\xbf\xe8i\xb6\xc3W\r\xe5?\x95\x91\x96\xb1K\xcd\xc6?r6Z\x115\x15\xe2\xbfO\xbcaEh\xc1\xea\xbfL\x1e\x03A\xf8!\xf3\xbfomV=a\xf3\xc1\xbf\x0c\x890n\xa6`\xe9\xbf\x19{Z\x1b9-\xe8?\xf3\xcf\xfbP\xf4\x8d\xfa\xbf\x93KQ\xce\x13\\\xe4?-F\xb4\xc1J\xf4\xd6\xbf\xe3I\xad\xea\xc2,\xcd\xbfhM\xe4S\'b\xf6?\xd3\xe6\xa4\xb1V\xea\xd5?\xde\xd1<\x1ed\xc8\xf2?\x8e\xea\x18X\xfa\x9d\xd5\xbf\x11\xee\x1a\x0b\x95\x86\xf6?\xeb\xfc\\\xc9\x0f\x0b\xe7\xbf,-\x99\xc4\x94\x81\xf9\xbf\xc1\xcd\xc9`\xb3\xeb\xec?\xfc\xf3D\xcbH\xb6\xd0?\xc5f\xc8\xbfP@\x00@(\xdd\xd0v\xc0\x1a\xfb?k\x8f\xd0a\xbf\xd2\xd0?R01\xf9%8\xe5\xbfE\xe6r\xfc.\xda\xda\xbfO\xc0\x02I\xda\xee\xde?\xc0%l\'\xa0\x8c\xde?1\x81l\x12\xca3\xe3?\xf6d\xdf\x07\xeen\xf9?\xeb@\xa4\xae!\xc6\x00@)bU\x9f\xad]\xeb\xbfF\x8d(}z\xbf\xe0\xbf)"\x03\xe4\x99\x1f\xe0?(\xaa|\xb0K\x80\x8f?\xe8\x91\xad\x1a4\xcd\xe7\xbf-TE\xda\xca\xfb\xdb??q\xcf \xcda\xd7?,4\xa2\x1e\x8a\x00\xe2?;\xa5\xac\t~\xd7\xe1\xbf:K\xed?J\xd8\xf6\xbf\xa9\x93,\x14(O\xca\xbf\x1b\xae\x95u\x1b\xb8\xf0?\xca\xce\x1a?\x8c\xc2\xdd?\x9fz#\x05\x16\x92\xf3?l\xce\x19\x8f\xcc\x84\xa8?\xec\x16a\xa1\xd8\x91\xef?p_\tG\xa8\xdc\xf4?%;,\\\xc9R\xe8\xbf\x9e@\xcd\x12a\x9c\xd1?\xba\x16~"~Q\xf2\xbf\xab\xa6\xfb\xe7\xe6n\xff?\x93s\xa8\xf6\xc1\xd7\xe7?\xeb\xeb#7\x14\x93\x02@\xc6\xfd\xac`\xc6b\x01@\xa8\x00;\xdb\xe1\x98\xd8?\x0e\xf9\xfa\x85v\xc7\xf1?\x08kO\x80\x1c\xc7\xf4?&\xca\x93\x02\x960\xf6?\x8b\xac\xbd\xd8Ft\xf1?O\xa6x\x19|\xac\xb9\xbfJ\x01?e\x86c\xf3\xbf\xed\xe6B\xbe\x00\x8b\xd0?\xb886\x91\x9d\x08\xcc?~^c>\xf9\xc6\xd1?@p\x81\xe5U\xd3\xb4?\xc9\xb5Y\xd3;\xc5\xca?$o\xf1\x8c\xc2h\xe3?\x03\x12ND\xc2\x8a\xfe?\x7fO@8#/\xf4?=\x86\xd61 ,\x80\xbf\x1d\xa4v\x10\x80\x0e\xfb?\xa7\xae\xf1@\xb8`\x01\xc0\x8e22B>\xcc\xdd?\xc4O\xd2\xdd\x83W\x01\xc0\x910Z\xd7Y\x85\x8f\xbf:\xab\xa8KR2\xe1?G\xfb\xff\xa9\xcf\xe4\xed?lL\x0bOi/\xd1\xbf!H\xe9)I=\xad?3PH\xef\x0c\x97\xe0\xbfA`\xd7\xa9Z\xf8\xf1?\rC\xd6\xdd\xcf\x82\xeb??*\xa3x\x84\xf6\xf5\xbfG\x9d^|9@\xda?\x9e\xc9gr\xb6@\xf6\xbf*\xcf>\xe8\xda\x87\xfb\xbf5\xf1\xe08\x0c\x13\xed\xbfB\x9e\xcbG>\x06\xe9\xbf\xaa$j\xd2\xee\x06\xe7?\x8b\xe2\xffCM\xb0\xc9\xbf\xc4V\xd6\x97\xa3K\xe4\xbf\x18\xc4\xcd;";\xf2?7\xb51HH2\x91\xbf\x0ba\xea\xb2\xa1A\xc5\xbf' -tbag2 -(g3 -(I0 -tS'b' -tRp7 -(I1 -(I10 -I36 -tg6 -I00 -S'\x9b\x0c\xbd\xa0\xd5\xc7\x08\xc0\x85X\xfd\x05\x90P\x02\xc0~\x88\xd9\x10\xac3\x8e\xbf\xf2Ui\xba=\x1f\x18\xc0\xc6\xb3\xd7N\xcb\x8b\xe7?\x00w\xe2\xff\xdc\xe4\x05@H"\xf8\xa6\xce\x1e\x0c@\xf4r\x1d\xc0y\x95\xfd?\x93/|\xc3\xfe\x92\xf1?\x19;>7\xc0\x17\xf2?\xe7\xdb9\x84\x14\x9e\xf9\xbf\x97\xec\xee\xa2q?\x02@N\xebB\x1e\x116\xf6?\xc6\xa77\xb4\x05\xcc\xc3\xbf\xa4C\xdc\xf8\xa9\xc7\xd6?uH\xa1@\x16\xb1\xf5\xbf\xd9\xa9AC\xba\x7f\xf1\xbf\xd4\x96M\x19\xf9\xfd\x04@\xe0\xbb\xbf\x95T8\x11\xc0+xkLA&\x16\xc0\x7fN\xa3\x02\xa1\x0e\x00@\x8dBN\xb4V,\xfc\xbf\x0b\xe3\xb0\xba\xb9u\xfa?\x1c?\xa6\x17\xecx\x0c\xc0U\xdf\xb0\xd6\x01\xcb\x01@\x15\xd1\xf3Z\xbe\xa0\xe0\xbf3=,`\x18\x80\x06\xc0\x80\\*\x9f[1\x10\xc0\x1c\x15Y\xff\x94g\xf0?y\xd1\x14F\x94=\xfe\xbf\x94\xa5\xe9\xd0\xd4\x8c\x10\xc0\x8c\x9c3\x12\xdb\xa0\t\xc0[\xcbj%\xf6^\xd9?\xe8\xabMcG\xdd\x10\xc0\x82l>\xcc^v\xf5\xbf@\x8f{\xb1\x01A\xf9?fLU\xa4|)\x0c@"\xdd\xa4G!\x1b\xf3?r\xe7\xde\xf6\x05!\xfd?\x08\xee\x92\xccs|\xe7?\r\x08\xec\xef\x8aR\xf3?\t\xd6\xf4\x15@,\xd2\x95Zdy\xc7\xbf\xe7G\xc5\xff,\xd0\t\xc0\xccuPW\x87\x8e\r@\xbe~H\x1f$\xb5\x08@\x00\x1b\x0c#N\xb7\xef\xbf0\x85\xcd\x9b\xd3\xf3\xf8\xbf\x02\xb1\xdc\x00\xe5`\x01\xc0}\xab\xc6\xd9=\x90\x0f@#\xcc>\x91o\x9e\xfc?\xbc&\xc6\xf6b6\t@\xa1\xf3!\xa5\xb3\xff\xf1\xbf\xbf\xf7Z\xa4\xe3C\x00@\x1b\x1e<\x98A\t\xfb\xbf\xe6 F4c\xa5\x06@\x1f\xf8\xb0\xd7\xba\x04\xe5\xbf\xef\xc5D\x91[\xf6\x13@r\x0f\t\\\x9c]\x02\xc0\x05\x8c)uc\x13\xf3\xbfU\x07:z\xdd\xf4\xfd\xbfO\xbe\xf0\xd1\xcd\xcf\x00\xc0\x84\x01h\xc2J\xf0\x01@6\xcdo\xeb\x97\xa9\x13\xc0v<\xe3\x7f\x9d\x16\x0e\xc0\x1a\n\x90`\xa6_\x0e@k\xfb\x1d\x7f\x02\xb5\xe8?v@\xffl\'\xb4\xd4\xbf\x80%\xbal\x9c\xf8\x07\xc0\xab<\x8f\xaeR!\xf5\xbfJ)/\'YX\x15@@\xc6#\x90d+\xfb?\xc3ik\x0e\x01\xe8\xef?l\xbe\xaaj\xcc\x08\xe8?,\xb0\x15\x94\xc1\xb6\xf8\xbf\xb4\x9by\xe0l\x03\xfd\xbf\xdcr\x0c\x89\xa3\xab\x18@\xf6\xaabG\xb5\x9a\x10@D\xa0\x08\xc8\xf6\xc1\xf3\xbf\xc85\x7fM\xc9!\x03@a\x81\x8cH\xcf\xbf\x14@\xa8d\r\xffx\xc9\x13\xc0[\xb6\xbb\xb9n\xc6\xaf?\xcc\x9a?Q_L\xdf\xbf\xe3W6%\xe7\x16\xf3\xbf\x8c\xf6\xe6\xe6F\n\x0b\xc0\x0b\xf4\xb2\x11\x9e\x8e\xba?0,\xf9\x8d\xc1\x8d\x03\xc0p\xb5\xf9n\xa8\x87\xf7\xbf\xd1\xa1\x9b\x96~\x08\xe6\xbf\xf6w\x85\x92s\xd4\x0e@G\x91\xa3|8l\x0e\xc0\xb9\xf6\xb6<\xb8. \xc03\'\xb6\xe3y\xa2\x01\xc0\x16\x025pJu\x11@5\xb4x@gp\xf0?O`\xc9\x97U\x00\xf5\xbf\x19\xed\x90\x8b}\x9d\x19\xc0\x9ct+=}\xad\x04@\x02\x16\xe4\x0c\xad^\xdd\xbf\xdd\xc3\x86z\xf1\x1b\xf9?\x8e\x9a:\xfa3\x12\x0c\xc0\x83\xd8\xfb\x1d\x06L\xf0?fI\xa0&\xfa\xa8\xf2\xbf|\xaa\xf0\xdaMg\n@KN\xd1\xd0\x9aj\x11\xc0\xf8\xed=\x80\xeb@\x0e@\x9b\xb9iY\x0e\x1a\xf5\xbf\x92\x9aP\xf5>\x97\xc1\xbf\x8a\x9cD\x1bY\x9e\x0e@;J\xd2d\x9c?\x12\xc0t\xbb\x18(\x00^\xc2\xbf/@G>[n\x00\xc0\xcdr\xbd\xa1\xaf\xf9\x17\xc0\xef\xa8\x8e\\m\xd8\x1b\xc0\xf3\xea\x15}\\\xe2\x11@O\x92\x9a[rD\x0c\xc0X4\x84\xdbEV\x04\xc0yL\x86dN\x08\xf0?\x8a\xfe\xaa\xf2\xe9\x00\xf2?\xb5\xfc`#\xd8\xcd\xe5\xbf`?\x00\x18$b\xfb?~\xa0\xdc4\xac\x1a\x0f@\xb6\xa4\x11\xc6\xfe\x1f\xf5?\x9aG\xc6e9\x9a\xf0?vsR\xde\x08_\xc2\xbfe\xda\xad[i\xbb\x10\xc0*\xd8O[:\xc6\x10@Q\x82\xa3\x97\xc8\x86\x11\xc0\x8f\xff^\x9fd\xb6\x03@\xf8T#\xc7U$\x0f@\x0f\x06\x1a\x07\xabv\xab?\x92\x8c\x01\xf8e\xbc\x0b@\xdc)\xac\xdc\xd3\xf0\x12\xc0\x95=\x88\x1c\x1c\x8f\x04\xc0\x1c\x18p\xf3\xe4>\xcc?\xe0B\xb6\xa1\xfc\xc3\n\xc0\xdf\xealfg\xfa\x01@\xb4\xb4\xc4\xd5\xd08\xf7?\x92\xad\x08q\xd6Q\xe7?\xf1\xf1\x071L\xf0\xf3\xbfNC\xc8\xae\xa9\xb2\x0e@\xd0\xed\xa2k\xd7Y\x01@h\'\xfb\xd9"*\xc5\xbf\x87\xb2\x9d \xb3\x8e\x12\xc0j\xc7\xb0\xe8:q\x14\xc0\x10\x8c\x19F\xfcW\x1d\xc0|B\x92#9\xd8\x11\xc0l\xe2X\xe3\xc4\x7f\x07\xc08\xa9[).\xa1\xc9?\xecKX\n}\xb6\xe2?\xf6`Ce\xad\xe4\r@\x13\xa7X\x93\x0e\xeb\xf5\xbf\xf2R\xca\xd7T\xd9\t@\xa6U%\xce\x98\xd5\xf6\xbf\x84\xf4w9y\x99\x0e\xc0\xb8\xf0\xd6\x12\x08$\x11\xc0G\xc6\xde (\x87\n\xc0\x90\xe6|\x14\xa2\xcf\x10@\xf7Th\\\xe8\x04\x0f\xc0\xff\x1c.}6\xfa\x83\xbf5e\xf5I\xaaI\x1c\xc0R\x81\xf4\x16\xd7\xb7\x07\xc0\x05_\xaf!\xd1\x1c\xe7?\xf0\xb8($ x\x16\xc0\x9c2\xbc\xb5\x16\xcf\x03\xc0\xe6\x9d\x1d\xd4\x03\xde\xd3?\x15\xfc\xd5\x96\x87\xa3\xfa?3\xe0\xc3\\Ns\xcc\xbf\n\x88\xd4xgi\x0c\xc09\xd2\xa9\xde\x02\x0f\x10@\xb5m]\xee\xe6\x92\n@\x96\xcc\x87\xff\x188\x08@\x8f:\x9b\xf3\x1c\xd3\xf1?2~_\xcdk\xce\x03\xc0,M\xdc\x13\xd4k\x18@MA\xea\xd8\x9b\xe6\xff\xbfM\xf0\n\xd1\xdf\x8f\x08\xc0\xd9\xe9\xf2\x8c\xa7\x15\x1f@\x04\xdfY\rG\xeb\xd6\xbf\xa3\x90h\xa0v\x9a\xfc\xbf\xbb\xefr\x85\xacH\t\xc0\xce\xbcq\xee\x16@\xf5\xbf\xaa>\x0b\xf2*\xcc\x07\xc0\x13\x88\xc9Ia\xfe\xd7\xbf\xdb\xeb\xd8\x93\xcc(\xf5\xbfrCh\xb1\x1d\x10\x11\xc0\x9ct.w\x15\xd6\xfc?7\x029T\xf0U\x1a\xc0o\xca\xef\x04@n\xca\xbf\xed\x07\xeb\xb2Z\xc9\x06@\x1a\x87\xfa\x98\xb6\xd5\xff?\xfe\xca\x9b\x98\x04\xa4\x12\xc0\xb2\x18\xf6\xbfu\xc2\x8c\xc2d\x83\x00\xc0$\xc7\x02\xc12\xf2\xfd?\xd5\xbfH\xb7EY\x12@7d\xd6J\xc1\xe0\x12\xc0\x88\xf4\xad\x85\x9d\t\xe3\xbf\xde\xdf\xce\x1b\xb6\x81\xe8\xbfk^\x81&\xba\xfa\xf0?%\x16\x06\xea\x02!\xf6\xbf\xe6\xf7\n\x00#\xed\xfe?\x81\xec\x87~{n\x10\xc0\x1cv9\x17m\xaf\xbc\xbf\xed\x99=\x03]\x9a\xeb\xbf\x91\x06zR\xb5\x7f\x04\xc0\x1b\x07\xd1\x8e\xf9\xbd\x04\xc0\x91\xc1\x84\xb2\x18\x1f\x02\xc0\xcdF\xb3_\x08\xb0\x0c@\xc0\xf5\x1dI\x85\x9b\xc9\xbf=)\xf1\x13\xae\xb0\xf7\xbfq|\xeaZ:\x14\xe8\xbf\xac\x7fT\xcd\x93\x92\xec?\xd7\x82\x1a\xdc\x81\xba\xe9\xbfv8\xd2D\xcf\xca\x06@Ju~\x9c\xdf\xaf\xe7\xbf2\x1afn\x0e \x0b@\xeb\xf1\xf7\x92i|\x04\xc0^<\x02\x82;(\xf8\xbf\x11\x00\x19\xc1\xda\xa1\x00\xc0l\xcfCf\x08z\xf5?\xd9H\xd9lD\xd2\t@1\xf6\xcb\xe98\xfd\x02@P"\xcd\x91\x8f\x05\xfb?Oi6N\xf3\xdf\x11@' -tbag2 -(g3 -(I0 -tS'b' -tRp10 -(I1 -(I10 -I1 -tg6 -I00 -S'\xe4\xf0\xfeF\xc5\xaf\x10\xc0\xdfI0\x14\x82\xc1\x04\xc0\xb1\xf0D\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x00\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80nn=7\xe3TR?z\xc3\x00\xd1\xfa\x1bg?\x83\xa6\xc7z\xce\x0e;?\xad\x19\x85\xfc\xde\t\xf2>\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\xa1\x0c\x11M\x17\xc0&?\xb6(M\xb0\xd5\x9eC?\xb3\xc3c%\xd3\xd0~?\rg,\xc2\xac\xa4\x9b?K*\xd3\xddc\xf0\xb2?\x02\x9f\te\x89\xb8\xb8?F)\x0b\xb7xL\xab?y\xc1b*>\xea\xac?i\x8b\xc7\xbd\xe2W\xb4?\xb9\x99z\xd1]@\xc0?\xb2V\xb8\x10=e\xb0?s\xffV\xc3\x989\xae?\xe8\n\xce\xb7\xf6\x91\x8e\xbf\xca\x1d\xf5\xda7\x03\xad?\x9dd\xe3\xbd\r\x16\xa9?\x02\xffT\xc1\xe9n\xa2?O\x18\x10\xb3\xcf\x12\xa0?M\x19}"\xdb\xe0\x95?S\x03#\x12\xee:\x94?\x00--\xab\x10jn?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80I\xa6j\xc5B\xabQ?\xdd\x1b\xf11\xfb\xc1}?+\xd0hz\x9abc?Q\xbe\x8c\xc6\xc5}|\xbf\xa31mt\x1c\x04b\xbf(\xfb5i!\x13\xc4?\x02vt\xa329\xce?\xf7\x0e\x95-\xf1(\xa0?A\xacfP\xd9\xa9\xb6?Y1BG\xfc\xc6\xc8?r\xb3\xf2\xa7\x99\x07\xb1\xbf\xac\x04Ia\xcak\x98\xbf\x1d\xf7\x08\xad\x12U\xcb?\xc4\xec\xadfF\xd2\xb9?\xeap]\xe3\xaco\xc4\xbf\xc7&\xf8\\\xa6{\x97\xbf\xa5\x93I`\xc3(\x89\xbfsNKgI\x04\xb7\xbf{k\x9e!\x07\x19\xad\xbf\x9c\x8dC\x16\xde\x8d\xb5?\xc32\xfd\xe2\x0c\xcb\xa7?\xeb\x02`&-7\xb3?\x08|a\x90\x8aY\xa4?\x12\xd7Qw{Tz?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x97\x17)\xd62\xc3c?\x86\x9f\xf8\xb0\x80\xb6\x92?,H\x84|1\xc3i?\xb7\xf5\x91\xc5\x13!\x90\xbf0[\\:\x11Z\xa4?D\xfeM\x1e\xa4)\xc0?>\x851\xd6\x0b+\xcc?E\xf2G\xfc\xdc(\xb8\xbf\xab\x85\xd2\x13\xf5\xbb\xb7?\x02+\x8b,\xae\xcd\xcf?c\xa3\x9c\x08\xcf\xe9\xbe?\xda\x8d\x9aF\x9f\xf6\xc0?\xf6C()e\xe8\x90?\xef\xbc\xdc\x82Z\xf3\xa2?a\xeey\x91\xc6"\xbf?\xf3I\xf2\xa9\x15\x97\x96\xbf\xad\x0c\xafF*v\xb1?\xbf\x075\xaf\xa8\x82\xcc?O\xc8\xaf3\x11\xe9\xc7\xbf\xb1!q\x83\xcfw\xb5?+}&a\x8f\x12\xa5?\xe7\xbb\x89\x84a\x1f\xd2?\x82\xd0\xc8N1\xc3\xc8?c\xb5,3E\x98\x90?RE\x92\x90{\xc0g?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc4\xb3\x8a\xce\x864?qF\x87\xeb\xc2%:?\x99%l\xc6\xaa\xba>\xbf\\\xfb\xfa\xfb\xf9y\xbc?\n\xfe\x1d\xf9\xc8-\xc2?\x88DvHQ\x9a\xa6?\x13\xed\xe4.\x93O\x98?x7\x19\x802\xf0\xca?\x13q\xeaF\xb7\xce\x95\xbf\\\x00\xff:\xa4A\xb0?\x8a\xe7\x9c\xdc\xcb\xfa~?@\x03\x80x\x08\xd0\xbb?\xd3\xbc\xd8\xd2H\x1bl\xbf\xc4\x8f\xbewy-\xc5\xbf\xc7\x8a\x0e\xc5\xe1\xb7\xb7?\x16odh9\x08\xb1\xbf\xf3\x06\xe3\x08Dm\x8c\xbf\x9e9\xf5\xd7A>\xb0\xbfX\xee7\xde\xa7\n\xa3?\xd2\x18\xbdY#\xb8\xc3\xbfH\x83\xed{\xb0\xe3\xbc?<\xba\x1aX8\x87\xd3\xbfd\x99\xc9\x91a\xbci?\xabW^\x18\xa5\x08\xc2?3\xbb\xbc\xbf8i\xa4?\xa5!\x9f4\x8e\xd1y\xbf\xcb]\x1e\x06\x12\x14H?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80\x16B\\Qa;\x9a\xbf\xd3<\x95\xc5\xa3\xa9\x9f?\xb9\x07\x03j$f\xb9?I-\xab\x0b\xfb\x84\xbd\xbfAS\xf11g y?\xc2\x8e\xd2\xb3\x1d\xa8\xc3\xbf\xaae?\x1f\xfc \xb3?Y\r\xff\x82H\xc6\xc8\xbfr\xd6\xd7j\x17t\xa4\xbf\x1d\xe4\x16S\x0e;\xb5?\xaf\xc1\x82G_\xf6\x97\xbfaC\x87\xfb\x14\xe3\xbc?)\x11\xf3\xba\x08\xc0\xb2?-\x82Zc\xaf\xc2t\xbf\xff\x18\x18\x94J\x0b\xb3?\x16\xdf\xb0\x8c\x90\xac\xb5?\x10\xa7+\x91\xe4\xa1\x8b\xbf\x8f\xb2]Xon~\xbf\xff\xfc\xcbY\xcd\xd3\x9f\xbfd\xee3\xcb\xae\xde\xc8\xbf\x15\x9cG\x90\x0e\xd7\xce\xbf\x07W\xedA\xd9\x15\xb7\xbf$M\xc5\x01\x8bJ|\xbf\xdd\xa9*\xa3B\xf1\xd1?Z\xaf\xe8\xbd7\x01\x85?eq\xb8\x9b\xb9\x88\x9b\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xc3"\x89\xa21\x8d\x18?\xc5\x94x\x14\xcb\xb9\xa2?}\xec`\xcd\x98\x91\xc2?\x00Vj\x04\x86\x87\x91?A\xc2f\x1c\xccp\xaf?\xd1\xe9\xe5\xf2Ro\xc6?\x05Sn\xce\xb3\x1b\xba?0\xf2\xb9>\xff\xa9\xc0?\xf2\r\xa2\xd6\x1f\xa0\xb3?J\x84)\xbf)j\xa5?\x7fP\x04yE\xa5\x88?G3\x84\xb8\xb8\xc1\xb2?\xa9]\x9a\x01\x9b/\xaa?\x9b\xf3\x9ejZ-u\xbf\xaa\xfd\\\xfb`\x07\x8d\xbf\xce&\xbeP\xe9\x99\xaa?`J\x80f\x0bC\xae?G\x08\x9f\x9d9\xd1\xb7\xbf\xd1\x13i]\xa6\x90\x97?pEFdGK\xa0\xbf\xb6\x0f\x9ew\xbb\xe5\xb9?\x8a\xc1\xee\xa8D\x1e\xca?\xb8\x1cn\xcc\xe6Z\xac\xbf\xb9t\x89\x88`\xe3\xb0\xbf\xaer\xc2\xe1\x97\xe8\xbb?{\x9ao\xb1n{\xc3\xbf\xb6\x876\x7f@gs\xbf\xd4\xe2\x95~\xa6o$\xbfg\x1b\xda]\x1f\x16Q?p\x9c\rG\xa8;\x9f\xbf\xb9\xd7_\x14\xe9\xbf\xc1?\xa6\x07\x96\xfb\x07v\x92\xbf\xef%\xb0\x15\x07\xfc\x9b\xbf\xeb\xc7\xcf7\xee\x9e\xc1?\xb2\xaf\xe9$\xb0o\xc2\xbf\x8bm\xfd\x147\xd2\xbe\xbf\xc7\x13\x984\x1fT\xce?\xadp\xfd:\xa4\xde\x9e\xbf\x14\xc0\xcd#\x9ev\x93?M8\xf7\xdc8E\xc2?HC\x07R\x1e\x9d\xbc\xbf\r\x19\r\x02\xee\x88\xb2?\x95\x8a>\x94\xc1\xc6\xb3?N\x8f\xb97\xd0\x96\x87\xbfm\xd7\xdd4\xbb>\xb4?\xb1\xebv\xa8\xf6\x8e\xc1?\xb6\x94g?M\xdc\xb7\xbf8\x83R\x13\x07\xe9\xbb\xbfL,\x93o\xaa_\x9f?\x10>\xe7\x0f\x96\x1b\xc8\xbfp\x07\xe2\x81\xcc\xd0\xa7\xbfd\x10}W&\xa1\xb1\xbf\x02\xfc;\x18S\x8e\xc7?y8\x9e\xad\xee\x95\xaf\xbf\x1d\x05\xde\x97\xc0\x0f\xa5?\x01!\xc1W"\xc3D\xbf\x94\x9b\xc1+\xed4x\xbfI\xc0[\xe8&u\xb1\xbf\xd8\xadT\xf0:h\xaf\xbf\xa9R\xa9F\x18\xa0\xcb\xbf\xa2p\xb1\xf5\xe3\x8fk?\n\xbb\xf6\x12\xca4\xc9?\x16\xc0\\ak\xec\x8d?NK\x1d\xf5\x00@\xbc?~p$\xa2\xa9\xe3\xa4\xbf\xffc\xe0\xa3\xa7\xdb\xcc?\xf3\x87\x10\x93\xff\xaf\xad?\xd8j\xbf]x\xf5\x98?\xd1\xe5B\x921B\xa3?\xe5\x97\xb0\xfc\x00b\xb7?\xc4\x8e\x85H\x16\xeat\xbf$\xd0l\x9dx\xfe\xbe?\x8fsO\xb4*\x02\xb0\xbf\xaa\x12\xb8\xc3\xf4\x13\xa5\xbf\xf1\xd9\x9eG\xf2\xad\xb2?jF\xbc;?\x89\xb5?\xff\xe2\xdd\x16\x08\x81\xb7\xbf\x0b\xa5f\xc6\x18\xa9\xa2?\x0fqdo\xb2\x06d?\x02;\x0b\xe8\x00\x94Y\xbf\xa4\xcd\xab\xa3\xc7\x0c\x94?l\x02\x0b\xcb5U\xad\xbf!q\x10\xd4/\x8b\x85\xbf\xf9B\xa6\xcb|`=\xbfPS@)\xf8\xf6\x92\xbf\xcd\x02\x96\x97\x80_\xa3\xbf\x83\xa0\xe3\xb59\x7f\xa3\xbf\xb1\xc9\xe2b\xd9#\xc1\xbf\xf2\xcb\xd0\xb6\xaa`\xae?,\xd7\x8f\xdd\x18c\x9e\xbf+\x01p{\xc5\x80\x97?\x9b\xe3&\xf9\nG\xc0?(\x87\xa2\xa2\xd3!\xa7?\xe5pgmsd\xa1\xbf5)\x0e \x890\xaa?\xcc\x908\xaeH\x02\xa0?R>W\xaa#\xe8\xa4?\x1f=\x9c\xbct\xf0\xc1?\x91UL\x98\xe3\x1e\x93?\x18xd5.\x06\x80?\x84u\x83HL\xd2\xc1?\xc0\x9fw\x97M\xb1\x9a?\xcbRxO[-\xb6?\xee5bA\xea\xf2\xa1?\x92\xa0\xc0o\x03\x95\x92\xbf\xcb\xc2x!y\xef\x90\xbf\xfbv\xc9\xaa-\x83\xc8?\x05\xf6b.I\xd1\xa5?dv\xf8F\x17.\xb0\xbf&.\xf1\xdc\xe8\xa9\xa1?\xfa\x0cq^\x90\x1c\xaf?\xcamB\xe6\xa6TK\xbf]\xed\xa7\xa1@\x96\xa0\xbf\x00\xc4\x81\xf8q\x96\xb3\xbf\x1c\'\x01\xb6\x04\xea\xa8?<2\x91\x1fhc\x9d\xbf\xbc\x95\xe8\xa2\x9e^\xc3?\xd5\t_\x06\xf0\x99\xb2?\xd5%i\xbdH5\xb1\xbf\x9d\xd3%\x93\x0f\x99\xbd\xbf\xd1\x0f\x7f\x19\x01s\xcb?\x94\xa68\x99d=\xa2?\x98\xac\x1dd\x96\x82\xb0?\'\x82\xb2\x93_\xba\xc5?\x8b\xd9\xf4.\x94?\xa9?\xb0~pG\x98\xae\xbb\xbf\x13y\x01q3\x8a\x9c\xbf\x82)\xde\x890\x98\xae?\x98\xcb\xab\xc0\xdf/~?P\x8f\xfc\x06\xdbp\xc1?\xe0q\xf3\x04E\x1b\xb8?ek\xd6\xda\x10\xed\xcc?R\x8al\xf8\xff\xa1\xbb?7R\xad~\xe30\xba?\xcc*t$\x00\xe2\xc1?\xc1K\xa7\x00+#\x8d\xbf\xf2Y\xc7w\xda\xae\xd2\xbf\n\\\x98\xb0fx\x92?\xda\xea$\xc2\xe5\x92\x87?\xd1\x08\xae\xe7\x05\xba]\xbf\xb2\x90\xd1^\xd6\xde\x91\xbf\x17\xfc\xfd\xa5\x0c\x03\xa6\xbf\xf9\xed\xb6-\xda\xc2\xa1\xbf\x05ag\x07\xabt\xb1\xbf\xdd\xc5\x8e\xf3\xb4m\xcc\xbf\xd8\x141\xb6{O\x9a?\t=g\xe2\xd3\xb5\xc6?\x16\xbc\x89\x87\xc14\xb3\xbf\\OP\xa1\x84\xf1\xa1\xbf\x14\x81\x03\xaa\xa2d\xc2\xbfO\x12\xd1\x97\x8f\xa4\xb6?\x15\xa9\xac\xbfO\xdey?\xeb\xf12\xa4\x96\x01\xb2?}Eqz\x93\xc7\xcd\xbf|\xd6\r\x90V\xee\xcb\xbf\x8b\x916\x80\x97"\x9a\xbf\xfcW\xa0\x87@>\xaa\xbf\xca\xa3\xa3\x82 6\xb4\xbfr\xe4\x08\x92\x02my?\xd5c\xe4\xf9\x1f\xec\xb9\xbf{<\xa1\xf8\xb1N\x91?LC(a\xddg\x82\xbf\xd3\x87\x9c\x84\xbd\x1e\xcd?\xec4\xddr\x03\xd7\xb5?\xeb\xfe\x1e\x80\x1d\xf1\xd3\xbf\x82\xdc\xde\xf3\xf6\xa5\xb7\xbf\xa7\xb2\xfc\xec\xc7\x9e\x8f\xbf\xcc"\xf6\xbf\xae\xbcT\xbf\xc6\xb2v\xc4\x7f\x94r\xbf\xc7\xc1\xc1\x10\x88Fk?\x1f\xcd\xc0t`\xb5\xb2\xbf\x90*\xf7\x15\xado\xb5?":a\xd6\xf26\xbc\xbf^\xd0\xbd\xcf\xfam\xb4\xbfF(\x85\x16&\xd8\xc7\xbf\xc1\xce\xd6x\x9f\xee\xbf?R\xd6~\xb1\xb1\xa6\xb5\xbf\xcf\x98\xe3C&C\xc4?4\x08\xf7:Sc\xca?\xbc\x0b\xf4\xd4\x83\xb1\xc5?\xadj\\\xa8\n\xeb\xb8\xbfY=\xb5\xfe\x90\xdd\xdd\xbfDX\x8ecm\xd5\xcb\xbf\xb9\x17D>\xeam\xc0?\xbf\x8f\x0ev3p\xb5\xbf\xb5b\xae\xe557\xce\xbf,S\x13f,\xbb\xa7\xbfOY\xa3\xd3\xdc\x1e\xab?Xi\xd2\xbf\x9e$u\xbf\x9e\x08\xb9+\xa9\x93\xbb?\xc2\xf4/\x17\xf7\x13\xd3?\xd1\xc9\x89\x15\xbd"\xa2?:\xd8\xed\xfc\xf68\xc1\xbf\xf3.\x92\xad0\xf6\xb5\xbfG\xa6\xf7\x05~\xe9\x83\xbf\x1cK\x81\xd6\xa9\xf99\xbf\x88.\r~\x9b\xa6\x8f?_\xca\xa3\xcc\xcf\xcdq\xbf;\xe7a\xad\x96\n\x9e?\'9!\xcf\xbb\x17\xb5\xbft_}E-\x00~?m\xe2\xbe#m\n\x83?\xfd8\xba\xaa\xab\r\xb4?\xc3\x07\x86"\xd4\x1f\xc1\xbf\xff\xb2\xe4\x99#3\xaf?\xdc{"\x1a~"\xd0?Rc\x814{\x95\xc0?\x98\xb8\xd5\n\xae\x8a\xbd?\x82\xf6\xc9\xc3\xf8\xa9\xca\xbf;mc\x8a)w\xde\xbf\xe1\xa0\x927\x18\xc9\x9d\xbf\xd2@\xc1xX\x88\x9c?S\x8f\xa3_\x80\x96\xb1\xbf\xf7\xe6\x8e\xa7\xfeF\xc8\xbf\xe40\xef\x06\x18\x0b\xb6\xbfJ\xb8\x81\xc0S3\xcd\xbf\xe7\xd1m"DP\xb8?\x93\xc1\xbf\\\x0eQ\xb4?\xcf\xb2D\xf7M\xf2\xbd?N\xf9I\xcb_\xe1\xca? \xcfW3E\x07?\xbfbE\x9d\x8fBe\x88?\x05J=\x04y\xedj\xbf\xb9\xe4\xa9-\xd9\xe0g?\x11\xbbx\xc6C\xc9Q?\xf7\x8a\xb3,\xd0v\x89\xbf\x93$`g\xbeV\xb7\xbf\x02F\xa6\x9f\xb6\x9a\xd4\xbfz\x00\xf2\xcb\x85\x88\xcd?\xd28\xf4\xe0\x92t\xae?\x9f\x95\xe3\xa2M\xd8j?\xb6A\x7fz^\xf8\x89\xbf\x07\xb5On\xbd\xbf\tz\x1e\xdf\xf1d\xba\xbf\x82\xc1\'\x94\xd7=\x7f\xbf\xc0\xba\xde\t\xdeb\x9a\xbf\xb8\x16\x08\xe7\xecS\xac?\xf1?W\xb3T\xa9\xc5?\xcd\x98\x0c\xdf\x9b\x0b\xc5?[\x1cV\xdc\x0e\xfbe?\xad\xce\xa8+\xe0C\xb7\xbf!\x86\xfe\x0b2\x90\x91?t>\xf2\xf2Y\xa8c?bf(\xe7\xd0\xf38\xbf\xf1\xcc\x1bQ\xea\xd9V?\xc5\x16Q|\x12\xff\xb0?\xd9\xc9\x96\xc0\xe6~\xc6\xbf\xe6 ?\x08^\xc7\xb4\xbf)\xca\xfc~m\x06\xa9\xbf2\x13\xc8\x96\xdeY\x81?D\x05\xe3\xd2^\x9a\xa7?\xc6\xbe\xfd\xae \x16\xa5?\xb8s1\xe4;\x9b\xb1?\xd0 \xaf\xa3s\xc4\xc2?\xf75\x9ec\x02\xb0\xc4\xbf\x8cO\x8e\x12&\xfb\xbc\xbf\xf6\xb8\xa4\xd7\'\xaf\xd4\xbf\x12\xfb\x11\xc7\xcer9\xc1?m\x85\xf8\xaa!e\xc2?\x18S7_S \xb0?Q_\xee\x1c\x97\xac\xa1?e\xe9=\xb2\xcd\xb4\xaf\xbf\xbb\x12V\xe5\x13p\xd3\xbf\xd7\x86F\x11\xb1?\xb7\xbfF\x00\xf7z\xbd\xf9\xc2\xbf\xc2)1\x15\x19\x07\x93\xbf\xe6k\x80\x8e\xae.\xcb?\x91\xaaZ\x0c\x81\t\xc1?\xf9\xda\xa8\x98\x1f\xe1\x95\xbfw\xb41\x9e5\xadr?f\xa9h\xb4\xe4\'\xa7\xbf]-Q\xbaMy\xa2??D7I\xac\xfc\xb7?\x8dQ\x88y\xf8\xc4\xb3\xbf\x1f<\xf7icT\xc0?\x08\xc0>:\xa2#\xa6\xbf\xb9\xe0SAb\x06\xbd\xbf^@\x84&\x99\x8a\xb4\xbfD2\xa1\xc2\xa4t6\xbf\x1e\xed\xed\x9c\xb9vH?P\x83\x80\xe0R\xe9\xb1?\x8ft\xb2&h\xa5\xc4\xbf\xc0\xd8\x15\x84\x8e\x01\xaa\xbf\x0eC\xd3y\xe9\x93\xb4?\xbbzU\x82\x9e\xfb\xbb?\xd8\xbdY`K\xd5\xd1?8!er\xac\x08\xc5?Y\xa9B\x03\x05\xdb\xc7?\x0715\x81\xee\x93\xbe?\xa2\xb5qxs\x94\xad\xbf\x9f\x04\xe2j\xdd\xbd\xce?\x01i@v6\xae\xc6\xbfH\xcb\xfc\xdc\x97]\xa3?\xc0\xba\xda\xc7W0\xa6\xbf.\x10Z\xf7A<\xa0\xbf\xeash\xb6J\xb8\xab?\xd7\xb0\xfd\x1fe\x1f\xb6?\x96wI~v\x8c\xa2\xbf\xfa\x12\xae\xe4\xd46\xb6?[\xb2\x11\xd1`\xa2\xba\xbf6\xc1W\xbexI\x83\xbf\xa5\x1a\xce\x01\xa87\xc7\xbf\x0b\xdf\x12\x14\x8d\xc4\xc0?\xf57)\xda)\xb9\xa5?\x8c#\xdfKe\xab\xc1\xbf\xa2\x12\x8e\x8e+~\xb1\xbf\x00\x00\x00\x00\x00\x00\x00\x80\xa7\xba\x1f/\x17\x83o?\x88\x8b\xe9\x99\xff\xaf\xc8?\xc4}VwH\x14\xb0?\xc1=)\x9d\xe8\x12\xbc\xbf\x93%\\C\xf3;\xab?\xa0\xf59\xc2s\xbe\xab?`H\x90\x9e$\xac\x93?\xe6=\xbc\xfa\x85\xcf\xbc?\xdfe\x8cX\xc9\xcb\xa3\xbf\x98\xd0\xff\x87\xd8\xcb\xb0?\xc5>\x91\xc1\x1aJ\x97?\xd5\xe0\xa0\xe0\xa7\x8a\xbf?\x0f\xe5\x1ai\xff\xc2\xc4?=\xa6\x9f\x90\xa8\xe7\xb6\xbf\x8d\x88\xa5\xb0\x9a;\xa2\xbf\x81\xea\xb9\xa7\x81\xf5\xb3\xbf\xfa]=\xee\xcd\xe4\x98\xbf|\x91Q\x92\xe0/\xb2?\xed\x18\xef\xa3\x80r\xb5?\xa8;\xf87\xcb\n\xbd\xbf\xb1\xf9\xe6Q\xe99\xc0?\xd4\xb6\xa9\x90\x81s\xa2?>\x85\xc3x\x93\x83\xc0\xbfQ[6\x05\xf1\x04\xb5?\x072\xd1j\x80\xac\xbf?\xe0pie"w\xa5\xbf\xd18\xf4\x19\x83\xc4\xd6Y\xd2\xce\xc3?ml\xa9+6%\xb1\xbf$\xd8\xfe\xcc\xf7G\x9e\xbf\xe1\xa6b\xfc\xb6\xc1\xb9\xbf\x14\xf6#\xbfh9\xba?\x11\x05:\x9b\xa5\x94\xb0?\xc3\xe4k\xba\xeb\xc3\xa0?\x9d\xf4\x8a\x1aeL"?[\xf0J\x85\xd0&:\xbfx\xde\xdfO\xb2\xcf&?\x0c\xfd\x92\xdf\xa2\x0f\xaa?9:v\xb4\x18n\x82\xbf\r\xc1F\xdb\xa1\xff\xb2\xbf\x07\xac,\xa0\xfeU\xbc\xbfDr\\\x00\xf7\xc9\xb2?:8\x14\xed\xfc\xab\xba\xbfO\x05\r \xc0-\xc5?X\x11\xdb=%\xb7\xc8?\xac\xe2\xe6\xd0\xa95\xc3?\xe6\xd9\xff\x8d\x13\xdf\xbe?\x1e\x19X\xc9\x17\xd7\xc6?\xc0H\xd1\xf6\xc02\xd8?\xdd\xbf\xf9@6\xa4\x99?\xa4$\xc9\x07\xf6W\x95?\xec\x80^\xdfT\x87\x94?\xea\n\x81\x92Q\xb7\xb2\xbfm\x0e\xca\xe4\x03<}?:\xdfe\xaf\xd7\xe6\x9d?\xe2\xe0\xbeIi\x9c\xba?\x04\xe5\x8a\xdeF+\xc9?\xda\x10>\xa1\xaf\xff\xbc\xbfB\x12S?\x1fi\xa5?\xd7\x08[.\x06\x88\x8c\xbf\x84e\x012\xc6D\xad\xbf\xecR\x84x\x97\x88\x92\xbf\xb9\xa6\xa7\x8e\x0e\xe40?\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x0f\x1b\x01\xea\x1dS\x95?\x85L\xf7\xc2\x97\xff\xc9?\x81F\x1f6$\xcc\xc4?\xf9\xb75\xb7pAr\xbf\x96\xca\xa4\x7f\xd6\xc6\xd2\xbf\x1c\x0f\xf7.t4\xc1?\xcb\xb98v\xae\x1b\xb1\xbf\x1fT;\x87s\xc6\xa1\xbf\x0ex\\\xdd\xe2\x00\x87\xbf\x87z\x1dF-.\xb7?vo\x87\x16}\xaa\x8e?\x9f\xaf\xcf\xf5\xcc\xcd\xb9?\xa1\xd1y=\xa6\xaf\xcc?\xc7P\xb8{\x91\x01\xc0?\xca-00\x9d\x86\xa7\xbf\x97\x1d\x83\xac[\xfa\xb7\xbf+\xb8A\xb4\x86?\x92?\xe4)\xb8\xfa\x16\xea\xc0\xbf\xae\xfc\xe7\x8b\xc7\xe0\xac\xbf\'\x83\x05\xe1/\xef\xb2?n3H\x9d\xdc\x18\xba?\x18\xb6Gq?f\xa2?\xac\x0c\xbdn\x8e\t\xa0\xbfI\xc3\xc84\xbb\xdf\xba\xbf\xe8\x06u\xcc&\xc7\xb0\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80F\xe3\x92\xaf_x\x1e?\xbb\xae\xf6\x14ot\xc5?\xbc\xcf\xc8\xb7\x80\x93\xcf?"N\x1d\xa0\xeb}\xc2\xbf\xcb;c8\xb8\xce\xc3?\xac[\xec\xf6&N\xa4?\xa6\xf9\xfa\x15\xcb\x88\xb0?m\xb7Qh\xa1\x14\xa7?7i?\x00\n\xb0\xb6?C\xf8tX\x1a\x03\x93?U\xac\xf0e\xf8\xc3\x94\xbf\x92\x10\xc8\xc0q\x9c\xb7?\xd6\x1e\x8d\x06&\xe5\xb6\xbf\x93\x05\x80\x93\x0b\xe4\xb5\xbf\xc4\x12\xd3\x9a\x08\x81\xb3?\xdd\xcd;\x16X\xd4\xb4?EU\x83\xe2W\xb8\xcc\xbf,&L`\\-\xa8?\x01v\xa8\xe1\x94c\xc1\xbf\xa4\x91P\x0bg.\xd0\xbf#\xfb(\xdf\x1c\xe0\xbb\xbf\xe7\r\x93\\\x01F\xc2\xbf\n\x8c\xfa\xff\x0c\xe5\xa4\xbfm\xcdn\x91z\xda\xa5\xbf\xa4\xd9%\xb4d0j\xbf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00(\r\xc4\xafz\x7fL\xbf\x8f\xb9 f/\r\x88?/\x05[\xb8\xf0~\xb2?Zs\x187IJ\x94\xbf,!\xc8\xaf\xeb\xd2\xa2\xbfX\x9b\x1a\x7f\xbc\x90\xa2\xbf\xca\x95\xe9h\xba\x04\xb9\xbf(\x98@\xa2\xf3\xcc\xa3\xbf\x1e0\x86\xd2T\x06\xb4\xbfF\x91\x84\x9c\x8e\xef\xb1\xbf\xa4xX\x054\x82\xb3\xbf\xd3\x11\xde\rT?\xb4\xbf\x9c]\xa2\x9f\xad\xd4r?4\xd6\x06a=\xd2\xd3?$\xe0\\\xf3\xfb\x83\xb7\xbf@\xe3R\xf8)g$\xbf\xd6\xf2\xaa>\xe6O\x8a\xbf\'\xc9\xb5\xf7w\x15\x99?\xc0\x16\x8d\xd7\x17u\xc5\xbf\x12Q\xd3.}\xb7\xbd\xbf~\x18s\x9e\xdb\xef\xa4?\x1c\x82\x08\x96\x88#\xba\xbf\x83\xa8\xce\x19\xed\x96`?S\x0f_\x1d\x83\x9fj\xbf3n\xee\x0c\xf0\x98\x1e?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80rR\x1dL\x80\x14<\xbfK_\xeem\x9aN\x8b?\xae\xc0\x88\xff\xc2\x9d\xb4\xbf\xe9/}\xfe\x81C\xc1\xbf\xcfE\x9f\x01\x11z\xd0\xbfB\x18\x07\xbc\xc9\xe1\xce\xbfn\x80\xac\xacR\x9a\xd1\xbfM\xe3\xe3\x80\xaa\x02\xd1\xbf]\xb71\xef\x0fO\xd3\xbf\x9fK?\x8cq\xc2\xce\xbf\x9a _\xea#\x0b\xcc\xbfY5y|P\x8c\xcd\xbf\x1a~\x0e\x03\xe9\x94\xd4\xbf\x96\xa0/\xa2g\xf0\xca\xbf"\xccrj\x14\x07\xc3\xbf\x18\x07\xa8\xd6\xf5\xce\xc5\xbfB\x17i\xe2\xa4\xc2\xb0?\xdcD\xddL\xbcU\xae?\xe7\xf4_\xd5l\x95p?\x86\xdd\x80\x88\x85\x18\x8d\xbf\xb6\x1f\x99z\xb5\xb6\x90\xbf}\xbe[\xe0\xc7i\x11\xbf\x18\xba\xe1\xd6\xf4p\xfd\xbe\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x98A\xca\x0b\xb1\xfeS?\x83\x91\xa6\xcb\x8f\x1bq\xbfB\x01\x15\xd5me\x98\xbfv\x1e\xc2=8\x06\xa9\xbf\x0e<\xe9\x81,F\xb5\xbf\xd0\xf2\xc5w\xe2\x97\xb6\xbfN\x81\x10F\xef\x16\xa9\xbf\x92FfDb0\xb9\xbf\'\nt\x82\xf5\xbe\xc3\xbf,\x11Z\xd5Jl\xc8\xbfU\xc5X%\x82\xf4\xd1\xbfD\xf0\x9c\xcbG\x17\xce\xbf\xae4\xf4\x8a\xb4\xc1\xc4\xbf*\x1c\xf8\xed\x1c\x16\xb5\xbf\xa3\xa7R+\x9a\xc2\xab\xbf\xbd\xb4e\x1d-\xb9\xa4\xbf\x14!d\xd4`2\xa3\xbf\x0e\x00\xf1\x13\xdf\x83\x94\xbf\x10\x1dp\xd4\xdc\x90\x93\xbf\xde\xa7\xed\x87\xc2Re\xbf\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80' -tbag2 -(g3 -(I0 -tS'b' -tRp7 -(I1 -(I10 -I30 -tg6 -I00 -S'\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\x832C\x89w\x81\xf0?\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xe1"\xf1t7 \x1f\xc0\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbf\xfc\x8a\x9a\x9f\xe0\x9d\x86\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbfgq\xd8\xbf\xd6\x18\x90\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xfeq\xd7\x8f\xca\xc4\xb5\xbf\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?\xe6)7h\x07\x1f\xa3?K\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbfK\xe5\x14\xaa\xa2\xe2\x83\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf\x85\x828\xd6%\xc1\xa5\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbf6\xb3\x037\xf8\xbe\xad\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbfX>k\xf4\x1e|\xb7\xbf' -tba(lp8 -g2 -(g3 -(I0 -tS'b' -tRp9 -(I1 -(I30 -I1 -tg6 -I00 -S'r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0r\xe6iE$9\x00\xc0' -tbag2 -(g3 -(I0 -tS'b' -tRp10 -(I1 -(I10 -I1 -tg6 -I00 -S"5a\x17\x8f\xbd\x949\xc0\xb8\x92\xd9\xe4\xbb\xe4\x0b@?\xc5^\x8a\xb6\xdf\x01\xc0\xa3:\xef@\xfe7\x03\xc0&t\xe5\x16\xbe\xb8\xf8\xbfD+\xe7B\xd5\x92\x04\xc0\x1a\x9b\xf1UVh\x03\xc0{\x92\xa7\xb1@D\xfc\xbf\xa3&Z\xc4\xfav\xfd\xbf\x1c6#>'\x88\xf8\xbf" -tbatp11 -. \ No newline at end of file diff --git a/from_3b1b/old/number_line_scene.py b/from_3b1b/old/number_line_scene.py deleted file mode 100644 index 2a19452b..00000000 --- a/from_3b1b/old/number_line_scene.py +++ /dev/null @@ -1,77 +0,0 @@ -from manimlib.imports import * - -class NumberLineScene(Scene): - def construct(self, **number_line_config): - self.number_line = NumberLine(**number_line_config) - self.displayed_numbers = self.number_line.default_numbers_to_display() - self.number_mobs = self.number_line.get_number_mobjects(*self.displayed_numbers) - self.add(self.number_line, *self.number_mobs) - - def zoom_in_on(self, number, zoom_factor, run_time = 2.0): - unit_length_to_spatial_width = self.number_line.unit_length_to_spatial_width*zoom_factor - radius = FRAME_X_RADIUS/unit_length_to_spatial_width - tick_frequency = 10**(np.floor(np.log10(radius))) - left_tick = tick_frequency*(np.ceil((number-radius)/tick_frequency)) - new_number_line = NumberLine( - numerical_radius = radius, - unit_length_to_spatial_width = unit_length_to_spatial_width, - tick_frequency = tick_frequency, - leftmost_tick = left_tick, - number_at_center = number - ) - new_displayed_numbers = new_number_line.default_numbers_to_display() - new_number_mobs = new_number_line.get_number_mobjects(*new_displayed_numbers) - - transforms = [] - additional_mobjects = [] - squished_new_line = new_number_line.copy() - squished_new_line.scale(1.0/zoom_factor) - squished_new_line.shift(self.number_line.number_to_point(number)) - squished_new_line.points[:,1] = self.number_line.number_to_point(0)[1] - transforms.append(Transform(squished_new_line, new_number_line)) - for mob, num in zip(new_number_mobs, new_displayed_numbers): - point = Point(self.number_line.number_to_point(num)) - point.shift(new_number_line.get_vertical_number_offset()) - transforms.append(Transform(point, mob)) - for mob in self.mobjects: - if mob == self.number_line: - new_mob = mob.copy() - new_mob.shift(-self.number_line.number_to_point(number)) - new_mob.stretch(zoom_factor, 0) - transforms.append(Transform(mob, new_mob)) - continue - mob_center = mob.get_center() - number_under_center = self.number_line.point_to_number(mob_center) - new_point = new_number_line.number_to_point(number_under_center) - new_point += mob_center[1]*UP - if mob in self.number_mobs: - transforms.append(Transform(mob, Point(new_point))) - else: - transforms.append(ApplyMethod(mob.shift, new_point - mob_center)) - additional_mobjects.append(mob) - line_to_hide_pixelation = Line( - self.number_line.get_left(), - self.number_line.get_right(), - color = self.number_line.get_color() - ) - self.add(line_to_hide_pixelation) - self.play(*transforms, run_time = run_time) - self.clear() - self.number_line = new_number_line - self.displayed_numbers = new_displayed_numbers - self.number_mobs = new_number_mobs - self.add(self.number_line, *self.number_mobs) - self.add(*additional_mobjects) - - def show_multiplication(self, num, **kwargs): - if "path_func" not in kwargs: - if num > 0: - kwargs["path_func"] = straight_path - else: - kwargs["path_func"] = counterclockwise_path() - self.play(*[ - ApplyMethod(self.number_line.stretch, num, 0, **kwargs) - ]+[ - ApplyMethod(mob.shift, (num-1)*mob.get_center()[0]*RIGHT, **kwargs) - for mob in self.number_mobs - ]) diff --git a/from_3b1b/old/patreon.py b/from_3b1b/old/patreon.py deleted file mode 100644 index dc3fc845..00000000 --- a/from_3b1b/old/patreon.py +++ /dev/null @@ -1,702 +0,0 @@ -from manimlib.imports import * - - -class SideGigToFullTime(Scene): - def construct(self): - morty = Mortimer() - morty.next_to(ORIGIN, DOWN) - self.add(morty) - - self.side_project(morty) - self.income(morty) - self.full_time(morty) - - def side_project(self, morty): - rect = PictureInPictureFrame() - rect.next_to(morty, UP+LEFT) - side_project = TextMobject("Side project") - side_project.next_to(rect, UP) - dollar_sign = TexMobject("\\$") - cross = VGroup(*[ - Line(vect, -vect, color = RED) - for vect in (UP+RIGHT, UP+LEFT) - ]) - cross.set_height(dollar_sign.get_height()) - no_money = VGroup(dollar_sign, cross) - no_money.next_to(rect, DOWN) - - self.play( - morty.change_mode, "raise_right_hand", - morty.look_at, rect - ) - self.play( - Write(side_project), - ShowCreation(rect) - ) - self.wait() - self.play(Blink(morty)) - self.wait() - self.play(Write(dollar_sign)) - self.play(ShowCreation(cross)) - - self.screen_title = side_project - self.cross = cross - - def income(self, morty): - dollar_signs = VGroup(*[ - TexMobject("\\$") - for x in range(10) - ]) - dollar_signs.arrange(RIGHT, buff = LARGE_BUFF) - dollar_signs.set_color(BLACK) - dollar_signs.next_to(morty.eyes, RIGHT, buff = 2*LARGE_BUFF) - - self.play( - morty.change_mode, "happy", - morty.look_at, dollar_signs, - dollar_signs.shift, LEFT, - dollar_signs.set_color, GREEN - ) - for x in range(5): - last_sign = dollar_signs[0] - dollar_signs.remove(last_sign) - self.play( - FadeOut(last_sign), - dollar_signs.shift, LEFT - ) - random.shuffle(dollar_signs.submobjects) - self.play( - ApplyMethod( - dollar_signs.shift, - (FRAME_Y_RADIUS+1)*DOWN, - lag_ratio = 0.5 - ), - morty.change_mode, "guilty", - morty.look, DOWN+RIGHT - ) - self.play(Blink(morty)) - - def full_time(self, morty): - new_title = TextMobject("Full time") - new_title.move_to(self.screen_title) - q_mark = TexMobject("?") - q_mark.next_to(self.cross) - q_mark.set_color(GREEN) - - self.play(morty.look_at, q_mark) - self.play(Transform(self.screen_title, new_title)) - self.play( - Transform(self.cross, q_mark), - morty.change_mode, "confused" - ) - self.play(Blink(morty)) - self.wait() - self.play( - morty.change_mode, "happy", - morty.look, UP+RIGHT - ) - self.play(Blink(morty)) - self.wait() - -class TakesTime(Scene): - def construct(self): - rect = PictureInPictureFrame(height = 4) - rect.to_edge(RIGHT, buff = LARGE_BUFF) - clock = Clock() - clock.hour_hand.set_color(BLUE_C) - clock.minute_hand.set_color(BLUE_D) - clock.next_to(rect, LEFT, buff = LARGE_BUFF) - self.add(rect) - self.play(ShowCreation(clock)) - for x in range(3): - self.play(ClockPassesTime(clock)) - -class GrowingToDoList(Scene): - def construct(self): - morty = Mortimer() - morty.flip() - morty.next_to(ORIGIN, DOWN+LEFT) - title = TextMobject("3blue1brown to-do list") - title.next_to(ORIGIN, RIGHT) - title.to_edge(UP) - underline = Line(title.get_left(), title.get_right()) - underline.next_to(title, DOWN) - - lines = VGroup(*list(map(TextMobject, [ - "That one on topology", - "Something with quaternions", - "Solving puzzles with binary counting", - "Tatoos on math", - "Laplace stuffs", - "The role of memorization in math", - "Strangeness of the axiom of choice", - "Tensors", - "Different view of $e^{\\pi i}$", - "Quadratic reciprocity", - "Fourier stuffs", - "$1+2+3+\\cdots = -\\frac{1}{12}$", - "Understanding entropy", - ]))) - lines.scale(0.65) - lines.arrange(DOWN, buff = MED_SMALL_BUFF, aligned_edge = LEFT) - lines.set_color_by_gradient(BLUE_C, YELLOW) - lines.next_to(title, DOWN, buff = LARGE_BUFF/2.) - lines.to_edge(RIGHT) - - self.play( - Write(title), - morty.look_at, title - ) - self.play( - Write(lines[0]), - morty.change_mode, "erm", - run_time = 1 - ) - for line in lines[1:3]: - self.play( - Write(line), - morty.look_at, line, - run_time = 1 - ) - self.play( - morty.change_mode, "pleading", - morty.look_at, lines, - Write( - VGroup(*lines[3:]), - ) - ) - -class TwoTypesOfVideos(Scene): - def construct(self): - morty = Mortimer().shift(2*DOWN) - stand_alone = TextMobject("Standalone videos") - stand_alone.shift(FRAME_X_RADIUS*LEFT/2) - stand_alone.to_edge(UP) - series = TextMobject("Series") - series.shift(FRAME_X_RADIUS*RIGHT/2) - series.to_edge(UP) - box = Rectangle(width = 16, height = 9, color = WHITE) - box.set_height(3) - box.next_to(stand_alone, DOWN) - series_list = VGroup(*[ - TextMobject("Essence of %s"%s) - for s in [ - "linear algebra", - "calculus", - "probability", - "real analysis", - "complex analysis", - "ODEs", - ] - ]) - series_list.arrange(DOWN, aligned_edge = LEFT, buff = MED_SMALL_BUFF) - series_list.set_width(FRAME_X_RADIUS-2) - series_list.next_to(series, DOWN, buff = MED_SMALL_BUFF) - series_list.to_edge(RIGHT) - - fridays = TextMobject("Every other friday") - when_done = TextMobject("When series is done") - for words, vect in (fridays, LEFT), (when_done, RIGHT): - words.set_color(YELLOW) - words.next_to( - morty, vect, - buff = MED_SMALL_BUFF, - aligned_edge = UP - ) - unless = TextMobject(""" - Unless you're - a patron \\dots - """) - unless.next_to(when_done, DOWN, buff = MED_SMALL_BUFF) - - self.add(morty) - self.play(Blink(morty)) - self.play( - morty.change_mode, "raise_right_hand", - morty.look_at, stand_alone, - Write(stand_alone, run_time = 2), - ) - self.play( - morty.change_mode, "raise_left_hand", - morty.look_at, series, - Write(series, run_time = 2), - ) - self.play(Blink(morty)) - self.wait() - self.play( - morty.change_mode, "raise_right_hand", - morty.look_at, box, - ShowCreation(box) - ) - for x in range(3): - self.wait(2) - self.play(Blink(morty)) - self.play( - morty.change_mode, "raise_left_hand", - morty.look_at, series - ) - for i, words in enumerate(series_list): - self.play(Write(words), run_time = 1) - self.play(Blink(morty)) - self.wait() - self.play(series_list[1].set_color, BLUE) - self.wait(2) - self.play(Blink(morty)) - self.wait() - pairs = [ - (fridays, "speaking"), - (when_done, "wave_2") , - (unless, "surprised"), - ] - for words, mode in pairs: - self.play( - Write(words), - morty.change_mode, mode, - morty.look_at, words - ) - self.wait() - -class ClassWatching(TeacherStudentsScene): - def construct(self): - rect = PictureInPictureFrame(height = 4) - rect.next_to(self.get_teacher(), UP, buff = LARGE_BUFF/2.) - rect.to_edge(RIGHT) - self.add(rect) - for pi in self.get_students(): - pi.look_at(rect) - - self.random_blink(5) - self.change_student_modes( - "raise_left_hand", - "raise_right_hand", - "sassy", - ) - self.play(self.get_teacher().change_mode, "pondering") - self.random_blink(3) - -class RandolphWatching(Scene): - def construct(self): - randy = Randolph() - randy.shift(2*LEFT) - randy.look(RIGHT) - - self.add(randy) - self.wait() - self.play(Blink(randy)) - self.wait() - self.play( - randy.change_mode, "pondering", - randy.look, RIGHT - ) - self.play(Blink(randy)) - self.wait() - -class RandolphWatchingWithLaptop(Scene): - pass - -class GrowRonaksSierpinski(Scene): - CONFIG = { - "colors" : [BLUE, YELLOW, BLUE_C, BLUE_E], - "dot_radius" : 0.08, - "n_layers" : 64, - } - def construct(self): - sierp = self.get_ronaks_sierpinski(self.n_layers) - dots = self.get_dots(self.n_layers) - self.triangle = VGroup(sierp, dots) - self.triangle.scale(1.5) - self.triangle.shift(3*UP) - sierp_layers = sierp.submobjects - dot_layers = dots.submobjects - - last_dot_layer = dot_layers[0] - self.play(ShowCreation(last_dot_layer)) - run_time = 1 - for n, sierp_layer, dot_layer in zip(it.count(1), sierp_layers, dot_layers[1:]): - self.play( - ShowCreation(sierp_layer, lag_ratio=1), - Animation(last_dot_layer), - run_time = run_time - ) - self.play(ShowCreation( - dot_layer, - run_time = run_time, - lag_ratio=1, - )) - # if n == 2: - # dot = dot_layer[1] - # words = TextMobject("Stop growth at pink") - # words.next_to(dot, DOWN, 2) - # arrow = Arrow(words, dot) - # self.play( - # Write(words), - # ShowCreation(arrow) - # ) - # self.wait() - # self.play(*map(FadeOut, [words, arrow])) - log2 = np.log2(n) - if n > 2 and log2-np.round(log2) == 0 and n < self.n_layers: - self.wait() - self.rescale() - run_time /= 1.3 - last_dot_layer = dot_layer - - def rescale(self): - shown_mobs = VGroup(*self.get_mobjects()) - shown_mobs_copy = shown_mobs.copy() - self.remove(shown_mobs) - self.add(shown_mobs_copy) - top = shown_mobs.get_top() - self.triangle.scale(0.5) - self.triangle.move_to(top, aligned_edge = UP) - self.play(Transform(shown_mobs_copy, shown_mobs)) - self.remove(shown_mobs_copy) - self.add(shown_mobs) - - def get_pascal_point(self, n, k): - return n*rotate_vector(RIGHT, -2*np.pi/3) + k*RIGHT - - def get_lines_at_layer(self, n): - lines = VGroup() - for k in range(n+1): - if choose(n, k)%2 == 1: - p1 = self.get_pascal_point(n, k) - p2 = self.get_pascal_point(n+1, k) - p3 = self.get_pascal_point(n+1, k+1) - lines.add(Line(p1, p2), Line(p1, p3)) - return lines - - def get_dot_layer(self, n): - dots = VGroup() - for k in range(n+1): - p = self.get_pascal_point(n, k) - dot = Dot(p, radius = self.dot_radius) - if choose(n, k)%2 == 0: - if choose(n-1, k)%2 == 0: - continue - dot.set_color(PINK) - else: - dot.set_color(WHITE) - dots.add(dot) - return dots - - def get_ronaks_sierpinski(self, n_layers): - ronaks_sierpinski = VGroup() - for n in range(n_layers): - ronaks_sierpinski.add(self.get_lines_at_layer(n)) - ronaks_sierpinski.set_color_by_gradient(*self.colors) - ronaks_sierpinski.set_stroke(width = 0)##TODO - return ronaks_sierpinski - - def get_dots(self, n_layers): - dots = VGroup() - for n in range(n_layers+1): - dots.add(self.get_dot_layer(n)) - return dots - -class PatreonLogo(Scene): - def construct(self): - words1 = TextMobject( - "Support future\\\\", - "3blue1brown videos" - ) - words2 = TextMobject( - "Early access to\\\\", - "``Essence of'' series" - ) - for words in words1, words2: - words.scale(2) - words.to_edge(DOWN) - self.play(Write(words1)) - self.wait(2) - self.play(Transform(words1, words2)) - self.wait(2) - -class PatreonLogin(Scene): - pass - -class PythagoreanTransformation(Scene): - def construct(self): - tri1 = VGroup( - Line(ORIGIN, 2*RIGHT, color = BLUE), - Line(2*RIGHT, 3*UP, color = YELLOW), - Line(3*UP, ORIGIN, color = MAROON_B), - ) - tri1.shift(2.5*(DOWN+LEFT)) - tri2, tri3, tri4 = copies = [ - tri1.copy().rotate(-i*np.pi/2) - for i in range(1, 4) - ] - a = TexMobject("a").next_to(tri1[0], DOWN, buff = MED_SMALL_BUFF) - b = TexMobject("b").next_to(tri1[2], LEFT, buff = MED_SMALL_BUFF) - c = TexMobject("c").next_to(tri1[1].get_center(), UP+RIGHT) - - c_square = Polygon(*[ - tri[1].get_end() - for tri in [tri1] + copies - ]) - c_square.set_stroke(width = 0) - c_square.set_fill(color = YELLOW, opacity = 0.5) - c_square_tex = TexMobject("c^2") - big_square = Polygon(*[ - tri[0].get_start() - for tri in [tri1] + copies - ]) - big_square.set_color(WHITE) - a_square = Square(side_length = 2) - a_square.shift(1.5*(LEFT+UP)) - a_square.set_stroke(width = 0) - a_square.set_fill(color = BLUE, opacity = 0.5) - a_square_tex = TexMobject("a^2") - a_square_tex.move_to(a_square) - b_square = Square(side_length = 3) - b_square.move_to( - a_square.get_corner(DOWN+RIGHT), - aligned_edge = UP+LEFT - ) - b_square.set_stroke(width = 0) - b_square.set_fill(color = MAROON_B, opacity = 0.5) - b_square_tex = TexMobject("b^2") - b_square_tex.move_to(b_square) - - self.play(ShowCreation(tri1, run_time = 2)) - self.play(*list(map(Write, [a, b, c]))) - self.wait() - self.play( - FadeIn(c_square), - Animation(c) - ) - self.play(Transform(c, c_square_tex)) - self.wait(2) - mover = tri1.copy() - for copy in copies: - self.play(Transform( - mover, copy, - path_arc = -np.pi/2 - )) - self.add(copy) - self.remove(mover) - self.add(big_square, *[tri1]+copies) - self.wait(2) - self.play(*list(map(FadeOut, [a, b, c, c_square]))) - self.play( - tri3.shift, - tri1.get_corner(UP+LEFT) -\ - tri3.get_corner(UP+LEFT) - ) - self.play(tri2.shift, 2*RIGHT) - self.play(tri4.shift, 3*UP) - self.wait() - self.play(FadeIn(a_square)) - self.play(FadeIn(b_square)) - self.play(Write(a_square_tex)) - self.play(Write(b_square_tex)) - self.wait(2) - -class KindWordsOnEoLA(TeacherStudentsScene): - def construct(self): - rect = Rectangle(width = 16, height = 9, color = WHITE) - rect.set_height(4) - title = TextMobject("Essence of linear algebra") - title.to_edge(UP) - rect.next_to(title, DOWN) - self.play( - Write(title), - ShowCreation(rect), - *[ - ApplyMethod(pi.look_at, rect) - for pi in self.get_pi_creatures() - ], - run_time = 2 - ) - self.random_blink() - self.change_student_modes(*["hooray"]*3) - self.random_blink() - self.play(self.get_teacher().change_mode, "happy") - self.random_blink() - -class MakeALotOfPiCreaturesHappy(Scene): - def construct(self): - width = 7 - height = 4 - pis = VGroup(*[ - VGroup(*[ - Randolph() - for x in range(7) - ]).arrange(RIGHT, buff = MED_LARGE_BUFF) - for x in range(4) - ]).arrange(DOWN, buff = MED_LARGE_BUFF) - - pi_list = list(it.chain(*[ - layer.submobjects - for layer in pis.submobjects - ])) - random.shuffle(pi_list) - colors = color_gradient([BLUE_D, GREY_BROWN], len(pi_list)) - for pi, color in zip(pi_list, colors): - pi.set_color(color) - pis = VGroup(*pi_list) - pis.set_height(6) - - self.add(pis) - pis.generate_target() - self.wait() - for pi, color in zip(pis.target, colors): - pi.change_mode("hooray") - # pi.scale_in_place(1) - pi.set_color(color) - self.play( - MoveToTarget( - pis, - run_time = 2, - lag_ratio = 0.5, - ) - ) - for x in range(10): - pi = random.choice(pi_list) - self.play(Blink(pi)) - - -class IntegrationByParts(Scene): - def construct(self): - rect = Rectangle(width = 5, height = 3) - # f = lambda t : 4*np.sin(t*np.pi/2) - f = lambda t : 4*t - g = lambda t : 3*smooth(t) - curve = ParametricFunction(lambda t : f(t)*RIGHT + g(t)*DOWN) - curve.set_color(YELLOW) - curve.center() - rect = Rectangle() - rect.replace(curve, stretch = True) - - regions = [] - for vect, color in (UP+RIGHT, BLUE), (DOWN+LEFT, GREEN): - region = curve.copy() - region.add_line_to(rect.get_corner(vect)) - region.set_stroke(width = 0) - region.set_fill(color = color, opacity = 0.5) - regions.append(region) - upper_right, lower_left = regions - - v_lines, h_lines = VGroup(), VGroup() - for alpha in np.linspace(0, 1, 30): - point = curve.point_from_proportion(alpha) - top_point = curve.points[0][1]*UP + point[0]*RIGHT - left_point = curve.points[0][0]*RIGHT + point[1]*UP - v_lines.add(Line(top_point, point)) - h_lines.add(Line(left_point, point)) - v_lines.set_color(BLUE_E) - h_lines.set_color(GREEN_E) - - equation = TexMobject( - "\\int_0^1 g\\,df", - "+\\int_0^1 f\\,dg", - "= \\big(fg \\big)_0^1" - ) - equation.to_edge(UP) - equation.set_color_by_tex( - "\\int_0^1 g\\,df", - upper_right.get_color() - ) - equation.set_color_by_tex( - "+\\int_0^1 f\\,dg", - lower_left.get_color() - ) - - left_brace = Brace(rect, LEFT) - down_brace = Brace(rect, DOWN) - g_T = left_brace.get_text("$g(t)\\big|_0^1$") - f_T = down_brace.get_text("$f(t)\\big|_0^1$") - - self.draw_curve(curve) - self.play(ShowCreation(rect)) - self.play(*list(map(Write, [down_brace, left_brace, f_T, g_T]))) - self.wait() - self.play(FadeIn(upper_right)) - self.play( - ShowCreation( - v_lines, - run_time = 2 - ), - Animation(curve), - Animation(rect) - ) - self.play(Write(equation[0])) - self.wait() - self.play(FadeIn(lower_left)) - self.play( - ShowCreation( - h_lines, - run_time = 2 - ), - Animation(curve), - Animation(rect) - ) - self.play(Write(equation[1])) - self.wait() - self.play(Write(equation[2])) - self.wait() - - def draw_curve(self, curve): - lp, lnum, comma, rnum, rp = coords = TexMobject( - "\\big(f(", "t", "), g(", "t", ")\\big)" - ) - coords.set_color_by_tex("0.00", BLACK) - dot = Dot(radius = 0.1) - dot.move_to(curve.points[0]) - coords.next_to(dot, UP+RIGHT) - self.play( - ShowCreation(curve), - UpdateFromFunc( - dot, - lambda d : d.move_to(curve.points[-1]) - ), - MaintainPositionRelativeTo(coords, dot), - run_time = 5, - rate_func=linear - ) - self.wait() - self.play(*list(map(FadeOut, [coords, dot]))) - -class EndScreen(TeacherStudentsScene): - def construct(self): - self.teacher_says( - """ - See you every - other friday! - """, - target_mode = "hooray" - ) - self.change_student_modes(*["happy"]*3) - self.random_blink() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/pi_day.py b/from_3b1b/old/pi_day.py deleted file mode 100644 index 7125c92c..00000000 --- a/from_3b1b/old/pi_day.py +++ /dev/null @@ -1,1101 +0,0 @@ -# -*- coding: utf-8 -*- - -from manimlib.imports import * - -###### Ben's stuff ######## - - -RADIUS = 2 -RADIUS_BUFF_HOR = 1.3 -RADIUS_BUFF_VER = 0.5 -RADIUS_COLOR = BLUE -CIRCUM_COLOR = YELLOW -DECIMAL_WIDTH = 0.5 - -HIGHLIGHT_COLOR = YELLOW - - -# Warning, this file uses ContinualChangingDecimal, -# which has since been been deprecated. Use a mobject -# updater instead - -class ArcLengthChange(Animation): - - def __init__(self, mobject = None, new_angle = TAU/3, **kwargs): - - self.old_angle = mobject.angle - self.start_angle = mobject.start_angle - self.new_angle = new_angle - Animation.__init__(self,mobject,**kwargs) - - def interpolate_mobject(self,alpha): - angle = interpolate(self.old_angle, self.new_angle, alpha) - self.mobject.angle = angle - self.mobject.init_points() - - -class LabelTracksLine(Animation): - - def __init__(self, mobject = None, line = None, buff = 0.2, **kwargs): - - self.line = line - self.buff = buff - Animation.__init__(self,mobject,**kwargs) - - def interpolate_mobject(self,alpha): - line_center = self.line.get_center() - line_end = self.line.points[-1] - v = line_end - line_center - v = v/get_norm(v) - w = np.array([-v[1],v[0],0]) - self.mobject.move_to(line_center + self.buff * w) - - - - - -class CircleConstants(Scene): - - def radial_label_func(self,a,b,theta): - - theta2 = theta % TAU - slope = (a-b)/(TAU/4) - - if theta2 < TAU/4: - x = a - slope * theta2 - elif theta < TAU/2: - x = b + slope * (theta2 - TAU/4) - elif theta < 3*TAU/4: - x = a - slope * (theta2 - TAU/2) - else: - x = b + slope * (theta2 - 3*TAU/4) - return x - - - def construct(self): - self.setup_circle() - self.change_arc_length(0.004) - self.pi_equals.next_to(self.decimal, LEFT) - self.wait() - self.change_arc_length(TAU/2) - self.wait() - self.change_arc_length(TAU) - self.wait() - self.change_arc_length(TAU/4) - self.wait() - self.change_arc_length(TAU/2) - self.wait() - - - - - def setup_circle(self): - - - self.circle_arc = Arc(angle = 0.004, radius = RADIUS) - self.radius = Line(ORIGIN, RADIUS * RIGHT) - self.radius.set_color(RADIUS_COLOR) - self.circle_arc.set_color(CIRCUM_COLOR) - - self.pi_equals = TexMobject("\pi\\approx", color = CIRCUM_COLOR) - self.decimal = DecimalNumber(0, color = CIRCUM_COLOR) - self.decimal.next_to(self.pi_equals, RIGHT, buff = 0.25) - self.circum_label = VGroup(self.pi_equals, self.decimal) - self.circum_label.next_to(self.radius, RIGHT, buff = RADIUS_BUFF_HOR) - - - self.one = TexMobject("1", color = RADIUS_COLOR) - self.one.next_to(self.radius, UP) - - self.play(ShowCreation(self.radius), FadeIn(self.one)) - self.play( - ShowCreation(self.circle_arc), - Write(self.pi_equals), - Write(self.decimal) - ) - - - def change_arc_length(self, new_angle): - - def decimal_position_update_func(decimal): - - angle = decimal.number - max_radius = RADIUS + RADIUS_BUFF_HOR - min_radius = RADIUS + RADIUS_BUFF_VER - label_radius = self.radial_label_func(max_radius, min_radius, angle) - label_center = label_radius * np.array([np.cos(angle), np.sin(angle),0]) - label_center += 0.5 * RIGHT - # label_center += pi_eq_stencil.get_width() * RIGHT - # print "label_center = ", label_center - decimal.move_to(label_center) - - - self.play( - Rotate(self.radius, new_angle - self.circle_arc.angle, about_point = ORIGIN), - ArcLengthChange(self.circle_arc,new_angle), - ChangeDecimalToValue( - self.decimal, new_angle, - position_update_func = decimal_position_update_func - ), - #MaintainPositionRelativeTo(self.one, self.radius), - MaintainPositionRelativeTo(self.pi_equals, self.decimal), - LabelTracksLine(self.one,self.radius, buff = 0.5), - run_time = 3, - ) - self.wait(2) - - -class AnalysisQuote(Scene): - - def construct(self): - - text = TextMobject('``We therefore set the radius of \\\\'\ - 'the circle\dots to be = 1, and \dots\\\\'\ - 'through approximations the \\\\'\ - 'semicircumference of said circle \\\\'\ - 'has been found to be $= 3.14159\dots$,\\\\'\ - 'for which number, for the sake of \\\\'\ - 'brevity, I will write $\pi$\dots"', - alignment = '') - for char in text.submobjects[12:24]: - char.set_fill(HIGHLIGHT_COLOR) - for char in text.submobjects[42:44]: - char.set_fill(HIGHLIGHT_COLOR) - for char in text.submobjects[75:92]: - char.set_fill(HIGHLIGHT_COLOR) - for char in text.submobjects[120:131]: - char.set_fill(HIGHLIGHT_COLOR) - text.submobjects[-5].set_fill(HIGHLIGHT_COLOR) - - text.to_edge(LEFT, buff = 1) - - self.play(LaggedStartMap(FadeIn,text), run_time = 5) - self.wait() - self.play(FadeOut(text)) - self.wait() - - -class BernoulliQuote(Scene): - - def construct(self): - - text = TextMobject('``Your most profound investigation of the series \\\\'\ - '$1+{1\over 4}+{1\over 9}+{1\over 16} + $ etc., which I had found to be \\\\'\ - 'one sixth of the square of $\pi$ itself\dots, not only\\\\'\ - ' gave me the greatest pleasure, but also renown \\\\'\ - 'among the whole Academy of St.\ Petersburg."') - text.submobjects[88].set_fill(HIGHLIGHT_COLOR) - for char in text.submobjects[41:60]: - char.set_fill(HIGHLIGHT_COLOR) - for char in text.submobjects[79:107]: - char.set_fill(HIGHLIGHT_COLOR) - for char in text.submobjects[127:143]: - char.set_fill(HIGHLIGHT_COLOR) - for char in text.submobjects[151:157]: - char.set_fill(HIGHLIGHT_COLOR) - - self.play(LaggedStartMap(FadeIn,text), run_time = 5) - self.wait() - self.play(FadeOut(text)) - self.wait - - -class EulerSignature(Scene): - - def construct(self): - - sig = SVGMobject(file_name = "euler-signature") - - self.play( - Write(sig, run_time = 5) - ) - - -########################### - -RESOURCE_DIR = os.path.join(MEDIA_DIR, "3b1b_videos", "π Day 2018", "images") -def get_image(name): - return ImageMobject(os.path.join(RESOURCE_DIR, name)) - -def get_circle_drawing_terms(radius = 1, positioning_func = lambda m : m.center()): - circle = Circle(color = YELLOW, radius = 1.25) - positioning_func(circle) - radius = Line(circle.get_center(), circle.points[0]) - radius.set_color(WHITE) - one = TexMobject("1") - one.scale(0.75) - one_update = UpdateFromFunc( - one, lambda m : m.move_to( - radius.get_center() + \ - 0.25*rotate_vector(radius.get_vector(), TAU/4) - ), - ) - decimal = DecimalNumber(0, num_decimal_places = 4, show_ellipsis = True) - decimal.scale(0.75) - def reposition_decimal(decimal): - vect = radius.get_vector() - unit_vect = vect/get_norm(vect) - angle = radius.get_angle() - alpha = (-np.cos(2*angle) + 1)/2 - interp_length = interpolate(decimal.get_width(), decimal.get_height(), alpha) - buff = interp_length/2 + MED_SMALL_BUFF - decimal.move_to(radius.get_end() + buff*unit_vect) - decimal.shift(UP*decimal.get_height()/2) - return decimal - - kwargs = {"run_time" : 3, "rate_func" : bezier([0, 0, 1, 1])} - changing_decimal = ChangingDecimal( - decimal, lambda a : a*TAU, - position_update_func = reposition_decimal, - **kwargs - ) - - terms = VGroup(circle, radius, one, decimal) - generate_anims1 = lambda : [ShowCreation(radius), Write(one)] - generate_anims2 = lambda : [ - ShowCreation(circle, **kwargs), - Rotating(radius, about_point = radius.get_start(), **kwargs), - changing_decimal, - one_update, - ] - - return terms, generate_anims1, generate_anims2 - -## - -class PiTauDebate(PiCreatureScene): - def construct(self): - pi, tau = self.pi, self.tau - self.add(pi, tau) - - pi_value = TextMobject("3.1415...!") - pi_value.set_color(BLUE) - tau_value = TextMobject("6.2831...!") - tau_value.set_color(GREEN) - - self.play(PiCreatureSays( - pi, pi_value, - target_mode = "angry", - look_at_arg = tau.eyes, - # bubble_kwargs = {"width" : 3} - )) - self.play(PiCreatureSays( - tau, tau_value, - target_mode = "angry", - look_at_arg = pi.eyes, - bubble_kwargs = {"width" : 3, "height" : 2}, - )) - self.wait() - - # Show tau - terms, generate_anims1, generate_anims2 = get_circle_drawing_terms( - radius = 1.25, - positioning_func = lambda m : m.to_edge(RIGHT, buff = 2).to_edge(UP, buff = 1) - ) - circle, radius, one, decimal = terms - - self.play( - RemovePiCreatureBubble(pi), - RemovePiCreatureBubble(tau), - *generate_anims1() - ) - self.play( - tau.change, "hooray", - pi.change, "sassy", - *generate_anims2() - ) - self.wait() - - - # Show pi - circle = Circle(color = RED, radius = 1.25/2) - circle.rotate(TAU/4) - circle.move_to(pi) - circle.to_edge(UP, buff = MED_LARGE_BUFF) - diameter = Line(circle.get_left(), circle.get_right()) - one = TexMobject("1") - one.scale(0.75) - one.next_to(diameter, UP, SMALL_BUFF) - - circum_line = diameter.copy().scale(np.pi) - circum_line.match_style(circle) - circum_line.next_to(circle, DOWN, buff = MED_LARGE_BUFF) - # circum_line.to_edge(LEFT) - brace = Brace(circum_line, DOWN, buff = SMALL_BUFF) - decimal = DecimalNumber(np.pi, num_decimal_places = 4, show_ellipsis = True) - decimal.scale(0.75) - decimal.next_to(brace, DOWN, SMALL_BUFF) - - self.play( - FadeIn(VGroup(circle, diameter, one)), - tau.change, "confused", - pi.change, "hooray" - ) - self.add(circle.copy().fade(0.5)) - self.play( - ReplacementTransform(circle, circum_line, run_time = 2) - ) - self.play(GrowFromCenter(brace), Write(decimal)) - self.wait(3) - # self.play() - - - def create_pi_creatures(self): - pi = self.pi = Randolph() - pi.to_edge(DOWN).shift(4*LEFT) - tau = self.tau = TauCreature( - # mode = "angry", - file_name_prefix = "TauCreatures", - color = GREEN_E - ).flip() - tau.to_edge(DOWN).shift(4*RIGHT) - return VGroup(pi, tau) - -class HartlAndPalais(Scene): - def construct(self): - hartl_rect = ScreenRectangle( - color = WHITE, - stroke_width = 1, - ) - hartl_rect.set_width(FRAME_X_RADIUS - 1) - hartl_rect.to_edge(LEFT) - palais_rect = hartl_rect.copy() - palais_rect.to_edge(RIGHT) - - tau_words = TextMobject("$\\tau$ ``tau''") - tau_words.next_to(hartl_rect, UP) - - hartl_words = TextMobject("Michael Hartl's \\\\ ``Tau manifesto''") - hartl_words.next_to(hartl_rect, DOWN) - - palais_words = TextMobject("Robert Palais' \\\\ ``Pi is Wrong!''") - palais_words.next_to(palais_rect, DOWN) - - for words in hartl_words, palais_words: - words.scale(0.7, about_edge = UP) - - three_legged_creature = ThreeLeggedPiCreature(height = 1.5) - three_legged_creature.next_to(palais_rect, UP) - - # self.add(hartl_rect, palais_rect) - self.add(hartl_words) - self.play(Write(tau_words)) - self.wait() - self.play(FadeIn(palais_words)) - self.play(FadeIn(three_legged_creature)) - self.play(three_legged_creature.change_mode, "wave") - self.play(Blink(three_legged_creature)) - self.play(Homotopy( - lambda x, y, z, t : (x + 0.1*np.sin(2*TAU*t)*np.exp(-10*(t-0.5 - 0.5*(y-1.85))**2), y, z), - three_legged_creature, - run_time = 2, - )) - self.wait() - -class ManyFormulas(Scene): - def construct(self): - formulas = VGroup( - TexMobject("\\sin(x + \\tau) = \\sin(x)"), - TexMobject("e^{\\tau i} = 1"), - TexMobject("n! \\approx \\sqrt{\\tau n} \\left(\\frac{n}{e} \\right)^n"), - TexMobject("c_n = \\frac{1}{\\tau} \\int_0^\\tau f(x) e^{inx}dx"), - ) - formulas.arrange(DOWN, buff = MED_LARGE_BUFF) - formulas.to_edge(LEFT) - - self.play(LaggedStartMap(FadeIn, formulas, run_time = 3)) - - circle = Circle(color = YELLOW, radius = 2) - circle.to_edge(RIGHT) - radius = Line(circle.get_center(), circle.get_right()) - radius.set_color(WHITE) - - angle_groups = VGroup() - for denom in 5, 4, 3, 2: - radius_copy = radius.copy() - radius_copy.rotate(TAU/denom, about_point = circle.get_center()) - arc = Arc( - angle = TAU/denom, - stroke_color = RED, - stroke_width = 4, - radius = circle.radius, - ) - arc.shift(circle.get_center()) - mini_arc = arc.copy() - mini_arc.set_stroke(WHITE, 2) - mini_arc.scale(0.15, about_point = circle.get_center()) - tau_tex = TexMobject("\\tau/%d"%denom) - point = mini_arc.point_from_proportion(0.5) - tau_tex.next_to(point, direction = point - circle.get_center()) - angle_group = VGroup(radius_copy, mini_arc, tau_tex, arc) - angle_groups.add(angle_group) - - - angle_group = angle_groups[0] - self.play(*list(map(FadeIn, [circle, radius]))) - self.play( - circle.set_stroke, {"width" : 1,}, - FadeIn(angle_group), - ) - self.wait() - for group in angle_groups[1:]: - self.play(Transform(angle_group, group, path_arc = TAU/8)) - self.wait() - -class HistoryOfOurPeople(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Today: The history \\\\ of our people.", - bubble_kwargs = {"width" : 4, "height" : 3} - ) - self.change_all_student_modes("hooray") - self.wait() - self.play(*[ - ApplyMethod(pi.change, "happy", self.screen) - for pi in self.pi_creatures - ]) - self.wait(4) - -class TauFalls(Scene): - def construct(self): - tau = TauCreature() - bottom = np.min(tau.body.points[:,1])*UP - angle = -0.15*TAU - tau.generate_target() - tau.target.change("angry") - tau.target.rotate(angle, about_point = bottom) - self.play(Rotate(tau, angle, rate_func = rush_into, path_arc = angle, about_point = bottom)) - # self.play(MoveToTarget(tau, rate_func = rush_into, path_arc = angle)) - self.play(MoveToTarget(tau)) - self.wait() - -class EulerWrites628(Scene): - CONFIG = { - "camera_config" : { - "background_opacity" : 1, - } - } - def construct(self): - image = ImageMobject(os.path.join(RESOURCE_DIR, "dalembert_zoom")) - image.set_width(FRAME_WIDTH - 1) - image.to_edge(UP, buff = MED_SMALL_BUFF) - image.fade(0.15) - rect = Rectangle( - width = 12, - height = 0.5, - stroke_width = 0, - fill_opacity = 0.3, - fill_color = GREEN, - ) - rect.insert_n_curves(20) - rect.apply_function(lambda p : np.array([p[0], p[1] - 0.005*p[0]**2, p[2]])) - rect.rotate(0.012*TAU) - rect.move_to(image) - rect.shift(0.15*DOWN) - - words = TextMobject( - "``Let", "$\\pi$", "be the", "circumference", - "of a circle whose", "radius = 1''", - ) - words.set_color_by_tex_to_color_map({ - "circumference" : YELLOW, - "radius" : GREEN, - }) - words.next_to(image, DOWN) - pi = words.get_part_by_tex("\\pi").copy() - - terms, generate_anims1, generate_anims2 = get_circle_drawing_terms( - radius = 1, - positioning_func = lambda circ : circ.next_to(words, DOWN, buff = 1.25) - ) - circle, radius, one, decimal = terms - - unwrapped_perimeter = Line(ORIGIN, TAU*RIGHT) - unwrapped_perimeter.match_style(circle) - unwrapped_perimeter.next_to(circle, DOWN) - brace = Brace(unwrapped_perimeter, UP, buff = SMALL_BUFF) - - perimeter = TexMobject( - "\\pi\\epsilon\\rho\\iota\\mu\\epsilon\\tau\\rho\\text{o}\\varsigma", - "\\text{ (perimeter)}", - "=" - ) - perimeter.next_to(brace, UP, submobject_to_align = perimeter[1], buff = SMALL_BUFF) - perimeter[0][0].set_color(GREEN) - - self.play(FadeInFromDown(image)) - self.play( - Write(words), - GrowFromPoint(rect, rect.point_from_proportion(0.9)) - ) - self.wait() - self.play(*generate_anims1()) - self.play(*generate_anims2()) - self.play(terms.shift, UP) - self.play( - pi.scale, 2, - pi.shift, DOWN, - pi.set_color, GREEN - ) - self.wait() - self.play( - GrowFromCenter(brace), - circle.set_stroke, YELLOW, 1, - ReplacementTransform(circle.copy(), unwrapped_perimeter), - decimal.scale, 1.25, - decimal.next_to, perimeter[-1].get_right(), RIGHT, - ReplacementTransform(pi, perimeter[0][0]), - Write(perimeter), - ) - self.wait() - -class HeroAndVillain(Scene): - CONFIG = { - "camera_config" : { - "background_opacity" : 1, - } - } - def construct(self): - good_euler = get_image("Leonhard_Euler_by_Handmann") - bad_euler_pixelated = get_image("Leonard_Euler_pixelated") - bad_euler = get_image("Leonard_Euler_revealed") - pictures = good_euler, bad_euler_pixelated, bad_euler - for mob in pictures: - mob.set_height(5) - - good_euler.move_to(FRAME_X_RADIUS*LEFT/2) - bad_euler.move_to(FRAME_X_RADIUS*RIGHT/2) - bad_euler_pixelated.move_to(bad_euler) - - good_euler_label = TextMobject("Leonhard Euler") - good_euler_label.next_to(good_euler, DOWN) - tau_words = TextMobject("Used 6.2831...") - tau_words.next_to(good_euler, UP) - tau_words.set_color(GREEN) - - bad_euler_label = TextMobject("Also Euler...") - bad_euler_label.next_to(bad_euler, DOWN) - pi_words = TextMobject("Used 3.1415...") - pi_words.set_color(RED) - pi_words.next_to(bad_euler, UP) - - self.play( - FadeInFromDown(good_euler), - Write(good_euler_label) - ) - self.play(LaggedStartMap(FadeIn, tau_words)) - self.wait() - self.play(FadeInFromDown(bad_euler_pixelated)) - self.play(LaggedStartMap(FadeIn, pi_words)) - self.wait(2) - self.play( - FadeIn(bad_euler), - Write(bad_euler_label), - ) - self.remove(bad_euler_pixelated) - self.wait(2) - -class AnalysisQuote(Scene): - def construct(self): - analysis = get_image("Analysis_page_showing_pi") - analysis.set_height(FRAME_HEIGHT) - analysis.to_edge(LEFT, buff = 0) - - text = TextMobject( - "``\\dots set the radius of", - "the circle\\dots to be = 1, \\dots \\\\", - "through approximations the", - "semicircumference \\\\", "of said circle", - "has been found to be", "$= 3.14159\\dots$,\\\\", - "for which number, for the sake of", - "brevity, \\\\ I will write", "$\pi$\\dots''", - alignment = '' - ) - pi_formula = TexMobject( - "\\pi", "=", "{ \\text{semicircumference}", "\\over", "\\text{radius}}" - ) - text.set_width(FRAME_X_RADIUS) - text.next_to(analysis, RIGHT, LARGE_BUFF) - text.to_edge(UP) - - HIGHLIGHT_COLOR= GREEN - for mob in text, pi_formula: - mob.set_color_by_tex_to_color_map({ - "semicircumference" : HIGHLIGHT_COLOR, - "3.14" : HIGHLIGHT_COLOR, - "\pi" : HIGHLIGHT_COLOR - }) - - terms, generate_anims1, generate_anims2 = get_circle_drawing_terms( - radius = 1, - positioning_func = lambda circ : circ.next_to(text, DOWN, LARGE_BUFF) - ) - terms[0].set_color(HIGHLIGHT_COLOR) - terms[-1].set_color(HIGHLIGHT_COLOR) - - pi_formula.next_to(terms, DOWN, buff = 0) - pi_formula.align_to(text, alignment_vect = RIGHT) - pi_formula[0].scale(2, about_edge = RIGHT) - - self.add(analysis) - self.play(*generate_anims2(), rate_func = lambda t : 0.5*smooth(t)) - self.play(LaggedStartMap(FadeIn,text), run_time = 5) - self.play(FadeIn(pi_formula)) - self.wait() - -class QuarterTurn(Scene): - def construct(self): - terms, generate_anims1, generate_anims2 = get_circle_drawing_terms( - radius = 2, - positioning_func = lambda circ : circ.move_to(4*RIGHT) - ) - circle, radius, one, decimal = terms - circle_ghost = circle.copy().set_stroke(YELLOW_E, 1) - radius_ghost = radius.copy().set_stroke(WHITE, 1) - - self.add(circle_ghost, radius_ghost) - self.play( - *generate_anims2(), - rate_func = lambda t : 0.25*smooth(t) - ) - self.wait() - - pi_halves = TexMobject("\\pi", "/2") - pi_halves[0].set_color(RED) - tau_fourths = TexMobject("\\tau", "/4") - tau_fourths[0].set_color(GREEN) - for mob in pi_halves, tau_fourths: - mob.next_to(decimal, UP) - - self.play(FadeInFromDown(pi_halves)) - self.wait() - self.play( - pi_halves.shift, 0.75*UP, - FadeInFromDown(tau_fourths) - ) - self.wait() - -class UsingTheta(Scene): - def construct(self): - plane = NumberPlane(x_unit_size = 3, y_unit_size = 3) - # planes.fade(0.5) - # plane.secondary_lines.fade(0.5) - plane.fade(0.5) - self.add(plane) - - circle = Circle(radius = 3) - circle.set_stroke(YELLOW, 2) - self.add(circle) - - radius = Line(ORIGIN, circle.get_right()) - arc = Arc(radius = 0.5, angle = TAU, num_anchors = 200) - arc.set_color(GREEN) - start_arc = arc.copy() - - theta = TexMobject("\\theta", "=") - theta[0].match_color(arc) - theta.add_background_rectangle() - update_theta = Mobject.add_updater( - theta, lambda m : m.shift( - rotate_vector(0.9*RIGHT, radius.get_angle()/2) \ - - m[1][0].get_center() - ) - ) - - angle = Integer(0, unit = "^\\circ") - update_angle = ContinualChangingDecimal( - angle, lambda a : radius.get_angle()*(360/TAU), - position_update_func = lambda a : a.next_to(theta, RIGHT, SMALL_BUFF) - ) - - self.add(update_theta, update_angle) - last_frac = 0 - for frac in 1./4, 1./12, 3./8, 1./6, 5./6: - self.play( - Rotate(radius, angle = (frac-last_frac)*TAU, about_point = ORIGIN), - UpdateFromAlphaFunc( - arc, - lambda m, a : m.pointwise_become_partial( - start_arc, 0, interpolate(last_frac, frac, a) - ) - ), - run_time = 3 - ) - last_frac = frac - self.wait() - -class ThingsNamedAfterEuler(Scene): - def construct(self): - group = VGroup(*list(map(TextMobject, [ - "Euler's formula (Complex analysis)", - "Euler's formula (Graph theory)", - "Euler's formula (Mechanical engineering)", - "Euler's formula (Analytical number theory)", - "Euler's formula (Continued fractions)", - "Euler-Lagrance equations (mechanics)", - "Euler number (topology)", - "Euler equations (fluid dynamics)", - "Euler angles (rigid-body mechanics)", - "Euler totient function (number theory)", - ]))) - group.arrange(DOWN, aligned_edge = LEFT) - group.set_height(FRAME_HEIGHT - 1) - - self.play(LaggedStartMap(FadeIn, group, lag_ratio = 0.2, run_time = 12)) - self.wait() - -class EulerThinking(Scene): - def construct(self): - image = get_image("Leonhard_Euler_by_Handmann") - image.set_height(4) - image.to_edge(DOWN) - image.shift(4*LEFT) - bubble = ThoughtBubble(height = 4.5) - bubble.next_to(image, RIGHT) - bubble.to_edge(UP, buff = SMALL_BUFF) - bubble.shift(0.8*LEFT) - bubble.set_fill(BLACK, 0.3) - - pi_vs_tau = TextMobject( - "Should $\\pi$ represent \\\\", "3.1415...", - "or", "6.2831...", "?" - ) - pi_vs_tau.set_color_by_tex_to_color_map({ - "3.14" : GREEN, - "6.28" : RED, - }) - pi_vs_tau.move_to(bubble.get_bubble_center()) - - question = TexMobject( - "\\frac{1}{1} + \\frac{1}{4} + \\frac{1}{9} + \\frac{1}{16} + \\cdots = ", - "\\;???" - ) - question[0].set_color_by_gradient(BLUE_C, BLUE_B) - question.move_to(bubble.get_bubble_center()) - question.shift(2*SMALL_BUFF*UP) - - cross = Cross(pi_vs_tau) - cross.set_stroke(RED, 8) - - self.add(image) - self.play( - ShowCreation(bubble), - Write(pi_vs_tau) - ) - self.play(ShowCreation(cross)) - self.wait() - self.play( - FadeOut(VGroup(pi_vs_tau, cross)), - FadeIn(question), - ) - self.wait() - - -class WhatIsRight(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs" : { - "color" : BLUE_D, - "filp_at_start" : False, - }, - "default_pi_creature_start_corner" : DOWN, - } - def construct(self): - randy = self.pi_creature - randy.scale(0.75, about_edge = DOWN) - title = TextMobject("Which is ``right''?") - title.scale(1.5) - title.to_edge(UP) - self.add(title) - - sum_over_N_converges = TexMobject("1+2+3+\\cdots = -\\frac{1}{12}") - sum_over_N_diverges = TexMobject("1+2+3+\\cdots \\text{ Diverges}") - - literal_derivative = TexMobject( - "f'(x) = \\frac{f(x+dx) - f(x)}{dx}" - ) - limit_derivative = TexMobject( - "f'(x) = \\lim_{h \\to 0}\\frac{f(x+h) - f(x)}{h}" - ) - - divide_by_zero_definable = TexMobject( - "\\frac{1}{0}", "\\text{ has meaning}" - ) - divide_by_zero_undefinable = TexMobject( - "\\frac{1}{0}", "\\text{ is undefined}" - ) - - left_mobs = VGroup( - sum_over_N_converges, literal_derivative, - divide_by_zero_definable - ) - right_mobs = VGroup( - sum_over_N_diverges, limit_derivative, - divide_by_zero_undefinable - ) - - for mob in left_mobs: - mob.next_to(randy, UP) - mob.shift(3.5*LEFT) - for mob in right_mobs: - mob.next_to(randy, UP) - mob.shift(3.5*RIGHT) - - left_mobs.set_color_by_gradient(YELLOW_C, YELLOW_D) - right_mobs.set_color_by_gradient(GREEN_C, GREEN_D) - - self.play(randy.change, "pondering", title) - self.wait() - self.play(randy.change, "sassy", title) - self.wait() - - last_terms = VGroup() - for left, right in zip(left_mobs, right_mobs)[:-1]: - right.align_to(left) - self.play( - randy.change, "raise_right_hand", - FadeInFromDown(left), - last_terms.shift, 1.75*UP - ) - self.wait() - self.play( - randy.change, "raise_left_hand", - FadeInFromDown(right) - ) - self.play(randy.change, "plain", right) - last_terms.add(left, right, title) - self.play( - randy.change, "shruggie", - FadeInFromDown(left_mobs[-1]), - FadeInFromDown(right_mobs[-1]), - last_terms.shift, 1.75*UP, - ) - self.wait(4) - -class AskPuzzle(TeacherStudentsScene): - def construct(self): - series = TexMobject( - "\\frac{1}{1} + \\frac{1}{4} + \\frac{1}{9} + \\cdots + " +\ - "\\frac{1}{n^2} + \\cdots = ", "\\,???" - ) - series[0].set_color_by_gradient(BLUE_C, BLUE_B) - series[1].set_color(YELLOW) - - question = TextMobject( - "How should we think about\\\\", - "$\\displaystyle \\sum_{n=1}^\\infty \\frac{1}{n^s}$", - "for arbitrary $s$?" - ) - question[1].set_color(BLUE) - question[0].shift(SMALL_BUFF*UP) - - response = TextMobject( - "What do you mean by ", - "$\\displaystyle \\sum_{n = 1}^{\\infty}$", - "?" - ) - response[1].set_color(BLUE) - - self.teacher_says(series) - self.change_all_student_modes("pondering", look_at_arg = series) - self.wait(3) - self.play( - FadeOut(self.teacher.bubble), - self.teacher.change, "happy", - series.scale, 0.5, - series.to_corner, UP+LEFT, - PiCreatureSays( - self.students[0], question, - target_mode = "raise_left_hand" - ) - ) - self.change_student_modes( - None, "confused", "confused", - added_anims = [self.students[0].look_at, question] - ) - self.wait(2) - - self.students[0].bubble.content = VGroup() - self.play( - RemovePiCreatureBubble(self.students[0]), - question.scale, 0.5, - question.next_to, series, DOWN, MED_LARGE_BUFF, LEFT, - PiCreatureSays(self.teacher, response) - ) - self.change_all_student_modes("erm") - self.wait(3) - -class ChangeTopic(PiCreatureScene): - def construct(self): - pi, tau = self.pi_creatures - title = TextMobject("Happy $\\pi$ day!") - title.scale(1.5) - title.to_edge(UP, buff = MED_SMALL_BUFF) - self.add(title) - - question = TextMobject( - "Have you ever seen why \\\\", - "$\\displaystyle \\frac{2}{1} \\cdot \\frac{2}{3} \\cdots"+ \ - "\\frac{4}{3} \\cdot \\frac{4}{5} \\cdot" + \ - "\\frac{6}{5} \\cdot \\frac{6}{7} \\cdots = \\frac{\\pi}{2}$", "?" - ) - question[0].shift(MED_SMALL_BUFF*UP) - question[1].set_color_by_gradient(YELLOW, GREEN) - - self.play( - PiCreatureSays( - tau, "We should \\emph{really} celebrate \\\\ on 6/28!", - target_mode = "angry", - ), - pi.change, "guilty", - ) - self.wait(2) - self.play( - PiCreatureSays(pi, question), - RemovePiCreatureBubble( - tau, target_mode = "pondering", look_at_arg = question, - ) - ) - self.play(pi.change, "pondering", question) - self.wait(4) - - - def create_pi_creatures(self): - tau = TauCreature(color = GREEN_E) - pi = Randolph().flip() - VGroup(pi, tau).scale(0.75) - tau.to_edge(DOWN).shift(3*LEFT) - pi.to_edge(DOWN).shift(3*RIGHT) - return pi, tau - -class SpecialThanks(Scene): - def construct(self): - title = TextMobject("Special thanks to:") - title.to_edge(UP, LARGE_BUFF) - title.scale(1.5) - title.set_color(BLUE) - h_line = Line(LEFT, RIGHT).scale(4) - h_line.next_to(title, DOWN) - h_line.set_stroke(WHITE, 1) - - people = VGroup(*list(map(TextMobject, [ - "Ben Hambrecht", - "University Library Basel", - "Martin Mattmüller", - "Library of the Institut de France", - ]))) - people.arrange(DOWN, aligned_edge = LEFT, buff = MED_LARGE_BUFF) - people.next_to(h_line, DOWN) - - self.add(title, h_line, people) - -class EndScene(PatreonEndScreen): - CONFIG = { - "camera_config" : { - "background_opacity" : 1, - } - } - def construct(self): - self.add_title() - title = self.title - basel_screen = ScreenRectangle(height = 2.35) - basel_screen.next_to(title, DOWN) - watch_basel = TextMobject( - "One such actual piece of math", "(quite pretty!)", - ) - watch_basel[0].set_color(YELLOW) - watch_basel.next_to(basel_screen, DOWN, submobject_to_align = watch_basel[0]) - - self.add(watch_basel) - # self.add(basel_screen) - - line = DashedLine(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) - line.next_to(watch_basel, DOWN) - self.add(line) - - plushie_square = Square(side_length = 2) - plushie_square.to_corner(DOWN+LEFT, buff = MED_LARGE_BUFF) - plushie_square.shift(UP) - - plushie_words = TextMobject( - "Plushie pi \\\\ creatures \\\\ now available.", - alignment = "" - ) - plushie_words.next_to(plushie_square, RIGHT) - - self.add(plushie_words) - # self.add(plushie_square) - - instagram_line = TextMobject( - "randy\\_the\\_pi" - ) - instagram_logo = ImageMobject("instagram_logo") - instagram_logo.match_height(instagram_line) - instagram_logo.next_to(instagram_line, LEFT, SMALL_BUFF) - instagram = Group(instagram_logo, instagram_line) - instagram.next_to(line, DOWN) - instagram.shift(FRAME_X_RADIUS*RIGHT/2) - self.add(instagram) - - - pictures = Group(*[ - ImageMobject("randy/randy_%s"%name) - for name in [ - "science", - "cooking", - "in_a_can", - "sandwhich", - "lab", - "fractal", - "flowers", - "group", - "labcoat", - "tennis", - ] - ]) - for i, picture in enumerate(pictures): - picture.set_height(2) - picture.next_to(instagram, DOWN, aligned_edge = RIGHT) - if i%3 != 0: - picture.next_to(last_picture, LEFT, buff = 0) - self.play(FadeIn(picture, run_time = 2)) - last_picture = picture - -class Thumbnail(Scene): - def construct(self): - pi, eq, num = formula = TexMobject( - "\\pi", "=", "6.283185\\dots" - ) - formula.scale(2) - pi.scale(1.5, about_edge = RIGHT) - formula.set_stroke(BLUE, 1) - formula.set_width(FRAME_WIDTH - 2) - # formula.shift(0.5*RIGHT) - self.add(formula) - - words = TextMobject("...according to Euler.") - words.scale(1.5) - words.next_to(formula, DOWN, MED_LARGE_BUFF) - self.add(words) - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/playground_counting_in_binary.py b/from_3b1b/old/playground_counting_in_binary.py deleted file mode 100644 index 0848d5b1..00000000 --- a/from_3b1b/old/playground_counting_in_binary.py +++ /dev/null @@ -1,229 +0,0 @@ -#!/usr/bin/env python - - -import numpy as np -import itertools as it -from copy import deepcopy -import sys - - -from animation import * -from mobject import * -from constants import * -from mobject.region import * -from scene.scene import Scene, SceneFromVideo -from script_wrapper import command_line_create_scene -from functools import reduce - -MOVIE_PREFIX = "counting_in_binary/" - -COUNT_TO_FRAME_NUM = { - 0 : 0, - 1 : 53, - 2 : 84, - 3 : 128, - 4 : 169, - 5 : 208, - 6 : 238, - 7 : 281, - 8 : 331, - 9 : 365, - 10 : 395, - 11 : 435, - 12 : 475, - 13 : 518, - 14 : 556, - 15 : 595, - 16 : 636, - 17 : 676, - 18 : 709, - 19 : 753, - 20 : 790, - 21 : 835, - 22 : 869, - 23 : 903, - 24 : 950, - 25 : 988, - 26 : 1027, - 27 : 1065, - 28 : 1104, - 29 : 1145, - 30 : 1181, - 31 : 1224, - 32 : 1239, -} - -class Hand(ImageMobject): - def __init__(self, num, **kwargs): - Mobject2D.__init__(self, **kwargs) - path = os.path.join( - VIDEO_DIR, MOVIE_PREFIX, "images", "Hand%d.png"%num - ) - invert = False - if self.read_in_cached_attrs(path, invert): - return - ImageMobject.__init__(self, path, invert) - center = self.get_center() - self.center() - self.rotate(np.pi, axis = RIGHT+UP) - self.sort_points(lambda p : np.log(complex(*p[:2])).imag) - self.rotate(np.pi, axis = RIGHT+UP) - self.shift(center) - self.cache_attrs(path, invert = False) - - -class EdgeDetection(SceneFromVideo): - args_list = [ - ("CountingInBinary.m4v", 35, 70), - ("CountingInBinary.m4v", 0, 100), - ("CountingInBinary.m4v", 10, 50), - ] - @staticmethod - def args_to_string(filename, t1, t2): - return "-".join([filename.split(".")[0], str(t1), str(t2)]) - - def construct(self, filename, t1, t2): - path = os.path.join(VIDEO_DIR, filename) - SceneFromVideo.construct(self, path) - self.apply_gaussian_blur() - self.apply_edge_detection(t1, t2) - -class BufferedCounting(SceneFromVideo): - def construct(self): - path = os.path.join(VIDEO_DIR, "CountingInBinary.m4v") - time_range = (3, 42) - SceneFromVideo.construct(self, path, time_range = time_range) - self.buffer_pixels(spreads = (3, 2)) - # self.make_all_black_or_white() - - def buffer_pixels(self, spreads = (2, 2)): - ksize = (5, 5) - sigmaX = 10 - threshold1 = 35 - threshold2 = 70 - - matrices = [ - thick_diagonal(dim, spread) - for dim, spread in zip(self.shape, spreads) - ] - for frame, index in zip(self.frames, it.count()): - print(index + "of" + len(self.frames)) - blurred = cv2.GaussianBlur(frame, ksize, sigmaX) - edged = cv2.Canny(blurred, threshold1, threshold2) - buffed = reduce(np.dot, [matrices[0], edged, matrices[1]]) - for i in range(3): - self.frames[index][:,:,i] = buffed - - - def make_all_black_or_white(self): - self.frames = [ - 255*(frame != 0).astype('uint8') - for frame in self.frames - ] - -class ClearLeftSide(SceneFromVideo): - args_list = [ - ("BufferedCounting",), - ] - @staticmethod - def args_to_string(scenename): - return scenename - - def construct(self, scenename): - path = os.path.join(VIDEO_DIR, MOVIE_PREFIX, scenename + ".mp4") - SceneFromVideo.construct(self, path) - self.set_color_region_over_time_range( - Region(lambda x, y : x < -1, shape = self.shape) - ) - - - -class DraggedPixels(SceneFromVideo): - args_list = [ - ("BufferedCounting",), - ("CountingWithLeftClear",), - ] - @staticmethod - def args_to_string(*args): - return args[0] - - def construct(self, video): - path = os.path.join(VIDEO_DIR, MOVIE_PREFIX, video+".mp4") - SceneFromVideo.construct(self, path) - self.drag_pixels() - - def drag_pixels(self, num_frames_to_drag_over = 5): - for index in range(len(self.frames)-1, 0, -1): - self.frames[index] = np.max([ - self.frames[k] - for k in range( - max(index-num_frames_to_drag_over, 0), - index - ) - ], axis = 0) - - -class SaveEachNumber(SceneFromVideo): - def construct(self): - path = os.path.join(VIDEO_DIR, MOVIE_PREFIX, "ClearLeftSideBufferedCounting.mp4") - SceneFromVideo.construct(self, path) - for count in COUNT_TO_FRAME_NUM: - path = os.path.join( - VIDEO_DIR, MOVIE_PREFIX, "images", - "Hand%d.png"%count - ) - Image.fromarray(self.frames[COUNT_TO_FRAME_NUM[count]]).save(path) - -class ShowCounting(SceneFromVideo): - args_list = [ - ("CountingWithLeftClear",), - ("ClearLeftSideBufferedCounting",), - ] - @staticmethod - def args_to_string(filename): - return filename - - def construct(self, filename): - path = os.path.join(VIDEO_DIR, MOVIE_PREFIX, filename + ".mp4") - SceneFromVideo.construct(self, path) - total_time = len(self.frames)*self.frame_duration - for count in range(32): - print(count) - mob = TexMobject(str(count)).scale(1.5) - mob.shift(0.3*LEFT).to_edge(UP, buff = 0.1) - index_range = list(range( - max(COUNT_TO_FRAME_NUM[count]-10, 0), - COUNT_TO_FRAME_NUM[count+1]-10)) - for index in index_range: - self.frames[index] = disp.paint_mobject( - mob, self.frames[index] - ) - -class ShowFrameNum(SceneFromVideo): - args_list = [ - ("ClearLeftSideBufferedCounting",), - ] - @staticmethod - def args_to_string(filename): - return filename - - def construct(self, filename): - path = os.path.join(VIDEO_DIR, MOVIE_PREFIX, filename+".mp4") - SceneFromVideo.construct(self, path) - for frame, count in zip(self.frames, it.count()): - print(count + "of" + len(self.frames)) - mob = Mobject(*[ - TexMobject(char).shift(0.3*x*RIGHT) - for char, x, in zip(str(count), it.count()) - ]) - self.frames[count] = disp.paint_mobject( - mob.to_corner(UP+LEFT), - frame - ) - - - - - -if __name__ == "__main__": - command_line_create_scene(MOVIE_PREFIX) diff --git a/from_3b1b/old/putnam.py b/from_3b1b/old/putnam.py deleted file mode 100644 index 22bd9ca5..00000000 --- a/from_3b1b/old/putnam.py +++ /dev/null @@ -1,1603 +0,0 @@ -from manimlib.imports import * - -class ShowExampleTest(ExternallyAnimatedScene): - pass - -class IntroducePutnam(Scene): - CONFIG = { - "dont_animate" : False, - } - def construct(self): - title = TextMobject("Putnam Competition") - title.to_edge(UP, buff = MED_SMALL_BUFF) - title.set_color(BLUE) - six_hours = TextMobject("6", "hours") - three_hours = TextMobject("3", "hours") - for mob in six_hours, three_hours: - mob.next_to(title, DOWN, MED_LARGE_BUFF) - # mob.set_color(BLUE) - three_hours.shift(FRAME_X_RADIUS*LEFT/2) - three_hours_copy = three_hours.copy() - three_hours_copy.shift(FRAME_X_RADIUS*RIGHT) - - question_groups = VGroup(*[ - VGroup(*[ - TextMobject("%s%d)"%(c, i)) - for i in range(1, 7) - ]).arrange(DOWN, buff = MED_LARGE_BUFF) - for c in ("A", "B") - ]).arrange(RIGHT, buff = FRAME_X_RADIUS - MED_SMALL_BUFF) - question_groups.to_edge(LEFT) - question_groups.to_edge(DOWN, MED_LARGE_BUFF) - flat_questions = VGroup(*it.chain(*question_groups)) - - rects = VGroup() - for questions in question_groups: - rect = SurroundingRectangle(questions, buff = MED_SMALL_BUFF) - rect.set_stroke(WHITE, 2) - rect.stretch_to_fit_width(FRAME_X_RADIUS - 1) - rect.move_to(questions.get_left() + MED_SMALL_BUFF*LEFT, LEFT) - rects.add(rect) - - out_of_tens = VGroup() - for question in flat_questions: - out_of_ten = TexMobject("/10") - out_of_ten.set_color(GREEN) - out_of_ten.move_to(question) - dist = rects[0].get_width() - 1.2 - out_of_ten.shift(dist*RIGHT) - out_of_tens.add(out_of_ten) - - out_of_120 = TexMobject("/120") - out_of_120.next_to(title, RIGHT, LARGE_BUFF) - out_of_120.set_color(GREEN) - - out_of_120.generate_target() - out_of_120.target.to_edge(RIGHT, LARGE_BUFF) - median = TexMobject("2") - median.next_to(out_of_120.target, LEFT, SMALL_BUFF) - median.set_color(RED) - median.align_to(out_of_120[-1]) - median_words = TextMobject("Typical median $\\rightarrow$") - median_words.next_to(median, LEFT) - - difficulty_strings = [ - "Pretty hard", - "Hard", - "Harder", - "Very hard", - "Ughhh", - "Can I go home?" - ] - colors = color_gradient([YELLOW, RED], len(difficulty_strings)) - difficulties = VGroup() - for i, s, color in zip(it.count(), difficulty_strings, colors): - for question_group in question_groups: - question = question_group[i] - text = TextMobject("\\dots %s \\dots"%s) - text.scale(0.7) - text.next_to(question, RIGHT) - text.set_color(color) - difficulties.add(text) - - - if self.dont_animate: - test = VGroup() - test.rect = rects[0] - test.questions = question_groups[0] - test.out_of_tens = VGroup(*out_of_tens[:6]) - test.difficulties = VGroup(*difficulties[::2]) - test.digest_mobject_attrs() - self.test = test - return - - self.add(title) - self.play(Write(six_hours)) - self.play(LaggedStartMap( - GrowFromCenter, flat_questions, - run_time = 3, - )) - self.play( - ReplacementTransform(six_hours, three_hours), - ReplacementTransform(six_hours.copy(), three_hours_copy), - *list(map(ShowCreation, rects)) - ) - self.wait() - self.play(LaggedStartMap( - DrawBorderThenFill, out_of_tens, - run_time = 3, - stroke_color = YELLOW - )) - self.wait() - self.play(ReplacementTransform( - out_of_tens.copy(), VGroup(out_of_120), - lag_ratio = 0.5, - run_time = 2, - )) - self.wait() - self.play( - title.next_to, median_words.copy(), LEFT, LARGE_BUFF, - MoveToTarget(out_of_120), - Write(median_words) - ) - self.play(Write(median)) - for difficulty in difficulties: - self.play(FadeIn(difficulty)) - self.wait() - -class NatureOf5sAnd6s(TeacherStudentsScene): - CONFIG = { - "test_scale_val" : 0.65 - } - def construct(self): - test = self.get_test() - - self.students.fade(1) - self.play( - test.scale, self.test_scale_val, - test.to_corner, UP+LEFT, - FadeIn(self.teacher), - self.get_student_changes( - *["horrified"]*3, - look_at_arg = test - ) - ) - self.wait() - - mover = VGroup( - test.questions[-1].copy(), - test.difficulties[-1].copy(), - ) - mover.generate_target() - mover.target.scale(1./self.test_scale_val) - mover.target.next_to( - self.teacher.get_corner(UP+LEFT), UP, - ) - new_words = TextMobject("\\dots Potentially very elegant \\dots") - new_words.set_color(GREEN) - new_words.set_height(mover.target[1].get_height()) - new_words.next_to(mover.target[0], RIGHT, SMALL_BUFF) - - self.play( - MoveToTarget(mover), - self.teacher.change, "raise_right_hand", - ) - self.change_student_modes(*["pondering"]*3) - self.play(Transform(mover[1], new_words)) - self.look_at((FRAME_X_RADIUS*RIGHT + FRAME_Y_RADIUS*UP)/2) - self.wait(4) - - - ### - - def get_test(self): - prev_scene = IntroducePutnam(dont_animate = True) - return prev_scene.test - -class OtherVideoClips(Scene): - def construct(self): - rect = ScreenRectangle() - rect.set_height(6.5) - rect.center() - rect.to_edge(DOWN) - titles = list(map(TextMobject, [ - "Essence of calculus, chapter 1", - "Pi hiding in prime regularities", - "How do cryptocurrencies work?" - ])) - - self.add(rect) - last_title = None - for title in titles: - title.to_edge(UP, buff = MED_SMALL_BUFF) - if last_title: - self.play(ReplacementTransform(last_title, title)) - else: - self.play(FadeIn(title)) - self.wait(3) - last_title = title - -class IntroduceTetrahedron(ExternallyAnimatedScene): - pass - -class IntroduceTetrahedronSupplement(Scene): - def construct(self): - title = TextMobject("4", "random$^*$ points on sphere") - title.set_color(YELLOW) - question = TextMobject("Probability that this tetrahedron \\\\ contains the sphere's center?") - question.next_to(title, DOWN, MED_LARGE_BUFF) - group = VGroup(title, question) - group.set_width(FRAME_WIDTH-1) - group.to_edge(DOWN) - - for n in range(1, 4): - num = TextMobject(str(n)) - num.replace(title[0], dim_to_match = 1) - num.set_color(YELLOW) - self.add(num) - self.wait(0.7) - self.remove(num) - self.add(title[0]) - self.play(FadeIn(title[1], lag_ratio = 0.5)) - self.wait(2) - self.play(Write(question)) - self.wait(2) - -class IntroduceTetrahedronFootnote(Scene): - def construct(self): - words = TextMobject(""" - $^*$Chosen independently with a \\\\ - uniform distribution on the sphere. - """) - words.to_corner(UP+LEFT) - self.add(words) - self.wait(2) - -class HowDoYouStart(TeacherStudentsScene): - def construct(self): - self.student_says( - "How do you even start?", - target_mode = "raise_left_hand" - ) - self.change_student_modes("confused", "raise_left_hand", "erm") - self.wait() - self.teacher_says("Try a simpler case.") - self.change_student_modes(*["thinking"]*3) - self.wait(2) - -class TwoDCase(Scene): - CONFIG = { - "center" : ORIGIN, - "random_seed" : 4, - "radius" : 2.5, - "center_color" : BLUE, - "point_color" : YELLOW, - "positive_triangle_color" : BLUE, - "negative_triangle_color" : RED, - "triangle_fill_opacity" : 0.25, - "n_initial_random_choices" : 9, - "n_p3_random_moves" : 4, - } - def construct(self): - self.add_title() - self.add_circle() - self.choose_three_random_points() - self.simplify_further() - self.fix_two_points_in_place() - self.note_special_region() - self.draw_lines_through_center() - self.ask_about_probability_p3_lands_in_this_arc() - self.various_arc_sizes_for_p1_p2_placements() - self.ask_about_average_arc_size() - self.fix_p1_in_place() - self.overall_probability() - - def add_title(self): - title = TextMobject("2D Case") - title.to_corner(UP+LEFT) - self.add(title) - self.set_variables_as_attrs(title) - - def add_circle(self): - circle = Circle(radius = self.radius, color = WHITE) - center_dot = Dot(color = self.center_color).center() - radius = DashedLine(ORIGIN, circle.radius*RIGHT) - VGroup(circle, center_dot, radius).shift(self.center) - - self.add(center_dot) - self.play(ShowCreation(radius)) - self.play( - ShowCreation(circle), - Rotating(radius, angle = 2*np.pi, about_point = self.center), - rate_func = smooth, - run_time = 2, - ) - self.play(ShowCreation( - radius, - rate_func = lambda t : smooth(1-t), - remover = True - )) - self.wait() - - self.set_variables_as_attrs(circle, center_dot) - - def choose_three_random_points(self): - point_mobs = self.get_point_mobs() - point_labels = self.get_point_mob_labels() - triangle = self.get_triangle() - self.point_labels_update = self.get_labels_update(point_mobs, point_labels) - self.triangle_update = self.get_triangle_update(point_mobs, triangle) - self.update_animations = [ - self.triangle_update, - self.point_labels_update, - ] - for anim in self.update_animations: - anim.update(0) - - question = TextMobject( - "Probability that \\\\ this triangle \\\\", - "contains the center", "?", - arg_separator = "", - ) - question.set_color_by_tex("center", self.center_color) - question.scale(0.8) - question.to_corner(UP+RIGHT) - self.question = question - - self.play(LaggedStartMap(DrawBorderThenFill, point_mobs)) - self.play(FadeIn(triangle)) - self.wait() - self.play(LaggedStartMap(Write, point_labels)) - self.wait() - self.play(Write(question)) - for x in range(self.n_initial_random_choices): - self.change_point_mobs_randomly() - self.wait() - angles = self.get_point_mob_angles() - target_angles = [5*np.pi/8, 7*np.pi/8, 0] - self.change_point_mobs([ta - a for a, ta in zip(angles, target_angles)]) - self.wait() - - def simplify_further(self): - morty = Mortimer().flip() - morty.scale(0.75) - morty.to_edge(DOWN) - morty.shift(3.5*LEFT) - - bubble = SpeechBubble( - direction = RIGHT, - height = 3, width = 3 - ) - bubble.pin_to(morty) - bubble.to_edge(LEFT, SMALL_BUFF) - bubble.write("Simplify \\\\ more!") - - self.play(FadeIn(morty)) - self.play( - morty.change, "hooray", - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(Blink(morty)) - self.wait() - self.play( - morty.change, "happy", - morty.fade, 1, - *list(map(FadeOut, [bubble, bubble.content])) - ) - self.remove(morty) - - def fix_two_points_in_place(self): - push_pins = VGroup() - for point_mob in self.point_mobs[:-1]: - push_pin = SVGMobject(file_name = "push_pin") - push_pin.set_height(0.5) - push_pin.move_to(point_mob.get_center(), DOWN) - line = Line(ORIGIN, UP) - line.set_stroke(WHITE, 2) - line.set_height(0.1) - line.move_to(push_pin, UP) - line.shift(0.3*SMALL_BUFF*(2*DOWN+LEFT)) - push_pin.add(line) - push_pin.set_fill(LIGHT_GREY) - push_pin.save_state() - push_pin.shift(UP) - push_pin.fade(1) - push_pins.add(push_pin) - - self.play(LaggedStartMap( - ApplyMethod, push_pins, - lambda mob : (mob.restore,) - )) - self.add_foreground_mobjects(push_pins) - d_thetas = 2*np.pi*np.random.random(self.n_p3_random_moves) - for d_theta in d_thetas: - self.change_point_mobs([0, 0, d_theta]) - self.wait() - - self.set_variables_as_attrs(push_pins) - - def note_special_region(self): - point_mobs = self.point_mobs - angles = self.get_point_mob_angles() - - all_arcs = self.get_all_arcs() - arc = all_arcs[-1] - arc_lines = VGroup() - for angle in angles[:2]: - line = Line(LEFT, RIGHT).scale(SMALL_BUFF) - line.shift(self.radius*RIGHT) - line.rotate(angle + np.pi) - line.shift(self.center) - line.set_stroke(arc.get_color()) - arc_lines.add(line) - - self.play(ShowCreation(arc_lines)) - self.change_point_mobs([0, 0, angles[0]+np.pi-angles[2]]) - self.change_point_mobs( - [0, 0, arc.angle], - ShowCreation(arc, run_time = 2) - ) - self.change_point_mobs([0, 0, np.pi/4 - angles[1]]) - self.change_point_mobs([0, 0, 0.99*np.pi], run_time = 4) - self.wait() - - self.set_variables_as_attrs(all_arcs, arc, arc_lines) - - def draw_lines_through_center(self): - point_mobs = self.point_mobs - angles = self.get_point_mob_angles() - all_arcs = self.all_arcs - - lines = self.get_center_lines() - - self.add_foreground_mobjects(self.center_dot) - for line in lines: - self.play(ShowCreation(line)) - self.play(FadeIn(all_arcs), Animation(point_mobs)) - self.remove(self.circle) - self.wait() - self.play( - all_arcs.space_out_submobjects, 1.5, - Animation(point_mobs), - rate_func = there_and_back, - run_time = 1.5, - ) - self.wait() - self.change_point_mobs( - [0, 0, np.mean(angles[:2])+np.pi-angles[2]] - ) - self.wait() - for x in range(3): - self.change_point_mobs([0, 0, np.pi/2]) - self.wait() - - def ask_about_probability_p3_lands_in_this_arc(self): - arc = self.arc - - arrow = Vector(LEFT, color = BLUE) - arrow.next_to(arc.get_center(), RIGHT, MED_LARGE_BUFF) - question = TextMobject("Probability of landing \\\\ in this arc?") - question.scale(0.8) - question.next_to(arrow, RIGHT) - question.shift_onto_screen() - question.shift(SMALL_BUFF*UP) - - answer = TexMobject( - "{\\text{Length of arc}", "\\over", - "\\text{Circumference}}" - ) - answer.set_color_by_tex("arc", BLUE) - answer.scale(0.8) - answer.next_to(arrow, RIGHT) - equals = TexMobject("=") - equals.rotate(np.pi/2) - equals.next_to(answer, UP, buff = 0.35) - - self.play(FadeIn(question), GrowArrow(arrow)) - self.have_p3_jump_around_randomly(15) - self.play( - question.next_to, answer, UP, LARGE_BUFF, - Write(equals), - FadeIn(answer) - ) - self.have_p3_jump_around_randomly(4) - angles = self.get_point_mob_angles() - self.change_point_mobs( - [0, 0, 1.35*np.pi - angles[2]], - run_time = 0, - ) - self.wait() - - question.add(equals) - self.arc_prob_question = question - self.arc_prob = answer - self.arc_size_arrow = arrow - - def various_arc_sizes_for_p1_p2_placements(self): - arc = self.arc - - self.triangle.save_state() - self.play(*list(map(FadeOut, [ - self.push_pins, self.triangle, self.arc_lines - ]))) - self.update_animations.remove(self.triangle_update) - self.update_animations += [ - self.get_center_lines_update(self.point_mobs, self.center_lines), - self.get_arcs_update(self.all_arcs) - ] - - #90 degree angle - self.change_point_mobs_to_angles([np.pi/2, np.pi], run_time = 1) - elbow = VGroup( - Line(DOWN, DOWN+RIGHT), - Line(DOWN+RIGHT, RIGHT), - ) - elbow.scale(0.25) - elbow.shift(self.center) - ninety_degrees = TexMobject("90^\\circ") - ninety_degrees.next_to(elbow, DOWN+RIGHT, buff = 0) - proportion = DecimalNumber(0.25) - proportion.set_color(self.center_color) - # proportion.next_to(arc.point_from_proportion(0.5), DOWN, MED_LARGE_BUFF) - proportion.next_to(self.arc_size_arrow, DOWN) - def proportion_update_func(alpha): - angles = self.get_point_mob_angles() - diff = abs(angles[1]-angles[0])/(2*np.pi) - return min(diff, 1-diff) - proportion_update = ChangingDecimal(proportion, proportion_update_func) - - self.play(ShowCreation(elbow), FadeIn(ninety_degrees)) - self.wait() - self.play( - ApplyMethod( - arc.rotate_in_place, np.pi/12, - rate_func = wiggle, - ) - ) - self.play(LaggedStartMap(FadeIn, proportion, run_time = 1)) - self.wait() - - #Non right angles - angle_pairs = [ - (0.26*np.pi, 1.24*np.pi), - (0.73*np.pi, 0.78*np.pi), - (0.5*np.pi, np.pi), - ] - self.update_animations.append(proportion_update) - for angle_pair in angle_pairs: - self.change_point_mobs_to_angles( - angle_pair, - VGroup(elbow, ninety_degrees).fade, 1, - ) - self.remove(elbow, ninety_degrees) - self.wait() - - self.set_variables_as_attrs(proportion, proportion_update) - - def ask_about_average_arc_size(self): - proportion = self.proportion - brace = Brace(proportion, DOWN, buff = SMALL_BUFF) - average = brace.get_text("Average?", buff = SMALL_BUFF) - - self.play( - GrowFromCenter(brace), - Write(average) - ) - for x in range(6): - self.change_point_mobs_to_angles( - 2*np.pi*np.random.random(2) - ) - self.change_point_mobs_to_angles( - [1.2*np.pi, 0.3*np.pi] - ) - self.wait() - - self.set_variables_as_attrs(brace, average) - - def fix_p1_in_place(self): - push_pin = self.push_pins[0] - P1, P2, P3 = point_mobs = self.point_mobs - - self.change_point_mobs_to_angles([0.9*np.pi]) - push_pin.move_to(P1.get_center(), DOWN) - push_pin.save_state() - push_pin.shift(UP) - push_pin.fade(1) - self.play(push_pin.restore) - for angle in [0.89999*np.pi, -0.09999*np.pi, 0.4*np.pi]: - self.change_point_mobs_to_angles( - [0.9*np.pi, angle], - run_time = 4, - ) - self.play(FadeOut(self.average[-1])) - - def overall_probability(self): - point_mobs = self.point_mobs - triangle = self.triangle - - one_fourth = TexMobject("1/4") - one_fourth.set_color(BLUE) - one_fourth.next_to(self.question, DOWN) - - self.triangle_update.update(1) - self.play( - FadeIn(triangle), - Animation(point_mobs) - ) - self.update_animations.append(self.triangle_update) - self.have_p3_jump_around_randomly(8, wait_time = 0.25) - self.play(ReplacementTransform( - self.proportion.copy(), VGroup(one_fourth) - )) - self.have_p3_jump_around_randomly(32, wait_time = 0.25) - - ##### - - def get_point_mobs(self): - points = np.array([ - self.center + rotate_vector(self.radius*RIGHT, theta) - for theta in 2*np.pi*np.random.random(3) - ]) - for index in 0, 1, 0: - if self.points_contain_center(points): - break - points[index] -= self.center - points[index] *= -1 - points[index] += self.center - point_mobs = self.point_mobs = VGroup(*[ - Dot().move_to(point) for point in points - ]) - point_mobs.set_color(self.point_color) - return point_mobs - - def get_point_mob_labels(self): - point_labels = VGroup(*[ - TexMobject("P_%d"%(i+1)) - for i in range(len(self.point_mobs)) - ]) - point_labels.set_color(self.point_mobs.get_color()) - self.point_labels = point_labels - return point_labels - - def get_triangle(self): - triangle = self.triangle = RegularPolygon(n = 3) - triangle.set_fill(WHITE, opacity = self.triangle_fill_opacity) - return triangle - - def get_center_lines(self): - angles = self.get_point_mob_angles() - lines = VGroup() - for angle in angles[:2]: - line = DashedLine( - self.radius*RIGHT, self.radius*LEFT - ) - line.rotate(angle) - line.shift(self.center) - line.set_color(self.point_color) - lines.add(line) - self.center_lines = lines - return lines - - def get_labels_update(self, point_mobs, labels): - def update_labels(labels): - for point_mob, label in zip(point_mobs, labels): - label.move_to(point_mob) - vect = point_mob.get_center() - self.center - vect /= get_norm(vect) - label.shift(MED_LARGE_BUFF*vect) - return labels - return UpdateFromFunc(labels, update_labels) - - def get_triangle_update(self, point_mobs, triangle): - def update_triangle(triangle): - points = [pm.get_center() for pm in point_mobs] - triangle.set_points_as_corners(points) - if self.points_contain_center(points): - triangle.set_color(self.positive_triangle_color) - else: - triangle.set_color(self.negative_triangle_color) - return triangle - return UpdateFromFunc(triangle, update_triangle) - - def get_center_lines_update(self, point_mobs, center_lines): - def update_lines(center_lines): - for point_mob, line in zip(point_mobs, center_lines): - point = point_mob.get_center() - self.center - line.rotate_in_place( - angle_of_vector(point) - line.get_angle() - ) - line.move_to(self.center) - return center_lines - return UpdateFromFunc(center_lines, update_lines) - - def get_arcs_update(self, all_arcs): - def update_arcs(arcs): - new_arcs = self.get_all_arcs() - Transform(arcs, new_arcs).update(1) - return arcs - return UpdateFromFunc(all_arcs, update_arcs) - - def get_all_arcs(self): - angles = self.get_point_mob_angles() - all_arcs = VGroup() - for da0, da1 in it.product(*[[0, np.pi]]*2): - arc_angle = (angles[1]+da1) - (angles[0]+da0) - arc_angle = (arc_angle+np.pi)%(2*np.pi)-np.pi - arc = Arc( - start_angle = angles[0]+da0, - angle = arc_angle, - radius = self.radius, - stroke_width = 5, - ) - arc.shift(self.center) - all_arcs.add(arc) - all_arcs.set_color_by_gradient(RED, MAROON_B, PINK, BLUE) - self.all_arcs = all_arcs - return all_arcs - - def points_contain_center(self, points): - p0, p1, p2 = points - v1 = p1 - p0 - v2 = p2 - p0 - c = self.center - p0 - M = np.matrix([v1[:2], v2[:2]]).T - M_inv = np.linalg.inv(M) - coords = np.dot(M_inv, c[:2]) - return np.all(coords > 0) and (np.sum(coords.flatten()) <= 1) - - def get_point_mob_theta_change_anim(self, point_mob, d_theta): - curr_theta = angle_of_vector(point_mob.get_center() - self.center) - d_theta = (d_theta + np.pi)%(2*np.pi) - np.pi - new_theta = curr_theta + d_theta - - def update_point(point_mob, alpha): - theta = interpolate(curr_theta, new_theta, alpha) - point_mob.move_to(self.center + self.radius*( - np.cos(theta)*RIGHT + np.sin(theta)*UP - )) - return point_mob - return UpdateFromAlphaFunc(point_mob, update_point, run_time = 2) - - def change_point_mobs(self, d_thetas, *added_anims, **kwargs): - anims = it.chain( - self.update_animations, - [ - self.get_point_mob_theta_change_anim(pm, dt) - for pm, dt in zip(self.point_mobs, d_thetas) - ], - added_anims - ) - self.play(*anims, **kwargs) - for update in self.update_animations: - update.update(1) - - def change_point_mobs_randomly(self, *added_anims, **kwargs): - d_thetas = 2*np.pi*np.random.random(len(self.point_mobs)) - self.change_point_mobs(d_thetas, *added_anims, **kwargs) - - def change_point_mobs_to_angles(self, target_angles, *added_anims, **kwargs): - angles = self.get_point_mob_angles() - n_added_targets = len(angles) - len(target_angles) - target_angles = list(target_angles) + list(angles[-n_added_targets:]) - self.change_point_mobs( - [ta-a for a, ta in zip(angles, target_angles)], - *added_anims, **kwargs - ) - - def get_point_mob_angles(self): - point_mobs = self.point_mobs - points = [pm.get_center() - self.center for pm in point_mobs] - return np.array(list(map(angle_of_vector, points))) - - def have_p3_jump_around_randomly(self, n_jumps, wait_time = 0.75, run_time = 0): - for x in range(n_jumps): - self.change_point_mobs( - [0, 0, 2*np.pi*random.random()], - run_time = run_time - ) - self.wait(wait_time) - -class FixThreePointsOnSphere(ExternallyAnimatedScene): - pass - -class AddCenterLinesAndPlanesToSphere(ExternallyAnimatedScene): - pass - -class AverageSizeOfSphericalTriangleSection(ExternallyAnimatedScene): - pass - -class AverageSizeOfSphericalTriangleSectionSupplement(Scene): - def construct(self): - words = TextMobject( - "Average size of \\\\", "this section", "?", - arg_separator = "" - ) - words.set_color_by_tex("section", GREEN) - words.set_width(FRAME_WIDTH - 1) - words.to_edge(DOWN) - self.play(Write(words)) - self.wait(3) - -class TryASurfaceIntegral(TeacherStudentsScene): - def construct(self): - self.student_says("Can you do \\\\ a surface integral?") - self.change_student_modes("confused", "raise_left_hand", "confused") - self.wait() - self.teacher_says( - "I mean...you can \\emph{try}", - target_mode = "sassy", - ) - self.wait(2) - -class RevisitTwoDCase(TwoDCase): - CONFIG = { - "random_seed" : 4, - "center" : 3*LEFT + 0.5*DOWN, - "radius" : 2, - "n_random_trials" : 200, - } - def construct(self): - self.force_skipping() - - self.setup_circle() - self.show_probability() - self.add_lines_and_comment_on_them() - self.rewrite_random_procedure() - self.four_possibilities_for_coin_flips() - - def setup_circle(self): - point_mobs = self.get_point_mobs() - point_labels = self.get_point_mob_labels() - triangle = self.get_triangle() - circle = Circle(radius = self.radius, color = WHITE) - center_dot = Dot(color = self.center_color) - VGroup(circle, center_dot).shift(self.center) - - self.point_labels_update = self.get_labels_update(point_mobs, point_labels) - self.triangle_update = self.get_triangle_update(point_mobs, triangle) - self.update_animations = [ - self.triangle_update, - self.point_labels_update, - ] - for anim in self.update_animations: - anim.update(1) - - self.add( - center_dot, circle, triangle, - point_mobs, point_labels - ) - self.add_foreground_mobjects(center_dot) - self.set_variables_as_attrs(circle, center_dot) - - def show_probability(self): - title = TexMobject( - "P(\\text{triangle contains the center})", - "=", "1/4" - ) - title.to_edge(UP, buff = MED_SMALL_BUFF) - title.set_color_by_tex("1/4", BLUE) - four = title[-1][-1] - four_circle = Circle(color = YELLOW) - four_circle.replace(four, dim_to_match = 1) - four_circle.scale_in_place(1.2) - - self.n_in = 0 - self.n_out = 0 - frac = TexMobject( - "{0", "\\over", "\\quad 0", "+", "0 \\quad}", "=" - ) - placeholders = frac.get_parts_by_tex("0") - positions = [ORIGIN, RIGHT, LEFT] - frac.next_to(self.circle, RIGHT, 1.5*LARGE_BUFF) - - def place_random_triangles(n, wait_time): - for x in range(n): - self.change_point_mobs_randomly(run_time = 0) - contain_center = self.points_contain_center( - [pm.get_center() for pm in self.point_mobs] - ) - if contain_center: - self.n_in += 1 - else: - self.n_out += 1 - nums = list(map(Integer, [self.n_in, self.n_in, self.n_out])) - VGroup(*nums[:2]).set_color(self.positive_triangle_color) - VGroup(*nums[2:]).set_color(self.negative_triangle_color) - for num, placeholder, position in zip(nums, placeholders, positions): - num.move_to(placeholder, position) - decimal = DecimalNumber(float(self.n_in)/(self.n_in + self.n_out)) - decimal.next_to(frac, RIGHT, SMALL_BUFF) - - self.add(decimal, *nums) - self.wait(wait_time) - self.remove(decimal, *nums) - return VGroup(decimal, *nums) - - - self.play(Write(title)) - self.add(frac) - self.remove(*placeholders) - place_random_triangles(10, 0.25) - nums = place_random_triangles(self.n_random_trials, 0.05) - self.add(nums) - self.wait() - self.play(*list(map(FadeOut, [frac, nums, title]))) - - def add_lines_and_comment_on_them(self): - center_lines = self.get_center_lines() - center_lines.save_state() - center_line_shadows = center_lines.copy() - center_line_shadows.set_stroke(LIGHT_GREY, 2) - arcs = self.get_all_arcs() - - center_lines.generate_target() - center_lines.target.to_edge(RIGHT, buff = LARGE_BUFF) - rect = SurroundingRectangle(center_lines.target, buff = MED_SMALL_BUFF) - rect.set_stroke(WHITE, 2) - - words1 = TextMobject("Helpful new objects") - words2 = TextMobject("Reframe problem around these") - for words in words1, words2: - words.scale(0.8) - words.next_to(rect, UP) - words.shift_onto_screen() - - self.play(LaggedStartMap(ShowCreation, center_lines, run_time = 1)) - self.play( - LaggedStartMap(FadeIn, arcs, run_time = 1), - Animation(self.point_mobs), - ) - self.wait() - self.add(center_line_shadows) - self.play(MoveToTarget(center_lines)) - self.play(ShowCreation(rect), Write(words1)) - self.wait(2) - self.play(ReplacementTransform(words1, words2)) - self.wait(2) - self.play( - center_lines.restore, - center_lines.fade, 1, - *list(map(FadeOut, [ - rect, words2, center_line_shadows, - self.triangle, arcs, - self.point_mobs, - self.point_labels, - ])) - ) - center_lines.restore() - self.remove(center_lines) - - def rewrite_random_procedure(self): - point_mobs = self.point_mobs - center_lines = self.center_lines - - random_procedure = TextMobject("Random procedure") - underline = Line(LEFT, RIGHT) - underline.stretch_to_fit_width(random_procedure.get_width()) - underline.scale(1.1) - underline.next_to(random_procedure, DOWN) - group = VGroup(random_procedure, underline) - group.to_corner(UP+RIGHT) - - words = VGroup(*list(map(TextMobject, [ - "Choose 3 random points", - "Choose 2 random lines", - "Flip coin for each line \\\\ to get $P_1$ and $P_2$", - "Choose $P_3$ at random" - ]))) - words.scale(0.8) - words.arrange(DOWN, buff = MED_LARGE_BUFF) - words.next_to(underline, DOWN) - words[1].set_color(YELLOW) - - point_label_groups = VGroup() - for point_mob, label in zip(self.point_mobs, self.point_labels): - group = VGroup(point_mob, label) - group.save_state() - group.move_to(words[0], LEFT) - group.fade(1) - point_label_groups.add(group) - self.point_label_groups = point_label_groups - - cross = Cross(words[0]) - cross.set_stroke(RED, 6) - - self.center_lines_update = self.get_center_lines_update( - point_mobs, center_lines - ) - self.update_animations.append(self.center_lines_update) - self.update_animations.remove(self.triangle_update) - - #Choose random points - self.play( - Write(random_procedure), - ShowCreation(underline) - ) - self.play(FadeIn(words[0])) - self.play(LaggedStartMap( - ApplyMethod, point_label_groups, - lambda mob : (mob.restore,), - )) - self.play( - ShowCreation(cross), - point_label_groups.fade, 1, - ) - self.wait() - - #Choose two random lines - self.center_lines_update.update(1) - self.play( - FadeIn(words[1]), - LaggedStartMap(GrowFromCenter, center_lines) - ) - for x in range(3): - self.change_point_mobs_randomly(run_time = 1) - self.change_point_mobs_to_angles([0.8*np.pi, 1.3*np.pi]) - - #Flip a coin for each line - def flip_point_label_back_and_forth(point_mob, label): - for x in range(6): - point_mob.rotate(np.pi, about_point = self.center) - self.point_labels_update.update(1) - self.wait(0.5) - self.wait(0.5) - - def choose_p1_and_p2(): - for group in point_label_groups[:2]: - group.set_fill(self.point_color, 1) - flip_point_label_back_and_forth(*group) - - choose_p1_and_p2() - self.play(Write(words[2])) - - #Seems convoluted - randy = Randolph().flip() - randy.scale(0.5) - randy.to_edge(DOWN) - randy.shift(2*RIGHT) - - self.play(point_label_groups.fade, 1) - self.change_point_mobs_randomly(run_time = 1) - choose_p1_and_p2() - point_label_groups.fade(1) - self.change_point_mobs_randomly(FadeIn(randy)) - self.play( - PiCreatureSays( - randy, "Seems \\\\ convoluted", - bubble_kwargs = {"height" : 2, "width" : 2}, - target_mode = "confused" - ) - ) - choose_p1_and_p2() - self.play( - FadeOut(randy.bubble), - FadeOut(randy.bubble.content), - randy.change, "pondering", - ) - self.play(Blink(randy)) - self.play(FadeOut(randy)) - - #Choosing the third point - self.change_point_mobs([0, 0, -np.pi/2], run_time = 0) - p3_group = point_label_groups[2] - p3_group.save_state() - p3_group.move_to(words[3], LEFT) - - self.play(Write(words[3], run_time = 1)) - self.play( - p3_group.restore, - p3_group.set_fill, YELLOW, 1 - ) - self.wait() - self.play(Swap(*words[2:4])) - self.wait() - - #Once the continuous randomness is handled - rect = SurroundingRectangle(VGroup(words[1], words[3])) - rect.set_stroke(WHITE, 2) - brace = Brace(words[2], DOWN) - brace_text = brace.get_text("4 equally likely outcomes") - brace_text.scale_in_place(0.8) - - self.play(ShowCreation(rect)) - self.play(GrowFromCenter(brace)) - self.play(Write(brace_text)) - self.wait() - - self.random_procedure_words = words - - def four_possibilities_for_coin_flips(self): - arcs = self.all_arcs - point_mobs = self.point_mobs - arc = arcs[-1] - point_label_groups = self.point_label_groups - arc_update = self.get_arcs_update(arcs) - arc_update.update(1) - self.update_animations.append(arc_update) - - def second_arc_update_func(arcs): - VGroup(*arcs[:-1]).set_stroke(width = 0) - arcs[-1].set_stroke(PINK, 6) - return arcs - second_arc_update = UpdateFromFunc(arcs, second_arc_update_func) - second_arc_update.update(1) - self.update_animations.append(second_arc_update) - self.update_animations.append(Animation(point_label_groups)) - - def do_the_rounds(): - for index in 0, 1, 0, 1: - point_mob = point_mobs[index] - point_mob.generate_target() - point_mob.target.rotate( - np.pi, about_point = self.center, - ) - self.play( - MoveToTarget(point_mob), - *self.update_animations, - run_time = np.sqrt(2)/4 #Hacky reasons to be irrational - ) - self.wait() - - self.revert_to_original_skipping_status() - do_the_rounds() - self.triangle_update.update(1) - self.remove(arcs) - self.update_animations.remove(arc_update) - self.update_animations.remove(second_arc_update) - self.play(FadeIn(self.triangle)) - self.wait() - self.update_animations.insert(0, self.triangle_update) - do_the_rounds() - self.wait() - self.change_point_mobs_randomly() - for x in range(2): - do_the_rounds() - -class ThisIsWhereItGetsGood(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "This is where \\\\ things get good", - target_mode = "hooray" - ) - self.change_student_modes(*["hooray"]*3) - self.wait(2) - -class ContrastTwoRandomProcesses(TwoDCase): - CONFIG = { - "radius" : 1.5, - "random_seed" : 0, - } - def construct(self): - circle = Circle(color = WHITE, radius = self.radius) - point_mobs = self.get_point_mobs() - for point in point_mobs: - point.scale_in_place(1.5) - point.set_stroke(RED, 1) - labels = self.get_point_mob_labels() - self.get_labels_update(point_mobs, labels).update(1) - center_lines = self.get_center_lines() - point_label_groups = VGroup(*[ - VGroup(*pair) for pair in zip(point_mobs, labels) - ]) - - right_circles = VGroup(*[ - VGroup(circle, *point_label_groups[:i+1]).copy() - for i in range(3) - ]) - left_circles = VGroup( - VGroup(circle, center_lines).copy(), - VGroup( - circle, center_lines, - point_label_groups[2] - ).copy(), - VGroup( - circle, center_lines, - *point_label_groups[2::-1] - ).copy(), - ) - for circles in left_circles, right_circles: - circles.scale(0.5) - circles[0].to_edge(UP, buff = MED_LARGE_BUFF) - circles[2].to_edge(DOWN, buff = MED_LARGE_BUFF) - for c1, c2 in zip(circles, circles[1:]): - circles.add(Arrow(c1[0], c2[0], color = GREEN)) - left_circles.shift(3*LEFT) - right_circles.shift(3*RIGHT) - - vs = TextMobject("vs.") - self.show_creation_of_circle_group(left_circles) - self.play(Write(vs)) - self.show_creation_of_circle_group(right_circles) - self.wait() - - def show_creation_of_circle_group(self, group): - circles = group[:3] - arrows = group[3:] - - self.play( - ShowCreation(circles[0][0]), - FadeIn(VGroup(*circles[0][1:])), - ) - for c1, c2, arrow in zip(circles, circles[1:], arrows): - self.play( - GrowArrow(arrow), - ApplyMethod( - c1.copy().shift, - c2[0].get_center() - c1[0].get_center(), - remover = True - ) - ) - self.add(c2) - n = len(c2) - len(c1) - self.play(*list(map(GrowFromCenter, c2[-n:]))) - -class Rewrite3DRandomProcedure(Scene): - def construct(self): - random_procedure = TextMobject("Random procedure") - underline = Line(LEFT, RIGHT) - underline.stretch_to_fit_width(random_procedure.get_width()) - underline.scale(1.1) - underline.next_to(random_procedure, DOWN) - group = VGroup(random_procedure, underline) - group.to_corner(UP+LEFT) - - words = VGroup(*list(map(TextMobject, [ - "Choose 4 random points", - "Choose 3 random lines", - "Choose $P_4$ at random", - "Flip coin for each line \\\\ to get $P_1$, $P_2$, $P_3$", - ]))) - words.scale(0.8) - words.arrange(DOWN, buff = MED_LARGE_BUFF) - words.next_to(underline, DOWN) - words[1].set_color(YELLOW) - cross = Cross(words[0]) - cross.set_stroke(RED, 6) - - self.play( - Write(random_procedure), - ShowCreation(underline) - ) - self.play(FadeIn(words[0])) - self.play(ShowCreation(cross)) - self.wait() - self.play(LaggedStartMap(FadeIn, words[1])) - self.play(LaggedStartMap(FadeIn, words[2])) - self.wait(2) - self.play(Write(words[3])) - self.wait(3) - -class AntipodalViewOfThreeDCase(ExternallyAnimatedScene): - pass - -class ThreeDAnswer(Scene): - def construct(self): - words = TextMobject( - "Probability that the tetrahedron contains center:", - "$\\frac{1}{8}$" - ) - words.set_width(FRAME_WIDTH - 1) - words.to_edge(DOWN) - words[1].set_color(BLUE) - - self.play(Write(words)) - self.wait(2) - -class FormalWriteupScreenCapture(ExternallyAnimatedScene): - pass - -class Formality(TeacherStudentsScene): - def construct(self): - words = TextMobject( - "Write-up by Ralph Howard and Paul Sisson (link below)" - ) - words.scale(0.7) - words.to_corner(UP+LEFT, buff = MED_SMALL_BUFF) - - self.student_says( - "How would you \\\\ write that down?", - target_mode = "sassy" - ) - self.change_student_modes("confused", "sassy", "erm") - self.wait() - self.play( - Write(words), - FadeOut(self.students[1].bubble), - FadeOut(self.students[1].bubble.content), - self.teacher.change, "raise_right_hand" - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = words - ) - self.wait(8) - -class ProblemSolvingTakeaways(Scene): - def construct(self): - title = TextMobject("Problem solving takeaways") - underline = Line(LEFT, RIGHT) - underline.set_width(title.get_width()*1.1) - underline.next_to(title, DOWN) - group = VGroup(title, underline) - group.to_corner(UP+LEFT) - - points = VGroup(*[ - TextMobject(string, alignment = "") - for string in [ - "Ask a simpler version \\\\ of the question", - "Try reframing the question \\\\ around new constructs", - ] - ]) - points[0].set_color(BLUE) - points[1].set_color(YELLOW) - points.arrange( - DOWN, buff = LARGE_BUFF, - aligned_edge = LEFT - ) - points.next_to(group, DOWN, LARGE_BUFF) - - self.play(Write(title), ShowCreation(underline)) - self.wait() - for point in points: - self.play(Write(point)) - self.wait(3) - -class BrilliantPuzzle(PiCreatureScene): - CONFIG = { - "random_seed" : 2, - } - def construct(self): - students = self.students - tests = VGroup() - for student in students: - test = self.get_test() - test.move_to(0.75*student.get_center()) - tests.add(test) - student.test = test - for i, student in enumerate(students): - student.right = students[(i+1)%len(students)] - student.left = students[(i-1)%len(students)] - arrows = VGroup() - for s1, s2 in adjacent_pairs(self.students): - arrow = Arrow( - s1.get_center(), s2.get_center(), - path_arc = np.pi/2, - buff = 0.8 - ) - arrow.tip.shift(SMALL_BUFF*arrow.get_vector()) - arrow.tip.shift(-0.1*SMALL_BUFF*arrow.tip.get_center()) - # arrow.shift(-MED_SMALL_BUFF*arrow.get_vector()) - arrow.set_color(RED) - arrow.pointing_right = True - arrows.add(arrow) - s1.arrow = arrow - arrow.student = s1 - - title = TextMobject("Puzzle from Brilliant") - title.scale(0.75) - title.to_corner(UP+LEFT) - - question = TextMobject("Expected number of \\\\ circled students?") - question.to_corner(UP+RIGHT) - - self.remove(students) - self.play(Write(title)) - self.play(LaggedStartMap(GrowFromCenter, students)) - self.play( - LaggedStartMap(Write, tests), - LaggedStartMap( - ApplyMethod, students, - lambda m : (m.change, "horrified", m.test) - ) - ) - self.wait() - self.play(LaggedStartMap( - ApplyMethod, students, - lambda m : (m.change, "conniving") - )) - self.play(LaggedStartMap(ShowCreation, arrows)) - for x in range(2): - self.swap_arrows_randomly(arrows) - self.wait() - circles = self.circle_students() - self.play(Write(question)) - for x in range(10): - self.swap_arrows_randomly(arrows, FadeOut(circles)) - circles = self.circle_students() - self.wait() - - #### - - def get_test(self): - lines = VGroup(*[Line(ORIGIN, 0.5*RIGHT) for x in range(6)]) - lines.arrange(DOWN, buff = SMALL_BUFF) - rect = SurroundingRectangle(lines) - rect.set_stroke(WHITE) - lines.set_stroke(WHITE, 2) - test = VGroup(rect, lines) - test.set_height(0.5) - return test - - def create_pi_creatures(self): - self.students = VGroup(*[ - PiCreature( - color = random.choice([BLUE_C, BLUE_D, BLUE_E, GREY_BROWN]) - ).scale(0.25).move_to(3*vect) - for vect in compass_directions(8) - ]) - return self.students - - def get_arrow_swap_anim(self, arrow): - arrow.generate_target() - if arrow.pointing_right: - target_color = GREEN - target_angle = np.pi - np.pi/4 - else: - target_color = RED - target_angle = np.pi + np.pi/4 - arrow.target.set_color(target_color) - arrow.target.rotate( - target_angle, - about_point = arrow.student.get_center() - ) - arrow.pointing_right = not arrow.pointing_right - return MoveToTarget(arrow, path_arc = np.pi) - - def swap_arrows_randomly(self, arrows, *added_anims): - anims = [] - for arrow in arrows: - if random.choice([True, False]): - anims.append(self.get_arrow_swap_anim(arrow)) - self.play(*anims + list(added_anims)) - - def circle_students(self): - circles = VGroup() - circled_students = list(self.students) - for student in self.students: - if student.arrow.pointing_right: - to_remove = student.right - else: - to_remove = student.left - if to_remove in circled_students: - circled_students.remove(to_remove) - for student in circled_students: - circle = Circle(color = YELLOW) - circle.set_height(1.2*student.get_height()) - circle.move_to(student) - circles.add(circle) - self.play(ShowCreation(circle)) - return circles - -class ScrollThroughBrilliantCourses(ExternallyAnimatedScene): - pass - -class BrilliantProbability(ExternallyAnimatedScene): - pass - -class Promotion(PiCreatureScene): - CONFIG = { - "seconds_to_blink" : 5, - } - def construct(self): - url = TextMobject("https://brilliant.org/3b1b/") - url.to_corner(UP+LEFT) - - rect = Rectangle(height = 9, width = 16) - rect.set_height(5.5) - rect.next_to(url, DOWN) - rect.to_edge(LEFT) - - self.play( - Write(url), - self.pi_creature.change, "raise_right_hand" - ) - self.play(ShowCreation(rect)) - self.wait(2) - self.change_mode("thinking") - self.wait() - self.look_at(url) - self.wait(10) - self.change_mode("happy") - self.wait(10) - self.change_mode("raise_right_hand") - self.wait(10) - - self.remove(rect) - self.play( - url.next_to, self.pi_creature, UP+LEFT - ) - url_rect = SurroundingRectangle(url) - self.play(ShowCreation(url_rect)) - self.play(FadeOut(url_rect)) - self.wait(3) - -class AddedPromoWords(Scene): - def construct(self): - words = TextMobject( - "First", "$2^8$", "vistors get", - "$(e^\\pi - \\pi)\\%$", "off" - ) - words.set_width(FRAME_WIDTH - 1) - words.to_edge(DOWN) - words.set_color_by_tex("2^8", YELLOW) - words.set_color_by_tex("pi", PINK) - - self.play(Write(words)) - self.wait() - -class PatreonThanks(PatreonEndScreen): - CONFIG = { - "specific_patrons" : [ - "Randall Hunt", - "Burt Humburg", - "CrypticSwarm", - "Juan Benet", - "David Kedmey", - "Marcus Schiebold", - "Ali Yahya", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Jordan Scales", - "Markus Persson", - "Egor Gumenuk", - "Yoni Nazarathy", - "Ryan Atallah", - "Joseph John Cox", - "Luc Ritchie", - "James Park", - "Samantha D. Suplee", - "Delton", - "Thomas Tarler", - "Jake Alzapiedi", - "Jonathan Eppele", - "Taro Yoshioka", - "1stViewMaths", - "Jacob Magnuson", - "Mark Govea", - "Dagan Harrington", - "Clark Gaebel", - "Eric Chow", - "Mathias Jansson", - "David Clark", - "Michael Gardner", - "Erik Sundell", - "Awoo", - "Dr. David G. Stork", - "Tianyu Ge", - "Ted Suzman", - "Linh Tran", - "Andrew Busey", - "John Haley", - "Ankalagon", - "Eric Lavault", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Ryan Dahl", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ] - } - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/pythagorean_proof.py b/from_3b1b/old/pythagorean_proof.py deleted file mode 100644 index 067c4b3f..00000000 --- a/from_3b1b/old/pythagorean_proof.py +++ /dev/null @@ -1,509 +0,0 @@ -import numpy as np -import itertools as it -from copy import deepcopy -import sys - -from constants import * - -from scene.scene import Scene -from geometry import Polygon -from mobject.region import region_from_polygon_vertices, region_from_line_boundary - -A_COLOR = BLUE -B_COLOR = MAROON_D -C_COLOR = YELLOW - -TEX_MOB_SCALE_FACTOR = 0.5 -POINTS = np.array([ - DOWN, - 2*UP, - DOWN+RIGHT, - 2*DOWN, - 2*DOWN+RIGHT, - DOWN+3*LEFT, - 2*UP+3*LEFT, - 4*RIGHT, - 3*UP+3*RIGHT, -]) - -class Triangle(Polygon): - def __init__(self, **kwargs): - kwargs["color"] = C_COLOR - Polygon.__init__( - self, - *POINTS[[0, 1, 2]], - edge_colors = [B_COLOR, C_COLOR, A_COLOR], - **kwargs - ) - nudge = 0.2 - target = POINTS[0]+nudge*(UP+RIGHT) - for direction in UP, RIGHT: - self.add_line(POINTS[0]+nudge*direction, target, color = WHITE) - - - def add_all_letters(self): - for char in "abc": - self.add_letter(char) - return self - - def add_letter(self, char, nudge = 0.3): - mob = TexMobject(char).scale(TEX_MOB_SCALE_FACTOR) - if char == "a": - points = self.get_vertices()[[0, 2, 1]] - elif char == "b": - points = self.get_vertices()[[1, 0, 2]] - elif char == "c": - points = self.get_vertices()[[2, 1, 0]] - center = 0.5*sum(points[:2]) #average of first two points - mob.shift(center) - normal_dir = rotate_vector(points[1] - points[0], np.pi/2, OUT) - if np.dot(normal_dir, points[2]-center) > 0: - normal_dir = -normal_dir - normal_dir /= get_norm(normal_dir) - mob.shift(nudge*normal_dir) - self.add(mob) - return self - - def place_hypotenuse_on(self, point1, point2): - #self.vertices[1], self.vertices[2] - start1, start2 = self.get_vertices()[[1, 2]] - target_vect = np.array(point2)-np.array(point1) - curr_vect = start2-start1 - self.scale(get_norm(target_vect)/get_norm(curr_vect)) - self.rotate(angle_of_vector(target_vect)-angle_of_vector(curr_vect)) - self.shift(point1-self.get_vertices()[1]) - return self - - - -def a_square(**kwargs): - return Polygon(*POINTS[[0, 2, 4, 3]], color = A_COLOR, **kwargs) - -def b_square(**kwargs): - return Polygon(*POINTS[[1, 0, 5, 6]], color = B_COLOR, **kwargs) - -def c_square(**kwargs): - return Polygon(*POINTS[[1, 2, 7, 8]], color = C_COLOR, **kwargs) - - -class DrawPointsReference(Scene): - def construct(self): - for point, count in zip(POINTS, it.count()): - mob = TexMobject(str(count)).scale(TEX_MOB_SCALE_FACTOR) - mob.shift(POINTS[count]) - self.add(mob) - -class DrawTriangle(Scene): - def construct(self): - self.add(Triangle().add_all_letters()) - -class DrawAllThreeSquares(Scene): - def construct(self): - a = a_square() - b = b_square() - c = c_square() - self.add(Triangle(), a, b, c) - for letter, mob in zip("abc", [a, b, c]): - char_mob = TexMobject(letter+"^2").scale(TEX_MOB_SCALE_FACTOR) - char_mob.shift(mob.get_center()) - self.add(char_mob) - - -class AddParallelLines(DrawAllThreeSquares): - args_list = [ - (1, False), - (2, False), - (3, False), - (3, True), - ] - @staticmethod - def args_to_string(num, trim): - return str(num) + ("Trimmed" if trim else "") - - def construct(self, num, trim): - DrawAllThreeSquares.construct(self) - shift_pairs = [ - (4*RIGHT, 3*UP), - (ORIGIN, DOWN), - (3*LEFT, 2*DOWN) - ] - for side_shift, vert_shift in shift_pairs[:num]: - line1 = Line(BOTTOM, TOP, color = WHITE) - line1.shift(side_shift) - line2 = Line(LEFT_SIDE, RIGHT_SIDE, color = WHITE) - line2.shift(vert_shift) - self.add(line1, line2) - if trim: - for mob in self.mobjects: - mob.filter_out(lambda p : p[0] > 4) - mob.filter_out(lambda p : p[0] < -3) - mob.filter_out(lambda p : p[1] > 3) - mob.filter_out(lambda p : p[1] < -2) - -class HighlightEmergentTriangles(AddParallelLines): - args_list = [(3,True)] - def construct(self, *args): - AddParallelLines.construct(self, *args) - triplets = [ - [(0, 2), (0, -1), (1, -1)], - [(1, -1), (4, -1), (4, 0)], - [(4, 0), (4, 3), (3, 3)], - [(3, 3), (0, 3), (0, 2)], - ] - for triplet in triplets: - self.set_color_region( - region_from_polygon_vertices(*triplet), - color = "DARK_BLUE" - ) - -class IndicateTroublePointFromParallelLines(AddParallelLines): - args_list = [(3,True)] - def construct(self, *args): - AddParallelLines.construct(self, *args) - circle = Circle(radius = 0.25) - circle.shift(DOWN+RIGHT) - vect = DOWN+RIGHT - arrow = Arrow(circle.get_center()+2*vect, circle.get_boundary_point(vect)) - arrow.set_color(circle.get_color()) - self.add_mobjects_among(list(locals().values())) - - -class DrawAllThreeSquaresWithMoreTriangles(DrawAllThreeSquares): - args_list = [ - (1, True), - (2, True), - (3, True), - (4, True), - (5, True), - (6, True), - (7, True), - (8, True), - (9, True), - (10, True), - (10, False) - ] - @staticmethod - def args_to_string(num, fill): - fill_string = "" if fill else "HollowTriangles" - return str(num) + fill_string - - def construct(self, num, fill): - DrawAllThreeSquares.construct(self) - pairs = [ - ((0, 2, 0), (1, -1, 0)), - ((-3, -1, 0), (0, -2, 0)), - ((4, -1, 0), (1, -2, 0)), - ((0, -2, 0), (-3, -1, 0)), - ((1, -2, 0), (4, -1, 0)), - ((1, -1, 0), (4, 0, 0)), - ((4, 0, 0), (3, 3, 0)), - ((3, 3, 0), (0, 2, 0)), - ((-3, 3, 0), (0, 2, 0)), - ((0, 2, 0), (-3, 3, 0)) - ] - to_flip = [1, 3, 8, 9] - for n in range(num): - triangle = Triangle() - if n in to_flip: - triangle.rotate(np.pi, UP) - self.add(triangle.place_hypotenuse_on(*pairs[n])) - vertices = list(triangle.get_vertices()) - if n not in to_flip: - vertices.reverse() - if fill: - self.set_color_region( - region_from_polygon_vertices(*vertices), - color = DARK_BLUE - ) - -class IndicateBigRectangleTroublePoint(DrawAllThreeSquaresWithMoreTriangles): - args_list = [(10, False)] - def construct(self, *args): - DrawAllThreeSquaresWithMoreTriangles.construct(self, *args) - circle = Circle(radius = 0.25, color = WHITE) - circle.shift(4*RIGHT) - vect = DOWN+RIGHT - arrow = Arrow(circle.get_center()+vect, circle.get_boundary_point(vect)) - self.add_mobjects_among(list(locals().values())) - -class ShowBigRectangleDimensions(DrawAllThreeSquaresWithMoreTriangles): - args_list = [(10, False)] - def construct(self, num, fill): - DrawAllThreeSquaresWithMoreTriangles.construct(self, num, fill) - u_brace = Underbrace((-3, -2, 0), (4, -2, 0)) - side_brace = Underbrace((-3, -3, 0), (2, -3, 0)) - for brace in u_brace, side_brace: - brace.shift(0.2*DOWN) - side_brace.rotate(-np.pi/2) - a_plus_2b = TexMobject("a+2b").scale(TEX_MOB_SCALE_FACTOR) - b_plus_2a = TexMobject("b+2a").scale(TEX_MOB_SCALE_FACTOR) - a_plus_2b.next_to(u_brace, DOWN) - b_plus_2a.next_to(side_brace, LEFT) - self.add_mobjects_among(list(locals().values())) - -class FillInAreaOfBigRectangle(DrawAllThreeSquaresWithMoreTriangles): - args_list = [(10, False)] - def construct(self, *args): - DrawAllThreeSquaresWithMoreTriangles.construct(self, *args) - args_list = [(10, False)] - color = Color("yellow") - color.set_rgb(0.3*np.array(color.get_rgb())) - self.set_color_region( - region_from_polygon_vertices( - (-3, 3), - (-3, -2), - (4, -2), - (4, 3) - ), - color = color - ) - -class DrawOnlyABSquares(Scene): - def construct(self): - a = a_square() - b = b_square() - for char, mob in zip("ab", [a, b]): - symobl = TexMobject(char+"^2").scale(TEX_MOB_SCALE_FACTOR) - symobl.shift(mob.get_center()) - self.add(symobl) - triangle = Triangle() - self.add_mobjects_among(list(locals().values())) - -class AddTriangleCopyToABSquares(DrawOnlyABSquares): - def construct(self): - DrawOnlyABSquares.construct(self) - triangle = Triangle() - triangle.rotate(np.pi, UP) - triangle.place_hypotenuse_on(3*LEFT+DOWN, 2*DOWN) - self.add(triangle) - self.set_color_triangles() - - def set_color_triangles(self): - for mob in self.mobjects: - if isinstance(mob, Triangle): - vertices = list(mob.get_vertices()) - for x in range(2): - self.set_color_region(region_from_polygon_vertices( - *vertices - ), color = DARK_BLUE) - vertices.reverse()#silly hack - -class AddAllTrianglesToABSquares(AddTriangleCopyToABSquares): - def construct(self): - AddTriangleCopyToABSquares.construct(self) - self.add(Triangle().place_hypotenuse_on(RIGHT+DOWN, 2*UP)) - triangle = Triangle() - triangle.rotate(np.pi, UP) - triangle.place_hypotenuse_on(2*DOWN, 3*LEFT+DOWN) - self.add(triangle) - self.set_color_triangles() - - - -class DrawNakedCSqurae(Scene): - def construct(self): - c = c_square().center() - triangle = Triangle().place_hypotenuse_on(*c.get_vertices()[[0,1]]) - triangle.add_all_letters() - self.add(triangle, c) - - -class DrawCSquareWithAllTraingles(Scene): - args_list = [ - (False, False, False, False), - (False, True, False, True), - (True, True, False, False), - (False, True, True, False), - ] - @staticmethod - def args_to_string(*toggle_vector): - return "".join(map(str, list(map(int, toggle_vector)))) - - def construct(self, *toggle_vector): - if len(toggle_vector) == 0: - toggle_vector = [False]*4 - self.c_square = c_square().center() - vertices = it.cycle(self.c_square.get_vertices()) - last_vertex = next(vertices) - have_letters = False - self.triangles = [] - for vertex, should_flip in zip(vertices, toggle_vector): - triangle = Triangle() - pair = np.array([last_vertex, vertex]) - if should_flip: - triangle.rotate(np.pi, UP) - pair = pair[[1, 0]] - triangle.place_hypotenuse_on(*pair) - if not have_letters: - triangle.add_all_letters() - have_letters = True - self.triangles.append(triangle) - self.add(triangle) - last_vertex = vertex - self.add(self.c_square) - -class HighlightCSquareInBigSquare(DrawCSquareWithAllTraingles): - args_list = [tuple([False]*4)] - def construct(self, *args): - DrawCSquareWithAllTraingles.construct(self, *args) - self.set_color_region(region_from_polygon_vertices( - *c_square().center().get_vertices() - ), color = YELLOW) - -class IndicateCSquareTroublePoint(DrawCSquareWithAllTraingles): - def construct(self, *toggle_vector): - DrawCSquareWithAllTraingles.construct(self, *toggle_vector) - circle = Circle(color = WHITE) - circle.scale(0.25) - vertex = self.c_square.get_vertices()[1] - circle.shift(vertex) - vect = 2*RIGHT+DOWN - arrow = Arrow(vertex+vect, circle.get_boundary_point(vect)) - self.add(circle, arrow) - - -class ZoomInOnTroublePoint(Scene): - args_list = list(it.product([True, False], [True, False])) - - @staticmethod - def args_to_string(with_labels, rotate): - label_string = "WithLabels" if with_labels else "WithoutLabels" - rotate_string = "Rotated" if rotate else "" - return label_string + rotate_string - - def construct(self, with_labels, rotate): - zoom_factor = 10 - density = zoom_factor*DEFAULT_POINT_DENSITY_1D - c = c_square(density = density) - c.shift(-c.get_vertices()[1]) - c.scale(zoom_factor) - vertices = c.get_vertices() - for index in 0, 1: - triangle = Triangle(density = density) - triangle.place_hypotenuse_on(vertices[index], vertices[index+1]) - self.add(triangle) - circle = Circle(radius = 2.5, color = WHITE) - angle1_arc = Circle(color = WHITE) - angle2_arc = Circle(color = WHITE).scale(0.5) - angle1_arc.filter_out(lambda x_y_z2 : not (x_y_z2[0] > 0 and x_y_z2[1] > 0 and x_y_z2[1] < x_y_z2[0]/3)) - angle2_arc.filter_out(lambda x_y_z3 : not (x_y_z3[0] < 0 and x_y_z3[1] > 0 and x_y_z3[1] < -3*x_y_z3[0])) - - self.add_mobjects_among(list(locals().values())) - self.add_elbow() - if rotate: - for mob in self.mobjects: - mob.rotate(np.pi/2) - if with_labels: - alpha = TexMobject("\\alpha").scale(TEX_MOB_SCALE_FACTOR) - beta = TexMobject("90-\\alpha").scale(TEX_MOB_SCALE_FACTOR) - if rotate: - alpha.next_to(angle1_arc, UP+0.1*LEFT) - beta.next_to(angle2_arc, DOWN+0.5*LEFT) - else: - alpha.next_to(angle1_arc, RIGHT) - beta.next_to(angle2_arc, LEFT) - self.add(alpha, beta) - - - - def add_elbow(self): - c = 0.1 - p1 = c*LEFT + 3*c*UP - p2 = 3*c*RIGHT + c*UP - p3 = 2*c*RIGHT + 4*c*UP - self.add(Line(p1, p3, color = WHITE)) - self.add(Line(p2, p3, color = WHITE)) - - -class DrawTriangleWithAngles(Scene): - def construct(self): - triangle = Triangle(density = 2*DEFAULT_POINT_DENSITY_1D) - triangle.scale(2).center().add_all_letters() - vertices = triangle.get_vertices() - kwargs = {"color" : WHITE} - angle1_arc = Circle(radius = 0.4, **kwargs).filter_out( - lambda x_y_z : not(x_y_z[0] > 0 and x_y_z[1] < 0 and x_y_z[1] < -3*x_y_z[0]) - ).shift(vertices[1]) - angle2_arc = Circle(radius = 0.2, **kwargs).filter_out( - lambda x_y_z1 : not(x_y_z1[0] < 0 and x_y_z1[1] > 0 and x_y_z1[1] < -3*x_y_z1[0]) - ).shift(vertices[2]) - alpha = TexMobject("\\alpha") - beta = TexMobject("90-\\alpha") - alpha.shift(vertices[1]+3*RIGHT+DOWN) - beta.shift(vertices[2]+3*RIGHT+UP) - arrow1 = Arrow(alpha, angle1_arc) - arrow2 = Arrow(beta, angle2_arc) - - self.add(triangle, angle1_arc, angle2_arc, alpha, beta, arrow1, arrow2) - - -class LabelLargeSquare(DrawCSquareWithAllTraingles): - args_list = [] - def construct(self): - DrawCSquareWithAllTraingles.construct(self) - everything = Mobject(*self.mobjects) - u_brace = Underbrace(2*(DOWN+LEFT), 2*(DOWN+RIGHT)) - u_brace.shift(0.2*DOWN) - side_brace = deepcopy(u_brace).rotate(np.pi/2) - upper_brace = deepcopy(u_brace).rotate(np.pi) - a_plus_b = TexMobject("a+b").scale(TEX_MOB_SCALE_FACTOR) - upper_brace.add(a_plus_b.next_to(upper_brace, UP)) - side_brace.add(a_plus_b.next_to(side_brace, RIGHT)) - self.add(upper_brace, side_brace) - -class CompletelyFillLargeSquare(LabelLargeSquare): - def construct(self): - LabelLargeSquare.construct(self) - vertices = [2*(DOWN+LEFT), 2*(DOWN+RIGHT), 2*(UP+RIGHT), 2*(UP+LEFT)] - vertices.append(vertices[0]) - pairs = list(zip(vertices, vertices[1:])) - self.set_color_region(region_from_line_boundary(*pairs), color = BLUE) - - -class FillComponentsOfLargeSquare(LabelLargeSquare): - def construct(self): - LabelLargeSquare.construct(self) - points = np.array([ - 2*UP+2*LEFT, - UP+2*LEFT, - 2*DOWN+2*LEFT, - 2*DOWN+LEFT, - 2*DOWN+2*RIGHT, - DOWN+2*RIGHT, - 2*UP+2*RIGHT, - RIGHT+2*UP - ]) - for triplet in [[0, 1, 7], [2, 3, 1], [4, 5, 3], [6, 7, 5]]: - triplet.append(triplet[0]) - self.set_color_region(region_from_line_boundary(*[ - [points[i], points[j]] - for i, j in zip(triplet, triplet[1:]) - ]), color = DARK_BLUE) - vertices = points[[1, 3, 5, 7, 1]] - self.set_color_region(region_from_line_boundary(*[ - [p1, p2] - for p1, p2 in zip(vertices, vertices[1:]) - ]), color = YELLOW) - -class ShowRearrangementInBigSquare(DrawCSquareWithAllTraingles): - args_list = [] - def construct(self): - self.add(Square(side_length = 4, color = WHITE)) - DrawCSquareWithAllTraingles.construct(self) - self.remove(self.c_square) - self.triangles[1].shift(LEFT) - for i, j in [(0, 2), (3, 1)]: - self.triangles[i].place_hypotenuse_on( - *self.triangles[j].get_vertices()[[2, 1]] - ) - - -class ShowRearrangementInBigSquareWithRegions(ShowRearrangementInBigSquare): - def construct(self): - ShowRearrangementInBigSquare.construct(self) - self.set_color_region(region_from_polygon_vertices( - 2*(LEFT+UP), 2*LEFT+DOWN, RIGHT+DOWN, RIGHT+2*UP - ), color = B_COLOR) - self.set_color_region(region_from_polygon_vertices( - RIGHT+DOWN, RIGHT+2*DOWN, 2*RIGHT+2*DOWN, 2*RIGHT+DOWN - ), color = A_COLOR) diff --git a/from_3b1b/old/qa_round_two.py b/from_3b1b/old/qa_round_two.py deleted file mode 100644 index ff64ff17..00000000 --- a/from_3b1b/old/qa_round_two.py +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from manimlib.imports import * -from from_3b1b.old.efvgt import get_confetti_animations - - -class Test(Scene): - def construct(self): - pass - -class Announcements(PiCreatureScene): - def construct(self): - title = TextMobject("Announcements!") - title.scale(1.5) - title.to_edge(UP) - title.shift(LEFT) - underline = Line(LEFT, RIGHT) - underline.set_width(1.2*title.get_width()) - underline.next_to(title, DOWN) - - announcements = VGroup(*[ - TextMobject("$\\cdot$ %s"%s) - for s in [ - "Q\\&A Round 2", - "The case against Net Neutrality?", - ] - ]) - announcements.arrange( - DOWN, - buff = LARGE_BUFF, - aligned_edge = LEFT, - ) - announcements.next_to(underline, DOWN, LARGE_BUFF, aligned_edge = LEFT) - announcements.set_color_by_gradient(GREEN, YELLOW) - - self.play( - Write(title), - LaggedStartMap(FadeIn, announcements), - ShowCreation(underline), - self.pi_creature.change, "hooray", underline, - ) - self.play(self.pi_creature.change, "confused", announcements) - self.wait(2) - - -class PowersOfTwo(Scene): - def construct(self): - powers_of_2 = VGroup(*[ - TexMobject("2^{%d}"%n, "=", "{:,}".format(2**n)) - for n in range(20) - ]) - powers_of_2.to_edge(UP) - max_height = 6 - center = MED_LARGE_BUFF*DOWN - - mob = Dot(color = BLUE) - mob.move_to(center) - vects = it.cycle(5*[UP] + 5*[RIGHT]) - curr_po2 = powers_of_2[0] - - for i, vect, po2 in zip(it.count(), vects, powers_of_2[1:]): - if i == 10: - rect = SurroundingRectangle(mob, color = GREEN) - group = VGroup(mob, rect) - two_to_ten = group.copy() - group.generate_target() - group.target.set_height(0.2) - group.target[1].set_fill(BLUE, 1) - - self.play(ShowCreation(rect)) - self.play(MoveToTarget(group)) - self.remove(group) - mob = rect - self.add(mob) - m1, m2 = mob.copy(), mob.copy() - group = VGroup(m1, m2) - group.arrange( - vect, buff = SMALL_BUFF - ) - if group.get_height() > max_height: - group.set_height(max_height) - group.move_to(center) - pa = np.pi/3 - self.play( - Transform(curr_po2, po2), - ReplacementTransform(mob, m1, path_arc = pa), - ReplacementTransform(mob.copy(), m2, path_arc = pa), - ) - mob = VGroup(*it.chain(m1, m2)) - - #Show two_to_ten for comparrison - self.play( - mob.space_out_submobjects, 1.1, - mob.to_edge, RIGHT - ) - two_to_ten.to_edge(LEFT) - lines = VGroup(*[ - Line( - two_to_ten.get_corner(vect+RIGHT), - mob[0].get_corner(vect+LEFT), - ) - for vect in (UP, DOWN) - ]) - two_to_ten.save_state() - two_to_ten.replace(mob[0]) - self.play( - two_to_ten.restore, - *list(map(ShowCreation, lines)) - ) - - curr_po2_outline = curr_po2[-1].copy() - curr_po2_outline.set_fill(opacity = 0) - curr_po2_outline.set_stroke(width = 2) - curr_po2_outline.set_color_by_gradient( - YELLOW, RED, PINK, PURPLE, BLUE, GREEN - ) - - self.play( - LaggedStartMap( - FadeIn, curr_po2_outline, - rate_func = lambda t : wiggle(t, 8), - run_time = 2, - lag_ratio = 0.75, - ), - *get_confetti_animations(50) - ) - -class PiHoldingScreen(PiCreatureScene): - def construct(self): - morty = self.pi_creature - screen = ScreenRectangle() - screen.set_height(5.5) - screen.to_edge(UP, buff = LARGE_BUFF) - screen.to_edge(LEFT) - - words = VGroup( - TextMobject("Ben Eater"), - TextMobject("The Case Against Net Neutrality?"), - ) - words.next_to(screen, UP, SMALL_BUFF) - - self.play( - ShowCreation(screen), - morty.change, "raise_right_hand", screen - ) - self.wait(10) - self.play( - morty.change, "hooray", words[0], - Write(words[0]) - ) - self.wait(10) - self.play( - morty.change, "pondering", words[1], - Transform(words[0], words[1]) - ) - self.wait(10) - -class QuestionsLink(Scene): - def construct(self): - link = TextMobject("https://3b1b.co/questions") - link.set_width(FRAME_WIDTH) - link.to_edge(DOWN) - self.play(Write(link)) - self.wait() - -class Thumbnail(Scene): - def construct(self): - equation = TexMobject("2^{19} = " + "{:,}".format(2**19)) - equation.set_width(FRAME_X_RADIUS) - equation.to_edge(DOWN, buff = LARGE_BUFF) - - q_and_a = TextMobject("Q\\&A \\\\ Round 2") - q_and_a.set_color_by_gradient(BLUE, YELLOW) - q_and_a.set_width(FRAME_X_RADIUS) - q_and_a.to_edge(UP, buff = LARGE_BUFF) - - eater = ImageMobject("eater", height = 3) - eater.to_corner(UP+RIGHT, buff = 0) - - confetti_anims = get_confetti_animations(100) - for anim in confetti_anims: - anim.update(0.5) - confetti = VGroup(*[a.mobject for a in confetti_anims]) - - self.add(equation, q_and_a, eater) - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/quat3d.py b/from_3b1b/old/quat3d.py deleted file mode 100644 index 28f62a72..00000000 --- a/from_3b1b/old/quat3d.py +++ /dev/null @@ -1,1408 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.quaternions import * - -W_COLOR = YELLOW -I_COLOR = GREEN -J_COLOR = RED -K_COLOR = BLUE - - -class QuaternionLabel(VGroup): - CONFIG = { - "decimal_config": {} - } - - def __init__(self, quat, **kwargs): - VGroup.__init__(self, **kwargs) - dkwargs = dict(self.decimal_config) - decimals = VGroup() - decimals.add(DecimalNumber(quat[0], color=W_COLOR, **dkwargs)) - dkwargs["include_sign"] = True - decimals.add( - DecimalNumber(quat[1], color=I_COLOR, **dkwargs), - DecimalNumber(quat[2], color=J_COLOR, **dkwargs), - DecimalNumber(quat[3], color=K_COLOR, **dkwargs), - ) - self.add( - decimals[0], - decimals[1], TexMobject("i"), - decimals[2], TexMobject("j"), - decimals[3], TexMobject("k"), - ) - self.arrange(RIGHT, buff=SMALL_BUFF) - - self.decimals = decimals - - def set_value(self, quat): - for decimal, coord in zip(self.decimals, quat): - decimal.set_value(coord) - return self - - -class RandyPrism(Cube): - CONFIG = { - "height": 0.25, - "width": 1, - "depth": 1.2, - "fill_color": BLUE_D, - "fill_opacity": 0.9, - "stroke_color": WHITE, - "stroke_width": 1, - } - - def __init__(self, **kwargs): - Cube.__init__(self, **kwargs) - self.set_height(1) - randy = Randolph(mode="pondering") - randy.set_height(0.8) - randy.rotate(TAU / 4, RIGHT) - randy.shift(0.7 * DOWN) - randy.set_shade_in_3d(True, z_index_as_group=True) - self.randy = randy - self.add(randy) - self.set_height(self.height, stretch=True) - self.set_width(self.width, stretch=True) - self.set_depth(self.depth, stretch=True) - self.center() - - -class Gimbal(VGroup): - CONFIG = { - "inner_r": 1.2, - "outer_r": 2.6, - } - - def __init__(self, alpha=0, beta=0, gamma=0, inner_mob=None, **kwargs): - VGroup.__init__(self, **kwargs) - r1, r2, r3, r4, r5, r6, r7 = np.linspace( - self.inner_r, self.outer_r, 7 - ) - rings = VGroup( - self.get_ring(r5, r6), - self.get_ring(r3, r4), - self.get_ring(r1, r2), - ) - for i, p1, p2 in [(0, r6, r7), (1, r4, r5), (2, r2, r3)]: - annulus = rings[i] - lines = VGroup( - Line(p1 * UP, p2 * UP), - Line(p1 * DOWN, p2 * DOWN), - ) - lines.set_stroke(RED) - annulus.lines = lines - annulus.add(lines) - rings[1].lines.rotate(90 * DEGREES, about_point=ORIGIN) - rings.rotate(90 * DEGREES, RIGHT, about_point=ORIGIN) - rings.set_shade_in_3d(True) - self.rings = rings - self.add(rings) - - if inner_mob is not None: - corners = [ - inner_mob.get_corner(v1 + v2) - for v1 in [LEFT, RIGHT] - for v2 in [IN, OUT] - ] - lines = VGroup() - for corner in corners: - corner[1] = 0 - line = Line( - corner, self.inner_r * normalize(corner), - color=WHITE, - stroke_width=1 - ) - lines.add(line) - lines.set_shade_in_3d(True) - rings[2].add(lines, inner_mob) - - # Rotations - angles = [alpha, beta, gamma] - for i, angle in zip(it.count(), angles): - vect = rings[i].lines[0].get_vector() - rings[i:].rotate(angle=angle, axis=vect) - - def get_ring(self, in_r, out_r, angle=TAU / 4): - result = VGroup() - for start_angle in np.arange(0, TAU, angle): - start_angle += angle / 2 - sector = AnnularSector( - inner_radius=in_r, - outer_radius=out_r, - angle=angle, - start_angle=start_angle - ) - sector.set_fill(LIGHT_GREY, 0.8) - arcs = VGroup(*[ - Arc( - angle=angle, - start_angle=start_angle, - radius=r - ) - for r in [in_r, out_r] - ]) - arcs.set_stroke(BLACK, 1, opacity=0.5) - sector.add(arcs) - result.add(sector) - return result - - -# Scenes - -class ButFirst(TeacherStudentsScene): - def construct(self): - for student in self.students: - student.change("surprised") - - self.teacher_says("But first!") - self.change_all_student_modes("happy") - self.play(RemovePiCreatureBubble( - self.teacher, - target_mode="raise_right_hand" - )) - self.change_student_modes( - *["pondering"] * 3, - look_at_arg=self.screen, - ) - self.play(self.teacher.look_at, self.screen) - self.wait(4) - - -class Introduction(QuaternionHistory): - CONFIG = { - "names_and_quotes": [ - ( - "Oliver Heaviside", - """\\Huge ``the quaternion was not only not - required, but was a positive evil''""" - ), - ( - "Lord Kelvin", - """\\Huge ``Quaternions... though beautifully \\\\ ingenious, - have been an unmixed evil'' """ - ), - ] - } - - def construct(self): - title_word = TextMobject("Quaternions:") - title_equation = TexMobject( - "i^2 = j^2 = k^2 = ijk = -1", - tex_to_color_map={ - "i": I_COLOR, - "j": J_COLOR, - "k": K_COLOR, - } - ) - # label = QuaternionLabel([ - # float(str((TAU * 10**(3 * k)) % 10)[:4]) - # for k in range(4) - # ]) - title = VGroup(title_word, title_equation) - title.arrange(RIGHT) - title.to_edge(UP) - - images_group = self.get_dissenter_images_quotes_and_names() - images_group.to_edge(DOWN) - images, quotes, names = images_group - for pair in images_group: - pair[1].align_to(pair[0], UP) - - self.play( - FadeInFromDown(title_word), - Write(title_equation) - ) - self.wait() - for image, name, quote in zip(images, names, quotes): - self.play( - FadeInFrom(image, 3 * DOWN), - FadeInFromLarge(name), - LaggedStartMap( - FadeIn, VGroup(*it.chain(*quote)), - lag_ratio=0.3, - run_time=2 - ) - ) - self.wait(2) - self.play( - title.shift, 2 * UP, - *[ - ApplyMethod(mob.shift, FRAME_WIDTH * vect / 2) - for pair in images_group - for mob, vect in zip(pair, [LEFT, RIGHT]) - ], - ) - - -class WhoCares(TeacherStudentsScene): - def construct(self): - quotes = Group(*[ - ImageMobject( - "CoderQuaternionResponse_{}".format(d), - height=2 - ) - for d in range(4) - ]) - logos = Group(*[ - ImageMobject(name, height=0.5) - for name in [ - "TwitterLogo", - "HackerNewsLogo", - "RedditLogo", - "YouTubeLogo", - ] - ]) - for quote, logo in zip(quotes, logos): - logo.move_to(quote.get_corner(UR)) - quote.add(logo) - - quotes.arrange_in_grid() - quotes.set_height(4) - quotes.to_corner(UL) - - self.student_says( - "Um...who cares?", - target_mode="sassy", - added_anims=[self.teacher.change, "guilty"] - ) - self.change_student_modes("angry", "sassy", "sad") - self.wait(2) - self.play( - RemovePiCreatureBubble(self.students[1]), - self.teacher.change, "raise_right_hand" - ) - # self.play( - # LaggedStartMap( - # FadeInFromDown, quotes, - # run_time=3 - # ), - # self.get_student_changes(*3 * ["pondering"], look_at_arg=quotes) - # ) - # self.wait(2) - - # # Show HN - # hn_quote = quotes[1] - # hn_context = TextMobject("news.ycombinator.com/item?id=17933908") - # hn_context.scale(0.7) - # hn_context.to_corner(UL) - - # vr_headsets = VGroup() - # for pi in self.students: - # vr_headset = SVGMobject("VR_headset") - # vr_headset.set_fill(LIGHT_GREY, opacity=0.9) - # vr_headset.set_width(pi.eyes.get_width() + 0.3) - # vr_headset.move_to(pi.eyes) - # vr_headsets.add(vr_headset) - - # self.play( - # hn_quote.scale, 2, {"about_edge": DL}, - # FadeOutAndShift(quotes[0], 5 * UP), - # FadeOutAndShift(quotes[2], UR), - # FadeOutAndShift(quotes[3], RIGHT), - # FadeInFromDown(hn_context), - # ) - # hn_rect = Rectangle( - # height=0.1 * hn_quote.get_height(), - # width=0.6 * hn_quote.get_width(), - # color=RED - # ) - # hn_rect.move_to(hn_quote, UL) - # hn_rect.shift(0.225 * RIGHT + 0.75 * DOWN) - # self.play( - # ShowCreation(hn_rect), - # self.get_student_changes( - # "erm", "thinking", "confused", - # look_at_arg=hn_quote, - # ) - # ) - # self.add_foreground_mobjects(vr_headsets) - # self.play( - # LaggedStartMap( - # FadeInFrom, vr_headsets, - # lambda m: (m, UP), - # ), - # self.get_student_changes( - # *3 * ["sick"], - # look_at_arg=hn_quote, - # run_time=3 - # ) - # ) - # self.wait(3) - - # Show Twitter - t_quote = quotes[0] - # t_quote.next_to(FRAME_WIDTH * LEFT / 2 + FRAME_WIDTH * UP / 2, UR) - # t_quote.set_opacity(0) - # self.play( - # FadeOutAndShift(hn_quote, 4 * LEFT), - # FadeOutAndShift(hn_rect, 4 * LEFT), - # FadeOutAndShift(hn_context, UP), - # FadeOut(vr_headsets), - # t_quote.set_opacity, 1, - # t_quote.scale, 2, - # t_quote.to_corner, UL, - # ) - # self.remove_foreground_mobjects(vr_headsets) - t_quote.fade(1) - t_quote.to_corner(UL) - self.play( - self.get_student_changes(*3 * ["pondering"], look_at_arg=quotes), - t_quote.set_opacity, 1, - t_quote.scale, 2, - t_quote.to_corner, UL, - ) - self.wait(2) - self.change_student_modes( - "pondering", "happy", "tease", - look_at_arg=t_quote - ) - self.wait(2) - self.play(FadeOut(t_quote)) - self.wait(5) - - -class ShowSeveralQuaternionRotations(SpecialThreeDScene): - CONFIG = { - "quaternions": [ - [0, 1, 0, 0], - [1, 0, 0, 0], - [1, 0, 1, 0], - [1, 1, 1, -1], - [0, -1, 2, 1], - [1, 0, 0, -1], - [1, -1, 0, 0], - [1, -1, 1, 0], - [1, -1, 1, -1], - [1, 0, 0, 0], - ], - "start_phi": 70 * DEGREES, - "start_theta": -140 * DEGREES, - "ambient_rotation_rate": 0.01, - } - - def construct(self): - self.add_q_tracker() - self.setup_labels() - self.setup_camera_position() - self.add_prism() - self.add_axes() - self.apply_quaternions() - - def add_q_tracker(self): - self.q_tracker = QuaternionTracker() - self.q_tracker.add_updater(lambda m: m.normalize()) - self.add(self.q_tracker) - - def setup_labels(self): - left_q_label = QuaternionLabel([1, 0, 0, 0]) - right_q_label = QuaternionLabel([1, 0, 0, 0]) - for label in left_q_label, right_q_label: - lp, rp = TexMobject("()") - lp.next_to(label, LEFT, SMALL_BUFF) - rp.next_to(label, RIGHT, SMALL_BUFF) - label.add(lp, rp) - point_label = TexMobject( - *"(xi+yj+zk)", - tex_to_color_map={ - "i": I_COLOR, - "j": J_COLOR, - "k": K_COLOR, - } - ) - left_q_label.next_to(point_label, LEFT) - right_q_label.next_to(point_label, RIGHT) - group = VGroup(left_q_label, point_label, right_q_label) - group.arrange(RIGHT) - group.set_width(FRAME_WIDTH - 1) - group.to_edge(UP) - self.add_fixed_in_frame_mobjects(BackgroundRectangle(group)) - - for label, text in zip(group, ["$q$", "Some 3d point", "$q^{-1}$"]): - brace = Brace(label, DOWN) - text_mob = TextMobject(text) - if text_mob.get_width() > brace.get_width(): - text_mob.match_width(brace) - text_mob.next_to(brace, DOWN, buff=SMALL_BUFF) - text_mob.add_background_rectangle() - label.add(brace, text_mob) - - self.add_fixed_in_frame_mobjects(*group) - - left_q_label.add_updater( - lambda m: m.set_value(self.q_tracker.get_value()) - ) - left_q_label.add_updater(lambda m: self.add_fixed_in_frame_mobjects(m)) - right_q_label.add_updater( - lambda m: m.set_value(quaternion_conjugate( - self.q_tracker.get_value() - )) - ) - right_q_label.add_updater(lambda m: self.add_fixed_in_frame_mobjects(m)) - - def setup_camera_position(self): - self.set_camera_orientation( - phi=self.start_phi, - theta=self.start_theta, - ) - self.begin_ambient_camera_rotation(self.ambient_rotation_rate) - - def add_prism(self): - prism = self.prism = self.get_prism() - prism.add_updater( - lambda p: p.become(self.get_prism( - self.q_tracker.get_value() - )) - ) - self.add(prism) - - def add_axes(self): - axes = self.axes = always_redraw(self.get_axes) - self.add(axes) - - def apply_quaternions(self): - for quat in self.quaternions: - self.change_q(quat) - self.wait(2) - - # - def get_unrotated_prism(self): - return RandyPrism().scale(2) - - def get_prism(self, quaternion=[1, 0, 0, 0]): - prism = self.get_unrotated_prism() - angle, axis = angle_axis_from_quaternion(quaternion) - prism.rotate(angle=angle, axis=axis, about_point=ORIGIN) - return prism - - def get_axes(self): - prism = self.prism - centers = [sm.get_center() for sm in prism[:6]] - axes = VGroup() - for i in range(3): - for u in [-1, 1]: - vect = np.zeros(3) - vect[i] = u - dots = [np.dot(normalize(c), vect) for c in centers] - max_i = np.argmax(dots) - ec = centers[max_i] - prism.get_edge_center(vect) - p1 = np.zeros(3) - p1[i] = ec[i] - p1 *= dots[max_i] - p2 = 10 * vect - axes.add(Line(p1, p2)) - axes.set_stroke(LIGHT_GREY, 1) - axes.set_shade_in_3d(True) - return axes - - def change_q(self, value, run_time=3, added_anims=None, **kwargs): - if added_anims is None: - added_anims = [] - self.play( - self.q_tracker.set_value, value, - *added_anims, - run_time=run_time, - **kwargs - ) - - -class PauseAndPlayOverlay(Scene): - def construct(self): - pause = TexMobject("=").rotate(TAU / 4) - pause.stretch(2, 0) - pause.scale(1.5) - arrow = Vector(RIGHT, color=WHITE) - interact = TextMobject("Interact...") - group = VGroup(pause, arrow, interact) - group.arrange(RIGHT) - group.scale(2) - - not_yet = TextMobject("...well, not yet") - not_yet.scale(2) - not_yet.next_to(group, DOWN, MED_LARGE_BUFF) - - self.play(Write(pause)) - self.play( - GrowFromPoint( - interact, arrow.get_left(), - rate_func=squish_rate_func(smooth, 0.3, 1) - ), - VFadeIn(interact), - GrowArrow(arrow), - ) - self.wait(2) - self.play(Write(not_yet)) - self.wait() - - -class RotationMatrix(ShowSeveralQuaternionRotations): - CONFIG = { - "start_phi": 60 * DEGREES, - "start_theta": -60 * DEGREES, - } - - def construct(self): - self.add_q_tracker() - self.setup_camera_position() - self.add_prism() - self.add_basis_vector_labels() - self.add_axes() - - title = TextMobject("Rotation matrix") - title.scale(1.5) - title.to_corner(UL) - self.add_fixed_in_frame_mobjects(title) - - angle = 75 * DEGREES - axis = [0.3, 1, 0.3] - matrix = rotation_matrix(angle=angle, axis=axis) - matrix_mob = DecimalMatrix(matrix, h_buff=1.6) - matrix_mob.next_to(title, DOWN) - matrix_mob.to_edge(LEFT) - title.next_to(matrix_mob, UP) - self.add_fixed_in_frame_mobjects(matrix_mob) - - colors = [I_COLOR, J_COLOR, K_COLOR] - matrix_mob.set_column_colors(*colors) - - columns = matrix_mob.get_columns() - column_rects = VGroup(*[ - SurroundingRectangle(c).match_color(c[0]) - for c in columns - ]) - labels = VGroup(*[ - TextMobject( - "Where", tex, "goes", - tex_to_color_map={tex: rect.get_color()} - ).next_to(rect, DOWN) - for letter, rect in zip(["\\i", "\\j", "k"], column_rects) - for tex in ["$\\hat{\\textbf{%s}}$" % (letter)] - ]) - labels.space_out_submobjects(0.8) - - quaternion = quaternion_from_angle_axis(angle, axis) - - self.play(Write(matrix_mob)) - self.change_q(quaternion) - self.wait() - last_label = VectorizedPoint(matrix_mob.get_bottom()) - last_rect = VMobject() - for label, rect in zip(labels, column_rects): - self.add_fixed_in_frame_mobjects(rect, label) - self.play( - FadeIn(label), - FadeOut(last_label), - ShowCreation(rect), - FadeOut(last_rect) - ) - self.wait() - last_label = label - last_rect = rect - self.play(FadeOut(last_label), FadeOut(last_rect)) - self.wait(5) - - def get_unrotated_prism(self): - prism = RandyPrism() - prism.scale(1.5) - arrows = VGroup() - for i, color in enumerate([I_COLOR, J_COLOR, K_COLOR]): - vect = np.zeros(3) - vect[i] = 1 - arrow = Arrow( - prism.get_edge_center(vect), 2 * vect, - preserve_tip_size_when_scaling=False, - color=color, - buff=0, - ) - arrows.add(arrow) - arrows.set_shade_in_3d(True) - prism.arrows = arrows - prism.add(arrows) - return prism - - def add_basis_vector_labels(self): - labels = VGroup( - TexMobject("\\hat{\\textbf{\\i}}"), - TexMobject("\\hat{\\textbf{\\j}}"), - TexMobject("\\hat{\\textbf{k}}"), - ) - - def generate_updater(arrow): - return lambda m: m.move_to( - arrow.get_end() + 0.2 * normalize(arrow.get_vector()), - ) - - for arrow, label in zip(self.prism.arrows, labels): - label.match_color(arrow) - label.add_updater(generate_updater(arrow)) - self.add_fixed_orientation_mobjects(label) - - -class EulerAnglesAndGimbal(ShowSeveralQuaternionRotations): - def construct(self): - self.setup_position() - self.setup_angle_trackers() - self.setup_gimbal() - self.add_axes() - self.add_title() - self.show_rotations() - - def setup_position(self): - self.set_camera_orientation( - theta=-140 * DEGREES, - phi=70 * DEGREES, - ) - self.begin_ambient_camera_rotation(rate=0.015) - - def setup_angle_trackers(self): - self.alpha_tracker = ValueTracker(0) - self.beta_tracker = ValueTracker(0) - self.gamma_tracker = ValueTracker(0) - - def setup_gimbal(self): - gimbal = always_redraw(self.get_gimbal) - self.gimbal = gimbal - self.add(gimbal) - - def add_title(self): - title = TextMobject("Euler angles") - title.scale(1.5) - title.to_corner(UL) - angle_labels = VGroup( - TexMobject("\\alpha").set_color(YELLOW), - TexMobject("\\beta").set_color(GREEN), - TexMobject("\\gamma").set_color(PINK), - ) - angle_labels.scale(2) - angle_labels.arrange(RIGHT, buff=MED_LARGE_BUFF) - angle_labels.next_to(title, DOWN, aligned_edge=LEFT) - self.angle_labels = angle_labels - - gl_label = VGroup( - Arrow(LEFT, RIGHT, color=WHITE), - TextMobject("Gimbal lock").scale(1.5), - ) - gl_label.arrange(RIGHT) - gl_label.next_to(title, RIGHT) - self.gimbal_lock_label = gl_label - - VGroup(title, angle_labels, gl_label).center().to_edge(UP) - - self.add_fixed_in_frame_mobjects(title, angle_labels, gl_label) - self.remove(angle_labels) - self.remove(gl_label) - - def show_rotations(self): - gimbal = self.gimbal - alpha_tracker = self.alpha_tracker - beta_tracker = self.beta_tracker - gamma_tracker = self.gamma_tracker - - angles = [-60 * DEGREES, 50 * DEGREES, 45 * DEGREES] - trackers = [alpha_tracker, beta_tracker, gamma_tracker] - in_rs = [0.6, 0.5, 0.6] - for i in range(3): - tracker = trackers[i] - angle = angles[i] - in_r = in_rs[i] - ring = gimbal.rings[i] - - vect = ring.lines[0].get_vector() - line = self.get_dotted_line(vect, in_r=in_r) - angle_label = self.angle_labels[i] - line.match_color(angle_label) - self.play( - ShowCreation(line), - FadeInFromDown(angle_label) - ) - self.play( - tracker.set_value, angle, - run_time=3 - ) - self.play(FadeOut(line)) - self.wait() - self.wait(3) - self.play(Write(self.gimbal_lock_label)) - self.play( - alpha_tracker.set_value, 0, - beta_tracker.set_value, 0, - run_time=2 - ) - self.play( - alpha_tracker.set_value, 90 * DEGREES, - gamma_tracker.set_value, -90 * DEGREES, - run_time=3 - ) - self.play( - FadeOut(self.gimbal_lock_label), - *[ApplyMethod(t.set_value, 0) for t in trackers], - run_time=3 - ) - self.play( - alpha_tracker.set_value, 30 * DEGREES, - beta_tracker.set_value, 120 * DEGREES, - gamma_tracker.set_value, -50 * DEGREES, - run_time=3 - ) - self.play( - alpha_tracker.set_value, 120 * DEGREES, - beta_tracker.set_value, -30 * DEGREES, - gamma_tracker.set_value, 90 * DEGREES, - run_time=4 - ) - self.play( - beta_tracker.set_value, 150 * DEGREES, - run_time=2 - ) - self.play( - alpha_tracker.set_value, 0, - beta_tracker.set_value, 0, - gamma_tracker.set_value, 0, - run_time=4 - ) - self.wait() - - # - def get_gimbal(self): - self.prism = RandyPrism() - return Gimbal( - alpha=self.alpha_tracker.get_value(), - beta=self.beta_tracker.get_value(), - gamma=self.gamma_tracker.get_value(), - inner_mob=self.prism - ) - - def get_dotted_line(self, vect, in_r=0, out_r=10): - line = VGroup(*it.chain(*[ - DashedLine( - in_r * normalize(u * vect), - out_r * normalize(u * vect), - ) - for u in [-1, 1] - ])) - line.sort(get_norm) - line.set_shade_in_3d(True) - line.set_stroke(YELLOW, 5) - line.center() - return line - - -class InterpolationFail(Scene): - def construct(self): - words = TextMobject( - "Sometimes interpolating 3d\\\\" - "orientations is tricky..." - ) - words.to_edge(UP) - self.add(words) - - -class QuaternionInterpolation(ShowSeveralQuaternionRotations): - def construct(self): - self.add_q_tracker() - self.setup_camera_position() - self.add_prism() - self.add_axes() - - self.change_q([1, 1, 1, 0], run_time=0) - self.wait(2) - self.change_q([1, 0.2, 0.6, -0.5], run_time=4) - self.wait(2) - self.change_q([1, -0.6, 0.2, -1], run_time=4) - self.wait(2) - - -class QuaternionInterpolationScematic(Scene): - def construct(self): - title = TextMobject("Slice of a hypersphere") - title.to_edge(UP, buff=MED_SMALL_BUFF) - self.add(title) - - radius = 3 - circle = Circle(radius=radius) - circle.set_stroke(LIGHT_GREY, 1) - qs = [circle.point_from_proportion(p) for p in (0.55, 0.35, 0.15)] - colors = [YELLOW, PINK, RED] - q_dots = [Dot(q, color=c) for q, c in zip(qs, colors)] - q_labels = [ - TexMobject("q_{}".format(i + 1)).next_to( - dot, normalize(dot.get_center()), SMALL_BUFF - ).match_color(dot) - for i, dot in enumerate(q_dots) - ] - - q1, q2, q3 = qs - lines = [ - DashedLine(q1, q2) - for q1, q2 in zip(qs, qs[1:]) - ] - for color, line in zip([GREEN, BLUE], lines): - line.set_stroke(color, 3) - line.proj = line.copy().apply_function( - lambda p: radius * normalize(p) - ) - dot = Dot(qs[0], color=WHITE) - - self.add(circle) - self.add(dot) - self.add(*q_dots + q_labels) - - self.wait(2) - for line, q in zip(lines, qs[1:]): - self.play( - ShowCreation(line), - ShowCreation(line.proj), - dot.move_to, q, - run_time=4 - ) - self.wait(2) - - -class RememberComplexNumbers(TeacherStudentsScene): - def construct(self): - complex_number = TexMobject( - "\\cos(\\theta) + \\sin(\\theta)i", - tex_to_color_map={ - "\\cos(\\theta)": GREEN, - "\\sin(\\theta)": RED - } - ) - complex_number.scale(1.2) - complex_number.next_to(self.students, UP, MED_LARGE_BUFF) - - self.teacher_says( - "Remember how \\\\ complex numbers \\\\ compute rotations" - ) - self.change_all_student_modes("pondering") - self.wait() - self.play( - FadeInFromDown(complex_number), - self.get_student_changes( - "thinking", "confused", "happy", - look_at_arg=complex_number.get_center() + UP - ), - run_time=2 - ) - self.change_student_modes( - ) - self.wait(5) - - -class ComplexNumberRotation(Scene): - CONFIG = { - "angle": 30 * DEGREES, - } - - def construct(self): - self.add_plane() - self.add_number() - self.show_complex_unit() - self.show_product() - - def add_plane(self): - plane = self.plane = ComplexPlane() - self.play(Write(plane)) - - def add_number(self): - plane = self.plane - origin = plane.coords_to_point(0, 0) - angle = self.angle - - point = plane.coords_to_point(4, 1) - dot = Dot(point, color=YELLOW) - label = TexMobject("(4, 1)") - label.next_to(dot, UR, buff=0) - line = DashedLine(origin, point) - rotated_line = line.copy().rotate(angle, about_point=origin) - rotated_line.set_color(GREY) - rotated_dot = dot.copy().rotate(angle, about_point=origin) - rotated_dot.set_color(YELLOW_E) - mystery_label = TexMobject("(?, ?)") - mystery_label.next_to(rotated_dot, UR, buff=0) - - arc = Arc( - start_angle=line.get_angle(), - angle=angle, - radius=0.75 - ) - angle_tex = str(int(np.round(angle / DEGREES))) + "^\\circ" - angle_label = TexMobject(angle_tex) - angle_label.next_to( - arc.point_from_proportion(0.3), - UR, buff=SMALL_BUFF - ) - - self.play( - FadeInFromDown(label), - GrowFromCenter(dot), - ShowCreation(line) - ) - self.wait() - self.play( - ShowCreation(arc), - FadeInFromDown(angle_label), - TransformFromCopy(line, rotated_line), - TransformFromCopy(dot, rotated_dot), - path_arc=angle, - ) - self.play(Write(mystery_label)) - self.wait() - - self.rotation_mobs = VGroup( - arc, angle_label, - rotated_line, rotated_dot, - mystery_label - ) - self.angle_tex = angle_tex - self.number_label = label - - def show_complex_unit(self): - plane = self.plane - angle = self.angle - angle_tex = self.angle_tex - complex_coordinate_labels = plane.get_coordinate_labels() - unit_circle = Circle(radius=1, color=YELLOW) - - origin = plane.number_to_point(0) - z_point = plane.coords_to_point(np.cos(angle), np.sin(angle)) - one_point = plane.number_to_point(1) - z_dot = Dot(z_point, color=WHITE) - one_dot = Dot(one_point, color=WHITE) - one_dot.fade(1) - z_line = Line(origin, z_point) - one_line = Line(origin, one_point) - VGroup(z_dot, one_dot, z_line, one_line).set_color(BLUE) - - cos_tex = "\\cos(" + angle_tex + ")" - sin_tex = "\\sin(" + angle_tex + ")" - label = TexMobject( - cos_tex, "+", sin_tex, "i", - tex_to_color_map={cos_tex: GREEN, sin_tex: RED} - ) - label.add_background_rectangle() - label.scale(0.8) - label.next_to(plane.coords_to_point(0, 1), UR, SMALL_BUFF) - arrow = Arrow(label.get_bottom(), z_point) - - number_label = self.number_label - new_number_label = TexMobject( - "4 + 1i", tex_to_color_map={"4": GREEN, "1": RED} - ) - new_number_label.move_to(number_label, LEFT) - new_number_label.add_background_rectangle() - - self.play( - Write(complex_coordinate_labels, run_time=2), - FadeOut(self.rotation_mobs) - ) - self.play(ShowCreation(unit_circle)) - self.play( - TransformFromCopy(one_dot, z_dot), - TransformFromCopy(one_line, z_line), - FadeInFromDown(label), - GrowArrow(arrow), - ) - self.wait() - self.play(FadeOutAndShiftDown(number_label)) - self.play(FadeInFromDown(new_number_label)) - self.wait() - - self.left_z_label = label - self.right_z_label = new_number_label - self.cos_tex = cos_tex - self.sin_tex = sin_tex - self.unit_z_group = VGroup( - unit_circle, z_line, z_dot, label, arrow - ) - - def show_product(self): - plane = self.plane - cos_tex = self.cos_tex - sin_tex = self.sin_tex - angle = self.angle - - line = Line( - FRAME_WIDTH * LEFT / 2 + FRAME_HEIGHT * UP / 2, - plane.coords_to_point(-0.5, 1.5) - ) - rect = BackgroundRectangle(line, buff=0) - - left_z = self.left_z_label - right_z = self.right_z_label - new_left_z = left_z.copy() - new_right_z = right_z.copy() - - lp1, rp1, lp2, rp2 = parens = TexMobject("()()") - top_line = VGroup( - lp1, new_left_z, rp1, - lp2, new_right_z, rp2, - ) - top_line.arrange(RIGHT, buff=SMALL_BUFF) - top_line.set_width(rect.get_width() - 1) - top_line.next_to(rect.get_top(), DOWN, MED_SMALL_BUFF) - - mid_line = TexMobject( - "\\big(", "4", cos_tex, "-", "1", sin_tex, "\\big)", "+", - "\\big(", "1", cos_tex, "+", "4", sin_tex, "\\big)", "i", - tex_to_color_map={ - cos_tex: GREEN, - sin_tex: RED, - "4": GREEN, - "1": RED, - } - ) - mid_line.set_width(rect.get_width() - 0.5) - mid_line.next_to(top_line, DOWN, MED_LARGE_BUFF) - - new_z = np.exp(angle * complex(0, 1)) * complex(4, 1) - low_line = TexMobject( - "\\approx", - str(np.round(new_z.real, 2)), "+", - str(np.round(new_z.imag, 2)), "i", - ) - low_line.next_to(mid_line, DOWN, MED_LARGE_BUFF) - - self.play(FadeIn(rect)) - self.play( - TransformFromCopy(left_z, new_left_z), - TransformFromCopy(right_z, new_right_z), - Write(parens) - ) - self.wait() - self.play(FadeInFrom(mid_line, UP)) - self.wait() - self.play(FadeInFrom(low_line, UP)) - self.wait(2) - self.play(FadeOut(self.unit_z_group)) - self.rotation_mobs.save_state() - self.rotation_mobs.rotate(-angle, about_point=ORIGIN) - self.rotation_mobs.fade(1) - self.play(self.rotation_mobs.restore) - self.wait() - - mystery_label = self.rotation_mobs[-1] - result = low_line[1:].copy() - result.add_background_rectangle() - self.play( - result.move_to, mystery_label, LEFT, - FadeOutAndShiftDown(mystery_label), - ) - self.wait() - - -class ISquaredRule(Scene): - def construct(self): - tex = TextMobject("Use", "$i^2 = -1$") - tex[1].set_color(RED) - tex.scale(2) - self.add(tex) - self.play(Write(tex)) - self.wait() - - -class RuleForQuaternionRotations(EulerAnglesAndGimbal): - CONFIG = { - "start_phi": 70 * DEGREES, - "start_theta": -120 * DEGREES, - "ambient_rotation_rate": 0.015, - } - - def construct(self): - self.add_q_tracker() - self.setup_camera_position() - self.add_prism() - self.add_axes() - - self.show_axis() - self.construct_quaternion() - self.add_point_with_coordinates() - self.add_inverse() - - def get_axes(self): - axes = EulerAnglesAndGimbal.get_axes(self) - for axis in axes: - vect = normalize(axis.get_vector()) - perp = rotate_vector(vect, TAU / 3, axis=[1, 1, 1]) - for i in range(1, 4): - tick = Line(-perp, perp).scale(0.1) - tick.match_style(axis) - tick.move_to(2 * i * vect) - axis.add(tick) - axes.set_shade_in_3d(True) - return axes - - def show_axis(self): - vect = normalize([1, -1, -0.5]) - line = self.get_dotted_line(vect, 0, 4) - quat = np.append(0, vect) - - axis_label = TextMobject("Axis of rotation") - axis_label.next_to(line.get_corner(DR), DOWN, MED_LARGE_BUFF) - axis_label.match_color(line) - - self.add_fixed_orientation_mobjects(axis_label) - self.play( - ShowCreation(line), - Write(axis_label) - ) - self.change_q(quat, run_time=2) - self.change_q([1, 0, 0, 0], run_time=2) - - # Unit vector - vect_mob = Vector(2 * vect) - vect_mob.pointwise_become_partial(vect_mob, 0, 0.95) - pieces = VGroup(*vect_mob.get_pieces(25)) - pieces.set_stroke(vect_mob.get_color(), 2) - vect_mob.set_stroke(width=0) - vect_mob.add_to_back(pieces) - vect_mob.set_shade_in_3d(True) - - vect_label = TexMobject( - "{:.2f}".format(vect[0]), "i", - "{:+.2f}".format(vect[1]), "j", - "{:+.2f}".format(vect[2]), "k", - ) - magnitude_label = TexMobject( - "x", "^2 + ", - "y", "^2 + ", - "z", "^2 = 1", - ) - for label in vect_label, magnitude_label: - decimals = label[::2] - colors = [I_COLOR, J_COLOR, K_COLOR] - for d1, color in zip(decimals, colors): - d1.set_color(color) - label.rotate(TAU / 4, RIGHT).scale(0.7) - label.next_to(vect_mob.get_end(), RIGHT, SMALL_BUFF) - - magnitude_label.next_to(vect_label, IN) - - self.play( - FadeOut(line), - FadeOutAndShiftDown(axis_label), - ShowCreation(vect_mob) - ) - # self.add_fixed_orientation_mobjects(vect_label) - self.play(FadeInFromDown(vect_label)) - self.wait(3) - self.play(TransformFromCopy(vect_label, magnitude_label)) - self.wait(3) - - self.vect = vect - self.vect_mob = vect_mob - self.vect_label = vect_label - self.magnitude_label = magnitude_label - - def construct_quaternion(self): - full_angle_q = self.get_quaternion_label("40^\\circ") - half_angle_q = self.get_quaternion_label("40^\\circ / 2") - for label in full_angle_q, half_angle_q: - label.to_corner(UL) - brace = Brace(half_angle_q, DOWN) - q_label = brace.get_tex("q") - full_angle_q.align_data(half_angle_q) - rect = SurroundingRectangle(full_angle_q[5]) - - for mob in full_angle_q, half_angle_q, brace, q_label, rect: - self.add_fixed_in_frame_mobjects(mob) - self.remove(mob) - self.play(FadeInFromDown(full_angle_q[1])) - self.wait() - self.play( - FadeInFromDown(full_angle_q[0]), - LaggedStartMap(FadeInFromDown, full_angle_q[2:]), - ) - self.play( - GrowFromCenter(brace), - Write(q_label) - ) - self.wait(2) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.wait(2) - self.play(ReplacementTransform(full_angle_q, half_angle_q)) - self.wait(6) - # TODO - - def add_point_with_coordinates(self): - prism = self.prism - point = prism.get_corner(UR + OUT) - template_sphere = Sphere(radius=0.1) - template_sphere.set_stroke(width=0) - template_sphere.set_color(PINK) - ghost_sphere = template_sphere.copy() - ghost_sphere.fade(0.8) - for face in template_sphere: - c = face.get_center() - if c[0] < 0 and c[2] < 0: - template_sphere.remove(face) - template_sphere.move_to(point) - ghost_sphere.move_to(point) - - def get_sphere(): - result = template_sphere.copy() - quat = self.q_tracker.get_value() - angle, axis = angle_axis_from_quaternion(quat) - result.rotate(angle=angle, axis=axis, about_point=ORIGIN) - return result - - sphere = always_redraw(get_sphere) - - point_label = TexMobject( - "p", "=", - "{:.2f}".format(point[0]), "i", "+" - "{:.2f}".format(point[1]), "j", "+" - "{:.2f}".format(point[2]), "k", - ) - colors = [PINK, I_COLOR, J_COLOR, K_COLOR] - for part, color in zip(point_label[::2], colors): - part.set_color(color) - point_label.scale(0.7) - point_label.rotate(TAU / 4, RIGHT) - point_label.next_to(point, RIGHT) - - self.stop_ambient_camera_rotation() - self.begin_ambient_camera_rotation(-0.01) - self.play(FadeInFromLarge(sphere)) - self.play(Write(point_label)) - self.wait(3) - - # Rotate - quat = quaternion_from_angle_axis(40 * DEGREES, self.vect) - r = get_norm(point) - curved_arrow = Arrow( - r * RIGHT, rotate_vector(r * RIGHT, 30 * DEGREES, OUT), - buff=0, - path_arc=60 * DEGREES, - color=LIGHT_GREY, - ) - curved_arrow.pointwise_become_partial(curved_arrow, 0, 0.9) - curved_arrow.rotate(150 * DEGREES, about_point=ORIGIN) - curved_arrow.apply_matrix(z_to_vector(self.vect)) - self.add(ghost_sphere, sphere) - self.change_q( - quat, - added_anims=[ShowCreation(curved_arrow)], - run_time=3 - ) - - mystery_label = TexMobject("(?, ?, ?)") - mystery_label.add_background_rectangle() - arrow = Vector(0.5 * DR, color=WHITE) - arrow.next_to(mystery_label, DR, buff=0) - # mystery_label.add(arrow) - mystery_label.rotate(TAU / 4, RIGHT) - mystery_label.next_to(sphere, OUT + LEFT, buff=0) - self.play(FadeInFromDown(mystery_label)) - self.wait(5) - - def add_inverse(self): - label = TexMobject( - "p", "\\rightarrow", - "q", "\\cdot", "p", "\\cdot", "q^{-1}", - tex_to_color_map={"p": PINK} - ) - label.to_corner(UR) - label.shift(2 * LEFT) - self.add_fixed_in_frame_mobjects(label) - - self.play(FadeInFromDown(label)) - self.wait(3) - self.change_q( - [1, 0, 0, 0], - rate_func=there_and_back, - run_time=5 - ) - self.wait(3) - - # - def get_quaternion_label(self, angle_tex): - vect_label = self.vect_label.copy() - vect_label.rotate(TAU / 4, LEFT) - vect_label.replace(TexMobject(vect_label.get_tex_string())) - vect_label.add_background_rectangle() - result = VGroup( - TexMobject("\\big("), - TexMobject("\\text{cos}(", angle_tex, ")"), - TexMobject("+"), - TexMobject("\\text{sin}(", angle_tex, ")"), - TexMobject("("), - vect_label, - TexMobject(")"), - TexMobject("\\big)"), - ) - for i in 1, 3: - result[i][1].set_color(YELLOW) - result.arrange(RIGHT, buff=SMALL_BUFF) - result.scale(0.7) - return result - - -class ExpandOutFullProduct(TeacherStudentsScene): - def construct(self): - product = TexMobject( - """ - (w_0 + x_0 i + y_0 j + z_0 k) - (x_1 i + y_1 j + z_1 k) - (w_0 - x_0 i - y_0 j - z_0 k) - """, - tex_to_color_map={ - "w_0": W_COLOR, "(": WHITE, ")": WHITE, - "x_0": I_COLOR, "y_0": J_COLOR, "z_0": K_COLOR, - "x_1": I_COLOR, "y_1": J_COLOR, "z_1": K_COLOR, - } - ) - product.set_width(FRAME_WIDTH - 1) - product.to_edge(UP) - - n = 10 - q_brace = Brace(product[:n], DOWN) - p_brace = Brace(product[n:-n], DOWN) - q_inv_brace = Brace(product[-n:], DOWN) - braces = VGroup(q_brace, p_brace, q_inv_brace) - for brace, tex in zip(braces, ["q", "p", "q^{-1}"]): - brace.add(brace.get_tex(tex)) - - words = TextMobject("= Rotation of $p$") - words.next_to(braces, DOWN) - - self.play( - self.teacher.change, "raise_right_hand", - FadeInFromDown(product) - ) - self.play( - LaggedStartMap(GrowFromCenter, braces), - self.get_student_changes( - "confused", "horrified", "confused" - ) - ) - self.wait(2) - self.play(Write(words)) - self.change_student_modes( - "pondering", "confused", "erm", - look_at_arg=words - ) - self.wait(5) - - -class Link(Scene): - def construct(self): - word = TextMobject("eater.net/quaternions") - word.add_background_rectangle() - rect = SurroundingRectangle(word) - rect.set_color(BLUE) - arrow = Vector(UR, color=GREEN) - arrow.next_to(rect, UP) - arrow.align_to(rect, RIGHT) - short_arrow = arrow.copy().scale(0.8, about_edge=DL) - - self.add(word) - self.play( - ShowCreation(rect), - GrowArrow(arrow), - ) - for x in range(10): - self.play(Transform( - arrow, short_arrow, - rate_func=there_and_back, - run_time=2 - )) diff --git a/from_3b1b/old/quaternions.py b/from_3b1b/old/quaternions.py deleted file mode 100644 index 6d2a12db..00000000 --- a/from_3b1b/old/quaternions.py +++ /dev/null @@ -1,6427 +0,0 @@ -from manimlib.imports import * - - -def q_mult(q1, q2): - w1, x1, y1, z1 = q1 - w2, x2, y2, z2 = q2 - w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2 - x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 - y = w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2 - z = w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2 - return np.array([w, x, y, z]) - - -def stereo_project_point(point, axis=0, r=1, max_norm=10000): - point = fdiv(point * r, point[axis] + r) - point[axis] = 0 - norm = get_norm(point) - if norm > max_norm: - point *= max_norm / norm - return point - - -def stereo_project(mobject, axis=0, r=1, outer_r=10, **kwargs): - epsilon = 1 - for submob in mobject.family_members_with_points(): - points = submob.points - n = len(points) - for i in range(n): - if points[i, axis] == -r: - js = it.chain( - range(i + 1, n), - range(i - 1, -1, -1) - ) - for j in js: - if points[j, axis] == -r: - continue - else: - vect = points[j] - points[i] - points[i] += epsilon * vect - break - submob.apply_function( - lambda p: stereo_project_point(p, axis, r, **kwargs) - ) - - # If all points are outside a certain range, this - # shouldn't be displayed - norms = np.apply_along_axis(get_norm, 1, submob.points) - if np.all(norms > outer_r): - # TODO, instead set opacity? - # submob.points[:, :] = 0 - submob.set_fill(opacity=0) - submob.set_stroke(opacity=0) - - return mobject - - -class Linus(VGroup): - CONFIG = { - "body_config": { - "stroke_width": 15, - "stroke_color": LIGHT_GREY, - "sheen": 0.4, - }, - "height": 2, - } - - def __init__(self, **kwargs): - VGroup.__init__(self, **kwargs) - self.body = self.get_body_line() - self.eyes = Eyes(self.body) - - self.add(self.body, self.eyes) - self.set_height(self.height) - self.center() - - def change_mode(self, mode, thing_to_look_at=None): - self.eyes.change_mode(mode, thing_to_look_at) - if mode == "sad": - self.become_squiggle() - elif mode == "confused": - self.become_squiggle(factor=-0.1) - elif mode == "pleading": - self.become_squiggle(factor=0.3) - else: - self.become_line() - return self - - def change(self, *args, **kwargs): - self.change_mode(*args, **kwargs) - return self - - def look_at(self, thing_to_look_at=None): - self.eyes.look_at(thing_to_look_at) - return self - - def blink(self): - self.eyes.blink() - return self - - def get_squiggle(self, factor=0.2): - sine_curve = FunctionGraph( - lambda x: factor * np.sin(x), - x_min=0, x_max=TAU, - ) - sine_curve.rotate(TAU / 4) - sine_curve.match_style(self.body) - sine_curve.match_height(self.body) - sine_curve.move_to(self.body, UP) - return sine_curve - - def get_body_line(self, **kwargs): - config = dict(self.body_config) - config.update(kwargs) - line = Line(ORIGIN, 1.5 * UP, **config) - if hasattr(self, "body"): - line.match_style(self.body) - line.match_height(self.body) - line.move_to(self.body, UP) - return line - - def become_squiggle(self, **kwargs): - self.body.become(self.get_squiggle(**kwargs)) - return self - - def become_line(self, **kwargs): - self.body.become(self.get_body_line(**kwargs)) - return self - - def copy(self): - return self.deepcopy() - - -class Felix(PiCreature): - CONFIG = { - "color": GREEN_D - } - - -class PushPin(SVGMobject): - CONFIG = { - "file_name": "push_pin", - "height": 0.5, - "sheen": 0.7, - "fill_color": GREY, - } - - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - self.rotate(20 * DEGREES) - - def pin_to(self, point): - self.move_to(point, DR) - - -class Hand(SVGMobject): - CONFIG = { - "file_name": "pinch_hand", - "height": 0.5, - "sheen": 0.2, - "fill_color": ORANGE, - } - - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - # self.rotate(30 * DEGREES) - self.add(VectorizedPoint().next_to(self, UP, buff=0.17)) - - -class CheckeredCircle(Circle): - CONFIG = { - "n_pieces": 16, - "colors": [BLUE_E, BLUE_C], - "stroke_width": 5, - } - - def __init__(self, **kwargs): - Circle.__init__(self, **kwargs) - pieces = self.get_pieces(self.n_pieces) - self.points = np.zeros((0, 3)) - self.add(*pieces) - n_colors = len(self.colors) - for i, color in enumerate(self.colors): - self[i::n_colors].set_color(color) - - -class StereoProjectedSphere(Sphere): - CONFIG = { - "stereo_project_config": { - "axis": 2, - }, - "max_r": 32, - "max_width": FRAME_WIDTH, - "max_height": FRAME_WIDTH, - "max_depth": FRAME_WIDTH, - "radius": 1, - } - - def __init__(self, rotation_matrix=None, **kwargs): - digest_config(self, kwargs) - if rotation_matrix is None: - rotation_matrix = np.identity(3) - self.rotation_matrix = rotation_matrix - - self.stereo_project_config["r"] = self.radius - ParametricSurface.__init__( - self, self.post_projection_func, **kwargs - ) - self.submobjects.sort( - key=lambda m: -m.get_width() - ) - self.fade_far_out_submobjects() - - def post_projection_func(self, u, v): - point = self.radius * Sphere.func(self, u, v) - rot_point = np.dot(point, self.rotation_matrix.T) - result = stereo_project_point( - rot_point, **self.stereo_project_config - ) - epsilon = 1e-4 - if np.any(np.abs(result) == np.inf) or np.any(np.isnan(result)): - return self.func(u + epsilon, v) - return result - - def fade_far_out_submobjects(self, **kwargs): - max_r = kwargs.get("max_r", self.max_r) - max_width = kwargs.get("max_width", self.max_width) - max_height = kwargs.get("max_height", self.max_height) - max_depth = kwargs.get("max_depth", self.max_depth) - for submob in self.submobjects: - violations = [ - np.any(np.apply_along_axis(get_norm, 1, submob.get_anchors()) > max_r), - submob.get_width() > max_width, - submob.get_height() > max_height, - submob.get_depth() > max_depth - ] - if any(violations): - # self.remove(submob) - submob.fade(1) - return self - - -class StereoProjectedSphereFromHypersphere(StereoProjectedSphere): - CONFIG = { - "stereo_project_config": { - "axis": 0, - }, - "radius": 2, - "multiply_from_right": False, - } - - def __init__(self, quaternion=None, null_axis=0, **kwargs): - if quaternion is None: - quaternion = np.array([1, 0, 0, 0]) - self.quaternion = quaternion - self.null_axis = null_axis - ParametricSurface.__init__(self, self.q_mult_projection_func, **kwargs) - self.fade_far_out_submobjects() - - def q_mult_projection_func(self, u, v): - point = list(Sphere.func(self, u, v)) - point.insert(self.null_axis, 0) - if self.multiply_from_right: - post_q_mult = q_mult(point, self.quaternion) - else: - post_q_mult = q_mult(self.quaternion, point) - projected = list(self.radius * stereo_project_point( - post_q_mult, **self.stereo_project_config - )) - if np.any(np.abs(projected) == np.inf): - return self.func(u + 0.001, v) - ignored_axis = self.stereo_project_config["axis"] - projected.pop(ignored_axis) - return np.array(projected) - - -class StereoProjectedCircleFromHypersphere(CheckeredCircle): - CONFIG = { - "n_pieces": 48, - "radius": 2, - "max_length": 3 * FRAME_WIDTH, - "max_r": 50, - "basis_vectors": [ - [1, 0, 0, 0], - [0, 1, 0, 0], - ], - "multiply_from_right": False, - } - - def __init__(self, quaternion=None, **kwargs): - CheckeredCircle.__init__(self, **kwargs) - if quaternion is None: - quaternion = [1, 0, 0, 0] - self.quaternion = quaternion - self.pre_positioning_matrix = self.get_pre_positioning_matrix() - self.apply_function(self.projection) - self.remove_large_pieces() - self.set_shade_in_3d(True) - - def get_pre_positioning_matrix(self): - v1, v2 = [np.array(v) for v in self.basis_vectors] - v1 = normalize(v1) - v2 = v2 - np.dot(v1, v2) * v1 - v2 = normalize(v2) - return np.array([v1, v2]).T - - def projection(self, point): - q1 = self.quaternion - q2 = np.dot(self.pre_positioning_matrix, point[:2]).flatten() - if self.multiply_from_right: - new_q = q_mult(q2, q1) - else: - new_q = q_mult(q1, q2) - projected = stereo_project_point( - new_q, axis=0, r=self.radius, - ) - if np.any(projected == np.inf) or np.any(np.isnan(projected)): - epsilon = 1e-6 - return self.projection(rotate_vector(point, epsilon)) - return projected[1:] - - def remove_large_pieces(self): - for piece in self: - length = get_norm(piece.points[0] - piece.points[-1]) - violations = [ - length > self.max_length, - get_norm(piece.get_center()) > self.max_r, - ] - if any(violations): - piece.fade(1) - - -class QuaternionTracker(ValueTracker): - CONFIG = { - "force_unit": True, - "dim": 4, - } - - def __init__(self, four_vector=None, **kwargs): - Mobject.__init__(self, **kwargs) - if four_vector is None: - four_vector = np.array([1, 0, 0, 0]) - self.set_value(four_vector) - if self.force_unit: - self.add_updater(lambda q: q.normalize()) - - def set_value(self, vector): - self.points = np.array(vector).reshape((1, 4)) - return self - - def get_value(self): - return self.points[0] - - def normalize(self): - self.set_value(normalize( - self.get_value(), - fall_back=np.array([1, 0, 0, 0]) - )) - return self - - -class RubiksCube(VGroup): - CONFIG = { - "colors": [ - "#FFD500", # Yellow - "#C41E3A", # Orange - "#009E60", # Green - "#FF5800", # Red - "#0051BA", # Blue - "#FFFFFF" # White - ], - } - - def __init__(self, **kwargs): - digest_config(self, kwargs) - vectors = [OUT, RIGHT, UP, LEFT, DOWN, IN] - faces = [ - self.create_face(color, vector) - for color, vector in zip(self.colors, vectors) - ] - VGroup.__init__(self, *it.chain(*faces), **kwargs) - self.set_shade_in_3d(True) - - def create_face(self, color, vector): - squares = VGroup(*[ - self.create_square(color) - for x in range(9) - ]) - squares.arrange_in_grid( - 3, 3, - buff=0 - ) - squares.set_width(2) - squares.move_to(OUT, OUT) - squares.apply_matrix(z_to_vector(vector)) - return squares - - def create_square(self, color): - square = Square( - stroke_width=3, - stroke_color=BLACK, - fill_color=color, - fill_opacity=1, - side_length=1, - ) - square.flip() - return square - # back = square.copy() - # back.set_fill(BLACK, 0.85) - # back.set_stroke(width=0) - # back.shift(0.5 * IN) - # return VGroup(square, back) - - def get_face(self, vect): - self.sort(lambda p: np.dot(p, vect)) - return self[-(12 + 9):] - - -# Abstract scenes - - -class ManyNumberSystems(Scene): - def construct(self): - # Too much dumb manually positioning in here... - title = Title("Number systems") - name_location_color_example_tuples = [ - ("Reals", [-4, 2, 0], YELLOW, "1.414"), - ("Complex numbers", [4, 0, 0], BLUE, "2 + i"), - ("Quaternions", [0, 2, 0], PINK, "2 + 7i + 1j + 8k"), - ("Rationals", [3, -2, 0], RED, "1 \\over 3"), - ("p-adic numbers", [-2, -2, 0], GREEN, "\\overline{142857}2"), - ("Octonions", [-3, 0, 0], LIGHT_GREY, "3e_1 - 2.3e_2 + \\dots + 1.6e_8"), - ] - systems = VGroup() - for name, location, color, ex in name_location_color_example_tuples: - system = TextMobject(name) - system.set_color(color) - system.move_to(location) - example = TexMobject(ex) - example.next_to(system, DOWN) - system.add(example) - systems.add(system) - R_label, C_label, H_label = systems[:3] - - number_line = NumberLine(x_min=-3, x_max=3) - number_line.add_numbers() - number_line.shift(0.25 * FRAME_WIDTH * LEFT) - number_line.shift(0.5 * DOWN) - R_example_dot = Dot(number_line.number_to_point(1.414)) - plane = ComplexPlane(x_radius=3.5, y_radius=2.5) - plane.add_coordinates() - plane.shift(0.25 * FRAME_WIDTH * RIGHT) - plane.shift(0.5 * DOWN) - C_example_dot = Dot(plane.coords_to_point(2, 1)) - - self.add(title) - self.play(FadeInFromLarge(H_label)) - self.play(LaggedStartMap( - FadeInFromLarge, VGroup(*it.chain(systems[:2], systems[3:])), - lambda m: (m, 4) - )) - self.wait() - self.add(number_line, plane, systems) - self.play( - R_label.move_to, 0.25 * FRAME_WIDTH * LEFT + 2 * UP, - C_label.move_to, 0.25 * FRAME_WIDTH * RIGHT + 2 * UP, - H_label.move_to, 0.75 * FRAME_WIDTH * RIGHT + 2 * UP, - FadeOutAndShift(systems[3:], 2 * DOWN), - Write(number_line), - Write(plane), - GrowFromCenter(R_example_dot), - R_label[-1].next_to, R_example_dot, UP, - GrowFromCenter(C_example_dot), - C_label[-1].next_to, C_example_dot, UR, SMALL_BUFF, - C_label[-1].shift, 0.4 * LEFT, - ) - number_line.add(R_example_dot) - plane.add(C_example_dot) - self.wait(2) - self.play(LaggedStartMap( - ApplyMethod, - VGroup( - H_label, - VGroup(plane, C_label), - VGroup(number_line, R_label), - ), - lambda m: (m.shift, 0.5 * FRAME_WIDTH * LEFT), - lag_ratio=0.8, - )) - randy = Randolph(height=1.5) - randy.next_to(plane, RIGHT) - randy.to_edge(DOWN) - self.play( - randy.change, "maybe", H_label, - VFadeIn(randy), - ) - self.play(Blink(randy)) - self.play(randy.change, "confused", H_label.get_top()) - self.wait() - - -class RotationsIn3d(SpecialThreeDScene): - def construct(self): - self.set_camera_orientation(**self.get_default_camera_position()) - self.begin_ambient_camera_rotation(rate=0.02) - sphere = self.get_sphere() - vectors = VGroup(*[ - Vector(u * v, color=color).next_to(sphere, u * v, buff=0) - for v, color in zip( - [RIGHT, UP, OUT], - [GREEN, RED, BLUE], - ) - for u in [-1, 1] - ]) - vectors.set_shade_in_3d(True) - sphere.add(vectors) - - self.add(self.get_axes()) - self.add(sphere) - angle_axis_pairs = [ - (90 * DEGREES, RIGHT), - (120 * DEGREES, UR), - (-45 * DEGREES, OUT), - (60 * DEGREES, IN + DOWN), - (90 * DEGREES, UP), - (30 * DEGREES, UP + OUT + RIGHT), - ] - for angle, axis in angle_axis_pairs: - self.play(Rotate( - sphere, angle, - axis=axis, - run_time=2, - )) - self.wait() - - -class IntroduceHamilton(Scene): - def construct(self): - hamilton = ImageMobject("Hamilton", height=6) - hamilton.to_corner(UL) - shamrock = SVGMobject(file_name="shamrock") - shamrock.set_height(1) - shamrock.set_color("#009a49") - shamrock.set_fill(opacity=0.25) - shamrock.next_to(hamilton.get_corner(UL), DR) - shamrock.align_to(hamilton, UP) - hamilton_name = TextMobject( - "William Rowan Hamilton" - ) - hamilton_name.match_width(hamilton) - hamilton_name.next_to(hamilton, DOWN) - - quote = TextMobject( - """\\huge ...Every morning in the early part of the above-cited - month, on my coming down to breakfast, your (then) - little brother William Edwin, and yourself, used to - ask me,""", - "``Well, Papa, can you multiply triplets''?", - """Whereto I was always obliged to reply, with a sad - shake of the head: ``No, I can only add and subtract - them.''...""", - alignment="" - ) - quote.set_color_by_tex("Papa", YELLOW) - quote = VGroup(*it.chain(*quote)) - quote.set_width(FRAME_WIDTH - hamilton.get_width() - 2) - quote.to_edge(RIGHT) - quote_rect = SurroundingRectangle(quote, buff=MED_SMALL_BUFF) - quote_rect.set_stroke(WHITE, 2) - quote_rect.stretch(1.1, 1) - quote_label = TextMobject( - "August 5, 1865 Letter\\\\from Hamilton to his son" - ) - quote_label.next_to(quote_rect, UP) - quote_label.set_color(BLUE) - VGroup(quote, quote_rect, quote_label).to_edge(UP) - - plaque = ImageMobject("BroomBridgePlaque") - plaque.set_width(FRAME_WIDTH / 2) - plaque.to_edge(LEFT) - plaque.shift(UP) - equation = TexMobject( - "i^2 = j^2 = k^2 = ijk = -1", - tex_to_color_map={"i": GREEN, "j": RED, "k": BLUE} - ) - equation_rect = Rectangle(width=3.25, height=0.7) - equation_rect.move_to(3.15 * LEFT + 0.25 * DOWN) - equation_rect.set_color(WHITE) - equation_arrow = Vector(DOWN) - equation_arrow.match_color(equation_rect) - equation_arrow.next_to(equation_rect, DOWN) - equation.next_to(equation_arrow, DOWN) - - self.play( - FadeInFromDown(hamilton), - Write(hamilton_name), - ) - self.play(DrawBorderThenFill(shamrock)) - self.wait() - - self.play( - LaggedStartMap( - FadeIn, quote, - lag_ratio=0.2, - run_time=4 - ), - FadeInFromDown(quote_label), - ShowCreation(quote_rect) - ) - self.wait(3) - self.play( - ApplyMethod( - VGroup(hamilton, shamrock, hamilton_name).to_edge, RIGHT, - run_time=2, - rate_func=squish_rate_func(smooth, 0.5, 1), - ), - LaggedStartMap( - FadeOutAndShiftDown, VGroup(*it.chain( - quote, quote_rect, quote_label - )) - ) - ) - self.wait() - self.play(FadeIn(plaque)) - self.play( - ShowCreation(equation_rect), - GrowArrow(equation_arrow) - ) - self.play(ReplacementTransform( - equation.copy().replace(equation_rect).fade(1), - equation - )) - self.wait() - - -class QuaternionHistory(Scene): - CONFIG = { - "names_and_quotes": [ - ( - "Oliver Heaviside", - """\\huge ``As far as the vector analysis I required was - concerned, the quaternion was not only not - required, but was a positive evil of no - inconsiderable magnitude.''""" - ), - ( - "Lord Kelvin", - """\\huge ``Quaternions... though beautifully \\\\ ingenious, - have been an unmixed evil to those who have - touched them in any way, including Clerk Maxwell.''""" - ), - ] - } - - def construct(self): - self.show_dot_product_and_cross_product() - self.teaching_students_quaternions() - self.show_anti_quaternion_quote() - self.mad_hatter() - - def show_dot_product_and_cross_product(self): - date = TexMobject("1843") - date.scale(2) - date.to_edge(UP) - - t2c = self.t2c = { - "x_1": GREEN, - "x_2": GREEN, - "y_1": RED, - "y_2": RED, - "z_1": BLUE, - "z_2": BLUE, - } - - def get_colored_tex_mobject(tex): - return TexMobject(tex, tex_to_color_map=t2c) - - v1, v2 = [ - Matrix([ - ["{}_{}".format(c, i)] - for c in "xyz" - ], element_to_mobject=get_colored_tex_mobject) - for i in (1, 2) - ] - dot_rhs = get_colored_tex_mobject( - "x_1 x_2 + y_1 y_2 + z_1 z_2", - ) - cross_rhs = Matrix([ - ["y_1 z_2 - z_1 y_2"], - ["z_1 x_2 - x_1 z_2"], - ["x_1 y_2 - y_1 x_2"], - ], element_to_mobject=get_colored_tex_mobject) - - dot_product = VGroup( - v1.copy(), TexMobject("\\cdot").scale(2), - v2.copy(), TexMobject("="), - dot_rhs - ) - cross_product = VGroup( - v1.copy(), TexMobject("\\times"), - v2.copy(), TexMobject("="), - cross_rhs - ) - for product in dot_product, cross_product: - product.arrange(RIGHT, buff=2 * SMALL_BUFF) - product.set_height(1.5) - dot_product.next_to(date, DOWN, buff=MED_LARGE_BUFF) - dot_product.to_edge(LEFT, buff=LARGE_BUFF) - cross_product.next_to( - dot_product, DOWN, - buff=MED_LARGE_BUFF, - aligned_edge=LEFT, - ) - - self.play(FadeInFrom(dot_product, 2 * RIGHT)) - self.play(FadeInFrom(cross_product, 2 * LEFT)) - self.wait() - self.play(FadeInFromDown(date)) - self.play(ApplyMethod(dot_product.fade, 0.7)) - self.play(ApplyMethod(cross_product.fade, 0.7)) - self.wait() - self.play( - FadeOutAndShift(dot_product, 2 * LEFT), - FadeOutAndShift(cross_product, 2 * RIGHT), - ) - - self.date = date - - def teaching_students_quaternions(self): - hamilton = ImageMobject("Hamilton") - hamilton.set_height(4) - hamilton.pixel_array = hamilton.pixel_array[:, ::-1, :] - hamilton.to_corner(UR) - hamilton.shift(MED_SMALL_BUFF * DOWN) - - colors = color_gradient([BLUE_E, GREY_BROWN, BLUE_B], 7) - random.shuffle(colors) - students = VGroup(*[ - PiCreature(color=color) - for color in colors - ]) - students.set_height(2) - students.arrange(RIGHT) - students.set_width(FRAME_WIDTH - hamilton.get_width() - 1) - students.to_corner(DL) - - equation = TexMobject(""" - (x_1 i + y_1 j + z_1 k) - (x_2 i + y_2 j + z_2 k) - = - (-x_1 x_2 - y_1 y_2 - z_1 z_2) + - (y_1 z_2 - z_1 y_2)i + - (z_1 x_2 - x_1 z_2)j + - (x_1 y_2 - y_1 x_2)k - """, tex_to_color_map=self.t2c) - equation.set_width(FRAME_WIDTH - 1) - equation.to_edge(UP, buff=MED_SMALL_BUFF) - - images = Group() - image_labels = VGroup() - images_with_labels = Group() - names = ["Peter Tait", "Robert Ball", "Macfarlane Alexander"] - for name in names: - image = ImageMobject(name) - image.set_height(3) - label = TextMobject(name) - label.scale(0.5) - label.next_to(image, DOWN) - image.label = label - image_labels.add(label) - images.add(image) - images_with_labels.add(Group(image, label)) - images_with_labels.arrange(RIGHT) - images_with_labels.next_to(hamilton, LEFT, LARGE_BUFF) - images_with_labels.shift(MED_LARGE_BUFF * DOWN) - society_title = TextMobject("Quaternion society") - society_title.next_to(images, UP, MED_LARGE_BUFF, UP) - - def blink_wait(n_loops): - for x in range(n_loops): - self.play(Blink(random.choice(students))) - self.wait(random.random()) - - self.play( - FadeInFromDown(hamilton), - Write( - self.date, - rate_func=lambda t: smooth(1 - t), - remover=True - ) - ) - self.play(LaggedStartMap( - FadeInFrom, students, - lambda m: (m, LEFT), - )) - self.play( - LaggedStartMap( - ApplyMethod, students, - lambda pi: ( - pi.change, - random.choice(["confused", "maybe", "erm"]), - 3 * LEFT + 2 * UP, - ), - ), - Write(equation), - ) - blink_wait(3) - self.play( - LaggedStartMap(FadeInFromDown, images), - LaggedStartMap(FadeInFromLarge, image_labels), - Write(society_title) - ) - blink_wait(3) - self.play( - FadeOutAndShift(hamilton, RIGHT), - LaggedStartMap( - FadeOutAndShift, images_with_labels, - lambda m: (m, UP) - ), - FadeOutAndShift(students, DOWN), - FadeOut(society_title), - run_time=1 - ) - - self.equation = equation - - def show_anti_quaternion_quote(self): - group = self.get_dissenter_images_quotes_and_names() - images, quotes, names = group - self.play( - LaggedStartMap(FadeInFromDown, images), - LaggedStartMap(FadeInFromLarge, names), - lag_ratio=0.75, - run_time=2, - ) - for quote in quotes: - self.play(LaggedStartMap( - FadeIn, VGroup(*quote.family_members_with_points()), - lag_ratio=0.3 - )) - self.wait() - self.play(FadeOut(images_with_quotes)) - - def mad_hatter(self): - title = TextMobject( - "Lewis Carroll's", "``Alice in wonderland''" - ) - title.to_edge(UP, buff=LARGE_BUFF) - author_brace = Brace(title[0], DOWN) - aka = TextMobject("a.k.a. Mathematician Charles Dodgson") - aka.scale(0.8) - aka.set_color(BLUE) - aka.next_to(author_brace, DOWN) - - quote = TextMobject( - """ - ``Why, you might just as well say that\\\\ - ‘I see what I eat’ is the same thing as\\\\ - ‘I eat what I see’!'' - """, - tex_to_color_map={ - "I see what I eat": BLUE, - "I eat what I see": YELLOW, - }, - alignment="" - ) - quote.to_edge(UP, buff=LARGE_BUFF) - - hatter = PiCreature(color=RED, mode="surprised") - hat = SVGMobject(file_name="hat") - hat_back = hat.copy() - hat_back[0].remove(*[ - sm for sm in hat_back[0] if sm.is_subpath - ]) - hat_back.set_fill(DARK_GREY) - hat.add_to_back(hat_back) - hat.set_height(1.25) - hat.next_to(hatter.body, UP, buff=-MED_SMALL_BUFF) - hatter.add(hat) - hatter.look(DL) - hatter.pupils[1].save_state() - hatter.look(UL) - hatter.pupils[1].restore() - hatter.set_height(2) - - hare = SVGMobject(file_name="bunny") - mouse = SVGMobject(file_name="mouse") - for mob in hare, mouse: - mob.set_color(LIGHT_GREY) - mob.set_sheen(0.2, UL) - mob.set_height(1.5) - - characters = VGroup(hatter, hare, mouse) - for mob, p in zip(characters, [UP, DL, DR]): - mob.move_to(p) - hare.shift(MED_SMALL_BUFF * LEFT) - - characters.space_out_submobjects(1.5) - characters.to_edge(DOWN) - - def change_single_place(char, **kwargs): - i = characters.submobjects.index(char) - target = characters[(i + 1) % 3] - return ApplyMethod( - char.move_to, target, - path_arc=-90 * DEGREES, - **kwargs - ) - - def get_change_places(): - return LaggedStartMap( - change_single_place, characters, - lag_ratio=0.6 - ) - - self.play( - Write(title), - LaggedStartMap(FadeInFromDown, characters) - ) - self.play( - get_change_places(), - GrowFromCenter(author_brace), - FadeIn(aka) - ) - for x in range(4): - self.play(get_change_places()) - self.play( - FadeOutAndShift(VGroup(title, author_brace, aka)), - FadeInFromDown(quote), - ) - self.play(get_change_places()) - self.play( - get_change_places(), - VFadeOut(characters, run_time=2) - ) - self.remove(characters) - self.wait() - - # - def get_dissenter_images_quotes_and_names(self): - names_and_quotes = self.names_and_quotes - images = Group() - quotes = VGroup() - names = VGroup() - images_with_quotes = Group() - for name, quote_text in names_and_quotes: - image = Group(ImageMobject(name)) - image.set_height(4) - label = TextMobject(name) - label.next_to(image, DOWN) - names.add(label) - quote = TextMobject( - quote_text, - tex_to_color_map={ - "positive evil": RED, - "unmixed evil": RED, - }, - alignment="" - ) - quote.scale(0.3) - quote.next_to(image, UP) - images.add(image) - quotes.add(quote) - images_with_quotes.add(Group(image, label, quote)) - - images_with_quotes.arrange(RIGHT, buff=LARGE_BUFF) - images_with_quotes.to_edge(DOWN, MED_LARGE_BUFF) - return Group(images, quotes, names) - - -class QuaternionRotationOverlay(Scene): - def construct(self): - equations = VGroup( - TexMobject( - "p", "\\rightarrow", - "{}", - "{}", - "\\left(q_1", - "p", - "q_1^{-1}\\right)", - "{}", - "{}", - ), - TexMobject( - "p", "\\rightarrow", - "{}", - "\\left(q_2", - "\\left(q_1", - "p", - "q_1^{-1}\\right)", - "q_2^{-1}\\right)", - "{}", - ), - TexMobject( - "p", "\\rightarrow", - "\\left(q_3", - "\\left(q_2", - "\\left(q_1", - "p", - "q_1^{-1}\\right)", - "q_2^{-1}\\right)", - "q_3^{-1}\\right)", - ), - ) - for equation in equations: - equation.set_color_by_tex_to_color_map({ - "1": GREEN, "2": RED, "3": BLUE, - }) - equation.set_color_by_tex("rightarrow", WHITE) - equation.to_corner(UL) - - equation = equations[0].copy() - self.play(Write(equation)) - self.wait() - for new_equation in equations[1:]: - self.play( - Transform(equation, new_equation) - ) - self.wait(2) - - -class RotateCubeThreeTimes(SpecialThreeDScene): - def construct(self): - cube = RubiksCube() - cube.set_fill(opacity=0.8) - cube.set_stroke(width=1) - randy = Randolph(mode="pondering") - randy.set_height(cube.get_height() - 2 * SMALL_BUFF) - randy.move_to(cube.get_edge_center(OUT)) - randy.set_fill(opacity=0.8) - # randy.set_shade_in_3d(True) - cube.add(randy) - axes = self.get_axes() - - self.add(axes, cube) - self.move_camera( - phi=70 * DEGREES, - theta=-140 * DEGREES, - ) - self.begin_ambient_camera_rotation(rate=0.02) - self.wait(2) - self.play(Rotate(cube, TAU / 4, RIGHT, run_time=3)) - self.wait(2) - self.play(Rotate(cube, TAU / 4, UP, run_time=3)) - self.wait(2) - self.play(Rotate(cube, -TAU / 3, np.ones(3), run_time=3)) - self.wait(7) - - -class QuantumSpin(Scene): - def construct(self): - title = Title("Two-state system") - - electron = Dot(color=BLUE) - electron.set_height(1) - electron.set_sheen(0.3, UL) - electron.set_fill(opacity=0.8) - kwargs = { - "path_arc": PI, - } - curved_arrows = VGroup( - Arrow(RIGHT, LEFT, **kwargs), - Arrow(LEFT, RIGHT, **kwargs), - ) - curved_arrows.set_color(LIGHT_GREY) - curved_arrows.set_stroke(width=2) - - y_arrow = Vector(UP) - y_arrow.set_color(RED) - - y_ca = curved_arrows.copy() - y_ca.rotate(70 * DEGREES, LEFT) - y_group = VGroup(y_ca[0], y_arrow, electron.copy(), y_ca[1]) - - x_arrow = y_arrow.copy().rotate(90 * DEGREES, IN, about_point=ORIGIN) - x_arrow.set_color(GREEN) - x_ca = curved_arrows.copy() - x_ca.rotate(70 * DEGREES, UP) - x_group = VGroup(x_ca[0], x_arrow, electron.copy(), x_ca[1]) - - z_ca = curved_arrows.copy() - z_group = VGroup(electron.copy(), z_ca, Dot(color=BLUE)) - - groups = VGroup(x_group, y_group, z_group) - groups.arrange(RIGHT, buff=1.5) - groups.move_to(UP) - - y_ca.rotation = Rotating( - y_ca, axis=rotate_vector(OUT, 70 * DEGREES, LEFT) - ) - x_ca.rotation = Rotating( - x_ca, axis=rotate_vector(OUT, 70 * DEGREES, UP) - ) - z_ca.rotation = Rotating(z_ca, axis=OUT) - rotations = [ca.rotation for ca in [x_ca, y_ca, z_ca]] - - matrices = VGroup( - Matrix([["0", "1"], ["1", "0"]]), - Matrix([["0", "-i"], ["i", "0"]]), - Matrix([["1", "0"], ["0", "-1"]]), - ) - for matrix, group in zip(matrices, groups): - matrix.next_to(group, DOWN) - for matrix in matrices[1:]: - matrix.align_to(matrices[0], DOWN) - - self.add(title, groups) - self.play() - self.play( - LaggedStartMap(FadeInFromDown, matrices, run_time=3), - *rotations - ) - for x in range(2): - self.play(*rotations) - - -class HereWeTackle4d(TeacherStudentsScene): - def construct(self): - titles = VGroup( - TextMobject( - "This video:\\\\", - "Quaternions in 4d" - ), - TextMobject( - "Next video:\\\\", - "Quaternions acting on 3d" - ) - ) - for title in titles: - title.move_to(self.hold_up_spot, DOWN) - titles[0].set_color(YELLOW) - - self.play( - self.teacher.change, "raise_right_hand", - FadeInFromDown(titles[0]), - self.get_student_changes("confused", "horrified", "sad") - ) - self.look_at(self.screen) - self.wait() - self.change_student_modes( - "erm", "thinking", "pondering", - look_at_arg=self.screen - ) - self.wait(3) - self.change_student_modes( - "pondering", "confused", "happy" - ) - self.look_at(self.screen) - self.wait(3) - self.play( - self.teacher.change, "hooray", - FadeInFrom(titles[1]), - ApplyMethod( - titles[0].shift, 2 * UP, - rate_func=squish_rate_func(smooth, 0.2, 1) - ) - ) - self.change_all_student_modes("hooray") - self.play(self.teacher.change, "happy") - self.look_at(self.screen) - self.wait(3) - self.change_student_modes("pondering", "happy", "thinking") - self.wait(4) - - -class TableOfContents(Scene): - def construct(self): - chapters = VGroup( - TextMobject( - "\\underline{Chapter 1}\\\\", "Linus the Linelander" - ), - TextMobject( - "\\underline{Chapter 2}\\\\", "Felix the Flatlander" - ), - TextMobject( - "\\underline{Chapter 3}\\\\", " You, the 3d-lander" - ), - ) - for chapter in chapters: - chapter.space_out_submobjects(1.5) - chapters.arrange( - DOWN, buff=1.5, aligned_edge=LEFT - ) - chapters.to_edge(LEFT) - - for chapter in chapters: - self.play(FadeInFromDown(chapter)) - self.wait(2) - for chapter in chapters: - chapters.save_state() - other_chapters = VGroup(*[ - c for c in chapters if c is not chapter - ]) - self.play( - chapter.set_width, 0.5 * FRAME_WIDTH, - chapter.center, - other_chapters.fade, 1 - ) - self.wait(3) - self.play(chapters.restore) - - -class IntroduceLinusTheLinelander(Scene): - def construct(self): - self.introduce_linus() - self.show_real_number_line() - self.look_at_complex_plane() - - def introduce_linus(self): - linus = Linus() - linus.move_to(3 * LEFT) - name = TextMobject("Linus the Linelander") - name.next_to(linus, DR, buff=MED_LARGE_BUFF) - arrow = Arrow(name.get_top(), linus.get_right()) - - self.play(FadeInFromDown(linus)) - self.play( - Write(name), - GrowArrow(arrow), - linus.change, "gracious", name, - ) - self.play( - linus.become_squiggle, {"factor": -0.1}, - ) - self.play(Blink(linus)) - self.wait() - - self.name_text = name - self.name_arrow = arrow - self.linus = linus - - def show_real_number_line(self): - linus = self.linus - number_line = NumberLine() - number_line.add_numbers() - number_line.to_edge(UP) - - algebra = VGroup( - TexMobject("3 \\cdot 4 = 12"), - TexMobject("3 + 4 = 7"), - TexMobject("(-2) \\cdot 3 = -6"), - ) - algebra.arrange(DOWN) - algebra.next_to(number_line, DOWN, LARGE_BUFF) - algebra.shift(3 * RIGHT) - - self.play( - ShowCreation(number_line), - linus.look_at, number_line - ) - self.play( - LaggedStartMap(FadeInFromDown, number_line.numbers), - LaggedStartMap(ShowCreation, number_line.tick_marks), - linus.change, "happy" - ) - self.play( - LaggedStartMap(FadeInFromDown, algebra), - linus.look_at, algebra - ) - self.play(Blink(linus)) - self.wait() - - self.algebra = algebra - - def look_at_complex_plane(self): - linus = self.linus - to_fade = VGroup( - self.name_text, - self.name_arrow, - self.algebra, - ) - frame = ScreenRectangle() - frame.set_width(8) - frame.to_corner(DR) - - q_marks = VGroup(*[ - TexMobject("?").shift( - random.random() * RIGHT, - random.random() * UP, - ) - for x in range(50) - ]) - q_marks.next_to(linus.body, UP, buff=0) - q_marks.set_color_by_gradient(BLUE, GREEN, YELLOW) - random.shuffle(q_marks.submobjects) - q_marks_anim = LaggedStartMap( - FadeIn, q_marks, - run_time=15, - rate_func=there_and_back, - lag_ratio=0.1 - ) - q_marks_continual = turn_animation_into_updater(q_marks_anim) - - self.play( - FadeOut(to_fade), - ShowCreation(frame), - linus.look_at, frame - ) - self.add(q_marks_continual) - self.play(linus.change_mode, "confused") - self.wait() - self.play(Blink(linus)) - self.play(linus.change, "confused", frame.get_bottom()) - self.wait() - self.play(linus.change, "sad", frame.get_center()) - self.wait(10) - - -class ShowComplexMultiplicationExamples(Scene): - CONFIG = { - "plane_config": { - "x_radius": 9, - "y_radius": 9, - "stroke_width": 3, - }, - "background_plane_config": { - "color": LIGHT_GREY, - "secondary_color": DARK_GREY, - "stroke_width": 0.5, - "stroke_opacity": 0.5, - "secondary_line_ratio": 0, - } - } - - def construct(self): - self.add_planes() - z_tuples = [ - (complex(2, 1), "2 + i", UP), - (complex(5, 2), "5 + 2i", LEFT), - ( - complex(-np.sqrt(2) / 2, np.sqrt(2) / 2), - "-\\frac{\\sqrt{2}}{2} + \\frac{\\sqrt{2}}{2} i", - LEFT, - ), - (complex(-4, 1.5), "-4 + 1.5i", RIGHT), - (complex(3, 0), "3 + 0i", UP), - (complex(4, -3), "4 + -3i", UP), - ] - - for z, z_tex, label_vect in z_tuples: - self.show_multiplication(z, z_tex, label_vect) - - def add_planes(self, include_title=True): - plane = ComplexPlane(**self.plane_config) - self.plane = plane - background_plane = ComplexPlane(**self.background_plane_config) - background_plane.add_coordinates() - self.background_plane = background_plane - - self.add(background_plane) - self.add(plane) - - if include_title: - title = TextMobject("Complex plane") - title.scale(1.5) - title.to_corner(UL, buff=MED_LARGE_BUFF) - title.shift(SMALL_BUFF * UR) - self.title = title - self.add_foreground_mobjects(title) - - def show_multiplication(self, z, z_tex, label_vect): - z_color = WHITE - plane = self.plane - new_plane = plane.deepcopy() - real_tex, imag_tex = z_tex.split("+") - label = TexMobject( - "\\text{Multiply by}\\\\", - real_tex, "+", imag_tex, - alignment="", - ) - label[1].set_color(GREEN) - label[3].set_color(RED) - label.scale(1.2) - - h_line = Line( - plane.number_to_point(0), - plane.number_to_point(z.real), - color=GREEN, - stroke_width=5, - ) - v_line = Line( - plane.number_to_point(z.real), - plane.number_to_point(z), - color=RED, - stroke_width=5, - ) - lines = VGroup(h_line, v_line) - - z_point = plane.number_to_point(z) - z_dot = Dot(z_point) - z_dot.set_color(z_color) - label[1:].next_to(z_dot, label_vect) - label[0].next_to(label[1:], UP) - for mob in label: - label.add_to_back(BackgroundRectangle(mob)) - - one_dot = Dot(plane.number_to_point(1)) - one_dot.set_color(YELLOW) - for dot in z_dot, one_dot: - dot.save_state() - dot.scale(5) - dot.set_fill(opacity=0) - dot.set_stroke(width=1, opacity=0.5) - to_fade_out = VGroup( - plane, label, lines, z_dot, one_dot - ) - - self.play( - ShowCreation(lines), - FadeInFromDown(label), - Restore(z_dot), - ) - self.play(Restore(one_dot)) - angle = np.log(z).imag - self.play( - one_dot.move_to, z_dot, - plane.apply_complex_function, lambda w: z * w, - path_arc=angle, - run_time=3 - ) - self.wait() - self.play( - FadeOut(to_fade_out), - FadeIn(new_plane), - ) - self.plane = new_plane - - -class DefineComplexNumbersPurelyAlgebraically(Scene): - def construct(self): - self.add_linus() - self.add_title() - self.show_example_number() - self.show_multiplication() - self.emphsize_result_has_same_form() - - def add_linus(self): - linus = self.linus = Linus() - linus.move_to(3 * LEFT) - - def add_title(self): - title = self.title = Title( - "No spatial reasoning, just symbols" - ) - self.play( - FadeInFromDown(title[:-1]), - ShowCreation(title[-1]), - self.linus.look_at, title - ) - - def show_example_number(self): - linus = self.linus - - number = TexMobject("2.35", "+", "3.14", "i") - number.next_to(self.title, DOWN, buff=1.5) - number.shift(3 * RIGHT) - real, imag = number[0], number[2] - real_brace = Brace(real, UP) - imag_brace = Brace(imag, DOWN) - real_label = real_brace.get_text("Some real number") - imag_label = imag_brace.get_text("Some other real number") - VGroup(real, real_label).set_color(GREEN) - VGroup(imag, imag_label).set_color(RED) - - i = number[-1] - i_def = TexMobject("i", "^2", "=", "-1") - i_def.next_to( - self.title, DOWN, - buff=MED_LARGE_BUFF, - aligned_edge=LEFT, - ) - i_def_rect = SurroundingRectangle(i_def, color=YELLOW, buff=MED_SMALL_BUFF) - definition_label = TextMobject("Definition") - definition_label.next_to(i_def_rect, DOWN) - definition_label.match_color(i_def_rect) - - self.play(Write(number, run_time=1)) - self.play( - GrowFromCenter(real_brace), - LaggedStartMap(FadeIn, real_label), - linus.change, "confused", number, - run_time=1 - ) - self.wait() - self.play( - GrowFromCenter(imag_brace), - LaggedStartMap(FadeIn, imag_label), - run_time=1 - ) - self.play(Blink(linus)) - self.play( - linus.change, "erm", i_def, - ReplacementTransform( - i.copy(), i_def[0], - path_arc=-30 * DEGREES - ), - FadeIn(i_def_rect), - FadeIn(definition_label), - ) - self.play(Write(i_def[1:])) - self.wait() - self.play(Blink(linus)) - - self.to_fade = VGroup( - real_brace, real_label, - imag_brace, imag_label, - ) - self.number = number - - def show_multiplication(self): - linus = self.linus - to_fade = self.to_fade - z1 = self.number - - z2 = TexMobject("4", "+", "5", "i") - z2.match_style(z1) - - for z in z1, z2: - lp, rp = z.parens = TexMobject("()") - lp.next_to(z, LEFT, SMALL_BUFF) - rp.next_to(z, RIGHT, SMALL_BUFF) - z.real = z[0] - z.imag = z[2:] - for part in z.real, z.imag: - part.targets = [part.copy(), part.copy()] - - z1.generate_target() - product = VGroup( - VGroup(z1.target, z1.parens), - VGroup(z2, z2.parens), - ) - product.arrange(RIGHT, SMALL_BUFF) - product.move_to(2 * RIGHT + 2 * UP) - - foil = VGroup(*map(TextMobject, [ - "First", "Outside", "Inside", "Last", - ])) - foil.arrange( - DOWN, buff=MED_SMALL_BUFF, - aligned_edge=LEFT - ) - foil.scale(1.25) - for word in foil: - word[0].set_color(BLUE) - foil.move_to(product).to_edge(DOWN, LARGE_BUFF) - - def get_cdot(): - return TexMobject("\\cdot") - - def get_lp(): - return TexMobject("(") - - def get_rp(): - return TexMobject(")") - - def get_plus(): - return TexMobject("+") - - expansion = VGroup( - z1.real.targets[0], get_cdot(), z2.real.targets[0], get_plus(), - z1.real.targets[1], get_cdot(), z2.imag.targets[0], get_plus(), - z1.imag.targets[0], get_cdot(), z2.real.targets[1], get_plus(), - z1.imag.targets[1], get_cdot(), z2.imag.targets[1], - ) - expansion.arrange(RIGHT, buff=0.15) - expansion.next_to(product, DOWN, buff=LARGE_BUFF) - expansion_parts = VGroup(*[ - expansion[4 * i: 4 * i + 3] - for i in range(4) - ]) - expansion_part_braces = VGroup(*[ - Brace(part, DOWN) for part in expansion_parts - ]) - for word, brace in zip(foil, expansion_part_braces): - word.next_to(brace, DOWN) - - final_prouct = VGroup( - get_lp(), - z1[0].copy(), get_cdot(), z2[0].copy(), - TexMobject("-"), - z1[2].copy(), get_cdot(), z2[2].copy(), - get_rp(), get_plus(), - get_lp(), - z1[0].copy(), get_cdot(), z2[2].copy(), - get_plus(), - z1[2].copy(), get_cdot(), z2[0].copy(), - get_rp(), TexMobject("i") - ) - final_prouct.arrange(RIGHT, buff=0.15) - final_prouct.next_to(expansion, DOWN, buff=2) - final_arrows = VGroup() - for i, brace in zip([1, 11, 15, 5], expansion_part_braces): - target = final_prouct[i:i + 3] - if i == 5: - arrow = Line( - brace.get_bottom() + SMALL_BUFF * DOWN, - target.get_top() + MED_SMALL_BUFF * UP, - ) - arrow.points[1] = arrow.points[0] + DOWN - arrow.points[2] = arrow.points[3] + UP - tip = RegularPolygon(3, start_angle=-100 * DEGREES) - tip.set_height(0.2) - tip.set_stroke(width=0) - tip.set_fill(WHITE, opacity=1) - tip.move_to(arrow.get_end()) - arrow.add(tip) - else: - arrow = Arrow( - brace.get_bottom(), - target.get_top(), - ) - final_arrows.add(arrow) - final_arrows.set_stroke(BLACK, width=6, background=True) - - # Move example number into product - self.play( - FadeOut(to_fade), - MoveToTarget(z1), - FadeIn(z1.parens), - FadeInFromDown(z2), - FadeIn(z2.parens), - linus.change, "happy", product, - ) - self.wait() - - # Show distribution - pairs = list(it.product([z1.real, z1.imag], [z2.real, z2.imag])) - for i in range(4): - left, right = pair = VGroup(*pairs[i]) - word = foil[i] - dot = expansion[4 * i + 1] - plus = expansion[4 * i + 3] if i < 3 else VMobject() - brace = expansion_part_braces[i] - - self.play(pair.shift, 0.5 * DOWN) - self.play( - FadeIn(dot), - GrowFromCenter(brace), - FadeInFromDown(word), - linus.move_to, 4 * LEFT + DOWN, - *[ - ReplacementTransform( - part.copy(), - part.targets.pop(0) - ) - for part in pair - ] - ) - self.play( - pair.shift, 0.5 * UP, - FadeIn(plus) - ) - self.play(Blink(linus)) - - self.play( - FadeOut(foil), - FadeInFromDown(final_prouct), - linus.look_at, final_prouct, - ) - self.play( - LaggedStartMap(ShowCreation, final_arrows), - run_time=3, - ) - self.play(linus.change, "confused") - self.wait() - - self.final_prouct = final_prouct - - def emphsize_result_has_same_form(self): - final_product = self.final_prouct - real = final_product[1:1 + 7] - imag = final_product[11:11 + 7] - - real_brace = Brace(real, DOWN) - real_label = real_brace.get_text("Some real number") - real_label.set_color(GREEN) - imag_brace = Brace(imag, DOWN) - imag_label = imag_brace.get_text( - "Some other \\\\ real number" - ) - imag_label.set_color(RED) - braces = VGroup(real_brace, imag_brace) - labels = VGroup(real_label, imag_label) - self.play( - LaggedStartMap(GrowFromCenter, braces), - LaggedStartMap(Write, labels), - ) - self.wait() - - -class TextbookQuaternionDefinition(TeacherStudentsScene): - CONFIG = { - "random_seed": 1, - } - - def construct(self): - equation = TexMobject( - """ - (w_1 + x_1 i + y_1 j + z_1 k) - (w_2 + x_2 i + y_2 j + z_2 k) = - &(w_1 w_2 - x_1 x_2 - y_1 y_2 - z_1 z_2) \\, +\\\\ - &(w_1 x_2 + x_1 w_2 + y_1 z_2 - z_1 y_2)i \\, +\\\\ - &(w_1 y_2 + y_1 w_2 + z_1 x_2 - x_1 z_2)j \\, +\\\\ - &(w_1 z_2 + z_1 w_2 + x_1 y_2 - y_1 x_2)k \\\\ - """, - tex_to_color_map={ - "w_1": YELLOW, - "w_2": YELLOW, - "x_1": GREEN, - "x_2": GREEN, - "y_1": RED, - "y_2": RED, - "z_1": BLUE, - "z_2": BLUE, - } - ) - equation.set_width(FRAME_WIDTH - 1) - equation.to_edge(UP) - - defining_products = VGroup(*[ - TexMobject( - tex, - tex_to_color_map={ - "i": GREEN, - "j": RED, - "k": BLUE, - } - ) - for tex in [ - "i^2 = j^2 = k^2 = -1", - "ij = -ji = k", - "ki = -ik = j", - "jk = -kj = i", - ] - ]) - defining_products.arrange(DOWN) - defining_products.next_to(self.students, UP, LARGE_BUFF) - def_rect = SurroundingRectangle(defining_products) - - self.play( - LaggedStartMap(FadeInFromDown, defining_products), - self.get_student_changes(*3 * ["confused"]), - self.teacher.change, "raise_right_hand", - ) - self.play(ShowCreation(def_rect)) - self.play( - Write(equation, run_time=4, lag_ratio=0.2), - self.get_student_changes( - "horrified", "pleading", "sick", - equation - ), - self.teacher.change, "erm", equation, - ) - self.blink() - self.look_at(equation.get_corner(UL)) - self.blink() - self.look_at(equation.get_corner(UR)) - self.play(self.teacher.change, "sassy", equation) - self.wait(2) - self.change_all_student_modes("pondering") - self.look_at(equation) - self.wait() - self.play(self.teacher.change, "thinking", equation) - self.wait(8) - - -class ProblemsWhereComplexNumbersArise(Scene): - def construct(self): - text = "Problems where complex numbers are surprisingly useful" - title = TextMobject(*text.split(" ")) - title.to_edge(UP) - title.set_color(BLUE) - underline = Line(LEFT, RIGHT) - underline.set_width(title.get_width() + 0.5) - underline.next_to(title, DOWN) - - problems = VGroup( - TextMobject( - "Integer solutions to\\\\ $a^2 + b^2 = c^2$", - alignment="" - ), - TextMobject( - "Understanding\\\\", - "$1 - \\frac{1}{3} + \\frac{1}{5} - \\frac{1}{7} + \\cdots" + - "=\\frac{\\pi}{4}$", - alignment="", - ), - TextMobject("Frequency analysis") - ) - problems.arrange( - DOWN, buff=LARGE_BUFF, aligned_edge=LEFT - ) - for problem in problems: - problems.add(Dot().next_to(problem[0], LEFT)) - problems.next_to(underline, DOWN, buff=MED_LARGE_BUFF) - problems.to_edge(LEFT) - v_dots = TexMobject("\\vdots") - v_dots.scale(2) - v_dots.next_to(problems, DOWN, aligned_edge=LEFT) - - self.add(problems, v_dots) - self.play( - ShowCreation(underline), - LaggedStartMap(FadeInFromDown, title, lag_ratio=0.5), - run_time=3 - ) - self.wait() - - -class WalkThroughComplexMultiplication(ShowComplexMultiplicationExamples): - CONFIG = { - "z": complex(2, 3), - "w": complex(1, -1), - "z_color": YELLOW, - "w_color": PINK, - "product_color": RED, - } - - def construct(self): - self.add_planes(include_title=False) - self.introduce_z_and_w() - self.show_action_on_all_complex_numbers() - - def introduce_z_and_w(self): - # Tolerating code repetition here in case I want - # to specialize behavior for z or w... - plane = self.plane - origin = plane.number_to_point(0) - - z_point = plane.number_to_point(self.z) - z_dot = Dot(z_point) - z_line = Line(origin, z_point) - z_label = VGroup( - TexMobject("z="), - DecimalNumber(self.z, num_decimal_places=0) - ) - z_label.arrange( - RIGHT, buff=SMALL_BUFF, - ) - z_label.next_to(z_dot, UP, buff=SMALL_BUFF) - z_label.set_color(self.z_color) - z_label.add_background_rectangle() - VGroup(z_line, z_dot).set_color(self.z_color) - - w_point = plane.number_to_point(self.w) - w_dot = Dot(w_point) - w_line = Line(origin, w_point) - w_label = VGroup( - TexMobject("w="), - DecimalNumber(self.w, num_decimal_places=0) - ) - w_label.arrange(RIGHT, buff=SMALL_BUFF) - w_label.next_to(w_dot, DOWN, buff=SMALL_BUFF) - w_label.set_color(self.w_color) - w_label.add_background_rectangle() - VGroup(w_line, w_dot).set_color(self.w_color) - - VGroup( - z_label[1], w_label[1] - ).shift(0.25 * SMALL_BUFF * DOWN) - - product = TexMobject("z", "\\cdot", "w") - z_sym, w_sym = product[0], product[2] - z_sym.set_color(self.z_color) - w_sym.set_color(self.w_color) - product.scale(2) - product.to_corner(UL) - product.add_background_rectangle() - - self.add( - z_line, z_label, - w_line, w_label, - ) - self.play(LaggedStartMap( - FadeInFromLarge, VGroup(z_dot, w_dot), - lambda m: (m, 5), - lag_ratio=0.8, - run_time=1.5 - )) - self.play( - ReplacementTransform(z_label[1][0].copy(), z_sym) - ) - self.add(product[:-1]) - self.play( - ReplacementTransform(w_label[1][0].copy(), w_sym), - FadeInFrom(product[2], LEFT), - FadeIn(product[0]), - ) - self.wait() - - self.set_variables_as_attrs( - product, - z_point, w_point, - z_dot, w_dot, - z_line, w_line, - ) - - def show_action_on_all_complex_numbers(self): - plane = self.plane - plane.save_state() - origin = plane.number_to_point(0) - z = self.z - angle = np.log(z).imag - product_tex = self.product[1:] - z_sym, cdot, w_sym = product_tex - - product = self.z * self.w - product_point = plane.number_to_point(product) - product_dot = Dot(product_point) - product_line = Line(origin, product_point) - for mob in product_line, product_dot: - mob.set_color(self.product_color) - - rect = SurroundingRectangle(VGroup(z_sym, cdot)) - rect.set_fill(BLACK, opacity=1) - func_words = TextMobject("Function on the plane") - func_words.next_to( - rect, DOWN, - buff=MED_SMALL_BUFF, - aligned_edge=LEFT, - ) - func_words.set_color(self.z_color) - - sparkly_plane = VGroup() - for line in plane.family_members_with_points(): - if self.camera.is_in_frame(line): - for piece in line.get_pieces(10): - p1, p2 = piece.get_pieces(2) - p1.rotate(PI) - pair = VGroup(p1, p2) - pair.scale(0.3) - sparkly_plane.add(pair) - sparkly_plane.sort( - lambda p: 0.1 * get_norm(p) + random.random() - ) - sparkly_plane.set_color_by_gradient(YELLOW, RED, PINK, BLUE) - sparkly_plane.set_stroke(width=4) - - pin = PushPin() - pin.move_to(origin, DR) - - one_dot = Dot(plane.number_to_point(1)) - one_dot.set_fill(WHITE) - one_dot.set_stroke(BLACK, 1) - hand = Hand() - hand.move_to(plane.number_to_point(1), LEFT) - - zero_eq = TexMobject("z \\cdot 0 = 0") - one_eq = TexMobject("z \\cdot 1 = z") - equations = VGroup(zero_eq, one_eq) - equations.arrange(DOWN) - equations.scale(1.5) - for eq in equations: - eq.add_background_rectangle() - equations.next_to(func_words, DOWN) - equations.to_edge(LEFT) - - product_label = VGroup( - TexMobject("z \\cdot w ="), - DecimalNumber(product, num_decimal_places=0) - ) - product_label.arrange(RIGHT) - product_label[0].shift(0.025 * DOWN) - product_label.next_to(product_dot, UP, SMALL_BUFF) - product_label.add_background_rectangle() - - big_rect = Rectangle( - height=4, - width=6, - fill_color=BLACK, - fill_opacity=0.9, - stroke_width=0, - ) - big_rect.to_corner(UL, buff=0) - - self.add(big_rect, product_tex, rect, z_sym, cdot) - self.play( - FadeIn(big_rect), - ShowCreation(rect), - Write(func_words), - run_time=1 - ) - self.play( - ReplacementTransform( - self.w_line.copy(), product_line, - ), - ReplacementTransform( - self.w_dot.copy(), product_dot, - ), - path_arc=angle, - run_time=3 - ) - self.wait() - self.play(FadeOut(VGroup(product_line, product_dot))) - self.play(LaggedStartMap( - ShowCreationThenDestruction, sparkly_plane, - lag_ratio=0.5, - run_time=2 - )) - self.play( - plane.apply_complex_function, lambda w: z * w, - Transform(self.w_line, product_line), - Transform(self.w_dot, product_dot), - path_arc=angle, - run_time=9, - rate_func=lambda t: there_and_back_with_pause(t, 2 / 9) - ) - self.wait() - self.play(FadeInFrom(pin, UL)) - self.play(Write(zero_eq)) - self.play( - FadeInFromLarge(one_dot), - FadeInFrom(hand, UR) - ) - self.play(Write(one_eq)) - self.wait() - self.play( - plane.apply_complex_function, lambda w: z * w, - ReplacementTransform(self.w_line.copy(), product_line), - ReplacementTransform(self.w_dot.copy(), product_dot), - one_dot.move_to, self.z_point, - hand.move_to, self.z_point, LEFT, - path_arc=angle, - run_time=4, - ) - self.play(Write(product_label)) - - -class ShowUnitCircleActions(ShowComplexMultiplicationExamples): - CONFIG = { - "random_seed": 0, - "plane_config": { - "secondary_line_ratio": 0, - } - } - - def construct(self): - self.add_planes(include_title=False) - self.show_unit_circle_actions() - - def show_unit_circle_actions(self): - plane = self.plane - origin = plane.number_to_point(0) - one_point = plane.number_to_point(1) - one_dot = Dot(one_point) - one_dot.set_fill(WHITE) - one_dot.set_stroke(BLACK, 1) - plane.add(one_dot) - - pin = PushPin() - pin.move_to(origin, DR) - hand = Hand() - update_hand = UpdateFromFunc( - hand, lambda m: m.move_to(one_dot.get_center(), LEFT) - ) - - circle = Circle( - color=YELLOW, - radius=get_norm(one_point - origin) - ) - - self.add(circle) - self.add(pin, one_dot) - self.add_foreground_mobjects(hand) - - title = TextMobject( - "Numbers on the unit circle", - "$\\rightarrow$", "pure rotation." - ) - title.set_width(FRAME_WIDTH - 1) - title.to_edge(UP, buff=MED_SMALL_BUFF) - title.add_background_rectangle(buff=SMALL_BUFF) - self.add_foreground_mobjects(title) - self.background_plane.coordinate_labels.submobjects.pop(-1) - - n_angles = 12 - angles = list(np.linspace(-PI, PI, n_angles + 2)[1:-1]) - random.shuffle(angles) - - for angle in angles: - plane.save_state() - temp_plane = plane.copy() - - z = np.exp(complex(0, angle)) - if angle is angles[0]: - z_label = DecimalNumber( - z, num_decimal_places=2, - ) - z_label.set_stroke(BLACK, width=11, background=True) - z_label_rect = BackgroundRectangle(z_label) - z_label_rect.set_fill(opacity=0) - z_point = plane.number_to_point(z) - z_arrow = Arrow(2.5 * z_point, z_point, buff=SMALL_BUFF) - z_dot = Dot(z_point) - z_label_start_center = z_label.get_center() - z_label.next_to( - z_arrow.get_start(), - np.sign(z_arrow.get_start()[1]) * UP, - ) - z_label_end_center = z_label.get_center() - z_group = VGroup(z_arrow, z_dot, z_label) - z_group.set_color(GREEN) - z_group.add_to_back(z_label_rect) - z_arrow.set_stroke(BLACK, 1) - z_dot.set_stroke(BLACK, 1) - - if angle is angles[0]: - self.play( - FadeInFromDown(z_label_rect), - FadeInFromDown(z_label), - GrowArrow(z_arrow), - FadeInFromLarge(z_dot), - ) - else: - alpha_tracker = ValueTracker(0) - self.play( - ReplacementTransform(old_z_dot, z_dot), - ReplacementTransform(old_z_arrow, z_arrow), - UpdateFromFunc( - z_label_rect, - lambda m: m.replace(z_label) - ), - ChangeDecimalToValue( - z_label, z, - position_update_func=lambda m: m.move_to( - interpolate( - z_label_start_center, - z_label_end_center, - alpha_tracker.get_value() - ) - ) - ), - alpha_tracker.set_value, 1, - # hand.move_to, one_point, LEFT - ) - old_z_dot = z_dot - old_z_arrow = z_arrow - VGroup(old_z_arrow, old_z_dot) - self.play( - Rotate(plane, angle, run_time=2), - update_hand, - Animation(z_group), - ) - self.wait() - self.add(temp_plane, z_group) - self.play( - FadeOut(plane), - FadeOut(hand), - FadeIn(temp_plane), - ) - plane.restore() - self.remove(temp_plane) - self.add(plane, *z_group) - - -class IfYouNeedAWarmUp(TeacherStudentsScene): - def construct(self): - screen = self.screen - screen.set_height(4) - screen.to_corner(UL) - self.add(screen) - - self.teacher_says( - "If you need \\\\ a warm up", - bubble_kwargs={"width": 3.5, "height": 3}, - ) - self.change_all_student_modes( - "pondering", look_at_arg=screen, - ) - self.wait(3) - self.play(RemovePiCreatureBubble(self.teacher)) - self.wait(3) - - -class LinusThinksAboutStretching(Scene): - def construct(self): - linus = Linus() - top_line = NumberLine(color=GREY) - top_line.to_edge(UP) - top_line.add_numbers() - - linus.move_to(3 * LEFT + DOWN) - - self.add(linus, top_line) - - scalars = [3, 0.5, 2] - - for scalar in scalars: - lower_line = NumberLine( - x_min=-14, - x_max=14, - color=BLUE - ) - lower_line.next_to(top_line, DOWN, MED_LARGE_BUFF) - lower_line.numbers = lower_line.get_number_mobjects() - for number in lower_line.numbers: - number.add_updater(lambda m: m.next_to( - lower_line.number_to_point(m.get_value()), - DOWN, MED_SMALL_BUFF, - )) - lower_line.save_state() - lower_line.numbers.save_state() - self.add(lower_line, *lower_line.numbers) - - words = TextMobject("Multiply by {}".format(scalar)) - words.next_to(lower_line.numbers, DOWN) - - self.play( - ApplyMethod( - lower_line.stretch, scalar, 0, - run_time=2 - ), - # LaggedStartMap(FadeIn, words, run_time=1), - FadeInFromLarge(words, 1.0 / scalar), - linus.look_at, top_line.number_to_point(scalar) - ) - self.play(Blink(linus)) - self.play( - FadeOut(lower_line), - FadeOut(lower_line.numbers), - FadeOut(words), - FadeIn(lower_line.saved_state, remover=True), - FadeIn(lower_line.numbers.saved_state, remover=True), - linus.look_at, top_line.number_to_point(0) - ) - self.play(linus.change, "confused", DOWN + RIGHT) - self.wait(2) - self.play(Blink(linus)) - self.wait(2) - - -class LinusReactions(Scene): - def construct(self): - linus = Linus() - for mode in "confused", "sad", "erm", "angry", "pleading": - self.play(linus.change, mode, 2 * RIGHT) - self.wait() - self.play(Blink(linus)) - self.wait() - - -class OneDegreeOfFreedomForRotation(Scene): - def construct(self): - circle = CheckeredCircle(radius=2, stroke_width=10) - r_line = Line(circle.get_center(), circle.get_right()) - moving_r_line = r_line.copy() - right_dot = Dot(color=WHITE) - right_dot.move_to(circle.get_right()) - circle.add(moving_r_line, right_dot) - center_dot = Dot(color=WHITE) - - def get_angle(): - return moving_r_line.get_angle() % TAU - - angle_label = Integer(0, unit="^\\circ") - angle_label.scale(2) - angle_label.add_updater( - lambda m: m.set_value(get_angle() / DEGREES) - ) - angle_label.add_updater( - lambda m: m.next_to(circle, UP, MED_LARGE_BUFF) - ) - - def get_arc(): - return Arc( - angle=get_angle(), - radius=0.5, - color=LIGHT_GREY, - ) - - arc = get_arc() - arc.add_updater(lambda m: m.become(get_arc())) - - self.add(circle, center_dot, r_line, angle_label, arc) - angles = IntroduceStereographicProjection.CONFIG.get( - "example_angles" - ) - for angle in angles: - self.play(Rotate(circle, angle, run_time=4)) - self.wait() - - -class StereographicProjectionTitle(Scene): - def construct(self): - title = TextMobject("Stereographic projection") - final_title = title.copy() - final_title.set_width(10) - final_title.to_edge(UP) - - title.rotate(-90 * DEGREES) - title.next_to(RIGHT, RIGHT, SMALL_BUFF) - title.apply_complex_function(np.exp) - title.rotate(90 * DEGREES) - title.set_height(6) - title.to_edge(UP) - - self.play(Write(title)) - self.play(Transform(title, final_title, run_time=2)) - self.wait() - - -class IntroduceStereographicProjection(MovingCameraScene): - CONFIG = { - "n_sample_points": 16, - "circle_config": { - "n_pieces": 16, - "radius": 2, - "stroke_width": 7, - }, - "example_angles": [ - 30 * DEGREES, - 120 * DEGREES, - 240 * DEGREES, - 80 * DEGREES, - -60 * DEGREES, - 135 * DEGREES, - ] - } - - def construct(self): - self.setup_plane() - self.draw_lines() - self.describe_individual_points() - self.remind_that_most_points_are_not_projected() - - def setup_plane(self): - self.plane = self.get_plane() - self.circle = self.get_circle() - self.circle_shadow = self.get_circle_shadow() - - self.add(self.plane) - self.add(self.circle_shadow) - self.add(self.circle) - - def draw_lines(self): - plane = self.plane - circle = self.circle - circle.save_state() - circle.generate_target() - self.project_mobject(circle.target) - - circle_points = self.get_sample_circle_points() - dots = VGroup(*[Dot(p) for p in circle_points]) - dots.set_sheen(-0.2, DR) - dots.set_stroke(DARK_GREY, 2, background=True) - arrows = VGroup() - for dot in dots: - dot.scale(0.75) - dot.generate_target() - dot.target.move_to( - self.project(dot.get_center()) - ) - arrow = Arrow( - dot.get_center(), - dot.target.get_center(), - ) - arrows.add(arrow) - neg_one_point = plane.number_to_point(-1) - neg_one_dot = Dot(neg_one_point) - neg_one_dot.set_fill(YELLOW) - - lines = self.get_lines() - - special_index = self.n_sample_points // 2 + 1 - line = lines[special_index] - dot = dots[special_index] - arrow = arrows[special_index] - dot_target_outline = dot.target.copy() - dot_target_outline.set_stroke(RED, 2) - dot_target_outline.set_fill(opacity=0) - dot_target_outline.scale(1.5) - - v_line = Line(UP, DOWN) - v_line.set_height(FRAME_HEIGHT) - v_line.set_stroke(RED, 5) - - self.play(LaggedStartMap(FadeInFromLarge, dots)) - self.play(FadeInFromLarge(neg_one_dot)) - self.add(lines, neg_one_dot, dots) - self.play(LaggedStartMap(ShowCreation, lines)) - self.wait() - self.play( - lines.set_stroke, {"width": 0.5}, - line.set_stroke, {"width": 4}, - ) - self.play(ShowCreation(dot_target_outline)) - self.play(ShowCreationThenDestruction(v_line)) - self.play(MoveToTarget(dot)) - self.wait() - self.play( - lines.set_stroke, {"width": 1}, - FadeOut(dot_target_outline), - MoveToTarget(circle), - *map(MoveToTarget, dots), - run_time=2, - ) - self.wait() - - self.lines = lines - self.dots = dots - - def describe_individual_points(self): - plane = self.plane - one_point, zero_point, i_point, neg_i_point, neg_one_point = [ - plane.number_to_point(n) - for n in [1, 0, complex(0, 1), complex(0, -1), -1] - ] - i_pin = PushPin() - i_pin.pin_to(i_point) - neg_i_pin = PushPin() - neg_i_pin.pin_to(neg_i_point) - - dot = Dot() - dot.set_stroke(RED, 3) - dot.set_fill(opacity=0) - dot.scale(1.5) - dot.move_to(one_point) - - arc1 = Arc(angle=TAU / 4, radius=2) - arc2 = Arc( - angle=85 * DEGREES, radius=2, - start_angle=TAU / 4, - ) - arc3 = Arc( - angle=-85 * DEGREES, radius=2, - start_angle=-TAU / 4, - ) - VGroup(arc1, arc2, arc3).set_stroke(RED) - - frame = self.camera_frame - frame_height_tracker = ValueTracker(frame.get_height()) - frame_height_growth = frame_height_tracker.add_updater( - lambda m, dt: m.set_value(m.get_value + 0.5 * dt) - ) - - neg_one_tangent = VGroup( - Line(ORIGIN, UP), - Line(ORIGIN, DOWN), - ) - neg_one_tangent.set_height(25) - neg_one_tangent.set_stroke(YELLOW, 5) - neg_one_tangent.move_to(neg_one_point) - - self.play(ShowCreation(dot)) - self.wait() - self.play(dot.move_to, zero_point) - self.wait() - dot.move_to(i_point) - self.play(ShowCreation(dot)) - self.play(FadeInFrom(i_pin, UL)) - self.wait() - self.play( - dot.move_to, neg_i_point, - path_arc=-60 * DEGREES - ) - self.play(FadeInFrom(neg_i_pin, UL)) - self.wait() - self.play( - dot.move_to, one_point, - path_arc=-60 * DEGREES - ) - frame.add_updater( - lambda m: m.set_height(frame_height_tracker.get_value()) - ) - - triplets = [ - (arc1, i_point, TAU / 4), - (arc2, neg_one_point, TAU / 4), - (arc3, neg_one_point, -TAU / 4), - ] - for arc, point, path_arc in triplets: - self.play( - ShowCreation(arc), - dot.move_to, point, path_arc=path_arc, - run_time=2 - ) - self.wait() - self.play( - ApplyFunction(self.project_mobject, arc, run_time=2) - ) - self.wait() - self.play(FadeOut(arc)) - self.wait() - if arc is arc1: - self.add(frame, frame_height_growth) - elif arc is arc2: - self.play(dot.move_to, neg_i_point) - self.wait(2) - self.play(*map(ShowCreation, neg_one_tangent)) - self.wait() - self.play(FadeOut(neg_one_tangent)) - self.wait(2) - frame.clear_updaters() - self.play( - frame.set_height, FRAME_HEIGHT, - self.lines.set_stroke, {"width": 0.5}, - FadeOut(self.dots), - FadeOut(dot), - run_time=2, - ) - - def remind_that_most_points_are_not_projected(self): - plane = self.plane - circle = self.circle - - sample_values = [0, complex(1, 1), complex(-2, -1)] - sample_points = [ - plane.number_to_point(value) - for value in sample_values - ] - sample_dots = VGroup(*[Dot(p) for p in sample_points]) - sample_dots.set_fill(GREEN) - - self.play( - FadeOut(self.lines), - Restore(circle), - ) - - for value, dot in zip(sample_values, sample_dots): - cross = Cross(dot) - cross.scale(2) - label = Integer(value) - if value is sample_values[1]: - label.next_to(dot, UL, SMALL_BUFF) - else: - label.next_to(dot, UR, SMALL_BUFF) - self.play( - FadeInFromLarge(dot, 3), - FadeInFromDown(label) - ) - self.play(ShowCreation(cross)) - self.play(*map(FadeOut, [dot, cross, label])) - self.wait() - self.play( - FadeIn(self.lines), - MoveToTarget(circle, run_time=2), - ) - self.wait() - - # Helpers - def get_plane(self): - plane = ComplexPlane( - unit_size=2, - color=GREY, - secondary_color=DARK_GREY, - x_radius=FRAME_WIDTH, - y_radius=FRAME_HEIGHT, - stroke_width=2, - ) - plane.add_coordinates() - return plane - - def get_circle(self): - circle = CheckeredCircle( - **self.circle_config - ) - circle.set_stroke(width=7) - return circle - - def get_circle_shadow(self): - circle_shadow = CheckeredCircle( - **self.circle_config - ) - circle_shadow.set_stroke(opacity=0.65) - return circle_shadow - - def get_sample_circle_points(self): - plane = self.plane - n = self.n_sample_points - rotater = 1 - if hasattr(self, "circle_shadow"): - point = self.circle_shadow[0].points[0] - rotater = complex(*point[:2]) - rotater /= abs(rotater) - numbers = [ - rotater * np.exp(complex(0, TAU * x / n)) - for x in range(-(n // 2) + 1, n // 2) - ] - return [ - plane.number_to_point(number) - for number in numbers - ] - - def get_lines(self): - plane = self.plane - neg_one_point = plane.number_to_point(-1) - circle_points = self.get_sample_circle_points() - - lines = VGroup(*[ - Line(neg_one_point, point) - for point in circle_points - ]) - for line in lines: - length = line.get_length() - line.scale(20 / length, about_point=neg_one_point) - line.set_stroke(YELLOW, np.clip(length, 0, 1)) - return lines - - def project(self, point): - return stereo_project_point(point, axis=0, r=2) - - def project_mobject(self, mobject): - return stereo_project(mobject, axis=0, r=2, outer_r=6) - - -class IntroduceStereographicProjectionLinusView(IntroduceStereographicProjection): - def construct(self): - self.describe_individual_points() - self.point_at_infinity() - self.show_90_degree_rotation() - self.talk_through_90_degree_rotation() - self.show_four_rotations() - self.show_example_angles() - - def describe_individual_points(self): - plane = self.plane = self.get_plane() - circle = self.circle = self.get_circle() - linus = self.linus = self.get_linus() - - angles = np.arange(-135, 180, 45) * DEGREES - sample_numbers = [ - np.exp(complex(0, angle)) - for angle in angles - ] - sample_points = [ - plane.number_to_point(number) - for number in sample_numbers - ] - projected_sample_points = [ - self.project(point) - for point in sample_points - ] - dots = VGroup(*[Dot() for x in range(8)]) - dots.set_fill(WHITE) - dots.set_stroke(BLACK, 1) - - def generate_dot_updater(circle_piece): - return lambda d: d.move_to(circle_piece.points[0]) - - for dot, piece in zip(dots, circle[::(len(circle) // 8)]): - dot.add_updater(generate_dot_updater(piece)) - - stot = "(\\sqrt{2} / 2)" - labels_tex = [ - "-{}-{}i".format(stot, stot), - "-i", - "{}-{}i".format(stot, stot), - "1", - "{}+{}i".format(stot, stot), - "i", - "-{}+{}i".format(stot, stot), - ] - labels = VGroup(*[TexMobject(tex) for tex in labels_tex]) - vects = it.cycle([RIGHT, RIGHT]) - arrows = VGroup() - for label, point, vect in zip(labels, projected_sample_points, vects): - arrow = Arrow(vect, ORIGIN) - arrow.next_to(point, vect, 2 * SMALL_BUFF) - arrows.add(arrow) - label.set_stroke(width=0, background=True) - if stot in label.get_tex_string(): - label.set_height(0.5) - else: - label.set_height(0.5) - label.set_stroke(WHITE, 2, background=True) - label.next_to(arrow, vect, SMALL_BUFF) - - frame = self.camera_frame - frame.set_height(12) - - self.add(linus) - self.add(circle, *dots) - self.play( - ApplyFunction(self.project_mobject, circle), - run_time=2 - ) - self.play(linus.change, "confused") - self.wait() - for i in [1, 0]: - self.play( - LaggedStartMap(GrowArrow, arrows[i::2]), - LaggedStartMap(Write, labels[i::2]) - ) - self.play(Blink(linus)) - - self.dots = dots - - def point_at_infinity(self): - circle = self.circle - linus = self.linus - - label = TextMobject( - "$-1$ is \\\\ at $\\pm \\infty$" - ) - label.scale(1.5) - label.next_to(circle, LEFT, buff=1.25) - arrows = VGroup(*[ - Vector(3 * v + 0.0 * RIGHT).next_to(label, v, buff=MED_LARGE_BUFF) - for v in [UP, DOWN] - ]) - arrows.set_color(YELLOW) - - self.play( - Write(label), - linus.change, "awe", label, - *map(GrowArrow, arrows) - ) - - self.neg_one_label = VGroup(label, arrows) - - def show_90_degree_rotation(self): - angle_tracker = ValueTracker(0) - circle = self.circle - linus = self.linus - hand = Hand() - hand.flip() - one_dot = self.dots[0] - hand.add_updater( - lambda h: h.move_to(one_dot.get_center(), RIGHT) - ) - - def update_circle(circle): - angle = angle_tracker.get_value() - new_circle = self.get_circle() - new_circle.rotate(angle) - self.project_mobject(new_circle) - circle.become(new_circle) - - circle.add_updater(update_circle) - - self.play( - FadeIn(hand), - one_dot.set_fill, RED, - ) - for angle in 90 * DEGREES, 0: - self.play( - ApplyMethod( - angle_tracker.set_value, angle, - run_time=3, - ), - linus.change, "confused", hand - ) - self.wait() - self.play(Blink(linus)) - - self.hand = hand - self.angle_tracker = angle_tracker - - def talk_through_90_degree_rotation(self): - linus = self.linus - dots = self.dots - one_dot = dots[0] - i_dot = dots[2] - neg_i_dot = dots[-2] - - kwargs1 = { - "path_arc": -90 * DEGREES, - "buff": SMALL_BUFF, - } - kwargs2 = dict(kwargs1) - kwargs2["path_arc"] = -40 * DEGREES - arrows = VGroup( - Arrow(one_dot, i_dot, **kwargs1), - Arrow(i_dot, 6 * UP + LEFT, **kwargs2), - Arrow(6 * DOWN + LEFT, neg_i_dot, **kwargs2), - Arrow(neg_i_dot, one_dot, **kwargs1) - ) - arrows.set_stroke(WHITE, 3) - one_to_i, i_to_neg_1, neg_one_to_neg_i, neg_i_to_one = arrows - - for arrow in arrows: - self.play( - ShowCreation(arrow), - linus.look_at, arrow - ) - self.wait(2) - - self.arrows = arrows - - def show_four_rotations(self): - angle_tracker = self.angle_tracker - linus = self.linus - hand = self.hand - linus.add_updater(lambda l: l.look_at(hand)) - linus.add_updater(lambda l: l.eyes.next_to(l.body, UP, 0)) - - for angle in np.arange(TAU / 4, 5 * TAU / 4, TAU / 4): - self.play( - ApplyMethod( - angle_tracker.set_value, angle, - run_time=3, - ), - ) - self.wait() - self.play(FadeOut(self.arrows)) - - def show_example_angles(self): - angle_tracker = self.angle_tracker - angle_tracker.set_value(0) - - for angle in self.example_angles: - self.play( - ApplyMethod( - angle_tracker.set_value, angle, - run_time=4, - ), - ) - self.wait() - - # - def get_linus(self): - linus = Linus() - linus.move_to(3 * RIGHT) - linus.to_edge(DOWN) - linus.look_at(ORIGIN) - return linus - - -class ShowRotationUnderStereographicProjection(IntroduceStereographicProjection): - def construct(self): - self.setup_plane() - self.apply_projection() - self.show_90_degree_rotation() - self.talk_through_90_degree_rotation() - self.show_four_rotations() - self.show_example_angles() - - def apply_projection(self): - plane = self.plane - circle = self.circle - neg_one_point = plane.number_to_point(-1) - neg_one_dot = Dot(neg_one_point) - neg_one_dot.set_fill(YELLOW) - - lines = always_redraw(self.get_lines) - - def generate_dot_updater(circle_piece): - return lambda d: d.move_to(circle_piece.points[0]) - - for circ, color in [(self.circle_shadow, RED), (self.circle, WHITE)]: - for piece in circ[::(len(circ) // 8)]: - dot = Dot(color=color) - dot.set_fill(opacity=circ.get_stroke_opacity()) - dot.add_updater(generate_dot_updater(piece)) - self.add(dot) - - self.add(lines, neg_one_dot) - self.play(*map(ShowCreation, lines)) - self.play( - ApplyFunction(self.project_mobject, circle), - lines.set_stroke, {"width": 0.5}, - run_time=2 - ) - self.play( - self.camera_frame.set_height, 12, - run_time=2 - ) - self.wait() - - def show_90_degree_rotation(self): - circle = self.circle - circle_shadow = self.circle_shadow - - def get_rotated_one_point(): - return circle_shadow[0].points[0] - - def get_angle(): - return angle_of_vector(get_rotated_one_point()) - - self.get_angle = get_angle - - one_dot = Dot(color=RED) - one_dot.add_updater( - lambda m: m.move_to(get_rotated_one_point()) - ) - hand = Hand() - hand.move_to(one_dot.get_center(), LEFT) - - def update_circle(circle): - new_circle = self.get_circle() - new_circle.rotate(get_angle()) - self.project_mobject(new_circle) - circle.become(new_circle) - - circle.add_updater(update_circle) - - self.add(one_dot, hand) - hand.add_updater( - lambda h: h.move_to(one_dot.get_center(), LEFT) - ) - self.play( - FadeInFrom(hand, RIGHT), - FadeInFromLarge(one_dot, 3), - ) - for angle in 90 * DEGREES, -90 * DEGREES: - self.play( - Rotate(circle_shadow, angle, run_time=3), - ) - self.wait(2) - - def talk_through_90_degree_rotation(self): - plane = self.plane - points = [ - plane.number_to_point(z) - for z in [1, complex(0, 1), -1, complex(0, -1)] - ] - arrows = VGroup() - for p1, p2 in adjacent_pairs(points): - arrow = Arrow( - p1, p2, path_arc=180 * DEGREES, - ) - arrow.set_stroke(LIGHT_GREY, width=3) - arrow.tip.set_fill(LIGHT_GREY) - arrows.add(arrow) - - for arrow in arrows: - self.play(ShowCreation(arrow)) - self.wait(2) - - self.arrows = arrows - - def show_four_rotations(self): - circle_shadow = self.circle_shadow - for x in range(4): - self.play( - Rotate(circle_shadow, TAU / 4, run_time=3) - ) - self.wait() - self.play(FadeOut(self.arrows)) - - def show_example_angles(self): - circle_shadow = self.circle_shadow - angle_label = Integer(0, unit="^\\circ") - angle_label.scale(1.5) - angle_label.next_to( - circle_shadow.get_top(), UR, - ) - - self.play(FadeInFromDown(angle_label)) - self.add(angle_label) - for angle in self.example_angles: - d_angle = angle - self.get_angle() - self.play( - Rotate(circle_shadow, d_angle), - ChangingDecimal( - angle_label, - lambda a: (self.get_angle() % TAU) / DEGREES - ), - run_time=4 - ) - self.wait() - - -class WriteITimesW(Scene): - def construct(self): - mob = TexMobject("i", "\\cdot", "w") - mob[0].set_color(GREEN) - mob.scale(3) - self.play(Write(mob)) - self.wait() - - -class IntroduceFelix(PiCreatureScene, SpecialThreeDScene): - def setup(self): - PiCreatureScene.setup(self) - SpecialThreeDScene.setup(self) - - def construct(self): - self.introduce_felix() - self.add_plane() - self.show_in_three_d() - - def introduce_felix(self): - felix = self.felix = self.pi_creature - - arrow = Vector(DL, color=WHITE) - arrow.next_to(felix, UR) - - label = TextMobject("Felix the Flatlander") - label.next_to(arrow.get_start(), UP) - - self.add(felix) - self.play( - felix.change, "wave_1", label, - Write(label), - GrowArrow(arrow), - ) - self.play(Blink(felix)) - self.play(felix.change, "thinking", label) - - self.to_fade = VGroup(label, arrow) - - def add_plane(self): - plane = NumberPlane(y_radius=10) - axes = self.get_axes() - to_fade = self.to_fade - felix = self.felix - - self.add(axes, plane, felix) - self.play( - ShowCreation(axes), - ShowCreation(plane), - FadeOut(to_fade), - ) - self.wait() - - self.plane = plane - self.axes = axes - - def show_in_three_d(self): - felix = self.pi_creature - plane = self.plane - axes = self.axes - - # back_plane = Rectangle().replace(plane, stretch=True) - # back_plane.shade_in_3d = True - # back_plane.set_fill(LIGHT_GREY, opacity=0.5) - # back_plane.set_sheen(1, UL) - # back_plane.shift(SMALL_BUFF * IN) - # back_plane.set_stroke(width=0) - # back_plane = ParametricSurface( - # lambda u, v: u * RIGHT + v * UP - # ) - # back_plane.replace(plane, stretch=True) - # back_plane.set_stroke(width=0) - # back_plane.set_fill(LIGHT_GREY, opacity=0.5) - - sphere = self.get_sphere() - # sphere.set_fill(BLUE_E, 0.5) - - self.move_camera( - phi=70 * DEGREES, - theta=-110 * DEGREES, - added_anims=[FadeOut(plane)], - run_time=2 - ) - self.begin_ambient_camera_rotation() - self.add(axes, sphere) - self.play( - Write(sphere), - felix.change, "confused" - ) - self.wait() - - axis_angle_pairs = [ - (RIGHT, 90 * DEGREES), - (OUT, 45 * DEGREES), - (UR + OUT, 120 * DEGREES), - (RIGHT, 90 * DEGREES), - ] - for axis, angle in axis_angle_pairs: - self.play(Rotate( - sphere, angle=angle, axis=axis, - run_time=2, - )) - self.wait(2) - - # - def create_pi_creature(self): - return Felix().move_to(4 * LEFT + 2 * DOWN) - - -class IntroduceThreeDNumbers(SpecialThreeDScene): - CONFIG = { - "camera_config": { - "exponential_projection": False, - } - } - - def construct(self): - self.add_third_axis() - self.reorient_axes() - self.show_example_number() - - def add_third_axis(self): - plane = ComplexPlane( - y_radius=FRAME_WIDTH / 4, - unit_size=2, - secondary_line_ratio=1, - ) - plane.add_coordinates() - title = TextMobject("Complex Plane") - title.scale(1.8) - title.add_background_rectangle() - title.to_corner(UL, buff=MED_SMALL_BUFF) - - real_line = Line(LEFT, RIGHT).set_width(FRAME_WIDTH) - imag_line = Line(DOWN, UP).set_height(FRAME_HEIGHT) - real_line.set_color(YELLOW) - imag_line.set_color(RED) - - for label in plane.coordinate_labels: - label.remove(label.background_rectangle) - label.shift(SMALL_BUFF * IN) - self.add_fixed_orientation_mobjects(label) - reals = plane.coordinate_labels[:7] - imags = plane.coordinate_labels[7:] - - self.add(plane, title) - for line, group in (real_line, reals), (imag_line, imags): - line.set_stroke(width=5) - self.play( - ShowCreationThenDestruction(line), - LaggedStartMap( - Indicate, group, - rate_func=there_and_back, - color=line.get_color(), - ), - run_time=2, - ) - - self.plane = plane - self.title = title - - def reorient_axes(self): - z_axis = NumberLine(unit_size=2) - z_axis.rotate(90 * DEGREES, axis=DOWN) - z_axis.rotate(90 * DEGREES, axis=OUT) - z_axis.set_color(WHITE) - z_axis_top = Line( - z_axis.number_to_point(0), - z_axis.get_end(), - ) - z_axis_top.match_style(z_axis) - - z_unit_line = Line( - z_axis.number_to_point(0), - z_axis.number_to_point(1), - color=RED, - stroke_width=5 - ) - - j_labels = VGroup( - TexMobject("-2j"), - TexMobject("-j"), - TexMobject("j"), - TexMobject("2j"), - ) - for label, num in zip(j_labels, [-2, -1, 1, 2]): - label.next_to(z_axis.number_to_point(num), RIGHT, MED_SMALL_BUFF) - self.add_fixed_orientation_mobjects(label) - - plane = self.plane - - colored_ = Line(LEFT, RIGHT).set_width(FRAME_WIDTH) - y_line = Line(DOWN, UP).set_height(FRAME_WIDTH) - z_line = Line(IN, OUT).set_depth(FRAME_WIDTH) - colored_.set_stroke(GREEN, 5) - y_line.set_stroke(RED, 5) - z_line.set_stroke(YELLOW, 5) - colored_coord_lines = VGroup(colored_, y_line, z_line) - - coord_lines = VGroup( - plane.axes[0], plane.axes[1], z_axis, - ) - for i1, i2 in [(0, 2), (1, 0), (2, 1)]: - coord_lines[i1].target = coord_lines[i2].copy() - - new_title = TextMobject("Imaginary plane") - new_title.replace(self.title) - new_title.move_to(self.title) - - self.add(z_axis, plane, z_axis_top) - self.move_camera( - phi=70 * DEGREES, - theta=-80 * DEGREES, - added_anims=[ - plane.set_stroke, {"opacity": 0.5}, - ], - run_time=2, - ) - self.begin_ambient_camera_rotation(rate=0.02) - self.wait() - self.play(FadeInFrom(j_labels, IN)) - z_axis.add(j_labels) - self.play( - ShowCreationThenDestruction(z_unit_line), - run_time=2 - ) - self.wait(4) - - group = VGroup(*it.chain(plane.coordinate_labels, j_labels)) - for label in group: - label.generate_target() - axis = np.ones(3) - label.target.rotate_about_origin(-120 * DEGREES, axis=axis) - label.target.rotate(120 * DEGREES, axis=axis) - for y, label in zip([-2, -1, 1, 2], j_labels): - label.target.scale(0.65) - label.target.next_to( - 2 * y * UP, RIGHT, 2 * SMALL_BUFF - ) - self.remove(z_axis_top) - self.play( - LaggedStartMap(MoveToTarget, group, lag_ratio=0.8), - LaggedStartMap(MoveToTarget, coord_lines, lag_ratio=0.8), - FadeOut(self.title), - FadeIn(new_title), - run_time=3 - ) - self.add(z_axis_top) - self.wait(3) - for line, wait in zip(colored_coord_lines, [False, True, True]): - self.play( - ShowCreationThenDestruction(line), - run_time=2 - ) - if wait: - self.wait() - - def show_example_number(self): - x, y, z = coords = 2 * np.array([1.5, -1, 1.25]) - dot = Sphere(radius=0.05) - dot.set_fill(LIGHT_GREY) - dot.move_to(coords) - point_line = Line(ORIGIN, coords) - point_line.set_stroke(WHITE, 1) - - z_line = Line(ORIGIN, z * OUT) - x_line = Line(z_line.get_end(), z_line.get_end() + x * RIGHT) - y_line = Line(x_line.get_end(), x_line.get_end() + y * UP) - - x_line.set_stroke(GREEN, 5) - y_line.set_stroke(RED, 5) - z_line.set_stroke(YELLOW, 5) - lines = VGroup(z_line, x_line, y_line) - - number_label = TexMobject( - str(z / 2), "+", str(x / 2), "i", "+", str(y / 2), "j", - tex_to_color_map={ - str(z / 2): YELLOW, - str(x / 2): GREEN, - str(y / 2): RED, - } - ) - number_label.next_to(ORIGIN, RIGHT, LARGE_BUFF) - number_label.to_edge(UP) - - self.add_fixed_in_frame_mobjects(number_label) - self.play( - ShowCreation(point_line), - FadeInFrom(dot, -coords), - FadeInFromDown(number_label) - ) - self.wait() - for num, line in zip([z, x, y], lines): - tex = number_label.get_part_by_tex(str(num / 2)) - rect = SurroundingRectangle(tex) - rect.set_color(WHITE) - - self.add_fixed_in_frame_mobjects(rect) - self.play( - ShowCreation(line), - ShowCreationThenDestruction(rect), - run_time=2 - ) - self.remove_fixed_in_frame_mobjects(rect) - self.wait() - self.wait(15) - - -class MentionImpossibilityOf3dNumbers(TeacherStudentsScene): - def construct(self): - equations = VGroup( - TexMobject("ij = ?"), - TexMobject("ji = ?"), - ) - equations.arrange(RIGHT, buff=LARGE_BUFF) - equations.scale(1.5) - equations.to_edge(UP) - self.add(equations) - - why = TextMobject("Why not?") - why.next_to(self.students[1], UP) - - self.teacher_says( - "Such 3d numbers \\\\ have no good \\\\ multiplication rule", - bubble_kwargs={"width": 4, "height": 3}, - ) - self.change_all_student_modes("confused") - self.wait(2) - self.play( - self.students[1].change, "maybe", - FadeInFromLarge(why), - ) - self.wait(4) - - -class SphereExamplePointsDecimal(Scene): - CONFIG = { - "point_rotation_angle_axis_pairs": [ - (45 * DEGREES, DOWN), - (120 * DEGREES, OUT), - (35 * DEGREES, rotate_vector(RIGHT, 30 * DEGREES)), - (90 * DEGREES, IN), - ] - } - - def construct(self): - decimals = VGroup(*[ - DecimalNumber( - 0, - num_decimal_places=3, - color=color, - include_sign=True, - edge_to_fix=RIGHT, - ) - for color in [YELLOW, GREEN, RED] - ]) - number_label = VGroup( - decimals[0], TexMobject("+"), - decimals[1], TexMobject("i"), TexMobject("+"), - decimals[2], TexMobject("j"), - ) - number_label.arrange(RIGHT, buff=SMALL_BUFF) - number_label.to_corner(UL) - - point = VectorizedPoint(OUT) - - def generate_decimal_updater(decimal, index): - shifted_i = (index - 1) % 3 - decimal.add_updater(lambda d: d.set_value( - point.get_location()[shifted_i] - )) - return decimal - - for i, decimal in enumerate(decimals): - self.add(generate_decimal_updater(decimal, i)) - - decimal_braces = VGroup() - for decimal, char in zip(decimals, "wxy"): - brace = Brace(decimal, DOWN, buff=SMALL_BUFF) - label = brace.get_tex(char, buff=SMALL_BUFF) - label.match_color(decimal) - brace.add(label) - decimal_braces.add(brace) - - equation = TexMobject( - "w^2 + x^2 + y^2 = 1", - tex_to_color_map={ - "w": YELLOW, - "x": GREEN, - "y": RED, - } - ) - equation.next_to(decimal_braces, DOWN, MED_LARGE_BUFF) - - self.add(number_label) - self.add(decimal_braces) - self.add(equation) - - pairs = self.point_rotation_angle_axis_pairs - for angle, axis in pairs: - self.play( - Rotate(point, angle, axis=axis, about_point=ORIGIN), - run_time=2 - ) - self.wait() - - -class TwoDStereographicProjection(IntroduceFelix): - CONFIG = { - "camera_config": { - "exponential_projection": False, - }, - "sphere_sample_point_u_range": np.arange( - 0, PI, PI / 16, - ), - "sphere_sample_point_v_range": np.arange( - 0, TAU, TAU / 16, - ), - "n_sample_rotation_cycles": 2, - "lift_labels": True, - } - - def construct(self): - self.add_parts() - self.talk_through_sphere() - self.draw_projection_lines() - self.show_point_at_infinity() - self.show_a_few_rotations() - - def add_parts(self, run_time=1): - felix = self.felix = self.pi_creature - felix.shift(1.5 * DL) - axes = self.axes = self.get_axes() - sphere = self.sphere = self.get_sphere() - - c2p = axes.coords_to_point - labels = self.labels = VGroup( - TexMobject("i").next_to(c2p(1, 0, 0), DR, SMALL_BUFF), - TexMobject("-i").next_to(c2p(-1, 0, 0), DL, SMALL_BUFF), - TexMobject("j").next_to(c2p(0, 1, 0), UL, SMALL_BUFF), - TexMobject("-j").next_to(c2p(0, -1, 0), DL, SMALL_BUFF), - TexMobject("1").rotate( - 90 * DEGREES, RIGHT, - ).next_to(c2p(0, 0, 1), RIGHT + OUT, SMALL_BUFF), - TexMobject("-1").rotate( - 90 * DEGREES, RIGHT, - ).next_to(c2p(0, 0, -1), RIGHT + IN, SMALL_BUFF), - ) - if self.lift_labels: - for sm in labels[:4].family_members_with_points(): - sm.add(VectorizedPoint( - 0.25 * DOWN + 0.25 * OUT - )) - labels.set_stroke(width=0, background=True) - for submob in labels.get_family(): - submob.shade_in_3d = True - - self.add(felix, axes, sphere, labels) - self.move_camera( - **self.get_default_camera_position(), - run_time=run_time - ) - self.begin_ambient_camera_rotation(rate=0.01) - self.play( - felix.change, "pondering", sphere, - run_time=run_time, - ) - - def talk_through_sphere(self): - point = VectorizedPoint(OUT) - arrow = Vector(IN, shade_in_3d=True) - arrow.set_fill(PINK) - arrow.set_stroke(BLACK, 1) - - def get_dot(): - dot = Sphere(radius=0.05, u_max=PI / 2) - dot.set_fill(PINK) - dot.set_stroke(width=0) - dot.move_to(2.05 * OUT) - dot.apply_matrix( - z_to_vector(normalize(point.get_location())), - about_point=ORIGIN - ) - return dot - - dot = get_dot() - dot.add_updater( - lambda d: d.become(get_dot()) - ) - - def update_arrow(arrow): - target_point = 2.1 * point.get_location() - rot_matrix = np.dot( - z_to_vector(normalize(target_point)), - np.linalg.inv( - z_to_vector(normalize(-arrow.get_vector())) - ) - ) - arrow.apply_matrix(rot_matrix) - arrow.shift(target_point - arrow.get_end()) - return arrow - arrow.add_updater(update_arrow) - - self.add(self.sphere, dot, arrow) - pairs = SphereExamplePointsDecimal.CONFIG.get( - "point_rotation_angle_axis_pairs" - ) - for angle, axis in pairs: - self.play( - Rotate(point, angle, axis=axis, about_point=ORIGIN), - run_time=2 - ) - self.wait() - self.play(FadeOut(dot), FadeOut(arrow)) - - def draw_projection_lines(self): - sphere = self.sphere - axes = self.axes - radius = sphere.get_width() / 2 - - neg_one_point = axes.coords_to_point(0, 0, -1) - neg_one_dot = Dot( - neg_one_point, - color=YELLOW, - shade_in_3d=True - ) - - xy_plane = StereoProjectedSphere( - u_max=15 * PI / 16, - **self.sphere_config - ) - xy_plane.set_fill(WHITE, 0.25) - xy_plane.set_stroke(width=0) - - point_mob = VectorizedPoint(2 * OUT) - point_mob.add_updater( - lambda m: m.move_to(radius * normalize(m.get_center())) - ) - point_mob.move_to([1, -1, 1]) - point_mob.update(0) - - def get_projection_line(sphere_point): - to_sphere = Line(neg_one_point, sphere_point) - to_plane = Line( - sphere_point, - self.project_point(sphere_point) - ) - line = VGroup(to_sphere, to_plane) - line.set_stroke(YELLOW, 3) - for submob in line: - submob.shade_in_3d = True - return line - - def get_sphere_dot(sphere_point): - dot = Dot() - dot.set_shade_in_3d(True) - dot.set_fill(PINK) - dot.shift(OUT) - dot.apply_matrix( - z_to_vector(sphere_point), - about_point=ORIGIN, - ) - dot.move_to(1.01 * sphere_point) - dot.add(VectorizedPoint(5 * sphere_point)) - return dot - - def get_projection_dot(sphere_point): - projection = self.project_point(sphere_point) - dot = Dot(projection, shade_in_3d=True) - dot.add(VectorizedPoint(dot.get_center() + 0.1 * OUT)) - dot.set_fill(WHITE) - return dot - - point = point_mob.get_location() - dot = get_sphere_dot(point) - line = get_projection_line(point) - projection_dot = get_projection_dot(point) - - sample_points = [ - radius * sphere.func(u, v) - for u in self.sphere_sample_point_u_range - for v in self.sphere_sample_point_v_range - ] - - lines = VGroup(*[get_projection_line(p) for p in sample_points]) - lines.set_stroke(width=1) - north_lines = lines[:len(lines) // 2] - south_lines = lines[len(lines) // 2:] - - self.add(xy_plane, sphere) - self.play(Write(xy_plane)) - self.wait(2) - self.play(sphere.set_fill, BLUE_E, 0.5) - self.play(FadeInFromLarge(dot)) - self.play( - FadeIn(neg_one_dot), - ShowCreation(line), - ) - self.wait(2) - self.play(ReplacementTransform( - dot.copy(), projection_dot - )) - - def get_point(): - return 2 * normalize(point_mob.get_location()) - - dot.add_updater( - lambda d: d.become(get_sphere_dot(get_point())) - ) - line.add_updater( - lambda l: l.become(get_projection_line(get_point())) - ) - projection_dot.add_updater( - lambda d: d.become(get_projection_dot(get_point())) - ) - - self.play( - point_mob.move_to, - radius * normalize(np.array([1, -1, -1])), - run_time=3 - ) - self.move_camera( - theta=-150 * DEGREES, - run_time=3 - ) - self.add(axes, sphere, xy_plane, dot, line) - for point in np.array([-2, 1, -0.5]), np.array([-0.01, -0.01, 1]): - self.play( - point_mob.move_to, - radius * normalize(point), - run_time=3 - ) - self.wait(2) - - # Project norther hemisphere - north_hemisphere = self.get_sphere() - n = len(north_hemisphere) - north_hemisphere.remove(*north_hemisphere[n // 2:]) - north_hemisphere.generate_target() - self.project_mobject(north_hemisphere.target) - north_hemisphere.set_fill(opacity=0.8) - - self.play( - LaggedStartMap(ShowCreation, north_lines), - FadeIn(north_hemisphere) - ) - self.play( - MoveToTarget(north_hemisphere), - run_time=3, - rate_func=lambda t: smooth(0.99 * t) - ) - self.play(FadeOut(north_lines)) - self.wait(2) - - # Unit circle - circle = Sphere( - radius=2.01, - u_min=PI / 2 - 0.01, - u_max=PI / 2 + 0.01, - resolution=(1, 24), - ) - for submob in circle: - submob.add(VectorizedPoint(1.5 * submob.get_center())) - circle.set_fill(YELLOW) - circle_path = Circle(radius=2) - circle_path.rotate(-90 * DEGREES) - - self.play(FadeInFromLarge(circle)) - self.play(point_mob.move_to, circle_path.points[0]) - self.play(MoveAlongPath(point_mob, circle_path, run_time=6)) - self.move_camera( - phi=0, - theta=-90 * DEGREES, - rate_func=there_and_back_with_pause, - run_time=6, - ) - self.play(point_mob.move_to, OUT) - self.wait() - - # Southern hemisphere - south_hemisphere = self.get_sphere() - n = len(south_hemisphere) - south_hemisphere.remove(*south_hemisphere[:n // 2]) - south_hemisphere.remove( - *south_hemisphere[-sphere.resolution[1]:] - ) - south_hemisphere.generate_target() - self.project_mobject(south_hemisphere.target) - south_hemisphere.set_fill(opacity=0.8) - south_hemisphere.target[-sphere.resolution[1] // 2:].set_fill( - opacity=0 - ) - - self.play( - LaggedStartMap(ShowCreation, south_lines), - FadeIn(south_hemisphere) - ) - self.play( - MoveToTarget(south_hemisphere), - FadeOut(south_lines), - FadeOut(xy_plane), - run_time=3, - rate_func=lambda t: smooth(0.99 * t) - ) - self.wait(3) - - self.projected_sphere = VGroup( - north_hemisphere, - south_hemisphere, - ) - self.equator = circle - self.point_mob = point_mob - - def show_point_at_infinity(self): - points = list(compass_directions( - 12, start_vect=rotate_vector(RIGHT, 3.25 * DEGREES) - )) - points.pop(7) - points.pop(2) - arrows = VGroup(*[ - Arrow(6 * p, 11 * p) - for p in points - ]) - arrows.set_fill(RED) - arrows.set_stroke(RED, 5) - neg_ones = VGroup(*[ - TexMobject("-1").next_to(arrow.get_start(), -p) - for p, arrow in zip(points, arrows) - ]) - neg_ones.set_stroke(width=0, background=True) - - sphere_arcs = VGroup() - for angle in np.arange(0, TAU, TAU / 12): - arc = Arc(PI, radius=2) - arc.set_stroke(RED) - arc.rotate(PI / 2, axis=DOWN, about_point=ORIGIN) - arc.rotate(angle, axis=OUT, about_point=ORIGIN) - sphere_arcs.add(arc) - sphere_arcs.set_stroke(RED) - - self.play( - LaggedStartMap(GrowArrow, arrows), - LaggedStartMap(Write, neg_ones) - ) - self.wait(3) - self.play( - FadeOut(self.projected_sphere), - FadeOut(arrows), - FadeOut(neg_ones), - ) - for x in range(2): - self.play( - ShowCreationThenDestruction( - sphere_arcs, - lag_ratio=0, - run_time=3, - ) - ) - - def show_a_few_rotations(self): - sphere = self.sphere - felix = self.felix - point_mob = self.point_mob - point_mob.add_updater( - lambda m: m.move_to(sphere.get_all_points()[0]) - ) - coord_point_mobs = VGroup( - VectorizedPoint(RIGHT), - VectorizedPoint(UP), - VectorizedPoint(OUT), - ) - for pm in coord_point_mobs: - pm.shade_in_3d = True - - def get_rot_matrix(): - return np.array([ - pm.get_location() - for pm in coord_point_mobs - ]).T - - def get_projected_sphere(): - result = StereoProjectedSphere( - get_rot_matrix(), - max_r=10, - **self.sphere_config, - ) - result.set_fill(opacity=0.2) - result.fade_far_out_submobjects(max_r=32) - for submob in result: - if submob.get_center()[1] < -11: - submob.fade(1) - return result - - projected_sphere = get_projected_sphere() - projected_sphere.add_updater( - lambda m: m.become(get_projected_sphere()) - ) - - def get_projected_equator(): - equator = CheckeredCircle( - n_pieces=24, - radius=2, - ) - for submob in equator.get_family(): - submob.shade_in_3d = True - equator.set_stroke(YELLOW, 5) - equator.apply_matrix(get_rot_matrix()) - self.project_mobject(equator) - return equator - - projected_equator = get_projected_equator() - projected_equator.add_updater( - lambda m: m.become(get_projected_equator()) - ) - - self.add(sphere, projected_sphere) - self.move_camera(phi=60 * DEGREES) - self.play( - sphere.set_fill_by_checkerboard, - BLUE_E, BLUE_D, {"opacity": 0.8}, - FadeIn(projected_sphere) - ) - sphere.add(coord_point_mobs) - sphere.add(self.equator) - self.add(projected_equator) - pairs = self.get_sample_rotation_angle_axis_pairs() - for x in range(self.n_sample_rotation_cycles): - for angle, axis in pairs: - self.play( - Rotate( - sphere, angle=angle, axis=axis, - about_point=ORIGIN, - run_time=3, - ), - felix.change, "confused", - ) - self.wait() - - self.projected_sphere = projected_sphere - - # - def project_mobject(self, mobject): - return stereo_project(mobject, axis=2, r=2, outer_r=20) - - def project_point(self, point): - return stereo_project_point(point, axis=2, r=2) - - def get_sample_rotation_angle_axis_pairs(self): - return SphereExamplePointsDecimal.CONFIG.get( - "point_rotation_angle_axis_pairs" - ) - - -class FelixViewOfProjection(TwoDStereographicProjection): - CONFIG = {} - - def construct(self): - self.add_axes() - self.show_a_few_rotations() - - def add_axes(self): - axes = Axes( - axis_config={ - "unit_size": 2, - "color": WHITE, - } - ) - labels = VGroup( - TexMobject("i"), - TexMobject("-i"), - TexMobject("j"), - TexMobject("-j"), - TexMobject("1"), - ) - coords = [(1, 0), (-1, 0), (0, 1), (0, -1), (0, 0)] - vects = [DOWN, DOWN, RIGHT, RIGHT, 0.25 * DR] - for label, coords, vect in zip(labels, coords, vects): - point = axes.coords_to_point(*coords) - label.next_to(point, vect, buff=MED_SMALL_BUFF) - - self.add(axes, labels) - self.pi_creature.change("confused") - - def show_a_few_rotations(self): - felix = self.pi_creature - coord_point_mobs = VGroup([ - VectorizedPoint(point) - for point in [RIGHT, UP, OUT] - ]) - - def get_rot_matrix(): - return np.array([ - pm.get_location() - for pm in coord_point_mobs - ]).T - - def get_projected_sphere(): - return StereoProjectedSphere( - get_rot_matrix(), - **self.sphere_config, - ) - - def get_projected_equator(): - equator = Circle(radius=2, num_anchors=24) - equator.set_stroke(YELLOW, 5) - equator.apply_matrix(get_rot_matrix()) - self.project_mobject(equator) - return equator - - projected_sphere = get_projected_sphere() - projected_sphere.add_updater( - lambda m: m.become(get_projected_sphere()) - ) - - equator = get_projected_equator() - equator.add_updater( - lambda m: m.become(get_projected_equator()) - ) - - dot = Dot(color=PINK) - dot.add_updater( - lambda d: d.move_to( - self.project_point( - np.dot(2 * OUT, get_rot_matrix().T) - ) - ) - ) - hand = Hand() - hand.add_updater( - lambda h: h.move_to(dot.get_center(), LEFT) - ) - felix.add_updater(lambda f: f.look_at(dot)) - - self.add(projected_sphere) - self.add(equator) - self.add(dot) - self.add(hand) - - pairs = self.get_sample_rotation_angle_axis_pairs() - for x in range(self.n_sample_rotation_cycles): - for angle, axis in pairs: - self.play( - Rotate( - coord_point_mobs, angle=angle, axis=axis, - about_point=ORIGIN, - run_time=3, - ), - ) - self.wait() - - -class ShowRotationsJustWithReferenceCircles(TwoDStereographicProjection): - CONFIG = { - "flat_view": False, - } - - def construct(self): - self.add_parts(run_time=1) - self.begin_ambient_camera_rotation(rate=0.03) - self.edit_parts() - self.show_1i_circle() - self.show_1j_circle() - self.show_random_circle() - self.show_rotations() - - def edit_parts(self): - sphere = self.sphere - axes = self.axes - axes.set_stroke(width=1) - xy_plane = StereoProjectedSphere(u_max=15 * PI / 16) - xy_plane.set_fill(WHITE, 0.2) - xy_plane.set_stroke(width=0, opacity=0) - - self.add(xy_plane, sphere) - self.play( - FadeIn(xy_plane), - sphere.set_fill, BLUE_E, {"opacity": 0.2}, - sphere.set_stroke, {"width": 0.1, "opacity": 0.5} - ) - - def show_1i_circle(self): - axes = self.axes - - circle = self.get_circle(GREEN_E, GREEN) - circle.rotate(TAU / 4, RIGHT) - circle.rotate(TAU / 4, DOWN) - - projected = self.get_projected_circle(circle) - - labels = VGroup(*map(TexMobject, ["0", "2i", "3i"])) - labels.set_shade_in_3d(True) - if self.flat_view: - labels.fade(1) - for label, x in zip(labels, [0, 2, 3]): - label.next_to( - axes.coords_to_point(x, 0, 0), DR, SMALL_BUFF - ) - - self.play(ShowCreation(circle, run_time=3)) - self.wait() - self.play(ReplacementTransform( - circle.copy(), projected, - run_time=3 - )) - # self.axes.x_axis.pieces.set_stroke(width=0) - self.wait(7) - self.move_camera( - phi=60 * DEGREES, - ) - self.play( - LaggedStartMap( - FadeInFrom, labels, - lambda m: (m, UP) - ) - ) - self.wait(2) - self.play(FadeOut(labels)) - - self.one_i_circle = circle - self.projected_one_i_circle = projected - - def show_1j_circle(self): - circle = self.get_circle(RED_E, RED) - circle.rotate(TAU / 4, DOWN) - - projected = self.get_projected_circle(circle) - - self.move_camera(theta=-170 * DEGREES) - self.play(ShowCreation(circle, run_time=3)) - self.wait() - self.play(ReplacementTransform( - circle.copy(), projected, run_time=3 - )) - # self.axes.y_axis.pieces.set_stroke(width=0) - self.wait(3) - - self.one_j_circle = circle - self.projected_one_j_circle = projected - - def show_random_circle(self): - sphere = self.sphere - - circle = self.get_circle(BLUE_E, BLUE) - circle.set_width(2 * sphere.radius * np.sin(30 * DEGREES)) - circle.shift(sphere.radius * np.cos(30 * DEGREES) * OUT) - circle.rotate(150 * DEGREES, UP, about_point=ORIGIN) - - projected = self.get_projected_circle(circle) - - self.play(ShowCreation(circle, run_time=2)) - self.wait() - self.play(ReplacementTransform( - circle.copy(), projected, - run_time=2 - )) - self.wait(3) - self.play( - FadeOut(circle), - FadeOut(projected), - ) - - def show_rotations(self): - sphere = self.sphere - c1i = self.one_i_circle - pc1i = self.projected_one_i_circle - c1j = self.one_j_circle - pc1j = self.projected_one_j_circle - cij = self.get_circle(YELLOW_E, YELLOW) - pcij = self.get_projected_circle(cij) - - circles = VGroup(c1i, c1j, cij) - x_axis = self.axes.x_axis - y_axis = self.axes.y_axis - - arrow = Arrow( - 2 * RIGHT, 2 * UP, - buff=SMALL_BUFF, - path_arc=PI, - ) - arrow.set_stroke(LIGHT_GREY, 3) - arrow.tip.set_fill(LIGHT_GREY) - arrows = VGroup(arrow, *[ - arrow.copy().rotate(angle, about_point=ORIGIN) - for angle in np.arange(TAU / 4, TAU, TAU / 4) - ]) - arrows.rotate(TAU / 4, RIGHT, about_point=ORIGIN) - arrows.rotate(TAU / 2, OUT, about_point=ORIGIN) - arrows.rotate(TAU / 4, UP, about_point=ORIGIN) - arrows.space_out_submobjects(1.2) - - self.play(FadeInFromLarge(cij)) - sphere.add(circles) - - pc1i.add_updater( - lambda c: c.become(self.get_projected_circle(c1i)) - ) - pc1j.add_updater( - lambda c: c.become(self.get_projected_circle(c1j)) - ) - pcij.add_updater( - lambda c: c.become(self.get_projected_circle(cij)) - ) - self.add(pcij) - - # About j-axis - self.play(ShowCreation(arrows, run_time=3, rate_func=linear)) - self.wait(3) - for x in range(2): - y_axis.pieces.set_stroke(width=1) - self.play( - Rotate(sphere, 90 * DEGREES, axis=UP), - run_time=4, - ) - y_axis.pieces.set_stroke(width=0) - self.wait(2) - - # About i axis - self.move_camera(theta=-45 * DEGREES) - self.play(Rotate(arrows, TAU / 4, axis=OUT)) - self.wait(2) - for x in range(2): - x_axis.pieces.set_stroke(width=1) - self.play( - Rotate(sphere, -90 * DEGREES, axis=RIGHT), - run_time=4, - ) - x_axis.pieces.set_stroke(width=0) - self.wait(2) - self.wait(2) - - # About real axis - self.move_camera( - theta=-135 * DEGREES, - added_anims=[FadeOut(arrows)] - ) - self.ambient_camera_rotation.rate = 0.01 - for x in range(2): - x_axis.pieces.set_stroke(width=1) - y_axis.pieces.set_stroke(width=1) - self.play( - Rotate(sphere, 90 * DEGREES, axis=OUT), - run_time=4, - ) - # x_axis.pieces.set_stroke(width=0) - # y_axis.pieces.set_stroke(width=0) - self.wait(2) - - # - def get_circle(self, *colors): - sphere = self.sphere - circle = CheckeredCircle(colors=colors, n_pieces=48) - circle.set_shade_in_3d(True) - circle.match_width(sphere) - if self.flat_view: - circle[::2].fade(1) - - return circle - - def get_projected_circle(self, circle): - result = circle.deepcopy() - self.project_mobject(result) - result[::2].fade(1) - for sm in result: - if sm.get_width() > FRAME_WIDTH: - sm.fade(1) - if sm.get_height() > FRAME_HEIGHT: - sm.fade(1) - return result - - -class ReferernceSpheresFelixView(ShowRotationsJustWithReferenceCircles): - CONFIG = { - "flat_view": True, - "lift_labels": False, - } - - def add_parts(self, **kwargs): - ShowRotationsJustWithReferenceCircles.add_parts(self, **kwargs) - one = TexMobject("1") - one.next_to(ORIGIN, DR, SMALL_BUFF) - self.add(one) - - def get_default_camera_position(self): - return {} - - def move_camera(self, **kwargs): - kwargs["phi"] = 0 - kwargs["theta"] = -90 * DEGREES - ShowRotationsJustWithReferenceCircles.move_camera(self, **kwargs) - - def begin_ambient_camera_rotation(self, rate): - self.ambient_camera_rotation = VectorizedPoint() - - def capture_mobjects_in_camera(self, mobjects, **kwargs): - mobs_on_xy = [ - sm - for sm in self.camera.extract_mobject_family_members( - mobjects, only_those_with_points=True - ) - if abs(sm.get_center()[2]) < 0.001 - ] - return Scene.capture_mobjects_in_camera(self, mobs_on_xy, **kwargs) - - -class IntroduceQuaternions(Scene): - def construct(self): - self.compare_three_number_systems() - self.mention_four_perpendicular_axes() - self.bring_back_complex() - self.show_components_of_quaternion() - - def compare_three_number_systems(self): - numbers = self.get_example_numbers() - labels = VGroup( - TextMobject("Complex number"), - TextMobject("Not-actually-a-number-system 3d number"), - TextMobject("Quaternion"), - ) - - for number, label in zip(numbers, labels): - label.next_to(number, UP, aligned_edge=LEFT) - - self.play( - FadeInFromDown(number), - Write(label), - ) - self.play(ShowCreationThenFadeAround( - number[2:], - surrounding_rectangle_config={"color": BLUE} - )) - self.wait() - - shift_size = FRAME_HEIGHT / 2 - labels[2].get_top()[1] - MED_LARGE_BUFF - self.play( - numbers.shift, shift_size * UP, - labels.shift, shift_size * UP, - ) - - self.numbers = numbers - self.labels = labels - - def mention_four_perpendicular_axes(self): - number = self.numbers[2] - three_axes = VGroup(*[ - self.get_simple_axes(label, color) - for label, color in zip( - ["i", "j", "k"], - [GREEN, RED, BLUE], - ) - ]) - three_axes.arrange(RIGHT, buff=LARGE_BUFF) - three_axes.next_to(number, DOWN, LARGE_BUFF) - - self.play(LaggedStartMap(FadeInFromLarge, three_axes)) - self.wait(2) - - self.three_axes = three_axes - - def bring_back_complex(self): - numbers = self.numbers - labels = self.labels - numbers[0].move_to(numbers[1], LEFT) - labels[0].move_to(labels[1], LEFT) - numbers.remove(numbers[1]) - labels.remove(labels[1]) - - group = VGroup(numbers, labels) - self.play( - group.to_edge, UP, - FadeOutAndShift(self.three_axes, DOWN) - ) - self.wait() - - def show_components_of_quaternion(self): - quat = self.numbers[-1] - real_part = quat[0] - imag_part = quat[2:] - real_brace = Brace(real_part, DOWN) - imag_brace = Brace(imag_part, DOWN) - real_word = TextMobject("Real \\\\ part") - imag_word = TextMobject("Imaginary \\\\ part") - scalar_word = TextMobject("Scalar \\\\ part") - vector_word = TextMobject("``Vector'' \\\\ part") - for word in real_word, scalar_word: - word.next_to(real_brace, DOWN, SMALL_BUFF) - for word in imag_word, vector_word: - word.next_to(imag_brace, DOWN, SMALL_BUFF) - braces = VGroup(real_brace, imag_brace) - VGroup(scalar_word, vector_word).set_color(YELLOW) - - self.play( - LaggedStartMap(GrowFromCenter, braces), - LaggedStartMap( - FadeInFrom, VGroup(real_word, imag_word), - lambda m: (m, UP) - ) - ) - self.wait() - self.play( - FadeOutAndShift(real_word, DOWN), - FadeInFrom(scalar_word, DOWN), - ) - self.wait(2) - self.play(ChangeDecimalToValue(real_part, 0)) - self.wait() - self.play( - FadeOutAndShift(imag_word, DOWN), - FadeInFrom(vector_word, DOWN) - ) - self.wait(2) - - # - def get_example_numbers(self): - number_2d = VGroup( - DecimalNumber(3.14), - TexMobject("+"), - DecimalNumber(1.59), - TexMobject("i") - ) - number_3d = VGroup( - DecimalNumber(2.65), - TexMobject("+"), - DecimalNumber(3.58), - TexMobject("i"), - TexMobject("+"), - DecimalNumber(9.79), - TexMobject("j"), - ) - number_4d = VGroup( - DecimalNumber(3.23), - TexMobject("+"), - DecimalNumber(8.46), - TexMobject("i"), - TexMobject("+"), - DecimalNumber(2.64), - TexMobject("j"), - TexMobject("+"), - DecimalNumber(3.38), - TexMobject("k"), - ) - numbers = VGroup(number_2d, number_3d, number_4d) - for number in numbers: - number.arrange(RIGHT, buff=SMALL_BUFF) - for part in number: - if isinstance(part, TexMobject): - # part.set_color_by_tex_to_color_map({ - # "i": GREEN, - # "j": RED, - # "k": BLUE, - # }) - if part.get_tex_string() == "j": - part.shift(0.5 * SMALL_BUFF * DL) - number[2].set_color(GREEN) - if len(number) > 5: - number[5].set_color(RED) - if len(number) > 8: - number[8].set_color(BLUE) - numbers.arrange( - DOWN, buff=2, aligned_edge=LEFT - ) - numbers.center() - numbers.shift(LEFT) - return numbers - - def get_simple_axes(self, label, color): - axes = Axes( - x_min=-2.5, - x_max=2.5, - y_min=-2.5, - y_max=2.5, - ) - axes.set_height(2.5) - label_mob = TexMobject(label) - label_mob.set_color(color) - label_mob.next_to(axes.coords_to_point(0, 1.5), RIGHT, SMALL_BUFF) - reals_label_mob = TextMobject("Reals") - reals_label_mob.next_to( - axes.coords_to_point(1, 0), DR, SMALL_BUFF - ) - axes.add(label_mob, reals_label_mob) - return axes - - -class SimpleImaginaryQuaternionAxes(SpecialThreeDScene): - def construct(self): - self.three_d_axes_config.update({ - "axis_config": {"unit_size": 2}, - "x_min": -2, - "x_max": 2, - "y_min": -2, - "y_max": 2, - "z_min": -1.25, - "z_max": 1.25, - }) - axes = self.get_axes() - labels = VGroup(*[ - TexMobject(tex).set_color(color) - for tex, color in zip( - ["i", "j", "k"], - [GREEN, RED, BLUE] - ) - ]) - labels[0].next_to(axes.coords_to_point(1, 0, 0), DOWN + IN, SMALL_BUFF) - labels[1].next_to(axes.coords_to_point(0, 1, 0), RIGHT, SMALL_BUFF) - labels[2].next_to(axes.coords_to_point(0, 0, 1), RIGHT, SMALL_BUFF) - - self.add(axes) - self.add(labels) - for label in labels: - self.add_fixed_orientation_mobjects(label) - - self.move_camera(**self.get_default_camera_position()) - self.begin_ambient_camera_rotation(rate=0.05) - self.wait(15) - - -class ShowDotProductCrossProductFromOfQMult(Scene): - def construct(self): - v_tex = "\\vec{\\textbf{v}}" - product = TexMobject( - "(", "w_1", "+", - "x_1", "i", "+", "y_1", "j", "+", "z_1", "k", ")" - "(", "w_2", "+", - "x_2", "i", "+", "y_2", "j", "+", "z_2", "k", ")", - "=", - "(w_1", ",", v_tex + "_1", ")", - "(w_2", ",", v_tex + "_2", ")", - "=" - ) - product.set_width(FRAME_WIDTH - 1) - - i1 = product.index_of_part_by_tex("x_1") - i2 = product.index_of_part_by_tex(")") - i3 = product.index_of_part_by_tex("x_2") - i4 = product.index_of_part_by_tex("z_2") + 2 - vector_parts = [product[i1:i2], product[i3:i4]] - - vector_defs = VGroup() - braces = VGroup() - for i, vp in zip(it.count(1), vector_parts): - brace = Brace(vp, UP) - vector = Matrix([ - ["x_" + str(i)], - ["y_" + str(i)], - ["z_" + str(i)], - ]) - colors = [GREEN, RED, BLUE] - for mob, color in zip(vector.get_entries(), colors): - mob.set_color(color) - group = VGroup( - TexMobject("{}_{} = ".format(v_tex, i)), - vector, - ) - group.arrange(RIGHT, SMALL_BUFF) - group.next_to(brace, UP) - - braces.add(brace) - vector_defs.add(group) - - result = TexMobject( - "\\left(", "w_1", "w_2", - "-", v_tex + "_1", "\\cdot", v_tex, "_2", ",\\,", - "w_1", v_tex + "_2", "+", "w_2", v_tex + "_1", - "+", "{}_1 \\times {}_2".format(v_tex, v_tex), - "\\right)" - ) - result.match_width(product) - result.next_to(product, DOWN, LARGE_BUFF) - for mob in product, result: - mob.set_color_by_tex_to_color_map({ - "w": YELLOW, - "x": GREEN, - "y": RED, - "z": BLUE, - }) - mob.set_color_by_tex(v_tex, WHITE) - - self.add(product) - self.add(braces) - self.add(vector_defs) - self.play(LaggedStartMap(FadeInFromLarge, result)) - self.wait() - - -class ShowComplexMagnitude(ShowComplexMultiplicationExamples): - def construct(self): - self.add_planes() - plane = self.plane - tex_to_color_map = { - "a": YELLOW, - "b": GREEN, - } - - z = complex(3, 2) - z_point = plane.number_to_point(z) - z_dot = Dot(z_point) - z_dot.set_color(PINK) - z_line = Line(plane.number_to_point(0), z_point) - z_line.set_stroke(WHITE, 2) - z_label = TexMobject( - "z", "=", "a", "+", "b", "i", - tex_to_color_map=tex_to_color_map - ) - z_label.add_background_rectangle() - z_label.next_to(z_dot, UR, buff=SMALL_BUFF) - z_norm_label = TexMobject("||z||") - z_norm_label.add_background_rectangle() - z_norm_label.next_to(ORIGIN, UP, SMALL_BUFF) - z_norm_label.rotate(z_line.get_angle(), about_point=ORIGIN) - z_norm_label.shift(z_line.get_center()) - - h_line = Line( - plane.number_to_point(0), - plane.number_to_point(z.real), - stroke_color=YELLOW, - stroke_width=5, - ) - v_line = Line( - plane.number_to_point(z.real), - plane.number_to_point(z), - stroke_color=GREEN, - stroke_width=5, - ) - - z_norm_equation = TexMobject( - "||z||", "=", "\\sqrt", "{a^2", "+", "b^2", "}", - tex_to_color_map=tex_to_color_map - ) - z_norm_equation.set_background_stroke(width=0) - z_norm_equation.add_background_rectangle() - z_norm_equation.next_to(z_label, UP) - - self.add(z_line, h_line, v_line, z_dot, z_label) - self.play(ShowCreation(z_line)) - self.play(FadeInFromDown(z_norm_label)) - self.wait() - self.play( - FadeIn(z_norm_equation[0]), - FadeIn(z_norm_equation[2:]), - TransformFromCopy( - z_norm_label[1:], - VGroup(z_norm_equation[1]), - ), - ) - self.wait() - - -class BreakUpQuaternionMultiplicationInParts(Scene): - def construct(self): - q1_color = MAROON_B - q2_color = YELLOW - - product = TexMobject( - "q_1", "\\cdot", "q_2", "=", - "\\left(", "{q_1", "\\over", "||", "q_1", "||}", "\\right)", - "||", "q_1", "||", "\\cdot", "q_2", - ) - product.set_color_by_tex("q_1", q1_color) - product.set_color_by_tex("q_2", q2_color) - lhs = product[:3] - scale_part = product[-5:] - rotate_part = product[4:-5] - lhs_rect = SurroundingRectangle(lhs) - lhs_rect.set_color(YELLOW) - lhs_words = TextMobject("Quaternion \\\\ multiplication") - lhs_words.next_to(lhs_rect, UP, LARGE_BUFF) - scale_brace = Brace(scale_part, UP) - rotate_brace = Brace(rotate_part, DOWN) - scale_words = TextMobject("Scale", "$q_2$") - scale_words.set_color_by_tex("q_2", q2_color) - scale_words.next_to(scale_brace, UP) - rotate_words = TextMobject("Apply special \\\\ 4d rotation") - rotate_words.next_to(rotate_brace, DOWN) - - norm_equation = TexMobject( - "||", "q_1", "||", "=", - "||", "w_1", "+", - "x_1", "i", "+", - "y_1", "j", "+", - "z_1", "k", "||", "=", - "\\sqrt", - "{w_1^2", "+", - "x_1^2", "+", - "y_1^2", "+", - "z_1^2", "}", - ) - # norm_equation.set_color_by_tex_to_color_map({ - # "w": YELLOW, - # "x": GREEN, - # "y": RED, - # "z": BLUE, - # }) - norm_equation.set_color_by_tex("q_1", q1_color) - norm_equation.to_edge(UP) - norm_equation.set_background_stroke(width=0) - - line1 = Line(ORIGIN, 0.5 * LEFT + 3 * UP) - line2 = Line(ORIGIN, UR) - zero_dot = Dot() - zero_label = TexMobject("0") - zero_label.next_to(zero_dot, DOWN, SMALL_BUFF) - q1_dot = Dot(line1.get_end()) - q2_dot = Dot(line2.get_end()) - q1_label = TexMobject("q_1").next_to(q1_dot, UP, SMALL_BUFF) - q2_label = TexMobject("q_2").next_to(q2_dot, UR, SMALL_BUFF) - VGroup(q1_dot, q1_label).set_color(q1_color) - VGroup(q2_dot, q2_label).set_color(q2_color) - dot_group = VGroup( - line1, line2, q1_dot, q2_dot, q1_label, q2_label, - zero_dot, zero_label, - ) - dot_group.set_height(3) - dot_group.center() - dot_group.to_edge(LEFT) - - q1_dot.add_updater(lambda d: d.move_to(line1.get_end())) - q1_label.add_updater(lambda l: l.next_to(q1_dot, UP, SMALL_BUFF)) - q2_dot.add_updater(lambda d: d.move_to(line2.get_end())) - q2_label.add_updater(lambda l: l.next_to(q2_dot, UR, SMALL_BUFF)) - - self.add(norm_equation) - self.wait() - self.play( - FadeInFromDown(lhs), - Write(dot_group), - ) - self.add(*dot_group) - self.add( - VGroup(line2, q2_dot, q2_label).copy().fade(0.5) - ) - self.play( - ShowCreation(lhs_rect), - FadeIn(lhs_words) - ) - self.play(FadeOut(lhs_rect)) - self.wait() - self.play( - TransformFromCopy(lhs, product[3:]), - # FadeOut(lhs_words) - ) - self.play( - GrowFromCenter(scale_brace), - Write(scale_words), - ) - self.play( - line2.scale, 2, {"about_point": line2.get_start()} - ) - self.wait() - self.play( - GrowFromCenter(rotate_brace), - FadeInFrom(rotate_words, UP), - ) - self.play( - Rotate( - line2, -line1.get_angle(), - about_point=line2.get_start(), - run_time=3 - ) - ) - self.wait() - - # Ask - randy = Randolph(height=2) - randy.flip() - randy.next_to(rotate_words, RIGHT) - randy.to_edge(DOWN) - q_marks = TexMobject("???") - random.shuffle(q_marks.submobjects) - q_marks.next_to(randy, UP) - self.play( - FadeIn(randy) - ) - self.play( - randy.change, "confused", rotate_words, - ShowCreationThenFadeAround(rotate_words), - ) - self.play(LaggedStartMap( - FadeInFrom, q_marks, - lambda m: (m, LEFT), - lag_ratio=0.8, - )) - self.play(Blink(randy)) - self.wait(2) - - -class SphereProjectionsWrapper(Scene): - def construct(self): - rect_rows = VGroup(*[ - VGroup(*[ - ScreenRectangle(height=3) - for x in range(3) - ]).arrange(RIGHT, buff=LARGE_BUFF) - for y in range(2) - ]).arrange(DOWN, buff=2 * LARGE_BUFF) - rect_rows.set_width(FRAME_WIDTH - 1) - - sphere_labels = VGroup( - TextMobject("Circle in 2d"), - TextMobject("Sphere in 3d"), - TextMobject("Hypersphere in 4d"), - ) - for label, rect in zip(sphere_labels, rect_rows[0]): - label.next_to(rect, UP) - - projected_labels = VGroup( - TextMobject("Sterographically projected \\\\ circle in 1d"), - TextMobject("Sterographically projected \\\\ sphere in 2d"), - TextMobject("Sterographically projected \\\\ hypersphere in 3d"), - ) - for label, rect in zip(projected_labels, rect_rows[1]): - label.match_width(rect) - label.next_to(rect, UP) - - q_marks = TexMobject("???") - q_marks.scale(2) - q_marks.move_to(rect_rows[0][2]) - - self.add(rect_rows) - for l1, l2 in zip(sphere_labels, projected_labels): - added_anims = [] - if l1 is sphere_labels[2]: - added_anims.append(FadeIn(q_marks)) - self.play(FadeIn(l1), *added_anims) - self.play(FadeIn(l2)) - self.wait() - - -class HypersphereStereographicProjection(SpecialThreeDScene): - CONFIG = { - # "fancy_dot": False, - "fancy_dot": True, - "initial_quaternion_sample_values": [ - [0, 1, 0, 0], - [-1, 1, 0, 0], - [0, 0, 1, 1], - [0, 1, -1, 1], - ], - "unit_labels_scale_factor": 1, - } - - def construct(self): - self.setup_axes() - self.introduce_quaternion_label() - self.show_one() - self.show_unit_sphere() - self.show_quaternions_with_nonzero_real_part() - self.emphasize_only_units() - self.show_reference_spheres() - - def setup_axes(self): - axes = self.axes = self.get_axes() - axes.set_stroke(width=1) - self.add(axes) - self.move_camera( - **self.get_default_camera_position(), - run_time=0 - ) - self.begin_ambient_camera_rotation(rate=0.01) - - def introduce_quaternion_label(self): - q_tracker = QuaternionTracker() - coords = [ - DecimalNumber(0, color=color, include_sign=sign, edge_to_fix=RIGHT) - for color, sign in zip( - [YELLOW, GREEN, RED, BLUE], - [False, True, True, True], - ) - ] - label = VGroup( - coords[0], VectorizedPoint(), - coords[1], TexMobject("i"), - coords[2], TexMobject("j"), - coords[3], TexMobject("k"), - ) - label.arrange(RIGHT, buff=SMALL_BUFF) - label.to_corner(UR) - - def update_label(label): - self.remove_fixed_in_frame_mobjects(label) - quat = q_tracker.get_value() - for value, coord in zip(quat, label[::2]): - coord.set_value(value) - self.add_fixed_in_frame_mobjects(label) - return label - - label.add_updater(update_label) - self.pink_dot_label = label - - def get_pq_point(): - point = self.project_quaternion(q_tracker.get_value()) - if get_norm(point) > 100: - return point * 100 / get_norm(point) - return point - - pq_dot = self.get_dot() - pq_dot.add_updater(lambda d: d.move_to(get_pq_point())) - dot_radius = pq_dot.get_width() / 2 - - def get_pq_line(): - point = get_pq_point() - norm = get_norm(point) - origin = self.axes.coords_to_point(0, 0, 0) - if norm > dot_radius: - point -= origin - point *= (norm - dot_radius) / norm - point += origin - result = Line(origin, point) - result.set_stroke(width=1) - return result - - pq_line = get_pq_line() - pq_line.add_updater(lambda cl: cl.become(get_pq_line())) - - self.add(q_tracker, label, pq_line, pq_dot) - - self.q_tracker = q_tracker - self.q_label = label - self.pq_line = pq_line - self.pq_dot = pq_dot - - rect = SurroundingRectangle(label, color=WHITE) - self.add_fixed_in_frame_mobjects(rect) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.remove_fixed_orientation_mobjects(rect) - - for value in self.initial_quaternion_sample_values: - self.set_quat(value) - self.wait() - - def show_one(self): - q_tracker = self.q_tracker - - one_label = TexMobject("1") - one_label.rotate(TAU / 4, RIGHT) - one_label.next_to(ORIGIN, IN + RIGHT, SMALL_BUFF) - one_label.set_shade_in_3d(True) - one_label.set_background_stroke(width=0) - - self.play( - ApplyMethod( - q_tracker.set_value, [1, 0, 0, 0], - run_time=2 - ), - FadeInFromDown(one_label) - ) - self.wait(4) - - def show_unit_sphere(self): - sphere = self.sphere = self.get_projected_sphere( - quaternion=[1, 0, 0, 0], null_axis=0, - solid=False, - stroke_width=0.5 - ) - self.specially_color_sphere(sphere) - labels = self.get_unit_labels() - labels.remove(labels[3]) - - real_part = self.q_label[0] - brace = Brace(real_part, DOWN) - words = TextMobject("Real part zero") - words.next_to(brace, DOWN, SMALL_BUFF, LEFT) - - self.play(Write(sphere)) - self.play(LaggedStartMap( - FadeInFrom, labels, - lambda m: (m, IN) - )) - self.add_fixed_in_frame_mobjects(brace, words) - self.set_quat( - [0, 1, 0, 0], - added_anims=[ - GrowFromCenter(brace), - Write(words), - ] - ) - self.wait() - self.set_quat([0, 1, -1, 1]) - self.wait(2) - self.set_quat([0, -1, -1, 1]) - self.wait(2) - self.set_quat([0, 0, 0, 1]) - self.wait(2) - self.set_quat([0, 0, -1, 0]) - self.wait(2) - self.set_quat([0, 1, 0, 0]) - self.wait(2) - self.play(FadeOut(words)) - self.remove_fixed_in_frame_mobjects(words) - - self.real_part_brace = brace - - def show_quaternions_with_nonzero_real_part(self): - # Positive real part - self.set_quat( - [1, 1, 2, 0], - added_anims=[ - ApplyMethod( - self.sphere.copy().scale, 0, - remover=True - ) - ] - ) - self.wait(2) - self.set_quat([4, 0, -1, -1]) - self.wait(2) - # Negative real part - self.set_quat( - [-1, 1, 2, 0], - added_anims=[ - ApplyFunction( - lambda s: s.scale(10).fade(1), - self.sphere.copy(), - remover=True - ) - ] - ) - self.wait(2) - self.set_quat([-2, 0, -1, 1]) - self.wait(2) - self.set_quat([-1, 1, 0, 0]) - self.move_camera(theta=-160 * DEGREES, run_time=3) - self.set_quat([-1, 0.001, 0, 0]) - self.wait(2) - - def emphasize_only_units(self): - q_label = self.q_label - brace = self.real_part_brace - - brace.target = Brace(q_label, DOWN, buff=SMALL_BUFF) - words = TextMobject( - "Only those where \\\\", - "$w^2 + x^2 + y^2 + z^2 = 1$" - ) - words.next_to(brace.target, DOWN, SMALL_BUFF) - - self.add_fixed_in_frame_mobjects(words) - self.play( - MoveToTarget(brace), - Write(words) - ) - self.set_quat([1, 1, 1, 1]) - self.wait(2) - self.set_quat([1, 1, -1, 1]) - self.wait(2) - self.set_quat([-1, 1, -1, 1]) - self.wait(8) - self.play(FadeOut(brace), FadeOut(words)) - self.remove_fixed_in_frame_mobjects(brace, words) - - # TODO - def show_reference_spheres(self): - sphere = self.sphere - self.move_camera( - phi=60 * DEGREES, - theta=-150 * DEGREES, - added_anims=[ - self.q_tracker.set_value, [1, 0, 0, 0] - ] - ) - sphere_ijk = self.get_projected_sphere(null_axis=0) - sphere_1jk = self.get_projected_sphere(null_axis=1) - sphere_1ik = self.get_projected_sphere(null_axis=2) - sphere_1ij = self.get_projected_sphere(null_axis=3) - circle = StereoProjectedCircleFromHypersphere(axes=[0, 1]) - - circle_words = TextMobject( - "Circle through\\\\", "$1, i, -1, -i$" - ) - sphere_1ij_words = TextMobject( - "Sphere through\\\\", "$1, i, j, -1, -i, -j$" - ) - sphere_1jk_words = TextMobject( - "Sphere through\\\\", "$1, j, k, -1, -j, -k$" - ) - sphere_1ik_words = TextMobject( - "Sphere through\\\\", "$1, i, k, -1, -i, -k$" - ) - for words in [circle_words, sphere_1ij_words, sphere_1jk_words, sphere_1ik_words]: - words.to_corner(UL) - self.add_fixed_in_frame_mobjects(words) - - self.play( - ShowCreation(circle), - Write(circle_words), - ) - self.set_quat([0, 1, 0, 0]) - self.set_quat([1, 0, 0, 0]) - self.remove(sphere) - sphere_ijk.match_style(sphere) - self.add(sphere_ijk) - - # Show xy plane - self.play( - FadeOutAndShift(circle_words, DOWN), - FadeInFromDown(sphere_1ij_words), - FadeOut(circle), - sphere_ijk.set_stroke, {"width": 0.0} - ) - self.play(Write(sphere_1ij)) - self.wait(10) - return - - # Show yz plane - self.play( - FadeOutAndShift(sphere_1ij_words, DOWN), - FadeInFromDown(sphere_1jk_words), - sphere_1ij.set_fill, BLUE_E, 0.25, - sphere_1ij.set_stroke, {"width": 0.0}, - Write(sphere_1jk) - ) - self.wait(5) - - # Show xz plane - self.play( - FadeOutAndShift(sphere_1jk_words, DOWN), - FadeInFromDown(sphere_1ik_words), - sphere_1jk.set_fill, GREEN_E, 0.25, - sphere_1jk.set_stroke, {"width": 0.0}, - Write(sphere_1ik) - ) - self.wait(5) - self.play( - sphere_1ik.set_fill, RED_E, 0.25, - sphere_1ik.set_stroke, {"width": 0.0}, - FadeOut(sphere_1ik_words) - ) - - # Start applying quaternion multiplication - kwargs = {"solid": False, "stroke_width": 0} - sphere_ijk.add_updater( - lambda s: s.become(self.get_projected_sphere(0, **kwargs)) - ) - sphere_1jk.add_updater( - lambda s: s.become(self.get_projected_sphere(1, **kwargs)) - ) - sphere_1ik.add_updater( - lambda s: s.become(self.get_projected_sphere(2, **kwargs)) - ) - sphere_1ij.add_updater( - lambda s: s.become(self.get_projected_sphere(3, **kwargs)) - ) - - self.set_quat([0, 1, 1, 1]) - - # - def project_quaternion(self, quat): - return self.axes.coords_to_point( - *stereo_project_point(quat, axis=0, r=1)[1:] - ) - - def get_dot(self): - if self.fancy_dot: - sphere = self.get_sphere() - sphere.set_width(0.2) - sphere.set_stroke(width=0) - sphere.set_fill(PINK) - return sphere - else: - return VGroup( - Dot(color=PINK), - Dot(color=PINK).rotate(TAU / 4, RIGHT), - ) - - def get_unit_labels(self): - c2p = self.axes.coords_to_point - tex_coords_vects = [ - ("i", [1, 0, 0], IN + RIGHT), - ("-i", [-1, 0, 0], IN + LEFT), - ("j", [0, 1, 0], UP + OUT + RIGHT), - ("-j", [0, -1, 0], RIGHT + DOWN), - ("k", [0, 0, 1], OUT + RIGHT), - ("-k", [0, 0, -1], IN + RIGHT), - ] - labels = VGroup() - for tex, coords, vect in tex_coords_vects: - label = TexMobject(tex) - label.scale(self.unit_labels_scale_factor) - label.rotate(90 * DEGREES, RIGHT) - label.next_to(c2p(*coords), vect, SMALL_BUFF) - labels.add(label) - labels.set_shade_in_3d(True) - labels.set_background_stroke(width=0) - return labels - - def set_quat(self, value, run_time=3, added_anims=None): - if added_anims is None: - added_anims = [] - self.play( - self.q_tracker.set_value, value, - *added_anims, - run_time=run_time - ) - - def get_projected_sphere(self, null_axis, quaternion=None, solid=True, **kwargs): - if quaternion is None: - quaternion = self.get_multiplier() - axes_to_color = { - 0: interpolate_color(YELLOW, BLACK, 0.5), - 1: GREEN_E, - 2: RED_D, - 3: BLUE_E, - } - color = axes_to_color[null_axis] - config = dict(self.sphere_config) - config.update({ - "stroke_color": WHITE, - "stroke_width": 0.5, - "stroke_opacity": 0.5, - "max_r": 24, - }) - if solid: - config.update({ - "checkerboard_colors": [ - color, interpolate_color(color, BLACK, 0.5) - ], - "fill_opacity": 1, - }) - else: - config.update({ - "checkerboard_colors": [], - "fill_color": color, - "fill_opacity": 0.25, - }) - config.update(kwargs) - sphere = StereoProjectedSphereFromHypersphere( - quaternion=quaternion, - null_axis=null_axis, - **config - ) - sphere.set_shade_in_3d(True) - return sphere - - def get_projected_circle(self, quaternion=None, **kwargs): - if quaternion is None: - quaternion = self.get_multiplier() - return StereoProjectedCircleFromHypersphere(quaternion, **kwargs) - - def get_multiplier(self): - return self.q_tracker.get_value() - - def specially_color_sphere(self, sphere): - sphere.set_color_by_gradient(BLUE, GREEN, PINK) - return sphere - # for submob in sphere: - # u, v = submob.u1, submob.v1 - # x = np.cos(v) * np.sin(u) - # y = np.sin(v) * np.sin(u) - # z = np.cos(u) - # # rgb = sum([ - # # (x**2) * hex_to_rgb(GREEN), - # # (y**2) * hex_to_rgb(RED), - # # (z**2) * hex_to_rgb(BLUE), - # # ]) - # # clip_in_place(rgb, 0, 1) - # # color = rgb_to_hex(rgb) - # color = interpolate_color(BLUE, RED, ((z**3) + 1) / 2) - # submob.set_fill(color) - # return sphere - - -class MissingLabels(Scene): - def construct(self): - labels = VGroup( - TexMobject("i").move_to(UR), - TexMobject("1").move_to(0.3 * DOWN), - TexMobject("-i").move_to(DOWN + 1.2 * LEFT), - TexMobject("-j").move_to(1.7 * RIGHT + 0.8 * DOWN), - ) - labels.set_background_stroke(width=0) - self.add(labels) - - -class RuleOfQuaternionMultiplicationOverlay(Scene): - def construct(self): - q_mob, times_mob, p_mob = q_times_p = TexMobject( - "q", "\\cdot", "p" - ) - q_times_p.scale(2) - q_mob.set_color(MAROON_B) - p_mob.set_color(YELLOW) - q_arrow = Vector(DOWN, color=WHITE) - q_arrow.next_to(q_mob, UP) - p_arrow = Vector(UP, color=WHITE) - p_arrow.next_to(p_mob, DOWN) - - q_words = TextMobject("Think of as\\\\ an action") - q_words.next_to(q_arrow, UP) - p_words = TextMobject("Think of as\\\\ a point") - p_words.next_to(p_arrow, DOWN) - - i_mob = TexMobject("i")[0] - i_mob.scale(2) - i_mob.move_to(q_mob, RIGHT) - i_mob.set_color(GREEN) - - self.add(q_times_p) - self.play( - FadeInFrom(q_words, UP), - GrowArrow(q_arrow), - ) - self.play( - FadeInFrom(p_words, DOWN), - GrowArrow(p_arrow), - ) - self.wait() - self.play(*map(FadeOut, [ - q_words, q_arrow, - p_words, p_arrow, - ])) - self.play( - FadeInFromDown(i_mob), - FadeOutAndShift(q_mob, UP) - ) - product = VGroup(i_mob, times_mob, p_mob) - self.play(product.to_edge, UP) - - # Show i products - underline = Line(LEFT, RIGHT) - underline.set_width(product.get_width() + MED_SMALL_BUFF) - underline.next_to(product, DOWN) - - kwargs = { - "tex_to_color_map": { - "i": GREEN, - "j": RED, - "k": BLUE - } - } - i_products = VGroup( - TexMobject("i", "\\cdot", "1", "=", "i", **kwargs), - TexMobject("i", "\\cdot", "i", "=", "-1", **kwargs), - TexMobject("i", "\\cdot", "j", "=", "k", **kwargs), - TexMobject("i", "\\cdot", "k", "=", "-j", **kwargs), - ) - i_products.scale(2) - i_products.arrange( - DOWN, buff=MED_LARGE_BUFF, - aligned_edge=LEFT, - ) - i_products.next_to(underline, DOWN, LARGE_BUFF) - i_products.align_to(i_mob, LEFT) - - self.play(ShowCreation(underline)) - self.wait() - for i_product in i_products: - self.play(TransformFromCopy( - product, i_product[:3] - )) - self.wait() - self.play(TransformFromCopy( - i_product[:3], i_product[3:], - )) - self.wait() - - rect = SurroundingRectangle( - VGroup(product, i_products), - buff=0.4 - ) - rect.set_stroke(WHITE, width=5) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - - -class RuleOfQuaternionMultiplication(HypersphereStereographicProjection): - CONFIG = { - "fancy_dot": True, - "initial_quaternion_sample_values": [], - } - - def construct(self): - self.setup_all_trackers() - self.show_multiplication_by_i_on_circle_1i() - self.show_multiplication_by_i_on_circle_jk() - self.show_multiplication_by_i_on_ijk_sphere() - - def setup_all_trackers(self): - self.setup_multiplier_tracker() - self.force_skipping() - self.setup_axes() - self.introduce_quaternion_label() - self.add_unit_labels() - self.revert_to_original_skipping_status() - - def setup_multiplier_tracker(self): - self.multiplier_tracker = QuaternionTracker([1, 0, 0, 0]) - self.multiplier_tracker.add_updater( - lambda m: m.set_value(normalize( - m.get_value(), - fall_back=[1, 0, 0, 0] - )) - ) - self.add(self.multiplier_tracker) - - def add_unit_labels(self): - labels = self.unit_labels = self.get_unit_labels() - one_label = TexMobject("1") - one_label.scale(self.unit_labels_scale_factor) - one_label.set_shade_in_3d(True) - one_label.rotate(90 * DEGREES, RIGHT) - one_label.next_to(ORIGIN, IN + RIGHT, SMALL_BUFF) - labels.add(one_label) - self.add(labels) - - def show_multiplication_by_i_on_circle_1i(self): - m_tracker = self.multiplier_tracker - - def get_circle_1i(): - return self.get_projected_circle( - basis_vectors=[ - [1, 0, 0, 0], - [1, 1, 0, 0], - ], - colors=[GREEN, YELLOW], - quaternion=m_tracker.get_value(), - ) - circle = get_circle_1i() - arrows = self.get_i_circle_arrows() - - def set_to_q_value(mt): - mt.set_value(self.q_tracker.get_value()) - - self.play(ShowCreation(circle, run_time=2)) - self.play(LaggedStartMap(ShowCreation, arrows, lag_ratio=0.25)) - self.wait() - circle.add_updater(lambda c: c.become(get_circle_1i())) - m_tracker.add_updater(set_to_q_value) - self.add(m_tracker) - self.set_quat([0, 1, 0, 0]) - self.wait() - self.set_quat([-1, 0.001, 0, 0]) - self.wait() - self.q_tracker.set_value([-1, -0.001, 0, 0]) - self.set_quat([0, -1, 0, 0]) - self.wait() - self.set_quat([1, 0, 0, 0]) - self.wait(3) - self.play(FadeOut(arrows)) - - m_tracker.remove_updater(set_to_q_value) - self.circle_1i = circle - - def show_multiplication_by_i_on_circle_jk(self): - def get_circle_jk(): - return self.get_projected_circle( - basis_vectors=[ - [0, 0, 1, 0], - [0, 0, 0, 1], - ], - colors=[RED, BLUE_E] - ) - circle = get_circle_jk() - arrows = self.get_jk_circle_arrows() - m_tracker = self.multiplier_tracker - q_tracker = self.q_tracker - - def set_q_to_mj(qt): - qt.set_value(q_mult( - m_tracker.get_value(), [0, 0, 1, 0] - )) - - self.move_camera(theta=-50 * DEGREES) - self.play(ShowCreation(circle, run_time=2)) - circle.add_updater(lambda c: c.become(get_circle_jk())) - self.wait(10) - self.stop_ambient_camera_rotation() - self.begin_ambient_camera_rotation(rate=-0.01) - self.play(*map(ShowCreation, arrows)) - self.wait() - self.set_quat([0, 0, 1, 0], run_time=1) - q_tracker.add_updater(set_q_to_mj, index=0) - self.add(self.circle_1i) - self.play( - m_tracker.set_value, [0, 1, 0, 0], - run_time=3 - ) - self.wait() - self.play( - m_tracker.set_value, [-1, 0.001, 0, 0], - run_time=3 - ) - self.wait() - m_tracker.set_value([-1, 0.001, 0, 0]) - self.play( - m_tracker.set_value, [0, -1, 0, 0], - run_time=3 - ) - self.wait() - self.play( - m_tracker.set_value, [1, 0, 0, 0], - run_time=3 - ) - self.wait() - q_tracker.remove_updater(set_q_to_mj) - self.play( - FadeOut(arrows), - q_tracker.set_value, [1, 0, 0, 0], - ) - self.wait(10) - - self.circle_jk = circle - - def show_multiplication_by_i_on_ijk_sphere(self): - m_tracker = self.multiplier_tracker - q_tracker = self.q_tracker - m_tracker.add_updater(lambda m: m.set_value(q_tracker.get_value())) - - def get_sphere(): - result = self.get_projected_sphere(null_axis=0, solid=False) - self.specially_color_sphere(result) - return result - - sphere = get_sphere() - - self.play(Write(sphere)) - sphere.add_updater(lambda s: s.become(get_sphere())) - - self.set_quat([0, 1, 0, 0]) - self.wait() - self.set_quat([-1, 0.001, 0, 0]) - self.wait() - self.q_tracker.set_value([-1, -0.001, 0, 0]) - self.set_quat([0, -1, 0, 0]) - self.wait() - self.set_quat([1, 0, 0, 0]) - self.wait(3) - - # - def get_multiplier(self): - return self.multiplier_tracker.get_value() - - def get_i_circle_arrows(self): - c2p = self.axes.coords_to_point - i_arrow = Arrow( - ORIGIN, 2 * RIGHT, path_arc=-120 * DEGREES, - buff=SMALL_BUFF, - ) - neg_one_arrow = Arrow( - ORIGIN, 5.5 * RIGHT + UP, - path_arc=-30 * DEGREES, - buff=SMALL_BUFF, - ) - neg_i_arrow = Arrow( - 4.5 * LEFT + 1.5 * UP, ORIGIN, - path_arc=-30 * DEGREES, - buff=SMALL_BUFF, - ) - one_arrow = i_arrow.copy() - result = VGroup(i_arrow, neg_one_arrow, neg_i_arrow, one_arrow) - for arrow in result: - arrow.set_color(LIGHT_GREY) - arrow.set_stroke(width=3) - arrow.rotate(90 * DEGREES, RIGHT) - i_arrow.next_to(c2p(0, 0, 0), OUT + RIGHT, SMALL_BUFF) - neg_one_arrow.next_to(c2p(1, 0, 0), OUT + RIGHT, SMALL_BUFF) - neg_i_arrow.next_to(c2p(-1, 0, 0), OUT + LEFT, SMALL_BUFF) - one_arrow.next_to(c2p(0, 0, 0), OUT + LEFT, SMALL_BUFF) - return result - - def get_jk_circle_arrows(self): - arrow = Arrow( - 1.5 * RIGHT, 1.5 * UP, - path_arc=90 * DEGREES, - buff=SMALL_BUFF, - use_rectangular_stem=False - ) - arrow.set_color(LIGHT_GREY) - arrow.set_stroke(width=3) - arrows = VGroup(*[ - arrow.copy().rotate(angle, about_point=ORIGIN) - for angle in np.arange(0, TAU, TAU / 4) - ]) - arrows.rotate(90 * DEGREES, RIGHT) - arrows.rotate(90 * DEGREES, OUT) - return arrows - - -class ShowDistributionOfI(TeacherStudentsScene): - def construct(self): - tex_to_color_map = { - "q": PINK, - "w": YELLOW, - "x": GREEN, - "y": RED, - "z": BLUE, - } - top_product = TexMobject( - "q", "\\cdot", "\\left(", - "w", "1", "+", "x", "i", "+", "y", "j", "+", "z", "k", - "\\right)" - ) - top_product.to_edge(UP) - self.add(top_product) - bottom_product = TexMobject( - "w", "q", "\\cdot", "1", - "+", "x", "q", "\\cdot", "i", - "+", "y", "q", "\\cdot", "j", - "+", "z", "q", "\\cdot", "k", - ) - bottom_product.next_to(top_product, DOWN, MED_LARGE_BUFF) - - for product in [top_product, bottom_product]: - for tex, color in tex_to_color_map.items(): - product.set_color_by_tex(tex, color, substring=False) - - self.student_says( - "What does it do \\\\ to other quaternions?", - target_mode="raise_left_hand" - ) - self.change_student_modes( - "pondering", "raise_left_hand", "erm", - look_at_arg=top_product, - ) - self.wait(2) - self.play( - self.teacher.change, "raise_right_hand", - RemovePiCreatureBubble(self.students[1], target_mode="pondering"), - *[ - TransformFromCopy( - top_product.get_parts_by_tex(tex, substring=False), - bottom_product.get_parts_by_tex(tex, substring=False), - run_time=2 - ) - for tex in ["1", "w", "x", "i", "y", "j", "z", "k", "+"] - ] - ) - self.play(*[ - TransformFromCopy( - top_product.get_parts_by_tex(tex, substring=False), - bottom_product.get_parts_by_tex(tex, substring=False), - run_time=2 - ) - for tex in ["q", "\\cdot"] - ]) - self.change_all_student_modes("thinking") - self.wait(3) - - -class ComplexPlane135(Scene): - def construct(self): - plane = ComplexPlane(unit_size=2) - plane.add_coordinates() - for mob in plane.coordinate_labels: - mob.scale(2, about_edge=UR) - - angle = 3 * TAU / 8 - circle = Circle(radius=2, color=YELLOW) - arc = Arc(angle, radius=0.5) - angle_label = Integer(0, unit="^\\circ") - angle_label.next_to(arc.point_from_proportion(0.5), UR, SMALL_BUFF) - line = Line(ORIGIN, 2 * RIGHT) - - point = circle.point_from_proportion(angle / TAU) - dot = Dot(point, color=PINK) - arrow = Vector(DR) - arrow.next_to(dot, UL, SMALL_BUFF) - arrow.match_color(dot) - label = TexMobject("-\\frac{\\sqrt{2}}{2} + \\frac{\\sqrt{2}}{2} i") - label.next_to(arrow.get_start(), UP, SMALL_BUFF) - label.set_background_stroke(width=0) - - self.add(plane, circle, line, dot, label, arrow) - self.play( - Rotate(line, angle, about_point=ORIGIN), - ShowCreation(arc), - ChangeDecimalToValue(angle_label, 135), - run_time=3 - ) - self.wait() - - -class ShowMultiplicationBy135Example(RuleOfQuaternionMultiplication): - CONFIG = { - "fancy_dot": True, - } - - def construct(self): - self.setup_all_trackers() - self.add_circles() - self.add_ijk_sphere() - self.show_multiplication() - - def add_circles(self): - self.circle_1i = self.add_auto_updating_circle( - basis_vectors=[ - [1, 0, 0, 0], - [0, 1, 0, 0], - ], - colors=[YELLOW, GREEN_E] - ) - self.circle_jk = self.add_auto_updating_circle( - basis_vectors=[ - [0, 0, 1, 0], - [0, 0, 0, 1], - ], - colors=[RED, BLUE_E] - - ) - - def add_auto_updating_circle(self, **circle_config): - circle = self.get_projected_circle(**circle_config) - circle.add_updater( - lambda c: c.become(self.get_projected_circle(**circle_config)) - ) - self.add(circle) - return circle - - def add_ijk_sphere(self): - def get_sphere(): - result = self.get_projected_sphere( - null_axis=0, - solid=False, - stroke_width=0.5, - stroke_opacity=0.2, - fill_opacity=0.2, - ) - self.specially_color_sphere(result) - return result - sphere = get_sphere() - sphere.add_updater(lambda s: s.become(get_sphere())) - self.add(sphere) - self.sphere = sphere - - def show_multiplication(self): - m_tracker = self.multiplier_tracker - - quat = normalize(np.array([-1, 1, 0, 0])) - point = self.project_quaternion(quat) - arrow = Vector(DR) - arrow.next_to(point, UL, MED_SMALL_BUFF) - arrow.set_color(PINK) - label = TexMobject( - "-{\\sqrt{2} \\over 2}", "+", - "{\\sqrt{2} \\over 2}", "i", - ) - label.next_to(arrow.get_start(), UP) - label.set_background_stroke(width=0) - - def get_one_point(): - return self.circle_1i[0].points[0] - - def get_j_point(): - return self.circle_jk[0].points[0] - - one_point = VectorizedPoint() - one_point.add_updater(lambda v: v.set_location(get_one_point())) - self.add(one_point) - - hand = Hand() - hand.rotate(45 * DEGREES, RIGHT) - hand.add_updater( - lambda h: h.move_to(get_one_point(), LEFT) - ) - - j_line = Line(ORIGIN, get_j_point()) - moving_j_line = j_line.deepcopy() - moving_j_line.add_updater( - lambda m: m.put_start_and_end_on(ORIGIN, get_j_point()) - ) - - self.add(j_line, moving_j_line) - self.set_camera_orientation( - phi=60 * DEGREES, theta=-70 * DEGREES - ) - self.play( - FadeInFromLarge(label, 3), - GrowArrow(arrow) - ) - self.set_quat(quat) - self.wait(5) - self.play(FadeInFromLarge(hand)) - self.add(m_tracker) - for q in [quat, [1, 0, 0, 0], quat]: - self.play( - m_tracker.set_value, q, - UpdateFromFunc( - m_tracker, - lambda m: m.set_value(normalize(m.get_value())) - ), - run_time=5 - ) - self.wait() - - -class JMultiplicationChart(Scene): - def construct(self): - # Largely copy-pasted....what are you gonna do about it? - product = TexMobject("j", "\\cdot", "p") - product[0].set_color(RED) - product.scale(2) - product.to_edge(UP) - - underline = Line(LEFT, RIGHT) - underline.set_width(product.get_width() + MED_SMALL_BUFF) - underline.next_to(product, DOWN) - - kwargs = { - "tex_to_color_map": { - "i": GREEN, - "j": RED, - "k": BLUE - } - } - j_products = VGroup( - TexMobject("j", "\\cdot", "1", "=", "j", **kwargs), - TexMobject("j", "\\cdot", "j", "=", "-1", **kwargs), - TexMobject("j", "\\cdot", "i", "=", "-k", **kwargs), - TexMobject("j", "\\cdot", "k", "=", "i", **kwargs), - ) - j_products.scale(2) - j_products.arrange( - DOWN, buff=MED_LARGE_BUFF, - aligned_edge=LEFT, - ) - j_products.next_to(underline, DOWN, LARGE_BUFF) - j_products.align_to(product, LEFT) - - self.play(FadeInFromDown(product)) - self.play(ShowCreation(underline)) - self.wait() - for j_product in j_products: - self.play(TransformFromCopy( - product, j_product[:3] - )) - self.wait() - self.play(TransformFromCopy( - j_product[:3], j_product[3:], - )) - self.wait() - - rect = SurroundingRectangle( - VGroup(product, j_products), - buff=MED_SMALL_BUFF - ) - rect.set_stroke(WHITE, width=5) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - - -class ShowJMultiplication(ShowMultiplicationBy135Example): - CONFIG = { - "fancy_dot": True, - "run_time_per_rotation": 4, - } - - def construct(self): - self.setup_all_trackers() - self.add_circles() - self.add_ijk_sphere() - self.show_multiplication() - - def add_circles(self): - self.circle_1j = self.add_auto_updating_circle( - basis_vectors=[ - [1, 0, 0, 0], - [0, 0, 1, 0], - ], - colors=[YELLOW, RED] - ) - self.circle_ik = self.add_auto_updating_circle( - basis_vectors=[ - [0, 1, 0, 0], - [0, 0, 0, 1], - ], - colors=[GREEN, BLUE_E] - ) - - def show_multiplication(self): - self.set_camera_orientation(theta=-80 * DEGREES) - - q_tracker = self.q_tracker - m_tracker = self.multiplier_tracker - - def normalize_tracker(t): - t.set_value(normalize(t.get_value())) - - updates = [ - UpdateFromFunc(tracker, normalize_tracker) - for tracker in (q_tracker, m_tracker) - ] - - run_time = self.run_time_per_rotation - self.play( - m_tracker.set_value, [0, 0, 1, 0], - q_tracker.set_value, [0, 0, 1, 0], - *updates, - run_time=run_time, - ) - self.wait(2) - self.play( - m_tracker.set_value, [-1, 0, 1e-3, 0], - q_tracker.set_value, [-1, 0, 1e-3, 0], - *updates, - run_time=run_time, - ) - self.wait(2) - - # Show ik circle - circle = self.circle_ik.deepcopy() - circle.clear_updaters() - self.play(FadeInFromLarge(circle, remover=True)) - m_tracker.set_value([-1, 0, 0, 0]) - q_tracker.set_value([0, 1, 0, 0]) - self.wait() - self.play( - m_tracker.set_value, [0, 0, -1, 0], - q_tracker.set_value, [0, 0, 0, -1], - *updates, - run_time=run_time, - ) - self.wait(2) - self.play( - m_tracker.set_value, [1, 0, -1e-3, 0], - q_tracker.set_value, [0, -1, 0, 0], - *updates, - run_time=run_time, - ) - self.wait(2) - - -class ShowArbitraryMultiplication(ShowMultiplicationBy135Example): - CONFIG = { - "fancy_dot": True, - "run_time_per_rotation": 4, - "special_quaternion": [-0.5, 0.5, 0.5, 0.5], - } - - def construct(self): - self.setup_all_trackers() - self.add_circles() - self.add_ijk_sphere() - self.show_multiplication() - - def add_circles(self): - self.circle1 = self.add_auto_updating_circle( - basis_vectors=[ - [1, 0, 0, 0], - [0, 1, 1, 1], - ], - colors=[YELLOW_E, YELLOW] - ) - bv1 = normalize([0, -1, -1, 2]) - bv2 = [0] + list(normalize(np.cross([1, 1, 1], bv1[1:]))) - self.circle2 = self.add_auto_updating_circle( - basis_vectors=[bv1, bv2], - colors=[WHITE, GREY] - ) - - def show_multiplication(self): - q_tracker = self.q_tracker - m_tracker = self.multiplier_tracker - run_time = self.run_time_per_rotation - - def normalize_tracker(t): - t.set_value(normalize(t.get_value())) - - # for tracker in q_tracker, m_tracker: - # self.add(Mobject.add_updater(tracker, normalize_tracker)) - updates = [ - UpdateFromFunc(tracker, normalize_tracker) - for tracker in (q_tracker, m_tracker) - ] - - special_q = self.special_quaternion - pq_point = self.project_quaternion(special_q) - label = TextMobject("Some unit quaternion") - label.set_color(PINK) - label.rotate(90 * DEGREES, RIGHT) - label.next_to(pq_point, IN + RIGHT, SMALL_BUFF) - - circle1, circle2 = self.circle1, self.circle2 - for circle in [circle1, circle2]: - circle.tucked_away_updaters = circle.updaters - circle.clear_updaters() - self.remove(circle) - - hand = Hand() - hand.rotate(90 * DEGREES, RIGHT) - hand.move_to(ORIGIN, LEFT) - hand.set_shade_in_3d(True) - one_dot = self.get_dot() - one_dot.set_color(YELLOW_E) - one_dot.move_to(ORIGIN) - one_dot.add_updater( - lambda m: m.move_to(circle1[0].points[0]) - ) - self.add(one_dot) - - self.stop_ambient_camera_rotation() - self.begin_ambient_camera_rotation(rate=0.02) - self.set_quat(special_q) - self.play(FadeInFrom(label, IN)) - self.wait(3) - for circle in [circle1, circle2]: - self.play(ShowCreation(circle, run_time=3)) - circle.updaters = circle.tucked_away_updaters - self.wait(2) - self.play( - FadeInFrom(hand, 2 * IN + 2 * RIGHT), - run_time=2 - ) - hand.add_updater( - lambda h: h.move_to(circle1[0].points[0], LEFT) - ) - - for quat in [special_q, [1, 0, 0, 0], special_q]: - self.play( - m_tracker.set_value, special_q, - *updates, - run_time=run_time, - ) - self.wait() - - -class MentionCommutativity(TeacherStudentsScene): - def construct(self): - kwargs = { - "tex_to_color_map": { - "q": MAROON_B, - "p": YELLOW, - "i": GREEN, - "j": RED, - "k": BLUE, - } - } - general_eq = TexMobject("q \\cdot p \\ne p \\cdot q", **kwargs) - general_eq.get_part_by_tex("\\ne").submobjects.reverse() - ij_eq = TexMobject("i \\cdot j = k", **kwargs) - ji_eq = TexMobject("j \\cdot i = -k", **kwargs) - - for mob in [general_eq, ij_eq, ji_eq]: - mob.move_to(self.hold_up_spot, DOWN) - - words = TextMobject("Multiplication doesn't \\\\ commute") - words.next_to(general_eq, UP, MED_LARGE_BUFF) - words.shift_onto_screen() - - joke = TextMobject("Quaternions work from home") - joke.scale(0.75) - joke.to_corner(UL, MED_SMALL_BUFF) - - self.play( - FadeInFromDown(general_eq), - self.teacher.change, "raise_right_hand", - self.get_student_changes("erm", "confused", "sassy") - ) - self.play(FadeInFrom(words, RIGHT)) - self.wait(2) - self.play( - ReplacementTransform(words, joke), - general_eq.shift, UP, - FadeInFromDown(ij_eq), - self.get_student_changes(*["pondering"] * 3) - ) - self.look_at(self.screen) - self.wait(3) - self.play( - FadeInFrom(ji_eq), - LaggedStartMap( - ApplyMethod, VGroup(ij_eq, general_eq), - lambda m: (m.shift, UP), - lag_ratio=0.8, - ) - ) - self.look_at(self.screen) - self.wait(5) - - -class RubuiksCubeOperations(SpecialThreeDScene): - def construct(self): - self.set_camera_orientation(**self.get_default_camera_position()) - self.begin_ambient_camera_rotation() - cube = RubiksCube() - cube.shift(2.5 * RIGHT) - cube2 = cube.copy() - - self.add(cube) - self.play( - Rotate(cube.get_face(RIGHT), 90 * DEGREES, RIGHT), - run_time=2 - ) - self.play( - Rotate(cube.get_face(DOWN), 90 * DEGREES, UP), - run_time=2 - ) - self.wait() - self.play( - cube.shift, 5 * LEFT, - FadeIn(cube2) - ) - self.play( - Rotate(cube2.get_face(DOWN), 90 * DEGREES, UP), - run_time=2 - ) - self.play( - Rotate(cube2.get_face(RIGHT), 90 * DEGREES, RIGHT), - run_time=2 - ) - self.wait(6) - - -class RotationsOfCube(SpecialThreeDScene): - def construct(self): - self.set_camera_orientation(**self.get_default_camera_position()) - self.begin_ambient_camera_rotation(0.0001) - cube = RubiksCube() - cube2 = cube.copy() - axes = self.get_axes() - axes.scale(0.75) - - label1 = TextMobject( - "z-axis\\\\", - "then x-axis" - ) - label2 = TextMobject( - "x-axis\\\\", - "then z-axis" - ) - for label in [label1, label2]: - for part in label: - part.add_background_rectangle() - label.rotate(90 * DEGREES, RIGHT) - label.move_to(3 * OUT + 0.5 * IN) - - self.add(axes, cube) - self.play( - Rotate(cube, 90 * DEGREES, OUT, run_time=2), - FadeInFrom(label1[0], IN), - ) - self.play( - Rotate(cube, 90 * DEGREES, RIGHT, run_time=2), - FadeInFrom(label1[1], IN), - ) - self.wait() - self.play( - cube.shift, 5 * RIGHT, - label1.shift, 5 * RIGHT, - Write(cube2, run_time=1) - ) - self.play( - Rotate(cube2, 90 * DEGREES, RIGHT, run_time=2), - FadeInFrom(label2[0], IN), - ) - self.play( - Rotate(cube2, 90 * DEGREES, OUT, run_time=2), - FadeInFrom(label2[1], IN), - ) - self.wait(5) - - -class OneFinalPoint(TeacherStudentsScene): - def construct(self): - self.teacher_says("One final point!") - self.change_student_modes("happy", "tease", "thinking") - self.wait(3) - - -class MultiplicationFromTheRight(Scene): - def construct(self): - i, dot, j = product = TexMobject("i", "\\cdot", "j") - product.set_height(1.5) - product.to_edge(UP, buff=LARGE_BUFF) - i.set_color(GREEN) - j.set_color(RED) - i_rect = SurroundingRectangle(i) - j_rect = SurroundingRectangle(j) - i_rect.match_height(j_rect, about_edge=UP, stretch=True) - VGroup(i_rect, j_rect).set_color(WHITE) - action_words = TextMobject("Think of as \\\\ an action") - point_words = TextMobject("Think of as \\\\ a point") - action_words.next_to(i_rect, LEFT) - point_words.next_to(j_rect, RIGHT) - - arrow = Arrow( - i.get_top(), - j.get_top(), - path_arc=-PI, - ) - arrow.set_stroke(width=2) - - self.add(product) - self.play(ShowCreation(arrow)) - self.play( - FadeIn(i_rect), - Write(action_words) - ) - self.wait() - self.play( - FadeIn(j_rect), - Write(point_words) - ) - self.wait(2) - self.play(arrow.flip) - self.play(Swap(point_words, action_words)) - - -class MultiplicationByJFromRight(ShowJMultiplication): - def show_multiplication(self): - self.set_camera_orientation(theta=-80 * DEGREES) - - q_tracker = self.q_tracker - m_tracker = self.multiplier_tracker - - def normalize_tracker(t): - t.set_value(normalize(t.get_value())) - - updates = [ - UpdateFromFunc(tracker, normalize_tracker) - for tracker in (q_tracker, m_tracker) - ] - - run_time = self.run_time_per_rotation - - m_values = [[1, 0, 0, 0]] - q_values = [[0, 1, 0, 0]] - for values in m_values, q_values: - for x in range(4): - values.append( - q_mult(values[-1], [0, 0, 1, 0]) - ) - - for m_val, q_val in zip(m_values, q_values): - self.play( - m_tracker.set_value, m_val, - q_tracker.set_value, q_val, - *updates, - run_time=run_time, - ) - self.wait(2) - - def get_projected_sphere(self, *args, **kwargs): - kwargs["multiply_from_right"] = True - return ShowJMultiplication.get_projected_sphere( - self, *args, **kwargs - ) - - def get_projected_circle(self, *args, **kwargs): - kwargs["multiply_from_right"] = True - return ShowJMultiplication.get_projected_circle( - self, *args, **kwargs - ) - - -class HowQuaternionsRotate3dPoints(Scene): - def construct(self): - title = TextMobject( - "Coming up:\\\\", - "How quaternions act on 3d points" - ) - title.to_edge(UP) - - expression = TexMobject( - "q", "\\cdot", "p", "\\cdot", "q^{-1}" - ) - expression.scale(2) - expression.set_color_by_tex("q", PINK) - expression.set_color_by_tex("p", YELLOW) - - right_arrow = Arrow( - expression[0].get_top(), - expression[2].get_top(), - path_arc=-PI, - color=WHITE, - ) - right_arrow.set_stroke(width=4) - left_arrow = right_arrow.copy() - left_arrow.flip(about_point=expression[2].get_top()) - left_arrow.shift(SMALL_BUFF * RIGHT) - - self.add(title) - self.play(Write(expression)) - self.play(ShowCreation(right_arrow)) - self.play(ShowCreation(left_arrow)) - self.wait() - - -class HoldUpQuanta(TeacherStudentsScene): - def construct(self): - logo = ImageMobject("Quanta logo") - logo.set_width(6) - logo.next_to(self.teacher, UR) - logo.to_edge(RIGHT, buff=2) - - words = TextMobject("Associated quaternion post") - words.to_edge(UP, buff=LARGE_BUFF) - arrow = Arrow(logo.get_top(), words.get_bottom()) - - self.play( - FadeInFromDown(logo), - self.teacher.change, "raise_right_hand" - ) - self.change_all_student_modes("hooray") - self.play( - GrowArrow(arrow), - GrowFromPoint(words, arrow.get_start()) - ) - self.wait(3) - - -class ShareWithFriends(PiCreatureScene): - def construct(self): - pi1, pi2 = self.pi_creatures - - self.pi_creature_says( - pi1, "Come learn about \\\\ quaternions!", - bubble_kwargs={ - "direction": LEFT, - "width": 4, - "height": 3, - }, - target_mode="hooray", - look_at_arg=pi2.eyes, - added_anims=[ - ApplyMethod( - pi2.change, "confused", - run_time=1.5, - rate_func=squish_rate_func(smooth, 0.3, 1) - ) - ] - ) - bubble = ThoughtBubble( - direction=RIGHT, - width=4, - height=4, - ) - bubble.pin_to(pi2) - bubble.write("What's that, some\\\\kind of particle?") - bubble.resize_to_content() - VGroup(bubble, bubble.content).shift_onto_screen(buff=SMALL_BUFF) - self.play( - pi2.look_at, pi1.eyes, - pi1.look_at, pi2.eyes, - ShowCreation(bubble), - Write(bubble.content), - ) - self.wait(1) - self.play( - RemovePiCreatureBubble(pi1, target_mode="raise_right_hand") - ) - self.wait() - - time_words = TextMobject("Oh man...\\\\30 minutes") - time_words.move_to(bubble.content) - self.play( - Animation(VectorizedPoint().next_to(pi1, UL, LARGE_BUFF)), - pi2.change, "sad", - FadeOutAndShift(bubble.content, DOWN), - FadeInFromDown(time_words, DOWN), - ) - self.wait(7) - - def create_pi_creatures(self): - pi1 = PiCreature(color=BLUE) - pi1.to_edge(DOWN) - pi1.flip() - pi1.shift(2.5 * RIGHT) - pi2 = PiCreature(color=RED) - pi2.to_edge(DOWN) - pi2.shift(2 * LEFT) - return [pi1, pi2] - - -class QuaternionEndscreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "Juan Benet", - "Matt Russell", - "soekul", - "Desmos", - "Burt Humburg", - "Dinesh Dharme", - "Scott Walter", - "Brice Gower", - "Peter Mcinerney", - "brian tiger chow", - "Joseph Kelly", - "Roy Larson", - "Andrew Sachs", - "Hoàng Tùng Lâm", - "Devin Scott", - "Akash Kumar", - "Arthur Zey", - "David Kedmey", - "Ali Yahya", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Yu Jun", - "dave nicponski", - "Jordan Scales", - "Markus Persson", - "Lukáš Nový", - "Fela", - "Randy C. Will", - "Britt Selvitelle", - "Jonathan Wilson", - "Ryan Atallah", - "Joseph John Cox", - "Luc Ritchie", - "Ryan Williams", - "Michael Hardel", - "Federico Lebron", - "L0j1k", - "Ayan Doss", - "Dylan Houlihan", - "Steven Soloway", - "Art Ianuzzi", - "Nate Heckmann", - "Michael Faust", - "Richard Comish", - "Nero Li", - "Valeriy Skobelev", - "Adrian Robinson", - "Solara570", - "Peter Ehrnstrom", - "Kai Siang Ang", - "Alexis Olson", - "Ludwig Schubert", - "Omar Zrien", - "Sindre Reino Trosterud", - "Jeff Straathof", - "Matt Langford", - "Matt Roveto", - "Magister Mugit", - "Stevie Metke", - "Cooper Jones", - "James Hughes", - "John V Wertheim", - "Song Gao", - "Richard Burgmann", - "John Griffith", - "Chris Connett", - "Steven Tomlinson", - "Jameel Syed", - "Bong Choung", - "Zhilong Yang", - "Giovanni Filippi", - "Eric Younge", - "Prasant Jagannath", - "Cody Brocious", - "James H. Park", - "Norton Wang", - "Kevin Le", - "Oliver Steele", - "Yaw Etse", - "Dave B", - "Delton Ding", - "Thomas Tarler", - "1stViewMaths", - "Jacob Magnuson", - "Clark Gaebel", - "Mathias Jansson", - "David Clark", - "Michael Gardner", - "Mads Elvheim", - "Awoo", - "Dr David G. Stork", - "Ted Suzman", - "Linh Tran", - "Andrew Busey", - "John Haley", - "Ankalagon", - "Eric Lavault", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Robert Teed", - "Jason Hise", - "Bernd Sing", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ], - } - - -class ThumbnailP1(RuleOfQuaternionMultiplication): - CONFIG = { - "three_d_axes_config": { - "num_axis_pieces": 20, - }, - "unit_labels_scale_factor": 1.5, - "quaternion": [1, 0, 0, 0], - } - - def construct(self): - self.setup_all_trackers() - self.remove(self.pink_dot_label) - q_tracker = self.q_tracker - m_tracker = self.multiplier_tracker - - # quat = normalize([-0.5, 0.5, -0.5, 0.5]) - quat = normalize(self.quaternion) - m_tracker.set_value(quat) - q_tracker.set_value(quat) - proj_sphere = self.get_projected_sphere(0, solid=False) - # self.specially_color_sphere(proj_sphere) - proj_sphere.set_color_by_gradient( - BLUE, YELLOW - ) - proj_sphere.set_stroke(WHITE) - proj_sphere.set_fill(opacity=0.4) - for i, face in enumerate(proj_sphere): - alpha = i / len(proj_sphere) - opacity = 0.7 * (1 - there_and_back(alpha)) - face.set_fill(opacity=opacity) - - # unit_sphere = self.get_projected_sphere(0, quaternion=[1, 0, 0, 0], solid=False) - # self.specially_color_sphere(unit_sphere) - # unit_sphere.set_stroke(width=0) - # proj_sphere.set_fill_by_checkerboard(BLUE_E, BLUE, opacity=0.8) - for face in proj_sphere: - face.points = face.points[::-1] - max_r = np.max(np.apply_along_axis(get_norm, 1, face.points)) - if max_r > 30: - face.fade(1) - - for label in self.unit_labels: - label.set_shade_in_3d(False) - label.set_background_stroke(color=BLACK, width=2) - - self.add(proj_sphere) - # self.add(unit_sphere) - - for mobject in self.mobjects: - try: - mobject.shift(IN) - except ValueError: - pass - - self.set_camera_orientation( - phi=70 * DEGREES, - theta=-110 * DEGREES, - ) - - -class ThumbnailP2(ThumbnailP1): - CONFIG = { - "quaternion": [0, 1, 0, 0], - } - - -class ThumbnailOverlay(Scene): - def construct(self): - title = TextMobject("Quaternions \\\\", "visualized") - title.set_width(7) - # title[1].scale(0.7, about_edge=UP) - title.to_edge(UP, buff=MED_SMALL_BUFF) - v_line = Line(DOWN, UP) - v_line.set_height(FRAME_HEIGHT) - - title.set_background_stroke(color=BLACK, width=1) - - for part in (title[0][4:6], title[1][4:5]): - rect = BackgroundRectangle(part) - rect.set_fill(opacity=1) - rect.stretch(0.9, 0) - rect.stretch(1.1, 1) - title.add_to_back(rect) - # title.add_to_back(BackgroundRectangle(title[0])) - - arrow = Arrow(LEFT, RIGHT) - arrow.scale(1.5) - arrow.tip.scale(2) - arrow.set_stroke(width=10) - arrow.set_color(YELLOW) - - self.add(v_line) - self.add(arrow) - self.add(title) diff --git a/from_3b1b/old/sphere_area.py b/from_3b1b/old/sphere_area.py deleted file mode 100644 index 5d3c86a1..00000000 --- a/from_3b1b/old/sphere_area.py +++ /dev/null @@ -1,3708 +0,0 @@ -from manimlib.imports import * -from active_projects.shadows import * - - -# Abstract scenes - -class Cylinder(Sphere): - """ - Inherits from sphere so as to be as aligned as possible - for transformations - """ - - def func(self, u, v): - return np.array([ - np.cos(v), - np.sin(v), - np.cos(u) - ]) - - -class UnwrappedCylinder(Cylinder): - def func(self, u, v): - return np.array([ - v - PI, - -self.radius, - np.cos(u) - ]) - - -class ParametricDisc(Sphere): - CONFIG = { - "u_min": 0, - "u_max": 1, - "stroke_width": 0, - "checkerboard_colors": [BLUE_D], - } - - def func(self, u, v): - return np.array([ - u * np.cos(v), - u * np.sin(v), - 0, - ]) - - -class SphereCylinderScene(SpecialThreeDScene): - CONFIG = { - "cap_config": { - "stroke_width": 1, - "stroke_color": WHITE, - "fill_color": BLUE_D, - "fill_opacity": 1, - } - } - - def get_cylinder(self, **kwargs): - config = merge_dicts_recursively(self.sphere_config, kwargs) - return Cylinder(**config) - - def get_cylinder_caps(self): - R = self.sphere_config["radius"] - caps = VGroup(*[ - Circle( - radius=R, - **self.cap_config, - ).shift(R * vect) - for vect in [IN, OUT] - ]) - caps.set_shade_in_3d(True) - return caps - - def get_unwrapped_cylinder(self): - return UnwrappedCylinder(**self.sphere_config) - - def get_xy_plane(self): - pass - - def get_ghost_surface(self, surface): - result = surface.copy() - result.set_fill(BLUE_E, opacity=0.5) - result.set_stroke(WHITE, width=0.5, opacity=0.5) - return result - - def project_point(self, point): - radius = self.sphere_config["radius"] - result = np.array(point) - result[:2] = normalize(result[:2]) * radius - return result - - def project_mobject(self, mobject): - return mobject.apply_function(self.project_point) - - -# Scenes for video - -class AskAboutShadowRelation(SpecialThreeDScene): - CONFIG = { - "R_color": YELLOW, - "space_out_factor": 1.15, - } - - def construct(self): - self.show_surface_area() - self.show_four_circles() - self.ask_why() - self.show_all_pieces() - - def show_surface_area(self): - sphere = self.get_sphere() - sphere.set_fill(BLUE_E, opacity=0.5) - sphere.add_updater( - lambda s, dt: s.rotate(0.1 * dt, axis=OUT) - ) - pieces = sphere.deepcopy() - pieces.space_out_submobjects(1.5) - pieces.shift(IN) - pieces.set_color(GREEN) - - # radial_line = Line(ORIGIN, sphere.get_right()) - # R_label = TexMobject("R") - # R_label.set_color(BLUE) - # R_label.rotate(90 * DEGREES, RIGHT) - # R_label.next_to(radial_line, OUT, SMALL_BUFF) - - sa_equation = TexMobject( - "\\text{Surface area} = 4\\pi R^2", - tex_to_color_map={"R": BLUE} - ) - sa_equation.scale(1.5) - sa_equation.to_edge(UP) - - self.set_camera_orientation( - phi=70 * DEGREES, - theta=-90 * DEGREES, - ) - self.add_fixed_in_frame_mobjects(sa_equation) - self.play( - Write(sphere, stroke_width=1), - FadeInFromDown(sa_equation), - # ShowCreation(radial_line), - # FadeInFrom(R_label, IN), - ) - # self.play( - # Transform( - # sphere, pieces, - # rate_func=there_and_back_with_pause, - # run_time=2 - # ) - # ) - self.play(LaggedStartMap( - UpdateFromAlphaFunc, sphere, - lambda mob: (mob, lambda m, a: m.set_fill( - color=interpolate_color(BLUE_E, YELLOW, a), - opacity=interpolate(0.5, 1, a) - )), - rate_func=there_and_back, - lag_ratio=0.1, - run_time=4 - )) - self.play( - sphere.scale, 0.75, - sphere.shift, 3 * LEFT, - sa_equation.shift, 3 * LEFT, - ) - self.wait(2) - - self.sphere = sphere - self.sa_equation = sa_equation - - def show_four_circles(self): - sphere = self.sphere - shadow = Circle( - radius=sphere.get_width() / 2, - stroke_color=WHITE, - stroke_width=1, - fill_color=BLUE_E, - fill_opacity=1, - ) - radial_line = Line( - shadow.get_center(), - shadow.get_right(), - color=YELLOW - ) - R_label = TexMobject("R").set_color(self.R_color) - R_label.scale(0.8) - R_label.next_to(radial_line, DOWN, SMALL_BUFF) - shadow.add(radial_line, R_label) - shadow.move_to( - self.camera.transform_points_pre_display(sphere, [sphere.get_center()])[0] - ) - - shadows = VGroup(*[shadow.copy() for x in range(4)]) - shadows.arrange_in_grid(buff=MED_LARGE_BUFF) - shadows.to_edge(RIGHT) - - area_label = TexMobject( - "\\pi R^2", - tex_to_color_map={"R": self.R_color} - ) - area_label.scale(1.2) - area_labels = VGroup(*[ - area_label.copy().move_to(interpolate( - mob.get_center(), mob.get_top(), 0.5 - )) - for mob in shadows - ]) - - # shadow.move_to(sphere) - self.add_fixed_in_frame_mobjects(shadow) - self.play(DrawBorderThenFill(shadow)) - anims = [] - for new_shadow in shadows: - old_shadow = shadow.copy() - self.add_fixed_in_frame_mobjects(old_shadow) - anims.append(Transform( - old_shadow, new_shadow, remover=True - )) - self.remove(shadow) - self.play(*anims) - self.add_fixed_in_frame_mobjects(shadows, area_labels) - self.play(LaggedStartMap(FadeInFromLarge, area_labels)) - self.wait() - - self.shadows = shadows - self.shadow_area_labels = area_labels - - def ask_why(self): - pass - - def show_all_pieces(self): - shadows = self.shadows - area_labels = self.shadow_area_labels - sphere = self.sphere - - shadow_pieces_group = VGroup() - for shadow in shadows: - pieces = ParametricSurface( - func=lambda u, v: np.array([ - u * np.cos(TAU * v), - u * np.sin(TAU * v), - 0, - ]), - resolution=(12, 24) - ) - pieces.replace(shadow) - pieces.match_style(sphere) - shadow_pieces_group.add(pieces) - - self.add_fixed_in_frame_mobjects(shadow_pieces_group) - self.play( - FadeOut(shadows), - FadeOut(area_labels), - FadeIn(shadow_pieces_group) - ) - self.show_area_pieces(sphere) - self.wait() - self.show_area_pieces(*shadow_pieces_group) - self.wait(2) - self.unshow_area_pieces(sphere, *shadow_pieces_group) - self.wait(3) - - def show_area_pieces(self, *mobjects): - for mob in mobjects: - mob.generate_target() - mob.target.space_out_submobjects(self.space_out_factor) - self.play(*map(MoveToTarget, mobjects)) - self.play(*[ - LaggedStartMap( - ApplyMethod, mob, - lambda m: (m.set_fill, YELLOW, 1), - rate_func=there_and_back, - lag_ratio=0.25, - run_time=1.5 - ) - for mob in mobjects - ]) - - def unshow_area_pieces(self, *mobjects): - self.play(*[ - ApplyMethod( - mob.space_out_submobjects, - 1.0 / self.space_out_factor - ) - for mob in mobjects - ]) - - -class ButWhy(TeacherStudentsScene): - CONFIG = { - "student_mode": "raise_right_hand", - "question": "But why?", - "teacher_mode": "guilty" - } - - def construct(self): - for student in self.students: - student.change("pondering", self.screen) - self.student_says( - "But why?", - student_index=2, - target_mode=self.student_mode, - bubble_kwargs={"direction": LEFT}, - ) - self.play( - self.teacher.change, self.teacher_mode, self.students[2] - ) - self.wait(5) - - -class ButWhyHappy(ButWhy): - CONFIG = { - "teacher_mode": "happy" - } - - -class ButWhySassy(ButWhy): - CONFIG = { - "teacher_mode": "sassy" - } - - -class ButWhyHesitant(ButWhy): - CONFIG = { - "teacher_mode": "hesitant" - } - - -class ButWhyAngry(ButWhy): - CONFIG = { - "teacher_mode": "angry" - } - - -class TryFittingCirclesDirectly(ExternallyAnimatedScene): - pass - - -class PreviewTwoMethods(MovingCameraScene): - def construct(self): - thumbnails = Group( - ImageMobject("SphereSurfaceProof1"), - ImageMobject("SphereSurfaceProof2"), - ) - for thumbnail in thumbnails: - thumbnail.set_width(FRAME_WIDTH / 2 - 1) - thumbnail.add_to_back(SurroundingRectangle( - thumbnail, buff=0, - stroke_color=WHITE, - stroke_width=5 - )) - thumbnails.arrange(RIGHT, buff=LARGE_BUFF) - - title = TextMobject("Two proofs") - title.scale(2) - title.to_edge(UP) - - frame = self.camera.frame - - self.add(title) - for thumbnail in thumbnails: - self.play(FadeInFromDown(thumbnail)) - self.wait() - for thumbnail in thumbnails: - frame.save_state() - self.play( - frame.replace, thumbnail, - run_time=2 - ) - self.wait() - self.play(Restore(frame, run_time=2)) - - -class MapSphereOntoCylinder(SphereCylinderScene): - def construct(self): - sphere = self.get_sphere() - sphere_ghost = self.get_ghost_surface(sphere) - sphere_ghost.set_stroke(width=0) - axes = self.get_axes() - cylinder = self.get_cylinder() - cylinder.set_fill(opacity=0.75) - radius = cylinder.get_width() / 2 - - self.add(axes, sphere_ghost, sphere) - self.wait() - self.begin_ambient_camera_rotation() - self.move_camera( - **self.default_angled_camera_position, - run_time=2, - ) - self.wait(2) - self.play( - ReplacementTransform(sphere, cylinder), - run_time=3 - ) - self.wait(3) - - # Get rid of caps - caps = self.get_cylinder_caps() - caps[1].set_shade_in_3d(False) - label = TextMobject("Label") - label.scale(1.5) - label.stretch(0.8, 0) - label.rotate(90 * DEGREES, RIGHT) - label.rotate(90 * DEGREES, OUT) - label.shift(np.log(radius + SMALL_BUFF) * RIGHT) - label.apply_complex_function(np.exp) - label.rotate(90 * DEGREES, IN, about_point=ORIGIN) - label.shift(OUT) - label.set_background_stroke(width=0) - - self.play(FadeIn(caps)) - self.wait() - self.play( - caps.space_out_submobjects, 2, - caps.fade, 1, - remover=True - ) - self.play(Write(label)) - self.wait(2) - self.play(FadeOut(label)) - - # Unwrap - unwrapped_cylinder = self.get_unwrapped_cylinder() - unwrapped_cylinder.set_fill(opacity=0.75) - self.play( - ReplacementTransform(cylinder, unwrapped_cylinder), - run_time=3 - ) - self.stop_ambient_camera_rotation() - self.move_camera( - phi=90 * DEGREES, - theta=-90 * DEGREES, - ) - - # Show dimensions - stroke_width = 5 - top_line = Line( - PI * radius * LEFT + radius * OUT, - PI * radius * RIGHT + radius * OUT, - color=YELLOW, - stroke_width=stroke_width, - ) - side_line = Line( - PI * radius * LEFT + radius * OUT, - PI * radius * LEFT + radius * IN, - color=RED, - stroke_width=stroke_width, - ) - lines = VGroup(top_line, side_line) - lines.shift(radius * DOWN) - two_pi_R = TexMobject("2\\pi R") - two_R = TexMobject("2R") - texs = VGroup(two_pi_R, two_R) - for tex in texs: - tex.scale(1.5) - tex.rotate(90 * DEGREES, RIGHT) - two_pi_R.next_to(top_line, OUT) - two_R.next_to(side_line, RIGHT) - - self.play( - ShowCreation(top_line), - FadeInFrom(two_pi_R, IN) - ) - self.wait() - self.play( - ShowCreation(side_line), - FadeInFrom(two_R, RIGHT) - ) - self.wait() - - -class CircumferenceOfCylinder(SphereCylinderScene): - def construct(self): - sphere = self.get_sphere() - sphere_ghost = self.get_ghost_surface(sphere) - cylinder = self.get_cylinder() - cylinder_ghost = self.get_ghost_surface(cylinder) - cylinder_ghost.set_stroke(width=0) - - radius = self.sphere_config["radius"] - circle = Circle(radius=radius) - circle.set_stroke(YELLOW, 5) - circle.shift(radius * OUT) - - height = Line(radius * IN, radius * OUT) - height.shift(radius * LEFT) - height.set_stroke(RED, 5) - - self.set_camera_orientation( - phi=70 * DEGREES, - theta=-95 * DEGREES, - ) - self.begin_ambient_camera_rotation(0.01) - self.add(sphere_ghost, cylinder_ghost) - self.wait() - self.play(ShowCreation(circle)) - self.wait(2) - self.play(ShowCreation(height)) - self.wait(5) - - -class UnfoldCircles(Scene): - CONFIG = { - "circle_style": { - "fill_color": GREY_BROWN, - "fill_opacity": 0, - "stroke_color": GREY_BROWN, - "stroke_width": 2, - }, - "radius": 1.0, - "dr": 0.01, - } - - def construct(self): - self.show_rectangle_with_formula() - self.add_four_circles() - - def show_rectangle_with_formula(self): - TexMobject.CONFIG["background_stroke_width"] = 1 - R = self.radius - rect = Rectangle(width=TAU * R, height=2 * R) - rect.set_fill(BLUE_E, 1) - rect.set_stroke(width=0) - p0, p1, p2 = [rect.get_corner(v) for v in (DL, UL, UR)] - h_line = Line(p0, p1) - h_line.set_stroke(RED, 3) - w_line = Line(p1, p2) - w_line.set_stroke(YELLOW, 3) - two_R = TexMobject("2", "R") - two_R.next_to(h_line, LEFT) - two_pi_R = TexMobject("2", "\\pi", "R") - two_pi_R.next_to(w_line, UP) - - pre_area_label = TexMobject( - "2\\cdot", "2", "\\pi", "R", "\\cdot R" - ) - area_label = TexMobject("4", "\\pi", "R^2") - for label in [area_label, pre_area_label]: - label.next_to(rect.get_center(), UP, SMALL_BUFF) - - self.rect_group = VGroup( - rect, h_line, w_line, - two_R, two_pi_R, area_label - ) - self.area_label = area_label - self.rect = rect - - self.add(rect, h_line, w_line, two_R, two_pi_R) - self.play( - TransformFromCopy(two_R[0], pre_area_label[0]), - TransformFromCopy(two_R[1], pre_area_label[-1]), - TransformFromCopy(two_pi_R, pre_area_label[1:-1]), - ) - self.wait() - self.play( - ReplacementTransform(pre_area_label[:2], area_label[:1]), - ReplacementTransform(pre_area_label[2], area_label[1]), - ReplacementTransform(pre_area_label[3:], area_label[2:]), - ) - self.wait() - self.play( - self.rect_group.to_corner, UL, - {"buff": MED_SMALL_BUFF} - ) - - def add_four_circles(self): - rect_group = self.rect_group - radius = self.radius - - radii_lines = VGroup(*[ - Line(radius * UP, ORIGIN).set_stroke(WHITE, 2) - for x in range(4) - ]) - radii_lines.arrange_in_grid(buff=1.3) - radii_lines[2:].shift(RIGHT) - radii_lines.next_to(rect_group, DOWN, buff=1.3) - R_labels = VGroup(*[ - TexMobject("R").next_to(line, LEFT, SMALL_BUFF) - for line in radii_lines - ]) - - unwrap_factor_tracker = ValueTracker(0) - - def get_circle(line): - return always_redraw( - lambda: self.get_unwrapped_circle( - radius=radius, dr=self.dr, - unwrap_factor=unwrap_factor_tracker.get_value(), - center=line.get_top() - ) - ) - - circles = VGroup(*[get_circle(line) for line in radii_lines]) - circle_copies = circles.copy() - for mob in circle_copies: - mob.clear_updaters() - - self.play( - LaggedStartMap(Write, circle_copies, lag_ratio=0.7), - LaggedStartMap(Write, R_labels), - LaggedStartMap(ShowCreation, radii_lines), - ) - self.remove(circle_copies) - self.add(circles, radii_lines, R_labels) - self.wait() - self.play( - radii_lines[2:].shift, 2.9 * RIGHT, - R_labels[2:].shift, 2.9 * RIGHT, - VGroup( - radii_lines[:2], R_labels[:2] - ).to_edge, LEFT, {"buff": 1.0} - ) - self.play( - unwrap_factor_tracker.set_value, 1, - run_time=2 - ) - self.wait() - - # Move triangles - triangles = circles.copy() - for triangle in triangles: - triangle.clear_updaters() - border = Polygon(*[ - triangle.get_corner(vect) - for vect in (DL, UL, DR) - ]) - border.set_stroke(WHITE, 1) - triangle.add(border) - triangle.generate_target() - rect = self.rect - for i, triangle in enumerate(triangles): - if i % 2 == 1: - triangle.target.rotate(PI) - vect = UP if i < 2 else DOWN - triangle.target.move_to(rect, vect) - - self.play(FadeIn(triangles)) - self.add(triangles, triangles.copy(), self.area_label) - self.play(MoveToTarget(triangles[0])) - self.wait() - self.play(LaggedStartMap(MoveToTarget, triangles)) - self.wait() - - # - def get_unwrapped_circle(self, radius, dr, unwrap_factor=0, center=ORIGIN): - radii = np.arange(0, radius + dr, dr) - rings = VGroup() - for r in radii: - r_factor = 1 + 3 * (r / radius) - alpha = unwrap_factor**r_factor - alpha = np.clip(alpha, 0, 0.9999) - angle = interpolate(TAU, 0, alpha) - length = TAU * r - arc_radius = length / angle - ring = Arc( - start_angle=-90 * DEGREES, - angle=angle, - radius=arc_radius, - **self.circle_style - ) - ring.shift(center + (r - arc_radius) * DOWN) - # ring = AnnularSector( - # inner_radius=r1, - # outer_radius=r2, - # angle=TAU, - # start_angle=-TAU / 4, - # **self.circle_style - # ) - rings.add(ring) - return rings - - -class ShowProjection(SphereCylinderScene): - CONFIG = { - # "default_angled_camera_position": { - # "theta": -155 * DEGREES, - # } - } - - def construct(self): - self.setup_shapes() - self.show_many_tiny_rectangles() - self.project_all_rectangles() - self.focus_on_one() - self.label_sides() - self.show_length_scaled_up() - self.show_height_scaled_down() - - def setup_shapes(self): - self.sphere = self.get_sphere() - self.cylinder = self.get_cylinder() - self.ghost_sphere = self.get_ghost_surface(self.sphere) - self.ghost_sphere.scale(0.99) - self.ghost_cylinder = self.get_ghost_surface(self.cylinder) - self.ghost_cylinder.set_stroke(width=0) - - self.add(self.get_axes()) - self.set_camera_to_default_position() - self.begin_ambient_camera_rotation() - - def show_many_tiny_rectangles(self): - ghost_sphere = self.ghost_sphere - pieces = self.sphere.copy() - random.shuffle(pieces.submobjects) - for piece in pieces: - piece.save_state() - pieces.space_out_submobjects(2) - pieces.fade(1) - - self.add(ghost_sphere) - self.play(LaggedStartMap(Restore, pieces)) - self.wait() - - self.pieces = pieces - - def project_all_rectangles(self): - pieces = self.pieces - for piece in pieces: - piece.save_state() - piece.generate_target() - self.project_mobject(piece.target) - piece.target.set_fill(opacity=0.5) - - example_group = self.get_example_group([1, -1, 2]) - proj_lines = example_group[1] - self.example_group = example_group - - self.play(*map(ShowCreation, proj_lines)) - self.play( - LaggedStartMap(MoveToTarget, pieces), - ) - self.wait() - - def focus_on_one(self): - ghost_cylinder = self.ghost_cylinder - pieces = self.pieces - - example_group = self.example_group - original_rect, rect_proj_lines, rect = example_group - - equation = self.get_equation(rect) - lhs, equals, rhs = equation[:3] - lhs.save_state() - rhs.save_state() - - self.play( - FadeIn(ghost_cylinder), - FadeOut(pieces), - FadeIn(original_rect), - ) - self.play(TransformFromCopy(original_rect, rect)) - self.wait() - self.add_fixed_in_frame_mobjects(lhs, equals, rhs) - self.move_fixed_in_frame_mob_to_unfixed_mob(lhs, original_rect) - self.move_fixed_in_frame_mob_to_unfixed_mob(rhs, rect) - self.play( - Restore(lhs), - Restore(rhs), - FadeInFromDown(equals), - ) - self.wait(5) - - self.equation = equation - self.example_group = example_group - - def label_sides(self): - sphere = self.sphere - equation = self.equation - w_brace, h_brace = equation.braces - width_label, height_label = equation.labels - - u_values, v_values = sphere.get_u_values_and_v_values() - radius = sphere.radius - lat_lines = VGroup(*[ - ParametricFunction( - lambda t: radius * sphere.func(u, t), - t_min=sphere.v_min, - t_max=sphere.v_max, - ) - for u in u_values - ]) - lon_lines = VGroup(*[ - ParametricFunction( - lambda t: radius * sphere.func(t, v), - t_min=sphere.u_min, - t_max=sphere.u_max, - ) - for v in v_values - ]) - for lines in lat_lines, lon_lines: - for line in lines: - line.add(DashedVMobject(line, spacing=-1)) - line.set_points([]) - line.set_stroke(width=2) - lines.set_shade_in_3d(True) - lat_lines.set_color(RED) - lon_lines.set_color(PINK) - - self.play( - *map(ShowCreationThenDestruction, lat_lines), - run_time=2 - ) - self.add_fixed_in_frame_mobjects(w_brace, width_label) - self.play( - GrowFromCenter(w_brace), - Write(width_label), - ) - self.wait() - self.play( - *map(ShowCreationThenDestruction, lon_lines), - run_time=2 - ) - self.add_fixed_in_frame_mobjects(h_brace, height_label) - self.play( - GrowFromCenter(h_brace), - Write(height_label), - ) - self.wait(2) - - def show_length_scaled_up(self): - ghost_sphere = self.ghost_sphere - example_group = self.example_group - equation = self.equation - equation.save_state() - new_example_groups = [ - self.get_example_group([1, -1, z]) - for z in [6, 0.25] - ] - r1, lines, r2 = example_group - - self.stop_ambient_camera_rotation() - self.move_camera( - phi=65 * DEGREES, - theta=-80 * DEGREES, - added_anims=[ - ghost_sphere.set_stroke, {"opacity": 0.1}, - lines.set_stroke, {"width": 3}, - ] - ) - for eg in new_example_groups: - eg[1].set_stroke(width=3) - self.show_length_stretch_of_rect(example_group) - all_example_groups = [example_group, *new_example_groups] - for eg1, eg2 in zip(all_example_groups, all_example_groups[1:]): - if eg1 is new_example_groups[0]: - self.move_camera( - phi=70 * DEGREES, - theta=-110 * DEGREES - ) - self.remove(eg1) - self.play( - ReplacementTransform(eg1.deepcopy(), eg2), - Transform( - equation, - self.get_equation(eg2[2]) - ) - ) - if eg1 is example_group: - self.move_camera( - phi=0, - theta=-90 * DEGREES, - ) - self.show_length_stretch_of_rect(eg2) - self.play( - ReplacementTransform(all_example_groups[-1], example_group), - Restore(equation) - ) - - def show_length_stretch_of_rect(self, example_group): - s_rect, proj_lines, c_rect = example_group - rects = VGroup(s_rect, c_rect) - line1, line2 = lines = VGroup(*[ - Line(m.get_anchors()[1], m.get_anchors()[2]) - for m in rects - ]) - lines.set_stroke(YELLOW, 5) - lines.set_shade_in_3d(True) - proj_lines_to_fade = VGroup( - proj_lines[0], - proj_lines[3], - ) - self.play( - FadeIn(lines[0]), - FadeOut(rects), - FadeOut(proj_lines_to_fade) - ) - for x in range(3): - anims = [] - if lines[1] in self.mobjects: - anims.append(FadeOut(lines[1])) - self.play( - TransformFromCopy(lines[0], lines[1]), - *anims - ) - self.play( - FadeOut(lines), - FadeIn(rects), - FadeIn(proj_lines_to_fade), - ) - self.remove(rects, proj_lines_to_fade) - self.add(example_group) - - def show_height_scaled_down(self): - ghost_sphere = self.ghost_sphere - ghost_cylinder = self.ghost_cylinder - example_group = self.example_group - equation = self.equation - r1, lines, r2 = example_group - to_fade = VGroup(*[ - mob - for mob in it.chain(ghost_sphere, ghost_cylinder) - if np.dot(mob.get_center(), [1, 1, 0]) < 0 - ]) - to_fade.save_state() - - new_example_groups = [ - self.get_example_group([1, -1, z]) - for z in [6, 0.25] - ] - for eg in new_example_groups: - eg[::2].set_stroke(YELLOW, 2) - eg.set_stroke(width=1) - all_example_groups = VGroup(example_group, *new_example_groups) - - self.play( - to_fade.shift, IN, - to_fade.fade, 1, - remover=True - ) - self.move_camera( - phi=85 * DEGREES, - theta=-135 * DEGREES, - added_anims=[ - lines.set_stroke, {"width": 1}, - r1.set_stroke, YELLOW, 2, 1, - r2.set_stroke, YELLOW, 2, 1, - ] - ) - self.show_shadow(example_group) - for eg1, eg2 in zip(all_example_groups, all_example_groups[1:]): - self.remove(*eg1.get_family()) - self.play( - ReplacementTransform(eg1.deepcopy(), eg2), - Transform( - equation, - self.get_equation(eg2[2]) - ) - ) - self.show_shadow(eg2) - self.move_camera( - phi=70 * DEGREES, - theta=-115 * DEGREES, - added_anims=[ - ReplacementTransform( - all_example_groups[-1], - example_group, - ), - Restore(equation), - ] - ) - self.begin_ambient_camera_rotation() - self.play(Restore(to_fade)) - self.wait(5) - - def show_shadow(self, example_group): - s_rect, lines, c_rect = example_group - for x in range(3): - self.play(TransformFromCopy(s_rect, c_rect)) - - # - def get_projection_lines(self, piece): - result = VGroup() - radius = self.sphere_config["radius"] - for corner in piece.get_anchors()[:-1]: - start = np.array(corner) - end = np.array(corner) - start[:2] = np.zeros(2) - end[:2] = (radius + 0.03) * normalize(end[:2]) - kwargs = { - "color": YELLOW, - "stroke_width": 0.5, - } - result.add(VGroup(*[ - Line(p1, p2, **kwargs) - for p1, p2 in [(start, corner), (corner, end)] - ])) - result.set_shade_in_3d(True) - return result - - def get_equation(self, rect): - length = get_norm(rect.get_anchors()[1] - rect.get_anchors()[0]) - height = get_norm(rect.get_anchors()[2] - rect.get_anchors()[1]) - lhs = Rectangle(width=length, height=height) - rhs = Rectangle(width=height, height=length) - eq_rects = VGroup(lhs, rhs) - eq_rects.set_stroke(width=0) - eq_rects.set_fill(YELLOW, 1) - eq_rects.scale(2) - equals = TexMobject("=") - equation = VGroup(lhs, equals, rhs) - equation.arrange(RIGHT) - equation.to_corner(UR) - - brace = Brace(Line(ORIGIN, 0.5 * RIGHT), DOWN) - w_brace = brace.copy().match_width(lhs, stretch=True) - h_brace = brace.copy().rotate(-90 * DEGREES) - h_brace.match_height(lhs, stretch=True) - w_brace.next_to(lhs, DOWN, buff=SMALL_BUFF) - h_brace.next_to(lhs, LEFT, buff=SMALL_BUFF) - braces = VGroup(w_brace, h_brace) - - width_label = TextMobject("Width") - height_label = TextMobject("Height") - labels = VGroup(width_label, height_label) - labels.scale(0.75) - width_label.next_to(w_brace, DOWN, SMALL_BUFF) - height_label.next_to(h_brace, LEFT, SMALL_BUFF) - - equation.braces = braces - equation.labels = labels - equation.add(braces, labels) - - return equation - - def move_fixed_in_frame_mob_to_unfixed_mob(self, m1, m2): - phi = self.camera.phi_tracker.get_value() - theta = self.camera.theta_tracker.get_value() - target = m2.get_center() - target = rotate_vector(target, -theta - 90 * DEGREES, OUT) - target = rotate_vector(target, -phi, RIGHT) - - m1.move_to(target) - m1.scale(0.1) - m1.shift(SMALL_BUFF * UR) - return m1 - - def get_example_group(self, vect): - pieces = self.pieces - rect = pieces[np.argmax([ - np.dot(r.saved_state.get_center(), vect) - for r in pieces - ])].saved_state.copy() - rect_proj_lines = self.get_projection_lines(rect) - rect.set_fill(YELLOW, 1) - original_rect = rect.copy() - self.project_mobject(rect) - rect.shift( - 0.001 * np.array([*rect.get_center()[:2], 0]) - ) - result = VGroup(original_rect, rect_proj_lines, rect) - result.set_shade_in_3d(True) - return result - - -class SlantedShadowSquishing(ShowShadows): - CONFIG = { - "num_reorientations": 4, - "random_seed": 2, - } - - def setup(self): - ShowShadows.setup(self) - self.surface_area_label.fade(1) - self.shadow_area_label.fade(1) - self.shadow_area_decimal.fade(1) - - def construct(self): - # Show creation - self.set_camera_orientation( - phi=70 * DEGREES, - theta=-150 * DEGREES, - ) - self.begin_ambient_camera_rotation(0.01) - square = self.obj3d.deepcopy() - square.clear_updaters() - shadow = always_redraw(lambda: get_shadow(square)) - - # Reorient - self.add(square, shadow) - for n in range(self.num_reorientations): - angle = 40 * DEGREES - self.play( - Rotate(square, angle, axis=RIGHT), - run_time=2 - ) - - def get_object(self): - square = Square() - square.set_shade_in_3d(True) - square.set_height(2) - square.set_stroke(WHITE, 0.5) - square.set_fill(BLUE_C, 1) - return square - - -class JustifyLengthStretch(ShowProjection): - CONFIG = { - "R_color": RED, - "d_color": WHITE, - "d_ambiguity_iterations": 4, - } - - def construct(self): - self.setup_shapes() - self.add_ghosts() - self.add_example_group() - self.cut_cross_section() - self.label_R() - self.label_d() - self.show_similar_triangles() - - def add_ghosts(self): - self.add(self.ghost_sphere, self.ghost_cylinder) - - def add_example_group(self): - self.pieces = self.sphere - for piece in self.pieces: - piece.save_state() - self.example_group = self.get_example_group([1, 0.1, 1.5]) - self.add(self.example_group) - self.set_camera_orientation(theta=-45 * DEGREES) - - def cut_cross_section(self): - sphere = self.ghost_sphere - cylinder = self.ghost_cylinder - to_fade = VGroup(*[ - mob - for mob in it.chain(sphere, cylinder) - if np.dot(mob.get_center(), DOWN) > 0 - ]) - self.lost_hemisphere = to_fade - to_fade.save_state() - - circle = Circle( - stroke_width=2, - stroke_color=PINK, - radius=self.sphere.radius - ) - circle.rotate(90 * DEGREES, RIGHT) - self.circle = circle - - self.example_group.set_stroke(YELLOW, 1) - - self.stop_ambient_camera_rotation() - self.play( - Rotate( - to_fade, -PI, - axis=OUT, - about_point=sphere.get_left(), - run_time=2 - ), - VFadeOut(to_fade, run_time=2), - FadeIn(circle), - ) - # self.move_camera( - # phi=80 * DEGREES, - # theta=-85 * DEGREES, - # ) - - def label_R(self): - circle = self.circle - R_line = Line(ORIGIN, circle.get_right()) - R_line.set_color(self.R_color) - R_label = TexMobject("R") - R_label.next_to(R_line, IN) - - self.add_fixed_orientation_mobjects(R_label) - self.play( - ShowCreation(R_line), - FadeInFrom(R_label, IN), - ) - self.wait() - - self.R_line = R_line - self.R_label = R_label - - def label_d(self): - example_group = self.example_group - s_rect = example_group[0] - d_line = self.get_d_line( - s_rect.get_corner(IN + RIGHT + DOWN) - ) - alt_d_line = self.get_d_line( - s_rect.get_corner(OUT + LEFT + DOWN) - ) - d_label = TexMobject("d") - d_label.next_to(d_line, IN) - - self.add_fixed_orientation_mobjects(d_label) - self.play( - ShowCreation(d_line), - FadeInFrom(d_label, IN), - ) - self.wait() - for x in range(self.d_ambiguity_iterations): - to_fade_out = [d_line, alt_d_line][x % 2] - to_fade_in = [d_line, alt_d_line][(x + 1) % 2] - self.play( - FadeIn(to_fade_in), - FadeOut(to_fade_out), - d_label.next_to, to_fade_in, IN, - ) - - self.d_line = d_line - self.d_label = d_label - - def show_similar_triangles(self): - d_line = self.d_line - d_label = self.d_label - R_line = self.R_line - R_label = self.R_label - example_group = self.example_group - s_rect, proj_lines, c_rect = example_group - - p1 = s_rect.get_anchors()[1] - p2 = s_rect.get_anchors()[2] - p0 = np.array(p1) - p0[:2] = np.zeros(2) - triangle = Polygon(p0, p1, p2) - triangle.set_stroke(width=0) - triangle.set_fill(GREEN, opacity=1) - base = Line(p1, p2) - base.set_stroke(PINK, 3) - - big_triangle = Polygon( - p0, self.project_point(p1), self.project_point(p2) - ) - big_triangle.set_stroke(width=0) - big_triangle.set_fill(RED, opacity=1) - - equation = VGroup( - triangle.copy().rotate(-3 * DEGREES), - TexMobject("\\sim"), - big_triangle.copy().rotate(-3 * DEGREES) - ) - equation.arrange(RIGHT, buff=SMALL_BUFF) - equation.to_corner(UL) - eq_d = TexMobject("d").next_to(equation[0], DOWN, SMALL_BUFF) - eq_R = TexMobject("R").next_to(equation[2], DOWN, SMALL_BUFF) - VGroup(equation, eq_d, eq_R).rotate(20 * DEGREES, axis=RIGHT) - - for mob in [triangle, big_triangle]: - mob.set_shade_in_3d(True) - mob.set_fill(opacity=0.8) - - self.move_camera( - phi=40 * DEGREES, - theta=-85 * DEGREES, - added_anims=[ - d_label.next_to, d_line, DOWN, SMALL_BUFF, - d_line.set_stroke, {"width": 1}, - FadeOut(proj_lines[0]), - FadeOut(proj_lines[3]), - FadeOut(R_label) - ], - run_time=2, - ) - point = VectorizedPoint(p0) - point.set_shade_in_3d(True) - self.play( - ReplacementTransform(point, triangle), - Animation(self.camera.phi_tracker) - ) - self.add_fixed_in_frame_mobjects(equation, eq_d, eq_R) - self.play( - FadeInFrom(equation[0], 7 * RIGHT + 2.5 * DOWN), - FadeIn(equation[1:]), - FadeInFromDown(eq_d), - FadeInFromDown(eq_R), - Animation(self.camera.phi_tracker) - ) - self.wait() - for x in range(2): - self.play(ShowCreationThenDestruction(base)) - self.wait() - d_line_copy = d_line.copy().set_stroke(WHITE, 3) - self.play(ShowCreation(d_line_copy)) - self.play(FadeOut(d_line_copy)) - self.wait(2) - R_label.next_to(R_line, DOWN, SMALL_BUFF) - R_label.shift(0.25 * IN) - self.play( - ReplacementTransform(triangle, big_triangle), - FadeIn(R_label), - Animation(self.camera.phi_tracker) - ) - self.wait(3) - - self.move_camera( - phi=70 * DEGREES, - theta=-70 * DEGREES, - added_anims=[ - big_triangle.set_fill, {"opacity": 0.25}, - d_label.next_to, d_line, IN, {"buff": 0.3}, - ] - ) - self.begin_ambient_camera_rotation() - lost_hemisphere = self.lost_hemisphere - lost_hemisphere.restore() - left_point = self.sphere.get_left() - lost_hemisphere.rotate(-PI, axis=OUT, about_point=left_point) - self.play( - Rotate( - lost_hemisphere, PI, - axis=OUT, - about_point=left_point, - run_time=2, - ), - VFadeIn(lost_hemisphere), - FadeOut(self.circle), - R_line.set_color, self.R_color, - ) - self.wait(10) - - # - def get_d_line(self, sphere_point): - end = sphere_point - start = np.array(end) - start[:2] = np.zeros(2) - - d_line = Line(start, end) - d_line.set_color(self.d_color) - return d_line - - -class JustifyLengthStretchHigherRes(JustifyLengthStretch): - CONFIG = { - "sphere_config": { - "resolution": (2 * 24, 2 * 48) - }, - } - - -class JustifyLengthStretchHighestRes(JustifyLengthStretch): - CONFIG = { - "sphere_config": { - "resolution": (4 * 24, 4 * 48) - }, - } - - -class ProTipNameThings(Scene): - CONFIG = { - "tip_descriptor": "(Deceptively simple) problem solving tip:", - "tip": "Start with names", - } - - def construct(self): - words = TextMobject( - self.tip_descriptor, - self.tip, - ) - words[1].set_color(YELLOW) - words.to_edge(UP) - - self.play(FadeIn(words[0])) - self.wait() - self.play(FadeInFromDown(words[1])) - self.wait() - - -class WidthScaleLabel(Scene): - def construct(self): - text = TexMobject( - "\\text{Width scale factor} =", - "\\frac{R}{d}" - ) - self.play(Write(text)) - self.wait() - - -class HeightSquishLabel(Scene): - def construct(self): - text = TexMobject( - "\\text{Height squish factor} =", - "\\frac{R}{d}" - ) - self.play(Write(text)) - self.wait() - - -class TinierAndTinerRectangles(SphereCylinderScene): - CONFIG = { - "n_iterations": 5, - } - - def construct(self): - spheres = [ - self.get_sphere( - resolution=(12 * (2**n), 24 * (2**n)), - stroke_width=0, - ) - for n in range(self.n_iterations) - ] - - self.set_camera_to_default_position() - self.add(self.get_axes()) - self.begin_ambient_camera_rotation() - self.add(spheres[0]) - for s1, s2 in zip(spheres, spheres[1:]): - self.wait() - random.shuffle(s2.submobjects) - for piece in s2: - piece.save_state() - s2.space_out_submobjects(1.2) - s2.fade(1) - for piece in s1: - piece.add(VectorizedPoint(piece.get_center() / 2)) - self.play( - LaggedStartMap(Restore, s2) - ) - self.remove(s1) - self.wait(5) - - -class LimitViewToCrossSection(JustifyLengthStretch): - CONFIG = { - "d_ambiguity_iterations": 0, - } - - def construct(self): - self.setup_shapes() - self.add_ghosts() - self.add_example_group() - self.cut_cross_section() - self.label_R() - self.label_d() - self.move_camera( - phi=90 * DEGREES, - theta=-90 * DEGREES, - ) - self.play( - FadeOut(self.ghost_sphere), - FadeOut(self.ghost_cylinder), - ) - - -class JustifyHeightSquish(MovingCameraScene): - CONFIG = { - # As measured from previous scene - "top_phi": 0.5242654422649652, - "low_phi": 0.655081802831207, - "radius": 2, - "R_color": RED, - "d_color": WHITE, - "little_triangle_color": BLUE, - "big_triangle_color": GREY_BROWN, - "alpha_color": WHITE, - "beta_color": WHITE, - } - - def construct(self): - self.recreate_cross_section() - self.show_little_triangle() - self.show_tangent_to_radius() - self.tweak_parameter() - self.label_angles() - - def recreate_cross_section(self): - axes = Axes( - axis_config={ - "unit_size": 2, - } - ) - circle = Circle( - radius=self.radius, - stroke_color=PINK, - stroke_width=2, - ) - R_line = self.get_R_line(90 * DEGREES) - R_line.set_color(self.R_color) - R_label = TexMobject("R") - R_label.next_to(R_line, DOWN, SMALL_BUFF) - d_lines = VGroup(*[ - self.get_d_line(phi) - for phi in [self.low_phi, self.top_phi] - ]) - d_line = d_lines[0] - d_line.set_color(self.d_color) - d_label = TexMobject("d") - d_label.next_to(d_line, DOWN, SMALL_BUFF) - - proj_lines = VGroup(*[ - self.get_R_line(phi) - for phi in [self.top_phi, self.low_phi] - ]) - proj_lines.set_stroke(YELLOW, 1) - - s_rect_line, c_rect_line = [ - Line( - *[l.get_end() for l in lines], - stroke_color=YELLOW, - stroke_width=2, - ) - for lines in [d_lines, proj_lines] - ] - - mobjects = [ - axes, circle, - R_line, d_line, - R_label, d_label, - proj_lines, - s_rect_line, c_rect_line, - ] - self.add(*mobjects) - self.set_variables_as_attrs(*mobjects) - - def show_little_triangle(self): - phi = self.low_phi - d_phi = abs(self.low_phi - self.top_phi) - tri_group = self.get_double_triangle_group(phi, d_phi) - lil_tri, big_tri = tri_group - frame = self.camera_frame - frame.save_state() - scale_factor = 0.1 - sw_sf = 0.2 # stroke_width scale factor - d_sf = 0.3 # d_label scale factor - - hyp = lil_tri.hyp - leg = lil_tri.leg2 - leg.rotate(PI) - VGroup(hyp, leg).set_stroke(YELLOW, 1) - hyp_word = TextMobject("Rectangle height $\\rightarrow$") - leg_word = TextMobject("$\\leftarrow$ Projection") - words = VGroup(hyp_word, leg_word) - words.set_height(0.4 * lil_tri.get_height()) - words.set_background_stroke(width=0) - hyp_word.next_to(hyp.get_center(), LEFT, buff=0.05) - leg_word.next_to(leg, RIGHT, buff=0.02) - - stroke_width_changers = VGroup() - for mob in self.mobjects: - if mob in [self.d_label, frame]: - continue - mob.generate_target() - mob.save_state() - mob.target.set_stroke( - width=sw_sf * mob.get_stroke_width() - ) - stroke_width_changers.add(mob) - - self.play( - frame.scale, scale_factor, - frame.move_to, lil_tri, - self.d_label.scale, d_sf, {"about_edge": UP}, - *map(MoveToTarget, stroke_width_changers) - ) - self.play(DrawBorderThenFill(lil_tri, stroke_width=0.5)) - self.wait() - self.play( - ShowCreation(hyp), - LaggedStartMap( - DrawBorderThenFill, hyp_word, - stroke_width=0.5, - run_time=1, - ), - ) - self.wait() - self.play(TransformFromCopy(hyp, leg)) - self.play(TransformFromCopy( - hyp_word, leg_word, - path_arc=-PI / 2, - )) - self.wait() - self.play( - frame.restore, - self.d_label.scale, 1 / d_sf, {"about_edge": UP}, - *map(Restore, stroke_width_changers), - run_time=3 - ) - - self.set_variables_as_attrs( - hyp_word, leg_word, tri_group - ) - - def show_tangent_to_radius(self): - tri_group = self.tri_group - lil_tri, big_tri = tri_group - lil_hyp = lil_tri.hyp - phi = self.low_phi - circle_point = self.get_circle_point(phi) - - tangent = lil_hyp.copy() - tangent.set_stroke(WHITE, 2) - tangent.scale(5 / tangent.get_length()) - tangent.move_to(circle_point) - - R_line = self.R_line - R_label = self.R_label - d_label = self.d_label - elbow = Elbow(angle=(-phi - PI / 2), width=0.15) - elbow.shift(circle_point) - elbow.set_stroke(WHITE, 1) - self.tangent_elbow = elbow - - self.play(GrowFromPoint(tangent, circle_point)) - self.wait() - self.play( - Rotate( - R_line, 90 * DEGREES - phi, - about_point=ORIGIN, - ), - R_label.next_to, 0.5 * circle_point, DR, {"buff": 0}, - d_label.shift, SMALL_BUFF * UL, - ) - self.play(ShowCreation(elbow)) - self.wait() - self.add(big_tri, d_label, R_line, elbow) - d_label.set_background_stroke(width=0) - self.play(DrawBorderThenFill(big_tri)) - self.wait() - - self.set_variables_as_attrs(tangent, elbow) - - def tweak_parameter(self): - tri_group = self.tri_group - lil_tri = tri_group[0] - d_label = self.d_label - d_line = self.d_line - R_label = self.R_label - R_line = self.R_line - frame = self.camera_frame - - to_fade = VGroup( - self.proj_lines, - self.s_rect_line, self.c_rect_line, - self.hyp_word, self.leg_word, - lil_tri.hyp, lil_tri.leg2, - ) - rad_tangent = VGroup( - R_line, - self.tangent, - self.elbow, - ) - - phi_tracker = ValueTracker(self.low_phi) - - self.play( - frame.scale, 0.6, - frame.shift, UR, - R_label.scale, 0.6, {"about_edge": UL}, - d_label.scale, 0.6, - {"about_point": d_label.get_top() + SMALL_BUFF * DOWN}, - *map(FadeOut, to_fade), - ) - - curr_phi = self.low_phi - d_phi = abs(self.top_phi - self.low_phi) - alt_phis = [ - 80 * DEGREES, - 20 * DEGREES, - 50 * DEGREES, - curr_phi - ] - for new_phi in alt_phis: - self.add(tri_group, d_label) - self.play( - phi_tracker.set_value, new_phi, - UpdateFromFunc( - tri_group, - lambda tg: tg.become( - self.get_double_triangle_group( - phi_tracker.get_value(), - d_phi - ) - ) - ), - Rotate( - rad_tangent, - -(new_phi - curr_phi), - about_point=ORIGIN, - ), - MaintainPositionRelativeTo(R_label, R_line), - UpdateFromFunc( - d_line, - lambda dl: dl.become( - self.get_d_line(phi_tracker.get_value()) - ), - ), - MaintainPositionRelativeTo(d_label, d_line), - run_time=2 - ) - self.wait() - curr_phi = new_phi - for tri in tri_group: - self.play(Indicate(tri)) - self.wait() - self.play(*map(FadeIn, to_fade)) - self.remove(phi_tracker) - - def label_angles(self): - # Getting pretty hacky here... - tri_group = self.tri_group - lil_tri, big_tri = tri_group - d_label = self.d_label - R_label = self.R_label - frame = self.camera_frame - - alpha = self.low_phi - beta = 90 * DEGREES - alpha - circle_point = self.get_circle_point(alpha) - alpha_arc = Arc( - start_angle=90 * DEGREES, - angle=-alpha, - radius=0.2, - stroke_width=2, - ) - beta_arc = Arc( - start_angle=PI, - angle=beta, - radius=0.2, - stroke_width=2, - ) - beta_arc.shift(circle_point) - alpha_label = TexMobject("\\alpha") - alpha_label.scale(0.5) - alpha_label.set_color(self.alpha_color) - alpha_label.next_to(alpha_arc, UP, buff=SMALL_BUFF) - alpha_label.shift(0.05 * DR) - beta_label = TexMobject("\\beta") - beta_label.scale(0.5) - beta_label.set_color(self.beta_color) - beta_label.next_to(beta_arc, LEFT, buff=SMALL_BUFF) - beta_label.shift(0.07 * DR) - VGroup(alpha_label, beta_label).set_background_stroke(width=0) - - elbow = Elbow(width=0.15, angle=-90 * DEGREES) - elbow.shift(big_tri.get_corner(UL)) - elbow.set_stroke(width=2) - - equation = TexMobject( - "\\alpha", "+", "\\beta", "+", - "90^\\circ", "=", "180^\\circ" - ) - equation.scale(0.6) - equation.next_to(frame.get_corner(UR), DL) - - movers = VGroup( - alpha_label.deepcopy(), beta_label.deepcopy(), - elbow.copy() - ) - indices = [0, 2, 4] - for mover, index in zip(movers, indices): - mover.target = VGroup(equation[index]) - - # Show equation - self.play( - FadeOut(d_label), - FadeOut(R_label), - ShowCreation(alpha_arc), - ShowCreation(beta_arc), - ) - self.wait() - self.play(FadeInFrom(alpha_label, UP)) - self.wait() - self.play(FadeInFrom(beta_label, LEFT)) - self.wait() - self.play(ShowCreation(elbow)) - self.wait() - - self.play( - LaggedStartMap(MoveToTarget, movers), - LaggedStartMap(FadeInFromDown, equation[1:4:2]) - ) - self.wait() - self.play(FadeInFrom(equation[-2:], LEFT)) - self.remove(equation, movers) - self.add(equation) - self.wait() - - # Zoom in - self.remove(self.tangent_elbow) - stroke_width_changers = VGroup(*[ - mob for mob in self.mobjects - if mob not in [ - beta_arc, beta_label, frame, equation, - ] - ]) - for mob in stroke_width_changers: - mob.generate_target() - mob.save_state() - mob.target.set_stroke( - width=0.3 * mob.get_stroke_width() - ) - equation.set_background_stroke(width=0) - scaled_arcs = VGroup(beta_arc, self.tangent_elbow) - beta_label.set_background_stroke(color=BLACK, width=0.3) - self.play( - ApplyMethod( - VGroup(frame, equation).scale, 0.15, - {"about_point": circle_point + 0.1 * LEFT}, - ), - ApplyMethod( - beta_label.scale, 0.3, - {"about_point": circle_point}, - ), - scaled_arcs.set_stroke, {"width": 0.3}, - scaled_arcs.scale, 0.3, {"about_point": circle_point}, - *map(MoveToTarget, stroke_width_changers) - ) - - # Show small triangle angles - TexMobject.CONFIG["background_stroke_width"] = 0 - words = VGroup(self.hyp_word, self.leg_word) - alpha_arc1 = Arc( - start_angle=90 * DEGREES + beta, - angle=0.95 * alpha, - radius=0.3 * 0.2, - stroke_width=beta_arc.get_stroke_width(), - ).shift(circle_point) - alpha_arc2 = Arc( - start_angle=0, - angle=-0.95 * alpha, - radius=0.3 * 0.2, - stroke_width=beta_arc.get_stroke_width(), - ).shift(lil_tri.hyp.get_end()) - beta_arc1 = Arc( - start_angle=90 * DEGREES, - angle=beta, - radius=0.3 * 0.2, - stroke_width=beta_arc.get_stroke_width(), - ).shift(circle_point) - deg90 = TexMobject("90^\\circ") - deg90.set_height(0.8 * beta_label.get_height()) - deg90.next_to(self.tangent_elbow, DOWN, buff=0.025) - # deg90.set_background_stroke(width=0) - q_mark = TexMobject("?") - q_mark.set_height(0.5 * beta_label.get_height()) - q_mark.next_to(alpha_arc1, LEFT, buff=0.025) - q_mark.shift(0.01 * UP) - alpha_label1 = TexMobject("\\alpha") - alpha_label1.set_height(0.7 * q_mark.get_height()) - alpha_label1.move_to(q_mark) - - alpha_label2 = alpha_label1.copy() - alpha_label2.next_to( - alpha_arc2, RIGHT, buff=0.01 - ) - alpha_label2.set_background_stroke(color=BLACK, width=0.3) - - beta_label1 = beta_label.copy() - beta_label1.scale(0.7) - beta_label1.set_background_stroke(color=BLACK, width=0.3) - beta_label1.next_to( - beta_arc1, UP, buff=0.01 - ) - beta_label1.shift(0.01 * LEFT) - - self.play(FadeOut(words)) - self.play(FadeInFrom(deg90, 0.1 * UP)) - self.wait(0.25) - self.play(WiggleOutThenIn(beta_label)) - self.wait(0.25) - self.play( - ShowCreation(alpha_arc1), - FadeInFrom(q_mark, 0.1 * RIGHT) - ) - self.wait() - self.play(ShowPassingFlash( - self.tangent.copy().scale(0.1).set_stroke(PINK, 0.5) - )) - self.wait() - self.play(ReplacementTransform(q_mark, alpha_label1)) - self.play(ShowCreationThenFadeAround( - equation, - surrounding_rectangle_config={ - "buff": 0.015, - "stroke_width": 0.5, - }, - )) - self.wait() - self.play( - ShowCreation(alpha_arc2), - FadeIn(alpha_label2), - ) - self.play( - ShowCreation(beta_arc1), - FadeIn(beta_label1), - ) - self.wait() - - # - def get_double_triangle_group(self, phi, d_phi): - p0 = self.get_circle_point(phi) - p1 = self.get_circle_point(phi - d_phi) - p2 = np.array(p1) - p2[0] = p0[0] - - little_triangle = Polygon( - p0, p1, p2, - stroke_width=0, - fill_color=self.little_triangle_color, - fill_opacity=1, - ) - big_triangle = Polygon( - p0, ORIGIN, p0 - p0[0] * RIGHT, - stroke_width=0, - fill_color=self.big_triangle_color, - fill_opacity=1 - ) - result = VGroup(little_triangle, big_triangle) - for tri in result: - p0, p1, p2 = tri.get_anchors()[:3] - tri.hyp = Line(p0, p1) - tri.leg1 = Line(p1, p2) - tri.leg2 = Line(p2, p0) - tri.side_lines = VGroup( - tri.hyp, tri.leg1, tri.leg2 - ) - tri.side_lines.set_stroke(WHITE, 1) - result.set_stroke(width=0) - return result - - def get_R_line(self, phi): - y = self.radius * np.cos(phi) - x = self.radius - return Line(ORIGIN, x * RIGHT).shift(y * UP) - - def get_d_line(self, phi): - end = self.get_circle_point(phi) - start = np.array(end) - start[0] = 0 - return Line(start, end) - - def get_circle_point(self, phi): - return rotate_vector(self.radius * UP, -phi) - - -class ProTipTangentRadii(ProTipNameThings): - CONFIG = { - "tip_descriptor": "Pro tip:", - "tip": "A circle's tangent is perpendicular to its radius", - } - - -class ProTipTweakParameters(ProTipNameThings): - CONFIG = { - "tip_descriptor": "Pro tip:", - "tip": "Tweak parameters $\\rightarrow$ make", - } - - -class DrawSquareThenFade(Scene): - def construct(self): - square = Square(color=YELLOW, stroke_width=5) - self.play(ShowCreation(square)) - self.play(FadeOut(square)) - - -class WhyAreWeDoingThis(TeacherStudentsScene): - def construct(self): - self.student_says( - "Hang on, what \\\\ are we doing?", - student_index=2, - bubble_kwargs={"direction": LEFT}, - target_mode="hesitant" - ) - self.change_student_modes( - "maybe", "pondering", "hesitant", - added_anims=[self.teacher.change, "tease"] - ) - self.wait(3) - self.play( - RemovePiCreatureBubble(self.students[2]), - self.teacher.change, "raise_right_hand", - self.change_student_modes(*2 * ["pondering"]) - ) - self.look_at(self.screen) - self.wait(2) - - -class SameEffectAsRotating(Scene): - CONFIG = { - "rectangle_config": { - "height": 2, - "width": 1, - "stroke_width": 0, - "fill_color": YELLOW, - "fill_opacity": 1, - "background_stroke_width": 2, - "background_stroke_color": BLACK, - }, - "x_stretch": 2, - "y_stretch": 0.5, - } - - def construct(self): - rect1 = Rectangle(**self.rectangle_config) - rect2 = rect1.copy() - rect2.stretch(self.x_stretch, 0) - rect2.stretch(self.y_stretch, 1) - rotated_rect1 = rect1.copy() - rotated_rect1.rotate(-90 * DEGREES) - - arrow = Arrow(ORIGIN, RIGHT, buff=0, color=WHITE) - group = VGroup(rect1, arrow, rect2) - group.arrange(RIGHT) - group.center() - moving_rect = rect1.copy() - - low_brace = always_redraw( - lambda: Brace( - moving_rect, DOWN, buff=SMALL_BUFF, - min_num_quads=2, - ) - ) - right_brace = always_redraw( - lambda: Brace( - moving_rect, RIGHT, buff=SMALL_BUFF, - min_num_quads=2, - ) - ) - times_R_over_d = TexMobject("\\times \\frac{R}{d}") - times_d_over_R = TexMobject("\\times \\frac{d}{R}") - times_R_over_d.add_updater( - lambda m: m.next_to(low_brace, DOWN, SMALL_BUFF) - ) - times_d_over_R.add_updater( - lambda m: m.next_to(right_brace, RIGHT, SMALL_BUFF) - ) - - self.add(rect1, arrow) - self.play(moving_rect.move_to, rect2) - self.add(low_brace) - self.play( - moving_rect.match_width, rect2, {"stretch": True}, - FadeIn(times_R_over_d), - ) - self.add(right_brace) - self.play( - moving_rect.match_height, rect2, {"stretch": True}, - FadeIn(times_d_over_R), - ) - self.wait() - rotated_rect1.move_to(moving_rect) - self.play(TransformFromCopy( - rect1, rotated_rect1, - path_arc=-90 * DEGREES, - run_time=2 - )) - - -class NotSameEffectAsRotating(SameEffectAsRotating): - CONFIG = { - "rectangle_config": { - "width": 1.5, - "height": 1.5, - } - } - - -class ShowParameterization(Scene): - def construct(self): - u_color = PINK - v_color = GREEN - tex_kwargs = { - "tex_to_color_map": { - "u": u_color, - "v": v_color, - } - } - vector = Matrix( - [ - ["\\text{cos}(u)\\text{sin}(v)"], - ["\\text{sin}(u)\\text{sin}(v)"], - ["\\text{cos}(v)"] - ], - element_to_mobject_config=tex_kwargs, - element_alignment_corner=DOWN, - ) - vector.to_edge(UP) - - ranges = VGroup( - TexMobject("0 \\le u \\le 2\\pi", **tex_kwargs), - TexMobject("0 \\le v \\le \\pi", **tex_kwargs), - TextMobject( - "Sample $u$ and $v$ with \\\\ the same density", - tex_to_color_map={ - "$u$": u_color, - "$v$": v_color, - } - ) - ) - ranges.arrange(DOWN) - ranges.next_to(vector, DOWN) - - self.add(vector) - self.add(ranges) - - -class RdLabels(Scene): - def construct(self): - rect = Rectangle(height=1, width=0.5) - cR = TexMobject("cR") - cR.next_to(rect, LEFT, SMALL_BUFF) - cd = TexMobject("cd") - cd.next_to(rect, DOWN, SMALL_BUFF) - - labels = VGroup(cd, cR) - for label in labels: - label[1].set_color(BLUE) - self.play(FadeInFromDown(label)) - - -class RotateAllPiecesWithExpansion(ShowProjection): - CONFIG = { - "sphere_config": { - "radius": 1.5, - }, - "with_expansion": True - } - - def construct(self): - self.setup_shapes() - self.rotate_all_pieces() - - def rotate_all_pieces(self): - sphere = self.sphere - cylinder = self.cylinder - ghost_sphere = self.ghost_sphere - ghost_sphere.scale(0.99) - - # Shuffle sphere and cylinder same way - random.seed(0) - random.shuffle(sphere.submobjects) - random.seed(0) - random.shuffle(cylinder.submobjects) - - sphere_target = VGroup() - for piece in sphere: - p0, p1, p2, p3 = piece.get_anchors()[:4] - piece.set_points_as_corners([ - p3, p0, p1, p2, p3 - ]) - piece.generate_target() - sphere_target.add(piece.target) - piece.target.move_to( - (1 + random.random()) * piece.get_center() - ) - - self.add(ghost_sphere, sphere) - self.wait() - if self.with_expansion: - self.play(LaggedStartMap( - MoveToTarget, sphere - )) - self.wait() - self.play(*[ - Rotate(piece, 90 * DEGREES, axis=piece.get_center()) - for piece in sphere - ]) - self.wait() - self.play(Transform(sphere, cylinder, run_time=2)) - self.wait(5) - - -class RotateAllPiecesWithoutExpansion(RotateAllPiecesWithExpansion): - CONFIG = { - "with_expansion": False, - } - - -class ThinkingCritically(PiCreatureScene): - def construct(self): - randy = self.pi_creature - - self.play(randy.change, "pondering") - self.wait() - self.play( - randy.change, "hesitant", 2 * UP, - ) - self.wait() - self.play(randy.change, "sassy") - self.wait() - self.play(randy.change, "angry") - self.wait(4) - - -class WriteNotEquals(Scene): - def construct(self): - symbol = TexMobject("\\ne") - symbol.scale(2) - symbol.set_background_stroke(width=0) - self.play(Write(symbol)) - self.wait() - - -class RectangulatedSphere(SphereCylinderScene): - CONFIG = { - "sphere_config": { - "resolution": (10, 20) - }, - "uniform_color": False, - "wait_time": 10, - } - - def construct(self): - sphere = self.get_sphere() - if self.uniform_color: - sphere.set_stroke(BLUE_E, width=0.5) - sphere.set_fill(BLUE_E) - self.set_camera_to_default_position() - self.begin_ambient_camera_rotation(0.05) - self.add(sphere) - self.wait(self.wait_time) - - -class SmoothSphere(RectangulatedSphere): - CONFIG = { - "sphere_config": { - "resolution": (200, 400), - }, - "uniform_color": True, - "wait_time": 0, - } - - -class SequenceOfSpheres(SphereCylinderScene): - def construct(self): - n_shapes = 4 - spheres, cylinders = groups = VGroup(*[ - VGroup(*[ - func(resolution=(n, 2 * n)) - for k in range(1, n_shapes + 1) - for n in [3 * (2**k)] - ]) - for func in [self.get_sphere, self.get_cylinder] - ]) - groups.scale(0.5) - for group in groups: - for shape in group: - for piece in shape: - piece.make_jagged() - shape.set_stroke(width=0) - - for group in groups: - group.add(self.get_oriented_tex("?").scale(2)) - group.arrange(RIGHT, buff=LARGE_BUFF) - groups.arrange(IN, buff=1.5) - - all_equals = VGroup() - for sphere, cylinder in zip(spheres, cylinders): - equals = self.get_oriented_tex("=") - equals.scale(1.5) - equals.rotate(90 * DEGREES, UP) - equals.move_to(interpolate( - sphere.get_nadir(), cylinder.get_zenith(), 0.5 - )) - all_equals.add(equals) - all_equals.remove(all_equals[-1]) - - arrow_groups = VGroup() - for group in groups: - arrow_group = VGroup() - for m1, m2 in zip(group, group[1:]): - arrow = self.get_oriented_tex("\\rightarrow") - arrow.move_to(interpolate( - m1.get_right(), m2.get_left(), 0.5 - )) - arrow_group.add(arrow) - arrow_groups.add(arrow_group) - - q_marks = VGroup(*[ - group[-1] - for group in groups - ]) - final_arrows = VGroup( - arrow_groups[0][-1], - arrow_groups[1][-1], - ) - for arrow in final_arrows: - dots = self.get_oriented_tex("\\dots") - dots.next_to(arrow, RIGHT, SMALL_BUFF) - arrow.add(dots) - q_marks.shift(MED_LARGE_BUFF * RIGHT) - tilted_final_arrows = VGroup( - final_arrows[0].copy().rotate( - -45 * DEGREES, axis=DOWN - ).shift(0.75 * IN), - final_arrows[1].copy().rotate( - 45 * DEGREES, axis=DOWN - ).shift(0.75 * OUT), - ) - final_q_mark = q_marks[0].copy() - final_q_mark.move_to(q_marks) - - self.set_camera_orientation( - phi=80 * DEGREES, - theta=-90 * DEGREES, - ) - - for i in range(n_shapes): - anims = [ - FadeInFrom(spheres[i], LEFT), - FadeInFrom(cylinders[i], LEFT), - ] - if i > 0: - anims += [ - Write(arrow_group[i - 1]) - for arrow_group in arrow_groups - ] - self.play(*anims, run_time=1) - self.play(GrowFromCenter(all_equals[i])) - self.play( - FadeInFrom(q_marks, LEFT), - Write(final_arrows) - ) - self.wait() - self.play( - Transform(final_arrows, tilted_final_arrows), - Transform(q_marks, VGroup(final_q_mark)), - ) - self.wait() - - def get_oriented_tex(self, tex): - result = TexMobject(tex) - result.rotate(90 * DEGREES, RIGHT) - return result - - -class WhatIsSurfaceArea(SpecialThreeDScene): - CONFIG = { - "change_power": True, - } - - def construct(self): - title = TextMobject("What is surface area?") - title.scale(1.5) - title.to_edge(UP) - title.shift(0.035 * RIGHT) - self.add_fixed_in_frame_mobjects(title) - - power_tracker = ValueTracker(1) - surface = always_redraw( - lambda: self.get_surface( - radius=3, - amplitude=1, - power=power_tracker.get_value() - ) - ) - - pieces = surface.copy() - pieces.clear_updaters() - random.shuffle(pieces.submobjects) - - self.set_camera_to_default_position() - self.begin_ambient_camera_rotation() - # self.add(self.get_axes()) - self.play(LaggedStartMap( - DrawBorderThenFill, pieces, - lag_ratio=0.2, - )) - self.remove(pieces) - self.add(surface) - if self.change_power: - self.play( - power_tracker.set_value, 5, - run_time=2 - ) - self.play( - power_tracker.set_value, 1, - run_time=2 - ) - self.wait(2) - - def get_surface(self, radius, amplitude, power): - def alt_pow(x, y): - return np.sign(x) * (np.abs(x) ** y) - return ParametricSurface( - lambda u, v: radius * np.array([ - v * np.cos(TAU * u), - v * np.sin(TAU * u), - 0, - ]) + amplitude * np.array([ - 0, - 0, - (v**2) * alt_pow(np.sin(5 * TAU * u), power), - ]), - resolution=(100, 20), - v_min=0.01 - ) - - -class AltWhatIsSurfaceArea(WhatIsSurfaceArea): - CONFIG = { - "change_power": False, - } - - def get_surface(self, radius, amplitude, power): - return ParametricSurface( - lambda u, v: radius * (1 - 0.8 * (v**2) ** power) * np.array([ - np.cos(TAU * u) * (1 + 0.5 * v * np.sin(5 * TAU * u)), - np.sin(TAU * u) * (1 + 0.5 * v * np.sin(5 * TAU * u)), - v, - ]), - v_min=-1, - v_max=1, - resolution=(100, 25), - ) - - -class EoCWrapper(Scene): - def construct(self): - title = TextMobject("Essence of calculus") - title.scale(1.5) - title.to_edge(UP) - rect = ScreenRectangle(height=6) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - -class RoleOfCalculus(SpecialThreeDScene): - CONFIG = { - "camera_config": { - "light_source_start_point": [-4, 5, 7], - } - } - - def construct(self): - calc = TexMobject("\\int", "\\int") - calc.space_out_submobjects(0.4) - calc.scale(2) - arrow = Vector(2 * RIGHT, color=WHITE) - sphere = self.get_sphere() - sphere.rotate(70 * DEGREES, axis=LEFT) - - group = VGroup(calc, arrow, sphere) - group.arrange(RIGHT) - group.shift(0.5 * RIGHT) - cross = Cross(group[:2], stroke_width=10) - - # arrow2 = arrow.copy() - - self.add(calc, arrow) - self.play(Write(sphere)) - self.wait() - self.play(ShowCreation(cross)) - self.wait() - self.play( - sphere.next_to, ORIGIN, LEFT, 1.0, - arrow.move_to, ORIGIN, LEFT, - calc.next_to, ORIGIN, RIGHT, 2.25, - FadeOut(cross), - path_arc=PI, - run_time=2, - ) - self.wait() - - -class UnwrappedCircleLogic(UnfoldCircles): - CONFIG = { - "radius": 1.25, - "dr": 0.01, - } - - def construct(self): - radius = self.radius - dr = self.dr - - TexMobject.CONFIG["background_stroke_width"] = 2 - unwrap_factor_tracker = ValueTracker(0) - center_tracker = VectorizedPoint() - highligt_prop_tracker = ValueTracker(0.5) - - def get_highlight_prop(): - return highligt_prop_tracker.get_value() - - def get_r(): - return radius * get_highlight_prop() - - center_tracker.move_to(4.5 * LEFT) - - def get_unwrapped_circle(): - result = self.get_unwrapped_circle( - radius=radius, dr=dr, - unwrap_factor=unwrap_factor_tracker.get_value(), - center=center_tracker.get_center() - ) - self.get_submob_from_prop( - result, get_highlight_prop() - ).set_stroke(YELLOW, 2) - return result - - unwrapped_circle = always_redraw(get_unwrapped_circle) - circle = unwrapped_circle.copy() - circle.clear_updaters() - R_line = Line(circle.get_center(), circle.get_bottom()) - R_line.set_stroke(WHITE, 2) - R_label = TexMobject("R") - R_label.next_to(R_line, LEFT) - circle_group = VGroup(circle, R_line, R_label) - - tri_R_line = always_redraw( - lambda: Line( - ORIGIN, radius * DOWN - ).shift(center_tracker.get_center()) - ) - - # Unwrap - self.play(FadeInFromDown(circle_group)) - self.add(circle_group, unwrapped_circle, tri_R_line, R_label) - circle_group.set_stroke(opacity=0.5) - self.play( - unwrap_factor_tracker.set_value, 1, - run_time=2 - ) - self.play( - center_tracker.move_to, - circle.get_right() + (radius + MED_SMALL_BUFF) * RIGHT, - circle_group.set_stroke, {"opacity": 1}, - ) - self.wait() - - # Change radius - r_line = always_redraw( - lambda: Line( - ORIGIN, get_r() * DOWN, - stroke_width=2, - stroke_color=WHITE, - ).shift(circle.get_center()) - ) - r_label = TexMobject("r") - r_label.add_updater( - lambda m: m.next_to(r_line, LEFT, SMALL_BUFF) - ) - two_pi_r_label = TexMobject("2\\pi r") - two_pi_r_label.add_updater( - lambda m: m.next_to( - self.get_submob_from_prop( - unwrapped_circle, - get_highlight_prop(), - ), DOWN, SMALL_BUFF - ) - ) - - circle.add_updater( - lambda m: m.match_style(unwrapped_circle) - ) - - self.play( - ReplacementTransform(R_line, r_line), - ReplacementTransform(R_label, r_label), - FadeInFromDown( - two_pi_r_label.copy().clear_updaters(), - remover=True - ) - ) - self.add(two_pi_r_label) - for prop in [0.2, 0.8, 0.5]: - self.play( - highligt_prop_tracker.set_value, prop, - run_time=2 - ) - - # Show line - line = Line(*[ - unwrapped_circle.get_corner(vect) - for vect in (UL, DR) - ]) - line.set_color(PINK) - line.set_fill(BLACK, 1) - line_word = TextMobject("Line") - line_word.next_to(ORIGIN, UP, SMALL_BUFF) - line_word.rotate(line.get_angle(), about_point=ORIGIN) - line_word.shift(line.get_center()) - - curve = line.copy() - curve.points[1] = unwrapped_circle.get_corner(DL) - not_line = TextMobject("Not line") - not_line.rotate(line.get_angle() / 2) - not_line.move_to(line_word) - not_line.shift(0.3 * DOWN) - - self.play( - ShowCreation(line), - Write(line_word), - ) - self.wait() - self.play(highligt_prop_tracker.set_value, 1) - self.wait() - - # Bend - line.save_state() - line_word.save_state() - self.play( - Transform(line, curve), - Transform(line_word, not_line), - ) - self.wait() - self.play( - Restore(line), - Restore(line_word), - # FadeIn(two_pi_r_label), - ) - self.wait() - - def get_submob_from_prop(self, mob, prop): - n = len(mob.submobjects) - return mob[min(int(prop * n), n - 1)] - - -class AskAboutDirectConnection(TeacherStudentsScene, SpecialThreeDScene): - CONFIG = { - "camera_config": { - "light_source_start_point": [-4, 5, 7], - } - } - - def construct(self): - sphere = Sphere() - cylinder = Cylinder() - for mob in sphere, cylinder: - mob.rotate(70 * DEGREES, LEFT) - formula = TexMobject("4\\pi R^2") - formula.set_color(BLUE) - circle = Circle() - circle.set_stroke(width=0) - circle.set_fill(GREY_BROWN, 1) - area_label = TexMobject("\\pi R^2", background_stroke_width=0) - area_label.scale(1.5) - circle.add(area_label) - group = VGroup( - sphere, cylinder, formula, circle - ) - for mob in group: - mob.set_height(1.5) - formula.scale(0.5) - group.arrange(RIGHT, buff=1.5) - group.to_edge(UP, buff=2) - group[1:3].to_edge(UP) - - arrows = VGroup() - for m1, m2 in zip(group, group[1:]): - arrow = Arrow( - m1.get_center(), m2.get_center(), - buff=1, - color=WHITE - ) - arrows.add(arrow) - direct_arrow = Arrow( - sphere, circle, color=WHITE - ) - q_marks = TexMobject(*"???") - q_marks.space_out_submobjects(1.5) - q_marks.scale(1.5) - q_marks.next_to(direct_arrow, DOWN) - - self.play( - self.teacher.change, "raise_right_hand", - self.get_student_changes( - *3 * ["pondering"], - look_at_arg=group, - ), - LaggedStartMap(FadeInFromDown, group), - LaggedStartMap(GrowArrow, arrows) - ) - self.wait() - self.play( - self.teacher.change, "pondering", - self.students[2].change, "raise_right_hand", - GrowArrow(direct_arrow), - LaggedStartMap( - FadeInFrom, q_marks, - lambda m: (m, UP), - lag_ratio=0.8, - run_time=1.5, - ) - ) - self.change_student_modes( - "erm", "sassy", "raise_right_hand", - ) - self.wait(2) - self.look_at(group) - self.wait(2) - - -class ExercisesGiveLearning(MovingCameraScene): - def construct(self): - bulb = Lightbulb() - arrow1 = Arrow(ORIGIN, RIGHT, buff=0) - lectures = TextMobject("Lectures") - exercises = TextMobject("Exercises") - frame = self.camera_frame - frame.scale(0.7) - - bulb.next_to(arrow1, RIGHT) - for word in lectures, exercises: - word.next_to(arrow1, LEFT) - - cross = Cross(lectures) - - # Knock down lectures - self.add(lectures) - self.play(GrowArrow(arrow1)) - self.play(LaggedStartMap(DrawBorderThenFill, bulb)) - self.play(ShowCreation(cross)) - self.play( - VGroup(lectures, cross).shift, DOWN, - FadeInFrom(exercises, UP) - ) - self.wait() - - # Show self - arrow2 = arrow1.copy() - arrow2.next_to(lectures, LEFT) - logo = Logo() - logo.set_height(1) - logo.next_to(arrow2, LEFT) - pupil_copy = logo.pupil.copy() - - self.add(logo, pupil_copy) - self.play( - frame.shift, 1.5 * LEFT, - Write(logo, run_time=1.5) - ) - self.remove(pupil_copy) - self.play( - GrowArrow(arrow2), - FadeOut(cross) - ) - self.wait() - self.play( - VGroup(logo, arrow2).next_to, - exercises, LEFT - ) - self.wait() - - -class NobodyLikesHomework(TeacherStudentsScene): - def construct(self): - self.change_student_modes( - "angry", "pleading", "angry", - added_anims=[self.teacher.change, "guilty"] - ) - self.wait() - self.change_all_student_modes( - "tired", look_at_arg=8 * RIGHT + 4 * DOWN, - added_anims=[self.teacher.change, "tease"] - ) - self.wait(2) - - -class ChallengeMode(Scene): - def construct(self): - words = TextMobject("Challenge mode: Predict the proof") - words.scale(1.5) - words.to_edge(UP) - self.play(Write(words)) - self.wait() - - -class SecondProof(SpecialThreeDScene): - CONFIG = { - "sphere_config": { - "resolution": (30, 30), - }, - "n_random_subsets": 12, - } - - def construct(self): - self.setup_shapes() - self.divide_into_rings() - self.show_shadows() - self.correspond_to_every_other_ring() - self.cut_cross_section() - self.show_theta() - self.enumerate_rings() - self.ask_about_ring_circumference() - self.ask_about_shadow_area() - self.ask_about_2_to_1_correspondance() - self.show_all_shadow_rings() - self.ask_about_global_correspondance() - - def setup_shapes(self): - sphere = self.get_sphere() - sphere.set_stroke(WHITE, width=0.25) - self.add(sphere) - self.sphere = sphere - - u_values, v_values = sphere.get_u_values_and_v_values() - rings = VGroup(*[VGroup() for u in u_values]) - for piece in sphere: - rings[piece.u_index].add(piece.copy()) - self.set_ring_colors(rings) - self.rings = rings - - self.axes = self.get_axes() - self.add(self.axes) - - self.set_camera_to_default_position() - self.begin_ambient_camera_rotation() - - def divide_into_rings(self): - rings = self.rings - - self.play(FadeIn(rings), FadeOut(self.sphere)) - self.play( - rings.space_out_submobjects, 1.5, - rate_func=there_and_back_with_pause, - run_time=3 - ) - self.wait(2) - rings.save_state() - - def show_shadows(self): - rings = self.rings - north_rings = rings[:len(rings) // 2] - ghost_rings = rings.copy() - ghost_rings.set_fill(opacity=0.0) - ghost_rings.set_stroke(WHITE, width=0.5, opacity=0.2) - - north_rings.submobjects.reverse() - shadows = self.get_shadow(north_rings) - for piece in shadows.family_members_with_points(): - piece.set_stroke( - piece.get_fill_color(), - width=0.5, - ) - for shadow in shadows: - shadow.save_state() - shadows.become(north_rings) - - self.add(ghost_rings) - self.play(FadeOut(rings), Animation(shadows)) - self.play(LaggedStartMap(Restore, shadows)) - self.wait() - self.move_camera(phi=40 * DEGREES) - self.wait(3) - - # Show circle - radius = self.sphere_config["radius"] - radial_line = Line(ORIGIN, radius * RIGHT) - radial_line.set_stroke(RED) - R_label = TexMobject("R") - R_label.set_background_stroke(width=1) - R_label.next_to(radial_line, DOWN) - - self.play( - FadeInFromDown(R_label), - ShowCreation(radial_line) - ) - self.play(Rotating( - radial_line, angle=TAU, - about_point=ORIGIN, - rate_func=smooth, - run_time=3, - )) - self.wait() - - self.set_variables_as_attrs( - shadows, ghost_rings, - radial_line, R_label - ) - - def correspond_to_every_other_ring(self): - rings = self.rings - shadows = self.shadows - shadows.submobjects.reverse() - - rings.restore() - self.set_ring_colors(rings) - every_other_ring = rings[1::2] - self.move_camera( - phi=70 * DEGREES, - theta=-135 * DEGREES, - added_anims=[ - FadeOut(self.R_label), - FadeOut(self.radial_line), - ], - run_time=2, - ) - shadows_copy = shadows.copy() - shadows.fade(1) - self.play( - ReplacementTransform( - shadows_copy, every_other_ring - ), - FadeOut(self.ghost_rings), - run_time=2, - ) - self.wait(5) - - self.every_other_ring = every_other_ring - - def cut_cross_section(self): - shadows = self.shadows - every_other_ring = self.every_other_ring - rings = self.rings - - back_half = self.get_hemisphere(rings, UP) - front_half = self.get_hemisphere(rings, DOWN) - front_half_ghost = front_half.copy() - front_half_ghost.set_fill(opacity=0.2) - front_half_ghost.set_stroke(opacity=0) - - # shaded_back_half = back_half.copy() - # for piece in shaded_back_half.family_members_with_points(): - # piece.points = piece.points[::-1] - # shaded_back_half.scale(0.999) - # shaded_back_half.set_fill(opacity=0.5) - - radius = self.sphere_config["radius"] - circle = Circle(radius=radius) - circle.set_stroke(PINK, 2) - circle.rotate(90 * DEGREES, RIGHT) - - every_other_ring_copy = every_other_ring.copy() - self.add(every_other_ring_copy) - self.remove(every_other_ring) - rings.set_fill(opacity=0.8) - rings.set_stroke(opacity=0.6) - self.play( - FadeIn(back_half), - FadeIn(front_half_ghost), - FadeIn(circle), - FadeOut(shadows), - FadeOut(every_other_ring_copy), - ) - self.wait() - - self.set_variables_as_attrs( - back_half, front_half, - front_half_ghost, - slice_circle=circle - ) - - def show_theta(self): - theta_tracker = ValueTracker(0) - get_theta = theta_tracker.get_value - theta_group = always_redraw( - lambda: self.get_theta_group(get_theta()) - ) - theta_mob_opacity_tracker = ValueTracker(0) - get_theta_mob_opacity = theta_mob_opacity_tracker.get_value - theta_mob = theta_group[-1] - theta_mob.add_updater( - lambda m: m.set_fill(opacity=get_theta_mob_opacity()) - ) - theta_mob.add_updater( - lambda m: m.set_background_stroke( - width=get_theta_mob_opacity() - ) - ) - - lit_ring = always_redraw( - lambda: self.get_ring_from_theta( - self.rings, get_theta() - ).copy().set_color(YELLOW) - ) - - self.stop_ambient_camera_rotation() - self.move_camera(theta=-60 * DEGREES) - - self.add(theta_group, lit_ring) - n_rings = len(self.rings) - 1 - lit_ring_index = int((30 / 180) * n_rings) - angle = PI * lit_ring_index / n_rings - for alpha in [angle, 0, PI, angle]: - self.play( - theta_tracker.set_value, alpha, - theta_mob_opacity_tracker.set_value, 1, - Animation(self.camera.phi_tracker), - run_time=2, - ) - self.wait() - - # Label d-theta - radius = self.sphere_config["radius"] - d_theta = PI / len(self.rings) - alt_theta = get_theta() + d_theta - alt_theta_group = self.get_theta_group(alt_theta) - alt_R_line = alt_theta_group[1] - # d_theta_arc = Arc( - # start_angle=get_theta(), - # angle=d_theta, - # radius=theta_group[0].radius, - # stroke_color=PINK, - # stroke_width=3, - # ) - # d_theta_arc.rotate(90 * DEGREES, axis=RIGHT, about_point=ORIGIN) - brace = Brace(Line(ORIGIN, radius * d_theta * RIGHT), UP) - brace.rotate(90 * DEGREES, RIGHT) - brace.next_to(self.sphere, OUT, buff=0) - brace.add_to_back(brace.copy().set_stroke(BLACK, 3)) - brace.rotate( - get_theta() + d_theta / 2, - axis=UP, - about_point=ORIGIN, - ) - brace_label = TexMobject("R\\,d\\theta") - brace_label.rotate(90 * DEGREES, RIGHT) - brace_label.next_to(brace, OUT + RIGHT, buff=0) - radial_line = self.radial_line - R_label = self.R_label - R_label.rotate(90 * DEGREES, RIGHT) - R_label.next_to(radial_line, IN, SMALL_BUFF) - - self.play( - TransformFromCopy(theta_group[1], alt_R_line), - GrowFromCenter(brace), - Animation(self.camera.phi_tracker), - ) - self.wait() - self.move_camera( - phi=90 * DEGREES, - theta=-90 * DEGREES, - ) - self.wait() - self.play( - FadeInFrom(brace_label, IN), - ) - self.play( - ShowCreation(radial_line), - FadeIn(R_label), - ) - self.wait() - self.move_camera( - phi=70 * DEGREES, - theta=-70 * DEGREES, - ) - self.wait(3) - - self.set_variables_as_attrs( - theta_tracker, lit_ring, theta_group, - brace, brace_label, d_theta, - alt_R_line, theta_mob_opacity_tracker, - ) - - def enumerate_rings(self): - pass # Skip, for now... - - def ask_about_ring_circumference(self): - theta = self.theta_tracker.get_value() - radius = self.sphere_config["radius"] - circle = Circle( - radius=radius * np.sin(theta) - ) - circle.shift(radius * np.cos(theta) * OUT) - circle.set_stroke(Color("red"), 5) - - to_fade = VGroup( - self.R_label, self.radial_line, - self.brace, self.brace_label - ) - - self.move_camera( - phi=0 * DEGREES, - theta=-90 * DEGREES, - added_anims=[FadeOut(to_fade)] - ) - self.play(ShowCreation(circle)) - self.wait() - self.move_camera( - phi=70 * DEGREES, - theta=-70 * DEGREES, - added_anims=[ - FadeIn(to_fade), - FadeOut(circle), - ] - ) - self.wait() - - def ask_about_shadow_area(self): - lit_ring = self.lit_ring - lit_ring_copy = lit_ring.copy() - lit_ring_copy.clear_updaters() - - all_shadows = self.shadows - all_shadows.set_fill(BLUE_E, 0.5) - for piece in all_shadows.family_members_with_points(): - piece.set_stroke(width=0) - - radius = self.sphere_config["radius"] - shadow = self.get_shadow(lit_ring) - theta = self.theta_tracker.get_value() - d_theta = self.d_theta - - def get_dashed_line(angle): - p0 = np.cos(angle) * OUT + np.sin(angle) * RIGHT - p0 *= radius - p1 = np.array([*p0[:2], 0]) - result = DashedLine(p0, p1) - result.set_stroke(WHITE, 1) - result.add_to_back( - result.copy().set_stroke(BLACK, 2) - ) - result.set_shade_in_3d(True) - return result - - dashed_lines = VGroup(*[ - get_dashed_line(t) - for t in [theta, theta + d_theta] - ]) - - self.play( - ReplacementTransform(lit_ring_copy, shadow), - FadeOut(self.R_label), - FadeOut(self.radial_line), - Animation(self.camera.phi_tracker), - *map(ShowCreation, dashed_lines), - run_time=2, - ) - self.wait(2) - - self.set_variables_as_attrs( - dashed_lines, - lit_shadow=shadow, - ) - - def ask_about_2_to_1_correspondance(self): - theta_tracker = ValueTracker(0) - get_theta = theta_tracker.get_value - new_lit_ring = always_redraw( - lambda: self.get_ring_from_theta( - self.rings, get_theta() - ).copy().set_color(PINK) - ) - - self.add(new_lit_ring) - for angle in [PI, 0, PI]: - self.play( - theta_tracker.set_value, angle, - Animation(self.camera.phi_tracker), - run_time=3 - ) - self.remove(new_lit_ring) - self.remove(theta_tracker) - - def show_all_shadow_rings(self): - lit_ring_copy = self.lit_ring.copy() - lit_ring_copy.clear_updaters() - self.remove(self.lit_ring) - theta_group_copy = self.theta_group.copy() - theta_group_copy.clear_updaters() - self.remove(self.theta_group, *self.theta_group) - to_fade = VGroup( - theta_group_copy, self.alt_R_line, - self.brace, self.brace_label, - lit_ring_copy, self.lit_shadow, - self.slice_circle, - self.dashed_lines, - ) - - R_label = self.R_label - radial_line = self.radial_line - R_label.rotate( - -90 * DEGREES, - axis=RIGHT, about_point=radial_line.get_center() - ) - shadows = self.shadows - self.set_ring_colors(shadows, [GREY_BROWN, DARK_GREY]) - for submob in shadows: - submob.save_state() - shadows.become(self.rings.saved_state[:len(shadows)]) - - self.play( - FadeOut(to_fade), - LaggedStartMap(FadeIn, shadows), - self.theta_mob_opacity_tracker.set_value, 0, - ) - self.play( - LaggedStartMap(Restore, shadows), - ApplyMethod( - self.camera.phi_tracker.set_value, 60 * DEGREES, - ), - ApplyMethod( - self.camera.theta_tracker.set_value, -130 * DEGREES, - ), - run_time=3 - ) - self.play( - ShowCreation(radial_line), - FadeIn(R_label), - Animation(self.camera.phi_tracker), - ) - self.begin_ambient_camera_rotation() - self.wait() - - rings = self.rings - rings_copy = rings.saved_state.copy() - self.set_ring_colors(rings_copy) - self.play( - FadeOut(R_label), - FadeOut(radial_line), - FadeIn(rings_copy) - ) - self.remove(rings_copy) - rings.become(rings_copy) - self.add(rings) - - def ask_about_global_correspondance(self): - rings = self.rings - - self.play( - FadeOut(rings[::2]) - ) - self.wait(8) - - # - def set_ring_colors(self, rings, colors=[BLUE_E, BLUE_D]): - for i, ring in enumerate(rings): - color = colors[i % len(colors)] - ring.set_fill(color, opacity=1) - ring.set_stroke(color, width=0.5, opacity=1) - for piece in ring: - piece.insert_n_curves(4) - piece.on_sphere = True - piece.points = np.array([ - *piece.points[3:-1], - *piece.points[:3], - piece.points[3] - ]) - return rings - - def get_shadow(self, mobject): - result = mobject.copy() - result.apply_function( - lambda p: np.array([*p[:2], 0]) - ) - return result - - def get_hemisphere(self, group, vect): - if len(group.submobjects) == 0: - if np.dot(group.get_center(), vect) > 0: - return group - else: - return VMobject() - else: - return VGroup(*[ - self.get_hemisphere(submob, vect) - for submob in group - ]) - - def get_northern_hemisphere(self, group): - return self.get_hemisphere(group, OUT) - - def get_theta(self, ring): - piece = ring[0] - point = piece.points[3] - return np.arccos(point[2] / get_norm(point)) - - def get_theta_group(self, theta): - arc = Arc( - start_angle=90 * DEGREES, - angle=-theta, - radius=0.5, - ) - arc.rotate(90 * DEGREES, RIGHT, about_point=ORIGIN) - arc.set_stroke(YELLOW, 2) - theta_mob = TexMobject("\\theta") - theta_mob.rotate(90 * DEGREES, RIGHT) - vect = np.cos(theta / 2) * OUT + np.sin(theta / 2) * RIGHT - theta_mob.move_to( - (arc.radius + 0.25) * normalize(vect), - ) - theta_mob.set_background_stroke(width=1) - - radius = self.sphere_config["radius"] - point = arc.point_from_proportion(1) - radial_line = Line( - ORIGIN, radius * normalize(point) - ) - radial_line.set_stroke(WHITE, 2) - - return VGroup(arc, radial_line, theta_mob) - - def get_ring_from_theta(self, rings, theta): - n_rings = len(rings) - index = min(int((theta / PI) * n_rings), n_rings - 1) - return rings[index] - - -class SecondProofHigherRes(SecondProof): - CONFIG = { - "sphere_config": { - "resolution": (60, 60), - }, - } - - -class SecondProofHighestRes(SecondProof): - CONFIG = { - "sphere_config": { - "resolution": (120, 120), - }, - } - - -class RangeFrom0To180(Scene): - def construct(self): - angle = Integer(0, unit="^\\circ") - angle.scale(2) - - self.add(angle) - self.wait() - self.play(ChangeDecimalToValue( - angle, 180, - run_time=2, - )) - self.wait() - - -class Question1(Scene): - def construct(self): - kwargs = { - "tex_to_color_map": { - "circumference": RED, - } - } - question = TextMobject( - """ - \\small - Question \\#1: What is the circumference of\\\\ - one of these rings (in terms of $R$ and $\\theta$)?\\\\ - """, - **kwargs - ) - prompt = TextMobject( - """ - Multiply this circumference by $R\\,d\\theta$ to \\\\ - get an approximation of the ring's area. - """, - **kwargs - - ) - for words in question, prompt: - words.set_width(FRAME_WIDTH - 1) - - self.play(FadeInFromDown(question)) - self.wait(2) - for word in question, prompt: - word.circum = word.get_part_by_tex("circumference") - word.remove(word.circum) - self.play( - FadeOutAndShift(question, UP), - FadeInFromDown(prompt), - question.circum.replace, prompt.circum, - run_time=1.5 - ) - self.wait() - - -class YouCouldIntegrate(TeacherStudentsScene): - def construct(self): - self.student_says( - "Integrate?", - student_index=2, - bubble_kwargs={"direction": LEFT}, - ) - self.play(self.teacher.change, "hesitant") - self.wait() - self.teacher_says( - "We'll be a bit \\\\ more Archimedean", - target_mode="speaking" - ) - self.change_all_student_modes("confused") - self.wait() - - -class Question2(Scene): - def construct(self): - question = TextMobject( - """ - Question \\#2: What is the area of the shadow of\\\\ - one of these rings? (In terms of $R$, $\\theta$, and $d\\theta$). - """, - tex_to_color_map={ - "shadow": YELLOW, - } - ) - question.set_width(FRAME_WIDTH - 1) - - self.play(FadeInFromDown(question)) - self.wait() - - -class Question3(Scene): - def construct(self): - question = TextMobject("Question \\#3:") - question.to_edge(LEFT) - equation = TextMobject( - "(Shadow area)", "=", "$\\frac{1}{2}$", - "(Area of one of the rings)" - ) - equation[0][1:-1].set_color(YELLOW) - equation[3][1:-1].set_color(PINK) - equation.next_to(question, RIGHT) - which_one = TextMobject("Which one?") - # which_one.set_color(YELLOW) - brace = Brace(equation[-1], DOWN, buff=SMALL_BUFF) - which_one.next_to(brace, DOWN, SMALL_BUFF) - - self.add(question) - self.play(FadeInFrom(equation)) - self.wait() - self.play( - GrowFromCenter(brace), - Write(which_one) - ) - self.wait() - - -class ExtraHint(Scene): - def construct(self): - title = TextMobject("Extra hint") - title.scale(2.5) - title.shift(UP) - equation = TexMobject( - "\\sin(2\\theta) = 2\\sin(\\theta)\\cos(\\theta)" - ) - equation.next_to(title, DOWN) - self.add(title, equation) - - -class Question4(Scene): - def construct(self): - question = TextMobject( - "Question \\#4:", - "Explain how the shadows relate to\\\\" - "every other ring on the sphere.", - tex_to_color_map={ - "shadows": YELLOW, - "every other ring": BLUE, - } - ) - - self.add(question[0]) - self.wait() - self.play(FadeInFromDown(question[1:])) - self.wait() - - -class Question5(Scene): - def construct(self): - question = TextMobject( - """ - Question \\#5: Why does this imply that the \\\\ - shadow is $\\frac{1}{4}$ the surface area? - """ - ) - self.play(FadeInFromDown(question)) - self.wait() - - -class SpherePatronThanks(Scene): - CONFIG = { - "specific_patrons": [ - "1stViewMaths", - "Adrian Robinson", - "Alexis Olson", - "Ali Yahya", - "Andrew Busey", - "Ankalagon", - "Antonio Juarez", - "Art Ianuzzi", - "Arthur Zey", - "Awoo", - "Bernd Sing", - "Boris Veselinovich", - "Brian Staroselsky", - "brian tiger chow", - "Brice Gower", - "Britt Selvitelle", - "Burt Humburg", - "Carla Kirby", - "Charles Southerland", - "Chris Connett", - "Christian Kaiser", - "Clark Gaebel", - "Cooper Jones", - "Danger Dai", - "Dave B", - "Dave Kester", - "dave nicponski", - "David Clark", - "David Gow", - "Delton Ding", - "Devarsh Desai", - "Devin Scott", - "eaglle", - "Eric Younge", - "Eryq Ouithaqueue", - "Evan Phillips", - "Federico Lebron", - "Florian Chudigiewitsch", - "Giovanni Filippi", - "Graham", - "Hal Hildebrand", - "J", - "Jacob Magnuson", - "Jameel Syed", - "James Hughes", - "Jan Pijpers", - "Jason Hise", - "Jeff Linse", - "Jeff Straathof", - "Jerry Ling", - "John Griffith", - "John Haley", - "John Shaughnessy", - "John V Wertheim", - "Jonathan Eppele", - "Jonathan Wilson", - "Joseph John Cox", - "Joseph Kelly", - "Juan Benet", - "Julian Pulgarin", - "Kai-Siang Ang", - "Kanan Gill", - "Kaustuv DeBiswas", - "L0j1k", - "Linh Tran", - "Luc Ritchie", - "Ludwig Schubert", - "Lukas -krtek.net- Novy", - "Lukas Biewald", - "Magister Mugit", - "Magnus Dahlström", - "Magnus Lysfjord", - "Mark B Bahu", - "Markus Persson", - "Mathew Bramson", - "Mathias Jansson", - "Matt Langford", - "Matt Roveto", - "Matt Russell", - "Matthew Cocke", - "Maurício Collares", - "Mehdi Razavi", - "Michael Faust", - "Michael Hardel", - "MrSneaky", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nathan Jessurun", - "Nero Li", - "Oliver Steele", - "Omar Zrien", - "Peter Ehrnstrom", - "Peter Mcinerney", - "Quantopian", - "Randy C. Will", - "Richard Burgmann", - "Ripta Pasay", - "Rish Kundalia", - "Robert Teed", - "Roobie", - "Roy Larson", - "Ryan Atallah", - "Ryan Williams", - "Scott Walter, Ph.D.", - "Sindre Reino Trosterud", - "soekul", - "Solara570", - "Song Gao", - "Steven Soloway", - "Stevie Metke", - "Ted Suzman", - "Valeriy Skobelev", - "Vassili Philippov", - "Xavier Bernard", - "Yana Chernobilsky", - "Yaw Etse", - "YinYangBalance Asia", - "Zach Cardwell", - ], - } - - def construct(self): - self.add_title() - self.show_columns() - - def add_title(self): - title = TextMobject("Funded by the community, with special thanks to:") - title.set_color(YELLOW) - title.to_edge(UP) - underline = Line(LEFT, RIGHT) - underline.set_width(title.get_width() + MED_SMALL_BUFF) - underline.next_to(title, DOWN, SMALL_BUFF) - title.add(underline) - - self.add(title) - self.title = title - - def show_columns(self): - random.seed(1) - random.shuffle(self.specific_patrons) - patrons = VGroup(*[ - TextMobject(name) - for name in self.specific_patrons - ]) - columns = VGroup() - column_size = 15 - for n in range(0, len(patrons), column_size): - column = patrons[n:n + column_size] - column.arrange( - DOWN, - aligned_edge=LEFT - ) - columns.add(column) - columns.set_height(6) - for group in columns[:4], columns[4:]: - for k, column in enumerate(group): - column.move_to( - 6.5 * LEFT + 3.75 * k * RIGHT + 2.5 * UP, - UL - ) - - self.add(columns[:4]) - self.wait() - for k in range(4): - self.play( - FadeOut(columns[k]), - FadeIn(columns[k + 4]), - ) - self.wait() - - -class EndScreen(PatreonEndScreen): - CONFIG = { - "thanks_words": "", - } - - -class ForThoseStillAround(Scene): - def construct(self): - words = TextMobject("Still here?") - words.scale(1.5) - url = TextMobject("3blue1brown.com/store") - # url.scale(1.5) - url.to_edge(UP, buff=MED_SMALL_BUFF) - - self.play(Write(words)) - self.wait() - self.play(ReplacementTransform(words, url)) - self.wait() - - -class PatronWords(Scene): - def construct(self): - words = TextMobject("\\$2+ Patrons get \\\\ 50\\% off") - words.to_corner(UL) - words.set_color(RED) - self.add(words) - - -class PlushMe(TeacherStudentsScene): - def construct(self): - self.student_says("Plushie me?") - self.change_student_modes("happy", None, "happy") - self.play(self.teacher.change, "confused") - self.wait() - self.teacher_says("...why?", target_mode="maybe") - self.wait(2) - - -class Thumbnail(SpecialThreeDScene): - CONFIG = { - "camera_config": { - "light_source_start_point": [-10, 5, 7], - } - } - - def construct(self): - radius = 1.75 - sphere = self.get_sphere(radius=radius) - sphere.rotate(70 * DEGREES, LEFT) - sphere.set_fill(BLUE_E) - sphere.set_stroke(WHITE, 0.5) - - circles = VGroup(*[ - Circle(radius=radius) - for x in range(4) - ]) - circles.set_stroke(WHITE, 2) - circles.set_fill(BLUE_E, 1) - circles[0].set_fill(GREY_BROWN) - circles.arrange_in_grid() - for circle in circles: - formula = TexMobject("\\pi", "R", "^2") - formula.set_color_by_tex("R", YELLOW) - formula.scale(2) - formula.move_to(circle) - circle.add(formula) - - equals = TexMobject("=") - equals.scale(3) - - group = VGroup(sphere, equals, circles) - group.arrange(RIGHT, buff=MED_SMALL_BUFF) - equals.shift(3 * SMALL_BUFF * RIGHT) - - why = TextMobject("Why?!") - why.set_color(YELLOW) - why.scale(2.5) - why.next_to(sphere, UP) - - sa_formula = TexMobject("4\\pi", "R", "^2") - sa_formula.set_color_by_tex("R", YELLOW) - sa_formula.scale(2) - sa_formula.next_to(sphere, DOWN) - - self.camera.distance_tracker.set_value(100) - - self.add(sphere, equals, circles, why, sa_formula) diff --git a/from_3b1b/old/spirals.py b/from_3b1b/old/spirals.py deleted file mode 100644 index ed5b3cb9..00000000 --- a/from_3b1b/old/spirals.py +++ /dev/null @@ -1,4931 +0,0 @@ -from manimlib.imports import * -import json -import numbers - - -OUTPUT_DIRECTORY = "spirals" -INV_113_MOD_710 = 377 # Inverse of 113 mode 710 -INV_7_MOD_44 = 19 - - -def is_prime(n): - if n < 2: - return False - for k in range(2, int(np.sqrt(n)) + 1): - if n % k == 0: - return False - return True - - -def generate_prime_list(*args): - if len(args) == 1: - start, stop = 2, args[0] - elif len(args) == 2: - start, stop = args - start = max(start, 2) - else: - raise TypeError("generate_prime_list takes 1 or 2 arguments") - - result = [ - n for n in range(start, stop) - if is_prime(n) - ] - return result - - -def get_gcd(x, y): - while y > 0: - x, y = y, x % y - return x - - -def read_in_primes(max_N=None): - if max_N is None: - max_N = int(1e7) - - if max_N < 1e5: - file = "primes_1e5.json" - elif max_N < 1e6: - file = "primes_1e6.json" - else: - file = "primes_1e7.json" - - with open(os.path.join("assets", file)) as fp: - primes = np.array(json.load(fp)) - return primes[primes <= max_N] - - -class SpiralScene(MovingCameraScene): - CONFIG = { - "axes_config": { - "axis_config": { - "stroke_width": 1.5, - } - }, - "default_dot_color": TEAL, - "p_spiral_width": 6, - } - - def setup(self): - super().setup() - self.axes = Axes(**self.axes_config) - self.add(self.axes) - - def get_v_spiral(self, sequence, axes=None, box_width=None): - if axes is None: - axes = self.axes - if box_width is None: - unit = get_norm(axes.c2p(1, 0) - axes.c2p(0, 0)), - box_width = max( - 0.2 / (-np.log10(unit) + 1), - 0.02, - ) - - return VGroup(*[ - Square( - side_length=box_width, - fill_color=self.default_dot_color, - fill_opacity=1, - stroke_width=0, - ).move_to(self.get_polar_point(n, n, axes)) - for n in sequence - ]) - - def get_p_spiral(self, sequence, axes=None): - if axes is None: - axes = self.axes - result = PMobject( - color=self.default_dot_color, - stroke_width=self.p_spiral_width, - ) - result.add_points([ - self.get_polar_point(n, n, axes) - for n in sequence - ]) - return result - - def get_prime_v_spiral(self, max_N, **kwargs): - primes = read_in_primes(max_N) - return self.get_v_spiral(primes, **kwargs) - - def get_prime_p_spiral(self, max_N, **kwargs): - primes = read_in_primes(max_N) - return self.get_p_spiral(primes, **kwargs) - - def get_polar_point(self, r, theta, axes=None): - if axes is None: - axes = self.axes - return axes.c2p(r * np.cos(theta), r * np.sin(theta)) - - def set_scale(self, scale, - axes=None, - spiral=None, - to_shrink=None, - min_box_width=0.05, - target_p_spiral_width=None, - added_anims=[], - run_time=3): - if axes is None: - axes = self.axes - if added_anims is None: - added_anims = [] - sf = self.get_scale_factor(scale, axes) - - anims = [] - for mob in [axes, spiral, to_shrink]: - if mob is None: - continue - mob.generate_target() - mob.target.scale(sf, about_point=ORIGIN) - if mob is spiral: - if isinstance(mob, VMobject): - old_width = mob[0].get_width() - for submob in mob.target: - submob.set_width(max( - old_width * sf, - min_box_width, - )) - elif isinstance(mob, PMobject): - if target_p_spiral_width is not None: - mob.target.set_stroke_width(target_p_spiral_width) - anims.append(MoveToTarget(mob)) - anims += added_anims - - if run_time == 0: - for anim in anims: - anim.begin() - anim.update(1) - anim.finish() - else: - self.play( - *anims, - run_time=run_time, - rate_func=lambda t: interpolate( - smooth(t), - smooth(t)**(sf**(0.5)), - t, - ) - ) - - def get_scale_factor(self, target_scale, axes=None): - if axes is None: - axes = self.axes - unit = get_norm(axes.c2p(1, 0) - axes.c2p(0, 0)) - return 1 / (target_scale * unit) - - def get_labels(self, sequence, scale_func=np.sqrt): - labels = VGroup() - for n in sequence: - label = Integer(n) - label.set_stroke(width=0, background=True) - label.scale(scale_func(n)) - label.next_to( - self.get_polar_point(n, n), UP, - buff=0.5 * label.get_height(), - ) - labels.add(label) - return labels - - def get_prime_labels(self, max_N): - primes = read_in_primes(max_N) - return self.get_labels(primes) - - -# Scenes - -class AltTitle(Scene): - def construct(self): - title_text = """ - How pretty but pointless patterns\\\\ - in polar plots of primes\\\\ - prompt pretty important ponderings\\\\ - on properties of those primes. - """ - words = [w + " " for w in title_text.split(" ") if w] - title = TextMobject(*words) - title.set_width(FRAME_WIDTH - 1) - - title[2:5].set_color(TEAL) - title[12:15].set_color(YELLOW) - title.set_stroke(BLACK, 5, background=True) - - image = ImageMobject("PrimeSpiral") - image.set_height(FRAME_HEIGHT) - rect = FullScreenFadeRectangle(fill_opacity=0.25) - - self.add(image, rect) - - for word in title: - self.play( - FadeIn( - word, run_time=0.05 * len(word), - lag_ratio=0.4, - ) - ) - self.wait() - - -class HoldUpMathExchange(TeacherStudentsScene): - def construct(self): - title = TextMobject("Mathematics Stack Exchange") - title.scale(1.5) - title.to_edge(UP) - - self.add(title) - self.play(self.teacher.change, "raise_right_hand", ORIGIN), - self.change_all_student_modes("thinking", look_at_arg=ORIGIN) - self.wait(3) - self.change_all_student_modes("confused", look_at_arg=ORIGIN) - self.wait(3) - - -class MathExchangeNames(Scene): - def construct(self): - names = VGroup( - TextMobject("dwymark"), - TextMobject("Greg Martin"), - ) - names.arrange(DOWN, buff=1) - for name in names: - self.play(FadeInFrom(name, RIGHT)) - self.wait() - - -class MathExchange(ExternallyAnimatedScene): - pass - - -class PrimesAndPi(Scene): - def construct(self): - self.show_primes() - self.show_rational_approximations() - - def show_primes(self): - n_rows = 10 - n_cols = 10 - matrix = IntegerMatrix([ - [n_cols * x + y for y in range(n_cols)] - for x in range(n_rows) - ]) - numbers = matrix.get_entries() - primes = VGroup(*filter( - lambda m: is_prime(m.get_value()), - numbers, - )) - non_primes = VGroup(*filter( - lambda m: not is_prime(m.get_value()), - numbers - )) - - self.add(numbers) - - self.play( - LaggedStart(*[ - ApplyFunction( - lambda m: m.set_color(TEAL).scale(1.2), - prime - ) - for prime in primes - ]), - non_primes.set_opacity, 0.25, - run_time=2, - ) - self.wait() - - self.numbers = numbers - - def show_rational_approximations(self): - numbers = self.numbers - - approxs = TexMobject( - "{22 \\over 7} &=", "{:.12}\\dots\\\\".format(22 / 7), - "{355 \\over 113} &=", "{:.12}\\dots\\\\".format(355 / 113), - "\\pi &=", "{:.12}\\dots\\\\".format(PI), - ) - approxs[:2].shift(MED_LARGE_BUFF * UP) - approxs[-2:].shift(MED_LARGE_BUFF * DOWN) - approxs[-2:].set_color(YELLOW) - approxs[1][:4].set_color(YELLOW) - approxs[3][:8].set_color(YELLOW) - approxs.scale(1.5) - - randy = Randolph(color=YELLOW, height=1) - randy.move_to(approxs[-2][0], RIGHT) - approxs[-2][0].set_opacity(0) - - self.play( - LaggedStartMap(FadeOutAndShiftDown, numbers), - LaggedStartMap(FadeIn, approxs), - FadeIn(randy) - ) - self.play(Blink(randy)) - self.play(randy.change, "pondering", UR) - self.wait() - - -class RefresherOnPolarCoordinates(MovingCameraScene): - CONFIG = { - "x_color": GREEN, - "y_color": RED, - "r_color": YELLOW, - "theta_color": LIGHT_PINK, - } - - def construct(self): - self.show_xy_coordinates() - self.transition_to_polar_grid() - self.show_polar_coordinates() - - self.show_all_nn_tuples() - - def show_xy_coordinates(self): - plane = NumberPlane() - plane.add_coordinates() - - x = 3 * np.cos(PI / 6) - y = 3 * np.sin(PI / 6) - - point = plane.c2p(x, y) - xp = plane.c2p(x, 0) - origin = plane.c2p(0, 0) - - x_color = self.x_color - y_color = self.y_color - - x_line = Line(origin, xp, color=x_color) - y_line = Line(xp, point, color=y_color) - - dot = Dot(point) - - coord_label = self.get_coord_label(0, 0, x_color, y_color) - x_coord = coord_label.x_coord - y_coord = coord_label.y_coord - - coord_label.next_to(dot, UR, SMALL_BUFF) - - x_brace = Brace(x_coord, UP) - y_brace = Brace(y_coord, UP) - x_brace.add(x_brace.get_tex("x").set_color(x_color)) - y_brace.add(y_brace.get_tex("y").set_color(y_color)) - x_brace.add_updater(lambda m: m.next_to(x_coord, UP, SMALL_BUFF)) - y_brace.add_updater(lambda m: m.next_to(y_coord, UP, SMALL_BUFF)) - - self.add(plane) - self.add(dot, coord_label) - self.add(x_brace, y_brace) - - coord_label.add_updater( - lambda m: m.next_to(dot, UR, SMALL_BUFF) - ) - - self.play( - ShowCreation(x_line), - ChangeDecimalToValue(x_coord, x), - UpdateFromFunc( - dot, - lambda d: d.move_to(x_line.get_end()), - ), - run_time=2, - ) - self.play( - ShowCreation(y_line), - ChangeDecimalToValue(y_coord, y), - UpdateFromFunc( - dot, - lambda d: d.move_to(y_line.get_end()), - ), - run_time=2, - ) - self.wait() - - self.xy_coord_mobjects = VGroup( - x_line, y_line, coord_label, - x_brace, y_brace, - ) - self.plane = plane - self.dot = dot - - def transition_to_polar_grid(self): - self.polar_grid = self.get_polar_grid() - self.add(self.polar_grid, self.dot) - self.play( - FadeOut(self.xy_coord_mobjects), - FadeOut(self.plane), - ShowCreation(self.polar_grid, run_time=2), - ) - self.wait() - - def show_polar_coordinates(self): - dot = self.dot - plane = self.plane - origin = plane.c2p(0, 0) - - r_color = self.r_color - theta_color = self.theta_color - - r_line = Line(origin, dot.get_center()) - r_line.set_color(r_color) - r_value = r_line.get_length() - theta_value = r_line.get_angle() - - coord_label = self.get_coord_label(r_value, theta_value, r_color, theta_color) - r_coord = coord_label.x_coord - theta_coord = coord_label.y_coord - - coord_label.add_updater(lambda m: m.next_to(dot, UP, buff=SMALL_BUFF)) - r_coord.add_updater(lambda d: d.set_value( - get_norm(dot.get_center()) - )) - theta_coord.add_background_rectangle() - theta_coord.add_updater(lambda d: d.set_value( - (angle_of_vector(dot.get_center()) % TAU) - )) - coord_label[-1].add_updater( - lambda m: m.next_to(theta_coord, RIGHT, SMALL_BUFF) - ) - - non_coord_parts = VGroup(*[ - part - for part in coord_label - if part not in [r_coord, theta_coord] - ]) - - r_label = TexMobject("r") - r_label.set_color(r_color) - r_label.add_updater(lambda m: m.next_to(r_coord, UP)) - theta_label = TexMobject("\\theta") - theta_label.set_color(theta_color) - theta_label.add_updater(lambda m: m.next_to(theta_coord, UP)) - - r_coord_copy = r_coord.copy() - r_coord_copy.add_updater( - lambda m: m.next_to(r_line.get_center(), UL, buff=0) - ) - - degree_label = DecimalNumber(0, num_decimal_places=1, unit="^\\circ") - arc = Arc(radius=1, angle=theta_value) - arc.set_color(theta_color) - degree_label.set_color(theta_color) - - # Show r - self.play( - ShowCreation(r_line, run_time=2), - ChangeDecimalToValue(r_coord_copy, r_value, run_time=2), - VFadeIn(r_coord_copy, run_time=0.5), - ) - r_coord.set_value(r_value) - self.add(non_coord_parts, r_coord_copy) - self.play( - FadeIn(non_coord_parts), - ReplacementTransform(r_coord_copy, r_coord), - FadeInFromDown(r_label), - ) - self.wait() - - # Show theta - degree_label.next_to(arc.get_start(), UR, SMALL_BUFF) - line = r_line.copy() - line.rotate(-theta_value, about_point=ORIGIN) - line.set_color(theta_color) - self.play( - ShowCreation(arc), - Rotate(line, theta_value, about_point=ORIGIN), - VFadeInThenOut(line), - ChangeDecimalToValue(degree_label, theta_value / DEGREES), - ) - self.play( - degree_label.scale, 0.9, - degree_label.move_to, theta_coord, - FadeInFromDown(theta_label), - ) - self.wait() - - degree_cross = Cross(degree_label) - radians_word = TextMobject("in radians") - radians_word.scale(0.9) - radians_word.set_color(theta_color) - radians_word.add_background_rectangle() - radians_word.add_updater( - lambda m: m.next_to(theta_label, RIGHT, aligned_edge=DOWN) - ) - - self.play(ShowCreation(degree_cross)) - self.play( - FadeOutAndShift( - VGroup(degree_label, degree_cross), - DOWN - ), - FadeIn(theta_coord) - ) - self.play(FadeIn(radians_word)) - self.wait() - - # Move point around - r_line.add_updater( - lambda l: l.put_start_and_end_on(ORIGIN, dot.get_center()) - ) - theta_tracker = ValueTracker(0) - theta_tracker.add_updater( - lambda m: m.set_value(r_line.get_angle() % TAU) - ) - self.add(theta_tracker) - arc.add_updater( - lambda m: m.become( - self.get_arc(theta_tracker.get_value()) - ) - ) - - self.add(coord_label) - for angle in [PI - theta_value, PI - 0.001, -TAU + 0.002]: - self.play( - Rotate(dot, angle, about_point=ORIGIN), - run_time=3, - ) - self.wait() - self.play( - FadeOut(coord_label), - FadeOut(r_label), - FadeOut(theta_label), - FadeOut(radians_word), - FadeOut(r_line), - FadeOut(arc), - FadeOut(dot), - ) - - self.dot = dot - self.r_line = r_line - self.arc = arc - self.theta_tracker = theta_tracker - - def show_all_nn_tuples(self): - dot = self.dot - arc = self.arc - r_line = self.r_line - theta_tracker = self.theta_tracker - - primes = generate_prime_list(20) - non_primes = list(range(1, 20)) - for prime in primes: - non_primes.remove(prime) - - pp_points = VGroup(*map(self.get_nn_point, primes)) - pp_points[0][1].shift(0.3 * LEFT + SMALL_BUFF * UP) - np_points = VGroup(*map(self.get_nn_point, non_primes)) - pp_points.set_color(TEAL) - np_points.set_color(WHITE) - pp_points.set_stroke(BLACK, 4, background=True) - np_points.set_stroke(BLACK, 4, background=True) - - frame = self.camera_frame - self.play( - ApplyMethod(frame.scale, 2), - LaggedStartMap( - FadeInFromDown, pp_points - ), - run_time=2 - ) - self.wait() - self.play(LaggedStartMap(FadeIn, np_points)) - self.play(frame.scale, 0.5) - self.wait() - - # Talk about 1 - one = np_points[0] - dot.move_to(self.get_polar_point(1, 1)) - self.add(dot) - theta_tracker.clear_updaters() - theta_tracker.set_value(1) - # r_line = Line(ORIGIN, one.dot.get_center()) - # r_line.set_color(self.r_color) - # pre_arc = Line(RIGHT, UR, color=self.r_color) - # theta_tracker = ValueTracker(1) - # arc = always_redraw(lambda: self.get_arc(theta_tracker.get_value())) - - one_rect = SurroundingRectangle(one) - one_r_rect = SurroundingRectangle(one.label[1]) - one_theta_rect = SurroundingRectangle(one.label[3]) - one_theta_rect.set_color(self.theta_color) - - self.play(ShowCreation(one_rect)) - self.add(r_line, np_points, pp_points, one_rect) - self.play( - ReplacementTransform(one_rect, one_r_rect), - ShowCreation(r_line) - ) - self.wait() - # self.play(TransformFromCopy(r_line, pre_arc)) - # self.add(pre_arc, one) - self.play( - ReplacementTransform( - Line(*r_line.get_start_and_end()), arc - ), - ReplacementTransform(one_r_rect, one_theta_rect) - ) - self.add(arc, one, one_theta_rect) - self.play(FadeOut(one_theta_rect)) - self.wait() - - # Talk about 2, 3 then 4 - for n in [2, 3, 4]: - self.play( - Rotate(dot, 1, about_point=ORIGIN), - theta_tracker.set_value, n, - ) - self.wait() - self.play(dot.move_to, self.get_polar_point(n, n)) - self.wait() - - # Zoom out and show spiral - big_anim = Succession(*3 * [Animation(Mobject())], *it.chain(*[ - [ - AnimationGroup( - Rotate(dot, 1, about_point=ORIGIN), - ApplyMethod(theta_tracker.set_value, n), - ), - ApplyMethod(dot.move_to, self.get_polar_point(n, n)) - ] - for n in [5, 6, 7, 8, 9] - ])) - - spiral = ParametricFunction( - lambda t: self.get_polar_point(t, t), - t_min=0, - t_max=25, - stroke_width=1.5, - ) - - # self.add(spiral, pp_points, np_points) - - self.polar_grid.generate_target() - for mob in self.polar_grid: - if not isinstance(mob[0], Integer): - mob.set_stroke(width=1) - - self.play( - frame.scale, 3, - big_anim, - run_time=10, - ) - self.play( - # ApplyMethod( - # frame.scale, 1.5, - # run_time=2, - # rate_func=lambda t: smooth(t, 2) - # ), - ShowCreation( - spiral, - run_time=4, - ), - FadeOut(r_line), - FadeOut(arc), - FadeOut(dot), - # MoveToTarget(self.polar_grid) - ) - self.wait() - - # - def get_nn_point(self, n): - point = self.get_polar_point(n, n) - dot = Dot(point) - coord_label = self.get_coord_label( - n, n, - include_background_rectangle=False, - num_decimal_places=0 - ) - coord_label.next_to(dot, UR, buff=0) - result = VGroup(dot, coord_label) - result.dot = dot - result.label = coord_label - return result - - def get_polar_grid(self, radius=25): - plane = self.plane - axes = VGroup( - Line(radius * DOWN, radius * UP), - Line(radius * LEFT, radius * RIGHT), - ) - axes.set_stroke(width=2) - circles = VGroup(*[ - Circle(color=BLUE, stroke_width=1, radius=r) - for r in range(1, int(radius)) - ]) - rays = VGroup(*[ - Line( - ORIGIN, radius * RIGHT, - color=BLUE, - stroke_width=1, - ).rotate(angle, about_point=ORIGIN) - for angle in np.arange(0, TAU, TAU / 16) - ]) - labels = VGroup(*[ - Integer(n).scale(0.5).next_to( - plane.c2p(n, 0), DR, SMALL_BUFF - ) - for n in range(1, int(radius)) - ]) - - return VGroup( - circles, rays, labels, axes, - ) - - def get_coord_label(self, - x=0, - y=0, - x_color=WHITE, - y_color=WHITE, - include_background_rectangle=True, - **decimal_kwargs): - coords = VGroup() - for n in x, y: - if isinstance(n, numbers.Number): - coord = DecimalNumber(n, **decimal_kwargs) - elif isinstance(n, str): - coord = TexMobject(n) - else: - raise Exception("Invalid type") - coords.add(coord) - - x_coord, y_coord = coords - x_coord.set_color(x_color) - y_coord.set_color(y_color) - - coord_label = VGroup( - TexMobject("("), x_coord, - TexMobject(","), y_coord, - TexMobject(")") - ) - coord_label.arrange(RIGHT, buff=SMALL_BUFF) - coord_label[2].align_to(coord_label[0], DOWN) - - coord_label.x_coord = x_coord - coord_label.y_coord = y_coord - if include_background_rectangle: - coord_label.add_background_rectangle() - return coord_label - - def get_polar_point(self, r, theta): - plane = self.plane - return plane.c2p(r * np.cos(theta), r * np.sin(theta)) - - def get_arc(self, theta, r=1, color=None): - if color is None: - color = self.theta_color - return ParametricFunction( - lambda t: self.get_polar_point(1 + 0.025 * t, t), - t_min=0, - t_max=theta, - dt=0.25, - color=color, - stroke_width=3, - ) - # return Arc( - # angle=theta, - # radius=r, - # stroke_color=color, - # ) - - -class IntroducePolarPlot(RefresherOnPolarCoordinates): - def construct(self): - self.plane = NumberPlane() - grid = self.get_polar_grid() - title = TextMobject("Polar coordinates") - title.scale(3) - title.set_stroke(BLACK, 10, background=True) - title.to_edge(UP) - - self.add(grid, title) - self.play( - ShowCreation(grid, lag_ratio=0.1), - run_time=3, - ) - - -class ReplacePolarCoordinatesWithPrimes(RefresherOnPolarCoordinates): - def construct(self): - coords, p_coords = [ - self.get_coord_label( - *pair, - x_color=self.r_color, - y_color=self.theta_color, - ).scale(2) - for pair in [("r", "\\theta"), ("p", "p")] - ] - p_coords.x_coord.set_color(LIGHT_GREY) - p_coords.y_coord.set_color(LIGHT_GREY) - - some_prime = TextMobject("Some prime") - some_prime.scale(1.5) - some_prime.next_to(p_coords.get_left(), DOWN, buff=1.5) - arrows = VGroup(*[ - Arrow( - some_prime.get_top(), coord.get_bottom(), - stroke_width=5, - tip_length=0.4 - ) - for coord in [p_coords.x_coord, p_coords.y_coord] - ]) - - equals = TexMobject("=") - equals.next_to(p_coords, LEFT) - - self.add(coords) - self.wait() - self.play( - coords.next_to, equals, LEFT, - FadeIn(equals), - FadeIn(p_coords), - ) - self.play( - FadeInFromDown(some_prime), - ShowCreation(arrows), - ) - self.wait() - - -class IntroducePrimePatterns(SpiralScene): - CONFIG = { - "small_n_primes": 25000, - "big_n_primes": 1000000, - "axes_config": { - "x_min": -25, - "x_max": 25, - "y_min": -25, - "y_max": 25, - }, - "spiral_scale": 3e3, - "ray_scale": 1e5, - } - - def construct(self): - self.slowly_zoom_out() - self.show_clumps_of_four() - - def slowly_zoom_out(self): - zoom_time = 8 - - prime_spiral = self.get_prime_p_spiral(self.small_n_primes) - prime_spiral.set_stroke_width(25) - self.add(prime_spiral) - - self.set_scale(3, spiral=prime_spiral) - self.wait() - self.set_scale( - self.spiral_scale, - spiral=prime_spiral, - target_p_spiral_width=8, - run_time=zoom_time, - ) - self.wait() - - self.remove(prime_spiral) - prime_spiral = self.get_prime_p_spiral(self.big_n_primes) - prime_spiral.set_stroke_width(8) - self.set_scale( - self.ray_scale, - spiral=prime_spiral, - target_p_spiral_width=4, - run_time=zoom_time, - ) - self.wait() - - def show_clumps_of_four(self): - line_groups = VGroup() - for n in range(71): - group = VGroup() - for k in [-3, -1, 1, 3]: - r = ((10 * n + k) * INV_113_MOD_710) % 710 - group.add(self.get_arithmetic_sequence_line( - 710, r, self.big_n_primes - )) - line_groups.add(group) - - line_groups.set_stroke(YELLOW, 2, opacity=0.5) - - self.play(ShowCreation(line_groups[0])) - for g1, g2 in zip(line_groups, line_groups[1:5]): - self.play( - FadeOut(g1), - ShowCreation(g2) - ) - - self.play( - FadeOut(line_groups[4]), - LaggedStartMap( - VFadeInThenOut, - line_groups[4:], - lag_ratio=0.5, - run_time=5, - ) - ) - self.wait() - - def get_arithmetic_sequence_line(self, N, r, max_val, skip_factor=5): - line = VMobject() - line.set_points_smoothly([ - self.get_polar_point(x, x) - for x in range(r, max_val, skip_factor * N) - ]) - return line - - -class AskWhat(TeacherStudentsScene): - def construct(self): - screen = self.screen - self.student_says( - "I'm sorry,\\\\what?!?", - target_mode="angry", - look_at_arg=screen, - student_index=2, - added_anims=[ - self.teacher.change, "happy", screen, - self.students[0].change, "confused", screen, - self.students[1].change, "confused", screen, - ] - ) - self.wait(3) - - -class CountSpirals(IntroducePrimePatterns): - CONFIG = { - "count_sound": "pen_click.wav", - } - - def construct(self): - prime_spiral = self.get_prime_p_spiral(self.small_n_primes) - - self.add(prime_spiral) - self.set_scale( - self.spiral_scale, - spiral=prime_spiral, - run_time=0, - ) - - spiral_lines = self.get_all_primative_arithmetic_lines( - 44, self.small_n_primes, INV_7_MOD_44, - ) - spiral_lines.set_stroke(YELLOW, 2, opacity=0.5) - - counts = VGroup() - for n, spiral in zip(it.count(1), spiral_lines): - count = Integer(n) - count.move_to(spiral.point_from_proportion(0.25)) - counts.add(count) - - run_time = 3 - self.play( - ShowIncreasingSubsets(spiral_lines), - ShowSubmobjectsOneByOne(counts), - run_time=run_time, - rate_func=linear, - ) - self.add_count_clicks(len(spiral_lines), run_time) - self.play( - counts[-1].scale, 3, - counts[-1].set_stroke, BLACK, 5, {"background": True}, - ) - self.wait() - - def get_all_primative_arithmetic_lines(self, N, max_val, mult_factor): - lines = VGroup() - for r in range(1, N): - if get_gcd(N, r) == 1: - lines.add( - self.get_arithmetic_sequence_line(N, (mult_factor * r) % N, max_val) - ) - return lines - - def add_count_clicks(self, N, time, rate_func=linear): - alphas = np.arange(0, 1, 1 / N) - if rate_func is linear: - delays = time * alphas - else: - delays = time * np.array([ - binary_search(rate_func, alpha, 0, 1) - for alpha in alphas - ]) - - for delay in delays: - self.add_sound( - self.count_sound, - time_offset=-delay, - gain=-15, - ) - - -class CountRays(CountSpirals): - def construct(self): - prime_spiral = self.get_prime_p_spiral(self.big_n_primes) - - self.add(prime_spiral) - self.set_scale( - self.ray_scale, - spiral=prime_spiral, - run_time=0, - ) - - spiral_lines = self.get_all_primative_arithmetic_lines( - 710, self.big_n_primes, INV_113_MOD_710, - ) - spiral_lines.set_stroke(YELLOW, 2, opacity=0.5) - - counts = VGroup() - for n, spiral in zip(it.count(1), spiral_lines): - count = Integer(n) - count.move_to(spiral.point_from_proportion(0.25)) - counts.add(count) - - run_time = 6 - self.play( - ShowIncreasingSubsets(spiral_lines), - ShowSubmobjectsOneByOne(counts), - run_time=run_time, - rate_func=smooth, - ) - self.add_count_clicks(len(spiral_lines), run_time, rate_func=smooth) - self.play( - counts[-1].scale, 3, - counts[-1].set_stroke, BLACK, 5, {"background": True}, - ) - self.wait() - self.play(FadeOut(spiral_lines)) - self.wait() - - -class AskAboutRelationToPrimes(TeacherStudentsScene): - def construct(self): - numbers = TextMobject("20, 280") - arrow = Arrow(LEFT, RIGHT) - primes = TextMobject("2, 3, 5, 7, 11, \\dots") - q_marks = TextMobject("???") - q_marks.set_color(YELLOW) - - group = VGroup(primes, arrow, numbers) - group.arrange(RIGHT) - q_marks.next_to(arrow, UP) - group.add(q_marks) - group.scale(1.5) - group.next_to(self.pi_creatures, UP, LARGE_BUFF) - - self.play( - self.get_student_changes( - *3 * ["maybe"], - look_at_arg=numbers, - ), - self.teacher.change, "maybe", numbers, - ShowCreation(arrow), - FadeInFrom(numbers, RIGHT) - ) - self.play( - FadeInFrom(primes, LEFT), - ) - self.play( - LaggedStartMap(FadeInFromDown, q_marks[0]), - Blink(self.teacher) - ) - self.wait(3) - - -class ZoomOutOnPrimesWithNumbers(IntroducePrimePatterns): - CONFIG = { - "n_labeled_primes": 1000, - "big_n_primes": int(5e6), - "thicknesses": [8, 3, 2], - "thicker_target": False, - } - - def construct(self): - zoom_time = 20 - - prime_spiral = self.get_prime_p_spiral(self.big_n_primes) - prime_spiral.set_stroke_width(25) - - prime_labels = self.get_prime_labels(self.n_labeled_primes) - - self.add(prime_spiral) - self.add(prime_labels) - - scales = [self.spiral_scale, self.ray_scale, 5e5] - thicknesses = self.thicknesses - - for scale, tp in zip(scales, thicknesses): - kwargs = { - "spiral": prime_spiral, - "to_shrink": prime_labels, - "run_time": zoom_time, - "target_p_spiral_width": tp, - } - if self.thicker_target: - kwargs["target_p_spiral_width"] += 1 - self.set_scale(scale, **kwargs) - prime_spiral.set_stroke_width(tp) - self.wait() - self.remove(prime_labels) - - -class ThickZoomOutOnPrimesWithNumbers(ZoomOutOnPrimesWithNumbers): - CONFIG = { - # The only purpose of this scene is for overlay - # with the last one to smooth things out. - "thicker_target": True, - } - - -class HighlightGapsInSpirals(IntroducePrimePatterns): - def construct(self): - self.setup_spiral() - - max_n_tracker = ValueTracker(0) - get_max_n = max_n_tracker.get_value - gaps = always_redraw(lambda: VGroup(*[ - self.get_highlighted_gap(n - 1, n + 1, get_max_n()) - for n in [11, 33] - ])) - - self.add(gaps) - self.play(max_n_tracker.set_value, 25000, run_time=5) - gaps.clear_updaters() - self.play(FadeOut(gaps)) - - def setup_spiral(self): - p_spiral = self.get_p_spiral(read_in_primes(self.small_n_primes)) - self.add(p_spiral) - self.set_scale( - scale=self.spiral_scale, - spiral=p_spiral, - target_p_spiral_width=8, - run_time=0, - ) - - def get_highlighted_gap(self, n1, n2, max_n): - l1, l2 = [ - [ - self.get_polar_point(k, k) - for k in range(INV_7_MOD_44 * n, int(max_n), 5 * 44) - ] - for n in (n1, n2) - ] - - if len(l1) == 0 or len(l2) == 0: - return VectorizedPoint() - - result = VMobject() - result.set_points_as_corners( - [*l1, *reversed(l2)] - ) - result.make_smooth() - - result.set_stroke(GREY, width=0) - result.set_fill(DARK_GREY, 1) - - return result - - -class QuestionIsMisleading(TeacherStudentsScene): - def construct(self): - self.student_says( - "Whoa, is this some\\\\divine hidden structure\\\\in the primes?", - target_mode="surprised", - student_index=0, - added_anims=[ - self.students[1].change, "pondering", - self.students[2].change, "pondering", - ] - ) - self.wait(2) - - self.students[0].bubble = None - self.teacher_says( - "Er...not exactly", - bubble_kwargs={"width": 3, "height": 2}, - target_mode="guilty" - ) - self.wait(3) - - -class JustPrimesLabel(Scene): - def construct(self): - text = TextMobject("Just the primes") - text.scale(2) - text.to_corner(UL) - self.play(Write(text)) - self.wait(3) - self.play(FadeOutAndShift(text, DOWN)) - - -class DirichletComingUp(Scene): - def construct(self): - image = ImageMobject("Dirichlet") - image.set_height(3) - words = TextMobject( - "Coming up: \\\\", "Dirichlet's theorem", - alignment="", - ) - words.set_color_by_tex("Dirichlet's", YELLOW) - words.scale(1.5) - words.next_to(image, RIGHT) - words.set_stroke(BLACK, 8, background=True) - Group(words, image).center() - - self.play( - FadeInFrom(image, RIGHT), - FadeInFrom(words, LEFT), - ) - self.wait() - - -class ImagineYouFoundIt(TeacherStudentsScene): - def construct(self): - you = self.students[1] - others = VGroup( - self.students[0], - self.students[2], - self.teacher, - ) - bubble = you.get_bubble(direction=LEFT) - bubble[-1].set_fill(GREEN_SCREEN, 1) - - you_label = TextMobject("You") - arrow = Vector(DOWN) - arrow.next_to(you, UP) - you_label.next_to(arrow, UP) - - self.play( - you.change, "hesitant", you_label, - FadeInFromDown(you_label), - GrowArrow(arrow), - others.set_opacity, 0.25, - ) - self.play(Blink(you)) - self.play( - FadeIn(bubble), - FadeOut(you_label), - FadeOut(arrow), - you.change, "pondering", - ) - self.play(you.look_at, bubble.get_corner(UR)) - self.play(Blink(you)) - self.wait() - self.play(you.change, "hooray") - self.play(Blink(you)) - self.wait() - self.play(you.change, "sassy", bubble.get_top()) - self.wait(6) - - -class ShowSpiralsForWholeNumbers(CountSpirals): - CONFIG = { - "max_prime": 10000, - "scale_44": 1e3, - "scale_6": 10, - "n_labels": 100, - "axes_config": { - "x_min": -50, - "x_max": 50, - "y_min": -50, - "y_max": 50, - }, - } - - def construct(self): - self.zoom_out_with_whole_numbers() - self.count_44_spirals() - self.zoom_back_in_to_6() - - def zoom_out_with_whole_numbers(self): - wholes = self.get_p_spiral(range(self.max_prime)) - primes = self.get_prime_p_spiral(self.max_prime) - - wholes.set_color(YELLOW) - wholes.set_stroke_width(20) - primes.set_stroke_width(20) - spiral = PGroup(wholes, primes) - - labels = self.get_labels(range(1, self.n_labels)) - - self.add(spiral, labels) - self.set_scale( - self.scale_44, - spiral=spiral, - to_shrink=labels, - target_p_spiral_width=6, - run_time=10, - ) - self.wait(2) - - self.spiral = spiral - self.labels = labels - - def count_44_spirals(self): - curr_spiral = self.spiral - - new_spirals = PGroup(*[ - self.get_p_spiral(range( - (INV_7_MOD_44 * k) % 44, self.max_prime, 44 - )) - for k in range(44) - ]) - new_spirals.set_color(YELLOW) - - counts = VGroup() - for n, spiral in zip(it.count(1), new_spirals): - count = Integer(n) - count.scale(2) - count.move_to(spiral.points[50]) - counts.add(count) - - self.remove(curr_spiral) - run_time = 3 - self.play( - ShowIncreasingSubsets(new_spirals), - ShowSubmobjectsOneByOne(counts), - run_time=run_time, - rate_func=linear, - ) - self.add_count_clicks(44, run_time) - self.play( - counts[-1].scale, 2, {"about_edge": DL}, - counts[-1].set_stroke, BLACK, 5, {"background": True}, - ) - self.wait() - self.play( - FadeOut(counts[-1]), - FadeOut(new_spirals), - FadeIn(curr_spiral), - ) - - def zoom_back_in_to_6(self): - spiral = self.spiral - - self.rescale_labels(self.labels) - self.set_scale( - self.scale_6, - spiral=spiral, - to_shrink=self.labels, - target_p_spiral_width=15, - run_time=6, - ) - self.wait() - - def rescale_labels(self, labels): - for i, label in zip(it.count(1), labels): - height = label.get_height() - label.set_height( - 3 * height / (i**0.25), - about_point=label.get_bottom() + 0.5 * label.get_height() * DOWN, - ) - - -class PrimeSpiralsAtScale1000(SpiralScene): - def construct(self): - spiral = self.get_prime_p_spiral(10000) - self.add(spiral) - self.set_scale( - scale=1000, - spiral=spiral, - target_p_spiral_width=15, - run_time=0, - ) - - -class SeparateIntoTwoQuestions(Scene): - def construct(self): - top_q = TextMobject("Why do", " primes", " cause", " spirals", "?") - top_q.scale(2) - top_q.to_edge(UP) - - q1 = TextMobject("Where do the\\\\", "spirals", " come from?") - q2 = TextMobject("What happens when\\\\", "filtering to", " primes", "?") - for q in q1, q2: - q.scale(1.3) - q.next_to(top_q, DOWN, LARGE_BUFF) - q1.to_edge(LEFT) - q1.set_color(YELLOW) - q2.to_edge(RIGHT) - q2.set_color(TEAL) - - v_line = DashedLine( - top_q.get_bottom() + MED_SMALL_BUFF * DOWN, - FRAME_HEIGHT * DOWN / 2, - ) - - self.add(top_q) - self.wait() - - for q, text in [(q1, "spirals"), (q2, "primes")]: - self.play( - top_q.get_part_by_tex(text).set_color, q.get_color(), - TransformFromCopy( - top_q.get_part_by_tex(text), - q.get_part_by_tex(text), - ), - LaggedStartMap( - FadeIn, - filter( - lambda m: m is not q.get_part_by_tex(text), - q, - ) - ), - ) - self.wait() - self.play(ShowCreation(v_line)) - self.wait() - - -class TopQuestionCross(Scene): - def construct(self): - top_q = TextMobject("Why do", " primes", " cause", " spirals", "?") - top_q.scale(2) - top_q.to_edge(UP) - cross = Cross(top_q) - - self.play(ShowCreation(cross)) - self.wait() - - -class ExplainSixSpirals(ShowSpiralsForWholeNumbers): - CONFIG = { - "max_N": 150, - } - - def construct(self): - self.add_spirals_and_labels() - self.comment_on_arms() - self.talk_though_multiples_of_six() - self.limit_to_primes() - - def add_spirals_and_labels(self): - max_N = self.max_N - - spiral = self.get_v_spiral(range(max_N)) - primes = generate_prime_list(max_N) - spiral.set_color(YELLOW) - for n, box in enumerate(spiral): - if n in primes: - box.set_color(TEAL) - - labels = self.get_labels(range(max_N)) - - self.add(spiral, labels) - self.set_scale( - spiral=spiral, - scale=self.scale_6, - to_shrink=labels, - min_box_width=0.08, - run_time=0, - ) - self.rescale_labels(labels) - - self.spiral = spiral - self.labels = labels - - def comment_on_arms(self): - labels = self.labels - spiral = self.spiral - - label_groups = VGroup(*[labels[k::6] for k in range(6)]) - spiral_groups = VGroup(*[spiral[k::6] for k in range(6)]) - six_groups = VGroup(*[ - VGroup(sg, lg) - for sg, lg in zip(spiral_groups, label_groups) - ]) - rect_groups = VGroup(*[ - VGroup(*[ - SurroundingRectangle(label, stroke_width=2, buff=0.05) - for label in group - ]) - for group in label_groups - ]) - - formula = VGroup( - *TexMobject("6k", "+"), - Integer(1) - ) - formula.arrange(RIGHT, buff=SMALL_BUFF) - formula.scale(2) - formula.set_color(YELLOW) - formula.to_corner(UL) - formula_rect = SurroundingRectangle(formula, buff=MED_LARGE_BUFF - SMALL_BUFF) - formula_rect.set_fill(DARK_GREY, opacity=1) - formula_rect.set_stroke(WHITE, 1) - - # 6k - self.add(six_groups, formula_rect) - self.play( - LaggedStartMap(ShowCreation, rect_groups[0]), - FadeInFromDown(formula_rect), - FadeInFromDown(formula[0]), - *[ - ApplyMethod(group.set_opacity, 0.25) - for group in six_groups[1:] - ], - run_time=2 - ) - self.play( - LaggedStartMap( - FadeOut, rect_groups[0], - run_time=1, - ), - ) - self.wait() - - # 6k + 1 - self.play( - six_groups[0].set_opacity, 0.25, - six_groups[1].set_opacity, 1, - FadeIn(formula[1:]), - ) - self.wait(2) - - # 6k + m - for m in [2, 3, 4, 5]: - self.play( - six_groups[m - 1].set_opacity, 0.25, - six_groups[m].set_opacity, 1, - ChangeDecimalToValue(formula[2], m), - ) - self.wait() - self.play( - six_groups[5].set_opacity, 0.25, - six_groups[0].set_opacity, 1, - formula[1:].set_opacity, 0, - ) - self.wait() - - self.six_groups = six_groups - self.formula = VGroup(formula_rect, *formula) - - def talk_though_multiples_of_six(self): - spiral = self.spiral - labels = self.labels - formula = self.formula - - # Zoom in - self.add(spiral, labels, formula) - self.set_scale( - 4.5, - spiral=spiral, - to_shrink=labels, - run_time=2, - ) - self.wait() - - boxes = VGroup(*[ - VGroup(b.copy(), l.copy()) - for b, l in zip(spiral, labels) - ]) - boxes.set_opacity(1) - - lines = VGroup(*[ - Line(ORIGIN, box[0].get_center()) - for box in boxes - ]) - lines.set_stroke(LIGHT_GREY, width=2) - - arcs = self.get_arcs(range(31)) - - trash = VGroup() - - def show_steps(start, stop, added_anims=None, run_time=2): - if added_anims is None: - added_anims = [] - self.play( - *[ - ShowSubmobjectsOneByOne(group[start:stop + 1]) - for group in [arcs, boxes, lines] - ], - *added_anims, - rate_func=linear, - run_time=run_time, - ) - self.add_count_clicks(N=6, time=run_time) - trash.add(VGroup(arcs[stop], boxes[stop], lines[stop])) - - # Writing next to the 6 - six = boxes[6][1] - rhs = TexMobject( - "\\text{radians}", - "\\approx", - "2\\pi", - "\\text{ radians}" - ) - rhs.next_to(six, RIGHT, 2 * SMALL_BUFF, aligned_edge=DOWN) - rhs.add_background_rectangle() - tau_value = TexMobject("{:.8}\\dots".format(TAU)) - tau_value.next_to(rhs[3], UP, aligned_edge=LEFT) - - # Animations - show_steps(0, 6, run_time=3) - self.wait() - self.play(FadeIn(rhs)) - self.wait() - self.play(FadeInFromDown(tau_value)) - self.wait(2) - - show_steps(6, 12) - self.wait() - - show_steps(12, 18) - self.wait() - - # Zoom out - frame = self.camera_frame - frame.add(formula) - show_steps(18, 24, added_anims=[frame.scale, 2.5]) - self.wait() - show_steps(24, 30) - self.wait(2) - - self.play( - FadeOut(trash), - FadeOut(rhs), - FadeOut(tau_value), - spiral.set_opacity, 1, - labels.set_opacity, 1, - formula[1].set_opacity, 0, - ) - - def limit_to_primes(self): - formula = self.formula - formula_rect, six_k, plus, m_sym = formula - spiral = self.spiral - labels = self.labels - six_groups = self.six_groups - frame = self.camera_frame - - boxes = VGroup(*[ - VGroup(b, l) - for b, l in zip(spiral, labels) - ]) - prime_numbers = read_in_primes(self.max_N) - primes = VGroup() - non_primes = VGroup() - for n, box in enumerate(boxes): - if n in prime_numbers: - primes.add(box) - else: - non_primes.add(box) - - prime_label = TextMobject("Primes") - prime_label.set_color(TEAL) - prime_label.match_width(VGroup(six_k, m_sym)) - prime_label.move_to(six_k, LEFT) - - # Show just primes - self.add(primes, non_primes, formula) - self.play( - FadeIn(prime_label), - non_primes.set_opacity, 0.25, - ) - frame.add(prime_label) - self.play( - frame.scale, 1.5, - run_time=2, - ) - self.wait(2) - - cross_groups = VGroup() - for group in six_groups: - group.save_state() - boxes, labels = group - cross_group = VGroup() - for label in labels: - cross_group.add(Cross(label)) - cross_groups.add(cross_group) - cross_groups.set_stroke(width=3) - cross_groups[2].remove(cross_groups[2][0]) - cross_groups[3].remove(cross_groups[3][0]) - - # Show multiples of 6 - for r in [0, 2, 4, 3]: - arm = six_groups[r] - crosses = cross_groups[r] - self.add(arm, frame) - - anims = [arm.set_opacity, 1] - if r == 0: - anims += [ - prime_label.set_opacity, 0, - six_k.set_opacity, 1 - ] - elif r == 2: - m_sym.set_value(2) - anims += [ - plus.set_opacity, 1, - m_sym.set_opacity, 1, - ] - else: - anims.append(ChangeDecimalToValue(m_sym, r)) - self.play(*anims) - self.add(*crosses, frame) - self.play( - LaggedStartMap(ShowCreation, crosses), - ) - self.wait() - - # Fade forbidden groups - to_fade = VGroup(*[ - VGroup(six_groups[r], cross_groups[r]) - for r in (0, 2, 3, 4) - ]) - self.add(to_fade, frame) - self.play( - to_fade.set_opacity, 0.25, - VGroup(six_k, plus, m_sym).set_opacity, 0, - prime_label.set_opacity, 1, - ) - self.wait() - - # - def arc_func(self, t): - r = 0.25 + 0.02 * t - return r * np.array([np.cos(t), np.sin(t), 0]) - - def get_arc(self, n): - if n == 0: - return VectorizedPoint() - return ParametricFunction( - self.arc_func, - t_min=0, - t_max=n, - step_size=0.1, - stroke_width=2, - stroke_color=PINK, - ) - - def get_arcs(self, sequence): - return VGroup(*map(self.get_arc, sequence)) - - -class IntroduceResidueClassTerminology(Scene): - def construct(self): - self.add_title() - self.add_sequences() - self.add_terms() - self.highlight_example() - self.simple_english() - - def add_title(self): - title = TextMobject("Overly-fancy ", "terminology") - title.scale(1.5) - title.to_edge(UP, buff=MED_SMALL_BUFF) - underline = Line().match_width(title) - underline.next_to(title, DOWN, SMALL_BUFF) - - pre_title = TextMobject("Terminology") - pre_title.replace(title, dim_to_match=1) - - self.play(FadeInFromDown(pre_title)) - self.wait() - title[0].set_color(BLUE) - underline.set_color(BLUE) - self.play( - ReplacementTransform(pre_title[0], title[1]), - FadeInFrom(title[0], RIGHT), - GrowFromCenter(underline) - ) - self.play( - title[0].set_color, WHITE, - underline.set_color, WHITE, - ) - self.wait() - - title.add(underline) - self.add(title) - - self.title = title - self.underline = underline - - def add_sequences(self): - sequences = VGroup() - n_terms = 7 - - for r in range(6): - sequence = VGroup(*[ - Integer(6 * k + r) - for k in range(n_terms) - ]) - sequence.arrange(RIGHT, buff=0.4) - sequences.add(sequence) - - sequences.arrange(DOWN, buff=0.7, aligned_edge=LEFT) - for sequence in sequences: - for s1, s2 in zip(sequence[:n_terms], sequences[-1]): - s1.align_to(s2, RIGHT) - commas = VGroup() - for num in sequence[:-1]: - comma = TextMobject(",") - comma.next_to(num.get_corner(DR), RIGHT, SMALL_BUFF) - commas.add(comma) - dots = TexMobject("\\dots") - dots.next_to(sequence.get_corner(DR), RIGHT, SMALL_BUFF) - sequence.numbers = VGroup(*sequence) - sequence.commas = commas - sequence.dots = dots - - sequence.add(*commas) - sequence.add(dots) - sequence.sort(lambda p: p[0]) - - labels = VGroup(*[ - TexMobject("6k + {}:".format(r)) - for r in range(6) - ]) - labels.set_color(YELLOW) - for label, sequence in zip(labels, sequences): - label.next_to(sequence, LEFT, MED_LARGE_BUFF) - - group = VGroup(sequences, labels) - group.to_edge(LEFT).to_edge(DOWN, buff=MED_LARGE_BUFF) - - self.add(labels) - self.play(LaggedStart(*[ - LaggedStartMap( - FadeInFrom, sequence, - lambda m: (m, LEFT), - ) - for sequence in sequences - ], lag_ratio=0.3)) - self.wait() - - self.sequences = sequences - self.sequence_labels = labels - - def add_terms(self): - sequences = self.sequences - - terms = TextMobject( - "``", "Residue\\\\", - "classes\\\\", - "mod ", "6''" - ) - terms.scale(1.5) - terms.set_color(YELLOW) - terms.to_edge(RIGHT) - - res_brace = Brace(terms.get_part_by_tex("Residue"), UP) - remainder = TextMobject("Remainder") - remainder.next_to(res_brace, UP, SMALL_BUFF) - - mod_brace = Brace(terms.get_part_by_tex("mod"), DOWN) - mod_def = TextMobject( - "``where the thing\\\\you divide by is''" - ) - mod_def.next_to(mod_brace, DOWN, SMALL_BUFF) - - arrows = VGroup(*[ - Arrow(terms.get_left(), sequence.get_right()) - for sequence in sequences - ]) - arrows.set_color(YELLOW) - - self.play( - FadeIn(terms), - LaggedStartMap(ShowCreation, arrows), - ) - self.wait() - self.play( - GrowFromCenter(res_brace), - FadeInFromDown(remainder), - ) - self.wait() - self.play(GrowFromCenter(mod_brace)) - self.play(Write(mod_def)) - self.wait() - - self.terminology = VGroup( - terms, - res_brace, remainder, - mod_brace, mod_def, - arrows - ) - - def highlight_example(self): - sequences = self.sequences - labels = self.sequence_labels - - r = 2 - k = 3 - sequence = sequences[r] - label = labels[r] - r_tex = label[0][3] - n_rects = VGroup(*[ - SurroundingRectangle(num) - for num in sequence.numbers - ]) - r_rect = SurroundingRectangle(r_tex) - n_rects.set_color(RED) - r_rect.set_color(RED) - - n_rect = n_rects.submobjects.pop(k) - - self.play(ShowCreation(n_rect)) - self.wait() - self.play( - TransformFromCopy(n_rect, r_rect, path_arc=30 * DEGREES) - ) - self.wait() - self.play(ShowCreation(n_rects)) - self.wait() - self.play( - ShowCreationThenFadeOut( - self.underline.copy().set_color(PINK), - ) - ) - - def simple_english(self): - terminology = self.terminology - sequences = self.sequences - - randy = Randolph() - randy.set_height(2) - randy.flip() - randy.to_corner(DR) - - new_phrase = TextMobject("Everything 2 above\\\\a multiple of 6") - new_phrase.scale(1.2) - new_phrase.set_color(RED_B) - new_phrase.next_to(sequences[2]) - - self.play( - FadeOut(terminology), - FadeIn(new_phrase), - VFadeIn(randy), - randy.change, "sassy" - ) - self.wait() - self.play(randy.change, "angry") - for x in range(2): - self.play(Blink(randy)) - self.wait() - self.play( - FadeOut(new_phrase), - FadeIn(terminology), - FadeOutAndShift(randy, DOWN) - ) - self.wait() - self.wait(6) - - -class SimpleLongDivision(MovingCameraScene): - CONFIG = { - "camera_config": { - "background_color": DARKER_GREY - } - } - - def construct(self): - divisor = Integer(6) - num = Integer(20) - quotient = Integer(num.get_value() // divisor.get_value()) - to_subtract = Integer(-1 * quotient.get_value() * divisor.get_value()) - remainder = Integer(num.get_value() + to_subtract.get_value()) - - div_sym = VMobject() - div_sym.set_points_as_corners([0.2 * UP, UP, UP + 3 * RIGHT]) - - divisor.next_to(div_sym, LEFT, MED_SMALL_BUFF) - num.next_to(divisor, RIGHT, MED_LARGE_BUFF) - to_subtract.next_to(num, DOWN, buff=MED_LARGE_BUFF, aligned_edge=RIGHT) - h_line = Line(LEFT, RIGHT) - h_line.next_to(to_subtract, DOWN, buff=MED_SMALL_BUFF) - remainder.next_to(to_subtract, DOWN, buff=MED_LARGE_BUFF, aligned_edge=RIGHT) - quotient.next_to(num, UP, buff=MED_LARGE_BUFF, aligned_edge=RIGHT) - - remainder_rect = SurroundingRectangle(remainder) - remainder_rect.set_color(RED) - - frame = self.camera_frame - frame.scale(0.7, about_point=ORIGIN) - - divisor.set_color(YELLOW) - num.set_color(RED) - - self.add(divisor) - self.add(div_sym) - self.add(num) - self.play(FadeInFromDown(quotient)) - self.play( - TransformFromCopy(divisor, to_subtract.copy()), - TransformFromCopy(quotient, to_subtract), - ) - self.play(ShowCreation(h_line)) - self.play(Write(remainder)) - self.play(ShowCreation(remainder_rect)) - self.wait() - self.play(FadeOut(remainder_rect)) - - -class ZoomOutWords(Scene): - def construct(self): - words = TextMobject("Zoom out!") - words.scale(3) - self.play(FadeInFromLarge(words)) - self.wait() - - -class Explain44Spirals(ExplainSixSpirals): - CONFIG = { - "max_N": 3000, - "initial_scale": 10, - "zoom_factor_1": 7, - "zoom_factor_2": 3, - "n_labels": 80, - } - - def construct(self): - self.add_spirals_and_labels() - self.show_44_steps() - self.show_top_right_arithmetic() - self.show_pi_approx_arithmetic() - self.count_by_44() - - def add_spirals_and_labels(self): - max_N = self.max_N - - wholes = self.get_p_spiral(range(max_N)) - primes = self.get_prime_p_spiral(max_N) - wholes.set_color(YELLOW) - spiral = PGroup(wholes, primes) - - labels = self.get_labels(range(self.n_labels)) - - self.add(spiral, labels) - self.set_scale( - spiral=spiral, - scale=self.initial_scale, - to_shrink=labels, - target_p_spiral_width=10, - run_time=0, - ) - self.rescale_labels(labels) - - self.spiral = spiral - self.labels = labels - - def show_44_steps(self): - labels = self.labels - - ns = range(45) - points = [self.get_polar_point(n, n) for n in ns] - lines = VGroup(*[ - Line(ORIGIN, point) - for point in points - ]) - lines.set_stroke(WHITE, 2) - arcs = self.get_arcs(ns) - - opaque_labels = labels.copy() - labels.set_opacity(0.25) - - trash = VGroup() - - def show_steps(start, stop, added_anims=None, run_time=2): - if added_anims is None: - added_anims = [] - - def rate_func(t): - return smooth(t, 2) - - self.play( - *[ - ShowSubmobjectsOneByOne(group[start:stop + 1]) - for group in [arcs, opaque_labels, lines] - ], - *added_anims, - rate_func=rate_func, - run_time=run_time, - ) - self.add_count_clicks( - N=(stop - start), time=run_time, - rate_func=rate_func - ) - trash.add(arcs[stop], opaque_labels[stop], lines[stop]) - - show_steps(0, 6) - self.wait() - show_steps(6, 44, added_anims=[FadeOut(trash)], run_time=4) - self.wait() - - self.spiral_group = trash[-3:] - - def show_top_right_arithmetic(self): - labels = self.labels - ff = labels[44].copy() - ff.generate_target() - - radians = TextMobject("radians") - ff.target.scale(1.5) - ff.target.set_opacity(1) - - unit_conversion = TexMobject( - "/\\,", "\\left(", "2\\pi", - "{\\text{radians}", "\\over", "\\text{rotations}}", - "\\right)" - ) - unit_conversion[1:].scale(0.7, about_edge=LEFT) - - top_line = VGroup(ff.target, radians, unit_conversion) - top_line.arrange(RIGHT) - ff.target.align_to(radians, DOWN) - top_line.to_corner(UR, buff=0.4) - - next_line = TexMobject( - "=", "44", "/", "2\\pi", - "\\text{ rotations}" - ) - next_line.next_to(top_line, DOWN, MED_LARGE_BUFF, aligned_edge=LEFT) - - brace = Brace(next_line[1:4], DOWN, buff=SMALL_BUFF) - value = DecimalNumber(44 / TAU, num_decimal_places=8, show_ellipsis=True) - value.next_to(brace, DOWN) - - rect = SurroundingRectangle(VGroup(top_line, value), buff=MED_SMALL_BUFF) - rect.set_stroke(WHITE, 2) - rect.set_fill(DARKER_GREY, 0.9) - - self.play(MoveToTarget(ff)) - top_line.add(ff) - self.play(FadeInFrom(radians, LEFT)) - self.wait() - self.add(rect, top_line, unit_conversion) - self.play( - FadeIn(rect), - FadeIn(unit_conversion), - ) - self.wait() - self.play( - TransformFromCopy(ff, next_line.get_part_by_tex("44")), - FadeIn(next_line.get_part_by_tex("=")), - TransformFromCopy( - unit_conversion.get_part_by_tex("/"), - next_line.get_part_by_tex("/"), - ), - TransformFromCopy( - unit_conversion.get_part_by_tex("rotations"), - next_line.get_part_by_tex("rotations"), - ), - TransformFromCopy( - unit_conversion.get_part_by_tex("2\\pi"), - next_line.get_part_by_tex("2\\pi"), - ), - ) - self.wait() - self.play( - GrowFromCenter(brace), - Write(value), - ) - self.wait() - - self.right_arithmetic = VGroup( - rect, top_line, next_line, - brace, value - ) - - def show_pi_approx_arithmetic(self): - ra = self.right_arithmetic - ra_rect, ra_l1, ra_l2, ra_brace, ra_value = ra - - lines = VGroup( - TexMobject("{44", "\\over", "2\\pi}", "\\approx", "7"), - TexMobject("\\Leftrightarrow"), - TexMobject("{44", "\\over", "7}", "\\approx", "2\\pi"), - TexMobject("{22", "\\over", "7}", "\\approx", "\\pi"), - ) - lines.arrange(RIGHT, buff=MED_SMALL_BUFF) - lines.to_corner(UL) - lines[3].move_to(lines[2], LEFT) - - rect = SurroundingRectangle(lines, buff=MED_LARGE_BUFF) - rect.match_style(ra_rect) - - self.play( - FadeIn(rect), - LaggedStart( - TransformFromCopy(ra_l2[1:4], lines[0][:3]), - FadeIn(lines[0].get_part_by_tex("approx")), - TransformFromCopy(ra_value[0], lines[0].get_part_by_tex("7")), - run_time=2, - ) - ) - self.wait() - l0_copy = lines[0].copy() - self.play( - l0_copy.move_to, lines[2], - Write(lines[1]), - ) - self.play( - LaggedStart(*[ - ReplacementTransform( - l0_copy.get_part_by_tex(tex), - lines[2].get_part_by_tex(tex), - path_arc=60 * DEGREES, - ) - for tex in ["44", "\\over", "7", "approx", "2\\pi"] - ], lag_ratio=0.1, run_time=2), - ) - self.wait() - self.play(Transform(lines[2], lines[3])) - self.wait() - - left_arithmetic = VGroup(rect, lines[:3]) - self.play( - LaggedStart( - FadeOut(self.spiral_group[0]), - FadeOut(left_arithmetic), - FadeOut(self.right_arithmetic), - ) - ) - - def count_by_44(self): - ff_label, ff_line = self.spiral_group[1:] - faded_labels = self.labels - frame = self.camera_frame - - n_values = 100 - mod = 44 - values = range(mod, n_values * mod, mod) - points = [ - self.get_polar_point(n, n) - for n in values - ] - - p2l_tracker = ValueTracker( - get_norm(ff_label.get_bottom() - points[0]) - ) - get_p2l = p2l_tracker.get_value - l_height_ratio_tracker = ValueTracker( - ff_label.get_height() / frame.get_height() - ) - get_l_height_ratio = l_height_ratio_tracker.get_value - - n_labels = 10 - labels = VGroup(*[Integer(n) for n in values[:n_labels]]) - for label, point in zip(labels, points): - label.point = point - label.add_updater( - lambda l: l.set_height( - frame.get_height() * get_l_height_ratio() - ) - ) - label.add_updater( - lambda l: l.move_to( - l.point + get_p2l() * UP, - DOWN, - ) - ) - labels.set_stroke(BLACK, 2, background=True) - - lines = VGroup(ff_line) - for p1, p2 in zip(points, points[1:]): - lines.add(Line(p1, p2)) - lines.match_style(ff_line) - - self.remove(self.spiral_group) - self.remove(faded_labels[44]) - self.play( - frame.scale, self.zoom_factor_1, - p2l_tracker.set_value, 1, - l_height_ratio_tracker.set_value, 0.025, - FadeOut( - ff_label, - rate_func=squish_rate_func(smooth, 0, 1 / 8), - ), - LaggedStart( - *2 * [Animation(Group())], # Weird and dumb - *map(FadeIn, labels), - lag_ratio=0.5, - ), - LaggedStart( - *2 * [Animation(Group())], - *map(ShowCreation, lines[:len(labels)]), - lag_ratio=1, - ), - run_time=8, - ) - self.play( - frame.scale, self.zoom_factor_2, - l_height_ratio_tracker.set_value, 0.01, - ShowCreation(lines[len(labels):]), - run_time=8, - ) - - self.ff_spiral_lines = lines - self.ff_spiral_labels = labels - - # - def arc_func(self, t): - r = 0.1 * t - return r * np.array([np.cos(t), np.sin(t), 0]) - - -class Label44Spirals(Explain44Spirals): - def construct(self): - self.setup_spirals() - self.enumerate_spirals() - - def setup_spirals(self): - max_N = self.max_N - mod = 44 - primes = read_in_primes(max_N) - spirals = VGroup() - for r in range(mod): - ns = range(r, max_N, mod) - spiral = self.get_v_spiral(ns, box_width=1) - for box, n in zip(spiral, ns): - box.n = n - if n in primes: - box.set_color(TEAL) - else: - box.set_color(YELLOW) - spirals.add(spiral) - - self.add(spirals) - scale = np.prod([ - self.initial_scale, - self.zoom_factor_1, - self.zoom_factor_2, - ]) - self.set_scale( - spiral=VGroup(*it.chain(*spirals)), - scale=scale, - run_time=0 - ) - - self.spirals = spirals - - def enumerate_spirals(self): - spirals = self.spirals - labels = self.get_spiral_arm_labels(spirals) - - self.play( - spirals[1:].set_opacity, 0.25, - FadeIn(labels[0]), - ) - self.wait() - - for n in range(10): - arc = Arc( - start_angle=n + 0.2, - angle=0.9, - radius=1.5, - ) - arc.add_tip() - mid_point = arc.point_from_proportion(0.5) - r_label = TextMobject("1 radian") - # r_label.rotate( - # angle_of_vector(arc.get_end() - arc.get_start()) - PI - # ) - r_label.next_to(mid_point, normalize(mid_point)) - if n > 2: - r_label.set_opacity(0) - - self.play( - ShowCreation(arc), - FadeIn(r_label), - spirals[n + 1].set_opacity, 1, - TransformFromCopy(labels[n], labels[n + 1]) - ) - self.play( - FadeOut(arc), - FadeOut(r_label), - spirals[n].set_opacity, 0.25, - FadeOut(labels[n]), - ) - - # - def get_spiral_arm_labels(self, spirals, index=15): - mod = 44 - labels = VGroup(*[ - VGroup( - *TexMobject("44k", "+"), - Integer(n) - ).arrange(RIGHT, buff=SMALL_BUFF) - for n in range(mod) - ]) - labels[0][1:].set_opacity(0) - labels.scale(1.5) - labels.set_color(YELLOW) - - for label, spiral in zip(labels, spirals): - box = spiral[index] - vect = rotate_vector(box.get_center(), 90 * DEGREES) - label.next_to(box, normalize(vect), SMALL_BUFF) - labels[0].shift(UR + 1.25 * RIGHT) - return labels - - -class ResidueClassMod44Label(Scene): - def construct(self): - text = TextMobject( - "``Residue class mod 44''" - ) - text.scale(2) - text.to_corner(UL) - - self.play(Write(text)) - self.wait() - - -class EliminateNonPrimativeResidueClassesOf44(Label44Spirals): - CONFIG = { - "max_N": 7000, - } - - def construct(self): - self.setup_spirals() - self.eliminate_classes() - self.zoom_out() - self.filter_to_primes() - - def eliminate_classes(self): - spirals = self.spirals - labels = self.get_spiral_arm_labels(spirals) - - # Eliminate factors of 2 - self.play( - spirals[1:].set_opacity, 0.5, - FadeIn(labels[0]), - ) - self.wait() - self.play( - FadeOut(spirals[0]), - FadeOut(labels[0]), - ) - for n in range(2, 8, 2): - self.play( - FadeIn(labels[n]), - spirals[n].set_opacity, 1, - ) - self.play(FadeOut(VGroup(labels[n], spirals[n]))) - - words = TextMobject("All even numbers") - words.scale(1.5) - words.to_corner(UL) - self.play( - LaggedStart(*[ - ApplyMethod(spiral.set_opacity, 1) - for spiral in spirals[8::2] - ], lag_ratio=0.01), - FadeIn(words), - ) - self.play( - FadeOut(words), - FadeOut(spirals[8::2]) - ) - self.wait() - - # Eliminate factors of 11 - for k in [11, 33]: - self.play( - spirals[k].set_opacity, 1, - FadeIn(labels[k]) - ) - self.wait() - self.play( - FadeOut(spirals[k]), - FadeOut(labels[k]), - ) - - admissible_spirals = VGroup(*[ - spiral - for n, spiral in enumerate(spirals) - if n % 2 != 0 and n % 11 != 0 - - ]) - - self.play(admissible_spirals.set_opacity, 1) - - self.admissible_spirals = admissible_spirals - - def zoom_out(self): - frame = self.camera_frame - admissible_spirals = self.admissible_spirals - admissible_spirals.generate_target() - for spiral in admissible_spirals.target: - for box in spiral: - box.scale(3) - - self.play( - frame.scale, 4, - MoveToTarget(admissible_spirals), - run_time=3, - ) - - def filter_to_primes(self): - admissible_spirals = self.admissible_spirals - frame = self.camera_frame - primes = read_in_primes(self.max_N) - - to_fade = VGroup(*[ - box - for spiral in admissible_spirals - for box in spiral - if box.n not in primes - ]) - words = TextMobject("Just the primes") - words.set_height(0.1 * frame.get_height()) - words.next_to(frame.get_corner(UL), DR, LARGE_BUFF) - - self.play( - LaggedStartMap(FadeOut, to_fade, lag_ratio=2 / len(to_fade)), - FadeIn(words), - ) - self.wait() - - -class IntroduceTotientJargon(TeacherStudentsScene): - def construct(self): - self.add_title() - self.eliminate_non_coprimes() - - def add_title(self): - self.teacher_says( - "More jargon!", - target_mode="hooray", - ) - self.change_all_student_modes("erm") - words = self.teacher.bubble.content - - words.generate_target() - words.target.scale(1.5) - words.target.center().to_edge(UP, buff=MED_SMALL_BUFF) - words.target.set_color(BLUE) - underline = Line(LEFT, RIGHT) - underline.match_width(words.target) - underline.next_to(words.target, DOWN, SMALL_BUFF) - underline.scale(1.2) - - self.play( - MoveToTarget(words), - FadeOut(self.teacher.bubble), - LaggedStart(*[ - FadeOutAndShift(pi, 4 * DOWN) - for pi in self.pi_creatures - ]), - ShowCreation(underline) - ) - - def eliminate_non_coprimes(self): - number_grid = VGroup(*[ - VGroup(*[ - Integer(n) for n in range(11 * k, 11 * (k + 1)) - ]).arrange(DOWN) - for k in range(4) - ]).arrange(RIGHT, buff=1) - numbers = VGroup(*it.chain(*number_grid)) - numbers.set_height(6) - numbers.move_to(4 * LEFT) - numbers.to_edge(DOWN) - - evens = VGroup(*filter( - lambda nm: nm.get_value() % 2 == 0, - numbers - )) - div11 = VGroup(*filter( - lambda nm: nm.get_value() % 11 == 0, - numbers - )) - coprimes = VGroup(*filter( - lambda nm: nm not in evens and nm not in div11, - numbers - )) - - words = TextMobject( - "Which ones ", "don't\\\\", - "share any factors\\\\", - "with ", "44", - alignment="" - ) - words.scale(1.5) - words.next_to(ORIGIN, RIGHT) - - ff = words.get_part_by_tex("44") - ff.set_color(YELLOW) - ff.generate_target() - - # Show coprimes - self.play( - ShowIncreasingSubsets(numbers, run_time=3), - FadeInFrom(words, LEFT) - ) - self.wait() - for group in evens, div11: - rects = VGroup(*[ - SurroundingRectangle(number, color=RED) - for number in group - ]) - self.play(LaggedStartMap(ShowCreation, rects, run_time=1)) - self.play( - LaggedStart(*[ - ApplyMethod(number.set_opacity, 0.2) - for number in group - ]), - LaggedStartMap(FadeOut, rects), - run_time=1 - ) - self.wait() - - # Rearrange words - dsf = words[1:3] - dsf.generate_target() - dsf.target.arrange(RIGHT) - dsf.target[0].align_to(dsf.target[1][0], DOWN) - - example = numbers[35].copy() - example.generate_target() - example.target.match_height(ff) - num_pair = VGroup( - ff.target, - TextMobject("and").scale(1.5), - example.target, - ) - num_pair.arrange(RIGHT) - num_pair.move_to(words.get_top(), DOWN) - dsf.target.next_to(num_pair, DOWN, MED_LARGE_BUFF) - - phrase1 = TextMobject("are ", "``relatively prime''") - phrase2 = TextMobject("are ", "``coprime''") - for phrase in phrase1, phrase2: - phrase.scale(1.5) - phrase.move_to(dsf.target) - phrase[1].set_color(BLUE) - phrase.arrow = TexMobject("\\Updownarrow") - phrase.arrow.scale(1.5) - phrase.arrow.next_to(phrase, DOWN, 2 * SMALL_BUFF) - phrase.rect = SurroundingRectangle(phrase[1]) - phrase.rect.set_stroke(BLUE) - - self.play( - FadeOut(words[0]), - FadeOut(words[3]), - MoveToTarget(dsf), - MoveToTarget(ff), - GrowFromCenter(num_pair[1]), - ) - self.play( - MoveToTarget(example, path_arc=30 * DEGREES), - ) - self.wait() - self.play( - dsf.next_to, phrase1.arrow, DOWN, SMALL_BUFF, - GrowFromEdge(phrase1.arrow, UP), - GrowFromCenter(phrase1), - ShowCreation(phrase1.rect) - ) - self.play(FadeOut(phrase1.rect)) - self.wait() - self.play( - VGroup(dsf, phrase1, phrase1.arrow).next_to, - phrase2.arrow, DOWN, SMALL_BUFF, - GrowFromEdge(phrase2.arrow, UP), - GrowFromCenter(phrase2), - ShowCreation(phrase2.rect) - ) - self.play(FadeOut(phrase2.rect)) - self.wait() - - # Count through coprimes - coprime_rects = VGroup(*map(SurroundingRectangle, coprimes)) - coprime_rects.set_stroke(BLUE, 2) - example_anim = UpdateFromFunc( - example, lambda m: m.set_value(coprimes[len(coprime_rects) - 1].get_value()) - ) - self.play( - ShowIncreasingSubsets(coprime_rects, int_func=np.ceil), - example_anim, - run_time=3, - rate_func=linear, - ) - self.wait() - - # Show totient function - words_to_keep = VGroup(ff, num_pair[1], example, phrase2) - to_fade = VGroup(phrase2.arrow, phrase1, phrase1.arrow, dsf) - - totient = TexMobject("\\phi", "(", "44", ")", "=", "20") - totient.set_color_by_tex("44", YELLOW) - totient.scale(1.5) - totient.move_to(num_pair, UP) - phi = totient.get_part_by_tex("phi") - rhs = Integer(20) - rhs.replace(totient[-1], dim_to_match=1) - totient.submobjects[-1] = rhs - - self.play( - words_to_keep.to_edge, DOWN, - MaintainPositionRelativeTo(to_fade, words_to_keep), - VFadeOut(to_fade), - ) - self.play(FadeIn(totient)) - self.wait() - - # Label totient - brace = Brace(phi, DOWN) - etf = TextMobject("Euler's totient function") - etf.next_to(brace, DOWN) - etf.shift(RIGHT) - - self.play( - GrowFromCenter(brace), - FadeInFrom(etf, UP) - ) - self.wait() - self.play( - ShowIncreasingSubsets(coprime_rects), - UpdateFromFunc( - rhs, lambda m: m.set_value(len(coprime_rects)), - ), - example_anim, - rate_func=linear, - run_time=3, - ) - self.wait() - - # Show totatives - totient_group = VGroup(totient, brace, etf) - for cp, rect in zip(coprimes, coprime_rects): - cp.add(rect) - - self.play( - coprimes.arrange, RIGHT, {"buff": SMALL_BUFF}, - coprimes.set_width, FRAME_WIDTH - 1, - coprimes.move_to, 2 * UP, - FadeOut(evens), - FadeOut(div11[1::2]), - FadeOutAndShiftDown(words_to_keep), - totient_group.center, - totient_group.to_edge, DOWN, - ) - - totatives = TextMobject("``Totatives''") - totatives.scale(2) - totatives.set_color(BLUE) - totatives.move_to(ORIGIN) - arrows = VGroup(*[ - Arrow(totatives.get_top(), coprime.get_bottom()) - for coprime in coprimes - ]) - arrows.set_color(WHITE) - - self.play( - FadeIn(totatives), - LaggedStartMap(VFadeInThenOut, arrows, run_time=4, lag_ratio=0.05) - ) - self.wait(2) - - -class TwoUnrelatedFacts(Scene): - def construct(self): - self.add_title() - self.show_columns() - - def add_title(self): - title = TextMobject("Two (unrelated) bits of number theory") - title.set_width(FRAME_WIDTH - 1) - title.to_edge(UP) - h_line = Line() - h_line.match_width(title) - h_line.next_to(title, DOWN, SMALL_BUFF) - h_line.set_stroke(LIGHT_GREY) - - self.play( - FadeIn(title), - ShowCreation(h_line), - ) - - self.h_line = h_line - - def show_columns(self): - h_line = self.h_line - v_line = Line( - h_line.get_center() + MED_SMALL_BUFF * DOWN, - FRAME_HEIGHT * DOWN / 2, - ) - v_line.match_style(h_line) - - approx = TexMobject( - "{44 \\over 7} \\approx 2\\pi" - ) - approx.scale(1.5) - approx.next_to( - h_line.point_from_proportion(0.25), - DOWN, MED_LARGE_BUFF, - ) - - mod = 44 - n_terms = 9 - residue_classes = VGroup() - prime_numbers = read_in_primes(1000) - primes = VGroup() - non_primes = VGroup() - for r in range(mod): - if r <= 11 or r == 43: - row = VGroup() - for n in range(r, r + n_terms * mod, mod): - elem = Integer(n) - comma = TexMobject(",") - comma.next_to( - elem.get_corner(DR), - RIGHT, SMALL_BUFF - ) - elem.add(comma) - row.add(elem) - if n in prime_numbers: - primes.add(elem) - else: - non_primes.add(elem) - row.arrange(RIGHT, buff=0.3) - dots = TexMobject("\\dots") - dots.next_to(row.get_corner(DR), RIGHT, SMALL_BUFF) - dots.shift(SMALL_BUFF * UP) - row.add(dots) - row.r = r - if r == 12: - row = TexMobject("\\vdots") - residue_classes.add(row) - - residue_classes.arrange(DOWN) - residue_classes[-2].align_to(residue_classes, LEFT) - residue_classes[-2].shift(MED_SMALL_BUFF * RIGHT) - residue_classes.set_height(6) - residue_classes.next_to(ORIGIN, RIGHT) - residue_classes.to_edge(DOWN, buff=MED_SMALL_BUFF) - - def get_line(row): - return Line( - row.get_left(), row.get_right(), - stroke_color=RED, - stroke_width=4, - ) - - even_lines = VGroup(*[ - get_line(row) - for row in residue_classes[:12:2] - ]) - eleven_line = get_line(residue_classes[11]) - eleven_line.set_color(PINK) - for line in [even_lines[1], eleven_line]: - line.scale(0.93, about_edge=RIGHT) - - self.play(ShowCreation(v_line)) - self.wait() - self.play(FadeInFrom(approx, DOWN)) - self.wait() - self.play(FadeIn(residue_classes)) - self.wait() - self.play( - LaggedStartMap(ShowCreation, even_lines), - ) - self.wait() - self.play(ShowCreation(eleven_line)) - self.wait() - self.play( - primes.set_color, TEAL, - non_primes.set_opacity, 0.25, - even_lines.set_opacity, 0.25, - eleven_line.set_opacity, 0.25, - ) - self.wait() - - -class ExplainRays(Explain44Spirals): - CONFIG = { - "max_N": int(5e5), - "axes_config": { - "x_min": -1000, - "x_max": 1000, - "y_min": -1000, - "y_max": 1000, - "axis_config": { - "tick_frequency": 50, - }, - }, - } - - def construct(self): - self.add_spirals_and_labels() - self.show_710th_point() - self.show_arithmetic() - self.zoom_and_count() - - def show_710th_point(self): - spiral = self.spiral - axes = self.axes - labels = self.labels - - scale_factor = 12 - - fade_rect = FullScreenFadeRectangle() - fade_rect.scale(scale_factor) - - new_ns = list(range(711)) - bright_boxes = self.get_v_spiral(new_ns) - bright_boxes.set_color(YELLOW) - for n, box in enumerate(bright_boxes): - box.set_height(0.02 * np.sqrt(n)) - - big_labels = self.get_labels(new_ns) - - index_tracker = ValueTracker(44) - - labeled_box = VGroup(Square(), Integer(0)) - - def update_labeled_box(mob): - index = int(index_tracker.get_value()) - labeled_box[0].become(bright_boxes[index]) - labeled_box[1].become(big_labels[index]) - - labeled_box.add_updater(update_labeled_box) - - self.set_scale( - scale=120, - spiral=spiral, - to_shrink=labels, - ) - - box_710 = self.get_v_spiral([710])[0] - box_710.scale(2) - box_710.set_color(YELLOW) - label_710 = Integer(710) - label_710.scale(1.5) - label_710.next_to(box_710, UP) - - arrow = Arrow( - ORIGIN, DOWN, - stroke_width=6, - max_tip_length_to_length_ratio=0.35, - max_stroke_width_to_length_ratio=10, - tip_length=0.35 - ) - arrow.match_color(box_710) - arrow.next_to(box_710, UP, SMALL_BUFF) - label_710.next_to(arrow, UP, SMALL_BUFF) - - self.add(spiral, fade_rect, axes, labels) - self.play( - FadeIn(fade_rect), - FadeOut(labels), - FadeInFromLarge(box_710), - FadeInFrom(label_710, DOWN), - ShowCreation(arrow), - ) - self.wait() - - self.fade_rect = fade_rect - self.box_710 = box_710 - self.label_710 = label_710 - self.arrow = arrow - - def show_arithmetic(self): - label_710 = self.label_710 - - equation = TexMobject( - "710", "\\text{ radians}", "=", - "(710 / 2\\pi)", "\\text{ rotations}", - ) - equation.to_corner(UL) - frac = equation.get_part_by_tex("710 / 2\\pi") - brace = Brace(frac, DOWN, buff=SMALL_BUFF) - value = TextMobject("{:.15}".format(710 / TAU)) - value.next_to(brace, DOWN, SMALL_BUFF) - values = VGroup(*[ - value[0][:n].deepcopy().next_to(brace, DOWN, SMALL_BUFF) - for n in [3, *range(5, 13)] - ]) - - group = VGroup(equation, brace, value) - rect = SurroundingRectangle(group, buff=MED_SMALL_BUFF) - rect.set_stroke(WHITE, 2) - rect.set_fill(DARK_GREY, 1) - - approx = TexMobject( - "{710", "\\over", "113}", - "\\approx", "2\\pi", - ) - approx.next_to(rect, DOWN) - approx.align_to(equation, LEFT) - - approx2 = TexMobject( - "{355", "\\over", "113}", - "\\approx", "\\pi", - ) - approx2.next_to(approx, RIGHT, LARGE_BUFF) - - self.play( - FadeIn(rect), - TransformFromCopy(label_710, equation[0]), - FadeIn(equation[1:3]), - ) - self.play( - FadeInFrom(equation[3:], LEFT) - ) - self.play(GrowFromCenter(brace)) - self.play( - ShowSubmobjectsOneByOne(values), - run_time=3, - rate_func=linear, - ) - self.wait() - self.play( - rect.stretch, 2, 1, {"about_edge": UP}, - LaggedStart( - TransformFromCopy( # 710 - equation[3][1:4], - approx[0], - ), - FadeIn(approx[1][0]), - TransformFromCopy( # 113 - values[-1][:3], - approx[2], - ), - FadeIn(approx[3]), - TransformFromCopy( # 2pi - equation[3][5:7], - approx[4], - ), - run_time=2, - ) - ) - self.wait() - self.play( - TransformFromCopy(approx, approx2), - ) - self.wait() - self.play( - FadeOut(VGroup( - rect, equation, brace, values[-1], - approx, approx2 - )), - self.fade_rect.set_opacity, 0.25, - ) - - def zoom_and_count(self): - label = self.label_710 - arrow = self.arrow - box = self.box_710 - axes = self.axes - spiral = self.spiral - - times = TexMobject("\\times") - times.next_to(label, LEFT, SMALL_BUFF) - k_label = Integer(1) - k_label.match_height(label) - k_label.set_color(YELLOW) - k_label.next_to(times, LEFT) - - boxes = VGroup(*[box.copy() for x in range(150)]) - box_height_tracker = ValueTracker(box.get_height()) - - def get_k(): - max_x = axes.x_axis.p2n(label.get_center()) - return max(1, int(max_x / 710)) - - def get_k_point(k): - return self.get_polar_point(710 * k, 710 * k) - - def update_arrow(arrow): - point = get_k_point(get_k()) - arrow.put_start_and_end_on( - label.get_bottom() + SMALL_BUFF * DOWN, - point + SMALL_BUFF * UP - ) - - def get_unit(): - return get_norm(axes.c2p(1, 0) - axes.c2p(0, 0)) - - def update_boxes(boxes): - box_height = box_height_tracker.get_value() - for k, box in enumerate(boxes): - box.set_height(box_height) - box.move_to(get_k_point(k)) - - arrow.add_updater(update_arrow) - boxes.add_updater(update_boxes) - k_label.add_updater( - lambda d: d.set_value(get_k()).next_to( - times, LEFT, SMALL_BUFF - ) - ) - - self.remove(box) - self.add(times, k_label, boxes) - self.set_scale( - scale=10000, - spiral=self.spiral, - run_time=8, - target_p_spiral_width=2, - added_anims=[ - box_height_tracker.set_value, 0.035, - ] - ) - self.wait() - - # Show other residue classes - new_label = TexMobject( - "710", "k", "+", - tex_to_color_map={"k": YELLOW} - ) - new_label.match_height(label) - new_label.next_to(boxes, UP, SMALL_BUFF) - new_label.to_edge(RIGHT) - new_label[2].set_opacity(0) - - r_label = Integer(1) - r_label.match_height(new_label) - r_label.set_opacity(0) - r_label.add_updater( - lambda m: m.next_to(new_label, RIGHT, SMALL_BUFF) - ) - - k_label.clear_updaters() - self.play( - FadeOut(times), - ReplacementTransform(label, new_label[0]), - ReplacementTransform(k_label, new_label[1]), - FadeOut(arrow) - ) - - boxes.clear_updaters() - for r in range(1, 12): - if r in [3, 6]: - vect = UR - else: - vect = RIGHT - point = rotate_vector(boxes[40].get_center(), 1) - new_boxes = boxes.copy() - new_boxes.rotate(1, about_point=ORIGIN) - for box in new_boxes: - box.rotate(-1) - self.play( - FadeOut(boxes), - LaggedStartMap(FadeIn, new_boxes, lag_ratio=0.01), - new_label.set_opacity, 1, - new_label.next_to, point, vect, - r_label.set_opacity, 1, - ChangeDecimalToValue(r_label, r), - run_time=1, - ) - self.remove(boxes) - boxes = new_boxes - self.add(boxes) - self.wait() - - # Show just the primes - self.play( - FadeOut(boxes), - FadeOut(new_label), - FadeOut(r_label), - FadeOut(self.fade_rect) - ) - self.set_scale( - 30000, - spiral=spiral, - run_time=4, - ) - self.wait() - self.remove(spiral) - self.add(spiral[1]) - self.wait() - - -class CompareTauToApprox(Scene): - def construct(self): - eqs = VGroup( - TexMobject("2\\pi", "=", "{:.10}\\dots".format(TAU)), - TexMobject("\\frac{710}{113}", "=", "{:.10}\\dots".format(710 / 113)), - ) - eqs.arrange(DOWN, buff=LARGE_BUFF) - eqs[1].shift((eqs[0][2].get_left()[0] - eqs[1][2].get_left()[0]) * RIGHT) - - eqs.generate_target() - for eq in eqs.target: - eq[2][:8].set_color(RED) - eq.set_stroke(BLACK, 8, background=True) - - self.play(LaggedStart( - FadeInFrom(eqs[0], DOWN), - FadeInFrom(eqs[1], UP), - )) - self.play(MoveToTarget(eqs)) - self.wait() - - -class RecommendedMathologerVideo(Scene): - def construct(self): - full_rect = FullScreenFadeRectangle() - full_rect.set_fill(DARK_GREY, 1) - self.add(full_rect) - - title = TextMobject("Recommended Mathologer video") - title.set_width(FRAME_WIDTH - 1) - title.to_edge(UP) - screen_rect = SurroundingRectangle(ScreenRectangle(height=5.9), buff=SMALL_BUFF) - screen_rect.next_to(title, DOWN) - screen_rect.set_fill(BLACK, 1) - screen_rect.set_stroke(WHITE, 3) - - self.add(screen_rect) - self.play(Write(title)) - self.wait() - - -class ShowClassesOfPrimeRays(SpiralScene): - CONFIG = { - "max_N": int(1e6), - "scale": 1e5 - } - - def construct(self): - self.setup_rays() - self.show_classes() - - def setup_rays(self): - spiral = self.get_prime_p_spiral(self.max_N) - self.add(spiral) - self.set_scale( - scale=self.scale, - spiral=spiral, - target_p_spiral_width=3, - run_time=0 - ) - - def show_classes(self): - max_N = self.max_N - mod = 710 - primes = read_in_primes(max_N) - - rect = FullScreenFadeRectangle() - rect.set_opacity(0) - self.add(rect) - - last_ray = PMobject() - last_label = VGroup(*[VectorizedPoint() for x in range(3)]) - for i in range(40): - if get_gcd(i, mod) != 1: - continue - - r = (INV_113_MOD_710 * i) % mod - - sequence = filter( - lambda x: x in primes, - range(r, max_N, mod) - ) - ray = self.get_v_spiral(sequence, box_width=0.03) - ray.set_color(GREEN) - ray.set_opacity(0.9) - - label = VGroup( - *TexMobject("710k", "+"), - Integer(r) - ) - label.arrange(RIGHT, buff=SMALL_BUFF) - label.next_to(ray[100], UL, SMALL_BUFF) - - label[2].save_state() - label[2].set_opacity(0) - label[2].move_to(last_label, RIGHT) - - self.play( - rect.set_opacity, 0.5, - ShowCreation(ray), - LaggedStartMap(FadeOut, last_ray), - ReplacementTransform(last_label[:2], label[:2]), - Restore(label[2]), - last_label[2].move_to, label[2].saved_state, - last_label[2].set_opacity, 0, - ) - self.remove(last_label) - self.add(label) - self.wait() - - last_ray = ray - last_label = label - - -class ShowFactorsOf710(Scene): - def construct(self): - equation = TexMobject( - "710", "=", - "71", "\\cdot", - "5", "\\cdot", - "2", - ) - equation.scale(1.5) - equation.to_corner(UL) - ten = TexMobject("10") - ten.match_height(equation) - ten.move_to(equation.get_part_by_tex("5"), LEFT) - - self.add(equation[0]) - self.wait() - self.play( - TransformFromCopy( - equation[0][:2], - equation[2], - ), - FadeIn(equation[1]), - FadeIn(equation[3]), - TransformFromCopy( - equation[0][2:], - ten, - ) - ) - self.wait() - self.remove(ten) - self.play(*[ - TransformFromCopy(ten, mob) - for mob in equation[4:] - ]) - self.wait() - - # Circle factors - colors = [RED, BLUE, PINK] - for factor, color in zip(equation[:-6:-2], colors): - rect = SurroundingRectangle(factor) - rect.set_color(color) - self.play(ShowCreation(rect)) - self.wait() - self.play(FadeOut(rect)) - - -class LookAtRemainderMod710(Scene): - def construct(self): - t2c = { - "n": YELLOW, - "r": GREEN - } - equation = TexMobject( - "n", "=", "710", "k", "+", "r", - tex_to_color_map=t2c, - ) - equation.scale(1.5) - - n_arrow = Vector(UP).next_to(equation.get_part_by_tex("n"), DOWN) - r_arrow = Vector(UP).next_to(equation.get_part_by_tex("r"), DOWN) - n_arrow.set_color(t2c["n"]) - r_arrow.set_color(t2c["r"]) - - n_label = TextMobject("Some\\\\number") - r_label = TextMobject("Remainder") - VGroup(n_label, r_label).scale(1.5) - n_label.next_to(n_arrow, DOWN) - n_label.match_color(n_arrow) - r_label.next_to(r_arrow, DOWN) - r_label.match_color(r_arrow) - - self.add(equation) - self.play( - FadeInFrom(n_label, UP), - ShowCreation(n_arrow), - ) - self.wait() - self.play( - FadeInFrom(r_label, DOWN), - ShowCreation(r_arrow), - ) - self.wait() - - -class EliminateNonPrimative710Residues(ShowClassesOfPrimeRays): - CONFIG = { - "max_N": int(5e5), - "scale": 5e4, - } - - def construct(self): - self.setup_rays() - self.eliminate_classes() - - def setup_rays(self): - mod = 710 - rays = PGroup(*[ - self.get_p_spiral(range(r, self.max_N, mod)) - for r in range(mod) - ]) - rays.set_color(YELLOW) - self.add(rays) - self.set_scale( - scale=self.scale, - spiral=rays, - target_p_spiral_width=1, - run_time=0, - ) - - self.rays = rays - - def eliminate_classes(self): - rays = self.rays - rect = FullScreenFadeRectangle() - rect.set_opacity(0) - - for r, ray in enumerate(rays): - ray.r = r - - mod = 710 - odds = PGroup(*[rays[i] for i in range(1, mod, 2)]) - mult5 = PGroup(*[rays[i] for i in range(0, mod, 5) if i % 2 != 0]) - mult71 = PGroup(*[rays[i] for i in range(0, mod, 71) if (i % 2 != 0 and i % 5 != 0)]) - colors = [RED, BLUE, PINK] - - pre_label, r_label = label = VGroup( - TexMobject("710k + "), - Integer(100) - ) - label.scale(1.5) - label.arrange(RIGHT, buff=SMALL_BUFF) - label.set_stroke(BLACK, 5, background=True, family=True) - label.next_to(ORIGIN, DOWN) - - r_label.group = odds - r_label.add_updater( - lambda m: m.set_value(m.group[-1].r if len(m.group) > 0 else 1), - ) - - self.remove(rays) - # Odds - odds.set_stroke_width(3) - self.add(odds, label) - self.play( - ShowIncreasingSubsets(odds, int_func=np.ceil), - run_time=10, - ) - self.play(FadeOut(label)) - self.remove(odds) - self.add(*odds) - self.wait() - - # Multiples of 5 then 71 - for i, group in [(1, mult5), (2, mult71)]: - group_copy = group.copy() - group_copy.set_color(colors[i]) - group_copy.set_stroke_width(4) - r_label.group = group_copy - self.add(group_copy, label) - self.play( - ShowIncreasingSubsets(group_copy, int_func=np.ceil, run_time=10), - ) - self.play(FadeOut(label)) - self.wait() - self.remove(group_copy, *group) - self.wait() - - -class Show280Computation(Scene): - def construct(self): - equation = TexMobject( - "\\phi(710) = ", - "710", - "\\left({1 \\over 2}\\right)", - "\\left({4 \\over 5}\\right)", - "\\left({70 \\over 71}\\right)", - "=", - "280", - ) - equation.set_width(FRAME_WIDTH - 1) - equation.move_to(UP) - words = VGroup( - TextMobject("Filter out\\\\evens"), - TextMobject("Filter out\\\\multiples of 5"), - TextMobject("Filter out\\\\multiples of 71"), - ) - vects = [DOWN, UP, DOWN] - colors = [RED, BLUE, LIGHT_PINK] - for part, word, vect, color in zip(equation[2:5], words, vects, colors): - brace = Brace(part, vect) - brace.stretch(0.8, 0) - word.brace = brace - word.next_to(brace, vect) - part.set_color(color) - word.set_color(color) - word.set_stroke(BLACK, 5, background=True) - equation.set_stroke(BLACK, 5, background=True) - - etf_label = TextMobject("Euler's totient function") - etf_label.to_corner(UL) - arrow = Arrow(etf_label.get_bottom(), equation[0][0].get_top()) - equation[0][0].set_color(YELLOW) - etf_label.set_color(YELLOW) - - rect = FullScreenFadeRectangle(fill_opacity=0.9) - self.play( - FadeIn(rect), - FadeInFromDown(equation), - FadeIn(etf_label), - GrowArrow(arrow), - ) - self.wait() - for word in words: - self.play( - FadeIn(word), - GrowFromCenter(word.brace), - ) - self.wait() - - -class TeacherHoldUp(TeacherStudentsScene): - def construct(self): - self.change_all_student_modes( - "pondering", look_at_arg=2 * UP, - added_anims=[ - self.teacher.change, "raise_right_hand" - ] - ) - self.wait(8) - - -class DiscussPrimesMod10(Scene): - def construct(self): - labels = VGroup(*[ - TextMobject(str(n), " mod 10:") - for n in range(10) - ]) - labels.arrange(DOWN, buff=0.35, aligned_edge=LEFT) - labels.to_edge(LEFT) - # digits = VGroup(*[l[0] for l in labels]) - labels.set_submobject_colors_by_gradient(YELLOW, BLUE) - - sequences = VGroup(*[ - VGroup(*[ - Integer(n).shift((n // 10) * RIGHT) - for n in range(r, 100 + r, 10) - ]) - for r in range(10) - ]) - for sequence, label in zip(sequences, labels): - sequence.next_to(label, RIGHT, buff=MED_LARGE_BUFF) - for item in sequence: - if item is sequence[-1]: - punc = TexMobject("\\dots") - else: - punc = TextMobject(",") - punc.next_to(item.get_corner(DR), RIGHT, SMALL_BUFF) - item.add(punc) - - # Introduce everything - self.play(LaggedStart(*[ - FadeInFrom(label, UP) - for label in labels - ])) - self.wait() - self.play( - LaggedStart(*[ - LaggedStart(*[ - FadeInFrom(item, LEFT) - for item in sequence - ]) - for sequence in sequences - ]) - ) - self.wait() - - # Highlight 0's then 1's - for sequence in sequences[:2]: - lds = VGroup(*[item[-2] for item in sequence]) - rects = VGroup(*[ - SurroundingRectangle(ld, buff=0.05) - for ld in lds - ]) - rects.set_color(YELLOW) - self.play( - LaggedStartMap( - ShowCreationThenFadeOut, rects - ) - ) - self.wait() - - # Eliminate certain residues - two = sequences[2][0] - five = sequences[5][0] - evens = VGroup(*it.chain(*sequences[::2])) - evens.remove(two) - div5 = sequences[5][1:] - prime_numbers = read_in_primes(100) - - primes = VGroup(*[ - item - for seq in sequences - for item in seq - if int(item.get_value()) in prime_numbers - ]) - non_primes = VGroup(*[ - item - for seq in sequences - for item in seq - if reduce(op.and_, [ - int(item.get_value()) not in prime_numbers, - item.get_value() % 2 != 0, - item.get_value() % 5 != 0, - ]) - ]) - - for prime, group in [(two, evens), (five, div5)]: - self.play(ShowCreationThenFadeAround(prime)) - self.play(LaggedStart(*[ - ApplyMethod(item.set_opacity, 0.2) - for item in group - ])) - self.wait() - - # Highlight primes - self.play( - LaggedStart(*[ - ApplyFunction( - lambda m: m.scale(1.2).set_color(TEAL), - prime - ) - for prime in primes - ]), - LaggedStart(*[ - ApplyFunction( - lambda m: m.scale(0.8).set_opacity(0.8), - non_prime - ) - for non_prime in non_primes - ]), - ) - self.wait() - - # Highlight coprime residue classes - rects = VGroup(*[ - SurroundingRectangle(VGroup(labels[r], sequences[r])) - for r in [1, 3, 7, 9] - ]) - for rect in rects: - rect.reverse_points() - - fade_rect = FullScreenFadeRectangle() - fade_rect.scale(1.1) - new_fade_rect = fade_rect.copy() - fade_rect.append_vectorized_mobject(rects[0]) - for rect in rects: - new_fade_rect.append_vectorized_mobject(rect) - - self.play(DrawBorderThenFill(fade_rect)) - self.wait() - self.play( - FadeOut(fade_rect), - FadeIn(new_fade_rect), - ) - self.wait() - - -class BucketPrimesByLastDigit(Scene): - CONFIG = { - "bar_colors": [YELLOW, BLUE], - "mod": 10, - "max_n": 10000, - "n_to_animate": 20, - "n_to_show": 1000, - "x_label_scale_factor": 1, - "x_axis_label": "Last digit", - "bar_width": 0.5, - } - - def construct(self): - self.add_axes() - self.add_bars() - self.bucket_primes() - - def add_axes(self): - mod = self.mod - - axes = Axes( - x_min=0, - x_max=mod + 0.5, - x_axis_config={ - "unit_size": 10 / mod, - "include_tip": False, - }, - y_min=0, - y_max=100, - y_axis_config={ - "unit_size": 0.055, - "tick_frequency": 12.5, - "include_tip": False, - }, - ) - - x_labels = VGroup() - for x in range(mod): - digit = Integer(x) - digit.scale(self.x_label_scale_factor) - digit.next_to(axes.x_axis.n2p(x + 1), DOWN, MED_SMALL_BUFF) - x_labels.add(digit) - self.modify_x_labels(x_labels) - x_labels.set_submobject_colors_by_gradient(*self.bar_colors) - axes.add(x_labels) - axes.x_labels = x_labels - - y_labels = VGroup() - for y in range(25, 125, 25): - label = Integer(y, unit="\\%") - label.next_to(axes.y_axis.n2p(y), LEFT, MED_SMALL_BUFF) - y_labels.add(label) - axes.add(y_labels) - - x_axis_label = TextMobject(self.x_axis_label) - x_axis_label.next_to(axes.x_axis.get_end(), RIGHT, buff=MED_LARGE_BUFF) - axes.add(x_axis_label) - - y_axis_label = TextMobject("Proportion") - y_axis_label.next_to(axes.y_axis.get_end(), UP, buff=MED_LARGE_BUFF) - # y_axis_label.set_color(self.bar_colors[0]) - axes.add(y_axis_label) - - axes.center() - axes.set_width(FRAME_WIDTH - 1) - axes.to_edge(DOWN) - - self.axes = axes - self.add(axes) - - def add_bars(self): - axes = self.axes - mod = self.mod - - count_trackers = Group(*[ - ValueTracker(0) - for x in range(mod) - ]) - - bars = VGroup() - for x in range(mod): - bar = Rectangle( - height=1, - width=self.bar_width, - fill_opacity=1, - ) - bar.bottom = axes.x_axis.n2p(x + 1) - bars.add(bar) - bars.set_submobject_colors_by_gradient(*self.bar_colors) - bars.set_stroke(WHITE, 1) - - def update_bars(bars): - values = [ct.get_value() for ct in count_trackers] - total = sum(values) - if total == 0: - props = [0 for x in range(mod)] - elif total < 1: - props = values - else: - props = [value / total for value in values] - - for bar, prop in zip(bars, props): - bar.set_height( - max( - 1e-5, - 100 * prop * axes.y_axis.unit_size, - ), - stretch=True - ) - # bar.set_height(1) - bar.move_to(bar.bottom, DOWN) - - bars.add_updater(update_bars) - - self.add(count_trackers) - self.add(bars) - - self.bars = bars - self.count_trackers = count_trackers - - def bucket_primes(self): - bars = self.bars - count_trackers = self.count_trackers - - max_n = self.max_n - n_to_animate = self.n_to_animate - n_to_show = self.n_to_show - mod = self.mod - - primes = VGroup(*[ - Integer(prime).scale(2).to_edge(UP, buff=LARGE_BUFF) - for prime in read_in_primes(max_n) - ]) - - arrow = Arrow(ORIGIN, DOWN) - x_labels = self.axes.x_labels - rects = VGroup(*map(SurroundingRectangle, x_labels)) - rects.set_color(RED) - - self.play(FadeIn(primes[0])) - for i, p, np in zip(it.count(), primes[:n_to_show], primes[1:]): - d = int(p.get_value()) % mod - self.add(rects[d]) - if i < n_to_animate: - self.play( - p.scale, 0.5, - p.move_to, bars[d].get_top(), - p.set_opacity, 0, - FadeIn(np), - count_trackers[d].increment_value, 1, - ) - self.remove(p) - else: - arrow.next_to(bars[d], UP) - self.add(arrow) - self.add(p) - count_trackers[d].increment_value(1) - self.wait(0.1) - self.remove(p) - self.remove(rects[d]) - - # - def modify_x_labels(self, labels): - pass - - -class PhraseDirichletsTheoremFor10(TeacherStudentsScene): - def construct(self): - expression = TexMobject( - "\\lim_{x \\to \\infty}", - "\\left(", - "{\\text{\\# of primes $p$ where $p \\le x$} \\text{ and $p \\equiv 1$ mod 10}", - "\\over", - "\\text{\\# of primes $p$ where $p \\le x$}}", - "\\right)", - "=", - "\\frac{1}{4}", - ) - lim, lp, num, over, denom, rp, eq, fourth = expression - expression.shift(UP) - - denom.save_state() - denom.move_to(self.hold_up_spot, DOWN) - denom.shift_onto_screen() - - num[len(denom):].set_color(YELLOW) - - x_example = VGroup( - TextMobject("Think, for example, $x = $"), - Integer(int(1e6)), - ) - x_example.arrange(RIGHT) - x_example.scale(1.5) - x_example.to_edge(UP) - - # - teacher = self.teacher - students = self.students - self.play( - FadeInFromDown(denom), - teacher.change, "raise_right_hand", - self.get_student_changes(*["pondering"] * 3), - ) - self.wait() - self.play(FadeInFromDown(x_example)) - self.wait() - self.play( - Restore(denom), - teacher.change, "thinking", - ) - self.play( - TransformFromCopy(denom, num[:len(denom)]), - Write(over), - ) - self.play( - Write(num[len(denom):]), - students[0].change, "confused", - students[2].change, "erm", - ) - self.wait(2) - self.play( - Write(lp), - Write(rp), - Write(eq), - ) - self.play(FadeInFrom(fourth, LEFT)) - self.play(FadeInFrom(lim, RIGHT)) - self.play( - ChangeDecimalToValue( - x_example[1], int(1e7), - run_time=8, - rate_func=linear, - ), - VFadeOut(x_example, run_time=8), - self.get_student_changes(*["thinking"] * 3), - Blink( - teacher, - run_time=4, - rate_func=squish_rate_func(there_and_back, 0.65, 0.7) - ), - ) - - -class InsertNewResidueClasses(Scene): - def construct(self): - nums = VGroup(*map(Integer, [3, 7, 9])) - colors = [GREEN, TEAL, BLUE] - for num, color in zip(nums, colors): - num.set_color(color) - num.add_background_rectangle(buff=SMALL_BUFF, opacity=1) - self.play(FadeInFrom(num, UP)) - self.wait() - - -class BucketPrimesBy44(BucketPrimesByLastDigit): - CONFIG = { - "mod": 44, - "n_to_animate": 5, - "x_label_scale_factor": 0.5, - "x_axis_label": "r mod 44", - "bar_width": 0.1, - } - - def modify_x_labels(self, labels): - labels[::2].set_opacity(0) - - -class BucketPrimesBy9(BucketPrimesByLastDigit): - CONFIG = { - "mod": 9, - "n_to_animate": 5, - "x_label_scale_factor": 1, - "x_axis_label": "r mod 9", - "bar_width": 1, - } - - def modify_x_labels(self, labels): - pass - - -class DirichletIn1837(MovingCameraScene): - def construct(self): - # Add timeline - dates = list(range(1780, 2030, 10)) - timeline = NumberLine( - x_min=1700, - x_max=2020, - tick_frequency=1, - numbers_with_elongated_ticks=dates, - unit_size=0.2, - stroke_color=GREY, - stroke_width=2, - ) - timeline.add_numbers( - *dates, - number_config={"group_with_commas": False}, - ) - timeline.numbers.shift(SMALL_BUFF * DOWN) - timeline.to_edge(RIGHT) - - # Special dates - d_arrow, rh_arrow, pnt_arrow = arrows = VGroup(*[ - Vector(DOWN).next_to(timeline.n2p(date), UP) - for date in [1837, 1859, 1896] - ]) - d_label, rh_label, pnt_label = labels = VGroup(*[ - TextMobject(text).next_to(arrow, UP) - for arrow, text in zip(arrows, [ - "Dirichlet's\\\\theorem\\\\1837", - "Riemann\\\\hypothesis\\\\1859", - "Prime number\\\\theorem\\\\1896", - ]) - ]) - - # Back in time - frame = self.camera_frame - self.add(timeline, arrows, labels) - self.play( - frame.move_to, timeline.n2p(1837), - run_time=4, - ) - self.wait() - - # Show picture - image = ImageMobject("Dirichlet") - image.set_height(3) - image.next_to(d_label, LEFT) - self.play(FadeInFrom(image, RIGHT)) - self.wait() - - # Flash - self.play( - Flash( - d_label.get_center(), - num_lines=12, - line_length=0.25, - flash_radius=1.5, - line_stroke_width=3, - lag_ratio=0.05, - ) - ) - self.wait() - - # Transition title - title, underline = self.get_title_and_underline() - n = len(title[0]) - self.play( - ReplacementTransform(d_label[0][:n], title[0][:n]), - FadeOut(d_label[0][n:]), - LaggedStartMap( - FadeOutAndShiftDown, Group( - image, d_arrow, - rh_label, rh_arrow, - pnt_label, pnt_arrow, - *timeline, - ) - ), - ) - self.play(ShowCreation(underline)) - self.wait() - - def get_title_and_underline(self): - frame = self.camera_frame - title = TextMobject("Dirichlet's theorem") - title.scale(1.5) - title.next_to(frame.get_top(), DOWN, buff=MED_LARGE_BUFF) - underline = Line() - underline.match_width(title) - underline.next_to(title, DOWN, SMALL_BUFF) - return title, underline - - -class PhraseDirichletsTheorem(DirichletIn1837): - def construct(self): - self.add(*self.get_title_and_underline()) - - # Copy-pasted, which isn't great - expression = TexMobject( - "\\lim_{x \\to \\infty}", - "\\left(", - "{\\text{\\# of primes $p$ where $p \\le x$} \\text{ and $p \\equiv 1$ mod 10}", - "\\over", - "\\text{\\# of primes $p$ where $p \\le x$}}", - "\\right)", - "=", - "\\frac{1}{4}", - ) - lim, lp, num, over, denom, rp, eq, fourth = expression - expression.shift(1.5 * UP) - expression.to_edge(LEFT, MED_SMALL_BUFF) - num[len(denom):].set_color(YELLOW) - # - - # Terms and labels - ten = num[-2:] - one = num[-6] - four = fourth[-1] - - N = TexMobject("N") - r = TexMobject("r") - one_over_phi_N = TexMobject("1", "\\over", "\\phi(", "N", ")") - - N.set_color(MAROON_B) - r.set_color(BLUE) - one_over_phi_N.set_color_by_tex("N", N.get_color()) - - N.move_to(ten, DL) - r.move_to(one, DOWN) - one_over_phi_N.move_to(fourth, LEFT) - - N_label = TextMobject("$N$", " is any number") - N_label.set_color_by_tex("N", N.get_color()) - N_label.next_to(expression, DOWN, LARGE_BUFF) - - r_label = TextMobject("$r$", " is coprime to ", "$N$") - r_label[0].set_color(r.get_color()) - r_label[2].set_color(N.get_color()) - r_label.next_to(N_label, DOWN, MED_LARGE_BUFF) - - phi_N_label = TexMobject( - "\\phi({10}) = ", - "\\#\\{1, 3, 7, 9\\} = 4", - tex_to_color_map={ - "{10}": N.get_color(), - } - ) - phi_N_label[-1][2:9:2].set_color(r.get_color()) - phi_N_label.next_to(r_label, DOWN, MED_LARGE_BUFF) - # - - self.play( - LaggedStart(*[ - FadeIn(denom), - ShowCreation(over), - FadeIn(num), - Write(VGroup(lp, rp)), - FadeIn(lim), - Write(VGroup(eq, fourth)), - ]), - run_time=3, - lag_ratio=0.7, - ) - self.wait() - for mob in denom, num: - self.play(ShowCreationThenFadeAround(mob)) - self.wait() - self.play( - FadeInFrom(r, DOWN), - FadeOutAndShift(one, UP), - ) - self.play( - FadeInFrom(N, DOWN), - FadeOutAndShift(ten, UP), - ) - self.wait() - self.play( - TransformFromCopy(N, N_label[0]), - FadeIn(N_label[1:], DOWN) - ) - self.wait() - self.play( - FadeIn(r_label[1:-1], DOWN), - TransformFromCopy(r, r_label[0]), - ) - self.play( - TransformFromCopy(N_label[0], r_label[-1]), - ) - self.wait() - - self.play( - ShowCreationThenFadeAround(fourth), - ) - self.play( - FadeInFrom(one_over_phi_N[2:], LEFT), - FadeOutAndShift(four, RIGHT), - ReplacementTransform(fourth[0], one_over_phi_N[0][0]), - ReplacementTransform(fourth[1], one_over_phi_N[1][0]), - ) - self.play( - FadeInFrom(phi_N_label, DOWN) - ) - self.wait() - - # Fancier version - new_expression = TexMobject( - "\\lim_{x \\to \\infty}", - "\\left(", - "{\\pi(x; {N}, {r})", - "\\over", - "\\pi(x)}", - "\\right)", - "=", - "\\frac{1}{\\phi({N})}", - tex_to_color_map={ - "{N}": N.get_color(), - "{r}": r.get_color(), - "\\pi": WHITE, - } - ) - pis = new_expression.get_parts_by_tex("\\pi") - - randy = Randolph(height=2) - randy.next_to(new_expression, LEFT, buff=LARGE_BUFF) - randy.shift(0.75 * DOWN) - - new_expression.next_to(expression, DOWN, LARGE_BUFF) - ne_rect = SurroundingRectangle(new_expression, color=BLUE) - - label_group = VGroup(N_label, r_label) - label_group.generate_target() - label_group.target.arrange(RIGHT, buff=LARGE_BUFF) - label_group.target.next_to(new_expression, DOWN, buff=LARGE_BUFF) - - self.play( - FadeIn(randy), - ) - self.play( - randy.change, "hooray", expression - ) - self.play(Blink(randy)) - self.wait() - self.play( - FadeIn(new_expression), - MoveToTarget(label_group), - phi_N_label.to_edge, DOWN, MED_LARGE_BUFF, - randy.change, "horrified", new_expression, - ) - self.play(ShowCreation(ne_rect)) - self.play(randy.change, "confused") - self.play(Blink(randy)) - self.wait() - self.play( - LaggedStartMap( - ShowCreationThenFadeAround, pis, - ), - randy.change, "angry", new_expression - ) - self.wait() - self.play(Blink(randy)) - self.wait() - - -class MoreModestDirichlet(Scene): - def construct(self): - ed = TextMobject( - "Each (coprime) residue class ", - "is equally dense with ", - "primes." - ) - inf = TextMobject( - "Each (coprime) residue class ", - "has infinitely many ", - "primes." - ) - ed[1].set_color(BLUE) - inf[1].set_color(GREEN) - - for mob in [*ed, *inf]: - mob.save_state() - - cross = Cross(ed[1]) - c_group = VGroup(ed[1], cross) - - self.add(ed) - self.wait() - self.play(ShowCreation(cross)) - self.play( - c_group.shift, DOWN, - c_group.set_opacity, 0.5, - ReplacementTransform(ed[::2], inf[::2]), - FadeIn(inf[1]) - ) - self.wait() - self.remove(*inf) - self.play( - inf[1].shift, UP, - Restore(ed[0]), - Restore(ed[1]), - Restore(ed[2]), - FadeOut(cross), - ) - self.wait() - - -class TalkAboutProof(TeacherStudentsScene): - def construct(self): - teacher = self.teacher - students = self.students - - # Ask question - self.student_says( - "So how'd he\\\\prove it?", - student_index=0, - ) - bubble = students[0].bubble - students[0].bubble = None - self.play( - teacher.change, "hesitant", - students[1].change, "happy", - students[2].change, "happy", - ) - self.wait() - self.teacher_says( - "...er...it's a\\\\bit complicated", - target_mode="guilty", - ) - self.change_all_student_modes( - "tired", - look_at_arg=teacher.bubble, - lag_ratio=0.1, - ) - self.play( - FadeOut(bubble), - FadeOut(bubble.content), - ) - self.wait(3) - - # Bring up complex analysis - ca = TextMobject("Complex ", "Analysis") - ca.move_to(self.hold_up_spot, DOWN) - - self.play( - teacher.change, "raise_right_hand", - FadeInFromDown(ca), - FadeOut(teacher.bubble), - FadeOut(teacher.bubble.content), - self.get_student_changes(*["pondering"] * 3), - ) - self.wait() - self.play( - ca.scale, 2, - ca.center, - ca.to_edge, UP, - teacher.change, "happy", - ) - self.play(ca[1].set_color, GREEN) - self.wait(2) - self.play(ca[0].set_color, YELLOW) - self.wait(2) - self.change_all_student_modes( - "confused", look_at_arg=ca, - ) - self.wait(4) - - -class HighlightTwinPrimes(Scene): - def construct(self): - self.add_paper_titles() - self.show_twin_primes() - - def add_paper_titles(self): - gpy = TextMobject( - "Goldston, Pintz, Yildirim\\\\", - "2005", - ) - zhang = TextMobject("Zhang\\\\2014") - - gpy.move_to(FRAME_WIDTH * LEFT / 4) - gpy.to_edge(UP) - zhang.move_to(FRAME_WIDTH * RIGHT / 4) - zhang.to_edge(UP) - - self.play(LaggedStartMap( - FadeInFromDown, VGroup(gpy, zhang), - lag_ratio=0.3, - )) - - def show_twin_primes(self): - max_x = 300 - line = NumberLine( - x_min=0, - x_max=max_x, - unit_size=0.5, - numbers_with_elongated_ticks=range(10, max_x, 10), - ) - line.move_to(2.5 * DOWN + 7 * LEFT, LEFT) - line.add_numbers(*range(10, max_x, 10)) - - primes = read_in_primes(max_x) - prime_mobs = VGroup(*[ - Integer(p).next_to(line.n2p(p), UP) - for p in primes - ]) - dots = VGroup(*[Dot(line.n2p(p)) for p in primes]) - - arcs = VGroup() - for pm, npm in zip(prime_mobs, prime_mobs[1:]): - p = pm.get_value() - np = npm.get_value() - if np - p == 2: - angle = 30 * DEGREES - arc = Arc( - start_angle=angle, - angle=PI - 2 * angle, - color=RED, - ) - arc.set_width( - get_norm(npm.get_center() - pm.get_center()) - ) - arc.next_to(VGroup(pm, npm), UP, SMALL_BUFF) - arcs.add(arc) - - dots.set_color(TEAL) - prime_mobs.set_color(TEAL) - - line.add(dots) - - self.play( - FadeIn(line, lag_ratio=0.9), - LaggedStartMap(FadeInFromDown, prime_mobs), - run_time=2, - ) - line.add(prime_mobs) - self.wait() - - self.play(FadeIn(arcs)) - self.play( - line.shift, 100 * LEFT, - arcs.shift, 100 * LEFT, - run_time=20, - rate_func=lambda t: smooth(t, 5) - ) - self.wait() - - -class RandomToImportant(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs": { - "color": GREY_BROWN, - }, - "camera_config": { - "background_color": DARKER_GREY, - } - } - - def construct(self): - morty = self.pi_creature - morty.center().to_edge(DOWN) - - left_comment = TextMobject("Arbitrary question") - left_comment.to_edge(UP) - left_comment.shift(3.5 * LEFT) - - right_comment = TextMobject("Deep fact") - right_comment.to_edge(UP) - right_comment.shift(3.5 * RIGHT) - - arrow = Arrow( - left_comment.get_right(), - right_comment.get_left(), - buff=0.5, - ) - - self.play( - morty.change, "raise_left_hand", left_comment, - FadeInFromDown(left_comment) - ) - self.wait(2) - self.play( - morty.change, "raise_right_hand", right_comment, - FadeInFromDown(right_comment) - ) - self.play( - ShowCreation(arrow), - morty.look_at, right_comment, - ) - self.wait(2) - - -class RandomWalkOfTopics(Scene): - CONFIG = { - "n_dots": 30, - "n_edge_range": [2, 4], - "super_dot_n_edges": 20, - "isolated_threashold": 0.5, - } - - def construct(self): - self.setup_network() - self.define_important() - self.perform_walk() - - def setup_network(self): - n_dots = self.n_dots - dots = VGroup() - - while len(dots) < n_dots: - point = np.random.uniform(-1, 1, size=3) - point[2] = 0 - point[0] *= 7 - point[1] *= 3 - isolated = True - for dot in dots: - if get_norm(dot.get_center() - point) < self.isolated_threashold: - isolated = False - if not isolated: - continue - dot = Dot(point) - dot.edges = VGroup() - dot.neighbors = VGroup() - dots.add(dot) - super_dot = dots[len(dots) // 2] - - all_edges = VGroup() - - def add_edge(d1, d2): - if d1 is d2: - return - edge = Line( - d1.get_center(), d2.get_center(), - buff=d1.get_width() / 2 - ) - d1.edges.add(edge) - d2.edges.add(edge) - d1.neighbors.add(d2) - d2.neighbors.add(d1) - all_edges.add(edge) - - for dot in dots: - # others = list(dots[i + 1:]) - others = [d for d in dots if d is not dot] - others.sort(key=lambda d: get_norm(d.get_center() - dot.get_center())) - n_edges = np.random.randint(*self.n_edge_range) - for other in others[:n_edges]: - if dot in other.neighbors: - continue - add_edge(dot, other) - - for dot in dots: - if len(super_dot.neighbors) > self.super_dot_n_edges: - break - elif dot in super_dot.neighbors: - continue - add_edge(super_dot, dot) - - dots.sort(lambda p: p[0]) - - all_edges.set_stroke(WHITE, 2) - dots.set_fill(LIGHT_GREY, 1) - - VGroup(dots, all_edges).to_edge(DOWN, buff=MED_SMALL_BUFF) - - self.dots = dots - self.edges = all_edges - self.super_dot = super_dot - - def define_important(self): - sd = self.super_dot - dots = self.dots - edges = self.edges - - sd.set_color(RED) - for mob in [*dots, *edges]: - mob.save_state() - mob.set_opacity(0) - - sd.set_opacity(1) - sd.edges.set_opacity(1) - sd.neighbors.set_opacity(1) - - # angles = np.arange(0, TAU, TAU / len(sd.neighbors)) - # center = 0.5 * DOWN - # sd.move_to(center) - # for dot, edge, angle in zip(sd.neighbors, sd.edges, angles): - # dot.move_to(center + rotate_vector(2.5 * RIGHT, angle)) - # if edge.get_length() > 0: - # edge.put_start_and_end_on( - # sd.get_center(), - # dot.get_center() - # ) - # rad = dot.get_width() / 2 - # llen = edge.get_length() - # edge.scale((llen - 2 * rad) / llen) - - title = VGroup( - TextMobject("Important"), - TexMobject("\\Leftrightarrow"), - TextMobject("Many connections"), - ) - title.scale(1.5) - title.arrange(RIGHT, buff=MED_LARGE_BUFF) - title.to_edge(UP) - - arrow_words = TextMobject("(in my view)") - arrow_words.set_width(2 * title[1].get_width()) - arrow_words.next_to(title[1], UP, SMALL_BUFF) - - title[0].save_state() - title[0].set_x(0) - - self.add(title[0]) - self.play( - FadeInFromLarge(sd), - title[0].set_color, RED, - ) - title[0].saved_state.set_color(RED) - self.play( - Restore(title[0]), - GrowFromCenter(title[1]), - FadeIn(arrow_words), - FadeInFrom(title[2], LEFT), - LaggedStartMap( - ShowCreation, sd.edges, - run_time=3, - ), - LaggedStartMap( - GrowFromPoint, sd.neighbors, - lambda m: (m, sd.get_center()), - run_time=3, - ), - ) - self.wait() - self.play(*map(Restore, [*dots, *edges])) - self.wait() - - def perform_walk(self): - # dots = self.dots - # edges = self.edges - sd = self.super_dot - - path = VGroup(sd) - random.seed(1) - for x in range(3): - new_choice = None - while new_choice is None or new_choice in path: - new_choice = random.choice(path[0].neighbors) - path.add_to_back(new_choice) - - for d1, d2 in zip(path, path[1:]): - self.play(Flash(d1.get_center())) - self.play( - ShowCreationThenDestruction( - Line( - d1.get_center(), - d2.get_center(), - color=YELLOW, - ) - ) - ) - - self.play(Flash(sd)) - self.play(LaggedStart(*[ - ApplyMethod( - edge.set_stroke, YELLOW, 5, - rate_func=there_and_back, - ) - for edge in sd.edges - ])) - self.wait() - - -class DeadEnds(RandomWalkOfTopics): - CONFIG = { - "n_dots": 20, - "n_edge_range": [2, 4], - "super_dot_n_edges": 2, - "random_seed": 1, - } - - def construct(self): - self.setup_network() - dots = self.dots - edges = self.edges - - self.add(dots, edges) - - VGroup( - edges[3], - edges[4], - edges[7], - edges[10], - edges[12], - edges[15], - edges[27], - edges[30], - edges[33], - ).set_opacity(0) - - # for i, edge in enumerate(edges): - # self.add(Integer(i).move_to(edge)) - - -class Rediscovery(Scene): - def construct(self): - randy = Randolph() - randy.to_edge(DOWN) - randy.shift(2 * RIGHT) - - lightbulb = Lightbulb() - lightbulb.set_stroke(width=4) - lightbulb.scale(1.5) - lightbulb.next_to(randy, UP) - - rings = self.get_rings( - lightbulb.get_center(), - max_radius=10.0, - delta_r=0.1, - ) - - bubble = ThoughtBubble() - bubble.pin_to(randy) - # bubble[-1].set_fill(GREEN_SCREEN, 0.5) - self.play( - randy.change, "pondering", - ShowCreation(bubble), - FadeInFromDown(lightbulb), - ) - self.add(rings, bubble) - self.play( - randy.change, "thinking", - LaggedStartMap( - VFadeInThenOut, - rings, - lag_ratio=0.002, - run_time=3, - ) - ) - self.wait(4) - - def get_rings(self, center, max_radius, delta_r): - radii = np.arange(0, max_radius, delta_r) - rings = VGroup(*[ - Annulus( - inner_radius=r1, - outer_radius=r2, - fill_opacity=0.75 * (1 - fdiv(r1, max_radius)), - fill_color=YELLOW - ) - for r1, r2 in zip(radii, radii[1:]) - ]) - rings.move_to(center) - return rings - - -class BePlayful(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "So be playful!", - target_mode="hooray", - ) - self.change_student_modes("thinking", "hooray", "happy") - self.wait(3) - - -class SpiralsPatronThanks(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "Juan Benet", - "Kurt Dicus", - "Vassili Philippov", - "Burt Humburg", - "Matt Russell", - "Scott Gray", - "soekul", - "Tihan Seale", - "D. Sivakumar", - "Ali Yahya", - "Arthur Zey", - "dave nicponski", - "Joseph Kelly", - "Kaustuv DeBiswas", - "kkm", - "Lambda AI Hardware", - "Lukas Biewald", - "Mark Heising", - "Nicholas Cahill", - "Peter Mcinerney", - "Quantopian", - "Scott Walter, Ph.D.", - "Tauba Auerbach", - "Yana Chernobilsky", - "Yu Jun", - "Jordan Scales", - "Lukas -krtek.net- Novy", - "Andrew Weir", - "Britt Selvitelle", - "Britton Finley", - "David Gow", - "J", - "Jonathan Wilson", - "Joseph John Cox", - "Magnus Dahlström", - "Mattéo Delabre", - "Randy C. Will", - "Ryan Atallah", - "Luc Ritchie", - "1stViewMaths", - "Aidan Shenkman", - "Alex Mijalis", - "Alexis Olson", - "Andreas Benjamin Brössel", - "Andrew Busey", - "Andrew R. Whalley", - "Ankalagon", - "Anthony Turvey", - "Antoine Bruguier", - "Antonio Juarez", - "Arjun Chakroborty", - "Art Ianuzzi", - "Austin Goodman", - "Avi Finkel", - "Awoo", - "Azeem Ansar", - "AZsorcerer", - "Barry Fam", - "Bernd Sing", - "Boris Veselinovich", - "Bradley Pirtle", - "Brian Staroselsky", - "Calvin Lin", - "Charles Southerland", - "Charlie N", - "Chris Connett", - "Christian Kaiser", - "Clark Gaebel", - "Danger Dai", - "Daniel Herrera C", - "Daniel Pang", - "Dave B", - "Dave Kester", - "David B. Hill", - "David Clark", - "DeathByShrimp", - "Delton Ding", - "Dominik Wagner", - "eaglle", - "emptymachine", - "Eric Younge", - "Ero Carrera", - "Eryq Ouithaqueue", - "Federico Lebron", - "Fernando Via Canel", - "Frank R. Brown, Jr.", - "Giovanni Filippi", - "Hal Hildebrand", - "Hause Lin", - "Hitoshi Yamauchi", - "Ivan Sorokin", - "j eduardo perez", - "Jacob Baxter", - "Jacob Harmon", - "Jacob Hartmann", - "Jacob Magnuson", - "Jameel Syed", - "James Stevenson", - "Jason Hise", - "Jeff Linse", - "Jeff Straathof", - "John C. Vesey", - "John Griffith", - "John Haley", - "John V Wertheim", - "Jonathan Eppele", - "Josh Kinnear", - "Joshua Claeys", - "Kai-Siang Ang", - "Kanan Gill", - "Kartik Cating-Subramanian", - "L0j1k", - "Lee Redden", - "Linh Tran", - "Ludwig Schubert", - "Magister Mugit", - "Mark B Bahu", - "Mark Mann", - "Martin Price", - "Mathias Jansson", - "Matt Langford", - "Matt Roveto", - "Matthew Bouchard", - "Matthew Cocke", - "Michael Faust", - "Michael Hardel", - "Michele Donadoni", - "Mirik Gogri", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nero Li", - "Nikita Lesnikov", - "Omar Zrien", - "Owen Campbell-Moore", - "Patrick Lucas", - "Pedro Igor Salomão Budib", - "Peter Ehrnstrom", - "RedAgent14", - "rehmi post", - "Rex Godby", - "Richard Barthel", - "Ripta Pasay", - "Rish Kundalia", - "Roman Sergeychik", - "Roobie", - "Ryan Williams", - "Sebastian Garcia", - "Solara570", - "Steven Siddals", - "Stevie Metke", - "Suthen Thomas", - "Tal Einav", - "Ted Suzman", - "The Responsible One", - "Thomas Tarler", - "Tianyu Ge", - "Tom Fleming", - "Tyler VanValkenburg", - "Valeriy Skobelev", - "Veritasium", - "Vinicius Reis", - "Xuanji Li", - "Yavor Ivanov", - "YinYangBalance.Asia", - ] - } - - -class Thumbnail(SpiralScene): - CONFIG = { - "max_N": 8000, - "just_show": True, - } - - def construct(self): - self.add_dots() - if not self.just_show: - pass - - def add_dots(self): - self.set_scale(scale=1e3) - - p_spiral = self.get_prime_p_spiral(self.max_N) - dots = VGroup(*[ - Dot( - point, - radius=interpolate(0.01, 0.07, min(0.5 * get_norm(point), 1)), - fill_color=TEAL, - # fill_opacity=interpolate(0.5, 1, min(get_norm(point), 1)) - ) - for point in p_spiral.points - ]) - dots.set_fill([TEAL_E, TEAL_A]) - dots.set_stroke(BLACK, 1) - - label = TextMobject( - "($p$, $p$) for all primes $p$,\\\\", - "in polar coordinates", - tex_to_color_map={ - "$p$": YELLOW, - }, - ) - - label.scale(2) - label.set_stroke(BLACK, 10, background=True) - label.add_background_rectangle_to_submobjects() - label.to_corner(DL, MED_LARGE_BUFF) - - self.add(dots) - # self.add(label) - - self.dots = dots diff --git a/from_3b1b/old/tattoo.py b/from_3b1b/old/tattoo.py deleted file mode 100644 index 72c16a8a..00000000 --- a/from_3b1b/old/tattoo.py +++ /dev/null @@ -1,898 +0,0 @@ -from manimlib.imports import * - - -class TrigRepresentationsScene(Scene): - CONFIG = { - "unit_length" : 1.5, - "arc_radius" : 0.5, - "axes_color" : WHITE, - "circle_color" : RED, - "theta_color" : YELLOW, - "theta_height" : 0.3, - "theta_value" : np.pi/5, - "x_line_colors" : MAROON_B, - "y_line_colors" : BLUE, - } - def setup(self): - self.init_axes() - self.init_circle() - self.init_theta_group() - - def init_axes(self): - self.axes = Axes( - unit_size = self.unit_length, - ) - self.axes.set_color(self.axes_color) - self.add(self.axes) - - def init_circle(self): - self.circle = Circle( - radius = self.unit_length, - color = self.circle_color - ) - self.add(self.circle) - - def init_theta_group(self): - self.theta_group = self.get_theta_group() - self.add(self.theta_group) - - def add_trig_lines(self, *funcs, **kwargs): - lines = VGroup(*[ - self.get_trig_line(func, **kwargs) - for func in funcs - ]) - self.add(*lines) - - def get_theta_group(self): - arc = Arc( - self.theta_value, - radius = self.arc_radius, - color = self.theta_color, - ) - theta = TexMobject("\\theta") - theta.shift(1.5*arc.point_from_proportion(0.5)) - theta.set_color(self.theta_color) - theta.set_height(self.theta_height) - line = Line(ORIGIN, self.get_circle_point()) - dot = Dot(line.get_end(), radius = 0.05) - return VGroup(line, arc, theta, dot) - - def get_circle_point(self): - return rotate_vector(self.unit_length*RIGHT, self.theta_value) - - def get_trig_line(self, func_name = "sin", color = None): - assert(func_name in ["sin", "tan", "sec", "cos", "cot", "csc"]) - is_co = func_name in ["cos", "cot", "csc"] - if color is None: - if is_co: - color = self.y_line_colors - else: - color = self.x_line_colors - - #Establish start point - if func_name in ["sin", "cos", "tan", "cot"]: - start_point = self.get_circle_point() - else: - start_point = ORIGIN - - #Establish end point - if func_name is "sin": - end_point = start_point[0]*RIGHT - elif func_name is "cos": - end_point = start_point[1]*UP - elif func_name in ["tan", "sec"]: - end_point = (1./np.cos(self.theta_value))*self.unit_length*RIGHT - elif func_name in ["cot", "csc"]: - end_point = (1./np.sin(self.theta_value))*self.unit_length*UP - return Line(start_point, end_point, color = color) - -class Introduce(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Something different today!", - target_mode = "hooray", - run_time = 2 - ) - self.change_student_modes("thinking", "happy", "sassy") - self.random_blink(2) - -class ReactionsToTattoo(PiCreatureScene): - def construct(self): - modes = [ - "horrified", - "hesitant", - "pondering", - "thinking", - "sassy", - ] - tattoo_on_math = TextMobject("Tattoo on \\\\ math") - tattoo_on_math.to_edge(UP) - self.wait(2) - for mode in modes: - self.play( - self.pi_creature.change_mode, mode, - self.pi_creature.look, UP+RIGHT - ) - self.wait(2) - self.play( - Write(tattoo_on_math), - self.pi_creature.change_mode, "hooray", - self.pi_creature.look, UP - ) - self.wait() - self.change_mode("happy") - self.wait(2) - - - def create_pi_creature(self): - randy = Randolph() - randy.next_to(ORIGIN, DOWN) - return randy - -class IntroduceCSC(TrigRepresentationsScene): - def construct(self): - self.clear() - Cam_S_C = TextMobject("Cam", "S.", "C.") - CSC = TextMobject("C", "S", "C", arg_separator = "") - csc_of_theta = TextMobject("c", "s", "c", "(\\theta)", arg_separator = "") - csc, of_theta = VGroup(*csc_of_theta[:3]), csc_of_theta[-1] - of_theta[1].set_color(YELLOW) - CSC.move_to(csc, DOWN) - - csc_line = self.get_trig_line("csc") - csc_line.set_stroke(width = 8) - cot_line = self.get_trig_line("cot") - cot_line.set_color(WHITE) - brace = Brace(csc_line, LEFT) - - self.play(Write(Cam_S_C)) - self.wait() - self.play(Transform(Cam_S_C, CSC)) - self.wait() - self.play(Transform(Cam_S_C, csc)) - self.remove(Cam_S_C) - self.add(csc) - self.play(Write(of_theta)) - self.wait(2) - - csc_of_theta.add_to_back(BackgroundRectangle(csc)) - self.play( - ShowCreation(self.axes), - ShowCreation(self.circle), - GrowFromCenter(brace), - csc_of_theta.rotate, np.pi/2, - csc_of_theta.next_to, brace, LEFT, - path_arc = np.pi/2, - ) - self.play(Write(self.theta_group, run_time = 1)) - self.play(ShowCreation(cot_line)) - self.play( - ShowCreation(csc_line), - csc.set_color, csc_line.get_color(), - ) - self.wait(3) - -class TeachObscureTrigFunctions(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "$\\sec(\\theta)$, ", - "$\\csc(\\theta)$, ", - "$\\cot(\\theta)$", - ) - content = self.teacher.bubble.content.copy() - self.change_student_modes(*["confused"]*3) - self.student_says( - "But why?", - target_mode = "pleading", - added_anims = [content.to_corner, UP+RIGHT] - ) - self.wait() - self.play(self.get_teacher().change_mode, "pondering") - self.wait(3) - -class CanYouExplainTheTattoo(TeacherStudentsScene): - def construct(self): - self.student_says(""" - Wait, can you explain - the actual tattoo here? - """) - self.random_blink() - self.play(self.get_teacher().change_mode, "hooray") - self.wait() - -class ExplainTrigFunctionDistances(TrigRepresentationsScene, PiCreatureScene): - CONFIG = { - "use_morty" : False, - "alt_theta_val" : 2*np.pi/5, - } - def setup(self): - PiCreatureScene.setup(self) - TrigRepresentationsScene.setup(self) - - def construct(self): - self.introduce_angle() - self.show_sine_and_cosine() - self.show_tangent_and_cotangent() - self.show_secant_and_cosecant() - self.explain_cosecant() - self.summarize_full_group() - - def introduce_angle(self): - self.remove(self.circle) - self.remove(self.theta_group) - line, arc, theta, dot = self.theta_group - line.rotate(-self.theta_value) - brace = Brace(line, UP, buff = SMALL_BUFF) - one = brace.get_text("1", buff = SMALL_BUFF) - VGroup(line, brace, one).rotate(self.theta_value) - one.rotate_in_place(-self.theta_value) - self.circle.rotate(self.theta_value) - - words = TextMobject("Corresponding point") - words.next_to(dot, UP+RIGHT, buff = 1.5*LARGE_BUFF) - words.shift_onto_screen() - arrow = Arrow(words.get_bottom(), dot, buff = SMALL_BUFF) - - self.play( - ShowCreation(line), - ShowCreation(arc), - ) - self.play(Write(theta)) - self.play(self.pi_creature.change_mode, "pondering") - self.play( - ShowCreation(self.circle), - Rotating(line, rate_func = smooth, in_place = False), - run_time = 2 - ) - self.play( - Write(words), - ShowCreation(arrow), - ShowCreation(dot) - ) - self.wait() - self.play( - GrowFromCenter(brace), - Write(one) - ) - self.wait(2) - self.play(*list(map(FadeOut, [ - words, arrow, brace, one - ]))) - self.radial_line_label = VGroup(brace, one) - - def show_sine_and_cosine(self): - sin_line, sin_brace, sin_text = sin_group = self.get_line_brace_text("sin") - cos_line, cos_brace, cos_text = cos_group = self.get_line_brace_text("cos") - - self.play(ShowCreation(sin_line)) - self.play( - GrowFromCenter(sin_brace), - Write(sin_text), - ) - self.play(self.pi_creature.change_mode, "happy") - self.play(ShowCreation(cos_line)) - self.play( - GrowFromCenter(cos_brace), - Write(cos_text), - ) - self.wait() - self.change_mode("well") - - mover = VGroup( - sin_group, - cos_group, - self.theta_group, - ) - thetas = np.linspace(self.theta_value, self.alt_theta_val, 100) - targets = [] - for theta in thetas: - self.theta_value = theta - targets.append(VGroup( - self.get_line_brace_text("sin"), - self.get_line_brace_text("cos"), - self.get_theta_group() - )) - self.play(Succession( - *[ - Transform(mover, target, rate_func=linear) - for target in targets - ], - run_time = 5, - rate_func = there_and_back - )) - self.theta_value = thetas[0] - - self.change_mode("happy") - self.wait() - self.sin_group, self.cos_group = sin_group, cos_group - - def show_tangent_and_cotangent(self): - tan_group = self.get_line_brace_text("tan") - cot_group = self.get_line_brace_text("cot") - tan_text = tan_group[-1] - cot_text = cot_group[-1] - line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - line.rotate(self.theta_value) - line.move_to(self.theta_group[-1]) - line.set_stroke(width = 2) - - sin_tex = "{\\sin(\\theta)}" - cos_tex = "{\\cos(\\theta)}" - tan_frac = TexMobject("= \\frac" + sin_tex + cos_tex) - cot_frac = TexMobject("= \\frac" + cos_tex + sin_tex) - tan_frac.to_corner(UP+LEFT) - tan_frac.shift(2*RIGHT) - cot_frac.next_to(tan_frac, DOWN) - - - self.change_mode("pondering") - for frac, text in (tan_frac, tan_text), (cot_frac, cot_text): - VGroup(frac[5], frac[-2]).set_color(YELLOW) - frac.scale_in_place(0.7) - text.save_state() - text.next_to(frac, LEFT) - self.play(Write(VGroup(text, frac))) - self.wait() - self.change_mode("confused") - self.wait() - self.play(*list(map(FadeOut, [ - tan_frac, cot_frac, self.sin_group, self.cos_group - ]))) - self.wait() - - self.play( - self.theta_group[-1].set_color, YELLOW, - ShowCreation(line), - self.pi_creature.change_mode, 'pondering' - ) - small_lines = VGroup() - for group in tan_group, cot_group: - small_line, brace, text = group - self.play( - ShowCreation(small_line), - GrowFromCenter(brace), - text.restore, - ) - self.wait() - small_lines.add(small_line) - self.play(FadeOut(line), Animation(small_lines)) - - mover = VGroup( - tan_group, - cot_group, - self.theta_group, - ) - thetas = np.linspace(self.theta_value, self.alt_theta_val, 100) - targets = [] - for theta in thetas: - self.theta_value = theta - targets.append(VGroup( - self.get_line_brace_text("tan"), - self.get_line_brace_text("cot"), - self.get_theta_group() - )) - self.play(Succession( - *[ - Transform(mover, target, rate_func=linear) - for target in targets - ], - run_time = 5, - rate_func = there_and_back - )) - self.theta_value = thetas[0] - - self.change_mode("happy") - self.wait(2) - - self.tangent_line = self.get_tangent_line() - self.add(self.tangent_line) - self.play(*it.chain(*[ - list(map(FadeOut, [tan_group, cot_group])), - [Animation(self.theta_group[-1])] - ])) - - def show_secant_and_cosecant(self): - sec_group = self.get_line_brace_text("sec") - csc_group = self.get_line_brace_text("csc") - sec_line, sec_brace, sec_text = sec_group - csc_line, csc_brace, csc_text = csc_group - - sec_frac = TexMobject("= \\frac{1}{\\cos(\\theta)}") - sec_frac.to_corner(UP+LEFT).shift(2*RIGHT) - csc_frac = TexMobject("= \\frac{1}{\\sin(\\theta)}") - csc_frac.next_to(sec_frac, DOWN) - - sec_dot, csc_dot = [ - Dot(line.get_end(), color = line.get_color()) - for line in (sec_line, csc_line) - ] - sec_group.add(sec_dot) - csc_group.add(csc_dot) - - for text, frac in (sec_text, sec_frac), (csc_text, csc_frac): - frac[-2].set_color(YELLOW) - frac.scale_in_place(0.7) - text.save_state() - text.next_to(frac, LEFT) - frac.add_to_back(text.copy()) - self.play( - Write(frac), - self.pi_creature.change_mode, "erm" - ) - self.wait() - self.wait() - for group in sec_group, csc_group: - line, brace, text, dot = group - dot.save_state() - dot.move_to(text) - dot.set_fill(opacity = 0) - self.play(dot.restore) - self.wait() - self.play( - ShowCreation(line), - GrowFromCenter(brace), - text.restore, - self.pi_creature.change_mode, "pondering" - ) - self.wait() - - mover = VGroup( - sec_group, - csc_group, - self.theta_group, - self.tangent_line, - ) - thetas = np.linspace(self.theta_value, self.alt_theta_val, 100) - targets = [] - for theta in thetas: - self.theta_value = theta - new_sec_group = self.get_line_brace_text("sec") - new_csc_group = self.get_line_brace_text("csc") - for group in new_sec_group, new_csc_group: - line = group[0] - group.add( - Dot(line.get_end(), color = line.get_color()) - ) - targets.append(VGroup( - new_sec_group, - new_csc_group, - self.get_theta_group(), - self.get_tangent_line(), - )) - self.play(Succession( - *[ - Transform(mover, target, rate_func=linear) - for target in targets - ], - run_time = 5, - rate_func = there_and_back - )) - self.theta_value = thetas[0] - - self.change_mode("confused") - self.wait(2) - - self.play(*list(map(FadeOut, [ - sec_group, sec_frac - ]))) - self.csc_group = csc_group - self.csc_frac =csc_frac - - def explain_cosecant(self): - sin_group = self.get_line_brace_text("sin") - sin_line, sin_brace, sin_text = sin_group - csc_line, csc_brace, csc_text, csc_dot = self.csc_group - csc_subgroup = VGroup(csc_brace, csc_text) - - arc_theta = VGroup(*self.theta_group[1:3]).copy() - arc_theta.rotate(-np.pi/2) - arc_theta.shift(csc_line.get_end()) - arc_theta[1].rotate_in_place(np.pi/2) - - radial_line = self.theta_group[0] - - tri1 = Polygon( - ORIGIN, radial_line.get_end(), sin_line.get_end(), - color = GREEN, - stroke_width = 8, - ) - tri2 = Polygon( - csc_line.get_end(), ORIGIN, radial_line.get_end(), - color = GREEN, - stroke_width = 8, - ) - - opp_over_hyp = TexMobject( - "\\frac{\\text{Opposite}}{\\text{Hypotenuse}} =" - ) - frac1 = TexMobject("\\frac{\\sin(\\theta)}{1}") - frac1.next_to(opp_over_hyp) - frac1[-4].set_color(YELLOW) - frac2 = TexMobject("= \\frac{1}{\\csc(\\theta)}") - frac2.next_to(frac1) - frac2[-2].set_color(YELLOW) - frac_group = VGroup(opp_over_hyp, frac1, frac2) - frac_group.set_width(FRAME_X_RADIUS-1) - frac_group.next_to(ORIGIN, RIGHT).to_edge(UP) - - question = TextMobject("Why is this $\\theta$?") - question.set_color(YELLOW) - question.to_corner(UP+RIGHT) - arrow = Arrow(question.get_bottom(), arc_theta) - - one_brace, one = self.radial_line_label - one.move_to(one_brace.get_center_of_mass()) - - self.play(ShowCreation(tri1)) - self.play( - ApplyMethod(tri1.rotate_in_place, np.pi/12, rate_func = wiggle), - self.pi_creature.change_mode, "thinking" - ) - self.wait() - tri1.save_state() - self.play(Transform(tri1, tri2, path_arc = np.pi/2)) - self.play(Write(arc_theta)) - self.play(ApplyMethod( - tri1.rotate_in_place, np.pi/12, - rate_func = wiggle - )) - self.wait(2) - self.play( - Write(question), - ShowCreation(arrow), - self.pi_creature.change_mode, "confused" - ) - self.wait(2) - self.play(*list(map(FadeOut, [question, arrow]))) - - - self.play(Write(opp_over_hyp)) - self.wait() - csc_subgroup.save_state() - self.play( - tri1.restore, - csc_subgroup.fade, 0.7 - ) - self.play( - ShowCreation(sin_line), - GrowFromCenter(sin_brace), - Write(sin_text) - ) - self.wait() - self.play(Write(one)) - self.wait() - self.play(Write(frac1)) - self.wait() - self.play( - Transform(tri1, tri2), - FadeOut(sin_group) - ) - self.play( - radial_line.rotate_in_place, np.pi/12, - rate_func = wiggle - ) - self.wait() - self.play(csc_subgroup.restore) - self.wait() - self.play(Write(frac2)) - self.change_mode("happy") - self.play(FadeOut(opp_over_hyp)) - self.reciprocate(frac1, frac2) - self.play(*list(map(FadeOut, [ - one, self.csc_group, tri1, - frac1, frac2, self.csc_frac, - arc_theta - ]))) - - def reciprocate(self, frac1, frac2): - # Not general, meant only for these definitions: - # frac1 = TexMobject("\\frac{\\sin(\\theta)}{1}") - # frac2 = TexMobject("= \\frac{1}{\\csc(\\theta)}") - num1 = VGroup(*frac1[:6]) - dem1 = frac1[-1] - num2 = frac2[1] - dem2 = VGroup(*frac2[-6:]) - group = VGroup(frac1, frac2) - - self.play( - group.scale, 1/0.7, - group.to_corner, UP+RIGHT, - ) - self.play( - num1.move_to, dem1, - dem1.move_to, num1, - num2.move_to, dem2, - dem2.move_to, num2, - path_arc = np.pi - ) - self.wait() - self.play( - dem2.move_to, frac2[2], - VGroup(*frac2[1:3]).set_fill, BLACK, 0 - ) - self.wait() - - def summarize_full_group(self): - scale_factor = 1.5 - theta_subgroup = VGroup(self.theta_group[0], self.theta_group[-1]) - self.play(*it.chain(*[ - [mob.scale, scale_factor] - for mob in [ - self.circle, self.axes, - theta_subgroup, self.tangent_line - ] - ])) - self.unit_length *= scale_factor - - to_fade = VGroup() - for func_name in ["sin", "tan", "sec", "cos", "cot", "csc"]: - line, brace, text = self.get_line_brace_text(func_name) - if func_name in ["sin", "cos"]: - angle = line.get_angle() - if np.cos(angle) < 0: - angle += np.pi - if func_name is "sin": - target = line.get_center()+0.2*LEFT+0.1*DOWN - else: - target = VGroup(brace, line).get_center_of_mass() - text.scale(0.75) - text.rotate(angle) - text.move_to(target) - line.set_stroke(width = 6) - self.play( - ShowCreation(line), - Write(text, run_time = 1) - ) - else: - self.play( - ShowCreation(line), - GrowFromCenter(brace), - Write(text, run_time = 1) - ) - if func_name in ["sec", "csc", "cot"]: - to_fade.add(*self.get_mobjects_from_last_animation()) - if func_name is "sec": - self.wait() - self.wait() - self.change_mode("surprised") - self.wait(2) - self.remove(self.tangent_line) - self.play( - FadeOut(to_fade), - self.pi_creature.change_mode, "sassy" - ) - self.wait(2) - - def get_line_brace_text(self, func_name = "sin"): - line = self.get_trig_line(func_name) - angle = line.get_angle() - vect = rotate_vector(UP, angle) - vect = np.round(vect, 1) - if (vect[1] < 0) ^ (func_name is "sec"): - vect = -vect - angle += np.pi - brace = Brace( - Line( - line.get_length()*LEFT/2, - line.get_length()*RIGHT/2, - ), - UP - ) - brace.rotate(angle) - brace.shift(line.get_center()) - brace.set_color(line.get_color()) - text = TexMobject("\\%s(\\theta)"%func_name) - text.scale(0.75) - text[-2].set_color(self.theta_color) - text.add_background_rectangle() - text.next_to(brace.get_center_of_mass(), vect, buff = 1.2*MED_SMALL_BUFF) - return VGroup(line, brace, text) - - def get_tangent_line(self): - return Line( - self.unit_length*(1./np.sin(self.theta_value))*UP, - self.unit_length*(1./np.cos(self.theta_value))*RIGHT, - color = GREY - ) - -class RenameAllInTermsOfSine(Scene): - def construct(self): - texs = [ - "\\sin(\\theta)", - "\\cos(\\theta)", - "\\tan(\\theta)", - "\\csc(\\theta)", - "\\sec(\\theta)", - "\\cot(\\theta)", - ] - shift_vals = [ - 4*LEFT+3*UP, - 4*LEFT+UP, - 4*LEFT+DOWN, - 4*RIGHT+3*UP, - 4*RIGHT+UP, - 4*RIGHT+DOWN, - ] - equivs = [ - "", - "= \\sin(90^\\circ - \\theta)", - "= \\frac{\\sin(\\theta)}{\\sin(90^\\circ - \\theta)}", - "= \\frac{1}{\\sin(\\theta)}", - "= \\frac{1}{\\sin(90^\\circ - \\theta)}", - "= \\frac{\\sin(90^\\circ - \\theta)}{\\sin(\\theta)}", - ] - mobs = VGroup(*list(map(TexMobject, texs))) - sin, cos, tan = mobs[:3] - sin.target_color = YELLOW - cos.target_color = GREEN - tan.target_color = RED - - rhs_mobs = VGroup(*list(map(TexMobject, equivs))) - rhs_mobs.submobjects[0] = VectorizedPoint() - for mob, shift_val in zip(mobs, shift_vals): - mob.shift(shift_val) - self.play(Write(mobs)) - self.wait() - for mob, rhs_mob in zip(mobs, rhs_mobs): - rhs_mob.next_to(mob) - rhs_mob.set_color(sin.target_color) - mob.save_state() - mob.generate_target() - VGroup(mob.target, rhs_mob).move_to(mob) - sin.target.set_color(sin.target_color) - self.play(*it.chain(*[ - list(map(MoveToTarget, mobs)), - [Write(rhs_mobs)] - ])) - self.wait(2) - - anims = [] - for mob, rhs_mob in list(zip(mobs, rhs_mobs))[1:3]: - anims += [ - FadeOut(rhs_mob), - mob.restore, - mob.set_color, mob.target_color, - ] - self.play(*anims) - self.wait() - - new_rhs_mobs = [ - TexMobject("=\\frac{1}{\\%s(\\theta)}"%s).set_color(color) - for s, color in [ - ("cos", cos.target_color), - ("tan", tan.target_color), - ] - ] - anims = [] - for mob, rhs, new_rhs in zip(mobs[-2:], rhs_mobs[-2:], new_rhs_mobs): - new_rhs.next_to(mob) - VGroup(mob.target, new_rhs).move_to( - VGroup(mob, rhs) - ) - anims += [ - MoveToTarget(mob), - Transform(rhs, new_rhs) - ] - self.play(*anims) - self.wait(2) - -class MisMatchOfCoPrefix(TeacherStudentsScene): - def construct(self): - eq1 = TexMobject( - "\\text{secant}(\\theta) = \\frac{1}{\\text{cosine}(\\theta)}" - ) - eq2 = TexMobject( - "\\text{cosecant}(\\theta) = \\frac{1}{\\text{sine}(\\theta)}" - ) - eq1.to_corner(UP+LEFT) - eq1.to_edge(LEFT) - eq2.next_to(eq1, DOWN, buff = LARGE_BUFF) - eqs = VGroup(eq1, eq2) - - self.play( - self.get_teacher().change_mode, "speaking", - Write(eqs), - *[ - ApplyMethod(pi.look_at, eqs) - for pi in self.get_students() - ] - ) - self.random_blink() - self.play( - VGroup(*eq1[-9:-7]).set_color, YELLOW, - VGroup(*eq2[:2]).set_color, YELLOW, - *[ - ApplyMethod(pi.change_mode, "confused") - for pi in self.get_students() - ] - ) - self.random_blink(2) - -class Credit(Scene): - def construct(self): - morty = Mortimer() - morty.next_to(ORIGIN, DOWN) - morty.to_edge(RIGHT) - - headphones = Headphones(height = 1) - headphones.move_to(morty.eyes, aligned_edge = DOWN) - headphones.shift(0.1*DOWN) - - url = TextMobject("www.audibletrial.com/3blue1brown") - url.scale(0.8) - url.to_corner(UP+RIGHT, buff = LARGE_BUFF) - - book = ImageMobject("zen_and_motorcycles") - book.set_height(5) - book.to_edge(DOWN, buff = LARGE_BUFF) - border = Rectangle(color = WHITE) - border.replace(book, stretch = True) - - self.play(PiCreatureSays( - morty, "Book recommendation!", - target_mode = "surprised" - )) - self.play(Blink(morty)) - self.play( - FadeIn(headphones), - morty.change_mode, "thinking", - FadeOut(morty.bubble), - FadeOut(morty.bubble.content), - ) - self.play(Write(url)) - self.play(morty.change_mode, "happy") - self.wait(2) - self.play(Blink(morty)) - self.wait(2) - self.play( - morty.change_mode, "raise_right_hand", - morty.look_at, url - ) - self.wait(2) - self.play( - morty.change_mode, "happy", - morty.look_at, book - ) - self.play(FadeIn(book)) - self.play(ShowCreation(border)) - self.wait(2) - self.play(Blink(morty)) - self.wait() - self.play( - morty.change_mode, "thinking", - morty.look_at, book - ) - self.wait(2) - self.play(Blink(morty)) - self.wait(4) - self.play(Blink(morty)) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/tau_poem.py b/from_3b1b/old/tau_poem.py deleted file mode 100644 index c0008df7..00000000 --- a/from_3b1b/old/tau_poem.py +++ /dev/null @@ -1,587 +0,0 @@ -#!/usr/bin/env python - - -import numpy as np -import itertools as it -from copy import deepcopy -import sys - -from manimlib.imports import * -from script_wrapper import command_line_create_scene -from .generate_logo import LogoGeneration - -POEM_LINES = """Fixed poorly in notation with that two, -you shine so loud that you deserve a name. -Late though we are to make a change, it's true, -We can extol you 'til you have pi's fame. -One might object, ``Conventions matter not! -Great formulae cast truths transcending names.'' -I've noticed, though, how language molds my thoughts; -the natural terms make heart and head the same. -So lose the two inside your autograph, -then guide our thoughts without your ``better'' half. -Wonders math imparts become so neat -when phrased with you, and pi remains off-screen. -Sine and exp both cycle to your beat. -Jive with Fourier, and forms are clean. -``Wait! Area of circles'', pi would say, -``sticks oddly to one half when tau's preferred.'' -More to you then! For write it in this way, -then links to triangles can be inferred. -Nix pi, then all within geometry -shines clean and clear, as if by poetry.""".split("\n") - -DIGIT_TO_WORD = { - '0' : "Zero", - '1' : "One", - '2' : "Two", - '3' : "Three", - '4' : "Four", - '5' : "Five", - '6' : "Six", - '7' : "Seven", - '8' : "Eight", - '9' : "Nine", -} - -FORMULAE = [ - "e^{x + \\tau i} = e^{x}", - "&\\Leftrightarrow", - "e^{x + 2\\pi i} = e^{x} \\\\", - "A = \\frac{1}{2} \\tau r^2", - "&\\Leftrightarrow", - "A = \\pi r^2 \\\\", - "n! \\sim \\sqrt{\\tau n}\\left(\\frac{n}{e}\\right)^n", - "&\\Leftrightarrow", - "n! \\sim \\sqrt{2\\pi n}\\left(\\frac{n}{e}\\right)^n \\\\", - # "\\sum_{n = 0}^\\infty \\frac{(-1)^n}{2n+1} = \\frac{\\tau}{8}", - # "&\\Leftrightarrow", - # "\\sum_{n = 0}^\\infty \\frac{(-1)^n}{2n+1} = \\frac{\\pi}{4} \\\\", -] - -DIGITS = list(map(str, list("62831853071795864769"))) -DIGITS[1] = "." + DIGITS[1] #2->.2 - -BUFF = 1.0 - -MOVIE_PREFIX = "tau_poem/" - -class Welcome(LogoGeneration): - def construct(self): - text = "Happy $\\tau$ Day, from 3Blue1Brown!" - self.add(TextMobject(text).to_edge(UP)) - LogoGeneration.construct(self) - -class HappyTauDayWords(Scene): - def construct(self): - words = TextMobject("Happy Tau Day Everybody!").scale(2) - tau = TauCreature().move_to(2*LEFT + UP) - pi = PiCreature().move_to(2*RIGHT + 3*DOWN) - pi.set_color("red") - self.add(words, tau, pi) - self.wait() - self.play(BlinkPiCreature(tau)) - self.play(BlinkPiCreature(pi)) - -class TauPoem(Scene): - args_list = [(x,) for x in range(len(POEM_LINES))] - @staticmethod - def args_to_string(line_num, *ignore): - return str(line_num) - - def __init__(self, line_num, *args, **kwargs): - self.line_num = line_num - self.anim_kwargs = { - "run_time" : 4.0, - } - self.line_num_to_method = { - 0 : self.line0, - 1 : self.line1, - 2 : self.line2, - 3 : self.line3, - 4 : self.line4, - 5 : self.line5, - 6 : self.line6, - 7 : self.line7, - 8 : self.line8, - 9 : self.line9, - 10 : self.line10, - 11 : self.line11, - 12 : self.line12, - 13 : self.line13, - 14 : self.line14, - 15 : self.line15, - 16 : self.line16, - 17 : self.line17, - 18 : self.line18, - 19 : self.line19, - } - Scene.__init__(self, *args, **kwargs) - - def construct(self): - self.add_line_and_number() - self.line_num_to_method[self.line_num]() - self.first_word_to_last_digit() - - def add_line_and_number(self): - self.first_digits, new_digit, last_digits = TexMobject([ - "".join(DIGITS[:self.line_num]), - DIGITS[self.line_num], - "".join(DIGITS[(self.line_num+1):]), - ]).to_edge(UP, buff=0.2).split() - line_str = POEM_LINES[self.line_num] - if self.line_num == 0: - index = line_str.index("ed ") - elif self.line_num == 10: - index = line_str.index("ders") - else: - index = line_str.index(" ") - first_word, rest_of_line = TextMobject( - [line_str[:index], line_str[index:]] - ).to_edge(UP).shift(BUFF*DOWN).split() - first_word.shift(0.15*RIGHT) #Stupid - number_word = TextMobject(DIGIT_TO_WORD[DIGITS[self.line_num][-1]]) - number_word.shift(first_word.get_center()) - number_word.shift(BUFF * UP / 2) - - kwargs = { - "rate_func" : squish_rate_func(smooth), - } - self.add(first_word, rest_of_line, self.first_digits) - self.first_word = first_word - self.number_word = number_word - self.new_digit = new_digit - - def first_word_to_last_digit(self): - if self.line_num == 19: - shift_val = FRAME_Y_RADIUS*DOWN - self.new_digit.shift(shift_val) - self.play(ApplyMethod( - self.first_digits.shift, shift_val, run_time = 2.0 - )) - self.wait(2) - self.play_over_time_range(0, 2, - Transform( - deepcopy(self.first_word), self.number_word, - rate_func = squish_rate_func(smooth) - ) - ) - self.play_over_time_range(2, 4, - Transform( - self.number_word, self.new_digit, - rate_func = squish_rate_func(smooth) - ) - ) - - def line0(self): - two, pi = TexMobject(["2", "\\pi"]).scale(2).split() - self.add(two, pi) - two_copy = deepcopy(two).rotate(np.pi/10).set_color("yellow") - self.play(Transform( - two, two_copy, - rate_func = squish_rate_func( - lambda t : wiggle(t), - 0.4, 0.9, - ), - **self.anim_kwargs - )) - - def line1(self): - two_pi = TexMobject(["2", "\\pi"]).scale(2) - tau = TauCreature() - tau.to_symbol() - sphere = Mobject() - sphere.interpolate( - two_pi, - Sphere().set_color("yellow"), - 0.8 - ) - self.add(two_pi) - self.wait() - self.play(CounterclockwiseTransform( - two_pi, sphere, - rate_func = lambda t : 2*smooth(t/2) - )) - self.remove(two_pi) - self.play(CounterclockwiseTransform( - sphere, tau, - rate_func = lambda t : 2*(smooth(t/2+0.5)-0.5) - )) - self.remove(sphere) - self.add(tau) - self.wait() - - def line2(self): - tau = TauCreature() - tau.make_sad() - tau.mouth.points = np.array(sorted( - tau.mouth.points, - lambda p0, p1 : cmp(p0[0], p1[0]) - )) - blinked = deepcopy(tau).blink() - for eye in blinked.eyes: - eye.set_color("black") - self.add(*set(tau.parts).difference(tau.white_parts)) - self.play(*[ - Transform(*eyes) - for eyes in zip(blinked.eyes, tau.eyes) - ]) - self.play(ShowCreation(tau.mouth)) - self.wait(2) - - def line3(self): - tau = TauCreature().make_sad() - pi = PiCreature() - self.add(*tau.parts) - self.wait() - self.play( - Transform(tau.leg, pi.left_leg), - ShowCreation(pi.right_leg), - run_time = 1.0, - ) - self.play(*[ - Transform(*parts) - for parts in zip(tau.white_parts, pi.white_parts) - ]) - self.remove(*tau.parts + pi.parts) - self.play(BlinkPiCreature(pi)) - - def pi_speaking(self, text): - pi = PiCreature() - pi.set_color("red").give_straight_face() - pi.shift(3*DOWN + LEFT) - bubble = SpeechBubble().speak_from(pi) - bubble.write(text) - return pi, bubble - - def line4(self): - pi, bubble = self.pi_speaking("Conventions matter \\\\ not!") - self.add(pi) - self.wait() - self.play(Transform( - Point(bubble.tip).set_color("black"), - bubble - )) - - - def line5(self): - pi, bubble = self.pi_speaking(""" - Great formulae cast \\\\ - truths transcending \\\\ - names. - """) - self.add(pi, bubble) - - formulae = TexMobject(FORMULAE, size = "\\small") - formulae.scale(1.25) - formulae.to_corner([1, -1, 0]) - self.play(FadeIn(formulae)) - - def line6(self): - bubble = ThoughtBubble() - self.play(ApplyFunction( - lambda p : 2 * p / get_norm(p), - bubble, - rate_func = wiggle, - run_time = 3.0, - )) - - def line7(self): - bubble = ThoughtBubble() - heart = ImageMobject("heart") - heart.scale(0.5).shift(DOWN).set_color("red") - for mob in bubble, heart: - mob.sort_points(get_norm) - - self.add(bubble) - self.wait() - self.remove(bubble) - bubble_copy = deepcopy(bubble) - self.play(CounterclockwiseTransform(bubble_copy, heart)) - self.wait() - self.remove(bubble_copy) - self.play(CounterclockwiseTransform(heart, bubble)) - self.wait() - - - def line8(self): - pi = PiCreature().give_straight_face() - tau = TauCreature() - two = ImageMobject("big2").scale(0.5).shift(1.6*LEFT + 0.1*DOWN) - - self.add(two, *pi.parts) - self.wait() - self.play( - Transform(pi.left_leg, tau.leg), - Transform( - pi.right_leg, - Point(pi.right_leg.points[0,:]).set_color("black") - ), - Transform(pi.mouth, tau.mouth), - CounterclockwiseTransform( - two, - Dot(two.get_center()).set_color("black") - ) - ) - - def line9(self): - tau = TauCreature() - pi = PiCreature().set_color("red").give_straight_face() - pi.scale(0.2).move_to(tau.arm.points[-1,:]) - point = Point(pi.get_center()).set_color("black") - vanish_local = 3*(LEFT + UP) - new_pi = deepcopy(pi) - new_pi.scale(0.01) - new_pi.rotate(np.pi) - new_pi.shift(vanish_local) - Mobject.set_color(new_pi, "black") - - self.add(tau) - self.wait() - self.play(Transform(point, pi)) - self.remove(point) - self.add(pi) - self.play(WaveArm(tau),Transform(pi, new_pi)) - - def line10(self): - formulae = TexMobject(FORMULAE, "\\small") - formulae.scale(1.5).to_edge(DOWN) - self.add(formulae) - - def line11(self): - formulae = TexMobject(FORMULAE, "\\small") - formulae.scale(1.5).to_edge(DOWN) - formulae = formulae.split() - f_copy = deepcopy(formulae) - for mob, count in zip(f_copy, it.count()): - if count%3 == 0: - mob.to_edge(LEFT).shift(RIGHT*(FRAME_X_RADIUS-1)) - else: - mob.shift(FRAME_WIDTH*RIGHT) - self.play(*[ - Transform(*mobs, run_time = 2.0) - for mobs in zip(formulae, f_copy) - ]) - - def line12(self): - interval_size = 0.5 - axes_center = FRAME_X_RADIUS*LEFT/2 - grid_center = FRAME_X_RADIUS*RIGHT/2 - radius = FRAME_X_RADIUS / 2.0 - axes = Axes( - radius = radius, - interval_size = interval_size - ) - axes.shift(axes_center) - def sine_curve(t): - t += 1 - result = np.array((-np.pi*t, np.sin(np.pi*t), 0)) - result *= interval_size - result += axes_center - return result - sine = ParametricFunction(sine_curve) - sine_period = Line( - axes_center, - axes_center + 2*np.pi*interval_size*RIGHT - ) - grid = Grid(radius = radius).shift(grid_center) - circle = Circle().scale(interval_size).shift(grid_center) - grid.add(TexMobject("e^{ix}").shift(grid_center+UP+RIGHT)) - circle.set_color("white") - tau_line = Line( - *[np.pi*interval_size*vect for vect in (LEFT, RIGHT)], - density = 5*DEFAULT_POINT_DENSITY_1D - ) - tau_line.set_color("red") - tau = TexMobject("\\tau") - tau.shift(tau_line.get_center() + 0.5*UP) - - self.add(axes, grid) - self.play( - TransformAnimations( - ShowCreation(sine), - ShowCreation(deepcopy(sine).shift(2*np.pi*interval_size*RIGHT)), - run_time = 2.0, - rate_func = smooth - ), - ShowCreation(circle) - ) - self.play( - CounterclockwiseTransform(sine_period, tau_line), - CounterclockwiseTransform(circle, deepcopy(tau_line)), - FadeOut(axes), - FadeOut(grid), - FadeOut(sine), - FadeIn(tau), - ) - self.wait() - - - def line13(self): - formula = form_start, two_pi, form_end = TexMobject([ - "\\hat{f^{(n)}}(\\xi) = (", - "2\\pi", - "i\\xi)^n \\hat{f}(\\xi)" - ]).shift(DOWN).split() - tau = TauCreature().center() - tau.scale(two_pi.get_width()/tau.get_width()) - tau.shift(two_pi.get_center()+0.2*UP) - tau.rewire_part_attributes() - - self.add(*formula) - self.wait() - self.play(CounterclockwiseTransform(two_pi, tau)) - self.remove(two_pi) - self.play(BlinkPiCreature(tau)) - self.wait() - - def line14(self): - pi, bubble = self.pi_speaking( - "Wait! Area \\\\ of circles" - ) - self.add(pi) - self.play( - Transform(Point(bubble.tip).set_color("black"), bubble) - ) - - def line15(self): - pi, bubble = self.pi_speaking( - "sticks oddly \\\\ to one half when \\\\ tau's preferred." - ) - formula = form_start, half, form_end = TexMobject([ - "A = ", - "\\frac{1}{2}", - "\\tau r^2" - ]).split() - - self.add(pi, bubble, *formula) - self.wait(2) - self.play(ApplyMethod(half.set_color, "yellow")) - - def line16(self): - self.add(TexMobject( - "\\frac{1}{2}\\tau r^2" - ).scale(2).shift(DOWN)) - - def line17(self): - circle = Dot( - radius = 1, - density = 4*DEFAULT_POINT_DENSITY_1D - ) - blue_rgb = np.array(Color("blue").get_rgb()) - white_rgb = np.ones(3) - circle.rgbas = np.array([ - alpha * blue_rgb + (1 - alpha) * white_rgb - for alpha in np.arange(0, 1, 1.0/len(circle.rgbas)) - ]) - for index in range(circle.points.shape[0]): - circle.rgbas - def trianglify(xxx_todo_changeme): - (x, y, z) = xxx_todo_changeme - norm = get_norm((x, y, z)) - comp = complex(x, y)*complex(0, 1) - return ( - norm * np.log(comp).imag, - -norm, - 0 - ) - tau_r = TexMobject("\\tau r").shift(1.3*DOWN) - r = TexMobject("r").shift(0.2*RIGHT + 0.7*DOWN) - lines = [ - Line(DOWN+np.pi*LEFT, DOWN+np.pi*RIGHT), - Line(ORIGIN, DOWN) - ] - for line in lines: - line.set_color("red") - - self.play(ApplyFunction(trianglify, circle, run_time = 2.0)) - self.add(tau_r, r) - self.play(*[ - ShowCreation(line, run_time = 1.0) - for line in lines - ]) - self.wait() - - def line18(self): - tau = TauCreature() - tau.shift_eyes() - tau.move_to(DOWN) - pi = PiCreature() - pi.set_color("red") - pi.move_to(DOWN + 3*LEFT) - mad_tau = deepcopy(tau).make_mean() - mad_tau.arm.wag(0.5*UP, LEFT, 2.0) - sad_pi = deepcopy(pi).shift_eyes().make_sad() - blinked_tau = deepcopy(tau).blink() - blinked_pi = deepcopy(pi).blink() - - self.add(*pi.parts + tau.parts) - self.wait(0.8) - self.play(*[ - Transform(*eyes, run_time = 0.2, rate_func = rush_into) - for eyes in [ - (tau.left_eye, blinked_tau.left_eye), - (tau.right_eye, blinked_tau.right_eye), - ] - ]) - self.remove(tau.left_eye, tau.right_eye) - self.play(*[ - Transform(*eyes, run_time = 0.2, rate_func = rush_from) - for eyes in [ - (blinked_tau.left_eye, mad_tau.left_eye), - (blinked_tau.right_eye, mad_tau.right_eye), - ] - ]) - self.remove(blinked_tau.left_eye, blinked_tau.right_eye) - self.add(mad_tau.left_eye, mad_tau.right_eye) - self.play( - Transform(tau.arm, mad_tau.arm), - Transform(tau.mouth, mad_tau.mouth), - run_time = 0.5 - ) - self.remove(*tau.parts + blinked_tau.parts) - self.add(*mad_tau.parts) - - self.play(*[ - Transform(*eyes, run_time = 0.2, rate_func = rush_into) - for eyes in [ - (pi.left_eye, blinked_pi.left_eye), - (pi.right_eye, blinked_pi.right_eye), - ] - ]) - self.remove(pi.left_eye, pi.right_eye) - self.play(*[ - Transform(*eyes, run_time = 0.2, rate_func = rush_from) - for eyes in [ - (blinked_pi.left_eye, sad_pi.left_eye), - (blinked_pi.right_eye, sad_pi.right_eye), - ] - ] + [ - Transform(pi.mouth, sad_pi.mouth, run_time = 0.2) - ]) - self.remove(*blinked_pi.parts + pi.parts + sad_pi.parts) - self.play( - WalkPiCreature(sad_pi, DOWN+4*LEFT), - run_time = 1.0 - ) - self.wait() - - def line19(self): - pass - - -if __name__ == "__main__": - command_line_create_scene(MOVIE_PREFIX) - - - - - - - - - - - - - diff --git a/from_3b1b/old/three_dimensions.py b/from_3b1b/old/three_dimensions.py deleted file mode 100644 index da17fdd4..00000000 --- a/from_3b1b/old/three_dimensions.py +++ /dev/null @@ -1,113 +0,0 @@ -import numpy as np -import itertools as it - -from mobject.mobject import Mobject, Mobject1D, Mobject2D, Mobject -from geometry import Line -from constants import * - -class Stars(Mobject1D): - CONFIG = { - "stroke_width" : 1, - "radius" : FRAME_X_RADIUS, - "num_points" : 1000, - } - def init_points(self): - radii, phis, thetas = [ - scalar*np.random.random(self.num_points) - for scalar in [self.radius, np.pi, 2*np.pi] - ] - self.add_points([ - ( - r * np.sin(phi)*np.cos(theta), - r * np.sin(phi)*np.sin(theta), - r * np.cos(phi) - ) - for r, phi, theta in zip(radii, phis, thetas) - ]) - -class CubeWithFaces(Mobject2D): - def init_points(self): - self.add_points([ - sgn * np.array(coords) - for x in np.arange(-1, 1, self.epsilon) - for y in np.arange(x, 1, self.epsilon) - for coords in it.permutations([x, y, 1]) - for sgn in [-1, 1] - ]) - self.pose_at_angle() - self.set_color(BLUE) - - def unit_normal(self, coords): - return np.array([1 if abs(x) == 1 else 0 for x in coords]) - -class Cube(Mobject1D): - def init_points(self): - self.add_points([ - ([a, b, c][p[0]], [a, b, c][p[1]], [a, b, c][p[2]]) - for p in [(0, 1, 2), (2, 0, 1), (1, 2, 0)] - for a, b, c in it.product([-1, 1], [-1, 1], np.arange(-1, 1, self.epsilon)) - ]) - self.pose_at_angle() - self.set_color(YELLOW) - -class Octohedron(Mobject1D): - def init_points(self): - x = np.array([1, 0, 0]) - y = np.array([0, 1, 0]) - z = np.array([0, 0, 1]) - vertex_pairs = [(x+y, x-y), (x+y,-x+y), (-x-y,-x+y), (-x-y,x-y)] - vertex_pairs += [ - (b[0]*x+b[1]*y, b[2]*np.sqrt(2)*z) - for b in it.product(*[(-1, 1)]*3) - ] - for pair in vertex_pairs: - self.add_points( - Line(pair[0], pair[1], density = 1/self.epsilon).points - ) - self.pose_at_angle() - self.set_color(MAROON_D) - -class Dodecahedron(Mobject1D): - def init_points(self): - phi = (1 + np.sqrt(5)) / 2 - x = np.array([1, 0, 0]) - y = np.array([0, 1, 0]) - z = np.array([0, 0, 1]) - v1, v2 = (phi, 1/phi, 0), (phi, -1/phi, 0) - vertex_pairs = [ - (v1, v2), - (x+y+z, v1), - (x+y-z, v1), - (x-y+z, v2), - (x-y-z, v2), - ] - five_lines_points = Mobject(*[ - Line(pair[0], pair[1], density = 1.0/self.epsilon) - for pair in vertex_pairs - ]).points - #Rotate those 5 edges into all 30. - for i in range(3): - perm = [j%3 for j in range(i, i+3)] - for b in [-1, 1]: - matrix = b*np.array([x[perm], y[perm], z[perm]]) - self.add_points(np.dot(five_lines_points, matrix)) - self.pose_at_angle() - self.set_color(GREEN) - -class Sphere(Mobject2D): - def init_points(self): - self.add_points([ - ( - np.sin(phi) * np.cos(theta), - np.sin(phi) * np.sin(theta), - np.cos(phi) - ) - for phi in np.arange(self.epsilon, np.pi, self.epsilon) - for theta in np.arange(0, 2 * np.pi, 2 * self.epsilon / np.sin(phi)) - ]) - self.set_color(BLUE) - - def unit_normal(self, coords): - return np.array(coords) / get_norm(coords) - - \ No newline at end of file diff --git a/from_3b1b/old/triangle_of_power/end.py b/from_3b1b/old/triangle_of_power/end.py deleted file mode 100644 index 6af256e5..00000000 --- a/from_3b1b/old/triangle_of_power/end.py +++ /dev/null @@ -1,353 +0,0 @@ -from manimlib.imports import * - -from from_3b1b.old.triangle_of_power.triangle import TOP, OPERATION_COLORS - -class DontLearnFromSymbols(Scene): - def construct(self): - randy = Randolph().to_corner() - bubble = randy.get_bubble() - bubble.content_scale_factor = 0.6 - bubble.add_content(TOP(2, 3, 8).scale(0.7)) - equation = VMobject( - TOP(2, "x"), - TexMobject("\\times"), - TOP(2, "y"), - TexMobject("="), - TOP(2, "x+y") - ) - equation.arrange() - q_marks = TextMobject("???") - q_marks.set_color(YELLOW) - q_marks.next_to(randy, UP) - - self.add(randy) - self.play(FadeIn(bubble)) - self.wait() - top = bubble.content - bubble.add_content(equation) - self.play( - FadeOut(top), - ApplyMethod(randy.change_mode, "sassy"), - Write(bubble.content), - Write(q_marks), - run_time = 1 - ) - self.wait(3) - - -class NotationReflectsMath(Scene): - def construct(self): - top_expr = TextMobject("Notation $\\Leftrightarrow$ Math") - top_expr.shift(2*UP) - better_questions = TextMobject("Better questions") - arrow = Arrow(top_expr, better_questions) - - self.play(Write(top_expr)) - self.play( - ShowCreationPerSubmobject( - arrow, - rate_func = lambda t : min(smooth(3*t), 1) - ), - Write(better_questions), - run_time = 3 - ) - self.wait(2) - -class AsymmetriesInTheMath(Scene): - def construct(self): - assyms_of_top = VMobject( - TextMobject("Asymmetries of "), - TOP("a", "b", "c", radius = 0.75).set_color(BLUE) - ).arrange() - assyms_of_top.to_edge(UP) - assyms_of_math = TextMobject(""" - Asymmetries of - $\\underbrace{a \\cdot a \\cdots a}_{\\text{$b$ times}} = c$ - """) - VMobject(*assyms_of_math.split()[13:]).set_color(YELLOW) - assyms_of_math.next_to(assyms_of_top, DOWN, buff = 2) - rad = TexMobject("\\sqrt{\\quad}").to_edge(LEFT).shift(UP) - rad.set_color(RED) - log = TexMobject("\\log").next_to(rad, DOWN) - log.set_color(RED) - - self.play(FadeIn(assyms_of_top)) - self.wait() - self.play(FadeIn(assyms_of_math)) - self.wait() - self.play(Write(VMobject(rad, log))) - self.wait() - -class AddedVsOplussed(Scene): - def construct(self): - top = TOP() - left_times = top.put_in_vertex(0, TexMobject("\\times")) - left_dot = top.put_in_vertex(0, Dot()) - right_times = top.put_in_vertex(2, TexMobject("\\times")) - right_dot = top.put_in_vertex(2, Dot()) - plus = top.put_in_vertex(1, TexMobject("+")) - oplus = top.put_in_vertex(1, TexMobject("\\oplus")) - left_times.set_color(YELLOW) - right_times.set_color(YELLOW) - plus.set_color(GREEN) - oplus.set_color(BLUE) - - self.add(top, left_dot, plus, right_times) - self.wait() - self.play( - Transform( - VMobject(left_dot, plus, right_times), - VMobject(right_dot, oplus, left_times), - path_arc = np.pi/2 - ) - ) - self.wait() - - - -class ReciprocalTop(Scene): - def construct(self): - top = TOP() - start_two = top.put_on_vertex(2, 2) - end_two = top.put_on_vertex(0, 2) - x = top.put_on_vertex(1, "x") - one_over_x = top.put_on_vertex(1, "\\dfrac{1}{x}") - x.set_color(GREEN) - one_over_x.set_color(BLUE) - start_two.set_color(YELLOW) - end_two.set_color(YELLOW) - - self.add(top, start_two, x) - self.wait() - self.play( - Transform( - VMobject(start_two, x), - VMobject(end_two, one_over_x) - ), - ApplyMethod(top.rotate, np.pi, UP, path_arc = np.pi/7) - ) - self.wait() - - - -class NotSymbolicPatterns(Scene): - def construct(self): - randy = Randolph() - symbolic_patterns = TextMobject("Symbolic patterns") - symbolic_patterns.to_edge(RIGHT).shift(UP) - line = Line(LEFT, RIGHT, color = RED, stroke_width = 7) - line.replace(symbolic_patterns) - substantive_reasoning = TextMobject("Substantive reasoning") - substantive_reasoning.to_edge(RIGHT).shift(DOWN) - - self.add(randy, symbolic_patterns) - self.wait() - self.play(ShowCreation(line)) - self.play( - Write(substantive_reasoning), - ApplyMethod(randy.change_mode, "pondering_looking_left"), - run_time = 1 - ) - self.wait(2) - self.play( - ApplyMethod(line.shift, 10*DOWN), - ApplyMethod(substantive_reasoning.shift, 10*DOWN), - ApplyMethod(randy.change_mode, "sad"), - run_time = 1 - ) - self.wait(2) - -class ChangeWeCanBelieveIn(Scene): - def construct(self): - words = TextMobject("Change we can believe in") - change = VMobject(*words.split()[:6]) - top = TOP(radius = 0.75) - top.shift(change.get_right()-top.get_right()) - - self.play(Write(words)) - self.play( - FadeOut(change), - GrowFromCenter(top) - ) - self.wait(3) - - - -class TriangleOfPowerIsBetter(Scene): - def construct(self): - top = TOP("x", "y", "z", radius = 0.75) - top.set_color(BLUE) - alts = VMobject(*list(map(TexMobject, [ - "x^y", "\\log_x(z)", "\\sqrt[y]{z}" - ]))) - for mob, color in zip(alts.split(), OPERATION_COLORS): - mob.set_color(color) - alts.arrange(DOWN) - greater_than = TexMobject(">") - top.next_to(greater_than, LEFT) - alts.next_to(greater_than, RIGHT) - - self.play(Write(VMobject(top, greater_than, alts))) - self.wait() - - -class InYourOwnNotes(Scene): - def construct(self): - anims = [ - self.get_log_anim(3*LEFT), - self.get_exp_anim(3*RIGHT), - ] - for anim in anims: - self.add(anim.mobject) - self.wait(2) - self.play(*anims) - self.wait(2) - - - def get_log_anim(self, center): - O_log_n = TexMobject(["O(", "\\log(n)", ")"]) - O_log_n.shift(center) - log_n = O_log_n.split()[1] - #super hacky - g = log_n.split()[2] - for mob in g.submobjects: - mob.is_subpath = False - mob.set_fill(BLACK, 1.0) - log_n.add(mob) - g.submobjects = [] - #end hack - top = TOP(2, None, "n", radius = 0.75) - top.set_width(log_n.get_width()) - top.shift(log_n.get_center()) - new_O_log_n = O_log_n.copy() - new_O_log_n.submobjects[1] = top - return Transform(O_log_n, new_O_log_n) - - - def get_exp_anim(self, center): - epii = TexMobject("e^{\\pi i} = -1") - epii.shift(center) - top = TOP("e", "\\pi i", "-1", radius = 0.75) - top.shift(center) - e, pi, i, equals, minus, one = epii.split() - ##hacky - loop = e.submobjects[0] - loop.is_subpath = False - loop.set_fill(BLACK, 1.0) - e.submobjects = [] - ## - start = VMobject( - equals, - VMobject(e, loop), - VMobject(pi, i), - VMobject(minus, one) - ) - return Transform(start, top) - - - -class Qwerty(Scene): - def construct(self): - qwerty = VMobject( - TextMobject(list("QWERTYUIOP")), - TextMobject(list("ASDFGHJKL")), - TextMobject(list("ZXCVBNM")), - ) - qwerty.arrange(DOWN) - dvorak = VMobject( - TextMobject(list("PYFGCRL")), - TextMobject(list("AOEUIDHTNS")), - TextMobject(list("QJKXBMWVZ")), - ) - dvorak.arrange(DOWN) - d1, d2, d3 = dvorak.split() - d1.shift(0.9*RIGHT) - d3.shift(0.95*RIGHT) - - self.add(qwerty) - self.wait(2) - self.play(Transform(qwerty, dvorak)) - self.wait(2) - - -class ShowLog(Scene): - def construct(self): - equation = VMobject(*[ - TOP(2, None, "x"), - TexMobject("+"), - TOP(2, None, "y"), - TexMobject("="), - TOP(2, None, "xy") - ]).arrange() - old_eq = TexMobject("\\log_2(x) + \\log_2(y) = \\log_2(xy)") - old_eq.to_edge(UP) - - self.play(FadeIn(equation)) - self.wait(3) - self.play(FadeIn(old_eq)) - self.wait(2) - - -class NoOneWillActuallyDoThis(Scene): - def construct(self): - randy = Randolph().to_corner() - bubble = SpeechBubble().pin_to(randy) - words = TextMobject("No one will actually do this...") - tau_v_pi = TexMobject("\\tau > \\pi").scale(2) - morty = Mortimer("speaking").to_corner(DOWN+RIGHT) - morty_bubble = SpeechBubble( - direction = RIGHT, - height = 4 - ) - morty_bubble.pin_to(morty) - final_words = TextMobject("If this war is won, it will \\\\ not be won with that attitude") - lil_thought_bubble = ThoughtBubble(height = 3, width = 5) - lil_thought_bubble.set_fill(BLACK, 1.0) - lil_thought_bubble.pin_to(randy) - lil_thought_bubble.write("Okay buddy, calm down, it's notation \\\\ we're talking about not war.") - lil_thought_bubble.show() - - - self.add(randy) - self.play( - ApplyMethod(randy.change_mode, "sassy"), - ShowCreation(bubble) - ) - bubble.add_content(words) - self.play(Write(words), run_time = 2) - self.play(Blink(randy)) - bubble.add_content(tau_v_pi) - self.play( - FadeOut(words), - GrowFromCenter(tau_v_pi), - ApplyMethod(randy.change_mode, "speaking") - ) - self.remove(words) - self.play(Blink(randy)) - self.wait(2) - self.play( - FadeOut(bubble), - FadeIn(morty), - ShowCreation(morty_bubble), - ApplyMethod(randy.change_mode, "plain") - ) - morty_bubble.add_content(final_words) - self.play(Write(final_words)) - self.wait() - self.play(Blink(morty)) - self.wait(2) - self.play(ShowCreation(lil_thought_bubble)) - - - - - - - - - - - - - - diff --git a/from_3b1b/old/triangle_of_power/intro.py b/from_3b1b/old/triangle_of_power/intro.py deleted file mode 100644 index e7cbccb8..00000000 --- a/from_3b1b/old/triangle_of_power/intro.py +++ /dev/null @@ -1,489 +0,0 @@ -from manimlib.imports import * - - -class TrigAnimation(Animation): - CONFIG = { - "rate_func" : None, - "run_time" : 5, - "sin_color" : BLUE, - "cos_color" : RED, - "tan_color" : GREEN - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - x_axis = NumberLine( - x_min = -3, - x_max = 3, - color = BLUE_E - ) - y_axis = x_axis.copy().rotate(np.pi/2) - circle = Circle(color = WHITE) - self.trig_lines = [ - Line(ORIGIN, RIGHT, color = color) - for color in (self.sin_color, self.cos_color, self.tan_color) - ] - mobject = VMobject( - x_axis, y_axis, circle, - *self.trig_lines - ) - mobject.to_edge(RIGHT) - self.center = mobject.get_center() - Animation.__init__(self, mobject, **kwargs) - - def interpolate_mobject(self, alpha): - theta = 2*np.pi*alpha - circle_point = np.cos(theta)*RIGHT+np.sin(theta)*UP+self.center - points = [ - circle_point[0]*RIGHT, - circle_point[1]*UP+self.center, - ( - np.sign(np.cos(theta))*np.sqrt( - np.tan(theta)**2 - np.sin(theta)**2 - ) + np.cos(theta) - )*RIGHT + self.center, - ] - for line, point in zip(self.trig_lines, points): - line.set_points_as_corners([circle_point, point]) - - - -class Notation(Scene): - def construct(self): - self.introduce_notation() - self.shift_to_good_and_back() - self.shift_to_visuals() - self.swipe_left() - - - def introduce_notation(self): - notation = TextMobject("Notation") - notation.to_edge(UP) - - self.sum1 = TexMobject("\\sum_{n=1}^\\infty \\dfrac{1}{n}") - self.prod1 = TexMobject("\\prod_{p\\text{ prime}}\\left(1-p^{-s}\\right)") - self.trigs1 = TexMobject([ - ["\\sin", "(x)"], - ["\\cos", "(x)"], - ["\\tan", "(x)"], - ], next_to_direction = DOWN) - self.func1 = TexMobject("f(x) = y") - symbols = [self.sum1, self.prod1, self.trigs1, self.func1] - for sym, vect in zip(symbols, compass_directions(4, UP+LEFT)): - sym.scale(0.5) - vect[0] *= 2 - sym.shift(vect) - self.symbols = VMobject(*symbols) - - self.play(Write(notation)) - self.play(Write(self.symbols)) - self.wait() - self.add(notation, self.symbols) - - - - def shift_to_good_and_back(self): - sum2 = self.sum1.copy() - sigma = sum2.submobjects[1] - plus = TexMobject("+").replace(sigma) - sum2.submobjects[1] = plus - - prod2 = self.prod1.copy() - pi = prod2.submobjects[0] - times = TexMobject("\\times").replace(pi) - prod2.submobjects[0] = times - - new_sin, new_cos, new_tan = [ - VMobject().set_points_as_corners( - corners - ).replace(trig_part.split()[0]) - for corners, trig_part in zip( - [ - [RIGHT, RIGHT+UP, LEFT], - [RIGHT+UP, LEFT, RIGHT], - [RIGHT+UP, RIGHT, LEFT], - ], - self.trigs1.split() - ) - ] - x1, x2, x3 = [ - trig_part.split()[1] - for trig_part in self.trigs1.split() - ] - trigs2 = VMobject( - VMobject(new_sin, x1), - VMobject(new_cos, x2), - VMobject(new_tan, x3), - ) - - x, arrow, y = TexMobject("x \\rightarrow y").split() - f = TexMobject("f") - f.next_to(arrow, UP) - func2 = VMobject(f, VMobject(), x, VMobject(), arrow, y) - func2.scale(0.5) - func2.shift(self.func1.get_center()) - - good_symbols = VMobject(sum2, prod2, trigs2, func2) - bad_symbols = self.symbols.copy() - self.play(Transform( - self.symbols, good_symbols, - path_arc = np.pi - )) - self.wait(3) - self.play(Transform( - self.symbols, bad_symbols, - path_arc = np.pi - )) - self.wait() - - - def shift_to_visuals(self): - sigma, prod, trig, func = self.symbols.split() - new_trig = trig.copy() - sin, cos, tan = [ - trig_part.split()[0] - for trig_part in new_trig.split() - ] - trig_anim = TrigAnimation() - sin.set_color(trig_anim.sin_color) - cos.set_color(trig_anim.cos_color) - tan.set_color(trig_anim.tan_color) - new_trig.to_corner(UP+RIGHT) - sum_lines = self.get_harmonic_sum_lines() - - self.play( - Transform(trig, new_trig), - *it.starmap(ApplyMethod, [ - (sigma.to_corner, UP+LEFT), - (prod.shift, 15*LEFT), - (func.shift, 5*UP), - ]) - ) - sum_lines.next_to(sigma, DOWN) - self.remove(prod, func) - self.play( - trig_anim, - Write(sum_lines) - ) - self.play(trig_anim) - self.wait() - - def get_harmonic_sum_lines(self): - result = VMobject() - for n in range(1, 8): - big_line = NumberLine( - x_min = 0, - x_max = 1.01, - tick_frequency = 1./n, - numbers_with_elongated_ticks = [], - color = WHITE - ) - little_line = Line( - big_line.number_to_point(0), - big_line.number_to_point(1./n), - color = RED - ) - big_line.add(little_line) - big_line.shift(0.5*n*DOWN) - result.add(big_line) - return result - - - def swipe_left(self): - everyone = VMobject(*self.mobjects) - self.play(ApplyMethod(everyone.shift, 20*LEFT)) - - -class ButDots(Scene): - def construct(self): - but = TextMobject("but") - dots = TexMobject("\\dots") - dots.next_to(but, aligned_edge = DOWN) - but.shift(20*RIGHT) - self.play(ApplyMethod(but.shift, 20*LEFT)) - self.play(Write(dots, run_time = 5)) - self.wait() - - -class ThreesomeOfNotation(Scene): - def construct(self): - exp = TexMobject("x^y = z") - log = TexMobject("\\log_x(z) = y") - rad = TexMobject("\\sqrt[y]{z} = x") - exp.to_edge(LEFT).shift(2*UP) - rad.to_edge(RIGHT).shift(2*DOWN) - x1, y1, eq, z1 = exp.split() - l, o, g, x2, p, z2, p, eq, y2 = log.split() - y3, r, r, z3, eq, x3 = rad.split() - vars1 = VMobject(x1, y1, z1).copy() - vars2 = VMobject(x2, y2, z2) - vars3 = VMobject(x3, y3, z3) - - self.play(Write(exp)) - self.play(Transform(vars1, vars2, path_arc = -np.pi)) - self.play(Write(log)) - self.play(Transform(vars1, vars3, path_arc = -np.pi)) - self.play(Write(rad)) - self.wait() - - words = TextMobject("Artificially unrelated") - words.to_corner(UP+RIGHT) - words.set_color(YELLOW) - self.play(Write(words)) - self.wait() - - -class TwoThreeEightExample(Scene): - def construct(self): - start = TexMobject("2 \\cdot 2 \\cdot 2 = 8") - two1, dot1, two2, dot2, two3, eq, eight = start.split() - brace = Brace(VMobject(two1, two3), DOWN) - three = TexMobject("3").next_to(brace, DOWN, buff = 0.2) - rogue_two = two1.copy() - - self.add(two1) - self.play( - Transform(rogue_two, two2), - Write(dot1), - run_time = 0.5 - ) - self.add(two2) - self.play( - Transform(rogue_two, two3), - Write(dot2), - run_time = 0.5 - ) - self.add(two3) - self.remove(rogue_two) - self.play( - Write(eq), Write(eight), - GrowFromCenter(brace), - Write(three), - run_time = 1 - ) - self.wait() - - exp = TexMobject("2^3") - exp.next_to(eq, LEFT) - exp.shift(0.2*UP) - base_two, exp_three = exp.split() - self.play( - Transform( - VMobject(two1, dot1, two2, dot2, brace, three), - exp_three, - path_arc = -np.pi/2 - ), - Transform(two3, base_two) - ) - self.clear() - self.add(base_two, exp_three, eq, eight) - self.wait(3) - - rad_three, rad1, rad2, rad_eight, rad_eq, rad_two = \ - TexMobject("\\sqrt[3]{8} = 2").split() - self.play(*[ - Transform(*pair, path_arc = np.pi/2) - for pair in [ - (exp_three, rad_three), - (VMobject(), rad1), - (VMobject(), rad2), - (eight, rad_eight), - (eq, rad_eq), - (base_two, rad_two) - ] - ]) - self.wait() - self.play(ApplyMethod( - VMobject(rad1, rad2).set_color, RED, - rate_func = there_and_back, - run_time = 2 - )) - self.remove(rad1, rad2) - self.wait() - - l, o, g, log_two, p1, log_eight, p2, log_eq, log_three = \ - TexMobject("\\log_2(8) = 3").split() - self.clear() - self.play(*[ - Transform(*pair, path_arc = np.pi/2) - for pair in [ - (rad1, l), - (rad2, o), - (rad2.copy(), g), - (rad_two, log_two), - (VMobject(), p1), - (rad_eight, log_eight), - (VMobject(), p2), - (rad_eq, log_eq), - (rad_three, log_three) - ] - ]) - self.wait() - self.play(ApplyMethod( - VMobject(l, o, g).set_color, RED, - rate_func = there_and_back, - run_time = 2 - )) - self.wait() - -class WhatTheHell(Scene): - def construct(self): - randy = Randolph() - randy.to_corner(DOWN+LEFT) - exp, rad, log = list(map(TexMobject,[ - "2^3 = 8", - "\\sqrt[3]{8} = 2", - "\\log_2(8) = 3", - ])) - # exp.set_color(BLUE_D) - # rad.set_color(RED_D) - # log.set_color(GREEN_D) - arrow1 = DoubleArrow(DOWN, UP) - arrow2 = arrow1.copy() - last = exp - for mob in arrow1, rad, arrow2, log: - mob.next_to(last, DOWN) - last = mob - q_marks = VMobject(*[ - TexMobject("?!").next_to(arrow, RIGHT) - for arrow in (arrow1, arrow2) - ]) - q_marks.set_color(RED_D) - everyone = VMobject(exp, rad, log, arrow1, arrow2, q_marks) - everyone.scale(0.7) - everyone.to_corner(UP+RIGHT) - phrases = [ - TextMobject( - ["Communicate with", words] - ).next_to(mob, LEFT, buff = 1) - for words, mob in [ - ("position", exp), - ("a new symbol", rad), - ("a word", log) - ] - ] - for phrase, color in zip(phrases, [BLUE, RED, GREEN]): - phrase.split()[1].set_color(color) - - self.play(ApplyMethod(randy.change_mode, "angry")) - self.play(FadeIn(VMobject(exp, rad, log))) - self.play( - ShowCreationPerSubmobject(arrow1), - ShowCreationPerSubmobject(arrow2) - ) - self.play(Write(q_marks)) - self.wait() - self.remove(randy) - self.play(Write(VMobject(*phrases))) - self.wait() - -class Countermathematical(Scene): - def construct(self): - counterintuitive = TextMobject("Counterintuitive") - mathematical = TextMobject("mathematical") - intuitive = VMobject(*counterintuitive.split()[7:]) - mathematical.shift(intuitive.get_left()-mathematical.get_left()) - - self.add(counterintuitive) - self.wait() - self.play(Transform(intuitive, mathematical)) - self.wait() - - -class PascalsCollision(Scene): - def construct(self): - pascals_triangle = PascalsTriangle() - pascals_triangle.scale(0.5) - final_triangle = PascalsTriangle() - final_triangle.fill_with_n_choose_k() - pascals_triangle.to_corner(UP+LEFT) - final_triangle.scale(0.7) - final_triangle.to_edge(UP) - equation = TexMobject([ - "{n \\choose k}", - " = \\dfrac{n!}{(n-k)!k!}" - ]) - equation.scale(0.5) - equation.to_corner(UP+RIGHT) - n_choose_k, formula = equation.split() - words = TextMobject("Seemingly unrelated") - words.shift(2*DOWN) - to_remove = VMobject(*words.split()[:-7]) - - self.add(pascals_triangle, n_choose_k, formula) - self.play(Write(words)) - self.wait() - self.play( - Transform(pascals_triangle, final_triangle), - Transform(n_choose_k, final_triangle), - FadeOut(formula), - ApplyMethod(to_remove.shift, 5*DOWN) - ) - self.wait() - - -class LogarithmProperties(Scene): - def construct(self): - randy = Randolph() - randy.to_corner() - bubble = ThoughtBubble().pin_to(randy) - props = [ - TexMobject("\\log_a(x) = \\dfrac{\\log_b(a)}{\\log_b(x)}"), - TexMobject("\\log_a(x) = \\dfrac{\\log_b(x)}{\\log_b(a)}"), - TexMobject("\\log_a(x) = \\log_b(x) - \\log_b(a)"), - TexMobject("\\log_a(x) = \\log_b(x) + \\log_b(a)"), - TexMobject("\\log_a(x) = \\dfrac{\\log_b(x)}{\\log_b(a)}"), - ] - bubble.add_content(props[0]) - words = TextMobject("What was it again?") - words.set_color(YELLOW) - words.scale(0.5) - words.next_to(props[0], UP) - - self.play( - ApplyMethod(randy.change_mode, "confused"), - ShowCreation(bubble), - Write(words) - ) - self.show_frame() - for i, prop in enumerate(props[1:]): - self.play(ApplyMethod(bubble.add_content, prop)) - if i%2 == 0: - self.play(Blink(randy)) - else: - self.wait() - - -class HaveToShare(Scene): - def construct(self): - words = list(map(TextMobject, [ - "Lovely", "Symmetrical", "Utterly Reasonable" - ])) - for w1, w2 in zip(words, words[1:]): - w2.next_to(w1, DOWN) - VMobject(*words).center() - left_dot, top_dot, bottom_dot = [ - Dot(point, radius = 0.1) - for point in (ORIGIN, RIGHT+0.5*UP, RIGHT+0.5*DOWN) - ] - line1, line2 = [ - Line(left_dot.get_center(), dot.get_center(), buff = 0) - for dot in (top_dot, bottom_dot) - ] - share = VMobject(left_dot, top_dot, bottom_dot, line1, line2) - share.next_to(words[1], RIGHT, buff = 1) - share.set_color(RED) - - for word in words: - self.play(FadeIn(word)) - self.wait() - self.play(Write(share, run_time = 1)) - self.wait() - - - - - - - - - diff --git a/from_3b1b/old/triangle_of_power/triangle.py b/from_3b1b/old/triangle_of_power/triangle.py deleted file mode 100644 index 1940b05d..00000000 --- a/from_3b1b/old/triangle_of_power/triangle.py +++ /dev/null @@ -1,823 +0,0 @@ -import numbers -from manimlib.imports import * -from functools import reduce - -OPERATION_COLORS = [YELLOW, GREEN, BLUE_B] - -def get_equation(index, x = 2, y = 3, z = 8, expression_only = False): - assert(index in [0, 1, 2]) - if index == 0: - tex1 = "\\sqrt[%d]{%d}"%(y, z), - tex2 = " = %d"%x - elif index == 1: - tex1 = "\\log_%d(%d)"%(x, z), - tex2 = " = %d"%y - elif index == 2: - tex1 = "%d^%d"%(x, y), - tex2 = " = %d"%z - if expression_only: - tex = tex1 - else: - tex = tex1+tex2 - return TexMobject(tex).set_color(OPERATION_COLORS[index]) - -def get_inverse_rules(): - return list(map(TexMobject, [ - "x^{\\log_x(z)} = z", - "\\log_x\\left(x^y \\right) = y", - "\\sqrt[y]{x^y} = x", - "\\left(\\sqrt[y]{z}\\right)^y = z", - "\\sqrt[\\log_x(z)]{z} = x", - "\\log_{\\sqrt[y]{z}}(z) = y", - ])) - -def get_top_inverse_rules(): - result = [] - pairs = [#Careful of order here! - (0, 2), - (0, 1), - (1, 0), - (1, 2), - (2, 0), - (2, 1), - ] - for i, j in pairs: - top = get_top_inverse(i, j) - char = ["x", "y", "z"][j] - eq = TexMobject("= %s"%char) - eq.scale(2) - eq.next_to(top, RIGHT) - diff = eq.get_center() - top.triangle.get_center() - eq.shift(diff[1]*UP) - result.append(VMobject(top, eq)) - return result - -def get_top_inverse(i, j): - args = [None]*3 - k = set([0, 1, 2]).difference([i, j]).pop() - args[i] = ["x", "y", "z"][i] - big_top = TOP(*args) - args[j] = ["x", "y", "z"][j] - lil_top = TOP(*args, triangle_height_to_number_height = 1.5) - big_top.set_value(k, lil_top) - return big_top - -class TOP(VMobject): - CONFIG = { - "triangle_height_to_number_height" : 3, - "offset_multiple" : 1.5, - "radius" : 1.5, - } - def __init__(self, x = None, y = None, z = None, **kwargs): - digest_config(self, kwargs, locals()) - VMobject.__init__(self, **kwargs) - - def init_points(self): - vertices = [ - self.radius*rotate_vector(RIGHT, 7*np.pi/6 - i*2*np.pi/3) - for i in range(3) - ] - self.triangle = Polygon( - *vertices, - color = WHITE, - stroke_width = 5 - ) - self.values = [VMobject()]*3 - self.set_values(self.x, self.y, self.z) - - def set_values(self, x, y, z): - for i, mob in enumerate([x, y, z]): - self.set_value(i, mob) - - def set_value(self, index, value): - self.values[index] = self.put_on_vertex(index, value) - self.reset_submobjects() - - def put_on_vertex(self, index, value): - assert(index in [0, 1, 2]) - if value is None: - value = VectorizedPoint() - if isinstance(value, numbers.Number): - value = str(value) - if isinstance(value, str): - value = TexMobject(value) - if isinstance(value, TOP): - return self.put_top_on_vertix(index, value) - self.rescale_corner_mobject(value) - value.center() - if index == 0: - offset = -value.get_corner(UP+RIGHT) - elif index == 1: - offset = -value.get_bottom() - elif index == 2: - offset = -value.get_corner(UP+LEFT) - value.shift(self.offset_multiple*offset) - anchors = self.triangle.get_anchors_and_handles()[0] - value.shift(anchors[index]) - return value - - def put_top_on_vertix(self, index, top): - top.set_height(2*self.get_value_height()) - vertices = np.array(top.get_vertices()) - vertices[index] = 0 - start = reduce(op.add, vertices)/2 - end = self.triangle.get_anchors_and_handles()[0][index] - top.shift(end-start) - return top - - def put_in_vertex(self, index, mobject): - self.rescale_corner_mobject(mobject) - mobject.center() - mobject.shift(interpolate( - self.get_center(), - self.get_vertices()[index], - 0.7 - )) - return mobject - - - def get_surrounding_circle(self, color = YELLOW): - return Circle( - radius = 1.7*self.radius, - color = color - ).shift( - self.triangle.get_center(), - (self.triangle.get_height()/6)*DOWN - ) - - def rescale_corner_mobject(self, mobject): - mobject.set_height(self.get_value_height()) - return self - - def get_value_height(self): - return self.triangle.get_height()/self.triangle_height_to_number_height - - def get_center(self): - return center_of_mass(self.get_vertices()) - - def get_vertices(self): - return self.triangle.get_anchors_and_handles()[0][:3] - - def reset_submobjects(self): - self.submobjects = [self.triangle] + self.values - return self - - -class IntroduceNotation(Scene): - def construct(self): - top = TOP() - equation = TexMobject("2^3 = 8") - equation.to_corner(UP+LEFT) - two, three, eight = [ - top.put_on_vertex(i, num) - for i, num in enumerate([2, 3, 8]) - ] - - self.play(FadeIn(equation)) - self.wait() - self.play(ShowCreation(top)) - for num in two, three, eight: - self.play(ShowCreation(num), run_time=2) - self.wait() - -class ShowRule(Scene): - args_list = [(0,), (1,), (2,)] - - @staticmethod - def args_to_string(index): - return str(index) - - @staticmethod - def string_to_args(index_string): - result = int(index_string) - assert(result in [0, 1, 2]) - return result - - def construct(self, index): - equation = get_equation(index) - equation.to_corner(UP+LEFT) - top = TOP(2, 3, 8) - new_top = top.copy() - equals = TexMobject("=").scale(1.5) - new_top.next_to(equals, LEFT, buff = 1) - new_top.values[index].next_to(equals, RIGHT, buff = 1) - circle = Circle( - radius = 1.7*top.radius, - color = OPERATION_COLORS[index] - ) - - - self.add(equation, top) - self.wait() - self.play( - Transform(top, new_top), - ShowCreation(equals) - ) - - circle.shift(new_top.triangle.get_center_of_mass()) - new_circle = circle.copy() - new_top.put_on_vertex(index, new_circle) - self.wait() - self.play(ShowCreation(circle)) - self.wait() - self.play( - Transform(circle, new_circle), - ApplyMethod(new_top.values[index].set_color, circle.color) - ) - self.wait() - - -class AllThree(Scene): - def construct(self): - tops = [] - equations = [] - args = (2, 3, 8) - for i in 2, 1, 0: - new_args = list(args) - new_args[i] = None - top = TOP(*new_args, triangle_height_to_number_height = 2) - # top.set_color(OPERATION_COLORS[i]) - top.shift(i*4.5*LEFT) - equation = get_equation(i, expression_only = True) - equation.scale(3) - equation.next_to(top, DOWN, buff = 0.7) - tops.append(top) - equations.append(equation) - VMobject(*tops+equations).center() - # name = TextMobject("Triangle of Power") - # name.to_edge(UP) - - for top, eq in zip(tops, equations): - self.play(FadeIn(top), FadeIn(eq)) - self.wait(3) - # self.play(Write(name)) - self.wait() - -class SixDifferentInverses(Scene): - def construct(self): - rules = get_inverse_rules() - vects = it.starmap(op.add, it.product( - [3*UP, 0.5*UP, 2*DOWN], [2*LEFT, 2*RIGHT] - )) - for rule, vect in zip(rules, vects): - rule.shift(vect) - general_idea = TexMobject("f(f^{-1}(a)) = a") - - self.play(Write(VMobject(*rules))) - self.wait() - for s, color in (rules[:4], GREEN), (rules[4:], RED): - mob = VMobject(*s) - self.play(ApplyMethod(mob.set_color, color)) - self.wait() - self.play(ApplyMethod(mob.set_color, WHITE)) - self.play( - ApplyMethod(VMobject(*rules[::2]).to_edge, LEFT), - ApplyMethod(VMobject(*rules[1::2]).to_edge, RIGHT), - GrowFromCenter(general_idea) - ) - self.wait() - - top_rules = get_top_inverse_rules() - for rule, top_rule in zip(rules, top_rules): - top_rule.set_height(1.5) - top_rule.center() - top_rule.shift(rule.get_center()) - self.play(*list(map(FadeOut, rules))) - self.remove(*rules) - self.play(*list(map(GrowFromCenter, top_rules))) - self.wait() - self.remove(general_idea) - rules = get_inverse_rules() - original = None - for i, (top_rule, rule) in enumerate(zip(top_rules, rules)): - rule.center().to_edge(UP) - rule.set_color(GREEN if i < 4 else RED) - self.add(rule) - new_top_rule = top_rule.copy().center().scale(1.5) - anims = [Transform(top_rule, new_top_rule)] - if original is not None: - anims.append(FadeIn(original)) - original = top_rule.copy() - self.play(*anims) - self.wait() - self.animate_top_rule(top_rule) - self.remove(rule) - - def animate_top_rule(self, top_rule): - lil_top, lil_symbol, symbol_index = None, None, None - big_top = top_rule.submobjects[0] - equals, right_symbol = top_rule.submobjects[1].split() - for i, value in enumerate(big_top.values): - if isinstance(value, TOP): - lil_top = value - elif isinstance(value, TexMobject): - symbol_index = i - else: - lil_symbol_index = i - lil_symbol = lil_top.values[lil_symbol_index] - - assert(lil_top is not None and lil_symbol is not None) - cancel_parts = [ - VMobject(top.triangle, top.values[symbol_index]) - for top in (lil_top, big_top) - ] - new_symbol = lil_symbol.copy() - new_symbol.replace(right_symbol) - vect = equals.get_center() - right_symbol.get_center() - new_symbol.shift(2*vect[0]*RIGHT) - self.play( - Transform(*cancel_parts, rate_func = rush_into) - ) - self.play( - FadeOut(VMobject(*cancel_parts)), - Transform(lil_symbol, new_symbol, rate_func = rush_from) - ) - self.wait() - self.remove(lil_symbol, top_rule, VMobject(*cancel_parts)) - - -class SixSixSix(Scene): - def construct(self): - randy = Randolph(mode = "pondering").to_corner() - bubble = ThoughtBubble().pin_to(randy) - rules = get_inverse_rules() - sixes = TexMobject(["6", "6", "6"], next_to_buff = 1) - sixes.to_corner(UP+RIGHT) - sixes = sixes.split() - speech_bubble = SpeechBubble() - speech_bubble.pin_to(randy) - speech_bubble.write("I'll just study art!") - - self.add(randy) - self.play(ShowCreation(bubble)) - bubble.add_content(VectorizedPoint()) - for i, rule in enumerate(rules): - if i%2 == 0: - anim = ShowCreation(sixes[i/2]) - else: - anim = Blink(randy) - self.play( - ApplyMethod(bubble.add_content, rule), - anim - ) - self.wait() - self.wait() - words = speech_bubble.content - equation = bubble.content - speech_bubble.clear() - bubble.clear() - self.play( - ApplyMethod(randy.change_mode, "angry"), - Transform(bubble, speech_bubble), - Transform(equation, words), - FadeOut(VMobject(*sixes)) - ) - self.wait() - -class AdditiveProperty(Scene): - def construct(self): - exp_rule, log_rule = self.write_old_style_rules() - t_exp_rule, t_log_rule = self.get_new_style_rules() - - self.play( - ApplyMethod(exp_rule.to_edge, UP), - ApplyMethod(log_rule.to_edge, DOWN, 1.5) - ) - t_exp_rule.next_to(exp_rule, DOWN) - t_exp_rule.set_color(GREEN) - t_log_rule.next_to(log_rule, UP) - t_log_rule.set_color(RED) - self.play( - FadeIn(t_exp_rule), - FadeIn(t_log_rule), - ApplyMethod(exp_rule.set_color, GREEN), - ApplyMethod(log_rule.set_color, RED), - ) - self.wait() - all_tops = [m for m in t_exp_rule.split()+t_log_rule.split() if isinstance(m, TOP)] - self.put_in_circles(all_tops) - self.set_color_appropriate_parts(t_exp_rule, t_log_rule) - - - - - def write_old_style_rules(self): - start = TexMobject("a^x a^y = a^{x+y}") - end = TexMobject("\\log_a(xy) = \\log_a(x) + \\log_a(y)") - start.shift(UP) - end.shift(DOWN) - a1, x1, a2, y1, eq1, a3, p1, x2, y2 = start.split() - a4, x3, y3, eq2, a5, x4, p2, a6, y4 = np.array(end.split())[ - [3, 5, 6, 8, 12, 14, 16, 20, 22] - ] - start_copy = start.copy() - self.play(Write(start_copy)) - self.wait() - self.play(Transform( - VMobject(a1, x1, a2, y1, eq1, a3, p1, x2, a3.copy(), y2), - VMobject(a4, x3, a4.copy(), y3, eq2, a5, p2, x4, a6, y4) - )) - self.play(Write(end)) - self.clear() - self.add(start_copy, end) - self.wait() - return start_copy, end - - def get_new_style_rules(self): - upper_mobs = [ - TOP("a", "x", "R"), Dot(), - TOP("a", "y", "R"), TexMobject("="), - TOP("a", "x+y") - ] - lower_mobs = [ - TOP("a", None, "xy"), TexMobject("="), - TOP("a", None, "x"), TexMobject("+"), - TOP("a", None, "y"), - ] - for mob in upper_mobs + lower_mobs: - if isinstance(mob, TOP): - mob.scale(0.5) - for group in upper_mobs, lower_mobs: - for m1, m2 in zip(group, group[1:]): - m2.next_to(m1) - for top in upper_mobs[0], upper_mobs[2]: - top.set_value(2, None) - upper_mobs = VMobject(*upper_mobs).center().shift(2*UP) - lower_mobs = VMobject(*lower_mobs).center().shift(2*DOWN) - return upper_mobs, lower_mobs - - def put_in_circles(self, tops): - anims = [] - for top in tops: - for i, value in enumerate(top.values): - if isinstance(value, VectorizedPoint): - index = i - circle = top.put_on_vertex(index, Circle(color = WHITE)) - anims.append( - Transform(top.copy().set_color(YELLOW), circle) - ) - self.add(*[anim.mobject for anim in anims]) - self.wait() - self.play(*anims) - self.wait() - - def set_color_appropriate_parts(self, t_exp_rule, t_log_rule): - #Horribly hacky - circle1 = t_exp_rule.split()[0].put_on_vertex( - 2, Circle() - ) - top_dot = t_exp_rule.split()[1] - circle2 = t_exp_rule.split()[2].put_on_vertex( - 2, Circle() - ) - top_plus = t_exp_rule.split()[4].values[1] - - bottom_times = t_log_rule.split()[0].values[2] - circle3 = t_log_rule.split()[2].put_on_vertex( - 1, Circle() - ) - bottom_plus = t_log_rule.split()[3] - circle4 = t_log_rule.split()[4].put_on_vertex( - 1, Circle() - ) - - mob_lists = [ - [circle1, top_dot, circle2], - [top_plus], - [bottom_times], - [circle3, bottom_plus, circle4] - ] - for mobs in mob_lists: - copies = VMobject(*mobs).copy() - self.play(ApplyMethod( - copies.set_color, YELLOW, - run_time = 0.5 - )) - self.play(ApplyMethod( - copies.scale_in_place, 1.2, - rate_func = there_and_back - )) - self.wait() - self.remove(copies) - - -class DrawInsideTriangle(Scene): - def construct(self): - top = TOP() - top.scale(2) - dot = top.put_in_vertex(0, Dot()) - plus = top.put_in_vertex(1, TexMobject("+")) - times = top.put_in_vertex(2, TexMobject("\\times")) - plus.set_color(GREEN) - times.set_color(YELLOW) - - self.add(top) - self.wait() - for mob in dot, plus, times: - self.play(Write(mob, run_time = 1)) - self.wait() - -class ConstantOnTop(Scene): - def construct(self): - top = TOP() - dot = top.put_in_vertex(1, Dot()) - times1 = top.put_in_vertex(0, TexMobject("\\times")) - times2 = top.put_in_vertex(2, TexMobject("\\times")) - times1.set_color(YELLOW) - times2.set_color(YELLOW) - three = top.put_on_vertex(1, "3") - lower_left_x = top.put_on_vertex(0, "x") - lower_right_x = top.put_on_vertex(2, "x") - x_cubed = TexMobject("x^3").to_edge(UP) - x_cubed.submobjects.reverse() #To align better - cube_root_x = TexMobject("\\sqrt[3]{x}").to_edge(UP) - - self.add(top) - self.play(ShowCreation(three)) - self.play( - FadeIn(lower_left_x), - Write(x_cubed), - run_time = 1 - ) - self.wait() - self.play(*[ - Transform(*pair, path_arc = np.pi) - for pair in [ - (lower_left_x, lower_right_x), - (x_cubed, cube_root_x), - ] - ]) - self.wait(2) - for mob in dot, times1, times2: - self.play(ShowCreation(mob)) - self.wait() - -def get_const_top_TOP(*args): - top = TOP(*args) - dot = top.put_in_vertex(1, Dot()) - times1 = top.put_in_vertex(0, TexMobject("\\times")) - times2 = top.put_in_vertex(2, TexMobject("\\times")) - times1.set_color(YELLOW) - times2.set_color(YELLOW) - top.add(dot, times1, times2) - return top - - -class MultiplyWithConstantTop(Scene): - def construct(self): - top1 = get_const_top_TOP("x", "3") - top2 = get_const_top_TOP("y", "3") - top3 = get_const_top_TOP("xy", "3") - times = TexMobject("\\times") - equals = TexMobject("=") - top_exp_equation = VMobject( - top1, times, top2, equals, top3 - ) - top_exp_equation.arrange() - old_style_exp = TexMobject("(x^3)(y^3) = (xy)^3") - old_style_exp.to_edge(UP) - old_style_exp.set_color(GREEN) - old_style_rad = TexMobject("\\sqrt[3]{x} \\sqrt[3]{y} = \\sqrt[3]{xy}") - old_style_rad.to_edge(UP) - old_style_rad.set_color(RED) - - self.add(top_exp_equation, old_style_exp) - self.wait(3) - - old_tops = [top1, top2, top3] - new_tops = [] - for top in old_tops: - new_top = top.copy() - new_top.put_on_vertex(2, new_top.values[0]) - new_top.shift(0.5*LEFT) - new_tops.append(new_top) - self.play( - Transform(old_style_exp, old_style_rad), - Transform( - VMobject(*old_tops), - VMobject(*new_tops), - path_arc = np.pi/2 - ) - ) - self.wait(3) - -class RightStaysConstantQ(Scene): - def construct(self): - top1, top2, top3 = old_tops = [ - TOP(None, s, "8") - for s in ("x", "y", TexMobject("x?y")) - ] - q_mark = TexMobject("?").scale(2) - equation = VMobject( - top1, q_mark, top2, TexMobject("="), top3 - ) - equation.arrange(buff = 0.7) - symbols_at_top = VMobject(*[ - top.values[1] - for top in (top1, top2, top3) - ]) - symbols_at_lower_right = VMobject(*[ - top.put_on_vertex(0, top.values[1].copy()) - for top in (top1, top2, top3) - ]) - old_style_eq1 = TexMobject("\\sqrt[x]{8} ? \\sqrt[y]{8} = \\sqrt[x?y]{8}") - old_style_eq1.set_color(BLUE) - old_style_eq2 = TexMobject("\\log_x(8) ? \\log_y(8) = \\log_{x?y}(8)") - old_style_eq2.set_color(YELLOW) - for eq in old_style_eq1, old_style_eq2: - eq.to_edge(UP) - - randy = Randolph() - randy.to_corner() - bubble = ThoughtBubble().pin_to(randy) - bubble.add_content(TOP(None, None, "8")) - - self.add(randy, bubble) - self.play(ApplyMethod(randy.change_mode, "pondering")) - self.wait(3) - triangle = bubble.content.triangle - eight = bubble.content.values[2] - bubble.clear() - self.play( - Transform(triangle, equation), - FadeOut(eight), - ApplyPointwiseFunction( - lambda p : (p+2*DOWN)*15/get_norm(p+2*DOWN), - bubble - ), - FadeIn(old_style_eq1), - ApplyMethod(randy.shift, 3*DOWN + 3*LEFT), - run_time = 2 - ) - self.remove(triangle) - self.add(equation) - self.wait(4) - self.play( - Transform( - symbols_at_top, symbols_at_lower_right, - path_arc = np.pi/2 - ), - Transform(old_style_eq1, old_style_eq2) - ) - self.wait(2) - - -class AOplusB(Scene): - def construct(self): - self.add(TexMobject( - "a \\oplus b = \\dfrac{1}{\\frac{1}{a} + \\frac{1}{b}}" - ).scale(2)) - self.wait() - - -class ConstantLowerRight(Scene): - def construct(self): - top = TOP() - times = top.put_in_vertex(0, TexMobject("\\times")) - times.set_color(YELLOW) - oplus = top.put_in_vertex(1, TexMobject("\\oplus")) - oplus.set_color(BLUE) - dot = top.put_in_vertex(2, Dot()) - eight = top.put_on_vertex(2, TexMobject("8")) - - self.add(top) - self.play(ShowCreation(eight)) - for mob in dot, oplus, times: - self.play(ShowCreation(mob)) - self.wait() - - top.add(eight) - top.add(times, oplus, dot) - top1, top2, top3 = tops = [ - top.copy() for i in range(3) - ] - big_oplus = TexMobject("\\oplus").scale(2).set_color(BLUE) - equals = TexMobject("=") - equation = VMobject( - top1, big_oplus, top2, equals, top3 - ) - equation.arrange() - top3.shift(0.5*RIGHT) - x, y, xy = [ - t.put_on_vertex(0, s) - for t, s in zip(tops, ["x", "y", "xy"]) - ] - old_style_eq = TexMobject( - "\\dfrac{1}{\\frac{1}{\\log_x(8)} + \\frac{1}{\\log_y(8)}} = \\log_{xy}(8)" - ) - old_style_eq.to_edge(UP).set_color(RED) - - triple_top_copy = VMobject(*[ - top.copy() for i in range(3) - ]) - self.clear() - self.play( - Transform(triple_top_copy, VMobject(*tops)), - FadeIn(VMobject(x, y, xy, big_oplus, equals)) - ) - self.remove(triple_top_copy) - self.add(*tops) - self.play(Write(old_style_eq)) - self.wait(3) - - syms = VMobject(x, y, xy) - new_syms = VMobject(*[ - t.put_on_vertex(1, s) - for t, s in zip(tops, ["x", "y", "x \\oplus y"]) - ]) - new_old_style_eq = TexMobject( - "\\sqrt[x]{8} \\sqrt[y]{8} = \\sqrt[X]{8}" - ) - X = new_old_style_eq.split()[-4] - frac = TexMobject("\\frac{1}{\\frac{1}{x} + \\frac{1}{y}}") - frac.replace(X) - frac_lower_right = frac.get_corner(DOWN+RIGHT) - frac.scale(2) - frac.shift(frac_lower_right - frac.get_corner(DOWN+RIGHT)) - new_old_style_eq.submobjects[-4] = frac - new_old_style_eq.to_edge(UP) - new_old_style_eq.set_color(RED) - big_times = TexMobject("\\times").set_color(YELLOW) - big_times.shift(big_oplus.get_center()) - self.play( - Transform(old_style_eq, new_old_style_eq), - Transform(syms, new_syms, path_arc = np.pi/2), - Transform(big_oplus, big_times) - ) - self.wait(4) - - -class TowerExponentFrame(Scene): - def construct(self): - words = TextMobject(""" - Consider an expression like $3^{3^3}$. It's - ambiguous whether this means $27^3$ or $3^{27}$, - which is the difference between $19{,}683$ and - $7{,}625{,}597{,}484{,}987$. But with the triangle - of power, the difference is crystal clear: - """) - words.set_width(FRAME_WIDTH-1) - words.to_edge(UP) - top1 = TOP(TOP(3, 3), 3) - top2 = TOP(3, (TOP(3, 3))) - for top in top1, top2: - top.next_to(words, DOWN) - top1.shift(3*LEFT) - top2.shift(3*RIGHT) - - self.add(words, top1, top2) - self.wait() - - -class ExponentialGrowth(Scene): - def construct(self): - words = TextMobject(""" - Let's say you are studying a certain growth rate, - and you come across an expression like $T^a$. It - matters a lot whether you consider $T$ or $a$ - to be the variable, since exponential growth and - polynomial growth have very different flavors. The - nice thing about having a triangle that you can write - inside is that you can clarify this kind of ambiguity - by writing a little dot next to the constant and - a ``$\\sim$'' next to the variable. - """) - words.scale(0.75) - words.to_edge(UP) - top = TOP("T", "a") - top.next_to(words, DOWN) - dot = top.put_in_vertex(0, TexMobject("\\cdot")) - sim = top.put_in_vertex(1, TexMobject("\\sim")) - - self.add(words, top, dot, sim) - self.show_frame() - self.wait() - - - - -class GoExplore(Scene): - def construct(self): - explore = TextMobject("Go explore!") - by_the_way = TextMobject("by the way \\dots") - by_the_way.shift(20*RIGHT) - - self.play(Write(explore)) - self.wait(4) - self.play( - ApplyMethod( - VMobject(explore, by_the_way).shift, - 20*LEFT - ) - ) - self.wait(3) - - - - - - - - - - diff --git a/from_3b1b/old/triples.py b/from_3b1b/old/triples.py deleted file mode 100644 index 4c24d907..00000000 --- a/from_3b1b/old/triples.py +++ /dev/null @@ -1,3101 +0,0 @@ -import fractions -from manimlib.imports import * - -A_COLOR = BLUE -B_COLOR = GREEN -C_COLOR = YELLOW -SIDE_COLORS = [A_COLOR, B_COLOR, C_COLOR] -U_COLOR = GREEN -V_COLOR = RED - -#revert_to_original_skipping_status - -def complex_string_with_i(z): - if z.real == 0: - return str(int(z.imag)) + "i" - elif z.imag == 0: - return str(int(z.real)) - return complex_string(z).replace("j", "i") - -class IntroduceTriples(TeacherStudentsScene): - def construct(self): - title = TexMobject("a", "^2", "+", "b", "^2", "=", "c", "^2") - for color, char in zip(SIDE_COLORS, "abc"): - title.set_color_by_tex(char, color) - title.to_corner(UP + RIGHT) - - triples = [ - (3, 4, 5), - (5, 12, 13), - (8, 15, 17), - (7, 24, 25), - ] - - self.add(title) - for a, b, c in triples: - triangle = Polygon( - ORIGIN, a*RIGHT, a*RIGHT+b*UP, - stroke_width = 0, - fill_color = WHITE, - fill_opacity = 0.5 - ) - hyp_line = Line(ORIGIN, a*RIGHT+b*UP) - elbow = VMobject() - elbow.set_points_as_corners([LEFT, LEFT+UP, UP]) - elbow.set_width(0.2*triangle.get_width()) - elbow.move_to(triangle, DOWN+RIGHT) - triangle.add(elbow) - - square = Square(side_length = 1) - square_groups = VGroup() - for n, color in zip([a, b, c], SIDE_COLORS): - square_group = VGroup(*[ - square.copy().shift(x*RIGHT + y*UP) - for x in range(n) - for y in range(n) - ]) - square_group.set_stroke(color, width = 3) - square_group.set_fill(color, opacity = 0.5) - square_groups.add(square_group) - a_square, b_square, c_square = square_groups - a_square.move_to(triangle.get_bottom(), UP) - b_square.move_to(triangle.get_right(), LEFT) - c_square.move_to(hyp_line.get_center(), DOWN) - c_square.rotate( - hyp_line.get_angle(), - about_point = hyp_line.get_center() - ) - if c in [5, 13, 25]: - if c == 5: - keys = list(range(0, 5, 2)) - elif c == 13: - keys = list(range(0, 13, 3)) - elif c == 25: - keys = list(range(0, 25, 4)) - i_list = [i for i in range(c**2) if (i%c) in keys and (i//c) in keys] - else: - i_list = list(range(a**2)) - not_i_list = list(filter( - lambda i : i not in i_list, - list(range(c**2)), - )) - c_square_parts = [ - VGroup(*[c_square[i] for i in i_list]), - VGroup(*[c_square[i] for i in not_i_list]), - ] - full_group = VGroup(triangle, square_groups) - full_group.set_height(4) - full_group.center() - full_group.to_edge(UP) - - equation = TexMobject( - str(a), "^2", "+", str(b), "^2", "=", str(c), "^2" - ) - for num, color in zip([a, b, c], SIDE_COLORS): - equation.set_color_by_tex(str(num), color) - equation.next_to(title, DOWN, MED_LARGE_BUFF) - equation.shift_onto_screen() - - self.play( - FadeIn(triangle), - self.teacher.change_mode, "raise_right_hand" - ) - self.play(LaggedStartMap(FadeIn, a_square)) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = triangle, - added_anims = [LaggedStartMap(FadeIn, b_square)] - ) - self.play(self.teacher.change_mode, "happy") - for start, target in zip([a_square, b_square], c_square_parts): - mover = start.copy().set_fill(opacity = 0) - target.set_color(start.get_color()) - self.play(ReplacementTransform( - mover, target, - run_time = 2, - path_arc = np.pi/2 - )) - self.play(Write(equation)) - self.play(c_square.set_color, C_COLOR) - self.wait() - self.play(*list(map(FadeOut, [full_group, equation]))) - -class CompareToFermatsLastTheorem(TeacherStudentsScene): - def construct(self): - expressions = [ - TexMobject( - "a", "^%d"%d, "+", "b", "^%d"%d, - "=", "c", "^%d"%d - ) - for d in range(2, 9) - ] - for expression in expressions: - for char, color in zip("abc", SIDE_COLORS): - expression.set_color_by_tex(char, color) - expression.next_to(self.get_pi_creatures(), UP, buff = 1.3) - square_expression = expressions[0] - low_expression = expressions[1] - square_expression.to_edge(UP, buff = 1.3) - top_brace = Brace(square_expression, UP, buff = SMALL_BUFF) - top_text = top_brace.get_text( - "Abundant integer solutions", buff = SMALL_BUFF - ) - low_brace = Brace(low_expression, DOWN, buff = SMALL_BUFF) - low_text = low_brace.get_text( - "No integer solutions", buff = SMALL_BUFF - ) - low_text.set_color(RED) - - self.add(square_expression, top_brace, top_text) - self.change_student_modes(*["pondering"]*3) - self.play(self.teacher.change, "happy", run_time = 0) - self.play( - ReplacementTransform( - square_expression.copy(), - low_expression - ), - self.teacher.change_mode, "raise_right_hand", - *[ - ApplyMethod(pi.change, "confused", expressions[1]) - for pi in self.get_students() - ] - ) - self.wait() - self.play(Transform(low_expression, expressions[2])) - self.play( - GrowFromCenter(low_brace), - FadeIn(low_text), - ) - self.change_student_modes( - "sassy", "angry", "erm", - look_at_arg = low_expression, - added_anims = [Transform(low_expression, expressions[3])] - ) - for expression in expressions[4:]: - self.play(Transform(low_expression, expression)) - self.wait() - -class WritePythagoreanTriple(Scene): - def construct(self): - words = TextMobject("``Pythagorean triple''") - words.set_width(FRAME_WIDTH - LARGE_BUFF) - words.to_corner(DOWN+LEFT) - self.play(Write(words)) - self.wait(2) - -class ShowManyTriples(Scene): - def construct(self): - triples = [ - (u**2 - v**2, 2*u*v, u**2 + v**2) - for u in range(1, 15) - for v in range(1, u) - if fractions.gcd(u, v) == 1 and not (u%2 == v%2) - ][:40] - triangles = VGroup() - titles = VGroup() - for i, (a, b, c) in enumerate(triples): - triangle = Polygon(ORIGIN, a*RIGHT, a*RIGHT+b*UP) - triangle.set_color(WHITE) - max_width = max_height = 4 - triangle.set_height(max_height) - if triangle.get_width() > max_width: - triangle.set_width(max_width) - triangle.move_to(2*RIGHT) - num_strings = list(map(str, (a, b, c))) - labels = list(map(TexMobject, num_strings)) - for label, color in zip(labels, SIDE_COLORS): - label.set_color(color) - labels[0].next_to(triangle, DOWN) - labels[1].next_to(triangle, RIGHT) - labels[2].next_to(triangle.get_center(), UP+LEFT) - triangle.add(*labels) - - title = TexMobject( - str(a), "^2", "+", str(b), "^2", "=", str(c), "^2" - ) - for num, color in zip([a, b, c], SIDE_COLORS): - title.set_color_by_tex(str(num), color) - title.next_to(triangle, UP, LARGE_BUFF) - title.generate_target() - title.target.scale(0.5) - - title.target.move_to( - (-FRAME_X_RADIUS + MED_LARGE_BUFF + 2.7*(i//8))*RIGHT + \ - (FRAME_Y_RADIUS - MED_LARGE_BUFF - (i%8))*UP, - UP+LEFT - ) - - triangles.add(triangle) - titles.add(title) - - triangle = triangles[0] - title = titles[0] - self.play( - Write(triangle), - Write(title), - run_time = 2, - ) - self.wait() - self.play(MoveToTarget(title)) - for i in range(1, 17): - new_triangle = triangles[i] - new_title = titles[i] - if i < 4: - self.play( - Transform(triangle, new_triangle), - FadeIn(new_title) - ) - self.wait() - self.play(MoveToTarget(new_title)) - else: - self.play( - Transform(triangle, new_triangle), - FadeIn(new_title.target) - ) - self.wait() - self.play(FadeOut(triangle)) - self.play(LaggedStartMap( - FadeIn, - VGroup(*[ - title.target - for title in titles[17:] - ]), - run_time = 5 - )) - - self.wait(2) - -class BabylonianTablets(Scene): - def construct(self): - title = TextMobject("Plimpton 322 Tablets \\\\ (1800 BC)") - title.to_corner(UP+LEFT) - ac_pairs = [ - (119, 169), - (3367, 4825), - (4601, 6649), - (12709, 18541), - (65, 97), - (319, 481), - (2291, 3541), - (799, 1249), - (481, 769), - (4961, 8161), - (45, 75), - (1679, 2929), - (161, 289), - (1771, 3229), - (56, 106), - ] - triples = VGroup() - for a, c in ac_pairs: - b = int(np.sqrt(c**2 - a**2)) - tex = "%s^2 + %s^2 = %s^2"%tuple( - map("{:,}".format, [a, b, c]) - ) - tex = tex.replace(",", "{,}") - triple = TexMobject(tex) - triples.add(triple) - triples.arrange(DOWN, aligned_edge = LEFT) - triples.set_height(FRAME_HEIGHT - LARGE_BUFF) - triples.to_edge(RIGHT) - - self.add(title) - self.wait() - self.play(LaggedStartMap(FadeIn, triples, run_time = 5)) - self.wait() - -class AskAboutFavoriteProof(TeacherStudentsScene): - def construct(self): - self.student_says( - "What's you're \\\\ favorite proof?", - target_mode = "raise_right_hand" - ) - self.change_student_modes("happy", "raise_right_hand", "happy") - self.teacher_thinks("", target_mode = "thinking") - self.wait() - self.zoom_in_on_thought_bubble() - -class PythagoreanProof(Scene): - def construct(self): - self.add_title() - self.show_proof() - - def add_title(self): - title = TexMobject("a^2", "+", "b^2", "=", "c^2") - for color, char in zip(SIDE_COLORS, "abc"): - title.set_color_by_tex(char, color) - title.to_edge(UP) - self.add(title) - self.title = title - - def show_proof(self): - triangle = Polygon( - ORIGIN, 5*RIGHT, 5*RIGHT+12*UP, - stroke_color = WHITE, - stroke_width = 2, - fill_color = WHITE, - fill_opacity = 0.5 - ) - triangle.set_height(3) - triangle.center() - side_labels = self.get_triangle_side_labels(triangle) - triangle_copy = triangle.copy() - squares = self.get_abc_squares(triangle) - a_square, b_square, c_square = squares - - - self.add(triangle, triangle_copy) - self.play(Write(side_labels)) - self.wait() - self.play(*list(map(DrawBorderThenFill, squares))) - self.add_labels_to_squares(squares, side_labels) - self.wait() - self.play( - VGroup(triangle_copy, a_square, b_square).move_to, - 4*LEFT+2*DOWN, DOWN, - VGroup(triangle, c_square).move_to, - 4*RIGHT+2*DOWN, DOWN, - run_time = 2, - path_arc = np.pi/2, - ) - self.wait() - self.add_new_triangles( - triangle, - self.get_added_triangles_to_c_square(triangle, c_square) - ) - self.wait() - self.add_new_triangles( - triangle_copy, - self.get_added_triangles_to_ab_squares(triangle_copy, a_square) - ) - self.wait() - - big_squares = VGroup(*list(map( - self.get_big_square, - [triangle, triangle_copy] - ))) - negative_space_words = TextMobject( - "Same negative \\\\ space" - ) - negative_space_words.scale(0.75) - negative_space_words.shift(UP) - double_arrow = DoubleArrow(LEFT, RIGHT) - double_arrow.next_to(negative_space_words, DOWN) - - self.play( - FadeIn(big_squares), - Write(negative_space_words), - ShowCreation(double_arrow), - *list(map(FadeOut, squares)) - ) - self.wait(2) - self.play(*it.chain( - list(map(FadeIn, squares)), - list(map(Animation, big_squares)), - )) - self.wait(2) - - def add_labels_to_squares(self, squares, side_labels): - for label, square in zip(side_labels, squares): - label.target = TexMobject(label.get_tex_string() + "^2") - label.target.set_color(label.get_color()) - # label.target.scale(0.7) - label.target.move_to(square) - square.add(label) - - self.play(LaggedStartMap(MoveToTarget, side_labels)) - - def add_new_triangles(self, triangle, added_triangles): - brace = Brace(added_triangles, DOWN) - label = TexMobject("a", "+", "b") - label.set_color_by_tex("a", A_COLOR) - label.set_color_by_tex("b", B_COLOR) - label.next_to(brace, DOWN) - - self.play(ReplacementTransform( - VGroup(triangle.copy().set_fill(opacity = 0)), - added_triangles, - run_time = 2, - )) - self.play(GrowFromCenter(brace)) - self.play(Write(label)) - triangle.added_triangles = added_triangles - - def get_big_square(self, triangle): - square = Square(stroke_color = RED) - square.replace( - VGroup(triangle, triangle.added_triangles), - stretch = True - ) - square.scale_in_place(1.01) - return square - - ##### - - def get_triangle_side_labels(self, triangle): - a, b, c = list(map(TexMobject, "abc")) - for mob, color in zip([a, b, c], SIDE_COLORS): - mob.set_color(color) - a.next_to(triangle, DOWN) - b.next_to(triangle, RIGHT) - c.next_to(triangle.get_center(), LEFT) - return VGroup(a, b, c) - - def get_abc_squares(self, triangle): - a_square, b_square, c_square = squares = [ - Square( - stroke_color = color, - fill_color = color, - fill_opacity = 0.5, - ) - for color in SIDE_COLORS - ] - a_square.set_width(triangle.get_width()) - a_square.move_to(triangle.get_bottom(), UP) - b_square.set_height(triangle.get_height()) - b_square.move_to(triangle.get_right(), LEFT) - hyp_line = Line( - triangle.get_corner(UP+RIGHT), - triangle.get_corner(DOWN+LEFT), - ) - c_square.set_width(hyp_line.get_length()) - c_square.move_to(hyp_line.get_center(), UP) - c_square.rotate( - hyp_line.get_angle(), - about_point = hyp_line.get_center() - ) - - return a_square, b_square, c_square - - def get_added_triangles_to_c_square(self, triangle, c_square): - return VGroup(*[ - triangle.copy().rotate(i*np.pi/2, about_point = c_square.get_center()) - for i in range(1, 4) - ]) - - def get_added_triangles_to_ab_squares(self, triangle, a_square): - t1 = triangle.copy() - t1.rotate_in_place(np.pi) - group = VGroup(triangle, t1).copy() - group.rotate(-np.pi/2) - group.move_to(a_square.get_right(), LEFT) - t2, t3 = group - return VGroup(t1, t2, t3) - -class ReframeOnLattice(PiCreatureScene): - CONFIG = { - "initial_plane_center" : 3*LEFT + DOWN, - "new_plane_center" : ORIGIN, - "initial_unit_size" : 0.5, - "new_unit_size" : 0.8, - "dot_radius" : 0.075, - "dot_color" : YELLOW, - } - def construct(self): - self.remove(self.pi_creature) - self.add_plane() - self.wander_over_lattice_points() - self.show_whole_distance_examples() - self.resize_plane() - self.show_root_example() - self.view_as_complex_number() - self.mention_squaring_it() - self.work_out_square_algebraically() - self.walk_through_square_geometrically() - - def add_plane(self): - plane = ComplexPlane( - center_point = self.initial_plane_center, - unit_size = self.initial_unit_size, - stroke_width = 2, - secondary_line_ratio = 0, - ) - plane.axes.set_stroke(width = 4) - plane.coordinate_labels = VGroup() - for x in range(-8, 20, 2): - if x == 0: - continue - label = TexMobject(str(x)) - label.scale(0.5) - label.add_background_rectangle(opacity = 1) - label.next_to(plane.coords_to_point(x, 0), DOWN, SMALL_BUFF) - plane.coordinate_labels.add(label) - - self.add(plane, plane.coordinate_labels) - self.plane = plane - - def wander_over_lattice_points(self): - initial_examples = [(5, 3), (6, 8), (2, 7)] - integer_distance_examples = [(3, 4), (12, 5), (15, 8)] - dot_tuple_groups = VGroup() - for x, y in initial_examples + integer_distance_examples: - dot = Dot( - self.plane.coords_to_point(x, y), - color = self.dot_color, - radius = self.dot_radius, - ) - tuple_mob = TexMobject("(", str(x), ",", str(y), ")") - tuple_mob.add_background_rectangle() - tuple_mob.next_to(dot, UP+RIGHT, buff = 0) - dot_tuple_groups.add(VGroup(dot, tuple_mob)) - dot_tuple_group = dot_tuple_groups[0] - final_group = dot_tuple_groups[-len(integer_distance_examples)] - - all_dots = self.get_all_plane_dots() - - self.play(Write(dot_tuple_group, run_time = 2)) - self.wait() - for new_group in dot_tuple_groups[1:len(initial_examples)]: - self.play(Transform(dot_tuple_group, new_group)) - self.wait() - self.play(LaggedStartMap( - FadeIn, all_dots, - rate_func = there_and_back, - run_time = 3, - lag_ratio = 0.2, - )) - self.wait() - self.play(ReplacementTransform( - dot_tuple_group, final_group - )) - - self.integer_distance_dot_tuple_groups = VGroup( - *dot_tuple_groups[len(initial_examples):] - ) - - def show_whole_distance_examples(self): - dot_tuple_groups = self.integer_distance_dot_tuple_groups - for dot_tuple_group in dot_tuple_groups: - dot, tuple_mob = dot_tuple_group - p0 = self.plane.get_center_point() - p1 = dot.get_center() - triangle = Polygon( - p0, p1[0]*RIGHT + p0[1]*UP, p1, - stroke_width = 0, - fill_color = BLUE, - fill_opacity = 0.75, - ) - line = Line(p0, p1, color = dot.get_color()) - a, b = self.plane.point_to_coords(p1) - c = int(np.sqrt(a**2 + b**2)) - hyp_label = TexMobject(str(c)) - hyp_label.add_background_rectangle() - hyp_label.next_to( - triangle.get_center(), UP+LEFT, buff = SMALL_BUFF - ) - line.add(hyp_label) - - dot_tuple_group.triangle = triangle - dot_tuple_group.line = line - - group = dot_tuple_groups[0] - - self.play(Write(group.line)) - self.play(FadeIn(group.triangle), Animation(group.line)) - self.wait(2) - for new_group in dot_tuple_groups[1:]: - self.play( - Transform(group, new_group), - Transform(group.triangle, new_group.triangle), - Transform(group.line, new_group.line), - ) - self.wait(2) - self.play(*list(map(FadeOut, [group, group.triangle, group.line]))) - - def resize_plane(self): - new_plane = ComplexPlane( - plane_center = self.new_plane_center, - unit_size = self.new_unit_size, - y_radius = 8, - x_radius = 11, - stroke_width = 2, - secondary_line_ratio = 0, - ) - new_plane.axes.set_stroke(width = 4) - self.plane.generate_target() - self.plane.target.unit_size = self.new_unit_size - self.plane.target.plane_center = self.new_plane_center - self.plane.target.shift( - new_plane.coords_to_point(0, 0) - \ - self.plane.target.coords_to_point(0, 0) - ) - self.plane.target.scale( - self.new_unit_size / self.initial_unit_size - ) - coordinate_labels = self.plane.coordinate_labels - for coord in coordinate_labels: - x = int(coord.get_tex_string()) - coord.generate_target() - coord.target.scale(1.5) - coord.target.next_to( - new_plane.coords_to_point(x, 0), - DOWN, buff = SMALL_BUFF - ) - - - self.play( - MoveToTarget(self.plane), - *list(map(MoveToTarget, self.plane.coordinate_labels)), - run_time = 2 - ) - self.remove(self.plane) - self.plane = new_plane - self.plane.coordinate_labels = coordinate_labels - self.add(self.plane, coordinate_labels) - self.wait() - - def show_root_example(self): - x, y = (2, 1) - point = self.plane.coords_to_point(x, y) - dot = Dot( - point, - color = self.dot_color, - radius = self.dot_radius - ) - tuple_label = TexMobject(str((x, y))) - tuple_label.add_background_rectangle() - tuple_label.next_to(dot, RIGHT, SMALL_BUFF) - line = Line(self.plane.get_center_point(), point) - line.set_color(dot.get_color()) - distance_labels = VGroup() - for tex in "2^2 + 1^2", "5": - pre_label = TexMobject("\\sqrt{%s}"%tex) - rect = BackgroundRectangle(pre_label) - label = VGroup( - rect, - VGroup(*pre_label[:2]), - VGroup(*pre_label[2:]), - ) - label.scale(0.8) - label.next_to(line.get_center(), UP, SMALL_BUFF) - label.rotate( - line.get_angle(), - about_point = line.get_center() - ) - distance_labels.add(label) - - self.play( - ShowCreation(line), - DrawBorderThenFill( - dot, - stroke_width = 3, - stroke_color = PINK - ) - ) - self.play(Write(tuple_label)) - self.wait() - self.play(FadeIn(distance_labels[0])) - self.wait(2) - self.play(Transform(*distance_labels)) - self.wait(2) - - self.distance_label = distance_labels[0] - self.example_dot = dot - self.example_line = line - self.example_tuple_label = tuple_label - - def view_as_complex_number(self): - imag_coords = VGroup() - for y in range(-4, 5, 2): - if y == 0: - continue - label = TexMobject("%di"%y) - label.add_background_rectangle() - label.scale(0.75) - label.next_to( - self.plane.coords_to_point(0, y), - LEFT, SMALL_BUFF - ) - imag_coords.add(label) - tuple_label = self.example_tuple_label - new_label = TexMobject("2+i") - new_label.add_background_rectangle() - new_label.next_to( - self.example_dot, - DOWN+RIGHT, buff = 0, - ) - - self.play(Write(imag_coords)) - self.wait() - self.play(FadeOut(tuple_label)) - self.play(FadeIn(new_label)) - self.wait(2) - - self.example_label = new_label - self.plane.coordinate_labels.add(*imag_coords) - - def mention_squaring_it(self): - morty = self.pi_creature - arrow = Arrow( - self.plane.coords_to_point(2, 1), - self.plane.coords_to_point(3, 4), - path_arc = np.pi/3, - color = MAROON_B - ) - square_label = TexMobject("z \\to z^2") - square_label.set_color(arrow.get_color()) - square_label.add_background_rectangle() - square_label.next_to( - arrow.point_from_proportion(0.5), - RIGHT, buff = SMALL_BUFF - ) - - self.play(FadeIn(morty)) - self.play( - PiCreatureSays( - morty, "Try squaring \\\\ it!", - target_mode = "hooray", - bubble_kwargs = {"width" : 4, "height" : 3}, - ) - ) - self.play( - ShowCreation(arrow), - Write(square_label) - ) - self.wait() - self.play(RemovePiCreatureBubble( - morty, target_mode = "pondering", - look_at_arg = self.example_label - )) - - def work_out_square_algebraically(self): - rect = Rectangle( - height = 3.5, width = 6.5, - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 0.8 - ) - rect.to_corner(UP+LEFT, buff = 0) - top_line = TexMobject("(2+i)", "(2+i)") - top_line.next_to(rect.get_top(), DOWN) - second_line = TexMobject( - "2^2 + 2i + 2i + i^2" - ) - second_line.next_to(top_line, DOWN, MED_LARGE_BUFF) - final_line = TexMobject("3 + 4i") - final_line.next_to(second_line, DOWN, MED_LARGE_BUFF) - - result_dot = Dot( - self.plane.coords_to_point(3, 4), - color = MAROON_B, - radius = self.dot_radius - ) - - self.play( - FadeIn(rect), - ReplacementTransform( - VGroup(self.example_label[1].copy()), - top_line - ), - run_time = 2 - ) - self.wait() - - #From top line to second line - index_alignment_lists = [ - [(0, 1, 0), (1, 1, 1)], - [(0, 2, 2), (0, 1, 3), (1, 3, 4)], - [(0, 2, 5), (1, 1, 6), (0, 3, 7)], - [(0, 2, 8), (0, 3, 9), (1, 3, 10)], - ] - for index_alignment in index_alignment_lists: - self.play(*[ - ReplacementTransform( - top_line[i][j].copy(), second_line[k], - ) - for i, j, k in index_alignment - ]) - self.wait(2) - - #From second line to final line - index_alignment_lists = [ - [(0, 0), (1, 0), (9, 0), (10, 0)], - [(2, 1), (3, 2), (4, 3), (6, 2), (7, 3)], - ] - for index_alignment in index_alignment_lists: - self.play(*[ - ReplacementTransform( - second_line[i].copy(), final_line[j], - run_time = 1.5 - ) - for i, j in index_alignment - ]) - self.wait() - - #Move result to appropriate place - result_label = final_line.copy() - result_label.add_background_rectangle() - self.play( - result_label.next_to, result_dot, UP+RIGHT, SMALL_BUFF, - Animation(final_line), - run_time = 2, - ) - self.play(DrawBorderThenFill( - result_dot, - stroke_width = 4, - stroke_color = PINK - )) - self.wait(2) - - def walk_through_square_geometrically(self): - line = self.example_line - dot = self.example_dot - example_label = self.example_label - distance_label = self.distance_label - - alt_line = line.copy().set_color(RED) - arc = Arc( - angle = line.get_angle(), - radius = 0.7, - color = WHITE - ) - double_arc = Arc( - angle = 2*line.get_angle(), - radius = 0.8, - color = RED, - ) - theta = TexMobject("\\theta") - two_theta = TexMobject("2\\theta") - for tex_mob, arc_mob in (theta, arc), (two_theta, double_arc): - tex_mob.scale(0.75) - tex_mob.add_background_rectangle() - point = arc_mob.point_from_proportion(0.5) - tex_mob.move_to(point) - tex_mob.shift(tex_mob.get_width()*point/get_norm(point)) - - - self.play(self.pi_creature.change, "happy", arc) - self.play(ShowCreation(alt_line)) - self.play(ShowCreation(line)) - self.remove(alt_line) - self.wait() - self.play( - ShowCreation(arc), - Write(theta) - ) - self.wait() - self.play(Indicate(distance_label)) - self.wait() - - #Multiply full plane under everything - everything = VGroup(*self.get_top_level_mobjects()) - everything.remove(self.plane) - self.plane.save_state() - ghost_plane = self.plane.copy().fade() - method_args_list = [ - (self.plane.rotate, (line.get_angle(),)), - (self.plane.scale, (np.sqrt(5),)), - (self.plane.restore, ()), - ] - for method, args in method_args_list: - self.play( - Animation(ghost_plane), - ApplyMethod(method, *args), - Animation(everything), - run_time = 1.5 - ) - self.wait() - - #Multiply number by itself - ghost_arc = arc.copy().fade() - ghost_line = line.copy().fade() - ghots_dot = dot.copy().fade() - self.add(ghost_arc, ghost_line, ghots_dot) - - self.play( - VGroup( - line, dot, distance_label, - ).rotate, line.get_angle(), - Transform(arc, double_arc), - Transform(theta, two_theta), - ) - self.wait() - five = distance_label[2] - distance_label.remove(five) - for mob in five, line, dot: - mob.generate_target() - line.target.scale(np.sqrt(5)) - five.target.shift(line.target.get_center()-line.get_center()) - dot.target.move_to(line.target.get_end()) - self.play( - FadeOut(distance_label), - *list(map(MoveToTarget, [five, line, dot])), - run_time = 2 - ) - self.wait(2) - - #### - - def get_all_plane_dots(self): - x_min, y_min = list(map(int, self.plane.point_to_coords( - FRAME_X_RADIUS*LEFT + FRAME_Y_RADIUS*DOWN - ))) - x_max, y_max = list(map(int, self.plane.point_to_coords( - FRAME_X_RADIUS*RIGHT + FRAME_Y_RADIUS*UP - ))) - result = VGroup(*[ - Dot( - self.plane.coords_to_point(x, y), - radius = self.dot_radius, - color = self.dot_color, - ) - for x in range(int(x_min), int(x_max)+1) - for y in range(int(y_min), int(y_max)+1) - ]) - result.sort(lambda p : np.dot(p, UP+RIGHT)) - return result - - def create_pi_creature(self): - morty = Mortimer().flip() - morty.to_corner(DOWN+LEFT, buff = MED_SMALL_BUFF) - return morty - -class TimeToGetComplex(TeacherStudentsScene): - def construct(self): - self.teacher_says("Time to \\\\ get complex") - self.change_student_modes("angry", "sassy", "pleading") - self.wait(2) - -class OneMoreExample(Scene): - CONFIG = { - "unit_size" : 0.5, - "plane_center" : 3*LEFT + 3*DOWN, - "dot_color" : YELLOW, - "x_label_range" : list(range(-6, 25, 3)), - "y_label_range" : list(range(3, 13, 3)), - } - def construct(self): - self.add_plane() - self.add_point() - self.square_algebraically() - self.plot_result() - self.show_triangle() - - def add_plane(self): - plane = ComplexPlane( - unit_size = self.unit_size, - center_point = self.plane_center, - stroke_width = 2, - ) - plane.axes.set_stroke(width = 4) - coordinate_labels = VGroup() - for x in self.x_label_range: - if x == 0: - continue - coord = TexMobject(str(x)) - coord.scale(0.75) - coord.next_to(plane.coords_to_point(x, 0), DOWN, SMALL_BUFF) - coord.add_background_rectangle() - coordinate_labels.add(coord) - for y in self.y_label_range: - if y == 0: - continue - coord = TexMobject("%di"%y) - coord.scale(0.75) - coord.next_to(plane.coords_to_point(0, y), LEFT, SMALL_BUFF) - coord.add_background_rectangle() - coordinate_labels.add(coord) - self.add(plane, coordinate_labels) - - self.plane = plane - self.plane.coordinate_labels = coordinate_labels - - def add_point(self): - point = self.plane.coords_to_point(3, 2) - dot = Dot(point, color = self.dot_color) - line = Line(self.plane.get_center_point(), point) - line.set_color(dot.get_color()) - number_label = TexMobject("3+2i") - number_label.add_background_rectangle() - number_label.next_to(dot, RIGHT, SMALL_BUFF) - distance_labels = VGroup() - for tex in "3^2 + 2^2", "13": - pre_label = TexMobject("\\sqrt{%s}"%tex) - label = VGroup( - BackgroundRectangle(pre_label), - VGroup(*pre_label[:2]), - VGroup(*pre_label[2:]), - ) - label.scale(0.75) - label.next_to(line.get_center(), UP, SMALL_BUFF) - label.rotate( - line.get_angle(), - about_point = line.get_center() - ) - distance_labels.add(label) - - self.play( - FadeIn(number_label), - ShowCreation(line), - DrawBorderThenFill(dot) - ) - self.play(Write(distance_labels[0])) - self.wait() - self.play(ReplacementTransform(*distance_labels)) - self.wait() - - self.distance_label = distance_labels[1] - self.line = line - self.dot = dot - self.number_label = number_label - - def square_algebraically(self): - #Crazy hacky. To anyone looking at this, for God's - #sake, don't mimic this. - rect = Rectangle( - height = 3.5, width = 7, - stroke_color = WHITE, - stroke_width = 2, - fill_color = BLACK, - fill_opacity = 0.8 - ) - rect.to_corner(UP+RIGHT, buff = 0) - number = self.number_label[1].copy() - - top_line = TexMobject("(3+2i)", "(3+2i)") - for part in top_line: - for i, color in zip([1, 3], [BLUE, YELLOW]): - part[i].set_color(color) - second_line = TexMobject( - "\\big( 3^2 + (2i)^2 \\big) + " + \ - "\\big(3 \\cdot 2 + 2 \\cdot 3 \\big)i" - ) - for i in 1, 12, 18: - second_line[i].set_color(BLUE) - for i in 5, 14, 16: - second_line[i].set_color(YELLOW) - second_line.scale(0.9) - final_line = TexMobject("5 + 12i") - for i in 0, 2, 3: - final_line[i].set_color(GREEN) - lines = VGroup(top_line, second_line, final_line) - lines.arrange(DOWN, buff = MED_LARGE_BUFF) - lines.next_to(rect.get_top(), DOWN) - minus = TexMobject("-").scale(0.9) - minus.move_to(second_line[3]) - - self.play( - FadeIn(rect), - Transform(VGroup(number), top_line), - run_time = 2 - ) - self.wait() - - index_alignment_lists = [ - [(0, 0, 0), (0, 1, 1), (1, 1, 2), (1, 5, 9)], - [ - (0, 2, 3), (1, 3, 4), (0, 3, 5), - (0, 4, 6), (1, 4, 7), (1, 3, 8) - ], - [ - (0, 2, 10), (0, 0, 11), (0, 1, 12), - (1, 3, 13), (1, 3, 14), (1, 5, 19), - (0, 4, 20), (1, 4, 20), - ], - [ - (0, 2, 15), (0, 3, 16), - (1, 1, 17), (1, 1, 18), - ], - ] - for index_alignment in index_alignment_lists[:2]: - self.play(*[ - ReplacementTransform( - top_line[i][j].copy(), second_line[k], - run_time = 1.5 - ) - for i, j, k in index_alignment - ]) - self.wait() - self.play( - Transform(second_line[3], minus), - FadeOut(VGroup(*[ - second_line[i] - for i in (4, 6, 7) - ])), - second_line[5].shift, 0.35*RIGHT, - ) - self.play(VGroup(*second_line[:4]).shift, 0.55*RIGHT) - self.wait() - for index_alignment in index_alignment_lists[2:]: - self.play(*[ - ReplacementTransform( - top_line[i][j].copy(), second_line[k], - run_time = 1.5 - ) - for i, j, k in index_alignment - ]) - self.wait() - self.play(FadeIn(final_line)) - self.wait() - - self.final_line = final_line - - def plot_result(self): - result_label = self.final_line.copy() - result_label.add_background_rectangle() - - point = self.plane.coords_to_point(5, 12) - dot = Dot(point, color = GREEN) - line = Line(self.plane.get_center_point(), point) - line.set_color(dot.get_color()) - distance_label = TexMobject("13") - distance_label.add_background_rectangle() - distance_label.next_to(line.get_center(), UP+LEFT, SMALL_BUFF) - - self.play( - result_label.next_to, dot, UP+LEFT, SMALL_BUFF, - Animation(self.final_line), - DrawBorderThenFill(dot) - ) - self.wait() - self.play(*[ - ReplacementTransform(m1.copy(), m2) - for m1, m2 in [ - (self.line, line), - (self.distance_label, distance_label) - ] - ]) - self.wait() - - def show_triangle(self): - triangle = Polygon(*[ - self.plane.coords_to_point(x, y) - for x, y in [(0, 0), (5, 0), (5, 12)] - ]) - triangle.set_stroke(WHITE, 1) - triangle.set_fill(BLUE, opacity = 0.75) - - self.play( - FadeIn(triangle), - Animation(VGroup( - self.line, self.dot, - self.number_label[1], *self.distance_label[1:] - )), - run_time = 2 - ) - self.wait(2) - -class ThisIsMagic(TeacherStudentsScene): - def construct(self): - self.student_says( - "This is magic", target_mode = "hooray" - ) - self.play(self.teacher.change, "happy") - self.wait(2) - -class GeneralExample(OneMoreExample): - CONFIG = { - "number" : complex(4, 1), - "square_color" : MAROON_B, - "result_label_vect" : UP+LEFT, - } - def construct(self): - self.add_plane() - self.square_point() - - def square_point(self): - z = self.number - z_point = self.plane.number_to_point(z) - zero_point = self.plane.number_to_point(0) - dot = Dot(z_point, color = self.dot_color) - line = Line(zero_point, z_point) - line.set_color(dot.get_color()) - label = TexMobject(complex_string_with_i(z)) - label.add_background_rectangle() - label.next_to(dot, RIGHT, SMALL_BUFF) - - square_point = self.plane.number_to_point(z**2) - square_dot = Dot(square_point, color = self.square_color) - square_line = Line(zero_point, square_point) - square_line.set_color(square_dot.get_color()) - square_label = TexMobject(complex_string_with_i(z**2)) - square_label.add_background_rectangle() - square_label.next_to(square_dot, UP+RIGHT, SMALL_BUFF) - result_length_label = TexMobject(str(int(abs(z**2)))) - result_length_label.next_to( - square_line.get_center(), self.result_label_vect - ) - result_length_label.add_background_rectangle() - - arrow = Arrow( - z_point, square_point, - # buff = SMALL_BUFF, - path_arc = np.pi/2 - ) - arrow.set_color(WHITE) - z_to_z_squared = TexMobject("z", "\\to", "z^2") - z_to_z_squared.set_color_by_tex("z", dot.get_color()) - z_to_z_squared.set_color_by_tex("z^2", square_dot.get_color()) - z_to_z_squared.next_to( - arrow.point_from_proportion(0.5), - RIGHT, MED_SMALL_BUFF - ) - z_to_z_squared.add_to_back( - BackgroundRectangle(VGroup( - z_to_z_squared[2][0], - *z_to_z_squared[:-1] - )), - BackgroundRectangle(z_to_z_squared[2][1]) - ) - - - self.play( - Write(label), - ShowCreation(line), - DrawBorderThenFill(dot) - ) - self.wait() - self.play( - ShowCreation(arrow), - FadeIn(z_to_z_squared), - Animation(label), - ) - self.play(*[ - ReplacementTransform( - start.copy(), target, - path_arc = np.pi/2, - run_time = 1.5 - ) - for start, target in [ - (dot, square_dot), - (line, square_line), - (label, square_label), - ] - ]) - self.wait() - self.play(Write(result_length_label)) - self.wait() - - self.example_dot = dot - self.example_label = label - self.example_line = line - self.square_dot = square_dot - self.square_label = square_label - self.square_line = square_line - self.z_to_z_squared = z_to_z_squared - self.z_to_z_squared_arrow = arrow - self.result_length_label = result_length_label - -class BoringExample(GeneralExample): - CONFIG = { - "number" : complex(2, 2), - "result_label_vect" : RIGHT, - } - def construct(self): - self.add_plane() - self.square_point() - self.show_associated_triplet() - - def show_associated_triplet(self): - arrow = Arrow(LEFT, RIGHT, color = GREEN) - arrow.next_to(self.square_label, RIGHT) - triple = TexMobject("0^2 + 8^2 = 8^2") - for part, color in zip(triple[::3], SIDE_COLORS): - part.set_color(color) - triple.add_background_rectangle() - triple.next_to(arrow, RIGHT) - - morty = Mortimer() - morty.next_to(self.plane.coords_to_point(12, 0), UP) - - self.play( - ShowCreation(arrow), - FadeIn(morty) - ) - self.play( - Write(triple), - morty.change, "raise_right_hand", triple - ) - self.play(Blink(morty)) - self.play(morty.change, "tired") - self.wait(2) - self.play(Blink(morty)) - self.wait() - -class FiveTwoExample(GeneralExample): - CONFIG = { - "number" : complex(5, 2), - "unit_size" : 0.25, - "x_label_range" : list(range(-10, 40, 5)), - "y_label_range" : list(range(0, 30, 5)), - } - -class WriteGeneralFormula(GeneralExample): - CONFIG = { - "plane_center" : 2*RIGHT, - "x_label_range" : [], - "y_label_range" : [], - "unit_size" : 0.7, - "number" : complex(2, 1), - } - def construct(self): - self.add_plane() - self.show_squaring() - self.expand_square() - self.draw_triangle() - self.show_uv_to_triples() - - def show_squaring(self): - self.force_skipping() - self.square_point() - dot = self.example_dot - old_label = self.example_label - line = self.example_line - square_dot = self.square_dot - old_square_label = self.square_label - square_line = self.square_line - z_to_z_squared = self.z_to_z_squared - arrow = self.z_to_z_squared_arrow - result_length_label = self.result_length_label - self.clear() - self.add(self.plane, self.plane.coordinate_labels) - self.revert_to_original_skipping_status() - - label = TexMobject("u+vi") - label.move_to(old_label, LEFT) - label.add_background_rectangle() - square_label = TexMobject("(u+vi)^2") - square_label.move_to(old_square_label, LEFT) - square_label.add_background_rectangle() - - self.add(label, dot, line) - self.play( - ShowCreation(arrow), - FadeIn(z_to_z_squared) - ) - self.play(*[ - ReplacementTransform( - start.copy(), target, - run_time = 1.5, - path_arc = np.pi/2 - ) - for start, target in [ - (dot, square_dot), - (line, square_line), - (label, square_label), - ] - ]) - - self.example_label = label - self.square_label = square_label - - def expand_square(self): - rect = Rectangle( - height = 2.5, width = 7, - stroke_width = 0, - fill_color = BLACK, - fill_opacity = 0.8, - ) - rect.to_corner(UP+LEFT, buff = 0) - top_line = TexMobject("(u+vi)(u+vi)") - for i in 1, 7: - top_line[i].set_color(U_COLOR) - top_line[i+2].set_color(V_COLOR) - top_line.next_to(rect.get_top(), DOWN) - second_line = TexMobject( - "\\big(", "u^2 - v^2", "\\big)", "+", - "\\big(", "2uv", "\\big)", "i" - ) - for i, j in (1, 0), (5, 1): - second_line[i][j].set_color(U_COLOR) - for i, j in (1, 3), (5, 2): - second_line[i][j].set_color(V_COLOR) - second_line.next_to(top_line, DOWN, MED_LARGE_BUFF) - real_part = second_line[1] - imag_part = second_line[5] - for part in real_part, imag_part: - part.add_to_back(BackgroundRectangle(part)) - - z = self.number**2 - square_point = self.plane.number_to_point(z) - zero_point = self.plane.number_to_point(0) - real_part_point = self.plane.number_to_point(z.real) - real_part_line = Line(zero_point, real_part_point) - imag_part_line = Line(real_part_point, square_point) - for line in real_part_line, imag_part_line: - line.set_color(self.square_color) - - - self.play(*list(map(FadeIn, [rect, top_line, second_line]))) - self.wait() - self.play( - real_part.copy().next_to, real_part_line.copy(), - DOWN, SMALL_BUFF, - ShowCreation(real_part_line) - ) - self.wait() - self.play( - FadeOut(VGroup( - self.example_label, self.example_dot, self.example_line, - self.z_to_z_squared, self.z_to_z_squared_arrow - )), - imag_part.copy().next_to, imag_part_line.copy(), - RIGHT, SMALL_BUFF, - ShowCreation(imag_part_line) - ) - self.wait() - - self.corner_rect = rect - - def draw_triangle(self): - hyp_length = TexMobject("u", "^2", "+", "v", "^2") - hyp_length.set_color_by_tex("u", U_COLOR) - hyp_length.set_color_by_tex("v", V_COLOR) - hyp_length.add_background_rectangle() - line = self.square_line - hyp_length.next_to(line.get_center(), UP, SMALL_BUFF) - hyp_length.rotate( - line.get_angle(), - about_point = line.get_center() - ) - triangle = Polygon( - ORIGIN, RIGHT, RIGHT+UP, - stroke_width = 0, - fill_color = MAROON_B, - fill_opacity = 0.5, - ) - triangle.replace(line, stretch = True) - - self.play(Write(hyp_length)) - self.wait() - self.play(FadeIn(triangle)) - self.wait() - - def show_uv_to_triples(self): - rect = self.corner_rect.copy() - rect.stretch_to_fit_height(FRAME_HEIGHT) - rect.move_to(self.corner_rect.get_bottom(), UP) - - h_line = Line(rect.get_left(), rect.get_right()) - h_line.next_to(rect.get_top(), DOWN, LARGE_BUFF) - v_line = Line(rect.get_top(), rect.get_bottom()) - v_line.shift(1.3*LEFT) - uv_title = TexMobject("(u, v)") - triple_title = TexMobject("(u^2 - v^2, 2uv, u^2 + v^2)") - uv_title.scale(0.75) - triple_title.scale(0.75) - uv_title.next_to( - h_line.point_from_proportion(1./6), - UP, SMALL_BUFF - ) - triple_title.next_to( - h_line.point_from_proportion(2./3), - UP, SMALL_BUFF - ) - - pairs = [(2, 1), (3, 2), (4, 1), (4, 3), (5, 2), (5, 4)] - pair_mobs = VGroup() - triple_mobs = VGroup() - for u, v in pairs: - a, b, c = u**2 - v**2, 2*u*v, u**2 + v**2 - pair_mob = TexMobject("(", str(u), ",", str(v), ")") - pair_mob.set_color_by_tex(str(u), U_COLOR) - pair_mob.set_color_by_tex(str(v), V_COLOR) - triple_mob = TexMobject("(%d, %d, %d)"%(a, b, c)) - pair_mobs.add(pair_mob) - triple_mobs.add(triple_mob) - pair_mob.scale(0.75) - triple_mob.scale(0.75) - pair_mobs.arrange(DOWN) - pair_mobs.next_to(uv_title, DOWN, MED_LARGE_BUFF) - triple_mobs.arrange(DOWN) - triple_mobs.next_to(triple_title, DOWN, MED_LARGE_BUFF) - - self.play(*list(map(FadeIn, [ - rect, h_line, v_line, - uv_title, triple_title - ]))) - self.play(*[ - LaggedStartMap( - FadeIn, mob, - run_time = 5, - lag_ratio = 0.2 - ) - for mob in (pair_mobs, triple_mobs) - ]) - -class VisualizeZSquared(Scene): - CONFIG = { - "initial_unit_size" : 0.4, - "final_unit_size" : 0.1, - "plane_center" : 3*LEFT + 2*DOWN, - "x_label_range" : list(range(-12, 24, 4)), - "y_label_range" : list(range(-4, 24, 4)), - "dot_color" : YELLOW, - "square_color" : MAROON_B, - "big_dot_radius" : 0.075, - "dot_radius" : 0.05, - } - def construct(self): - self.force_skipping() - - self.add_plane() - self.write_z_to_z_squared() - self.draw_arrows() - self.draw_dots() - self.add_colored_grid() - self.apply_transformation() - self.show_triangles() - self.zoom_out() - self.show_more_triangles() - - def add_plane(self): - width = (FRAME_X_RADIUS+abs(self.plane_center[0]))/self.final_unit_size - height = (FRAME_Y_RADIUS+abs(self.plane_center[1]))/self.final_unit_size - background_plane = ComplexPlane( - x_radius = width, - y_radius = height, - stroke_width = 2, - stroke_color = BLUE_E, - secondary_line_ratio = 0, - ) - background_plane.axes.set_stroke(width = 4) - - background_plane.scale(self.initial_unit_size) - background_plane.shift(self.plane_center) - - coordinate_labels = VGroup() - z_list = np.append( - self.x_label_range, - complex(0, 1)*np.array(self.y_label_range) - ) - for z in z_list: - if z == 0: - continue - if z.imag == 0: - tex = str(int(z.real)) - else: - tex = str(int(z.imag)) + "i" - label = TexMobject(tex) - label.scale(0.75) - label.add_background_rectangle() - point = background_plane.number_to_point(z) - if z.imag == 0: - label.next_to(point, DOWN, SMALL_BUFF) - else: - label.next_to(point, LEFT, SMALL_BUFF) - coordinate_labels.add(label) - - self.add(background_plane, coordinate_labels) - self.background_plane = background_plane - self.coordinate_labels = coordinate_labels - - def write_z_to_z_squared(self): - z_to_z_squared = TexMobject("z", "\\to", "z^2") - z_to_z_squared.set_color_by_tex("z", YELLOW) - z_to_z_squared.set_color_by_tex("z^2", MAROON_B) - z_to_z_squared.add_background_rectangle() - z_to_z_squared.to_edge(UP) - z_to_z_squared.shift(2*RIGHT) - - self.play(Write(z_to_z_squared)) - self.wait() - self.z_to_z_squared = z_to_z_squared - - def draw_arrows(self): - z_list = [ - complex(2, 1), - complex(3, 2), - complex(0, 1), - complex(-1, 0), - ] - - arrows = VGroup() - dots = VGroup() - for z in z_list: - z_point, square_point, mid_point = [ - self.background_plane.number_to_point(z**p) - for p in (1, 2, 1.5) - ] - angle = Line(mid_point, square_point).get_angle() - angle -= Line(z_point, mid_point).get_angle() - angle *= 2 - arrow = Arrow( - z_point, square_point, - path_arc = angle, - color = WHITE, - tip_length = 0.15, - buff = SMALL_BUFF, - ) - - z_dot, square_dot = [ - Dot( - point, color = color, - radius = self.big_dot_radius, - ) - for point, color in [ - (z_point, self.dot_color), - (square_point, self.square_color), - ] - ] - z_label = TexMobject(complex_string_with_i(z)) - square_label = TexMobject(complex_string_with_i(z**2)) - for label, point in (z_label, z_point), (square_label, square_point): - if abs(z) > 2: - vect = RIGHT - else: - vect = point - self.plane_center - vect /= get_norm(vect) - if abs(vect[1]) < 0.1: - vect[1] = -1 - label.next_to(point, vect) - label.add_background_rectangle() - - self.play(*list(map(FadeIn, [z_label, z_dot]))) - self.wait() - self.play(ShowCreation(arrow)) - self.play(ReplacementTransform( - z_dot.copy(), square_dot, - path_arc = angle - )) - self.play(FadeIn(square_label)) - self.wait() - self.play( - FadeOut(z_label), - FadeOut(square_label), - Animation(arrow) - ) - - arrows.add(arrow) - dots.add(z_dot, square_dot) - self.wait() - self.play(*list(map(FadeOut, [ - dots, arrows, self.z_to_z_squared - ]))) - - def draw_dots(self): - min_corner, max_corner = [ - self.background_plane.point_to_coords( - u*FRAME_X_RADIUS*RIGHT + u*FRAME_Y_RADIUS*UP - ) - for u in (-1, 1) - ] - x_min, y_min = list(map(int, min_corner[:2])) - x_max, y_max = list(map(int, max_corner[:2])) - - dots = VGroup(*[ - Dot( - self.background_plane.coords_to_point(x, y), - color = self.dot_color, - radius = self.dot_radius, - ) - for x in range(x_min, x_max+1) - for y in range(y_min, y_max+1) - ]) - dots.sort(lambda p : np.dot(p, UP+RIGHT)) - - self.add_foreground_mobject(self.coordinate_labels) - self.play(LaggedStartMap( - DrawBorderThenFill, dots, - stroke_width = 3, - stroke_color = PINK, - run_time = 3, - lag_ratio = 0.2 - )) - self.wait() - - self.dots = dots - - def add_colored_grid(self): - color_grid = self.get_color_grid() - - self.play( - self.background_planes.set_stroke, None, 1, - LaggedStartMap( - FadeIn, color_grid, - run_time = 2 - ), - Animation(self.dots), - ) - self.wait() - - self.color_grid = color_grid - - def apply_transformation(self): - for dot in self.dots: - dot.start_point = dot.get_center() - def update_dot(dot, alpha): - event = list(dot.start_point) + [alpha] - dot.move_to(self.homotopy(*event)) - return dot - self.play( - Homotopy(self.homotopy, self.color_grid), - *[ - UpdateFromAlphaFunc(dot, update_dot) - for dot in self.dots - ], - run_time = 3 - ) - self.wait(2) - self.play(self.color_grid.set_stroke, None, 3) - self.wait() - scale_factor = self.big_dot_radius/self.dot_radius - self.play(LaggedStartMap( - ApplyMethod, self.dots, - lambda d : (d.scale_in_place, scale_factor), - rate_func = there_and_back, - run_time = 3 - )) - self.wait() - - def show_triangles(self): - z_list = [ - complex(u, v)**2 - for u, v in [(2, 1), (3, 2), (4, 1)] - ] - triangles = self.get_triangles(z_list) - triangle = triangles[0] - triangle.save_state() - triangle.scale(0.01, about_point = triangle.tip) - - self.play(triangle.restore, run_time = 2) - self.wait(2) - for new_triangle in triangles[1:]: - self.play(Transform(triangle, new_triangle)) - self.wait(2) - self.play(FadeOut(triangle)) - - def zoom_out(self): - self.remove_foreground_mobject(self.coordinate_labels) - movers = [ - self.background_plane, - self.color_grid, - self.dots, - self.coordinate_labels, - ] - scale_factor = self.final_unit_size/self.initial_unit_size - for mover in movers: - mover.generate_target() - mover.target.scale( - scale_factor, - about_point = self.plane_center - ) - for dot in self.dots.target: - dot.scale_in_place(1./scale_factor) - self.background_plane.target.fade() - - self.revert_to_original_skipping_status() - self.play( - *list(map(MoveToTarget, movers)), - run_time = 3 - ) - self.wait(2) - - def show_more_triangles(self): - z_list = [ - complex(u, v)**2 - for u in range(4, 7) - for v in range(1, u) - ] - triangles = self.get_triangles(z_list) - triangle = triangles[0] - - self.play(FadeOut(triangle)) - self.wait(2) - for new_triangle in triangles[1:]: - self.play(Transform(triangle, new_triangle)) - self.wait(2) - - ### - - def get_color_grid(self): - width = (FRAME_X_RADIUS+abs(self.plane_center[0]))/self.initial_unit_size - height = (FRAME_Y_RADIUS+abs(self.plane_center[1]))/self.initial_unit_size - color_grid = ComplexPlane( - x_radius = width, - y_radius = int(height), - secondary_line_ratio = 0, - stroke_width = 2, - ) - color_grids.set_color_by_gradient( - *[GREEN, RED, MAROON_B, TEAL]*2 - ) - color_grid.remove(color_grid.axes[0]) - for line in color_grid.family_members_with_points(): - center = line.get_center() - if center[0] <= 0 and abs(center[1]) < 0.01: - line_copy = line.copy() - line.scale(0.499, about_point = line.get_start()) - line_copy.scale(0.499, about_point = line_copy.get_end()) - color_grid.add(line_copy) - color_grid.scale(self.initial_unit_size) - color_grid.shift(self.plane_center) - color_grid.prepare_for_nonlinear_transform() - return color_grid - - def get_triangles(self, z_list): - triangles = VGroup() - for z in z_list: - point = self.background_plane.number_to_point(z) - line = Line(self.plane_center, point) - triangle = Polygon( - ORIGIN, RIGHT, RIGHT+UP, - stroke_color = BLUE, - stroke_width = 2, - fill_color = BLUE, - fill_opacity = 0.5, - ) - triangle.replace(line, stretch = True) - a = int(z.real) - b = int(z.imag) - c = int(abs(z)) - a_label, b_label, c_label = labels = [ - TexMobject(str(num)) - for num in (a, b, c) - ] - for label in b_label, c_label: - label.add_background_rectangle() - a_label.next_to(triangle.get_bottom(), UP, SMALL_BUFF) - b_label.next_to(triangle, RIGHT, SMALL_BUFF) - c_label.next_to(line.get_center(), UP+LEFT, SMALL_BUFF) - triangle.add(*labels) - triangle.tip = point - triangles.add(triangle) - return triangles - - def homotopy(self, x, y, z, t): - z_complex = self.background_plane.point_to_number(np.array([x, y, z])) - result = z_complex**(1+t) - return self.background_plane.number_to_point(result) - -class AskAboutHittingAllPoints(TeacherStudentsScene): - def construct(self): - self.student_says( - "Does this hit \\\\ all pythagorean triples?", - target_mode = "raise_left_hand" - ) - self.wait() - self.teacher_says("No", target_mode = "sad") - self.change_student_modes(*["hesitant"]*3) - self.wait() - -class PointsWeMiss(VisualizeZSquared): - CONFIG = { - "final_unit_size" : 0.4, - "plane_center" : 2*LEFT + 2*DOWN, - "dot_x_range" : list(range(-5, 6)), - "dot_y_range" : list(range(-4, 4)), - } - def construct(self): - self.add_plane() - self.add_transformed_color_grid() - self.add_dots() - self.show_missing_point() - self.show_second_missing_point() - self.mention_one_half_rule() - - def add_transformed_color_grid(self): - color_grid = self.get_color_grid() - func = lambda p : self.homotopy(p[0], p[1], p[1], 1) - color_grid.apply_function(func) - color_grid.set_stroke(width = 4) - self.add(color_grid, self.coordinate_labels) - self.color_grid = color_grid - - def add_dots(self): - z_list = [ - complex(x, y)**2 - for x in self.dot_x_range - for y in self.dot_y_range - ] - dots = VGroup(*[ - Dot( - self.background_plane.number_to_point(z), - color = self.dot_color, - radius = self.big_dot_radius, - ) - for z in z_list - ]) - dots.sort(get_norm) - self.add(dots) - self.dots = dots - - def show_missing_point(self): - z_list = [complex(6, 8), complex(9, 12), complex(3, 4)] - points = list(map( - self.background_plane.number_to_point, - z_list - )) - dots = VGroup(*list(map(Dot, points))) - for dot in dots[:2]: - dot.set_stroke(RED, 4) - dot.set_fill(opacity = 0) - labels = VGroup(*[ - TexMobject(complex_string_with_i(z)) - for z in z_list - ]) - labels.set_color(RED) - labels[2].set_color(GREEN) - rhss = VGroup() - for label, dot in zip(labels, dots): - label.add_background_rectangle() - label.next_to(dot, UP+RIGHT, SMALL_BUFF) - if label is labels[-1]: - rhs = TexMobject("= (2+i)^2") - else: - rhs = TexMobject("\\ne (u+vi)^2") - rhs.add_background_rectangle() - rhs.next_to(label, RIGHT) - rhss.add(rhs) - triangles = self.get_triangles(z_list) - - self.play(FocusOn(dots[0])) - self.play(ShowCreation(dots[0])) - self.play(Write(labels[0])) - self.wait() - self.play(FadeIn(triangles[0])) - self.wait(2) - self.play(Write(rhss[0])) - self.wait(2) - groups = triangles, dots, labels, rhss - for i in 1, 2: - self.play(*[ - Transform(group[0], group[i]) - for group in groups - ]) - self.wait(3) - self.play(*[ - FadeOut(group[0]) - for group in groups - ]) - - def show_second_missing_point(self): - z_list = [complex(4, 3), complex(8, 6)] - points = list(map( - self.background_plane.number_to_point, - z_list - )) - dots = VGroup(*list(map(Dot, points))) - dots[0].set_stroke(RED, 4) - dots[0].set_fill(opacity = 0) - labels = VGroup(*[ - TexMobject(complex_string_with_i(z)) - for z in z_list - ]) - labels[0].set_color(RED) - labels[1].set_color(GREEN) - rhss = VGroup() - for label, dot in zip(labels, dots): - label.add_background_rectangle() - label.next_to(dot, UP+RIGHT, SMALL_BUFF) - if label is labels[-1]: - rhs = TexMobject("= (3+i)^2") - else: - rhs = TexMobject("\\ne (u+vi)^2") - rhs.add_background_rectangle() - rhs.next_to(label, RIGHT) - rhss.add(rhs) - triangles = self.get_triangles(z_list) - groups = [dots, labels, rhss, triangles] - for group in groups: - group[0].save_state() - - self.play(ShowCreation(dots[0])) - self.play(Write(VGroup(labels[0], rhss[0]))) - self.play(FadeIn(triangles[0])) - self.wait(3) - self.play(*[Transform(*group) for group in groups]) - self.wait(3) - self.play(*[group[0].restore for group in groups]) - self.wait(2) - - def mention_one_half_rule(self): - morty = Mortimer() - morty.flip() - morty.to_corner(DOWN+LEFT) - - self.play(FadeIn(morty)) - self.play(PiCreatureSays( - morty, - "Never need to scale \\\\ by less than $\\frac{1}{2}$" - )) - self.play(Blink(morty)) - self.wait(2) - -class PointsWeMissAreMultiplesOfOnesWeHit(TeacherStudentsScene): - def construct(self): - words = TextMobject( - "Every point we", - "miss", - "is \\\\ a multiple of one we", - "hit" - ) - words.set_color_by_tex("miss", RED) - words.set_color_by_tex("hit", GREEN) - self.teacher_says(words) - self.change_student_modes(*["pondering"]*3) - self.wait(2) - -class DrawSingleRadialLine(PointsWeMiss): - def construct(self): - self.add_plane() - self.background_plane.set_stroke(width = 1) - self.add_transformed_color_grid() - self.color_grid.set_stroke(width = 1) - self.add_dots() - self.draw_line() - - def draw_line(self): - point = self.background_plane.coords_to_point(3, 4) - dot = Dot(point, color = RED) - line = Line( - self.plane_center, - self.background_plane.coords_to_point(15, 20), - color = WHITE, - ) - added_dots = VGroup(*[ - Dot(self.background_plane.coords_to_point(3*k, 4*k)) - for k in (2, 3, 5) - ]) - added_dots.set_color(GREEN) - - self.play(GrowFromCenter(dot)) - self.play(Indicate(dot)) - self.play(ShowCreation(line), Animation(dot)) - self.wait() - self.play(LaggedStartMap( - DrawBorderThenFill, added_dots, - stroke_color = PINK, - stroke_width = 4, - run_time = 3 - )) - self.wait() - -class DrawRadialLines(PointsWeMiss): - CONFIG = { - "final_unit_size" : 0.2, - "dot_x_range" : list(range(-4, 10)), - "dot_y_range" : list(range(-4, 10)), - "x_label_range" : list(range(-12, 40, 4)), - "y_label_range" : list(range(-4, 32, 4)), - "big_dot_radius" : 0.05, - } - def construct(self): - self.add_plane() - self.add_transformed_color_grid() - self.resize_plane() - self.add_dots() - self.create_lines() - self.show_single_line() - self.show_all_lines() - self.show_triangles() - - def resize_plane(self): - everything = VGroup(*self.get_top_level_mobjects()) - everything.scale( - self.final_unit_size/self.initial_unit_size, - about_point = self.plane_center - ) - self.background_plane.set_stroke(width = 1) - - def create_lines(self): - coord_strings = set([]) - reduced_coords_yet_to_be_reached = set([]) - for dot in self.dots: - point = dot.get_center() - float_coords = self.background_plane.point_to_coords(point) - coords = np.round(float_coords).astype('int') - gcd = fractions.gcd(*coords) - reduced_coords = coords/abs(gcd) - - if np.all(coords == [3, 4]): - first_dot = dot - - dot.coords = coords - dot.reduced_coords = reduced_coords - coord_strings.add(str(coords)) - reduced_coords_yet_to_be_reached.add(str(reduced_coords)) - lines = VGroup() - for dot in [first_dot] + list(self.dots): - rc_str = str(dot.reduced_coords) - if rc_str not in reduced_coords_yet_to_be_reached: - continue - reduced_coords_yet_to_be_reached.remove(rc_str) - new_dots = VGroup() - for k in range(50): - new_coords = k*dot.reduced_coords - if str(new_coords) in coord_strings: - continue - coord_strings.add(str(new_coords)) - point = self.background_plane.coords_to_point(*new_coords) - if abs(point[0]) > FRAME_X_RADIUS or abs(point[1]) > FRAME_Y_RADIUS: - continue - new_dot = Dot( - point, color = GREEN, - radius = self.big_dot_radius - ) - new_dots.add(new_dot) - line = Line(self.plane_center, dot.get_center()) - line.scale( - FRAME_WIDTH/line.get_length(), - about_point = self.plane_center - ) - line.set_stroke(width = 1) - line.seed_dot = dot.copy() - line.new_dots = new_dots - lines.add(line) - self.lines = lines - - def show_single_line(self): - line = self.lines[0] - dot = line.seed_dot - - self.play( - dot.scale_in_place, 2, - dot.set_color, RED - ) - self.play(ReplacementTransform(dot, line)) - self.wait() - self.play(LaggedStartMap( - DrawBorderThenFill, line.new_dots, - stroke_width = 4, - stroke_color = PINK, - run_time = 3, - )) - self.wait() - - def show_all_lines(self): - seed_dots = VGroup(*[line.seed_dot for line in self.lines]) - new_dots = VGroup(*[line.new_dots for line in self.lines]) - for dot in seed_dots: - dot.generate_target() - dot.target.scale_in_place(1.5) - dot.target.set_color(RED) - - self.play(LaggedStartMap( - MoveToTarget, seed_dots, - run_time = 2 - )) - self.play(ReplacementTransform( - seed_dots, self.lines, - run_time = 3, - lag_ratio = 0.5 - )) - self.play(LaggedStartMap( - DrawBorderThenFill, new_dots, - stroke_width = 4, - stroke_color = PINK, - run_time = 3, - )) - self.wait() - - self.new_dots = new_dots - - def show_triangles(self): - z_list = [ - complex(9, 12), - complex(7, 24), - complex(8, 15), - complex(21, 20), - complex(36, 15), - ] - triangles = self.get_triangles(z_list) - triangle = triangles[0] - - self.play(FadeIn(triangle)) - self.wait(2) - for new_triangle in triangles[1:]: - self.play(Transform(triangle, new_triangle)) - self.wait(2) - -class RationalPointsOnUnitCircle(DrawRadialLines): - CONFIG = { - "initial_unit_size" : 1.2, - "final_unit_size" : 0.4, - "plane_center" : 1.5*DOWN - } - def construct(self): - self.add_plane() - self.show_rational_points_on_unit_circle() - self.divide_by_c_squared() - self.from_rational_point_to_triple() - - def add_plane(self): - added_x_coords = list(range(-4, 6, 2)) - added_y_coords = list(range(-2, 4, 2)) - self.x_label_range += added_x_coords - self.y_label_range += added_y_coords - DrawRadialLines.add_plane(self) - - def show_rational_points_on_unit_circle(self): - circle = self.get_unit_circle() - - coord_list = [ - (12, 5), - (8, 15), - (7, 24), - (3, 4), - ] - groups = VGroup() - for x, y in coord_list: - norm = np.sqrt(x**2 + y**2) - point = self.background_plane.coords_to_point( - x/norm, y/norm - ) - dot = Dot(point, color = YELLOW) - line = Line(self.plane_center, point) - line.set_color(dot.get_color()) - label = TexMobject( - "{"+str(x), "\\over", str(int(norm))+"}", - "+", - "{"+str(y), "\\over", str(int(norm))+"}", - "i" - ) - label.next_to(dot, UP+RIGHT, buff = 0) - label.add_background_rectangle() - - group = VGroup(line, dot, label) - group.coords = (x, y) - groups.add(group) - group = groups[0].copy() - - self.add(circle, self.coordinate_labels) - self.play(FadeIn(group)) - self.wait() - for new_group in groups[1:]: - self.play(Transform(group, new_group)) - self.wait() - - self.curr_example_point_group = group - self.next_rational_point_example = groups[0] - self.unit_circle = circle - - def divide_by_c_squared(self): - top_line = TexMobject( - "a", "^2", "+", "b", "^2", "=", "c", "^2 \\phantom{1}" - ) - top_line.shift(FRAME_X_RADIUS*RIGHT/2) - top_line.to_corner(UP + LEFT) - top_line.shift(RIGHT) - top_rect = BackgroundRectangle(top_line) - - second_line = TexMobject( - "\\left(", "{a", "\\over", "c}", "\\right)", "^2", - "+", - "\\left(", "{b", "\\over", "c}", "\\right)", "^2", - "=", "1" - ) - second_line.move_to(top_line, UP) - second_line.shift_onto_screen() - second_rect = BackgroundRectangle(second_line) - - circle_label = TextMobject( - "All $x+yi$ where \\\\", - "$x^2 + y^2 = 1$" - ) - circle_label.next_to(second_line, DOWN, MED_LARGE_BUFF) - circle_label.shift_onto_screen() - circle_label.set_color_by_tex("x^2", GREEN) - circle_label.add_background_rectangle() - circle_arrow = Arrow( - circle_label.get_bottom(), - self.unit_circle.point_from_proportion(0.45), - color = GREEN - ) - - self.play(FadeIn(top_rect), FadeIn(top_line)) - self.wait() - self.play(*[ - ReplacementTransform(top_rect, second_rect) - ] + [ - ReplacementTransform( - top_line.get_parts_by_tex(tex, substring = False), - second_line.get_parts_by_tex(tex), - run_time = 2, - path_arc = -np.pi/3 - ) - for tex in ("a", "b", "c", "^2", "+", "=") - ] + [ - ReplacementTransform( - top_line.get_parts_by_tex("1"), - second_line.get_parts_by_tex("1"), - run_time = 2 - ) - ] + [ - Write( - second_line.get_parts_by_tex(tex), - run_time = 2, - rate_func = squish_rate_func(smooth, 0, 0.5) - ) - for tex in ("(", ")", "over",) - ]) - self.wait(2) - self.play(Write(circle_label)) - self.play(ShowCreation(circle_arrow)) - self.wait(2) - self.play(FadeOut(circle_arrow)) - - self.algebra = VGroup( - second_rect, second_line, circle_label, - ) - - def from_rational_point_to_triple(self): - rational_point_group = self.next_rational_point_example - scale_factor = self.final_unit_size/self.initial_unit_size - - self.play(ReplacementTransform( - self.curr_example_point_group, - rational_point_group - )) - self.wait(2) - self.play(*[ - ApplyMethod( - mob.scale_about_point, - scale_factor, - self.plane_center - ) - for mob in [ - self.background_plane, - self.coordinate_labels, - self.unit_circle, - rational_point_group, - ] - ] + [ - Animation(self.algebra), - ]) - - #mimic_group - point = self.background_plane.coords_to_point( - *rational_point_group.coords - ) - dot = Dot(point, color = YELLOW) - line = Line(self.plane_center, point) - line.set_color(dot.get_color()) - x, y = rational_point_group.coords - label = TexMobject(str(x), "+", str(y), "i") - label.next_to(dot, UP+RIGHT, buff = 0) - label.add_background_rectangle() - integer_point_group = VGroup(line, dot, label) - distance_label = TexMobject( - str(int(np.sqrt(x**2 + y**2))) - ) - distance_label.add_background_rectangle() - distance_label.next_to(line.get_center(), UP+LEFT, SMALL_BUFF) - - self.play(ReplacementTransform( - rational_point_group, - integer_point_group - )) - self.play(Write(distance_label)) - self.wait(2) - - ### - - def get_unit_circle(self): - template_line = Line(*[ - self.background_plane.number_to_point(z) - for z in (-1, 1) - ]) - circle = Circle(color = GREEN) - circle.replace(template_line, dim_to_match = 0) - return circle - -class ProjectPointsOntoUnitCircle(DrawRadialLines): - def construct(self): - ### - self.force_skipping() - self.add_plane() - self.add_transformed_color_grid() - self.resize_plane() - self.add_dots() - self.create_lines() - self.show_all_lines() - self.revert_to_original_skipping_status() - ### - - self.add_unit_circle() - self.project_all_dots() - self.zoom_in() - self.draw_infinitely_many_lines() - - - def add_unit_circle(self): - template_line = Line(*[ - self.background_plane.number_to_point(n) - for n in (-1, 1) - ]) - circle = Circle(color = BLUE) - circle.replace(template_line, dim_to_match = 0) - - self.play(ShowCreation(circle)) - self.unit_circle = circle - - def project_all_dots(self): - dots = self.dots - dots.add(*self.new_dots) - dots.sort( - lambda p : get_norm(p - self.plane_center) - ) - unit_length = self.unit_circle.get_width()/2.0 - for dot in dots: - dot.generate_target() - point = dot.get_center() - vect = point-self.plane_center - if np.round(vect[0], 3) == 0 and abs(vect[1]) > 2*unit_length: - dot.target.set_fill(opacity = 0) - continue - distance = get_norm(vect) - dot.target.scale( - unit_length/distance, - about_point = self.plane_center - ) - dot.target.set_width(0.01) - - self.play(LaggedStartMap( - MoveToTarget, dots, - run_time = 3, - lag_ratio = 0.2 - )) - - def zoom_in(self): - target_height = 5.0 - scale_factor = target_height / self.unit_circle.get_height() - group = VGroup( - self.background_plane, self.coordinate_labels, - self.color_grid, - self.lines, self.unit_circle, - self.dots, - ) - - self.play( - group.shift, -self.plane_center, - group.scale, scale_factor, - run_time = 2 - ) - self.wait(2) - - def draw_infinitely_many_lines(self): - lines = VGroup(*[ - Line(ORIGIN, FRAME_WIDTH*vect) - for vect in compass_directions(1000) - ]) - - self.play(LaggedStartMap( - ShowCreation, lines, - run_time = 3 - )) - self.play(FadeOut(lines)) - self.wait() - -class ICanOnlyDrawFinitely(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "I can only \\\\ draw finitely", - run_time = 2 - ) - self.wait(2) - -class SupposeMissingPoint(PointsWeMiss): - def construct(self): - self.add_plane() - self.background_plane.set_stroke(width = 1) - self.draw_missing_triple() - self.project_onto_unit_circle() - - def draw_missing_triple(self): - point = self.background_plane.coords_to_point(12, 5) - origin = self.plane_center - line = Line(origin, point, color = WHITE) - dot = Dot(point, color = YELLOW) - triangle = Polygon(ORIGIN, RIGHT, RIGHT+UP) - triangle.set_stroke(BLUE, 2) - triangle.set_fill(BLUE, 0.5) - triangle.replace(line, stretch = True) - a = TexMobject("a") - a.next_to(triangle.get_bottom(), UP, SMALL_BUFF) - b = TexMobject("b") - b.add_background_rectangle() - b.next_to(triangle, RIGHT, SMALL_BUFF) - c = TexMobject("c") - c.add_background_rectangle() - c.next_to(line.get_center(), UP+LEFT, SMALL_BUFF) - triangle.add(a, b, c) - words = TextMobject( - "If we missed \\\\ a triple \\dots" - ) - words.add_background_rectangle() - words.next_to(dot, UP+RIGHT) - words.shift_onto_screen() - - self.add(triangle, line, dot) - self.play(Write(words)) - self.wait() - - self.words = words - self.triangle = triangle - self.line = line - self.dot = dot - - - def project_onto_unit_circle(self): - dot, line = self.dot, self.line - template_line = Line(*[ - self.background_plane.number_to_point(n) - for n in (-1, 1) - ]) - circle = Circle(color = GREEN) - circle.replace(template_line, dim_to_match = 0) - z = self.background_plane.point_to_number(dot.get_center()) - z_norm = abs(z) - unit_z = z/z_norm - new_point = self.background_plane.number_to_point(unit_z) - dot.generate_target() - dot.target.move_to(new_point) - line.generate_target() - line.target.scale(1./z_norm, about_point = self.plane_center) - - rational_point_word = TexMobject("(a/c) + (b/c)i") - rational_point_word.next_to( - self.background_plane.coords_to_point(0, 6), RIGHT - ) - rational_point_word.add_background_rectangle() - arrow = Arrow( - rational_point_word.get_bottom(), - dot.target, - buff = SMALL_BUFF - ) - - self.play(ShowCreation(circle)) - self.add(dot.copy().fade()) - self.add(line.copy().set_stroke(GREY, 1)) - self.play(*list(map(MoveToTarget, [dot, line]))) - self.wait() - self.play( - Write(rational_point_word), - ShowCreation(arrow) - ) - self.wait(2) - -class ProofTime(TeacherStudentsScene): - def construct(self): - self.teacher_says("Proof time!", target_mode = "hooray") - self.change_student_modes(*["hooray"]*3) - self.wait(2) - -class FinalProof(RationalPointsOnUnitCircle): - def construct(self): - self.add_plane() - self.draw_rational_point() - self.draw_line_from_example_point() - self.show_slope_is_rational() - self.show_all_rational_slopes() - self.square_example_point() - self.project_onto_circle() - self.show_same_slope() - self.write_v_over_u_slope() - - def draw_rational_point(self): - circle = self.get_unit_circle() - coords = (3./5., 4./5.) - point = self.background_plane.coords_to_point(*coords) - dot = Dot(point, color = YELLOW) - label = TexMobject( - "(a/c) + (b/c)i" - ) - label.add_background_rectangle() - label.next_to(dot, UP+RIGHT, buff = 0) - - self.add(circle) - self.play( - Write(label, run_time = 2), - DrawBorderThenFill(dot) - ) - self.wait() - - self.example_dot = dot - self.example_label = label - self.unit_circle = circle - - def draw_line_from_example_point(self): - neg_one_point = self.background_plane.number_to_point(-1) - neg_one_dot = Dot(neg_one_point, color = RED) - line = Line( - neg_one_point, self.example_dot.get_center(), - color = RED - ) - - self.play( - ShowCreation(line, run_time = 2), - Animation(self.example_label) - ) - self.play(DrawBorderThenFill(neg_one_dot)) - self.wait() - - self.neg_one_dot = neg_one_dot - self.secant_line = line - - def show_slope_is_rational(self): - p0 = self.neg_one_dot.get_center() - p1 = self.example_dot.get_center() - p_mid = p1[0]*RIGHT + p0[1]*UP - - h_line = Line(p0, p_mid, color = MAROON_B) - v_line = Line(p_mid, p1, color = MAROON_B) - run_brace = Brace(h_line, DOWN) - run_text = run_brace.get_text( - "Run = $1 + \\frac{a}{c}$" - ) - run_text.add_background_rectangle() - rise_brace = Brace(v_line, RIGHT) - rise_text = rise_brace.get_text("Rise = $\\frac{b}{c}$") - rise_text.add_background_rectangle() - - self.play(*list(map(ShowCreation, [h_line, v_line]))) - self.wait() - self.play( - GrowFromCenter(rise_brace), - FadeIn(rise_text) - ) - self.wait() - self.play( - GrowFromCenter(run_brace), - FadeIn(run_text) - ) - self.wait(3) - self.play(*list(map(FadeOut, [ - self.example_dot, self.example_label, - self.secant_line, - h_line, v_line, - run_brace, rise_brace, - run_text, rise_text, - ]))) - - def show_all_rational_slopes(self): - lines = VGroup() - labels = VGroup() - for u in range(2, 7): - for v in range(1, u): - if fractions.gcd(u, v) != 1: - continue - z_squared = complex(u, v)**2 - unit_z_squared = z_squared/abs(z_squared) - point = self.background_plane.number_to_point(unit_z_squared) - dot = Dot(point, color = YELLOW) - line = Line( - self.background_plane.number_to_point(-1), - point, - color = self.neg_one_dot.get_color() - ) - line.add(dot) - - label = TexMobject( - "\\text{Slope = }", - str(v), "/", str(u) - ) - label.add_background_rectangle() - label.next_to( - self.background_plane.coords_to_point(1, 1.5), - RIGHT - ) - - lines.add(line) - labels.add(label) - line = lines[0] - label = labels[0] - - self.play( - ShowCreation(line), - FadeIn(label) - ) - self.wait() - for new_line, new_label in zip(lines, labels)[1:]: - self.play( - Transform(line, new_line), - Transform(label, new_label), - ) - self.wait() - self.play(*list(map(FadeOut, [line, label]))) - - def square_example_point(self): - z = complex(2, 1) - point = self.background_plane.number_to_point(z) - uv_dot = Dot(point, color = YELLOW) - uv_label = TexMobject("u", "+", "v", "i") - uv_label.add_background_rectangle() - uv_label.next_to(uv_dot, DOWN+RIGHT, buff = 0) - uv_line = Line( - self.plane_center, point, - color = YELLOW - ) - uv_arc = Arc( - angle = uv_line.get_angle(), - radius = 0.75 - ) - uv_arc.shift(self.plane_center) - theta = TexMobject("\\theta") - theta.next_to(uv_arc, RIGHT, SMALL_BUFF, DOWN) - theta.scale_in_place(0.8) - - square_point = self.background_plane.number_to_point(z**2) - square_dot = Dot(square_point, color = MAROON_B) - square_label = TexMobject("(u+vi)^2") - square_label.add_background_rectangle() - square_label.next_to(square_dot, RIGHT) - square_line = Line( - self.plane_center, square_point, - color = MAROON_B - ) - square_arc = Arc( - angle = square_line.get_angle(), - radius = 0.65 - ) - square_arc.shift(self.plane_center) - two_theta = TexMobject("2\\theta") - two_theta.next_to( - self.background_plane.coords_to_point(0, 1), - UP+RIGHT, SMALL_BUFF, - ) - two_theta_arrow = Arrow( - two_theta.get_right(), - square_arc.point_from_proportion(0.75), - tip_length = 0.15, - path_arc = -np.pi/2, - color = WHITE, - buff = SMALL_BUFF - ) - self.two_theta_group = VGroup(two_theta, two_theta_arrow) - - z_to_z_squared_arrow = Arrow( - point, square_point, - path_arc = np.pi/3, - color = WHITE - ) - z_to_z_squared = TexMobject("z", "\\to", "z^2") - z_to_z_squared.set_color_by_tex("z", YELLOW) - z_to_z_squared.set_color_by_tex("z^2", MAROON_B) - z_to_z_squared.add_background_rectangle() - z_to_z_squared.next_to( - z_to_z_squared_arrow.point_from_proportion(0.5), - RIGHT, SMALL_BUFF - ) - - self.play( - Write(uv_label), - DrawBorderThenFill(uv_dot) - ) - self.play(ShowCreation(uv_line)) - self.play(ShowCreation(uv_arc)) - self.play(Write(theta)) - self.wait() - self.play( - ShowCreation(z_to_z_squared_arrow), - FadeIn(z_to_z_squared) - ) - self.play(*[ - ReplacementTransform( - m1.copy(), m2, - path_arc = np.pi/3 - ) - for m1, m2 in [ - (uv_dot, square_dot), - (uv_line, square_line), - (uv_label, square_label), - (uv_arc, square_arc), - ] - ]) - self.wait() - self.play( - Write(two_theta), - ShowCreation(two_theta_arrow) - ) - self.wait(2) - self.play(FadeOut(self.two_theta_group)) - - self.theta_group = VGroup(uv_arc, theta) - self.uv_line = uv_line - self.uv_dot = uv_dot - self.uv_label = uv_label - self.square_line = square_line - self.square_dot = square_dot - - def project_onto_circle(self): - line = self.square_line.copy() - dot = self.square_dot.copy() - self.square_line.fade() - self.square_dot.fade() - - radius = self.unit_circle.get_width()/2 - line.generate_target() - line.target.scale( - radius / line.get_length(), - about_point = line.get_start() - ) - dot.generate_target() - dot.target.move_to(line.target.get_end()) - - self.play( - MoveToTarget(line), - MoveToTarget(dot), - ) - self.wait() - self.play(FadeIn(self.two_theta_group)) - self.wait() - self.play(FadeOut(self.two_theta_group)) - self.wait(6) ##circle geometry - - self.rational_point_dot = dot - - def show_same_slope(self): - line = Line( - self.neg_one_dot.get_center(), - self.rational_point_dot.get_center(), - color = self.neg_one_dot.get_color() - ) - theta_group_copy = self.theta_group.copy() - same_slope_words = TextMobject("Same slope") - same_slope_words.add_background_rectangle() - same_slope_words.shift(4*LEFT + 0.33*UP) - line_copies = VGroup( - line.copy(), - self.uv_line.copy() - ) - line_copies.generate_target() - line_copies.target.next_to(same_slope_words, DOWN) - - self.play(ShowCreation(line)) - self.wait() - self.play( - theta_group_copy.shift, - line.get_start() - self.uv_line.get_start() - ) - self.wait() - self.play( - Write(same_slope_words), - MoveToTarget(line_copies) - ) - self.wait() - - self.same_slope_words = same_slope_words - - def write_v_over_u_slope(self): - p0 = self.plane_center - p1 = self.uv_dot.get_center() - p_mid = p1[0]*RIGHT + p0[1]*UP - h_line = Line(p0, p_mid, color = YELLOW) - v_line = Line(p_mid, p1, color = YELLOW) - - rhs = TexMobject("=", "{v", "\\over", "u}") - rhs.next_to(self.same_slope_words, RIGHT) - rect = SurroundingRectangle(VGroup(*rhs[1:])) - morty = Mortimer().flip() - morty.scale(0.5) - morty.next_to(self.same_slope_words, UP, buff = 0) - - self.play(ShowCreation(h_line)) - self.play(ShowCreation(v_line)) - self.wait() - self.play(*[ - ReplacementTransform( - self.uv_label.get_part_by_tex(tex).copy(), - rhs.get_part_by_tex(tex), - run_time = 2 - ) - for tex in ("u", "v") - ] + [ - Write(rhs.get_part_by_tex(tex)) - for tex in ("=", "over") - ]) - self.wait(2) - self.play( - ShowCreation(rect), - FadeIn(morty) - ) - self.play(PiCreatureSays( - morty, "Free to choose!", - bubble_kwargs = {"height" : 1.5, "width" : 3}, - target_mode = "hooray", - look_at_arg = rect - )) - self.play(Blink(morty)) - self.wait(2) - -class BitOfCircleGeometry(Scene): - def construct(self): - circle = Circle(color = BLUE, radius = 3) - p0, p1, p2 = [ - circle.point_from_proportion(alpha) - for alpha in (0, 0.15, 0.55) - ] - O = circle.get_center() - O_dot = Dot(O, color = WHITE) - self.add(circle, O_dot) - - - groups = VGroup() - for point, tex, color in (O, "2", MAROON_B), (p2, "", RED): - line1 = Line(point, p0) - line2 = Line(point, p1) - dot1 = Dot(p0) - dot2 = Dot(p1) - angle = line1.get_angle() - arc = Arc( - angle = line2.get_angle()-line1.get_angle(), - start_angle = line1.get_angle(), - radius = 0.75, - color = WHITE - ) - arc.set_stroke(YELLOW, 3) - arc.shift(point) - label = TexMobject(tex + "\\theta") - label.next_to( - arc.point_from_proportion(0.9), RIGHT - ) - - group = VGroup(line1, line2, dot1, dot2) - group.set_color(color) - group.add(arc, label) - if len(groups) == 0: - self.play(*list(map(ShowCreation, [dot1, dot2]))) - self.play(*list(map(ShowCreation, [line1, line2]))) - self.play(ShowCreation(arc)) - self.play(FadeIn(label)) - groups.add(group) - - self.wait(2) - self.play(ReplacementTransform( - groups[0].copy(), groups[1] - )) - self.wait(2) - -class PatreonThanksTriples(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "Burt Humburg", - "CrypticSwarm", - "David Beyer", - "Erik Sundell", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Karan Bhargava", - "Ankit Agarwal", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Juan Benet", - "Othman Alikhan", - "Markus Persson", - "Yoni Nazarathy", - "Joseph John Cox", - "Dan Buchoff", - "Luc Ritchie", - "Ankalagon", - "Eric Lavault", - "Tomohiro Furusawa", - "Boris Veselinovich", - "Julian Pulgarin", - "John Haley", - "Jeff Linse", - "Suraj Pratap", - "Cooper Jones", - "Ryan Dahl", - "Ahmad Bamieh", - "Mark Govea", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "Nils Schneider", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ], - } - -class Thumbnail(DrawRadialLines): - def construct(self): - self.force_skipping() - self.add_plane() - self.add_transformed_color_grid() - self.color_grid.set_stroke(width = 5) - self.resize_plane() - self.add_dots() - self.create_lines() - self.show_single_line() - self.show_all_lines() - - rect = Rectangle( - height = 4.3, width = 4.2, - stroke_width = 3, - stroke_color = WHITE, - fill_color = BLACK, - fill_opacity = 1, - ) - rect.to_corner(UP+RIGHT, buff = 0.01) - triples = VGroup(*list(map(TexMobject, [ - "3^2 + 4^2 = 5^2", - "5^2 + 12^2 = 13^2", - "8^2 + 15^2 = 17^2", - "\\vdots" - ]))) - triples.arrange(DOWN, buff = MED_LARGE_BUFF) - triples.next_to(rect.get_top(), DOWN) - self.add(rect, triples) - -class Poster(DrawRadialLines): - CONFIG = { - "final_unit_size" : 0.1, - "plane_center" : ORIGIN, - } - def construct(self): - self.force_skipping() - self.add_plane() - self.add_transformed_color_grid() - self.color_grid.set_stroke(width = 5) - self.resize_plane() - self.add_dots() - self.create_lines() - self.show_single_line() - self.show_all_lines() - - for dot_group in self.dots, self.new_dots: - for dot in dot_group.family_members_with_points(): - dot.scale_in_place(0.5) - self.remove(self.coordinate_labels) - - # rect = Rectangle( - # height = 4.3, width = 4.2, - # stroke_width = 3, - # stroke_color = WHITE, - # fill_color = BLACK, - # fill_opacity = 1, - # ) - # rect.to_corner(UP+RIGHT, buff = 0.01) - # triples = VGroup(*map(TexMobject, [ - # "3^2 + 4^2 = 5^2", - # "5^2 + 12^2 = 13^2", - # "8^2 + 15^2 = 17^2", - # "\\vdots" - # ])) - # triples.arrange(DOWN, buff = MED_LARGE_BUFF) - # triples.next_to(rect.get_top(), DOWN) - # self.add(rect, triples) - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/turbulence.py b/from_3b1b/old/turbulence.py deleted file mode 100644 index c3765f44..00000000 --- a/from_3b1b/old/turbulence.py +++ /dev/null @@ -1,1783 +0,0 @@ -from manimlib.imports import * -from from_3b1b.old.div_curl import PureAirfoilFlow -from from_3b1b.old.div_curl import move_submobjects_along_vector_field -from from_3b1b.old.div_curl import move_points_along_vector_field -from from_3b1b.old.div_curl import four_swirls_function -from from_3b1b.old.lost_lecture import ShowWord - - -class CreationDestructionMobject(VMobject): - CONFIG = { - "start_time": 0, - "frequency": 0.25, - "max_ratio_shown": 0.3, - "use_copy": True, - } - - def __init__(self, template, **kwargs): - VMobject.__init__(self, **kwargs) - if self.use_copy: - self.ghost_mob = template.copy().fade(1) - self.add(self.ghost_mob) - else: - self.ghost_mob = template - # Don't add - self.shown_mob = template.deepcopy() - self.shown_mob.clear_updaters() - self.add(self.shown_mob) - self.total_time = self.start_time - - def update(mob, dt): - mob.total_time += dt - period = 1.0 / mob.frequency - unsmooth_alpha = (mob.total_time % period) / period - alpha = bezier([0, 0, 1, 1])(unsmooth_alpha) - mrs = mob.max_ratio_shown - mob.shown_mob.pointwise_become_partial( - mob.ghost_mob, - max(interpolate(-mrs, 1, alpha), 0), - min(interpolate(0, 1 + mrs, alpha), 1), - ) - - self.add_updater(update) - - -class Eddy(VMobject): - CONFIG = { - "cd_mob_config": { - "frequency": 0.2, - "max_ratio_shown": 0.3 - }, - "n_spirils": 5, - "n_layers": 20, - "radius": 1, - "colors": [BLUE_A, BLUE_E], - } - - def __init__(self, **kwargs): - VMobject.__init__(self, **kwargs) - lines = self.get_lines() - # self.add(lines) - self.add(*[ - CreationDestructionMobject(line, **self.cd_mob_config) - for line in lines - ]) - self.randomize_times() - - def randomize_times(self): - for submob in self.submobjects: - if hasattr(submob, "total_time"): - T = 1.0 / submob.frequency - submob.total_time = T * random.random() - - def get_lines(self): - a = 0.2 - return VGroup(*[ - self.get_line(r=self.radius * (1 - a + 2 * a * random.random())) - for x in range(self.n_layers) - ]) - - def get_line(self, r): - return ParametricFunction( - lambda t: r * (t + 1)**(-1) * np.array([ - np.cos(TAU * t), - np.sin(TAU * t), - 0, - ]), - t_min=0.1 * random.random(), - t_max=self.n_spirils, - stroke_width=1, - color=interpolate_color(*self.colors, random.random()) - ) - - -class Chaos(Eddy): - CONFIG = { - "n_lines": 12, - "height": 1, - "width": 2, - "n_midpoints": 4, - "cd_mob_config": { - "use_copy": False, - "frequency": 1, - "max_ratio_shown": 0.8 - } - } - - def __init__(self, **kwargs): - VMobject.__init__(self, **kwargs) - rect = Rectangle(height=self.height, width=self.width) - rect.move_to(ORIGIN, DL) - rect.fade(1) - self.rect = rect - self.add(rect) - - lines = self.get_lines() - self.add(*[ - CreationDestructionMobject(line, **self.cd_mob_config) - for line in lines - ]) - self.randomize_times() - lines.fade(1) - self.add(lines) - - def get_lines(self): - return VGroup(*[ - self.get_line(y) - for y in np.linspace(0, self.height, self.n_lines) - ]) - - def get_line(self, y): - frequencies = [0] + list(2 + 2 * np.random.random(self.n_midpoints)) + [0] - rect = self.rect - line = Line( - y * UP, y * UP + self.width * RIGHT, - stroke_width=1 - ) - line.insert_n_curves(self.n_midpoints) - line.total_time = random.random() - delta_h = self.height / (self.n_lines - 1) - - def update(line, dt): - x0, y0 = rect.get_corner(DL)[:2] - x1, y1 = rect.get_corner(UR)[:2] - line.total_time += dt - xs = np.linspace(x0, x1, self.n_midpoints + 2) - new_anchors = [ - np.array([ - x + 1.0 * delta_h * np.cos(f * line.total_time), - y0 + y + 1.0 * delta_h * np.cos(f * line.total_time), - 0 - ]) - for (x, f) in zip(xs, frequencies) - ] - line.set_points_smoothly(new_anchors) - - line.add_updater(update) - return line - - -class DoublePendulum(VMobject): - CONFIG = { - "start_angles": [3 * PI / 7, 3 * PI / 4], - "color1": BLUE, - "color2": RED, - } - - def __init__(self, **kwargs): - VMobject.__init__(self, **kwargs) - line1 = Line(ORIGIN, UP) - dot1 = Dot(color=self.color1) - dot1.add_updater(lambda d: d.move_to(line1.get_end())) - line2 = Line(UP, 2 * UP) - dot2 = Dot(color=self.color2) - dot2.add_updater(lambda d: d.move_to(line2.get_end())) - self.add(line1, line2, dot1, dot2) - - # Largely copied from https://scipython.com/blog/the-double-pendulum/ - # Pendulum rod lengths (m), bob masses (kg). - L1, L2 = 1, 1 - m1, m2 = 1, 1 - # The gravitational acceleration (m.s-2). - g = 9.81 - - self.state_vect = np.array([ - self.start_angles[0], 0, - self.start_angles[1], 0, - ]) - self.state_vect += np.random.random(4) * 1e-7 - - def update(group, dt): - for x in range(2): - line1, line2 = group.submobjects[:2] - theta1, z1, theta2, z2 = group.state_vect - - c, s = np.cos(theta1 - theta2), np.sin(theta1 - theta2) - - theta1dot = z1 - z1dot = (m2 * g * np.sin(theta2) * c - m2 * s * (L1 * (z1**2) * c + L2 * z2**2) - - (m1 + m2) * g * np.sin(theta1)) / L1 / (m1 + m2 * s**2) - theta2dot = z2 - z2dot = ((m1 + m2) * (L1 * (z1**2) * s - g * np.sin(theta2) + g * np.sin(theta1) * c) + - m2 * L2 * (z2**2) * s * c) / L2 / (m1 + m2 * s**2) - - group.state_vect += 0.5 * dt * np.array([ - theta1dot, z1dot, theta2dot, z2dot, - ]) - group.state_vect[1::2] *= 0.9999 - - p1 = L1 * np.sin(theta1) * RIGHT - L1 * np.cos(theta1) * UP - p2 = p1 + L2 * np.sin(theta2) * RIGHT - L2 * np.cos(theta2) * UP - - line1.put_start_and_end_on(ORIGIN, p1) - line2.put_start_and_end_on(p1, p2) - - self.add_updater(update) - - -class DoublePendulums(VGroup): - def __init__(self, **kwargs): - colors = [BLUE, RED, YELLOW, PINK, MAROON_B, PURPLE, GREEN] - VGroup.__init__( - self, - *[ - DoublePendulum( - color1=random.choice(colors), - color2=random.choice(colors), - ) - for x in range(5) - ], - **kwargs, - ) - - -class Diffusion(VMobject): - CONFIG = { - "height": 1.5, - "n_dots": 1000, - "colors": [RED, BLUE] - } - - def __init__(self, **kwargs): - VMobject.__init__(self, **kwargs) - self.add_dots() - self.add_invisible_circles() - - def add_dots(self): - dots = VGroup(*[Dot() for x in range(self.n_dots)]) - dots.arrange_in_grid(buff=SMALL_BUFF) - dots.center() - dots.set_height(self.height) - dots.sort(lambda p: p[0]) - dots[:len(dots) // 2].set_color(self.colors[0]) - dots[len(dots) // 2:].set_color(self.colors[1]) - dots.set_fill(opacity=0.8) - self.dots = dots - self.add(dots) - - def add_invisible_circles(self): - circles = VGroup() - for dot in self.dots: - point = dot.get_center() - radius = get_norm(point) - circle = Circle(radius=radius) - circle.rotate(angle_of_vector(point)) - circle.fade(1) - circles.add(circle) - self.add_updater_to_dot(dot, circle) - self.add(circles) - - def add_updater_to_dot(self, dot, circle): - dot.total_time = 0 - radius = get_norm(dot.get_center()) - freq = 0.1 + 0.05 * random.random() + 0.05 / radius - - def update(dot, dt): - dot.total_time += dt - prop = (freq * dot.total_time) % 1 - dot.move_to(circle.point_from_proportion(prop)) - - dot.add_updater(update) - - -class NavierStokesEquations(TexMobject): - CONFIG = { - "tex_to_color_map": { - "\\rho": YELLOW, - "\\mu": GREEN, - "\\textbf{v}": BLUE, - "p{}": RED, - }, - "width": 10, - } - - def __init__(self, **kwargs): - v_tex = "\\textbf{v}" - TexMobject.__init__( - self, - "\\rho", - "\\left(" - "{\\partial", v_tex, "\\over", - "\\partial", "t}", - "+", - v_tex, "\\cdot", "\\nabla", v_tex, - "\\right)", - "=", - "-", "\\nabla", "p{}", "+", - "\\mu", "\\nabla^2", v_tex, "+", - # "\\frac{1}{3}", "\\mu", "\\nabla", - # "(", "\\nabla", "\\cdot", v_tex, ")", "+", - "\\textbf{F}", - "\\qquad\\qquad", - "\\nabla", "\\cdot", v_tex, "=", "0", - **kwargs - ) - self.set_width(self.width) - - def get_labels(self): - parts = self.get_parts() - words = [ - "Analogous to \\\\ mass $\\times$ acceleration", - "Pressure\\\\forces", - "Viscous\\\\forces", - "External\\\\forces", - ] - - result = VGroup() - braces = VGroup() - word_mobs = VGroup() - for i, part, word in zip(it.count(), parts, words): - brace = Brace(part, DOWN, buff=SMALL_BUFF) - word_mob = brace.get_text(word) - word_mob.scale(0.7, about_edge=UP) - word_mobs.add(word_mob) - braces.add(brace) - result.add(VGroup(brace, word_mob)) - word_mobs[1:].arrange(RIGHT, buff=MED_SMALL_BUFF) - word_mobs[1:].next_to(braces[2], DOWN, SMALL_BUFF) - word_mobs[1].set_color(RED) - word_mobs[2].set_color(GREEN) - return result - - def get_parts(self): - return VGroup( - self[:12], - self[13:16], - self[17:20], - self[21:22], - ) - - -class Test(Scene): - def construct(self): - self.add(DoublePendulums()) - self.wait(30) - -# Scenes - - -class EddyReference(Scene): - CONFIG = { - "radius": 1, - "label": "Eddy", - "label": "", - } - - def construct(self): - eddy = Eddy(radius=self.radius) - new_eddy = eddy.get_lines() - for line in new_eddy: - line.set_stroke( - width=(3 + 3 * random.random()) - ) - label = TextMobject(self.label) - label.next_to(new_eddy, UP) - - self.play( - LaggedStartMap(ShowCreationThenDestruction, new_eddy), - FadeIn( - label, - rate_func=there_and_back_with_pause, - ), - run_time=3 - ) - - -class EddyReferenceWithLabel(EddyReference): - CONFIG = { - "label": "Eddy" - } - - -class EddyLabels(Scene): - def construct(self): - labels = VGroup( - TextMobject("Large eddy"), - TextMobject("Medium eddy"), - TextMobject("Small eddy"), - ) - for label in labels: - self.play(FadeIn( - label, - rate_func=there_and_back_with_pause, - run_time=3 - )) - - -class LargeEddyReference(EddyReference): - CONFIG = { - "radius": 2, - "label": "" - } - - -class MediumEddyReference(EddyReference): - CONFIG = { - "radius": 0.8, - "label": "Medium eddy" - } - - -class SmallEddyReference(EddyReference): - CONFIG = { - "radius": 0.25, - "label": "Small eddy" - } - - -class SomeTurbulenceEquations(PiCreatureScene): - def construct(self): - randy, morty = self.pi_creatures - navier_stokes = NavierStokesEquations() - line = Line(randy.get_right(), morty.get_left()) - navier_stokes.replace(line, dim_to_match=0) - navier_stokes.scale(1.2) - - distribution = TexMobject( - "E(k) \\propto k^{-5/3}", - tex_to_color_map={ - "k": GREEN, - "-5/3": YELLOW, - } - ) - distribution.next_to(morty, UL) - brace = Brace(distribution, DOWN, buff=SMALL_BUFF) - brace_words = brace.get_text("Explained soon...") - brace_group = VGroup(brace, brace_words) - - self.play( - Write(navier_stokes), - randy.change, "confused", navier_stokes, - morty.change, "confused", navier_stokes, - ) - self.wait(3) - self.play( - morty.change, "raise_right_hand", distribution, - randy.look_at, distribution, - FadeInFromDown(distribution), - navier_stokes.fade, 0.5, - ) - self.play(GrowFromCenter(brace_group)) - self.play(randy.change, "pondering", distribution) - self.wait(3) - dist_group = VGroup(distribution, brace_group) - self.play( - LaggedStartMap(FadeOut, VGroup(randy, morty, navier_stokes)), - dist_group.scale, 1.5, - dist_group.center, - dist_group.to_edge, UP, - ) - self.wait() - - def create_pi_creatures(self): - randy, morty = Randolph(), Mortimer() - randy.to_corner(DL) - morty.to_corner(DR) - return (randy, morty) - - -class JokeRingEquation(Scene): - def construct(self): - items = VGroup( - TextMobject("Container with a lip"), - TextMobject("Fill with smoke (or fog)"), - TextMobject("Hold awkwardly"), - ) - line = Line(LEFT, RIGHT).set_width(items.get_width() + 1) - items.add(line) - items.add(TextMobject("Vortex ring")) - items.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - line.shift(LEFT) - plus = TexMobject("+") - plus.next_to(line.get_left(), UR, SMALL_BUFF) - line.add(plus) - items.to_edge(RIGHT) - - point = 3.8 * LEFT + 0.2 * UP - arrow1 = Arrow( - items[0].get_left(), point + 0.8 * UP + 0.3 * RIGHT, - path_arc=90 * DEGREES, - ) - arrow1.pointwise_become_partial(arrow1, 0, 0.99) - - arrow2 = Arrow( - items[1].get_left(), point, - ) - arrows = VGroup(arrow1, arrow2) - - for i in 0, 1: - self.play( - FadeInFromDown(items[i]), - ShowCreation(arrows[i]) - ) - self.wait() - self.play(LaggedStartMap(FadeIn, items[2:])) - self.wait() - self.play(FadeOut(arrows)) - self.wait() - - -class VideoOnPhysicsGirlWrapper(Scene): - def construct(self): - rect = ScreenRectangle(height=6) - title = TextMobject("Video on Physics Girl") - title.scale(1.5) - title.to_edge(UP) - rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - -class LightBouncingOffFogParticle(Scene): - def construct(self): - words = TextMobject( - "Light bouncing\\\\", - "off fog particles" - ) - arrow = Vector(UP + 0.5 * RIGHT) - arrow.next_to(words, UP) - arrow.set_color(WHITE) - - self.add(words) - self.play(GrowArrow(arrow)) - self.wait() - - -class NightHawkInLightWrapper(Scene): - def construct(self): - title = TextMobject("NightHawkInLight") - title.scale(1.5) - title.to_edge(UP) - rect = ScreenRectangle(height=6) - rect.next_to(title, DOWN) - self.add(title) - self.play(ShowCreation(rect)) - self.wait() - - -class CarefulWithLasers(TeacherStudentsScene): - def construct(self): - morty = self.teacher - randy = self.students[1] - randy2 = self.students[2] - # randy.change('hooray') - laser = VGroup( - Rectangle( - height=0.1, - width=0.3, - fill_color=LIGHT_GREY, - fill_opacity=1, - stroke_color=DARK_GREY, - stroke_width=1, - ), - Line(ORIGIN, 10 * RIGHT, color=GREEN_SCREEN) - ) - laser.arrange(RIGHT, buff=0) - laser.rotate(45 * DEGREES) - laser.shift(randy.get_corner(UR) - laser[0].get_center() + 0.1 * DR) - - laser.time = 0 - - def update_laser(laser, dt): - laser.time += dt - laser.rotate( - 0.5 * dt * np.sin(laser.time), - about_point=laser[0].get_center() - ) - laser.add_updater(update_laser) - - self.play(LaggedStartMap(FadeInFromDown, self.pi_creatures, run_time=1)) - self.add(self.pi_creatures, laser) - for pi in self.pi_creatures: - pi.add_updater(lambda p: p.look_at(laser[1])) - self.play( - ShowCreation(laser), - self.get_student_changes( - "surprised", "hooray", "horrified", - look_at_arg=laser - ) - ) - self.teacher_says( - "Careful with \\\\ the laser!", - target_mode="angry" - ) - self.wait(2.2) - morty.save_state() - randy2.save_state() - self.play( - morty.blink, randy2.blink, - run_time=0.3 - ) - self.wait(2) - self.play( - morty.restore, randy2.restore, - run_time=0.3 - ) - self.wait(2) - - -class SetAsideTurbulence(PiCreatureScene): - def construct(self): - self.pi_creature_says( - "Forget vortex rings", - target_mode="speaking" - ) - self.wait() - self.pi_creature_says( - "look at that\\\\ turbulence!", - target_mode="surprised" - ) - self.wait() - - def create_pi_creature(self): - morty = Mortimer() - morty.to_corner(DR) - return morty - - -class WavingRodLabel(Scene): - def construct(self): - words = TextMobject( - "(Waving a small flag \\\\ through the air)" - ) - self.play(Write(words)) - self.wait() - - -class SeekOrderWords(Scene): - def construct(self): - words = TextMobject("Seek order amidst chaos") - words.scale(1.5) - self.play(Write(words)) - self.wait() - - -class LongEddy(Scene): - def construct(self): - self.add(Eddy()) - self.wait(30) - - -class LongDoublePendulum(Scene): - def construct(self): - self.add(DoublePendulums()) - self.wait(30) - - -class LongDiffusion(Scene): - def construct(self): - self.add(Diffusion()) - self.wait(30) - - -class AskAboutTurbulence(TeacherStudentsScene): - def construct(self): - self.pi_creatures_ask() - self.divide_by_qualitative_quantitative() - self.three_qualitative_descriptors() - self.rigorous_definition() - - def pi_creatures_ask(self): - morty = self.teacher - randy = self.students[1] - morty.change("surprised") - - words = TextMobject("Wait,", "what", "exactly \\\\", "is turbulence?") - question = TextMobject("What", "is turbulence?") - question.to_edge(UP, buff=MED_SMALL_BUFF) - h_line = Line(LEFT, RIGHT).set_width(FRAME_WIDTH - 1) - h_line.next_to(question, DOWN, buff=MED_LARGE_BUFF) - - self.student_says( - words, - target_mode='raise_left_hand', - added_anims=[morty.change, 'pondering'] - ) - self.change_student_modes( - "erm", "raise_left_hand", "confused", - ) - self.wait(3) - self.play( - morty.change, "raise_right_hand", - FadeOut(randy.bubble), - ReplacementTransform(VGroup(words[1], words[3]), question), - FadeOut(VGroup(words[0], words[2])), - self.get_student_changes( - *3 * ["pondering"], - look_at_arg=question - ) - ) - self.play( - ShowCreation(h_line), - LaggedStartMap( - FadeOutAndShiftDown, self.pi_creatures, - run_time=1, - lag_ratio=0.8 - ) - ) - self.wait() - - self.question = question - self.h_line = h_line - - def divide_by_qualitative_quantitative(self): - v_line = Line( - self.h_line.get_center(), - FRAME_HEIGHT * DOWN / 2, - ) - words = VGroup( - TextMobject("Features", color=YELLOW), - TextMobject("Rigorous definition", color=BLUE), - ) - words.next_to(self.h_line, DOWN) - words[0].shift(FRAME_WIDTH * LEFT / 4) - words[1].shift(FRAME_WIDTH * RIGHT / 4) - self.play( - ShowCreation(v_line), - LaggedStartMap(FadeInFromDown, words) - ) - self.wait() - - self.words = words - - def three_qualitative_descriptors(self): - words = VGroup( - TextMobject("- Eddies"), - TextMobject("- Chaos"), - TextMobject("- Diffusion"), - ) - words.arrange( - DOWN, buff=1.25, - aligned_edge=LEFT - ) - words.to_edge(LEFT) - words.shift(MED_LARGE_BUFF * DOWN) - - # objects = VGroup( - # Eddy(), - # DoublePendulum(), - # Diffusion(), - # ) - - # for word, obj in zip(words, objects): - for word in words: - # obj.next_to(word, RIGHT) - self.play( - FadeInFromDown(word), - # VFadeIn(obj) - ) - self.wait(3) - - def rigorous_definition(self): - randy = Randolph() - randy.move_to(FRAME_WIDTH * RIGHT / 4) - randy.change("pondering", self.words[1]) - - self.play(FadeIn(randy)) - self.play(Blink(randy)) - self.wait() - self.play(randy.change, "shruggie") - for x in range(2): - self.play(Blink(randy)) - self.wait() - self.play(randy.look, LEFT) - self.wait(2) - self.play(randy.look, UP) - self.play(Blink(randy)) - self.wait() - - -class BumpyPlaneRide(Scene): - def construct(self): - plane = SVGMobject(file_name="plane2") - self.add(plane) - - total_time = 0 - while total_time < 10: - point = 2 * np.append(np.random.random(2), 2) + DL - point *= 0.2 - time = 0.2 * random.random() - total_time += time - arc = PI * random.random() - PI / 2 - self.play( - plane.move_to, point, - run_time=time, - path_arc=arc - ) - - -class PureAirfoilFlowCopy(PureAirfoilFlow): - def modify_vector_field(self, vector_field): - PureAirfoilFlow.modify_vector_field(self, vector_field) - vector_field.set_fill(opacity=0.1) - vector_field.set_stroke(opacity=0.1) - - -class LaminarFlowLabel(Scene): - def construct(self): - words = TextMobject("Laminar flow") - words.scale(1.5) - words.to_edge(UP) - subwords = TextMobject( - "`Lamina', in Latin, means \\\\" - "``a thin sheet of material''", - tex_to_color_map={"Lamina": YELLOW}, - arg_separator="", - ) - subwords.next_to(words, DOWN, MED_LARGE_BUFF) - VGroup(words, subwords).set_background_stroke(width=4) - self.play(Write(words)) - self.wait() - self.play(FadeInFromDown(subwords)) - self.wait() - - -class HighCurlFieldBreakingLayers(Scene): - CONFIG = { - "flow_anim": move_submobjects_along_vector_field, - } - - def construct(self): - lines = VGroup(*[ - self.get_line() - for x in range(20) - ]) - lines.arrange(DOWN, buff=MED_SMALL_BUFF) - lines[0::2].set_color(BLUE) - lines[1::2].set_color(RED) - all_dots = VGroup(*it.chain(*lines)) - - def func(p): - vect = four_swirls_function(p) - norm = get_norm(vect) - if norm > 2: - vect *= 4.0 / get_norm(vect)**2 - return vect - - self.add(lines) - self.add(self.flow_anim(all_dots, func)) - self.wait(16) - - def get_line(self): - line = VGroup(*[Dot() for x in range(100)]) - line.set_height(0.1) - line.arrange(RIGHT, buff=0) - line.set_width(10) - return line - - -class HighCurlFieldBreakingLayersLines(HighCurlFieldBreakingLayers): - CONFIG = { - "flow_anim": move_points_along_vector_field - } - - def get_line(self): - line = Line(LEFT, RIGHT) - line.insert_n_curves(500) - line.set_width(5) - return line - - -class VorticitySynonyms(Scene): - def construct(self): - words = VGroup( - TextMobject("High", "vorticity"), - TexMobject( - "\\text{a.k.a} \\,", - "|\\nabla \\times \\vec{\\textbf{v}}| > 0" - ), - TextMobject("a.k.a", "high", "swirly-swirly", "factor"), - ) - words[0].set_color_by_tex("vorticity", BLUE) - words[1].set_color_by_tex("nabla", BLUE) - words[2].set_color_by_tex("swirly", BLUE) - words.arrange( - DOWN, - aligned_edge=LEFT, - buff=MED_LARGE_BUFF - ) - - for word in words: - word.add_background_rectangle() - self.play(FadeInFromDown(word)) - self.wait() - - -class VorticityDoesNotImplyTurbulence(TeacherStudentsScene): - def construct(self): - t_to_v = TextMobject( - "Turbulence", "$\\Rightarrow$", "Vorticity", - ) - v_to_t = TextMobject( - "Vorticity", "$\\Rightarrow$", "Turbulence", - ) - for words in t_to_v, v_to_t: - words.move_to(self.hold_up_spot, DR) - words.set_color_by_tex_to_color_map({ - "Vorticity": BLUE, - "Turbulence": GREEN, - }) - v_to_t.submobjects.reverse() - cross = Cross(v_to_t[1]) - - morty = self.teacher - self.play( - morty.change, "raise_right_hand", - FadeInFromDown(t_to_v) - ) - self.wait() - self.play(t_to_v.shift, 2 * UP,) - self.play( - TransformFromCopy(t_to_v, v_to_t, path_arc=PI / 2), - self.get_student_changes( - "erm", "confused", "sassy", - run_time=1 - ), - ShowCreation(cross, run_time=2), - ) - self.add(cross) - self.wait(4) - - -class SurroundingRectangleSnippet(Scene): - def construct(self): - rect = Rectangle() - rect.set_color(YELLOW) - rect.set_stroke(width=5) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - - -class FeynmanOnTurbulence(Scene): - def construct(self): - feynman = ImageMobject("Feynman_Woods", height=4) - name = TextMobject("Richard Feynman") - name.next_to(feynman, DOWN) - quote = TextMobject( - "``", "Turbulence", "is the most\\\\" - "important", "unsolved problem\\\\", - "of classical physics.''", - tex_to_color_map={ - "Turbulence": BLUE, - "unsolved problem\\\\": YELLOW, - }, - ) - quote[0].shift(SMALL_BUFF * RIGHT) - quote.next_to(feynman, RIGHT) - Group(feynman, name, quote).center() - - self.play( - FadeInFrom(feynman, UP), - FadeInFrom(name, DOWN), - Write(quote, run_time=4) - ) - self.wait() - - -class ShowNavierStokesEquations(Scene): - def construct(self): - self.introduce_equations() - self.ask_about_evolution() - self.ask_about_reasonable() - self.ask_about_blowup() - self.show_money() - - def introduce_equations(self): - name = TextMobject("Navier-Stokes equations (incompressible)") - equations = NavierStokesEquations() - name.to_edge(UP) - equations.next_to(name, DOWN, MED_LARGE_BUFF) - labels = equations.get_labels() - parts = equations.get_parts() - newtons_second = TextMobject( - "Newton's 2nd law \\\\ $ma = F$" - ) - newtons_second.next_to(parts, DOWN) - variables = TexMobject( - "&\\textbf{v}", "\\text{ is velocity}\\\\", - "&\\rho", "\\text{ is density}\\\\", - "&p{}", "\\text{ is pressure}\\\\", - "&\\mu", "\\text{ is viscosity}\\\\", - tex_to_color_map=NavierStokesEquations.CONFIG["tex_to_color_map"] - ) - variables.to_corner(DL) - - self.play(FadeInFromDown(equations)) - self.play(Write(name)) - self.play(LaggedStartMap( - FadeInFrom, variables, - lambda m: (m, RIGHT), - )) - self.wait() - self.play(Write(newtons_second)) - self.wait() - self.play( - FadeInFromDown(labels[0]), - newtons_second.next_to, variables, RIGHT, LARGE_BUFF - ) - self.play(ShowCreationThenFadeAround(parts[0])) - self.wait() - self.play(LaggedStartMap(FadeInFrom, labels[1:])) - self.wait(3) - self.play(LaggedStartMap( - FadeOut, VGroup(*it.chain(labels, variables, newtons_second)) - )) - - self.equations = equations - - def ask_about_evolution(self): - words = TextMobject( - "Given a start state...", - "...how does it evolve?" - ) - words.arrange(RIGHT, buff=2) - - words.next_to(self.equations, DOWN, LARGE_BUFF) - - self.play(Write(words[0])) - self.wait() - self.play(Write(words[1])) - self.wait(2) - self.play(FadeOut(words)) - - def ask_about_reasonable(self): - question = TextMobject( - "Do ``reasonable'' \\\\" - "solutions always\\\\" - "exist?" - ) - self.play(FadeInFromDown(question)) - self.wait() - - self.reasonable_question = question - - def ask_about_blowup(self): - axes, graph = self.get_axes_and_graph() - question = TextMobject("Is this possible?") - question.set_color(YELLOW) - question.move_to(axes.get_corner(UR), LEFT) - question.align_to(axes, UP) - q_arrow = Arrow( - question.get_bottom(), - graph.point_from_proportion(0.8), - buff=SMALL_BUFF, - path_arc=-60 * DEGREES - ) - q_arrow.set_stroke(WHITE, 3) - morty = Mortimer() - morty.to_corner(DR) - morty.change('confused', graph) - - self.play( - Write(axes, run_time=1), - self.reasonable_question.to_edge, LEFT, - self.reasonable_question.shift, DOWN, - ) - self.play( - Write(question), - ShowCreation(graph), - FadeIn(morty), - ) - self.add(q_arrow, morty) - self.play(ShowCreation(q_arrow), Blink(morty)) - self.wait() - self.play(morty.look_at, question) - self.wait() - self.play(morty.change, "maybe", graph) - self.wait(2) - to_fade = VGroup(question, q_arrow, axes, graph) - self.play( - LaggedStartMap(FadeOut, to_fade), - morty.change, "pondering" - ) - self.wait(2) - self.play(Blink(morty)) - self.wait(2) - - self.morty = morty - - def show_money(self): - # Million dollar problem - problem = TextMobject( - "Navier-Stokes existence \\\\ and smoothness problems" - ) - money = TextMobject("\\$1{,}000{,}000") - money.set_color(GREEN) - money.next_to(problem, DOWN) - pi1 = Randolph() - pi2 = self.morty - pi1.to_corner(DL) - pis = VGroup(pi1, pi2) - for pi in pis: - pi.change("pondering") - pi.money_eyes = VGroup() - for eye in pi.eyes: - cash = TexMobject("\\$") - cash.set_color(GREEN) - cash.replace(eye, dim_to_match=1) - pi.money_eyes.add(cash) - - self.play( - ReplacementTransform( - self.reasonable_question, - problem, - ), - pi2.look_at, problem, - pi1.look_at, problem, - VFadeIn(pi1), - ) - self.wait() - self.play(FadeInFromLarge(money)) - self.play( - pi1.change, "hooray", - pi2.change, "hooray", - ) - self.play( - ReplacementTransform(pi1.pupils, pi1.money_eyes), - ReplacementTransform(pi2.pupils, pi2.money_eyes), - ) - self.wait() - - # Helpers - def get_axes_and_graph(self): - axes = Axes( - x_min=-1, - x_max=5, - y_min=-1, - y_max=5, - ) - time = TextMobject("Time") - time.next_to(axes.x_axis, RIGHT) - ke = TextMobject("Kinetic energy") - ke.next_to(axes.y_axis, UP) - axes.add(time, ke) - axes.set_height(4) - axes.center() - axes.to_edge(DOWN) - v_line = DashedLine( - axes.coords_to_point(4, 0), - axes.coords_to_point(4, 5), - ) - axes.add(v_line) - graph = axes.get_graph( - lambda x: -1.0 / (x - 4), - x_min=0.01, - x_max=3.8, - ) - graph.set_color(BLUE) - return axes, graph - - -class NewtonsSecond(Scene): - def construct(self): - square = Square( - stroke_color=WHITE, - fill_color=LIGHT_GREY, - fill_opacity=0.5, - side_length=1 - ) - label = TexMobject("m") - label.scale(1.5) - label.move_to(square) - square.add(label) - square.save_state() - arrows = VGroup( - Vector(0.5 * UP).next_to(square, UP, buff=0), - Vector(RIGHT).next_to(square, RIGHT, buff=0), - ) - - self.play( - square.shift, 4 * RIGHT + 2 * UP, - rate_func=lambda t: t**2, - run_time=2 - ) - self.wait() - square.restore() - self.play( - LaggedStartMap(GrowArrow, arrows) - ) - square.add(arrows) - self.play( - square.shift, 4 * RIGHT + 2 * UP, - rate_func=lambda t: t**2, - run_time=2 - ) - self.wait() - - -class CandleLabel(Scene): - def construct(self): - word = TextMobject("Candle") - arrow = Vector(DR, color=WHITE) - arrow.move_to(word.get_bottom() + SMALL_BUFF * DOWN, UL) - self.play( - FadeInFromDown(word), - GrowArrow(arrow) - ) - self.wait() - - -class FiguresOfFluidDynamics(Scene): - def construct(self): - names = [ - "Leonhard Euler", - "George Stokes", - "Hermann von Helmholtz", - "Lewis Richardson", - "Geoffrey Taylor", - "Andrey Kolmogorov", - ] - images = Group(*[ - ImageMobject(name.replace(" ", "_"), height=3) - for name in names - ]) - images.arrange(RIGHT, buff=MED_SMALL_BUFF) - image_groups = Group() - for image, name in zip(images, names): - name_mob = TextMobject(name) - name_mob.scale(0.6) - name_mob.next_to(image, DOWN) - image_groups.add(Group(image, name_mob)) - image_groups.arrange_in_grid(2, 3) - image_groups.set_height(FRAME_HEIGHT - 1) - - self.play(LaggedStartMap( - FadeInFromDown, image_groups, - lag_ratio=0.5, - run_time=3 - )) - self.wait() - to_fade = image_groups[:-1] - to_fade.generate_target() - to_fade.target.space_out_submobjects(3) - to_fade.target.shift(3 * UL) - to_fade.target.fade(1) - self.play( - MoveToTarget(to_fade, remover=True), - image_groups[-1].set_height, 5, - image_groups[-1].center, - ) - self.wait() - - -class KineticEnergyBreakdown(Scene): - def construct(self): - title = TextMobject("Kinetic energy breakdown") - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).set_width(FRAME_WIDTH) - h_line.next_to(title, DOWN) - v_line = Line(h_line.get_center(), FRAME_HEIGHT * DOWN / 2) - lc_title = TextMobject("Simpler physics") - lc_title.set_color(YELLOW) - rc_title = TextMobject("Turbulence physics") - rc_title.set_color(GREEN) - for word, vect in (lc_title, LEFT), (rc_title, RIGHT): - word.next_to(h_line, DOWN) - word.shift(FRAME_WIDTH * vect / 4) - - left_items = VGroup( - TextMobject("- Big moving things"), - TextMobject("- Heat"), - ) - left_items.arrange(DOWN, aligned_edge=LEFT) - left_items.next_to(lc_title, DOWN, MED_LARGE_BUFF) - left_items.to_edge(LEFT) - - self.play( - Write(VGroup(*it.chain( - title, h_line, v_line, lc_title, rc_title - ))) - ) - self.wait() - for item in left_items: - self.play(FadeInFrom(item)) - self.wait() - - -class MovingCar(Scene): - def construct(self): - car = Car() - x = 3 - car.move_to(x * LEFT) - self.play(MoveCar(car, x * RIGHT, run_time=4)) - - -class Heat(Scene): - def construct(self): - box = Square( - side_length=2, - stroke_color=WHITE, - ) - balls = VGroup(*[ - self.get_ball(box) - for x in range(20) - ]) - - self.add(box, balls) - self.wait(20) - - def get_ball(self, box): - speed_factor = random.random() - ball = Dot( - radius=0.05, - color=interpolate_color(BLUE, RED, speed_factor) - ) - speed = 2 + 3 * speed_factor - direction = rotate_vector(RIGHT, TAU * random.random()) - ball.velocity = speed * direction - x0, y0, z0 = box.get_corner(DL) - x1, y1, z1 = box.get_corner(UR) - ball.move_to(np.array([ - interpolate(x0, x1, random.random()), - interpolate(y0, y1, random.random()), - 0 - ])) - - def update(ball, dt): - ball.shift(ball.velocity * dt) - if ball.get_left()[0] < box.get_left()[0]: - ball.velocity[0] = abs(ball.velocity[0]) - if ball.get_right()[0] > box.get_right()[0]: - ball.velocity[0] = -abs(ball.velocity[0]) - if ball.get_bottom()[1] < box.get_bottom()[1]: - ball.velocity[1] = abs(ball.velocity[1]) - if ball.get_top()[1] > box.get_top()[1]: - ball.velocity[1] = -abs(ball.velocity[1]) - return ball - - ball.add_updater(update) - return ball - - -class GrowArrowScene(Scene): - def construct(self): - arrow = Arrow(UP, DOWN, color=WHITE) - self.play(GrowArrow(arrow)) - self.wait() - - -class Poem(Scene): - def construct(self): - picture = ImageMobject("Lewis_Richardson") - picture.set_height(4) - picture.center().to_edge(LEFT, buff=LARGE_BUFF) - - title = TextMobject("Poem by Lewis F. Richardson") - title.to_edge(UP) - - poem_text = """ - Big{\\,\\,}whirls have little{\\,\\,}whirls\\\\ - which feed on their velocity,\\\\ - And little{\\,\\,}whirls have lesser{\\,\\,}whirls\\\\ - And so on to viscosity.\\\\ - """ - poem_words = [s for s in poem_text.split(" ") if s] - poem = TextMobject(*poem_words, alignment="") - poem.next_to(picture, RIGHT, LARGE_BUFF) - - self.add(picture) - self.play(FadeInFrom(title, DOWN)) - self.wait() - for word in poem: - if "whirl" in word.get_tex_string(): - word.set_color(BLUE) - self.play(ShowWord(word)) - self.wait(0.005 * len(word)**1.5) - - -class SwirlDiameterD(Scene): - def construct(self): - kwargs = { - "path_arc": PI, - "buff": SMALL_BUFF, - "color": WHITE - } - swirl = VGroup( - Arrow(RIGHT, LEFT, **kwargs), - Arrow(LEFT, RIGHT, **kwargs), - ) - swirl.set_stroke(width=5) - f = 1.5 - swirl.scale(f) - - h_line = DashedLine( - f * LEFT, f * RIGHT, - color=YELLOW, - ) - D_label = TexMobject("D") - D_label.scale(2) - D_label.next_to(h_line, UP, SMALL_BUFF) - D_label.match_color(h_line) - # diam = VGroup(h_line, D_label) - - self.play(*map(ShowCreation, swirl)) - self.play( - GrowFromCenter(h_line), - FadeInFrom(D_label, UP), - ) - self.wait() - - -class KolmogorovGraph(Scene): - def construct(self): - axes = Axes( - x_min=-1, - y_min=-1, - x_max=7, - y_max=9, - y_axis_config={ - "unit_size": 0.7, - } - ) - axes.center().shift(1.5 * RIGHT) - x_label = TexMobject("\\log(D)") - x_label.next_to(axes.x_axis.get_right(), UP) - y_label = TexMobject("\\log(\\text{K.E. at length scale D})") - y_label.scale(0.8) - y_label.next_to(axes.y_axis.get_top(), LEFT) - y_label.shift_onto_screen() - axes.add(x_label, y_label) - - v_lines = VGroup(*[ - DashedLine( - axes.coords_to_point(x, 0), - axes.coords_to_point(x, 9), - color=YELLOW, - stroke_width=1 - ) - for x in [0.5, 5] - ]) - inertial_subrange = TextMobject("``Inertial subrange''") - inertial_subrange.scale(0.7) - inertial_subrange.next_to(v_lines.get_bottom(), UP) - - def func(x): - if 0.5 < x < 5: - return (5 / 3) * x - elif x < 0.5: - return 5 * (x - 0.5) + 0.5 * (5 / 3) - elif x > 5: - return np.log(x) + (5 / 3) * 5 - np.log(5) - - graph = axes.get_graph(func, x_min=0.3, x_max=7) - - prop_label = TexMobject("\\text{K.E.} \\propto D^{5/3}") - prop_label.next_to( - graph.point_from_proportion(0.5), UL, - buff=0 - ) - - self.add(axes) - self.play(ShowCreation(graph)) - self.play(FadeInFromDown(prop_label)) - self.wait() - self.add(v_lines) - self.play(Write(inertial_subrange)) - self.wait() - - -class TechnicalNote(Scene): - def construct(self): - title = TextMobject("Technical note:") - title.to_edge(UP) - title.set_color(RED) - self.add(title) - - words = TextMobject(""" - This idea of quantifying the energy held at different - length scales is typically defined - in terms of an ``energy spectrum'' involving the Fourier - transform of a function measuring the correlations - between the fluid's velocities at different points in space. - I know, yikes! - \\quad\\\\ - \\quad\\\\ - Building up the relevant background for that is a bit cumbersome, - so we'll be thinking about the energy at different scales in - terms of all eddy's with a given diameter. This is admittedly - a less well-defined notion, but it does capture the spirit - of Kolmogorov's result. - \\quad\\\\ - \\quad\\\\ - See the links in the description for more details, - if you're curious. - """, alignment="") - words.scale(0.75) - words.next_to(title, DOWN, LARGE_BUFF) - - self.add(title, words) - - -class FiveThirds(TeacherStudentsScene): - def construct(self): - words = TextMobject( - "5/3", "is a sort of fundamental\\\\ constant of turbulence" - ) - self.teacher_says(words) - self.change_student_modes("pondering", "maybe", "erm") - self.play( - FadeOut(self.teacher.bubble), - FadeOut(words[1]), - self.teacher.change, "raise_right_hand", - words[0].scale, 1.5, - words[0].move_to, self.hold_up_spot - ) - self.change_student_modes("thinking", "pondering", "hooray") - self.wait(3) - - -class TurbulenceGifLabel(Scene): - def construct(self): - title = TextMobject("Turbulence in 2d") - title.to_edge(UP) - - attribution = TextMobject( - "Animation by Gabe Weymouth (@gabrielweymouth)" - ) - attribution.scale(0.5) - attribution.to_edge(DOWN) - - self.play(Write(title)) - self.play(FadeInFrom(attribution, UP)) - self.wait() - - -class VortexStretchingLabel(Scene): - def construct(self): - title = TextMobject("Vortex stretching") - self.play(Write(title)) - self.wait() - - -class VortedStretching(ThreeDScene): - CONFIG = { - "n_circles": 200, - } - - def construct(self): - axes = ThreeDAxes() - axes.set_stroke(width=1) - self.add(axes) - self.move_camera( - phi=70 * DEGREES, - theta=-145 * DEGREES, - run_time=0, - ) - self.begin_ambient_camera_rotation() - - short_circles = self.get_cylinder_circles(2, 0.5, 0.5) - tall_circles = short_circles.copy().scale(0.125) - tall_circles.stretch(16 * 4, 2) - torus_circles = tall_circles.copy() - for circle in torus_circles: - circle.shift(RIGHT) - z = circle.get_center()[2] - circle.shift(z * IN) - angle = PI * z / 2 - circle.rotate(angle, axis=DOWN, about_point=ORIGIN) - - circles = short_circles.copy() - flow_lines = self.get_flow_lines(circles) - - self.add(circles, flow_lines) - self.play(LaggedStartMap(ShowCreation, circles)) - self.wait(5) - self.play(Transform(circles, tall_circles, run_time=3)) - self.wait(10) - self.play(Transform( - circles, torus_circles, - run_time=3 - )) - self.wait(10) - - def get_cylinder_circles(self, radius, radius_var, max_z): - return VGroup(*[ - ParametricFunction( - lambda t: np.array([ - np.cos(TAU * t) * r, - np.sin(TAU * t) * r, - z - ]), - **self.get_circle_kwargs() - ) - for z in sorted(max_z * np.random.random(self.n_circles)) - for r in [radius + radius_var * random.random()] - ]).center() - - def get_torus_circles(self, out_r, in_r, in_r_var): - result = VGroup() - for u in sorted(np.random.random(self.n_circles)): - r = in_r + in_r_var * random.random() - circle = ParametricFunction( - lambda t: r * np.array([ - np.cos(TAU * t), - np.sin(TAU * t), - 0, - ]), - **self.get_circle_kwargs() - ) - circle.shift(out_r * RIGHT) - circle.rotate( - TAU * u - PI, - about_point=ORIGIN, - axis=DOWN, - ) - result.add(circle) - return result - - def get_flow_lines(self, circle_group): - window = 0.3 - - def update_circle(circle, dt): - circle.total_time += dt - diameter = get_norm( - circle.template.point_from_proportion(0) - - circle.template.point_from_proportion(0.5) - ) - modulus = np.sqrt(diameter) + 0.1 - alpha = (circle.total_time % modulus) / modulus - circle.pointwise_become_partial( - circle.template, - max(interpolate(-window, 1, alpha), 0), - min(interpolate(0, 1 + window, alpha), 1), - ) - - result = VGroup() - for template in circle_group: - circle = template.deepcopy() - circle.set_stroke( - color=interpolate_color(BLUE_A, BLUE_E, random.random()), - # width=3 * random.random() - width=1, - ) - circle.template = template - circle.total_time = 4 * random.random() - circle.add_updater(update_circle) - result.add(circle) - return result - - def get_circle_kwargs(self): - return { - "stroke_color": BLACK, - "stroke_width": 0, - } - - -class TurbulenceEndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "1stViewMaths", - "Adrian Robinson", - "Alexis Olson", - "Andrew Busey", - "Ankalagon", - "Art Ianuzzi", - "Awoo", - "Ayan Doss", - "Bernd Sing", - "Boris Veselinovich", - "Brian Staroselsky", - "Britt Selvitelle", - "Carla Kirby", - "Charles Southerland", - "Chris Connett", - "Christian Kaiser", - "Clark Gaebel", - "Cooper Jones", - "Danger Dai", - "Dave B", - "Dave Kester", - "David Clark", - "Delton Ding", - "Devarsh Desai", - "eaglle", - "Eric Younge", - "Eryq Ouithaqueue", - "Federico Lebron", - "Florian Chudigiewitsch", - "Giovanni Filippi", - "Hal Hildebrand", - "Igor Napolskikh", - "Jacob Magnuson", - "Jameel Syed", - "James Hughes", - "Jan Pijpers", - "Jason Hise", - "Jeff Linse", - "Jeff Straathof", - "Jerry Ling", - "John Griffith", - "John Haley", - "John V Wertheim", - "Jonathan Eppele", - "Jonathan Wilson", - "Jordan Scales", - "Joseph John Cox", - "Julian Pulgarin", - "Kai-Siang Ang", - "Kanan Gill", - "L0j1k", - "Linh Tran", - "Luc Ritchie", - "Ludwig Schubert", - "Lukas -krtek.net- Novy", - "Magister Mugit", - "Magnus Dahlström", - "Mark B Bahu", - "Markus Persson", - "Mathew Bramson", - "Mathias Jansson", - "Matt Langford", - "Matt Roveto", - "Matthew Cocke", - "Mehdi Razavi", - "Michael Faust", - "Michael Hardel", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nero Li", - "Oliver Steele", - "Omar Zrien", - "Peter Ehrnstrom", - "Prasant Jagannath", - "Randy C. Will", - "Richard Burgmann", - "Ripta Pasay", - "Rish Kundalia", - "Robert Teed", - "Roobie", - "Ryan Atallah", - "Ryan Williams", - "Sindre Reino Trosterud", - "Solara570", - "Song Gao", - "Steven Soloway", - "Steven Tomlinson", - "Stevie Metke", - "Ted Suzman", - "Valeriy Skobelev", - "Xavier Bernard", - "Yaw Etse", - "YinYangBalance.Asia", - "Zach Cardwell", - ], - } - - -class LaserWord(Scene): - def construct(self): - self.add(TextMobject("Laser").scale(2)) - - -class TurbulenceWord(Scene): - def construct(self): - self.add(TextMobject("Turbulence").scale(2)) - - -class ArrowScene(Scene): - def construct(self): - arrow = Arrow(LEFT, RIGHT, color=WHITE) - arrow.add_to_back(arrow.copy().set_stroke(BLACK, 5)) - self.add(arrow) diff --git a/from_3b1b/old/uncertainty.py b/from_3b1b/old/uncertainty.py deleted file mode 100644 index f707a3f0..00000000 --- a/from_3b1b/old/uncertainty.py +++ /dev/null @@ -1,4739 +0,0 @@ -# -*- coding: utf-8 -*- - -import scipy -from manimlib.imports import * -from from_3b1b.old.fourier import * - -import warnings -warnings.warn(""" - Warning: This file makes use of - ContinualAnimation, which has since - been deprecated -""") - -FREQUENCY_COLOR = RED -USE_ALMOST_FOURIER_BY_DEFAULT = False - -class GaussianDistributionWrapper(Line): - """ - This is meant to encode a 2d normal distribution as - a mobject (so as to be able to have it be interpolated - during animations). It is a line whose center is the mean - mu of a distribution, and whose radial vector (center to end) - is the distribution's standard deviation - """ - CONFIG = { - "stroke_width" : 0, - "mu" : ORIGIN, - "sigma" : RIGHT, - } - def __init__(self, **kwargs): - Line.__init__(self, ORIGIN, RIGHT, **kwargs) - self.change_parameters(self.mu, self.sigma) - - def change_parameters(self, mu = None, sigma = None): - curr_mu, curr_sigma = self.get_parameters() - mu = mu if mu is not None else curr_mu - sigma = sigma if sigma is not None else curr_sigma - self.put_start_and_end_on(mu - sigma, mu + sigma) - return self - - def get_parameters(self): - """ Return mu_x, mu_y, sigma_x, sigma_y""" - center, end = self.get_center(), self.get_end() - return center, end-center - - def get_random_points(self, size = 1): - mu, sigma = self.get_parameters() - return np.array([ - np.array([ - np.random.normal(mu_coord, sigma_coord) - for mu_coord, sigma_coord in zip(mu, sigma) - ]) - for x in range(size) - ]) - -class ProbabalisticMobjectCloud(ContinualAnimation): - CONFIG = { - "fill_opacity" : 0.25, - "n_copies" : 100, - "gaussian_distribution_wrapper_config" : {}, - "time_per_change" : 1./60, - "start_up_time" : 0, - } - def __init__(self, prototype, **kwargs): - digest_config(self, kwargs) - fill_opacity = self.fill_opacity or prototype.get_fill_opacity() - if "mu" not in self.gaussian_distribution_wrapper_config: - self.gaussian_distribution_wrapper_config["mu"] = prototype.get_center() - self.gaussian_distribution_wrapper = GaussianDistributionWrapper( - **self.gaussian_distribution_wrapper_config - ) - self.time_since_last_change = np.inf - group = VGroup(*[ - prototype.copy().set_fill(opacity = fill_opacity) - for x in range(self.n_copies) - ]) - ContinualAnimation.__init__(self, group, **kwargs) - self.update_mobject(0) - - def update_mobject(self, dt): - self.time_since_last_change += dt - if self.time_since_last_change < self.time_per_change: - return - self.time_since_last_change = 0 - - group = self.mobject - points = self.gaussian_distribution_wrapper.get_random_points(len(group)) - for mob, point in zip(group, points): - self.update_mobject_by_point(mob, point) - return self - - def update_mobject_by_point(self, mobject, point): - mobject.move_to(point) - return self - -class ProbabalisticDotCloud(ProbabalisticMobjectCloud): - CONFIG = { - "color" : BLUE, - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - dot = Dot(color = self.color) - ProbabalisticMobjectCloud.__init__(self, dot) - -class ProbabalisticVectorCloud(ProbabalisticMobjectCloud): - CONFIG = { - "color" : RED, - "n_copies" : 20, - "fill_opacity" : 0.5, - "center_func" : lambda : ORIGIN, - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - vector = Vector( - RIGHT, color = self.color, - max_tip_length_to_length_ratio = 1, - ) - ProbabalisticMobjectCloud.__init__(self, vector) - - def update_mobject_by_point(self, vector, point): - vector.put_start_and_end_on( - self.center_func(), - point - ) - -class RadarDish(SVGMobject): - CONFIG = { - "file_name" : "radar_dish", - "fill_color" : LIGHT_GREY, - "stroke_color" : WHITE, - "stroke_width" : 1, - "height" : 1, - } - -class Plane(SVGMobject): - CONFIG = { - "file_name" : "plane", - "color" : LIGHT_GREY, - "height" : 1, - } - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - self.rotate(-TAU/4) - -class FalconHeavy(SVGMobject): - CONFIG = { - "file_name" : "falcon_heavy", - "color" : WHITE, - "logo_color" : BLUE_E, - "height" : 1.5, - } - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - self.logo = self[-9:] - self.logo.set_color(self.logo_color) - -class RadarPulseSingleton(ContinualAnimation): - CONFIG = { - "speed" : 3.0, - "direction" : RIGHT, - "start_up_time" : 0, - "fade_in_time" : 0.5, - "color" : WHITE, - "stroke_width" : 3, - } - def __init__(self, radar_dish, target, **kwargs): - digest_config(self, kwargs) - self.direction = self.direction/get_norm(self.direction) - self.radar_dish = radar_dish - self.target = target - self.reflection_distance = None - self.arc = Arc( - start_angle = -30*DEGREES, - angle = 60*DEGREES, - ) - self.arc.set_height(0.75*radar_dish.get_height()) - self.arc.move_to(radar_dish, UP+RIGHT) - self.start_points = np.array(self.arc.points) - self.start_center = self.arc.get_center() - self.finished = False - - ContinualAnimation.__init__(self, self.arc, **kwargs) - - def update_mobject(self, dt): - arc = self.arc - total_distance = self.speed*self.internal_time - arc.points = np.array(self.start_points) - arc.shift(total_distance*self.direction) - - if self.internal_time < self.fade_in_time: - alpha = np.clip(self.internal_time/self.fade_in_time, 0, 1) - arc.set_stroke(self.color, alpha*self.stroke_width) - - if self.reflection_distance is None: - #Check if reflection is happening - arc_point = arc.get_edge_center(self.direction) - target_point = self.target.get_edge_center(-self.direction) - arc_distance = np.dot(arc_point, self.direction) - target_distance = np.dot(target_point, self.direction) - if arc_distance > target_distance: - self.reflection_distance = target_distance - #Don't use elif in case the above code creates reflection_distance - if self.reflection_distance is not None: - delta_distance = total_distance - self.reflection_distance - point_distances = np.dot(self.direction, arc.points.T) - diffs = point_distances - self.reflection_distance - shift_vals = np.outer(-2*np.maximum(diffs, 0), self.direction) - arc.points += shift_vals - - #Check if done - arc_point = arc.get_edge_center(-self.direction) - if np.dot(arc_point, self.direction) < np.dot(self.start_center, self.direction): - self.finished = True - self.arc.fade(1) - - def is_finished(self): - return self.finished - -class RadarPulse(ContinualAnimation): - CONFIG = { - "n_pulse_singletons" : 8, - "frequency" : 0.05, - "colors" : [BLUE, YELLOW] - } - def __init__(self, *args, **kwargs): - digest_config(self, kwargs) - colors = color_gradient(self.colors, self.n_pulse_singletons) - self.pulse_singletons = [ - RadarPulseSingleton(*args, color = color, **kwargs) - for color in colors - ] - pluse_mobjects = VGroup(*[ps.mobject for ps in self.pulse_singletons]) - ContinualAnimation.__init__(self, pluse_mobjects, **kwargs) - - def update_mobject(self, dt): - for i, ps in enumerate(self.pulse_singletons): - ps.internal_time = self.internal_time - i*self.frequency - ps.update_mobject(dt) - - def is_finished(self): - return all([ps.is_finished() for ps in self.pulse_singletons]) - -class MultipleFlashes(Succession): - CONFIG = { - "run_time_per_flash" : 1.0, - "num_flashes" : 3, - } - def __init__(self, *args, **kwargs): - digest_config(self, kwargs) - kwargs["run_time"] = self.run_time_per_flash - Succession.__init__(self, *[ - Flash(*args, **kwargs) - for x in range(self.num_flashes) - ]) - -class TrafficLight(SVGMobject): - CONFIG = { - "file_name" : "traffic_light", - "height" : 0.7, - "post_height" : 2, - "post_width" : 0.05, - } - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - post = Rectangle( - height = self.post_height, - width = self.post_width, - stroke_width = 0, - fill_color = WHITE, - fill_opacity = 1, - ) - self.move_to(post.get_top(), DOWN) - self.add_to_back(post) - -################### - -class MentionUncertaintyPrinciple(TeacherStudentsScene): - def construct(self): - title = TextMobject("Heisenberg Uncertainty Principle") - title.to_edge(UP) - - dot_cloud = ProbabalisticDotCloud() - vector_cloud = ProbabalisticVectorCloud( - gaussian_distribution_wrapper_config = {"sigma_x" : 0.2}, - center_func = lambda : dot_cloud.gaussian_distribution_wrapper.get_parameters()[0], - ) - for cloud in dot_cloud, vector_cloud: - cloud.gaussian_distribution_wrapper.next_to( - title, DOWN, 2*LARGE_BUFF - ) - vector_cloud.gaussian_distribution_wrapper.shift(3*RIGHT) - - def get_brace_text_group_update(gdw, vect, text, color): - brace = Brace(gdw, vect) - text = brace.get_tex("2\\sigma_{\\text{%s}}"%text, buff = SMALL_BUFF) - group = VGroup(brace, text) - def update_group(group): - brace, text = group - brace.match_width(gdw, stretch = True) - brace.next_to(gdw, vect) - text.next_to(brace, vect, buff = SMALL_BUFF) - group.set_color(color) - return Mobject.add_updater(group, update_group) - - dot_brace_anim = get_brace_text_group_update( - dot_cloud.gaussian_distribution_wrapper, - DOWN, "position", dot_cloud.color - ) - vector_brace_anim = get_brace_text_group_update( - vector_cloud.gaussian_distribution_wrapper, - UP, "momentum", vector_cloud.color - ) - - self.add(title) - self.add(dot_cloud) - self.play( - Write(title), - self.teacher.change, "raise_right_hand", - self.get_student_changes(*["pondering"]*3) - ) - self.play( - Write(dot_brace_anim.mobject, run_time = 1) - ) - self.add(dot_brace_anim) - self.wait() - # self.wait(2) - self.play( - dot_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma" : 0.1*RIGHT}, - run_time = 2, - ) - self.wait() - self.add(vector_cloud) - self.play( - FadeIn(vector_brace_anim.mobject) - ) - self.add(vector_brace_anim) - self.play( - vector_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma" : RIGHT}, - self.get_student_changes(*3*["confused"]), - run_time = 3, - ) - #Back and forth - for x in range(2): - self.play( - dot_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma" : 2*RIGHT}, - vector_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma" : 0.1*RIGHT}, - run_time = 3, - ) - self.change_student_modes("thinking", "erm", "sassy") - self.play( - dot_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma" : 0.1*RIGHT}, - vector_cloud.gaussian_distribution_wrapper.change_parameters, - {"sigma" : 1*RIGHT}, - run_time = 3, - ) - self.wait() - -class FourierTradeoff(Scene): - CONFIG = { - "show_text" : True, - "complex_to_real_func" : lambda z : z.real, - "widths" : [6, 0.02, 1], - } - def construct(self): - #Setup axes - time_mean = 4 - time_axes = Axes( - x_min = 0, - x_max = 2*time_mean, - x_axis_config = {"unit_size" : 1.5}, - y_min = -2, - y_max = 2, - y_axis_config = {"unit_size" : 0.5} - ) - time_label = TextMobject("Time") - time_label.scale(1.5) - time_label.next_to( - time_axes.x_axis.get_right(), UP+LEFT, - buff = MED_SMALL_BUFF, - ) - time_axes.add(time_label) - time_axes.center().to_edge(UP) - time_axes.x_axis.add_numbers(*list(range(1, 2*time_mean))) - - frequency_axes = Axes( - x_min = 0, - x_max = 8, - x_axis_config = {"unit_size" : 1.5}, - y_min = -0.025, - y_max = 0.075, - y_axis_config = { - "unit_size" : 30, - "tick_frequency" : 0.025, - }, - color = TEAL, - ) - frequency_label = TextMobject("Frequency") - frequency_label.scale(1.5) - frequency_label.next_to( - frequency_axes.x_axis.get_right(), UP+LEFT, - buff = MED_SMALL_BUFF, - ) - frequency_label.set_color(FREQUENCY_COLOR) - frequency_axes.add(frequency_label) - frequency_axes.move_to(time_axes, LEFT) - frequency_axes.to_edge(DOWN, buff = LARGE_BUFF) - frequency_axes.x_axis.add_numbers() - - # Graph information - - #x-coordinate of this point determines width of wave_packet graph - width_tracker = ExponentialValueTracker(0.5) - get_width = width_tracker.get_value - - def get_wave_packet_function(): - factor = 1./get_width() - return lambda t : (factor**0.25)*np.cos(4*TAU*t)*np.exp(-factor*(t-time_mean)**2) - - def get_wave_packet(): - graph = time_axes.get_graph( - get_wave_packet_function(), - num_graph_points = 200, - ) - graph.set_color(YELLOW) - return graph - - time_radius = 10 - def get_wave_packet_fourier_transform(): - return get_fourier_graph( - frequency_axes, - get_wave_packet_function(), - t_min = time_mean - time_radius, - t_max = time_mean + time_radius, - n_samples = 2*time_radius*17, - complex_to_real_func = self.complex_to_real_func, - color = FREQUENCY_COLOR, - ) - - wave_packet = get_wave_packet() - wave_packet_update = UpdateFromFunc( - wave_packet, - lambda g : Transform(g, get_wave_packet()).update(1) - ) - fourier_graph = get_wave_packet_fourier_transform() - fourier_graph_update = UpdateFromFunc( - fourier_graph, - lambda g : Transform(g, get_wave_packet_fourier_transform()).update(1) - ) - - arrow = Arrow( - wave_packet, frequency_axes.coords_to_point( - 4, frequency_axes.y_max/2, - ), - color = FREQUENCY_COLOR, - ) - fourier_words = TextMobject("Fourier Transform") - fourier_words.next_to(arrow, LEFT, buff = MED_LARGE_BUFF) - sub_words = TextMobject("(To be explained shortly)") - sub_words.set_color(BLUE) - sub_words.scale(0.75) - sub_words.next_to(fourier_words, DOWN) - - #Draw items - self.add(time_axes, frequency_axes) - self.play(ShowCreation(wave_packet, rate_func = double_smooth)) - anims = [ReplacementTransform( - wave_packet.copy(), fourier_graph - )] - if self.show_text: - anims += [ - GrowArrow(arrow), - Write(fourier_words, run_time = 1) - ] - self.play(*anims) - # self.play(FadeOut(arrow)) - self.wait() - for width in self.widths: - self.play( - width_tracker.set_value, width, - wave_packet_update, - fourier_graph_update, - run_time = 3 - ) - if sub_words not in self.mobjects and self.show_text: - self.play(FadeIn(sub_words)) - else: - self.wait() - self.wait() - -class ShowPlan(PiCreatureScene): - def construct(self): - self.add_title() - words = self.get_words() - self.play_sound_anims(words[0]) - self.play_doppler_anims(words[1]) - self.play_quantum_anims(words[2]) - - def add_title(self): - title = TextMobject("The plan") - title.scale(1.5) - title.to_edge(UP) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.next_to(title, DOWN) - self.add(title, h_line) - - def get_words(self): - trips = [ - ("sound waves", "(time vs. frequency)", YELLOW), - ("Doppler radar", "(distance vs. velocity)", GREEN), - ("quantum particles", "(position vs. momentum)", BLUE), - ] - words = VGroup() - for topic, tradeoff, color in trips: - word = TextMobject("Uncertainty for", topic, tradeoff) - word[1:].set_color(color) - word[2].scale(0.75) - word[2].next_to(word[1], DOWN, buff = 1.5*SMALL_BUFF) - words.add(word) - words.arrange(DOWN, aligned_edge = LEFT, buff = MED_LARGE_BUFF) - words.to_edge(LEFT) - - return words - - def play_sound_anims(self, word): - morty = self.pi_creature - wave = FunctionGraph( - lambda x : 0.3*np.sin(15*x)*np.sin(0.5*x), - x_min = 0, x_max = 30, - step_size = 0.001, - ) - wave.next_to(word, RIGHT) - rect = BackgroundRectangle(wave, fill_opacity = 1) - rect.stretch(2, 1) - rect.next_to(wave, LEFT, buff = 0) - always_shift(wave, direction=LEFT, rate=5) - wave_fader = UpdateFromAlphaFunc( - wave, - lambda w, a : w.set_stroke(width = 3*a) - ) - checkmark = self.get_checkmark(word) - - self.add(wave) - self.add_foreground_mobjects(rect, word) - self.play( - Animation(word), - wave_fader, - morty.change, "raise_right_hand", word - ) - self.wait(2) - wave_fader.rate_func = lambda a : 1-smooth(a) - self.add_foreground_mobjects(checkmark) - self.play( - Write(checkmark), - morty.change, "happy", - wave_fader, - ) - self.remove_foreground_mobjects(rect, word) - self.add(word) - self.wait() - - def play_doppler_anims(self, word): - morty = self.pi_creature - - radar_dish = RadarDish() - radar_dish.next_to(word, DOWN, aligned_edge = LEFT) - target = Plane() - # target.match_height(radar_dish) - target.next_to(radar_dish, RIGHT, buff = LARGE_BUFF) - always_shift(target, direction = RIGHT, rate = 1.25) - - pulse = RadarPulse(radar_dish, target) - - checkmark = self.get_checkmark(word) - - self.add(target) - self.play( - Write(word), - DrawBorderThenFill(radar_dish), - UpdateFromAlphaFunc( - target, lambda m, a : m.set_fill(opacity = a) - ), - morty.change, "pondering", - run_time = 1 - ) - self.add(pulse) - count = it.count() #TODO, this is not a great hack... - while not pulse.is_finished() and next(count) < 15: - self.play( - morty.look_at, pulse.mobject, - run_time = 0.5 - ) - self.play( - Write(checkmark), - UpdateFromAlphaFunc( - target, lambda m, a : m.set_fill(opacity = 1-a) - ), - FadeOut(radar_dish), - morty.change, "happy" - ) - self.wait() - - def play_quantum_anims(self, word): - morty = self.pi_creature - dot_cloud = ProbabalisticDotCloud() - gdw = dot_cloud.gaussian_distribution_wrapper - gdw.next_to(word, DOWN, MED_LARGE_BUFF) - gdw.rotate(5*DEGREES) - gdw.save_state() - gdw.scale(0) - - - checkmark = self.get_checkmark(word) - ish = TextMobject("$\\dots$ish") - ish.next_to(checkmark, RIGHT, -SMALL_BUFF, DOWN) - - self.add(dot_cloud) - self.play( - Write(word), - FadeIn(dot_cloud.mobject), - morty.change, "confused", - ) - self.play(gdw.restore, run_time = 2) - self.play(Write(checkmark)) - self.wait() - self.play( - Write(ish), - morty.change, 'maybe' - ) - self.wait(6) - - - ## - - def get_checkmark(self, word): - checkmark = TexMobject("\\checkmark") - checkmark.set_color(GREEN) - checkmark.scale(1.25) - checkmark.next_to(word[1], UP+RIGHT, buff = 0) - return checkmark - -class StartWithIntuition(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "You already \\\\ have this \\\\ intuition", - bubble_kwargs = { - "height" : 3.5, - "width" : 3, - }, - ) - self.change_student_modes("pondering", "erm", "maybe") - self.look_at(VectorizedPoint(4*LEFT + 2*UP)) - self.wait(5) - -class TwoCarsAtRedLight(Scene): - CONFIG = { - "text_scale_val" : 0.75, - } - def construct(self): - self.pull_up_behind() - self.flash_in_sync_short_time() - self.show_low_confidence() - self.flash_in_sync_long_time() - self.show_high_confidence() - - def pull_up_behind(self): - #Setup Traffic light - traffic_light = TrafficLight() - traffic_light.move_to(6*RIGHT + 2.5*DOWN, DOWN) - source_point = VectorizedPoint( - traffic_light[2].get_right() - ) - screen = Line(ORIGIN, UP) - screen.next_to(source_point, RIGHT, LARGE_BUFF) - red_light = Spotlight( - color = RED, - source_point = source_point, - radius = 0.5, - screen = screen, - num_levels = 20, - opacity_function = lambda r : 1/(10*r**2+1) - ) - red_light.fade(0.5) - red_light.rotate(TAU/2, about_edge = LEFT) - self.add(red_light, traffic_light) - - #Setup cars - car1, car2 = cars = self.cars = VGroup(*[ - Car() for x in range(2) - ]) - cars.arrange(RIGHT, buff = LARGE_BUFF) - cars.next_to( - traffic_light, LEFT, - buff = LARGE_BUFF, aligned_edge = DOWN - ) - car2.pi_creature.set_color(GREY_BROWN) - car1.start_point = car1.get_corner(DOWN+RIGHT) - car1.shift(FRAME_X_RADIUS*LEFT) - - #Pull up car - self.add(cars) - self.play( - SwitchOn( - red_light, - rate_func = squish_rate_func(smooth, 0, 0.3), - ), - Animation(traffic_light), - self.get_flashes(car2, num_flashes = 3), - MoveCar( - car1, car1.start_point, - run_time = 3, - rate_func = rush_from, - ) - ) - - def flash_in_sync_short_time(self): - car1, car2 = cars = self.cars - - #Setup axes - axes = Axes( - x_min = 0, - x_max = 5, - y_min = 0, - y_max = 2, - y_axis_config = { - "tick_frequency" : 0.5, - }, - ) - axes.x_axis.add_numbers(1, 2, 3) - time_label = TextMobject("Time") - time_label.scale(self.text_scale_val) - time_label.next_to(axes.x_axis.get_right(), DOWN) - y_title = TextMobject("Signal") - y_title.scale(self.text_scale_val) - y_title.next_to(axes.y_axis, UP, SMALL_BUFF) - axes.add(time_label, y_title) - axes.to_corner(UP+LEFT, buff = MED_SMALL_BUFF) - graph = axes.get_graph( - self.get_multispike_function(list(range(1, 4))), - x_min = 0.8, - x_max = 3.8, - ) - graph.set_color(YELLOW) - - #Label short duration - brace = Brace(Line( - axes.input_to_graph_point(1, graph), - axes.input_to_graph_point(3, graph), - ), UP) - text = TextMobject("Short duration observation") - text.scale(self.text_scale_val) - text.next_to(brace, UP, SMALL_BUFF) - text.align_to( - axes.coords_to_point(0.25, 0), LEFT - ) - - - self.play( - self.get_flashes(car1, num_flashes = 2), - self.get_flashes(car2, num_flashes = 2), - LaggedStartMap(FadeIn, VGroup( - axes, time_label, y_title, - )) - ) - self.play( - self.get_flashes(car1, num_flashes = 3), - self.get_flashes(car2, num_flashes = 3), - ShowCreation(graph, rate_func=linear, run_time = 3) - ) - self.play( - self.get_flashes(car1, num_flashes = 10), - self.get_flashes(car2, num_flashes = 10, run_time_per_flash = 0.98), - GrowFromCenter(brace), - Write(text), - ) - - self.time_axes = axes - self.time_graph = graph - self.time_graph_label = VGroup( - brace, text - ) - - def show_low_confidence(self): - car1, car2 = cars = self.cars - time_axes = self.time_axes - - #Setup axes - frequency_axes = Axes( - x_min = 0, - x_max = 3, - y_min = 0, - y_max = 1.5, - y_axis_config = { - "tick_frequency" : 0.5, - } - ) - frequency_axes.next_to(time_axes, DOWN, LARGE_BUFF) - frequency_axes.set_color(LIGHT_GREY) - frequency_label = TextMobject("Frequency") - frequency_label.scale(self.text_scale_val) - frequency_label.next_to(frequency_axes.x_axis.get_right(), DOWN) - frequency_axes.add( - frequency_label, - VectorizedPoint(frequency_axes.y_axis.get_top()) - ) - frequency_axes.x_axis.add_numbers(1, 2) - frequency_graph = frequency_axes.get_graph( - lambda x : np.exp(-4*(x-1)**2), - x_min = 0, - x_max = 2, - ) - frequency_graph.set_color(RED) - peak_point = frequency_axes.input_to_graph_point( - 1, frequency_graph - ) - - #Setup label - label = TextMobject("Low confidence") - label.scale(self.text_scale_val) - label.move_to(peak_point + UP+RIGHT, DOWN) - label.match_color(frequency_graph) - arrow = Arrow(label.get_bottom(), peak_point, buff = 2*SMALL_BUFF) - arrow.match_color(frequency_graph) - - self.play( - ReplacementTransform( - self.time_axes.copy(), frequency_axes - ), - ReplacementTransform( - self.time_graph.copy(), frequency_graph - ), - ) - self.play( - Write(label), - GrowArrow(arrow) - ) - self.wait() - - self.frequency_axes = frequency_axes - self.frequency_graph = frequency_graph - self.frequency_graph_label = VGroup( - label, arrow - ) - - def flash_in_sync_long_time(self): - time_graph = self.time_graph - time_axes = self.time_axes - frequency_graph = self.frequency_graph - frequency_axes = self.frequency_axes - - n_spikes = 12 - new_time_graph = time_axes.get_graph( - self.get_multispike_function(list(range(1, n_spikes+1))), - x_min = 0.8, - x_max = n_spikes + 0.8, - ) - new_time_graph.match_color(time_graph) - - new_frequency_graph = frequency_axes.get_graph( - lambda x : np.exp(-500*(x-1)**2), - x_min = 0, - x_max = 2, - num_anchors = 500, - ) - new_frequency_graph.match_color(self.frequency_graph) - - def pin_freq_graph_end_points(freq_graph): - freq_graph.points[0] = frequency_axes.coords_to_point(0, 0) - freq_graph.points[-1] = frequency_axes.coords_to_point(2, 0) - - self.play(LaggedStartMap( - FadeOut, VGroup( - self.time_graph_label, - self.frequency_graph_label, - self.time_graph, - ) - )) - self.play( - ApplyMethod( - self.time_axes.x_axis.stretch, 2.5, 0, - {"about_edge" : LEFT}, - run_time = 4, - rate_func = squish_rate_func(smooth, 0.3, 0.6), - ), - UpdateFromFunc( - self.time_axes.x_axis.tip, - lambda m : m.move_to( - self.time_axes.x_axis.get_right(), - LEFT - ) - ), - ShowCreation( - new_time_graph, - run_time = n_spikes, - rate_func=linear, - ), - ApplyMethod( - frequency_graph.stretch, 0.1, 0, - run_time = n_spikes, - ), - UpdateFromFunc(frequency_graph, pin_freq_graph_end_points), - *[ - self.get_flashes(car, num_flashes = n_spikes) - for car in self.cars - ] - ) - - self.new_time_graph = new_time_graph - self.new_frequency_graph = new_frequency_graph - - def show_high_confidence(self): - #Frequency stuff - arrow = self.frequency_graph_label[1] - label = TextMobject("High confidence") - label.scale(self.text_scale_val) - label.next_to(arrow.get_start(), UP, SMALL_BUFF) - label.match_color(arrow) - - frequency_axes = self.frequency_axes - - #Time stuff - new_time_graph = self.new_time_graph - brace = Brace(new_time_graph, UP, buff = SMALL_BUFF) - text = TextMobject("Long duration observation") - text.scale(self.text_scale_val) - text.next_to(brace, UP, buff = SMALL_BUFF) - - self.play( - FadeIn(label), - GrowArrow(arrow), - *list(map(self.get_flashes, self.cars)) - ) - self.play( - GrowFromCenter(brace), - Write(text, run_time = 1), - *list(map(self.get_flashes, self.cars)) - ) - self.play(*[ - self.get_flashes(car, num_flashes = 10) - for car in self.cars - ]) - - ### - - def get_flashes(self, car, colors = [YELLOW, RED], num_flashes = 1, **kwargs): - return AnimationGroup(*[ - MultipleFlashes(light, color, num_flashes = num_flashes, **kwargs) - for light, color in zip(car.get_lights(), colors) - ]) - - def get_multispike_function(self, spike_times): - return lambda x : sum([ - 1.25*np.exp(-100*(x-m)**2) - for m in spike_times - ]) - -class VariousMusicalNotes(Scene): - def construct(self): - freq = 20 - # x-coordinate of this point represents log(a) - # where the bell curve component of the signal - # is exp(-a*(x**2)) - graph_width_tracker = ExponentialValueTracker(1) - def get_graph(): - a = graph_width_tracker.get_value() - return FunctionGraph( - lambda x : np.exp(-a*x**2)*np.sin(freq*x)-0.5, - step_size = 0.001, - ) - graph = get_graph() - def graph_update(graph): - graph.points = get_graph().points - graph_update_anim = UpdateFromFunc(graph, graph_update) - def change_width_anim(width, **kwargs): - a = 2.0/(width**2) - return AnimationGroup( - ApplyMethod(graph_width_tracker.set_value, a), - graph_update_anim, - **kwargs - ) - change_width_anim(FRAME_X_RADIUS).update(1) - graph_update_anim.update(0) - - phrases = [ - TextMobject(*words.split(" ")) - for words in [ - "Very clear frequency", - "Less clear frequency", - "Extremely unclear frequency", - ] - ] - - - #Show graphs and phrases - widths = [FRAME_X_RADIUS, 1, 0.2] - for width, phrase in zip(widths, phrases): - brace = Brace(Line(LEFT, RIGHT), UP) - brace.stretch(width, 0) - brace.next_to(graph.get_center(), UP, buff = 1.2) - phrase.next_to(brace, UP) - - if width is widths[0]: - self.play(ShowCreation(graph, rate_func=linear)), - self.play( - GrowFromCenter(brace), - Write(phrase, run_time = 1) - ) - else: - self.play( - change_width_anim(width), - ReplacementTransform( - VGroup(last_phrase, last_brace), - VGroup(phrase, brace), - rate_func = squish_rate_func(smooth, 0.5, 1), - ), - run_time = 2 - ) - self.wait() - # self.play(*map(FadeOut, [graph, brace, phrase])) - last_phrase = phrase - last_brace = brace - - #Talk about correlations - short_signal_words = TextMobject( - "Short", "signal", "correlates", - "with", "wide range", "of frequencies" - ) - long_signal_words = TextMobject( - "Only", "wide", "signals", "correlate", - "with a", "short range", "of frequencies" - ) - phrases = VGroup(short_signal_words, long_signal_words) - for phrase in phrases: - phrase.scale(0.8) - phrase.set_color_by_tex_to_color_map({ - "short" : RED, - "long" : GREEN, - "wide" : GREEN, - }, case_sensitive = False) - phrases.arrange(DOWN) - phrases.to_edge(UP) - - long_graph = FunctionGraph( - lambda x : 0.5*np.sin(freq*x), - x_min = -FRAME_WIDTH, - x_max = FRAME_WIDTH, - n_components = 0.001 - ) - long_graph.set_color(BLUE) - long_graph.next_to(graph, UP, MED_LARGE_BUFF) - - self.play( - ShowCreation(long_graph), - *list(map(FadeOut, [last_brace, last_phrase])) - ) - self.play( - Write(short_signal_words), - change_width_anim(widths[2]) - ) - self.play( - long_graph.stretch, 0.35, 0, - long_graph.set_color, GREEN, - run_time = 5, - rate_func = wiggle - ) - self.wait() - self.play( - Write(long_signal_words), - change_width_anim(widths[0]), - ) - self.play( - long_graph.stretch, 0.95, 0, - long_graph.set_color, average_color(GREEN, BLUE), - run_time = 4, - rate_func = wiggle - ) - self.wait() - -class CrossOutDefinitenessAndCertainty(TeacherStudentsScene): - def construct(self): - words = VGroup( - TextMobject("Definiteness"), - TextMobject("Certainty"), - ) - words.arrange(DOWN) - words.next_to(self.teacher, UP+LEFT) - crosses = VGroup(*list(map(Cross, words))) - - self.add(words) - self.play( - self.teacher.change, "sassy", - ShowCreation(crosses[0]) - ) - self.play( - self.get_student_changes(*3*["erm"]), - ShowCreation(crosses[1]) - ) - self.wait(2) - -class BringInFourierTranform(TeacherStudentsScene): - def construct(self): - fourier = TextMobject("Fourier") - fourier.scale(1.5) - fourier.next_to(self.teacher.get_corner(UP+LEFT), UP, LARGE_BUFF) - fourier.save_state() - fourier.shift(DOWN) - fourier.fade(1) - - self.play( - self.teacher.change, "raise_right_hand", - fourier.restore - ) - self.change_student_modes("happy", "erm", "confused") - self.look_at(3*LEFT + 2*UP) - self.wait(3) - -class LastVideoWrapper(Scene): - def construct(self): - title = TextMobject("Visualizing the Fourier Transform") - title.to_edge(UP) - screen_rect = ScreenRectangle(height = 6) - screen_rect.next_to(title, DOWN) - - self.add(title) - self.play(ShowCreation(screen_rect)) - self.wait() - -class FourierRecapScene(DrawFrequencyPlot): - CONFIG = { - "frequency_axes_config" : { - "x_max" : 10.0, - "x_axis_config" : { - "unit_size" : 0.7, - "numbers_to_show" : list(range(1, 10, 1)), - } - }, - "initial_winding_frequency" : 0.1, - } - def construct(self): - self.setup_axes() - self.preview_fourier_plot() - self.wrap_signal_around_circle() - self.match_winding_to_beat_frequency() - self.follow_center_of_mass() - self.draw_fourier_plot() - self.set_color_spike() - - def setup_axes(self): - self.remove(self.pi_creature) - time_axes = self.get_time_axes() - time_axes.to_edge(UP, buff = MED_SMALL_BUFF) - time_axes.scale(0.9, about_edge = UP) - frequency_axes = self.get_frequency_axes() - circle_plane = self.get_circle_plane() - - self.add(time_axes) - - self.set_variables_as_attrs( - time_axes, frequency_axes, - circle_plane - ) - - def preview_fourier_plot(self): - time_graph = self.graph = self.get_time_graph( - width = 2, - num_graph_points = 200, - ) - fourier_graph = self.get_fourier_transform_graph( - time_graph - ) - fourier_graph.pointwise_become_partial(fourier_graph, 0.1, 1) - - #labels - signal_label = TextMobject("Signal") - fourier_label = TextMobject("Fourier transform") - signal_label.next_to(time_graph, UP, buff = SMALL_BUFF) - fourier_label.next_to(fourier_graph, UP) - fourier_label.match_color(fourier_graph) - - self.play( - ShowCreation(time_graph, run_time = 2), - Write(signal_label), - ) - self.wait() - self.play( - LaggedStartMap(FadeIn, self.frequency_axes), - ReplacementTransform( - time_graph.copy(), - fourier_graph, - run_time = 2 - ), - ReplacementTransform( - signal_label.copy(), - fourier_label, - run_time = 2, - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - ) - self.wait() - self.play(LaggedStartMap( - Indicate, self.frequency_axes.x_axis.numbers, - run_time = 4, - rate_func = wiggle, - )) - self.wait() - self.play(*list(map(FadeOut, [ - self.frequency_axes, fourier_graph, - signal_label, fourier_label, - ]))) - - self.time_graph = time_graph - self.set_variables_as_attrs(time_graph, fourier_label) - - def wrap_signal_around_circle(self): - time_graph = self.time_graph - circle_plane = self.circle_plane - freq = self.initial_winding_frequency - pol_graph = self.get_polarized_mobject(time_graph, freq) - winding_freq_label = self.get_winding_frequency_label() - winding_freq_label.add_to_back(BackgroundRectangle(winding_freq_label)) - winding_freq_label.move_to(circle_plane.get_top(), DOWN) - - self.add_foreground_mobjects(winding_freq_label) - self.play( - Write(circle_plane, run_time = 1), - ReplacementTransform( - time_graph.copy(), pol_graph, - path_arc = -TAU/4, - run_time_per_flash = 2, - run_time = 2, - ), - FadeIn(winding_freq_label), - ) - freq = 0.3 - self.change_frequency(freq, run_time = 2) - ghost_pol_graph = pol_graph.copy() - self.remove(pol_graph) - self.play(ghost_pol_graph.set_stroke, {"width" : 0.5}) - self.play( - *self.get_vector_animations(time_graph), - run_time = 15 - ) - self.remove(ghost_pol_graph) - self.wait() - - def match_winding_to_beat_frequency(self): - self.v_lines_indicating_periods = self.get_v_lines_indicating_periods(0.3) - self.add(self.v_lines_indicating_periods) - for freq in range(1, 6): - self.change_frequency(freq, run_time = 5) - self.play( - *self.get_vector_animations( - self.time_graph, - draw_polarized_graph = False - ), - run_time = 10 - ) - self.wait() - - def follow_center_of_mass(self): - com_dot = self.get_center_of_mass_dot() - self.generate_center_of_mass_dot_update_anim() - com_arrow = Arrow(UP+3*RIGHT, ORIGIN) - com_arrow.shift(com_dot.get_center()) - com_arrow.match_color(com_dot) - com_words = TextMobject("Center of mass") - com_words.next_to(com_arrow.get_start(), UP) - com_words.match_color(com_arrow) - com_words.add_background_rectangle() - - com_dot.save_state() - com_dot.move_to(com_arrow.get_start()) - com_dot.fade(1) - - self.play( - com_dot.restore, - GrowArrow(com_arrow, rate_func = squish_rate_func(smooth, 0.2, 1)), - Write(com_words), - ) - self.wait() - squished_func = squish_rate_func(smooth, 0, 0.2) - self.change_frequency( - 4, - added_anims = [ - FadeOut(com_arrow, rate_func = squished_func), - FadeOut(com_words, rate_func = squished_func), - ], - run_time = 5 - ) - - def draw_fourier_plot(self): - frequency_axes = self.frequency_axes - fourier_label = self.fourier_label - - self.change_frequency(0, run_time = 2) - self.play( - FadeIn(frequency_axes), - FadeIn(fourier_label), - ) - - fourier_graph = self.get_fourier_transform_graph(self.time_graph) - self.get_fourier_graph_drawing_update_anim(fourier_graph) - self.generate_fourier_dot_transform(fourier_graph) - - self.change_frequency(5, run_time = 20) - self.wait() - self.change_frequency(7.5, run_time = 10) - self.fourier_graph_drawing_update_anim = Animation(Mobject()) - self.fourier_graph = fourier_graph - - def set_color_spike(self): - spike_point = self.frequency_axes.input_to_graph_point( - 5, self.fourier_graph - ) - circle = Circle(color = YELLOW, radius = 0.25) - circle.move_to(spike_point) - circle.save_state() - circle.scale(5) - circle.fade(1) - - self.change_frequency(5) - self.play(circle.restore) - self.play(FadeOut(circle)) - self.wait() - for x in range(2): - self.change_frequency(5.2, run_time = 3) - self.change_frequency(4.8, run_time = 3) - self.change_frequency(5, run_time = 1.5) - self.wait() - - - ######### - - def get_time_graph(self, frequency = 5, width = 2, **kwargs): - # low_x = center-width/2 - # high_x = center+width/2 - # new_smooth = lambda x : np.clip(smooth((x+0.5)), 0, 1) - # def func(x): - # pure_signal = 0.9*np.cos(TAU*frequency*x) - # factor = new_smooth(x - low_x) - new_smooth(x-high_x) - # return 1 + factor*pure_signal - graph = self.time_axes.get_graph( - lambda x : 1+0.9*np.cos(TAU*frequency*x), - x_min = 0, x_max = width, - **kwargs - ) - graph.set_color(YELLOW) - return graph - -class RealPartOfInsert(Scene): - def construct(self): - words = TextMobject("(Real part of the)") - words.set_color(RED) - self.add(words) - self.play(Write(words)) - self.wait(5) - -class CenterOfMassDescription(FourierRecapScene): - def construct(self): - self.remove(self.pi_creature) - circle_plane = self.get_circle_plane() - circle_plane.save_state() - circle_plane.generate_target() - circle_plane.target.set_height(FRAME_HEIGHT) - circle_plane.target.center() - circle_plane.target.axes.set_stroke(width = 2) - circle_plane.targets.set_stroke(width = 2) - circle_plane.target.secondary_lines.set_stroke(width = 1) - - start_coords = (0.5, 0.5) - alt_coords = (0.8, 0.8) - - com_dot = Dot(color = self.center_of_mass_color) - com_dot.move_to(circle_plane.coords_to_point(*start_coords)) - - self.add(circle_plane, com_dot) - self.wait() - self.play( - MoveToTarget(circle_plane), - com_dot.move_to, - circle_plane.target.coords_to_point(*start_coords) - ) - self.wait() - - alt_com_dot = com_dot.copy().move_to( - circle_plane.coords_to_point(*alt_coords) - ) - - for dot in com_dot, alt_com_dot: - line = Line(ORIGIN, dot.get_center()) - line.match_color(com_dot) - angle = line.get_angle() - line.rotate(-angle, about_point = ORIGIN) - brace = Brace(line, UP) - words = brace.get_text("Strength of frequency") - words.add_background_rectangle() - dot.length_label_group = VGroup(line, brace, words) - dot.length_label_group.rotate(angle, about_point = ORIGIN) - - line, brace, words = com_dot.length_label_group - self.play( - GrowFromCenter(line), - GrowFromCenter(brace), - FadeIn(words), - ) - self.wait() - self.play( - Transform( - com_dot.length_label_group, - alt_com_dot.length_label_group, - ), - Transform(com_dot, alt_com_dot), - rate_func = there_and_back, - run_time = 4, - ) - - #Do rotation - line = com_dot.length_label_group[0] - com_dot.length_label_group.remove(line) - angle = line.get_angle() - arc, alt_arc = [ - Arc( - start_angle = 0, - angle = factor*angle, - radius = 0.5, - ) - for factor in (1, 2) - ] - theta = TexMobject("\\theta") - theta.shift(1.5*arc.point_from_proportion(0.5)) - - self.play( - FadeOut(com_dot.length_label_group), - Animation(line), - ShowCreation(arc), - Write(theta) - ) - self.play( - Rotate( - VGroup(line, com_dot), - angle, about_point = ORIGIN - ), - Transform(arc, alt_arc), - theta.move_to, 1.5*alt_arc.point_from_proportion(0.5), - rate_func = there_and_back, - run_time = 4 - ) - self.wait() - -class AskAboutLongVsShort(TeacherStudentsScene): - def construct(self): - self.student_says( - "What happens if we \\\\ change the length of \\\\ the signal?", - student_index = 2, - ) - self.play( - self.teacher.change, "happy", - self.get_student_changes("pondering", "confused", "raise_right_hand") - ) - self.wait(5) - -class LongAndShortSignalsInWindingMachine(FourierRecapScene): - CONFIG = { - "num_fourier_graph_points" : 1000, - } - def construct(self): - self.setup_axes() - self.extend_for_long_time() - self.note_sharp_fourier_peak() - self.very_short_signal() - self.note_wide_fourier_peak() - - def setup_axes(self): - FourierRecapScene.setup_axes(self) - self.add(self.circle_plane) - self.add(self.frequency_axes) - self.time_graph = self.graph = self.get_time_graph(width = 2) - self.add(self.time_graph) - - self.force_skipping() - self.wrap_signal_around_circle() - - fourier_graph = self.get_fourier_transform_graph(self.time_graph) - self.fourier_graph = fourier_graph - self.add(fourier_graph) - self.change_frequency(5) - - self.revert_to_original_skipping_status() - - def extend_for_long_time(self): - short_time_graph = self.time_graph - long_time_graph = self.get_time_graph( - width = 10, - num_graph_points = 500, - ) - long_time_graph.set_stroke(width = 2) - new_freq = 5.1 - long_pol_graph = self.get_polarized_mobject( - long_time_graph, - freq = new_freq - ) - fourier_graph = self.fourier_graph - - self.change_frequency(new_freq) - self.play( - FadeOut(self.graph), - FadeOut(self.graph.polarized_mobject), - FadeOut(fourier_graph) - ) - self.play( - ShowCreation(long_time_graph, rate_func=linear), - ShowCreation(long_pol_graph, rate_func=linear), - run_time = 5 - ) - self.wait() - - self.time_graph = self.graph = long_time_graph - - def note_sharp_fourier_peak(self): - fourier_graph = self.get_fourier_transform_graph( - self.time_graph, - num_graph_points = self.num_fourier_graph_points - ) - self.fourier_graph = fourier_graph - self.note_fourier_peak(fourier_graph, 5, 5.1) - - def very_short_signal(self): - time_graph = self.time_graph - fourier_graph = self.fourier_graph - short_time_graph = self.get_time_graph(width = 0.6) - new_freq = 5.1 - short_pol_graph = self.get_polarized_mobject( - short_time_graph, - freq = new_freq - ) - - self.play( - FadeOut(fourier_graph), - FadeOut(time_graph), - FadeOut(time_graph.polarized_mobject), - ) - self.play( - ShowCreation(short_time_graph), - ShowCreation(short_time_graph.polarized_mobject), - ) - self.graph = self.time_graph = short_time_graph - self.change_frequency(6.66, run_time = 5) - - def note_wide_fourier_peak(self): - fourier_graph = self.get_fourier_transform_graph( - self.graph, - num_graph_points = self.num_fourier_graph_points - ) - self.fourier_graph = fourier_graph - self.note_fourier_peak(fourier_graph, 5, 6.66) - - - ### - - def note_fourier_peak(self, fourier_graph, freq1, freq2): - fourier_graph = self.fourier_graph - dots = self.get_fourier_graph_dots(fourier_graph, freq1, freq2) - self.get_center_of_mass_dot() - self.generate_center_of_mass_dot_update_anim() - self.generate_fourier_dot_transform(fourier_graph) - dot = self.fourier_graph_dot - arrow = Arrow(UP, ORIGIN, buff = SMALL_BUFF) - arrow.next_to(dot, UP, buff = SMALL_BUFF) - - self.play(ShowCreation(fourier_graph)) - self.change_frequency(freq1, - added_anims = [ - MaintainPositionRelativeTo(arrow, dot), - UpdateFromAlphaFunc( - arrow, - lambda m, a : m.set_fill(opacity = a) - ), - ], - run_time = 3, - ) - self.wait() - self.change_frequency(freq2, - added_anims = [ - MaintainPositionRelativeTo(arrow, dot) - ], - run_time = 3 - ) - self.wait() - self.play(*list(map(FadeOut, [ - dot, arrow, self.center_of_mass_dot - ]))) - #This is not great... - for attr in "center_of_mass_dot", "fourier_graph_dot": - self.__dict__.pop(attr) - - - def get_fourier_graph_dots(self, fourier_graph, *freqs): - axis_point = self.frequency_axes.coords_to_point(4.5, -0.25) - dots = VGroup() - for freq in freqs: - point = self.frequency_axes.input_to_graph_point(freq, fourier_graph) - dot = Dot(point) - dot.scale(0.5) - dots.add(dot) - vect = point - axis_point - vect *= 1.3/get_norm(vect) - arrow = Arrow(vect, ORIGIN, buff = SMALL_BUFF) - arrow.set_color(YELLOW) - arrow.shift(point) - dot.arrow = arrow - return dots - -class FocusRectangleInsert(FourierRecapScene): - CONFIG = { - "target_width" : 0.5 - } - def construct(self): - self.setup_axes() - self.clear() - point = self.frequency_axes.coords_to_point(5, 0.25) - rect = ScreenRectangle(height = 2.1*FRAME_Y_RADIUS) - rect.set_stroke(YELLOW, 2) - self.add(rect) - self.wait() - self.play( - rect.stretch_to_fit_width, self.target_width, - rect.stretch_to_fit_height, 1.5, - rect.move_to, point, - run_time = 2 - ) - self.wait(3) - -class BroadPeakFocusRectangleInsert(FocusRectangleInsert): - CONFIG = { - "target_width" : 1.5, - } - -class CleanerFourierTradeoff(FourierTradeoff): - CONFIG = { - "show_text" : False, - "complex_to_real_func" : lambda z : z.real, - "widths" : [0.02, 6, 1], - } - -class MentionDopplerRadar(TeacherStudentsScene): - def construct(self): - words = TextMobject("Doppler Radar") - words.next_to(self.teacher, UP) - words.save_state() - words.shift(DOWN).fade(1) - dish = RadarDish() - dish.next_to(self.students, UP, buff = 2, aligned_edge = LEFT) - plane = Plane() - plane.to_edge(RIGHT) - plane.align_to(dish) - always_shift(plane, LEFT, 1) - plane.flip() - pulse = RadarPulse(dish, plane) - look_at_anims = [ - Mobject.add_updater( - pi, lambda pi : pi.look_at(pulse.mobject) - ) - for pi in self.get_pi_creatures() - ] - - self.add(dish, plane, pulse, *look_at_anims) - self.play( - self.teacher.change, "hooray", - words.restore - ) - self.change_student_modes("pondering", "erm", "sassy") - self.wait(2) - self.play( - self.teacher.change, "happy", - self.get_student_changes(*["thinking"]*3) - ) - self.wait() - dish.set_stroke(width = 0) - self.play(UpdateFromAlphaFunc( - VGroup(plane, dish), - lambda m, a : m.set_fill(opacity = 1 - a) - )) - -class IntroduceDopplerRadar(Scene): - CONFIG = { - "frequency_spread_factor" : 100, - } - def construct(self): - self.setup_axes() - self.measure_distance_with_time() - self.show_frequency_shift() - self.show_frequency_shift_in_fourier() - - def setup_axes(self): - self.dish = RadarDish() - self.dish.to_corner(UP+LEFT) - axes = Axes( - x_min = 0, - x_max = 10, - y_min = -1.5, - y_max = 1.5 - ) - axes.move_to(DOWN) - time_label = TextMobject("Time") - time_label.next_to(axes.x_axis.get_right(), UP) - axes.time_label = time_label - axes.add(time_label) - self.axes = axes - - self.add(self.dish) - self.add(axes) - - def measure_distance_with_time(self): - dish = self.dish - axes = self.axes - distance = 5 - time_diff = 5 - speed = (2*distance)/time_diff - randy = Randolph().flip() - randy.match_height(dish) - randy.move_to(dish.get_right(), LEFT) - randy.shift(distance*RIGHT) - - pulse_graph, echo_graph, sum_graph = \ - self.get_pulse_and_echo_graphs( - self.get_single_pulse_graph, - (1,), (1+time_diff,) - ) - words = ["Original signal", "Echo"] - for graph, word in zip([pulse_graph, echo_graph], words): - arrow = Vector(DOWN) - arrow.next_to(graph.peak_point, UP, SMALL_BUFF) - arrow.match_color(graph) - graph.arrow = arrow - label = TextMobject(word) - label.next_to(arrow.get_start(), UP, SMALL_BUFF) - label.match_color(graph) - graph.label = label - - double_arrow = DoubleArrow( - pulse_graph.peak_point, - echo_graph.peak_point, - color = WHITE - ) - distance_text = TextMobject("$2 \\times$ distance/(signal speed)") - distance_text.set_width(0.9*double_arrow.get_width()) - distance_text.next_to(double_arrow, UP, SMALL_BUFF) - - #v_line anim? - - pulse = RadarPulseSingleton( - dish, randy, - speed = 0.97*speed, #Just needs slightly better alignment - ) - graph_draw = turn_animation_into_updater( - ShowCreation( - sum_graph, - rate_func=linear, - run_time = 0.97*axes.x_max - ) - ) - randy_look_at = Mobject.add_updater( - randy, lambda pi : pi.look_at(pulse.mobject) - ) - axes_anim = ContinualAnimation(axes) - - self.add(randy_look_at, axes_anim, graph_draw) - self.wait(0.5) - self.add(pulse) - self.play( - Write(pulse_graph.label), - GrowArrow(pulse_graph.arrow), - run_time = 1, - ) - self.play(randy.change, "pondering") - self.wait(time_diff - 2) - self.play( - Write(echo_graph.label), - GrowArrow(echo_graph.arrow), - run_time = 1 - ) - self.wait() - self.play( - GrowFromCenter(double_arrow), - FadeIn(distance_text) - ) - self.wait() - - self.remove(graph_draw, pulse, randy_look_at, axes_anim) - self.add(axes) - self.play(LaggedStartMap(FadeOut, VGroup( - sum_graph, randy, - pulse_graph.arrow, pulse_graph.label, - echo_graph.arrow, echo_graph.label, - double_arrow, distance_text - ))) - - def show_frequency_shift(self): - axes = self.axes - dish = self.dish - plane = Plane() - plane.flip() - plane.move_to(dish) - plane.to_edge(RIGHT) - - time_diff = 6 - - pulse_graph, echo_graph, sum_graph = graphs = \ - self.get_pulse_and_echo_graphs( - self.get_frequency_pulse_graph, - (1,25), (1+time_diff,50) - ) - for graph in graphs: - graph.set_stroke(width = 3) - signal_graph = self.get_frequency_pulse_graph(1) - - pulse_brace = Brace(Line(ORIGIN, RIGHT), UP) - pulse_brace.move_to(axes.coords_to_point(1, 1.2)) - echo_brace = pulse_brace.copy() - echo_brace.stretch(0.6, 0) - echo_brace.move_to(axes.coords_to_point(7, 1.2)) - pulse_text = pulse_brace.get_text("Original signal") - pulse_text.add_background_rectangle() - echo_text = echo_brace.get_text("Echo") - echo_subtext = TextMobject("(Higher frequency)") - echo_subtext.next_to(echo_text, RIGHT) - echo_subtext.match_color(echo_graph) - - graph_draw = turn_animation_into_updater( - ShowCreation(sum_graph, run_time = 8, rate_func=linear) - ) - pulse = RadarPulse(dish, plane, n_pulse_singletons = 12) - always_shift(plane, LEFT, 1.5) - - self.add(graph_draw, pulse, plane) - self.play(UpdateFromAlphaFunc( - plane, lambda m, a : m.set_fill(opacity = a) - )) - self.play( - GrowFromCenter(pulse_brace), - FadeIn(pulse_text), - ) - self.wait(3) - self.play( - GrowFromCenter(echo_brace), - GrowFromCenter(echo_text), - ) - self.play(UpdateFromAlphaFunc( - plane, lambda m, a : m.set_fill(opacity = 1-a) - )) - #Only for when -s is run - graph_draw.update(10) - self.wait(0.1) - self.play(Write(echo_subtext, run_time = 1)) - self.wait() - self.remove(graph_draw, pulse, plane) - - pulse_graph.set_stroke(width = 0) - echo_graph.set_stroke(width = 0) - self.time_graph_group = VGroup( - axes, pulse_brace, pulse_text, - echo_brace, echo_text, echo_subtext, - pulse_graph, echo_graph, sum_graph, - ) - self.set_variables_as_attrs(*self.time_graph_group) - - def show_frequency_shift_in_fourier(self): - sum_graph = self.sum_graph - pulse_graph = self.pulse_graph - pulse_label = VGroup(self.pulse_brace, self.pulse_text) - echo_graph = self.echo_graph - echo_label = VGroup( - self.echo_brace, self.echo_text, self.echo_subtext - ) - - #Setup all fourier graph stuff - f_max = 0.02 - frequency_axes = Axes( - x_min = 0, x_max = 20, - x_axis_config = {"unit_size" : 0.5}, - y_min = -f_max, y_max = f_max, - y_axis_config = { - "unit_size" : 50, - "tick_frequency" : 0.01, - }, - ) - frequency_axes.move_to(self.axes, LEFT) - frequency_axes.to_edge(DOWN) - frequency_label = TextMobject("Frequency") - frequency_label.next_to( - frequency_axes.x_axis.get_right(), UP, - ) - frequency_label.to_edge(RIGHT) - frequency_axes.add(frequency_label) - - for graph in pulse_graph, echo_graph, sum_graph: - graph.fourier_transform = get_fourier_graph( - frequency_axes, graph.underlying_function, - frequency_axes.x_min, 25, - complex_to_real_func = abs, - ) - - #Braces labeling F.T. - original_fourier_brace = Brace( - Line( - frequency_axes.coords_to_point(7, 0.9*f_max), - frequency_axes.coords_to_point(9, 0.9*f_max), - ), - UP, - ).set_color(BLUE) - echo_fourier_brace = Brace( - Line( - frequency_axes.coords_to_point(14, 0.4*f_max), - frequency_axes.coords_to_point(18, 0.4*f_max), - ), - UP, - ).set_color(YELLOW) - # braces = [original_fourier_brace, echo_fourier_brace] - # words = ["original signal", "echo"] - # for brace, word in zip(braces, words): - # brace.add(brace.get_text("F.T. of \\\\ %s"%word)) - fourier_label = TexMobject("||\\text{Fourier transform}||") - # fourier_label.next_to(sum_graph.fourier_transform, UP, MED_LARGE_BUFF) - fourier_label.next_to(frequency_axes.y_axis, UP, buff = SMALL_BUFF) - fourier_label.shift_onto_screen() - fourier_label.set_color(RED) - - - #v_lines - v_line = DashedLine( - frequency_axes.coords_to_point(8, 0), - frequency_axes.coords_to_point(8, 1.2*f_max), - color = YELLOW, - dash_length = 0.025, - ) - v_line_pair = VGroup(*[ - v_line.copy().shift(u*0.6*RIGHT) - for u in (-1, 1) - ]) - v_line = VGroup(v_line) - - double_arrow = DoubleArrow( - frequency_axes.coords_to_point(8, 0.007), - frequency_axes.coords_to_point(16, 0.007), - buff = 0, - color = WHITE - ) - - self.play( - self.time_graph_group.to_edge, UP, - ApplyMethod( - self.dish.shift, 2*UP, - remover = True - ), - FadeIn(frequency_axes) - ) - self.wait() - self.play( - FadeOut(sum_graph), - FadeOut(echo_label), - pulse_graph.set_stroke, {"width" : 3}, - ) - self.play( - ReplacementTransform( - pulse_label[0].copy(), - original_fourier_brace - ), - ShowCreation(pulse_graph.fourier_transform) - ) - self.play(Write(fourier_label)) - self.wait() - self.play(ShowCreation(v_line)) - self.wait() - self.play(ReplacementTransform(v_line, v_line_pair)) - self.wait() - self.play(FadeOut(v_line_pair)) - self.wait() - - self.play( - FadeOut(pulse_graph), - FadeIn(sum_graph), - ReplacementTransform( - pulse_graph.fourier_transform, - sum_graph.fourier_transform - ) - ) - self.play(FadeIn(echo_label)) - self.play(ReplacementTransform( - echo_label[0].copy(), - echo_fourier_brace, - )) - self.wait(2) - self.play(GrowFromCenter(double_arrow)) - self.wait() - - - ### - - def get_graph(self, func, **kwargs): - graph = self.axes.get_graph(func, **kwargs) - graph.peak_point = self.get_peak_point(graph) - return graph - - def get_single_pulse_graph(self, x, **kwargs): - return self.get_graph(self.get_single_pulse_function(x), **kwargs) - - def get_single_pulse_function(self, x): - return lambda t : -2*np.sin(10*(t-x))*np.exp(-100*(t-x)**2) - - def get_frequency_pulse_graph(self, x, freq = 50, **kwargs): - return self.get_graph( - self.get_frequency_pulse_function(x, freq), - num_graph_points = 700, - **kwargs - ) - - def get_frequency_pulse_function(self, x, freq): - factor = self.frequency_spread_factor - return lambda t : op.mul( - 2*np.cos(2*freq*(t-x)), - min(np.exp(-(freq**2/factor)*(t-x)**2), 0.5) - ) - - def get_peak_point(self, graph): - anchors = graph.get_anchors() - return anchors[np.argmax([p[1] for p in anchors])] - - def get_pulse_and_echo_graphs(self, func, args1, args2): - pulse_graph = func(*args1, color = BLUE) - echo_graph = func(*args2, color = YELLOW) - sum_graph = self.axes.get_graph( - lambda x : sum([ - pulse_graph.underlying_function(x), - echo_graph.underlying_function(x), - ]), - num_graph_points = echo_graph.get_num_curves(), - color = WHITE - ) - sum_graph.background_image_file = "blue_yellow_gradient" - return pulse_graph, echo_graph, sum_graph - -class DopplerFormulaInsert(Scene): - def construct(self): - formula = TexMobject( - "f_{\\text{echo}", "=", - "\\left(1 + \\frac{v}{c}\\right)", - "f_{\\text{pulse}}" - ) - formula[0].set_color(BLUE) - formula[3].set_color(YELLOW) - - randy = Randolph(color = BLUE_C) - formula.scale(1.5) - formula.next_to(randy, UP+LEFT) - formula.shift_onto_screen() - - self.add(randy) - self.play( - LaggedStartMap(FadeIn, formula), - randy.change, "pondering", randy.get_bottom(), - ) - self.play(Blink(randy)) - self.wait(2) - self.play(Blink(randy)) - self.wait() - -class MentionPRFNuance(TeacherStudentsScene): - def construct(self): - title = TextMobject( - "Speed of light", "$\\gg$", "Speed of a plane" - ) - title.to_edge(UP) - self.add(title) - - axes = self.axes = Axes( - x_min = 0, x_max = 10, - y_min = 0, y_max = 2, - ) - axes.next_to(title, DOWN, buff = MED_LARGE_BUFF) - frequency_label = TextMobject("Frequency") - frequency_label.scale(0.7) - frequency_label.next_to(axes.x_axis.get_right(), UP) - axes.add(frequency_label) - self.add(axes) - - pulse_x, shift_x = 4, 6 - pulse_graph = self.get_spike_graph(pulse_x) - shift_graph = self.get_spike_graph(shift_x) - shift_graph.set_stroke(YELLOW, 2) - peak_points = VGroup(pulse_graph.peak_point, shift_graph.peak_point) - self.add(pulse_graph) - - brace = Brace(peak_points, UP, buff = SMALL_BUFF) - displayed_doppler_shift = TextMobject("How I'm showing the \\\\", "Doppler shift") - actual_doppler_shift = TextMobject("Actual\\\\", "Doppler shift") - doppler_shift_words = VGroup(displayed_doppler_shift, actual_doppler_shift) - doppler_shift_words.set_color(YELLOW) - doppler_shift_words.scale(0.75) - displayed_doppler_shift.next_to(brace, UP, buff = SMALL_BUFF) - actual_doppler_shift.move_to(pulse_graph.peak_point) - actual_doppler_shift.align_to(displayed_doppler_shift) - - self.play( - Animation(pulse_graph), - self.teacher.change, "raise_right_hand", - run_time = 1 - ) - self.play( - ShowCreation(shift_graph), - FadeIn(brace), - Write(displayed_doppler_shift, run_time = 1), - self.get_student_changes(*3*["sassy"]), - ) - self.play( - UpdateFromAlphaFunc( - shift_graph, - lambda g, a : Transform( - g, self.get_spike_graph( - interpolate(shift_x, pulse_x+0.01, a), - ).match_style(shift_graph) - ).update(1), - ), - UpdateFromFunc( - brace, - lambda b : b.match_width( - peak_points, stretch = True - ).next_to(peak_points, UP, SMALL_BUFF) - ), - Transform( - displayed_doppler_shift, actual_doppler_shift, - rate_func = squish_rate_func(smooth, 0.3, 0.6) - ), - run_time = 3 - ) - self.wait(2) - - everything = VGroup( - title, - axes, pulse_graph, shift_graph, - brace, displayed_doppler_shift - ) - rect = SurroundingRectangle(everything, color = WHITE) - everything.add(rect) - - self.teacher_says( - "I'll ignore certain \\\\ nuances for now.", - target_mode = "shruggie", - added_anims = [ - everything.scale, 0.4, - everything.to_corner, UP+LEFT, - UpdateFromAlphaFunc( - rect, lambda m, a : m.set_stroke(width = 2*a) - ) - ], - ) - self.change_student_modes(*3*["hesitant"]) - self.wait(2) - - - - - def get_spike_graph(self, x, color = RED, **kwargs): - graph = self.axes.get_graph( - lambda t : np.exp(-10*(t-x)**2)*np.cos(10*(t-x)), - color = color, - **kwargs - ) - graph.peak_point = VectorizedPoint(self.axes.input_to_graph_point(x, graph)) - graph.add(graph.peak_point) - return graph - -class TimeAndFrequencyGivePositionAndVelocity(IntroduceDopplerRadar): - def construct(self): - x = 7 - freq = 25 - - axes = self.axes = Axes( - x_min = 0, x_max = 10, - y_min = -2, y_max = 2, - ) - axes.center() - title = TextMobject("Echo signal") - title.next_to(axes.y_axis, UP) - axes.add(title) - axes.to_edge(UP) - graph = self.get_frequency_pulse_graph(x = x, freq = freq) - graph.background_image_file = "blue_yellow_gradient" - - arrow = Arrow( - axes.coords_to_point(0, -1.5), - axes.coords_to_point(x, -1.5), - color = WHITE, - buff = SMALL_BUFF, - ) - time = TextMobject("Time") - time.next_to(arrow, DOWN, SMALL_BUFF) - - delta_x = 0.7 - brace = Brace( - Line( - axes.coords_to_point(x-delta_x, 1), - axes.coords_to_point(x+delta_x, 1) - ), - UP - ) - frequency = TextMobject("Frequency") - frequency.set_color(YELLOW) - frequency.next_to(brace, UP, SMALL_BUFF) - - time_updown_arrow = TexMobject("\\Updownarrow") - time_updown_arrow.next_to(time, DOWN, SMALL_BUFF) - freq_updown_arrow = time_updown_arrow.copy() - freq_updown_arrow.next_to(frequency, UP, SMALL_BUFF) - distance = TextMobject("Distance") - distance.next_to(time_updown_arrow, DOWN, SMALL_BUFF) - velocity = TextMobject("Velocity") - velocity.next_to(freq_updown_arrow, UP, SMALL_BUFF) - VGroup(freq_updown_arrow, velocity).match_style(frequency) - - self.add(axes) - self.play(ShowCreation(graph)) - self.play( - GrowArrow(arrow), - LaggedStartMap(FadeIn, time, run_time = 1) - ) - self.play( - GrowFromCenter(brace), - LaggedStartMap(FadeIn, frequency, run_time = 1) - ) - self.wait() - self.play( - GrowFromPoint(time_updown_arrow, time_updown_arrow.get_top()), - ReplacementTransform( - time.copy().fade(1), - distance - ) - ) - self.play( - GrowFromPoint(freq_updown_arrow, freq_updown_arrow.get_top()), - ReplacementTransform( - frequency.copy().fade(1), - velocity - ) - ) - self.wait() - -class RadarOperatorUncertainty(Scene): - def construct(self): - dish = RadarDish() - dish.scale(3) - dish.move_to(4*RIGHT + 2*DOWN) - dish_words = TextMobject("3b1b industrial \\\\ enterprises") - dish_words.scale(0.25) - dish_words.set_stroke(BLACK, 0.5) - dish_words.set_color(BLACK) - dish_words.move_to(dish, DOWN) - dish_words.shift(SMALL_BUFF*(UP+2*LEFT)) - dish.add(dish_words) - randy = Randolph() - randy.next_to(dish, LEFT, aligned_edge = DOWN) - bubble = randy.get_bubble( - width = 7, - height = 4, - ) - - echo_object = Square() - echo_object.move_to(dish) - echo_object.shift(FRAME_X_RADIUS*RIGHT) - pulse = RadarPulse(dish, echo_object, speed = 6) - - plane = Plane().scale(0.5) - plane.move_to(bubble.get_bubble_center()+LEFT) - plane_cloud = ProbabalisticMobjectCloud( - plane, - fill_opacity = 0.3, - n_copies = 10, - ) - plane_gdw = plane_cloud.gaussian_distribution_wrapper - - vector_cloud = ProbabalisticVectorCloud( - center_func = plane_gdw.get_center, - ) - vector_gdw = vector_cloud.gaussian_distribution_wrapper - vector_gdw.scale(0.05) - vector_gdw.move_to(plane_gdw) - vector_gdw.shift(2*RIGHT) - - self.add(randy, dish, bubble, plane_cloud, pulse) - self.play(randy.change, "confused") - self.wait(3) - self.add(vector_cloud) - for i in range(3): - for plane_factor, vector_factor, freq in (0.05, 10, 0.01), (20, 0.1, 0.1): - pulse.internal_time = 0 - pulse.frequency = freq - self.play( - randy.change, "pondering", plane, - plane_gdw.scale, plane_factor, - vector_gdw.scale, vector_factor, - ) - self.wait(2) - -class AmbiguityInLongEchos(IntroduceDopplerRadar, PiCreatureScene): - CONFIG = { - "object_x_coords" : [7, 4, 6, 9, 8], - "frequency_spread_factor" : 200, - "n_pulse_singletons" : 16, - "pulse_frequency" : 0.025, - } - def construct(self): - self.setup_axes() - self.setup_objects() - self.send_long_pulse_single_echo() - self.introduce_multiple_objects() - self.use_short_pulse() - self.fourier_transform_of_one_pulse() - self.show_echos_of_moving_objects() - self.overlapping_frequenies_of_various_objects() - self.echos_of_long_pure_signal_in_frequency_space() - self.concentrated_fourier_requires_long_time() - - def setup_axes(self): - axes = self.axes = Axes( - x_min = 0, x_max = 10, - y_min = -1.5, y_max = 1.5, - ) - time_label = TextMobject("Time") - time_label.next_to(axes.x_axis.get_right(), UP) - axes.add(time_label) - axes.center() - axes.shift(DOWN) - self.add(axes) - - dish = self.dish = RadarDish() - dish.move_to(axes, LEFT) - dish.to_edge(UP, buff = LARGE_BUFF) - self.add(dish) - - def setup_objects(self): - objects = self.objects = VGroup( - Plane().flip(), - SVGMobject( - file_name = "blimp", - color = BLUE_C, - height = 0.5, - ), - SVGMobject( - file_name = "biplane", - color = RED_D, - height = 0.5, - ), - SVGMobject( - file_name = "helicopter", - color = LIGHT_GREY, - height = 0.5, - ).rotate(-TAU/24), - FalconHeavy(), - ) - y_shifts = [0.25, 0, 0.5, 0.25, -0.5] - for x, y, obj in zip(self.object_x_coords, y_shifts, objects): - obj.move_to(self.axes.coords_to_point(x, 0)) - obj.align_to(self.dish) - obj.shift(y*UP) - - self.object_velocities = [ - 0.7*LEFT, - 0.1*RIGHT, - 0.4*LEFT, - 0.4*RIGHT, - 0.5*UP, - ] - - def send_long_pulse_single_echo(self): - x = self.object_x_coords[0] - plane = self.objects[0] - self.add(plane) - randy = self.pi_creature - self.remove(randy) - - pulse_graph = self.get_frequency_pulse_graph(x) - pulse_graph.background_image_file = "blue_yellow_gradient" - - pulse = self.get_pulse(self.dish, plane) - - brace = Brace( - Line( - self.axes.coords_to_point(x-1, 1), - self.axes.coords_to_point(x+1, 1), - ), UP - ) - words = brace.get_text("Spread over time") - - self.add(pulse) - self.wait() - squished_rate_func = squish_rate_func(smooth, 0.6, 0.9) - self.play( - ShowCreation(pulse_graph, rate_func=linear), - GrowFromCenter(brace, rate_func = squished_rate_func), - Write(words, rate_func = squished_rate_func), - run_time = 3, - ) - self.remove(pulse) - self.play(FadeIn(randy)) - self.play(PiCreatureBubbleIntroduction( - randy, "Who cares?", - bubble_class = ThoughtBubble, - bubble_kwargs = { - "direction" : LEFT, - "width" : 2, - "height": 1.5, - }, - target_mode = "maybe", - look_at_arg = brace, - )) - self.play(Blink(randy)) - self.play(LaggedStartMap( - FadeOut, VGroup( - randy.bubble, randy.bubble.content, - brace, words, - ) - )) - - self.curr_graph = pulse_graph - - def introduce_multiple_objects(self): - objects = self.objects - x_coords = self.object_x_coords - curr_graph = self.curr_graph - randy = self.pi_creature - - graphs = VGroup(*[ - self.get_frequency_pulse_graph(x) - for x in x_coords - ]) - graphs.set_color_by_gradient(BLUE, YELLOW) - sum_graph = self.axes.get_graph( - lambda t : sum([ - graph.underlying_function(t) - for graph in graphs - ]), - num_graph_points = 1000 - ) - - noise_function = lambda t : np.sum([ - 0.5*np.sin(f*t)/f - for f in (2, 3, 5, 7, 11, 13) - ]) - noisy_graph = self.axes.get_graph( - lambda t : sum_graph.underlying_function(t)*(1+noise_function(t)), - num_graph_points = 1000 - ) - for graph in sum_graph, noisy_graph: - graph.background_image_file = "blue_yellow_gradient" - - pulses = self.get_pulses() - - self.play( - LaggedStartMap(GrowFromCenter, objects[1:]), - FadeOut(curr_graph), - randy.change, "pondering" - ) - self.add(*pulses) - self.wait(0.5) - self.play( - ShowCreation( - sum_graph, - rate_func=linear, - run_time = 3.5, - ), - randy.change, "confused" - ) - self.remove(*pulses) - self.play(randy.change, "pondering") - self.play(Transform( - sum_graph, noisy_graph, - rate_func = lambda t : wiggle(t, 4), - run_time = 3 - )) - self.wait(2) - - self.curr_graph = sum_graph - - def use_short_pulse(self): - curr_graph = self.curr_graph - objects = self.objects - x_coords = self.object_x_coords - randy = self.pi_creature - - self.frequency_spread_factor = 10 - self.n_pulse_singletons = 4 - self.pulse_frequency = 0.015 - - graphs = VGroup(*[ - self.get_frequency_pulse_graph(x) - for x in x_coords - ]) - sum_graph = self.axes.get_graph( - lambda t : sum([ - graph.underlying_function(t) - for graph in graphs - ]), - num_graph_points = 1000 - ) - sum_graph.background_image_file = "blue_yellow_gradient" - - pulses = self.get_pulses() - - self.play(FadeOut(curr_graph)) - self.add(*pulses) - self.wait(0.5) - self.play( - ShowCreation( - sum_graph, - rate_func=linear, - run_time = 3.5, - ), - randy.change, "happy" - ) - self.wait() - - self.curr_graph = sum_graph - self.first_echo_graph = graphs[0] - self.first_echo_graph.set_color(YELLOW) - - def fourier_transform_of_one_pulse(self): - frequency_axes = Axes( - x_min = 0, x_max = 20, - x_axis_config = { - "unit_size" : 0.5, - "tick_frequency" : 2, - }, - y_min = -.01, y_max = .01, - y_axis_config = { - "unit_size" : 110, - "tick_frequency" : 0.006 - } - ) - frequency_label = TextMobject("Frequency") - frequency_label.next_to(frequency_axes.x_axis.get_right(), UP) - frequency_axes.add(frequency_label) - first_echo_graph = self.first_echo_graph - - self.play( - ApplyMethod( - VGroup(self.axes, first_echo_graph).to_edge, UP, - {"buff" : SMALL_BUFF}, - rate_func = squish_rate_func(smooth, 0.5, 1) - ), - LaggedStartMap(FadeOut, self.objects), - LaggedStartMap(FadeOut, VGroup( - self.curr_graph, self.dish, self.pi_creature - )), - run_time = 2 - ) - - # - frequency_axes.next_to(self.axes, DOWN, LARGE_BUFF, LEFT) - fourier_graph = get_fourier_graph( - frequency_axes, first_echo_graph.underlying_function, - t_min = 0, t_max = 25, - complex_to_real_func = np.abs, - ) - fourier_graph.save_state() - fourier_graph.move_to(first_echo_graph) - h_vect = 4*RIGHT - fourier_graph.shift(h_vect) - fourier_graph.fade(1) - - f = 8 - v_line = DashedLine( - frequency_axes.coords_to_point(f, 0), - frequency_axes.coords_to_point(f, frequency_axes.y_max), - ) - v_lines = VGroup( - v_line.copy().shift(2*LEFT), - v_line.copy().shift(2*RIGHT), - ) - rect = Rectangle(stroke_width = 0, fill_color = YELLOW, fill_opacity = 0.25) - rect.replace(v_lines, stretch = True) - rect.save_state() - rect.stretch(0, 0) - - self.play(Write(frequency_axes, run_time = 1)) - self.play( - ApplyFunction( - lambda m : m.move_to(fourier_graph.saved_state).shift(-h_vect).fade(1), - first_echo_graph.copy(), - remover = True, - ), - fourier_graph.restore - ) - self.wait() - self.play(ShowCreation(v_line)) - self.play( - ReplacementTransform(VGroup(v_line), v_lines), - rect.restore - ) - self.wait() - self.play(FadeOut(v_lines), FadeOut(rect)) - - self.frequency_axes = frequency_axes - self.fourier_graph = fourier_graph - - def show_echos_of_moving_objects(self): - objects = self.objects - objects.save_state() - object_velocities = self.object_velocities - - movements = self.object_movements = [ - always_shift( - obj, - direction = v/get_norm(v), - rate = get_norm(v) - ) - for v, obj in zip(object_velocities, objects) - ] - pulses = self.get_pulses() - continual_anims = pulses+movements - - self.play( - FadeOut(self.axes), - FadeOut(self.first_echo_graph), - LaggedStartMap(FadeIn, objects), - FadeIn(self.dish) - ) - self.add(*continual_anims) - self.wait(4) - self.play(*[ - UpdateFromAlphaFunc( - obj, - lambda m, a : m.set_fill(opacity = 1-a), - ) - for obj in objects - ]) - self.remove(*continual_anims) - self.wait() - - def overlapping_frequenies_of_various_objects(self): - frequency_axes = self.frequency_axes - fourier_graph = self.fourier_graph - shifted_graphs = self.get_shifted_frequency_graphs(fourier_graph) - color = fourier_graph.get_color() - shifted_graphs.set_color_by_gradient( - average_color(color, WHITE), - color, - average_color(color, BLACK), - ) - sum_graph = self.get_sum_graph(frequency_axes, shifted_graphs) - sum_graph.match_style(fourier_graph) - - shifted_graphs.save_state() - - self.play(ReplacementTransform( - VGroup(fourier_graph), shifted_graphs, - lag_ratio = 0.5, - run_time = 2 - )) - self.wait() - self.play( - shifted_graphs.arrange, DOWN, - shifted_graphs.move_to, fourier_graph, DOWN, - ) - self.wait() - self.play(shifted_graphs.restore), - self.play(ReplacementTransform( - shifted_graphs, VGroup(sum_graph), - )) - self.wait() - - self.curr_fourier_graph = sum_graph - - def echos_of_long_pure_signal_in_frequency_space(self): - curr_fourier_graph = self.curr_fourier_graph - f_max = self.frequency_axes.y_max - new_fourier_graph = self.frequency_axes.get_graph( - lambda x : f_max * np.exp(-100*(x-8)**2), - num_graph_points = 1000, - ) - new_fourier_graph.set_color(PINK) - - self.play( - FadeOut(curr_fourier_graph), - FadeIn(new_fourier_graph), - ) - self.fourier_graph = new_fourier_graph - self.overlapping_frequenies_of_various_objects() - - def concentrated_fourier_requires_long_time(self): - objects = self.objects - objects.restore() - object_movements = self.object_movements - self.n_pulse_singletons = 32 - pulses = self.get_pulses() - randy = self.pi_creature - - continual_anims = object_movements+pulses - self.play(FadeIn(randy)) - self.add(*continual_anims) - self.play(randy.change, "angry", *[ - UpdateFromAlphaFunc(obj, lambda m, a : m.set_fill(opacity = a)) - for obj in objects - ]) - self.play(Blink(randy)) - self.wait(2) - self.play(Blink(randy)) - self.wait() - self.play(randy.change, "plain", *[ - UpdateFromAlphaFunc(obj, lambda m, a : m.set_fill(opacity = 1-a)) - for obj in objects - ]) - self.wait() - - - ### - - def get_frequency_pulse_graph(self, x, freq = 25, **kwargs): - graph = IntroduceDopplerRadar.get_frequency_pulse_graph( - self, x, freq, **kwargs - ) - return graph - - def get_pulse(self, dish, echo_object): - return RadarPulse( - dish, echo_object, - n_pulse_singletons = self.n_pulse_singletons, - frequency = 0.025, - speed = 5.0, - ) - - def get_pulses(self): - return [ - self.get_pulse( - self.dish.copy().shift(0.01*obj.get_center()[0]), - obj - ) - for obj in self.objects - ] - - def create_pi_creature(self): - randy = Randolph() - randy.scale(0.5).flip() - randy.to_edge(RIGHT, buff = 1.7).shift(0.5*UP) - return randy - - def get_shifted_frequency_graphs(self, fourier_graph): - frequency_axes = self.frequency_axes - def get_func(v): - return lambda f : fourier_graph.underlying_function(np.clip( - f-5*v[0], - frequency_axes.x_min, - frequency_axes.x_max, - )) - def get_graph(func): - return frequency_axes.get_graph(func) - shifted_graphs = VGroup(*list(map( - get_graph, list(map(get_func, self.object_velocities)) - ))) - shifted_graphs.match_style(fourier_graph) - return shifted_graphs - - def get_sum_graph(self, axes, graphs): - def get_func(graph): - return graph.underlying_function - funcs = list(map(get_func, graphs)) - return axes.get_graph( - lambda t : sum([func(t) for func in funcs]), - ) - -class SummarizeFourierTradeoffForDoppler(Scene): - def construct(self): - time_axes = Axes( - x_min = 0, x_max = 12, - y_min = -0.5, y_max = 1, - ) - time_axes.center().to_edge(UP, buff = LARGE_BUFF) - frequency_axes = time_axes.copy() - frequency_axes.next_to(time_axes, DOWN, buff = 2) - time_label = TextMobject("Time") - frequency_label = TextMobject("Frequency") - for label, axes in (time_label, time_axes), (frequency_label, frequency_axes): - label.next_to(axes.get_right(), UP, SMALL_BUFF) - axes.add(label) - frequency_label.shift_onto_screen() - title = TextMobject("Fourier Trade-off") - title.next_to(time_axes, DOWN) - self.add(title) - - - #Position determines log of scale value for exponentials - a_mob = VectorizedPoint() - x_values = [3, 5, 6, 7, 8] - v_values = [5, 5.5, 5.75, 6.5, 7] - def get_top_graphs(): - a = np.exp(a_mob.get_center()[0]) - graphs = VGroup(*[ - time_axes.get_graph(lambda t : np.exp(-5*a*(t-x)**2)) - for x in x_values - ]) - graphs.set_color(WHITE) - graphs.color_using_background_image("blue_yellow_gradient") - return graphs - def get_bottom_graphs(): - a = np.exp(a_mob.get_center()[0]) - graphs = VGroup(*[ - frequency_axes.get_graph(lambda t : np.exp(-(5./a)*(t-v)**2)) - for v in v_values - ]) - graphs.set_color(RED) - return graphs - - top_graphs = get_top_graphs() - bottom_graphs = get_bottom_graphs() - update_top_graphs = Mobject.add_updater( - top_graphs, - lambda g : Transform(g, get_top_graphs()).update(1) - ) - update_bottom_graphs = Mobject.add_updater( - bottom_graphs, - lambda g : Transform(g, get_bottom_graphs()).update(1) - ) - - self.add(time_axes, frequency_axes) - self.add(update_top_graphs, update_bottom_graphs) - - shift_vect = 2*RIGHT - for s in 1, -2, 1: - self.play(a_mob.shift, s*shift_vect, run_time = 3) - -class MentionUncertaintyPrincipleCopy(MentionUncertaintyPrinciple): - pass - -class IntroduceDeBroglie(Scene): - CONFIG = { - "default_wave_frequency" : 1, - "wave_colors" : [BLUE_D, YELLOW], - "dispersion_factor" : 1, - "amplitude" : 1, - } - def construct(self): - text_scale_val = 0.8, - - #Overlay real tower in video editor - eiffel_tower = Line(3*DOWN, 3*UP, stroke_width = 0) - picture = ImageMobject("de_Broglie") - picture.set_height(4) - picture.to_corner(UP+LEFT) - name = TextMobject("Louis de Broglie") - name.next_to(picture, DOWN) - - picture.save_state() - picture.scale(0) - picture.move_to(eiffel_tower.get_top()) - - - broadcasts = [ - Broadcast( - eiffel_tower.get_top(), - big_radius = 10, - n_circles = 10, - lag_ratio = 0.9, - run_time = 7, - rate_func = squish_rate_func(smooth, a, a+0.3), - color = WHITE, - ) - for a in np.linspace(0, 0.7, 3) - ] - - self.play(*broadcasts) - self.play(picture.restore) - self.play(Write(name)) - self.wait() - - #Time line - time_line = NumberLine( - x_min = 1900, - x_max = 1935, - tick_frequency = 1, - numbers_with_elongated_ticks = list(range(1900, 1941, 10)), - color = BLUE_D - ) - time_line.stretch_to_fit_width(FRAME_WIDTH - picture.get_width() - 2) - time_line.add_numbers(*time_line.numbers_with_elongated_ticks) - time_line.next_to(picture, RIGHT, MED_LARGE_BUFF, DOWN) - - year_to_words = { - 1914 : "Wold War I begins", - 1915 : "Einstein field equations", - 1916 : "Lewis dot formulas", - 1917 : "Not a lot of physics...because war", - 1918 : "S'more Rutherford badassery", - 1919 : "Eddington confirms general relativity predictions", - 1920 : "World is generally stoked on general relativity", - 1921 : "Einstein gets long overdue Nobel prize", - 1922 : "Stern-Gerlach Experiment", - 1923 : "Compton scattering observed", - 1924 : "de Broglie's thesis" - } - arrow = Vector(DOWN, color = WHITE) - arrow.next_to(time_line.number_to_point(1914), UP) - words = TextMobject(year_to_words[1914]) - words.scale(text_scale_val) - date = Integer(1914) - date.next_to(arrow, UP, LARGE_BUFF) - - def get_year(alpha = 0): - return int(time_line.point_to_number(arrow.get_end())) - - def update_words(words): - text = year_to_words.get(get_year(), "Hi there") - if text not in words.get_tex_string(): - words.__init__(text) - words.scale(text_scale_val) - words.move_to(interpolate( - arrow.get_top(), date.get_bottom(), 0.5 - )) - update_words(words) - self.play( - FadeIn(time_line), - GrowArrow(arrow), - Write(words), - Write(date), - run_time = 1 - ) - self.wait() - self.play( - arrow.next_to, time_line.number_to_point(1924), UP, - ChangingDecimal( - date, get_year, - position_update_func = lambda m : m.next_to(arrow, UP, LARGE_BUFF) - ), - UpdateFromFunc(words, update_words), - run_time = 3, - ) - self.wait() - - #Transform time_line - line = time_line - self.play( - FadeOut(time_line.numbers), - VGroup(arrow, words, date).shift, MED_LARGE_BUFF*UP, - *[ - ApplyFunction( - lambda m : m.rotate(TAU/4).set_stroke(width = 0), - mob, - remover = True - ) - for mob in time_line.tick_marks - ] - ) - - #Wave function - particle = VectorizedPoint() - axes = Axes(x_min = -1, x_max = 10) - axes.match_width(line) - axes.shift(line.get_center() - axes.x_axis.get_center()) - im_line = line.copy() - im_line.set_color(YELLOW) - wave_update_animation = self.get_wave_update_animation( - axes, particle, line, im_line - ) - - for x in range(3): - particle.move_to(axes.coords_to_point(-10, 0)) - self.play( - ApplyMethod( - particle.move_to, axes.coords_to_point(22, 0), - rate_func=linear - ), - wave_update_animation, - run_time = 3 - ) - self.wait() - - ### - def get_wave_update_animation(self, axes, particle, re_line = None, im_line = None): - line = Line( - axes.x_axis.get_left(), - axes.x_axis.get_right(), - ) - if re_line is None: - re_line = line.copy() - re_line.set_color(self.wave_colors[0]) - if im_line is None: - im_line = line.copy() - im_line.set_color(self.wave_colors[1]) - lines = VGroup(im_line, re_line) - def update_lines(lines): - waves = self.get_wave_pair(axes, particle) - for line, wave in zip(lines, waves): - wave.match_style(line) - Transform(line, wave).update(1) - return UpdateFromFunc(lines, update_lines) - - def get_wave( - self, axes, particle, - complex_to_real_func = lambda z : z.real, - freq = None, - **kwargs): - freq = freq or self.default_wave_frequency - k0 = 1./freq - t0 = axes.x_axis.point_to_number(particle.get_center()) - def func(x): - dispersion = fdiv(1., self.dispersion_factor)*(np.sqrt(1./(1+t0**2))) - wave_part = complex_to_real_func(np.exp( - complex(0, TAU*freq*(x-dispersion)) - )) - bell_part = np.exp(-dispersion*(x-t0)**2) - amplitude = self.amplitude - return amplitude*wave_part*bell_part - graph = axes.get_graph(func) - return graph - - def get_wave_pair(self, axes, particle, colors = None, **kwargs): - if colors is None and "color" not in kwargs: - colors = self.wave_colors - return VGroup(*[ - self.get_wave( - axes, particle, - C_to_R, color = color, - **kwargs - ) - for C_to_R, color in zip( - [lambda z : z.imag, lambda z : z.real], - colors - ) - ]) - -class ShowMomentumFormula(IntroduceDeBroglie, TeacherStudentsScene): - CONFIG = { - "default_wave_frequency" : 2, - "dispersion_factor" : 0.25, - "p_color" : BLUE, - "xi_color" : YELLOW, - "amplitude" : 0.5, - } - def construct(self): - self.introduce_formula() - self.react_to_claim() - - def introduce_formula(self): - formula = p, eq, h, xi = TexMobject("p", "=", "h", "\\xi") - formula.move_to(ORIGIN) - formula.scale(1.5) - - word_shift_val = 1.75 - p_words = TextMobject("Momentum") - p_words.next_to(p, UP, LARGE_BUFF).shift(word_shift_val*LEFT) - p_arrow = Arrow( - p_words.get_bottom(), p.get_corner(UP+LEFT), - buff = SMALL_BUFF - ) - added_p_words = TextMobject("(Classically $m \\times v$)") - added_p_words.move_to(p_words, DOWN) - VGroup(p, p_words, added_p_words, p_arrow).set_color(self.p_color) - - xi_words = TextMobject("Spatial frequency") - added_xi_words = TextMobject("(cycles per unit \\emph{distance})") - xi_words.next_to(xi, UP, LARGE_BUFF).shift(word_shift_val*RIGHT) - xi_words.align_to(p_words) - xi_arrow = Arrow( - xi_words.get_bottom(), xi.get_corner(UP+RIGHT), - buff = SMALL_BUFF - ) - added_xi_words.move_to(xi_words, DOWN) - added_xi_words.align_to(added_p_words, DOWN) - VGroup(xi, xi_words, added_xi_words, xi_arrow).set_color(self.xi_color) - - axes = Axes( - x_min = 0, x_max = FRAME_WIDTH, - y_min = -1, y_max = 1, - ) - axes.center().to_edge(UP, buff = -0.5) - # axes.next_to(formula, RIGHT) - particle = VectorizedPoint() - wave_update_animation = self.get_wave_update_animation(axes, particle) - wave = wave_update_animation.mobject - wave[0].set_stroke(width = 0) - particle.next_to(wave, LEFT, buff = 2) - wave_propagation = AnimationGroup( - ApplyMethod(particle.move_to, axes.coords_to_point(30, 0)), - wave_update_animation, - run_time = 4, - rate_func=linear, - ) - stopped_wave_propagation = AnimationGroup( - ApplyMethod(particle.move_to, xi_words), - wave_update_animation, - run_time = 3, - rate_func=linear, - ) - n_v_lines = 10 - v_lines = VGroup(*[ - DashedLine(UP, DOWN) - for x in range(n_v_lines) - ]) - v_lines.match_color(xi) - v_lines.arrange( - RIGHT, - buff = float(axes.x_axis.unit_size)/self.default_wave_frequency - ) - v_lines.move_to(stopped_wave_propagation.sub_anims[0].target_mobject) - v_lines.align_to(wave) - v_lines.shift(0.125*RIGHT) - - self.add(formula, wave) - self.play( - self.teacher.change, "raise_right_hand", - GrowArrow(p_arrow), - Succession( - Write, p_words, - ApplyMethod, p_words.next_to, added_p_words, UP, - ), - FadeIn( - added_p_words, - rate_func = squish_rate_func(smooth, 0.5, 1), - run_time = 2, - ), - wave_propagation - ) - self.play( - Write(xi_words), - GrowArrow(xi_arrow), - self.get_student_changes("confused", "erm", "sassy"), - stopped_wave_propagation - ) - self.play( - FadeIn(added_xi_words), - xi_words.next_to, added_xi_words, UP, - ) - self.play( - LaggedStartMap(ShowCreation, v_lines), - self.get_student_changes(*["pondering"]*3) - ) - self.play(LaggedStartMap(FadeOut, v_lines)) - self.wait() - - self.formula_labels = VGroup( - p_words, p_arrow, added_p_words, - xi_words, xi_arrow, added_xi_words, - ) - self.set_variables_as_attrs(wave, wave_propagation, formula) - - def react_to_claim(self): - formula_labels = self.formula_labels - full_formula = VGroup(self.formula, formula_labels) - full_formula.save_state() - wave_propagation = self.wave_propagation - - student = self.students[2] - self.student_says( - "Hang on...", - bubble_kwargs = {"height" : 2, "width" : 2, "direction" : LEFT}, - target_mode = "sassy", - student_index = 2, - added_anims = [self.teacher.change, "plain"] - ) - student.bubble.add(student.bubble.content) - self.wait() - kwargs = { - "path_arc" : TAU/4, - "lag_ratio" : 0.5, - "lag_ratio" : 0.7, - "run_time" : 1.5, - } - self.play( - full_formula.scale, 0, - full_formula.move_to, student.eyes.get_bottom()+SMALL_BUFF*DOWN, - Animation(student.bubble), - **kwargs - ) - self.play(full_formula.restore, Animation(student.bubble), **kwargs) - wave_propagation.update_config( - rate_func = lambda a : interpolate(0.35, 1, a) - ) - self.play( - wave_propagation, - RemovePiCreatureBubble(student, target_mode = "confused"), - ) - wave_propagation.update_config(rate_func = lambda t : t) - self.student_says( - "Physics is \\\\ just weird", - bubble_kwargs = {"height" : 2.5, "width" : 3}, - target_mode = "shruggie", - student_index = 0, - added_anims = [ApplyMethod(full_formula.shift, UP)] - ) - self.wait() - self.play( - wave_propagation, - ApplyMethod(full_formula.shift, DOWN), - FadeOut(self.students[0].bubble), - FadeOut(self.students[0].bubble.content), - self.get_student_changes(*3*["pondering"]), - self.teacher.change, "pondering", - ) - self.play(wave_propagation) - -class AskPhysicists(PiCreatureScene): - def construct(self): - morty, physy1, physy2, physy3 = self.pi_creatures - formula = TexMobject("p", "=", "h", "\\xi") - formula.set_color_by_tex_to_color_map({ - "p" : BLUE, - "\\xi" : YELLOW, - }) - formula.scale(1.5) - - formula.to_edge(UP) - formula.save_state() - formula.shift(DOWN) - formula.fade(1) - self.play(formula.restore) - self.pi_creature_says( - morty, "So...why?", - target_mode = "maybe" - ) - self.wait(2) - self.play( - RemovePiCreatureBubble(morty), - PiCreatureSays( - physy2, - "Take the Schrödinger equation \\\\ with $H = \\frac{p^2}{2m}+V(x)$", - bubble_kwargs = {"fill_opacity" : 0.9}, - ), - ) - self.play( - PiCreatureSays( - physy1, - "Even classically position and \\\\ momentum are conjugate", - target_mode = "surprised", - bubble_kwargs = {"fill_opacity" : 0.9}, - ), - ) - self.play( - PiCreatureSays( - physy3, - "Consider special relativity \\\\ together with $E = hf$", - target_mode = "hooray", - bubble_kwargs = {"fill_opacity" : 0.9}, - ), - morty.change, "guilty" - ) - self.wait(2) - - - - ### - - def create_pi_creatures(self): - scale_factor = 0.85 - morty = Mortimer().flip() - morty.scale(scale_factor) - morty.to_corner(DOWN+LEFT) - - physies = VGroup(*[ - PiCreature(color = c).flip() - for c in (GREY, LIGHT_GREY, DARK_GREY) - ]) - physies.arrange(RIGHT, buff = MED_SMALL_BUFF) - physies.scale(scale_factor) - physies.to_corner(DOWN+RIGHT) - - self.add(physies) - return VGroup(morty, *physies) - -class SortOfDopplerEffect(PiCreatureScene): - CONFIG = { - "omega" : np.pi, - "arrow_spacing" : 0.25, - } - def setup(self): - PiCreatureScene.setup(self) - rect = self.screen_rect = ScreenRectangle(height = FRAME_HEIGHT) - rect.set_stroke(width = 0) - self.camera = MovingCamera( - rect, **self.camera_config - ) - - def construct(self): - screen_rect = self.screen_rect - - #x-coordinate gives time - t_tracker = VectorizedPoint() - #x-coordinate gives wave number - k_tracker = VectorizedPoint(2*RIGHT) - always_shift(t_tracker, RIGHT, 1) - def get_wave(): - t = t_tracker.get_center()[0] - k = k_tracker.get_center()[0] - omega = self.omega - color = interpolate_color( - BLUE, RED, (k-2)/2.0 - ) - func = lambda x : 0.5*np.cos(omega*t - k*x) - graph = FunctionGraph( - func, - x_min = -5*FRAME_X_RADIUS, - x_max = FRAME_X_RADIUS, - color = color, - ) - return VGroup(graph, *[ - Arrow( - x*RIGHT, x*RIGHT + func(x)*UP, - color = color - ) - for x in np.arange( - -4*FRAME_X_RADIUS, FRAME_X_RADIUS, - self.arrow_spacing - ) - ]) - return - wave = get_wave() - wave_update = Mobject.add_updater( - wave, lambda w : Transform(w, get_wave()).update(1) - ) - - rect = ScreenRectangle(height = 2) - rect.to_edge(RIGHT) - always_shift(rect, LEFT, 1) - rect_movement = rect - - randy = self.pi_creature - randy_look_at = Mobject.add_updater( - randy, lambda r : r.look_at(rect) - ) - - ref_frame1 = TextMobject("Reference frame 1") - # ref_frame1.next_to(randy, UP, aligned_edge = LEFT) - ref_frame1.to_edge(UP) - ref_frame2 = TextMobject("Reference frame 2") - ref_frame2.next_to(rect, UP) - # ref_frame2.set_fill(opacity = 0) - ref_frame2_follow = Mobject.add_updater( - ref_frame2, lambda m : m.next_to(rect, UP) - ) - ref_frame_1_continual_anim = ContinualAnimation(ref_frame1) - - self.add( - t_tracker, wave_update, rect_movement, randy_look_at, - ref_frame2_follow, ref_frame_1_continual_anim - ) - self.add(ref_frame1) - self.play(randy.change, "pondering") - self.wait(4) - start_height = screen_rect.get_height() - start_center = screen_rect.get_center() - self.play( - UpdateFromAlphaFunc( - screen_rect, - lambda m, a : m.move_to( - interpolate(start_center, rect.get_center(), a) - ) - ), - k_tracker.shift, 2*RIGHT, - ) - self.play( - MaintainPositionRelativeTo( - screen_rect, rect, - run_time = 4 - ), - ) - self.play( - screen_rect.move_to, rect.get_right()+FRAME_X_RADIUS*LEFT, - k_tracker.shift, 2*LEFT, - ) - - #Frequency words - temporal_frequency = TextMobject("Temporal", "frequency") - spatial_frequency = TextMobject("Spatial", "frequency") - temporal_frequency.move_to(screen_rect).to_edge(UP) - spatial_frequency.next_to(temporal_frequency, DOWN) - cross = Cross(temporal_frequency[0]) - - time = TextMobject("Time") - space = TextMobject("Space") - time.next_to(temporal_frequency, RIGHT, buff = 2) - space.next_to(time, DOWN) - space.align_to(spatial_frequency) - - self.play(FadeIn(temporal_frequency)) - self.play(ShowCreation(cross)) - self.play(Write(spatial_frequency)) - self.wait() - self.play(FadeIn(time), FadeIn(space)) - self.play( - Transform(time, space), - Transform(space, time), - lag_ratio = 0.5, - run_time = 1, - ) - self.play(FadeOut(time), FadeOut(space)) - self.wait(3) - - ### - - def create_pi_creature(self): - return Randolph().scale(0.5).to_corner(DOWN+LEFT) - -class HangingWeightsScene(MovingCameraScene): - CONFIG = { - "frequency" : 0.5, - "ceiling_radius" : 3*FRAME_X_RADIUS, - "n_springs" : 72, - "amplitude" : 0.6, - "spring_radius" : 0.15, - } - def construct(self): - self.setup_springs() - self.setup_weights() - self.introduce() - self.show_analogy_with_electron() - self.metaphor_for_something() - self.moving_reference_frame() - - def setup_springs(self): - ceiling = self.ceiling = Line(LEFT, RIGHT) - ceiling.scale(self.ceiling_radius) - ceiling.to_edge(UP, buff = LARGE_BUFF) - self.add(ceiling) - - def get_spring(alpha, height = 2): - t_max = 6.5 - r = self.spring_radius - s = (height - r)/(t_max**2) - spring = ParametricFunction( - lambda t : op.add( - r*(np.sin(TAU*t)*RIGHT+np.cos(TAU*t)*UP), - s*((t_max - t)**2)*DOWN, - ), - t_min = 0, t_max = t_max, - color = WHITE, - stroke_width = 2, - ) - spring.alpha = alpha - spring.move_to(ceiling.point_from_proportion(alpha), UP) - spring.color_using_background_image("grey_gradient") - return spring - alphas = np.linspace(0, 1, self.n_springs) - bezier([0, 1, 0, 1]) - springs = self.springs = VGroup(*list(map(get_spring, alphas))) - - k_tracker = self.k_tracker = VectorizedPoint() - t_tracker = self.t_tracker = VectorizedPoint() - always_shift(t_tracker, RIGHT, 1) - self.t_tracker_walk = t_tracker - equilibrium_height = springs.get_height() - def update_springs(springs): - for spring in springs: - k = k_tracker.get_center()[0] - t = t_tracker.get_center()[0] - f = self.frequency - x = spring.get_top()[0] - A = self.amplitude - d_height = A*np.cos(TAU*f*t - k*x) - new_spring = get_spring(spring.alpha, 2+d_height) - Transform(spring, new_spring).update(1) - spring_update_anim = Mobject.add_updater(springs, update_springs) - self.spring_update_anim = spring_update_anim - spring_update_anim.update(0) - - self.play( - ShowCreation(ceiling), - LaggedStartMap(ShowCreation, springs) - ) - - def setup_weights(self): - weights = self.weights = VGroup() - weight_anims = weight_anims = [] - for spring in self.springs: - x = spring.get_top()[0] - mass = np.exp(-0.1*x**2) - weight = Circle(radius = 0.15) - weight.start_radius = 0.15 - weight.target_radius = 0.25*mass #For future update - weight.spring = spring - weight_anim = Mobject.add_updater( - weight, lambda w : w.move_to(w.spring.get_bottom()) - ) - weight_anim.update(0) - weight_anims.append(weight_anim) - weights.add(weight) - weights.set_fill(opacity = 1) - weights.set_color_by_gradient(BLUE_D, BLUE_E, BLUE_D) - weights.set_stroke(WHITE, 1) - - self.play(LaggedStartMap(GrowFromCenter, weights)) - self.add(self.t_tracker_walk) - self.add(self.spring_update_anim) - self.add(*weight_anims) - - def introduce(self): - arrow = Arrow(4*LEFT, LEFT) - arrows = VGroup(arrow, arrow.copy().flip(about_point = ORIGIN)) - arrows.set_color(WHITE) - - self.wait(3) - self.play(*list(map(GrowArrow, arrows))) - self.play(*[ - UpdateFromAlphaFunc( - weight, lambda w, a : w.set_width( - 2*interpolate(w.start_radius, w.target_radius, a) - ), - run_time = 2 - ) - for weight in self.weights - ]) - self.play(FadeOut(arrows)) - self.wait(3) - - def show_analogy_with_electron(self): - words = TextMobject( - "Analogous to the energy of a particle \\\\", - "(in the sense of $E=mc^2$)" - ) - words.move_to(DOWN) - - self.play(Write(words)) - self.wait(3) - self.play(FadeOut(words)) - - def metaphor_for_something(self): - de_broglie = ImageMobject("de_Broglie") - de_broglie.set_height(3.5) - de_broglie.to_corner(DOWN+RIGHT) - words = TextMobject(""" - If a photon's energy is carried as a wave \\\\ - is this true for any particle? - """) - words.next_to(de_broglie, LEFT) - - einstein = ImageMobject("Einstein") - einstein.match_height(de_broglie) - einstein.to_corner(DOWN+LEFT) - - for picture in de_broglie, einstein: - picture.backdrop = Rectangle() - picture.backdrop.replace(picture, stretch = True) - picture.backdrop.set_fill(BLACK, 1) - picture.backdrop.set_stroke(BLACK, 0) - - self.play( - Animation(de_broglie.backdrop, remover = True), - FadeIn(de_broglie) - ) - self.play(Write(words)) - self.wait(7) - self.play( - FadeOut(words), - Animation(einstein.backdrop, remover = True), - FadeIn(einstein) - ) - self.wait(2) - - self.de_broglie = de_broglie - self.einstein = einstein - - def moving_reference_frame(self): - rect = ScreenRectangle(height = 2.1*FRAME_Y_RADIUS) - rect_movement = always_shift(rect, direction = LEFT, rate = 2) - camera_frame = self.camera_frame - - self.add(rect) - self.play( - Animation(self.de_broglie.backdrop, remover = True), - FadeOut(self.de_broglie), - Animation(self.einstein.backdrop, remover = True), - FadeOut(self.einstein), - ) - self.play(camera_frame.scale, 3, {"about_point" : 2*UP}) - self.play(rect.shift, FRAME_WIDTH*RIGHT, path_arc = -TAU/2) - self.add(rect_movement) - self.wait(3) - - def zoom_into_reference_frame(): - original_height = camera_frame.get_height() - original_center = camera_frame.get_center() - self.play( - UpdateFromAlphaFunc( - camera_frame, lambda c, a : c.set_height( - interpolate(original_height, 0.95*rect.get_height(), a) - ).move_to( - interpolate(original_center, rect.get_center(), a) - ) - ), - ApplyMethod(self.k_tracker.shift, RIGHT) - ) - self.play(MaintainPositionRelativeTo( - camera_frame, rect, - run_time = 6 - )) - self.play( - camera_frame.set_height, original_height, - camera_frame.move_to, original_center, - ApplyMethod(self.k_tracker.shift, LEFT) - ) - - zoom_into_reference_frame() - self.wait() - self.play( - UpdateFromAlphaFunc(rect, lambda m, a : m.set_stroke(width = 2*(1-a))) - ) - - index = int(0.5*len(self.springs)) - weights = VGroup(self.weights[index], self.weights[index+4]) - flashes = list(map(self.get_peak_flash_anim, weights)) - weights.save_state() - weights.set_fill(RED) - self.add(*flashes) - self.wait(5) - - rect.align_to(camera_frame, RIGHT) - self.play(UpdateFromAlphaFunc(rect, lambda m, a : m.set_stroke(width = 2*a))) - - randy = Randolph(mode = "pondering") - randy.look(UP+RIGHT) - de_broglie = ImageMobject("de_Broglie") - de_broglie.set_height(6) - de_broglie.next_to(4*DOWN, DOWN) - self.add( - Mobject.add_updater( - randy, lambda m : m.next_to( - rect.get_corner(DOWN+LEFT), UP+RIGHT, MED_LARGE_BUFF, - ).look_at(weights) - ), - de_broglie - ) - self.wait(2) - - zoom_into_reference_frame() - self.wait(8) - - ### - - def get_peak_flash_anim(self, weight): - mobject = Mobject() #Dummy - mobject.last_y = 0 - mobject.last_dy = 0 - mobject.curr_anim = None - mobject.curr_anim_time = 0 - mobject.time_since_last_flash = 0 - def update(mob, dt): - mob.time_since_last_flash += dt - point = weight.get_center() - y = point[1] - mob.dy = y - mob.last_y - different_dy = np.sign(mob.dy) != np.sign(mob.last_dy) - if different_dy and mob.time_since_last_flash > 0.5: - mob.curr_anim = Flash( - VectorizedPoint(point), - flash_radius = 0.5, - line_length = 0.3, - run_time = 0.2, - ) - mob.submobjects = [mob.curr_anim.mobject] - mob.time_since_last_flash = 0 - mob.last_y = float(y) - mob.last_dy = float(mob.dy) - ## - if mob.curr_anim: - mob.curr_anim_time += dt - if mob.curr_anim_time > mob.curr_anim.run_time: - mob.curr_anim = None - mob.submobjects = [] - mob.curr_anim_time = 0 - return - mob.curr_anim.update(mob.curr_anim_time/mob.curr_anim.run_time) - - return Mobject.add_updater(mobject, update) - -class MinutPhysicsWrapper(Scene): - def construct(self): - logo = ImageMobject("minute_physics_logo", invert = True) - logo.to_corner(UP+LEFT) - self.add(logo) - - title = TextMobject("Minute Physics on special relativity") - title.to_edge(UP).shift(MED_LARGE_BUFF*RIGHT) - - screen_rect = ScreenRectangle() - screen_rect.set_width(title.get_width() + LARGE_BUFF) - screen_rect.next_to(title, DOWN) - - self.play(ShowCreation(screen_rect)) - self.play(Write(title)) - self.wait(2) - -class WhatDoesTheFourierTradeoffTellUs(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "So! What does \\\\ the Fourier trade-off \\\\ tell us?", - target_mode = "surprised", - bubble_kwargs = {"width" : 4, "height" : 3} - ) - self.change_student_modes(*["thinking"]*3) - self.wait(4) - -class FourierTransformOfWaveFunction(Scene): - CONFIG = { - "wave_stroke_width" : 3, - "wave_color" : BLUE, - } - def construct(self): - self.show_wave_packet() - self.take_fourier_transform() - self.show_correlations_with_pure_frequencies() - self.this_is_momentum() - self.show_tradeoff() - - def setup(self): - self.x0_tracker = ValueTracker(-3) - self.k_tracker = ValueTracker(1) - self.a_tracker = ExponentialValueTracker(0.5) - - def show_wave_packet(self): - axes = Axes( - x_min = 0, x_max = 12, - y_min = -1, y_max = 1, - y_axis_config = { - "tick_frequency" : 0.5 - } - ) - position_label = TextMobject("Position") - position_label.next_to(axes.x_axis.get_right(), UP) - axes.add(position_label) - axes.center().to_edge(UP, buff = LARGE_BUFF) - - wave = self.get_wave(axes) - wave_update_animation = UpdateFromFunc( - wave, lambda w : Transform(w, self.get_wave(axes)).update(1) - ) - - self.add(axes, wave) - self.play( - self.x0_tracker.set_value, 5, - wave_update_animation, - run_time = 3, - ) - self.wait() - - self.wave_function = wave.underlying_function - self.wave_update_animation = wave_update_animation - self.wave = wave - self.axes = axes - - def take_fourier_transform(self): - wave = self.wave - wave_update_animation = self.wave_update_animation - frequency_axes = Axes( - x_min = 0, x_max = 3, - x_axis_config = { - "unit_size" : 4, - "tick_frequency" : 0.25, - "numbers_with_elongated_ticks" : [1, 2] - }, - y_min = -0.15, - y_max = 0.15, - y_axis_config = { - "unit_size" : 7.5, - "tick_frequency" : 0.05, - } - ) - label = self.frequency_x_axis_label = TextMobject("Spatial frequency") - label.next_to(frequency_axes.x_axis.get_right(), UP) - frequency_axes.add(label) - frequency_axes.move_to(self.axes, LEFT) - frequency_axes.to_edge(DOWN, buff = LARGE_BUFF) - label.shift_onto_screen() - - def get_wave_function_fourier_graph(): - return get_fourier_graph( - frequency_axes, self.get_wave_func(), - t_min = 0, t_max = 15, - ) - fourier_graph = get_wave_function_fourier_graph() - self.fourier_graph_update_animation = UpdateFromFunc( - fourier_graph, lambda m : Transform( - m, get_wave_function_fourier_graph() - ).update(1) - ) - - wave_copy = wave.copy() - wave_copy.generate_target() - wave_copy.target.move_to(fourier_graph, LEFT) - wave_copy.target.fade(1) - fourier_graph.save_state() - fourier_graph.move_to(wave, LEFT) - fourier_graph.fade(1) - - arrow = Arrow( - self.axes.coords_to_point(5, -1), - frequency_axes.coords_to_point(1, 0.1), - color = YELLOW, - ) - fourier_label = TextMobject("Fourier Transform") - fourier_label.next_to(arrow.get_center(), RIGHT) - - self.play(ReplacementTransform( - self.axes.copy(), frequency_axes - )) - self.play( - MoveToTarget(wave_copy, remover = True), - fourier_graph.restore, - GrowArrow(arrow), - Write(fourier_label, run_time = 1), - ) - self.wait() - - self.frequency_axes = frequency_axes - self.fourier_graph = fourier_graph - self.fourier_label = VGroup(arrow, fourier_label) - - def show_correlations_with_pure_frequencies(self): - frequency_axes = self.frequency_axes - axes = self.axes - - sinusoid = axes.get_graph( - lambda x : 0.5*np.cos(TAU*x), - x_min = -FRAME_X_RADIUS, x_max = 3*FRAME_X_RADIUS, - ) - sinusoid.to_edge(UP, buff = SMALL_BUFF) - - v_line = DashedLine(1.5*UP, ORIGIN, color = YELLOW) - v_line.move_to(frequency_axes.coords_to_point(1, 0), DOWN) - - f_equals = TexMobject("f = ") - freq_decimal = DecimalNumber(1) - freq_decimal.next_to(f_equals, RIGHT, buff = SMALL_BUFF) - freq_label = VGroup(f_equals, freq_decimal) - freq_label.next_to( - v_line, UP, SMALL_BUFF, - submobject_to_align = f_equals[0] - ) - - self.play( - ShowCreation(sinusoid), - ShowCreation(v_line), - Write(freq_label, run_time = 1), - FadeOut(self.fourier_label) - ) - last_f = 1 - for f in 1.4, 0.7, 1: - self.play( - sinusoid.stretch,f/last_f, 0, - {"about_point" : axes.coords_to_point(0, 0)}, - v_line.move_to, frequency_axes.coords_to_point(f, 0), DOWN, - MaintainPositionRelativeTo(freq_label, v_line), - ChangeDecimalToValue(freq_decimal, f), - run_time = 3, - ) - last_f = f - self.play(*list(map(FadeOut, [ - sinusoid, v_line, freq_label - ]))) - - def this_is_momentum(self): - formula = TexMobject("p", "=", "h", "\\xi") - formula.set_color_by_tex_to_color_map({ - "p" : BLUE, - "xi" : YELLOW, - }) - formula.next_to( - self.frequency_x_axis_label, UP - ) - - f_max = 0.12 - brace = Brace(Line(2*LEFT, 2*RIGHT), UP) - brace.move_to(self.frequency_axes.coords_to_point(1, f_max), DOWN) - words = TextMobject("This wave \\\\ describes momentum") - words.next_to(brace, UP) - - self.play(Write(formula)) - self.wait() - self.play( - GrowFromCenter(brace), - Write(words) - ) - brace.add(words) - for k in 2, 0.5, 1: - self.play( - self.k_tracker.set_value, k, - self.wave_update_animation, - self.fourier_graph_update_animation, - UpdateFromFunc( - brace, lambda b : b.move_to( - self.frequency_axes.coords_to_point( - self.k_tracker.get_value(), - f_max, - ), - DOWN - ) - ), - run_time = 2 - ) - self.wait() - self.play(*list(map(FadeOut, [brace, words, formula]))) - - def show_tradeoff(self): - for a in 5, 0.1, 0.01, 10, 0.5: - self.play( - ApplyMethod( - self.a_tracker.set_value, a, - run_time = 2 - ), - self.wave_update_animation, - self.fourier_graph_update_animation - ) - self.wait() - - ## - - def get_wave_func(self): - x0 = self.x0_tracker.get_value() - k = self.k_tracker.get_value() - a = self.a_tracker.get_value() - A = a**(0.25) - return lambda x : A*np.cos(TAU*k*x)*np.exp(-a*(x - x0)**2) - - def get_wave(self, axes): - return axes.get_graph( - self.get_wave_func(), - color = self.wave_color, - stroke_width = self.wave_stroke_width - ) - -class DopplerComparisonTodos(TODOStub): - CONFIG = { - "message" : """ - Insert some Doppler footage, - insert some hanging spring scene, - insert position-momentum Fourier trade-off - """ - } - -class MusicalNote(AddingPureFrequencies): - def construct(self): - speaker = self.speaker = SVGMobject(file_name = "speaker") - speaker.move_to(2*DOWN) - randy = self.pi_creature - - axes = Axes( - x_min = 0, x_max = 10, - y_min = -1.5, y_max = 1.5 - ) - axes.center().to_edge(UP) - time_label = TextMobject("Time") - time_label.next_to(axes.x_axis.get_right(), UP) - axes.add(time_label) - - graph = axes.get_graph( - lambda x : op.mul( - np.exp(-0.2*(x-4)**2), - 0.3*(np.cos(2*TAU*x) + np.cos(3*TAU*x) + np.cos(5*TAU*x)), - ), - ) - graph.set_color(BLUE) - v_line = DashedLine(ORIGIN, 0.5*UP) - v_line_update = UpdateFromFunc( - v_line, lambda l : l.put_start_and_end_on_with_projection( - graph.points[-1], - axes.x_axis.number_to_point( - axes.x_axis.point_to_number(graph.points[-1]) - ) - ) - ) - - self.add(speaker, axes) - self.play( - randy.change, "pondering", - self.get_broadcast_animation(n_circles = 6, run_time = 5), - self.get_broadcast_animation(n_circles = 12, run_time = 5), - ShowCreation(graph, run_time = 5, rate_func=linear), - v_line_update - ) - self.wait(2) - -class AskAboutUncertainty(TeacherStudentsScene): - def construct(self): - self.student_says( - "What does this have \\\\ to do with ``certainty''", - bubble_kwargs = {"direction" : LEFT}, - student_index = 2 - ) - self.play(PiCreatureSays( - self.students[0], - "What even are \\\\ these waves?", - target_mode = "confused" - )) - self.wait(2) - -class ProbabalisticDetection(FourierTransformOfWaveFunction): - CONFIG = { - "wave_stroke_width" : 2, - } - def construct(self): - self.setup_wave() - self.detect_only_single_points() - self.show_probability_distribution() - self.show_concentration_of_the_wave() - - def setup_wave(self): - axes = Axes( - x_min = 0, x_max = 10, - y_min = -0.5, y_max = 1.5, - y_axis_config = { - "unit_size" : 1.5, - "tick_frequency" : 0.25, - } - ) - axes.set_stroke(width = 2) - axes.center() - self.x0_tracker.set_value(5) - self.k_tracker.set_value(1) - self.a_tracker.set_value(0.2) - wave = self.get_wave(axes) - self.wave_update_animation = UpdateFromFunc( - wave, lambda w : Transform(w, self.get_wave(axes)).update(1) - ) - - self.k_tracker.save_state() - self.k_tracker.set_value(0) - bell_curve = self.get_wave(axes) - self.k_tracker.restore() - bell_curve.set_stroke(width = 0) - bell_curve.set_fill(BLUE, opacity = 0.5) - squared_bell_curve = axes.get_graph( - lambda x : bell_curve.underlying_function(x)**2 - ).match_style(bell_curve) - - self.set_variables_as_attrs( - axes, wave, bell_curve, squared_bell_curve - ) - - def detect_only_single_points(self): - particle = ProbabalisticDotCloud( - n_copies = 100, - fill_opacity = 0.05, - time_per_change = 0.05, - ) - particle.mobject[0].set_fill(BLUE, opacity = 1) - gdw = particle.gaussian_distribution_wrapper - - rect = Rectangle( - stroke_width = 0, - height = 0.5, - width = 2, - ) - rect.set_fill(YELLOW, 0.3) - rect.move_to(self.axes.coords_to_point(self.x0_tracker.get_value(), 0)) - brace = Brace(rect, UP, buff = 0) - question = TextMobject("Do we detect the particle \\\\ in this region?") - question.next_to(brace, UP) - question.add_background_rectangle() - rect.save_state() - rect.stretch(0, 0) - - gdw_anim = Mobject.add_updater( - gdw, lambda m : m.set_width( - 2.0/(self.a_tracker.get_value()**(0.5)) - ).move_to(rect) - ) - - self.add(rect, brace, question) - - yes = TextMobject("Yes").set_color(GREEN) - no = TextMobject("No").set_color(RED) - for word in yes, no: - word.next_to(rect, DOWN) - # word.add_background_rectangle() - answer = VGroup() - def update_answer(answer): - px = particle.mobject[0].get_center()[0] - lx = rect.get_left()[0] - rx = rect.get_right()[0] - if lx < px < rx: - answer.submobjects = [yes] - else: - answer.submobjects = [no] - answer_anim = Mobject.add_updater(answer, update_answer) - - self.add(gdw_anim, particle) - self.play( - GrowFromCenter(brace), - rect.restore, - Write(question) - ) - self.wait() - self.add(answer_anim) - self.wait(4) - self.add_foreground_mobjects(answer, particle.mobject) - - self.question_group = VGroup(question, brace) - self.particle = particle - self.rect = rect - - def show_probability_distribution(self): - axes = self.axes - wave = self.wave - bell_curve = self.bell_curve - question_group = self.question_group - gdw = self.particle.gaussian_distribution_wrapper - rect = self.rect - - v_lines = VGroup(*[ - DashedLine(ORIGIN, 3*UP).move_to(point, DOWN) - for point in (rect.get_left(), rect.get_right()) - ]) - - self.play( - FadeIn(VGroup(axes, wave)), - question_group.next_to, v_lines, UP, {"buff" : 0}, - *list(map(ShowCreation, v_lines)) - ) - self.wait(10) - - def show_concentration_of_the_wave(self): - self.play( - self.a_tracker.set_value, 5, - self.wave_update_animation, - ) - self.wait(10) - -class HeisenbergCommentTodos(TODOStub): - CONFIG = { - "message" : "Insert position-momentum trade-off" - } - -class HeisenbergPetPeeve(PiCreatureScene): - def construct(self): - morty, other = self.pi_creatures - particle = ProbabalisticDotCloud() - gdw = particle.gaussian_distribution_wrapper - gdw.to_edge(UP, buff = LARGE_BUFF) - gdw.stretch_to_fit_width(3) - gdw.rotate(3*DEGREES) - - self.add(particle) - self.wait() - self.play(PiCreatureSays( - other, """ - According to the H.U.P., the \\\\ - universe is unknowable! - """, - target_mode = "speaking" - )) - self.play(morty.change, "angry") - self.wait(3) - self.play( - PiCreatureSays( - morty, "Well, yes and no", - target_mode = "sassy", - ), - RemovePiCreatureBubble( - other, target_mode = "erm" - ) - ) - self.wait(4) - - ### - def create_pi_creatures(self): - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - other = PiCreature(color = MAROON_E) - other.to_edge(DOWN).shift(3*LEFT) - return VGroup(morty, other) - -class OneLevelDeeper(Scene): - def construct(self): - heisenberg = ImageMobject("Heisenberg") - heisenberg.to_corner(UP+LEFT) - self.add(heisenberg) - - hup_words = TextMobject("Heisenberg's uncertainty principle") - wave_words = TextMobject("Interpretation of the wave function") - arrow = Vector(UP) - group = VGroup(hup_words, arrow, wave_words) - group.arrange(DOWN) - - randomness = ProbabalisticMobjectCloud( - TextMobject("Randomness"), - n_copies = 5, - time_per_change = 0.05 - ) - gdw = randomness.gaussian_distribution_wrapper - gdw.rotate(TAU/4) - gdw.set_height(1) - # gdw.set_width(4) - gdw.next_to(hup_words, UP, MED_LARGE_BUFF) - - self.add(hup_words, randomness) - self.wait(4) - self.play( - FadeIn(wave_words), - GrowArrow(arrow), - ApplyMethod( - gdw.next_to, wave_words, DOWN, MED_LARGE_BUFF, - path_arc = TAU/2, - ) - ) - self.wait(6) - -class BetterTranslation(TeacherStudentsScene): - def construct(self): - english_term = TextMobject("Uncertainty principle") - german_word = TextMobject("Unschärferelation") - translation = TextMobject("Unsharpness relation") - - to_german_words = TextMobject("In German") - to_german_words.scale(0.5) - to_german_arrow = Vector(DOWN, color = WHITE, buff = SMALL_BUFF) - to_german_words.next_to(to_german_arrow, RIGHT, SMALL_BUFF) - to_german_words.set_color(YELLOW) - to_german_group = VGroup(to_german_arrow, to_german_words) - - translation_words = TextMobject("Literal translation") - translation_words.scale(0.5) - translation_arrow = Vector(DOWN, color = WHITE, buff = SMALL_BUFF) - translation_words.next_to(translation_arrow, LEFT, SMALL_BUFF) - translation_words.set_color(YELLOW) - translation_group = VGroup(translation_arrow, translation_words) - - english_term.next_to(self.teacher, UP+LEFT) - english_term.save_state() - english_term.shift(DOWN) - english_term.fade(1) - self.play( - english_term.restore, - self.get_student_changes(*["pondering"]*3) - ) - self.wait() - - german_word.move_to(english_term) - to_german_group.next_to( - german_word, UP, - submobject_to_align = to_german_arrow - ) - self.play( - self.teacher.change, "raise_right_hand", - english_term.next_to, to_german_arrow, UP - ) - self.play( - GrowArrow(to_german_arrow), - FadeIn(to_german_words), - ReplacementTransform( - english_term.copy().fade(1), - german_word - ) - ) - self.wait(2) - - group = VGroup(english_term, to_german_group, german_word) - translation.move_to(german_word) - translation_group.next_to( - german_word, UP, - submobject_to_align = translation_arrow - ) - self.play( - group.next_to, translation_arrow, UP, - ) - self.play( - GrowArrow(translation_arrow), - FadeIn(translation_words), - ReplacementTransform( - german_word.copy().fade(1), - translation - ) - ) - self.change_student_modes(*["happy"]*3) - self.wait(2) - -class ThinkOfHeisenbergUncertainty(PiCreatureScene): - def construct(self): - morty = self.pi_creature - morty.center().to_edge(DOWN).shift(LEFT) - - dot_cloud = ProbabalisticDotCloud() - dot_gdw = dot_cloud.gaussian_distribution_wrapper - dot_gdw.set_width(1) - dot_gdw.rotate(TAU/8) - dot_gdw.move_to(FRAME_X_RADIUS*RIGHT/2), - - vector_cloud = ProbabalisticVectorCloud( - center_func = dot_gdw.get_center - ) - vector_gdw = vector_cloud.gaussian_distribution_wrapper - vector_gdw.set_width(0.1) - vector_gdw.rotate(TAU/8) - vector_gdw.next_to(dot_gdw, UP+LEFT, LARGE_BUFF) - - time_tracker = ValueTracker(0) - self.add() - freq = 1 - continual_anims = [ - always_shift(time_tracker, direction = RIGHT, rate = 1), - Mobject.add_updater( - dot_gdw, - lambda d : d.set_width( - (np.cos(freq*time_tracker.get_value()) + 1.1)/2 - ) - ), - Mobject.add_updater( - vector_gdw, - lambda d : d.set_width( - (-np.cos(freq*time_tracker.get_value()) + 1.1)/2 - ) - ), - dot_cloud, vector_cloud - ] - self.add(*continual_anims) - - position, momentum, time, frequency = list(map(TextMobject, [ - "Position", "Momentum", "Time", "Frequency" - ])) - VGroup(position, time).set_color(BLUE) - VGroup(momentum, frequency).set_color(YELLOW) - groups = VGroup() - for m1, m2 in (position, momentum), (time, frequency): - arrow = TexMobject("\\updownarrow").scale(1.5) - group = VGroup(m1, arrow, m2) - group.arrange(DOWN) - lp, rp = parens = TexMobject("\\big(\\big)") - parens.stretch(1.5, 1) - parens.match_height(group) - lp.next_to(group, LEFT, buff = SMALL_BUFF) - rp.next_to(group, RIGHT, buff = SMALL_BUFF) - group.add(parens) - groups.add(group) - arrow = TexMobject("\\Leftrightarrow").scale(2) - groups.submobjects.insert(1, arrow) - groups.arrange(RIGHT) - groups.next_to(morty, UP+RIGHT, LARGE_BUFF) - groups.shift_onto_screen() - - - self.play(PiCreatureBubbleIntroduction( - morty, "Heisenberg \\\\ uncertainty \\\\ principle", - bubble_class = ThoughtBubble, - bubble_kwargs = {"height" : 4, "width" : 4, "direction" : RIGHT}, - target_mode = "pondering" - )) - self.wait() - self.play(morty.change, "confused", dot_gdw) - self.wait(10) - self.play( - ApplyMethod( - VGroup(dot_gdw, vector_gdw ).shift, - FRAME_X_RADIUS*RIGHT, - rate_func = running_start - ) - ) - self.remove(*continual_anims) - self.play( - morty.change, "raise_left_hand", groups, - FadeIn( - groups, - lag_ratio = 0.5, - run_time = 3, - ) - ) - self.wait(2) - -# End things - -class PatreonMention(PatreonThanks): - def construct(self): - morty = Mortimer() - morty.next_to(ORIGIN, DOWN) - - patreon_logo = PatreonLogo() - patreon_logo.to_edge(UP) - - thank_you = TextMobject("Thank you.") - thank_you.next_to(patreon_logo, DOWN) - - self.play( - DrawBorderThenFill(patreon_logo), - morty.change, "gracious" - ) - self.play(Write(thank_you)) - self.wait(3) - -class Promotion(PiCreatureScene): - CONFIG = { - "camera_class" : ThreeDCamera, - "seconds_to_blink" : 5, - } - def construct(self): - aops_logo = AoPSLogo() - aops_logo.next_to(self.pi_creature, UP+LEFT) - url = TextMobject( - "AoPS.com/", "3b1b", - arg_separator = "" - ) - url.to_corner(UP+LEFT) - url_rect = Rectangle(color = BLUE) - url_rect.replace( - url.get_part_by_tex("3b1b"), - stretch = True - ) - - url_rect.stretch_in_place(1.1, dim = 1) - - rect = Rectangle(height = 9, width = 16) - rect.set_height(4.5) - rect.next_to(url, DOWN) - rect.to_edge(LEFT) - rect.set_stroke(width = 0) - mathy = Mathematician() - mathy.flip() - mathy.to_corner(DOWN+RIGHT) - morty = self.pi_creature - morty.save_state() - book = ImageMobject("AoPS_volume_2") - book.set_height(2) - book.next_to(mathy, UP+LEFT).shift(MED_LARGE_BUFF*LEFT) - mathy.get_center = mathy.get_top - - words = TextMobject(""" - Interested in working for \\\\ - one of my favorite math\\\\ - education companies? - """, alignment = "") - words.to_edge(UP) - - arrow = Arrow( - aops_logo.get_top(), - morty.get_top(), - path_arc = -0.4*TAU, - stroke_width = 5, - tip_length = 0.5, - ) - arrow.tip.shift(SMALL_BUFF*DOWN) - - self.add(words) - self.play( - self.pi_creature.change_mode, "raise_right_hand", - *[ - DrawBorderThenFill( - submob, - run_time = 2, - rate_func = squish_rate_func(double_smooth, a, a+0.5) - ) - for submob, a in zip(aops_logo, np.linspace(0, 0.5, len(aops_logo))) - ] - ) - self.play( - words.scale, 0.75, - words.next_to, url, DOWN, LARGE_BUFF, - words.shift_onto_screen, - Write(url), - ) - self.wait(2) - self.play( - LaggedStartMap( - ApplyFunction, aops_logo, - lambda mob : (lambda m : m.shift(0.2*UP).set_color(YELLOW), mob), - rate_func = there_and_back, - run_time = 1, - ), - morty.change, "thinking" - ) - self.wait() - self.play(ShowCreation(arrow)) - self.play(FadeOut(arrow)) - self.wait() - - # To teacher - self.play( - morty.change_mode, "plain", - morty.flip, - morty.scale, 0.7, - morty.next_to, mathy, LEFT, LARGE_BUFF, - morty.to_edge, DOWN, - FadeIn(mathy), - ) - self.play( - PiCreatureSays( - mathy, "", - bubble_kwargs = {"width" : 5}, - look_at_arg = morty.eyes, - ), - morty.change, "happy", - aops_logo.shift, 1.5*UP + 0.5*RIGHT - ) - self.play(Blink(mathy)) - self.wait() - self.play( - RemovePiCreatureBubble( - mathy, target_mode = "raise_right_hand" - ), - aops_logo.to_corner, UP+RIGHT, - aops_logo.shift, MED_SMALL_BUFF*DOWN, - GrowFromPoint(book, mathy.get_corner(UP+LEFT)), - ) - self.play(morty.change, "pondering", book) - self.wait(3) - self.play(Blink(mathy)) - self.wait() - self.play( - Animation( - BackgroundRectangle(book, fill_opacity = 1), - remover = True - ), - FadeOut(book), - ) - print(self.num_plays) - self.play( - FadeOut(words), - ShowCreation(rect), - morty.restore, - morty.change, "happy", rect, - FadeOut(mathy), - ) - self.wait(10) - self.play(ShowCreation(url_rect)) - self.play( - FadeOut(url_rect), - url.get_part_by_tex("3b1b").set_color, BLUE, - ) - self.wait(15) - -class PuzzleStatement(Scene): - def construct(self): - aops_logo = AoPSLogo() - url = TextMobject("AoPS.com/3b1b") - url.next_to(aops_logo, UP) - group = VGroup(aops_logo, url) - group.to_edge(UP) - self.add(group) - - words = TextMobject(""" - AoPS must choose one of 20 people to send to a - tug-of-war tournament. We don't care who we send, - as long as we don't send our weakest person. \\\\ \\\\ - - Each person has a different strength, but we don't know - those strengths. We get 10 intramural 10-on-10 matches - to determine who we send. Can we make sure we don't send - the weakest person? - """, alignment = "") - words.set_width(FRAME_WIDTH - 2) - words.next_to(group, DOWN, LARGE_BUFF) - self.play(LaggedStartMap(FadeIn, words, run_time = 5, lag_ratio = 0.2)) - self.wait(2) - -class UncertaintyEndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons" : [ - "CrypticSwarm", - "Ali Yahya", - "Juan Benet", - "Markus Persson", - "Damion Kistler", - "Burt Humburg", - "Yu Jun", - "Dave Nicponski", - "Kaustuv DeBiswas", - "Joseph John Cox", - "Luc Ritchie", - "Achille Brighton", - "Rish Kundalia", - "Yana Chernobilsky", - "Shìmín Kuang", - "Mathew Bramson", - "Jerry Ling", - "Mustafa Mahdi", - "Meshal Alshammari", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Robert Teed", - "Samantha D. Suplee", - "Mark Govea", - "John Haley", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Desmos ", - "Boris Veselinovich", - "Ryan Dahl", - "Ripta Pasay", - "Eric Lavault", - "Randall Hunt", - "Andrew Busey", - "Mads Elvheim", - "Tianyu Ge", - "Awoo", - "Dr. David G. Stork", - "Linh Tran", - "Jason Hise", - "Bernd Sing", - "James H. Park", - "Ankalagon ", - "Mathias Jansson", - "David Clark", - "Ted Suzman", - "Eric Chow", - "Michael Gardner", - "David Kedmey", - "Jonathan Eppele", - "Clark Gaebel", - "Jordan Scales", - "Ryan Atallah", - "supershabam ", - "1stViewMaths", - "Jacob Magnuson", - "Chloe Zhou", - "Ross Garber", - "Thomas Tarler", - "Isak Hietala", - "Egor Gumenuk", - "Waleed Hamied", - "Oliver Steele", - "Yaw Etse", - "David B", - "Delton Ding", - "James Thornton", - "Felix Tripier", - "Arthur Zey", - "George Chiesa", - "Norton Wang", - "Kevin Le", - "Alexander Feldman", - "David MacCumber", - "Jacob Kohl", - "Frank Secilia", - "George John", - "Akash Kumar", - "Britt Selvitelle", - "Jonathan Wilson", - "Michael Kunze", - "Giovanni Filippi", - "Eric Younge", - "Prasant Jagannath", - "Andrejs olins", - "Cody Brocious", - ], - } - -class Thumbnail(Scene): - def construct(self): - uncertainty_principle = TextMobject("Uncertainty \\\\", "principle") - uncertainty_principle[1].shift(SMALL_BUFF*UP) - quantum = TextMobject("Quantum") - VGroup(uncertainty_principle, quantum).scale(2.5) - uncertainty_principle.to_edge(UP, MED_LARGE_BUFF) - quantum.to_edge(DOWN, MED_LARGE_BUFF) - - arrow = TexMobject("\\Downarrow") - arrow.scale(4) - arrow.move_to(Line( - uncertainty_principle.get_bottom(), - quantum.get_top(), - )) - - cross = Cross(arrow) - cross.set_stroke(RED, 20) - - is_word, not_word = is_not = TextMobject("is", "\\emph{NOT}") - is_not.scale(3) - is_word.move_to(arrow) - # is_word.shift(0.6*UP) - not_word.set_color(RED) - not_word.set_stroke(RED, 3) - not_word.rotate(10*DEGREES, about_edge = DOWN+LEFT) - not_word.next_to(is_word, DOWN, 0.1*SMALL_BUFF) - - dot_cloud = ProbabalisticDotCloud( - n_copies = 1000, - ) - dot_gdw = dot_cloud.gaussian_distribution_wrapper - # dot_gdw.rotate(3*DEGREES) - dot_gdw.rotate(25*DEGREES) - # dot_gdw.scale(2) - dot_gdw.scale(2) - # dot_gdw.move_to(quantum.get_bottom()+SMALL_BUFF*DOWN) - dot_gdw.move_to(quantum) - - - - def get_func(a): - return lambda t : 0.5*np.exp(-a*t**2)*np.cos(TAU*t) - axes = Axes( - x_min = -6, x_max = 6, - x_axis_config = {"unit_size" : 0.25} - ) - graphs = VGroup(*[ - axes.get_graph(get_func(a)) - for a in (10, 3, 1, 0.3, 0.1,) - ]) - graphs.arrange(DOWN, buff = 0.6) - graphs.to_corner(UP+LEFT) - graphs.set_color_by_gradient(BLUE_B, BLUE_D) - - frequency_axes = Axes( - x_min = 0, x_max = 2, - x_axis_config = {"unit_size" : 1} - ) - fourier_graphs = VGroup(*[ - get_fourier_graph( - frequency_axes, graph.underlying_function, - t_min = -10, t_max = 10, - ) - for graph in graphs - ]) - for graph, fourier_graph in zip(graphs, fourier_graphs): - fourier_graph.pointwise_become_partial(fourier_graph, 0.02, 0.06) - fourier_graph.scale(3) - fourier_graph.stretch(3, 1) - fourier_graph.move_to(graph) - fourier_graph.to_edge(RIGHT) - - self.add(graphs, fourier_graphs) - - - self.add(dot_cloud) - self.add( - uncertainty_principle, quantum, - ) - self.add(arrow, cross) - # self.add(is_word) - # self.add(is_not) - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/wallis.py b/from_3b1b/old/wallis.py deleted file mode 100644 index 890e0c30..00000000 --- a/from_3b1b/old/wallis.py +++ /dev/null @@ -1,5307 +0,0 @@ -# -*- coding: utf-8 -*- - - -from manimlib.imports import * -from once_useful_constructs.light import AmbientLight -from once_useful_constructs.light import Lighthouse -from once_useful_constructs.light import SwitchOn -from functools import reduce -# from once_useful_constructs.light import LightSource - -PRODUCT_COLOR = BLUE -DEFAULT_OPACITY_FUNCTION = inverse_power_law(1, 1.5, 1, 4) -CHEAP_AMBIENT_LIGHT_CONFIG = { - "num_levels": 5, - "radius": 0.25, - "opacity_function": DEFAULT_OPACITY_FUNCTION, -} -HIGHT_QUALITY_AMBIENT_LIGHT_CONFIG = { - "opacity_function": DEFAULT_OPACITY_FUNCTION, - "num_levels": 100, - "radius": 5, - "max_opacity": 0.8, - "color": PRODUCT_COLOR, -} - - -def get_chord_f_label(chord, arg="f", direction=DOWN): - chord_f = TextMobject("Chord(", "$%s$" % arg, ")", arg_separator="") - chord_f.set_color_by_tex("$%s$" % arg, YELLOW) - chord_f.add_background_rectangle() - chord_f.next_to(chord.get_center(), direction, SMALL_BUFF) - angle = ((chord.get_angle() + TAU / 2) % TAU) - TAU / 2 - if np.abs(angle) > TAU / 4: - angle += TAU / 2 - chord_f.rotate(angle, about_point=chord.get_center()) - chord_f.angle = angle - return chord_f - - -class WallisNumeratorDenominatorGenerator(object): - def __init__(self): - self.n = 0 - - def __iter__(self): - return self - - def __next__(self): - return next(self) - - def __next__(self): - n = self.n - self.n += 1 - if n % 2 == 0: - return (n + 2, n + 1) - else: - return (n + 1, n + 2) - - -def get_wallis_product(n_terms=6, show_result=True): - tex_mob_args = [] - nd_generator = WallisNumeratorDenominatorGenerator() - for x in range(n_terms): - numerator, denominator = next(nd_generator) - tex_mob_args += [ - "{%d" % numerator, "\\over", "%d}" % denominator, "\\cdot" - ] - tex_mob_args[-1] = "\\cdots" - if show_result: - tex_mob_args += ["=", "{\\pi", "\\over", "2}"] - - result = TexMobject(*tex_mob_args) - return result - - -def get_wallis_product_numerical_terms(n_terms=20): - result = [] - nd_generator = WallisNumeratorDenominatorGenerator() - for x in range(n_terms): - n, d = next(nd_generator) - result.append(float(n) / d) - return result - - -# Scenes - -class Introduction(Scene): - def construct(self): - n_terms = 10 - - number_line = NumberLine( - x_min=0, - x_max=2, - unit_size=5, - tick_frequency=0.25, - numbers_with_elongated_ticks=[0, 1, 2], - color=LIGHT_GREY, - ) - number_line.add_numbers() - number_line.move_to(DOWN) - - numerical_terms = get_wallis_product_numerical_terms(400) - partial_products = np.cumprod(numerical_terms) - curr_product = partial_products[0] - - arrow = Vector(DOWN, color=YELLOW) - - def get_arrow_update(): - return ApplyFunction( - lambda mob: mob.next_to( - number_line.number_to_point(curr_product), - UP, SMALL_BUFF - ), - arrow, - ) - get_arrow_update().update(1) - decimal = DecimalNumber( - curr_product, num_decimal_places=5, show_ellipsis=True) - decimal.next_to(arrow, UP, SMALL_BUFF, submobject_to_align=decimal[:5]) - decimal_anim = ChangingDecimal( - decimal, - lambda a: number_line.point_to_number(arrow.get_center()), - tracked_mobject=arrow - ) - - product_mob = get_wallis_product(n_terms) - product_mob.to_edge(UP) - - rects = VGroup(*[ - SurroundingRectangle(product_mob[:n]) - for n in list(range(3, 4 * n_terms, 4)) + [4 * n_terms] - ]) - rect = rects[0].copy() - - pi_halves_arrow = Vector(UP, color=BLUE) - pi_halves_arrow.next_to( - number_line.number_to_point(np.pi / 2), DOWN, SMALL_BUFF - ) - pi_halves_term = TexMobject("\\pi / 2") - pi_halves_term.next_to(pi_halves_arrow, DOWN) - - self.add(product_mob, number_line, rect, arrow, decimal) - self.add(pi_halves_arrow, pi_halves_term) - for n in range(1, len(rects)): - curr_product = partial_products[n] - self.play( - get_arrow_update(), - decimal_anim, - Transform(rect, rects[n]), - run_time=0.5 - ) - self.wait(0.5) - - for n in range(len(rects), len(numerical_terms), 31): - curr_product = partial_products[n] - self.play( - get_arrow_update(), - decimal_anim, - run_time=0.25 - ) - curr_product = np.pi / 2 - self.play( - get_arrow_update(), - decimal_anim, - run_time=0.5 - ) - self.wait() - - -class TableOfContents(Scene): - def construct(self): - topics = VGroup( - TextMobject("The setup"), - TextMobject("Circle geometry with complex polynomials"), - TextMobject("Proof of the Wallis product"), - TextMobject("Formalities not discussed"), - TextMobject( - "Generalizing this proof to get \\\\ the product formula for sine"), - ) - for topic in topics: - dot = Dot(color=BLUE) - dot.next_to(topic, LEFT) - topic.add(dot) - topics.arrange( - DOWN, aligned_edge=LEFT, buff=LARGE_BUFF - ) - self.add(topics) - self.wait() - for i in range(len(topics)): - self.play( - topics[i + 1:].set_fill, {"opacity": 0.25}, - topics[:i].set_fill, {"opacity": 0.25}, - topics[i].set_fill, {"opacity": 1}, - ) - self.wait(2) - - -class SourcesOfOriginality(TeacherStudentsScene): - def construct(self): - self.mention_excitement() - self.break_down_value_of_math_presentations() - self.where_we_fit_in() - - def mention_excitement(self): - self.teacher_says( - "This one came about \\\\ a bit differently...", - target_mode="speaking", - run_time=1 - ) - self.change_student_modes("happy", "confused", "erm") - self.wait(2) - - def break_down_value_of_math_presentations(self): - title = TextMobject("The value of a", "math", "presentation") - title.to_edge(UP, buff=MED_SMALL_BUFF) - value_of, math, presentation = title - - MATH_COLOR = YELLOW - COMMUNICATION_COLOR = BLUE - - big_rect = self.big_rect = Rectangle( - width=title.get_width() + 2 * MED_LARGE_BUFF, - height=3.5, - color=WHITE - ) - big_rect.next_to(title, DOWN) - - left_rect, right_rect = self.left_rect, self.right_rect = [ - Rectangle( - height=big_rect.get_height() - 2 * SMALL_BUFF, - width=0.5 * big_rect.get_width() - 2 * SMALL_BUFF, - color=color - ) - for color in (MATH_COLOR, COMMUNICATION_COLOR) - ] - right_rect.flip() - left_rect.next_to(big_rect.get_left(), RIGHT, SMALL_BUFF) - right_rect.next_to(big_rect.get_right(), LEFT, SMALL_BUFF) - - underlying_math = TextMobject("Underlying", "math") - underlying_math.set_color(MATH_COLOR) - communication = TextMobject("Communication") - communication.set_color(COMMUNICATION_COLOR) - VGroup(underlying_math, communication).scale(0.75) - underlying_math.next_to(left_rect.get_top(), DOWN, SMALL_BUFF) - communication.next_to(right_rect.get_top(), DOWN, SMALL_BUFF) - - formula = TexMobject( - "\\sum_{n = 1}^\\infty \\frac{1}{n^2} = \\frac{\\pi^2}{2}", - ) - formula.scale(0.75) - formula.next_to(underlying_math, DOWN) - - based_on_wastlund = TextMobject( - "Previous video based on\\\\", - "a paper by Johan W\\\"{a}stlund" - ) - based_on_wastlund.set_width( - left_rect.get_width() - MED_SMALL_BUFF) - based_on_wastlund.next_to(formula, DOWN, MED_LARGE_BUFF) - - communication_parts = TextMobject("Visuals, narrative, etc.") - communication_parts.scale(0.75) - communication_parts.next_to(communication, DOWN, MED_LARGE_BUFF) - lighthouse = Lighthouse(height=0.5) - lighthouse.next_to(communication_parts, DOWN, LARGE_BUFF) - ambient_light = AmbientLight( - num_levels=200, - radius=5, - opacity_function=DEFAULT_OPACITY_FUNCTION, - ) - ambient_light.move_source_to(lighthouse.get_top()) - - big_rect.save_state() - big_rect.stretch(0, 1) - big_rect.stretch(0.5, 0) - big_rect.move_to(title) - - self.play( - FadeInFromDown(title), - RemovePiCreatureBubble( - self.teacher, - target_mode="raise_right_hand", - look_at_arg=title, - ), - self.get_student_changes( - *["pondering"] * 3, - look_at_arg=title - ) - ) - self.play(big_rect.restore) - self.play(*list(map(ShowCreation, [left_rect, right_rect]))) - self.wait() - self.play( - math.match_color, left_rect, - ReplacementTransform(VGroup(math.copy()), underlying_math) - ) - self.play(FadeIn(formula)) - self.play( - presentation.match_color, right_rect, - ReplacementTransform(presentation.copy(), communication) - ) - self.play( - FadeIn(communication_parts), - FadeIn(lighthouse), - SwitchOn(ambient_light) - ) - self.play(self.teacher.change, "tease") - self.wait() - - self.play( - FadeIn(based_on_wastlund), - self.get_student_changes( - "sassy", "erm", "plain", - look_at_arg=based_on_wastlund - ), - ) - self.wait() - - self.math_content = VGroup(formula, based_on_wastlund) - - def where_we_fit_in(self): - right_rect = self.right_rect - left_rect = self.left_rect - - points = [ - right_rect.get_left() + SMALL_BUFF * RIGHT, - right_rect.get_corner(UL), - right_rect.get_corner(UR), - right_rect.get_right() + SMALL_BUFF * LEFT, - right_rect.get_corner(DR), - right_rect.get_bottom() + SMALL_BUFF * UP, - right_rect.get_corner(DL), - ] - added_points = [ - left_rect.get_bottom(), - left_rect.get_corner(DL), - left_rect.get_corner(DL) + 1.25 * UP, - left_rect.get_bottom() + 1.25 * UP, - ] - - blob1, blob2 = VMobject(), VMobject() - blob1.set_points_smoothly(points + [points[0]]) - blob1.append_points(3 * len(added_points) * [points[0]]) - blob2.set_points_smoothly(points + added_points + [points[0]]) - for blob in blob1, blob2: - blob.set_stroke(width=0) - blob.set_fill(BLUE, opacity=0.5) - - our_contribution = TextMobject("Our target \\\\ contribution") - our_contribution.scale(0.75) - our_contribution.to_corner(UR) - arrow = Arrow( - our_contribution.get_bottom(), - right_rect.get_right() + MED_LARGE_BUFF * LEFT, - color=BLUE - ) - - wallis_product = get_wallis_product(n_terms=4) - wallis_product.set_width( - left_rect.get_width() - 2 * MED_LARGE_BUFF) - wallis_product.move_to(self.math_content, UP) - wallis_product_name = TextMobject("``Wallis product''") - wallis_product_name.scale(0.75) - wallis_product_name.next_to(wallis_product, DOWN, MED_SMALL_BUFF) - - new_proof = TextMobject("New proof") - new_proof.next_to(wallis_product_name, DOWN, MED_LARGE_BUFF) - - self.play( - DrawBorderThenFill(blob1), - Write(our_contribution), - GrowArrow(arrow), - ) - self.wait(2) - self.play(FadeOut(self.math_content)) - self.play( - FadeIn(wallis_product), - Write(wallis_product_name, run_time=1) - ) - self.wait(2) - self.play( - Transform(blob1, blob2, path_arc=-90 * DEGREES), - FadeIn(new_proof), - self.teacher.change, "hooray", - ) - self.change_all_student_modes("hooray", look_at_arg=new_proof) - self.wait(5) - - -class Six(Scene): - def construct(self): - six = TexMobject("6") - six.add_background_rectangle(opacity = 1) - six.background_rectangle.stretch(1.5, 0) - six.set_height(7) - self.add(six) - - - -class SridharWatchingScene(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs": { - "color": YELLOW_E, - "flip_at_start": False, - }, - } - - def construct(self): - laptop = Laptop() - laptop.scale(1.8) - laptop.to_corner(DR) - sridhar = self.pi_creature - sridhar.next_to(laptop, LEFT, SMALL_BUFF, DOWN) - bubble = ThoughtBubble() - bubble.flip() - bubble.pin_to(sridhar) - - basel = TexMobject( - "{1", "\\over", "1^2}", "+" - "{1", "\\over", "2^2}", "+" - "{1", "\\over", "3^2}", "+", "\\cdots", - "= \\frac{\\pi^2}{6}" - ) - wallis = get_wallis_product(n_terms=4) - VGroup(basel, wallis).scale(0.7) - basel.move_to(bubble.get_bubble_center()) - basel.to_edge(UP, buff=MED_SMALL_BUFF) - wallis.next_to(basel, DOWN, buff=0.75) - arrow = TexMobject("\\updownarrow") - arrow.move_to(VGroup(basel, wallis)) - basel.set_color(YELLOW) - wallis.set_color(BLUE) - - self.play(LaggedStartMap(DrawBorderThenFill, laptop)) - self.play(sridhar.change, "pondering", laptop.screen) - self.wait() - self.play(ShowCreation(bubble)) - self.play(LaggedStartMap(FadeIn, basel)) - self.play( - ReplacementTransform(basel.copy(), wallis), - GrowFromPoint(arrow, arrow.get_top()) - ) - self.wait(4) - self.play(sridhar.change, "thinking", wallis) - self.wait(4) - self.play(LaggedStartMap( - ApplyFunction, - VGroup(*list(laptop) + [bubble, basel, arrow, wallis, sridhar]), - lambda mob: (lambda m: m.set_color(BLACK).fade(1).scale(0.8), mob), - run_time=3, - )) - - -class ShowProduct(Scene): - def construct(self): - self.setup_axes() - self.setup_wallis_product() - self.show_larger_terms() - self.show_smaller_terms() - self.interleave_terms() - self.show_answer() - - def setup_axes(self): - axes = self.axes = self.get_axes(unit_size=0.75) - self.add(axes) - - def setup_wallis_product(self): - full_wallis_product = get_wallis_product(n_terms=16, show_result=False) - wallis_product_parts = VGroup(*[ - full_wallis_product[i:i + 4] - for i in range(0, len(full_wallis_product), 4) - ]) - - larger_parts = self.larger_parts = wallis_product_parts[::2] - larger_parts.set_color(YELLOW) - dots = TexMobject("\\cdots") - dots.move_to(larger_parts[-1][-1], LEFT) - larger_parts[-1][-1].submobjects = dots.submobjects - - smaller_parts = self.smaller_parts = wallis_product_parts[1::2] - smaller_parts.set_color(BLUE) - - for parts in larger_parts, smaller_parts: - parts.arrange(RIGHT, buff=2 * SMALL_BUFF) - # Move around the dots - for part1, part2 in zip(parts, parts[1:]): - dot = part1.submobjects.pop(-1) - part2.add_to_back(dot) - - larger_parts.to_edge(UP) - smaller_parts.next_to(larger_parts, DOWN, LARGE_BUFF) - - self.wallis_product_terms = get_wallis_product_numerical_terms(40) - - def show_larger_terms(self): - axes = self.axes - parts = self.larger_parts - terms = self.wallis_product_terms[::2] - partial_products = np.cumprod(terms) - - dots = VGroup(*[ - Dot(axes.coords_to_point(n + 1, prod)) - for n, prod in enumerate(partial_products) - ]) - dots.match_color(parts) - lines = VGroup(*[ - Line(d1.get_center(), d2.get_center()) - for d1, d2 in zip(dots, dots[1:]) - ]) - - braces = VGroup(*[ - Brace(parts[:n + 1], DOWN) - for n in range(len(parts)) - ]) - - brace = braces[0].copy() - decimal = DecimalNumber(partial_products[0], num_decimal_places=4) - decimal.next_to(brace, DOWN) - - self.add(brace, decimal, dots[0], parts[0]) - tuples = list(zip(parts[1:], lines, dots[1:], partial_products[1:], braces[1:])) - for part, line, dot, prod, new_brace in tuples: - self.play( - FadeIn(part), - Transform(brace, new_brace), - ChangeDecimalToValue( - decimal, prod, - position_update_func=lambda m: m.next_to(brace, DOWN) - ), - ShowCreation(line), - GrowFromCenter(dot, rate_func=squish_rate_func(smooth, 0.5, 1)), - run_time=0.5, - ) - self.wait(0.5) - N = len(parts) - self.play( - LaggedStartMap(ShowCreation, lines[N - 1:], lag_ratio=0.2), - LaggedStartMap(FadeIn, dots[N:], lag_ratio=0.2), - brace.stretch, 1.2, 0, {"about_edge": LEFT}, - ChangeDecimalToValue( - decimal, partial_products[-1], - position_update_func=lambda m: m.next_to(brace, DOWN) - ), - run_time=4, - rate_func=linear, - ) - self.play( - FadeOut(brace), - ChangeDecimalToValue( - decimal, partial_products[-1] + 2, - position_update_func=lambda m: m.next_to(brace, DOWN) - ), - UpdateFromFunc( - decimal, lambda d: d.shift(self.frame_duration * RIGHT) - ), - UpdateFromAlphaFunc( - decimal, lambda d, a: d.set_fill(opacity=1 - a) - ), - ) - self.remove(decimal) - - self.graph_to_remove = VGroup(dots, lines) - - def show_smaller_terms(self): - larger_parts = self.larger_parts - larger_parts.save_state() - larger_parts_mover = larger_parts.copy() - larger_parts.fade(0.5) - - smaller_parts = self.smaller_parts - for parts in larger_parts_mover, smaller_parts: - parts.denominators = VGroup( - parts[0][2], - *[part[3] for part in parts[1:]] - ) - vect = op.sub( - smaller_parts.denominators[1].get_left(), - smaller_parts.denominators[0].get_left(), - ) - smaller_parts.denominators.shift(vect) - - self.play( - larger_parts_mover.move_to, smaller_parts, LEFT, - FadeOut(self.graph_to_remove) - ) - self.play( - larger_parts_mover.denominators.shift, -vect, - smaller_parts.denominators.shift, -vect, - UpdateFromAlphaFunc( - larger_parts_mover, - lambda m, a: m.set_fill(opacity=1 - a), - remover=True - ), - UpdateFromAlphaFunc( - smaller_parts, - lambda m, a: m.set_fill(opacity=a) - ), - ) - - # Rescale axes - new_axes = self.get_axes(unit_size=1.5) - self.play(ReplacementTransform(self.axes, new_axes)) - axes = self.axes = new_axes - - # Show graph - terms = self.wallis_product_terms[1::2] - partial_products = np.cumprod(terms)[:15] - - dots = VGroup(*[ - Dot(axes.coords_to_point(n + 1, prod)) - for n, prod in enumerate(partial_products) - ]) - dots.match_color(smaller_parts) - lines = VGroup(*[ - Line(d1.get_center(), d2.get_center()) - for d1, d2 in zip(dots, dots[1:]) - ]) - - self.play( - ShowCreation(lines), - LaggedStartMap(FadeIn, dots, lag_ratio=0.1), - run_time=3, - rate_func=linear, - ) - self.wait(2) - self.play(FadeOut(VGroup(dots, lines))) - - def interleave_terms(self): - larger_parts = self.larger_parts - smaller_parts = self.smaller_parts - index = 6 - larger_parts.restore() - for parts in larger_parts, smaller_parts: - parts.prefix = parts[:index] - parts.suffix = parts[index:] - parts.prefix.generate_target() - larger_parts.fade(0.5) - full_product = VGroup(*it.chain( - *list(zip(larger_parts.prefix.target, smaller_parts.prefix.target)) - )) - for i, tex, vect in (0, "\\cdot", LEFT), (-1, "\\cdots", RIGHT): - part = smaller_parts.prefix.target[i] - dot = TexMobject(tex) - dot.match_color(part) - dot.next_to(part, vect, buff=2 * SMALL_BUFF) - part.add(dot) - full_product.arrange(RIGHT, buff=2 * SMALL_BUFF) - full_product.to_edge(UP) - - for parts in larger_parts, smaller_parts: - self.play( - MoveToTarget(parts.prefix), - FadeOut(parts.suffix) - ) - self.wait() - - # Dots and lines - # In poor form, this is modified copy-pasted from show_larger_terms - axes = self.axes - parts = full_product - terms = self.wallis_product_terms - partial_products = np.cumprod(terms) - partial_products_iter = iter(partial_products) - print(partial_products) - - dots = VGroup(*[ - Dot(axes.coords_to_point(n + 1, prod)) - for n, prod in enumerate(partial_products) - ]) - dots.set_color(GREEN) - lines = VGroup(*[ - Line(d1.get_center(), d2.get_center()) - for d1, d2 in zip(dots, dots[1:]) - ]) - - braces = VGroup(*[ - Brace(parts[:n + 1], DOWN) - for n in range(len(parts)) - ]) - - brace = braces[0].copy() - decimal = DecimalNumber(next(partial_products_iter), num_decimal_places=4) - decimal.next_to(brace, DOWN) - - self.play(*list(map(FadeIn, [brace, decimal, dots[0]]))) - tuples = list(zip(lines, dots[1:], braces[1:])) - for line, dot, new_brace in tuples: - self.play( - Transform(brace, new_brace), - ChangeDecimalToValue( - decimal, next(partial_products_iter), - position_update_func=lambda m: m.next_to(brace, DOWN) - ), - ShowCreation(line), - GrowFromCenter(dot, rate_func=squish_rate_func(smooth, 0.5, 1)), - run_time=0.5, - ) - self.wait(0.5) - - def get_decimal_anim(): - return ChangeDecimalToValue( - decimal, next(partial_products_iter), - run_time=1, - rate_func=squish_rate_func(smooth, 0, 0.5), - ) - - self.play( - FadeIn(lines[len(parts) - 1:]), - FadeIn(dots[len(parts):]), - get_decimal_anim() - ) - for x in range(3): - self.play(get_decimal_anim()) - - self.partial_product_decimal = decimal - self.get_decimal_anim = get_decimal_anim - - def show_answer(self): - decimal = self.partial_product_decimal - axes = self.axes - - pi_halves = TexMobject("{\\pi", "\\over", "2}") - pi_halves.scale(1.5) - pi_halves.move_to(decimal, UP) - - randy = Randolph(height=1.7) - randy.next_to(decimal, DL) - randy.change("confused") - randy.save_state() - randy.change("plain") - randy.fade(1) - - h_line = DashedLine( - axes.coords_to_point(0, np.pi / 2), - axes.coords_to_point(20, np.pi / 2), - color=RED - ) - - self.play( - ShowCreation(h_line), - randy.restore, - self.get_decimal_anim() - ) - self.play(Blink(randy), self.get_decimal_anim()) - self.play(self.get_decimal_anim()) - self.play( - self.get_decimal_anim(), - UpdateFromAlphaFunc( - decimal, - lambda m, a: m.set_fill(opacity=1 - a) - ), - ReplacementTransform(randy, pi_halves[0]), - Write(pi_halves[1:]), - ) - self.remove(decimal) - self.wait() - - # Helpers - - def get_axes(self, unit_size): - y_max = 7 - axes = Axes( - x_min=-1, - x_max=12.5, - y_min=-0.5, - y_max=y_max + 0.25, - y_axis_config={ - "unit_size": unit_size, - "numbers_with_elongated_ticks": list(range(1, y_max + 1)), - "tick_size": 0.05, - }, - ) - axes.shift(6 * LEFT + 3 * DOWN - axes.coords_to_point(0, 0)) - - axes.y_axis.label_direction = LEFT - axes.y_axis.add_numbers(*list(range(1, y_max + 1))) - return axes - - -class TeacherShowing(TeacherStudentsScene): - def construct(self): - screen = self.screen - screen.set_height(4) - screen.next_to(self.students, UP, MED_LARGE_BUFF, RIGHT) - self.play( - ShowCreation(screen), - self.teacher.change, "raise_right_hand", screen, - self.get_student_changes( - *["pondering"] * 3, - look_at_arg=screen - ) - ) - self.wait(5) - - -class DistanceProductScene(MovingCameraScene): - CONFIG = { - "ambient_light_config": HIGHT_QUALITY_AMBIENT_LIGHT_CONFIG, - "circle_color": BLUE, - "circle_radius": 3, - "num_lighthouses": 6, - "lighthouse_height": 0.5, - "ignored_lighthouse_indices": [], - "observer_config": { - "color": MAROON_B, - "mode": "pondering", - "height": 0.25, - "flip_at_start": True, - }, - "observer_fraction": 1.0 / 3, - "d_label_height": 0.35, - "numeric_distance_label_height": 0.25, - "default_product_column_top": FRAME_WIDTH * RIGHT / 4 + 1.5 * UP, - "include_lighthouses": True, - "include_distance_labels_background_rectangle": True, - } - - def setup(self): - super(DistanceProductScene, self).setup() - self.circle = Circle( - color=self.circle_color, - radius=self.circle_radius, - ) - - def get_circle_point_at_proportion(self, alpha): - radius = self.get_radius() - center = self.circle.get_center() - angle = alpha * TAU - unit_circle_point = np.cos(angle) * RIGHT + np.sin(angle) * UP - return radius * unit_circle_point + center - - def get_lh_points(self): - return np.array([ - self.get_circle_point_at_proportion(fdiv(i, self.num_lighthouses)) - for i in range(self.num_lighthouses) - if i not in self.ignored_lighthouse_indices - ]) - - def get_observer_point(self, fraction=None): - if fraction is None: - fraction = self.observer_fraction - return self.get_circle_point_at_proportion(fraction / self.num_lighthouses) - - def get_observer(self): - observer = self.observer = PiCreature(**self.observer_config) - observer.next_to(self.get_observer_point(), RIGHT, buff=SMALL_BUFF) - return observer - - def get_observer_dot(self): - self.observer_dot = Dot( - self.get_observer_point(), - color=self.observer_config["color"] - ) - return self.observer_dot - - def get_lighthouses(self): - self.lighthouses = VGroup() - for point in self.get_lh_points(): - lighthouse = Lighthouse() - lighthouse.set_height(self.lighthouse_height) - lighthouse.move_to(point) - self.lighthouses.add(lighthouse) - return self.lighthouses - - def get_lights(self): - self.lights = VGroup() - for point in self.get_lh_points(): - light = AmbientLight( - source_point=VectorizedPoint(point), - **self.ambient_light_config - ) - self.lights.add(light) - return self.lights - - def get_distance_lines(self, start_point=None, line_class=Line): - if start_point is None: - start_point = self.get_observer_point() - lines = VGroup(*[ - line_class(start_point, point) - for point in self.get_lh_points() - ]) - lines.set_stroke(width=2) - self.distance_lines = lines - return self.distance_lines - - def get_symbolic_distance_labels(self): - if not hasattr(self, "distance_lines"): - self.get_distance_lines() - self.d_labels = VGroup() - for i, line in enumerate(self.distance_lines): - d_label = TexMobject("d_%d" % i) - d_label.set_height(self.d_label_height) - vect = rotate_vector(line.get_vector(), 90 * DEGREES) - vect *= 2.5 * SMALL_BUFF / get_norm(vect) - d_label.move_to(line.get_center() + vect) - self.d_labels.add(d_label) - return self.d_labels - - def get_numeric_distance_labels(self, lines=None, num_decimal_places=3, show_ellipsis=True): - radius = self.circle.get_width() / 2 - if lines is None: - if not hasattr(self, "distance_lines"): - self.get_distance_lines() - lines = self.distance_lines - labels = self.numeric_distance_labels = VGroup() - for line in lines: - label = DecimalNumber( - line.get_length() / radius, - num_decimal_places=num_decimal_places, - show_ellipsis=show_ellipsis, - include_background_rectangle=self.include_distance_labels_background_rectangle, - ) - label.set_height(self.numeric_distance_label_height) - max_width = 0.5 * max(line.get_length(), 0.1) - if label.get_width() > max_width: - label.set_width(max_width) - angle = (line.get_angle() % TAU) - TAU / 2 - if np.abs(angle) > TAU / 4: - angle += np.sign(angle) * np.pi - label.angle = angle - label.next_to(line.get_center(), UP, SMALL_BUFF) - label.rotate(angle, about_point=line.get_center()) - labels.add(label) - return labels - - def get_distance_product_column(self, column_top=None, labels=None, fraction=None): - if column_top is None: - column_top = self.default_product_column_top - if labels is None: - if not hasattr(self, "numeric_distance_labels"): - self.get_numeric_distance_labels() - labels = self.numeric_distance_labels - stacked_labels = labels.copy() - for label in stacked_labels: - label.rotate(-label.angle) - label.set_height(self.numeric_distance_label_height) - stacked_labels.arrange(DOWN) - stacked_labels.move_to(column_top, UP) - - h_line = Line(LEFT, RIGHT) - h_line.set_width(1.5 * stacked_labels.get_width()) - h_line.next_to(stacked_labels, DOWN, aligned_edge=RIGHT) - times = TexMobject("\\times") - times.next_to(h_line, UP, SMALL_BUFF, aligned_edge=LEFT) - - product_decimal = DecimalNumber( - self.get_distance_product(fraction), - num_decimal_places=3, - show_ellipsis=True, - include_background_rectangle=self.include_distance_labels_background_rectangle, - ) - product_decimal.set_height(self.numeric_distance_label_height) - product_decimal.next_to(h_line, DOWN) - product_decimal.align_to(stacked_labels, RIGHT) - product_decimal[1].set_color(BLUE) - self.distance_product_column = VGroup( - stacked_labels, h_line, times, product_decimal - ) - return self.distance_product_column - - def get_fractional_arc(self, fraction, start_fraction=0): - arc = Arc( - angle=fraction * TAU, - start_angle=start_fraction * TAU, - radius=self.get_radius(), - ) - arc.shift(self.circle.get_center()) - return arc - - def get_halfway_indication_arcs(self): - fraction = 0.5 / self.num_lighthouses - arcs = VGroup( - self.get_fractional_arc(fraction), - self.get_fractional_arc(-fraction, start_fraction=2 * fraction), - ) - arcs.set_stroke(YELLOW, 4) - return arcs - - def get_circle_group(self): - group = VGroup(self.circle) - if not hasattr(self, "observer_dot"): - self.get_observer_dot() - if not hasattr(self, "observer"): - self.get_observer() - if not hasattr(self, "lighthouses"): - self.get_lighthouses() - if not hasattr(self, "lights"): - self.get_lights() - group.add(self.observer_dot, self.observer) - if self.include_lighthouses: - group.add(self.lighthouses) - group.add(self.lights) - return group - - def setup_lighthouses_and_observer(self): - self.add(*self.get_circle_group()) - - # Numerical results - - def get_radius(self): - return self.circle.get_width() / 2.0 - - def get_distance_product(self, fraction=None): - radius = self.get_radius() - observer_point = self.get_observer_point(fraction) - distances = [ - get_norm(point - observer_point) / radius - for point in self.get_lh_points() - ] - return reduce(op.mul, distances, 1.0) - - # Animating methods - - def add_numeric_distance_labels(self, show_line_creation=True): - anims = [] - if not hasattr(self, "distance_lines"): - self.get_distance_lines() - if not hasattr(self, "numeric_distance_labels"): - self.get_numeric_distance_labels() - if show_line_creation: - anims.append(LaggedStartMap(ShowCreation, self.distance_lines)) - anims.append(LaggedStartMap(FadeIn, self.numeric_distance_labels)) - - self.play(*anims) - - def show_distance_product_in_column(self, **kwargs): - group = self.get_distance_product_column(**kwargs) - stacked_labels, h_line, times, product_decimal = group - labels = self.numeric_distance_labels - - self.play(ReplacementTransform(labels.copy(), stacked_labels)) - self.play( - ShowCreation(h_line), - Write(times) - ) - self.play( - ReplacementTransform( - stacked_labels.copy(), - VGroup(product_decimal) - ) - ) - - -class IntroduceDistanceProduct(DistanceProductScene): - CONFIG = { - "ambient_light_config": {"color": YELLOW}, - } - - def construct(self): - self.draw_circle_with_points() - self.turn_into_lighthouses_and_observer() - self.show_sum_of_inverse_squares() - self.transition_to_lemma_1() - - def draw_circle_with_points(self): - circle = self.circle - - lh_dots = self.lh_dots = VGroup(*[ - Dot(point) for point in self.get_lh_points() - ]) - lh_dot_arrows = VGroup(*[ - Arrow(*[ - interpolate(circle.get_center(), dot.get_center(), a) - for a in (0.6, 0.9) - ], buff=0) - for dot in lh_dots - ]) - evenly_space_dots_label = TextMobject("Evenly-spaced \\\\ dots") - evenly_space_dots_label.set_width(0.5 * circle.get_width()) - evenly_space_dots_label.move_to(circle) - - special_dot = self.special_dot = self.get_observer_dot() - special_dot_arrow = Vector(DL) - special_dot_arrow.next_to(special_dot, UR, SMALL_BUFF) - special_dot_arrow.match_color(special_dot) - special_dot_label = TextMobject("Special dot") - special_dot_label.next_to( - special_dot_arrow.get_start(), UP, SMALL_BUFF) - special_dot_label.match_color(special_dot) - special_dot.save_state() - special_dot.next_to(special_dot_arrow, UR) - special_dot.set_fill(opacity=0) - - self.play(ShowCreation(circle)) - self.play( - LaggedStartMap(ShowCreation, lh_dots), - LaggedStartMap(GrowArrow, lh_dot_arrows), - Write(evenly_space_dots_label) - ) - self.wait() - self.play( - special_dot.restore, - GrowArrow(special_dot_arrow), - Write(special_dot_label, run_time=1), - FadeOut(VGroup(lh_dot_arrows, evenly_space_dots_label)) - ) - self.wait() - self.play(FadeOut(VGroup(special_dot_arrow, special_dot_label))) - - def turn_into_lighthouses_and_observer(self): - lighthouses = self.get_lighthouses() - lights = self.get_lights() - - observer = self.get_observer() - observer.save_state() - observer.set_height(2) - observer.change_mode("happy") - observer.to_edge(RIGHT) - - self.play( - LaggedStartMap(FadeOut, self.lh_dots), - LaggedStartMap(FadeIn, lighthouses), - LaggedStartMap(SwitchOn, lights), - ) - self.wait() - self.play(FadeIn(observer)) - self.play(observer.restore) - self.wait() - - def show_sum_of_inverse_squares(self): - lines = self.get_distance_lines() - labels = self.get_symbolic_distance_labels() - - sum_of_inverse_squares = TexMobject(*it.chain(*[ - ["{1", "\\over", "(", "d_%d" % i, ")", "^2}", "+"] - for i in range(len(lines)) - ])) - sum_of_inverse_squares.submobjects.pop(-1) - sum_of_inverse_squares.to_edge(UP) - d_terms = sum_of_inverse_squares.get_parts_by_tex("d_") - d_terms.set_color(YELLOW) - plusses = sum_of_inverse_squares.get_parts_by_tex("+") - last_term = sum_of_inverse_squares[-6:] - non_d_terms = VGroup(*[m for m in sum_of_inverse_squares if m not in d_terms and m not in last_term]) - - brace = Brace(sum_of_inverse_squares, DOWN) - brace_text = brace.get_text("Total intensity of light") - - arrow = Vector(DOWN, color=WHITE).next_to(brace, DOWN) - basel_sum = TexMobject( - "{1 \\over 1^2} + ", - "{1 \\over 2^2} + ", - "{1 \\over 3^2} + ", - "{1 \\over 4^2} + ", - "\\cdots", - ) - basel_sum.next_to(arrow, DOWN) - basel_cross = Cross(basel_sum) - useful_for = TextMobject("Useful for") - useful_for.next_to(arrow, RIGHT) - - wallis_product = TexMobject( - "{2 \\over 1} \\cdot", "{2 \\over 3} \\cdot", - "{4 \\over 3} \\cdot", "{4 \\over 5} \\cdot", - "{6 \\over 5} \\cdot", "{6 \\over 7} \\cdot", - "\\cdots" - ) - wallis_product.move_to(basel_sum) - - light_rings = VGroup(*it.chain(*self.lights)) - - self.play( - LaggedStartMap(ShowCreation, lines), - LaggedStartMap(Write, labels), - ) - circle_group = VGroup(*self.get_top_level_mobjects()) - self.wait() - self.play( - ReplacementTransform(labels[-1].copy(), last_term[3]), - Write(VGroup(*it.chain(last_term[:3], last_term[4:]))) - ) - self.remove(last_term) - self.add(last_term) - self.wait() - self.play( - Write(non_d_terms), - ReplacementTransform( - labels[:-1].copy(), - d_terms[:-1], - ), - circle_group.scale, 0.8, {"about_point": FRAME_Y_RADIUS * DOWN} - ) - self.wait() - self.play(LaggedStartMap( - ApplyMethod, light_rings, - lambda m: (m.set_fill, {"opacity": 2 * m.get_fill_opacity()}), - rate_func=there_and_back, - run_time=3, - )) - self.wait() - - # Mention useful just to basel problem - circle_group.save_state() - v_point = VectorizedPoint( - FRAME_X_RADIUS * LEFT + FRAME_Y_RADIUS * DOWN) - self.play( - circle_group.next_to, v_point, UR, { - "submobject_to_align": self.circle}, - circle_group.scale, 0.5, {"about_point": v_point.get_center()}, - ) - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait() - self.play( - FadeOut(brace_text), - GrowArrow(arrow), - FadeIn(useful_for), - FadeIn(basel_sum), - ) - self.wait() - self.play( - ShowCreation(basel_cross), - FadeOut(VGroup(arrow, useful_for, brace)) - ) - basel_group = VGroup(basel_sum, basel_cross) - self.play( - basel_group.scale, 0.5, - basel_group.to_corner, DR, - ) - self.play(Write(wallis_product)) - self.wait() - - # Transition to distance product - self.play( - circle_group.restore, - wallis_product.match_width, basel_sum, - wallis_product.next_to, basel_sum, UP, {"aligned_edge": RIGHT}, - ) - self.play( - d_terms.shift, 0.75 * d_terms.get_height() * UP, - d_terms.set_color, PRODUCT_COLOR, - light_rings.set_fill, PRODUCT_COLOR, - *[ - FadeOut(mob) - for mob in sum_of_inverse_squares - if mob not in d_terms and mob not in plusses - ] - ) - self.wait() - self.play( - FadeOut(plusses), - d_terms.arrange, RIGHT, 0.25 * SMALL_BUFF, - d_terms.move_to, sum_of_inverse_squares, DOWN, - ) - self.wait() - - # Label distance product - brace = Brace(d_terms, UP, buff=SMALL_BUFF) - distance_product_label = brace.get_text("``Distance product''") - - self.play( - GrowFromCenter(brace), - Write(distance_product_label) - ) - line_copies = lines.copy().set_color(RED) - self.play(LaggedStartMap(ShowCreationThenDestruction, line_copies)) - self.wait() - self.play(LaggedStartMap( - ApplyFunction, light_rings, - lambda mob: ( - lambda m: m.shift( - MED_SMALL_BUFF * UP).set_fill(opacity=2 * m.get_fill_opacity()), - mob - ), - rate_func=wiggle, - run_time=6, - )) - self.wait() - - def transition_to_lemma_1(self): - self.lighthouse_height = Lemma1.CONFIG["lighthouse_height"] - self.circle_radius = Lemma1.CONFIG["circle_radius"] - self.observer_fraction = Lemma1.CONFIG["observer_fraction"] - - self.ambient_light_config["color"] = BLUE - - circle = self.circle - lighthouses = self.lighthouses - lights = self.lights - - circle.generate_target() - circle.target.set_width(2 * self.circle_radius) - circle.target.to_corner(DL) - self.circle = circle.target - - new_lighthouses = self.get_lighthouses() - new_lights = self.get_lights() - - self.clear() - self.play( - MoveToTarget(circle), - Transform(lighthouses, new_lighthouses), - Transform(lights, new_lights), - ApplyMethod( - self.observer_dot.move_to, - self.get_circle_point_at_proportion( - self.observer_fraction / self.num_lighthouses - ) - ), - MaintainPositionRelativeTo(self.observer, self.observer_dot), - ) - - -class Lemma1(DistanceProductScene): - CONFIG = { - "circle_radius": 2.5, - "observer_fraction": 0.5, - "lighthouse_height": 0.25, - "lemma_text": "distance product = 2", - "include_distance_labels_background_rectangle": False, - # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, - } - - def construct(self): - self.add_title() - self.add_circle_group() - self.state_lemma_premise() - self.show_product() - self.wiggle_observer() - - def add_title(self): - title = self.title = TextMobject("Two lemmas:") - title.set_color(YELLOW) - title.to_edge(UP, buff=MED_SMALL_BUFF) - self.add(title) - - def add_circle_group(self): - self.circle.to_corner(DL) - circle_group = self.get_circle_group() - self.play(LaggedStartMap(FadeIn, VGroup( - *circle_group.family_members_with_points()))) - - def state_lemma_premise(self): - premise = TextMobject( - "Lemma 1: If observer is halfway between lighthouses,") - self.premise = premise - premise.next_to(self.title, DOWN) - - frac = 1.0 / self.num_lighthouses - arc1, arc2 = arcs = VGroup(VMobject(), VMobject()) - arc1.pointwise_become_partial(self.circle, 0, frac / 2) - arc2.pointwise_become_partial(self.circle, frac / 2, frac) - arc1.reverse_points() - arcs.set_stroke(YELLOW, 5) - show_arcs = ShowCreationThenDestruction( - arcs, - lag_ratio=0, - run_time=2, - ) - - self.play(Write(premise), show_arcs, run_time=2) - self.wait() - self.play(show_arcs) - self.wait() - - def show_product(self): - lemma = TextMobject(self.lemma_text) - lemma.set_color(BLUE) - lemma.next_to(self.premise, DOWN) - self.add_numeric_distance_labels() - self.play(Write(lemma, run_time=1)) - self.show_distance_product_in_column() - self.wait() - - def wiggle_observer(self): - # Overwriting existing method - self.get_observer_point = lambda dummy=None: self.observer_dot.get_center() - - center = self.circle.get_center() - observer_angle = angle_of_vector(self.get_observer_point() - center) - observer_angle_tracker = ValueTracker(observer_angle) - - def update_dot(dot): - dot.move_to(self.get_circle_point_at_proportion( - observer_angle_tracker.get_value() / TAU - )) - - def update_distance_lines(lines): - new_lines = self.get_distance_lines(start_point=self.get_observer_point()) - lines.submobjects = new_lines.submobjects - - def update_numeric_distance_labels(labels): - new_labels = self.get_numeric_distance_labels(self.distance_lines) - labels.submobjects = new_labels.submobjects - - def update_distance_product_column(column): - new_column = self.get_distance_product_column() - column.submobjects = new_column.submobjects - - self.remove(*VGroup( - self.observer, self.observer_dot, - self.distance_lines, - self.numeric_distance_labels, - self.distance_product_column, - ).get_family()) - self.play( - ApplyMethod( - observer_angle_tracker.set_value, observer_angle + 0.05 * TAU, - rate_func=wiggle - ), - UpdateFromFunc(self.observer_dot, update_dot), - MaintainPositionRelativeTo(self.observer, self.observer_dot), - UpdateFromFunc(self.distance_lines, update_distance_lines), - UpdateFromFunc(self.numeric_distance_labels, update_numeric_distance_labels), - UpdateFromFunc(self.distance_product_column, update_distance_product_column), - run_time=5 - ) - self.distance_product_column[-1].set_color(BLUE).scale_in_place(1.05) - self.wait() - - -class Lemma1With7Lighthouses(Lemma1): - CONFIG = { - "num_lighthouses": 7, - } - - -class Lemma1With8Lighthouses(Lemma1): - CONFIG = { - "num_lighthouses": 8, - } - - -class Lemma1With9Lighthouses(Lemma1): - CONFIG = { - "num_lighthouses": 9, - } - - -class Lemma2(Lemma1): - CONFIG = { - # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, - "lemma_text": "distance product = \\# Initial lighthouses" - } - - def construct(self): - self.add_title() - self.add_circle_group() - self.state_lemma_premise() - self.replace_first_lighthouse() - self.show_product() - self.wiggle_observer() - - def state_lemma_premise(self): - premise = self.premise = TextMobject( - "Lemma 2: If the observer replaces a lighthouse," - ) - premise.next_to(self.title, DOWN) - - self.play(Write(premise, run_time=1)) - - def replace_first_lighthouse(self): - dot = self.observer_dot - observer_anim = MaintainPositionRelativeTo(self.observer, dot) - lighthouse_group = VGroup(self.lighthouses[0], self.lights[0]) - point = self.get_lh_points()[0] - - self.play( - lighthouse_group.shift, 5 * RIGHT, - lighthouse_group.fade, 1, - run_time=1.5, - rate_func=running_start, - remover=True, - ) - self.play( - dot.move_to, point, - observer_anim, - path_arc=(-120 * DEGREES), - ) - self.wait() - - self.ignored_lighthouse_indices = [0] - self.observer_fraction = 0 - for group in self.lighthouses, self.lights: - self.lighthouses.submobjects.pop(0) - - -class Lemma2With7Lighthouses(Lemma2): - CONFIG = { - "num_lighthouses": 7, - } - - -class Lemma2With8Lighthouses(Lemma2): - CONFIG = { - "num_lighthouses": 8, - } - - -class Lemma2With9Lighthouses(Lemma2): - CONFIG = { - "num_lighthouses": 9, - } - - -class ConfusedPiCreature(Scene): - def construct(self): - randy = Randolph(color=GREY_BROWN) - self.add(randy) - self.play(Blink(randy)) - self.play(randy.change, "confused") - self.play(Blink(randy)) - self.wait() - - -class FromGeometryToAlgebra(DistanceProductScene): - CONFIG = { - "num_lighthouses": 7, - # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, - } - - def construct(self): - self.setup_lights() - self.point_out_evenly_spaced() - self.transition_to_complex_plane() - self.discuss_powers() - self.raise_everything_to_the_nth() - - def setup_lights(self): - circle = self.circle - circle.set_height(5, about_edge=DOWN) - lights = self.get_lights() - dots = VGroup(*[Dot(point) for point in self.get_lh_points()]) - for dot, light in zip(dots, lights): - light.add_to_back(dot) - - self.add(circle, lights) - - def point_out_evenly_spaced(self): - circle = self.circle - step = 1.0 / self.num_lighthouses / 2 - alpha_range = np.arange(0, 1 + step, step) - arcs = VGroup(*[ - VMobject().pointwise_become_partial(circle, a1, a2) - for a1, a2 in zip(alpha_range, alpha_range[1:]) - ]) - arcs.set_stroke(YELLOW, 5) - - for arc in arcs[::2]: - arc.reverse_points() - - arcs_anim = ShowCreationThenDestruction( - arcs, lag_ratio=0, run_time=2 - ) - - spacing_words = self.spacing_words = TextMobject("Evenly-spaced") - spacing_words.set_width(self.get_radius()) - spacing_words.move_to(circle) - - arrows = self.get_arrows() - - geometric_words = self.geometric_words = TextMobject( - "Geometric property") - geometric_words.to_edge(UP) - geometric_words.add_background_rectangle() - - self.add(geometric_words) - self.play( - FadeIn(spacing_words), - arcs_anim, - *list(map(GrowArrow, arrows)) - ) - self.play(FadeOut(arrows), arcs_anim) - self.wait() - - def transition_to_complex_plane(self): - plane = self.complex_plane = ComplexPlane( - unit_size=2, y_radius=6, x_radius=9, - ) - plane.shift(1.5 * RIGHT) - plane.add_coordinates() - origin = plane.number_to_point(0) - h_line = Line(plane.number_to_point(-1), plane.number_to_point(1)) - - circle = self.circle - circle_group = VGroup(circle, self.lights) - circle_group.generate_target() - circle_group.target.scale(h_line.get_width() / circle.get_width()) - circle_group.target.shift( - origin - circle_group.target[0].get_center() - ) - circle_group.target[0].set_stroke(RED) - - geometric_words = self.geometric_words - geometric_words.generate_target() - arrow = TexMobject("\\rightarrow") - arrow.add_background_rectangle() - algebraic_words = TextMobject("Algebraic property") - algebraic_words.add_background_rectangle() - word_group = VGroup(geometric_words.target, arrow, algebraic_words) - word_group.arrange(RIGHT) - word_group.move_to(origin) - word_group.to_edge(UP) - - unit_circle_words = TextMobject("Unit circle", "") - unit_circle_words.match_color(circle_group.target[0]) - for part in unit_circle_words: - part.add_background_rectangle() - unit_circle_words.next_to(origin, UP) - - complex_plane_words = TextMobject("Complex Plane") - self.complex_plane_words = complex_plane_words - complex_plane_words.move_to(word_group) - complex_plane_words.add_background_rectangle() - - roots_of_unity_words = TextMobject("Roots of\\\\", "unity") - roots_of_unity_words.move_to(origin) - roots_of_unity_words.set_color(YELLOW) - for part in roots_of_unity_words: - part.add_background_rectangle() - - self.play( - Write(plane), - MoveToTarget(circle_group), - FadeOut(self.spacing_words), - MoveToTarget(geometric_words), - FadeIn(arrow), - FadeIn(algebraic_words), - ) - word_group.submobjects[0] = geometric_words - self.play(Write(unit_circle_words, run_time=1)) - - # Show complex values - outer_arrows = self.outer_arrows = self.get_arrows() - for arrow, point in zip(outer_arrows, self.get_lh_points()): - arrow.rotate(np.pi, about_point=point) - outer_arrow = self.outer_arrow = outer_arrows[3].copy() - - values = list(map(plane.point_to_number, self.get_lh_points())) - complex_decimal = self.complex_decimal = DecimalNumber( - values[3], - num_decimal_places=3, - include_background_rectangle=True - ) - complex_decimal.next_to(outer_arrow.get_start(), LEFT, SMALL_BUFF) - complex_decimal_rect = SurroundingRectangle(complex_decimal) - complex_decimal_rect.fade(1) - - self.play( - FadeIn(complex_plane_words), - FadeOut(word_group), - FadeIn(complex_decimal), - FadeIn(outer_arrow) - ) - self.wait(2) - self.play( - ChangeDecimalToValue( - complex_decimal, values[1], - tracked_mobject=complex_decimal_rect - ), - complex_decimal_rect.next_to, outer_arrows[1].get_start( - ), UP, SMALL_BUFF, - Transform(outer_arrow, outer_arrows[1]), - run_time=1.5 - ) - self.wait() - - arrows = self.get_arrows() - arrows.set_color(YELLOW) - self.play( - ReplacementTransform(unit_circle_words, roots_of_unity_words), - LaggedStartMap(GrowArrow, arrows) - ) - self.wait() - self.play( - complex_plane_words.move_to, word_group, - LaggedStartMap(FadeOut, VGroup(*it.chain( - arrows, roots_of_unity_words - ))) - ) - - # Turn decimal into z - x_term = self.x_term = TexMobject("x") - x_term.add_background_rectangle() - x_term.move_to(complex_decimal, DOWN) - x_term.shift(0.5 * SMALL_BUFF * (DR)) - self.play(ReplacementTransform(complex_decimal, x_term)) - - def discuss_powers(self): - x_term = self.x_term - outer_arrows = self.outer_arrows - outer_arrows.add(outer_arrows[0].copy()) - plane = self.complex_plane - origin = plane.number_to_point(0) - - question = TextMobject("What is $x^2$") - question.next_to(x_term, RIGHT, LARGE_BUFF) - question.set_color(YELLOW) - - lh_points = list(self.get_lh_points()) - lh_points.append(lh_points[0]) - lines = VGroup(*[ - Line(origin, point) - for point in lh_points - ]) - lines.set_color(GREEN) - step = 1.0 / self.num_lighthouses - angle_arcs = VGroup(*[ - Arc(angle=alpha * TAU, radius=0.35).shift(origin) - for alpha in np.arange(0, 1 + step, step) - ]) - angle_labels = VGroup() - for i, arc in enumerate(angle_arcs): - label = TexMobject("(%d / %d)\\tau" % (i, self.num_lighthouses)) - label.scale(0.5) - label.add_background_rectangle() - point = arc.point_from_proportion(0.5) - point += 1.2 * (point - origin) - label.move_to(point) - angle_labels.add(label) - if i == 0: - label.shift(0.75 * label.get_height() * DOWN) - - line = self.angle_line = lines[1].copy() - line_ghost = DashedLine(line.get_start(), line.get_end()) - self.ghost_angle_line = line_ghost - line_ghost.set_stroke(line.get_color(), 2) - angle_arc = angle_arcs[1].copy() - angle_label = angle_labels[1].copy() - angle_label.shift(0.25 * SMALL_BUFF * DR) - - magnitude_label = TexMobject("1") - magnitude_label.next_to(line.get_center(), UL, buff=SMALL_BUFF) - - power_labels = VGroup() - for i, arrow in enumerate(outer_arrows[:-1]): - label = TexMobject("x^%d" % i) - label.next_to( - arrow.get_start(), -arrow.get_vector(), - submobject_to_align=label[0] - ) - label.add_background_rectangle() - power_labels.add(label) - power_labels[0].next_to(outer_arrows[-1].get_start(), UR, SMALL_BUFF) - power_labels.submobjects[1] = x_term - - L_labels = self.L_labels = VGroup(*[ - TexMobject("L_%d" % i).move_to(power_label, DOWN).add_background_rectangle( - opacity=1 - ) - for i, power_label in enumerate(power_labels) - ]) - - # Ask about squaring - self.play(Write(question)) - self.wait() - self.play( - ShowCreation(line), - Write(magnitude_label) - ) - self.wait() - self.play( - ShowCreation(angle_arc), - Write(angle_label) - ) - self.wait() - self.add(line_ghost) - for i in list(range(2, self.num_lighthouses)) + [0]: - anims = [ - Transform(angle_arc, angle_arcs[i]), - Transform(angle_label, angle_labels[i]), - Transform(line, lines[i], path_arc=TAU / self.num_lighthouses), - ] - if i == 2: - anims.append(FadeOut(magnitude_label)) - if i == 3: - anims.append(FadeOut(question)) - self.play(*anims) - new_anims = [ - GrowArrow(outer_arrows[i]), - Write(power_labels[i]), - ] - if i == 2: - new_anims.append(FadeOut(self.complex_plane_words)) - self.play(*new_anims) - self.wait() - self.play(ReplacementTransform(power_labels, L_labels)) - self.wait() - self.play( - Rotate(self.lights, TAU / self.num_lighthouses / 2), - rate_func=wiggle - ) - self.wait() - self.play( - FadeOut(angle_arc), - FadeOut(angle_label), - *list(map(ShowCreationThenDestruction, lines)) - ) - self.wait() - - def raise_everything_to_the_nth(self): - func_label = TexMobject("L \\rightarrow L^7") - func_label.set_color(YELLOW) - func_label.to_corner(UL, buff=LARGE_BUFF) - func_label.add_background_rectangle() - - polynomial_scale_factor = 0.8 - - polynomial = TexMobject("x^%d - 1" % self.num_lighthouses, "=", "0") - polynomial.scale(polynomial_scale_factor) - polynomial.next_to(func_label, UP) - polynomial.to_edge(LEFT) - - factored_polynomial = TexMobject( - "(x-L_0)(x-L_1)\\cdots(x-L_{%d - 1})" % self.num_lighthouses, "=", "0" - ) - factored_polynomial.scale(polynomial_scale_factor) - factored_polynomial.next_to(polynomial, DOWN, aligned_edge=LEFT) - for group in polynomial, factored_polynomial: - for part in group: - part.add_background_rectangle() - - origin = self.complex_plane.number_to_point(0) - - lights = self.lights - lights.save_state() - rotations = [] - for i, light in enumerate(lights): - rotations.append(Rotating( - light, - radians=(i * TAU - i * TAU / self.num_lighthouses), - about_point=origin, - rate_func=bezier([0, 0, 1, 1]), - )) - - self.play(Write(func_label, run_time=1)) - for i, rotation in enumerate(rotations[:4]): - if i == 3: - rect = SurroundingRectangle(polynomial) - rect.set_color(YELLOW) - self.play( - FadeIn(polynomial), - ShowCreationThenDestruction(rect) - ) - self.play( - rotation, - run_time=np.sqrt(i + 1) - ) - self.play(*rotations[4:], run_time=3) - self.wait() - - self.play(lights.restore) - self.play( - FadeOut(func_label), - FadeIn(factored_polynomial) - ) - self.wait(3) - self.play( - factored_polynomial[0].next_to, polynomial[1], RIGHT, 1.5 * SMALL_BUFF, - FadeOut(polynomial[2]), - FadeOut(factored_polynomial[1:]), - ) - - # Comment on formula - formula = VGroup(polynomial[0], polynomial[1], factored_polynomial[0]) - rect = SurroundingRectangle(formula) - - brace = Brace(factored_polynomial[0], DOWN) - brace2 = Brace(polynomial[0], DOWN) - - morty = PiCreature(color=GREY_BROWN) - morty.scale(0.5) - morty.next_to(brace.get_center(), DL, buff=LARGE_BUFF) - - L1_rhs = TexMobject("= \\cos(\\tau / 7) + \\\\", "\\sin(\\tau / 7)i") - L1_rhs.next_to(self.L_labels[1], RIGHT, aligned_edge=UP) - for part in L1_rhs: - part.add_background_rectangle() - - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.wait() - self.play(GrowFromCenter(brace)) - self.play(FadeIn(morty)) - self.play(morty.change, "horrified", brace) - self.play(Blink(morty)) - self.wait() - self.play( - Write(L1_rhs), - morty.change, "confused", L1_rhs - ) - self.play(Blink(morty)) - self.wait() - self.play( - Transform(brace, brace2), - morty.change, "hooray", brace2 - ) - self.play(Blink(morty)) - self.wait() - - # Nothing special about 7 - new_lights = self.lights.copy() - new_lights.rotate( - TAU / self.num_lighthouses / 2, - about_point=origin - ) - sevens = VGroup(polynomial[0][1][1], factored_polynomial[0][1][-4]) - n_terms = VGroup() - for seven in sevens: - n_term = TexMobject("N") - n_term.replace(seven, dim_to_match=1) - n_term.scale(0.9) - n_term.shift(0.25 * SMALL_BUFF * DR) - n_terms.add(n_term) - - self.play(LaggedStartMap(FadeOut, VGroup(*it.chain( - L1_rhs, self.outer_arrows, self.L_labels, self.outer_arrow, - self.angle_line, self.ghost_angle_line - )))) - self.play(LaggedStartMap(SwitchOn, new_lights), morty.look_at, new_lights) - self.play(Transform(sevens, n_terms)) - self.wait() - self.play(Blink(morty)) - self.wait() - # - - def get_arrows(self): - return VGroup(*[ - Arrow( - interpolate(self.circle.get_center(), point, 0.6), - interpolate(self.circle.get_center(), point, 0.9), - buff=0 - ) - for point in self.get_lh_points() - ]) - - -class PlugObserverIntoPolynomial(DistanceProductScene): - CONFIG = { - # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, - "num_lighthouses": 7, - # This makes it look slightly better, but renders much slower - "add_lights_in_foreground": True, - } - - def construct(self): - self.add_plane() - self.add_circle_group() - self.label_roots() - self.add_polynomial() - self.point_out_rhs() - self.introduce_observer() - self.raise_observer_to_the_N() - - def add_plane(self): - plane = self.plane = ComplexPlane( - unit_size=2, - y_radius=5, - ) - plane.shift(DOWN) - plane.add_coordinates() - plane.coordinate_labels.submobjects.pop(-4) - self.origin = plane.number_to_point(0) - - self.add(plane) - - def add_circle_group(self): - self.circle.set_color(RED) - self.circle.set_width( - 2 * get_norm(self.plane.number_to_point(1) - self.origin) - ) - self.circle.move_to(self.origin) - - lights = self.lights = self.get_lights() - dots = VGroup(*[ - Dot(point) for point in self.get_lh_points() - ]) - for dot, light in zip(dots, lights): - light.add_to_back(dot) - - self.add(self.circle, lights) - if self.add_lights_in_foreground: - self.add_foreground_mobject(lights) - - def label_roots(self): - origin = self.origin - labels = VGroup(*[ - TexMobject("L_%d" % d) - for d in range(self.num_lighthouses) - ]) - self.root_labels = labels - points = self.get_lh_points() - for label, point in zip(labels, points): - label.move_to(interpolate(origin, point, 1.2)) - labels[0].align_to(origin, UP) - labels[0].shift(SMALL_BUFF * DOWN) - self.add(labels) - - def add_polynomial(self, arg="x"): - self.polynomial = self.get_polynomial_equation(arg) - self.add(self.polynomial) - - def point_out_rhs(self): - rhs = self.get_polynomial_rhs(self.polynomial) - brace = Brace(rhs, DOWN, buff=SMALL_BUFF) - brace_text = brace.get_text( - "Useful for distance product", buff=SMALL_BUFF) - brace_text.set_color(YELLOW) - brace_text.add_background_rectangle() - - self.play( - GrowFromCenter(brace), - Write(brace_text) - ) - self.wait() - self.play(FadeOut(VGroup(brace, brace_text))) - - def introduce_observer(self): - dot = self.observer_dot = Dot() - dot.move_to(self.plane.coords_to_point(1.6, 0.8)) - observer = PiCreature(**self.observer_config) - observer.move_to(dot) - dot.match_color(observer) - - vect = 2 * DOWN + LEFT - vect /= get_norm(vect) - arrow = self.arrow = Vector(0.5 * vect) - arrow.next_to(observer, -vect, buff=SMALL_BUFF) - arrow.set_color(WHITE) - - full_name = TextMobject("Observer") - var_name = self.var_name = TexMobject("O") - for mob in full_name, var_name: - mob.match_color(observer) - mob.next_to(arrow.get_start(), UP, SMALL_BUFF) - mob.add_background_rectangle() - - complex_decimal = DecimalNumber(0, include_background_rectangle=True) - equals = TexMobject("=") - complex_decimal_animation = ChangingDecimal( - complex_decimal, - lambda a: self.plane.point_to_number(dot.get_center()), - position_update_func=lambda m: m.next_to(equals, RIGHT, SMALL_BUFF) - ) - complex_decimal_animation.update(0) - equals_decimal = VGroup(equals, complex_decimal) - equals_decimal.next_to(var_name, RIGHT) - - new_polynomial = self.get_polynomial_equation("O") - O_terms = new_polynomial.get_parts_by_tex("O") - - lhs, poly_eq, rhs = self.get_polynomial_split(new_polynomial) - lhs_rect = SurroundingRectangle(lhs, color=YELLOW) - rhs_rect = SurroundingRectangle(rhs, color=YELLOW) - self.lhs, self.rhs = lhs, rhs - self.lhs_rect, self.rhs_rect = lhs_rect, rhs_rect - - lines = self.lines = self.get_lines() - lines_update = self.lines_update = UpdateFromFunc( - lines, lambda l: Transform(l, self.get_lines()).update(1) - ) - - anims_for_dot_movement = self.anims_for_dot_movement = [ - MaintainPositionRelativeTo(arrow, dot), - MaintainPositionRelativeTo(var_name, arrow), - MaintainPositionRelativeTo(equals, var_name), - complex_decimal_animation, - lines_update, - ] - - self.play( - FadeInFrom(observer, direction=-vect), - GrowArrow(arrow) - ) - self.play(Write(full_name)) - self.wait() - self.play( - ReplacementTransform(full_name[0], var_name[0]), - ReplacementTransform(full_name[1][0], var_name[1][0]), - FadeOut(full_name[1][1:]), - ReplacementTransform(observer, dot), - FadeIn(equals_decimal) - ) - self.add_foreground_mobject(dot) - - # Substitute - self.wait() - self.play( - ReplacementTransform(var_name.copy(), O_terms), - ReplacementTransform(self.polynomial, new_polynomial) - ) - self.polynomial = new_polynomial - self.wait() - - # Show distances - self.play(ShowCreation(rhs_rect)) - self.play( - LaggedStartMap(ShowCreation, lines), - Animation(dot) - ) - - self.play( - Rotating( - dot, - radians=TAU, - rate_func=smooth, - about_point=dot.get_center() + MED_LARGE_BUFF * LEFT, - run_time=4 - ), - *anims_for_dot_movement - ) - self.wait() - - self.remove(rhs_rect) - self.play(ReplacementTransform(rhs_rect.copy(), lhs_rect)) - self.wait() - - # Move onto circle - angle = self.observer_angle = TAU / self.num_lighthouses / 3.0 - target_point = self.plane.number_to_point( - np.exp(complex(0, angle)) - ) - self.play( - dot.move_to, target_point, - *anims_for_dot_movement - ) - self.play(FadeOut(VGroup( - equals, complex_decimal, - var_name, arrow, - ))) - - def raise_observer_to_the_N(self): - dot = self.observer_dot - origin = self.origin - radius = self.get_radius() - - text_scale_val = 0.8 - - question = TextMobject( - "What fraction \\\\", "between $L_0$ and $L_1$", "?", - arg_separator="" - ) - question.scale(text_scale_val) - question.next_to(dot, RIGHT) - question.add_background_rectangle_to_submobjects() - - f_words = TextMobject("$f$", "of the way") - third_words = TextMobject("$\\frac{1}{3}$", "of the way") - for words in f_words, third_words: - words.scale(text_scale_val) - words.move_to(question[0]) - words[0].set_color(YELLOW) - words.add_background_rectangle() - - obs_angle = self.observer_angle - full_angle = TAU / self.num_lighthouses - - def get_arc(angle): - result = Arc(angle=angle, radius=radius, - color=YELLOW, stroke_width=4) - result.shift(origin) - return result - - arc = get_arc(obs_angle) - O_to_N_arc = get_arc(obs_angle * self.num_lighthouses) - - O_to_N_dot = dot.copy().move_to(O_to_N_arc.point_from_proportion(1)) - O_to_N_arrow = Vector(0.5 * DR).next_to(O_to_N_dot, UL, SMALL_BUFF) - O_to_N_arrow.set_color(WHITE) - O_to_N_label = TexMobject("O", "^N") - O_to_N_label.set_color_by_tex("O", dot.get_color()) - O_to_N_label.next_to(O_to_N_arrow.get_start(), UP, SMALL_BUFF) - O_to_N_label.shift(SMALL_BUFF * RIGHT) - O_to_N_group = VGroup(O_to_N_arc, O_to_N_arrow, O_to_N_label) - - around_circle_words = TextMobject("around the circle") - around_circle_words.scale(text_scale_val) - around_circle_words.add_background_rectangle() - around_circle_words.next_to(self.circle.get_top(), UR) - - chord = Line(O_to_N_dot.get_center(), self.circle.get_right()) - chord.set_stroke(GREEN) - - chord_halves = VGroup( - Line(chord.get_center(), chord.get_start()), - Line(chord.get_center(), chord.get_end()), - ) - chord_halves.set_stroke(WHITE, 5) - - chord_label = TexMobject("|", "O", "^N", "-", "1", "|") - chord_label.set_color_by_tex("O", MAROON_B) - chord_label.add_background_rectangle() - chord_label.next_to(chord.get_center(), DOWN, SMALL_BUFF) - chord_label.rotate( - chord.get_angle(), about_point=chord.get_center() - ) - - numeric_chord_label = DecimalNumber( - np.sqrt(3), - num_decimal_places=4, - include_background_rectangle=True, - show_ellipsis=True, - ) - numeric_chord_label.rotate(chord.get_angle()) - numeric_chord_label.move_to(chord_label) - - self.play( - FadeIn(question), - ShowCreation(arc), - ) - for angle in [full_angle - obs_angle, -full_angle, obs_angle]: - last_angle = angle_of_vector(dot.get_center() - origin) - self.play( - self.lines_update, - UpdateFromAlphaFunc( - arc, lambda arc, a: Transform( - arc, get_arc(last_angle + a * angle) - ).update(1) - ), - Rotate(dot, angle, about_point=origin), - run_time=2 - ) - self.play( - FadeOut(question[0]), - FadeOut(question[2]), - FadeIn(f_words), - ) - self.wait() - self.play( - FadeOut(self.lines), - FadeOut(self.root_labels), - ) - self.play( - ReplacementTransform(dot.copy(), O_to_N_dot), - ReplacementTransform(arc, O_to_N_arc), - path_arc=O_to_N_arc.angle - arc.angle, - ) - self.add_foreground_mobject(O_to_N_dot) - self.play( - FadeIn(O_to_N_label), - GrowArrow(O_to_N_arrow), - ) - self.wait() - self.play( - FadeOut(question[1]), - f_words.next_to, around_circle_words, UP, SMALL_BUFF, - FadeIn(around_circle_words) - ) - self.wait() - self.play( - FadeIn(chord_label[0]), - ReplacementTransform(self.lhs.copy(), chord_label[1]), - ShowCreation(chord) - ) - self.wait() - - # Talk through current example - light_rings = VGroup(*it.chain(self.lights)) - self.play(LaggedStartMap( - ApplyMethod, light_rings, - lambda m: (m.shift, MED_SMALL_BUFF * UP), - rate_func=wiggle - )) - self.play( - FadeOut(around_circle_words), - FadeIn(question[1]), - ReplacementTransform(f_words, third_words) - ) - self.play( - Rotate(dot, 0.05 * TAU, about_point=origin, rate_func=wiggle) - ) - self.wait(2) - self.play(ReplacementTransform( - dot.copy(), O_to_N_dot, path_arc=TAU / 3)) - self.play( - third_words.next_to, around_circle_words, UP, SMALL_BUFF, - FadeIn(around_circle_words), - FadeOut(question[1]) - ) - self.wait() - self.play(Indicate(self.lhs)) - for x in range(2): - self.play(ShowCreationThenDestruction(chord_halves)) - self.play( - FadeOut(chord_label), - FadeIn(numeric_chord_label) - ) - self.wait() - self.remove(self.lhs_rect) - self.play( - FadeOut(chord), - FadeOut(numeric_chord_label), - FadeOut(O_to_N_group), - FadeIn(self.lines), - ReplacementTransform(self.lhs_rect.copy(), self.rhs_rect) - ) - self.wait() - - # Add new lights - for light in self.lights: - light[1:].fade(0.5) - added_lights = self.lights.copy() - added_lights.rotate(full_angle / 2, about_point=origin) - new_lights = VGroup(*it.chain(*list(zip(self.lights, added_lights)))) - self.num_lighthouses *= 2 - dot.generate_target() - dot.target.move_to(self.get_circle_point_at_proportion( - obs_angle / TAU / 2 - )) - dot.save_state() - dot.move_to(dot.target) - new_lines = self.get_lines() - dot.restore() - - self.play(Transform(self.lights, new_lights)) - self.play( - MoveToTarget(dot), - Transform(self.lines, new_lines) - ) - self.wait() - self.play( - third_words.next_to, question[1], UP, SMALL_BUFF, - FadeOut(around_circle_words), - FadeIn(question[1]), - ) - self.wait() - - chord_group = VGroup(chord, numeric_chord_label[1]) - chord_group.set_color(YELLOW) - self.add_foreground_mobjects(*chord_group) - self.play( - FadeIn(chord), - FadeIn(numeric_chord_label), - ) - self.wait() - - # Helpers - - def get_polynomial_equation(self, var="x", color=None): - if color is None: - color = self.observer_config["color"] - equation = TexMobject( - "\\left(", var, "^N", "-", "1", "\\right)", "=", - "\\left(", var, "-", "L_0", "\\right)", - "\\left(", var, "-", "L_1", "\\right)", - "\\cdots", - "\\left(", var, "-", "L_{N-1}", "\\right)", - ) - equation.set_color_by_tex(var, color) - equation.to_edge(UP) - equation.add_background_rectangle() - return equation - - def get_polynomial_rhs(self, polynomial): - return self.get_polynomial_split(polynomial)[2] - - def get_polynomial_lhs(self, polynomial): - return self.get_polynomial_split(polynomial)[0] - - def get_polynomial_split(self, polynomial): - eq = polynomial.get_part_by_tex("=") - i = polynomial[1].submobjects.index(eq) - return polynomial[1][:i], polynomial[1][i], polynomial[1][i + 1:] - - def get_lines(self, start_point=None): - return self.get_distance_lines( - start_point=start_point, - line_class=DashedLine - ) - - def get_observer_point(self, dummy_arg=None): - return self.observer_dot.get_center() - - -class PlugObserverIntoPolynomial5Lighthouses(PlugObserverIntoPolynomial): - CONFIG = { - "num_lighthouses": 5, - } - - -class PlugObserverIntoPolynomial3Lighthouses(PlugObserverIntoPolynomial): - CONFIG = { - "num_lighthouses": 3, - } - - -class PlugObserverIntoPolynomial2Lighthouses(PlugObserverIntoPolynomial): - CONFIG = { - "num_lighthouses": 2, - } - - -class DefineChordF(Scene): - def construct(self): - radius = 2.5 - - full_chord_f = TextMobject( - "``", "Chord(", "$f$", ")", "''", arg_separator="") - full_chord_f.set_color_by_tex("$f$", YELLOW) - full_chord_f.to_edge(UP) - chord_f = full_chord_f[1:-1] - chord_f.generate_target() - - circle = Circle(radius=2.5) - circle.set_color(RED) - radius_line = Line(circle.get_center(), circle.get_right()) - one_label = TexMobject("1") - one_label.next_to(radius_line, DOWN, SMALL_BUFF) - - chord = Line(*[circle.point_from_proportion(f) for f in [0, 1. / 3]]) - chord.set_color(YELLOW) - chord_third = TextMobject("Chord(", "$1/3$", ")", arg_separator="") - chord_third.set_color_by_tex("1/3", YELLOW) - for term in chord_third, chord_f.target: - term.next_to(chord.get_center(), UP, SMALL_BUFF) - chord_angle = chord.get_angle() + np.pi - term.rotate(chord_angle, about_point=chord.get_center()) - - brace = Brace(Line(ORIGIN, TAU * UP / 3), RIGHT, buff=0) - brace.generate_target() - brace.target.stretch(0.5, 0) - brace.target.apply_complex_function(np.exp) - VGroup(brace, brace.target).scale(radius) - brace.next_to(circle.get_right(), RIGHT, SMALL_BUFF, DOWN) - brace.scale(0.5, about_edge=DOWN) - brace.target.move_to(brace, DR) - brace.target.shift(2 * SMALL_BUFF * LEFT) - - f_label = TexMobject("f") - f_label.set_color(YELLOW) - point = circle.point_from_proportion(1.0 / 6) - f_label.move_to(point + 0.4 * (point - circle.get_center())) - - third_label = TexMobject("\\frac{1}{3}") - third_label.scale(0.7) - third_label.move_to(f_label) - third_label.match_color(f_label) - - alphas = np.linspace(0, 1, 4) - third_arcs = VGroup(*[ - VMobject().pointwise_become_partial(circle, a1, a2) - for a1, a2 in zip(alphas, alphas[1:]) - ]) - third_arcs.set_color_by_gradient(BLUE, PINK, GREEN) - - # Terms for sine formula - origin = circle.get_center() - height = DashedLine(origin, chord.get_center()) - half_chords = VGroup( - Line(chord.get_start(), chord.get_center()), - Line(chord.get_end(), chord.get_center()), - ) - half_chords.set_color_by_gradient(BLUE, PINK) - alt_radius_line = Line(origin, chord.get_end()) - alt_radius_line.set_color(WHITE) - angle_arc = Arc( - radius=0.3, - angle=TAU / 6, - ) - angle_arc.shift(origin) - angle_label = TexMobject("\\frac{f}{2}", "2\\pi") - angle_label[0][0].set_color(YELLOW) - angle_label.scale(0.6) - angle_label.next_to(angle_arc, RIGHT, SMALL_BUFF, DOWN) - angle_label.shift(SMALL_BUFF * UR) - - circle_group = VGroup( - circle, chord, radius_line, one_label, - brace, f_label, chord_f, - half_chords, height, - angle_arc, angle_label, - ) - - formula = TexMobject( - "= 2 \\cdot \\sin\\left(\\frac{f}{2} 2\\pi \\right)", - "= 2 \\cdot \\sin\\left(f \\pi \\right)", - ) - for part in formula: - part[7].set_color(YELLOW) - - # Draw circle and chord - self.add(radius_line, circle, one_label) - self.play(Write(full_chord_f)) - self.play(ShowCreation(chord)) - self.play( - MoveToTarget(chord_f), - FadeOut(VGroup(full_chord_f[0], full_chord_f[-1])) - ) - self.play(GrowFromEdge(brace, DOWN)) - self.play(MoveToTarget(brace, path_arc=TAU / 3)) - self.play(Write(f_label)) - self.wait(2) - - # Show third - self.remove(chord_f, f_label) - self.play( - ReplacementTransform(chord_f.copy(), chord_third), - ReplacementTransform(f_label.copy(), third_label), - ) - chord_copies = VGroup() - last_chord = chord - for color in PINK, BLUE: - chord_copy = last_chord.copy() - old_color = chord_copy.get_color() - self.play( - Rotate(chord_copy, -TAU / 6, about_point=last_chord.get_end()), - UpdateFromAlphaFunc( - chord_copy, - lambda m, a: m.set_stroke( - interpolate_color(old_color, color, a)) - ) - ) - chord_copy.reverse_points() - last_chord = chord_copy - chord_copies.add(chord_copy) - self.wait() - self.play( - FadeOut(chord_copies), - ReplacementTransform(chord_third, chord_f), - ReplacementTransform(third_label, f_label), - ) - - # Show sine formula - top_chord_f = chord_f.copy() - top_chord_f.generate_target() - top_chord_f.target.rotate(-chord_angle) - top_chord_f.target.center().to_edge(UP, buff=LARGE_BUFF) - top_chord_f.target.shift(3 * LEFT) - formula.next_to(top_chord_f.target, RIGHT) - - self.play( - ShowCreation(height), - FadeIn(half_chords), - ShowCreation(angle_arc), - Write(angle_label) - ) - self.wait() - self.play( - MoveToTarget(top_chord_f), - circle_group.shift, 1.5 * DOWN, - ) - self.play(Write(formula[0], run_time=1)) - self.wait() - self.play(ReplacementTransform( - formula[0].copy(), formula[1], - path_arc=45 * DEGREES - )) - self.wait() - - -class DistanceProductIsChordF(PlugObserverIntoPolynomial): - CONFIG = { - "include_lighthouses": False, - "num_lighthouses": 8, - # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, - # "add_lights_in_foreground": False, - } - - def construct(self): - self.add_plane() - self.add_circle_group() - self.add_polynomial("O") - self.show_all_animations() - - def show_all_animations(self): - fraction = self.observer_fraction = 0.3 - circle = self.circle - - O_dot = self.observer_dot = Dot() - O_dot.set_color(self.observer_config["color"]) - O_to_N_dot = O_dot.copy() - O_dot.move_to(self.get_circle_point_at_proportion( - fraction / self.num_lighthouses)) - O_to_N_dot.move_to(self.get_circle_point_at_proportion(fraction)) - - for dot, vect, tex in [(O_dot, DL, "O"), (O_to_N_dot, DR, "O^N")]: - arrow = Vector(0.5 * vect, color=WHITE) - arrow.next_to(dot.get_center(), -vect, SMALL_BUFF) - label = TexMobject(tex) - O_part = label[0] - O_part.match_color(dot) - label.add_background_rectangle() - label.next_to(arrow.get_start(), -vect, buff=0, - submobject_to_align=O_part) - dot.arrow = arrow - dot.label = label - self.add_foreground_mobject(dot) - self.add(arrow, label) - # For the transition to f = 1 / 2 - dot.generate_target() - - fraction_words = VGroup( - TextMobject("$f$", "of the way"), - TextMobject("between lighthouses") - ) - fraction_words.scale(0.8) - fraction_words[0][0].set_color(YELLOW) - fraction_words.arrange(DOWN, SMALL_BUFF, aligned_edge=LEFT) - fraction_words.next_to(O_dot.label, RIGHT) - list(map(TexMobject.add_background_rectangle, fraction_words)) - - f_arc, new_arc = [ - Arc( - angle=(TAU * f / self.num_lighthouses), - radius=self.get_radius(), - color=YELLOW, - ).shift(circle.get_center()) - for f in (fraction, 0.5) - ] - self.add(f_arc) - - lines = self.lines = self.get_lines() - labels = self.get_numeric_distance_labels() - - black_rect = Rectangle(height=6, width=3.5) - black_rect.set_stroke(width=0) - black_rect.set_fill(BLACK, 0.8) - black_rect.to_corner(DL, buff=0) - colum_group = self.get_distance_product_column( - column_top=black_rect.get_top() + MED_SMALL_BUFF * DOWN - ) - stacked_labels, h_line, times, product_decimal = colum_group - - chord = Line(*[ - self.get_circle_point_at_proportion(f) - for f in (0, fraction) - ]) - chord.set_stroke(YELLOW) - chord_f = get_chord_f_label(chord) - chord_f_as_product = chord_f.copy() - chord_f_as_product.generate_target() - chord_f_as_product.target.rotate(-chord_f_as_product.angle) - chord_f_as_product.target.scale(0.8) - chord_f_as_product.target.move_to(product_decimal, RIGHT) - - # Constructs for the case f = 1 / 2 - new_chord = Line(circle.get_right(), circle.get_left()) - new_chord.match_style(chord) - chord_half = get_chord_f_label(new_chord, "1/2") - - f_terms = VGroup(fraction_words[0][1][0], chord_f_as_product[1][1]) - half_terms = VGroup(*[ - TexMobject("\\frac{1}{2}").scale(0.6).set_color(YELLOW).move_to(f) - for f in f_terms - ]) - half_terms[1].move_to(chord_f_as_product.target[1][1]) - - O_dot.target.move_to(self.get_circle_point_at_proportion( - 0.5 / self.num_lighthouses)) - O_to_N_dot .target.move_to(circle.get_left()) - self.observer_dot = O_dot.target - new_lines = self.get_lines() - - changing_decimals = [] - radius = self.get_radius() - for decimal, line in zip(stacked_labels, new_lines): - changing_decimals.append( - ChangeDecimalToValue(decimal, line.get_length() / radius) - ) - - equals_two_terms = VGroup(*[ - TexMobject("=2").next_to(mob, DOWN, SMALL_BUFF) - for mob in (chord_half, chord_f_as_product.target) - ]) - - # Animations - - self.play(Write(fraction_words)) - self.wait() - self.play( - LaggedStartMap(ShowCreation, lines), - LaggedStartMap(FadeIn, labels), - ) - self.play( - FadeIn(black_rect), - ReplacementTransform(labels.copy(), stacked_labels), - ShowCreation(h_line), - Write(times), - ) - self.wait(2) - self.add_foreground_mobjects( - chord_f[1], chord, O_dot, O_to_N_dot - ) - self.play( - FadeOut(labels), - ShowCreation(chord), - FadeIn(chord_f), - ) - self.play(MoveToTarget(chord_f_as_product)) - self.wait(2) - - # Transition to f = 1 / 2 - self.play( - Transform(lines, new_lines), - Transform(f_arc, new_arc), - Transform(chord, new_chord), - chord_f.rotate, -chord_f.angle, - chord_f.move_to, chord_half, - MoveToTarget(O_dot), - MoveToTarget(O_to_N_dot), - MaintainPositionRelativeTo(O_dot.arrow, O_dot), - MaintainPositionRelativeTo(O_dot.label, O_dot), - MaintainPositionRelativeTo(O_to_N_dot.arrow, O_to_N_dot), - MaintainPositionRelativeTo(O_to_N_dot.label, O_to_N_dot), - *changing_decimals, - path_arc=(45 * DEGREES), - run_time=2 - ) - self.play( - Transform(chord_f, chord_half), - Transform(f_terms, half_terms), - ) - self.wait() - for term in equals_two_terms: - term.add_background_rectangle() - self.add_foreground_mobject(term[1]) - self.play( - Write(equals_two_terms) - ) - self.wait() - - -class ProveLemma2(PlugObserverIntoPolynomial): - CONFIG = { - "include_lighthouses": False, - "num_lighthouses": 8, - # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, - # "add_lights_in_foreground": False, - } - - def construct(self): - self.add_plane() - self.add_circle_group() - self.add_polynomial("O") - - self.replace_first_lighthouse() - self.rearrange_polynomial() - self.plug_in_one() - - def replace_first_lighthouse(self): - light_to_remove = self.lights[0] - dot = self.observer_dot = Dot(color=self.observer_config["color"]) - dot.move_to(self.get_circle_point_at_proportion( - 0.5 / self.num_lighthouses)) - arrow = Vector(0.5 * DL, color=WHITE) - arrow.next_to(dot, UR, SMALL_BUFF) - O_label = self.O_dot_label = TexMobject("O") - O_label.match_color(dot) - O_label.add_background_rectangle() - O_label.next_to(arrow, UR, SMALL_BUFF) - - # First, move the lighthouse - self.add_foreground_mobject(dot) - self.play( - dot.move_to, light_to_remove, - MaintainPositionRelativeTo(arrow, dot), - MaintainPositionRelativeTo(O_label, dot), - path_arc=-TAU / 2 - ) - - black_rect = Rectangle( - height=6, width=3.5, - stroke_width=0, - fill_color=BLACK, - fill_opacity=1, - ) - black_rect.to_corner(DL, buff=0) - lines = self.get_lines(self.circle.get_right()) - labels = self.get_numeric_distance_labels() - column_group = self.get_distance_product_column( - black_rect.get_top() + MED_SMALL_BUFF * DOWN - ) - stacked_labels, h_line, times, product_decimal = column_group - q_marks = self.q_marks = TextMobject("???") - q_marks.move_to(product_decimal, LEFT) - q_marks.match_color(product_decimal) - - zero_rects = VGroup( - *list(map(SurroundingRectangle, [dot, stacked_labels[0]]))) - - self.play( - LaggedStartMap(ShowCreation, lines), - LaggedStartMap(FadeIn, labels), - ) - self.play( - FadeIn(black_rect), - ShowCreation(h_line), - Write(times), - ReplacementTransform(labels.copy(), stacked_labels) - ) - self.wait() - self.play(ReplacementTransform( - stacked_labels.copy(), - VGroup(product_decimal) - )) - self.wait() - self.add_foreground_mobject(zero_rects) - self.play(*list(map(ShowCreation, zero_rects))) - self.wait(2) - self.play( - VGroup(light_to_remove, zero_rects[0] - ).shift, FRAME_WIDTH * RIGHT / 2, - path_arc=-60 * DEGREES, - rate_func=running_start, - remover=True - ) - self.play( - VGroup(stacked_labels[0], zero_rects[1]).shift, 4 * LEFT, - rate_func=running_start, - remover=True, - ) - self.remove_foreground_mobjects(zero_rects) - self.play( - FadeOut(product_decimal), - FadeIn(q_marks) - ) - self.play(FadeOut(labels)) - self.wait() - - def rearrange_polynomial(self): - dot = self.observer_dot - lhs, equals, rhs = self.get_polynomial_split(self.polynomial) - polynomial_background = self.polynomial[0] - first_factor = rhs[:5] - remaining_factors = rhs[5:] - equals_remaining_factors = VGroup(equals, remaining_factors) - - # first_factor_rect = SurroundingRectangle(first_factor) - lhs_rect = SurroundingRectangle(lhs) - - frac_line = Line(LEFT, RIGHT, color=WHITE) - frac_line.match_width(lhs, stretch=True) - frac_line.next_to(lhs, DOWN, SMALL_BUFF) - O_minus_1 = TexMobject("\\left(", "O", "-", "1", "\\right)") - O_minus_1.next_to(frac_line, DOWN, SMALL_BUFF) - new_lhs_background = BackgroundRectangle( - VGroup(lhs, O_minus_1), buff=SMALL_BUFF) - new_lhs_rect = SurroundingRectangle(VGroup(lhs, O_minus_1)) - - roots_of_unity_circle = VGroup(*[ - Circle(radius=0.2, color=YELLOW).move_to(point) - for point in self.get_lh_points() - ]) - for circle in roots_of_unity_circle: - circle.save_state() - circle.scale(4) - circle.fade(1) - - self.play(ShowCreation(lhs_rect)) - self.add_foreground_mobject(roots_of_unity_circle) - self.play(LaggedStartMap( - ApplyMethod, roots_of_unity_circle, - lambda m: (m.restore,) - )) - self.wait() - frac_line_copy = frac_line.copy() - self.play( - FadeIn(new_lhs_background), - polynomial_background.stretch, 0.8, 0, - polynomial_background.move_to, frac_line_copy, LEFT, - equals_remaining_factors.arrange, RIGHT, SMALL_BUFF, - equals_remaining_factors.next_to, frac_line_copy, RIGHT, MED_SMALL_BUFF, - ReplacementTransform(first_factor, O_minus_1, - path_arc=-90 * DEGREES), - ShowCreation(frac_line), - Animation(lhs), - ReplacementTransform(lhs_rect, new_lhs_rect), - ) - self.play( - roots_of_unity_circle[0].shift, FRAME_WIDTH * RIGHT / 2, - path_arc=(-60 * DEGREES), - rate_func=running_start, - remover=True - ) - - # Expand rhs - expanded_rhs = self.expanded_rhs = TexMobject( - "=", "1", "+", - "O", "+", - "O", "^2", "+", - "\\cdots", - "O", "^{N-1}" - ) - expanded_rhs.next_to(frac_line, RIGHT) - expanded_rhs.shift(LEFT) - expanded_rhs.scale(0.9) - expanded_rhs.set_color_by_tex("O", dot.get_color()) - - self.play( - polynomial_background.stretch, 1.8, 0, {"about_edge": LEFT}, - FadeIn(expanded_rhs), - equals_remaining_factors.scale, 0.9, - equals_remaining_factors.next_to, expanded_rhs, - VGroup( - new_lhs_background, lhs, frac_line, O_minus_1, - new_lhs_rect, - ).shift, LEFT, - ) - self.wait() - - def plug_in_one(self): - expanded_rhs = self.expanded_rhs - O_terms = expanded_rhs.get_parts_by_tex("O") - ones = VGroup(*[ - TexMobject("1").move_to(O_term, RIGHT) - for O_term in O_terms - ]) - ones.match_color(O_terms[0]) - - equals_1 = TexMobject("= 1") - equals_1.next_to(self.O_dot_label, RIGHT, SMALL_BUFF) - brace = Brace(expanded_rhs[1:], DOWN) - N_term = brace.get_text("N") - - product = DecimalNumber( - self.num_lighthouses, - num_decimal_places=3, - show_ellipsis=True - ) - product.move_to(self.q_marks, LEFT) - - self.play(Write(equals_1)) - self.play( - FocusOn(brace), - GrowFromCenter(brace) - ) - self.wait(2) - self.play(ReplacementTransform(O_terms, ones)) - self.wait() - self.play(Write(N_term)) - self.play(FocusOn(product)) - self.play( - FadeOut(self.q_marks), - FadeIn(product) - ) - self.wait() - - -class LocalMathematician(PiCreatureScene): - def construct(self): - randy, mathy = self.pi_creatures - screen = ScreenRectangle(height=2) - screen.to_corner(UL) - screen.fade(1) - - mathy_name = TextMobject("Local \\\\ mathematician") - mathy_name.next_to(mathy, LEFT, LARGE_BUFF) - arrow = Arrow(mathy_name, mathy) - - self.play( - Animation(screen), - mathy.change, "pondering", - PiCreatureSays( - randy, "Check these \\\\ out!", - target_mode="surprised", - bubble_kwargs={"height": 3, "width": 4}, - look_at_arg=screen, - ), - ) - self.play( - Animation(screen), - RemovePiCreatureBubble( - randy, - target_mode="raise_right_hand", - look_at_arg=screen, - ) - ) - self.wait(2) - self.play( - PiCreatureSays( - mathy, "Ah yes, consider \\\\ $x^n - 1$ over $\\mathds{C}$...", - look_at_arg=randy.eyes - ), - randy.change, "happy", mathy.eyes - ) - self.wait(3) - - def create_pi_creatures(self): - randy = Randolph().flip() - mathy = Mathematician() - randy.scale(0.9) - randy.to_edge(DOWN).shift(4 * RIGHT) - mathy.to_edge(DOWN).shift(4 * LEFT) - return randy, mathy - - -class ArmedWithTwoKeyFacts(TeacherStudentsScene, DistanceProductScene): - CONFIG = { - "num_lighthouses": 6, - "ambient_light_config": { - "opacity_function": inverse_power_law(1, 1, 1, 6), - "radius": 1, - "num_levels": 100, - "max_opacity": 1, - }, - } - - def setup(self): - TeacherStudentsScene.setup(self) - DistanceProductScene.setup(self) - - def construct(self): - circle1 = self.circle - circle1.set_height(1.5) - circle1.to_corner(UL) - circle2 = circle1.copy() - circle2.next_to(circle1, DOWN, MED_LARGE_BUFF) - - wallis_product = get_wallis_product(n_terms=8) - - N = self.num_lighthouses - labels = VGroup() - for circle, f, dp in (circle1, 0.5, "2"), (circle2, 0, "N"): - self.circle = circle - lights = self.get_lights() - if f == 0: - lights.submobjects.pop(0) - observer = Dot(color=MAROON_B) - frac = f / N - point = self.get_circle_point_at_proportion(frac) - observer.move_to(point) - lines = self.get_distance_lines(point, line_class=DashedLine) - - label = TextMobject("Distance product = %s" % dp) - label.scale(0.7) - label.next_to(circle, RIGHT) - labels.add(label) - - group = VGroup(lines, observer, label) - self.play( - FadeIn(circle), - LaggedStartMap(FadeIn, VGroup(*it.chain(lights))), - LaggedStartMap( - FadeIn, VGroup( - *it.chain(group.family_members_with_points())) - ), - self.teacher.change, "raise_right_hand", - self.get_student_changes(*["pondering"] * 3) - ) - wallis_product.move_to(labels).to_edge(RIGHT) - self.play( - LaggedStartMap(FadeIn, wallis_product), - self.teacher.change_mode, "hooray", - self.get_student_changes( - *["thinking"] * 3, look_at_arg=wallis_product) - ) - self.wait(2) - - -class Sailor(PiCreature): - CONFIG = { - "flip_at_start": True, - "color": YELLOW_D, - "hat_height_factor": 1.0 / 6, - } - - def __init__(self, *args, **kwargs): - PiCreature.__init__(self, *args, **kwargs) - height = self.get_height() * self.hat_height_factor - sailor_hat = SVGMobject(file_name="sailor_hat", height=height) - # Rhombus is a horrible hack... - rhombus = Polygon( - UP, UP + 2 * RIGHT, - 1.75 * RIGHT + 0.5 * UP, 0.5 * RIGHT + 0.1 * DOWN, - 1.25 * LEFT + 0.15 * DOWN, - ) - rhombus.set_fill(BLACK, opacity=1) - rhombus.set_stroke(width=0) - rhombus.set_height(sailor_hat.get_height() / 3) - rhombus.rotate(5 * DEGREES) - rhombus.move_to(sailor_hat, DR) - rhombus.shift(0.05 * sailor_hat.get_width() * LEFT) - sailor_hat.add_to_back(rhombus) - sailor_hat.rotate(-15 * DEGREES) - sailor_hat.move_to(self.eyes.get_center(), DOWN) - sailor_hat.shift( - 0.1 * self.eyes.get_width() * RIGHT, - 0.1 * self.eyes.get_height() * UP, - ) - self.add(sailor_hat) - - -class KeeperAndSailor(DistanceProductScene, PiCreatureScene): - CONFIG = { - "num_lighthouses": 9, - "circle_radius": 2.75, - # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, - "add_lights_in_foreground": False, # Keep this way - "text_scale_val": 0.7, - "observer_fraction": 0.5, - "keeper_color": BLUE, - "sailor_color": YELLOW_D, - "include_distance_labels_background_rectangle": False, - "big_circle_center": FRAME_WIDTH * LEFT / 2 + SMALL_BUFF * RIGHT, - } - - def setup(self): - DistanceProductScene.setup(self) - PiCreatureScene.setup(self) - self.remove(*self.pi_creatures) - - def construct(self): - self.place_lighthouses() - self.introduce_observers() - self.write_distance_product_fraction() - self.break_down_distance_product_by_parts() - self.grow_circle_and_N() - self.show_limit_for_each_fraction() - self.show_limit_of_lhs() - - def place_lighthouses(self): - circle = self.circle - circle.to_corner(DL) - circle.shift(MED_SMALL_BUFF * UR) - circle.set_color(RED) - - lighthouses = self.get_lighthouses() - lights = self.get_lights() - for light in lights: - dot = Dot(radius=0.06).move_to(light) - dot.match_color(light) - light.add_to_back(dot) - origin = circle.get_center() - arrows = VGroup(*[ - Arrow(0.6 * (p - origin), 0.9 * (p - origin), buff=0).shift(origin) - for p in self.get_lh_points() - ]) - arrows.set_color(WHITE) - - words = TextMobject("N evenly-spaced \\\\ lighthouses") - words.scale(0.8) - words.move_to(origin) - - self.add(circle) - if self.add_lights_in_foreground: - self.add_foreground_mobject(lights) - self.add_foreground_mobject(words) - self.play( - LaggedStartMap(FadeIn, VGroup(*it.chain(lights))), - LaggedStartMap(FadeIn, lighthouses), - LaggedStartMap(GrowArrow, arrows), - ) - self.remove_foreground_mobjects(words) - self.play(FadeOut(words), FadeOut(arrows)) - self.wait() - - def introduce_observers(self): - keeper, sailor = observers = self.observers - keeper.target_point = self.get_keeper_point() - sailor.target_point = self.get_sailor_point() - - for pi, text in (keeper, "Keeper"), (sailor, "Sailor"): - pi.title = TextMobject(text) - pi.title.next_to(pi, DOWN) - pi.dot = Dot() - pi.dot.match_color(pi) - pi.dot.next_to(pi, LEFT) - pi.dot.set_fill(opacity=0) - - self.play(LaggedStartMap( - Succession, observers, - lambda m: (FadeIn, m, ApplyMethod, m.change, "wave_1") - )) - for pi in observers: - self.play( - FadeIn(pi.title), - pi.change, "plain" - ) - self.wait() - if self.add_lights_in_foreground: - self.add_foreground_mobjects(keeper, keeper.dot, keeper.title) - for pi in observers: - self.play( - pi.set_height, 0.5, - pi.next_to, pi.target_point, RIGHT, SMALL_BUFF, - pi.dot.move_to, pi.target_point, - pi.dot.set_fill, {"opacity": 1}, - pi.title.scale, self.text_scale_val, - pi.title.next_to, pi.target_point, RIGHT, {"buff": 0.6}, - ) - if pi is sailor: - arcs = self.get_halfway_indication_arcs() - self.play(*list(map(ShowCreationThenDestruction, arcs))) - self.wait() - - def write_distance_product_fraction(self): - fraction = self.distance_product_fraction = TexMobject( - "{\\text{Keeper's distance product}", "\\over", - "\\text{Sailor's distance product}}" - ) - fraction.scale(self.text_scale_val) - fraction.to_corner(UR) - - keeper_lines = self.get_distance_lines( - self.get_keeper_point(), - line_class=DashedLine - ) - sailor_lines = self.get_distance_lines( - self.get_sailor_point(), - line_class=DashedLine - ) - sailor_line_lengths = self.get_numeric_distance_labels(sailor_lines) - keeper_line_lengths = self.get_numeric_distance_labels(keeper_lines) - sailor_dp_column, keeper_dp_column = [ - self.get_distance_product_column( - 4 * RIGHT + 1.5 * UP, labels, frac - ) - for labels, frac in [ - (sailor_line_lengths, 0.5), - (keeper_line_lengths, 0), - ] - ] - sailor_dp_decimal = sailor_dp_column[-1] - sailor_dp_decimal_rect = SurroundingRectangle(sailor_dp_decimal) - keeper_dp_decimal = keeper_dp_column[-1] - keeper_dp_decimal_rect = SurroundingRectangle(keeper_dp_decimal) - keeper_top_zero_rect = SurroundingRectangle(keeper_dp_column[0][0]) - - # stacked_labels, h_line, times, product_decimal = column - - # Define result fraction - equals = self.distance_product_equals = TexMobject("=") - result_fraction = self.result_fraction = TexMobject( - "{N", "{\\text{distance} \\choose \\text{between obs.}}", "\\over", "2}" - ) - N, dist, frac_line, two = result_fraction - result_fraction.to_corner(UR) - equals.next_to(frac_line, LEFT) - for part in result_fraction: - part.save_state() - part.generate_target() - div = TexMobject("/") - first_denom = VGroup(two.target, div, dist) - first_denom.arrange(RIGHT, buff=SMALL_BUFF) - first_denom.move_to(two, UP) - N.next_to(frac_line, UP, SMALL_BUFF) - - # Define terms to be removed - first_light_group = VGroup(self.lights[0], self.lighthouses[0]) - keeper_top_zero_group = VGroup( - keeper_dp_column[0][0], keeper_top_zero_rect) - - new_keeper_dp_decimal = DecimalNumber( - self.num_lighthouses, - num_decimal_places=3, - ) - new_keeper_dp_decimal.replace(keeper_dp_decimal, dim_to_match=1) - new_keeper_dp_decimal.set_color(YELLOW) - - self.play(*list(map(ShowCreation, keeper_lines))) - self.play(ReplacementTransform( - keeper_lines.copy(), VGroup(fraction[0]) - )) - self.play(FadeOut(keeper_lines)) - self.play(*list(map(ShowCreation, sailor_lines))) - self.play( - ReplacementTransform( - sailor_lines.copy(), - VGroup(fraction[2]) - ), - ShowCreation(fraction[1]) - ) - self.wait() - self.play(LaggedStartMap(FadeIn, sailor_line_lengths)) - self.play(ReplacementTransform( - sailor_line_lengths.copy(), sailor_dp_column[0] - )) - self.play(FadeIn(sailor_dp_column[1:])) - self.play(ShowCreation(sailor_dp_decimal_rect)) - self.play( - fraction.next_to, equals, LEFT, - FadeIn(equals), - ShowCreation(frac_line), - ReplacementTransform(sailor_dp_decimal.copy(), two), - FadeOut(sailor_dp_decimal_rect) - ) - self.wait() - - # Note, sailor_lines and sailor_line_lengths get changed here - self.remove(*list(sailor_lines) + list(sailor_line_lengths)) - self.play( - FadeOut(sailor_dp_column), - ReplacementTransform(sailor_lines.deepcopy(), keeper_lines), - ReplacementTransform( - sailor_line_lengths.deepcopy(), keeper_line_lengths), - ) - self.play(ReplacementTransform( - keeper_line_lengths.copy(), keeper_dp_column[0] - )) - self.play(FadeIn(keeper_dp_column[1:])) - self.wait() - self.play( - ShowCreation(keeper_dp_decimal_rect), - ShowCreation(keeper_top_zero_rect) - ) - self.wait(2) - - # Remove first lighthouse - self.play( - first_light_group.shift, 0.6 * FRAME_WIDTH * RIGHT, - keeper_top_zero_group.shift, 0.4 * FRAME_WIDTH * RIGHT, - FadeOut(keeper_dp_decimal), - FadeOut(keeper_dp_decimal_rect), - path_arc=-30 * DEGREES, - rate_func=running_start, - ) - self.remove(first_light_group, keeper_top_zero_group) - self.wait() - self.play(ReplacementTransform( - keeper_dp_column[0][1:].copy(), - VGroup(new_keeper_dp_decimal), - )) - self.wait() - self.play(ReplacementTransform(new_keeper_dp_decimal.copy(), N,)) - self.wait(2) - - sailor_lines[0].set_color(RED) - sailor_line_lengths[0].set_color(RED) - sailor_line_lengths[0].set_stroke(RED, 1) - self.remove(*list(keeper_lines) + list(keeper_line_lengths)) - self.play( - ReplacementTransform(keeper_lines.copy(), sailor_lines), - ReplacementTransform( - keeper_line_lengths.copy(), sailor_line_lengths), - FadeOut(keeper_dp_column[:-1]), - FadeOut(new_keeper_dp_decimal), - ) - self.play( - Rotate(sailor_line_lengths[0], 30 * DEGREES, rate_func=wiggle) - ) - self.wait() - self.play( - ReplacementTransform(sailor_lines[0].copy(), dist), - FadeIn(div), - MoveToTarget(two), - ) - self.wait() - self.play( - two.restore, - FadeOut(div), - dist.restore, - N.restore, - ) - self.play( - FadeOut(sailor_lines), - FadeOut(sailor_line_lengths), - ) - self.wait() - - def break_down_distance_product_by_parts(self): - result_fraction = self.result_fraction - result_fraction_rect = SurroundingRectangle(result_fraction) - - product_parts = self.product_parts = TexMobject( - "{|L_1 - K|", "\\over", "|L_1 - S|}", "\\cdot", - "{|L_2 - K|", "\\over", "|L_2 - S|}", "\\cdot", - "{|L_3 - K|", "\\over", "|L_3 - S|}", "\\cdots", - ) - product_parts.set_color_by_tex_to_color_map({ - "K": BLUE, - "S": YELLOW, - }) - product_parts.set_width(0.4 * FRAME_WIDTH) - product_parts.next_to(result_fraction, DOWN, LARGE_BUFF, RIGHT) - product_parts.shift(MED_SMALL_BUFF * RIGHT) - - sailor_lines = self.get_sailor_lines() - sailor_lines.save_state() - keeper_lines = self.get_keeper_lines() - keeper_lines.save_state() - - sailor_length_braces = VGroup(VMobject()) # Add fluff first object - keeper_length_braces = VGroup(VMobject()) # Add fluff first object - triplets = [ - ("S", sailor_length_braces, DOWN), - ("K", keeper_length_braces, UP), - ] - for char, brace_group, vect in triplets: - for part in product_parts.get_parts_by_tex(char): - brace = Brace(part, vect, buff=SMALL_BUFF) - brace.match_color(part) - brace_group.add(brace) - - # Animations - self.replace_lighthouses_with_labels() - self.play( - LaggedStartMap(FadeIn, product_parts), - LaggedStartMap(FadeIn, sailor_lines, - rate_func=there_and_back, remover=True), - LaggedStartMap(FadeIn, keeper_lines, - rate_func=there_and_back, remover=True), - ) - sailor_lines.restore() - keeper_lines.restore() - self.wait() - - keeper_line = self.keeper_line = keeper_lines[1].copy() - sailor_line = self.sailor_line = sailor_lines[1].copy() - keeper_brace = keeper_length_braces[1].copy() - sailor_brace = sailor_length_braces[1].copy() - self.play( - ShowCreation(keeper_line), - GrowFromCenter(keeper_brace), - ) - self.wait() - self.play( - ShowCreation(sailor_line), - GrowFromCenter(sailor_brace), - ) - self.wait() - for i in range(2, 4): - self.play( - Transform(keeper_line, keeper_lines[i]), - Transform(keeper_brace, keeper_length_braces[i]), - ) - self.play( - Transform(sailor_line, sailor_lines[i]), - Transform(sailor_brace, sailor_length_braces[i]), - ) - self.wait() - for i in range(4, self.num_lighthouses): - anims = [ - Transform(keeper_line, keeper_lines[i]), - Transform(sailor_line, sailor_lines[i]), - ] - if i == 4: - anims += [ - FadeOut(sailor_brace), - FadeOut(keeper_brace), - ] - self.play(*anims) - self.play(FocusOn(result_fraction)) - self.play(ShowPassingFlash(result_fraction_rect)) - self.wait(3) - - def grow_circle_and_N(self, circle_scale_factor=2, N_multiple=3, added_anims=None): - if added_anims is None: - added_anims = [] - circle = self.circle - lights = self.lights - labels = self.lighthouse_labels - keeper = self.keeper - sailor = self.sailor - half_N = self.num_lighthouses / 2 - - anims = [] - - circle.generate_target() - for pi in keeper, sailor: - for mob in pi, pi.dot, pi.title: - mob.generate_target() - - circle.target.scale(circle_scale_factor) - circle.target.move_to(self.big_circle_center) - self.circle = circle.target - anims.append(MoveToTarget(circle)) - - self.num_lighthouses = int(N_multiple * self.num_lighthouses) - new_lights = self.get_lights() - for light in new_lights: - light.scale(1.0 / circle_scale_factor) - new_labels = self.get_light_labels() - anims.append(ReplacementTransform(labels[1:], new_labels[1:])) - - if hasattr(self, "keeper_line"): - keeper_line = self.keeper_line - sailor_line = self.sailor_line - self.keeper_lines = self.get_keeper_lines() - self.sailor_lines = self.get_sailor_lines() - anims += [ - Transform(keeper_line, self.keeper_lines[-1]), - Transform(sailor_line, self.sailor_lines[-1]), - ] - - for group in lights, labels, new_lights, new_labels: - group[0].fade(1) - - for mob in lights, labels: - for x in range(len(new_lights) - len(mob)): - mob.submobjects.insert( - half_N + 1, VectorizedPoint(circle.get_left())) - anims.append(ReplacementTransform(lights, new_lights)) - - keeper.dot.target.move_to(self.get_keeper_point()) - sailor.dot.target.move_to(self.get_sailor_point()) - for pi in keeper, sailor: - pi.target.scale(0) - pi.target.move_to(pi.dot.target) - pi.title.target.scale(0.85) - pi.title.target.next_to(pi.dot.target, RIGHT, SMALL_BUFF) - anims += [ - MoveToTarget(part) - for pi in self.observers - for part in [pi, pi.dot, pi.title] - ] - - anims += added_anims - - self.circle = circle - - self.play(*anims, run_time=2) - if self.add_lights_in_foreground: - self.remove_foreground_mobjects(*self.lights) - self.remove_foreground_mobjects(*self.lighthouse_labels) - self.add_foreground_mobjects(new_lights, new_labels) - self.wait() - self.lights = new_lights - self.lighthouse_labels = new_labels - - def show_limit_for_each_fraction(self): - product_parts = self.product_parts - keeper_line = self.keeper_line - keeper_lines = self.keeper_lines - sailor_line = self.sailor_line - sailor_lines = self.sailor_lines - labels = self.lighthouse_labels - - center = self.circle.get_center() - center_dot = Dot(center) - lh_points = self.get_lh_points() - sailor_point = self.get_sailor_point() - keeper_point = self.get_keeper_point() - - def get_angle_mob(p1, p2): - angle1 = angle_of_vector(p1 - center) - angle2 = angle_of_vector(p2 - center) - arc = Arc(start_angle=angle1, angle=(angle2 - angle1), radius=1) - arc.shift(center) - return VGroup( - center_dot, - Line(center, p1), - Line(center, p2), - arc, - ) - - angle_mob = get_angle_mob(lh_points[1], keeper_point) - - ratios = VGroup(*[ - product_parts[i:i + 3] - for i in [0, 4, 8] - ]) - term_rects = self.get_term_rects(ratios) - - limit_fractions = VGroup( - TexMobject("{2", "\\over", "1}"), - TexMobject("{4", "\\over", "3}"), - TexMobject("{6", "\\over", "5}"), - ) - limit_arrows = VGroup() - for rect, fraction in zip(term_rects, limit_fractions): - fraction.next_to(rect, DOWN, LARGE_BUFF) - arrow = Arrow(rect, fraction, color=WHITE) - limit_arrows.add(arrow) - - approx = TexMobject("\\approx") - approx.scale(1.5) - approx.rotate(90 * DEGREES) - approx.move_to(limit_arrows[0]) - - braces = self.get_all_circle_braces() - - # Show first lighthouse - term_rect = term_rects[0].copy() - self.play( - Transform(keeper_line, keeper_lines[1]), - Transform(sailor_line, sailor_lines[1]), - FadeIn(term_rect), - path_arc=-180 * DEGREES - ) - self.wait(2) - self.play( - FadeOut(VGroup(keeper_line, sailor_line)), - FadeIn(braces[:2]), - FadeIn(angle_mob) - ) - self.wait() - self.play(Transform(angle_mob, get_angle_mob( - lh_points[1], sailor_point))) - self.wait(2) - self.play( - Write(approx), - ReplacementTransform(ratios[0].copy(), limit_fractions[0]), - FadeOut(angle_mob) - ) - self.wait() - self.play(ReplacementTransform(approx, limit_arrows[0])) - self.let_N_approach_infinity(braces[:2]) - - # Show second lighthouse - self.play( - Transform(term_rect, term_rects[1]), - ReplacementTransform(limit_arrows[0].copy(), limit_arrows[1]), - FadeIn(braces[2:4]) - ) - for group, color in (braces[:4], self.keeper_color), (braces[1:4], self.sailor_color): - self.play( - group.scale, 0.95, {"about_point": center}, - group.set_color, color, - rate_func=there_and_back - ) - self.wait(0.5) - self.play( - ReplacementTransform(ratios[1].copy(), limit_fractions[1]) - ) - self.wait() - - # Show third lighthouse - braces[4:6].set_color(YELLOW) - self.play( - Transform(term_rect, term_rects[2]), - ReplacementTransform(limit_arrows[1].copy(), limit_arrows[2]), - FadeIn(braces[4:6]), - braces[1:4].set_color, YELLOW, - ReplacementTransform(limit_fractions[1].copy(), limit_fractions[2]) - ) - self.let_N_approach_infinity(braces[:6]) - self.wait() - - # Set up for lighthouse "before" keeper - ccw_product_group = VGroup( - product_parts, limit_arrows, limit_fractions) - cw_product_parts = TexMobject( - "\\cdots", "{|L_{-3} - K|", "\\over", "|L_{-3} - S|}", - "\\cdot", "{|L_{-2} - K|", "\\over", "|L_{-2} - S|}", - "\\cdot", "{|L_{-1} - K|", "\\over", "|L_{-1} - S|}", - ) - cw_product_parts.match_height(product_parts) - cw_product_parts.set_color_by_tex_to_color_map({ - "K": BLUE, - "S": YELLOW, - }) - cw_product_parts.move_to(ratios, RIGHT) - cw_ratios = VGroup(*[cw_product_parts[i:i + 3] for i in (9, 5, 1)]) - cw_term_rects = self.get_term_rects(cw_ratios) - cw_limit_fractions = VGroup( - TexMobject("{2", "\\over", "3}"), - TexMobject("{4", "\\over", "5}"), - TexMobject("{6", "\\over", "7}"), - ) - cw_limit_arrows = VGroup() - for rect, fraction in zip(cw_term_rects, cw_limit_fractions): - fraction.next_to(rect, DOWN, LARGE_BUFF) - arrow = Arrow(rect, fraction, color=WHITE) - cw_limit_arrows.add(arrow) - - cw_product_parts.save_state() - cw_product_parts.next_to(product_parts, RIGHT, LARGE_BUFF) - - cw_label_rects = self.get_term_rects(labels[-1:-5:-1]) - cw_label_rects.set_color(RED) - - braces[-8:].set_color(BLUE) - braces[0].set_color(YELLOW) - - def show_braces(n): - cw_group = braces[-2 * n:] - for group in cw_group, VGroup(braces[0], *cw_group): - self.play( - group.scale, 0.95, {"about_point": center}, - rate_func=there_and_back - ) - self.wait(0.5) - - # Animated clockwise-from-keeper terms - self.play( - ccw_product_group.scale, 0.5, {"about_edge": UL}, - ccw_product_group.to_corner, UL, - FadeOut(term_rect), - FadeOut(braces[:6]), - cw_product_parts.restore, - ) - term_rect = cw_term_rects[0].copy() - self.play(LaggedStartMap(ShowCreationThenDestruction, cw_label_rects)) - self.wait() - self.play( - FadeIn(term_rect), - FadeIn(braces[-2:]), - FadeIn(braces[0]), - ) - show_braces(1) - self.play( - GrowArrow(cw_limit_arrows[0]), - FadeIn(cw_limit_fractions[0]) - ) - self.wait() - - # Second and third lighthouse before - self.play( - Transform(term_rect, cw_term_rects[1]), - ReplacementTransform( - cw_limit_arrows[0].copy(), cw_limit_arrows[1]), - FadeIn(braces[-4:-2]), - Write(cw_limit_fractions[1]) - ) - show_braces(2) - self.wait() - self.play( - Transform(term_rect, cw_term_rects[2]), - ReplacementTransform( - cw_limit_arrows[1].copy(), cw_limit_arrows[2]), - FadeIn(braces[-6:-4]), - Write(cw_limit_fractions[2]) - ) - show_braces(3) - self.let_N_approach_infinity(VGroup(braces[0], *braces[-6:])) - self.wait() - - # Organize fractions - fractions = VGroup(*it.chain(*list(zip( - limit_fractions, cw_limit_fractions, - )))) - fractions.generate_target() - wallis_product = VGroup() - dots = VGroup() - for fraction in fractions.target: - fraction.match_height(cw_limit_fractions[0]) - wallis_product.add(fraction) - dot = TexMobject("\\cdot") - wallis_product.add(dot) - dots.add(dot) - final_dot = TexMobject("\\cdots") - for group in wallis_product, dots: - group.submobjects[-1] = final_dot - wallis_product.arrange(RIGHT, buff=MED_SMALL_BUFF) - wallis_product.to_edge(RIGHT) - - self.play( - FadeOut(limit_arrows), - FadeOut(cw_limit_arrows), - FadeOut(braces[-6:]), - FadeOut(braces[0]), - FadeOut(term_rect), - ) - self.play( - cw_product_parts.scale, 0.5, - cw_product_parts.next_to, product_parts, DOWN, { - "aligned_edge": LEFT}, - MoveToTarget(fractions), - Write(dots), - run_time=2, - path_arc=90 * DEGREES - ) - self.wait() - - self.wallis_product = VGroup(dots, fractions) - self.observers_brace = braces[0] - - def show_limit_of_lhs(self): - brace = self.observers_brace - wallis_product = self.wallis_product - result_fraction = self.result_fraction - N, dist, over, two = result_fraction - distance_product_equals = self.distance_product_equals - - result_rect = SurroundingRectangle(result_fraction) - result_rect.set_color(WHITE) - - equals = TexMobject("=") - equals.next_to(brace, LEFT, SMALL_BUFF) - approx1, approx2, approx3 = [TexMobject("\\approx") for x in range(3)] - approx1.next_to(brace, LEFT, SMALL_BUFF) - half_two_pi_over_N = TexMobject( - "{1", "\\over", "2}", "{2", "\\pi", "\\over", "N}", - ) - pi = half_two_pi_over_N.get_part_by_tex("\\pi") - half_two_pi_over_N.next_to(approx1, LEFT) - approx2.next_to(half_two_pi_over_N, LEFT, SMALL_BUFF) - - approx3.move_to(distance_product_equals) - - pi_over_N = TexMobject("(", "\\pi", "/", "N", ")") - pi_over_N.next_to(N, RIGHT) - N_shift = MED_LARGE_BUFF * RIGHT - pi_over_N.shift(N_shift) - - pi_halves = TexMobject("{\\pi", "\\over", "2}") - pi_halves.next_to(result_rect, DOWN, LARGE_BUFF) - pi_halves.shift(RIGHT) - pi_halves_arrow = Arrow( - result_rect.get_bottom(), - pi_halves.get_top(), - color=WHITE, - buff=SMALL_BUFF - ) - - last_equals = TexMobject("=") - last_equals.next_to(pi_halves, LEFT) - - self.play(ShowCreation(result_rect)) - self.wait() - self.play( - dist.next_to, equals, LEFT, - FadeIn(equals), - GrowFromCenter(brace), - ) - self.wait() - approx2.next_to(dist, LEFT, SMALL_BUFF) - half_two_pi_over_N.next_to(approx2, LEFT) - self.play( - Write(half_two_pi_over_N), - FadeIn(approx2) - ) - self.wait() - self.play( - FadeOut(half_two_pi_over_N[:4]), - pi.shift, SMALL_BUFF * LEFT, - ) - self.wait() - self.play( - ReplacementTransform( - half_two_pi_over_N[-3:].copy(), - pi_over_N[1:4] - ), - FadeIn(pi_over_N[0]), - FadeIn(pi_over_N[-1]), - N.shift, N_shift * RIGHT, - ReplacementTransform(distance_product_equals, approx3) - ) - self.wait() - self.play( - GrowArrow(pi_halves_arrow), - wallis_product.shift, DOWN, - ) - self.play(Write(pi_halves)) - self.wait(2) - self.play( - wallis_product.next_to, last_equals, LEFT, 2 * SMALL_BUFF, - FadeIn(last_equals) - ) - final_rect = SurroundingRectangle( - VGroup(wallis_product, pi_halves), - buff=MED_SMALL_BUFF - ) - final_rect.set_color(YELLOW) - self.play(ShowCreation(final_rect)) - self.wait(2) - - # - - def let_N_approach_infinity(self, braces=None, factor=4, run_time=5, zoom_in_after=False): - lights = self.lights - labels = self.lighthouse_labels - keeper, sailor = self.observers - circle = self.circle - - if braces is None: - braces = VGroup() - - start_fraction = 1.0 / self.num_lighthouses - target_fraction = start_fraction / factor - half_N = self.num_lighthouses / 2 - - fraction_tracker = ValueTracker(start_fraction) - - def get_fraction(): - return fraction_tracker.get_value() - - def get_ks_distance(): - return get_norm(keeper.dot.get_center() - sailor.dot.get_center()) - - def update_title_heights(*titles): - for title in titles: - if not hasattr(title, "original_height"): - title.original_height = title.get_height() - title.set_height(min( - title.original_height, - 0.8 * get_ks_distance(), - )) - if len(titles) > 1: - return titles - else: - return titles[0] - - initial_light_width = lights[0].get_width() - - def update_lights(lights): - for k in range(-half_N, half_N + 1): - if k == 0: - continue - light = lights[k] - light = light.set_width( - (get_fraction() / start_fraction) * initial_light_width - ) - point = self.get_circle_point_at_proportion(k * get_fraction()) - light.move_source_to(point) - return lights - - def update_braces(braces): - for brace in braces: - f1 = brace.fraction1 * (get_fraction() / start_fraction) - f2 = brace.fraction2 * (get_fraction() / start_fraction) - new_brace = self.get_circle_brace(f1, f2) - new_brace.match_style(brace) - Transform(brace, new_brace).update(1) - return braces - - light_update_anim = UpdateFromFunc(lights, update_lights) - label_update_anim = UpdateFromFunc( - labels, - lambda ls: self.position_labels_outside_lights( - update_title_heights(*ls)), - ) - sailor_dot_anim = UpdateFromFunc( - sailor.dot, - lambda d: d.move_to( - self.get_circle_point_at_proportion(get_fraction() / 2)) - ) - sailor_title_anim = UpdateFromFunc( - sailor.title, - lambda m: update_title_heights(m).next_to( - sailor.dot, RIGHT, SMALL_BUFF) - ) - keeper_title_anim = UpdateFromFunc( - keeper.title, - lambda m: update_title_heights(m).next_to( - keeper.dot, RIGHT, SMALL_BUFF) - ) - braces_update_anim = UpdateFromFunc(braces, update_braces) - - lights[0].fade(1) - labels[0].fade(1) - - all_updates = [ - light_update_anim, - label_update_anim, - sailor_dot_anim, - sailor_title_anim, - keeper_title_anim, - braces_update_anim, - ] - - self.play( - fraction_tracker.set_value, target_fraction, - *all_updates, - run_time=run_time - ) - if zoom_in_after: - self.play( - circle.scale, factor, {"about_point": circle.get_right()}, - *all_updates, - run_time=1 - ) - self.wait() - self.play( - circle.scale, 1.0 / - factor, {"about_point": circle.get_right()}, - *all_updates, - run_time=1 - ) - self.wait() - self.play( - fraction_tracker.set_value, start_fraction, - *all_updates, - run_time=run_time / 2 - ) - - def get_keeper_point(self): - return self.get_circle_point_at_proportion(0) - - def get_sailor_point(self): - return self.get_circle_point_at_proportion(0.5 / self.num_lighthouses) - - def create_pi_creatures(self): - keeper = self.keeper = PiCreature(color=self.keeper_color).flip() - sailor = self.sailor = Sailor() - observers = self.observers = VGroup(keeper, sailor) - observers.set_height(3) - keeper.shift(4 * RIGHT + 2 * DOWN) - sailor.shift(4 * RIGHT + 2 * UP) - return VGroup(keeper, sailor) - - def replace_lighthouses_with_labels(self): - lighthouse_labels = self.get_light_labels() - self.lighthouse_labels = lighthouse_labels - - self.remove(self.lights[0], self.lighthouses[0]) - self.play( - FadeOut(self.lighthouses[1:]), - FadeIn(lighthouse_labels[1:]), - ) - - def get_light_labels(self): - labels = VGroup() - for count, point in enumerate(self.get_lh_points()): - if count > self.num_lighthouses / 2: - count -= self.num_lighthouses - label = TexMobject("L_{%d}" % count) - label.scale(0.8) - labels.add(label) - self.position_labels_outside_lights(labels) - return labels - - def position_labels_outside_lights(self, labels): - center = self.circle.get_center() - for light, label in zip(self.lights, labels): - point = light[0].get_center() - vect = (point - center) - norm = get_norm(vect) - buff = label.get_height() - vect *= (norm + buff) / norm - label.move_to(center + vect) - return labels - - def get_keeper_lines(self, line_class=Line): - lines = self.get_distance_lines(self.get_keeper_point()) - lines.set_stroke(self.keeper_color, 3) - return lines - - def get_sailor_lines(self, line_class=Line): - lines = self.get_distance_lines(self.get_sailor_point()) - lines.set_stroke(self.sailor_color, 3) - return lines - - def get_term_rects(self, terms): - return VGroup(*[ - SurroundingRectangle(term, color=WHITE) - for term in terms - ]) - - def get_circle_brace(self, f1, f2): - line = Line( - self.get_circle_point_at_proportion(f1), - self.get_circle_point_at_proportion(f2), - ) - angle = (line.get_angle() + TAU / 2) % TAU - scale_factor = 1.5 - line.rotate(-angle, about_point=ORIGIN) - line.scale(scale_factor, about_point=ORIGIN) - brace = Brace(line, DOWN, buff=SMALL_BUFF) - group = VGroup(line, brace) - group.scale(1.0 / scale_factor, about_point=ORIGIN) - group.rotate(angle, about_point=ORIGIN) - - # Keep track of a fraction between -0.5 and 0.5 - if f1 > 0.5: - f1 -= 1 - if f2 > 0.5: - f2 -= 1 - brace.fraction1 = f1 - brace.fraction2 = f2 - return brace - - def get_all_circle_braces(self): - fractions = np.linspace(0, 1, 2 * self.num_lighthouses + 1) - return VGroup(*[ - self.get_circle_brace(f1, f2) - for f1, f2 in zip(fractions, fractions[1:]) - ]) - - -class MentionJohnWallis(Scene): - def construct(self): - product = get_wallis_product(10) - product.to_edge(UP) - - name = TextMobject("``Wallis product''") - name.scale(1.5) - name.set_color(BLUE) - name.next_to(product, DOWN, MED_LARGE_BUFF) - - image = ImageMobject("John_Wallis") - image.set_height(3) - image.next_to(name, DOWN) - image_name = TextMobject("John Wallis") - image_name.next_to(image, DOWN) - - infinity = TexMobject("\\infty") - infinity.set_height(1.5) - infinity.next_to(image, RIGHT, MED_LARGE_BUFF) - - self.add(product) - self.wait() - self.play(Write(name)) - self.play(GrowFromEdge(image, UP)) - self.play(Write(image_name)) - self.wait(2) - self.play(Write(infinity, run_time=3)) - self.wait(2) - - -class HowThisArgumentRequiresCommunitingLimits(PiCreatureScene): - def construct(self): - mathy, morty = self.pi_creatures - - scale_val = 0.7 - factors = TexMobject( - "{|L_1 - K|", "\\over", "|L_1 - S|}", "\\cdot", - "{|L_{-1} - K|", "\\over", "|L_{-1} - S|}", "\\cdot", - "{|L_2 - K|", "\\over", "|L_2 - S|}", "\\cdot", - "{|L_{-2} - K|", "\\over", "|L_{-2} - S|}", "\\cdot", - "{|L_3 - K|", "\\over", "|L_3 - S|}", "\\cdot", - "{|L_{-3} - K|", "\\over", "|L_{-3} - S|}", "\\cdots", - ) - factors.set_color_by_tex_to_color_map({ - "K": BLUE, - "S": YELLOW, - }) - equals = TexMobject("=") - result = TexMobject( - "{N", "{\\text{distance} \\choose \\text{between obs.}}", - "\\over", "2}" - ) - - top_line = VGroup(factors, equals, result) - top_line.arrange(RIGHT, buff=SMALL_BUFF) - result.shift(SMALL_BUFF * UP) - top_line.scale(scale_val) - top_line.to_edge(UP) - - fractions = VGroup(*[ - factors[i:i + 3] - for i in range(0, len(factors), 4) - ]) - fraction_limit_arrows = VGroup(*[ - Vector(0.5 * DOWN).next_to(fraction, DOWN) - for fraction in fractions - ]) - fraction_limit_arrows.set_color(WHITE) - - wallis_product = get_wallis_product(6) - fraction_limits = VGroup(*[ - wallis_product[i:i + 3] - for i in range(0, 4 * 6, 4) - ]) - for lf, arrow in zip(fraction_limits, fraction_limit_arrows): - lf.next_to(arrow, DOWN, MED_SMALL_BUFF) - - result_limit_arrow = fraction_limit_arrows[0].copy() - result_limit_arrow.next_to(result, DOWN) - result_limit_arrow.align_to(fraction_limit_arrows[0]) - - result_limit = wallis_product[-3:] - result_limit.next_to(result_limit_arrow, DOWN, MED_SMALL_BUFF) - - lower_equals = TexMobject("=") - lower_equals.next_to(result_limit, LEFT) - - mult_signs = VGroup() - for f1, f2 in zip(fraction_limits, fraction_limits[1:]): - mult_sign = TexMobject("\\times") - mult_sign.move_to(VGroup(f1, f2)) - mult_signs.add(mult_sign) - cdots = TexMobject("\\cdots") - cdots.move_to(VGroup(fraction_limits[-1], lower_equals)) - mult_signs.add(cdots) - - # Pi creatures react - self.play( - PiCreatureSays( - mathy, - "Whoa whoa whoa \\\\ there buddy", - look_at_arg=morty.eyes, - target_mode="sassy", - ), - morty.change, "guilty", mathy.eyes, - ) - self.wait(2) - - # Write out commutative diagram - self.play( - RemovePiCreatureBubble( - mathy, - target_mode="raise_right_hand", - look_at_arg=factors, - ), - morty.change, "pondering", factors, - LaggedStartMap(FadeIn, factors), - ) - self.wait() - self.play( - FadeIn(equals), - Write(result) - ) - self.wait() - self.play( - LaggedStartMap(GrowArrow, fraction_limit_arrows), - LaggedStartMap( - FadeInFrom, fraction_limits, - direction=UP - ), - run_time=4, - lag_ratio=0.25, - ) - self.wait() - self.play( - LaggedStartMap(FadeIn, mult_signs), - FadeIn(lower_equals), - mathy.change, "sassy", - ) - self.play( - GrowArrow(result_limit_arrow), - FadeInFrom(result_limit, direction=UP), - morty.change, "confused", - ) - self.wait(2) - - # Write general limit rule - limit_rule = TexMobject( - "\\left( \\lim_{N \\to \\infty} a_N^{(1)} \\right)", - "\\left( \\lim_{N \\to \\infty} a_N^{(2)} \\right)", - "\\cdots", "=", - "\\lim_{N \\to \\infty} \\left( a_N^{(1)} a_N^{(2)} \\cdots \\right)" - ) - limit_rule.next_to(self.pi_creatures, UP) - q_marks = TexMobject("???") - q_marks.set_color(YELLOW) - limit_equals = limit_rule.get_part_by_tex("=") - q_marks.next_to(limit_equals, UP, SMALL_BUFF) - - index_of_equals = limit_rule.index_of_part(limit_equals) - lhs_brace = Brace(limit_rule[:index_of_equals], UP) - rhs_brace = Brace(limit_rule[index_of_equals + 1:], UP) - - self.play( - FadeInFromDown(limit_rule), - mathy.change, "angry", - morty.change, "erm", - ) - self.play(GrowFromCenter(lhs_brace)) - self.wait() - self.play(ReplacementTransform(lhs_brace, rhs_brace)) - self.wait(2) - self.play(FadeOut(rhs_brace), Write(q_marks)) - limit_rule.add(q_marks) - self.wait(2) - self.play(morty.change, "pondering") - self.play(mathy.change, "tease") - self.wait(3) - self.play( - Animation(limit_rule), - morty.change, "pondering", - mathy.change, "pondering", - ) - self.wait(3) - - # Write dominated convergence - mover = VGroup( - top_line, fraction_limits, fraction_limit_arrows, - mult_signs, lower_equals, - result_limit, result_limit_arrow, - ) - self.play( - mover.next_to, FRAME_HEIGHT * UP / 2, UP, - limit_rule.to_edge, UP, - ) - dominated_convergence = TextMobject("``Dominated convergence''") - dominated_convergence.set_color(BLUE) - dominated_convergence.next_to(limit_rule, DOWN, LARGE_BUFF) - - see_blog_post = TextMobject("(See supplementary blog post)") - see_blog_post.next_to(dominated_convergence, DOWN) - - self.play( - FadeInFromDown(dominated_convergence), - mathy.change, "raise_right_hand", - ) - self.play(morty.change, "thinking") - self.wait() - self.play(Write(see_blog_post)) - self.wait(4) - - def create_pi_creatures(self): - group = VGroup(PiCreature(color=GREY), Mortimer()) - group.set_height(2) - group.arrange(RIGHT, buff=4) - group.to_edge(DOWN) - return group - - -class NonCommunitingLimitsExample(Scene): - CONFIG = { - "num_terms_per_row": 6, - "num_rows": 5, - "x_spacing": 0.75, - "y_spacing": 1, - } - - def construct(self): - rows = VGroup(*[ - self.get_row(seven_index) - for seven_index in range(self.num_rows) - ]) - rows.add(self.get_v_dot_row()) - for n, row in enumerate(rows): - row.move_to(n * self.y_spacing * DOWN) - rows.center().to_edge(UP) - rows[-1].shift(MED_SMALL_BUFF * UP) - - row_rects = VGroup(*list(map(SurroundingRectangle, rows[:-1]))) - row_rects.set_color(YELLOW) - - columns = VGroup(*[ - VGroup(*[row[0][i] for row in rows]) - for i in range(len(rows[0][0])) - ]) - column_rects = VGroup(*list(map(SurroundingRectangle, columns))) - column_rects.set_color(BLUE) - - row_arrows = VGroup(*[ - TexMobject("\\rightarrow").next_to(row, RIGHT) - for row in rows[:-1] - ]) - row_products = VGroup(*[ - Integer(7).next_to(arrow) - for arrow in row_arrows - ]) - row_products.set_color(YELLOW) - - row_product_limit_dots = TexMobject("\\vdots") - row_product_limit_dots.next_to(row_products, DOWN) - row_product_limit = Integer(7) - row_product_limit.set_color(YELLOW) - row_product_limit.next_to(row_product_limit_dots, DOWN) - - column_arrows = VGroup(*[ - TexMobject("\\downarrow").next_to(part, DOWN, SMALL_BUFF) - for part in rows[-1][0] - ]) - column_limits = VGroup(*[ - Integer(1).next_to(arrow, DOWN) - for arrow in column_arrows - ]) - column_limits.set_color(BLUE) - column_limit_dots = self.get_row_dots(column_limits) - - column_limits_arrow = TexMobject("\\rightarrow").next_to( - column_limit_dots, RIGHT - ) - product_of_limits = Integer(1).next_to(column_limits_arrow, RIGHT) - product_of_limits.set_color(BLUE) - - self.add(rows) - self.wait() - self.play(LaggedStartMap(ShowCreation, row_rects)) - self.wait(2) - row_products_iter = iter(row_products) - self.play( - LaggedStartMap(Write, row_arrows), - LaggedStartMap( - ReplacementTransform, rows[:-1].copy(), - lambda r: (r, next(row_products_iter)) - ) - ) - self.wait() - self.play(Write(row_product_limit_dots)) - self.play(Write(row_product_limit)) - self.wait() - - self.play(LaggedStartMap(FadeOut, row_rects)) - self.play(LaggedStartMap(FadeIn, column_rects)) - self.wait() - column_limit_iter = iter(column_limits) - self.play( - LaggedStartMap(Write, column_arrows), - LaggedStartMap( - ReplacementTransform, columns.copy(), - lambda c: (c, next(column_limit_iter)) - ) - ) - self.wait() - self.play(Write(column_limits_arrow)) - self.play( - Write(product_of_limits), - FadeOut(row_product_limit) - ) - self.wait() - - - def get_row(self, seven_index): - terms = [1] * (self.num_terms_per_row) - if seven_index < len(terms): - terms[seven_index] = 7 - row = VGroup(*list(map(Integer, terms))) - self.arrange_row(row) - dots = self.get_row_dots(row) - - return VGroup(row, dots) - - def get_v_dot_row(self): - row = VGroup(*[ - TexMobject("\\vdots") - for x in range(self.num_terms_per_row) - ]) - self.arrange_row(row) - dots = self.get_row_dots(row) - dots.fade(1) - return VGroup(row, dots) - - def arrange_row(self, row): - for n, part in enumerate(row): - part.move_to(n * self.x_spacing * RIGHT) - - def get_row_dots(self, row): - dots = VGroup() - for p1, p2 in zip(row, row[1:]): - dots.add(TexMobject("\\cdot").move_to(VGroup(p1, p2))) - dots.add(TexMobject("\\cdots").next_to(row, RIGHT)) - return dots - - -class DelicacyInIntermixingSeries(Scene): - def construct(self): - n_terms = 6 - - top_product, bottom_product = products = VGroup(*[ - TexMobject(*it.chain(*[ - ["{%d" % (2 * x), "\\over", "%d}" % (2 * x + u), "\\cdot"] - for x in range(1, n_terms + 1) - ])) - for u in (-1, 1) - ]) - top_product.set_color(GREEN) - bottom_product.set_color(BLUE) - top_product.to_edge(UP) - bottom_product.next_to(top_product, DOWN, LARGE_BUFF) - - infinity = TexMobject("\\infty") - top_product.limit = infinity - zero = TexMobject("0") - bottom_product.limit = zero - - for product in products: - cdots = TexMobject("\\cdots") - cdots.move_to(product[-1], LEFT) - cdots.match_color(product) - product.submobjects[-1] = cdots - product.parts = VGroup(*[ - product[i:i + 4] - for i in range(0, len(product), 4) - ]) - arrow = Vector(0.75 * RIGHT) - arrow.set_color(WHITE) - arrow.next_to(product, RIGHT) - product.arrow = arrow - product.limit.next_to(arrow, RIGHT) - product.limit.match_color(product) - - group = VGroup(products, infinity) - h_line = Line(LEFT, RIGHT) - h_line.stretch_to_fit_width(group.get_width() + LARGE_BUFF) - h_line.next_to(group, DOWN, aligned_edge=RIGHT) - times = TexMobject("\\times") - times.next_to(h_line, UP, aligned_edge=LEFT) - q_marks = TexMobject("?????") - q_marks.set_color_by_gradient(BLUE, YELLOW) - q_marks.scale(2) - q_marks.next_to(h_line, DOWN) - - # Show initial products - self.play( - LaggedStartMap(FadeIn, top_product), - LaggedStartMap(FadeIn, bottom_product), - ) - self.wait() - for product in products: - self.play( - GrowArrow(product.arrow), - FadeInFrom(product.limit, direction=LEFT) - ) - self.wait() - self.play( - Write(times), - ShowCreation(h_line) - ) - self.play(Write(q_marks, run_time=3)) - self.wait(2) - - # Show alternate interweaving - top_parts_iter = iter(top_product.parts) - bottom_parts_iter = iter(bottom_product.parts) - movers1 = VGroup() - while True: - try: - new_terms = [ - next(bottom_parts_iter), - next(top_parts_iter), - next(top_parts_iter), - ] - movers1.add(*new_terms) - except StopIteration: - break - new_product = VGroup() - movers1.save_state() - for mover in movers1: - mover.generate_target() - new_product.add(mover.target) - new_product.arrange(RIGHT, buff=SMALL_BUFF) - new_product.next_to(h_line, DOWN, LARGE_BUFF, aligned_edge=LEFT) - - new_arrow = top_product.arrow.copy() - new_arrow.next_to(new_product, RIGHT) - - ghost_top = top_product.copy().fade() - ghost_bottom = bottom_product.copy().fade() - self.add(ghost_top, top_product) - self.add(ghost_bottom, bottom_product) - - new_limit = TexMobject("\\frac{\\pi}{2}", "\\sqrt{2}") - new_limit.next_to(new_arrow, RIGHT) - - randy = Randolph(height=1.5) - randy.flip() - randy.to_corner(DR) - - movers2 = VGroup(*it.chain(*list(zip( - top_product.parts, bottom_product.parts - )))) - final_product = VGroup() - for mover in movers2: - mover.final_position = mover.copy() - if mover is movers2[-2]: - # Excessive ellipses - final_dot = mover.final_position[-1][0] - mover.final_position.submobjects[-1] = final_dot - final_product.add(mover.final_position) - final_product.arrange(RIGHT, buff=SMALL_BUFF) - final_product.move_to(new_product, RIGHT) - - self.play( - FadeOut(q_marks), - LaggedStartMap( - MoveToTarget, movers1, - run_time=5, - lag_ratio=0.2, - ) - ) - self.play( - GrowArrow(new_arrow), - FadeInFrom(new_limit, LEFT), - bottom_product.parts[3:].fade, 1, - ) - self.play(FadeIn(randy)) - self.play(randy.change, "confused", new_limit) - self.wait() - self.play(Blink(randy)) - self.wait() - self.play(LaggedStartMap( - Transform, movers2, - lambda m: (m, m.final_position), - run_time=3, - path_arc=TAU / 4, - )) - self.play( - FadeOut(new_limit[1]), - randy.change, "pondering", new_limit - ) - self.wait(2) - self.play(Blink(randy)) - self.wait(2) - - -class JustTechnicalities(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "These are just \\\\ technicalities" - ) - self.change_all_student_modes("happy") - self.play(RemovePiCreatureBubble( - self.teacher, target_mode="raise_right_hand", - )) - self.look_at(self.screen) - self.wait(4) - - -class KeeperAndSailorForSineProduct(KeeperAndSailor): - CONFIG = { - # "ambient_light_config": CHEAP_AMBIENT_LIGHT_CONFIG, - "new_sailor_fraction": 0.7, - "big_circle_center": FRAME_WIDTH * LEFT / 2 + 2.6 * LEFT, - } - - def construct(self): - # Rerun old animation (probably to be skipped)g - self.force_skipping() - self.place_lighthouses() - self.introduce_observers() - self.write_distance_product_fraction() - self.revert_to_original_skipping_status() - - # New animations - self.replace_lighthouses_with_labels() - self.move_sailor_to_new_spot() - self.show_new_distance_product() - self.show_new_limit_of_ratio() - self.show_new_infinite_product() - - def move_sailor_to_new_spot(self): - sailor = self.sailor - fraction = self.new_sailor_fraction - self.get_sailor_point = lambda: self.get_circle_point_at_proportion( - fraction / self.num_lighthouses - ) - target_point = self.get_sailor_point() - - brace1 = self.get_circle_brace(0, 0.5 / self.num_lighthouses) - brace2 = self.get_circle_brace(0, fraction / self.num_lighthouses) - - center = self.circle.get_center() - radius = self.get_radius() - - def warp_func(point): - vect = point - center - norm = get_norm(vect) - new_norm = norm + 0.5 * (radius - norm) - return center + new_norm * vect / norm - brace1.apply_function(warp_func) - brace2.apply_function(warp_func) - - scale_val = 0.7 - words1 = TextMobject("Instead of", "$\\frac{1}{2}$") - words1.set_color_by_tex("$", YELLOW) - words1.scale(scale_val) - words1.next_to(brace1.get_center(), LEFT) - words2 = TextMobject("Some other fraction", "$f$") - words2.set_color_by_tex("$", GREEN) - words2.scale(scale_val) - words2.next_to(brace2.get_center(), LEFT) - - self.play( - GrowFromCenter(brace1), - Write(words1, run_time=1) - ) - self.wait() - self.play( - sailor.dot.move_to, target_point, - sailor.dot.set_color, GREEN, - sailor.set_color, GREEN, - MaintainPositionRelativeTo(sailor, sailor.dot), - MaintainPositionRelativeTo(sailor.title, sailor), - ReplacementTransform(brace1, brace2), - ReplacementTransform(words1, words2), - run_time=1.5 - ) - - self.fraction_label_group = VGroup(brace2, words2) - - def show_new_distance_product(self): - result_fraction = self.result_fraction - N, dist, over, two = result_fraction - sailor_dp = self.distance_product_fraction[-1] - sailor_dp_rect = SurroundingRectangle(sailor_dp) - sailor_dp_rect.set_color(GREEN) - - sailor_lines = self.get_distance_lines( - self.sailor.dot.get_center(), line_class=DashedLine - ) - chord_f = TextMobject("Chord(", "$f$", ")", arg_separator="") - chord_f.set_color_by_tex("$f$", GREEN) - chord_f.move_to(two, UP) - - two_cross = Cross(SurroundingRectangle(two)) - two_group = VGroup(two, two_cross) - - self.play( - ShowCreation(sailor_dp_rect), - LaggedStartMap(ShowCreation, sailor_lines), - ) - self.wait() - self.play(ShowCreation(two_cross)) - self.play( - two_group.next_to, chord_f, DOWN, - ReplacementTransform( - VGroup( - sailor_dp[:-6].copy(), - sailor_dp[-6:-4].copy(), - sailor_dp[-4:-2].copy(), - sailor_dp[-2:].copy(), - ), - chord_f - ) - ) - self.wait() - self.play(LaggedStartMap(FadeOut, VGroup( - sailor_lines, sailor_dp_rect, two_group - ))) - - result_fraction.submobjects[-1] = chord_f - - def show_new_limit_of_ratio(self): - fraction_label_group = self.fraction_label_group - fraction_brace, fraction_words = fraction_label_group - - frac1 = TexMobject( - "{N", "(", "f", " \\cdot 2\\pi / N)", "\\over", - "\\text{Chord}(", "f", ")}" - ) - frac2 = TexMobject( - "{f", "\\cdot", "2", "\\pi", "\\over", - "\\text{Chord}(", "f", ")}" - ) - for frac in frac1, frac2: - frac.set_color_by_tex("f", GREEN, substring=False) - arrow1, arrow2 = [ - Vector(0.75 * DOWN, color=WHITE) - for x in range(2) - ] - - group = VGroup(arrow1, frac1, arrow2, frac2) - group.arrange(DOWN) - group.next_to(self.result_fraction, DOWN) - big_group = VGroup(self.result_fraction, *group) - - arrow = TexMobject("\\rightarrow") - arrow.move_to(self.distance_product_equals) - - fraction_brace.generate_target() - fraction_brace.target.rotate(-10 * DEGREES) - fraction_brace.target.scale(0.65) - fraction_brace.target.align_to(ORIGIN, DOWN) - fraction_brace.target.shift(3.3 * LEFT) - fraction_words.generate_target() - fraction_words.target[0][:9].next_to( - VGroup(fraction_words.target[0][9:], fraction_words.target[1]), - UP, SMALL_BUFF - ) - fraction_words.target.next_to(fraction_brace.target, LEFT, SMALL_BUFF) - - self.play(LaggedStartMap(FadeIn, group)) - self.grow_circle_and_N( - added_anims=[ - MoveToTarget(fraction_brace), - MoveToTarget(fraction_words), - ] - ) - self.wait() - self.play( - big_group.next_to, self.distance_product_equals, RIGHT, - {"submobject_to_align": frac2}, - UpdateFromAlphaFunc( - big_group[:-1], - lambda m, a: m.set_fill(opacity=1 - a), - ), - Transform(self.distance_product_equals, arrow) - ) - self.wait() - - self.result_fraction = frac2 - - def show_new_infinite_product(self): - scale_val = 0.7 - fractions = TexMobject( - "\\cdots", - "{|L_{-2} - K|", "\\over", "|L_{-2} - S|}", "\\cdot", - "{|L_{-1} - K|", "\\over", "|L_{-1} - S|}", "\\cdot", - "{|L_1 - K|", "\\over", "|L_1 - S|}", "\\cdot", - "{|L_2 - K|", "\\over", "|L_2 - S|}", "\\cdot", - "{|L_3 - K|", "\\over", "|L_3 - S|}", "\\cdots", - ) - fractions.scale(scale_val) - fractions.move_to(self.observers) - fractions.to_edge(RIGHT) - fractions.set_color_by_tex_to_color_map({ - "K": BLUE, - "S": GREEN, - }) - - keeper_lines = self.get_keeper_lines() - sailor_lines = self.get_sailor_lines() - sailor_lines.set_color(GREEN) - - ratios = VGroup(*[ - fractions[i:i + 3] - for i in range(1, len(fractions), 4) - ]) - limit_arrows = VGroup(*[ - Vector(0.5 * DOWN, color=WHITE).next_to(ratio, DOWN, SMALL_BUFF) - for ratio in ratios - ]) - limits = VGroup(*[ - TexMobject("{%d" % k, "\\over", "%d" % k, "-", "f}") - for k in (-2, -1, 1, 2, 3) - ]) - for limit, arrow in zip(limits, limit_arrows): - limit.set_color_by_tex("f", GREEN) - limit.scale(scale_val) - limit.next_to(arrow, DOWN, SMALL_BUFF) - dots = VGroup() - dots.add(TexMobject("\\cdots").next_to(limits, LEFT)) - for l1, l2 in zip(limits, limits[1:]): - dots.add(TexMobject("\\cdot").move_to(VGroup(l1, l2))) - dots.add(TexMobject("\\cdots").next_to(limits, RIGHT)) - full_limits_group = VGroup(*list(limits) + list(dots)) - - # brace = Brace(limits, DOWN) - product = TexMobject( - "\\prod_{k \\ne 0}", "{k", "\\over", "k", "-", "f}" - ) - product.next_to(limits, DOWN, LARGE_BUFF) - product_lines = VGroup( - DashedLine(full_limits_group.get_corner(DL), product.get_corner(UL)), - DashedLine(full_limits_group.get_corner(DR), product.get_corner(UR)), - ) - product_lines.set_color(YELLOW) - - self.play( - LaggedStartMap(FadeIn, fractions), - *[ - LaggedStartMap( - FadeIn, VGroup(*list(lines[-10:]) + list(lines[1:10])), - rate_func=there_and_back, - remover=True, - run_time=3, - lag_ratio=0.1 - ) - for lines in (keeper_lines, sailor_lines) - ] - ) - self.wait() - self.play( - LaggedStartMap(GrowArrow, limit_arrows), - LaggedStartMap( - FadeInFrom, limits, - lambda m: (m, UP), - ), - LaggedStartMap(FadeIn, dots) - ) - self.wait() - self.play( - # GrowFromCenter(brace), - ShowCreation(product_lines, lag_ratio=0), - FadeIn(product) - ) - self.wait() - - # Shift everything - result_fraction = self.result_fraction - big_group = VGroup( - fractions, limit_arrows, full_limits_group, - # brace, - product_lines, - product, - ) - big_group.generate_target() - big_group.target.to_edge(UP) - equals = TexMobject("=") - equals.next_to(big_group.target[-1], LEFT) - result_fraction.generate_target() - result_fraction.target.next_to(equals, LEFT) - - self.play( - MoveToTarget(big_group), - FadeIn(equals), - MoveToTarget(result_fraction), - FadeOut(self.distance_product_fraction), - FadeOut(self.distance_product_equals), - ) - self.wait() - - # Replace chord with sine - chord_f = result_fraction[-3:] - f_pi = VGroup(result_fraction[0], result_fraction[3]) - over = result_fraction.get_part_by_tex("\\over") - dot_two = result_fraction[1:3] - - two_sine_f_pi = TexMobject("2", "\\sin(", "f", "\\pi", ")") - sine_f_pi = two_sine_f_pi[1:] - two_sine_f_pi.set_color_by_tex("f", GREEN) - two_sine_f_pi.move_to(chord_f) - - self.play( - FadeIn(two_sine_f_pi), - chord_f.shift, DOWN - ) - self.wait() - self.play(FadeOut(chord_f)) - self.wait() - self.play( - f_pi.arrange, RIGHT, {"buff": SMALL_BUFF}, - f_pi.next_to, over, UP, SMALL_BUFF, - FadeOut(dot_two), - FadeOut(two_sine_f_pi[0]), - sine_f_pi.shift, SMALL_BUFF * LEFT, - ) - self.wait() - - # Reciprocate - pairs = VGroup() - for num, denom in zip(fractions[1::4], fractions[3::4]): - pairs.add(VGroup(num, denom)) - for limit in limits: - pairs.add(VGroup(limit[0], limit[2:])) - pairs.add( - VGroup(f_pi, sine_f_pi), - VGroup(product[1], product[3:]), - ) - - for pair in pairs: - pair.generate_target() - pair.target[0].move_to(pair[1], UP) - pair.target[1].move_to(pair[0], DOWN) - - self.play( - LaggedStartMap( - MoveToTarget, pairs, - path_arc=180 * DEGREES, - run_time=3, - ), - product_lines[1].scale, 0.9, {"about_point": product_lines[1].get_start()}, - product_lines[1].shift, SMALL_BUFF * UP - ) - self.wait() - - # Rearrange - one_minus_f_over_k = TexMobject( - "\\left(", "1", "-", "{f", "\\over", "k}", "\\right)" - ) - # 0 1 2 3 4 - # k / k - f - one_minus_f_over_k.set_color_by_tex("{f", GREEN) - one_minus_f_over_k.next_to(product[0], RIGHT, buff=SMALL_BUFF) - one_minus_f_over_k.shift(SMALL_BUFF * UP) - - self.play( - FadeIn(one_minus_f_over_k[0]), - FadeIn(one_minus_f_over_k[-1]), - FadeOut(product_lines), - *[ - ReplacementTransform(product[i], one_minus_f_over_k[j]) - for i, j in [(3, 1), (4, 2), (5, 3), (2, 4), (1, 5)] - ] - ) - self.wait() - - product = VGroup(product[0], *one_minus_f_over_k) - product.generate_target() - f_pi.generate_target() - f_pi.target.next_to(equals, RIGHT, SMALL_BUFF) - product.target.next_to(f_pi.target, RIGHT, SMALL_BUFF) - product.target.shift(SMALL_BUFF * DOWN) - - self.play( - sine_f_pi.next_to, equals, LEFT, SMALL_BUFF, - FadeOut(over), - MoveToTarget(f_pi), - MoveToTarget(product), - ) - self.wait() - - # Show final result - rect = SurroundingRectangle(VGroup(sine_f_pi, product)) - rect.set_color(BLUE) - pi_creatures = VGroup( - PiCreature(color=BLUE_E), - PiCreature(color=BLUE_C), - PiCreature(color=BLUE_D), - Mortimer() - ) - pi_creatures.arrange(RIGHT, LARGE_BUFF) - pi_creatures.set_height(1) - pi_creatures.next_to(rect, DOWN) - for pi in pi_creatures: - pi.change("hooray", rect) - pi.save_state() - pi.change("plain") - pi.fade(1) - - self.play( - ShowCreation(rect), - LaggedStartMap(ApplyMethod, pi_creatures, lambda m: (m.restore,)) - ) - for x in range(4): - self.play(Blink(random.choice(pi_creatures))) - self.wait() - - -class Conclusion(TeacherStudentsScene): - CONFIG = { - "camera_config": {"background_opacity": 1}, - } - - def construct(self): - wallis_product = get_wallis_product(6) - wallis_product.move_to(self.hold_up_spot, DOWN) - wallis_product.shift_onto_screen() - for i in range(0, len(wallis_product) - 5, 4): - color = GREEN if (i / 4) % 2 == 0 else BLUE - wallis_product[i:i + 3].set_color(color) - - sine_formula = TexMobject( - "\\sin(", "f", "\\pi", ")", "=", "f", "\\pi", - "\\prod_{k \\ne 0}", "\\left(", "1", "-", - "{f", "\\over", "k}", "\\right)" - ) - sine_formula.set_color_by_tex("f", GREEN) - sine_formula.set_color_by_tex("left", WHITE) - sine_formula.move_to(self.hold_up_spot, DOWN) - sine_formula.shift_onto_screen() - - euler = ImageMobject("Euler") - euler.set_height(2.5) - basel_problem = TexMobject( - "\\frac{1}{1^2} + ", - "\\frac{1}{2^2} + ", - "\\frac{1}{3^2} + ", - "\\cdots", - "\\frac{\\pi^2}{6}" - ) - basel_problem.move_to(self.hold_up_spot, DOWN) - implication = TexMobject("\\Rightarrow") - implication.next_to(basel_problem[0][1], LEFT) - - self.play( - self.teacher.change, "raise_right_hand", - FadeInFromDown(wallis_product), - self.get_student_changes("thinking", "hooray", "surprised") - ) - self.wait() - self.play( - self.teacher.change, "hooray", - FadeInFromDown(sine_formula), - wallis_product.to_edge, UP, - self.get_student_changes("pondering", "thinking", "erm") - ) - self.wait(3) - self.play( - sine_formula.next_to, implication, LEFT, - {"submobject_to_align": sine_formula[-1]}, - FadeIn(implication), - ) - euler.next_to(sine_formula, UP) - self.play( - FadeIn(euler), - LaggedStartMap(FadeIn, basel_problem), - self.teacher.change, "happy", - self.get_student_changes("sassy", "confused", "hesitant") - ) - self.wait(2) - - wallis_rect = SurroundingRectangle(wallis_product) - wallis_rect.set_color(BLUE) - basel_rect = SurroundingRectangle(basel_problem) - basel_rect.set_color(YELLOW) - - self.play( - ShowCreation(wallis_rect), - FadeOut(implication), - FadeOut(euler), - FadeOut(sine_formula), - ) - self.play( - ShowCreation(basel_rect), - self.teacher.change, "surprised", - self.get_student_changes(*["happy"] * 3) - ) - self.wait(5) - - -class ByLine(Scene): - def construct(self): - self.add(TextMobject(""" - Written and animated by \\\\ - \\quad \\\\ - Sridhar Ramesh \\\\ - Grant Sanderson - """).shift(2 * UP)) - - -class SponsorUnderlay(PiCreatureScene): - CONFIG = { - "default_pi_creature_start_corner": DR, - } - - def construct(self): - url = TextMobject("https://udacity.com/3b1b/") - url.to_corner(UL) - - rect = ScreenRectangle(height=5.5) - rect.next_to(url, DOWN) - rect.to_edge(LEFT) - url.next_to(rect, UP) - url.save_state() - - url.next_to(self.pi_creature.get_corner(UL), UP) - - logo = SVGMobject(file_name="udacity") - logo.set_fill("#02b3e4") - logo.to_corner(UR) - - self.add(logo) - - self.play( - Write(url), - self.pi_creature.change, "raise_right_hand" - ) - self.play( - url.restore, - ShowCreation(rect), - path_arc=90 * DEGREES, - ) - self.wait(2) - self.change_mode("thinking") - self.wait() - self.look_at(url) - self.wait(10) - self.change_mode("happy") - self.wait(10) - self.change_mode("raise_right_hand") - self.wait(10) - - self.remove(rect) - self.play(url.next_to, self.pi_creature, UL) - url_rect = SurroundingRectangle(url) - self.play(ShowCreation(url_rect)) - self.play(FadeOut(url_rect)) - self.wait(3) - - -class EndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "Juan Benet", - "Keith Smith", - "Chloe Zhou", - "Ross Garber", - "Desmos", - "Burt Humburg", - "CrypticSwarm", - "Hoang Tung Lam", - "Sergei", - "Devin Scott", - "George John", - "Akash Kumar", - "Felix Tripier", - "Arthur Zey", - "David Kedmey", - "Ali Yahya", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Yu Jun", - "dave nicponski", - "Damion Kistler", - "Jordan Scales", - "Markus Persson", - "Fred Ehrsam", - "Britt Selvitelle", - "Jonathan Wilson", - "Ryan Atallah", - "Joseph John Cox", - "Luc Ritchie", - "Cooper Jones", - "James Hughes", - "John V Wertheim", - "Chris Giddings", - "Song Gao", - "William Fritzon", - "Alexander Feldman", - # "孟子易", - "Mengzi yi", - "zheng zhang", - "Matt Langford", - "Max Mitchell", - "Richard Burgmann", - "John Griffith", - "Chris Connett", - "Steven Tomlinson", - "Jameel Syed", - "Bong Choung", - "Ignacio Freiberg", - "Zhilong Yang", - "Dan Esposito (Guardion)", - "Giovanni Filippi", - "Eric Younge", - "Prasant Jagannath", - "Cody Brocious", - "James H. Park", - "Norton Wang", - "Kevin Le", - "Tianyu Ge", - "David MacCumber", - "Oliver Steele", - "Yaw Etse", - "Dave B", - "Waleed Hamied", - "George Chiesa", - "supershabam", - "Delton Ding", - "Thomas Tarler", - "Jonathan Eppele", - "Isak Hietala", - "1stViewMaths", - "Jacob Magnuson", - "Mark Govea", - "Clark Gaebel", - "Mathias Jansson", - "David Clark", - "Michael Gardner", - "Mads Elvheim", - "Awoo", - "Dr David G. Stork", - "Ted Suzman", - "Linh Tran", - "Andrew Busey", - "John Haley", - "Ankalagon", - "Eric Lavault", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Ryan Dahl", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - # "世珉 匡", - "Shi min kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ] - } - - -class Thumbnail(DistanceProductScene): - def construct(self): - product = get_wallis_product() - product.scale(1.5) - product.move_to(2.5 * UP) - frac_lines = product.get_parts_by_tex("\\over") - frac_indices = list(map(product.index_of_part, frac_lines)) - parts = VGroup(*[ - product[i-1:i+2] - for i in frac_indices - ]) - parts[::2].set_color(WHITE) - parts[1::2].set_color(WHITE) - parts[-1].set_color(WHITE) - parts[-1].set_background_stroke(color=YELLOW, width=3) - parts[-1].scale(1.5, about_edge=LEFT) - product[-4:].next_to(product[:-4], DOWN, MED_LARGE_BUFF) - product.scale(1.2).center().to_edge(UP) - - # new_proof = TextMobject("New proof") - # new_proof.scale(2.5) - # new_proof.set_color(YELLOW) - # new_proof.set_stroke(RED, 0.75) - # new_proof.next_to(product, DOWN, MED_LARGE_BUFF) - - circle = self.circle = Circle(radius=8, color=YELLOW) - circle.move_to(3 * DOWN, DOWN) - bottom_dot = Dot(color=BLUE) - bottom_dot.move_to(circle.get_bottom()) - observer = PiCreature(mode="pondering") - observer.set_height(0.5) - observer.next_to(bottom_dot, DOWN) - - lights = VGroup() - lines = VGroup() - light_config = dict(HIGHT_QUALITY_AMBIENT_LIGHT_CONFIG) - # light_config = dict(CHEAP_AMBIENT_LIGHT_CONFIG) - light_config["max_opacity"] = 1 - step = 0.03 - for frac in np.arange(step, 0.2, step): - for u in -1, 1: - light = AmbientLight(**light_config) - dot = Dot(color=BLUE) - dot.move_to(light) - light.add_to_back(dot) - light.move_to(self.get_circle_point_at_proportion(u * frac - 0.25)) - lights.add(light) - line = DashedLine(dot.get_center(), circle.get_bottom()) - lines.add(line) - - - self.add(circle) - self.add(lights) - self.add(product) - # self.add(new_proof) - self.add(bottom_dot, observer) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/old/waves.py b/from_3b1b/old/waves.py deleted file mode 100644 index ea4dd720..00000000 --- a/from_3b1b/old/waves.py +++ /dev/null @@ -1,4305 +0,0 @@ -from manimlib.imports import * - - -import warnings -warnings.warn(""" - Warning: This file makes use of - ContinualAnimation, which has since - been deprecated -""") - - -E_COLOR = BLUE -M_COLOR = YELLOW - - -# Warning, much of what is below was implemented using -# ConintualAnimation, which has now been deprecated. One -# Should use Mobject updaters instead. -# -# That is, anything below implemented as a ContinualAnimation -# should instead be a Mobject, where the update methods -# should be added via Mobject.add_udpater. - - -class OscillatingVector(ContinualAnimation): - CONFIG = { - "tail" : ORIGIN, - "frequency" : 1, - "A_vect" : [1, 0, 0], - "phi_vect" : [0, 0, 0], - "vector_to_be_added_to" : None, - } - def setup(self): - self.vector = self.mobject - - def update_mobject(self, dt): - f = self.frequency - t = self.internal_time - angle = 2*np.pi*f*t - vect = np.array([ - A*np.exp(complex(0, angle + phi)) - for A, phi in zip(self.A_vect, self.phi_vect) - ]).real - self.update_tail() - self.vector.put_start_and_end_on(self.tail, self.tail+vect) - - def update_tail(self): - if self.vector_to_be_added_to is not None: - self.tail = self.vector_to_be_added_to.get_end() - -class OscillatingVectorComponents(ContinualAnimationGroup): - CONFIG = { - "tip_to_tail" : False, - } - def __init__(self, oscillating_vector, **kwargs): - digest_config(self, kwargs) - vx = Vector(UP, color = GREEN).fade() - vy = Vector(UP, color = RED).fade() - kwargs = { - "frequency" : oscillating_vector.frequency, - "tail" : oscillating_vector.tail, - } - ovx = OscillatingVector( - vx, - A_x = oscillating_vector.A_x, - phi_x = oscillating_vector.phi_x, - A_y = 0, - phi_y = 0, - **kwargs - ) - ovy = OscillatingVector( - vy, - A_x = 0, - phi_x = 0, - A_y = oscillating_vector.A_y, - phi_y = oscillating_vector.phi_y, - **kwargs - ) - components = [ovx, ovy] - self.vectors = VGroup(ovx.vector, ovy.vector) - if self.tip_to_tail: - ovy.vector_to_be_added_to = ovx.vector - else: - self.lines = VGroup() - for ov1, ov2 in (ovx, ovy), (ovy, ovx): - ov_line = ov1.copy() - ov_line.mobject = ov_line.vector = DashedLine( - UP, DOWN, color = ov1.vector.get_color() - ) - ov_line.vector_to_be_added_to = ov2.vector - components.append(ov_line) - self.lines.add(ov_line.line) - - ContinualAnimationGroup.__init__(self, *components, **kwargs) - -class EMWave(ContinualAnimationGroup): - CONFIG = { - "wave_number" : 1, - "frequency" : 0.25, - "n_vectors" : 40, - "propogation_direction" : RIGHT, - "start_point" : FRAME_X_RADIUS*LEFT + DOWN + OUT, - "length" : FRAME_WIDTH, - "amplitude" : 1, - "rotation" : 0, - "A_vect" : [0, 0, 1], - "phi_vect" : [0, 0, 0], - "requires_start_up" : False, - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - if not all(self.propogation_direction == RIGHT): - self.matrix_transform = np.dot( - z_to_vector(self.propogation_direction), - np.linalg.inv(z_to_vector(RIGHT)), - ) - else: - self.matrix_transform = None - - vector_oscillations = [] - self.E_vects = VGroup() - self.M_vects = VGroup() - - self.A_vect = np.array(self.A_vect)/get_norm(self.A_vect) - self.A_vect *= self.amplitude - - for alpha in np.linspace(0, 1, self.n_vectors): - tail = interpolate(ORIGIN, self.length*RIGHT, alpha) - phase = -alpha*self.length*self.wave_number - kwargs = { - "phi_vect" : np.array(self.phi_vect) + phase, - "frequency" : self.frequency, - "tail" : np.array(tail), - } - E_ov = OscillatingVector( - Vector( - OUT, color = E_COLOR, - normal_vector = UP, - ), - A_vect = self.A_vect, - **kwargs - ) - M_ov = OscillatingVector( - Vector( - UP, color = M_COLOR, - normal_vector = OUT, - ), - A_vect = rotate_vector(self.A_vect, np.pi/2, RIGHT), - **kwargs - ) - vector_oscillations += [E_ov, M_ov] - self.E_vects.add(E_ov.vector) - self.M_vects.add(M_ov.vector) - ContinualAnimationGroup.__init__(self, *vector_oscillations) - - def update_mobject(self, dt): - if self.requires_start_up: - n_wave_lengths = self.length / (2*np.pi*self.wave_number) - prop_time = n_wave_lengths/self.frequency - middle_alpha = interpolate( - 0.4, 1.4, - self.external_time / prop_time - ) - new_smooth = squish_rate_func(smooth, 0.4, 0.6) - - ovs = self.continual_animations - for ov, alpha in zip(ovs, np.linspace(0, 1, len(ovs))): - epsilon = 0.0001 - new_amplitude = np.clip( - new_smooth(middle_alpha - alpha), epsilon, 1 - ) - norm = get_norm(ov.A_vect) - if norm != 0: - ov.A_vect = new_amplitude * np.array(ov.A_vect) / norm - - ContinualAnimationGroup.update_mobject(self, dt) - self.mobject.rotate(self.rotation, RIGHT) - if self.matrix_transform: - self.mobject.apply_matrix(self.matrix_transform) - self.mobject.shift(self.start_point) - -class WavePacket(Animation): - CONFIG = { - "EMWave_config" : { - "wave_number" : 0, - "start_point" : FRAME_X_RADIUS*LEFT, - "phi_vect" : np.ones(3)*np.pi/4, - }, - "em_wave" : None, - "run_time" : 4, - "rate_func" : None, - "packet_width" : 6, - "include_E_vects" : True, - "include_M_vects" : True, - "filter_distance" : FRAME_X_RADIUS, - "get_filtered" : False, - "remover" : True, - "width" : 2*np.pi, - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - em_wave = self.em_wave - if em_wave is None: - em_wave = EMWave(**self.EMWave_config) - em_wave.update(0) - self.em_wave = em_wave - - self.vects = VGroup() - if self.include_E_vects: - self.vects.add(*em_wave.E_vects) - if self.include_M_vects: - self.vects.add(*em_wave.M_vects) - for vect in self.vects: - vect.save_state() - - u = em_wave.propogation_direction - self.wave_packet_start, self.wave_packet_end = [ - em_wave.start_point - u*self.packet_width/2, - em_wave.start_point + u*(em_wave.length + self.packet_width/2) - ] - Animation.__init__(self, self.vects, **kwargs) - - def interpolate_mobject(self, alpha): - packet_center = interpolate( - self.wave_packet_start, - self.wave_packet_end, - alpha - ) - em_wave = self.em_wave - for vect in self.vects: - tail = vect.get_start() - distance_from_packet = np.dot( - tail - packet_center, - em_wave.propogation_direction - ) - A = em_wave.amplitude*self.E_func(distance_from_packet) - distance_from_start = get_norm(tail - em_wave.start_point) - if self.get_filtered and distance_from_start > self.filter_distance: - A = 0 - epsilon = 0.05 - if abs(A) < epsilon: - A = 0 - vect.restore() - vect.scale(A/vect.get_length(), about_point = tail) - - def E_func(self, x): - x0 = 2*np.pi*x/self.width - return np.sin(x0)*np.exp(-0.25*x0*x0) - -class FilterLabel(TexMobject): - def __init__(self, tex, degrees, **kwargs): - TexMobject.__init__(self, tex + " \\uparrow", **kwargs) - self[-1].rotate(-degrees * np.pi / 180) - -class PolarizingFilter(Circle): - CONFIG = { - "stroke_color" : DARK_GREY, - "fill_color" : LIGHT_GREY, - "fill_opacity" : 0.5, - "label_tex" : None, - "filter_angle" : 0, - "include_arrow_label" : True, - "arrow_length" : 0.7, - } - def __init__(self, **kwargs): - Circle.__init__(self, **kwargs) - - if self.label_tex: - self.label = TexMobject(self.label_tex) - self.label.next_to(self.get_top(), DOWN, MED_SMALL_BUFF) - self.add(self.label) - - arrow = Arrow( - ORIGIN, self.arrow_length*UP, - color = WHITE, - buff = 0, - ) - arrow.shift(self.get_top()) - arrow.rotate(-self.filter_angle) - self.add(arrow) - self.arrow = arrow - shade_in_3d(self) - - if self.include_arrow_label: - arrow_label = TexMobject( - "%.1f^\\circ"%(self.filter_angle*180/np.pi) - ) - arrow_label.add_background_rectangle() - arrow_label.next_to(arrow.get_tip(), UP) - self.add(arrow_label) - self.arrow_label = arrow_label - -################ - -class FilterScene(ThreeDScene): - CONFIG = { - "filter_x_coordinates" : [0], - "pol_filter_configs" : [{}], - "EMWave_config" : { - "start_point" : FRAME_X_RADIUS*LEFT + DOWN+OUT - }, - "axes_config" : {}, - "start_phi" : 0.8*np.pi/2, - "start_theta" : -0.6*np.pi, - "ambient_rotation_rate" : 0.01, - } - def setup(self): - self.axes = ThreeDAxes(**self.axes_config) - self.add(self.axes) - for x in range(len(self.filter_x_coordinates) - len(self.pol_filter_configs)): - self.pol_filter_configs.append({}) - self.pol_filters = VGroup(*[ - PolarizingFilter(**config) - for config in self.pol_filter_configs - ]) - self.pol_filters.rotate(np.pi/2, RIGHT) - self.pol_filters.rotate(-np.pi/2, OUT) - pol_filter_shift = np.array(self.EMWave_config["start_point"]) - pol_filter_shift[0] = 0 - self.pol_filters.shift(pol_filter_shift) - for x, pf in zip(self.filter_x_coordinates, self.pol_filters): - pf.shift(x*RIGHT) - self.add(self.pol_filters) - self.pol_filter = self.pol_filters[0] - - self.set_camera_orientation(self.start_phi, self.start_theta) - if self.ambient_rotation_rate > 0: - self.begin_ambient_camera_rotation(self.ambient_rotation_rate) - - def get_filter_absorption_animation(self, pol_filter, photon): - x = pol_filter.get_center()[0] - alpha = (x + FRAME_X_RADIUS) / (FRAME_WIDTH) - return ApplyMethod( - pol_filter.set_fill, RED, - run_time = photon.run_time, - rate_func = squish_rate_func(there_and_back, alpha - 0.1, alpha + 0.1) - ) - -class DirectionOfPolarizationScene(FilterScene): - CONFIG = { - "pol_filter_configs" : [{ - "include_arrow_label" : False, - }], - "target_theta" : -0.97*np.pi, - "target_phi" : 0.9*np.pi/2, - "ambient_rotation_rate" : 0.005, - "apply_filter" : False, - "quantum" : False, - } - def setup(self): - self.reference_line = Line(ORIGIN, RIGHT) - self.reference_line.set_stroke(width = 0) - self.em_wave = EMWave(**self.EMWave_config) - self.add(self.em_wave) - - FilterScene.setup(self) - - def change_polarization_direction(self, angle, **kwargs): - added_anims = kwargs.get("added_anims", []) - self.play( - ApplyMethod( - self.reference_line.rotate, angle, - **kwargs - ), - *added_anims - ) - - def setup_rectangles(self): - rect1 = Rectangle( - height = 2*self.em_wave.amplitude, - width = FRAME_X_RADIUS + 0.25, - stroke_color = BLUE, - fill_color = BLUE, - fill_opacity = 0.2, - ) - rect1.rotate(np.pi/2, RIGHT) - pf_copy = self.pol_filter.deepcopy() - pf_copy.remove(pf_copy.arrow) - center = pf_copy.get_center() - rect1.move_to(center, RIGHT) - rect2 = rect1.copy() - rect2.move_to(center, LEFT) - - self.rectangles = VGroup(rect1, rect2) - - def update_mobjects(self, *args, **kwargs): - reference_angle = self.reference_line.get_angle() - self.em_wave.rotation = reference_angle - FilterScene.update_mobjects(self, *args, **kwargs) - if self.apply_filter: - self.apply_filters() - self.update_rectangles() - - def apply_filters(self): - vect_groups = [self.em_wave.E_vects, self.em_wave.M_vects] - filters = sorted( - self.pol_filters, - lambda pf1, pf2 : cmp( - pf1.get_center()[0], - pf2.get_center()[0], - ) - ) - for pol_filter in filters: - filter_x = pol_filter.arrow.get_center()[0] - for vect_group, angle in zip(vect_groups, [0, -np.pi/2]): - target_angle = pol_filter.filter_angle + angle - for vect_mob in vect_group: - vect = vect_mob.get_vector() - vect_angle = angle_of_vector([ - vect[2], -vect[1] - ]) - angle_diff = target_angle - vect_angle - angle_diff = (angle_diff+np.pi/2)%np.pi - np.pi/2 - start, end = vect_mob.get_start_and_end() - if start[0] > filter_x: - vect_mob.rotate(angle_diff, RIGHT) - if not self.quantum: - vect_mob.scale( - np.cos(angle_diff), - about_point = start, - ) - - def update_rectangles(self): - if not hasattr(self, "rectangles") or self.rectangles not in self.mobjects: - return - - r1, r2 = self.rectangles - - target_angle = self.reference_line.get_angle() - anchors = r1.get_anchors() - vect = anchors[0] - anchors[3] - curr_angle = angle_of_vector([vect[2], -vect[1]]) - r1.rotate_in_place(target_angle - curr_angle, RIGHT) - - epsilon = 0.001 - curr_depth = max(r2.get_depth(), epsilon) - target_depth = max( - 2*self.em_wave.amplitude*abs(np.cos(target_angle)), - epsilon - ) - r2.stretch_in_place(target_depth/curr_depth, 2) - -################ - -class WantToLearnQM(TeacherStudentsScene): - def construct(self): - question1 = TexMobject( - "\\text{What does }\\qquad \\\\", - "|\\!\\psi \\rangle", "=", - "\\frac{1}{\\sqrt{2}}", "|\\!\\uparrow \\rangle", "+", - "\\frac{1}{\\sqrt{2}}", "|\\!\\downarrow \\rangle \\\\", - "\\text{mean?}\\qquad\\quad" - ) - question1.set_color_by_tex_to_color_map({ - "psi" : BLUE, - "uparrow" : GREEN, - "downarrow" : RED, - }) - question2 = TextMobject( - "Why are complex \\\\ numbers involved?" - ) - question3 = TextMobject( - "How do you compute \\\\ quantum probabilities?" - ) - questions = [question1, question2, question3] - bubbles = VGroup() - - for i, question in zip([1, 2, 0], questions): - self.student_says( - question, - content_introduction_kwargs = {"run_time" : 2}, - student_index = i, - bubble_kwargs = {"fill_opacity" : 1}, - bubble_creation_class = FadeIn, - ) - bubble = self.students[i].bubble - bubble.add(bubble.content) - bubbles.add(bubble) - self.students - self.students[i].bubble = None - self.wait(2) - self.teacher_says( - "First, lots and lots \\\\ of linear algebra", - added_anims = list(map(FadeOut, bubbles)) - ) - self.wait() - -class Goal(PiCreatureScene): - def construct(self): - randy = self.pi_creature - - goal = TextMobject("Goal: ") - goal.set_color(YELLOW) - goal.shift(FRAME_X_RADIUS*LEFT/2 + UP) - weirdness = TextMobject("Eye-catching quantum weirdness") - weirdness.next_to(goal, RIGHT) - cross = Cross(weirdness) - foundations = TextMobject("Foundational intuitions") - foundations.next_to(goal, RIGHT) - - goal.save_state() - goal.scale(0.01) - goal.move_to(randy.get_right()) - - self.play( - goal.restore, - randy.change, "raise_right_hand" - ) - self.play(Write(weirdness, run_time = 2)) - self.play( - ShowCreation(cross), - randy.change, "sassy" - ) - self.wait() - self.play( - VGroup(weirdness, cross).shift, DOWN, - Write(foundations, run_time = 2), - randy.change, "happy" - ) - self.wait(2) - - - #### - - def create_pi_creature(self): - return Randolph().to_corner(DOWN+LEFT) - -class AskWhatsDifferentInQM(TeacherStudentsScene): - def construct(self): - self.student_says( - "What's different in \\\\ quantum mechanics?" - ) - self.play(self.teacher.change, "pondering") - self.wait(3) - -class VideoWrapper(Scene): - CONFIG = { - "title" : "" - } - def construct(self): - title = TextMobject(self.title) - title.to_edge(UP) - self.add(title) - rect = ScreenRectangle() - rect.set_height(6) - rect.next_to(title, DOWN) - self.add(rect) - self.wait() - -class BellsWrapper(VideoWrapper): - CONFIG = { - "title" : "Bell's inequalities" - } - -class FromOtherVideoWrapper(VideoWrapper): - CONFIG = { - "title" : "See the other video..." - } - -class OriginOfQuantumMechanicsWrapper(VideoWrapper): - CONFIG = { - "title" : "The origin of quantum mechanics" - } - -class IntroduceElectricField(PiCreatureScene): - CONFIG = { - "vector_field_colors" : [BLUE_B, BLUE_D], - "max_vector_length" : 0.9, - } - def construct(self): - self.write_title() - self.draw_field() - self.add_particle() - self.let_particle_wander() - - def write_title(self): - morty = self.pi_creature - - title = TextMobject( - "Electro", "magnetic", " field", - arg_separator = "" - ) - title.next_to(morty, UP+LEFT) - electric = TextMobject("Electric") - electric.next_to(title[-1], LEFT) - electric.set_color(BLUE) - - title.save_state() - title.shift(DOWN) - title.fade(1) - - self.play( - title.restore, - morty.change, "raise_right_hand", - ) - self.play( - title[0].set_color, BLUE, - title[1].set_color, YELLOW, - ) - self.wait() - self.play( - ShrinkToCenter(title[1]), - Transform(title[0], electric) - ) - - title.add_background_rectangle() - self.title = title - - def draw_field(self): - morty = self.pi_creature - vector_field = self.get_vector_field() - self.play( - LaggedStartMap( - ShowCreation, vector_field, - run_time = 3 - ), - self.title.center, - self.title.scale, 1.5, - self.title.to_edge, UP, - morty.change, "happy", ORIGIN, - ) - self.wait() - - self.vector_field = vector_field - - def add_particle(self): - morty = self.pi_creature - point = UP+LEFT + SMALL_BUFF*(UP+RIGHT) - particle = self.get_particle() - particle.move_to(point) - - vector = self.get_vector(particle.get_center()) - vector.set_color(RED) - vector.scale(1.5, about_point = point) - vector.shift(SMALL_BUFF*vector.get_vector()) - force = TextMobject("Force") - force.next_to(ORIGIN, UP+RIGHT, SMALL_BUFF) - force.rotate(vector.get_angle()) - force.shift(vector.get_start()) - - particle.save_state() - particle.move_to(morty.get_left() + 0.5*UP + 0.2*RIGHT) - particle.fade(1) - - self.play( - particle.restore, - morty.change, "raise_right_hand", - ) - self.play(morty.change, "thinking", particle) - self.play( - ShowCreation(vector), - Write(force, run_time = 1), - ) - self.wait(2) - - self.particle = particle - self.force_vector = VGroup(vector, force) - - def let_particle_wander(self): - possible_points = [v.get_start() for v in self.vector_field] - points = random.sample(possible_points, 45) - points.append(3*UP+3*LEFT) - particles = VGroup(self.particle, *[ - self.particle.copy().move_to(point) - for point in points - ]) - for particle in particles: - particle.velocity = np.zeros(3) - - self.play( - FadeOut(self.force_vector), - LaggedStartMap(FadeIn, VGroup(*particles[1:])) - ) - self.moving_particles = particles - self.add_foreground_mobjects(self.moving_particles, self.pi_creature) - self.always_update_mobjects = True - self.wait(10) - - ### - - def update_mobjects(self, *args, **kwargs): - Scene.update_mobjects(self, *args, **kwargs) - if hasattr(self, "moving_particles"): - dt = self.frame_duration - for p in self.moving_particles: - vect = self.field_function(p.get_center()) - p.velocity += vect*dt - p.shift(p.velocity*dt) - self.pi_creature.look_at(self.moving_particles[-1]) - - def get_particle(self): - particle = Circle(radius = 0.2) - particle.set_stroke(RED, 3) - particle.set_fill(RED, 0.5) - plus = TexMobject("+") - plus.scale(0.7) - plus.move_to(particle) - particle.add(plus) - return particle - - def get_vector_field(self): - result = VGroup(*[ - self.get_vector(point) - for x in np.arange(-9, 9) - for y in np.arange(-5, 5) - for point in [x*RIGHT + y*UP] - ]) - shading_list = list(result) - shading_list.sort( - key=lambda m: m1.get_length() - ) - VGroup(*shading_list).set_color_by_gradient(*self.vector_field_colors) - result.set_fill(opacity = 0.75) - result.sort(get_norm) - - return result - - def get_vector(self, point): - return Vector(self.field_function(point)).shift(point) - - def field_function(self, point): - x, y = point[:2] - result = y*RIGHT + np.sin(x)*UP - return self.normalized(result) - - def normalized(self, vector): - norm = get_norm(vector) or 1 - target_length = self.max_vector_length * sigmoid(0.1*norm) - return target_length * vector/norm - -class IntroduceMagneticField(IntroduceElectricField, ThreeDScene): - CONFIG = { - "vector_field_colors" : [YELLOW_C, YELLOW_D] - } - def setup(self): - IntroduceElectricField.setup(self) - self.remove(self.pi_creature) - - def construct(self): - self.set_camera_orientation(0.1, -np.pi/2) - self.add_title() - self.add_vector_field() - self.introduce_moving_charge() - self.show_force() - # self.many_charges() - - def add_title(self): - title = TextMobject("Magnetic", "field") - title[0].set_color(YELLOW) - title.scale(1.5) - title.to_edge(UP) - title.add_background_rectangle() - - self.add(title) - self.title = title - - def add_vector_field(self): - vector_field = self.get_vector_field() - - self.play( - LaggedStartMap(ShowCreation, vector_field, run_time = 3), - Animation(self.title) - ) - self.wait() - - def introduce_moving_charge(self): - point = 3*RIGHT + UP - particle = self.get_particle() - particle.move_to(point) - - velocity = Vector(2*RIGHT).shift(particle.get_right()) - velocity.set_color(WHITE) - velocity_word = TextMobject("Velocity") - velocity_word.set_color(velocity.get_color()) - velocity_word.add_background_rectangle() - velocity_word.next_to(velocity, UP, 0, LEFT) - - M_vect = self.get_vector(point) - M_vect.set_color(YELLOW) - M_vect.shift(SMALL_BUFF*M_vect.get_vector()) - - particle.save_state() - particle.shift(FRAME_WIDTH*LEFT) - - self.play( - particle.restore, - run_time = 2, - rate_func=linear, - ) - self.add(velocity) - self.play(Write(velocity_word, run_time = 0.5)) - # self.play(ShowCreation(M_vect)) - self.wait() - - self.particle = particle - - def show_force(self): - point = self.particle.get_center() - F_vect = Vector( - 3*np.cross(self.field_function(point), RIGHT), - color = GREEN - ) - F_vect.shift(point) - F_word = TextMobject("Force") - F_word.rotate(np.pi/2, RIGHT) - F_word.next_to(F_vect, OUT) - F_word.set_color(F_vect.get_color()) - F_eq = TexMobject( - "=","q", "\\textbf{v}", "\\times", "\\textbf{B}" - ) - F_eq.set_color_by_tex_to_color_map({ - "q" : RED, - "B" : YELLOW, - }) - F_eq.rotate(np.pi/2, RIGHT) - F_eq.next_to(F_word, RIGHT) - - - self.move_camera(0.8*np.pi/2, -0.55*np.pi) - self.begin_ambient_camera_rotation() - self.play(ShowCreation(F_vect)) - self.play(Write(F_word)) - self.wait() - self.play(Write(F_eq)) - self.wait(8) - - def many_charges(self): - charges = VGroup() - for y in range(2, 3): - charge = self.get_particle() - charge.move_to(3*LEFT + y*UP) - charge.velocity = (2*RIGHT).astype('float') - charges.add(charge) - - self.revert_to_original_skipping_status() - self.add_foreground_mobjects(*charges) - self.moving_particles = charges - self.wait(5) - - - ### - - def update_mobjects(self, *args, **kwargs): - Scene.update_mobjects(self, *args, **kwargs) - if hasattr(self, "moving_particles"): - dt = self.frame_duration - for p in self.moving_particles: - M_vect = self.field_function(p.get_center()) - F_vect = 3*np.cross(p.velocity, M_vect) - p.velocity += F_vect*dt - p.shift(p.velocity*dt) - - def field_function(self, point): - x, y = point[:2] - y += 0.5 - gauss = lambda r : np.exp(-0.5*r**2) - result = (y**2 - 1)*RIGHT + x*(gauss(y+2) - gauss(y-2))*UP - return self.normalized(result) - -class CurlRelationBetweenFields(ThreeDScene): - def construct(self): - self.add_axes() - self.loop_in_E() - self.loop_in_M() - self.second_loop_in_E() - - def add_axes(self): - self.add(ThreeDAxes(x_axis_radius = FRAME_X_RADIUS)) - - def loop_in_E(self): - E_vects = VGroup(*[ - Vector(0.5*rotate_vector(vect, np.pi/2)).shift(vect) - for vect in compass_directions(8) - ]) - E_vects.set_color(E_COLOR) - point = 1.2*RIGHT + 2*UP + OUT - E_vects.shift(point) - - M_vect = Vector( - IN, - normal_vector = DOWN, - color = M_COLOR - ) - M_vect.shift(point) - M_vect.save_state() - M_vect.scale(0.01, about_point = M_vect.get_start()) - - self.play(ShowCreation(E_vects, run_time = 2)) - self.wait() - self.move_camera(0.8*np.pi/2, -0.45*np.pi) - self.begin_ambient_camera_rotation() - self.play(M_vect.restore, run_time = 3, rate_func=linear) - self.wait(3) - - self.E_vects = E_vects - self.E_circle_center = point - self.M_vect = M_vect - - def loop_in_M(self): - M_vects = VGroup(*[ - Vector( - rotate_vector(vect, np.pi/2), - normal_vector = IN, - color = M_COLOR - ).shift(vect) - for vect in compass_directions(8, LEFT)[1:] - ]) - M_vects.rotate(np.pi/2, RIGHT) - new_point = self.E_circle_center + RIGHT - M_vects.shift(new_point) - - E_vect = self.E_vects[0] - - self.play( - ShowCreation(M_vects, run_time = 2), - *list(map(FadeOut, self.E_vects[1:])) - ) - self.wait() - self.play( - E_vect.rotate, np.pi, RIGHT, [], new_point, - E_vect.scale_about_point, 3, new_point, - run_time = 4, - rate_func=linear, - ) - self.wait() - - self.M_circle_center = new_point - M_vects.add(self.M_vect) - self.M_vects = M_vects - self.E_vect = E_vect - - def second_loop_in_E(self): - E_vects = VGroup(*[ - Vector(1.5*rotate_vector(vect, np.pi/2)).shift(vect) - for vect in compass_directions(8, LEFT)[1:] - ]) - E_vects.set_color(E_COLOR) - point = self.M_circle_center + RIGHT - E_vects.shift(point) - - M_vect = self.M_vects[3] - self.M_vects.remove(M_vect) - - self.play(FadeOut(self.M_vects)) - self.play(ShowCreation(E_vects), Animation(M_vect)) - self.play( - M_vect.rotate, np.pi, RIGHT, [], point, - run_time = 5, - rate_func=linear, - ) - self.wait(3) - -class WriteCurlEquations(Scene): - def construct(self): - eq1 = TexMobject( - "\\nabla \\times", "\\textbf{E}", "=", - "-\\frac{1}{c}", - "\\frac{\\partial \\textbf{B}}{\\partial t}" - ) - eq2 = TexMobject( - "\\nabla \\times", "\\textbf{B}", "=^*", - "\\frac{1}{c}", - "\\frac{\\partial \\textbf{E}}{\\partial t}" - ) - eqs = VGroup(eq1, eq2) - eqs.arrange(DOWN, buff = LARGE_BUFF) - eqs.set_height(FRAME_HEIGHT - 1) - eqs.to_edge(LEFT) - for eq in eqs: - eq.set_color_by_tex_to_color_map({ - "E" : E_COLOR, - "B" : M_COLOR, - }) - footnote = TextMobject("*Ignoring currents") - footnote.next_to(eqs[1], RIGHT) - footnote.to_edge(RIGHT) - - self.play(Write(eq1, run_time = 2)) - self.wait(3) - self.play(Write(eq2, run_time = 2)) - self.play(FadeIn(footnote)) - self.wait(3) - -class IntroduceEMWave(ThreeDScene): - CONFIG = { - "EMWave_config" : { - "requires_start_up" : True - } - } - def setup(self): - self.axes = ThreeDAxes() - self.add(self.axes) - self.em_wave = EMWave(**self.EMWave_config) - self.add(self.em_wave) - self.set_camera_orientation(0.8*np.pi/2, -0.7*np.pi) - self.begin_ambient_camera_rotation() - - def construct(self): - words = TextMobject( - "Electro", "magnetic", " radiation", - arg_separator = "" - ) - words.set_color_by_tex_to_color_map({ - "Electro" : E_COLOR, - "magnetic" : M_COLOR, - }) - words.next_to(ORIGIN, LEFT, MED_LARGE_BUFF) - words.to_edge(UP) - words.rotate(np.pi/2, RIGHT) - - self.wait(7) - self.play(Write(words, run_time = 2)) - self.wait(20) - - ##### - -class SimpleEMWave(IntroduceEMWave): - def construct(self): - self.wait(30) - -class ListRelevantWaveIdeas(TeacherStudentsScene): - def construct(self): - title = TextMobject("Wave","topics") - title.to_corner(UP + LEFT, LARGE_BUFF) - title.set_color(BLUE) - h_line = Line(title.get_left(), title.get_right()) - h_line.next_to(title, DOWN, SMALL_BUFF) - - topics = VGroup(*list(map(TextMobject, [ - "- Superposition", - "- Amplitudes", - "- How phase influences addition", - ]))) - topics.scale(0.8) - topics.arrange(DOWN, aligned_edge = LEFT) - topics.next_to(h_line, DOWN, aligned_edge = LEFT) - - quantum = TextMobject("Quantum") - quantum.set_color(GREEN) - quantum.move_to(title[0], LEFT) - - wave_point = self.teacher.get_corner(UP+LEFT) + 2*UP - self.play( - Animation(VectorizedPoint(wave_point)), - self.teacher.change, "raise_right_hand" - ) - self.wait(2) - self.play( - Write(title, run_time = 2), - ShowCreation(h_line) - ) - self.change_student_modes( - *["pondering"]*3, - added_anims = [LaggedStartMap( - FadeIn, topics, - run_time = 3 - )], - look_at_arg = title - ) - self.play( - Animation(title), - self.teacher.change, "happy" - ) - self.play( - title[0].next_to, quantum.copy(), UP, MED_SMALL_BUFF, LEFT, - title[0].fade, 0.5, - title[1].next_to, quantum.copy(), RIGHT, 2*SMALL_BUFF, - Write(quantum), - ) - self.wait(5) - -class DirectWaveOutOfScreen(IntroduceEMWave): - CONFIG = { - "EMWave_config" : { - "requires_start_up" : False, - "amplitude" : 2, - "start_point" : FRAME_X_RADIUS*LEFT, - "A_vect" : [0, 1, 0], - "start_up_time" : 0, - } - } - def setup(self): - IntroduceEMWave.setup(self) - self.remove(self.axes) - for ov in self.em_wave.continual_animations: - ov.vector.normal_vector = RIGHT - self.set_camera_orientation(0.9*np.pi/2, -0.3*np.pi) - - def construct(self): - self.move_into_position() - self.fade_M_vects() - self.fade_all_but_last_E_vects() - - def move_into_position(self): - self.wait(2) - self.update_mobjects() - faded_vectors = VGroup(*[ - ov.vector - for ov in self.em_wave.continual_animations[:-2] - ]) - self.move_camera( - 0.99*np.pi/2, -0.01, - run_time = 2, - added_anims = [faded_vectors.set_fill, None, 0.5] - ) - self.stop_ambient_camera_rotation() - self.move_camera( - np.pi/2, 0, - added_anims = [faded_vectors.set_fill, None, 0.05], - run_time = 2, - ) - self.faded_vectors = faded_vectors - - def fade_M_vects(self): - self.play( - self.em_wave.M_vects.set_fill, None, 0 - ) - self.wait(2) - - def fade_all_but_last_E_vects(self): - self.play(self.faded_vectors.set_fill, None, 0) - self.wait(4) - -class ShowVectorEquation(Scene): - CONFIG = { - "f_color" : RED, - "phi_color" : MAROON_B, - "A_color" : GREEN, - } - def construct(self): - self.add_vector() - self.add_plane() - self.write_horizontally_polarized() - self.write_components() - self.show_graph() - self.add_phi() - self.add_amplitude() - self.add_kets() - self.switch_to_vertically_polarized_light() - - def add_vector(self): - self.vector = Vector(2*RIGHT, color = E_COLOR) - self.oscillating_vector = OscillatingVector( - self.vector, - A_vect = [2, 0, 0], - frequency = 0.25, - ) - self.add(self.oscillating_vector) - self.wait(3) - - def add_plane(self): - xy_plane = NumberPlane( - axes_color = LIGHT_GREY, - color = DARK_GREY, - secondary_color = DARK_GREY, - x_unit_size = 2, - y_unit_size = 2, - ) - xy_plane.add_coordinates() - xy_plane.add(xy_plane.get_axis_labels()) - - self.play( - Write(xy_plane), - Animation(self.vector) - ) - self.wait(2) - self.xy_plane = xy_plane - - def write_horizontally_polarized(self): - words = TextMobject( - "``", "Horizontally", " polarized", "''", - arg_separator = "" - ) - words.next_to(ORIGIN, LEFT) - words.to_edge(UP) - words.add_background_rectangle() - - self.play(Write(words, run_time = 3)) - self.wait() - - self.horizontally_polarized_words = words - - def write_components(self): - x, y = components = VGroup( - TexMobject("\\cos(", "2\\pi", "f_x", "t", "+ ", "\\phi_x", ")"), - TexMobject("0", "") - ) - components.arrange(DOWN) - lb, rb = brackets = TexMobject("[]") - brackets.set_height(components.get_height() + SMALL_BUFF) - lb.next_to(components, LEFT, buff = 0.3) - rb.next_to(components, RIGHT, buff = 0.3) - E, equals = E_equals = TexMobject( - "\\vec{\\textbf{E}}", "=" - ) - E.set_color(E_COLOR) - E_equals.next_to(brackets, LEFT) - E_equals.add_background_rectangle() - brackets.add_background_rectangle() - group = VGroup(E_equals, brackets, components) - group.next_to( - self.horizontally_polarized_words, - DOWN, MED_LARGE_BUFF, RIGHT - ) - - x_without_phi = TexMobject("\\cos(", "2\\pi", "f_x", "t", ")") - x_without_phi.move_to(x) - for mob in x, x_without_phi: - mob.set_color_by_tex_to_color_map({ - "f_x" : self.f_color, - "phi_x" : self.phi_color, - }) - - def update_brace(brace): - brace.stretch_to_fit_width( - max(self.vector.get_width(), 0.001) - ) - brace.next_to(self.vector.get_center(), DOWN, SMALL_BUFF) - return brace - moving_brace = Mobject.add_updater( - Brace(Line(LEFT, RIGHT), DOWN), update_brace - ) - moving_x_without_phi = Mobject.add_updater( - x_without_phi.copy().add_background_rectangle(), - lambda m : m.next_to(moving_brace.mobject, DOWN, SMALL_BUFF) - ) - - self.play(Write(E_equals), Write(brackets)) - y.save_state() - y.move_to(self.horizontally_polarized_words) - y.set_fill(opacity = 0) - self.play(y.restore) - self.wait() - self.add(moving_brace, moving_x_without_phi) - self.play( - FadeIn(moving_brace.mobject), - FadeIn(x_without_phi), - FadeIn(moving_x_without_phi.mobject), - lag_ratio = 0.5, - run_time = 2, - ) - self.wait(3) - self.play( - FadeOut(moving_brace.mobject), - FadeOut(moving_x_without_phi.mobject), - ) - self.remove(moving_brace, moving_x_without_phi) - - self.E_equals = E_equals - self.brackets = brackets - self.x_without_phi = x_without_phi - self.components = components - - def show_graph(self): - axes = Axes( - x_min = -0.5, - x_max = 5.2, - y_min = -1.5, - y_max = 1.5, - ) - axes.x_axis.add_numbers(*list(range(1, 6))) - t = TexMobject("t") - t.next_to(axes.x_axis, UP, SMALL_BUFF, RIGHT) - cos = self.x_without_phi.copy() - cos.next_to(axes.y_axis, RIGHT, SMALL_BUFF, UP) - cos_arg = VGroup(*cos[1:-1]) - fx_equals_1 = TexMobject("f_x", "= 1") - fx_equals_fourth = TexMobject("f_x", "= 0.25") - fx_group = VGroup(fx_equals_1, fx_equals_fourth) - for fx in fx_group: - fx[0].set_color(self.f_color) - fx.move_to(axes, UP+RIGHT) - high_f_graph, low_f_graph = graphs = VGroup(*[ - FunctionGraph( - lambda x : np.cos(2*np.pi*f*x), - color = E_COLOR, - x_min = 0, - x_max = 4/f, - num_steps = 20/f, - ) - for f in (1, 0.25,) - ]) - - group = VGroup(axes, t, cos, high_f_graph, *fx_group) - rect = SurroundingRectangle( - group, - buff = MED_LARGE_BUFF, - stroke_color = WHITE, - stroke_width = 3, - fill_color = BLACK, - fill_opacity = 0.9 - ) - group.add_to_back(rect) - group.scale(0.8) - group.to_corner(UP+RIGHT, buff = -SMALL_BUFF) - group.remove(*it.chain(fx_group, graphs)) - low_f_graph.scale(0.8) - low_f_graph.move_to(high_f_graph, LEFT) - - cos_arg_rect = SurroundingRectangle(cos_arg) - - new_ov = OscillatingVector( - Vector(RIGHT, color = E_COLOR), - A_vect = [2, 0, 0], - frequency = 1, - start_up_time = 0, - ) - - self.play(FadeIn(group)) - self.play( - ReplacementTransform( - self.components[0].get_part_by_tex("f_x").copy(), - fx_equals_1 - ), - ) - self.wait(4 - (self.oscillating_vector.internal_time%4)) - self.remove(self.oscillating_vector) - self.add(new_ov) - self.play(ShowCreation( - high_f_graph, run_time = 4, - rate_func=linear, - )) - self.wait() - self.play(FadeOut(new_ov.vector)) - self.remove(new_ov) - self.add(self.oscillating_vector) - self.play( - ReplacementTransform(*fx_group), - ReplacementTransform(*graphs), - FadeOut(new_ov.vector), - FadeIn(self.vector) - ) - self.wait(4) - self.play(ShowCreation(cos_arg_rect)) - self.play(FadeOut(cos_arg_rect)) - self.wait(5) - - self.corner_group = group - self.fx_equals_fourth = fx_equals_fourth - self.corner_cos = cos - self.low_f_graph = low_f_graph - self.graph_axes = axes - - def add_phi(self): - corner_cos = self.corner_cos - corner_phi = TexMobject("+", "\\phi_x") - corner_phi.set_color_by_tex("phi", self.phi_color) - corner_phi.scale(0.8) - corner_phi.next_to(corner_cos[-2], RIGHT, SMALL_BUFF) - - x, y = self.components - x_without_phi = self.x_without_phi - - words = TextMobject("``Phase shift''") - words.next_to(ORIGIN, UP+LEFT) - words.set_color(self.phi_color) - words.add_background_rectangle() - arrow = Arrow(words.get_top(), x[-2]) - arrow.set_color(WHITE) - - self.play( - ReplacementTransform( - VGroup(*x_without_phi[:-1]), - VGroup(*x[:-3]), - ), - ReplacementTransform(x_without_phi[-1], x[-1]), - Write(VGroup(*x[-3:-1])), - corner_cos[-1].next_to, corner_phi.copy(), RIGHT, SMALL_BUFF, - Write(corner_phi), - FadeOut(self.fx_equals_fourth), - ) - self.play(self.low_f_graph.shift, MED_LARGE_BUFF*LEFT) - self.play( - Write(words, run_time = 1), - ShowCreation(arrow) - ) - self.wait(3) - self.play(*list(map(FadeOut, [words, arrow]))) - - self.corner_cos.add(corner_phi) - - def add_amplitude(self): - x, y = self.components - corner_cos = self.corner_cos - graph = self.low_f_graph - graph_y_axis = self.graph_axes.y_axis - - A = TexMobject("A_x") - A.set_color(self.A_color) - A.move_to(x.get_left()) - corner_A = A.copy() - corner_A.scale(0.8) - corner_A.move_to(corner_cos, LEFT) - - h_brace = Brace(Line(ORIGIN, 2*RIGHT), UP) - v_brace = Brace(Line( - graph_y_axis.number_to_point(0), - graph_y_axis.number_to_point(1), - ), LEFT, buff = SMALL_BUFF) - for brace in h_brace, v_brace: - brace.A = brace.get_tex("A_x") - brace.A.set_color(self.A_color) - v_brace.A.scale(0.5, about_point = v_brace.get_center()) - all_As = VGroup(A, corner_A, h_brace.A, v_brace.A) - - def update_vect(vect): - self.oscillating_vector.A_vect[0] = h_brace.get_width() - return vect - - self.play( - GrowFromCenter(h_brace), - GrowFromCenter(v_brace), - ) - self.wait(2) - self.play( - x.next_to, A, RIGHT, SMALL_BUFF, - corner_cos.next_to, corner_A, RIGHT, SMALL_BUFF, - FadeIn(all_As) - ) - x.add(A) - corner_cos.add(corner_A) - self.wait() - factor = 0.5 - self.play( - v_brace.stretch_in_place, factor, 1, - v_brace.move_to, v_brace.copy(), DOWN, - MaintainPositionRelativeTo(v_brace.A, v_brace), - h_brace.stretch_in_place, factor, 0, - h_brace.move_to, h_brace.copy(), LEFT, - MaintainPositionRelativeTo(h_brace.A, h_brace), - UpdateFromFunc(self.vector, update_vect), - graph.stretch_in_place, factor, 1, - ) - self.wait(4) - - self.h_brace = h_brace - self.v_brace = v_brace - - def add_kets(self): - x, y = self.components - E_equals = self.E_equals - for mob in x, y, E_equals: - mob.add_background_rectangle() - mob.generate_target() - - right_ket = TexMobject("|\\rightarrow\\rangle") - up_ket = TexMobject("|\\uparrow\\rangle") - kets = VGroup(right_ket, up_ket) - kets.set_color(YELLOW) - for ket in kets: - ket.add_background_rectangle() - plus = TextMobject("+") - group = VGroup( - E_equals.target, - x.target, right_ket, plus, - y.target, up_ket, - ) - group.arrange(RIGHT) - E_equals.target.shift(SMALL_BUFF*UP) - group.scale(0.8) - group.move_to(self.brackets, DOWN) - group.to_edge(LEFT, buff = MED_SMALL_BUFF) - - kets_word = TextMobject("``kets''") - kets_word.next_to(kets, DOWN, buff = 0.8) - arrows = VGroup(*[ - Arrow(kets_word.get_top(), ket, color = ket.get_color()) - for ket in kets - ]) - ket_rects = VGroup(*list(map(SurroundingRectangle, kets))) - ket_rects.set_color(WHITE) - unit_vectors = VGroup(*[Vector(2*vect) for vect in (RIGHT, UP)]) - unit_vectors.set_fill(YELLOW) - - self.play( - FadeOut(self.brackets), - *list(map(MoveToTarget, [E_equals, x, y])) - ) - self.play(*list(map(Write, [right_ket, plus, up_ket])), run_time = 1) - self.play( - Write(kets_word), - LaggedStartMap(ShowCreation, arrows, lag_ratio = 0.7), - run_time = 2, - ) - self.wait() - for ket, ket_rect, unit_vect in zip(kets, ket_rects, unit_vectors): - self.play(ShowCreation(ket_rect)) - self.play(FadeOut(ket_rect)) - self.play(ReplacementTransform(ket[1][1].copy(), unit_vect)) - self.wait() - self.play(FadeOut(unit_vectors)) - self.play(*list(map(FadeOut, [kets_word, arrows]))) - - self.kets = kets - self.plus = plus - - def switch_to_vertically_polarized_light(self): - x, y = self.components - x_ket, y_ket = self.kets - plus = self.plus - - x.target = TexMobject("0", "").add_background_rectangle() - y.target = TexMobject( - "A_y", "\\cos(", "2\\pi", "f_y", "t", "+", "\\phi_y", ")" - ) - y.target.set_color_by_tex_to_color_map({ - "A" : self.A_color, - "f" : self.f_color, - "phi" : self.phi_color, - }) - y.target.add_background_rectangle() - VGroup(x.target, y.target).scale(0.8) - for mob in [plus] + list(self.kets): - mob.generate_target() - - movers = x, x_ket, plus, y, y_ket - group = VGroup(*[m.target for m in movers]) - group.arrange(RIGHT) - group.move_to(x, LEFT) - - vector_A_vect = np.array(self.oscillating_vector.A_vect) - def update_vect(vect, alpha): - self.oscillating_vector.A_vect = rotate_vector( - vector_A_vect, alpha*np.pi/2 - ) - return vect - - new_h_brace = Brace(Line(ORIGIN, UP), RIGHT) - - words = TextMobject( - "``", "Vertically", " polarized", "''", - arg_separator = "", - ) - words.add_background_rectangle() - words.move_to(self.horizontally_polarized_words) - - self.play( - UpdateFromAlphaFunc(self.vector, update_vect), - Transform(self.h_brace, new_h_brace), - self.h_brace.A.next_to, new_h_brace, RIGHT, SMALL_BUFF, - Transform(self.horizontally_polarized_words, words), - *list(map(FadeOut, [ - self.corner_group, self.v_brace, - self.v_brace.A, self.low_f_graph, - ])) - ) - self.play(*list(map(MoveToTarget, movers))) - self.wait(5) - -class ChangeFromHorizontalToVerticallyPolarized(DirectionOfPolarizationScene): - CONFIG = { - "filter_x_coordinates" : [], - "EMWave_config" : { - "start_point" : FRAME_X_RADIUS*LEFT, - "A_vect" : [0, 2, 0], - } - } - def setup(self): - DirectionOfPolarizationScene.setup(self) - self.axes.z_axis.rotate(np.pi/2, OUT) - self.axes.y_axis.rotate(np.pi/2, UP) - self.remove(self.pol_filter) - self.em_wave.M_vects.set_fill(opacity = 0) - for vect in self.em_wave.E_vects: - vect.normal_vector = RIGHT - vect.set_fill(opacity = 0.5) - self.em_wave.E_vects[-1].set_fill(opacity = 1) - - self.set_camera_orientation(0.9*np.pi/2, -0.05*np.pi) - - def construct(self): - self.wait(3) - self.change_polarization_direction(np.pi/2) - self.wait(10) - -class SumOfTwoWaves(ChangeFromHorizontalToVerticallyPolarized): - CONFIG = { - "axes_config" : { - "y_max" : 1.5, - "y_min" : -1.5, - "z_max" : 1.5, - "z_min" : -1.5, - }, - "EMWave_config" : { - "A_vect" : [0, 0, 1], - }, - "ambient_rotation_rate" : 0, - } - def setup(self): - ChangeFromHorizontalToVerticallyPolarized.setup(self) - for vect in self.em_wave.E_vects[:-1]: - vect.set_fill(opacity = 0.3) - self.side_em_waves = [] - for shift_vect, A_vect in (5*DOWN, [0, 1, 0]), (5*UP, [0, 1, 1]): - axes = self.axes.copy() - em_wave = copy.deepcopy(self.em_wave) - axes.shift(shift_vect) - em_wave.mobject.shift(shift_vect) - em_wave.start_point += shift_vect - for ov in em_wave.continual_animations: - ov.A_vect = np.array(A_vect) - self.add(axes, em_wave) - self.side_em_waves.append(em_wave) - - self.set_camera_orientation(0.95*np.pi/2, -0.03*np.pi) - - def construct(self): - plus, equals = pe = VGroup(*list(map(TexMobject, "+="))) - pe.scale(2) - pe.rotate(np.pi/2, RIGHT) - pe.rotate(np.pi/2, OUT) - plus.shift(2.5*DOWN) - equals.shift(2.5*UP) - self.add(pe) - - self.wait(16) - -class ShowTipToTailSum(ShowVectorEquation): - def construct(self): - self.force_skipping() - self.add_vector() - self.add_plane() - self.add_vertial_vector() - self.revert_to_original_skipping_status() - - self.add_kets() - self.show_vector_sum() - self.write_superposition() - self.add_amplitudes() - self.add_phase_shift() - - def add_vertial_vector(self): - self.h_vector = self.vector - self.h_oscillating_vector = self.oscillating_vector - self.h_oscillating_vector.start_up_time = 0 - - self.v_oscillating_vector = self.h_oscillating_vector.copy() - self.v_vector = self.v_oscillating_vector.vector - self.v_oscillating_vector.A_vect = [0, 2, 0] - self.v_oscillating_vector.update(0) - - self.d_oscillating_vector = Mobject.add_updater( - Vector(UP+RIGHT, color = E_COLOR), - lambda v : v.put_start_and_end_on( - ORIGIN, - self.v_vector.get_end()+ self.h_vector.get_end(), - ) - ) - self.d_vector = self.d_oscillating_vector.mobject - self.d_oscillating_vector.update(0) - - self.add(self.v_oscillating_vector) - self.add_foreground_mobject(self.v_vector) - - def add_kets(self): - h_ket, v_ket = kets = VGroup(*[ - TexMobject( - "\\cos(", "2\\pi", "f", "t", ")", - "|\\!\\%sarrow\\rangle"%s - ) - for s in ("right", "up") - ]) - for ket in kets: - ket.set_color_by_tex_to_color_map({ - "f" : self.f_color, - "rangle" : YELLOW, - }) - ket.add_background_rectangle(opacity = 1) - ket.scale(0.8) - - h_ket.next_to(2*RIGHT, UP, SMALL_BUFF) - v_ket.next_to(2*UP, UP, SMALL_BUFF) - self.add_foreground_mobject(kets) - - self.kets = kets - - def show_vector_sum(self): - h_line = DashedLine(ORIGIN, 2*RIGHT) - v_line = DashedLine(ORIGIN, 2*UP) - - h_line.update = self.generate_dashed_line_update( - self.h_vector, self.v_vector - ) - v_line.update = self.generate_dashed_line_update( - self.v_vector, self.h_vector - ) - - h_ket, v_ket = self.kets - for ket in self.kets: - ket.generate_target() - plus = TexMobject("+") - ket_sum = VGroup(h_ket.target, plus, v_ket.target) - ket_sum.arrange(RIGHT) - ket_sum.next_to(3*RIGHT + 2*UP, UP, SMALL_BUFF) - - self.wait(4) - self.remove(self.h_oscillating_vector, self.v_oscillating_vector) - self.add(self.h_vector, self.v_vector) - h_line.update(h_line) - v_line.update(v_line) - self.play(*it.chain( - list(map(MoveToTarget, self.kets)), - [Write(plus)], - list(map(ShowCreation, [h_line, v_line])), - )) - blue_black = average_color(BLUE, BLACK) - self.play( - GrowFromPoint(self.d_vector, ORIGIN), - self.h_vector.set_fill, blue_black, - self.v_vector.set_fill, blue_black, - ) - self.wait() - self.add( - self.h_oscillating_vector, - self.v_oscillating_vector, - self.d_oscillating_vector, - Mobject.add_updater(h_line, h_line.update), - Mobject.add_updater(v_line, v_line.update), - ) - self.wait(4) - - self.ket_sum = VGroup(h_ket, plus, v_ket) - - def write_superposition(self): - superposition_words = TextMobject( - "``Superposition''", "of", - "$|\\!\\rightarrow\\rangle$", "and", - "$|\\!\\uparrow\\rangle$", - ) - superposition_words.scale(0.8) - superposition_words.set_color_by_tex("rangle", YELLOW) - superposition_words.add_background_rectangle() - superposition_words.to_corner(UP+LEFT) - ket_sum = self.ket_sum - ket_sum.generate_target() - ket_sum.target.move_to(superposition_words) - ket_sum.target.align_to(ket_sum, UP) - - sum_word = TextMobject("", "Sum") - weighted_sum_word = TextMobject("Weighted", "sum") - for word in sum_word, weighted_sum_word: - word.scale(0.8) - word.set_color(GREEN) - word.add_background_rectangle() - word.move_to(superposition_words.get_part_by_tex("Super")) - - self.play( - Write(superposition_words, run_time = 2), - MoveToTarget(ket_sum) - ) - self.wait(2) - self.play( - FadeIn(sum_word), - superposition_words.shift, MED_LARGE_BUFF*DOWN, - ket_sum.shift, MED_LARGE_BUFF*DOWN, - ) - self.wait() - self.play(ReplacementTransform( - sum_word, weighted_sum_word - )) - self.wait(2) - - def add_amplitudes(self): - h_ket, plus, r_ket = self.ket_sum - for mob in self.ket_sum: - mob.generate_target() - h_A, v_A = 2, 0.5 - h_A_mob, v_A_mob = A_mobs = VGroup(*[ - TexMobject(str(A)).add_background_rectangle() - for A in [h_A, v_A] - ]) - A_mobs.scale(0.8) - A_mobs.set_color(GREEN) - h_A_mob.move_to(h_ket, LEFT) - VGroup(h_ket.target, plus.target).next_to( - h_A_mob, RIGHT, SMALL_BUFF - ) - v_A_mob.next_to(plus.target, RIGHT, SMALL_BUFF) - r_ket.target.next_to(v_A_mob, RIGHT, SMALL_BUFF) - A_mobs.shift(0.4*SMALL_BUFF*UP) - - h_ov = self.h_oscillating_vector - v_ov = self.v_oscillating_vector - - - self.play(*it.chain( - list(map(MoveToTarget, self.ket_sum)), - list(map(Write, A_mobs)), - [ - UpdateFromAlphaFunc( - ov.vector, - self.generate_A_update( - ov, - A*np.array(ov.A_vect), - np.array(ov.A_vect) - ) - ) - for ov, A in [(h_ov, h_A), (v_ov, v_A)] - ] - )) - self.wait(4) - - self.A_mobs = A_mobs - - def add_phase_shift(self): - h_ket, plus, v_ket = self.ket_sum - - plus_phi = TexMobject("+", "\\pi/2") - plus_phi.set_color_by_tex("pi", self.phi_color) - plus_phi.scale(0.8) - plus_phi.next_to(v_ket.get_part_by_tex("t"), RIGHT, SMALL_BUFF) - v_ket.generate_target() - VGroup(*v_ket.target[1][-2:]).next_to(plus_phi, RIGHT, SMALL_BUFF) - v_ket.target[0].replace(v_ket.target[1]) - - - h_ov = self.h_oscillating_vector - v_ov = self.v_oscillating_vector - - ellipse = Circle() - ellipse.stretch_to_fit_height(2) - ellipse.stretch_to_fit_width(8) - ellipse.set_color(self.phi_color) - - h_A_mob, v_A_mob = self.A_mobs - new_h_A_mob = v_A_mob.copy() - new_h_A_mob.move_to(h_A_mob, RIGHT) - - self.add_foreground_mobject(plus_phi) - self.play( - MoveToTarget(v_ket), - Write(plus_phi), - UpdateFromAlphaFunc( - v_ov.vector, - self.generate_phi_update( - v_ov, - np.array([0, np.pi/2, 0]), - np.array(v_ov.phi_vect) - ) - ) - ) - self.play(FadeIn(ellipse)) - self.wait(5) - self.play( - UpdateFromAlphaFunc( - h_ov.vector, - self.generate_A_update( - h_ov, - 0.25*np.array(h_ov.A_vect), - np.array(h_ov.A_vect), - ) - ), - ellipse.stretch, 0.25, 0, - Transform(h_A_mob, new_h_A_mob) - ) - self.wait(8) - - ##### - - def generate_A_update(self, ov, A_vect, prev_A_vect): - def update(vect, alpha): - ov.A_vect = interpolate( - np.array(prev_A_vect), - A_vect, - alpha - ) - return vect - return update - - def generate_phi_update(self, ov, phi_vect, prev_phi_vect): - def update(vect, alpha): - ov.phi_vect = interpolate( - prev_phi_vect, phi_vect, alpha - ) - return vect - return update - - def generate_dashed_line_update(self, v1, v2): - def update_line(line): - line.put_start_and_end_on_with_projection( - *v1.get_start_and_end() - ) - line.shift(v2.get_end() - line.get_start()) - return update_line - -class FromBracketFootnote(Scene): - def construct(self): - words = TextMobject( - "From, ``Bra", "ket", "''", - arg_separator = "" - ) - words.set_color_by_tex("ket", YELLOW) - words.set_width(FRAME_WIDTH - 1) - self.add(words) - -class Ay(Scene): - def construct(self): - sym = TexMobject("A_y").set_color(GREEN) - sym.scale(5) - self.add(sym) - -class CircularlyPolarizedLight(SumOfTwoWaves): - CONFIG = { - "EMWave_config" : { - "phi_vect" : [0, np.pi/2, 0], - }, - } - -class AlternateBasis(ShowTipToTailSum): - def construct(self): - self.force_skipping() - self.add_vector() - self.add_plane() - self.add_vertial_vector() - self.add_kets() - self.show_vector_sum() - self.remove(self.ket_sum, self.kets) - self.reset_amplitude() - self.revert_to_original_skipping_status() - - self.add_superposition_text() - self.rotate_plane() - self.show_vertically_polarized() - - def reset_amplitude(self): - self.h_oscillating_vector.A_vect = np.array([1, 0, 0]) - - def add_superposition_text(self): - self.hv_superposition, self.da_superposition = superpositions = [ - TexMobject( - "\\vec{\\textbf{E}}", "=", - "(\\dots)", - "|\\!\\%sarrow\\rangle"%s1, - "+", - "(\\dots)", - "|\\!\\%sarrow\\rangle"%s2, - ) - for s1, s2 in [("right", "up"), ("ne", "nw")] - ] - for superposition in superpositions: - superposition.set_color_by_tex("rangle", YELLOW) - superposition.set_color_by_tex("E", E_COLOR) - superposition.add_background_rectangle(opacity = 1) - superposition.to_edge(UP) - self.add(self.hv_superposition) - - def rotate_plane(self): - new_plane = NumberPlane( - x_unit_size = 2, - y_unit_size = 2, - y_radius = FRAME_X_RADIUS, - secondary_line_ratio = 0, - ) - new_plane.add_coordinates() - new_plane.save_state() - new_plane.fade(1) - - d = (RIGHT + UP)/np.sqrt(2) - a = (LEFT + UP)/np.sqrt(2) - - self.wait(4) - self.play( - self.xy_plane.fade, 0.5, - self.xy_plane.coordinate_labels.fade, 1, - new_plane.restore, - new_plane.rotate, np.pi/4, - UpdateFromAlphaFunc( - self.h_vector, - self.generate_A_update( - self.h_oscillating_vector, - 2*d*np.dot(0.5*RIGHT + UP, d), - np.array(self.h_oscillating_vector.A_vect) - ) - ), - UpdateFromAlphaFunc( - self.v_vector, - self.generate_A_update( - self.v_oscillating_vector, - 2*a*np.dot(0.5*RIGHT + UP, a), - np.array(self.v_oscillating_vector.A_vect) - ) - ), - Transform(self.hv_superposition, self.da_superposition), - run_time = 2, - ) - self.wait(4) - - def show_vertically_polarized(self): - self.play( - UpdateFromAlphaFunc( - self.h_vector, - self.generate_A_update( - self.h_oscillating_vector, - np.array([0.7, 0.7, 0]), - np.array(self.h_oscillating_vector.A_vect) - ) - ), - UpdateFromAlphaFunc( - self.v_vector, - self.generate_A_update( - self.v_oscillating_vector, - np.array([-0.7, 0.7, 0]), - np.array(self.v_oscillating_vector.A_vect) - ) - ), - ) - self.wait(8) - -class WriteBasis(Scene): - def construct(self): - words = TextMobject("Choice of ``basis''") - words.set_width(FRAME_WIDTH-1) - self.play(Write(words)) - self.wait() - -class ShowPolarizingFilter(DirectionOfPolarizationScene): - CONFIG = { - "EMWave_config" : { - "start_point" : FRAME_X_RADIUS*LEFT, - }, - "apply_filter" : True, - } - def construct(self): - self.setup_rectangles() - self.fade_M_vects() - self.axes.fade(0.5) - - self.initial_rotation() - self.mention_energy_absorption() - self.write_as_superposition() - self.diagonal_filter() - - def setup_rectangles(self): - DirectionOfPolarizationScene.setup_rectangles(self) - self.rectangles[-1].fade(1) - - def fade_M_vects(self): - self.em_wave.M_vects.set_fill(opacity = 0) - - def initial_rotation(self): - self.wait() - self.play(FadeIn(self.rectangles)) - self.wait() - self.change_polarization_direction(np.pi/2, run_time = 3) - self.move_camera(phi = 0.9*np.pi/2, theta = -0.05*np.pi) - - def mention_energy_absorption(self): - words = TextMobject("Absorbs horizontal \\\\ energy") - words.set_color(RED) - words.next_to(ORIGIN, UP+RIGHT, MED_LARGE_BUFF) - words.rotate(np.pi/2, RIGHT) - words.rotate(np.pi/2, OUT) - - lines = VGroup(*[ - Line( - np.sin(a)*RIGHT + np.cos(a)*UP, - np.sin(a)*LEFT + np.cos(a)*UP, - color = RED, - stroke_width = 2, - ) - for a in np.linspace(0, np.pi, 15) - ]) - lines.rotate(np.pi/2, RIGHT) - lines.rotate(np.pi/2, OUT) - - self.play( - Write(words, run_time = 2), - *list(map(GrowFromCenter, lines)) - ) - self.wait(6) - self.play(FadeOut(lines)) - self.play(FadeOut(words)) - - def write_as_superposition(self): - superposition, continual_updates = self.get_superposition_tex(0, "right", "up") - rect = superposition.rect - - self.play(Write(superposition, run_time = 2)) - self.add(*continual_updates) - for angle in np.pi/4, -np.pi/6: - self.change_polarization_direction(angle) - self.wait(3) - - self.move_camera( - theta = -0.6*np.pi, - added_anims = [ - Rotate(superposition, -0.6*np.pi, axis = OUT) - ] - ) - rect.set_stroke(YELLOW, 3) - self.play(ShowCreation(rect)) - arrow = Arrow( - rect.get_nadir(), 3*RIGHT + 0.5*OUT, - normal_vector = DOWN - ) - self.play(ShowCreation(arrow)) - - for angle in np.pi/3, -np.pi/3, np.pi/6: - self.change_polarization_direction(angle) - self.wait(2) - self.play( - FadeOut(superposition), - FadeOut(arrow), - *[ - FadeOut(cu.mobject) - for cu in continual_updates - ] - ) - self.move_camera(theta = -0.1*np.pi) - - def diagonal_filter(self): - superposition, continual_updates = self.get_superposition_tex(-np.pi/4, "nw", "ne") - - def update_filter_angle(pf, alpha): - pf.filter_angle = interpolate(0, -np.pi/4, alpha) - - self.play( - Rotate(self.pol_filter, np.pi/4, axis = LEFT), - UpdateFromAlphaFunc(self.pol_filter, update_filter_angle), - Animation(self.em_wave.mobject) - ) - superposition.rect.set_stroke(YELLOW, 2) - self.play(Write(superposition, run_time = 2)) - self.add(*continual_updates) - for angle in np.pi/4, -np.pi/3, -np.pi/6: - self.change_polarization_direction(np.pi/4) - self.wait(2) - - ####### - - def get_superposition_tex(self, angle, s1, s2): - superposition = TexMobject( - "0.00", "\\cos(", "2\\pi", "f", "t", ")", - "|\\! \\%sarrow \\rangle"%s1, - "+", - "1.00", "\\cos(", "2\\pi", "f", "t", ")", - "|\\! \\%sarrow \\rangle"%s2, - ) - - A_x = DecimalNumber(0) - A_y = DecimalNumber(1) - A_x.move_to(superposition[0]) - A_y.move_to(superposition[8]) - superposition.submobjects[0] = A_x - superposition.submobjects[8] = A_y - VGroup(A_x, A_y).set_color(GREEN) - superposition.set_color_by_tex("f", RED) - superposition.set_color_by_tex("rangle", YELLOW) - plus = superposition.get_part_by_tex("+") - plus.add_to_back(BackgroundRectangle(plus)) - - v_part = VGroup(*superposition[8:]) - rect = SurroundingRectangle(v_part) - rect.fade(1) - superposition.rect = rect - superposition.add(rect) - - superposition.shift(3*UP + SMALL_BUFF*LEFT) - superposition.rotate(np.pi/2, RIGHT) - superposition.rotate(np.pi/2, OUT) - - def generate_decimal_update(trig_func): - def update_decimal(decimal): - new_decimal = DecimalNumber(abs(trig_func( - self.reference_line.get_angle() - angle - ))) - new_decimal.rotate(np.pi/2, RIGHT) - new_decimal.rotate(np.pi/2, OUT) - new_decimal.rotate(self.camera.get_theta(), OUT) - new_decimal.set_depth(decimal.get_depth()) - new_decimal.move_to(decimal, UP) - new_decimal.set_color(decimal.get_color()) - decimal.align_data(new_decimal) - families = [ - mob.family_members_with_points() - for mob in (decimal, new_decimal) - ] - for sm1, sm2 in zip(*families): - sm1.interpolate(sm1, sm2, 1) - return decimal - return update_decimal - - continual_updates = [ - Mobject.add_updater( - A_x, generate_decimal_update(np.sin), - ), - Mobject.add_updater( - A_y, generate_decimal_update(np.cos), - ), - ] - - return superposition, continual_updates - -class NamePolarizingFilter(Scene): - def construct(self): - words = TextMobject("Polarizing filter") - words.set_width(FRAME_WIDTH - 1) - self.play(Write(words)) - self.wait() - -class EnergyOfWavesWavePortion(DirectWaveOutOfScreen): - CONFIG = { - "EMWave_config" : { - "A_vect" : [0, 1, 1], - "amplitude" : 4, - "start_point" : FRAME_X_RADIUS*LEFT + 2*DOWN, - } - } - def construct(self): - self.grow_arrows() - self.move_into_position() - self.fade_M_vects() - self.label_A() - self.add_components() - self.scale_up_and_down() - - def grow_arrows(self): - for ov in self.em_wave.continual_animations: - ov.vector.rectangular_stem_width = 0.1 - ov.vector.tip_length = 0.5 - - def label_A(self): - brace = Brace(Line(ORIGIN, 4*RIGHT)) - brace.rotate(np.pi/4, OUT) - brace.A = brace.get_tex("A", buff = MED_SMALL_BUFF) - brace.A.scale_in_place(2) - brace.A.set_color(GREEN) - brace_group = VGroup(brace, brace.A) - self.position_brace_group(brace_group) - self.play(Write(brace_group, run_time = 1)) - self.wait(12) - - self.brace = brace - - def add_components(self): - h_wave = self.em_wave.copy() - h_wave.A_vect = [0, 1, 0] - v_wave = self.em_wave.copy() - v_wave.A_vect = [0, 0, 1] - length = 4/np.sqrt(2) - for wave in h_wave, v_wave: - for ov in wave.continual_animations: - ov.A_vect = length*np.array(wave.A_vect) - - h_brace = Brace(Line(ORIGIN, length*RIGHT)) - v_brace = Brace(Line(ORIGIN, length*UP), LEFT) - for brace, c in (h_brace, "x"), (v_brace, "y"): - brace.A = brace.get_tex("A_%s"%c, buff = MED_LARGE_BUFF) - brace.A.scale_in_place(2) - brace.A.set_color(GREEN) - brace_group = VGroup(h_brace, h_brace.A, v_brace, v_brace.A) - self.position_brace_group(brace_group) - - rhs = TexMobject("= \\sqrt{A_x^2 + A_y^2}") - rhs.scale(2) - for i in 3, 5, 7, 9: - rhs[i].set_color(GREEN) - rhs.rotate(np.pi/2, RIGHT) - rhs.rotate(np.pi/2, OUT) - - period = 1./self.em_wave.frequency - self.add(h_wave, v_wave) - self.play( - FadeIn(h_wave.mobject), - FadeIn(v_wave.mobject), - self.brace.A.move_to, self.brace, - self.brace.A.shift, SMALL_BUFF*(2*UP+IN), - ReplacementTransform(self.brace, h_brace), - Write(h_brace.A) - ) - self.wait(6) - - self.play( - ReplacementTransform(h_brace.copy(), v_brace), - Write(v_brace.A) - ) - self.wait(6) - rhs.next_to(self.brace.A, UP, SMALL_BUFF) - self.play(Write(rhs)) - self.wait(2*period) - - self.h_brace = h_brace - self.v_brace = v_brace - self.h_wave = h_wave - self.v_wave = v_wave - - def scale_up_and_down(self): - for scale_factor in 1.25, 0.4, 1.5, 0.3, 2: - self.scale_wave(scale_factor) - self.wait() - self.wait(4) - - ###### - - def position_brace_group(self, brace_group): - brace_group.rotate(np.pi/2, RIGHT) - brace_group.rotate(np.pi/2, OUT) - brace_group.shift(2*DOWN) - - def scale_wave(self, factor): - def generate_vect_update(ov): - prev_A = np.array(ov.A_vect) - new_A = factor*prev_A - def update(vect, alpha): - ov.A_vect = interpolate( - prev_A, new_A, alpha - ) - return vect - return update - h_brace = self.h_brace - v_brace = self.v_brace - - h_brace.generate_target() - h_brace.target.stretch_about_point( - factor, 1, h_brace.get_bottom() - ) - v_brace.generate_target() - v_brace.target.stretch_about_point( - factor, 2, v_brace.get_nadir() - ) - self.play( - MoveToTarget(h_brace), - MoveToTarget(v_brace), - *[ - UpdateFromAlphaFunc(ov.vector, generate_vect_update(ov)) - for ov in it.chain( - self.em_wave.continual_animations, - self.h_wave.continual_animations, - self.v_wave.continual_animations, - ) - ] - ) - -class EnergyOfWavesTeacherPortion(TeacherStudentsScene): - def construct(self): - self.show_energy_equation() - self.show_both_ways_of_thinking_about_it() - - def show_energy_equation(self): - dot = Dot(self.teacher.get_top() + 2*(UP+LEFT)) - dot.fade(1) - self.dot = dot - - energy = TexMobject( - "\\frac{\\text{Energy}}{\\text{Volume}}", - "=", - "\\epsilon_0", "A", "^2" - ) - energy.set_color_by_tex("A", GREEN) - energy.to_corner(UP+LEFT) - - component_energy = TexMobject( - "=", "\\epsilon_0", "A_x", "^2", - "+", "\\epsilon_0", "A_y", "^2", - ) - for i in 2, 6: - component_energy[i][0].set_color(GREEN) - component_energy[i+1].set_color(GREEN) - component_energy.next_to(energy[1], DOWN, MED_LARGE_BUFF, LEFT) - - self.play( - Animation(dot), - self.teacher.change, "raise_right_hand", dot, - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = dot - ) - self.wait(2) - self.play(Write(energy)) - self.play(self.teacher.change, "happy") - self.wait(3) - self.play( - ReplacementTransform( - VGroup(*energy[-4:]).copy(), - VGroup(*component_energy[:4]) - ), - ReplacementTransform( - VGroup(*energy[-4:]).copy(), - VGroup(*component_energy[4:]) - ) - ) - self.change_student_modes(*["happy"]*3, look_at_arg = energy) - self.wait() - - def show_both_ways_of_thinking_about_it(self): - s1, s2 = self.get_students()[:2] - b1, b2 = [ - ThoughtBubble(direction = v).scale(0.5) - for v in (LEFT, RIGHT) - ] - b1.pin_to(s1) - b2.pin_to(s2) - - b1.write("Add \\\\ components") - b2.write("Pythagorean \\\\ theorem") - - for b, s in (b1, s1), (b2, s2): - self.play( - ShowCreation(b), - Write(b.content, run_time = 2), - s.change, "thinking" - ) - self.wait(2) - self.change_student_modes( - *["plain"]*3, - look_at_arg = self.dot, - added_anims = [ - self.teacher.change, "raise_right_hand", self.dot - ] - - ) - self.play(self.teacher.look_at, self.dot) - self.wait(5) - -class DescribePhoton(ThreeDScene): - CONFIG = { - "x_color" : RED, - "y_color" : GREEN, - } - def setup(self): - self.axes = ThreeDAxes() - self.add(self.axes) - - self.set_camera_orientation(phi = 0.8*np.pi/2, theta = -np.pi/4) - em_wave = EMWave( - start_point = FRAME_X_RADIUS*LEFT, - A_vect = [0, 1, 1], - wave_number = 0, - amplitude = 3, - ) - for ov in em_wave.continual_animations: - ov.vector.normal_vector = RIGHT - ov.vector.set_fill(opacity = 0.7) - for M_vect in em_wave.M_vects: - M_vect.set_fill(opacity = 0) - em_wave.update(0) - photon = WavePacket( - em_wave = em_wave, - run_time = 2, - ) - - self.photon = photon - self.em_wave = em_wave - - def construct(self): - self.add_ket_equation() - self.shoot_a_few_photons() - self.freeze_photon() - self.reposition_to_face_photon_head_on() - self.show_components() - self.show_amplitude_and_phase() - self.change_basis() - self.write_different_meaning() - self.write_components() - self.describe_via_energy() - self.components_not_possible_in_isolation() - self.ask_what_they_mean() - self.change_camera() - - def add_ket_equation(self): - equation = TexMobject( - "|\\!\\psi\\rangle", - "=", - "\\alpha", "|\\!\\rightarrow \\rangle", "+", - "\\beta", "|\\!\\uparrow \\rangle", - ) - equation.to_edge(UP) - equation.set_color_by_tex("psi", E_COLOR) - equation.set_color_by_tex("alpha", self.x_color) - equation.set_color_by_tex("beta", self.y_color) - rect = SurroundingRectangle(equation.get_part_by_tex("psi")) - rect.set_color(E_COLOR) - words = TextMobject("Polarization\\\\", "state") - words.next_to(rect, DOWN) - for part in words: - bg_rect = BackgroundRectangle(part) - bg_rect.stretch_in_place(2, 1) - part.add_to_back(bg_rect) - equation.rect = rect - equation.words = words - equation.add_background_rectangle() - equation.add(rect, words) - VGroup(rect, words).fade(1) - - equation.rotate(np.pi/2, RIGHT) - equation.rotate(np.pi/2 + self.camera.get_theta(), OUT) - - self.add(equation) - self.equation = equation - self.superposition = VGroup(*equation[1][2:]) - - def shoot_a_few_photons(self): - for x in range(2): - self.play(self.photon) - - def freeze_photon(self): - self.play( - self.photon, - rate_func = lambda x : 0.55*x, - run_time = 1 - ) - self.add(self.photon.mobject) - self.photon.rate_func = lambda x : x - self.photon.run_time = 2 - - def reposition_to_face_photon_head_on(self): - plane = NumberPlane( - color = LIGHT_GREY, - secondary_color = DARK_GREY, - x_unit_size = 2, - y_unit_size = 2, - y_radius = FRAME_X_RADIUS, - ) - plane.add_coordinates(x_vals = list(range(-3, 4)), y_vals = []) - plane.rotate(np.pi/2, RIGHT) - plane.rotate(np.pi/2, OUT) - - self.play(self.em_wave.M_vects.set_fill, None, 0) - self.move_camera( - phi = np.pi/2, theta = 0, - added_anims = [ - Rotate(self.equation, -self.camera.get_theta()) - ] - ) - self.play( - Write(plane, run_time = 1), - Animation(self.equation) - ) - - self.xy_plane = plane - - def show_components(self): - h_arrow, v_arrow = [ - Vector( - 1.38*direction, - color = color, - normal_vector = RIGHT, - ) - for color, direction in [(self.x_color, UP), (self.y_color, OUT)] - ] - v_arrow.move_to(h_arrow.get_end(), IN) - h_part = VGroup(*self.equation[1][2:4]).copy() - v_part = VGroup(*self.equation[1][5:7]).copy() - - self.play( - self.equation.rect.set_stroke, BLUE, 4, - self.equation.words.set_fill, WHITE, 1, - ) - for part, arrow, d in (h_part, h_arrow, IN), (v_part, v_arrow, UP): - self.play( - part.next_to, arrow.get_center(), d, - ShowCreation(arrow) - ) - part.rotate(np.pi/2, DOWN) - bg_rect = BackgroundRectangle(part) - bg_rect.stretch_in_place(1.3, 0) - part.add_to_back(bg_rect) - part.rotate(np.pi/2, UP) - self.add(part) - self.wait() - - self.h_part_tex = h_part - self.h_arrow = h_arrow - self.v_part_tex = v_part - self.v_arrow = v_arrow - - def show_amplitude_and_phase(self): - alpha = self.h_part_tex[1] - new_alpha = alpha.copy().shift(IN) - rhs = TexMobject( - "=", "A_x", "e", - "^{i", "(2\\pi", "f", "t", "+", "\\phi_x)}" - ) - A_rect = SurroundingRectangle(rhs.get_part_by_tex("A_x"), buff = 0.5*SMALL_BUFF) - A_word = TextMobject("Amplitude") - A_word.add_background_rectangle() - A_word.next_to(A_rect, DOWN, aligned_edge = LEFT) - A_group = VGroup(A_rect, A_word) - A_group.set_color(YELLOW) - phase_rect = SurroundingRectangle(VGroup(*rhs[4:]), buff = 0.5*SMALL_BUFF) - phase_word = TextMobject("Phase") - phase_word.add_background_rectangle() - phase_word.next_to(phase_rect, UP) - phase_group = VGroup(phase_word, phase_rect) - phase_group.set_color(MAROON_B) - rhs.add_background_rectangle() - - group = VGroup(rhs, A_group, phase_group) - group.rotate(np.pi/2, RIGHT) - group.rotate(np.pi/2, OUT) - group.next_to(new_alpha, UP, SMALL_BUFF) - - self.play( - ReplacementTransform(alpha.copy(), new_alpha), - FadeIn(rhs) - ) - for word, rect in A_group, phase_group: - self.play( - ShowCreation(rect), - Write(word, run_time = 1) - ) - self.wait() - self.play(*list(map(FadeOut, [new_alpha, group]))) - - def change_basis(self): - superposition = self.superposition - plane = self.xy_plane - h_arrow = self.h_arrow - v_arrow = self.v_arrow - h_part = self.h_part_tex - v_part = self.v_part_tex - axes = self.axes - movers = [ - plane, axes, - h_arrow, v_arrow, - h_part, v_part, - self.equation, - superposition, - ] - for mob in movers: - mob.save_state() - - superposition.target = TexMobject( - "\\gamma", "|\\! \\nearrow \\rangle", "+", - "\\delta", "|\\! \\nwarrow \\rangle", - ) - superposition.target.set_color_by_tex("gamma", TEAL_D) - superposition.target.set_color_by_tex("delta", MAROON) - for part in superposition.target.get_parts_by_tex("rangle"): - part[1].rotate_in_place(-np.pi/12) - superposition.target.rotate(np.pi/2, RIGHT) - superposition.target.rotate(np.pi/2, OUT) - superposition.target.move_to(superposition) - - for mob in plane, axes: - mob.generate_target() - mob.target.rotate(np.pi/6, RIGHT) - - A = 1.9 - h_arrow.target = Vector( - A*np.cos(np.pi/12)*rotate_vector(UP, np.pi/6, RIGHT), - normal_vector = RIGHT, - color = TEAL - ) - v_arrow.target = Vector( - A*np.sin(np.pi/12)*rotate_vector(OUT, np.pi/6, RIGHT), - normal_vector = RIGHT, - color = MAROON - ) - v_arrow.target.shift(h_arrow.target.get_vector()) - - h_part.target = VGroup(*superposition.target[:2]).copy() - v_part.target = VGroup(*superposition.target[3:]).copy() - h_part.target.next_to( - h_arrow.target.get_center(), IN+UP, SMALL_BUFF - ) - v_part.target.next_to( - v_arrow.target.get_center(), UP, SMALL_BUFF - ) - for part in h_part.target, v_part.target: - part.rotate(np.pi/2, DOWN) - part.add_to_back(BackgroundRectangle(part)) - part.rotate(np.pi/2, UP) - - self.equation.generate_target() - - self.play(*list(map(MoveToTarget, movers))) - self.wait(2) - self.play(*[mob.restore for mob in movers]) - self.wait() - - def write_different_meaning(self): - superposition = self.superposition - superposition.rotate(np.pi/2, DOWN) - rect = SurroundingRectangle(superposition) - VGroup(superposition, rect).rotate(np.pi/2, UP) - morty = Mortimer(mode = "confused") - blinked = morty.copy().blink() - words = TextMobject("Means something \\\\ different...") - for mob in morty, blinked, words: - mob.rotate(np.pi/2, RIGHT) - mob.rotate(np.pi/2, OUT) - words.next_to(rect, UP) - VGroup(morty, blinked).next_to(words, IN) - - self.play( - ShowCreation(rect), - Write(words, run_time = 2) - ) - self.play(FadeIn(morty)) - self.play(Transform( - morty, blinked, - rate_func = squish_rate_func(there_and_back) - )) - self.wait() - self.play(*list(map(FadeOut, [ - morty, words, rect, - self.equation.rect, - self.equation.words, - ]))) - - def write_components(self): - d_brace = Brace(Line(ORIGIN, 2*RIGHT), UP, buff = SMALL_BUFF) - h_brace = Brace(Line(ORIGIN, (2/np.sqrt(2))*RIGHT), DOWN, buff = SMALL_BUFF) - v_brace = Brace(Line(ORIGIN, (2/np.sqrt(2))*UP), RIGHT, buff = SMALL_BUFF) - d_brace.rotate(np.pi/4) - v_brace.shift((2/np.sqrt(2))*RIGHT) - braces = VGroup(d_brace, h_brace, v_brace) - group = VGroup(braces) - - tex = ["1"] + 2*["\\sqrt{1/2}"] - colors = BLUE, self.x_color, self.y_color - for brace, tex, color in zip(braces, tex, colors): - brace.label = brace.get_tex(tex, buff = SMALL_BUFF) - brace.label.add_background_rectangle() - brace.label.set_color(color) - group.add(brace.label) - - group.rotate(np.pi/2, RIGHT) - group.rotate(np.pi/2, OUT) - - self.play( - GrowFromCenter(d_brace), - Write(d_brace.label) - ) - self.wait() - self.play( - FadeOut(self.h_part_tex), - FadeOut(self.v_part_tex), - GrowFromCenter(h_brace), - GrowFromCenter(v_brace), - ) - self.play( - Write(h_brace.label), - Write(v_brace.label), - ) - self.wait() - - self.d_brace = d_brace - self.h_brace = h_brace - self.v_brace = v_brace - - def describe_via_energy(self): - energy = TexMobject( - "&\\text{Energy}", - "=", "(hf)", "(", "1", ")^2\\\\", - "&=", "(hf)", "\\left(", "\\sqrt{1/2}", "\\right)^2", - "+", "(hf)", "\\left(", "\\sqrt{1/2}", "\\right)^2", - ) - energy.scale(0.8) - one = energy.get_part_by_tex("1", substring = False) - one.set_color(BLUE) - halves = energy.get_parts_by_tex("1/2") - halves[0].set_color(self.x_color) - halves[1].set_color(self.y_color) - indices = [0, 3, 6, len(energy)] - parts = VGroup(*[ - VGroup(*energy[i1:i2]) - for i1, i2 in zip(indices, indices[1:]) - ]) - for part in parts: - bg_rect = BackgroundRectangle(part) - bg_rect.stretch_in_place(1.5, 1) - part.add_to_back(bg_rect) - - parts.to_corner(UP+LEFT, buff = MED_SMALL_BUFF) - parts.shift(DOWN) - parts.rotate(np.pi/2, RIGHT) - parts.rotate(np.pi/2, OUT) - - self.play(Write(parts[0]), run_time = 2) - self.play(Indicate(energy.get_part_by_tex("hf"))) - self.play( - Transform( - self.d_brace.label.copy(), - one.copy(), - remover = True - ), - Write(parts[1], run_time = 1), - ) - self.wait() - self.play( - Transform( - self.h_brace.label[1].copy(), - halves[0].copy(), - remover = True, - rate_func = squish_rate_func(smooth, 0, 0.75) - ), - Transform( - self.v_brace.label[1].copy(), - halves[1].copy(), - remover = True, - rate_func = squish_rate_func(smooth, 0.25, 1) - ), - Write(parts[2]), - run_time = 2 - ) - self.wait() - - self.energy_equation_parts = parts - - def components_not_possible_in_isolation(self): - half_hf = VGroup(*self.energy_equation_parts[2][1:6]) - half_hf.rotate(np.pi/2, DOWN) - rect = SurroundingRectangle(half_hf) - VGroup(half_hf, rect).rotate(np.pi/2, UP) - - randy = Randolph() - randy.scale(0.7) - randy.look(UP) - randy.rotate(np.pi/2, RIGHT) - randy.rotate(np.pi/2, OUT) - randy.next_to(rect, IN) - - self.play( - ShowCreation(rect), - FadeIn(randy) - ) - self.play( - randy.rotate, np.pi/2, IN, - randy.rotate, np.pi/2, LEFT, - randy.change, "maybe", - randy.rotate, np.pi/2, RIGHT, - randy.rotate, np.pi/2, OUT, - ) - self.wait() - - def ask_what_they_mean(self): - morty = Mortimer(mode = "confused") - morty.scale(0.7) - morty.to_edge(LEFT) - - bubble = morty.get_bubble() - bubble.write("?!?") - bubble.resize_to_content() - bubble.add(bubble.content) - bubble.pin_to(morty) - - group = VGroup(morty, bubble) - group.to_corner(DOWN+RIGHT) - group.rotate(np.pi/2, RIGHT) - group.rotate(np.pi/2, OUT) - - component = VGroup(self.h_arrow, self.h_brace, self.h_brace.label) - - self.play( - FadeIn(morty), - component.next_to, morty, DOWN, OUT, - component.shift, MED_LARGE_BUFF*(DOWN + OUT), - ) - component.rotate(np.pi/2, DOWN) - cross = Cross(component) - VGroup(component, cross).rotate(np.pi/2, UP) - cross.set_color("#ff0000") - self.play(ShowCreation(cross)) - bubble.remove(bubble.content) - self.play( - ShowCreation(bubble), - Write(bubble.content), - morty.look_at, component, - ) - self.wait() - - def change_camera(self): - everything = VGroup(*self.get_top_level_mobjects()) - everything.remove(self.photon.mobject) - everything.remove(self.axes) - - self.play(*list(map(FadeOut, everything))) - self.move_camera( - phi = 0.8*np.pi/2, - theta = -0.3*np.pi, - run_time = 2 - ) - self.play( - self.photon, - rate_func = lambda x : min(x + 0.55, 1), - run_time = 2, - ) - self.photon.rate_func = lambda x : x - self.play(self.photon) - self.wait() - -class SeeCommentInDescription(Scene): - def construct(self): - words = TextMobject(""" - \\begin{flushleft} - $^*$See comment in the \\\\ - description on single-headed \\\\ - vs. double-headed arrows - \\end{flushleft} - """) - words.set_width(FRAME_WIDTH - 1) - words.to_corner(DOWN+LEFT) - self.add(words) - -class SeeCommentInDescriptionAgain(Scene): - def construct(self): - words = TextMobject("$^*$Again, see description") - words.set_width(FRAME_WIDTH - 1) - words.to_corner(DOWN+LEFT) - self.add(words) - -class GetExperimental(TeacherStudentsScene): - def construct(self): - self.teacher_says("Get experimental!", target_mode = "hooray") - self.change_student_modes(*["hooray"]*3) - self.wait(3) - -class ShootPhotonThroughFilter(DirectionOfPolarizationScene): - CONFIG = { - "EMWave_config" : { - "wave_number" : 0, - "A_vect" : [0, 1, 1], - "start_point" : FRAME_X_RADIUS*LEFT, - "amplitude" : np.sqrt(2), - }, - "pol_filter_configs" : [{ - "label_tex" : "\\text{Filter}", - "include_arrow_label" : False, - }], - "apply_filter" : True, - "quantum" : True, - "pre_filter_alpha" : 0.35, - "ambient_rotation_rate" : 0, - } - def setup(self): - DirectionOfPolarizationScene.setup(self) - self.em_wave.update(0) - self.remove(self.em_wave) - - def construct(self): - self.force_skipping() - - self.add_superposition_tex() - self.ask_what_would_happen() - self.expect_half_energy_to_be_absorbed() - self.probabalistic_passing_and_blocking() - # self.note_change_in_polarization() - - def add_superposition_tex(self): - superposition_tex = TexMobject( - "|\\!\\nearrow\\rangle", - "=", - "(\\sqrt{1/2})", "|\\!\\rightarrow \\rangle", "+", - "(\\sqrt{1/2})", "|\\!\\uparrow \\rangle", - ) - superposition_tex.scale(0.9) - superposition_tex[0].set_color(E_COLOR) - halves = superposition_tex.get_parts_by_tex("1/2") - for half, color in zip(halves, [RED, GREEN]): - half.set_color(color) - - h_rect = SurroundingRectangle(VGroup(*superposition_tex[2:4])) - v_rect = SurroundingRectangle(VGroup(*superposition_tex[5:7])) - VGroup(h_rect, v_rect).fade(1) - superposition_tex.h_rect = h_rect - superposition_tex.v_rect = v_rect - superposition_tex.add(h_rect, v_rect) - - superposition_tex.next_to(ORIGIN, LEFT) - superposition_tex.to_edge(UP) - superposition_tex.rotate(np.pi/2, RIGHT) - self.superposition_tex = superposition_tex - - def ask_what_would_happen(self): - photon = self.get_photon( - rate_func = lambda t : self.pre_filter_alpha*t, - remover = False, - run_time = 0.6, - ) - question = TextMobject("What's going to happen?") - question.add_background_rectangle() - question.set_color(YELLOW) - question.rotate(np.pi/2, RIGHT) - question.next_to(self.superposition_tex, IN) - - self.pol_filter.add( - self.pol_filter.arrow.copy().rotate(np.pi/2, OUT) - ) - self.pol_filter.save_state() - self.pol_filter.shift(5*OUT) - - self.set_camera_orientation(theta = -0.9*np.pi) - self.play(self.pol_filter.restore) - self.move_camera( - theta = -0.6*np.pi, - ) - self.play( - photon, - FadeIn(self.superposition_tex) - ) - self.play(Write(question, run_time = 1)) - self.wait() - self.play(FadeOut(self.pol_filter.label)) - self.pol_filter.remove(self.pol_filter.label) - self.add(self.pol_filter) - - self.question = question - self.frozen_photon = photon - - def expect_half_energy_to_be_absorbed(self): - words = TextMobject("Absorbs horizontal \\\\ energy") - words.set_color(RED) - words.next_to(ORIGIN, UP+RIGHT, MED_LARGE_BUFF) - words.rotate(np.pi/2, RIGHT) - words.rotate(np.pi/2, OUT) - - lines = VGroup(*[ - Line( - np.sin(a)*RIGHT + np.cos(a)*UP, - np.sin(a)*LEFT + np.cos(a)*UP, - color = RED, - stroke_width = 2, - ) - for a in np.linspace(0, np.pi, 15) - ]) - lines.rotate(np.pi/2, RIGHT) - lines.rotate(np.pi/2, OUT) - - self.move_camera( - phi = np.pi/2, theta = 0, - added_anims = [ - Rotate(self.superposition_tex, np.pi/2), - ] + [ - ApplyMethod( - v.rotate_in_place, - -np.pi/2, - method_kwargs = {"axis" : v.get_vector()} - ) - for v in self.frozen_photon.mobject - ] - ) - self.play( - Write(words, run_time = 2), - self.superposition_tex.h_rect.set_stroke, RED, 3, - *list(map(GrowFromCenter, lines))+\ - [ - Animation(self.pol_filter), - Animation(self.frozen_photon.mobject) - ] - ) - self.wait(2) - self.move_camera( - phi = 0.8*np.pi/2, theta = -0.7*np.pi, - added_anims = [ - FadeOut(words), - Animation(lines), - Rotate(self.superposition_tex, -np.pi/2), - ] + [ - ApplyMethod( - v.rotate_in_place, - np.pi/2, - method_kwargs = {"axis" : v.get_vector()} - ) - for v in self.frozen_photon.mobject - ] - ) - self.play( - FadeOut(lines), - FadeOut(self.question), - self.superposition_tex.h_rect.fade, 1, - Animation(self.pol_filter) - ) - self.wait() - - self.absorption_words = words - - def probabalistic_passing_and_blocking(self): - absorption = self.get_filter_absorption_animation( - self.pol_filter, self.get_blocked_photon() - ) - prob = TexMobject("P(", "\\text{pass}", ")", "=", "1/2") - prob.set_color_by_tex("pass", GREEN) - prob.rotate(np.pi/2, RIGHT) - prob.next_to(self.superposition_tex, IN, MED_SMALL_BUFF, RIGHT) - - self.remove(self.frozen_photon.mobject) - self.play( - self.get_photon(), - rate_func = lambda t : min(t+self.pre_filter_alpha, 1), - ) - self.play( - FadeIn(prob), - self.get_blocked_photon(), - absorption - ) - bools = 6*[True] + 6*[False] - self.revert_to_original_skipping_status() - random.shuffle(bools) - for should_pass in bools: - if should_pass: - self.play(self.get_photon(), run_time = 1) - else: - self.play( - self.get_blocked_photon(), - Animation(self.axes), - absorption, - run_time = 1 - ) - self.play(FadeOut(prob)) - - def note_change_in_polarization(self): - words = TextMobject( - "``Collapses'' \\\\ from", "$|\\!\\nearrow\\rangle$", - "to", "$|\\!\\uparrow\\rangle$" - ) - words.set_color_by_tex("nearrow", E_COLOR) - words.set_color_by_tex("uparrow", GREEN) - words.next_to(ORIGIN, RIGHT, MED_LARGE_BUFF) - words.shift(2*UP) - words.rotate(np.pi/2, RIGHT) - photon = self.get_photon(run_time = 4) - for vect in photon.mobject: - if vect.get_center()[0] > 0: - vect.saved_state.set_fill(GREEN) - - self.play(FadeIn(words), photon) - for x in range(3): - self.play(photon) - - ###### - - def get_photon(self, **kwargs): - kwargs["run_time"] = kwargs.get("run_time", 1) - kwargs["include_M_vects"] = False - return WavePacket(em_wave = self.em_wave.copy(), **kwargs) - - def get_blocked_photon(self, **kwargs): - kwargs["get_filtered"] = True - return self.get_photon(self, **kwargs) - -class PhotonPassesCompletelyOrNotAtAllStub(ExternallyAnimatedScene): - pass - -class YouCanSeeTheCollapse(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "You can literally \\\\ \\emph{see} the collapse", - target_mode = "hooray" - ) - self.change_student_modes("confused", "hooray", "erm") - self.wait(3) - -class ThreeFilters(ShootPhotonThroughFilter): - CONFIG = { - "filter_x_coordinates" : [-4, 0, 4], - "pol_filter_configs" : [ - {"filter_angle" : 0}, - {"filter_angle" : np.pi/4}, - {"filter_angle" : np.pi/2}, - ], - "EMWave_config" : { - "A_vect" : [0, 0, 1], - "amplitude" : 1.5, - "n_vectors" : 60, - }, - "line_start_length" : 8, - "line_end_length" : 8, - "n_lines" : 20, - "lines_depth" : 1.8, - "lines_shift_vect" : SMALL_BUFF*OUT, - "random_seed" : 6, - } - def construct(self): - self.remove(self.axes) - self.setup_filters() - self.setup_lines() - self.setup_arrows() - - self.fifty_percent_pass_second() - self.show_changed_to_diagonal() - self.fifty_percent_to_pass_third() - self.show_lines_with_middle() - self.remove_middle_then_put_back() - - def setup_filters(self): - for pf in self.pol_filters: - pf.arrow_label.rotate(np.pi/2, OUT) - pf.arrow_label.next_to(pf.arrow, RIGHT) - pf.arrow_label.rotate(np.pi/2, LEFT) - pf.arrow_label.add_background_rectangle() - pf.arrow_label.rotate(np.pi/2, RIGHT) - self.add_foreground_mobject(pf.arrow_label) - - def setup_lines(self): - lines_group = VGroup(*[ - self.get_lines(pf1, pf2, ratio) - for pf1, pf2, ratio in zip( - [None] + list(self.pol_filters), - list(self.pol_filters) + [None], - [1, 1, 0.5, 0.25] - ) - ]) - lines = lines_group[0] - spacing = lines[1].get_start() - lines[0].get_start() - lines.add(lines.copy().shift(spacing/2)) - self.lines_group = lines_group - - self.A_to_C_lines = self.get_lines( - self.pol_filters[0], self.pol_filters[2], - ) - - def setup_arrows(self): - for E_vect in self.em_wave.E_vects: - E_vect.normal_vector = IN+DOWN - self.em_wave.update(0) - - def fifty_percent_pass_second(self): - arrow = Arrow( - ORIGIN, 3*RIGHT, - use_rectangular_stem = False, - path_arc = -0.8*np.pi - ) - label = TexMobject("50\\%") - label.next_to(arrow, UP) - group = VGroup(arrow, label) - group.rotate(np.pi/2, RIGHT) - group.next_to(self.pol_filters[1], OUT, buff = 0) - group.set_color(BLUE) - - l1, l2, l3 = self.lines_group[:3] - pf1, pf2, pf3 = self.pol_filters - kwargs = { - "lag_ratio" : 0, - "rate_func" : None, - } - - self.play(ShowCreation(l1, run_time = 1, **kwargs)) - self.play( - ShowCreation(l2, **kwargs), - Animation(VGroup(pf1, l1)), - ShowCreation(arrow), - run_time = 0.5, - ) - self.play( - ShowCreation(l3, **kwargs), - Animation(VGroup(pf2, l2, pf1, l1)), - FadeIn(label), - run_time = 0.5, - ) - self.wait(2) - self.play( - FadeOut(l3), - Animation(pf2), - FadeOut(l2), - Animation(pf1), - FadeOut(l1) - ) - - self.fifty_percent_arrow_group = group - - def show_changed_to_diagonal(self): - photon = self.get_photon( - run_time = 2, - rate_func = lambda x : 0.6*x, - remover = False, - ) - brace = Brace(Line(1.5*LEFT, 1.5*RIGHT), DOWN) - label = brace.get_text( - "Changed to", - "$|\\!\\nearrow\\rangle$" - ) - label.set_color_by_tex("rangle", BLUE) - group = VGroup(brace, label) - group.rotate(np.pi/2, RIGHT) - group.shift(2*RIGHT + 0.5*IN) - - self.play(photon) - self.play( - GrowFromCenter(brace), - Write(label, run_time = 1) - ) - kwargs = { - "run_time" : 3, - "rate_func" : there_and_back_with_pause, - } - self.move_camera( - phi = np.pi/2, - theta = 0, - added_anims = [ - Animation(VGroup(*self.pol_filters[:2])) - ] + [ - Rotate( - v, np.pi/2, - axis = v.get_vector(), - in_place = True, - **kwargs - ) - for v in photon.mobject - ] + [ - Animation(self.pol_filters[2]), - Rotate( - label, np.pi/2, - axis = OUT, - in_place = True, - **kwargs - ), - ], - **kwargs - ) - self.wait() - - self.photon = photon - self.brace_group = VGroup(brace, label) - - def fifty_percent_to_pass_third(self): - arrow_group = self.fifty_percent_arrow_group.copy() - arrow_group.shift(4*RIGHT) - arrow, label = arrow_group - - a = self.photon.rate_func(1) - new_photon = self.get_photon( - rate_func = lambda x : (1-a)*x + a, - run_time = 1 - ) - - self.revert_to_original_skipping_status() - self.play( - ShowCreation(arrow), - Write(label, run_time = 1) - ) - self.remove(self.photon.mobject) - self.play(new_photon) - - self.second_fifty_percent_arrow_group = arrow_group - - def show_lines_with_middle(self): - l1, l2, l3, l4 = self.lines_group - pf1, pf2, pf3 = self.pol_filters - - self.play( - FadeIn(l4), - Animation(pf3), - FadeIn(l3), - Animation(pf2), - FadeIn(l2), - Animation(pf1), - FadeIn(l1), - FadeOut(self.brace_group) - ) - self.wait(2) - - def remove_middle_then_put_back(self): - l1, l2, l3, l4 = self.lines_group - pf1, pf2, pf3 = self.pol_filters - mid_lines = self.A_to_C_lines - mover = VGroup( - pf2, - self.fifty_percent_arrow_group, - self.second_fifty_percent_arrow_group, - ) - - arrow = Arrow( - ORIGIN, 7*RIGHT, - path_arc = 0.5*np.pi, - ) - labels = VGroup(*list(map(TexMobject, ["0\\%", "25\\%"]))) - labels.scale(1.5) - labels.next_to(arrow, DOWN) - group = VGroup(arrow, labels) - group.rotate(np.pi/2, RIGHT) - group.shift(2*LEFT + IN) - group.set_color(GREEN) - - self.remove(l2, l3) - self.play( - FadeOut(l4), - Animation(pf3), - FadeOut(l3), - ApplyMethod( - mover.shift, 3*OUT, - rate_func = running_start - ), - ReplacementTransform(l2.copy(), mid_lines), - Animation(pf1), - Animation(l1) - ) - self.play( - ShowCreation(arrow), - Write(labels[0], run_time = 1) - ) - self.wait(2) - self.play( - FadeIn(l4), - Animation(pf3), - FadeOut(mid_lines), - FadeIn(l3), - mover.shift, 3*IN, - FadeIn(l2), - Animation(pf1), - Animation(l1) - ) - self.play(ReplacementTransform(*labels)) - self.wait(3) - - - #### - - def get_photon(self, **kwargs): - return ShootPhotonThroughFilter.get_photon(self, width = 4, **kwargs) - - def get_lines(self, filter1 = None, filter2 = None, ratio = 1.0): - n = self.n_lines - start, end = [ - (f.point_from_proportion(0.75) if f is not None else None) - for f in (filter1, filter2) - ] - if start is None: - start = end + self.line_start_length*LEFT - if end is None: - end = start + self.line_end_length*RIGHT - nudge = (float(self.lines_depth)/self.n_lines)*OUT - lines = VGroup(*[ - Line(start, end).shift(z*nudge) - for z in range(n) - ]) - lines.set_stroke(YELLOW, 2) - lines.move_to(start, IN+LEFT) - lines.shift(self.lines_shift_vect) - n_to_block = int((1-ratio)*self.n_lines) - random.seed(self.random_seed) - indices_to_block = random.sample( - list(range(self.n_lines)), n_to_block - ) - VGroup(*[lines[i] for i in indices_to_block]).set_stroke(width = 0) - return lines - -class PhotonAtSlightAngle(ThreeFilters): - CONFIG = { - "filter_x_coordinates" : [3], - "pol_filter_configs" : [{ - "label_tex" : "", - "include_arrow_label" : False, - "radius" : 1.4, - }], - "EMWave_config" : { - "wave_number" : 0, - "A_vect" : [0, np.sin(np.pi/8), np.cos(np.pi/8)], - "start_point" : FRAME_X_RADIUS*LEFT, - "amplitude" : 2, - }, - "axes_config" : { - "z_max" : 2.5, - }, - "radius" : 1.3, - "lines_depth" : 2.5, - "line_start_length" : 12, - } - def construct(self): - self.force_skipping() - - self.shoot_photon() - self.reposition_camera_to_head_on() - self.write_angle() - self.write_components() - self.classical_energy_conception() - self.reposition_camera_back() - self.rewrite_15_percent_meaning() - self.probabalistic_passing() - - def shoot_photon(self): - photon = self.get_photon( - rate_func = lambda x : 0.5*x, - remover = False, - ) - self.play(photon) - self.photon = photon - - def reposition_camera_to_head_on(self): - self.move_camera( - phi = np.pi/2, theta = 0, - added_anims = list(it.chain(*[ - [ - v.rotate_in_place, np.pi/2, v.get_vector(), - v.set_fill, None, 0.7, - ] - for v in self.photon.mobject - ])) + [Animation(self.pol_filter)] - ) - - def write_angle(self): - arc = Arc( - start_angle = np.pi/2, angle = -np.pi/8, - radius = self.pol_filter.radius, - ) - label = TexMobject("22.5^\\circ") - label.next_to(arc.get_center(), UP+RIGHT, SMALL_BUFF) - - group = VGroup(arc, label) - group.rotate(np.pi/2, RIGHT) - group.rotate(np.pi/2, OUT) - - self.play( - FadeOut(self.pol_filter), - ShowCreation(arc), - Write(label, run_time = 1) - ) - self.wait() - - self.arc = arc - self.angle_label = label - - def write_components(self): - d_brace = Brace(Line(ORIGIN, self.radius*RIGHT), UP, buff = SMALL_BUFF) - d_brace.rotate(np.pi/2 - np.pi/8) - d_brace.label = d_brace.get_tex("1", buff = SMALL_BUFF) - d_brace.label.add_background_rectangle() - - h_arrow = Vector( - self.radius*np.sin(np.pi/8)*RIGHT, - color = RED, - ) - h_label = TexMobject("\\sin(22.5^\\circ)") - h_label.scale(0.7) - h_label.set_color(RED) - h_label.next_to(h_arrow.get_center(), DOWN, aligned_edge = LEFT) - - v_arrow = Vector( - self.radius*np.cos(np.pi/8)*UP, - color = GREEN - ) - v_arrow.shift(h_arrow.get_vector()) - v_label = TexMobject("\\cos(22.5^\\circ)") - v_label.scale(0.7) - v_label.set_color(GREEN) - v_label.next_to(v_arrow, RIGHT, SMALL_BUFF) - - state = TexMobject( - "|\\!\\psi\\rangle", - "=", "\\sin(22.5^\\circ)", "|\\!\\rightarrow\\rangle", - "+", "\\cos(22.5^\\circ)", "|\\!\\uparrow\\rangle", - ) - state.set_color_by_tex_to_color_map({ - "psi" : BLUE, - "rightarrow" : RED, - "uparrow" : GREEN, - }) - # state.add_background_rectangle() - state.to_edge(UP) - - sin_brace = Brace(state.get_part_by_tex("sin"), DOWN, buff = SMALL_BUFF) - sin_brace.label = sin_brace.get_tex("%.2f"%np.sin(np.pi/8), buff = SMALL_BUFF) - cos_brace = Brace(state.get_part_by_tex("cos"), DOWN, buff = SMALL_BUFF) - cos_brace.label = cos_brace.get_tex("%.2f"%np.cos(np.pi/8), buff = SMALL_BUFF) - - group = VGroup( - d_brace, d_brace.label, - h_arrow, h_label, - v_arrow, v_label, - state, - sin_brace, sin_brace.label, - cos_brace, cos_brace.label, - ) - group.rotate(np.pi/2, RIGHT) - group.rotate(np.pi/2, OUT) - - self.play( - GrowFromCenter(d_brace), - Write(d_brace.label) - ) - self.wait() - self.play( - GrowFromPoint(h_arrow, ORIGIN), - Write(h_label, run_time = 1) - ) - self.play( - Write(VGroup(*state[:2])), - ReplacementTransform( - h_label.copy(), - state.get_part_by_tex("sin") - ), - ReplacementTransform( - h_arrow.copy(), - state.get_part_by_tex("rightarrow") - ), - Write(state.get_part_by_tex("+")) - ) - self.play( - GrowFromCenter(sin_brace), - Write(sin_brace.label, run_time = 1) - ) - self.wait() - self.play( - GrowFromPoint(v_arrow, h_arrow.get_end()), - Write(v_label, run_time = 1) - ) - self.play( - ReplacementTransform( - v_label.copy(), - state.get_part_by_tex("cos") - ), - ReplacementTransform( - v_arrow.copy(), - state.get_part_by_tex("uparrow") - ), - ) - self.play( - GrowFromCenter(cos_brace), - Write(cos_brace.label, run_time = 1) - ) - self.wait() - - self.d_brace = d_brace - self.state_equation = state - self.state_equation.add( - sin_brace, sin_brace.label, - cos_brace, cos_brace.label, - ) - self.sin_brace = sin_brace - self.cos_brace = cos_brace - self.h_arrow = h_arrow - self.h_label = h_label - self.v_arrow = v_arrow - self.v_label = v_label - - def classical_energy_conception(self): - randy = Randolph(mode = "pondering").flip() - randy.scale(0.7) - randy.next_to(ORIGIN, LEFT) - randy.to_edge(DOWN) - - bubble = ThoughtBubble(direction = RIGHT) - h_content = TexMobject( - "0.38", "^2", "= 0.15", "\\text{ energy}\\\\", - "\\text{in the }", "\\rightarrow", "\\text{ direction}" - ) - alt_h_content = TexMobject( - "0.38", "^2", "=& 15\\%", "\\text{ of energy}\\\\", - "&\\text{absorbed}", "", "", - ) - h_content.set_color_by_tex("rightarrow", RED) - alt_h_content.set_color_by_tex("rightarrow", RED) - alt_h_content.scale(0.8) - v_content = TexMobject( - "0.92", "^2", "= 0.85", "\\text{ energy}\\\\", - "\\text{in the }", "\\uparrow", "\\text{ direction}" - ) - v_content.set_color_by_tex("uparrow", GREEN) - - bubble.add_content(h_content) - bubble.resize_to_content() - v_content.move_to(h_content) - bubble_group = VGroup(bubble, h_content, v_content) - bubble_group.scale(0.8) - bubble_group.next_to(randy, UP+LEFT, SMALL_BUFF) - - classically = TextMobject("Classically...") - classically.next_to(bubble[-1], UP) - classically.set_color(YELLOW) - alt_h_content.next_to(classically, DOWN) - - group = VGroup(randy, bubble_group, classically, alt_h_content) - group.rotate(np.pi/2, RIGHT) - group.rotate(np.pi/2, OUT) - - filter_lines = self.get_filter_lines(self.pol_filter) - - self.play( - FadeIn(randy), - FadeIn(classically), - ShowCreation(bubble), - ) - self.play( - ReplacementTransform( - self.sin_brace.label.copy(), - h_content[0] - ), - ReplacementTransform( - self.state_equation.get_part_by_tex("rightarrow").copy(), - h_content.get_part_by_tex("rightarrow") - ) - ) - self.play( - Write(VGroup(*h_content[1:5])), - Write(h_content.get_part_by_tex("direction")), - run_time = 2, - ) - self.wait(2) - self.play(h_content.shift, 2*IN) - self.play( - ReplacementTransform( - self.cos_brace.label.copy(), - v_content[0] - ), - ReplacementTransform( - self.state_equation.get_part_by_tex("uparrow").copy(), - v_content.get_part_by_tex("uparrow") - ) - ) - self.play( - Write(VGroup(*v_content[1:5])), - Write(v_content.get_part_by_tex("direction")), - run_time = 2, - ) - self.wait(2) - self.play( - FadeOut(randy), - FadeOut(bubble), - FadeOut(v_content), - Transform(h_content, alt_h_content), - FadeIn(self.pol_filter), - Animation(self.arc) - ) - self.play(ShowCreation(filter_lines, lag_ratio = 0)) - self.play(FadeOut(filter_lines)) - self.wait() - - self.classically = VGroup(classically, h_content) - - def reposition_camera_back(self): - self.move_camera( - phi = 0.8*np.pi/2, theta = -0.6*np.pi, - added_anims = [ - FadeOut(self.h_arrow), - FadeOut(self.h_label), - FadeOut(self.v_arrow), - FadeOut(self.v_label), - FadeOut(self.d_brace), - FadeOut(self.d_brace.label), - FadeOut(self.arc), - FadeOut(self.angle_label), - Rotate(self.state_equation, np.pi/2, IN), - Rotate(self.classically, np.pi/2, IN), - ] + [ - Rotate( - v, np.pi/2, - axis = v.get_vector(), - in_place = True, - ) - for v in self.photon.mobject - ], - run_time = 1.5 - ) - - def rewrite_15_percent_meaning(self): - self.classically.rotate(np.pi/2, LEFT) - cross = Cross(self.classically) - cross.set_color("#ff0000") - VGroup(self.classically, cross).rotate(np.pi/2, RIGHT) - - new_conception = TextMobject( - "$0.38^2 = 15\\%$ chance of \\\\ getting blocked" - ) - new_conception.scale(0.8) - new_conception.rotate(np.pi/2, RIGHT) - new_conception.move_to(self.classically, OUT) - - a = self.photon.rate_func(1) - finish_photon = self.get_blocked_photon( - rate_func = lambda t : a + (1-a)*t - ) - finish_photon.mobject.set_fill(opacity = 0.7) - - self.play(ShowCreation(cross)) - self.classically.add(cross) - self.play( - self.classically.shift, 4*IN, - FadeIn(new_conception), - ) - self.remove(self.photon.mobject) - self.revert_to_original_skipping_status() - self.play( - finish_photon, - ApplyMethod( - self.pol_filter.set_color, RED, - rate_func = squish_rate_func(there_and_back, 0, 0.3), - run_time = finish_photon.run_time - ) - ) - - def probabalistic_passing(self): - # photons = [ - # self.get_photon() - # for x in range(3) - # ] + [self.get_blocked_photon()] - # random.shuffle(photons) - # for photon in photons: - # added_anims = [] - # if photon.get_filtered: - # added_anims.append( - # self.get_filter_absorption_animation( - # self.pol_filter, photon, - # ) - # ) - # self.play(photon, *added_anims) - # self.wait() - - l1 = self.get_lines(None, self.pol_filter) - l2 = self.get_lines(self.pol_filter, None, 0.85) - for line in it.chain(l1, l2): - if line.get_stroke_width() > 0: - line.set_stroke(width = 3) - - arrow = Arrow( - 2*LEFT, 2*RIGHT, - path_arc = 0.8*np.pi, - ) - label = TexMobject("15\\% \\text{ absorbed}") - label.next_to(arrow, DOWN) - group = VGroup(arrow, label) - group.set_color(RED) - group.rotate(np.pi/2, RIGHT) - group.shift(3*RIGHT + 1.5*IN) - - kwargs = { - "rate_func" : None, - "lag_ratio" : 0, - } - self.play( - ShowCreation(arrow), - Write(label, run_time = 1), - ShowCreation(l1, **kwargs) - ) - self.play( - ShowCreation(l2, run_time = 0.5, **kwargs), - Animation(self.pol_filter), - Animation(l1) - ) - self.wait() - - ### - - def get_filter_lines(self, pol_filter): - lines = VGroup(*[ - Line( - np.sin(a)*RIGHT + np.cos(a)*UP, - np.sin(a)*LEFT + np.cos(a)*UP, - color = RED, - stroke_width = 2, - ) - for a in np.linspace(0, np.pi, 15) - ]) - lines.scale(pol_filter.radius) - lines.rotate(np.pi/2, RIGHT) - lines.rotate(np.pi/2, OUT) - lines.shift(pol_filter.get_center()[0]*RIGHT) - return lines - - def get_blocked_photon(self, **kwargs): - return self.get_photon( - filter_distance = FRAME_X_RADIUS + 3, - get_filtered = True, - **kwargs - ) - -class CompareWaveEquations(TeacherStudentsScene): - def construct(self): - self.add_equation() - self.show_complex_plane() - self.show_interpretations() - - def add_equation(self): - equation = TexMobject( - "|\\!\\psi\\rangle", - "=", "\\alpha", "|\\!\\rightarrow\\rangle", - "+", "\\beta", "|\\!\\uparrow\\rangle", - ) - equation.set_color_by_tex_to_color_map({ - "psi" : BLUE, - "rightarrow" : RED, - "uparrow" : GREEN, - }) - equation.next_to(ORIGIN, LEFT) - equation.to_edge(UP) - - psi_rect = SurroundingRectangle(equation.get_part_by_tex("psi")) - psi_rect.set_color(WHITE) - state_words = TextMobject("Polarization \\\\ state") - state_words.set_color(BLUE) - state_words.scale(0.8) - state_words.next_to(psi_rect, DOWN) - - equation.save_state() - equation.scale(0.01) - equation.fade(1) - equation.move_to(self.teacher.get_left()) - equation.shift(SMALL_BUFF*UP) - - self.play( - equation.restore, - self.teacher.change, "raise_right_hand", - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = psi_rect, - added_anims = [ - ShowCreation(psi_rect), - Write(state_words, run_time = 1) - ], - run_time = 1 - ) - self.play(FadeOut(psi_rect)) - - self.equation = equation - self.state_words = state_words - - def show_complex_plane(self): - new_alpha, new_beta = terms = [ - self.equation.get_part_by_tex(tex).copy() - for tex in ("alpha", "beta") - ] - for term in terms: - term.save_state() - term.generate_target() - term.target.scale(0.7) - - plane = ComplexPlane( - x_radius = 1.5, - y_radius = 1.5, - ) - plane.add_coordinates() - plane.scale(1.3) - plane.next_to(ORIGIN, RIGHT, MED_LARGE_BUFF) - plane.to_edge(UP) - - alpha_dot, beta_dot = [ - Dot( - plane.coords_to_point(x, 0.5), - radius = 0.05, - color = color - ) - for x, color in [(-0.5, RED), (0.5, GREEN)] - ] - new_alpha.target.next_to(alpha_dot, UP+LEFT, 0.5*SMALL_BUFF) - new_alpha.target.set_color(RED) - new_beta.target.next_to(beta_dot, UP+RIGHT, 0.5*SMALL_BUFF) - new_beta.target.set_color(GREEN) - - rhs = TexMobject( - "=", "A_y", "e", "^{i(", - "2\\pi", "f", "t", "+", "\\phi_y", ")}" - ) - rhs.scale(0.7) - rhs.next_to(new_beta.target, RIGHT, SMALL_BUFF) - rhs.shift(0.5*SMALL_BUFF*UP) - rhs.set_color_by_tex_to_color_map({ - "A_y" : GREEN, - "phi" : MAROON_B, - }) - A_copy = rhs.get_part_by_tex("A_y").copy() - phi_copy = rhs.get_part_by_tex("phi_y").copy() - A_line = Line( - plane.coords_to_point(0, 0), - plane.coords_to_point(0.5, 0.5), - color = GREEN, - stroke_width = 2, - ) - - arc = Arc(angle = np.pi/4, radius = 0.5) - arc.shift(plane.get_center()) - - self.play( - Write(plane, run_time = 2), - MoveToTarget(new_alpha), - MoveToTarget(new_beta), - DrawBorderThenFill(alpha_dot, run_time = 1), - DrawBorderThenFill(beta_dot, run_time = 1), - ) - self.play( - Write(rhs), - ShowCreation(A_line), - ShowCreation(arc) - ) - self.play( - phi_copy.next_to, arc, RIGHT, SMALL_BUFF, - phi_copy.shift, 0.5*SMALL_BUFF*UP - ) - self.play( - A_copy.next_to, A_line.get_center(), - UP, SMALL_BUFF, - A_copy.shift, 0.5*SMALL_BUFF*(UP+LEFT), - ) - self.wait() - - def show_interpretations(self): - c_words = TexMobject( - "\\text{Classically: }", "&|\\beta|^2", - "\\rightarrow", - "\\text{Component of} \\\\", - "&\\text{energy in }", "|\\!\\uparrow\\rangle", - "\\text{ direction}", - ) - qm_words = TexMobject( - "\\text{Quantum: }", "&|\\beta|^2", - "\\rightarrow", - "\\text{Probability that}", "\\text{ \\emph{all}} \\\\", - "&\\text{energy is measured in }", "|\\!\\uparrow\\rangle", - "\\text{ direction}", - ) - for words in c_words, qm_words: - words.set_color_by_tex_to_color_map({ - "Classically" : YELLOW, - "Quantum" : BLUE, - "{all}" : BLUE, - "beta" : GREEN, - "uparrow" : GREEN, - }) - words.scale(0.7) - c_words.to_edge(LEFT) - c_words.shift(2*UP) - qm_words.next_to(c_words, DOWN, MED_LARGE_BUFF, LEFT) - - self.play( - FadeOut(self.state_words), - Write(c_words), - self.teacher.change, "happy" - ) - self.change_student_modes( - *["happy"]*3, look_at_arg = c_words - ) - self.play(Write(qm_words)) - self.change_student_modes( - "erm", "confused", "pondering", - look_at_arg = qm_words - ) - self.wait() - -class CircularPhotons(ShootPhotonThroughFilter): - CONFIG = { - "EMWave_config" : { - "phi_vect" : [0, -np.pi/2, 0], - "wave_number" : 1, - "start_point" : 10*LEFT, - "length" : 20, - "n_vectors" : 60, - }, - "apply_filter" : False, - } - def construct(self): - self.set_camera_orientation(theta = -0.75*np.pi) - self.setup_filter() - self.show_phase_difference() - self.shoot_circular_photons() - self.show_filter() - self.show_vertically_polarized_light() - - def setup_filter(self): - pf = self.pol_filter - pf.remove(pf.label) - pf.remove(pf.arrow) - self.remove(pf.label, pf.arrow) - arrows = VGroup(*[ - Arrow( - v1, v2, - color = WHITE, - path_arc = np.pi, - ) - for v1, v2 in [(LEFT, RIGHT), (RIGHT, LEFT)] - ]) - arrows.scale(0.7) - arrows.rotate(np.pi/2, RIGHT) - arrows.rotate(np.pi/2, OUT) - arrows.move_to(center_of_mass(pf.points)) - - pf.label = arrows - pf.add(arrows) - self.remove(pf) - - def show_phase_difference(self): - equation = TexMobject( - "|\\!\\circlearrowright\\rangle", - "=", "\\frac{1}{\\sqrt{2}}", "|\\!\\rightarrow\\rangle", - "+", "\\frac{i}{\\sqrt{2}}", "|\\!\\uparrow\\rangle", - ) - equation.set_color_by_tex_to_color_map({ - "circlearrowright" : BLUE, - "rightarrow" : RED, - "uparrow" : GREEN, - }) - equation.next_to(ORIGIN, LEFT, LARGE_BUFF) - equation.to_edge(UP) - rect = SurroundingRectangle(equation.get_part_by_tex("frac{i}")) - words = TextMobject("Phase shift") - words.next_to(rect, DOWN) - words.set_color(YELLOW) - - group = VGroup(equation, rect, words) - group.rotate(np.pi/2, RIGHT) - group.rotate(np.pi/4, IN) - - self.play(FadeIn(equation)) - self.play(self.get_circular_photon()) - self.play( - ShowCreation(rect), - Write(words, run_time = 1) - ) - - self.circ_equation_group = group - - def shoot_circular_photons(self): - for x in range(2): - self.play(self.get_circular_photon()) - - def show_filter(self): - pf = self.pol_filter - pf.save_state() - pf.shift(4*OUT) - pf.fade(1) - - self.play(pf.restore) - self.play( - self.get_circular_photon(), - Animation(self.circ_equation_group) - ) - self.play(FadeOut(self.circ_equation_group)) - - def show_vertically_polarized_light(self): - equation = TexMobject( - "|\\!\\uparrow \\rangle", - "=", "\\frac{i}{\\sqrt{2}}", "|\\!\\circlearrowleft \\rangle", - "+", "\\frac{-i}{\\sqrt{2}}", "|\\!\\circlearrowright \\rangle", - ) - equation.set_color_by_tex_to_color_map({ - "circlearrowright" : BLUE, - "frac{-i}" : BLUE, - "circlearrowleft" : YELLOW, - "frac{i}" : YELLOW, - "uparrow" : GREEN, - }) - equation.next_to(ORIGIN, LEFT, LARGE_BUFF) - equation.to_edge(UP) - - prob = TexMobject( - "P(", "\\text{passing}", ")", - "=", "\\left(", "\\frac{-i}{\\sqrt{2}}", "\\right)^2" - ) - prob.set_color_by_tex("sqrt{2}", BLUE) - prob.next_to(equation, DOWN) - - group = VGroup(equation, prob) - group.rotate(np.pi/2, RIGHT) - group.rotate(np.pi/4, IN) - - em_wave = EMWave( - wave_number = 0, - amplitude = 2, - start_point = 10*LEFT, - length = 20, - ) - v_photon = WavePacket( - em_wave = em_wave, - include_M_vects = False, - run_time = 2 - ) - c_photon = self.get_circular_photon() - for v_vect in v_photon.mobject: - v_vect.saved_state.set_fill(GREEN) - if v_vect.get_start()[0] > 0: - v_vect.saved_state.set_fill(opacity = 0) - for c_vect in c_photon.mobject: - if c_vect.get_start()[0] < 0: - c_vect.saved_state.set_fill(opacity = 0) - blocked_v_photon = copy.deepcopy(v_photon) - blocked_v_photon.get_filtered = True - blocked_v_photon.filter_distance = 10 - - self.play(Write(equation, run_time = 1)) - self.play(v_photon, c_photon) - self.play(FadeIn(prob)) - bools = 3*[True] + 3*[False] - random.shuffle(bools) - for should_pass in bools: - if should_pass: - self.play(v_photon, c_photon) - else: - self.play( - blocked_v_photon, - self.get_filter_absorption_animation( - self.pol_filter, blocked_v_photon - ) - ) - self.wait() - - #### - - def get_circular_photon(self, **kwargs): - kwargs["run_time"] = kwargs.get("run_time", 2) - photon = ShootPhotonThroughFilter.get_photon(self, **kwargs) - photon.E_func = lambda x : np.exp(-0.25*(2*np.pi*x/photon.width)**2) - return photon - -class ClockwisePhotonInsert(Scene): - def construct(self): - eq = TexMobject( - "\\left| \\frac{-i}{\\sqrt{2}} \\right|^2" - ) - eq.set_color(BLUE) - VGroup(*it.chain(eq[:4], eq[-5:])).set_color(WHITE) - eq.set_height(FRAME_HEIGHT - 1) - eq.to_edge(LEFT) - self.add(eq) - -class OrClickHere(Scene): - def construct(self): - words = TextMobject("Or click here") - words.scale(3) - arrow = Vector( - 2*UP + 2*RIGHT, - rectangular_stem_width = 0.1, - tip_length = 0.5 - ) - arrow.next_to(words, UP).shift(RIGHT) - - self.play( - Write(words), - ShowCreation(arrow) - ) - self.wait() - -class WavesPatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Desmos", - "CrypticSwarm", - "Burt Humburg", - "Charlotte", - "Juan Batiz-Benet", - "Ali Yahya", - "William", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Samantha D. Suplee", - "James Park", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Yu Jun", - "dave nicponski", - "Damion Kistler", - "Markus Persson", - "Yoni Nazarathy", - "Ed Kellett", - "Joseph John Cox", - "Dan Rose", - "Luc Ritchie", - "Harsev Singh", - "Mads Elvheim", - "Erik Sundell", - "Xueqi Li", - "David G. Stork", - "Tianyu Ge", - "Ted Suzman", - "Linh Tran", - "Andrew Busey", - "Michael McGuffin", - "John Haley", - "Ankalagon", - "Eric Lavault", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Ryan Dahl", - "Mark Govea", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "Nils Schneider", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ], - } - -class Footnote(Scene): - def construct(self): - words = TextMobject(""" - \\begin{flushleft} - \\Large - By the way, in the quantum mechanical description - of polarization, states are written like - $|\\! \\leftrightarrow \\rangle$ with a double-headed - arrow, rather than $|\\! \\rightarrow \\rangle$ with - a single-headed arrow. This conveys how there's no distinction - between left and right; they each have the same measurable - state: horizontal. \\\\ - \\quad \\\\ - Because of how I chose to motivate things with classical waves, - I'll stick with the single-headed $|\\! \\rightarrow \\rangle$ - for this video, but just keep in mind that this differs - from quantum mechanics conventions. - \\end{flushleft} - """) - words.set_width(FRAME_WIDTH - 2) - self.add(words) - - - - - - - - - - - diff --git a/from_3b1b/old/wcat.py b/from_3b1b/old/wcat.py deleted file mode 100644 index 37fc0a6b..00000000 --- a/from_3b1b/old/wcat.py +++ /dev/null @@ -1,2118 +0,0 @@ -from manimlib.imports import * - - -class ClosedLoopScene(Scene): - CONFIG = { - "loop_anchor_points" : [ - 3*RIGHT, - 2*RIGHT+UP, - 3*RIGHT + 3*UP, - UP, - 2*UP+LEFT, - 2*LEFT + 2*UP, - 3*LEFT, - 2*LEFT+DOWN, - 3*LEFT+2*DOWN, - 2*DOWN+RIGHT, - LEFT+DOWN, - ], - "square_vertices" : [ - 2*RIGHT+UP, - 2*UP+LEFT, - 2*LEFT+DOWN, - 2*DOWN+RIGHT - ], - "rect_vertices" : [ - 0*RIGHT + 1*UP, - -1*RIGHT + 2*UP, - -3*RIGHT + 0*UP, - -2*RIGHT + -1*UP, - ], - "dot_color" : YELLOW, - "connecting_lines_color" : BLUE, - "pair_colors" : [MAROON_B, PURPLE_B], - } - def setup(self): - self.dots = VGroup() - self.connecting_lines = VGroup() - self.add_loop() - - def add_loop(self): - self.loop = self.get_default_loop() - self.add(self.loop) - - def get_default_loop(self): - loop = VMobject() - loop.set_points_smoothly( - self.loop_anchor_points + [self.loop_anchor_points[0]] - ) - return loop - - def get_square(self): - return Polygon(*self.square_vertices) - - def get_rect_vertex_dots(self, square = False): - if square: - vertices = self.square_vertices - else: - vertices = self.rect_vertices - dots = VGroup(*[Dot(v) for v in vertices]) - dots.set_color(self.dot_color) - return dots - - def get_rect_alphas(self, square = False): - #Inefficient and silly, but whatever. - dots = self.get_rect_vertex_dots(square = square) - return self.get_dot_alphas(dots) - - def add_dot(self, dot): - self.add_dots(dot) - - def add_dots(self, *dots): - self.dots.add(*dots) - self.add(self.dots) - - def add_rect_dots(self, square = False): - self.add_dots(*self.get_rect_vertex_dots(square = square)) - - def add_dots_at_alphas(self, *alphas): - self.add_dots(*[ - Dot( - self.loop.point_from_proportion(alpha), - color = self.dot_color - ) - for alpha in alphas - ]) - - def add_connecting_lines(self, cyclic = False): - if cyclic: - pairs = adjacent_pairs(self.dots) - else: - n_pairs = len(list(self.dots))/2 - pairs = list(zip(self.dots[:n_pairs], self.dots[n_pairs:])) - for d1, d2 in pairs: - line = Line(d1.get_center(), d2.get_center()) - line.start_dot = d1 - line.end_dot = d2 - line.update_anim = UpdateFromFunc( - line, - lambda l : l.put_start_and_end_on( - l.start_dot.get_center(), - l.end_dot.get_center() - ) - ) - line.set_color(d1.get_color()) - self.connecting_lines.add(line) - if cyclic: - self.connecting_lines.set_color(self.connecting_lines_color) - self.connecting_lines.set_stroke(width = 6) - self.add(self.connecting_lines, self.dots) - - def get_line_anims(self): - return [ - line.update_anim - for line in self.connecting_lines - ] + [Animation(self.dots)] - - def get_dot_alphas(self, dots = None, precision = 0.005): - if dots == None: - dots = self.dots - alphas = [] - alpha_range = np.arange(0, 1, precision) - loop_points = np.array(list(map(self.loop.point_from_proportion, alpha_range))) - for dot in dots: - vects = loop_points - dot.get_center() - norms = np.apply_along_axis(get_norm, 1, vects) - index = np.argmin(norms) - alphas.append(alpha_range[index]) - return alphas - - def let_dots_wonder(self, run_time = 5, random_seed = None, added_anims = []): - if random_seed is not None: - np.random.seed(random_seed) - start_alphas = self.get_dot_alphas() - alpha_rates = 0.05 + 0.1*np.random.random(len(list(self.dots))) - def generate_rate_func(start, rate): - return lambda t : (start + t*rate*run_time)%1 - anims = [ - MoveAlongPath( - dot, - self.loop, - rate_func = generate_rate_func(start, rate) - ) - for dot, start, rate in zip(self.dots, start_alphas, alpha_rates) - ] - anims += self.get_line_anims() - anims += added_anims - self.play(*anims, run_time = run_time) - - def move_dots_to_alphas(self, alphas, run_time = 3): - assert(len(alphas) == len(list(self.dots))) - start_alphas = self.get_dot_alphas() - def generate_rate_func(start_alpha, alpha): - return lambda t : interpolate(start_alpha, alpha, smooth(t)) - anims = [ - MoveAlongPath( - dot, self.loop, - rate_func = generate_rate_func(sa, a), - run_time = run_time, - ) - for dot, sa, a in zip(self.dots, start_alphas, alphas) - ] - anims += self.get_line_anims() - self.play(*anims) - - def transform_loop(self, target_loop, added_anims = [], **kwargs): - alphas = self.get_dot_alphas() - dot_anims = [] - for dot, alpha in zip(self.dots, alphas): - dot.generate_target() - dot.target.move_to(target_loop.point_from_proportion(alpha)) - dot_anims.append(MoveToTarget(dot)) - self.play( - Transform(self.loop, target_loop), - *dot_anims + self.get_line_anims() + added_anims, - **kwargs - ) - - def set_color_dots_by_pair(self): - n_pairs = len(list(self.dots))/2 - for d1, d2, c in zip(self.dots[:n_pairs], self.dots[n_pairs:], self.pair_colors): - VGroup(d1, d2).set_color(c) - - def find_square(self): - alpha_quads = list(it.combinations( - np.arange(0, 1, 0.02) , 4 - )) - quads = np.array([ - [ - self.loop.point_from_proportion(alpha) - for alpha in quad - ] - for quad in alpha_quads - ]) - scores = self.square_scores(quads) - index = np.argmin(scores) - return quads[index] - - def square_scores(self, all_quads): - midpoint_diffs = np.apply_along_axis( - get_norm, 1, - 0.5*(all_quads[:,0] + all_quads[:,2]) - 0.5*(all_quads[:,1] + all_quads[:,3]) - ) - vects1 = all_quads[:,0] - all_quads[:,2] - vects2 = all_quads[:,1] - all_quads[:,3] - distances1 = np.apply_along_axis(get_norm, 1, vects1) - distances2 = np.apply_along_axis(get_norm, 1, vects2) - distance_diffs = np.abs(distances1 - distances2) - midpoint_diffs /= distances1 - distance_diffs /= distances2 - - buffed_d1s = np.repeat(distances1, 3).reshape(vects1.shape) - buffed_d2s = np.repeat(distances2, 3).reshape(vects2.shape) - unit_v1s = vects1/buffed_d1s - unit_v2s = vects2/buffed_d2s - dots = np.abs(unit_v1s[:,0]*unit_v2s[:,0] + unit_v1s[:,1]*unit_v2s[:,1] + unit_v1s[:,2]*unit_v2s[:,2]) - - return midpoint_diffs + distance_diffs + dots - - -############################# - -class Introduction(TeacherStudentsScene): - def construct(self): - self.play(self.get_teacher().change_mode, "hooray") - self.random_blink() - self.teacher_says("") - for pi in self.get_students(): - pi.generate_target() - pi.target.change_mode("happy") - pi.target.look_at(self.get_teacher().bubble) - self.play(*list(map(MoveToTarget, self.get_students()))) - self.random_blink(3) - self.teacher_says( - "Here's why \\\\ I'm excited...", - target_mode = "hooray" - ) - for pi in self.get_students(): - pi.target.look_at(self.get_teacher().eyes) - self.play(*list(map(MoveToTarget, self.get_students()))) - self.wait() - -class WhenIWasAKid(TeacherStudentsScene): - def construct(self): - children = self.get_children() - speaker = self.get_speaker() - - self.prepare_everyone(children, speaker) - self.state_excitement(children, speaker) - self.students = children - self.teacher = speaker - self.run_class() - self.grow_up() - - def state_excitement(self, children, speaker): - self.teacher_says( - """ - Here's why - I'm excited! - """, - target_mode = "hooray" - ) - self.change_student_modes(*["happy"]*3) - self.wait() - - speaker.look_at(children) - me = children[-1] - self.play( - FadeOut(self.get_students()), - FadeOut(self.get_teacher().bubble), - FadeOut(self.get_teacher().bubble.content), - Transform(self.get_teacher(), me) - ) - self.remove(self.get_teacher()) - self.add(me) - self.play(*list(map(FadeIn, children[:-1] + [speaker]))) - self.random_blink() - - def run_class(self): - children = self.students - speaker = self.teacher - title = TextMobject("Topology") - title.to_edge(UP) - pi1, pi2, pi3, me = children - - self.random_blink() - self.teacher_says( - """ - Math! Excitement! - You are the future! - """, - target_mode = "hooray" - ) - self.play( - pi1.look_at, pi2.eyes, - pi1.change_mode, "erm", - pi2.look_at, pi1.eyes, - pi2.change_mode, "surprised", - ) - self.play( - pi3.look_at, me.eyes, - pi3.change_mode, "sassy", - me.look_at, pi3.eyes, - ) - self.random_blink(2) - - self.play( - self.teacher.change_mode, "speaking", - FadeOut(self.teacher.bubble), - FadeOut(self.teacher.bubble.content), - ) - self.play(Write(title)) - self.random_blink() - - self.play(pi1.change_mode, "raise_right_hand") - self.random_blink() - self.play( - pi2.change_mode, "confused", - pi3.change_mode, "happy", - pi2.look_at, pi3.eyes, - pi3.look_at, pi2.eyes, - ) - self.random_blink() - self.play(me.change_mode, "pondering") - self.wait() - self.random_blink(2) - self.play(pi1.change_mode, "raise_left_hand") - self.wait() - self.play(pi2.change_mode, "erm") - self.random_blink() - self.student_says( - "How is this math?", - student_index = -1, - target_mode = "pleading", - width = 5, - height = 3, - direction = RIGHT - ) - self.play( - pi1.change_mode, "pondering", - pi2.change_mode, "pondering", - pi3.change_mode, "pondering", - ) - self.play(speaker.change_mode, "pondering") - self.random_blink() - - def grow_up(self): - me = self.students[-1] - self.students.remove(me) - morty = Mortimer(mode = "pondering") - morty.flip() - morty.move_to(me, aligned_edge = DOWN) - morty.to_edge(LEFT) - morty.look(RIGHT) - - self.play( - Transform(me, morty), - *list(map(FadeOut, [ - self.students, self.teacher, - me.bubble, me.bubble.content - ])) - ) - self.remove(me) - self.add(morty) - self.play(Blink(morty)) - self.wait() - self.play(morty.change_mode, "hooray") - self.wait() - - - def prepare_everyone(self, children, speaker): - self.everyone = list(children) + [speaker] - for pi in self.everyone: - pi.bubble = None - - def get_children(self): - colors = [MAROON_E, YELLOW_D, PINK, GREY_BROWN] - children = VGroup(*[ - BabyPiCreature(color = color) - for color in colors - ]) - children.arrange(RIGHT) - children.to_edge(DOWN, buff = LARGE_BUFF) - children.to_edge(LEFT) - return children - - def get_speaker(self): - speaker = Mathematician(mode = "happy") - speaker.flip() - speaker.to_edge(DOWN, buff = LARGE_BUFF) - speaker.to_edge(RIGHT) - return speaker - - def get_pi_creatures(self): - if hasattr(self, "everyone"): - return self.everyone - else: - return TeacherStudentsScene.get_pi_creatures(self) - -class FormingTheMobiusStrip(Scene): - def construct(self): - pass - -class DrawLineOnMobiusStrip(Scene): - def construct(self): - pass - -class MugIntoTorus(Scene): - def construct(self): - pass - -class DefineInscribedSquareProblem(ClosedLoopScene): - def construct(self): - self.draw_loop() - self.cycle_through_shapes() - self.ask_about_rectangles() - - def draw_loop(self): - self.title = TextMobject("Inscribed", "square", "problem") - self.title.to_edge(UP) - - #Draw loop - self.remove(self.loop) - self.play(Write(self.title)) - self.wait() - self.play(ShowCreation( - self.loop, - run_time = 5, - rate_func=linear - )) - self.wait() - self.add_rect_dots(square = True) - self.play(ShowCreation(self.dots, run_time = 2)) - self.wait() - self.add_connecting_lines(cyclic = True) - self.play( - ShowCreation( - self.connecting_lines, - lag_ratio = 0, - run_time = 2 - ), - Animation(self.dots) - ) - self.wait(2) - - def cycle_through_shapes(self): - circle = Circle(radius = 2.5, color = WHITE) - ellipse = circle.copy() - ellipse.stretch(1.5, 0) - ellipse.stretch(0.7, 1) - ellipse.rotate(-np.pi/2) - ellipse.set_height(4) - pi_loop = TexMobject("\\pi")[0] - pi_loop.set_fill(opacity = 0) - pi_loop.set_stroke( - color = WHITE, - width = DEFAULT_STROKE_WIDTH - ) - pi_loop.set_height(4) - randy = Randolph() - randy.look(DOWN) - randy.set_width(pi_loop.get_width()) - randy.move_to(pi_loop, aligned_edge = DOWN) - randy.body.set_fill(opacity = 0) - randy.mouth.set_stroke(width = 0) - - self.transform_loop(circle) - self.remove(self.loop) - self.loop = circle - self.add(self.loop, self.connecting_lines, self.dots) - self.wait() - odd_eigths = np.linspace(1./8, 7./8, 4) - self.move_dots_to_alphas(odd_eigths) - self.wait() - for nudge in 0.1, -0.1, 0: - self.move_dots_to_alphas(odd_eigths+nudge) - self.wait() - self.transform_loop(ellipse) - self.wait() - nudge = 0.055 - self.move_dots_to_alphas( - odd_eigths + [nudge, -nudge, nudge, -nudge] - ) - self.wait(2) - self.transform_loop(pi_loop) - self.let_dots_wonder() - randy_anims = [ - FadeIn(randy), - Animation(randy), - Blink(randy), - Animation(randy), - Blink(randy), - Animation(randy), - Blink(randy, rate_func = smooth) - ] - for anim in randy_anims: - self.let_dots_wonder( - run_time = 1.5, - random_seed = 0, - added_anims = [anim] - ) - self.remove(randy) - self.transform_loop(self.get_default_loop()) - - def ask_about_rectangles(self): - morty = Mortimer() - morty.next_to(ORIGIN, DOWN) - morty.to_edge(RIGHT) - - new_title = TextMobject("Inscribed", "rectangle", "problem") - new_title.set_color_by_tex("rectangle", YELLOW) - new_title.to_edge(UP) - rect_dots = self.get_rect_vertex_dots() - rect_alphas = self.get_dot_alphas(rect_dots) - - self.play(FadeIn(morty)) - self.play(morty.change_mode, "speaking") - self.play(Transform(self.title, new_title)) - self.move_dots_to_alphas(rect_alphas) - self.wait() - self.play(morty.change_mode, "hooray") - self.play(Blink(morty)) - self.wait() - self.play(FadeOut(self.connecting_lines)) - self.connecting_lines = VGroup() - self.play(morty.change_mode, "plain") - - dot_pairs = [ - VGroup(self.dots[i], self.dots[j]) - for i, j in [(0, 2), (1, 3)] - ] - pair_colors = MAROON_B, PURPLE_B - diag_lines = [ - Line(d1.get_center(), d2.get_center(), color = c) - for (d1, d2), c in zip(dot_pairs, pair_colors) - ] - - for pair, line in zip(dot_pairs, diag_lines): - self.play( - FadeIn(line), - pair.set_color, line.get_color(), - ) - -class RectangleProperties(Scene): - def construct(self): - rect = Rectangle(color = BLUE) - vertex_dots = VGroup(*[ - Dot(anchor, color = YELLOW) - for anchor in rect.get_anchors_and_handles()[0] - ]) - dot_pairs = [ - VGroup(vertex_dots[i], vertex_dots[j]) - for i, j in [(0, 2), (1, 3)] - ] - colors = [MAROON_B, PURPLE_B] - diag_lines = [ - Line(d1.get_center(), d2.get_center(), color = c) - for (d1, d2), c in zip(dot_pairs, colors) - ] - braces = [Brace(rect).next_to(ORIGIN, DOWN) for x in range(2)] - for brace, line in zip(braces, diag_lines): - brace.stretch_to_fit_width(line.get_length()) - brace.rotate(line.get_angle()) - a, b, c, d = labels = VGroup(*[ - TexMobject(s).next_to(dot, dot.get_center(), buff = SMALL_BUFF) - for s, dot in zip("abcd", vertex_dots) - ]) - midpoint = Dot(ORIGIN, color = RED) - - - self.play(ShowCreation(rect)) - self.wait() - self.play( - ShowCreation(vertex_dots), - Write(labels) - ) - self.wait() - mob_lists = [ - (a, c, dot_pairs[0]), - (b, d, dot_pairs[1]), - ] - for color, mob_list in zip(colors, mob_lists): - self.play(*[ - ApplyMethod(mob.set_color, color) - for mob in mob_list - ]) - self.wait() - for line, brace in zip(diag_lines, braces): - self.play( - ShowCreation(line), - GrowFromCenter(brace) - ) - self.wait() - self.play(FadeOut(brace)) - self.play(FadeIn(midpoint)) - self.wait() - -class PairOfPairBecomeRectangle(Scene): - def construct(self): - dots = VGroup( - Dot(4*RIGHT+0.5*DOWN, color = MAROON_B), - Dot(5*RIGHT+3*UP, color = MAROON_B), - Dot(LEFT+0.1*DOWN, color = PURPLE_B), - Dot(2*LEFT+UP, color = PURPLE_B) - ) - labels = VGroup() - for dot, char in zip(dots, "acbd"): - label = TexMobject(char) - y_coord = dot.get_center()[1] - label.next_to(dot, np.sign(dot.get_center()[1])*UP) - label.set_color(dot.get_color()) - labels.add(label) - lines = [ - Line( - dots[i].get_center(), - dots[j].get_center(), - color = dots[i].get_color() - ) - for i, j in [(0, 1), (2, 3)] - ] - groups = [ - VGroup(dots[0], dots[1], labels[0], labels[1], lines[0]), - VGroup(dots[2], dots[3], labels[2], labels[3], lines[1]), - ] - midpoint = Dot(LEFT, color = RED) - - words = VGroup(*list(map(TextMobject, [ - "Common midpoint", - "Same distance apart", - "$\\Downarrow$", - "Rectangle", - ]))) - words.arrange(DOWN) - words.to_edge(RIGHT) - words[-1].set_color(BLUE) - - self.play( - ShowCreation(dots), - Write(labels) - ) - self.play(*list(map(ShowCreation, lines))) - self.wait() - self.play(*[ - ApplyMethod( - group.shift, - -group[-1].get_center()+midpoint.get_center() - ) - for group in groups - ]) - self.play( - ShowCreation(midpoint), - Write(words[0]) - ) - factor = lines[0].get_length()/lines[1].get_length() - grower = groups[1].copy() - new_line = grower[-1] - new_line.scale_in_place(factor) - grower[0].move_to(new_line.get_start()) - grower[2].next_to(grower[0], DOWN) - grower[1].move_to(new_line.get_end()) - grower[3].next_to(grower[1], UP) - - self.play(Transform(groups[1], grower)) - self.play(Write(words[1])) - self.wait() - - rectangle = Polygon(*[ - dots[i].get_center() - for i in (0, 2, 1, 3) - ]) - rectangle.set_color(BLUE) - self.play( - ShowCreation(rectangle), - Animation(dots) - ) - self.play(*list(map(Write, words[2:]))) - self.wait() - -class SearchForRectangleOnLoop(ClosedLoopScene): - def construct(self): - self.add_dots_at_alphas(*np.linspace(0.2, 0.8, 4)) - self.set_color_dots_by_pair() - rect_alphas = self.get_rect_alphas() - - self.play(ShowCreation(self.dots)) - self.add_connecting_lines() - self.play(ShowCreation(self.connecting_lines)) - self.let_dots_wonder(2) - self.move_dots_to_alphas(rect_alphas) - - midpoint = Dot( - center_of_mass([d.get_center() for d in self.dots]), - color = RED - ) - self.play(ShowCreation(midpoint)) - self.wait() - angles = [line.get_angle() for line in self.connecting_lines] - angle_mean = np.mean(angles) - self.play( - *[ - ApplyMethod(line.rotate_in_place, angle_mean-angle) - for line, angle in zip(self.connecting_lines, angles) - ] + [Animation(midpoint)], - rate_func = there_and_back - ) - self.add(self.connecting_lines.copy(), midpoint) - self.connecting_lines = VGroup() - self.wait() - self.add_connecting_lines(cyclic = True) - self.play( - ShowCreation(self.connecting_lines), - Animation(self.dots) - ) - self.wait() - -class DeclareFunction(ClosedLoopScene): - def construct(self): - self.add_dots_at_alphas(0.2, 0.8) - self.set_color_dots_by_pair() - self.add_connecting_lines() - VGroup( - self.loop, self.dots, self.connecting_lines - ).scale(0.7).to_edge(LEFT).shift(DOWN) - arrow = Arrow(LEFT, RIGHT).next_to(self.loop) - self.add(arrow) - - self.add_tex() - self.let_dots_wonder(10) - - def add_tex(self): - tex = TexMobject("f", "(A, B)", "=", "(x, y, z)") - tex.to_edge(UP) - tex.shift(LEFT) - - ab_brace = Brace(tex[1]) - xyz_brace = Brace(tex[-1], RIGHT) - ab_brace.add(ab_brace.get_text("Pair of points on the loop")) - xyz_brace.add(xyz_brace.get_text("Point in 3d space")) - ab_brace.set_color_by_gradient(MAROON_B, PURPLE_B) - xyz_brace.set_color(BLUE) - - self.add(tex) - self.play(Write(ab_brace)) - self.wait() - self.play(Write(xyz_brace)) - self.wait() - -class DefinePairTo3dFunction(Scene): - def construct(self): - pass - -class LabelMidpoint(Scene): - def construct(self): - words = TextMobject("Midpoint $M$") - words.set_color(RED) - words.scale(2) - self.play(Write(words, run_time = 1)) - self.wait() - -class LabelDistance(Scene): - def construct(self): - words = TextMobject("Distance $d$") - words.set_color(MAROON_B) - words.scale(2) - self.play(Write(words, run_time = 1)) - self.wait() - -class DrawingOneLineOfTheSurface(Scene): - def construct(self): - pass - -class FunctionSurface(Scene): - def construct(self): - pass - -class PointPairApprocahingEachother3D(Scene): - def construct(self): - pass - -class InputPairToFunction(Scene): - def construct(self): - tex = TexMobject("f(X, X)", "=X") - tex.set_color_by_tex("=X", BLUE) - tex.scale(2) - self.play(Write(tex[0])) - self.wait(2) - self.play(Write(tex[1])) - self.wait(2) - -class WigglePairUnderSurface(Scene): - def construct(self): - pass - -class WriteContinuous(Scene): - def construct(self): - self.play(Write(TextMobject("Continuous").scale(2))) - self.wait(2) - -class DistinctPairCollisionOnSurface(Scene): - def construct(self): - pass - -class PairsOfPointsOnLoop(ClosedLoopScene): - def construct(self): - self.add_dots_at_alphas(0.2, 0.5) - self.dots.set_color(MAROON_B) - self.add_connecting_lines() - self.let_dots_wonder(run_time = 10) - -class PairOfRealsToPlane(Scene): - def construct(self): - r1, r2 = numbers = -3, 2 - colors = GREEN, RED - dot1, dot2 = dots = VGroup(*[Dot(color = c) for c in colors]) - for dot, number in zip(dots, numbers): - dot.move_to(number*RIGHT) - pair_label = TexMobject("(", str(r1), ",", str(r2), ")") - for number, color in zip(numbers, colors): - pair_label.set_color_by_tex(str(number), color) - pair_label.next_to(dots, UP, buff = 2) - arrows = VGroup(*[ - Arrow(pair_label[i], dot, color = dot.get_color()) - for i, dot in zip([1, 3], dots) - ]) - two_d_point = Dot(r1*RIGHT + r2*UP, color = YELLOW) - pair_label.add_background_rectangle() - - x_axis = NumberLine(color = BLUE) - y_axis = NumberLine(color = BLUE) - plane = NumberPlane().fade() - - self.add(x_axis, y_axis, dots, pair_label) - self.play(ShowCreation(arrows, run_time = 2)) - self.wait() - self.play( - pair_label.next_to, two_d_point, UP+LEFT, SMALL_BUFF, - Rotate(y_axis, np.pi/2), - Rotate(dot2, np.pi/2), - FadeOut(arrows) - ) - lines = VGroup(*[ - DashedLine(dot, two_d_point, color = dot.get_color()) - for dot in dots - ]) - self.play(*list(map(ShowCreation, lines))) - self.play(ShowCreation(two_d_point)) - everything = VGroup(*self.get_mobjects()) - self.play( - FadeIn(plane), - Animation(everything), - Animation(dot2) - ) - self.wait() - -class SeekSurfaceForPairs(ClosedLoopScene): - def construct(self): - self.loop.to_edge(LEFT) - self.add_dots_at_alphas(0.2, 0.3) - self.set_color_dots_by_pair() - self.add_connecting_lines() - - arrow = Arrow(LEFT, RIGHT).next_to(self.loop) - words = TextMobject("Some 2d surface") - words.next_to(arrow, RIGHT) - - anims = [ - ShowCreation(arrow), - Write(words) - ] - for anim in anims: - self.let_dots_wonder( - random_seed = 1, - added_anims = [anim], - run_time = anim.run_time - ) - self.let_dots_wonder(random_seed = 1, run_time = 10) - -class AskAbouPairType(TeacherStudentsScene): - def construct(self): - self.student_says(""" - Do you mean ordered - or unordered pairs? - """) - self.play(*[ - ApplyMethod(self.get_students()[i].change_mode, "confused") - for i in (0, 2) - ]) - self.random_blink(3) - -class DefineOrderedPair(ClosedLoopScene): - def construct(self): - title = TextMobject("Ordered pairs") - title.to_edge(UP) - subtitle = TexMobject( - "(", "a", ",", "b", ")", - "\\ne", - "(", "b", ",", "a", ")" - ) - labels_start = VGroup(subtitle[1], subtitle[3]) - labels_end = VGroup(subtitle[9], subtitle[7]) - subtitle.next_to(title, DOWN) - colors = GREEN, RED - for char, color in zip("ab", colors): - subtitle.set_color_by_tex(char, color) - self.loop.next_to(subtitle, DOWN) - self.add(title, subtitle) - - self.add_dots_at_alphas(0.5, 0.6) - dots = self.dots - for dot, color, char in zip(dots, colors, "ab"): - dot.set_color(color) - label = TexMobject(char) - label.set_color(color) - label.next_to(dot, RIGHT, buff = SMALL_BUFF) - dot.label = label - self.dots[1].label.shift(0.3*UP) - first = TextMobject("First") - first.next_to(self.dots[0], UP+2*LEFT, LARGE_BUFF) - arrow = Arrow(first.get_bottom(), self.dots[0], color = GREEN) - - self.wait() - self.play(*[ - Transform(label.copy(), dot.label) - for label, dot in zip(labels_start, dots) - ]) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(*[d.label for d in dots]) - self.wait() - self.play( - Write(first), - ShowCreation(arrow) - ) - self.wait() - -class DefineUnorderedPair(ClosedLoopScene): - def construct(self): - title = TextMobject("Unordered pairs") - title.to_edge(UP) - subtitle = TexMobject( - "\\{a,b\\}", - "=", - "\\{b,a\\}", - ) - subtitle.next_to(title, DOWN) - for char in "ab": - subtitle.set_color_by_tex(char, PURPLE_B) - self.loop.next_to(subtitle, DOWN) - self.add(title, subtitle) - - self.add_dots_at_alphas(0.5, 0.6) - dots = self.dots - dots.set_color(PURPLE_B) - - labels = VGroup(*[subtitle[i].copy() for i in (0, 2)]) - for label, vect in zip(labels, [LEFT, RIGHT]): - label.next_to(dots, vect, LARGE_BUFF) - arrows = [ - Arrow(*pair, color = PURPLE_B) - for pair in it.product(labels, dots) - ] - arrow_pairs = [VGroup(*arrows[:2]), VGroup(*arrows[2:])] - - for label, arrow_pair in zip(labels, arrow_pairs): - self.play(*list(map(FadeIn, [label, arrow_pair]))) - self.wait() - for x in range(2): - self.play( - dots[0].move_to, dots[1], - dots[1].move_to, dots[0], - path_arc = np.pi/2 - ) - self.wait() - -class BeginWithOrdered(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - One must know order - before he can ignore it. - """) - self.random_blink(3) - -class DeformToInterval(ClosedLoopScene): - def construct(self): - interval = UnitInterval(color = WHITE) - interval.shift(2*DOWN) - numbers = interval.get_number_mobjects(0, 1) - line = Line(interval.get_left(), interval.get_right()) - line.insert_n_curves(self.loop.get_num_curves()) - line.make_smooth() - - self.loop.scale(0.7) - self.loop.to_edge(UP) - original_loop = self.loop.copy() - cut_loop = self.loop.copy() - cut_loop.points[0] += 0.3*(UP+RIGHT) - cut_loop.points[-1] += 0.3*(DOWN+RIGHT) - - #Unwrap loop - self.transform_loop(cut_loop, path_arc = np.pi) - self.wait() - self.transform_loop( - line, - run_time = 3, - path_arc = np.pi/2 - ) - self.wait() - self.play(ShowCreation(interval)) - self.play(Write(numbers)) - self.wait() - - #Follow points - self.loop = original_loop.copy() - self.play(FadeIn(self.loop)) - self.add(original_loop) - self.add_dots_at_alphas(*np.linspace(0, 1, 20)) - self.dots.set_color_by_gradient(BLUE, MAROON_C, BLUE) - dot_at_1 = self.dots[-1] - dot_at_1.generate_target() - dot_at_1.target.move_to(interval.get_right()) - dots_copy = self.dots.copy() - fading_dots = VGroup(*list(self.dots)+list(dots_copy)) - end_dots = VGroup( - self.dots[0], self.dots[-1], - dots_copy[0], dots_copy[-1] - ) - fading_dots.remove(*end_dots) - - self.play(Write(self.dots)) - self.add(dots_copy) - self.wait() - self.transform_loop( - line, - added_anims = [MoveToTarget(dot_at_1)], - run_time = 3 - ) - self.wait() - self.loop = original_loop - self.dots = dots_copy - dot_at_1 = self.dots[-1] - dot_at_1.target.move_to(cut_loop.points[-1]) - self.transform_loop( - cut_loop, - added_anims = [MoveToTarget(dot_at_1)] - ) - self.wait() - fading_dots.generate_target() - fading_dots.target.set_fill(opacity = 0.3) - self.play(MoveToTarget(fading_dots)) - self.play( - end_dots.shift, 0.2*UP, - rate_func = wiggle - ) - self.wait() - -class RepresentPairInUnitSquare(ClosedLoopScene): - def construct(self): - interval = UnitInterval(color = WHITE) - interval.shift(2.5*DOWN) - interval.shift(LEFT) - numbers = interval.get_number_mobjects(0, 1) - line = Line(interval.get_left(), interval.get_right()) - line.insert_n_curves(self.loop.get_num_curves()) - line.make_smooth() - vert_interval = interval.copy() - square = Square() - square.set_width(interval.get_width()) - square.set_stroke(width = 0) - square.set_fill(color = BLUE, opacity = 0.3) - square.move_to( - interval.get_left(), - aligned_edge = DOWN+LEFT - ) - - right_words = VGroup(*[ - TextMobject("Pair of\\\\ loop points"), - TexMobject("\\Downarrow"), - TextMobject("Point in \\\\ unit square") - ]) - right_words.arrange(DOWN) - right_words.to_edge(RIGHT) - - dot_coords = (0.3, 0.7) - self.loop.scale(0.7) - self.loop.to_edge(UP) - self.add_dots_at_alphas(*dot_coords) - self.dots.set_color_by_gradient(GREEN, RED) - - self.play( - Write(self.dots), - Write(right_words[0]) - ) - self.wait() - self.transform_loop(line) - self.play( - ShowCreation(interval), - Write(numbers), - Animation(self.dots) - ) - self.wait() - self.play(*[ - Rotate(mob, np.pi/2, about_point = interval.get_left()) - for mob in (vert_interval, self.dots[1]) - ]) - - #Find interior point - point = self.dots[0].get_center()[0]*RIGHT - point += self.dots[1].get_center()[1]*UP - inner_dot = Dot(point, color = YELLOW) - dashed_lines = VGroup(*[ - DashedLine(dot, inner_dot, color = dot.get_color()) - for dot in self.dots - ]) - self.play(ShowCreation(dashed_lines)) - self.play(ShowCreation(inner_dot)) - self.play( - FadeIn(square), - Animation(self.dots), - *list(map(Write, right_words[1:])) - ) - self.wait() - - #Shift point in square - - movers = list(dashed_lines)+list(self.dots)+[inner_dot] - for mob in movers: - mob.generate_target() - shift_vals = [ - RIGHT+DOWN, - LEFT+DOWN, - LEFT+2*UP, - 3*DOWN, - 2*RIGHT+UP, - RIGHT+UP, - 3*LEFT+3*DOWN - ] - for shift_val in shift_vals: - inner_dot.target.shift(shift_val) - self.dots[0].target.shift(shift_val[0]*RIGHT) - self.dots[1].target.shift(shift_val[1]*UP) - for line, dot in zip(dashed_lines, self.dots): - line.target.put_start_and_end_on( - dot.target.get_center(), - inner_dot.target.get_center() - ) - self.play(*list(map(MoveToTarget, movers))) - self.wait() - self.play(*list(map(FadeOut, [dashed_lines, self.dots]))) - -class EdgesOfSquare(Scene): - def construct(self): - square = self.add_square() - x_edges, y_edges = self.get_edges(square) - label_groups = self.get_coordinate_labels(square) - arrow_groups = self.get_arrows(x_edges, y_edges) - - for edge in list(x_edges) + list(y_edges): - self.play(ShowCreation(edge)) - self.wait() - for label_group in label_groups: - for label in label_group[:3]: - self.play(FadeIn(label)) - self.wait() - self.play(Write(VGroup(*label_group[3:]))) - self.wait() - self.play(FadeOut(VGroup(*label_groups))) - for arrows in arrow_groups: - self.play(ShowCreation(arrows, run_time = 2)) - self.wait() - self.play(*[ - ApplyMethod( - n.next_to, - square.get_corner(vect+LEFT), - LEFT, - MED_SMALL_BUFF, - path_arc = np.pi/2 - ) - for n, vect in zip(self.numbers, [DOWN, UP]) - ]) - self.wait() - - def add_square(self): - interval = UnitInterval(color = WHITE) - interval.shift(2.5*DOWN) - bottom_left = interval.get_left() - for tick in interval.tick_marks: - height = tick.get_height() - tick.scale_in_place(0.5) - tick.shift(height*DOWN/4.) - self.numbers = interval.get_number_mobjects(0, 1) - vert_interval = interval.copy() - vert_interval.rotate(np.pi, axis = UP+RIGHT, about_point = bottom_left) - square = Square() - square.set_width(interval.get_width()) - square.set_stroke(width = 0) - square.set_fill(color = BLUE, opacity = 0.3) - square.move_to( - bottom_left, - aligned_edge = DOWN+LEFT - ) - self.add(interval, self.numbers, vert_interval, square) - return square - - def get_edges(self, square): - y_edges = VGroup(*[ - Line( - square.get_corner(vect+LEFT), - square.get_corner(vect+RIGHT), - ) - for vect in (DOWN, UP) - ]) - y_edges.set_color(BLUE) - x_edges = VGroup(*[ - Line( - square.get_corner(vect+DOWN), - square.get_corner(vect+UP), - ) - for vect in (LEFT, RIGHT) - ]) - x_edges.set_color(MAROON_B) - return x_edges, y_edges - - def get_coordinate_labels(self, square): - alpha_range = np.arange(0, 1.1, 0.1) - dot_groups = [ - VGroup(*[ - Dot(interpolate( - square.get_corner(DOWN+vect), - square.get_corner(UP+vect), - alpha - )) - for alpha in alpha_range - ]) - for vect in (LEFT, RIGHT) - ] - for group in dot_groups: - group.set_color_by_gradient(YELLOW, PURPLE_B) - label_groups = [ - VGroup(*[ - TexMobject("(%s, %s)"%(a, b)).scale(0.7) - for b in alpha_range - ]) - for a in (0, 1) - ] - for dot_group, label_group in zip(dot_groups, label_groups): - for dot, label in zip(dot_group, label_group): - label[1].set_color(MAROON_B) - label.next_to(dot, RIGHT*np.sign(dot.get_center()[0])) - label.add(dot) - return label_groups - - def get_arrows(self, x_edges, y_edges): - alpha_range = np.linspace(0, 1, 4) - return [ - VGroup(*[ - VGroup(*[ - Arrow( - edge.point_from_proportion(a1), - edge.point_from_proportion(a2), - buff = 0 - ) - for a1, a2 in zip(alpha_range, alpha_range[1:]) - ]) - for edge in edges - ]).set_color(edges.get_color()) - for edges in (x_edges, y_edges) - ] - -class EndpointsGluedTogether(ClosedLoopScene): - def construct(self): - interval = UnitInterval(color = WHITE) - interval.shift(2*DOWN) - numbers = interval.get_number_mobjects(0, 1) - line = Line(interval.get_left(), interval.get_right()) - line.insert_n_curves(self.loop.get_num_curves()) - line.make_smooth() - - self.loop.scale(0.7) - self.loop.to_edge(UP) - original_loop = self.loop - self.remove(original_loop) - - self.loop = line - dots = VGroup(*[ - Dot(line.get_bounding_box_point(vect)) - for vect in (LEFT, RIGHT) - ]) - dots.set_color(BLUE) - - self.add(interval, dots) - self.play(dots.rotate_in_place, np.pi/20, rate_func = wiggle) - self.wait() - self.transform_loop( - original_loop, - added_anims = [ - ApplyMethod(dot.move_to, original_loop.points[0]) - for dot in dots - ], - run_time = 3 - ) - self.wait() - -class WrapUpToTorus(Scene): - def construct(self): - pass - -class TorusPlaneAnalogy(ClosedLoopScene): - def construct(self): - top_arrow = DoubleArrow(LEFT, RIGHT) - top_arrow.to_edge(UP, buff = 2*LARGE_BUFF) - single_pointed_top_arrow = Arrow(LEFT, RIGHT) - single_pointed_top_arrow.to_edge(UP, buff = 2*LARGE_BUFF) - low_arrow = DoubleArrow(LEFT, RIGHT).shift(2*DOWN) - self.loop.scale(0.5) - self.loop.next_to(top_arrow, RIGHT) - self.loop.shift_onto_screen() - self.add_dots_at_alphas(0.3, 0.5) - self.dots.set_color_by_gradient(GREEN, RED) - - plane = NumberPlane() - plane.scale(0.3).next_to(low_arrow, LEFT) - number_line = NumberLine() - number_line.scale(0.3) - number_line.next_to(low_arrow, RIGHT) - number_line.add( - Dot(number_line.number_to_point(3), color = GREEN), - Dot(number_line.number_to_point(-2), color = RED), - ) - - self.wait() - self.play(ShowCreation(single_pointed_top_arrow)) - self.wait() - self.play(ShowCreation(top_arrow)) - self.wait() - self.play(ShowCreation(plane)) - self.play(ShowCreation(low_arrow)) - self.play(ShowCreation(number_line)) - self.wait() - -class WigglingPairOfPoints(ClosedLoopScene): - def construct(self): - alpha_pairs = [ - (0.4, 0.6), - (0.42, 0.62), - ] - self.add_dots_at_alphas(*alpha_pairs[-1]) - self.add_connecting_lines() - self.dots.set_color_by_gradient(GREEN, RED) - self.connecting_lines.set_color(YELLOW) - for x, pair in zip(list(range(20)), it.cycle(alpha_pairs)): - self.move_dots_to_alphas(pair, run_time = 0.3) - - -class WigglingTorusPoint(Scene): - def construct(self): - pass - -class WhatAboutUnordered(TeacherStudentsScene): - def construct(self): - self.student_says( - "What about \\\\ unordered pairs?" - ) - self.play(self.get_teacher().change_mode, "pondering") - self.random_blink(2) - -class TrivialPairCollision(ClosedLoopScene): - def construct(self): - self.loop.to_edge(RIGHT) - self.add_dots_at_alphas(0.35, 0.55) - self.dots.set_color_by_gradient(BLUE, YELLOW) - a, b = self.dots - a_label = TexMobject("a").next_to(a, RIGHT) - a_label.set_color(a.get_color()) - b_label = TexMobject("b").next_to(b, LEFT) - b_label.set_color(b.get_color()) - line = Line( - a.get_corner(DOWN+LEFT), - b.get_corner(UP+RIGHT), - color = MAROON_B - ) - midpoint = Dot(self.dots.get_center(), color = RED) - randy = Randolph(mode = "pondering") - randy.next_to(self.loop, LEFT, aligned_edge = DOWN) - randy.look_at(b) - self.add(randy) - - for label in a_label, b_label: - self.play( - Write(label, run_time = 1), - randy.look_at, label - ) - self.play(Blink(randy)) - self.wait() - swappers = [a, b, a_label, b_label] - for mob in swappers: - mob.save_state() - self.play( - a.move_to, b, - b.move_to, a, - a_label.next_to, b, LEFT, - b_label.next_to, a, RIGHT, - randy.look_at, a, - path_arc = np.pi - ) - self.play(ShowCreation(midpoint)) - self.play(ShowCreation(line), Animation(midpoint)) - self.play(randy.change_mode, "erm", randy.look_at, b) - self.play( - randy.look_at, a, - *[m.restore for m in swappers], - path_arc = -np.pi - ) - self.play(Blink(randy)) - self.wait() - -class NotHelpful(Scene): - def construct(self): - morty = Mortimer() - morty.next_to(ORIGIN, DOWN) - bubble = morty.get_bubble(SpeechBubble, width = 4, height = 3) - bubble.write("Not helpful!") - - self.add(morty) - self.play( - FadeIn(bubble), - FadeIn(bubble.content), - morty.change_mode, "angry", - morty.look, OUT - ) - self.play(Blink(morty)) - self.wait() - -class FoldUnitSquare(EdgesOfSquare): - def construct(self): - self.add_triangles() - self.add_arrows() - self.show_points_to_glue() - self.perform_fold() - self.show_singleton_pairs() - self.ask_about_gluing() - self.clarify_edge_gluing() - - def add_triangles(self): - square = self.add_square() - triangles = VGroup(*[ - Polygon(*[square.get_corner(vect) for vect in vects]) - for vects in [ - (DOWN+LEFT, UP+RIGHT, UP+LEFT), - (DOWN+LEFT, UP+RIGHT, DOWN+RIGHT), - ] - ]) - triangles.set_stroke(width = 0) - triangles.set_fill( - color = square.get_color(), - opacity = square.get_fill_opacity() - ) - self.remove(square) - self.square = square - self.add(triangles) - self.triangles = triangles - - def add_arrows(self): - start_arrows = VGroup() - end_arrows = VGroup() - colors = MAROON_B, BLUE - for a in 0, 1: - for color in colors: - b_range = np.linspace(0, 1, 4) - for b1, b2 in zip(b_range, b_range[1:]): - arrow = Arrow( - self.get_point_from_coords(a, b1), - self.get_point_from_coords(a, b2), - buff = 0, - color = color - ) - if color is BLUE: - arrow.rotate( - -np.pi/2, - about_point = self.square.get_center() - ) - if (a is 0): - start_arrows.add(arrow) - else: - end_arrows.add(arrow) - self.add(start_arrows, end_arrows) - self.start_arrows = start_arrows - self.end_arrows = VGroup(*list(end_arrows[3:])+list(end_arrows[:3])).copy() - self.end_arrows.set_color( - color_gradient([MAROON_B, BLUE], 3)[1] - ) - - def show_points_to_glue(self): - colors = YELLOW, MAROON_B, PINK - pairs = [(0.2, 0.3), (0.5, 0.7), (0.25, 0.6)] - unit = self.square.get_width() - - start_dots = VGroup() - end_dots = VGroup() - for (x, y), color in zip(pairs, colors): - old_x_line, old_y_line = None, None - for (a, b) in (x, y), (y, x): - point = self.get_point_from_coords(a, b) - dot = Dot(point) - dot.set_color(color) - if color == colors[-1]: - s = "(x, y)" if a < b else "(y, x)" - label = TexMobject(s) - else: - label = TexMobject("(%.01f, %.01f)"%(a, b)) - vect = UP+RIGHT if a < b else DOWN+RIGHT - label.next_to(dot, vect, buff = SMALL_BUFF) - - self.play(*list(map(FadeIn, [dot, label]))) - x_line = Line(point+a*unit*LEFT, point) - y_line = Line(point+b*unit*DOWN, point) - x_line.set_color(GREEN) - y_line.set_color(RED) - if old_x_line is None: - self.play(ShowCreation(x_line), Animation(dot)) - self.play(ShowCreation(y_line), Animation(dot)) - old_x_line, old_y_line = y_line, x_line - else: - self.play(Transform(old_x_line, x_line), Animation(dot)) - self.play(Transform(old_y_line, y_line), Animation(dot)) - self.remove(old_x_line, old_y_line) - self.add(x_line, y_line, dot) - self.wait(2) - self.play(FadeOut(label)) - if a < b: - start_dots.add(dot) - else: - end_dots.add(dot) - self.play(*list(map(FadeOut, [x_line, y_line]))) - self.start_dots, self.end_dots = start_dots, end_dots - - def perform_fold(self): - diag_line = DashedLine( - self.square.get_corner(DOWN+LEFT), - self.square.get_corner(UP+RIGHT), - color = RED - ) - - self.play(ShowCreation(diag_line)) - self.wait() - self.play( - Transform(*self.triangles), - Transform(self.start_dots, self.end_dots), - Transform(self.start_arrows, self.end_arrows), - ) - self.wait() - self.diag_line = diag_line - - def show_singleton_pairs(self): - xs = [0.7, 0.4, 0.5] - old_label = None - old_dot = None - for x in xs: - point = self.get_point_from_coords(x, x) - dot = Dot(point) - if x is xs[-1]: - label = TexMobject("(x, x)") - else: - label = TexMobject("(%.1f, %.1f)"%(x, x)) - label.next_to(dot, UP+LEFT, buff = SMALL_BUFF) - VGroup(dot, label).set_color(RED) - if old_label is None: - self.play( - ShowCreation(dot), - Write(label) - ) - old_label = label - old_dot = dot - else: - self.play( - Transform(old_dot, dot), - Transform(old_label, label), - ) - self.wait() - #Some strange bug necesitating this - self.remove(old_label) - self.add(label) - - def ask_about_gluing(self): - keepers = VGroup( - self.triangles[0], - self.start_arrows, - self.diag_line - ).copy() - faders = VGroup(*self.get_mobjects()) - randy = Randolph() - randy.next_to(ORIGIN, DOWN) - bubble = randy.get_bubble(height = 4, width = 6) - bubble.write("How do you \\\\ glue those arrows?") - - self.play( - FadeOut(faders), - Animation(keepers) - ) - self.play( - keepers.scale, 0.6, - keepers.shift, 4*RIGHT + UP, - FadeIn(randy) - ) - self.play( - randy.change_mode, "pondering", - randy.look_at, keepers, - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(Blink(randy)) - self.wait() - self.randy = randy - - def clarify_edge_gluing(self): - dots = VGroup(*[ - Dot(self.get_point_from_coords(*coords), radius = 0.1) - for coords in [ - (0.1, 0), - (1, 0.1), - (0.9, 0), - (1, 0.9), - ] - ]) - dots.scale(0.6) - dots.shift(4*RIGHT + UP) - for dot in dots[:2]: - dot.set_color(YELLOW) - self.play( - ShowCreation(dot), - self.randy.look_at, dot - ) - self.wait() - for dot in dots[2:]: - dot.set_color(MAROON_B) - self.play( - ShowCreation(dot), - self.randy.look_at, dot - ) - self.play(Blink(self.randy)) - self.wait() - - def get_point_from_coords(self, x, y): - left, right, bottom, top = [ - self.triangles.get_edge_center(vect) - for vect in (LEFT, RIGHT, DOWN, UP) - ] - x_point = interpolate(left, right, x) - y_point = interpolate(bottom, top, y) - return x_point[0]*RIGHT + y_point[1]*UP - -class PrepareForMobiusStrip(Scene): - def construct(self): - self.add_triangles() - self.perform_cut() - self.rearrange_pieces() - - def add_triangles(self): - triangles = VGroup( - Polygon( - DOWN+LEFT, - DOWN+RIGHT, - ORIGIN, - ), - Polygon( - DOWN+RIGHT, - UP+RIGHT, - ORIGIN, - ), - ) - triangles.set_fill(color = BLUE, opacity = 0.6) - triangles.set_stroke(width = 0) - triangles.center() - triangles.scale(2) - arrows_color = color_gradient([PINK, BLUE], 3)[1] - for tri in triangles: - anchors = tri.get_anchors_and_handles()[0] - alpha_range = np.linspace(0, 1, 4) - arrows = VGroup(*[ - Arrow( - interpolate(anchors[0], anchors[1], a), - interpolate(anchors[0], anchors[1], b), - buff = 0, - color = arrows_color - ) - for a, b in zip(alpha_range, alpha_range[1:]) - ]) - tri.original_arrows = arrows - tri.add(arrows) - i, j, k = (0, 2, 1) if tri is triangles[0] else (1, 2, 0) - dashed_line = DashedLine( - anchors[i], anchors[j], - color = RED - ) - tri.add(dashed_line) - - #Add but don't draw cut_arrows - start, end = anchors[j], anchors[k] - cut_arrows = VGroup(*[ - Arrow( - interpolate(start, end, a), - interpolate(start, end, b), - buff = 0, - color = YELLOW - ) - for a, b in zip(alpha_range, alpha_range[1:]) - ]) - tri.cut_arrows = cut_arrows - self.add(triangles) - self.triangles = triangles - - def perform_cut(self): - tri1, tri2 = self.triangles - - - self.play(ShowCreation(tri1.cut_arrows)) - for tri in self.triangles: - tri.add(tri.cut_arrows) - self.wait() - self.play( - tri1.shift, (DOWN+LEFT)/2., - tri2.shift, (UP+RIGHT)/2., - ) - self.wait() - - def rearrange_pieces(self): - tri1, tri2 = self.triangles - self.play( - tri1.rotate, np.pi, UP+RIGHT, - tri1.next_to, ORIGIN, RIGHT, - tri2.next_to, ORIGIN, LEFT, - ) - self.wait() - self.play(*[ - ApplyMethod(tri.shift, tri.points[0][0]*LEFT) - for tri in self.triangles - ]) - self.play(*[ - FadeOut(tri.original_arrows) - for tri in self.triangles - ]) - for tri in self.triangles: - tri.remove(tri.original_arrows) - self.wait() - # self.play(*[ - # ApplyMethod(tri.rotate, -np.pi/4) - # for tri in self.triangles - # ]) - # self.wait() - -class FoldToMobius(Scene): - def construct(self): - pass - -class MobiusPlaneAnalogy(ClosedLoopScene): - def construct(self): - top_arrow = Arrow(LEFT, RIGHT) - top_arrow.to_edge(UP, buff = 2*LARGE_BUFF) - low_arrow = Arrow(LEFT, RIGHT).shift(2*DOWN) - self.loop.scale(0.5) - self.loop.next_to(top_arrow, RIGHT) - self.loop.shift_onto_screen() - self.add_dots_at_alphas(0.3, 0.5) - self.dots.set_color(PURPLE_B) - - plane = NumberPlane() - plane.scale(0.3).next_to(low_arrow, LEFT) - number_line = NumberLine() - number_line.scale(0.3) - number_line.next_to(low_arrow, RIGHT) - number_line.add( - Dot(number_line.number_to_point(3), color = GREEN), - Dot(number_line.number_to_point(-2), color = RED), - ) - - self.wait() - self.play(ShowCreation(top_arrow)) - self.wait() - self.play(ShowCreation(plane)) - self.play(ShowCreation(low_arrow)) - self.play(ShowCreation(number_line)) - self.wait() - -class DrawRightArrow(Scene): - CONFIG = { - "tex" : "\\Rightarrow" - } - def construct(self): - arrow = TexMobject(self.tex) - arrow.scale(4) - self.play(Write(arrow)) - self.wait() - -class DrawLeftrightArrow(DrawRightArrow): - CONFIG = { - "tex" : "\\Leftrightarrow" - } - -class MobiusToPairToSurface(ClosedLoopScene): - def construct(self): - self.loop.scale(0.5) - self.loop.next_to(ORIGIN, RIGHT) - self.loop.to_edge(UP) - self.add_dots_at_alphas(0.4, 0.6) - self.dots.set_color(MAROON_B) - self.add_connecting_lines() - strip_dot = Dot().next_to(self.loop, LEFT, buff = 2*LARGE_BUFF) - surface_dot = Dot().next_to(self.loop, DOWN, buff = 2*LARGE_BUFF) - - top_arrow = Arrow(strip_dot, self.loop) - right_arrow = Arrow(self.loop, surface_dot) - diag_arrow = Arrow(strip_dot, surface_dot) - - randy = self.randy = Randolph(mode = "pondering") - randy.next_to(ORIGIN, DOWN+LEFT) - - self.look_at(strip_dot) - self.play( - ShowCreation(top_arrow), - randy.look_at, self.loop - ) - self.wait() - self.look_at(strip_dot, surface_dot) - self.play(ShowCreation(diag_arrow)) - self.play(Blink(randy)) - self.look_at(strip_dot, self.loop) - self.wait() - self.play( - ShowCreation(right_arrow), - randy.look_at, surface_dot - ) - self.play(Blink(randy)) - self.play(randy.change_mode, "happy") - self.play(Blink(randy)) - self.wait() - - - def look_at(self, *things): - for thing in things: - self.play(self.randy.look_at, thing) - -class MapMobiusStripOntoSurface(Scene): - def construct(self): - pass - -class StripMustIntersectItself(TeacherStudentsScene): - def construct(self): - self.teacher_says( - """ - The strip must - intersect itself - during this process - """, - width = 4 - ) - dot = Dot(2*UP + 4*LEFT) - for student in self.get_students(): - student.generate_target() - student.target.change_mode("pondering") - student.target.look_at(dot) - self.play(*list(map(MoveToTarget, self.get_students()))) - self.random_blink(4) - -class PairOfMobiusPointsLandOnEachother(Scene): - def construct(self): - pass - -class ThatsTheProof(TeacherStudentsScene): - def construct(self): - self.teacher_says( - """ - Bada boom - bada bang! - """, - target_mode = "hooray", - width = 4 - ) - self.change_student_modes(*["hooray"]*3) - self.random_blink() - self.change_student_modes( - "confused", "sassy", "erm" - ) - self.teacher_says( - """ - If you trust - the mobius strip - fact... - """, - target_mode = "guilty", - width = 4, - ) - self.random_blink() - -class TryItYourself(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - It's actually an - edifying exercise. - """) - self.random_blink() - self.change_student_modes(*["pondering"]*3) - self.random_blink(2) - - pi = self.get_students()[1] - bubble = pi.get_bubble( - "thought", - width = 4, height = 4, - direction = RIGHT - ) - bubble.set_fill(BLACK, opacity = 1) - bubble.write("Orientation seem\\\\ to matter...") - self.play( - FadeIn(bubble), - Write(bubble.content) - ) - self.random_blink(3) - -class OneMoreAnimation(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - One more animation, - but first... - """) - self.change_student_modes(*["happy"]*3) - self.random_blink() - -class PatreonThanks(Scene): - CONFIG = { - "specific_patrons" : [ - "Loo Yu Jun", - "Tom", - "Othman Alikhan", - "Juan Batiz-Benet", - "Markus Persson", - "Joseph John Cox", - "Achille Brighton", - "Kirk Werklund", - "Luc Ritchie", - "Ripta Pasay", - "PatrickJMT ", - "Felipe Diniz", - ] - } - def construct(self): - morty = Mortimer() - morty.next_to(ORIGIN, DOWN) - - n_patrons = len(self.specific_patrons) - special_thanks = TextMobject("Special thanks to:") - special_thanks.set_color(YELLOW) - special_thanks.shift(2*UP) - - left_patrons = VGroup(*list(map(TextMobject, - self.specific_patrons[:n_patrons/2] - ))) - right_patrons = VGroup(*list(map(TextMobject, - self.specific_patrons[n_patrons/2:] - ))) - for patrons, vect in (left_patrons, LEFT), (right_patrons, RIGHT): - patrons.arrange(DOWN, aligned_edge = LEFT) - patrons.next_to(special_thanks, DOWN) - patrons.to_edge(vect, buff = LARGE_BUFF) - - self.play(morty.change_mode, "gracious") - self.play(Write(special_thanks, run_time = 1)) - self.play( - Write(left_patrons), - morty.look_at, left_patrons - ) - self.play( - Write(right_patrons), - morty.look_at, right_patrons - ) - self.play(Blink(morty)) - for patrons in left_patrons, right_patrons: - for index in 0, -1: - self.play(morty.look_at, patrons[index]) - self.wait() - -class CreditTWo(Scene): - def construct(self): - morty = Mortimer() - morty.next_to(ORIGIN, DOWN) - morty.to_edge(RIGHT) - - brother = PiCreature(color = GOLD_E) - brother.next_to(morty, LEFT) - brother.look_at(morty.eyes) - - headphones = Headphones(height = 1) - headphones.move_to(morty.eyes, aligned_edge = DOWN) - headphones.shift(0.1*DOWN) - - url = TextMobject("www.audible.com/3b1b") - url.to_corner(UP+RIGHT, buff = LARGE_BUFF) - - self.add(morty) - self.play(Blink(morty)) - self.play( - FadeIn(headphones), - Write(url), - Animation(morty) - ) - self.play(morty.change_mode, "happy") - self.wait() - self.play(Blink(morty)) - self.wait() - self.play( - FadeIn(brother), - morty.look_at, brother.eyes - ) - self.play(brother.change_mode, "surprised") - self.play(Blink(brother)) - self.wait() - self.play( - morty.look, LEFT, - brother.change_mode, "happy", - brother.look, LEFT - ) - self.play(Blink(morty)) - self.wait() - -class CreditThree(Scene): - def construct(self): - logo_dot = Dot().to_edge(UP).shift(3*RIGHT) - randy = Randolph() - randy.next_to(ORIGIN, DOWN) - randy.to_edge(LEFT) - randy.look(RIGHT) - self.add(randy) - bubble = randy.get_bubble(width = 2, height = 2) - - domains = VGroup(*list(map(TextMobject, [ - "visualnumbertheory.com", - "buymywidgets.com", - "learnwhatilearn.com", - ]))) - domains.arrange(DOWN, aligned_edge = LEFT) - domains.next_to(randy, UP, buff = LARGE_BUFF) - domains.shift_onto_screen() - - promo_code = TextMobject("Promo code: TOPOLOGY") - promo_code.shift(3*RIGHT) - self.add(promo_code) - whois = TextMobject("Free WHOIS privacy") - whois.next_to(promo_code, DOWN, buff = LARGE_BUFF) - - self.play(Blink(randy)) - self.play( - randy.change_mode, "happy", - randy.look_at, logo_dot - ) - self.wait() - self.play( - ShowCreation(bubble), - randy.change_mode, "pondering", - run_time = 2 - ) - self.play(Blink(randy)) - self.play( - Transform(bubble, VectorizedPoint(randy.get_corner(UP+LEFT))), - randy.change_mode, "sad" - ) - self.wait() - self.play( - Write(domains, run_time = 5), - randy.look_at, domains - ) - self.wait() - self.play(Blink(randy)) - self.play( - randy.change_mode, "hooray", - randy.look_at, logo_dot, - FadeOut(domains) - ) - self.wait() - self.play( - Write(whois), - randy.change_mode, "confused", - randy.look_at, whois - ) - self.wait(2) - self.play(randy.change_mode, "sassy") - self.wait(2) - self.play( - randy.change_mode, "happy", - randy.look_at, logo_dot - ) - self.play(Blink(randy)) - self.wait() - - -class ShiftingLoopPairSurface(Scene): - def construct(self): - pass - -class ThumbnailImage(ClosedLoopScene): - def construct(self): - self.add_rect_dots(square = True) - for dot in self.dots: - dot.scale_in_place(1.5) - self.add_connecting_lines(cyclic = True) - self.connecting_lines.set_stroke(width = 10) - self.loop.add(self.connecting_lines, self.dots) - - title = TextMobject("Unsolved") - title.scale(2.5) - title.to_edge(UP) - title.set_color_by_gradient(YELLOW, MAROON_B) - self.add(title) - self.loop.next_to(title, DOWN, buff = MED_SMALL_BUFF) - self.loop.shift(2*LEFT) - - - - - - - - - diff --git a/from_3b1b/old/windmill.py b/from_3b1b/old/windmill.py deleted file mode 100644 index 06cff945..00000000 --- a/from_3b1b/old/windmill.py +++ /dev/null @@ -1,4134 +0,0 @@ -from manimlib.imports import * -import json - - -class IntroduceIMO(Scene): - CONFIG = { - "num_countries": 130, - "use_real_images": True, - # "use_real_images": False, - "include_labels": False, - "camera_config": {"background_color": DARKER_GREY}, - "random_seed": 6, - "year": 2019, - "n_flag_rows": 10, - "reorganize_students": True, - } - - def construct(self): - self.add_title() - self.show_flags() - self.show_students() - self.move_title() - self.isolate_usa() - - def add_title(self): - title = TextMobject( - "International ", "Mathematical ", "Olympiad", - ) - title.scale(1.25) - logo = ImageMobject("imo_logo") - logo.set_height(1) - - group = Group(logo, title) - group.arrange(RIGHT) - group.to_edge(UP, buff=MED_SMALL_BUFF) - - self.add(title, logo) - self.title = title - self.logo = logo - - def show_flags(self): - flags = self.get_flags() - flags.set_height(6) - flags.to_edge(DOWN) - random_flags = Group(*flags) - random_flags.shuffle() - - self.play( - LaggedStartMap( - FadeInFromDown, random_flags, - run_time=2, - lag_ratio=0.03, - ) - ) - self.remove(random_flags) - self.add(flags) - self.wait() - - self.flags = flags - - def show_students(self): - flags = self.flags - - student_groups = VGroup() - all_students = VGroup() - for flag in flags: - group = self.get_students(flag) - student_groups.add(group) - for student in group: - student.preimage = VectorizedPoint() - student.preimage.move_to(flag) - all_students.add(student) - all_students.shuffle() - - student_groups.generate_target() - student_groups.target.arrange_in_grid( - n_rows=self.n_flag_rows, - buff=SMALL_BUFF, - ) - # student_groups.target[-9:].align_to(student_groups.target[0], LEFT) - student_groups.target.match_height(flags) - student_groups.target.match_y(flags) - student_groups.target.to_edge(RIGHT, buff=0.25) - - self.play(LaggedStart( - *[ - ReplacementTransform( - student.preimage, student - ) - for student in all_students - ], - run_time=2, - lag_ratio=0.2, - )) - self.wait() - if self.reorganize_students: - self.play( - MoveToTarget(student_groups), - flags.space_out_submobjects, 0.75, - flags.to_edge, LEFT, MED_SMALL_BUFF, - ) - self.wait() - - self.student_groups = student_groups - - def move_title(self): - title = self.title - logo = self.logo - - new_title = TextMobject("IMO") - new_title.match_height(title) - - logo.generate_target() - group = Group(logo.target, new_title) - group.arrange(RIGHT, buff=SMALL_BUFF) - group.match_y(title) - group.match_x(self.student_groups, UP) - - title.generate_target() - for word, letter in zip(title.target, new_title[0]): - for nl in word: - nl.move_to(letter) - word.set_opacity(0) - word[0].set_opacity(1) - word[0].become(letter) - - self.play( - MoveToTarget(title), - MoveToTarget(logo), - ) - self.wait() - - def isolate_usa(self): - flags = self.flags - student_groups = self.student_groups - - us_flag = flags[0] - random_flags = Group(*flags[1:]) - random_flags.shuffle() - - old_height = us_flag.get_height() - us_flag.label.set_width(0.8 * us_flag.get_width()) - us_flag.label.next_to( - us_flag, DOWN, - buff=0.2 * us_flag.get_height(), - ) - us_flag.label.set_opacity(0) - us_flag.add(us_flag.label) - us_flag.generate_target() - us_flag.target.scale(1 / old_height) - us_flag.target.to_corner(UL) - us_flag.target[1].set_opacity(1) - - self.remove(us_flag) - self.play( - LaggedStart( - *[ - FadeOutAndShift(flag, DOWN) - for flag in random_flags - ], - lag_ratio=0.05, - run_time=1.5 - ), - MoveToTarget(us_flag), - student_groups[1:].fade, 0.9, - ) - self.wait() - - # - def get_students(self, flag): - dots = VGroup(*[Dot() for x in range(6)]) - dots.arrange_in_grid(n_cols=2, buff=SMALL_BUFF) - dots.match_height(flag) - dots.next_to(flag, RIGHT, SMALL_BUFF) - dots[flag.n_students:].set_opacity(0) - - if isinstance(flag, ImageMobject): - rgba = random.choice(random.choice(flag.pixel_array)) - if np.all(rgba < 100): - rgba = interpolate(rgba, 256 * np.ones(len(rgba)), 0.5) - color = rgba_to_color(rgba / 256) - else: - color = random_bright_color() - dots.set_color(color) - dots.set_stroke(WHITE, 1, background=True) - - return dots - - def get_flags(self): - year = self.year - file = "{}_imo_countries.json".format(year) - with open(os.path.join("assets", file)) as fp: - countries_with_counts = json.load(fp) - with open(os.path.join("assets", "country_codes.json")) as fp: - country_codes = json.load(fp) - country_to_code2 = dict([ - (country.lower(), code2.lower()) - for country, code2, code3 in country_codes - ]) - country_to_code3 = dict([ - (country.lower(), code3.lower()) - for country, code2, code3 in country_codes - ]) - - images = Group() - for country, count in countries_with_counts: - country = country.lower() - - alt_names = [ - ("united states of america", "united states"), - ("people's republic of china", "china"), - ("macau", "macao"), - ("syria", "syrian arab republic"), - ("north macedonia", "macedonia, the former yugoslav republic of"), - ("tanzania", "united republic of tanzania"), - ("vietnam", "viet nam"), - ("ivory coast", "cote d'ivoire") - ] - for n1, n2 in alt_names: - if country == n1: - country = n2 - - if country not in country_to_code2: - print("Can't find {}".format(country)) - continue - short_code = country_to_code2[country] - try: - image = ImageMobject(os.path.join("flags", short_code)) - image.set_width(1) - label = VGroup(*[ - TextMobject(l) - for l in country_to_code3[country].upper() - ]) - label.arrange(RIGHT, buff=0.05, aligned_edge=DOWN) - label.set_height(0.25) - if not self.use_real_images: - rect = SurroundingRectangle(image, buff=0) - rect.set_stroke(WHITE, 1) - image = rect - image.label = label - image.n_students = count - images.add(image) - except OSError: - print("Failed on {}".format(country)) - - n_rows = self.n_flag_rows - images.arrange_in_grid( - n_rows=n_rows, - buff=1.25, - ) - images[-(len(images) % n_rows):].align_to(images[0], LEFT) - sf = 1.7 - images.stretch(sf, 0) - for i, image in enumerate(images): - image.set_height(1) - image.stretch(1 / sf, 0) - image.label.next_to(image, DOWN, SMALL_BUFF) - if self.include_labels: - image.add(image.label) - - images.set_width(FRAME_WIDTH - 1) - if images.get_height() > FRAME_HEIGHT - 1: - images.set_height(FRAME_HEIGHT - 1) - images.center() - return images - - -class ShowTinyTao(IntroduceIMO): - CONFIG = { - "reorganize_students": False, - } - - def construct(self): - self.force_skipping() - self.add_title() - self.show_flags() - self.show_students() - self.revert_to_original_skipping_status() - - image = ImageMobject("TerryTaoIMO") - label = TextMobject("Terence Tao at 12") - label.match_width(image) - label.next_to(image, DOWN, SMALL_BUFF) - image.add(label) - - ausie = self.flags[17] - image.replace(ausie) - image.set_opacity(0) - - self.play(image.set_opacity, 1) - self.play( - image.set_height, 5, - image.to_corner, DR, {"buff": MED_SMALL_BUFF}, - ) - self.wait() - self.play(FadeOut(image)) - - -class FootnoteToIMOIntro(Scene): - def construct(self): - words = TextMobject("$^*$Based on data from 2019 test") - self.play(FadeInFrom(words, UP)) - self.wait() - - -class ShowTest(Scene): - def construct(self): - self.introduce_test() - - def introduce_test(self): - test = self.get_test() - test.generate_target() - test.target.to_edge(UP) - - # Time label - time_labels = VGroup( - TextMobject("Day 1", ": 4.5 hours"), - TextMobject("Day 2", ": 4.5 hours"), - ) - time_labels.scale(1.5) - day_labels = VGroup() - hour_labels = VGroup() - for label, page in zip(time_labels, test.target): - label.next_to(page, DOWN) - label[0].save_state() - label[0].next_to(page, DOWN) - label[1][1:].set_color(YELLOW) - day_labels.add(label[0]) - hour_labels.add(label[1]) - - # Problem descriptions - problem_rects = self.get_problem_rects(test.target[0]) - proof_words = VGroup() - for rect in problem_rects: - word = TextMobject("Proof") - word.scale(2) - word.next_to(rect, RIGHT, buff=3) - word.set_color(BLUE) - proof_words.add(word) - - proof_words.space_out_submobjects(2) - - proof_arrows = VGroup() - for rect, word in zip(problem_rects, proof_words): - arrow = Arrow(word.get_left(), rect.get_right()) - arrow.match_color(word) - proof_arrows.add(arrow) - - scores = VGroup() - for word in proof_words: - score = VGroup(Integer(0), TexMobject("/"), Integer(7)) - score.arrange(RIGHT, buff=SMALL_BUFF) - score.scale(2) - score.move_to(word) - score.to_edge(RIGHT) - scores.add(score) - score[0].add_updater(lambda m: m.set_color( - interpolate_color(RED, GREEN, m.get_value() / 7) - )) - - # Introduce test - self.play( - LaggedStart( - FadeInFrom(test[0], 2 * RIGHT), - FadeInFrom(test[1], 2 * LEFT), - lag_ratio=0.3, - ) - ) - self.wait() - self.play( - MoveToTarget(test, lag_ratio=0.2), - FadeInFrom(day_labels, UP, lag_ratio=0.2), - ) - self.wait() - self.play( - *map(Restore, day_labels), - FadeInFrom(hour_labels, LEFT), - ) - self.wait() - - # Discuss problems - self.play( - FadeOut(test[1]), - FadeOut(time_labels[1]), - LaggedStartMap(ShowCreation, problem_rects), - run_time=1, - ) - self.play( - LaggedStart(*[ - FadeInFrom(word, LEFT) - for word in proof_words - ]), - LaggedStart(*[ - GrowArrow(arrow) - for arrow in proof_arrows - ]), - ) - self.wait() - self.play(FadeIn(scores)) - self.play( - LaggedStart(*[ - ChangeDecimalToValue(score[0], 7) - for score in scores - ], lag_ratio=0.2, rate_func=rush_into) - ) - self.wait() - - self.scores = scores - self.proof_arrows = proof_arrows - self.proof_words = proof_words - self.problem_rects = problem_rects - self.test = test - self.time_labels = time_labels - - def get_test(self): - group = Group( - ImageMobject("imo_2011_p1"), - ImageMobject("imo_2011_p2"), - ) - group.set_height(6) - group.arrange(RIGHT, buff=LARGE_BUFF) - for page in group: - rect = SurroundingRectangle(page, buff=0.01) - rect.set_stroke(WHITE, 1) - page.add(rect) - # page.pixel_array[:, :, :3] = 255 - page.pixel_array[:, :, :3] - return group - - def get_problem_rects(self, page): - pw = page.get_width() - rects = VGroup(*[Rectangle() for x in range(3)]) - rects.set_stroke(width=2) - rects.set_color_by_gradient([BLUE_E, BLUE_C, BLUE_D]) - - rects.set_width(pw * 0.75) - for factor, rect in zip([0.095, 0.16, 0.1], rects): - rect.set_height(factor * pw, stretch=True) - rects.arrange(DOWN, buff=0.08) - rects.move_to(page) - rects.shift(0.09 * pw * DOWN) - return rects - - -class USProcessAlt(IntroduceIMO): - CONFIG = { - } - - def construct(self): - self.add_flag_and_label() - self.show_tests() - self.show_imo() - - def add_flag_and_label(self): - flag = ImageMobject("flags/us") - flag.set_height(1) - flag.to_corner(UL) - label = VGroup(*map(TextMobject, "USA")) - label.arrange(RIGHT, buff=0.05, aligned_edge=DOWN) - label.set_width(0.8 * flag.get_width()) - label.next_to(flag, DOWN, buff=0.2 * flag.get_height()) - - self.add(flag, label) - - self.flag = flag - - def show_tests(self): - tests = VGroup( - self.get_test( - ["American ", "Mathematics ", "Contest"], - n_questions=25, - time_string="75 minutes", - hours=1.25, - n_students=250000, - ), - self.get_test( - ["American ", "Invitational ", "Math ", "Exam"], - n_questions=15, - time_string="3 hours", - hours=3, - n_students=12000, - ), - self.get_test( - ["U", "S", "A ", "Math ", "Olympiad"], - n_questions=6, - time_string="$2 \\times 4.5$ hours", - hours=4.5, - n_students=500, - ), - self.get_test( - ["Mathematical ", "Olympiad ", "Program"], - n_questions=None, - time_string="3 weeks", - hours=None, - n_students=60 - ) - ) - amc, aime, usamo, mop = tests - arrows = VGroup() - - amc.to_corner(UR) - top_point = amc.get_top() - last_arrow = VectorizedPoint() - last_arrow.to_corner(DL) - next_anims = [] - - self.force_skipping() - for test in tests: - test.move_to(top_point, UP) - test.shift_onto_screen() - self.play( - Write(test.name), - *next_anims, - run_time=1, - ) - self.wait() - self.animate_name_abbreviation(test) - self.wait() - - if isinstance(test.nq_label[0], Integer): - int_mob = test.nq_label[0] - n = int_mob.get_value() - int_mob.set_value(0) - self.play( - ChangeDecimalToValue(int_mob, n), - FadeIn(test.nq_label[1:]) - ) - else: - self.play(FadeIn(test.nq_label)) - - self.play( - FadeIn(test.t_label) - ) - self.wait() - - test.generate_target() - test.target.scale(0.575) - test.target.next_to(last_arrow, RIGHT, buff=SMALL_BUFF) - test.target.shift_onto_screen() - - next_anims = [ - MoveToTarget(test), - GrowArrow(last_arrow), - ] - last_arrow = Vector(0.5 * RIGHT) - last_arrow.set_color(WHITE) - last_arrow.next_to(test.target, RIGHT, SMALL_BUFF) - arrows.add(last_arrow) - self.play(*next_anims) - - self.revert_to_original_skipping_status() - self.play( - LaggedStartMap( - FadeInFrom, tests, - lambda m: (m, LEFT), - ), - LaggedStartMap( - GrowArrow, arrows[:-1] - ), - lag_ratio=0.4, - ) - self.wait() - - self.tests = tests - - def show_imo(self): - tests = self.tests - logo = ImageMobject("imo_logo") - logo.set_height(1) - name = TextMobject("IMO") - name.scale(2) - group = Group(logo, name) - group.arrange(RIGHT) - group.to_corner(UR) - group.shift(2 * LEFT) - - students = VGroup(*[ - PiCreature() - for x in range(6) - ]) - students.arrange_in_grid(n_cols=3, buff=LARGE_BUFF) - students.set_height(2) - students.next_to(group, DOWN) - colors = it.cycle([RED, LIGHT_GREY, BLUE]) - for student, color in zip(students, colors): - student.set_color(color) - student.save_state() - student.move_to(tests[-1]) - student.fade(1) - - self.play( - FadeInFromDown(group), - LaggedStartMap( - Restore, students, - run_time=3, - lag_ratio=0.3, - ) - ) - self.play( - LaggedStart(*[ - ApplyMethod(student.change, "hooray") - for student in students - ]) - ) - for x in range(3): - self.play(Blink(random.choice(students))) - self.wait() - - # - def animate_name_abbreviation(self, test): - name = test.name - short_name = test.short_name - short_name.move_to(name, LEFT) - name.generate_target() - for p1, p2 in zip(name.target, short_name): - for letter in p1: - letter.move_to(p2[0]) - letter.set_opacity(0) - p1[0].set_opacity(1) - self.add(test.rect, test.name, test.ns_label) - self.play( - FadeIn(test.rect), - MoveToTarget(name), - FadeIn(test.ns_label), - ) - - test.remove(name) - test.add(short_name) - self.remove(name) - self.add(short_name) - - def get_test(self, name_parts, n_questions, time_string, hours, n_students): - T_COLOR = GREEN_B - Q_COLOR = YELLOW - - name = TextMobject(*name_parts) - short_name = TextMobject(*[np[0] for np in name_parts]) - if n_questions: - nq_label = VGroup( - Integer(n_questions), - TextMobject("questions") - ) - nq_label.arrange(RIGHT) - else: - nq_label = TextMobject("Lots of training") - nq_label.set_color(Q_COLOR) - - if time_string: - t_label = TextMobject(time_string) - t_label.set_color(T_COLOR) - else: - t_label = Integer(0).set_opacity(0) - - clock = Clock() - clock.hour_hand.set_opacity(0) - clock.minute_hand.set_opacity(0) - clock.set_stroke(WHITE, 2) - if hours: - sector = Sector( - start_angle=TAU / 4, - angle=-TAU * (hours / 12), - outer_radius=clock.get_width() / 2, - arc_center=clock.get_center() - ) - sector.set_fill(T_COLOR, 0.5) - sector.set_stroke(T_COLOR, 2) - clock.add(sector) - if hours == 4.5: - plus = TexMobject("+").scale(2) - plus.next_to(clock, RIGHT) - clock_copy = clock.copy() - clock_copy.next_to(plus, RIGHT) - clock.add(plus, clock_copy) - else: - clock.set_opacity(0) - clock.set_height(1) - clock.next_to(t_label, RIGHT, buff=MED_LARGE_BUFF) - t_label.add(clock) - - ns_label = TextMobject("$\\sim${:,} students".format(n_students)) - - result = VGroup( - name, - nq_label, - t_label, - ns_label, - ) - result.arrange( - DOWN, - buff=MED_LARGE_BUFF, - aligned_edge=LEFT, - ) - rect = SurroundingRectangle(result, buff=MED_SMALL_BUFF) - rect.set_width( - result[1:].get_width() + MED_LARGE_BUFF, - about_edge=LEFT, - stretch=True, - ) - rect.set_stroke(WHITE, 2) - rect.set_fill(BLACK, 1) - result.add_to_back(rect) - - result.name = name - result.short_name = short_name - result.nq_label = nq_label - result.t_label = t_label - result.ns_label = ns_label - result.rect = rect - result.clock = clock - - return result - - -class Describe2011IMO(IntroduceIMO): - CONFIG = { - "year": 2011, - "use_real_images": True, - "n_flag_rows": 10, - "student_data": [ - [1, "Lisa Sauermann", "de", [7, 7, 7, 7, 7, 7]], - [2, "Jeck Lim", "sg", [7, 5, 7, 7, 7, 7]], - [3, "Lin Chen", "cn", [7, 3, 7, 7, 7, 7]], - [4, "Jun Jie Joseph Kuan", "sg", [7, 7, 7, 7, 7, 1]], - [4, "David Yang", "us", [7, 7, 7, 7, 7, 1]], - [6, "Jie Jun Ang", "sg", [7, 7, 7, 7, 7, 0]], - [6, "Kensuke Yoshida", "jp", [7, 6, 7, 7, 7, 1]], - [6, "Raul Sarmiento", "pe", [7, 7, 7, 7, 7, 0]], - [6, "Nipun Pitimanaaree", "th", [7, 7, 7, 7, 7, 0]], - ], - } - - def construct(self): - self.add_title() - self.add_flags_and_students() - self.comment_on_primality() - self.show_top_three_scorers() - - def add_title(self): - year = TexMobject("2011") - logo = ImageMobject("imo_logo") - imo = TextMobject("IMO") - group = Group(year, logo, imo) - group.scale(1.25) - logo.set_height(1) - group.arrange(RIGHT) - group.to_corner(UR, buff=MED_SMALL_BUFF) - group.shift(LEFT) - - self.add(group) - self.play(FadeInFrom(year, RIGHT)) - - self.title = group - - def add_flags_and_students(self): - flags = self.get_flags() - flags.space_out_submobjects(0.8) - sf = 0.8 - flags.stretch(sf, 0) - for flag in flags: - flag.stretch(1 / sf, 0) - flags.set_height(5) - flags.to_corner(DL) - - student_groups = VGroup(*[ - self.get_students(flag) - for flag in flags - ]) - student_groups.arrange_in_grid( - n_rows=self.n_flag_rows, - buff=SMALL_BUFF, - ) - student_groups[-1].align_to(student_groups, LEFT) - student_groups.set_height(6) - student_groups.next_to(self.title, DOWN) - flags.align_to(student_groups, UP) - - all_students = VGroup(*it.chain(*[ - [ - student - for student in group - if student.get_fill_opacity() > 0 - ] - for group in student_groups - ])) - - # Counters - student_counter = VGroup( - Integer(0), - TextMobject("Participants"), - ) - student_counter.set = all_students - student_counter.next_to(self.title, LEFT, MED_LARGE_BUFF) - student_counter.right_edge = student_counter.get_right() - - def update_counter(counter): - counter[0].set_value(len(counter.set)) - counter.arrange(RIGHT) - counter[0].align_to(counter[1][0][0], DOWN) - counter.move_to(counter.right_edge, RIGHT) - - student_counter.add_updater(update_counter) - - flag_counter = VGroup( - Integer(0), - TextMobject("Countries") - ) - flag_counter.set = flags - flag_counter.next_to(student_counter, LEFT, buff=0.75) - flag_counter.align_to(student_counter[0], DOWN) - flag_counter.right_edge = flag_counter.get_right() - flag_counter.add_updater(update_counter) - - self.add(student_counter) - self.play( - ShowIncreasingSubsets(all_students), - run_time=3, - ) - self.wait() - self.add(flag_counter) - self.play( - ShowIncreasingSubsets(flags), - run_time=3, - ) - self.wait() - - self.student_counter = student_counter - self.flag_counter = flag_counter - self.all_students = all_students - self.student_groups = student_groups - self.flags = flags - - def comment_on_primality(self): - full_rect = FullScreenFadeRectangle(opacity=0.9) - numbers = VGroup( - self.title[0], - self.student_counter[0], - self.flag_counter[0], - ) - lines = VGroup(*[ - Line().match_width(number).next_to(number, DOWN, SMALL_BUFF) - for number in numbers - ]) - lines.set_stroke(TEAL, 2) - - randy = Randolph() - randy.to_corner(DL) - randy.look_at(numbers) - - words = VGroup(*[ - TextMobject("Prime").next_to(line, DOWN) - for line in reversed(lines) - ]) - words.match_color(lines) - - self.add(full_rect, numbers) - self.play( - FadeIn(full_rect), - randy.change, "sassy", - VFadeIn(randy), - ) - self.play( - ShowCreation(lines), - randy.change, "pondering", - ) - self.play(Blink(randy)) - self.play( - randy.change, "thinking", - LaggedStart(*[ - FadeInFrom(word, UP) - for word in words - ], run_time=3, lag_ratio=0.5) - ) - self.play(Blink(randy)) - self.play( - FadeOut(randy), - FadeOut(words), - ) - self.play(FadeOut(full_rect), FadeOut(lines)) - - def show_top_three_scorers(self): - student_groups = self.student_groups - all_students = self.all_students - flags = self.flags - student_counter = self.student_counter - flag_counter = self.flag_counter - - student = student_groups[10][0] - flag = flags[10] - - students_to_fade = VGroup(*filter( - lambda s: s is not student, - all_students - )) - flags_to_fade = Group(*filter( - lambda s: s is not flag, - flags - )) - - grid = self.get_score_grid() - grid.shift(3 * DOWN) - title_row = grid.rows[0] - top_row = grid.rows[1] - - self.play( - LaggedStartMap(FadeOutAndShiftDown, students_to_fade), - LaggedStartMap(FadeOutAndShiftDown, flags_to_fade), - ChangeDecimalToValue(student_counter[0], 1), - FadeOut(flag_counter), - run_time=2 - ) - student_counter[1][0][-1].fade(1) - self.play( - Write(top_row[0]), - ReplacementTransform(student, top_row[1][1]), - flag.replace, top_row[1][0], - ) - self.remove(flag) - self.add(top_row[1]) - self.play( - LaggedStartMap(FadeIn, title_row[2:]), - LaggedStartMap(FadeIn, top_row[2:]), - ) - self.wait() - self.play( - LaggedStart(*[ - FadeInFrom(row, UP) - for row in grid.rows[2:4] - ]), - LaggedStart(*[ - ShowCreation(line) - for line in grid.h_lines[:2] - ]), - lag_ratio=0.5, - ) - self.wait() - self.play( - ShowCreationThenFadeAround( - Group(title_row[3], grid.rows[3][3]), - ) - ) - self.wait() - student_counter.clear_updaters() - self.play( - FadeOutAndShift(self.title, UP), - FadeOutAndShift(student_counter, UP), - grid.rows[:4].shift, 3 * UP, - grid.h_lines[:3].shift, 3 * UP, - ) - remaining_rows = grid.rows[4:] - remaining_lines = grid.h_lines[3:] - Group(remaining_rows, remaining_lines).shift(3 * UP) - self.play( - LaggedStartMap( - FadeInFrom, remaining_rows, - lambda m: (m, UP), - ), - LaggedStartMap(ShowCreation, remaining_lines), - lag_ratio=0.3, - run_time=2, - ) - self.wait() - - def get_score_grid(self): - data = self.student_data - - ranks = VGroup(*[ - Integer(row[0]) - for row in data - ]) - - # Combine students with flags - students = VGroup(*[ - TextMobject(row[1]) - for row in data - ]) - flags = Group(*[ - ImageMobject("flags/{}.png".format(row[2])).set_height(0.3) - for row in data - ]) - students = Group(*[ - Group(flag.next_to(student, LEFT, buff=0.2), student) - for flag, student in zip(flags, students) - ]) - - score_rows = VGroup(*[ - VGroup(*map(Integer, row[3])) - for row in data - ]) - colors = color_gradient([RED, YELLOW, GREEN], 8) - for score_row in score_rows: - for score in score_row: - score.set_color(colors[score.get_value()]) - - titles = VGroup(*[ - VectorizedPoint(), - VectorizedPoint(), - *[ - TextMobject("P{}".format(i)) - for i in range(1, 7) - ] - ]) - titles.arrange(RIGHT, buff=MED_LARGE_BUFF) - titles[2:].shift(students.get_width() * RIGHT) - rows = Group(titles, *[ - Group(rank, student, *score_row) - for rank, flag, student, score_row in zip( - ranks, flags, students, score_rows - ) - ]) - rows.arrange(DOWN) - rows.to_edge(UP) - for row in rows: - for i, e1, e2 in zip(it.count(), titles, row): - if i < 2: - e2.align_to(e1, LEFT) - else: - e2.match_x(e1) - ranks.next_to(students, LEFT) - - h_lines = VGroup() - for r1, r2 in zip(rows[1:], rows[2:]): - line = Line() - line.set_stroke(WHITE, 0.5) - line.match_width(r2) - line.move_to(midpoint(r1.get_bottom(), r2.get_top())) - line.align_to(r2, LEFT) - h_lines.add(line) - - grid = Group(rows, h_lines) - grid.rows = rows - grid.h_lines = h_lines - - return grid - - -class AskWhatsOnTest(ShowTest, MovingCameraScene): - def construct(self): - self.force_skipping() - self.introduce_test() - self.revert_to_original_skipping_status() - - self.ask_about_questions() - - def ask_about_questions(self): - scores = self.scores - arrows = self.proof_arrows - proof_words = self.proof_words - - question = TextMobject("What kind \\\\ of problems?") - question.scale(1.5) - question.move_to(proof_words, LEFT) - - research = TextMobject("Research-lite") - research.scale(1.5) - research.move_to(question, LEFT) - research.shift(MED_SMALL_BUFF * RIGHT) - research.set_color(BLUE) - - arrows.generate_target() - for arrow in arrows.target: - end = arrow.get_end() - start = arrow.get_start() - arrow.put_start_and_end_on( - interpolate(question.get_left(), start, 0.1), - end - ) - - self.play( - FadeOut(scores), - FadeOut(proof_words), - MoveToTarget(arrows), - Write(question), - ) - self.wait() - self.play( - FadeInFrom(research, DOWN), - question.shift, 2 * UP, - ) - self.wait() - - # Experience - randy = Randolph(height=2) - randy.move_to(research.get_corner(UL), DL) - randy.shift(SMALL_BUFF * RIGHT) - clock = Clock() - clock.set_height(1) - clock.next_to(randy, UR) - - self.play( - FadeOut(question), - FadeIn(randy), - FadeInFromDown(clock), - ) - self.play( - randy.change, "pondering", - ClockPassesTime(clock, run_time=5, hours_passed=5), - ) - self.play( - ClockPassesTime(clock, run_time=2, hours_passed=2), - VFadeOut(clock), - Blink(randy), - VFadeOut(randy), - LaggedStartMap( - FadeOut, - VGroup( - research, - *arrows, - *self.problem_rects, - self.time_labels[0] - ) - ), - ) - - # Second part - big_rect = FullScreenFadeRectangle() - lil_rect = self.problem_rects[1].copy() - lil_rect.reverse_points() - big_rect.append_vectorized_mobject(lil_rect) - frame = self.camera_frame - frame.generate_target() - frame.target.scale(0.35) - frame.target.move_to(lil_rect) - - self.play( - FadeInFromDown(self.test[1]), - ) - self.wait() - self.play( - FadeIn(big_rect), - MoveToTarget(frame, run_time=6), - ) - self.wait() - - -class ReadQuestions(Scene): - def construct(self): - background = ImageMobject("AskWhatsOnTest_final_image") - background.set_height(FRAME_HEIGHT) - self.add(background) - - lines = SVGMobject("imo_2011_2_underline-01") - lines.set_width(FRAME_WIDTH - 1) - lines.move_to(0.1 * DOWN) - lines.set_stroke(TEAL, 3) - - clump_sizes = [1, 2, 3, 2, 1, 2] - partial_sums = list(np.cumsum(clump_sizes)) - clumps = VGroup(*[ - lines[i:j] - for i, j in zip( - [0] + partial_sums, - partial_sums, - ) - ]) - - faders = [] - for clump in clumps: - rects = VGroup() - for line in clump: - rect = Rectangle() - rect.set_stroke(width=0) - rect.set_fill(TEAL, 0.25) - rect.set_width(line.get_width() + SMALL_BUFF) - rect.set_height(0.35, stretch=True) - rect.move_to(line, DOWN) - rects.add(rect) - - self.play( - ShowCreation(clump, run_time=2), - FadeIn(rects), - *faders, - ) - self.wait() - faders = [ - FadeOut(clump), - FadeOut(rects), - ] - self.play(*faders) - self.wait() - - -# Windmill scenes - -class WindmillScene(Scene): - CONFIG = { - "dot_config": { - "fill_color": LIGHT_GREY, - "radius": 0.05, - "background_stroke_width": 2, - "background_stroke_color": BLACK, - }, - "windmill_style": { - "stroke_color": RED, - "stroke_width": 2, - "background_stroke_width": 3, - "background_stroke_color": BLACK, - }, - "windmill_length": 2 * FRAME_WIDTH, - "windmill_rotation_speed": 0.25, - # "windmill_rotation_speed": 0.5, - # "hit_sound": "pen_click.wav", - "hit_sound": "pen_click.wav", - "leave_shadows": False, - } - - def get_random_point_set(self, n_points=11, width=6, height=6): - return np.array([ - [ - -width / 2 + np.random.random() * width, - -height / 2 + np.random.random() * height, - 0 - ] - for n in range(n_points) - ]) - - def get_dots(self, points): - return VGroup(*[ - Dot(point, **self.dot_config) - for point in points - ]) - - def get_windmill(self, points, pivot=None, angle=TAU / 4): - line = Line(LEFT, RIGHT) - line.set_length(self.windmill_length) - line.set_angle(angle) - line.set_style(**self.windmill_style) - - line.point_set = points - - if pivot is not None: - line.pivot = pivot - else: - line.pivot = points[0] - - line.rot_speed = self.windmill_rotation_speed - - line.add_updater(lambda l: l.move_to(l.pivot)) - return line - - def get_pivot_dot(self, windmill, color=YELLOW): - pivot_dot = Dot(color=YELLOW) - pivot_dot.add_updater(lambda d: d.move_to(windmill.pivot)) - return pivot_dot - - def start_leaving_shadows(self): - self.leave_shadows = True - self.add(self.get_windmill_shadows()) - - def get_windmill_shadows(self): - if not hasattr(self, "windmill_shadows"): - self.windmill_shadows = VGroup() - return self.windmill_shadows - - def next_pivot_and_angle(self, windmill): - curr_angle = windmill.get_angle() - pivot = windmill.pivot - non_pivots = list(filter( - lambda p: not np.all(p == pivot), - windmill.point_set - )) - - angles = np.array([ - -(angle_of_vector(point - pivot) - curr_angle) % PI - for point in non_pivots - ]) - - # Edge case for 2 points - tiny_indices = angles < 1e-6 - if np.all(tiny_indices): - return non_pivots[0], PI - - angles[tiny_indices] = np.inf - index = np.argmin(angles) - return non_pivots[index], angles[index] - - def rotate_to_next_pivot(self, windmill, max_time=None, added_anims=None): - """ - Returns animations to play following the contact, and total run time - """ - new_pivot, angle = self.next_pivot_and_angle(windmill) - change_pivot_at_end = True - - if added_anims is None: - added_anims = [] - - run_time = angle / windmill.rot_speed - if max_time is not None and run_time > max_time: - ratio = max_time / run_time - rate_func = (lambda t: ratio * t) - run_time = max_time - change_pivot_at_end = False - else: - rate_func = linear - - for anim in added_anims: - if anim.run_time > run_time: - anim.run_time = run_time - - self.play( - Rotate( - windmill, - -angle, - rate_func=rate_func, - run_time=run_time, - ), - *added_anims, - ) - - if change_pivot_at_end: - self.handle_pivot_change(windmill, new_pivot) - - # Return animations to play - return [self.get_hit_flash(new_pivot)], run_time - - def handle_pivot_change(self, windmill, new_pivot): - windmill.pivot = new_pivot - self.add_sound(self.hit_sound) - if self.leave_shadows: - new_shadow = windmill.copy() - new_shadow.fade(0.5) - new_shadow.set_stroke(width=1) - new_shadow.clear_updaters() - shadows = self.get_windmill_shadows() - shadows.add(new_shadow) - - def let_windmill_run(self, windmill, time): - # start_time = self.get_time() - # end_time = start_time + time - # curr_time = start_time - anims_from_last_hit = [] - while time > 0: - anims_from_last_hit, last_run_time = self.rotate_to_next_pivot( - windmill, - max_time=time, - added_anims=anims_from_last_hit, - ) - time -= last_run_time - # curr_time = self.get_time() - - def add_dot_color_updater(self, dots, windmill, **kwargs): - for dot in dots: - dot.add_updater(lambda d: self.update_dot_color( - d, windmill, **kwargs - )) - - def update_dot_color(self, dot, windmill, color1=BLUE, color2=GREY_BROWN): - perp = rotate_vector(windmill.get_vector(), TAU / 4) - dot_product = np.dot(perp, dot.get_center() - windmill.pivot) - if dot_product > 0: - dot.set_color(color1) - # elif dot_product < 0: - else: - dot.set_color(color2) - # else: - # dot.set_color(WHITE) - - dot.set_stroke( - # interpolate_color(dot.get_fill_color(), WHITE, 0.5), - WHITE, - width=2, - background=True - ) - - def get_hit_flash(self, point): - flash = Flash( - point, - line_length=0.1, - flash_radius=0.2, - run_time=0.5, - remover=True, - ) - flash_mob = flash.mobject - for submob in flash_mob: - submob.reverse_points() - return Uncreate( - flash.mobject, - run_time=0.25, - lag_ratio=0, - ) - - def get_pivot_counters(self, windmill, counter_height=0.25, buff=0.2, color=WHITE): - points = windmill.point_set - counters = VGroup() - for point in points: - counter = Integer(0) - counter.set_color(color) - counter.set_height(counter_height) - counter.next_to(point, UP, buff=buff) - counter.point = point - counter.windmill = windmill - counter.is_pivot = False - counter.add_updater(self.update_counter) - counters.add(counter) - return counters - - def update_counter(self, counter): - dist = get_norm(counter.point - counter.windmill.pivot) - counter.will_be_pivot = (dist < 1e-6) - if (not counter.is_pivot) and counter.will_be_pivot: - counter.increment_value() - counter.is_pivot = counter.will_be_pivot - - def get_orientation_arrows(self, windmill, n_tips=20): - tips = VGroup(*[ - ArrowTip(start_angle=0) - for x in range(n_tips) - ]) - tips.stretch(0.75, 1) - tips.scale(0.5) - - tips.rotate(windmill.get_angle()) - tips.match_color(windmill) - tips.set_stroke(BLACK, 1, background=True) - for tip, a in zip(tips, np.linspace(0, 1, n_tips)): - tip.shift( - windmill.point_from_proportion(a) - tip.points[0] - ) - return tips - - def get_left_right_colorings(self, windmill, opacity=0.3): - rects = VGroup(VMobject(), VMobject()) - rects.const_opacity = opacity - - def update_regions(rects): - p0, p1 = windmill.get_start_and_end() - v = p1 - p0 - vl = rotate_vector(v, 90 * DEGREES) - vr = rotate_vector(v, -90 * DEGREES) - p2 = p1 + vl - p3 = p0 + vl - p4 = p1 + vr - p5 = p0 + vr - rects[0].set_points_as_corners([p0, p1, p2, p3]) - rects[1].set_points_as_corners([p0, p1, p4, p5]) - rects.set_stroke(width=0) - rects[0].set_fill(BLUE, rects.const_opacity) - rects[1].set_fill(GREY_BROWN, rects.const_opacity) - return rects - - rects.add_updater(update_regions) - return rects - - -class IntroduceWindmill(WindmillScene): - CONFIG = { - "final_run_time": 60, - "windmill_rotation_speed": 0.5, - } - - def construct(self): - self.add_points() - self.exclude_colinear() - self.add_line() - self.switch_pivots() - self.continue_and_count() - - def add_points(self): - points = self.get_random_point_set(8) - points[-1] = midpoint(points[0], points[1]) - dots = self.get_dots(points) - dots.set_color(YELLOW) - dots.set_height(3) - braces = VGroup( - Brace(dots, LEFT), - Brace(dots, RIGHT), - ) - - group = VGroup(dots, braces) - group.set_height(4) - group.center().to_edge(DOWN) - - S, eq = S_eq = TexMobject("\\mathcal{S}", "=") - S_eq.scale(2) - S_eq.next_to(braces, LEFT) - - self.play( - FadeIn(S_eq), - FadeInFrom(braces[0], RIGHT), - FadeInFrom(braces[1], LEFT), - ) - self.play( - LaggedStartMap(FadeInFromLarge, dots) - ) - self.wait() - self.play( - S.next_to, dots, LEFT, - {"buff": 2, "aligned_edge": UP}, - FadeOut(braces), - FadeOut(eq), - ) - - self.S_label = S - self.dots = dots - - def exclude_colinear(self): - dots = self.dots - - line = Line(dots[0].get_center(), dots[1].get_center()) - line.scale(1.5) - line.set_stroke(WHITE) - - words = TextMobject("Not allowed!") - words.scale(2) - words.set_color(RED) - words.next_to(line.get_center(), RIGHT) - - self.add(line, dots) - self.play( - ShowCreation(line), - FadeInFrom(words, LEFT), - dots[-1].set_color, RED, - ) - self.wait() - self.play( - FadeOut(line), - FadeOut(words), - ) - self.play( - FadeOutAndShift( - dots[-1], 3 * RIGHT, - path_arc=-PI / 4, - rate_func=running_start, - ) - ) - dots.remove(dots[-1]) - self.wait() - - def add_line(self): - dots = self.dots - points = np.array(list(map(Mobject.get_center, dots))) - p0 = points[0] - - windmill = self.get_windmill(points, p0, angle=60 * DEGREES) - pivot_dot = self.get_pivot_dot(windmill) - - l_label = TexMobject("\\ell") - l_label.scale(1.5) - p_label = TexMobject("P") - - l_label.next_to( - p0 + 2 * normalize(windmill.get_vector()), - RIGHT, - ) - l_label.match_color(windmill) - p_label.next_to(p0, RIGHT) - p_label.match_color(pivot_dot) - - arcs = VGroup(*[ - Arc(angle=-45 * DEGREES, radius=1.5) - for x in range(2) - ]) - arcs[1].rotate(PI, about_point=ORIGIN) - for arc in arcs: - arc.add_tip(tip_length=0.2) - arcs.rotate(windmill.get_angle()) - arcs.shift(p0) - - self.add(windmill, dots) - self.play( - GrowFromCenter(windmill), - FadeInFrom(l_label, DL), - ) - self.wait() - self.play( - TransformFromCopy(pivot_dot, p_label), - GrowFromCenter(pivot_dot), - dots.set_color, WHITE, - ) - self.wait() - self.play(*map(ShowCreation, arcs)) - self.wait() - - # Rotate to next pivot - next_pivot, angle = self.next_pivot_and_angle(windmill) - self.play( - *[ - Rotate( - mob, -0.99 * angle, - about_point=p0, - rate_func=linear, - ) - for mob in [windmill, arcs, l_label] - ], - VFadeOut(l_label), - ) - self.add_sound(self.hit_sound) - self.play( - self.get_hit_flash(next_pivot) - ) - self.wait() - - self.pivot2 = next_pivot - self.pivot_dot = pivot_dot - self.windmill = windmill - self.p_label = p_label - self.arcs = arcs - - def switch_pivots(self): - windmill = self.windmill - pivot2 = self.pivot2 - p_label = self.p_label - arcs = self.arcs - - q_label = TexMobject("Q") - q_label.set_color(YELLOW) - q_label.next_to(pivot2, DR, buff=SMALL_BUFF) - - self.rotate_to_next_pivot(windmill) - self.play( - FadeInFrom(q_label, LEFT), - FadeOut(p_label), - FadeOut(arcs), - ) - self.wait() - flashes, run_time = self.rotate_to_next_pivot(windmill) - self.remove(q_label) - self.add_sound(self.hit_sound) - self.play(*flashes) - self.wait() - self.let_windmill_run(windmill, 10) - - def continue_and_count(self): - windmill = self.windmill - pivot_dot = self.pivot_dot - - p_label = TexMobject("P") - p_label.match_color(pivot_dot) - p_label.next_to(pivot_dot, DR, buff=0) - - l_label = TexMobject("\\ell") - l_label.scale(1.5) - l_label.match_color(windmill) - l_label.next_to( - windmill.get_center() + -3 * normalize(windmill.get_vector()), - DR, - buff=SMALL_BUFF, - ) - - self.play(FadeInFrom(p_label, UL)) - self.play(FadeInFrom(l_label, LEFT)) - self.wait() - - self.add( - windmill.copy().fade(0.75), - pivot_dot.copy().fade(0.75), - ) - pivot_counters = self.get_pivot_counters(windmill) - self.add(pivot_counters) - windmill.rot_speed *= 2 - - self.let_windmill_run(windmill, self.final_run_time) - - -class ContrastToOtherOlympiadProblems(AskWhatsOnTest): - def construct(self): - self.zoom_to_other_questions() - - def zoom_to_other_questions(self): - test = self.get_test() - rects = self.get_all_rects() - - big_rects = VGroup() - for rect in rects: - big_rect = FullScreenFadeRectangle() - rect.reverse_points() - big_rect.append_vectorized_mobject(rect) - big_rects.add(big_rect) - - frame = self.camera_frame - frame.generate_target() - frame.target.scale(0.35) - frame.target.move_to(rects[1]) - big_rect = big_rects[1].copy() - - self.add(test) - self.play( - FadeIn(big_rect), - MoveToTarget(frame, run_time=3), - ) - self.wait() - for i in [2, 0, 3, 5]: - self.play( - frame.move_to, rects[i], - Transform(big_rect, big_rects[i]) - ) - self.wait() - - def get_all_rects(self, test): - rects = self.get_problem_rects(test[0]) - new_rects = VGroup(rects[1], rects[0], rects[2]).copy() - new_rects[0].stretch(0.85, 1) - new_rects[1].stretch(0.8, 1) - new_rects[2].stretch(0.8, 1) - new_rects.arrange(DOWN, buff=0.08) - new_rects.move_to(test[1]) - new_rects.align_to(rects, UP) - rects.add(*new_rects) - return rects - - -class WindmillExample30Points(WindmillScene): - CONFIG = { - "n_points": 30, - "random_seed": 0, - "run_time": 60, - "counter_config": { - "counter_height": 0.15, - "buff": 0.1, - }, - } - - def construct(self): - points = self.get_random_point_set(self.n_points) - points[:, 0] *= 1.5 - sorted_points = sorted(list(points), key=lambda p: p[1]) - sorted_points[4] += RIGHT - - dots = self.get_dots(points) - windmill = self.get_windmill(points, sorted_points[5], angle=PI / 4) - pivot_dot = self.get_pivot_dot(windmill) - # self.add_dot_color_updater(dots, windmill) - - self.add(windmill) - self.add(dots) - self.add(pivot_dot) - self.add(self.get_pivot_counters( - windmill, **self.counter_config - )) - - self.let_windmill_run(windmill, self.run_time) - - -class WindmillExample15Points(WindmillExample30Points): - CONFIG = { - "n_points": 15, - "run_time": 60, - "random_seed": 2, - "counter_config": { - "counter_height": 0.25, - "buff": 0.1, - }, - } - - -class TheQuestion(Scene): - def construct(self): - words = TextMobject( - "Will each point be hit infinitely many times?" - ) - words.set_width(FRAME_WIDTH - 1) - words.to_edge(UP) - - self.add(words) - - -class SpiritOfIMO(PiCreatureScene): - def construct(self): - randy = self.pi_creature - - problems = VGroup(*[ - TextMobject("P{})".format(i)) - for i in range(1, 7) - ]) - problems.arrange_in_grid(n_cols=2, buff=LARGE_BUFF) - problems.scale(1.5) - problems[3:].shift(1.5 * RIGHT) - problems.to_corner(UR, buff=LARGE_BUFF) - problems.shift(2 * LEFT) - - light_bulbs = VGroup() - lights = VGroup() - for problem in problems: - light_bulb = Lightbulb() - light_bulb.base = light_bulb[:3] - light_bulb.light = light_bulb[3:] - light_bulb.set_height(1) - light_bulb.next_to(problem, RIGHT) - light_bulbs.add(light_bulb) - - light = self.get_light(light_bulb.get_center()) - lights.add(light) - - self.play( - LaggedStartMap(FadeInFromDown, problems) - ) - self.play( - LaggedStartMap( - FadeIn, light_bulbs, - run_time=1, - ), - LaggedStartMap( - LaggedStartMap, lights, - lambda l: (VFadeInThenOut, l), - run_time=3 - ), - randy.change, "thinking" - ) - self.wait() - self.pi_creature_thinks( - "Oh, I've\\\\seen this...", - target_mode="surprised", - ) - self.wait(3) - - def get_light(self, point): - radii = np.arange(0, 5, 0.1) - result = VGroup(*[ - Annulus( - inner_radius=r1, - outer_radius=r2, - arc_center=point, - fill_opacity=(1 / (r1 + 1)**2), - fill_color=YELLOW, - ) - for r1, r2 in zip(radii[1:], radii[2:]) - ]) - return result - - -# TODO -class HowToPrepareForThis(Scene): - def construct(self): - pass - - -class HarderThanExpected(TeacherStudentsScene): - def construct(self): - title = TextMobject("Unusual aspect \\#2") - title.scale(1.5) - title.to_edge(UP) - - line = Line(LEFT, RIGHT) - line.match_width(title) - line.next_to(title, DOWN) - - words = TextMobject("Harder than expected") - words.set_color(RED) - words.scale(1.5) - words.next_to(line, DOWN, LARGE_BUFF) - - self.play( - FadeInFromDown(title), - ShowCreation(line), - self.teacher.change, "raise_right_hand", - self.get_student_changes("pondering", "confused", "sassy") - ) - self.wait() - self.play( - FadeInFrom(words, UP), - self.get_student_changes(*3 * ["horrified"]), - ) - self.wait(3) - - -class TraditionalDifficulty(ContrastToOtherOlympiadProblems): - def construct(self): - test = self.get_test() - rects = self.get_all_rects(test) - for rect in rects: - rect.reverse_points() - - big_rects = VGroup(*[ - FullScreenFadeRectangle() - for x in range(3) - ]) - for br, r1, r2 in zip(big_rects, rects, rects[3:]): - br.append_vectorized_mobject(r1) - br.append_vectorized_mobject(r2) - big_rect = big_rects[0].copy() - - p_labels = VGroup() - for i, rect in enumerate(rects): - p_label = TextMobject("P{}".format(i + 1)) - p_label.next_to(rect, LEFT) - p_labels.add(p_label) - - arrow = Vector(3 * DOWN) - arrow.next_to(test[0], RIGHT) - arrow.match_y(rects) - harder_words = TextMobject("Get harder") - harder_words.scale(2) - harder_words.next_to(arrow, RIGHT) - harder_words.set_color(RED) - - p_words = VGroup( - TextMobject("Doable", color=GREEN), - TextMobject("Challenging", color=YELLOW), - TextMobject("Brutal", color=RED), - ) - p_words.add(*p_words.copy()) - for rect, word, label in zip(rects, p_words, p_labels): - word.next_to(rect, UP) - label.match_color(word) - - self.add(test[0]) - self.play( - FadeIn(harder_words), - GrowArrow(arrow), - LaggedStart(*[FadeInFrom(p, UP) for p in p_labels[:3]]), - LaggedStartMap(ShowCreation, rects[:3]), - ) - self.wait() - self.play( - FadeIn(test[1]), - FadeIn(p_labels[3:]), - FadeIn(rects[3:]), - FadeOut(harder_words), - FadeOut(arrow), - ) - self.wait() - self.add(big_rect, p_labels[0], p_labels[3]) - self.play( - FadeIn(big_rect), - FadeOut(rects), - FadeOut(p_labels[1:3]), - FadeOut(p_labels[4:]), - FadeInFromDown(p_words[0::3]), - ) - self.wait() - self.play( - Transform(big_rect, big_rects[1]), - FadeOut(p_labels[0::3]), - FadeIn(p_labels[1::3]), - FadeOutAndShift(p_words[0::3], DOWN), - FadeInFrom(p_words[1::3], UP), - ) - self.wait() - self.play( - Transform(big_rect, big_rects[2]), - FadeOut(p_labels[1::3]), - FadeIn(p_labels[2::3]), - FadeOutAndShift(p_words[1::3], DOWN), - FadeInFrom(p_words[2::3], UP), - ) - self.wait() - - -class PerfectScoreData(Describe2011IMO): - CONFIG = { - "n_students": 563, - "n_perfect_scores_per_problem": [ - 345, 22, 51, 267, 170, 6, - ], - "full_bar_width": 7, - } - - def construct(self): - self.add_title() - self.show_total_number_of_students() - self.add_subtitle() - self.show_data() - self.analyze_data() - - def add_title(self): - self.force_skipping() - super().add_title() - self.revert_to_original_skipping_status() - self.title.center().to_edge(UP) - - def show_total_number_of_students(self): - title = self.title - - bar = self.get_bar(self.n_students, ORIGIN) - bar.next_to(title, DOWN, buff=0.3) - counter = self.get_bar_counter(bar) - counter_label = TextMobject("Students") - counter_label.add_updater( - lambda m: m.next_to(counter, RIGHT) - ) - - self.add(counter, counter_label) - self.play( - self.get_bar_growth_anim(bar), - run_time=2, - ) - self.wait() - - def add_subtitle(self): - title = self.title - - subtitle = TextMobject( - "Number of perfect scores on each problem:" - ) - subtitle.scale(1.25) - subtitle.set_color(GREEN) - subtitle.next_to(title, DOWN, buff=LARGE_BUFF) - - problems = VGroup(*[ - TextMobject("P{})".format(i)) - for i in range(1, 7) - ]) - problems.arrange_in_grid(n_cols=2, buff=LARGE_BUFF) - problems[3:].shift(5 * RIGHT) - problems.next_to(subtitle, DOWN, LARGE_BUFF) - problems.to_edge(LEFT) - - self.play( - FadeInFromDown(subtitle), - LaggedStartMap(FadeInFromDown, problems), - ) - - self.problems = problems - - def show_data(self): - problems = self.problems - bars = VGroup(*[ - self.get_bar(n, p.get_right() + SMALL_BUFF * RIGHT) - for n, p in zip( - self.n_perfect_scores_per_problem, - problems, - ) - ]) - counters = VGroup(*map(self.get_bar_counter, bars)) - - self.play( - VFadeIn(counters), - *[ - self.get_bar_growth_anim(bar) - for bar in bars - ], - ) - counters.set_fill(WHITE, 1) - self.wait() - - self.problem_bars = bars - self.problem_counters = counters - - def analyze_data(self): - problems = VGroup(*[ - VGroup(p, pb, pc) - for p, pb, pc in zip( - self.problems, - self.problem_bars, - self.problem_counters, - ) - ]) - rects = VGroup(*[ - SurroundingRectangle(p, color=p[1].get_color()) - for p in problems - ]) - - rect = rects[1].copy() - self.play(ShowCreation(rect)) - self.wait() - self.play(TransformFromCopy(rect, rects[4])) - self.wait() - self.play(TransformFromCopy(rect, rects[2])) - self.wait() - self.play( - ReplacementTransform(rect, rects[5]), - ReplacementTransform(rects[4], rects[5]), - ReplacementTransform(rects[2], rects[5]), - ) - self.wait() - - # - def get_bar(self, number, left_side): - bar = Rectangle() - bar.set_stroke(width=0) - bar.set_fill(WHITE, 1) - bar.set_height(0.25) - bar.set_width( - self.full_bar_width * number / self.n_students, - stretch=True - ) - bar.move_to(left_side, LEFT) - - def update_bar_color(bar): - frac = bar.get_width() / self.full_bar_width - if 0 < frac <= 0.25: - alpha = 4 * frac - bar.set_color(interpolate_color(RED, YELLOW, alpha)) - elif 0.25 < frac <= 0.5: - alpha = 4 * (frac - 0.25) - bar.set_color(interpolate_color(YELLOW, GREEN, alpha)) - else: - alpha = 2 * (frac - 0.5) - bar.set_color(interpolate_color(GREEN, BLUE, alpha)) - bar.add_updater(update_bar_color) - return bar - - def get_bar_growth_anim(self, bar): - bar.save_state() - bar.stretch(0, 0, about_edge=LEFT) - return Restore( - bar, - suspend_mobject_updating=False, - run_time=2, - ) - - def get_bar_counter(self, bar): - counter = Integer() - counter.add_updater( - lambda m: m.set_value( - self.n_students * bar.get_width() / self.full_bar_width - ) - ) - counter.add_updater(lambda m: m.next_to(bar, RIGHT, SMALL_BUFF)) - return counter - - -class SixOnSix(Describe2011IMO): - CONFIG = { - "student_data": [ - [1, "Lisa Sauermann", "de", [7, 7, 7, 7, 7, 7]], - [2, "Jeck Lim", "sg", [7, 5, 7, 7, 7, 7]], - [3, "Lin Chen", "cn", [7, 3, 7, 7, 7, 7]], - [14, "Mina Dalirrooyfard", "ir", [7, 0, 2, 7, 7, 7]], - [202, "Georgios Kalantzis", "gr", [7, 0, 1, 1, 2, 7]], - [202, "Chi Hong Chow", "hk", [7, 0, 3, 1, 0, 7]], - ], - } - - def construct(self): - grid = self.get_score_grid() - grid.to_edge(DOWN, buff=LARGE_BUFF) - for row in grid.rows: - row[0].set_opacity(0) - grid.h_lines.stretch(0.93, 0, about_edge=RIGHT) - - sf = 1.25 - title = TextMobject("Only 6 solved P6") - title.scale(sf) - title.to_edge(UP, buff=MED_SMALL_BUFF) - subtitle = TextMobject("P2 evaded 5 of them") - subtitle.set_color(YELLOW) - subtitle.scale(sf) - subtitle.next_to(title, DOWN) - - six_rect, two_rect = [ - SurroundingRectangle(VGroup( - grid.rows[0][index], - grid.rows[-1][index], - )) - for index in [7, 3] - ] - - self.play( - Write(title), - LaggedStart(*[FadeInFrom(row, UP) for row in grid.rows]), - LaggedStart(*[ShowCreation(line) for line in grid.h_lines]), - ) - self.play(ShowCreation(six_rect)) - self.wait() - self.play( - ReplacementTransform(six_rect, two_rect), - FadeInFrom(subtitle, UP) - ) - self.wait() - - -class AlwaysStartSimple(TeacherStudentsScene): - def construct(self): - self.teacher_says("Always start\\\\simple") - self.change_all_student_modes("pondering") - self.wait(3) - - -class TryOutSimplestExamples(WindmillScene): - CONFIG = { - "windmill_rotation_speed": TAU / 8, - } - - def construct(self): - self.two_points() - self.add_third_point() - self.add_fourth_point() - self.move_starting_line() - - def two_points(self): - points = [1.5 * LEFT, 1.5 * RIGHT] - dots = self.dots = self.get_dots(points) - windmill = self.windmill = self.get_windmill(points, angle=TAU / 8) - pivot_dot = self.pivot_dot = self.get_pivot_dot(windmill) - - self.play( - ShowCreation(windmill), - LaggedStartMap( - FadeInFromLarge, dots, - scale_factor=10, - run_time=1, - lag_ratio=0.4, - ), - GrowFromCenter(pivot_dot), - ) - self.let_windmill_run(windmill, 8) - - def add_third_point(self): - windmill = self.windmill - - new_point = 2 * DOWN - new_dot = self.get_dots([new_point]) - windmill.point_set.append(new_point) - - self.add(new_dot, self.pivot_dot) - self.play(FadeInFromLarge(new_dot, scale_factor=10)) - self.let_windmill_run(windmill, 8) - - def add_fourth_point(self): - windmill = self.windmill - dot = self.get_dots([ORIGIN]) - dot.move_to(DOWN + 2 * RIGHT) - words = TextMobject("Never hit!") - words.set_color(RED) - words.scale(0.75) - words.move_to(0.7 * DOWN, DOWN) - - self.add(dot, self.pivot_dot) - self.play( - FadeInFromLarge(dot, scale_factor=10) - ) - windmill.point_set.append(dot.get_center()) - windmill.rot_speed = TAU / 4 - self.let_windmill_run(windmill, 4) - - # Shift point - self.play( - dot.next_to, words, DOWN, - FadeInFrom(words, RIGHT), - ) - windmill.point_set[3] = dot.get_center() - self.let_windmill_run(windmill, 4) - self.wait() - - self.dots.add(dot) - self.never_hit_words = words - - def move_starting_line(self): - windmill = self.windmill - dots = self.dots - - windmill.suspend_updating() - self.play( - windmill.move_to, dots[-1], - FadeOut(self.never_hit_words), - ) - windmill.pivot = dots[-1].get_center() - windmill.resume_updating() - - counters = self.get_pivot_counters(windmill) - self.play( - LaggedStart(*[ - FadeInFrom(counter, DOWN) - for counter in counters - ]) - ) - self.wait() - - windmill.rot_speed = TAU / 8 - self.let_windmill_run(windmill, 16) - highlight = windmill.copy() - highlight.set_stroke(YELLOW, 4) - self.play( - ShowCreationThenDestruction(highlight), - ) - self.let_windmill_run(windmill, 8) - - -class FearedCase(WindmillScene): - CONFIG = { - "n_points": 25, - "windmill_rotation_speed": TAU / 16, - } - - def construct(self): - points = self.get_random_point_set(self.n_points) - sorted_points = sorted(list(points), key=lambda p: p[1]) - - dots = self.get_dots(points) - windmill = self.get_windmill( - points, - sorted_points[self.n_points // 2], - angle=0, - ) - pivot_dot = self.get_pivot_dot(windmill) - # self.add_dot_color_updater(dots, windmill) - counters = self.get_pivot_counters( - windmill, - counter_height=0.15, - buff=0.1 - ) - - self.add(windmill) - self.add(dots) - self.add(pivot_dot) - self.add(counters) - - self.let_windmill_run(windmill, 32) - windmill.pivot = sorted_points[0] - self.let_windmill_run(windmill, 32) - - -class WhereItStartsItEnds(WindmillScene): - CONFIG = { - "n_points": 11, - "windmill_rotation_speed": TAU / 8, - "random_seed": 1, - "points_shift_val": 2 * LEFT, - } - - def construct(self): - self.show_stays_in_middle() - self.ask_about_proof() - - def show_stays_in_middle(self): - points = self.get_random_point_set(self.n_points) - points += self.points_shift_val - sorted_points = sorted(list(points), key=lambda p: p[1]) - dots = self.get_dots(points) - - windmill = self.get_windmill( - points, - sorted_points[self.n_points // 2], - angle=0 - ) - pivot_dot = self.get_pivot_dot(windmill) - - sf = 1.25 - start_words = TextMobject("Starts in the ", "``middle''") - start_words.scale(sf) - start_words.next_to(windmill, UP, MED_SMALL_BUFF) - start_words.to_edge(RIGHT) - end_words = TextMobject("Stays in the ", "``middle''") - end_words.scale(sf) - end_words.next_to(windmill, DOWN, MED_SMALL_BUFF) - end_words.to_edge(RIGHT) - start_words.match_x(end_words) - - self.add(dots) - self.play( - ShowCreation(windmill), - GrowFromCenter(pivot_dot), - FadeInFrom(start_words, LEFT), - ) - self.wait() - self.start_leaving_shadows() - self.add(windmill, dots, pivot_dot) - half_time = PI / windmill.rot_speed - self.let_windmill_run(windmill, time=half_time) - self.play(FadeInFrom(end_words, UP)) - self.wait() - self.let_windmill_run(windmill, time=half_time) - self.wait() - - self.start_words = start_words - self.end_words = end_words - self.windmill = windmill - self.dots = dots - self.pivot_dot = pivot_dot - - def ask_about_proof(self): - sf = 1.25 - middle_rects = self.get_middle_rects() - middle_words = TextMobject("Can you formalize this?") - middle_words.scale(sf) - middle_words.next_to(middle_rects, DOWN, MED_LARGE_BUFF) - middle_words.to_edge(RIGHT) - middle_words.match_color(middle_rects) - - proof_words = TextMobject("Can you prove this?") - proof_words.next_to( - self.end_words.get_left(), - DL, - buff=2, - ) - proof_words.shift(RIGHT) - proof_words.scale(sf) - proof_arrow = Arrow( - proof_words.get_top(), - self.end_words.get_corner(DL), - buff=SMALL_BUFF, - ) - proof_words2 = TextMobject("Then prove the result?") - proof_words2.scale(sf) - proof_words2.next_to(middle_words, DOWN, MED_LARGE_BUFF) - proof_words2.to_edge(RIGHT) - VGroup(proof_words, proof_words2, proof_arrow).set_color(YELLOW) - - self.play( - Write(proof_words), - GrowArrow(proof_arrow), - run_time=1, - ) - self.wait() - self.play( - FadeOut(proof_arrow), - FadeOut(proof_words), - LaggedStartMap(ShowCreation, middle_rects), - Write(middle_words), - ) - self.wait() - self.play(FadeInFrom(proof_words2, UP)) - self.wait() - self.let_windmill_run(self.windmill, time=10) - - def get_middle_rects(self): - middle_rects = VGroup(*[ - SurroundingRectangle(words[1]) - for words in [ - self.start_words, - self.end_words - ] - ]) - middle_rects.set_color(TEAL) - return middle_rects - - -class AltWhereItStartsItEnds(WhereItStartsItEnds): - CONFIG = { - "n_points": 9, - "random_seed": 3, - } - - -class FormalizeMiddle(WhereItStartsItEnds): - CONFIG = { - "random_seed": 2, - "points_shift_val": 3 * LEFT, - } - - def construct(self): - self.show_stays_in_middle() - self.problem_solving_tip() - self.define_colors() - self.mention_odd_case() - self.ask_about_numbers() - - def problem_solving_tip(self): - mid_words = VGroup( - self.start_words, - self.end_words, - ) - mid_words.save_state() - - sf = 1.25 - pst = TextMobject("Problem-solving tip:") - pst.scale(sf) - underline = Line(LEFT, RIGHT) - underline.match_width(pst) - underline.move_to(pst.get_bottom()) - pst.add(underline) - pst.to_corner(UR) - # pst.set_color(YELLOW) - - steps = VGroup( - TextMobject("Vague idea"), - TextMobject("Put numbers to it"), - TextMobject("Ask about those numbers"), - ) - steps.scale(sf) - steps.arrange(DOWN, buff=LARGE_BUFF) - steps.next_to(pst, DOWN, buff=MED_LARGE_BUFF) - steps.shift_onto_screen() - pst.match_x(steps) - - colors = color_gradient([BLUE, YELLOW], 3) - for step, color in zip(steps, colors): - step.set_color(color) - - arrows = VGroup() - for s1, s2 in zip(steps, steps[1:]): - arrow = Arrow(s1.get_bottom(), s2.get_top(), buff=SMALL_BUFF) - arrows.add(arrow) - - self.play(Write(pst), run_time=1) - self.wait() - self.play( - mid_words.scale, 0.75, - mid_words.set_opacity, 0.25, - mid_words.to_corner, DL, - FadeInFromDown(steps[0]), - ) - self.wait() - for arrow, step in zip(arrows, steps[1:]): - self.play( - FadeInFrom(step, UP), - GrowArrow(arrow), - ) - self.wait() - - steps.generate_target() - steps.target.scale(0.75) - steps.target.arrange(DOWN, buff=0.2) - steps.target.to_corner(UR) - - self.play( - FadeOut(pst), - MoveToTarget(steps), - Restore(mid_words), - FadeOut(arrows) - ) - self.wait() - - self.tip_words = steps - self.mid_words = mid_words - - def define_colors(self): - windmill = self.windmill - mid_words = self.mid_words - tip_words = self.tip_words - shadows = self.windmill_shadows - self.leave_shadows = False - - full_time = TAU / windmill.rot_speed - - self.play(FadeOut(shadows)) - self.add(windmill, tip_words, mid_words, self.dots, self.pivot_dot) - self.let_windmill_run(windmill, time=full_time / 4) - windmill.rotate(PI) - self.wait() - - # Show regions - rects = self.get_left_right_colorings(windmill) - rects.suspend_updating() - rects.save_state() - rects.stretch(0, 0, about_point=windmill.get_center()) - - counters = VGroup(Integer(0), Integer(0)) - counters.scale(2) - counters[0].set_stroke(BLUE, 3, background=True) - counters[1].set_stroke(GREY_BROWN, 3, background=True) - - new_dots = self.dots.copy() - new_dots.set_color(WHITE) - for dot in new_dots: - dot.scale(1.25) - new_dots.sort(lambda p: p[0]) - k = self.n_points // 2 - dot_sets = VGroup(new_dots[:k], new_dots[-k:]) - - label_sets = VGroup() - for dot_set, direction in zip(dot_sets, [LEFT, RIGHT]): - label_set = VGroup() - for i, dot in zip(it.count(1), dot_set): - label = Integer(i) - label.set_height(0.15) - label.next_to(dot, direction, SMALL_BUFF) - label_set.add(label) - label_sets.add(label_set) - - for counter, dot_set in zip(counters, dot_sets): - counter.move_to(dot_set) - counter.to_edge(UP) - - self.add(rects, *self.get_mobjects()) - self.play( - Restore(rects), - FadeIn(counters), - ) - for counter, dot_set, label_set in zip(counters, dot_sets, label_sets): - self.play( - ShowIncreasingSubsets(dot_set), - ShowIncreasingSubsets(label_set), - ChangingDecimal(counter, lambda a: len(dot_set)), - rate_func=linear, - ) - self.wait() - self.wait() - - self.remove(self.dots) - self.dots = new_dots - - # Show orientation - tips = self.get_orientation_arrows(windmill) - - self.play(ShowCreation(tips)) - windmill.add(tips) - self.wait() - - self.add_dot_color_updater(new_dots, windmill) - - rects.suspend_updating() - for rect in rects: - self.play(rect.set_opacity, 1) - self.play(rect.set_opacity, rects.const_opacity) - rects.resume_updating() - self.wait() - self.play( - counters.space_out_submobjects, 0.8, - counters.next_to, mid_words, DOWN, LARGE_BUFF, - FadeOut(label_sets), - ) - eq = TexMobject("=") - eq.scale(2) - eq.move_to(counters) - self.play(FadeIn(eq)) - self.wait() - - self.counters = counters - self.colored_regions = rects - rects.resume_updating() - - def mention_odd_case(self): - dots = self.dots - counters = self.counters - - sf = 1.0 - words = TextMobject( - "Assume odd \\# points" - ) - words.scale(sf) - words.to_corner(UL) - example = VGroup( - TextMobject("Example:"), - Integer(0) - ) - example.arrange(RIGHT) - example.scale(sf) - example.next_to(words, DOWN) - example.align_to(words, LEFT) - - k = self.n_points // 2 - dot_rects = VGroup() - for i, dot in zip(it.count(1), dots): - dot_rect = SurroundingRectangle(dot) - dot_rect.match_color(dot) - dot_rects.add(dot_rect) - - self.play(FadeInFrom(words, DOWN)) - self.wait() - - self.play( - ShowCreationThenFadeAround(dots[k]), - self.pivot_dot.set_color, WHITE, - ) - - self.play(FadeInFrom(example, UP)) - self.play( - ShowIncreasingSubsets(dot_rects), - ChangingDecimal( - example[1], - lambda a: len(dot_rects) - ), - rate_func=linear - ) - self.wait() - - self.remove(dot_rects) - self.play( - ShowCreationThenFadeOut(dot_rects[:k]), - ShowCreationThenFadeOut( - SurroundingRectangle(counters[0], color=BLUE) - ), - ) - self.play( - ShowCreationThenFadeOut(dot_rects[-k:]), - ShowCreationThenFadeOut( - SurroundingRectangle(counters[1], color=GREY_BROWN) - ), - ) - self.wait() - self.play( - FadeOut(words), - FadeOut(example), - ) - - def ask_about_numbers(self): - self.windmill.rot_speed *= 0.5 - self.add(self.dots, self.pivot_dot) - self.let_windmill_run(self.windmill, 20) - - -class SecondColoringExample(WindmillScene): - CONFIG = { - "run_time": 30, - "n_points": 9, - } - - def construct(self): - points = self.get_random_point_set(self.n_points) - points += RIGHT - sorted_points = sorted(list(points), key=lambda p: p[0]) - - dots = self.get_dots(points) - windmill = self.get_windmill( - points, - pivot=sorted_points[self.n_points // 2], - angle=PI / 2 - ) - pivot_dot = self.get_pivot_dot(windmill) - pivot_dot.set_color(WHITE) - rects = self.get_left_right_colorings(windmill) - self.add_dot_color_updater(dots, windmill) - - counts = VGroup( - TextMobject("\\# Blues = 4"), - TextMobject("\\# Browns = 4"), - ) - counts.arrange(DOWN, aligned_edge=LEFT, buff=MED_LARGE_BUFF) - counts.to_corner(UL) - counts[0].set_color(interpolate_color(BLUE, WHITE, 0.25)) - counts[1].set_color(interpolate_color(GREY_BROWN, WHITE, 0.5)) - counts[0].set_stroke(BLACK, 5, background=True) - counts[1].set_stroke(BLACK, 5, background=True) - - const_words = TextMobject("Stay constant$\\dots$why?") - const_words.next_to(counts, RIGHT, buff=1.5, aligned_edge=UP) - arrows = VGroup(*[ - Arrow( - const_words.get_left(), - count.get_right(), - buff=SMALL_BUFF, - max_tip_length_to_length_ratio=0.15, - max_stroke_width_to_length_ratio=3, - ) - for count in counts - ]) - - self.add(rects, windmill, dots, pivot_dot) - self.add(counts, const_words, arrows) - - self.let_windmill_run(windmill, time=self.run_time) - - -class TalkThroughPivotChange(WindmillScene): - CONFIG = { - "windmill_rotation_speed": 0.2, - } - - def construct(self): - self.setup_windmill() - self.ask_about_pivot_change() - self.show_above_and_below() - self.change_pivot() - - def setup_windmill(self): - points = self.points = np.array([ - DR, UR, UL, DL, 0.5 * LEFT - ]) - points *= 3 - self.dots = self.get_dots(points) - self.windmill = self.get_windmill(points, points[-1]) - self.pivot_dot = self.get_pivot_dot(self.windmill) - self.pivot_dot.set_color(WHITE) - self.add_dot_color_updater(self.dots, self.windmill) - self.rects = self.get_left_right_colorings(self.windmill) - - self.add( - self.rects, - self.windmill, - self.dots, - self.pivot_dot, - ) - - def ask_about_pivot_change(self): - windmill = self.windmill - - new_pivot, angle = self.next_pivot_and_angle(windmill) - words = TextMobject("Think about\\\\pivot change") - words.next_to(new_pivot, UP, buff=2) - words.to_edge(LEFT) - arrow = Arrow(words.get_bottom(), new_pivot, buff=0.2) - - self.play( - Rotate( - windmill, -0.9 * angle, - run_time=3, - rate_func=linear - ), - Write(words, run_time=1), - ShowCreation(arrow), - ) - self.wait() - - self.question = words - self.question_arrow = arrow - - def show_above_and_below(self): - windmill = self.windmill - vect = normalize(windmill.get_vector()) - angle = windmill.get_angle() - tips = self.get_orientation_arrows(windmill) - top_half = Line(windmill.get_center(), windmill.get_end()) - low_half = Line(windmill.get_center(), windmill.get_start()) - top_half.set_stroke(YELLOW, 3) - low_half.set_stroke(PINK, 3) - halves = VGroup(top_half, low_half) - - top_words = TextMobject("Above pivot") - low_words = TextMobject("Below pivot") - all_words = VGroup(top_words, low_words) - for words, half in zip(all_words, halves): - words.next_to(ORIGIN, DOWN) - words.rotate(angle, about_point=ORIGIN) - words.shift(half.point_from_proportion(0.15)) - words.match_color(half) - - self.play(ShowCreation(tips)) - self.wait() - self.add(top_half, tips) - self.play( - ShowCreationThenFadeOut(top_half), - FadeInFrom(top_words, -vect), - ) - self.add(low_half, tips) - self.play( - ShowCreationThenFadeOut(low_half), - FadeInFrom(low_words, vect), - ) - self.wait() - - windmill.add(tips) - self.above_below_words = all_words - - def change_pivot(self): - windmill = self.windmill - dots = self.dots - arrow = self.question_arrow - - blue_rect = SurroundingRectangle(dots[3]) - blue_rect.set_color(BLUE) - new_pivot_word = TextMobject("New pivot") - new_pivot_word.next_to(blue_rect, LEFT) - old_pivot_word = TextMobject("Old pivot") - old_pivot = windmill.pivot - old_pivot_word.next_to( - old_pivot, LEFT, - buff=SMALL_BUFF + MED_SMALL_BUFF - ) - - self.play( - FadeOut(self.above_below_words), - ReplacementTransform( - self.question, - new_pivot_word, - ), - ReplacementTransform(arrow, blue_rect), - ) - self.wait() - anims, time = self.rotate_to_next_pivot(windmill) - self.play( - *anims, - Rotate( - windmill, - angle=-windmill.rot_speed, - rate_func=linear, - ) - ) - self.wait() - self.play( - TransformFromCopy(new_pivot_word, old_pivot_word), - blue_rect.move_to, old_pivot, - ) - self.wait(2) - - # Hit new point - brown_rect = SurroundingRectangle(dots[1]) - brown_rect.set_color(GREY_BROWN) - - self.play(TransformFromCopy(blue_rect, brown_rect)) - self.play( - blue_rect.move_to, windmill.pivot, - blue_rect.set_color, GREY_BROWN, - old_pivot_word.move_to, new_pivot_word, - FadeOutAndShift(new_pivot_word, DL) - ) - self.let_windmill_run(windmill, 1) - self.wait() - self.play( - FadeOut(old_pivot_word), - FadeOut(blue_rect), - FadeOut(brown_rect), - ) - self.let_windmill_run(windmill, 20) - - -class InsightNumber1(Scene): - def construct(self): - words = TextMobject( - "Key insight 1: ", - "\\# Points on either side is constant" - ) - words[0].set_color(YELLOW) - words.set_width(FRAME_WIDTH - 1) - self.play(FadeInFromDown(words)) - self.wait() - - -class Rotate180Argument(WindmillScene): - CONFIG = { - "n_points": 21, - "random_seed": 3, - } - - def construct(self): - self.setup_windmill() - self.add_total_rotation_label() - self.rotate_180() - self.show_parallel_lines() - self.rotate_180() - self.rotate_180() - - def setup_windmill(self): - n = self.n_points - points = self.get_random_point_set(n) - points[:, 0] *= 1.5 - points += RIGHT - points = sorted(points, key=lambda p: p[0]) - mid_point = points[n // 2] - points[n // 2 - 1] += 0.2 * LEFT - self.points = points - - self.dots = self.get_dots(points) - self.windmill = self.get_windmill(points, mid_point) - self.pivot_dot = self.get_pivot_dot(self.windmill) - self.pivot_dot.set_color(WHITE) - self.add_dot_color_updater(self.dots, self.windmill) - self.rects = self.get_left_right_colorings(self.windmill) - - p_label = TexMobject("P_0") - p_label.next_to(mid_point, RIGHT, SMALL_BUFF) - self.p_label = p_label - - self.add( - self.rects, - self.windmill, - self.dots, - self.pivot_dot, - self.p_label, - ) - - def add_total_rotation_label(self): - windmill = self.windmill - - words = TextMobject("Total rotation:") - counter = Integer(0, unit="^\\circ") - title = VGroup(words, counter) - title.arrange(RIGHT) - title.to_corner(UL) - rot_arrow = Vector(UP) - rot_arrow.set_color(RED) - rot_arrow.next_to(title, DOWN) - circle = Circle() - circle.replace(rot_arrow, dim_to_match=1) - circle.set_stroke(WHITE, 1) - - rot_arrow.add_updater( - lambda m: m.set_angle(windmill.get_angle()) - ) - rot_arrow.add_updater( - lambda m: m.move_to(circle) - ) - - def update_count(c): - new_val = 90 - windmill.get_angle() * 360 / TAU - while abs(new_val - c.get_value()) > 90: - new_val += 360 - c.set_value(new_val) - - counter.add_updater(update_count) - - rect = SurroundingRectangle( - VGroup(title, circle), - buff=MED_LARGE_BUFF, - ) - rect.set_fill(BLACK, 0.8) - rect.set_stroke(WHITE, 1) - title.shift(MED_SMALL_BUFF * LEFT) - - self.rotation_label = VGroup( - rect, words, counter, circle, rot_arrow - ) - - self.add(self.rotation_label) - - def rotate_180(self): - windmill = self.windmill - self.add(self.pivot_dot) - self.let_windmill_run( - windmill, - PI / windmill.rot_speed, - ) - self.wait() - - def show_parallel_lines(self): - points = self.points - rotation_label = self.rotation_label - dots = self.dots - windmill = self.windmill - - lines = VGroup() - for point in points: - line = Line(DOWN, UP) - line.set_height(2 * FRAME_HEIGHT) - line.set_stroke(RED, 1, opacity=0.5) - line.move_to(point) - lines.add(line) - lines.shuffle() - - self.add(lines, dots, rotation_label) - self.play( - ShowCreation(lines, lag_ratio=0.5, run_time=3) - ) - self.wait() - - self.rects.suspend_updating() - for rect in self.rects: - self.play( - rect.set_opacity, 0, - rate_func=there_and_back, - run_time=2 - ) - self.rects.resume_updating() - self.wait() - - pivot_tracker = VectorizedPoint(windmill.pivot) - pivot_tracker.save_state() - - def update_pivot(w): - w.pivot = pivot_tracker.get_center() - windmill.add_updater(update_pivot) - - for x in range(4): - point = random.choice(points) - self.play( - pivot_tracker.move_to, point - ) - self.wait() - self.play(Restore(pivot_tracker)) - self.play(FadeOut(lines)) - windmill.remove_updater(update_pivot) - self.wait() - - -class Rotate180ArgumentFast(Rotate180Argument): - CONFIG = { - "windmill_rotation_speed": 0.5, - } - - -class EvenCase(Rotate180Argument): - CONFIG = { - "n_points": 10, - "dot_config": {"radius": 0.075}, - } - - def construct(self): - self.ask_about_even_number() - self.choose_halfway_point() - - self.add_total_rotation_label() - self.rotate_180() - self.rotate_180() - self.show_parallel_lines() - self.rotate_180() - self.rotate_180() - - def ask_about_even_number(self): - n = self.n_points - points = self.get_random_point_set(n) - points[:, 0] *= 2 - points += DOWN - points = sorted(points, key=lambda p: p[0]) - dots = self.get_dots(points) - - windmill = self.get_windmill(points, points[3]) - region_rects = self.rects = self.get_left_right_colorings(windmill) - pivot_dot = self.get_pivot_dot(windmill) - pivot_dot.set_color(WHITE) - - dot_rects = VGroup(*map(SurroundingRectangle, dots)) - - question = TextMobject("What about an even number?") - # question.to_corner(UL) - question.to_edge(UP) - counter_label = TextMobject("\\# Points", ":") - counter = Integer(0) - counter_group = VGroup(counter_label, counter) - counter_group.arrange(RIGHT) - counter.align_to(counter_label[1], DOWN) - counter_group.next_to(question, DOWN, MED_LARGE_BUFF) - counter_group.set_color(YELLOW) - # counter_group.align_to(question, LEFT) - - self.add(question, counter_label) - self.add(windmill, dots, pivot_dot) - - self.add_dot_color_updater(dots, windmill) - self.add(region_rects, question, counter_group, windmill, dots, pivot_dot) - - self.play( - ShowIncreasingSubsets(dot_rects), - ChangingDecimal(counter, lambda a: len(dot_rects)), - rate_func=linear - ) - self.play(FadeOut(dot_rects)) - self.wait() - - # region_rects.suspend_updating() - # self.play( - # FadeIn(region_rects), - # FadeOut(dot_rects), - # ) - # region_rects.resume_updating() - # self.wait() - - # Count by color - blue_rects = dot_rects[:3] - blue_rects.set_color(BLUE) - brown_rects = dot_rects[4:] - brown_rects.set_color(GREY_BROWN) - pivot_rect = dot_rects[3] - pivot_rect.set_color(GREY_BROWN) - - blues_label = TextMobject("\\# Blues", ":") - blues_counter = Integer(len(blue_rects)) - blues_group = VGroup(blues_label, blues_counter) - blues_group.set_color(BLUE) - browns_label = TextMobject("\\# Browns", ":") - browns_counter = Integer(len(brown_rects)) - browns_group = VGroup(browns_label, browns_counter) - browns_group.set_color(interpolate_color(GREY_BROWN, WHITE, 0.5)) - groups = VGroup(blues_group, browns_group) - for group in groups: - group.arrange(RIGHT) - group[-1].align_to(group[0][-1], DOWN) - groups.arrange(DOWN, aligned_edge=LEFT) - groups.next_to(counter_group, DOWN, aligned_edge=LEFT) - - self.play( - FadeInFrom(blues_group, UP), - ShowCreation(blue_rects), - ) - self.play( - FadeInFrom(browns_group, UP), - ShowCreation(brown_rects), - ) - self.wait() - - # Pivot counts as brown - pivot_words = TextMobject("Pivot counts as brown") - arrow = Vector(LEFT) - arrow.next_to(pivot_dot, RIGHT, SMALL_BUFF) - pivot_words.next_to(arrow, RIGHT, SMALL_BUFF) - - self.play( - FadeInFrom(pivot_words, LEFT), - ShowCreation(arrow), - ) - self.play( - ShowCreation(pivot_rect), - ChangeDecimalToValue(browns_counter, len(brown_rects) + 1), - FadeOut(pivot_dot), - ) - self.wait() - self.play( - FadeOut(dot_rects), - FadeOut(pivot_words), - FadeOut(arrow), - ) - self.wait() - - blues_counter.add_updater( - lambda c: c.set_value(len(list(filter( - lambda d: d.get_fill_color() == Color(BLUE), - dots - )))) - ) - browns_counter.add_updater( - lambda c: c.set_value(len(list(filter( - lambda d: d.get_fill_color() == Color(GREY_BROWN), - dots - )))) - ) - - self.windmill = windmill - self.dots = dots - self.points = points - self.question = question - self.counter_group = VGroup( - counter_group, - blues_group, - browns_group, - ) - - def choose_halfway_point(self): - windmill = self.windmill - points = self.points - n = self.n_points - - p_label = TexMobject("P_0") - p_label.next_to(points[n // 2], RIGHT, SMALL_BUFF) - - pivot_tracker = VectorizedPoint(windmill.pivot) - - def update_pivot(w): - w.pivot = pivot_tracker.get_center() - - windmill.add_updater(update_pivot) - - self.play( - pivot_tracker.move_to, points[n // 2], - run_time=2 - ) - self.play(FadeInFrom(p_label, LEFT)) - self.wait() - windmill.remove_updater(update_pivot) - - def add_total_rotation_label(self): - super().add_total_rotation_label() - self.rotation_label.scale(0.8, about_edge=UL) - - self.play( - FadeOut(self.question), - FadeIn(self.rotation_label), - self.counter_group.to_edge, UP, - ) - - -class TwoTakeaways(TeacherStudentsScene): - def construct(self): - title = TextMobject("Two takeaways") - title.scale(2) - title.to_edge(UP) - - line = Line() - line.match_width(title) - line.next_to(title, DOWN, SMALL_BUFF) - - items = VGroup(*[ - TextMobject("1) Social"), - TextMobject("2) Mathematical"), - ]) - items.scale(1.5) - items.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - items.next_to(line, DOWN, buff=MED_LARGE_BUFF) - - self.play( - ShowCreation(line), - GrowFromPoint(title, self.hold_up_spot), - self.teacher.change, "raise_right_hand", - ) - self.change_all_student_modes("pondering") - self.wait() - for item in items: - self.play(FadeInFrom(item, LEFT)) - item.big = item.copy() - item.small = item.copy() - item.big.scale(1.5, about_edge=LEFT) - item.big.set_color(BLUE) - item.small.scale(0.75, about_edge=LEFT) - item.small.fade(0.5) - self.play(self.teacher.change, "happy") - self.wait() - for i, j in [(0, 1), (1, 0)]: - self.play( - items[i].become, items[i].big, - items[j].become, items[j].small, - ) - self.wait() - - -class EasyToFoolYourself(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs": { - "color": GREY_BROWN, - } - } - - def construct(self): - morty = self.pi_creature - morty.to_corner(DL) - - bubble = ThoughtBubble() - for i, part in enumerate(bubble): - part.shift(2 * i * SMALL_BUFF * DOWN) - bubble.pin_to(morty) - - fool_word = TextMobject("Fool") - fool_word.scale(1.5) - fool_arrow = Vector(LEFT) - fool_arrow.next_to(morty, RIGHT, buff=0) - fool_word.next_to(fool_arrow, RIGHT) - - self.add(morty) - self.play( - ShowCreation(bubble), - morty.change, "pondering", - ) - self.play( - bubble[3].set_fill, GREEN_SCREEN, 0.5, - ) - self.wait() - self.play(morty.change, "thinking") - self.play( - FadeInFrom(fool_word, LEFT), - ShowCreation(fool_arrow), - ) - self.wait() - self.pi_creature_says( - "Isn't it\\\\obvious?", - target_mode="maybe", - added_anims=[FadeOut(bubble)] - ) - self.wait(4) - - # - words = TextMobject("No it's not!") - words.scale(1.5) - words.set_color(RED) - words.next_to(morty.bubble, RIGHT, LARGE_BUFF) - words.match_y(morty.bubble.content) - - self.play( - FadeInFromLarge(words), - morty.change, "guilty", - ) - self.wait() - - # for i, part in enumerate(bubble): - # self.add(Integer(i).move_to(part)) - - -class FailureToEmpathize(PiCreatureScene): - def construct(self): - randy, morty = self.pi_creatures - - # What a mess... - big_bubble = ThoughtBubble(height=4, width=5) - big_bubble.scale(1.75) - big_bubble.flip(UR) - for part in big_bubble: - part.rotate(90 * DEGREES) - big_bubble[:3].rotate(-30 * DEGREES) - for i, part in enumerate(big_bubble[:3]): - part.rotate(30 * DEGREES) - part.shift((3 - i) * SMALL_BUFF * DOWN) - big_bubble[0].shift(MED_SMALL_BUFF * RIGHT) - big_bubble[:3].next_to(big_bubble[3], LEFT) - big_bubble[:3].shift(0.3 * DOWN) - big_bubble.set_fill(DARKER_GREY) - big_bubble.to_corner(UR) - - equation = TexMobject( - "\\sum_{k=1}^n (2k - 1) = n^2" - ) - self.pi_creature_thinks( - randy, equation, - target_mode="confused", - look_at_arg=equation, - ) - randy_group = VGroup( - randy, randy.bubble, - randy.bubble.content - ) - self.wait() - self.play( - DrawBorderThenFill(big_bubble), - morty.change, "confused", - randy_group.scale, 0.5, - randy_group.move_to, big_bubble.get_bubble_center(), - randy_group.shift, 0.5 * DOWN + RIGHT, - ) - self.wait() - self.play(morty.change, "maybe") - self.wait(2) - - # Zoom out - morty_group = VGroup(morty, big_bubble) - ap = 5 * RIGHT + 2.5 * UP - self.add(morty_group, randy_group) - self.play( - morty_group.scale, 2, {"about_point": ap}, - morty_group.fade, 1, - randy_group.scale, 2, {"about_point": ap}, - run_time=2 - ) - self.wait() - - def create_pi_creatures(self): - randy = Randolph() - morty = Mortimer() - randy.flip().to_corner(DR) - morty.flip().to_corner(DL) - - return (randy, morty) - - -class DifficultyEstimateVsReality(Scene): - def construct(self): - axes = Axes( - x_min=-1, - x_max=10, - x_axis_config={ - "include_tip": False, - }, - y_min=-1, - y_max=5, - ) - axes.set_height(FRAME_HEIGHT - 1) - axes.center() - axes.x_axis.tick_marks.set_opacity(0) - - y_label = TextMobject("Average score") - y_label.scale(1.25) - y_label.rotate(90 * DEGREES) - y_label.next_to(axes.y_axis, LEFT, SMALL_BUFF) - y_label.shift(UP) - - estimated = [1.8, 2.6, 3, 4, 5] - actual = [1.5, 0.5, 1, 1.2, 1.8] - - colors = [GREEN, RED] - estimated_color, actual_color = colors - - estimated_bars = VGroup() - actual_bars = VGroup() - bar_pairs = VGroup() - - width = 0.25 - for a, e in zip(actual, estimated): - bars = VGroup( - Rectangle(width=width, height=e), - Rectangle(width=width, height=a), - ) - bars.set_stroke(width=1) - bars[0].set_fill(estimated_color, 0.75) - bars[1].set_fill(actual_color, 0.75) - bars.arrange(RIGHT, buff=0, aligned_edge=DOWN) - bar_pairs.add(bars) - estimated_bars.add(bars[0]) - actual_bars.add(bars[1]) - - bar_pairs.arrange(RIGHT, buff=1.5, aligned_edge=DOWN) - bar_pairs.move_to(axes.c2p(5, 0), DOWN) - for bp in bar_pairs: - for bar in bp: - bar.save_state() - bar.stretch(0, 1, about_edge=DOWN) - - x_labels = VGroup(*[ - TextMobject("Q{}".format(i)).next_to(bp, DOWN) - for i, bp in zip(it.count(1), bar_pairs) - ]) - - data_labels = VGroup( - TextMobject("Estimated average"), - TextMobject("Actual average"), - ) - data_labels.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - data_labels.to_edge(UP) - for color, label in zip(colors, data_labels): - square = Square() - square.set_height(0.5) - square.set_fill(color, 0.75) - square.set_stroke(WHITE, 1) - square.next_to(label, LEFT, SMALL_BUFF) - label.add(square) - - self.play(Write(axes)) - self.play(Write(y_label)) - - self.play( - LaggedStartMap( - FadeInFrom, x_labels, - lambda m: (m, UP), - run_time=2, - ), - LaggedStartMap( - Restore, - estimated_bars, - run_time=3, - ), - FadeIn(data_labels[0]), - ) - self.wait() - self.play( - LaggedStartMap( - Restore, - actual_bars, - run_time=3, - ), - FadeIn(data_labels[1]), - ) - self.wait() - - -class KeepInMindWhenTeaching(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "I don't know\\\\what you know!", - target_mode="pleading" - ) - self.wait(2) - self.play( - PiCreatureSays( - self.students[0], "We know", - target_mode="hooray", - ), - self.students[1].change, "happy", - self.students[2].change, "happy", - ) - self.wait(2) - - -class VastSpaceOfConsiderations(Scene): - def construct(self): - considerations = VGroup(*[ - TextMobject(phrase) - for phrase in [ - "Define ``outer'' points", - "Convex hulls", - "Linear equations", - "Sort points by when they're hit", - "Sort points by some kind of angle?", - "How does this permute the $n \\choose 2$ lines through pairs?", - "Some points are hit more than others, can we quantify this?", - ] - ]) - considerations.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - considerations.to_edge(LEFT) - - self.play(LaggedStart(*[ - FadeInFrom(mob, UP) - for mob in considerations - ], run_time=3, lag_ratio=0.2)) - - -class WhatStaysConstantWrapper(Scene): - CONFIG = { - "camera_config": { - "background_color": DARKER_GREY - } - } - - def construct(self): - rect = ScreenRectangle() - rect.set_height(6) - rect.set_stroke(WHITE, 2) - rect.set_fill(BLACK, 1) - title1 = TextMobject("What stays constant?") - title2 = TextMobject("Find an ", "``invariant''") - title2[1].set_color(YELLOW) - for title in [title1, title2]: - title.scale(2) - title.to_edge(UP) - rect.next_to(title1, DOWN) - - self.add(rect) - self.play(FadeInFromDown(title1)) - self.wait() - self.play( - FadeOutAndShift(title1, UP), - FadeInFromDown(title2), - ) - self.wait() - - -class CountHoles(Scene): - def construct(self): - labels = VGroup( - TextMobject("Genus ", "0"), - TextMobject("Genus ", "1"), - TextMobject("Genus ", "2"), - ) - - labels.scale(2) - labels.arrange(RIGHT, buff=1.5) - labels.move_to(2 * DOWN) - - equation = TexMobject("y^2 = x^3 + ax + b") - equation.scale(1.5) - equation.shift(UP) - equation.to_edge(LEFT) - # arrow = TexMobject("\\approx").scale(2) - arrow = Vector(2 * RIGHT) - arrow.next_to(equation, RIGHT) - - equation_text = TextMobject("Some other problem") - equation_text.next_to(equation, DOWN, MED_LARGE_BUFF) - equation_text.match_width(equation) - equation_text.set_color(YELLOW) - - self.play(LaggedStartMap( - FadeInFromDown, labels, - lag_ratio=0.5, - )) - self.wait() - self.play( - labels[1].shift, 4 * RIGHT, - FadeOut(labels[0::2]), - ) - self.play( - FadeInFrom(equation, RIGHT), - GrowArrow(arrow), - ) - self.play(FadeInFrom(equation_text, UP)) - self.wait() - - -class LorenzTransform(Scene): - def construct(self): - grid = NumberPlane( - # faded_line_ratio=0, - # y_axis_config={ - # "y_min": -10, - # "y_max": 10, - # } - ) - grid.scale(2) - back_grid = grid.copy() - back_grid.set_stroke(GREY, 0.5) - # back_grid.set_opacity(0.5) - - c_lines = VGroup(Line(DL, UR), Line(DR, UL)) - c_lines.scale(FRAME_HEIGHT) - c_lines.set_stroke(YELLOW, 3) - - equation = TexMobject( - "d\\tau^2 = dt^2 - dx^2" - ) - equation.scale(1.7) - equation.to_corner(UL, buff=MED_SMALL_BUFF) - equation.shift(2.75 * DOWN) - equation.set_stroke(BLACK, 5, background=True) - - self.add(back_grid, grid, c_lines) - self.add(equation) - beta = 0.4 - self.play( - grid.apply_matrix, np.array([ - [1, beta], - [beta, 1], - ]) / (1 - beta**2), - run_time=2 - ) - self.wait() - - -class OnceACleverDiscovery(Scene): - def construct(self): - energy = TextMobject("energy") - rect = SurroundingRectangle(energy) - words = TextMobject("Once a clever discovery") - vect = Vector(DR) - vect.next_to(rect.get_top(), UL, SMALL_BUFF) - words.next_to(vect.get_start(), UP) - words.set_color(YELLOW) - vect.set_color(YELLOW) - - self.play( - ShowCreation(vect), - ShowCreation(rect), - ) - self.play(FadeInFromDown(words)) - self.wait() - - -class TerryTaoQuote(Scene): - def construct(self): - image = ImageMobject("TerryTao") - image.set_height(4) - name = TextMobject("Terence Tao") - name.scale(1.5) - name.next_to(image, DOWN, buff=0.2) - tao = Group(image, name) - tao.to_corner(DL, buff=MED_SMALL_BUFF) - - tiny_tao = ImageMobject("TerryTaoIMO") - tiny_tao.match_height(image) - tiny_tao.next_to(image, RIGHT, LARGE_BUFF) - - quote = self.get_quote() - - self.play( - FadeInFromDown(image), - Write(name), - ) - self.wait() - self.play( - FadeInFrom(tiny_tao, LEFT) - ) - self.wait() - self.play(FadeOut(tiny_tao)) - - # - self.play( - FadeIn( - quote, - lag_ratio=0.05, - run_time=5, - rate_func=bezier([0, 0, 1, 1]) - ) - ) - self.wait() - story_line = Line() - story_line.match_width(quote.story_part) - story_line.next_to(quote.story_part, DOWN, buff=0) - story_line.set_color(TEAL), - self.play( - quote.story_part.set_color, TEAL, - ShowCreation(story_line), - lag_ratio=0.2, - ) - self.wait() - - def get_quote(self): - story_words = "fables, stories, and anecdotes" - quote = TextMobject( - """ - \\Large - ``Mathematical problems, or puzzles, are important to real mathematics - (like solving real-life problems), just as fables, stories, and anecdotes - are important to the young in understanding real life.''\\\\ - """, - alignment="", - arg_separator=" ", - substrings_to_isolate=[story_words] - ) - quote.story_part = quote.get_part_by_tex(story_words) - quote.set_width(FRAME_WIDTH - 2.5) - quote.to_edge(UP) - - return quote - - -class WindmillFairyTale(Scene): - def construct(self): - paths = SVGMobject(file_name="windmill_fairytale") - - paths.set_height(FRAME_HEIGHT - 1) - paths.set_stroke(width=0) - paths.set_fill([LIGHT_GREY, WHITE]) - - for path in paths: - path.reverse_points() - - self.play(Write(paths[0], run_time=3)) - self.wait() - self.play( - LaggedStart( - FadeInFrom(paths[1], RIGHT), - FadeInFrom(paths[2], RIGHT), - lag_ratio=0.2, - run_time=3, - ) - ) - - -class SolveAProblemOneDay(SpiritOfIMO, PiCreatureScene): - def construct(self): - randy = self.pi_creature - light_bulb = Lightbulb() - light_bulb.base = light_bulb[:3] - light_bulb.light = light_bulb[3:] - light_bulb.set_height(1) - light_bulb.next_to(randy, UP, MED_LARGE_BUFF) - - light = self.get_light(light_bulb.get_center()) - - bubble = ThoughtBubble() - bubble.pin_to(randy) - - you = TextMobject("You") - you.scale(1.5) - arrow = Vector(LEFT) - arrow.next_to(randy, RIGHT) - you.next_to(arrow) - - self.play( - ShowCreation(bubble), - randy.change, "pondering", - ) - self.play( - FadeInFrom(you, LEFT), - GrowArrow(arrow) - ) - self.wait(2) - self.play( - FadeInFromDown(light_bulb), - randy.change, "hooray", - ) - self.play( - LaggedStartMap( - VFadeInThenOut, light, - run_time=2 - ), - randy.change, "thinking", light, - ) - self.wait(2) - - -class QuixoteReference(Scene): - def construct(self): - rect = FullScreenFadeRectangle() - rect.set_fill([DARK_GREY, GREY]) - - windmill = SVGMobject("windmill") - windmill.set_fill([GREY_BROWN, WHITE], 1) - windmill.set_stroke(width=0) - windmill.set_height(6) - windmill.to_edge(RIGHT) - # windmill.to_edge(DOWN, buff=0) - - quixote = SVGMobject("quixote") - quixote.flip() - quixote.set_height(4) - quixote.to_edge(LEFT) - quixote.set_stroke(BLACK, width=0) - quixote.set_fill(BLACK, 1) - quixote.align_to(windmill, DOWN) - - self.add(rect) - # self.add(windmill) - self.play(LaggedStart( - DrawBorderThenFill(windmill), - DrawBorderThenFill( - quixote, - stroke_width=1, - ), - lag_ratio=0.4, - run_time=3 - )) - self.wait() - - -class WindmillEndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons": [ - "Juan Benet", - "Kurt Dicus", - "Vassili Philippov", - "Davie Willimoto", - "Burt Humburg", - "Hardik Meisheri", - "L. Z.", - "Matt Russell", - "Scott Gray", - "soekul", - "Tihan Seale", - "D. Sivakumar", - "Richard Barthel", - "Ali Yahya", - "Arthur Zey", - "dave nicponski", - "Joseph Kelly", - "Kaustuv DeBiswas", - "kkm", - "Lambda AI Hardware", - "Lukas Biewald", - "Mark Heising", - "Nicholas Cahill", - "Peter Mcinerney", - "Quantopian", - "Roy Larson", - "Scott Walter, Ph.D.", - "Tauba Auerbach", - "Yana Chernobilsky", - "Yu Jun", - "Jordan Scales", - "Lukas -krtek.net- Novy", - "Britt Selvitelle", - "David Gow", - "J", - "Jonathan Wilson", - "Joseph John Cox", - "Magnus Dahlström", - "Randy C. Will", - "Ryan Atallah", - "Luc Ritchie", - "1stViewMaths", - "Adrian Robinson", - "Aidan Shenkman", - "Alex Mijalis", - "Alexis Olson", - "Andreas Benjamin Brössel", - "Andrew Busey", - "Ankalagon", - "Antoine Bruguier", - "Antonio Juarez", - "Arjun Chakroborty", - "Art Ianuzzi", - "Austin Goodman", - "Awoo", - "Ayan Doss", - "AZsorcerer", - "Barry Fam", - "Bernd Sing", - "Boris Veselinovich", - "Bradley Pirtle", - "Brian Staroselsky", - "Charles Southerland", - "Charlie N", - "Chris Connett", - "Christian Kaiser", - "Clark Gaebel", - "Cooper Jones", - "Danger Dai", - "Daniel Pang", - "Dave B", - "Dave Kester", - "David B. Hill", - "David Clark", - "DeathByShrimp", - "Delton Ding", - "Dheeraj Vepakomma", - "eaglle", - "Empirasign", - "emptymachine", - "Eric Younge", - "Ero Carrera", - "Eryq Ouithaqueue", - "Federico Lebron", - "Fernando Via Canel", - "Gero Bone-Winkel", - "Giovanni Filippi", - "Hal Hildebrand", - "Hitoshi Yamauchi", - "Isaac Jeffrey Lee", - "Ivan Sorokin", - "j eduardo perez", - "Jacob Harmon", - "Jacob Hartmann", - "Jacob Magnuson", - "Jameel Syed", - "Jason Hise", - "Jeff Linse", - "Jeff Straathof", - "John C. Vesey", - "John Griffith", - "John Haley", - "John V Wertheim", - "Jonathan Eppele", - "Jordan A Purcell", - "Josh Kinnear", - "Joshua Claeys", - "Kai-Siang Ang", - "Kanan Gill", - "Kartik Cating-Subramanian", - "L0j1k", - "Lee Redden", - "Linh Tran", - "Ludwig Schubert", - "Magister Mugit", - "Mark B Bahu", - "Martin Price", - "Mathias Jansson", - "Matt Langford", - "Matt Roveto", - "Matthew Bouchard", - "Matthew Cocke", - "Michael Faust", - "Michael Hardel", - "Mirik Gogri", - "Mustafa Mahdi", - "Márton Vaitkus", - "Nero Li", - "Nikita Lesnikov", - "Omar Zrien", - "Owen Campbell-Moore", - "Patrick Lucas", - "Peter Ehrnstrom", - "RedAgent14", - "rehmi post", - "Ripta Pasay", - "Rish Kundalia", - "Roman Sergeychik", - "Roobie", - "Ryan Williams", - "Sebastian Garcia", - "Solara570", - "Steven Siddals", - "Stevie Metke", - "Tal Einav", - "Ted Suzman", - "Thomas Tarler", - "Tianyu Ge", - "Tom Fleming", - "Tyler VanValkenburg", - "Valeriy Skobelev", - "Vinicius Reis", - "Xuanji Li", - "Yavor Ivanov", - "YinYangBalance.Asia", - "Zach Cardwell", - ], - } - - -class Thumbnail(WindmillScene): - CONFIG = { - "dot_config": { - "radius": 0.15, - "stroke_width": 1, - }, - "random_seed": 7, - "animate": False, - } - - def construct(self): - points = self.get_random_point_set(11) - points[:, 0] *= 1.7 - points += 0.5 * LEFT - - points[1] = ORIGIN - points[10] += LEFT - points[6] += 3 * RIGHT - - windmill = self.get_windmill( - points, points[1], - angle=45 * DEGREES, - ) - dots = self.get_dots(points) - # rects = self.get_left_right_colorings(windmill) - pivot_dot = self.get_pivot_dot(windmill) - pivot_dot.scale(2) - pivot_dot.set_color(WHITE) - - new_pivot = points[5] - new_pivot2 = points[3] - - flash = Flash(pivot_dot, flash_radius=0.5) - - wa = windmill.get_angle() - arcs = VGroup(*[ - Arc( - start_angle=wa + a, - angle=90 * DEGREES, - radius=1.5, - stroke_width=10, - ).add_tip(tip_length=0.7) - for a in [0, PI] - ]) - arcs.move_to(windmill.pivot) - arcs.set_color([LIGHT_GREY, WHITE]) - - polygon1 = Polygon( - (FRAME_HEIGHT * UP + FRAME_WIDTH * LEFT) / 2, - (FRAME_HEIGHT * UP + FRAME_HEIGHT * RIGHT) / 2, - (FRAME_HEIGHT * DOWN + FRAME_HEIGHT * LEFT) / 2, - (FRAME_HEIGHT * DOWN + FRAME_WIDTH * LEFT) / 2, - ) - polygon1.set_color([BLUE, DARKER_GREY]) - polygon1.set_fill(opacity=0.5) - polygon2 = Polygon( - (FRAME_HEIGHT * UP + FRAME_WIDTH * RIGHT) / 2, - (FRAME_HEIGHT * UP + FRAME_HEIGHT * RIGHT) / 2, - (FRAME_HEIGHT * DOWN + FRAME_HEIGHT * LEFT) / 2, - (FRAME_HEIGHT * DOWN + FRAME_WIDTH * RIGHT) / 2, - ) - polygon2.set_sheen_direction(DR) - polygon2.set_color([GREY_BROWN, BLACK]) - polygon2.set_fill(opacity=1) - - self.add(polygon1, polygon2) - # self.add(rects[0]) - self.add(windmill, dots, pivot_dot) - self.add(arcs) - self.add(flash.mobject) - self.add_dot_color_updater(dots, windmill, color2=WHITE) - - words = TextMobject("Next\\\\", "pivot") - words2 = TextMobject("Next\\\\", "next\\\\", "pivot", alignment="") - words.scale(2) - words2.scale(2) - # words.next_to(windmill.pivot, RIGHT) - words.to_edge(UR) - words2.to_corner(DL) - - arrow = Arrow(words[1].get_left(), new_pivot, buff=0.6) - arrow.set_stroke(width=10) - arrow.set_color(YELLOW) - arrow2 = Arrow(words2[-1].get_right(), new_pivot2, buff=0.6) - arrow2.match_style(arrow) - arrow.rotate( - arrow2.get_angle() + PI - arrow.get_angle(), - about_point=new_pivot, - ) - - self.add(words, arrow) - self.add(words2, arrow2) - - # for i, dot in enumerate(dots): - # self.add(Integer(i).move_to(dot)) - - if self.animate: - sorted_dots = VGroup(*dots) - sorted_dots.sort(lambda p: np.dot(p, DR)) - - self.play( - polygon1.shift, FRAME_WIDTH * LEFT, - polygon2.shift, FRAME_WIDTH * RIGHT, - LaggedStart(*[ - ApplyMethod(mob.scale, 0) - for mob in [sorted_dots[6], *flash.mobject, windmill, pivot_dot] - ]), - LaggedStart(*[ - ApplyMethod(dot.to_edge, LEFT, {"buff": -1}) - for dot in sorted_dots[:6] - ]), - LaggedStart(*[ - ApplyMethod(dot.to_edge, RIGHT, {"buff": -1}) - for dot in sorted_dots[7:] - ]), - LaggedStart(*[ - FadeOutAndShift(word, RIGHT) - for word in words - ]), - LaggedStart(*[ - FadeOutAndShift(word, LEFT) - for word in words2 - ]), - LaggedStartMap( - Uncreate, - VGroup(arrow, arrow2, *arcs), - ), - run_time=3, - ) - - -class ThumbanailAnimated(Thumbnail): - CONFIG = { - "animate": True, - } - - -class Thumbnail2(Scene): - def construct(self): - words = TextMobject("Olympics\\\\", "for\\\\", "math", alignment="") - # words.arrange(DOWN, aligned_edge=LEFT) - - words.set_height(FRAME_HEIGHT - 1.5) - words.to_edge(LEFT) - - logo = ImageMobject("imo_logo") - logo.set_height(4.5) - logo.to_corner(DR, buff=LARGE_BUFF) - - rect = FullScreenFadeRectangle() - rect.set_fill([GREY, BLACK], 1) - - self.clear() - self.add(rect) - self.add(words) - self.add(logo) diff --git a/from_3b1b/old/zeta.py b/from_3b1b/old/zeta.py deleted file mode 100644 index 5bf88bed..00000000 --- a/from_3b1b/old/zeta.py +++ /dev/null @@ -1,3343 +0,0 @@ - -from manimlib.imports import * - -import mpmath -mpmath.mp.dps = 7 - - -def zeta(z): - max_norm = FRAME_X_RADIUS - try: - return np.complex(mpmath.zeta(z)) - except: - return np.complex(max_norm, 0) - -def d_zeta(z): - epsilon = 0.01 - return (zeta(z + epsilon) - zeta(z))/epsilon - -class ZetaTransformationScene(ComplexTransformationScene): - CONFIG = { - "anchor_density" : 35, - "min_added_anchors" : 10, - "max_added_anchors" : 300, - "num_anchors_to_add_per_line" : 75, - "post_transformation_stroke_width" : 2, - "default_apply_complex_function_kwargs" : { - "run_time" : 5, - }, - "x_min" : 1, - "x_max" : int(FRAME_X_RADIUS+2), - "extra_lines_x_min" : -2, - "extra_lines_x_max" : 4, - "extra_lines_y_min" : -2, - "extra_lines_y_max" : 2, - } - def prepare_for_transformation(self, mob): - for line in mob.family_members_with_points(): - #Find point of line cloest to 1 on C - if not isinstance(line, Line): - line.insert_n_curves(self.min_added_anchors) - continue - p1 = line.get_start()+LEFT - p2 = line.get_end()+LEFT - t = (-np.dot(p1, p2-p1))/(get_norm(p2-p1)**2) - closest_to_one = interpolate( - line.get_start(), line.get_end(), t - ) - #See how big this line will become - diameter = abs(zeta(complex(*closest_to_one[:2]))) - target_num_curves = np.clip( - int(self.anchor_density*np.pi*diameter), - self.min_added_anchors, - self.max_added_anchors, - ) - num_curves = line.get_num_curves() - if num_curves < target_num_curves: - line.insert_n_curves(target_num_curves-num_curves) - line.make_smooth() - - def add_extra_plane_lines_for_zeta(self, animate = False, **kwargs): - dense_grid = self.get_dense_grid(**kwargs) - if animate: - self.play(ShowCreation(dense_grid)) - self.plane.add(dense_grid) - self.add(self.plane) - - def get_dense_grid(self, step_size = 1./16): - epsilon = 0.1 - x_range = np.arange( - max(self.x_min, self.extra_lines_x_min), - min(self.x_max, self.extra_lines_x_max), - step_size - ) - y_range = np.arange( - max(self.y_min, self.extra_lines_y_min), - min(self.y_max, self.extra_lines_y_max), - step_size - ) - vert_lines = VGroup(*[ - Line( - self.y_min*UP, - self.y_max*UP, - ).shift(x*RIGHT) - for x in x_range - if abs(x-1) > epsilon - ]) - vert_lines.set_color_by_gradient( - self.vert_start_color, self.vert_end_color - ) - horiz_lines = VGroup(*[ - Line( - self.x_min*RIGHT, - self.x_max*RIGHT, - ).shift(y*UP) - for y in y_range - if abs(y) > epsilon - ]) - horiz_lines.set_color_by_gradient( - self.horiz_start_color, self.horiz_end_color - ) - dense_grid = VGroup(horiz_lines, vert_lines) - dense_grid.set_stroke(width = 1) - return dense_grid - - def add_reflected_plane(self, animate = False): - reflected_plane = self.get_reflected_plane() - if animate: - self.play(ShowCreation(reflected_plane, run_time = 5)) - self.plane.add(reflected_plane) - self.add(self.plane) - - def get_reflected_plane(self): - reflected_plane = self.plane.copy() - reflected_plane.rotate(np.pi, UP, about_point = RIGHT) - for mob in reflected_plane.family_members_with_points(): - mob.set_color( - Color(rgb = 1-0.5*color_to_rgb(mob.get_color())) - ) - self.prepare_for_transformation(reflected_plane) - reflected_plane.submobjects = list(reversed( - reflected_plane.family_members_with_points() - )) - return reflected_plane - - def apply_zeta_function(self, **kwargs): - transform_kwargs = dict(self.default_apply_complex_function_kwargs) - transform_kwargs.update(kwargs) - self.apply_complex_function(zeta, **kwargs) - -class TestZetaOnHalfPlane(ZetaTransformationScene): - CONFIG = { - "anchor_density" : 15, - } - def construct(self): - self.add_transformable_plane() - self.add_extra_plane_lines_for_zeta() - self.prepare_for_transformation(self.plane) - print(sum([ - mob.get_num_points() - for mob in self.plane.family_members_with_points() - ])) - print(len(self.plane.family_members_with_points())) - self.apply_zeta_function() - self.wait() - -class TestZetaOnFullPlane(ZetaTransformationScene): - def construct(self): - self.add_transformable_plane(animate = True) - self.add_extra_plane_lines_for_zeta(animate = True) - self.add_reflected_plane(animate = True) - self.apply_zeta_function() - - -class TestZetaOnLine(ZetaTransformationScene): - def construct(self): - line = Line(UP+20*LEFT, UP+20*RIGHT) - self.add_transformable_plane() - self.plane.submobjects = [line] - self.apply_zeta_function() - self.wait(2) - self.play(ShowCreation(line, run_time = 10)) - self.wait(3) - -###################### - -class IntroduceZeta(ZetaTransformationScene): - CONFIG = { - "default_apply_complex_function_kwargs" : { - "run_time" : 8, - } - } - def construct(self): - title = TextMobject("Riemann zeta function") - title.add_background_rectangle() - title.to_corner(UP+LEFT) - func_mob = VGroup( - TexMobject("\\zeta(s) = "), - TexMobject("\\sum_{n=1}^\\infty \\frac{1}{n^s}") - ) - func_mob.arrange(RIGHT, buff = 0) - for submob in func_mob: - submob.add_background_rectangle() - func_mob.next_to(title, DOWN) - - randy = Randolph().flip() - randy.to_corner(DOWN+RIGHT) - - self.add_foreground_mobjects(title, func_mob) - self.add_transformable_plane() - self.add_extra_plane_lines_for_zeta() - self.play(ShowCreation(self.plane, run_time = 2)) - reflected_plane = self.get_reflected_plane() - self.play(ShowCreation(reflected_plane, run_time = 2)) - self.plane.add(reflected_plane) - self.wait() - self.apply_zeta_function() - self.wait(2) - self.play(FadeIn(randy)) - self.play( - randy.change_mode, "confused", - randy.look_at, func_mob, - ) - self.play(Blink(randy)) - self.wait() - -class WhyPeopleMayKnowIt(TeacherStudentsScene): - def construct(self): - title = TextMobject("Riemann zeta function") - title.to_corner(UP+LEFT) - func_mob = TexMobject( - "\\zeta(s) = \\sum_{n=1}^\\infty \\frac{1}{n^s}" - ) - func_mob.next_to(title, DOWN, aligned_edge = LEFT) - self.add(title, func_mob) - - mercenary_thought = VGroup( - TexMobject("\\$1{,}000{,}000").set_color_by_gradient(GREEN_B, GREEN_D), - TexMobject("\\zeta(s) = 0") - ) - mercenary_thought.arrange(DOWN) - divergent_sum = VGroup( - TexMobject("1+2+3+4+\\cdots = -\\frac{1}{12}"), - TexMobject("\\zeta(-1) = -\\frac{1}{12}") - ) - divergent_sum.arrange(DOWN) - divergent_sum[0].set_color_by_gradient(YELLOW, MAROON_B) - divergent_sum[1].set_color(BLACK) - - #Thoughts - self.play(*it.chain(*[ - [pi.change_mode, "pondering", pi.look_at, func_mob] - for pi in self.get_pi_creatures() - ])) - self.random_blink() - self.student_thinks( - mercenary_thought, student_index = 2, - target_mode = "surprised", - ) - student = self.get_students()[2] - self.random_blink() - self.wait(2) - self.student_thinks( - divergent_sum, student_index = 1, - added_anims = [student.change_mode, "plain"] - ) - student = self.get_students()[1] - self.play( - student.change_mode, "confused", - student.look_at, divergent_sum, - ) - self.random_blink() - self.play(*it.chain(*[ - [pi.change_mode, "confused", pi.look_at, divergent_sum] - for pi in self.get_pi_creatures() - ])) - self.wait() - self.random_blink() - divergent_sum[1].set_color(WHITE) - self.play(Write(divergent_sum[1])) - self.random_blink() - self.wait() - - #Ask about continuation - self.student_says( - TextMobject("Can you explain \\\\" , "``analytic continuation''?"), - student_index = 1, - target_mode = "raise_right_hand" - ) - self.change_student_modes( - "raise_left_hand", - "raise_right_hand", - "raise_left_hand", - ) - self.play( - self.get_teacher().change_mode, "happy", - self.get_teacher().look_at, student.eyes, - ) - self.random_blink() - self.wait(2) - self.random_blink() - self.wait() - -class ComplexValuedFunctions(ComplexTransformationScene): - def construct(self): - title = TextMobject("Complex-valued function") - title.scale(1.5) - title.add_background_rectangle() - title.to_edge(UP) - self.add(title) - - z_in = Dot(UP+RIGHT, color = YELLOW) - z_out = Dot(4*RIGHT + 2*UP, color = MAROON_B) - arrow = Arrow(z_in, z_out, buff = 0.1) - arrow.set_color(WHITE) - z = TexMobject("z").next_to(z_in, DOWN+LEFT, buff = SMALL_BUFF) - z.set_color(z_in.get_color()) - f_z = TexMobject("f(z)").next_to(z_out, UP+RIGHT, buff = SMALL_BUFF) - f_z.set_color(z_out.get_color()) - - self.add(z_in, z) - self.wait() - self.play(ShowCreation(arrow)) - self.play( - ShowCreation(z_out), - Write(f_z) - ) - self.wait(2) - -class PreviewZetaAndContinuation(ZetaTransformationScene): - CONFIG = { - "default_apply_complex_function_kwargs" : { - "run_time" : 4, - } - } - def construct(self): - self.add_transformable_plane() - self.add_extra_plane_lines_for_zeta() - reflected_plane = self.get_reflected_plane() - - titles = [ - TextMobject( - "What does", "%s"%s, - "look like?", - alignment = "", - ) - for s in [ - "$\\displaystyle \\sum_{n=1}^\\infty \\frac{1}{n^s}$", - "analytic continuation" - ] - ] - for mob in titles: - mob[1].set_color(YELLOW) - mob.to_corner(UP+LEFT, buff = 0.7) - mob.add_background_rectangle() - - self.remove(self.plane) - self.play(Write(titles[0], run_time = 2)) - self.add_foreground_mobjects(titles[0]) - self.play(FadeIn(self.plane)) - self.apply_zeta_function() - reflected_plane.apply_complex_function(zeta) - reflected_plane.make_smooth() - reflected_plane.set_stroke(width = 2) - self.wait() - self.play(Transform(*titles)) - self.wait() - self.play(ShowCreation( - reflected_plane, - lag_ratio = 0, - run_time = 2 - )) - self.wait() - -class AssumeKnowledgeOfComplexNumbers(ComplexTransformationScene): - def construct(self): - z = complex(5, 2) - dot = Dot(z.real*RIGHT + z.imag*UP, color = YELLOW) - line = Line(ORIGIN, dot.get_center(), color = dot.get_color()) - x_line = Line(ORIGIN, z.real*RIGHT, color = GREEN_B) - y_line = Line(ORIGIN, z.imag*UP, color = RED) - y_line.shift(z.real*RIGHT) - complex_number_label = TexMobject( - "%d+%di"%(int(z.real), int(z.imag)) - ) - complex_number_label[0].set_color(x_line.get_color()) - complex_number_label[2].set_color(y_line.get_color()) - complex_number_label.next_to(dot, UP) - - text = VGroup( - TextMobject("Assumed knowledge:"), - TextMobject("1) What complex numbers are."), - TextMobject("2) How to work with them."), - TextMobject("3) Maybe derivatives?"), - ) - text.arrange(DOWN, aligned_edge = LEFT) - for words in text: - words.add_background_rectangle() - text[0].shift(LEFT) - text[-1].set_color(PINK) - text.to_corner(UP+LEFT) - - self.play(Write(text[0])) - self.wait() - self.play(FadeIn(text[1])) - self.play( - ShowCreation(x_line), - ShowCreation(y_line), - ShowCreation(VGroup(line, dot)), - Write(complex_number_label), - ) - self.play(Write(text[2])) - self.wait(2) - self.play(Write(text[3])) - self.wait() - self.play(text[3].fade) - -class DefineForRealS(PiCreatureScene): - def construct(self): - zeta_def, s_group = self.get_definition("s") - - self.initial_definition(zeta_def) - self.plug_in_two(zeta_def) - self.plug_in_three_and_four(zeta_def) - self.plug_in_negative_values(zeta_def) - - def initial_definition(self, zeta_def): - zeta_s, sum_terms, brace, sigma = zeta_def - - self.say("Let's define $\\zeta(s)$") - self.blink() - pre_zeta_s = VGroup( - *self.pi_creature.bubble.content.copy()[-4:] - ) - pre_zeta_s.add(VectorizedPoint(pre_zeta_s.get_right())) - self.play( - Transform(pre_zeta_s, zeta_s), - *self.get_bubble_fade_anims() - ) - self.remove(pre_zeta_s) - self.add(zeta_s) - self.wait() - - for count, term in enumerate(sum_terms): - self.play(FadeIn(term), run_time = 0.5) - if count%2 == 0: - self.wait() - self.play( - GrowFromCenter(brace), - Write(sigma), - self.pi_creature.change_mode, "pondering" - ) - self.wait() - - def plug_in_two(self, zeta_def): - two_def = self.get_definition("2")[0] - number_line = NumberLine( - x_min = 0, - x_max = 3, - tick_frequency = 0.25, - numbers_with_elongated_ticks = list(range(4)), - unit_size = 3, - ) - number_line.add_numbers() - number_line.next_to(self.pi_creature, LEFT) - number_line.to_edge(LEFT) - self.number_line = number_line - - lines, braces, dots, pi_dot = self.get_sum_lines(2) - fracs = VGroup(*[ - TexMobject("\\frac{1}{%d}"%((d+1)**2)).scale(0.7) - for d, brace in enumerate(braces) - ]) - for frac, brace, line in zip(fracs, braces, lines): - frac.set_color(line.get_color()) - frac.next_to(brace, UP, buff = SMALL_BUFF) - if frac is fracs[-1]: - frac.shift(0.5*RIGHT + 0.2*UP) - arrow = Arrow( - frac.get_bottom(), brace.get_top(), - tip_length = 0.1, - buff = 0.1 - ) - arrow.set_color(line.get_color()) - frac.add(arrow) - - pi_term = TexMobject("= \\frac{\\pi^2}{6}") - pi_term.next_to(zeta_def[1], RIGHT) - pi_arrow = Arrow( - pi_term[-1].get_bottom(), pi_dot, - color = pi_dot.get_color() - ) - approx = TexMobject("\\approx 1.645") - approx.next_to(pi_term) - - self.play(Transform(zeta_def, two_def)) - self.wait() - self.play(ShowCreation(number_line)) - - for frac, brace, line in zip(fracs, braces, lines): - self.play( - Write(frac), - GrowFromCenter(brace), - ShowCreation(line), - run_time = 0.7 - ) - self.wait(0.7) - self.wait() - self.play( - ShowCreation(VGroup(*lines[4:])), - Write(dots) - ) - self.wait() - self.play( - Write(pi_term), - ShowCreation(VGroup(pi_arrow, pi_dot)), - self.pi_creature.change_mode, "hooray" - ) - self.wait() - self.play( - Write(approx), - self.pi_creature.change_mode, "happy" - ) - self.wait(3) - self.play(*list(map(FadeOut, [ - fracs, pi_arrow, pi_dot, approx, - ]))) - self.lines = lines - self.braces = braces - self.dots = dots - self.final_dot = pi_dot - self.final_sum = pi_term - - def plug_in_three_and_four(self, zeta_def): - final_sums = ["1.202\\dots", "\\frac{\\pi^4}{90}"] - sum_terms, brace, sigma = zeta_def[1:] - for exponent, final_sum in zip([3, 4], final_sums): - self.transition_to_new_input(zeta_def, exponent, final_sum) - self.wait() - - arrow = Arrow(sum_terms.get_left(), sum_terms.get_right()) - arrow.next_to(sum_terms, DOWN) - smaller_words = TextMobject("Getting smaller") - smaller_words.next_to(arrow, DOWN) - self.arrow, self.smaller_words = arrow, smaller_words - - self.wait() - self.play( - ShowCreation(arrow), - Write(smaller_words) - ) - self.change_mode("happy") - self.wait(2) - - def plug_in_negative_values(self, zeta_def): - zeta_s, sum_terms, brace, sigma = zeta_def - arrow = self.arrow - smaller_words = self.smaller_words - bigger_words = TextMobject("Getting \\emph{bigger}?") - bigger_words.move_to(self.smaller_words) - - #plug in -1 - self.transition_to_new_input(zeta_def, -1, "-\\frac{1}{12}") - self.play( - Transform(self.smaller_words, bigger_words), - self.pi_creature.change_mode, "confused" - ) - new_sum_terms = TexMobject( - list("1+2+3+4+") + ["\\cdots"] - ) - new_sum_terms.move_to(sum_terms, LEFT) - arrow.target = arrow.copy().next_to(new_sum_terms, DOWN) - arrow.target.stretch_to_fit_width(new_sum_terms.get_width()) - bigger_words.next_to(arrow.target, DOWN) - new_brace = Brace(new_sum_terms, UP) - self.play( - Transform(sum_terms, new_sum_terms), - Transform(brace, new_brace), - sigma.next_to, new_brace, UP, - MoveToTarget(arrow), - Transform(smaller_words, bigger_words), - self.final_sum.next_to, new_sum_terms, RIGHT - ) - self.wait(3) - - #plug in -2 - new_sum_terms = TexMobject( - list("1+4+9+16+") + ["\\cdots"] - ) - new_sum_terms.move_to(sum_terms, LEFT) - new_zeta_def, ignore = self.get_definition("-2") - zeta_minus_two, ignore, ignore, new_sigma = new_zeta_def - new_sigma.next_to(brace, UP) - new_final_sum = TexMobject("=0") - new_final_sum.next_to(new_sum_terms) - lines, braces, dots, final_dot = self.get_sum_lines(-2) - - self.play( - Transform(zeta_s, zeta_minus_two), - Transform(sum_terms, new_sum_terms), - Transform(sigma, new_sigma), - Transform(self.final_sum, new_final_sum), - Transform(self.lines, lines), - Transform(self.braces, braces), - ) - self.wait() - self.change_mode("pleading") - self.wait(2) - - def get_definition(self, input_string, input_color = YELLOW): - inputs = VGroup() - num_shown_terms = 4 - n_input_chars = len(input_string) - - zeta_s_eq = TexMobject("\\zeta(%s) = "%input_string) - zeta_s_eq.to_edge(LEFT, buff = LARGE_BUFF) - zeta_s_eq.shift(0.5*UP) - inputs.add(*zeta_s_eq[2:2+n_input_chars]) - - sum_terms = TexMobject(*it.chain(*list(zip( - [ - "\\frac{1}{%d^{%s}}"%(d, input_string) - for d in range(1, 1+num_shown_terms) - ], - it.cycle(["+"]) - )))) - sum_terms.add(TexMobject("\\cdots").next_to(sum_terms)) - sum_terms.next_to(zeta_s_eq, RIGHT) - for x in range(num_shown_terms): - inputs.add(*sum_terms[2*x][-n_input_chars:]) - - - brace = Brace(sum_terms, UP) - sigma = TexMobject( - "\\sum_{n=1}^\\infty \\frac{1}{n^{%s}}"%input_string - ) - sigma.next_to(brace, UP) - inputs.add(*sigma[-n_input_chars:]) - - inputs.set_color(input_color) - group = VGroup(zeta_s_eq, sum_terms, brace, sigma) - return group, inputs - - def get_sum_lines(self, exponent, line_thickness = 6): - num_lines = 100 if exponent > 0 else 6 - powers = [0] + [x**(-exponent) for x in range(1, num_lines)] - power_sums = np.cumsum(powers) - lines = VGroup(*[ - Line( - self.number_line.number_to_point(s1), - self.number_line.number_to_point(s2), - ) - for s1, s2 in zip(power_sums, power_sums[1:]) - ]) - lines.set_stroke(width = line_thickness) - # VGroup(*lines[:4]).set_color_by_gradient(RED, GREEN_B) - # VGroup(*lines[4:]).set_color_by_gradient(GREEN_B, MAROON_B) - VGroup(*lines[::2]).set_color(MAROON_B) - VGroup(*lines[1::2]).set_color(RED) - - braces = VGroup(*[ - Brace(line, UP) - for line in lines[:4] - ]) - dots = TexMobject("...") - dots.stretch_to_fit_width( - 0.8 * VGroup(*lines[4:]).get_width() - ) - dots.next_to(braces, RIGHT, buff = SMALL_BUFF) - - final_dot = Dot( - self.number_line.number_to_point(power_sums[-1]), - color = GREEN_B - ) - - return lines, braces, dots, final_dot - - def transition_to_new_input(self, zeta_def, exponent, final_sum): - new_zeta_def = self.get_definition(str(exponent))[0] - lines, braces, dots, final_dot = self.get_sum_lines(exponent) - final_sum = TexMobject("=" + final_sum) - final_sum.next_to(new_zeta_def[1][-1]) - final_sum.shift(SMALL_BUFF*UP) - self.play( - Transform(zeta_def, new_zeta_def), - Transform(self.lines, lines), - Transform(self.braces, braces), - Transform(self.dots, dots), - Transform(self.final_dot, final_dot), - Transform(self.final_sum, final_sum), - self.pi_creature.change_mode, "pondering" - ) - -class ReadIntoZetaFunction(Scene): - CONFIG = { - "statement" : "$\\zeta(-1) = -\\frac{1}{12}$", - "target_mode" : "frustrated", - } - def construct(self): - randy = Randolph(mode = "pondering") - randy.shift(3*LEFT+DOWN) - paper = Rectangle(width = 4, height = 5) - paper.next_to(randy, RIGHT, aligned_edge = DOWN) - paper.set_color(WHITE) - max_width = 0.8*paper.get_width() - - title = TextMobject("$\\zeta(s)$ manual") - title.next_to(paper.get_top(), DOWN) - title.set_color(YELLOW) - paper.add(title) - paragraph_lines = VGroup( - Line(LEFT, RIGHT), - Line(LEFT, RIGHT).shift(0.2*DOWN), - Line(LEFT, ORIGIN).shift(0.4*DOWN) - ) - paragraph_lines.set_width(max_width) - paragraph_lines.next_to(title, DOWN, MED_LARGE_BUFF) - paper.add(paragraph_lines) - max_height = 1.5*paragraph_lines.get_height() - - statement = TextMobject(self.statement) - if statement.get_width() > max_width: - statement.set_width(max_width) - if statement.get_height() > max_height: - statement.set_height(max_height) - - statement.next_to(paragraph_lines, DOWN) - statement.set_color(GREEN_B) - paper.add(paragraph_lines.copy().next_to(statement, DOWN, MED_LARGE_BUFF)) - - randy.look_at(statement) - self.add(randy, paper) - self.play(Write(statement)) - self.play( - randy.change_mode, self.target_mode, - randy.look_at, title - ) - self.play(Blink(randy)) - self.play(randy.look_at, statement) - self.wait() - -class ReadIntoZetaFunctionTrivialZero(ReadIntoZetaFunction): - CONFIG = { - "statement" : "$\\zeta(-2n) = 0$" - } - -class ReadIntoZetaFunctionAnalyticContinuation(ReadIntoZetaFunction): - CONFIG = { - "statement" : "...analytic \\\\ continuation...", - "target_mode" : "confused", - } - -class IgnoreNegatives(TeacherStudentsScene): - def construct(self): - definition = TexMobject(""" - \\zeta(s) = \\sum_{n=1}^{\\infty} \\frac{1}{n^s} - """) - VGroup(definition[2], definition[-1]).set_color(YELLOW) - definition.to_corner(UP+LEFT) - self.add(definition) - brace = Brace(definition, DOWN) - only_s_gt_1 = brace.get_text(""" - Only defined - for $s > 1$ - """) - only_s_gt_1[-3].set_color(YELLOW) - - - self.change_student_modes(*["confused"]*3) - words = TextMobject( - "Ignore $s \\le 1$ \\dots \\\\", - "For now." - ) - words[0][6].set_color(YELLOW) - words[1].set_color(BLACK) - self.teacher_says(words) - self.play(words[1].set_color, WHITE) - self.change_student_modes(*["happy"]*3) - self.play( - GrowFromCenter(brace), - Write(only_s_gt_1), - *it.chain(*[ - [pi.look_at, definition] - for pi in self.get_pi_creatures() - ]) - ) - self.random_blink(3) - -class RiemannFatherOfComplex(ComplexTransformationScene): - def construct(self): - name = TextMobject( - "Bernhard Riemann $\\rightarrow$ Complex analysis" - ) - name.to_corner(UP+LEFT) - name.shift(0.25*DOWN) - name.add_background_rectangle() - # photo = Square() - photo = ImageMobject("Riemann", invert = False) - photo.set_width(5) - photo.next_to(name, DOWN, aligned_edge = LEFT) - - - self.add(photo) - self.play(Write(name)) - self.wait() - - input_dot = Dot(2*RIGHT+UP, color = YELLOW) - arc = Arc(-2*np.pi/3) - arc.rotate(-np.pi) - arc.add_tip() - arc.shift(input_dot.get_top()-arc.points[0]+SMALL_BUFF*UP) - output_dot = Dot( - arc.points[-1] + SMALL_BUFF*(2*RIGHT+DOWN), - color = MAROON_B - ) - for dot, tex in (input_dot, "z"), (output_dot, "f(z)"): - dot.label = TexMobject(tex) - dot.label.add_background_rectangle() - dot.label.next_to(dot, DOWN+RIGHT, buff = SMALL_BUFF) - dot.label.set_color(dot.get_color()) - - self.play( - ShowCreation(input_dot), - Write(input_dot.label) - ) - self.play(ShowCreation(arc)) - self.play( - ShowCreation(output_dot), - Write(output_dot.label) - ) - self.wait() - -class FromRealToComplex(ComplexTransformationScene): - CONFIG = { - "plane_config" : { - "space_unit_to_x_unit" : 2, - "space_unit_to_y_unit" : 2, - }, - "background_label_scale_val" : 0.7, - "output_color" : GREEN_B, - "num_lines_in_spiril_sum" : 1000, - } - def construct(self): - self.handle_background() - self.show_real_to_real() - self.transition_to_complex() - self.single_out_complex_exponent() - ##Fade to several scenes defined below - self.show_s_equals_two_lines() - self.transition_to_spiril_sum() - self.vary_complex_input() - self.show_domain_of_convergence() - self.ask_about_visualizing_all() - - def handle_background(self): - self.remove(self.background) - #Oh yeah, this is great practice... - self.background[-1].remove(*self.background[-1][-3:]) - - def show_real_to_real(self): - zeta = self.get_zeta_definition("2", "\\frac{\\pi^2}{6}") - number_line = NumberLine( - unit_size = 2, - tick_frequency = 0.5, - numbers_with_elongated_ticks = list(range(-2, 3)) - ) - number_line.add_numbers() - input_dot = Dot(number_line.number_to_point(2)) - input_dot.set_color(YELLOW) - - output_dot = Dot(number_line.number_to_point(np.pi**2/6)) - output_dot.set_color(self.output_color) - - arc = Arc( - 2*np.pi/3, start_angle = np.pi/6, - ) - arc.stretch_to_fit_width( - (input_dot.get_center()-output_dot.get_center())[0] - ) - arc.stretch_to_fit_height(0.5) - arc.next_to(input_dot.get_center(), UP, aligned_edge = RIGHT) - arc.add_tip() - - two = zeta[1][2].copy() - sum_term = zeta[-1] - self.add(number_line, *zeta[:-1]) - self.wait() - self.play(Transform(two, input_dot)) - self.remove(two) - self.add(input_dot) - self.play(ShowCreation(arc)) - self.play(ShowCreation(output_dot)) - self.play(Transform(output_dot.copy(), sum_term)) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(sum_term) - self.wait(2) - self.play( - ShowCreation( - self.background, - run_time = 2 - ), - FadeOut(VGroup(arc, output_dot, number_line)), - Animation(zeta), - Animation(input_dot) - ) - self.wait(2) - - self.zeta = zeta - self.input_dot = input_dot - - def transition_to_complex(self): - complex_zeta = self.get_zeta_definition("2+i", "???") - input_dot = self.input_dot - input_dot.generate_target() - input_dot.target.move_to( - self.background.num_pair_to_point((2, 1)) - ) - input_label = TexMobject("2+i") - input_label.set_color(YELLOW) - input_label.next_to(input_dot.target, DOWN+RIGHT, buff = SMALL_BUFF) - input_label.add_background_rectangle() - input_label.save_state() - input_label.replace(VGroup(*complex_zeta[1][2:5])) - input_label.background_rectangle.scale_in_place(0.01) - self.input_label = input_label - - self.play(Transform(self.zeta, complex_zeta)) - self.wait() - self.play( - input_label.restore, - MoveToTarget(input_dot) - ) - self.wait(2) - - def single_out_complex_exponent(self): - frac_scale_factor = 1.2 - - randy = Randolph() - randy.to_corner() - bubble = randy.get_bubble(height = 4) - bubble.set_fill(BLACK, opacity = 1) - - frac = VGroup( - VectorizedPoint(self.zeta[2][3].get_left()), - self.zeta[2][3], - VectorizedPoint(self.zeta[2][3].get_right()), - self.zeta[2][4], - ).copy() - frac.generate_target() - frac.target.scale(frac_scale_factor) - bubble.add_content(frac.target) - new_frac = TexMobject( - "\\Big(", "\\frac{1}{2}", "\\Big)", "^{2+i}" - ) - new_frac[-1].set_color(YELLOW) - new_frac.scale(frac_scale_factor) - new_frac.move_to(frac.target) - new_frac.shift(LEFT+0.2*UP) - - words = TextMobject("Not repeated \\\\", " multiplication") - words.scale(0.8) - words.set_color(RED) - words.next_to(new_frac, RIGHT) - - new_words = TextMobject("Not \\emph{super} \\\\", "crucial to know...") - new_words.replace(words) - new_words.scale_in_place(1.3) - - self.play(FadeIn(randy)) - self.play( - randy.change_mode, "confused", - randy.look_at, bubble, - ShowCreation(bubble), - MoveToTarget(frac) - ) - self.play(Blink(randy)) - self.play(Transform(frac, new_frac)) - self.play(Write(words)) - for x in range(2): - self.wait(2) - self.play(Blink(randy)) - self.play( - Transform(words, new_words), - randy.change_mode, "maybe" - ) - self.wait() - self.play(Blink(randy)) - self.play(randy.change_mode, "happy") - self.wait() - self.play(*list(map(FadeOut, [randy, bubble, frac, words]))) - - def show_s_equals_two_lines(self): - self.input_label.save_state() - zeta = self.get_zeta_definition("2", "\\frac{\\pi^2}{6}") - lines, output_dot = self.get_sum_lines(2) - sum_terms = self.zeta[2][:-1:3] - dots_copy = zeta[2][-1].copy() - pi_copy = zeta[3].copy() - def transform_and_replace(m1, m2): - self.play(Transform(m1, m2)) - self.remove(m1) - self.add(m2) - - self.play( - self.input_dot.shift, 2*DOWN, - self.input_label.fade, 0.7, - ) - self.play(Transform(self.zeta, zeta)) - - for term, line in zip(sum_terms, lines): - line.save_state() - line.next_to(term, DOWN) - term_copy = term.copy() - transform_and_replace(term_copy, line) - self.play(line.restore) - later_lines = VGroup(*lines[4:]) - transform_and_replace(dots_copy, later_lines) - self.wait() - transform_and_replace(pi_copy, output_dot) - self.wait() - - self.lines = lines - self.output_dot = output_dot - - def transition_to_spiril_sum(self): - zeta = self.get_zeta_definition("2+i", "1.15 - 0.44i") - zeta.set_width(FRAME_WIDTH-1) - zeta.to_corner(UP+LEFT) - lines, output_dot = self.get_sum_lines(complex(2, 1)) - - self.play( - self.input_dot.shift, 2*UP, - self.input_label.restore, - ) - self.wait() - self.play(Transform(self.zeta, zeta)) - self.wait() - self.play( - Transform(self.lines, lines), - Transform(self.output_dot, output_dot), - run_time = 2, - path_arc = -np.pi/6, - ) - self.wait() - - def vary_complex_input(self): - zeta = self.get_zeta_definition("s", "") - zeta[3].set_color(BLACK) - self.play(Transform(self.zeta, zeta)) - self.play(FadeOut(self.input_label)) - self.wait(2) - inputs = [ - complex(1.5, 1.8), - complex(1.5, -1), - complex(3, -1), - complex(1.5, 1.8), - complex(1.5, -1.8), - complex(1.4, -1.8), - complex(1.5, 0), - complex(2, 1), - ] - for s in inputs: - input_point = self.z_to_point(s) - lines, output_dot = self.get_sum_lines(s) - self.play( - self.input_dot.move_to, input_point, - Transform(self.lines, lines), - Transform(self.output_dot, output_dot), - run_time = 2 - ) - self.wait() - self.wait() - - def show_domain_of_convergence(self, opacity = 0.2): - domain = Rectangle( - width = FRAME_X_RADIUS-2, - height = FRAME_HEIGHT, - stroke_width = 0, - fill_color = YELLOW, - fill_opacity = opacity, - ) - domain.to_edge(RIGHT, buff = 0) - anti_domain = Rectangle( - width = FRAME_X_RADIUS+2, - height = FRAME_HEIGHT, - stroke_width = 0, - fill_color = RED, - fill_opacity = opacity, - ) - anti_domain.to_edge(LEFT, buff = 0) - - domain_words = TextMobject(""" - $\\zeta(s)$ happily - converges and - makes sense - """) - domain_words.to_corner(UP+RIGHT, buff = MED_LARGE_BUFF) - - anti_domain_words = TextMobject(""" - Not so much... - """) - anti_domain_words.next_to(ORIGIN, LEFT, buff = LARGE_BUFF) - anti_domain_words.shift(1.5*DOWN) - - self.play(FadeIn(domain)) - self.play(Write(domain_words)) - self.wait() - self.play(FadeIn(anti_domain)) - self.play(Write(anti_domain_words)) - self.wait(2) - self.play(*list(map(FadeOut, [ - anti_domain, anti_domain_words, - ]))) - self.domain_words = domain_words - - def ask_about_visualizing_all(self): - morty = Mortimer().flip() - morty.scale(0.7) - morty.to_corner(DOWN+LEFT) - bubble = morty.get_bubble(SpeechBubble, height = 4) - bubble.set_fill(BLACK, opacity = 0.5) - bubble.write(""" - How can we visualize - this for all inputs? - """) - - self.play(FadeIn(morty)) - self.play( - morty.change_mode, "speaking", - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(Blink(morty)) - self.wait(3) - self.play( - morty.change_mode, "pondering", - morty.look_at, self.input_dot, - *list(map(FadeOut, [ - bubble, bubble.content, self.domain_words - ])) - ) - arrow = Arrow(self.input_dot, self.output_dot, buff = SMALL_BUFF) - arrow.set_color(WHITE) - self.play(ShowCreation(arrow)) - self.play(Blink(morty)) - self.wait() - - def get_zeta_definition(self, input_string, output_string, input_color = YELLOW): - inputs = VGroup() - num_shown_terms = 4 - n_input_chars = len(input_string) - - zeta_s_eq = TexMobject("\\zeta(%s) = "%input_string) - zeta_s_eq.to_edge(LEFT, buff = LARGE_BUFF) - zeta_s_eq.shift(0.5*UP) - inputs.add(*zeta_s_eq[2:2+n_input_chars]) - - - raw_sum_terms = TexMobject(*[ - "\\frac{1}{%d^{%s}} + "%(d, input_string) - for d in range(1, 1+num_shown_terms) - ]) - sum_terms = VGroup(*it.chain(*[ - [ - VGroup(*term[:3]), - VGroup(*term[3:-1]), - term[-1], - ] - for term in raw_sum_terms - ])) - sum_terms.add(TexMobject("\\cdots").next_to(sum_terms[-1])) - sum_terms.next_to(zeta_s_eq, RIGHT) - for x in range(num_shown_terms): - inputs.add(*sum_terms[3*x+1]) - - output = TexMobject("= \\," + output_string) - output.next_to(sum_terms, RIGHT) - output.set_color(self.output_color) - - inputs.set_color(input_color) - group = VGroup(zeta_s_eq, sum_terms, output) - group.to_edge(UP) - group.add_to_back(BackgroundRectangle(group)) - return group - - def get_sum_lines(self, exponent, line_thickness = 6): - powers = [0] + [ - x**(-exponent) - for x in range(1, self.num_lines_in_spiril_sum) - ] - power_sums = np.cumsum(powers) - lines = VGroup(*[ - Line(*list(map(self.z_to_point, z_pair))) - for z_pair in zip(power_sums, power_sums[1:]) - ]) - widths = np.linspace(line_thickness, 0, len(list(lines))) - for line, width in zip(lines, widths): - line.set_stroke(width = width) - VGroup(*lines[::2]).set_color(MAROON_B) - VGroup(*lines[1::2]).set_color(RED) - - final_dot = Dot( - # self.z_to_point(power_sums[-1]), - self.z_to_point(zeta(exponent)), - color = self.output_color - ) - - return lines, final_dot - -class TerritoryOfExponents(ComplexTransformationScene): - def construct(self): - self.add_title() - familiar_territory = TextMobject("Familiar territory") - familiar_territory.set_color(YELLOW) - familiar_territory.next_to(ORIGIN, UP+RIGHT) - familiar_territory.shift(2*UP) - real_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - real_line.set_color(YELLOW) - arrow1 = Arrow(familiar_territory.get_bottom(), real_line.get_left()) - arrow2 = Arrow(familiar_territory.get_bottom(), real_line.get_right()) - VGroup(arrow1, arrow2).set_color(WHITE) - - extended_realm = TextMobject("Extended realm") - extended_realm.move_to(familiar_territory) - full_plane = Rectangle( - width = FRAME_WIDTH, - height = FRAME_HEIGHT, - fill_color = YELLOW, - fill_opacity = 0.3 - ) - - self.add(familiar_territory) - self.play(ShowCreation(arrow1)) - self.play( - Transform(arrow1, arrow2), - ShowCreation(real_line) - ) - self.play(FadeOut(arrow1)) - self.play( - FadeIn(full_plane), - Transform(familiar_territory, extended_realm), - Animation(real_line) - ) - - def add_title(self): - exponent = TexMobject( - "\\left(\\frac{1}{2}\\right)^s" - ) - exponent[-1].set_color(YELLOW) - exponent.next_to(ORIGIN, LEFT, MED_LARGE_BUFF).to_edge(UP) - self.add_foreground_mobjects(exponent) - -class ComplexExponentiation(Scene): - def construct(self): - self.extract_pure_imaginary_part() - self.add_on_planes() - self.show_imaginary_powers() - - def extract_pure_imaginary_part(self): - original = TexMobject( - "\\left(\\frac{1}{2}\\right)", "^{2+i}" - ) - split = TexMobject( - "\\left(\\frac{1}{2}\\right)", "^{2}", - "\\left(\\frac{1}{2}\\right)", "^{i}", - ) - VGroup(original[-1], split[1], split[3]).set_color(YELLOW) - VGroup(original, split).shift(UP) - real_part = VGroup(*split[:2]) - imag_part = VGroup(*split[2:]) - - brace = Brace(real_part) - we_understand = brace.get_text( - "We understand this" - ) - VGroup(brace, we_understand).set_color(GREEN_B) - - self.add(original) - self.wait() - self.play(*[ - Transform(*pair) - for pair in [ - (original[0], split[0]), - (original[1][0], split[1]), - (original[0].copy(), split[2]), - (VGroup(*original[1][1:]), split[3]), - ] - ]) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(real_part, imag_part) - self.wait() - self.play( - GrowFromCenter(brace), - FadeIn(we_understand), - real_part.set_color, GREEN_B - ) - self.wait() - self.play( - imag_part.move_to, imag_part.get_left(), - *list(map(FadeOut, [brace, we_understand, real_part])) - ) - self.wait() - self.imag_exponent = imag_part - - def add_on_planes(self): - left_plane = NumberPlane(x_radius = (FRAME_X_RADIUS-1)/2) - left_plane.to_edge(LEFT, buff = 0) - imag_line = Line(DOWN, UP).scale(FRAME_Y_RADIUS) - imag_line.set_color(YELLOW).fade(0.3) - imag_line.move_to(left_plane.get_center()) - left_plane.add(imag_line) - left_title = TextMobject("Input space") - left_title.add_background_rectangle() - left_title.set_color(YELLOW) - left_title.next_to(left_plane.get_top(), DOWN) - - right_plane = NumberPlane(x_radius = (FRAME_X_RADIUS-1)/2) - right_plane.to_edge(RIGHT, buff = 0) - unit_circle = Circle() - unit_circle.set_color(MAROON_B).fade(0.3) - unit_circle.shift(right_plane.get_center()) - right_plane.add(unit_circle) - right_title = TextMobject("Output space") - right_title.add_background_rectangle() - right_title.set_color(MAROON_B) - right_title.next_to(right_plane.get_top(), DOWN) - - for plane in left_plane, right_plane: - labels = VGroup() - for x in range(-2, 3): - label = TexMobject(str(x)) - label.move_to(plane.num_pair_to_point((x, 0))) - labels.add(label) - for y in range(-3, 4): - if y == 0: - continue - label = TexMobject(str(y) + "i") - label.move_to(plane.num_pair_to_point((0, y))) - labels.add(label) - for label in labels: - label.scale_in_place(0.5) - label.next_to( - label.get_center(), DOWN+RIGHT, - buff = SMALL_BUFF - ) - plane.add(labels) - - arrow = Arrow(LEFT, RIGHT) - - self.play( - ShowCreation(left_plane), - Write(left_title), - run_time = 3 - ) - self.play( - ShowCreation(right_plane), - Write(right_title), - run_time = 3 - ) - self.play(ShowCreation(arrow)) - self.wait() - self.left_plane = left_plane - self.right_plane = right_plane - - def show_imaginary_powers(self): - i = complex(0, 1) - input_dot = Dot(self.z_to_point(i)) - input_dot.set_color(YELLOW) - output_dot = Dot(self.z_to_point(0.5**(i), is_input = False)) - output_dot.set_color(MAROON_B) - - output_dot.save_state() - output_dot.move_to(input_dot) - output_dot.set_color(input_dot.get_color()) - - curr_base = 0.5 - def output_dot_update(ouput_dot): - y = input_dot.get_center()[1] - output_dot.move_to(self.z_to_point( - curr_base**complex(0, y), is_input = False - )) - return output_dot - - def walk_up_and_down(): - for vect in 3*DOWN, 5*UP, 5*DOWN, 2*UP: - self.play( - input_dot.shift, vect, - UpdateFromFunc(output_dot, output_dot_update), - run_time = 3 - ) - - exp = self.imag_exponent[-1] - new_exp = TexMobject("ti") - new_exp.set_color(exp.get_color()) - new_exp.set_height(exp.get_height()) - new_exp.move_to(exp, LEFT) - - nine = TexMobject("9") - nine.set_color(BLUE) - denom = self.imag_exponent[0][3] - denom.save_state() - nine.replace(denom) - - self.play(Transform(exp, new_exp)) - self.play(input_dot.shift, 2*UP) - self.play(input_dot.shift, 2*DOWN) - self.wait() - self.play(output_dot.restore) - self.wait() - walk_up_and_down() - self.wait() - curr_base = 1./9 - self.play(Transform(denom, nine)) - walk_up_and_down() - self.wait() - - def z_to_point(self, z, is_input = True): - if is_input: - plane = self.left_plane - else: - plane = self.right_plane - return plane.num_pair_to_point((z.real, z.imag)) - -class SizeAndRotationBreakdown(Scene): - def construct(self): - original = TexMobject( - "\\left(\\frac{1}{2}\\right)", "^{2+i}" - ) - split = TexMobject( - "\\left(\\frac{1}{2}\\right)", "^{2}", - "\\left(\\frac{1}{2}\\right)", "^{i}", - ) - VGroup(original[-1], split[1], split[3]).set_color(YELLOW) - VGroup(original, split).shift(UP) - real_part = VGroup(*split[:2]) - imag_part = VGroup(*split[2:]) - - size_brace = Brace(real_part) - size = size_brace.get_text("Size") - rotation_brace = Brace(imag_part, UP) - rotation = rotation_brace.get_text("Rotation") - - self.add(original) - self.wait() - self.play(*[ - Transform(*pair) - for pair in [ - (original[0], split[0]), - (original[1][0], split[1]), - (original[0].copy(), split[2]), - (VGroup(*original[1][1:]), split[3]), - ] - ]) - self.play( - GrowFromCenter(size_brace), - Write(size) - ) - self.play( - GrowFromCenter(rotation_brace), - Write(rotation) - ) - self.wait() - -class SeeLinksInDescription(TeacherStudentsScene): - def construct(self): - self.teacher_says(""" - See links in the - description for more. - """) - self.play(*it.chain(*[ - [pi.change_mode, "hooray", pi.look, DOWN] - for pi in self.get_students() - ])) - self.random_blink(3) - -class ShowMultiplicationOfRealAndImaginaryExponentialParts(FromRealToComplex): - def construct(self): - self.break_up_exponent() - self.show_multiplication() - - def break_up_exponent(self): - original = TexMobject( - "\\left(\\frac{1}{2}\\right)", "^{2+i}" - ) - split = TexMobject( - "\\left(\\frac{1}{2}\\right)", "^{2}", - "\\left(\\frac{1}{2}\\right)", "^{i}", - ) - VGroup(original[-1], split[1], split[3]).set_color(YELLOW) - VGroup(original, split).to_corner(UP+LEFT) - rect = BackgroundRectangle(split) - real_part = VGroup(*split[:2]) - imag_part = VGroup(*split[2:]) - - self.add(rect, original) - self.wait() - self.play(*[ - Transform(*pair) - for pair in [ - (original[0], split[0]), - (original[1][0], split[1]), - (original[0].copy(), split[2]), - (VGroup(*original[1][1:]), split[3]), - ] - ]) - self.remove(*self.get_mobjects_from_last_animation()) - self.add(real_part, imag_part) - self.wait() - self.real_part = real_part - self.imag_part = imag_part - - def show_multiplication(self): - real_part = self.real_part.copy() - imag_part = self.imag_part.copy() - for part in real_part, imag_part: - part.add_to_back(BackgroundRectangle(part)) - - fourth_point = self.z_to_point(0.25) - fourth_line = Line(ORIGIN, fourth_point) - brace = Brace(fourth_line, UP, buff = SMALL_BUFF) - fourth_dot = Dot(fourth_point) - fourth_group = VGroup(fourth_line, brace, fourth_dot) - fourth_group.set_color(RED) - - circle = Circle(radius = 2, color = MAROON_B) - circle.fade(0.3) - imag_power_point = self.z_to_point(0.5**complex(0, 1)) - imag_power_dot = Dot(imag_power_point) - imag_power_line = Line(ORIGIN, imag_power_point) - VGroup(imag_power_dot, imag_power_line).set_color(MAROON_B) - - full_power_tex = TexMobject( - "\\left(\\frac{1}{2}\\right)", "^{2+i}" - ) - full_power_tex[-1].set_color(YELLOW) - full_power_tex.add_background_rectangle() - full_power_tex.scale(0.7) - full_power_tex.next_to( - 0.5*self.z_to_point(0.5**complex(2, 1)), - UP+RIGHT - ) - - self.play( - real_part.scale, 0.7, - real_part.next_to, brace, UP, SMALL_BUFF, LEFT, - ShowCreation(fourth_dot) - ) - self.play( - GrowFromCenter(brace), - ShowCreation(fourth_line), - ) - self.wait() - self.play( - imag_part.scale, 0.7, - imag_part.next_to, imag_power_dot, DOWN+RIGHT, SMALL_BUFF, - ShowCreation(imag_power_dot) - ) - self.play(ShowCreation(circle), Animation(imag_power_dot)) - self.play(ShowCreation(imag_power_line)) - self.wait(2) - self.play( - fourth_group.rotate, imag_power_line.get_angle() - ) - real_part.generate_target() - imag_part.generate_target() - real_part.target.next_to(brace, UP+RIGHT, buff = 0) - imag_part.target.next_to(real_part.target, buff = 0) - self.play(*list(map(MoveToTarget, [real_part, imag_part]))) - self.wait() - -class ComplexFunctionsAsTransformations(ComplexTransformationScene): - def construct(self): - self.add_title() - input_dots, output_dots, arrows = self.get_dots() - - self.play(FadeIn( - input_dots, - run_time = 2, - lag_ratio = 0.5 - )) - for in_dot, out_dot, arrow in zip(input_dots, output_dots, arrows): - self.play( - Transform(in_dot.copy(), out_dot), - ShowCreation(arrow) - ) - self.wait() - self.wait() - - - def add_title(self): - title = TextMobject("Complex functions as transformations") - title.add_background_rectangle() - title.to_edge(UP) - self.add(title) - - def get_dots(self): - input_points = [ - RIGHT+2*UP, - 4*RIGHT+DOWN, - 2*LEFT+2*UP, - LEFT+DOWN, - 6*LEFT+DOWN, - ] - output_nudges = [ - DOWN+RIGHT, - 2*UP+RIGHT, - 2*RIGHT+2*DOWN, - 2*RIGHT+DOWN, - RIGHT+2*UP, - ] - input_dots = VGroup(*list(map(Dot, input_points))) - input_dots.set_color(YELLOW) - output_dots = VGroup(*[ - Dot(ip + on) - for ip, on in zip(input_points, output_nudges) - ]) - output_dots.set_color(MAROON_B) - arrows = VGroup(*[ - Arrow(in_dot, out_dot, buff = 0.1, color = WHITE) - for in_dot, out_dot, in zip(input_dots, output_dots) - ]) - for i, dot in enumerate(input_dots): - label = TexMobject("s_%d"%i) - label.set_color(dot.get_color()) - label.next_to(dot, DOWN+LEFT, buff = SMALL_BUFF) - dot.add(label) - for i, dot in enumerate(output_dots): - label = TexMobject("f(s_%d)"%i) - label.set_color(dot.get_color()) - label.next_to(dot, UP+RIGHT, buff = SMALL_BUFF) - dot.add(label) - return input_dots, output_dots, arrows - -class VisualizingSSquared(ComplexTransformationScene): - CONFIG = { - "num_anchors_to_add_per_line" : 100, - "horiz_end_color" : GOLD, - "y_min" : 0, - } - def construct(self): - self.add_title() - self.plug_in_specific_values() - self.show_transformation() - self.comment_on_two_dimensions() - - def add_title(self): - title = TexMobject("f(", "s", ") = ", "s", "^2") - title.set_color_by_tex("s", YELLOW) - title.add_background_rectangle() - title.scale(1.5) - title.to_corner(UP+LEFT) - self.play(Write(title)) - self.add_foreground_mobject(title) - self.wait() - self.title = title - - def plug_in_specific_values(self): - inputs = list(map(complex, [2, -1, complex(0, 1)])) - input_dots = VGroup(*[ - Dot(self.z_to_point(z), color = YELLOW) - for z in inputs - ]) - output_dots = VGroup(*[ - Dot(self.z_to_point(z**2), color = BLUE) - for z in inputs - ]) - arrows = VGroup() - VGroup(*[ - ParametricFunction( - lambda t : self.z_to_point(z**(1.1+0.8*t)) - ) - for z in inputs - ]) - for z, dot in zip(inputs, input_dots): - path = ParametricFunction( - lambda t : self.z_to_point(z**(1+t)) - ) - dot.path = path - arrow = ParametricFunction( - lambda t : self.z_to_point(z**(1.1+0.8*t)) - ) - stand_in_arrow = Arrow( - arrow.points[-2], arrow.points[-1], - tip_length = 0.2 - ) - arrow.add(stand_in_arrow.tip) - arrows.add(arrow) - arrows.set_color(WHITE) - - for input_dot, output_dot, arrow in zip(input_dots, output_dots, arrows): - input_dot.save_state() - input_dot.move_to(self.title[1][1]) - input_dot.set_fill(opacity = 0) - - self.play(input_dot.restore) - self.wait() - self.play(ShowCreation(arrow)) - self.play(ShowCreation(output_dot)) - self.wait() - self.add_foreground_mobjects(arrows, output_dots, input_dots) - self.input_dots = input_dots - self.output_dots = output_dots - - def add_transformable_plane(self, **kwargs): - ComplexTransformationScene.add_transformable_plane(self, **kwargs) - self.plane.next_to(ORIGIN, UP, buff = 0.01) - self.plane.add(self.plane.copy().rotate(np.pi, RIGHT)) - self.plane.add( - Line(ORIGIN, FRAME_X_RADIUS*RIGHT, color = self.horiz_end_color), - Line(ORIGIN, FRAME_X_RADIUS*LEFT, color = self.horiz_end_color), - ) - self.add(self.plane) - - def show_transformation(self): - self.add_transformable_plane() - self.play(ShowCreation(self.plane, run_time = 3)) - - self.wait() - self.apply_complex_homotopy( - lambda z, t : z**(1+t), - added_anims = [ - MoveAlongPath(dot, dot.path, run_time = 5) - for dot in self.input_dots - ], - run_time = 5 - ) - self.wait(2) - - - def comment_on_two_dimensions(self): - morty = Mortimer().flip() - morty.scale(0.7) - morty.to_corner(DOWN+LEFT) - bubble = morty.get_bubble(SpeechBubble, height = 2, width = 4) - bubble.set_fill(BLACK, opacity = 0.9) - bubble.write(""" - It all happens - in two dimensions! - """) - self.foreground_mobjects = [] - - self.play(FadeIn(morty)) - self.play( - morty.change_mode, "hooray", - ShowCreation(bubble), - Write(bubble.content), - ) - self.play(Blink(morty)) - self.wait(2) - -class ShowZetaOnHalfPlane(ZetaTransformationScene): - CONFIG = { - "x_min" : 1, - "x_max" : int(FRAME_X_RADIUS+2), - } - def construct(self): - self.add_title() - self.initial_transformation() - self.react_to_transformation() - self.show_cutoff() - self.set_color_i_line() - self.show_continuation() - self.emphsize_sum_doesnt_make_sense() - - - def add_title(self): - zeta = TexMobject( - "\\zeta(", "s", ")=", - *[ - "\\frac{1}{%d^s} + "%d - for d in range(1, 5) - ] + ["\\cdots"] - ) - zeta[1].set_color(YELLOW) - for mob in zeta[3:3+4]: - mob[-2].set_color(YELLOW) - zeta.add_background_rectangle() - zeta.scale(0.8) - zeta.to_corner(UP+LEFT) - self.add_foreground_mobjects(zeta) - self.zeta = zeta - - def initial_transformation(self): - self.add_transformable_plane() - self.wait() - self.add_extra_plane_lines_for_zeta(animate = True) - self.wait(2) - self.plane.save_state() - self.apply_zeta_function() - self.wait(2) - - def react_to_transformation(self): - morty = Mortimer().flip() - morty.to_corner(DOWN+LEFT) - bubble = morty.get_bubble(SpeechBubble) - bubble.set_fill(BLACK, 0.5) - bubble.write("\\emph{Damn}!") - bubble.resize_to_content() - bubble.pin_to(morty) - - self.play(FadeIn(morty)) - self.play( - morty.change_mode, "surprised", - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(Blink(morty)) - self.play(morty.look_at, self.plane.get_top()) - self.wait() - self.play( - morty.look_at, self.plane.get_bottom(), - *list(map(FadeOut, [bubble, bubble.content])) - ) - self.play(Blink(morty)) - self.play(FadeOut(morty)) - - def show_cutoff(self): - words = TextMobject("Such an abrupt stop...") - words.add_background_rectangle() - words.next_to(ORIGIN, UP+LEFT) - words.shift(LEFT+UP) - - line = Line(*list(map(self.z_to_point, [ - complex(np.euler_gamma, u*FRAME_Y_RADIUS) - for u in (1, -1) - ]))) - line.set_color(YELLOW) - arrows = [ - Arrow(words.get_right(), point) - for point in line.get_start_and_end() - ] - - self.play(Write(words, run_time = 2)) - self.play(ShowCreation(arrows[0])) - self.play( - Transform(*arrows), - ShowCreation(line), - run_time = 2 - ) - self.play(FadeOut(arrows[0])) - self.wait(2) - self.play(*list(map(FadeOut, [words, line]))) - - def set_color_i_line(self): - right_i_lines, left_i_lines = [ - VGroup(*[ - Line( - vert_vect+RIGHT, - vert_vect+(FRAME_X_RADIUS+1)*horiz_vect - ) - for vert_vect in (UP, DOWN) - ]) - for horiz_vect in (RIGHT, LEFT) - ] - right_i_lines.set_color(YELLOW) - left_i_lines.set_color(BLUE) - for lines in right_i_lines, left_i_lines: - self.prepare_for_transformation(lines) - - self.restore_mobjects(self.plane) - self.plane.add(*right_i_lines) - colored_plane = self.plane.copy() - right_i_lines.set_stroke(width = 0) - self.play( - self.plane.set_stroke, GREY, 1, - ) - right_i_lines.set_stroke(YELLOW, width = 3) - self.play(ShowCreation(right_i_lines)) - self.plane.save_state() - self.wait(2) - self.apply_zeta_function() - self.wait(2) - - left_i_lines.save_state() - left_i_lines.apply_complex_function(zeta) - self.play(ShowCreation(left_i_lines, run_time = 5)) - self.wait() - self.restore_mobjects(self.plane, left_i_lines) - self.play(Transform(self.plane, colored_plane)) - self.wait() - self.left_i_lines = left_i_lines - - def show_continuation(self): - reflected_plane = self.get_reflected_plane() - self.play(ShowCreation(reflected_plane, run_time = 2)) - self.plane.add(reflected_plane) - self.remove(self.left_i_lines) - self.wait() - self.apply_zeta_function() - self.wait(2) - self.play(ShowCreation( - reflected_plane, - run_time = 6, - rate_func = lambda t : 1-there_and_back(t) - )) - self.wait(2) - - def emphsize_sum_doesnt_make_sense(self): - brace = Brace(VGroup(*self.zeta[1][3:])) - words = brace.get_text(""" - Still fails to converge - when Re$(s) < 1$ - """, buff = SMALL_BUFF) - words.add_background_rectangle() - words.scale_in_place(0.8) - divergent_sum = TexMobject("1+2+3+4+\\cdots") - divergent_sum.next_to(ORIGIN, UP) - divergent_sum.to_edge(LEFT) - divergent_sum.add_background_rectangle() - - self.play( - GrowFromCenter(brace), - Write(words) - ) - self.wait(2) - self.play(Write(divergent_sum)) - self.wait(2) - - def restore_mobjects(self, *mobjects): - self.play(*it.chain(*[ - [m.restore, m.make_smooth] - for m in mobjects - ]), run_time = 2) - for m in mobjects: - self.remove(m) - m.restore() - self.add(m) - -class ShowConditionalDefinition(Scene): - def construct(self): - zeta = TexMobject("\\zeta(s)=") - zeta[2].set_color(YELLOW) - sigma = TexMobject("\\sum_{n=1}^\\infty \\frac{1}{n^s}") - sigma[-1].set_color(YELLOW) - something_else = TextMobject("Something else...") - conditions = VGroup(*[ - TextMobject("if Re$(s) %s 1$"%s) - for s in (">", "\\le") - ]) - definitions = VGroup(sigma, something_else) - definitions.arrange(DOWN, buff = MED_LARGE_BUFF, aligned_edge = LEFT) - conditions.arrange(DOWN, buff = LARGE_BUFF) - definitions.shift(2*LEFT+2*UP) - conditions.next_to(definitions, RIGHT, buff = LARGE_BUFF, aligned_edge = DOWN) - brace = Brace(definitions, LEFT) - zeta.next_to(brace, LEFT) - - sigma.save_state() - sigma.next_to(zeta) - self.add(zeta, sigma) - self.wait() - self.play( - sigma.restore, - GrowFromCenter(brace), - FadeIn(something_else) - ) - self.play(Write(conditions)) - self.wait() - - underbrace = Brace(something_else) - question = underbrace.get_text(""" - What to put here? - """) - VGroup(underbrace, question).set_color(GREEN_B) - - self.play( - GrowFromCenter(underbrace), - Write(question), - something_else.set_color, GREEN_B - ) - self.wait(2) - -class SquiggleOnExtensions(ZetaTransformationScene): - CONFIG = { - "x_min" : 1, - "x_max" : int(FRAME_X_RADIUS+2), - } - def construct(self): - self.show_negative_one() - self.cycle_through_options() - self.lock_into_place() - - def show_negative_one(self): - self.add_transformable_plane() - thin_plane = self.plane.copy() - thin_plane.add(self.get_reflected_plane()) - self.remove(self.plane) - self.add_extra_plane_lines_for_zeta() - reflected_plane = self.get_reflected_plane() - self.plane.add(reflected_plane) - self.remove(self.plane) - self.add(thin_plane) - - dot = self.note_point(-1, "-1") - self.play( - ShowCreation(self.plane, run_time = 2), - Animation(dot), - run_time = 2 - ) - self.remove(thin_plane) - self.apply_zeta_function(added_anims = [ - ApplyMethod( - dot.move_to, self.z_to_point(-1./12), - run_time = 5 - ) - ]) - dot_to_remove = self.note_point(-1./12, "-\\frac{1}{12}") - self.remove(dot_to_remove) - self.left_plane = reflected_plane - self.dot = dot - - def note_point(self, z, label_tex): - dot = Dot(self.z_to_point(z)) - dot.set_color(YELLOW) - label = TexMobject(label_tex) - label.add_background_rectangle() - label.next_to(dot, UP+LEFT, buff = SMALL_BUFF) - label.shift(LEFT) - arrow = Arrow(label.get_right(), dot, buff = SMALL_BUFF) - - self.play(Write(label, run_time = 1)) - self.play(*list(map(ShowCreation, [arrow, dot]))) - self.wait() - self.play(*list(map(FadeOut, [arrow, label]))) - return dot - - def cycle_through_options(self): - gamma = np.euler_gamma - def shear(point): - x, y, z = point - return np.array([ - x, - y+0.25*(1-x)**2, - 0 - ]) - def mixed_scalar_func(point): - x, y, z = point - scalar = 1 + (gamma-x)/(gamma+FRAME_X_RADIUS) - return np.array([ - (scalar**2)*x, - (scalar**3)*y, - 0 - ]) - def alt_mixed_scalar_func(point): - x, y, z = point - scalar = 1 + (gamma-x)/(gamma+FRAME_X_RADIUS) - return np.array([ - (scalar**5)*x, - (scalar**2)*y, - 0 - ]) - def sinusoidal_func(point): - x, y, z = point - freq = np.pi/gamma - return np.array([ - x-0.2*np.sin(x*freq)*np.sin(y), - y-0.2*np.sin(x*freq)*np.sin(y), - 0 - ]) - funcs = [ - shear, - mixed_scalar_func, - alt_mixed_scalar_func, - sinusoidal_func, - ] - for mob in self.left_plane.family_members_with_points(): - if np.all(np.abs(mob.points[:,1]) < 0.1): - self.left_plane.remove(mob) - - new_left_planes = [ - self.left_plane.copy().apply_function(func) - for func in funcs - ] - new_dots = [ - self.dot.copy().move_to(func(self.dot.get_center())) - for func in funcs - ] - self.left_plane.save_state() - for plane, dot in zip(new_left_planes, new_dots): - self.play( - Transform(self.left_plane, plane), - Transform(self.dot, dot), - run_time = 3 - ) - self.wait() - self.play(FadeOut(self.dot)) - - #Squiggle on example - self.wait() - self.play(FadeOut(self.left_plane)) - self.play(ShowCreation( - self.left_plane, - run_time = 5, - rate_func=linear - )) - self.wait() - - def lock_into_place(self): - words = TextMobject( - """Only one extension - has a """, - "\\emph{derivative}", - "everywhere", - alignment = "" - ) - words.to_corner(UP+LEFT) - words.set_color_by_tex("\\emph{derivative}", YELLOW) - words.add_background_rectangle() - - self.play(Write(words)) - self.add_foreground_mobjects(words) - self.play(self.left_plane.restore) - self.wait() - -class DontKnowDerivatives(TeacherStudentsScene): - def construct(self): - self.student_says( - """ - You said we don't - need derivatives! - """, - target_mode = "pleading" - ) - self.random_blink(2) - self.student_says( - """ - I get $\\frac{df}{dx}$, just not - for complex functions - """, - target_mode = "confused", - student_index = 2 - ) - self.random_blink(2) - self.teacher_says( - """ - Luckily, there's a purely - geometric intuition here. - """, - target_mode = "hooray" - ) - self.change_student_modes(*["happy"]*3) - self.random_blink(3) - -class IntroduceAnglePreservation(VisualizingSSquared): - CONFIG = { - "num_anchors_to_add_per_line" : 50, - "use_homotopy" : True, - } - def construct(self): - self.add_title() - self.show_initial_transformation() - self.talk_about_derivative() - self.cycle_through_line_pairs() - self.note_grid_lines() - self.name_analytic() - - def add_title(self): - title = TexMobject("f(", "s", ")=", "s", "^2") - title.set_color_by_tex("s", YELLOW) - title.scale(1.5) - title.to_corner(UP+LEFT) - title.add_background_rectangle() - self.title = title - - self.add_transformable_plane() - self.play(Write(title)) - self.add_foreground_mobjects(title) - self.wait() - - def show_initial_transformation(self): - self.apply_function() - self.wait(2) - self.reset() - - def talk_about_derivative(self): - randy = Randolph().scale(0.8) - randy.to_corner(DOWN+LEFT) - morty = Mortimer() - morty.to_corner(DOWN+RIGHT) - randy.make_eye_contact(morty) - for pi, words in (randy, "$f'(s) = 2s$"), (morty, "Here's some \\\\ related geometry..."): - pi.bubble = pi.get_bubble(SpeechBubble) - pi.bubble.set_fill(BLACK, opacity = 0.7) - pi.bubble.write(words) - pi.bubble.resize_to_content() - pi.bubble.pin_to(pi) - for index in 3, 7: - randy.bubble.content[index].set_color(YELLOW) - - self.play(*list(map(FadeIn, [randy, morty]))) - self.play( - randy.change_mode, "speaking", - ShowCreation(randy.bubble), - Write(randy.bubble.content) - ) - self.play(Blink(morty)) - self.wait() - self.play( - morty.change_mode, "speaking", - randy.change_mode, "pondering", - ShowCreation(morty.bubble), - Write(morty.bubble.content), - ) - self.play(Blink(randy)) - self.wait() - self.play(*list(map(FadeOut, [ - randy, morty, - randy.bubble, randy.bubble.content, - morty.bubble, morty.bubble.content, - ]))) - - - def cycle_through_line_pairs(self): - line_pairs = [ - ( - Line(3*DOWN+3*RIGHT, 2*UP), - Line(DOWN+RIGHT, 3*UP+4*RIGHT) - ), - ( - Line(RIGHT+3.5*DOWN, RIGHT+2.5*UP), - Line(3*LEFT+0.5*UP, 3*RIGHT+0.5*UP), - ), - ( - Line(4*RIGHT+4*DOWN, RIGHT+2*UP), - Line(4*DOWN+RIGHT, 2*UP+2*RIGHT) - ), - ] - for lines in line_pairs: - self.show_angle_preservation_between_lines(*lines) - self.reset() - - def note_grid_lines(self): - intersection_inputs = [ - complex(x, y) - for x in np.arange(-5, 5, 0.5) - for y in np.arange(0, 3, 0.5) - if not (x <= 0 and y == 0) - ] - brackets = VGroup(*list(map( - self.get_right_angle_bracket, - intersection_inputs - ))) - self.apply_function() - self.wait() - self.play( - ShowCreation(brackets, run_time = 5), - Animation(self.plane) - ) - self.wait() - - def name_analytic(self): - equiv = TextMobject("``Analytic'' $\\Leftrightarrow$ Angle-preserving") - kind_of = TextMobject("...kind of") - for text in equiv, kind_of: - text.scale(1.2) - text.add_background_rectangle() - equiv.set_color(YELLOW) - kind_of.set_color(RED) - kind_of.next_to(equiv, RIGHT) - VGroup(equiv, kind_of).next_to(ORIGIN, UP, buff = 1) - - self.play(Write(equiv)) - self.wait(2) - self.play(Write(kind_of, run_time = 1)) - self.wait(2) - - def reset(self, faded = True): - self.play(FadeOut(self.plane)) - self.add_transformable_plane() - if faded: - self.plane.fade() - self.play(FadeIn(self.plane)) - - def apply_function(self, **kwargs): - if self.use_homotopy: - self.apply_complex_homotopy( - lambda z, t : z**(1+t), - run_time = 5, - **kwargs - ) - else: - self.apply_complex_function( - lambda z : z**2, - **kwargs - ) - - def show_angle_preservation_between_lines(self, *lines): - R2_endpoints = [ - [l.get_start()[:2], l.get_end()[:2]] - for l in lines - ] - R2_intersection_point = intersection(*R2_endpoints) - intersection_point = np.array(list(R2_intersection_point) + [0]) - - angle1, angle2 = [l.get_angle() for l in lines] - arc = Arc( - start_angle = angle1, - angle = angle2-angle1, - radius = 0.4, - color = YELLOW - ) - arc.shift(intersection_point) - arc.insert_n_curves(10) - arc.generate_target() - input_z = complex(*arc.get_center()[:2]) - scale_factor = abs(2*input_z) - arc.target.scale_about_point(1./scale_factor, intersection_point) - arc.target.apply_complex_function(lambda z : z**2) - - angle_tex = TexMobject( - "%d^\\circ"%abs(int((angle2-angle1)*180/np.pi)) - ) - angle_tex.set_color(arc.get_color()) - angle_tex.add_background_rectangle() - self.put_angle_tex_next_to_arc(angle_tex, arc) - angle_arrow = Arrow( - angle_tex, arc, - color = arc.get_color(), - buff = 0.1, - ) - angle_group = VGroup(angle_tex, angle_arrow) - - - self.play(*list(map(ShowCreation, lines))) - self.play( - Write(angle_tex), - ShowCreation(angle_arrow), - ShowCreation(arc) - ) - self.wait() - - self.play(FadeOut(angle_group)) - self.plane.add(*lines) - self.apply_function(added_anims = [ - MoveToTarget(arc, run_time = 5) - ]) - self.put_angle_tex_next_to_arc(angle_tex, arc) - arrow = Arrow(angle_tex, arc, buff = 0.1) - arrow.set_color(arc.get_color()) - self.play( - Write(angle_tex), - ShowCreation(arrow) - ) - self.wait(2) - self.play(*list(map(FadeOut, [arc, angle_tex, arrow]))) - - def put_angle_tex_next_to_arc(self, angle_tex, arc): - vect = arc.point_from_proportion(0.5)-interpolate( - arc.points[0], arc.points[-1], 0.5 - ) - unit_vect = vect/get_norm(vect) - angle_tex.move_to(arc.get_center() + 1.7*unit_vect) - - def get_right_angle_bracket(self, input_z): - output_z = input_z**2 - derivative = 2*input_z - rotation = np.log(derivative).imag - - brackets = VGroup( - Line(RIGHT, RIGHT+UP), - Line(RIGHT+UP, UP) - ) - brackets.scale(0.15) - brackets.set_stroke(width = 2) - brackets.set_color(YELLOW) - brackets.shift(0.02*UP) ##Why??? - brackets.rotate(rotation, about_point = ORIGIN) - brackets.shift(self.z_to_point(output_z)) - return brackets - -class AngleAtZeroDerivativePoints(IntroduceAnglePreservation): - CONFIG = { - "use_homotopy" : True - } - def construct(self): - self.add_title() - self.is_before_transformation = True - self.add_transformable_plane() - self.plane.fade() - line = Line(3*LEFT+0.5*UP, 3*RIGHT+0.5*DOWN) - self.show_angle_preservation_between_lines( - line, line.copy().rotate(np.pi/5) - ) - self.wait() - - def add_title(self): - title = TexMobject("f(", "s", ")=", "s", "^2") - title.set_color_by_tex("s", YELLOW) - title.scale(1.5) - title.to_corner(UP+LEFT) - title.add_background_rectangle() - derivative = TexMobject("f'(0) = 0") - derivative.set_color(RED) - derivative.scale(1.2) - derivative.add_background_rectangle() - derivative.next_to(title, DOWN) - - self.add_foreground_mobjects(title, derivative) - - - def put_angle_tex_next_to_arc(self, angle_tex, arc): - IntroduceAnglePreservation.put_angle_tex_next_to_arc( - self, angle_tex, arc - ) - if not self.is_before_transformation: - two_dot = TexMobject("2 \\times ") - two_dot.set_color(angle_tex.get_color()) - two_dot.next_to(angle_tex, LEFT, buff = SMALL_BUFF) - two_dot.add_background_rectangle() - center = angle_tex.get_center() - angle_tex.add_to_back(two_dot) - angle_tex.move_to(center) - else: - self.is_before_transformation = False - -class AnglePreservationAtAnyPairOfPoints(IntroduceAnglePreservation): - def construct(self): - self.add_transformable_plane() - self.plane.fade() - line_pairs = self.get_line_pairs() - line_pair = line_pairs[0] - for target_pair in line_pairs[1:]: - self.play(Transform( - line_pair, target_pair, - run_time = 2, - path_arc = np.pi - )) - self.wait() - self.show_angle_preservation_between_lines(*line_pair) - self.show_example_analytic_functions() - - def get_line_pairs(self): - return list(it.starmap(VGroup, [ - ( - Line(3*DOWN, 3*LEFT+2*UP), - Line(2*LEFT+DOWN, 3*UP+RIGHT) - ), - ( - Line(2*RIGHT+DOWN, 3*LEFT+2*UP), - Line(LEFT+3*DOWN, 4*RIGHT+3*UP), - ), - ( - Line(LEFT+3*DOWN, LEFT+3*UP), - Line(5*LEFT+UP, 3*RIGHT+UP) - ), - ( - Line(4*RIGHT+3*DOWN, RIGHT+2*UP), - Line(3*DOWN+RIGHT, 2*UP+2*RIGHT) - ), - ])) - - def show_example_analytic_functions(self): - words = TextMobject("Examples of analytic functions:") - words.shift(2*UP) - words.set_color(YELLOW) - words.add_background_rectangle() - words.next_to(UP, UP).to_edge(LEFT) - functions = TextMobject( - "$e^x$, ", - "$\\sin(x)$, ", - "any polynomial, " - "$\\log(x)$, ", - "\\dots", - ) - functions.next_to(ORIGIN, UP).to_edge(LEFT) - for function in functions: - function.add_to_back(BackgroundRectangle(function)) - - self.play(Write(words)) - for function in functions: - self.play(FadeIn(function)) - self.wait() - -class NoteZetaFunctionAnalyticOnRightHalf(ZetaTransformationScene): - CONFIG = { - "anchor_density" : 35, - } - def construct(self): - self.add_title() - self.add_transformable_plane(animate = False) - self.add_extra_plane_lines_for_zeta(animate = True) - self.apply_zeta_function() - self.note_right_angles() - - def add_title(self): - title = TexMobject( - "\\zeta(s) = \\sum_{n=1}^\\infty \\frac{1}{n^s}" - ) - title[2].set_color(YELLOW) - title[-1].set_color(YELLOW) - title.add_background_rectangle() - title.to_corner(UP+LEFT) - self.add_foreground_mobjects(title) - - def note_right_angles(self): - intersection_inputs = [ - complex(x, y) - for x in np.arange(1+2./16, 1.4, 1./16) - for y in np.arange(-0.5, 0.5, 1./16) - if abs(y) > 1./16 - ] - brackets = VGroup(*list(map( - self.get_right_angle_bracket, - intersection_inputs - ))) - self.play(ShowCreation(brackets, run_time = 3)) - self.wait() - - def get_right_angle_bracket(self, input_z): - output_z = zeta(input_z) - derivative = d_zeta(input_z) - rotation = np.log(derivative).imag - - brackets = VGroup( - Line(RIGHT, RIGHT+UP), - Line(RIGHT+UP, UP) - ) - brackets.scale(0.1) - brackets.set_stroke(width = 2) - brackets.set_color(YELLOW) - brackets.rotate(rotation, about_point = ORIGIN) - brackets.shift(self.z_to_point(output_z)) - return brackets - -class InfiniteContinuousJigsawPuzzle(ZetaTransformationScene): - CONFIG = { - "anchor_density" : 35, - } - def construct(self): - self.set_stage() - self.add_title() - self.show_jigsaw() - self.name_analytic_continuation() - - def set_stage(self): - self.plane = self.get_dense_grid() - left_plane = self.get_reflected_plane() - self.plane.add(left_plane) - self.apply_zeta_function(run_time = 0) - self.remove(left_plane) - lines_per_piece = 5 - pieces = [ - VGroup(*left_plane[lines_per_piece*i:lines_per_piece*(i+1)]) - for i in range(len(list(left_plane))/lines_per_piece) - ] - random.shuffle(pieces) - self.pieces = pieces - - def add_title(self): - title = TextMobject("Infinite ", "continuous ", "jigsaw puzzle") - title.scale(1.5) - title.to_edge(UP) - for word in title: - word.add_to_back(BackgroundRectangle(word)) - self.play(FadeIn(word)) - self.wait() - self.add_foreground_mobjects(title) - self.title = title - - def show_jigsaw(self): - for piece in self.pieces: - self.play(FadeIn(piece, run_time = 0.5)) - self.wait() - - def name_analytic_continuation(self): - words = TextMobject("``Analytic continuation''") - words.set_color(YELLOW) - words.scale(1.5) - words.next_to(self.title, DOWN, buff = LARGE_BUFF) - words.add_background_rectangle() - self.play(Write(words)) - self.wait() - -class ThatsHowZetaIsDefined(TeacherStudentsScene): - def construct(self): - self.add_zeta_definition() - self.teacher_says(""" - So that's how - $\\zeta(s)$ is defined - """) - self.change_student_modes(*["hooray"]*3) - self.random_blink(2) - - def add_zeta_definition(self): - zeta = TexMobject( - "\\zeta(s) = \\sum_{n=1}^\\infty \\frac{1}{n^s}" - ) - VGroup(zeta[2], zeta[-1]).set_color(YELLOW) - zeta.to_corner(UP+LEFT) - self.add(zeta) - -class ManyIntersectingLinesPreZeta(ZetaTransformationScene): - CONFIG = { - "apply_zeta" : False, - "lines_center" : RIGHT, - "nudge_size" : 0.9, - "function" : zeta, - "angle" : np.pi/5, - "arc_scale_factor" : 0.3, - "shift_directions" : [LEFT, RIGHT], - } - def construct(self): - self.establish_plane() - self.add_title() - - line = Line(DOWN+2*LEFT, UP+2*RIGHT) - lines = VGroup(line, line.copy().rotate(self.angle)) - arc = Arc(start_angle = line.get_angle(), angle = self.angle) - arc.scale(self.arc_scale_factor) - arc.set_color(YELLOW) - lines.add(arc) - # lines.set_stroke(WHITE, width = 5) - lines.shift(self.lines_center + self.nudge_size*RIGHT) - - if self.apply_zeta: - self.apply_zeta_function(run_time = 0) - lines.set_stroke(width = 0) - - added_anims = self.get_modified_line_anims(lines) - for vect in self.shift_directions: - self.play( - ApplyMethod(lines.shift, 2*self.nudge_size*vect, path_arc = np.pi), - *added_anims, - run_time = 3 - ) - - def establish_plane(self): - self.add_transformable_plane() - self.add_extra_plane_lines_for_zeta() - self.add_reflected_plane() - self.plane.fade() - - - def add_title(self): - if self.apply_zeta: - title = TextMobject("After \\\\ transformation") - else: - title = TextMobject("Before \\\\ transformation") - title.add_background_rectangle() - title.to_edge(UP) - self.add_foreground_mobjects(title) - - def get_modified_line_anims(self, lines): - return [] - -class ManyIntersectingLinesPostZeta(ManyIntersectingLinesPreZeta): - CONFIG = { - "apply_zeta" : True, - # "anchor_density" : 5 - } - def get_modified_line_anims(self, lines): - n_inserted_points = 30 - new_lines = lines.copy() - new_lines.set_stroke(width = 5) - def update_new_lines(lines_to_update): - transformed = lines.copy() - self.prepare_for_transformation(transformed) - transformed.apply_complex_function(self.function) - transformed.make_smooth() - transformed.set_stroke(width = 5) - for start, end in zip(lines_to_update, transformed): - if start.get_num_points() > 0: - start.points = np.array(end.points) - return [UpdateFromFunc(new_lines, update_new_lines)] - -class ManyIntersectingLinesPreSSquared(ManyIntersectingLinesPreZeta): - CONFIG = { - "x_min" : -int(FRAME_X_RADIUS), - "apply_zeta" : False, - "lines_center" : ORIGIN, - "nudge_size" : 0.9, - "function" : lambda z : z**2, - "shift_directions" : [LEFT, RIGHT, UP, DOWN, DOWN+LEFT, UP+RIGHT], - } - def establish_plane(self): - self.add_transformable_plane() - self.plane.fade() - - def apply_zeta_function(self, **kwargs): - self.apply_complex_function(self.function, **kwargs) - -class ManyIntersectingLinesPostSSquared(ManyIntersectingLinesPreSSquared): - CONFIG = { - "apply_zeta" : True, - } - def get_modified_line_anims(self, lines): - n_inserted_points = 30 - new_lines = lines.copy() - new_lines.set_stroke(width = 5) - def update_new_lines(lines_to_update): - transformed = lines.copy() - self.prepare_for_transformation(transformed) - transformed.apply_complex_function(self.function) - transformed.make_smooth() - transformed.set_stroke(width = 5) - for start, end in zip(lines_to_update, transformed): - if start.get_num_points() > 0: - start.points = np.array(end.points) - return [UpdateFromFunc(new_lines, update_new_lines)] - -class ButWhatIsTheExensions(TeacherStudentsScene): - def construct(self): - self.student_says( - """ - But what exactly \\emph{is} - that continuation? - """, - target_mode = "sassy" - ) - self.change_student_modes("confused", "sassy", "confused") - self.random_blink(2) - self.teacher_says(""" - You're $\\$1{,}000{,}000$ richer - if you can answer - that fully - """, target_mode = "shruggie") - self.change_student_modes(*["pondering"]*3) - self.random_blink(3) - -class MathematiciansLookingAtFunctionEquation(Scene): - def construct(self): - equation = TexMobject( - "\\zeta(s)", - "= 2^s \\pi ^{s-1}", - "\\sin\\left(\\frac{\\pi s}{2}\\right)", - "\\Gamma(1-s)", - "\\zeta(1-s)", - ) - equation.shift(UP) - - mathy = Mathematician().to_corner(DOWN+LEFT) - mathys = VGroup(mathy) - for x in range(2): - mathys.add(Mathematician().next_to(mathys)) - for mathy in mathys: - mathy.change_mode("pondering") - mathy.look_at(equation) - - self.add(mathys) - self.play(Write(VGroup(*equation[:-1]))) - self.play(Transform( - equation[0].copy(), - equation[-1], - path_arc = -np.pi/3, - run_time = 2 - )) - for mathy in mathys: - self.play(Blink(mathy)) - self.wait() - -class DiscussZeros(ZetaTransformationScene): - def construct(self): - self.establish_plane() - self.ask_about_zeros() - self.show_trivial_zeros() - self.show_critical_strip() - self.transform_bit_of_critical_line() - self.extend_transformed_critical_line() - - def establish_plane(self): - self.add_transformable_plane() - self.add_extra_plane_lines_for_zeta() - self.add_reflected_plane() - self.plane.fade() - - def ask_about_zeros(self): - dots = VGroup(*[ - Dot( - (2+np.sin(12*alpha))*\ - rotate_vector(RIGHT, alpha+nudge) - ) - for alpha in np.arange(3*np.pi/20, 2*np.pi, 2*np.pi/5) - for nudge in [random.random()*np.pi/6] - ]) - dots.set_color(YELLOW) - q_marks = VGroup(*[ - TexMobject("?").next_to(dot, UP) - for dot in dots - ]) - arrows = VGroup(*[ - Arrow(dot, ORIGIN, buff = 0.2, tip_length = 0.1) - for dot in dots - ]) - question = TextMobject("Which numbers go to $0$?") - question.add_background_rectangle() - question.to_edge(UP) - - for mob in dots, arrows, q_marks: - self.play(ShowCreation(mob)) - self.play(Write(question)) - self.wait(2) - dots.generate_target() - for i, dot in enumerate(dots.target): - dot.move_to(2*(i+1)*LEFT) - self.play( - FadeOut(arrows), - FadeOut(q_marks), - FadeOut(question), - MoveToTarget(dots), - ) - self.wait() - self.dots = dots - - def show_trivial_zeros(self): - trivial_zero_words = TextMobject("``Trivial'' zeros") - trivial_zero_words.next_to(ORIGIN, UP) - trivial_zero_words.to_edge(LEFT) - - randy = Randolph().flip() - randy.to_corner(DOWN+RIGHT) - bubble = randy.get_bubble() - bubble.set_fill(BLACK, opacity = 0.8) - bubble.write("$1^1 + 2^2 + 3^2 + \\cdots = 0$") - bubble.resize_to_content() - bubble.pin_to(randy) - - self.plane.save_state() - self.dots.save_state() - for dot in self.dots.target: - dot.move_to(ORIGIN) - self.apply_zeta_function( - added_anims = [MoveToTarget(self.dots, run_time = 3)], - run_time = 3 - ) - self.wait(3) - self.play( - self.plane.restore, - self.plane.make_smooth, - self.dots.restore, - run_time = 2 - ) - self.remove(*self.get_mobjects_from_last_animation()) - self.plane.restore() - self.dots.restore() - self.add(self.plane, self.dots) - - self.play(Write(trivial_zero_words)) - self.wait() - self.play(FadeIn(randy)) - self.play( - randy.change_mode, "confused", - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(Blink(randy)) - self.wait() - self.play(Blink(randy)) - self.play(*list(map(FadeOut, [ - randy, bubble, bubble.content, trivial_zero_words - ]))) - - def show_critical_strip(self): - strip = Rectangle( - height = FRAME_HEIGHT, - width = 1 - ) - strip.next_to(ORIGIN, RIGHT, buff = 0) - strip.set_stroke(width = 0) - strip.set_fill(YELLOW, opacity = 0.3) - name = TextMobject("Critical strip") - name.add_background_rectangle() - name.next_to(ORIGIN, LEFT) - name.to_edge(UP) - arrow = Arrow(name.get_bottom(), 0.5*RIGHT+UP) - primes = TexMobject("2, 3, 5, 7, 11, 13, 17, \\dots") - primes.to_corner(UP+RIGHT) - # photo = Square() - photo = ImageMobject("Riemann", invert = False) - photo.set_width(5) - photo.to_corner(UP+LEFT) - new_dots = VGroup(*[ - Dot(0.5*RIGHT + y*UP) - for y in np.linspace(-2.5, 3.2, 5) - ]) - new_dots.set_color(YELLOW) - critical_line = Line( - 0.5*RIGHT+FRAME_Y_RADIUS*DOWN, - 0.5*RIGHT+FRAME_Y_RADIUS*UP, - color = YELLOW - ) - - self.give_dots_wandering_anims() - - self.play(FadeIn(strip), *self.get_dot_wandering_anims()) - self.play( - Write(name, run_time = 1), - ShowCreation(arrow), - *self.get_dot_wandering_anims() - ) - self.play(*self.get_dot_wandering_anims()) - self.play( - FadeIn(primes), - *self.get_dot_wandering_anims() - ) - for x in range(7): - self.play(*self.get_dot_wandering_anims()) - self.play( - GrowFromCenter(photo), - FadeOut(name), - FadeOut(arrow), - *self.get_dot_wandering_anims() - ) - self.play(Transform(self.dots, new_dots)) - self.play(ShowCreation(critical_line)) - self.wait(3) - self.play( - photo.shift, 7*LEFT, - *list(map(FadeOut, [ - primes, self.dots, strip - ])) - ) - self.remove(photo) - self.critical_line = critical_line - - def give_dots_wandering_anims(self): - def func(t): - result = (np.sin(6*2*np.pi*t) + 1)*RIGHT/2 - result += 3*np.cos(2*2*np.pi*t)*UP - return result - - self.wandering_path = ParametricFunction(func) - for i, dot in enumerate(self.dots): - dot.target = dot.copy() - q_mark = TexMobject("?") - q_mark.next_to(dot.target, UP) - dot.target.add(q_mark) - dot.target.move_to(self.wandering_path.point_from_proportion( - (float(2+2*i)/(4*len(list(self.dots))))%1 - )) - self.dot_anim_count = 0 - - def get_dot_wandering_anims(self): - self.dot_anim_count += 1 - if self.dot_anim_count == 1: - return list(map(MoveToTarget, self.dots)) - denom = 4*(len(list(self.dots))) - def get_rate_func(index): - return lambda t : (float(self.dot_anim_count + 2*index + t)/denom)%1 - return [ - MoveAlongPath( - dot, self.wandering_path, - rate_func = get_rate_func(i) - ) - for i, dot in enumerate(self.dots) - ] - - def transform_bit_of_critical_line(self): - self.play( - self.plane.scale, 0.8, - self.critical_line.scale, 0.8, - rate_func = there_and_back, - run_time = 2 - ) - self.wait() - self.play( - self.plane.set_stroke, GREY, 1, - Animation(self.critical_line) - ) - self.plane.add(self.critical_line) - self.apply_zeta_function() - self.wait(2) - self.play( - self.plane.fade, - Animation(self.critical_line) - ) - - def extend_transformed_critical_line(self): - def func(t): - z = zeta(complex(0.5, t)) - return z.real*RIGHT + z.imag*UP - full_line = VGroup(*[ - ParametricFunction(func, t_min = t0, t_max = t0+1) - for t0 in range(100) - ]) - full_line.set_color_by_gradient( - YELLOW, BLUE, GREEN, RED, YELLOW, BLUE, GREEN, RED, - ) - self.play(ShowCreation(full_line, run_time = 20, rate_func=linear)) - self.wait() - -class AskAboutRelationToPrimes(TeacherStudentsScene): - def construct(self): - self.student_says(""" - Whoa! Where the heck - do primes come in here? - """, target_mode = "confused") - self.random_blink(3) - self.teacher_says(""" - Perhaps in a - different video. - """, target_mode = "hesitant") - self.random_blink(3) - -class HighlightCriticalLineAgain(DiscussZeros): - def construct(self): - self.establish_plane() - title = TexMobject("\\zeta(", "s", ") = 0") - title.set_color_by_tex("s", YELLOW) - title.add_background_rectangle() - title.to_corner(UP+LEFT) - self.add(title) - - strip = Rectangle( - height = FRAME_HEIGHT, - width = 1 - ) - strip.next_to(ORIGIN, RIGHT, buff = 0) - strip.set_stroke(width = 0) - strip.set_fill(YELLOW, opacity = 0.3) - line = Line( - 0.5*RIGHT+FRAME_Y_RADIUS*UP, - 0.5*RIGHT+FRAME_Y_RADIUS*DOWN, - color = YELLOW - ) - randy = Randolph().to_corner(DOWN+LEFT) - million = TexMobject("\\$1{,}000{,}000") - million.set_color(GREEN_B) - million.next_to(ORIGIN, UP+LEFT) - million.shift(2*LEFT) - arrow1 = Arrow(million.get_right(), line.get_top()) - arrow2 = Arrow(million.get_right(), line.get_bottom()) - - self.add(randy, strip) - self.play(Write(million)) - self.play( - randy.change_mode, "pondering", - randy.look_at, line.get_top(), - ShowCreation(arrow1), - run_time = 3 - ) - self.play( - randy.look_at, line.get_bottom(), - ShowCreation(line), - Transform(arrow1, arrow2) - ) - self.play(FadeOut(arrow1)) - self.play(Blink(randy)) - self.wait() - self.play(randy.look_at, line.get_center()) - self.play(randy.change_mode, "confused") - self.play(Blink(randy)) - self.wait() - self.play(randy.change_mode, "pondering") - self.wait() - -class DiscussSumOfNaturals(Scene): - def construct(self): - title = TexMobject( - "\\zeta(s) = \\sum_{n=1}^\\infty \\frac{1}{n^s}" - ) - VGroup(title[2], title[-1]).set_color(YELLOW) - title.to_corner(UP+LEFT) - - neg_twelfth, eq, zeta_neg_1, sum_naturals = equation = TexMobject( - "-\\frac{1}{12}", - "=", - "\\zeta(-1)", - "= 1 + 2 + 3 + 4 + \\cdots" - ) - neg_twelfth.set_color(GREEN_B) - VGroup(*zeta_neg_1[2:4]).set_color(YELLOW) - q_mark = TexMobject("?").next_to(sum_naturals[0], UP) - q_mark.set_color(RED) - randy = Randolph() - randy.to_corner(DOWN+LEFT) - analytic_continuation = TextMobject("Analytic continuation") - analytic_continuation.next_to(title, RIGHT, 3*LARGE_BUFF) - - sum_to_zeta = Arrow(title.get_corner(DOWN+RIGHT), zeta_neg_1) - sum_to_ac = Arrow(title.get_right(), analytic_continuation) - ac_to_zeta = Arrow(analytic_continuation.get_bottom(), zeta_neg_1.get_top()) - cross = TexMobject("\\times") - cross.scale(2) - cross.set_color(RED) - cross.rotate(np.pi/6) - cross.move_to(sum_to_zeta.get_center()) - - brace = Brace(VGroup(zeta_neg_1, sum_naturals)) - words = TextMobject( - "If not equal, at least connected", - "\\\\(see links in description)" - ) - words.next_to(brace, DOWN) - - self.add(neg_twelfth, eq, zeta_neg_1, randy, title) - self.wait() - self.play( - Write(sum_naturals), - Write(q_mark), - randy.change_mode, "confused" - ) - self.play(Blink(randy)) - self.wait() - self.play(randy.change_mode, "angry") - self.play( - ShowCreation(sum_to_zeta), - Write(cross) - ) - self.play(Blink(randy)) - self.wait() - self.play( - Transform(sum_to_zeta, sum_to_ac), - FadeOut(cross), - Write(analytic_continuation), - randy.change_mode, "pondering", - randy.look_at, analytic_continuation, - ) - self.play(ShowCreation(ac_to_zeta)) - self.play(Blink(randy)) - self.wait() - self.play( - GrowFromCenter(brace), - Write(words[0]), - randy.look_at, words[0], - ) - self.wait() - self.play(FadeIn(words[1])) - self.play(Blink(randy)) - self.wait() - -class InventingMathPreview(Scene): - def construct(self): - rect = Rectangle(height = 9, width = 16) - rect.set_height(4) - title = TextMobject("What does it feel like to invent math?") - title.next_to(rect, UP) - sum_tex = TexMobject("1+2+4+8+\\cdots = -1") - sum_tex.set_width(rect.get_width()-1) - - self.play( - ShowCreation(rect), - Write(title) - ) - self.play(Write(sum_tex)) - self.wait() - -class FinalAnimationTease(Scene): - def construct(self): - morty = Mortimer().shift(2*(DOWN+RIGHT)) - bubble = morty.get_bubble(SpeechBubble) - bubble.write(""" - Want to know what - $\\zeta'(s)$ looks like? - """) - - self.add(morty) - self.play( - morty.change_mode, "hooray", - morty.look_at, bubble.content, - ShowCreation(bubble), - Write(bubble.content) - ) - self.play(Blink(morty)) - self.wait() - -class PatreonThanks(Scene): - CONFIG = { - "specific_patrons" : [ - "CrypticSwarm", - "Ali Yahya", - "Damion Kistler", - "Juan Batiz-Benet", - "Yu Jun", - "Othman Alikhan", - "Markus Persson", - "Joseph John Cox", - "Luc Ritchie", - "Shimin Kuang", - "Einar Johansen", - "Rish Kundalia", - "Achille Brighton", - "Kirk Werklund", - "Ripta Pasay", - "Felipe Diniz", - ] - } - def construct(self): - morty = Mortimer() - morty.next_to(ORIGIN, DOWN) - - n_patrons = len(self.specific_patrons) - special_thanks = TextMobject("Special thanks to:") - special_thanks.set_color(YELLOW) - special_thanks.shift(3*UP) - patreon_logo = ImageMobject("patreon", invert = False) - patreon_logo.set_height(1.5) - patreon_logo.next_to(special_thanks, DOWN) - - left_patrons = VGroup(*list(map(TextMobject, - self.specific_patrons[:n_patrons/2] - ))) - right_patrons = VGroup(*list(map(TextMobject, - self.specific_patrons[n_patrons/2:] - ))) - for patrons, vect in (left_patrons, LEFT), (right_patrons, RIGHT): - patrons.arrange(DOWN, aligned_edge = LEFT) - patrons.next_to(special_thanks, DOWN) - patrons.to_edge(vect, buff = LARGE_BUFF) - - self.add(patreon_logo) - self.play(morty.change_mode, "gracious") - self.play(Write(special_thanks, run_time = 1)) - self.play( - Write(left_patrons), - morty.look_at, left_patrons - ) - self.play( - Write(right_patrons), - morty.look_at, right_patrons - ) - self.play(Blink(morty)) - for patrons in left_patrons, right_patrons: - for index in 0, -1: - self.play(morty.look_at, patrons[index]) - self.wait() - -class CreditTwo(Scene): - def construct(self): - morty = Mortimer() - morty.next_to(ORIGIN, DOWN) - morty.to_edge(RIGHT) - - brother = PiCreature(color = GOLD_E) - brother.next_to(morty, LEFT) - brother.look_at(morty.eyes) - - headphones = Headphones(height = 1) - headphones.move_to(morty.eyes, aligned_edge = DOWN) - headphones.shift(0.1*DOWN) - - url = TextMobject("www.audible.com/3blue1brown") - url.to_corner(UP+RIGHT, buff = LARGE_BUFF) - - self.add(morty) - self.play(Blink(morty)) - self.play( - FadeIn(headphones), - Write(url), - Animation(morty) - ) - self.play(morty.change_mode, "happy") - for x in range(4): - self.wait() - self.play(Blink(morty)) - self.wait() - self.play( - FadeIn(brother), - morty.look_at, brother.eyes - ) - self.play(brother.change_mode, "surprised") - self.play(Blink(brother)) - self.wait() - self.play( - morty.look, LEFT, - brother.change_mode, "happy", - brother.look, LEFT - ) - for x in range(10): - self.play(Blink(morty)) - self.wait() - self.play(Blink(brother)) - self.wait() - -class FinalAnimation(ZetaTransformationScene): - CONFIG = { - "min_added_anchors" : 100, - } - def construct(self): - self.add_transformable_plane() - self.add_extra_plane_lines_for_zeta() - self.add_reflected_plane() - title = TexMobject("s", "\\to \\frac{d\\zeta}{ds}(", "s", ")") - title.set_color_by_tex("s", YELLOW) - title.add_background_rectangle() - title.scale(1.5) - title.to_corner(UP+LEFT) - - self.play(Write(title)) - self.add_foreground_mobjects(title) - self.wait() - self.apply_complex_function(d_zeta, run_time = 8) - self.wait() - -class Thumbnail(ZetaTransformationScene): - CONFIG = { - "anchor_density" : 35 - } - def construct(self): - self.y_min = -4 - self.y_max = 4 - self.x_min = 1 - self.x_max = int(FRAME_X_RADIUS+2) - self.add_transformable_plane() - self.add_extra_plane_lines_for_zeta() - self.add_reflected_plane() - # self.apply_zeta_function() - self.plane.set_stroke(width = 4) - - div_sum = TexMobject("-\\frac{1}{12} = ", "1+2+3+4+\\cdots") - div_sum.set_width(FRAME_WIDTH-1) - div_sum.to_edge(DOWN) - div_sum.set_color(YELLOW) - div_sum.set_background_stroke(width=8) - # for mob in div_sum.submobjects: - # mob.add_to_back(BackgroundRectangle(mob)) - - zeta = TexMobject("\\zeta(s)") - zeta.set_height(FRAME_Y_RADIUS-1) - zeta.to_corner(UP+LEFT) - - million = TexMobject("\\$1{,}000{,}000") - million.set_width(FRAME_X_RADIUS+1) - million.to_edge(UP+RIGHT) - million.set_color(GREEN_B) - million.set_background_stroke(width=8) - - self.add(div_sum, million, zeta) - - -class ZetaPartialSums(ZetaTransformationScene): - CONFIG = { - "anchor_density" : 35, - "num_partial_sums" : 12, - } - def construct(self): - self.add_transformable_plane() - self.add_extra_plane_lines_for_zeta() - self.prepare_for_transformation(self.plane) - - N_list = [2**k for k in range(self.num_partial_sums)] - sigma = TexMobject( - "\\sum_{n = 1}^N \\frac{1}{n^s}" - ) - sigmas = [] - for N in N_list + ["\\infty"]: - tex = TexMobject(str(N)) - tex.set_color(YELLOW) - new_sigma = sigma.copy() - top = new_sigma[0] - tex.move_to(top, DOWN) - new_sigma.remove(top) - new_sigma.add(tex) - new_sigma.to_corner(UP+LEFT) - sigmas.append(new_sigma) - - def get_partial_sum_func(n_terms): - return lambda s : sum([1./(n**s) for n in range(1, n_terms+1)]) - interim_planes = [ - self.plane.copy().apply_complex_function( - get_partial_sum_func(N) - ) - for N in N_list - ] - interim_planes.append(self.plane.copy().apply_complex_function(zeta)) - symbol = VGroup(TexMobject("s")) - symbol.scale(2) - symbol.set_color(YELLOW) - symbol.to_corner(UP+LEFT) - for plane, sigma in zip(interim_planes, sigmas): - self.play( - Transform(self.plane, plane), - Transform(symbol, sigma) - ) - self.wait() diff --git a/from_3b1b/on_hold/aliquot.py b/from_3b1b/on_hold/aliquot.py deleted file mode 100644 index 713f573d..00000000 --- a/from_3b1b/on_hold/aliquot.py +++ /dev/null @@ -1,77 +0,0 @@ -from manimlib.imports import * - - -def get_factors(n): - return filter( - lambda k: (n % k) == 0, - range(1, n) - ) - - -class AmicableNumbers(Scene): - CONFIG = { - "n1": 28, - "n2": 284, - "colors": [ - BLUE_C, - BLUE_B, - BLUE_D, - GREY_BROWN, - GREEN_C, - GREEN_B, - GREEN_D, - GREY, - ] - } - - def construct(self): - self.show_n1() - self.show_n1_factors() - self.show_n1_factor_sum() - self.show_n2_factors() - self.show_n2_factor_sum() - - def show_n1(self): - dots = VGroup(*[Dot() for x in range(self.n1)]) - dots.set_color(BLUE) - dots.set_sheen(0.2, UL) - dots.arrange(RIGHT, buff=SMALL_BUFF) - dots.set_width(FRAME_WIDTH - 1) - - rects = self.get_all_factor_rectangles(dots) - rects.rotate(90 * DEGREES) - rects.arrange(RIGHT, buff=MED_SMALL_BUFF, aligned_edge=DOWN) - for rect in rects: - rect.first_col.set_stroke(WHITE, 3) - rects.set_height(FRAME_HEIGHT - 1) - - self.add(rects) - - def show_n1_factors(self): - pass - - def show_n1_factor_sum(self): - pass - - def show_n2_factors(self): - pass - - def show_n2_factor_sum(self): - pass - - # - def show_factors(self, dot_group): - pass - - def get_all_factor_rectangles(self, dot_group): - n = len(dot_group) - factors = get_factors(n) - colors = it.cycle(self.colors) - result = VGroup() - for k, color in zip(factors, colors): - group = dot_group.copy() - group.set_color(color) - group.arrange_in_grid(n_rows=k, buff=SMALL_BUFF) - group.first_col = group[::(n // k)] - result.add(group) - return result diff --git a/from_3b1b/on_hold/eola2/cramer.py b/from_3b1b/on_hold/eola2/cramer.py deleted file mode 100644 index 1b46660a..00000000 --- a/from_3b1b/on_hold/eola2/cramer.py +++ /dev/null @@ -1,2280 +0,0 @@ -from manimlib.imports import * - -X_COLOR = GREEN -Y_COLOR = RED -Z_COLOR = BLUE -OUTPUT_COLOR = YELLOW -INPUT_COLOR = MAROON_B - - -OUTPUT_DIRECTORY = "eola2/cramer" - - -def get_cramer_matrix(matrix, output_vect, index=0): - """ - The inputs matrix and output_vect should be Matrix mobjects - """ - new_matrix = np.array(matrix.mob_matrix) - new_matrix[:, index] = output_vect.mob_matrix[:, 0] - # Create a new Matrix mobject with copies of these entries - result = Matrix(new_matrix, element_to_mobject=lambda m: m.copy()) - result.match_height(matrix) - return result - - -class LinearSystem(VGroup): - CONFIG = { - "matrix_config": { - "element_to_mobject": Integer, - }, - "dimensions": 3, - "min_int": -9, - "max_int": 10, - "height": 4, - } - - def __init__(self, matrix=None, output_vect=None, **kwargs): - VGroup.__init__(self, **kwargs) - if matrix is None: - dim = self.dimensions - matrix = np.random.randint( - self.min_int, - self.max_int, - size=(dim, dim) - ) - else: - dim = len(matrix) - self.matrix_mobject = Matrix(matrix, **self.matrix_config) - self.equals = TexMobject("=") - self.equals.scale(1.5) - - colors = [X_COLOR, Y_COLOR, Z_COLOR][:dim] - chars = ["x", "y", "z"][:dim] - self.input_vect_mob = Matrix(np.array(chars)) - self.input_vect_mob.elements.set_color_by_gradient(*colors) - - if output_vect is None: - output_vect = np.random.randint( - self.min_int, self.max_int, size=(dim, 1)) - self.output_vect_mob = IntegerMatrix(output_vect) - self.output_vect_mob.elements.set_color(OUTPUT_COLOR) - - for mob in self.matrix_mobject, self.input_vect_mob, self.output_vect_mob: - mob.set_height(self.height) - - self.add( - self.matrix_mobject, - self.input_vect_mob, - self.equals, - self.output_vect_mob, - ) - self.arrange(RIGHT, buff=SMALL_BUFF) - - -# Scenes - - -class AltOpeningQuote(OpeningQuote): - def construct(self): - kw = { - "tex_to_color_map": { - "Jerry": YELLOW, - "Kramer": BLUE, - }, - "arg_separator": "", - "alignment": "", - } - parts = VGroup( - TextMobject("Jerry: Ah, you're crazy!", **kw), - TextMobject( - "Kramer:", - "{} Am I? Or am I so sane that\\\\", - "you just blew your mind?", - **kw - ), - TextMobject("Jerry: It's impossible!", **kw), - TextMobject( - "Kramer:", "{} Is it?! Or is it so possible\\\\", - "your head is spinning like a top?", - **kw - ) - ) - for part in parts[1::2]: - part[-1].align_to(part[-2], LEFT) - - parts.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - - self.add(parts[0]) - self.play(FadeIn(parts[1], lag_ratio=0.1, run_time=2)) - self.play(FadeIn(parts[2], lag_ratio=0)) - self.play(FadeIn(parts[3], lag_ratio=0.1, run_time=2)) - self.wait() - - -class CramerOpeningQuote(OpeningQuote): - CONFIG = { - "quote": ["Computers are useless. They \\\\ can only give you answers."], - "author": "Pablo Picasso", - } - - -class LeaveItToComputers(TeacherStudentsScene): - CONFIG = { - "random_seed": 1, - } - - def construct(self): - system = LinearSystem(height=3) - system.next_to(self.pi_creatures, UP) - system.generate_target() - system.target.scale(0.5) - system.target.to_corner(UL) - - cramer_groups = VGroup() - for i in range(3): - numer_matrix = get_cramer_matrix( - system.matrix_mobject, system.output_vect_mob, - index=i - ) - # color = colors[i] - color = YELLOW - VGroup(*numer_matrix.mob_matrix[:, i]).set_color(color) - VGroup(*numer_matrix.mob_matrix[:, i]).set_stroke(color, 1) - numer = VGroup( - get_det_text(numer_matrix, initial_scale_factor=3), - numer_matrix - ) - numer.to_corner(UP) - denom_matrix_mobject = system.matrix_mobject.deepcopy() - denom = VGroup( - get_det_text(denom_matrix_mobject, initial_scale_factor=3), - denom_matrix_mobject, - ) - rhs = VGroup(numer, Line(LEFT, RIGHT).match_width(numer), denom) - rhs.arrange(DOWN) - rhs.set_height(2.25) - rhs.move_to(self.hold_up_spot, DOWN) - rhs.to_edge(RIGHT, buff=LARGE_BUFF) - equals = TexMobject("=").next_to(rhs, LEFT) - variable = system.input_vect_mob.elements[i].copy() - variable.next_to(equals, LEFT) - cramer_group = VGroup(variable, equals, rhs) - cramer_group.variable = variable - cramer_group.equals = equals - cramer_group.rhs = rhs - cramer_groups.add(cramer_group) - - self.play( - Write(system), - self.teacher.change, "raise_right_hand", - self.get_student_changes("pondering", "thinking", "hooray") - ) - self.wait(2) - self.play( - PiCreatureSays( - self.teacher, "Let the computer \\\\ handle it", - target_mode="shruggie", - ), - MoveToTarget(system, path_arc=90 * DEGREES), - self.get_student_changes(*["confused"] * 3) - ) - self.wait(3) - - cg = cramer_groups[0] - scale_factor = 1.5 - cg.scale(scale_factor, about_edge=DOWN) - numer, line, denom = cg.rhs - x_copy = system.input_vect_mob.elements[0].copy() - self.play( - RemovePiCreatureBubble( - self.teacher, target_mode="raise_right_hand"), - ShowCreation(line), - ReplacementTransform( - system.matrix_mobject.copy(), - denom[1], - ), - Write(denom[0]), - FadeIn(cg.equals), - ReplacementTransform(x_copy, cg.variable), - ) - denom_mover = denom.deepcopy() - denom_mover.target = numer.deepcopy() - column1 = VGroup(*denom_mover.target[1].mob_matrix[:, 0]) - column1.set_fill(opacity=0) - column1.set_stroke(width=0) - self.play(MoveToTarget(denom_mover)) - self.look_at(system) - self.play( - ReplacementTransform( - system.output_vect_mob.elements.copy(), - VGroup(*numer[1].mob_matrix[:, 0]), - path_arc=90 * DEGREES - ), - self.teacher.change, "happy", - ) - self.remove(denom_mover) - self.add(cg) - self.change_all_student_modes("sassy") - self.wait(2) - self.play( - cramer_groups[0].scale, 1 / scale_factor, - cramer_groups[0].next_to, cramer_groups[1], LEFT, MED_LARGE_BUFF, - FadeIn(cramer_groups[1]), - FadeOut(system), - self.get_student_changes(*3 * ["horrified"], look_at_arg=UP), - ) - self.wait() - self.play( - FadeIn(cramer_groups[2]), - cramer_groups[:2].next_to, cramer_groups[2], LEFT, MED_LARGE_BUFF, - self.get_student_changes(*3 * ["horrified"], look_at_arg=UP), - ) - self.wait() - - brace = Brace(cramer_groups, UP) - rule_text = brace.get_text("``Cramer's rule''") - - self.play( - GrowFromCenter(brace), - Write(rule_text), - self.get_student_changes( - "pondering", "erm", "maybe", - look_at_arg=brace, - ) - ) - self.wait(3) - - -class ShowComputer(ThreeDScene): - def construct(self): - laptop = Laptop() - laptop.add_updater(lambda m, dt: m.rotate(0.1 * dt, axis=UP)) - self.play(DrawBorderThenFill( - laptop, - lag_ratio=0.1, - rate_func=smooth - )) - self.wait(8) - self.play(FadeOut(laptop)) - - -class PrerequisiteKnowledge(TeacherStudentsScene): - CONFIG = { - "camera_config": {"background_opacity": 1} - } - - def construct(self): - self.remove(*self.pi_creatures) - randy = self.students[1] - self.add(randy) - - title = TextMobject("Prerequisites") - title.to_edge(UP) - - h_line = Line(LEFT, RIGHT).scale(5) - h_line.next_to(title, DOWN) - - images = Group(*[ - ImageMobject("eola%d_thumbnail" % d) - for d in [5, 7, 6] - ]) - images.arrange(RIGHT, buff=LARGE_BUFF) - images.next_to(h_line, DOWN, MED_LARGE_BUFF) - for image in images: - rect = SurroundingRectangle(image, color=BLUE) - image.rect = rect - - self.add(title) - self.play( - ShowCreation(h_line), - randy.change, "erm" - ) - self.wait() - for image in images: - self.play( - FadeInFromDown(image), - FadeInFromDown(image.rect), - randy.change, "pondering" - ) - self.wait() - - -class NotTheMostComputationallyEfficient(Scene): - CONFIG = { - "words": "Not the most \\\\ computationally efficient", - "word_height": 4, - "opacity": 0.7, - } - - def construct(self): - big_rect = FullScreenFadeRectangle(opacity=self.opacity) - self.add(big_rect) - - words = TextMobject(self.words) - words.set_color(RED) - words.set_stroke(WHITE, 1) - words.set_width(FRAME_WIDTH - 2 * MED_LARGE_BUFF) - self.play(Write(words)) - self.wait() - - -class GaussTitle(Scene): - def construct(self): - title = TextMobject("Gaussian Elimination") - title.scale(1.5) - title.to_edge(UP) - line = Line(LEFT, RIGHT).scale(7) - line.next_to(title, DOWN) - self.play( - FadeIn(title, lag_ratio=0.2), - ShowCreation(line), - ) - self.wait() - - -class WhyLearnIt(TeacherStudentsScene): - def construct(self): - self.student_says( - "What?!? Then why \\\\ learn it?", - bubble_kwargs={"direction": LEFT}, - student_index=2, - target_mode="angry", - ) - self.change_all_student_modes("angry") - self.wait() - self.play( - self.teacher.change, "raise_right_hand", - self.get_student_changes("erm", "happy" "pondering"), - RemovePiCreatureBubble(self.students[2], target_mode="pondering"), - ) - self.look_at(self.screen) - self.wait(10) - - -class SetupSimpleSystemOfEquations(LinearTransformationScene): - CONFIG = { - "matrix": [[3, 2], [-1, 2]], - "output_vect": [-4, -2], - "quit_before_final_transformation": False, - "array_scale_factor": 0.75, - "compare_to_big_system": True, - "transition_to_geometric_view": True, - } - - def construct(self): - self.remove_grid() - self.introduce_system() - self.from_system_to_matrix() - self.show_geometry() - - def remove_grid(self): - self.clear() - - def introduce_system(self): - system = self.system = self.get_system(self.matrix, self.output_vect) - dim = len(self.matrix) - big_dim = 7 # Big system size - big_matrix = np.random.randint(-9, 10, size=(big_dim, big_dim)) - big_output_vect = np.random.randint(-9, 10, size=big_dim) - big_matrix[:dim, :dim] = self.matrix - big_output_vect[:dim] = self.output_vect - big_system = self.get_system(big_matrix, big_output_vect) - - unknown_circles = VGroup(*[ - Circle(color=YELLOW).replace(term).scale(1.5) - for term in system.unknowns - ]) - unknown_circles.set_stroke(YELLOW, 2) - for circle in unknown_circles: - circle.save_state() - circle.scale(5) - circle.fade(1) - row_rects = VGroup(*list(map(SurroundingRectangle, system))) - row_rects.set_stroke(BLUE, 2) - - self.add(system) - self.wait() - self.play(LaggedStartMap( - ApplyMethod, unknown_circles, - lambda m: (m.restore,), - lag_ratio=0.7 - )) - self.play(FadeOut(unknown_circles)) - self.play(LaggedStartMap(ShowCreation, row_rects, - run_time=1, lag_ratio=0.8)) - self.play(FadeOut(row_rects)) - self.wait() - if self.compare_to_big_system: - self.remove(system) - self.play(ReplacementTransform(system.copy(), big_system)) - self.wait(2) - # Oh yeah, super readable line... - cutoff = 3 * dim - 1 - self.play(*[ - ReplacementTransform(big_system[i][:cutoff], system[i][:cutoff]) - for i in range(dim) - ] + [ - ReplacementTransform(big_system[i][-2:], system[i][-2:]) - for i in range(dim) - ] + [ - FadeOut(big_system[i][start:end]) - for i in range(big_dim) - for start in [cutoff if i < dim else 0] - for end in [-2 if i < dim else len(big_system[i])] - ]) - self.remove(big_system, system) - self.add(system) - - def from_system_to_matrix(self): - # dim = len(self.matrix) - system_in_lines = self.system - matrix_system = self.matrix_system = LinearSystem( - self.matrix, self.output_vect, height=2 - ) - matrix_system.center() - - corner_rect = self.corner_rect = SurroundingRectangle( - matrix_system, buff=MED_SMALL_BUFF - ) - corner_rect.set_stroke(width=0) - corner_rect.set_fill(BLACK, opacity=0.8) - corner_rect.set_height(2) - corner_rect.to_corner(UL, buff=0) - - self.play(system_in_lines.to_edge, UP) - system_in_lines_copy = system_in_lines.deepcopy() - self.play( - ReplacementTransform( - VGroup(*list(map(VGroup, system_in_lines_copy.matrix_elements))), - matrix_system.matrix_mobject.elements, - ), - Write(matrix_system.matrix_mobject.brackets), - Write(matrix_system.output_vect_mob.brackets), - Write(matrix_system.input_vect_mob.brackets), - Write(matrix_system.equals) - ) - self.play(ReplacementTransform( - VGroup(*list(map(VGroup, system_in_lines_copy.output_vect_elements))), - matrix_system.output_vect_mob.elements, - )) - self.play(*[ - ReplacementTransform(uk, elem) - for uk, elem in zip( - system_in_lines_copy.unknowns, - it.cycle(matrix_system.input_vect_mob.elements) - ) - ]) - self.wait() - if self.transition_to_geometric_view: - self.play( - Write(self.background_plane), - Write(self.plane), - FadeOut(system_in_lines), - FadeIn(corner_rect), - matrix_system.set_height, corner_rect.get_height() - MED_LARGE_BUFF, - matrix_system.move_to, corner_rect, - ) - self.play(*list(map(GrowArrow, self.basis_vectors))) - - self.add_foreground_mobject(corner_rect) - self.add_foreground_mobject(matrix_system) - - def show_geometry(self): - system = self.matrix_system - matrix_mobject = system.matrix_mobject - columns = VGroup(*[ - VGroup(*matrix_mobject.mob_matrix[:, i]) - for i in (0, 1) - ]) - - matrix = np.array(self.matrix) - first_half_matrix = np.identity(matrix.shape[0]) - first_half_matrix[:, 0] = matrix[:, 0] - second_half_matrix = np.dot( - matrix, - np.linalg.inv(first_half_matrix), - ) - - scale_factor = self.array_scale_factor - - column_mobs = VGroup() - for i in 0, 1: - column_mob = IntegerMatrix(matrix[:, i]) - column_mob.elements.set_color([X_COLOR, Y_COLOR][i]) - column_mob.scale(scale_factor) - column_mob.next_to( - self.plane.coords_to_point(*matrix[:, i]), RIGHT) - column_mob.add_to_back(BackgroundRectangle(column_mob)) - column_mobs.add(column_mob) - - output_vect_mob = self.get_vector(self.output_vect, color=OUTPUT_COLOR) - output_vect_label = system.output_vect_mob.deepcopy() - output_vect_label.add_to_back(BackgroundRectangle(output_vect_label)) - output_vect_label.generate_target() - output_vect_label.target.scale(scale_factor) - output_vect_label.target.next_to( - output_vect_mob.get_end(), LEFT, SMALL_BUFF) - - input_vect = np.dot(np.linalg.inv(self.matrix), self.output_vect) - input_vect_mob = self.get_vector(input_vect, color=INPUT_COLOR) - q_marks = TexMobject("????") - q_marks.set_color_by_gradient(INPUT_COLOR, OUTPUT_COLOR) - q_marks.next_to(input_vect_mob.get_end(), DOWN, SMALL_BUFF) - q_marks_rect = SurroundingRectangle(q_marks, color=WHITE) - - # Show output vector - self.play( - GrowArrow(output_vect_mob), - MoveToTarget(output_vect_label), - ) - self.add_foreground_mobjects(output_vect_mob, output_vect_label) - self.wait() - - # Show columns - for column, color in zip(columns, [X_COLOR, Y_COLOR]): - rect = SurroundingRectangle(column, color=WHITE) - self.play( - column.set_color, color, - system.input_vect_mob.elements.set_color, WHITE, - ShowPassingFlash(rect), - ) - matrices = [first_half_matrix, second_half_matrix] - for column, column_mob, m in zip(columns, column_mobs, matrices): - column_mob.save_state() - column_mob[0].scale(0).move_to(matrix_mobject) - column_mob.elements.become(column) - column_mob.brackets.become(matrix_mobject.brackets) - self.add_foreground_mobject(column_mob) - self.apply_matrix(m, added_anims=[ - ApplyMethod(column_mob.restore, path_arc=90 * DEGREES) - ]) - self.wait() - - # Do inverse transformation to reveal input - self.remove_foreground_mobjects(column_mobs) - self.apply_inverse(self.matrix, run_time=1, added_anims=[ - ReplacementTransform(output_vect_mob.copy(), input_vect_mob), - ReplacementTransform(output_vect_label.elements.copy(), q_marks), - FadeOut(column_mobs) - ]) - self.play(ShowPassingFlash(q_marks_rect)) - self.wait(2) - if not self.quit_before_final_transformation: - self.apply_matrix(self.matrix, added_anims=[ - FadeOut(q_marks), - ReplacementTransform(input_vect_mob, output_vect_mob), - FadeIn(column_mobs), - ]) - self.wait() - - self.q_marks = q_marks - self.input_vect_mob = input_vect_mob - self.output_vect_mob = output_vect_mob - self.output_vect_label = output_vect_label - self.column_mobs = column_mobs - - # Helpers - - def get_system(self, matrix, output_vect): - if len(matrix) <= 3: - chars = "xyzwv" - else: - chars = ["x_%d" % d for d in range(len(matrix))] - colors = [ - color - for i, color in zip( - list(range(len(matrix))), - it.cycle([X_COLOR, Y_COLOR, Z_COLOR, YELLOW, MAROON_B, TEAL]) - ) - ] - system = VGroup() - system.matrix_elements = VGroup() - system.output_vect_elements = VGroup() - system.unknowns = VGroup() - for row, num in zip(matrix, output_vect): - args = [] - for i in range(len(row)): - if i + 1 == len(row): - sign = "=" - elif row[i + 1] < 0: - sign = "-" - else: - sign = "+" - args += [str(abs(row[i])), chars[i], sign] - args.append(str(num)) - line = TexMobject(*args) - line.set_color_by_tex_to_color_map(dict([ - (char, color) - for char, color in zip(chars, colors) - ])) - system.add(line) - system.matrix_elements.add(*line[0:-1:3]) - system.unknowns.add(*line[1:-1:3]) - system.output_vect_elements.add(line[-1]) - - system.output_vect_elements.set_color(OUTPUT_COLOR) - system.arrange( - DOWN, - buff=0.75, - index_of_submobject_to_align=-2 - ) - return system - - -class FloatingMinus(Scene): - def construct(self): - minus = TexMobject("-") - self.add(minus) - self.play(minus.shift, 2.9 * UP, run_time=1) - - -class ShowZeroDeterminantCase(LinearTransformationScene): - CONFIG = { - "show_basis_vectors": True, - "matrix": [[3, -2.0], [1, -2.0 / 3]], - "tex_scale_factor": 1.25, - "det_eq_symbol": "=", - } - - def construct(self): - self.add_equation() - self.show_det_zero() - - def add_equation(self): - equation = self.equation = TexMobject( - "A", "\\vec{\\textbf{x}}", "=", "\\vec{\\textbf{v}}" - ) - equation.scale(self.tex_scale_factor) - equation.set_color_by_tex("{x}", INPUT_COLOR) - equation.set_color_by_tex("{v}", OUTPUT_COLOR) - equation.add_background_rectangle() - equation.to_corner(UL) - self.add(equation) - self.add_foreground_mobject(equation) - - def show_det_zero(self): - matrix = self.matrix - - # vect_in_span = [2, 2.0 / 3] - vect_in_span = [2, 2.0 / 3] - vect_off_span = [1, 2] - vect_in_span_mob = self.get_vector(vect_in_span, color=OUTPUT_COLOR) - vect_off_span_mob = self.get_vector(vect_off_span, color=OUTPUT_COLOR) - - for vect_mob in vect_in_span_mob, vect_off_span_mob: - circle = Circle(color=WHITE, radius=0.15, stroke_width=2) - circle.move_to(vect_mob.get_end()) - vect_mob.circle = circle - - vect_off_span_words = TextMobject("No input lands here") - vect_off_span_words.next_to(vect_off_span_mob.circle, UP) - vect_off_span_words.add_background_rectangle() - - vect_in_span_words = TextMobject("Many inputs lands here") - vect_in_span_words.next_to(vect_in_span_mob.circle, DR) - vect_in_span_words.shift_onto_screen() - vect_in_span_words.add_background_rectangle() - - moving_group = VGroup(self.plane, self.basis_vectors) - moving_group.save_state() - - solution = np.dot(np.linalg.pinv(matrix), vect_in_span) - import sympy - null_space_basis = np.array(sympy.Matrix(matrix).nullspace()) - null_space_basis = null_space_basis.flatten().astype(float) - solution_vectors = VGroup(*[ - self.get_vector( - solution + x * null_space_basis, - rectangular_stem_width=0.025, - tip_length=0.2, - ) - for x in np.linspace(-4, 4, 20) - ]) - solution_vectors.set_color_by_gradient(YELLOW, MAROON_B) - - self.apply_matrix(matrix, path_arc=0) - self.wait() - self.show_det_equation() - self.wait() - - # Mention zero determinants - self.play(GrowArrow(vect_off_span_mob)) - self.play( - ShowCreation(vect_off_span_mob.circle), - Write(vect_off_span_words), - ) - self.wait() - self.play( - FadeOut(vect_off_span_mob.circle), - ReplacementTransform(vect_off_span_mob, vect_in_span_mob), - ReplacementTransform(vect_off_span_words, vect_in_span_words), - ReplacementTransform(vect_off_span_mob.circle, - vect_in_span_mob.circle), - ) - self.wait(2) - self.play( - FadeOut(vect_in_span_words), - FadeOut(vect_in_span_mob.circle), - ApplyMethod(moving_group.restore, run_time=2), - ReplacementTransform( - VGroup(vect_in_span_mob), - solution_vectors, - run_time=2, - ), - ) - self.wait() - - # Helpers - - def show_det_equation(self): - equation = self.equation - det_equation = TexMobject( - "\\det(", "A", ")", self.det_eq_symbol, "0" - ) - det_equation.scale(self.tex_scale_factor) - det_equation.next_to( - equation, DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) - det_rect = BackgroundRectangle(det_equation) - self.play( - FadeIn(det_rect), - Write(det_equation[0]), - ReplacementTransform( - equation.get_part_by_tex("A").copy(), - det_equation.get_part_by_tex("A").copy(), - ), - Write(det_equation[2:]), - ) - self.add_foreground_mobject(det_rect, det_equation) - - -class NonZeroDeterminantCase(ShowZeroDeterminantCase, SetupSimpleSystemOfEquations): - CONFIG = { - "det_eq_symbol": "\\neq" - } - - def construct(self): - self.add_equation() - self.show_det_equation() - - output_vect = self.output_vect - matrix = self.matrix - input_vect = np.dot(np.linalg.inv(matrix), output_vect) - - input_vect_mob = self.get_vector(input_vect, color=INPUT_COLOR) - output_vect_mob = self.get_vector(output_vect, color=OUTPUT_COLOR) - - input_vect_label = TextMobject("Input") - input_vect_label.next_to(input_vect_mob.get_end(), DOWN, SMALL_BUFF) - input_vect_label.match_color(input_vect_mob) - output_vect_label = TextMobject("Output") - output_vect_label.next_to(output_vect_mob.get_end(), DOWN, SMALL_BUFF) - output_vect_label.match_color(output_vect_mob) - for label in input_vect_label, output_vect_label: - label.scale(1.25, about_edge=UP) - label.add_background_rectangle() - - self.apply_matrix(matrix) - self.wait() - self.apply_inverse(matrix) - self.wait() - self.play( - GrowArrow(input_vect_mob), - Write(input_vect_label), - run_time=1 - ) - self.wait() - self.remove(input_vect_mob, input_vect_label) - self.apply_matrix(matrix, added_anims=[ - ReplacementTransform(input_vect_mob.copy(), output_vect_mob), - ReplacementTransform(input_vect_label.copy(), output_vect_label), - ], run_time=2) - self.wait() - self.remove(output_vect_mob, output_vect_label) - self.apply_inverse(matrix, added_anims=[ - ReplacementTransform(output_vect_mob.copy(), input_vect_mob), - ReplacementTransform(output_vect_label.copy(), input_vect_label), - ], run_time=2) - self.wait() - - -class ThinkOfPuzzleAsLinearCombination(SetupSimpleSystemOfEquations): - CONFIG = { - "output_vect": [-4, -2], - } - - def construct(self): - self.force_skipping() - super(ThinkOfPuzzleAsLinearCombination, self).construct() - self.revert_to_original_skipping_status() - - self.rearrange_equation_as_linear_combination() - self.show_linear_combination_of_vectors() - - def rearrange_equation_as_linear_combination(self): - system = self.matrix_system - corner_rect = self.corner_rect - matrix, input_vect, equals, output_vect = system - - columns = VGroup(*[ - VGroup(*matrix.mob_matrix[:, i].flatten()) - for i in (0, 1) - ]) - column_arrays = VGroup(*[ - MobjectMatrix(matrix.deepcopy().mob_matrix[:, i]) - for i in (0, 1) - ]) - for column_array in column_arrays: - column_array.match_height(output_vect) - x, y = input_vect.elements - movers = VGroup(x, y, equals, output_vect) - for mover in movers: - mover.generate_target() - plus = TexMobject("+") - - new_system = VGroup( - x.target, column_arrays[0], plus, - y.target, column_arrays[1], - equals.target, - output_vect.target - ) - new_system.arrange(RIGHT, buff=SMALL_BUFF) - new_system.move_to(matrix, LEFT) - - corner_rect.generate_target() - corner_rect.target.stretch_to_fit_width( - new_system.get_width() + MED_LARGE_BUFF, - about_edge=LEFT - ) - - self.remove_foreground_mobjects(corner_rect, system) - self.play( - MoveToTarget(corner_rect), - FadeOut(input_vect.brackets), - ReplacementTransform(matrix.brackets, column_arrays[0].brackets), - ReplacementTransform(matrix.brackets.copy(), - column_arrays[1].brackets), - ReplacementTransform(columns[0], column_arrays[0].elements), - ReplacementTransform(columns[1], column_arrays[1].elements), - Write(plus, rate_func=squish_rate_func(smooth, 0.5, 1)), - *[ - MoveToTarget(mover, replace_mobject_with_target_in_scene=True) - for mover in movers - ], - path_arc=90 * DEGREES, - run_time=2 - ) - self.add_foreground_mobject(corner_rect, new_system) - self.wait() - - def show_linear_combination_of_vectors(self): - basis_vectors = self.basis_vectors - input_vect = np.dot(np.linalg.inv(self.matrix), self.output_vect) - origin = self.plane.coords_to_point(0, 0) - for basis, scalar in zip(basis_vectors, input_vect): - basis.ghost = basis.copy() - basis.ghost.set_color(average_color(basis.get_color(), BLACK)) - self.add_foreground_mobjects(basis.ghost, basis) - basis.generate_target() - basis_coords = np.array( - self.plane.point_to_coords(basis.get_end())) - new_coords = scalar * basis_coords - basis.target.put_start_and_end_on( - origin, self.plane.coords_to_point(*new_coords), - ) - - dashed_lines = VGroup(*[DashedLine(LEFT, RIGHT) for x in range(2)]) - - def update_dashed_lines(lines): - for i in 0, 1: - lines[i].put_start_and_end_on( - basis_vectors[i].get_start(), - basis_vectors[i].get_end(), - ) - lines[i].shift(basis_vectors[1 - i].get_end() - origin) - return lines - update_dashed_lines(dashed_lines) - self.play(LaggedStartMap(ShowCreation, dashed_lines, lag_ratio=0.7)) - for basis in basis_vectors: - self.play( - MoveToTarget(basis, run_time=2), - UpdateFromFunc(dashed_lines, update_dashed_lines) - ) - self.wait() - - -class WrongButHelpful(TeacherStudentsScene): - def construct(self): - self.teacher_says("What's next is wrong, \\\\ but helpful") - self.change_student_modes("sassy", "sad", "angry") - self.wait(3) - - -class LookAtDotProducts(SetupSimpleSystemOfEquations): - CONFIG = { - "quit_before_final_transformation": True, - "equation_scale_factor": 0.7, - } - - def construct(self): - self.force_skipping() - super(LookAtDotProducts, self).construct() - self.revert_to_original_skipping_status() - - self.remove_corner_system() - self.show_dot_products() - - def remove_corner_system(self): - to_remove = [self.corner_rect, self.matrix_system] - self.remove_foreground_mobjects(*to_remove) - self.remove(*to_remove) - - q_marks = self.q_marks - input_vect_mob = self.input_vect_mob - - equations = self.equations = VGroup() - for i in 0, 1: - basis = [0, 0] - basis[i] = 1 - equation = VGroup( - Matrix(["x", "y"]), - TexMobject("\\cdot"), - IntegerMatrix(basis), - TexMobject("="), - TexMobject(["x", "y"][i]), - ) - for part in equation: - if isinstance(part, Matrix): - part.scale(self.array_scale_factor) - equation[2].elements.set_color([X_COLOR, Y_COLOR][i]) - equation.arrange(RIGHT, buff=SMALL_BUFF) - equation.scale(self.equation_scale_factor) - equations.add(equation) - equations.arrange(DOWN, buff=MED_LARGE_BUFF) - equations.to_corner(UL) - corner_rect = self.corner_rect = BackgroundRectangle( - equations, opacity=0.8) - self.resize_corner_rect_to_mobjet(corner_rect, equations) - corner_rect.save_state() - self.resize_corner_rect_to_mobjet(corner_rect, equations[0]) - - xy_vect_mob = Matrix(["x", "y"], include_background_rectangle=True) - xy_vect_mob.scale(self.array_scale_factor) - xy_vect_mob.next_to(input_vect_mob.get_end(), DOWN, SMALL_BUFF) - q_marks.add_background_rectangle() - q_marks.next_to(xy_vect_mob, RIGHT) - - origin = self.plane.coords_to_point(0, 0) - input_vect_end = input_vect_mob.get_end() - x_point = (input_vect_end - origin)[0] * RIGHT + origin - y_point = (input_vect_end - origin)[1] * UP + origin - v_dashed_line = DashedLine(input_vect_end, x_point) - h_dashed_line = DashedLine(input_vect_end, y_point) - - h_brace = Brace(Line(x_point, origin), UP, buff=SMALL_BUFF) - v_brace = Brace(Line(y_point, origin), RIGHT, buff=SMALL_BUFF) - - self.add(xy_vect_mob) - self.wait() - self.play( - FadeIn(corner_rect), - FadeIn(equations[0][:-1]), - ShowCreation(v_dashed_line), - GrowFromCenter(h_brace), - ) - self.play( - ReplacementTransform(h_brace.copy(), equations[0][-1]) - ) - self.wait() - self.play( - corner_rect.restore, - Animation(equations[0]), - FadeIn(equations[1]), - ) - self.wait() - self.play( - ReplacementTransform(equations[1][-1].copy(), v_brace), - ShowCreation(h_dashed_line), - GrowFromCenter(v_brace) - ) - self.wait() - - self.to_fade = VGroup( - h_dashed_line, v_dashed_line, - h_brace, v_brace, - xy_vect_mob, q_marks, - ) - - def show_dot_products(self): - moving_equations = self.equations.copy() - transformed_equations = VGroup() - implications = VGroup() - transformed_input_rects = VGroup() - transformed_basis_rects = VGroup() - for equation in moving_equations: - equation.generate_target() - xy_vect, dot, basis, equals, coord = equation.target - T1, lp1, rp1 = TexMobject("T", "(", ")") - lp1.scale(1, about_edge=LEFT) - rp1.scale(1, about_edge=LEFT) - for paren in lp1, rp1: - paren.stretch_to_fit_height(equation.get_height()) - T2, lp2, rp2 = T1.copy(), lp1.copy(), rp1.copy() - transformed_equation = VGroup( - T1, lp1, xy_vect, rp1, dot, - T2, lp2, basis, rp2, equals, - coord - ) - transformed_equation.arrange(RIGHT, buff=SMALL_BUFF) - # transformed_equation.scale(self.equation_scale_factor) - - implies = TexMobject("\\Rightarrow").scale(1.2) - implies.next_to(equation, RIGHT) - implies.set_color(BLUE) - - transformed_equation.next_to(implies, RIGHT) - implications.add(implies) - - transformed_input_rects.add(SurroundingRectangle( - transformed_equation[:4], - color=OUTPUT_COLOR - )) - transformed_basis_rects.add(SurroundingRectangle( - transformed_equation[5:5 + 4], - color=basis.elements.get_color() - )) - - for mob in [implies]: - mob.add(TexMobject("?").next_to(mob, UP, SMALL_BUFF)) - - transformed_equation.parts_to_write = VGroup( - T1, lp1, rp1, T2, lp2, rp2 - ) - - transformed_equations.add(transformed_equation) - - corner_rect = self.corner_rect - corner_rect.generate_target() - group = VGroup(self.equations, transformed_equations) - self.resize_corner_rect_to_mobjet(corner_rect.target, group) - - for array in [self.output_vect_label] + list(self.column_mobs): - array.rect = SurroundingRectangle(array) - array.rect.match_color(array.elements) - - self.play( - MoveToTarget(corner_rect), - Animation(self.equations), - FadeOut(self.to_fade), - LaggedStartMap(Write, implications), - ) - self.remove(self.input_vect_mob) - self.apply_matrix(self.matrix, added_anims=[ - Animation(VGroup(corner_rect, self.equations, implications)), - MoveToTarget(moving_equations[0]), - LaggedStartMap(FadeIn, transformed_equations[0].parts_to_write), - FadeIn(self.column_mobs), - ReplacementTransform( - self.input_vect_mob.copy(), self.output_vect_mob) - ]) - self.play( - MoveToTarget(moving_equations[1]), - LaggedStartMap(FadeIn, transformed_equations[1].parts_to_write), - path_arc=-30 * DEGREES, - run_time=2 - ) - self.wait() - - # Show rectangles - self.play( - LaggedStartMap(ShowCreation, transformed_input_rects, lag_ratio=0.8), - ShowCreation(self.output_vect_label.rect), - ) - for tbr, column_mob in zip(transformed_basis_rects, self.column_mobs): - self.play( - ShowCreation(tbr), - ShowCreation(column_mob.rect), - ) - self.wait() - self.play(FadeOut(VGroup( - transformed_input_rects, - transformed_basis_rects, - self.output_vect_label.rect, - *[cm.rect for cm in self.column_mobs] - ))) - - # These computations assume plane is centered at ORIGIN - output_vect = self.output_vect_mob.get_end() - c1 = self.basis_vectors[0].get_end() - c2 = self.basis_vectors[1].get_end() - x_point = c1 * np.dot(output_vect, c1) / (get_norm(c1)**2) - y_point = c2 * np.dot(output_vect, c2) / (get_norm(c2)**2) - - dashed_line_to_x = DashedLine(self.output_vect_mob.get_end(), x_point) - dashed_line_to_y = DashedLine(self.output_vect_mob.get_end(), y_point) - - self.play(ShowCreation(dashed_line_to_x)) - self.play(ShowCreation(dashed_line_to_y)) - self.wait() - - # Helpers - - def resize_corner_rect_to_mobjet(self, rect, mobject): - rect.stretch_to_fit_width( - mobject.get_width() + MED_LARGE_BUFF + SMALL_BUFF) - rect.stretch_to_fit_height( - mobject.get_height() + MED_LARGE_BUFF + SMALL_BUFF) - rect.to_corner(UL, buff=0) - return rect - - -class NotAtAllTrue(NotTheMostComputationallyEfficient): - CONFIG = { - "words": "Not at all \\\\ True", - "word_height": 4, - } - - -class ShowDotProductChanging(LinearTransformationScene): - CONFIG = { - "matrix": [[2, -5.0 / 3], [-5.0 / 3, 2]], - "v": [1, 2], - "w": [2, 1], - "v_label": "v", - "w_label": "w", - "v_color": YELLOW, - "w_color": MAROON_B, - "rhs1": "> 0", - "rhs2": "< 0", - "foreground_plane_kwargs": { - "x_radius": 2 * FRAME_WIDTH, - "y_radius": 2 * FRAME_HEIGHT, - }, - "equation_scale_factor": 1.5, - } - - def construct(self): - v_mob = self.add_vector(self.v, self.v_color, animate=False) - w_mob = self.add_vector(self.w, self.w_color, animate=False) - kwargs = { - "transformation_name": "T", - "at_tip": True, - "animate": False, - } - v_label = self.add_transformable_label(v_mob, self.v_label, **kwargs) - w_label = self.add_transformable_label(w_mob, self.w_label, **kwargs) - - start_equation = self.get_equation(v_label, w_label, self.rhs1) - start_equation.to_corner(UR) - self.play( - Write(start_equation[0::2]), - ReplacementTransform(v_label.copy(), start_equation[1]), - ReplacementTransform(w_label.copy(), start_equation[3]), - ) - self.wait() - self.add_foreground_mobject(start_equation) - self.apply_matrix(self.matrix) - self.wait() - - end_equation = self.get_equation(v_label, w_label, self.rhs2) - end_equation.next_to(start_equation, DOWN, aligned_edge=RIGHT) - self.play( - FadeIn(end_equation[0]), - ReplacementTransform( - start_equation[2::2].copy(), - end_equation[2::2], - ), - ReplacementTransform(v_label.copy(), end_equation[1]), - ReplacementTransform(w_label.copy(), end_equation[3]), - ) - self.wait(2) - - def get_equation(self, v_label, w_label, rhs): - equation = VGroup( - v_label.copy(), - TexMobject("\\cdot"), - w_label.copy(), - TexMobject(rhs), - ) - equation.arrange(RIGHT, buff=SMALL_BUFF) - equation.add_to_back(BackgroundRectangle(equation)) - equation.scale(self.equation_scale_factor) - return equation - - -class ShowDotProductChangingAwayFromZero(ShowDotProductChanging): - CONFIG = { - "matrix": [[2, 2], [0, -1]], - "v": [1, 0], - "w": [0, 1], - "v_label": "x", - "w_label": "y", - "v_color": X_COLOR, - "w_color": Y_COLOR, - "rhs1": "= 0", - "rhs2": "\\ne 0", - } - - -class OrthonormalWords(Scene): - def construct(self): - v_tex = "\\vec{\\textbf{v}}" - w_tex = "\\vec{\\textbf{w}}" - top_words = TexMobject( - "\\text{If }", - "T(", v_tex, ")", "\\cdot", - "T(", w_tex, ")", "=", - v_tex, "\\cdot", w_tex, - "\\text{ for all }", v_tex, "\\text{ and }", w_tex, - ) - top_words.set_color_by_tex_to_color_map({ - v_tex: YELLOW, - w_tex: MAROON_B, - }) - bottom_words = TextMobject( - "$T$", "is", "``Orthonormal''" - ) - bottom_words.set_color_by_tex("Orthonormal", BLUE) - words = VGroup(top_words, bottom_words) - words.arrange(DOWN, buff=MED_LARGE_BUFF) - for word in words: - word.add_background_rectangle() - words.to_edge(UP) - - self.play(Write(words)) - self.wait() - - -class ShowSomeOrthonormalTransformations(LinearTransformationScene): - CONFIG = { - "random_seed": 1, - "n_angles": 7, - } - - def construct(self): - for x in range(self.n_angles): - angle = TAU * np.random.random() - TAU / 2 - matrix = rotation_matrix(angle, OUT)[:2, :2] - if x in [2, 4]: - matrix[:, 1] *= -1 - self.apply_matrix(matrix, run_time=1) - self.wait() - - -class SolvingASystemWithOrthonormalMatrix(LinearTransformationScene): - CONFIG = { - "array_scale_factor": 0.6, - } - - def construct(self): - # Setup system - angle = TAU / 12 - matrix = np.array([ - [np.cos(angle), -np.sin(angle)], - [np.sin(angle), np.cos(angle)] - ]) - output_vect = [1, 2] - symbolic_matrix = [ - ["\\cos(30^\\circ)", "-\\sin(30^\\circ)"], - ["\\sin(30^\\circ)", "\\cos(30^\\circ)"], - ] - system = LinearSystem( - matrix=symbolic_matrix, - output_vect=output_vect, - matrix_config={ - "h_buff": 2.5, - "element_to_mobject": TexMobject, - }, - height=1.25, - ) - system.to_corner(UL) - system.matrix_mobject.set_column_colors(X_COLOR, Y_COLOR) - system.input_vect_mob.elements.set_color(WHITE) - system_rect = BackgroundRectangle(system, buff=MED_SMALL_BUFF) - - matrix_brace = Brace(system.matrix_mobject, DOWN, buff=SMALL_BUFF) - orthonomal_label = TextMobject("Orthonormal") - orthonomal_label.set_color(WHITE) - orthonomal_label.next_to(matrix_brace, DOWN, SMALL_BUFF) - orthonomal_label.add_background_rectangle() - - # Input and output vectors - output_vect_mob = self.get_vector(output_vect, color=OUTPUT_COLOR) - output_vect_label = system.output_vect_mob.copy() - output_vect_label.add_background_rectangle() - output_vect_label.scale(self.array_scale_factor) - output_vect_label.next_to( - output_vect_mob.get_end(), RIGHT, buff=SMALL_BUFF) - - input_vect = np.dot(np.linalg.inv(matrix), output_vect) - input_vect_mob = self.get_vector(input_vect, color=INPUT_COLOR) - input_vect_label = TextMobject("Mystery input vector") - input_vect_label.add_background_rectangle() - input_vect_label.next_to(input_vect_mob.get_end(), RIGHT, SMALL_BUFF) - input_vect_label.match_color(input_vect_mob) - - # Column arrays - column_mobs = VGroup() - for i, vect in zip(list(range(2)), [DR, DL]): - elements = system.matrix_mobject.deepcopy().mob_matrix[:, i] - column_mob = MobjectMatrix(elements) - column_mob.add_background_rectangle() - column_mob.scale(self.array_scale_factor) - column_mob.next_to( - self.get_vector(matrix[:, i]).get_end(), vect, - buff=SMALL_BUFF - ) - column_mobs.add(column_mob) - column_mobs[1].shift(SMALL_BUFF * UP) - - # Dot product lines - x_point = self.plane.coords_to_point(input_vect[0], 0) - y_point = self.plane.coords_to_point(0, input_vect[1]) - input_dashed_lines = VGroup( - DashedLine(input_vect_mob.get_end(), x_point), - DashedLine(input_vect_mob.get_end(), y_point), - ) - output_dashed_lines = input_dashed_lines.copy() - output_dashed_lines.apply_matrix(matrix) - - self.add_foreground_mobjects(system_rect, system) - self.add_foreground_mobjects(matrix_brace, orthonomal_label) - self.add_foreground_mobjects(output_vect_mob, output_vect_label) - self.plane.set_stroke(width=2) - - self.apply_matrix(matrix) - self.wait() - self.play(LaggedStartMap(ShowCreation, output_dashed_lines)) - self.play(*self.get_column_animations(system.matrix_mobject, column_mobs)) - self.wait() - self.remove(*output_dashed_lines) - self.apply_inverse(matrix, added_anims=[ - FadeOut(column_mobs), - ReplacementTransform(output_vect_mob.copy(), input_vect_mob), - ReplacementTransform( - output_dashed_lines.copy(), input_dashed_lines), - FadeIn(input_vect_label), - ]) - self.wait() - self.remove(input_dashed_lines, input_vect_mob) - self.apply_matrix(matrix, added_anims=[ - FadeOut(input_vect_label), - ReplacementTransform(input_vect_mob.copy(), output_vect_mob), - ReplacementTransform(input_dashed_lines.copy(), - output_dashed_lines), - FadeIn(column_mobs), - ]) - - # Write dot product equations - equations = VGroup() - for i in 0, 1: - moving_output_vect_label = output_vect_label.copy() - moving_column_mob = column_mobs[i].copy() - moving_var = system.input_vect_mob.elements[i].copy() - equation = VGroup( - moving_var.generate_target(), - TexMobject("="), - moving_output_vect_label.generate_target(), - TexMobject("\\cdot"), - moving_column_mob.generate_target(use_deepcopy=True) - ) - equation.movers = VGroup( - moving_var, moving_output_vect_label, moving_column_mob - ) - for element in moving_column_mob.target.get_family(): - if not isinstance(element, TexMobject): - continue - tex_string = element.get_tex_string() - if "sin" in tex_string or "cos" in tex_string: - element.set_stroke(width=1) - element.scale(1.25) - equation.to_write = equation[1::2] - equation[2].match_height(equation[4]) - equation.arrange(RIGHT, buff=SMALL_BUFF) - equation.background_rectangle = BackgroundRectangle(equation) - equation.add_to_back(equation.background_rectangle) - equations.add(equation) - equations.arrange(DOWN, buff=MED_LARGE_BUFF) - equations.scale(1.25) - equations.to_corner(UR, buff=MED_SMALL_BUFF) - equations_rect = BackgroundRectangle(equations, buff=MED_LARGE_BUFF) - equations_rect.set_fill(opacity=0.9) - - for i, equation in enumerate(equations): - anims = [ - FadeIn(equation.background_rectangle), - Write(equation.to_write), - LaggedStartMap( - MoveToTarget, equation.movers, - path_arc=60 * DEGREES - ) - ] - if i == 0: - anims.insert(0, FadeIn(equations_rect)) - self.play(*anims) - self.wait() - - def get_column_animations(self, matrix_mobject, column_mobs): - def get_kwargs(i): - return { - "rate_func": squish_rate_func(smooth, 0.4 * i, 0.6 + 0.4 * i), - "run_time": 2, - } - return list(it.chain(*[ - [ - FadeIn(cm[0], **get_kwargs(i)), - ReplacementTransform( - matrix_mobject.brackets.copy(), - cm.brackets, - **get_kwargs(i) - ), - ReplacementTransform( - VGroup(*matrix_mobject.mob_matrix[:, i]).copy(), - cm.elements, - **get_kwargs(i) - ), - ] - for i, cm in enumerate(column_mobs) - ])) - - -class TransitionToParallelogramIdea(TeacherStudentsScene): - def construct(self): - teacher_words = TextMobject( - "Now ask about \\\\ other geometric \\\\ views of", - "$x$", "and", "$y$" - ) - teacher_words.set_color_by_tex_to_color_map({ - "$x$": X_COLOR, - "$y$": Y_COLOR, - }) - - self.student_says( - "But that's a super \\\\ specific case", - target_mode="sassy", - added_anims=[self.teacher.change, "guilty"] - ) - self.change_student_modes("confused", "sassy", "angry") - self.wait() - self.teacher_says( - teacher_words, - added_anims=[self.get_student_changes(*["pondering"] * 3)] - ) - self.wait() - - -class TransformingAreasYCoord(LinearTransformationScene): - CONFIG = { - # Determines whether x-coordinate or y-coordinate is computed - "index": 1, - "matrix": [[2, -1], [0, 1]], - "input_vect": [3, 2], - "array_scale_factor": 0.7, - } - - def construct(self): - self.init_matrix() - self.init_plane() - self.show_coord_parallelogram() - self.transform_space() - self.solve_for_coord() - - def init_matrix(self): - self.matrix = np.array(self.matrix) - - def init_plane(self): - self.plane.set_stroke(width=2) - - def show_coord_parallelogram(self): - index = self.index - non_index = (index + 1) % 2 - - input_vect = self.input_vect - input_vect_mob = self.get_vector(input_vect, color=INPUT_COLOR) - input_vect_label = Matrix(["x", "y"]) - input_vect_label.add_background_rectangle() - input_vect_label.scale(self.array_scale_factor) - self.set_input_vect_label_position(input_vect_mob, input_vect_label) - - mystery_words = TextMobject("Mystery input vector") - mystery_words.next_to(input_vect_label, RIGHT) - mystery_words.add_background_rectangle() - - # Add basis vector labels - basis_labels = self.basis_labels = VGroup() - basis_vectors = self.basis_vectors - chars = ["\\imath", "\\jmath"] - directions = ["right", "left"] - for basis, char, direction in zip(basis_vectors, chars, directions): - label = self.get_vector_label( - basis, "\\mathbf{\\hat{%s}}" % char, - direction=direction - ) - self.basis_labels.add(label) - - ip = self.get_input_parallelogram(input_vect_mob) - area_arrow_direction = 1.5 * DOWN + RIGHT if self.index == 0 else DR - area_arrow = Vector( - area_arrow_direction, color=WHITE, - rectangular_stem_width=0.025, - tip_length=0.2, - ) - area_arrow.shift(ip.get_center() - area_arrow.get_end() + SMALL_BUFF * DL) - area_words = TexMobject( - "\\text{Area}", "=", "1", "\\times", - ["x", "y"][index] - ) - area_words.next_to( - area_arrow.get_start(), UL, SMALL_BUFF, - submobject_to_align=area_words[0] - ) - area_words.set_color_by_tex_to_color_map({ - "Area": YELLOW, - "x": X_COLOR, - "y": Y_COLOR, - }) - area_words.rect = BackgroundRectangle(area_words) - - origin = self.plane.coords_to_point(0, 0) - unit_brace = Brace( - Line(origin, basis_vectors[non_index].get_end()), - [DOWN, LEFT][non_index], - buff=SMALL_BUFF - ) - one = unit_brace.get_tex("1") - one.add_background_rectangle() - coord_brace = self.get_parallelogram_braces(ip)[index] - - self.add(input_vect_mob, input_vect_label) - self.add(basis_labels) - - self.play( - FadeIn(ip), - Animation(VGroup(basis_vectors, input_vect_mob)) - ) - self.play( - ShowCreationThenDestruction(SurroundingRectangle(basis_labels[non_index])), - GrowArrow(self.basis_vectors[non_index].copy(), remover=True) - ) - self.play( - ShowCreationThenDestruction(SurroundingRectangle(input_vect_label)), - GrowArrow(input_vect_mob.copy(), remover=True), - ) - self.wait() - self.play( - FadeIn(area_words.rect), - Write(area_words[:2]), - GrowArrow(area_arrow), - ) - self.wait() - self.play( - FadeOut(basis_labels), - GrowFromCenter(unit_brace), - FadeIn(one), - FadeIn(area_words[2]) - ) - self.wait() - self.play( - GrowFromCenter(coord_brace), - FadeIn(coord_brace.label), - FadeIn(area_words[3:]), - ) - self.wait() - self.play( - area_words.rect.stretch_to_fit_width, - area_words[:3].get_width() + SMALL_BUFF, {"about_edge": LEFT}, - FadeOut(area_words[2:4]), - area_words[4].shift, - (area_words[2].get_left()[0] - area_words[4].get_left()[0]) * RIGHT, - Animation(area_words[:2]), - ) - area_words.remove(*area_words[2:4]) - self.wait() - - # Run with me - morty = Mortimer(height=2).flip() - morty.to_corner(DL) - randy = Randolph(height=2, color=BLUE_C).flip() - randy.move_to(4 * RIGHT) - randy.to_edge(DOWN) - self.play(FadeIn(randy)) - self.play(randy.change, "confused", ip) - self.play(Blink(randy), FadeIn(morty)) - self.play( - PiCreatureSays(morty, "Run with \\\\ me here...", look_at_arg=randy.eyes), - randy.look_at, morty.eyes, - ) - self.play(Blink(morty)) - self.play(FadeOut(VGroup(morty, morty.bubble, morty.bubble.content, randy))) - - # Signed area - signed = TextMobject("Signed") - signed.match_color(area_words[0]) - signed.next_to(area_words, LEFT) - - brace_group = VGroup(coord_brace, coord_brace.label) - - def update_brace_group(brace_group): - new_brace = self.get_parallelogram_braces(ip)[index] - new_group = VGroup(new_brace, new_brace.label) - Transform(brace_group, new_group).update(1) - - area_words.add_to_back(signed) - self.play( - area_words.rect.stretch_to_fit_width, area_words.get_width(), - {"about_edge": RIGHT}, - Write(signed), - Animation(area_words), - ) - self.play( - UpdateFromFunc( - ip, lambda m: Transform(m, self.get_input_parallelogram(input_vect_mob)).update(1) - ), - input_vect_mob.rotate, np.pi, {"about_point": origin}, - Animation(self.basis_vectors), - UpdateFromFunc(brace_group, update_brace_group), - UpdateFromFunc( - input_vect_label, - lambda ivl: self.set_input_vect_label_position(input_vect_mob, ivl) - ), - MaintainPositionRelativeTo(area_arrow, ip), - MaintainPositionRelativeTo(area_words.rect, area_arrow), - MaintainPositionRelativeTo(area_words, area_arrow), - run_time=9, - rate_func=there_and_back_with_pause, - ) - - # Fade out unneeded bits - self.play(LaggedStartMap(FadeOut, VGroup( - unit_brace, one, coord_brace, coord_brace.label, - ))) - - self.input_parallelogram = ip - self.area_words = area_words - self.area_arrow = area_arrow - self.input_vect_mob = input_vect_mob - self.input_vect_label = input_vect_label - - def transform_space(self): - matrix = self.matrix - ip = self.input_parallelogram - area_words = self.area_words - area_arrow = self.area_arrow - input_vect_mob = self.input_vect_mob - input_vect_label = self.input_vect_label - basis_vectors = self.basis_vectors - index = self.index - non_index = (index + 1) % 2 - - apply_words = TextMobject("Apply") - apply_words.add_background_rectangle() - matrix_mobject = IntegerMatrix(self.matrix) - matrix_mobject.set_column_colors(X_COLOR, Y_COLOR) - matrix_mobject.add_background_rectangle() - matrix_mobject.next_to(apply_words, RIGHT) - matrix_brace = Brace(matrix_mobject, DOWN, buff=SMALL_BUFF) - matrix_label = matrix_brace.get_tex("A") - matrix_label.add_background_rectangle() - apply_group = VGroup(apply_words, matrix_mobject, matrix_brace, matrix_label) - apply_group.to_corner(UL, buff=MED_SMALL_BUFF) - - area_scale_words = TextMobject("All areas get scaled by", "$\\det(A)$") - area_scale_words.scale(1.5) - area_scale_words.move_to(2 * DOWN) - area_scale_words.add_background_rectangle() - - blobs = VGroup( - Circle(radius=0.5).move_to(2 * LEFT + UP), - Square(side_length=1).rotate(TAU / 12).move_to(2 * UP + 0.5 * RIGHT), - TexMobject("\\pi").scale(3).move_to(3 * RIGHT) - ) - blobs.set_stroke(YELLOW, 3) - blobs.set_fill(YELLOW, 0.3) - - # Initial transform - self.add_transformable_mobject(ip) - self.add(self.basis_vectors) - self.add_vector(input_vect_mob, animate=False) - self.play( - Write(apply_words), FadeIn(matrix_mobject), - GrowFromCenter(matrix_brace), Write(matrix_label), - ) - self.add_foreground_mobjects(apply_group) - self.play(*list(map(FadeOut, [ - area_words.rect, area_words, area_arrow, input_vect_label, - ]))) - self.apply_matrix(matrix) - self.wait(2) - self.apply_inverse(matrix, run_time=0) - - # Show many areas - self.play( - LaggedStartMap(DrawBorderThenFill, blobs), - Write(area_scale_words) - ) - self.add_transformable_mobject(blobs) - self.add_foreground_mobject(area_scale_words) - self.apply_matrix(matrix) - - # Ask about parallelogram - ip_copy = ip.copy() - ip_copy.set_stroke(BLACK, 4) - ip_copy.set_fill(BLACK, 0) - - q_marks = TexMobject("???") - q_marks.scale(1.5) - q_marks.rect = BackgroundRectangle(q_marks) - q_marks_group = VGroup(q_marks, q_marks.rect) - q_marks_group.rotate(input_vect_mob.get_angle()) - q_marks_group.move_to(ip) - - column_mobs = VGroup() - for i, vect in zip(list(range(2)), [DOWN, LEFT]): - column = matrix_mobject.deepcopy().mob_matrix[:, i] - column_mob = MobjectMatrix(column) - column_mob.scale(self.array_scale_factor) - column_mob.next_to(basis_vectors[i].get_end(), vect) - column_mob.add_background_rectangle() - column_mobs.add(column_mob) - column_mob = column_mobs[non_index] - - transformed_input_vect_label = VGroup(input_vect_label.copy()) - transformed_input_vect_label.add_to_back(matrix_label.copy()) - transformed_input_vect_label.arrange(RIGHT, buff=SMALL_BUFF) - transformed_input_vect_label.next_to(input_vect_mob.get_end(), UP) - - self.play( - ShowPassingFlash(ip_copy), - FadeIn(q_marks.rect), - Animation(ip), - Animation(basis_vectors), - Animation(input_vect_mob), - Write(q_marks), - LaggedStartMap(FadeOut, blobs), - ) - self.transformable_mobjects.remove(blobs) - self.play( - FadeIn(column_mob.background_rectangle), - ReplacementTransform( - matrix_mobject.brackets.copy(), column_mob.brackets - ), - ReplacementTransform( - VGroup(*matrix_mobject.mob_matrix[:, non_index]).copy(), - column_mob.elements - ), - ) - self.wait() - self.play( - ReplacementTransform(matrix_label.copy(), transformed_input_vect_label[0]), - FadeIn(transformed_input_vect_label[1]) - ) - self.wait(2) - - # Back to input state - self.remove(q_marks.rect) - self.apply_inverse(matrix, added_anims=[ - FadeOut(q_marks), - FadeOut(transformed_input_vect_label), - FadeOut(column_mob), - FadeIn(area_words.rect), - FadeIn(area_words), - FadeIn(area_arrow), - ]) - self.wait(2) - - # Show how parallelogram scales by det(A) - self.apply_matrix(matrix, added_anims=[ - UpdateFromFunc( - area_arrow, - lambda a: a.put_start_and_end_on( - area_arrow.get_start(), ip.get_center() + SMALL_BUFF * DL - ) - ), - Animation(area_words.rect), - Animation(area_words), - ]) - - det_A = area_scale_words.get_part_by_tex("det").copy() - det_A.generate_target() - det_A.target.scale(1.0 / 1.5) - det_A.target.next_to( - area_words[1:3], RIGHT, SMALL_BUFF, - aligned_edge=DOWN, - submobject_to_align=det_A.target[0] - ) - coord = area_words[-1] - coord.generate_target() - coord.target.next_to(det_A.target, RIGHT, SMALL_BUFF) - - self.play( - area_words.rect.match_width, VGroup(area_words, coord.target), - {"stretch": True, "about_edge": LEFT}, - Animation(area_words), - MoveToTarget(det_A), - MoveToTarget(coord), - ) - self.wait(2) - area_words.submobjects.insert(-1, det_A) - - self.area_scale_words = area_scale_words - self.apply_group = apply_group - - def solve_for_coord(self): - apply_group = self.apply_group - area_words = self.area_words.copy() - index = self.index - non_index = (index + 1) % 2 - - # Setup rearrangement - signed, area, equals, det, coord = area_words - for part in area_words: - part.add_background_rectangle() - part.generate_target() - apply_word, matrix_mobject, matrix_brace, matrix_label = apply_group - - h_line = Line(LEFT, RIGHT).match_width(det) - frac = VGroup(area.target, h_line, det.target) - frac.arrange(DOWN) - coord_equation = VGroup(coord.target, equals.target, frac) - equals.target.next_to(coord.target, RIGHT) - frac.next_to(equals.target, RIGHT, submobject_to_align=h_line) - coord_equation.next_to(ORIGIN, DOWN, buff=1.2) - coord_equation.to_edge(LEFT) - - output_vect = np.dot(self.matrix, self.input_vect) - new_matrix = np.array(self.matrix) - new_matrix[:, self.index] = output_vect - - # Setup rhs - frac_matrix_height = 1.5 - matrix_mobject_copy = matrix_mobject.copy() - matrix_mobject_copy.set_height(frac_matrix_height) - denom_det_text = get_det_text(matrix_mobject_copy) - top_matrix_mobject = IntegerMatrix(new_matrix) - top_matrix_mobject.set_height(frac_matrix_height) - top_matrix_mobject.set_column_colors(X_COLOR, Y_COLOR) - VGroup(*top_matrix_mobject.mob_matrix[:, self.index]).set_color(MAROON_B) - top_matrix_mobject.add_background_rectangle() - num_det_text = get_det_text(top_matrix_mobject) - rhs_h_line = Line(LEFT, RIGHT) - rhs_h_line.match_width(num_det_text) - rhs = VGroup( - VGroup(num_det_text, top_matrix_mobject), - rhs_h_line, - VGroup(matrix_mobject_copy, denom_det_text) - ) - rhs.arrange(DOWN, buff=SMALL_BUFF) - rhs_equals = TexMobject("=") - rhs_equals.next_to(h_line, RIGHT) - rhs.next_to(rhs_equals, submobject_to_align=rhs_h_line) - - # Setup linear system - output_vect_label = IntegerMatrix(output_vect) - output_vect_label.elements.match_color(self.input_vect_mob) - output_vect_label.scale(self.array_scale_factor) - output_vect_label.add_background_rectangle() - self.set_input_vect_label_position(self.input_vect_mob, output_vect_label) - - matrix_mobject.generate_target() - system_input = Matrix(["x", "y"]) - system_input.add_background_rectangle() - system_output = output_vect_label.copy() - system_output.generate_target() - system_eq = TexMobject("=") - for array in system_input, system_output.target: - array.match_height(matrix_mobject.target) - system = VGroup( - matrix_mobject.target, - system_input, system_eq, system_output.target - ) - system.arrange(RIGHT, buff=SMALL_BUFF) - system.to_corner(UL) - - # Rearrange - self.play( - FadeOut(self.area_scale_words), - ShowCreation(h_line), - *list(map(MoveToTarget, area_words[1:])), - run_time=3 - ) - self.wait() - self.play( - ReplacementTransform(matrix_mobject.copy(), matrix_mobject_copy), - Write(rhs_equals), - Write(denom_det_text), - ShowCreation(rhs_h_line) - ) - self.wait(2) - - # Show output coord - self.play( - FadeIn(output_vect_label.background_rectangle), - ReplacementTransform( - self.input_vect_mob.copy(), - output_vect_label.elements, - ), - Write(output_vect_label.brackets), - ) - self.wait() - self.play( - FadeOut(apply_word), - MoveToTarget(matrix_mobject), - MaintainPositionRelativeTo(matrix_brace, matrix_mobject), - MaintainPositionRelativeTo(matrix_label, matrix_mobject), - ) - self.play( - FadeIn(system_input), - FadeIn(system_eq), - MoveToTarget(system_output), - area_words.rect.stretch_to_fit_width, self.area_words[1:].get_width(), - {"about_edge": RIGHT}, - Animation(self.area_words[1:]), - FadeOut(self.area_words[0]), - ) - self.wait() - replaced_column = VGroup(*top_matrix_mobject.mob_matrix[:, self.index]) - replaced_column.set_fill(opacity=0) - self.play( - ReplacementTransform(matrix_mobject_copy.copy(), top_matrix_mobject), - ReplacementTransform(denom_det_text.copy(), num_det_text), - ) - self.wiggle_vector(self.basis_vectors[non_index]) - self.wait() - self.wiggle_vector(self.input_vect_mob) - self.remove(replaced_column) - self.play( - Transform( - output_vect_label.elements.copy(), - replaced_column.copy().set_fill(opacity=1), - remover=True - ) - ) - replaced_column.set_fill(opacity=1) - self.wait() - - # Circle equation - equation = VGroup( - area_words[1:], h_line, - rhs_equals, rhs, - ) - equation_rect = SurroundingRectangle(equation) - equation_rect.set_stroke(YELLOW, 3) - equation_rect.set_fill(BLACK, 0.9) - - self.play( - DrawBorderThenFill(equation_rect), - Animation(equation) - ) - self.wait() - - # Helpers - - def get_input_parallelogram(self, input_vect_mob): - input_vect = np.array(self.plane.point_to_coords(input_vect_mob.get_end())) - matrix = self.matrix - dim = matrix.shape[0] - # Transofmration from unit square to input parallelogram - square_to_ip = self.square_to_ip = np.identity(dim) - square_to_ip[:, self.index] = np.array(input_vect) - - ip = self.get_unit_square() - ip.apply_matrix(self.square_to_ip) - if input_vect[self.index] < 0: - ip.set_color(GOLD) - - return ip - - def get_parallelogram_braces(self, input_parallelogram): - braces = VGroup(*[ - Brace( - input_parallelogram, vect, - buff=SMALL_BUFF, - min_num_quads=3, - max_num_quads=3, - ) - for vect in (DOWN, RIGHT) - ]) - for brace, tex, color in zip(braces, "xy", [X_COLOR, Y_COLOR]): - brace.label = brace.get_tex(tex, buff=SMALL_BUFF) - brace.label.add_background_rectangle() - brace.label.set_color(color) - return braces - - def set_input_vect_label_position(self, input_vect_mob, input_vect_label): - direction = np.sign(input_vect_mob.get_vector()) - input_vect_label.next_to(input_vect_mob.get_end(), direction, buff=SMALL_BUFF) - return input_vect_label - - def wiggle_vector(self, vector_mob): - self.play( - Rotate( - vector_mob, 15 * DEGREES, - about_point=ORIGIN, - rate_func=wiggle, - ) - ) - - -class TransformingAreasXCoord(TransformingAreasYCoord): - CONFIG = { - "index": 0, - } - - -class ThreeDDotVectorWithKHat(ExternallyAnimatedScene): - pass - - -class ZEqualsVDotK(Scene): - def construct(self): - equation = VGroup( - TexMobject("z"), - TexMobject("="), - Matrix(["x", "y", "z"]), - TexMobject("\\cdot"), - IntegerMatrix([0, 0, 1]), - ) - equation[2].elements.set_color_by_gradient(X_COLOR, Y_COLOR, Z_COLOR) - equation[4].elements.set_color(BLUE) - equation.arrange(RIGHT, buff=MED_SMALL_BUFF) - equation.to_edge(LEFT) - - self.play(Write(equation)) - self.wait() - - -class InputParallelepipedAngledView(ExternallyAnimatedScene): - pass - - -class InputParallelepipedTopViewToSideView(ExternallyAnimatedScene): - pass - - -class ParallelepipedForXCoordinate(ExternallyAnimatedScene): - pass - - -class ParallelepipedForYCoordinate(ExternallyAnimatedScene): - pass - - -class ThreeDCoordinatesAsVolumes(Scene): - def construct(self): - colors = [X_COLOR, Y_COLOR, Z_COLOR] - x, y, z = coords = VGroup(*list(map(TexMobject, "xyz"))) - coords.set_color_by_gradient(*colors) - matrix = IntegerMatrix(np.identity(3)) - matrix.set_column_colors(*colors) - det_text = get_det_text(matrix) - equals = TexMobject("=") - equals.next_to(det_text[0], LEFT) - equals.shift(SMALL_BUFF * DOWN) - coords.next_to(equals, LEFT) - - columns = VGroup(*[ - VGroup(*matrix.mob_matrix[:, i]) - for i in range(3) - ]) - - coord_column = coords.copy() - for coord, m_elem in zip(coord_column, columns[2]): - coord.move_to(m_elem) - coord_column.set_color(WHITE) - coord_column[2].set_color(BLUE) - coord_column.generate_target() - - self.play(LaggedStartMap(FadeIn, VGroup( - z, equals, det_text, matrix.brackets, - VGroup(*matrix.mob_matrix[:, :2].flatten()), - coord_column - ))) - self.wait(2) - coord_column.target.move_to(columns[1]) - coord_column.target.set_color_by_gradient(WHITE, Y_COLOR, WHITE) - self.play( - MoveToTarget(coord_column, path_arc=np.pi), - FadeOut(columns[1]), - FadeIn(columns[2]), - FadeInFromDown(y), - z.shift, UP, - z.fade, 1, - ) - self.wait(2) - coord_column.target.move_to(columns[0]) - coord_column.target.set_color_by_gradient(X_COLOR, WHITE, WHITE) - self.play( - MoveToTarget(coord_column, path_arc=np.pi), - FadeOut(columns[0]), - FadeIn(columns[1]), - FadeInFromDown(x), - y.shift, UP, - y.fade, 1, - ) - self.wait(2) - - -class WriteCramersRule(Scene): - def construct(self): - words = TextMobject("``Cramer's Rule''") - words.set_width(FRAME_WIDTH - LARGE_BUFF) - words.add_background_rectangle() - self.play(Write(words)) - self.wait() - - -class CramersYEvaluation(Scene): - def construct(self): - frac = TexMobject("{(2)(2) - (4)(0) \\over (2)(1) - (-1)(0)}")[0] - VGroup(frac[1], frac[11], frac[15], frac[26]).set_color(GREEN) - VGroup(frac[4], frac[8]).set_color(MAROON_B) - VGroup(frac[18], frac[22], frac[23]).set_color(RED) - - group = VGroup( - TexMobject("="), frac, - TexMobject("= \\frac{4}{2}"), TexMobject("=2") - ) - group.arrange(RIGHT) - - self.add(group) - self.wait(1) - - -# Largely copy-pasted. Not great, but what are you gonna do. -class CramersXEvaluation(Scene): - def construct(self): - frac = TexMobject("{(4)(1) - (-1)(2) \\over (2)(1) - (-1)(0)}")[0] - VGroup(frac[1], frac[12]).set_color(MAROON_B) - VGroup(frac[4], frac[8], frac[9]).set_color(RED) - VGroup(frac[16], frac[27]).set_color(GREEN) - VGroup(frac[19], frac[23], frac[24]).set_color(RED) - - group = VGroup( - TexMobject("="), frac, - TexMobject("= \\frac{6}{2}"), TexMobject("=3") - ) - group.arrange(RIGHT) - group.add_to_back(BackgroundRectangle(group)) - - self.add(group) - self.wait(1) - - -class FourPlusTwo(Scene): - def construct(self): - p1 = TexMobject("(4)(1)") - p2 = TexMobject("-(-1)(2)") - p2.next_to(p1, RIGHT, SMALL_BUFF) - b1 = Brace(p1, UP) - b2 = Brace(p2, UP) - t1 = b1.get_tex("4", buff=SMALL_BUFF) - t2 = b2.get_tex("+2", buff=SMALL_BUFF) - - for b, t in (b1, t1), (b2, t2): - t.set_stroke(BLACK, 3, background=True) - self.play( - GrowFromCenter(b), - Write(t) - ) - self.wait() - - -class Equals2(Scene): - def construct(self): - self.add(TexMobject("=2").add_background_rectangle()) - self.wait() - - -class Equals3(Scene): - def construct(self): - self.add(TexMobject("=3").add_background_rectangle()) - self.wait() - - -class Introduce3DSystem(SetupSimpleSystemOfEquations): - CONFIG = { - "matrix": [[3, 2, -7], [1, 2, -4], [4, 0, 1]], - "output_vect": [4, 2, 5], - "compare_to_big_system": False, - "transition_to_geometric_view": False, - } - - def construct(self): - self.remove_grid() - self.introduce_system() - self.from_system_to_matrix() - - -class MysteryInputLabel(Scene): - def construct(self): - brace = Brace(Line(ORIGIN, RIGHT), DOWN) - text = brace.get_text("Mystery input") - self.play( - GrowFromCenter(brace), - Write(text) - ) - self.wait() - - -class ThinkItThroughYourself(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Try thinking \\\\ it through!", - target_mode="hooray" - ) - self.change_all_student_modes("pondering") - self.wait(4) - - -class TransformParallelepipedWithoutGrid(ExternallyAnimatedScene): - pass - - -class TransformParallelepipedWithGrid(ExternallyAnimatedScene): - pass - - -class AreYouPausingAndPondering(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Are you pausing \\\\ and pondering?", - target_mode="sassy", - added_anims=[self.get_student_changes(*3 * ["guilty"])] - ) - self.wait() - self.change_all_student_modes( - "thinking", - added_anims=[ - RemovePiCreatureBubble(self.teacher, target_mode="raise_right_hand") - ], - look_at_arg=self.screen - ) - self.wait(6) - - -class Thumbnail(TransformingAreasYCoord, MovingCameraScene): - CONFIG = { - "background_plane_kwargs": { - "y_max": 6, - }, - } - - def setup(self): - TransformingAreasYCoord.setup(self) - MovingCameraScene.setup(self) - - def construct(self): - self.matrix = np.array(self.matrix) - vect = self.add_vector([1, 1.5], color=MAROON_B) - ip = self.get_input_parallelogram(vect) - self.add_transformable_mobject(ip) - self.apply_transposed_matrix([[2, -0.5], [1, 2]]) - self.square.set_fill(opacity=0.7) - self.square.set_sheen(0.75, UR) - self.camera_frame.shift(UP) - - words = TextMobject("Cramer's", "rule") - words.scale(3) - words.set_stroke(BLACK, 6, background=True) - words.to_edge(UP, buff=-MED_LARGE_BUFF) - words.add_background_rectangle() - self.add(words) diff --git a/from_3b1b/on_hold/eola2/determinant_puzzle.py b/from_3b1b/on_hold/eola2/determinant_puzzle.py deleted file mode 100644 index 35bb7360..00000000 --- a/from_3b1b/on_hold/eola2/determinant_puzzle.py +++ /dev/null @@ -1,283 +0,0 @@ -from manimlib.imports import * - - -class WorkOutNumerically(Scene): - CONFIG = { - "M1_COLOR": TEAL, - "M2_COLOR": PINK, - } - - def construct(self): - self.add_question() - self.add_example() - self.compute_rhs() - self.compute_lhs() - - def add_question(self): - equation = self.original_equation = TexMobject( - "\\det(", "M_1", "M_2", ")", "=", - "\\det(", "M_1", ")", - "\\det(", "M_2", ")", - ) - equation.set_color_by_tex_to_color_map({ - "M_1": self.M1_COLOR, - "M_2": self.M2_COLOR, - }) - challenge = TextMobject("Explain in one sentence") - challenge.set_color(YELLOW) - group = VGroup(challenge, equation) - group.arrange(DOWN) - group.to_edge(UP) - - self.add(equation) - self.play(Write(challenge)) - self.wait() - - def add_example(self): - M1 = self.M1 = Matrix([[2, -1], [1, 1]]) - M1.set_color(self.M1_COLOR) - self.M1_copy = M1.copy() - M2 = self.M2 = Matrix([[-1, 4], [1, 1]]) - M2.set_color(self.M2_COLOR) - self.M2_copy = M2.copy() - eq_parts = TexMobject( - "\\det", "\\big(", "\\big)", "=", - "\\det", "\\big(", "\\big)", - "\\det", "\\big(", "\\big)", - ) - for part in eq_parts.get_parts_by_tex("\\big"): - part.scale(2) - part.stretch(1.5, 1) - i1, i2, i3 = [ - eq_parts.index_of_part(part) + 1 - for part in eq_parts.get_parts_by_tex("\\big(") - ] - equation = self.equation_with_numbers = VGroup(*it.chain( - eq_parts[:i1], [M1, M2], - eq_parts[i1:i2], [self.M1_copy], - eq_parts[i2:i3], [self.M2_copy], - eq_parts[i3:], - )) - equation.arrange(RIGHT, buff=SMALL_BUFF) - eq_parts.get_part_by_tex("=").shift(0.2 * SMALL_BUFF * DOWN) - equation.set_width(FRAME_WIDTH - 2 * LARGE_BUFF) - equation.next_to(self.original_equation, DOWN, MED_LARGE_BUFF) - - self.play(LaggedStartMap(FadeIn, equation)) - - def compute_rhs(self): - M1, M2 = self.M1_copy, self.M2_copy - - line1 = VGroup( - TexMobject( - "\\big(", "2", "\\cdot", "2", "-", - "(-1)", "\\cdot", "1", "\\big)" - ), - TexMobject( - "\\big(", "-1", "\\cdot", "1", "-", - "4", "\\cdot", "1", "\\big)" - ), - ) - line1.arrange(RIGHT, buff=SMALL_BUFF) - line1[0].set_color(self.M1_COLOR) - line1[1].set_color(self.M2_COLOR) - indices = [1, 3, 5, 7] - - line2 = TexMobject("(3)", "(-5)") - line2.match_style(line1) - line3 = TexMobject("-15") - arrows = [TexMobject("\\downarrow") for x in range(2)] - lines = VGroup(line1, arrows[0], line2, arrows[1], line3) - lines.arrange(DOWN, buff=MED_SMALL_BUFF) - lines.next_to(self.equation_with_numbers, DOWN, buff=MED_LARGE_BUFF) - lines.to_edge(RIGHT) - - for matrix, det in zip([M1, M2], line1): - numbers = VGroup(*[det[i] for i in indices]) - numbers_iter = iter(numbers) - non_numbers = VGroup(*[m for m in det if m not in numbers]) - matrix_numbers = VGroup(*[ - matrix.mob_matrix[i][j].copy() - for i, j in ((0, 0), (1, 1), (0, 1), (1, 0)) - ]) - self.play( - LaggedStartMap(FadeIn, non_numbers, run_time=1), - LaggedStartMap( - ReplacementTransform, - matrix_numbers, - lambda m: (m, next(numbers_iter)), - path_arc=TAU / 6 - ), - ) - self.play(LaggedStartMap(FadeIn, lines[1:], run_time=3)) - - def compute_lhs(self): - matrix = Matrix([[-3, 7], [0, 5]]) - matrix.set_color(BLUE) - matrix.scale(0.8) - empty_det_tex = TexMobject("\\det", "\\big(", "\\big)") - empty_det_tex[1:].scale(1.5) - empty_det_tex[1:].match_height(matrix, stretch=True) - det_tex = VGroup(empty_det_tex[:2], matrix, *empty_det_tex[2:]) - det_tex.arrange(RIGHT, buff=SMALL_BUFF) - - group = VGroup( - det_tex, - TexMobject("\\downarrow"), - TexMobject("(-3)(5) - (7)(0)").scale(0.8), - TexMobject("\\downarrow"), - TexMobject("-15"), - ) - group.arrange(DOWN, buff=2 * SMALL_BUFF) - # group.set_height(0.4*FRAME_HEIGHT) - group.next_to(self.equation_with_numbers, DOWN) - group.shift(FRAME_WIDTH * LEFT / 4) - - self.play(FadeIn(empty_det_tex)) - self.play(*[ - ReplacementTransform(M.copy(), matrix) - for M in (self.M1, self.M2) - ]) - self.play(LaggedStartMap(FadeIn, group[1:])) - self.wait() - - -class LetsGoInOneSentence(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Here we go, \\\\", "one sentence!" - ) - self.change_all_student_modes("hooray") - self.teacher_says( - "Or three...", "", - target_mode="guilty" - ) - self.change_all_student_modes("sassy") - self.wait(4) - - -class SuccessiveLinearTransformations(LinearTransformationScene): - CONFIG = { - "matrix_2": [[3, -1], [0, 1]], - "matrix_1": [[2, 3], [-1. / 3, 2]], - } - - def construct(self): - self.create_product_and_inverse() - self.scale_area_successively() - self.apply_transformations_successively() - self.scale_area_successively( - "\\det(M_2)", "\\det(M_1)", "\\det(M_1 M_2)", - reset=False - ) - # self.show_det_as_scaling_factor() - - def create_product_and_inverse(self): - self.matrix_product = np.dot(self.matrix_1, self.matrix_2) - self.matrix_product_inverse = np.linalg.inv(self.matrix_product) - - def scale_area_successively(self, tex2="3", tex1="5", tex_prod="15", reset=True): - self.add_unit_square() - t1 = "$%s \\, \\cdot $" % tex1 - t2 = "$%s \\, \\cdot $" % tex2 - t3 = "Area" - areas = VGroup( - TextMobject("", "", t3), - TextMobject("", t2, t3), - TextMobject(t1, t2, t3), - ) - areas.scale(0.8) - areas.move_to(self.square) - area = areas[0] - self.add_moving_mobject(area, areas[1]) - - self.play( - FadeIn(self.square), - Write(area), - Animation(self.basis_vectors) - ) - self.apply_matrix(self.matrix_2) - self.wait() - self.add_moving_mobject(area, areas[2]) - self.apply_matrix(self.matrix_1) - self.wait() - - product = VGroup(area[:2]) - brace = Brace(product, DOWN, buff=SMALL_BUFF) - brace_tex = brace.get_tex(tex_prod, buff=SMALL_BUFF) - brace_tex.scale(0.8, about_edge=UP) - - self.play( - GrowFromCenter(brace), - Write(brace_tex) - ) - self.wait() - if reset: - self.play( - FadeOut(VGroup(self.square, area, brace, brace_tex)), - Animation(self.plane), - Animation(self.basis_vectors) - ) - self.transformable_mobjects.remove(self.square) - self.moving_mobjects = [] - self.reset_plane() - self.wait() - - def apply_transformations_successively(self): - M1, M2, all_space = expression = TexMobject( - "M_1", "M_2", "\\text{(All 2d space)}" - ) - expression.set_color_by_tex_to_color_map({ - "M_1": TEAL, - "M_2": PINK, - }) - expression.shift(FRAME_WIDTH * LEFT / 4) - expression.to_edge(UP) - for part in expression: - part.add_background_rectangle() - part.background_rectangle.stretch(1.05, 0) - M1.save_state() - M1.move_to(ORIGIN) - M1.fade(1) - - # Apply one after the other - self.play( - FocusOn(M2, run_time=1), - FadeIn(VGroup(M2, all_space)) - ) - self.add_foreground_mobjects(M2, all_space) - self.apply_matrix(self.matrix_2) - self.wait() - self.play(M1.restore) - self.add_foreground_mobjects(M1) - self.apply_matrix(self.matrix_1) - self.wait() - - # Show full composition - rp, lp = parens = TexMobject("()") - matrices = VGroup(M1, M2) - matrices.generate_target() - parens.match_height(matrices) - lp.move_to(matrices, RIGHT) - matrices.target.next_to(lp, LEFT, SMALL_BUFF) - rp.next_to(matrices.target, LEFT, SMALL_BUFF) - - self.reset_plane() - self.play( - MoveToTarget(matrices), - *list(map(GrowFromCenter, parens)) - ) - self.apply_matrix(self.matrix_product) - self.wait() - self.reset_plane() - - def show_det_as_scaling_factor(self): - pass - - ### - - def reset_plane(self): - plane_and_bases = VGroup(self.plane, self.basis_vectors) - self.play(FadeOut(plane_and_bases)) - self.apply_matrix(self.matrix_product_inverse, run_time=0) - self.play(FadeIn(plane_and_bases)) diff --git a/from_3b1b/on_hold/eola2/gauss.py b/from_3b1b/on_hold/eola2/gauss.py deleted file mode 100644 index 40ef0a10..00000000 --- a/from_3b1b/on_hold/eola2/gauss.py +++ /dev/null @@ -1,222 +0,0 @@ -from fractions import Fraction - -from manimlib.imports import * -from functools import reduce - - -class FractionMobject(VGroup): - CONFIG = { - "max_height": 1, - } - - def __init__(self, fraction, **kwargs): - VGroup.__init__(self, **kwargs) - numerator = self.numerator = Integer(fraction.numerator) - self.add(numerator) - if fraction.denominator != 1: - denominator = Integer(fraction.denominator) - line = TexMobject("/") - numerator.next_to(line, LEFT, SMALL_BUFF) - denominator.next_to(line, RIGHT, SMALL_BUFF) - self.add(numerator, line, denominator) - self.set_height(min(self.max_height, self.get_height())) - self.value = fraction - - def add_plus_if_needed(self): - if self.value > 0: - plus = TexMobject("+") - plus.next_to(self, LEFT, SMALL_BUFF) - plus.match_color(self) - self.add_to_back(plus) - - -class ShowRowReduction(Scene): - CONFIG = { - "matrices": [ - [ - [2, -1, -1], - [0, 3, -4], - [-3, 2, 1], - ], - [ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1], - ], - # [[1], [2], [3]], - ], - "h_spacing": 2, - "extra_h_spacing": 0.5, - "v_spacing": 1, - "include_separation_lines": True, - "changing_row_color": YELLOW, - "reference_row_color": BLUE, - } - - def construct(self): - self.initialize_terms() - - self.apply_row_rescaling(0, Fraction(1, 2)) - self.add_row_multiple_to_row(2, 0, 3) - self.apply_row_rescaling(1, Fraction(1, 3)) - self.add_row_multiple_to_row(2, 1, Fraction(-1, 2)) - self.apply_row_rescaling(2, Fraction(6)) - self.add_row_multiple_to_row(0, 1, Fraction(1, 2)) - self.add_row_multiple_to_row(0, 2, Fraction(7, 6)) - self.add_row_multiple_to_row(1, 2, Fraction(4, 3)) - self.wait() - - def initialize_terms(self): - full_matrix = reduce( - lambda m, v: np.append(m, v, axis=1), - self.matrices - ) - mobject_matrix = np.vectorize(FractionMobject)(full_matrix) - rows = self.rows = VGroup(*it.starmap(VGroup, mobject_matrix)) - for i, row in enumerate(rows): - for j, term in enumerate(row): - term.move_to( - i * self.v_spacing * DOWN + - j * self.h_spacing * RIGHT - ) - - # Visually seaprate distinct parts - separation_lines = self.separation_lines = VGroup() - lengths = [len(m[0]) for m in self.matrices] - for partial_sum in np.cumsum(lengths)[:-1]: - VGroup(*mobject_matrix[:, partial_sum:].flatten()).shift( - self.extra_h_spacing * RIGHT - ) - c1 = VGroup(*mobject_matrix[:, partial_sum - 1]) - c2 = VGroup(*mobject_matrix[:, partial_sum]) - line = DashedLine(c1.get_top(), c1.get_bottom()) - line.move_to(VGroup(c1, c2)) - separation_lines.add(line) - - if self.include_separation_lines: - group = VGroup(rows, separation_lines) - else: - group = rows - group.center().to_edge(DOWN, buff=2) - self.add(group) - - def add_variables(self): - # If it is meant to represent a system of equations - pass - - def apply_row_rescaling(self, row_index, scale_factor): - row = self.rows[row_index] - new_row = VGroup() - for element in row: - target = FractionMobject(element.value * scale_factor) - target.move_to(element) - new_row.add(target) - new_row.set_color(self.changing_row_color) - - label = VGroup( - TexMobject("r_%d" % (row_index + 1)), - TexMobject("\\rightarrow"), - TexMobject("("), - FractionMobject(scale_factor), - TexMobject(")"), - TexMobject("r_%d" % (row_index + 1)), - ) - label.arrange(RIGHT, buff=SMALL_BUFF) - label.to_edge(UP) - VGroup(label[0], label[-1]).set_color(self.changing_row_color) - - scalar_mob = FractionMobject(scale_factor) - scalar_mob.add_to_back( - TexMobject("\\times").next_to(scalar_mob, LEFT, SMALL_BUFF) - ) - scalar_mob.scale(0.5) - scalar_mob.next_to(row[0], DR, SMALL_BUFF) - - # Do do, fancier illustrations here - self.play( - FadeIn(label), - row.set_color, self.changing_row_color, - ) - self.play(FadeIn(scalar_mob)) - for elem, new_elem in zip(row, new_row): - self.play(scalar_mob.next_to, elem, DR, SMALL_BUFF) - self.play(ReplacementTransform(elem, new_elem, path_arc=30 * DEGREES)) - self.play(FadeOut(scalar_mob)) - self.play(new_row.set_color, WHITE) - self.play(FadeOut(label)) - self.rows.submobjects[row_index] = new_row - - def add_row_multiple_to_row(self, row1_index, row2_index, scale_factor): - row1 = self.rows[row1_index] - row2 = self.rows[row2_index] - new_row1 = VGroup() - scaled_row2 = VGroup() - for elem1, elem2 in zip(row1, row2): - target = FractionMobject(elem1.value + scale_factor * elem2.value) - target.move_to(elem1) - new_row1.add(target) - - scaled_term = FractionMobject(scale_factor * elem2.value) - scaled_term.move_to(elem2) - scaled_row2.add(scaled_term) - new_row1.set_color(self.changing_row_color) - scaled_row2.set_color(self.reference_row_color) - - for elem1, elem2 in zip(row1, scaled_row2): - elem2.add_plus_if_needed() - elem2.scale(0.5) - elem2.next_to(elem1, UL, buff=SMALL_BUFF) - - label = VGroup( - TexMobject("r_%d" % (row1_index + 1)), - TexMobject("\\rightarrow"), - TexMobject("r_%d" % (row1_index + 1)), - TexMobject("+"), - TexMobject("("), - FractionMobject(scale_factor), - TexMobject(")"), - TexMobject("r_%d" % (row2_index + 1)), - ) - label.arrange(RIGHT, buff=SMALL_BUFF) - label.to_edge(UP) - VGroup(label[0], label[2]).set_color(self.changing_row_color) - label[-1].set_color(self.reference_row_color) - - self.play( - FadeIn(label), - row1.set_color, self.changing_row_color, - row2.set_color, self.reference_row_color, - ) - row1.target.next_to(self.rows, UP, buff=2) - row1.target.align_to(row1, LEFT) - - row2.target.next_to(row1.target, DOWN, buff=MED_LARGE_BUFF) - lp, rp = row2_parens = TexMobject("()") - row2_parens.set_height(row2.get_height() + 2 * SMALL_BUFF) - lp.next_to(row2, LEFT, SMALL_BUFF) - rp.next_to(row2, RIGHT, SMALL_BUFF) - scalar = FractionMobject(scale_factor) - scalar.next_to(lp, LEFT, SMALL_BUFF) - scalar.add_plus_if_needed() - - self.play( - FadeIn(row2_parens), - Write(scalar), - ) - self.play(ReplacementTransform(row2.copy(), scaled_row2)) - self.wait() - for elem, new_elem, s_elem in zip(row1, new_row1, scaled_row2): - self.play( - FadeOut(elem), - FadeIn(new_elem), - Transform(s_elem, new_elem.copy().fade(1), remover=True) - ) - self.wait() - self.play( - FadeOut(label), - FadeOut(row2_parens), - FadeOut(scalar), - new_row1.set_color, WHITE, - row2.set_color, WHITE, - ) - self.rows.submobjects[row1_index] = new_row1 diff --git a/from_3b1b/on_hold/eop/bayes.py b/from_3b1b/on_hold/eop/bayes.py deleted file mode 100644 index 95df1be7..00000000 --- a/from_3b1b/on_hold/eop/bayes.py +++ /dev/null @@ -1,2254 +0,0 @@ -from manimlib.imports import * - -#revert_to_original_skipping_status - -######### - -class BayesOpeningQuote(OpeningQuote): - CONFIG = { - "quote" : [ - "Inside every non-Bayesian there \\\\ is a Bayesian struggling to get out." - ], - "author" : "Dennis V. Lindley", - } - -class IntroducePokerHand(PiCreatureScene, SampleSpaceScene): - CONFIG = { - "community_cards_center" : 1.5*DOWN, - "community_card_values" : ["10S", "QH", "AH", "2C", "5H"], - "your_hand_values" : ["JS", "KC"], - } - def construct(self): - self.add_cards() - self.indicate_straight() - self.show_flush_potential() - self.compute_flush_probability() - self.show_flush_sample_space() - self.talk_through_sample_space() - self.place_high_bet() - self.change_belief() - self.move_community_cards_out_of_the_way() - self.name_bayes_rule() - - def add_cards(self): - you, her = self.you, self.her - community_cards = VGroup(*list(map( - PlayingCard, self.community_card_values - ))) - community_cards.arrange(RIGHT) - community_cards.move_to(self.community_cards_center) - deck = VGroup(*[ - PlayingCard(turned_over = True) - for x in range(5) - ]) - for i, card in enumerate(deck): - card.shift(i*(0.03*RIGHT + 0.015*DOWN)) - deck.next_to(community_cards, LEFT) - - you.hand = self.get_hand(you, self.your_hand_values) - her.hand = self.get_hand(her, None) - hand_cards = VGroup(*it.chain(*list(zip(you.hand, her.hand)))) - - self.add(deck) - for group in hand_cards, community_cards: - for card in group: - card.generate_target() - card.scale(0.01) - card.move_to(deck[-1], UP+RIGHT) - self.play(LaggedStartMap(MoveToTarget, group, lag_ratio = 0.8)) - self.wait() - self.wait() - - self.community_cards = community_cards - self.deck = deck - - def indicate_straight(self): - you = self.you - community_cards = self.community_cards - you.hand.save_state() - you.hand.generate_target() - for card in you.hand.target: - card.set_height(community_cards.get_height()) - - selected_community_cards = VGroup(*[card for card in community_cards if card.numerical_value >= 10]) - selected_community_cards.submobjects.sort( - key=lambda c: c.numerical_value - ) - - selected_community_cards.save_state() - for card in selected_community_cards: - card.generate_target() - - straight_cards = VGroup(*it.chain( - you.hand.target, - [c.target for c in selected_community_cards] - )) - straight_cards.submobjects.sort( - key=lambda c: c.numerical_value - ) - straight_cards.arrange(RIGHT, buff = SMALL_BUFF) - straight_cards.next_to(community_cards, UP, aligned_edge = LEFT) - you.hand.target.shift(MED_SMALL_BUFF*UP) - - self.play(LaggedStartMap( - MoveToTarget, - selected_community_cards, - run_time = 1.5 - )) - self.play(MoveToTarget(you.hand)) - self.play(LaggedStartMap( - ApplyMethod, - straight_cards, - lambda m : (m.set_color, YELLOW), - rate_func = there_and_back, - run_time = 1.5, - lag_ratio = 0.5, - remover = True, - )) - self.play(you.change, "hooray", straight_cards) - self.wait(2) - self.play( - selected_community_cards.restore, - you.hand.restore, - you.change_mode, "happy" - ) - self.wait() - - def show_flush_potential(self): - you, her = self.you, self.her - heart_cards = VGroup(*[c for c in self.community_cards if c.suit == "hearts"]) - heart_cards.save_state() - - her.hand.save_state() - her.hand.generate_target() - her.hand.target.arrange(RIGHT) - her.hand.target.next_to(heart_cards, UP) - her.hand.target.to_edge(UP) - - her.glasses.save_state() - her.glasses.move_to(her.hand.target) - her.glasses.set_fill(opacity = 0) - - heart_qs = VGroup() - hearts = VGroup() - q_marks = VGroup() - for target in her.hand.target: - heart = SuitSymbol("hearts") - q_mark = TexMobject("?") - heart_q = VGroup(heart, q_mark) - for mob in heart_q: - mob.set_height(0.5) - heart_q.arrange(RIGHT, buff = SMALL_BUFF) - heart_q.move_to(target) - heart_qs.add(heart, q_mark) - hearts.add(heart) - q_marks.add(q_mark) - - self.play(heart_cards.shift, heart_cards.get_height()*UP) - self.play(you.change_mode, "hesitant") - self.play(MoveToTarget(her.hand)) - self.play(LaggedStartMap(DrawBorderThenFill, heart_qs)) - self.play( - her.change, "happy", - her.glasses.restore, - ) - self.pi_creatures.remove(her) - new_suit_pairs = [ - ("clubs", "diamonds"), - ("diamonds", "spades"), - ("spades", "clubs"), - ("hearts", "hearts"), - ] - for new_suit_pair in new_suit_pairs: - new_symbols = VGroup(*list(map(SuitSymbol, new_suit_pair))) - for new_symbol, heart in zip(new_symbols, hearts): - new_symbol.replace(heart, dim_to_match = 1) - self.play(Transform( - hearts, new_symbols, - lag_ratio = 0.5 - )) - self.wait() - self.play(FadeOut(heart_qs)) - self.play( - heart_cards.restore, - her.hand.restore, - you.change_mode, "pondering", - ) - - self.q_marks = q_marks - - def compute_flush_probability(self): - you, her = self.you, self.her - equation = TexMobject( - "{ {10 \\choose 2}", "\\over", "{45 \\choose 2} }", - "=", "{45 \\over 990}", "\\approx", "4.5\\%" - ) - equation.next_to(self.community_cards, UP, buff = LARGE_BUFF) - percentage = equation.get_part_by_tex("4.5") - - ten = VGroup(*equation[0][1:3]) - num_hearts = TextMobject("\\# Remaining hearts") - num_hearts.scale(0.75) - num_hearts.next_to( - ten, UP, aligned_edge = LEFT - ) - num_hearts.to_edge(UP) - num_hearts.set_color(RED) - num_hearts_arrow = Arrow( - num_hearts.get_bottom(), ten.get_right(), - color = RED, buff = SMALL_BUFF - ) - - fourty_five = VGroup(*equation[2][1:3]) - num_cards = TextMobject("\\# Remaining cards") - num_cards.scale(0.75) - num_cards.next_to(fourty_five, LEFT) - num_cards.to_edge(LEFT) - num_cards.set_color(BLUE) - num_cards_arrow = Arrow( - num_cards, fourty_five, - color = BLUE, buff = SMALL_BUFF - ) - - self.play(LaggedStartMap(FadeIn, equation)) - self.wait(2) - self.play( - FadeIn(num_hearts), - ShowCreation(num_hearts_arrow), - ten.set_color, RED, - ) - self.play( - FadeIn(num_cards), - ShowCreation(num_cards_arrow), - fourty_five.set_color, BLUE - ) - self.wait(3) - equation.remove(percentage) - self.play(*list(map(FadeOut, [ - equation, - num_hearts, num_hearts_arrow, - num_cards, num_cards_arrow, - ]))) - - - self.percentage = percentage - - def show_flush_sample_space(self): - you, her = self.you, self.her - percentage = self.percentage - - sample_space = self.get_sample_space() - sample_space.add_title("Your belief") - sample_space.move_to(VGroup(you.hand, her.hand)) - sample_space.to_edge(UP, buff = MED_SMALL_BUFF) - p = 1./22 - sample_space.divide_horizontally( - p, colors = [SuitSymbol.CONFIG["red"], BLUE_E] - ) - braces, labels = sample_space.get_side_braces_and_labels([ - percentage.get_tex_string(), "95.5\\%" - ]) - top_label, bottom_label = labels - - self.play( - FadeIn(sample_space), - ReplacementTransform(percentage, top_label) - ) - self.play(*list(map(GrowFromCenter, [ - brace for brace in braces - ]))) - self.wait(2) - self.play(Write(bottom_label)) - self.wait(2) - - self.sample_space = sample_space - - def talk_through_sample_space(self): - her = self.her - sample_space = self.sample_space - top_part, bottom_part = self.sample_space.horizontal_parts - - flush_hands, non_flush_hands = hand_lists = [ - [self.get_hand(her, keys) for keys in key_list] - for key_list in [ - [("3H", "8H"), ("4H", "5H"), ("JH", "KH")], - [("AC", "6D"), ("3D", "6S"), ("JH", "4C")], - ] - ] - for hand_list, part in zip(hand_lists, [top_part, bottom_part]): - self.play(Indicate(part, scale_factor = 1)) - for hand in hand_list: - hand.save_state() - hand.scale(0.01) - hand.move_to(part.get_right()) - self.play(hand.restore) - self.wait() - self.wait() - self.play(*list(map(FadeOut, it.chain(*hand_lists)))) - - def place_high_bet(self): - you, her = self.you, self.her - pre_money = VGroup(*[ - VGroup(*[ - TexMobject("\\$") - for x in range(10) - ]).arrange(RIGHT, buff = SMALL_BUFF) - for y in range(4) - ]).arrange(UP, buff = SMALL_BUFF) - money = VGroup(*it.chain(*pre_money)) - money.set_color(GREEN) - money.scale(0.8) - money.next_to(her.hand, DOWN) - for dollar in money: - dollar.save_state() - dollar.scale(0.01) - dollar.move_to(her.get_boundary_point(RIGHT)) - dollar.set_fill(opacity = 0) - - self.play(LaggedStartMap( - ApplyMethod, - money, - lambda m : (m.restore,), - run_time = 5, - )) - self.play(you.change_mode, "confused") - self.wait() - - self.money = money - - def change_belief(self): - numbers = self.sample_space.horizontal_parts.labels - rect = Rectangle(stroke_width = 0) - rect.set_fill(BLACK, 1) - rect.stretch_to_fit_width(numbers.get_width()) - rect.stretch_to_fit_height(self.sample_space.get_height()) - rect.move_to(numbers, UP) - - self.play(FadeIn(rect)) - anims = self.get_horizontal_division_change_animations(0.2) - anims.append(Animation(rect)) - self.play( - *anims, - run_time = 3, - rate_func = there_and_back - ) - self.play(FadeOut(rect)) - - def move_community_cards_out_of_the_way(self): - cards = self.community_cards - cards.generate_target() - cards.target.arrange( - RIGHT, buff = -cards[0].get_width() + MED_SMALL_BUFF, - ) - cards.target.move_to(self.deck) - cards.target.to_edge(LEFT) - - self.sample_space.add_braces_and_labels() - - self.play( - self.deck.scale, 0.7, - self.deck.next_to, cards.target, UP, - self.deck.to_edge, LEFT, - self.sample_space.shift, 3*DOWN, - MoveToTarget(cards) - ) - - def name_bayes_rule(self): - title = TextMobject("Bayes' rule") - title.set_color(BLUE) - title.to_edge(UP) - subtitle = TextMobject("Update ", "prior ", "beliefs") - subtitle.scale(0.8) - subtitle.next_to(title, DOWN) - prior_word = subtitle.get_part_by_tex("prior") - numbers = self.sample_space.horizontal_parts.labels - rect = SurroundingRectangle(numbers, color = GREEN) - arrow = Arrow(prior_word.get_bottom(), rect.get_top()) - arrow.set_color(GREEN) - - words = TextMobject( - "Maybe she really \\\\ does have a flush $\\dots$", - alignment = "" - ) - words.scale(0.7) - words.next_to(self.money, DOWN, aligned_edge = LEFT) - - self.play( - Write(title, run_time = 2), - self.you.change_mode, "pondering" - ) - self.wait() - self.play(FadeIn(subtitle)) - self.play(prior_word.set_color, GREEN) - self.play( - ShowCreation(rect), - ShowCreation(arrow) - ) - self.wait(3) - self.play(Write(words)) - self.wait(3) - - - ###### - - def create_pi_creatures(self): - shift_val = 3 - you = PiCreature(color = BLUE_D) - her = PiCreature(color = BLUE_B).flip() - for pi in you, her: - pi.scale(0.5) - you.to_corner(UP+LEFT) - her.to_corner(UP+RIGHT) - you.make_eye_contact(her) - - glasses = SunGlasses(her) - her.glasses = glasses - - self.you = you - self.her = her - return VGroup(you, her) - - def get_hand(self, pi_creature, keys = None): - if keys is not None: - hand = VGroup(*list(map(PlayingCard, keys))) - else: - hand = VGroup(*[ - PlayingCard(turned_over = True) - for x in range(2) - ]) - hand.scale(0.7) - card1, card2 = hand - vect = np.sign(pi_creature.get_center()[0])*LEFT - card2.move_to(card1) - card2.shift(MED_SMALL_BUFF*RIGHT + SMALL_BUFF*DOWN) - hand.next_to( - pi_creature, vect, - buff = MED_LARGE_BUFF, - aligned_edge = UP - ) - return hand - -class HowDoesPokerWork(TeacherStudentsScene): - def construct(self): - self.student_says( - "Wait, how does \\\\ poker work again?", - target_mode = "confused", - run_time = 1 - ) - self.change_student_modes(*["confused"]*3) - self.wait(2) - -class YourGutKnowsBayesRule(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Your gut knows \\\\ Bayes' rule.", - run_time = 1 - ) - self.change_student_modes("confused", "gracious", "guilty") - self.wait(3) - -class UpdatePokerPrior(SampleSpaceScene): - CONFIG = { - "double_heart_template" : "HH", - "cash_string" : "\\$\\$\\$", - } - def construct(self): - self.add_sample_space() - self.add_top_conditionals() - self.react_to_top_conditionals() - self.add_bottom_conditionals() - self.ask_where_conditionals_come_from() - self.vary_conditionals() - self.show_restricted_space() - self.write_P_flush_given_bet() - self.reshape_rectangles() - self.compare_prior_to_posterior() - self.preview_tweaks() - self.tweak_non_flush_case() - self.tweak_flush_case() - self.tweak_prior() - self.compute_posterior() - - def add_sample_space(self): - p = 1./22 - sample_space = SampleSpace(fill_opacity = 0) - sample_space.shift(LEFT) - sample_space.divide_horizontally(p, colors = [ - SuitSymbol.CONFIG["red"], BLUE_E - ]) - labels = self.get_prior_labels(p) - braces_and_labels = sample_space.get_side_braces_and_labels(labels) - - self.play( - LaggedStartMap(FadeIn, sample_space), - Write(braces_and_labels) - ) - self.wait() - - sample_space.add(braces_and_labels) - self.sample_space = sample_space - - def add_top_conditionals(self): - top_part = self.sample_space.horizontal_parts[0] - color = average_color(YELLOW, GREEN, GREEN) - p = 0.97 - top_part.divide_vertically(p, colors = [color, BLUE]) - label = self.get_conditional_label(p, True) - brace, _ignore = top_part.get_top_braces_and_labels([label]) - - explanation = TextMobject( - "Probability of", "high bet", "given", "flush" - ) - explanation.set_color_by_tex("high bet", GREEN) - explanation.set_color_by_tex("flush", RED) - explanation.scale(0.6) - explanation.next_to(label, UP) - - self.play( - FadeIn(top_part.vertical_parts), - FadeIn(label), - GrowFromCenter(brace), - ) - self.play(Write(explanation, run_time = 3)) - self.wait(2) - - self.sample_space.add(brace, label) - - self.top_explanation = explanation - self.top_conditional_rhs = label[-1] - - def react_to_top_conditionals(self): - her = PiCreature(color = BLUE_B).flip() - her.next_to(self.sample_space, RIGHT) - her.to_edge(RIGHT) - glasses = SunGlasses(her) - glasses.save_state() - glasses.shift(UP) - glasses.set_fill(opacity = 0) - her.glasses = glasses - - self.play(FadeIn(her)) - self.play(glasses.restore) - self.play( - her.change_mode, "happy", - Animation(glasses) - ) - self.wait(2) - - self.her = her - - def add_bottom_conditionals(self): - her = self.her - bottom_part = self.sample_space.horizontal_parts[1] - p = 0.3 - bottom_part.divide_vertically(p, colors = [GREEN_E, BLUE_E]) - label = self.get_conditional_label(p, False) - brace, _ignore = bottom_part.get_bottom_braces_and_labels([label]) - - explanation = TextMobject( - "Probability of", "high bet", "given", "no flush" - ) - explanation.set_color_by_tex("high bet", GREEN) - explanation.set_color_by_tex("no flush", RED) - explanation.scale(0.6) - explanation.next_to(label, DOWN) - - self.play(DrawBorderThenFill(bottom_part.vertical_parts)) - self.play( - GrowFromCenter(brace), - FadeIn(label) - ) - self.play( - her.change_mode, "shruggie", - MaintainPositionRelativeTo(her.glasses, her.eyes) - ) - self.wait() - self.play(*[ - ReplacementTransform( - VGroup(*label[i1:i2]).copy(), - VGroup(explanation[j]), - run_time = 2, - rate_func = squish_rate_func(smooth, a, a+0.5) - ) - for a, (i1, i2, j) in zip(np.linspace(0, 0.5, 4), [ - (0, 1, 0), - (1, 2, 1), - (2, 3, 2), - (3, 6, 3), - ]) - ]) - self.wait() - self.play(Write(VGroup(*label[-2:]))) - self.wait(2) - self.play(*list(map(FadeOut, [her, her.glasses]))) - - self.sample_space.add(brace, label) - self.bottom_explanation = explanation - self.bottom_conditional_rhs = label[-1] - - def ask_where_conditionals_come_from(self): - randy = Randolph().flip() - randy.scale(0.75) - randy.to_edge(RIGHT) - randy.shift(2*DOWN) - words = TextMobject("Where do these \\\\", "numbers", "come from?") - numbers_word = words.get_part_by_tex("numbers") - numbers_word.set_color(YELLOW) - words.scale(0.7) - bubble = ThoughtBubble(height = 3, width = 4) - bubble.pin_to(randy) - bubble.shift(MED_LARGE_BUFF*RIGHT) - bubble.add_content(words) - - numbers = VGroup( - self.top_conditional_rhs, - self.bottom_conditional_rhs - ) - numbers.save_state() - arrows = VGroup(*[ - Arrow( - numbers_word.get_left(), - num.get_right(), - buff = 2*SMALL_BUFF - ) - for num in numbers - ]) - - questions = VGroup(*list(map(TextMobject, [ - "Does she bluff?", - "How much does she have?", - "Does she take risks?", - "What's her model of me?", - "\\vdots" - ]))) - questions.arrange(DOWN, aligned_edge = LEFT) - questions[-1].next_to(questions[-2], DOWN) - questions.scale(0.7) - questions.next_to(randy, UP) - questions.shift_onto_screen() - - self.play( - randy.change_mode, "confused", - ShowCreation(bubble), - Write(words, run_time = 2) - ) - self.play(*list(map(ShowCreation, arrows))) - self.play(numbers.set_color, YELLOW) - self.play(Blink(randy)) - self.play(randy.change_mode, "maybe") - self.play(*list(map(FadeOut, [ - bubble, words, arrows - ]))) - for question in questions: - self.play( - FadeIn(question), - randy.look_at, question - ) - self.wait() - self.play(Blink(randy)) - self.wait() - self.play( - randy.change_mode, "pondering", - FadeOut(questions) - ) - - self.randy = randy - - def vary_conditionals(self): - randy = self.randy - rects = VGroup(*[ - SurroundingRectangle( - VGroup(explanation), - buff = SMALL_BUFF, - ) - for explanation, rhs in zip( - [self.top_explanation, self.bottom_explanation], - [self.top_conditional_rhs, self.bottom_conditional_rhs], - ) - ]) - - new_conditionals = [ - (0.91, 0.4), - (0.83, 0.1), - (0.99, 0.2), - (0.97, 0.3), - ] - - self.play(*list(map(ShowCreation, rects))) - self.play(FadeOut(rects)) - for i, value in enumerate(it.chain(*new_conditionals)): - self.play( - randy.look_at, rects[i%2], - *self.get_conditional_change_anims(i%2, value) - ) - if i%2 == 1: - self.wait() - self.play(FadeOut(randy)) - - def show_restricted_space(self): - high_bet_space, low_bet_space = [ - VGroup(*[ - self.sample_space.horizontal_parts[i].vertical_parts[j] - for i in range(2) - ]) - for j in range(2) - ] - words = TexMobject("P(", self.cash_string, ")") - words.set_color_by_tex(self.cash_string, GREEN) - words.next_to(self.sample_space, RIGHT) - low_bet_space.generate_target() - for submob in low_bet_space.target: - submob.set_color(average_color( - submob.get_color(), *[BLACK]*4 - )) - arrows = VGroup(*[ - Arrow( - words.get_left(), - submob.get_edge_center(vect), - color = submob.get_color() - ) - for submob, vect in zip(high_bet_space, [DOWN, RIGHT]) - ]) - - self.play(MoveToTarget(low_bet_space)) - self.play( - Write(words), - *list(map(ShowCreation, arrows)) - ) - self.wait() - for rect in high_bet_space: - self.play(Indicate(rect, scale_factor = 1)) - self.play(*list(map(FadeOut, [words, arrows]))) - - self.high_bet_space = high_bet_space - - def write_P_flush_given_bet(self): - posterior_tex = TexMobject( - "P(", self.double_heart_template, - "|", self.cash_string, ")" - ) - posterior_tex.scale(0.7) - posterior_tex.set_color_by_tex(self.cash_string, GREEN) - self.insert_double_heart(posterior_tex) - rects = self.high_bet_space.copy() - rects = [rects[0].copy()] + list(rects) - for rect in rects: - rect.generate_target() - numerator = rects[0].target - plus = TexMobject("+") - denominator = VGroup(rects[1].target, plus, rects[2].target) - denominator.arrange(RIGHT, buff = SMALL_BUFF) - frac_line = TexMobject("\\over") - frac_line.stretch_to_fit_width(denominator.get_width()) - fraction = VGroup(numerator, frac_line, denominator) - fraction.arrange(DOWN) - - arrow = TexMobject("\\downarrow") - group = VGroup(posterior_tex, arrow, fraction) - group.arrange(DOWN) - group.to_corner(UP+RIGHT) - - self.play(LaggedStartMap(FadeIn, posterior_tex)) - self.play(Write(arrow)) - self.play(MoveToTarget(rects[0])) - self.wait() - self.play(*it.chain( - list(map(Write, [frac_line, plus])), - list(map(MoveToTarget, rects[1:])) - )) - self.wait(3) - self.play(*list(map(FadeOut, [arrow, fraction] + rects))) - - self.posterior_tex = posterior_tex - - def reshape_rectangles(self): - post_rects = self.get_posterior_rectangles() - prior_rects = self.get_prior_rectangles() - braces, labels = self.get_posterior_rectangle_braces_and_labels( - post_rects, [self.posterior_tex.copy()] - ) - height_rect = SurroundingRectangle(braces) - - self.play( - ReplacementTransform( - prior_rects.copy(), post_rects, - run_time = 2, - ), - ) - self.wait(2) - self.play(ReplacementTransform(self.posterior_tex, labels[0])) - self.posterior_tex = labels[0] - self.play(GrowFromCenter(braces)) - self.wait() - self.play(ShowCreation(height_rect)) - self.play(FadeOut(height_rect)) - self.wait() - - self.post_rects = post_rects - - def compare_prior_to_posterior(self): - prior_tex = self.sample_space.horizontal_parts.labels[0] - post_tex = self.posterior_tex - prior_rect, post_rect = [ - SurroundingRectangle(tex, stroke_width = 2) - for tex in [prior_tex, post_tex] - ] - - post_words = TextMobject("Posterior", "probability") - post_words.scale(0.8) - post_words.to_corner(UP+RIGHT) - post_arrow = Arrow( - post_words[0].get_bottom(), post_tex.get_top(), - color = WHITE - ) - - self.play(ShowCreation(prior_rect)) - self.wait() - self.play(ReplacementTransform(prior_rect, post_rect)) - self.wait() - self.play(FadeOut(post_rect)) - self.play(Indicate(post_tex.get_part_by_tex(self.cash_string))) - self.wait() - self.play( - Write(post_words), - ShowCreation(post_arrow) - ) - self.wait() - self.play(post_words[1].fade, 0.8) - self.wait(2) - self.play(*list(map(FadeOut, [post_words, post_arrow]))) - - def preview_tweaks(self): - post_rects = self.post_rects - new_value_lists = [ - (0.85, 0.1, 0.11), - (0.97, 0.3, 1./22), - ] - for new_values in new_value_lists: - for i, value in zip(list(range(2)), new_values): - self.play(*self.get_conditional_change_anims( - i, value, post_rects - )) - self.play(*self.get_prior_change_anims( - new_values[-1], post_rects - )) - self.wait(2) - - def tweak_non_flush_case(self): - her = self.her - her.scale_in_place(0.7) - her.change_mode("plain") - her.shift(DOWN) - her.glasses = SunGlasses(her) - post_rects = self.post_rects - posterior = VGroup(post_rects.braces, post_rects.labels) - prior_rects = self.get_prior_rectangles() - risk_averse_words = TextMobject( - "Suppose risk \\\\ averse \\dots" - ) - risk_averse_words.scale(0.7) - risk_averse_words.next_to(her, DOWN) - risk_averse_words.shift_onto_screen() - - arrows = VGroup(*[ - Arrow(ORIGIN, LEFT, tip_length = SMALL_BUFF) - for x in range(3) - ]) - arrows.arrange(DOWN) - arrows.next_to(prior_rects[1], RIGHT, SMALL_BUFF) - - self.wait(2) - self.play(*list(map(FadeIn, [her, her.glasses]))) - self.play(LaggedStartMap(FadeIn, risk_averse_words)) - self.play(her.change_mode, "sad", Animation(her.glasses)) - self.wait() - self.play(ShowCreation(arrows)) - self.play( - *it.chain( - self.get_conditional_change_anims(1, 0.1, post_rects), - [Animation(arrows)] - ), - run_time = 3 - ) - self.play(FadeOut(arrows)) - self.wait(2) - post_surrounding_rect = SurroundingRectangle(posterior) - self.play(ShowCreation(post_surrounding_rect)) - self.play(FadeOut(post_surrounding_rect)) - self.wait() - self.play( - FadeOut(risk_averse_words), - *self.get_conditional_change_anims(1, 0.3, post_rects), - run_time = 2 - ) - - def tweak_flush_case(self): - her = self.her - post_rects = self.post_rects - - self.play( - her.change_mode, "erm", Animation(her.glasses) - ) - self.play( - *self.get_conditional_change_anims(0, 0.47, post_rects), - run_time = 3 - ) - self.wait(3) - self.play(*self.get_conditional_change_anims( - 0, 0.97, post_rects - )) - self.wait() - - def tweak_prior(self): - her = self.her - post_rects = self.post_rects - - self.play( - her.change_mode, "happy", Animation(her.glasses) - ) - self.play( - *self.get_prior_change_anims(0.3, post_rects), - run_time = 2 - ) - self.wait(3) - self.play( - *self.get_prior_change_anims(1./22, post_rects), - run_time = 2 - ) - self.play(*list(map(FadeOut, [her, her.glasses]))) - - def compute_posterior(self): - prior_rects = self.get_prior_rectangles() - post_tex = self.posterior_tex - prior_rhs_group = self.get_prior_rhs_group() - - fraction = TexMobject( - "{(0.045)", "(0.97)", "\\over", - "(0.995)", "(0.3)", "+", "(0.045)", "(0.97)}" - ) - products = [ - VGroup(*[ - fraction.get_parts_by_tex(tex)[i] - for tex in tex_list - ]) - for i, tex_list in [ - (0, ["0.045", "0.97"]), - (0, ["0.995", "0.3"]), - (1, ["0.045", "0.97"]), - ] - ] - for i in 0, 2: - products[i].set_color(prior_rects[0].get_color()) - products[1].set_color(prior_rects[1].get_color()) - fraction.scale(0.65) - fraction.to_corner(UP+RIGHT, buff = MED_SMALL_BUFF) - arrow_kwargs = { - "color" : WHITE, - "tip_length" : 0.15, - } - rhs = TexMobject("\\approx", "0.13") - rhs.scale(0.8) - rhs.next_to(post_tex, RIGHT) - to_rhs_arrow = Arrow( - fraction.get_bottom(), rhs.get_top(), - **arrow_kwargs - ) - - pre_top_rect_products = VGroup( - prior_rhs_group[0], self.top_conditional_rhs - ) - pre_bottom_rect_products = VGroup( - prior_rhs_group[1], self.bottom_conditional_rhs - ) - - self.play(Indicate(prior_rects[0], scale_factor = 1)) - self.play(*[ - ReplacementTransform( - mob.copy(), term, - run_time = 2, - ) - for mob, term in zip( - pre_top_rect_products, products[0] - ) - ]) - self.play(Write(fraction.get_part_by_tex("over"))) - for pair in zip(pre_top_rect_products, products[0]): - self.play(*list(map(Indicate, pair))) - self.wait() - self.wait() - self.play(Indicate(prior_rects[1], scale_factor = 1)) - self.play(*[ - ReplacementTransform( - mob.copy(), term, - run_time = 2, - ) - for mob, term in zip( - pre_bottom_rect_products, products[1] - ) - ]) - self.wait() - for pair in zip(pre_bottom_rect_products, products[1]): - self.play(*list(map(Indicate, pair))) - self.wait() - self.play( - Write(fraction.get_part_by_tex("+")), - ReplacementTransform(products[0].copy(), products[2]) - ) - self.wait() - self.play(ShowCreation(to_rhs_arrow)) - self.play(Write(rhs)) - self.wait(3) - - - ###### - - def get_prior_labels(self, value): - p_str = "%0.3f"%value - q_str = "%0.3f"%(1-value) - labels = [ - TexMobject( - "P(", s, self.double_heart_template, ")", - "= ", num - ) - for s, num in (("", p_str), ("\\text{not }", q_str)) - ] - for label in labels: - label.scale(0.7) - self.insert_double_heart(label) - - return labels - - def get_prior_rhs_group(self): - labels = self.sample_space.horizontal_parts.labels - return VGroup(*[label[-1] for label in labels]) - - def get_conditional_label(self, value, given_flush = True): - label = TexMobject( - "P(", self.cash_string, "|", - "" if given_flush else "\\text{not }", - self.double_heart_template, ")", - "=", str(value) - ) - self.insert_double_heart(label) - label.set_color_by_tex(self.cash_string, GREEN) - label.scale(0.7) - return label - - def insert_double_heart(self, tex_mob): - double_heart = SuitSymbol("hearts") - double_heart.add(SuitSymbol("hearts")) - double_heart.arrange(RIGHT, buff = SMALL_BUFF) - double_heart.get_tex_string = lambda : self.double_heart_template - template = tex_mob.get_part_by_tex(self.double_heart_template) - double_heart.replace(template) - tex_mob.submobjects[tex_mob.index_of_part(template)] = double_heart - return tex_mob - - def get_prior_change_anims(self, value, post_rects = None): - space = self.sample_space - parts = space.horizontal_parts - anims = self.get_horizontal_division_change_animations( - value, new_label_kwargs = { - "labels" : self.get_prior_labels(value) - } - ) - if post_rects is not None: - anims += self.get_posterior_rectangle_change_anims(post_rects) - return anims - - def get_conditional_change_anims( - self, sub_sample_space_index, value, - post_rects = None - ): - given_flush = (sub_sample_space_index == 0) - label = self.get_conditional_label(value, given_flush) - return SampleSpaceScene.get_conditional_change_anims( - self, sub_sample_space_index, value, post_rects, - new_label_kwargs = {"labels" : [label]}, - ) - -class BayesRuleInMemory(Scene): - def construct(self): - randy = Randolph() - randy.to_corner(DOWN+LEFT) - bubble = ThoughtBubble(height = 4) - bubble.pin_to(randy) - B = "\\text{Belief}" - D = "\\text{Data}" - rule = TexMobject( - "P(", B, "|", D, ")", "=", - "P(", "B", ")", - "{P(", D, "|", B, ")", "\\over", "P(", D, ")}" - ) - rule.set_color_by_tex(B, RED) - rule.set_color_by_tex(D, GREEN) - rule.next_to(randy, RIGHT, LARGE_BUFF, UP) - rule.generate_target() - bubble.add_content(rule.target) - screen_rect = ScreenRectangle() - screen_rect.next_to(randy, UP+RIGHT) - - self.add(randy) - self.play( - LaggedStartMap(FadeIn, rule), - randy.change, "erm", rule - ) - self.play(Blink(randy)) - self.play( - ShowCreation(bubble), - MoveToTarget(rule), - randy.change, "pondering", - ) - self.wait() - self.play(rule.fade, 0.7, run_time = 2) - self.play(randy.change, "confused", rule) - self.play(Blink(randy)) - self.wait(2) - self.play( - FadeOut(VGroup(bubble, rule)), - randy.change, "pondering", screen_rect, - ) - self.play( - randy.look_at, screen_rect.get_right(), - ShowCreation(screen_rect), - ) - self.wait(4) - -class NextVideoWrapper(TeacherStudentsScene): - CONFIG = { - "title" : "Upcoming chapter: Bayesian networks" - } - def construct(self): - title = TextMobject(self.title) - title.scale(0.8) - title.to_edge(UP, buff = SMALL_BUFF) - screen = ScreenRectangle(height = 4) - screen.next_to(title, DOWN) - title.save_state() - title.shift(DOWN) - title.set_fill(opacity = 0) - - self.play( - title.restore, - self.teacher.change, "raise_right_hand" - ) - self.play(ShowCreation(screen)) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = screen - ) - self.play(Animation(screen)) - self.wait(5) - -class BayesianNetworkPreview(Scene): - def construct(self): - self.add_network() - self.show_propogation(self.network.nodes[0]) - self.show_propogation(self.network.nodes[-1]) - - def add_network(self): - radius = MED_SMALL_BUFF - node = Circle(color = WHITE, radius = radius) - node.shift(2*DOWN) - nodes = VGroup(*[ - node.copy().shift(x*RIGHT + y*UP) - for x, y in [ - (-1, 0), - (1, 0), - (-2, 2), - (0, 2), - (2, 2), - (-2, 4), - (0, 4), - ] - ]) - for node in nodes: - node.children = VGroup() - node.parents = VGroup() - node.outgoing_edges = VGroup() - edge_index_pairs = [ - (2, 0), - (3, 0), - (3, 1), - (4, 1), - (5, 2), - (6, 3), - ] - edges = VGroup() - for i1, i2 in edge_index_pairs: - n1, n2 = nodes[i1], nodes[i2] - edge = Arrow( - n1.get_center(), - n2.get_center(), - buff = radius, - color = WHITE, - ) - n1.outgoing_edges.add(edge) - edges.add(edge) - n1.children.add(n2) - n2.parents.add(n1) - - network = VGroup(nodes, edges) - network.nodes = nodes - network.edges = edges - self.add(network) - self.network = network - - def show_propogation(self, node): - self.set_network_fills() - all_ghosts = VGroup() - curr_nodes = [node] - covered_nodes = set() - self.play(GrowFromCenter(node.fill)) - self.remove(node.fill) - while curr_nodes: - next_nodes = set([]) - anims = [] - for node in curr_nodes: - node.ghost = node.fill.copy().fade() - self.add(node.ghost) - all_ghosts.add(node.ghost) - connected_nodes = [n for n in it.chain(node.children, node.parents) if n not in covered_nodes] - for next_node in connected_nodes: - if next_node in covered_nodes: - continue - next_nodes.add(next_node) - anims.append(Transform( - node.fill.copy(), next_node.fill, - remover = True - )) - if len(connected_nodes) == 0: - anims.append(FadeOut(node.fill)) - if anims: - self.play(*anims) - covered_nodes.update(curr_nodes) - curr_nodes = list(next_nodes) - self.wait() - self.play(FadeOut(all_ghosts)) - - - def set_network_fills(self): - for node in self.network.nodes: - node.fill = self.get_fill(node) - - - def get_fill(self, node): - fill = node.copy() - fill.set_fill(YELLOW, 1) - fill.set_stroke(width = 0) - return fill - -class GeneralizeBayesRule(SampleSpaceScene): - def construct(self): - self.add_sample_space() - self.add_title() - self.add_posterior_rectangles() - self.add_bayes_rule() - self.talk_through_terms() - self.name_likelihood() - self.dont_memorize() - self.show_space_restriction() - - def add_sample_space(self): - sample_space = SampleSpace( - full_space_config = { - "height" : 3, - "width" : 3, - "fill_opacity" : 0 - } - ) - sample_space.divide_horizontally(0.4) - sample_space.horizontal_parts.set_fill(opacity = 0) - labels = [ - TexMobject("P(", "B", ")"), - TexMobject("P(\\text{not }", "B", ")"), - ] - for label in labels: - label.scale(0.7) - self.color_label(label) - sample_space.get_side_braces_and_labels(labels) - sample_space.add_braces_and_labels() - - parts = sample_space.horizontal_parts - values = [0.8, 0.4] - given_strs = ["", "\\text{not }"] - color_pairs = [(GREEN, BLUE), (GREEN_E, BLUE_E)] - vects = [UP, DOWN] - for tup in zip(parts, values, given_strs, color_pairs, vects): - part, value, given_str, colors, vect = tup - part.divide_vertically(value, colors = colors) - part.vertical_parts.set_fill(opacity = 0.8) - label = TexMobject( - "P(", "I", "|", given_str, "B", ")" - ) - label.scale(0.7) - self.color_label(label) - part.get_subdivision_braces_and_labels( - part.vertical_parts, [label], vect - ) - sample_space.add( - part.vertical_parts.braces, - part.vertical_parts.labels, - ) - sample_space.to_edge(LEFT) - - self.add(sample_space) - self.sample_space = sample_space - - def add_title(self): - title = TextMobject( - "Updating", "Beliefs", "from new", "Information" - ) - self.color_label(title) - title.scale(0.8) - title.to_corner(UP+LEFT) - - self.add(title) - - def add_posterior_rectangles(self): - prior_rects = self.get_prior_rectangles() - post_rects = self.get_posterior_rectangles() - - label = TexMobject("P(", "B", "|", "I", ")") - label.scale(0.7) - self.color_label(label) - braces, labels = self.get_posterior_rectangle_braces_and_labels( - post_rects, [label] - ) - - self.play(ReplacementTransform( - prior_rects.copy(), post_rects, - run_time = 2 - )) - self.play( - GrowFromCenter(braces), - Write(label) - ) - - self.post_rects = post_rects - self.posterior_tex = label - - def add_bayes_rule(self): - rule = TexMobject( - "=", "{P(", "B", ")", "P(", "I", "|", "B", ")", - "\\over", "P(", "I", ")}", - ) - self.color_label(rule) - rule.scale(0.7) - rule.next_to(self.posterior_tex, RIGHT) - - bayes_rule_words = TextMobject("Bayes' rule") - bayes_rule_words.next_to(VGroup(*rule[1:]), UP, LARGE_BUFF) - bayes_rule_words.shift_onto_screen() - - self.play(FadeIn(rule)) - self.play(Write(bayes_rule_words)) - self.wait(2) - - self.bayes_rule_words = bayes_rule_words - self.bayes_rule = rule - - def talk_through_terms(self): - prior = self.sample_space.horizontal_parts.labels[0] - posterior = self.posterior_tex - prior_target = VGroup(*self.bayes_rule[1:4]) - likelihood = VGroup(*self.bayes_rule[4:9]) - P_I = VGroup(*self.bayes_rule[-3:]) - - prior_word = TextMobject("Prior") - posterior_word = TextMobject("Posterior") - words = [prior_word, posterior_word] - for word in words: - word.set_color(YELLOW) - word.scale(0.7) - prior_rect = SurroundingRectangle(prior) - posterior_rect = SurroundingRectangle(posterior) - for rect in prior_rect, posterior_rect: - rect.set_stroke(YELLOW, 2) - - prior_word.next_to(prior, UP, LARGE_BUFF) - posterior_word.next_to(posterior, DOWN, LARGE_BUFF) - for word in words: - word.shift_onto_screen() - prior_arrow = Arrow( - prior_word.get_bottom(), prior.get_top(), - tip_length = 0.15 - ) - posterior_arrow = Arrow( - posterior_word.get_top(), posterior.get_bottom(), - tip_length = 0.15 - ) - - self.play( - Write(prior_word), - ShowCreation(prior_arrow), - ShowCreation(prior_rect), - ) - self.wait() - self.play(Transform( - prior.copy(), prior_target, - run_time = 2, - path_arc = -np.pi/3, - remover = True, - )) - self.wait() - parts = self.sample_space[0].vertical_parts - self.play( - Indicate(likelihood), - Indicate(parts.labels), - Indicate(parts.braces), - ) - self.wait() - self.play(Indicate(P_I)) - self.play(FocusOn(self.sample_space[0][0])) - for i in range(2): - self.play(Indicate( - self.sample_space[i][0], - scale_factor = 1 - )) - self.wait() - self.play( - Write(posterior_word), - ShowCreation(posterior_arrow), - ShowCreation(posterior_rect), - ) - - self.prior_label = VGroup(prior_word, prior_arrow, prior_rect) - self.posterior_label = VGroup(posterior_word, posterior_arrow, posterior_rect) - self.likelihood = likelihood - - def name_likelihood(self): - likelihoods = [ - self.sample_space[0].vertical_parts.labels[0], - self.likelihood - ] - rects = [ - SurroundingRectangle(mob, buff = SMALL_BUFF) - for mob in likelihoods - ] - name = TextMobject("Likelihood") - name.scale(0.7) - name.next_to(self.posterior_tex, UP, 1.5*LARGE_BUFF) - arrows = [ - Arrow( - name, rect.get_edge_center(vect), - tip_length = 0.15 - ) - for rect, vect in zip(rects, [RIGHT, UP]) - ] - VGroup(name, *arrows+rects).set_color(YELLOW) - - morty = Mortimer() - morty.scale(0.5) - morty.next_to(rects[1], UP, buff = 0) - morty.shift(SMALL_BUFF*RIGHT) - - self.play( - self.bayes_rule_words.to_edge, UP, - Write(name), - *list(map(ShowCreation, arrows+rects)) - ) - self.wait() - - self.play(FadeIn(morty)) - self.play(morty.change, "confused", name) - self.play(Blink(morty)) - self.play(morty.look, DOWN) - self.wait() - self.play(morty.look_at, name) - self.play(Blink(morty)) - self.play(morty.change, "shruggie") - - self.play(FadeOut(VGroup(name, *arrows+rects))) - self.play(FadeOut(morty)) - self.play(FadeOut(self.posterior_label)) - self.play(FadeOut(self.prior_label)) - - def dont_memorize(self): - rule = VGroup(*self.bayes_rule[1:]) - word = TextMobject("Memorize") - word.scale(0.7) - word.next_to(rule, DOWN) - cross = VGroup( - Line(UP+LEFT, DOWN+RIGHT), - Line(UP+RIGHT, DOWN+LEFT), - ) - cross.set_stroke(RED, 6) - cross.replace(word, stretch = True) - - self.play(Write(word)) - self.wait() - self.play(ShowCreation(cross)) - self.wait() - self.play(FadeOut(VGroup(cross, word))) - self.play(FadeOut(self.bayes_rule)) - self.play( - FadeOut(self.post_rects), - FadeOut(self.post_rects.braces), - FadeOut(self.post_rects.labels), - ) - - def show_space_restriction(self): - prior_rects = self.get_prior_rectangles() - non_I_rects = VGroup(*[ - self.sample_space[i][1] - for i in range(2) - ]) - post_rects = self.post_rects - - self.play(non_I_rects.fade, 0.8) - self.play(LaggedStartMap( - ApplyMethod, - prior_rects, - lambda m : (m.set_color, YELLOW), - rate_func = there_and_back, - lag_ratio = 0.7 - )) - self.wait(2) - self.play(ReplacementTransform( - prior_rects.copy(), post_rects, - run_time = 2 - )) - self.play(*list(map(FadeIn, [ - post_rects.braces, post_rects.labels - ]))) - self.wait() - self.play(*self.get_conditional_change_anims(1, 0.2, post_rects)) - self.play(*self.get_conditional_change_anims(0, 0.6, post_rects)) - self.wait() - self.play(*it.chain( - self.get_division_change_animations( - self.sample_space, - self.sample_space.horizontal_parts, - 0.1 - ), - self.get_posterior_rectangle_change_anims(post_rects) - )) - self.wait(3) - - - #### - - def color_label(self, label): - label.set_color_by_tex("B", RED) - label.set_color_by_tex("I", GREEN) - -class MoreExamples(TeacherStudentsScene): - def construct(self): - self.teacher_says("More examples!", target_mode = "hooray") - self.change_student_modes(*["hooray"]*3) - self.wait(2) - -class MusicExample(SampleSpaceScene, PiCreatureScene): - def construct(self): - self.introduce_musician() - self.add_prior() - self.record_track() - self.add_bottom_conditionl() - self.friend_gives_compliment() - self.friends_dont_like() - self.false_compliment() - self.add_top_conditionl() - self.get_positive_review() - self.restrict_space() - self.show_posterior_rectangles() - self.show_prior_rectangle_areas() - self.show_posterior_probability() - self.intuition_of_positive_feedback() - self.make_friends_honest() - self.fade_out_post_rect() - self.get_negative_feedback() - self.compare_prior_to_post_given_negative() - self.intuition_of_negative_feedback() - - def introduce_musician(self): - randy = self.pi_creature - randy.change_mode("soulful_musician") - randy.arms = randy.get_arm_copies() - guitar = randy.guitar = Guitar() - guitar.move_to(randy) - guitar.shift(0.31*RIGHT + 0.6*UP) - - randy.change_mode("plain") - self.play( - randy.change_mode, "soulful_musician", - path_arc = np.pi/6, - ) - self.play( - Animation(randy), - DrawBorderThenFill(guitar), - Animation(randy.arms) - ) - randy.add(guitar, randy.arms) - self.wait() - self.play_notes(guitar) - self.change_pi_creature_with_guitar("concerned_musician") - self.wait(2) - self.play( - randy.scale, 0.7, - randy.to_corner, UP+LEFT, - ) - self.play_notes(guitar) - - def add_prior(self): - sample_space = SampleSpace() - sample_space.shift(DOWN) - sample_space.divide_horizontally(0.8, colors = [MAROON_D, BLUE_E]) - labels = VGroup( - TexMobject("P(S) = ", "0.8"), - TexMobject("P(\\text{not } S) = ", "0.2"), - ) - labels.scale(0.7) - braces, labels = sample_space.get_side_braces_and_labels(labels) - VGroup(sample_space, braces, labels).to_edge(LEFT) - words = list(map(TextMobject, [ - "Blunt honesty", "Some confidence" - ])) - for word, part in zip(words, sample_space.horizontal_parts): - word.scale(0.6) - word.move_to(part) - - self.play(LaggedStartMap(FadeIn, sample_space, run_time = 1)) - self.play(*list(map(GrowFromCenter, braces))) - for label in labels: - self.play(Write(label, run_time = 2)) - self.wait() - for word, mode in zip(words, ["maybe", "soulful_musician"]): - self.play(LaggedStartMap(FadeIn, word, run_time = 1)) - self.change_pi_creature_with_guitar(mode) - self.wait() - self.wait() - self.play(*list(map(FadeOut, words))) - - self.sample_space = sample_space - - def record_track(self): - randy = self.pi_creature - friends = VGroup(*[ - PiCreature(mode = "happy", color = color).flip() - for color in (BLUE_B, GREY_BROWN, MAROON_E) - ]) - friends.scale(0.6) - friends.arrange(RIGHT) - friends.next_to(randy, RIGHT, LARGE_BUFF, DOWN) - friends.to_edge(RIGHT) - for friend in friends: - friend.look_at(randy.eyes) - - headphones = VGroup(*list(map(Headphones, friends))) - - self.play(FadeIn(friends)) - self.pi_creatures.add(*friends) - self.play( - FadeIn(headphones), - Animation(friends) - ) - self.play_notes(randy.guitar) - self.play(LaggedStartMap( - ApplyMethod, friends, - lambda pi : (pi.change, "hooray"), - run_time = 2, - )) - - self.friends = friends - self.headphones = headphones - - def add_bottom_conditionl(self): - p = 0.99 - bottom_part = self.sample_space[1] - bottom_part.divide_vertically(p, colors = [GREEN_E, YELLOW]) - label = self.get_conditional_label(p, False) - braces, labels = bottom_part.get_bottom_braces_and_labels([label]) - brace = braces[0] - - self.play(FadeIn(bottom_part.vertical_parts)) - self.play(GrowFromCenter(brace)) - self.play(Write(label)) - self.wait() - - def friend_gives_compliment(self): - friends = self.friends - bubble = SpeechBubble( - height = 1.25, width = 3, direction = RIGHT, - fill_opacity = 0, - ) - content = TextMobject("Phenomenal!") - content.scale(0.75) - bubble.add_content(content) - VGroup(bubble, content).next_to(friends, LEFT, SMALL_BUFF) - VGroup(bubble, content).to_edge(UP, SMALL_BUFF) - - self.play(LaggedStartMap( - ApplyMethod, friends, - lambda pi : (pi.change_mode, "conniving") - )) - self.wait() - self.play( - ShowCreation(bubble), - Write(bubble.content, run_time = 1), - ApplyMethod(friends[0].change_mode, "hooray"), - LaggedStartMap( - ApplyMethod, VGroup(*friends[1:]), - lambda pi : (pi.change_mode, "happy") - ), - ) - self.wait(2) - self.play(*list(map(FadeOut, [bubble, content]))) - - def friends_dont_like(self): - friends = self.friends - pi1, pi2, pi3 = friends - for friend in friends: - friend.generate_target() - pi1.target.change("guilty", pi2.eyes) - pi2.target.change("hesitant", pi1.eyes) - pi3.target.change("pondering", pi2.eyes) - - self.play(LaggedStartMap( - MoveToTarget, friends - )) - self.change_pi_creature_with_guitar("concerned_musician") - self.wait() - - def false_compliment(self): - friend = self.friends[0] - bubble = SpeechBubble( - height = 1.25, width = 4.5, direction = RIGHT, - fill_opacity = 0, - ) - content = TextMobject("The beat was consistent.") - content.scale(0.75) - bubble.add_content(content) - VGroup(bubble, content).next_to(friend, LEFT, SMALL_BUFF) - VGroup(bubble, content).to_edge(UP, SMALL_BUFF) - - self.play( - friend.change_mode, "maybe", - ShowCreation(bubble), - Write(content) - ) - self.change_pi_creature_with_guitar("happy") - self.wait() - self.play(*list(map(FadeOut, [bubble, content]))) - - self.bubble = bubble - - def add_top_conditionl(self): - p = 0.9 - top_part = self.sample_space[0] - top_part.divide_vertically(p, colors = [TEAL_E, RED_E]) - label = self.get_conditional_label(p, True) - braces, labels = top_part.get_top_braces_and_labels([label]) - brace = braces[0] - - self.play(FadeIn(top_part.vertical_parts)) - self.play(GrowFromCenter(brace)) - self.play(Write(label, run_time = 2)) - self.wait() - - def get_positive_review(self): - friends = self.friends - - self.change_pi_creature_with_guitar( - "soulful_musician", - LaggedStartMap( - ApplyMethod, friends, - lambda pi : (pi.change, "happy"), - run_time = 1, - ) - ) - self.play_notes(self.pi_creature.guitar) - - def restrict_space(self): - positive_space, negative_space = [ - VGroup(*[ - self.sample_space[i][j] - for i in range(2) - ]) - for j in range(2) - ] - negative_space.save_state() - - self.play(negative_space.fade, 0.8) - self.play(LaggedStartMap( - ApplyMethod, positive_space, - lambda m : (m.set_color, YELLOW), - rate_func = there_and_back, - run_time = 2, - lag_ratio = 0.7, - )) - self.wait() - - self.negative_space = negative_space - - def show_posterior_rectangles(self): - prior_rects = self.get_prior_rectangles() - post_rects = self.get_posterior_rectangles() - label = TexMobject("P(S | ", "\\checkmark", ")") - label.scale(0.7) - label.set_color_by_tex("\\checkmark", GREEN) - braces, labels = self.get_posterior_rectangle_braces_and_labels( - post_rects, [label] - ) - brace = braces[0] - - self.play(ReplacementTransform( - prior_rects.copy(), post_rects, - run_time = 2 - )) - self.play(GrowFromCenter(brace)) - self.play(Write(label)) - self.wait() - - self.post_rects = post_rects - self.post_tex = label - - def show_prior_rectangle_areas(self): - prior_rects = self.get_prior_rectangles() - products = VGroup( - TexMobject("(", "0.8", ")(", "0.9", ")"), - TexMobject("(", "0.2", ")(", "0.99", ")"), - ) - top_product, bottom_product = products - for product, rect in zip(products, prior_rects): - product.scale(0.7) - product.move_to(rect) - side_labels = self.sample_space.horizontal_parts.labels - top_labels = self.sample_space[0].vertical_parts.labels - bottom_labels = self.sample_space[1].vertical_parts.labels - - self.play( - ReplacementTransform( - side_labels[0][-1].copy(), - top_product[1], - ), - ReplacementTransform( - top_labels[0][-1].copy(), - top_product[3], - ), - Write(VGroup(*top_product[::2])) - ) - self.wait(2) - self.play( - ReplacementTransform( - side_labels[1][-1].copy(), - bottom_product[1], - ), - ReplacementTransform( - bottom_labels[0][-1].copy(), - bottom_product[3], - ), - Write(VGroup(*bottom_product[::2])) - ) - self.wait(2) - - self.products = products - - def show_posterior_probability(self): - post_tex = self.post_tex - rhs = TexMobject("\\approx", "0.78") - rhs.scale(0.7) - rhs.next_to(post_tex, RIGHT) - ratio = TexMobject( - "{(0.8)(0.9)", "\\over", - "(0.8)(0.9)", "+", "(0.2)(0.99)}" - ) - ratio.scale(0.6) - ratio.next_to(VGroup(post_tex, rhs), DOWN, LARGE_BUFF) - ratio.to_edge(RIGHT) - arrow_kwargs = { - "tip_length" : 0.15, - "color" : WHITE, - "buff" : 2*SMALL_BUFF, - } - to_ratio_arrow = Arrow( - post_tex.get_bottom(), ratio.get_top(), **arrow_kwargs - ) - to_rhs_arrow = Arrow( - ratio.get_top(), rhs[1].get_bottom(), **arrow_kwargs - ) - - prior_rects = self.get_prior_rectangles() - - self.play( - ShowCreation(to_ratio_arrow), - FadeIn(ratio) - ) - self.wait(2) - for mob in prior_rects, prior_rects[0]: - self.play( - mob.set_color, YELLOW, - Animation(self.products), - rate_func = there_and_back, - run_time = 2 - ) - self.wait() - self.wait() - self.play(ShowCreation(to_rhs_arrow)) - self.play(Write(rhs, run_time = 1)) - self.wait(2) - - self.post_rhs = rhs - self.ratio_group = VGroup(ratio, to_ratio_arrow, to_rhs_arrow) - - def intuition_of_positive_feedback(self): - friends = self.friends - prior_num = self.sample_space.horizontal_parts.labels[0][-1] - prior_num_ghost = prior_num.copy().set_fill(opacity = 0.5) - post_num = self.post_rhs[-1] - prior_rect = SurroundingRectangle(prior_num) - post_rect = SurroundingRectangle(post_num) - - self.play(ShowCreation(prior_rect)) - self.play(Transform( - prior_num_ghost, post_num, - remover = True, - path_arc = -np.pi/6, - run_time = 2, - )) - self.play(ShowCreation(post_rect)) - self.wait(2) - for mode, time in ("shruggie", 2), ("hesitant", 0): - self.play(LaggedStartMap( - ApplyMethod, friends, - lambda pi : (pi.change, mode), - run_time = 2, - )) - self.wait(time) - self.play(*list(map(FadeOut, [ - prior_rect, post_rect, - self.ratio_group, self.post_rhs - ]))) - - self.prior_num_rect = prior_rect - - def make_friends_honest(self): - post_rects = self.post_rects - - self.play(FadeOut(self.products)) - for value in 0.5, 0.1, 0.9: - label = self.get_conditional_label(value) - self.play(*self.get_top_conditional_change_anims( - value, post_rects, - new_label_kwargs = {"labels" : [label]}, - ), run_time = 2) - self.wait(2) - - def fade_out_post_rect(self): - self.play(*list(map(FadeOut, [ - self.post_rects, - self.post_rects.braces, - self.post_rects.labels, - ]))) - self.play(self.negative_space.restore) - - def get_negative_feedback(self): - friends = self.friends - old_prior_rects = self.get_prior_rectangles() - for part in self.sample_space.horizontal_parts: - part.vertical_parts.submobjects.reverse() - new_prior_rects = self.get_prior_rectangles() - post_rects = self.get_posterior_rectangles() - label = TexMobject( - "P(S | \\text{not } ", "\\checkmark", ")", - "\\approx", "0.98" - ) - label.scale(0.7) - label.set_color_by_tex("\\checkmark", GREEN) - braces, labels = self.get_posterior_rectangle_braces_and_labels( - post_rects, [label] - ) - brace = braces[0] - - self.play(old_prior_rects.fade, 0.8) - self.play(LaggedStartMap( - ApplyMethod, friends, - lambda pi : (pi.change, "pondering", post_rects), - run_time = 1 - )) - self.wait() - self.play(ReplacementTransform( - new_prior_rects.copy(), post_rects, - run_time = 2 - )) - self.play(GrowFromCenter(brace)) - self.wait(2) - self.play(Write(label)) - self.wait(3) - - self.post_rects = post_rects - - def compare_prior_to_post_given_negative(self): - post_num = self.post_rects.labels[0][-1] - post_num_rect = SurroundingRectangle(post_num) - - self.play(ShowCreation(self.prior_num_rect)) - self.wait() - self.play(ShowCreation(post_num_rect)) - self.wait() - - self.post_num_rect = post_num_rect - - def intuition_of_negative_feedback(self): - friends = self.friends - randy = self.pi_creature - bubble = self.bubble - - modes = ["sassy", "pleading", "horrified"] - for friend, mode in zip(friends, modes): - friend.generate_target() - friend.target.change(mode, randy.eyes) - content = TextMobject("Horrible. Just horrible.") - content.scale(0.6) - bubble.add_content(content) - - self.play(*list(map(MoveToTarget, friends))) - self.play( - ShowCreation(bubble), - Write(bubble.content) - ) - self.change_pi_creature_with_guitar("sad") - self.wait() - self.change_pi_creature_with_guitar("concerned_musician") - self.wait(3) - - ###### - - def create_pi_creature(self): - randy = Randolph() - randy.left_arm_range = [.36, .45] - self.randy = randy - return randy - - def get_conditional_label(self, value, given_suck = True): - positive_str = "\\checkmark" - label = TexMobject( - "P(", positive_str, "|", - "" if given_suck else "\\text{not }", - "S", ")", - "=", str(value) - ) - label.set_color_by_tex(positive_str, GREEN) - label.scale(0.7) - return label - - def change_pi_creature_with_guitar(self, target_mode, *added_anims): - randy = self.pi_creature - randy.remove(randy.arms, randy.guitar) - target = randy.copy() - target.change_mode(target_mode) - target.arms = target.get_arm_copies() - target.guitar = randy.guitar.copy() - for pi in randy, target: - pi.add(pi.guitar, pi.arms) - self.play(Transform(randy, target), *added_anims) - - def play_notes(self, guitar): - note = SVGMobject(file_name = "8th_note") - note.set_height(0.5) - note.set_stroke(width = 0) - note.set_fill(BLUE, 1) - note.move_to(guitar) - note.shift(MED_SMALL_BUFF*(DOWN+2*LEFT)) - notes = VGroup(*[note.copy() for x in range(10)]) - sine_wave = FunctionGraph(np.sin, x_min = -5, x_max = 5) - sine_wave.scale(0.75) - sine_wave.rotate(np.pi/6, about_point = ORIGIN) - sine_wave.shift( - notes.get_center() - \ - sine_wave.point_from_proportion(0) - ) - self.play(LaggedStartMap( - MoveAlongPath, notes, - lambda n : (n, sine_wave), - path_arc = np.pi/2, - run_time = 4, - lag_ratio = 0.5, - rate_func = lambda t : t, - )) - -class FinalWordsOnRule(SampleSpaceScene): - def construct(self): - self.add_sample_space() - self.add_uses() - self.tweak_values() - - def add_sample_space(self): - sample_space = self.sample_space = SampleSpace() - prior = 0.2 - top_conditional = 0.8 - bottom_condional = 0.3 - sample_space.divide_horizontally(prior) - sample_space[0].divide_vertically( - top_conditional, colors = [GREEN, RED] - ) - sample_space[1].divide_vertically( - bottom_condional, colors = [GREEN_E, RED_E] - ) - B = "\\text{Belief}" - D = "\\text{Data}" - P_B = TexMobject("P(", B, ")") - P_D_given_B = TexMobject("P(", D, "|", B, ")") - P_D_given_not_B = TexMobject( - "P(", D, "|", "\\text{not }", B, ")" - ) - P_B_given_D = TexMobject("P(", B, "|", D, ")") - labels = VGroup(P_B, P_D_given_B, P_D_given_not_B, P_B_given_D) - for label in labels: - label.scale(0.7) - label.set_color_by_tex(B, BLUE) - label.set_color_by_tex(D, GREEN) - - prior_rects = self.get_prior_rectangles() - post_rects = self.get_posterior_rectangles() - for i in range(2): - sample_space[i][1].fade(0.7) - - braces = VGroup() - bs, ls = sample_space.get_side_braces_and_labels([P_B]) - braces.add(*bs) - bs, ls = sample_space[0].get_top_braces_and_labels([P_D_given_B]) - braces.add(*bs) - bs, ls = sample_space[1].get_bottom_braces_and_labels([P_D_given_not_B]) - braces.add(*bs) - bs, ls = self.get_posterior_rectangle_braces_and_labels( - post_rects, [P_B_given_D] - ) - braces.add(*bs) - - group = VGroup(sample_space, braces, labels, post_rects) - group.to_corner(DOWN + LEFT) - self.add(group) - - self.post_rects = post_rects - - def add_uses(self): - uses = TextMobject( - "Machine learning, ", - "scientific inference, $\\dots$", - ) - uses.to_edge(UP) - for use in uses: - self.play(Write(use, run_time = 2)) - self.wait() - - def tweak_values(self): - post_rects = self.post_rects - new_value_lists = [ - (0.85, 0.1, 0.11), - (0.3, 0.9, 0.4), - (0.97, 0.3, 1./22), - ] - for new_values in new_value_lists: - for i, value in zip(list(range(2)), new_values): - self.play(*self.get_conditional_change_anims( - i, value, post_rects - )) - self.wait() - self.play(*it.chain( - self.get_horizontal_division_change_animations(new_values[-1]), - self.get_posterior_rectangle_change_anims(post_rects) - )) - self.wait() - self.wait(2) - -class FootnoteWrapper(NextVideoWrapper): - CONFIG = { - "title" : "Thoughts on the classic Bayes example" - } - -class PatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "Burt Humburg", - "CrypticSwarm", - "Juan Benet", - "Mark Zollo", - "James Park", - "Erik Sundell", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Karan Bhargava", - "Ankit Agarwal", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Markus Persson", - "Yoni Nazarathy", - "Ed Kellett", - "Joseph John Cox", - "Dan Buchoff", - "Luc Ritchie", - "Michael McGuffin", - "John Haley", - "Mourits de Beer", - "Ankalagon", - "Eric Lavault", - "Tomohiro Furusawa", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Ryan Dahl", - "Mark Govea", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "Nils Schneider", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ] - } - -class Thumbnail(SampleSpaceScene): - def construct(self): - title = TextMobject("Bayes' rule") - title.scale(2) - title.to_edge(UP) - self.add(title) - - prior_label = TexMobject("P(", "H", ")") - post_label = TexMobject("P(", "H", "|", "D", ")") - for label in prior_label, post_label: - label.set_color_by_tex("H", YELLOW) - label.set_color_by_tex("D", GREEN) - label.scale(1.5) - - sample_space = self.get_sample_space() - sample_space.set_height(4.5) - sample_space.divide_horizontally(0.3) - sample_space[0].divide_vertically(0.8, colors = [GREEN, BLUE]) - sample_space[1].divide_vertically(0.3, colors = [GREEN_E, BLUE_E]) - sample_space.get_side_braces_and_labels([prior_label]) - sample_space.add_braces_and_labels() - post_rects = self.get_posterior_rectangles() - group = self.get_posterior_rectangle_braces_and_labels( - post_rects, [post_label] - ) - post_rects.add(group) - - VGroup(sample_space, post_rects).next_to(title, DOWN, LARGE_BUFF) - self.add(sample_space, post_rects) - - - - - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/bayes_footnote.py b/from_3b1b/on_hold/eop/bayes_footnote.py deleted file mode 100644 index fe1de7fd..00000000 --- a/from_3b1b/on_hold/eop/bayes_footnote.py +++ /dev/null @@ -1,1532 +0,0 @@ -from manimlib.imports import * - -from active_projects.eop.bayes import IntroducePokerHand - -SICKLY_GREEN = "#9BBD37" - - -class BayesClassicExampleOpeningQuote(OpeningQuote): - CONFIG = { - "quote" : [ - "When faced with a difficult question, we often " \ - "answer an easier one instead, usually without " \ - "noticing the substitution.", - ], - "author" : "Daniel Kahneman", - } - -class Introduction(TeacherStudentsScene): - def construct(self): - self.hold_up_example() - self.write_counter_intuitive() - self.comment_on_crazy_results() - self.put_it_first() - self.swap_example_order() - self.other_culprit() - - def hold_up_example(self): - everyone = self.get_pi_creatures() - self.teacher.change_mode("raise_right_hand") - rect = ScreenRectangle() - rect.set_stroke(YELLOW, 2) - rect.to_edge(UP) - randy = Randolph() - randy.scale(0.7) - name = TextMobject(r""" - Bayes' theorem \\ - disease example - """) - name.next_to(rect.get_top(), DOWN, SMALL_BUFF) - randy.next_to(name, DOWN) - example = VGroup(rect, name, randy) - - self.remove(everyone) - self.add(name, randy) - self.play( - randy.change_mode, "sick", - randy.set_color, SICKLY_GREEN - ) - self.play(ShowCreation(rect)) - self.play( - FadeIn(everyone), - example.scale, 0.5, - example.next_to, self.teacher.get_corner(UP+LEFT), UP, - ) - self.wait(2) - - self.example = example - - def write_counter_intuitive(self): - bayes = TextMobject("Bayes") - arrow = TexMobject("\\leftrightarrow") - intuition = TextMobject("Intuition") - - group = VGroup(bayes, arrow, intuition) - group.arrange(RIGHT, buff = SMALL_BUFF) - group.scale(0.8) - group.next_to(self.example, UP, buff = SMALL_BUFF) - group.shift_onto_screen() - cross = VGroup( - Line(UP+LEFT, DOWN+RIGHT), - Line(UP+RIGHT, DOWN+LEFT), - ) - cross.replace(arrow, stretch = True) - cross.set_stroke(RED, 6) - group.add(cross) - - self.play(*list(map(FadeIn, [bayes, intuition]))) - self.play(Write(arrow)) - self.play(ShowCreation(cross)) - self.change_student_modes(*["confused"]*3) - self.wait(2) - - self.bayes_to_intuition = group - - def comment_on_crazy_results(self): - disease_group = VGroup(self.example, self.bayes_to_intuition) - disease_group.save_state() - - self.teacher_says( - "Who doesn't love \\\\ crazy results?", - target_mode = "hooray", - added_anims = [disease_group.to_corner, UP+LEFT] - ) - self.wait(2) - - self.disease_group = disease_group - - def put_it_first(self): - poker_example = self.get_poker_example() - music_example = self.get_music_example() - disease_group = self.disease_group - - self.play( - disease_group.restore, - disease_group.to_edge, LEFT, - RemovePiCreatureBubble( - self.teacher, - target_mode = "hesitant" - ) - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = disease_group - ) - - poker_example.next_to(self.example, RIGHT) - music_example.next_to(poker_example, RIGHT) - examples = VGroup(poker_example, music_example) - brace = Brace(examples, UP) - bayes_to_intuition = VGroup(*list(map(TextMobject, [ - "Bayes", "$\\leftrightarrow$", "Intuition" - ]))) - bayes_to_intuition.arrange(RIGHT, buff = SMALL_BUFF) - bayes_to_intuition.next_to(brace, UP, SMALL_BUFF) - check = TexMobject("\\checkmark") - check.set_color(GREEN) - check.next_to(bayes_to_intuition[1], UP, SMALL_BUFF) - - for example in examples: - self.play(FadeIn(example)) - self.wait() - self.play(GrowFromCenter(brace)) - self.play(FadeIn(bayes_to_intuition)) - self.play(Write(check)) - self.wait(2) - - self.intuitive_examples = VGroup( - examples, brace, bayes_to_intuition, check - ) - - def swap_example_order(self): - intuitive_examples = self.intuitive_examples - disease_group = VGroup( - self.example, self.bayes_to_intuition - ) - - self.play( - disease_group.next_to, - self.teacher.get_corner(UP+LEFT), UP, - disease_group.shift, LEFT, - intuitive_examples.scale, 0.7, - intuitive_examples.to_corner, UP+LEFT, - self.teacher.change_mode, "sassy" - ) - - def other_culprit(self): - bayes = self.bayes_to_intuition[0] - something_else = TextMobject("Something else") - something_else.set_color(YELLOW) - something_else.set_height(bayes.get_height()) - something_else.move_to(bayes, RIGHT) - new_group = VGroup( - something_else, - *self.bayes_to_intuition[1:] - ) - - self.play(bayes.to_edge, UP) - self.play(Write(something_else)) - self.play(new_group.next_to, self.example, UP, SMALL_BUFF) - self.change_student_modes( - "erm", "confused", "hesitant", - added_anims = [self.teacher.change_mode, "happy"] - ) - self.wait(3) - - - ##### - - def get_poker_example(self): - rect = self.get_example_rect() - values = IntroducePokerHand.CONFIG["community_card_values"] - community_cards = VGroup(*list(map(PlayingCard, values))) - community_cards.arrange(RIGHT) - deck = VGroup(*[ - PlayingCard(turned_over = True) - for x in range(5) - ]) - for i, card in enumerate(deck): - card.shift(i*(0.03*RIGHT + 0.015*DOWN)) - deck.next_to(community_cards, LEFT) - cards = VGroup(deck, community_cards) - cards.set_width(rect.get_width() - 2*SMALL_BUFF) - cards.next_to(rect.get_bottom(), UP, MED_SMALL_BUFF) - - probability = TexMobject( - "P(", "\\text{Flush}", "|", "\\text{High bet}", ")" - ) - probability.set_color_by_tex("Flush", RED) - probability.set_color_by_tex("High bet", GREEN) - probability.scale(0.5) - probability.next_to(rect.get_top(), DOWN) - - return VGroup(rect, probability, cards) - - def get_music_example(self): - rect = self.get_example_rect() - - musician = Randolph(mode = "soulful_musician") - musician.left_arm_range = [.36, .45] - musician.arms = musician.get_arm_copies() - guitar = musician.guitar = Guitar() - guitar.move_to(musician) - guitar.shift(0.31*RIGHT + 0.6*UP) - musician.add(guitar, musician.arms) - musician.set_height(0.7*rect.get_height()) - musician.next_to(rect.get_bottom(), UP, SMALL_BUFF) - - probability = TexMobject( - "P(", "\\text{Suck }", "|", "\\text{ Good review}", ")" - ) - probability.set_color_by_tex("Suck", RED) - probability.set_color_by_tex("Good", GREEN) - probability.scale(0.5) - probability.next_to(rect.get_top(), DOWN) - - return VGroup(rect, musician, probability) - - def get_example_rect(self): - rect = self.example[0].copy() - rect.set_color(WHITE) - return rect - -class OneInOneThousandHaveDisease(Scene): - def construct(self): - title = TextMobject("1 in 1{,}000") - title.to_edge(UP) - creature = PiCreature() - all_creatures = VGroup(*[ - VGroup(*[ - creature.copy() - for y in range(25) - ]).arrange(DOWN, SMALL_BUFF) - for x in range(40) - ]).arrange(RIGHT, SMALL_BUFF) - all_creatures.set_width(FRAME_WIDTH - 4) - all_creatures.next_to(title, DOWN) - randy = all_creatures[0][0] - all_creatures[0].remove(randy) - randy.change_mode("sick") - randy.set_color(SICKLY_GREEN) - randy.save_state() - randy.set_height(3) - randy.center() - randy.change_mode("plain") - randy.set_color(BLUE) - - self.add(randy) - self.play( - randy.change_mode, "sick", - randy.set_color, SICKLY_GREEN - ) - self.play(Blink(randy)) - self.play(randy.restore) - self.play( - Write(title), - LaggedStartMap(FadeIn, all_creatures, run_time = 3) - ) - self.wait() - -class TestScene(PiCreatureScene): - def get_result(self, creature, word, color): - arrow = self.get_test_arrow() - test_result = TextMobject(word) - test_result.set_color(color) - test_result.next_to(arrow.get_end(), RIGHT) - group = VGroup(arrow, test_result) - group.next_to(creature, RIGHT, aligned_edge = UP) - return group - - def get_positive_result(self, creature): - return self.get_result(creature, "Diseased", SICKLY_GREEN) - - def get_negative_result(self, creature): - return self.get_result(creature, "Healthy", GREEN) - - def get_test_arrow(self): - arrow = Arrow( - LEFT, RIGHT, - color = WHITE, - ) - word = TextMobject("Test") - word.scale(0.8) - word.next_to(arrow, UP, buff = 0) - arrow.add(word) - return arrow - - def create_pi_creature(self): - randy = Randolph() - randy.next_to(ORIGIN, LEFT) - return randy - -class TestDiseaseCase(TestScene): - def construct(self): - randy = self.pi_creature - randy.change_mode("sick") - randy.set_color(SICKLY_GREEN) - result = self.get_positive_result(randy) - accuracy = TextMobject("100\\% Accuracy") - accuracy.next_to(VGroup(randy, result), UP) - accuracy.to_edge(UP) - - self.add(randy) - self.play(FadeIn(result[0])) - self.play(Write(result[1])) - self.play(FadeIn(accuracy)) - self.wait() - -class TestNonDiseaseCase(TestScene): - def construct(self): - randy = self.pi_creature - randy.change_mode("happy") - randy.next_to(ORIGIN, LEFT) - result = self.get_negative_result(randy) - accuracy = TextMobject("99\\% Accuracy") - accuracy.next_to(VGroup(randy, result), UP) - accuracy.to_edge(UP) - - all_creatures = VGroup(*[ - VGroup(*[ - randy.copy() - for y in range(10) - ]).arrange(DOWN) - for y in range(10) - ]).arrange(RIGHT) - all_creatures.set_height(6) - all_creatures.to_corner(DOWN+LEFT) - last_guy = all_creatures[-1][-1] - rect = SurroundingRectangle(last_guy, buff = 0) - rect.set_color(YELLOW) - - self.add(randy, accuracy) - self.play(FadeIn(result[0])) - self.play(Write(result[1])) - self.play(Blink(randy)) - self.play( - ReplacementTransform(randy, all_creatures[0][0]), - LaggedStartMap(FadeIn, all_creatures, run_time = 2), - FadeOut(result) - ) - self.play(ShowCreation(rect)) - self.play( - last_guy.set_height, 2, - last_guy.next_to, all_creatures, RIGHT - ) - result = self.get_positive_result(last_guy) - false_positive = TextMobject("False positive") - false_positive.scale(0.8) - false_positive.next_to(result, UP, LARGE_BUFF) - false_positive.to_edge(RIGHT) - arrow = Arrow( - false_positive.get_bottom(), result[1].get_top(), - buff = SMALL_BUFF - ) - self.play(FadeIn(result)) - self.play( - FadeIn(false_positive), - ShowCreation(arrow), - last_guy.change, "confused", result, - ) - for x in range(2): - self.play(Blink(last_guy)) - self.wait(2) - -class ReceivePositiveResults(TestScene): - def construct(self): - status = TextMobject("Health status: ???") - status.to_edge(UP) - - randy = self.pi_creature - result = self.get_positive_result(randy) - accuracy = TextMobject("99\% Accuracy") - accuracy.next_to(result[1], DOWN, LARGE_BUFF) - accuracy.set_color(YELLOW) - - self.add(status, randy) - self.play(FadeIn(result[0])) - self.wait() - self.play(Write(result[1])) - self.play(randy.change, "maybe", result) - self.wait(2) - self.play( - randy.change, "pleading", accuracy, - Write(accuracy, run_time = 1) - ) - self.wait() - self.play(randy.change, "sad", accuracy) - self.wait(2) - -class AskAboutRephrasingQuestion(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "What if we rephrased \\\\ the question?", - run_time = 1 - ) - self.wait(3) - -class RephraseQuestion(Scene): - def construct(self): - words = VGroup(*list(map(TextMobject, [ - r"1 in $1{,000}$ chance \\ of having disease", - r"1 in $100$ \\ false positive rate.", - r"""\underline{\phantom{1 in 10}} chance \\ - of having disease \\ - after testing positive. - """, - ]))) - words.arrange(RIGHT, buff = LARGE_BUFF) - words.set_width(2*(FRAME_X_RADIUS - MED_LARGE_BUFF)) - - prior = TextMobject("Prior") - prior.set_color(GREEN) - prior.next_to(words[0], UP, 1.5*LARGE_BUFF) - prior_arrow = Arrow(prior, words[0]) - prior_arrow.set_color(prior.get_color()) - - posterior = TextMobject("Posterior") - posterior.next_to(words[2], UP) - posterior.shift( - (prior.get_center() - posterior.get_center())[1]*UP - ) - posterior.set_color(YELLOW) - posterior_arrow = Arrow(posterior, words[2]) - posterior_arrow.set_color(posterior.get_color()) - - self.add(words[0]) - self.play( - LaggedStartMap(FadeIn, prior), - ShowCreation(prior_arrow), - run_time = 1 - ) - self.wait() - self.play(FadeIn(words[1])) - self.wait() - self.play(FadeIn(words[2])) - self.play( - LaggedStartMap(FadeIn, posterior), - ShowCreation(posterior_arrow), - run_time = 1 - ) - self.wait(2) - -class TryUnitSquareVisual(SampleSpaceScene): - def construct(self): - sample_space = self.get_sample_space() - self.add_prior_division() - self.add(sample_space) - self.add_conditional_divisions() - prior_label = sample_space.horizontal_parts.labels[0] - final_labels = self.final_labels - - hard_to_see = TextMobject("Hard to see") - hard_to_see.scale(0.7) - hard_to_see.next_to(prior_label, UP) - hard_to_see.to_edge(UP) - hard_to_see.set_color(YELLOW) - arrow = Arrow(hard_to_see, prior_label) - - self.wait() - anims = self.get_division_change_animations( - sample_space, sample_space.horizontal_parts, - 0.001, new_label_kwargs = {"labels" : final_labels} - ) - self.play(*anims, run_time = 2) - self.wait() - self.play( - Write(hard_to_see, run_time = 2), - ShowCreation(arrow) - ) - self.wait(2) - - def add_prior_division(self): - sample_space = self.sample_space - sample_space.divide_horizontally(0.1) - initial_labels, final_labels = [ - VGroup( - TexMobject("P(\\text{Disease})", s1), - TexMobject("P(\\text{Not disease})", s2), - ).scale(0.7) - for s1, s2 in (("", ""), ("= 0.001", "= 0.999")) - ] - sample_space.get_side_braces_and_labels(initial_labels) - sample_space.add_braces_and_labels() - - self.final_labels = final_labels - - def add_conditional_divisions(self): - sample_space = self.sample_space - top_part, bottom_part = sample_space.horizontal_parts - - top_brace = Brace(top_part, UP) - top_label = TexMobject( - "P(", "+", "|", "\\text{Disease}", ")", "=", "1" - ) - top_label.scale(0.7) - top_label.next_to(top_brace, UP) - top_label.set_color_by_tex("+", GREEN) - - self.play(GrowFromCenter(top_brace)) - self.play(FadeIn(top_label)) - self.wait() - - bottom_part.divide_vertically( - 0.95, colors = [BLUE_E, YELLOW_E] - ) - bottom_label = TexMobject( - "P(", "+", "|", "\\text{Not disease}", ")", "=", "1" - ) - bottom_label.scale(0.7) - bottom_label.set_color_by_tex("+", GREEN) - braces, labels = bottom_part.get_bottom_braces_and_labels( - [bottom_label] - ) - bottom_brace = braces[0] - - self.play( - FadeIn(bottom_part.vertical_parts), - GrowFromCenter(bottom_brace), - ) - self.play(FadeIn(bottom_label)) - self.wait() - -class ShowRestrictedSpace(Scene): - CONFIG = { - "n_rows" : 25, - "n_cols" : 40, - "n_false_positives" : 10, - "false_positive_color" : YELLOW_E, - } - def construct(self): - self.add_all_creatures() - self.show_accurate_positive_result() - self.show_false_positive_conditional() - self.show_false_positive_individuals() - self.fade_out_negative_result_individuals() - self.show_posterior_probability() - self.contrast_with_prior() - - def add_all_creatures(self): - title = TextMobject("$1{,}000$ individuals") - title.to_edge(UP) - all_creatures = self.get_all_creatures() - sick_one = all_creatures.sick_one - healthy_creatures = all_creatures.healthy_creatures - - sick_one.save_state() - sick_one_words = TextMobject("1 sick") - sick_one_words.next_to(sick_one, RIGHT) - sick_one_words.to_edge(RIGHT) - sick_one_words.set_color(SICKLY_GREEN) - sick_one_arrow = Arrow( - sick_one_words, sick_one, - color = SICKLY_GREEN - ) - - healthy_words = TextMobject("999 healthy") - healthy_words.next_to(sick_one_words, UP, MED_LARGE_BUFF) - healthy_words.shift_onto_screen() - healthy_words.set_color(BLUE) - - self.add(title) - self.play(LaggedStartMap(FadeIn, all_creatures)) - self.play( - FadeIn(sick_one_words), - ShowCreation(sick_one_arrow) - ) - self.play( - sick_one.set_height, 2, - sick_one.next_to, sick_one_words, DOWN, - sick_one.to_edge, RIGHT, - ) - self.wait() - self.play(sick_one.restore) - self.play( - Write(healthy_words), - LaggedStartMap( - ApplyMethod, healthy_creatures, - lambda m : (m.shift, MED_SMALL_BUFF*UP), - rate_func = there_and_back, - lag_ratio = 0.2, - ) - ) - self.wait() - self.play(FadeOut(title)) - - self.all_creatures = all_creatures - self.healthy_creatures = healthy_creatures - self.sick_one = sick_one - self.sick_one_label = VGroup(sick_one_words, sick_one_arrow) - self.healthy_ones_label = healthy_words - - def show_accurate_positive_result(self): - equation = TexMobject( - "P(", "\\text{Test positive }", "|", - "\\text{ sick}", ")", "=", "100\\%" - ) - equation.set_color_by_tex("positive", YELLOW) - equation.set_color_by_tex("sick", SICKLY_GREEN) - equation.to_corner(UP+LEFT) - - self.play(Write(equation, run_time = 1)) - self.wait(2) - - self.disease_conditional = equation - - def show_false_positive_conditional(self): - equation = TexMobject( - "P(", "\\text{Test positive }", "|", - "\\text{ healthy}", ")", "=", "1\\%" - ) - equation.set_color_by_tex("positive", YELLOW) - equation.set_color_by_tex("healthy", BLUE) - equation.to_corner(UP+LEFT) - - self.play(ReplacementTransform( - self.disease_conditional, equation - )) - self.wait() - - self.healthy_conditional = equation - - def show_false_positive_individuals(self): - all_creatures = self.all_creatures - false_positives = VGroup( - *all_creatures[-1][1:1+self.n_false_positives] - ) - self.healthy_creatures.remove(*false_positives) - brace = Brace(false_positives, RIGHT) - words = TextMobject("10 False positives") - words.scale(0.8) - words.next_to(brace, RIGHT) - - self.play( - GrowFromCenter(brace), - LaggedStartMap( - ApplyMethod, false_positives, - lambda pi : (pi.set_color, self.false_positive_color), - run_time = 1 - ) - ) - self.play(Write(words)) - self.wait() - - self.false_positives = false_positives - self.false_positives_brace = brace - self.false_positives_words = words - - def fade_out_negative_result_individuals(self): - to_fade = VGroup( - self.healthy_creatures, - self.healthy_conditional, - self.sick_one_label, - self.healthy_ones_label, - ) - movers = VGroup(self.sick_one, *self.false_positives) - movers.generate_target() - movers.target.set_width(1) - movers.target.arrange(RIGHT) - movers.target.shift(DOWN) - brace = Brace(VGroup(*movers.target[1:])) - - words = TextMobject("You are one of these") - words.to_edge(UP) - arrows = [ - Arrow(words.get_bottom(), movers.target[i].get_top()) - for i in (0, -1) - ] - - self.play(FadeOut(to_fade)) - self.play( - MoveToTarget(movers), - ReplacementTransform(self.false_positives_brace, brace), - self.false_positives_words.next_to, brace, DOWN - ) - self.play( - Write(words, run_time = 2), - ShowCreation(arrows[0]) - ) - self.play(Transform( - *arrows, run_time = 4, rate_func = there_and_back - )) - self.play(*list(map(FadeOut, [words, arrows[0]]))) - - self.brace = brace - - def show_posterior_probability(self): - posterior = TexMobject( - "P(", "\\text{Sick }", "|", - "\\text{ Positive test result}", ")", - "\\approx \\frac{1}{11}", "\\approx 9\\%" - ) - posterior.set_color_by_tex("Sick", SICKLY_GREEN) - posterior.set_color_by_tex("Positive", YELLOW) - posterior.to_edge(UP) - posterior.shift(LEFT) - - self.play(FadeIn(posterior)) - self.wait(2) - - self.posterior = posterior - - def contrast_with_prior(self): - prior = TexMobject( - "P(", "\\text{Sick}", ")", "= 0.1\\%" - ) - prior.set_color_by_tex("Sick", SICKLY_GREEN) - prior.move_to(self.posterior, UP+RIGHT) - - self.revert_to_original_skipping_status() - self.play( - Write(prior, run_time = 1), - self.posterior.shift, DOWN, - ) - arrow = Arrow( - prior.get_right(), self.posterior.get_right(), - path_arc = -np.pi, - ) - times_90 = TexMobject("\\times 90") - times_90.next_to(arrow, RIGHT) - self.play(ShowCreation(arrow)) - self.play(Write(times_90, run_time = 1)) - self.wait(2) - - ###### - - def get_all_creatures(self): - creature = PiCreature() - all_creatures = VGroup(*[ - VGroup(*[ - creature.copy() - for y in range(self.n_rows) - ]).arrange(DOWN, SMALL_BUFF) - for x in range(self.n_cols) - ]).arrange(RIGHT, SMALL_BUFF) - all_creatures.set_height(5) - all_creatures.center().to_edge(LEFT) - - healthy_creatures = VGroup(*it.chain(*all_creatures)) - sick_one = all_creatures[-1][0] - sick_one.change_mode("sick") - sick_one.set_color(SICKLY_GREEN) - healthy_creatures.remove(sick_one) - all_creatures.sick_one = sick_one - all_creatures.healthy_creatures = healthy_creatures - return all_creatures - -class DepressingForMedicalTestDesigners(TestScene): - def construct(self): - self.remove(self.pi_creature) - self.show_99_percent_accuracy() - self.reject_test() - - def show_99_percent_accuracy(self): - title = TextMobject("99\\% Accuracy") - title.to_edge(UP) - title.generate_target() - title.target.to_corner(UP+LEFT) - - checks = VGroup(*[ - VGroup(*[ - TexMobject("\\checkmark").set_color(GREEN) - for y in range(10) - ]).arrange(DOWN) - for x in range(10) - ]).arrange(RIGHT) - cross = TexMobject("\\times") - cross.replace(checks[-1][-1]) - cross.set_color(RED) - Transform(checks[-1][-1], cross).update(1) - checks.set_height(6) - checks.next_to(title, DOWN) - checks.generate_target() - checks.target.scale(0.5) - checks.target.next_to(title.target, DOWN) - - self.add(title) - self.play(Write(checks)) - self.wait(2) - self.play(*list(map(MoveToTarget, [title, checks]))) - - def reject_test(self): - randy = self.pi_creature - randy.to_edge(DOWN) - result = self.get_positive_result(randy) - - self.play(FadeIn(randy)) - self.play( - FadeIn(result), - randy.change_mode, "pondering" - ) - self.wait() - self.say( - "Whatever, I'm 91\\% \\\\ sure that's wrong", - target_mode = "shruggie" - ) - self.wait(2) - -class HowMuchCanYouChangeThisPrior(ShowRestrictedSpace, PiCreatureScene): - def construct(self): - self.single_out_new_sick_one() - self.show_subgroups() - self.isolate_special_group() - - def create_pi_creatures(self): - creatures = self.get_all_creatures() - creatures.set_height(6.5) - creatures.center() - creatures.submobjects = list(it.chain(*creatures)) - - self.add(creatures) - self.sick_one = creatures.sick_one - return creatures - - def single_out_new_sick_one(self): - creatures = self.pi_creatures - sick_one = self.sick_one - new_sick_one = sick_one.copy() - new_sick_one.shift(1.3*sick_one.get_width()*RIGHT) - sick_one.change_mode("plain") - sick_one.set_color(BLUE_E) - - self.add(new_sick_one) - self.sick_one = new_sick_one - - def show_subgroups(self): - subgroups = VGroup(*[ - VGroup(*it.chain( - self.pi_creatures[i:i+5], - self.pi_creatures[i+25:i+25+5:] - )) - for i in range(0, 1000) - if i%5 == 0 and (i/25)%2 == 0 - ]) - special_group = subgroups[-5] - special_group.add(self.sick_one) - subgroups.generate_target() - width_factor = FRAME_WIDTH/subgroups.get_width() - height_factor = FRAME_HEIGHT/subgroups.get_height() - subgroups.target.stretch_in_place(width_factor, 0) - subgroups.target.stretch_in_place(height_factor, 1) - for subgroup in subgroups.target: - subgroup.stretch_in_place(1./width_factor, 0) - subgroup.stretch_in_place(1./height_factor, 1) - - self.wait() - self.play(MoveToTarget(subgroups)) - subgroups.remove(special_group) - - rects = VGroup(*[ - SurroundingRectangle( - group, buff = 0, color = GREEN - ) - for group in subgroups - ]) - special_rect = SurroundingRectangle( - special_group, buff = 0, color = RED - ) - self.play(FadeIn(rects), FadeIn(special_rect)) - self.wait() - - self.to_fade = VGroup(subgroups, rects) - self.special_group = special_group - self.special_rect = special_rect - - def isolate_special_group(self): - to_fade, special_group = self.to_fade, self.special_group - self.play(FadeOut(to_fade)) - self.play( - FadeOut(self.special_rect), - special_group.set_height, 6, - special_group.center, - ) - self.wait() - -class ShowTheFormula(TeacherStudentsScene): - CONFIG = { - "seconds_to_blink" : 3, - } - def construct(self): - scale_factor = 0.7 - sick = "\\text{sick}" - positive = "\\text{positive}" - formula = TexMobject( - "P(", sick, "\\,|\\,", positive, ")", "=", - "{\\quad P(", sick, "\\text{ and }", positive, ") \\quad", - "\\over", - "P(", positive, ")}", "=", - "{1/1{,}000", "\\over", "1/1{,}000", "+", "(999/1{,}000)(0.01)}" - ) - formula.scale(scale_factor) - formula.next_to(self.pi_creatures, UP, LARGE_BUFF) - formula.shift_onto_screen(buff = MED_LARGE_BUFF) - equals_group = formula.get_parts_by_tex("=") - equals_indices = [ - formula.index_of_part(equals) - for equals in equals_group - ] - - lhs = VGroup(*formula[:equals_indices[0]]) - initial_formula = VGroup(*formula[:equals_indices[1]]) - initial_formula.save_state() - initial_formula.shift(3*RIGHT) - - over = formula.get_part_by_tex("\\over") - num_start_index = equals_indices[0] + 1 - num_end_index = formula.index_of_part(over) - numerator = VGroup( - *formula[num_start_index:num_end_index] - ) - numerator_rect = SurroundingRectangle(numerator) - - alt_numerator = TexMobject( - "P(", sick, ")", "P(", positive, "\\,|\\,", sick, ")" - ) - alt_numerator.scale(scale_factor) - alt_numerator.move_to(numerator) - - number_fraction = VGroup(*formula[equals_indices[-1]:]) - - rhs = TexMobject("\\approx 0.09") - rhs.scale(scale_factor) - rhs.move_to(equals_group[-1], LEFT) - rhs.set_color(YELLOW) - - for mob in formula, alt_numerator: - mob.set_color_by_tex(sick, SICKLY_GREEN) - mob.set_color_by_tex(positive, YELLOW) - - - #Ask question - self.student_says("What does the \\\\ formula look like here?") - self.play(self.teacher.change, "happy") - self.wait() - self.play( - Write(lhs), - RemovePiCreatureBubble( - self.students[1], target_mode = "pondering", - ), - self.teacher.change, "raise_right_hand", - self.students[0].change, "pondering", - self.students[2].change, "pondering", - ) - self.wait() - - #Show initial formula - lhs_copy = lhs.copy() - self.play( - LaggedStartMap( - FadeIn, initial_formula, - lag_ratio = 0.7 - ), - Animation(lhs_copy, remover = True), - ) - self.wait(2) - self.play(ShowCreation(numerator_rect)) - self.play(FadeOut(numerator_rect)) - self.wait() - self.play(Transform(numerator, alt_numerator)) - initial_formula.add(*numerator) - formula.add(*numerator) - self.wait(3) - - #Show number_fraction - self.play( - initial_formula.move_to, initial_formula.saved_state, - FadeIn(VGroup(*number_fraction[:3])) - ) - self.wait(2) - self.play(LaggedStartMap( - FadeIn, VGroup(*number_fraction[3:]), - run_time = 3, - lag_ratio = 0.7 - )) - self.wait(2) - - #Show rhs - self.play(formula.shift, UP) - self.play(Write(rhs)) - self.change_student_modes(*["happy"]*3) - self.look_at(rhs) - self.wait(2) - -class SourceOfConfusion(Scene): - CONFIG = { - "arrow_width" : 5, - } - def construct(self): - self.add_progression() - self.ask_question() - self.write_bayes_rule() - self.shift_arrow() - - def add_progression(self): - prior = TexMobject("P(", "S", ")", "= 0.001") - arrow = Arrow(ORIGIN, self.arrow_width*RIGHT) - posterior = TexMobject("P(", "S", "|", "+", ")", "= 0.09") - for mob in prior, posterior: - mob.set_color_by_tex("S", SICKLY_GREEN) - mob.set_color_by_tex("+", YELLOW) - progression = VGroup(prior, arrow, posterior) - progression.arrange(RIGHT) - progression.shift(DOWN) - - bayes_rule_words = TextMobject("Bayes' rule") - bayes_rule_words.next_to(arrow, UP, buff = 0) - arrow.add(bayes_rule_words) - - for mob, word in (prior, "Prior"), (posterior, "Posterior"): - brace = Brace(mob, DOWN) - label = brace.get_text(word) - mob.add(brace, label) - - self.add(progression) - self.progression = progression - self.bayes_rule_words = bayes_rule_words - - def ask_question(self): - question = TextMobject( - "Where do math and \\\\ intuition disagree?" - ) - question.to_corner(UP+LEFT) - question_arrow = Arrow( - question.get_bottom(), - self.bayes_rule_words.get_top(), - color = WHITE - ) - - self.play(Write(question)) - self.wait() - self.play(ShowCreation(question_arrow)) - - self.question = question - self.question_arrow = question_arrow - - def write_bayes_rule(self): - words = self.bayes_rule_words - words_rect = SurroundingRectangle(words) - rule = TexMobject( - "P(", "S", "|", "+", ")", "=", - "P(", "S", ")", - "{P(", "+", "|", "S", ")", "\\over", - "P(", "+", ")}" - ) - rule.set_color_by_tex("S", SICKLY_GREEN) - rule.set_color_by_tex("+", YELLOW) - rule.to_corner(UP+RIGHT) - rule_rect = SurroundingRectangle(rule) - rule_rect.set_color(BLUE) - rule.save_state() - rule.replace(words_rect) - rule.scale_in_place(0.9) - rule.set_fill(opacity = 0) - - self.play(ShowCreation(words_rect)) - self.play( - ReplacementTransform(words_rect, rule_rect), - rule.restore, - run_time = 2 - ) - self.wait(3) - - def shift_arrow(self): - new_arrow = Arrow( - self.question.get_bottom(), - self.progression[0].get_top(), - color = WHITE - ) - - self.play(Transform( - self.question_arrow, - new_arrow - )) - self.wait(2) - -class StatisticsVsEmpathy(PiCreatureScene): - def construct(self): - randy, morty = self.randy, self.morty - sick_one = PiCreature() - sick_one.scale(0.5) - sick_group = VGroup( - sick_one, VectorizedPoint(sick_one.get_bottom()) - ) - priors = VGroup(*[ - TexMobject("%.1f"%p+ "\\%").move_to(ORIGIN, RIGHT) - for p in np.arange(0.1, 2.0, 0.1) - ]) - priors.next_to(randy, UP+LEFT, LARGE_BUFF) - prior = priors[0] - prior.save_state() - - self.play(PiCreatureSays( - morty, - "1 in 1{,}000 people \\\\ have this disease.", - look_at_arg = randy.eyes - )) - self.play(randy.change, "pondering", morty.eyes) - self.wait() - self.play(Write(prior)) - self.wait() - self.play( - prior.scale, 0.1, - prior.set_fill, None, 0, - prior.move_to, randy.eyes - ) - self.wait() - self.play( - PiCreatureBubbleIntroduction( - randy, sick_group, - target_mode = "guilty", - bubble_class = ThoughtBubble, - content_introduction_class = FadeIn, - look_at_arg = sick_one, - ), - RemovePiCreatureBubble(morty) - ) - self.play( - sick_one.change_mode, "sick", - sick_one.set_color, SICKLY_GREEN - ) - self.wait() - - probably_me = TextMobject("That's probably \\\\ me") - probably_me.next_to(sick_one, DOWN) - target_sick_group = VGroup( - sick_one.copy(), - probably_me - ) - target_sick_group.scale(0.8) - self.pi_creature_thinks( - target_sick_group, - target_mode = "pleading", - ) - self.wait(2) - - self.play(prior.restore) - for new_prior in priors[1:]: - self.play(Transform(prior, new_prior, run_time = 0.5)) - self.wait() - - ###### - - def create_pi_creatures(self): - randy = self.randy = Randolph() - morty = self.morty = Mortimer() - randy.to_edge(DOWN).shift(3*LEFT) - morty.to_edge(DOWN).shift(3*RIGHT) - return VGroup(randy, morty) - -class LessMedicalExample(Scene): - def construct(self): - disease = TexMobject("P(\\text{Having a disease})") - telepathy = TexMobject("P(\\text{Telepathy})") - cross = Cross(disease) - - self.add(disease) - self.wait() - self.play(ShowCreation(cross)) - self.play( - FadeIn(telepathy), - VGroup(disease, cross).shift, 2*UP - ) - self.wait() - -class PlaneCrashProbability(Scene): - def construct(self): - plane_prob = TexMobject( - "P(\\text{Dying in a }", "\\text{plane}", "\\text{ crash})", - "\\approx", "1/", "11{,}000{,}000" - ) - plane_prob.set_color_by_tex("plane", BLUE) - car_prob = TexMobject( - "P(\\text{Dying in a }", "\\text{car}", "\\text{ crash})", - "\\approx", "1/", "5{,}000" - ) - car_prob.set_color_by_tex("car", YELLOW) - plane_prob.shift(UP) - car_prob.shift( - plane_prob.get_part_by_tex("approx").get_center() -\ - car_prob.get_part_by_tex("approx").get_center() +\ - DOWN - ) - - self.play(Write(plane_prob)) - self.wait(2) - self.play(ReplacementTransform( - plane_prob.copy(), car_prob - )) - self.wait(2) - -class IntroduceTelepathyExample(StatisticsVsEmpathy): - def construct(self): - self.force_skipping() - - self.show_mind_reading_powers() - self.claim_mind_reading_powers() - self.generate_random_number() - self.guess_number_correctly() - self.ask_about_chances() - self.revert_to_original_skipping_status() - self.say_you_probably_got_lucky() - - def show_mind_reading_powers(self): - randy, morty = self.randy, self.morty - title = TextMobject("1 in 1{,}000 read minds") - title.to_edge(UP) - - self.add(title) - self.read_mind(randy, morty) - - self.title = title - - def claim_mind_reading_powers(self): - randy, morty = self.randy, self.morty - self.play(PiCreatureSays( - randy, "I have the gift.", - run_time = 1, - look_at_arg = morty.eyes, - )) - self.wait() - self.play(RemovePiCreatureBubble( - randy, - target_mode = "happy", - look_at_arg = morty.eyes - )) - - def generate_random_number(self): - morty = self.morty - bubble = morty.get_bubble("", direction = LEFT) - numbers = [ - Integer(random.choice(list(range(100)))) - for x in range(30) - ] - numbers.append(Integer(67)) - for number in numbers: - number.next_to(morty, UP, LARGE_BUFF, RIGHT) - - - for number in numbers: - self.add(number) - Scene.wait(self, 0.1) - self.remove(number) - self.play( - ShowCreation(bubble), - number.move_to, bubble.get_bubble_center(), DOWN+LEFT, - morty.change, "pondering", - ) - self.wait() - - morty.bubble = bubble - self.number = number - - def guess_number_correctly(self): - randy, morty = self.randy, self.morty - number_copy = self.number.copy() - - self.read_mind(randy, morty) - self.play(PiCreatureSays( - randy, number_copy, - target_mode = "hooray", - look_at_arg = morty.eyes - )) - self.wait() - - def ask_about_chances(self): - probability = TexMobject( - "P(", "\\text{Telepath }", "|", "\\text{ Correct}", ")", - "=", "???" - ) - probability.set_color_by_tex("Telepath", BLUE) - probability.set_color_by_tex("Correct", GREEN) - probability.to_edge(UP) - - self.play(morty.change, "confused", randy.eyes) - self.wait() - self.play(ReplacementTransform( - self.title, VGroup(*it.chain(*probability)) - )) - self.wait() - - def say_you_probably_got_lucky(self): - randy, morty = self.randy, self.morty - - self.play( - PiCreatureSays( - morty, "You probably \\\\ got lucky.", - target_mode = "sassy", - look_at_arg = randy.eyes, - bubble_kwargs = {"height" : 3, "width" : 4} - ), - RemovePiCreatureBubble( - randy, - target_mode = "plain", - look_at_arg = morty.eyes, - ) - ) - self.wait(2) - - - ### - - def read_mind(self, pi1, pi2): - self.play(pi1.change, "telepath", pi2.eyes) - self.send_mind_waves(pi1, pi2) - self.send_mind_waves(pi2, pi1) - self.wait() - - def send_mind_waves(self, pi1, pi2): - angle = np.pi/3 - n_arcs = 5 - vect = pi2.eyes.get_center() - pi1.eyes.get_center() - vect[1] = 0 - - arc = Arc(angle = angle) - arc.rotate(-angle/2 + angle_of_vector(vect), about_point = ORIGIN) - arc.scale(3) - arcs = VGroup(*[arc.copy() for x in range(n_arcs)]) - arcs.move_to(pi2.eyes.get_center(), vect) - arcs.set_stroke(BLACK, 0) - for arc in arcs: - arc.save_state() - arcs.scale(0.1) - arcs.move_to(pi1.eyes, vect) - arcs.set_stroke(WHITE, 4) - - self.play(LaggedStartMap( - ApplyMethod, arcs, - lambda m : (m.restore,), - lag_ratio = 0.7, - )) - self.remove(arcs) - -class CompareNumbersInBothExamples(Scene): - def construct(self): - v_line = Line(UP, DOWN).scale(FRAME_Y_RADIUS) - v_line.shift(MED_LARGE_BUFF*LEFT) - h_line = Line(LEFT, RIGHT).scale(FRAME_X_RADIUS) - h_line.to_edge(UP, buff = 1.25*LARGE_BUFF) - titles = VGroup() - for word, vect in ("Disease", LEFT), ("Telepathy", RIGHT): - title = TextMobject("%s example"%word) - title.shift(vect*FRAME_X_RADIUS/2.0) - title.to_edge(UP) - titles.add(title) - priors = VGroup(*[ - TexMobject( - "P(", "\\text{%s}"%s, ")", "= 1/1{,}000}" - ) - for s in ("Sick", "Powers") - ]) - likelihoods = VGroup(*[ - TexMobject( - "P(", "\\text{%s}"%s1, "|", - "\\text{Not }", "\\text{%s}"%s2, ")", - "=", "1/100" - ) - for s1, s2 in [("+", "Sick"), ("Correct", "Powers")] - ]) - priors.next_to(likelihoods, UP, LARGE_BUFF) - for group in priors, likelihoods: - for mob, vect in zip(group, [LEFT, RIGHT]): - mob.set_color_by_tex("Sick", BLUE) - mob.set_color_by_tex("Powers", BLUE) - mob.set_color_by_tex("+", GREEN) - mob.set_color_by_tex("Correct", GREEN) - mob.scale(0.8) - mob.shift(vect*FRAME_X_RADIUS/2) - - self.play( - LaggedStartMap(FadeIn, titles, lag_ratio = 0.7), - *list(map(ShowCreation, [h_line, v_line])) - ) - self.wait() - self.play(FadeIn(priors)) - self.wait() - self.play(FadeIn(likelihoods)) - self.wait(3) - - -class NonchalantReactionToPositiveTest(TestScene): - def construct(self): - randy = self.pi_creature - randy.shift(DOWN+2*RIGHT) - result = self.get_positive_result(randy) - accuracy = TextMobject("99\\% Accuracy") - accuracy.set_color(YELLOW) - accuracy.next_to(result, DOWN, LARGE_BUFF, RIGHT) - - self.add(accuracy) - self.play(Write(result, run_time = 2)) - self.play(randy.change, "pondering", result) - self.wait() - words = TextMobject("Pssht, I'm probably fine.") - words.scale(0.8) - self.pi_creature_says( - words, - target_mode = "shruggie", - bubble_kwargs = { - "direction" : RIGHT, - "width" : 6, - "height" : 3, - }, - content_introduction_class = FadeIn, - ) - self.wait(4) - -class OneInOneThousandHaveDiseaseCopy(OneInOneThousandHaveDisease): - pass - -class ExampleMeasuresDisbeliefInStatistics(Introduction): - def construct(self): - self.teacher.shift(LEFT) - self.hold_up_example() - self.write_counter_intuitive() - self.write_new_theory() - self.either_way() - - def write_new_theory(self): - bayes_to_intuition = self.bayes_to_intuition - cross = bayes_to_intuition[-1] - bayes_to_intuition.remove(cross) - statistics_to_belief = TextMobject( - "Statistics ", "$\\leftrightarrow$", " Belief" - ) - statistics_to_belief.scale(0.8) - arrow = statistics_to_belief.get_part_by_tex("arrow") - statistics_to_belief.next_to(self.example, UP) - - self.revert_to_original_skipping_status() - self.play(bayes_to_intuition.to_edge, UP) - self.play( - LaggedStartMap(FadeIn, statistics_to_belief), - cross.move_to, arrow - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = statistics_to_belief - ) - self.wait(3) - - statistics_to_belief.add(cross) - self.statistics_to_belief = statistics_to_belief - - def either_way(self): - b_to_i = self.bayes_to_intuition - s_to_b = self.statistics_to_belief - - self.play(FadeOut(self.example)) - self.play( - self.teacher.change_mode, "raise_left_hand", - self.teacher.look, UP+RIGHT, - b_to_i.next_to, self.teacher, UP, - b_to_i.to_edge, RIGHT, MED_SMALL_BUFF, - ) - self.wait(2) - self.play( - self.teacher.change_mode, "raise_right_hand", - self.teacher.look, UP+LEFT, - s_to_b.next_to, self.teacher, UP, - s_to_b.shift, LEFT, - b_to_i.shift, 2*UP - ) - self.wait(2) - -class AlwaysPictureTheSpaceOfPossibilities(PiCreatureScene): - def construct(self): - self.pi_creature_thinks( - "", bubble_kwargs = { - "height" : 4.5, - "width" : 8, - } - ) - self.wait(3) - - def create_pi_creature(self): - return Randolph().to_corner(DOWN+LEFT) - -class Thumbnail(Scene): - def construct(self): - title = TextMobject("Why is this \\\\ counterintuitive?") - title.scale(2) - title.to_edge(UP) - - randy = Randolph(mode = "sick", color = SICKLY_GREEN) - # randy.look_at(title) - randy.scale(1.5) - randy.shift(3*LEFT) - randy.to_edge(DOWN) - - prob = TexMobject("P(", "\\text{Sick}", "|", "\\text{Test+}", ")") - prob.scale(2) - prob.set_color_by_tex("Sick", YELLOW) - prob.set_color_by_tex("Test", GREEN) - prob.next_to(randy, RIGHT) - - self.add(title, randy, prob) - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/birthday.py b/from_3b1b/on_hold/eop/birthday.py deleted file mode 100644 index 78126eff..00000000 --- a/from_3b1b/on_hold/eop/birthday.py +++ /dev/null @@ -1,32 +0,0 @@ -from manimlib.imports import * - -class Birthday(Scene): - - def construct(self): - - sidelength = 6.0 - corner = np.array([-sidelength/2,-sidelength/2,0]) - nb_days_left = 365.0 - toggle = False - - def probability(): - width = rect.get_width() - height = rect.get_height() - return width * height / sidelength**2 - - rect = Square().scale(sidelength/2) - - while probability() > 0.5: - - self.add(rect.copy()) - nb_days_left -= 1 - - if toggle: - dim = 0 - else: - dim = 1 - - rect.stretch_about_point(nb_days_left / 365, dim, corner) - - toggle = not toggle - diff --git a/from_3b1b/on_hold/eop/chapter0.py b/from_3b1b/on_hold/eop/chapter0.py deleted file mode 100644 index 5a6015ed..00000000 --- a/from_3b1b/on_hold/eop/chapter0.py +++ /dev/null @@ -1,82 +0,0 @@ -from manimlib.imports import * - -class Introduction(TeacherStudentsScene): - - CONFIG = { - "default_pi_creature_kwargs": { - "color": MAROON_E, - "flip_at_start": True, - }, - } - - def construct(self): - self.show_series() - self.show_examples() - - def show_series(self): - series = VideoSeries(num_videos = 11) - series.to_edge(UP) - this_video = series[0] - this_video.set_color(YELLOW) - this_video.save_state() - this_video.set_fill(opacity = 0) - this_video.center() - this_video.set_height(FRAME_HEIGHT) - self.this_video = this_video - - - words = TextMobject( - "Welcome to \\\\", - "Essence of Probability" - ) - words.set_color_by_tex("Essence of Probability", YELLOW) - - self.teacher.change_mode("happy") - self.play( - FadeIn( - series, - lag_ratio = 0.5, - run_time = 2 - ), - Blink(self.get_teacher()) - ) - self.teacher_says(words, target_mode = "hooray") - self.change_student_modes( - *["hooray"]*3, - look_at_arg = series[1].get_left(), - added_anims = [ - ApplyMethod(this_video.restore, run_time = 3), - ] - ) - self.play(*[ - ApplyMethod( - video.shift, 0.5*video.get_height()*DOWN, - run_time = 3, - rate_func = squish_rate_func( - there_and_back, alpha, alpha+0.3 - ) - ) - for video, alpha in zip(series, np.linspace(0, 0.7, len(series))) - ]+[ - Animation(self.teacher.bubble), - Animation(self.teacher.bubble.content), - ]) - - self.play( - FadeOut(self.teacher.bubble), - FadeOut(self.teacher.bubble.content), - self.get_teacher().change_mode, "raise_right_hand", - *[ - ApplyMethod(pi.change_mode, "pondering") - for pi in self.get_students() - ] - ) - self.wait() - - self.series = series - - - def show_examples(self): - - self.wait(10) - # put examples here in video editor diff --git a/from_3b1b/on_hold/eop/chapter0/intro.py b/from_3b1b/on_hold/eop/chapter0/intro.py deleted file mode 100644 index 5a6015ed..00000000 --- a/from_3b1b/on_hold/eop/chapter0/intro.py +++ /dev/null @@ -1,82 +0,0 @@ -from manimlib.imports import * - -class Introduction(TeacherStudentsScene): - - CONFIG = { - "default_pi_creature_kwargs": { - "color": MAROON_E, - "flip_at_start": True, - }, - } - - def construct(self): - self.show_series() - self.show_examples() - - def show_series(self): - series = VideoSeries(num_videos = 11) - series.to_edge(UP) - this_video = series[0] - this_video.set_color(YELLOW) - this_video.save_state() - this_video.set_fill(opacity = 0) - this_video.center() - this_video.set_height(FRAME_HEIGHT) - self.this_video = this_video - - - words = TextMobject( - "Welcome to \\\\", - "Essence of Probability" - ) - words.set_color_by_tex("Essence of Probability", YELLOW) - - self.teacher.change_mode("happy") - self.play( - FadeIn( - series, - lag_ratio = 0.5, - run_time = 2 - ), - Blink(self.get_teacher()) - ) - self.teacher_says(words, target_mode = "hooray") - self.change_student_modes( - *["hooray"]*3, - look_at_arg = series[1].get_left(), - added_anims = [ - ApplyMethod(this_video.restore, run_time = 3), - ] - ) - self.play(*[ - ApplyMethod( - video.shift, 0.5*video.get_height()*DOWN, - run_time = 3, - rate_func = squish_rate_func( - there_and_back, alpha, alpha+0.3 - ) - ) - for video, alpha in zip(series, np.linspace(0, 0.7, len(series))) - ]+[ - Animation(self.teacher.bubble), - Animation(self.teacher.bubble.content), - ]) - - self.play( - FadeOut(self.teacher.bubble), - FadeOut(self.teacher.bubble.content), - self.get_teacher().change_mode, "raise_right_hand", - *[ - ApplyMethod(pi.change_mode, "pondering") - for pi in self.get_students() - ] - ) - self.wait() - - self.series = series - - - def show_examples(self): - - self.wait(10) - # put examples here in video editor diff --git a/from_3b1b/on_hold/eop/chapter1/all_sequences.py b/from_3b1b/on_hold/eop/chapter1/all_sequences.py deleted file mode 100644 index 97ff42e0..00000000 --- a/from_3b1b/on_hold/eop/chapter1/all_sequences.py +++ /dev/null @@ -1,64 +0,0 @@ - -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - -class ShuffleThroughAllSequences(Scene): - CONFIG = { - "nb_coins" : 20, - "run_time" : 5, - "fps" : int(1.0/PRODUCTION_QUALITY_FRAME_DURATION), - "coin_size" : 0.5, - "coin_spacing" : 0.65 - } - - - def construct(self): - - nb_frames = self.run_time * self.fps - nb_relevant_coins = int(np.log2(nb_frames)) + 1 - print("relevant coins:", nb_relevant_coins) - nb_idle_coins = self.nb_coins - nb_relevant_coins - - idle_heads = CoinSequence(nb_idle_coins * ["H"], - radius = self.coin_size * 0.5, - spacing = self.coin_spacing) - idle_tails = CoinSequence(nb_idle_coins * ["T"], - radius = self.coin_size * 0.5, - spacing = self.coin_spacing) - idle_tails.fade(0.5) - - idle_part = VGroup(idle_heads, idle_tails) - left_idle_part = CoinSequence(6 * ["H"], - radius = self.coin_size * 0.5, - spacing = self.coin_spacing) - - #self.add(idle_part, left_idle_part) - self.add(left_idle_part) - last_coin_seq = VGroup() - - for i in range(2**nb_relevant_coins): - binary_seq = binary(i) - # pad to the left with 0s - nb_leading_zeroes = nb_relevant_coins - len(binary_seq) - for j in range(nb_leading_zeroes): - binary_seq.insert(0, 0) - seq2 = ["H" if x == 0 else "T" for x in binary_seq] - coin_seq = CoinSequence(seq2, - radius = self.coin_size * 0.5, - spacing = self.coin_spacing) - coin_seq.next_to(idle_part, LEFT, buff = self.coin_spacing - self.coin_size) - left_idle_part.next_to(coin_seq, LEFT, buff = self.coin_spacing - self.coin_size) - all_coins = VGroup(left_idle_part, coin_seq) #, idle_part) - all_coins.center() - self.remove(last_coin_seq) - self.add(coin_seq) - #self.wait(1.0/self.fps) - self.update_frame() - self.add_frames(self.get_frame()) - last_coin_seq = coin_seq - print(float(i)/2**nb_relevant_coins) - - - - - diff --git a/from_3b1b/on_hold/eop/chapter1/area_model_bayes.py b/from_3b1b/on_hold/eop/chapter1/area_model_bayes.py deleted file mode 100644 index 3cb0bc1b..00000000 --- a/from_3b1b/on_hold/eop/chapter1/area_model_bayes.py +++ /dev/null @@ -1,228 +0,0 @@ -from manimlib.imports import * - - -class IllustrateAreaModelBayes(Scene): - - def construct(self): - - color_A = YELLOW - color_not_A = YELLOW_E - color_B = MAROON - color_not_B = MAROON_E - opacity_B = 0.7 - - - # show independent events - - sample_space_width = sample_space_height = 3 - p_of_A = 0.7 - p_of_not_A = 1 - p_of_A - p_of_B = 0.8 - p_of_not_B = 1 - p_of_B - - - rect_A = Rectangle( - width = p_of_A * sample_space_width, - height = 1 * sample_space_height, - stroke_width = 0, - fill_color = color_A, - fill_opacity = 1.0 - ).move_to(3 * RIGHT + 1.5 * UP) - - rect_not_A = Rectangle( - width = p_of_not_A * sample_space_width, - height = 1 * sample_space_height, - stroke_width = 0, - fill_color = color_not_A, - fill_opacity = 1.0 - ).next_to(rect_A, RIGHT, buff = 0) - - brace_A = Brace(rect_A, DOWN) - label_A = TexMobject("P(A)").next_to(brace_A, DOWN).scale(0.7) - brace_not_A = Brace(rect_not_A, DOWN) - label_not_A = TexMobject("P(\\text{not }A)").next_to(brace_not_A, DOWN).scale(0.7) - - # self.play( - # LaggedStartMap(FadeIn, VGroup(rect_A, rect_not_A)) - # ) - # self.play( - # ShowCreation(brace_A), - # Write(label_A), - # ) - - - - rect_B = Rectangle( - width = 1 * sample_space_width, - height = p_of_B * sample_space_height, - stroke_width = 0, - fill_color = color_B, - fill_opacity = opacity_B - ) - rect_not_B = Rectangle( - width = 1 * sample_space_width, - height = p_of_not_B * sample_space_height, - stroke_width = 0, - fill_color = color_not_B, - fill_opacity = opacity_B - ).next_to(rect_B, UP, buff = 0) - - VGroup(rect_B, rect_not_B).move_to(VGroup(rect_A, rect_not_A)) - - brace_B = Brace(rect_B, LEFT) - label_B = TexMobject("P(B)").next_to(brace_B, LEFT).scale(0.7) - brace_not_B = Brace(rect_not_B, LEFT) - label_not_B = TexMobject("P(\\text{not }B)").next_to(brace_not_B, LEFT).scale(0.7) - - # self.play( - # LaggedStartMap(FadeIn, VGroup(rect_B, rect_not_B)) - # ) - # self.play( - # ShowCreation(brace_B), - # Write(label_B), - # ) - - rect_A_and_B = Rectangle( - width = p_of_A * sample_space_width, - height = p_of_B * sample_space_height, - stroke_width = 3, - fill_opacity = 0.0 - ).align_to(rect_A, DOWN).align_to(rect_A,LEFT) - label_A_and_B = TexMobject("P(A\\text{ and }B)").scale(0.7) - label_A_and_B.move_to(rect_A_and_B) - - # self.play( - # ShowCreation(rect_A_and_B) - # ) - - indep_formula = TexMobject("P(A\\text{ and }B)", "=", "P(A)", "\cdot", "P(B)") - indep_formula = indep_formula.scale(0.7) - label_p_of_b = indep_formula.get_part_by_tex("P(B)") - - label_A_and_B_copy = label_A_and_B.copy() - label_A_copy = label_A.copy() - label_B_copy = label_B.copy() - # self.add(label_A_and_B_copy, label_A_copy, label_B_copy) - - # self.play(Transform(label_A_and_B_copy, indep_formula[0])) - # self.play(FadeIn(indep_formula[1])) - # self.play(Transform(label_A_copy, indep_formula[2])) - # self.play(FadeIn(indep_formula[3])) - # self.play(Transform(label_B_copy, indep_formula[4])) - - #self.wait() - - label_A_and_B_copy = indep_formula[0] - label_A_copy = indep_formula[2] - label_B_copy = indep_formula[4] - - # show conditional prob - - rect_A_and_B.set_fill(color = RED, opacity = 0.5) - rect_A_and_not_B = Rectangle( - width = p_of_A * sample_space_width, - height = p_of_not_B * sample_space_height, - stroke_width = 0, - fill_color = color_not_B, - fill_opacity = opacity_B - ).next_to(rect_A_and_B, UP, buff = 0) - - rect_not_A_and_B = Rectangle( - width = p_of_not_A * sample_space_width, - height = p_of_B * sample_space_height, - stroke_width = 0, - fill_color = color_B, - fill_opacity = opacity_B - ).next_to(rect_A_and_B, RIGHT, buff = 0) - - rect_not_A_and_not_B = Rectangle( - width = p_of_not_A * sample_space_width, - height = p_of_not_B * sample_space_height, - stroke_width = 0, - fill_color = color_not_B, - fill_opacity = opacity_B - ).next_to(rect_not_A_and_B, UP, buff = 0) - - - indep_formula.next_to(rect_not_A, LEFT, buff = 5) - #indep_formula.shift(UP) - - self.play(Write(indep_formula)) - - self.play( - FadeIn(VGroup( - rect_A, rect_not_A, brace_A, label_A, brace_B, label_B, - rect_A_and_not_B, rect_not_A_and_B, rect_not_A_and_not_B, - rect_A_and_B, - label_A_and_B, - )) - ) - - self.wait() - - - p_of_B_knowing_A = 0.6 - rect_A_and_B.target = Rectangle( - width = p_of_A * sample_space_width, - height = p_of_B_knowing_A * sample_space_height, - stroke_width = 3, - fill_color = color_B, - fill_opacity = opacity_B - ).align_to(rect_A_and_B, DOWN).align_to(rect_A_and_B, LEFT) - - rect_A_and_not_B.target = Rectangle( - width = p_of_A * sample_space_width, - height = (1 - p_of_B_knowing_A) * sample_space_height, - stroke_width = 0, - fill_color = color_not_B, - fill_opacity = opacity_B - ).next_to(rect_A_and_B.target, UP, buff = 0) - - brace_B.target = Brace(rect_A_and_B.target, LEFT) - label_B.target = TexMobject("P(B\mid A)").scale(0.7).next_to(brace_B.target, LEFT) - - - self.play( - MoveToTarget(rect_A_and_B), - MoveToTarget(rect_A_and_not_B), - MoveToTarget(brace_B), - MoveToTarget(label_B), - label_A_and_B.move_to,rect_A_and_B.target - ) - label_B_knowing_A = label_B - - #self.play(FadeOut(label_B_copy)) - self.remove(indep_formula.get_part_by_tex("P(B)")) - indep_formula.remove(indep_formula.get_part_by_tex("P(B)")) - label_B_knowing_A_copy = label_B_knowing_A.copy() - self.add(label_B_knowing_A_copy) - - self.play( - label_B_knowing_A_copy.next_to, indep_formula.get_part_by_tex("\cdot"), RIGHT, - ) - - # solve formula for P(B|A) - - rearranged_formula = TexMobject("P(B\mid A)", "=", "{P(A\\text{ and }B) \over P(A)}") - rearranged_formula.move_to(indep_formula) - - self.wait() - - - self.play( - # in some places get_part_by_tex does not find the correct part - # so I picked out fitting indices - label_B_knowing_A_copy.move_to, rearranged_formula.get_part_by_tex("P(B\mid A)"), - label_A_copy.move_to, rearranged_formula[-1][10], - label_A_and_B_copy.move_to, rearranged_formula[-1][3], - indep_formula.get_part_by_tex("=").move_to, rearranged_formula.get_part_by_tex("="), - Transform(indep_formula.get_part_by_tex("\cdot"), rearranged_formula[2][8]), - ) - - rect = SurroundingRectangle(rearranged_formula, buff = 0.5 * MED_LARGE_BUFF) - self.play(ShowCreation(rect)) - - - self.wait() - - diff --git a/from_3b1b/on_hold/eop/chapter1/area_model_erf.py b/from_3b1b/on_hold/eop/chapter1/area_model_erf.py deleted file mode 100644 index 642f2ddb..00000000 --- a/from_3b1b/on_hold/eop/chapter1/area_model_erf.py +++ /dev/null @@ -1,113 +0,0 @@ - -from manimlib.imports import * -from from_3b1b.old.eoc.chapter8 import * - -import scipy.special - -class IllustrateAreaModelErf(GraphScene): - CONFIG = { - "x_min" : -3.0, - "x_max" : 3.0, - "y_min" : 0, - "y_max" : 1.0, - "num_rects": 400, - "y_axis_label" : "", - "x_axis_label" : "", - "variable_point_label" : "a", - "graph_origin": 2.5 * DOWN + 4 * RIGHT, - "x_axis_width": 5, - "y_axis_height": 5 - } - - def construct(self): - - # integral bounds - x_min_1 = -0.0001 - x_max_1 = 0.0001 - - x_min_2 = self.x_min - x_max_2 = self.x_max - - self.setup_axes() - self.remove(self.x_axis, self.y_axis) - graph = self.get_graph(lambda x: np.exp(-x**2) * 2.0 / TAU ** 0.5) - area = self.area = self.get_area(graph, x_min_1, x_max_1) - - - pdf_formula = TexMobject("p(x) = {1\over \sigma\sqrt{2\pi}}e^{-{1\over 2}({x\over\sigma})^2}") - pdf_formula.set_color(graph.color) - - cdf_formula = TexMobject("P(|X| < ", "a", ") = \int", "_{-a}", "^a", "p(x) dx") - cdf_formula.set_color_by_tex("a", YELLOW) - cdf_formula.next_to(graph, LEFT, buff = 2) - pdf_formula.next_to(cdf_formula, UP) - - formulas = VGroup(pdf_formula, cdf_formula) - self.play(Write(pdf_formula)) - self.play(Write(cdf_formula)) - - self.wait() - - - self.play(ShowCreation(self.x_axis)) - self.play(ShowCreation(graph)) - self.play(FadeIn(area)) - - self.v_graph = graph - self.add_T_label( - x_min_1, - label = "-a", - side = LEFT, - color = YELLOW, - animated = False - ) - self.add_T_label( - x_max_1, - label = "a", - side = RIGHT, - color = YELLOW, - animated = False - ) - # don't show the labels just yet - self.remove( - self.left_T_label_group[0], - self.right_T_label_group[0], - ) - - def integral_update_func(t): - return scipy.special.erf( - self.point_to_coords(self.right_v_line.get_center())[0] - ) - - def integral_update_func_percent(t): - return 100 * integral_update_func(t) - - equals_sign = TexMobject("=").next_to(cdf_formula, buff = MED_LARGE_BUFF) - - cdf_value = DecimalNumber(0, color = graph.color, num_decimal_places = 3) - cdf_value.next_to(equals_sign) - self.play( - FadeIn(equals_sign), - FadeIn(cdf_value) - ) - self.add_foreground_mobject(cdf_value) - - cdf_percentage = DecimalNumber(0, unit = "\\%") - cdf_percentage.move_to(self.coords_to_point(0,0.2)) - self.add_foreground_mobject(cdf_percentage) - - cdf_value.add_updater( - lambda m: m.set_value(integral_update_func()) - ) - - anim = self.get_animation_integral_bounds_change( - graph, x_min_2, x_max_2, - run_time = 3) - - - self.play( - anim - ) - - rect = SurroundingRectangle(formulas, buff = 0.5 * MED_LARGE_BUFF) - self.play(ShowCreation(rect)) diff --git a/from_3b1b/on_hold/eop/chapter1/area_model_expectation.py b/from_3b1b/on_hold/eop/chapter1/area_model_expectation.py deleted file mode 100644 index 345324b4..00000000 --- a/from_3b1b/on_hold/eop/chapter1/area_model_expectation.py +++ /dev/null @@ -1,93 +0,0 @@ - -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - - -class IllustrateAreaModelExpectation(Scene): - - def construct(self): - - formula = TexMobject("E[X] = \sum_{i=1}^N p_i x_i").move_to(3 * LEFT + UP) - self.play(Write(formula)) - - - x_scale = 5.0 - y_scale = 1.0 - - probabilities = np.array([1./8, 3./8, 3./8, 1./8]) - prob_strings = ["{1\over 8}","{3\over 8}","{3\over 8}","{1\over 8}"] - cumulative_probabilities = np.cumsum(probabilities) - cumulative_probabilities = np.insert(cumulative_probabilities, 0, 0) - y_values = np.array([0, 1, 2, 3]) - - hist = Histogram(probabilities, y_values, - mode = "widths", - x_scale = x_scale, - y_scale = y_scale, - x_labels = "none" - ) - - flat_hist = Histogram(probabilities, 0 * y_values, - mode = "widths", - x_scale = x_scale, - y_scale = y_scale, - x_labels = "none" - ) - - self.play(FadeIn(flat_hist)) - self.play( - ReplacementTransform(flat_hist, hist) - ) - - braces = VGroup() - p_labels = VGroup() - # add x labels (braces) - for (p,string,bar) in zip(probabilities, prob_strings,hist.bars): - brace = Brace(bar, DOWN, buff = 0.1) - p_label = TexMobject(string).next_to(brace, DOWN, buff = SMALL_BUFF).scale(0.7) - group = VGroup(brace, p_label) - braces.add(brace) - p_labels.add(p_label) - - self.play( - LaggedStartMap(FadeIn,braces), - LaggedStartMap(FadeIn, p_labels) - ) - - - - y_average = np.mean(y_values) - averaged_y_values = y_average * np.ones(np.shape(y_values)) - - averaged_hist = flat_hist = Histogram(probabilities, averaged_y_values, - mode = "widths", - x_scale = x_scale, - y_scale = y_scale, - x_labels = "none", - y_labels = "none" - ).fade(0.2) - - ghost_hist = hist.copy().fade(0.8) - self.bring_to_back(ghost_hist) - - self.play(Transform(hist, averaged_hist, run_time = 3)) - self.wait() - - average_brace = Brace(averaged_hist, RIGHT, buff = 0.1) - average_label = TexMobject(str(y_average)).scale(0.7) - average_label.next_to(average_brace, RIGHT, SMALL_BUFF) - average_group = VGroup(average_brace, average_label) - - one_brace = Brace(averaged_hist, DOWN, buff = 0.1) - one_p_label = TexMobject(str(1)).next_to(one_brace, DOWN, buff = SMALL_BUFF).scale(0.7) - one_group = VGroup(one_brace, one_p_label) - - self.play( - FadeIn(average_group), - Transform(braces, one_brace), - Transform(p_labels, one_p_label), - ) - - rect = SurroundingRectangle(formula, buff = 0.5 * MED_LARGE_BUFF) - self.play(ShowCreation(rect)) - diff --git a/from_3b1b/on_hold/eop/chapter1/brick_row_scene.py b/from_3b1b/on_hold/eop/chapter1/brick_row_scene.py deleted file mode 100644 index 758fd57e..00000000 --- a/from_3b1b/on_hold/eop/chapter1/brick_row_scene.py +++ /dev/null @@ -1,1175 +0,0 @@ -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - - -class BrickRowScene(PiCreatureScene): - - - def split_tallies(self, row, direction = DOWN): - # Split all tally symbols at once and move the copies - # either horizontally on top of the brick row - # or diagonally into the bricks - - self.tallies_copy = self.tallies.copy() - self.add_foreground_mobject(self.tallies_copy) - - tally_targets_left = [ - rect.get_center() + 0.25 * rect.get_width() * LEFT - for rect in row.rects - ] - - tally_targets_right = [ - rect.get_center() + 0.25 * rect.get_width() * RIGHT - for rect in row.rects - ] - - if np.all(direction == LEFT) or np.all(direction == RIGHT): - - tally_y_pos = self.tallies[0].anchor[1] - for target in tally_targets_left: - target[1] = tally_y_pos - for target in tally_targets_right: - target[1] = tally_y_pos - - for (i, tally) in enumerate(self.tallies): - - target_left = tally_targets_left[i] - new_tally_left = TallyStack(tally.nb_heads + 1, tally.nb_tails) - new_tally_left.move_anchor_to(target_left) - v = target_left - tally.anchor - - self.play( - tally.move_anchor_to, target_left, - ) - tally.anchor = target_left - self.play(Transform(tally, new_tally_left)) - - tally_copy = self.tallies_copy[i] - - target_right = tally_targets_right[i] - new_tally_right = TallyStack(tally.nb_heads, tally.nb_tails + 1) - new_tally_right.move_anchor_to(target_right) - v = target_right - tally_copy.anchor - - self.play(tally_copy.move_anchor_to, target_right) - tally_copy.anchor = target_right - self.play(Transform(tally_copy, new_tally_right)) - - tally_copy.nb_heads = new_tally_right.nb_heads - tally_copy.nb_tails = new_tally_right.nb_tails - tally.nb_heads = new_tally_left.nb_heads - tally.nb_tails = new_tally_left.nb_tails - - - - - def tally_split_animations(self, row, direction = DOWN): - # Just creates the animations and returns them - # Execution can be timed afterwards - # Returns two lists: first all those going left, then those to the right - - self.tallies_copy = self.tallies.copy() - self.add_foreground_mobject(self.tallies_copy) - - tally_targets_left = [ - rect.get_center() + 0.25 * rect.get_width() * LEFT - for rect in row.rects - ] - - tally_targets_right = [ - rect.get_center() + 0.25 * rect.get_width() * RIGHT - for rect in row.rects - ] - - if np.all(direction == LEFT) or np.all(direction == RIGHT): - - tally_y_pos = self.tallies[0].anchor[1] - for target in tally_targets_left: - target[1] = tally_y_pos - for target in tally_targets_right: - target[1] = tally_y_pos - - - anims1 = [] - - for (i, tally) in enumerate(self.tallies): - - new_tally_left = TallyStack(tally.nb_heads + 1, tally.nb_tails) - target_left = tally_targets_left[i] - new_tally_left.move_to(target_left) - - anims1.append(Transform(tally, new_tally_left)) - - tally.anchor = target_left - tally.nb_heads = new_tally_left.nb_heads - tally.nb_tails = new_tally_left.nb_tails - - - anims2 = [] - - for (i, tally) in enumerate(self.tallies_copy): - - new_tally_right = TallyStack(tally.nb_heads, tally.nb_tails + 1) - target_right = tally_targets_right[i] - new_tally_right.move_to(target_right) - - anims2.append(Transform(tally, new_tally_right)) - - tally.anchor = target_right - tally.nb_heads = new_tally_right.nb_heads - tally.nb_tails = new_tally_right.nb_tails - - return anims1, anims2 - - - - def split_tallies_at_once(self, row, direction = DOWN): - anims1, anims2 = self.tally_split_animations(row, direction = direction) - self.play(*(anims1 + anims2)) - - def split_tallies_in_two_steps(self, row, direction = DOWN): - # First all those to the left, then those to the right - anims1, anims2 = self.tally_split_animations(row, direction = direction) - self.play(*anims1) - self.wait(0.3) - self.play(*anims2) - - - - - def merge_rects_by_subdiv(self, row): - - half_merged_row = row.copy() - half_merged_row.subdiv_level += 1 - half_merged_row.init_points() - half_merged_row.move_to(row) - - self.play(FadeIn(half_merged_row)) - self.remove(row) - return half_merged_row - - - - - def merge_tallies(self, row, target_pos = UP): - - r = row.subdiv_level - - if np.all(target_pos == DOWN): - tally_targets = [ - rect.get_center() - for rect in row.get_rects_for_level(r) - ] - elif np.all(target_pos == UP): - y_pos = row.get_center()[1] + 1.2 * 0.5 * row.get_height() - for target in tally_targets: - target[1] = y_pos - else: - raise Exception("Invalid target position (either UP or DOWN)") - - anims = [] - for (tally, target) in zip(self.tallies[1:], tally_targets[1:-1]): - anims.append(tally.move_anchor_to) - anims.append(target) - - for (tally, target) in zip(self.tallies_copy[:-1], tally_targets[1:-1]): - anims.append(tally.move_anchor_to) - anims.append(target) - - self.play(*anims) - # update anchors - for (tally, target) in zip(self.tallies[1:], tally_targets[1:-1]): - tally.anchor = target - for (tally, target) in zip(self.tallies_copy[:-1], tally_targets[1:-1]): - tally.anchor = target - - self.remove(self.tallies_copy) - self.tallies.add(self.tallies_copy[-1]) - - - def merge_rects_by_coloring(self, row): - - merged_row = row.copy() - merged_row.coloring_level += 1 - merged_row.init_points() - merged_row.move_to(row) - - self.play(FadeIn(merged_row)) - self.remove(row) - return merged_row - - - - def move_tallies_on_top(self, row): - self.play( - self.tallies.shift, 1.2 * 0.5 * row.height * UP - ) - for tally in self.tallies: - tally.anchor += 1.2 * 0.5 * row.height * UP - - def create_pi_creature(self): - randy = CoinFlippingPiCreature(color = MAROON_E) - return randy - - - - - - - - def construct(self): - self.force_skipping() - - randy = self.get_primary_pi_creature() - randy = randy.scale(0.5).move_to(3*DOWN + 6*LEFT) - #self.add(randy) - self.row = BrickRow(0, height = 2, width = 10) - self.wait() - - self.play(FadeIn(self.row)) - - self.wait() - - - # move in all kinds of sequences - coin_seqs = VGroup() - for i in range(20): - n = np.random.randint(1,10) - seq = [np.random.choice(["H", "T"]) for j in range(n)] - coin_seq = CoinSequence(seq).scale(1.5) - loc = np.random.uniform(low = -10, high = 10) * RIGHT - loc += np.random.uniform(low = -6, high = 6) * UP - coin_seq.move_to(loc) - - coin_seq.target = coin_seq.copy().scale(0.3).move_to(0.4 * loc) - coin_seq.target.fade(1) - coin_seqs.add(coin_seq) - - self.play( - LaggedStartMap( - Succession, coin_seqs, lambda m: (FadeIn(m, run_time = 0.1), MoveToTarget(m)), - run_time = 5, - lag_ratio = 0.5 - ) - ) - - # # # # # # # # - # FIRST FLIP # - # # # # # # # # - - - self.play(FlipCoin(randy)) - - self.play(SplitRectsInBrickWall(self.row)) - self.row = self.merge_rects_by_subdiv(self.row) - self.row = self.merge_rects_by_coloring(self.row) - # - # put tallies on top - - single_flip_labels = VGroup(UprightHeads(), UprightTails()) - for (label, rect) in zip(single_flip_labels, self.row.rects): - label.next_to(rect, UP) - self.play(FadeIn(label)) - self.wait() - - self.wait() - - - # # # # # # # # - # SECOND FLIP # - # # # # # # # # - - self.play(FlipCoin(randy)) - self.wait() - - self.play( - SplitRectsInBrickWall(self.row) - ) - self.wait() - - - # split sequences - single_flip_labels_copy = single_flip_labels.copy() - self.add(single_flip_labels_copy) - - - v = self.row.get_outcome_centers_for_level(2)[0] - single_flip_labels[0].get_center() - self.play( - single_flip_labels.shift, v - ) - new_heads = VGroup(UprightHeads(), UprightHeads()) - for i in range(2): - new_heads[i].move_to(single_flip_labels[i]) - new_heads[i].shift(COIN_SEQUENCE_SPACING * DOWN) - self.play(FadeIn(new_heads)) - - v = self.row.get_outcome_centers_for_level(2)[-1] - single_flip_labels_copy[-1].get_center() - self.play( - single_flip_labels_copy.shift, v - ) - new_tails = VGroup(UprightTails(), UprightTails()) - for i in range(2): - new_tails[i].move_to(single_flip_labels_copy[i]) - new_tails[i].shift(COIN_SEQUENCE_SPACING * DOWN) - self.play(FadeIn(new_tails)) - - self.add_foreground_mobject(single_flip_labels) - self.add_foreground_mobject(new_heads) - self.add_foreground_mobject(single_flip_labels_copy) - self.add_foreground_mobject(new_tails) - - # get individual outcomes - outcomes = self.row.get_outcome_rects_for_level(2, with_labels = False, - inset = True) - grouped_outcomes = VGroup(outcomes[0], outcomes[1:3], outcomes[3]) - - decimal_tallies = VGroup() - # introduce notion of tallies - rects = self.row.get_rects_for_level(2) - - rect = rects[0] - tally = DecimalTally(2,0) - tally.next_to(rect, UP) - decimal_tallies.add(tally) - self.play( - FadeIn(tally), - FadeIn(grouped_outcomes[0]) - ) - self.wait() - - rect = rects[1] - tally = DecimalTally(1,1) - tally.next_to(rect, UP) - decimal_tallies.add(tally) - self.play( - FadeIn(tally), - FadeOut(grouped_outcomes[0]), - FadeIn(grouped_outcomes[1]) - ) - self.wait() - - rect = rects[2] - tally = DecimalTally(0,2) - tally.next_to(rect, UP) - decimal_tallies.add(tally) - self.play( - FadeIn(tally), - FadeOut(grouped_outcomes[1]), - FadeIn(grouped_outcomes[2]) - ) - self.wait() - - self.play( - FadeOut(grouped_outcomes[2]) - ) - self.wait() - - self.wait() - self.play( - FadeOut(single_flip_labels), - FadeOut(new_heads), - FadeOut(single_flip_labels_copy), - FadeOut(new_tails) - ) - self.wait() - - self.tallies = VGroup() - for (i, rect) in enumerate(self.row.get_rects_for_level(2)): - tally = TallyStack(2-i, i, show_decimals = False) - tally.move_to(rect) - self.tallies.add(tally) - - - self.play(FadeIn(self.tallies)) - self.wait() - - - anims = [] - for (decimal_tally, tally_stack) in zip(decimal_tallies, self.tallies): - anims.append(ApplyFunction( - tally_stack.position_decimal_tally, decimal_tally - )) - - self.play(*anims) - self.wait() - - - # replace the original decimal tallies with - # the ones that belong to the TallyStacks - for (decimal_tally, tally_stack) in zip(decimal_tallies, self.tallies): - self.remove(decimal_tally) - tally_stack.position_decimal_tally(tally_stack.decimal_tally) - tally_stack.add(tally_stack.decimal_tally) - - self.add_foreground_mobject(self.tallies) - self.row = self.merge_rects_by_subdiv(self.row) - self.wait() - self.row = self.merge_rects_by_coloring(self.row) - self.wait() - - - # # # # # # # # # # # # # - # CALLBACK TO SEQUENCES # - # # # # # # # # # # # # # - - outcomes = self.row.get_outcome_rects_for_level(2, with_labels = True, - inset = True) - subdivs = self.row.get_sequence_subdivs_for_level(2) - self.play( - FadeIn(outcomes), - FadeIn(subdivs), - FadeOut(self.tallies) - ) - - self.wait() - - rect_to_dice = self.row.get_outcome_rects_for_level(2, with_labels = False, - inset = False)[1] - N = 10 - dice_width = rect_to_dice.get_width()/N - dice_height = rect_to_dice.get_height()/N - prototype_dice = Rectangle( - width = dice_width, - height = dice_height, - stroke_width = 2, - stroke_color = WHITE, - fill_color = WHITE, - fill_opacity = 0 - ) - prototype_dice.align_to(rect_to_dice, direction = UP) - prototype_dice.align_to(rect_to_dice, direction = LEFT) - all_dice = VGroup() - for i in range(N): - for j in range(N): - dice_copy = prototype_dice.copy() - dice_copy.shift(j * dice_width * RIGHT + i * dice_height * DOWN) - all_dice.add(dice_copy) - - self.play( - LaggedStartMap(FadeIn, all_dice), - FadeOut(outcomes[1]) - ) - self.wait() - - table = Ellipse(width = 1.5, height = 1) - table.set_fill(color = GREEN_E, opacity = 1) - table.next_to(rect_to_dice, UP) - self.add(table) - coin1 = UprightHeads(radius = 0.1) - coin2 = UprightTails(radius = 0.1) - - def get_random_point_in_ellipse(ellipse): - width = ellipse.get_width() - height = ellipse.get_height() - x = y = 1 - while x**2 + y**2 > 0.9: - x = np.random.uniform(-1,1) - y = np.random.uniform(-1,1) - x *= width/2 - y *= height/2 - return ellipse.get_center() + x * RIGHT + y * UP - - - for dice in all_dice: - p1 = get_random_point_in_ellipse(table) - p2 = get_random_point_in_ellipse(table) - coin1.move_to(p1) - coin2.move_to(p2) - self.add(coin1, coin2) - self.play( - ApplyMethod(dice.set_fill, {"opacity" : 0.5}, - rate_func = there_and_back, - run_time = 0.05) - ) - - self.wait() - - self.play( - FadeOut(outcomes), - FadeOut(subdivs), - FadeOut(all_dice), - FadeOut(table), - FadeOut(coin1), - FadeOut(coin2), - FadeIn(self.tallies) - ) - - self.wait() - - # # # # # # # # - # THIRD FLIP # - # # # # # # # # - - # move row up, leave a copy without tallies below - new_row = self.row.copy() - self.clear() - self.add(randy, self.row, self.tallies) - self.bring_to_back(new_row) - self.play( - self.row.shift, 2.5 * UP, - self.tallies.shift, 2.5 * UP, - ) - - old_row = self.row - self.row = new_row - - self.play(FlipCoin(randy)) - - self.wait() - - self.play( - SplitRectsInBrickWall(self.row) - ) - self.wait() - - self.split_tallies_in_two_steps(self.row) - self.wait() - - self.add_foreground_mobject(self.tallies) - self.add_foreground_mobject(self.tallies_copy) - - - - - self.remove(new_row) - new_row = self.row - - - - self.clear() - self.add(randy, self.row, old_row) - self.add_foreground_mobject(self.tallies) - self.add_foreground_mobject(self.tallies_copy) - - - self.play( - self.row.fade, 0.7, - old_row.fade, 0.7, - FadeOut(self.tallies), - FadeOut(self.tallies_copy), - ) - - - # # # # # # # # # # # # # # # # # - # SHOW SPLITTING WITH OUTCOMES # - # # # # # # # # # # # # # # # # # - - # # show individual outcomes - # old_outcomes = old_row.get_outcome_rects_for_level(2, with_labels = True) - # old_outcomes_copy = old_outcomes.copy() - # new_outcomes = self.row.get_outcome_rects_for_level(3, with_labels = True) - - # self.play( - # FadeIn(old_outcomes[0]), - # FadeIn(old_outcomes_copy[0]), - # ) - - # self.wait() - - # self.play( - # Transform(old_outcomes_copy[0], new_outcomes[1]) - # ) - - # self.wait() - - # self.play( - # FadeIn(old_outcomes[1:3]), - # FadeIn(old_outcomes_copy[1:3]), - # ) - - # self.wait() - - # self.play( - # Transform(old_outcomes_copy[1:3], new_outcomes[2:4]) - # ) - - # self.wait() - - # self.row = self.merge_rects_by_subdiv(self.row) - # self.wait() - - # self.play( - # FadeOut(old_row), - # FadeOut(old_outcomes[0:3]), - # FadeOut(new_outcomes[1:4]), - # self.row.fade,0, - # FadeIn(self.tallies[1]), - # FadeIn(self.tallies_copy[0]), - # ) - - # # rest of the new row - # self.play( - # FadeIn(self.tallies[:1]), - # FadeIn(self.tallies[2:]), - # FadeIn(self.tallies_copy[1:]) - # ) - - # self.wait() - - # self.merge_tallies(self.row, target_pos = DOWN) - # self.add_foreground_mobject(self.tallies) - # self.row = self.merge_rects_by_coloring(self.row) - # self.wait() - - - # # # # # # # # # # # # # # # # - # SHOW SPLITTING WITH TALLIES # - # # # # # # # # # # # # # # # # - - tally_left = TallyStack(2,0) - tally_left.move_to(old_row.rects[0]) - tally_right = TallyStack(1,1) - tally_right.move_to(old_row.rects[1]) - - rect_left = old_row.rects[0].copy() - rect_right = old_row.rects[1].copy() - - self.play( - FadeIn(rect_left), - FadeIn(rect_right), - FadeIn(tally_left), - FadeIn(tally_right) - ) - - rect_left.target = rect_left.copy() - rect_left.target.stretch(0.5,0) - left_target_pos = self.row.get_outcome_centers_for_level(3)[1] - left_v = left_target_pos - rect_left.get_center() - rect_left.target.move_to(left_target_pos) - - rect_right.target = rect_right.copy() - rect_right.target.stretch(0.5,0) - right_target_pos = 0.5 * (self.row.get_outcome_centers_for_level(3)[2] + self.row.get_outcome_centers_for_level(3)[3]) - right_v = right_target_pos - rect_right.get_center() - rect_right.target.move_to(right_target_pos) - - self.play( - MoveToTarget(rect_left), - tally_left.move_to, left_target_pos - ) - #tally_left.anchor += left_v - - self.wait() - - new_tally_left = TallyStack(2,1) - #new_tally_left.move_anchor_to(tally_left.anchor) - new_tally_left.move_to(tally_left) - - self.play( - Transform(tally_left, new_tally_left) - ) - - self.play( - MoveToTarget(rect_right), - tally_right.move_to, right_target_pos - ) - #tally_right.anchor += right_v - - self.wait() - new_tally_right = TallyStack(2,1) - #new_tally_right.move_anchor_to(tally_right.anchor) - new_tally_right.move_to(tally_right) - - self.play( - Transform(tally_right, new_tally_right) - ) - - self.wait() - - self.row = self.merge_rects_by_subdiv(self.row) - self.wait() - - self.play( - FadeOut(old_row), - self.row.fade,0, - FadeOut(new_tally_left), - FadeOut(new_tally_right), - FadeIn(self.tallies[1]), - FadeIn(self.tallies_copy[0]), - ) - - # rest of the new row - self.play( - FadeIn(self.tallies[:1]), - FadeIn(self.tallies[2:]), - FadeIn(self.tallies_copy[1:]) - ) - - self.wait() - - self.merge_tallies(self.row, target_pos = DOWN) - self.add_foreground_mobject(self.tallies) - self.row = self.merge_rects_by_coloring(self.row) - self.wait() - - - # show the 8 individual outcomes - outcomes = self.row.get_outcome_rects_for_level(3, - with_labels = True, - inset = True) - self.play(FadeOut(self.tallies)) - self.wait() - self.play(LaggedStartMap( - FadeIn, outcomes, - #rate_func = there_and_back_with_pause, - run_time = 5)) - self.wait() - - braces = VGroup(*[Brace(rect, UP) for rect in self.row.rects]) - counts = [choose(3, i) for i in range(4)] - probs = VGroup(*[TexMobject("{" + str(k) + "\over 8}") for k in counts]) - for (brace, prob) in zip(braces, probs): - prob.next_to(brace, UP) - - - self.play( - LaggedStartMap(ShowCreation, braces), - LaggedStartMap(Write, probs) - ) - self.wait() - self.play(LaggedStartMap( - FadeOut, outcomes, - #rate_func = there_and_back_with_pause, - run_time = 5), - ) - self.play( - FadeIn(self.tallies) - ) - self.wait() - - self.play( - FadeOut(braces), - FadeOut(probs) - ) - self.wait() - - - # put visuals for other probability distribtuions here - - # back to three coin flips, show all 8 outcomes - run_time = 5 - self.play( - LaggedStartMap(FadeIn, outcomes, - #rate_func = there_and_back_with_pause, - run_time = run_time), - FadeOut(self.tallies, - run_time = run_time) - ) - self.wait() - self.play( - LaggedStartMap(FadeOut, outcomes, - #rate_func = there_and_back_with_pause, - run_time = 5), - FadeIn(self.tallies, - run_time = run_time) - ) - - - - - # # # # # # # # - # FOURTH FLIP # - # # # # # # # # - - - - - previous_row = self.row.copy() - self.add(previous_row) - - v = 1.25 * self.row.height * UP - self.play( - previous_row.shift, v, - self.tallies.shift, v, - ) - self.add_foreground_mobject(self.tallies) - - self.play( - SplitRectsInBrickWall(self.row) - ) - self.wait() - - self.row = self.merge_rects_by_subdiv(self.row) - - - self.revert_to_original_skipping_status() - - self.wait() - - n = 3 # level to split - k = 1 # tally to split - - # show individual outcomes - outcomes = previous_row.get_outcome_rects_for_level(n, - with_labels = False, - inset = True - ) - grouped_outcomes = VGroup() - index = 0 - for i in range(n + 1): - size = choose(n,i) - grouped_outcomes.add(VGroup(outcomes[index:index + size])) - index += size - - - grouped_outcomes_copy = grouped_outcomes.copy() - - original_grouped_outcomes = grouped_outcomes.copy() - # for later reference - - self.play( - LaggedStartMap(FadeIn, grouped_outcomes), - LaggedStartMap(FadeIn, grouped_outcomes_copy), - ) - self.wait() - - # show how the outcomes in one tally split into two copies - # going into the neighboring tallies - - #self.revert_to_original_skipping_status() - - target_outcomes = self.row.get_outcome_rects_for_level(n + 1, - with_labels = False, - inset = True - ) - grouped_target_outcomes = VGroup() - index = 0 - old_tally_sizes = [choose(n,i) for i in range(n + 1)] - new_tally_sizes = [choose(n + 1,i) for i in range(n + 2)] - - for i in range(n + 2): - size = new_tally_sizes[i] - grouped_target_outcomes.add(VGroup(target_outcomes[index:index + size])) - index += size - - old_tally_sizes.append(0) # makes the edge cases work properly - - # split all tallies - for i in range(n + 1): - self.play( - Transform(grouped_outcomes[i][0], - grouped_target_outcomes[i][0][old_tally_sizes[i - 1]:] - ), - Transform(grouped_outcomes_copy[i][0], - grouped_target_outcomes[i + 1][0][:old_tally_sizes[i]] - ) - ) - return - - self.wait() - - # fade in new tallies - new_rects = self.row.get_rects_for_level(4) - new_tallies = VGroup(*[ - TallyStack(n + 1 - i, i).move_to(rect) for (i, rect) in enumerate(new_rects) - ]) - self.play(FadeIn(new_tallies)) - self.add_foreground_mobject(new_tallies[1]) - # remove outcomes and sizes except for one tally - anims = [] - for i in range(n + 1): - if i != k - 1: - anims.append(FadeOut(grouped_outcomes_copy[i])) - if i != k: - anims.append(FadeOut(grouped_outcomes[i])) - anims.append(FadeOut(new_tallies[i])) - - #anims.append(FadeOut(self.tallies[0])) - #anims.append(FadeOut(self.tallies[2:])) - anims.append(FadeOut(new_tallies[-1])) - - self.play(*anims) - - self.wait() - - self.play( - Transform(grouped_outcomes_copy[k - 1], original_grouped_outcomes[k - 1]) - ) - - self.play( - Transform(grouped_outcomes[k], original_grouped_outcomes[k]) - ) - - new_rects = self.row.get_rects_for_level(n + 1) - - self.play( - Transform(grouped_outcomes[k][0],grouped_target_outcomes[k][0][old_tally_sizes[k - 1]:]), - Transform(grouped_outcomes_copy[k - 1][0],grouped_target_outcomes[k][0][:old_tally_sizes[k - 1]]), - ) - - self.play( - FadeOut(previous_row), - FadeOut(self.tallies), - ) - - self.row = self.merge_rects_by_coloring(self.row) - - self.play( - FadeIn(new_tallies[0]), - FadeIn(new_tallies[2:]), - ) - - - - - # # # # # # # # # # - # EVEN MORE FLIPS # - # # # # # # # # # # - - self.play(FadeOut(new_tallies)) - self.clear() - self.row = BrickRow(3) - self.add(randy, self.row) - - - for i in range(3): - - self.play(FlipCoin(randy)) - - self.wait() - - previous_row = self.row.copy() - - self.play(previous_row.shift, 1.25 * self.row.height * UP) - - self.play( - SplitRectsInBrickWall(self.row) - ) - self.wait() - self.row = self.merge_rects_by_subdiv(self.row) - self.wait() - self.row = self.merge_rects_by_coloring(self.row) - self.wait() - - self.play(FadeOut(previous_row)) - - - - - - -class ShowProbsInBrickRow3(BrickRowScene): - - def construct(self): - - randy = self.get_primary_pi_creature() - randy = randy.scale(0.5).move_to(3*DOWN + 6*LEFT) - #self.add(randy) - self.row = BrickRow(3, height = 2, width = 10) - self.wait() - - self.add(self.row) - - tallies = VGroup() - for (i, rect) in enumerate(self.row.get_rects_for_level(3)): - tally = TallyStack(3-i, i, show_decimals = False) - tally.move_to(rect) - tallies.add(tally) - - self.add(tallies) - self.wait(6) - - braces = VGroup(*[Brace(rect, UP) for rect in self.row.rects]) - counts = [choose(3, i) for i in range(4)] - probs = VGroup(*[TexMobject("{" + str(k) + "\over 8}") for k in counts]) - for (brace, prob) in zip(braces, probs): - prob.next_to(brace, UP) - - self.wait() - self.play( - LaggedStartMap(ShowCreation, braces, run_time = 3), - LaggedStartMap(Write, probs, run_time = 3) - ) - self.wait() - - self.play(FadeOut(braces),FadeOut(probs)) - - - - -class ShowOutcomesInBrickRow4(BrickRowScene): - - def construct(self): - - randy = self.get_primary_pi_creature() - randy = randy.scale(0.5).move_to(3*DOWN + 6*LEFT) - #self.add(randy) - self.row = BrickRow(3, height = 2, width = 10) - - previous_row = self.row.copy() - v = 1.25 * self.row.height * UP - self.play( - previous_row.shift, v, - ) - - self.add(self.row) - self.add(previous_row) - - - - - self.wait() - previous_outcomes = previous_row.get_outcome_rects_for_level(3, - with_labels = True, inset = True) - - previous_outcomes_copy = previous_outcomes.copy() - - - - self.play( - LaggedStartMap(FadeIn, previous_outcomes), - LaggedStartMap(FadeIn, previous_outcomes_copy), - ) - - self.wait() - - new_outcomes = self.row.get_outcome_rects_for_level(4, - with_labels = True, inset = True) - # remove each last coin - - - new_outcomes_left = VGroup( - new_outcomes[0], - new_outcomes[2], - new_outcomes[3], - new_outcomes[4], - new_outcomes[8], - new_outcomes[9], - new_outcomes[10], - new_outcomes[14] - ) - new_outcomes_right = VGroup( - new_outcomes[1], - new_outcomes[5], - new_outcomes[6], - new_outcomes[7], - new_outcomes[11], - new_outcomes[12], - new_outcomes[13], - new_outcomes[15] - ) - heads_labels = VGroup(*[outcome.label[-1] for outcome in new_outcomes_left]) - tails_labels = VGroup(*[outcome.label[-1] for outcome in new_outcomes_right]) - heads_labels.save_state() - tails_labels.save_state() - for outcome in new_outcomes: - outcome.label[-1].fade(1) - - run_time = 0.5 - self.play(Transform(previous_outcomes[0], new_outcomes_left[0], run_time = run_time)) - self.play(Transform(previous_outcomes[1:4], new_outcomes_left[1:4], run_time = run_time)) - self.play(Transform(previous_outcomes[4:7], new_outcomes_left[4:7], run_time = run_time)) - self.play(Transform(previous_outcomes[7], new_outcomes_left[7], run_time = run_time)) - - - self.play(heads_labels.restore) - - - self.play(Transform(previous_outcomes_copy[0], new_outcomes_right[0], run_time = run_time)) - self.play(Transform(previous_outcomes_copy[1:4], new_outcomes_right[1:4], run_time = run_time)) - self.play(Transform(previous_outcomes_copy[4:7], new_outcomes_right[4:7], run_time = run_time)) - self.play(Transform(previous_outcomes_copy[7], new_outcomes_right[7], run_time = run_time)) - - - self.play(tails_labels.restore) - - - self.wait() - - anims = [FadeOut(previous_outcomes),FadeOut(previous_outcomes_copy)] - - for outcome in new_outcomes_left: - anims.append(FadeOut(outcome.label[-1])) - for outcome in new_outcomes_right: - anims.append(FadeOut(outcome.label[-1])) - - self.play(*anims) - - self.wait() - - - - - -class SplitTalliesIntoBrickRow4(BrickRowScene): - - def construct(self): - - randy = self.get_primary_pi_creature() - randy = randy.scale(0.5).move_to(3*DOWN + 6*LEFT) - #self.add(randy) - self.row = BrickRow(3, height = 2, width = 10) - - previous_row = self.row.copy() - v = 1.25 * self.row.height * UP - self.play( - previous_row.shift, v, - ) - - tallies = VGroup() - for (i, rect) in enumerate(previous_row.get_rects_for_level(3)): - tally = TallyStack(3-i, i, show_decimals = True) - tally.move_to(rect) - tallies.add(tally) - - moving_tallies_left = tallies.copy() - moving_tallies_right = tallies.copy() - - self.add(self.row, previous_row) - self.add_foreground_mobject(tallies) - self.add_foreground_mobject(moving_tallies_left) - self.add_foreground_mobject(moving_tallies_right) - - - self.play(SplitRectsInBrickWall(self.row)) - - anims = [] - for (tally, rect) in zip(moving_tallies_left, previous_row.rects): - anims.append(tally.move_to) - anims.append(rect.get_center() + rect.get_width() * 0.25 * LEFT) - - self.play(*anims) - - new_tallies_left = VGroup() - for (i, tally) in enumerate(moving_tallies_left): - new_tally = TallyStack(4-i,i, with_labels = True) - new_tally.move_to(tally) - new_tallies_left.add(new_tally) - - self.play(Transform(moving_tallies_left, new_tallies_left)) - - anims = [] - for (tally, rect) in zip(moving_tallies_right, previous_row.rects): - anims.append(tally.move_to) - anims.append(rect.get_center() + rect.get_width() * 0.25 * RIGHT) - - self.play(*anims) - - new_tallies_right = VGroup() - for (i, tally) in enumerate(moving_tallies_right): - new_tally = TallyStack(3-i,i+1, with_labels = True) - new_tally.move_to(tally) - new_tallies_right.add(new_tally) - - self.play(Transform(moving_tallies_right, new_tallies_right)) - - - hypothetical_new_row = BrickRow(4, height = 2, width = 10) - anims = [] - for (tally, rect) in zip(moving_tallies_left[1:], hypothetical_new_row.rects[1:-1]): - anims.append(tally.move_to) - anims.append(rect) - for (tally, rect) in zip(moving_tallies_right[:-1], hypothetical_new_row.rects[1:-1]): - anims.append(tally.move_to) - anims.append(rect) - self.play(*anims) - self.wait() - self.row = self.merge_rects_by_subdiv(self.row) - self.wait() - self.row = self.merge_rects_by_coloring(self.row) - self.wait() - - - - - diff --git a/from_3b1b/on_hold/eop/chapter1/entire_brick_wall.py b/from_3b1b/on_hold/eop/chapter1/entire_brick_wall.py deleted file mode 100644 index 34f23317..00000000 --- a/from_3b1b/on_hold/eop/chapter1/entire_brick_wall.py +++ /dev/null @@ -1,164 +0,0 @@ - -from manimlib.imports import * -from active_projects.eop.reusable_imports import * -from active_projects.eop.chapter1.brick_row_scene import BrickRowScene - -class EntireBrickWall(BrickRowScene, MovingCameraScene): - - def setup(self): - super(BrickRowScene, self).setup() - super(PiCreatureScene, self).setup() - - - def construct(self): - - self.remove(self.get_primary_pi_creature()) - - row_height = 0.3 - nb_rows = 20 - start_point = 3 * UP + 1 * LEFT - - rows = VMobject() - rows.add(BrickRow(0, height = row_height)) - rows.move_to(start_point) - self.add(rows) - - zero_counter = Integer(0).next_to(start_point + 0.5 * rows[0].width * RIGHT) - nb_flips_text = TextMobject("\# of flips") - nb_flips_text.next_to(zero_counter, RIGHT, buff = LARGE_BUFF) - self.add(zero_counter, nb_flips_text) - flip_counters = VGroup(zero_counter) - - for i in range(1, nb_rows + 1): - rows.add(rows[-1].copy()) - self.bring_to_back(rows[-1]) - anims = [ - rows[-1].shift, row_height * DOWN, - Animation(rows[-2]) - ] - - if i % 5 == 0: - counter = Integer(i) - counter.next_to(rows[-1].get_right() + row_height * DOWN, RIGHT) - flip_counters.add(counter) - anims.append(FadeIn(counter)) - - self.play(*anims) - - self.play(SplitRectsInBrickWall(rows[-1])) - rows.submobjects[-1] = self.merge_rects_by_subdiv(rows[-1]) - rows.submobjects[-1] = self.merge_rects_by_coloring(rows[-1]) - - - # draw indices under the last row for the number of tails - tails_counters = VGroup() - for (i, rect) in enumerate(rows[-1].rects): - if i < 6 or i > 14: - continue - if i == 6: - counter = TexMobject("\dots", color = COLOR_TAILS) - counter.next_to(rect, DOWN, buff = 1.5 * MED_SMALL_BUFF) - elif i == 14: - counter = TexMobject("\dots", color = COLOR_TAILS) - counter.next_to(rect, DOWN, buff = 1.5 * MED_SMALL_BUFF) - counter.shift(0.2 * RIGHT) - else: - counter = Integer(i, color = COLOR_TAILS) - counter.next_to(rect, DOWN) - tails_counters.add(counter) - - nb_tails_text = TextMobject("\# of tails", color = COLOR_TAILS) - nb_tails_text.next_to(tails_counters[-1], RIGHT, buff = LARGE_BUFF) - - self.play( - LaggedStartMap(FadeIn, tails_counters), - FadeIn(nb_tails_text) - ) - - # remove any hidden brick rows - self.clear() - self.add(nb_flips_text) - - mobs_to_shift = VGroup( - rows, flip_counters, tails_counters, nb_tails_text, - ) - self.play(mobs_to_shift.shift, 3 * UP) - - last_row_rect = SurroundingRectangle(rows[-1], buff = 0) - last_row_rect.set_stroke(color = YELLOW, width = 6) - - rows.save_state() - self.play( - rows.fade, 0.9, - ShowCreation(last_row_rect) - ) - - def highlighted_brick(row = 20, nb_tails = 10): - brick_copy = rows[row].rects[nb_tails].copy() - brick_copy.set_fill(color = YELLOW, opacity = 0.8) - prob_percentage = float(choose(row, nb_tails)) / 2**row * 100 - brick_label = DecimalNumber(prob_percentage, - unit = "\%", num_decimal_places = 1, color = BLACK) - brick_label.move_to(brick_copy) - brick_label.set_height(0.8 * brick_copy.get_height()) - return VGroup(brick_copy, brick_label) - - highlighted_bricks = [ - highlighted_brick(row = 20, nb_tails = i) - for i in range(20) - ] - self.wait() - self.play( - FadeIn(highlighted_bricks[10]) - ) - self.wait() - self.play( - FadeOut(highlighted_bricks[10]), - FadeIn(highlighted_bricks[9]), - FadeIn(highlighted_bricks[11]), - ) - self.wait() - self.play( - FadeOut(highlighted_bricks[9]), - FadeOut(highlighted_bricks[11]), - FadeIn(highlighted_bricks[8]), - FadeIn(highlighted_bricks[12]), - ) - self.wait() - self.play( - FadeOut(highlighted_bricks[8]), - FadeOut(highlighted_bricks[12]), - FadeOut(last_row_rect), - rows.restore, - ) - self.wait() - new_frame = self.camera_frame.copy() - new_frame.scale(0.0001).move_to(rows.get_corner(DR)) - - self.play( - Transform(self.camera_frame, new_frame, - run_time = 9, - rate_func = exponential_decay - ) - ) - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/chapter1/intro.py b/from_3b1b/on_hold/eop/chapter1/intro.py deleted file mode 100644 index f503c14e..00000000 --- a/from_3b1b/on_hold/eop/chapter1/intro.py +++ /dev/null @@ -1,55 +0,0 @@ -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - -class Chapter1OpeningQuote(OpeningQuote): - CONFIG = { - "fade_in_kwargs": { - "lag_ratio": 0.5, - "rate_func": linear, - "run_time": 10, - }, - "text_size" : "\\normalsize", - "use_quotation_marks": False, - "quote" : [ - "To see a world in a grain of sand\\\\", - "And a heaven in a wild flower,\\\\", - "Hold infinity in the palm of your hand\\\\", - "\phantom{r}And eternity in an hour.\\\\" - ], - "quote_arg_separator" : " ", - "highlighted_quote_terms" : {}, - "author" : "William Blake: \\\\ \emph{Auguries of Innocence}", - } - -class Introduction(TeacherStudentsScene): - - CONFIG = { - "default_pi_creature_kwargs": { - "color": MAROON_E, - "flip_at_start": True, - }, - } - - def construct(self): - - self.wait(5) - - self.change_student_modes( - "confused", "frustrated", "dejected", - look_at_arg = UP + 2 * RIGHT - ) - - self.wait() - - self.play( - self.get_teacher().change_mode,"raise_right_hand" - ) - self.wait() - - self.wait(30) - # put examples here in video editor - - - # # # # # # # # # # # # # # # # # # - # show examples of the area model # - # # # # # # # # # # # # # # # # # # \ No newline at end of file diff --git a/from_3b1b/on_hold/eop/chapter1/just_randy_flipping_coin.py b/from_3b1b/on_hold/eop/chapter1/just_randy_flipping_coin.py deleted file mode 100644 index 580f70de..00000000 --- a/from_3b1b/on_hold/eop/chapter1/just_randy_flipping_coin.py +++ /dev/null @@ -1,39 +0,0 @@ - -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - -class JustFlipping(Scene): - - def construct(self): - - randy = CoinFlippingPiCreature(color = MAROON_E, flip_height = 1).shift(2 * DOWN) - self.add(randy) - - self.wait(2) - - for i in range(10): - self.wait() - self.play(FlipCoin(randy)) - - - -class JustFlippingWithResults(Scene): - - def construct(self): - - randy = CoinFlippingPiCreature(color = MAROON_E, flip_height = 1).shift(2 * DOWN) - self.add(randy) - - self.wait(2) - - for i in range(10): - self.wait() - self.play(FlipCoin(randy)) - result = random.choice(["H", "T"]) - if result == "H": - coin = UprightHeads().scale(3) - else: - coin = UprightTails().scale(3) - coin.move_to(2 * UP + 2.5 * LEFT + i * 0.6 * RIGHT) - self.play(FadeIn(coin)) - diff --git a/from_3b1b/on_hold/eop/chapter1/million_flips.py b/from_3b1b/on_hold/eop/chapter1/million_flips.py deleted file mode 100644 index 84ff4b74..00000000 --- a/from_3b1b/on_hold/eop/chapter1/million_flips.py +++ /dev/null @@ -1,118 +0,0 @@ -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - - -class MillionFlips(Scene): - def construct(self): - title = TextMobject("1{,}000{,}000 flips") - title.to_edge(UP) - self.add(title) - - small_wait_time = 1.0 / 15 # Um... - - n_flips_label = TextMobject("\\# Flips: ") - n_heads_label = TextMobject("\\# Heads: ") - n_flips_count = Integer(0) - n_heads_count = Integer(0) - n_heads_label.to_edge(RIGHT, buff=2 * LARGE_BUFF) - n_flips_label.next_to(n_heads_label, DOWN, aligned_edge=LEFT) - n_flips_count.next_to(n_flips_label[-1], RIGHT) - n_heads_count.next_to(n_heads_label[-1], RIGHT) - VGroup(n_flips_count, n_heads_count).shift(0.5 * SMALL_BUFF * UP) - self.add(n_flips_label, n_heads_label, n_flips_count, n_heads_count) - - coins = VGroup(*[ - FlatHeads() if random.random() < 0.5 else FlatTails() - for x in range(100) - ]) - self.organize_group(coins) - - proportions = np.random.normal(0.5, 0.5 * 0.1, 100) - hundred_boxes = VGroup(*[ - Square( - stroke_width=1, - stroke_color=WHITE, - fill_opacity=1, - fill_color=interpolate_color(COLOR_HEADS, COLOR_TAILS, prop) - ) - for prop in proportions - ]) - self.organize_group(hundred_boxes) - - ten_k_proportions = np.random.normal(0.5, 0.5 * 0.01, 100) - ten_k_boxes = VGroup(*[ - Square( - stroke_width=1, - stroke_color=WHITE, - fill_opacity=1, - fill_color=interpolate_color(COLOR_HEADS, COLOR_TAILS, prop) - ) - for prop in ten_k_proportions - ]) - self.organize_group(ten_k_boxes) - - # Animations - for coin in coins: - self.add(coin) - self.increment(n_flips_count) - if isinstance(coin, FlatHeads): - self.increment(n_heads_count) - self.wait(small_wait_time) - - self.play( - FadeIn(hundred_boxes[0]), - coins.set_stroke, {"width": 0}, - coins.replace, hundred_boxes[0] - ) - hundred_boxes[0].add(coins) - for box, prop in list(zip(hundred_boxes, proportions))[1:]: - self.add(box) - self.increment(n_flips_count, 100) - self.increment(n_heads_count, int(np.round(prop * 100))) - self.wait(small_wait_time) - - self.play( - FadeIn(ten_k_boxes[0]), - hundred_boxes.set_stroke, {"width": 0}, - hundred_boxes.replace, ten_k_boxes[0] - ) - ten_k_boxes[0].add(hundred_boxes) - for box, prop in list(zip(ten_k_boxes, ten_k_proportions))[1:]: - self.add(box) - self.increment(n_flips_count, 10000) - self.increment(n_heads_count, int(np.round(prop * 10000))) - self.wait(small_wait_time) - self.wait() - - def organize_group(self, group): - group.arrange_in_grid(10) - group.set_height(5) - group.shift(DOWN + 2 * LEFT) - - def increment(self, integer_mob, value=1): - new_int = Integer(integer_mob.number + value) - new_int.move_to(integer_mob, DL) - integer_mob.number += value - integer_mob.submobjects = new_int.submobjects - - -class PropHeadsWithinThousandth(Scene): - def construct(self): - prob = TexMobject( - "P(499{,}000 \\le", "\\# \\text{H}", "\\le 501{,}000)", - "\\approx", "0.9545", - ) - prob[1].set_color(RED) - prob[-1].set_color(YELLOW) - self.add(prob) - - -class PropHeadsWithinHundredth(Scene): - def construct(self): - prob = TexMobject( - "P(490{,}000 \\le", "\\# \\text{H}", "\\le 510{,}000)", - "\\approx", "0.99999999\\dots", - ) - prob[1].set_color(RED) - prob[-1].set_color(YELLOW) - self.add(prob) diff --git a/from_3b1b/on_hold/eop/chapter1/morph_brick_row_into_histogram.py b/from_3b1b/on_hold/eop/chapter1/morph_brick_row_into_histogram.py deleted file mode 100644 index 0223c699..00000000 --- a/from_3b1b/on_hold/eop/chapter1/morph_brick_row_into_histogram.py +++ /dev/null @@ -1,299 +0,0 @@ - -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - -class GenericMorphBrickRowIntoHistogram(Scene): - - CONFIG = { - "level" : 3, - "bar_width" : 2.0, - "bar_anchor_height" : -3.0, - "show_tallies" : False, - "show_nb_flips" : True - } - - def construct(self): - - self.row = BrickRow(self.level, height = self.bar_width, width = 10) - self.bars = OutlineableBars(*[self.row.rects[i] for i in range(self.level + 1)]) - self.bar_anchors = [self.bar_anchor_height * UP + self.row.height * (i - 0.5 * self.level) * RIGHT for i in range(self.level + 1)] - - self.add(self.row) - - if self.show_tallies: - - tallies = VMobject() - - for (i,brick) in enumerate(self.row.rects): - tally = TallyStack(self.level - i, i) - tally.move_to(brick) - self.add(tally) - tallies.add(tally) - brick.set_stroke(width = 3) - - if self.show_nb_flips: - nb_flips_text = TextMobject("\# of flips: " + str(self.level)) - nb_flips_text.to_corner(UR) - self.add(nb_flips_text) - - self.remove(self.row.subdivs, self.row.border) - - for rect in self.row.rects: - rect.set_stroke(color = WHITE, width = 3) - - self.wait() - self.play( - self.row.rects.space_out_submobjects, {"factor" : 1.3}, - FadeOut(tallies) - ) - self.wait() - anims = [] - for brick in self.row.rects: - anims.append(brick.rotate) - anims.append(TAU/4) - - self.play(*anims) - - self.wait() - - anims = [] - for (i,brick) in enumerate(self.row.rects): - anims.append(brick.next_to) - anims.append(self.bar_anchors[i]) - anims.append({"direction" : UP, "buff" : 0}) - self.play(*anims) - self.wait() - - self.bars.create_outline() - anims = [ - ApplyMethod(rect.set_stroke, {"width" : 0}) - for rect in self.bars - ] - anims.append(FadeIn(self.bars.outline)) - self.play(*anims) - - self.wait() - - - -class MorphBrickRowIntoHistogram3(GenericMorphBrickRowIntoHistogram): - - CONFIG = { - "level" : 3, - "prob_denominator" : 8, - "bar_width" : 2.0, - "bar_anchor_height" : -3.0, - "show_tallies" : True, - "show_nb_flips" : False - } - - def construct(self): - - - - super(MorphBrickRowIntoHistogram3,self).construct() - - - # draw x-axis - - x_axis = Line(ORIGIN, 10 * RIGHT, color = WHITE, buff = 0) - x_axis.next_to(self.bars, DOWN, buff = 0) - #x_labels = VMobject(*[TexMobject(str(i)) for i in range(4)]) - x_labels = VMobject() - - for (i, bar) in enumerate(self.bars): - label = Integer(i) - label.next_to(self.bar_anchors[i], DOWN) - x_labels.add(label) - - nb_tails_label = TextMobject("\# of tails") - nb_tails_label.next_to(x_labels[-1], RIGHT, MED_LARGE_BUFF) - - - - # draw y-guides - - y_guides = VMobject() - for i in range(0,self.prob_denominator + 1): - y_guide = Line(5 * LEFT, 5 * RIGHT, stroke_color = GRAY) - y_guide.move_to(self.bar_anchor_height * UP + i * float(self.row.width) / self.prob_denominator * UP) - y_guide_label = TexMobject("{" + str(i) + "\over " + str(self.prob_denominator) + "}", color = GRAY) - y_guide_label.scale(0.7) - y_guide_label.next_to(y_guide, LEFT) - if i != 0: - y_guide.add(y_guide_label) - y_guides.add(y_guide) - self.wait() - - self.play( - FadeIn(y_guides), - Animation(self.bars.outline), - Animation(self.bars) - ) - self.wait() - - self.play( - FadeIn(x_axis), - FadeIn(x_labels), - FadeIn(nb_tails_label) - ) - - self.add_foreground_mobject(nb_tails_label) - area_color = YELLOW - - total_area_text = TextMobject("total area =", color = area_color) - area_decimal = DecimalNumber(0, color = area_color, num_decimal_places = 3) - area_decimal.next_to(total_area_text, RIGHT) - - total_area_group = VGroup(total_area_text, area_decimal) - total_area_group.move_to(2.7 * UP) - self.wait() - - self.play( - FadeIn(total_area_text), - ) - self.wait() - - cumulative_areas = [0.125, 0.5, 0.875, 1] - covering_rects = self.bars.copy() - for (i,rect) in enumerate(covering_rects): - rect.set_fill(color = area_color, opacity = 0.5) - self.play( - FadeIn(rect, rate_func = linear), - ChangeDecimalToValue(area_decimal, cumulative_areas[i], - rate_func = linear) - ) - self.wait(0.2) - - self.wait() - - total_area_rect = SurroundingRectangle( - total_area_group, - buff = MED_SMALL_BUFF, - stroke_color = area_color - ) - - self.play( - FadeOut(covering_rects), - ShowCreation(total_area_rect) - ) - self.wait() - - - -class MorphBrickRowIntoHistogram20(GenericMorphBrickRowIntoHistogram): - CONFIG = { - "level" : 20, - "prob_ticks" : 0.05, - "bar_width" : 0.5, - "bar_anchor_height" : -3.0, - "x_ticks": 5 - } - - def construct(self): - - super(MorphBrickRowIntoHistogram20, self).construct() - - x_axis = Line(ORIGIN, 10 * RIGHT, color = WHITE, buff = 0) - x_axis.next_to(self.bars, DOWN, buff = 0) - #x_labels = VMobject(*[TexMobject(str(i)) for i in range(4)]) - x_labels = VMobject() - for (i, bar) in enumerate(self.bars): - if i % self.x_ticks != 0: - continue - label = Integer(i) - label.next_to(self.bar_anchors[i], DOWN) - x_labels.add(label) - - nb_tails_label = TextMobject("\# of tails") - nb_tails_label.move_to(5 * RIGHT + 2.5 * DOWN) - self.wait() - - self.play( - FadeIn(x_axis), - FadeIn(x_labels), - FadeIn(nb_tails_label) - ) - self.wait() - - # draw y-guides - - max_prob = float(choose(self.level, self.level/2)) / 2 ** self.level - - y_guides = VMobject() - y_guide_heights = [] - prob_grid = np.arange(self.prob_ticks, 1.3 * max_prob, self.prob_ticks) - for i in prob_grid: - y_guide = Line(5 * LEFT, 5 * RIGHT, stroke_color = GRAY) - y_guide_height = self.bar_anchor_height + i * float(self.row.width) - y_guide_heights.append(y_guide_height) - y_guide.move_to(y_guide_height * UP) - y_guide_label = DecimalNumber(i, num_decimal_places = 2, color = GRAY) - y_guide_label.scale(0.7) - y_guide_label.next_to(y_guide, LEFT) - y_guide.add(y_guide_label) - y_guides.add(y_guide) - - self.bring_to_back(y_guides) - self.play(FadeIn(y_guides), Animation(self.bars)) - self.wait() - - histogram_width = self.bars.get_width() - histogram_height = self.bars.get_height() - - # scale to fit screen - self.scale_x = 10.0/((len(self.bars) - 1) * self.bar_width) - self.scale_y = 6.0/histogram_height - - - anims = [] - for (bar, x_label) in zip(self.bars, x_labels): - v = (self.scale_x - 1) * x_label.get_center()[0] * RIGHT - anims.append(x_label.shift) - anims.append(v) - - - anims.append(self.bars.stretch_about_point) - anims.append(self.scale_x) - anims.append(0) - anims.append(ORIGIN) - anims.append(self.bars.outline.stretch_about_point) - anims.append(self.scale_x) - anims.append(0) - anims.append(ORIGIN) - - self.play(*anims) - self.wait() - - anims = [] - for (guide, i, h) in zip(y_guides, prob_grid, y_guide_heights): - new_y_guide_height = self.bar_anchor_height + i * self.scale_y * float(self.row.width) - v = (new_y_guide_height - h) * UP - anims.append(guide.shift) - anims.append(v) - - anims.append(self.bars.stretch_about_point) - anims.append(self.scale_y) - anims.append(1) - anims.append(self.bars.get_bottom()) - anims.append(self.bars.outline.stretch_about_point) - anims.append(self.scale_y) - anims.append(1) - anims.append(self.bars.get_bottom()) - - self.play(*anims) - self.wait() - -class MorphBrickRowIntoHistogram100(MorphBrickRowIntoHistogram20): - CONFIG = { - "level" : 100, - "x_ticks": 20, - "prob_ticks": 0.02 - } - -class MorphBrickRowIntoHistogram500(MorphBrickRowIntoHistogram20): - CONFIG = { - "level" : 500, - "x_ticks": 100, - "prob_ticks": 0.01 - } diff --git a/from_3b1b/on_hold/eop/chapter1/prob_dist_visuals.py b/from_3b1b/on_hold/eop/chapter1/prob_dist_visuals.py deleted file mode 100644 index 127d9800..00000000 --- a/from_3b1b/on_hold/eop/chapter1/prob_dist_visuals.py +++ /dev/null @@ -1,321 +0,0 @@ -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - - -class ProbabilityDistributions(PiCreatureScene): - - CONFIG = { - "default_pi_creature_kwargs": { - "color": MAROON_E, - "flip_at_start": False, - }, - } - - def construct(self): - - lag_ratio = 0.2 - run_time = 3 - - -# WEATHER FORECAST - - - unit_rect = Rectangle( - height = 3, width = 3 - ).shift(DOWN) - - p_rain = 0.23 - p_sun = 1 - p_rain - opacity = 0.7 - - rain_rect = unit_rect.copy().stretch(p_rain, 0) - rain_rect.align_to(unit_rect, LEFT) - rain_rect.set_fill(color = BLUE, opacity = opacity) - rain_rect.set_stroke(width = 0) - - sun_rect = unit_rect.copy().stretch(p_sun, 0) - sun_rect.next_to(rain_rect, RIGHT, buff = 0) - sun_rect.set_fill(color = YELLOW, opacity = opacity) - sun_rect.set_stroke(width = 0) - - self.add(unit_rect, rain_rect, sun_rect) - - rain = SVGMobject(file_name = "rain").scale(0.25) - sun = SVGMobject(file_name = "sun").scale(0.35) - - rain.flip().move_to(rain_rect) - sun.move_to(sun_rect) - - self.add(rain, sun) - - - text_scale = 0.7 - - brace_rain = Brace(rain_rect, UP) - p_rain_label = TextMobject("$P($rain$)=$").scale(text_scale) - p_rain_decimal = DecimalNumber(p_rain).scale(text_scale) - p_rain_decimal.next_to(p_rain_label) - p_rain_whole_label = VGroup(p_rain_label, p_rain_decimal) - p_rain_whole_label.next_to(brace_rain, UP) - - brace_sun = Brace(sun_rect, DOWN) - p_sun_label = TextMobject("$P($sunshine$)=$").scale(text_scale) - p_sun_decimal = DecimalNumber(p_sun).scale(text_scale) - p_sun_decimal.next_to(p_sun_label) - p_sun_whole_label = VGroup(p_sun_label, p_sun_decimal) - p_sun_whole_label.next_to(brace_sun, DOWN) - - self.add(brace_rain, p_rain_whole_label, brace_sun, p_sun_whole_label) - - self.wait(6) - - - - # new_p_rain = 0.68 - # new_p_sun = 1 - new_p_rain - - # new_rain_rect = unit_rect.copy().stretch(new_p_rain, 0) - # new_rain_rect.align_to(unit_rect, LEFT) - # new_rain_rect.set_fill(color = BLUE, opacity = opacity) - # new_rain_rect.set_stroke(width = 0) - - # new_sun_rect = unit_rect.copy().stretch(new_p_sun, 0) - # new_sun_rect.next_to(new_rain_rect, RIGHT, buff = 0) - # new_sun_rect.set_fill(color = YELLOW, opacity = opacity) - # new_sun_rect.set_stroke(width = 0) - - # new_rain = SVGMobject(file_name = "rain").scale(0.35) - # new_sun = SVGMobject(file_name = "sun").scale(0.35) - - # new_rain.flip().move_to(new_rain_rect) - # new_sun.move_to(new_sun_rect) - - # new_brace_rain = Brace(new_rain_rect, UP) - # new_p_rain_label = TextMobject("$P($rain$)=$").scale(text_scale) - # new_p_rain_decimal = DecimalNumber(new_p_rain).scale(text_scale) - # new_p_rain_decimal.next_to(new_p_rain_label) - # new_p_rain_whole_label = VGroup(new_p_rain_label, new_p_rain_decimal) - # new_p_rain_whole_label.next_to(new_brace_rain, UP) - - - # new_brace_sun = Brace(new_sun_rect, DOWN) - # new_p_sun_label = TextMobject("$P($sunshine$)=$").scale(text_scale) - # new_p_sun_decimal = DecimalNumber(new_p_sun).scale(text_scale) - # new_p_sun_decimal.next_to(new_p_sun_label) - # new_p_sun_whole_label = VGroup(new_p_sun_label, new_p_sun_decimal) - # new_p_sun_whole_label.next_to(new_brace_sun, DOWN) - - # def rain_update_func(alpha): - # return alpha * new_p_rain + (1 - alpha) * p_rain - - # def sun_update_func(alpha): - # return 1 - rain_update_func(alpha) - - # update_p_rain = ChangingDecimal( - # p_rain_decimal, rain_update_func, - # tracked_mobject = p_rain_label, - # run_time = run_time - # ) - # update_p_sun = ChangingDecimal( - # p_sun_decimal, sun_update_func, - # tracked_mobject = p_sun_label, - # run_time = run_time - # ) - - # self.play( - # Transform(rain_rect, new_rain_rect, run_time = run_time), - # Transform(sun_rect, new_sun_rect, run_time = run_time), - # Transform(rain, new_rain, run_time = run_time), - # Transform(sun, new_sun, run_time = run_time), - # Transform(brace_rain, new_brace_rain, run_time = run_time), - # Transform(brace_sun, new_brace_sun, run_time = run_time), - # Transform(p_rain_label, new_p_rain_label, run_time = run_time), - # Transform(p_sun_label, new_p_sun_label, run_time = run_time), - # update_p_rain, - # update_p_sun - # ) - - - - # move the forecast into a corner - - forecast = VGroup( - rain_rect, sun_rect, rain, sun, brace_rain, brace_sun, - p_rain_whole_label, p_sun_whole_label, unit_rect - ) - - forecast.target = forecast.copy().scale(0.5) - forecast.target.to_corner(UL) - - self.play(MoveToTarget(forecast)) - - self.play( - FadeOut(brace_rain), - FadeOut(brace_sun), - FadeOut(p_rain_whole_label), - FadeOut(p_sun_whole_label), - ) - - self.wait(3) - - -# DOUBLE DICE THROW - - cell_size = 0.5 - dice_table = TwoDiceTable(cell_size = cell_size, label_scale = 0.7) - dice_table.shift(0.8 * DOWN) - dice_unit_rect = SurroundingRectangle( - dice_table.cells, buff = 0, - stroke_color=WHITE - ) - - dice_table_grouped_cells = VGroup() - - for i in range(6): - dice_table_grouped_cells.add(VGroup(*[ - VGroup( - dice_table.cells[6 * i - 5 * k], - dice_table.labels[6 * i - 5 * k], - ) - for k in range(i + 1) - ])) - - for i in range(5): - dice_table_grouped_cells.add(VGroup(*[ - VGroup( - dice_table.cells[31 + i - 5 * k], - dice_table.labels[31 + i - 5 * k], - ) - for k in range(5 - i) - ])) - - # self.play( - # FadeIn(dice_unit_rect), - # FadeIn(dice_table.rows) - # ) - - # for (cell, label) in zip(dice_table.cells, dice_table.labels): - # cell.add(label) - - # self.play( - # LaggedStartMap(FadeIn, dice_table_grouped_cells, - # lag_ratio = lag_ratio, run_time = run_time) - # ) - self.play( - FadeIn(dice_table_grouped_cells), - FadeIn(dice_unit_rect), - FadeIn(dice_table.rows) - ) - - self.wait(3) - - - self.play( - dice_table_grouped_cells.space_out_submobjects, {"factor" : 1.5}, - rate_func=there_and_back_with_pause, - run_time=run_time - ) - - dice_table.add(dice_unit_rect) - dice_table_target = dice_table.deepcopy() - dice_table_target.scale(0.5) - dice_table_target.to_corner(UR, buff=LARGE_BUFF) - dice_table_target.shift(0.4 * UP) - - self.play(Transform(dice_table, dice_table_target)) - - self.play( - FadeOut(dice_table.rows), - FadeOut(dice_unit_rect), - ) - - self.wait(3) - -# TITLE - - text = TextMobject("Probability distributions") - text.to_edge(UP) - text_rect = SurroundingRectangle(text, buff=MED_SMALL_BUFF) - text_rect.match_color(text) - - self.play( - FadeIn(text), - ShowCreation(text_rect) - ) - - self.wait(3) - - -# COIN FLIP - - - brick_row = BrickRow(3, height = 2, width = 10) - coin_flip_rect = VGroup(brick_row) - - tallies = VGroup() - for (i, brick) in enumerate(brick_row.rects): - tally = TallyStack(3 - i, i) - tally.move_to(brick) - tallies.add(tally) - coin_flip_rect.add(tallies) - - coin_flip_rect.scale(0.65).shift(RIGHT) - self.play(FadeIn(coin_flip_rect)) - - counts = [1, 3, 3, 1] - braces = VGroup() - labels = VGroup() - for (rect, count) in zip(brick_row.rects, counts): - label = TexMobject("{" + str(count) + "\\over 8}").scale(0.5) - brace = Brace(rect, DOWN) - label.next_to(brace, DOWN) - braces.add(brace) - labels.add(label) - - self.play( - FadeIn(braces), - FadeIn(labels) - ) - - coin_flip_rect.add(braces, labels) - - - self.wait(6) - - outcomes = brick_row.get_outcome_rects_for_level(3, with_labels = True, - inset = True) - outcomes.scale(0.65) - outcomes.move_to(brick_row.get_center()) - outcome_braces = VGroup(*[ - Brace(outcome, DOWN) for outcome in outcomes - ]) - outcome_labels = VGroup(*[ - TexMobject("{1\over 8}").scale(0.5).next_to(brace, DOWN) - for brace in outcome_braces - ]) - - self.play( - FadeOut(tallies), - FadeIn(outcomes), - FadeOut(braces), - FadeOut(labels), - FadeIn(outcome_braces), - FadeIn(outcome_labels) - ) - - - self.wait(10) - - - - - - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/chapter1/quiz_result.py b/from_3b1b/on_hold/eop/chapter1/quiz_result.py deleted file mode 100644 index fc8cb290..00000000 --- a/from_3b1b/on_hold/eop/chapter1/quiz_result.py +++ /dev/null @@ -1,278 +0,0 @@ -from manimlib.imports import * -from active_projects.eop.reusable_imports import * -from active_projects.eop.independence import * - -from for_3b1b_videos.pi_class import PiCreatureClass - -class QuizResult(PiCreatureScene): - CONFIG = { - "pi_creatures_start_on_screen" : False, - "random_seed" : 0 - } - def construct(self): - - - def get_example_quiz(): - quiz = get_quiz( - "Define ``Brachistochrone'' ", - "Define ``Tautochrone'' ", - "Define ``Cycloid'' ", - ) - rect = SurroundingRectangle(quiz, buff = 0) - rect.set_fill(color = BLACK, opacity = 1) - rect.set_stroke(width = 0) - quiz.add_to_back(rect) - return quiz - - - highlight_color = WHITE - - nb_students_x = 5 - nb_students_y = 3 - spacing_students_x = 2.0 - spacing_students_y = 2.2 - - all_students = PiCreatureClass( - width = nb_students_x, height = nb_students_y)# VGroup() - student_points = [] - grades = [] - grades_count = [] - hist_y_values = np.zeros(4) - for i in range(nb_students_x): - for j in range(nb_students_y): - x = i * spacing_students_x - y = j * spacing_students_y - #pi = PiCreature().scale(0.3) - #pi.move_to([x,y,0]) - #all_students.add(pi) - all_students[i*nb_students_y + j].move_to([x,y,0]) - q1 = np.random.choice([True, False]) - q2 = np.random.choice([True, False]) - q3 = np.random.choice([True, False]) - student_points.append([q1, q2, q3]) - grade = q1*1+q2*1+q3*1 - grades.append(grade) - hist_y_values[grade] += 1 - # How many times has this grade already occured? - grade_count = grades.count(grade) - grades_count.append(grade_count) - - - all_students.move_to(ORIGIN) - self.pi_creatures = all_students - self.play(FadeIn(all_students)) - - all_quizzes = VGroup() - - quiz = get_example_quiz().scale(0.2) - for pi in all_students: - quiz_copy = quiz.copy() - quiz_copy.next_to(pi, UP) - all_quizzes.add(quiz_copy) - - master_quiz = get_example_quiz() - self.play(Write(master_quiz), run_time = 2) - self.wait() - self.play(ReplacementTransform( - VGroup(master_quiz), all_quizzes, - run_time=2, - lag_ratio=0.5 - )) - self.wait(2) - - grades_mob = VGroup() - for (pi, quiz, grade) in zip(all_students, all_quizzes, grades): - grade_mob = TexMobject(str(grade) + "/3") - grade_mob.move_to(quiz) - grades_mob.add(grade_mob) - - self.remove(master_quiz) - self.wait() - self.play( - FadeOut(all_quizzes), - FadeIn(grades_mob) - ) - - # self.play( - # all_students[2:].fade, 0.8, - # grades_mob[2:].fade, 0.8 - # ) - - students_points_mob = VGroup() - for (pi, quiz, points) in zip(all_students, all_quizzes, student_points): - slot = get_slot_group(points, include_qs = False) - slot.scale(0.5).move_to(quiz) - students_points_mob.add(slot) - - self.wait() - self.play( - #all_students.fade, 0, - FadeOut(grades_mob), - FadeIn(students_points_mob) - ) - - all_students.save_state() - students_points_mob.save_state() - self.wait() - randy = all_students[0] - morty = all_students[nb_students_y] - all_other_students = VGroup(*all_students) - all_other_students.remove(randy, morty) - randy_points = students_points_mob[0] - morty_points = students_points_mob[nb_students_y] - all_other_points = VGroup(*students_points_mob) - all_other_points.remove(randy_points, morty_points) - self.play( - all_other_students.fade, 0.8, - all_other_points.fade, 0.8, - ) - self.wait() - scale = 1.5 - self.play(randy_points.scale,scale) - self.play(randy_points.scale,1.0/scale, morty_points.scale,scale) - self.play(morty_points.scale,1.0/scale) - - self.wait() - self.play( - all_students.restore, - students_points_mob.restore, - ) - - self.wait() - anims = [] - for points in students_points_mob: - anims.append(points.scale) - anims.append(scale) - self.play(*anims) - - self.wait() - anims = [] - for points in students_points_mob: - anims.append(points.scale) - anims.append(1.0/scale) - self.play(*anims) - - anims = [] - anchor_point = 3 * DOWN + 1 * LEFT - for (pi, grade, grades_count) in zip(all_students, grades, grades_count): - anims.append(pi.move_to) - anims.append(anchor_point + grade * RIGHT + grades_count * UP) - anims.append(FadeOut(students_points_mob)) - - self.wait() - self.play(*anims) - - grade_labels = VGroup() - for i in range(4): - grade_label = Integer(i, color = highlight_color) - grade_label.move_to(i * RIGHT) - grade_labels.add(grade_label) - grade_labels.next_to(all_students, DOWN) - out_of_label = TextMobject("out of 3", color = highlight_color) - out_of_label.next_to(grade_labels, RIGHT, buff = MED_LARGE_BUFF) - grade_labels.add(out_of_label) - self.wait() - self.play(Write(grade_labels)) - - grade_hist = Histogram( - np.ones(4), - hist_y_values, - mode = "widths", - x_labels = "none", - y_label_position = "center", - bar_stroke_width = 0, - outline_stroke_width = 5 - ) - grade_hist.move_to(all_students) - - self.wait() - self.play( - FadeIn(grade_hist), - FadeOut(all_students) - ) - - - nb_students_label = TextMobject("\# of students", color = highlight_color) - nb_students_label.move_to(5 * RIGHT + 1 * UP) - arrows = VGroup(*[ - Arrow(nb_students_label.get_left(), grade_hist.bars[i].get_center(), - color = highlight_color) - for i in range(4) - ]) - self.wait() - self.play(Write(nb_students_label), LaggedStartMap(GrowArrow,arrows)) - - percentage_label = TextMobject("\% of students", color = highlight_color) - percentage_label.move_to(nb_students_label) - percentages = hist_y_values / (nb_students_x * nb_students_y) * 100 - anims = [] - for (label, percentage) in zip(grade_hist.y_labels_group, percentages): - new_label = DecimalNumber(percentage, - num_decimal_places = 1, - unit = "\%", - color = highlight_color - ) - new_label.scale(0.7) - new_label.move_to(label) - anims.append(Transform(label, new_label)) - anims.append(ReplacementTransform(nb_students_label, percentage_label)) - self.wait() - self.play(*anims) - - self.remove(all_quizzes) - # put small copy of class in corner - for (i,pi) in enumerate(all_students): - x = i % 5 - y = i / 5 - pi.move_to(x * RIGHT + y * UP) - all_students.scale(0.8) - all_students.to_corner(DOWN + LEFT) - self.wait() - self.play(FadeIn(all_students)) - - prob_label = TextMobject("probability", color = highlight_color) - prob_label.move_to(percentage_label) - self.wait() - self.play( - all_students[8].set_color, MAROON_E, - #all_students[:8].fade, 0.6, - #all_students[9:].fade, 0.6, - ReplacementTransform(percentage_label, prob_label) - ) - - self.wait() - self.play( - FadeOut(prob_label), - FadeOut(arrows) - ) - - flash_hist = FlashThroughHistogram( - grade_hist, - direction = "vertical", - mode = "random", - cell_opacity = 0.5, - run_time = 5, - rate_func = linear - ) - - flash_class = FlashThroughClass( - all_students, - mode = "random", - highlight_color = MAROON_E, - run_time = 5, - rate_func = linear - ) - - self.wait() - for i in range(3): - self.play(flash_hist, flash_class) - self.remove(flash_hist.prototype_cell) - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/chapter1/show_proportion.py b/from_3b1b/on_hold/eop/chapter1/show_proportion.py deleted file mode 100644 index 9711b94f..00000000 --- a/from_3b1b/on_hold/eop/chapter1/show_proportion.py +++ /dev/null @@ -1,145 +0,0 @@ -from manimlib.imports import * - - -class ProbabilityRect(VMobject): - CONFIG = { - "unit_width" : 2, - "unit_height" : 2, - "alignment" : LEFT, - "color": YELLOW, - "opacity": 1.0, - "num_decimal_places": 2, - "use_percent" : False - } - - def __init__(self, p0, **kwargs): - - VMobject.__init__(self, **kwargs) - self.unit_rect = Rectangle( - width = self.unit_width, - height = self.unit_height, - stroke_color = self.color - ) - self.p = p0 - self.prob_rect = self.create_prob_rect(p0) - self.prob_label = self.create_prob_label(p0) - - self.add(self.unit_rect, self.prob_rect, self.prob_label) - - - def create_prob_rect(self, p): - - prob_width, prob_height = self.unit_width, self.unit_height - - if self.alignment in [LEFT, RIGHT]: - prob_width *= p - elif self.alignment in [UP, DOWN]: - prob_height *= p - else: - raise Exception("Aligment must be LEFT, RIGHT, UP or DOWN") - - prob_rect = Rectangle( - width = prob_width, - height = prob_height, - fill_color = self.color, - fill_opacity = self.opacity, - stroke_color = self.color - ) - - prob_rect.align_to(self.unit_rect, direction = self.alignment) - return prob_rect - - - def create_prob_label(self, p): - - if self.use_percent: - prob_label = DecimalNumber( - p * 100, - color = BLACK, - num_decimal_places = self.num_decimal_places, - unit = "\%" - ) - else: - prob_label = DecimalNumber( - p, - color = BLACK, - num_decimal_places = self.num_decimal_places, - ) - - prob_label.move_to(self.prob_rect) - - return prob_label - - -class ChangeProbability(Animation): - - def __init__(self, prob_mob, p1, **kwargs): - - if not isinstance(prob_mob, ProbabilityRect): - raise Exception("ChangeProportion's mobject must be a ProbabilityRect") - - self.p1 = p1 - self.p0 = prob_mob.p - Animation.__init__(self, prob_mob, **kwargs) - - - def interpolate_mobject(self, alpha): - - p = (1 - alpha) * self.p0 + alpha * self.p1 - self.mobject.remove(self.mobject.prob_rect, self.mobject.prob_label) - self.mobject.prob_rect = self.mobject.create_prob_rect(p) - self.mobject.prob_label = self.mobject.create_prob_label(p) - self.mobject.add(self.mobject.prob_rect, self.mobject.prob_label) - - - def clean_up_from_scene(self, scene=None): - self.mobject.p = self.p1 - super(ChangeProbability, self).clean_up_from_scene(scene = scene) - - - - - -class ShowProbAsProportion(Scene): - - def construct(self): - - p0 = 0.3 - p1 = 1 - p2 = 0.18 - p3 = 0.64 - - prob_mob = ProbabilityRect(p0, - unit_width = 4, - unit_height = 2, - use_percent = False, - num_decimal_places = 2 - ) - - self.add(prob_mob) - self.wait() - self.play( - ChangeProbability(prob_mob, p1, - run_time = 3) - ) - self.wait(0.5) - self.play( - ChangeProbability(prob_mob, p2, - run_time = 3) - ) - self.wait(0.5) - self.play( - ChangeProbability(prob_mob, p3, - run_time = 3) - ) - - - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/chapter1/show_uncertainty_darts.py b/from_3b1b/on_hold/eop/chapter1/show_uncertainty_darts.py deleted file mode 100644 index e8994b2a..00000000 --- a/from_3b1b/on_hold/eop/chapter1/show_uncertainty_darts.py +++ /dev/null @@ -1,45 +0,0 @@ -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - - -class ShowUncertaintyDarts(Scene): - - - def throw_darts(self, n, run_time = 1): - - points = np.random.normal( - loc = self.dartboard.get_center(), - scale = 0.6 * np.ones(3), - size = (n,3) - ) - points[:,2] = 0 - dots = VGroup() - for point in points: - dot = Dot(point, radius = 0.04, fill_opacity = 0.7) - dots.add(dot) - self.add(dot) - - self.play( - LaggedStartMap(FadeIn, dots, lag_ratio = 0.01, run_time = run_time) - ) - - - def construct(self): - - self.dartboard = ImageMobject("dartboard").scale(2) - dartboard_circle = Circle( - radius = self.dartboard.get_width() / 2, - fill_color = BLACK, - fill_opacity = 0.5, - stroke_color = WHITE, - stroke_width = 5 - ) - self.dartboard.add(dartboard_circle) - - self.add(self.dartboard) - - self.throw_darts(5,5) - self.throw_darts(20,5) - self.throw_darts(100,5) - self.throw_darts(1000,5) - diff --git a/from_3b1b/on_hold/eop/chapter1/show_uncertainty_dice.py b/from_3b1b/on_hold/eop/chapter1/show_uncertainty_dice.py deleted file mode 100644 index 1d6dcd1f..00000000 --- a/from_3b1b/on_hold/eop/chapter1/show_uncertainty_dice.py +++ /dev/null @@ -1,63 +0,0 @@ -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - -class ShowUncertaintyDice(Scene): - - def throw_a_die(self, run_time = 0.3): - - eye = np.random.randint(1,7) - face = self.row_of_dice.submobjects[eye - 1] - - self.play( - ApplyMethod(face.submobjects[0].set_fill, {"opacity": 1}, - rate_func = there_and_back, - run_time = run_time, - ), - ) - - def construct(self): - - self.row_of_dice = RowOfDice(direction = DOWN).scale(0.5) - self.add(self.row_of_dice) - - for i in range(5): - self.throw_a_die() - self.wait(1) - - for i in range(10): - self.throw_a_die() - self.wait(0.3) - - for i in range(100): - self.throw_a_die(0.05) - self.wait(0.0) - - - -class IdealizedDieHistogram(Scene): - - def construct(self): - - self.probs = 1.0/6 * np.ones(6) - x_scale = 1.3 - - y_labels = ["${1\over 6}$"] * 6 - - hist = Histogram(np.ones(6), self.probs, - mode = "widths", - x_labels = "none", - y_labels = y_labels, - y_label_position = "center", - y_scale = 20, - x_scale = x_scale, - ) - hist.rotate(-TAU/4) - - for label in hist.y_labels_group: - label.rotate(TAU/4) - hist.remove(hist.y_labels_group) - - - self.play(FadeIn(hist)) - self.play(LaggedStartMap(FadeIn, hist.y_labels_group)) - diff --git a/from_3b1b/on_hold/eop/chapter1/show_uncertainty_disease.py b/from_3b1b/on_hold/eop/chapter1/show_uncertainty_disease.py deleted file mode 100644 index c4366f4b..00000000 --- a/from_3b1b/on_hold/eop/chapter1/show_uncertainty_disease.py +++ /dev/null @@ -1,104 +0,0 @@ -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - - - -class RandyIsSickOrNot(Scene): - - - def construct(self): - title = TextMobject("1 in 200") - title.to_edge(UP) - - - randy = SicklyPiCreature() - randy.set_height(3) - randy.move_to(2*LEFT) - randy.change_mode("plain") - randy.set_color(BLUE) - randy.save_state() - - self.add(randy) - - p_sick = TexMobject("p(","\\text{sick}",") = 0.5\%").scale(1.7) - p_sick.set_color_by_tex("sick", SICKLY_GREEN) - p_sick.next_to(randy, UP, buff = LARGE_BUFF) - self.add(p_sick) - self.wait() - - self.play( - ApplyMethod(randy.get_slightly_sick, rate_func = there_and_back) - ) - self.play(Blink(randy)) - self.wait(2) - - self.play( - ApplyMethod(randy.get_sick) - ) - - self.play(Blink(randy)) - self.wait() - - self.play(randy.get_better) - - self.play( - ApplyMethod(randy.get_slightly_sick, rate_func = there_and_back) - ) - self.play(Blink(randy)) - self.wait(0.5) - - self.play( - ApplyMethod(randy.get_sick) - ) - - self.play(Blink(randy)) - self.play(randy.get_better) - self.wait(3) - - - -class OneIn200HasDisease(Scene): - def construct(self): - title = TextMobject("1 in 200") - title.to_edge(UP) - creature = PiCreature() - - all_creatures = VGroup(*[ - VGroup(*[ - creature.copy() - for y in range(20) - ]).arrange(DOWN, SMALL_BUFF) - for x in range(10) - ]).arrange(RIGHT, SMALL_BUFF) - all_creatures.set_height(FRAME_HEIGHT * 0.8) - all_creatures.next_to(title, DOWN) - randy = all_creatures[0][0] - all_creatures[0].remove(randy) - randy.change_mode("sick") - randy.set_color(SICKLY_GREEN) - randy.save_state() - randy.set_height(3) - randy.center() - randy.change_mode("plain") - randy.set_color(BLUE) - - self.add(randy) - - #p_sick = TexMobject("p(","\\text{sick}",") = 0.5\%") - #p_sick.set_color_by_tex("sick", SICKLY_GREEN) - #p_sick.next_to(randy, RIGHT+UP) - #self.add(p_sick) - self.wait() - - self.play( - randy.change_mode, "sick", - randy.set_color, SICKLY_GREEN - ) - self.play(Blink(randy)) - self.play(randy.restore) - self.wait() - self.play( - Write(title), - LaggedStartMap(FadeIn, all_creatures, run_time = 3) - ) - self.wait() diff --git a/from_3b1b/on_hold/eop/chapter1/stacking_coins.py b/from_3b1b/on_hold/eop/chapter1/stacking_coins.py deleted file mode 100644 index 3d69a077..00000000 --- a/from_3b1b/on_hold/eop/chapter1/stacking_coins.py +++ /dev/null @@ -1,29 +0,0 @@ -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - - -class StackingCoins(Scene): - - def construct(self): - - h = t = 0 - heads_stack = HeadsStack(size = h) - heads_stack.next_to(0.5*LEFT + 3*DOWN, UP) - tails_stack = TailsStack(size = t) - tails_stack.next_to(0.5*RIGHT + 3*DOWN, UP) - self.add(heads_stack, tails_stack) - - for i in range(120): - flip = np.random.choice(["H", "T"]) - if flip == "H": - h += 1 - new_heads_stack = HeadsStack(size = h) - new_heads_stack.next_to(0.5*LEFT + 3*DOWN, UP) - self.play(Transform(heads_stack, new_heads_stack, - run_time = 0.2)) - elif flip == "T": - t += 1 - new_tails_stack = TailsStack(size = t) - new_tails_stack.next_to(0.5*RIGHT + 3*DOWN, UP) - self.play(Transform(tails_stack, new_tails_stack, - run_time = 0.2)) diff --git a/from_3b1b/on_hold/eop/chapter1/think_about_coin.py b/from_3b1b/on_hold/eop/chapter1/think_about_coin.py deleted file mode 100644 index 12ba8f40..00000000 --- a/from_3b1b/on_hold/eop/chapter1/think_about_coin.py +++ /dev/null @@ -1,31 +0,0 @@ - -from manimlib.imports import * -from active_projects.eop.reusable_imports import * - -class RandyThinksAboutCoin(PiCreatureScene): - - def construct(self): - - randy = self.get_primary_pi_creature() - randy.center() - self.add(randy) - self.wait() - h_or_t = BinaryOption(UprightHeads().scale(3), UprightTails().scale(3), - text_scale = 1.5) - self.think(h_or_t, direction = LEFT) - - v = 0.3 - self.play( - h_or_t[0].shift,v*UP, - h_or_t[2].shift,v*DOWN, - ) - self.play( - h_or_t[0].shift,2*v*DOWN, - h_or_t[2].shift,2*v*UP, - ) - self.play( - h_or_t[0].shift,v*UP, - h_or_t[2].shift,v*DOWN, - ) - - self.wait() diff --git a/from_3b1b/on_hold/eop/chapter1/various_intro_visuals.py b/from_3b1b/on_hold/eop/chapter1/various_intro_visuals.py deleted file mode 100644 index 6e8fc741..00000000 --- a/from_3b1b/on_hold/eop/chapter1/various_intro_visuals.py +++ /dev/null @@ -1,132 +0,0 @@ -from manimlib.imports import * -from active_projects.eop.reusable_imports import * -from active_projects.eop.combinations import * -from active_projects.eop.independence import * - -import itertools as it - -class RandyFlipsAndStacks(Scene): - - def construct(self): - - randy = CoinFlippingPiCreature(color = MAROON_E) - randy.scale(0.5).to_edge(LEFT + DOWN) - - heads = tails = 0 - tally = TallyStack(heads, tails, anchor = ORIGIN) - - nb_flips = 10 - - flips = np.random.randint(2, size = nb_flips) - - for i in range(nb_flips): - - self.play(FlipCoin(randy)) - self.wait(0.5) - - flip = flips[i] - if flip == 0: - heads += 1 - elif flip == 1: - tails += 1 - else: - raise Exception("That side does not exist on this coin") - - new_tally = TallyStack(heads, tails, anchor = ORIGIN) - - if tally.nb_heads == 0 and new_tally.nb_heads == 1: - self.play(FadeIn(new_tally.heads_stack)) - elif tally.nb_tails == 0 and new_tally.nb_tails == 1: - self.play(FadeIn(new_tally.tails_stack)) - else: - self.play(Transform(tally, new_tally)) - - tally = new_tally - - - -class TwoDiceTableScene(Scene): - - def construct(self): - - table = TwoDiceTable(cell_size = 1) - - table.center() - self.add(table) - - - - - -class VisualCovariance(Scene): - - - def construct(self): - - size = 4 - square = Square(side_length = size) - n_points = 30 - cloud = VGroup(*[ - Dot((x + 0.8*y) * RIGHT + y * UP).set_fill(WHITE, 1) - for x, y in zip( - np.random.normal(0, 1, n_points), - np.random.normal(0, 1, n_points) - ) - ]) - self.add_foreground_mobject(cloud) - - x_axis = Vector(8*RIGHT, color = WHITE).move_to(2.5*DOWN) - y_axis = Vector(5*UP, color = WHITE).move_to(4*LEFT) - - self.add(x_axis, y_axis) - - - random_pairs = [ (p1, p2) for (p1, p2) in - it.combinations(cloud, 2) - ] - np.random.shuffle(random_pairs) - - - - for (p1, p2) in random_pairs: - c1, c2 = p1.get_center(), p2.get_center() - x1, y1, x2, y2 = c1[0], c1[1], c2[0], c2[1] - if x1 >= x2: - continue - if y2 > y1: - # make a red rect - color = RED - opacity = 0.1 - - elif y2 < y1: - # make a blue rect - color = BLUE - opacity = 0.2 - - rect = Rectangle(width = x2 - x1, height = abs(y2 - y1)) - rect.set_fill(color = color, opacity = opacity) - rect.set_stroke(width = 0) - rect.move_to((c1+c2)/2) - - self.play(FadeIn(rect), run_time = 0.05) - - - - -class BinaryChoices(Scene): - - def construct(self): - - example1 = BinaryOption(UprightHeads(), UprightTails()) - example2 = BinaryOption(Male(), Female()) - example3 = BinaryOption(Checkmark(), Xmark()) - - example2.next_to(example1, DOWN, buff = MED_LARGE_BUFF) - example3.next_to(example2, DOWN, buff = MED_LARGE_BUFF) - - all = VGroup(example1, example2, example3) - all = all.scale(2) - - self.play( - LaggedStartMap(FadeIn, all) - ) diff --git a/from_3b1b/on_hold/eop/chapter1/what_does_probability_mean.py b/from_3b1b/on_hold/eop/chapter1/what_does_probability_mean.py deleted file mode 100644 index b64aa984..00000000 --- a/from_3b1b/on_hold/eop/chapter1/what_does_probability_mean.py +++ /dev/null @@ -1,43 +0,0 @@ -from manimlib.imports import * - - -class WhatDoesItReallyMean(TeacherStudentsScene): - CONFIG = { - "default_pi_creature_kwargs": { - "color": MAROON_E, - "flip_at_start": True, - }, - } - - def construct(self): - student_q = TextMobject( - "What does", "``probability''\\\\", - "\\emph{actually}", "mean?" - ) - student_q.set_color_by_tex("probability", YELLOW) - self.student_says(student_q, target_mode="sassy") - self.wait() - self.play( - self.students[1].change_mode, "confused" - ) - self.wait(2) - student_bubble = self.students[1].bubble - self.students[1].bubble = None - student_bubble.add(student_bubble.content) - self.play( - student_bubble.scale, 0.5, - student_bubble.to_corner, UL, - ) - self.teacher_says( - "Don't worry -- philosophy\\\\ can come later!", - added_anims=[self.get_student_changes(*3 * ["happy"])], - ) - self.wait(2) - self.play(RemovePiCreatureBubble(self.teacher)) - self.play(*[ - ApplyMethod(pi.look_at, ORIGIN) for pi in self.get_pi_creatures() - ]) - self.change_all_student_modes("pondering", look_at_arg=UP) - self.wait(3) - self.change_student_modes("confused", look_at_arg=UP) - self.wait(3) diff --git a/from_3b1b/on_hold/eop/chapter2/permutation_grid.py b/from_3b1b/on_hold/eop/chapter2/permutation_grid.py deleted file mode 100644 index 496cd105..00000000 --- a/from_3b1b/on_hold/eop/chapter2/permutation_grid.py +++ /dev/null @@ -1,103 +0,0 @@ -from manimlib.imports import * - -def print_permutation(index_list): - - - n = max(max(index_list), len(index_list)) - for i in range(0,n): - if index_list[i] > n - i: - raise Exception("Impossible indices!") - - #print "given index list:", index_list - perm_list = n * ["_"] - alphabet = ["A", "B", "C", "D", "E", "F", - "G", "H", "I", "J", "K", "L", - "M", "N", "O", "P", "Q", "R", - "S", "T", "U", "V", "W", "X", - "Y", "Z"] - free_indices = list(range(n)) - free_indices_p1 = list(range(1,n + 1)) - #print perm_list - for i in range(n): - findex = index_list[i] - 1 - #print "place next letter at", findex + 1, "th free place" - tindex = free_indices[findex] - #print "so at position", tindex + 1 - perm_list[tindex] = alphabet[i] - free_indices.remove(tindex) - free_indices_p1.remove(tindex + 1) - #print "remaining free places:", free_indices_p1 - #print perm_list - - return "".join(perm_list) - - -class PermutationGrid(Scene): - - def text_box(self, str): - box = TextMobject(str).scale(0.3) - box.add(SurroundingRectangle(box, stroke_color = DARK_GREY)) - return box - - - def construct(self): - - - N = 5 - - index_list = [] - perm5_box = VGroup() - for i in range(1, N + 1): - index_list.append(i) - perm4_box = VGroup() - for j in range(1, N): - index_list.append(j) - perm3_box = VGroup() - for k in range(1, N - 1): - index_list.append(k) - perm2_box = VGroup() - for l in range(1, N - 2): - index_list.append(l) - index_list.append(1) - perm_box = self.text_box(print_permutation(index_list)) - if l > 1: - perm_box.next_to(perm2_box[-1], DOWN, buff = 0) - perm2_box.add(perm_box) - index_list.pop() - index_list.pop() - if k > 1: - perm2_box.next_to(perm3_box[-1], RIGHT, buff = 0.08) - perm3_box.add(perm2_box) - index_list.pop() - perm3_box.add(SurroundingRectangle(perm3_box, buff = 0.12, stroke_color = LIGHT_GRAY)) - if j > 1: - perm3_box.next_to(perm4_box[-1], DOWN, buff = 0) - perm4_box.add(perm3_box) - index_list.pop() - if i > 1: - perm4_box.next_to(perm5_box[-1], RIGHT, buff = 0.16) - perm5_box.add(perm4_box) - index_list.pop() - - perm5_box.move_to(ORIGIN) - self.add(perm5_box) - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/combinations.py b/from_3b1b/on_hold/eop/combinations.py deleted file mode 100644 index 92522e52..00000000 --- a/from_3b1b/on_hold/eop/combinations.py +++ /dev/null @@ -1,3635 +0,0 @@ -from manimlib.imports import * - -#revert_to_original_skipping_status - -def get_stack( - obj1, obj2, n, k, - fixed_start = None, - fixed_end = None, - obj_to_obj_buff = SMALL_BUFF, - vertical_buff = MED_SMALL_BUFF, - ): - stack = VGroup() - for indices in it.combinations(list(range(n)), k): - term = VGroup(*[ - obj1.copy() if i in indices else obj2.copy() - for i in range(n) - ]) - if fixed_start: - term.add_to_back(fixed_start.copy()) - if fixed_end: - term.add(fixed_end.copy()) - term.arrange(RIGHT, buff = obj_to_obj_buff) - stack.add(term) - stack.arrange(DOWN, buff = vertical_buff) - return stack - -def get_stacks(obj1, obj2, n, **kwargs): - stacks = VGroup() - for k in range(n+1): - stacks.add(get_stack(obj1, obj2, n, k, **kwargs)) - stacks.arrange( - RIGHT, - buff = MED_LARGE_BUFF, - aligned_edge = DOWN - ) - return stacks - -class Male(TexMobject): - CONFIG = { - "height" : 0.4, - "tex" : "\\male", - "color" : BLUE, - } - def __init__(self, **kwargs): - digest_config(self, kwargs) - TexMobject.__init__(self, self.tex, **kwargs) - self.set_height(self.height) - self.set_color(self.color) - -class Female(Male): - CONFIG = { - "tex" : "\\female", - "color" : MAROON_B, - } - -class PascalsTriangle(VGroup): - CONFIG = { - "n_rows" : 9, - "distance" : 0.8, - "max_width_to_distance_ratio" : 0.7, - "angle" : 0.2*np.pi, - } - def __init__(self, **kwargs): - VGroup.__init__(self, **kwargs) - - distance = self.distance - angle = self.angle - max_width = self.max_width_to_distance_ratio * distance - t_down = rotate_vector(distance*DOWN, -angle) - t_right = 2*distance*np.sin(angle)*RIGHT - - for n in range(self.n_rows): - row = VGroup() - for k in range(n+1): - num = TexMobject(str(choose(n, k))) - num.shift(n*t_down + k*t_right) - row.add(num) - self.add(row) - self.center() - -###################### - -class ExperienceProblemSolver(PiCreatureScene): - def construct(self): - self.add_equation() - self.jenny_solves() - self.no_genius() - self.think_about_patterns() - - def add_equation(self): - equation = TexMobject( - "\\frac{x^3 + y^3}{(x+y)^2} + \\frac{3xy}{x+y}" - ) - equation.to_edge(UP) - - self.play(Write(equation)) - self.wait() - - self.equation = equation - - def jenny_solves(self): - randy, jenny = self.randy, self.jenny - jenny_words = TextMobject("It's just $x+y$") - randy_words = TextMobject("...wait...") - randy_words.next_to(randy.get_corner(UP+RIGHT), RIGHT) - - self.pi_creature_says( - jenny, jenny_words, - target_mode = "hooray", - bubble_kwargs = {"height" : 2, "width" : 3} - ) - self.wait() - self.play( - randy.change, "confused", self.equation, - Write(randy_words) - ) - self.play(randy.look_at, self.equation.get_left()) - self.play(randy.look_at, jenny.eyes) - self.play(jenny.change, "happy") - self.play(randy.change, "tired") - self.wait() - self.play(*list(map(FadeOut, [ - jenny.bubble, jenny_words, randy_words - ]))) - - def no_genius(self): - randy, jenny = self.randy, self.jenny - - lightbulb = Lightbulb() - lightbulb.next_to(jenny, UP) - cross = Cross(lightbulb) - cross.set_stroke(RED, 8) - - self.play(LaggedStartMap(ShowCreation, lightbulb)) - self.play( - ShowCreation(cross), - jenny.change, "sassy", cross, - randy.change, "happy" - ) - self.wait(2) - - self.to_fade = VGroup(lightbulb, cross) - - def think_about_patterns(self): - randy, jenny = self.randy, self.jenny - rows = PascalsTriangle( - n_rows = 6, - distance = 0.6, - ) - rows.scale(0.8) - for row in rows: - for num in row: - n = float(num.get_tex_string()) - num.set_color(interpolate_color( - BLUE, YELLOW, n/10.0 - )) - - self.pi_creature_thinks( - jenny, "", - bubble_kwargs = {"width" : 5, "height" : 4.2}, - added_anims = [ - FadeOut(self.to_fade), - FadeOut(self.equation), - randy.change, "plain" - ] - ) - rows.move_to( - jenny.bubble.get_bubble_center() + \ - MED_SMALL_BUFF*(UP+LEFT) - ) - self.play(FadeIn(rows[0])) - for last_row, curr_row in zip(rows, rows[1:]): - self.play(*[ - Transform( - last_row.copy(), VGroup(*mobs), - remover = True - ) - for mobs in (curr_row[1:], curr_row[:-1]) - ]) - self.add(curr_row) - self.wait(3) - - - - - ############ - - def create_pi_creatures(self): - randy = Randolph() - randy.to_edge(DOWN) - randy.shift(4*LEFT) - jenny = PiCreature(color = BLUE_C).flip() - jenny.to_edge(DOWN) - jenny.shift(4*RIGHT) - self.randy, self.jenny = randy, jenny - return randy, jenny - -class InitialFiveChooseThreeExample(Scene): - CONFIG = { - "n" : 5, - "zero_color" : BLUE, - "one_color" : PINK, - } - def construct(self): - self.show_all_stacks() - self.add_title() - self.show_binomial_name() - self.issolate_single_stack() - self.count_chosen_stack() - self.count_ways_to_fill_slots() - self.walk_though_notation() - self.emphasize_pattern_over_number() - - def show_all_stacks(self): - stacks = get_stacks( - self.get_obj1(), self.get_obj2(), self.n, - vertical_buff = SMALL_BUFF - ) - stacks.to_edge(DOWN, buff = MED_LARGE_BUFF) - - for stack in stacks: - self.play(FadeIn( - stack, - run_time = 0.2*len(stack), - lag_ratio = 0.5 - )) - self.wait() - - self.set_variables_as_attrs(stacks) - - def add_title(self): - n = self.n - stacks = self.stacks - - n_choose_k = TexMobject("n \\choose k") - n_choose_k_words = TextMobject("``n choose k''") - nCk_group = VGroup(n_choose_k, n_choose_k_words) - nCk_group.arrange(RIGHT) - nCk_group.to_edge(UP) - - binomials = VGroup(*[ - TexMobject("%d \\choose %d"%(n, k)) - for k in range(n+1) - ]) - binomial_equations = VGroup() - for k, binomial in enumerate(binomials): - binomial.scale(0.75) - number = TexMobject(str(choose(n, k))) - equation = VGroup(binomial, TexMobject("="), number) - equation.arrange(RIGHT, buff = SMALL_BUFF) - equation.set_color(YELLOW) - equation[1].set_color(WHITE) - binomial_equations.add(equation) - - for stack, eq in zip(stacks, binomial_equations): - eq.set_width(0.9*stack.get_width()) - eq.next_to(stack, UP) - - mover = VGroup() - for eq in binomial_equations: - point = VectorizedPoint(n_choose_k.get_center()) - group = VGroup(n_choose_k, point, point).copy() - group.target = eq - mover.add(group) - - self.play(FadeIn(nCk_group)) - self.play(LaggedStartMap( - MoveToTarget, mover, - run_time = 3, - )) - self.remove(mover) - self.add(binomial_equations) - self.wait() - - self.set_variables_as_attrs( - n_choose_k, n_choose_k_words, - binomial_equations - ) - - def show_binomial_name(self): - new_words = TextMobject("``Binomial coefficients''") - new_words.move_to(self.n_choose_k_words, LEFT) - - self.play(Transform(self.n_choose_k_words, new_words)) - self.wait(2) - - def issolate_single_stack(self): - stack = self.stacks[3] - equation = self.binomial_equations[3] - - to_fade = VGroup(*self.stacks) - to_fade.add(*self.binomial_equations) - to_fade.add(self.n_choose_k, self.n_choose_k_words) - to_fade.remove(stack, equation) - - self.play( - FadeOut(to_fade), - equation.scale, 1.5, equation.get_bottom(), - ) - self.wait() - for line in stack: - ones = VGroup(*[mob for mob in line if "1" in mob.get_tex_string()]) - line.ones = ones - self.play(LaggedStartMap( - ApplyMethod, ones, - lambda mob : (mob.set_color, YELLOW), - rate_func = there_and_back, - lag_ratio = 0.7, - run_time = 1, - )) - - def count_chosen_stack(self): - stack = self.stacks[3] - for i, line in enumerate(stack): - number = TexMobject(str(i+1)) - number.next_to(stack, LEFT) - brace = Brace(VGroup(*stack[:i+1]), LEFT) - number.next_to(brace, LEFT) - line.save_state() - line.set_color(YELLOW) - self.add(number, brace) - self.wait(0.25) - self.remove(number, brace) - line.restore() - self.add(number, brace) - self.wait() - - self.set_variables_as_attrs( - stack_brace = brace, - stack_count = number - ) - - def count_ways_to_fill_slots(self): - lines = VGroup(*[Line(ORIGIN, 0.25*RIGHT) for x in range(5)]) - lines.arrange(RIGHT) - lines.next_to(self.stacks[3], LEFT, LARGE_BUFF, UP) - - self.play(ShowCreation(lines)) - count = 1 - for indices in it.combinations(list(range(5)), 3): - ones = VGroup(*[ - self.get_obj1().next_to(lines[i], UP) - for i in indices - ]) - num = TexMobject(str(count)) - num.next_to(lines, DOWN) - self.add(ones, num) - self.wait(0.35) - self.remove(ones, num) - count += 1 - self.add(num, ones) - self.wait() - self.play(*list(map(FadeOut, [lines, num, ones]))) - - def walk_though_notation(self): - equation = self.binomial_equations[3] - rect = SurroundingRectangle(equation[0]) - rect.set_color(WHITE) - words = TextMobject("``5 choose 3''") - words.next_to(rect, UP) - - self.play(Write(words)) - self.play(ShowCreation(rect)) - self.play(FadeOut(rect)) - self.wait(2) - - def emphasize_pattern_over_number(self): - morty = Mortimer().flip() - morty.to_corner(DOWN+LEFT) - words = TextMobject("Remember the pattern \\\\ not the number") - words.next_to(morty, UP) - words.shift_onto_screen() - - self.play(FadeIn(morty)) - self.play( - morty.change, "speaking", - Write(words, run_time = 2) - ) - self.play( - Blink(morty), - morty.change, "happy" - ) - self.revert_to_original_skipping_status() - last_ones = VGroup() - last_ones.save_state() - for x in range(2): - for line in self.stacks[3]: - ones = line.ones - ones.save_state() - self.play( - ones.set_color, YELLOW, - last_ones.restore, - morty.look_at, ones, - run_time = 0.25 - ) - last_ones = ones - self.wait() - - #### - - def get_obj1(self): - return TexMobject("1").set_color(self.one_color) - - def get_obj2(self): - return TexMobject("0").set_color(self.zero_color) - -class SixChooseThreeExample(InitialFiveChooseThreeExample): - CONFIG = { - "n" : 6, - "k" : 3, - "stack_height" : 7, - } - def construct(self): - self.show_stack() - self.talk_through_one_line() - self.count_stack() - self.think_about_pattern() - - def show_stack(self): - stack = get_stack( - self.get_obj1(), self.get_obj2(), - self.n, self.k, - vertical_buff = SMALL_BUFF - ) - stack.set_height(self.stack_height) - stack.to_edge(DOWN) - for line in stack: - line.ones = VGroup(*[mob for mob in line if "1" in mob.get_tex_string()]) - - equation = TexMobject( - "{%d \\choose %d}"%(self.n, self.k), - "=", str(choose(self.n, self.k)) - ) - equation.set_color(YELLOW) - equation.set_color_by_tex("=", WHITE) - equation.next_to(stack, RIGHT, LARGE_BUFF) - - self.add(equation) - self.play(LaggedStartMap( - FadeIn, stack, - lag_ratio = 0.1, - run_time = 10, - )) - self.wait() - - self.set_variables_as_attrs(stack) - - def talk_through_one_line(self): - line = self.stack[8] - line.save_state() - distance = FRAME_X_RADIUS/2 - - self.play(line.shift, distance*LEFT) - - brace = Brace(line, UP) - n_options = TextMobject(str(self.n), "options") - n_options.set_color_by_tex(str(self.n), YELLOW) - n_options.next_to(brace, UP) - arrows = VGroup(*[ - Vector(0.5*UP).next_to(one, DOWN, SMALL_BUFF) - for one in line.ones - ]) - arrows.set_color(self.one_color) - choose_k = TextMobject("Choose", str(self.k), "of them") - choose_k.set_color_by_tex(str(self.k), YELLOW) - choose_k.next_to(arrows, DOWN) - - self.play( - GrowFromCenter(brace), - Write(n_options), - run_time = 1 - ) - self.play( - LaggedStartMap(GrowArrow, arrows), - Write(choose_k, run_time = 1) - ) - self.wait(2) - self.play( - line.restore, - *list(map(FadeOut, [brace, n_options, arrows, choose_k])) - ) - - def count_stack(self): - stack = self.stack - for i, line in enumerate(stack): - brace = Brace(VGroup(*stack[:i+1]), LEFT) - num = TexMobject(str(i+1)) - num.next_to(brace, LEFT) - line.ones.save_state() - line.ones.set_color(YELLOW) - line.ones.set_stroke(RED, 1) - self.add(brace, num) - self.wait(0.15) - self.remove(brace, num) - line.ones.restore() - self.add(brace, num) - self.wait() - - lhs = TexMobject( - "\\frac{6 \\cdot 5 \\cdot 3}{1 \\cdot 2 \\cdot 3} =" - ) - lhs.next_to(num, LEFT) - coming_soon = TextMobject("Coming soon...") - coming_soon.next_to(lhs, UP) - coming_soon.set_color(MAROON_B) - - self.play(*list(map(FadeIn, [lhs, coming_soon]))) - self.wait() - self.play( - ApplyMethod( - lhs.shift, 0.65*FRAME_X_RADIUS*(LEFT+UP), - path_arc = np.pi/2, - rate_func = running_start, - remover = True, - ), - *list(map(FadeOut, [brace, num, coming_soon])) - ) - self.wait() - - def think_about_pattern(self): - self.revert_to_original_skipping_status() - last_ones = VGroup() - last_ones.save_state() - for x in range(2): - for line in self.stack: - ones = line.ones - ones.save_state() - self.play( - ones.set_color, YELLOW, - ones.set_stroke, RED, 1, - last_ones.restore, - run_time = 0.2 - ) - last_ones = ones - self.wait() - -class SixChooseThreeInOtherContext(Scene): - def construct(self): - self.add_dots() - self.count_paths_to_three_three() - - def add_dots(self): - n = 4 - dots = VGroup(*[Dot() for x in range(n**2)]) - dots.arrange_in_grid(n, n, buff = LARGE_BUFF) - dots.next_to(ORIGIN, LEFT) - self.add(dots) - - self.dots = dots - self.dot_to_dot_distance = get_norm( - dots[1].get_center() - dots[0].get_center() - ) - - def count_paths_to_three_three(self): - dots = self.dots - d = self.dot_to_dot_distance - lower_left = dots.get_corner(DOWN+LEFT) - lower_left += dots[0].radius*(UP+RIGHT) - - right = Vector(d*RIGHT, color = PINK) - up = Vector(d*UP, color = BLUE) - - last_rights = None - last_ups = None - last_line = None - for indices in it.combinations(list(range(6)), 3): - bools = [i in indices for i in range(6)] - arrows = VGroup(*[ - right.deepcopy() if b else up.deepcopy() - for b in bools - ]) - last_point = np.array(lower_left) - ups, rights = VGroup(), VGroup() - for arrow, b in zip(arrows, bools): - arrow.shift(last_point - arrow.get_start()) - last_point = arrow.get_end() - group = rights if b else ups - group.add(arrow) - - line = VGroup(*[arrow.tip.copy() for arrow in arrows]) - line.arrange(RIGHT, buff = 0.5*SMALL_BUFF) - if last_line is None: - line.shift(FRAME_X_RADIUS*RIGHT/2) - line.to_edge(UP) - self.play( - ShowCreation(arrows), - ShowCreation(line) - ) - else: - line.next_to(last_line, DOWN, SMALL_BUFF) - self.play( - FadeIn(line), - ReplacementTransform(last_rights, rights), - ReplacementTransform(last_ups, ups), - ) - last_rights = rights - last_ups = ups - last_line = line - self.wait() - -# class Introduction(Scene): -# CONFIG = { -# "start_n" : 4, -# } -# def construct(self): -# self.write_n_choose_k() -# self.show_binomial_coefficients() -# self.perform_shift() - -# def write_n_choose_k(self): -# symbol = TexMobject("n \\choose k") -# words = TextMobject("``n choose k''") -# group = VGroup(symbol, words) -# group.arrange(RIGHT) - -# self.play( -# FadeIn(symbol), -# Write(words) -# ) -# self.wait() - -# self.set_variables_as_attrs(n_choose_k_group = group) - -# def show_binomial_coefficients(self): -# n = self.start_n -# n_choose_k, n_choose_k_words = self.n_choose_k_group -# binomials = VGroup(*[ -# TexMobject("%d \\choose %d"%(n, k)) -# for k in range(n+1) -# ]) -# binomial_equations = VGroup() -# for k, binomial in enumerate(binomials): -# binomial.scale(0.75) -# number = TexMobject(str(choose(n, k))) -# equation = VGroup(binomial, TexMobject("="), number) -# equation.arrange(RIGHT, buff = SMALL_BUFF) -# equation.set_color(YELLOW) -# equation[1].set_color(WHITE) -# binomial_equations.add(equation) -# new_words = TextMobject("``Binomial coefficients''") - -# stacks = get_stacks( -# TexMobject("x").set_color(BLUE), -# TexMobject("y").set_color(RED), -# n -# ) -# stacks.to_edge(DOWN, buff = LARGE_BUFF) -# for stack, eq in zip(stacks, binomial_equations): -# eq.set_width(0.9*stack.get_width()) -# eq.next_to(stack, UP) - -# self.play( -# FadeIn(stacks, run_time = 2, lag_ratio = 0.5), -# self.n_choose_k_group.to_edge, UP -# ) -# new_words.move_to(n_choose_k_words, LEFT) -# self.play(Transform(n_choose_k_words, new_words)) -# for eq in binomial_equations: -# point = VectorizedPoint(n_choose_k.get_center()) -# self.play(ReplacementTransform( -# VGroup(n_choose_k, point, point).copy(), -# eq -# )) -# self.wait() - -# self.set_variables_as_attrs(stacks, binomial_equations) - -# def perform_shift(self): -# n = self.start_n -# to_fade = VGroup( -# self.n_choose_k_group, -# self.binomial_equations -# ) -# stacks = self.stacks -# top_stacks = stacks.copy() -# top_stacks.to_edge(UP, buff = MED_SMALL_BUFF) - -# line = Line(LEFT, RIGHT, color = WHITE) -# line.scale(FRAME_X_RADIUS) -# line.next_to(top_stacks, DOWN) - -# x = TexMobject("x").set_color(BLUE) -# y = TexMobject("y").set_color(RED) -# add_x, add_y = [ -# TextMobject("Prepend", "$%s$"%s).set_color_by_tex(s, color) -# for s, color in ("x", BLUE), ("y", RED) -# ] -# add_x.to_corner(UP+LEFT) -# add_y.to_edge(LEFT).shift(MED_SMALL_BUFF*DOWN) - -# new_stacks, new_top_stacks = [ -# get_stacks(x, y, n, fixed_start = var) -# for var in y, x -# ] -# new_top_stacks.to_edge(UP, buff = MED_SMALL_BUFF) -# new_stacks.to_edge(DOWN) -# for s in new_stacks, new_top_stacks: -# s.start_terms = VGroup() -# for stack in s: -# for term in stack: -# s.start_terms.add(term[0]) - -# s_to_s_distance = \ -# new_stacks[1].get_center()[0] - \ -# new_stacks[0].get_center()[0] - -# self.play( -# FadeOut(to_fade), -# stacks.to_edge, DOWN, -# ReplacementTransform(stacks.copy(), top_stacks), -# ) -# self.play(ShowCreation(line)) -# self.play(Write(add_x, run_time = 1)) -# self.play(Transform(top_stacks, new_top_stacks)) -# self.play(LaggedStartMap( -# Indicate, new_top_stacks.start_terms, -# rate_func = there_and_back, -# run_time = 1, -# remover = True -# )) -# self.wait() -# self.play(Write(add_y, run_time = 1)) -# self.play(Transform(stacks, new_stacks)) -# self.play(LaggedStartMap( -# Indicate, new_stacks.start_terms, -# rate_func = there_and_back, -# run_time = 1, -# remover = True -# )) -# self.wait() - -# self.play( -# top_stacks.shift, s_to_s_distance*RIGHT/2, -# stacks.shift, s_to_s_distance*LEFT/2, -# ) -# self.play(*map(FadeOut, [add_x, add_y, line])) - -# point = VectorizedPoint() -# point.move_to(top_stacks[0].get_bottom()) -# point.shift(s_to_s_distance*LEFT) -# top_stacks.add_to_back(point) - -# point = VectorizedPoint() -# point.move_to(stacks[-1].get_bottom()) -# point.shift(s_to_s_distance*RIGHT) -# point.shift(MED_SMALL_BUFF*DOWN) -# stacks.add(point) - -# for k, stack, top_stack in zip(it.count(), stacks, top_stacks): -# top_stack.generate_target() -# top_stack.target.next_to(stack, UP, MED_SMALL_BUFF) -# # term = TexMobject( -# # str(choose(n+1, k)), -# # "x^%d"%(n+1-k), -# # "y^%d"%k -# # ) -# term = TexMobject( -# "{%d \\choose %d}"%(n+1, k), -# "=", -# str(choose(n+1, k)) -# ) -# term[0].scale(0.85, about_point = term[0].get_right()) -# term[0].set_color(YELLOW) -# term[2].set_color(YELLOW) -# term.scale(0.85) -# term.next_to(top_stack.target, UP) - -# self.play(MoveToTarget(top_stack)) -# self.play(Write(term)) -# self.wait() - -# class DifferentWaysToThinkAboutNChooseK(Scene): -# CONFIG = { -# "n" : 5, -# "k" : 3, -# "stack_height" : 5, -# } -# def construct(self): -# self.add_n_choose_k_term() -# self.add_stack() -# self.choose_k() -# self.split_stack_by_start() -# self.split_choices_by_start() - -# def add_n_choose_k_term(self): -# term = TexMobject("{5 \\choose 3} = 10") -# term.to_edge(UP) -# self.play(FadeIn(term, lag_ratio = 0.5)) -# self.wait() - -# self.n_choose_k_term = term - -# def add_stack(self): -# n, k = self.n, self.k -# x = TexMobject("x").set_color(BLUE) -# y = TexMobject("y").set_color(RED) -# stack = get_stack(x, y, n, k) -# stack.set_height(self.stack_height) -# stack.shift(FRAME_X_RADIUS*LEFT/2) -# stack.to_edge(DOWN) -# numbers = VGroup(*[ -# TexMobject("%d"%(d+1)) -# for d in range(choose(n, k)) -# ]) -# numbers.next_to(stack, UP) - -# last_number = None -# for term, number in zip(stack, numbers): -# self.add(term, number) -# if last_number: -# self.remove(last_number) -# self.wait(0.25) -# last_number = number -# self.wait() - -# self.stack = stack -# self.stack_count = last_number -# self.numbers = numbers - -# def choose_k(self): -# n, k = self.n, self.k - -# letter_set = TexMobject( -# "(", -# "A", ",", -# "B", ",", -# "C", ",", -# "D", ",", -# "E", ")" -# ) -# letters = VGroup(*letter_set[1::2]) -# letter_set.shift(FRAME_X_RADIUS*RIGHT/2) -# letter_set.to_edge(UP) - -# letter_subsets = list(it.combinations(letters, k)) -# subset_mobs = VGroup(*[ -# VGroup(*letter_subset).copy().arrange( -# RIGHT, buff = SMALL_BUFF -# ) -# for letter_subset in letter_subsets -# ]).arrange(DOWN, buff = MED_SMALL_BUFF) -# subset_mobs.set_height(self.stack_height) -# subset_mobs.shift(FRAME_X_RADIUS*RIGHT/2) -# subset_mobs.to_edge(DOWN) - -# choose_words = TextMobject("Choose %d"%k) -# choose_words.scale(0.9) -# choose_words.next_to(letter_set, DOWN) -# choose_words.set_color(YELLOW) - -# self.revert_to_original_skipping_status() -# self.play(Write(letter_set, run_time = 1)) -# self.play( -# Write(choose_words, run_time = 1), -# LaggedStartMap(FadeIn, subset_mobs) -# ) -# self.wait() -# for subset, subset_mob in zip(letter_subsets, subset_mobs): -# VGroup(subset_mob, *subset).set_color(BLUE) -# self.wait(0.5) -# VGroup(*subset).set_color(WHITE) -# self.wait() - -# self.set_variables_as_attrs( -# subset_mobs, letter_set, choose_words, -# ) - -# def split_stack_by_start(self): -# n, k = self.n, self.k -# stack = self.stack -# stack_count = self.stack_count - -# top_num = choose(n-1, k-1) -# top_stack = VGroup(*stack[:top_num]) -# bottom_stack = VGroup(*stack[top_num:]) - -# self.play( -# FadeOut(stack_count), -# top_stack.shift, UP -# ) -# for stack, new_k in (top_stack, k-1), (bottom_stack, k): -# brace = Brace(stack, RIGHT) -# brace_tex = brace.get_tex( -# "{%d \\choose %d} = %d"%(n-1, new_k, choose(n-1, new_k)) -# ) -# rect = SurroundingRectangle(VGroup(*[ -# VGroup(*term[1:]) -# for term in stack -# ]), buff = 0.5*SMALL_BUFF) -# rect.set_stroke(WHITE, 2) -# self.play( -# GrowFromCenter(brace), -# Write(brace_tex), -# ShowCreation(rect) -# ) -# self.wait() - -# def split_choices_by_start(self): -# subset_mobs = self.subset_mobs -# subset_mobs.generate_target() -# subset_mobs.target.shift(LEFT) -# brace = Brace(subset_mobs.target, RIGHT) -# expression = brace.get_tex( -# "\\frac{5 \\cdot 4 \\cdot 3}{1 \\cdot 2 \\cdot 3}", -# "= 10" -# ) - -# self.play( -# MoveToTarget(subset_mobs), -# GrowFromCenter(brace) -# ) -# self.play(Write(expression)) -# self.wait() - -# class FormulaVsPattern(TeacherStudentsScene): -# def construct(self): -# self.show_formula() -# self.show_pattern() - -# def show_formula(self): -# formula = TexMobject( -# "{n \\choose k} = {n! \\over (n-k)!k!}", -# ) -# for i in 1, 5, 9: -# formula[i].set_color(BLUE) -# for i in 2, 11, 14: -# formula[i].set_color(YELLOW) - -# self.student_thinks(formula, student_index = 1) -# self.play(self.teacher.change, "sassy") -# self.wait(2) -# self.play( -# FadeOut(self.students[1].bubble), -# FadeOut(formula), -# self.teacher.change, "raise_right_hand", -# self.get_student_changes(*["pondering"]*3) -# ) - -# def show_pattern(self): -# words = TextMobject( -# "What is the \\\\ probability of a flush?" -# ) -# values = random.sample(PlayingCard.CONFIG["possible_values"], 5) -# cards = VGroup(*[ -# PlayingCard(value = value, suit = "hearts") -# for value in values -# ]) -# cards.arrange(RIGHT) -# cards.to_corner(UP+RIGHT) -# words.next_to(cards, LEFT) -# words.shift_onto_screen() - -# self.play(LaggedStartMap(DrawBorderThenFill, cards)) -# self.play(Write(words)) -# self.wait(3) - -class ProbabilityOfKWomenInGroupOfFive(Scene): - CONFIG = { - "random_seed" : 0, - "n_people_per_lineup" : 5, - "n_examples" : 18, - "item_line_width" : 0.4, - } - def construct(self): - self.ask_question() - self.show_all_possibilities() - self.stack_all_choices_by_number_of_women() - self.go_through_stacks() - self.remember_this_sensation() - self.show_answer_to_question() - self.ask_about_pattern() - - def ask_question(self): - title = TextMobject("5 randomly chosen people") - title.to_edge(UP) - self.add(title) - - lineup_point = 1.5*UP - prob_words = VGroup(*[ - TextMobject( - "Probability of", str(n), "women?" - ).set_color_by_tex(str(n), YELLOW) - for n in range(self.n_people_per_lineup+1) - ]) - prob_words.arrange(DOWN) - prob_words.next_to(lineup_point, DOWN, MED_LARGE_BUFF) - - def get_lineup(): - lineup = self.get_random_lineup_of_men_and_women() - lineup.scale(1.5) - lineup.move_to(lineup_point, DOWN) - return lineup - - last_lineup = get_lineup() - self.play(LaggedStartMap(FadeIn, last_lineup, run_time = 1)) - - for x in range(self.n_examples): - lineup = get_lineup() - anims = [last_lineup.items.fade, 1] - anims += list(map(GrowFromCenter, lineup.items)) - if x >= 12 and x-12 < len(prob_words): - anims.append(FadeIn(prob_words[x-12])) - self.play(*anims, run_time = 0.75) - self.remove(last_lineup) - self.add(lineup) - self.wait(0.25) - last_lineup = lineup - - self.title = title - self.prob_words = prob_words - self.lineup = last_lineup - - def show_all_possibilities(self): - man, woman = Male(), Female() - - vects = [ - 1.5*UP, - 0.65*UP, - 0.25*UP, - 3.5*RIGHT, - 1.5*RIGHT, - ] - lineup_groups = VGroup() - for k in range(6): - lineup_group = VGroup() - for tup in it.product(*[[woman, man]]*k): - lineup = self.get_lineup(*list(tup) + (5-k)*[None]) - lineup.scale(1.4*(0.9)**k) - lineup.move_to(0.5*DOWN) - for mob, vect in zip(tup, vects): - if mob is woman: - lineup.shift(vect) - else: - lineup.shift(-vect) - lineup_group.add(lineup) - lineup_groups.add(lineup_group) - - n_possibilities = TexMobject( - "2 \\cdot", "2 \\cdot", "2 \\cdot", "2 \\cdot", "2", - "\\text{ Possibilities}" - ) - n_possibilities.next_to(self.title, DOWN) - twos = VGroup(*n_possibilities[-2::-1]) - twos.set_color(YELLOW) - two_anims = [ - ReplacementTransform( - VectorizedPoint(twos[0].get_center()), - twos[0] - ) - ] + [ - ReplacementTransform(t1.copy(), t2) - for t1, t2 in zip(twos, twos[1:]) - ] - - curr_lineup_group = lineup_groups[0] - self.play( - ReplacementTransform(self.lineup, curr_lineup_group[0]), - FadeOut(self.prob_words) - ) - for i, lineup_group in enumerate(lineup_groups[1:]): - anims = [ReplacementTransform(curr_lineup_group, lineup_group)] - anims += two_anims[:i+1] - if i == 0: - anims.append(FadeIn(n_possibilities[-1])) - self.remove(twos) - self.play(*anims) - - men, women = VGroup(), VGroup() - for lineup in lineup_group: - item = lineup.items[i] - if "female" in item.get_tex_string(): - women.add(item) - else: - men.add(item) - for group in men, women: - self.play(LaggedStartMap( - ApplyMethod, group, - lambda m : (m.shift, MED_SMALL_BUFF*RIGHT), - rate_func = there_and_back, - lag_ratio = 0.9**i, - run_time = 1, - )) - self.wait() - curr_lineup_group = lineup_group - self.lineups = curr_lineup_group - - eq_32 = TexMobject("=", "32") - eq_32.move_to(twos.get_right()) - eq_32.set_color_by_tex("32", YELLOW) - self.play( - n_possibilities[-1].next_to, eq_32, RIGHT, - twos.next_to, eq_32, LEFT, - FadeIn(eq_32), - ) - self.wait() - - n_possibilities.add(*eq_32) - self.set_variables_as_attrs(n_possibilities) - - def stack_all_choices_by_number_of_women(self): - lineups = self.lineups - stacks = VGroup(*[VGroup() for x in range(6)]) - for lineup in lineups: - lineup.women = VGroup(*[m for m in lineup.items if "female" in m.get_tex_string()]) - stacks[len(lineup.women)].add(lineup) - stacks.generate_target() - stacks.target.scale(0.75) - for stack in stacks.target: - stack.arrange(DOWN, buff = 1.5*SMALL_BUFF) - stacks.target.arrange( - RIGHT, buff = MED_LARGE_BUFF, aligned_edge = DOWN - ) - stacks.target.to_edge(DOWN) - - self.play(MoveToTarget( - stacks, - run_time = 2, - path_arc = np.pi/2 - )) - self.wait() - - self.stacks = stacks - - def go_through_stacks(self): - stacks = self.stacks - n = len(stacks) - 1 - equations = VGroup() - for k, stack in enumerate(stacks): - items = VGroup() - lines = VGroup() - women = VGroup() - for lineup in stack: - items.add(lineup.items) - lines.add(lineup.lines) - for item in lineup.items: - if "female" in item.get_tex_string(): - women.add(item) - equation = TexMobject( - "{%d \\choose %d}"%(n, k), - "=", - str(len(stack)) - ) - equation[0].scale_in_place(0.6) - equation.arrange(RIGHT, SMALL_BUFF) - equation.set_color(YELLOW) - equation.set_color_by_tex("=", WHITE) - equation.next_to(stack, UP) - equations.add(equation) - - self.play( - items.set_fill, None, 1, - lines.set_stroke, WHITE, 3, - Write(equation, run_time = 1) - ) - self.play(LaggedStartMap(Indicate, women, rate_func = there_and_back)) - self.wait() - - self.equations = equations - self.numbers = VGroup(*[eq[-1] for eq in equations]) - - def remember_this_sensation(self): - n_possibilities = self.n_possibilities - n_possibilities_rect = SurroundingRectangle(n_possibilities) - twos = VGroup(*n_possibilities[:5]) - numbers = self.numbers - - self.play(ShowCreation(n_possibilities_rect)) - self.play(LaggedStartMap( - Indicate, twos, - rate_func = wiggle - )) - self.play(FadeOut(n_possibilities_rect)) - for number in numbers: - self.play(Indicate(number, color = PINK, run_time = 0.5)) - self.wait() - - def show_answer_to_question(self): - stacks = self.stacks - numbers = self.numbers - n_possibilities = VGroup( - self.n_possibilities[-1], - self.n_possibilities[-3] - ) - n_possibilities_part_to_fade = VGroup( - self.n_possibilities[-2], - *self.n_possibilities[:-3] - ) - total = n_possibilities[-1] - title = self.title - n = self.n_people_per_lineup - - self.play( - FadeOut(title), - FadeOut(n_possibilities_part_to_fade), - n_possibilities.to_corner, UP+RIGHT - ) - for k, stack, num in zip(it.count(), stacks, numbers): - rect = SurroundingRectangle(stack) - num.save_state() - prob_words = TexMobject( - "P(", "\\#", "\\female", "=", str(k), ")" - "=", "{\\quad \\over", "32}", - "\\approx", "%0.3f"%(choose(n, k)/32.0) - ) - prob_words.set_color_by_tex_to_color_map({ - "female" : MAROON_B, - "32" : YELLOW, - }) - frac_line = prob_words.get_parts_by_tex("over") - prob_words.to_corner(UP+LEFT) - - self.play( - num.next_to, frac_line, UP, SMALL_BUFF, - FadeIn(prob_words) - ) - self.play(ShowCreation(rect)) - self.wait(2) - self.play( - num.restore, - FadeOut(rect), - FadeOut(prob_words) - ) - - def ask_about_pattern(self): - question = TextMobject("Where do these \\\\ numbers come from?") - question.to_edge(UP) - numbers = self.numbers - circles = VGroup(*[ - Circle().replace(num, dim_to_match = 1).scale_in_place(1.5) - for num in numbers - ]) - circles.set_color(WHITE) - - self.play(LaggedStartMap(FadeIn, question)) - self.play(LaggedStartMap(ShowCreationThenDestruction, circles)) - self.wait(2) - - ###### - - def get_random_lineup_of_men_and_women(self): - man, woman = Male(), Female() - lineup = self.get_lineup(*[ - woman if random.choice([True, False]) else man - for y in range(self.n_people_per_lineup) - ]) - return lineup - - def get_lineup(self, *mobjects, **kwargs): - buff = kwargs.get("buff", MED_SMALL_BUFF) - lines = VGroup(*[ - Line(ORIGIN, self.item_line_width*RIGHT) - for mob in mobjects - ]) - lines.arrange(RIGHT, buff = buff) - items = VGroup() - for line, mob in zip(lines, mobjects): - item = VectorizedPoint() if mob is None else mob.copy() - item.next_to(line, UP, SMALL_BUFF) - items.add(item) - result = VGroup(lines, items) - result.lines = lines - result.items = items - return result - -class AskAboutAllPossibilities(ProbabilityOfKWomenInGroupOfFive): - def construct(self): - man, woman = Male(), Female() - all_lineups = VGroup() - for bits in it.product(*[[False, True]]*5): - mobs = [ - woman.copy() if bit else man.copy() - for bit in bits - ] - all_lineups.add(self.get_lineup(*mobs)) - brace = Brace(all_lineups, UP) - question = brace.get_text("What are all possibilities?") - - self.add(brace, question) - for lineup in all_lineups: - self.add(lineup) - self.wait(0.25) - self.remove(lineup) - -class RememberThisSensation(TeacherStudentsScene): - def construct(self): - self.teacher_says("Remember this \\\\ sensation") - self.change_student_modes("confused", "pondering", "erm") - self.wait(2) - -class TeacherHoldingSomething(TeacherStudentsScene): - def construct(self): - self.play( - self.teacher.change, "raise_right_hand", - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = 2*UP+2*RIGHT - ) - self.wait(6) - -# class GroupsOf6(Scene): -# def construct(self): -# title = TexMobject("2^6 =", "64", "\\text{ Possibilities}") -# title.to_edge(UP, buff = MED_SMALL_BUFF) -# title.set_color_by_tex("64", YELLOW) -# man, woman = Male(), Female() -# stacks = get_stacks(man, woman, 6, vertical_buff = SMALL_BUFF) -# stacks.set_height(6.25) -# stacks.to_edge(DOWN, buff = MED_SMALL_BUFF) -# women_groups = VGroup() -# for stack in stacks: -# for lineup in stack: -# group = VGroup() -# for item in lineup: -# if "female" in item.get_tex_string(): -# group.add(item) -# women_groups.add(group) - -# numbers = VGroup() -# for stack in stacks: -# number = TexMobject(str(len(stack))) -# number.next_to(stack, UP, SMALL_BUFF) -# numbers.add(number) - -# self.add(title) -# self.play(LaggedStartMap( -# LaggedStartMap, stacks, -# lambda s : (FadeIn, s), -# run_time = 3, -# )) -# self.play(Write(numbers, run_time = 3)) -# self.wait() -# self.play(LaggedStartMap( -# ApplyMethod, women_groups, -# lambda m : (m.set_color, PINK), -# lag_ratio = 0.1, -# rate_func = wiggle, -# run_time = 6, -# )) - -# class GroupsOf7(Scene): -# def construct(self): -# stack = get_stack(Male(), Female(), 7, 3) -# question = TextMobject( -# "How many groups \\\\ of 7 with 3 ", "$\\female$", "?" -# ) -# question.set_color_by_tex("female", MAROON_B) -# question.shift(1.5*UP) - -# self.add(question) -# for n, item in enumerate(stack): -# item.center() -# number = TexMobject(str(n)) -# number.next_to(ORIGIN, DOWN, LARGE_BUFF) -# self.add(item, number) -# self.wait(0.2) -# self.remove(item, number) -# self.add(item, number) -# self.wait(2) - -class BuildFiveFromFour(ProbabilityOfKWomenInGroupOfFive): - def construct(self): - self.show_all_configurations_of_four() - self.organize_into_stacks() - self.walk_through_stacks() - self.split_into_two_possibilities() - self.combine_stacks() - - def show_all_configurations_of_four(self): - man, woman = Male(), Female() - n = 4 - vects = [ - 1.5*UP, - 0.5*UP, - 3.5*RIGHT, - 1.5*RIGHT, - ] - lineup_groups = VGroup() - for k in range(n+1): - lineup_group = VGroup() - for tup in it.product(*[[man, woman]]*k): - lineup = self.get_lineup(*list(tup) + (n-k)*[None]) - lineup.scale(1.4*(0.9)**k) - lineup.move_to(0.5*DOWN) - for mob, vect in zip(tup, vects): - if mob is woman: - lineup.shift(vect) - else: - lineup.shift(-vect) - lineup_group.add(lineup) - lineup_groups.add(lineup_group) - - n_possibilities = TexMobject( - "2 \\cdot", "2 \\cdot", "2 \\cdot", "2", - "\\text{ Possibilities}" - ) - n_possibilities.to_edge(UP) - twos = VGroup(*n_possibilities[-2::-1]) - two_anims = [ - ReplacementTransform( - VectorizedPoint(twos[0].get_center()), - twos[0] - ) - ] + [ - ReplacementTransform(t1.copy(), t2) - for t1, t2 in zip(twos, twos[1:]) - ] - - curr_lineup_group = lineup_groups[0] - self.play( - ShowCreation(curr_lineup_group[0]), - ) - for i, lineup_group in enumerate(lineup_groups[1:]): - anims = [ReplacementTransform(curr_lineup_group, lineup_group)] - anims += two_anims[:i+1] - if i == 0: - anims.append(FadeIn(n_possibilities[-1])) - self.remove(twos) - self.play(*anims) - self.wait() - curr_lineup_group = lineup_group - self.lineups = curr_lineup_group - - eq_16 = TexMobject("=", "16") - eq_16.move_to(twos.get_right()) - eq_16.set_color_by_tex("16", YELLOW) - self.play( - n_possibilities[-1].next_to, eq_16, RIGHT, - twos.next_to, eq_16, LEFT, - FadeIn(eq_16), - ) - self.wait() - - n_possibilities.add(eq_16) - self.n_possibilities = n_possibilities - - def organize_into_stacks(self): - lineups = self.lineups - stacks = VGroup(*[VGroup() for x in range(5)]) - for lineup in lineups: - women = [m for m in lineup.items if "female" in m.get_tex_string()] - stacks[len(women)].add(lineup) - stacks.generate_target() - stacks.target.scale(0.75) - for stack in stacks.target: - stack.arrange(DOWN, buff = SMALL_BUFF) - stacks.target.arrange( - RIGHT, buff = MED_LARGE_BUFF, aligned_edge = DOWN - ) - stacks.target.to_edge(DOWN, buff = MED_SMALL_BUFF) - - self.play(MoveToTarget( - stacks, - run_time = 2, - path_arc = np.pi/2 - )) - self.wait() - - self.stacks = stacks - - def walk_through_stacks(self): - stacks = self.stacks - numbers = VGroup() - - for stack in stacks: - rect = SurroundingRectangle(stack) - rect.set_stroke(WHITE, 2) - self.play(ShowCreation(rect)) - for n, lineup in enumerate(stack): - lineup_copy = lineup.copy() - lineup_copy.set_color(YELLOW) - number = TexMobject(str(n+1)) - number.next_to(stack, UP) - self.add(lineup_copy, number) - self.wait(0.25) - self.remove(lineup_copy, number) - self.add(number) - numbers.add(number) - self.play(FadeOut(rect)) - self.wait() - - stacks.numbers = numbers - - def split_into_two_possibilities(self): - bottom_stacks = self.stacks - top_stacks = bottom_stacks.deepcopy() - top_group = VGroup(top_stacks, top_stacks.numbers) - - h_line = DashedLine(FRAME_X_RADIUS*LEFT, FRAME_X_RADIUS*RIGHT) - - #Initial split - self.play( - FadeOut(self.n_possibilities), - top_group.to_edge, UP, MED_SMALL_BUFF, - ) - self.play(ShowCreation(h_line)) - - #Add extra slot - for stacks, sym in (top_stacks, Female()), (bottom_stacks, Male()): - sym.set_fill(opacity = 0) - new_stacks = VGroup() - to_fade_in = VGroup() - for stack in stacks: - new_stack = VGroup() - for lineup in stack: - new_lineup = self.get_lineup(*[ - Female() if "female" in item.get_tex_string() else Male() - for item in lineup.items - ] + [sym], buff = SMALL_BUFF) - new_lineup.replace(lineup, dim_to_match = 1) - new_stack.add(new_lineup) - for group in lineup.items, lineup.lines: - point = VectorizedPoint(group[-1].get_center()) - group.add(point) - to_fade_in.add(lineup.items[-1]) - new_stacks.add(new_stack) - new_stacks.arrange( - RIGHT, buff = MED_LARGE_BUFF, aligned_edge = DOWN - ) - new_stacks.move_to(stacks, DOWN) - stacks.target = new_stacks - stacks.to_fade_in = to_fade_in - - stacks.numbers.generate_target() - for number, stack in zip(stacks.numbers.target, new_stacks): - number.next_to(stack, UP) - - for stacks in top_stacks, bottom_stacks: - self.play( - MoveToTarget(stacks), - MoveToTarget(stacks.numbers) - ) - self.wait() - - #Fill extra slot - add_man = TextMobject("Add", "$\\male$") - add_man.set_color_by_tex("male", BLUE) - add_woman = TextMobject("Add", "$\\female$") - add_woman.set_color_by_tex("female", MAROON_B) - - add_man.next_to(ORIGIN, DOWN).to_edge(LEFT) - add_woman.to_corner(UP+LEFT) - - for stacks, words in (bottom_stacks, add_man), (top_stacks, add_woman): - to_fade_in = stacks.to_fade_in - to_fade_in.set_fill(opacity = 1) - to_fade_in.save_state() - Transform(to_fade_in, VGroup(words[-1])).update(1) - - self.play(Write(words, run_time = 1)) - self.play(to_fade_in.restore) - self.wait() - - #Perform shift - dist = top_stacks[1].get_center()[0] - top_stacks[0].get_center()[0] - self.play( - top_stacks.shift, dist*RIGHT/2, - top_stacks.numbers.shift, dist*RIGHT/2, - bottom_stacks.shift, dist*LEFT/2, - bottom_stacks.numbers.shift, dist*LEFT/2, - ) - self.wait() - self.play(*list(map(FadeOut, [add_man, add_woman, h_line]))) - - self.set_variables_as_attrs(top_stacks, bottom_stacks) - - def combine_stacks(self): - top_stacks = self.top_stacks - bottom_stacks = self.bottom_stacks - - rects = VGroup() - for stacks, color in (top_stacks, MAROON_C), (bottom_stacks, BLUE_D): - for stack in stacks: - rect = SurroundingRectangle(stack) - rect.set_stroke(color, 2) - rects.add(rect) - stack.add(rect) - - new_numbers = VGroup() - - self.play(LaggedStartMap(ShowCreation, rects, run_time = 1)) - for i, top_stack in enumerate(top_stacks[:-1]): - bottom_stack = bottom_stacks[i+1] - top_number = top_stacks.numbers[i] - bottom_number = bottom_stacks.numbers[i+1] - movers = top_stack, top_number, bottom_number - for mob in movers: - mob.generate_target() - top_stack.target.move_to(bottom_stack.get_top(), DOWN) - plus = TexMobject("+") - expr = VGroup(top_number.target, plus, bottom_number.target) - expr.arrange(RIGHT, buff = SMALL_BUFF) - expr.next_to(top_stack.target.get_top(), UP) - - new_number = TexMobject(str( - len(top_stack) + len(bottom_stack) - 2 - )) - new_number.next_to(expr, UP) - new_numbers.add(new_number) - - self.play( - Write(plus), - *list(map(MoveToTarget, movers)) - ) - self.play( - VGroup(top_stacks[-1], top_stacks.numbers[-1]).align_to, - bottom_stacks, DOWN - ) - self.wait() - - new_numbers.add_to_back(bottom_stacks.numbers[0].copy()) - new_numbers.add(top_stacks.numbers[-1].copy()) - new_numbers.set_color(PINK) - self.play(Write(new_numbers, run_time = 3)) - self.wait() - -class BuildUpFromStart(Scene): - CONFIG = { - "n_iterations" : 7, - } - def construct(self): - stacks = VGroup(VGroup(Male()), VGroup(Female())) - stacks.arrange(RIGHT, buff = LARGE_BUFF) - stacks.numbers = self.get_numbers(stacks) - - max_width = FRAME_WIDTH - 3 - max_height = FRAME_Y_RADIUS - 1 - - self.add(stacks, stacks.numbers) - for x in range(self.n_iterations): - if x < 2: - wait_time = 1 - else: - wait_time = 0.2 - #Divide - low_stacks = stacks - low_group = VGroup(low_stacks, low_stacks.numbers) - top_stacks = stacks.deepcopy() - top_group = VGroup(top_stacks, top_stacks.numbers) - for group, vect in (top_group, UP), (low_group, DOWN): - group.generate_target() - if group[0].get_height() > max_height: - group.target[0].stretch_to_fit_height(max_height) - for stack, num in zip(*group.target): - num.next_to(stack, UP) - group.target.next_to(ORIGIN, vect) - self.play(*list(map(MoveToTarget, [top_group, low_group]))) - self.wait(wait_time) - - #Expand - for stacks, i in (low_stacks, 0), (top_stacks, -1): - sym = stacks[i][i][i] - new_stacks = VGroup() - for stack in stacks: - new_stack = VGroup() - for line in stack: - new_line = line.copy() - new_sym = sym.copy() - buff = 0.3*line.get_height() - new_sym.next_to(line, RIGHT, buff = buff) - new_line.add(new_sym) - line.add(VectorizedPoint(line[-1].get_center())) - new_stack.add(new_line) - new_stacks.add(new_stack) - new_stacks.arrange( - RIGHT, buff = LARGE_BUFF, aligned_edge = DOWN - ) - if new_stacks.get_width() > max_width: - new_stacks.stretch_to_fit_width(max_width) - if new_stacks.get_height() > max_height: - new_stacks.stretch_to_fit_height(max_height) - new_stacks.move_to(stacks, DOWN) - stacks.target = new_stacks - stacks.numbers.generate_target() - - for num, stack in zip(stacks.numbers.target, new_stacks): - num.next_to(stack, UP) - self.play(*list(map(MoveToTarget, [ - top_stacks, low_stacks, - top_stacks.numbers, low_stacks.numbers, - ]))) - self.wait(wait_time) - - #Shift - dist = top_stacks[1].get_center()[0] - top_stacks[0].get_center()[0] - self.play( - top_group.shift, dist*RIGHT/2, - low_group.shift, dist*LEFT/2, - ) - self.wait(wait_time) - - #Stack - all_movers = VGroup() - plusses = VGroup() - expressions = VGroup(low_stacks.numbers[0]) - stacks = VGroup(low_stacks[0]) - v_buff = 0.25*stacks[0][0].get_height() - - for i, top_stack in enumerate(top_stacks[:-1]): - low_stack = low_stacks[i+1] - top_num = top_stacks.numbers[i] - low_num = low_stacks.numbers[i+1] - movers = [top_stack, top_num, low_num] - for mover in movers: - mover.generate_target() - plus = TexMobject("+") - expr = VGroup(top_num.target, plus, low_num.target) - expr.arrange(RIGHT, buff = SMALL_BUFF) - top_stack.target.next_to(low_stack, UP, buff = v_buff) - expr.next_to(top_stack.target, UP) - - all_movers.add(*movers) - plusses.add(plus) - expressions.add(VGroup(top_num, plus, low_num)) - stacks.add(VGroup(*it.chain(low_stack, top_stack))) - - last_group = VGroup(top_stacks[-1], top_stacks.numbers[-1]) - last_group.generate_target() - last_group.target.align_to(low_stacks, DOWN) - all_movers.add(last_group) - stacks.add(top_stacks[-1]) - expressions.add(top_stacks.numbers[-1]) - - self.play(*it.chain( - list(map(MoveToTarget, all_movers)), - list(map(Write, plusses)), - )) - - #Add - new_numbers = self.get_numbers(stacks) - self.play(ReplacementTransform( - expressions, VGroup(*list(map(VGroup, new_numbers))) - )) - self.wait(wait_time) - stacks.numbers = new_numbers - - - #### - - def get_numbers(self, stacks): - return VGroup(*[ - TexMobject(str(len(stack))).next_to(stack, UP) - for stack in stacks - ]) - -class IntroducePascalsTriangle(Scene): - CONFIG = { - "max_n" : 9, - } - def construct(self): - self.show_triangle() - self.show_sum_of_two_over_rule() - self.keep_in_mind_what_these_mean() - self.issolate_9_choose_4_term() - self.show_9_choose_4_pattern() - self.cap_off_triangle() - - def show_triangle(self): - rows = PascalsTriangle(n_rows = self.max_n+1) - self.play(FadeIn(rows[1])) - for last_row, curr_row in zip(rows[1:], rows[2:]): - self.play(*[ - Transform( - last_row.copy(), VGroup(*mobs), - remover = True - ) - for mobs in (curr_row[1:], curr_row[:-1]) - ]) - self.add(curr_row) - self.wait() - - self.rows = rows - - def show_sum_of_two_over_rule(self): - rows = self.rows - - example = rows[5][3] - ex_top1 = rows[4][2] - ex_top2 = rows[4][3] - - rects = VGroup() - for mob, color in (example, GREEN), (ex_top1, BLUE), (ex_top2, YELLOW): - mob.rect = SurroundingRectangle(mob, color = color) - rects.add(mob.rect) - - rows_to_fade = VGroup(*rows[1:4], *rows[6:]) - rows_to_fade.save_state() - - top_row = rows[4] - low_row = rows[5] - top_row_copy = top_row.copy() - top_row.save_state() - top_row.add(ex_top2.rect) - top_row_copy.add(ex_top1.rect) - h_line = Line(LEFT, RIGHT) - h_line.stretch_to_fit_width(low_row.get_width() + 2) - h_line.next_to(low_row, UP, 1.5*SMALL_BUFF) - plus = TexMobject("+") - plus.next_to(h_line.get_left(), UP+RIGHT, buff = 1.5*SMALL_BUFF) - - self.play(ShowCreation(example.rect)) - self.play( - ReplacementTransform(example.rect.copy(), ex_top1.rect), - ReplacementTransform(example.rect.copy(), ex_top2.rect), - ) - self.wait(2) - self.play(rows_to_fade.fade, 1) - self.play( - top_row.align_to, low_row, LEFT, - top_row_copy.next_to, top_row, UP, - top_row_copy.align_to, low_row, RIGHT, - ) - self.play( - ShowCreation(h_line), - Write(plus) - ) - self.wait(2) - for row in top_row, top_row_copy: - row.remove(row[-1]) - self.play( - rows_to_fade.restore, - top_row.restore, - Transform( - top_row_copy, top_row.saved_state, - remover = True - ), - FadeOut(VGroup(h_line, plus)), - FadeOut(rects), - ) - self.wait() - - def keep_in_mind_what_these_mean(self): - morty = Mortimer().flip() - morty.scale(0.7) - morty.to_edge(LEFT) - morty.shift(DOWN) - - numbers = VGroup(*it.chain(*self.rows[1:])) - random.shuffle(numbers.submobjects) - - self.play(FadeIn(morty)) - self.play(PiCreatureSays( - morty, "Keep in mind \\\\ what these mean.", - bubble_kwargs = { - "width" : 3.5, - "height" : 2.5, - } - )) - self.play( - Blink(morty), - LaggedStartMap( - Indicate, numbers, - rate_func = wiggle, - color = PINK, - ) - ) - self.play(*list(map(FadeOut, [ - morty, morty.bubble, morty.bubble.content - ]))) - - def issolate_9_choose_4_term(self): - rows = self.rows - - for n in range(1, self.max_n+1): - num = rows[n][0] - line = get_stack(Female(), Male(), n, 0)[0] - if n < self.max_n: - line.next_to(num, LEFT) - else: - line.next_to(num, DOWN, MED_LARGE_BUFF) - self.set_color_num(num) - self.add(line) - if n < self.max_n: - self.wait(0.25) - else: - self.wait(1.25) - self.dehighlight_num(num) - self.remove(line) - for k in range(1, 5): - num = rows[self.max_n][k] - line = get_stack(Female(), Male(), self.max_n, k)[0] - line.next_to(num, DOWN, MED_LARGE_BUFF) - self.set_color_num(num) - self.add(line) - self.wait(0.5) - self.dehighlight_num(num) - self.remove(line) - num.set_color(YELLOW) - num.scale_in_place(1.2) - self.add(line) - self.wait() - - self.nine_choose_four_term = num - self.nine_choose_four_line = line - - def show_9_choose_4_pattern(self): - rows = VGroup(*self.rows[1:]) - all_stacks = get_stacks(Female(), Male(), 9) - stack = all_stacks[4] - all_lines = VGroup(*it.chain(*all_stacks)) - - self.play( - rows.shift, 3*UP, - self.nine_choose_four_line.shift, 2.5*UP, - ) - self.remove(self.nine_choose_four_line) - - for n, line in enumerate(stack): - line.next_to(self.nine_choose_four_term, DOWN, LARGE_BUFF) - num = Integer(n+1) - num.next_to(line, DOWN, MED_LARGE_BUFF) - self.add(line, num) - self.wait(0.1) - self.remove(line, num) - self.add(line, num) - self.wait() - self.curr_line = line - - #Probability - expr = TexMobject( - "P(4", "\\female", "\\text{ out of }", "9", ")", "=" - ) - expr.move_to(num.get_left()) - expr.set_color_by_tex("female", MAROON_B) - nine_choose_four_term = self.nine_choose_four_term.copy() - nine_choose_four_term.generate_target() - nine_choose_four_term.target.scale(1./1.2) - over_512 = TexMobject("\\quad \\over 2^9") - frac = VGroup(nine_choose_four_term.target, over_512) - frac.arrange(DOWN, buff = SMALL_BUFF) - frac.next_to(expr, RIGHT, SMALL_BUFF) - eq_result = TexMobject("\\approx 0.246") - eq_result.next_to(frac, RIGHT) - - def show_random_lines(n, wait_time = 1): - for x in range(n): - if x == n-1: - wait_time = 0 - new_line = random.choice(all_lines) - new_line.move_to(self.curr_line) - self.remove(self.curr_line) - self.curr_line = new_line - self.add(self.curr_line) - self.wait(wait_time) - - self.play(FadeOut(num), FadeIn(expr)) - show_random_lines(4) - self.play( - MoveToTarget(nine_choose_four_term), - Write(over_512) - ) - show_random_lines(4) - self.play(Write(eq_result)) - show_random_lines(6) - self.play( - self.nine_choose_four_term.scale_in_place, 1./1.2, - self.nine_choose_four_term.set_color, WHITE, - *list(map(FadeOut, [ - expr, nine_choose_four_term, - over_512, eq_result, self.curr_line - ])) - ) - self.play(rows.shift, 3*DOWN) - - def cap_off_triangle(self): - top_row = self.rows[0] - circle = Circle(color = YELLOW) - circle.replace(top_row, dim_to_match = 1) - circle.scale_in_place(1.5) - - line_groups = VGroup() - for n in range(4, -1, -1): - line = VGroup(*[ - random.choice([Male, Female])() - for k in range(n) - ]) - if n == 0: - line.add(Line(LEFT, RIGHT).scale(0.1).set_stroke(BLACK, 0)) - line.arrange(RIGHT, SMALL_BUFF) - line.shift(FRAME_X_RADIUS*RIGHT/2 + FRAME_Y_RADIUS*UP/2) - brace = Brace(line, UP) - if n == 1: - label = "1 Person" - else: - label = "%d People"%n - brace_text = brace.get_text(label) - line_group = VGroup(line, brace, brace_text) - line_groups.add(line_group) - - self.play(ShowCreation(circle)) - self.play(Write(top_row)) - self.wait() - curr_line_group = line_groups[0] - self.play(FadeIn(curr_line_group)) - for line_group in line_groups[1:]: - self.play(ReplacementTransform( - curr_line_group, line_group - )) - curr_line_group = line_group - self.wait() - - ### - - def set_color_num(self, num): - num.set_color(YELLOW) - num.scale_in_place(1.2) - - def dehighlight_num(self, num): - num.set_color(WHITE) - num.scale_in_place(1.0/1.2) - -class StacksApproachBellCurve(Scene): - CONFIG = { - "n_iterations" : 30, - } - def construct(self): - bar = Square(side_length = 1) - bar.set_fill(BLUE) - bar.set_stroke(width = 0) - bars = VGroup(bar) - - numbers = VGroup(Integer(1)) - numbers.next_to(bars, UP, SMALL_BUFF) - - max_width = FRAME_WIDTH - 2 - max_height = FRAME_Y_RADIUS - 1.5 - - for x in range(self.n_iterations): - if x == 0: - distance = 1.5 - else: - distance = bars[1].get_center()[0] - bars[0].get_center()[0] - - bars_copy = bars.copy() - - #Copy and shift - for mob, vect in (bars, DOWN), (bars_copy, UP): - mob.generate_target() - if mob.target.get_height() > max_height: - mob.target.stretch_to_fit_height(max_height) - if mob.target.get_width() > max_width: - mob.target.stretch_to_fit_width(max_width) - mob.target.next_to(ORIGIN, vect, MED_LARGE_BUFF) - colors = color_gradient([BLUE, YELLOW], len(bars)+1) - for color, bar in zip(colors, bars.target): - bar.set_fill(color) - for color, bar in zip(colors[1:], bars_copy.target): - bar.set_fill(color) - bars_copy.set_fill(opacity = 0) - - numbers_copy = numbers.copy() - for bs, ns in (bars, numbers), (bars_copy, numbers_copy): - ns.generate_target() - for bar, number in zip(bs.target, ns.target): - # if number.get_width() > bar.get_width(): - # number.set_width(bar.get_width()) - number.next_to(bar, UP, SMALL_BUFF) - - self.play(*list(map(MoveToTarget, [ - bars, bars_copy, - numbers, numbers_copy - ]))) - self.play( - bars.shift, distance*LEFT/2, - numbers.shift, distance*LEFT/2, - bars_copy.shift, distance*RIGHT/2, - numbers_copy.shift, distance*RIGHT/2, - ) - - #Stack - bars_copy.generate_target() - numbers.generate_target() - numbers_copy.generate_target() - new_numbers = VGroup() - min_scale_val = 1 - for i in range(len(bars)-1): - top_bar = bars_copy.target[i] - low_bar = bars[i+1] - top_num = numbers_copy.target[i] - low_num = numbers.target[i+1] - new_num = Integer(top_num.number + low_num.number) - if new_num.get_width() > top_bar.get_width(): - min_scale_val = min( - min_scale_val, - top_bar.get_width() / new_num.get_width() - ) - new_numbers.add(new_num) - - top_bar.move_to(low_bar.get_top(), DOWN) - new_num.next_to(top_bar, UP, SMALL_BUFF) - Transform(low_num, new_num).update(1) - Transform(top_num, new_num).update(1) - for group in new_numbers, numbers.target[1:], numbers_copy.target[:-1]: - for num in group: - num.scale(min_scale_val, about_point = num.get_bottom()) - if x > 1: - height = numbers.target[1].get_height() - for mob in numbers.target[0], numbers_copy.target[-1]: - mob.set_height(height) - - bars_copy.target[-1].align_to(bars, DOWN) - numbers_copy.target[-1].next_to(bars_copy.target[-1], UP, SMALL_BUFF) - - self.play(*[ - MoveToTarget(mob, lag_ratio = 0.5) - for mob in (bars_copy, numbers, numbers_copy) - ]) - self.remove(numbers, numbers_copy) - numbers = VGroup(numbers[0]) - numbers.add(*new_numbers) - numbers.add(numbers_copy[-1]) - - #Resize lower bars - for top_bar, low_bar in zip(bars_copy[:-1], bars[1:]): - bottom = low_bar.get_bottom() - low_bar.replace( - VGroup(low_bar, top_bar), - stretch = True - ) - low_bar.move_to(bottom, DOWN) - bars.add(bars_copy[-1]) - self.remove(bars_copy) - self.add(bars) - - self.add(numbers) - self.wait() - -# class IsThereABetterWayToCompute(TeacherStudentsScene): -# def construct(self): -# self.student_says( -# "Is there a better \\\\ way to compute these?", -# target_mode = "raise_left_hand", -# ) -# self.change_student_modes("confused", "raise_left_hand", "erm") -# self.wait() -# self.play(self.teacher.change_mode, "happy") -# self.wait() -# self.teacher_says( -# "There is! But first...", -# target_mode = "hooray" -# ) -# self.wait(2) - -class ChooseThreeFromFive(InitialFiveChooseThreeExample, PiCreatureScene): - CONFIG = { - "n" : 5, - "k" : 3, - "pi_creature_scale_val" : 0.3, - "people_colors" : [ - PURPLE, BLUE, GREEN, GOLD_E, GREY, - ], - } - def construct(self): - self.remove(self.people) - self.show_binary_strings() - self.add_people() - self.choose_triplets() - self.show_association_with_binary(3) - self.show_association_with_binary(5) - self.order_doesnt_matter() - self.that_phrase_is_confusing() - self.pattern_is_unambiguous() - - def show_binary_strings(self): - n, k = self.n, self.k - stack = get_stack( - self.get_obj1(), self.get_obj2(), n, k, - vertical_buff = SMALL_BUFF, - ) - stack.to_edge(DOWN, buff = LARGE_BUFF) - equation = TexMobject( - "{%d \\choose %d}"%(n, k), - "=", str(choose(n, k)), - ) - equation[0].scale(0.75, about_point = equation[0].get_right()) - equation.next_to(stack, UP) - - for i, line in enumerate(stack): - num = TexMobject(str(i+1)) - num.next_to(stack, UP) - self.add(line, num) - self.wait(0.25) - self.remove(num) - self.play( - Write(VGroup(*equation[:-1])), - ReplacementTransform(num, equation[-1]) - ) - self.wait() - - self.set_variables_as_attrs(stack, equation) - - def add_people(self): - people = self.people - - names = self.get_names(people) - braces = self.get_people_braces(people) - - self.play( - Write(braces), - LaggedStartMap(FadeIn, people), - VGroup(self.stack, self.equation).to_edge, RIGHT, LARGE_BUFF - ) - self.play(LaggedStartMap(FadeIn, names)) - - self.set_variables_as_attrs(names, braces) - - def choose_triplets(self): - movers = VGroup() - movers.generate_target() - max_name_width = max([n.get_width() for n in self.names]) - for name_triplet in it.combinations(self.names, 3): - mover = VGroup(*name_triplet).copy() - mover.generate_target() - if hasattr(self, "stack"): - mover.target.set_height(self.stack[0].get_height()) - for name in mover.target[:2]: - name[-1].set_fill(opacity = 1) - mover.target.arrange(RIGHT, MED_SMALL_BUFF) - movers.add(mover) - movers.target.add(mover.target) - movers.target.arrange( - DOWN, buff = SMALL_BUFF, - aligned_edge = LEFT, - ) - movers.target.next_to(self.people, DOWN, MED_LARGE_BUFF) - if hasattr(self, "stack"): - movers.target.align_to(self.stack, UP) - - self.play(LaggedStartMap( - MoveToTarget, movers, - lag_ratio = 0.2, - run_time = 4, - )) - self.wait() - - self.name_triplets = movers - - def show_association_with_binary(self, index): - people = self.people - names = self.names - for mob in people, names: - mob.save_state() - mob.generate_target() - - line = self.stack[index].copy() - triplet = self.name_triplets[index] - triplet.save_state() - line.generate_target() - for bit, name in zip(line.target, self.names): - bit.next_to(name, UP) - - line_rect = SurroundingRectangle(line) - full_line_rect = SurroundingRectangle(VGroup(line, triplet)) - people_rects = VGroup() - for pi, name, obj in zip(people.target, names.target, line): - if "1" in obj.get_tex_string(): - rect = SurroundingRectangle(VGroup(pi, name)) - people_rects.add(rect) - pi.change_mode("hooray") - else: - pi.fade(0.5) - name.fade(0.5) - - self.play(ShowCreation(line_rect)) - self.play(MoveToTarget(line)) - self.play( - LaggedStartMap(ShowCreation, people_rects), - MoveToTarget(people), - MoveToTarget(names), - ) - self.wait() - self.play( - ReplacementTransform(line_rect, full_line_rect), - triplet.set_color, YELLOW - ) - self.wait(2) - self.play( - people.restore, - names.restore, - triplet.restore, - FadeOut(line), - FadeOut(full_line_rect), - FadeOut(people_rects), - ) - - def order_doesnt_matter(self): - triplet = self.name_triplets[0].copy() - triplet.set_fill(opacity = 1) - triplet.next_to( - self.name_triplets, RIGHT, - buff = LARGE_BUFF, - aligned_edge = UP, - ) - updownarrow = TexMobject("\\Updownarrow") - updownarrow.set_color(YELLOW) - updownarrow.next_to(triplet, DOWN, SMALL_BUFF) - permutations = VGroup() - for indices in it.permutations(list(range(len(triplet)))): - perm = triplet.copy() - resorter = VGroup(*[ - perm[i] for i in indices - ]) - resorter.arrange(RIGHT, MED_SMALL_BUFF) - resorter.next_to(updownarrow, DOWN) - permutations.add(perm) - - words = TextMobject("``Order doesn't matter''") - words.scale(0.75) - words.set_color(BLUE) - words.next_to(permutations, DOWN) - - self.play(ReplacementTransform( - self.name_triplets[0].copy(), triplet - )) - curr_perm = permutations[0] - self.play( - ReplacementTransform(triplet.copy(), curr_perm), - Write(updownarrow) - ) - for i in range(8): - new_perm = permutations[i%(len(permutations)-1)+1] - anims = [ - Transform( - curr_perm, new_perm, - path_arc = np.pi, - ) - ] - if i == 1: - self.wait() - if i == 4: - anims.append(Write(words, run_time = 1)) - self.play(*anims) - self.play(*list(map(FadeOut, [triplet, curr_perm, updownarrow]))) - - self.order_doesnt_matter_words = words - - def that_phrase_is_confusing(self): - odm_words = self.order_doesnt_matter_words - odm_words_outline = VGroup() - for letter in odm_words: - mob = VMobject() - mob.points = letter.points - odm_words_outline.add(mob) - odm_words_outline.set_fill(opacity = 0) - odm_words_outline.set_stroke(YELLOW, 1) - - line = self.stack[0].copy() - - q_marks = TextMobject("???") - q_marks.next_to(odm_words, DOWN) - q_marks.set_color(YELLOW) - - self.play( - LaggedStartMap( - ShowCreationThenDestruction, odm_words_outline, - lag_ratio = 0.2, - run_time = 1, - ), - LaggedStartMap( - ApplyMethod, self.people, - lambda pi : (pi.change, "confused", odm_words,) - ), - LaggedStartMap(FadeIn, q_marks), - ) - self.play(line.next_to, odm_words, UP) - for x in range(6): - line.generate_target() - resorter = VGroup(*line.target) - resorter.sort(lambda p : random.random()) - resorter.arrange(RIGHT, buff = SMALL_BUFF) - resorter.move_to(line) - self.play(MoveToTarget(line, path_arc = np.pi)) - self.play(FadeOut(q_marks)) - - line.sort(lambda p : p[0]) - words = VGroup(*list(map(TextMobject, ["First", "Second", "Fifth"]))) - words.set_color(YELLOW) - words.scale(0.75) - word_arrow_groups = VGroup() - for i, word in zip([0, 1, 4], words): - arrow = Vector(0.5*DOWN) - arrow.set_color(YELLOW) - arrow.next_to(line[i], UP, SMALL_BUFF) - word.next_to(arrow, UP, SMALL_BUFF) - word_arrow_groups.add(VGroup(word, arrow)) - - for x in range(2): - for i in range(len(word_arrow_groups)+1): - anims = [] - if i > 0: - anims.append(FadeOut(word_arrow_groups[i-1])) - if i < len(word_arrow_groups): - anims.append(FadeIn(word_arrow_groups[i])) - self.play(*anims) - self.wait() - word_arrow_groups.submobjects = [ - word_arrow_groups[j] - for j in (1, 2, 0) - ] - self.play(*list(map(FadeOut, [line, odm_words]))) - - def pattern_is_unambiguous(self): - all_ones = VGroup() - for line in self.stack: - ones = VGroup(*[m for m in line if "1" in m.get_tex_string()]).copy() - ones.set_color(YELLOW) - all_ones.add(ones) - - self.play( - LaggedStartMap( - FadeIn, all_ones, - lag_ratio = 0.2, - run_time = 3, - rate_func = there_and_back - ), - LaggedStartMap( - ApplyMethod, self.people, - lambda pi : (pi.change, "happy", ones), - ) - ) - self.wait() - for trip in it.combinations(self.people, 3): - rects = VGroup(*list(map(SurroundingRectangle, trip))) - self.add(rects) - self.wait(0.3) - self.remove(rects) - self.wait() - - ### - - def create_pi_creatures(self): - people = VGroup(*[ - PiCreature(color = color).scale(self.pi_creature_scale_val) - for color in self.people_colors - ]) - people.arrange(RIGHT) - people.shift(3*LEFT) - people.to_edge(UP, buff = 1.25) - self.people = people - return people - - def get_names(self, people): - names = VGroup(*[ - TextMobject(name + ",") - for name in ("Ali", "Ben", "Cam", "Denis", "Evan") - ]) - for name, pi in zip(names, people): - name[-1].set_fill(opacity = 0) - name.scale(0.75) - name.next_to(pi, UP, 2*SMALL_BUFF) - pi.name = name - return names - - def get_people_braces(self, people): - group = VGroup(people, *[pi.name for pi in people]) - lb, rb = braces = TexMobject("\\{ \\}") - braces.scale(2) - braces.stretch_to_fit_height(1.3*group.get_height()) - lb.next_to(group, LEFT, SMALL_BUFF) - rb.next_to(group, RIGHT, SMALL_BUFF) - return braces - -class SubsetProbabilityExample(ChooseThreeFromFive): - CONFIG = { - "random_seed" : 1, - } - def construct(self): - self.setup_people() - self.ask_question() - self.show_all_triplets() - self.circle_those_with_ali() - - def setup_people(self): - people = self.people - names = self.get_names(people) - braces = self.get_people_braces(people) - group = VGroup(people, names, braces) - - self.play(group.shift, -group.get_center()[0]*RIGHT) - self.wait() - - self.set_variables_as_attrs(names, braces) - - def ask_question(self): - pi_name_groups = VGroup(*[ - VGroup(pi, pi.name) - for pi in self.people - ]) - - words = TextMobject( - "Choose 3 people randomly.\\\\", - "Probability", "Ali", "is one of them?" - ) - words.set_color_by_tex("Ali", self.people[0].get_color()) - words.next_to(pi_name_groups, DOWN, 2*LARGE_BUFF) - - checkmark = TexMobject("\\checkmark").set_color(GREEN) - cross = TexMobject("\\times").set_color(RED) - for mob in checkmark, cross: - mob.scale(2) - mob.next_to(self.braces, DOWN, aligned_edge = LEFT) - mob.shift(MED_SMALL_BUFF*LEFT) - - ali = pi_name_groups[0] - - self.play(FadeIn(words)) - for x in range(4): - group = VGroup(*random.sample(pi_name_groups, 3)) - group.save_state() - group.generate_target() - group.target.shift(LARGE_BUFF*DOWN) - for pi, name in group.target: - pi.change("hooray", checkmark) - if ali in group: - symbol = checkmark - rect = SurroundingRectangle( - group.target[group.submobjects.index(ali)] - ) - rect.set_stroke(GREEN) - else: - symbol = cross - rect = VGroup() - - run_time = 1 - self.play( - MoveToTarget(group), - FadeIn(symbol), - ShowCreation(rect), - run_time = run_time, - ) - self.wait(0.5) - self.play( - group.restore, - FadeOut(symbol), - FadeOut(rect), - run_time = run_time, - ) - - self.question = words - self.set_variables_as_attrs(pi_name_groups) - - def show_all_triplets(self): - self.play( - self.question.scale, 0.75, - self.question.to_corner, UP+RIGHT, - VGroup(self.people, self.names, self.braces).to_edge, LEFT, - ) - self.choose_triplets() - - brace = Brace(self.name_triplets, RIGHT) - total_count = brace.get_tex( - "{5 \\choose 3}", "=", "10", - buff = MED_LARGE_BUFF - ) - total_count.set_color(BLUE) - self.play( - GrowFromCenter(brace), - Write(total_count), - ) - self.wait() - - self.set_variables_as_attrs(brace, total_count) - - def circle_those_with_ali(self): - name_triplets = self.name_triplets - five_choose_three, equals, ten = self.total_count - names = self.names - - with_ali = VGroup(*name_triplets[:6]) - alis = VGroup(*[group[0] for group in with_ali]) - rect = SurroundingRectangle(with_ali) - - frac_lines = VGroup() - for vect in LEFT, RIGHT: - frac_line = TexMobject("\\quad \\over \\quad") - if vect is LEFT: - frac_line.stretch(1.5, 0) - frac_line.next_to(equals, vect) - frac_lines.add(frac_line) - four_choose_two = TexMobject("4 \\choose 2") - four_choose_two.next_to(frac_lines[0], UP, SMALL_BUFF) - six = TexMobject("6") - six.next_to(frac_lines[1], UP, SMALL_BUFF) - - self.play( - ShowCreation(rect), - alis.set_color, YELLOW - ) - for pair in it.combinations(names[1:], 2): - arrows = VGroup() - for pi in pair: - arrow = Vector(0.5*DOWN, color = YELLOW) - arrow.next_to(pi, UP) - arrows.add(arrow) - self.add(arrows) - self.wait(0.5) - self.remove(arrows) - self.add(arrows) - self.wait() - self.play( - FadeIn(frac_lines), - five_choose_three.next_to, frac_lines[0], DOWN, SMALL_BUFF, - ten.next_to, frac_lines[1], DOWN, SMALL_BUFF, - Write(four_choose_two) - ) - self.wait() - self.play(ReplacementTransform( - four_choose_two.copy(), six - )) - self.play(FadeOut(arrows)) - - for x in range(20): - name_rect = SurroundingRectangle(random.choice(name_triplets)) - name_rect.set_color(BLUE) - name_rect.set_fill(BLUE, opacity = 0.25) - self.play(Animation(name_rect, run_time = 0)) - self.wait(0.25) - self.remove(name_rect) - -class StudentsGetConfused(PiCreatureScene): - def construct(self): - pi1, pi2 = self.pi_creatures - line = VGroup( - Male(), Female(), Female(), Male(), Female() - ) - width = line.get_width() - for i, mob in enumerate(line): - mob.shift((i*width+SMALL_BUFF)*RIGHT) - line.scale(1.5) - line.arrange(RIGHT, SMALL_BUFF) - line.move_to(self.pi_creatures, UP) - - self.add(line) - self.play( - self.get_shuffle_anim(line), - PiCreatureSays( - pi1, "Wait \\dots order matters now?", - target_mode = "confused", - look_at_arg = line - ) - ) - self.play( - self.get_shuffle_anim(line), - *[ - ApplyMethod(pi.change, "confused", line) - for pi in self.pi_creatures - ] - ) - for x in range(4): - self.play(self.get_shuffle_anim(line)) - self.wait() - - def create_pi_creatures(self): - pis = VGroup(*[ - Randolph(color = color) - for color in (BLUE_D, BLUE_B) - ]) - pis[1].flip() - pis.arrange(RIGHT, buff = 5) - pis.to_edge(DOWN) - return pis - - def get_shuffle_anim(self, line): - indices = list(range(len(line))) - random.shuffle(indices) - line.generate_target() - for i, m in zip(indices, line.target): - m.move_to(line[i]) - return MoveToTarget(line, path_arc = np.pi) - -class HowToComputeNChooseK(ChooseThreeFromFive): - CONFIG = { - "n" : 5, - "k" : 3, - "line_colors" : [GREEN, YELLOW], - "n_permutaitons_to_show" : 5, - } - def construct(self): - self.force_skipping() - - self.setup_people() - self.choose_example_ordered_triplets() - self.count_possibilities() - self.show_permutations_of_ABC() - self.count_permutations_of_ABC() - self.reset_stage() - self.show_whats_being_counted() - - self.revert_to_original_skipping_status() - self.indicate_final_answer() - - def setup_people(self): - people = self.people - names = self.get_names(people) - braces = self.get_people_braces(people) - people_group = VGroup(people, names, braces) - people_group.center().to_edge(UP, buff = MED_LARGE_BUFF) - - self.add(people_group) - self.set_variables_as_attrs( - names, people_group, - people_braces = braces - ) - - def choose_example_ordered_triplets(self): - n, k = self.n, self.k - names = self.names - - lines, place_words = self.get_lines_and_place_words() - - for x in range(3): - chosen_names = VGroup(*random.sample(names, k)) - chosen_names.save_state() - for name, line, word in zip(chosen_names, lines, place_words): - name.generate_target() - name.target.next_to(line, UP, SMALL_BUFF) - anims = [MoveToTarget(name)] - if x == 0: - anims += [ShowCreation(line), FadeIn(word)] - self.play(*anims) - self.wait() - self.play(chosen_names.restore) - self.wait() - - self.set_variables_as_attrs(lines, place_words) - - def count_possibilities(self): - n, k = self.n, self.k - lines = self.lines - - choice_counts = self.get_choice_counts(n, k) - arrows = self.get_choice_count_arrows(choice_counts) - - name_rects = VGroup() - for name in self.names: - name.rect = SurroundingRectangle(name) - name_rects.add(name.rect) - - chosen_names = VGroup(*random.sample(self.names, k)) - self.names.save_state() - - for name, line, count, arrow in zip(chosen_names, lines, choice_counts, arrows): - self.play( - FadeIn(count), - LaggedStartMap( - FadeIn, name_rects, - rate_func = there_and_back, - remover = True, - ) - ) - self.play( - name.next_to, line, UP, SMALL_BUFF, - GrowArrow(arrow) - ) - self.wait() - - name_rects.remove(name.rect) - name_rects.set_stroke(YELLOW, 3) - - #Consolidate choice counts - choice_numbers = VGroup(*[ - cc.submobjects.pop(1) - for cc in choice_counts - ]) - choice_numbers.generate_target() - dots = VGroup(*[TexMobject("\\cdot") for x in range(k-1)]) - product = VGroup(*it.chain(*list(zip(choice_numbers.target, dots)))) - product.add(choice_numbers.target[-1]) - product.arrange(RIGHT, buff = SMALL_BUFF) - chosen_names_brace = Brace(chosen_names, UP) - product.next_to(chosen_names_brace, UP) - - self.play( - FadeOut(choice_counts), - FadeOut(arrows), - MoveToTarget(choice_numbers), - Write(dots), - GrowFromCenter(chosen_names_brace), - ) - self.wait() - - self.set_variables_as_attrs( - chosen_names, chosen_names_brace, choice_numbers, - choice_numbers_dots = dots, - ) - - def show_permutations_of_ABC(self): - chosen_names = self.chosen_names - lines = self.lines - - n_perms = self.n_permutaitons_to_show + 1 - for indices in list(it.permutations(list(range(3))))[1:n_perms]: - self.play(*[ - ApplyMethod( - name.next_to, lines[i], UP, SMALL_BUFF, - path_arc = np.pi - ) - for i, name in zip(indices, chosen_names) - ]) - self.wait(0.5) - - def count_permutations_of_ABC(self): - n, k = self.n, self.k - lines = self.lines - - chosen_names = self.chosen_names - brace = self.chosen_names_brace - numerator = VGroup( - self.choice_numbers, self.choice_numbers_dots, - ) - frac_line = Line(LEFT, RIGHT) - frac_line.replace(numerator, dim_to_match = 0) - frac_line.to_edge(RIGHT) - - choice_counts = self.get_choice_counts(k, k) - arrows = self.get_choice_count_arrows(choice_counts) - - self.play( - chosen_names.shift, UP, - chosen_names.to_edge, LEFT, - numerator.next_to, frac_line, UP, SMALL_BUFF, - FadeOut(brace), - ) - shuffled_names = random.sample(chosen_names, k) - for line, name, count, arrow in zip(lines, shuffled_names, choice_counts, arrows): - self.play(FadeIn(count), GrowArrow(arrow)) - self.play( - name.next_to, line, UP, SMALL_BUFF, - path_arc = -np.pi/3, - ) - self.wait() - - #Consolidate choice counts - choice_numbers = VGroup(*[ - cc.submobjects.pop(1) - for cc in choice_counts - ]) - choice_numbers.generate_target() - dots = VGroup(*[TexMobject("\\cdot") for x in range(k-1)]) - product = VGroup(*it.chain(*list(zip(choice_numbers.target, dots)))) - product.add(choice_numbers.target[-1]) - product.arrange(RIGHT, buff = SMALL_BUFF) - product.next_to(frac_line, DOWN, SMALL_BUFF) - - self.play( - FadeOut(choice_counts), - FadeOut(arrows), - MoveToTarget(choice_numbers), - Write(dots), - ShowCreation(frac_line), - ) - self.wait() - - self.fraction = VGroup( - numerator, frac_line, VGroup(choice_numbers, dots) - ) - - def reset_stage(self): - n, k = self.n, self.k - n_choose_k_equals = TexMobject( - "{%d \\choose %d} ="%(n, k) - ) - n_choose_k_equals.next_to(ORIGIN, RIGHT, LARGE_BUFF) - n_choose_k_equals.to_edge(UP, LARGE_BUFF) - - self.play( - self.names.restore, - FadeOut(self.lines), - FadeOut(self.place_words), - ) - self.play( - self.people_group.to_edge, LEFT, - FadeIn(n_choose_k_equals), - self.fraction.next_to, n_choose_k_equals, RIGHT, SMALL_BUFF - ) - - def show_whats_being_counted(self): - n, k = self.n, self.k - letters = VGroup(*[name[0] for name in self.names]) - - rhs = TexMobject("=", "{60", "\\over", "6}") - rhs.next_to(self.fraction, RIGHT) - - all_groups = VGroup() - lines = VGroup() - for ordered_triplet in it.combinations(letters, k): - line = VGroup() - for triplet in it.permutations(ordered_triplet): - group = VGroup(*triplet).copy() - group.save_state() - group.arrange(RIGHT, buff = SMALL_BUFF) - line.add(group) - all_groups.add(group) - line.arrange(RIGHT, buff = LARGE_BUFF) - lines.add(line) - lines.arrange(DOWN) - lines.scale(0.8) - lines.to_edge(DOWN) - rects = VGroup(*[ - SurroundingRectangle( - line, buff = 0, - stroke_width = 0, - fill_color = BLUE, - fill_opacity = 0.5, - ) - for line in lines - ]) - - self.play( - Write(VGroup(*rhs[:-1])), - LaggedStartMap( - ApplyMethod, all_groups, - lambda g : (g.restore,), - rate_func = lambda t : smooth(1-t), - run_time = 4, - lag_ratio = 0.2, - ), - ) - self.wait() - self.play( - LaggedStartMap(FadeIn, rects), - Write(rhs[-1]) - ) - self.wait() - - self.ordered_triplets = lines - self.triplet_group_rects = rects - self.rhs = rhs - - def indicate_final_answer(self): - ordered_triplets = self.ordered_triplets - rects = self.triplet_group_rects - fraction = VGroup(*self.rhs[1:]) - frac_rect = SurroundingRectangle(fraction) - - brace = Brace(rects, LEFT) - brace_tex = brace.get_tex("10") - - self.play(FocusOn(fraction)) - self.play(ShowCreation(frac_rect)) - self.play(FadeOut(frac_rect)) - self.wait() - self.play( - GrowFromCenter(brace), - Write(brace_tex), - ) - self.wait() - - - #### - - def get_choice_counts(self, n, k): - people_braces = self.people_braces - choice_counts = VGroup(*[ - TextMobject( - "(", str(n0), " choices", ")", - arg_separator = "" - ) - for n0 in range(n, n-k, -1) - ]) - choice_counts.arrange(RIGHT, buff = SMALL_BUFF) - choice_counts.set_color_by_gradient(*self.line_colors) - choice_counts.next_to(people_braces, DOWN) - return choice_counts - - def get_choice_count_arrows(self, choice_counts): - lines = self.lines - return VGroup(*[ - Arrow( - count.get_bottom(), - line.get_center() + MED_LARGE_BUFF*UP, - color = line.get_color() - ) - for count, line in zip(choice_counts, lines) - ]) - - def get_lines_and_place_words(self): - n, k = self.n, self.k - width = max([n.get_width() for n in self.names]) + MED_SMALL_BUFF - lines = VGroup(*[ - Line(ORIGIN, width*RIGHT) - for x in range(k) - ]) - lines.arrange(RIGHT) - lines.next_to(ORIGIN, DOWN, buff = LARGE_BUFF) - place_words = VGroup(*[ - TexMobject("%d^\\text{%s}"%(i+1, s)) - for i, s in zip( - list(range(k)), - it.chain(["st", "nd", "rd"], it.repeat("th")) - ) - ]) - for mob in place_words, lines: - mob.set_color_by_gradient(*self.line_colors) - for word, line in zip(place_words, lines): - word.next_to(line, DOWN, SMALL_BUFF) - - self.set_variables_as_attrs(lines, place_words) - return lines, place_words - -class NineChooseFourExample(HowToComputeNChooseK): - CONFIG = { - "random_seed" : 2, - "n" : 9, - "k" : 4, - "line_colors" : [RED, MAROON_B], - "n_permutaitons_to_show" : 3, - } - def construct(self): - self.setup_people() - self.show_n_choose_k() - self.show_n_choose_k_pattern() - self.choose_k_people() - self.count_how_to_choose_k() - self.show_permutations() - self.finish_computation() - - def setup_people(self): - self.remove(self.people) - self.people = TextMobject(" ".join([ - chr(ord('A') + i ) - for i in range(self.n) - ])) - self.people.set_color_by_gradient(BLUE, YELLOW) - self.names = self.people - self.people.to_edge(UP, buff = LARGE_BUFF + MED_SMALL_BUFF) - lb, rb = braces = TextMobject("\\{\\}") - braces.scale(1.5) - lb.next_to(self.people, LEFT, SMALL_BUFF) - rb.next_to(self.people, RIGHT, SMALL_BUFF) - - self.people_group = VGroup(braces, self.people) - self.people_braces = braces - - def show_n_choose_k(self): - n, k = self.n, self.k - n_choose_k = TexMobject("{%d \\choose %d}"%(n, k)) - n_choose_k.to_corner(UP + LEFT) - self.play(FadeIn(n_choose_k)) - self.set_variables_as_attrs(n_choose_k) - - def show_n_choose_k_pattern(self): - n, k = self.n, self.k - stack = get_stack( - TexMobject("1").set_color(PINK), - TexMobject("0").set_color(BLUE), - n, k - ) - l = len(stack) - n_stacks = 6 - columns = VGroup(*[ - VGroup(*stack[(i*l)/n_stacks:((i+1)*l)/n_stacks]) - for i in range(n_stacks) - ]) - columns.arrange( - RIGHT, - aligned_edge = UP, - buff = MED_LARGE_BUFF - ) - columns.set_height(7) - columns.to_corner(DOWN + RIGHT) - - for line in stack: - self.play(FadeIn(line, run_time = 0.1)) - self.wait(2) - self.play(FadeOut( - stack, lag_ratio = 0.5, run_time = 2 - )) - - def choose_k_people(self): - n, k = self.n, self.k - people = self.people - braces = self.people_braces - - n_items = TextMobject("%d items"%n) - choose_k = TextMobject("choose %d"%k) - n_items.next_to(people, UP, buff = MED_LARGE_BUFF) - choose_k.next_to(people, DOWN, buff = LARGE_BUFF) - - chosen_subset = VGroup(*random.sample(people, k)) - - self.play( - Write(braces), - LaggedStartMap(FadeIn, people, run_time = 1), - FadeIn(n_items), - ) - self.wait() - self.play( - FadeIn(choose_k), - LaggedStartMap( - ApplyMethod, chosen_subset, - lambda m : (m.shift, MED_LARGE_BUFF*DOWN) - ) - ) - self.wait() - self.play( - chosen_subset.shift, MED_LARGE_BUFF*UP, - n_items.next_to, n_items.get_center(), LEFT, - choose_k.next_to, n_items.get_center(), RIGHT, - ) - - def count_how_to_choose_k(self): - lines, place_words = self.get_lines_and_place_words() - self.play( - LaggedStartMap(FadeIn, lines), - LaggedStartMap(FadeIn, place_words), - run_time = 1 - ) - self.count_possibilities() - - def show_permutations(self): - self.show_permutations_of_ABC() - self.count_permutations_of_ABC() - - def finish_computation(self): - equals = TexMobject("=") - equals.shift(2*LEFT) - fraction = self.fraction - six = fraction[0][0][3] - eight = fraction[0][0][1] - two_three = VGroup(*fraction[2][0][1:3]) - four = fraction[2][0][0] - - rhs = TexMobject("= 9 \\cdot 2 \\cdot 7 = 126") - - self.play( - self.names.restore, - FadeOut(self.lines), - FadeOut(self.place_words), - self.n_choose_k.next_to, equals, LEFT, - self.fraction.next_to, equals, RIGHT, - FadeIn(equals), - ) - self.wait() - - for mob in six, eight, two_three, four: - mob.cross = Cross(mob) - mob.cross.set_stroke("red", 5) - two = TexMobject("2") - two.set_color(eight.get_fill_color()) - two.next_to(eight, UP) - rhs.next_to(fraction, RIGHT) - - self.play( - ShowCreation(six.cross), - ShowCreation(two_three.cross), - ) - self.wait() - self.play( - ShowCreation(eight.cross), - ShowCreation(four.cross), - FadeIn(two) - ) - self.wait() - self.play(Write(rhs)) - self.wait() - -class WeirdKindOfCancelation(TeacherStudentsScene): - def construct(self): - fraction = TexMobject( - "{5 \\cdot 4 \\cdot 3", - "\\text{ ordered}", "\\text{ triplets}", - "\\over", - "1 \\cdot 2 \\cdot 3", "\\text{ orderings \\;\\qquad}}" - ) - top_numbers, ordered, triplets, frac_line, bottom_numbers, orderings = fraction - for mob in top_numbers, bottom_numbers: - mob.set_color_by_gradient(GREEN, YELLOW) - fraction.next_to(self.teacher, UP+LEFT) - - names = VGroup(*list(map(TextMobject, [ - "Ali", "Ben", "Cam", "Denis", "Evan" - ]))) - names.arrange(RIGHT) - names.to_edge(UP, buff = LARGE_BUFF) - names.save_state() - lb, rb = braces = TexMobject("\\{\\}") - braces.scale(2) - lb.next_to(names, LEFT, SMALL_BUFF) - rb.next_to(names, RIGHT, SMALL_BUFF) - - chosen_names = VGroup(*random.sample(names, 3)) - chosen_names.generate_target() - chosen_names.target.arrange(RIGHT) - chosen_names.target.next_to(top_numbers, UP, MED_LARGE_BUFF) - for name, name_target in zip(chosen_names, chosen_names.target): - name.target = name_target - - self.teacher_says("It's like unit cancellation.") - self.change_student_modes(*["confused"]*3) - self.play( - RemovePiCreatureBubble( - self.teacher, target_mode = "raise_right_hand" - ), - LaggedStartMap(FadeIn, fraction, run_time = 1), - FadeIn(braces), - LaggedStartMap(FadeIn, names) - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = fraction - ) - - #Go through numerators - for num, name in zip(top_numbers[::2], chosen_names): - rect = SurroundingRectangle(num) - name.target.set_color(num.get_color()) - self.play( - ShowCreationThenDestruction(rect), - MoveToTarget(name), - ) - self.wait(2) - - #Go through denominators - permutations = list(it.permutations(list(range(3))))[1:] - - self.shuffle(chosen_names, permutations[:2]) - self.play(LaggedStartMap( - ShowCreationThenDestruction, - VGroup(*list(map(SurroundingRectangle, bottom_numbers[::2]))) - )) - self.shuffle(chosen_names, permutations[2:]) - self.wait() - - #Show cancelation - top_cross = Cross(ordered) - bottom_cross = Cross(orderings) - - self.play( - ShowCreation(top_cross), - self.teacher.change, "maybe", - ) - self.play(ShowCreation(bottom_cross)) - self.change_student_modes(*["happy"]*3) - self.wait(3) - - ### - - def shuffle(self, mobject, permutations): - for permutation in permutations: - self.play(*[ - ApplyMethod( - m.move_to, mobject[i].get_center(), - path_arc = np.pi, - ) - for i, m in zip(permutation, mobject) - ]) - -class ABCNotBCA(Scene): - def construct(self): - words = TextMobject("If order mattered:") - equation = TextMobject("(A, B, C) $\\ne$ (B, C, A)") - equation.set_color(YELLOW) - equation.next_to(words, DOWN) - group = VGroup(words, equation) - group.set_width(FRAME_WIDTH - 1) - group.to_edge(DOWN) - self.add(words, equation) - -class ShowFormula(Scene): - def construct(self): - specific_formula = TexMobject( - "{9 \\choose 4}", "=", - "{9 \\cdot 8 \\cdot 7 \\cdot 6", "\\over", - "4 \\cdot 3 \\cdot 2 \\cdot 1}" - ) - general_formula = TexMobject( - "{n \\choose k}", "=", - "{n \\cdot (n-1) \\cdots (n-k+1)", "\\over", - "k \\cdot (k-1) \\cdots 2 \\cdot 1}" - ) - for i, j in (0, 1), (2, 0), (2, 3), (2, 11): - general_formula[i][j].set_color(BLUE) - for i, j in (0, 2), (2, 13), (4, 0), (4, 3): - general_formula[i][j].set_color(YELLOW) - formulas = VGroup(specific_formula, general_formula) - formulas.arrange(DOWN, buff = 2) - formulas.to_edge(UP) - - self.play(FadeIn(specific_formula)) - self.play(FadeIn(general_formula)) - self.wait(3) - -class ConfusedPi(Scene): - def construct(self): - morty = Mortimer() - morty.scale(2.5) - morty.to_corner(UP+LEFT) - morty.look(UP+LEFT) - - self.add(morty) - self.play(Blink(morty)) - self.play(morty.change, "confused") - self.wait() - self.play(Blink(morty)) - self.wait(2) - -class SumsToPowerOf2(Scene): - CONFIG = { - "n" : 5, - "alt_n" : 7, - } - def construct(self): - self.setup_stacks() - self.count_32() - self.show_sum_as_n_choose_k() - self.show_alternate_sum() - - def setup_stacks(self): - stacks = get_stacks( - TexMobject("1").set_color(PINK), - TexMobject("0").set_color(BLUE), - n = self.n, - vertical_buff = SMALL_BUFF, - ) - stacks.to_corner(DOWN+LEFT) - numbers = VGroup(*[ - TexMobject(str(choose(self.n, k))) - for k in range(self.n + 1) - ]) - for number, stack in zip(numbers, stacks): - number.next_to(stack, UP) - - self.play( - LaggedStartMap(FadeIn, stacks), - LaggedStartMap(FadeIn, numbers), - ) - self.wait() - - self.set_variables_as_attrs(stacks, numbers) - - def count_32(self): - lines = VGroup(*it.chain(*self.stacks)) - rhs = TexMobject("= 2^{%d}"%self.n) - rhs.to_edge(UP, buff = LARGE_BUFF) - rhs.to_edge(RIGHT, buff = 2) - - numbers = self.numbers.copy() - numbers.target = VGroup(*[ - TexMobject("{%d \\choose %d}"%(self.n, k)) - for k in range(self.n + 1) - ]) - plusses = VGroup(*[TexMobject("+") for n in numbers]) - plusses.remove(plusses[-1]) - plusses.add(TexMobject("=")) - sum_group = VGroup(*it.chain(*list(zip( - numbers.target, plusses - )))) - sum_group.arrange(RIGHT, SMALL_BUFF) - sum_group.next_to(numbers, UP, LARGE_BUFF) - sum_group.shift(MED_LARGE_BUFF*RIGHT) - - for i, line in zip(it.count(1), lines): - line_copy = line.copy().set_color(YELLOW) - number = Integer(i) - number.scale(1.5) - number.to_edge(UP) - VGroup(number, line_copy).set_color(YELLOW) - self.add(line_copy, number) - self.wait(0.15) - self.remove(line_copy, number) - sum_result = number - self.add(sum_result) - self.wait() - - sum_result.target = TexMobject(str(2**self.n)) - sum_result.target.set_color(sum_result.get_color()) - sum_result.target.next_to(sum_group, RIGHT) - rhs.next_to(sum_result.target, RIGHT, aligned_edge = DOWN) - self.play( - MoveToTarget(sum_result), - MoveToTarget(numbers), - Write(plusses), - Write(rhs), - ) - self.wait() - - self.set_variables_as_attrs( - plusses, sum_result, rhs, - n_choose_k_terms = numbers - ) - - def show_sum_as_n_choose_k(self): - numbers = self.numbers - plusses = self.plusses - n_choose_k_terms = self.n_choose_k_terms - rhs = VGroup(self.sum_result, self.rhs) - n = self.n - - - fractions = self.get_fractions(n) - plusses.generate_target() - sum_group = VGroup(*it.chain(*list(zip( - fractions, plusses.target - )))) - sum_group.arrange(RIGHT, buff = 2*SMALL_BUFF) - sum_group.next_to(rhs, LEFT) - sum_group.shift(0.5*SMALL_BUFF*DOWN) - - self.play( - Transform(n_choose_k_terms, fractions), - MoveToTarget(plusses), - lag_ratio = 0.5, - run_time = 2 - ) - self.wait() - - def show_alternate_sum(self): - fractions = self.get_fractions(self.alt_n) - fractions.remove(*fractions[4:-1]) - fractions.submobjects.insert(4, TexMobject("\\cdots")) - plusses = VGroup(*[ - TexMobject("+") for f in fractions[:-1] - ] + [TexMobject("=")]) - sum_group = VGroup(*it.chain(*list(zip( - fractions, plusses - )))) - sum_group.arrange(RIGHT) - sum_group.next_to( - self.n_choose_k_terms, DOWN, - aligned_edge = LEFT, buff = LARGE_BUFF - ) - sum_group.shift(SMALL_BUFF*DOWN) - rhs = TexMobject( - str(2**self.alt_n), - "=", "2^{%d}"%(self.alt_n) - ) - rhs[0].set_color(YELLOW) - rhs.next_to(sum_group, RIGHT) - - self.play( - LaggedStartMap(FadeOut, self.stacks), - LaggedStartMap(FadeOut, self.numbers), - LaggedStartMap(FadeIn, sum_group), - ) - self.play(LaggedStartMap(FadeIn, rhs)) - self.wait(2) - - #### - - def get_fractions(self, n): - fractions = VGroup(TexMobject("1")) - dot_str = " \\!\\cdot\\! " - for k in range(1, n+1): - ts = str(n) - bs = "1" - for i in range(1, k): - ts += dot_str + str(n-i) - bs += dot_str + str(i+1) - fraction = TexMobject("{%s \\over %s}"%(ts, bs)) - fractions.add(fraction) - return fractions - -class AskWhyTheyAreCalledBinomial(TeacherStudentsScene): - def construct(self): - example_binomials = VGroup(*[ - TexMobject("(x+y)^%d"%d) - for d in range(2, 7) - ]) - example_binomials.arrange(UP) - example_binomials.next_to( - self.teacher.get_corner(UP+LEFT), UP - ) - - pascals = PascalsTriangle(n_rows = 6) - pascals.set_height(3) - pascals.to_corner(UP+LEFT, buff = MED_SMALL_BUFF) - pascals.set_color_by_gradient(BLUE, YELLOW) - - binomial_word = TextMobject( - "Bi", "nomials", - arg_separator = "", - ) - binomial_word.set_color_by_tex("Bi", YELLOW) - binomial_word.set_color_by_tex("nomials", WHITE) - binomial_word.next_to(example_binomials, LEFT, buff = 1.5) - arrows = VGroup(*[ - Arrow(binomial_word.get_right(), binom.get_left()) - for binom in example_binomials - ]) - arrows.set_color(BLUE) - - two_variables = TextMobject("Two", "variables") - two_variables.next_to(binomial_word, DOWN) - two_variables.shift(SMALL_BUFF*LEFT) - for tv, bw in zip(two_variables, binomial_word): - tv.set_color(bw.get_color()) - - self.student_says( - "Why are they called \\\\ ``binomial coefficients''?" - ) - self.play(LaggedStartMap(FadeIn, pascals)) - self.wait() - self.play( - FadeIn(example_binomials[0]), - RemovePiCreatureBubble(self.students[1]), - self.teacher.change, "raise_right_hand", - ) - moving_binom = example_binomials[0].copy() - for binom in example_binomials[1:]: - self.play(Transform(moving_binom, binom)) - self.add(binom) - self.wait() - - #Name themn - self.play( - Write(binomial_word), - LaggedStartMap(GrowArrow, arrows) - ) - self.change_student_modes(*["pondering"]*3) - self.play(Write(two_variables)) - self.wait(2) - -class NextVideo(Scene): - def construct(self): - title = TextMobject("Next video: Binomial distribution") - title.to_edge(UP) - screen = ScreenRectangle(height = 6) - screen.next_to(title, DOWN) - - self.play( - Write(title), - ShowCreation(screen) - ) - self.wait() - -class CombinationsPatreonEndScreen(PatreonEndScreen): - CONFIG = { - "specific_patrons" : [ - "Randall Hunt", - "Desmos", - "Burt Humburg", - "CrypticSwarm", - "Juan Benet", - "David Kedmey", - "Ali Yahya", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Jordan Scales", - "Markus Persson", - "Egor Gumenuk", - "Yoni Nazarathy", - "Ryan Atallah", - "Joseph John Cox", - "Luc Ritchie", - "Supershabam", - "James Park", - "Samantha D. Suplee", - "Delton Ding", - "Thomas Tarler", - "Jonathan Eppele", - "Isak Hietala", - "1stViewMaths", - "Jacob Magnuson", - "Mark Govea", - "Dagan Harrington", - "Clark Gaebel", - "Eric Chow", - "Mathias Jansson", - "David Clark", - "Michael Gardner", - "Mads Elvheim", - "Erik Sundell", - "Awoo", - "Dr. David G. Stork", - "Tianyu Ge", - "Ted Suzman", - "Linh Tran", - "Andrew Busey", - "John Haley", - "Ankalagon", - "Eric Lavault", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Ryan Dahl", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ] - } - -class Thumbnail(Scene): - def construct(self): - n_choose_k = TexMobject("n \\choose k") - n_choose_k[1].set_color(YELLOW) - n_choose_k[2].set_color(YELLOW) - n_choose_k.scale(2) - n_choose_k.to_edge(UP) - stacks = get_stacks( - TexMobject("1").set_color(PINK), - TexMobject("0").set_color(BLUE), - n = 5, vertical_buff = SMALL_BUFF, - ) - stacks.to_edge(DOWN) - stacks.shift(MED_SMALL_BUFF*LEFT) - - self.add(n_choose_k, stacks) - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/independence.py b/from_3b1b/on_hold/eop/independence.py deleted file mode 100644 index 38ef3041..00000000 --- a/from_3b1b/on_hold/eop/independence.py +++ /dev/null @@ -1,3575 +0,0 @@ -from manimlib.imports import * - -from scene.scene import ProgressDisplay -import scipy - -#revert_to_original_skipping_status - -def get_binomial_distribution(n, p): - return lambda k : choose(n, k)*(p**(k))*((1-p)**(n-k)) - -def get_quiz(*questions): - q_mobs = VGroup(*list(map(TextMobject, [ - "%d. %s"%(i+1, question) - for i, question in enumerate(questions) - ]))) - q_mobs.arrange( - DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT, - ) - content = VGroup( - TextMobject("Quiz").scale(1.5), - Line(q_mobs.get_left(), q_mobs.get_right()), - q_mobs - ) - content.arrange(DOWN, buff = MED_SMALL_BUFF) - rect = SurroundingRectangle(content, buff = MED_LARGE_BUFF) - rect.shift(MED_SMALL_BUFF*DOWN) - rect.set_color(WHITE) - quiz = VGroup(rect, content) - quiz.questions = q_mobs - quiz.scale(0.7) - return quiz - -class Checkmark(TexMobjectFromPresetString): - CONFIG = { - "tex" : "\\checkmark", - "color" : GREEN - } - -class Xmark(TexMobjectFromPresetString): - CONFIG = { - "tex" : "\\times", - "color" : RED - } - - - -def get_slot_group( - bool_list, - buff = MED_LARGE_BUFF, - include_qs = True, - min_bool_list_len = 3, - ): - if len(bool_list) < min_bool_list_len: - bool_list += [None]*(min_bool_list_len - len(bool_list)) - n = len(bool_list) - - lines = VGroup(*[ - Line(ORIGIN, MED_LARGE_BUFF*RIGHT) - for x in range(n) - ]) - lines.arrange(RIGHT, buff = buff) - if include_qs: - labels = VGroup(*[ - TextMobject("Q%d"%d) for d in range(1, n+1) - ]) - else: - labels = VGroup(*[VectorizedPoint() for d in range(n)]) - for label, line in zip(labels, lines): - label.scale(0.7) - label.next_to(line, DOWN, SMALL_BUFF) - slot_group = VGroup() - slot_group.lines = lines - slot_group.labels = labels - slot_group.content = VGroup() - slot_group.digest_mobject_attrs() - slot_group.to_edge(RIGHT) - slot_group.bool_list = bool_list - - total_height = FRAME_Y_RADIUS - base = 2.3 - - for i, line in enumerate(lines): - if i >= len(bool_list) or bool_list[i] is None: - mob = VectorizedPoint() - elif bool_list[i]: - mob = TexMobject("\\checkmark") - mob.set_color(GREEN) - slot_group.shift(total_height*DOWN / (base**(i+1))) - else: - mob = TexMobject("\\times") - mob.set_color(RED) - slot_group.shift(total_height*UP / (base**(i+1))) - mob.next_to(line, UP, SMALL_BUFF) - slot_group.content.add(mob) - return slot_group - -def get_probability_of_slot_group(bool_list, conditioned_list = None): - filler_tex = "Fi"*max(len(bool_list), 3) - if conditioned_list is None: - result = TexMobject("P(", filler_tex, ")") - else: - result = TexMobject("P(", filler_tex, "|", filler_tex, ")") - fillers = result.get_parts_by_tex(filler_tex) - for filler, bl in zip(fillers, [bool_list, conditioned_list]): - slot_group = get_slot_group( - bl, buff = SMALL_BUFF, include_qs = False, - ) - slot_group.replace(filler, dim_to_match = 0) - slot_group.shift(0.5*SMALL_BUFF*DOWN) - index = result.index_of_part(filler) - result.submobjects[index] = slot_group - return result - - -######### - -class IndependenceOpeningQuote(OpeningQuote): - CONFIG = { - "quote" : [ - "Far better an ", "approximate", - " answer to the ", " right question", - ", which is often vague, than an ", "exact", - " answer to the ", "wrong question", "." - ], - "highlighted_quote_terms" : { - "approximate" : GREEN, - "right" : GREEN, - "exact" : RED, - "wrong" : RED, - }, - "author" : "John Tukey", - "quote_arg_separator" : "", - } - -class DangerInProbability(Scene): - def construct(self): - warning = self.get_warning_sign() - probability = TextMobject("Probability") - probability.scale(2) - - self.play(Write(warning, run_time = 1)) - self.play( - warning.next_to, probability, UP, LARGE_BUFF, - LaggedStartMap(FadeIn, probability) - ) - self.wait() - - - ##### - - def get_warning_sign(self): - triangle = RegularPolygon(n = 3, start_angle = np.pi/2) - triangle.set_stroke(RED, 12) - triangle.set_height(2) - bang = TextMobject("!") - bang.set_height(0.6*triangle.get_height()) - bang.move_to(interpolate( - triangle.get_bottom(), - triangle.get_top(), - 0.4, - )) - triangle.add(bang) - return triangle - -class MeaningOfIndependence(SampleSpaceScene): - CONFIG = { - "sample_space_config" : { - "height" : 4, - "width" : 4, - } - } - def construct(self): - self.add_labeled_space() - self.align_conditionals() - self.relabel() - self.assume_independence() - self.no_independence() - - def add_labeled_space(self): - self.add_sample_space(**self.sample_space_config) - self.sample_space.shift(2*LEFT) - self.sample_space.divide_horizontally(0.3) - self.sample_space[0].divide_vertically( - 0.9, colors = [BLUE_D, GREEN_C] - ) - self.sample_space[1].divide_vertically( - 0.5, colors = [BLUE_E, GREEN_E] - ) - side_braces_and_labels = self.sample_space.get_side_braces_and_labels( - ["P(A)", "P(\\overline A)"] - ) - top_braces_and_labels, bottom_braces_and_labels = [ - part.get_subdivision_braces_and_labels( - part.vertical_parts, - labels = ["P(B | %s)"%s, "P(\\overline B | %s)"%s], - direction = vect - ) - for part, s, vect in zip( - self.sample_space.horizontal_parts, - ["A", "\\overline A"], - [UP, DOWN], - ) - ] - braces_and_labels_groups = VGroup( - side_braces_and_labels, - top_braces_and_labels, - bottom_braces_and_labels, - ) - - self.add(self.sample_space) - self.play(Write(braces_and_labels_groups, run_time = 4)) - - def align_conditionals(self): - line = Line(*[ - interpolate( - self.sample_space.get_corner(vect+LEFT), - self.sample_space.get_corner(vect+RIGHT), - 0.7 - ) - for vect in (UP, DOWN) - ]) - line.set_stroke(RED, 8) - word = TextMobject("Independence") - word.scale(1.5) - word.next_to(self.sample_space, RIGHT, buff = LARGE_BUFF) - word.set_color(RED) - - self.play(*it.chain( - self.get_top_conditional_change_anims(0.7), - self.get_bottom_conditional_change_anims(0.7) - )) - self.play( - ShowCreation(line), - Write(word, run_time = 1) - ) - self.wait() - - self.independence_word = word - self.independence_line = line - - def relabel(self): - old_labels = self.sample_space[0].vertical_parts.labels - ignored_braces, new_top_labels = self.sample_space[0].get_top_braces_and_labels( - ["P(B)", "P(\\overline B)"] - ) - equation = TexMobject( - "P(B | A) = P(B)" - ) - equation.scale(1.5) - equation.move_to(self.independence_word) - - self.play( - Transform(old_labels, new_top_labels), - FadeOut(self.sample_space[1].vertical_parts.labels), - FadeOut(self.sample_space[1].vertical_parts.braces), - ) - self.play( - self.independence_word.next_to, equation, UP, MED_LARGE_BUFF, - Write(equation) - ) - self.wait() - - self.equation = equation - - def assume_independence(self): - everything = VGroup(*self.get_top_level_mobjects()) - morty = Mortimer() - morty.scale(0.7) - morty.to_corner(DOWN+RIGHT) - bubble = ThoughtBubble(direction = RIGHT) - bubble.pin_to(morty) - bubble.set_fill(opacity = 0) - - self.play( - FadeIn(morty), - everything.scale, 0.5, - everything.move_to, bubble.get_bubble_center(), - ) - self.play( - morty.change, "hooray", everything, - ShowCreation(bubble) - ) - self.wait() - self.play(Blink(morty)) - self.wait() - - self.morty = morty - - def no_independence(self): - for part in self.sample_space.horizontal_parts: - part.vertical_parts.labels = None - self.play(*it.chain( - self.get_top_conditional_change_anims(0.9), - self.get_bottom_conditional_change_anims(0.5), - [ - self.independence_word.fade, 0.7, - self.equation.fade, 0.7, - self.morty.change, "confused", self.sample_space, - FadeOut(self.independence_line) - ] - )) - self.wait() - -class IntroduceBinomial(Scene): - CONFIG = { - "n" : 8, - "p" : 0.7, - } - def construct(self): - self.add_title() - self.add_bar_chart() - self.add_p_slider() - self.write_independence_assumption() - self.play_with_p_value(0.2, 0.5) - self.cross_out_assumption() - self.play_with_p_value(0.8, 0.4) - self.shift_weight_to_tails() - - - def add_title(self): - title = TextMobject("Binomial distribution") - title.scale(1.3) - title.to_edge(RIGHT) - title.shift(2*UP) - - formula = TexMobject( - "P(X=", "k", ")=", - "{n \\choose k}", - "p", "^k", - "(1-", "p", ")", "^{n-", "k}", - arg_separator = "" - ) - formula.set_color_by_tex("k", BLUE) - formula.set_color_by_tex("p", YELLOW) - choose_part = formula.get_part_by_tex("choose") - choose_part.set_color(WHITE) - choose_part[-2].set_color(BLUE) - formula.next_to(title, DOWN, MED_LARGE_BUFF) - - self.formula = formula - self.title = title - self.add(title, formula) - - def add_bar_chart(self): - n, p = self.n, self.p - dist = get_binomial_distribution(n, p) - chart = BarChart( - [dist(k) for k in range(n+1)], - bar_names = list(range(n+1)), - ) - chart.to_edge(LEFT) - self.bar_chart = chart - - self.play(LaggedStartMap( - FadeIn, VGroup(*it.chain(*chart)), - run_time = 2 - )) - - def add_p_slider(self): - interval = UnitInterval(color = LIGHT_GREY) - interval.set_width(4) - interval.next_to( - VGroup(self.bar_chart.x_axis, self.bar_chart.y_axis), - UP, MED_LARGE_BUFF - ) - interval.add_numbers(0, 1) - triangle = RegularPolygon( - n=3, start_angle = -np.pi/2, - stroke_width = 0, - fill_color = YELLOW, - fill_opacity = 1, - ) - triangle.set_height(0.25) - triangle.move_to(interval.number_to_point(self.p), DOWN) - label = TexMobject("p") - label.next_to(triangle, UP, SMALL_BUFF) - label.set_color(triangle.get_color()) - - self.p_slider = VGroup(interval, triangle, label) - self.play(Write(self.p_slider, run_time = 1)) - - def play_with_p_value(self, *values): - for value in values: - self.change_p(value) - self.wait() - - def write_independence_assumption(self): - assumption = TextMobject("Independence assumption") - assumption.scale(1.2) - assumption.next_to(self.formula, DOWN, MED_LARGE_BUFF, LEFT) - assumption.set_color(GREEN_C) - - self.play(Write(assumption, run_time = 2)) - self.wait() - - self.assumption = assumption - - def cross_out_assumption(self): - cross = Cross(self.assumption) - cross.set_color(GREY) - self.bar_chart.save_state() - - self.play(ShowCreation(cross)) - self.play(self.bar_chart.fade, 0.7) - self.wait(2) - self.play(self.bar_chart.restore) - - def shift_weight_to_tails(self): - chart = self.bar_chart - chart_copy = chart.copy() - dist = get_binomial_distribution(self.n, self.p) - values = np.array(list(map(dist, list(range(self.n+1))))) - values += 0.1 - values /= sum(values) - - old_bars = chart.bars - old_bars.generate_target() - new_bars = chart_copy.bars - for bars, vect in (old_bars.target, LEFT), (new_bars, RIGHT): - for bar in bars: - corner = bar.get_corner(DOWN+vect) - bar.stretch(0.5, 0) - bar.move_to(corner, DOWN+vect) - old_bars.target.set_color(RED) - old_bars.target.fade() - - self.play( - MoveToTarget(old_bars), - ReplacementTransform( - old_bars.copy().set_fill(opacity = 0), - new_bars - ) - ) - self.play( - chart_copy.change_bar_values, values - ) - self.wait(2) - - - - ##### - - def change_p(self, p): - interval, triangle, p_label = self.p_slider - alt_dist = get_binomial_distribution(self.n, p) - self.play( - ApplyMethod( - self.bar_chart.change_bar_values, - [alt_dist(k) for k in range(self.n+1)], - ), - triangle.move_to, interval.number_to_point(p), DOWN, - MaintainPositionRelativeTo(p_label, triangle) - ) - self.p = p - -class IntroduceQuiz(PiCreatureScene): - def construct(self): - self.add_quiz() - self.ask_about_probabilities() - self.show_distribution() - self.show_single_question_probability() - - def add_quiz(self): - quiz = self.get_example_quiz() - quiz.next_to(self.randy, UP+RIGHT) - - self.play( - Write(quiz), - self.randy.change, "pondering", quiz - ) - self.wait() - - self.quiz = quiz - - def ask_about_probabilities(self): - probabilities, abbreviated_probabilities = [ - VGroup(*[ - TexMobject( - "P(", s_tex, "=", str(score), ")", rhs - ).set_color_by_tex_to_color_map({ - str(score) : YELLOW, - "text" : GREEN, - }) - for score in range(4) - ]) - for s_tex, rhs in [ - ("\\text{Score}", "= \\, ???"), - ("\\text{S}", "") - ] - ] - for group in probabilities, abbreviated_probabilities: - group.arrange( - DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - group.to_corner(UP+LEFT) - - self.play( - LaggedStartMap(FadeIn, probabilities, run_time = 3), - self.quiz.set_height, 0.7*self.randy.get_height(), - self.quiz.next_to, self.randy, RIGHT, - self.randy.change, "confused", probabilities - ) - self.wait() - - self.probabilities = probabilities - self.abbreviated_probabilities = abbreviated_probabilities - - def show_distribution(self): - dist = get_binomial_distribution(3, 0.7) - values = list(map(dist, list(range(4)))) - chart = BarChart( - values, - width = 7, - bar_names = list(range(4)) - ) - chart.to_edge(RIGHT) - for short_p, bar in zip(self.abbreviated_probabilities, chart.bars): - short_p.set_width(1.75*bar.get_width()) - short_p.next_to(bar, UP) - - self.play( - LaggedStartMap(Write, VGroup( - *[m for m in chart if m is not chart.bars] - )), - ) - self.play(*[ - ReplacementTransform( - bar.copy().stretch_to_fit_height(0).move_to(bar.get_bottom()), - bar - ) - for bar in chart.bars - ]) - self.play(*[ - ReplacementTransform(p.copy(), short_p) - for p, short_p in zip( - self.probabilities, - self.abbreviated_probabilities, - ) - ]) - self.wait() - - self.bar_chart = chart - - def show_single_question_probability(self): - prob = TexMobject( - "P(", "\\text{Can answer a given question}", ")", - "= 0.8" - ) - prob.to_corner(UP+RIGHT) - prob.set_color_by_tex("text", GREEN) - rect = SurroundingRectangle(prob, buff = MED_SMALL_BUFF) - - self.play( - Write(prob), - self.randy.change, "happy", prob - ) - self.play(ShowCreation(rect)) - self.wait() - - self.single_question_probability = VGroup( - prob, rect - ) - - - ###### - - def create_pi_creature(self): - randy = Randolph() - randy.scale(0.7) - randy.to_corner(DOWN+LEFT) - self.randy = randy - return randy - - def get_example_quiz(self): - return get_quiz( - "Define ``Brachistochrone'' ", - "Define ``Tautochrone'' ", - "Define ``Cycloid'' ", - ) - -class BreakDownQuestionPatterns(IntroduceQuiz): - def construct(self): - self.add_parts_from_last_scene() - self.break_down_possibilities() - self.count_patterns() - - def add_parts_from_last_scene(self): - self.force_skipping() - IntroduceQuiz.construct(self) - self.revert_to_original_skipping_status() - - chart_group = VGroup( - self.bar_chart, - self.abbreviated_probabilities - ) - self.play( - self.single_question_probability.scale, 0.8, - self.single_question_probability.to_corner, UP+LEFT, - chart_group.scale, 0.7, chart_group.get_top(), - chart_group.to_edge, LEFT, - FadeOut(self.probabilities) - ) - - def break_down_possibilities(self): - slot_group_groups = VGroup(*[VGroup() for x in range(4)]) - bool_lists = [[]] - while bool_lists: - bool_list = bool_lists.pop() - slot_group = self.get_slot_group(bool_list) - slot_group_groups[len(bool_list)].add(slot_group) - if len(bool_list) < 3: - bool_lists += [ - list(bool_list) + [True], - list(bool_list) + [False], - ] - - group_group = slot_group_groups[0] - self.revert_to_original_skipping_status() - self.play(Write(group_group, run_time = 1)) - self.wait() - for new_group_group in slot_group_groups[1:]: - self.play(Transform(group_group, new_group_group)) - self.wait(2) - - self.slot_groups = slot_group_groups[-1] - - def count_patterns(self): - brace = Brace(self.slot_groups, LEFT) - count = TexMobject("2^3 = 8") - count.next_to(brace, LEFT) - - self.play( - GrowFromCenter(brace), - Write(count) - ) - self.wait() - - ####### - - def get_slot_group(self, bool_list): - return get_slot_group(bool_list, include_qs = len(bool_list) < 3) - -class AssociatePatternsWithScores(BreakDownQuestionPatterns): - CONFIG = { - "score_group_scale_val" : 0.8, - } - def construct(self): - self.add_slot_groups() - self.show_score_groups() - self.think_about_binomial_patterns() - - def add_slot_groups(self): - self.slot_groups = VGroup(*list(map( - self.get_slot_group, - it.product(*[[True, False]]*3) - ))) - self.add(self.slot_groups) - self.remove(self.randy) - - def show_score_groups(self): - score_groups = [VGroup() for x in range(4)] - scores = VGroup() - full_score_groups = VGroup() - - for slot_group in self.slot_groups: - score_groups[sum(slot_group.bool_list)].add(slot_group) - for i, score_group in enumerate(score_groups): - score = TextMobject("Score", "=", str(i)) - score.set_color_by_tex("Score", GREEN) - scores.add(score) - score_group.organized = score_group.deepcopy() - score_group.organized.arrange(UP, buff = SMALL_BUFF) - score_group.organized.scale(self.score_group_scale_val) - brace = Brace(score_group.organized, LEFT) - score.next_to(brace, LEFT) - score.add(brace) - full_score_groups.add(VGroup(score, score_group.organized)) - full_score_groups.arrange( - DOWN, buff = MED_LARGE_BUFF, - aligned_edge = RIGHT - ) - full_score_groups.to_edge(LEFT) - - for score, score_group in zip(scores, score_groups): - score_group.save_state() - self.play(score_group.next_to, score_group, LEFT, MED_LARGE_BUFF) - self.wait() - self.play( - ReplacementTransform( - score_group.copy(), score_group.organized - ), - LaggedStartMap(FadeIn, score, run_time = 1) - ) - self.play(score_group.restore) - self.wait() - - def think_about_binomial_patterns(self): - triangle = PascalsTriangle( - nrows = 5, - height = 3, - width = 3, - ) - triangle.to_edge(UP+RIGHT) - row = VGroup(*[ - triangle.coords_to_mobs[3][k] - for k in range(4) - ]) - self.randy.center().to_edge(DOWN) - bubble = ThoughtBubble() - bubble.add_content(triangle) - bubble.resize_to_content() - triangle.shift(SMALL_BUFF*(3*UP + RIGHT)) - bubble.add(triangle) - bubble.next_to(self.randy, UP+RIGHT, SMALL_BUFF) - bubble.remove(triangle) - - self.play( - FadeOut(self.slot_groups), - FadeIn(self.randy), - FadeIn(bubble) - ) - self.play( - self.randy.change, "pondering", - LaggedStartMap(FadeIn, triangle, run_time = 4), - ) - self.play(row.set_color, YELLOW) - self.wait(4) - -class BeforeCounting(TeacherStudentsScene): - def construct(self): - triangle = PascalsTriangle(nrows = 7) - triangle.set_height(4) - triangle.next_to(self.teacher, UP+LEFT) - - prob = get_probability_of_slot_group([True, True, False]) - prob.to_edge(UP) - brace = Brace(prob, DOWN) - q_marks = brace.get_text("???") - - self.teacher.change_mode("raise_right_hand") - self.add(triangle) - self.change_student_modes(*["hooray"]*3) - self.play( - triangle.scale, 0.5, - triangle.to_corner, UP+RIGHT, - self.teacher.change_mode, "sassy" - ) - self.change_student_modes(*["confused"]*3) - self.play(Write(prob)) - self.play( - GrowFromCenter(brace), - LaggedStartMap(FadeIn, q_marks) - ) - self.wait(2) - -class TemptingButWrongCalculation(BreakDownQuestionPatterns): - def construct(self): - self.add_title() - self.write_simple_product() - - def add_title(self): - title = TextMobject("Tempting$\\dots$") - title.scale(1.5) - title.to_edge(UP) - self.add(title) - self.title = title - - def write_simple_product(self): - lhs = TexMobject("P\\big(", "Filler Blah", "\\big)", "= ") - lhs.next_to(ORIGIN, UP+LEFT) - p_of = lhs.get_part_by_tex("P\\big(") - filler = lhs.get_part_by_tex("Filler") - rp = lhs.get_part_by_tex("\\big)") - slot_group = self.get_slot_group([True, True, False]) - slot_group.replace(filler, dim_to_match = 0) - lhs.submobjects.remove(filler) - - rhs = VGroup(*[ - TexMobject("P(", "\\checkmark" if b else "\\times", ")") - for b in slot_group.bool_list - ]) - rhs.arrange(RIGHT, SMALL_BUFF) - rhs.next_to(lhs, RIGHT, SMALL_BUFF) - for part, b in zip(rhs, slot_group.bool_list): - part.set_color_by_tex_to_color_map({ - "checkmark" : GREEN, - "times" : RED, - }) - brace = Brace(part, UP) - if b: - value = TexMobject("(0.8)") - else: - value = TexMobject("(0.2)") - value.set_color(part[1].get_color()) - value.next_to(brace, UP) - part.brace = brace - part.value = value - - question = TextMobject("What about correlations?") - question.next_to(rhs, DOWN, LARGE_BUFF) - - self.play( - Write(lhs), - ShowCreation(slot_group.lines), - LaggedStartMap(FadeIn, slot_group.content, run_time = 3), - self.randy.change, "pondering" - ) - self.wait(2) - for part, mob in zip(rhs, slot_group.content): - self.play(*[ - ReplacementTransform( - mob.copy(), subpart, - path_arc = np.pi/6 - ) - for subpart, mob in zip(part, [ - p_of, mob, rp - ]) - ]) - self.play(GrowFromCenter(part.brace)) - self.play(FadeIn(part.value)) - self.wait() - self.wait() - self.play( - Write(question), - self.randy.change, "confused" - ) - self.wait(3) - - self.question = question - self.rhs = rhs - -class ThousandPossibleQuizzes(Scene): - CONFIG = { - "n_quiz_rows" : 25, - "n_quiz_cols" : 40, - "n_movers" : 100, - # "n_quiz_rows" : 5, - # "n_quiz_cols" : 8, - # "n_movers" : 4, - "quizzes_height" : 4, - } - def construct(self): - self.draw_all_quizzes() - self.show_division_by_first_question() - self.ask_about_second_question() - self.show_uncorrelated_division_by_second() - self.increase_second_correct_slice() - self.second_division_among_first_wrong() - self.show_that_second_is_still_80() - self.emphasize_disproportionate_divide() - self.show_third_question_results() - - def draw_all_quizzes(self): - quizzes = self.get_thousand_quizzes() - title = TextMobject("$1{,}000$ possible quizzes") - title.scale(1.5) - title.next_to(quizzes, UP) - full_quizzes = VGroup( - get_quiz( - "Define ``Brachistochrone''", - "Define ``Tautochrone''", - "Define ``Cycloid''", - ), - get_quiz( - "Define $\\dfrac{df}{dx}$", - "Define $\\displaystyle \\lim_{h \\to 0} f(h)$", - "Prove $\\dfrac{d(x^2)}{dx} = 2x$ ", - ), - get_quiz( - "Find all primes $p$ \\\\ where $p+2$ is prime.", - "Find all primes $p$ \\\\ where $2^{p}-1$ is prime.", - "Solve $\\zeta(s) = 0$", - ), - ) - full_quizzes.arrange(RIGHT) - target_quizzes = VGroup(*quizzes[:len(full_quizzes)]) - - for quiz in full_quizzes: - self.play(FadeIn(quiz, run_time = 3, lag_ratio = 0.5)) - self.play( - Transform(full_quizzes, target_quizzes), - FadeIn(title) - ) - self.play( - LaggedStartMap( - FadeIn, quizzes, - run_time = 3, - lag_ratio = 0.2, - ), - Animation(full_quizzes, remover = True) - ) - self.wait() - - self.quizzes = quizzes - self.title = title - - def show_division_by_first_question(self): - n = int(0.8*len(self.quizzes)) - top_split = VGroup(*self.quizzes[:n]) - bottom_split = VGroup(*self.quizzes[n:]) - for split, color, vect in (top_split, GREEN, UP), (bottom_split, RED, DOWN): - split.sort(lambda p : p[0]) - split.generate_target() - split.target.shift(MED_LARGE_BUFF*vect) - for quiz in split.target: - quiz[0].set_color(color) - - labels = VGroup() - for num, b, split in (800, True, top_split), (200, False, bottom_split): - label = VGroup( - TexMobject(str(num)), - get_slot_group([b], buff = SMALL_BUFF, include_qs = False) - ) - label.arrange(DOWN) - label.next_to(split.target, LEFT, buff = LARGE_BUFF) - labels.add(label) - - self.play( - FadeOut(self.title), - MoveToTarget(top_split), - MoveToTarget(bottom_split), - ) - for label in labels: - self.play(FadeIn(label)) - self.wait() - - self.splits = VGroup(top_split, bottom_split) - self.q1_split_labels = labels - - def ask_about_second_question(self): - top_split = self.splits[0] - sg1, sg2 = slot_groups = VGroup(*[ - get_slot_group( - [True, b], - include_qs = False, - buff = SMALL_BUFF - ) - for b in (True, False) - ]) - question = VGroup( - TextMobject("Where are"), sg1, - TextMobject("and"), sg2, TextMobject("?"), - ) - question.arrange(RIGHT, aligned_edge = DOWN) - question[-1].next_to(question[-2], RIGHT, SMALL_BUFF) - question.next_to(top_split, UP, MED_LARGE_BUFF) - slot_groups.shift(SMALL_BUFF*DOWN) - little_rects = VGroup(*[ - SurroundingRectangle( - VGroup(sg.lines[1], sg.content[1]) - ) - for sg in slot_groups - ]) - big_rect = SurroundingRectangle(top_split) - - self.play(Write(question)) - self.play(ShowCreation(little_rects)) - self.wait() - self.play(FadeOut(little_rects)) - self.play(ShowCreation(big_rect)) - self.play( - FadeOut(big_rect), - FadeOut(question), - ) - self.wait() - - def show_uncorrelated_division_by_second(self): - top_split = self.splits[0] - top_label = self.q1_split_labels[0] - n = int(0.8*len(top_split)) - left_split = VGroup(*top_split[:n]) - right_split = VGroup(*top_split[n:]) - for split, color in (left_split, GREEN_E), (right_split, RED_E): - split.generate_target() - for quiz in split.target: - quiz[1].set_color(color) - left_split.target.shift(LEFT) - - left_label = VGroup( - TexMobject("(0.8)", "800 =", "640"), - get_slot_group([True, True], buff = SMALL_BUFF, include_qs = False) - ) - left_label.arrange(RIGHT, buff = MED_LARGE_BUFF) - left_label.next_to(left_split.target, UP) - - self.play( - MoveToTarget(left_split), - MaintainPositionRelativeTo(top_label, left_split), - MoveToTarget(right_split), - ) - self.play(FadeIn(left_label)) - self.play(LaggedStartMap( - ApplyMethod, left_split, - lambda m : (m.set_color, YELLOW), - rate_func = there_and_back, - lag_ratio = 0.2, - )) - self.wait() - - self.top_left_label = left_label - self.top_splits = VGroup(left_split, right_split) - - def increase_second_correct_slice(self): - left_split, right_split = self.top_splits - left_label = self.top_left_label - left_label_equation = left_label[0] - movers = VGroup(*right_split[:self.n_movers]) - movers.generate_target() - for quiz in movers.target: - quiz[1].set_color(left_split[0][1].get_color()) - movers.target.shift(LEFT) - - new_equation = TexMobject("(0.925)", "800 =", "740") - for i in 0, 2: - new_equation[i].set_color(YELLOW) - new_equation.move_to(left_label_equation) - - self.play( - MoveToTarget( - movers, - lag_ratio = 0.5, - run_time = 3, - ), - Transform(left_label_equation, new_equation) - ) - self.wait(2) - self.play(Indicate(left_label_equation[0])) - self.wait() - - left_split.add(*movers) - right_split.remove(*movers) - self.top_left_split = left_split - self.top_right_split = right_split - self.top_movers = movers - self.top_equation = left_label_equation - - def second_division_among_first_wrong(self): - top_label, bottom_label = self.q1_split_labels - top_split, bottom_split = self.splits - top_left_label = self.top_left_label - top_group = VGroup(top_split, top_left_label, top_label) - - n = int(0.8*len(bottom_split)) - left_split = VGroup(*bottom_split[:n]) - right_split = VGroup(*bottom_split[n:]) - for split, color in (left_split, GREEN_E), (right_split, RED_E): - split.generate_target() - for quiz in split.target: - quiz[1].set_color(color) - left_split.target.shift(LEFT) - - movers = VGroup(*left_split[-self.n_movers:]) - movers.generate_target() - for quiz in movers.target: - quiz[1].set_color(right_split.target[0][1].get_color()) - - equation = TexMobject("(0.8)", "200 = ", "160") - slot_group = get_slot_group([False, True], buff = SMALL_BUFF, include_qs = False) - label = VGroup(equation, slot_group) - label.arrange(DOWN, buff = SMALL_BUFF) - label.next_to(left_split.target, UP, SMALL_BUFF, LEFT) - alt_equation = TexMobject("(0.3)", "200 = ", "60") - for i in 0, 2: - alt_equation[i].set_color(YELLOW) - alt_equation.move_to(equation) - - self.play(top_group.to_edge, UP, SMALL_BUFF) - self.play( - bottom_label.shift, LEFT, - *list(map(MoveToTarget, [left_split, right_split])) - ) - self.play(FadeIn(label)) - self.wait() - self.play( - MoveToTarget( - movers, - lag_ratio = 0.5, - run_time = 3, - ), - Transform(equation, alt_equation) - ) - self.wait() - - left_split.remove(*movers) - right_split.add(*movers) - self.bottom_left_split = left_split - self.bottom_right_split = right_split - self.bottom_movers = movers - self.bottom_equation = equation - self.bottom_left_label = label - - def show_that_second_is_still_80(self): - second_right = VGroup( - self.bottom_left_split, self.top_left_split - ) - second_wrong = VGroup( - self.bottom_right_split, self.top_right_split - ) - rects = VGroup(*[ - SurroundingRectangle(mob, buff = SMALL_BUFF) - for mob in second_right - ]) - - num1 = self.top_equation[-1].copy() - num2 = self.bottom_equation[-1].copy() - - equation = TexMobject("740", "+", "60", "=", "800") - for tex in "740", "60": - equation.set_color_by_tex(tex, YELLOW) - slot_group = get_slot_group([True, True]) - slot_group.content[0].set_fill(BLACK, 0) - label = VGroup(equation, slot_group) - label.arrange(DOWN) - label.next_to(self.quizzes, LEFT, LARGE_BUFF) - - self.play( - FadeOut(self.q1_split_labels), - ShowCreation(rects) - ) - self.play( - FadeIn(slot_group), - Transform( - num1, equation[0], - rate_func = squish_rate_func(smooth, 0, 0.7), - ), - Transform( - num2, equation[2], - rate_func = squish_rate_func(smooth, 0.3, 1), - ), - run_time = 2 - ) - self.play( - Write(equation), - *list(map(Animation, [num1, num2])) - ) - self.remove(num1, num2) - self.wait() - self.play(FadeOut(rects)) - - def emphasize_disproportionate_divide(self): - top_movers = self.top_movers - bottom_movers = self.bottom_movers - both_movers = VGroup(top_movers, bottom_movers) - both_movers.save_state() - - top_movers.target = bottom_movers.copy().shift(LEFT) - bottom_movers.target = top_movers.copy().shift(RIGHT) - for quiz in top_movers.target: - quiz[0].set_color(RED) - for quiz in bottom_movers.target: - quiz[0].set_color(GREEN) - - line = Line(UP, DOWN, color = YELLOW) - line.set_height(self.quizzes.get_height()) - line.next_to(bottom_movers.target, LEFT, MED_LARGE_BUFF, UP) - - self.revert_to_original_skipping_status() - self.play(*list(map(MoveToTarget, both_movers))) - self.play(ShowCreation(line)) - self.play(FadeOut(line)) - self.wait() - self.play(both_movers.restore) - self.wait() - - def show_third_question_results(self): - all_splits = VGroup( - self.top_left_split, self.top_right_split, - self.bottom_left_split, self.bottom_right_split - ) - proportions = [0.9, 0.8, 0.8, 0.4] - for split, prop in zip(all_splits, proportions): - n = int(prop*len(split)) - split.sort(lambda p : -p[1]) - split.generate_target() - top_part = VGroup(*split.target[:n]) - top_part.shift(MED_SMALL_BUFF*UP) - bottom_part = VGroup(*split.target[n:]) - bottom_part.shift(MED_SMALL_BUFF*DOWN) - for quiz in top_part: - quiz[-1].set_color(GREEN) - for quiz in bottom_part: - quiz[-1].set_color(RED) - - split = self.top_left_split - n_all_right = int(proportions[0]*len(split)) - all_right = VGroup(*split[:n_all_right]) - - self.play( - FadeOut(self.top_left_label), - FadeOut(self.bottom_left_label), - ) - for split in all_splits: - self.play(MoveToTarget(split)) - self.wait() - self.play(LaggedStartMap( - ApplyMethod, all_right, - lambda m : (m.set_color, YELLOW), - rate_func = there_and_back, - lag_ratio = 0.2, - run_time = 2 - )) - self.wait(2) - - ##### - - def get_thousand_quizzes(self): - rows = VGroup() - for x in range(self.n_quiz_rows): - quiz = VGroup(*[ - Rectangle( - height = SMALL_BUFF, - width = 0.5*SMALL_BUFF - ) - for x in range(3) - ]) - quiz.arrange(RIGHT, buff = 0) - quiz.set_stroke(width = 0) - quiz.set_fill(LIGHT_GREY, 1) - row = VGroup(*[quiz.copy() for y in range(self.n_quiz_cols)]) - row.arrange(RIGHT, buff = SMALL_BUFF) - rows.add(row) - - rows.arrange(DOWN, buff = SMALL_BUFF) - quizzes = VGroup(*it.chain(*rows)) - quizzes.set_height(self.quizzes_height) - quizzes.to_edge(RIGHT) - quizzes.shift(MED_LARGE_BUFF*DOWN) - return quizzes - -class ExampleConditional(Scene): - def construct(self): - prob = get_probability_of_slot_group( - [True, True], [True] - ) - rhs = TexMobject("=", "0.925", ">", "0.8") - rhs.set_color_by_tex("0.925", YELLOW) - rhs.next_to(prob, RIGHT) - expression = VGroup(prob, rhs) - expression.set_width(FRAME_WIDTH - 1) - expression.center().to_edge(DOWN) - - self.play(Write(expression)) - self.wait() - -class HarderQuizzes(Scene): - def construct(self): - quizzes = VGroup( - get_quiz( - "Find all primes $p$ \\\\ where $p+2$ is prime.", - "Find all primes $p$ \\\\ where $2^{p}-1$ is prime.", - "Solve $\\zeta(s) = 0$", - ), - get_quiz( - "Find $S$ such that \\\\ $\\#\\mathds{N} < \\#S < \\#\\mathcal{P}(\\mathds{N})$", - "Describe ``forcing''", - "Prove from ZFC that $S \\notin S$.", - ), - ) - quizzes.arrange(RIGHT) - quizzes.to_edge(DOWN) - crosses = VGroup(*[ - Cross(quiz.questions[0]) - for quiz in quizzes - ]) - - for quiz in quizzes: - self.play(FadeIn(quiz)) - self.wait() - for cross in crosses: - self.play(ShowCreation(cross)) - self.wait() - -class WritePSecond(Scene): - def construct(self): - prob = get_probability_of_slot_group([None, True, None]) - rhs = TexMobject("= 0.8") - rhs.next_to(prob, RIGHT) - prob.add(rhs) - prob.set_width(FRAME_WIDTH - 1) - prob.center().to_edge(DOWN) - self.play(Write(prob)) - -class SubmitToTemptation(TemptingButWrongCalculation): - def construct(self): - self.force_skipping() - TemptingButWrongCalculation.construct(self) - self.revert_to_original_skipping_status() - - title = self.title - question = self.question - title.generate_target() - title.target.scale_in_place(1./1.5) - new_words = TextMobject("and", "okay", "assuming independence.") - new_words.set_color_by_tex("okay", GREEN) - new_words.next_to(title.target, RIGHT) - VGroup(title.target, new_words).center().to_edge(UP) - - self.play( - MoveToTarget(title), - FadeOut(question) - ) - self.play( - Write(new_words, run_time = 2), - self.randy.change, "hooray" - ) - for part in self.rhs: - self.play(Indicate(part.value)) - self.wait() - -class AccurateProductRule(SampleSpaceScene, ThreeDScene): - def construct(self): - self.setup_terms() - self.add_sample_space() - self.wait() - self.show_first_division() - self.show_second_division() - self.move_to_third_dimension() - self.show_final_probability() - self.show_confusion() - - def setup_terms(self): - filler_tex = "Filler" - lhs = TexMobject("P(", filler_tex, ")", "=") - p1 = TexMobject("P(", filler_tex, ")") - p2 = TexMobject("P(", filler_tex, "|", filler_tex, ")") - p3 = TexMobject("P(", filler_tex, "|", filler_tex, ")") - terms = VGroup(lhs, p1, p2, p3) - terms.arrange(RIGHT, buff = SMALL_BUFF) - terms.to_edge(UP, buff = LARGE_BUFF) - - kwargs = {"buff" : SMALL_BUFF, "include_qs" : False} - slot_group_lists = [ - [get_slot_group([True, True, False], **kwargs)], - [get_slot_group([True], **kwargs)], - [ - get_slot_group([True, True], **kwargs), - get_slot_group([True], **kwargs), - ], - [ - get_slot_group([True, True, False], **kwargs), - get_slot_group([True, True], **kwargs), - ], - ] - for term, slot_group_list in zip(terms, slot_group_lists): - parts = term.get_parts_by_tex(filler_tex) - for part, slot_group in zip(parts, slot_group_list): - slot_group.replace(part, dim_to_match = 0) - term.submobjects[term.index_of_part(part)] = slot_group - # terms[2][1].content[0].set_fill(BLACK, 0) - # VGroup(*terms[3][1].content[:2]).set_fill(BLACK, 0) - - value_texs = ["0.8", ">0.8", "<0.2"] - for term, tex in zip(terms[1:], value_texs): - term.value = TexMobject(tex) - term.value.next_to(term, UP) - - self.terms = terms - self.add(terms[0]) - - def add_sample_space(self): - SampleSpaceScene.add_sample_space(self, height = 4, width = 5) - self.sample_space.to_edge(DOWN) - - def show_first_division(self): - space = self.sample_space - space.divide_horizontally( - [0.8], colors = [GREEN_E, RED_E] - ) - space.horizontal_parts.fade(0.1) - top_label = self.terms[1].copy() - bottom_label = top_label.copy() - slot_group = get_slot_group([False], buff = SMALL_BUFF, include_qs = False) - slot_group.replace(bottom_label[1]) - Transform(bottom_label[1], slot_group).update(1) - braces_and_labels = space.get_side_braces_and_labels( - [top_label, bottom_label] - ) - - self.play( - FadeIn(space.horizontal_parts), - FadeIn(braces_and_labels) - ) - self.play(ReplacementTransform( - top_label.copy(), self.terms[1] - )) - self.wait() - self.play(Write(self.terms[1].value)) - self.wait() - - space.add(braces_and_labels) - self.top_part = space.horizontal_parts[0] - - def show_second_division(self): - space = self.sample_space - top_part = self.top_part - green_red_mix = average_color(GREEN_E, RED_E) - top_part.divide_vertically( - [0.9], colors = [GREEN_E, green_red_mix] - ) - label = self.terms[2].deepcopy() - braces_and_labels = top_part.get_top_braces_and_labels( - labels = [label] - ) - - self.play( - FadeIn(top_part.vertical_parts), - FadeIn(braces_and_labels) - ) - self.play(ReplacementTransform( - label.copy(), self.terms[2] - )) - self.wait() - self.play(Write(self.terms[2].value)) - self.wait() - - space.add(braces_and_labels) - self.top_left_part = top_part.vertical_parts[0] - - def move_to_third_dimension(self): - space = self.sample_space - part = self.top_left_part - cubes = VGroup( - Cube(fill_color = RED_E), - Cube(fill_color = GREEN_E), - ) - cubes.set_fill(opacity = 0) - cubes.stretch_to_fit_width(part.get_width()) - cubes.stretch_to_fit_height(part.get_height()) - cubes[1].move_to(part, IN) - cubes[0].stretch(0.2, 2) - cubes[0].move_to(cubes[1].get_edge_center(OUT), IN) - space.add(cubes) - - self.play( - space.rotate, 0.9*np.pi/2, LEFT, - space.rotate, np.pi/12, UP, - space.to_corner, DOWN+RIGHT, LARGE_BUFF - ) - space.remove(cubes) - self.play( - cubes[0].set_fill, None, 1, - cubes[0].set_stroke, WHITE, 1, - cubes[1].set_fill, None, 0.5, - cubes[1].set_stroke, WHITE, 1, - ) - self.wait() - - self.cubes = cubes - - def show_final_probability(self): - cube = self.cubes[0] - face = cube[2] - points = face.get_anchors() - line = Line(points[2], points[3]) - line.set_stroke(YELLOW, 8) - brace = Brace(line, LEFT) - label = self.terms[3].copy() - label.next_to(brace, LEFT) - - self.play( - GrowFromCenter(brace), - FadeIn(label), - ) - self.wait() - self.play(ReplacementTransform( - label.copy(), self.terms[3] - )) - self.wait() - - def show_confusion(self): - randy = Randolph() - randy.to_corner(DOWN+LEFT) - - self.play(FadeIn(randy)) - self.play(randy.change, "confused", self.terms) - self.play(randy.look_at, self.cubes) - self.play(Blink(randy)) - self.play(randy.look_at, self.terms) - self.wait() - -class ShowAllEightConditionals(Scene): - def construct(self): - self.show_all_conditionals() - self.suggest_independence() - - def show_all_conditionals(self): - equations = VGroup() - filler_tex = "Filler" - for bool_list in it.product(*[[True, False]]*3): - equation = TexMobject( - "P(", filler_tex, ")", "=", - "P(", filler_tex, ")", - "P(", filler_tex, "|", filler_tex, ")", - "P(", filler_tex, "|", filler_tex, ")", - ) - sub_bool_lists = [ - bool_list[:n] for n in (3, 1, 2, 1, 3, 2) - ] - parts = equation.get_parts_by_tex(filler_tex) - for part, sub_list in zip(parts, sub_bool_lists): - slot_group = get_slot_group( - sub_list, - buff = SMALL_BUFF, - include_qs = False - ) - slot_group.replace(part, dim_to_match = 0) - index = equation.index_of_part(part) - equation.submobjects[index] = slot_group - equations.add(equation) - equations.arrange(DOWN) - - rect = SurroundingRectangle( - VGroup(*equations[0][7:], *equations[-1][7:]), - buff = SMALL_BUFF - ) - rect.shift(0.5*SMALL_BUFF*RIGHT) - - self.play(LaggedStartMap( - FadeIn, equations, - run_time = 5, - lag_ratio = 0.3 - )) - self.wait() - self.play(ShowCreation(rect, run_time = 2)) - self.play(FadeOut(rect)) - self.wait() - - def suggest_independence(self): - full_screen_rect = FullScreenFadeRectangle() - randy = Randolph() - randy.to_corner(DOWN+LEFT) - - - self.play( - FadeIn(full_screen_rect), - FadeIn(randy) - ) - self.play(PiCreatureSays( - randy, "Let's just assume \\\\ independence.", - target_mode = "shruggie" - )) - self.play(Blink(randy)) - self.wait() - -class ShowIndependenceSymbolically(Scene): - def construct(self): - filler_tex = "Filler" - rhs = TexMobject("=", "0.8") - rhs.set_color_by_tex("0.8", YELLOW) - rhs.next_to(ORIGIN, RIGHT, LARGE_BUFF) - lhs = TexMobject("P(", filler_tex, "|", filler_tex, ")") - lhs.next_to(rhs, LEFT) - VGroup(lhs, rhs).scale(1.5) - for part in lhs.get_parts_by_tex(filler_tex): - slot_group = get_slot_group( - [True, True, True], - buff = SMALL_BUFF, - include_qs = False, - ) - slot_group.replace(part, dim_to_match = 0) - lhs.submobjects[lhs.index_of_part(part)] = slot_group - VGroup(*lhs[1].content[:2]).set_fill(BLACK, 0) - condition = lhs[3] - condition.content[2].set_fill(BLACK, 0) - bool_lists = [ - [False], [True, False], [False, True], [True], - ] - arrow = Arrow(UP, DOWN) - arrow.next_to(condition, UP) - arrow.set_color(RED) - words = TextMobject("Doesn't matter") - words.set_color(RED) - words.next_to(arrow, UP) - - self.add(rhs, lhs, arrow, words) - self.wait() - for bool_list in bool_lists: - slot_group = get_slot_group(bool_list, SMALL_BUFF, False) - slot_group.replace(condition) - slot_group.move_to(condition, DOWN) - self.play(Transform(condition, slot_group)) - self.wait() - -class ComputeProbabilityOfOneWrong(Scene): - CONFIG = { - "score" : 2, - "final_result_rhs_tex" : [ - "3", "(0.8)", "^2", "(0.2)", "=", "0.384", - ], - "default_bool" : True, - "default_p" : "0.8", - "default_q" : "0.2", - } - def construct(self): - self.show_all_three_patterns() - self.show_final_result() - - def show_all_three_patterns(self): - probabilities = VGroup() - point_8s = VGroup() - point_2s = VGroup() - for i in reversed(list(range(3))): - bool_list = [self.default_bool]*3 - bool_list[i] = not self.default_bool - probs = ["(%s)"%self.default_p]*3 - probs[i] = "(%s)"%self.default_q - lhs = get_probability_of_slot_group(bool_list) - rhs = TexMobject("=", *probs) - rhs.set_color_by_tex("0.8", GREEN) - rhs.set_color_by_tex("0.2", RED) - point_8s.add(*rhs.get_parts_by_tex("0.8")) - point_2s.add(*rhs.get_parts_by_tex("0.2")) - rhs.next_to(lhs, RIGHT) - probabilities.add(VGroup(lhs, rhs)) - probabilities.arrange(DOWN, buff = LARGE_BUFF) - probabilities.center() - - self.play(Write(probabilities[0])) - self.wait(2) - for i in range(2): - self.play(ReplacementTransform( - probabilities[i].copy(), - probabilities[i+1] - )) - self.wait() - for group in point_8s, point_2s: - self.play(LaggedStartMap( - Indicate, group, - rate_func = there_and_back, - lag_ratio = 0.7 - )) - self.wait() - - def show_final_result(self): - result = TexMobject( - "P(", "\\text{Score} = %s"%self.score, ")", "=", - *self.final_result_rhs_tex - ) - result.set_color_by_tex_to_color_map({ - "0.8" : GREEN, - "0.2" : RED, - "Score" : YELLOW, - }) - result[-1].set_color(YELLOW) - result.set_color_by_tex("0.8", GREEN) - result.set_color_by_tex("0.2", RED) - result.to_edge(UP) - - self.play(Write(result)) - self.wait() - -class ComputeProbabilityOfOneRight(ComputeProbabilityOfOneWrong): - CONFIG = { - "score" : 1, - "final_result_rhs_tex" : [ - "3", "(0.8)", "(0.2)", "^2", "=", "0.096", - ], - "default_bool" : False, - "default_p" : "0.2", - "default_q" : "0.8", - } - -class ShowFullDistribution(Scene): - def construct(self): - self.add_scores_one_and_two() - self.add_scores_zero_and_three() - self.show_bar_chart() - self.compare_to_binomial_pattern() - self.show_alternate_values_of_p() - - def add_scores_one_and_two(self): - scores = VGroup( - TexMobject( - "P(", "\\text{Score} = 0", ")", - "=", "(0.2)", "^3", - "=", "0.008", - ), - TexMobject( - "P(", "\\text{Score} = 1", ")", - "=", "3", "(0.8)", "(0.2)", "^2", - "=", "0.096", - ), - TexMobject( - "P(", "\\text{Score} = 2", ")", - "=", "3", "(0.8)", "^2", "(0.2)", - "=", "0.384", - ), - TexMobject( - "P(", "\\text{Score} = 3", ")", - "=", "(0.8)", "^3", - "=", "0.512", - ), - ) - scores.arrange( - DOWN, - buff = MED_LARGE_BUFF, - aligned_edge = LEFT - ) - scores.shift(MED_LARGE_BUFF*UP) - scores.to_edge(LEFT) - for score in scores: - score.set_color_by_tex_to_color_map({ - "0.8" : GREEN, - "0.2" : RED, - }) - score[-1].set_color(YELLOW) - - self.add(*scores[1:3]) - self.scores = scores - - def add_scores_zero_and_three(self): - self.p_slot_groups = VGroup() - - self.wait() - self.add_edge_score(0, UP, False) - self.add_edge_score(3, DOWN, True) - - def add_edge_score(self, index, vect, q_bool): - score = self.scores[index] - prob = VGroup(*score[:3]) - brace = Brace(prob, vect) - p_slot_group = get_probability_of_slot_group([q_bool]*3) - p_slot_group.next_to(brace, vect) - group = VGroup(*it.chain(p_slot_group, brace, score)) - - self.play(LaggedStartMap( - FadeIn, group, - run_time = 2, - lag_ratio = 0.7, - )) - self.wait(2) - self.p_slot_groups.add(brace, p_slot_group) - - def show_bar_chart(self): - p_terms = VGroup() - to_fade = VGroup(self.p_slot_groups) - value_mobs = VGroup() - for score in self.scores: - p_terms.add(VGroup(*score[:3])) - to_fade.add(VGroup(*score[3:-1])) - value_mobs.add(score[-1]) - dist = get_binomial_distribution(3, 0.8) - values = list(map(dist, list(range(4)))) - chart = BarChart( - values, bar_names = list(range(4)), - ) - chart.shift(DOWN) - - new_p_terms = VGroup(*[ - TexMobject("P(", "S=%d"%k, ")") - for k in range(4) - ]) - for term, bar in zip(new_p_terms, chart.bars): - term[1].set_color(YELLOW) - term.set_width(1.5*bar.get_width()) - term.next_to(bar, UP) - - self.play( - ReplacementTransform( - value_mobs, chart.bars, - lag_ratio = 0.5, - run_time = 2 - ) - ) - self.play( - LaggedStartMap(FadeIn, VGroup(*it.chain(*[ - submob - for submob in chart - if submob is not chart.bars - ]))), - Transform(p_terms, new_p_terms), - FadeOut(to_fade), - ) - self.wait(2) - - chart.bar_top_labels = p_terms - chart.add(p_terms) - self.bar_chart = chart - - def compare_to_binomial_pattern(self): - dist = get_binomial_distribution(3, 0.5) - values = list(map(dist, list(range(4)))) - alt_chart = BarChart(values) - alt_chart.move_to(self.bar_chart) - bars = alt_chart.bars - bars.set_fill(GREY, opacity = 0.5) - vect = 4*UP - bars.shift(vect) - nums = VGroup(*list(map(TexMobject, list(map(str, [1, 3, 3, 1]))))) - for num, bar in zip(nums, bars): - num.next_to(bar, UP) - bars_copy = bars.copy() - - self.play( - LaggedStartMap(FadeIn, bars), - LaggedStartMap(FadeIn, nums), - ) - self.wait(2) - self.play(bars_copy.shift, -vect) - self.play(ReplacementTransform( - bars_copy, self.bar_chart.bars - )) - self.wait(2) - self.play( - VGroup(self.bar_chart, bars, nums).to_edge, LEFT - ) - - self.alt_bars = bars - self.alt_bars_labels = nums - - def show_alternate_values_of_p(self): - new_prob = TexMobject( - "P(", "\\text{Correct}", ")", "=", "0.8" - ) - new_prob.set_color_by_tex("Correct", GREEN) - new_prob.shift(FRAME_X_RADIUS*RIGHT/2) - new_prob.to_edge(UP) - - alt_ps = 0.5, 0.65, 0.25 - alt_rhss = VGroup() - alt_charts = VGroup() - for p in alt_ps: - rhs = TexMobject(str(p)) - rhs.set_color(YELLOW) - rhs.move_to(new_prob[-1]) - alt_rhss.add(rhs) - - dist = get_binomial_distribution(3, p) - values = list(map(dist, list(range(4)))) - chart = self.bar_chart.copy() - chart.change_bar_values(values) - for label, bar in zip(chart.bar_top_labels, chart.bars): - label.next_to(bar, UP) - alt_charts.add(chart) - - self.play(FadeIn(new_prob)) - self.play(Transform(new_prob[-1], alt_rhss[0])) - point_5_probs = self.show_point_5_probs(new_prob) - self.wait() - self.play(Transform(self.bar_chart, alt_charts[0])) - self.wait() - self.play(FadeOut(point_5_probs)) - for rhs, chart in list(zip(alt_rhss, alt_charts))[1:]: - self.play(Transform(new_prob[-1], rhs)) - self.play(Transform(self.bar_chart, chart)) - self.wait(2) - - def show_point_5_probs(self, mob): - probs = VGroup() - last = mob - for k in range(4): - buff = MED_LARGE_BUFF - for indices in it.combinations(list(range(3)), k): - bool_list = np.array([False]*3) - bool_list[list(indices)] = True - prob = get_probability_of_slot_group(bool_list) - rhs = TexMobject("= (0.5)^3") - rhs.next_to(prob, RIGHT) - prob.add(rhs) - prob.scale(0.9) - prob.next_to(last, DOWN, buff) - probs.add(prob) - last = prob - buff = SMALL_BUFF - - self.play(LaggedStartMap(FadeIn, probs)) - self.wait() - return probs - -class ProbablyWrong(TeacherStudentsScene): - def construct(self): - self.teacher_says( - "Probably wrong!", - run_time = 1, - ) - self.change_student_modes( - *["angry"]*3, - run_time = 1 - ) - self.wait() - -class ShowTrueDistribution(PiCreatureScene): - def construct(self): - self.force_skipping() - - self.remove(self.randy) - self.add_title() - self.show_distributions() - self.show_emotion() - self.imagine_score_0() - - self.revert_to_original_skipping_status() - self.get_angry() - - def add_title(self): - title = TexMobject("P(", "\\text{Correct}", ")", "=", "0.65") - title.to_edge(UP) - title.set_color_by_tex("Correct", GREEN) - - self.add(title) - self.title = title - - def show_distributions(self): - dist = get_binomial_distribution(3, 0.65) - values = np.array(list(map(dist, list(range(4))))) - alt_values = values + [0.2, 0, 0, 0.2] - alt_values /= sum(alt_values) - chart = BarChart(values, bar_names = list(range(4))) - bars = chart.bars - old_bars = bars.copy() - arrows = VGroup() - for bar, old_bar in zip(bars, old_bars): - for mob, vect in (bar, RIGHT), (old_bar, LEFT): - mob.generate_target() - mob.target.do_about_point( - mob.get_corner(DOWN+vect), - mob.target.stretch, 0.5, 0 - ) - old_bar.target.set_color(average_color(RED_E, BLACK)) - old_bar.target.set_stroke(width = 0) - arrow = Arrow(ORIGIN, UP, buff = 0, color = GREEN) - arrow.move_to(bar.get_bottom()) - arrow.shift(3*UP) - arrows.add(arrow) - for arrow in arrows[1:3]: - arrow.rotate_in_place(np.pi) - arrow.set_color(RED) - arrows.set_color_by_gradient(BLUE, YELLOW) - - self.add(chart) - self.play(*list(map(MoveToTarget, it.chain(bars, old_bars)))) - self.play( - chart.change_bar_values, alt_values, - *list(map(ShowCreation, arrows)) - ) - self.wait(2) - - self.bar_chart = chart - self.old_bars = old_bars - - def show_emotion(self): - randy = self.randy - - self.play(FadeIn(randy)) - self.play(randy.change, "sad") - self.play(Blink(randy)) - - def imagine_score_0(self): - prob_rect = SurroundingRectangle(self.title[-1]) - bar_rect = SurroundingRectangle(VGroup( - self.bar_chart.bars[0], self.old_bars[0], - self.bar_chart.bar_labels[0], - )) - - self.play(ShowCreation(prob_rect)) - self.wait() - self.play(ReplacementTransform( - prob_rect, bar_rect - )) - self.wait() - self.play(FadeOut(bar_rect)) - - def get_angry(self): - randy = self.randy - - self.play(randy.change, "angry") - self.wait(2) - self.play(PiCreatureSays( - randy, "It's not representative!", - target_mode = "pleading", - bubble_kwargs = {"fill_opacity" : 1} - )) - self.wait(2) - - ##### - - def create_pi_creature(self): - self.randy = Randolph() - self.randy.to_corner(DOWN+LEFT) - return self.randy - -class TeacherAssessingLiklihoodOfZero(TeacherStudentsScene): - def construct(self): - self.add_title() - self.fade_other_students() - self.show_independence_probability() - self.teacher_reacts() - - def add_title(self): - title = TexMobject("P(", "\\text{Correct}", ")", "=", "0.65") - title.to_edge(UP) - title.set_color_by_tex("Correct", GREEN) - q_mark = TexMobject("?") - q_mark.next_to(title[-2], UP, SMALL_BUFF) - title.add(q_mark) - - self.add(title) - self.title = title - - def fade_other_students(self): - for student in self.students[0::2]: - student.fade(0.7) - self.pi_creatures.remove(student) - - def show_independence_probability(self): - prob = get_probability_of_slot_group(3*[False]) - rhs = TexMobject("=", "(0.35)", "^3", "\\approx 4.3\\%") - rhs.set_color_by_tex("0.35", RED) - rhs.next_to(prob, RIGHT) - prob.add(rhs) - prob.next_to(self.teacher, UP+LEFT) - words = TextMobject("Assuming independence") - words.next_to(prob, UP) - - self.play( - self.teacher.change, "raise_right_hand", - FadeIn(words), - Write(prob) - ) - self.wait() - - self.ind_group = VGroup(prob, words) - - def teacher_reacts(self): - ind_group = self.ind_group - box = SurroundingRectangle(ind_group) - box.set_stroke(WHITE, 0) - ind_group.add(box) - ind_group.generate_target() - ind_group.target.scale(0.7) - ind_group.target.to_corner(UP+RIGHT, MED_SMALL_BUFF) - ind_group.target[-1].set_stroke(WHITE, 2) - - randy = self.students[1] - - self.teacher_says( - "Highly unlikely", - target_mode = "sassy", - added_anims = [MoveToTarget(ind_group)], - run_time = 2, - ) - self.play(randy.change, "sad") - self.wait(2) - self.play( - RemovePiCreatureBubble( - self.teacher, target_mode = "guilty", - ), - PiCreatureSays(randy, "Wait!", target_mode = "surprised"), - run_time = 1 - ) - self.wait(1) - -class CorrelationsWith35Percent(ThousandPossibleQuizzes): - def construct(self): - self.add_top_calculation() - self.show_first_split() - self.show_second_split() - self.show_third_split() - self.comment_on_final_size() - - def add_top_calculation(self): - equation = VGroup( - get_probability_of_slot_group(3*[False]), - TexMobject("="), - get_probability_of_slot_group([False]), - get_probability_of_slot_group(2*[False], [False]), - get_probability_of_slot_group(3*[False], 2*[False]), - ) - equation.arrange(RIGHT, buff = SMALL_BUFF) - equation.to_edge(UP) - - self.add(equation) - self.equation = equation - - def show_first_split(self): - quizzes = self.get_thousand_quizzes() - n = int(0.65*len(quizzes)) - top_part = VGroup(*quizzes[:n]) - bottom_part = VGroup(*quizzes[n:]) - parts = [top_part, bottom_part] - for part, color in zip(parts, [GREEN, RED]): - part.generate_target() - for quiz in part.target: - quiz[0].set_color(color) - top_part.target.shift(UP) - brace = Brace(bottom_part, LEFT) - prop = TexMobject("0.35") - prop.next_to(brace, LEFT) - - term = self.equation[2] - term_brace = Brace(term, DOWN) - - self.add(quizzes) - self.wait() - self.play( - GrowFromCenter(brace), - FadeIn(prop), - *list(map(MoveToTarget, parts)) - ) - self.wait() - self.play( - top_part.fade, 0.8, - Transform(brace, term_brace), - prop.next_to, term_brace, DOWN, - ) - self.wait() - - self.quizzes = bottom_part - self.quizzes.sort(lambda p : p[0]) - - def show_second_split(self): - n = int(0.45*len(self.quizzes)) - left_part = VGroup(*self.quizzes[:n]) - right_part = VGroup(*self.quizzes[n:]) - parts = [left_part, right_part] - for part, color in zip(parts, [GREEN, RED_E]): - part.generate_target() - for quiz in part.target: - quiz[1].set_color(color) - left_part.target.shift(LEFT) - brace = Brace(right_part, UP) - prop = TexMobject(">0.35") - prop.next_to(brace, UP) - - term = self.equation[3] - term_brace = Brace(term, DOWN) - - self.play( - GrowFromCenter(brace), - FadeIn(prop), - *list(map(MoveToTarget, parts)) - ) - self.wait() - self.play( - Transform(brace, term_brace), - prop.next_to, term_brace, DOWN - ) - self.play(left_part.fade, 0.8) - - self.quizzes = right_part - self.quizzes.sort(lambda p : -p[1]) - - def show_third_split(self): - quizzes = self.quizzes - n = int(0.22*len(quizzes)) - top_part = VGroup(*quizzes[:n]) - bottom_part = VGroup(*quizzes[n:]) - parts = [top_part, bottom_part] - for part, color in zip(parts, [GREEN, RED_B]): - part.generate_target() - for quiz in part.target: - quiz[2].set_color(color) - top_part.target.shift(0.5*UP) - brace = Brace(bottom_part, LEFT) - prop = TexMobject("\\gg 0.35") - prop.next_to(brace, LEFT) - - term = self.equation[4] - term_brace = Brace(term, DOWN) - - self.play( - GrowFromCenter(brace), - FadeIn(prop), - *list(map(MoveToTarget, parts)) - ) - self.wait() - self.play( - Transform(brace, term_brace), - prop.next_to, term_brace, DOWN, - ) - self.play(top_part.fade, 0.8) - self.wait() - - self.quizzes = bottom_part - - def comment_on_final_size(self): - rect = SurroundingRectangle(self.quizzes) - words = TextMobject( - "Much more than ", "$(0.35)^3 \\approx 4.3\\%$" - ) - words.next_to(rect, LEFT) - - self.play( - ShowCreation(rect), - FadeIn(words) - ) - self.wait() - -class WeighingIndependenceAssumption(PiCreatureScene): - def construct(self): - randy = self.randy - - title = TextMobject("Independence") - title.scale(1.5) - title.to_edge(UP) - self.add(title) - formula = TexMobject( - "P(", "A", "B", ")", "=" - "P(", "A", ")", "P(", "B", ")" - ) - formula.set_color_by_tex("A", BLUE) - formula.set_color_by_tex("B", GREEN) - - clean = TextMobject("Clean") - clean.next_to(formula, UP) - VGroup(clean, formula).next_to(randy, UP+LEFT) - clean.save_state() - clean.shift(2*(DOWN+RIGHT)) - clean.set_fill(opacity = 0) - - self.play( - randy.change, "raise_left_hand", clean, - clean.restore - ) - self.play(Write(formula)) - self.play( - randy.change, "raise_right_hand", - randy.look, UP+RIGHT, - ) - self.wait(2) - - #### - - def create_pi_creature(self): - self.randy = Randolph().to_edge(DOWN) - return self.randy - -class NameBinomial(Scene): - CONFIG = { - "flip_indices" : [0, 2, 4, 5, 6, 7], - } - def construct(self): - self.name_distribution() - self.add_quiz_questions() - self.change_to_gender() - self.change_bar_chart_for_gender_example() - self.point_out_example_input() - self.write_probability_of_girl() - self.think_through_probabilities() - - def name_distribution(self): - ns = [3, 10] - p = 0.65 - charts = VGroup() - for n in ns: - dist = get_binomial_distribution(n, p) - values = list(map(dist, list(range(n+1)))) - chart = BarChart(values, bar_names = list(range(n+1))) - chart.to_edge(LEFT) - charts.add(chart) - - probability = TexMobject( - "P(", "\\checkmark", ")", "=", str(p) - ) - probability.set_color_by_tex("checkmark", GREEN) - probability.move_to(charts, UP) - - title = TextMobject("``Binomial distribution''") - title.next_to(charts, UP) - title.to_edge(UP) - formula = TexMobject( - "P(X=", "k", ")=", - "{n \\choose k}", - "p", "^k", - "(1-", "p", ")", "^{n-", "k}", - arg_separator = "" - ) - formula.set_color_by_tex("p", YELLOW) - formula.set_color_by_tex("k", GREEN) - choose_part = formula.get_part_by_tex("choose") - choose_part.set_color(WHITE) - choose_part[-2].set_color(GREEN) - formula.to_corner(UP+RIGHT) - - self.add(charts[0], probability) - self.wait() - self.play(Write(title)) - self.wait() - self.play(ReplacementTransform(*charts)) - self.play(Write(formula)) - self.wait() - self.play( - formula.scale, 0.7, - formula.next_to, charts, DOWN, - ) - - self.chart = charts[1] - self.probability = probability - self.title = title - self.formula = formula - - def add_quiz_questions(self): - n = 10 - checkmarks = VGroup(*[ - TexMobject("\\checkmark").set_color(GREEN) - for x in range(n) - ]) - checkmarks.arrange(DOWN, buff = 0.3) - crosses = VGroup() - arrows = VGroup() - for checkmark in checkmarks: - cross = TexMobject("\\times") - cross.set_color(RED) - cross.next_to(checkmark, RIGHT, LARGE_BUFF) - crosses.add(cross) - arrow = Arrow( - checkmark, cross, - tip_length = 0.15, - color = WHITE - ) - arrows.add(arrow) - full_group = VGroup(checkmarks, crosses, arrows) - full_group.center().to_corner(UP + RIGHT, buff = MED_LARGE_BUFF) - flip_indices = self.flip_indices - flipped_arrows, faded_crosses, full_checks = [ - VGroup(*[group[i] for i in flip_indices]) - for group in (arrows, crosses, checkmarks) - ] - faded_checkmarks = VGroup(*[m for m in checkmarks if m not in full_checks]) - - self.play(*[ - LaggedStartMap( - Write, mob, - run_time = 3, - lag_ratio = 0.3 - ) - for mob in full_group - ]) - self.wait() - self.play( - LaggedStartMap( - Rotate, flipped_arrows, - angle = np.pi, - in_place = True, - run_time = 2, - lag_ratio = 0.5 - ), - faded_crosses.set_fill, None, 0.5, - faded_checkmarks.set_fill, None, 0.5, - ) - self.wait() - - self.checkmarks = checkmarks - self.crosses = crosses - self.arrows = arrows - - def change_to_gender(self): - flip_indices = self.flip_indices - male = self.get_male() - female = self.get_female() - - girls, boys = [ - VGroup(*[ - template.copy().move_to(mob) - for mob in group - ]) - for template, group in [ - (female, self.checkmarks), (male, self.crosses) - ] - ] - for i in range(len(boys)): - mob = boys[i] if i in flip_indices else girls[i] - mob.set_fill(opacity = 0.5) - - brace = Brace(girls, LEFT) - words = brace.get_text("$n$ children") - - self.play( - GrowFromCenter(brace), - FadeIn(words) - ) - for m1, m2 in (self.crosses, boys), (self.checkmarks, girls): - self.play(ReplacementTransform( - m1, m2, - lag_ratio = 0.5, - run_time = 3 - )) - self.wait() - - self.boys = boys - self.girls = girls - self.children_brace = brace - self.n_children_words = words - - def change_bar_chart_for_gender_example(self): - checkmark = self.probability.get_part_by_tex("checkmark") - p_mob = self.probability[-1] - - female = self.get_female() - female.move_to(checkmark) - new_p_mob = TexMobject("0.49") - new_p_mob.move_to(p_mob, LEFT) - - dist = get_binomial_distribution(10, 0.49) - values = list(map(dist, list(range(11)))) - - self.play( - Transform(checkmark, female), - Transform(p_mob, new_p_mob), - ) - self.play(self.chart.change_bar_values, values) - self.wait() - - def point_out_example_input(self): - boy_girl_groups = VGroup(*[ - VGroup(boy, girl) - for boy, girl in zip(self.boys, self.girls) - ]) - girl_rects = VGroup(*[ - SurroundingRectangle( - boy_girl_groups[i], - color = MAROON_B, - buff = SMALL_BUFF, - ) - for i in sorted(self.flip_indices) - ]) - - chart = self.chart - n_girls = len(girl_rects) - chart_rect = SurroundingRectangle( - VGroup(chart.bars[n_girls], chart.bar_labels[n_girls]), - buff = SMALL_BUFF - ) - - prob = TexMobject( - "P(", "\\# \\text{Girls}", "=", "6", ")" - ) - prob.set_color_by_tex("Girls", MAROON_B) - arrow = Arrow(UP, ORIGIN, tip_length = 0.15) - arrow.set_color(MAROON_B) - arrow.next_to(prob, DOWN) - prob.add(arrow) - prob.next_to(chart_rect, UP) - girls = VGroup(*[self.girls[i] for i in self.flip_indices]) - - - self.play(ShowCreation(chart_rect)) - self.play(LaggedStartMap( - ShowCreation, girl_rects, - run_time = 2, - lag_ratio = 0.5, - )) - self.wait() - - self.play(Write(prob)) - self.play(LaggedStartMap( - Indicate, girls, - run_time = 3, - lag_ratio = 0.3, - rate_func = there_and_back - )) - self.play(FadeOut(prob)) - self.wait() - - self.chart_rect = chart_rect - self.girl_rects = girl_rects - - def write_probability_of_girl(self): - probability = self.probability - probability_copies = VGroup(*[ - probability.copy().scale(0.7).next_to( - girl, LEFT, MED_LARGE_BUFF - ) - for girl in self.girls - ]) - - self.play(FocusOn(probability)) - self.play(Indicate(probability[-1])) - self.wait() - self.play( - ReplacementTransform( - VGroup(probability.copy()), probability_copies - ), - FadeOut(self.children_brace), - FadeOut(self.n_children_words), - ) - self.wait() - - self.probability_copies = probability_copies - - def think_through_probabilities(self): - randy = Randolph().scale(0.5) - randy.next_to(self.probability_copies, LEFT, LARGE_BUFF) - - self.play(FadeIn(randy)) - self.play(randy.change, "pondering") - self.play(Blink(randy)) - self.wait() - - ## - - def get_male(self): - return TexMobject("\\male").scale(1.3).set_color(BLUE) - - def get_female(self): - return TexMobject("\\female").scale(1.3).set_color(MAROON_B) - -class CycleThroughPatterns(NameBinomial): - CONFIG = { - "n_patterns_shown" : 100, - "pattern_scale_value" : 2.7, - "n" : 10, - "k" : 6, - } - def construct(self): - n = self.n - k = self.k - question = TextMobject( - "How many patterns have \\\\ %d "%k, - "$\\female$", - " and %d "%(n-k), - "$\\male$", - "?", - arg_separator = "" - ) - question.set_color_by_tex("male", BLUE) - question.set_color_by_tex("female", MAROON_B) - question.set_width(FRAME_WIDTH - 1) - question.to_edge(UP, buff = LARGE_BUFF) - self.add(question) - - all_combinations = list(it.combinations(list(range(n)), k)) - shown_combinations = all_combinations[:self.n_patterns_shown] - patterns = VGroup(*[ - self.get_pattern(indicies) - for indicies in shown_combinations - ]) - patterns.to_edge(DOWN, buff = LARGE_BUFF) - pattern = patterns[0] - self.add(pattern) - for new_pattern in ProgressDisplay(patterns[1:]): - self.play(*[ - Transform( - getattr(pattern, attr), - getattr(new_pattern, attr), - path_arc = np.pi - ) - for attr in ("boys", "girls") - ]) - - #### - - def get_pattern(self, indices): - pattern = VGroup() - pattern.boys = VGroup() - pattern.girls = VGroup() - for i in range(self.n): - if i in indices: - mob = self.get_female() - pattern.girls.add(mob) - else: - mob = self.get_male() - pattern.boys.add(mob) - mob.shift(i*MED_LARGE_BUFF*RIGHT) - pattern.add(mob) - pattern.scale(self.pattern_scale_value) - pattern.to_edge(LEFT) - return pattern - -class Compute6of10GirlsProbability(CycleThroughPatterns): - def construct(self): - self.show_combinations() - self.write_n_choose_k() - - def show_combinations(self): - pattern_rect = ScreenRectangle(height = 4) - pattern_rect.center() - pattern_rect.to_edge(UP, buff = MED_SMALL_BUFF) - - self.add(pattern_rect) - self.wait(5) - - self.pattern_rect = pattern_rect - - def write_n_choose_k(self): - brace = Brace(self.pattern_rect, DOWN) - ten_choose_six = brace.get_tex("{10 \\choose 6}") - see_chapter_one = TextMobject("(See chapter 1)") - see_chapter_one.next_to(ten_choose_six, DOWN) - see_chapter_one.set_color(GREEN) - computation = TexMobject( - "=\\frac{%s}{%s}"%( - "\\cdot ".join(map(str, list(range(10, 4, -1)))), - "\\cdot ".join(map(str, list(range(1, 7)))), - ) - ) - computation.move_to(ten_choose_six, UP) - rhs = TexMobject("=", "210") - rhs.next_to(computation, RIGHT) - - self.play( - FadeIn(see_chapter_one), - GrowFromCenter(brace) - ) - self.play(Write(ten_choose_six)) - self.wait(2) - self.play( - ten_choose_six.next_to, computation.copy(), LEFT, - Write(VGroup(computation, rhs)) - ) - self.wait() - - self.ten_choose_six = ten_choose_six - self.rhs = rhs - -class ProbabilityOfAGivenBoyGirlPattern(CycleThroughPatterns): - def construct(self): - self.write_total_count() - self.write_example_probability() - self.write_total_probability() - - def write_total_count(self): - count = TextMobject( - "${10 \\choose 6}$", " $= 210$", - "total patterns." - ) - count.to_edge(UP) - self.add(count) - - self.count = count - - def write_example_probability(self): - prob = TexMobject("P\\big(", "O "*15, "\\big)", "=") - indices = [1, 2, 4, 6, 8, 9] - pattern = self.get_pattern(indices) - pattern.replace(prob[1], dim_to_match = 0) - prob.submobjects[1] = pattern - prob.next_to(self.count, DOWN, LARGE_BUFF) - - gp = TexMobject("P(\\female)") - gp[2].set_color(MAROON_B) - bp = TexMobject("P(\\male)") - bp[2].set_color(BLUE) - gp_num = TexMobject("(0.49)").set_color(MAROON_B) - bp_num = TexMobject("(0.51)").set_color(BLUE) - gp_nums = VGroup() - bp_nums = VGroup() - factored = VGroup() - factored_in_nums = VGroup() - for i in range(10): - if i in indices: - num_mob = gp_num.copy() - gp_nums.add(num_mob) - p_mob = gp.copy() - else: - num_mob = bp_num.copy() - bp_nums.add(num_mob) - p_mob = bp.copy() - factored_in_nums.add(num_mob) - factored.add(p_mob) - for group in factored, factored_in_nums: - group.arrange(RIGHT, buff = SMALL_BUFF) - group.next_to(prob, DOWN, MED_LARGE_BUFF) - gp_nums.save_state() - bp_nums.save_state() - - final_probability = TexMobject( - "(0.49)^6", "(0.51)^4" - ) - final_probability.set_color_by_tex("0.49", MAROON_B) - final_probability.set_color_by_tex("0.51", BLUE) - final_probability.next_to(factored_in_nums, DOWN, LARGE_BUFF) - - self.play(FadeIn(prob)) - self.wait() - self.play(ReplacementTransform( - pattern.copy(), factored, - run_time = 1.5, - )) - self.wait(2) - self.play(ReplacementTransform( - factored, factored_in_nums, - run_time = 2, - lag_ratio = 0.5 - )) - self.wait(2) - for group, tex in (gp_nums, "0.49"), (bp_nums, "0.51"): - part = final_probability.get_part_by_tex(tex) - self.play(group.shift, MED_LARGE_BUFF*DOWN) - self.play( - ReplacementTransform( - group.copy(), VGroup(VGroup(*part[:-1])) - ), - Write(part[-1]) - ) - self.wait() - self.play(group.restore) - self.wait() - - self.final_probability = final_probability - - def write_total_probability(self): - ten_choose_six = self.count[0].copy() - ten_choose_six.generate_target() - ten_choose_six.target.move_to(self.final_probability) - p_tex = TexMobject("P(", "\\text{6 Girls}", ")", "=") - p_tex.set_color_by_tex("Girls", MAROON_B) - p_tex.next_to(ten_choose_six.target, LEFT) - - self.play( - Write(p_tex, run_time = 2), - self.final_probability.next_to, - ten_choose_six.target, RIGHT - ) - self.play(MoveToTarget(ten_choose_six)) - self.wait() - -class CycleThroughPatternsForThree(CycleThroughPatterns): - CONFIG = { - "k" : 3, - "n_patterns_shown" : 20, - } - -class GeneralBinomialDistributionValues(Scene): - CONFIG = { - "n" : 10, - "alt_n" : 8, - "p" : 0.49, - } - def construct(self): - self.add_chart() - self.show_a_few_values() - self.compare_to_pascal_row() - self.mention_center_concentration() - self.generalize() - self.play_with_p_value() - - def add_chart(self): - dist = get_binomial_distribution(self.n, self.p) - values = list(map(dist, list(range(self.n+1)))) - chart = BarChart( - values, - bar_names = list(range(self.n+1)) - ) - chart.to_edge(LEFT) - - full_probability = self.get_probability_expression( - "10", "k", "(0.49)", "(0.51)" - ) - full_probability.next_to(chart, UP, aligned_edge = LEFT) - - self.add(chart) - - self.chart = chart - self.full_probability = full_probability - - def show_a_few_values(self): - chart = self.chart - probabilities = VGroup() - for i, bar in enumerate(chart.bars): - prob = self.get_probability_expression( - "10", str(i), "(0.49)", "(0.51)", - full = False - ) - arrow = Arrow( - UP, DOWN, - color = WHITE, - tip_length = 0.15 - ) - arrow.next_to(bar, UP, SMALL_BUFF) - prob.next_to(arrow, UP, SMALL_BUFF) - ## - prob.shift(LEFT) - prob.shift_onto_screen() - prob.shift(RIGHT) - ## - prob.add(arrow) - probabilities.add(prob) - shown_prob = probabilities[6].copy() - - self.play(FadeIn(shown_prob)) - self.wait() - self.play(LaggedStartMap( - FadeIn, self.full_probability, - run_time = 4, - lag_ratio = 0.5, - )) - self.wait() - last_k = 6 - for k in 3, 8, 5, 9, 6: - self.play(Transform( - shown_prob, probabilities[k], - path_arc = -np.pi/6 if k > last_k else np.pi/6 - )) - self.wait(2) - last_k = k - - self.shown_prob = shown_prob - - def compare_to_pascal_row(self): - triangle = PascalsTriangle(nrows = 11) - triangle.set_width(6) - triangle.to_corner(UP+RIGHT) - last_row = VGroup(*[ - triangle.coords_to_mobs[10][k] - for k in range(11) - ]) - ten_choose_ks = VGroup() - for k, mob in enumerate(last_row): - ten_choose_k = TexMobject("10 \\choose %s"%k) - ten_choose_k.scale(0.5) - ten_choose_k.stretch(0.8, 0) - ten_choose_k.next_to(mob, DOWN) - ten_choose_ks.add(ten_choose_k) - ten_choose_ks.set_color_by_gradient(BLUE, YELLOW) - - self.play( - LaggedStartMap(FadeIn, triangle), - FadeOut(self.shown_prob) - ) - self.play( - last_row.set_color_by_gradient, BLUE, YELLOW, - Write(ten_choose_ks, run_time = 2) - ) - self.wait() - self.play(ApplyWave(self.chart.bars, direction = UP)) - self.play(FocusOn(last_row)) - self.play(LaggedStartMap( - ApplyMethod, last_row, - lambda m : (m.scale_in_place, 1.2), - rate_func = there_and_back, - )) - self.wait() - - self.pascals_triangle = triangle - self.ten_choose_ks = ten_choose_ks - - def mention_center_concentration(self): - bars = self.chart.bars - bars.generate_target() - bars.save_state() - bars.target.arrange(UP, buff = 0) - bars.target.stretch_to_fit_height(self.chart.height) - bars.target.move_to( - self.chart.x_axis.point_from_proportion(0.05), - DOWN - ) - brace = Brace(VGroup(*bars.target[4:7]), RIGHT) - words = brace.get_text("Most probability \\\\ in middle values") - - self.play(MoveToTarget(bars)) - self.play( - GrowFromCenter(brace), - FadeIn(words) - ) - self.wait(2) - self.play( - bars.restore, - *list(map(FadeOut, [ - brace, words, - self.pascals_triangle, - self.ten_choose_ks - ])) - ) - - def generalize(self): - alt_n = self.alt_n - dist = get_binomial_distribution(alt_n, self.p) - values = list(map(dist, list(range(alt_n + 1)))) - alt_chart = BarChart( - values, bar_names = list(range(alt_n + 1)) - ) - alt_chart.move_to(self.chart) - - alt_probs = [ - self.get_probability_expression("n", "k", "(0.49)", "(0.51)"), - self.get_probability_expression("n", "k", "p", "(1-p)"), - ] - for prob in alt_probs: - prob.move_to(self.full_probability) - - self.play(FocusOn( - self.full_probability.get_part_by_tex("choose") - )) - self.play( - ReplacementTransform(self.chart, alt_chart), - Transform(self.full_probability, alt_probs[0]) - ) - self.chart = alt_chart - self.wait(2) - self.play(Transform(self.full_probability, alt_probs[1])) - self.wait() - - def play_with_p_value(self): - p = self.p - interval = UnitInterval(color = WHITE) - interval.set_width(5) - interval.next_to(self.full_probability, DOWN, LARGE_BUFF) - interval.add_numbers(0, 0.5, 1) - triangle = RegularPolygon( - n=3, start_angle = -np.pi/2, - fill_color = MAROON_B, - fill_opacity = 1, - stroke_width = 0, - ) - triangle.set_height(0.25) - triangle.move_to(interval.number_to_point(p), DOWN) - p_mob = TexMobject("p") - p_mob.set_color(MAROON_B) - p_mob.next_to(triangle, UP, SMALL_BUFF) - triangle.add(p_mob) - - new_p_values = [0.8, 0.4, 0.2, 0.9, 0.97, 0.6] - - self.play( - ShowCreation(interval), - Write(triangle, run_time = 1) - ) - self.wait() - for new_p in new_p_values: - p = new_p - dist = get_binomial_distribution(self.alt_n, p) - values = list(map(dist, list(range(self.alt_n + 1)))) - self.play( - self.chart.change_bar_values, values, - triangle.move_to, interval.number_to_point(p), DOWN - ) - self.wait() - - ####### - - def get_probability_expression( - self, n = "n", k = "k", p = "p", q = "(1-p)", - full = True - ): - args = [] - if full: - args += ["P(", "\\# \\text{Girls}", "=", k, ")", "="] - args += [ - "{%s \\choose %s}"%(n, k), - p, "^%s"%k, - q, "^{%s"%n, "-", "%s}"%k, - ] - result = TexMobject(*args, arg_separator = "") - color_map = { - "Girls" : MAROON_B, - n : WHITE, - k : YELLOW, - p : MAROON_B, - q : BLUE, - } - result.set_color_by_tex_to_color_map(color_map) - choose_part = result.get_part_by_tex("choose") - choose_part.set_color(WHITE) - VGroup(*choose_part[1:1+len(n)]).set_color(color_map[n]) - VGroup(*choose_part[-1-len(k):-1]).set_color(color_map[k]) - return result - -class PointOutSimplicityOfFormula(TeacherStudentsScene, GeneralBinomialDistributionValues): - def construct(self): - prob = self.get_probability_expression(full = False) - corner = self.teacher.get_corner(UP+LEFT) - prob.next_to(corner, UP, MED_LARGE_BUFF) - prob.save_state() - prob.move_to(corner) - prob.set_fill(opacity = 0) - - self.play( - prob.restore, - self.teacher.change_mode, "raise_right_hand" - ) - self.change_student_modes( - *["pondering"]*3, - look_at_arg = prob - ) - self.wait() - self.student_says( - "Simpler than I feared", - target_mode = "hooray", - student_index = 0, - added_anims = [prob.to_corner, UP+RIGHT] - ) - self.wait() - self.teacher_says("Due to \\\\ independence") - self.wait(2) - -class CorrectForDependence(NameBinomial): - CONFIG = { - "flip_indices" : [3, 6, 8], - } - def setup(self): - self.force_skipping() - self.name_distribution() - self.add_quiz_questions() - self.revert_to_original_skipping_status() - - def construct(self): - self.mention_dependence() - self.show_tendency_to_align() - self.adjust_chart() - - def mention_dependence(self): - brace = Brace(self.checkmarks, LEFT) - words = brace.get_text("What if there's \\\\ correlation?") - formula = self.formula - cross = Cross(formula) - - self.play( - GrowFromCenter(brace), - Write(words) - ) - self.wait() - self.play(ShowCreation(cross)) - self.wait() - - def show_tendency_to_align(self): - checkmarks = self.checkmarks - arrows = self.arrows - crosses = self.crosses - groups = [ - VGroup(*trip) - for trip in zip(checkmarks, arrows, crosses) - ] - top_rect = SurroundingRectangle(groups[0]) - top_rect.set_color(GREEN) - indices_to_follow = [1, 4, 5, 7] - - self.play(ShowCreation(top_rect)) - self.play(*self.get_arrow_flip_anims([0])) - self.wait() - self.play(*self.get_arrow_flip_anims(indices_to_follow)) - self.play(FocusOn(self.chart.bars)) - - def adjust_chart(self): - chart = self.chart - bars = chart.bars - old_bars = bars.copy() - old_bars.generate_target() - bars.generate_target() - for group, vect in (old_bars, LEFT), (bars, RIGHT): - for bar in group.target: - side = bar.get_edge_center(vect) - bar.stretch(0.5, 0) - bar.move_to(side, vect) - for bar in old_bars.target: - bar.set_color(average_color(RED_E, BLACK)) - - dist = get_binomial_distribution(10, 0.65) - values = np.array(list(map(dist, list(range(11))))) - alt_values = values + 0.1 - alt_values[0] -= 0.06 - alt_values[1] -= 0.03 - alt_values /= sum(alt_values) - arrows = VGroup() - arrow_template = Arrow( - 0.5*UP, ORIGIN, buff = 0, - tip_length = 0.15, - color = WHITE - ) - for value, alt_value, bar in zip(values, alt_values, bars): - arrow = arrow_template.copy() - if value < alt_value: - arrow.rotate(np.pi, about_point = ORIGIN) - arrow.next_to(bar, UP) - arrows.add(arrow) - - self.play( - MoveToTarget(old_bars), - MoveToTarget(bars), - ) - self.wait() - self.play(*list(map(ShowCreation, arrows))) - self.play(chart.change_bar_values, alt_values) - - ###### - - def get_arrow_flip_anims(self, indices): - checkmarks, arrows, crosses = movers = [ - VGroup(*[ - group[i] - for i in range(len(group)) - if i in indices - ]) - for group in (self.checkmarks, self.arrows, self.crosses) - ] - for arrow in arrows: - arrow.target = arrow.deepcopy() - arrow.target.rotate_in_place(np.pi) - for group in checkmarks, crosses: - for mob, arrow in zip(group, arrows): - mob.generate_target() - c = mob.get_center() - start, end = arrow.target.get_start_and_end() - to_end = get_norm(c - end) - to_start = get_norm(c - start) - if to_end < to_start: - mob.target.set_fill(opacity = 1) - else: - mob.target.set_fill(opacity = 0.5) - for checkmark in checkmarks: - checkmark.target.scale_in_place(1.2) - - kwargs = {"path_arc" : np.pi} - if len(indices) > 1: - kwargs.update({"run_time" : 2}) - return [ - LaggedStartMap( - MoveToTarget, mover, - **kwargs - ) - for mover in movers - ] - -class ButWhatsTheAnswer(TeacherStudentsScene): - def construct(self): - self.student_says( - "But what's the \\\\ actual answer?", - target_mode = "confused" - ) - self.change_student_modes(*["confused"]*3) - self.wait() - self.play(self.teacher.change, "pondering") - self.wait(3) - -class PermuteQuizQuestions(Scene): - def construct(self): - quiz = get_quiz( - "Define ``Brachistochrone''", - "Define ``Tautochrone''", - "Define ``Cycloid''", - ) - questions = [ - VGroup(*q[2:]) - for q in quiz.questions - ] - colors = [BLUE, GREEN, RED] - for color, question in zip(colors, questions): - question.set_color(color) - quiz.scale(2) - - self.add(quiz) - self.wait() - for m1, m2 in it.combinations(questions, 2): - self.play( - m1.move_to, m2, LEFT, - m2.move_to, m1, LEFT, - path_arc = np.pi - ) - self.wait() - -class AssumeOrderDoesntMatter(Scene): - def construct(self): - self.force_skipping() - - self.add_title() - self.show_equality() - self.mention_correlation() - - self.revert_to_original_skipping_status() - self.coming_soon() - - def add_title(self): - title = TextMobject( - "Softer simplifying assumption: " +\ - "Order doesn't matter" - ) - title.to_edge(UP) - - self.add(title) - self.title = title - - def show_equality(self): - n = 3 - prob_groups = VGroup(*[ - VGroup(*list(map( - get_probability_of_slot_group, - [t for t in it.product(*[[True, False]]*n) if sum(t) == k] - ))) - for k in range(n+1) - ]) - for prob_group in prob_groups: - for prob in prob_group[:-1]: - equals = TexMobject("=") - equals.next_to(prob, RIGHT) - prob.add(equals) - prob_group.arrange(RIGHT) - max_width = FRAME_WIDTH - 1 - if prob_group.get_width() > max_width: - prob_group.set_width(max_width) - prob_groups.arrange(DOWN, buff = 0.7) - prob_groups.next_to(self.title, DOWN, MED_LARGE_BUFF) - - self.play(FadeIn( - prob_groups[1], - run_time = 2, - lag_ratio = 0.5 - )) - self.wait(2) - self.play(FadeIn( - VGroup(prob_groups[0], *prob_groups[2:]), - run_time = 3, - lag_ratio = 0.5 - )) - self.wait() - - self.prob_groups = prob_groups - - def mention_correlation(self): - assumption_group = VGroup(*self.get_top_level_mobjects()) - question = TextMobject( - "But what is ", "``correlation''", "?", - arg_separator = "" - ) - question.set_color(BLUE) - question.to_edge(UP) - bottom = question.get_bottom() - - self.play( - Write(question), - assumption_group.next_to, bottom, DOWN, LARGE_BUFF - ) - self.wait() - - self.assumption_group = assumption_group - self.question = question - - def coming_soon(self): - self.play( - LaggedStartMap( - ApplyMethod, self.assumption_group, - lambda m : (m.shift, FRAME_HEIGHT*DOWN), - remover = True, - ), - ApplyMethod( - self.question.center, - rate_func = squish_rate_func(smooth, 0.5, 1), - run_time = 2 - ) - ) - - part = self.question.get_part_by_tex("correlation") - brace = Brace(part, UP) - words = brace.get_text("Coming soon!") - self.play( - GrowFromCenter(brace), - part.set_color, YELLOW - ) - self.play(Write(words)) - self.wait() - - - -class FormulaCanBeRediscovered(PointOutSimplicityOfFormula): - def construct(self): - prob = self.get_probability_expression(full = False) - corner = self.teacher.get_corner(UP+LEFT) - prob.next_to(corner, UP, MED_LARGE_BUFF) - brace = Brace(prob, UP) - rediscover = brace.get_text("Rediscover") - - self.play( - Write(prob), - self.teacher.change, "hesitant", prob - ) - self.wait() - self.play( - GrowFromCenter(brace), - Write(rediscover, run_time = 1) - ) - self.change_student_modes(*["happy"]*3) - self.wait(2) - -class CompareTwoSituations(PiCreatureScene): - def construct(self): - randy = self.randy - top_left, top_right = screens = [ - ScreenRectangle(height = 3).to_corner(vect) - for vect in (UP+LEFT, UP+RIGHT) - ] - arrow = DoubleArrow(*screens, buff = SMALL_BUFF) - arrow.set_color(BLUE) - - for screen, s in zip(screens, ["left", "right"]): - self.play( - randy.change, "raise_%s_hand"%s, screen, - ShowCreation(screen) - ) - self.wait(3) - self.play( - randy.change, "pondering", arrow, - ShowCreation(arrow) - ) - self.wait(2) - - #### - - def create_pi_creature(self): - self.randy = Randolph().to_edge(DOWN) - return self.randy - -class SkepticalOfDistributions(TeacherStudentsScene): - CONFIG = { - "chart_height" : 3, - } - def construct(self): - self.show_binomial() - self.show_alternate_distributions() - self.emphasize_underweighted_tails() - - def show_binomial(self): - binomial = self.get_binomial() - binomial.next_to(self.teacher.get_corner(UP+LEFT), UP) - title = TextMobject("Probable scores") - title.scale(0.85) - title.next_to(binomial.bars, UP, 1.5*LARGE_BUFF) - - self.play( - Write(title, run_time = 1), - FadeIn(binomial, run_time = 1, lag_ratio = 0.5), - self.teacher.change, "raise_right_hand" - ) - for values in binomial.values_list: - self.play(binomial.change_bar_values, values) - self.wait() - self.student_says( - "Is that valid?", target_mode = "sassy", - student_index = 0, - run_time = 1 - ) - self.play(self.teacher.change, "guilty") - self.wait() - - binomial.add(title) - self.binomial = binomial - - def show_alternate_distributions(self): - poisson = self.get_poisson() - VGroup(poisson, poisson.title).next_to( - self.students, UP, LARGE_BUFF - ).shift(RIGHT) - gaussian = self.get_gaussian() - VGroup(gaussian, gaussian.title).next_to( - poisson, RIGHT, LARGE_BUFF - ) - - - self.play( - FadeIn(poisson, lag_ratio = 0.5), - RemovePiCreatureBubble(self.students[0]), - self.teacher.change, "raise_right_hand", - self.binomial.scale, 0.5, - self.binomial.to_corner, UP+LEFT, - ) - self.play(Write(poisson.title, run_time = 1)) - self.play(FadeIn(gaussian, lag_ratio = 0.5)) - self.play(Write(gaussian.title, run_time = 1)) - self.wait(2) - self.change_student_modes( - *["sassy"]*3, - added_anims = [self.teacher.change, "plain"] - ) - self.wait(2) - - self.poisson = poisson - self.gaussian = gaussian - - def emphasize_underweighted_tails(self): - poisson_arrows = VGroup() - arrow_template = Arrow( - ORIGIN, UP, color = GREEN, - tip_length = 0.15 - ) - for bar in self.poisson.bars[-4:]: - arrow = arrow_template.copy() - arrow.next_to(bar, UP, SMALL_BUFF) - poisson_arrows.add(arrow) - - gaussian_arrows = VGroup() - for prop in 0.2, 0.8: - point = self.gaussian[0][0].point_from_proportion(prop) - arrow = arrow_template.copy() - arrow.next_to(point, UP, SMALL_BUFF) - gaussian_arrows.add(arrow) - - for arrows in poisson_arrows, gaussian_arrows: - self.play( - ShowCreation( - arrows, - lag_ratio = 0.5, - run_time = 2 - ), - *[ - ApplyMethod(pi.change, "thinking", arrows) - for pi in self.pi_creatures - ] - ) - self.wait() - self.wait(2) - - #### - - def get_binomial(self): - k_range = list(range(11)) - dists = [ - get_binomial_distribution(10, p) - for p in (0.2, 0.8, 0.5) - ] - values_list = [ - list(map(dist, k_range)) - for dist in dists - ] - chart = BarChart( - values = values_list[-1], - bar_names = k_range - ) - chart.set_height(self.chart_height) - chart.values_list = values_list - return chart - - def get_poisson(self): - k_range = list(range(11)) - L = 2 - values = [ - np.exp(-L) * (L**k) / (scipy.special.gamma(k+1)) - for k in k_range - ] - chart = BarChart( - values = values, - bar_names = k_range, - bar_colors = [RED, YELLOW] - ) - chart.set_height(self.chart_height) - title = TextMobject( - "Poisson distribution \\\\", - "$e^{-\\lambda}\\frac{\\lambda^k}{k!}$" - ) - title.scale(0.75) - title.move_to(chart, UP) - title.shift(MED_SMALL_BUFF*RIGHT) - title[0].shift(SMALL_BUFF*UP) - chart.title = title - - return chart - - def get_gaussian(self): - axes = VGroup(self.binomial.x_axis, self.binomial.y_axis).copy() - graph = FunctionGraph( - lambda x : 5*np.exp(-x**2), - mark_paths_closed = True, - fill_color = BLUE_E, - fill_opacity = 1, - stroke_color = BLUE, - ) - graph.set_width(axes.get_width()) - graph.move_to(axes[0], DOWN) - - title = TextMobject( - "Gaussian distribution \\\\ ", - "$\\frac{1}{\\sqrt{2\\pi \\sigma^2}} e^{-\\frac{(x-\\mu)^2}{2\\sigma^2}}$" - ) - title.scale(0.75) - title.move_to(axes, UP) - title.shift(MED_SMALL_BUFF*RIGHT) - title[0].shift(SMALL_BUFF*UP) - result = VGroup(axes, graph) - result.title = title - - return result - -class IndependencePatreonThanks(PatreonThanks): - CONFIG = { - "specific_patrons" : [ - "Ali Yahya", - "Desmos", - "Burt Humburg", - "CrypticSwarm", - "Juan Benet", - "Mayank M. Mehrotra", - "Lukas Biewald", - "Samantha D. Suplee", - "James Park", - "Erik Sundell", - "Yana Chernobilsky", - "Kaustuv DeBiswas", - "Kathryn Schmiedicke", - "Karan Bhargava", - "Yu Jun", - "Dave Nicponski", - "Damion Kistler", - "Markus Persson", - "Yoni Nazarathy", - "Corey Ogburn", - "Ed Kellett", - "Joseph John Cox", - "Dan Buchoff", - "Luc Ritchie", - "Tianyu Ge", - "Ted Suzman", - "Amir Fayazi", - "Linh Tran", - "Andrew Busey", - "Michael McGuffin", - "John Haley", - "Mourits de Beer", - "Ankalagon", - "Eric Lavault", - "Tomohiro Furusawa", - "Boris Veselinovich", - "Julian Pulgarin", - "Jeff Linse", - "Cooper Jones", - "Ryan Dahl", - "Mark Govea", - "Robert Teed", - "Jason Hise", - "Meshal Alshammari", - "Bernd Sing", - "Nils Schneider", - "James Thornton", - "Mustafa Mahdi", - "Mathew Bramson", - "Jerry Ling", - "Vecht", - "Shimin Kuang", - "Rish Kundalia", - "Achille Brighton", - "Ripta Pasay", - ], - } - -class Thumbnail(DangerInProbability): - def construct(self): - n, p = 15, 0.5 - dist = get_binomial_distribution(n, p) - values = np.array(list(map(dist, list(range(n+1))))) - values *= 2 - chart = BarChart( - values = values, - label_y_axis = False, - width = FRAME_WIDTH - 3, - height = 1.5*FRAME_Y_RADIUS - ) - chart.to_edge(DOWN) - self.add(chart) - - - warning = self.get_warning_sign() - warning.set_height(2) - warning.to_edge(UP) - self.add(warning) - - - words = TextMobject("Independence") - words.scale(2.5) - words.next_to(warning, DOWN) - self.add(words) - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/pascal.py b/from_3b1b/on_hold/eop/pascal.py deleted file mode 100644 index 085f2a9d..00000000 --- a/from_3b1b/on_hold/eop/pascal.py +++ /dev/null @@ -1,347 +0,0 @@ - -from manimlib.imports import * -from once_useful_constructs.combinatorics import * - -nb_levels = 5 - -dev_x_step = 2 -dev_y_step = 5 - -GRADE_COLOR_1 = RED -GRADE_COLOR_2 = BLUE - - - -def graded_square(n,k): - return Square( - side_length = 1, - fill_color = graded_color(n,k), - fill_opacity = 1, - stroke_width = 1 - ) - -def graded_binomial(n,k): - return Integer( - choose(n,k), - color = graded_color(n,k) - ) - -def split_square(n,k): - width = 1 - height = 1 - - proportion = float(choose(n,k)) / 2**n - - lower_height = proportion * height - upper_height = (1 - proportion) * height - lower_rect = Rectangle( - width = width, - height = lower_height, - fill_color = RED, - fill_opacity = 1.0, - stroke_color = WHITE, - stroke_width = 3 - ) - upper_rect = Rectangle( - width = width, - height = upper_height, - fill_color = BLUE, - fill_opacity = 1.0, - stroke_color = WHITE, - stroke_width = 3 - ) - upper_rect.next_to(lower_rect,UP,buff = 0) - square = VGroup(lower_rect, upper_rect).move_to(ORIGIN) - return square - - -class BuildNewPascalRow(Transform): - - def __init__(self,mobject, duplicate_row = None, **kwargs): - if mobject.__class__ != GeneralizedPascalsTriangle and mobject.__class__ != PascalsTriangle: - raise("Transform BuildNewPascalRow only works on members of (Generalized)PascalsTriangle!") - - n = mobject.nrows - 1 - lowest_row_copy1 = mobject.get_lowest_row() - lowest_row_copy2 = duplicate_row - - start_mob = VGroup(lowest_row_copy1, lowest_row_copy2) - - new_pt = mobject.copy() - new_pt.nrows += 1 - new_pt.init_points() - # align with original (copy got centered on screen) - c1 = new_pt.coords_to_mobs[0][0].get_center() - c2 = mobject.coords_to_mobs[0][0].get_center() - print(c1, c2) - v = c2 - c1 - new_pt.shift(v) - - new_row_left_copy = VGroup(*[ - new_pt.coords_to_mobs[n+1][k] - for k in range(0,n+1) - ]) - - new_row_right_copy = VGroup(*[ - new_pt.coords_to_mobs[n+1][k] - for k in range(1,n+2) - ]).copy() - - target_mob = VGroup(new_row_left_copy, new_row_right_copy) - - Transform.__init__(self, start_mob, target_mob, **kwargs) - - - - - -class SimplePascal(Scene): - - def build_new_pascal_row(self,old_pt): - - lowest_row_copy = old_pt.get_lowest_row().copy() - self.add(lowest_row_copy) - - n = old_pt.nrows - 1 - lowest_row_copy1 = old_pt.get_lowest_row() - lowest_row_copy2 = lowest_row_copy1.copy() - - - start_mob = VGroup(lowest_row_copy1, lowest_row_copy2) - self.add(start_mob) - - new_pt = old_pt.copy() - cell_height = old_pt.height / old_pt.nrows - cell_width = old_pt.width / old_pt.nrows - new_pt.nrows += 1 - new_pt.height = new_pt.nrows * cell_height - new_pt.width = new_pt.nrows * cell_width - - new_pt.init_points() - # align with original (copy got centered on screen) - c1 = new_pt.coords_to_mobs[0][0].get_center() - c2 = old_pt.coords_to_mobs[0][0].get_center() - v = c2 - c1 - new_pt.shift(v) - - new_row_left_copy = VGroup(*[ - new_pt.coords_to_mobs[n+1][k] - for k in range(0,n+1) - ]) - - new_row_right_copy = VGroup(*[ - new_pt.coords_to_mobs[n+1][k] - for k in range(1,n+2) - ]).copy() - - target_mob = VGroup(new_row_left_copy, new_row_right_copy) - self.play(Transform(start_mob, target_mob)) - - return new_pt - - - - def construct(self): - - cell_height = 1 - cell_width = 1 - nrows = 1 - pt = GeneralizedPascalsTriangle( - nrows = nrows, - height = nrows * cell_height, - width = nrows * cell_width, - submob_class = graded_square, - portion_to_fill = 0.9 - ) - pt.shift(3 * UP) - self.add(pt) - lowest_row_copy = pt.get_lowest_row().copy() - self.add(lowest_row_copy) - #self.play(BuildNewPascalRow(pt, duplicate_row = lowest_row_copy)) - for i in range(7): - pt = self.build_new_pascal_row(pt) - - - - - -class PascalNetScene(Scene): - - def construct(self): - - unit_width = 0.25 - top_height = 4.0 - level_height = 2.0 * top_height / nb_levels - - start_points = np.array([top_height * UP]) - - dev_start = start_points[0] - - j = 0 - - for n in range(nb_levels): - - half_width = 0.5 * (n + 0.5) * unit_width - - stop_points_left = start_points.copy() - stop_points_left[:,0] -= 0.5 * unit_width - stop_points_left[:,1] -= level_height - - stop_points_right = start_points.copy() - stop_points_right[:,0] += 0.5 * unit_width - stop_points_right[:,1] -= level_height - - for (p,q) in zip(start_points,stop_points_left): - alpha = np.abs((p[0]+q[0])/2) / half_width - color = rainbow_color(alpha) - line = Line(p,q, stroke_color = color) - self.add(line) - - for (i,(p,q)) in enumerate(zip(start_points,stop_points_right)): - alpha = np.abs((p[0]+q[0])/2) / half_width - color = rainbow_color(alpha) - line = Line(p,q, stroke_color = color) - self.add(line) - - if (n + 1) % dev_y_step == 0 and n != 1: - j += dev_x_step - dev_stop = stop_points_left[j] - line = Line(dev_start,dev_stop,stroke_color = WHITE) - self.add(line) - dot = Dot(dev_stop, fill_color = WHITE) - self.add_foreground_mobject(dot) - dev_start = dev_stop - - start_points = np.append(stop_points_left,[stop_points_right[-1]], axis = 0) - - - self.wait() - - - -class RescaledPascalNetScene(Scene): - - def construct(self): - - half_width = 3.0 - top_height = 4.0 - level_height = 2.0 * top_height / nb_levels - - start_points = np.array([top_height * UP]) - left_edge = top_height * UP + half_width * LEFT - right_edge = top_height * UP + half_width * RIGHT - - dev_start = start_points[0] - - j = 0 - - for n in range(nb_levels): - - if n == 0: - start_points_left_shift = np.array([left_edge]) - else: - start_points_left_shift = start_points[:-1] - start_points_left_shift = np.insert(start_points_left_shift,0,left_edge, axis = 0) - stop_points_left = 0.5 * (start_points + start_points_left_shift) - stop_points_left += level_height * DOWN - - - if n == 0: - start_points_right_shift = np.array([right_edge]) - else: - start_points_right_shift = start_points[1:] - start_points_right_shift = np.append(start_points_right_shift,np.array([right_edge]), axis = 0) - stop_points_right = 0.5 * (start_points + start_points_right_shift) - stop_points_right += level_height * DOWN - - - for (i,(p,q)) in enumerate(zip(start_points,stop_points_left)): - - color = LIGHT_GRAY - - if n % 2 == 0 and i <= n/2: - m = n/2 + 0.25 - jj = i - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) - - elif n % 2 == 0 and i > n/2: - m = n/2 + 0.25 - jj = n - i + 0.5 - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) - - elif n % 2 == 1 and i <= n/2: - m = n/2 + 0.75 - jj = i - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) - - elif n % 2 == 1 and i > n/2: - m = n/2 + 0.75 - jj = n - i + 0.5 - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) - - line = Line(p,q, stroke_color = color) - self.add(line) - - for (i,(p,q)) in enumerate(zip(start_points,stop_points_right)): - - color = LIGHT_GRAY - - if n % 2 == 0 and i < n/2: - m = n/2 + 0.25 - jj = i + 0.5 - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) - - elif n % 2 == 0 and i >= n/2: - m = n/2 + 0.25 - jj = n - i - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) - - elif n % 2 == 1 and i <= n/2: - m = n/2 + 0.75 - jj = i + 0.5 - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) - - elif n % 2 == 1 and i > n/2: - m = n/2 + 0.75 - jj = n - i - alpha = 1 - float(jj)/m - color = rainbow_color(alpha) - - - line = Line(p,q, stroke_color = color) - self.add(line) - - if (n + 1) % dev_y_step == 0 and n != 1: - j += dev_x_step - dev_stop = stop_points_left[j] - line = Line(dev_start,dev_stop,stroke_color = WHITE) - self.add(line) - dot = Dot(dev_stop, fill_color = WHITE) - self.add_foreground_mobject(dot) - dev_start = dev_stop - - - - start_points = np.append(stop_points_left,[stop_points_right[-1]], axis = 0) - - left_edge += level_height * DOWN - right_edge += level_height * DOWN - - - self.wait() - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/reusable_imports.py b/from_3b1b/on_hold/eop/reusable_imports.py deleted file mode 100644 index 7dcafd43..00000000 --- a/from_3b1b/on_hold/eop/reusable_imports.py +++ /dev/null @@ -1,11 +0,0 @@ -from active_projects.eop.reusables.binary_option import * -from active_projects.eop.reusables.brick_row import * -from active_projects.eop.reusables.coin_flip_tree import * -from active_projects.eop.reusables.coin_flipping_pi_creature import * -from active_projects.eop.reusables.coin_stacks import * -from active_projects.eop.reusables.dice import * -from active_projects.eop.reusables.eop_constants import * -from active_projects.eop.reusables.eop_helpers import * -from active_projects.eop.reusables.histograms import * -from active_projects.eop.reusables.sick_pi_creature import * -from active_projects.eop.reusables.upright_coins import * diff --git a/from_3b1b/on_hold/eop/reusables/binary_option.py b/from_3b1b/on_hold/eop/reusables/binary_option.py deleted file mode 100644 index b9911c93..00000000 --- a/from_3b1b/on_hold/eop/reusables/binary_option.py +++ /dev/null @@ -1,15 +0,0 @@ -from mobject.types.vectorized_mobject import * -from mobject.svg.tex_mobject import * - -class BinaryOption(VMobject): - CONFIG = { - "text_scale" : 0.5 - } - - def __init__(self, mob1, mob2, **kwargs): - - VMobject.__init__(self, **kwargs) - text = TextMobject("or").scale(self.text_scale) - mob1.next_to(text, LEFT) - mob2.next_to(text, RIGHT) - self.add(mob1, text, mob2) diff --git a/from_3b1b/on_hold/eop/reusables/brick_row.py b/from_3b1b/on_hold/eop/reusables/brick_row.py deleted file mode 100644 index 071348b1..00000000 --- a/from_3b1b/on_hold/eop/reusables/brick_row.py +++ /dev/null @@ -1,208 +0,0 @@ -from manimlib.imports import * -from active_projects.eop.reusables.eop_helpers import * -from active_projects.eop.reusables.eop_constants import * -from active_projects.eop.reusables.upright_coins import * - -class BrickRow(VMobject): - - CONFIG = { - "left_color" : COLOR_HEADS, - "right_color" : COLOR_TAILS, - "height" : 1.0, - "width" : 8.0, - "outcome_shrinkage_factor_x" : 0.95, - "outcome_shrinkage_factor_y" : 0.94 - } - - def __init__(self, n, **kwargs): - self.subdiv_level = n - self.coloring_level = n - VMobject.__init__(self, **kwargs) - - - def init_points(self): - - self.submobjects = [] - self.rects = self.get_rects_for_level(self.coloring_level) - self.add(self.rects) - self.subdivs = self.get_subdivs_for_level(self.subdiv_level) - self.add(self.subdivs) - - self.border = SurroundingRectangle(self, - buff = 0, color = WHITE) - self.add(self.border) - - - - def get_rects_for_level(self,r): - rects = VGroup() - for k in range(r + 1): - proportion = float(choose(r,k)) / 2**r - new_rect = Rectangle( - width = proportion * self.width, - height = self.height, - fill_color = graded_color(r,k), - fill_opacity = 1, - stroke_width = 0 - ) - if len(rects.submobjects) > 0: - new_rect.next_to(rects,RIGHT,buff = 0) - else: - new_rect.next_to(self.get_center() + 0.5 * self.width * LEFT, RIGHT, buff = 0) - rects.add(new_rect) - return rects - - - def get_subdivs_for_level(self,r): - subdivs = VGroup() - x = - 0.5 * self.width - for k in range(0, r): - proportion = float(choose(r,k)) / 2**r - x += proportion * self.width - subdiv = Line( - x * RIGHT + 0.5 * self.height * UP, - x * RIGHT + 0.5 * self.height * DOWN, - ) - subdivs.add(subdiv) - subdivs.move_to(self.get_center()) - return subdivs - - - def get_sequence_subdivs_for_level(self,r): - subdivs = VGroup() - x = - 0.5 * self.width - dx = 1.0 / 2**r - for k in range(1, 2 ** r): - proportion = dx - x += proportion * self.width - subdiv = DashedLine( - x * RIGHT + 0.5 * self.height * UP, - x * RIGHT + 0.5 * self.height * DOWN, - ) - subdivs.add(subdiv) - subdivs.move_to(self.get_center()) - return subdivs - - - def get_outcome_centers_for_level(self,r): - - dpos = float(self.width) / (2 ** r) * RIGHT - pos = 0.5 * self.width * LEFT + 0.5 * dpos - centers = [] - for k in range(0, 2 ** r): - centers.append(self.get_center() + pos + k * dpos) - - return centers - - def get_outcome_rects_for_level(self, r, inset = False, with_labels = False): - - centers = self.get_outcome_centers_for_level(r) - if inset == True: - outcome_width = self.outcome_shrinkage_factor_x * float(self.width) / (2 ** r) - outcome_height = self.outcome_shrinkage_factor_y * self.height - else: - outcome_width = float(self.width) / (2 ** r) - outcome_height = self.height - - corner_radius = 0.1 # max(0.1, 0.3 * min(outcome_width, outcome_height)) - # this scales down the corner radius for very narrow rects - rect = RoundedRectangle( - width = outcome_width, - height = outcome_height, - corner_radius = corner_radius, - fill_color = OUTCOME_COLOR, - fill_opacity = OUTCOME_OPACITY, - stroke_width = 0 - ) - rects = VGroup() - for center in centers: - rects.add(rect.copy().move_to(center)) - - rects.move_to(self.get_center()) - - - if with_labels == False: - return rects - - # else - sequences = self.get_coin_sequences_for_level(r) - labels = VGroup() - for (seq, rect) in zip(sequences, rects): - coin_seq = CoinSequence(seq, direction = DOWN) - coin_seq.shift(rect.get_center() - coin_seq.get_center()) - # not simply move_to bc coin_seq is not centered - rect.add(coin_seq) - rect.label = coin_seq - - return rects - - def get_coin_sequences_for_level(self,r): - # array of arrays of characters - if r < 0 or int(r) != r: - raise Exception("Level must be a positive integer") - if r == 0: - return [] - if r == 1: - return [["H"], ["T"]] - - previous_seq_array = self.get_coin_sequences_for_level(r - 1) - subdiv_lengths = [choose(r - 1, k) for k in range(r)] - - seq_array = [] - index = 0 - for length in subdiv_lengths: - - for seq in previous_seq_array[index:index + length]: - seq_copy = copy.copy(seq) - seq_copy.append("H") - seq_array.append(seq_copy) - - for seq in previous_seq_array[index:index + length]: - seq_copy = copy.copy(seq) - seq_copy.append("T") - seq_array.append(seq_copy) - index += length - - return seq_array - - - def get_outcome_width_for_level(self,r): - return self.width / (2**r) - - def get_rect_widths_for_level(self, r): - ret_arr = [] - for k in range(0, r): - proportion = float(choose(r,k)) / 2**r - ret_arr.append(proportion * self.width) - return ret_arr - - - - -class SplitRectsInBrickWall(AnimationGroup): - - def __init__(self, mobject, **kwargs): - - #print mobject.height, mobject.get_height() - r = self.subdiv_level = mobject.subdiv_level + 1 - - subdivs = VGroup() - x = -0.5 * mobject.get_width() - - anims = [] - for k in range(0, r): - proportion = float(choose(r,k)) / 2**r - x += proportion * mobject.get_width() - subdiv = DashedLine( - mobject.get_top() + x * RIGHT, - mobject.get_bottom() + x * RIGHT, - dash_length = 0.05 - ) - subdivs.add(subdiv) - anims.append(ShowCreation(subdiv)) - - mobject.add(subdivs) - AnimationGroup.__init__(self, *anims, **kwargs) - - - diff --git a/from_3b1b/on_hold/eop/reusables/coin_flip_tree.py b/from_3b1b/on_hold/eop/reusables/coin_flip_tree.py deleted file mode 100644 index 7a5e79ed..00000000 --- a/from_3b1b/on_hold/eop/reusables/coin_flip_tree.py +++ /dev/null @@ -1,76 +0,0 @@ -from mobject.geometry import * -from active_projects.eop.reusables.eop_helpers import * -from active_projects.eop.reusables.eop_constants import * - -class CoinFlipTree(VGroup): - CONFIG = { - "total_width": 12, - "level_height": 0.8, - "nb_levels": 4, - "sort_until_level": 3 - } - - def __init__(self, **kwargs): - - VGroup.__init__(self, **kwargs) - - self.rows = [] - for n in range(self.nb_levels + 1): - if n <= self.sort_until_level: - self.create_row(n, sorted = True) - else: - self.create_row(n, sorted = False) - - - for row in self.rows: - for leaf in row: - dot = Dot() - dot.move_to(leaf[0]) - line = Line(leaf[2], leaf[0]) - if leaf[2][0] > leaf[0][0]: - line_color = COLOR_HEADS - else: - line_color = COLOR_TAILS - line.set_stroke(color = line_color) - group = VGroup() - group.add(dot) - group.add_to_back(line) - self.add(group) - - - - - def create_row(self, level, sorted = True): - - if level == 0: - new_row = [[ORIGIN,0,ORIGIN]] # is its own parent - self.rows.append(new_row) - return - - previous_row = self.rows[level - 1] - new_row = [] - dx = float(self.total_width) / (2 ** level) - x = - 0.5 * self.total_width + 0.5 * dx - y = - self.level_height * level - for root in previous_row: - root_point = root[0] - root_tally = root[1] - for i in range(2): # 0 = heads = left, 1 = tails = right - leaf = x * RIGHT + y * UP - new_row.append([leaf, root_tally + i, root_point]) # leaf and its parent - x += dx - - if sorted: - # sort the new_row by its tallies - sorted_row = [] - x = - 0.5 * self.total_width + 0.5 * dx - for i in range(level + 1): - for leaf in new_row: - if leaf[1] == i: - sorted_leaf = leaf - sorted_leaf[0][0] = x - x += dx - sorted_row.append(leaf) - self.rows.append(sorted_row) - else: - self.rows.append(new_row) diff --git a/from_3b1b/on_hold/eop/reusables/coin_flipping_pi_creature.py b/from_3b1b/on_hold/eop/reusables/coin_flipping_pi_creature.py deleted file mode 100644 index a46f4823..00000000 --- a/from_3b1b/on_hold/eop/reusables/coin_flipping_pi_creature.py +++ /dev/null @@ -1,112 +0,0 @@ -from mobject.types.vectorized_mobject import * -from animation.animation import * -from animation.composition import * -from mobject.geometry import Rectangle, Line -from utils.rate_functions import * -from for_3b1b_videos.pi_creature_scene import * -from active_projects.eop.reusables.eop_helpers import * -from active_projects.eop.reusables.eop_constants import * -from active_projects.eop.reusables.coin_flipping_pi_creature import * - - -class PiCreatureCoin(VMobject): - CONFIG = { - "diameter": 0.8, - "thickness": 0.2, - "nb_ridges" : 7, - "stroke_color": YELLOW, - "stroke_width": 3, - "fill_color": YELLOW, - "fill_opacity": 0.7, - } - - def init_points(self): - outer_rect = Rectangle( - width = self.diameter, - height = self.thickness, - fill_color = self.fill_color, - fill_opacity = self.fill_opacity, - stroke_color = self.stroke_color, - stroke_width = 0, #self.stroke_width - ) - self.add(outer_rect) - PI = TAU/2 - ridge_angles = np.arange(PI/self.nb_ridges,PI,PI/self.nb_ridges) - ridge_positions = 0.5 * self.diameter * np.array([ - np.cos(theta) for theta in ridge_angles - ]) - ridge_color = interpolate_color(BLACK,self.stroke_color,0.5) - for x in ridge_positions: - ridge = Line( - x * RIGHT + 0.5 * self.thickness * DOWN, - x * RIGHT + 0.5 * self.thickness * UP, - stroke_color = ridge_color, - stroke_width = self.stroke_width - ) - self.add(ridge) - -class CoinFlippingPiCreature(PiCreature): - CONFIG = { - "flip_height": 3 - } - - def __init__(self, mode = "coin_flip_1", **kwargs): - - coin = PiCreatureCoin() - PiCreature.__init__(self, mode = mode, **kwargs) - self.coin = coin - self.add(coin) - right_arm = self.get_arm_copies()[1] - coin.rotate(-TAU/24) - coin.next_to(right_arm, RIGHT+UP, buff = 0) - coin.shift(0.1 * self.get_width() * LEFT) - coin.shift(0.2 * DOWN) - - def flip_coin_up(self): - self.change("coin_flip_2") - - - -class FlipUpAndDown(Animation): - CONFIG = { - "vector" : UP, - "height" : 3, - "nb_turns" : 1 - } - - def update(self,t): - self.mobject.shift(self.height * 4 * t * (1 - t) * self.vector) - self.mobject.rotate(t * self.nb_turns * TAU) - -class FlipCoin(AnimationGroup): - CONFIG = { - "coin_rate_func" : there_and_back, - "pi_rate_func" : lambda t : there_and_back_with_pause(t, 1./4) - } - def __init__(self, pi_creature, **kwargs): - digest_config(self, kwargs) - pi_creature_motion = ApplyMethod( - pi_creature.flip_coin_up, - rate_func = self.pi_rate_func, - **kwargs - ) - coin_motion = Succession( - EmptyAnimation(run_time = 1.0), - FlipUpAndDown( - pi_creature.coin, - vector = UP, - nb_turns = 5, - height = pi_creature.flip_height * pi_creature.get_height(), - rate_func = self.coin_rate_func, - **kwargs - ) - ) - AnimationGroup.__init__(self,pi_creature_motion, coin_motion) - -class CoinFlippingPiCreatureScene(Scene): - - def construct(self): - - randy = CoinFlippingPiCreature(color = MAROON_E) - self.add(randy) - self.play(FlipCoin(randy, run_time = 3)) diff --git a/from_3b1b/on_hold/eop/reusables/coin_stacks.py b/from_3b1b/on_hold/eop/reusables/coin_stacks.py deleted file mode 100644 index 07e8bf4c..00000000 --- a/from_3b1b/on_hold/eop/reusables/coin_stacks.py +++ /dev/null @@ -1,111 +0,0 @@ -from mobject.geometry import * -from mobject.svg.tex_mobject import * -from active_projects.eop.reusables.upright_coins import * - - -class CoinStack(VGroup): - CONFIG = { - "coin_thickness": COIN_THICKNESS, - "size": 5, - "face": FlatCoin, - } - - def init_points(self): - for n in range(self.size): - coin = self.face(thickness = self.coin_thickness) - coin.shift(n * self.coin_thickness * UP) - self.add(coin) - if self.size == 0: - point = VectorizedPoint() - self.add(point) - -class HeadsStack(CoinStack): - CONFIG = { - "face": FlatHeads - } - -class TailsStack(CoinStack): - CONFIG = { - "face": FlatTails - } - - - -class DecimalTally(TextMobject): - - def __init__(self, heads, tails, **kwargs): - - TextMobject.__init__(self, str(heads), "\\textemdash\,", str(tails), **kwargs) - self[0].set_color(COLOR_HEADS) - self[-1].set_color(COLOR_TAILS) - # this only works for single-digit tallies - - - - -class TallyStack(VGroup): - CONFIG = { - "coin_thickness": COIN_THICKNESS, - "show_decimals": True - } - - def __init__(self, h, t, anchor = ORIGIN, **kwargs): - self.nb_heads = h - self.nb_tails = t - self.anchor = anchor - VGroup.__init__(self,**kwargs) - - def init_points(self): - stack1 = HeadsStack(size = self.nb_heads, coin_thickness = self.coin_thickness) - stack2 = TailsStack(size = self.nb_tails, coin_thickness = self.coin_thickness) - stack1.next_to(self.anchor, LEFT, buff = 0.5 * SMALL_BUFF) - stack2.next_to(self.anchor, RIGHT, buff = 0.5 * SMALL_BUFF) - stack1.align_to(self.anchor, DOWN) - stack2.align_to(self.anchor, DOWN) - self.heads_stack = stack1 - self.tails_stack = stack2 - self.add(stack1, stack2) - self.background_rect = background_rect = RoundedRectangle( - width = TALLY_BACKGROUND_WIDTH, - height = TALLY_BACKGROUND_WIDTH, - corner_radius = 0.1, - fill_color = TALLY_BACKGROUND_COLOR, - fill_opacity = 1.0, - stroke_width = 3 - ).align_to(self.anchor, DOWN).shift(0.1 * DOWN) - self.add_to_back(background_rect) - - self.decimal_tally = DecimalTally(self.nb_heads, self.nb_tails) - self.position_decimal_tally(self.decimal_tally) - if self.show_decimals: - self.add(self.decimal_tally) - - def position_decimal_tally(self, decimal_tally): - decimal_tally.match_width(self.background_rect) - decimal_tally.scale(0.6) - decimal_tally.next_to(self.background_rect.get_top(), DOWN, buff = 0.15) - return decimal_tally - - - def move_anchor_to(self, new_anchor): - for submob in self.submobjects: - submob.shift(new_anchor - self.anchor) - - self.anchor = new_anchor - self.position_decimal_tally(self.decimal_tally) - - return self - - - - - - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/reusables/dice.py b/from_3b1b/on_hold/eop/reusables/dice.py deleted file mode 100644 index 39195fad..00000000 --- a/from_3b1b/on_hold/eop/reusables/dice.py +++ /dev/null @@ -1,76 +0,0 @@ -from mobject.svg.svg_mobject import * -from mobject.geometry import * -from mobject.numbers import * - -class DieFace(SVGMobject): - - def __init__(self, value, **kwargs): - - self.value = value - self.file_name = "Dice-" + str(value) - self.ensure_valid_file() - SVGMobject.__init__(self, file_name = self.file_name) - -class RowOfDice(VGroup): - CONFIG = { - "values" : list(range(1,7)), - "direction": RIGHT, - } - - def init_points(self): - for value in self.values: - new_die = DieFace(value) - new_die.submobjects[0].set_fill(opacity = 0) - new_die.submobjects[0].set_stroke(width = 7) - new_die.next_to(self, self.direction) - self.add(new_die) - self.move_to(ORIGIN) - - -class TwoDiceTable(VMobject): - CONFIG = { - "cell_size" : 1, - "label_scale": 0.7 - } - - def __init__(self, **kwargs): - - VMobject.__init__(self, **kwargs) - colors = color_gradient([RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE], 13) - - self.cells = VGroup() - self.labels = VGroup() - for i in range(1,7): - for j in range(1,7): - cell = Square(side_length = self.cell_size) - cell.set_fill(color = colors[i+j], opacity = 0.8) - cell.move_to(i*self.cell_size*DOWN + j*self.cell_size*RIGHT) - self.cells.add(cell) - label = Integer(i+j).scale(self.label_scale) - label.move_to(cell) - self.labels.add(label) - - - self.add(self.cells, self.labels) - row1 = RowOfDice().match_width(self) - row2 = row1.copy().rotate(-TAU/4) - row1.next_to(self, UP) - row2.next_to(self, LEFT) - self.rows = VGroup(row1, row2) - self.add(self.rows) - self.center() - - - - - - - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/reusables/eop_constants.py b/from_3b1b/on_hold/eop/reusables/eop_constants.py deleted file mode 100644 index 5e5eb762..00000000 --- a/from_3b1b/on_hold/eop/reusables/eop_constants.py +++ /dev/null @@ -1,24 +0,0 @@ - -from constants import * - -COIN_RADIUS = 0.18 -COIN_THICKNESS = 0.4 * COIN_RADIUS -COIN_FORESHORTENING = 0.5 -COIN_NB_RIDGES = 20 -COIN_STROKE_WIDTH = 2 - -COIN_SEQUENCE_SPACING = 0.1 - -GRADE_COLOR_1 = COLOR_HEADS = RED_E -GRADE_COLOR_2 = COLOR_TAILS = BLUE_C - -COLOR_HEADS_COIN = RED -COLOR_TAILS_COIN = BLUE_E - -TALLY_BACKGROUND_WIDTH = 1.0 -TALLY_BACKGROUND_COLOR = BLACK - -SICKLY_GREEN = "#9BBD37" - -OUTCOME_COLOR = WHITE -OUTCOME_OPACITY = 0.5 \ No newline at end of file diff --git a/from_3b1b/on_hold/eop/reusables/eop_helpers.py b/from_3b1b/on_hold/eop/reusables/eop_helpers.py deleted file mode 100644 index 7ea58377..00000000 --- a/from_3b1b/on_hold/eop/reusables/eop_helpers.py +++ /dev/null @@ -1,36 +0,0 @@ -from utils.color import * -from active_projects.eop.reusables.eop_constants import * - -def binary(i): - # returns an array of 0s and 1s - if i == 0: - return [] - j = i - binary_array = [] - while j > 0: - jj = j/2 - if jj > 0: - binary_array.append(j % 2) - else: - binary_array.append(1) - j = jj - return binary_array[::-1] - -def nb_of_ones(i): - return binary(i).count(1) - - -def rainbow_color(alpha): - nb_colors = 100 - rainbow = color_gradient([RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE], nb_colors) - rainbow = np.append(rainbow,PURPLE) - index = int(alpha * nb_colors) - return rainbow[index] - -def graded_color(n,k): - if n != 0: - alpha = float(k)/n - else: - alpha = 0.5 - color = interpolate_color(GRADE_COLOR_1, GRADE_COLOR_2, alpha) - return color diff --git a/from_3b1b/on_hold/eop/reusables/histograms.py b/from_3b1b/on_hold/eop/reusables/histograms.py deleted file mode 100644 index 63fb8f85..00000000 --- a/from_3b1b/on_hold/eop/reusables/histograms.py +++ /dev/null @@ -1,348 +0,0 @@ -from manimlib.imports import * -from random import * - -def text_range(start,stop,step): # a range as a list of strings - numbers = np.arange(start,stop,step) - labels = [] - for x in numbers: - labels.append(str(x)) - return labels - - -class Histogram(VMobject): - - CONFIG = { - "start_color" : RED, - "end_color" : BLUE, - "x_scale" : 1.0, - "y_scale" : 1.0, - "x_labels" : "auto", # widths, mids, auto, none, [...] - "y_labels" : "auto", # auto, none, [...] - "y_label_position" : "top", # "center" - "x_min" : 0, - "bar_stroke_width" : 5, - "outline_stroke_width" : 0, - "stroke_color" : WHITE - } - - def __init__(self, x_values, y_values, mode = "widths", **kwargs): - # mode = "widths" : x_values means the widths of the bars - # mode = "posts" : x_values means the delimiters btw the bars - - digest_config(self, kwargs) - - if mode == "widths" and len(x_values) != len(y_values): - raise Exception("Array lengths do not match up!") - elif mode == "posts" and len(x_values) != len(y_values) + 1: - raise Exception("Array lengths do not match up!") - - - self.y_values = y_values - self.x_values = x_values - self.mode = mode - self.process_values() - - VMobject.__init__(self, **kwargs) - - - def process_values(self): - - # preliminaries - self.y_values = np.array(self.y_values) - - if self.mode == "widths": - self.widths = self.x_values - self.posts = np.cumsum(self.widths) - self.posts = np.insert(self.posts, 0, 0) - self.posts += self.x_min - self.x_max = self.posts[-1] - elif self.mode == "posts": - self.posts = self.x_values - self.widths = self.x_values[1:] - self.x_values[:-1] - self.x_min = self.posts[0] - self.x_max = self.posts[-1] - else: - raise Exception("Invalid mode or no mode specified!") - - self.x_mids = 0.5 * (self.posts[:-1] + self.posts[1:]) - - self.widths_scaled = self.x_scale * self.widths - self.posts_scaled = self.x_scale * self.posts - self.x_min_scaled = self.x_scale * self.x_min - self.x_max_scaled = self.x_scale * self.x_max - - self.y_values_scaled = self.y_scale * self.y_values - - - - def init_points(self): - - self.process_values() - for submob in self.submobjects: - self.remove(submob) - - def empty_string_array(n): - arr = [] - for i in range(n): - arr.append("") - return arr - - def num_arr_to_string_arr(arr): # converts number array to string array - ret_arr = [] - for x in arr: - if x == np.floor(x): - new_x = int(np.floor(x)) - else: - new_x = x - ret_arr.append(str(new_x)) - return ret_arr - - previous_bar = ORIGIN - self.bars = VGroup() - self.x_labels_group = VGroup() - self.y_labels_group = VGroup() - outline_points = [] - - if self.x_labels == "widths": - self.x_labels = num_arr_to_string_arr(self.widths) - elif self.x_labels == "mids": - self.x_labels = num_arr_to_string_arr(self.x_mids) - elif self.x_labels == "auto": - self.x_labels = num_arr_to_string_arr(self.x_mids) - elif self.x_labels == "none": - self.x_labels = empty_string_array(len(self.widths)) - - if self.y_labels == "auto": - self.y_labels = num_arr_to_string_arr(self.y_values) - elif self.y_labels == "none": - self.y_labels = empty_string_array(len(self.y_values)) - - - - - for (i,x) in enumerate(self.x_mids): - - bar = Rectangle( - width = self.widths_scaled[i], - height = self.y_values_scaled[i], - stroke_width = self.bar_stroke_width, - stroke_color = self.stroke_color, - ) - if bar.height == 0: - bar.height = 0.01 - bar.init_points() - - t = float(x - self.x_min)/(self.x_max - self.x_min) - bar_color = interpolate_color( - self.start_color, - self.end_color, - t - ) - bar.set_fill(color = bar_color, opacity = 1) - bar.next_to(previous_bar,RIGHT,buff = 0, aligned_edge = DOWN) - - self.bars.add(bar) - - x_label = TextMobject(self.x_labels[i]) - x_label.next_to(bar,DOWN) - self.x_labels_group.add(x_label) - - y_label = TextMobject(self.y_labels[i]) - if self.y_label_position == "top": - y_label.next_to(bar, UP) - elif self.y_label_position == "center": - y_label.move_to(bar) - else: - raise Exception("y_label_position must be top or center") - self.y_labels_group.add(y_label) - - if i == 0: - # start with the lower left - outline_points.append(bar.get_anchors()[-2]) - - # upper two points of each bar - outline_points.append(bar.get_anchors()[0]) - outline_points.append(bar.get_anchors()[1]) - - previous_bar = bar - # close the outline - # lower right - outline_points.append(bar.get_anchors()[2]) - # lower left - outline_points.append(outline_points[0]) - - self.outline = Polygon(*outline_points, - stroke_width = self.outline_stroke_width, - stroke_color = self.stroke_color) - self.add(self.bars, self.x_labels_group, self.y_labels_group, self.outline) - - self.move_to(ORIGIN) - - def get_lower_left_point(self): - return self.bars[0].get_anchors()[-2] - - - -class BuildUpHistogram(Animation): - - def __init__(self, hist, **kwargs): - self.histogram = hist - - - - - - - -class FlashThroughHistogram(Animation): - CONFIG = { - "cell_color" : WHITE, - "cell_opacity" : 0.8, - "hist_opacity" : 0.2 - } - - def __init__(self, mobject, - direction = "horizontal", - mode = "random", - **kwargs): - - digest_config(self, kwargs) - - self.cell_height = mobject.y_scale - self.prototype_cell = Rectangle( - width = 1, - height = self.cell_height, - fill_color = self.cell_color, - fill_opacity = self.cell_opacity, - stroke_width = 0, - ) - - x_values = mobject.x_values - y_values = mobject.y_values - - self.mode = mode - self.direction = direction - - self.generate_cell_indices(x_values,y_values) - Animation.__init__(self,mobject,**kwargs) - - - - def generate_cell_indices(self,x_values,y_values): - - self.cell_indices = [] - for (i,x) in enumerate(x_values): - - nb_cells = int(np.floor(y_values[i])) - for j in range(nb_cells): - self.cell_indices.append((i, j)) - - self.reordered_cell_indices = self.cell_indices - if self.mode == "random": - shuffle(self.reordered_cell_indices) - - - def cell_for_index(self,i,j): - - if self.direction == "vertical": - width = self.mobject.x_scale - height = self.mobject.y_scale - x = (i + 0.5) * self.mobject.x_scale - y = (j + 0.5) * self.mobject.y_scale - center = self.mobject.get_lower_left_point() + x * RIGHT + y * UP - - elif self.direction == "horizontal": - width = self.mobject.x_scale / self.mobject.y_values[i] - height = self.mobject.y_scale * self.mobject.y_values[i] - x = i * self.mobject.x_scale + (j + 0.5) * width - y = height / 2 - center = self.mobject.get_lower_left_point() + x * RIGHT + y * UP - - cell = Rectangle(width = width, height = height) - cell.move_to(center) - return cell - - - def interpolate_mobject(self,t): - - if t == 0: - self.mobject.add(self.prototype_cell) - - flash_nb = int(t * (len(self.cell_indices))) - 1 - (i,j) = self.reordered_cell_indices[flash_nb] - cell = self.cell_for_index(i,j) - self.prototype_cell.width = cell.get_width() - self.prototype_cell.height = cell.get_height() - self.prototype_cell.init_points() - self.prototype_cell.move_to(cell.get_center()) - - if t == 1: - self.mobject.remove(self.prototype_cell) - - - def clean_up_from_scene(self, scene = None): - Animation.clean_up_from_scene(self, scene) - self.update(1) - if scene is not None: - if self.is_remover(): - scene.remove(self.prototype_cell) - else: - scene.add(self.prototype_cell) - return self - - - -class OutlineableBars(VGroup): - - # A group of bars (rectangles), together with - # a method that draws an outline around them, - # assuming the bars are arranged in a histogram - # (aligned at the bottom without gaps). - - # We use this to morph a row of bricks into a histogram. - - CONFIG = { - "outline_stroke_width" : 3, - "stroke_color" : WHITE - } - def create_outline(self, animated = False, **kwargs): - - outline_points = [] - - for (i, bar) in enumerate(self.submobjects): - - if i == 0: - # start with the lower left - outline_points.append(bar.get_corner(DOWN + LEFT)) - - # upper two points of each bar - outline_points.append(bar.get_corner(UP + LEFT)) - outline_points.append(bar.get_corner(UP + RIGHT)) - - previous_bar = bar - # close the outline - # lower right - outline_points.append(previous_bar.get_corner(DOWN + RIGHT)) - # lower left - outline_points.append(outline_points[0]) - - self.outline = Polygon(*outline_points, - stroke_width = self.outline_stroke_width, - stroke_color = self.stroke_color) - - if animated: - self.play(FadeIn(self.outline, **kwargs)) - return self.outline - - - - - - - - - - - - - diff --git a/from_3b1b/on_hold/eop/reusables/sick_pi_creature.py b/from_3b1b/on_hold/eop/reusables/sick_pi_creature.py deleted file mode 100644 index 3b754e19..00000000 --- a/from_3b1b/on_hold/eop/reusables/sick_pi_creature.py +++ /dev/null @@ -1,22 +0,0 @@ - -from for_3b1b_videos.pi_creature import * -from active_projects.eop.reusables.eop_constants import * - - -class SicklyPiCreature(PiCreature): - CONFIG = { - "sick_color": SICKLY_GREEN - } - - def get_slightly_sick(self): - - self.save_state() - self.set_color(self.sick_color) - - def get_sick(self): - - self.get_slightly_sick() - self.change_mode("sick") - - def get_better(self): - self.restore() \ No newline at end of file diff --git a/from_3b1b/on_hold/eop/reusables/upright_coins.py b/from_3b1b/on_hold/eop/reusables/upright_coins.py deleted file mode 100644 index 399348c7..00000000 --- a/from_3b1b/on_hold/eop/reusables/upright_coins.py +++ /dev/null @@ -1,132 +0,0 @@ - -from mobject.geometry import * -from mobject.svg.tex_mobject import * -from utils.color import * - -from active_projects.eop.reusables.eop_helpers import * -from active_projects.eop.reusables.eop_constants import * - -class UprightCoin(Circle): -# For use in coin sequences - CONFIG = { - "radius": COIN_RADIUS, - "stroke_width": COIN_STROKE_WIDTH, - "stroke_color": WHITE, - "fill_opacity": 1, - "symbol": "\euro" - } - - def __init__(self, **kwargs): - Circle.__init__(self,**kwargs) - self.symbol_mob = TextMobject(self.symbol, stroke_color = self.stroke_color) - self.symbol_mob.set_height(0.5*self.get_height()).move_to(self) - self.add(self.symbol_mob) - -class UprightHeads(UprightCoin): - CONFIG = { - "fill_color": COLOR_HEADS_COIN, - "symbol": "H", - } - -class UprightTails(UprightCoin): - CONFIG = { - "fill_color": COLOR_TAILS_COIN, - "symbol": "T", - } - -class CoinSequence(VGroup): - CONFIG = { - "sequence": [], - "radius" : COIN_RADIUS, - "spacing": COIN_SEQUENCE_SPACING, - "direction": RIGHT - } - - def __init__(self, sequence, **kwargs): - VGroup.__init__(self, **kwargs) - self.sequence = sequence - offset = 0 - for symbol in self.sequence: - if symbol == "H": - new_coin = UprightHeads(radius = self.radius) - elif symbol == "T": - new_coin = UprightTails(radius = self.radius) - else: - new_coin = UprightCoin(symbol = symbol, radius = self.radius) - new_coin.shift(offset * self.direction) - self.add(new_coin) - offset += self.spacing - - -class FlatCoin(UprightCoin): -# For use in coin stacks - CONFIG = { - "thickness": COIN_THICKNESS, - "foreshortening": COIN_FORESHORTENING, - "nb_ridges": COIN_NB_RIDGES - } - - def __init__(self, **kwargs): - UprightCoin.__init__(self, **kwargs) - self.symbol_mob.rotate(TAU/8) - self.stretch_in_place(self.foreshortening, 1) - - # draw the edge - control_points1 = self.points[12:25].tolist() - control_points2 = self.copy().shift(self.thickness * DOWN).points[12:25].tolist() - edge_anchors_and_handles = control_points1 - edge_anchors_and_handles.append(edge_anchors_and_handles[-1] + self.thickness * DOWN) - edge_anchors_and_handles.append(edge_anchors_and_handles[-1] + self.thickness * UP) - edge_anchors_and_handles += control_points2[::-1] # list concatenation - edge_anchors_and_handles.append(edge_anchors_and_handles[-1] + self.thickness * UP) - edge_anchors_and_handles.append(edge_anchors_and_handles[-1] + self.thickness * DOWN) - edge_anchors_and_handles.append(control_points1[0]) - #edge_anchors_and_handles = edge_anchors_and_handles[::-1] - edge = VMobject() - edge.set_points(edge_anchors_and_handles) - edge.set_fill( - color = self.fill_color, - opacity = self.fill_opacity - ) - edge.set_stroke(width = self.stroke_width) - self.add(edge) - - # draw the ridges - PI = TAU/2 - dtheta = PI/self.nb_ridges - ridge_angles = np.arange(dtheta,PI,dtheta) - # add a twist onto each coin - ridge_angles += np.random.rand(1) * dtheta - # crop the angles that overshoot on either side - ridge_angles = ridge_angles[(ridge_angles > 0) * (ridge_angles < PI)] - ridge_positions = 0.5 * 2 * self.radius * np.array([ - np.cos(theta) for theta in ridge_angles - ]) - ridge_color = interpolate_color(self.stroke_color, self.fill_color, 0.7) - for x in ridge_positions: - y = -(1 - (x/self.radius)**2)**0.5 * self.foreshortening * self.radius - ridge = Line( - x * RIGHT + y * UP, - x * RIGHT + y * UP + self.thickness * DOWN, - stroke_color = ridge_color, - stroke_width = self.stroke_width - ) - self.add(ridge) - - # redraw the unfilled edge to cover the ridge ends - empty_edge = edge.copy() - empty_edge.set_fill(opacity = 0) - self.add(empty_edge) - -class FlatHeads(FlatCoin): - CONFIG = { - "fill_color": COLOR_HEADS_COIN, - "symbol": "H", - } - -class FlatTails(FlatCoin): - CONFIG = { - "fill_color": COLOR_TAILS_COIN, - "symbol": "T", - } - \ No newline at end of file diff --git a/from_3b1b/on_hold/eop/what_does_probability_mean.py b/from_3b1b/on_hold/eop/what_does_probability_mean.py deleted file mode 100644 index a692a774..00000000 --- a/from_3b1b/on_hold/eop/what_does_probability_mean.py +++ /dev/null @@ -1,30 +0,0 @@ - -from manimlib.imports import * - -class WhatDoesItReallyMean(TeacherStudentsScene): - - CONFIG = { - "default_pi_creature_kwargs": { - "color": MAROON_E, - "flip_at_start": True, - }, - } - - def construct(self): - - student_q = TextMobject("What does", "``probability''", "\emph{actually}", "mean?") - student_q.set_color_by_tex("probability", YELLOW) - self.student_says(student_q, target_mode = "sassy") - self.wait() - - question_bubble = VGroup(student_q, students[1].bubble) - scaled_qb = question_bubble.copy() - scaled_qb.scale(0.4).to_corner(UL) - self.play(Transform(question_bubble, scaled_qb)) - self.wait() - - - - self.teacher_says("Don't worry -- philosophy can come later!") - self.wait() - diff --git a/from_3b1b/on_hold/holomorphic.py b/from_3b1b/on_hold/holomorphic.py deleted file mode 100644 index 91721cd1..00000000 --- a/from_3b1b/on_hold/holomorphic.py +++ /dev/null @@ -1,182 +0,0 @@ -from manimlib.imports import * - - -class ComplexAnalysisOverlay(Scene): - def construct(self): - words = TextMobject("Complex analysis") - words.scale(1.25) - words.to_edge(UP) - words.add_background_rectangle() - self.add(words) - self.wait() - - -class AnalyzeZSquared(ComplexTransformationScene, ZoomedScene): - CONFIG = { - "plane_config": { - "line_frequency": 0.1, - }, - "num_anchors_to_add_per_line": 20, - "complex_homotopy": lambda z, t: z**(1.0 + t), - "zoom_factor": 0.05, - } - - def setup(self): - ComplexTransformationScene.setup(self) - ZoomedScene.setup(self) - - def construct(self): - self.edit_background_plane() - self.add_title() - # self.add_transforming_planes() - # self.preview_some_numbers() - self.zoom_in_to_one_plus_half_i() - self.write_derivative() - - def add_title(self): - title = TexMobject("z \\rightarrow z^2") - title.add_background_rectangle() - title.scale(1.5) - title.to_corner(UL, buff=MED_SMALL_BUFF) - self.add_foreground_mobject(title) - - def edit_background_plane(self): - self.backgrounds.set_stroke(GREY, 2) - self.background.secondary_lines.set_stroke(DARK_GREY, 1) - self.add_foreground_mobject(self.background.coordinate_labels) - - def add_transforming_planes(self): - self.plane = self.get_plane() - self.add_transformable_mobjects(self.plane) - - def preview_some_numbers(self): - dots = VGroup(*[ - Dot().move_to(self.background.number_to_point(z)) - for z in [ - 1, 2, complex(0, 1), - -1, complex(2, 0.5), complex(-1, -1), complex(3, 0.5), - ] - ]) - dots.set_color_by_gradient(RED, YELLOW) - d_angle = 30 * DEGREES - - dot_groups = VGroup() - for dot in dots: - point = dot.get_center() - z = self.background.point_to_number(point) - z_out = self.complex_homotopy(z, 1) - out_point = self.background.number_to_point(z_out) - path_arc = angle_of_vector(point) - if abs(z - 1) < 0.01: - # One is special - arrow = Arc( - start_angle=(-90 * DEGREES + d_angle), - angle=(360 * DEGREES - 2 * d_angle), - radius=0.25 - ) - arrow.add_tip(tip_length=0.15) - arrow.pointwise_become_partial(arrow, 0, 0.9) - arrow.next_to(dot, UP, buff=0) - else: - arrow = Arrow( - point, out_point, - path_arc=path_arc, - buff=SMALL_BUFF, - ) - arrow.match_color(dot) - - out_dot = dot.copy() - # out_dot.set_fill(opacity=0.5) - out_dot.set_stroke(BLUE, 1) - out_dot.move_to(out_point) - dot.path_arc = path_arc - dot.out_dot = out_dot - - dot_group = VGroup(dot, arrow, out_dot) - dot_groups.add(dot_group) - - dot_copy = dot.copy() - dot.save_state() - dot.scale(3) - dot.fade(1) - - dot_group.anim = Succession( - ApplyMethod(dot.restore), - AnimationGroup( - ShowCreation(arrow), - ReplacementTransform( - dot_copy, out_dot, - path_arc=path_arc - ) - ) - ) - - for dot_group in dot_groups[:3]: - self.play(dot_group.anim) - self.wait() - self.play(*[dg.anim for dg in dot_groups[3:]]) - - self.apply_complex_homotopy( - self.complex_homotopy, - added_anims=[Animation(dot_groups)] - ) - self.wait() - self.play(FadeOut(dot_groups)) - self.wait() - self.play(FadeOut(self.plane)) - self.transformable_mobjects.remove(self.plane) - - def zoom_in_to_one_plus_half_i(self): - z = complex(1, 0.5) - point = self.background.number_to_point(z) - point_mob = VectorizedPoint(point) - frame = self.zoomed_camera.frame - frame.move_to(point) - tiny_plane = NumberPlane( - x_radius=2, y_radius=2, - color=GREEN, - secondary_color=GREEN_E - ) - tiny_plane.replace(frame) - - plane = self.get_plane() - - words = TextMobject("What does this look like") - words.add_background_rectangle() - words.next_to(self.zoomed_display, LEFT, aligned_edge=UP) - arrow = Arrow(words.get_bottom(), self.zoomed_display.get_left()) - VGroup(words, arrow).set_color(YELLOW) - - self.play(FadeIn(plane)) - self.activate_zooming(animate=True) - self.play(ShowCreation(tiny_plane)) - self.wait() - self.add_transformable_mobjects(plane, tiny_plane, point_mob) - self.add_foreground_mobjects(words, arrow) - self.apply_complex_homotopy( - self.complex_homotopy, - added_anims=[ - Write(words), - GrowArrow(arrow), - MaintainPositionRelativeTo(frame, point_mob) - ] - ) - self.wait(2) - - def write_derivative(self): - pass - - # Helpers - - def get_plane(self): - top_plane = NumberPlane( - y_radius=FRAME_HEIGHT / 2, - x_line_frequency=0.1, - y_line_frequency=0.1, - ) - self.prepare_for_transformation(top_plane) - bottom_plane = top_plane.copy() - tiny_tiny_buff = 0.001 - top_plane.next_to(ORIGIN, UP, buff=tiny_tiny_buff) - bottom_plane.next_to(ORIGIN, DOWN, buff=tiny_tiny_buff) - return VGroup(top_plane, bottom_plane) diff --git a/from_3b1b/on_hold/moduli.py b/from_3b1b/on_hold/moduli.py deleted file mode 100644 index 7b79cdc9..00000000 --- a/from_3b1b/on_hold/moduli.py +++ /dev/null @@ -1,928 +0,0 @@ -from manimlib.imports import * - - -class TriangleModuliSpace(Scene): - CONFIG = { - "camera_config": { - "background_image": "chalkboard", - }, - "degen_color": GREEN_D, - "x1_color": GREEN_B, - "y1_color": RED, - "x_eq_y_color": YELLOW, - "right_color": TEAL, - "obtuse_color": PINK, - "acute_color": GREEN, - "triangle_fill_opacity": 0.5, - "random_seed": 0, - "example_triangle_width": 6, - } - - def setup(self): - self.plane = NumberPlane( - axis_config={ - "unit_size": 2, - } - ) - - def construct(self): - self.show_meaning_of_similar() - self.show_xy_rule() - - def show_meaning_of_similar(self): - # Setup titles - title = TextMobject("Space", " of all ", "triangles") - title.scale(1.5) - title.to_edge(UP) - - subtitle = TextMobject("up to similarity.") - subtitle.scale(1.5) - subtitle.next_to(title, DOWN, MED_SMALL_BUFF) - - question = TextMobject("What ", "is ", "a\\\\", "moduli ", "space", "?") - question.scale(2) - - # Setup all triangles - all_triangles, tri_classes = self.get_triangles_and_classes() - tri_classes[2][1].scale(0.5) - tri_classes[2][1].scalar *= 0.5 - - all_triangles.to_edge(DOWN) - - # Triangles pop up... - self.play( - LaggedStartMap(FadeInFromDown, question), - ) - self.wait() - - self.play( - ReplacementTransform( - question.get_part_by_tex("space"), - title.get_part_by_tex("Space"), - ), - FadeOut(question[:-2]), - FadeOut(question[-1]), - FadeIn(title[1:]), - LaggedStartMap( - DrawBorderThenFill, all_triangles, - rate_func=bezier([0, 0, 1.5, 1, 1]), - run_time=5, - lag_ratio=0.05, - ) - ) - self.wait() - - # ...Then divide into classes - tri_classes.generate_target() - colors = Color(BLUE).range_to(Color(RED), len(tri_classes)) - for group, color in zip(tri_classes.target, colors): - group.arrange(DOWN) - group.set_color(color) - tri_classes.target.arrange(RIGHT, buff=1.25, aligned_edge=UP) - tri_classes.target.scale(0.85) - tri_classes.target.to_corner(DL) - max_width = max([tc.get_width() for tc in tri_classes.target]) - height = tri_classes.target.get_height() + 0.5 - rects = VGroup(*[ - Rectangle( - height=height, - width=max_width + 0.25, - stroke_width=2, - ).move_to(tri_class, UP) - for tri_class in tri_classes.target - ]) - rects.shift(MED_SMALL_BUFF * UP) - - # Dumb shifts - # tri_classes.target[1][2].shift(0.25 * UP) - tri_classes.target[2].scale(0.9, about_edge=UP) - tri_classes.target[2][2].shift(0.2 * UP) - tri_classes.target[3][0].shift(0.5 * DOWN) - tri_classes.target[3][1].shift(1.0 * DOWN) - tri_classes.target[3][2].shift(1.2 * DOWN) - tri_classes.target[4][1:].shift(0.7 * UP) - - # Dots - per_class_dots = VGroup(*[ - TexMobject("\\vdots").move_to( - tri_class - ).set_y(rects.get_bottom()[1] + 0.4) - for tri_class in tri_classes.target - ]) - all_class_dots = TexMobject("\\dots").next_to( - rects, RIGHT, MED_SMALL_BUFF, - ) - - self.play( - FadeInFromDown(subtitle), - MoveToTarget(tri_classes), - ) - self.play( - LaggedStartMap(FadeIn, rects), - Write(per_class_dots), - Write(all_class_dots), - ) - self.wait(2) - - # Similar - - tri1 = tri_classes[2][1] - tri2 = tri_classes[2][2] - tri1.save_state() - tri2.save_state() - - sim_sign = TexMobject("\\sim") - sim_sign.set_width(1) - sim_sign.move_to(midpoint(rects.get_top(), TOP)) - sim_sign.shift(0.25 * DOWN) - - similar_word = TextMobject("Similar") - similar_word.scale(1.5) - similar_word.move_to(sim_sign) - similar_word.to_edge(UP) - - self.play( - FadeOutAndShift(VGroup(title, subtitle), UP), - tri1.next_to, sim_sign, LEFT, 0.75, - tri2.next_to, sim_sign, RIGHT, 0.75, - ) - self.play( - FadeInFromDown(sim_sign), - Write(similar_word, run_time=1) - ) - self.wait() - - # Move into place - tri1_copy = tri1.copy() - self.play( - tri1_copy.next_to, tri2, - RIGHT, LARGE_BUFF, - path_arc=90 * DEGREES, - ) - self.play(Rotate(tri1_copy, tri2.angle - tri1.angle)) - self.play(tri1_copy.scale, tri2.scalar / tri1.scalar) - self.play( - tri1_copy.move_to, tri2, - ) - tri1_copy.set_color(YELLOW) - self.play( - FadeOut(tri1_copy), - rate_func=rush_from, - ) - self.wait(2) - - # Show non-similar example - not_similar_word = TextMobject("Not ", "Similar") - not_similar_word.scale(1.5) - not_similar_word.move_to(similar_word) - not_similar_word.set_color(RED) - - sim_cross = Line(DL, UR) - sim_cross.set_color(RED) - sim_cross.match_width(sim_sign) - sim_cross.move_to(sim_sign) - sim_cross.set_stroke(BLACK, 5, background=True) - - tri3 = tri_classes[1][2] - tri3.save_state() - tri3.generate_target() - tri3.target.move_to(tri2, LEFT) - - tri1_copy = tri1.copy() - - self.play( - Restore(tri2), - MoveToTarget(tri3), - ) - self.play( - ReplacementTransform( - similar_word[0], - not_similar_word[1], - ), - GrowFromCenter(not_similar_word[0]), - ShowCreation(sim_cross), - ) - self.play(tri1_copy.move_to, tri3) - self.play(Rotate(tri1_copy, 90 * DEGREES)) - self.play( - tri1_copy.match_height, tri3, - tri1_copy.move_to, tri3, RIGHT, - ) - self.play(WiggleOutThenIn(tri1_copy, n_wiggles=10)) - self.play(FadeOut(tri1_copy)) - - self.wait() - - # Back to classes - new_title = TextMobject("Space of all\\\\", "Similarity classes") - new_title.scale(1.5) - new_title[1].set_color(YELLOW) - new_title.to_edge(UP) - new_title_underline = Line(LEFT, RIGHT) - new_title_underline.match_width(new_title[1]) - new_title_underline.match_color(new_title[1]) - new_title_underline.next_to(new_title, DOWN, buff=0.05) - - self.play( - Restore(tri1), - Restore(tri2), - Restore(tri3), - FadeOut(not_similar_word), - FadeOut(sim_sign), - FadeOut(sim_cross), - FadeInFrom(new_title[1], UP), - ) - self.play( - ShowCreationThenDestruction(new_title_underline), - LaggedStartMap( - ApplyMethod, rects, - lambda m: (m.set_stroke, YELLOW, 5), - rate_func=there_and_back, - run_time=1, - ) - ) - self.wait() - self.play(Write(new_title[0])) - self.wait() - - # Show abstract space - blob = ThoughtBubble()[-1] - blob.set_height(2) - blob.to_corner(UR) - - dots = VGroup(*[ - Dot(color=tri.get_color()).move_to( - self.get_triangle_x(tri) * RIGHT + - self.get_triangle_y(tri) * UP, - ) - for tri_class in tri_classes - for tri in tri_class[0] - ]) - dots.space_out_submobjects(2) - dots.move_to(blob) - - self.play( - DrawBorderThenFill(blob), - new_title.shift, LEFT, - ) - - self.play(LaggedStart( - *[ - ReplacementTransform( - tri_class.copy().set_fill(opacity=0), - dot - ) - for tri_class, dot in zip(tri_classes, dots) - ], - run_time=3, - lag_ratio=0.3, - )) - - # Isolate one triangle - - tri = tri_classes[0][0] - verts = tri.get_vertices() - angle = PI + angle_of_vector(verts[1] - verts[2]) - - self.play( - tri.rotate, -angle, - tri.set_width, self.example_triangle_width, - tri.center, - FadeOut(tri_classes[0][1:]), - FadeOut(tri_classes[1:]), - FadeOut(rects), - FadeOut(per_class_dots), - FadeOut(all_class_dots), - FadeOut(blob), - FadeOut(dots), - FadeOut(new_title), - ) - - self.triangle = tri - - def show_xy_rule(self): - unit_factor = 4.0 - - if hasattr(self, "triangle"): - triangle = self.triangle - else: - triangle = self.get_triangles_and_classes()[0][0] - verts = triangle.get_vertices() - angle = PI + angle_of_vector(verts[1] - verts[2]) - triangle.rotate(-angle) - triangle.set_width(self.example_triangle_width) - triangle.center() - self.add(triangle) - - side_trackers = VGroup(*[Line() for x in range(3)]) - side_trackers.set_stroke(width=0, opacity=0) - side_trackers.triangle = triangle - - def update_side_trackers(st): - verts = st.triangle.get_vertices() - st[0].put_start_and_end_on(verts[0], verts[1]) - st[1].put_start_and_end_on(verts[1], verts[2]) - st[2].put_start_and_end_on(verts[2], verts[0]) - - side_trackers.add_updater(update_side_trackers) - - def get_length_labels(): - result = VGroup() - for line in side_trackers: - vect = normalize(line.get_vector()) - perp_vect = rotate_vector(vect, -90 * DEGREES) - perp_vect = np.round(perp_vect, 1) - label = DecimalNumber(line.get_length() / unit_factor) - label.move_to(line.get_center()) - label.next_to(line.get_center(), perp_vect, buff=0.15) - result.add(label) - return result - - side_labels = always_redraw(get_length_labels) - - b_label, c_label, a_label = side_labels - b_side, c_side, a_side = side_trackers - - # Rescale - self.add(side_trackers) - self.play(LaggedStartMap(FadeIn, side_labels, lag_ratio=0.3, run_time=1)) - self.add(side_labels) - self.wait() - self.play(triangle.set_width, unit_factor) - self.play(ShowCreationThenFadeAround(c_label)) - self.wait() - - # Label x and y - x_label = TexMobject("x") - y_label = TexMobject("y") - xy_labels = VGroup(x_label, y_label) - xy_labels.scale(1.5) - - x_color = self.x1_color - y_color = self.y1_color - - x_label[0].set_color(x_color) - y_label[0].set_color(y_color) - - # side_labels.clear_updaters() - for var, num, vect in zip(xy_labels, [b_label, a_label], [DR, DL]): - buff = 0.15 - var.move_to(num, vect) - var.brace = Brace(num, UP) - var.brace.num = num - var.brace.add_updater( - lambda m: m.next_to(m.num, UP, buff=buff) - ) - var.add_updater( - lambda m: m.next_to(m.brace, UP, buff=buff) - ) - - var.suspend_updating() - var.brace.suspend_updating() - self.play( - FadeInFrom(var, DOWN), - Write(var.brace, run_time=1), - # MoveToTarget(num) - ) - self.wait() - - # Show plane - to_move = VGroup( - triangle, - side_labels, - x_label, - x_label.brace, - y_label, - y_label.brace, - ) - - axes = Axes( - x_min=-0.25, - x_max=1.5, - y_min=-0.25, - y_max=1.5, - axis_config={ - "tick_frequency": 0.25, - "unit_size": 3, - } - ) - x_axis = axes.x_axis - y_axis = axes.y_axis - - x_axis.add(TexMobject("x", color=x_color).next_to(x_axis, RIGHT)) - y_axis.add(TexMobject("y", color=y_color).next_to(y_axis, UP)) - - for axis, vect in [(x_axis, DOWN), (y_axis, LEFT)]: - axis.add_numbers( - 0.5, 1.0, - number_config={"num_decimal_places": 1}, - direction=vect, - ) - - axes.to_corner(DR, buff=LARGE_BUFF) - - self.play( - to_move.to_corner, UL, {"buff": LARGE_BUFF}, - to_move.shift, MED_LARGE_BUFF * DOWN, - Write(axes), - ) - - # Show coordinates - coords = VGroup(b_label.copy(), a_label.copy()) - - x_coord, y_coord = coords - x_coord.add_updater(lambda m: m.set_value(b_side.get_length() / unit_factor)) - y_coord.add_updater(lambda m: m.set_value(a_side.get_length() / unit_factor)) - - def get_coord_values(): - return [c.get_value() for c in coords] - - def get_ms_point(): - return axes.c2p(*get_coord_values()) - - dot = always_redraw( - lambda: triangle.copy().set_width(0.1).move_to(get_ms_point()) - ) - - y_line = always_redraw( - lambda: DashedLine( - x_axis.n2p(x_coord.get_value()), - get_ms_point(), - color=y_color, - stroke_width=1, - ) - ) - x_line = always_redraw( - lambda: DashedLine( - y_axis.n2p(y_coord.get_value()), - get_ms_point(), - color=x_color, - stroke_width=1, - ) - ) - - coord_label = TexMobject("(", "0.00", ",", "0.00", ")") - cl_buff = 0 - coord_label.next_to(dot, UR, buff=cl_buff) - for i, coord in zip([1, 3], coords): - coord.generate_target() - coord.target.replace(coord_label[i], dim_to_match=0) - coord_label[i].set_opacity(0) - - self.play( - MoveToTarget(x_coord), - MoveToTarget(y_coord), - FadeIn(coord_label), - ReplacementTransform(triangle.copy().set_fill(opacity=0), dot), - ) - coord_label.add(*coords) - coord_label.add_updater(lambda m: m.next_to(dot, UR, buff=cl_buff)) - self.add(x_label, y_label, dot) - self.play( - ShowCreation(x_line), - ShowCreation(y_line), - ) - self.wait() - - # Adjust triangle - tip_tracker = VectorizedPoint(triangle.points[0]) - - def update_triangle(tri): - point = tip_tracker.get_location() - tri.points[0] = point - tri.points[-1] = point - tri.make_jagged() - - triangle.add_updater(update_triangle) - - self.add(tip_tracker) - self.play(tip_tracker.shift, 0.5 * LEFT + 1.0 * UP) - self.play(tip_tracker.shift, 2.0 * DOWN) - self.play(tip_tracker.shift, 1.5 * RIGHT) - self.play(tip_tracker.shift, 1.0 * LEFT + 1.0 * UP) - self.wait() - - # Show box - t2c = {"x": x_color, "y": y_color} - ineq1 = TexMobject("0", "\\le ", "x", "\\le", "1", tex_to_color_map=t2c) - ineq2 = TexMobject("0", "\\le ", "y", "\\le", "1", tex_to_color_map=t2c) - - ineqs = VGroup(ineq1, ineq2) - ineqs.scale(1.5) - ineqs.arrange(DOWN, buff=MED_LARGE_BUFF) - ineqs.next_to(triangle, DOWN, buff=1.5) - - box = Square( - fill_color=DARK_GREY, - fill_opacity=0.75, - stroke_color=LIGHT_GREY, - stroke_width=2, - ) - box.replace(Line(axes.c2p(0, 0), axes.c2p(1, 1))) - box_outline = box.copy() - box_outline.set_fill(opacity=0) - box_outline.set_stroke(YELLOW, 3) - - self.add(box, axes, x_line, y_line, coord_label, dot) - self.play( - FadeIn(box), - LaggedStartMap(FadeInFromDown, ineqs) - ) - self.play( - ShowCreationThenFadeOut(box_outline) - ) - self.wait() - - # x >= y slice - region = Polygon( - axes.c2p(0, 0), - axes.c2p(1, 0), - axes.c2p(1, 1), - fill_color=GREY_BROWN, - fill_opacity=0.75, - stroke_color=GREY_BROWN, - stroke_width=2, - ) - region_outline = region.copy() - region_outline.set_fill(opacity=0) - region_outline.set_stroke(YELLOW, 3) - - x_eq_y_line = Line(axes.c2p(0, 0), axes.c2p(1, 1)) - x_eq_y_line.set_stroke(self.x_eq_y_color, 2) - x_eq_y_label = TexMobject("x=y", tex_to_color_map=t2c) - x_eq_y_label.next_to(x_eq_y_line.get_end(), LEFT, MED_LARGE_BUFF) - x_eq_y_label.shift(0.75 * DL) - - ineq = TexMobject("0", "\\le", "y", "\\le", "x", "\\le", "1") - ineq.set_color_by_tex("x", x_color) - ineq.set_color_by_tex("y", y_color) - ineq.scale(1.5) - ineq.move_to(ineqs, LEFT) - - self.add(region, axes, x_line, y_line, coord_label, dot) - self.play( - FadeIn(region), - ShowCreation(x_eq_y_line), - # FadeInFromDown(x_eq_y_label), - Transform(ineq1[:2], ineq[:2], remover=True), - Transform(ineq1[2:], ineq[4:], remover=True), - Transform(ineq2[:4], ineq[:4], remover=True), - Transform(ineq2[4:], ineq[6:], remover=True), - ) - self.add(ineq) - self.play(ShowCreationThenFadeOut(region_outline)) - self.wait() - - # x + y <= 1 slice - xpy1_line = Line(axes.c2p(0, 1), axes.c2p(1, 0)) - xpy1_line.set_stroke(GREEN, 2) - xpy1_label = TexMobject("x+y=1", tex_to_color_map=t2c) - xpy1_label.next_to(xpy1_line.get_start(), RIGHT, MED_LARGE_BUFF) - xpy1_label.shift(0.75 * DR) - - xpy1_ineq = TexMobject("1 \\le x + y", tex_to_color_map=t2c) - xpy1_ineq.scale(1.5) - xpy1_ineq.next_to(ineq, DOWN, buff=MED_LARGE_BUFF) - - ms_region = Polygon( - axes.c2p(1, 0), - axes.c2p(0.5, 0.5), - axes.c2p(1, 1), - fill_color=BLUE_E, - fill_opacity=0.75, - stroke_width=0, - ) - ms_outline = ms_region.copy() - ms_outline.set_fill(opacity=0) - ms_outline.set_stroke(YELLOW, 2) - - tt_line = Line(DOWN, UP, color=WHITE) - tt_line.set_height(0.25) - tt_line.add_updater(lambda m: m.move_to(tip_tracker)) - - self.play( - ShowCreation(xpy1_line), - # FadeInFrom(xpy1_label, DOWN), - FadeInFrom(xpy1_ineq, UP) - ) - self.wait() - self.play( - tip_tracker.set_y, triangle.get_bottom()[1] + 0.01, - FadeIn(tt_line), - ) - self.wait() - - self.add(ms_region, axes, x_line, y_line, coord_label, dot) - self.play( - FadeIn(ms_region), - region.set_fill, DARK_GREY, - ) - self.wait() - - # Move tip around - self.play( - tip_tracker.shift, UP + RIGHT, - FadeOut(tt_line), - ) - self.wait() - self.play(tip_tracker.shift, 0.5 * DOWN + LEFT, run_time=2) - self.wait() - self.play(tip_tracker.shift, UP + 0.7 * LEFT, run_time=2) - self.wait() - equilateral_point = triangle.get_bottom() + unit_factor * 0.5 * np.sqrt(3) * UP - self.play( - tip_tracker.move_to, - equilateral_point, - run_time=2, - ) - self.wait() - - # Label as moduli space - ms_words = TextMobject("Moduli\\\\", "Space") - ms_words.scale(1.5) - ms_words.next_to(ms_region, RIGHT, buff=0.35) - ms_arrow = Arrow( - ms_words[1].get_corner(DL), - ms_region.get_center(), - path_arc=-90 * DEGREES, - buff=0.1, - ) - # ms_arrow.rotate(-10 * DEGREES) - ms_arrow.shift(0.1 * RIGHT) - ms_arrow.scale(0.95) - - self.play( - FadeInFrom(ms_words, LEFT), - ) - self.play(ShowCreation(ms_arrow)) - self.wait() - - # Show right triangles - alpha = np.arcsin(0.8) - vect = rotate_vector(0.6 * unit_factor * LEFT, -alpha) - new_tip = triangle.get_corner(DR) + vect - - elbow = VMobject() - elbow.start_new_path(RIGHT) - elbow.add_line_to(UR) - elbow.add_line_to(UP) - - elbow.rotate(3 * TAU / 4 - alpha, about_point=ORIGIN) - elbow.scale(0.2, about_point=ORIGIN) - elbow.shift(new_tip) - - elbow_circle = Circle() - elbow_circle.replace(elbow) - elbow_circle.scale(3) - elbow_circle.move_to(new_tip) - elbow_circle.set_stroke(self.right_color, 3) - - right_words = TextMobject("Right triangle") - right_words.scale(1.5) - right_words.set_color(self.right_color) - right_words.next_to(triangle, DOWN, buff=1.5) - - ineqs = VGroup(ineq, xpy1_ineq) - - self.play( - tip_tracker.move_to, new_tip, - FadeOut(ms_words), - FadeOut(ms_arrow), - ) - self.play( - ShowCreation(elbow), - FadeInFrom(right_words, UP), - FadeOutAndShift(ineqs, DOWN), - ) - self.play( - ShowCreationThenFadeOut(elbow_circle), - ) - - # Show circular arc - pythag_eq = TexMobject("x^2 + y^2", "=", "1", tex_to_color_map=t2c) - pythag_eq.scale(1.5) - pythag_eq.next_to(right_words, DOWN, buff=MED_LARGE_BUFF) - - arc = Arc( - start_angle=90 * DEGREES, - angle=-90 * DEGREES, - color=self.right_color, - ) - arc.replace(box) - - self.play( - FadeInFrom(pythag_eq, UP), - ) - self.add(arc, arc) - self.play(ShowCreation(arc)) - self.wait() - - # Acute region - arc_piece = VMobject() - arc_piece.pointwise_become_partial(arc, 0.5, 1.0) - - acute_region = VMobject() - acute_region.start_new_path(axes.c2p(1, 1)) - acute_region.add_line_to(arc_piece.get_start()) - acute_region.append_vectorized_mobject(arc_piece) - acute_region.add_line_to(axes.c2p(1, 1)) - acute_region.set_fill(self.acute_color, 1) - acute_region.set_stroke(width=0) - - obtuse_region = VMobject() - obtuse_region.start_new_path(axes.c2p(1, 0)) - obtuse_region.add_line_to(axes.c2p(0.5, 0.5)) - obtuse_region.add_line_to(arc_piece.get_start()) - obtuse_region.append_vectorized_mobject(arc_piece) - obtuse_region.set_fill(self.obtuse_color, 1) - obtuse_region.set_stroke(width=0) - - acute_words = TextMobject("Acute triangle") - acute_words.set_color(self.acute_color) - obtuse_words = TextMobject("Obtuse triangle") - obtuse_words.set_color(self.obtuse_color) - for words in [acute_words, obtuse_words]: - words.scale(1.5) - words.move_to(right_words) - - eq = pythag_eq[-2] - gt = TexMobject(">").replace(eq) - gt.set_color(self.acute_color) - lt = TexMobject("<").replace(eq) - lt.set_color(self.obtuse_color) - - self.add(acute_region, coord_label, x_line, y_line, xpy1_line, x_eq_y_line, dot) - self.play( - tip_tracker.shift, 0.5 * UP, - coord_label.set_opacity, 0, - FadeOut(elbow), - FadeIn(acute_region), - FadeOutAndShift(right_words, UP), - FadeOutAndShift(eq, UP), - FadeInFrom(acute_words, DOWN), - FadeInFrom(gt, DOWN), - ) - self.wait() - self.play(tip_tracker.shift, 0.5 * RIGHT) - self.wait() - self.add(obtuse_region, coord_label, x_line, y_line, xpy1_line, x_eq_y_line, dot) - self.play( - tip_tracker.shift, 1.5 * DOWN, - FadeIn(obtuse_region), - FadeOutAndShift(acute_words, DOWN), - FadeOutAndShift(gt, DOWN), - FadeInFrom(obtuse_words, UP), - FadeInFrom(lt, UP), - ) - self.wait() - self.play(tip_tracker.shift, 0.5 * LEFT) - self.play(tip_tracker.shift, 0.5 * DOWN) - self.play(tip_tracker.shift, 0.5 * RIGHT) - self.play(tip_tracker.shift, 0.5 * UP) - self.wait() - - # Ambient changes - self.play( - FadeOut(obtuse_words), - FadeOut(pythag_eq[:-2]), - FadeOut(pythag_eq[-1]), - FadeOut(lt), - ) - self.play( - tip_tracker.move_to, equilateral_point + 0.25 * DL, - path_arc=30 * DEGREES, - run_time=8, - ) - - # - def get_triangles_and_classes(self): - original_triangles = VGroup(*[ - self.get_random_triangle() - for x in range(5) - ]) - original_triangles.submobjects[4] = self.get_random_triangle() # Hack - all_triangles = VGroup() - tri_classes = VGroup() - for triangle in original_triangles: - all_triangles.add(triangle) - tri_class = VGroup() - tri_class.add(triangle) - for x in range(2): - tri_copy = triangle.copy() - angle = TAU * np.random.random() - scalar = 0.25 + 1.5 * np.random.random() - - tri_copy.rotate(angle - tri_copy.angle) - tri_copy.angle = angle - tri_copy.scale(scalar / tri_copy.scalar) - tri_copy.scalar = scalar - - all_triangles.add(tri_copy) - tri_class.add(tri_copy) - tri_classes.add(tri_class) - - colors = Color(BLUE).range_to(Color(RED), len(all_triangles)) - for triangle, color in zip(all_triangles, colors): - # triangle.set_color(random_bright_color()) - triangle.set_color(color) - - all_triangles.shuffle() - all_triangles.arrange_in_grid(3, 5, buff=MED_LARGE_BUFF) - all_triangles.set_height(6) - sf = 1.25 - all_triangles.stretch(sf, 0) - for triangle in all_triangles: - triangle.stretch(1 / sf, 0) - # all_triangles.next_to(title, DOWN) - all_triangles.to_edge(DOWN, LARGE_BUFF) - - return all_triangles, tri_classes - - def get_random_triangle(self, x=None, y=None): - y = np.random.random() - x = y + np.random.random() - if x + y <= 1: - diff = 1 - (x + y) - x += diff - y += diff - tri = self.get_triangle(x, y) - tri.angle = TAU * np.random.random() - tri.scalar = 0.25 + np.random.random() * 1.5 - - tri.rotate(tri.angle) - tri.scale(tri.scalar) - return tri - - def get_triangle(self, x, y): - # Enforce assumption that x > y - if y > x: - raise Exception("Please ensure x >= y. Thank you.") - plane = self.plane - - # Heron - s = (1 + x + y) / 2.0 - area = np.sqrt(s * (s - 1.0) * (s - x) * (s - y)) - beta = np.arcsin(2 * area / x) - tip_point = RIGHT + rotate_vector(x * LEFT, -beta) - - color = self.get_triangle_color(x, y) - return Polygon( - plane.c2p(0, 0), - plane.c2p(1, 0), - plane.c2p(*tip_point[:2]), - color=color, - fill_opacity=self.triangle_fill_opacity, - ) - - def get_triangle_color(self, x, y): - epsilon = 1e-4 - if x + y == 1: - return self.x_eq_y_color - elif x == 1: - return self.x1_color - elif y == 1: - return self.y1_color - elif np.abs(x**2 + y**2 - 1) < epsilon: - return self.right_color - elif x**2 + y**2 < 1: - return self.obtuse_color - elif x**2 + y**2 > 1: - return self.acute_color - assert(False) # Should not get here - - def get_triangle_xy(self, triangle): - A, B, C = triangle.get_start_anchors()[:3] - a = get_norm(B - C) - b = get_norm(C - A) - c = get_norm(A - B) - sides = np.array(sorted([a, b, c])) - sides = sides / np.max(sides) - return sides[1], sides[0] - - def get_triangle_x(self, triangle): - return self.get_triangle_xy(triangle)[0] - - def get_triangle_y(self, triangle): - return self.get_triangle_xy(triangle)[1] - - -class Credits(Scene): - def construct(self): - items = VGroup( - TextMobject("Written by\\\\Jayadev Athreya"), - TextMobject("Illustrated and Narrated by\\\\Grant Sanderson"), - TextMobject( - "3Blue1Brown\\\\", - "\\copyright {} Copyright 2019\\\\", - "www.3blue1brown.com\\\\", - ), - ) - items.arrange(DOWN, buff=LARGE_BUFF) - - items[-1].set_color(LIGHT_GREY) - items[-1].scale(0.8, about_edge=UP) - items[-1].to_edge(DOWN) - - self.add(items[-1]) - self.play(LaggedStartMap(FadeInFromDown, items[:-1])) - self.wait() diff --git a/from_3b1b/on_hold/shadows.py b/from_3b1b/on_hold/shadows.py deleted file mode 100644 index 7ac30e4d..00000000 --- a/from_3b1b/on_hold/shadows.py +++ /dev/null @@ -1,415 +0,0 @@ -from manimlib.imports import * - - -# Helpers -def get_shadow(mobject, opacity=0.5): - result = mobject.deepcopy() - result.apply_function(lambda p: [p[0], p[1], 0]) - color = interpolate_color( - mobject.get_fill_color(), BLACK, - mobject.get_fill_opacity() - ) - # color = BLACK - result.set_fill(color, opacity=opacity) - result.set_stroke(BLACK, 0.5, opacity=opacity) - result.set_shade_in_3d(False) - return result - - -def get_boundary_points(shadow, n_points=20): - points = shadow.get_points_defining_boundary() - return np.array([ - points[np.argmax(np.dot(points, vect.T))] - for vect in compass_directions(n_points) - ]) - - -def get_area(planar_mobject): - boundary = get_boundary_points(planar_mobject, 100) - xs = boundary[:, 0] - ys = boundary[:, 1] - dxs = np.append(xs[-1], xs[:-1]) - xs - dys = np.append(ys[-1], ys[:-1]) - ys - return abs(sum([ - 0.5 * (x * dy - y * dx) - for x, dx, y, dy in zip(xs, dxs, ys, dys) - ])) - - -def get_xy_plane_projection_point(p1, p2): - """ - Draw a line from source to p1 to p2. Where does it - intersect the xy plane? - """ - vect = p2 - p1 - return p1 - (p1[2] / vect[2]) * vect - - -# Scenes - - -class ShowShadows(ThreeDScene): - CONFIG = { - "object_center": [0, 0, 3], - "area_label_center": [0, -1.5, 0], - "surface_area": 6.0, - "num_reorientations": 10, - "camera_config": { - "light_source_start_point": 10 * OUT, - "frame_center": [0, 0, 0.5], - }, - "initial_orientation_config": { - "phi": 60 * DEGREES, - "theta": -120 * DEGREES, - } - } - - def setup(self): - self.add_plane() - self.setup_orientation_trackers() - self.setup_object_and_shadow() - self.add_shadow_area_label() - self.add_surface_area_label() - - def add_plane(self): - plane = self.plane = Rectangle( - width=FRAME_WIDTH, - height=24.2, - stroke_width=0, - fill_color=WHITE, - fill_opacity=0.35, - ) - plane.set_sheen(0.2, DR) - grid = NumberPlane( - color=LIGHT_GREY, - secondary_color=DARK_GREY, - y_radius=int(plane.get_height() / 2), - stroke_width=1, - secondary_line_ratio=0, - ) - plane.add(grid) - plane.add(VectorizedPoint(10 * IN)) - plane.set_shade_in_3d(True, z_index_as_group=True) - self.add(plane) - - def setup_orientation_trackers(self): - # Euler angles - self.alpha_tracker = ValueTracker(0) - self.beta_tracker = ValueTracker(0) - self.gamma_tracker = ValueTracker(0) - - def setup_object_and_shadow(self): - self.obj3d = always_redraw(self.get_reoriented_object) - self.shadow = always_redraw(lambda: get_shadow(self.obj3d)) - - def add_shadow_area_label(self): - text = TextMobject("Shadow area: ") - decimal = DecimalNumber(0) - label = VGroup(text, decimal) - label.arrange(RIGHT) - label.scale(1.5) - label.move_to(self.area_label_center - decimal.get_center()) - self.shadow_area_label = label - self.shadow_area_decimal = decimal - - # def update_decimal(decimal): - # # decimal.set_value(get_area(self.shadow)) - # self.add_fixed_in_frame_mobjects(decimal) - - # decimal.add_updater(update_decimal) - decimal.add_updater( - lambda d: d.set_value(get_area(self.shadow)) - ) - decimal.add_updater( - lambda d: self.add_fixed_in_frame_mobjects(d) - ) - - # self.add_fixed_orientation_mobjects(label) - self.add_fixed_in_frame_mobjects(label) - self.add(label) - self.add(decimal) - - def add_surface_area_label(self): - text = TextMobject("Surface area: ") - decimal = DecimalNumber(self.surface_area) - label = VGroup(text, decimal) - label.arrange(RIGHT) - label.scale(1.25) - label.set_fill(YELLOW) - label.set_background_stroke(width=3) - label.next_to(self.obj3d, RIGHT, LARGE_BUFF) - label.shift(MED_LARGE_BUFF * IN) - self.surface_area_label = label - self.add_fixed_orientation_mobjects(label) - - def construct(self): - # Show creation - obj3d = self.obj3d.copy() - obj3d.clear_updaters() - temp_shadow = always_redraw(lambda: get_shadow(obj3d)) - self.add(temp_shadow) - self.move_camera( - **self.initial_orientation_config, - added_anims=[ - LaggedStartMap(DrawBorderThenFill, obj3d) - ], - run_time=2 - ) - self.begin_ambient_camera_rotation(0.01) - self.remove(obj3d, temp_shadow) - - average_label = self.get_average_label() - # Reorient - self.add(self.obj3d, self.shadow) - for n in range(self.num_reorientations): - self.randomly_reorient() - if n == 3: - self.add_fixed_in_frame_mobjects(average_label) - self.play(Write(average_label, run_time=2)) - else: - self.wait() - - def randomly_reorient(self, run_time=3): - a, b, c = TAU * np.random.random(3) - self.play( - self.alpha_tracker.set_value, a, - self.beta_tracker.set_value, b, - self.gamma_tracker.set_value, c, - run_time=run_time - ) - - # - def get_object(self): - cube = Cube() - cube.set_height(1) - # cube.set_width(2, stretch=True) - cube.set_stroke(WHITE, 0.5) - return cube - - def get_reoriented_object(self): - obj3d = self.get_object() - angles = [ - self.alpha_tracker.get_value(), - self.beta_tracker.get_value(), - self.gamma_tracker.get_value(), - ] - vects = [OUT, RIGHT, OUT] - - center = self.object_center - obj3d.move_to(center) - for angle, vect in zip(angles, vects): - obj3d.rotate(angle, vect, about_point=center) - return obj3d - - def get_average_label(self): - rect = SurroundingRectangle( - self.shadow_area_decimal, - buff=SMALL_BUFF, - color=RED, - ) - words = TextMobject( - "Average", "=", - "$\\frac{\\text{Surface area}}{4}$" - ) - words.scale(1.5) - words[0].match_color(rect) - words[2].set_color(self.surface_area_label[0].get_fill_color()) - words.set_background_stroke(width=3) - words.next_to( - rect, DOWN, - index_of_submobject_to_align=0, - ) - # words.shift(MED_LARGE_BUFF * LEFT) - return VGroup(rect, words) - - -class ShowInfinitelyFarLightSource(ShowShadows): - CONFIG = { - "num_reorientations": 1, - "camera_center": [0, 0, 1], - } - - def construct(self): - self.force_skipping() - ShowShadows.construct(self) - self.revert_to_original_skipping_status() - - self.add_light_source_based_shadow_updater() - self.add_light() - self.move_light_around() - self.show_vertical_lines() - - def add_light(self): - light = self.light = self.get_light() - light_source = self.camera.light_source - light.move_to(light_source) - light_source.add_updater(lambda m: m.move_to(light)) - self.add(light_source) - self.add_fixed_orientation_mobjects(light) - - def move_light_around(self): - light = self.light - self.add_foreground_mobjects(self.shadow_area_label) - self.play( - light.move_to, 5 * OUT + DOWN, - run_time=3 - ) - self.play(Rotating( - light, angle=TAU, about_point=5 * OUT, - rate_func=smooth, run_time=3 - )) - self.play( - light.move_to, 30 * OUT, - run_time=3, - ) - self.remove(light) - - def show_vertical_lines(self): - lines = self.get_vertical_lines() - obj3d = self.obj3d - shadow = self.shadow - target_obj3d = obj3d.copy() - target_obj3d.become(shadow) - target_obj3d.match_style(obj3d) - target_obj3d.set_shade_in_3d(False) - source_obj3d = obj3d.copy() - source_obj3d.set_shade_in_3d(False) - source_obj3d.fade(1) - - self.play(LaggedStartMap(ShowCreation, lines)) - self.wait() - self.add(source_obj3d, lines) - self.play( - ReplacementTransform(source_obj3d, target_obj3d), - run_time=2 - ) - self.add(target_obj3d, lines) - self.play(FadeOut(target_obj3d),) - self.wait() - lines.add_updater(lambda m: m.become(self.get_vertical_lines())) - for x in range(5): - self.randomly_reorient() - - def add_light_source_based_shadow_updater(self): - shadow = self.shadow - light_source = self.camera.light_source - obj3d = self.obj3d - center = obj3d.get_center() - - def update(shadow): - lsp = light_source.get_center() - proj_center = get_xy_plane_projection_point(lsp, center) - c_to_lsp = lsp - center - unit_c_to_lsp = normalize(c_to_lsp) - rotation = rotation_matrix( - angle=np.arccos(np.dot(unit_c_to_lsp, OUT)), - axis=normalize(np.cross(unit_c_to_lsp, OUT)) - ) - new_shadow = get_shadow( - self.obj3d.copy().apply_matrix(rotation) - ) - shadow.become(new_shadow) - shadow.scale(get_norm(lsp) / get_norm(c_to_lsp)) - shadow.move_to(proj_center) - return shadow - shadow.add_updater(update) - - def get_light(self): - n_rings = 40 - radii = np.linspace(0, 2, n_rings) - rings = VGroup(*[ - Annulus(inner_radius=r1, outer_radius=r2) - for r1, r2 in zip(radii, radii[1:]) - ]) - opacities = np.linspace(1, 0, n_rings)**1.5 - for opacity, ring in zip(opacities, rings): - ring.set_fill(YELLOW, opacity) - ring.set_stroke(YELLOW, width=0.1, opacity=opacity) - return rings - - def get_vertical_lines(self): - shadow = self.shadow - points = get_boundary_points(shadow, 10) - # half_points = [(p1 + p2) / 2 for p1, p2 in adjacent_pairs(points)] - # points = np.append(points, half_points, axis=0) - light_source = self.light.get_center() - lines = VGroup(*[ - DashedLine(light_source, point) - for point in points - ]) - lines.set_shade_in_3d(True) - for line in lines: - line.remove(*line[:int(0.8 * len(line))]) - line[-10:].set_shade_in_3d(False) - line.set_stroke(YELLOW, 1) - return lines - - -class CylinderShadows(ShowShadows): - CONFIG = { - "surface_area": 2 * PI + 2 * PI * 2, - "area_label_center": [0, -2, 0], - } - - def get_object(self): - height = 2 - cylinder = ParametricSurface( - lambda u, v: np.array([ - np.cos(TAU * v), - np.sin(TAU * v), - height * (1 - u) - ]), - resolution=(6, 32) - ) - # circle = Circle(radius=1) - circle = ParametricSurface( - lambda u, v: np.array([ - (v + 0.01) * np.cos(TAU * u), - (v + 0.01) * np.sin(TAU * u), - 0, - ]), - resolution=(16, 8) - ) - # circle.set_fill(GREEN, opacity=0.5) - for surface in cylinder, circle: - surface.set_fill_by_checkerboard(GREEN, GREEN_E, opacity=1.0) - # surface.set_fill(GREEN, opacity=0.5) - cylinder.add(circle) - cylinder.add(circle.copy().flip().move_to(height * OUT)) - cylinder.set_shade_in_3d(True) - cylinder.set_stroke(width=0) - cylinder.scale(1.003) - return cylinder - - -class PrismShadows(ShowShadows): - CONFIG = { - "surface_area": 3 * np.sqrt(3) / 2 + 3 * (np.sqrt(3) * 2), - "object_center": [0, 0, 3], - "area_label_center": [0, -2.25, 0], - } - - def get_object(self): - height = 2 - prism = VGroup() - triangle = RegularPolygon(3) - verts = triangle.get_anchors()[:3] - rects = [ - Polygon(v1, v2, v2 + height * OUT, v1 + height * OUT) - for v1, v2 in adjacent_pairs(verts) - ] - prism.add(triangle, *rects) - prism.add(triangle.copy().shift(height * OUT)) - triangle.reverse_points() - prism.set_shade_in_3d(True) - prism.set_fill(PINK, 0.8) - prism.set_stroke(WHITE, 1) - return prism - - -class TheseFourPiAreSquare(PiCreatureScene): - def construct(self): - pass - - def create_pi_creatures(self): - pass diff --git a/logo/logo.py b/logo/logo.py index 14b485d7..f44da45d 100644 --- a/logo/logo.py +++ b/logo/logo.py @@ -60,10 +60,10 @@ class Thumbnail(GraphScene): triangle.scale(0.1) # - x_label_p1 = TexMobject("a") - output_label_p1 = TexMobject("f(a)") - x_label_p2 = TexMobject("b") - output_label_p2 = TexMobject("f(b)") + x_label_p1 = Tex("a") + output_label_p1 = Tex("f(a)") + x_label_p2 = Tex("b") + output_label_p2 = Tex("f(b)") v_line_p1 = get_v_line(input_tracker_p1) v_line_p2 = get_v_line(input_tracker_p2) h_line_p1 = get_h_line(input_tracker_p1) @@ -170,7 +170,7 @@ class Thumbnail(GraphScene): # adding manim picture = Group(*self.mobjects) picture.scale(0.6).to_edge(LEFT, buff=SMALL_BUFF) - manim = TextMobject("Manim").set_height(1.5) \ + manim = TexText("Manim").set_height(1.5) \ .next_to(picture, RIGHT) \ .shift(DOWN * 0.7) self.add(manim) diff --git a/logo/transparent_graph.png b/logo/transparent_graph.png new file mode 100644 index 00000000..61d5a25b Binary files /dev/null and b/logo/transparent_graph.png differ diff --git a/logo/white_with_name.png b/logo/white_with_name.png new file mode 100644 index 00000000..63ee0a82 Binary files /dev/null and b/logo/white_with_name.png differ diff --git a/manim.py b/manim.py deleted file mode 100755 index 2bebaea6..00000000 --- a/manim.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python -import manimlib - -if __name__ == "__main__": - manimlib.main() diff --git a/manim_docker_diagram.png b/manim_docker_diagram.png deleted file mode 100644 index 44a6d740..00000000 Binary files a/manim_docker_diagram.png and /dev/null differ diff --git a/manimlib/__init__.py b/manimlib/__init__.py index 4c8d7331..b7262f2f 100644 --- a/manimlib/__init__.py +++ b/manimlib/__init__.py @@ -1,14 +1,65 @@ -#!/usr/bin/env python -import manimlib.config -import manimlib.constants -import manimlib.extract_scene +from manimlib.constants import * +from manimlib.animation.animation import * +from manimlib.animation.composition import * +from manimlib.animation.creation import * +from manimlib.animation.fading import * +from manimlib.animation.growing import * +from manimlib.animation.indication import * +from manimlib.animation.movement import * +from manimlib.animation.numbers import * +from manimlib.animation.rotation import * +from manimlib.animation.specialized import * +from manimlib.animation.transform import * +from manimlib.animation.transform_matching_parts import * +from manimlib.animation.update import * -def main(): - args = manimlib.config.parse_cli() - config = manimlib.config.get_configuration(args) - manimlib.constants.initialize_directories(config) - scenes = manimlib.extract_scene.main(config) +from manimlib.camera.camera import * - for scene in scenes: - scene.run() +from manimlib.window import * + +from manimlib.mobject.coordinate_systems import * +from manimlib.mobject.changing import * +from manimlib.mobject.frame import * +from manimlib.mobject.functions import * +from manimlib.mobject.geometry import * +from manimlib.mobject.interactive import * +from manimlib.mobject.matrix import * +from manimlib.mobject.mobject import * +from manimlib.mobject.number_line import * +from manimlib.mobject.numbers import * +from manimlib.mobject.probability import * +from manimlib.mobject.shape_matchers import * +from manimlib.mobject.svg.brace import * +from manimlib.mobject.svg.drawings import * +from manimlib.mobject.svg.svg_mobject import * +from manimlib.mobject.svg.tex_mobject import * +from manimlib.mobject.svg.text_mobject import * +from manimlib.mobject.three_dimensions import * +from manimlib.mobject.types.image_mobject import * +from manimlib.mobject.types.point_cloud_mobject import * +from manimlib.mobject.types.surface import * +from manimlib.mobject.types.vectorized_mobject import * +from manimlib.mobject.types.dot_cloud import * +from manimlib.mobject.mobject_update_utils import * +from manimlib.mobject.value_tracker import * +from manimlib.mobject.vector_field import * + +from manimlib.scene.scene import * +from manimlib.scene.three_d_scene import * + +from manimlib.utils.bezier import * +from manimlib.utils.color import * +from manimlib.utils.config_ops import * +from manimlib.utils.customization import * +from manimlib.utils.debug import * +from manimlib.utils.directories import * +from manimlib.utils.images import * +from manimlib.utils.iterables import * +from manimlib.utils.file_ops import * +from manimlib.utils.paths import * +from manimlib.utils.rate_functions import * +from manimlib.utils.simple_functions import * +from manimlib.utils.sounds import * +from manimlib.utils.space_ops import * +from manimlib.utils.strings import * diff --git a/manimlib/__main__.py b/manimlib/__main__.py new file mode 100644 index 00000000..ebd342fe --- /dev/null +++ b/manimlib/__main__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +import manimlib.config +import manimlib.extract_scene +import manimlib.utils.init_config + + +def main(): + args = manimlib.config.parse_cli() + + if args.config: + manimlib.utils.init_config.init_customization() + else: + config = manimlib.config.get_configuration(args) + scenes = manimlib.extract_scene.main(config) + + for scene in scenes: + scene.run() diff --git a/manimlib/animation/__init__.py b/manimlib/animation/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manimlib/animation/animation.py b/manimlib/animation/animation.py index ec49a226..8a6cd8c9 100644 --- a/manimlib/animation/animation.py +++ b/manimlib/animation/animation.py @@ -1,5 +1,6 @@ from copy import deepcopy +from manimlib.mobject.mobject import _AnimationBuilder from manimlib.mobject.mobject import Mobject from manimlib.utils.config_ops import digest_config from manimlib.utils.rate_functions import smooth @@ -17,13 +18,15 @@ class Animation(object): "name": None, # Does this animation add or remove a mobject form the screen "remover": False, + # What to enter into the update function upon completion + "final_alpha_value": 1, # If 0, the animation is applied to all submobjects # at the same time # If 1, it is applied to each successively. # If 0 < lag_ratio < 1, its applied to each # with lagged start times "lag_ratio": DEFAULT_ANIMATION_LAG_RATIO, - "suspend_mobject_updating": True, + "suspend_mobject_updating": False, } def __init__(self, mobject, **kwargs): @@ -41,7 +44,6 @@ class Animation(object): # played. As much initialization as possible, # especially any mobject copying, should live in # this method - self.mobject.prepare_for_animation() self.starting_mobject = self.create_starting_mobject() if self.suspend_mobject_updating: # All calls to self.mobject's internal updaters @@ -55,8 +57,7 @@ class Animation(object): self.interpolate(0) def finish(self): - self.interpolate(1) - self.mobject.cleanup_from_animation() + self.interpolate(self.final_alpha_value) if self.suspend_mobject_updating: self.mobject.resume_updating() @@ -76,7 +77,7 @@ class Animation(object): def get_all_families_zipped(self): return zip(*[ - mob.family_members_with_points() + mob.get_family() for mob in self.get_all_mobjects() ]) @@ -159,3 +160,13 @@ class Animation(object): def is_remover(self): return self.remover + + +def prepare_animation(anim): + if isinstance(anim, _AnimationBuilder): + return anim.build() + + if isinstance(anim, Animation): + return anim + + raise TypeError(f"Object {anim} cannot be converted to an animation") diff --git a/manimlib/animation/composition.py b/manimlib/animation/composition.py index c120f60b..ba175dce 100644 --- a/manimlib/animation/composition.py +++ b/manimlib/animation/composition.py @@ -1,6 +1,6 @@ import numpy as np -from manimlib.animation.animation import Animation +from manimlib.animation.animation import Animation, prepare_animation from manimlib.mobject.mobject import Group from manimlib.utils.bezier import integer_interpolate from manimlib.utils.bezier import interpolate @@ -29,7 +29,7 @@ class AnimationGroup(Animation): def __init__(self, *animations, **kwargs): digest_config(self, kwargs) - self.animations = animations + self.animations = [prepare_animation(anim) for anim in animations] if self.group is None: self.group = Group(*remove_list_redundancies( [anim.mobject for anim in animations] diff --git a/manimlib/animation/creation.py b/manimlib/animation/creation.py index 155a1465..6a69815f 100644 --- a/manimlib/animation/creation.py +++ b/manimlib/animation/creation.py @@ -16,6 +16,18 @@ class ShowPartial(Animation): """ Abstract class for ShowCreation and ShowPassingFlash """ + CONFIG = { + "should_match_start": False, + } + + def begin(self): + super().begin() + if not self.should_match_start: + self.mobject.lock_matching_data(self.mobject, self.starting_mobject) + + def finish(self): + super().finish() + self.mobject.unlock_data() def interpolate_submobject(self, submob, start_submob, alpha): submob.pointwise_become_partial( @@ -38,7 +50,8 @@ class ShowCreation(ShowPartial): class Uncreate(ShowCreation): CONFIG = { "rate_func": lambda t: smooth(1 - t), - "remover": True + "remover": True, + "should_match_start": True, } @@ -53,26 +66,34 @@ class DrawBorderThenFill(Animation): } def __init__(self, vmobject, **kwargs): - self.check_validity_of_input(vmobject) + assert(isinstance(vmobject, VMobject)) + self.sm_to_index = dict([ + (hash(sm), 0) + for sm in vmobject.get_family() + ]) super().__init__(vmobject, **kwargs) - def check_validity_of_input(self, vmobject): - if not isinstance(vmobject, VMobject): - raise Exception( - "DrawBorderThenFill only works for VMobjects" - ) - def begin(self): + # Trigger triangulation calculation + for submob in self.mobject.get_family(): + submob.get_triangulation() + self.outline = self.get_outline() super().begin() + self.mobject.match_style(self.outline) + self.mobject.lock_matching_data(self.mobject, self.outline) + + def finish(self): + super().finish() + self.mobject.unlock_data() def get_outline(self): outline = self.mobject.copy() outline.set_fill(opacity=0) - for sm in outline.family_members_with_points(): + for sm in outline.get_family(): sm.set_stroke( color=self.get_stroke_color(sm), - width=self.stroke_width + width=float(self.stroke_width) ) return outline @@ -88,11 +109,19 @@ class DrawBorderThenFill(Animation): def interpolate_submobject(self, submob, start, outline, alpha): index, subalpha = integer_interpolate(0, 2, alpha) + + if index == 1 and self.sm_to_index[hash(submob)] == 0: + # First time crossing over + submob.set_data(outline.data) + submob.unlock_data() + if not self.mobject.has_updaters: + submob.lock_matching_data(submob, start) + submob.needs_new_triangulation = False + self.sm_to_index[hash(submob)] = 1 + if index == 0: submob.pointwise_become_partial(outline, 0, subalpha) - submob.match_style(outline) else: - submob.pointwise_become_partial(outline, 0, 1) submob.interpolate(outline, start, subalpha) @@ -124,7 +153,7 @@ class Write(DrawBorderThenFill): class ShowIncreasingSubsets(Animation): CONFIG = { "suspend_mobject_updating": False, - "int_func": np.floor, + "int_func": np.round, } def __init__(self, group, **kwargs): diff --git a/manimlib/animation/fading.py b/manimlib/animation/fading.py index 9f5ea190..2263b00d 100644 --- a/manimlib/animation/fading.py +++ b/manimlib/animation/fading.py @@ -1,8 +1,9 @@ +import numpy as np + from manimlib.animation.animation import Animation -from manimlib.animation.animation import DEFAULT_ANIMATION_LAG_RATIO from manimlib.animation.transform import Transform -from manimlib.constants import DOWN -from manimlib.mobject.types.vectorized_mobject import VMobject +from manimlib.mobject.mobject import Group +from manimlib.constants import ORIGIN from manimlib.utils.bezier import interpolate from manimlib.utils.rate_functions import there_and_back @@ -10,21 +11,18 @@ from manimlib.utils.rate_functions import there_and_back DEFAULT_FADE_LAG_RATIO = 0 -class FadeOut(Transform): +class Fade(Transform): CONFIG = { - "remover": True, "lag_ratio": DEFAULT_FADE_LAG_RATIO, } - def create_target(self): - return self.mobject.copy().fade(1) - - def clean_up_from_scene(self, scene=None): - super().clean_up_from_scene(scene) - self.interpolate(0) + def __init__(self, mobject, shift=ORIGIN, scale=1, **kwargs): + self.shift_vect = shift + self.scale_factor = scale + super().__init__(mobject, **kwargs) -class FadeIn(Transform): +class FadeIn(Fade): CONFIG = { "lag_ratio": DEFAULT_FADE_LAG_RATIO, } @@ -34,96 +32,100 @@ class FadeIn(Transform): def create_starting_mobject(self): start = super().create_starting_mobject() - start.fade(1) - if isinstance(start, VMobject): - start.set_stroke(width=0) - start.set_fill(opacity=0) + start.set_opacity(0) + start.scale(1.0 / self.scale_factor) + start.shift(-self.shift_vect) return start -class FadeInFrom(Transform): +class FadeOut(Fade): CONFIG = { - "direction": DOWN, - "lag_ratio": DEFAULT_ANIMATION_LAG_RATIO, + "remover": True, + # Put it back in original state when done + "final_alpha_value": 0, } - def __init__(self, mobject, direction=None, **kwargs): - if direction is not None: - self.direction = direction - super().__init__(mobject, **kwargs) - def create_target(self): - return self.mobject.copy() - - def begin(self): - super().begin() - self.starting_mobject.shift(self.direction) - self.starting_mobject.fade(1) - - -class FadeInFromDown(FadeInFrom): - """ - Identical to FadeInFrom, just with a name that - communicates the default - """ - CONFIG = { - "direction": DOWN, - "lag_ratio": DEFAULT_ANIMATION_LAG_RATIO, - } - - -class FadeOutAndShift(FadeOut): - CONFIG = { - "direction": DOWN, - } - - def __init__(self, mobject, direction=None, **kwargs): - if direction is not None: - self.direction = direction - super().__init__(mobject, **kwargs) - - def create_target(self): - target = super().create_target() - target.shift(self.direction) - return target - - -class FadeOutAndShiftDown(FadeOutAndShift): - """ - Identical to FadeOutAndShift, just with a name that - communicates the default - """ - CONFIG = { - "direction": DOWN, - } + result = self.mobject.copy() + result.set_opacity(0) + result.shift(self.shift_vect) + result.scale(self.scale_factor) + return result class FadeInFromPoint(FadeIn): def __init__(self, mobject, point, **kwargs): - self.point = point - super().__init__(mobject, **kwargs) - - def create_starting_mobject(self): - start = super().create_starting_mobject() - start.scale(0) - start.move_to(self.point) - return start + super().__init__( + mobject, + shift=mobject.get_center() - point, + scale=np.inf, + **kwargs, + ) -class FadeInFromLarge(FadeIn): +class FadeOutToPoint(FadeOut): + def __init__(self, mobject, point, **kwargs): + super().__init__( + mobject, + shift=point - mobject.get_center(), + scale=0, + **kwargs, + ) + + +class FadeTransform(Transform): CONFIG = { - "scale_factor": 2, + "stretch": True, + "dim_to_match": 1, } - def __init__(self, mobject, scale_factor=2, **kwargs): - if scale_factor is not None: - self.scale_factor = scale_factor - super().__init__(mobject, **kwargs) + def __init__(self, mobject, target_mobject, **kwargs): + self.to_add_on_completion = target_mobject + mobject.save_state() + super().__init__( + Group(mobject, target_mobject.copy()), + **kwargs + ) - def create_starting_mobject(self): - start = super().create_starting_mobject() - start.scale(self.scale_factor) - return start + def begin(self): + self.ending_mobject = self.mobject.copy() + Animation.begin(self) + # Both 'start' and 'end' consists of the source and target mobjects. + # At the start, the traget should be faded replacing the source, + # and at the end it should be the other way around. + start, end = self.starting_mobject, self.ending_mobject + for m0, m1 in ((start[1], start[0]), (end[0], end[1])): + self.ghost_to(m0, m1) + + def ghost_to(self, source, target): + source.replace(target, stretch=self.stretch, dim_to_match=self.dim_to_match) + source.set_opacity(0) + + def get_all_mobjects(self): + return [ + self.mobject, + self.starting_mobject, + self.ending_mobject, + ] + + def get_all_families_zipped(self): + return Animation.get_all_families_zipped(self) + + def clean_up_from_scene(self, scene): + Animation.clean_up_from_scene(self, scene) + scene.remove(self.mobject) + self.mobject[0].restore() + scene.add(self.to_add_on_completion) + + +class FadeTransformPieces(FadeTransform): + def begin(self): + self.mobject[0].align_family(self.mobject[1]) + super().begin() + + def ghost_to(self, source, target): + for sm0, sm1 in zip(source.get_family(), target.get_family()): + super().ghost_to(sm0, sm1) class VFadeIn(Animation): @@ -145,7 +147,9 @@ class VFadeIn(Animation): class VFadeOut(VFadeIn): CONFIG = { - "remover": True + "remover": True, + # Put it back in original state when done + "final_alpha_value": 0, } def interpolate_submobject(self, submob, start, alpha): @@ -156,4 +160,6 @@ class VFadeInThenOut(VFadeIn): CONFIG = { "rate_func": there_and_back, "remover": True, + # Put it back in original state when done + "final_alpha_value": 0.5, } diff --git a/manimlib/animation/indication.py b/manimlib/animation/indication.py index 62026e63..0c425b7f 100644 --- a/manimlib/animation/indication.py +++ b/manimlib/animation/indication.py @@ -1,4 +1,5 @@ import numpy as np +import math from manimlib.constants import * from manimlib.animation.animation import Animation @@ -13,6 +14,7 @@ from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.geometry import Circle from manimlib.mobject.geometry import Dot from manimlib.mobject.shape_matchers import SurroundingRectangle +from manimlib.mobject.shape_matchers import Underline from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.geometry import Line from manimlib.utils.bezier import interpolate @@ -61,7 +63,7 @@ class Indicate(Transform): def create_target(self): target = self.mobject.copy() - target.scale_in_place(self.scale_factor) + target.scale(self.scale_factor) target.set_color(self.color) return target @@ -152,6 +154,87 @@ class ShowPassingFlash(ShowPartial): submob.pointwise_become_partial(start, 0, 1) +class VShowPassingFlash(Animation): + CONFIG = { + "time_width": 0.3, + "taper_width": 0.02, + "remover": True, + } + + def begin(self): + self.mobject.align_stroke_width_data_to_points() + # Compute an array of stroke widths for each submobject + # which tapers out at either end + self.submob_to_anchor_widths = dict() + for sm in self.mobject.get_family(): + original_widths = sm.get_stroke_widths() + anchor_widths = np.array([*original_widths[0::3], original_widths[-1]]) + + def taper_kernel(x): + if x < self.taper_width: + return x + elif x > 1 - self.taper_width: + return 1.0 - x + return 1.0 + + taper_array = list(map(taper_kernel, np.linspace(0, 1, len(anchor_widths)))) + self.submob_to_anchor_widths[hash(sm)] = anchor_widths * taper_array + super().begin() + + def interpolate_submobject(self, submobject, starting_sumobject, alpha): + anchor_widths = self.submob_to_anchor_widths[hash(submobject)] + # Create a gaussian such that 3 sigmas out on either side + # will equals time_width + tw = self.time_width + sigma = tw / 6 + mu = interpolate(-tw / 2, 1 + tw / 2, alpha) + + def gauss_kernel(x): + if abs(x - mu) > 3 * sigma: + return 0 + z = (x - mu) / sigma + return math.exp(-0.5 * z * z) + + kernel_array = list(map(gauss_kernel, np.linspace(0, 1, len(anchor_widths)))) + scaled_widths = anchor_widths * kernel_array + new_widths = np.zeros(submobject.get_num_points()) + new_widths[0::3] = scaled_widths[:-1] + new_widths[2::3] = scaled_widths[1:] + new_widths[1::3] = (new_widths[0::3] + new_widths[2::3]) / 2 + submobject.set_stroke(width=new_widths) + + def finish(self): + super().finish() + for submob, start in self.get_all_families_zipped(): + submob.match_style(start) + + +class FlashAround(VShowPassingFlash): + CONFIG = { + "stroke_width": 4.0, + "color": YELLOW, + "buff": SMALL_BUFF, + "time_width": 1.0, + "n_inserted_curves": 20, + } + + def __init__(self, mobject, **kwargs): + digest_config(self, kwargs) + path = self.get_path(mobject) + path.insert_n_curves(self.n_inserted_curves) + path.set_points(path.get_points_without_null_curves()) + path.set_stroke(self.color, self.stroke_width) + super().__init__(path, **kwargs) + + def get_path(self, mobject): + return SurroundingRectangle(mobject, buff=self.buff) + + +class FlashUnder(FlashAround): + def get_path(self, mobject): + return Underline(mobject, buff=self.buff) + + class ShowCreationThenDestruction(ShowPassingFlash): CONFIG = { "time_width": 2.0, @@ -260,7 +343,7 @@ class WiggleOutThenIn(Animation): return self.mobject.get_center() def interpolate_submobject(self, submobject, starting_sumobject, alpha): - submobject.points[:, :] = starting_sumobject.points + submobject.match_points(starting_sumobject) submobject.scale( interpolate(1, self.scale_value, there_and_back(alpha)), about_point=self.get_scale_about_point() diff --git a/manimlib/animation/movement.py b/manimlib/animation/movement.py index f6a3ec05..d1cea65e 100644 --- a/manimlib/animation/movement.py +++ b/manimlib/animation/movement.py @@ -20,7 +20,7 @@ class Homotopy(Animation): return lambda p: self.homotopy(*p, t) def interpolate_submobject(self, submob, start, alpha): - submob.points = start.points + submob.match_points(start) submob.apply_function( self.function_at_time_t(alpha), **self.apply_function_kwargs @@ -28,20 +28,22 @@ class Homotopy(Animation): class SmoothedVectorizedHomotopy(Homotopy): - def interpolate_submobject(self, submob, start, alpha): - Homotopy.interpolate_submobject(self, submob, start, alpha) - submob.make_smooth() + CONFIG = { + "apply_function_kwargs": {"make_smooth": True}, + } class ComplexHomotopy(Homotopy): def __init__(self, complex_homotopy, mobject, **kwargs): """ - Complex Hootopy a function Cx[0, 1] to C + Given a function form (z, t) -> w, where z and w + are complex numbers and t is time, this animates + the state over time """ def homotopy(x, y, z, t): c = complex_homotopy(complex(x, y), t) return (c.real, c.imag, z) - Homotopy.__init__(self, homotopy, mobject, **kwargs) + super().__init__(homotopy, mobject, **kwargs) class PhaseFlow(Animation): diff --git a/manimlib/animation/numbers.py b/manimlib/animation/numbers.py index 3f999242..1cbd2489 100644 --- a/manimlib/animation/numbers.py +++ b/manimlib/animation/numbers.py @@ -1,5 +1,3 @@ -import warnings - from manimlib.animation.animation import Animation from manimlib.mobject.numbers import DecimalNumber from manimlib.utils.bezier import interpolate @@ -11,31 +9,10 @@ class ChangingDecimal(Animation): } def __init__(self, decimal_mob, number_update_func, **kwargs): - self.check_validity_of_input(decimal_mob) - self.yell_about_depricated_configuration(**kwargs) + assert(isinstance(decimal_mob, DecimalNumber)) self.number_update_func = number_update_func super().__init__(decimal_mob, **kwargs) - def check_validity_of_input(self, decimal_mob): - if not isinstance(decimal_mob, DecimalNumber): - raise Exception( - "ChangingDecimal can only take " - "in a DecimalNumber" - ) - - def yell_about_depricated_configuration(self, **kwargs): - # Obviously this would optimally be removed at - # some point. - for attr in ["tracked_mobject", "position_update_func"]: - if attr in kwargs: - warnings.warn(""" - Don't use {} for ChangingDecimal, - that functionality has been depricated - and you should use a mobject updater - instead - """.format(attr) - ) - def interpolate_mobject(self, alpha): self.mobject.set_value( self.number_update_func(alpha) diff --git a/manimlib/animation/rotation.py b/manimlib/animation/rotation.py index 3fc4263e..a37fb0b6 100644 --- a/manimlib/animation/rotation.py +++ b/manimlib/animation/rotation.py @@ -1,8 +1,8 @@ from manimlib.animation.animation import Animation -from manimlib.animation.transform import Transform from manimlib.constants import OUT from manimlib.constants import PI from manimlib.constants import TAU +from manimlib.constants import ORIGIN from manimlib.utils.rate_functions import linear from manimlib.utils.rate_functions import smooth @@ -25,7 +25,7 @@ class Rotating(Animation): def interpolate_mobject(self, alpha): for sm1, sm2 in self.get_all_families_zipped(): - sm1.points[:] = sm2.points + sm1.set_points(sm2.get_points()) self.mobject.rotate( alpha * self.angle, axis=self.axis, @@ -38,6 +38,7 @@ class Rotate(Rotating): CONFIG = { "run_time": 1, "rate_func": smooth, + "about_edge": ORIGIN, } def __init__(self, mobject, angle=PI, axis=OUT, **kwargs): diff --git a/manimlib/animation/specialized.py b/manimlib/animation/specialized.py index c4f5457d..a0812b4c 100644 --- a/manimlib/animation/specialized.py +++ b/manimlib/animation/specialized.py @@ -1,51 +1,10 @@ -import operator as op - from manimlib.animation.composition import LaggedStart -from manimlib.animation.transform import ApplyMethod from manimlib.animation.transform import Restore from manimlib.constants import WHITE from manimlib.constants import BLACK from manimlib.mobject.geometry import Circle -from manimlib.mobject.svg.drawings import Car from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.utils.config_ops import digest_config -from manimlib.utils.space_ops import get_norm - - -class MoveCar(ApplyMethod): - CONFIG = { - "moving_forward": True, - "run_time": 5, - } - - def __init__(self, car, target_point, **kwargs): - self.check_if_input_is_car(car) - self.target_point = target_point - super().__init__(car.move_to, target_point, **kwargs) - - def check_if_input_is_car(self, car): - if not isinstance(car, Car): - raise Exception("MoveCar must take in Car object") - - def begin(self): - super().begin() - car = self.mobject - distance = get_norm(op.sub( - self.target_mobject.get_right(), - self.starting_mobject.get_right(), - )) - if not self.moving_forward: - distance *= -1 - tire_radius = car.get_tires()[0].get_width() / 2 - self.total_tire_radians = -distance / tire_radius - - def interpolate_mobject(self, alpha): - ApplyMethod.interpolate_mobject(self, alpha) - if alpha == 0: - return - radians = alpha * self.total_tire_radians - for tire in self.mobject.get_tires(): - tire.rotate_in_place(radians) class Broadcast(LaggedStart): diff --git a/manimlib/animation/transform.py b/manimlib/animation/transform.py index ef07090c..d76047ff 100644 --- a/manimlib/animation/transform.py +++ b/manimlib/animation/transform.py @@ -40,16 +40,23 @@ class Transform(Animation): ) def begin(self): - # Use a copy of target_mobject for the align_data - # call so that the actual target_mobject stays - # preserved. self.target_mobject = self.create_target() self.check_target_mobject_validity() + # Use a copy of target_mobject for the align_data_and_family + # call so that the actual target_mobject stays + # preserved, since calling allign_data will potentailly + # change the structure of both arguments self.target_copy = self.target_mobject.copy() - # Note, this potentially changes the structure - # of both mobject and target_mobject - self.mobject.align_data(self.target_copy) + self.mobject.align_data_and_family(self.target_copy) super().begin() + self.mobject.lock_matching_data( + self.starting_mobject, + self.target_copy, + ) + + def finish(self): + super().finish() + self.mobject.unlock_data() def create_target(self): # Has no meaningful effect here, but may be useful @@ -58,9 +65,8 @@ class Transform(Animation): def check_target_mobject_validity(self): if self.target_mobject is None: - message = "{}.create_target not properly implemented" raise Exception( - message.format(self.__class__.__name__) + f"{self.__class__.__name__}.create_target not properly implemented" ) def clean_up_from_scene(self, scene): @@ -87,7 +93,7 @@ class Transform(Animation): def get_all_families_zipped(self): return zip(*[ - mob.family_members_with_points() + mob.get_family() for mob in [ self.mobject, self.starting_mobject, @@ -96,10 +102,7 @@ class Transform(Animation): ]) def interpolate_submobject(self, submob, start, target_copy, alpha): - submob.interpolate( - start, target_copy, - alpha, self.path_func - ) + submob.interpolate(start, target_copy, alpha, self.path_func) return self @@ -146,6 +149,12 @@ class MoveToTarget(Transform): ) +class _MethodAnimation(MoveToTarget): + def __init__(self, mobject, methods): + self.methods = methods + super().__init__(mobject) + + class ApplyMethod(Transform): def __init__(self, method, *args, **kwargs): """ @@ -291,7 +300,7 @@ class Swap(CyclicReplace): pass # Renaming, more understandable for two entries -# TODO, this may be depricated...worth reimplementing? +# TODO, this may be deprecated...worth reimplementing? class TransformAnimations(Transform): CONFIG = { "rate_func": squish_rate_func(smooth) @@ -307,10 +316,10 @@ class TransformAnimations(Transform): anim.set_run_time(self.run_time) if start_anim.starting_mobject.get_num_points() != end_anim.starting_mobject.get_num_points(): - start_anim.starting_mobject.align_data(end_anim.starting_mobject) + start_anim.starting_mobject.align_data_and_family(end_anim.starting_mobject) for anim in start_anim, end_anim: if hasattr(anim, "target_mobject"): - anim.starting_mobject.align_data(anim.target_mobject) + anim.starting_mobject.align_data_and_family(anim.target_mobject) Transform.__init__(self, start_anim.mobject, end_anim.mobject, **kwargs) diff --git a/manimlib/animation/transform_matching_parts.py b/manimlib/animation/transform_matching_parts.py new file mode 100644 index 00000000..3ee228a9 --- /dev/null +++ b/manimlib/animation/transform_matching_parts.py @@ -0,0 +1,141 @@ +import numpy as np + +from manimlib.animation.composition import AnimationGroup +from manimlib.animation.fading import FadeTransformPieces +from manimlib.animation.fading import FadeInFromPoint +from manimlib.animation.fading import FadeOutToPoint +from manimlib.animation.transform import Transform + +from manimlib.mobject.mobject import Mobject +from manimlib.mobject.mobject import Group +from manimlib.mobject.types.vectorized_mobject import VGroup +from manimlib.mobject.types.vectorized_mobject import VMobject +from manimlib.utils.config_ops import digest_config + + +class TransformMatchingParts(AnimationGroup): + CONFIG = { + "mobject_type": Mobject, + "group_type": Group, + "transform_mismatches": False, + "fade_transform_mismatches": False, + "key_map": dict(), + } + + def __init__(self, mobject, target_mobject, **kwargs): + digest_config(self, kwargs) + assert(isinstance(mobject, self.mobject_type)) + assert(isinstance(target_mobject, self.mobject_type)) + source_map = self.get_shape_map(mobject) + target_map = self.get_shape_map(target_mobject) + + # Create two mobjects whose submobjects all match each other + # according to whatever keys are used for source_map and + # target_map + transform_source = self.group_type() + transform_target = self.group_type() + kwargs["final_alpha_value"] = 0 + for key in set(source_map).intersection(target_map): + transform_source.add(source_map[key]) + transform_target.add(target_map[key]) + anims = [Transform(transform_source, transform_target, **kwargs)] + # User can manually specify when one part should transform + # into another despite not matching by using key_map + key_mapped_source = self.group_type() + key_mapped_target = self.group_type() + for key1, key2 in self.key_map.items(): + if key1 in source_map and key2 in target_map: + key_mapped_source.add(source_map[key1]) + key_mapped_target.add(target_map[key2]) + source_map.pop(key1, None) + target_map.pop(key2, None) + if len(key_mapped_source) > 0: + anims.append(FadeTransformPieces( + key_mapped_source, + key_mapped_target, + )) + + fade_source = self.group_type() + fade_target = self.group_type() + for key in set(source_map).difference(target_map): + fade_source.add(source_map[key]) + for key in set(target_map).difference(source_map): + fade_target.add(target_map[key]) + + if self.transform_mismatches: + anims.append(Transform(fade_source.copy(), fade_target, **kwargs)) + if self.fade_transform_mismatches: + anims.append(FadeTransformPieces(fade_source, fade_target, **kwargs)) + else: + anims.append(FadeOutToPoint( + fade_source, fade_target.get_center(), **kwargs + )) + anims.append(FadeInFromPoint( + fade_target.copy(), fade_source.get_center(), **kwargs + )) + + super().__init__(*anims) + + self.to_remove = mobject + self.to_add = target_mobject + + def get_shape_map(self, mobject): + shape_map = {} + for sm in self.get_mobject_parts(mobject): + key = self.get_mobject_key(sm) + if key not in shape_map: + shape_map[key] = VGroup() + shape_map[key].add(sm) + return shape_map + + def clean_up_from_scene(self, scene): + for anim in self.animations: + anim.update(0) + scene.remove(self.mobject) + scene.remove(self.to_remove) + scene.add(self.to_add) + + @staticmethod + def get_mobject_parts(mobject): + # To be implemented in subclass + return mobject + + @staticmethod + def get_mobject_key(mobject): + # To be implemented in subclass + return hash(mobject) + + +class TransformMatchingShapes(TransformMatchingParts): + CONFIG = { + "mobject_type": VMobject, + "group_type": VGroup, + } + + @staticmethod + def get_mobject_parts(mobject): + return mobject.family_members_with_points() + + @staticmethod + def get_mobject_key(mobject): + mobject.save_state() + mobject.center() + mobject.set_height(1) + result = hash(np.round(mobject.get_points(), 3).tobytes()) + mobject.restore() + return result + + +class TransformMatchingTex(TransformMatchingParts): + CONFIG = { + "mobject_type": VMobject, + "group_type": VGroup, + } + + @staticmethod + def get_mobject_parts(mobject): + return mobject.submobjects + + @staticmethod + def get_mobject_key(mobject): + return mobject.get_tex() diff --git a/manimlib/camera/__init__.py b/manimlib/camera/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manimlib/camera/camera.py b/manimlib/camera/camera.py index 9676b67f..41c432b4 100644 --- a/manimlib/camera/camera.py +++ b/manimlib/camera/camera.py @@ -1,5 +1,6 @@ import moderngl from colour import Color +import OpenGL.GL as gl from PIL import Image import numpy as np @@ -9,11 +10,7 @@ from manimlib.constants import * from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Point from manimlib.utils.config_ops import digest_config -from manimlib.utils.iterables import batch_by_property from manimlib.utils.simple_functions import fdiv -from manimlib.utils.shaders import shader_info_to_id -from manimlib.utils.shaders import shader_id_to_info -from manimlib.utils.shaders import get_shader_code_from_file from manimlib.utils.simple_functions import clip from manimlib.utils.space_ops import angle_of_vector from manimlib.utils.space_ops import rotation_matrix_transpose_from_quaternion @@ -24,43 +21,46 @@ from manimlib.utils.space_ops import quaternion_mult class CameraFrame(Mobject): CONFIG = { - "width": FRAME_WIDTH, - "height": FRAME_HEIGHT, + "frame_shape": (FRAME_WIDTH, FRAME_HEIGHT), "center_point": ORIGIN, # Theta, phi, gamma "euler_angles": [0, 0, 0], - "focal_distance": 5, + "focal_distance": 2, } + def init_data(self): + super().init_data() + self.data["euler_angles"] = np.array(self.euler_angles, dtype=float) + self.refresh_rotation_matrix() + def init_points(self): - self.points = np.array([self.center_point]) - self.euler_angles = np.array(self.euler_angles, dtype='float64') + self.set_points([ORIGIN, LEFT, RIGHT, DOWN, UP]) + self.set_width(self.frame_shape[0], stretch=True) + self.set_height(self.frame_shape[1], stretch=True) + self.move_to(self.center_point) def to_default_state(self): self.center() self.set_height(FRAME_HEIGHT) self.set_width(FRAME_WIDTH) - self.set_rotation(0, 0, 0) + self.set_euler_angles(0, 0, 0) return self - def get_inverse_camera_position_matrix(self): - result = np.identity(4) - # First shift so that origin of real space coincides with camera origin - result[:3, 3] = -self.get_center().T - # Rotate based on camera orientation - result[:3, :3] = np.dot(self.get_inverse_camera_rotation_matrix(), result[:3, :3]) - # Scale to have height 2 (matching the height of the box [-1, 1]^2) - result *= 2 / self.height - return result + def get_euler_angles(self): + return self.data["euler_angles"] def get_inverse_camera_rotation_matrix(self): - theta, phi, gamma = self.euler_angles + return self.inverse_camera_rotation_matrix + + def refresh_rotation_matrix(self): + # Rotate based on camera orientation + theta, phi, gamma = self.get_euler_angles() quat = quaternion_mult( - quaternion_from_angle_axis(theta, OUT), - quaternion_from_angle_axis(phi, RIGHT), - quaternion_from_angle_axis(gamma, OUT), + quaternion_from_angle_axis(theta, OUT, axis_normalized=True), + quaternion_from_angle_axis(phi, RIGHT, axis_normalized=True), + quaternion_from_angle_axis(gamma, OUT, axis_normalized=True), ) - return rotation_matrix_transpose_from_quaternion(quat) + self.inverse_camera_rotation_matrix = rotation_matrix_transpose_from_quaternion(quat) def rotate(self, angle, axis=OUT, **kwargs): curr_rot_T = self.get_inverse_camera_rotation_matrix() @@ -74,63 +74,83 @@ class CameraFrame(Mobject): rotation_matrix_transpose(theta, OUT), ) gamma = angle_of_vector(np.dot(partial_rot_T, new_rot_T.T)[:, 0]) - # TODO, write a function that converts quaternions to euler angles - self.euler_angles[:] = theta, phi, gamma + self.set_euler_angles(theta, phi, gamma) return self - def set_rotation(self, theta=0, phi=0, gamma=0): - self.euler_angles[:] = theta, phi, gamma + def set_euler_angles(self, theta=None, phi=None, gamma=None, units=RADIANS): + if theta is not None: + self.data["euler_angles"][0] = theta * units + if phi is not None: + self.data["euler_angles"][1] = phi * units + if gamma is not None: + self.data["euler_angles"][2] = gamma * units + self.refresh_rotation_matrix() return self + def reorient(self, theta_degrees=None, phi_degrees=None, gamma_degrees=None): + """ + Shortcut for set_euler_angles, defaulting to taking + in angles in degrees + """ + self.set_euler_angles(theta_degrees, phi_degrees, gamma_degrees, units=DEGREES) + return self + + def set_theta(self, theta): + return self.set_euler_angles(theta=theta) + + def set_phi(self, phi): + return self.set_euler_angles(phi=phi) + + def set_gamma(self, gamma): + return self.set_euler_angles(gamma=gamma) + def increment_theta(self, dtheta): - self.euler_angles[0] += dtheta + self.data["euler_angles"][0] += dtheta + self.refresh_rotation_matrix() return self def increment_phi(self, dphi): - self.euler_angles[1] = clip(self.euler_angles[1] + dphi, 0, PI) + phi = self.data["euler_angles"][1] + new_phi = clip(phi + dphi, 0, PI) + self.data["euler_angles"][1] = new_phi + self.refresh_rotation_matrix() return self def increment_gamma(self, dgamma): - self.euler_angles[2] += dgamma + self.data["euler_angles"][2] += dgamma + self.refresh_rotation_matrix() return self - def scale(self, scale_factor, **kwargs): - # TODO, handle about_point and about_edge? - self.height *= scale_factor - self.width *= scale_factor - return self - - def set_height(self, height): - self.height = height - return self - - def set_width(self, width): - self.width = width - return self - - def get_height(self): - return self.height - - def get_width(self): - return self.width + def get_shape(self): + return (self.get_width(), self.get_height()) def get_center(self): - return self.points[0] + # Assumes first point is at the center + return self.get_points()[0] + + def get_width(self): + points = self.get_points() + return points[2, 0] - points[1, 0] + + def get_height(self): + points = self.get_points() + return points[4, 1] - points[3, 1] def get_focal_distance(self): - return self.focal_distance + return self.focal_distance * self.get_height() - def interpolate(self, mobject1, mobject2, alpha, **kwargs): - pass + def interpolate(self, *args, **kwargs): + super().interpolate(*args, **kwargs) + self.refresh_rotation_matrix() class Camera(object): CONFIG = { "background_image": None, "frame_config": {}, - "pixel_height": DEFAULT_PIXEL_HEIGHT, "pixel_width": DEFAULT_PIXEL_WIDTH, - "frame_rate": DEFAULT_FRAME_RATE, # TODO, move this elsewhere + "pixel_height": DEFAULT_PIXEL_HEIGHT, + "frame_rate": DEFAULT_FRAME_RATE, # Note: frame height and width will be resized to match # the pixel aspect ratio "background_color": BLACK, @@ -141,80 +161,88 @@ class Camera(object): "image_mode": "RGBA", "n_channels": 4, "pixel_array_dtype": 'uint8', - "light_source_position": [-10, 10, 10], # TODO, add multiple light sources - "apply_depth_test": False, + "light_source_position": [-10, 10, 10], + # Measured in pixel widths, used for vector graphics + "anti_alias_width": 1.5, + # Although vector graphics handle antialiasing fine + # without multisampling, for 3d scenes one might want + # to set samples to be greater than 0. + "samples": 0, } def __init__(self, ctx=None, **kwargs): digest_config(self, kwargs, locals()) self.rgb_max_val = np.iinfo(self.pixel_array_dtype).max + self.background_rgba = [ + *Color(self.background_color).get_rgb(), + self.background_opacity + ] self.init_frame() self.init_context(ctx) self.init_shaders() self.init_textures() self.init_light_source() + self.refresh_perspective_uniforms() + self.static_mobject_to_render_group_list = {} def init_frame(self): self.frame = CameraFrame(**self.frame_config) def init_context(self, ctx=None): - if ctx is not None: - self.ctx = ctx - self.fbo = self.ctx.detect_framebuffer() + if ctx is None: + ctx = moderngl.create_standalone_context() + fbo = self.get_fbo(ctx, 0) else: - self.ctx = moderngl.create_standalone_context() - self.fbo = self.get_fbo() - self.fbo.use() + fbo = ctx.detect_framebuffer() - flag = moderngl.BLEND - if self.apply_depth_test: - flag |= moderngl.DEPTH_TEST - self.ctx.enable(flag) - self.ctx.blend_func = ( # Needed? + # For multisample antialiasing + fbo_msaa = self.get_fbo(ctx, self.samples) + fbo_msaa.use() + + ctx.enable(moderngl.BLEND) + ctx.blend_func = ( moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA, moderngl.ONE, moderngl.ONE ) - self.background_fbo = None + + self.ctx = ctx + self.fbo = fbo + self.fbo_msaa = fbo_msaa def init_light_source(self): self.light_source = Point(self.light_source_position) # Methods associated with the frame buffer - def get_fbo(self): - return self.ctx.simple_framebuffer( - (self.pixel_width, self.pixel_height) + def get_fbo(self, ctx, samples=0): + pw = self.pixel_width + ph = self.pixel_height + return ctx.framebuffer( + color_attachments=ctx.texture( + (pw, ph), + components=self.n_channels, + samples=samples, + ), + depth_attachment=ctx.depth_renderbuffer( + (pw, ph), + samples=samples + ) ) - def resize_frame_shape(self, fixed_dimension=0): - """ - Changes frame_shape to match the aspect ratio - of the pixels, where fixed_dimension determines - whether frame_height or frame_width - remains fixed while the other changes accordingly. - """ - pixel_height = self.get_pixel_height() - pixel_width = self.get_pixel_width() - frame_height = self.get_frame_height() - frame_width = self.get_frame_width() - aspect_ratio = fdiv(pixel_width, pixel_height) - if fixed_dimension == 0: - frame_height = frame_width / aspect_ratio - else: - frame_width = aspect_ratio * frame_height - self.frame.set_height(frame_height) - self.frame.set_width(frame_width) - def clear(self): - rgba = (*Color(self.background_color).get_rgb(), self.background_opacity) - self.fbo.clear(*rgba) + self.fbo.clear(*self.background_rgba) + self.fbo_msaa.clear(*self.background_rgba) def reset_pixel_shape(self, new_width, new_height): self.pixel_width = new_width self.pixel_height = new_height - self.refresh_shader_uniforms() + self.refresh_perspective_uniforms() - # Various ways to read from the fbo def get_raw_fbo_data(self, dtype='f1'): + # Copy blocks from the fbo_msaa to the drawn fbo using Blit + pw, ph = (self.pixel_width, self.pixel_height) + gl.glBindFramebuffer(gl.GL_READ_FRAMEBUFFER, self.fbo_msaa.glo) + gl.glBindFramebuffer(gl.GL_DRAW_FRAMEBUFFER, self.fbo.glo) + gl.glBlitFramebuffer(0, 0, pw, ph, 0, 0, pw, ph, gl.GL_COLOR_BUFFER_BIT, gl.GL_LINEAR) return self.fbo.read( viewport=self.fbo.viewport, components=self.n_channels, @@ -269,12 +297,27 @@ class Camera(object): def get_frame_center(self): return self.frame.get_center() + def resize_frame_shape(self, fixed_dimension=0): + """ + Changes frame_shape to match the aspect ratio + of the pixels, where fixed_dimension determines + whether frame_height or frame_width + remains fixed while the other changes accordingly. + """ + pixel_height = self.get_pixel_height() + pixel_width = self.get_pixel_width() + frame_height = self.get_frame_height() + frame_width = self.get_frame_width() + aspect_ratio = fdiv(pixel_width, pixel_height) + if fixed_dimension == 0: + frame_height = frame_width / aspect_ratio + else: + frame_width = aspect_ratio * frame_height + self.frame.set_height(frame_height) + self.frame.set_width(frame_width) + def pixel_coords_to_space_coords(self, px, py, relative=False): - # pw, ph = self.fbo.size - # Bad hack, not sure why this is needed. - pw, ph = self.get_pixel_shape() - pw //= 2 - ph //= 2 + pw, ph = self.fbo.size fw, fh = self.get_frame_shape() fc = self.get_frame_center() if relative: @@ -286,82 +329,124 @@ class Camera(object): # Rendering def capture(self, *mobjects, **kwargs): - self.refresh_shader_uniforms() + self.refresh_perspective_uniforms() + for mobject in mobjects: + for render_group in self.get_render_group_list(mobject): + self.render(render_group) - shader_infos = it.chain(*[ - mob.get_shader_info_list() - for mob in mobjects - ]) - batches = batch_by_property(shader_infos, shader_info_to_id) + def render(self, render_group): + shader_wrapper = render_group["shader_wrapper"] + shader_program = render_group["prog"] + self.set_shader_uniforms(shader_program, shader_wrapper) + self.update_depth_test(shader_wrapper) + render_group["vao"].render(int(shader_wrapper.render_primitive)) + if render_group["single_use"]: + self.release_render_group(render_group) - for info_group, sid in batches: - if len(info_group) == 1: - data = info_group[0]["data"] + def update_depth_test(self, shader_wrapper): + if shader_wrapper.depth_test: + self.ctx.enable(moderngl.DEPTH_TEST) + else: + self.ctx.disable(moderngl.DEPTH_TEST) + + def get_render_group_list(self, mobject): + try: + return self.static_mobject_to_render_group_list[id(mobject)] + except KeyError: + return map(self.get_render_group, mobject.get_shader_wrapper_list()) + + def get_render_group(self, shader_wrapper, single_use=True): + # Data buffers + vbo = self.ctx.buffer(shader_wrapper.vert_data.tobytes()) + if shader_wrapper.vert_indices is None: + ibo = None + else: + vert_index_data = shader_wrapper.vert_indices.astype('i4').tobytes() + if vert_index_data: + ibo = self.ctx.buffer(vert_index_data) else: - data = np.hstack([info["data"] for info in info_group]) + ibo = None - shader = self.get_shader(info_group[0]) - render_primative = int(info_group[0]["render_primative"]) - self.render(shader, data, render_primative) + # Program and vertex array + shader_program, vert_format = self.get_shader_program(shader_wrapper) + vao = self.ctx.vertex_array( + program=shader_program, + content=[(vbo, vert_format, *shader_wrapper.vert_attributes)], + index_buffer=ibo, + ) + return { + "vbo": vbo, + "ibo": ibo, + "vao": vao, + "prog": shader_program, + "shader_wrapper": shader_wrapper, + "single_use": single_use, + } - def render(self, shader, data, render_primative): - if data is None or len(data) == 0: - return - if shader is None: - return - vbo = self.ctx.buffer(data.tobytes()) - vao = self.ctx.simple_vertex_array(shader, vbo, *data.dtype.names) - vao.render(render_primative) + def release_render_group(self, render_group): + for key in ["vbo", "ibo", "vao"]: + if render_group[key] is not None: + render_group[key].release() + + def set_mobjects_as_static(self, *mobjects): + # Creates buffer and array objects holding each mobjects shader data + for mob in mobjects: + self.static_mobject_to_render_group_list[id(mob)] = [ + self.get_render_group(sw, single_use=False) + for sw in mob.get_shader_wrapper_list() + ] + + def release_static_mobjects(self): + for rg_list in self.static_mobject_to_render_group_list.values(): + for render_group in rg_list: + self.release_render_group(render_group) + self.static_mobject_to_render_group_list = {} # Shaders def init_shaders(self): # Initialize with the null id going to None - self.id_to_shader = {"": None} + self.id_to_shader_program = {"": None} - def get_shader(self, shader_info): - sid = shader_info_to_id(shader_info) - if sid not in self.id_to_shader: - info = shader_id_to_info(sid) - shader = self.ctx.program( - vertex_shader=get_shader_code_from_file(info["vert"]), - geometry_shader=get_shader_code_from_file(info["geom"]), - fragment_shader=get_shader_code_from_file(info["frag"]), - ) - if info["texture_path"]: - # TODO, this currently assumes that the uniform Sampler2D - # is named Texture - tid = self.get_texture_id(info["texture_path"]) - shader["Texture"].value = tid + def get_shader_program(self, shader_wrapper): + sid = shader_wrapper.get_program_id() + if sid not in self.id_to_shader_program: + # Create shader program for the first time, then cache + # in the id_to_shader_program dictionary + program = self.ctx.program(**shader_wrapper.get_program_code()) + vert_format = moderngl.detect_format(program, shader_wrapper.vert_attributes) + self.id_to_shader_program[sid] = (program, vert_format) + return self.id_to_shader_program[sid] - self.set_shader_uniforms(shader) - self.id_to_shader[sid] = shader - return self.id_to_shader[sid] - - def set_shader_uniforms(self, shader): - if shader is None: - return - # TODO, think about how uniforms come from mobjects as well. - pw, ph = self.get_pixel_shape() - - transform = self.frame.get_inverse_camera_position_matrix() - light = self.light_source.get_location() - transformed_light = np.dot(transform, [*light, 1])[:3] - mapping = { - 'to_screen_space': tuple(transform.T.flatten()), - 'aspect_ratio': (pw / ph), # AR based on pixel shape - 'focal_distance': self.frame.get_focal_distance(), - 'anti_alias_width': 3 / ph, # 1.5 Pixel widths - 'light_source_position': tuple(transformed_light), - } - for key, value in mapping.items(): + def set_shader_uniforms(self, shader, shader_wrapper): + for name, path in shader_wrapper.texture_paths.items(): + tid = self.get_texture_id(path) + shader[name].value = tid + for name, value in it.chain(shader_wrapper.uniforms.items(), self.perspective_uniforms.items()): try: - shader[key].value = value + shader[name].value = value except KeyError: pass - def refresh_shader_uniforms(self): - for sid, shader in self.id_to_shader.items(): - self.set_shader_uniforms(shader) + def refresh_perspective_uniforms(self): + frame = self.frame + pw, ph = self.get_pixel_shape() + fw, fh = frame.get_shape() + # TODO, this should probably be a mobject uniform, with + # the camera taking care of the conversion factor + anti_alias_width = self.anti_alias_width / (ph / fh) + # Orient light + rotation = frame.get_inverse_camera_rotation_matrix() + light_pos = self.light_source.get_location() + light_pos = np.dot(rotation, light_pos) + + self.perspective_uniforms = { + "frame_shape": frame.get_shape(), + "anti_alias_width": anti_alias_width, + "camera_center": tuple(frame.get_center()), + "camera_rotation": tuple(np.array(rotation).T.flatten()), + "light_source_position": tuple(light_pos), + "focal_distance": frame.get_focal_distance(), + } def init_textures(self): self.path_to_texture_id = {} @@ -381,6 +466,8 @@ class Camera(object): return self.path_to_texture_id[path] +# Mostly just defined so old scenes don't break class ThreeDCamera(Camera): - # Purely here to keep old scenes happy - pass + CONFIG = { + "samples": 4, + } diff --git a/manimlib/camera/mapping_camera.py b/manimlib/camera/mapping_camera.py deleted file mode 100644 index a143459d..00000000 --- a/manimlib/camera/mapping_camera.py +++ /dev/null @@ -1,113 +0,0 @@ -import numpy as np - -from manimlib.camera.camera import Camera -from manimlib.mobject.types.vectorized_mobject import VMobject -from manimlib.utils.config_ops import DictAsObject -from manimlib.utils.config_ops import digest_config - -# TODO: Add an attribute to mobjects under which they can specify that they should just -# map their centers but remain otherwise undistorted (useful for labels, etc.) - - -# TODO, this class is deprecated -class MappingCamera(Camera): - CONFIG = { - "mapping_func": lambda p: p, - "min_num_curves": 50, - "allow_object_intrusion": False - } - - def points_to_pixel_coords(self, points): - return Camera.points_to_pixel_coords(self, np.apply_along_axis(self.mapping_func, 1, points)) - - def capture_mobjects(self, mobjects, **kwargs): - mobjects = self.get_mobjects_to_display(mobjects, **kwargs) - if self.allow_object_intrusion: - mobject_copies = mobjects - else: - mobject_copies = [mobject.copy() for mobject in mobjects] - for mobject in mobject_copies: - if isinstance(mobject, VMobject) and \ - 0 < mobject.get_num_curves() < self.min_num_curves: - mobject.insert_n_curves(self.min_num_curves) - Camera.capture_mobjects( - self, mobject_copies, - excluded_mobjects=None, - ) - - -# Note: This allows layering of multiple cameras onto the same portion of the pixel array, -# the later cameras overwriting the former -# -# TODO: Add optional separator borders between cameras (or perhaps peel this off into a -# CameraPlusOverlay class) - -# TODO, the classes below should likely be deleted -class OldMultiCamera(Camera): - def __init__(self, *cameras_with_start_positions, **kwargs): - self.shifted_cameras = [ - DictAsObject( - { - "camera": camera_with_start_positions[0], - "start_x": camera_with_start_positions[1][1], - "start_y": camera_with_start_positions[1][0], - "end_x": camera_with_start_positions[1][1] + camera_with_start_positions[0].get_pixel_width(), - "end_y": camera_with_start_positions[1][0] + camera_with_start_positions[0].get_pixel_height(), - }) - for camera_with_start_positions in cameras_with_start_positions - ] - Camera.__init__(self, **kwargs) - - def capture_mobjects(self, mobjects, **kwargs): - for shifted_camera in self.shifted_cameras: - shifted_camera.camera.capture_mobjects(mobjects, **kwargs) - - self.pixel_array[ - shifted_camera.start_y:shifted_camera.end_y, - shifted_camera.start_x:shifted_camera.end_x] \ - = shifted_camera.camera.pixel_array - - def set_background(self, pixel_array, **kwargs): - for shifted_camera in self.shifted_cameras: - shifted_camera.camera.set_background( - pixel_array[ - shifted_camera.start_y:shifted_camera.end_y, - shifted_camera.start_x:shifted_camera.end_x], - **kwargs - ) - - def set_pixel_array(self, pixel_array, **kwargs): - Camera.set_pixel_array(self, pixel_array, **kwargs) - for shifted_camera in self.shifted_cameras: - shifted_camera.camera.set_pixel_array( - pixel_array[ - shifted_camera.start_y:shifted_camera.end_y, - shifted_camera.start_x:shifted_camera.end_x], - **kwargs - ) - - def init_background(self): - Camera.init_background(self) - for shifted_camera in self.shifted_cameras: - shifted_camera.camera.init_background() - -# A OldMultiCamera which, when called with two full-size cameras, initializes itself -# as a splitscreen, also taking care to resize each individual camera within it - - -class SplitScreenCamera(OldMultiCamera): - def __init__(self, left_camera, right_camera, **kwargs): - digest_config(self, kwargs) - self.left_camera = left_camera - self.right_camera = right_camera - - half_width = self.get_pixel_width() / 2 - for camera in [self.left_camera, self.right_camera]: - # TODO: Round up on one if width is odd - camera.reset_pixel_shape(half_width, camera.get_pixel_height()) - - OldMultiCamera.__init__( - self, - (left_camera, (0, 0)), - (right_camera, (0, half_width)), - ) diff --git a/manimlib/camera/moving_camera.py b/manimlib/camera/moving_camera.py deleted file mode 100644 index 74c83a4c..00000000 --- a/manimlib/camera/moving_camera.py +++ /dev/null @@ -1,82 +0,0 @@ -from manimlib.camera.camera import Camera -from manimlib.constants import FRAME_HEIGHT -from manimlib.constants import FRAME_WIDTH -from manimlib.constants import ORIGIN -from manimlib.constants import WHITE -from manimlib.mobject.frame import ScreenRectangle -from manimlib.mobject.types.vectorized_mobject import VGroup -from manimlib.utils.config_ops import digest_config - - -# Depricated? -class MovingCamera(Camera): - """ - Stays in line with the height, width and position of it's 'frame', which is a Rectangle - """ - - CONFIG = { - "fixed_dimension": 0, # width - "default_frame_stroke_color": WHITE, - "default_frame_stroke_width": 0, - } - - def __init__(self, frame=None, **kwargs): - """ - frame is a Mobject, (should almost certainly be a rectangle) - determining which region of space the camera displys - """ - digest_config(self, kwargs) - if frame is None: - frame = ScreenRectangle(height=FRAME_HEIGHT) - frame.set_stroke( - self.default_frame_stroke_color, - self.default_frame_stroke_width, - ) - self.frame = frame - Camera.__init__(self, **kwargs) - - # TODO, make these work for a rotated frame - def get_frame_height(self): - return self.frame.get_height() - - def get_frame_width(self): - return self.frame.get_width() - - def get_frame_center(self): - return self.frame.get_center() - - def set_frame_height(self, frame_height): - self.frame.stretch_to_fit_height(frame_height) - - def set_frame_width(self, frame_width): - self.frame.stretch_to_fit_width(frame_width) - - def set_frame_center(self, frame_center): - self.frame.move_to(frame_center) - - # Since the frame can be moving around, the cairo - # context used for updating should be regenerated - # at each frame. So no caching. - def get_cached_cairo_context(self, pixel_array): - return None - - def cache_cairo_context(self, pixel_array, ctx): - pass - - # def reset_frame_center(self): - # self.frame_center = self.frame.get_center() - - # def realign_frame_shape(self): - # height, width = self.frame_shape - # if self.fixed_dimension == 0: - # self.frame_shape = (height, self.frame.get_width()) - # else: - # self.frame_shape = (self.frame.get_height(), width) - # self.resize_frame_shape(fixed_dimension=self.fixed_dimension) - - def get_mobjects_indicating_movement(self): - """ - Returns all mobjets whose movement implies that the camera - should think of all other mobjects on the screen as moving - """ - return [self.frame] diff --git a/manimlib/camera/multi_camera.py b/manimlib/camera/multi_camera.py deleted file mode 100644 index 920b7da3..00000000 --- a/manimlib/camera/multi_camera.py +++ /dev/null @@ -1,57 +0,0 @@ -from manimlib.camera.moving_camera import MovingCamera -from manimlib.utils.iterables import list_difference_update - - -class MultiCamera(MovingCamera): - CONFIG = { - "allow_cameras_to_capture_their_own_display": False, - } - - def __init__(self, *image_mobjects_from_cameras, **kwargs): - self.image_mobjects_from_cameras = [] - for imfc in image_mobjects_from_cameras: - self.add_image_mobject_from_camera(imfc) - MovingCamera.__init__(self, **kwargs) - - def add_image_mobject_from_camera(self, image_mobject_from_camera): - # A silly method to have right now, but maybe there are things - # we want to guarantee about any imfc's added later. - imfc = image_mobject_from_camera - assert(isinstance(imfc.camera, MovingCamera)) - self.image_mobjects_from_cameras.append(imfc) - - def update_sub_cameras(self): - """ Reshape sub_camera pixel_arrays """ - for imfc in self.image_mobjects_from_cameras: - pixel_height, pixel_width = self.get_pixel_array().shape[:2] - imfc.camera.frame_shape = ( - imfc.camera.frame.get_height(), - imfc.camera.frame.get_width(), - ) - imfc.camera.reset_pixel_shape( - int(pixel_width * imfc.get_width() / self.get_frame_width()), - int(pixel_height * imfc.get_height() / self.get_frame_height()), - ) - - def reset(self): - for imfc in self.image_mobjects_from_cameras: - imfc.camera.reset() - MovingCamera.reset(self) - return self - - def capture_mobjects(self, mobjects, **kwargs): - self.update_sub_cameras() - for imfc in self.image_mobjects_from_cameras: - to_add = list(mobjects) - if not self.allow_cameras_to_capture_their_own_display: - to_add = list_difference_update( - to_add, imfc.get_family() - ) - imfc.camera.capture_mobjects(to_add, **kwargs) - MovingCamera.capture_mobjects(self, mobjects, **kwargs) - - def get_mobjects_indicating_movement(self): - return [self.frame] + [ - imfc.camera.frame - for imfc in self.image_mobjects_from_cameras - ] diff --git a/manimlib/config.py b/manimlib/config.py index cf7eb7ef..5c0e88eb 100644 --- a/manimlib/config.py +++ b/manimlib/config.py @@ -1,11 +1,14 @@ import argparse import colour -import importlib.util +import inspect +import importlib import os import sys -import types +import yaml +from screeninfo import get_monitors -import manimlib.constants +from manimlib.utils.config_ops import merge_dicts_recursively +from manimlib.utils.init_config import init_customization def parse_cli(): @@ -22,71 +25,81 @@ def parse_cli(): nargs="*", help="Name of the Scene class you want to see", ) - parser.add_argument( - "-p", "--preview", - action="store_true", - help="Automatically open the saved file once its done", - ), parser.add_argument( "-w", "--write_file", action="store_true", help="Render the scene as a movie file", - ), + ) parser.add_argument( "-s", "--skip_animations", action="store_true", help="Save the last frame", - ), + ) parser.add_argument( "-l", "--low_quality", action="store_true", help="Render at a low quality (for faster rendering)", - ), + ) parser.add_argument( "-m", "--medium_quality", action="store_true", help="Render at a medium quality", - ), + ) parser.add_argument( - "--high_quality", + "--hd", action="store_true", - help="Render at a high quality", - ), + help="Render at a 1080p", + ) + parser.add_argument( + "--uhd", + action="store_true", + help="Render at a 4k", + ) + parser.add_argument( + "-f", "--full_screen", + action="store_true", + help="Show window in full screen", + ) parser.add_argument( "-g", "--save_pngs", action="store_true", help="Save each frame as a png", - ), + ) parser.add_argument( - "-i", "--save_as_gif", + "-i", "--gif", action="store_true", help="Save the video as gif", - ), - parser.add_argument( - "-f", "--show_file_in_finder", - action="store_true", - help="Show the output file in finder", - ), + ) parser.add_argument( "-t", "--transparent", action="store_true", help="Render to a movie file with an alpha channel", - ), + ) parser.add_argument( "-q", "--quiet", action="store_true", help="", - ), + ) parser.add_argument( "-a", "--write_all", action="store_true", help="Write all the scenes from a file", - ), + ) parser.add_argument( "-o", "--open", action="store_true", help="Automatically open the saved file once its done", ) + parser.add_argument( + "--finder", + action="store_true", + help="Show the output file in finder", + ) + parser.add_argument( + "--config", + action="store_true", + help="Guide for automatic configuration", + ) parser.add_argument( "--file_name", help="Name for the movie or image file", @@ -100,7 +113,11 @@ def parse_cli(): ) parser.add_argument( "-r", "--resolution", - help="Resolution, passed as \"height,width\"", + help="Resolution, passed as \"WxH\", e.g. \"1920x1080\"", + ) + parser.add_argument( + "--frame_rate", + help="Frame rate, as an integer", ) parser.add_argument( "-c", "--color", @@ -112,66 +129,25 @@ def parse_cli(): help="Leave progress bars displayed in terminal", ) parser.add_argument( - "--media_dir", - help="directory to write media", - ) - video_group = parser.add_mutually_exclusive_group() - video_group.add_argument( "--video_dir", - help="directory to write file tree for video", - ) - video_group.add_argument( - "--video_output_dir", help="directory to write video", ) - parser.add_argument( - "--tex_dir", - help="directory to write tex", - ) - - # For live streaming - module_location.add_argument( - "--livestream", - action="store_true", - help="Run in streaming mode", - ) - parser.add_argument( - "--to-twitch", - action="store_true", - help="Stream to twitch", - ) - parser.add_argument( - "--with-key", - dest="twitch_key", - help="Stream key for twitch", - ) args = parser.parse_args() - - if args.file is None and not args.livestream: - parser.print_help() - sys.exit(2) - if args.to_twitch and not args.livestream: - print("You must run in streaming mode in order to stream to twitch") - sys.exit(2) - if args.to_twitch and args.twitch_key is None: - print("Specify the twitch stream key with --with-key") - sys.exit(2) return args except argparse.ArgumentError as err: print(str(err)) sys.exit(2) +def get_manim_dir(): + manimlib_module = importlib.import_module("manimlib") + manimlib_dir = os.path.dirname(inspect.getabsfile(manimlib_module)) + return os.path.abspath(os.path.join(manimlib_dir, "..")) + + def get_module(file_name): - if file_name == "-": - module = types.ModuleType("input_scenes") - code = "from manimlib.imports import *\n\n" + sys.stdin.read() - try: - exec(code, module.__dict__) - return module - except Exception as e: - print(f"Failed to render scene: {str(e)}") - sys.exit(2) + if file_name is None: + return None else: module_name = file_name.replace(os.sep, ".").replace(".py", "") spec = importlib.util.spec_from_file_location(module_name, file_name) @@ -180,61 +156,93 @@ def get_module(file_name): return module -def get_configuration(args): - module = get_module(args.file) +def get_custom_config(): + filename = "custom_config.yml" + global_defaults_file = os.path.join(get_manim_dir(), "manimlib", "default_config.yml") - write_file = any([ - args.write_file, - args.open, - args.show_file_in_finder, - ]) + if os.path.exists(global_defaults_file): + with open(global_defaults_file, "r") as file: + config = yaml.safe_load(file) + + if os.path.exists(filename): + with open(filename, "r") as file: + local_defaults = yaml.safe_load(file) + if local_defaults: + config = merge_dicts_recursively( + config, + local_defaults, + ) + else: + with open(filename, "r") as file: + config = yaml.safe_load(file) + + return config + + +def get_configuration(args): + local_config_file = "custom_config.yml" + global_defaults_file = os.path.join(get_manim_dir(), "manimlib", "default_config.yml") + if not (os.path.exists(global_defaults_file) or os.path.exists(local_config_file)): + print("There is no configuration file detected. Initial configuration:\n") + init_customization() + elif not os.path.exists(local_config_file): + print(f"""Warning: Using the default configuration file, which you can modify in {global_defaults_file} + If you want to create a local configuration file, you can create a file named {local_config_file}, or run manimgl --config + """) + custom_config = get_custom_config() + + write_file = any([args.write_file, args.open, args.finder]) + if args.transparent: + file_ext = ".mov" + elif args.gif: + file_ext = ".gif" + else: + file_ext = ".mp4" file_writer_config = { - # By default, write to file "write_to_movie": not args.skip_animations and write_file, + "break_into_partial_movies": custom_config["break_into_partial_movies"], "save_last_frame": args.skip_animations and write_file, "save_pngs": args.save_pngs, - "save_as_gif": args.save_as_gif, # If -t is passed in (for transparent), this will be RGBA "png_mode": "RGBA" if args.transparent else "RGB", - "movie_file_extension": ".mov" if args.transparent else ".mp4", + "movie_file_extension": file_ext, + "mirror_module_path": custom_config["directories"]["mirror_module_path"], + "output_directory": args.video_dir or custom_config["directories"]["output"], "file_name": args.file_name, - "input_file_path": args.file, + "input_file_path": args.file or "", "open_file_upon_completion": args.open, - "show_file_location_upon_completion": args.show_file_in_finder, + "show_file_location_upon_completion": args.finder, "quiet": args.quiet, } - if hasattr(module, "OUTPUT_DIRECTORY"): - file_writer_config["output_directory"] = module.OUTPUT_DIRECTORY - - # If preview wasn't set, but there is no filewriting, preview anyway - # so that the user sees something - if not (args.preview or write_file): - args.preview = True + module = get_module(args.file) config = { "module": module, "scene_names": args.scene_names, - "preview": args.preview, "file_writer_config": file_writer_config, "quiet": args.quiet or args.write_all, "write_all": args.write_all, "start_at_animation_number": args.start_at_animation_number, + "preview": not write_file, "end_at_animation_number": None, "leave_progress_bars": args.leave_progress_bars, - "media_dir": args.media_dir, - "video_dir": args.video_dir, - "video_output_dir": args.video_output_dir, - "tex_dir": args.tex_dir, } # Camera configuration - config["camera_config"] = get_camera_configuration(args) + config["camera_config"] = get_camera_configuration(args, custom_config) + + # Default to making window half the screen size + # but make it full screen if -f is passed in + monitors = get_monitors() + mon_index = custom_config["window_monitor"] + monitor = monitors[min(mon_index, len(monitors) - 1)] + window_width = monitor.width + if not args.full_screen: + window_width //= 2 + window_height = window_width * 9 // 16 config["window_config"] = { - "size": ( - config["camera_config"]["pixel_width"], - config["camera_config"]["pixel_height"], - ) + "size": (window_width, window_height), } # Arguments related to skipping @@ -254,40 +262,42 @@ def get_configuration(args): return config -def get_camera_configuration(args): +def get_camera_configuration(args, custom_config): camera_config = {} + camera_qualities = get_custom_config()["camera_qualities"] if args.low_quality: - camera_config.update(manimlib.constants.LOW_QUALITY_CAMERA_CONFIG) + quality = camera_qualities["low"] elif args.medium_quality: - camera_config.update(manimlib.constants.MEDIUM_QUALITY_CAMERA_CONFIG) - elif args.high_quality: - camera_config.update(manimlib.constants.HIGH_QUALITY_CAMERA_CONFIG) - elif args.preview: # Without a quality specified, preview at medium quality - camera_config.update(manimlib.constants.MEDIUM_QUALITY_CAMERA_CONFIG) - else: # Without anything specified, render to production quality - camera_config.update(manimlib.constants.PRODUCTION_QUALITY_CAMERA_CONFIG) + quality = camera_qualities["medium"] + elif args.hd: + quality = camera_qualities["high"] + elif args.uhd: + quality = camera_qualities["ultra_high"] + else: + quality = camera_qualities[camera_qualities["default_quality"]] - # If the resolution was passed in via -r if args.resolution: - if "," in args.resolution: - height_str, width_str = args.resolution.split(",") - height = int(height_str) - width = int(width_str) - else: - height = int(args.resolution) - width = int(16 * height / 9) - camera_config.update({ - "pixel_height": height, - "pixel_width": width, - }) + quality["resolution"] = args.resolution + if args.frame_rate: + quality["frame_rate"] = int(args.frame_rate) - if args.color: - try: - camera_config["background_color"] = colour.Color(args.color) - except AttributeError as err: - print("Please use a valid color") - print(err) - sys.exit(2) + width_str, height_str = quality["resolution"].split("x") + width = int(width_str) + height = int(height_str) + + camera_config.update({ + "pixel_width": width, + "pixel_height": height, + "frame_rate": quality["frame_rate"], + }) + + try: + bg_color = args.color or custom_config["style"]["background_color"] + camera_config["background_color"] = colour.Color(bg_color) + except AttributeError as err: + print("Please use a valid color") + print(err) + sys.exit(2) # If rendering a transparent image/move, make sure the # scene has a background opacity of 0 diff --git a/manimlib/constants.py b/manimlib/constants.py index d6dd1770..6c82bba2 100644 --- a/manimlib/constants.py +++ b/manimlib/constants.py @@ -1,165 +1,16 @@ import numpy as np -import os - -MEDIA_DIR = "" -VIDEO_DIR = "" -VIDEO_OUTPUT_DIR = "" -TEX_DIR = "" -TEXT_DIR = "" -MOBJECT_POINTS_DIR = "" - - -def initialize_directories(config): - global MEDIA_DIR - global VIDEO_DIR - global VIDEO_OUTPUT_DIR - global TEX_DIR - global TEXT_DIR - global MOBJECT_POINTS_DIR - - video_path_specified = config["video_dir"] or config["video_output_dir"] - - if not (video_path_specified and config["tex_dir"]): - if config["media_dir"]: - MEDIA_DIR = config["media_dir"] - else: - MEDIA_DIR = os.path.join( - os.path.expanduser('~'), - "Dropbox (3Blue1Brown)/3Blue1Brown Team Folder" - ) - if not os.path.isdir(MEDIA_DIR): - MEDIA_DIR = "./media" - print( - f"Media will be written to {MEDIA_DIR + os.sep}. You can change " - "this behavior with the --media_dir flag." - ) - else: - if config["media_dir"]: - print( - "Ignoring --media_dir, since both --tex_dir and a video " - "directory were both passed" - ) - - TEX_DIR = config["tex_dir"] or os.path.join(MEDIA_DIR, "Tex") - TEXT_DIR = os.path.join(MEDIA_DIR, "texts") - MOBJECT_POINTS_DIR = os.path.join(MEDIA_DIR, "mobject_points") - if not video_path_specified: - VIDEO_DIR = os.path.join(MEDIA_DIR, "videos") - VIDEO_OUTPUT_DIR = os.path.join(MEDIA_DIR, "videos") - elif config["video_output_dir"]: - VIDEO_OUTPUT_DIR = config["video_output_dir"] - else: - VIDEO_DIR = config["video_dir"] - - for folder in [VIDEO_DIR, VIDEO_OUTPUT_DIR, TEX_DIR, TEXT_DIR, MOBJECT_POINTS_DIR]: - if folder != "" and not os.path.exists(folder): - os.makedirs(folder) - - -NOT_SETTING_FONT_MSG = ''' -Warning: -You haven't set font. -If you are not using English, this may cause text rendering problem. -You set font like: -text = Text('your text', font='your font') -or: -class MyText(Text): - CONFIG = { - 'font': 'My Font' - } -''' -START_X = 30 -START_Y = 20 -NORMAL = 'NORMAL' -ITALIC = 'ITALIC' -OBLIQUE = 'OBLIQUE' -BOLD = 'BOLD' - -TEX_USE_CTEX = False -TEX_TEXT_TO_REPLACE = "YourTextHere" -TEMPLATE_TEX_FILE = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - "tex_template.tex" if not TEX_USE_CTEX else "ctex_template.tex" -) -with open(TEMPLATE_TEX_FILE, "r") as infile: - TEMPLATE_TEXT_FILE_BODY = infile.read() - TEMPLATE_TEX_FILE_BODY = TEMPLATE_TEXT_FILE_BODY.replace( - TEX_TEXT_TO_REPLACE, - "\\begin{align*}\n" + TEX_TEXT_TO_REPLACE + "\n\\end{align*}", - ) - - -SHADER_DIR = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - "shaders" -) - -HELP_MESSAGE = """ - Usage: - python extract_scene.py [] - -p preview in low quality - -s show and save picture of last frame - -w write result to file [this is default if nothing else is stated] - -o write to a different file_name - -l use low quality - -m use medium quality - -a run and save every scene in the script, or all args for the given scene - -q don't print progress - -f when writing to a movie file, export the frames in png sequence - -t use transperency when exporting images - -n specify the number of the animation to start from - -r specify a resolution - -c specify a background color -""" -SCENE_NOT_FOUND_MESSAGE = """ - {} is not in the script -""" -CHOOSE_NUMBER_MESSAGE = """ -Choose number corresponding to desired scene/arguments. -(Use comma separated list for multiple entries) -Choice(s): """ -INVALID_NUMBER_MESSAGE = "Fine then, if you don't want to give a valid number I'll just quit" - -NO_SCENE_MESSAGE = """ - There are no scenes inside that module -""" - -# There might be other configuration than pixel shape later... -PRODUCTION_QUALITY_CAMERA_CONFIG = { - "pixel_height": 1440, - "pixel_width": 2560, - "frame_rate": 60, -} - -HIGH_QUALITY_CAMERA_CONFIG = { - "pixel_height": 1080, - "pixel_width": 1920, - "frame_rate": 60, -} - -MEDIUM_QUALITY_CAMERA_CONFIG = { - "pixel_height": 720, - "pixel_width": 1280, - "frame_rate": 30, -} - -LOW_QUALITY_CAMERA_CONFIG = { - "pixel_height": 480, - "pixel_width": 854, - "frame_rate": 15, -} - -DEFAULT_PIXEL_HEIGHT = PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_height"] -DEFAULT_PIXEL_WIDTH = PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_width"] -DEFAULT_FRAME_RATE = 60 - -DEFAULT_STROKE_WIDTH = 4 +# Sizes relevant to default camera frame +ASPECT_RATIO = 16.0 / 9.0 FRAME_HEIGHT = 8.0 -FRAME_WIDTH = FRAME_HEIGHT * DEFAULT_PIXEL_WIDTH / DEFAULT_PIXEL_HEIGHT +FRAME_WIDTH = FRAME_HEIGHT * ASPECT_RATIO FRAME_Y_RADIUS = FRAME_HEIGHT / 2 FRAME_X_RADIUS = FRAME_WIDTH / 2 +DEFAULT_PIXEL_HEIGHT = 1080 +DEFAULT_PIXEL_WIDTH = 1920 +DEFAULT_FRAME_RATE = 30 + SMALL_BUFF = 0.1 MED_SMALL_BUFF = 0.25 MED_LARGE_BUFF = 0.5 @@ -199,92 +50,94 @@ RIGHT_SIDE = FRAME_X_RADIUS * RIGHT PI = np.pi TAU = 2 * PI DEGREES = TAU / 360 +# Nice to have a constant for readability +# when juxtaposed with expressions like 30 * DEGREES +RADIANS = 1 FFMPEG_BIN = "ffmpeg" -# Colors -COLOR_MAP = { - "DARK_BLUE": "#236B8E", - "DARK_BROWN": "#8B4513", - "LIGHT_BROWN": "#CD853F", - "BLUE_E": "#1C758A", - "BLUE_D": "#29ABCA", - "BLUE_C": "#58C4DD", - "BLUE_B": "#9CDCEB", - "BLUE_A": "#C7E9F1", - "TEAL_E": "#49A88F", - "TEAL_D": "#55C1A7", - "TEAL_C": "#5CD0B3", - "TEAL_B": "#76DDC0", - "TEAL_A": "#ACEAD7", - "GREEN_E": "#699C52", - "GREEN_D": "#77B05D", - "GREEN_C": "#83C167", - "GREEN_B": "#A6CF8C", - "GREEN_A": "#C9E2AE", - "YELLOW_E": "#E8C11C", - "YELLOW_D": "#F4D345", - "YELLOW_C": "#FFFF00", - "YELLOW_B": "#FFEA94", - "YELLOW_A": "#FFF1B6", - "GOLD_E": "#C78D46", - "GOLD_D": "#E1A158", - "GOLD_C": "#F0AC5F", - "GOLD_B": "#F9B775", - "GOLD_A": "#F7C797", - "RED_E": "#CF5044", - "RED_D": "#E65A4C", - "RED_C": "#FC6255", - "RED_B": "#FF8080", - "RED_A": "#F7A1A3", - "MAROON_E": "#94424F", - "MAROON_D": "#A24D61", - "MAROON_C": "#C55F73", - "MAROON_B": "#EC92AB", - "MAROON_A": "#ECABC1", - "PURPLE_E": "#644172", - "PURPLE_D": "#715582", - "PURPLE_C": "#9A72AC", - "PURPLE_B": "#B189C6", - "PURPLE_A": "#CAA3E8", - "WHITE": "#FFFFFF", - "BLACK": "#000000", - "GREY_A": "#DDDDDD", - "GREY_B": "#BBBBBB", - "GREY_C": "#888888", - "GREY_D": "#444444", - "GREY_E": "#222222", - # TODO, remove these greys - "LIGHT_GRAY": "#BBBBBB", - "LIGHT_GREY": "#BBBBBB", - "GRAY": "#888888", - "GREY": "#888888", - "DARK_GREY": "#444444", - "DARK_GRAY": "#444444", - "DARKER_GREY": "#222222", - "DARKER_GRAY": "#222222", - "GREY_BROWN": "#736357", - "PINK": "#D147BD", - "LIGHT_PINK": "#DC75CD", - "GREEN_SCREEN": "#00FF00", - "ORANGE": "#FF862F", +JOINT_TYPE_MAP = { + "auto": 0, + "round": 1, + "bevel": 2, + "miter": 3, } -PALETTE = list(COLOR_MAP.values()) -locals().update(COLOR_MAP) -for name in [s for s in list(COLOR_MAP.keys()) if s.endswith("_C")]: - locals()[name.replace("_C", "")] = locals()[name] -# Streaming related configuration -LIVE_STREAM_NAME = "LiveStream" -TWITCH_STREAM_KEY = "YOUR_STREAM_KEY" -STREAMING_PROTOCOL = "tcp" -STREAMING_IP = "127.0.0.1" -STREAMING_PORT = "2000" -STREAMING_CLIENT = "ffplay" -STREAMING_URL = f"{STREAMING_PROTOCOL}://{STREAMING_IP}:{STREAMING_PORT}?listen" -STREAMING_CONSOLE_BANNER = """ -Manim is now running in streaming mode. Stream animations by passing -them to manim.play(), e.g. ->>> c = Circle() ->>> manim.play(ShowCreation(c)) -""" +# Related to Text +START_X = 30 +START_Y = 20 +NORMAL = "NORMAL" +ITALIC = "ITALIC" +OBLIQUE = "OBLIQUE" +BOLD = "BOLD" + +DEFAULT_STROKE_WIDTH = 4 + +# Colors +BLUE_E = "#1C758A" +BLUE_D = "#29ABCA" +BLUE_C = "#58C4DD" +BLUE_B = "#9CDCEB" +BLUE_A = "#C7E9F1" +TEAL_E = "#49A88F" +TEAL_D = "#55C1A7" +TEAL_C = "#5CD0B3" +TEAL_B = "#76DDC0" +TEAL_A = "#ACEAD7" +GREEN_E = "#699C52" +GREEN_D = "#77B05D" +GREEN_C = "#83C167" +GREEN_B = "#A6CF8C" +GREEN_A = "#C9E2AE" +YELLOW_E = "#E8C11C" +YELLOW_D = "#F4D345" +YELLOW_C = "#FFFF00" +YELLOW_B = "#FFEA94" +YELLOW_A = "#FFF1B6" +GOLD_E = "#C78D46" +GOLD_D = "#E1A158" +GOLD_C = "#F0AC5F" +GOLD_B = "#F9B775" +GOLD_A = "#F7C797" +RED_E = "#CF5044" +RED_D = "#E65A4C" +RED_C = "#FC6255" +RED_B = "#FF8080" +RED_A = "#F7A1A3" +MAROON_E = "#94424F" +MAROON_D = "#A24D61" +MAROON_C = "#C55F73" +MAROON_B = "#EC92AB" +MAROON_A = "#ECABC1" +PURPLE_E = "#644172" +PURPLE_D = "#715582" +PURPLE_C = "#9A72AC" +PURPLE_B = "#B189C6" +PURPLE_A = "#CAA3E8" +GREY_E = "#222222" +GREY_D = "#444444" +GREY_C = "#888888" +GREY_B = "#BBBBBB" +GREY_A = "#DDDDDD" +WHITE = "#FFFFFF" +BLACK = "#000000" +GREY_BROWN = "#736357" +DARK_BROWN = "#8B4513" +LIGHT_BROWN = "#CD853F" +PINK = "#D147BD" +LIGHT_PINK = "#DC75CD" +GREEN_SCREEN = "#00FF00" +ORANGE = "#FF862F" + +# Abbreviated names for the "median" colors +BLUE = BLUE_C +TEAL = TEAL_C +GREEN = GREEN_C +YELLOW = YELLOW_C +GOLD = GOLD_C +RED = RED_C +MAROON = MAROON_C +PURPLE = PURPLE_C +GREY = GREY_C + +COLORMAP_3B1B = [BLUE_E, GREEN, YELLOW, RED] diff --git a/manimlib/container/container.py b/manimlib/container/container.py deleted file mode 100644 index 5314f2f8..00000000 --- a/manimlib/container/container.py +++ /dev/null @@ -1,20 +0,0 @@ -from manimlib.utils.config_ops import digest_config - -# Currently, this is only used by both Scene and Mobject. -# Still, we abstract its functionality here, albeit purely nominally. -# All actual implementation has to be handled by derived classes for now. - -# TODO: Move the "remove" functionality of Scene to this class - - -class Container(object): - def __init__(self, **kwargs): - digest_config(self, kwargs) - - def add(self, *items): - raise Exception( - "Container.add is not implemented; it is up to derived classes to implement") - - def remove(self, *items): - raise Exception( - "Container.remove is not implemented; it is up to derived classes to implement") diff --git a/manimlib/default_config.yml b/manimlib/default_config.yml new file mode 100644 index 00000000..c67dd502 --- /dev/null +++ b/manimlib/default_config.yml @@ -0,0 +1,57 @@ +directories: + # Set this to true if you want the path to video files + # to match the directory structure of the path to the + # sourcecode generating that video + mirror_module_path: False + # Where should manim output video and image files? + output: "" + # If you want to use images, manim will look to these folders to find them + raster_images: "" + vector_images: "" + # If you want to use sounds, manim will look here to find it. + sounds: "" + # Manim often generates tex_files or other kinds of serialized data + # to keep from having to generate the same thing too many times. By + # default, these will be stored at tempfile.gettempdir(), e.g. this might + # return whatever is at to the TMPDIR environment variable. If you want to + # specify them elsewhere, + temporary_storage: "" +tex: + executable: "latex" + template_file: "tex_template.tex" + intermediate_filetype: "dvi" + text_to_replace: "[tex_expression]" + # For ctex, use the following configuration + # executable: "xelatex -no-pdf" + # template_file: "ctex_template.tex" + # intermediate_filetype: "xdv" +universal_import_line: "from manimlib import *" +style: + font: "Consolas" + background_color: "#333333" +# Set the position of preview window, you can use directions, e.g. UL/DR/OL/OO/... +# also, you can also specify the position(pixel) of the upper left corner of +# the window on the monitor, e.g. "960,540" +window_position: UR +window_monitor: 0 +# If break_into_partial_movies is set to True, then many small +# files will be written corresponding to each Scene.play and +# Scene.wait call, and these files will then be combined +# to form the full scene. Sometimes video-editing is made +# easier when working with the broken up scene, which +# effectively has cuts at all the places you might want. +break_into_partial_movies: False +camera_qualities: + low: + resolution: "854x480" + frame_rate: 15 + medium: + resolution: "1280x720" + frame_rate: 30 + high: + resolution: "1920x1080" + frame_rate: 30 + ultra_high: + resolution: "3840x2160" + frame_rate: 60 + default_quality: "high" diff --git a/manimlib/event_handler/__init__.py b/manimlib/event_handler/__init__.py new file mode 100644 index 00000000..c6c25536 --- /dev/null +++ b/manimlib/event_handler/__init__.py @@ -0,0 +1,6 @@ +from manimlib.event_handler.event_dispatcher import EventDispatcher + + +# This is supposed to be a Singleton +# i.e., during runtime there should be only one object of Event Dispatcher +EVENT_DISPATCHER = EventDispatcher() diff --git a/manimlib/event_handler/event_dispatcher.py b/manimlib/event_handler/event_dispatcher.py new file mode 100644 index 00000000..a760d9ee --- /dev/null +++ b/manimlib/event_handler/event_dispatcher.py @@ -0,0 +1,92 @@ +import numpy as np + +from manimlib.event_handler.event_type import EventType +from manimlib.event_handler.event_listner import EventListner + + +class EventDispatcher(object): + def __init__(self): + self.event_listners = { + event_type: [] + for event_type in EventType + } + self.mouse_point = np.array((0., 0., 0.)) + self.mouse_drag_point = np.array((0., 0., 0.)) + self.pressed_keys = set() + self.draggable_object_listners = [] + + def add_listner(self, event_listner): + assert(isinstance(event_listner, EventListner)) + self.event_listners[event_listner.event_type].append(event_listner) + return self + + def remove_listner(self, event_listner): + assert(isinstance(event_listner, EventListner)) + try: + while event_listner in self.event_listners[event_listner.event_type]: + self.event_listners[event_listner.event_type].remove(event_listner) + except: + # raise ValueError("Handler is not handling this event, so cannot remove it.") + pass + return self + + def dispatch(self, event_type, **event_data): + + if event_type == EventType.MouseMotionEvent: + self.mouse_point = event_data["point"] + elif event_type == EventType.MouseDragEvent: + self.mouse_drag_point = event_data["point"] + elif event_type == EventType.KeyPressEvent: + self.pressed_keys.add(event_data["symbol"]) # Modifiers? + elif event_type == EventType.KeyReleaseEvent: + self.pressed_keys.difference_update({event_data["symbol"]}) # Modifiers? + elif event_type == EventType.MousePressEvent: + self.draggable_object_listners = [ + listner + for listner in self.event_listners[EventType.MouseDragEvent] + if listner.mobject.is_point_touching(self.mouse_point) + ] + elif event_type == EventType.MouseReleaseEvent: + self.draggable_object_listners = [] + + propagate_event = None + + if event_type == EventType.MouseDragEvent: + for listner in self.draggable_object_listners: + assert(isinstance(listner, EventListner)) + propagate_event = listner.callback(listner.mobject, event_data) + if propagate_event is not None and propagate_event is False: + return propagate_event + + elif event_type.value.startswith('mouse'): + for listner in self.event_listners[event_type]: + if listner.mobject.is_point_touching(self.mouse_point): + propagate_event = listner.callback( + listner.mobject, event_data) + if propagate_event is not None and propagate_event is False: + return propagate_event + + elif event_type.value.startswith('key'): + for listner in self.event_listners[event_type]: + propagate_event = listner.callback(listner.mobject, event_data) + if propagate_event is not None and propagate_event is False: + return propagate_event + + return propagate_event + + def get_listners_count(self): + return sum([len(value) for key, value in self.event_listners.items()]) + + def get_mouse_point(self): + return self.mouse_point + + def get_mouse_drag_point(self): + return self.mouse_drag_point + + def is_key_pressed(self, symbol): + return (symbol in self.pressed_keys) + + __iadd__ = add_listner + __isub__ = remove_listner + __call__ = dispatch + __len__ = get_listners_count diff --git a/manimlib/event_handler/event_listner.py b/manimlib/event_handler/event_listner.py new file mode 100644 index 00000000..2f8663f7 --- /dev/null +++ b/manimlib/event_handler/event_listner.py @@ -0,0 +1,15 @@ +class EventListner(object): + def __init__(self, mobject, event_type, event_callback): + self.mobject = mobject + self.event_type = event_type + self.callback = event_callback + + def __eq__(self, o: object) -> bool: + return_val = False + try: + return_val = self.callback == o.callback \ + and self.mobject == o.mobject \ + and self.event_type == o.event_type + except: + pass + return return_val diff --git a/manimlib/event_handler/event_type.py b/manimlib/event_handler/event_type.py new file mode 100644 index 00000000..6cd9f73e --- /dev/null +++ b/manimlib/event_handler/event_type.py @@ -0,0 +1,11 @@ +from enum import Enum + + +class EventType(Enum): + MouseMotionEvent = 'mouse_motion_event' + MousePressEvent = 'mouse_press_event' + MouseReleaseEvent = 'mouse_release_event' + MouseDragEvent = 'mouse_drag_event' + MouseScrollEvent = 'mouse_scroll_event' + KeyPressEvent = 'key_press_event' + KeyReleaseEvent = 'key_release_event' diff --git a/manimlib/extract_scene.py b/manimlib/extract_scene.py index 1a14ba36..6036765f 100644 --- a/manimlib/extract_scene.py +++ b/manimlib/extract_scene.py @@ -1,9 +1,15 @@ import inspect -import itertools as it import sys +import logging from manimlib.scene.scene import Scene -import manimlib.constants +from manimlib.config import get_custom_config + + +class BlankScene(Scene): + def construct(self): + exec(get_custom_config()["universal_import_line"]) + self.embed() def is_child_scene(obj, module): @@ -19,35 +25,31 @@ def is_child_scene(obj, module): def prompt_user_for_choice(scene_classes): - num_to_class = {} - for count, scene_class in zip(it.count(1), scene_classes): + name_to_class = {} + max_digits = len(str(len(scene_classes))) + for idx, scene_class in enumerate(scene_classes, start=1): name = scene_class.__name__ - print("%d: %s" % (count, name)) - num_to_class[count] = scene_class + print(f"{str(idx).zfill(max_digits)}: {name}") + name_to_class[name] = scene_class try: - user_input = input(manimlib.constants.CHOOSE_NUMBER_MESSAGE) + user_input = input( + "\nThat module has multiple scenes, " + "which ones would you like to render?" + "\nScene Name or Number: " + ) return [ - num_to_class[int(num_str)] - for num_str in user_input.split(",") + name_to_class[split_str] if not split_str.isnumeric() else scene_classes[int(split_str)-1] + for split_str in user_input.replace(" ", "").split(",") ] except KeyError: - print(manimlib.constants.INVALID_NUMBER_MESSAGE) + logging.log(logging.ERROR, "Invalid scene") sys.exit(2) - user_input = input(manimlib.constants.CHOOSE_NUMBER_MESSAGE) - return [ - num_to_class[int(num_str)] - for num_str in user_input.split(",") - ] except EOFError: sys.exit(1) -def get_scenes_to_render(scene_classes, config): - if len(scene_classes) == 0: - print(manimlib.constants.NO_SCENE_MESSAGE) - return [] - - scene_kwargs = dict([ +def get_scene_config(config): + return dict([ (key, config[key]) for key in [ "window_config", @@ -61,29 +63,32 @@ def get_scenes_to_render(scene_classes, config): ] ]) + +def get_scenes_to_render(scene_classes, scene_config, config): if config["write_all"]: - return [sc(**scene_kwargs) for sc in scene_classes] + return [sc(**scene_config) for sc in scene_classes] result = [] for scene_name in config["scene_names"]: found = False for scene_class in scene_classes: if scene_class.__name__ == scene_name: - scene = scene_class(**scene_kwargs) + scene = scene_class(**scene_config) result.append(scene) found = True break if not found and (scene_name != ""): - print( - manimlib.constants.SCENE_NOT_FOUND_MESSAGE.format( - scene_name - ), - file=sys.stderr + logging.log( + logging.ERROR, + f"No scene named {scene_name} found", ) if result: return result - result=[scene_classes[0]] if len(scene_classes) == 1 else prompt_user_for_choice(scene_classes) - return [scene_class(**scene_kwargs) for scene_class in result] + if len(scene_classes) == 1: + result = [scene_classes[0]] + else: + result = prompt_user_for_choice(scene_classes) + return [scene_class(**scene_config) for scene_class in result] def get_scene_classes_from_module(module): @@ -101,10 +106,11 @@ def get_scene_classes_from_module(module): def main(config): module = config["module"] + scene_config = get_scene_config(config) + if module is None: + # If no module was passed in, just play the blank scene + return [BlankScene(**scene_config)] + all_scene_classes = get_scene_classes_from_module(module) - scenes = get_scenes_to_render(all_scene_classes, config) + scenes = get_scenes_to_render(all_scene_classes, scene_config, config) return scenes - - -if __name__ == "__main__": - main() diff --git a/manimlib/files/Bubbles_speech.svg b/manimlib/files/Bubbles_speech.svg deleted file mode 100644 index 173ebf35..00000000 --- a/manimlib/files/Bubbles_speech.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/manimlib/files/Bubbles_thought.svg b/manimlib/files/Bubbles_thought.svg deleted file mode 100644 index c77ebca4..00000000 --- a/manimlib/files/Bubbles_thought.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - diff --git a/manimlib/files/PiCreatures_plain.svg b/manimlib/files/PiCreatures_plain.svg deleted file mode 100644 index 552043d6..00000000 --- a/manimlib/files/PiCreatures_plain.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - diff --git a/manimlib/for_3b1b_videos/common_scenes.py b/manimlib/for_3b1b_videos/common_scenes.py deleted file mode 100644 index 12bc160c..00000000 --- a/manimlib/for_3b1b_videos/common_scenes.py +++ /dev/null @@ -1,401 +0,0 @@ -import random - -from manimlib.animation.composition import LaggedStartMap -from manimlib.animation.creation import DrawBorderThenFill -from manimlib.animation.creation import Write -from manimlib.animation.fading import FadeIn -from manimlib.animation.fading import FadeOut -from manimlib.constants import * -from manimlib.for_3b1b_videos.pi_creature import Mortimer -from manimlib.for_3b1b_videos.pi_creature import Randolph -from manimlib.for_3b1b_videos.pi_creature_animations import Blink -from manimlib.for_3b1b_videos.pi_creature_scene import PiCreatureScene -from manimlib.mobject.geometry import DashedLine -from manimlib.mobject.geometry import Line -from manimlib.mobject.geometry import Rectangle -from manimlib.mobject.geometry import Square -from manimlib.mobject.svg.drawings import Logo -from manimlib.mobject.svg.drawings import PatreonLogo -from manimlib.mobject.svg.tex_mobject import TextMobject -from manimlib.mobject.types.vectorized_mobject import VGroup -from manimlib.mobject.mobject_update_utils import always_shift -from manimlib.scene.moving_camera_scene import MovingCameraScene -from manimlib.scene.scene import Scene -from manimlib.utils.rate_functions import linear -from manimlib.utils.space_ops import get_norm -from manimlib.utils.space_ops import normalize - - -class OpeningQuote(Scene): - CONFIG = { - "quote": [], - "quote_arg_separator": " ", - "highlighted_quote_terms": {}, - "author": "", - "fade_in_kwargs": { - "lag_ratio": 0.5, - "rate_func": linear, - "run_time": 5, - }, - "text_size": "\\Large", - "use_quotation_marks": True, - "top_buff": 1.0, - "author_buff": 1.0, - } - - def construct(self): - self.quote = self.get_quote() - self.author = self.get_author(self.quote) - - self.play(FadeIn(self.quote, **self.fade_in_kwargs)) - self.wait(2) - self.play(Write(self.author, run_time=3)) - self.wait() - - def get_quote(self, max_width=FRAME_WIDTH - 1): - text_mobject_kwargs = { - "alignment": "", - "arg_separator": self.quote_arg_separator, - } - if isinstance(self.quote, str): - if self.use_quotation_marks: - quote = TextMobject("``%s''" % - self.quote.strip(), **text_mobject_kwargs) - else: - quote = TextMobject("%s" % - self.quote.strip(), **text_mobject_kwargs) - else: - if self.use_quotation_marks: - words = [self.text_size + " ``"] + list(self.quote) + ["''"] - else: - words = [self.text_size] + list(self.quote) - quote = TextMobject(*words, **text_mobject_kwargs) - # TODO, make less hacky - if self.quote_arg_separator == " ": - quote[0].shift(0.2 * RIGHT) - quote[-1].shift(0.2 * LEFT) - for term, color in self.highlighted_quote_terms: - quote.set_color_by_tex(term, color) - quote.to_edge(UP, buff=self.top_buff) - if quote.get_width() > max_width: - quote.set_width(max_width) - return quote - - def get_author(self, quote): - author = TextMobject(self.text_size + " --" + self.author) - author.next_to(quote, DOWN, buff=self.author_buff) - author.set_color(YELLOW) - return author - - -class PatreonThanks(Scene): - CONFIG = { - "specific_patrons": [], - "max_patron_group_size": 20, - "patron_scale_val": 0.8, - } - - def construct(self): - morty = Mortimer() - morty.next_to(ORIGIN, DOWN) - - patreon_logo = PatreonLogo() - patreon_logo.to_edge(UP) - - patrons = list(map(TextMobject, self.specific_patronds)) - num_groups = float(len(patrons)) / self.max_patron_group_size - proportion_range = np.linspace(0, 1, num_groups + 1) - indices = (len(patrons) * proportion_range).astype('int') - patron_groups = [ - VGroup(*patrons[i:j]) - for i, j in zip(indices, indices[1:]) - ] - - for i, group in enumerate(patron_groups): - left_group = VGroup(*group[:len(group) / 2]) - right_group = VGroup(*group[len(group) / 2:]) - for subgroup, vect in (left_group, LEFT), (right_group, RIGHT): - subgroup.arrange(DOWN, aligned_edge=LEFT) - subgroup.scale(self.patron_scale_val) - subgroup.to_edge(vect) - - last_group = None - for i, group in enumerate(patron_groups): - anims = [] - if last_group is not None: - self.play( - FadeOut(last_group), - morty.look, UP + LEFT - ) - else: - anims += [ - DrawBorderThenFill(patreon_logo), - ] - self.play( - LaggedStartMap( - FadeIn, group, - run_time=2, - ), - morty.change, "gracious", group.get_corner(UP + LEFT), - *anims - ) - self.play(morty.look_at, group.get_corner(DOWN + LEFT)) - self.play(morty.look_at, group.get_corner(UP + RIGHT)) - self.play(morty.look_at, group.get_corner(DOWN + RIGHT)) - self.play(Blink(morty)) - last_group = group - - -class PatreonEndScreen(PatreonThanks, PiCreatureScene): - CONFIG = { - "n_patron_columns": 4, - "max_patron_width": 5, - "run_time": 20, - "randomize_order": True, - "capitalize": True, - "name_y_spacing": 0.6, - "thanks_words": "Many thanks to this channel's supporters", - "scroll_time": 20, - } - - def construct(self): - if self.randomize_order: - random.shuffle(self.specific_patrons) - if self.capitalize: - self.specific_patrons = [ - " ".join(map( - lambda s: s.capitalize(), - patron.split(" ") - )) - for patron in self.specific_patrons - ] - - # self.add_title() - self.scroll_through_patrons() - - def create_pi_creatures(self): - title = self.title = TextMobject("Clicky Stuffs") - title.scale(1.5) - title.to_edge(UP, buff=MED_SMALL_BUFF) - - randy, morty = self.pi_creatures = VGroup(Randolph(), Mortimer()) - for pi, vect in (randy, LEFT), (morty, RIGHT): - pi.set_height(title.get_height()) - pi.change_mode("thinking") - pi.look(DOWN) - pi.next_to(title, vect, buff=MED_LARGE_BUFF) - self.add(title, randy, morty) - self.foreground = VGroup(title, randy, morty) - return self.pi_creatures - - def scroll_through_patrons(self): - logo_box = Square(side_length=2.5) - logo_box.to_corner(DOWN + LEFT, buff=MED_LARGE_BUFF) - - black_rect = Rectangle( - fill_color=BLACK, - fill_opacity=1, - stroke_width=3, - stroke_color=BLACK, - width=FRAME_WIDTH, - height=0.6 * FRAME_HEIGHT, - ) - black_rect.to_edge(UP, buff=0) - line = DashedLine(FRAME_X_RADIUS * LEFT, FRAME_X_RADIUS * RIGHT) - line.move_to(ORIGIN) - - thanks = TextMobject(self.thanks_words) - thanks.scale(0.9) - thanks.next_to(black_rect.get_bottom(), UP, SMALL_BUFF) - thanks.set_color(YELLOW) - underline = Line(LEFT, RIGHT) - underline.match_width(thanks) - underline.scale(1.1) - underline.next_to(thanks, DOWN, SMALL_BUFF) - thanks.add(underline) - - changed_patron_names = list(map( - self.modify_patron_name, - self.specific_patrons, - )) - changed_patron_names.sort() - patrons = VGroup(*map( - TextMobject, - changed_patron_names, - )) - patrons.scale(self.patron_scale_val) - for patron in patrons: - if patron.get_width() > self.max_patron_width: - patron.set_width(self.max_patron_width) - columns = VGroup(*[ - VGroup(*patrons[i::self.n_patron_columns]) - for i in range(self.n_patron_columns) - ]) - column_x_spacing = 0.5 + max([c.get_width() for c in columns]) - - for i, column in enumerate(columns): - for n, name in enumerate(column): - name.shift(n * self.name_y_spacing * DOWN) - name.align_to(ORIGIN, LEFT) - column.move_to(i * column_x_spacing * RIGHT, UL) - columns.center() - - max_width = FRAME_WIDTH - 1 - if columns.get_width() > max_width: - columns.set_width(max_width) - underline.match_width(columns) - # thanks.to_edge(RIGHT, buff=MED_SMALL_BUFF) - columns.next_to(underline, DOWN, buff=4) - - columns.generate_target() - columns.target.to_edge(DOWN, buff=4) - vect = columns.target.get_center() - columns.get_center() - distance = get_norm(vect) - wait_time = self.scroll_time - always_shift( - columns, - direction=normalize(vect), - rate=(distance / wait_time) - ) - - self.add(columns, black_rect, line, thanks, self.foreground) - self.wait(wait_time) - - def modify_patron_name(self, name): - modification_map = { - "RedAgent14": "Brian Shepetofsky", - "DeathByShrimp": "Henry Bresnahan", - "akostrikov": "Aleksandr Kostrikov", - "Jacob Baxter": "Will Fleshman", - "Sansword Huang": "SansWord@TW", - "Sunil Nagaraj": "Ubiquity Ventures", - "Nitu Kitchloo": "Ish Kitchloo", - } - for n1, n2 in modification_map.items(): - if name.lower() == n1.lower(): - return n2 - return name - - -class LogoGenerationTemplate(MovingCameraScene): - def setup(self): - MovingCameraScene.setup(self) - frame = self.camera_frame - frame.shift(DOWN) - - self.logo = Logo() - name = TextMobject("3Blue1Brown") - name.scale(2.5) - name.next_to(self.logo, DOWN, buff=MED_LARGE_BUFF) - name.set_sheen(-0.2, DR) - self.channel_name = name - - def construct(self): - logo = self.logo - name = self.channel_name - - self.play( - Write(name, run_time=3), - *self.get_logo_animations(logo) - ) - self.wait() - - def get_logo_animations(self, logo): - return [] # For subclasses - - -class ExternallyAnimatedScene(Scene): - def construct(self): - raise Exception("Don't actually run this class.") - - -class TODOStub(Scene): - CONFIG = { - "message": "" - } - - def construct(self): - self.add(TextMobject("TODO: %s" % self.message)) - self.wait() - - -class Banner(Scene): - CONFIG = { - "camera_config": { - "pixel_height": 1440, - "pixel_width": 2560, - }, - "pi_height": 1.25, - "pi_bottom": 0.25 * DOWN, - "use_date": False, - "date": "Sunday, February 3rd", - "message_scale_val": 0.9, - "add_supporter_note": False, - "pre_date_text": "Next video on ", - } - - def __init__(self, **kwargs): - # Force these dimensions - self.camera_config = { - "pixel_height": 1440, - "pixel_width": 2560, - } - Scene.__init__(self, **kwargs) - - def construct(self): - pis = self.get_pis() - pis.set_height(self.pi_height) - pis.arrange(RIGHT, aligned_edge=DOWN) - pis.move_to(self.pi_bottom, DOWN) - self.pis = pis - self.add(pis) - - if self.use_date: - message = self.get_date_message() - else: - message = self.get_probabalistic_message() - message.scale(self.message_scale_val) - message.next_to(pis, DOWN) - self.add(message) - - if self.add_supporter_note: - note = self.get_supporter_note() - note.scale(0.5) - message.shift((MED_SMALL_BUFF - SMALL_BUFF) * UP) - note.next_to(message, DOWN, SMALL_BUFF) - self.add(note) - - yellow_parts = [sm for sm in message if sm.get_color() == YELLOW] - for pi in pis: - if yellow_parts: - pi.look_at(yellow_parts[-1]) - else: - pi.look_at(message) - - def get_pis(self): - return VGroup( - Randolph(color=BLUE_E, mode="pondering"), - Randolph(color=BLUE_D, mode="hooray"), - Randolph(color=BLUE_C, mode="sassy"), - Mortimer(color=GREY_BROWN, mode="thinking") - ) - - def get_probabalistic_message(self): - return TextMobject( - "New video every ", "Sunday ", - "(with probability 0.3)", - tex_to_color_map={"Sunday": YELLOW}, - ) - - def get_date_message(self): - return TextMobject( - self.pre_date_text, - self.date, - tex_to_color_map={self.date: YELLOW}, - ) - - def get_supporter_note(self): - return TextMobject( - "(Available to supporters for review now)", - color="#F96854", - ) diff --git a/manimlib/for_3b1b_videos/pi_class.py b/manimlib/for_3b1b_videos/pi_class.py deleted file mode 100644 index 77a813e1..00000000 --- a/manimlib/for_3b1b_videos/pi_class.py +++ /dev/null @@ -1,18 +0,0 @@ -from manimlib.constants import * -from manimlib.for_3b1b_videos.pi_creature import PiCreature -from manimlib.mobject.types.vectorized_mobject import VGroup - - -class PiCreatureClass(VGroup): - CONFIG = { - "width": 3, - "height": 2 - } - - def __init__(self, **kwargs): - VGroup.__init__(self, **kwargs) - for i in range(self.width): - for j in range(self.height): - pi = PiCreature().scale(0.3) - pi.move_to(i * DOWN + j * RIGHT) - self.add(pi) diff --git a/manimlib/for_3b1b_videos/pi_creature.py b/manimlib/for_3b1b_videos/pi_creature.py deleted file mode 100644 index 296c7c8d..00000000 --- a/manimlib/for_3b1b_videos/pi_creature.py +++ /dev/null @@ -1,395 +0,0 @@ -import os -import warnings - -import numpy as np - -from manimlib.constants import * -from manimlib.mobject.mobject import Mobject -from manimlib.mobject.geometry import Circle -from manimlib.mobject.svg.drawings import ThoughtBubble -from manimlib.mobject.svg.svg_mobject import SVGMobject -from manimlib.mobject.svg.tex_mobject import TextMobject -from manimlib.mobject.types.vectorized_mobject import VGroup -from manimlib.mobject.types.vectorized_mobject import VMobject -from manimlib.utils.config_ops import digest_config -from manimlib.utils.space_ops import get_norm -from manimlib.utils.space_ops import normalize - -pi_creature_dir_maybe = os.path.join(MEDIA_DIR, "assets", "PiCreature") -if os.path.exists(pi_creature_dir_maybe): - PI_CREATURE_DIR = pi_creature_dir_maybe -else: - PI_CREATURE_DIR = os.path.join("assets") - -PI_CREATURE_SCALE_FACTOR = 0.5 - -LEFT_EYE_INDEX = 0 -RIGHT_EYE_INDEX = 1 -LEFT_PUPIL_INDEX = 2 -RIGHT_PUPIL_INDEX = 3 -BODY_INDEX = 4 -MOUTH_INDEX = 5 - - -class PiCreature(SVGMobject): - CONFIG = { - "color": BLUE_E, - "file_name_prefix": "PiCreatures", - "stroke_width": 0, - "stroke_color": BLACK, - "fill_opacity": 1.0, - "height": 3, - "corner_scale_factor": 0.75, - "flip_at_start": False, - "is_looking_direction_purposeful": False, - "start_corner": None, - # Range of proportions along body where arms are - "right_arm_range": [0.55, 0.7], - "left_arm_range": [.34, .462], - "pupil_to_eye_width_ratio": 0.4, - "pupil_dot_to_pupil_width_ratio": 0.3, - } - - def __init__(self, mode="plain", **kwargs): - digest_config(self, kwargs) - self.mode = mode - self.parts_named = False - try: - svg_file = os.path.join( - PI_CREATURE_DIR, - f"{self.file_name_prefix}_{mode}.svg" - ) - SVGMobject.__init__(self, file_name=svg_file, **kwargs) - except Exception: - warnings.warn(f"No {self.file_name_prefix} design with mode {mode}") - svg_file = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - os.pardir, - "files", - "PiCreatures_plain.svg", - ) - SVGMobject.__init__(self, file_name=svg_file, **kwargs) - - if self.flip_at_start: - self.flip() - if self.start_corner is not None: - self.to_corner(self.start_corner) - self.unlock_triangulation() - - def align_data(self, mobject): - # This ensures that after a transform into a different mode, - # the pi creatures mode will be updated appropriately - SVGMobject.align_data(self, mobject) - if isinstance(mobject, PiCreature): - self.mode = mobject.get_mode() - - def name_parts(self): - self.mouth = self.submobjects[MOUTH_INDEX] - self.body = self.submobjects[BODY_INDEX] - self.pupils = VGroup(*[ - self.submobjects[LEFT_PUPIL_INDEX], - self.submobjects[RIGHT_PUPIL_INDEX] - ]) - self.eyes = VGroup(*[ - self.submobjects[LEFT_EYE_INDEX], - self.submobjects[RIGHT_EYE_INDEX] - ]) - self.eye_parts = VGroup(self.eyes, self.pupils) - self.parts_named = True - - def init_colors(self): - SVGMobject.init_colors(self) - if not self.parts_named: - self.name_parts() - self.mouth.set_fill(BLACK, opacity=1) - self.body.set_fill(self.color, opacity=1) - self.eyes.set_fill(WHITE, opacity=1) - self.init_pupils() - return self - - def init_pupils(self): - # Instead of what is drawn, make new circles. - # This is mostly because the paths associated - # with the eyes in all the drawings got slightly - # messed up. - for eye, pupil in zip(self.eyes, self.pupils): - pupil_r = eye.get_width() / 2 - pupil_r *= self.pupil_to_eye_width_ratio - dot_r = pupil_r - dot_r *= self.pupil_dot_to_pupil_width_ratio - - new_pupil = Circle( - radius=pupil_r, - color=BLACK, - fill_opacity=1, - stroke_width=0, - ) - dot = Circle( - radius=dot_r, - color=WHITE, - fill_opacity=1, - stroke_width=0, - ) - new_pupil.move_to(pupil) - pupil.become(new_pupil) - dot.shift( - new_pupil.point_from_proportion(3 / 8) - - dot.point_from_proportion(3 / 8) - ) - pupil.add(dot) - - def copy(self): - copy_mobject = SVGMobject.copy(self) - copy_mobject.name_parts() - return copy_mobject - - def set_color(self, color): - self.body.set_fill(color) - self.color = color - return self - - def change_mode(self, mode): - new_self = self.__class__(mode=mode) - new_self.match_style(self) - new_self.match_height(self) - if self.is_flipped() != new_self.is_flipped(): - new_self.flip() - new_self.shift(self.eyes.get_center() - new_self.eyes.get_center()) - if hasattr(self, "purposeful_looking_direction"): - new_self.look(self.purposeful_looking_direction) - self.become(new_self) - self.mode = mode - return self - - def get_mode(self): - return self.mode - - def look(self, direction): - norm = get_norm(direction) - if norm == 0: - return - direction /= norm - self.purposeful_looking_direction = direction - for pupil, eye in zip(self.pupils.split(), self.eyes.split()): - c = eye.get_center() - right = eye.get_right() - c - up = eye.get_top() - c - vect = direction[0] * right + direction[1] * up - v_norm = get_norm(vect) - p_radius = 0.5 * pupil.get_width() - vect *= (v_norm - 0.75 * p_radius) / v_norm - pupil.move_to(c + vect) - self.pupils[1].align_to(self.pupils[0], DOWN) - return self - - def look_at(self, point_or_mobject): - if isinstance(point_or_mobject, Mobject): - point = point_or_mobject.get_center() - else: - point = point_or_mobject - self.look(point - self.eyes.get_center()) - return self - - def change(self, new_mode, look_at_arg=None): - self.change_mode(new_mode) - if look_at_arg is not None: - self.look_at(look_at_arg) - return self - - def get_looking_direction(self): - vect = self.pupils.get_center() - self.eyes.get_center() - return normalize(vect) - - def get_look_at_spot(self): - return self.eyes.get_center() + self.get_looking_direction() - - def is_flipped(self): - return self.eyes.submobjects[0].get_center()[0] > \ - self.eyes.submobjects[1].get_center()[0] - - def blink(self): - eye_parts = self.eye_parts - eye_bottom_y = eye_parts.get_y(DOWN) - - for eye_part in eye_parts.family_members_with_points(): - eye_part.points[:, 1] = eye_bottom_y - - return self - - def to_corner(self, vect=None, **kwargs): - if vect is not None: - SVGMobject.to_corner(self, vect, **kwargs) - else: - self.scale(self.corner_scale_factor) - self.to_corner(DOWN + LEFT, **kwargs) - return self - - def get_bubble(self, *content, **kwargs): - bubble_class = kwargs.get("bubble_class", ThoughtBubble) - bubble = bubble_class(**kwargs) - if len(content) > 0: - if isinstance(content[0], str): - content_mob = TextMobject(*content) - else: - content_mob = content[0] - bubble.add_content(content_mob) - if "height" not in kwargs and "width" not in kwargs: - bubble.resize_to_content() - bubble.pin_to(self) - self.bubble = bubble - return bubble - - def make_eye_contact(self, pi_creature): - self.look_at(pi_creature.eyes) - pi_creature.look_at(self.eyes) - return self - - def shrug(self): - self.change_mode("shruggie") - top_mouth_point, bottom_mouth_point = [ - self.mouth.points[np.argmax(self.mouth.points[:, 1])], - self.mouth.points[np.argmin(self.mouth.points[:, 1])] - ] - self.look(top_mouth_point - bottom_mouth_point) - return self - - def get_arm_copies(self): - body = self.body - return VGroup(*[ - body.copy().pointwise_become_partial(body, *alpha_range) - for alpha_range in (self.right_arm_range, self.left_arm_range) - ]) - - def prepare_for_animation(self): - self.unlock_triangulation() - - def cleanup_from_animation(self): - self.lock_triangulation() - - -def get_all_pi_creature_modes(): - result = [] - prefix = PiCreature.CONFIG["file_name_prefix"] + "_" - suffix = ".svg" - for file in os.listdir(PI_CREATURE_DIR): - if file.startswith(prefix) and file.endswith(suffix): - result.append( - file[len(prefix):-len(suffix)] - ) - return result - - -class Randolph(PiCreature): - pass # Nothing more than an alternative name - - -class Mortimer(PiCreature): - CONFIG = { - "color": GREY_BROWN, - "flip_at_start": True, - } - - -class Mathematician(PiCreature): - CONFIG = { - "color": GREY, - } - - -class BabyPiCreature(PiCreature): - CONFIG = { - "scale_factor": 0.5, - "eye_scale_factor": 1.2, - "pupil_scale_factor": 1.3 - } - - def __init__(self, *args, **kwargs): - PiCreature.__init__(self, *args, **kwargs) - self.scale(self.scale_factor) - self.shift(LEFT) - self.to_edge(DOWN, buff=LARGE_BUFF) - eyes = VGroup(self.eyes, self.pupils) - eyes_bottom = eyes.get_bottom() - eyes.scale(self.eye_scale_factor) - eyes.move_to(eyes_bottom, aligned_edge=DOWN) - looking_direction = self.get_looking_direction() - for pupil in self.pupils: - pupil.scale_in_place(self.pupil_scale_factor) - self.look(looking_direction) - - -class TauCreature(PiCreature): - CONFIG = { - "file_name_prefix": "TauCreatures" - } - - -class ThreeLeggedPiCreature(PiCreature): - CONFIG = { - "file_name_prefix": "ThreeLeggedPiCreatures" - } - - -class Eyes(VMobject): - CONFIG = { - "height": 0.3, - "thing_to_look_at": None, - "mode": "plain", - } - - def __init__(self, body, **kwargs): - VMobject.__init__(self, **kwargs) - self.body = body - eyes = self.create_eyes() - self.become(eyes, copy_submobjects=False) - - def create_eyes(self, mode=None, thing_to_look_at=None): - if mode is None: - mode = self.mode - if thing_to_look_at is None: - thing_to_look_at = self.thing_to_look_at - self.thing_to_look_at = thing_to_look_at - self.mode = mode - looking_direction = None - - pi = PiCreature(mode=mode) - eyes = VGroup(pi.eyes, pi.pupils) - if self.submobjects: - eyes.match_height(self) - eyes.move_to(self, DOWN) - looking_direction = self[1].get_center() - self[0].get_center() - else: - eyes.set_height(self.height) - eyes.move_to(self.body.get_top(), DOWN) - - height = eyes.get_height() - if thing_to_look_at is not None: - pi.look_at(thing_to_look_at) - elif looking_direction is not None: - pi.look(looking_direction) - eyes.set_height(height) - - return eyes - - def change_mode(self, mode, thing_to_look_at=None): - new_eyes = self.create_eyes( - mode=mode, - thing_to_look_at=thing_to_look_at - ) - self.become(new_eyes, copy_submobjects=False) - return self - - def look_at(self, thing_to_look_at): - self.change_mode( - self.mode, - thing_to_look_at=thing_to_look_at - ) - return self - - def blink(self, **kwargs): # TODO, change Blink - bottom_y = self.get_bottom()[1] - for submob in self: - submob.apply_function( - lambda p: [p[0], bottom_y, p[2]] - ) - return self diff --git a/manimlib/for_3b1b_videos/pi_creature_animations.py b/manimlib/for_3b1b_videos/pi_creature_animations.py deleted file mode 100644 index 91df3773..00000000 --- a/manimlib/for_3b1b_videos/pi_creature_animations.py +++ /dev/null @@ -1,122 +0,0 @@ -from manimlib.animation.animation import Animation -from manimlib.animation.composition import AnimationGroup -from manimlib.animation.fading import FadeOut -from manimlib.animation.creation import DrawBorderThenFill -from manimlib.animation.creation import Write -from manimlib.animation.transform import ApplyMethod -from manimlib.animation.transform import MoveToTarget -from manimlib.constants import * -from manimlib.for_3b1b_videos.pi_class import PiCreatureClass -from manimlib.mobject.mobject import Group -from manimlib.mobject.svg.drawings import SpeechBubble -from manimlib.utils.config_ops import digest_config -from manimlib.utils.rate_functions import squish_rate_func -from manimlib.utils.rate_functions import there_and_back - - -class Blink(ApplyMethod): - CONFIG = { - "rate_func": squish_rate_func(there_and_back) - } - - def __init__(self, pi_creature, **kwargs): - ApplyMethod.__init__(self, pi_creature.blink, **kwargs) - - -class PiCreatureBubbleIntroduction(AnimationGroup): - CONFIG = { - "target_mode": "speaking", - "bubble_class": SpeechBubble, - "change_mode_kwargs": {}, - "bubble_creation_class": DrawBorderThenFill, - "bubble_creation_kwargs": {}, - "bubble_kwargs": {}, - "content_introduction_class": Write, - "content_introduction_kwargs": {}, - "look_at_arg": None, - } - - def __init__(self, pi_creature, *content, **kwargs): - digest_config(self, kwargs) - bubble = pi_creature.get_bubble( - *content, - bubble_class=self.bubble_class, - **self.bubble_kwargs - ) - Group(bubble, bubble.content).shift_onto_screen() - - pi_creature.generate_target() - pi_creature.target.change_mode(self.target_mode) - if self.look_at_arg is not None: - pi_creature.target.look_at(self.look_at_arg) - - change_mode = MoveToTarget(pi_creature, **self.change_mode_kwargs) - bubble_creation = self.bubble_creation_class( - bubble, **self.bubble_creation_kwargs - ) - content_introduction = self.content_introduction_class( - bubble.content, **self.content_introduction_kwargs - ) - AnimationGroup.__init__( - self, change_mode, bubble_creation, content_introduction, - **kwargs - ) - - -class PiCreatureSays(PiCreatureBubbleIntroduction): - CONFIG = { - "target_mode": "speaking", - "bubble_class": SpeechBubble, - } - - -class RemovePiCreatureBubble(AnimationGroup): - CONFIG = { - "target_mode": "plain", - "look_at_arg": None, - "remover": True, - } - - def __init__(self, pi_creature, **kwargs): - assert hasattr(pi_creature, "bubble") - digest_config(self, kwargs, locals()) - - pi_creature.generate_target() - pi_creature.target.change_mode(self.target_mode) - if self.look_at_arg is not None: - pi_creature.target.look_at(self.look_at_arg) - - AnimationGroup.__init__( - self, - MoveToTarget(pi_creature), - FadeOut(pi_creature.bubble), - FadeOut(pi_creature.bubble.content), - ) - - def clean_up_from_scene(self, scene=None): - AnimationGroup.clean_up_from_scene(self, scene) - self.pi_creature.bubble = None - if scene is not None: - scene.add(self.pi_creature) - - -class FlashThroughClass(Animation): - CONFIG = { - "highlight_color": GREEN, - } - - def __init__(self, mobject, mode="linear", **kwargs): - if not isinstance(mobject, PiCreatureClass): - raise Exception("FlashThroughClass mobject must be a PiCreatureClass") - digest_config(self, kwargs) - self.indices = list(range(mobject.height * mobject.width)) - if mode == "random": - np.random.shuffle(self.indices) - Animation.__init__(self, mobject, **kwargs) - - def interpolate_mobject(self, alpha): - index = int(np.floor(alpha * self.mobject.height * self.mobject.width)) - for pi in self.mobject: - pi.set_color(BLUE_E) - if index < self.mobject.height * self.mobject.width: - self.mobject[self.indices[index]].set_color(self.highlight_color) diff --git a/manimlib/for_3b1b_videos/pi_creature_scene.py b/manimlib/for_3b1b_videos/pi_creature_scene.py deleted file mode 100644 index 72bdbbb3..00000000 --- a/manimlib/for_3b1b_videos/pi_creature_scene.py +++ /dev/null @@ -1,383 +0,0 @@ -import itertools as it -import random - -from manimlib.animation.transform import ReplacementTransform -from manimlib.animation.transform import Transform -from manimlib.animation.transform import ApplyMethod -from manimlib.animation.composition import LaggedStart -from manimlib.constants import * -from manimlib.for_3b1b_videos.pi_creature import Mortimer -from manimlib.for_3b1b_videos.pi_creature import PiCreature -from manimlib.for_3b1b_videos.pi_creature import Randolph -from manimlib.for_3b1b_videos.pi_creature_animations import Blink -from manimlib.for_3b1b_videos.pi_creature_animations import PiCreatureBubbleIntroduction -from manimlib.for_3b1b_videos.pi_creature_animations import RemovePiCreatureBubble -from manimlib.mobject.mobject import Group -from manimlib.mobject.frame import ScreenRectangle -from manimlib.mobject.frame import FullScreenFadeRectangle -from manimlib.mobject.svg.drawings import SpeechBubble -from manimlib.mobject.svg.drawings import ThoughtBubble -from manimlib.mobject.types.vectorized_mobject import VGroup -from manimlib.scene.scene import Scene -from manimlib.utils.rate_functions import squish_rate_func -from manimlib.utils.rate_functions import there_and_back -from manimlib.utils.space_ops import get_norm - - -class PiCreatureScene(Scene): - CONFIG = { - "total_wait_time": 0, - "seconds_to_blink": 3, - "pi_creatures_start_on_screen": True, - "default_pi_creature_kwargs": { - "color": BLUE, - "flip_at_start": False, - }, - "default_pi_creature_start_corner": DL, - } - - def setup(self): - self.pi_creatures = VGroup(*self.create_pi_creatures()) - self.pi_creature = self.get_primary_pi_creature() - if self.pi_creatures_start_on_screen: - self.add(*self.pi_creatures) - - def create_pi_creatures(self): - """ - Likely updated for subclasses - """ - return VGroup(self.create_pi_creature()) - - def create_pi_creature(self): - pi_creature = PiCreature(**self.default_pi_creature_kwargs) - pi_creature.to_corner(self.default_pi_creature_start_corner) - return pi_creature - - def get_pi_creatures(self): - return self.pi_creatures - - def get_primary_pi_creature(self): - return self.pi_creatures[0] - - def any_pi_creatures_on_screen(self): - return len(self.get_on_screen_pi_creatures()) > 0 - - def get_on_screen_pi_creatures(self): - mobjects = self.get_mobject_family_members() - return VGroup(*[ - pi for pi in self.get_pi_creatures() - if pi in mobjects - ]) - - def introduce_bubble(self, *args, **kwargs): - if isinstance(args[0], PiCreature): - pi_creature = args[0] - content = args[1:] - else: - pi_creature = self.get_primary_pi_creature() - content = args - - bubble_class = kwargs.pop("bubble_class", SpeechBubble) - target_mode = kwargs.pop( - "target_mode", - "thinking" if bubble_class is ThoughtBubble else "speaking" - ) - bubble_kwargs = kwargs.pop("bubble_kwargs", {}) - bubble_removal_kwargs = kwargs.pop("bubble_removal_kwargs", {}) - added_anims = kwargs.pop("added_anims", []) - - anims = [] - on_screen_mobjects = self.get_mobject_family_members() - - def has_bubble(pi): - return hasattr(pi, "bubble") and \ - pi.bubble is not None and \ - pi.bubble in on_screen_mobjects - - pi_creatures_with_bubbles = list(filter(has_bubble, self.get_pi_creatures())) - if pi_creature in pi_creatures_with_bubbles: - pi_creatures_with_bubbles.remove(pi_creature) - old_bubble = pi_creature.bubble - bubble = pi_creature.get_bubble( - *content, - bubble_class=bubble_class, - **bubble_kwargs - ) - anims += [ - ReplacementTransform(old_bubble, bubble), - ReplacementTransform(old_bubble.content, bubble.content), - pi_creature.change_mode, target_mode - ] - else: - anims.append(PiCreatureBubbleIntroduction( - pi_creature, - *content, - bubble_class=bubble_class, - bubble_kwargs=bubble_kwargs, - target_mode=target_mode, - **kwargs - )) - anims += [ - RemovePiCreatureBubble(pi, **bubble_removal_kwargs) - for pi in pi_creatures_with_bubbles - ] - anims += added_anims - - self.play(*anims, **kwargs) - - def pi_creature_says(self, *args, **kwargs): - self.introduce_bubble( - *args, - bubble_class=SpeechBubble, - **kwargs - ) - - def pi_creature_thinks(self, *args, **kwargs): - self.introduce_bubble( - *args, - bubble_class=ThoughtBubble, - **kwargs - ) - - def say(self, *content, **kwargs): - self.pi_creature_says( - self.get_primary_pi_creature(), *content, **kwargs) - - def think(self, *content, **kwargs): - self.pi_creature_thinks( - self.get_primary_pi_creature(), *content, **kwargs) - - def compile_play_args_to_animation_list(self, *args, **kwargs): - """ - Add animations so that all pi creatures look at the - first mobject being animated with each .play call - """ - animations = Scene.compile_play_args_to_animation_list(self, *args, **kwargs) - anim_mobjects = Group(*[a.mobject for a in animations]) - all_movers = anim_mobjects.get_family() - if not self.any_pi_creatures_on_screen(): - return animations - - pi_creatures = self.get_on_screen_pi_creatures() - non_pi_creature_anims = [ - anim - for anim in animations - if len(set(anim.mobject.get_family()).intersection(pi_creatures)) == 0 - ] - if len(non_pi_creature_anims) == 0: - return animations - # Get pi creatures to look at whatever - # is being animated - first_anim = non_pi_creature_anims[0] - main_mobject = first_anim.mobject - for pi_creature in pi_creatures: - if pi_creature not in all_movers: - animations.append(ApplyMethod( - pi_creature.look_at, - main_mobject, - )) - return animations - - def blink(self): - self.play(Blink(random.choice(self.get_on_screen_pi_creatures()))) - - def joint_blink(self, pi_creatures=None, shuffle=True, **kwargs): - if pi_creatures is None: - pi_creatures = self.get_on_screen_pi_creatures() - creatures_list = list(pi_creatures) - if shuffle: - random.shuffle(creatures_list) - - def get_rate_func(pi): - index = creatures_list.index(pi) - proportion = float(index) / len(creatures_list) - start_time = 0.8 * proportion - return squish_rate_func( - there_and_back, - start_time, start_time + 0.2 - ) - - self.play(*[ - Blink(pi, rate_func=get_rate_func(pi), **kwargs) - for pi in creatures_list - ]) - return self - - def wait(self, time=1, blink=True, **kwargs): - if "stop_condition" in kwargs: - self.non_blink_wait(time, **kwargs) - return - while time >= 1: - time_to_blink = self.total_wait_time % self.seconds_to_blink == 0 - if blink and self.any_pi_creatures_on_screen() and time_to_blink: - self.blink() - else: - self.non_blink_wait(**kwargs) - time -= 1 - self.total_wait_time += 1 - if time > 0: - self.non_blink_wait(time, **kwargs) - return self - - def non_blink_wait(self, time=1, **kwargs): - Scene.wait(self, time, **kwargs) - return self - - def change_mode(self, mode): - self.play(self.get_primary_pi_creature().change_mode, mode) - - def look_at(self, thing_to_look_at, pi_creatures=None, **kwargs): - if pi_creatures is None: - pi_creatures = self.get_pi_creatures() - args = list(it.chain(*[ - [pi.look_at, thing_to_look_at] - for pi in pi_creatures - ])) - self.play(*args, **kwargs) - - -class MortyPiCreatureScene(PiCreatureScene): - CONFIG = { - "default_pi_creature_kwargs": { - "color": GREY_BROWN, - "flip_at_start": True, - }, - "default_pi_creature_start_corner": DR, - } - - -class TeacherStudentsScene(PiCreatureScene): - CONFIG = { - "student_colors": [BLUE_D, BLUE_E, BLUE_C], - "teacher_color": GREY_BROWN, - "background_color": DARKER_GREY, - "student_scale_factor": 0.8, - "seconds_to_blink": 2, - "screen_height": 3, - } - - def setup(self): - self.background = FullScreenFadeRectangle( - fill_color=self.background_color, - fill_opacity=1, - ) - self.add(self.background) - PiCreatureScene.setup(self) - self.screen = ScreenRectangle(height=self.screen_height) - self.screen.to_corner(UP + LEFT) - self.hold_up_spot = self.teacher.get_corner(UP + LEFT) + MED_LARGE_BUFF * UP - - def create_pi_creatures(self): - self.teacher = Mortimer(color=self.teacher_color) - self.teacher.to_corner(DOWN + RIGHT) - self.teacher.look(DOWN + LEFT) - self.students = VGroup(*[ - Randolph(color=c) - for c in self.student_colors - ]) - self.students.arrange(RIGHT) - self.students.scale(self.student_scale_factor) - self.students.to_corner(DOWN + LEFT) - self.teacher.look_at(self.students[-1].eyes) - for student in self.students: - student.look_at(self.teacher.eyes) - - return [self.teacher] + list(self.students) - - def get_teacher(self): - return self.teacher - - def get_students(self): - return self.students - - def teacher_says(self, *content, **kwargs): - return self.pi_creature_says( - self.get_teacher(), *content, **kwargs - ) - - def student_says(self, *content, **kwargs): - if "target_mode" not in kwargs: - target_mode = random.choice([ - "raise_right_hand", - "raise_left_hand", - ]) - kwargs["target_mode"] = target_mode - if "bubble_kwargs" not in kwargs: - kwargs["bubble_kwargs"] = {"direction": LEFT} - student = self.get_students()[kwargs.get("student_index", 2)] - return self.pi_creature_says( - student, *content, **kwargs - ) - - def teacher_thinks(self, *content, **kwargs): - return self.pi_creature_thinks( - self.get_teacher(), *content, **kwargs - ) - - def student_thinks(self, *content, **kwargs): - student = self.get_students()[kwargs.get("student_index", 2)] - return self.pi_creature_thinks(student, *content, **kwargs) - - def change_all_student_modes(self, mode, **kwargs): - self.change_student_modes(*[mode] * len(self.students), **kwargs) - - def change_student_modes(self, *modes, **kwargs): - added_anims = kwargs.pop("added_anims", []) - self.play( - self.get_student_changes(*modes, **kwargs), - *added_anims - ) - - def get_student_changes(self, *modes, **kwargs): - pairs = list(zip(self.get_students(), modes)) - pairs = [(s, m) for s, m in pairs if m is not None] - start = VGroup(*[s for s, m in pairs]) - target = VGroup(*[s.copy().change_mode(m) for s, m in pairs]) - if "look_at_arg" in kwargs: - for pi in target: - pi.look_at(kwargs["look_at_arg"]) - anims = [ - Transform(s, t) - for s, t in zip(start, target) - ] - return LaggedStart( - *anims, - lag_ratio=kwargs.get("lag_ratio", 0.5), - run_time=1, - ) - # return Transform( - # start, target, - # lag_ratio=lag_ratio, - # run_time=2 - # ) - - def zoom_in_on_thought_bubble(self, bubble=None, radius=FRAME_Y_RADIUS + FRAME_X_RADIUS): - if bubble is None: - for pi in self.get_pi_creatures(): - if hasattr(pi, "bubble") and isinstance(pi.bubble, ThoughtBubble): - bubble = pi.bubble - break - if bubble is None: - raise Exception("No pi creatures have a thought bubble") - vect = -bubble.get_bubble_center() - - def func(point): - centered = point + vect - return radius * centered / get_norm(centered) - self.play(*[ - ApplyPointwiseFunction(func, mob) - for mob in self.get_mobjects() - ]) - - def teacher_holds_up(self, mobject, target_mode="raise_right_hand", added_anims=None, **kwargs): - mobject.move_to(self.hold_up_spot, DOWN) - mobject.shift_onto_screen() - mobject_copy = mobject.copy() - mobject_copy.shift(DOWN) - mobject_copy.fade(1) - added_anims = added_anims or [] - self.play( - ReplacementTransform(mobject_copy, mobject), - self.teacher.change, target_mode, - *added_anims - ) diff --git a/manimlib/imports.py b/manimlib/imports.py deleted file mode 100644 index ae5a3e7a..00000000 --- a/manimlib/imports.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -I won't pretend like this is best practice, by in creating animations for a video, -it can be very nice to simply have all of the Mobjects, Animations, Scenes, etc. -of manim available without having to worry about what namespace they come from. - -Rather than having a large pile of "from import *" at the top of every such -script, the intent of this file is to make it so that one can just include -"from manimlib.imports import *". The effects of adding more modules -or refactoring the library on current or older scene scripts should be entirely -addressible by changing this file. - -Note: One should NOT import from this file for main library code, it is meant only -as a convenience for scripts creating scenes for videos. -""" - - -from manimlib.constants import * - -from manimlib.animation.animation import * -from manimlib.animation.composition import * -from manimlib.animation.creation import * -from manimlib.animation.fading import * -from manimlib.animation.growing import * -from manimlib.animation.indication import * -from manimlib.animation.movement import * -from manimlib.animation.numbers import * -from manimlib.animation.rotation import * -from manimlib.animation.specialized import * -from manimlib.animation.transform import * -from manimlib.animation.update import * - -from manimlib.camera.camera import * - -from manimlib.mobject.coordinate_systems import * -from manimlib.mobject.changing import * -from manimlib.mobject.frame import * -from manimlib.mobject.functions import * -from manimlib.mobject.geometry import * -from manimlib.mobject.matrix import * -from manimlib.mobject.mobject import * -from manimlib.mobject.number_line import * -from manimlib.mobject.numbers import * -from manimlib.mobject.probability import * -from manimlib.mobject.shape_matchers import * -from manimlib.mobject.svg.brace import * -from manimlib.mobject.svg.drawings import * -from manimlib.mobject.svg.svg_mobject import * -from manimlib.mobject.svg.tex_mobject import * -from manimlib.mobject.svg.text_mobject import * -from manimlib.mobject.three_dimensions import * -from manimlib.mobject.types.image_mobject import * -from manimlib.mobject.types.point_cloud_mobject import * -from manimlib.mobject.types.surface_mobject import * -from manimlib.mobject.types.vectorized_mobject import * -from manimlib.mobject.mobject_update_utils import * -from manimlib.mobject.value_tracker import * -from manimlib.mobject.vector_field import * - -from manimlib.for_3b1b_videos.common_scenes import * -from manimlib.for_3b1b_videos.pi_creature import * -from manimlib.for_3b1b_videos.pi_creature_animations import * -from manimlib.for_3b1b_videos.pi_creature_scene import * - -from manimlib.once_useful_constructs.arithmetic import * -from manimlib.once_useful_constructs.combinatorics import * -from manimlib.once_useful_constructs.complex_transformation_scene import * -from manimlib.once_useful_constructs.counting import * -from manimlib.once_useful_constructs.fractals import * -from manimlib.once_useful_constructs.graph_theory import * -from manimlib.once_useful_constructs.light import * - -from manimlib.scene.graph_scene import * -from manimlib.scene.moving_camera_scene import * -from manimlib.scene.reconfigurable_scene import * -from manimlib.scene.scene import * -from manimlib.scene.sample_space_scene import * -from manimlib.scene.graph_scene import * -from manimlib.scene.scene_from_video import * -from manimlib.scene.three_d_scene import * -from manimlib.scene.vector_space_scene import * -from manimlib.scene.zoomed_scene import * - -from manimlib.utils.bezier import * -from manimlib.utils.color import * -from manimlib.utils.config_ops import * -from manimlib.utils.debug import * -from manimlib.utils.images import * -from manimlib.utils.iterables import * -from manimlib.utils.file_ops import * -from manimlib.utils.paths import * -from manimlib.utils.rate_functions import * -from manimlib.utils.simple_functions import * -from manimlib.utils.sounds import * -from manimlib.utils.space_ops import * -from manimlib.utils.strings import * - -# Non manim libraries that are also nice to have without thinking - -import inspect -import itertools as it -import numpy as np -import operator as op -import os -import random -import re -import string -import sys -import math - -from PIL import Image -from colour import Color diff --git a/manimlib/media_dir.txt b/manimlib/media_dir.txt deleted file mode 100644 index 27949aaf..00000000 --- a/manimlib/media_dir.txt +++ /dev/null @@ -1 +0,0 @@ -media \ No newline at end of file diff --git a/manimlib/mobject/__init__.py b/manimlib/mobject/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manimlib/mobject/changing.py b/manimlib/mobject/changing.py index 20f74a4f..69e9302b 100644 --- a/manimlib/mobject/changing.py +++ b/manimlib/mobject/changing.py @@ -84,15 +84,15 @@ class TracedPath(VMobject): def update_path(self): new_point = self.traced_point_func() - if self.has_no_points(): + if not self.has_points(): self.start_new_path(new_point) self.add_line_to(new_point) else: # Set the end to be the new point - self.points[-1] = new_point + self.get_points()[-1] = new_point # Second to last point - nppcc = self.n_points_per_cubic_curve - dist = get_norm(new_point - self.points[-nppcc]) + nppcc = self.n_points_per_curve + dist = get_norm(new_point - self.get_points()[-nppcc]) if dist >= self.min_distance_to_new_point: self.add_line_to(new_point) diff --git a/manimlib/mobject/coordinate_systems.py b/manimlib/mobject/coordinate_systems.py index 288b6354..f02706b7 100644 --- a/manimlib/mobject/coordinate_systems.py +++ b/manimlib/mobject/coordinate_systems.py @@ -2,17 +2,21 @@ import numpy as np import numbers from manimlib.constants import * -from manimlib.mobject.functions import ParametricFunction +from manimlib.mobject.functions import ParametricCurve from manimlib.mobject.geometry import Arrow from manimlib.mobject.geometry import Line +from manimlib.mobject.geometry import DashedLine +from manimlib.mobject.geometry import Rectangle from manimlib.mobject.number_line import NumberLine -from manimlib.mobject.svg.tex_mobject import TexMobject +from manimlib.mobject.svg.tex_mobject import Tex from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.utils.config_ops import merge_dicts_recursively from manimlib.utils.simple_functions import binary_search from manimlib.utils.space_ops import angle_of_vector +from manimlib.utils.space_ops import get_norm +from manimlib.utils.space_ops import rotate_vector -# TODO: There should be much more code reuse between Axes, NumberPlane and GraphScene +EPSILON = 1e-8 class CoordinateSystem(): @@ -21,10 +25,11 @@ class CoordinateSystem(): """ CONFIG = { "dimension": 2, - "x_min": -FRAME_X_RADIUS, - "x_max": FRAME_X_RADIUS, - "y_min": -FRAME_Y_RADIUS, - "y_max": FRAME_Y_RADIUS, + "x_range": np.array([-8.0, 8.0, 1.0]), + "y_range": np.array([-4.0, 4.0, 1.0]), + "width": None, + "height": None, + "num_sampled_graph_points_per_tick": 5, } def coords_to_point(self, *coords): @@ -41,9 +46,15 @@ class CoordinateSystem(): """Abbreviation for point_to_coords""" return self.point_to_coords(point) + def get_origin(self): + return self.c2p(*[0] * self.dimension) + def get_axes(self): raise Exception("Not implemented") + def get_all_ranges(self): + raise Exception("Not implemented") + def get_axis(self, index): return self.get_axes()[index] @@ -69,7 +80,7 @@ class CoordinateSystem(): ) def get_axis_label(self, label_tex, axis, edge, direction, buff=MED_SMALL_BUFF): - label = TexMobject(label_tex) + label = Tex(label_tex) label.next_to( axis.get_edge_center(edge), direction, buff=buff @@ -84,16 +95,35 @@ class CoordinateSystem(): ) return self.axis_labels - def get_graph(self, function, x_min=None, x_max=None, **kwargs): - if x_min is None: - x_min = self.x_min - if x_max is None: - x_max = self.x_max + def get_line_from_axis_to_point(self, index, point, + line_func=DashedLine, + color=GREY_A, + stroke_width=2): + axis = self.get_axis(index) + line = line_func(axis.get_projection(point), point) + line.set_stroke(color, stroke_width) + return line - graph = ParametricFunction( - lambda t: self.coords_to_point(t, function(t)), - t_min=x_min, - t_max=x_max, + def get_v_line(self, point, **kwargs): + return self.get_line_from_axis_to_point(0, point, **kwargs) + + def get_h_line(self, point, **kwargs): + return self.get_line_from_axis_to_point(1, point, **kwargs) + + # Useful for graphing + def get_graph(self, function, x_range=None, **kwargs): + t_range = np.array(self.x_range, dtype=float) + if x_range is not None: + t_range[:len(x_range)] = x_range + # For axes, the third coordinate of x_range indicates + # tick frequency. But for functions, it indicates a + # sample frequency + if x_range is None or len(x_range) < 3: + t_range[2] /= self.num_sampled_graph_points_per_tick + + graph = ParametricCurve( + lambda t: self.c2p(t, function(t)), + t_range=t_range, **kwargs ) graph.underlying_function = function @@ -101,10 +131,8 @@ class CoordinateSystem(): def get_parametric_curve(self, function, **kwargs): dim = self.dimension - graph = ParametricFunction( - lambda t: self.coords_to_point( - *function(t)[:dim] - ), + graph = ParametricCurve( + lambda t: self.coords_to_point(*function(t)[:dim]), **kwargs ) graph.underlying_function = function @@ -119,143 +147,239 @@ class CoordinateSystem(): graph.point_from_proportion(a) )[0], target=x, - lower_bound=self.x_min, - upper_bound=self.x_max, + lower_bound=self.x_range[0], + upper_bound=self.x_range[1], ) if alpha is not None: return graph.point_from_proportion(alpha) else: return None + def i2gp(self, x, graph): + """ + Alias for input_to_graph_point + """ + return self.input_to_graph_point(x, graph) + + def get_graph_label(self, + graph, + label="f(x)", + x=None, + direction=RIGHT, + buff=MED_SMALL_BUFF, + color=None): + if isinstance(label, str): + label = Tex(label) + if color is None: + label.match_color(graph) + if x is None: + # Searching from the right, find a point + # whose y value is in bounds + max_y = FRAME_Y_RADIUS - label.get_height() + max_x = FRAME_X_RADIUS - label.get_width() + for x0 in np.arange(*self.x_range)[::-1]: + pt = self.i2gp(x0, graph) + if abs(pt[0]) < max_x and abs(pt[1]) < max_y: + x = x0 + break + if x is None: + x = self.x_range[1] + + point = self.input_to_graph_point(x, graph) + angle = self.angle_of_tangent(x, graph) + normal = rotate_vector(RIGHT, angle + 90 * DEGREES) + if normal[1] < 0: + normal *= -1 + label.next_to(point, normal, buff=buff) + label.shift_onto_screen() + return label + + def get_v_line_to_graph(self, x, graph, **kwargs): + return self.get_v_line(self.i2gp(x, graph), **kwargs) + + def get_h_line_to_graph(self, x, graph, **kwargs): + return self.get_h_line(self.i2gp(x, graph), **kwargs) + + # For calculus + def angle_of_tangent(self, x, graph, dx=EPSILON): + p0 = self.input_to_graph_point(x, graph) + p1 = self.input_to_graph_point(x + dx, graph) + return angle_of_vector(p1 - p0) + + def slope_of_tangent(self, x, graph, **kwargs): + return np.tan(self.angle_of_tangent(x, graph, **kwargs)) + + def get_tangent_line(self, x, graph, length=5, line_func=Line): + line = line_func(LEFT, RIGHT) + line.set_width(length) + line.rotate(self.angle_of_tangent(x, graph)) + line.move_to(self.input_to_graph_point(x, graph)) + return line + + def get_riemann_rectangles(self, + graph, + x_range=None, + dx=None, + input_sample_type="left", + stroke_width=1, + stroke_color=BLACK, + fill_opacity=1, + colors=(BLUE, GREEN), + show_signed_area=True): + if x_range is None: + x_range = self.x_range[:2] + if dx is None: + dx = self.x_range[2] + if len(x_range) < 3: + x_range = [*x_range, dx] + + rects = [] + xs = np.arange(*x_range) + for x0, x1 in zip(xs, xs[1:]): + if input_sample_type == "left": + sample = x0 + elif input_sample_type == "right": + sample = x1 + elif input_sample_type == "center": + sample = 0.5 * x0 + 0.5 * x1 + else: + raise Exception("Invalid input sample type") + height = get_norm( + self.i2gp(sample, graph) - self.c2p(sample, 0) + ) + rect = Rectangle(width=x1 - x0, height=height) + rect.move_to(self.c2p(x0, 0), DL) + rects.append(rect) + result = VGroup(*rects) + result.set_submobject_colors_by_gradient(*colors) + result.set_style( + stroke_width=stroke_width, + stroke_color=stroke_color, + fill_opacity=fill_opacity, + ) + return result + + def get_area_under_graph(self, graph, x_range, fill_color=BLUE, fill_opacity=1): + # TODO + pass + class Axes(VGroup, CoordinateSystem): CONFIG = { "axis_config": { - "color": LIGHT_GREY, "include_tip": True, - "exclude_zero_from_default_numbers": True, + "numbers_to_exclude": [0], }, "x_axis_config": {}, "y_axis_config": { - "label_direction": LEFT, + "line_to_number_direction": LEFT, }, - "center_point": ORIGIN, + "height": FRAME_HEIGHT - 2, + "width": FRAME_WIDTH - 2, } - def __init__(self, **kwargs): - VGroup.__init__(self, **kwargs) + def __init__(self, + x_range=None, + y_range=None, + **kwargs): + super().__init__(**kwargs) + if x_range is not None: + self.x_range[:len(x_range)] = x_range + if y_range is not None: + self.y_range[:len(y_range)] = y_range + self.x_axis = self.create_axis( - self.x_min, self.x_max, self.x_axis_config + self.x_range, self.x_axis_config, self.width, ) self.y_axis = self.create_axis( - self.y_min, self.y_max, self.y_axis_config + self.y_range, self.y_axis_config, self.height ) self.y_axis.rotate(90 * DEGREES, about_point=ORIGIN) - # Add as a separate group incase various other + # Add as a separate group in case various other # mobjects are added to self, as for example in # NumberPlane below self.axes = VGroup(self.x_axis, self.y_axis) self.add(*self.axes) - self.shift(self.center_point) + self.center() - def create_axis(self, min_val, max_val, axis_config): - new_config = merge_dicts_recursively( - self.axis_config, - {"x_min": min_val, "x_max": max_val}, - axis_config, - ) - return NumberLine(**new_config) + def create_axis(self, range_terms, axis_config, length): + new_config = merge_dicts_recursively(self.axis_config, axis_config) + new_config["width"] = length + axis = NumberLine(range_terms, **new_config) + axis.shift(-axis.n2p(0)) + return axis def coords_to_point(self, *coords): origin = self.x_axis.number_to_point(0) - result = np.array(origin) + result = origin.copy() for axis, coord in zip(self.get_axes(), coords): result += (axis.number_to_point(coord) - origin) return result - def c2p(self, *coords): - return self.coords_to_point(*coords) - def point_to_coords(self, point): return tuple([ axis.point_to_number(point) for axis in self.get_axes() ]) - def p2c(self, point): - return self.point_to_coords(point) - def get_axes(self): return self.axes - def get_coordinate_labels(self, x_vals=None, y_vals=None): - if x_vals is None: - x_vals = [] - if y_vals is None: - y_vals = [] - x_mobs = self.get_x_axis().get_number_mobjects(*x_vals) - y_mobs = self.get_y_axis().get_number_mobjects(*y_vals) + def get_all_ranges(self): + return [self.x_range, self.y_range] - self.coordinate_labels = VGroup(x_mobs, y_mobs) + def add_coordinate_labels(self, + x_values=None, + y_values=None, + **kwargs): + axes = self.get_axes() + self.coordinate_labels = VGroup() + for axis, values in zip(axes, [x_values, y_values]): + labels = axis.add_numbers(values, **kwargs) + self.coordinate_labels.add(labels) return self.coordinate_labels - def add_coordinates(self, x_vals=None, y_vals=None): - self.add(self.get_coordinate_labels(x_vals, y_vals)) - return self - class ThreeDAxes(Axes): CONFIG = { "dimension": 3, - "x_min": -5.5, - "x_max": 5.5, - "y_min": -5.5, - "y_max": 5.5, + "x_range": np.array([-6.0, 6.0, 1.0]), + "y_range": np.array([-5.0, 5.0, 1.0]), + "z_range": np.array([-4.0, 4.0, 1.0]), "z_axis_config": {}, - "z_min": -3.5, - "z_max": 3.5, "z_normal": DOWN, + "height": None, + "width": None, + "depth": None, "num_axis_pieces": 20, - "light_source": 9 * DOWN + 7 * LEFT + 10 * OUT, + "gloss": 0.5, } - def __init__(self, **kwargs): - Axes.__init__(self, **kwargs) - z_axis = self.z_axis = self.create_axis( - self.z_min, self.z_max, self.z_axis_config + def __init__(self, x_range=None, y_range=None, z_range=None, **kwargs): + Axes.__init__(self, x_range, y_range, **kwargs) + if z_range is not None: + self.z_range[:len(z_range)] = z_range + + z_axis = self.create_axis( + self.z_range, + self.z_axis_config, + self.depth, ) - z_axis.rotate(-np.pi / 2, UP, about_point=ORIGIN) + z_axis.rotate(-PI / 2, UP, about_point=ORIGIN) z_axis.rotate( angle_of_vector(self.z_normal), OUT, about_point=ORIGIN ) + z_axis.shift(self.x_axis.n2p(0)) self.axes.add(z_axis) self.add(z_axis) + self.z_axis = z_axis - self.add_3d_pieces() - self.set_axis_shading() - - def add_3d_pieces(self): for axis in self.axes: - axis.pieces = VGroup( - *axis.get_pieces(self.num_axis_pieces) - ) - axis.add(axis.pieces) - axis.set_stroke(width=0, family=False) - axis.set_shade_in_3d(True) + axis.insert_n_curves(self.num_axis_pieces - 1) - def set_axis_shading(self): - def make_func(axis): - vect = self.light_source - return lambda: ( - axis.get_edge_center(-vect), - axis.get_edge_center(vect), - ) - for axis in self: - for submob in axis.family_members_with_points(): - submob.get_gradient_start_and_end_points = make_func(axis) - submob.get_unit_normal = lambda a: np.ones(3) - submob.set_sheen(0.2) + def get_all_ranges(self): + return [self.x_range, self.y_range, self.z_range] class NumberPlane(Axes): @@ -266,27 +390,26 @@ class NumberPlane(Axes): "include_ticks": False, "include_tip": False, "line_to_number_buff": SMALL_BUFF, - "label_direction": DR, - "number_scale_val": 0.5, + "line_to_number_direction": DL, }, "y_axis_config": { - "label_direction": DR, + "line_to_number_direction": DL, }, "background_line_style": { "stroke_color": BLUE_D, "stroke_width": 2, "stroke_opacity": 1, }, + "height": None, + "width": None, # Defaults to a faded version of line_config "faded_line_style": None, - "x_line_frequency": 1, - "y_line_frequency": 1, "faded_line_ratio": 1, "make_smooth_after_applying_functions": True, } - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, x_range=None, y_range=None, **kwargs): + super().__init__(x_range, y_range, **kwargs) self.init_background_lines() def init_background_lines(self): @@ -300,12 +423,8 @@ class NumberPlane(Axes): self.faded_line_style = style self.background_lines, self.faded_lines = self.get_lines() - self.background_lines.set_style( - **self.background_line_style, - ) - self.faded_lines.set_style( - **self.faded_line_style, - ) + self.background_lines.set_style(**self.background_line_style) + self.faded_lines.set_style(**self.faded_line_style) self.add_to_back( self.faded_lines, self.background_lines, @@ -314,45 +433,32 @@ class NumberPlane(Axes): def get_lines(self): x_axis = self.get_x_axis() y_axis = self.get_y_axis() - x_freq = self.x_line_frequency - y_freq = self.y_line_frequency - x_lines1, x_lines2 = self.get_lines_parallel_to_axis( - x_axis, y_axis, x_freq, - self.faded_line_ratio, - ) - y_lines1, y_lines2 = self.get_lines_parallel_to_axis( - y_axis, x_axis, y_freq, - self.faded_line_ratio, - ) + x_lines1, x_lines2 = self.get_lines_parallel_to_axis(x_axis, y_axis) + y_lines1, y_lines2 = self.get_lines_parallel_to_axis(y_axis, x_axis) lines1 = VGroup(*x_lines1, *y_lines1) lines2 = VGroup(*x_lines2, *y_lines2) return lines1, lines2 - def get_lines_parallel_to_axis(self, axis1, axis2, freq, ratio): + def get_lines_parallel_to_axis(self, axis1, axis2): + freq = axis1.x_step + ratio = self.faded_line_ratio line = Line(axis1.get_start(), axis1.get_end()) dense_freq = (1 + ratio) step = (1 / dense_freq) * freq lines1 = VGroup() lines2 = VGroup() - ranges = ( - np.arange(0, axis2.x_max, step), - np.arange(0, axis2.x_min, -step), - ) - for inputs in ranges: - for k, x in enumerate(inputs): - new_line = line.copy() - new_line.move_to(axis2.number_to_point(x)) - if k % (1 + ratio) == 0: - lines1.add(new_line) - else: - lines2.add(new_line) + inputs = np.arange(axis2.x_min, axis2.x_max + step, step) + for i, x in enumerate(inputs): + new_line = line.copy() + new_line.shift(axis2.n2p(x) - axis2.n2p(0)) + if i % (1 + ratio) == 0: + lines1.add(new_line) + else: + lines2.add(new_line) return lines1, lines2 - def get_center_point(self): - return self.coords_to_point(0, 0) - def get_x_unit_size(self): return self.get_x_axis().get_unit_size() @@ -364,19 +470,14 @@ class NumberPlane(Axes): def get_vector(self, coords, **kwargs): kwargs["buff"] = 0 - return Arrow( - self.coords_to_point(0, 0), - self.coords_to_point(*coords), - **kwargs - ) + return Arrow(self.c2p(0, 0), self.c2p(*coords), **kwargs) def prepare_for_nonlinear_transform(self, num_inserted_curves=50): for mob in self.family_members_with_points(): num_curves = mob.get_num_curves() if num_inserted_curves > num_curves: - mob.insert_n_curves( - num_inserted_curves - num_curves - ) + mob.insert_n_curves(num_inserted_curves - num_curves) + mob.make_smooth_after_applying_functions = True return self @@ -401,15 +502,13 @@ class ComplexPlane(NumberPlane): return self.point_to_number(point) def get_default_coordinate_values(self): - x_numbers = self.get_x_axis().default_numbers_to_display() - y_numbers = self.get_y_axis().default_numbers_to_display() - y_numbers = [ - complex(0, y) for y in y_numbers if y != 0 - ] + x_numbers = self.get_x_axis().get_tick_range()[1:] + y_numbers = self.get_y_axis().get_tick_range()[1:] + y_numbers = [complex(0, y) for y in y_numbers if y != 0] return [*x_numbers, *y_numbers] - def get_coordinate_labels(self, *numbers, **kwargs): - if len(numbers) == 0: + def add_coordinate_labels(self, numbers=None, **kwargs): + if numbers is None: numbers = self.get_default_coordinate_values() self.coordinate_labels = VGroup() @@ -418,17 +517,11 @@ class ComplexPlane(NumberPlane): if abs(z.imag) > abs(z.real): axis = self.get_y_axis() value = z.imag - kwargs = merge_dicts_recursively( - kwargs, - {"number_config": {"unit": "i"}}, - ) + kwargs["unit"] = "i" else: axis = self.get_x_axis() value = z.real number_mob = axis.get_number_mobject(value, **kwargs) self.coordinate_labels.add(number_mob) - return self.coordinate_labels - - def add_coordinates(self, *numbers): - self.add(self.get_coordinate_labels(*numbers)) + self.add(self.coordinate_labels) return self diff --git a/manimlib/mobject/frame.py b/manimlib/mobject/frame.py index 4ea2260d..7523ab51 100644 --- a/manimlib/mobject/frame.py +++ b/manimlib/mobject/frame.py @@ -20,6 +20,9 @@ class ScreenRectangle(Rectangle): class FullScreenRectangle(ScreenRectangle): CONFIG = { "height": FRAME_HEIGHT, + "fill_color": GREY_E, + "fill_opacity": 1, + "stroke_width": 0, } diff --git a/manimlib/mobject/functions.py b/manimlib/mobject/functions.py index 3f9759aa..d0d64cee 100644 --- a/manimlib/mobject/functions.py +++ b/manimlib/mobject/functions.py @@ -1,90 +1,70 @@ from manimlib.constants import * from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.utils.config_ops import digest_config -from manimlib.utils.space_ops import get_norm -class ParametricFunction(VMobject): +class ParametricCurve(VMobject): CONFIG = { - "t_min": 0, - "t_max": 1, - "step_size": 0.2, - "min_samples": 8, - "dt": 1e-8, + "t_range": [0, 1, 0.1], + "epsilon": 1e-8, # TODO, automatically figure out discontinuities "discontinuities": [], + "use_smoothing": True, } - def __init__(self, function=None, **kwargs): - # either get a function from __init__ or from CONFIG - self.function = function or self.function + def __init__(self, t_func, t_range=None, **kwargs): + digest_config(self, kwargs) + if t_range is not None: + self.t_range[:len(t_range)] = t_range + # To be backward compatible with all the scenes specifying t_min, t_max, step_size + self.t_range = [ + kwargs.get("t_min", self.t_range[0]), + kwargs.get("t_max", self.t_range[1]), + kwargs.get("step_size", self.t_range[2]), + ] + self.t_func = t_func VMobject.__init__(self, **kwargs) - def get_function(self): - return self.function - def get_point_from_function(self, t): - return self.function(t) + return self.t_func(t) def init_points(self): - t_min, t_max = self.t_min, self.t_max - dt = self.dt + t_min, t_max, step = self.t_range - discontinuities = filter( - lambda t: t_min <= t <= t_max, - self.discontinuities - ) - discontinuities = np.array(list(discontinuities)) - boundary_times = [ - self.t_min, self.t_max, - *(discontinuities - dt), - *(discontinuities + dt), - ] + jumps = np.array(self.discontinuities) + jumps = jumps[(jumps > t_min) & (jumps < t_max)] + boundary_times = [t_min, t_max, *(jumps - self.epsilon), *(jumps + self.epsilon)] boundary_times.sort() for t1, t2 in zip(boundary_times[0::2], boundary_times[1::2]): - # Get an initial sample of points - t_range = list(np.linspace(t1, t2, self.min_samples + 1)) - samples = [self.function(t) for t in t_range] - - # Take more samples based on the distances between them - norms = [get_norm(p2 - p1) for p1, p2 in zip(samples, samples[1:])] - full_t_range = [t1] - for s1, s2, norm in zip(t_range, t_range[1:], norms): - n_inserts = int(norm / self.step_size) - full_t_range += list(np.linspace(s1, s2, n_inserts + 1)[1:]) - - points = np.array([self.function(t) for t in full_t_range]) - valid_indices = np.isfinite(points).all(1) - points = points[valid_indices] - if len(points) > 0: - self.start_new_path(points[0]) - self.add_points_as_corners(points[1:]) - self.make_smooth() + t_range = [*np.arange(t1, t2, step), t2] + points = np.array([self.t_func(t) for t in t_range]) + self.start_new_path(points[0]) + self.add_points_as_corners(points[1:]) + if self.use_smoothing: + self.make_approximately_smooth() return self -class FunctionGraph(ParametricFunction): +class FunctionGraph(ParametricCurve): CONFIG = { "color": YELLOW, - "x_min": -FRAME_X_RADIUS, - "x_max": FRAME_X_RADIUS, + "x_range": [-8, 8, 0.25], } - def __init__(self, function, **kwargs): + def __init__(self, function, x_range=None, **kwargs): digest_config(self, kwargs) - self.parametric_function = \ - lambda t: np.array([t, function(t), 0]) - ParametricFunction.__init__( - self, - self.parametric_function, - t_min=self.x_min, - t_max=self.x_max, - **kwargs - ) self.function = function + if x_range is not None: + self.x_range[:len(x_range)] = x_range + + def parametric_function(t): + return [t, function(t), 0] + + super().__init__(parametric_function, self.x_range, **kwargs) + def get_function(self): return self.function def get_point_from_function(self, x): - return self.parametric_function(x) + return self.t_func(x) diff --git a/manimlib/mobject/geometry.py b/manimlib/mobject/geometry.py index 878a8d8b..f8f523b8 100644 --- a/manimlib/mobject/geometry.py +++ b/manimlib/mobject/geometry.py @@ -17,12 +17,14 @@ from manimlib.utils.space_ops import find_intersection from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import normalize from manimlib.utils.space_ops import rotate_vector +from manimlib.utils.space_ops import rotation_matrix_transpose DEFAULT_DOT_RADIUS = 0.08 DEFAULT_SMALL_DOT_RADIUS = 0.04 DEFAULT_DASH_LENGTH = 0.05 DEFAULT_ARROW_TIP_LENGTH = 0.35 +DEFAULT_ARROW_TIP_WIDTH = 0.35 class TipableVMobject(VMobject): @@ -42,56 +44,46 @@ class TipableVMobject(VMobject): * Getters - Straightforward accessors, returning information pertaining to the TipableVMobject instance's tip(s), its length etc - """ CONFIG = { - "tip_length": DEFAULT_ARROW_TIP_LENGTH, - # TODO - "normal_vector": OUT, - "tip_style": { + "tip_config": { "fill_opacity": 1, "stroke_width": 0, - } + }, + "normal_vector": OUT, } # Adding, Creating, Modifying tips - - def add_tip(self, tip_length=None, at_start=False): + def add_tip(self, at_start=False, **kwargs): """ Adds a tip to the TipableVMobject instance, recognising that the endpoints might need to be switched if it's a 'starting tip' or not. """ - tip = self.create_tip(tip_length, at_start) + tip = self.create_tip(at_start, **kwargs) self.reset_endpoints_based_on_tip(tip, at_start) self.asign_tip_attr(tip, at_start) self.add(tip) return self - def create_tip(self, tip_length=None, at_start=False): + def create_tip(self, at_start=False, **kwargs): """ Stylises the tip, positions it spacially, and returns the newly instantiated tip to the caller. """ - tip = self.get_unpositioned_tip(tip_length) + tip = self.get_unpositioned_tip(**kwargs) self.position_tip(tip, at_start) return tip - def get_unpositioned_tip(self, tip_length=None): + def get_unpositioned_tip(self, **kwargs): """ Returns a tip that has been stylistically configured, but has not yet been given a position in space. """ - if tip_length is None: - tip_length = self.get_default_tip_length() - color = self.get_color() - style = { - "fill_color": color, - "stroke_color": color - } - style.update(self.tip_style) - tip = ArrowTip(length=tip_length, **style) - return tip + config = dict() + config.update(self.tip_config) + config.update(kwargs) + return ArrowTip(**config) def position_tip(self, tip, at_start=False): # Last two control points, defining both @@ -102,10 +94,7 @@ class TipableVMobject(VMobject): else: handle = self.get_last_handle() anchor = self.get_end() - tip.rotate( - angle_of_vector(handle - anchor) - - PI - tip.get_angle() - ) + tip.rotate(angle_of_vector(handle - anchor) - PI - tip.get_angle()) tip.shift(anchor - tip.get_tip_point()) return tip @@ -176,10 +165,10 @@ class TipableVMobject(VMobject): return self.tip_length def get_first_handle(self): - return self.points[1] + return self.get_points()[1] def get_last_handle(self): - return self.points[-2] + return self.get_points()[-2] def get_end(self): if self.has_tip(): @@ -212,26 +201,32 @@ class Arc(TipableVMobject): VMobject.__init__(self, **kwargs) def init_points(self): - self.set_pre_positioned_points() + self.set_points(Arc.create_quadratic_bezier_points( + angle=self.angle, + start_angle=self.start_angle, + n_components=self.n_components + )) self.scale(self.radius, about_point=ORIGIN) self.shift(self.arc_center) - def set_pre_positioned_points(self): + @staticmethod + def create_quadratic_bezier_points(angle, start_angle=0, n_components=8): samples = np.array([ - np.cos(a) * RIGHT + np.sin(a) * UP + [np.cos(a), np.sin(a), 0] for a in np.linspace( - self.start_angle, - self.start_angle + self.angle, - 2 * self.n_components + 1, + start_angle, + start_angle + angle, + 2 * n_components + 1, ) ]) - theta = self.angle / self.n_components + theta = angle / n_components samples[1::2] /= np.cos(theta / 2) - self.points = np.zeros((3 * self.n_components, self.dim)) - self.points[0::3] = samples[0:-1:2] - self.points[1::3] = samples[1::2] - self.points[2::3] = samples[2::2] + points = np.zeros((3 * n_components, 3)) + points[0::3] = samples[0:-1:2] + points[1::3] = samples[1::2] + points[2::3] = samples[2::2] + return points def get_arc_center(self): """ @@ -239,7 +234,7 @@ class Arc(TipableVMobject): anchors, and finds their intersection points """ # First two anchors and handles - a1, h, a2 = self.points[:3] + a1, h, a2 = self.get_points()[:3] # Tangent vectors t1 = h - a1 t2 = h - a2 @@ -263,7 +258,7 @@ class Arc(TipableVMobject): class ArcBetweenPoints(Arc): def __init__(self, start, end, angle=TAU / 4, **kwargs): - Arc.__init__(self, angle=angle, **kwargs) + super().__init__(angle=angle, **kwargs) if angle == 0: self.set_points_as_corners([LEFT, RIGHT]) self.put_start_and_end_on(start, end) @@ -315,8 +310,7 @@ class Dot(Circle): } def __init__(self, point=ORIGIN, **kwargs): - Circle.__init__(self, arc_center=point, **kwargs) - self.lock_triangulation() + super().__init__(arc_center=point, **kwargs) class SmallDot(Dot): @@ -332,7 +326,7 @@ class Ellipse(Circle): } def __init__(self, **kwargs): - Circle.__init__(self, **kwargs) + super().__init__(**kwargs) self.set_width(self.width, stretch=True) self.set_height(self.height, stretch=True) @@ -359,10 +353,10 @@ class AnnularSector(Arc): for radius in (self.inner_radius, self.outer_radius) ] outer_arc.reverse_points() - self.append_points(inner_arc.points) - self.add_line_to(outer_arc.points[0]) - self.append_points(outer_arc.points) - self.add_line_to(inner_arc.points[0]) + self.append_points(inner_arc.get_points()) + self.add_line_to(outer_arc.get_points()[0]) + self.append_points(outer_arc.get_points()) + self.add_line_to(inner_arc.get_points()[0]) class Sector(AnnularSector): @@ -387,8 +381,8 @@ class Annulus(Circle): outer_circle = Circle(radius=self.outer_radius) inner_circle = Circle(radius=self.inner_radius) inner_circle.reverse_points() - self.append_points(outer_circle.points) - self.append_points(inner_circle.points) + self.append_points(outer_circle.get_points()) + self.append_points(inner_circle.get_points()) self.shift(self.arc_center) @@ -396,31 +390,31 @@ class Line(TipableVMobject): CONFIG = { "buff": 0, # Angle of arc specified here - "path_arc": None, + "path_arc": 0, } def __init__(self, start=LEFT, end=RIGHT, **kwargs): digest_config(self, kwargs) self.set_start_and_end_attrs(start, end) - VMobject.__init__(self, **kwargs) + super().__init__(**kwargs) def init_points(self): - if self.path_arc: - arc = ArcBetweenPoints( - self.start, self.end, - angle=self.path_arc - ) - self.set_points(arc.points) + self.set_points_by_ends(self.start, self.end, self.buff, self.path_arc) + + def set_points_by_ends(self, start, end, buff=0, path_arc=0): + if path_arc: + self.set_points(Arc.create_quadratic_bezier_points(path_arc)) + self.put_start_and_end_on(start, end) else: - self.set_points_as_corners([self.start, self.end]) - self.account_for_buff() + self.set_points_as_corners([start, end]) + self.account_for_buff(self.buff) def set_path_arc(self, new_value): self.path_arc = new_value self.init_points() - def account_for_buff(self): - if self.buff == 0: + def account_for_buff(self, buff): + if buff == 0: return # if self.path_arc == 0: @@ -428,12 +422,10 @@ class Line(TipableVMobject): else: length = self.get_arc_length() # - if length < 2 * self.buff: + if length < 2 * buff: return - buff_proportion = self.buff / length - self.pointwise_become_partial( - self, buff_proportion, 1 - buff_proportion - ) + buff_prop = buff / length + self.pointwise_become_partial(self, buff_prop, 1 - buff_prop) return self def set_start_and_end_attrs(self, start, end): @@ -445,26 +437,30 @@ class Line(TipableVMobject): # Now that we know the direction between them, # we can find the appropriate boundary point from # start and end, if they're mobjects - self.start = self.pointify(start, vect) - self.end = self.pointify(end, -vect) + self.start = self.pointify(start, vect) + self.buff * vect + self.end = self.pointify(end, -vect) - self.buff * vect def pointify(self, mob_or_point, direction=None): + """ + Take an argument passed into Line (or subclass) and turn + it into a 3d point. + """ if isinstance(mob_or_point, Mobject): mob = mob_or_point if direction is None: return mob.get_center() else: - return mob.get_boundary_point(direction) - return mob_or_point + return mob.get_continuous_bounding_box_point(direction) + else: + point = mob_or_point + result = np.zeros(self.dim) + result[:len(point)] = point + return result def put_start_and_end_on(self, start, end): curr_start, curr_end = self.get_start_and_end() - if np.all(curr_start == curr_end): - # TODO, any problems with resetting - # these attrs? - self.start = start - self.end = end - self.init_points() + if (curr_start == curr_end).all(): + self.set_points_by_ends(start, end, self.path_arc) return super().put_start_and_end_on(start, end) def get_vector(self): @@ -476,14 +472,25 @@ class Line(TipableVMobject): def get_angle(self): return angle_of_vector(self.get_vector()) + def get_projection(self, point): + """ + Return projection of a point onto the line + """ + unit_vect = self.get_unit_vector() + start = self.get_start() + return start + np.dot(point - start, unit_vect) * unit_vect + def get_slope(self): return np.tan(self.get_angle()) - def set_angle(self, angle): + def set_angle(self, angle, about_point=None): + if about_point is None: + about_point = self.get_start() self.rotate( angle - self.get_angle(), - about_point=self.get_start(), + about_point=about_point, ) + return self def set_length(self, length): self.scale(length / self.get_length()) @@ -497,7 +504,7 @@ class DashedLine(Line): } def __init__(self, *args, **kwargs): - Line.__init__(self, *args, **kwargs) + super().__init__(*args, **kwargs) ps_ratio = self.positive_space_ratio num_dashes = self.calculate_num_dashes(ps_ratio) dashes = DashedVMobject( @@ -534,10 +541,10 @@ class DashedLine(Line): return Line.get_end(self) def get_first_handle(self): - return self.submobjects[0].points[1] + return self.submobjects[0].get_points()[1] def get_last_handle(self): - return self.submobjects[-1].points[-2] + return self.submobjects[-1].get_points()[-2] class TangentLine(Line): @@ -551,11 +558,7 @@ class TangentLine(Line): da = self.d_alpha a1 = clip(alpha - da, 0, 1) a2 = clip(alpha + da, 0, 1) - super().__init__( - vmob.point_from_proportion(a1), - vmob.point_from_proportion(a2), - **kwargs - ) + super().__init__(vmob.pfp(a1), vmob.pfp(a2), **kwargs) self.scale(self.length / self.get_length()) @@ -566,7 +569,7 @@ class Elbow(VMobject): } def __init__(self, **kwargs): - VMobject.__init__(self, **kwargs) + super().__init__(**kwargs) self.set_points_as_corners([UP, UP + RIGHT, RIGHT]) self.set_width(self.width, about_point=ORIGIN) self.rotate(self.angle, about_point=ORIGIN) @@ -574,77 +577,112 @@ class Elbow(VMobject): class Arrow(Line): CONFIG = { - "stroke_width": 6, + "fill_color": GREY_A, + "fill_opacity": 1, + "stroke_width": 0, "buff": MED_SMALL_BUFF, - "max_tip_length_to_length_ratio": 0.25, - "max_stroke_width_to_length_ratio": 5, - "preserve_tip_size_when_scaling": True, + "thickness": 0.05, + "tip_width_ratio": 5, + "tip_angle": PI / 3, + "max_tip_length_to_length_ratio": 0.5, + "max_width_to_length_ratio": 0.1, } - def __init__(self, *args, **kwargs): - Line.__init__(self, *args, **kwargs) - # TODO, should this be affected when - # Arrow.set_stroke is called? - self.initial_stroke_width = self.stroke_width - self.add_tip() - self.set_stroke_width_from_length() + def set_points_by_ends(self, start, end, buff=0, path_arc=0): + # Find the right tip length and thickness + vect = end - start + length = max(get_norm(vect), 1e-8) + thickness = self.thickness + w_ratio = fdiv(self.max_width_to_length_ratio, fdiv(thickness, length)) + if w_ratio < 1: + thickness *= w_ratio - def scale(self, factor, **kwargs): - if self.get_length() == 0: - return self + tip_width = self.tip_width_ratio * thickness + tip_length = tip_width / (2 * np.tan(self.tip_angle / 2)) + t_ratio = fdiv(self.max_tip_length_to_length_ratio, fdiv(tip_length, length)) + if t_ratio < 1: + tip_length *= t_ratio + tip_width *= t_ratio - has_tip = self.has_tip() - has_start_tip = self.has_start_tip() - if has_tip or has_start_tip: - old_tips = self.pop_tips() + # Find points for the stem + if path_arc == 0: + points1 = (length - tip_length) * np.array([RIGHT, 0.5 * RIGHT, ORIGIN]) + points1 += thickness * UP / 2 + points2 = points1[::-1] + thickness * DOWN + else: + # Solve for radius so that the tip-to-tail length matches |end - start| + a = 2 * (1 - np.cos(path_arc)) + b = -2 * tip_length * np.sin(path_arc) + c = tip_length**2 - length**2 + R = (-b + np.sqrt(b**2 - 4 * a * c)) / (2 * a) - VMobject.scale(self, factor, **kwargs) - self.set_stroke_width_from_length() + # Find arc points + points1 = Arc.create_quadratic_bezier_points(path_arc) + points2 = np.array(points1[::-1]) + points1 *= (R + thickness / 2) + points2 *= (R - thickness / 2) + if path_arc < 0: + tip_length *= -1 + rot_T = rotation_matrix_transpose(PI / 2 - path_arc, OUT) + for points in points1, points2: + points[:] = np.dot(points, rot_T) + points += R * DOWN - # So horribly confusing, must redo - if has_tip: - self.add_tip() - old_tips[0].points[:, :] = self.tip.points - self.remove(self.tip) - self.tip = old_tips[0] - self.add(self.tip) - if has_start_tip: - self.add_tip(at_start=True) - old_tips[1].points[:, :] = self.start_tip.points - self.remove(self.start_tip) - self.start_tip = old_tips[1] - self.add(self.start_tip) - return self + self.set_points(points1) + # Tip + self.add_line_to(tip_width * UP / 2) + self.add_line_to(tip_length * LEFT) + self.tip_index = len(self.get_points()) - 1 + self.add_line_to(tip_width * DOWN / 2) + self.add_line_to(points2[0]) + # Close it out + self.append_points(points2) + self.add_line_to(points1[0]) - def get_normal_vector(self): - p0, p1, p2 = self.tip.get_start_anchors()[:3] - return normalize(np.cross(p2 - p1, p1 - p0)) + if length > 0: + # Final correction + super().scale(length / self.get_length()) - def reset_normal_vector(self): - self.normal_vector = self.get_normal_vector() - return self - - def get_default_tip_length(self): - max_ratio = self.max_tip_length_to_length_ratio - return min( - self.tip_length, - max_ratio * self.get_length(), + self.rotate(angle_of_vector(vect) - self.get_angle()) + self.rotate( + PI / 2 - np.arccos(normalize(vect)[2]), + axis=rotate_vector(self.get_unit_vector(), -PI / 2), ) + self.shift(start - self.get_start()) + self.refresh_triangulation() - def set_stroke_width_from_length(self): - max_ratio = self.max_stroke_width_to_length_ratio - self.set_stroke( - width=min( - self.initial_stroke_width, - max_ratio * self.get_length(), - ), - family=False, + def reset_points_around_ends(self): + self.set_points_by_ends( + self.get_start(), self.get_end(), path_arc=self.path_arc ) return self - # TODO, should this be the default for everything? - # def copy(self): - # return self.deepcopy() + def get_start(self): + nppc = self.n_points_per_curve + points = self.get_points() + return (points[0] + points[-nppc]) / 2 + + def get_end(self): + return self.get_points()[self.tip_index] + + def put_start_and_end_on(self, start, end): + self.set_points_by_ends(start, end, buff=0, path_arc=self.path_arc) + return self + + def scale(self, *args, **kwargs): + super().scale(*args, **kwargs) + self.reset_points_around_ends() + return self + + def set_thickness(self, thickness): + self.thickness = thickness + self.reset_points_around_ends() + return self + + def set_path_arc(self, path_arc): + self.path_arc = path_arc + self.reset_points_around_ends() + return self class Vector(Arrow): @@ -655,7 +693,7 @@ class Vector(Arrow): def __init__(self, direction=RIGHT, **kwargs): if len(direction) == 2: direction = np.hstack([direction, 0]) - Arrow.__init__(self, ORIGIN, direction, **kwargs) + super().__init__(ORIGIN, direction, **kwargs) class DoubleArrow(Arrow): @@ -671,13 +709,13 @@ class CubicBezier(VMobject): class Polygon(VMobject): - CONFIG = { - "color": BLUE, - } - def __init__(self, *vertices, **kwargs): - VMobject.__init__(self, **kwargs) - self.set_points_as_corners([*vertices, vertices[0]]) + self.vertices = vertices + super().__init__(**kwargs) + + def init_points(self): + verts = self.vertices + self.set_points_as_corners([*verts, verts[0]]) def get_vertices(self): return self.get_start_anchors() @@ -709,7 +747,7 @@ class Polygon(VMobject): # To ensure that we loop through starting with last arcs = [arcs[-1], *arcs[:-1]] for arc1, arc2 in adjacent_pairs(arcs): - self.append_points(arc1.points) + self.append_points(arc1.get_points()) line = Line(arc1.get_end(), arc2.get_start()) # Make sure anchors are evenly distributed len_ratio = line.get_length() / arc1.get_arc_length() @@ -732,32 +770,35 @@ class RegularPolygon(Polygon): self.start_angle = (n % 2) * 90 * DEGREES start_vect = rotate_vector(RIGHT, self.start_angle) vertices = compass_directions(n, start_vect) - Polygon.__init__(self, *vertices, **kwargs) + super().__init__(*vertices, **kwargs) class Triangle(RegularPolygon): def __init__(self, **kwargs): - RegularPolygon.__init__(self, n=3, **kwargs) + super().__init__(n=3, **kwargs) class ArrowTip(Triangle): CONFIG = { "fill_opacity": 1, + "fill_color": WHITE, "stroke_width": 0, + "width": DEFAULT_ARROW_TIP_WIDTH, "length": DEFAULT_ARROW_TIP_LENGTH, - "start_angle": PI, + "angle": 0, } def __init__(self, **kwargs): - Triangle.__init__(self, **kwargs) - self.set_width(self.length) - self.set_height(self.length, stretch=True) + Triangle.__init__(self, start_angle=0, **kwargs) + self.set_height(self.width) + self.set_width(self.length, stretch=True) + self.rotate(self.angle) def get_base(self): return self.point_from_proportion(0.5) def get_tip_point(self): - return self.points[0] + return self.get_points()[0] def get_vector(self): return self.get_tip_point() - self.get_base() @@ -772,16 +813,22 @@ class ArrowTip(Triangle): class Rectangle(Polygon): CONFIG = { "color": WHITE, - "height": 2.0, "width": 4.0, + "height": 2.0, "mark_paths_closed": True, "close_new_points": True, } - def __init__(self, **kwargs): - Polygon.__init__(self, UL, UR, DR, DL, **kwargs) - self.set_width(self.width, stretch=True) - self.set_height(self.height, stretch=True) + def __init__(self, width=None, height=None, **kwargs): + Polygon.__init__(self, UR, UL, DL, DR, **kwargs) + + if width is None: + width = self.width + if height is None: + height = self.height + + self.set_width(width, stretch=True) + self.set_height(height, stretch=True) class Square(Rectangle): @@ -789,14 +836,13 @@ class Square(Rectangle): "side_length": 2.0, } - def __init__(self, **kwargs): + def __init__(self, side_length=None, **kwargs): digest_config(self, kwargs) - Rectangle.__init__( - self, - height=self.side_length, - width=self.side_length, - **kwargs - ) + + if side_length is None: + side_length = self.side_length + + super().__init__(side_length, side_length, **kwargs) class RoundedRectangle(Rectangle): diff --git a/manimlib/mobject/interactive.py b/manimlib/mobject/interactive.py new file mode 100644 index 00000000..df9e0432 --- /dev/null +++ b/manimlib/mobject/interactive.py @@ -0,0 +1,523 @@ +import numpy as np +from pyglet.window import key as PygletWindowKeys + +from manimlib.constants import FRAME_HEIGHT, FRAME_WIDTH +from manimlib.constants import LEFT, RIGHT, UP, DOWN, ORIGIN +from manimlib.constants import SMALL_BUFF, MED_SMALL_BUFF, MED_LARGE_BUFF +from manimlib.constants import BLACK, GREY_A, GREY_C, RED, GREEN, BLUE, WHITE +from manimlib.mobject.mobject import Mobject, Group +from manimlib.mobject.types.vectorized_mobject import VGroup +from manimlib.mobject.geometry import Dot, Line, Square, Rectangle, RoundedRectangle, Circle +from manimlib.mobject.svg.text_mobject import Text +from manimlib.mobject.value_tracker import ValueTracker +from manimlib.utils.config_ops import digest_config +from manimlib.utils.space_ops import get_norm, get_closest_point_on_line +from manimlib.utils.color import rgb_to_color, color_to_rgba, rgb_to_hex + + +# Interactive Mobjects + +class MotionMobject(Mobject): + """ + You could hold and drag this object to any position + """ + + def __init__(self, mobject, **kwargs): + super().__init__(**kwargs) + assert(isinstance(mobject, Mobject)) + self.mobject = mobject + self.mobject.add_mouse_drag_listner(self.mob_on_mouse_drag) + # To avoid locking it as static mobject + self.mobject.add_updater(lambda mob: None) + self.add(mobject) + + def mob_on_mouse_drag(self, mob, event_data): + mob.move_to(event_data["point"]) + return False + + +class Button(Mobject): + """ + Pass any mobject and register an on_click method + + The on_click method takes mobject as argument like updater + """ + + def __init__(self, mobject, on_click, **kwargs): + super().__init__(**kwargs) + assert(isinstance(mobject, Mobject)) + self.on_click = on_click + self.mobject = mobject + self.mobject.add_mouse_press_listner(self.mob_on_mouse_press) + self.add(self.mobject) + + def mob_on_mouse_press(self, mob, event_data): + self.on_click(mob) + return False + + +# Controls + +class ControlMobject(ValueTracker): + def __init__(self, value, *mobjects, **kwargs): + super().__init__(value=value, **kwargs) + self.add(*mobjects) + + # To avoid lock_static_mobject_data while waiting in scene + self.add_updater(lambda mob: None) + self.fix_in_frame() + + def set_value(self, value): + self.assert_value(value) + self.set_value_anim(value) + return ValueTracker.set_value(self, value) + + def assert_value(self, value): + # To be implemented in subclasses + pass + + def set_value_anim(self, value): + # To be implemented in subclasses + pass + + +class EnableDisableButton(ControlMobject): + CONFIG = { + "value_type": np.dtype(bool), + "rect_kwargs": { + "width": 0.5, + "height": 0.5, + "fill_opacity": 1.0 + }, + "enable_color": GREEN, + "disable_color": RED + } + + def __init__(self, value=True, **kwargs): + digest_config(self, kwargs) + self.box = Rectangle(**self.rect_kwargs) + super().__init__(value, self.box, **kwargs) + self.add_mouse_press_listner(self.on_mouse_press) + + def assert_value(self, value): + assert(isinstance(value, bool)) + + def set_value_anim(self, value): + if value: + self.box.set_fill(self.enable_color) + else: + self.box.set_fill(self.disable_color) + + def toggle_value(self): + super().set_value(not self.get_value()) + + def on_mouse_press(self, mob, event_data): + mob.toggle_value() + return False + + +class Checkbox(ControlMobject): + CONFIG = { + "value_type": np.dtype(bool), + "rect_kwargs": { + "width": 0.5, + "height": 0.5, + "fill_opacity": 0.0 + }, + + "checkmark_kwargs": { + "stroke_color": GREEN, + "stroke_width": 6, + }, + "cross_kwargs": { + "stroke_color": RED, + "stroke_width": 6, + }, + "box_content_buff": SMALL_BUFF + } + + def __init__(self, value=True, **kwargs): + digest_config(self, kwargs) + self.box = Rectangle(**self.rect_kwargs) + self.box_content = self.get_checkmark() if value else self.get_cross() + super().__init__(value, self.box, self.box_content, **kwargs) + self.add_mouse_press_listner(self.on_mouse_press) + + def assert_value(self, value): + assert(isinstance(value, bool)) + + def toggle_value(self): + super().set_value(not self.get_value()) + + def set_value_anim(self, value): + if value: + self.box_content.become(self.get_checkmark()) + else: + self.box_content.become(self.get_cross()) + + def on_mouse_press(self, mob, event_data): + mob.toggle_value() + return False + + # Helper methods + + def get_checkmark(self): + checkmark = VGroup( + Line(UP / 2 + 2 * LEFT, DOWN + LEFT, **self.checkmark_kwargs), + Line(DOWN + LEFT, UP + RIGHT, **self.checkmark_kwargs) + ) + + checkmark.stretch_to_fit_width(self.box.get_width()) + checkmark.stretch_to_fit_height(self.box.get_height()) + checkmark.scale(0.5) + checkmark.move_to(self.box) + return checkmark + + def get_cross(self): + cross = VGroup( + Line(UP + LEFT, DOWN + RIGHT, **self.cross_kwargs), + Line(UP + RIGHT, DOWN + LEFT, **self.cross_kwargs) + ) + + cross.stretch_to_fit_width(self.box.get_width()) + cross.stretch_to_fit_height(self.box.get_height()) + cross.scale(0.5) + cross.move_to(self.box) + return cross + + +class LinearNumberSlider(ControlMobject): + CONFIG = { + "value_type": np.float64, + "min_value": -10.0, + "max_value": 10.0, + "step": 1.0, + + "rounded_rect_kwargs": { + "height": 0.075, + "width": 2, + "corner_radius": 0.0375 + }, + "circle_kwargs": { + "radius": 0.1, + "stroke_color": GREY_A, + "fill_color": GREY_A, + "fill_opacity": 1.0 + } + } + + def __init__(self, value=0, **kwargs): + digest_config(self, kwargs) + self.bar = RoundedRectangle(**self.rounded_rect_kwargs) + self.slider = Circle(**self.circle_kwargs) + self.slider_axis = Line( + start=self.bar.get_bounding_box_point(LEFT), + end=self.bar.get_bounding_box_point(RIGHT) + ) + self.slider_axis.set_opacity(0.0) + self.slider.move_to(self.slider_axis) + + self.slider.add_mouse_drag_listner(self.slider_on_mouse_drag) + + super().__init__(value, self.bar, self.slider, self.slider_axis, ** kwargs) + + def assert_value(self, value): + assert(self.min_value <= value <= self.max_value) + + def set_value_anim(self, value): + prop = (value - self.min_value) / (self.max_value - self.min_value) + self.slider.move_to(self.slider_axis.point_from_proportion(prop)) + + def slider_on_mouse_drag(self, mob, event_data): + self.set_value(self.get_value_from_point(event_data["point"])) + return False + + # Helper Methods + + def get_value_from_point(self, point): + start, end = self.slider_axis.get_start_and_end() + point_on_line = get_closest_point_on_line(start, end, point) + prop = get_norm(point_on_line - start) / get_norm(end - start) + value = self.min_value + prop * (self.max_value - self.min_value) + no_of_steps = int((value - self.min_value) / self.step) + value_nearest_to_step = self.min_value + no_of_steps * self.step + return value_nearest_to_step + + +class ColorSliders(Group): + CONFIG = { + "sliders_kwargs": {}, + "rect_kwargs": { + "width": 2.0, + "height": 0.5, + "stroke_opacity": 1.0 + }, + "background_grid_kwargs": { + "colors": [GREY_A, GREY_C], + "single_square_len": 0.1 + }, + "sliders_buff": MED_LARGE_BUFF, + "default_rgb_value": 255, + "default_a_value": 1, + } + + def __init__(self, **kwargs): + digest_config(self, kwargs) + + rgb_kwargs = {"value": self.default_rgb_value, "min_value": 0, "max_value": 255, "step": 1} + a_kwargs = {"value": self.default_a_value, "min_value": 0, "max_value": 1, "step": 0.04} + + self.r_slider = LinearNumberSlider(**self.sliders_kwargs, **rgb_kwargs) + self.g_slider = LinearNumberSlider(**self.sliders_kwargs, **rgb_kwargs) + self.b_slider = LinearNumberSlider(**self.sliders_kwargs, **rgb_kwargs) + self.a_slider = LinearNumberSlider(**self.sliders_kwargs, **a_kwargs) + self.sliders = Group( + self.r_slider, + self.g_slider, + self.b_slider, + self.a_slider + ) + self.sliders.arrange(DOWN, buff=self.sliders_buff) + + self.r_slider.slider.set_color(RED) + self.g_slider.slider.set_color(GREEN) + self.b_slider.slider.set_color(BLUE) + self.a_slider.slider.set_color_by_gradient([BLACK, WHITE]) + + self.selected_color_box = Rectangle(**self.rect_kwargs) + self.selected_color_box.add_updater( + lambda mob: mob.set_fill( + self.get_picked_color(), self.get_picked_opacity() + ) + ) + self.background = self.get_background() + + super().__init__( + Group(self.background, self.selected_color_box).fix_in_frame(), + self.sliders, + **kwargs + ) + + self.arrange(DOWN) + + def get_background(self): + single_square_len = self.background_grid_kwargs["single_square_len"] + colors = self.background_grid_kwargs["colors"] + width = self.rect_kwargs["width"] + height = self.rect_kwargs["height"] + rows = int(height / single_square_len) + cols = int(width / single_square_len) + cols = (cols + 1) if (cols % 2 == 0) else cols + + single_square = Square(single_square_len) + grid = single_square.get_grid(n_rows=rows, n_cols=cols, buff=0.0) + grid.stretch_to_fit_width(width) + grid.stretch_to_fit_height(height) + grid.move_to(self.selected_color_box) + + for idx, square in enumerate(grid): + assert(isinstance(square, Square)) + square.set_stroke(width=0.0, opacity=0.0) + square.set_fill(colors[idx % len(colors)], 1.0) + + return grid + + def set_value(self, r, g, b, a): + self.r_slider.set_value(r) + self.g_slider.set_value(g) + self.b_slider.set_value(b) + self.a_slider.set_value(a) + + def get_value(self): + r = self.r_slider.get_value() / 255 + g = self.g_slider.get_value() / 255 + b = self.b_slider.get_value() / 255 + alpha = self.a_slider.get_value() + return color_to_rgba(rgb_to_color((r, g, b)), alpha=alpha) + + def get_picked_color(self): + rgba = self.get_value() + return rgb_to_hex(rgba[:3]) + + def get_picked_opacity(self): + rgba = self.get_value() + return rgba[3] + + +class Textbox(ControlMobject): + CONFIG = { + "value_type": np.dtype(object), + + "box_kwargs": { + "width": 2.0, + "height": 1.0, + "fill_color": WHITE, + "fill_opacity": 1.0, + }, + "text_kwargs": { + "color": BLUE + }, + "text_buff": MED_SMALL_BUFF, + "isInitiallyActive": False, + "active_color": BLUE, + "deactive_color": RED, + } + + def __init__(self, value="", **kwargs): + digest_config(self, kwargs) + self.isActive = self.isInitiallyActive + self.box = Rectangle(**self.box_kwargs) + self.box.add_mouse_press_listner(self.box_on_mouse_press) + self.text = Text(value, **self.text_kwargs) + super().__init__(value, self.box, self.text, **kwargs) + self.update_text(value) + self.active_anim(self.isActive) + self.add_key_press_listner(self.on_key_press) + + def set_value_anim(self, value): + self.update_text(value) + + def update_text(self, value): + text = self.text + self.remove(text) + text.__init__(value, **self.text_kwargs) + height = text.get_height() + text.set_width(self.box.get_width() - 2 * self.text_buff) + if text.get_height() > height: + text.set_height(height) + text.add_updater(lambda mob: mob.move_to(self.box)) + text.fix_in_frame() + self.add(text) + + def active_anim(self, isActive): + if isActive: + self.box.set_stroke(self.active_color) + else: + self.box.set_stroke(self.deactive_color) + + def box_on_mouse_press(self, mob, event_data): + self.isActive = not self.isActive + self.active_anim(self.isActive) + return False + + def on_key_press(self, mob, event_data): + symbol = event_data["symbol"] + modifiers = event_data["modifiers"] + char = chr(symbol) + if mob.isActive: + old_value = mob.get_value() + new_value = old_value + if char.isalnum(): + if (modifiers & PygletWindowKeys.MOD_SHIFT) or (modifiers & PygletWindowKeys.MOD_CAPSLOCK): + new_value = old_value + char.upper() + else: + new_value = old_value + char.lower() + elif symbol in [PygletWindowKeys.SPACE]: + new_value = old_value + char + elif symbol == PygletWindowKeys.TAB: + new_value = old_value + '\t' + elif symbol == PygletWindowKeys.BACKSPACE: + new_value = old_value[:-1] or '' + mob.set_value(new_value) + return False + + +class ControlPanel(Group): + CONFIG = { + "panel_kwargs": { + "width": FRAME_WIDTH / 4, + "height": MED_SMALL_BUFF + FRAME_HEIGHT, + "fill_color": GREY_C, + "fill_opacity": 1.0, + "stroke_width": 0.0 + }, + "opener_kwargs": { + "width": FRAME_WIDTH / 8, + "height": 0.5, + "fill_color": GREY_C, + "fill_opacity": 1.0 + }, + "opener_text_kwargs": { + "text": "Control Panel", + "size": 0.4 + } + } + + def __init__(self, *controls, **kwargs): + digest_config(self, kwargs) + + self.panel = Rectangle(**self.panel_kwargs) + self.panel.to_corner(UP + LEFT, buff=0) + self.panel.shift(self.panel.get_height() * UP) + self.panel.add_mouse_scroll_listner(self.panel_on_mouse_scroll) + + self.panel_opener_rect = Rectangle(**self.opener_kwargs) + self.panel_info_text = Text(**self.opener_text_kwargs) + self.panel_info_text.move_to(self.panel_opener_rect) + + self.panel_opener = Group(self.panel_opener_rect, self.panel_info_text) + self.panel_opener.next_to(self.panel, DOWN, aligned_edge=DOWN) + self.panel_opener.add_mouse_drag_listner(self.panel_opener_on_mouse_drag) + + self.controls = Group(*controls) + self.controls.arrange(DOWN, center=False, aligned_edge=ORIGIN) + self.controls.move_to(self.panel) + + super().__init__( + self.panel, self.panel_opener, + self.controls, + **kwargs + ) + + self.move_panel_and_controls_to_panel_opener() + self.fix_in_frame() + + def move_panel_and_controls_to_panel_opener(self): + self.panel.next_to( + self.panel_opener_rect, + direction=UP, + buff=0 + ) + + controls_old_x = self.controls.get_x() + self.controls.next_to( + self.panel_opener_rect, + direction=UP, + buff=MED_SMALL_BUFF + ) + + self.controls.set_x(controls_old_x) + + def add_controls(self, *new_controls): + self.controls.add(*new_controls) + self.move_panel_and_controls_to_panel_opener() + + def remove_controls(self, *controls_to_remove): + self.controls.remove(*controls_to_remove) + self.move_panel_and_controls_to_panel_opener() + + def open_panel(self): + panel_opener_x = self.panel_opener.get_x() + self.panel_opener.to_corner(DOWN + LEFT, buff=0.0) + self.panel_opener.set_x(panel_opener_x) + self.move_panel_and_controls_to_panel_opener() + return self + + def close_panel(self): + panel_opener_x = self.panel_opener.get_x() + self.panel_opener.to_corner(UP + LEFT, buff=0.0) + self.panel_opener.set_x(panel_opener_x) + self.move_panel_and_controls_to_panel_opener() + return self + + def panel_opener_on_mouse_drag(self, mob, event_data): + point = event_data["point"] + self.panel_opener.match_y(Dot(point)) + self.move_panel_and_controls_to_panel_opener() + return False + + def panel_on_mouse_scroll(self, mob, event_data): + offset = event_data["offset"] + factor = 10 * offset[1] + self.controls.set_y(self.controls.get_y() + factor) + return False diff --git a/manimlib/mobject/matrix.py b/manimlib/mobject/matrix.py index a689729c..4800aa01 100644 --- a/manimlib/mobject/matrix.py +++ b/manimlib/mobject/matrix.py @@ -1,11 +1,12 @@ import numpy as np +import itertools as it from manimlib.constants import * from manimlib.mobject.numbers import DecimalNumber from manimlib.mobject.numbers import Integer from manimlib.mobject.shape_matchers import BackgroundRectangle -from manimlib.mobject.svg.tex_mobject import TexMobject -from manimlib.mobject.svg.tex_mobject import TextMobject +from manimlib.mobject.svg.tex_mobject import Tex +from manimlib.mobject.svg.tex_mobject import TexText from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject @@ -27,7 +28,7 @@ def matrix_to_tex_string(matrix): def matrix_to_mobject(matrix): - return TexMobject(matrix_to_tex_string(matrix)) + return Tex(matrix_to_tex_string(matrix)) def vector_coordinate_label(vector_mob, integer_labels=True, @@ -56,13 +57,13 @@ class Matrix(VMobject): CONFIG = { "v_buff": 0.8, "h_buff": 1.3, - "bracket_h_buff": MED_SMALL_BUFF, - "bracket_v_buff": MED_SMALL_BUFF, + "bracket_h_buff": 0.2, + "bracket_v_buff": 0.25, "add_background_rectangles_to_entries": False, "include_background_rectangle": False, - "element_to_mobject": TexMobject, + "element_to_mobject": Tex, "element_to_mobject_config": {}, - "element_alignment_corner": DR, + "element_alignment_corner": DOWN, } def __init__(self, matrix, **kwargs): @@ -71,10 +72,11 @@ class Matrix(VMobject): or mobjects """ VMobject.__init__(self, **kwargs) - matrix = np.array(matrix, ndmin=1) + matrix = self.matrix = np.array(matrix, ndmin=2) mob_matrix = self.matrix_to_mob_matrix(matrix) self.organize_mob_matrix(mob_matrix) - self.elements = VGroup(*mob_matrix.flatten()) + # self.elements = VGroup(*mob_matrix.flatten()) + self.elements = VGroup(*it.chain(*mob_matrix)) self.add(self.elements) self.add_brackets() self.center() @@ -86,9 +88,13 @@ class Matrix(VMobject): self.add_background_rectangle() def matrix_to_mob_matrix(self, matrix): - return np.vectorize(self.element_to_mobject)( - matrix, **self.element_to_mobject_config - ) + return [ + [ + self.element_to_mobject(item, **self.element_to_mobject_config) + for item in row + ] + for row in matrix + ] def organize_mob_matrix(self, matrix): for i, row in enumerate(matrix): @@ -101,12 +107,19 @@ class Matrix(VMobject): return self def add_brackets(self): - bracket_pair = TexMobject("\\big[", "\\big]") - bracket_pair.scale(2) - bracket_pair.stretch_to_fit_height( - self.get_height() + 2 * self.bracket_v_buff + height = self.matrix.shape[0] + bracket_pair = Tex("".join([ + "\\left[", + "\\begin{array}{c}", + *height * ["\\quad \\\\"], + "\\end{array}" + "\\right]", + ]))[0] + bracket_pair.set_height( + self.get_height() + 1 * self.bracket_v_buff ) - l_bracket, r_bracket = bracket_pair.split() + l_bracket = bracket_pair[:len(bracket_pair) // 2] + r_bracket = bracket_pair[len(bracket_pair) // 2:] l_bracket.next_to(self, LEFT, self.bracket_h_buff) r_bracket.next_to(self, RIGHT, self.bracket_h_buff) self.add(l_bracket, r_bracket) @@ -115,8 +128,14 @@ class Matrix(VMobject): def get_columns(self): return VGroup(*[ - VGroup(*self.mob_matrix[:, i]) - for i in range(self.mob_matrix.shape[1]) + VGroup(*[row[i] for row in self.mob_matrix]) + for i in range(len(self.mob_matrix[0])) + ]) + + def get_rows(self): + return VGroup(*[ + VGroup(*row) + for row in self.mob_matrix ]) def set_column_colors(self, *colors): @@ -134,7 +153,7 @@ class Matrix(VMobject): return self.mob_matrix def get_entries(self): - return VGroup(*self.get_mob_matrix().flatten()) + return self.elements def get_brackets(self): return self.brackets @@ -150,6 +169,7 @@ class DecimalMatrix(Matrix): class IntegerMatrix(Matrix): CONFIG = { "element_to_mobject": Integer, + "element_alignment_corner": UP, } @@ -160,22 +180,22 @@ class MobjectMatrix(Matrix): def get_det_text(matrix, determinant=None, background_rect=False, initial_scale_factor=2): - parens = TexMobject("(", ")") + parens = Tex("(", ")") parens.scale(initial_scale_factor) parens.stretch_to_fit_height(matrix.get_height()) l_paren, r_paren = parens.split() l_paren.next_to(matrix, LEFT, buff=0.1) r_paren.next_to(matrix, RIGHT, buff=0.1) - det = TextMobject("det") + det = TexText("det") det.scale(initial_scale_factor) det.next_to(l_paren, LEFT, buff=0.1) if background_rect: det.add_background_rectangle() det_text = VGroup(det, l_paren, r_paren) if determinant is not None: - eq = TexMobject("=") + eq = Tex("=") eq.next_to(r_paren, RIGHT, buff=0.1) - result = TexMobject(str(determinant)) + result = Tex(str(determinant)) result.next_to(eq, RIGHT, buff=0.2) det_text.add(eq, result) return det_text diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 4a1e83f0..3edec5d8 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -1,95 +1,246 @@ -from functools import reduce import copy import itertools as it -import operator as op -import os import random import sys import moderngl +from functools import wraps -from colour import Color import numpy as np -import manimlib.constants as consts from manimlib.constants import * -from manimlib.container.container import Container from manimlib.utils.color import color_gradient -from manimlib.utils.color import interpolate_color +from manimlib.utils.color import get_colormap_list +from manimlib.utils.color import rgb_to_hex +from manimlib.utils.color import color_to_rgb +from manimlib.utils.config_ops import digest_config from manimlib.utils.iterables import batch_by_property from manimlib.utils.iterables import list_update +from manimlib.utils.iterables import resize_array +from manimlib.utils.iterables import resize_preserving_order +from manimlib.utils.iterables import resize_with_interpolation +from manimlib.utils.iterables import make_even +from manimlib.utils.iterables import listify +from manimlib.utils.bezier import interpolate from manimlib.utils.paths import straight_path from manimlib.utils.simple_functions import get_parameters from manimlib.utils.space_ops import angle_of_vector from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import rotation_matrix_transpose -from manimlib.utils.shaders import get_shader_info -from manimlib.utils.shaders import shader_info_to_id -from manimlib.utils.shaders import shader_id_to_info -from manimlib.utils.shaders import is_valid_shader_info +from manimlib.shader_wrapper import ShaderWrapper +from manimlib.shader_wrapper import get_colormap_code +from manimlib.event_handler import EVENT_DISPATCHER +from manimlib.event_handler.event_listner import EventListner +from manimlib.event_handler.event_type import EventType -# TODO: Explain array_attrs -# TODO: Incorporate shader defaults - -class Mobject(Container): +class Mobject(object): """ Mathematical Object """ CONFIG = { "color": WHITE, - "name": None, - "dim": 3, + "opacity": 1, + "dim": 3, # TODO, get rid of this + # Lighting parameters + # Positive gloss up to 1 makes it reflect the light. + "gloss": 0.0, + # Positive shadow up to 1 makes a side opposite the light darker + "shadow": 0.0, # For shaders - "vert_shader_file": "", - "geom_shader_file": "", - "frag_shader_file": "", - "render_primative": moderngl.TRIANGLE_STRIP, - "texture_path": "", + "shader_folder": "", + "render_primitive": moderngl.TRIANGLE_STRIP, + "texture_paths": None, + "depth_test": False, + # If true, the mobject will not get rotated according to camera position + "is_fixed_in_frame": False, # Must match in attributes of vert shader "shader_dtype": [ ('point', np.float32, (3,)), - ] + ], + # Event listener + "listen_to_events": False } def __init__(self, **kwargs): - Container.__init__(self, **kwargs) + digest_config(self, kwargs) self.submobjects = [] self.parents = [] self.family = [self] - self.color = Color(self.color) - if self.name is None: - self.name = self.__class__.__name__ - self.time_based_updaters = [] - self.non_time_updaters = [] - self.updating_suspended = False - self.shader_data_is_locked = False + self.locked_data_keys = set() + self.needs_new_bounding_box = True - self.reset_points() + self.init_data() + self.init_uniforms() + self.init_updaters() + self.init_event_listners() self.init_points() self.init_colors() self.init_shader_data() - def __str__(self): - return str(self.name) + if self.depth_test: + self.apply_depth_test() - def reset_points(self): - self.points = np.zeros((0, self.dim)) + def __str__(self): + return self.__class__.__name__ + + def init_data(self): + self.data = { + "points": np.zeros((0, 3)), + "bounding_box": np.zeros((3, 3)), + "rgbas": np.zeros((1, 4)), + } + + def init_uniforms(self): + self.uniforms = { + "is_fixed_in_frame": float(self.is_fixed_in_frame), + "gloss": self.gloss, + "shadow": self.shadow, + } def init_colors(self): - # For subclasses - pass + self.set_color(self.color, self.opacity) def init_points(self): - # Typically implemented in subclass, unless purposefully left blank + # Typically implemented in subclass, unlpess purposefully left blank pass + def set_data(self, data): + for key in data: + self.data[key] = data[key].copy() + return self + + def set_uniforms(self, uniforms): + for key in uniforms: + self.uniforms[key] = uniforms[key] # Copy? + return self + + @property + def animate(self): + # Borrowed from https://github.com/ManimCommunity/manim/ + return _AnimationBuilder(self) + + # Only these methods should directly affect points + + def resize_points(self, new_length, resize_func=resize_array): + if new_length != len(self.data["points"]): + self.data["points"] = resize_func(self.data["points"], new_length) + self.refresh_bounding_box() + return self + + def set_points(self, points): + if len(points) == len(self.data["points"]): + self.data["points"][:] = points + elif isinstance(points, np.ndarray): + self.data["points"] = points.copy() + else: + self.data["points"] = np.array(points) + self.refresh_bounding_box() + return self + + def append_points(self, new_points): + self.data["points"] = np.vstack([self.data["points"], new_points]) + self.refresh_bounding_box() + return self + + def reverse_points(self): + for mob in self.get_family(): + for key in mob.data: + mob.data[key] = mob.data[key][::-1] + return self + + def apply_points_function(self, func, about_point=None, about_edge=ORIGIN, works_on_bounding_box=False): + if about_point is None and about_edge is not None: + about_point = self.get_bounding_box_point(about_edge) + + for mob in self.get_family(): + arrs = [] + if mob.has_points(): + arrs.append(mob.get_points()) + if works_on_bounding_box: + arrs.append(mob.get_bounding_box()) + + for arr in arrs: + if about_point is None: + arr[:] = func(arr) + else: + arr[:] = func(arr - about_point) + about_point + + if not works_on_bounding_box: + self.refresh_bounding_box(recurse_down=True) + else: + for parent in self.parents: + parent.refresh_bounding_box() + return self + + # Others related to points + + def match_points(self, mobject): + self.set_points(mobject.get_points()) + return self + + def get_points(self): + return self.data["points"] + + def clear_points(self): + self.resize_points(0) + + def get_num_points(self): + return len(self.data["points"]) + + def get_all_points(self): + if self.submobjects: + return np.vstack([sm.get_points() for sm in self.get_family()]) + else: + return self.get_points() + + def has_points(self): + return self.get_num_points() > 0 + + def get_bounding_box(self): + if self.needs_new_bounding_box: + self.data["bounding_box"] = self.compute_bounding_box() + self.needs_new_bounding_box = False + return self.data["bounding_box"] + + def compute_bounding_box(self): + all_points = np.vstack([ + self.get_points(), + *( + mob.get_bounding_box() + for mob in self.get_family()[1:] + if mob.has_points() + ) + ]) + if len(all_points) == 0: + return np.zeros((3, self.dim)) + else: + # Lower left and upper right corners + mins = all_points.min(0) + maxs = all_points.max(0) + mids = (mins + maxs) / 2 + return np.array([mins, mids, maxs]) + + def refresh_bounding_box(self, recurse_down=False, recurse_up=True): + for mob in self.get_family(recurse_down): + mob.needs_new_bounding_box = True + if recurse_up: + for parent in self.parents: + parent.refresh_bounding_box() + return self + + def is_point_touching(self, point, buff=MED_SMALL_BUFF): + bb = self.get_bounding_box() + mins = (bb[0] - buff) + maxs = (bb[2] + buff) + return (point >= mins).all() and (point <= maxs).all() + # Family matters + def __getitem__(self, value): - self_list = self.split() if isinstance(value, slice): GroupClass = self.get_group_class() - return GroupClass(*self_list.__getitem__(value)) - return self_list.__getitem__(value) + return GroupClass(*self.split().__getitem__(value)) + return self.split().__getitem__(value) def __iter__(self): return iter(self.split()) @@ -98,21 +249,25 @@ class Mobject(Container): return len(self.split()) def split(self): - result = [self] if len(self.points) > 0 else [] - return result + self.submobjects + return self.submobjects def assemble_family(self): - sub_families = [sm.get_family() for sm in self.submobjects] + sub_families = (sm.get_family() for sm in self.submobjects) self.family = [self, *it.chain(*sub_families)] + self.refresh_has_updater_status() + self.refresh_bounding_box() for parent in self.parents: parent.assemble_family() return self - def get_family(self): - return self.family + def get_family(self, recurse=True): + if recurse: + return self.family + else: + return [self] def family_members_with_points(self): - return [m for m in self.get_family() if m.get_num_points() > 0] + return [m for m in self.get_family() if m.has_points()] def add(self, *mobjects): if self in mobjects: @@ -151,10 +306,6 @@ class Mobject(Container): self.add(*submobject_list) return self - def get_array_attrs(self): - # May be more for other Mobject types - return ["points"] - def digest_mobject_attrs(self): """ Ensures all attributes which are mobjects are included @@ -164,26 +315,85 @@ class Mobject(Container): self.set_submobjects(list_update(self.submobjects, mobject_attrs)) return self - def apply_over_attr_arrays(self, func): - for attr in self.get_array_attrs(): - setattr(self, attr, func(getattr(self, attr))) + # Submobject organization + + def arrange(self, direction=RIGHT, center=True, **kwargs): + for m1, m2 in zip(self.submobjects, self.submobjects[1:]): + m2.next_to(m1, direction, **kwargs) + if center: + self.center() return self - # Displaying + def arrange_in_grid(self, n_rows=None, n_cols=None, + buff=None, + h_buff=None, + v_buff=None, + buff_ratio=None, + h_buff_ratio=0.5, + v_buff_ratio=0.5, + aligned_edge=ORIGIN, + fill_rows_first=True): + submobs = self.submobjects + if n_rows is None and n_cols is None: + n_rows = int(np.sqrt(len(submobs))) + if n_rows is None: + n_rows = len(submobs) // n_cols + if n_cols is None: + n_cols = len(submobs) // n_rows - def get_image(self, camera=None): - # TODO, this doesn't...you know, seem to actually work - camera.clear() - camera.capture(self) - return camera.get_image() + if buff is not None: + h_buff = buff + v_buff = buff + else: + if buff_ratio is not None: + v_buff_ratio = buff_ratio + h_buff_ratio = buff_ratio + if h_buff is None: + h_buff = h_buff_ratio * self[0].get_width() + if v_buff is None: + v_buff = v_buff_ratio * self[0].get_height() - def show(self, camera): - self.get_image(camera).show() + x_unit = h_buff + max([sm.get_width() for sm in submobs]) + y_unit = v_buff + max([sm.get_height() for sm in submobs]) - def save_image(self, name=None): - self.get_image().save( - os.path.join(consts.VIDEO_DIR, (name or str(self)) + ".png") + for index, sm in enumerate(submobs): + if fill_rows_first: + x, y = index % n_cols, index // n_cols + else: + x, y = index // n_rows, index % n_rows + sm.move_to(ORIGIN, aligned_edge) + sm.shift(x * x_unit * RIGHT + y * y_unit * DOWN) + self.center() + return self + + def get_grid(self, n_rows, n_cols, height=None, **kwargs): + """ + Returns a new mobject containing multiple copies of this one + arranged in a grid + """ + grid = self.get_group_class()( + *(self.copy() for n in range(n_rows * n_cols)) ) + grid.arrange_in_grid(n_rows, n_cols, **kwargs) + if height is not None: + grid.set_height(height) + return grid + + def sort(self, point_to_num_func=lambda p: p[0], submob_func=None): + if submob_func is not None: + self.submobjects.sort(key=submob_func) + else: + self.submobjects.sort(key=lambda m: point_to_num_func(m.get_center())) + return self + + def shuffle(self, recurse=False): + if recurse: + for submob in self.submobjects: + submob.shuffle(recurse=True) + random.shuffle(self.submobjects) + return self + + # Copying def copy(self): # TODO, either justify reason for shallow copy, or @@ -195,18 +405,28 @@ class Mobject(Container): copy_mobject = copy.copy(self) self.parents = parents - copy_mobject.points = np.array(self.points) + copy_mobject.data = dict(self.data) + for key in self.data: + copy_mobject.data[key] = self.data[key].copy() + + # TODO, are uniforms ever numpy arrays? + copy_mobject.uniforms = dict(self.uniforms) + copy_mobject.submobjects = [] copy_mobject.add(*[sm.copy() for sm in self.submobjects]) copy_mobject.match_updaters(self) + copy_mobject.needs_new_bounding_box = self.needs_new_bounding_box + # Make sure any mobject or numpy array attributes are copied family = self.get_family() for attr, value in list(self.__dict__.items()): if isinstance(value, Mobject) and value in family and value is not self: setattr(copy_mobject, attr, value.copy()) if isinstance(value, np.ndarray): - setattr(copy_mobject, attr, np.array(value)) + setattr(copy_mobject, attr, value.copy()) + if isinstance(value, ShaderWrapper): + setattr(copy_mobject, attr, value.copy()) return copy_mobject def deepcopy(self): @@ -224,18 +444,40 @@ class Mobject(Container): self.target = self.copy() return self.target + def save_state(self, use_deepcopy=False): + if hasattr(self, "saved_state"): + # Prevent exponential growth of data + self.saved_state = None + if use_deepcopy: + self.saved_state = self.deepcopy() + else: + self.saved_state = self.copy() + return self + + def restore(self): + if not hasattr(self, "saved_state") or self.save_state is None: + raise Exception("Trying to restore without having saved") + self.become(self.saved_state) + return self + # Updating - def update(self, dt=0, recursive=True): - if self.updating_suspended: + def init_updaters(self): + self.time_based_updaters = [] + self.non_time_updaters = [] + self.has_updaters = False + self.updating_suspended = False + + def update(self, dt=0, recurse=True): + if not self.has_updaters or self.updating_suspended: return self for updater in self.time_based_updaters: updater(self, dt) for updater in self.non_time_updaters: updater(self) - if recursive: + if recurse: for submob in self.submobjects: - submob.update(dt, recursive) + submob.update(dt, recurse) return self def get_time_based_updaters(self): @@ -248,10 +490,7 @@ class Mobject(Container): return self.time_based_updaters + self.non_time_updaters def get_family_updaters(self): - return list(it.chain(*[ - sm.get_updaters() - for sm in self.get_family() - ])) + return list(it.chain(*[sm.get_updaters() for sm in self.get_family()])) def add_updater(self, update_function, index=None, call_updater=True): if "dt" in get_parameters(update_function): @@ -264,20 +503,23 @@ class Mobject(Container): else: updater_list.insert(index, update_function) + self.refresh_has_updater_status() if call_updater: - self.update(0) + self.update(dt=0) return self def remove_updater(self, update_function): for updater_list in [self.time_based_updaters, self.non_time_updaters]: while update_function in updater_list: updater_list.remove(update_function) + self.refresh_has_updater_status() return self - def clear_updaters(self, recursive=True): + def clear_updaters(self, recurse=True): self.time_based_updaters = [] self.non_time_updaters = [] - if recursive: + self.refresh_has_updater_status() + if recurse: for submob in self.submobjects: submob.clear_updaters() return self @@ -288,37 +530,39 @@ class Mobject(Container): self.add_updater(updater) return self - def suspend_updating(self, recursive=True): + def suspend_updating(self, recurse=True): self.updating_suspended = True - if recursive: + if recurse: for submob in self.submobjects: - submob.suspend_updating(recursive) + submob.suspend_updating(recurse) return self - def resume_updating(self, recursive=True): + def resume_updating(self, recurse=True, call_updater=True): self.updating_suspended = False - if recursive: + if recurse: for submob in self.submobjects: - submob.resume_updating(recursive) - self.update(dt=0, recursive=recursive) + submob.resume_updating(recurse) + for parent in self.parents: + parent.resume_updating(recurse=False, call_updater=False) + if call_updater: + self.update(dt=0, recurse=recurse) + return self + + def refresh_has_updater_status(self): + self.has_updaters = any(mob.get_updaters() for mob in self.get_family()) return self # Transforming operations - def set_points(self, points): - self.points = np.array(points) + + def shift(self, vector): + self.apply_points_function( + lambda points: points + vector, + about_edge=None, + works_on_bounding_box=True, + ) return self - def apply_to_family(self, func): - for mob in self.family_members_with_points(): - func(mob) - - def shift(self, *vectors): - total_vector = reduce(op.add, vectors) - for mob in self.get_family(): - mob.points += total_vector - return self - - def scale(self, scale_factor, **kwargs): + def scale(self, scale_factor, min_scale_factor=1e-8, **kwargs): """ Default behavior is to scale about the center of the mobject. The argument about_edge can be a vector, indicating which side of @@ -328,18 +572,27 @@ class Mobject(Container): Otherwise, if about_point is given a value, scaling is done with respect to that point. """ - self.apply_points_function_about_point( + scale_factor = max(scale_factor, min_scale_factor) + self.apply_points_function( lambda points: scale_factor * points, + works_on_bounding_box=True, **kwargs ) return self - def rotate_about_origin(self, angle, axis=OUT, axes=[]): + def stretch(self, factor, dim, **kwargs): + def func(points): + points[:, dim] *= factor + return points + self.apply_points_function(func, works_on_bounding_box=True, **kwargs) + return self + + def rotate_about_origin(self, angle, axis=OUT): return self.rotate(angle, axis, about_point=ORIGIN) def rotate(self, angle, axis=OUT, **kwargs): rot_matrix_T = rotation_matrix_transpose(angle, axis) - self.apply_points_function_about_point( + self.apply_points_function( lambda points: np.dot(points, rot_matrix_T), **kwargs ) @@ -348,18 +601,11 @@ class Mobject(Container): def flip(self, axis=UP, **kwargs): return self.rotate(TAU / 2, axis, **kwargs) - def stretch(self, factor, dim, **kwargs): - def func(points): - points[:, dim] *= factor - return points - self.apply_points_function_about_point(func, **kwargs) - return self - def apply_function(self, function, **kwargs): # Default to applying matrix about the origin, not mobjects center if len(kwargs) == 0: kwargs["about_point"] = ORIGIN - self.apply_points_function_about_point( + self.apply_points_function( lambda points: np.array([function(p) for p in points]), **kwargs ) @@ -381,7 +627,7 @@ class Mobject(Container): full_matrix = np.identity(self.dim) matrix = np.array(matrix) full_matrix[:matrix.shape[0], :matrix.shape[1]] = matrix - self.apply_points_function_about_point( + self.apply_points_function( lambda points: np.dot(points, full_matrix.T), **kwargs ) @@ -400,59 +646,16 @@ class Mobject(Container): def wag(self, direction=RIGHT, axis=DOWN, wag_factor=1.0): for mob in self.family_members_with_points(): - alphas = np.dot(mob.points, np.transpose(axis)) + alphas = np.dot(mob.get_points(), np.transpose(axis)) alphas -= min(alphas) alphas /= max(alphas) alphas = alphas**wag_factor - mob.points += np.dot( + mob.set_points(mob.get_points() + np.dot( alphas.reshape((len(alphas), 1)), np.array(direction).reshape((1, mob.dim)) - ) + )) return self - def reverse_points(self): - for mob in self.family_members_with_points(): - mob.apply_over_attr_arrays(lambda arr: arr[::-1]) - return self - - def repeat(self, count): - """ - This can make transition animations nicer - """ - for mob in self.family_members_with_points(): - mob.apply_over_attr_arrays(lambda arr: np.vstack([arr] * count)) - return self - - # In place operations. - # Note, much of these are now redundant with default behavior of - # above methods - - def apply_points_function_about_point(self, func, about_point=None, about_edge=None): - if about_point is None: - if about_edge is None: - about_edge = ORIGIN - about_point = self.get_bounding_box_point(about_edge) - for mob in self.family_members_with_points(): - mob.points -= about_point - mob.points[:] = func(mob.points) - mob.points += about_point - return self - - def rotate_in_place(self, angle, axis=OUT): - # redundant with default behavior of rotate now. - return self.rotate(angle, axis=axis) - - def scale_in_place(self, scale_factor, **kwargs): - # Redundant with default behavior of scale now. - return self.scale(scale_factor, **kwargs) - - def scale_about_point(self, scale_factor, point): - # Redundant with default behavior of scale now. - return self.scale(scale_factor, about_point=point) - - def pose_at_angle(self, angle=TAU / 14, axis=UR, **kwargs): - return self.rotate(angle, axis, **kwargs) - # Positioning methods def center(self): @@ -503,8 +706,7 @@ class Mobject(Container): else: aligner = self point_to_align = aligner.get_bounding_box_point(aligned_edge - direction) - self.shift((target_point - point_to_align + - buff * direction) * coor_mask) + self.shift((target_point - point_to_align + buff * direction) * coor_mask) return self def shift_onto_screen(self, **kwargs): @@ -598,11 +800,11 @@ class Mobject(Container): def replace(self, mobject, dim_to_match=0, stretch=False): if not mobject.get_num_points() and not mobject.submobjects: - raise Warning("Attempting to replace mobject with no points") + self.scale(0) return self if stretch: - self.stretch_to_fit_width(mobject.get_width()) - self.stretch_to_fit_height(mobject.get_height()) + for i in range(self.dim): + self.rescale_to_fit(mobject.length_over_dim(i), i, stretch=True) else: self.rescale_to_fit( mobject.length_over_dim(dim_to_match), @@ -618,10 +820,11 @@ class Mobject(Container): buff=MED_SMALL_BUFF): self.replace(mobject, dim_to_match, stretch) length = mobject.length_over_dim(dim_to_match) - self.scale_in_place((length + buff) / length) + self.scale((length + buff) / length) return self def put_start_and_end_on(self, start, end): + # TODO, this doesn't currently work in 3d curr_start, curr_end = self.get_start_and_end() curr_vect = curr_end - curr_start if np.all(curr_vect == 0): @@ -632,15 +835,127 @@ class Mobject(Container): about_point=curr_start, ) self.rotate( - angle_of_vector(target_vect) - - angle_of_vector(curr_vect), + angle_of_vector(target_vect) - angle_of_vector(curr_vect), about_point=curr_start ) self.shift(start - curr_start) return self + # Color functions + + def set_rgba_array(self, rgba_array, name="rgbas", recurse=False): + for mob in self.get_family(recurse): + mob.data[name] = np.array(rgba_array) + return self + + def set_color_by_rgba_func(self, func, recurse=True): + """ + Func should take in a point in R3 and output an rgba value + """ + for mob in self.get_family(recurse): + rgba_array = [func(point) for point in mob.get_points()] + mob.set_rgba_array(rgba_array) + return self + + def set_color_by_rgb_func(self, func, opacity=1, recurse=True): + """ + Func should take in a point in R3 and output an rgb value + """ + for mob in self.get_family(recurse): + rgba_array = [[*func(point), opacity] for point in mob.get_points()] + mob.set_rgba_array(rgba_array) + return self + + def set_rgba_array_by_color(self, color=None, opacity=None, name="rgbas", recurse=True): + if color is not None: + rgbs = np.array([color_to_rgb(c) for c in listify(color)]) + if opacity is not None: + opacities = listify(opacity) + + # Color only + if color is not None and opacity is None: + for mob in self.get_family(recurse): + mob.data[name] = resize_array(mob.data[name], len(rgbs)) + mob.data[name][:, :3] = rgbs + + # Opacity only + if color is None and opacity is not None: + for mob in self.get_family(recurse): + mob.data[name] = resize_array(mob.data[name], len(opacities)) + mob.data[name][:, 3] = opacities + + # Color and opacity + if color is not None and opacity is not None: + rgbas = np.array([ + [*rgb, o] + for rgb, o in zip(*make_even(rgbs, opacities)) + ]) + for mob in self.get_family(recurse): + mob.data[name] = rgbas.copy() + return self + + def set_color(self, color, opacity=None, recurse=True): + self.set_rgba_array_by_color(color, opacity, recurse=False) + # Recurse to submobjects differently from how set_rgba_array_by_color + # in case they implement set_color differently + if recurse: + for submob in self.submobjects: + submob.set_color(color, recurse=True) + return self + + def set_opacity(self, opacity, recurse=True): + self.set_rgba_array_by_color(color=None, opacity=opacity, recurse=False) + if recurse: + for submob in self.submobjects: + submob.set_opacity(opacity, recurse=True) + return self + + def get_color(self): + return rgb_to_hex(self.data["rgbas"][0, :3]) + + def get_opacity(self): + return self.data["rgbas"][0, 3] + + def set_color_by_gradient(self, *colors): + self.set_submobject_colors_by_gradient(*colors) + return self + + def set_submobject_colors_by_gradient(self, *colors): + if len(colors) == 0: + raise Exception("Need at least one color") + elif len(colors) == 1: + return self.set_color(*colors) + + # mobs = self.family_members_with_points() + mobs = self.submobjects + new_colors = color_gradient(colors, len(mobs)) + + for mob, color in zip(mobs, new_colors): + mob.set_color(color) + return self + + def fade(self, darkness=0.5, recurse=True): + self.set_opacity(1.0 - darkness, recurse=recurse) + + def get_gloss(self): + return self.uniforms["gloss"] + + def set_gloss(self, gloss, recurse=True): + for mob in self.get_family(recurse): + mob.uniforms["gloss"] = gloss + return self + + def get_shadow(self): + return self.uniforms["shadow"] + + def set_shadow(self, shadow, recurse=True): + for mob in self.get_family(recurse): + mob.uniforms["shadow"] = shadow + return self + # Background rectangle - def add_background_rectangle(self, color=BLACK, opacity=0.75, **kwargs): + + def add_background_rectangle(self, color=None, opacity=0.75, **kwargs): # TODO, this does not behave well when the mobject has points, # since it gets displayed on top from manimlib.mobject.shape_matchers import BackgroundRectangle @@ -662,144 +977,15 @@ class Mobject(Container): mob.add_background_rectangle(**kwargs) return self - # Color functions - - def set_color(self, color=YELLOW_C, family=True): - """ - Condition is function which takes in one arguments, (x, y, z). - Here it just recurses to submobjects, but in subclasses this - should be further implemented based on the the inner workings - of color - """ - if family: - for submob in self.submobjects: - submob.set_color(color, family=family) - self.color = color - return self - - def set_color_by_gradient(self, *colors): - self.set_submobject_colors_by_gradient(*colors) - return self - - def set_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK): - self.set_submobject_colors_by_radial_gradient( - center, radius, inner_color, outer_color) - return self - - def set_submobject_colors_by_gradient(self, *colors): - if len(colors) == 0: - raise Exception("Need at least one color") - elif len(colors) == 1: - return self.set_color(*colors) - - mobs = self.family_members_with_points() - new_colors = color_gradient(colors, len(mobs)) - - for mob, color in zip(mobs, new_colors): - mob.set_color(color, family=False) - return self - - def set_submobject_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK): - if center is None: - center = self.get_center() - - for mob in self.family_members_with_points(): - t = get_norm(mob.get_center() - center) / radius - t = min(t, 1) - mob_color = interpolate_color(inner_color, outer_color, t) - mob.set_color(mob_color, family=False) - - return self - - def to_original_color(self): - self.set_color(self.color) - return self - - def fade_to(self, color, alpha, family=True): - if self.get_num_points() > 0: - new_color = interpolate_color( - self.get_color(), color, alpha - ) - self.set_color(new_color, family=False) - if family: - for submob in self.submobjects: - submob.fade_to(color, alpha) - return self - - def fade(self, darkness=0.5, family=True): - if family: - for submob in self.submobjects: - submob.fade(darkness, family) - return self - - def get_color(self): - return self.color - - ## - - def save_state(self, use_deepcopy=False): - if hasattr(self, "saved_state"): - # Prevent exponential growth of data - self.saved_state = None - if use_deepcopy: - self.saved_state = self.deepcopy() - else: - self.saved_state = self.copy() - return self - - def restore(self): - if not hasattr(self, "saved_state") or self.save_state is None: - raise Exception("Trying to restore without having saved") - self.become(self.saved_state) - return self - - ## - - def get_merged_array(self, array_attr): - if self.submobjects: - return np.vstack([ - getattr(sm, array_attr) - for sm in self.get_family() - ]) - else: - return getattr(self, array_attr) - - def get_all_points(self): - if self.submobjects: - return np.vstack([ - sm.points for sm in self.get_family() - ]) - else: - return self.points - # Getters - def get_points_defining_boundary(self): - return self.get_all_points() - - def get_num_points(self): - return len(self.points) - def get_bounding_box_point(self, direction): - result = np.zeros(self.dim) bb = self.get_bounding_box() - result[direction < 0] = bb[0, direction < 0] - result[direction == 0] = bb[1, direction == 0] - result[direction > 0] = bb[2, direction > 0] - return result - - def get_bounding_box(self): - all_points = self.get_points_defining_boundary() - if len(all_points) == 0: - return np.zeros((3, self.dim)) - else: - # Lower left and upper right corners - mins = all_points.min(0) - maxs = all_points.max(0) - mids = (mins + maxs) / 2 - return np.array([mins, mids, maxs]) - - # Pseudonyms for more general get_bounding_box_point method + indices = (np.sign(direction) + 1).astype(int) + return np.array([ + bb[indices[i]][i] + for i in range(3) + ]) def get_edge_center(self, direction): return self.get_bounding_box_point(direction) @@ -808,16 +994,28 @@ class Mobject(Container): return self.get_bounding_box_point(direction) def get_center(self): - return self.get_bounding_box_point(np.zeros(self.dim)) + return self.get_bounding_box()[1] def get_center_of_mass(self): return self.get_all_points().mean(0) def get_boundary_point(self, direction): - all_points = self.get_points_defining_boundary() - index = np.argmax(np.dot(all_points, np.array(direction).T)) + all_points = self.get_all_points() + boundary_directions = all_points - self.get_center() + norms = np.linalg.norm(boundary_directions, axis=1) + boundary_directions /= np.repeat(norms, 3).reshape((len(norms), 3)) + index = np.argmax(np.dot(boundary_directions, np.array(direction).T)) return all_points[index] + def get_continuous_bounding_box_point(self, direction): + dl, center, ur = self.get_bounding_box() + corner_vect = (ur - center) + return center + direction / np.max(np.abs(np.true_divide( + direction, corner_vect, + out=np.zeros(len(direction)), + where=((corner_vect) != 0) + ))) + def get_top(self): return self.get_edge_center(UP) @@ -838,7 +1036,7 @@ class Mobject(Container): def length_over_dim(self, dim): bb = self.get_bounding_box() - return (bb[2] - bb[0])[dim] + return abs((bb[2] - bb[0])[dim]) def get_width(self): return self.length_over_dim(0) @@ -866,17 +1064,19 @@ class Mobject(Container): def get_start(self): self.throw_error_if_no_points() - return np.array(self.points[0]) + return np.array(self.get_points()[0]) def get_end(self): self.throw_error_if_no_points() - return np.array(self.points[-1]) + return np.array(self.get_points()[-1]) def get_start_and_end(self): return self.get_start(), self.get_end() def point_from_proportion(self, alpha): - raise Exception("Not implemented") + points = self.get_points() + i, subalpha = integer_interpolate(0, len(points) - 1, alpha) + return interpolate(points[i], points[i + 1], subalpha) def pfp(self, alpha): """Abbreviation fo point_from_proportion""" @@ -898,12 +1098,6 @@ class Mobject(Container): z_index_group = getattr(self, "z_index_group", self) return z_index_group.get_center() - def has_points(self): - return len(self.points) > 0 - - def has_no_points(self): - return not self.has_points() - # Match other mobject properties def match_color(self, mobject): @@ -940,7 +1134,7 @@ class Mobject(Container): def match_z(self, mobject, direction=ORIGIN): return self.match_coord(mobject, 2, direction) - def align_to(self, mobject_or_point, direction=ORIGIN, alignment_vect=UP): + def align_to(self, mobject_or_point, direction=ORIGIN): """ Examples: mob1.align_to(mob2, UP) moves mob1 vertically so that its @@ -963,117 +1157,69 @@ class Mobject(Container): def get_group_class(self): return Group - # Submobject organization - def arrange(self, direction=RIGHT, center=True, **kwargs): - for m1, m2 in zip(self.submobjects, self.submobjects[1:]): - m2.next_to(m1, direction, **kwargs) - if center: - self.center() - return self - - def arrange_in_grid(self, n_rows=None, n_cols=None, **kwargs): - submobs = self.submobjects - if n_rows is None and n_cols is None: - n_rows = int(np.sqrt(len(submobs))) - - if n_rows is not None: - v1 = RIGHT - v2 = DOWN - n = len(submobs) // n_rows - elif n_cols is not None: - v1 = DOWN - v2 = RIGHT - n = len(submobs) // n_cols - Group(*[ - Group(*submobs[i:i + n]).arrange(v1, **kwargs) - for i in range(0, len(submobs), n) - ]).arrange(v2, **kwargs) - return self - - def sort(self, point_to_num_func=lambda p: p[0], submob_func=None): - if submob_func is None: - submob_func = lambda m: point_to_num_func(m.get_center()) - self.submobjects.sort(key=submob_func) - return self - - def shuffle(self, recursive=False): - if recursive: - for submob in self.submobjects: - submob.shuffle(recursive=True) - random.shuffle(self.submobjects) - - # Just here to keep from breaking old scenes. - def arrange_submobjects(self, *args, **kwargs): - return self.arrange(*args, **kwargs) - - def sort_submobjects(self, *args, **kwargs): - return self.sort(*args, **kwargs) - - def shuffle_submobjects(self, *args, **kwargs): - return self.shuffle(*args, **kwargs) - # Alignment + + def align_data_and_family(self, mobject): + self.align_family(mobject) + self.align_data(mobject) + def align_data(self, mobject): - self.null_point_align(mobject) # Needed? - self.align_submobjects(mobject) + # In case any data arrays get resized when aligned to shader data + self.refresh_shader_data() for mob1, mob2 in zip(self.get_family(), mobject.get_family()): + # Separate out how points are treated so that subclasses + # can handle that case differently if they choose mob1.align_points(mob2) + for key in mob1.data.keys() & mob2.data.keys(): + if key == "points": + continue + arr1 = mob1.data[key] + arr2 = mob2.data[key] + if len(arr2) > len(arr1): + mob1.data[key] = resize_preserving_order(arr1, len(arr2)) + elif len(arr1) > len(arr2): + mob2.data[key] = resize_preserving_order(arr2, len(arr1)) def align_points(self, mobject): - count1 = self.get_num_points() - count2 = mobject.get_num_points() - if count1 < count2: - self.align_points_with_larger(mobject) - elif count2 < count1: - mobject.align_points_with_larger(self) + max_len = max(self.get_num_points(), mobject.get_num_points()) + for mob in (self, mobject): + mob.resize_points(max_len, resize_func=resize_preserving_order) return self - def align_points_with_larger(self, larger_mobject): - raise Exception("Not implemented") - - def align_submobjects(self, mobject): + def align_family(self, mobject): mob1 = self mob2 = mobject - n1 = len(mob1.submobjects) - n2 = len(mob2.submobjects) - mob1.add_n_more_submobjects(max(0, n2 - n1)) - mob2.add_n_more_submobjects(max(0, n1 - n2)) + n1 = len(mob1) + n2 = len(mob2) + if n1 != n2: + mob1.add_n_more_submobjects(max(0, n2 - n1)) + mob2.add_n_more_submobjects(max(0, n1 - n2)) # Recurse for sm1, sm2 in zip(mob1.submobjects, mob2.submobjects): - sm1.align_submobjects(sm2) - return self - - def null_point_align(self, mobject): - """ - If a mobject with points is being aligned to - one without, treat both as groups, and push - the one with points into its own submobjects - list. - """ - for m1, m2 in (self, mobject), (mobject, self): - if m1.has_no_points() and m2.has_points(): - m2.push_self_into_submobjects() + sm1.align_family(sm2) return self def push_self_into_submobjects(self): copy = self.deepcopy() copy.set_submobjects([]) - self.reset_points() + self.resize_points(0) self.add(copy) return self def add_n_more_submobjects(self, n): if n == 0: - return + return self curr = len(self.submobjects) if curr == 0: # If empty, simply add n point mobjects + null_mob = self.copy() + null_mob.set_points([self.get_center()]) self.set_submobjects([ - self.copy().scale(0) + null_mob.copy() for k in range(n) ]) - return + return self target = curr + n repeat_indices = (np.arange(target) * curr) // target split_factors = [ @@ -1084,115 +1230,342 @@ class Mobject(Container): for submob, sf in zip(self.submobjects, split_factors): new_submobs.append(submob) for k in range(1, sf): - new_submobs.append(submob.copy().fade(1)) + new_submob = submob.copy() + # If the submobject is at all transparent, then + # make the copy completely transparent + if submob.get_opacity() < 1: + new_submob.set_opacity(0) + new_submobs.append(new_submob) self.set_submobjects(new_submobs) return self - def interpolate(self, mobject1, mobject2, - alpha, path_func=straight_path): - """ - Turns self into an interpolation between mobject1 - and mobject2. - """ - self.points[:] = path_func(mobject1.points, mobject2.points, alpha) - self.interpolate_color(mobject1, mobject2, alpha) + # Interpolate + + def interpolate(self, mobject1, mobject2, alpha, path_func=straight_path): + for key in self.data: + if key in self.locked_data_keys: + continue + if len(self.data[key]) == 0: + continue + if key not in mobject1.data or key not in mobject2.data: + continue + + if key in ("points", "bounding_box"): + func = path_func + else: + func = interpolate + + self.data[key][:] = func( + mobject1.data[key], + mobject2.data[key], + alpha + ) + for key in self.uniforms: + self.uniforms[key] = interpolate( + mobject1.uniforms[key], + mobject2.uniforms[key], + alpha + ) return self - def interpolate_color(self, mobject1, mobject2, alpha): - pass # To implement in subclass - - def become_partial(self, mobject, a, b): + def pointwise_become_partial(self, mobject, a, b): """ Set points in such a way as to become only part of mobject. Inputs 0 <= a < b <= 1 determine what portion of mobject to become. """ - pass # To implement in subclasses - - # TODO, color? - - def pointwise_become_partial(self, mobject, a, b): pass # To implement in subclass def become(self, mobject): """ - Edit points, colors and submobjects to be idential + Edit all data and submobjects to be idential to another mobject """ - self.align_submobjects(mobject) + self.align_family(mobject) for sm1, sm2 in zip(self.get_family(), mobject.get_family()): - sm1.set_points(sm2.points) - sm1.interpolate_color(sm1, sm2, 1) + sm1.set_data(sm2.data) + sm1.set_uniforms(sm2.uniforms) + self.refresh_bounding_box(recurse_down=True) return self - def prepare_for_animation(self): - pass + # Locking data - def cleanup_from_animation(self): - pass + def lock_data(self, keys): + """ + To speed up some animations, particularly transformations, + it can be handy to acknowledge which pieces of data + won't change during the animation so that calls to + interpolate can skip this, and so that it's not + read into the shader_wrapper objects needlessly + """ + if self.has_updaters: + return + # Be sure shader data has most up to date information + self.refresh_shader_data() + self.locked_data_keys = set(keys) - # For shaders - def init_shader_data(self): - self.shader_data = np.zeros(len(self.points), dtype=self.shader_dtype) + def lock_matching_data(self, mobject1, mobject2): + for sm, sm1, sm2 in zip(self.get_family(), mobject1.get_family(), mobject2.get_family()): + keys = sm.data.keys() & sm1.data.keys() & sm2.data.keys() + sm.lock_data(list(filter( + lambda key: np.all(sm1.data[key] == sm2.data[key]), + keys, + ))) + return self - def get_blank_shader_data_array(self, size, name="shader_data"): - # If possible, try to populate an existing array, rather - # than recreating it each frame - arr = getattr(self, name) - if arr.size != size: - new_arr = np.resize(arr, size) - setattr(self, name, new_arr) - return new_arr - return arr + def unlock_data(self): + for mob in self.get_family(): + mob.locked_data_keys = set() - def lock_shader_data(self): - self.shader_data_is_locked = False - self.saved_shader_info_list = self.get_shader_info_list() - self.shader_data_is_locked = True + # Operations touching shader uniforms - def unlock_shader_data(self): - self.shader_data_is_locked = False + def affects_shader_info_id(func): + @wraps(func) + def wrapper(self): + for mob in self.get_family(): + func(mob) + mob.refresh_shader_wrapper_id() + return self + return wrapper - def get_shader_info_list(self): - if self.shader_data_is_locked: - return self.saved_shader_info_list + @affects_shader_info_id + def fix_in_frame(self): + self.uniforms["is_fixed_in_frame"] = 1.0 + return self - shader_infos = it.chain( - [self.get_shader_info()], - *[ - submob.get_shader_info_list() - for submob in self.submobjects - ] + @affects_shader_info_id + def unfix_from_frame(self): + self.uniforms["is_fixed_in_frame"] = 0.0 + return self + + @affects_shader_info_id + def apply_depth_test(self): + self.depth_test = True + return self + + @affects_shader_info_id + def deactivate_depth_test(self): + self.depth_test = False + return self + + # Shader code manipulation + + def replace_shader_code(self, old, new): + # TODO, will this work with VMobject structure, given + # that it does not simpler return shader_wrappers of + # family? + for wrapper in self.get_shader_wrapper_list(): + wrapper.replace_code(old, new) + return self + + def set_color_by_code(self, glsl_code): + """ + Takes a snippet of code and inserts it into a + context which has the following variables: + vec4 color, vec3 point, vec3 unit_normal. + The code should change the color variable + """ + self.replace_shader_code( + "///// INSERT COLOR FUNCTION HERE /////", + glsl_code ) - batches = batch_by_property(shader_infos, shader_info_to_id) + return self + + def set_color_by_xyz_func(self, glsl_snippet, + min_value=-5.0, max_value=5.0, + colormap="viridis"): + """ + Pass in a glsl expression in terms of x, y and z which returns + a float. + """ + # TODO, add a version of this which changes the point data instead + # of the shader code + for char in "xyz": + glsl_snippet = glsl_snippet.replace(char, "point." + char) + rgb_list = get_colormap_list(colormap) + self.set_color_by_code( + "color.rgb = float_to_color({}, {}, {}, {});".format( + glsl_snippet, + float(min_value), + float(max_value), + get_colormap_code(rgb_list) + ) + ) + return self + + # For shader data + + def init_shader_data(self): + # TODO, only call this when needed? + self.shader_data = np.zeros(len(self.get_points()), dtype=self.shader_dtype) + self.shader_indices = None + self.shader_wrapper = ShaderWrapper( + vert_data=self.shader_data, + shader_folder=self.shader_folder, + texture_paths=self.texture_paths, + depth_test=self.depth_test, + render_primitive=self.render_primitive, + ) + + def refresh_shader_wrapper_id(self): + self.shader_wrapper.refresh_id() + return self + + def get_shader_wrapper(self): + self.shader_wrapper.vert_data = self.get_shader_data() + self.shader_wrapper.vert_indices = self.get_shader_vert_indices() + self.shader_wrapper.uniforms = self.get_shader_uniforms() + self.shader_wrapper.depth_test = self.depth_test + return self.shader_wrapper + + def get_shader_wrapper_list(self): + shader_wrappers = it.chain( + [self.get_shader_wrapper()], + *[sm.get_shader_wrapper_list() for sm in self.submobjects] + ) + batches = batch_by_property(shader_wrappers, lambda sw: sw.get_id()) result = [] - for info_group, sid in batches: - shader_info = shader_id_to_info(sid) - shader_info["data"] = np.hstack([info["data"] for info in info_group]) - if is_valid_shader_info(shader_info): - result.append(shader_info) + for wrapper_group, sid in batches: + shader_wrapper = wrapper_group[0] + if not shader_wrapper.is_valid(): + continue + shader_wrapper.combine_with(*wrapper_group[1:]) + if len(shader_wrapper.vert_data) > 0: + result.append(shader_wrapper) return result - def get_shader_info(self): - return get_shader_info( - data=self.get_shader_data(), - vert_file=self.vert_shader_file, - geom_file=self.geom_shader_file, - frag_file=self.frag_shader_file, - texture_path=self.texture_path, - render_primative=self.render_primative, - ) + def check_data_alignment(self, array, data_key): + # Makes sure that self.data[key] can be brodcast into + # the given array, meaning its length has to be either 1 + # or the length of the array + d_len = len(self.data[data_key]) + if d_len != 1 and d_len != len(array): + self.data[data_key] = resize_with_interpolation( + self.data[data_key], len(array) + ) + return self - def get_shader_data(self): - # Typically to be implemented by subclasses - # Must return a structured numpy array + def get_resized_shader_data_array(self, length): + # If possible, try to populate an existing array, rather + # than recreating it each frame + if len(self.shader_data) != length: + self.shader_data = resize_array(self.shader_data, length) return self.shader_data + def read_data_to_shader(self, shader_data, shader_data_key, data_key): + if data_key in self.locked_data_keys: + return + self.check_data_alignment(shader_data, data_key) + shader_data[shader_data_key] = self.data[data_key] + + def get_shader_data(self): + shader_data = self.get_resized_shader_data_array(self.get_num_points()) + self.read_data_to_shader(shader_data, "point", "points") + return shader_data + + def refresh_shader_data(self): + self.get_shader_data() + + def get_shader_uniforms(self): + return self.uniforms + + def get_shader_vert_indices(self): + return self.shader_indices + + # Event Handlers + """ + Event handling follows the Event Bubbling model of DOM in javascript. + Return false to stop the event bubbling. + To learn more visit https://www.quirksmode.org/js/events_order.html + + Event Callback Argument is a callable function taking two arguments: + 1. Mobject + 2. EventData + """ + + def init_event_listners(self): + self.event_listners = [] + + def add_event_listner(self, event_type, event_callback): + event_listner = EventListner(self, event_type, event_callback) + self.event_listners.append(event_listner) + EVENT_DISPATCHER.add_listner(event_listner) + return self + + def remove_event_listner(self, event_type, event_callback): + event_listner = EventListner(self, event_type, event_callback) + while event_listner in self.event_listners: + self.event_listners.remove(event_listner) + EVENT_DISPATCHER.remove_listner(event_listner) + return self + + def clear_event_listners(self, recurse=True): + self.event_listners = [] + if recurse: + for submob in self.submobjects: + submob.clear_event_listners(recurse=recurse) + return self + + def get_event_listners(self): + return self.event_listners + + def get_family_event_listners(self): + return list(it.chain(*[sm.get_event_listners() for sm in self.get_family()])) + + def get_has_event_listner(self): + return any( + mob.get_event_listners() + for mob in self.get_family() + ) + + def add_mouse_motion_listner(self, callback): + self.add_event_listner(EventType.MouseMotionEvent, callback) + + def remove_mouse_motion_listner(self, callback): + self.remove_event_listner(EventType.MouseMotionEvent, callback) + + def add_mouse_press_listner(self, callback): + self.add_event_listner(EventType.MousePressEvent, callback) + + def remove_mouse_press_listner(self, callback): + self.remove_event_listner(EventType.MousePressEvent, callback) + + def add_mouse_release_listner(self, callback): + self.add_event_listner(EventType.MouseReleaseEvent, callback) + + def remove_mouse_release_listner(self, callback): + self.remove_event_listner(EventType.MouseReleaseEvent, callback) + + def add_mouse_drag_listner(self, callback): + self.add_event_listner(EventType.MouseDragEvent, callback) + + def remove_mouse_drag_listner(self, callback): + self.remove_event_listner(EventType.MouseDragEvent, callback) + + def add_mouse_scroll_listner(self, callback): + self.add_event_listner(EventType.MouseScrollEvent, callback) + + def remove_mouse_scroll_listner(self, callback): + self.remove_event_listner(EventType.MouseScrollEvent, callback) + + def add_key_press_listner(self, callback): + self.add_event_listner(EventType.KeyPressEvent, callback) + + def remove_key_press_listner(self, callback): + self.remove_event_listner(EventType.KeyPressEvent, callback) + + def add_key_release_listner(self, callback): + self.add_event_listner(EventType.KeyReleaseEvent, callback) + + def remove_key_release_listner(self, callback): + self.remove_event_listner(EventType.KeyReleaseEvent, callback) + # Errors + def throw_error_if_no_points(self): - if self.has_no_points(): + if not self.has_points(): message = "Cannot call Mobject.{} " +\ "for a Mobject with no points" caller_name = sys._getframe(1).f_code.co_name @@ -1224,10 +1597,58 @@ class Point(Mobject): return self.artificial_height def get_location(self): - return np.array(self.points[0]) + return self.get_points()[0].copy() def get_bounding_box_point(self, *args, **kwargs): return self.get_location() def set_location(self, new_loc): - self.points = np.array(new_loc, ndmin=2) + self.set_points(np.array(new_loc, ndmin=2, dtype=float)) + + +class _AnimationBuilder: + def __init__(self, mobject): + self.mobject = mobject + self.overridden_animation = None + self.mobject.generate_target() + self.is_chaining = False + self.methods = [] + + def __getattr__(self, method_name): + method = getattr(self.mobject.target, method_name) + self.methods.append(method) + has_overridden_animation = hasattr(method, "_override_animate") + + if (self.is_chaining and has_overridden_animation) or self.overridden_animation: + raise NotImplementedError( + "Method chaining is currently not supported for " + "overridden animations" + ) + + def update_target(*method_args, **method_kwargs): + if has_overridden_animation: + self.overridden_animation = method._override_animate( + self.mobject, *method_args, **method_kwargs + ) + else: + method(*method_args, **method_kwargs) + return self + + self.is_chaining = True + return update_target + + def build(self): + from manimlib.animation.transform import _MethodAnimation + + if self.overridden_animation: + return self.overridden_animation + + return _MethodAnimation(self.mobject, self.methods) + + +def override_animate(method): + def decorator(animation_method): + method._override_animate = animation_method + return animation_method + + return decorator diff --git a/manimlib/mobject/mobject_update_utils.py b/manimlib/mobject/mobject_update_utils.py index 71eb12e8..db59821f 100644 --- a/manimlib/mobject/mobject_update_utils.py +++ b/manimlib/mobject/mobject_update_utils.py @@ -41,9 +41,9 @@ def f_always(method, *arg_generators, **kwargs): return mobject -def always_redraw(func): - mob = func() - mob.add_updater(lambda m: mob.become(func())) +def always_redraw(func, *args, **kwargs): + mob = func(*args, **kwargs) + mob.add_updater(lambda m: mob.become(func(*args, **kwargs))) return mob diff --git a/manimlib/mobject/number_line.py b/manimlib/mobject/number_line.py index 647c886f..0346399c 100644 --- a/manimlib/mobject/number_line.py +++ b/manimlib/mobject/number_line.py @@ -1,5 +1,3 @@ -import operator as op - from manimlib.constants import * from manimlib.mobject.geometry import Line from manimlib.mobject.numbers import DecimalNumber @@ -13,72 +11,83 @@ from manimlib.utils.space_ops import normalize class NumberLine(Line): CONFIG = { - "color": LIGHT_GREY, + "color": GREY_B, "stroke_width": 2, - "x_min": -FRAME_X_RADIUS, - "x_max": FRAME_X_RADIUS, + # List of 2 or 3 elements, x_min, x_max, step_size + "x_range": [-8, 8, 1], + # How big is one one unit of this number line in terms of absolute spacial distance "unit_size": 1, + "width": None, "include_ticks": True, "tick_size": 0.1, - "tick_frequency": 1, - # Defaults to value near x_min s.t. 0 is a tick - # TODO, rename this - "leftmost_tick": None, + "longer_tick_multiple": 1.5, + "tick_offset": 0, # Change name - "numbers_with_elongated_ticks": [0], + "numbers_with_elongated_ticks": [], "include_numbers": False, - "numbers_to_show": None, - "longer_tick_multiple": 2, - "number_at_center": 0, - "number_scale_val": 0.75, - "label_direction": DOWN, + "line_to_number_direction": DOWN, "line_to_number_buff": MED_SMALL_BUFF, "include_tip": False, - "tip_width": 0.25, - "tip_height": 0.25, + "tip_config": { + "width": 0.25, + "length": 0.25, + }, "decimal_number_config": { "num_decimal_places": 0, + "font_size": 36, }, - "exclude_zero_from_default_numbers": False, + "numbers_to_exclude": None } - def __init__(self, **kwargs): + def __init__(self, x_range=None, **kwargs): digest_config(self, kwargs) - start = self.unit_size * self.x_min * RIGHT - end = self.unit_size * self.x_max * RIGHT - Line.__init__(self, start, end, **kwargs) - self.shift(-self.number_to_point(self.number_at_center)) + if x_range is None: + x_range = self.x_range + if len(x_range) == 2: + x_range = [*x_range, 1] + + x_min, x_max, x_step = x_range + # A lot of old scenes pass in x_min or x_max explicitly, + # so this is just here to keep those workin + self.x_min = kwargs.get("x_min", x_min) + self.x_max = kwargs.get("x_max", x_max) + self.x_step = kwargs.get("x_step", x_step) + + super().__init__(self.x_min * RIGHT, self.x_max * RIGHT, **kwargs) + if self.width: + self.set_width(self.width) + self.unit_size = self.get_unit_size() + else: + self.scale(self.unit_size) + self.center() - self.init_leftmost_tick() if self.include_tip: self.add_tip() - if self.include_ticks: - self.add_tick_marks() - if self.include_numbers: - self.add_numbers() - - def init_leftmost_tick(self): - if self.leftmost_tick is None: - self.leftmost_tick = op.mul( - self.tick_frequency, - np.ceil(self.x_min / self.tick_frequency) + self.tip.set_stroke( + self.stroke_color, + self.stroke_width, ) + if self.include_ticks: + self.add_ticks() + if self.include_numbers: + self.add_numbers(excluding=self.numbers_to_exclude) - def add_tick_marks(self): - tick_size = self.tick_size - self.tick_marks = VGroup(*[ - self.get_tick(x, tick_size) - for x in self.get_tick_numbers() - ]) - big_tick_size = tick_size * self.longer_tick_multiple - self.big_tick_marks = VGroup(*[ - self.get_tick(x, big_tick_size) - for x in self.numbers_with_elongated_ticks - ]) - self.add( - self.tick_marks, - self.big_tick_marks, - ) + def get_tick_range(self): + if self.include_tip: + x_max = self.x_max + else: + x_max = self.x_max + self.x_step + return np.arange(self.x_min, x_max, self.x_step) + + def add_ticks(self): + ticks = VGroup() + for x in self.get_tick_range(): + size = self.tick_size + if x in self.numbers_with_elongated_ticks: + size *= self.longer_tick_multiple + ticks.add(self.get_tick(x, size)) + self.add(ticks) + self.ticks = ticks def get_tick(self, x, size=None): if size is None: @@ -90,36 +99,18 @@ class NumberLine(Line): return result def get_tick_marks(self): - return VGroup( - *self.tick_marks, - *self.big_tick_marks, - ) - - def get_tick_numbers(self): - u = -1 if self.include_tip else 1 - return np.arange( - self.leftmost_tick, - self.x_max + u * self.tick_frequency / 2, - self.tick_frequency - ) + return self.ticks def number_to_point(self, number): alpha = float(number - self.x_min) / (self.x_max - self.x_min) - return interpolate( - self.get_start(), self.get_end(), alpha - ) + return interpolate(self.get_start(), self.get_end(), alpha) def point_to_number(self, point): - start_point, end_point = self.get_start_and_end() - full_vect = end_point - start_point - unit_vect = normalize(full_vect) - - def distance_from_start(p): - return np.dot(p - start_point, unit_vect) - + start, end = self.get_start_and_end() + unit_vect = normalize(end - start) proportion = fdiv( - distance_from_start(point), - distance_from_start(end_point) + np.dot(point - start, unit_vect), + np.dot(end - start, unit_vect), ) return interpolate(self.x_min, self.x_max, proportion) @@ -132,70 +123,55 @@ class NumberLine(Line): return self.point_to_number(point) def get_unit_size(self): - return (self.x_max - self.x_min) / self.get_length() + return self.get_length() / (self.x_max - self.x_min) - def default_numbers_to_display(self): - if self.numbers_to_show is not None: - return self.numbers_to_show - numbers = np.arange( - np.floor(self.leftmost_tick), - np.ceil(self.x_max), - ) - if self.exclude_zero_from_default_numbers: - numbers = numbers[numbers != 0] - return numbers - - def get_number_mobject(self, number, - number_config=None, - scale_val=None, + def get_number_mobject(self, x, direction=None, - buff=None): + buff=None, + **number_config): number_config = merge_dicts_recursively( - self.decimal_number_config, - number_config or {}, + self.decimal_number_config, number_config ) - if scale_val is None: - scale_val = self.number_scale_val if direction is None: - direction = self.label_direction - buff = buff or self.line_to_number_buff + direction = self.line_to_number_direction + if buff is None: + buff = self.line_to_number_buff - num_mob = DecimalNumber(number, **number_config) - num_mob.scale(scale_val) + num_mob = DecimalNumber(x, **number_config) num_mob.next_to( - self.number_to_point(number), + self.number_to_point(x), direction=direction, buff=buff ) + if x < 0 and direction[0] == 0: + # Align without the minus sign + num_mob.shift(num_mob[0].get_width() * LEFT / 2) return num_mob - def get_number_mobjects(self, *numbers, **kwargs): - if len(numbers) == 0: - numbers = self.default_numbers_to_display() - return VGroup(*[ - self.get_number_mobject(number, **kwargs) - for number in numbers - ]) + def add_numbers(self, x_values=None, excluding=None, font_size=24, **kwargs): + if x_values is None: + x_values = self.get_tick_range() - def get_labels(self): - return self.get_number_mobjects() + kwargs["font_size"] = font_size - def add_numbers(self, *numbers, **kwargs): - self.numbers = self.get_number_mobjects( - *numbers, **kwargs - ) - self.add(self.numbers) - return self + if excluding is None: + excluding = self.numbers_to_exclude + + numbers = VGroup() + for x in x_values: + if excluding is not None and x in excluding: + continue + numbers.add(self.get_number_mobject(x, **kwargs)) + self.add(numbers) + self.numbers = numbers + return numbers class UnitInterval(NumberLine): CONFIG = { - "x_min": 0, - "x_max": 1, - "unit_size": 6, - "tick_frequency": 0.1, + "x_range": [0, 1, 0.1], + "unit_size": 10, "numbers_with_elongated_ticks": [0, 1], - "number_at_center": 0.5, "decimal_number_config": { "num_decimal_places": 1, } diff --git a/manimlib/mobject/numbers.py b/manimlib/mobject/numbers.py index 338228fd..34d1fa6c 100644 --- a/manimlib/mobject/numbers.py +++ b/manimlib/mobject/numbers.py @@ -1,26 +1,66 @@ from manimlib.constants import * -from manimlib.mobject.svg.tex_mobject import SingleStringTexMobject +from manimlib.mobject.svg.tex_mobject import SingleStringTex +from manimlib.mobject.svg.text_mobject import Text from manimlib.mobject.types.vectorized_mobject import VMobject -# TODO, have this cache TexMobjects +string_to_mob_map = {} + + class DecimalNumber(VMobject): CONFIG = { + "stroke_width": 0, + "fill_opacity": 1.0, "num_decimal_places": 2, "include_sign": False, "group_with_commas": True, - "digit_to_digit_buff": 0.05, + "digit_buff_per_font_unit": 0.001, "show_ellipsis": False, "unit": None, # Aligned to bottom unless it starts with "^" "include_background_rectangle": False, "edge_to_fix": LEFT, + "font_size": 48, } def __init__(self, number=0, **kwargs): super().__init__(**kwargs) - self.number = number - self.initial_config = kwargs + self.set_submobjects_from_number(number) + self.init_colors() + def set_submobjects_from_number(self, number): + self.number = number + self.set_submobjects([]) + + num_string = self.get_num_string(number) + self.add(*map(self.string_to_mob, num_string)) + + # Add non-numerical bits + if self.show_ellipsis: + self.add(self.string_to_mob("...")) + if self.unit is not None: + self.unit_sign = self.string_to_mob(self.unit, SingleStringTex) + self.add(self.unit_sign) + + self.arrange( + buff=self.digit_buff_per_font_unit * self.get_font_size(), + aligned_edge=DOWN + ) + + # Handle alignment of parts that should be aligned + # to the bottom + for i, c in enumerate(num_string): + if c == "–" and len(num_string) > i + 1: + self[i].align_to(self[i + 1], UP) + self[i].shift(self[i + 1].get_height() * DOWN / 2) + elif c == ",": + self[i].shift(self[i].get_height() * DOWN / 2) + if self.unit and self.unit.startswith("^"): + self.unit_sign.align_to(self, UP) + + if self.include_background_rectangle: + self.add_background_rectangle() + + def get_num_string(self, number): if isinstance(number, complex): formatter = self.get_complex_formatter() else: @@ -33,45 +73,22 @@ class DecimalNumber(VMobject): num_string = "+" + num_string[1:] else: num_string = num_string[1:] + num_string = num_string.replace("-", "–") + return num_string - self.add(*[ - SingleStringTexMobject(char, **kwargs) - for char in num_string - ]) + def init_data(self): + super().init_data() + self.data["font_size"] = np.array([self.font_size], dtype=float) - # Add non-numerical bits - if self.show_ellipsis: - self.add(SingleStringTexMobject("\\dots")) + def get_font_size(self): + return self.data["font_size"][0] - if num_string.startswith("-"): - minus = self.submobjects[0] - minus.next_to( - self.submobjects[1], LEFT, - buff=self.digit_to_digit_buff - ) - - if self.unit is not None: - self.unit_sign = SingleStringTexMobject(self.unit, color=self.color) - self.add(self.unit_sign) - - self.arrange( - buff=self.digit_to_digit_buff, - aligned_edge=DOWN - ) - - # Handle alignment of parts that should be aligned - # to the bottom - for i, c in enumerate(num_string): - if c == "-" and len(num_string) > i + 1: - self[i].align_to(self[i + 1], UP) - self[i].shift(self[i + 1].get_height() * DOWN / 2) - elif c == ",": - self[i].shift(self[i].get_height() * DOWN / 2) - if self.unit and self.unit.startswith("^"): - self.unit_sign.align_to(self, UP) - # - if self.include_background_rectangle: - self.add_background_rectangle() + def string_to_mob(self, string, mob_class=Text): + if string not in string_to_mob_map: + string_to_mob_map[string] = mob_class(string, font_size=1) + mob = string_to_mob_map[string].copy() + mob.scale(self.get_font_size()) + return mob def get_formatter(self, **kwargs): """ @@ -109,25 +126,18 @@ class DecimalNumber(VMobject): "i" ]) - def set_value(self, number, **config): - full_config = dict(self.CONFIG) - full_config.update(self.initial_config) - full_config.update(config) - new_decimal = DecimalNumber(number, **full_config) - # Make sure last digit has constant height - new_decimal.scale( - self[-1].get_height() / new_decimal[-1].get_height() - ) - new_decimal.move_to(self, self.edge_to_fix) - new_decimal.match_style(self) + def set_value(self, number): + move_to_point = self.get_edge_center(self.edge_to_fix) + old_submobjects = self.submobjects + self.set_submobjects_from_number(number) + self.move_to(move_to_point, self.edge_to_fix) + for sm1, sm2 in zip(self.submobjects, old_submobjects): + sm1.match_style(sm2) + return self - old_family = self.get_family() - self.set_submobjects(new_decimal.submobjects) - for mob in old_family: - # Dumb hack...due to how scene handles families - # of animated mobjects - mob.points[:] = 0 - self.number = number + def scale(self, scale_factor, **kwargs): + super().scale(scale_factor, **kwargs) + self.data["font_size"] *= scale_factor return self def get_value(self): diff --git a/manimlib/mobject/probability.py b/manimlib/mobject/probability.py index 55705af1..69a71069 100644 --- a/manimlib/mobject/probability.py +++ b/manimlib/mobject/probability.py @@ -3,8 +3,8 @@ from manimlib.mobject.geometry import Line from manimlib.mobject.geometry import Rectangle from manimlib.mobject.mobject import Mobject from manimlib.mobject.svg.brace import Brace -from manimlib.mobject.svg.tex_mobject import TexMobject -from manimlib.mobject.svg.tex_mobject import TextMobject +from manimlib.mobject.svg.tex_mobject import Tex +from manimlib.mobject.svg.tex_mobject import TexText from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.utils.color import color_gradient from manimlib.utils.iterables import listify @@ -16,17 +16,17 @@ class SampleSpace(Rectangle): CONFIG = { "height": 3, "width": 3, - "fill_color": DARK_GREY, + "fill_color": GREY_D, "fill_opacity": 1, "stroke_width": 0.5, - "stroke_color": LIGHT_GREY, + "stroke_color": GREY_B, ## "default_label_scale_val": 1, } def add_title(self, title="Sample space", buff=MED_SMALL_BUFF): # TODO, should this really exist in SampleSpaceScene - title_mob = TextMobject(title) + title_mob = TexText(title) if title_mob.get_width() > self.get_width(): title_mob.set_width(self.get_width()) title_mob.next_to(self, UP, buff=buff) @@ -97,7 +97,7 @@ class SampleSpace(Rectangle): if isinstance(label, Mobject): label_mob = label else: - label_mob = TexMobject(label) + label_mob = Tex(label) label_mob.scale(self.default_label_scale_val) label_mob.next_to(brace, direction, buff) @@ -188,7 +188,7 @@ class BarChart(VGroup): if self.label_y_axis: labels = VGroup() for tick, value in zip(ticks, values): - label = TexMobject(str(np.round(value, 2))) + label = Tex(str(np.round(value, 2))) label.set_height(self.y_axis_label_height) label.next_to(tick, LEFT, SMALL_BUFF) labels.add(label) @@ -211,7 +211,7 @@ class BarChart(VGroup): bar_labels = VGroup() for bar, name in zip(bars, self.bar_names): - label = TexMobject(str(name)) + label = Tex(str(name)) label.scale(self.bar_label_scale_val) label.next_to(bar, DOWN, SMALL_BUFF) bar_labels.add(label) diff --git a/manimlib/mobject/shape_matchers.py b/manimlib/mobject/shape_matchers.py index a6933f21..9b320740 100644 --- a/manimlib/mobject/shape_matchers.py +++ b/manimlib/mobject/shape_matchers.py @@ -4,6 +4,7 @@ from manimlib.mobject.geometry import Rectangle from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.utils.color import Color +from manimlib.utils.customization import get_customization from manimlib.utils.config_ops import digest_config @@ -23,15 +24,16 @@ class SurroundingRectangle(Rectangle): class BackgroundRectangle(SurroundingRectangle): CONFIG = { - "color": BLACK, "stroke_width": 0, "stroke_opacity": 0, "fill_opacity": 0.75, "buff": 0 } - def __init__(self, mobject, **kwargs): - SurroundingRectangle.__init__(self, mobject, **kwargs) + def __init__(self, mobject, color=None, **kwargs): + if color is None: + color = get_customization()['style']['background_color'] + SurroundingRectangle.__init__(self, mobject, color=color, **kwargs) self.original_fill_opacity = self.fill_opacity def pointwise_become_partial(self, mobject, a, b): @@ -62,16 +64,17 @@ class BackgroundRectangle(SurroundingRectangle): class Cross(VGroup): CONFIG = { "stroke_color": RED, - "stroke_width": 6, + "stroke_width": [0, 6, 0], } def __init__(self, mobject, **kwargs): - VGroup.__init__(self, - Line(UP + LEFT, DOWN + RIGHT), - Line(UP + RIGHT, DOWN + LEFT), - ) + super().__init__( + Line(UL, DR), + Line(UR, DL), + ) + self.insert_n_curves(2) self.replace(mobject, stretch=True) - self.set_stroke(self.stroke_color, self.stroke_width) + self.set_stroke(self.stroke_color, width=self.stroke_width) class Underline(Line): diff --git a/manimlib/mobject/svg/__init__.py b/manimlib/mobject/svg/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manimlib/mobject/svg/brace.py b/manimlib/mobject/svg/brace.py index c6a0796f..cd686fbb 100644 --- a/manimlib/mobject/svg/brace.py +++ b/manimlib/mobject/svg/brace.py @@ -1,46 +1,53 @@ import numpy as np +import math from manimlib.animation.composition import AnimationGroup from manimlib.constants import * from manimlib.animation.fading import FadeIn from manimlib.animation.growing import GrowFromCenter -from manimlib.mobject.svg.tex_mobject import TexMobject -from manimlib.mobject.svg.tex_mobject import TextMobject +from manimlib.mobject.svg.tex_mobject import Tex +from manimlib.mobject.svg.tex_mobject import SingleStringTex +from manimlib.mobject.svg.tex_mobject import TexText +from manimlib.mobject.svg.text_mobject import Text from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.utils.config_ops import digest_config from manimlib.utils.space_ops import get_norm -class Brace(TexMobject): +class Brace(SingleStringTex): CONFIG = { "buff": 0.2, - "width_multiplier": 2, - "max_num_quads": 15, - "min_num_quads": 0, - "background_stroke_width": 0, + "tex_string": r"\underbrace{\qquad}" } def __init__(self, mobject, direction=DOWN, **kwargs): digest_config(self, kwargs, locals()) - angle = -np.arctan2(*direction[:2]) + np.pi + angle = -math.atan2(*direction[:2]) + PI mobject.rotate(-angle, about_point=ORIGIN) left = mobject.get_corner(DOWN + LEFT) right = mobject.get_corner(DOWN + RIGHT) target_width = right[0] - left[0] - # Adding int(target_width) qquads gives approximately the right width - num_quads = np.clip( - int(self.width_multiplier * target_width), - self.min_num_quads, self.max_num_quads - ) - tex_string = "\\underbrace{%s}" % (num_quads * "\\qquad") - TexMobject.__init__(self, tex_string, **kwargs) + super().__init__(self.tex_string, **kwargs) self.tip_point_index = np.argmin(self.get_all_points()[:, 1]) - self.stretch_to_fit_width(target_width) + self.set_initial_width(target_width) self.shift(left - self.get_corner(UP + LEFT) + self.buff * DOWN) for mob in mobject, self: mob.rotate(angle, about_point=ORIGIN) + def set_initial_width(self, width): + width_diff = width - self.get_width() + if width_diff > 0: + for tip, rect, vect in [(self[0], self[1], RIGHT), (self[5], self[4], LEFT)]: + rect.set_width( + width_diff / 2 + rect.get_width(), + about_edge=vect, stretch=True + ) + tip.shift(-width_diff / 2 * vect) + else: + self.set_width(width, stretch=True) + return self + def put_at_tip(self, mob, use_next_to=True, **kwargs): if use_next_to: mob.next_to( @@ -55,13 +62,14 @@ class Brace(TexMobject): mob.shift(self.get_direction() * shift_distance) return self - def get_text(self, *text, **kwargs): - text_mob = TextMobject(*text) - self.put_at_tip(text_mob, **kwargs) + def get_text(self, text, **kwargs): + buff = kwargs.pop("buff", SMALL_BUFF) + text_mob = Text(text, **kwargs) + self.put_at_tip(text_mob, buff=buff) return text_mob def get_tex(self, *tex, **kwargs): - tex_mob = TexMobject(*tex) + tex_mob = Tex(*tex) self.put_at_tip(tex_mob, **kwargs) return tex_mob @@ -78,7 +86,7 @@ class Brace(TexMobject): class BraceLabel(VMobject): CONFIG = { - "label_constructor": TexMobject, + "label_constructor": Tex, "label_scale": 1, } @@ -135,5 +143,5 @@ class BraceLabel(VMobject): class BraceText(BraceLabel): CONFIG = { - "label_constructor": TextMobject + "label_constructor": TexText } diff --git a/manimlib/mobject/svg/drawings.py b/manimlib/mobject/svg/drawings.py index 5d23876d..71953fb8 100644 --- a/manimlib/mobject/svg/drawings.py +++ b/manimlib/mobject/svg/drawings.py @@ -1,10 +1,6 @@ -import itertools as it -from colour import Color - from manimlib.animation.animation import Animation from manimlib.animation.rotation import Rotating from manimlib.constants import * -from manimlib.mobject.geometry import AnnularSector from manimlib.mobject.geometry import Arc from manimlib.mobject.geometry import Circle from manimlib.mobject.geometry import Line @@ -13,19 +9,34 @@ from manimlib.mobject.geometry import Rectangle from manimlib.mobject.geometry import Square from manimlib.mobject.mobject import Mobject from manimlib.mobject.svg.svg_mobject import SVGMobject -from manimlib.mobject.svg.tex_mobject import TexMobject -from manimlib.mobject.svg.tex_mobject import TextMobject +from manimlib.mobject.svg.tex_mobject import Tex +from manimlib.mobject.svg.tex_mobject import TexText from manimlib.mobject.three_dimensions import Cube from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject -from manimlib.mobject.types.vectorized_mobject import VectorizedPoint -from manimlib.utils.bezier import interpolate from manimlib.utils.config_ops import digest_config from manimlib.utils.rate_functions import linear from manimlib.utils.space_ops import angle_of_vector from manimlib.utils.space_ops import complex_to_R3 from manimlib.utils.space_ops import rotate_vector -from manimlib.utils.space_ops import center_of_mass + + +class Checkmark(TexText): + CONFIG = { + "color": GREEN + } + + def __init__(self, **kwargs): + super().__init__("\\ding{51}") + + +class Exmark(TexText): + CONFIG = { + "color": RED + } + + def __init__(self, **kwargs): + super().__init__("\\ding{55}") class Lightbulb(SVGMobject): @@ -39,49 +50,6 @@ class Lightbulb(SVGMobject): } -class BitcoinLogo(SVGMobject): - CONFIG = { - "file_name": "Bitcoin_logo", - "height": 1, - "fill_color": "#f7931a", - "inner_color": WHITE, - "fill_opacity": 1, - "stroke_width": 0, - } - - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - self[0].set_fill(self.fill_color, self.fill_opacity) - self[1].set_fill(self.inner_color, 1) - - -class Guitar(SVGMobject): - CONFIG = { - "file_name": "guitar", - "height": 2.5, - "fill_color": DARK_GREY, - "fill_opacity": 1, - "stroke_color": WHITE, - "stroke_width": 0.5, - } - - -class SunGlasses(SVGMobject): - CONFIG = { - "file_name": "sunglasses", - "glasses_width_to_eyes_width": 1.1, - } - - def __init__(self, pi_creature, **kwargs): - SVGMobject.__init__(self, **kwargs) - self.set_stroke(WHITE, width=0) - self.set_fill(GREY, 1) - self.set_width( - self.glasses_width_to_eyes_width * pi_creature.eyes.get_width() - ) - self.move_to(pi_creature.eyes, UP) - - class Speedometer(VMobject): CONFIG = { "arc_angle": 4 * np.pi / 3, @@ -103,7 +71,7 @@ class Speedometer(VMobject): for index, angle in enumerate(tick_angle_range): vect = rotate_vector(RIGHT, angle) tick = Line((1 - self.tick_length) * vect, vect) - label = TexMobject(str(10 * index)) + label = Tex(str(10 * index)) label.set_height(self.tick_length) label.shift((1 + self.tick_length) * vect) self.add(tick, label) @@ -149,58 +117,6 @@ class Speedometer(VMobject): return self -class AoPSLogo(SVGMobject): - CONFIG = { - "file_name": "aops_logo", - "height": 1.5, - } - - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - self.set_stroke(WHITE, width=0) - colors = [BLUE_E, "#008445", GREEN_B] - index_lists = [ - (10, 11, 12, 13, 14, 21, 22, 23, 24, 27, 28, 29, 30), - (0, 1, 2, 3, 4, 15, 16, 17, 26), - (5, 6, 7, 8, 9, 18, 19, 20, 25) - ] - for color, index_list in zip(colors, index_lists): - for i in index_list: - self.submobjects[i].set_fill(color, opacity=1) - - self.set_height(self.height) - self.center() - - -class PartyHat(SVGMobject): - CONFIG = { - "file_name": "party_hat", - "height": 1.5, - "pi_creature": None, - "stroke_width": 0, - "fill_opacity": 1, - "frills_colors": [MAROON_B, PURPLE], - "cone_color": GREEN, - "dots_colors": [YELLOW], - } - NUM_FRILLS = 7 - NUM_DOTS = 6 - - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - self.set_height(self.height) - if self.pi_creature is not None: - self.next_to(self.pi_creature.eyes, UP, buff=0) - - self.frills = VGroup(*self[:self.NUM_FRILLS]) - self.cone = self[self.NUM_FRILLS] - self.dots = VGroup(*self[self.NUM_FRILLS + 1:]) - - self.frills.set_color_by_gradient(*self.frills_colors) - self.cone.set_color(self.cone_color) - self.dots.set_color_by_gradient(*self.dots_colors) - - class Laptop(VGroup): CONFIG = { "width": 3, @@ -216,7 +132,7 @@ class Laptop(VGroup): }, "fill_opacity": 1, "stroke_width": 0, - "body_color": LIGHT_GREY, + "body_color": GREY_B, "shaded_body_color": GREY, "open_angle": np.pi / 4, } @@ -256,7 +172,7 @@ class Laptop(VGroup): fill_opacity=1, ) screen.replace(screen_plate, stretch=True) - screen.scale_in_place(self.screen_width_to_screen_plate_width) + screen.scale(self.screen_width_to_screen_plate_width) screen.next_to(screen_plate, OUT, buff=0.1 * SMALL_BUFF) screen_plate.add(screen) screen_plate.next_to(body, UP, buff=0) @@ -280,22 +196,6 @@ class Laptop(VGroup): self.rotate(np.pi / 6, DOWN, about_point=ORIGIN) -class PatreonLogo(SVGMobject): - CONFIG = { - "file_name": "patreon_logo", - "fill_color": "#F96854", - # "fill_color" : WHITE, - "fill_opacity": 1, - "stroke_width": 0, - "width": 4, - } - - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - self.set_width(self.width) - self.center() - - class VideoIcon(SVGMobject): CONFIG = { "file_name": "video_icon", @@ -325,28 +225,6 @@ class VideoSeries(VGroup): self.set_color_by_gradient(*self.gradient_colors) -class Headphones(SVGMobject): - CONFIG = { - "file_name": "headphones", - "height": 2, - "y_stretch_factor": 0.5, - "color": GREY, - } - - def __init__(self, pi_creature=None, **kwargs): - digest_config(self, kwargs) - SVGMobject.__init__(self, file_name=self.file_name, **kwargs) - self.stretch(self.y_stretch_factor, 1) - self.set_height(self.height) - self.set_stroke(width=0) - self.set_fill(color=self.color) - if pi_creature is not None: - eyes = pi_creature.eyes - self.set_height(3 * eyes.get_height()) - self.move_to(eyes, DOWN) - self.shift(DOWN * eyes.get_height() / 4) - - class Clock(VGroup): CONFIG = {} @@ -428,11 +306,7 @@ class Bubble(SVGMobject): digest_config(self, kwargs, locals()) if self.file_name is None: raise Exception("Must invoke Bubble subclass") - try: - SVGMobject.__init__(self, **kwargs) - except IOError: - self.file_name = os.path.join(FILE_DIR, self.file_name) - SVGMobject.__init__(self, **kwargs) + SVGMobject.__init__(self, self.file_name, **kwargs) self.center() self.stretch_to_fit_height(self.height) self.stretch_to_fit_width(self.width) @@ -459,6 +333,7 @@ class Bubble(SVGMobject): def flip(self, axis=UP): Mobject.flip(self, axis=axis) + self.refresh_unit_normal() self.refresh_triangulation() if abs(axis[1]) > 0: self.direction = -np.array(self.direction) @@ -490,7 +365,7 @@ class Bubble(SVGMobject): return self.content def write(self, *text): - self.add_content(TextMobject(*text)) + self.add_content(TexText(*text)) return self def resize_to_content(self): @@ -539,96 +414,6 @@ class ThoughtBubble(Bubble): return self -class Car(SVGMobject): - CONFIG = { - "file_name": "Car", - "height": 1, - "color": LIGHT_GREY, - "light_colors": [BLACK, BLACK], - } - - def __init__(self, **kwargs): - SVGMobject.__init__(self, **kwargs) - - path = self.submobjects[0] - subpaths = path.get_subpaths() - path.clear_points() - for indices in [(0, 1), (2, 3), (4, 6, 7), (5,), (8,)]: - part = VMobject() - for index in indices: - part.append_points(subpaths[index]) - path.add(part) - - self.set_height(self.height) - self.set_stroke(color=WHITE, width=0) - self.set_fill(self.color, opacity=1) - - from manimlib.for_3b1b_videos.pi_creature import Randolph - randy = Randolph(mode="happy") - randy.set_height(0.6 * self.get_height()) - randy.stretch(0.8, 0) - randy.look(RIGHT) - randy.move_to(self) - randy.shift(0.07 * self.height * (RIGHT + UP)) - self.randy = self.pi_creature = randy - self.add_to_back(randy) - - orientation_line = Line(self.get_left(), self.get_right()) - orientation_line.set_stroke(width=0) - self.add(orientation_line) - self.orientation_line = orientation_line - - for light, color in zip(self.get_lights(), self.light_colors): - light.set_fill(color, 1) - light.is_subpath = False - - self.add_treds_to_tires() - - def move_to(self, point_or_mobject): - vect = rotate_vector( - UP + LEFT, self.orientation_line.get_angle() - ) - self.next_to(point_or_mobject, vect, buff=0) - return self - - def get_front_line(self): - return DashedLine( - self.get_corner(UP + RIGHT), - self.get_corner(DOWN + RIGHT), - color=DISTANCE_COLOR, - dash_length=0.05, - ) - - def add_treds_to_tires(self): - for tire in self.get_tires(): - radius = tire.get_width() / 2 - center = tire.get_center() - tred = Line( - 0.7 * radius * RIGHT, 1.1 * radius * RIGHT, - stroke_width=2, - color=BLACK - ) - tred.rotate(PI / 5, about_point=tred.get_end()) - for theta in np.arange(0, 2 * np.pi, np.pi / 4): - new_tred = tred.copy() - new_tred.rotate(theta, about_point=ORIGIN) - new_tred.shift(center) - tire.add(new_tred) - return self - - def get_tires(self): - return VGroup(self[1][0], self[1][1]) - - def get_lights(self): - return VGroup(self.get_front_light(), self.get_rear_light()) - - def get_front_light(self): - return self[1][3] - - def get_rear_light(self): - return self[1][4] - - class VectorizedEarth(SVGMobject): CONFIG = { "file_name": "earth", @@ -646,448 +431,3 @@ class VectorizedEarth(SVGMobject): ) circle.replace(self) self.add_to_back(circle) - - -class Logo(VMobject): - CONFIG = { - "pupil_radius": 1.0, - "outer_radius": 2.0, - "iris_background_blue": "#74C0E3", - "iris_background_brown": "#8C6239", - "blue_spike_colors": [ - "#528EA3", - "#3E6576", - "#224C5B", - BLACK, - ], - "brown_spike_colors": [ - "#754C24", - "#603813", - "#42210b", - BLACK, - ], - "n_spike_layers": 4, - "n_spikes": 28, - "spike_angle": TAU / 28, - } - - def __init__(self, **kwargs): - VMobject.__init__(self, **kwargs) - self.add_iris_back() - self.add_spikes() - self.add_pupil() - - def add_iris_back(self): - blue_iris_back = AnnularSector( - inner_radius=self.pupil_radius, - outer_radius=self.outer_radius, - angle=270 * DEGREES, - start_angle=180 * DEGREES, - fill_color=self.iris_background_blue, - fill_opacity=1, - stroke_width=0, - ) - brown_iris_back = AnnularSector( - inner_radius=self.pupil_radius, - outer_radius=self.outer_radius, - angle=90 * DEGREES, - start_angle=90 * DEGREES, - fill_color=self.iris_background_brown, - fill_opacity=1, - stroke_width=0, - ) - self.iris_background = VGroup( - blue_iris_back, - brown_iris_back, - ) - self.add(self.iris_background) - - def add_spikes(self): - layers = VGroup() - radii = np.linspace( - self.outer_radius, - self.pupil_radius, - self.n_spike_layers, - endpoint=False, - ) - radii[:2] = radii[1::-1] # Swap first two - if self.n_spike_layers > 2: - radii[-1] = interpolate( - radii[-1], self.pupil_radius, 0.25 - ) - - for radius in radii: - tip_angle = self.spike_angle - half_base = radius * np.tan(tip_angle) - triangle, right_half_triangle = [ - Polygon( - radius * UP, - half_base * RIGHT, - vertex3, - fill_opacity=1, - stroke_width=0, - ) - for vertex3 in (half_base * LEFT, ORIGIN,) - ] - left_half_triangle = right_half_triangle.copy() - left_half_triangle.flip(UP, about_point=ORIGIN) - - n_spikes = self.n_spikes - full_spikes = [ - triangle.copy().rotate( - -angle, - about_point=ORIGIN - ) - for angle in np.linspace( - 0, TAU, n_spikes, endpoint=False - ) - ] - index = (3 * n_spikes) // 4 - if radius == radii[0]: - layer = VGroup(*full_spikes) - layer.rotate( - -TAU / n_spikes / 2, - about_point=ORIGIN - ) - layer.brown_index = index - else: - half_spikes = [ - right_half_triangle.copy(), - left_half_triangle.copy().rotate( - 90 * DEGREES, about_point=ORIGIN, - ), - right_half_triangle.copy().rotate( - 90 * DEGREES, about_point=ORIGIN, - ), - left_half_triangle.copy() - ] - layer = VGroup(*it.chain( - half_spikes[:1], - full_spikes[1:index], - half_spikes[1:3], - full_spikes[index + 1:], - half_spikes[3:], - )) - layer.brown_index = index + 1 - - layers.add(layer) - - # Color spikes - blues = self.blue_spike_colors - browns = self.brown_spike_colors - for layer, blue, brown in zip(layers, blues, browns): - index = layer.brown_index - layer[:index].set_color(blue) - layer[index:].set_color(brown) - - self.spike_layers = layers - self.add(layers) - - def add_pupil(self): - self.pupil = Circle( - radius=self.pupil_radius, - fill_color=BLACK, - fill_opacity=1, - stroke_width=0, - sheen=0.0, - ) - self.pupil.rotate(90 * DEGREES) - self.add(self.pupil) - - def cut_pupil(self): - pupil = self.pupil - center = pupil.get_center() - new_pupil = VGroup(*[ - pupil.copy().pointwise_become_partial(pupil, a, b) - for (a, b) in [(0.25, 1), (0, 0.25)] - ]) - for sector in new_pupil: - sector.add_cubic_bezier_curve_to([ - sector.points[-1], - *[center] * 3, - *[sector.points[0]] * 2 - ]) - self.remove(pupil) - self.add(new_pupil) - self.pupil = new_pupil - - def get_blue_part_and_brown_part(self): - if len(self.pupil) == 1: - self.cut_pupil() - # circle = Circle() - # circle.set_stroke(width=0) - # circle.set_fill(BLACK, opacity=1) - # circle.match_width(self) - # circle.move_to(self) - blue_part = VGroup( - self.iris_background[0], - *[ - layer[:layer.brown_index] - for layer in self.spike_layers - ], - self.pupil[0], - ) - brown_part = VGroup( - self.iris_background[1], - *[ - layer[layer.brown_index:] - for layer in self.spike_layers - ], - self.pupil[1], - ) - return blue_part, brown_part - - -# Cards -class DeckOfCards(VGroup): - def __init__(self, **kwargs): - possible_values = list(map(str, list(range(1, 11)))) + ["J", "Q", "K"] - possible_suits = ["hearts", "diamonds", "spades", "clubs"] - VGroup.__init__(self, *[ - PlayingCard(value=value, suit=suit, **kwargs) - for value in possible_values - for suit in possible_suits - ]) - - -class PlayingCard(VGroup): - CONFIG = { - "value": None, - "suit": None, - "key": None, # String like "8H" or "KS" - "height": 2, - "height_to_width": 3.5 / 2.5, - "card_height_to_symbol_height": 7, - "card_width_to_corner_num_width": 10, - "card_height_to_corner_num_height": 10, - "color": GREY_A, - "turned_over": False, - "possible_suits": ["hearts", "diamonds", "spades", "clubs"], - "possible_values": list(map(str, list(range(2, 11)))) + ["J", "Q", "K", "A"], - } - - def __init__(self, key=None, **kwargs): - VGroup.__init__(self, **kwargs) - - self.key = key - self.add(Rectangle( - height=self.height, - width=self.height / self.height_to_width, - stroke_color=WHITE, - stroke_width=2, - fill_color=self.color, - fill_opacity=1, - )) - if self.turned_over: - self.set_fill(DARK_GREY) - self.set_stroke(LIGHT_GREY) - contents = VectorizedPoint(self.get_center()) - else: - value = self.get_value() - symbol = self.get_symbol() - design = self.get_design(value, symbol) - corner_numbers = self.get_corner_numbers(value, symbol) - contents = VGroup(design, corner_numbers) - self.design = design - self.corner_numbers = corner_numbers - self.add(contents) - - def get_value(self): - value = self.value - if value is None: - if self.key is not None: - value = self.key[:-1] - else: - value = random.choice(self.possible_values) - value = str(value).upper() - if value == "1": - value = "A" - if value not in self.possible_values: - raise Exception("Invalid card value") - - face_card_to_value = { - "J": 11, - "Q": 12, - "K": 13, - "A": 14, - } - try: - self.numerical_value = int(value) - except Exception: - self.numerical_value = face_card_to_value[value] - return value - - def get_symbol(self): - suit = self.suit - if suit is None: - if self.key is not None: - suit = dict([ - (s[0].upper(), s) - for s in self.possible_suits - ])[self.key[-1].upper()] - else: - suit = random.choice(self.possible_suits) - if suit not in self.possible_suits: - raise Exception("Invalud suit value") - self.suit = suit - symbol_height = float(self.height) / self.card_height_to_symbol_height - symbol = SuitSymbol(suit, height=symbol_height) - return symbol - - def get_design(self, value, symbol): - if value == "A": - return self.get_ace_design(symbol) - if value in list(map(str, list(range(2, 11)))): - return self.get_number_design(value, symbol) - else: - return self.get_face_card_design(value, symbol) - - def get_ace_design(self, symbol): - design = symbol.copy().scale(1.5) - design.move_to(self) - return design - - def get_number_design(self, value, symbol): - num = int(value) - n_rows = { - 2: 2, - 3: 3, - 4: 2, - 5: 2, - 6: 3, - 7: 3, - 8: 3, - 9: 4, - 10: 4, - }[num] - n_cols = 1 if num in [2, 3] else 2 - insertion_indices = { - 5: [0], - 7: [0], - 8: [0, 1], - 9: [1], - 10: [0, 2], - }.get(num, []) - - top = self.get_top() + symbol.get_height() * DOWN - bottom = self.get_bottom() + symbol.get_height() * UP - column_points = [ - interpolate(top, bottom, alpha) - for alpha in np.linspace(0, 1, n_rows) - ] - - design = VGroup(*[ - symbol.copy().move_to(point) - for point in column_points - ]) - if n_cols == 2: - space = 0.2 * self.get_width() - column_copy = design.copy().shift(space * RIGHT) - design.shift(space * LEFT) - design.add(*column_copy) - design.add(*[ - symbol.copy().move_to( - center_of_mass(column_points[i:i + 2]) - ) - for i in insertion_indices - ]) - for symbol in design: - if symbol.get_center()[1] < self.get_center()[1]: - symbol.rotate_in_place(np.pi) - return design - - def get_face_card_design(self, value, symbol): - from manimlib.for_3b1b_videos.pi_creature import PiCreature - sub_rect = Rectangle( - stroke_color=BLACK, - fill_opacity=0, - height=0.9 * self.get_height(), - width=0.6 * self.get_width(), - ) - sub_rect.move_to(self) - - # pi_color = average_color(symbol.get_color(), GREY) - pi_color = symbol.get_color() - if Color(pi_color) == Color(BLACK): - pi_color = GREY_D - pi_mode = { - "J": "plain", - "Q": "thinking", - "K": "hooray" - }[value] - pi_creature = PiCreature( - mode=pi_mode, - color=pi_color, - ) - pi_creature.set_width(0.8 * sub_rect.get_width()) - if value in ["Q", "K"]: - prefix = "king" if value == "K" else "queen" - crown = SVGMobject(file_name=prefix + "_crown") - crown.set_stroke(width=0) - crown.set_fill(YELLOW, 1) - crown.stretch_to_fit_width(0.5 * sub_rect.get_width()) - crown.stretch_to_fit_height(0.17 * sub_rect.get_height()) - crown.move_to(pi_creature.eyes.get_center(), DOWN) - pi_creature.add_to_back(crown) - to_top_buff = 0 - else: - to_top_buff = SMALL_BUFF * sub_rect.get_height() - pi_creature.next_to(sub_rect.get_top(), DOWN, to_top_buff) - # pi_creature.shift(0.05*sub_rect.get_width()*RIGHT) - - pi_copy = pi_creature.copy() - pi_copy.rotate(np.pi, about_point=sub_rect.get_center()) - - return VGroup(sub_rect, pi_creature, pi_copy) - - def get_corner_numbers(self, value, symbol): - value_mob = TextMobject(value) - width = self.get_width() / self.card_width_to_corner_num_width - height = self.get_height() / self.card_height_to_corner_num_height - value_mob.set_width(width) - value_mob.stretch_to_fit_height(height) - value_mob.next_to( - self.get_corner(UP + LEFT), DOWN + RIGHT, - buff=MED_LARGE_BUFF * width - ) - value_mob.set_color(symbol.get_color()) - corner_symbol = symbol.copy() - corner_symbol.set_width(width) - corner_symbol.next_to( - value_mob, DOWN, - buff=MED_SMALL_BUFF * width - ) - corner_group = VGroup(value_mob, corner_symbol) - opposite_corner_group = corner_group.copy() - opposite_corner_group.rotate( - np.pi, about_point=self.get_center() - ) - - return VGroup(corner_group, opposite_corner_group) - - -class SuitSymbol(SVGMobject): - CONFIG = { - "height": 0.5, - "fill_opacity": 1, - "stroke_width": 0, - "red": "#D02028", - "black": BLACK, - } - - def __init__(self, suit_name, **kwargs): - digest_config(self, kwargs) - suits_to_colors = { - "hearts": self.red, - "diamonds": self.red, - "spades": self.black, - "clubs": self.black, - } - if suit_name not in suits_to_colors: - raise Exception("Invalid suit name") - SVGMobject.__init__(self, file_name=suit_name, **kwargs) - - color = suits_to_colors[suit_name] - self.set_stroke(width=0) - self.set_fill(color, 1) - self.set_height(self.height) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index abcd7d91..deb902a6 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -11,7 +11,6 @@ from manimlib.constants import DEFAULT_STROKE_WIDTH from manimlib.constants import ORIGIN, UP, DOWN, LEFT, RIGHT from manimlib.constants import BLACK from manimlib.constants import WHITE -import manimlib.constants as consts from manimlib.mobject.geometry import Circle from manimlib.mobject.geometry import Rectangle @@ -20,6 +19,8 @@ from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.utils.color import * from manimlib.utils.config_ops import digest_config +from manimlib.utils.directories import get_mobject_data_dir +from manimlib.utils.images import get_full_vector_image_path def string_to_numbers(num_string): @@ -43,30 +44,26 @@ class SVGMobject(VMobject): # TODO, style components should be read in, not defaulted "stroke_width": DEFAULT_STROKE_WIDTH, "fill_opacity": 1.0, + "path_string_config": {} } def __init__(self, file_name=None, **kwargs): digest_config(self, kwargs) self.file_name = file_name or self.file_name - self.ensure_valid_file() - VMobject.__init__(self, **kwargs) - self.move_into_position() - - def ensure_valid_file(self): - file_name = self.file_name if file_name is None: raise Exception("Must specify file for SVGMobject") - possible_paths = [ - os.path.join(os.path.join("assets", "svg_images"), file_name), - os.path.join(os.path.join("assets", "svg_images"), file_name + ".svg"), - os.path.join(os.path.join("assets", "svg_images"), file_name + ".xdv"), - file_name, - ] - for path in possible_paths: - if os.path.exists(path): - self.file_path = path - return - raise IOError(f"No file matching {file_name} in image directory") + self.file_path = get_full_vector_image_path(file_name) + + super().__init__(**kwargs) + self.move_into_position() + + def move_into_position(self): + if self.should_center: + self.center() + if self.height is not None: + self.set_height(self.height) + if self.width is not None: + self.set_width(self.width) def init_points(self): doc = minidom.parse(self.file_path) @@ -123,7 +120,10 @@ class SVGMobject(VMobject): return mob.submobjects def path_string_to_mobject(self, path_string): - return VMobjectFromSVGPathstring(path_string) + return VMobjectFromSVGPathstring( + path_string, + **self.path_string_config, + ) def use_to_mobjects(self, use_element): # Remove initial "#" character @@ -145,8 +145,8 @@ class SVGMobject(VMobject): def polygon_to_mobject(self, polygon_element): path_string = polygon_element.getAttribute("points") for digit in string.digits: - path_string = path_string.replace(f" {digit}", f"{digit} L") - path_string = "M" + path_string + path_string = path_string.replace(f" {digit}", f"L {digit}") + path_string = path_string.replace("L", "M", 1) return self.path_string_to_mobject(path_string) def circle_to_mobject(self, circle_element): @@ -169,7 +169,11 @@ class SVGMobject(VMobject): else 0.0 for key in ("cx", "cy", "rx", "ry") ] - return Circle().scale(rx * RIGHT + ry * UP).shift(x * RIGHT + y * DOWN) + result = Circle() + result.stretch(rx, 0) + result.stretch(ry, 1) + result.shift(x * RIGHT + y * DOWN) + return result def rect_to_mobject(self, rect_element): fill_color = rect_element.getAttribute("fill") @@ -256,7 +260,7 @@ class SVGMobject(VMobject): matrix[:, 1] *= -1 for mob in mobject.family_members_with_points(): - mob.points = np.dot(mob.points, matrix) + mob.apply_matrix(matrix.T) mobject.shift(x * RIGHT + y * UP) except: pass @@ -312,39 +316,29 @@ class SVGMobject(VMobject): new_refs = dict([(e.getAttribute('id'), e) for e in self.get_all_childNodes_have_id(defs)]) self.ref_to_element.update(new_refs) - def move_into_position(self): - if self.should_center: - self.center() - if self.height is not None: - self.set_height(self.height) - if self.width is not None: - self.set_width(self.width) - class VMobjectFromSVGPathstring(VMobject): CONFIG = { "long_lines": True, "should_subdivide_sharp_curves": False, + "should_remove_null_curves": False, } def __init__(self, path_string, **kwargs): self.path_string = path_string - VMobject.__init__(self, **kwargs) + super().__init__(**kwargs) def init_points(self): - # TODO, move this caching operation - # higher up to Mobject somehow. - hasher = hashlib.sha256() - hasher.update(self.path_string.encode()) + # After a given svg_path has been converted into points, the result + # will be saved to a file so that future calls for the same path + # don't need to retrace the same computation. + hasher = hashlib.sha256(self.path_string.encode()) path_hash = hasher.hexdigest()[:16] + points_filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}_points.npy") + tris_filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}_tris.npy") - filepath = os.path.join( - consts.MOBJECT_POINTS_DIR, - f"{path_hash}.npy" - ) - - if os.path.exists(filepath): - self.points = np.load(filepath) + if os.path.exists(points_filepath) and os.path.exists(tris_filepath): + self.set_points(np.load(points_filepath)) else: self.relative_point = np.array(ORIGIN) for command, coord_string in self.get_commands_and_coord_strings(): @@ -353,12 +347,13 @@ class VMobjectFromSVGPathstring(VMobject): if self.should_subdivide_sharp_curves: # For a healthy triangulation later self.subdivide_sharp_curves() + if self.should_remove_null_curves: + # Get rid of any null curves + self.set_points(self.get_points_without_null_curves()) # SVG treats y-coordinate differently self.stretch(-1, 1, about_point=ORIGIN) # Save to a file for future use - np.save(filepath, self.points) - # Faster rendering - self.lock_triangulation() + np.save(points_filepath, self.get_points()) def get_commands_and_coord_strings(self): all_commands = list(self.get_command_to_function_map().keys()) @@ -385,11 +380,11 @@ class VMobjectFromSVGPathstring(VMobject): command = "l" if command.islower(): leftover_points -= self.relative_point - self.relative_point = self.points[-1] + self.relative_point = self.get_last_point() self.handle_command(command, leftover_points) else: # Command is over, reset for future relative commands - self.relative_point = self.points[-1] + self.relative_point = self.get_last_point() def string_to_points(self, command, coord_string): numbers = string_to_numbers(coord_string) diff --git a/manimlib/mobject/svg/tex_mobject.py b/manimlib/mobject/svg/tex_mobject.py index d65757f8..8cebe1f5 100644 --- a/manimlib/mobject/svg/tex_mobject.py +++ b/manimlib/mobject/svg/tex_mobject.py @@ -1,53 +1,76 @@ from functools import reduce import operator as op +import re +import itertools as it from manimlib.constants import * from manimlib.mobject.geometry import Line from manimlib.mobject.svg.svg_mobject import SVGMobject -from manimlib.mobject.svg.svg_mobject import VMobjectFromSVGPathstring +from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.mobject.types.vectorized_mobject import VGroup -from manimlib.mobject.types.vectorized_mobject import VectorizedPoint from manimlib.utils.config_ops import digest_config -from manimlib.utils.strings import split_string_list_to_isolate_substrings from manimlib.utils.tex_file_writing import tex_to_svg_file +from manimlib.utils.tex_file_writing import get_tex_config +from manimlib.utils.tex_file_writing import display_during_execution -TEX_MOB_SCALE_FACTOR = 0.05 +SCALE_FACTOR_PER_FONT_POINT = 0.001 -class TexSymbol(VMobjectFromSVGPathstring): +tex_string_to_mob_map = {} + + +class SingleStringTex(VMobject): CONFIG = { - "should_subdivide_sharp_curves": True, - } - - -class SingleStringTexMobject(SVGMobject): - CONFIG = { - "template_tex_file_body": TEMPLATE_TEX_FILE_BODY, - "stroke_width": 0, "fill_opacity": 1.0, - "background_stroke_width": 1, - "background_stroke_color": BLACK, + "stroke_width": 0, "should_center": True, + "font_size": 48, "height": None, "organize_left_to_right": False, - "alignment": "", + "alignment": "\\centering", + "math_mode": True, } def __init__(self, tex_string, **kwargs): - digest_config(self, kwargs) + super().__init__(**kwargs) assert(isinstance(tex_string, str)) self.tex_string = tex_string - file_name = tex_to_svg_file( - self.get_modified_expression(tex_string), - self.template_tex_file_body - ) - SVGMobject.__init__(self, file_name=file_name, **kwargs) + if tex_string not in tex_string_to_mob_map: + with display_during_execution(f" Writing \"{tex_string}\""): + full_tex = self.get_tex_file_body(tex_string) + filename = tex_to_svg_file(full_tex) + svg_mob = SVGMobject( + filename, + height=None, + path_string_config={ + "should_subdivide_sharp_curves": True, + "should_remove_null_curves": True, + } + ) + tex_string_to_mob_map[tex_string] = svg_mob + self.add(*( + sm.copy() + for sm in tex_string_to_mob_map[tex_string] + )) + self.init_colors() + if self.height is None: - self.scale(TEX_MOB_SCALE_FACTOR) + self.scale(SCALE_FACTOR_PER_FONT_POINT * self.font_size) if self.organize_left_to_right: self.organize_submobjects_left_to_right() + def get_tex_file_body(self, tex_string): + new_tex = self.get_modified_expression(tex_string) + if self.math_mode: + new_tex = "\\begin{align*}\n" + new_tex + "\n\\end{align*}" + + tex_config = get_tex_config() + return tex_config["tex_body"].replace( + tex_config["text_to_replace"], + new_tex + ) + def get_modified_expression(self, tex_string): result = self.alignment + " " + tex_string result = result.strip() @@ -55,13 +78,14 @@ class SingleStringTexMobject(SVGMobject): return result def modify_special_strings(self, tex): - tex = self.remove_stray_braces(tex) + tex = tex.strip() should_add_filler = reduce(op.or_, [ # Fraction line needs something to be over tex == "\\over", tex == "\\overline", # Makesure sqrt has overbar tex == "\\sqrt", + tex == "\\sqrt{", # Need to add blank subscript or superscript tex.endswith("_"), tex.endswith("^"), @@ -81,6 +105,8 @@ class SingleStringTexMobject(SVGMobject): if tex.startswith("\\\\"): tex = tex.replace("\\\\", "\\quad\\\\") + tex = self.balance_braces(tex) + # Handle imbalanced \left and \right num_lefts, num_rights = [ len([ @@ -103,14 +129,11 @@ class SingleStringTexMobject(SVGMobject): tex = "" return tex - def remove_stray_braces(self, tex): + def balance_braces(self, tex): """ - Makes TexMobject resiliant to unmatched { at start + Makes Tex resiliant to unmatched { at start """ - num_lefts, num_rights = [ - tex.count(char) - for char in "{}" - ] + num_lefts, num_rights = [tex.count(char) for char in "{}"] while num_rights > num_lefts: tex = "{" + tex num_lefts += 1 @@ -119,33 +142,29 @@ class SingleStringTexMobject(SVGMobject): num_rights += 1 return tex - def get_tex_string(self): + def get_tex(self): return self.tex_string - def path_string_to_mobject(self, path_string): - # Overwrite superclass default to use - # specialized path_string mobject - return TexSymbol(path_string) - def organize_submobjects_left_to_right(self): self.sort(lambda p: p[0]) return self -class TexMobject(SingleStringTexMobject): +class Tex(SingleStringTex): CONFIG = { - "arg_separator": " ", - "substrings_to_isolate": [], + "arg_separator": "", + # Note, use of isolate is largely rendered + # moot by the fact that you can surround such strings in + # {{ and }} as needed. + "isolate": [], "tex_to_color_map": {}, } def __init__(self, *tex_strings, **kwargs): digest_config(self, kwargs) - tex_strings = self.break_up_tex_strings(tex_strings) - self.tex_strings = tex_strings - SingleStringTexMobject.__init__( - self, self.arg_separator.join(tex_strings), **kwargs - ) + self.tex_strings = self.break_up_tex_strings(tex_strings) + full_string = self.arg_separator.join(self.tex_strings) + super().__init__(full_string, **kwargs) self.break_up_by_substrings() self.set_color_by_tex_to_color_map(self.tex_to_color_map) @@ -153,18 +172,17 @@ class TexMobject(SingleStringTexMobject): self.organize_submobjects_left_to_right() def break_up_tex_strings(self, tex_strings): - substrings_to_isolate = op.add( - self.substrings_to_isolate, - list(self.tex_to_color_map.keys()) + # Separate out any strings specified in the isolate + # or tex_to_color_map lists. + patterns = ( + "({})".format(re.escape(ss)) + for ss in it.chain(self.isolate, self.tex_to_color_map.keys()) ) - split_list = split_string_list_to_isolate_substrings( - tex_strings, *substrings_to_isolate - ) - if self.arg_separator == ' ': - split_list = [str(x).strip() for x in split_list] - #split_list = list(map(str.strip, split_list)) - split_list = [s for s in split_list if s != ''] - return split_list + pattern = "|".join(patterns) + pieces = [] + for s in tex_strings: + pieces.extend(re.split(pattern, s)) + return list(filter(lambda s: s, pieces)) def break_up_by_substrings(self): """ @@ -172,23 +190,24 @@ class TexMobject(SingleStringTexMobject): deeper based on the structure of tex_strings (as a list of tex_strings) """ + if len(self.tex_strings) == 1: + submob = self.copy() + self.set_submobjects([submob]) + return self new_submobjects = [] curr_index = 0 config = dict(self.CONFIG) config["alignment"] = "" for tex_string in self.tex_strings: - sub_tex_mob = SingleStringTexMobject(tex_string, **config) - num_submobs = len(sub_tex_mob.submobjects) - new_index = curr_index + num_submobs + tex_string = tex_string.strip() + if len(tex_string) == 0: + continue + sub_tex_mob = SingleStringTex(tex_string, **config) + num_submobs = len(sub_tex_mob) if num_submobs == 0: - # For cases like empty tex_strings, we want the corresponing - # part of the whole TexMobject to be a VectorizedPoint - # positioned in the right part of the TexMobject - sub_tex_mob.set_submobjects([VectorizedPoint()]) - last_submob_index = min(curr_index, len(self.submobjects) - 1) - sub_tex_mob.move_to(self.submobjects[last_submob_index], RIGHT) - else: - sub_tex_mob.set_submobjects(self.submobjects[curr_index:new_index]) + continue + new_index = curr_index + num_submobs + sub_tex_mob.set_submobjects(self[curr_index:new_index]) new_submobjects.append(sub_tex_mob) curr_index = new_index self.set_submobjects(new_submobjects) @@ -204,72 +223,70 @@ class TexMobject(SingleStringTexMobject): else: return tex1 == tex2 - return VGroup(*[ - m - for m in self.submobjects - if isinstance(m, SingleStringTexMobject) and test(tex, m.get_tex_string()) - ]) + return VGroup(*filter( + lambda m: isinstance(m, SingleStringTex) and test(tex, m.get_tex()), + self.submobjects + )) def get_part_by_tex(self, tex, **kwargs): all_parts = self.get_parts_by_tex(tex, **kwargs) return all_parts[0] if all_parts else None def set_color_by_tex(self, tex, color, **kwargs): - parts_to_color = self.get_parts_by_tex(tex, **kwargs) - for part in parts_to_color: - part.set_color(color) + self.get_parts_by_tex(tex, **kwargs).set_color(color) return self - def set_color_by_tex_to_color_map(self, texs_to_color_map, **kwargs): - for texs, color in list(texs_to_color_map.items()): - try: - # If the given key behaves like tex_strings - texs + '' - self.set_color_by_tex(texs, color, **kwargs) - except TypeError: - # If the given key is a tuple - for tex in texs: - self.set_color_by_tex(tex, color, **kwargs) + def set_color_by_tex_to_color_map(self, tex_to_color_map, **kwargs): + for tex, color in list(tex_to_color_map.items()): + self.set_color_by_tex(tex, color, **kwargs) return self - def index_of_part(self, part): - split_self = self.split() - if part not in split_self: - raise Exception("Trying to get index of part not in TexMobject") - return split_self.index(part) + def index_of_part(self, part, start=0): + return self.submobjects.index(part, start) - def index_of_part_by_tex(self, tex, **kwargs): + def index_of_part_by_tex(self, tex, start=0, **kwargs): part = self.get_part_by_tex(tex, **kwargs) - return self.index_of_part(part) + return self.index_of_part(part, start) + + def slice_by_tex(self, start_tex=None, stop_tex=None, **kwargs): + if start_tex is None: + start_index = 0 + else: + start_index = self.index_of_part_by_tex(start_tex, **kwargs) + + if stop_tex is None: + return self[start_index:] + else: + stop_index = self.index_of_part_by_tex(stop_tex, start=start_index, **kwargs) + return self[start_index:stop_index] def sort_alphabetically(self): - self.submobjects.sort( - key=lambda m: m.get_tex_string() - ) + self.submobjects.sort(key=lambda m: m.get_tex()) + + def set_bstroke(self, color=BLACK, width=4): + self.set_stroke(color, width, background=True) + return self -class TextMobject(TexMobject): +class TexText(Tex): CONFIG = { - "template_tex_file_body": TEMPLATE_TEXT_FILE_BODY, - "alignment": "\\centering", + "math_mode": False, "arg_separator": "", } -class BulletedList(TextMobject): +class BulletedList(TexText): CONFIG = { "buff": MED_LARGE_BUFF, "dot_scale_factor": 2, - # Have to include because of handle_multiple_args implementation - "template_tex_file_body": TEMPLATE_TEXT_FILE_BODY, "alignment": "", } def __init__(self, *items, **kwargs): line_separated_items = [s + "\\\\" for s in items] - TextMobject.__init__(self, *line_separated_items, **kwargs) + TexText.__init__(self, *line_separated_items, **kwargs) for part in self: - dot = TexMobject("\\cdot").scale(self.dot_scale_factor) + dot = Tex("\\cdot").scale(self.dot_scale_factor) dot.next_to(part[0], LEFT, SMALL_BUFF) part.add_to_back(dot) self.arrange( @@ -293,7 +310,7 @@ class BulletedList(TextMobject): other_part.set_fill(opacity=opacity) -class TexMobjectFromPresetString(TexMobject): +class TexFromPresetString(Tex): CONFIG = { # To be filled by subclasses "tex": None, @@ -302,11 +319,11 @@ class TexMobjectFromPresetString(TexMobject): def __init__(self, **kwargs): digest_config(self, kwargs) - TexMobject.__init__(self, self.tex, **kwargs) + Tex.__init__(self, self.tex, **kwargs) self.set_color(self.color) -class Title(TextMobject): +class Title(TexText): CONFIG = { "scale_factor": 1, "include_underline": True, @@ -317,7 +334,7 @@ class Title(TextMobject): } def __init__(self, *text_parts, **kwargs): - TextMobject.__init__(self, *text_parts, **kwargs) + TexText.__init__(self, *text_parts, **kwargs) self.scale(self.scale_factor) self.to_edge(UP) if self.include_underline: diff --git a/manimlib/mobject/svg/text_mobject.py b/manimlib/mobject/svg/text_mobject.py index 8b6ff7d1..e0161f23 100644 --- a/manimlib/mobject/svg/text_mobject.py +++ b/manimlib/mobject/svg/text_mobject.py @@ -1,70 +1,63 @@ -import re -import os import copy import hashlib -import cairo -import manimlib.constants as consts +import os +import re +import typing +from contextlib import contextmanager +from pathlib import Path + +import manimpango from manimlib.constants import * +from manimlib.mobject.geometry import Dot from manimlib.mobject.svg.svg_mobject import SVGMobject +from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.utils.config_ops import digest_config +from manimlib.utils.customization import get_customization +from manimlib.utils.directories import get_downloads_dir, get_text_dir +from manimpango import PangoUtils +from manimpango import TextSetting - -TEXT_MOB_SCALE_FACTOR = 0.05 - - -class TextSetting(object): - def __init__(self, start, end, font, slant, weight, line_num=-1): - self.start = start - self.end = end - self.font = font - self.slant = slant - self.weight = weight - self.line_num = line_num +TEXT_MOB_SCALE_FACTOR = 0.001048 class Text(SVGMobject): CONFIG = { # Mobject - 'color': consts.WHITE, - 'height': None, + "color": WHITE, + "height": None, + "stroke_width": 0, # Text - 'font': '', - 'gradient': None, - 'lsh': -1, - 'size': 1, - 'slant': NORMAL, - 'weight': NORMAL, - 't2c': {}, - 't2f': {}, - 't2g': {}, - 't2s': {}, - 't2w': {}, + "font": '', + "gradient": None, + "lsh": -1, + "size": 1, + "font_size": 48, + "tab_width": 4, + "slant": NORMAL, + "weight": NORMAL, + "t2c": {}, + "t2f": {}, + "t2g": {}, + "t2s": {}, + "t2w": {}, + "disable_ligatures": True, } def __init__(self, text, **config): - self.text = text self.full2short(config) digest_config(self, config) self.lsh = self.size if self.lsh == -1 else self.lsh - + text_without_tabs = text + if text.find('\t') != -1: + text_without_tabs = text.replace('\t', ' ' * self.tab_width) + self.text = text_without_tabs file_name = self.text2svg() - self.remove_last_M(file_name) + PangoUtils.remove_last_M(file_name) + self.remove_empty_path(file_name) SVGMobject.__init__(self, file_name, **config) - - nppc = self.n_points_per_curve - for each in self: - if len(each.points) == 0: - continue - points = each.points - last = points[0] - each.clear_points() - for index, point in enumerate(points): - each.append_points([point]) - if index != len(points) - 1 and (index + 1) % nppc == 0 and any(point != points[index+1]): - each.add_line_to(last) - last = points[index + 1] - each.add_line_to(last) - + self.text = text + if self.disable_ligatures: + self.apply_space_chars() if self.t2c: self.set_color_by_t2c() if self.gradient: @@ -74,15 +67,24 @@ class Text(SVGMobject): # anti-aliasing if self.height is None: - self.scale(TEXT_MOB_SCALE_FACTOR) + self.scale(TEXT_MOB_SCALE_FACTOR * self.font_size) - def remove_last_M(self, file_name): + def remove_empty_path(self, file_name): with open(file_name, 'r') as fpr: content = fpr.read() - content = re.sub(r'Z M [^A-Za-z]*? "\/>', 'Z "/>', content) + content = re.sub(r'', '', content) with open(file_name, 'w') as fpw: fpw.write(content) + def apply_space_chars(self): + submobs = self.submobjects.copy() + for char_index in range(len(self.text)): + if self.text[char_index] in [" ", "\t", "\n"]: + space = Dot(radius=0, fill_opacity=0, stroke_opacity=0) + space.move_to(submobs[max(char_index - 1, 0)].get_center()) + submobs.insert(char_index, space) + self.set_submobjects(submobs) + def find_indexes(self, word): m = re.match(r'\[([0-9\-]{0,}):([0-9\-]{0,})\]', word) if m: @@ -99,6 +101,19 @@ class Text(SVGMobject): index = self.text.find(word, index + len(word)) return indexes + def get_parts_by_text(self, word): + return VGroup(*( + self[i:j] + for i, j in self.find_indexes(word) + )) + + def get_part_by_text(self, word): + parts = self.get_parts_by_text(word) + if len(parts) > 0: + return parts[0] + else: + return None + def full2short(self, config): for kwargs in [config, self.CONFIG]: if kwargs.__contains__('line_spacing_height'): @@ -126,25 +141,11 @@ class Text(SVGMobject): for start, end in self.find_indexes(word): self[start:end].set_color_by_gradient(*gradient) - def str2slant(self, string): - if string == NORMAL: - return cairo.FontSlant.NORMAL - if string == ITALIC: - return cairo.FontSlant.ITALIC - if string == OBLIQUE: - return cairo.FontSlant.OBLIQUE - - def str2weight(self, string): - if string == NORMAL: - return cairo.FontWeight.NORMAL - if string == BOLD: - return cairo.FontWeight.BOLD - def text2hash(self): settings = self.font + self.slant + self.weight settings += str(self.t2f) + str(self.t2s) + str(self.t2w) settings += str(self.lsh) + str(self.size) - id_str = self.text+settings + id_str = self.text + settings hasher = hashlib.sha256() hasher.update(id_str.encode()) return hasher.hexdigest()[:16] @@ -201,34 +202,79 @@ class Text(SVGMobject): lsh = self.lsh * 10 if self.font == '': - print(NOT_SETTING_FONT_MSG) + self.font = get_customization()['style']['font'] - dir_name = consts.TEXT_DIR + dir_name = get_text_dir() hash_name = self.text2hash() - file_name = os.path.join(dir_name, hash_name)+'.svg' + file_name = os.path.join(dir_name, hash_name) + '.svg' if os.path.exists(file_name): return file_name - - surface = cairo.SVGSurface(file_name, 600, 400) - context = cairo.Context(surface) - context.set_font_size(size) - context.move_to(START_X, START_Y) - settings = self.text2settings() - offset_x = 0 - last_line_num = 0 - for setting in settings: - font = setting.font - slant = self.str2slant(setting.slant) - weight = self.str2weight(setting.weight) - text = self.text[setting.start:setting.end].replace('\n', ' ') + width = 600 + height = 400 + disable_liga = self.disable_ligatures + return manimpango.text2svg( + settings, + size, + lsh, + disable_liga, + file_name, + START_X, + START_Y, + width, + height, + self.text, + ) - context.select_font_face(font, slant, weight) - if setting.line_num != last_line_num: - offset_x = 0 - last_line_num = setting.line_num - context.move_to(START_X + offset_x, START_Y + lsh*setting.line_num) - context.show_text(text) - offset_x += context.text_extents(text)[4] - return file_name +@contextmanager +def register_font(font_file: typing.Union[str, Path]): + """Temporarily add a font file to Pango's search path. + This searches for the font_file at various places. The order it searches it described below. + 1. Absolute path. + 2. Downloads dir. + + Parameters + ---------- + font_file : + The font file to add. + Examples + -------- + Use ``with register_font(...)`` to add a font file to search + path. + .. code-block:: python + with register_font("path/to/font_file.ttf"): + a = Text("Hello", font="Custom Font Name") + Raises + ------ + FileNotFoundError: + If the font doesn't exists. + AttributeError: + If this method is used on macOS. + Notes + ----- + This method of adding font files also works with :class:`CairoText`. + .. important :: + This method is available for macOS for ``ManimPango>=v0.2.3``. Using this + method with previous releases will raise an :class:`AttributeError` on macOS. + """ + + input_folder = Path(get_downloads_dir()).parent.resolve() + possible_paths = [ + Path(font_file), + input_folder / font_file, + ] + for path in possible_paths: + path = path.resolve() + if path.exists(): + file_path = path + break + else: + error = f"Can't find {font_file}." f"Tried these : {possible_paths}" + raise FileNotFoundError(error) + + try: + assert manimpango.register_font(str(file_path)) + yield + finally: + manimpango.unregister_font(str(file_path)) diff --git a/manimlib/mobject/three_dimensions.py b/manimlib/mobject/three_dimensions.py index 8e44d7c9..75740ae7 100644 --- a/manimlib/mobject/three_dimensions.py +++ b/manimlib/mobject/three_dimensions.py @@ -1,115 +1,175 @@ +import math + from manimlib.constants import * -from manimlib.mobject.geometry import Square -from manimlib.mobject.types.surface_mobject import SurfaceMobject +from manimlib.mobject.types.surface import Surface +from manimlib.mobject.types.surface import SGroup from manimlib.mobject.types.vectorized_mobject import VGroup +from manimlib.mobject.types.vectorized_mobject import VMobject +from manimlib.utils.config_ops import digest_config +from manimlib.utils.space_ops import get_norm +from manimlib.utils.space_ops import z_to_vector -class ParametricSurface(SurfaceMobject): +class SurfaceMesh(VGroup): CONFIG = { - "u_range": (0, 1), - "v_range": (0, 1), - "resolution": (32, 32), - "surface_piece_config": {}, - "fill_color": BLUE_D, - "fill_opacity": 1.0, - "checkerboard_colors": [BLUE_D, BLUE_E], - "stroke_color": LIGHT_GREY, - "stroke_width": 0.5, - "should_make_jagged": False, - "pre_function_handle_to_anchor_scale_factor": 0.00001, + "resolution": (21, 21), + "stroke_width": 1, + "normal_nudge": 1e-2, + "depth_test": True, + "flat_stroke": False, } - def __init__(self, function=None, **kwargs): - if function is None: - self.uv_func = self.func - else: - self.uv_func = function + def __init__(self, uv_surface, **kwargs): + if not isinstance(uv_surface, Surface): + raise Exception("uv_surface must be of type Surface") + self.uv_surface = uv_surface super().__init__(**kwargs) def init_points(self): - epsilon = 1e-6 # For differentials - nu, nv = self.resolution - u_range = np.linspace(*self.u_range, nu + 1) - v_range = np.linspace(*self.v_range, nv + 1) - # List of three grids, [Pure uv values, those nudged by du, those nudged by dv] - uv_grids = [ - np.array([[[u, v] for v in v_range] for u in u_range]) - for (du, dv) in [(0, 0), (epsilon, 0), (0, epsilon)] - ] - point_grid, points_nudged_du, points_nudged_dv = [ - np.apply_along_axis(lambda p: self.uv_func(*p), 2, uv_grid) - for uv_grid in uv_grids - ] - normal_grid = np.cross( - (points_nudged_du - point_grid) / epsilon, - (points_nudged_dv - point_grid) / epsilon, - ) + uv_surface = self.uv_surface - self.set_points( - self.get_triangle_ready_array_from_grid(point_grid), - self.get_triangle_ready_array_from_grid(normal_grid), - ) + full_nu, full_nv = uv_surface.resolution + part_nu, part_nv = self.resolution + u_indices = np.linspace(0, full_nu, part_nu).astype(int) + v_indices = np.linspace(0, full_nv, part_nv).astype(int) - # self.points = point_grid[indices] + points, du_points, dv_points = uv_surface.get_surface_points_and_nudged_points() + normals = uv_surface.get_unit_normals() + nudge = 1e-2 + nudged_points = points + nudge * normals - def get_triangle_ready_array_from_grid(self, grid): - # Given a grid, say of points or normals, this returns an Nx3 array - # whose rows are elements from this grid in such such a way that successive - # triplets of points form triangles covering the grid. - nu = grid.shape[0] - 1 - nv = grid.shape[1] - 1 - dim = grid.shape[2] - arr = np.zeros((nu * nv * 6, dim)) - # To match the triangles covering this surface - arr[0::6] = grid[:-1, :-1].reshape((nu * nv, 3)) # Top left - arr[1::6] = grid[+1:, :-1].reshape((nu * nv, 3)) # Bottom left - arr[2::6] = grid[:-1, +1:].reshape((nu * nv, 3)) # Top right - arr[3::6] = grid[:-1, +1:].reshape((nu * nv, 3)) # Top right - arr[4::6] = grid[+1:, :-1].reshape((nu * nv, 3)) # Bottom left - arr[5::6] = grid[+1:, +1:].reshape((nu * nv, 3)) # Bottom right - return arr - - def func(self, u, v): - pass + for ui in u_indices: + path = VMobject() + full_ui = full_nv * ui + path.set_points_smoothly(nudged_points[full_ui:full_ui + full_nv]) + self.add(path) + for vi in v_indices: + path = VMobject() + path.set_points_smoothly(nudged_points[vi::full_nv]) + self.add(path) -# Sphere, cylinder, cube, prism +# 3D shapes -class Sphere(ParametricSurface): +class Sphere(Surface): CONFIG = { - "resolution": (12, 24), + "resolution": (101, 51), "radius": 1, - "u_range": (0, PI), - "v_range": (0, TAU), + "u_range": (0, TAU), + "v_range": (0, PI), } - def func(self, u, v): + def uv_func(self, u, v): return self.radius * np.array([ - np.cos(v) * np.sin(u), - np.sin(v) * np.sin(u), - np.cos(u) + np.cos(u) * np.sin(v), + np.sin(u) * np.sin(v), + -np.cos(v) ]) -class Cube(VGroup): +class Torus(Surface): CONFIG = { - "fill_opacity": 0.75, - "fill_color": BLUE, - "stroke_width": 0, + "u_range": (0, TAU), + "v_range": (0, TAU), + "r1": 3, + "r2": 1, + } + + def uv_func(self, u, v): + P = np.array([math.cos(u), math.sin(u), 0]) + return (self.r1 - self.r2 * math.cos(v)) * P - math.sin(v) * OUT + + +class Cylinder(Surface): + CONFIG = { + "height": 2, + "radius": 1, + "axis": OUT, + "u_range": (0, TAU), + "v_range": (-1, 1), + "resolution": (101, 11), + } + + def init_points(self): + super().init_points() + self.scale(self.radius) + self.set_depth(self.height, stretch=True) + self.apply_matrix(z_to_vector(self.axis)) + return self + + def uv_func(self, u, v): + return [np.cos(u), np.sin(u), v] + + +class Line3D(Cylinder): + CONFIG = { + "width": 0.05, + "resolution": (21, 25) + } + + def __init__(self, start, end, **kwargs): + digest_config(self, kwargs) + axis = end - start + super().__init__( + height=get_norm(axis), + radius=self.width / 2, + axis=axis + ) + self.shift((start + end) / 2) + + +class Disk3D(Surface): + CONFIG = { + "radius": 1, + "u_range": (0, 1), + "v_range": (0, TAU), + "resolution": (2, 25), + } + + def init_points(self): + super().init_points() + self.scale(self.radius) + + def uv_func(self, u, v): + return [ + u * np.cos(v), + u * np.sin(v), + 0 + ] + + +class Square3D(Surface): + CONFIG = { + "side_length": 2, + "u_range": (-1, 1), + "v_range": (-1, 1), + "resolution": (2, 2), + } + + def init_points(self): + super().init_points() + self.scale(self.side_length / 2) + + def uv_func(self, u, v): + return [u, v, 0] + + +class Cube(SGroup): + CONFIG = { + "color": BLUE, + "opacity": 1, + "gloss": 0.5, + "square_resolution": (2, 2), "side_length": 2, } def init_points(self): - for vect in IN, OUT, LEFT, RIGHT, UP, DOWN: - face = Square( - side_length=self.side_length, - shade_in_3d=True, - ) - face.flip() - face.shift(self.side_length * OUT / 2.0) + for vect in [OUT, RIGHT, UP, LEFT, DOWN, IN]: + face = Square3D(resolution=self.square_resolution) + face.shift(OUT) face.apply_matrix(z_to_vector(vect)) - self.add(face) + self.set_height(self.side_length) class Prism(Cube): diff --git a/manimlib/mobject/types/__init__.py b/manimlib/mobject/types/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manimlib/mobject/types/dot_cloud.py b/manimlib/mobject/types/dot_cloud.py new file mode 100644 index 00000000..82e2b0ca --- /dev/null +++ b/manimlib/mobject/types/dot_cloud.py @@ -0,0 +1,108 @@ +import numpy as np +import moderngl + +from manimlib.constants import GREY_C +from manimlib.mobject.types.point_cloud_mobject import PMobject +from manimlib.utils.iterables import resize_preserving_order + + +DEFAULT_DOT_CLOUD_RADIUS = 0.05 +DEFAULT_GRID_HEIGHT = 6 +DEFAULT_BUFF_RATIO = 0.5 + + +class DotCloud(PMobject): + CONFIG = { + "color": GREY_C, + "opacity": 1, + "radius": DEFAULT_DOT_CLOUD_RADIUS, + "shader_folder": "true_dot", + "render_primitive": moderngl.POINTS, + "shader_dtype": [ + ('point', np.float32, (3,)), + ('radius', np.float32, (1,)), + ('color', np.float32, (4,)), + ], + } + + def __init__(self, points=None, **kwargs): + super().__init__(**kwargs) + if points is not None: + self.set_points(points) + + def init_data(self): + super().init_data() + self.data["radii"] = np.zeros((1, 1)) + self.set_radius(self.radius) + + def to_grid(self, n_rows, n_cols, n_layers=1, + buff_ratio=None, + h_buff_ratio=1.0, + v_buff_ratio=1.0, + d_buff_ratio=1.0, + height=DEFAULT_GRID_HEIGHT, + ): + n_points = n_rows * n_cols * n_layers + points = np.repeat(range(n_points), 3, axis=0).reshape((n_points, 3)) + points[:, 0] = points[:, 0] % n_cols + points[:, 1] = (points[:, 1] // n_cols) % n_rows + points[:, 2] = points[:, 2] // (n_rows * n_cols) + self.set_points(points.astype(float)) + + if buff_ratio is not None: + v_buff_ratio = buff_ratio + h_buff_ratio = buff_ratio + d_buff_ratio = buff_ratio + + radius = self.get_radius() + ns = [n_cols, n_rows, n_layers] + brs = [h_buff_ratio, v_buff_ratio, d_buff_ratio] + self.set_radius(0) + for n, br, dim in zip(ns, brs, range(3)): + self.rescale_to_fit(2 * radius * (1 + br) * (n - 1), dim, stretch=True) + self.set_radius(radius) + if height is not None: + self.set_height(height) + self.center() + return self + + def set_radii(self, radii): + self.data["radii"][:] = resize_preserving_order(radii, len(self.data["radii"])) + self.refresh_bounding_box() + return self + + def get_radii(self): + return self.data["radii"] + + def set_radius(self, radius): + self.data["radii"][:] = radius + self.refresh_bounding_box() + return self + + def get_radius(self): + return self.get_radii().max() + + def compute_bounding_box(self): + bb = super().compute_bounding_box() + radius = self.get_radius() + bb[0] += np.full((3,), -radius) + bb[2] += np.full((3,), radius) + return bb + + def scale(self, scale_factor, scale_radii=True, **kwargs): + super().scale(scale_factor, **kwargs) + if scale_radii: + self.set_radii(scale_factor * self.get_radii()) + return self + + def make_3d(self, gloss=0.5, shadow=0.2): + self.set_gloss(gloss) + self.set_shadow(shadow) + self.apply_depth_test() + return self + + def get_shader_data(self): + shader_data = super().get_shader_data() + self.read_data_to_shader(shader_data, "radius", "radii") + self.read_data_to_shader(shader_data, "color", "rgbas") + return shader_data diff --git a/manimlib/mobject/types/image_mobject.py b/manimlib/mobject/types/image_mobject.py index 222ee5c1..bada35e3 100644 --- a/manimlib/mobject/types/image_mobject.py +++ b/manimlib/mobject/types/image_mobject.py @@ -4,7 +4,7 @@ from PIL import Image from manimlib.constants import * from manimlib.mobject.mobject import Mobject -from manimlib.utils.bezier import interpolate +from manimlib.utils.bezier import inverse_interpolate from manimlib.utils.images import get_full_raster_image_path from manimlib.utils.iterables import listify @@ -13,8 +13,7 @@ class ImageMobject(Mobject): CONFIG = { "height": 4, "opacity": 1, - "vert_shader_file": "image_vert.glsl", - "frag_shader_file": "image_frag.glsl", + "shader_folder": "image", "shader_dtype": [ ('point', np.float32, (3,)), ('im_coords', np.float32, (2,)), @@ -25,45 +24,44 @@ class ImageMobject(Mobject): def __init__(self, filename, **kwargs): path = get_full_raster_image_path(filename) self.image = Image.open(path) - self.texture_path = path - Mobject.__init__(self, **kwargs) + self.texture_paths = {"Texture": path} + super().__init__(**kwargs) + + def init_data(self): + self.data = { + "points": np.array([UL, DL, UR, DR]), + "im_coords": np.array([(0, 0), (0, 1), (1, 0), (1, 1)]), + "opacity": np.array([[self.opacity]], dtype=np.float32), + } def init_points(self): - self.points = np.array([UL, DL, UR, DR]) size = self.image.size self.set_width(2 * size[0] / size[1], stretch=True) self.set_height(self.height) - self.im_coords = np.array( - [(0, 0), (0, 1), (1, 0), (1, 1)] - ) - - def init_colors(self): - self.set_opacity(self.opacity) - - def get_shader_data(self): - data = self.get_blank_shader_data_array(len(self.points)) - data["point"] = self.points - data["im_coords"] = self.im_coords - data["opacity"] = self.opacity - return data - - def set_opacity(self, alpha, family=True): - opacity = listify(alpha) - diff = 4 - len(opacity) - opacity += [opacity[-1]] * diff - self.opacity = np.array(opacity).reshape((4, 1)) - - if family: - for sm in self.submobjects: - sm.set_opacity(alpha) - - def fade(self, darkness=0.5, family=True): - self.set_opacity(1 - darkness, family) + def set_opacity(self, opacity, recurse=True): + for mob in self.get_family(recurse): + mob.data["opacity"] = np.array([[o] for o in listify(opacity)]) return self - def interpolate_color(self, mobject1, mobject2, alpha): - # TODO, transition between actual images? - self.opacity = interpolate( - mobject1.opacity, mobject2.opacity, alpha - ) + def point_to_rgb(self, point): + x0, y0 = self.get_corner(UL)[:2] + x1, y1 = self.get_corner(DR)[:2] + x_alpha = inverse_interpolate(x0, x1, point[0]) + y_alpha = inverse_interpolate(y0, y1, point[1]) + if not (0 <= x_alpha <= 1) and (0 <= y_alpha <= 1): + # TODO, raise smarter exception + raise Exception("Cannot sample color from outside an image") + + pw, ph = self.image.size + rgb = self.image.getpixel(( + int((pw - 1) * x_alpha), + int((ph - 1) * y_alpha), + )) + return np.array(rgb) / 255 + + def get_shader_data(self): + shader_data = super().get_shader_data() + self.read_data_to_shader(shader_data, "im_coords", "im_coords") + self.read_data_to_shader(shader_data, "opacity", "opacity") + return shader_data diff --git a/manimlib/mobject/types/point_cloud_mobject.py b/manimlib/mobject/types/point_cloud_mobject.py index 7e607274..e2ef6461 100644 --- a/manimlib/mobject/types/point_cloud_mobject.py +++ b/manimlib/mobject/types/point_cloud_mobject.py @@ -1,115 +1,60 @@ from manimlib.constants import * from manimlib.mobject.mobject import Mobject -from manimlib.utils.bezier import interpolate from manimlib.utils.color import color_gradient from manimlib.utils.color import color_to_rgba -from manimlib.utils.color import rgba_to_color -from manimlib.utils.iterables import stretch_array_to_length +from manimlib.utils.iterables import resize_with_interpolation +from manimlib.utils.iterables import resize_array class PMobject(Mobject): CONFIG = { - "stroke_width": DEFAULT_STROKE_WIDTH, + "opacity": 1.0, } - def reset_points(self): - self.rgbas = np.zeros((0, 4)) - self.points = np.zeros((0, 3)) + def resize_points(self, size, resize_func=resize_array): + # TODO + for key in self.data: + if len(self.data[key]) != size: + self.data[key] = resize_array(self.data[key], size) return self - def get_array_attrs(self): - return Mobject.get_array_attrs(self) + ["rgbas"] - - def add_points(self, points, rgbas=None, color=None, alpha=1): + def add_points(self, points, rgbas=None, color=None, opacity=None): """ points must be a Nx3 numpy array, as must rgbas if it is not None """ - if not isinstance(points, np.ndarray): - points = np.array(points) - num_new_points = len(points) - self.points = np.vstack([self.points, points]) - if rgbas is None: - color = Color(color) if color else self.color - rgbas = np.repeat( - [color_to_rgba(color, alpha)], - num_new_points, + self.append_points(points) + # rgbas array will have been resized with points + if color is not None: + if opacity is None: + opacity = self.data["rgbas"][-1, 3] + new_rgbas = np.repeat( + [color_to_rgba(color, opacity)], + len(points), axis=0 ) - elif len(rgbas) != len(points): - raise Exception("points and rgbas must have same shape") - self.rgbas = np.vstack([self.rgbas, rgbas]) + elif rgbas is not None: + new_rgbas = rgbas + self.data["rgbas"][-len(new_rgbas):] = new_rgbas return self - def set_color(self, color=YELLOW_C, family=True): - rgba = color_to_rgba(color) - mobs = self.family_members_with_points() if family else [self] - for mob in mobs: - mob.rgbas[:, :] = rgba - self.color = color - return self - - def get_stroke_width(self): - return self.stroke_width - - def set_stroke_width(self, width, family=True): - mobs = self.family_members_with_points() if family else [self] - for mob in mobs: - mob.stroke_width = width - return self - - # def set_color_by_gradient(self, start_color, end_color): def set_color_by_gradient(self, *colors): - self.rgbas = np.array(list(map( + self.data["rgbas"] = np.array(list(map( color_to_rgba, - color_gradient(colors, len(self.points)) + color_gradient(colors, self.get_num_points()) ))) return self - start_rgba, end_rgba = list(map(color_to_rgba, [start_color, end_color])) - for mob in self.family_members_with_points(): - num_points = mob.get_num_points() - mob.rgbas = np.array([ - interpolate(start_rgba, end_rgba, alpha) - for alpha in np.arange(num_points) / float(num_points) - ]) - return self - - def set_colors_by_radial_gradient(self, center=None, radius=1, inner_color=WHITE, outer_color=BLACK): - start_rgba, end_rgba = list(map(color_to_rgba, [start_color, end_color])) - if center is None: - center = self.get_center() - for mob in self.family_members_with_points(): - num_points = mob.get_num_points() - t = min(1, np.abs(mob.get_center() - center) / radius) - - mob.rgbas = np.array( - [interpolate(start_rgba, end_rgba, t)] * num_points - ) - return self - - def match_colors(self, mobject): - Mobject.align_data(self, mobject) - self.rgbas = np.array(mobject.rgbas) + def match_colors(self, pmobject): + self.data["rgbas"][:] = resize_with_interpolation( + pmobject.data["rgbas"], self.get_num_points() + ) return self def filter_out(self, condition): for mob in self.family_members_with_points(): - to_eliminate = ~np.apply_along_axis(condition, 1, mob.points) - mob.points = mob.points[to_eliminate] - mob.rgbas = mob.rgbas[to_eliminate] - return self - - def thin_out(self, factor=5): - """ - Removes all but every nth point for n = factor - """ - for mob in self.family_members_with_points(): - num_points = self.get_num_points() - mob.apply_over_attr_arrays( - lambda arr: arr[ - np.arange(0, num_points, factor) - ] - ) + to_keep = ~np.apply_along_axis(condition, 1, mob.get_points()) + for key in mob.data: + mob.data[key] = mob.data[key][to_keep] return self def sort_points(self, function=lambda p: p[0]): @@ -118,72 +63,37 @@ class PMobject(Mobject): """ for mob in self.family_members_with_points(): indices = np.argsort( - np.apply_along_axis(function, 1, mob.points) + np.apply_along_axis(function, 1, mob.get_points()) ) - mob.apply_over_attr_arrays(lambda arr: arr[indices]) + for key in mob.data: + mob.data[key] = mob.data[key][indices] return self - def fade_to(self, color, alpha): - self.rgbas = interpolate(self.rgbas, color_to_rgba(color), alpha) - for mob in self.submobjects: - mob.fade_to(color, alpha) - return self - - def get_all_rgbas(self): - return self.get_merged_array("rgbas") - def ingest_submobjects(self): - attrs = self.get_array_attrs() - arrays = list(map(self.get_merged_array, attrs)) - for attr, array in zip(attrs, arrays): - setattr(self, attr, array) - self.set_submobjects([]) + for key in self.data: + self.data[key] = np.vstack([ + sm.data[key] + for sm in self.get_family() + ]) return self - def get_color(self): - return rgba_to_color(self.rgbas[0, :]) - def point_from_proportion(self, alpha): index = alpha * (self.get_num_points() - 1) - return self.points[index] + return self.get_points()[int(index)] - # Alignment - def align_points_with_larger(self, larger_mobject): - assert(isinstance(larger_mobject, PMobject)) - self.apply_over_attr_arrays( - lambda a: stretch_array_to_length( - a, larger_mobject.get_num_points() - ) - ) - - def interpolate_color(self, mobject1, mobject2, alpha): - self.rgbas = interpolate( - mobject1.rgbas, mobject2.rgbas, alpha - ) - self.set_stroke_width(interpolate( - mobject1.get_stroke_width(), - mobject2.get_stroke_width(), - alpha, - )) + def pointwise_become_partial(self, pmobject, a, b): + lower_index = int(a * pmobject.get_num_points()) + upper_index = int(b * pmobject.get_num_points()) + for key in self.data: + self.data[key] = pmobject.data[key][lower_index:upper_index] return self - def pointwise_become_partial(self, mobject, a, b): - lower_index, upper_index = [ - int(x * mobject.get_num_points()) - for x in (a, b) - ] - for attr in self.get_array_attrs(): - full_array = getattr(mobject, attr) - partial_array = full_array[lower_index:upper_index] - setattr(self, attr, partial_array) - class PGroup(PMobject): def __init__(self, *pmobs, **kwargs): if not all([isinstance(m, PMobject) for m in pmobs]): raise Exception("All submobjects must be of type PMobject") - super().__init__(**kwargs) - self.add(*pmobs) + super().__init__(*pmobs, **kwargs) class Point(PMobject): @@ -192,5 +102,5 @@ class Point(PMobject): } def __init__(self, location=ORIGIN, **kwargs): - PMobject.__init__(self, **kwargs) + super().__init__(**kwargs) self.add_points([location]) diff --git a/manimlib/mobject/types/surface.py b/manimlib/mobject/types/surface.py new file mode 100644 index 00000000..b5e1571e --- /dev/null +++ b/manimlib/mobject/types/surface.py @@ -0,0 +1,280 @@ +import numpy as np +import moderngl + +from manimlib.constants import * +from manimlib.mobject.mobject import Mobject +from manimlib.utils.bezier import integer_interpolate +from manimlib.utils.bezier import interpolate +from manimlib.utils.images import get_full_raster_image_path +from manimlib.utils.iterables import listify +from manimlib.utils.space_ops import normalize_along_axis + + +class Surface(Mobject): + CONFIG = { + "u_range": (0, 1), + "v_range": (0, 1), + # Resolution counts number of points sampled, which for + # each coordinate is one more than the the number of + # rows/columns of approximating squares + "resolution": (101, 101), + "color": GREY, + "opacity": 1.0, + "gloss": 0.3, + "shadow": 0.4, + "prefered_creation_axis": 1, + # For du and dv steps. Much smaller and numerical error + # can crop up in the shaders. + "epsilon": 1e-5, + "render_primitive": moderngl.TRIANGLES, + "depth_test": True, + "shader_folder": "surface", + "shader_dtype": [ + ('point', np.float32, (3,)), + ('du_point', np.float32, (3,)), + ('dv_point', np.float32, (3,)), + ('color', np.float32, (4,)), + ] + } + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.compute_triangle_indices() + + def uv_func(self, u, v): + # To be implemented in subclasses + return (u, v, 0.0) + + def init_points(self): + dim = self.dim + nu, nv = self.resolution + u_range = np.linspace(*self.u_range, nu) + v_range = np.linspace(*self.v_range, nv) + + # Get three lists: + # - Points generated by pure uv values + # - Those generated by values nudged by du + # - Those generated by values nudged by dv + point_lists = [] + for (du, dv) in [(0, 0), (self.epsilon, 0), (0, self.epsilon)]: + uv_grid = np.array([[[u + du, v + dv] for v in v_range] for u in u_range]) + point_grid = np.apply_along_axis(lambda p: self.uv_func(*p), 2, uv_grid) + point_lists.append(point_grid.reshape((nu * nv, dim))) + # Rather than tracking normal vectors, the points list will hold on to the + # infinitesimal nudged values alongside the original values. This way, one + # can perform all the manipulations they'd like to the surface, and normals + # are still easily recoverable. + self.set_points(np.vstack(point_lists)) + + def compute_triangle_indices(self): + # TODO, if there is an event which changes + # the resolution of the surface, make sure + # this is called. + nu, nv = self.resolution + if nu == 0 or nv == 0: + self.triangle_indices = np.zeros(0, dtype=int) + return + index_grid = np.arange(nu * nv).reshape((nu, nv)) + indices = np.zeros(6 * (nu - 1) * (nv - 1), dtype=int) + indices[0::6] = index_grid[:-1, :-1].flatten() # Top left + indices[1::6] = index_grid[+1:, :-1].flatten() # Bottom left + indices[2::6] = index_grid[:-1, +1:].flatten() # Top right + indices[3::6] = index_grid[:-1, +1:].flatten() # Top right + indices[4::6] = index_grid[+1:, :-1].flatten() # Bottom left + indices[5::6] = index_grid[+1:, +1:].flatten() # Bottom right + self.triangle_indices = indices + + def get_triangle_indices(self): + return self.triangle_indices + + def get_surface_points_and_nudged_points(self): + points = self.get_points() + k = len(points) // 3 + return points[:k], points[k:2 * k], points[2 * k:] + + def get_unit_normals(self): + s_points, du_points, dv_points = self.get_surface_points_and_nudged_points() + normals = np.cross( + (du_points - s_points) / self.epsilon, + (dv_points - s_points) / self.epsilon, + ) + return normalize_along_axis(normals, 1) + + def pointwise_become_partial(self, smobject, a, b, axis=None): + assert(isinstance(smobject, Surface)) + if axis is None: + axis = self.prefered_creation_axis + if a <= 0 and b >= 1: + self.match_points(smobject) + return self + + nu, nv = smobject.resolution + self.set_points(np.vstack([ + self.get_partial_points_array(arr.copy(), a, b, (nu, nv, 3), axis=axis) + for arr in smobject.get_surface_points_and_nudged_points() + ])) + return self + + def get_partial_points_array(self, points, a, b, resolution, axis): + if len(points) == 0: + return points + nu, nv = resolution[:2] + points = points.reshape(resolution) + max_index = resolution[axis] - 1 + lower_index, lower_residue = integer_interpolate(0, max_index, a) + upper_index, upper_residue = integer_interpolate(0, max_index, b) + if axis == 0: + points[:lower_index] = interpolate( + points[lower_index], + points[lower_index + 1], + lower_residue + ) + points[upper_index + 1:] = interpolate( + points[upper_index], + points[upper_index + 1], + upper_residue + ) + else: + shape = (nu, 1, resolution[2]) + points[:, :lower_index] = interpolate( + points[:, lower_index], + points[:, lower_index + 1], + lower_residue + ).reshape(shape) + points[:, upper_index + 1:] = interpolate( + points[:, upper_index], + points[:, upper_index + 1], + upper_residue + ).reshape(shape) + return points.reshape((nu * nv, *resolution[2:])) + + def sort_faces_back_to_front(self, vect=OUT): + tri_is = self.triangle_indices + indices = list(range(len(tri_is) // 3)) + points = self.get_points() + + def index_dot(index): + return np.dot(points[tri_is[3 * index]], vect) + + indices.sort(key=index_dot) + for k in range(3): + tri_is[k::3] = tri_is[k::3][indices] + return self + + # For shaders + def get_shader_data(self): + s_points, du_points, dv_points = self.get_surface_points_and_nudged_points() + shader_data = self.get_resized_shader_data_array(len(s_points)) + if "points" not in self.locked_data_keys: + shader_data["point"] = s_points + shader_data["du_point"] = du_points + shader_data["dv_point"] = dv_points + self.fill_in_shader_color_info(shader_data) + return shader_data + + def fill_in_shader_color_info(self, shader_data): + self.read_data_to_shader(shader_data, "color", "rgbas") + return shader_data + + def get_shader_vert_indices(self): + return self.get_triangle_indices() + + +class ParametricSurface(Surface): + def __init__(self, uv_func, **kwargs): + self.passed_uv_func = uv_func + super().__init__(**kwargs) + + def uv_func(self, u, v): + return self.passed_uv_func(u, v) + + +class SGroup(Surface): + CONFIG = { + "resolution": (0, 0), + } + + def __init__(self, *parametric_surfaces, **kwargs): + super().__init__(uv_func=None, **kwargs) + self.add(*parametric_surfaces) + + def init_points(self): + pass # Needed? + + +class TexturedSurface(Surface): + CONFIG = { + "shader_folder": "textured_surface", + "shader_dtype": [ + ('point', np.float32, (3,)), + ('du_point', np.float32, (3,)), + ('dv_point', np.float32, (3,)), + ('im_coords', np.float32, (2,)), + ('opacity', np.float32, (1,)), + ] + } + + def __init__(self, uv_surface, image_file, dark_image_file=None, **kwargs): + if not isinstance(uv_surface, Surface): + raise Exception("uv_surface must be of type Surface") + # Set texture information + if dark_image_file is None: + dark_image_file = image_file + self.num_textures = 1 + else: + self.num_textures = 2 + self.texture_paths = { + "LightTexture": get_full_raster_image_path(image_file), + "DarkTexture": get_full_raster_image_path(dark_image_file), + } + + self.uv_surface = uv_surface + self.uv_func = uv_surface.uv_func + self.u_range = uv_surface.u_range + self.v_range = uv_surface.v_range + self.resolution = uv_surface.resolution + self.gloss = self.uv_surface.gloss + super().__init__(**kwargs) + + def init_data(self): + super().init_data() + self.data["im_coords"] = np.zeros((0, 2)) + self.data["opacity"] = np.zeros((0, 1)) + + def init_points(self): + nu, nv = self.uv_surface.resolution + self.set_points(self.uv_surface.get_points()) + self.data["im_coords"] = np.array([ + [u, v] + for u in np.linspace(0, 1, nu) + for v in np.linspace(1, 0, nv) # Reverse y-direction + ]) + + def init_uniforms(self): + super().init_uniforms() + self.uniforms["num_textures"] = self.num_textures + + def init_colors(self): + self.data["opacity"] = np.array([self.uv_surface.data["rgbas"][:, 3]]) + + def set_opacity(self, opacity, recurse=True): + for mob in self.get_family(recurse): + mob.data["opacity"] = np.array([[o] for o in listify(opacity)]) + return self + + def pointwise_become_partial(self, tsmobject, a, b, axis=1): + super().pointwise_become_partial(tsmobject, a, b, axis) + im_coords = self.data["im_coords"] + im_coords[:] = tsmobject.data["im_coords"] + if a <= 0 and b >= 1: + return self + nu, nv = tsmobject.resolution + im_coords[:] = self.get_partial_points_array( + im_coords, a, b, (nu, nv, 2), axis + ) + return self + + def fill_in_shader_color_info(self, shader_data): + self.read_data_to_shader(shader_data, "opacity", "opacity") + self.read_data_to_shader(shader_data, "im_coords", "im_coords") + return shader_data diff --git a/manimlib/mobject/types/surface_mobject.py b/manimlib/mobject/types/surface_mobject.py deleted file mode 100644 index b1774fef..00000000 --- a/manimlib/mobject/types/surface_mobject.py +++ /dev/null @@ -1,76 +0,0 @@ -import numpy as np -import moderngl - -# from PIL import Image - -from manimlib.constants import * -from manimlib.mobject.mobject import Mobject -from manimlib.utils.color import color_to_rgba - - -class SurfaceMobject(Mobject): - CONFIG = { - "color": GREY, - "opacity": 1, - "gloss": 1.0, - "render_primative": moderngl.TRIANGLES, - # "render_primative": moderngl.TRIANGLE_STRIP, - "vert_shader_file": "surface_vert.glsl", - "frag_shader_file": "surface_frag.glsl", - "shader_dtype": [ - ('point', np.float32, (3,)), - ('normal', np.float32, (3,)), - ('color', np.float32, (4,)), - ('gloss', np.float32, (1,)), - # ('im_coords', np.float32, (2,)), - ] - } - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def init_points(self): - self.points = np.zeros((0, self.dim)) - self.normals = np.zeros((0, self.dim)) - - def init_colors(self): - self.set_color(self.color, self.opacity) - - def set_points(self, points, normals=None): - self.points = np.array(points) - if normals is None: - v01 = points[1:-1] - points[:-2] - v02 = points[2:] - points[:-2] - crosses = np.cross(v01, v02) - crosses[1::2] *= -1 # Because of reversed orientation of every other triangle in the strip - self.normals = np.vstack([ - crosses, - crosses[-1:].repeat(2, 0) # Repeat last entry twice - ]) - else: - self.normals = np.array(normals) - - def set_color(self, color, opacity): - # TODO, allow for multiple colors - rgba = color_to_rgba(color, opacity) - self.rgbas = np.array([rgba]) - - def apply_function(self, function, **kwargs): - # Apply it to infinitesimal neighbors to preserve normals - pass - - def rotate(self, axis, angle, **kwargs): - # Account for normals - pass - - def stretch(self, factor, dim, **kwargs): - # Account for normals - pass - - def get_shader_data(self): - data = self.get_blank_shader_data_array(len(self.points)) - data["point"] = self.points - data["normal"] = self.normals - data["color"] = self.rgbas - data["gloss"] = self.gloss - return data diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index d4631deb..1394e78b 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -2,23 +2,22 @@ import itertools as it import operator as op import moderngl -from colour import Color -from functools import reduce +from functools import reduce, wraps from manimlib.constants import * from manimlib.mobject.mobject import Mobject from manimlib.mobject.mobject import Point from manimlib.utils.bezier import bezier -from manimlib.utils.bezier import get_smooth_handle_points +from manimlib.utils.bezier import get_smooth_quadratic_bezier_handle_points +from manimlib.utils.bezier import get_smooth_cubic_bezier_handle_points from manimlib.utils.bezier import get_quadratic_approximation_of_cubic from manimlib.utils.bezier import interpolate from manimlib.utils.bezier import integer_interpolate -from manimlib.utils.bezier import partial_bezier_points -from manimlib.utils.color import color_to_rgba +from manimlib.utils.bezier import partial_quadratic_bezier_points from manimlib.utils.color import rgb_to_hex from manimlib.utils.iterables import make_even -from manimlib.utils.iterables import stretch_array_to_length -from manimlib.utils.iterables import stretch_array_to_length_with_interpolation +from manimlib.utils.iterables import resize_array +from manimlib.utils.iterables import resize_with_interpolation from manimlib.utils.iterables import listify from manimlib.utils.space_ops import angle_between_vectors from manimlib.utils.space_ops import cross2d @@ -26,7 +25,7 @@ from manimlib.utils.space_ops import earclip_triangulation from manimlib.utils.space_ops import get_norm from manimlib.utils.space_ops import get_unit_normal from manimlib.utils.space_ops import z_to_vector -from manimlib.utils.shaders import get_shader_info +from manimlib.shader_wrapper import ShaderWrapper class VMobject(Mobject): @@ -37,15 +36,11 @@ class VMobject(Mobject): "stroke_opacity": 1.0, "stroke_width": DEFAULT_STROKE_WIDTH, "draw_stroke_behind_fill": False, - # TODO, currently sheen does nothing - "sheen_factor": 0.0, - "sheen_direction": UL, # Indicates that it will not be displayed, but # that it should count in parent mobject's path "pre_function_handle_to_anchor_scale_factor": 0.01, "make_smooth_after_applying_functions": False, "background_image_file": None, - "shade_in_3d": False, # This is within a pixel # TODO, do we care about accounting for # varying zoom levels? @@ -53,24 +48,18 @@ class VMobject(Mobject): "n_points_per_curve": 3, "long_lines": False, # For shaders - "stroke_vert_shader_file": "quadratic_bezier_stroke_vert.glsl", - "stroke_geom_shader_file": "quadratic_bezier_stroke_geom.glsl", - "stroke_frag_shader_file": "quadratic_bezier_stroke_frag.glsl", - "fill_vert_shader_file": "quadratic_bezier_fill_vert.glsl", - "fill_geom_shader_file": "quadratic_bezier_fill_geom.glsl", - "fill_frag_shader_file": "quadratic_bezier_fill_frag.glsl", - # Could also be Bevel, Miter, Round + "stroke_shader_folder": "quadratic_bezier_stroke", + "fill_shader_folder": "quadratic_bezier_fill", + # Could also be "bevel", "miter", "round" "joint_type": "auto", - # Positive gloss up to 1 makes it reflect the light. - "gloss": 0.2, - "render_primative": moderngl.TRIANGLES, + "flat_stroke": True, + "render_primitive": moderngl.TRIANGLES, "triangulation_locked": False, "fill_dtype": [ ('point', np.float32, (3,)), ('unit_normal', np.float32, (3,)), ('color', np.float32, (4,)), - ('fill_all', np.float32, (1,)), - ('gloss', np.float32, (1,)), + ('vert_index', np.float32, (1,)), ], "stroke_dtype": [ ("point", np.float32, (3,)), @@ -79,18 +68,30 @@ class VMobject(Mobject): ('unit_normal', np.float32, (3,)), ("stroke_width", np.float32, (1,)), ("color", np.float32, (4,)), - ("joint_type", np.float32, (1,)), - ("gloss", np.float32, (1,)), ] } + def __init__(self, **kwargs): + self.needs_new_triangulation = True + self.triangulation = np.zeros(0, dtype='i4') + super().__init__(**kwargs) + self.refresh_unit_normal() + def get_group_class(self): return VGroup + def init_data(self): + super().init_data() + self.data.pop("rgbas") + self.data.update({ + "fill_rgba": np.zeros((1, 4)), + "stroke_rgba": np.zeros((1, 4)), + "stroke_width": np.zeros((1, 1)), + "unit_normal": np.zeros((1, 3)) + }) + # Colors def init_colors(self): - self.fill_rgbas = np.zeros((1, 4)) - self.stroke_rgbas = np.zeros((1, 4)) self.set_fill( color=self.fill_color or self.color, opacity=self.fill_opacity, @@ -99,109 +100,97 @@ class VMobject(Mobject): color=self.stroke_color or self.color, width=self.stroke_width, opacity=self.stroke_opacity, + background=self.draw_stroke_behind_fill, ) - self.set_sheen( - factor=self.sheen_factor, - direction=self.sheen_direction, - ) + self.set_gloss(self.gloss) + self.set_flat_stroke(self.flat_stroke) return self - def generate_rgba_array(self, color, opacity): - """ - First arg can be either a color, or a tuple/list of colors. - Likewise, opacity can either be a float, or a tuple of floats. - """ - colors = listify(color) - opacities = listify(opacity) - return np.array([ - color_to_rgba(c, o) - for c, o in zip(*make_even(colors, opacities)) - ]) + def set_rgba_array(self, rgba_array, name=None, recurse=False): + if name is None: + names = ["fill_rgba", "stroke_rgba"] + else: + names = [name] - def update_rgbas_array(self, array_name, color, opacity): - rgbas = self.generate_rgba_array(color or BLACK, opacity or 0) - # Match up current rgbas array with the newly calculated - # one. 99% of the time they'll be the same. - curr_rgbas = getattr(self, array_name) - if len(curr_rgbas) < len(rgbas): - curr_rgbas = stretch_array_to_length(curr_rgbas, len(rgbas)) - setattr(self, array_name, curr_rgbas) - elif len(rgbas) < len(curr_rgbas): - rgbas = stretch_array_to_length(rgbas, len(curr_rgbas)) - # Only update rgb if color was not None, and only - # update alpha channel if opacity was passed in - if color is not None: - curr_rgbas[:, :3] = rgbas[:, :3] - if opacity is not None: - curr_rgbas[:, 3] = rgbas[:, 3] + for name in names: + super().set_rgba_array(rgba_array, name, recurse) return self - def set_fill(self, color=None, opacity=None, family=True): - if family: - for sm in self.submobjects: - sm.set_fill(color, opacity, family) - self.update_rgbas_array("fill_rgbas", color, opacity) + def set_fill(self, color=None, opacity=None, recurse=True): + self.set_rgba_array_by_color(color, opacity, 'fill_rgba', recurse) return self - def set_stroke(self, color=None, width=None, opacity=None, - background=None, family=True): - if family: - for sm in self.submobjects: - sm.set_stroke(color, width, opacity, background, family) - self.update_rgbas_array("stroke_rgbas", color, opacity) + def set_stroke(self, color=None, width=None, opacity=None, background=None, recurse=True): + self.set_rgba_array_by_color(color, opacity, 'stroke_rgba', recurse) + if width is not None: - self.stroke_width = np.array(listify(width)) + for mob in self.get_family(recurse): + if isinstance(width, np.ndarray): + arr = width.reshape((len(width), 1)) + else: + arr = np.array([[w] for w in listify(width)]) + mob.data['stroke_width'] = arr + if background is not None: - self.draw_stroke_behind_fill = background + for mob in self.get_family(recurse): + mob.draw_stroke_behind_fill = background return self + def align_stroke_width_data_to_points(self, recurse=True): + for mob in self.get_family(recurse): + mob.data["stroke_width"] = resize_with_interpolation( + mob.data["stroke_width"], len(mob.get_points()) + ) + def set_style(self, fill_color=None, fill_opacity=None, + fill_rgba=None, stroke_color=None, - stroke_width=None, stroke_opacity=None, - sheen_factor=None, - sheen_direction=None, - background_image_file=None, - family=True): - self.set_fill( - color=fill_color, - opacity=fill_opacity, - family=family - ) - self.set_stroke( - color=stroke_color, - width=stroke_width, - opacity=stroke_opacity, - family=family, - ) - if sheen_factor: - self.set_sheen( - factor=sheen_factor, - direction=sheen_direction, - family=family, + stroke_rgba=None, + stroke_width=None, + gloss=None, + shadow=None, + recurse=True): + if fill_rgba is not None: + self.data['fill_rgba'] = resize_with_interpolation(fill_rgba, len(fill_rgba)) + else: + self.set_fill( + color=fill_color, + opacity=fill_opacity, + recurse=recurse ) - if background_image_file: - self.color_using_background_image(background_image_file) + + if stroke_rgba is not None: + self.data['stroke_rgba'] = resize_with_interpolation(stroke_rgba, len(fill_rgba)) + self.set_stroke(width=stroke_width) + else: + self.set_stroke( + color=stroke_color, + width=stroke_width, + opacity=stroke_opacity, + recurse=recurse, + ) + + if gloss is not None: + self.set_gloss(gloss, recurse=recurse) + if shadow is not None: + self.set_shadow(shadow, recurse=recurse) return self def get_style(self): return { - "fill_color": self.get_fill_colors(), - "fill_opacity": self.get_fill_opacities(), - "stroke_color": self.get_stroke_colors(), - "stroke_width": self.get_stroke_width(), - "stroke_opacity": self.get_stroke_opacity(), - "sheen_factor": self.get_sheen_factor(), - "sheen_direction": self.get_sheen_direction(), - "background_image_file": self.get_background_image_file(), + "fill_rgba": self.data['fill_rgba'], + "stroke_rgba": self.data['stroke_rgba'], + "stroke_width": self.data['stroke_width'], + "gloss": self.get_gloss(), + "shadow": self.get_shadow(), } - def match_style(self, vmobject, family=True): - self.set_style(**vmobject.get_style(), family=False) - - if family: + def match_style(self, vmobject, recurse=True): + self.set_style(**vmobject.get_style(), recurse=False) + if recurse: # Does its best to match up submobject lists, and # match styles accordingly submobs1, submobs2 = self.submobjects, vmobject.submobjects @@ -213,43 +202,52 @@ class VMobject(Mobject): sm1.match_style(sm2) return self - def set_color(self, color, family=True): - self.set_fill(color, family=family) - self.set_stroke(color, family=family) + def set_color(self, color, recurse=True): + self.set_fill(color, recurse=recurse) + self.set_stroke(color, recurse=recurse) return self - def set_opacity(self, opacity, family=True): - self.set_fill(opacity=opacity, family=family) - self.set_stroke(opacity=opacity, family=family) + def set_opacity(self, opacity, recurse=True): + self.set_fill(opacity=opacity, recurse=recurse) + self.set_stroke(opacity=opacity, recurse=recurse) return self - def fade(self, darkness=0.5, family=True): + def fade(self, darkness=0.5, recurse=True): factor = 1.0 - darkness self.set_fill( opacity=factor * self.get_fill_opacity(), - family=False, + recurse=False, ) self.set_stroke( opacity=factor * self.get_stroke_opacity(), - family=False, + recurse=False, ) - super().fade(darkness, family) + super().fade(darkness, recurse) return self - def set_gloss(self, gloss, family=True): - if family: - for sm in self.get_family(): - sm.gloss = gloss - else: - self.gloss = gloss - return self + def get_fill_colors(self): + return [ + rgb_to_hex(rgba[:3]) + for rgba in self.data['fill_rgba'] + ] - def get_fill_rgbas(self): - try: - return self.fill_rgbas - except AttributeError: - return np.zeros((1, 4)) + def get_fill_opacities(self): + return self.data['fill_rgba'][:, 3] + def get_stroke_colors(self): + return [ + rgb_to_hex(rgba[:3]) + for rgba in self.data['stroke_rgba'] + ] + + def get_stroke_opacities(self): + return self.data['stroke_rgba'][:, 3] + + def get_stroke_widths(self): + return self.data['stroke_width'][:, 0] + + # TODO, it's weird for these to return the first of various lists + # rather than the full information def get_fill_color(self): """ If there are multiple colors (for gradient) @@ -264,156 +262,52 @@ class VMobject(Mobject): """ return self.get_fill_opacities()[0] - def get_fill_colors(self): - return [ - Color(rgb=rgba[:3]) - for rgba in self.get_fill_rgbas() - ] - - def get_fill_opacities(self): - return self.get_fill_rgbas()[:, 3] - - def get_stroke_rgbas(self): - try: - return self.stroke_rgbas - except AttributeError: - return np.zeros((1, 4)) - - # TODO, it's weird for these to return the first of various lists def get_stroke_color(self): return self.get_stroke_colors()[0] def get_stroke_width(self): - return self.stroke_width[0] + return self.get_stroke_widths()[0] def get_stroke_opacity(self): return self.get_stroke_opacities()[0] - def get_stroke_colors(self): - return [ - rgb_to_hex(rgba[:3]) - for rgba in self.get_stroke_rgbas() - ] - - def get_stroke_opacities(self): - return self.get_stroke_rgbas()[:, 3] - def get_color(self): - if np.all(self.get_fill_opacities() == 0): + if self.has_stroke(): return self.get_stroke_color() return self.get_fill_color() - # TODO, sheen currently has no effect - def set_sheen_direction(self, direction, family=True): - direction = np.array(direction) - if family: - for submob in self.get_family(): - submob.sheen_direction = direction - else: - self.sheen_direction = direction + def has_stroke(self): + return self.get_stroke_widths().any() and self.get_stroke_opacities().any() + + def has_fill(self): + return any(self.get_fill_opacities()) + + def get_opacity(self): + if self.has_fill(): + return self.get_fill_opacity() + return self.get_stroke_opacity() + + def set_flat_stroke(self, flat_stroke=True, recurse=True): + for mob in self.get_family(recurse): + mob.flat_stroke = flat_stroke return self - def set_sheen(self, factor, direction=None, family=True): - if family: - for submob in self.submobjects: - submob.set_sheen(factor, direction, family) - self.sheen_factor = factor - if direction is not None: - # family set to false because recursion will - # already be handled above - self.set_sheen_direction(direction, family=False) - # Reset color to put sheen_factor into effect - if factor != 0: - self.set_stroke(self.get_stroke_color(), family=family) - self.set_fill(self.get_fill_color(), family=family) - return self - - def get_sheen_direction(self): - return np.array(self.sheen_direction) - - def get_sheen_factor(self): - return self.sheen_factor - - def get_gradient_start_and_end_points(self): - if self.shade_in_3d: - return get_3d_vmob_gradient_start_and_end_points(self) - else: - direction = self.get_sheen_direction() - c = self.get_center() - bases = np.array([ - self.get_edge_center(vect) - c - for vect in [RIGHT, UP, OUT] - ]).transpose() - offset = np.dot(bases, direction) - return (c - offset, c + offset) - - def color_using_background_image(self, background_image_file): - self.background_image_file = background_image_file - self.set_color(WHITE) - for submob in self.submobjects: - submob.color_using_background_image(background_image_file) - return self - - def get_background_image_file(self): - return self.background_image_file - - def match_background_image_file(self, vmobject): - self.color_using_background_image(vmobject.get_background_image_file()) - return self - - def set_shade_in_3d(self, value=True, z_index_as_group=False): - for submob in self.get_family(): - submob.shade_in_3d = value - if z_index_as_group: - submob.z_index_group = self - return self - - def stretched_style_array_matching_points(self, array): - new_len = self.get_num_points() - long_arr = stretch_array_to_length_with_interpolation( - array, 1 + 2 * (new_len // 3) - ) - shape = array.shape - if len(shape) > 1: - result = np.zeros((new_len, shape[1])) - else: - result = np.zeros(new_len) - result[0::3] = long_arr[0:-1:2] - result[1::3] = long_arr[1::2] - result[2::3] = long_arr[2::2] - return result + def get_flat_stroke(self): + return self.flat_stroke # Points - def set_points(self, points): - super().set_points(points) - self.refresh_triangulation() - return self - - def get_points(self): - # TODO, shouldn't points always be a numpy array anyway? - return np.array(self.points) - def set_anchors_and_handles(self, anchors1, handles, anchors2): assert(len(anchors1) == len(handles) == len(anchors2)) nppc = self.n_points_per_curve - self.points = np.zeros((nppc * len(anchors1), self.dim)) + new_points = np.zeros((nppc * len(anchors1), self.dim)) arrays = [anchors1, handles, anchors2] for index, array in enumerate(arrays): - self.points[index::nppc] = array - return self - - def clear_points(self): - self.points = np.zeros((0, self.dim)) - - def append_points(self, new_points): - # TODO, check that number new points is a multiple of 4? - # or else that if len(self.points) % 4 == 1, then - # len(new_points) % 4 == 3? - self.points = np.vstack([self.points, new_points]) + new_points[index::nppc] = array + self.set_points(new_points) return self def start_new_path(self, point): - assert(len(self.points) % self.n_points_per_curve == 0) + assert(self.get_num_points() % self.n_points_per_curve == 0) self.append_points([point]) return self @@ -427,7 +321,7 @@ class VMobject(Mobject): """ self.throw_error_if_no_points() quadratic_approx = get_quadratic_approximation_of_cubic( - self.points[-1], handle1, handle2, anchor + self.get_last_point(), handle1, handle2, anchor ) if self.has_new_path_started(): self.append_points(quadratic_approx[1:]) @@ -439,10 +333,10 @@ class VMobject(Mobject): if self.has_new_path_started(): self.append_points([handle, anchor]) else: - self.append_points([self.points[-1], handle, anchor]) + self.append_points([self.get_last_point(), handle, anchor]) def add_line_to(self, point): - end = self.points[-1] + end = self.get_points()[-1] alphas = np.linspace(0, 1, self.n_points_per_curve) if self.long_lines: halfway = interpolate(end, point, 0.5) @@ -465,7 +359,7 @@ class VMobject(Mobject): def add_smooth_curve_to(self, point): if self.has_new_path_started(): - self.add_line_to(anchor) + self.add_line_to(point) else: self.throw_error_if_no_points() new_handle = self.get_reflection_of_last_handle() @@ -478,13 +372,14 @@ class VMobject(Mobject): self.add_cubic_bezier_curve_to(new_handle, handle, point) def has_new_path_started(self): - return len(self.points) % self.n_points_per_curve == 1 + return self.get_num_points() % self.n_points_per_curve == 1 def get_last_point(self): - return self.points[-1] + return self.get_points()[-1] def get_reflection_of_last_handle(self): - return 2 * self.points[-1] - self.points[-2] + points = self.get_points() + return 2 * points[-1] - points[-2] def close_path(self): if not self.is_closed(): @@ -492,15 +387,11 @@ class VMobject(Mobject): def is_closed(self): return self.consider_points_equals( - self.points[0], self.points[-1] + self.get_points()[0], self.get_points()[-1] ) - def subdivide_sharp_curves(self, angle_threshold=30 * DEGREES, family=True): - if family: - vmobs = self.family_members_with_points() - else: - vmobs = [self] if self.has_points() else [] - + def subdivide_sharp_curves(self, angle_threshold=30 * DEGREES, recurse=True): + vmobs = [vm for vm in self.get_family(recurse) if vm.has_points()] for vmob in vmobs: new_points = [] for tup in vmob.get_bezier_tuples(): @@ -509,12 +400,12 @@ class VMobject(Mobject): n = int(np.ceil(angle / angle_threshold)) alphas = np.linspace(0, 1, n + 1) new_points.extend([ - partial_bezier_points(tup, a1, a2) + partial_quadratic_bezier_points(tup, a1, a2) for a1, a2 in zip(alphas, alphas[1:]) ]) else: new_points.append(tup) - vmob.points = np.vstack(new_points) + vmob.set_points(np.vstack(new_points)) return self def add_points_as_corners(self, points): @@ -531,90 +422,98 @@ class VMobject(Mobject): ]) return self - def set_points_smoothly(self, points): + def set_points_smoothly(self, points, true_smooth=False): self.set_points_as_corners(points) self.make_smooth() return self def change_anchor_mode(self, mode): - assert(mode in ["jagged", "smooth"]) + assert(mode in ("jagged", "approx_smooth", "true_smooth")) nppc = self.n_points_per_curve for submob in self.family_members_with_points(): subpaths = submob.get_subpaths() submob.clear_points() for subpath in subpaths: anchors = np.vstack([subpath[::nppc], subpath[-1:]]) - if mode == "smooth": - h1, h2 = get_smooth_handle_points(anchors) - new_subpath = get_quadratic_approximation_of_cubic( - anchors[:-1], h1, h2, anchors[1:] - ) + new_subpath = np.array(subpath) + if mode == "approx_smooth": + new_subpath[1::nppc] = get_smooth_quadratic_bezier_handle_points(anchors) + elif mode == "true_smooth": + h1, h2 = get_smooth_cubic_bezier_handle_points(anchors) + new_subpath = get_quadratic_approximation_of_cubic(anchors[:-1], h1, h2, anchors[1:]) elif mode == "jagged": - new_subpath = np.array(subpath) - new_subpath[1::nppc] = interpolate( - anchors[:-1], anchors[1:], 0.5 - ) + new_subpath[1::nppc] = 0.5 * (anchors[:-1] + anchors[1:]) submob.append_points(new_subpath) submob.refresh_triangulation() return self def make_smooth(self): - # TODO, Change this to not rely on a cubic-to-quadratic conversion - return self.change_anchor_mode("smooth") + """ + This will double the number of points in the mobject, + so should not be called repeatedly. It also means + transforming between states before and after calling + this might have strange artifacts + """ + self.change_anchor_mode("true_smooth") + return self + + def make_approximately_smooth(self): + """ + Unlike make_smooth, this will not change the number of + points, but it also does not result in a perfectly smooth + curve. It's most useful when the points have been + sampled at a not-too-low rate from a continuous function, + as in the case of ParametricCurve + """ + self.change_anchor_mode("approx_smooth") + return self def make_jagged(self): - return self.change_anchor_mode("jagged") + self.change_anchor_mode("jagged") + return self def add_subpath(self, points): assert(len(points) % self.n_points_per_curve == 0) self.append_points(points) + return self def append_vectorized_mobject(self, vectorized_mobject): - new_points = list(vectorized_mobject.points) + new_points = list(vectorized_mobject.get_points()) if self.has_new_path_started(): # Remove last point, which is starting # a new path - self.points = self.points[:-1] + self.resize_data(len(self.get_points() - 1)) self.append_points(new_points) - - # TODO, how to be smart about tangents here? - def apply_function(self, function): - Mobject.apply_function(self, function) - if self.make_smooth_after_applying_functions: - self.make_smooth() return self - def flip(self, *args, **kwargs): - super().flip(*args, **kwargs) - self.refresh_triangulation() - # def consider_points_equals(self, p0, p1): - return np.allclose( - p0, p1, - atol=self.tolerance_for_point_equality - ) + return get_norm(p1 - p0) < self.tolerance_for_point_equality # Information about the curve def get_bezier_tuples_from_points(self, points): nppc = self.n_points_per_curve remainder = len(points) % nppc points = points[:len(points) - remainder] - return np.array([ + return [ points[i:i + nppc] for i in range(0, len(points), nppc) - ]) + ] def get_bezier_tuples(self): return self.get_bezier_tuples_from_points(self.get_points()) def get_subpaths_from_points(self, points): nppc = self.n_points_per_curve - split_indices = filter( - lambda n: not self.consider_points_equals(points[n - 1], points[n]), - range(nppc, len(points), nppc) - ) + diffs = points[nppc - 1:-1:nppc] - points[nppc::nppc] + splits = (diffs * diffs).sum(1) > self.tolerance_for_point_equality + split_indices = np.arange(nppc, len(points), nppc, dtype=int)[splits] + + # split_indices = filter( + # lambda n: not self.consider_points_equals(points[n - 1], points[n]), + # range(nppc, len(points), nppc) + # ) split_indices = [0, *split_indices, len(points)] return [ points[i1:i2] @@ -628,13 +527,13 @@ class VMobject(Mobject): def get_nth_curve_points(self, n): assert(n < self.get_num_curves()) nppc = self.n_points_per_curve - return self.points[nppc * n:nppc * (n + 1)] + return self.get_points()[nppc * n:nppc * (n + 1)] def get_nth_curve_function(self, n): return bezier(self.get_nth_curve_points(n)) def get_num_curves(self): - return len(self.points) // self.n_points_per_curve + return self.get_num_points() // self.n_points_per_curve def point_from_proportion(self, alpha): num_curves = self.get_num_curves() @@ -650,21 +549,23 @@ class VMobject(Mobject): for any i in range(0, len(anchors1)) """ nppc = self.n_points_per_curve + points = self.get_points() return [ - self.points[i::nppc] + points[i::nppc] for i in range(nppc) ] def get_start_anchors(self): - return self.points[0::self.n_points_per_curve] + return self.get_points()[0::self.n_points_per_curve] def get_end_anchors(self): nppc = self.n_points_per_curve - return self.points[nppc - 1::nppc] + return self.get_points()[nppc - 1::nppc] def get_anchors(self): - if len(self.points) == 1: - return self.points + points = self.get_points() + if len(points) == 1: + return points return np.array(list(it.chain(*zip( self.get_start_anchors(), self.get_end_anchors(), @@ -672,11 +573,12 @@ class VMobject(Mobject): def get_points_without_null_curves(self, atol=1e-9): nppc = self.n_points_per_curve + points = self.get_points() distinct_curves = reduce(op.or_, [ - (abs(self.points[i::nppc] - self.points[0::nppc]) > atol).any(1) + (abs(points[i::nppc] - points[0::nppc]) > atol).any(1) for i in range(1, nppc) ]) - return self.points[distinct_curves.repeat(nppc)] + return points[distinct_curves.repeat(nppc)] def get_arc_length(self, n_sample_points=None): if n_sample_points is None: @@ -694,12 +596,13 @@ class VMobject(Mobject): # the polygon formed by the anchor points, pointing # in a direction perpendicular to the polygon according # to the right hand rule. - if self.has_no_points(): + if not self.has_points(): return np.zeros(3) nppc = self.n_points_per_curve - p0 = self.points[0::nppc] - p1 = self.points[nppc - 1::nppc] + points = self.get_points() + p0 = points[0::nppc] + p1 = points[nppc - 1::nppc] # Each term goes through all edges [(x1, y1, z1), (x2, y2, z2)] return 0.5 * np.array([ @@ -708,34 +611,43 @@ class VMobject(Mobject): sum((p0[:, 0] + p1[:, 0]) * (p1[:, 1] - p0[:, 1])), # Add up (x1 + x2)*(y2 - y1) ]) - def get_unit_normal_vector(self): - if len(self.points) < 3: + def get_unit_normal(self, recompute=False): + if not recompute: + return self.data["unit_normal"][0] + + if self.get_num_points() < 3: return OUT + area_vect = self.get_area_vector() area = get_norm(area_vect) if area > 0: return area_vect / area else: + points = self.get_points() return get_unit_normal( - self.points[1] - self.points[0], - self.points[2] - self.points[1], + points[1] - points[0], + points[2] - points[1], ) + def refresh_unit_normal(self): + for mob in self.get_family(): + mob.data["unit_normal"][:] = mob.get_unit_normal(recompute=True) + return self + # Alignment def align_points(self, vmobject): - self.align_rgbas(vmobject) - if len(self.points) == len(vmobject.points): + if self.get_num_points() == len(vmobject.get_points()): return for mob in self, vmobject: # If there are no points, add one to # where the "center" is - if mob.has_no_points(): + if not mob.has_points(): mob.start_new_path(mob.get_center()) # If there's only one point, turn it into # a null curve if mob.has_new_path_started(): - mob.add_line_to(mob.points[0]) + mob.add_line_to(mob.get_points()[0]) # Figure out what the subpaths are, and align subpaths1 = self.get_subpaths() @@ -766,14 +678,14 @@ class VMobject(Mobject): vmobject.set_points(np.vstack(new_subpaths2)) return self - def insert_n_curves(self, n): - new_points = self.insert_n_curves_to_point_list(n, self.get_points()) - - # TODO, this should happen in insert_n_curves_to_point_list - if self.has_new_path_started(): - new_points = np.vstack([new_points, self.get_last_point()]) - - self.set_points(new_points) + def insert_n_curves(self, n, recurse=True): + for mob in self.get_family(recurse): + if mob.get_num_curves() > 0: + new_points = mob.insert_n_curves_to_point_list(n, mob.get_points()) + # TODO, this should happen in insert_n_curves_to_point_list + if mob.has_new_path_started(): + new_points = np.vstack([new_points, mob.get_last_point()]) + mob.set_points(new_points) return self def insert_n_curves_to_point_list(self, n, points): @@ -806,45 +718,25 @@ class VMobject(Mobject): # smaller quadratic curves alphas = np.linspace(0, 1, n_inserts + 2) for a1, a2 in zip(alphas, alphas[1:]): - new_points += partial_bezier_points(group, a1, a2) + new_points += partial_quadratic_bezier_points(group, a1, a2) return np.vstack(new_points) - def align_rgbas(self, vmobject): - attrs = ["fill_rgbas", "stroke_rgbas"] - for attr in attrs: - a1 = getattr(self, attr) - a2 = getattr(vmobject, attr) - if len(a1) > len(a2): - new_a2 = stretch_array_to_length(a2, len(a1)) - setattr(vmobject, attr, new_a2) - elif len(a2) > len(a1): - new_a1 = stretch_array_to_length(a1, len(a2)) - setattr(self, attr, new_a1) + def interpolate(self, mobject1, mobject2, alpha, *args, **kwargs): + super().interpolate(mobject1, mobject2, alpha, *args, **kwargs) + if self.has_fill(): + tri1 = mobject1.get_triangulation() + tri2 = mobject2.get_triangulation() + if len(tri1) != len(tri1) or not np.all(tri1 == tri2): + self.refresh_triangulation() return self - def interpolate_color(self, mobject1, mobject2, alpha): - attrs = [ - "fill_rgbas", - "stroke_rgbas", - "stroke_width", - # "sheen_direction", - # "sheen_factor", - ] - for attr in attrs: - m1a = getattr(mobject1, attr) - m2a = getattr(mobject2, attr) - setattr(self, attr, interpolate(m1a, m2a, alpha)) - - # TODO, somehow do this using stroke_width changes - # so as to not have to change the point list def pointwise_become_partial(self, vmobject, a, b): assert(isinstance(vmobject, VMobject)) - assert(len(self.points) >= len(vmobject.points)) if a <= 0 and b >= 1: - self.points[:] = vmobject.points + self.become(vmobject) return self - bezier_tuple = vmobject.get_bezier_tuples() - num_curves = len(bezier_tuple) + num_curves = vmobject.get_num_curves() + nppc = self.n_points_per_curve # Partial curve includes three portions: # - A middle section, which matches the curve exactly @@ -853,27 +745,31 @@ class VMobject(Mobject): lower_index, lower_residue = integer_interpolate(0, num_curves, a) upper_index, upper_residue = integer_interpolate(0, num_curves, b) + i1 = nppc * lower_index + i2 = nppc * (lower_index + 1) + i3 = nppc * upper_index + i4 = nppc * (upper_index + 1) - new_point_list = [] + vm_points = vmobject.get_points() + new_points = vm_points.copy() if num_curves == 0: - self.points[:] = 0 + new_points[:] = 0 return self if lower_index == upper_index: - new_point_list.append(partial_bezier_points( - bezier_tuple[lower_index], lower_residue, upper_residue - )) + tup = partial_quadratic_bezier_points(vm_points[i1:i2], lower_residue, upper_residue) + new_points[:i1] = tup[0] + new_points[i1:i4] = tup + new_points[i4:] = tup[2] + new_points[nppc:] = new_points[nppc - 1] else: - new_point_list.append(partial_bezier_points( - bezier_tuple[lower_index], lower_residue, 1 - )) - for tup in bezier_tuple[lower_index + 1:upper_index]: - new_point_list.append(tup) - new_point_list.append(partial_bezier_points( - bezier_tuple[upper_index], 0, upper_residue - )) - new_points = np.vstack(new_point_list) - self.points[:len(new_points)] = new_points - self.points[len(new_points):] = new_points[-1] + low_tup = partial_quadratic_bezier_points(vm_points[i1:i2], lower_residue, 1) + high_tup = partial_quadratic_bezier_points(vm_points[i3:i4], 0, upper_residue) + new_points[0:i1] = low_tup[0] + new_points[i1:i2] = low_tup + # Keep new_points i2:i3 as they are + new_points[i3:i4] = high_tup + new_points[i4:] = high_tup[2] + self.set_points(new_points) return self def get_subcurve(self, a, b): @@ -881,126 +777,33 @@ class VMobject(Mobject): vmob.pointwise_become_partial(self, a, b) return vmob - # For shaders - def init_shader_data(self): - self.fill_data = np.zeros(len(self.points), dtype=self.fill_dtype) - self.stroke_data = np.zeros(len(self.points), dtype=self.stroke_dtype) - - def get_shader_info_list(self): - if self.shader_data_is_locked: - return self.saved_shader_info_list - - stroke_info = get_shader_info( - vert_file=self.stroke_vert_shader_file, - geom_file=self.stroke_geom_shader_file, - frag_file=self.stroke_frag_shader_file, - texture_path=self.texture_path, - render_primative=self.render_primative, - ) - fill_info = get_shader_info( - vert_file=self.fill_vert_shader_file, - geom_file=self.fill_geom_shader_file, - frag_file=self.fill_frag_shader_file, - texture_path=self.texture_path, - render_primative=self.render_primative, - ) - - back_stroke_data = [] - stroke_data = [] - fill_data = [] - for submob in self.family_members_with_points(): - stroke_width = submob.get_stroke_width() - stroke_opacity = submob.get_stroke_opacity() - fill_opacity = submob.get_fill_opacity() - - if fill_opacity > 0: - fill_data.append(submob.get_fill_shader_data()) - - if stroke_width > 0 and stroke_opacity > 0: - if submob.draw_stroke_behind_fill: - data = back_stroke_data - else: - data = stroke_data - data.append(submob.get_stroke_shader_data()) - - result = [] - if back_stroke_data: - back_stroke_info = dict(stroke_info) # Copy - back_stroke_info["data"] = np.hstack(back_stroke_data) - result.append(back_stroke_info) - if fill_data: - fill_info["data"] = np.hstack(fill_data) - result.append(fill_info) - if stroke_data: - stroke_info["data"] = np.hstack(stroke_data) - result.append(stroke_info) - return result - - def get_stroke_shader_data(self): - joint_type_to_code = { - "auto": 0, - "round": 1, - "bevel": 2, - "miter": 3, - } - - rgbas = self.get_stroke_rgbas() - if len(rgbas) > 1: - rgbas = self.stretched_style_array_matching_points(rgbas) - - stroke_width = self.stroke_width - if len(stroke_width) > 1: - stroke_width = self.stretched_style_array_matching_points(stroke_width) - - points = self.get_points_without_null_curves() - nppc = self.n_points_per_curve - - data = self.get_blank_shader_data_array(len(points), "stroke_data") - data["point"] = points - data["prev_point"][:nppc] = points[-nppc:] - data["prev_point"][nppc:] = points[:-nppc] - data["next_point"][:-nppc] = points[nppc:] - data["next_point"][-nppc:] = points[:nppc] - data["unit_normal"] = self.get_unit_normal_vector() - data["stroke_width"][:, 0] = stroke_width - data["color"] = rgbas - data["joint_type"] = joint_type_to_code[self.joint_type] - data["gloss"] = self.gloss - return data - - def lock_triangulation(self, family=True): - mobs = self.get_family() if family else [self] - for mob in mobs: - mob.triangulation_locked = False - mob.saved_triangulation = mob.get_triangulation() - mob.triangulation_locked = True - return self - - def unlock_triangulation(self): - for sm in self.family_members_with_points(): - sm.triangulation_locked = False + # Related to triangulation def refresh_triangulation(self): - for sm in self.get_family(): - if sm.triangulation_locked: - sm.lock_triangulation(family=False) + for mob in self.get_family(): + mob.needs_new_triangulation = True + return self def get_triangulation(self, normal_vector=None): # Figure out how to triangulate the interior to know # how to send the points as to the vertex shader. # First triangles come directly from the points if normal_vector is None: - normal_vector = self.get_unit_normal_vector() + normal_vector = self.get_unit_normal() - if self.triangulation_locked: - return self.saved_triangulation + if not self.needs_new_triangulation: + return self.triangulation - if len(self.points) <= 1: - return [] + points = self.get_points() - # Rotate points such that unit normal vector is OUT - # TODO, 99% of the time this does nothing. Do a check for that? - points = np.dot(self.points, z_to_vector(normal_vector)) + if len(points) <= 1: + self.triangulation = np.zeros(0, dtype='i4') + self.needs_new_triangulation = False + return self.triangulation + + if not np.isclose(normal_vector, OUT).all(): + # Rotate points such that unit normal vector is OUT + points = np.dot(points, z_to_vector(normal_vector)) indices = np.arange(len(points), dtype=int) b0s = points[0::3] @@ -1030,40 +833,166 @@ class VMobject(Mobject): # Triangulate inner_verts = points[inner_vert_indices] - inner_tri_indices = inner_vert_indices[earclip_triangulation(inner_verts, rings)] + inner_tri_indices = inner_vert_indices[ + earclip_triangulation(inner_verts, rings) + ] tri_indices = np.hstack([indices, inner_tri_indices]) + self.triangulation = tri_indices + self.needs_new_triangulation = False return tri_indices + def triggers_refreshed_triangulation(func): + @wraps(func) + def wrapper(self, *args, **kwargs): + old_points = self.get_points() + func(self, *args, **kwargs) + if not np.all(self.get_points() == old_points): + self.refresh_unit_normal() + self.refresh_triangulation() + return wrapper + + @triggers_refreshed_triangulation + def set_points(self, points): + super().set_points(points) + return self + + @triggers_refreshed_triangulation + def set_data(self, data): + super().set_data(data) + return self + + # TODO, how to be smart about tangents here? + @triggers_refreshed_triangulation + def apply_function(self, function, make_smooth=False, **kwargs): + super().apply_function(function, **kwargs) + if self.make_smooth_after_applying_functions or make_smooth: + self.make_approximately_smooth() + return self + + def flip(self, *args, **kwargs): + super().flip(*args, **kwargs) + self.refresh_unit_normal() + self.refresh_triangulation() + return self + + # For shaders + def init_shader_data(self): + self.fill_data = np.zeros(0, dtype=self.fill_dtype) + self.stroke_data = np.zeros(0, dtype=self.stroke_dtype) + self.fill_shader_wrapper = ShaderWrapper( + vert_data=self.fill_data, + vert_indices=np.zeros(0, dtype='i4'), + shader_folder=self.fill_shader_folder, + render_primitive=self.render_primitive, + ) + self.stroke_shader_wrapper = ShaderWrapper( + vert_data=self.stroke_data, + shader_folder=self.stroke_shader_folder, + render_primitive=self.render_primitive, + ) + + def refresh_shader_wrapper_id(self): + for wrapper in [self.fill_shader_wrapper, self.stroke_shader_wrapper]: + wrapper.refresh_id() + return self + + def get_fill_shader_wrapper(self): + self.fill_shader_wrapper.vert_data = self.get_fill_shader_data() + self.fill_shader_wrapper.vert_indices = self.get_fill_shader_vert_indices() + self.fill_shader_wrapper.uniforms = self.get_shader_uniforms() + self.fill_shader_wrapper.depth_test = self.depth_test + return self.fill_shader_wrapper + + def get_stroke_shader_wrapper(self): + self.stroke_shader_wrapper.vert_data = self.get_stroke_shader_data() + self.stroke_shader_wrapper.uniforms = self.get_stroke_uniforms() + self.stroke_shader_wrapper.depth_test = self.depth_test + return self.stroke_shader_wrapper + + def get_shader_wrapper_list(self): + # Build up data lists + fill_shader_wrappers = [] + stroke_shader_wrappers = [] + back_stroke_shader_wrappers = [] + for submob in self.family_members_with_points(): + if submob.has_fill(): + fill_shader_wrappers.append(submob.get_fill_shader_wrapper()) + if submob.has_stroke(): + ssw = submob.get_stroke_shader_wrapper() + if submob.draw_stroke_behind_fill: + back_stroke_shader_wrappers.append(ssw) + else: + stroke_shader_wrappers.append(ssw) + + # Combine data lists + wrapper_lists = [ + back_stroke_shader_wrappers, + fill_shader_wrappers, + stroke_shader_wrappers + ] + result = [] + for wlist in wrapper_lists: + if wlist: + wrapper = wlist[0] + wrapper.combine_with(*wlist[1:]) + result.append(wrapper) + return result + + def get_stroke_uniforms(self): + result = dict(super().get_shader_uniforms()) + result["joint_type"] = JOINT_TYPE_MAP[self.joint_type] + result["flat_stroke"] = float(self.flat_stroke) + return result + + def get_stroke_shader_data(self): + points = self.get_points() + if len(self.stroke_data) != len(points): + self.stroke_data = resize_array(self.stroke_data, len(points)) + + if "points" not in self.locked_data_keys: + nppc = self.n_points_per_curve + self.stroke_data["point"] = points + self.stroke_data["prev_point"][:nppc] = points[-nppc:] + self.stroke_data["prev_point"][nppc:] = points[:-nppc] + self.stroke_data["next_point"][:-nppc] = points[nppc:] + self.stroke_data["next_point"][-nppc:] = points[:nppc] + + self.read_data_to_shader(self.stroke_data, "color", "stroke_rgba") + self.read_data_to_shader(self.stroke_data, "stroke_width", "stroke_width") + self.read_data_to_shader(self.stroke_data, "unit_normal", "unit_normal") + + return self.stroke_data + def get_fill_shader_data(self): - points = self.points - unit_normal = self.get_unit_normal_vector() - tri_indices = self.get_triangulation(unit_normal) + points = self.get_points() + if len(self.fill_data) != len(points): + self.fill_data = resize_array(self.fill_data, len(points)) + self.fill_data["vert_index"][:, 0] = range(len(points)) - # TODO, best way to enable multiple colors? - rgbas = self.get_fill_rgbas()[:1] + self.read_data_to_shader(self.fill_data, "point", "points") + self.read_data_to_shader(self.fill_data, "color", "fill_rgba") + self.read_data_to_shader(self.fill_data, "unit_normal", "unit_normal") - data = self.get_blank_shader_data_array(len(tri_indices), "fill_data") - data["point"] = points[tri_indices] - data["unit_normal"] = unit_normal - data["color"] = rgbas - # Assume the triangulation is such that the first n_points points - # are on the boundary, and the rest are in the interior - data["fill_all"][:len(points)] = 0 - data["fill_all"][len(points):] = 1 - data["gloss"] = self.gloss - return data + return self.fill_data + + def refresh_shader_data(self): + self.get_fill_shader_data() + self.get_stroke_shader_data() + + def get_fill_shader_vert_indices(self): + return self.get_triangulation() class VGroup(VMobject): def __init__(self, *vmobjects, **kwargs): if not all([isinstance(m, VMobject) for m in vmobjects]): raise Exception("All submobjects must be of type VMobject") - VMobject.__init__(self, **kwargs) + super().__init__(**kwargs) self.add(*vmobjects) -class VectorizedPoint(VMobject, Point): +class VectorizedPoint(Point, VMobject): CONFIG = { "color": BLACK, "fill_opacity": 0, @@ -1073,15 +1002,14 @@ class VectorizedPoint(VMobject, Point): } def __init__(self, location=ORIGIN, **kwargs): - VMobject.__init__(self, **kwargs) + super().__init__(**kwargs) self.set_points(np.array([location])) class CurvesAsSubmobjects(VGroup): def __init__(self, vmobject, **kwargs): - VGroup.__init__(self, **kwargs) - tuples = vmobject.get_bezier_tuples() - for tup in tuples: + super().__init__(**kwargs) + for tup in vmobject.get_bezier_tuples(): part = VMobject() part.set_points(tup) part.match_style(vmobject) @@ -1096,7 +1024,7 @@ class DashedVMobject(VMobject): } def __init__(self, vmobject, **kwargs): - VMobject.__init__(self, **kwargs) + super().__init__(**kwargs) num_dashes = self.num_dashes ps_ratio = self.positive_space_ratio if num_dashes > 0: @@ -1117,4 +1045,4 @@ class DashedVMobject(VMobject): ]) # Family is already taken care of by get_subcurve # implementation - self.match_style(vmobject, family=False) + self.match_style(vmobject, recurse=False) diff --git a/manimlib/mobject/value_tracker.py b/manimlib/mobject/value_tracker.py index 79cdedce..def00c14 100644 --- a/manimlib/mobject/value_tracker.py +++ b/manimlib/mobject/value_tracker.py @@ -5,22 +5,28 @@ from manimlib.mobject.mobject import Mobject class ValueTracker(Mobject): """ - Note meant to be displayed. Instead the position encodes some + Not meant to be displayed. Instead the position encodes some number, often one which another animation or continual_animation uses for its update function, and by treating it as a mobject it can still be animated and manipulated just like anything else. """ + CONFIG = { + "value_type": np.float64, + } def __init__(self, value=0, **kwargs): - Mobject.__init__(self, **kwargs) - self.points = np.zeros((1, 3)) + super().__init__(**kwargs) self.set_value(value) + def init_data(self): + super().init_data() + self.data["value"] = np.zeros((1, 1), dtype=self.value_type) + def get_value(self): - return self.points[0, 0] + return self.data["value"][0, 0] def set_value(self, value): - self.points[0, 0] = value + self.data["value"][0, 0] = value return self def increment_value(self, d_value): @@ -42,10 +48,6 @@ class ExponentialValueTracker(ValueTracker): class ComplexValueTracker(ValueTracker): - def get_value(self): - return complex(*self.points[0, :2]) - - def set_value(self, z): - z = complex(z) - self.points[0, :2] = (z.real, z.imag) - return self + CONFIG = { + "value_type": np.complex128 + } diff --git a/manimlib/mobject/vector_field.py b/manimlib/mobject/vector_field.py index bfc90c2c..0776a3cc 100644 --- a/manimlib/mobject/vector_field.py +++ b/manimlib/mobject/vector_field.py @@ -1,66 +1,32 @@ import numpy as np -import os import itertools as it -from PIL import Image import random from manimlib.constants import * from manimlib.animation.composition import AnimationGroup -from manimlib.animation.indication import ShowPassingFlash -from manimlib.mobject.geometry import Vector +from manimlib.animation.indication import VShowPassingFlash +from manimlib.mobject.geometry import Arrow from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.utils.bezier import inverse_interpolate from manimlib.utils.bezier import interpolate -from manimlib.utils.color import color_to_rgb -from manimlib.utils.color import rgb_to_color +from manimlib.utils.color import get_colormap_list +from manimlib.utils.config_ops import merge_dicts_recursively from manimlib.utils.config_ops import digest_config from manimlib.utils.rate_functions import linear from manimlib.utils.simple_functions import sigmoid from manimlib.utils.space_ops import get_norm -# from manimlib.utils.space_ops import normalize -DEFAULT_SCALAR_FIELD_COLORS = [BLUE_E, GREEN, YELLOW, RED] - - -def get_colored_background_image(scalar_field_func, - number_to_rgb_func, - pixel_height=DEFAULT_PIXEL_HEIGHT, - pixel_width=DEFAULT_PIXEL_WIDTH): - ph = pixel_height - pw = pixel_width - fw = FRAME_WIDTH - fh = FRAME_HEIGHT - points_array = np.zeros((ph, pw, 3)) - x_array = np.linspace(-fw / 2, fw / 2, pw) - x_array = x_array.reshape((1, len(x_array))) - x_array = x_array.repeat(ph, axis=0) - - y_array = np.linspace(fh / 2, -fh / 2, ph) - y_array = y_array.reshape((len(y_array), 1)) - y_array.repeat(pw, axis=1) - points_array[:, :, 0] = x_array - points_array[:, :, 1] = y_array - scalars = np.apply_along_axis(scalar_field_func, 2, points_array) - rgb_array = number_to_rgb_func(scalars.flatten()).reshape((ph, pw, 3)) - return Image.fromarray((rgb_array * 255).astype('uint8')) - - -def get_rgb_gradient_function(min_value=0, max_value=1, - colors=[BLUE, RED], - flip_alphas=True, # Why? - ): - rgbs = np.array(list(map(color_to_rgb, colors))) +def get_vectorized_rgb_gradient_function(min_value, max_value, color_map): + rgbs = np.array(get_colormap_list(color_map)) def func(values): alphas = inverse_interpolate( min_value, max_value, np.array(values) ) alphas = np.clip(alphas, 0, 1) - # if flip_alphas: - # alphas = 1 - alphas scaled_alphas = alphas * (len(rgbs) - 1) indices = scaled_alphas.astype(int) next_indices = np.clip(indices + 1, 0, len(rgbs) - 1) @@ -71,29 +37,9 @@ def get_rgb_gradient_function(min_value=0, max_value=1, return func -def get_color_field_image_file(scalar_func, - min_value=0, max_value=2, - colors=DEFAULT_SCALAR_FIELD_COLORS - ): - # try_hash - np.random.seed(0) - sample_inputs = 5 * np.random.random(size=(10, 3)) - 10 - sample_outputs = np.apply_along_axis(scalar_func, 1, sample_inputs) - func_hash = hash( - str(min_value) + str(max_value) + str(colors) + str(sample_outputs) - ) - file_name = "%d.png" % func_hash - full_path = os.path.join(RASTER_IMAGE_DIR, file_name) - if not os.path.exists(full_path): - print("Rendering color field image " + str(func_hash)) - rgb_gradient_func = get_rgb_gradient_function( - min_value=min_value, - max_value=max_value, - colors=colors - ) - image = get_colored_background_image(scalar_func, rgb_gradient_func) - image.save(full_path) - return full_path +def get_rgb_gradient_function(min_value, max_value, color_map): + vectorized_func = get_vectorized_rgb_gradient_function(min_value, max_value, color_map) + return lambda value: vectorized_func([value])[0] def move_along_vector_field(mobject, func): @@ -116,175 +62,215 @@ def move_submobjects_along_vector_field(mobject, func): return mobject -def move_points_along_vector_field(mobject, func): +def move_points_along_vector_field(mobject, func, coordinate_system): + cs = coordinate_system + origin = cs.get_origin() + def apply_nudge(self, dt): - self.mobject.apply_function( - lambda p: p + func(p) * dt + mobject.apply_function( + lambda p: p + (cs.c2p(*func(*cs.p2c(p))) - origin) * dt ) mobject.add_updater(apply_nudge) return mobject +def get_sample_points_from_coordinate_system(coordinate_system, step_multiple): + ranges = [] + for range_args in coordinate_system.get_all_ranges(): + _min, _max, step = range_args + step *= step_multiple + ranges.append(np.arange(_min, _max + step, step)) + return it.product(*ranges) + + # Mobjects class VectorField(VGroup): CONFIG = { - "delta_x": 0.5, - "delta_y": 0.5, - "x_min": int(np.floor(-FRAME_WIDTH / 2)), - "x_max": int(np.ceil(FRAME_WIDTH / 2)), - "y_min": int(np.floor(-FRAME_HEIGHT / 2)), - "y_max": int(np.ceil(FRAME_HEIGHT / 2)), - "min_magnitude": 0, - "max_magnitude": 2, - "colors": DEFAULT_SCALAR_FIELD_COLORS, + "step_multiple": 0.5, + "magnitude_range": (0, 2), + "color_map": "3b1b_colormap", # Takes in actual norm, spits out displayed norm "length_func": lambda norm: 0.45 * sigmoid(norm), "opacity": 1.0, "vector_config": {}, } - def __init__(self, func, **kwargs): - VGroup.__init__(self, **kwargs) + def __init__(self, func, coordinate_system, **kwargs): + super().__init__(**kwargs) self.func = func - self.rgb_gradient_function = get_rgb_gradient_function( - self.min_magnitude, - self.max_magnitude, - self.colors, - flip_alphas=False + self.coordinate_system = coordinate_system + self.value_to_rgb = get_rgb_gradient_function( + *self.magnitude_range, self.color_map, ) - x_range = np.arange( - self.x_min, - self.x_max + self.delta_x, - self.delta_x - ) - y_range = np.arange( - self.y_min, - self.y_max + self.delta_y, - self.delta_y - ) - for x, y in it.product(x_range, y_range): - point = x * RIGHT + y * UP - self.add(self.get_vector(point)) - self.set_opacity(self.opacity) - def get_vector(self, point, **kwargs): - output = np.array(self.func(point)) - norm = get_norm(output) - if norm == 0: - output *= 0 - else: - output *= self.length_func(norm) / norm - vector_config = dict(self.vector_config) - vector_config.update(kwargs) - vect = Vector(output, **vector_config) - vect.shift(point) - fill_color = rgb_to_color( - self.rgb_gradient_function(np.array([norm]))[0] + samples = get_sample_points_from_coordinate_system( + coordinate_system, self.step_multiple ) - vect.set_color(fill_color) + self.add(*( + self.get_vector(coords) + for coords in samples + )) + + def get_vector(self, coords, **kwargs): + vector_config = merge_dicts_recursively( + self.vector_config, + kwargs + ) + + output = np.array(self.func(*coords)) + norm = get_norm(output) + if norm > 0: + output *= self.length_func(norm) / norm + + origin = self.coordinate_system.get_origin() + _input = self.coordinate_system.c2p(*coords) + _output = self.coordinate_system.c2p(*output) + + vect = Arrow( + origin, _output, buff=0, + **vector_config + ) + vect.shift(_input - origin) + vect.set_rgba_array([[*self.value_to_rgb(norm), self.opacity]]) return vect class StreamLines(VGroup): CONFIG = { - # TODO, this is an awkward way to inherit - # defaults to a method. - "start_points_generator_config": {}, - # Config for choosing start points - "x_min": -8, - "x_max": 8, - "y_min": -5, - "y_max": 5, - "delta_x": 0.5, - "delta_y": 0.5, + "step_multiple": 0.5, "n_repeats": 1, "noise_factor": None, # Config for drawing lines "dt": 0.05, - "virtual_time": 3, - "n_anchors_per_line": 100, + "arc_len": 3, + "max_time_steps": 200, + "n_samples_per_line": 10, + "cutoff_norm": 15, + # Style info "stroke_width": 1, "stroke_color": WHITE, - "color_by_arc_length": True, - # Min and max arc lengths meant to define - # the color range, should color_by_arc_length be True - "min_arc_length": 0, - "max_arc_length": 12, - "color_by_magnitude": False, - # Min and max magnitudes meant to define - # the color range, should color_by_magnitude be True - "min_magnitude": 0.5, - "max_magnitude": 1.5, - "colors": DEFAULT_SCALAR_FIELD_COLORS, - "cutoff_norm": 15, + "stroke_opacity": 1, + "color_by_magnitude": True, + "magnitude_range": (0, 2.0), + "taper_stroke_width": False, + "color_map": "3b1b_colormap", } - def __init__(self, func, **kwargs): - VGroup.__init__(self, **kwargs) + def __init__(self, func, coordinate_system, **kwargs): + super().__init__(**kwargs) self.func = func - dt = self.dt + self.coordinate_system = coordinate_system + self.draw_lines() + self.init_style() - start_points = self.get_start_points( - **self.start_points_generator_config - ) - for point in start_points: + def point_func(self, point): + in_coords = self.coordinate_system.p2c(point) + out_coords = self.func(*in_coords) + return self.coordinate_system.c2p(*out_coords) + + def draw_lines(self): + lines = [] + origin = self.coordinate_system.get_origin() + for point in self.get_start_points(): points = [point] - for t in np.arange(0, self.virtual_time, dt): + total_arc_len = 0 + time = 0 + for x in range(self.max_time_steps): + time += self.dt last_point = points[-1] - points.append(last_point + dt * func(last_point)) + new_point = last_point + self.dt * (self.point_func(last_point) - origin) + points.append(new_point) + total_arc_len += get_norm(new_point - last_point) if get_norm(last_point) > self.cutoff_norm: break + if total_arc_len > self.arc_len: + break line = VMobject() - step = max(1, int(len(points) / self.n_anchors_per_line)) - line.set_points_smoothly(points[::step]) - self.add(line) - - self.set_stroke(self.stroke_color, self.stroke_width) - - if self.color_by_arc_length: - len_to_rgb = get_rgb_gradient_function( - self.min_arc_length, - self.max_arc_length, - colors=self.colors, - ) - for line in self: - arc_length = line.get_arc_length() - rgb = len_to_rgb([arc_length])[0] - color = rgb_to_color(rgb) - line.set_color(color) - elif self.color_by_magnitude: - image_file = get_color_field_image_file( - lambda p: get_norm(func(p)), - min_value=self.min_magnitude, - max_value=self.max_magnitude, - colors=self.colors, - ) - self.color_using_background_image(image_file) + line.virtual_time = time + step = max(1, int(len(points) / self.n_samples_per_line)) + line.set_points_as_corners(points[::step]) + line.make_approximately_smooth() + lines.append(line) + self.set_submobjects(lines) def get_start_points(self): - x_min = self.x_min - x_max = self.x_max - y_min = self.y_min - y_max = self.y_max - delta_x = self.delta_x - delta_y = self.delta_y - n_repeats = self.n_repeats - noise_factor = self.noise_factor + cs = self.coordinate_system + sample_coords = get_sample_points_from_coordinate_system( + cs, self.step_multiple, + ) + noise_factor = self.noise_factor if noise_factor is None: - noise_factor = delta_y / 2 + noise_factor = cs.x_range[2] * self.step_multiple * 0.5 + return np.array([ - x * RIGHT + y * UP + noise_factor * np.random.random(3) - for n in range(n_repeats) - for x in np.arange(x_min, x_max + delta_x, delta_x) - for y in np.arange(y_min, y_max + delta_y, delta_y) + cs.c2p(*coords) + noise_factor * np.random.random(3) + for n in range(self.n_repeats) + for coords in sample_coords ]) + def init_style(self): + if self.color_by_magnitude: + values_to_rgbs = get_vectorized_rgb_gradient_function( + *self.magnitude_range, self.color_map, + ) + cs = self.coordinate_system + for line in self.submobjects: + norms = [ + get_norm(self.func(*cs.p2c(point))) + for point in line.get_points() + ] + rgbs = values_to_rgbs(norms) + rgbas = np.zeros((len(rgbs), 4)) + rgbas[:, :3] = rgbs + rgbas[:, 3] = self.stroke_opacity + line.set_rgba_array(rgbas, "stroke_rgba") + else: + self.set_stroke(self.stroke_color, opacity=self.stroke_opacity) -# TODO: Make it so that you can have a group of stream_lines -# varying in response to a changing vector field, and still -# animate the resulting flow + if self.taper_stroke_width: + width = [0, self.stroke_width, 0] + else: + width = self.stroke_width + self.set_stroke(width=width) + + +class AnimatedStreamLines(VGroup): + CONFIG = { + "lag_range": 4, + "line_anim_class": VShowPassingFlash, + "line_anim_config": { + # "run_time": 4, + "rate_func": linear, + "time_width": 0.5, + }, + } + + def __init__(self, stream_lines, **kwargs): + super().__init__(**kwargs) + self.stream_lines = stream_lines + for line in stream_lines: + line.anim = self.line_anim_class( + line, + run_time=line.virtual_time, + **self.line_anim_config, + ) + line.anim.begin() + line.time = -self.lag_range * random.random() + self.add(line.anim.mobject) + + self.add_updater(lambda m, dt: m.update(dt)) + + def update(self, dt): + stream_lines = self.stream_lines + for line in stream_lines: + line.time += dt + adjusted_time = max(line.time, 0) % line.anim.run_time + line.anim.update(adjusted_time / line.anim.run_time) + + +# TODO: This class should be deleted class ShowPassingFlashWithThinningStrokeWidth(AnimationGroup): CONFIG = { "n_segments": 10, @@ -307,35 +293,3 @@ class ShowPassingFlashWithThinningStrokeWidth(AnimationGroup): np.linspace(max_time_width, 0, self.n_segments) ) ]) - - -# TODO, this is untested after turning it from a -# ContinualAnimation into a VGroup -class AnimatedStreamLines(VGroup): - CONFIG = { - "lag_range": 4, - "line_anim_class": ShowPassingFlash, - "line_anim_config": { - "run_time": 4, - "rate_func": linear, - "time_width": 0.3, - }, - } - - def __init__(self, stream_lines, **kwargs): - VGroup.__init__(self, **kwargs) - self.stream_lines = stream_lines - for line in stream_lines: - line.anim = self.line_anim_class(line, **self.line_anim_config) - line.anim.begin() - line.time = -self.lag_range * random.random() - self.add(line.anim.mobject) - - self.add_updater(lambda m, dt: m.update(dt)) - - def update(self, dt): - stream_lines = self.stream_lines - for line in stream_lines: - line.time += dt - adjusted_time = max(line.time, 0) % line.anim.run_time - line.anim.update(adjusted_time / line.anim.run_time) diff --git a/manimlib/once_useful_constructs/__init__.py b/manimlib/once_useful_constructs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manimlib/once_useful_constructs/arithmetic.py b/manimlib/once_useful_constructs/arithmetic.py index ea6debd8..c92f5505 100644 --- a/manimlib/once_useful_constructs/arithmetic.py +++ b/manimlib/once_useful_constructs/arithmetic.py @@ -2,7 +2,7 @@ import numpy as np from manimlib.animation.animation import Animation from manimlib.constants import * -from manimlib.mobject.svg.tex_mobject import TexMobject +from manimlib.mobject.svg.tex_mobject import Tex from manimlib.scene.scene import Scene @@ -66,7 +66,7 @@ class RearrangeEquation(Scene): """ num_start_terms = len(start_terms) all_mobs = np.array( - TexMobject(start_terms).split() + TexMobject(end_terms).split()) + Tex(start_terms).split() + Tex(end_terms).split()) all_terms = np.array(start_terms + end_terms) for term in set(all_terms): matches = all_terms == term @@ -86,7 +86,7 @@ class FlipThroughSymbols(Animation): } def __init__(self, tex_list, **kwargs): - mobject = TexMobject(self.curr_tex).shift(start_center) + mobject = Tex(self.curr_tex).shift(start_center) Animation.__init__(self, mobject, **kwargs) def interpolate_mobject(self, alpha): @@ -94,7 +94,7 @@ class FlipThroughSymbols(Animation): if new_tex != self.curr_tex: self.curr_tex = new_tex - self.mobject = TexMobject(new_tex).shift(self.start_center) + self.mobject = Tex(new_tex).shift(self.start_center) if not all(self.start_center == self.end_center): self.mobject.center().shift( (1 - alpha) * self.start_center + alpha * self.end_center diff --git a/manimlib/once_useful_constructs/combinatorics.py b/manimlib/once_useful_constructs/combinatorics.py index 703072e1..26638def 100644 --- a/manimlib/once_useful_constructs/combinatorics.py +++ b/manimlib/once_useful_constructs/combinatorics.py @@ -1,6 +1,6 @@ from manimlib.constants import * from manimlib.mobject.numbers import Integer -from manimlib.mobject.svg.tex_mobject import TexMobject +from manimlib.mobject.svg.tex_mobject import Tex from manimlib.mobject.types.vectorized_mobject import VMobject, VGroup from manimlib.scene.scene import Scene from manimlib.utils.simple_functions import choose @@ -45,7 +45,7 @@ class CountingScene(Scene): self.add(*mobjects) for mob, num in zip(mobjects, it.count(1)): if display_numbers: - num_mob = TexMobject(str(num)) + num_mob = Tex(str(num)) num_mob.center().shift(num_offset) self.add(num_mob) if mode == "highlight": @@ -74,7 +74,7 @@ class CountingScene(Scene): raise Warning("Unknown mode") frame_time = run_time / (len(regions)) for region, count in zip(regions, it.count(1)): - num_mob = TexMobject(str(count)) + num_mob = Tex(str(count)) num_mob.center().shift(num_offset) self.add(num_mob) self.set_color_region(region) @@ -113,7 +113,7 @@ class GeneralizedPascalsTriangle(VMobject): ] for n, k in self.coords: center = self.coords_to_center(n, k) - num_mob = self.submob_class(n, k) # TexMobject(str(num)) + num_mob = self.submob_class(n, k) # Tex(str(num)) scale_factor = min( 1, self.portion_to_fill * self.cell_height / num_mob.get_height(), @@ -137,7 +137,7 @@ class GeneralizedPascalsTriangle(VMobject): def generate_n_choose_k_mobs(self): self.coords_to_n_choose_k = {} for n, k in self.coords: - nck_mob = TexMobject(r"{%d \choose %d}" % (n, k)) + nck_mob = Tex(r"{%d \choose %d}" % (n, k)) scale_factor = min( 1, self.portion_to_fill * self.cell_height / nck_mob.get_height(), @@ -161,7 +161,7 @@ class GeneralizedPascalsTriangle(VMobject): return self def generate_sea_of_zeros(self): - zero = TexMobject("0") + zero = Tex("0") self.sea_of_zeros = [] for n in range(self.nrows): for a in range((self.nrows - n) / 2 + 1): diff --git a/manimlib/once_useful_constructs/counting.py b/manimlib/once_useful_constructs/counting.py index 1a46410b..181b5b32 100644 --- a/manimlib/once_useful_constructs/counting.py +++ b/manimlib/once_useful_constructs/counting.py @@ -6,7 +6,7 @@ from manimlib.constants import * from manimlib.mobject.geometry import Arrow from manimlib.mobject.geometry import Circle from manimlib.mobject.geometry import Dot -from manimlib.mobject.svg.tex_mobject import TexMobject +from manimlib.mobject.svg.tex_mobject import Tex from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.scene.scene import Scene @@ -26,7 +26,7 @@ class CountingScene(Scene): self.dots = VGroup() self.number = 0 self.max_place = 0 - self.number_mob = VGroup(TexMobject(str(self.number))) + self.number_mob = VGroup(Tex(str(self.number))) self.number_mob.scale(self.num_scale_factor) self.number_mob.shift(self.num_start_location) @@ -159,7 +159,7 @@ class CountingScene(Scene): place = 0 max_place = self.max_place while place < max_place: - digit = TexMobject(str(self.get_place_num(num, place))) + digit = Tex(str(self.get_place_num(num, place))) if place >= len(self.digit_place_colors): self.digit_place_colors += self.digit_place_colors digit.set_color(self.digit_place_colors[place]) diff --git a/manimlib/once_useful_constructs/fractals.py b/manimlib/once_useful_constructs/fractals.py index 88d6225c..6285f554 100644 --- a/manimlib/once_useful_constructs/fractals.py +++ b/manimlib/once_useful_constructs/fractals.py @@ -1,9 +1,9 @@ from functools import reduce from manimlib.constants import * -from manimlib.for_3b1b_videos.pi_creature import PiCreature -from manimlib.for_3b1b_videos.pi_creature import Randolph -from manimlib.for_3b1b_videos.pi_creature import get_all_pi_creature_modes +# from manimlib.for_3b1b_videos.pi_creature import PiCreature +# from manimlib.for_3b1b_videos.pi_creature import Randolph +# from manimlib.for_3b1b_videos.pi_creature import get_all_pi_creature_modes from manimlib.mobject.geometry import Circle from manimlib.mobject.geometry import Polygon from manimlib.mobject.geometry import RegularPolygon @@ -326,7 +326,7 @@ class FractalCurve(VMobject): self, *alpha_pair ) self.add(submobject) - self.points = np.zeros((0, 3)) + self.set_points(np.zeros((0, 3))) def init_colors(self): VMobject.init_colors(self) diff --git a/manimlib/scene/graph_scene.py b/manimlib/once_useful_constructs/graph_scene.py similarity index 94% rename from manimlib/scene/graph_scene.py rename to manimlib/once_useful_constructs/graph_scene.py index 7d5dcf48..40d7e790 100644 --- a/manimlib/scene/graph_scene.py +++ b/manimlib/once_useful_constructs/graph_scene.py @@ -4,13 +4,13 @@ from manimlib.animation.creation import Write, DrawBorderThenFill, ShowCreation from manimlib.animation.transform import Transform from manimlib.animation.update import UpdateFromAlphaFunc from manimlib.constants import * -from manimlib.mobject.functions import ParametricFunction +from manimlib.mobject.functions import ParametricCurve from manimlib.mobject.geometry import Line from manimlib.mobject.geometry import Rectangle from manimlib.mobject.geometry import RegularPolygon from manimlib.mobject.number_line import NumberLine -from manimlib.mobject.svg.tex_mobject import TexMobject -from manimlib.mobject.svg.tex_mobject import TextMobject +from manimlib.mobject.svg.tex_mobject import Tex +from manimlib.mobject.svg.tex_mobject import TexText from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VectorizedPoint from manimlib.scene.scene import Scene @@ -19,10 +19,9 @@ from manimlib.utils.color import color_gradient from manimlib.utils.color import invert_color from manimlib.utils.space_ops import angle_of_vector -# TODO, this should probably reimplemented entirely, especially so as to -# better reuse code from mobject/coordinate_systems. -# Also, I really dislike how the configuration is set up, this -# is way too messy to work with. +# TODO, this class should be deprecated, with all its +# functionality moved to Axes and handled at the mobject +# level rather than the scene level class GraphScene(Scene): @@ -82,9 +81,9 @@ class GraphScene(Scene): if len(self.x_labeled_nums) > 0: if self.exclude_zero_label: self.x_labeled_nums = [x for x in self.x_labeled_nums if x != 0] - x_axis.add_numbers(*self.x_labeled_nums) + x_axis.add_numbers(self.x_labeled_nums) if self.x_axis_label: - x_label = TextMobject(self.x_axis_label) + x_label = TexText(self.x_axis_label) x_label.next_to( x_axis.get_tick_marks(), UP + RIGHT, buff=SMALL_BUFF @@ -116,9 +115,9 @@ class GraphScene(Scene): if len(self.y_labeled_nums) > 0: if self.exclude_zero_label: self.y_labeled_nums = [y for y in self.y_labeled_nums if y != 0] - y_axis.add_numbers(*self.y_labeled_nums) + y_axis.add_numbers(self.y_labeled_nums) if self.y_axis_label: - y_label = TextMobject(self.y_axis_label) + y_label = TexText(self.y_axis_label) y_label.next_to( y_axis.get_corner(UP + RIGHT), UP + RIGHT, buff=SMALL_BUFF @@ -165,7 +164,7 @@ class GraphScene(Scene): y = self.y_max return self.coords_to_point(x, y) - graph = ParametricFunction( + graph = ParametricCurve( parameterized_function, color=color, **kwargs @@ -201,7 +200,7 @@ class GraphScene(Scene): buff=MED_SMALL_BUFF, color=None, ): - label = TexMobject(label) + label = Tex(label) color = color or graph.get_color() label.set_color(color) if x_val is None: @@ -307,7 +306,7 @@ class GraphScene(Scene): } added_anims = kwargs.get("added_anims", []) transform_kwargs.update(kwargs) - curr_rects.align_submobjects(new_rects) + curr_rects.align_family(new_rects) x_coords = set() # Keep track of new repetitions for rect in curr_rects: x = rect.get_center()[0] @@ -395,11 +394,11 @@ class GraphScene(Scene): labels = VGroup() if dx_label is not None: - group.dx_label = TexMobject(dx_label) + group.dx_label = Tex(dx_label) labels.add(group.dx_label) group.add(group.dx_label) if df_label is not None: - group.df_label = TexMobject(df_label) + group.df_label = Tex(df_label) labels.add(group.df_label) group.add(group.df_label) @@ -430,7 +429,7 @@ class GraphScene(Scene): if include_secant_line: secant_line_color = secant_line_color or self.default_derivative_color group.secant_line = Line(p1, p2, color=secant_line_color) - group.secant_line.scale_in_place( + group.secant_line.scale( secant_line_length / group.secant_line.get_length() ) group.add(group.secant_line) @@ -444,9 +443,9 @@ class GraphScene(Scene): triangle.set_fill(color, 1) triangle.set_stroke(width=0) if label is None: - T_label = TexMobject(self.variable_point_label, fill_color=color) + T_label = Tex(self.variable_point_label, fill_color=color) else: - T_label = TexMobject(label, fill_color=color) + T_label = Tex(label, fill_color=color) T_label.next_to(triangle, DOWN) v_line = self.get_vertical_line_to_graph( diff --git a/manimlib/once_useful_constructs/graph_theory.py b/manimlib/once_useful_constructs/graph_theory.py index 890a13ab..832a8cba 100644 --- a/manimlib/once_useful_constructs/graph_theory.py +++ b/manimlib/once_useful_constructs/graph_theory.py @@ -193,10 +193,10 @@ class DiscreteGraphScene(Scene): Scene.__init__(self, *args, **kwargs) def construct(self): - self.points = list(map(np.array, self.graph.vertices)) - self.vertices = self.dots = [Dot(p) for p in self.points] + self._points = list(map(np.array, self.graph.vertices)) + self.vertices = self.dots = [Dot(p) for p in self._points] self.edges = self.lines = [ - Line(self.points[i], self.points[j]) + Line(self._points[i], self._points[j]) for i, j in self.graph.edges ] self.add(*self.dots + self.edges) @@ -212,8 +212,8 @@ class DiscreteGraphScene(Scene): def region_from_cycle(self, cycle): point_pairs = [ [ - self.points[cycle[i]], - self.points[cycle[(i + 1) % len(cycle)]] + self._points[cycle[i]], + self._points[cycle[(i + 1) % len(cycle)]] ] for i in range(len(cycle)) ] @@ -236,7 +236,7 @@ class DiscreteGraphScene(Scene): start = Mobject(*self.vertices) end = Mobject(*[ Dot(point, radius=3 * Dot.DEFAULT_RADIUS, color="lightgreen") - for point in self.points + for point in self._points ]) self.play(Transform( start, end, rate_func=there_and_back, @@ -256,7 +256,7 @@ class DiscreteGraphScene(Scene): for vertex in self.vertices ] + [ ApplyMethod( - edge.scale_in_place, + edge.scale, (edge.get_length() - diameter) / edge.get_length() ) for edge in self.edges @@ -281,7 +281,7 @@ class DiscreteGraphScene(Scene): next_in_cycle = it.cycle(cycle) next(next_in_cycle) # jump one ahead self.traced_cycle = Mobject(*[ - Line(self.points[i], self.points[j]).set_color(color) + Line(self._points[i], self._points[j]).set_color(color) for i, j in zip(cycle, next_in_cycle) ]) self.play( @@ -306,8 +306,8 @@ class DiscreteGraphScene(Scene): to_check.add(pair[1]) self.spanning_tree = Mobject(*[ Line( - self.points[pair[0]], - self.points[pair[1]] + self._points[pair[0]], + self._points[pair[1]] ).set_color(color) for pair in self.spanning_tree_index_pairs ]) @@ -320,7 +320,7 @@ class DiscreteGraphScene(Scene): self.generate_spanning_tree() root = self.spanning_tree_root color = self.spanning_tree.get_color() - indices = list(range(len(self.points))) + indices = list(range(len(self._points))) # Build dicts parent_of = dict([ tuple(reversed(pair)) @@ -376,7 +376,7 @@ class DiscreteGraphScene(Scene): cycles = self.graph.region_cycles self.dual_points = [ center_of_mass([ - self.points[index] + self._points[index] for index in cycle ]) for cycle in cycles @@ -404,8 +404,8 @@ class DiscreteGraphScene(Scene): if all(dual_point_pair[i] == point_at_infinity): new_point = np.array(dual_point_pair[1 - i]) vect = center_of_mass([ - self.points[pair[0]], - self.points[pair[1]] + self._points[pair[0]], + self._points[pair[1]] ]) - new_point new_point += FRAME_X_RADIUS * vect / get_norm(vect) dual_point_pair[i] = new_point diff --git a/manimlib/once_useful_constructs/light.py b/manimlib/once_useful_constructs/light.py index 7bc15a6b..5ec132b9 100644 --- a/manimlib/once_useful_constructs/light.py +++ b/manimlib/once_useful_constructs/light.py @@ -74,12 +74,14 @@ class SwitchOff(LaggedStartMap): class Lighthouse(SVGMobject): CONFIG = { - "file_name": "lighthouse", "height": LIGHTHOUSE_HEIGHT, "fill_color": WHITE, "fill_opacity": 1.0, } + def __init__(self, **kwargs): + super().__init__("lighthouse", **kwargs) + def move_to(self, point): self.next_to(point, DOWN, buff=0) @@ -388,7 +390,7 @@ class LightSource(VMobject): def has_screen(self): if self.screen is None: return False - elif np.size(self.screen.points) == 0: + elif self.screen.get_num_points() == 0: return False else: return True @@ -572,7 +574,7 @@ class LightSource(VMobject): # add two control points for the outer cone if np.size(anchors) == 0: - self.shadow.points = [] + self.shadow.resize_points(0) return ray1 = anchors[source_index - 1] - projected_source diff --git a/manimlib/once_useful_constructs/matrix_multiplication.py b/manimlib/once_useful_constructs/matrix_multiplication.py index 49f914a8..09a0bb26 100644 --- a/manimlib/once_useful_constructs/matrix_multiplication.py +++ b/manimlib/once_useful_constructs/matrix_multiplication.py @@ -8,7 +8,7 @@ from manimlib.constants import * from manimlib.mobject.geometry import Circle from manimlib.mobject.geometry import Line from manimlib.mobject.matrix import Matrix -from manimlib.mobject.svg.tex_mobject import TexMobject +from manimlib.mobject.svg.tex_mobject import Tex from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.scene.scene import Scene @@ -48,7 +48,7 @@ class NumericalMatrixMultiplication(Scene): for c in range(k) for prefix in ["" if c == 0 else "+"] ] - mob_matrix[a][b] = TexMobject(parts, next_to_buff=0.1) + mob_matrix[a][b] = Tex(parts, next_to_buff=0.1) return Matrix(mob_matrix) def add_lines(self, left, right): @@ -80,7 +80,7 @@ class NumericalMatrixMultiplication(Scene): self.show_frame() def organize_matrices(self, left, right, result): - equals = TexMobject("=") + equals = Tex("=") everything = VGroup(left, right, equals, result) everything.arrange() everything.set_width(FRAME_WIDTH - 1) diff --git a/manimlib/scene/reconfigurable_scene.py b/manimlib/once_useful_constructs/reconfigurable_scene.py similarity index 100% rename from manimlib/scene/reconfigurable_scene.py rename to manimlib/once_useful_constructs/reconfigurable_scene.py diff --git a/manimlib/scene/__init__.py b/manimlib/scene/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manimlib/scene/media_dir.txt b/manimlib/scene/media_dir.txt deleted file mode 100644 index 27949aaf..00000000 --- a/manimlib/scene/media_dir.txt +++ /dev/null @@ -1 +0,0 @@ -media \ No newline at end of file diff --git a/manimlib/scene/moving_camera_scene.py b/manimlib/scene/moving_camera_scene.py deleted file mode 100644 index 7ad2c165..00000000 --- a/manimlib/scene/moving_camera_scene.py +++ /dev/null @@ -1,31 +0,0 @@ -from manimlib.camera.moving_camera import MovingCamera -from manimlib.scene.scene import Scene -from manimlib.utils.iterables import list_update -from manimlib.utils.family_ops import extract_mobject_family_members - - -class MovingCameraScene(Scene): - CONFIG = { - "camera_class": MovingCamera - } - - def setup(self): - Scene.setup(self) - assert(isinstance(self.camera, MovingCamera)) - self.camera_frame = self.camera.frame - # Hmm, this currently relies on the fact that MovingCamera - # willd default to a full-sized frame. Is that okay? - return self - - def get_moving_mobjects(self, *animations): - moving_mobjects = Scene.get_moving_mobjects(self, *animations) - all_moving_mobjects = extract_mobject_family_members( - moving_mobjects - ) - movement_indicators = self.camera.get_mobjects_indicating_movement() - for movement_indicator in movement_indicators: - if movement_indicator in all_moving_mobjects: - # When one of these is moving, the camera should - # consider all mobjects to be moving - return list_update(self.mobjects, moving_mobjects) - return moving_mobjects diff --git a/manimlib/scene/scene.py b/manimlib/scene/scene.py index 412c64a3..ad2f3838 100644 --- a/manimlib/scene/scene.py +++ b/manimlib/scene/scene.py @@ -1,28 +1,29 @@ import inspect import random -import warnings import platform import itertools as it +import logging +from functools import wraps from tqdm import tqdm as ProgressDisplay import numpy as np import time -from IPython.terminal.embed import InteractiveShellEmbed -from manimlib.animation.animation import Animation +from manimlib.animation.animation import prepare_animation from manimlib.animation.transform import MoveToTarget from manimlib.mobject.mobject import Point from manimlib.camera.camera import Camera -from manimlib.constants import * -from manimlib.container.container import Container +from manimlib.constants import DEFAULT_WAIT_TIME from manimlib.mobject.mobject import Mobject from manimlib.scene.scene_file_writer import SceneFileWriter +from manimlib.utils.config_ops import digest_config from manimlib.utils.family_ops import extract_mobject_family_members from manimlib.utils.family_ops import restructure_list_to_exclude_certain_family_members -from manimlib.window import Window +from manimlib.event_handler.event_type import EventType +from manimlib.event_handler import EVENT_DISPATCHER -class Scene(Container): +class Scene(object): CONFIG = { "window_config": {}, "camera_class": Camera, @@ -39,12 +40,11 @@ class Scene(Container): } def __init__(self, **kwargs): - Container.__init__(self, **kwargs) + digest_config(self, kwargs) if self.preview: - self.window = Window(self, **self.window_config) + from manimlib.window import Window + self.window = Window(scene=self, **self.window_config) self.camera_config["ctx"] = self.window.ctx - self.virtual_animation_start_time = 0 - self.real_animation_start_time = time.time() else: self.window = None @@ -55,20 +55,21 @@ class Scene(Container): self.time = 0 self.skip_time = 0 self.original_skipping_status = self.skip_animations - self.time_of_last_frame = time.time() # Items associated with interaction self.mouse_point = Point() self.mouse_drag_point = Point() - self.zoom_on_scroll = False - self.quit_interaction = False - # Much nice to work with deterministic scenes + # Much nicer to work with deterministic scenes if self.random_seed is not None: random.seed(self.random_seed) np.random.seed(self.random_seed) def run(self): + self.virtual_animation_start_time = 0 + self.real_animation_start_time = time.time() + self.file_writer.begin() + self.setup() try: self.construct() @@ -100,10 +101,13 @@ class Scene(Container): # which updates the frame while under # the hood calling the pyglet event loop self.quit_interaction = False - while not self.window.is_closing and not self.quit_interaction: + self.lock_static_mobject_data() + while not (self.window.is_closing or self.quit_interaction): self.update_frame() if self.window.is_closing: self.window.destroy() + if self.quit_interaction: + self.unlock_mobject_data() def embed(self): if not self.preview: @@ -114,12 +118,17 @@ class Scene(Container): self.linger_after_completion = False self.update_frame() + from IPython.terminal.embed import InteractiveShellEmbed shell = InteractiveShellEmbed() # Have the frame update after each command shell.events.register('post_run_cell', lambda *a, **kw: self.update_frame()) - # Stack depth of 2 means the shell will use - # the namespace of the caller, not this method - shell(stack_depth=2) + # Use the locals of the caller as the local namespace + # once embeded, and add a few custom shortcuts + local_ns = inspect.currentframe().f_back.f_locals + local_ns["touch"] = self.interact + for term in ("play", "wait", "add", "remove", "clear", "save_state", "restore"): + local_ns[term] = getattr(self, term) + shell(local_ns=local_ns, stack_depth=2) # End scene when exiting an embed. raise EndSceneEarlyException() @@ -130,6 +139,10 @@ class Scene(Container): def get_image(self): return self.camera.get_image() + def show(self): + self.update_frame(ignore_skipping=True) + self.get_image().show() + def update_frame(self, dt=0, ignore_skipping=False): self.increment_time(dt) self.update_mobjects(dt) @@ -143,8 +156,6 @@ class Scene(Container): if self.window: self.window.swap_buffers() - # win_time, win_dt = self.window.timer.next_frame() - # while (self.time - self.skip_time - win_time) > 0: vt = self.time - self.virtual_animation_start_time rt = time.time() - self.real_animation_start_time if rt < vt: @@ -154,8 +165,7 @@ class Scene(Container): if not self.skip_animations: self.file_writer.write_frame(self.camera) - ### - + # Related to updating def update_mobjects(self, dt): for mobject in self.mobjects: mobject.update(dt) @@ -166,15 +176,14 @@ class Scene(Container): for mob in self.mobjects ]) - ### - + # Related to time def get_time(self): return self.time def increment_time(self, dt): self.time += dt - ### + # Related to internal mobject organization def get_top_level_mobjects(self): # Return only those which are not in the family # of another mobject from the scene @@ -238,6 +247,21 @@ class Scene(Container): def get_mobject_copies(self): return [m.copy() for m in self.mobjects] + # Related to skipping + def update_skipping_status(self): + if self.start_at_animation_number is not None: + if self.num_plays == self.start_at_animation_number: + self.stop_skipping() + if self.end_at_animation_number is not None: + if self.num_plays >= self.end_at_animation_number: + raise EndSceneEarlyException() + + def stop_skipping(self): + if self.skip_animations: + self.skip_animations = False + self.skip_time += self.time + + # Methods associated with running animations def get_time_progression(self, run_time, n_iterations=None, override_skip_animations=False): if self.skip_animations and not override_skip_animations: times = [run_time] @@ -248,7 +272,7 @@ class Scene(Container): times, total=n_iterations, leave=self.leave_progress_bars, - ascii=False if platform.system() != 'Windows' else True + ascii=True if platform.system() == 'Windows' else None ) return time_progression @@ -264,6 +288,23 @@ class Scene(Container): ])) return time_progression + def get_wait_time_progression(self, duration, stop_condition): + if stop_condition is not None: + time_progression = self.get_time_progression( + duration, + n_iterations=-1, # So it doesn't show % progress + override_skip_animations=True + ) + time_progression.set_description( + "Waiting for {}".format(stop_condition.__name__) + ) + else: + time_progression = self.get_time_progression(duration) + time_progression.set_description( + "Waiting {}".format(self.num_plays) + ) + return time_progression + def anims_from_play_args(self, *args, **kwargs): """ Each arg can either be an animation, or a mobject method @@ -307,10 +348,7 @@ class Scene(Container): state["method_args"] = [] for arg in args: - if isinstance(arg, Animation): - compile_method(state) - animations.append(arg) - elif inspect.ismethod(arg): + if inspect.ismethod(arg): compile_method(state) state["curr_method"] = arg elif state["curr_method"] is not None: @@ -321,7 +359,13 @@ class Scene(Container): you meant to pass in as a Scene.play argument """) else: - raise Exception("Invalid play arguments") + try: + anim = prepare_animation(arg) + except TypeError: + raise TypeError(f"Unexpected argument {arg} passed to Scene.play()") + + compile_method(state) + animations.append(anim) compile_method(state) for animation in animations: @@ -331,21 +375,8 @@ class Scene(Container): return animations - def update_skipping_status(self): - if self.start_at_animation_number is not None: - if self.num_plays == self.start_at_animation_number: - self.stop_skipping() - if self.end_at_animation_number is not None: - if self.num_plays >= self.end_at_animation_number: - raise EndSceneEarlyException() - - def stop_skipping(self): - if self.skip_animations: - self.skip_animations = False - self.skip_time += self.time - - # Methods associated with running animations def handle_play_like_call(func): + @wraps(func) def wrapper(self, *args, **kwargs): self.update_skipping_status() should_write = not self.skip_animations @@ -370,15 +401,12 @@ class Scene(Container): for anim in animations ])) for mobject in self.mobjects: - if mobject in movers: + if mobject in movers or mobject.get_family_updaters(): continue - if mobject.get_family_updaters(): - continue - mobject.lock_shader_data() + self.camera.set_mobjects_as_static(mobject) def unlock_mobject_data(self): - for mobject in self.mobjects: - mobject.unlock_shader_data() + self.camera.release_static_mobjects() def begin_animations(self, animations): for animation in animations: @@ -388,9 +416,8 @@ class Scene(Container): # animated mobjects that are in the family of # those on screen, this can result in a restructuring # of the scene.mobjects list, which is usually desired. - mob = animation.mobject - if mob not in self.mobjects: - self.add(mob) + if animation.mobject not in self.mobjects: + self.add(animation.mobject) def progress_through_animations(self, animations): last_t = 0 @@ -408,9 +435,6 @@ class Scene(Container): for animation in animations: animation.finish() animation.clean_up_from_scene(self) - self.mobjects_from_last_animation = [ - anim.mobject for anim in animations - ] if self.skip_animations: self.update_mobjects(self.get_run_time(animations)) else: @@ -419,7 +443,10 @@ class Scene(Container): @handle_play_like_call def play(self, *args, **kwargs): if len(args) == 0: - warnings.warn("Called Scene.play with no animations") + logging.log( + logging.WARNING, + "Called Scene.play with no animations" + ) return animations = self.anims_from_play_args(*args, **kwargs) self.lock_static_mobject_data(*animations) @@ -428,33 +455,6 @@ class Scene(Container): self.finish_animations(animations) self.unlock_mobject_data() - def clean_up_animations(self, *animations): - for animation in animations: - animation.clean_up_from_scene(self) - return self - - def get_mobjects_from_last_animation(self): - if hasattr(self, "mobjects_from_last_animation"): - return self.mobjects_from_last_animation - return [] - - def get_wait_time_progression(self, duration, stop_condition): - if stop_condition is not None: - time_progression = self.get_time_progression( - duration, - n_iterations=-1, # So it doesn't show % progress - override_skip_animations=True - ) - time_progression.set_description( - "Waiting for {}".format(stop_condition.__name__) - ) - else: - time_progression = self.get_time_progression(duration) - time_progression.set_description( - "Waiting {}".format(self.num_plays) - ) - return time_progression - @handle_play_like_call def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None): self.update_mobjects(dt=0) # Any problems with this? @@ -500,10 +500,6 @@ class Scene(Container): time = self.get_time() + time_offset self.file_writer.add_sound(sound_file, time, gain, **kwargs) - def show(self): - self.update_frame(ignore_skipping=True) - self.get_image().show() - # Helpers for interactive development def save_state(self): self.saved_state = { @@ -524,39 +520,83 @@ class Scene(Container): self.mobjects = mobjects # Event handling + def on_mouse_motion(self, point, d_point): self.mouse_point.move_to(point) + event_data = {"point": point, "d_point": d_point} + propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseMotionEvent, **event_data) + if propagate_event is not None and propagate_event is False: + return + + frame = self.camera.frame + if self.window.is_key_pressed(ord("d")): + frame.increment_theta(-d_point[0]) + frame.increment_phi(d_point[1]) + elif self.window.is_key_pressed(ord("s")): + shift = -d_point + shift[0] *= frame.get_width() / 2 + shift[1] *= frame.get_height() / 2 + transform = frame.get_inverse_camera_rotation_matrix() + shift = np.dot(np.transpose(transform), shift) + frame.shift(shift) + def on_mouse_drag(self, point, d_point, buttons, modifiers): self.mouse_drag_point.move_to(point) - # Only if 3d rotation is enabled? - self.camera.frame.increment_theta(-d_point[0]) - self.camera.frame.increment_phi(d_point[1]) + + event_data = {"point": point, "d_point": d_point, "buttons": buttons, "modifiers": modifiers} + propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseDragEvent, **event_data) + if propagate_event is not None and propagate_event is False: + return def on_mouse_press(self, point, button, mods): - pass + event_data = {"point": point, "button": button, "mods": mods} + propagate_event = EVENT_DISPATCHER.dispatch(EventType.MousePressEvent, **event_data) + if propagate_event is not None and propagate_event is False: + return def on_mouse_release(self, point, button, mods): - pass + event_data = {"point": point, "button": button, "mods": mods} + propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseReleaseEvent, **event_data) + if propagate_event is not None and propagate_event is False: + return def on_mouse_scroll(self, point, offset): + event_data = {"point": point, "offset": offset} + propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseScrollEvent, **event_data) + if propagate_event is not None and propagate_event is False: + return + frame = self.camera.frame - if self.zoom_on_scroll: + if self.window.is_key_pressed(ord("z")): factor = 1 + np.arctan(10 * offset[1]) frame.scale(factor, about_point=point) else: - frame.shift(-30 * offset) + transform = frame.get_inverse_camera_rotation_matrix() + shift = np.dot(np.transpose(transform), offset) + frame.shift(-20.0 * shift) def on_key_release(self, symbol, modifiers): - if chr(symbol) == "z": - self.zoom_on_scroll = False + event_data = {"symbol": symbol, "modifiers": modifiers} + propagate_event = EVENT_DISPATCHER.dispatch(EventType.KeyReleaseEvent, **event_data) + if propagate_event is not None and propagate_event is False: + return def on_key_press(self, symbol, modifiers): - if chr(symbol) == "r": + try: + char = chr(symbol) + except OverflowError: + print(" Warning: The value of the pressed key is too large.") + return + + event_data = {"symbol": symbol, "modifiers": modifiers} + propagate_event = EVENT_DISPATCHER.dispatch(EventType.KeyPressEvent, **event_data) + if propagate_event is not None and propagate_event is False: + return + + if char == "r": self.camera.frame.to_default_state() - elif chr(symbol) == "z": - self.zoom_on_scroll = True - elif chr(symbol) == "q": + elif char == "q": self.quit_interaction = True def on_resize(self, width: int, height: int): diff --git a/manimlib/scene/scene_file_writer.py b/manimlib/scene/scene_file_writer.py index fd234bc0..153dff74 100644 --- a/manimlib/scene/scene_file_writer.py +++ b/manimlib/scene/scene_file_writer.py @@ -6,7 +6,6 @@ import os import sys import platform -import manimlib.constants as consts from manimlib.constants import FFMPEG_BIN from manimlib.utils.config_ops import digest_config from manimlib.utils.file_ops import guarantee_existence @@ -18,17 +17,20 @@ from manimlib.utils.sounds import get_full_sound_file_path class SceneFileWriter(object): CONFIG = { "write_to_movie": False, + "break_into_partial_movies": False, # TODO, save_pngs is doing nothing "save_pngs": False, "png_mode": "RGBA", "save_last_frame": False, "movie_file_extension": ".mp4", - "gif_file_extension": ".gif", - # Previous output_file_name - # TODO, address this in extract_scene et. al. - "file_name": None, + # Should the path of output files mirror the directory + # structure of the module holding the scene? + "mirror_module_path": False, + # What python file is generating this scene "input_file_path": "", + # Where should this be written "output_directory": None, + "file_name": None, "open_file_upon_completion": False, "show_file_location_upon_completion": False, "quiet": False, @@ -37,60 +39,36 @@ class SceneFileWriter(object): def __init__(self, scene, **kwargs): digest_config(self, kwargs) self.scene = scene + self.writing_process = None self.init_output_directories() self.init_audio() # Output directories and files def init_output_directories(self): - module_directory = self.output_directory or self.get_default_module_directory() + out_dir = self.output_directory + if self.mirror_module_path: + module_dir = self.get_default_module_directory() + out_dir = os.path.join(out_dir, module_dir) + scene_name = self.file_name or self.get_default_scene_name() if self.save_last_frame: - if consts.VIDEO_DIR != "": - image_dir = guarantee_existence(os.path.join( - consts.VIDEO_DIR, - module_directory, - "images", - )) - else: - image_dir = guarantee_existence(os.path.join( - consts.VIDEO_OUTPUT_DIR, - "images", - )) - self.image_file_path = os.path.join( - image_dir, - add_extension_if_not_present(scene_name, ".png") - ) + image_dir = guarantee_existence(os.path.join(out_dir, "images")) + image_file = add_extension_if_not_present(scene_name, ".png") + self.image_file_path = os.path.join(image_dir, image_file) if self.write_to_movie: - if consts.VIDEO_DIR != "": - movie_dir = guarantee_existence(os.path.join( - consts.VIDEO_DIR, - module_directory, - self.get_resolution_directory(), + movie_dir = guarantee_existence(os.path.join(out_dir, "videos")) + movie_file = add_extension_if_not_present(scene_name, self.movie_file_extension) + self.movie_file_path = os.path.join(movie_dir, movie_file) + if self.break_into_partial_movies: + self.partial_movie_directory = guarantee_existence(os.path.join( + movie_dir, "partial_movie_files", scene_name, )) - else: - movie_dir = guarantee_existence(consts.VIDEO_OUTPUT_DIR) - self.movie_file_path = os.path.join( - movie_dir, - add_extension_if_not_present( - scene_name, self.movie_file_extension - ) - ) - self.gif_file_path = os.path.join( - movie_dir, - add_extension_if_not_present( - scene_name, self.gif_file_extension - ) - ) - self.partial_movie_directory = guarantee_existence(os.path.join( - movie_dir, - "partial_movie_files", - scene_name, - )) def get_default_module_directory(self): - filename = os.path.basename(self.input_file_path) - root, _ = os.path.splitext(filename) - return root + path, _ = os.path.splitext(self.input_file_path) + if path.startswith("_"): + path = path[1:] + return path def get_default_scene_name(self): if self.file_name is None: @@ -163,41 +141,37 @@ class SceneFileWriter(object): self.add_audio_segment(new_segment, time, **kwargs) # Writers + def begin(self): + if not self.break_into_partial_movies and self.write_to_movie: + self.open_movie_pipe(self.get_movie_file_path()) + def begin_animation(self): - if self.write_to_movie: - self.open_movie_pipe() + if self.break_into_partial_movies and self.write_to_movie: + self.open_movie_pipe(self.get_next_partial_movie_path()) def end_animation(self): - if self.write_to_movie: + if self.break_into_partial_movies and self.write_to_movie: self.close_movie_pipe() - def write_frame(self, camera): - if self.write_to_movie: - raw_bytes = camera.get_raw_fbo_data() - self.writing_process.stdin.write(raw_bytes) - - def save_final_image(self, image): - file_path = self.get_image_file_path() - image.save(file_path) - self.print_file_ready_message(file_path) - def finish(self): if self.write_to_movie: - if hasattr(self, "writing_process"): - self.writing_process.terminate() - self.combine_movie_files() + if self.break_into_partial_movies: + self.combine_movie_files() + else: + self.close_movie_pipe() + if self.includes_sound: + self.add_sound_to_video() + self.print_file_ready_message(self.get_movie_file_path()) if self.save_last_frame: self.scene.update_frame(ignore_skipping=True) self.save_final_image(self.scene.get_image()) if self.should_open_file(): self.open_file() - def open_movie_pipe(self): - file_path = self.get_next_partial_movie_path() - temp_file_path = os.path.splitext(file_path)[0] + '_temp' + self.movie_file_extension - - self.partial_movie_file_path = file_path - self.temp_partial_movie_file_path = temp_file_path + def open_movie_pipe(self, file_path): + stem, ext = os.path.splitext(file_path) + self.final_file_path = file_path + self.temp_file_path = stem + "_temp" + ext fps = self.scene.camera.frame_rate width, height = self.scene.camera.get_pixel_shape() @@ -214,39 +188,34 @@ class SceneFileWriter(object): '-an', # Tells FFMPEG not to expect any audio '-loglevel', 'error', ] - # TODO, the test for a transparent background should not be based on - # the file extension. if self.movie_file_extension == ".mov": # This is if the background of the exported # video should be transparent. command += [ '-vcodec', 'qtrle', ] + elif self.movie_file_extension == ".gif": + command += [] else: command += [ '-vcodec', 'libx264', '-pix_fmt', 'yuv420p', - # '-pix_fmt', 'yuv444p14le', ] - command += [temp_file_path] + command += [self.temp_file_path] self.writing_process = sp.Popen(command, stdin=sp.PIPE) + def write_frame(self, camera): + if self.write_to_movie: + raw_bytes = camera.get_raw_fbo_data() + self.writing_process.stdin.write(raw_bytes) + def close_movie_pipe(self): self.writing_process.stdin.close() self.writing_process.wait() - shutil.move( - self.temp_partial_movie_file_path, - self.partial_movie_file_path, - ) + self.writing_process.terminate() + shutil.move(self.temp_file_path, self.final_file_path) def combine_movie_files(self): - # Manim renders the scene as many smaller movie files - # which are then concatenated to a larger one. The reason - # for this is that sometimes video-editing is made easier when - # one works with the broken up scene, which effectively has - # cuts at all the places you might want. But for viewing - # the scene as a whole, one of course wants to see it as a - # single piece. kwargs = { "remove_non_integer_files": True, "extension": self.movie_file_extension, @@ -275,7 +244,7 @@ class SceneFileWriter(object): for pf_path in partial_movie_files: if os.name == 'nt': pf_path = pf_path.replace('\\', '/') - fp.write("file \'{}\'\n".format(pf_path)) + fp.write(f"file \'{pf_path}\'\n") movie_file_path = self.get_movie_file_path() commands = [ @@ -294,41 +263,44 @@ class SceneFileWriter(object): combine_process = sp.Popen(commands) combine_process.wait() - if self.includes_sound: - sound_file_path = movie_file_path.replace( - self.movie_file_extension, ".wav" - ) - # Makes sure sound file length will match video file - self.add_audio_segment(AudioSegment.silent(0)) - self.audio_segment.export( - sound_file_path, - bitrate='312k', - ) - temp_file_path = movie_file_path.replace(".", "_temp.") - commands = [ - "ffmpeg", - "-i", movie_file_path, - "-i", sound_file_path, - '-y', # overwrite output file if it exists - "-c:v", "copy", - "-c:a", "aac", - "-b:a", "320k", - # select video stream from first file - "-map", "0:v:0", - # select audio stream from second file - "-map", "1:a:0", - '-loglevel', 'error', - # "-shortest", - temp_file_path, - ] - sp.call(commands) - shutil.move(temp_file_path, movie_file_path) - os.remove(sound_file_path) + def add_sound_to_video(self): + movie_file_path = self.get_movie_file_path() + stem, ext = os.path.splitext(movie_file_path) + sound_file_path = stem + ".wav" + # Makes sure sound file length will match video file + self.add_audio_segment(AudioSegment.silent(0)) + self.audio_segment.export( + sound_file_path, + bitrate='312k', + ) + temp_file_path = stem + "_temp" + ext + commands = [ + "ffmpeg", + "-i", movie_file_path, + "-i", sound_file_path, + '-y', # overwrite output file if it exists + "-c:v", "copy", + "-c:a", "aac", + "-b:a", "320k", + # select video stream from first file + "-map", "0:v:0", + # select audio stream from second file + "-map", "1:a:0", + '-loglevel', 'error', + # "-shortest", + temp_file_path, + ] + sp.call(commands) + shutil.move(temp_file_path, movie_file_path) + os.remove(sound_file_path) - self.print_file_ready_message(movie_file_path) + def save_final_image(self, image): + file_path = self.get_image_file_path() + image.save(file_path) + self.print_file_ready_message(file_path) def print_file_ready_message(self, file_path): - print("\nFile ready at {}\n".format(file_path)) + print(f"\nFile ready at {file_path}\n") def should_open_file(self): return any([ diff --git a/manimlib/scene/scene_from_video.py b/manimlib/scene/scene_from_video.py deleted file mode 100644 index d7957ae1..00000000 --- a/manimlib/scene/scene_from_video.py +++ /dev/null @@ -1,53 +0,0 @@ -from tqdm import tqdm as show_progress -import cv2 - -from manimlib.scene.scene import Scene - - -# TODO, is this depricated? -class SceneFromVideo(Scene): - def construct(self, file_name, - freeze_last_frame=True, - time_range=None): - cap = cv2.VideoCapture(file_name) - self.shape = ( - int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)), - int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)) - ) - fps = cap.get(cv2.cv.CV_CAP_PROP_FPS) - self.camera.frame_rate = fps - frame_count = int(cap.get(cv2.cv.CV_CAP_PROP_FRAME_COUNT)) - if time_range is None: - start_frame = 0 - end_frame = frame_count - else: - start_frame, end_frame = [fps * t for t in time_range] - - frame_count = end_frame - start_frame - print("Reading in " + file_name + "...") - for count in show_progress(list(range(start_frame, end_frame + 1))): - returned, frame = cap.read() - if not returned: - break - # b, g, r = cv2.split(frame) - # self.frames.append(cv2.merge([r, g, b])) - self.frames.append(frame) - cap.release() - - if freeze_last_frame and len(self.frames) > 0: - self.original_background = self.background = self.frames[-1] - - def apply_gaussian_blur(self, ksize=(5, 5), sigmaX=5): - self.frames = [ - cv2.GaussianBlur(frame, ksize, sigmaX) - for frame in self.frames - ] - - def apply_edge_detection(self, threshold1=50, threshold2=100): - edged_frames = [ - cv2.Canny(frame, threshold1, threshold2) - for frame in self.frames - ] - for index in range(len(self.frames)): - for i in range(3): - self.frames[index][:, :, i] = edged_frames[index] diff --git a/manimlib/scene/three_d_scene.py b/manimlib/scene/three_d_scene.py index 5cb847b8..2d29fff8 100644 --- a/manimlib/scene/three_d_scene.py +++ b/manimlib/scene/three_d_scene.py @@ -1,48 +1,18 @@ -from manimlib.animation.transform import ApplyMethod -from manimlib.constants import DEGREES -from manimlib.constants import PRODUCTION_QUALITY_CAMERA_CONFIG -from manimlib.mobject.coordinate_systems import ThreeDAxes -from manimlib.mobject.geometry import Line -from manimlib.mobject.three_dimensions import Sphere -from manimlib.mobject.types.vectorized_mobject import VGroup -from manimlib.mobject.types.vectorized_mobject import VectorizedPoint from manimlib.scene.scene import Scene -from manimlib.utils.config_ops import digest_config -from manimlib.utils.config_ops import merge_dicts_recursively -# TODO, these seem deprecated. - class ThreeDScene(Scene): CONFIG = { - "ambient_camera_rotation": None, - "default_angled_camera_orientation_kwargs": { - "phi": 70 * DEGREES, - "theta": -135 * DEGREES, + "camera_config": { + "samples": 4, } } - def set_camera_orientation(self, phi=None, theta=None, distance=None, gamma=None): - if phi is not None: - self.camera.set_phi(phi) - if theta is not None: - self.camera.set_theta(theta) - if distance is not None: - self.camera.set_distance(distance) - if gamma is not None: - self.camera.set_gamma(gamma) - def begin_ambient_camera_rotation(self, rate=0.02): - # TODO, use a ValueTracker for rate, so that it - # can begin and end smoothly - self.camera.theta_tracker.add_updater( - lambda m, dt: m.increment_value(rate * dt) - ) - self.add(self.camera.theta_tracker) + pass # TODO def stop_ambient_camera_rotation(self): - self.camera.theta_tracker.clear_updaters() - self.remove(self.camera.theta_tracker) + pass # TODO def move_camera(self, phi=None, @@ -50,133 +20,5 @@ class ThreeDScene(Scene): distance=None, gamma=None, frame_center=None, - added_anims=[], **kwargs): - anims = [] - value_tracker_pairs = [ - (phi, self.camera.phi_tracker), - (theta, self.camera.theta_tracker), - (distance, self.camera.distance_tracker), - (gamma, self.camera.gamma_tracker), - ] - for value, tracker in value_tracker_pairs: - if value is not None: - anims.append( - ApplyMethod(tracker.set_value, value, **kwargs) - ) - if frame_center is not None: - anims.append(ApplyMethod( - self.camera.frame_center.move_to, - frame_center - )) - - self.play(*anims + added_anims) - - def get_moving_mobjects(self, *animations): - moving_mobjects = Scene.get_moving_mobjects(self, *animations) - camera_mobjects = self.camera.get_value_trackers() - if any([cm in moving_mobjects for cm in camera_mobjects]): - return self.mobjects - return moving_mobjects - - def add_fixed_orientation_mobjects(self, *mobjects, **kwargs): - self.add(*mobjects) - self.camera.add_fixed_orientation_mobjects(*mobjects, **kwargs) - - def add_fixed_in_frame_mobjects(self, *mobjects): - self.add(*mobjects) - self.camera.add_fixed_in_frame_mobjects(*mobjects) - - def remove_fixed_orientation_mobjects(self, *mobjects): - self.camera.remove_fixed_orientation_mobjects(*mobjects) - - def remove_fixed_in_frame_mobjects(self, *mobjects): - self.camera.remove_fixed_in_frame_mobjects(*mobjects) - - ## - def set_to_default_angled_camera_orientation(self, **kwargs): - config = dict(self.default_camera_orientation_kwargs) - config.update(kwargs) - self.set_camera_orientation(**config) - - -class SpecialThreeDScene(ThreeDScene): - CONFIG = { - "cut_axes_at_radius": True, - "camera_config": { - "should_apply_shading": True, - "exponential_projection": True, - }, - "three_d_axes_config": { - "num_axis_pieces": 1, - "axis_config": { - "unit_size": 2, - "tick_frequency": 1, - "numbers_with_elongated_ticks": [0, 1, 2], - "stroke_width": 2, - } - }, - "sphere_config": { - "radius": 2, - "resolution": (24, 48), - }, - "default_angled_camera_position": { - "phi": 70 * DEGREES, - "theta": -110 * DEGREES, - }, - # When scene is extracted with -l flag, this - # configuration will override the above configuration. - "low_quality_config": { - "camera_config": { - "should_apply_shading": False, - }, - "three_d_axes_config": { - "num_axis_pieces": 1, - }, - "sphere_config": { - "resolution": (12, 24), - } - } - } - - def __init__(self, **kwargs): - digest_config(self, kwargs) - if self.camera_config["pixel_width"] == PRODUCTION_QUALITY_CAMERA_CONFIG["pixel_width"]: - config = {} - else: - config = self.low_quality_config - config = merge_dicts_recursively(config, kwargs) - ThreeDScene.__init__(self, **config) - - def get_axes(self): - axes = ThreeDAxes(**self.three_d_axes_config) - for axis in axes: - if self.cut_axes_at_radius: - p0 = axis.get_start() - p1 = axis.number_to_point(-1) - p2 = axis.number_to_point(1) - p3 = axis.get_end() - new_pieces = VGroup( - Line(p0, p1), Line(p1, p2), Line(p2, p3), - ) - for piece in new_pieces: - piece.shade_in_3d = True - new_pieces.match_style(axis.pieces) - axis.pieces.set_submobjects(new_pieces.submobjects) - for tick in axis.tick_marks: - tick.add(VectorizedPoint( - 1.5 * tick.get_center(), - )) - return axes - - def get_sphere(self, **kwargs): - config = merge_dicts_recursively(self.sphere_config, kwargs) - return Sphere(**config) - - def get_default_camera_position(self): - return self.default_angled_camera_position - - def set_camera_to_default_position(self): - self.set_camera_orientation( - **self.default_angled_camera_position - ) + pass # TODO diff --git a/manimlib/scene/vector_space_scene.py b/manimlib/scene/vector_space_scene.py index 902ad3bd..54651808 100644 --- a/manimlib/scene/vector_space_scene.py +++ b/manimlib/scene/vector_space_scene.py @@ -20,8 +20,8 @@ from manimlib.mobject.matrix import Matrix from manimlib.mobject.matrix import VECTOR_LABEL_SCALE_FACTOR from manimlib.mobject.matrix import vector_coordinate_label from manimlib.mobject.mobject import Mobject -from manimlib.mobject.svg.tex_mobject import TexMobject -from manimlib.mobject.svg.tex_mobject import TextMobject +from manimlib.mobject.svg.tex_mobject import Tex +from manimlib.mobject.svg.tex_mobject import TexText from manimlib.mobject.types.vectorized_mobject import VGroup from manimlib.mobject.types.vectorized_mobject import VMobject from manimlib.scene.scene import Scene @@ -123,10 +123,10 @@ class VectorScene(Scene): rotate=False, color=None, label_scale_factor=VECTOR_LABEL_SCALE_FACTOR): - if not isinstance(label, TexMobject): + if not isinstance(label, Tex): if len(label) == 1: label = "\\vec{\\textbf{%s}}" % label - label = TexMobject(label) + label = Tex(label) if color is None: color = vector.get_color() label.set_color(color) @@ -288,7 +288,7 @@ class LinearTransformationScene(VectorScene): "background_plane_kwargs": { "color": GREY, "axis_config": { - "stroke_color": LIGHT_GREY, + "stroke_color": GREY_B, }, "axis_config": { "color": GREY, @@ -408,7 +408,7 @@ class LinearTransformationScene(VectorScene): else: label_mob.target_text = "%s(%s)" % ( transformation_name, - label_mob.get_tex_string() + label_mob.get_tex() ) label_mob.vector = vector label_mob.kwargs = kwargs @@ -419,7 +419,7 @@ class LinearTransformationScene(VectorScene): def add_title(self, title, scale_factor=1.5, animate=False): if not isinstance(title, Mobject): - title = TextMobject(title).scale(scale_factor) + title = TexText(title).scale(scale_factor) title.to_edge(UP) title.add_background_rectangle() if animate: @@ -461,7 +461,7 @@ class LinearTransformationScene(VectorScene): v.target = Vector(func(v.get_end()), color=v.get_color()) norm = get_norm(v.target.get_end()) if norm < 0.1: - v.target.get_tip().scale_in_place(norm) + v.target.get_tip().scale(norm) return self.get_piece_movement(self.moving_vectors) def get_transformable_label_movement(self): diff --git a/manimlib/scene/zoomed_scene.py b/manimlib/scene/zoomed_scene.py deleted file mode 100644 index e5891c30..00000000 --- a/manimlib/scene/zoomed_scene.py +++ /dev/null @@ -1,90 +0,0 @@ -from manimlib.animation.transform import ApplyMethod -from manimlib.camera.moving_camera import MovingCamera -from manimlib.camera.multi_camera import MultiCamera -from manimlib.constants import * -from manimlib.scene.moving_camera_scene import MovingCameraScene -from manimlib.utils.simple_functions import fdiv - -# Note, any scenes from old videos using ZoomedScene will almost certainly -# break, as it was restructured. - - -# TODO, this scene no longer works -class ZoomedScene(MovingCameraScene): - CONFIG = { - "camera_class": MultiCamera, - "zoomed_display_height": 3, - "zoomed_display_width": 3, - "zoomed_display_center": None, - "zoomed_display_corner": UP + RIGHT, - "zoomed_display_corner_buff": DEFAULT_MOBJECT_TO_EDGE_BUFFER, - "zoomed_camera_config": { - "default_frame_stroke_width": 2, - "background_opacity": 1, - }, - "zoomed_camera_image_mobject_config": {}, - "zoomed_camera_frame_starting_position": ORIGIN, - "zoom_factor": 0.15, - "image_frame_stroke_width": 3, - "zoom_activated": False, - } - - def setup(self): - MovingCameraScene.setup(self) - # Initialize camera and display - zoomed_camera = MovingCamera(**self.zoomed_camera_config) - zoomed_display = ImageMobjectFromCamera( - zoomed_camera, **self.zoomed_camera_image_mobject_config - ) - zoomed_display.add_display_frame() - for mob in zoomed_camera.frame, zoomed_display: - mob.stretch_to_fit_height(self.zoomed_display_height) - mob.stretch_to_fit_width(self.zoomed_display_width) - zoomed_camera.frame.scale(self.zoom_factor) - - # Position camera and display - zoomed_camera.frame.move_to(self.zoomed_camera_frame_starting_position) - if self.zoomed_display_center is not None: - zoomed_display.move_to(self.zoomed_display_center) - else: - zoomed_display.to_corner( - self.zoomed_display_corner, - buff=self.zoomed_display_corner_buff - ) - - self.zoomed_camera = zoomed_camera - self.zoomed_display = zoomed_display - - def activate_zooming(self, animate=False): - self.zoom_activated = True - self.camera.add_image_mobject_from_camera(self.zoomed_display) - if animate: - self.play(self.get_zoom_in_animation()) - self.play(self.get_zoomed_display_pop_out_animation()) - self.add_foreground_mobjects( - self.zoomed_camera.frame, - self.zoomed_display, - ) - - def get_zoom_in_animation(self, run_time=2, **kwargs): - frame = self.zoomed_camera.frame - full_frame_height = self.camera.get_frame_height() - full_frame_width = self.camera.get_frame_width() - frame.save_state() - frame.stretch_to_fit_width(full_frame_width) - frame.stretch_to_fit_height(full_frame_height) - frame.center() - frame.set_stroke(width=0) - return ApplyMethod(frame.restore, run_time=run_time, **kwargs) - - def get_zoomed_display_pop_out_animation(self, **kwargs): - display = self.zoomed_display - display.save_state(use_deepcopy=True) - display.replace(self.zoomed_camera.frame, stretch=True) - return ApplyMethod(display.restore) - - def get_zoom_factor(self): - return fdiv( - self.zoomed_camera.frame.get_height(), - self.zoomed_display.get_height() - ) diff --git a/manimlib/shader_wrapper.py b/manimlib/shader_wrapper.py new file mode 100644 index 00000000..87f1ccc6 --- /dev/null +++ b/manimlib/shader_wrapper.py @@ -0,0 +1,165 @@ +import os +import re +import moderngl +import numpy as np +import copy + +from manimlib.utils.directories import get_shader_dir +from manimlib.utils.file_ops import find_file + +# Mobjects that should be rendered with +# the same shader will be organized and +# clumped together based on keeping track +# of a dict holding all the relevant information +# to that shader + + +class ShaderWrapper(object): + def __init__(self, + vert_data=None, + vert_indices=None, + shader_folder=None, + uniforms=None, # A dictionary mapping names of uniform variables + texture_paths=None, # A dictionary mapping names to filepaths for textures. + depth_test=False, + render_primitive=moderngl.TRIANGLE_STRIP, + ): + self.vert_data = vert_data + self.vert_indices = vert_indices + self.vert_attributes = vert_data.dtype.names + self.shader_folder = shader_folder + self.uniforms = uniforms or dict() + self.texture_paths = texture_paths or dict() + self.depth_test = depth_test + self.render_primitive = str(render_primitive) + self.init_program_code() + self.refresh_id() + + def copy(self): + result = copy.copy(self) + result.vert_data = np.array(self.vert_data) + if result.vert_indices is not None: + result.vert_indices = np.array(self.vert_indices) + if self.uniforms: + result.uniforms = dict(self.uniforms) + if self.texture_paths: + result.texture_paths = dict(self.texture_paths) + return result + + def is_valid(self): + return all([ + self.vert_data is not None, + self.program_code["vertex_shader"] is not None, + self.program_code["fragment_shader"] is not None, + ]) + + def get_id(self): + return self.id + + def get_program_id(self): + return self.program_id + + def create_id(self): + # A unique id for a shader + return "|".join(map(str, [ + self.program_id, + self.uniforms, + self.texture_paths, + self.depth_test, + self.render_primitive, + ])) + + def refresh_id(self): + self.program_id = self.create_program_id() + self.id = self.create_id() + + def create_program_id(self): + return hash("".join(( + self.program_code[f"{name}_shader"] or "" + for name in ("vertex", "geometry", "fragment") + ))) + + def init_program_code(self): + def get_code(name): + return get_shader_code_from_file( + os.path.join(self.shader_folder, f"{name}.glsl") + ) + + self.program_code = { + "vertex_shader": get_code("vert"), + "geometry_shader": get_code("geom"), + "fragment_shader": get_code("frag"), + } + + def get_program_code(self): + return self.program_code + + def replace_code(self, old, new): + code_map = self.program_code + for (name, code) in code_map.items(): + if code_map[name] is None: + continue + code_map[name] = re.sub(old, new, code_map[name]) + self.refresh_id() + + def combine_with(self, *shader_wrappers): + # Assume they are of the same type + if len(shader_wrappers) == 0: + return + if self.vert_indices is not None: + num_verts = len(self.vert_data) + indices_list = [self.vert_indices] + data_list = [self.vert_data] + for sw in shader_wrappers: + indices_list.append(sw.vert_indices + num_verts) + data_list.append(sw.vert_data) + num_verts += len(sw.vert_data) + self.vert_indices = np.hstack(indices_list) + self.vert_data = np.hstack(data_list) + else: + self.vert_data = np.hstack([self.vert_data, *[sw.vert_data for sw in shader_wrappers]]) + return self + + +# For caching +filename_to_code_map = {} + + +def get_shader_code_from_file(filename): + if not filename: + return None + if filename in filename_to_code_map: + return filename_to_code_map[filename] + + try: + filepath = find_file( + filename, + directories=[get_shader_dir(), "/"], + extensions=[], + ) + except IOError: + return None + + with open(filepath, "r") as f: + result = f.read() + + # To share functionality between shaders, some functions are read in + # from other files an inserted into the relevant strings before + # passing to ctx.program for compiling + # Replace "#INSERT " lines with relevant code + insertions = re.findall(r"^#INSERT .*\.glsl$", result, flags=re.MULTILINE) + for line in insertions: + inserted_code = get_shader_code_from_file( + os.path.join("inserts", line.replace("#INSERT ", "")) + ) + result = result.replace(line, inserted_code) + filename_to_code_map[filename] = result + return result + + +def get_colormap_code(rgb_list): + data = ",".join( + "vec3({}, {}, {})".format(*rgb) + for rgb in rgb_list + ) + return f"vec3[{len(rgb_list)}]({data})" diff --git a/manimlib/shaders/add_light.glsl b/manimlib/shaders/add_light.glsl deleted file mode 100644 index 1c1b61cb..00000000 --- a/manimlib/shaders/add_light.glsl +++ /dev/null @@ -1,23 +0,0 @@ -vec4 add_light(vec4 raw_color, vec3 point, vec3 unit_normal, vec3 light_coords, float gloss){ - if(gloss == 0.0) return raw_color; - - // TODO, do we actually want this? For VMobjects its nice to just choose whichever unit normal - // is pointing towards the camera. - if(unit_normal.z < 0){ - unit_normal *= -1; - } - - float camera_distance = 6; // TODO, read this in as a uniform? - // Assume everything has already been rotated such that camera is in the z-direction - vec3 to_camera = vec3(0, 0, camera_distance) - point; - vec3 to_light = light_coords - point; - vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal); - float dot_prod = dot(normalize(light_reflection), normalize(to_camera)); - // float shine = gloss * exp(-3 * pow(1 - dot_prod, 2)); - float shine = 2 * gloss * exp(-1 * pow(1 - dot_prod, 2)); - float dp2 = dot(normalize(to_light), unit_normal); - return vec4( - mix(0.5, 1.0, max(dp2, 0)) * mix(raw_color.rgb, vec3(1.0), shine), - raw_color.a - ); -} \ No newline at end of file diff --git a/manimlib/shaders/get_gl_Position.glsl b/manimlib/shaders/get_gl_Position.glsl deleted file mode 100644 index 071ebb52..00000000 --- a/manimlib/shaders/get_gl_Position.glsl +++ /dev/null @@ -1,14 +0,0 @@ -// Assumes the following uniforms exist in the surrounding context: -// uniform float aspect_ratio; -// uniform float focal_distance; - -vec4 get_gl_Position(vec3 point){ - point.x /= aspect_ratio; - point.z /= focal_distance; - point.xy /= max(1 - point.z, 0); - // Todo, does this discontinuity add weirdness? Theoretically, by this point, - // the z-coordiante of gl_Position only matter for z-indexing. The reason - // for thie line is to avoid agressive clipping of distant points. - if(point.z < 0) point.z *= 0.1; - return vec4(point.xy, -point.z, 1); -} \ No newline at end of file diff --git a/manimlib/shaders/image_frag.glsl b/manimlib/shaders/image/frag.glsl similarity index 100% rename from manimlib/shaders/image_frag.glsl rename to manimlib/shaders/image/frag.glsl diff --git a/manimlib/shaders/image_vert.glsl b/manimlib/shaders/image/vert.glsl similarity index 76% rename from manimlib/shaders/image_vert.glsl rename to manimlib/shaders/image/vert.glsl index f7e48932..febc0af1 100644 --- a/manimlib/shaders/image_vert.glsl +++ b/manimlib/shaders/image/vert.glsl @@ -1,9 +1,6 @@ #version 330 -uniform float aspect_ratio; -uniform float anti_alias_width; -uniform mat4 to_screen_space; -uniform float focal_distance; +#INSERT camera_uniform_declarations.glsl uniform sampler2D Texture; diff --git a/manimlib/shaders/inserts/NOTE.md b/manimlib/shaders/inserts/NOTE.md new file mode 100644 index 00000000..45f0c138 --- /dev/null +++ b/manimlib/shaders/inserts/NOTE.md @@ -0,0 +1,7 @@ +There seems to be no analog to #include in C++ for OpenGL shaders. While there are other options for sharing code between shaders, a lot of them aren't great, especially if the goal is to have all the logic for which specific bits of code to share handled in the shader file itself. So the way manim currently works is to replace any line which looks like + +#INSERT + +with the code from one of the files in this folder. + +The functions in this file often include reference to uniforms which are assumed to be part of the surrounding context into which they are inserted. diff --git a/manimlib/shaders/inserts/add_light.glsl b/manimlib/shaders/inserts/add_light.glsl new file mode 100644 index 00000000..8ef7cc11 --- /dev/null +++ b/manimlib/shaders/inserts/add_light.glsl @@ -0,0 +1,43 @@ +///// INSERT COLOR_MAP FUNCTION HERE ///// + +vec4 add_light(vec4 color, + vec3 point, + vec3 unit_normal, + vec3 light_coords, + float gloss, + float shadow){ + ///// INSERT COLOR FUNCTION HERE ///// + // The line above may be replaced by arbitrary code snippets, as per + // the method Mobject.set_color_by_code + if(gloss == 0.0 && shadow == 0.0) return color; + + // TODO, do we actually want this? It effectively treats surfaces as two-sided + if(unit_normal.z < 0){ + unit_normal *= -1; + } + + // TODO, read this in as a uniform? + float camera_distance = 6; + // Assume everything has already been rotated such that camera is in the z-direction + vec3 to_camera = vec3(0, 0, camera_distance) - point; + vec3 to_light = light_coords - point; + vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal); + float dot_prod = dot(normalize(light_reflection), normalize(to_camera)); + float shine = gloss * exp(-3 * pow(1 - dot_prod, 2)); + float dp2 = dot(normalize(to_light), unit_normal); + float darkening = mix(1, max(dp2, 0), shadow); + return vec4( + darkening * mix(color.rgb, vec3(1.0), shine), + color.a + ); +} + +vec4 finalize_color(vec4 color, + vec3 point, + vec3 unit_normal, + vec3 light_coords, + float gloss, + float shadow){ + // Put insertion here instead + return add_light(color, point, unit_normal, light_coords, gloss, shadow); +} \ No newline at end of file diff --git a/manimlib/shaders/inserts/camera_uniform_declarations.glsl b/manimlib/shaders/inserts/camera_uniform_declarations.glsl new file mode 100644 index 00000000..b40b1b90 --- /dev/null +++ b/manimlib/shaders/inserts/camera_uniform_declarations.glsl @@ -0,0 +1,6 @@ +uniform vec2 frame_shape; +uniform float anti_alias_width; +uniform vec3 camera_center; +uniform mat3 camera_rotation; +uniform float is_fixed_in_frame; +uniform float focal_distance; \ No newline at end of file diff --git a/manimlib/shaders/inserts/finalize_color.glsl b/manimlib/shaders/inserts/finalize_color.glsl new file mode 100644 index 00000000..0664deb0 --- /dev/null +++ b/manimlib/shaders/inserts/finalize_color.glsl @@ -0,0 +1,51 @@ +vec3 float_to_color(float value, float min_val, float max_val, vec3[9] colormap_data){ + float alpha = clamp((value - min_val) / (max_val - min_val), 0.0, 1.0); + int disc_alpha = min(int(alpha * 8), 7); + return mix( + colormap_data[disc_alpha], + colormap_data[disc_alpha + 1], + 8.0 * alpha - disc_alpha + ); +} + + +vec4 add_light(vec4 color, + vec3 point, + vec3 unit_normal, + vec3 light_coords, + float gloss, + float shadow){ + if(gloss == 0.0 && shadow == 0.0) return color; + + // TODO, do we actually want this? It effectively treats surfaces as two-sided + if(unit_normal.z < 0){ + unit_normal *= -1; + } + + // TODO, read this in as a uniform? + float camera_distance = 6; + // Assume everything has already been rotated such that camera is in the z-direction + vec3 to_camera = vec3(0, 0, camera_distance) - point; + vec3 to_light = light_coords - point; + vec3 light_reflection = -to_light + 2 * unit_normal * dot(to_light, unit_normal); + float dot_prod = dot(normalize(light_reflection), normalize(to_camera)); + float shine = gloss * exp(-3 * pow(1 - dot_prod, 2)); + float dp2 = dot(normalize(to_light), unit_normal); + float darkening = mix(1, max(dp2, 0), shadow); + return vec4( + darkening * mix(color.rgb, vec3(1.0), shine), + color.a + ); +} + +vec4 finalize_color(vec4 color, + vec3 point, + vec3 unit_normal, + vec3 light_coords, + float gloss, + float shadow){ + ///// INSERT COLOR FUNCTION HERE ///// + // The line above may be replaced by arbitrary code snippets, as per + // the method Mobject.set_color_by_code + return add_light(color, point, unit_normal, light_coords, gloss, shadow); +} \ No newline at end of file diff --git a/manimlib/shaders/inserts/get_gl_Position.glsl b/manimlib/shaders/inserts/get_gl_Position.glsl new file mode 100644 index 00000000..2f9d8934 --- /dev/null +++ b/manimlib/shaders/inserts/get_gl_Position.glsl @@ -0,0 +1,31 @@ +// Assumes the following uniforms exist in the surrounding context: +// uniform vec2 frame_shape; +// uniform float focal_distance; +// uniform float is_fixed_in_frame; + +const vec2 DEFAULT_FRAME_SHAPE = vec2(8.0 * 16.0 / 9.0, 8.0); + +float perspective_scale_factor(float z, float focal_distance){ + return max(0.0, focal_distance / (focal_distance - z)); +} + + +vec4 get_gl_Position(vec3 point){ + vec4 result = vec4(point, 1.0); + if(!bool(is_fixed_in_frame)){ + result.x *= 2.0 / frame_shape.x; + result.y *= 2.0 / frame_shape.y; + float psf = perspective_scale_factor(result.z, focal_distance); + if (psf > 0){ + result.xy *= psf; + // TODO, what's the better way to do this? + // This is to keep vertices too far out of frame from getting cut. + result.z *= 0.01; + } + } else{ + result.x *= 2.0 / DEFAULT_FRAME_SHAPE.x; + result.y *= 2.0 / DEFAULT_FRAME_SHAPE.y; + } + result.z *= -1; + return result; +} \ No newline at end of file diff --git a/manimlib/shaders/inserts/get_rotated_surface_unit_normal_vector.glsl b/manimlib/shaders/inserts/get_rotated_surface_unit_normal_vector.glsl new file mode 100644 index 00000000..a9d637fe --- /dev/null +++ b/manimlib/shaders/inserts/get_rotated_surface_unit_normal_vector.glsl @@ -0,0 +1,16 @@ +// Assumes the following uniforms exist in the surrounding context: +// uniform vec3 camera_center; +// uniform mat3 camera_rotation; + +vec3 get_rotated_surface_unit_normal_vector(vec3 point, vec3 du_point, vec3 dv_point){ + vec3 cp = cross( + (du_point - point), + (dv_point - point) + ); + if(length(cp) == 0){ + // Instead choose a normal to just dv_point - point in the direction of point + vec3 v2 = dv_point - point; + cp = cross(cross(v2, point), v2); + } + return normalize(rotate_point_into_frame(cp)); +} \ No newline at end of file diff --git a/manimlib/shaders/get_unit_normal.glsl b/manimlib/shaders/inserts/get_unit_normal.glsl similarity index 94% rename from manimlib/shaders/get_unit_normal.glsl rename to manimlib/shaders/inserts/get_unit_normal.glsl index ed1b975d..d755d458 100644 --- a/manimlib/shaders/get_unit_normal.glsl +++ b/manimlib/shaders/inserts/get_unit_normal.glsl @@ -13,7 +13,7 @@ vec3 get_unit_normal(in vec3[3] points){ if(new_cp_norm < tol){ // We only come here if all three points line up // on the z-axis. - return vec3(0.0, 1.0, 0.0); + return vec3(0.0, -1.0, 0.0); // return k_hat; } return new_cp / new_cp_norm; diff --git a/manimlib/shaders/inserts/position_point_into_frame.glsl b/manimlib/shaders/inserts/position_point_into_frame.glsl new file mode 100644 index 00000000..4cef7e38 --- /dev/null +++ b/manimlib/shaders/inserts/position_point_into_frame.glsl @@ -0,0 +1,19 @@ +// Assumes the following uniforms exist in the surrounding context: +// uniform float is_fixed_in_frame; +// uniform vec3 camera_center; +// uniform mat3 camera_rotation; + +vec3 rotate_point_into_frame(vec3 point){ + if(bool(is_fixed_in_frame)){ + return point; + } + return camera_rotation * point; +} + + +vec3 position_point_into_frame(vec3 point){ + if(bool(is_fixed_in_frame)){ + return point; + } + return rotate_point_into_frame(point - camera_center); +} diff --git a/manimlib/shaders/quadratic_bezier_distance.glsl b/manimlib/shaders/inserts/quadratic_bezier_distance.glsl similarity index 96% rename from manimlib/shaders/quadratic_bezier_distance.glsl rename to manimlib/shaders/inserts/quadratic_bezier_distance.glsl index d7fd8ccd..173e4aec 100644 --- a/manimlib/shaders/quadratic_bezier_distance.glsl +++ b/manimlib/shaders/inserts/quadratic_bezier_distance.glsl @@ -1,6 +1,3 @@ -// This file is not a shader, it's just a set of -// functions meant to be inserted into other shaders. - // Must be inserted in a context with a definition for modify_distance_for_endpoints // All of this is with respect to a curve that's been rotated/scaled diff --git a/manimlib/shaders/quadratic_bezier_geometry_functions.glsl b/manimlib/shaders/inserts/quadratic_bezier_geometry_functions.glsl similarity index 81% rename from manimlib/shaders/quadratic_bezier_geometry_functions.glsl rename to manimlib/shaders/inserts/quadratic_bezier_geometry_functions.glsl index 25ab9bd4..e27402d1 100644 --- a/manimlib/shaders/quadratic_bezier_geometry_functions.glsl +++ b/manimlib/shaders/inserts/quadratic_bezier_geometry_functions.glsl @@ -1,10 +1,27 @@ -// This file is not a shader, it's just a set of -// functions meant to be inserted into other shaders. - float cross2d(vec2 v, vec2 w){ return v.x * w.y - w.x * v.y; } + +mat3 get_xy_to_uv(vec2 b0, vec2 b1){ + mat3 shift = mat3( + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + -b0.x, -b0.y, 1.0 + ); + + float sf = length(b1 - b0); + vec2 I = (b1 - b0) / sf; + vec2 J = vec2(-I.y, I.x); + mat3 rotate = mat3( + I.x, J.x, 0.0, + I.y, J.y, 0.0, + 0.0, 0.0, 1.0 + ); + return (1 / sf) * rotate * shift; +} + + // Orthogonal matrix to convert to a uv space defined so that // b0 goes to [0, 0] and b1 goes to [1, 0] mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 unit_normal){ @@ -22,10 +39,10 @@ mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 unit_normal){ // Transpose (hence inverse) of matrix taking // i-hat to I, k-hat to unit_normal, and j-hat to their cross mat4 rotate = mat4( - I.x, J.x, K.x, 0, - I.y, J.y, K.y, 0, - I.z, J.z, K.z, 0, - 0, 0, 0 , 1 + I.x, J.x, K.x, 0.0, + I.y, J.y, K.y, 0.0, + I.z, J.z, K.z, 0.0, + 0.0, 0.0, 0.0, 1.0 ); return (1 / scale_factor) * rotate * shift; } @@ -39,7 +56,7 @@ mat4 get_xyz_to_uv(vec3 b0, vec3 b1, vec3 unit_normal){ // float get_reduced_control_points(vec3 b0, vec3 b1, vec3 b2, out vec3 new_points[3]){ float get_reduced_control_points(in vec3 points[3], out vec3 new_points[3]){ float length_threshold = 1e-6; - float angle_threshold = 1e-3; + float angle_threshold = 5e-2; vec3 p0 = points[0]; vec3 p1 = points[1]; diff --git a/manimlib/shaders/position_point_into_frame.glsl b/manimlib/shaders/position_point_into_frame.glsl deleted file mode 100644 index 91818735..00000000 --- a/manimlib/shaders/position_point_into_frame.glsl +++ /dev/null @@ -1,9 +0,0 @@ -// Must be used in an environment with the following uniforms: -// uniform mat4 to_screen_space; -// uniform float focal_distance; - -vec3 position_point_into_frame(vec3 point){ - // Apply the pre-computed to_screen_space matrix. - vec4 new_point = to_screen_space * vec4(point, 1); - return new_point.xyz; -} diff --git a/manimlib/shaders/quadratic_bezier_fill_frag.glsl b/manimlib/shaders/quadratic_bezier_fill/frag.glsl similarity index 70% rename from manimlib/shaders/quadratic_bezier_fill_frag.glsl rename to manimlib/shaders/quadratic_bezier_fill/frag.glsl index 76b934db..b2a1c82a 100644 --- a/manimlib/shaders/quadratic_bezier_fill_frag.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/frag.glsl @@ -1,19 +1,16 @@ #version 330 -uniform vec3 light_source_position; -uniform mat4 to_screen_space; +#INSERT camera_uniform_declarations.glsl in vec4 color; in float fill_all; // Either 0 or 1e in float uv_anti_alias_width; in vec3 xyz_coords; -in vec3 global_unit_normal; in float orientation; in vec2 uv_coords; in vec2 uv_b2; in float bezier_degree; -in float gloss; out vec4 frag_color; @@ -22,11 +19,7 @@ float modify_distance_for_endpoints(vec2 p, float dist, float t){ return dist; } -// To my knowledge, there is no notion of #include for shaders, -// so to share functionality between this and others, the caller -// replaces this line with the contents of quadratic_bezier_sdf.glsl #INSERT quadratic_bezier_distance.glsl -#INSERT add_light.glsl float sdf(){ @@ -55,19 +48,19 @@ float sdf(){ ); vec2 p = to_simple_space * uv_coords; // Sign takes care of whether we should be filling the inside or outside of curve. - float sn = orientation * sign(v2); - float Fp = sn * (p.x * p.x - p.y); - vec2 grad = vec2( - -2 * p.x * v2, // del C / del u - 4 * v2 - 4 * p.x * (2 - u2) // del C / del v - ); - return Fp / length(grad); + float sgn = orientation * sign(v2); + float Fp = (p.x * p.x - p.y); + if(sgn * Fp < 0){ + return 0.0; + }else{ + return min_dist_to_curve(uv_coords, uv_b2, bezier_degree); + } } void main() { if (color.a == 0) discard; - frag_color = add_light(color, xyz_coords, global_unit_normal, light_source_position, gloss); + frag_color = color; if (fill_all == 1.0) return; frag_color.a *= smoothstep(1, 0, sdf() / uv_anti_alias_width); } diff --git a/manimlib/shaders/quadratic_bezier_fill_geom.glsl b/manimlib/shaders/quadratic_bezier_fill/geom.glsl similarity index 77% rename from manimlib/shaders/quadratic_bezier_fill_geom.glsl rename to manimlib/shaders/quadratic_bezier_fill/geom.glsl index 8d0da6f1..4fd9245f 100644 --- a/manimlib/shaders/quadratic_bezier_fill_geom.glsl +++ b/manimlib/shaders/quadratic_bezier_fill/geom.glsl @@ -4,41 +4,49 @@ layout (triangles) in; layout (triangle_strip, max_vertices = 5) out; uniform float anti_alias_width; + // Needed for get_gl_Position -uniform float aspect_ratio; +uniform vec2 frame_shape; uniform float focal_distance; +uniform float is_fixed_in_frame; +// Needed for finalize_color +uniform vec3 light_source_position; +uniform float gloss; +uniform float shadow; in vec3 bp[3]; in vec3 v_global_unit_normal[3]; in vec4 v_color[3]; -in float v_fill_all[3]; -in float v_gloss[3]; +in float v_vert_index[3]; out vec4 color; -out float gloss; out float fill_all; out float uv_anti_alias_width; out vec3 xyz_coords; -out vec3 global_unit_normal; out float orientation; // uv space is where b0 = (0, 0), b1 = (1, 0), and transform is orthogonal out vec2 uv_coords; out vec2 uv_b2; out float bezier_degree; -// To my knowledge, there is no notion of #include for shaders, -// so to share functionality between this and others, the caller -// in manim replaces this line with the contents of named file + +// Analog of import for manim only #INSERT quadratic_bezier_geometry_functions.glsl #INSERT get_gl_Position.glsl #INSERT get_unit_normal.glsl +#INSERT finalize_color.glsl void emit_vertex_wrapper(vec3 point, int index){ - color = v_color[index]; - gloss = v_gloss[index]; - global_unit_normal = v_global_unit_normal[index]; + color = finalize_color( + v_color[index], + point, + v_global_unit_normal[index], + light_source_position, + gloss, + shadow + ); xyz_coords = point; gl_Position = get_gl_Position(xyz_coords); EmitVertex(); @@ -64,10 +72,10 @@ void emit_pentagon(vec3[3] points, vec3 normal){ vec3 p0_perp = cross(t01, normal); vec3 p2_perp = cross(t12, normal); - bool fill_in = orientation > 0; + bool fill_inside = orientation > 0; float aaw = anti_alias_width; vec3 corners[5]; - if(fill_in){ + if(fill_inside){ // Note, straight lines will also fall into this case, and since p0_perp and p2_perp // will point to the right of the curve, it's just what we want corners = vec3[5]( @@ -102,17 +110,22 @@ void emit_pentagon(vec3[3] points, vec3 normal){ void main(){ - fill_all = v_fill_all[0]; - vec3 local_unit_normal = get_unit_normal(vec3[3](bp[0], bp[1], bp[2])); - orientation = sign(dot(v_global_unit_normal[0], local_unit_normal)); + // If vert indices are sequential, don't fill all + fill_all = float( + (v_vert_index[1] - v_vert_index[0]) != 1.0 || + (v_vert_index[2] - v_vert_index[1]) != 1.0 + ); - if(fill_all == 1){ + if(fill_all == 1.0){ emit_simple_triangle(); return; } vec3 new_bp[3]; bezier_degree = get_reduced_control_points(vec3[3](bp[0], bp[1], bp[2]), new_bp); + vec3 local_unit_normal = get_unit_normal(new_bp); + orientation = sign(dot(v_global_unit_normal[0], local_unit_normal)); + if(bezier_degree >= 1){ emit_pentagon(new_bp, local_unit_normal); } diff --git a/manimlib/shaders/quadratic_bezier_fill/vert.glsl b/manimlib/shaders/quadratic_bezier_fill/vert.glsl new file mode 100644 index 00000000..dab9d256 --- /dev/null +++ b/manimlib/shaders/quadratic_bezier_fill/vert.glsl @@ -0,0 +1,23 @@ +#version 330 + +#INSERT camera_uniform_declarations.glsl + +in vec3 point; +in vec3 unit_normal; +in vec4 color; +in float vert_index; + +out vec3 bp; // Bezier control point +out vec3 v_global_unit_normal; +out vec4 v_color; +out float v_vert_index; + +// Analog of import for manim only +#INSERT position_point_into_frame.glsl + +void main(){ + bp = position_point_into_frame(point); + v_global_unit_normal = rotate_point_into_frame(unit_normal); + v_color = color; + v_vert_index = vert_index; +} \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_fill_vert.glsl b/manimlib/shaders/quadratic_bezier_fill_vert.glsl deleted file mode 100644 index 9f7ab752..00000000 --- a/manimlib/shaders/quadratic_bezier_fill_vert.glsl +++ /dev/null @@ -1,28 +0,0 @@ -#version 330 - -uniform mat4 to_screen_space; - -in vec3 point; -in vec3 unit_normal; -in vec4 color; -in float fill_all; // Either 0 or 1 -in float gloss; - -out vec3 bp; // Bezier control point -out vec3 v_global_unit_normal; -out vec4 v_color; -out float v_fill_all; -out float v_gloss; - -// To my knowledge, there is no notion of #include for shaders, -// so to share functionality between this and others, the caller -// replaces this line with the contents of named file -#INSERT position_point_into_frame.glsl - -void main(){ - bp = position_point_into_frame(point); - v_global_unit_normal = normalize(position_point_into_frame(unit_normal)); - v_color = color; - v_fill_all = fill_all; - v_gloss = gloss; -} \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_stroke_frag.glsl b/manimlib/shaders/quadratic_bezier_stroke/frag.glsl similarity index 80% rename from manimlib/shaders/quadratic_bezier_stroke_frag.glsl rename to manimlib/shaders/quadratic_bezier_stroke/frag.glsl index 67c5c08c..bd7c281f 100644 --- a/manimlib/shaders/quadratic_bezier_stroke_frag.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/frag.glsl @@ -1,16 +1,12 @@ #version 330 -uniform mat4 to_screen_space; -uniform vec3 light_source_position; +#INSERT camera_uniform_declarations.glsl -in vec3 xyz_coords; -in vec3 global_unit_normal; in vec2 uv_coords; in vec2 uv_b2; in float uv_stroke_width; in vec4 color; -in float gloss; in float uv_anti_alias_width; in float has_prev; @@ -46,7 +42,9 @@ float modify_distance_for_endpoints(vec2 p, float dist, float t){ // Dist for intersection of two lines float bevel_d = max(abs(p.y), abs((rot * p).y)); // Dist for union of this intersection with the real curve - return min(dist, bevel_d); + // intersected with radius 2 away from curve to smooth out + // really sharp corners + return max(min(dist, bevel_d), dist / 2); } // Otherwise, start will be rounded off }else if(t == 1){ @@ -73,30 +71,23 @@ float modify_distance_for_endpoints(vec2 p, float dist, float t){ abs(cross2d(p - uv_b2, v21_unit)), abs(cross2d((rot * (p - uv_b2)), v21_unit)) ); - return min(dist, bevel_d); + return max(min(dist, bevel_d), dist / 2); } // Otherwise, end will be rounded off } return dist; } -// To my knowledge, there is no notion of #include for shaders, -// so to share functionality between this and others, the caller -// replaces this line with the contents of named file + #INSERT quadratic_bezier_distance.glsl -#INSERT add_light.glsl void main() { if (uv_stroke_width == 0) discard; - - // Add lighting if needed - frag_color = add_light(color, xyz_coords, global_unit_normal, light_source_position, gloss); - float dist_to_curve = min_dist_to_curve(uv_coords, uv_b2, bezier_degree); // An sdf for the region around the curve we wish to color. float signed_dist = abs(dist_to_curve) - 0.5 * uv_stroke_width; - frag_color.a *= smoothstep(0.5, -0.5, signed_dist / uv_anti_alias_width); - // frag_color.a += 0.3; + frag_color = color; + frag_color.a *= smoothstep(0.5, -0.5, signed_dist / uv_anti_alias_width); } \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_stroke/geom.glsl b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl new file mode 100644 index 00000000..8baea0f9 --- /dev/null +++ b/manimlib/shaders/quadratic_bezier_stroke/geom.glsl @@ -0,0 +1,272 @@ +#version 330 + +layout (triangles) in; +layout (triangle_strip, max_vertices = 5) out; + +// Needed for get_gl_Position +uniform vec2 frame_shape; +uniform float focal_distance; +uniform float is_fixed_in_frame; + +uniform float anti_alias_width; +uniform float flat_stroke; + +//Needed for lighting +uniform vec3 light_source_position; +uniform float joint_type; +uniform float gloss; +uniform float shadow; + +in vec3 bp[3]; +in vec3 prev_bp[3]; +in vec3 next_bp[3]; +in vec3 v_global_unit_normal[3]; + +in vec4 v_color[3]; +in float v_stroke_width[3]; + +out vec4 color; +out float uv_stroke_width; +out float uv_anti_alias_width; + +out float has_prev; +out float has_next; +out float bevel_start; +out float bevel_end; +out float angle_from_prev; +out float angle_to_next; + +out float bezier_degree; + +out vec2 uv_coords; +out vec2 uv_b2; + +// Codes for joint types +const float AUTO_JOINT = 0; +const float ROUND_JOINT = 1; +const float BEVEL_JOINT = 2; +const float MITER_JOINT = 3; +const float PI = 3.141592653; + + +#INSERT quadratic_bezier_geometry_functions.glsl +#INSERT get_gl_Position.glsl +#INSERT get_unit_normal.glsl +#INSERT finalize_color.glsl + + +void flatten_points(in vec3[3] points, out vec2[3] flat_points){ + for(int i = 0; i < 3; i++){ + float sf = perspective_scale_factor(points[i].z, focal_distance); + flat_points[i] = sf * points[i].xy; + } +} + + +float angle_between_vectors(vec2 v1, vec2 v2){ + float v1_norm = length(v1); + float v2_norm = length(v2); + if(v1_norm == 0 || v2_norm == 0) return 0.0; + float dp = dot(v1, v2) / (v1_norm * v2_norm); + float angle = acos(clamp(dp, -1.0, 1.0)); + float sn = sign(cross2d(v1, v2)); + return sn * angle; +} + + +bool find_intersection(vec2 p0, vec2 v0, vec2 p1, vec2 v1, out vec2 intersection){ + // Find the intersection of a line passing through + // p0 in the direction v0 and one passing through p1 in + // the direction p1. + // That is, find a solutoin to p0 + v0 * t = p1 + v1 * s + float det = -v0.x * v1.y + v1.x * v0.y; + if(det == 0) return false; + float t = cross2d(p0 - p1, v1) / det; + intersection = p0 + v0 * t; + return true; +} + + +void create_joint(float angle, vec2 unit_tan, float buff, + vec2 static_c0, out vec2 changing_c0, + vec2 static_c1, out vec2 changing_c1){ + float shift; + if(abs(angle) < 1e-3){ + // No joint + shift = 0; + }else if(joint_type == MITER_JOINT){ + shift = buff * (-1.0 - cos(angle)) / sin(angle); + }else{ + // For a Bevel joint + shift = buff * (1.0 - cos(angle)) / sin(angle); + } + changing_c0 = static_c0 - shift * unit_tan; + changing_c1 = static_c1 + shift * unit_tan; +} + + +// This function is responsible for finding the corners of +// a bounding region around the bezier curve, which can be +// emitted as a triangle fan +int get_corners(vec2 controls[3], int degree, float stroke_widths[3], out vec2 corners[5]){ + vec2 p0 = controls[0]; + vec2 p1 = controls[1]; + vec2 p2 = controls[2]; + + // Unit vectors for directions between control points + vec2 v10 = normalize(p0 - p1); + vec2 v12 = normalize(p2 - p1); + vec2 v01 = -v10; + vec2 v21 = -v12; + + vec2 p0_perp = vec2(-v01.y, v01.x); // Pointing to the left of the curve from p0 + vec2 p2_perp = vec2(-v12.y, v12.x); // Pointing to the left of the curve from p2 + + // aaw is the added width given around the polygon for antialiasing. + // In case the normal is faced away from (0, 0, 1), the vector to the + // camera, this is scaled up. + float aaw = anti_alias_width; + float buff0 = 0.5 * stroke_widths[0] + aaw; + float buff2 = 0.5 * stroke_widths[2] + aaw; + float aaw0 = (1 - has_prev) * aaw; + float aaw2 = (1 - has_next) * aaw; + + vec2 c0 = p0 - buff0 * p0_perp + aaw0 * v10; + vec2 c1 = p0 + buff0 * p0_perp + aaw0 * v10; + vec2 c2 = p2 + buff2 * p2_perp + aaw2 * v12; + vec2 c3 = p2 - buff2 * p2_perp + aaw2 * v12; + + // Account for previous and next control points + if(has_prev > 0) create_joint(angle_from_prev, v01, buff0, c0, c0, c1, c1); + if(has_next > 0) create_joint(angle_to_next, v21, buff2, c3, c3, c2, c2); + + // Linear case is the simplest + if(degree == 1){ + // The order of corners should be for a triangle_strip. Last entry is a dummy + corners = vec2[5](c0, c1, c3, c2, vec2(0.0)); + return 4; + } + // Otherwise, form a pentagon around the curve + float orientation = sign(cross2d(v01, v12)); // Positive for ccw curves + if(orientation > 0) corners = vec2[5](c0, c1, p1, c2, c3); + else corners = vec2[5](c1, c0, p1, c3, c2); + // Replace corner[2] with convex hull point accounting for stroke width + find_intersection(corners[0], v01, corners[4], v21, corners[2]); + return 5; +} + + +void set_adjascent_info(vec2 c0, vec2 tangent, + int degree, + vec2 adj[3], + out float bevel, + out float angle + ){ + bool linear_adj = (angle_between_vectors(adj[1] - adj[0], adj[2] - adj[1]) < 1e-3); + angle = angle_between_vectors(c0 - adj[1], tangent); + // Decide on joint type + bool one_linear = (degree == 1 || linear_adj); + bool should_bevel = ( + (joint_type == AUTO_JOINT && one_linear) || + joint_type == BEVEL_JOINT + ); + bevel = should_bevel ? 1.0 : 0.0; +} + + +void find_joint_info(vec2 controls[3], vec2 prev[3], vec2 next[3], int degree){ + float tol = 1e-6; + + // Made as floats not bools so they can be passed to the frag shader + has_prev = float(distance(prev[2], controls[0]) < tol); + has_next = float(distance(next[0], controls[2]) < tol); + + if(bool(has_prev)){ + vec2 tangent = controls[1] - controls[0]; + set_adjascent_info( + controls[0], tangent, degree, prev, + bevel_start, angle_from_prev + ); + } + if(bool(has_next)){ + vec2 tangent = controls[1] - controls[2]; + set_adjascent_info( + controls[2], tangent, degree, next, + bevel_end, angle_to_next + ); + angle_to_next *= -1; + } +} + + +void main() { + // Convert control points to a standard form if they are linear or null + vec3 controls[3]; + vec3 prev[3]; + vec3 next[3]; + bezier_degree = get_reduced_control_points(vec3[3](bp[0], bp[1], bp[2]), controls); + if(bezier_degree == 0.0) return; // Null curve + int degree = int(bezier_degree); + get_reduced_control_points(vec3[3](prev_bp[0], prev_bp[1], prev_bp[2]), prev); + get_reduced_control_points(vec3[3](next_bp[0], next_bp[1], next_bp[2]), next); + + + // Adjust stroke width based on distance from the camera + float scaled_strokes[3]; + for(int i = 0; i < 3; i++){ + float sf = perspective_scale_factor(controls[i].z, focal_distance); + if(bool(flat_stroke)){ + vec3 to_cam = normalize(vec3(0.0, 0.0, focal_distance) - controls[i]); + sf *= abs(dot(v_global_unit_normal[i], to_cam)); + } + scaled_strokes[i] = v_stroke_width[i] * sf; + } + + // Control points are projected to the xy plane before drawing, which in turn + // gets tranlated to a uv plane. The z-coordinate information will be remembered + // by what's sent out to gl_Position, and by how it affects the lighting and stroke width + vec2 flat_controls[3]; + vec2 flat_prev[3]; + vec2 flat_next[3]; + flatten_points(controls, flat_controls); + flatten_points(prev, flat_prev); + flatten_points(next, flat_next); + + find_joint_info(flat_controls, flat_prev, flat_next, degree); + + // Corners of a bounding region around curve + vec2 corners[5]; + int n_corners = get_corners(flat_controls, degree, scaled_strokes, corners); + + int index_map[5] = int[5](0, 0, 1, 2, 2); + if(n_corners == 4) index_map[2] = 2; + + // Find uv conversion matrix + mat3 xy_to_uv = get_xy_to_uv(flat_controls[0], flat_controls[1]); + float scale_factor = length(flat_controls[1] - flat_controls[0]); + uv_anti_alias_width = anti_alias_width / scale_factor; + uv_b2 = (xy_to_uv * vec3(flat_controls[2], 1.0)).xy; + + // Emit each corner + for(int i = 0; i < n_corners; i++){ + uv_coords = (xy_to_uv * vec3(corners[i], 1.0)).xy; + uv_stroke_width = scaled_strokes[index_map[i]] / scale_factor; + // Apply some lighting to the color before sending out. + // vec3 xyz_coords = vec3(corners[i], controls[index_map[i]].z); + vec3 xyz_coords = vec3(corners[i], controls[index_map[i]].z); + color = finalize_color( + v_color[index_map[i]], + xyz_coords, + v_global_unit_normal[index_map[i]], + light_source_position, + gloss, + shadow + ); + gl_Position = vec4( + get_gl_Position(vec3(corners[i], 0.0)).xy, + get_gl_Position(controls[index_map[i]]).zw + ); + EmitVertex(); + } + EndPrimitive(); +} \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_stroke_vert.glsl b/manimlib/shaders/quadratic_bezier_stroke/vert.glsl similarity index 53% rename from manimlib/shaders/quadratic_bezier_stroke_vert.glsl rename to manimlib/shaders/quadratic_bezier_stroke/vert.glsl index 74e4242c..ed702e4f 100644 --- a/manimlib/shaders/quadratic_bezier_stroke_vert.glsl +++ b/manimlib/shaders/quadratic_bezier_stroke/vert.glsl @@ -1,7 +1,6 @@ #version 330 -uniform mat4 to_screen_space; -uniform float focal_distance; +#INSERT camera_uniform_declarations.glsl in vec3 point; in vec3 prev_point; @@ -10,8 +9,6 @@ in vec3 unit_normal; in float stroke_width; in vec4 color; -in float joint_type; -in float gloss; // Bezier control point out vec3 bp; @@ -21,24 +18,17 @@ out vec3 v_global_unit_normal; out float v_stroke_width; out vec4 v_color; -out float v_joint_type; -out float v_gloss; -const float STROKE_WIDTH_CONVERSION = 0.0025; +const float STROKE_WIDTH_CONVERSION = 0.01; -// To my knowledge, there is no notion of #include for shaders, -// so to share functionality between this and others, the caller -// replaces this line with the contents of named file #INSERT position_point_into_frame.glsl void main(){ bp = position_point_into_frame(point); prev_bp = position_point_into_frame(prev_point); next_bp = position_point_into_frame(next_point); - v_global_unit_normal = normalize(position_point_into_frame(unit_normal)); + v_global_unit_normal = rotate_point_into_frame(unit_normal); v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width; v_color = color; - v_joint_type = joint_type; - v_gloss = gloss; } \ No newline at end of file diff --git a/manimlib/shaders/quadratic_bezier_stroke_geom.glsl b/manimlib/shaders/quadratic_bezier_stroke_geom.glsl deleted file mode 100644 index 2cf39a29..00000000 --- a/manimlib/shaders/quadratic_bezier_stroke_geom.glsl +++ /dev/null @@ -1,275 +0,0 @@ -#version 330 - -layout (triangles) in; -layout (triangle_strip, max_vertices = 5) out; - -// Needed for get_gl_Position -uniform float aspect_ratio; -uniform float focal_distance; -uniform float anti_alias_width; - -in vec3 bp[3]; -in vec3 prev_bp[3]; -in vec3 next_bp[3]; -in vec3 v_global_unit_normal[3]; - -in vec4 v_color[3]; -in float v_stroke_width[3]; -in float v_joint_type[3]; -in float v_gloss[3]; - -out vec4 color; -out float uv_stroke_width; -out float gloss; -out float uv_anti_alias_width; - -out float has_prev; -out float has_next; -out float bevel_start; -out float bevel_end; -out float angle_from_prev; -out float angle_to_next; - -out float bezier_degree; - -out vec3 xyz_coords; -out vec3 global_unit_normal; -out vec2 uv_coords; -out vec2 uv_b2; - -// Codes for joint types -const float AUTO_JOINT = 0; -const float ROUND_JOINT = 1; -const float BEVEL_JOINT = 2; -const float MITER_JOINT = 3; - - -// To my knowledge, there is no notion of #include for shaders, -// so to share functionality between this and others, the caller -// replaces this line with the contents of named file -#INSERT quadratic_bezier_geometry_functions.glsl -#INSERT get_gl_Position.glsl -#INSERT get_unit_normal.glsl - - -float get_aaw_scalar(vec3 normal){ - return min(abs(normal.z), 5); -} - - -float angle_between_vectors(vec3 v1, vec3 v2, vec3 normal){ - float v1_norm = length(v1); - float v2_norm = length(v2); - if(v1_norm == 0 || v2_norm == 0) return 0; - vec3 nv1 = v1 / v1_norm; - vec3 nv2 = v2 / v2_norm; - // float signed_area = clamp(dot(cross(nv1, nv2), normal), -1, 1); - // return asin(signed_area); - float unsigned_angle = acos(clamp(dot(nv1, nv2), -1, 1)); - float sn = sign(dot(cross(nv1, nv2), normal)); - return sn * unsigned_angle; -} - - -bool find_intersection(vec3 p0, vec3 v0, vec3 p1, vec3 v1, vec3 normal, out vec3 intersection){ - // Find the intersection of a line passing through - // p0 in the direction v0 and one passing through p1 in - // the direction p1. - // That is, find a solutoin to p0 + v0 * t = p1 + v1 * s - // float det = -v0.x * v1.y + v1.x * v0.y; - float det = dot(cross(v1, v0), normal); - if(det == 0){ - // intersection = p0; - return false; - } - float t = dot(cross(p0 - p1, v1), normal) / det; - intersection = p0 + v0 * t; - return true; -} - - -bool is_between(vec3 p, vec3 a, vec3 b){ - // Assumes three points fall on a line, returns whether - // or not p sits between a and b. - float d_pa = distance(p, a); - float d_pb = distance(p, b); - float d_ab = distance(a, b); - return (d_ab >= d_pa && d_ab >= d_pb); -} - - -// Tries to detect if one of the corners defined by the buffer around -// b0 and b2 should be modified to form a better convex hull -bool should_motify_corner(vec3 c, vec3 from_c, vec3 o1, vec3 o2, vec3 from_o, vec3 normal, float buff){ - vec3 int1; - vec3 int2; - find_intersection(c, from_c, o1, from_o, normal, int1); - find_intersection(c, from_c, o2, from_o, normal, int2); - return !is_between(int2, c + 1 * from_c * buff, int1); -} - - -void create_joint(float angle, vec3 unit_tan, float buff, float should_bevel, - vec3 static_c0, out vec3 changing_c0, - vec3 static_c1, out vec3 changing_c1){ - float shift; - float joint_type = v_joint_type[0]; - bool miter = ( - (joint_type == AUTO_JOINT && abs(angle) > 2.8 && should_bevel == 1) || - (joint_type == MITER_JOINT) - ); - if(abs(angle) < 1e-3){ - // No joint - shift = 0; - }else if(miter){ - shift = buff * (-1.0 - cos(angle)) / sin(angle); - }else{ - // For a Bevel joint - shift = buff * (1.0 - cos(angle)) / sin(angle); - } - changing_c0 = static_c0 - shift * unit_tan; - changing_c1 = static_c1 + shift * unit_tan; -} - - -// This function is responsible for finding the corners of -// a bounding region around the bezier curve, which can be -// emitted as a triangle fan -int get_corners(vec3 controls[3], vec3 normal, int degree, out vec3 corners[5]){ - vec3 p0 = controls[0]; - vec3 p1 = controls[1]; - vec3 p2 = controls[2]; - - // Unit vectors for directions between control points - vec3 v10 = normalize(p0 - p1); - vec3 v12 = normalize(p2 - p1); - vec3 v01 = -v10; - vec3 v21 = -v12; - - // - vec3 p0_perp = cross(normal, v01); // Pointing to the left of the curve from p0 - vec3 p2_perp = cross(normal, v12); // Pointing to the left of the curve from p2 - - // aaw is the added width given around the polygon for antialiasing. - // In case the normal is faced away from (0, 0, 1), the vector to the - // camera, this is scaled up. - float aaw = anti_alias_width / get_aaw_scalar(normal); - float buff0 = 0.5 * v_stroke_width[0] + aaw; - float buff2 = 0.5 * v_stroke_width[2] + aaw; - float aaw0 = (1 - has_prev) * aaw; - float aaw2 = (1 - has_next) * aaw; - - vec3 c0 = p0 - buff0 * p0_perp + aaw0 * v10; - vec3 c1 = p0 + buff0 * p0_perp + aaw0 * v10; - vec3 c2 = p2 + buff2 * p2_perp + aaw2 * v12; - vec3 c3 = p2 - buff2 * p2_perp + aaw2 * v12; - - // Account for previous and next control points - if(has_prev > 0) create_joint(angle_from_prev, v01, buff0, bevel_start, c0, c0, c1, c1); - if(has_next > 0) create_joint(angle_to_next, v21, buff2, bevel_end, c3, c3, c2, c2); - - // Linear case is the simplest - if(degree == 1){ - // Swap between 2 and 3 is deliberate, the order of corners - // should be for a triangle_strip. Last entry is a dummy - corners = vec3[5](c0, c1, c3, c2, vec3(0.0)); - return 4; - } - // Otherwise, form a pentagon around the curve - float orientation = sign(dot(cross(v01, v12), normal)); // Positive for ccw curves - if(orientation > 0) corners = vec3[5](c0, c1, p1, c2, c3); - else corners = vec3[5](c1, c0, p1, c3, c2); - // Replace corner[2] with convex hull point accounting for stroke width - find_intersection(corners[0], v01, corners[4], v21, normal, corners[2]); - return 5; -} - - -void set_adjascent_info(vec3 c0, vec3 tangent, - int degree, - vec3 normal, - vec3 adj[3], - out float bevel, - out float angle - ){ - float joint_type = v_joint_type[0]; - vec3 new_adj[3]; - float adj_degree = get_reduced_control_points(adj, new_adj); - // Check if adj_degree is zero? - angle = angle_between_vectors(c0 - new_adj[1], tangent, normal); - // Decide on joint type - bool one_linear = (degree == 1 || adj_degree == 1.0); - bool should_bevel = ( - (joint_type == AUTO_JOINT && one_linear) || - joint_type == BEVEL_JOINT - ); - bevel = should_bevel ? 1.0 : 0.0; -} - - -void set_previous_and_next(vec3 controls[3], int degree, vec3 normal){ - float a_tol = 1e-8; - - // Made as floats not bools so they can be passed to the frag shader - has_prev = float(distance(prev_bp[2], bp[0]) < a_tol); - has_next = float(distance(next_bp[0], bp[2]) < a_tol); - - if(has_prev > 0){ - vec3 tangent = controls[1] - controls[0]; - set_adjascent_info( - controls[0], tangent, degree, normal, - vec3[3](prev_bp[0], prev_bp[1], prev_bp[2]), - bevel_start, angle_from_prev - ); - } - if(has_next > 0){ - vec3 tangent = controls[1] - controls[2]; - set_adjascent_info( - controls[2], tangent, degree, normal, - vec3[3](next_bp[0], next_bp[1], next_bp[2]), - bevel_end, angle_to_next - ); - angle_to_next *= -1; - } -} - - -void main() { - vec3 unit_normal = v_global_unit_normal[0]; - // anti_alias_width /= cos(0.5 * acos(abs(unit_normal.z))); - - vec3 controls[3]; - bezier_degree = get_reduced_control_points(vec3[3](bp[0], bp[1], bp[2]), controls); - int degree = int(bezier_degree); - - // Null curve - if(degree == 0) return; - - set_previous_and_next(controls, degree, unit_normal); - - // Find uv conversion matrix - mat4 xyz_to_uv = get_xyz_to_uv(controls[0], controls[1], unit_normal); - float scale_factor = length(controls[1] - controls[0]); - uv_anti_alias_width = anti_alias_width / scale_factor / get_aaw_scalar(unit_normal); - uv_b2 = (xyz_to_uv * vec4(controls[2], 1.0)).xy; - - // Corners of a bounding region around curve - vec3 corners[5]; - int n_corners = get_corners(controls, unit_normal, degree, corners); - - int index_map[5] = int[5](0, 0, 1, 2, 2); - if(n_corners == 4) index_map[2] = 2; - - // Emit each corner - for(int i = 0; i < n_corners; i++){ - xyz_coords = corners[i]; - uv_coords = (xyz_to_uv * vec4(xyz_coords, 1.0)).xy; - uv_stroke_width = v_stroke_width[index_map[i]] / scale_factor; - color = v_color[index_map[i]]; - gloss = v_gloss[index_map[i]]; - global_unit_normal = v_global_unit_normal[index_map[i]]; - gl_Position = get_gl_Position(xyz_coords); - EmitVertex(); - } - EndPrimitive(); -} \ No newline at end of file diff --git a/manimlib/shaders/scale_and_shift_point_for_frame.glsl b/manimlib/shaders/scale_and_shift_point_for_frame.glsl deleted file mode 100644 index 4c13eced..00000000 --- a/manimlib/shaders/scale_and_shift_point_for_frame.glsl +++ /dev/null @@ -1,8 +0,0 @@ -// Assumes the following uniforms exist in the surrounding context: -// uniform float aspect_ratio; -// TODO, rename - -vec3 get_gl_Position(vec3 point){ - point.x /= aspect_ratio; - return point; -} \ No newline at end of file diff --git a/manimlib/shaders/simple_vert.glsl b/manimlib/shaders/simple_vert.glsl index c5d8c546..79ea5919 100644 --- a/manimlib/shaders/simple_vert.glsl +++ b/manimlib/shaders/simple_vert.glsl @@ -1,9 +1,6 @@ #version 330 -uniform float aspect_ratio; -uniform float anti_alias_width; -uniform mat4 to_screen_space; -uniform float focal_distance; +#INSERT camera_uniform_declarations.glsl in vec3 point; diff --git a/manimlib/shaders/surface/frag.glsl b/manimlib/shaders/surface/frag.glsl new file mode 100644 index 00000000..db905275 --- /dev/null +++ b/manimlib/shaders/surface/frag.glsl @@ -0,0 +1,24 @@ +#version 330 + +uniform vec3 light_source_position; +uniform float gloss; +uniform float shadow; + +in vec3 xyz_coords; +in vec3 v_normal; +in vec4 v_color; + +out vec4 frag_color; + +#INSERT finalize_color.glsl + +void main() { + frag_color = finalize_color( + v_color, + xyz_coords, + normalize(v_normal), + light_source_position, + gloss, + shadow + ); +} \ No newline at end of file diff --git a/manimlib/shaders/surface/vert.glsl b/manimlib/shaders/surface/vert.glsl new file mode 100644 index 00000000..546c2410 --- /dev/null +++ b/manimlib/shaders/surface/vert.glsl @@ -0,0 +1,23 @@ +#version 330 + +#INSERT camera_uniform_declarations.glsl + +in vec3 point; +in vec3 du_point; +in vec3 dv_point; +in vec4 color; + +out vec3 xyz_coords; +out vec3 v_normal; +out vec4 v_color; + +#INSERT position_point_into_frame.glsl +#INSERT get_gl_Position.glsl +#INSERT get_rotated_surface_unit_normal_vector.glsl + +void main(){ + xyz_coords = position_point_into_frame(point); + v_normal = get_rotated_surface_unit_normal_vector(point, du_point, dv_point); + v_color = color; + gl_Position = get_gl_Position(xyz_coords); +} \ No newline at end of file diff --git a/manimlib/shaders/surface_frag.glsl b/manimlib/shaders/surface_frag.glsl deleted file mode 100644 index 3f0eb69c..00000000 --- a/manimlib/shaders/surface_frag.glsl +++ /dev/null @@ -1,12 +0,0 @@ -#version 330 - -// uniform sampler2D Texture; - -// in vec2 v_im_coords; -in vec4 v_color; - -out vec4 frag_color; - -void main() { - frag_color = v_color; -} \ No newline at end of file diff --git a/manimlib/shaders/surface_vert.glsl b/manimlib/shaders/surface_vert.glsl deleted file mode 100644 index 2a8284de..00000000 --- a/manimlib/shaders/surface_vert.glsl +++ /dev/null @@ -1,31 +0,0 @@ -#version 330 - -uniform float aspect_ratio; -uniform float anti_alias_width; -uniform mat4 to_screen_space; -uniform float focal_distance; -uniform vec3 light_source_position; - -// uniform sampler2D Texture; - -in vec3 point; -in vec3 normal; -// in vec2 im_coords; -in vec4 color; -in float gloss; - -// out vec2 v_im_coords; -out vec4 v_color; - -// Analog of import for manim only -#INSERT position_point_into_frame.glsl -#INSERT get_gl_Position.glsl -#INSERT add_light.glsl - -void main(){ - vec3 xyz_coords = position_point_into_frame(point); - vec3 unit_normal = normalize(position_point_into_frame(normal)); - // v_im_coords = im_coords; - v_color = add_light(color, xyz_coords, unit_normal, light_source_position, gloss); - gl_Position = get_gl_Position(xyz_coords); -} \ No newline at end of file diff --git a/manimlib/shaders/textured_surface/frag.glsl b/manimlib/shaders/textured_surface/frag.glsl new file mode 100644 index 00000000..ab45dad6 --- /dev/null +++ b/manimlib/shaders/textured_surface/frag.glsl @@ -0,0 +1,42 @@ +#version 330 + +uniform sampler2D LightTexture; +uniform sampler2D DarkTexture; +uniform float num_textures; +uniform vec3 light_source_position; +uniform float gloss; +uniform float shadow; + +in vec3 xyz_coords; +in vec3 v_normal; +in vec2 v_im_coords; +in float v_opacity; + +out vec4 frag_color; + +#INSERT finalize_color.glsl + +const float dark_shift = 0.2; + +void main() { + vec4 color = texture(LightTexture, v_im_coords); + if(num_textures == 2.0){ + vec4 dark_color = texture(DarkTexture, v_im_coords); + float dp = dot( + normalize(light_source_position - xyz_coords), + normalize(v_normal) + ); + float alpha = smoothstep(-dark_shift, dark_shift, dp); + color = mix(dark_color, color, alpha); + } + + frag_color = finalize_color( + color, + xyz_coords, + normalize(v_normal), + light_source_position, + gloss, + shadow + ); + frag_color.a = v_opacity; +} \ No newline at end of file diff --git a/manimlib/shaders/textured_surface/vert.glsl b/manimlib/shaders/textured_surface/vert.glsl new file mode 100644 index 00000000..d7e08987 --- /dev/null +++ b/manimlib/shaders/textured_surface/vert.glsl @@ -0,0 +1,26 @@ +#version 330 + +#INSERT camera_uniform_declarations.glsl + +in vec3 point; +in vec3 du_point; +in vec3 dv_point; +in vec2 im_coords; +in float opacity; + +out vec3 xyz_coords; +out vec3 v_normal; +out vec2 v_im_coords; +out float v_opacity; + +#INSERT position_point_into_frame.glsl +#INSERT get_gl_Position.glsl +#INSERT get_rotated_surface_unit_normal_vector.glsl + +void main(){ + xyz_coords = position_point_into_frame(point); + v_normal = get_rotated_surface_unit_normal_vector(point, du_point, dv_point); + v_im_coords = im_coords; + v_opacity = opacity; + gl_Position = get_gl_Position(xyz_coords); +} \ No newline at end of file diff --git a/manimlib/shaders/true_dot/frag.glsl b/manimlib/shaders/true_dot/frag.glsl new file mode 100644 index 00000000..a8965359 --- /dev/null +++ b/manimlib/shaders/true_dot/frag.glsl @@ -0,0 +1,34 @@ +#version 330 + +uniform vec3 light_source_position; +uniform float gloss; +uniform float shadow; +uniform float anti_alias_width; + +in vec4 color; +in float radius; +in vec2 center; +in vec2 point; + +out vec4 frag_color; + +#INSERT finalize_color.glsl + +void main() { + vec2 diff = point - center; + float dist = length(diff); + float signed_dist = dist - radius; + if (signed_dist > 0.5 * anti_alias_width){ + discard; + } + vec3 normal = vec3(diff / radius, sqrt(1 - (dist * dist) / (radius * radius))); + frag_color = finalize_color( + color, + vec3(point.xy, 0.0), + normal, + light_source_position, + gloss, + shadow + ); + frag_color.a *= smoothstep(0.5, -0.5, signed_dist / anti_alias_width); +} \ No newline at end of file diff --git a/manimlib/shaders/true_dot/geom.glsl b/manimlib/shaders/true_dot/geom.glsl new file mode 100644 index 00000000..2a652a35 --- /dev/null +++ b/manimlib/shaders/true_dot/geom.glsl @@ -0,0 +1,43 @@ +#version 330 + +layout (points) in; +layout (triangle_strip, max_vertices = 4) out; + +// Needed for get_gl_Position +uniform vec2 frame_shape; +uniform float focal_distance; +uniform float is_fixed_in_frame; +uniform float anti_alias_width; + +in vec3 v_point[1]; +in float v_radius[1]; +in vec4 v_color[1]; + +out vec4 color; +out float radius; +out vec2 center; +out vec2 point; + +#INSERT get_gl_Position.glsl + +void main() { + color = v_color[0]; + radius = v_radius[0]; + center = v_point[0].xy; + + radius = v_radius[0] / max(1.0 - v_point[0].z / focal_distance / frame_shape.y, 0.0); + float rpa = radius + anti_alias_width; + + for(int i = 0; i < 4; i++){ + // To account for perspective + + int x_index = 2 * (i % 2) - 1; + int y_index = 2 * (i / 2) - 1; + vec3 corner = v_point[0] + vec3(x_index * rpa, y_index * rpa, 0.0); + + gl_Position = get_gl_Position(corner); + point = corner.xy; + EmitVertex(); + } + EndPrimitive(); +} \ No newline at end of file diff --git a/manimlib/shaders/true_dot/vert.glsl b/manimlib/shaders/true_dot/vert.glsl new file mode 100644 index 00000000..61d77b89 --- /dev/null +++ b/manimlib/shaders/true_dot/vert.glsl @@ -0,0 +1,19 @@ +#version 330 + +#INSERT camera_uniform_declarations.glsl + +in vec3 point; +in float radius; +in vec4 color; + +out vec3 v_point; +out float v_radius; +out vec4 v_color; + +#INSERT position_point_into_frame.glsl + +void main(){ + v_point = position_point_into_frame(point); + v_radius = radius; + v_color = color; +} \ No newline at end of file diff --git a/manimlib/stream_starter.py b/manimlib/stream_starter.py deleted file mode 100644 index 4a3c5a9b..00000000 --- a/manimlib/stream_starter.py +++ /dev/null @@ -1,51 +0,0 @@ -from time import sleep -import code -import os -import subprocess - -from manimlib.scene.scene import Scene -import manimlib.constants - - -def start_livestream(to_twitch=False, twitch_key=None): - class Manim(): - def __new__(cls): - kwargs = { - "scene_name": manimlib.constants.LIVE_STREAM_NAME, - "open_video_upon_completion": False, - "show_file_in_finder": False, - # By default, write to file - "write_to_movie": True, - "show_last_frame": False, - "save_pngs": False, - # If -t is passed in (for transparent), this will be RGBA - "saved_image_mode": "RGB", - "movie_file_extension": ".mp4", - "quiet": True, - "ignore_waits": False, - "write_all": False, - "name": manimlib.constants.LIVE_STREAM_NAME, - "start_at_animation_number": 0, - "end_at_animation_number": None, - "skip_animations": False, - "camera_config": manimlib.constants.HIGH_QUALITY_CAMERA_CONFIG, - "livestreaming": True, - "to_twitch": to_twitch, - "twitch_key": twitch_key, - } - return Scene(**kwargs) - - if not to_twitch: - FNULL = open(os.devnull, 'w') - subprocess.Popen( - [manimlib.constants.STREAMING_CLIENT, manimlib.constants.STREAMING_URL], - stdout=FNULL, - stderr=FNULL) - sleep(3) - - variables = globals().copy() - variables.update(locals()) - shell = code.InteractiveConsole(variables) - shell.push("manim = Manim()") - shell.push("from manimlib.imports import *") - shell.interact(banner=manimlib.constants.STREAMING_CONSOLE_BANNER) diff --git a/manimlib/ctex_template.tex b/manimlib/tex_templates/ctex_template.tex similarity index 88% rename from manimlib/ctex_template.tex rename to manimlib/tex_templates/ctex_template.tex index 41edd968..65ff5df1 100644 --- a/manimlib/ctex_template.tex +++ b/manimlib/tex_templates/ctex_template.tex @@ -1,4 +1,5 @@ \documentclass[preview]{standalone} +\usepackage[UTF8]{ctex} \usepackage[english]{babel} \usepackage{amsmath} @@ -15,12 +16,10 @@ \usepackage{physics} \usepackage{xcolor} \usepackage{microtype} -%\DisableLigatures{encoding = *, family = * } -\usepackage[UTF8]{ctex} \linespread{1} \begin{document} -YourTextHere +[tex_expression] \end{document} diff --git a/manimlib/tex_template.tex b/manimlib/tex_templates/tex_template.tex similarity index 93% rename from manimlib/tex_template.tex rename to manimlib/tex_templates/tex_template.tex index b017bc7c..5f665b61 100644 --- a/manimlib/tex_template.tex +++ b/manimlib/tex_templates/tex_template.tex @@ -19,11 +19,10 @@ \usepackage{microtype} \usepackage{pifont} \DisableLigatures{encoding = *, family = * } -%\usepackage[UTF8]{ctex} \linespread{1} \begin{document} -YourTextHere +[tex_expression] \end{document} diff --git a/manimlib/utils/__init__.py b/manimlib/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manimlib/utils/bezier.py b/manimlib/utils/bezier.py index 32484cc2..8a3f6692 100644 --- a/manimlib/utils/bezier.py +++ b/manimlib/utils/bezier.py @@ -3,16 +3,21 @@ import numpy as np from manimlib.utils.simple_functions import choose from manimlib.utils.space_ops import find_intersection +from manimlib.utils.space_ops import cross2d CLOSED_THRESHOLD = 0.001 def bezier(points): n = len(points) - 1 - return lambda t: sum([ - ((1 - t)**(n - k)) * (t**k) * choose(n, k) * point - for k, point in enumerate(points) - ]) + + def result(t): + return sum([ + ((1 - t)**(n - k)) * (t**k) * choose(n, k) * point + for k, point in enumerate(points) + ]) + + return result def partial_bezier_points(points, a, b): @@ -39,10 +44,39 @@ def partial_bezier_points(points, a, b): ] +# Shortened version of partial_bezier_points just for quadratics, +# since this is called a fair amount +def partial_quadratic_bezier_points(points, a, b): + if a == 1: + return 3 * [points[-1]] + + def curve(t): + return points[0] * (1 - t) * (1 - t) + 2 * points[1] * t * (1 - t) + points[2] * t * t + # bezier(points) + h0 = curve(a) if a > 0 else points[0] + h2 = curve(b) if b < 1 else points[2] + h1_prime = (1 - a) * points[1] + a * points[2] + end_prop = (b - a) / (1. - a) + h1 = (1 - end_prop) * h0 + end_prop * h1_prime + return [h0, h1, h2] + + # Linear interpolation variants def interpolate(start, end, alpha): - return (1 - alpha) * start + alpha * end + try: + return (1 - alpha) * start + alpha * end + except TypeError: + print(type(start), start.dtype) + print(type(end), start.dtype) + print(alpha) + import sys + sys.exit(2) + + +def set_array_by_interpolation(arr, arr1, arr2, alpha, interp_func=interpolate): + arr[:] = interp_func(arr1, arr2, alpha) + return arr def integer_interpolate(start, end, alpha): @@ -81,10 +115,37 @@ def match_interpolate(new_start, new_end, old_start, old_end, old_value): ) -# Figuring out which bezier curves most smoothly connect a sequence of points +def get_smooth_quadratic_bezier_handle_points(points): + """ + Figuring out which bezier curves most smoothly connect a sequence of points. + + Given three successive points, P0, P1 and P2, you can compute that by defining + h = (1/4) P0 + P1 - (1/4)P2, the bezier curve defined by (P0, h, P1) will pass + through the point P2. + + So for a given set of four successive points, P0, P1, P2, P3, if we want to add + a handle point h between P1 and P2 so that the quadratic bezier (P1, h, P2) is + part of a smooth curve passing through all four points, we calculate one solution + for h that would produce a parbola passing through P3, call it smooth_to_right, and + another that would produce a parabola passing through P0, call it smooth_to_left, + and use the midpoint between the two. + """ + smooth_to_right, smooth_to_left = [ + 0.25 * ps[0:-2] + ps[1:-1] - 0.25 * ps[2:] + for ps in (points, points[::-1]) + ] + if np.isclose(points[0], points[-1]).all(): + last_str = 0.25 * points[-2] + points[-1] - 0.25 * points[1] + last_stl = 0.25 * points[1] + points[0] - 0.25 * points[-2] + else: + last_str = smooth_to_left[0] + last_stl = smooth_to_right[0] + handles = 0.5 * np.vstack([smooth_to_right, [last_str]]) + handles += 0.5 * np.vstack([last_stl, smooth_to_left[::-1]]) + return handles -def get_smooth_handle_points(points): +def get_smooth_cubic_bezier_handle_points(points): points = np.array(points) num_handles = len(points) - 1 dim = points.shape[1] @@ -117,6 +178,7 @@ def get_smooth_handle_points(points): def solve_func(b): return linalg.solve_banded((l, u), diag, b) + use_closed_solve_function = is_closed(points) if use_closed_solve_function: # Get equations to relate first and last points @@ -182,31 +244,26 @@ def get_quadratic_approximation_of_cubic(a0, h0, h1, a1): q = h1 - 2 * h0 + a0 r = a1 - 3 * h1 + 3 * h0 - a0 - def cross2d(v, w): - return v[:, 0] * w[:, 1] - v[:, 1] * w[:, 0] - a = cross2d(q, r) b = cross2d(p, r) c = cross2d(p, q) disc = b * b - 4 * a * c has_infl &= (disc > 0) - sqrt_disc = np.sqrt(abs(disc)) - # print(a, b, c, sqrt_disc) + sqrt_disc = np.sqrt(np.abs(disc)) settings = np.seterr(all='ignore') - ti_min, ti_max = [ - np.true_divide( - -b + sgn * sqrt_disc, 2 * a, - out=(-c / b), - where=(a != 0), - ) - for sgn in [-1, +1] - ] + ti_bounds = [] + for sgn in [-1, +1]: + ti = (-b + sgn * sqrt_disc) / (2 * a) + ti[a == 0] = (-c / b)[a == 0] + ti[(a == 0) & (b == 0)] = 0 + ti_bounds.append(ti) + ti_min, ti_max = ti_bounds np.seterr(**settings) ti_min_in_range = has_infl & (0 < ti_min) & (ti_min < 1) ti_max_in_range = has_infl & (0 < ti_max) & (ti_max < 1) - # Choose a value of t which is starts as 0.5, + # Choose a value of t which starts at 0.5, # but is updated to one of the inflection points # if they lie between 0 and 1 @@ -238,7 +295,8 @@ def get_quadratic_approximation_of_cubic(a0, h0, h1, a1): def get_smooth_quadratic_bezier_path_through(points): - h0, h1 = get_smooth_handle_points(points) + # TODO + h0, h1 = get_smooth_cubic_bezier_handle_points(points) a0 = points[:-1] a1 = points[1:] return get_quadratic_approximation_of_cubic(a0, h0, h1, a1) diff --git a/manimlib/utils/color.py b/manimlib/utils/color.py index 91d97142..a3450486 100644 --- a/manimlib/utils/color.py +++ b/manimlib/utils/color.py @@ -3,9 +3,10 @@ import random from colour import Color import numpy as np -from manimlib.constants import PALETTE from manimlib.constants import WHITE +from manimlib.constants import COLORMAP_3B1B from manimlib.utils.bezier import interpolate +from manimlib.utils.iterables import resize_with_interpolation from manimlib.utils.simple_functions import clip_in_place from manimlib.utils.space_ops import normalize @@ -35,7 +36,11 @@ def rgba_to_color(rgba): def rgb_to_hex(rgb): - return "#" + "".join(hex(int(255 * x))[2:] for x in rgb) + return "#" + "".join( + hex(int_x // 16)[2] + hex(int_x % 16)[2] + for x in rgb + for int_x in [int(255 * x)] + ) def hex_to_rgb(hex_code): @@ -97,7 +102,7 @@ def random_bright_color(): def random_color(): - return random.choice(PALETTE) + return Color(rgb=(random.random() for i in range(3))) def get_shaded_rgb(rgb, point, unit_normal_vect, light_source): @@ -108,3 +113,25 @@ def get_shaded_rgb(rgb, point, unit_normal_vect, light_source): result = rgb + factor clip_in_place(rgb + factor, 0, 1) return result + + +def get_colormap_list(map_name="viridis", n_colors=9): + """ + Options for map_name: + 3b1b_colormap + magma + inferno + plasma + viridis + cividis + twilight + twilight_shifted + turbo + """ + from matplotlib.cm import get_cmap + + if map_name == "3b1b_colormap": + rgbs = [color_to_rgb(color) for color in COLORMAP_3B1B] + else: + rgbs = get_cmap(map_name).colors # Make more general? + return resize_with_interpolation(np.array(rgbs), n_colors) diff --git a/manimlib/utils/customization.py b/manimlib/utils/customization.py new file mode 100644 index 00000000..bf79b1b8 --- /dev/null +++ b/manimlib/utils/customization.py @@ -0,0 +1,23 @@ +import os +import tempfile + +from manimlib.config import get_custom_config +from manimlib.config import get_manim_dir + +CUSTOMIZATION = {} + + +def get_customization(): + if not CUSTOMIZATION: + CUSTOMIZATION.update(get_custom_config()) + directories = CUSTOMIZATION["directories"] + # Unless user has specified otherwise, use the system default temp + # directory for storing tex files, mobject_data, etc. + if not directories["temporary_storage"]: + directories["temporary_storage"] = tempfile.gettempdir() + + # Assumes all shaders are written into manimlib/shaders + directories["shaders"] = os.path.join( + get_manim_dir(), "manimlib", "shaders" + ) + return CUSTOMIZATION diff --git a/manimlib/utils/directories.py b/manimlib/utils/directories.py new file mode 100644 index 00000000..c82a5368 --- /dev/null +++ b/manimlib/utils/directories.py @@ -0,0 +1,48 @@ +import os + +from manimlib.utils.file_ops import guarantee_existence +from manimlib.utils.customization import get_customization + + +def get_directories(): + return get_customization()["directories"] + + +def get_temp_dir(): + return get_directories()["temporary_storage"] + + +def get_tex_dir(): + return guarantee_existence(os.path.join(get_temp_dir(), "Tex")) + + +def get_text_dir(): + return guarantee_existence(os.path.join(get_temp_dir(), "Text")) + + +def get_mobject_data_dir(): + return guarantee_existence(os.path.join(get_temp_dir(), "mobject_data")) + + +def get_downloads_dir(): + return guarantee_existence(os.path.join(get_temp_dir(), "manim_downloads")) + + +def get_output_dir(): + return guarantee_existence(get_directories()["output"]) + + +def get_raster_image_dir(): + return get_directories()["raster_images"] + + +def get_vector_image_dir(): + return get_directories()["vector_images"] + + +def get_sound_dir(): + return get_directories()["sounds"] + + +def get_shader_dir(): + return get_directories()["shaders"] diff --git a/manimlib/utils/file_ops.py b/manimlib/utils/file_ops.py index 9c9752a4..19322825 100644 --- a/manimlib/utils/file_ops.py +++ b/manimlib/utils/file_ops.py @@ -1,5 +1,6 @@ import os import numpy as np +import validators def add_extension_if_not_present(file_name, extension): @@ -16,16 +17,34 @@ def guarantee_existence(path): return os.path.abspath(path) -def seek_full_path_from_defaults(file_name, default_dir, extensions): - possible_paths = [file_name] - possible_paths += [ - os.path.join(default_dir, file_name + extension) - for extension in ["", *extensions] - ] +def find_file(file_name, directories=None, extensions=None): + # Check if this is a file online first, and if so, download + # it to a temporary directory + if validators.url(file_name): + import urllib.request + from manimlib.utils.directories import get_downloads_dir + stem, name = os.path.split(file_name) + folder = get_downloads_dir() + path = os.path.join(folder, name) + urllib.request.urlretrieve(file_name, path) + return path + + # Check if what was passed in is already a valid path to a file + if os.path.exists(file_name): + return file_name + + # Otherwise look in local file system + directories = directories or [""] + extensions = extensions or [""] + possible_paths = ( + os.path.join(directory, file_name + extension) + for directory in directories + for extension in extensions + ) for path in possible_paths: if os.path.exists(path): return path - raise IOError("File {} not Found".format(file_name)) + raise IOError(f"{file_name} not Found") def get_sorted_integer_files(directory, diff --git a/manimlib/utils/images.py b/manimlib/utils/images.py index edd75530..e302c3b2 100644 --- a/manimlib/utils/images.py +++ b/manimlib/utils/images.py @@ -1,16 +1,24 @@ import numpy as np -import os - from PIL import Image -from manimlib.utils.file_ops import seek_full_path_from_defaults +from manimlib.utils.file_ops import find_file +from manimlib.utils.directories import get_raster_image_dir +from manimlib.utils.directories import get_vector_image_dir def get_full_raster_image_path(image_file_name): - return seek_full_path_from_defaults( + return find_file( image_file_name, - default_dir=os.path.join("assets", "raster_images"), - extensions=[".jpg", ".png", ".gif"] + directories=[get_raster_image_dir()], + extensions=[".jpg", ".jpeg", ".png", ".gif", ""] + ) + + +def get_full_vector_image_path(image_file_name): + return find_file( + image_file_name, + directories=[get_vector_image_dir()], + extensions=[".svg", ".xdv", ""], ) diff --git a/manimlib/utils/init_config.py b/manimlib/utils/init_config.py new file mode 100644 index 00000000..064db879 --- /dev/null +++ b/manimlib/utils/init_config.py @@ -0,0 +1,84 @@ +import yaml +import os + +def init_customization(): + configuration = { + "directories": { + "mirror_module_path": False, + "output": "", + "raster_images": "", + "vector_images": "", + "sounds": "", + "temporary_storage": "", + }, + "tex": { + "executable": "", + "template_file": "", + "intermediate_filetype": "", + "text_to_replace": "[tex_expression]", + }, + "universal_import_line": "from manimlib import *", + "style": { + "font": "Consolas", + "background_color": "", + }, + "window_position": "UR", + "break_into_partial_movies": False, + "camera_qualities": { + "low": { + "resolution": "854x480", + "frame_rate": 15, + }, + "medium": { + "resolution": "1280x720", + "frame_rate": 30, + }, + "high": { + "resolution": "1920x1080", + "frame_rate": 60, + }, + "ultra_high": { + "resolution": "3840x2160", + "frame_rate": 60, + }, + "default_quality": "", + } + } + + scope = input(" Please select the scope of the configuration [global/local]: ") + if scope == "global": + from manimlib.config import get_manim_dir + file_name = os.path.join(get_manim_dir(), "manimlib", "default_config.yml") + else: + file_name = os.path.join(os.getcwd(), "custom_config.yml") + + print("\n directories:") + configuration["directories"]["output"] = input(" [1/8] Where should manim output video and image files place: ") + configuration["directories"]["raster_images"] = input(" [2/8] Which folder should manim find raster images (.jpg .png .gif) in (optional): ") + configuration["directories"]["vector_images"] = input(" [3/8] Which folder should manim find vector images (.svg .xdv) in (optional): ") + configuration["directories"]["sounds"] = input(" [4/8] Which folder should manim find sound files (.mp3 .wav) in (optional): ") + configuration["directories"]["temporary_storage"] = input(" [5/8] Which folder should manim storage temporary files: ") + + print("\n tex:") + tex = input(" [6/8] Which executable file to use to compile [latex/xelatex]: ") + if tex == "latex": + configuration["tex"]["executable"] = "latex" + configuration["tex"]["template_file"] = "tex_template.tex" + configuration["tex"]["intermediate_filetype"] = "dvi" + else: + configuration["tex"]["executable"] = "xelatex -no-pdf" + configuration["tex"]["template_file"] = "ctex_template.tex" + configuration["tex"]["intermediate_filetype"] = "xdv" + + print("\n style:") + configuration["style"]["background_color"] = input(" [7/8] Which background color do you want (hex code): ") + + print("\n camera_qualities:") + print(" Four defined qualities: low: 480p15 medium: 720p30 high: 1080p60 ultra_high: 2160p60") + configuration["camera_qualities"]["default_quality"] = input(" [8/8] Which one to choose as the default rendering quality [low/medium/high/ultra_high]: ") + + with open(file_name, 'w', encoding="utf_8") as file: + yaml.dump(configuration, file) + + print(f"\nYou have set up a {scope} configuration file") + print(f"You can manually modify it again in: {file_name}\n") diff --git a/manimlib/utils/iterables.py b/manimlib/utils/iterables.py index 29474f81..4fd0582c 100644 --- a/manimlib/utils/iterables.py +++ b/manimlib/utils/iterables.py @@ -53,22 +53,21 @@ def batch_by_property(items, property_func): preserved) """ batch_prop_pairs = [] - - def add_batch_prop_pair(batch): - if len(batch) > 0: - prop = property_func(batch[0]) - batch_prop_pairs.append((batch, prop)) curr_batch = [] curr_prop = None for item in items: prop = property_func(item) if prop != curr_prop: - add_batch_prop_pair(curr_batch) + # Add current batch + if len(curr_batch) > 0: + batch_prop_pairs.append((curr_batch, curr_prop)) + # Redefine curr curr_prop = prop curr_batch = [item] else: curr_batch.append(item) - add_batch_prop_pair(curr_batch) + if len(curr_batch) > 0: + batch_prop_pairs.append((curr_batch, curr_prop)) return batch_prop_pairs @@ -81,17 +80,27 @@ def listify(obj): return [obj] -def stretch_array_to_length(nparray, length): - curr_len = len(nparray) - if curr_len > length: - raise Warning("Trying to stretch array to a length shorter than its own") - indices = np.arange(0, curr_len, curr_len / length).astype(int) +def resize_array(nparray, length): + if len(nparray) == length: + return nparray + return np.resize(nparray, (length, *nparray.shape[1:])) + + +def resize_preserving_order(nparray, length): + if len(nparray) == 0: + return np.zeros((length, *nparray.shape[1:])) + if len(nparray) == length: + return nparray + indices = np.arange(length) * len(nparray) // length return nparray[indices] -def stretch_array_to_length_with_interpolation(nparray, length): - curr_len = len(nparray) - cont_indices = np.linspace(0, curr_len - 1, length) +def resize_with_interpolation(nparray, length): + if len(nparray) == length: + return nparray + if length == 0: + return np.zeros((0, *nparray.shape[1:])) + cont_indices = np.linspace(0, len(nparray) - 1, length) return np.array([ (1 - a) * nparray[lh] + a * nparray[rh] for ci in cont_indices @@ -100,12 +109,14 @@ def stretch_array_to_length_with_interpolation(nparray, length): def make_even(iterable_1, iterable_2): - list_1 = list(iterable_1) - list_2 = list(iterable_2) - length = max(len(list_1), len(list_2)) + len1 = len(iterable_1) + len2 = len(iterable_2) + if len1 == len2: + return iterable_1, iterable_2 + new_len = max(len1, len2) return ( - [list_1[(n * len(list_1)) // length] for n in range(length)], - [list_2[(n * len(list_2)) // length] for n in range(length)] + [iterable_1[(n * len1) // new_len] for n in range(new_len)], + [iterable_2[(n * len2) // new_len] for n in range(new_len)] ) diff --git a/manimlib/utils/rate_functions.py b/manimlib/utils/rate_functions.py index 32fda05f..6f51bda0 100644 --- a/manimlib/utils/rate_functions.py +++ b/manimlib/utils/rate_functions.py @@ -1,28 +1,25 @@ import numpy as np from manimlib.utils.bezier import bezier -from manimlib.utils.simple_functions import sigmoid -from manimlib.utils.simple_functions import clip def linear(t): return t -def smooth(t, inflection=10.0): - error = sigmoid(-inflection / 2) - return clip( - (sigmoid(inflection * (t - 0.5)) - error) / (1 - 2 * error), - 0, 1, - ) +def smooth(t): + # Zero first and second derivatives at t=0 and t=1. + # Equivalent to bezier([0, 0, 0, 1, 1, 1]) + s = 1 - t + return (t**3) * (10 * s * s + 5 * s * t + t * t) -def rush_into(t, inflection=10.0): - return 2 * smooth(t / 2.0, inflection) +def rush_into(t): + return 2 * smooth(0.5 * t) -def rush_from(t, inflection=10.0): - return 2 * smooth(t / 2.0 + 0.5, inflection) - 1 +def rush_from(t): + return 2 * smooth(0.5 * (t + 1)) - 1 def slow_into(t): @@ -36,9 +33,9 @@ def double_smooth(t): return 0.5 * (1 + smooth(2 * t - 1)) -def there_and_back(t, inflection=10.0): +def there_and_back(t): new_t = 2 * t if t < 0.5 else 2 * (1 - t) - return smooth(new_t, inflection) + return smooth(new_t) def there_and_back_with_pause(t, pause_ratio=1. / 3): @@ -69,8 +66,7 @@ def squish_rate_func(func, a=0.4, b=0.6): def result(t): if a == b: return a - - if t < a: + elif t < a: return func(0) elif t > b: return func(1) diff --git a/manimlib/utils/shaders.py b/manimlib/utils/shaders.py deleted file mode 100644 index fb913950..00000000 --- a/manimlib/utils/shaders.py +++ /dev/null @@ -1,104 +0,0 @@ -import os -import warnings -import re -import moderngl - -from manimlib.constants import SHADER_DIR - -# Mobjects that should be rendered with -# the same shader will be organized and -# clumped together based on keeping track -# of a dict holding all the relevant information -# to that shader - - -SHADER_INFO_KEYS = [ - "data", - "vert", - "geom", - "frag", - "texture_path", - "render_primative", -] - - -def get_shader_info(data=None, - vert_file=None, - geom_file=None, - frag_file=None, - texture_path=None, - render_primative=moderngl.TRIANGLE_STRIP): - return { - key: value - for key, value in zip( - SHADER_INFO_KEYS, - [ - data, vert_file, geom_file, frag_file, - texture_path, str(render_primative) - ] - ) - } - - -def is_valid_shader_info(shader_info): - data = shader_info["data"] - return all([ - data is not None and len(data) > 0, - shader_info["vert"], - shader_info["frag"], - ]) - - -def shader_info_to_id(shader_info): - # A unique id for a shader based on the - # files holding its code and texture - return "|".join([ - shader_info.get(key, "") or "" - for key in SHADER_INFO_KEYS[1:] - ]) - - -def shader_id_to_info(sid): - return { - key: (value or None) - for key, value in zip( - SHADER_INFO_KEYS, - [None, *sid.split("|")] - ) - } - - -def same_shader_type(info1, info2): - return all([ - info1[key] == info2[key] - for key in [ - "vert", - "geom", - "frag", - "texture_path", - "render_primative", - ] - ]) - - -def get_shader_code_from_file(filename): - if not filename: - return None - - filepath = os.path.join(SHADER_DIR, filename) - if not os.path.exists(filepath): - warnings.warn(f"No file at {filepath}") - return - - with open(filepath, "r") as f: - result = f.read() - - # To share functionality between shaders, some functions are read in - # from other files an inserted into the relevant strings before - # passing to ctx.program for compiling - # Replace "#INSERT " lines with relevant code - insertions = re.findall(r"^#INSERT .*\.glsl$", result, flags=re.MULTILINE) - for line in insertions: - inserted_code = get_shader_code_from_file(line.replace("#INSERT ", "")) - result = result.replace(line, inserted_code) - return result diff --git a/manimlib/utils/sounds.py b/manimlib/utils/sounds.py index 26cfff07..c2e43269 100644 --- a/manimlib/utils/sounds.py +++ b/manimlib/utils/sounds.py @@ -1,39 +1,10 @@ -import os -from manimlib.utils.file_ops import seek_full_path_from_defaults - - -def play_chord(*nums): - commands = [ - "play", - "-n", - "-c1", - "--no-show-progress", - "synth", - ] + [ - "sin %-" + str(num) - for num in nums - ] + [ - "fade h 0.5 1 0.5", - ">", - os.devnull - ] - try: - os.system(" ".join(commands)) - except: - pass - - -def play_error_sound(): - play_chord(11, 8, 6, 1) - - -def play_finish_sound(): - play_chord(12, 9, 5, 2) +from manimlib.utils.file_ops import find_file +from manimlib.utils.directories import get_sound_dir def get_full_sound_file_path(sound_file_name): - return seek_full_path_from_defaults( + return find_file( sound_file_name, - default_dir=os.path.join("assets", "sounds"), + directories=[get_sound_dir()], extensions=[".wav", ".mp3"] ) diff --git a/manimlib/utils/space_ops.py b/manimlib/utils/space_ops.py index 19bf005b..c124cbec 100644 --- a/manimlib/utils/space_ops.py +++ b/manimlib/utils/space_ops.py @@ -1,10 +1,10 @@ import numpy as np -import math import itertools as it +import math from mapbox_earcut import triangulate_float32 as earcut from manimlib.constants import RIGHT -from manimlib.constants import UP +from manimlib.constants import DOWN from manimlib.constants import OUT from manimlib.constants import PI from manimlib.constants import TAU @@ -35,11 +35,10 @@ def quaternion_mult(*quats): return result -def quaternion_from_angle_axis(angle, axis): - return [ - math.cos(angle / 2), - *(math.sin(angle / 2) * normalize(axis)) - ] +def quaternion_from_angle_axis(angle, axis, axis_normalized=False): + if not axis_normalized: + axis = normalize(axis) + return [math.cos(angle / 2), *(math.sin(angle / 2) * axis)] def angle_axis_from_quaternion(quaternion): @@ -137,11 +136,14 @@ def z_to_vector(vector): Returns some matrix in SO(3) which takes the z-axis to the (normalized) vector provided as an argument """ - cp = cross(OUT, vector) - if get_norm(cp) == 0: - return np.identity(3) + axis = cross(OUT, vector) + if get_norm(axis) == 0: + if vector[2] > 0: + return np.identity(3) + else: + return rotation_matrix(PI, RIGHT) angle = np.arccos(np.dot(OUT, normalize(vector))) - return rotation_matrix(angle, axis=cp) + return rotation_matrix(angle, axis=axis) def angle_of_vector(vector): @@ -175,6 +177,14 @@ def normalize(vect, fall_back=None): return np.zeros(len(vect)) +def normalize_along_axis(array, axis, fall_back=None): + norms = np.sqrt((array * array).sum(axis)) + norms[norms == 0] = 1 + buffed_norms = np.repeat(norms, array.shape[axis]).reshape(array.shape) + array /= buffed_norms + return array + + def cross(v1, v2): return np.array([ v1[1] * v2[2] - v1[2] * v2[1], @@ -193,7 +203,7 @@ def get_unit_normal(v1, v2, tol=1e-6): new_cp = cross(cross(v1, OUT), v1) new_cp_norm = get_norm(new_cp) if new_cp_norm < tol: - return UP + return DOWN return new_cp / new_cp_norm return cp / cp_norm @@ -280,6 +290,21 @@ def find_intersection(p0, v0, p1, v1, threshold=1e-5): return p0 + ratio * v0 +def get_closest_point_on_line(a, b, p): + """ + It returns point x such that + x is on line ab and xp is perpendicular to ab. + If x lies beyond ab line, then it returns nearest edge(a or b). + """ + # x = b + t*(a-b) = t*a + (1-t)*b + t = np.dot(p - b, a - b) / np.dot(a - b, a - b) + if t < 0: + t = 0 + if t > 1: + t = 1 + return ((t * a) + ((1 - t) * b)) + + def get_winding_number(points): total_angle = 0 for p1, p2 in adjacent_pairs(points): @@ -319,46 +344,82 @@ def is_inside_triangle(p, a, b, c): def norm_squared(v): - return sum(v * v) + return v[0] * v[0] + v[1] * v[1] + v[2] * v[2] # TODO, fails for polygons drawn over themselves -def earclip_triangulation(verts, rings): - n = len(verts) - # Establish where loop indices should be connected - loop_connections = dict() - for e0, e1 in zip(rings, rings[1:]): - temp_i = e0 - # Find closet point in the first ring (j) to - # the first index of this ring (i) - norms = np.array([ - [j, norm_squared(verts[temp_i] - verts[j])] - for j in range(0, rings[0]) - if j not in loop_connections - ]) - j = int(norms[norms[:, 1].argmin()][0]) - # Find i closest to this j - norms = np.array([ - [i, norm_squared(verts[i] - verts[j])] - for i in range(e0, e1) - if i not in loop_connections - ]) - i = int(norms[norms[:, 1].argmin()][0]) +def earclip_triangulation(verts, ring_ends): + """ + Returns a list of indices giving a triangulation + of a polygon, potentially with holes + - verts is a numpy array of points + + - ring_ends is a list of indices indicating where + the ends of new paths are + """ + + # First, connect all the rings so that the polygon + # with holes is instead treated as a (very convex) + # polygon with one edge. Do this by drawing connections + # between rings close to each other + rings = [ + list(range(e0, e1)) + for e0, e1 in zip([0, *ring_ends], ring_ends) + ] + attached_rings = rings[:1] + detached_rings = rings[1:] + loop_connections = dict() + + while detached_rings: + i_range, j_range = [ + list(filter( + # Ignore indices that are already being + # used to draw some connection + lambda i: i not in loop_connections, + it.chain(*ring_group) + )) + for ring_group in (attached_rings, detached_rings) + ] + + # Closet point on the atttached rings to an estimated midpoint + # of the detached rings + tmp_j_vert = midpoint( + verts[j_range[0]], + verts[j_range[len(j_range) // 2]] + ) + i = min(i_range, key=lambda i: norm_squared(verts[i] - tmp_j_vert)) + # Closet point of the detached rings to the aforementioned + # point of the attached rings + j = min(j_range, key=lambda j: norm_squared(verts[i] - verts[j])) + # Recalculate i based on new j + i = min(i_range, key=lambda i: norm_squared(verts[i] - verts[j])) + + # Remember to connect the polygon at these points loop_connections[i] = j loop_connections[j] = i + # Move the ring which j belongs to from the + # attached list to the detached list + new_ring = next(filter( + lambda ring: ring[0] <= j < ring[-1], + detached_rings + )) + detached_rings.remove(new_ring) + attached_rings.append(new_ring) + # Setup linked list after = [] - e0 = 0 - for e1 in rings: - after.extend([*range(e0 + 1, e1), e0]) - e0 = e1 + end0 = 0 + for end1 in ring_ends: + after.extend(range(end0 + 1, end1)) + after.append(end0) + end0 = end1 # Find an ordering of indices walking around the polygon indices = [] i = 0 - for x in range(n + len(rings) - 1): + for x in range(len(verts) + len(ring_ends) - 1): # starting = False if i in loop_connections: j = loop_connections[i] @@ -372,87 +433,3 @@ def earclip_triangulation(verts, rings): meta_indices = earcut(verts[indices, :2], [len(indices)]) return [indices[mi] for mi in meta_indices] - - -def old_earclip_triangulation(verts, rings, orientation): - n = len(verts) - assert(n in rings) - result = [] - - # Establish where loop indices should be connected - loop_connections = dict() - e0 = 0 - for e1 in rings: - norms = np.array([ - [i, j, get_norm(verts[i] - verts[j])] - for i in range(e0, e1) - for j in it.chain(range(0, e0), range(e1, n)) - ]) - if len(norms) == 0: - continue - i, j = norms[np.argmin(norms[:, 2])][:2].astype(int) - loop_connections[i] = j - loop_connections[j] = i - e0 = e1 - - # Setup bidirectional linked list - before = [] - after = [] - e0 = 0 - for e1 in rings: - after += [*range(e0 + 1, e1), e0] - before += [e1 - 1, *range(e0, e1 - 1)] - e0 = e1 - - # Initialize edge triangles - edge_tris = [] - i = 0 - starting = True - while (i != 0 or starting): - starting = False - if i in loop_connections: - j = loop_connections[i] - edge_tris.append([before[i], i, j]) - edge_tris.append([i, j, after[j]]) - i = after[j] - else: - edge_tris.append([before[i], i, after[i]]) - i = after[i] - - # Set up a test for whether or not three indices - # form an ear of the polygon, meaning a convex corner - # which doesn't contain any other vertices - indices = list(range(n)) - - def is_ear(*tri_indices): - tri = [verts[i] for i in tri_indices] - v1 = tri[1] - tri[0] - v2 = tri[2] - tri[1] - cross = v1[0] * v2[1] - v2[0] * v1[1] - if orientation * cross < 0: - return False - for j in indices: - if j in tri_indices: - continue - elif is_inside_triangle(verts[j], *tri): - return False - return True - - # Loop through and clip off all the ears - n_failures = 0 - i = 0 - while n_failures < len(edge_tris): - n = len(edge_tris) - edge_tri = edge_tris[i % n] - if is_ear(*edge_tri): - result.extend(edge_tri) - edge_tris[(i - 1) % n][2] = edge_tri[2] - edge_tris[(i + 1) % n][0] = edge_tri[0] - if edge_tri[1] in indices: - indices.remove(edge_tri[1]) - edge_tris.remove(edge_tri) - n_failures = 0 - else: - n_failures += 1 - i += 1 - return result diff --git a/manimlib/utils/strings.py b/manimlib/utils/strings.py index b986577d..5e5b5d5c 100644 --- a/manimlib/utils/strings.py +++ b/manimlib/utils/strings.py @@ -1,4 +1,3 @@ -import itertools as it import re import string @@ -25,35 +24,19 @@ def complex_string(complex_num): return [c for c in str(complex_num) if c not in "()"] -def split_string_to_isolate_substrings(full_string, *substrings_to_isolate): +def split_string_to_isolate_substrings(full_string, *isolate): """ - Given a string, and an arbitrary number of possible substrings, returns a list - of strings which would concatenate to make the full string, and in which - these special substrings appear as their own elements. + Given a string, and an arbitrary number of possible substrings, + to isolate, this returns a list of strings which would concatenate + to make the full string, and in which these special substrings + appear as their own elements. - For example, split_string_to_isolate_substrings("to be or not to be", "to", "be") would - return ["to", " ", "be", " or not ", "to", " ", "be"] + For example,split_string_to_isolate_substrings("to be or not to be", "to", "be") + would return ["to", " ", "be", " or not ", "to", " ", "be"] """ - if len(substrings_to_isolate) == 0: - return [full_string] - substring_to_isolate = substrings_to_isolate[0] - all_substrings = list(it.chain(*list(zip( - full_string.split(substring_to_isolate), - it.repeat(substring_to_isolate) - )))) - all_substrings.pop(-1) - all_substrings = [s for s in all_substrings if s != ""] - return split_string_list_to_isolate_substrings( - all_substrings, *substrings_to_isolate[1:] - ) - - -def split_string_list_to_isolate_substrings(string_list, *substrings_to_isolate): - """ - Similar to split_string_to_isolate_substrings, but the first argument - is a list of strings, thought of as something already broken up a bit. - """ - return list(it.chain(*[ - split_string_to_isolate_substrings(s, *substrings_to_isolate) - for s in string_list - ])) + pattern = "|".join(*( + "({})".format(re.escape(ss)) + for ss in isolate + )) + pieces = re.split(pattern, full_string) + return list(filter(lambda s: s, pieces)) diff --git a/manimlib/utils/tex_file_writing.py b/manimlib/utils/tex_file_writing.py index 5df24de3..4d76d300 100644 --- a/manimlib/utils/tex_file_writing.py +++ b/manimlib/utils/tex_file_writing.py @@ -1,70 +1,102 @@ +import logging +import sys import os import hashlib +from contextlib import contextmanager -from manimlib.constants import TEX_TEXT_TO_REPLACE -from manimlib.constants import TEX_USE_CTEX -import manimlib.constants as consts +from manimlib.utils.directories import get_tex_dir +from manimlib.config import get_manim_dir +from manimlib.config import get_custom_config -def tex_hash(expression, template_tex_file_body): - id_str = str(expression + template_tex_file_body) - hasher = hashlib.sha256() - hasher.update(id_str.encode()) +SAVED_TEX_CONFIG = {} + + +def get_tex_config(): + """ + Returns a dict which should look something like this: + { + "executable": "latex", + "template_file": "tex_template.tex", + "intermediate_filetype": "dvi", + "text_to_replace": "YourTextHere", + "tex_body": "..." + } + """ + # Only load once, then save thereafter + if not SAVED_TEX_CONFIG: + custom_config = get_custom_config() + SAVED_TEX_CONFIG.update(custom_config["tex"]) + # Read in template file + template_filename = os.path.join( + get_manim_dir(), "manimlib", "tex_templates", + SAVED_TEX_CONFIG["template_file"], + ) + with open(template_filename, "r") as file: + SAVED_TEX_CONFIG["tex_body"] = file.read() + return SAVED_TEX_CONFIG + + +def tex_hash(tex_file_content): # Truncating at 16 bytes for cleanliness + hasher = hashlib.sha256(tex_file_content.encode()) return hasher.hexdigest()[:16] -def tex_to_svg_file(expression, template_tex_file_body): - tex_file = generate_tex_file(expression, template_tex_file_body) - dvi_file = tex_to_dvi(tex_file) - return dvi_to_svg(dvi_file) +def tex_to_svg_file(tex_file_content): + svg_file = os.path.join( + get_tex_dir(), tex_hash(tex_file_content) + ".svg" + ) + if not os.path.exists(svg_file): + # If svg doesn't exist, create it + tex_to_svg(tex_file_content, svg_file) + return svg_file -def generate_tex_file(expression, template_tex_file_body): - result = os.path.join( - consts.TEX_DIR, - tex_hash(expression, template_tex_file_body) - ) + ".tex" - if not os.path.exists(result): - print("Writing \"%s\" to %s" % ( - "".join(expression), result - )) - new_body = template_tex_file_body.replace( - TEX_TEXT_TO_REPLACE, expression - ) - with open(result, "w", encoding="utf-8") as outfile: - outfile.write(new_body) - return result +def tex_to_svg(tex_file_content, svg_file): + tex_file = svg_file.replace(".svg", ".tex") + with open(tex_file, "w", encoding="utf-8") as outfile: + outfile.write(tex_file_content) + svg_file = dvi_to_svg(tex_to_dvi(tex_file)) + + # Cleanup superfluous documents + tex_dir, name = os.path.split(svg_file) + stem, end = name.split(".") + for file in filter(lambda s: s.startswith(stem), os.listdir(tex_dir)): + if not file.endswith(end): + os.remove(os.path.join(tex_dir, file)) + + return svg_file def tex_to_dvi(tex_file): - result = tex_file.replace(".tex", ".dvi" if not TEX_USE_CTEX else ".xdv") + tex_config = get_tex_config() + program = tex_config["executable"] + file_type = tex_config["intermediate_filetype"] + result = tex_file.replace(".tex", "." + file_type) if not os.path.exists(result): commands = [ - "latex", + program, "-interaction=batchmode", "-halt-on-error", - "-output-directory=\"{}\"".format(consts.TEX_DIR), - "\"{}\"".format(tex_file), - ">", - os.devnull - ] if not TEX_USE_CTEX else [ - "xelatex", - "-no-pdf", - "-interaction=batchmode", - "-halt-on-error", - "-output-directory=\"{}\"".format(consts.TEX_DIR), - "\"{}\"".format(tex_file), + f"-output-directory=\"{os.path.dirname(tex_file)}\"", + f"\"{tex_file}\"", ">", os.devnull ] exit_code = os.system(" ".join(commands)) if exit_code != 0: log_file = tex_file.replace(".tex", ".log") - raise Exception( - ("Latex error converting to dvi. " if not TEX_USE_CTEX - else "Xelatex error converting to xdv. ") + - "See log output above or the log file: %s" % log_file) + logging.log( + logging.ERROR, + "\n\n LaTeX Error! Not a worry, it happens to the best of us.\n" + ) + with open(log_file, "r") as file: + for line in file.readlines(): + if line.startswith("!"): + print(line[1:]) + logging.log(logging.INFO, line) + sys.exit(2) return result @@ -75,7 +107,8 @@ def dvi_to_svg(dvi_file, regen_if_exists=False): Returns a list of PIL Image objects for these images sorted as they where in the dvi """ - result = dvi_file.replace(".dvi" if not TEX_USE_CTEX else ".xdv", ".svg") + file_type = get_tex_config()["intermediate_filetype"] + result = dvi_file.replace("." + file_type, ".svg") if not os.path.exists(result): commands = [ "dvisvgm", @@ -90,3 +123,15 @@ def dvi_to_svg(dvi_file, regen_if_exists=False): ] os.system(" ".join(commands)) return result + + +# TODO, perhaps this should live elsewhere +@contextmanager +def display_during_execution(message): + # Only show top line + to_print = message.split("\n")[0] + try: + print(to_print, end="\r") + yield + finally: + print(" " * len(to_print), end="\r") diff --git a/manimlib/window.py b/manimlib/window.py index d39d2d7f..b6dc40db 100644 --- a/manimlib/window.py +++ b/manimlib/window.py @@ -1,34 +1,62 @@ import moderngl_window as mglw from moderngl_window.context.pyglet.window import Window as PygletWindow from moderngl_window.timers.clock import Timer +from screeninfo import get_monitors -from manimlib.constants import DEFAULT_PIXEL_WIDTH -from manimlib.constants import DEFAULT_PIXEL_HEIGHT from manimlib.utils.config_ops import digest_config +from manimlib.utils.customization import get_customization class Window(PygletWindow): - size = (DEFAULT_PIXEL_WIDTH, DEFAULT_PIXEL_HEIGHT) fullscreen = False resizable = True gl_version = (3, 3) vsync = True - samples = 1 cursor = True - def __init__(self, scene, **kwargs): + def __init__(self, scene, size=(1280, 720), **kwargs): + super().__init__() digest_config(self, kwargs) - super().__init__(**kwargs) + self.scene = scene + self.pressed_keys = set() self.title = str(scene) - # Put at the top of the screen - self.position = (self.position[0], 0) + self.size = size mglw.activate_context(window=self) self.timer = Timer() self.config = mglw.WindowConfig(ctx=self.ctx, wnd=self, timer=self.timer) self.timer.start() + # No idea why, but when self.position is set once + # it sometimes doesn't actually change the position + # to the specified tuple on the rhs, but doing it + # twice seems to make it work. ¯\_(ツ)_/¯ + initial_position = self.find_initial_position(size) + self.position = initial_position + self.position = initial_position + + def find_initial_position(self, size): + custom_position = get_customization()["window_position"] + monitors = get_monitors() + mon_index = get_customization()["window_monitor"] + monitor = monitors[min(mon_index, len(monitors) - 1)] + window_width, window_height = size + # Position might be specified with a string of the form + # x,y for integers x and y + if "," in custom_position: + return tuple(map(int, custom_position.split(","))) + + # Alternatively, it might be specified with a string like + # UR, OO, DL, etc. specifiying what corner it should go to + char_to_n = {"L": 0, "U": 0, "O": 1, "R": 2, "D": 2} + width_diff = monitor.width - window_width + height_diff = monitor.height - window_height + return ( + monitor.x + char_to_n[custom_position[1]] * width_diff // 2, + -monitor.y + char_to_n[custom_position[0]] * height_diff // 2, + ) + # Delegate event handling to scene def pixel_coords_to_space_coords(self, px, py, relative=False): return self.scene.camera.pixel_coords_to_space_coords(px, py, relative) @@ -61,14 +89,16 @@ class Window(PygletWindow): offset = self.pixel_coords_to_space_coords(x_offset, y_offset, relative=True) self.scene.on_mouse_scroll(point, offset) - def on_key_release(self, symbol, modifiers): - super().on_key_release(symbol, modifiers) - self.scene.on_key_release(symbol, modifiers) - def on_key_press(self, symbol, modifiers): + self.pressed_keys.add(symbol) # Modifiers? super().on_key_press(symbol, modifiers) self.scene.on_key_press(symbol, modifiers) + def on_key_release(self, symbol, modifiers): + self.pressed_keys.difference_update({symbol}) # Modifiers? + super().on_key_release(symbol, modifiers) + self.scene.on_key_release(symbol, modifiers) + def on_resize(self, width: int, height: int): super().on_resize(width, height) self.scene.on_resize(width, height) @@ -84,3 +114,6 @@ class Window(PygletWindow): def on_close(self): super().on_close() self.scene.on_close() + + def is_key_pressed(self, symbol): + return (symbol in self.pressed_keys) diff --git a/requirements.txt b/requirements.txt index 4a41e5d1..c5f049a0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,19 @@ -argparse==1.4.0 -colour==0.1.5 -numpy==1.16.4 -Pillow==5.2.0 -progressbar==2.5 -scipy==1.3.0 -tqdm==4.24.0 -opencv-python==3.4.2.17 -pycairo==1.17.1; sys_platform == 'linux' -pycairo>=1.18.1; sys_platform == 'win32' -pydub==0.23.0 -pyreadline==2.1; sys_platform == 'win32' +argparse +colour +numpy +Pillow +scipy +sympy +tqdm +mapbox-earcut +matplotlib +moderngl +moderngl_window +pydub +pyyaml +screeninfo +pyreadline; sys_platform == 'win32' +validators +ipython +PyOpenGL +manimpango>=0.2.0,<0.3.0' diff --git a/setup.cfg b/setup.cfg index 8e6a9f37..01df3b6f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -name = manimlib +name = manimgl author = Grant Sanderson author-email= grant@3blue1brown.com summary = Animation engine for explanatory math videos @@ -8,13 +8,15 @@ description-content-type = text/markdown; charset=UTF-8 home-page = https://github.com/3b1b/manim project_urls = Bug Tracker = https://github.com/3b1b/manim/issues - Documentation = https://eulertour.com/learn/manim + Documentation = https://3b1b.github.io/manim/ Source Code = https://github.com/3b1b/manim license = MIT [files] packages = manimlib +extra_files = requirements.txt [entry_points] console_scripts = - manim = manimlib:main + manimgl = manimlib.__main__:main + manim-render = manimlib.__main__:main diff --git a/stage_scenes.py b/stage_scenes.py deleted file mode 100644 index 7d32712a..00000000 --- a/stage_scenes.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python -import inspect -import os -import sys -import importlib - -import manimlib.constants as consts -from manimlib.constants import PRODUCTION_QUALITY_CAMERA_CONFIG -from manimlib.config import get_module -from manimlib.extract_scene import is_child_scene - - -def get_sorted_scene_classes(module_name): - module = get_module(module_name) - if hasattr(module, "SCENES_IN_ORDER"): - return module.SCENES_IN_ORDER - # Otherwise, deduce from the order in which - # they're defined in a file - importlib.import_module(module.__name__) - line_to_scene = {} - name_scene_list = inspect.getmembers( - module, - lambda obj: is_child_scene(obj, module) - ) - for name, scene_class in name_scene_list: - if inspect.getmodule(scene_class).__name__ != module.__name__: - continue - lines, line_no = inspect.getsourcelines(scene_class) - line_to_scene[line_no] = scene_class - return [ - line_to_scene[index] - for index in sorted(line_to_scene.keys()) - ] - - -def stage_scenes(module_name): - scene_classes = get_sorted_scene_classes(module_name) - if len(scene_classes) == 0: - print("There are no rendered animations from this module") - return - # output_directory_kwargs = { - # "camera_config": PRODUCTION_QUALITY_CAMERA_CONFIG, - # } - # TODO, fix this - animation_dir = os.path.join( - os.path.expanduser('~'), - "Dropbox (3Blue1Brown)/3Blue1Brown Team Folder/videos", - "bayes/beta2", "1440p60" - ) - # - files = os.listdir(animation_dir) - sorted_files = [] - for scene_class in scene_classes: - scene_name = scene_class.__name__ - clips = [f for f in files if f.startswith(scene_name + ".")] - for clip in clips: - sorted_files.append(os.path.join(animation_dir, clip)) - # Partial movie file directory - # movie_dir = get_movie_output_directory( - # scene_class, **output_directory_kwargs - # ) - # if os.path.exists(movie_dir): - # for extension in [".mov", ".mp4"]: - # int_files = get_sorted_integer_files( - # pmf_dir, extension=extension - # ) - # for file in int_files: - # sorted_files.append(os.path.join(pmf_dir, file)) - # else: - - # animation_subdir = os.path.dirname(animation_dir) - count = 0 - while True: - staged_scenes_dir = os.path.join( - animation_dir, - os.pardir, - "staged_scenes_{}".format(count) - ) - if not os.path.exists(staged_scenes_dir): - os.makedirs(staged_scenes_dir) - break - # Otherwise, keep trying new names until - # there is a free one - count += 1 - for count, f in reversed(list(enumerate(sorted_files))): - # Going in reversed order means that when finder - # sorts by date modified, it shows up in the - # correct order - symlink_name = os.path.join( - staged_scenes_dir, - "Scene_{:03}_{}".format( - count, f.split(os.sep)[-1] - ) - ) - os.symlink(f, symlink_name) - - -if __name__ == "__main__": - if len(sys.argv) < 2: - raise Exception("No module given.") - module_name = sys.argv[1] - stage_scenes(module_name) diff --git a/travis/build_docs.sh b/travis/build_docs.sh deleted file mode 100755 index 09952375..00000000 --- a/travis/build_docs.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -pip install sphinx_rtd_theme -make --directory docs/ html -openssl aes-256-cbc -K $encrypted_1b28e850a424_key \ - -iv $encrypted_1b28e850a424_iv \ - -in travis/crypt.enc \ - -out travis/crypt -d -tar xf travis/crypt -travis/deploy_docs.sh diff --git a/travis/crypt.enc b/travis/crypt.enc deleted file mode 100644 index 00fc7025..00000000 Binary files a/travis/crypt.enc and /dev/null differ